[
  {
    "path": ".drone.yml",
    "content": "workspace:\n  base: /srv\n  path: caliopen\n\nclone:\n  git:\n    image: plugins/git\n    depth: 10\n    recursive: false\n\npipeline:\n\n##################################\n##          BASE IMAGES         ##\n##################################\n\n  build-caliopen-go:\n    group: build0\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY]\n    environment:\n      - DEPS=vendor\n      - BASE_DIR=src/backend\n      - PLUGIN_DOCKERFILE=src/backend/Dockerfile.caliopen-go\n      - PLUGIN_CONTEXT=/srv/caliopen/src/backend\n      - PLUGIN_REPO=registry.caliopen.org/caliopen_go\n    when:\n      branch: [ develop, master ]\n      event: [ push ]\n    commands:\n      - . devtools/drone/files_changed.sh\n      - . devtools/drone/build_images.sh\n\n#######################################################\n##          TEST ON MASTER OR DEVELOP PR             ##\n#######################################################\n\n  test-go:\n    group: test\n    pull: true\n    image: public-registry.caliopen.org/caliopen_go\n    environment:\n      - BASE_DIR=/go/src/github.com/CaliOpen/Caliopen/src/backend\n    when:\n      branch: [ develop ]\n      event: [ pull_request ]\n    commands:\n      - cp -r /srv/caliopen/src/backend/* $${BASE_DIR}\n      - cd $${BASE_DIR} && govendor sync -v\n      - go test ./...\n\n  test-py:\n    pull: true\n    group: test\n    image: public-registry.caliopen.org/caliopen_python\n    environment:\n      - CALIOPEN_BASEDIR=/srv/caliopen\n      - BASE_DIR=src/backend\n    when:\n      branch: [ develop, master ]\n      event: [ pull_request ]\n    commands:\n      - . devtools/drone/files_changed.sh\n      - . devtools/drone/test_py.sh\n\n  test-frontend:\n    group: test\n    image: node:16\n    when:\n      branch: [ develop, master ]\n      event: [ pull_request ]\n    environment:\n      - BASE_DIR=src/frontend\n    commands:\n      - . devtools/drone/files_changed.sh\n      - . devtools/drone/test_front.sh\n\n#########################################################\n##          BUILD AND PUBLISH ON DEVELOP PUSH          ##\n#########################################################\n\n  # Python develop images\n  build-py-cli-develop:\n    group: build1\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY]\n    environment:\n      - PLUGIN_DOCKERFILE=src/backend/Dockerfile.cli\n      - PLUGIN_CONTEXT=/srv/caliopen/src/backend\n      - PLUGIN_REPO=registry.caliopen.org/caliopen_cli\n      - BASE_DIR=src/backend\n      - LANG=python\n      - PROG=tools/py.CLI\n    when:\n      branch: [ develop ]\n      event: [ push ]\n    commands:\n      - export PLUGIN_TAGS=develop\n      - . devtools/drone/get_py_dependencies.sh # Get the list of dependencies\n      - . devtools/drone/files_changed.sh # Check if any file has been modified\n      - . devtools/drone/build_images.sh # Build docker image\n\n  build-apiv1-develop:\n    group: build1\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY]\n    environment:\n      - PLUGIN_DOCKERFILE=src/backend/Dockerfile.py-api\n      - PLUGIN_CONTEXT=/srv/caliopen/src/backend\n      - PLUGIN_REPO=registry.caliopen.org/caliopen_apiv1\n      - BASE_DIR=src/backend\n      - LANG=python\n      - PROG=interfaces/REST/py.server\n    when:\n      branch: [ develop ]\n      event: [ push ]\n    commands:\n      - export PLUGIN_TAGS=develop\n      - . devtools/drone/get_py_dependencies.sh # Get the list of dependencies\n      - . devtools/drone/files_changed.sh # Check if any file has been modified\n      - . devtools/drone/build_images.sh # Build docker image\n\n  build-mq-worker-develop:\n    group: build1\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY]\n    environment:\n      - PLUGIN_DOCKERFILE=src/backend/Dockerfile.mq-worker\n      - PLUGIN_CONTEXT=/srv/caliopen/src/backend\n      - PLUGIN_REPO=registry.caliopen.org/caliopen_mqworker\n      - BASE_DIR=src/backend\n      - LANG=python\n      - PROG=interfaces/NATS/py.client\n    when:\n      branch: [ develop ]\n      event: [ push ]\n    commands:\n      - export PLUGIN_TAGS=develop\n      - . devtools/drone/get_py_dependencies.sh # Get the list of dependencies\n      - . devtools/drone/files_changed.sh # Check if any file has been modified\n      - . devtools/drone/build_images.sh # Build docker image\n\n  ## GO develop images\n  build-apiv2-develop:\n    group: build2\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY]\n    environment:\n      - PLUGIN_DOCKERFILE=src/backend/Dockerfile.go-api\n      - PLUGIN_CONTEXT=/srv/caliopen/src/backend\n      - PLUGIN_REPO=registry.caliopen.org/caliopen_apiv2\n      - PROG=interfaces/REST/go.server/cmd/caliopen_rest\n      - BASE_DIR=src/backend\n      - LANG=go\n    when:\n      branch: [ develop ]\n      event: [ push ]\n    commands:\n      - export PLUGIN_TAGS=develop\n      - . devtools/drone/get_go_dependencies.sh\n      - . devtools/drone/files_changed.sh\n      - . devtools/drone/build_images.sh\n\n  build-lmtpd-develop:\n    group: build2\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY]\n    environment:\n      - PLUGIN_DOCKERFILE=src/backend/Dockerfile.go-lmtp\n      - PLUGIN_CONTEXT=/srv/caliopen/src/backend\n      - PLUGIN_REPO=registry.caliopen.org/caliopen_lmtpd\n      - PROG=protocols/go.smtp/cmd/caliopen_lmtpd\n      - BASE_DIR=src/backend\n      - LANG=go\n    when:\n      branch: [ develop ]\n      event: [ push ]\n    commands:\n      - export PLUGIN_TAGS=develop\n      - . devtools/drone/get_go_dependencies.sh\n      - . devtools/drone/files_changed.sh\n      - . devtools/drone/build_images.sh\n\n  build-idpoller-develop:\n    group: build2\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY]\n    environment:\n      - PLUGIN_DOCKERFILE=src/backend/Dockerfile.identity-poller\n      - PLUGIN_CONTEXT=/srv/caliopen/src/backend\n      - PLUGIN_REPO=registry.caliopen.org/caliopen_identitypoller\n      - PROG=workers/go.remoteIDs/cmd/idpoller\n      - BASE_DIR=src/backend\n      - LANG=go\n    when:\n      branch: [ develop ]\n      event: [ push ]\n    commands:\n      - export PLUGIN_TAGS=develop\n      - . devtools/drone/get_go_dependencies.sh\n      - . devtools/drone/files_changed.sh\n      - . devtools/drone/build_images.sh\n\n  build-imapworker-develop:\n    group: build2\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY]\n    environment:\n      - PLUGIN_DOCKERFILE=src/backend/Dockerfile.imap-worker\n      - PLUGIN_CONTEXT=/srv/caliopen/src/backend\n      - PLUGIN_REPO=registry.caliopen.org/caliopen_imapworker\n      - PROG=protocols/go.imap/cmd/imapworker\n      - BASE_DIR=src/backend\n      - LANG=go\n    when:\n      branch: [ develop ]\n      event: [ push ]\n    commands:\n      - export PLUGIN_TAGS=develop\n      - . devtools/drone/get_go_dependencies.sh\n      - . devtools/drone/files_changed.sh\n      - . devtools/drone/build_images.sh\n\n  build-twitterworker-develop:\n    group: build2\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY]\n    environment:\n    - PLUGIN_DOCKERFILE=src/backend/Dockerfile.twitter-worker\n    - PLUGIN_CONTEXT=/srv/caliopen/src/backend\n    - PLUGIN_REPO=registry.caliopen.org/caliopen_twitterworker\n    - PROG=protocols/go.twitter/cmd/twitterworker\n    - BASE_DIR=src/backend\n    - LANG=go\n    when:\n      branch: [ develop ]\n      event: [ push ]\n    commands:\n    - export PLUGIN_TAGS=develop\n    - . devtools/drone/get_go_dependencies.sh\n    - . devtools/drone/files_changed.sh\n    - . devtools/drone/build_images.sh\n\n  build-frontend-develop:\n    group: build3\n    image: public-registry.caliopen.org/caliopen_drone_docker\n    privileged: true\n    secrets: [ DOCKER_USERNAME , DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    environment:\n      - PLUGIN_DOCKERFILE=src/frontend/web_application/Dockerfile\n      - PLUGIN_CONTEXT=/srv/caliopen/src/frontend/web_application\n      - PLUGIN_REPO=registry.caliopen.org/caliopen_frontend\n      - BASE_DIR=src/frontend/web_application\n      - LANG=js\n    when:\n      branch: [ develop ]\n      event: [ push ]\n    commands:\n      - export PLUGIN_TAGS=develop\n      - . devtools/drone/files_changed.sh\n      - . devtools/drone/build_images.sh\n\n#########################################################\n##          BUILD AND PUBLISH ON TAG: RELEASE-         ##\n#########################################################\n\n  build-py-cli-release:\n    group: release1\n    image: plugins/docker\n    dockerfile: src/backend/Dockerfile.cli\n    context: /srv/caliopen/src/backend\n    repo: registry.caliopen.org/caliopen_cli\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    when:\n      ref: [ \"refs/tags/release-*\" ]\n      event: [ tag ]\n    tags:\n      - latest\n      - ${DRONE_TAG##release-}\n\n  build-apiv1-release:\n    group: release1\n    image: plugins/docker\n    dockerfile: src/backend/Dockerfile.py-api\n    context: /srv/caliopen/src/backend\n    repo: registry.caliopen.org/caliopen_apiv1\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    when:\n      ref: [ \"refs/tags/release-*\" ]\n      event: [ tag ]\n    tags:\n      - latest\n      - ${DRONE_TAG##release-}\n\n  build-mq-worker-release:\n    group: release1\n    image: plugins/docker\n    dockerfile: src/backend/Dockerfile.mq-worker\n    context: /srv/caliopen/src/backend\n    repo: registry.caliopen.org/caliopen_mqworker\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    when:\n      ref: [ \"refs/tags/release-*\" ]\n      event: [ tag ]\n    tags:\n      - latest\n      - ${DRONE_TAG##release-}\n\n  build-apiv2-release:\n    group: release2\n    image: plugins/docker\n    dockerfile: src/backend/Dockerfile.go-api\n    context: /srv/caliopen/src/backend\n    repo: registry.caliopen.org/caliopen_apiv2\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    when:\n      ref: [ \"refs/tags/release-*\" ]\n      event: [ tag ]\n    tags:\n      - latest\n      - ${DRONE_TAG##release-}\n\n  build-lmtpd-release:\n    group: release2\n    image: plugins/docker\n    dockerfile: src/backend/Dockerfile.go-lmtp\n    context: /srv/caliopen/src/backend\n    repo: registry.caliopen.org/caliopen_lmtpd\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    when:\n      ref: [ \"refs/tags/release-*\" ]\n      event: [ tag ]\n    tags:\n      - latest\n      - ${DRONE_TAG##release-}\n\n  build-idpoller-release:\n    group: release2\n    image: plugins/docker\n    dockerfile: src/backend/Dockerfile.identity-poller\n    context: /srv/caliopen/src/backend\n    repo: registry.caliopen.org/caliopen_identitypoller\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    when:\n      ref: [ \"refs/tags/release-*\" ]\n      event: [ tag ]\n    tags:\n      - latest\n      - ${DRONE_TAG##release-}\n\n  build-imapworker-release:\n    group: release2\n    image: plugins/docker\n    dockerfile: src/backend/Dockerfile.imap-worker\n    context: /srv/caliopen/src/backend\n    repo: registry.caliopen.org/caliopen_imapworker\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    when:\n      ref: [ \"refs/tags/release-*\" ]\n      event: [ tag ]\n    tags:\n      - latest\n      - ${DRONE_TAG##release-}\n\n  build-twitterworker-release:\n    group: release2\n    image: plugins/docker\n    dockerfile: src/backend/Dockerfile.twitter-worker\n    context: /srv/caliopen/src/backend\n    repo: registry.caliopen.org/caliopen_twitterworker\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    when:\n      ref: [ \"refs/tags/release-*\" ]\n      event: [ tag ]\n    tags:\n    - latest\n    - ${DRONE_TAG##release-}\n\n  build-frontend-release:\n    group: release3\n    image: plugins/docker\n    dockerfile: src/frontend/web_application/Dockerfile\n    context: /srv/caliopen/src/frontend/web_application\n    repo: registry.caliopen.org/caliopen_frontend\n    secrets: [ DOCKER_USERNAME, DOCKER_PASSWORD, DOCKER_REGISTRY ]\n    when:\n      ref: [ \"refs/tags/release-*\" ]\n      event: [ tag ]\n    tags:\n      - latest\n      - ${DRONE_TAG##release-}\n\n#########################################################\n##                     SERVICES                        ##\n#########################################################\n"
  },
  {
    "path": ".git-crypt/.gitattributes",
    "content": "# Do not edit this file.  To specify the files to encrypt, create your own\n# .gitattributes file in the directory where your files are.\n* !filter !diff\n*.gpg binary\n"
  },
  {
    "path": ".gitattributes",
    "content": "devtools/kubernetes/secrets/*.yaml filter=git-crypt diff=git-crypt\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by .ignore support plugin (hsz.mobi)\n### JetBrains template\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff:\n.idea/workspace.xml\n.idea/tasks.xml\n.idea/dictionaries\n.idea/vcs.xml\n.idea/jsLibraryMappings.xml\n\n# Sensitive or high-churn files:\n.idea/dataSources.ids\n.idea/dataSources.xml\n.idea/dataSources.local.xml\n.idea/sqlDataSources.xml\n.idea/dynamic.xml\n.idea/uiDesigner.xml\n\n# Gradle:\n.idea/gradle.xml\n.idea/libraries\n\n# Mongo Explorer plugin:\n.idea/mongoSettings.xml\n\n## File-based project format:\n*.iws\n\n## Plugin-specific files:\n\n# IntelliJ\n/out/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n### VirtualEnv template\n# Virtualenv\n# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/\n.Python\n[Ii]nclude\n[Ll]ib64\n[Ll]ocal\n[Ss]cripts\npyvenv.cfg\n.venv\npip-selfcheck.json\n### Python template\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*,cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# IPython Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# dotenv\n.env\n\n# virtualenv\nvenv/\nENV/\n\n# Spyder project settings\n.spyderproject\n\n# Rope project settings\n.ropeproject\n/.idea/\n*.iml\n\n# vagrant directory\n.vagrant\n\n# Docker data\n.data\n\n# Golang\nsrc/backend/**/vendor/**/\n\n# Node\nnode_modules\n\n# private fixtures\n/devtools/email-with-parts-and-attachments\n\n# registry configuration\n/devtools/registry.conf\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: required\nservices:\n- docker\nenv:\n  COMPOSE_VERSION: 1.9.0\nlanguage: node_js\nnode_js:\n- '8'\ncache:\n  - yarn\ngit:\n  depth: 10\nbefore_install:\n- curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname\n  -s`-`uname -m` > docker-compose\n- chmod +x docker-compose\n- sudo mv docker-compose /usr/local/bin\n- curl -o- -L https://yarnpkg.com/install.sh | bash\n- export PATH=\"$HOME/.yarn/bin:$PATH\"\nbefore_script:\n  - git fetch origin $TRAVIS_BRANCH:$TRAVIS_BRANCH\nscript:\n- devtools/run-tests.sh\nnotifications:\n  irc:\n    channels:\n      - \"chat.freenode.net#caliopdev\"\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)\nand this project adheres to\n[Semantic Versioning](http://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n## [0.27.1] 2023-05-09\n\n### Fixed\n\n- JS error when saving settings.\n\n## [0.27.0] 2023-05-05\n\n### Changed\n\n- CLI commands `resync_shard` and `resync_index` does not stop when an error\n  occured.\n\n## [0.26.1] 2023-05-03\n\n### Fixed\n\n- Empty discussion list.\n\n## [0.26.0] 2023-04-04\n\n### Changed\n\n- Replace PI message illustration by a PI score (A to E).\n\n## [0.25.3] 2023-02-28\n\n### Fixed\n\n- Improve device management frontend codebase\n\n## [0.25.2] 2023-01-25\n\n### Fixed\n\n- Handle username availability errors on the client.\n- Remove unexpected errors on login page on first display.\n\n## [0.25.1] 2023-01-12\n\n### Fixed\n\n- Update client's dependencies.\n- Load locale at run time on the client.\n- Small improvements in tag managements & Contact book.\n\n## [0.25.0] 2022-03-03\n\n### Change\n\n- Improve contact edit form. Better error handling.\n\n## [0.24.6] 2021-10-23\n\n### Fixed\n\n- Unable to to delete account.\n\n## [0.24.5] 2021-08-20\n\n### Fixed\n\n- Crash after signup, use a subquery instead.\n\n### Changed\n\n- Redirect to external account configuration after signup.\n\n## [0.24.4] 2021-07-25\n\n### Fixed\n\n- Advanced draft suggestions correctly displayed.\n- Color of signin & signup spinner button set to bright.\n\n## [0.24.3] 2021-06-14\n\n### Fixed\n\n- Unable to quick reply to a mastodon or tweeter dm\n\n## [0.24.2] 2021-06-11\n\n### Fixed\n\n- client BSOD: Make sure user is loaded before accessing to the contact.\n\n## [0.24.1] 2021-06-09\n\n### Fixed\n\n- tag management\n- login form\n- Mastodon DM handling\n\n## [0.24.0] 2021-03-02\n\n### Changed\n\n- re-enable Take a Tour.\n- update internal frontend dependencies.\n- Edit advanced draft in its specific page.\n- update dependencies\n\n### Fixed\n\n- Openpgp form when only one identity.\n\n## [0.23.1] 2019-09-17\n\n### Fixed\n\n- No feedbacks were displayed when errors happend on signin form.\n- No spinner on activity on signin form.\n\n## [0.23] 2019-07-19\n\n### Added\n\n- Mastodon protocol\n- Tag email with imap flags when fetching external account\n\n### Changed\n\n- Better Timeline display for large and small screens.\n- Batch notifications\n\n### Fixed\n\n- bugs in imap worker\n- bugs in some responsiveness UI components\n- username validation\n\n## [0.22] 2019-06-26\n\n## Added\n\n- The new about landpage.\n\n### Changed\n\n- Upload PGP keys using files rather than textboxes.\n- Notifications : output bach operations (external identities fetches for ex.)\n  into one notification\n\n### Fixed\n\n- Double redirections algorithm client and server when not authenticated\n- Prevent target blank anchor's xss.\n- Reload devices when changing page or when invalidated.\n- Page rendering after signout with unexpected content cached.\n- username validation to conform more tightly to specifications.\n\n## [0.21] 2019-06-06\n\n### Added\n\n- Always link participant to contact in a draft when contact exists (ensure\n  message can be encrypted)\n- Validate body cannot be empty for a Twitter DM.\n- Upload user's public key when adding a private key.\n- Discussion has unread message button.\n- Dynamic contacts references embedded in messages\n\n### Changed\n\n- FTS and participants lookup improvments\n- On quick reply, «Enter» will not send draft anymore, it must be CTRL+Enter.\n- Quick reply is now multilines.\n- Use message's excerpt in search results instead of garbled highlights\n- Move vcard file import route on apiv2 to use contact uniqueness and lookups\n  principles\n- Re-enable import contacts via vcard file.\n- New messages notification is not displayed anymore, it is now automatically\n  loaded.\n- Send button icon in advanced form.\n- Disable «Take a tour» which is not working properly\n- Disable the send button on quick draft when empty (thanks Sebbaz).\n- Look and feel of GPG buttons which look like disabled.\n\n### Fixed\n\n- Default locale saved on account creation.\n- Responsiveness for the «new device page».\n- BSOD while including regexp special chars in search query.\n- Email icon was always on Timeline.\n- Contact starting w/ a letter with a diacritic is not displayed on ContactBook.\n- Contacts list update after contact deletion\n- Twitter workers errors handling\n- Imap workers errors handling\n- Messages' excerpts showing html tags\n- BSOD when changing identity in draft when no recipients (thanks peha)\n- Many encoding issues during mail delivery\n- Participants algorithm edge case\n- Discussion last message sort better\n- Index user contact without alias using a workaround to bad core/mixin classes\n  design\n- Addresses emails parsing failed sometimes with strange values\n- Process better invalid or missing data and encoding problems in incoming\n  message\n- Signout wasn't effective\n\n## [0.20.0] 2019-05-15\n\n### Added\n\n- Validation of twitter username in contact edition\n- Address (and protocol) selection in a 1-to-1 discussion\n- Activate links in plain text messages.\n- Handle client crashes and provide a link to report an issue on\n  https://feedback.caliopen.org\n\n### Changed\n\n- Disable draft form in case there is no selected identity\n- Facebook username is no more available in contact edition\n- In a draft, switch identity will change the protocol of all recipients as well\n- Disable contact import\n- Do not display private key details, allow \"download\" instead.\n- Providers buttons are available according to backend configuration (api:\n  `/api/v2/providers`)\n\n### Fixed\n\n- Simple detection of PGP inline message\n- Apiv2 create and delete contacts does not use `user.shard_id`\n- Better logging for apiv2 and mq-worker\n- Do not fail if ContactLookup raise a NotFound\n- Twitter nick not displayed on contact book\n- Select Twitter identity according to parent message on a new draft\n- Show at least 1 participant per discussion on timeline\n- Send a quick draft by pressing «Enter» and display a spinner\n\n## [0.18.2] 2019-04-26\n\n### Fixed\n\n- signup does not authenticate and crash\n- hardcoded references to \"alpha.caliopen.org\" in client\n\n## [0.18.1] 2019-04-26\n\n### Changed\n\n- Better display of message's participants\n- Fix attachment visibility\n- Raise an explicit error on duplicate message for better processing\n- Validate better email address when cleaning it\n- Better oauth token validation\n\n### Fixed\n\n- Attachments visibility, it displays a warning if the message has been\n  encrypted\n\n## [0.18.0] 2019-04-23\n\n### Fixed\n\n- render form after draft deletion\n- Reply encrypted messages\n- Hide \"load\" more button when all messages are displayed\n- Display decryption error message\n- Glitches on encrypted mails\n- Display facebook names on contact book\n\n### Added\n\n- Send and receive text/plain MIME messages\n- Show full date on Hover\n- Suggest Twitter handles in draft form\n\n## [0.17.0] 2019-03-21\n\n### Added\n\n- End to end PGP encryption/decryption.\n- When user adds an external account, external identity is added to user's\n  contact card\n- Test for imap worker, twitter worker and identities worker\n- Actions for instant messages (delete, reply …) and tag list where missing\n- PWA: add to home screen\n- PWA: basic offline capabilities\n- Search when click on tags (basic search)\n- Verify device by mail\n\n### Fixed\n\n- Fix chronological order of messages in discussion scene\n- Better responsiveness on small screens for timeline & discussion & logo\n- Better responsiveness on small screens for dropdowns\n- inversion en/fr for some translations\n- BSOD on draft view in case there is no author (for example after remote\n  identity deletion)\n- Last messages not visible in case the discussion has been openned\n- Sync contact associated to the user when editing in the contact book\n- Accept only Private Key in user account\n\n### Changed\n\n- Remove device's locations field (IP definition) that wasn't saved and has no\n  effects for now\n\n## [0.16.0] 2019-02-25\n\n### Added\n\n- Handle lost authentication, redirect signin\n- A simple view with draft messages\n- Display the discussion related to the selected participants of a draft\n- Compute an experimental different PI structure for message entity\n- A caliopen_data python package for caliopen data manipulation\n- A machine learning model for message automatic tagging\n- Create an RFC 3156 compatible mime structure for PGP encrypted sent email\n- Add an API route to find if a discussion exist for a list of participants\n- Ignore already imported message\n\n### Changed\n\n- Move link external accounts to user menu\n- Smaller font for desktop\n- Use white color for plain text buttons\n- Refactor hover and active colors for buttons\n- Display a progress bar when downloading an attachment\n- New calcul for Privacy Index\n- Take A Tour has been moved in the new menu «Help & Info»\n- Display related emails for a PGP public key\n- Compute related discussion only when the message is sent\n- Better discussion match if any participant is a known contact\n- Reworked deeply job dispatching logic for protocol workers\n\n### Fixed\n\n- Bad redirection when canceling contact creation\n- Add spaces between buttons in contact edit & contact association pages\n- Unable to download an attachment due to missing request's headers\n\n## [0.15.2] 2019-01-23\n\n### Fixed\n\n- Many fixes on remote identities workers (imap, twitter) tested on production\n\n## [0.15.1] 2019-01-22\n\n### Fixed\n\n- BSOD on discussion when contacts not yet loaded\n\n## [0.15.0] 2019-01-15\n\n### Added\n\n- When an user authenticate we issue a device.login event\n- Add a contact from a discussion\n- Privacy Policy page available at\n  https://alpha.caliopen.org/privacy-policy.html\n\n### Fixed\n\n- typos on English catalog (thanks octplane)\n- Existing device are found better, lead to less untrusted device for user\n- Click on contact's title \"input\" submits the form\n- Empty contact book even when user has a contact (always actually)\n- Route /discussion/{discussion_id} to retrieve one discussion\n- `unread_count` in discussions list\n\n### Changed\n\n- Rework how privacy features are declared and managed\n- Change how a discussion hash is build, take contact_id as better key\n\n## [0.14.0] 2018-12-19\n\n### Added\n\n- special group user contact in contact-book\n- Toggle show spam\n- Placeholder when loading Timeline\n- Messages de-duplication when importing or re-importing from external accounts\n\n### Fixed\n\n- Timeline responsiveness (dates & action bar)\n- Display last protocol used for a discussion in the timeline\n- Automatic set read when displaying a message\n- participants added from suggestions always define `email` protocol\n- More permissive protocol validation in draft according to identities\n\n### Changed\n\n- Timeline colors\n\n## [0.13.2] 2018-12-11\n\n### Added\n\n- Switch identity, send twitter DM, validation…\n- Support new dm twitter notification\n\n### Fixed\n\n- Crash when replying a message.\n- Help button has no effects\n- Unable to change password\n- Better layout for twitter DM\n- Avatar size in tabs for twitter DM\n- Splash screen initialization after reconnect\n\n## [0.13.1] 2018-12-05\n\n### Fixed\n\n- BSOD when selecting contact\n- All messages detected as Twitter DM\n- Missing user_identity when sending the reset password notification\n- Legacy protocol values are considered as valid\n\n## [0.13.0] 2018-11-30\n\n### Added\n\n- API to manage cryptographic public keys related to a contact\n- API to list known remote identity providers\n- Create remote identity for gmail and twitter using Oauth mechanisms\n- A new worker to fetch twitter direct messages\n- Support touch scroll on navigation tabs\n- Manage public keys of a contact\n- App loader splash screen\n\n### Changed\n\n- Brand new UI\n- Message Timeline replaced by Discussion Timeline\n- IM address for a contact is more permisive\n\n### Removed\n\n- Sidescreen on small screen\n- In discussion messages grouped by date\n\n### Fixed\n\n- Translation of new device screen in English\n\n## [0.12.3] 2018-10-26\n\n### Fixed\n\n- Manage better how to declare an user index on signup\n- Index contact and message with correct user_id\n\n### Changed\n\n- Build go images with a vendor sync and with CA certificates for TLS connection\n\n## [0.12.2] 2018-10-15\n\n### Fixed\n\n- /suggest apiv2 route use user.shard_id index not user.user_id\n- message python object use user not user_id\n\n## [0.12.1] 2018-10-04\n\n### Fixed\n\n- Share an index for many users, scalibility of elasticsearch does not work\n  using old scheme\n\n### Added\n\n- Add an email or a social identity to a contact trigger PGP key discovery\n  process\n- A connection is possible per device\n\n### Changed\n\n- prevent invalidation of whole discussion during scroll\n- LocalIdentities and RemoteIdentities have been merged into a new UserIdentity\n  object\n- group discussion by list or all participants\n- API output more informations on discussions\n\n### Fixed\n\n- message sort on \"Load More\" in discussion view\n- revert mis-deleted signup and recovery mail links\n\n## [0.11.2] 2018-07-16\n\n### Fixed\n\n- load more doesn't load correctly when filter has been changed\n- prevent signin until JS is fully loaded, previously a json shows up with\n  informations about a fake device.\n- unlock correctly syncing state after a fetch failure\n- safer parsing of email with ',' or '\\r' character\n\n### Changed\n\n- remove unused safe/public/unsafe login buttons\n\n## [0.11.1] 2018-07-12\n\n### Added\n\n- explain how to authorize retrieve of an imap gmail account\n\n### Fixed\n\n- do not try to parse a null date_sort\n- ensure to not retrieve credentials when patching a remote identity\n- RawMessage.get raise correctly\n- request body must be utf8 encoded correctly, do not use it for the moment\n\n## [0.11.0] 2018-07-04\n\n### Added\n\n- client sign HTTP queries\n- API v1 and v2 check signed ecdsa http queries, but only log result\n- delete an user account\n- API for remote identities management\n- client can create an IMAP remote identities\n- poll remote identities to fetch an IMAP source\n- can use hashicorp vault to store sensible informations\n- fetch list of messages surrounding a given message\n- a go CLI to fix empty message participants\n\n### Changed\n\n- optimization, prevent timeline to fetch multiple times the API\n- Internal email delivery is more reliable, with better error handling,\n  reporting and logging.\n- scroll better on messages list\n\n### Fixed\n\n- scrolls to specific message, and restore scroll positions when navigating\n  between tabs.\n- change parent of a draft has no effects\n- sort messages of a discussion\n- discussion might not be up to date after creating a draft\n- sending two consecutives messages was failing when the second message is saved\n  before clicking on the send button.\n- contact lookup can reference a deleted contact, don't fail\n\n## [0.10.1] 2018-05-18\n\n### Fixed\n\n- a discussion_id should not be removed when patching a draft\n- bad device type mapping for smartphone devices, authentication wasn't possible\n- read message are flagged correctly read\n- search bar is more visible\n\n## [0.10.0] 2018-05-14\n\n### Added\n\n- Confirmation is asked before deleting a message, a discussion or a contact\n- Messages have new computed property : `date_sort`. Messages' list is sorted\n  on.\n- Basic support of new message's notifications\n- Poller and worker for IMAP remote identities fetch in backend\n\n### Changed\n\n- in mobile view, draft form can toggle with an excerpt to not use half of the\n  screen\n- Power to the not found unicorn\n\n### Fixed\n\n- Make favicon accessible without authentication\n\n## [0.9.2] 2018-04-12\n\n### Fixed\n\n- prevent button background color to change on hover when disabled\n- The draft delete button was always disabled even when draft was saved\n- The max file size was not rendered (attachments/contacts..)\n\n## [0.9.1] 2018-04-05\n\n### Fixed\n\n- check if contact.title is null or undefined when displaying ContactBook\n\n## [0.9.0] 2018-03-29\n\n### Added\n\n- Manage draft's attachements\n- Download message's attachements\n- Set context (safe, public, not safe) on signin (it has no effects yet)\n- Multiple messages delete on Timeline\n- Multiple messages' tags management on Timeline\n- Device management first part: declare new device with an ecdsa key\n- Backend notification base principle with a related API\n\n### Changed\n\n- Refactor vcard parsing logic to get more informations\n- Contacts API are now v2\n- Authenticate with a context and a device (known or a new one to declare)\n- Better email body sanitization\n- When updating a contact, compute it's new PI asynchronously\n\n### Fixed\n\n- Explain that the NSA joke is a joke using a `:)`\n\n## [0.8.1] 2018-01-25\n\n### Fixed\n\n- messages filtered by status draft/sent/received wasn't up-to-date\n- checkbox wasn't correctly checked in settings\n- fix BSOD (Black Screen Of Death) when an author is not in user's contact book\n\n## [0.8.0] 2018-01-19\n\n### Added\n\n- Tags management: user's tags, tags on messages and contacts\n- add a `internal` tag to messages sent from the same instance than receiver\n\n### Fixed\n\n- trim spaces in username on signin page\n- unable to load more messages on Timeline\n\n## [0.7.0] 2017-12-22\n\n### Added\n\n- Timeline filter (all, received, sent, drafts)\n- Add a is_received flag on message structure\n- Add german translations\n- Add take a tour for current features\n\n### Changed\n\n- Do not save the draft until body or participants is filled\n- Move activity spinner to the top right of contact page\n- Prevent double click on contact save\n\n### Fixed\n\n- Signout on mobile doesn't disconnect\n- suggest participants fails if a contact has no emails\n- Max body size of a request (import contact)\n- Sort message by date sent for user's messages\n- Settings can be saved when display delay is changed\n- A draft still appears after been deleted\n- Show correctly the message in reply on the current draft\n- Notify when saving contact failed\n- Refresh contacts after a deletion\n- Get browsers' autofills on SigninForm\n\n## [0.6.0] 2017-11-24\n\n### Added\n\n- Reset password\n- do not force phone nnumber normalization, accept everything and try to\n  normalize\n- permit to set contact title on user input, do not compute it strictly\n\n### Changed\n\n- User's contact cannot be deleted anymore\n- The name of the Timeline tab is now \"Messages\" instead of \"Discussions\"\n\n### Fixed\n\n- Do not close the dropdown when receiving new suggestions\n- Disable send message when already sent and add visual feedback (spinner)\n- Disable import contact button on uploading and add a spinner\n- Save updated password strength after password modification\n- Set max file size for contact import\n- Efficient search highlights\n- Access to /user/security route\n- Contact's name consistency\n- On editing/creating contact, disable submit button if form is untouched\n\n## [0.5.5] 2017-11-13\n\n### Added\n\n- Reset password API\n- In a draft, press comma or semicolon key to add a recipient\n- Basic search in messages and contacts\n- Delete a contact\n- Change password\n\n### Fixed\n\n- In a draft to edit last recipient, pressing backspace does not remove last\n  letter\n- In a draft, click outside of recipient list add a recipient\n- Unmarshal nested empty structures in go objects.\n- Save updated password strength after password modification\n\n## [0.5.4] 2017-11-03\n\n### Fixed\n\n- unmarshal nested empty structures in go objects\n- ancestors_id always an array, even empty\n- save a draft notify correctly\n\n## [0.5.3] 2017-11-02\n\n### Added\n\n- Delete a message from Timeline\n- Delete a draft\n- Notify the user the draft is saved after a manual save\n\n### Fixed\n\n- Disable buttons send and save when draft is untouched\n- ancestors_id empty array and not null\n\n## [0.5.2] 2017-10-31\n\n### Fixed\n\n- piwik site id environment variable ok with client build #590\n- lower case local identity lookup #589\n- empty string instead of none for family name #587\n- plain text body unescaped only #586\n- enforce uuid validation in apiv2 #584\n\n## [0.5.1] 2017-10-29\n\n### Fixed\n\n- fix sort in discussions\n- support https in api query configuration\n- Render correctly the frontend Server Side (%MARKUP% will not show up anymore)\n- fix empty UUID on patch #574\n- lmtp crash with invalid nats message #575\n- attach correctly to same discussion first outbound message and its reply #566\n- handle better invalid message unmarshalling #579\n\n## [0.5.0] 2017-10-25\n\n### Added\n\n- create new contact\n- the excerpt of the message in reply into draft form\n- frontend custom settings for the instance and the running environment\n- API to allow users to change their password, with email notification\n- Compute first contact privacy features\n- Permit to restrict registration to a whitelist of user recovery emails\n\n## [0.4.0]\n\n### Added\n\n- Connect settings and apply\n- The brand new Timeline\n- Load more in the discussions\n- API for importance level messages filtering\n- API for full-text searches on messages & contacts\n- Compute importance level v0 for inbound messages\n\n### Changed\n\n- Improve `PATCH` API\n- Backend produces `excerpt` for messages.\n- Backend produces plain or rich body using setting value\n- Frontend: refactoring Dropdown\n\n### Fixed\n\n- Render tag list in contact book\n\n## [0.3.0] 2017-08-31\n\n### Added\n\n- A python package caliopen_pi to group all logic related to privacy index\n  compute\n- Add route `GET /v2/contacts/{contact_id}/identities` to search & retrieve\n  identities from a contact.\n- New scene components related to Settings layout\n- New Importance Level range slider in tabs & alt navigation\n- In compose, add subject input field when recipient uses an email\n- Install postcss-loader and Autoprefixer (run by webpack)\n- Add user settings storage and API for management (GET and PATCH /settings)\n- Frontend can post draftID\n- Messages have 2 bodies : plain + HTML.\n- Only one body is returned to frontend for Message.\n- HTML body is sanitized before output to frontend.\n- MailMessages's subject decoded to always output an UTF-8 string.\n- Index operations return after index has been fully updated\n- API /v1/discussions returns messages ordered by last_messsage date_insert\n  field.\n\n### Changed\n\n- Rename <Account...> layout and related scenes to <User...>\n- Display notification if contact update failed\n- support TAB for adding a participant to a discussion\n- use participant suggestions API for a new message\n- Handle 4xx errors when updating contact fails\n- Compose in multiple tabs messages, not only one.\n- Cross-browser `<select>` element styles.\n- Refactor caliopen_main package for cleaner namespaces.\n- Move tags API to V2. CRUD operations are now on routes /v2/tags\n- Discussions creation from ingress messages are build from messages' external\n  references.\n- ingress & egress JSON payload are double validated by our API instead of only\n  relying on swagger.\n\n### Fixed\n\n- Bad wording for message update failures\n- Blink when composing a new message\n- Inconsistent date format string across stack\n- Bugs on patch operations (contact & messages)\n- Missing 'title' property on the user's Contact card\n- Elasticsearch inconsistent mapping\n\n## [0.2.1] - 2017-07-04\n\n### Added\n\n- GET /identities/remotes/<identifier> route to fix issue #383\n- add a docker compose for staging platform\n\n## [0.2.0] - 2017-07-02\n\n### Added\n\n- Participants suggestion API\n- Basic user remote identities API\n- API for attachment management during draft composition\n- Backend for outbound email message build with attachements\n- Basic privacy features extraction and pi computation when processing inbound\n  email\n- PI attribute added to user, public_key, contact, message entities\n- Contact vcard import API and client implementation\n- Contact book plugged with API\n- Tabbed navigation\n- Messages of a discussion\n- Add or reply save draft message\n- Send a draft message\n- Basic import of a contact book\n- Add piwik, the analytics plateform for the alpha testing purpose\n\n### Changed\n\n- Remove any cassandra counters and secondary index usage\n- Replace cassandra with scylladb in development stack\n- Upgrade elasticsearch from 2.4 version to 5.x ones\n- Network optimizations when loading the client\n- Infinite scroll on the lists\n- Display real contact book (previous was fake)\n\n## [0.1.0] - 2017-05-07\n\n### Added\n\n- Initial release\n- Basic inbound email processing using NATS as message queue broker\n- Contact API\n- Message API\n- Timeline build on basic discussion structure\n- development CLI with basic commands to manage storage and load fixtures\n- docker compose development stack\n- Account creation\n- Basic devices management\n- Basic tags management\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nFirst of all, thank you for contributing, you are awesome!\n\n- Report a bug or post a feature request: https://feedback.caliopen.org/\n- [Installation process](doc/install) for development purpose\n- You don't where to start and you want something simple to do?\n  [Take a look to the good first issues](https://github.com/CaliOpen/Caliopen/labels/good%20first%20issue)\n- You are more involved on Caliopen developments, please read the\n  [Welcome Guide](./doc/welcome.md)\n\n## Code of conduct\n\nAs in CaliOpen: _**Be Good**_. And be nice.\n\n_(This code of conduct may evolve)_.\n\n## Core Team\n\n- [@LaurentChemla](https://github.com/LaurentChemla)\n- [@gdchamal](https://github.com/gdchamal)\n- [@iamdey](https://github.com/iamdey)\n\n## Contributors\n\nOf course we can't show all of you from https://feedback.caliopen.org/. Thank\nyou all!\n\n- [@mardiros](https://github.com/mardiros)\n- [@cr0cK](https://github.com/cr0cK)\n- [@steventlucek](https://github.com/steventlucek)\n- [@themouette](https://github.com/themouette)\n- [@ziir](https://github.com/ziir)\n- [@ncoden](https://github.com/ncoden)\n- [@yanndinendal](https://github.com/yanndinendal)\n- [@pascalduez](https://github.com/pascalduez)\n- [@dgramop](https://github.com/dgramop)\n- alban\n- vinci\n- [@Zeriuno](https://github.com/Zeriuno)\n- The NSA (to make it happen)\n- [@ptran32](https://github.com/ptran32)\n- [@Mindiell](https://github.com/Mindiell)\n- [@goofy-bz](https://github.com/goofy-bz)\n- [@josepot](https://github.com/josepot)\n- [@lisonferez](https://github.com/lisonferez)\n- [@didimoo](https://github.com/didimoo)\n- [@octplane](https://github.com/octplane)\n- [@myleneb](https://github.com/myleneb)\n- [@judbd](https://github.com/orgs/CaliOpen/people/judbd)\n- [@Lauth0](https://github.com/Lauth0)\n- [@sapiens-sapide](https://github.com/sapiens-sapide)\n- [@MonsieurLanza](https://github.com/MonsieurLanza)\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<http://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "# Caliopen Messaging Platform\n\n[![Build Status](https://travis-ci.org/CaliOpen/Caliopen.svg)](https://travis-ci.org/CaliOpen/Caliopen)\n\n> The best way to discuss with your contacts, while preserving your privacy.\n\n![Actual alpha version](doc/assets/caliopen_ui.jpg)\n\nThis repository hosts all the source code, the scripts and the tools for the\n[CaliOpen](https://caliopen.org) project.\n\n\n## Features requests, Issues, Pull Requests or share the love\n\nTell us whatever you want:\n* our discourse: [feedback](https://feedback.caliopen.org/)\n* Twitter [@caliopen_org](https://twitter.com/caliopen_org)\n* IRC on [Freenode #caliopen](irc://chat.freenode.net:6697/caliopen).\n\n**Some usefull links for developers:**\n\n* Global documentation on [readthedocs](http://caliopen.readthedocs.io/en/latest/)\n* Some contributing resources: [CONTRIBUTING.md](CONTRIBUTING.md)\n\n## Installation\n\nUnfortunately Caliopen is not yet ready for production.\n"
  },
  {
    "path": "devtools/ES_queries/Recipient_suggest.json",
    "content": "{\n  \"query\": {\n    \"bool\": {\n      \"should\": [\n        {\n          \"nested\": {\n            \"path\": \"participants\",\n            \"query\": {\n              \"bool\": {\n                \"should\": [\n                  {\n                    \"wildcard\": {\n                      \"participants.label\": \"*laurent*\"\n                    }\n                  },\n                  {\n                    \"wildcard\": {\n                      \"participants.address\": \"*laurent*\"\n                    }\n                  }\n                ]\n              }\n            },\n            \"inner_hits\": {}\n          }\n        },\n        {\n          \"bool\": {\n            \"should\": [\n              {\n                \"wildcard\": {\n                  \"given_name\": {\n                    \"value\": \"*laurent*\",\n                    \"boost\": 2\n                  }\n                }\n              },\n              {\n                \"wildcard\": {\n                  \"family_name\": {\n                    \"value\": \"*laurent*\",\n                    \"boost\": 2\n                  }\n                }\n              }\n            ]\n          }\n        },\n        {\n          \"nested\": {\n            \"path\": \"emails\",\n            \"query\": {\n              \"bool\": {\n                \"should\": [\n                  {\n                    \"wildcard\": {\n                      \"emails.label\": {\n                        \"value\": \"*laurent*\",\n                        \"boost\": 2\n                      }\n                    }\n                  },\n                  {\n                    \"wildcard\": {\n                      \"emails.address\": {\n                        \"value\": \"*laurent*\",\n                        \"boost\": 2\n                      }\n                    }\n                  }\n                ]\n              }\n            },\n            \"inner_hits\": {}\n          }\n        }\n      ]\n    }\n  },\n  \"aggs\": {\n    \"last_messages\": {\n      \"max\": {\n        \"field\": \"date\"\n      }\n    }\n  },\n  \"_source\": [\n    \"date\",\n    \"type\",\n    \"given_name\",\n    \"family_name\"\n  ]\n}"
  },
  {
    "path": "devtools/Vagrantfile",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\n# All Vagrant configuration is done below. The \"2\" in Vagrant.configure\n# configures the configuration version (we support older styles for\n# backwards compatibility). Please don't change it unless you know what\n# you're doing.\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"debian/contrib-jessie64\"\n  # config.vm.box_check_update = false\n\n  # ReST API port\n  config.vm.network \"forwarded_port\", guest: 6543, host: 6543\n  # LMTP port\n  config.vm.network \"forwarded_port\", guest: 4025, host: 4025\n  # Web client port\n  config.vm.network \"forwarded_port\", guest: 4000, host: 4000\n\n\n\n  # config.vm.network \"private_network\", ip: \"192.168.33.10\"\n  # config.vm.network \"public_network\"\n  config.vm.synced_folder \"../\", \"/opt/caliopen/code\"\n\n\n  config.vm.provider \"virtualbox\" do |vb|\n  #   # Display the VirtualBox GUI when booting the machine\n  #   vb.gui = true\n  #\n  #   # Customize the amount of memory on the VM:\n     vb.memory = \"2048\"\n  end\n  config.vm.provision :shell, path: \"./bootstrap-debian.sh\"\nend\n"
  },
  {
    "path": "devtools/api-mock/README.md",
    "content": "# API mock\n\nThe purpose of this package is to mock the whole API using [bouchon][bouchon], it uses redux to\ncollect and render data in an oriented ReSTful server.\n\n## Requirements\n\n* [Node.js][node] >= 6\n* [yarn][yarn] >= 0.21\n\n## Usage\n\nLaunch `bin/start`, it will install and start the mock server on port `31415`.\n\n## Dev\n\nThe entry file is `all.fixture.js`, it loads all routes (and config) in the Javascript ES6 way.\n\nEach \"route\" folders contain configs and [redux][redux] actions and reducers in the `index.js` file\nand the initial fixtures in `data.json`.\n\nThere a middleware to tweak the response (e.g for route collection).\n\n## TODO\n\nIt could great to:\n\n* validates the response using a swagger parser and throw when the response is no more consistent\nwith the real API\n* generate fake datas (faker.js) for not configured routes (using the swagger parser)\n\n[bouchon]: https://github.com/cr0cK/bouchon\n[node]: http://nodejs.org/\n[yarn]: https://yarnpkg.com\n[redux]: http://redux.js.org/\n"
  },
  {
    "path": "devtools/api-mock/all.fixture.js",
    "content": "require('babel-core/register');\nrequire('babel-polyfill');\n\nmodule.exports = {\n  default: [\n    require('./authentications').default,\n    require('./contacts').default,\n    require('./devices').default,\n    require('./discussions').default,\n    require('./local_identities').default,\n    require('./me').default,\n    require('./messages').default,\n    require('./notifications').default,\n    require('./oauth-mock').default,\n    require('./participants').default,\n    require('./providers').default,\n    require('./remote_identities').default,\n    require('./search').default,\n    require('./settings').default,\n    require('./tags').default,\n  ],\n};\n"
  },
  {
    "path": "devtools/api-mock/authentications/data.json",
    "content": "{\n\t\"tokens\": {\n\t\t\"access_token\": \"de85ce575fe7f6262e0cd11582ff33ee848f1027\",\n\t\t\"expires_at\": \"2017-03-07T13:37:39.280870\",\n\t\t\"expires_in\": 99999999999,\n\t\t\"refresh_token\": \"f8576a2b8e1e6967b5a58a195b32e860fa8076ca34768d6b6e7a957ae95fff6214167af26f67455e\"\n\t},\n\t\"user_id\": \"d46d717a-57f5-4b11-b086-11caeac05261\",\n\t\"username\": \"john\",\n\t\"device\": {\n\t\t\"device_id\": \"\",\n\t\t\"status\": \"\"\n\t}\n}\n"
  },
  {
    "path": "devtools/api-mock/authentications/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { actions as devicesAction, selectors as devicesSelector } from '../devices';\nconst actions = {\n  post: createAction('Authenticate'),\n};\n\nconst selectors = {\n  all: (params) => createSelector(\n    [\n      ({ authentications }) => authentications,\n      devicesSelector.byId({ device_id: params.device.device_id }),\n    ],\n    (authentications, device) => ({\n      ...authentications,\n      device,\n    })\n  ),\n};\n\nconst reducer = {};\n\nconst routes = {\n  'POST /': {\n    action: [devicesAction.createOnSignin, actions.post],\n    selector: selectors.all,\n    status: 200,\n  },\n};\n\nexport default {\n  name: 'authentications',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v1/authentications',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/bin/start",
    "content": "#!/bin/bash\n\n# Generate swagger specifications from JSON schema definitions\n\nset -e\nPROJECT_DIRECTORY=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )/..\" && pwd )\"\nSCRIPT_PATH=${PROJECT_DIRECTORY}/node_modules/.bin/bouchon\nCMD=\"yarn start\"\n\n\nif [[ ! -f \"${SCRIPT_PATH}\" ]]; then\n\techo \"You should have nodejs > 6 and yarn\"\n\n\t(cd $PROJECT_DIRECTORY && yarn install)\nfi\n\n\n(cd $PROJECT_DIRECTORY && ${CMD})\n\nset +e\n"
  },
  {
    "path": "devtools/api-mock/collection-middleware/index.js",
    "content": "const createCollectionMiddleware = entityName => data => (req, res, next) => {\n  res.data = {\n    total: data.length,\n    [entityName]: data,\n  };\n\n  next();\n};\n\nexport default createCollectionMiddleware;\n"
  },
  {
    "path": "devtools/api-mock/contacts/data.json",
    "content": "[{\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"c-john-01\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"u-john-01\",\n  \"title\": \"John\",\n  \"additional_name\": \"\",\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"John Dœuf\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 25 },\n  \"tags\": [\"me\"],\n  \"infos\": {\n    \"birthday\": \"2016-05-09T15:01:42.11600\"\n  },\n  \"emails\": [{\n    \"email_id\": \"me-email\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"john@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"home\"\n  }],\n  \"family_name\": \"Dœuf\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"92d3727a-eefc-4537-b879-85f1c9d197bb\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"u-john-01\",\n  \"title\": \"Bender\",\n  \"additional_name\": \"\",\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Bender Bending\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 25 },\n  \"tags\": [\"robot\", \"planet-express\"],\n  \"infos\": {\n    \"birthday\": \"2016-05-09T15:01:42.11600\"\n  },\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00000001\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"bender@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Rodriguez\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"0ba2e346-e4f8-4c45-9adc-eeb1d42fuie0\",\n  \"date_insert\": \"2016-05-09T15:01:43.381000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-fc63-4e41-b490-5f4dd317aa51\",\n  \"title\": \"Zoidberg\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Zoidberg\",\n  \"name_prefix\": \"Dr\",\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 50, \"context\": 45, \"comportment\": 25 },\n  \"tags\": [\"robot\", \"INPORTANT\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00000002\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"zoidberg@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }, {\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00000003\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"zoidberg@planet-express.tld\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"work\"\n  }],\n  \"family_name\": \"Zoidberg\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"19c3ui42-e3ba-46e7-984f-4c3e8de11c05\",\n  \"date_insert\": \"2016-05-09T15:01:40.034000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-tref-4e41-b490-5f4dd317aa52\",\n  \"title\": \"Leela\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Leela\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 87, \"context\": 20, \"comportment\": 25 },\n  \"tags\": [\"planet-express\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00000100\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"leela@john.doe\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Turanga\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"92d3907a-eeui-4537-b229-85f1c9d197bb\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-fc63-4e41-b490-5f4dd1234553\",\n  \"title\": \"Fry\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Philip J.\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 35 },\n  \"tags\": [\"humans\", \"planet-express\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00002001\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"fry@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Fry\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"0ba2e346-e4f8-4c45-999c-eeb1d42f07e0\",\n  \"date_insert\": \"2016-05-09T15:01:43.381000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-gtpu-4e41-b490-5f4dd317aa54\",\n  \"title\": \"Amy\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Amy\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 45, \"context\": 34, \"comportment\": 25 },\n  \"tags\": [\"humans\", \"planet-express\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00000022\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"amy@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Wong\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"19c3ce42-e3ba-46e7-902f-4cui8de11c05\",\n  \"date_insert\": \"2016-05-09T15:01:40.034000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-hyzc-4e41-b490-5f4dd317aa55\",\n  \"title\": \"Farnsworth\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Hubert J.\",\n  \"name_prefix\": \"Prof.\",\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 46, \"context\": 45, \"comportment\": 87 },\n  \"tags\": [\"humans\", \"planet-express\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00000120\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"professor@planet.express\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Farnsworth\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"9ui3907a-eefc-4537-b229-85f1c9d197bb\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-bjsq-4e41-b490-5f4dd317aa53\",\n  \"title\": \"Lrrr\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Lrrr\",\n  \"name_prefix\": \"Emperor\",\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 87, \"context\": 20, \"comportment\": 50 },\n  \"tags\": [\"aliens\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-02000001\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"lrrr@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"98o3907a-eefc-6724-bv28-85f1c9d197bb\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-olds-4e41-b490-5f4dd317aa53\",\n  \"title\": \"Stephen Hawking\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Stephen\",\n  \"name_prefix\": \"Dr\",\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 10, \"context\": 15, \"comportment\": 25 },\n  \"tags\": [\"humans\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-20020001\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"stephen@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Hawking\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"0327707a-eefc-6724-bv28-85f1c9d197bb\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"355489c3-olds-4e41-b490-5f4dd317aa53\",\n  \"title\": \"Kif\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Kif\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 4, \"context\": 24, \"comportment\": 25 },\n  \"tags\": [\"aliens\", \"planet-express\", \"amphibians\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"0327707a-4398-4bd4-9bd5-20020001\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"kroker@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Kroker\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"5488707a-eefc-6724-bv28-85f1c9d197bb\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"356489c3-olds-4e41-b490-5f4dd317aa53\",\n  \"title\": \"Zapp Brannigan\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Zapp\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 41, \"context\": 15, \"comportment\": 25 },\n  \"tags\": [\"humans\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"5488707a-4398-4bd4-9bd5-20020001\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"zapp@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Brannigan\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"19c3ce42-e3ba-46e7-902f-4cui8d\",\n  \"date_insert\": \"2016-05-09T15:01:40.034000\",\n  \"identities\": [],\n  \"user_id\": \"340089c3-hyzc-4e41-b490-5f4dd317a\",\n  \"title\": \"Elzar\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Elzar\",\n  \"name_prefix\": \"Chef\",\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 48, \"context\": 45, \"comportment\": 25 },\n  \"tags\": [\"aliens\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00000120\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"francine@doop\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"a3ce42-e3ba-46e7-902f-4cfdsfsui8d\",\n  \"date_insert\": \"d016-05-09T15:01:40.034000\",\n  \"identities\": [],\n  \"user_id\": \"40489c3-hyzc-4e41-b490-5f4dd317a\",\n  \"title\": \"Scruffy\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Scruffy\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 35 },\n  \"tags\": [\"humans\", \"planet-express\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00000120\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"algore@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Scruffington\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"a3ce42-e3ba-46e7-dfsd902f-4cui8d\",\n  \"date_insert\": \"d016-05-09T15:01:40.034000\",\n  \"identities\": [],\n  \"user_id\": \"04489c3-hyzc-4e41-b490-5f4dd317a\",\n  \"title\": \"Hermès\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Hermès\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 87, \"context\": 70, \"comportment\": 95 },\n  \"tags\": [\"humans\", \"planet-express\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00000120\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"Conrad@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Conrad\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"92d3727a-eefc-4537-b879-85f1c7bb\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"u-john-01\",\n  \"title\": \"Flexo\",\n  \"additional_name\": \"\",\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Flexo\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 48, \"context\": 56, \"comportment\": 32 },\n  \"tags\": [\"robot\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00900001\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"flexo@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"0ba2e346-e4f8-4c45-9adc-eeb1die0\",\n  \"date_insert\": \"2016-05-09T15:01:43.381000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-fc63-4e41-b490-5f4dd317aa51\",\n  \"title\": \"Calculon\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Antonio\",\n  \"name_prefix\": \"\",\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 10, \"context\": 15, \"comportment\": 12 },\n  \"tags\": [\"robot\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-90000002\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"calc@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Calculon\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"19c3ui42-e3ba-46e7-984f-4c3e8c05\",\n  \"date_insert\": \"2016-05-09T15:01:40.034000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-tref-4e41-b490-5f4dd317aa52\",\n  \"title\": \"Mom\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 56, \"context\": 35, \"comportment\": 7 },\n  \"tags\": [\"humans\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00900100\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"mom@john.doe\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Mom\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"92d3907a-eeui-4537-b229-85f197bb\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-fc63-4e41-b490-5f4dd1234553\",\n  \"title\": \"Morbo the Annihilator\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Morbo\",\n  \"name_prefix\": null,\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 41, \"context\": 45, \"comportment\": 7 },\n  \"tags\": [\"aliens\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00002901\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"morbo@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"0ba2e346-e4f8-4c45-999c-eeb107e0\",\n  \"date_insert\": \"2016-05-09T15:01:43.381000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-gtpu-4e41-b490-5f4dd317aa54\",\n  \"title\": \"Panucci\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"\",\n  \"name_prefix\": \"Mr.\",\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 20, \"context\": 45, \"comportment\": 75 },\n  \"tags\": [\"humans\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-00900022\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"Panucci@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"Panucci\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"19c3ce42-e3ba-46e7-902f-4cuie11c05\",\n  \"date_insert\": \"2016-05-09T15:01:40.034000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-hyzc-4e41-b490-5f4dd317aa55\",\n  \"title\": \"Nibbler\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Nibbler\",\n  \"name_prefix\": \"Lord\",\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 65, \"context\": 45, \"comportment\": 25 },\n  \"tags\": [\"aliens\", \"planet-express\"],\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-0009120\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"nib@planet.express\",\n    \"date_insert\": \"2016-05-09T15:01:43.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}, {\n  \"addresses\": [],\n  \"privacy_features\": {},\n  \"phones\": [],\n  \"contact_id\": \"9ui3907a-eefc-4537-b229-f1c9d19b\",\n  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n  \"identities\": [],\n  \"user_id\": \"344489c3-bjsq-4e41-b490-5f4dd317aa53\",\n  \"title\": \"Hypnotoad\",\n  \"additional_name\": null,\n  \"date_update\": null,\n  \"organizations\": [],\n  \"ims\": [],\n  \"given_name\": \"Hypnotoad\",\n  \"name_prefix\": \"\",\n  \"tags\": [\"amphibians\", \"aliens\"],\n  \"deleted\": 0,\n  \"pi\": { \"technic\": 87, \"context\": 75, \"comportment\": 25 },\n  \"infos\": {},\n  \"emails\": [{\n    \"email_id\": \"93f03145-4398-4bd4-9bd5-02000901\",\n    \"is_primary\": 0,\n    \"date_update\": null,\n    \"label\": null,\n    \"address\": \"Hypnotoad@caliopen.local\",\n    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n    \"type\": \"other\"\n  }],\n  \"family_name\": \"\",\n  \"name_suffix\": null,\n  \"avatar\": \"avatar.png\",\n  \"public_keys\": []\n}]\n"
  },
  {
    "path": "devtools/api-mock/contacts/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nconst actions = {\n  get: createAction('Get contacts'),\n  patch: createAction('Patch contact'),\n  post: createAction('Post contact'),\n  patchTags: createAction('Patch contact\\'s tags'),\n};\n\nconst selectors = {\n  all: () => state => state.contacts,\n  byId: ({ contact_id }) => createSelector(\n    selectors.all(),\n    contacts => {\n      const contact = contacts.find(contact => contact.contact_id === contact_id);\n      if (contact) {\n        return contact;\n      }\n\n      throw new Error('contact not found');\n    }\n  ),\n  last: () => state => [...state.contacts].pop(),\n  lastLocation: () => createSelector(\n    selectors.last(),\n    contact => ({ location: `/api/v2/contacts/${contact.contact_id}` })\n  ),\n};\n\nconst reducer = {\n  [actions.post]: (state, { body }) => ([\n    ...state,\n    {\n      contact_id: uuidv1(),\n      ...body,\n    },\n  ]),\n  [actions.patch]: (state, { params, body }) => {\n    const nextState = [...state];\n    const original = state.find(contact => contact.contact_id === params.contact_id);\n    if (!original) {\n      throw `contact w/ id ${params.contact_id} not found`;\n    }\n    const index = nextState.indexOf(original);\n    const { current_state, ...props } = body;\n    nextState[index] = {\n      ...original,\n      ...props,\n    };\n\n    return nextState;\n  },\n  [actions.patchTags]: (state, { params, body }) => {\n    const original = state.find(contact => contact.contact_id === params.contact_id);\n    if (!original) {\n      throw `contact w/ id ${params.contact_id} not found`;\n    }\n\n    const index = state.indexOf(original);\n    const nextState = [...state];\n    const { tags } = body;\n\n    nextState[index] = {\n      ...original,\n      tags,\n    };\n\n    return nextState;\n  },\n};\n\nconst routes = {\n  'GET /v2/contacts/': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n    middlewares: [createCollectionMiddleware('contacts')],\n  },\n  'GET /v2/contacts/:contact_id': {\n    action: actions.get,\n    selector: selectors.byId,\n    status: 200,\n  },\n  'PATCH /v2/contacts/:contact_id': {\n    action: actions.patch,\n    status: 204,\n  },\n  'POST /v2/contacts/': {\n    action: actions.post,\n    selector: selectors.lastLocation,\n    status: 200,\n  },\n  'PATCH /v2/contacts/:contact_id/tags': {\n    action: actions.patchTags,\n    status: 204,\n  },\n};\n\nexport default {\n  name: 'contacts',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/devices/data.json",
    "content": "[\n  {\n    \"device_id\": \"00001\",\n    \"ecdsa_key\": {\n      \"curve\": \"P-256\",\n      \"hash\": \"SHA256\",\n      \"x\": \"fd73920f7e2ad3ce524b5b08cab21357dd6a4d7d5d3c267e42a834d8ac1af244\",\n      \"y\": \"1bf08f940c720b5a021863f03029cbf62ea7a4837460da957be9f4704eff76bd\"\n    },\n    \"status\": \"unverified\",\n    \"name\": \"device to revoke\",\n    \"type\": \"smartphone\"\n  }\n]\n"
  },
  {
    "path": "devtools/api-mock/devices/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nexport const actions = {\n  get: createAction('Get devices'),\n  createOnSignin: createAction('Create device'),\n  delete: createAction('Delete a device'),\n  patch: createAction('Patch a device'),\n  reqVerif: createAction('Req Verif of a device'),\n  verify: createAction('Verify'),\n};\n\nexport const selectors = {\n  all: () => state => state.devices,\n  last: () => state => [...state.devices].pop(),\n  byId: ({ device_id }) => createSelector(\n    selectors.all(),\n    devices => devices.find(device => device.device_id === device_id)\n  ),\n  location: () => createSelector(\n    selectors.last(),\n    device => ({ location: `/api/v2/devices/${device.device_id}` })\n  ),\n};\n\nconst devicesToIgnore = new Set(['00001']);\n\nconst reducer = {\n  [actions.createOnSignin]: (state, { body, req }) => {\n    const { device_id } = body.device;\n    const hasDevice = state.some(device => device.device_id === device_id);\n    const hasVerifiedDevice = state\n      .filter(device => !devicesToIgnore.has(device.device_id))\n      .some(device => device.status === 'verified');\n\n    if (hasDevice) {\n      return state;\n    }\n\n    const device = {\n      ...body.device,\n      status: !hasVerifiedDevice ? 'verified' : 'unverified',\n      name: !hasVerifiedDevice ? 'default' : `desktop ${state.length}`,\n    };\n\n    return [\n      ...state,\n      device,\n    ];\n  },\n  [actions.get]: state => state,\n  [actions.delete]: (state, { params }) => {\n    const copy = state.slice(0);\n    return [...state].filter(device => device.device_id !== params.device_id);\n  },\n  [actions.patch]: (state, { params, body }) => {\n    const nextState = [...state];\n    const original = state.find(device => device.device_id === params.device_id);\n    if (!original) {\n      throw `device w/ id ${params.device_id} not found`;\n    }\n    const index = nextState.indexOf(original);\n    const { current_state, ...props } = body;\n    nextState[index] = {\n      ...original,\n      ...props,\n    };\n\n    return nextState;\n  },\n  [actions.reqVerif]: (state, { params, body }) => {\n    const original = state.find(device => device.device_id === params.device_id);\n    if (!original) {\n      throw `device w/ id ${params.device_id} not found`;\n    }\n    const { actions } = body;\n\n    if (actions[0] !== 'device-validation') {\n      throw new Error('Unknown action');\n    }\n\n    return state;\n  },\n};\n\nconst routes = {\n  'GET /devices/': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n    middlewares: [createCollectionMiddleware('devices')],\n  },\n  'GET /devices/:device_id': {\n    action: actions.get,\n    selector: selectors.byId,\n    status: 200,\n  },\n  'DELETE /devices/:device_id': {\n    action: actions.delete,\n    status: 204,\n  },\n  'PATCH /devices/:device_id': {\n    action: actions.patch,\n    status: 204,\n  },\n  'POST /devices/:device_id/actions': {\n    action: actions.reqVerif,\n    status: 204,\n  },\n  'GET /validate-device/:token': {\n    action: actions.verify,\n    status: 204,\n    middlewares: [data => (req, res, next) => {\n      if (req.params.token === 'fail') {\n        res.status(500);\n\n        return;\n      }\n      if (req.params.token !== 'aaaa-bbbb') {\n        res.status(404);\n\n        return;\n      }\n    }],\n  },\n};\n\nexport default {\n  name: 'devices',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v2',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/discussions/data.json",
    "content": "[\n  {\n    \"tags\": [\n      {\n        \"tag_id\": \"foo-bar-1\",\n        \"name\": \"robot\",\n        \"type\": \"user\"\n      },\n      {\n        \"tag_id\": \"foo-bar-2\",\n        \"name\": \"work\",\n        \"type\": \"system\"\n      }\n    ],\n    \"last_message_id\": \"11111111-267d-4d9c-97ee-d0fc59c64202\",\n    \"last_message_subject\": \"\",\n    \"excerpt\": \"Fry! Stay back! He's too powerful! Can I use the gun? I barely knew Philip, but as a ...\",\n    \"privacy_index\": 1,\n    \"participants\": [\n      {\n        \"label\": \"test\",\n        \"type\": \"Cc\",\n        \"contact_ids\": [\"1039cdcc-1f6f-4b5d-9c8a-5d7c711f357f\"],\n        \"address\": \"test@caliopen.local\",\n        \"protocol\": \"email\"\n      },\n      {\n        \"type\": \"To\",\n        \"label\": \"Jaune\",\n        \"contact_ids\": [\"c-john-01\"],\n        \"address\": \"john@caliopen.local\",\n        \"protocol\": \"email\"\n      },\n      {\n        \"label\": \"zoidberg\",\n        \"type\": \"From\",\n        \"address\": \"zoidberg@planet-express.tld\",\n        \"protocol\": \"email\"\n      }\n    ],\n    \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n    \"date_update\": \"2017-03-21T10:55:00.153000+00:00\",\n    \"discussion_id\": \"cd53e13a-267d-4d9c-97ee-d0fc59c64200\",\n    \"total_count\": 1,\n    \"attachment_count\": 0,\n    \"importance_level\": 0,\n    \"unread_count\": 1\n  },\n  {\n    \"tags\": [\n      {\n        \"tag_id\": \"foo-bar-3\",\n        \"name\": \"best quotes\",\n        \"type\": \"user\"\n      },\n      {\n        \"tag_id\": \"foo-bar-4\",\n        \"name\": \"lolilol\",\n        \"type\": \"user\"\n      }\n    ],\n    \"excerpt\": \"Shut up and take my money! Leela, are you alright? You got wanged on the he=\\r\\nad. Bender, you risked your life To save me! Spare me your space age techno=\\r\\nbabble, Attila the Hun! Now that the, uh, ga\",\n    \"privacy_index\": 25,\n    \"participants\": [\n      {\n        \"type\": \"From\",\n        \"contact_ids\": [\"c-john-01\", \"1039cdcc-1f6f-4b5d-9c8a-5d7c711f357f\"],\n        \"address\": \"john@caliopen.local\",\n        \"protocol\": \"email\",\n        \"label\": \"Jaune\"\n      },\n      {\n        \"type\": \"To\",\n        \"contact_ids\": [\"0ba2e346-e4f8-4c45-9adc-eeb1d42f07e0\"],\n        \"address\": \"fry@planet-express.tld\",\n        \"protocol\": \"email\",\n        \"label\": \"fry\"\n      }\n    ],\n    \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n    \"date_update\": \"2017-03-21T10:55:00.153000+00:00\",\n    \"discussion_id\": \"46d30c27-6cd8-407b-8536-fda4196c20ca\",\n    \"last_message_id\": \"2222222-267d-4d9c-97ee-d0fc59c64201\",\n    \"last_message_subject\": \"Shut up and take my money\",\n    \"total_count\": 2,\n    \"attachment_count\": 2,\n    \"importance_level\": 0,\n    \"unread_count\": 2\n  },\n  {\n    \"tags\": [],\n    \"excerpt\": \"delete a message\",\n    \"privacy_index\": 25,\n    \"participants\": [\n      {\n        \"type\": \"From\",\n        \"contact_ids\": [\"1039cdcc-1f6f-4b5d-9c8a-5d7c711f357f\"],\n        \"address\": \"john@caliopen.local\",\n        \"label\": \"Jaune\",\n        \"protocol\": \"email\"\n      },\n      {\n        \"type\": \"To\",\n        \"contact_ids\": [\"0ba2e346-e4f8-4c45-9adc-eeb1d42f07e0\"],\n        \"address\": \"removeme@caliopen.local\",\n        \"protocol\": \"email\",\n        \"label\": \"Remove Me\"\n      }\n    ],\n    \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n    \"date_update\": \"2017-03-21T10:55:00.153000+00:00\",\n    \"discussion_id\": \"3330c27-6cd8-407b-8536-fda4196c20ca\",\n    \"last_message_subject\": \"remove message one by one\",\n    \"last_message_id\": \"3333-267d-4d9c-97ee-d0fc59c64201\",\n    \"total_count\": 2,\n    \"attachment_count\": 2,\n    \"importance_level\": 0,\n    \"unread_count\": 2\n  },\n  {\n    \"tags\": [],\n    \"excerpt\": \"delete all messages\",\n    \"privacy_index\": 25,\n    \"participants\": [\n      {\n        \"type\": \"From\",\n        \"contact_ids\": [\"1039cdcc-1f6f-4b5d-9c8a-5d7c711f357f\"],\n        \"address\": \"john@caliopen.local\",\n        \"protocol\": \"email\",\n        \"label\": \"Jaune\"\n      },\n      {\n        \"type\": \"To\",\n        \"contact_ids\": [\"0ba2e346-e4f8-4c45-9adc-eeb1d42f07e0\"],\n        \"address\": \"removeall@caliopen.local\",\n        \"protocol\": \"email\",\n        \"label\": \"Remove'em all\"\n      }\n    ],\n    \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n    \"date_update\": \"2017-03-21T10:55:00.153000+00:00\",\n    \"discussion_id\": \"44440c27-6cd8-407b-8536-fda4196c20ca\",\n    \"last_message_subject\": \"remove messages\",\n    \"last_message_id\": \"5555-267d-4d9c-97ee-d0fc59c64201\",\n    \"total_count\": 2,\n    \"attachment_count\": 2,\n    \"importance_level\": 0,\n    \"unread_count\": 2\n  },\n  {\n    \"tags\": [],\n    \"excerpt\": \"Les champignons gardent toute leur saveur quand ils\",\n    \"privacy_index\": 25,\n    \"participants\": [\n      {\n        \"address\": \"panoramix@caliopen.local\",\n        \"contacts_ids\": [\"df71ff9d-026b-4d18-b555-b8d110c57069\"],\n        \"protocol\": \"email\",\n        \"label\": \"Panoramix\",\n        \"type\": \"From\"\n      },\n      {\n        \"address\": \"john@caliopen.local\",\n        \"contact_ids\": [\"c-john-01\"],\n        \"label\": \"Jaune\",\n        \"protocol\": \"email\",\n        \"type\": \"To\"\n      },\n      {\n        \"address\": \"asterix@caliopen.local\",\n        \"contacts_ids\": [\"2c8fb086-86da-4a87-9abc-668b73578f49\"],\n        \"protocol\": \"email\",\n        \"label\": \"Astérix\",\n        \"type\": \"To\"\n      }\n    ],\n    \"date_insert\": \"0042-09-08T15:00:00+00:00\",\n    \"date_update\": \"0042-09-08T15:00:00+00:00\",\n    \"discussion_id\": \"b1256c1e-7b65-4fe2-8960-a7d6c0c4a051\",\n    \"last_message_id\": \"0bf58db0-b664-49e1-ade3-4f4e2424aa86\",\n    \"last_message_subject\": \"Ils sont fous ces romains.\",\n    \"total_count\": 5,\n    \"attachment_count\": 0,\n    \"importance_level\": 7,\n    \"unread_count\": 2\n  },\n  {\n    \"tags\": [],\n    \"excerpt\": \"Moi, je verrais plutôt les champignons en salade…\\n\",\n    \"privacy_index\": 1,\n    \"participants\": [\n      {\n        \"address\": \"agecanonix@caliopen.local\",\n        \"contacts_ids\": [\"51cbcbbd-3b88-44f0-963a-06740e546c33\"],\n        \"protocol\": \"email\",\n        \"label\": \"Agecanonix\",\n        \"type\": \"From\"\n      },\n      {\n        \"address\": \"john@caliopen.local\",\n        \"contact_ids\": [\"c-john-01\"],\n        \"label\": \"foo\",\n        \"protocol\": \"email\",\n        \"type\": \"To\"\n      },\n      {\n        \"address\": \"abraracourcix@caliopen.local\",\n        \"contacts_ids\": [\"643fb7b3-c6fe-4f2b-9c0d-2ab671f482d5\"],\n        \"protocol\": \"email\",\n        \"label\": \"Abraracourcix\",\n        \"type\": \"To\"\n      },\n      {\n        \"address\": \"obelix@caliopen.local\",\n        \"contacts_ids\": [\"629e086d-7257-49d7-9080-61cbb98b93ee\"],\n        \"protocol\": \"email\",\n        \"label\": \"Obélix\",\n        \"type\": \"To\"\n      },\n      {\n        \"address\": \"panoramix@caliopen.local\",\n        \"contacts_ids\": [\"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"],\n        \"protocol\": \"email\",\n        \"label\": \"Panoramix\",\n        \"type\": \"To\"\n      },\n      {\n        \"address\": \"asterix@caliopen.local\",\n        \"contacts_ids\": [\"2c8fb086-86da-4a87-9abc-668b73578f49\"],\n        \"protocol\": \"email\",\n        \"label\": \"Astérix\",\n        \"type\": \"To\"\n      }\n    ],\n    \"date_insert\": \"0042-09-08T14:32:00+00:00\",\n    \"date_update\": \"0042-09-08T14:32:00+00:00\",\n    \"discussion_id\": \"37e376ce-8be4-453c-a3bf-86eefaff1940\",\n    \"last_message_id\": \"193e0cf8-fce8-4e26-8fe2-85858009b81d\",\n    \"last_message_subject\": \"Ils sont fous ces romains.\",\n    \"total_count\": 8,\n    \"attachment_count\": 0,\n    \"importance_level\": 7,\n    \"unread_count\": 9\n  }\n]\n"
  },
  {
    "path": "devtools/api-mock/discussions/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nexport const actions = {\n  get: createAction('Get discussions'),\n  create: createAction('create discussion'),\n};\n\nconst selectors = {\n  all: () => (state) => state.discussions,\n  byId: ({ discussion_id }) =>\n    createSelector(selectors.all(), (discussions) => {\n      const discussion = discussions.find(\n        (discussion) => discussion.discussion_id === discussion_id\n      );\n      if (discussion) {\n        return discussion;\n      }\n\n      throw new Error('discussion not found');\n    }),\n};\n\nconst reducer = {\n  [actions.create]: (state, { body, req }) => {\n    if (body.discussion_id) {\n      return state;\n    }\n\n    const discussion = {\n      discussion_id: uuidv1(),\n      participants: body.participants.map((participant) => ({\n        ...participant,\n        protocol:\n          participant.protocol === 'smtp' ? 'email' : participant.protocol,\n      })),\n      excerpt: body.body.slice(0, 100), // it works beccause created using POST message route\n      privacy_index: 1,\n      date_insert: new Date(),\n      total_count: 1,\n      attachment_count: 0,\n      importance_level: 0,\n      unread_count: 0,\n    };\n\n    // set discussionId for new draft;\n    req.discussionId = discussion.discussion_id;\n\n    return [...state, discussion];\n  },\n};\n\nconst routes = {\n  'GET /': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n    middlewares: [createCollectionMiddleware('discussions')],\n  },\n  'GET /:discussion_id': {\n    action: actions.get,\n    selector: selectors.byId,\n    status: 200,\n  },\n};\n\nexport default {\n  name: 'discussions',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v2/discussions',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/local_identities/data.json",
    "content": "[\n  {\n    \"credentials\": {},\n    \"display_name\": \"Jown d'eau\",\n    \"identity_id\": \"b7a715aa-8fff-4083-9dab-0c85b2634c0b\",\n    \"identifier\": \"john@caliopen.local\",\n    \"infos\": {},\n    \"last_check\": \"0001-01-01T00:00:00Z\",\n    \"protocol\": \"smtp\",\n    \"status\": \"active\",\n    \"type\": \"local\",\n    \"user_id\": \"u-john-01\"\n  }\n]\n"
  },
  {
    "path": "devtools/api-mock/local_identities/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nconst actions = {\n  get: createAction('Get local_identities'),\n};\n\nconst selectors = {\n  all: () => state => state.local_identities,\n};\n\nconst reducer = {\n  [actions.get]: state => state,\n};\n\nconst routes = {\n  'GET /': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n    middlewares: [createCollectionMiddleware('local_identities')],\n  },\n};\n\nexport default {\n  name: 'local_identities',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v2/identities/locals',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/me/data.json",
    "content": "{\n\t\"user_id\": \"u-john-01\",\n\t\"name\": \"Jaune\",\n\t\"given_name\": \"John Dœuf\",\n\t\"contact_id\": \"foobar112\",\n\t\"contact\": {\n\t  \"addresses\": [],\n\t  \"privacy_features\": {},\n\t  \"phones\": [],\n\t  \"contact_id\": \"c-john-01\",\n\t  \"date_insert\": \"2016-05-09T15:01:42.381000\",\n\t  \"identities\": [],\n\t  \"user_id\": \"u-john-01\",\n\t  \"title\": \"John\",\n\t  \"additional_name\": \"\",\n\t  \"date_update\": null,\n\t  \"organizations\": [],\n\t  \"ims\": [],\n\t  \"given_name\": \"John Dœuf\",\n\t  \"name_prefix\": null,\n\t  \"deleted\": 0,\n\t  \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 25 },\n\t  \"tags\": [\"me\"],\n\t  \"infos\": {\n\t    \"birthday\": \"2016-05-09T15:01:42.11600\"\n\t  },\n\t  \"emails\": [{\n\t    \"email_id\": \"me-email\",\n\t    \"is_primary\": 0,\n\t    \"date_update\": null,\n\t    \"label\": null,\n\t    \"address\": \"john@caliopen.local\",\n\t    \"date_insert\": \"2016-05-09T15:01:42.116000\",\n\t    \"type\": \"home\"\n\t  }],\n\t  \"family_name\": \"Dœuf\",\n\t  \"name_suffix\": null,\n\t  \"avatar\": \"avatar.png\",\n\t  \"public_keys\": []\n\t},\n\t\"privacy_features\": {\n\t\t\"password_strength\": \"2\"\n\t}\n}\n"
  },
  {
    "path": "devtools/api-mock/me/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\n\nconst actions = {\n  get: createAction('me'),\n  patch: createAction('patch'),\n  actions: createAction('actions'),\n};\n\nconst selectors = {\n  all: () => state => state.me,\n};\n\nconst reducer = {\n  [actions.get]: state => state,\n};\n\nconst routes = {\n  'GET /v1/me/': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n  },\n  'PATCH /v2/users/:user_id/': {\n    action: actions.patch,\n    status: 204,\n  },\n  'POST /v2/users/:user_id/actions': {\n    action: actions.actions,\n    status: 204,\n    middlewares: [data => (req, res, next) => {\n      if (req.body.params.password !== '123456') {\n        res.status(424);\n        res.data = { errors: [{ message: '[RESTfacility] DeleteUser Wrong password' }]}\n      }\n      next();\n    }],\n  },\n};\n\nexport default {\n  name: 'me',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/messages/data.json",
    "content": "[{\n  \"attachments\": [],\n  \"body\": \"Fry! Stay back! He's too powerful! Can I use the gun? I barely knew Philip, but as a clergyman I have no problem telling his most intimate friends all about him. What are you hacking off? Is it my torso?! 'It is!' My precious torso!\",\n  \"excerpt\": \"Fry! Stay back! He's too powerful! Can I use the gun? I barely knew Philip, but as a ...\",\n  \"date\": \"2017-03-22T12:55:00.153000+00:00\",\n  \"date_insert\": \"2017-03-21T12:55:00.153000+00:00\",\n  \"date_sort\": \"2017-03-21T12:55:00.153000+00:00\",\n  \"discussion_id\": \"cd53e13a-267d-4d9c-97ee-d0fc59c64200\",\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"importance_level\": 0,\n  \"is_answered\": false,\n  \"is_draft\": false,\n  \"is_received\": false,\n  \"message_id\": \"11111111-267d-4d9c-97ee-d0fc59c64202\",\n  \"participants\": [{\n  \"address\": \"test@caliopen.local\",\n  \"label\": \"foo\",\n  \"protocol\": \"email\",\n  \"type\": \"Cc\"\n  }, {\n  \"address\": \"john@caliopen.local\",\n  \"contact_ids\": [\"c-john-01\"],\n  \"label\": \"Jaune\",\n  \"protocol\": \"email\",\n  \"type\": \"From\"\n  }, {\n  \"address\": \"zoidberg@planet-express.tld\",\n  \"contact_ids\": [\"0ba2e346-e4f8-4c45-9adc-eeb1d42fuie0\"],\n  \"label\": \"zoidberg\",\n  \"protocol\": \"email\",\n  \"type\": \"To\"\n  }],\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"aaaaaaaa-267d-4d9c-97ee-d0fc59c64202\",\n  \"tags\": [\"robot\", \"INBOX\"],\n  \"protocol\": \"email\",\n  \"pi\": { \"technic\": 30, \"context\": 25, \"comportment\": 25, \"version\": 1 },\n  \"pi_message\": { \"transport\": 30, \"content\": 25, \"social\": 25 },\n  \"user_id\": \"\"\n},\n{\n  \"attachments\": [],\n  \"body\": \"<p>It's okay, Bender. I like cooking too. You, minion. Lift my arm. AFTER H=\\r\\nIM! I don't know what you did, Fry, but once again, you screwed up! Now all\\r\\nthe planets are gonna start cracking wise abo\",\n  \"excerpt\": \"It's okay, Bender. I like cooking too. You, minion. Lift my arm. AFTER HIM! I don't ...\",\n  \"date\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_sort\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"discussion_id\": \"cd53e13a-267d-4d9c-97ee-d0fc59c64200\",\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"importance_level\": 0,\n  \"is_answered\": false,\n  \"is_draft\": false,\n  \"is_received\": true,\n  \"message_id\": \"11111111-267d-4d9c-97ee-d0fc59c64201\",\n  \"participants\": [{\n  \"address\": \"test@caliopen.local\",\n  \"label\": \"foo\",\n  \"protocol\": \"email\",\n  \"type\": \"To\"\n  }, {\n  \"address\": \"john@caliopen.local\",\n  \"contact_ids\": [\"c-john-01\"],\n  \"label\": \"Jaune\",\n  \"protocol\": \"email\",\n  \"type\": \"Cc\"\n  }, {\n  \"address\": \"zoidberg@planet-express.tld\",\n  \"contact_ids\": [\"0ba2e346-e4f8-4c45-9adc-eeb1d42fuie0\"],\n  \"label\": \"zoidberg\",\n  \"protocol\": \"email\",\n  \"type\": \"From\"\n  }],\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"aaaaaaaa-267d-4d9c-97ee-d0fc59c64201\",\n  \"subject\": \"It's okay, Bender. I like cooking too\",\n  \"tags\": [\"robot\", \"IMPORTANT\"],\n  \"protocol\": \"email\",\n  \"pi\": { \"technic\": 50, \"context\": 45, \"comportment\": 25, \"version\": 1 },\n  \"pi_message\": { \"transport\": 50, \"content\": 45, \"social\": 25 },\n  \"user_id\": \"\"\n},\n{\n  \"attachments\": [],\n  \"body\": \"Shut up and take my money! Leela, are you alright? You got wanged on the he=\\r\\nad. Bender, you risked your life To save me! Spare me your space age techno=\\r\\nbabble, Attila the Hun! Now that the, uh, ga\",\n  \"excerpt\": \"Shut up and take my money! Leela, are you alright? You got wanged on the head. Bender, ...\",\n  \"date\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_sort\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"discussion_id\": \"46d30c27-6cd8-407b-8536-fda4196c20ca\",\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"importance_level\": 0,\n  \"is_answered\": false,\n  \"is_draft\": false,\n  \"is_received\": true,\n  \"message_id\": \"2222222-267d-4d9c-97ee-d0fc59c64201\",\n  \"participants\": [{\n  \"address\": \"john@caliopen.local\",\n  \"contact_ids\": [\"c-john-01\"],\n  \"label\": \"Jaune\",\n  \"protocol\": \"email\",\n  \"type\": \"To\"\n  }, {\n  \"address\": \"zoidberg@planet-express.tld\",\n  \"contact_ids\": [\"0ba2e346-e4f8-4c45-9adc-eeb1d42fuie0\"],\n  \"label\": \"zoidberg\",\n  \"protocol\": \"email\",\n  \"type\": \"To\"\n  },{\n  \"address\": \"fry@planet-express.tld\",\n  \"contact_ids\": [\"92d3907a-eeui-4537-b229-85f1c9d197bb\"],\n  \"label\": \"Fry\",\n  \"protocol\": \"email\",\n  \"type\": \"From\"\n  }],\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"bbbbbbbbb-267d-4d9c-97ee-d0fc59c64201\",\n  \"subject\": \"Shut up and take my money\",\n  \"tags\": [\"INBOX\", \"SPAM\"],\n  \"protocol\": \"email\",\n  \"pi\": { \"technic\": 40, \"context\": 15, \"comportment\": 45, \"version\": 1 },\n  \"pi_message\": { \"transport\": 40, \"content\": 15, \"social\": 45 }\n},\n{\n  \"attachments\": [],\n  \"body\": \"first message to remove individually\",\n  \"excerpt\": \"first message to remove individually\",\n  \"date\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_sort\": \"2017-03-20T10:55:00.153000+00:00\",\n  \"discussion_id\": \"3330c27-6cd8-407b-8536-fda4196c20ca\",\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"importance_level\": 0,\n  \"is_answered\": false,\n  \"is_draft\": false,\n  \"is_received\": true,\n  \"message_id\": \"3333-267d-4d9c-97ee-d0fc59c64201\",\n  \"participants\": [{\n  \"address\": \"john@caliopen.local\",\n  \"contact_ids\": [\"c-john-01\"],\n  \"label\": \"Jaune\",\n  \"protocol\": \"email\",\n  \"type\": \"To\"\n  }, {\n  \"address\": \"removeme@caliopen.local\",\n  \"label\": \"remove message\",\n  \"protocol\": \"email\",\n  \"type\": \"From\"\n  }],\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"bbbbbbbbb-267d-4d9c-97ee-d0fc59c64201\",\n  \"subject\": \"remove message one by one\",\n  \"protocol\": \"email\",\n  \"pi\": { \"technic\": 50, \"context\": 45, \"comportment\": 25, \"version\": 1 },\n  \"pi_message\": { \"transport\": 50, \"content\": 45, \"social\": 25 }\n},\n{\n  \"attachments\": [],\n  \"body\": \"last message to remove individually\",\n  \"excerpt\": \"last message to remove individually\",\n  \"date\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_sort\": \"2017-03-20T10:55:00.153000+00:00\",\n  \"discussion_id\": \"3330c27-6cd8-407b-8536-fda4196c20ca\",\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"importance_level\": 0,\n  \"is_answered\": false,\n  \"is_draft\": false,\n  \"is_received\": false,\n  \"message_id\": \"4444-267d-4d9c-97ee-d0fc59c64201\",\n  \"participants\": [{\n  \"address\": \"john@caliopen.local\",\n  \"contact_ids\": [\"c-john-01\"],\n  \"label\": \"Jaune\",\n  \"protocol\": \"email\",\n  \"type\": \"From\"\n  }, {\n  \"address\": \"removeme@caliopen.local\",\n  \"label\": \"remove message\",\n  \"protocol\": \"email\",\n  \"type\": \"To\"\n  }],\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"bbbbbbbbb-267d-4d9c-97ee-d0fc59c64201\",\n  \"subject\": \"remove message one by one\",\n  \"protocol\": \"email\",\n  \"pi\": { \"technic\": 50, \"context\": 45, \"comportment\": 25, \"version\": 1 },\n  \"pi_message\": { \"transport\": 50, \"content\": 45, \"social\": 25 }\n},\n{\n  \"attachments\": [],\n  \"body\": \"a message of a collection to remove \",\n  \"excerpt\": \"a message of a collection to remove \",\n  \"date\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_sort\": \"2017-03-20T10:55:00.153000+00:00\",\n  \"discussion_id\": \"44440c27-6cd8-407b-8536-fda4196c20ca\",\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"importance_level\": 0,\n  \"is_answered\": false,\n  \"is_draft\": false,\n  \"is_received\": true,\n  \"message_id\": \"5555-267d-4d9c-97ee-d0fc59c64201\",\n  \"participants\": [{\n  \"address\": \"john@caliopen.local\",\n  \"contact_ids\": [\"c-john-01\"],\n  \"label\": \"Jaune\",\n  \"protocol\": \"email\",\n  \"type\": \"To\"\n  }, {\n  \"address\": \"removeme@caliopen.local\",\n  \"label\": \"remove message\",\n  \"protocol\": \"email\",\n  \"type\": \"From\"\n  }],\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"bbbbbbbbb-267d-4d9c-97ee-d0fc59c64201\",\n  \"subject\": \"remove messages\",\n  \"protocol\": \"email\",\n  \"pi\": { \"technic\": 50, \"context\": 45, \"comportment\": 25, \"version\": 1 },\n  \"pi_message\": { \"transport\": 50, \"content\": 45, \"social\": 25 }\n},\n{\n  \"attachments\": [],\n  \"body\": \"an other message of a collection to remove\",\n  \"excerpt\": \"an other message of a collection to remove\",\n  \"date\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n  \"date_sort\": \"2017-03-20T10:55:00.153000+00:00\",\n  \"discussion_id\": \"44440c27-6cd8-407b-8536-fda4196c20ca\",\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"importance_level\": 0,\n  \"is_answered\": false,\n  \"is_draft\": false,\n  \"is_received\": false,\n  \"message_id\": \"6666-267d-4d9c-97ee-d0fc59c64201\",\n  \"participants\": [{\n  \"address\": \"john@caliopen.local\",\n  \"contact_ids\": [\"c-john-01\"],\n  \"label\": \"Jaune\",\n  \"protocol\": \"email\",\n  \"type\": \"From\"\n  }, {\n  \"address\": \"removeme@caliopen.local\",\n  \"label\": \"remove message\",\n  \"protocol\": \"email\",\n  \"type\": \"To\"\n  }],\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"bbbbbbbbb-267d-4d9c-97ee-d0fc59c64201\",\n  \"subject\": \"remove message one by one\",\n  \"protocol\": \"email\",\n  \"pi\": { \"technic\": 50, \"context\": 45, \"comportment\": 25, \"version\": 1 },\n  \"pi_message\": { \"transport\": 50, \"content\": 45, \"social\": 25 }\n},\n{\n  \"attachment\": [],\n  \"discussion_id\": \"b1256c1e-7b65-4fe2-8960-a7d6c0c4a051\",\n  \"message_id\": \"74488b21-318c-4c60-be21-36cea07cfde2\",\n  \"is_answered\": true,\n  \"is_draft\": false,\n  \"is_received\": true,\n  \"importance_level\": 0,\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"1ac9a18b-2453-45e7-b778-528043de4373\",\n  \"subject\": \"Ils sont fous ces romains.\",\n  \"protocol\": \"twitter\",\n  \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n  \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n  \"participants\": [\n    {\n      \"address\": \"john@caliopen.local\",\n      \"contact_ids\": [\n        \"c-john-01\"\n      ],\n      \"label\": \"foo\",\n      \"protocol\": \"email\",\n      \"type\": \"From\"\n    },\n    {\n      \"address\": \"panoramix@caliopen.local\",\n      \"contacts_ids\": [\n        \"fedd1cc7-2db3-442f-a42a-24e21e98ff52\"\n      ],\n      \"protocol\": \"email\",\n      \"label\": \"Panoramix\",\n      \"type\": \"To\"\n    },\n    {\n      \"address\": \"asterix@caliopen.local\",\n      \"contacts_ids\": [\n        \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n      ],\n      \"protocol\": \"email\",\n      \"label\": \"Astérix\",\n      \"type\": \"To\"\n    }\n  ],\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"body\": \"Ils sont fous, ces gaulois.\\n\",\n  \"excerpt\": \"Ils sont fous, ces gaulois.\",\n  \"date\": \"0042-09-08T14:55:00+00:00\",\n  \"date_insert\": \"0042-09-08T15:00:00+00:00\",\n  \"date_sort\": \"0042-09-08T15:00:00+00:00\"\n},\n{\n  \"attachment\": [],\n  \"discussion_id\": \"b1256c1e-7b65-4fe2-8960-a7d6c0c4a051\",\n  \"message_id\": \"74488b21-318c-4c60-be21-36cea07cfde2\",\n  \"is_answered\": true,\n  \"is_draft\": false,\n  \"is_received\": true,\n  \"importance_level\": 0,\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"1ac9a18b-2453-45e7-b778-528043de4373\",\n  \"subject\": \"Ils sont fous ces romains.\",\n  \"type\": \"email\",\n  \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n  \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n  \"participants\": [\n      {\n          \"address\": \"panoramix@caliopen.local\",\n          \"contacts_ids\": [\n\t\t\t\t\t\t\"fedd1cc7-2db3-442f-a42a-24e21e98ff52\"\n          ],\n          \"protocol\": \"email\",\n          \"label\": \"Panoramix\",\n          \"type\": \"From\"\n      },\n      {\n          \"address\": \"john@caliopen.local\",\n          \"contact_ids\": [\n              \"c-john-01\"\n          ],\n          \"label\": \"foo\",\n          \"protocol\": \"email\",\n          \"type\": \"To\"\n      },\n      {\n          \"address\": \"asterix@caliopen.local\",\n          \"contacts_ids\": [\n              \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n          ],\n          \"protocol\": \"email\",\n          \"label\": \"Astérix\",\n          \"type\": \"To\"\n      }\n  ],\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"body\": \"Les champignons gardent toute leur saveur quand ils sont sautés.\\n\",\n  \"excerpt\": \"Les champignons gardent toute leur saveur quand ils\",\n  \"date\": \"0042-09-08T14:55:00+00:00\",\n  \"date_insert\": \"0042-09-08T15:00:00+00:00\",\n  \"date_sort\": \"0042-09-08T15:00:00+00:00\"\n},\n{\n  \"attachment\": [],\n  \"discussion_id\": \"b1256c1e-7b65-4fe2-8960-a7d6c0c4a051\",\n  \"message_id\": \"0bf58db0-b664-49e1-ade3-4f4e2424aa86\",\n  \"is_answered\": true,\n  \"is_draft\": false,\n  \"is_received\": true,\n  \"importance_level\": 0,\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"ebcdb6b2-ffac-4360-a9e7-cb44a091bc06\",\n  \"subject\": \"Ils sont fous ces romains.\",\n  \"protocol\": \"email\",\n  \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n  \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n  \"participants\": [\n      {\n          \"address\": \"panoramix@caliopen.local\",\n          \"contacts_ids\": [\n              \"df71ff9d-026b-4d18-b555-b8d110c57069\"\n          ],\n          \"protocol\": \"email\",\n          \"label\": \"Panoramix\",\n          \"type\": \"From\"\n      },\n      {\n          \"address\": \"john@caliopen.local\",\n          \"contact_ids\": [\n              \"c-john-01\"\n          ],\n          \"label\": \"Jaune\",\n          \"protocol\": \"email\",\n          \"type\": \"To\"\n      },\n      {\n          \"address\": \"asterix@caliopen.local\",\n          \"contacts_ids\": [\n              \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n          ],\n          \"protocol\": \"email\",\n          \"label\": \"Astérix\",\n          \"type\": \"To\"\n      }\n  ],\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"body\": \"Il faut les faire sauter !\\n\",\n  \"excerpt\": \"Il faut les faire sauter !\\n\",\n  \"date\": \"0042-09-08T14:49:00+00:00\",\n  \"date_insert\": \"0042-09-08T14:53:00+00:00\",\n  \"date_sort\": \"0042-09-08T14:53:00+00:00\"\n},\n{\n  \"attachment\": [],\n  \"discussion_id\": \"b1256c1e-7b65-4fe2-8960-a7d6c0c4a051\",\n  \"message_id\": \"956e52a5-5d73-4838-8d95-f5006973457c\",\n  \"is_answered\": true,\n  \"is_draft\": false,\n  \"is_received\": true,\n  \"importance_level\": 0,\n  \"privacy_features\": {},\n  \"raw_msg_id\": \"3ca3f788-8ba0-4b96-9f54-b5625d1c1a83\",\n  \"subject\": \"Ils sont fous ces romains.\",\n  \"protocol\": \"email\",\n  \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n  \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n  \"participants\": [\n      {\n          \"address\": \"asterix@caliopen.local\",\n          \"contacts_ids\": [\n              \"8165f843-c5c7-4409-9cc9-59054688d3cd\"\n          ],\n          \"protocol\": \"email\",\n          \"label\": \"Astérix\",\n          \"type\": \"From\"\n      },\n      {\n          \"address\": \"john@caliopen.local\",\n          \"contact_ids\": [\n              \"c-john-01\"\n          ],\n          \"label\": \"Jaune\",\n          \"protocol\": \"email\",\n          \"type\": \"To\"\n      },\n      {\n          \"address\": \"panoramix@caliopen.local\",\n          \"contacts_ids\": [\n              \"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"\n          ],\n          \"protocol\": \"email\",\n          \"label\": \"Panoramix\",\n          \"type\": \"To\"\n      }\n  ],\n  \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n  \"body\": \"Alors qu'allons-nous faire, ô Druide ?\\n\",\n  \"excerpt\": \"Alors qu'allons-nous faire, ô Druide ?\\n\",\n  \"date\": \"0042-09-08T14:40:00+00:00\",\n  \"date_insert\": \"0042-09-08T14:45:00+00:00\",\n  \"date_sort\": \"0042-09-08T14:45:00+00:00\"\n},\n{\n\t\"attachment\": [],\n\t\"discussion_id\": \"b1256c1e-7b65-4fe2-8960-a7d6c0c4a051\",\n\t\"message_id\": \"a0da20e1-0829-4f0f-b44a-7417177b4f4e\",\n\t\"is_answered\": true,\n\t\"is_draft\": false,\n\t\"is_received\": true,\n\t\"importance_level\": 0,\n\t\"privacy_features\": {},\n\t\"raw_msg_id\": \"0355031c-2ef7-4683-b75a-8b6b9a13f4f9\",\n\t\"subject\": \"Ils sont fous ces romains.\",\n\t\"protocol\": \"email\",\n\t\"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n\t\"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n\t\"participants\": [\n\t\t{\n\t\t\t\"address\": \"panoramix@caliopen.local\",\n\t\t\t\"contacts_ids\": [\n\t\t\t\t\"fd7bb4dd-9166-4e1b-86eb-cbcbddde23e8\"\n\t\t\t],\n\t\t\t\"protocol\": \"email\",\n\t\t\t\"label\": \"Panoramix\",\n\t\t\t\"type\": \"From\"\n\t\t},\n\t\t{\n\t\t\t\"address\": \"john@caliopen.local\",\n\t\t\t\"contact_ids\": [\n\t\t\t\t\"c-john-01\"\n\t\t\t],\n\t\t\t\"label\": \"Jaune\",\n\t\t\t\"protocol\": \"email\",\n\t\t\t\"type\": \"To\"\n\t\t},\n\t\t{\n\t\t\t\"address\": \"asterix@caliopen.local\",\n\t\t\t\"contacts_ids\": [\n\t\t\t\t\"2c8fb086-86da-4a87-9abc-668b73578f49\"\n\t\t\t],\n\t\t\t\"protocol\": \"email\",\n\t\t\t\"label\": \"Astérix\",\n\t\t\t\"type\": \"To\"\n\t\t}\n\t],\n\t\"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n\t\"body\": \"Parfois, j'ai l'impression que nos amis ne sont pas très sérieux… La bonne humeur des romains est peut-être mauvais signe pour nous.\\n\",\n\t\"excerpt\": \"Parfois, j'ai l'impression que nos amis ne sont pas\",\n\t\"date\": \"0042-09-08T14:33:00+00:00\",\n\t\"date_insert\": \"0042-09-08T14:39:00+00:00\",\n\t\"date_sort\": \"0042-09-08T14:39:00+00:00\"\n},\n{\n    \"attachment\": [],\n    \"discussion_id\": \"37e376ce-8be4-453c-a3bf-86eefaff1940\",\n    \"message_id\": \"193e0cf8-fce8-4e26-8fe2-85858009b81d\",\n    \"is_answered\": true,\n    \"is_draft\": false,\n    \"is_received\": true,\n    \"importance_level\": 0,\n    \"privacy_features\": {},\n    \"raw_msg_id\": \"6bb23fb9-6947-43b7-9b86-96207decabcd\",\n    \"subject\": \"Ils sont fous ces romains.\",\n    \"protocol\": \"twitter\",\n    \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n    \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n    \"participants\": [\n        {\n            \"address\": \"agecanonix@caliopen.local\",\n            \"contacts_ids\": [\n                \"51cbcbbd-3b88-44f0-963a-06740e546c33\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Agecanonix mais avec un nom très très très très long\",\n            \"type\": \"From\"\n        },\n        {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\n                \"c-john-01\"\n            ],\n            \"label\": \"Jaune\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"abraracourcix@caliopen.local\",\n            \"contacts_ids\": [\n                \"643fb7b3-c6fe-4f2b-9c0d-2ab671f482d5\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Abraracourcix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"obelix@caliopen.local\",\n            \"contacts_ids\": [\n                \"629e086d-7257-49d7-9080-61cbb98b93ee\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Obélix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"panoramix@caliopen.local\",\n            \"contacts_ids\": [\n                \"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Panoramix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"asterix@caliopen.local\",\n            \"contacts_ids\": [\n                \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Astérix\",\n            \"type\": \"To\"\n        }\n    ],\n    \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n    \"body\": \"Moi, je verrais plutôt les champignons en salade…\\n\",\n    \"excerpt\": \"Moi, je verrais plutôt les champignons en salade…\\n\",\n    \"date\": \"0042-09-08T14:27:00+00:00\",\n    \"date_insert\": \"0042-09-08T14:32:00+00:00\",\n    \"date_sort\": \"0042-09-08T14:32:00+00:00\"\n},\n{\n    \"attachment\": [],\n    \"discussion_id\": \"37e376ce-8be4-453c-a3bf-86eefaff1940\",\n    \"message_id\": \"e39919d5-d1cb-4887-8a42-95755a79f8b9\",\n    \"is_answered\": true,\n    \"is_draft\": false,\n    \"is_received\": true,\n    \"importance_level\": 0,\n    \"privacy_features\": {},\n    \"raw_msg_id\": \"bd29b3cf-7dec-423c-96f3-b1406c483984\",\n    \"subject\": \"Ils sont fous ces romains. Malheureusement ce sujet est trop long pour s'afficher sur une ligne\",\n    \"protocol\": \"email\",\n    \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n    \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n    \"participants\": [\n        {\n            \"address\": \"abraracourcix@caliopen.local\",\n            \"contacts_ids\": [\n                \"592a4bdb-085a-4e73-9aa8-fa3b41518b4b\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Abraracourcix but with a very long name\",\n            \"type\": \"From\"\n        },\n        {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\n                \"c-john-01\"\n            ],\n            \"label\": \"Jaune\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"agecanonix@caliopen.local\",\n            \"contacts_ids\": [\n                \"25e6de4d-eadb-434a-a12c-16a127e93c03\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Agecanonix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"obelix@caliopen.local\",\n            \"contacts_ids\": [\n                \"629e086d-7257-49d7-9080-61cbb98b93ee\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Obélix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"panoramix@caliopen.local\",\n            \"contacts_ids\": [\n                \"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Panoramix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"asterix@caliopen.local\",\n            \"contacts_ids\": [\n                \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Astérix\",\n            \"type\": \"To\"\n        }\n    ],\n    \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n    \"body\": \"Rien du tout ! Je suis le chef, justement ! Nous ferons une omelette !\\n J'ai aussi un extrementabusémentlongquidépassedelécransursmartphone et voilà\",\n    \"excerpt\": \"Rien du tout ! Je suis le chef, justement ! Nous fe\",\n    \"date\": \"0042-09-08T14:19:00+00:00\",\n    \"date_insert\": \"0042-09-08T14:24:00+00:00\",\n    \"date_sort\": \"0042-09-08T14:24:00+00:00\"\n},\n{\n    \"attachment\": [],\n    \"discussion_id\": \"37e376ce-8be4-453c-a3bf-86eefaff1940\",\n    \"message_id\": \"8fc1481d-5e06-463f-86f2-27ffbc2e1ef8\",\n    \"is_answered\": true,\n    \"is_draft\": false,\n    \"is_received\": true,\n    \"importance_level\": 0,\n    \"privacy_features\": {},\n    \"raw_msg_id\": \"dfb61e77-6bb7-45e6-826c-82622b617021\",\n    \"subject\": \"Ils sont fous ces romains.\",\n    \"type\": \"facebook\",\n    \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n    \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n    \"participants\": [\n        {\n            \"address\": \"obelix@caliopen.local\",\n            \"contacts_ids\": [\n                \"9c046b01-bc65-4769-aefc-3b0580ca9ab3\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Obélix\",\n            \"type\": \"From\"\n        },\n        {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\n                \"c-john-01\"\n            ],\n            \"label\": \"Jaune\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"agecanonix@caliopen.local\",\n            \"contacts_ids\": [\n                \"25e6de4d-eadb-434a-a12c-16a127e93c03\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Agecanonix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"abraracourcix@caliopen.local\",\n            \"contacts_ids\": [\n                \"643fb7b3-c6fe-4f2b-9c0d-2ab671f482d5\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Abraracourcix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"panoramix@caliopen.local\",\n            \"contacts_ids\": [\n                \"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Panoramix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"asterix@caliopen.local\",\n            \"contacts_ids\": [\n                \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Astérix\",\n            \"type\": \"To\"\n        }\n    ],\n    \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n    \"body\": \"Mais Abraracourcix notre chef…\\n\",\n    \"excerpt\": \"Mais Abraracourcix notre chef…\\n\",\n    \"date\": \"0042-09-08T14:12:00+00:00\",\n    \"date_insert\": \"0042-09-08T14:16:00+00:00\",\n    \"date_sort\": \"0042-09-08T14:16:00+00:00\"\n},\n{\n    \"attachment\": [],\n    \"discussion_id\": \"37e376ce-8be4-453c-a3bf-86eefaff1940\",\n    \"message_id\": \"9510f8ee-7701-4a23-bc82-4c9ae5c47778\",\n    \"is_answered\": true,\n    \"is_draft\": false,\n    \"is_received\": true,\n    \"importance_level\": 0,\n    \"privacy_features\": {},\n    \"raw_msg_id\": \"11025bc2-3513-44bf-9228-e73fa7474236\",\n    \"subject\": \"Ils sont fous ces romains.\",\n    \"protocol\": \"email\",\n    \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n    \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n    \"participants\": [\n        {\n            \"address\": \"abraracourcix@caliopen.local\",\n            \"contacts_ids\": [\n                \"18c156dc-9781-4851-abe6-f6966cdcaf38\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Abraracourcix\",\n            \"type\": \"From\"\n        },\n        {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\n                \"c-john-01\"\n            ],\n            \"label\": \"Jaune\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"agecanonix@caliopen.local\",\n            \"contacts_ids\": [\n                \"25e6de4d-eadb-434a-a12c-16a127e93c03\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Agecanonix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"obelix@caliopen.local\",\n            \"contacts_ids\": [\n                \"629e086d-7257-49d7-9080-61cbb98b93ee\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Obélix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"panoramix@caliopen.local\",\n            \"contacts_ids\": [\n                \"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Panoramix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"asterix@caliopen.local\",\n            \"contacts_ids\": [\n                \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Astérix\",\n            \"type\": \"To\"\n        }\n    ],\n    \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n    \"body\": \"C'est une omelette qu'il faut faire avec les champignons. Un vrai gourmet les mange en omelette !\\n\",\n    \"excerpt\": \"C'est une omelette qu'il faut faire avec les champi\",\n    \"date\": \"0042-09-08T14:05:00+00:00\",\n    \"date_insert\": \"0042-09-08T14:10:00+00:00\",\n    \"date_sort\": \"0042-09-08T14:10:00+00:00\"\n},\n{\n    \"attachment\": [],\n    \"discussion_id\": \"37e376ce-8be4-453c-a3bf-86eefaff1940\",\n    \"message_id\": \"de45139c-a74c-45b0-9f48-a75e0e568e6a\",\n    \"is_answered\": true,\n    \"is_draft\": false,\n    \"is_received\": true,\n    \"importance_level\": 0,\n    \"privacy_features\": {},\n    \"raw_msg_id\": \"4a1fa14c-be6d-41b3-947b-4eda3718050b\",\n    \"subject\": \"Ils sont fous ces romains.\",\n    \"type\": \"sms\",\n    \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n    \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n    \"participants\": [\n        {\n            \"address\": \"abraracourcix@caliopen.local\",\n            \"contacts_ids\": [\n                \"17bc2dde-e283-4800-8c84-abf55f372121\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Abraracourcix\",\n            \"type\": \"From\"\n        },\n        {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\n                \"c-john-01\"\n            ],\n            \"label\": \"Jaune\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"agecanonix@caliopen.local\",\n            \"contacts_ids\": [\n                \"25e6de4d-eadb-434a-a12c-16a127e93c03\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Agecanonix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"obelix@caliopen.local\",\n            \"contacts_ids\": [\n                \"629e086d-7257-49d7-9080-61cbb98b93ee\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Obélix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"panoramix@caliopen.local\",\n            \"contacts_ids\": [\n                \"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Panoramix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"asterix@caliopen.local\",\n            \"contacts_ids\": [\n                \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Astérix\",\n            \"type\": \"To\"\n        }\n    ],\n    \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n    \"body\": \"DE LA SOUPE ?!… C'EST TOUT CE QUE TU TROUVE À DIRE OBÉLIX ?!…\\n\",\n    \"excerpt\": \"DE LA SOUPE ?!… C'EST TOUT CE QUE TU TROUVE À DIRE \",\n    \"date\": \"0042-09-08T13:58:00+00:00\",\n    \"date_insert\": \"0042-09-08T14:02:00+00:00\",\n    \"date_sort\": \"0042-09-08T14:02:00+00:00\"\n},\n{\n    \"attachment\": [],\n    \"discussion_id\": \"37e376ce-8be4-453c-a3bf-86eefaff1940\",\n    \"message_id\": \"3202d2ee-9192-42a9-8330-b92297a885eb\",\n    \"is_answered\": true,\n    \"is_draft\": false,\n    \"is_received\": true,\n    \"importance_level\": 0,\n    \"privacy_features\": {},\n    \"raw_msg_id\": \"fbeb7ad7-b0b4-4508-a7c5-48cba66a3628\",\n    \"subject\": \"Ils sont fous ces romains.\",\n    \"type\": \"facebook\",\n    \"pi\": { \"technic\": 10, \"context\": 10, \"comportment\": 40, \"version\": 1 },\n    \"pi_message\": { \"transport\": 10, \"content\": 10, \"social\": 40 },\n    \"participants\": [\n        {\n            \"address\": \"obelix@caliopen.local\",\n            \"contacts_ids\": [\n                \"a836fea1-2e4a-49eb-9e0b-a25bd19c3902\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Obélix\",\n            \"type\": \"From\"\n        },\n        {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\n                \"c-john-01\"\n            ],\n            \"label\": \"Jaune\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"agecanonix@caliopen.local\",\n            \"contacts_ids\": [\n                \"25e6de4d-eadb-434a-a12c-16a127e93c03\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Agecanonix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"abraracourcix@caliopen.local\",\n            \"contacts_ids\": [\n                \"643fb7b3-c6fe-4f2b-9c0d-2ab671f482d5\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Abraracourcix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"panoramix@caliopen.local\",\n            \"contacts_ids\": [\n                \"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Panoramix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"asterix@caliopen.local\",\n            \"contacts_ids\": [\n                \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Astérix\",\n            \"type\": \"To\"\n        }\n    ],\n    \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n    \"body\": \"De la soupe. Avec les champignons, il faut faire de la soupe, c'est bon.\\n\",\n    \"excerpt\": \"De la soupe. Avec les champignons, il faut faire de\",\n    \"date\": \"0042-09-08T13:54:00+00:00\",\n    \"date_insert\": \"0042-09-08T13:55:00+00:00\",\n    \"date_sort\": \"0042-09-08T13:55:00+00:00\"\n},\n{\n    \"attachment\": [],\n    \"discussion_id\": \"37e376ce-8be4-453c-a3bf-86eefaff1940\",\n    \"message_id\": \"29b35424-f861-437a-b019-93c9eece1618\",\n    \"is_answered\": true,\n    \"is_draft\": false,\n    \"is_received\": true,\n    \"importance_level\": 0,\n    \"privacy_features\": {},\n    \"raw_msg_id\": \"16817826-6fd8-4ff8-83b6-5cd93c4402fa\",\n    \"subject\": \"Ils sont fous ces romains.\",\n    \"protocol\": \"twitter\",\n    \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n    \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n    \"participants\": [\n        {\n            \"address\": \"abraracourcix@caliopen.local\",\n            \"contacts_ids\": [\n                \"6b006c4f-782a-43b8-803e-518a8d7984a8\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Abraracourcix\",\n            \"type\": \"From\"\n        },\n        {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\n                \"c-john-01\"\n            ],\n            \"label\": \"Jaune\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"agecanonix@caliopen.local\",\n            \"contacts_ids\": [\n                \"25e6de4d-eadb-434a-a12c-16a127e93c03\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Agecanonix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"obelix@caliopen.local\",\n            \"contacts_ids\": [\n                \"629e086d-7257-49d7-9080-61cbb98b93ee\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Obélix\",\n            \"type\": \"To\"\n        },\n  {\n            \"address\": \"panoramix@caliopen.local\",\n            \"contacts_ids\": [\n                \"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Panoramix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"asterix@caliopen.local\",\n            \"contacts_ids\": [\n                \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Astérix\",\n            \"type\": \"To\"\n        }\n    ],\n    \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n    \"body\": \"Hmm… C'est étrange, ça, Agecanonix… Qu'allons-nous faire ?\\n\",\n    \"excerpt\": \"Hmm… C'est étrange, ça, Agecanonix, qu'allons-nous \",\n    \"date\": \"0042-09-08T13:47:00+00:00\",\n    \"date_insert\": \"0042-09-08T13:50:00+00:00\",\n    \"date_sort\": \"0042-09-08T13:50:00+00:00\"\n},\n{\n    \"attachment\": [],\n    \"discussion_id\": \"37e376ce-8be4-453c-a3bf-86eefaff1940\",\n    \"message_id\": \"38366a81-d890-4767-a8ba-c0e54c5c223c\",\n    \"is_answered\": true,\n    \"is_draft\": false,\n    \"is_received\": true,\n    \"importance_level\": 0,\n    \"privacy_features\": {},\n    \"raw_msg_id\": \"731594dd-e94f-4186-ac0d-d1ac5351df5d\",\n    \"subject\": \"Ils sont fous ces romains.\",\n    \"protocol\": \"twitter\",\n    \"pi\": { \"technic\": 30, \"context\": 10, \"comportment\": 1, \"version\": 1 },\n    \"pi_message\": { \"transport\": 30, \"content\": 10, \"social\": 1 },\n    \"participants\": [\n        {\n            \"address\": \"agecanonix@caliopen.local\",\n            \"contacts_ids\": [\n                \"6708a18a-a6c5-491b-b1f3-985b4c3d638a\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Agecanonix\",\n            \"type\": \"From\"\n        },\n        {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\n                \"c-john-01\"\n            ],\n            \"label\": \"Jaune\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"abraracourcix@caliopen.local\",\n            \"contacts_ids\": [\n                \"643fb7b3-c6fe-4f2b-9c0d-2ab671f482d5\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Abraracourcix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"obelix@caliopen.local\",\n            \"contacts_ids\": [\n                \"629e086d-7257-49d7-9080-61cbb98b93ee\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Obélix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"panoramix@caliopen.local\",\n            \"contacts_ids\": [\n                \"3e8769b9-77fb-4389-a00f-e2d1fd845fa1\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Panoramix\",\n            \"type\": \"To\"\n        },\n        {\n            \"address\": \"asterix@caliopen.local\",\n            \"contacts_ids\": [\n                \"2c8fb086-86da-4a87-9abc-668b73578f49\"\n            ],\n            \"protocol\": \"email\",\n            \"label\": \"Astérix\",\n            \"type\": \"To\"\n        }\n    ],\n    \"user_identities\": [\"aaaa-bbbb-cccc-ddd1\"],\n    \"body\": \"J'étais en train de cueillir des champignons du côté d'Aquarium, —ils sont bons, là-bas— quand j'ai entendu des cris de joie… Les romains ont l'air de très bonne humeur !\\n\",\n    \"excerpt\": \"J'étais en train de cueillir des champignons du côt\",\n    \"date\": \"0042-09-08T13:37:00+00:00\",\n    \"date_insert\": \"0042-09-08T13:43:00+00:00\",\n    \"date_sort\": \"0042-09-08T13:43:00+00:00\"\n}\n]\n"
  },
  {
    "path": "devtools/api-mock/messages/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v4 as uuidv4 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\nimport { actions as discussionActions } from '../discussions';\n\nconst actions = {\n  get: createAction('Get messages'),\n  post: createAction('Post message'),\n  patch: createAction('Patch message'),\n  delete: createAction('Delete message'),\n  actions: createAction('Actions message'),\n  patchTags: createAction('Patch message\\'s tags'),\n};\n\nconst notifiedMessageIds = '12345'.split('').map((v) => `1aaa-bbbb-000${v}`);\n\nconst getMessageFromNotifier = (messageId) => {\n  if (notifiedMessageIds.includes(messageId)) {\n    const now = new Date();\n\n    return {\n      attachments: [],\n      body: `notif ${messageId}`,\n      excerpt: `notif ${messageId}`,\n      date: now.toISOString(),\n      date_insert: now.toISOString(),\n      date_sort: now.toISOString(),\n      discussion_id: 'cd53e13a-267d-4d9c-97ee-d0fc59c64200',\n      user_identities: ['aaaa-bbbb-cccc-ddd1'],\n      importance_level: 0,\n      is_answered: false,\n      is_draft: false,\n      is_received: false,\n      message_id: messageId,\n      participants: [{\n        address: 'test@caliopen.local',\n        label: 'foo',\n        protocol: 'email',\n        type: 'Cc'\n      }, {\n        address: 'john@caliopen.local',\n        contact_ids: ['c-john-01'],\n        label: 'Jaune',\n        protocol: 'email',\n        type: 'From'\n      }, {\n        address: 'zoidberg@planet-express.tld',\n        contact_ids: ['0ba2e346-e4f8-4c45-9adc-eeb1d42fuie0'],\n        label: 'zoidberg',\n        protocol: 'email',\n        type: 'To'\n      }],\n    };\n  }\n\n  return undefined;\n}\n\n\nconst selectors = {\n  all: () => state => state.messages,\n  last: () => state => [...state.messages].pop(),\n  byQuery: ({ offset = 0, limit = 20, discussion_id, is_draft, is_received }) => createSelector(\n    [discussion_id ? selectors.byDiscussionId({ discussion_id }) :  selectors.all()],\n    messages => {\n      const end = new Number(offset) + new Number(limit);\n      return messages.filter(message => {\n        if (is_draft !== undefined && message.is_draft.toString() !== is_draft) {\n          return false;\n        }\n        if (is_received !== undefined && message.is_received.toString() !== is_received) {\n          return false;\n        }\n        return true;\n      }).slice(offset, end);\n    }\n  ),\n  byDiscussionId: ({ discussion_id }) => createSelector(\n    selectors.all(),\n    messages => messages.filter(message => message.discussion_id === discussion_id)\n  ),\n  lastLocation: () => createSelector(\n    selectors.last(),\n    message => ({ location: `/api/v2/messages/${message.message_id}` })\n  ),\n  byId: ({ message_id }) => createSelector(\n    selectors.all(),\n    messages => {\n      let result = messages.find(message => message.message_id === message_id);\n\n      if (!result) {\n        result = getMessageFromNotifier(message_id);\n      }\n\n      return result;\n    }\n  ),\n};\n\nconst filterAuthor = participants => participants.filter(participant => participant.type !== 'From');\nconst reduceParticipants = message => [\n  ...(message.participants ? filterAuthor(message.participants) : []),\n  {\n    address: 'john@caliopen.local',\n    contact_ids: ['c-john-01'],\n    label: 'Jaune john',\n    protocol: 'email',\n    type: 'From'\n  },\n];\n\nconst reducer = {\n  [actions.get]: state => state.sort((a, b) => new Date(b.date_sort) - new Date(a.date_sort)),\n  [actions.post]: (state, { body, req: { discussionId } }) => ([\n    ...state,\n    {\n      discussion_id: discussionId,\n      ...body,\n      excerpt: body.body.slice(0, 30) + '...',\n      message_id: body.message_id || uuidv4(),\n      is_draft: true,\n      is_unread: false,\n      is_received: false,\n      date: Date.now(),\n      date_insert: Date.now(),\n      date_sort: Date.now(),\n      pi: { technic: 50, context: 45, comportment: 25, version: 1 },\n      pi_message: { transport: 50, content: 45, social: 25 },\n      participants: reduceParticipants(body),\n    },\n  ]),\n  [actions.patch]: (state, { params, body }) => {\n    const nextState = [...state];\n    const original = state.find(message => message.message_id === params.message_id);\n    if (!original) {\n      throw `message w/ id ${params.message_id} not found`;\n    }\n    const index = nextState.indexOf(original);\n    const { current_state, ...props } = body;\n    nextState[index] = {\n      ...original,\n      ...props,\n      participants: state[index].is_draft ? reduceParticipants(props) : nextState[index].participants,\n    };\n\n    return nextState;\n  },\n  [actions.delete]: (state, { params, body }) => {\n    const original = state.find(message => message.message_id === params.message_id);\n    if (!original) {\n      throw `message w/ id ${params.message_id} not found`;\n    }\n\n    return [...state.filter(message => message.message_id !== params.message_id)];\n  },\n  [actions.patchTags]: (state, { params, body }) => {\n    const original = state.find(message => message.message_id === params.message_id);\n    if (!original) {\n      throw `message w/ id ${params.message_id} not found`;\n    }\n\n    const index = state.indexOf(original);\n    const nextState = [...state];\n    const { tags } = body;\n\n    nextState[index] = {\n      ...original,\n      tags,\n    };\n\n    return nextState;\n  },\n  [actions.actions]: (state, { params, body }) => {\n    const original = state.find(message => message.message_id === params.message_id);\n    if (!original) {\n      throw `message w/ id ${params.message_id} not found`;\n    }\n    const index = state.indexOf(original);\n\n    return body.actions.reduce((acc, action) => {\n      switch (action) {\n        case 'send':\n          acc[index] = { ...acc[index], is_draft: false, date: Date.now() };\n\n          return acc;\n        case 'set_read':\n          acc[index] = { ...acc[index], is_unread: false };\n\n          return acc;\n        case 'set_unread':\n          acc[index] = { ...acc[index], is_unread: true };\n\n          return acc;\n        default:\n          throw new Error(`Unexpected action \"${action}\"`);\n      }\n    }, [...state]);\n  },\n};\n\nconst routes = {\n  'GET /v2/messages/': {\n    action: actions.get,\n    selector: selectors.byQuery,\n    status: 200,\n    middlewares: [createCollectionMiddleware('messages')],\n  },\n  'GET /v2/messages/:message_id': {\n    action: actions.get,\n    selector: selectors.byId,\n    status: 200,\n    middlewares: [data => (req, res, next) => {\n      if (!data) {\n        res.status(404);\n      }\n    }],\n  },\n  'DELETE /v1/messages/:message_id': {\n    action: actions.delete,\n    status: 204,\n  },\n  'POST /v2/messages/:message_id/actions': {\n    action: actions.actions,\n    selector: selectors.byId,\n    status: 200,\n  },\n  'PATCH /v2/messages/:message_id/tags': {\n    action: actions.patchTags,\n    status: 204,\n  },\n  'POST /v1/messages/': {\n    action: [discussionActions.create, actions.post],\n    selector: selectors.lastLocation,\n    status: 200,\n  },\n  'PATCH /v1/messages/:message_id': {\n    action: actions.patch,\n    status: 204,\n  },\n};\n\nexport default {\n  name: 'messages',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/notifications/data.json",
    "content": "{\n  \"total\": 4,\n  \"notifications\": [\n    {\n      \"emitter\": \"imap_worker\",\n      \"notif_id\": \"aaaa-bbbb-0001\",\n      \"type\": \"new_message\",\n      \"body\": {\n        \"size\": 666\n      }\n    },\n    {\n      \"emitter\": \"imap_worker\",\n      \"notif_id\": \"aaaa-bbbb-0002\",\n      \"type\": \"new_message\",\n      \"body\": {\n        \"size\": 1,\n        \"elements\": [\n          { \"body\": { \"message_id\": \"1aaa-bbbb-0001\", \"discussion_id\": \"\" } }\n        ]\n      }\n    },\n    {\n      \"emitter\": \"contacts\",\n      \"notif_id\": \"aaaa-bbbb-0003\",\n      \"body\": \"success\",\n      \"type\": \"event\",\n      \"user_id\": \"u-john-01\"\n    },\n    {\n      \"emitter\": \"imap_worker\",\n      \"notif_id\": \"aaaa-bbbb-0004\",\n      \"type\": \"new_message\",\n      \"body\": {\n        \"size\": 3,\n        \"elements\": [\n          { \"body\": { \"message_id\": \"1aaa-bbbb-0002\", \"discussion_id\": \"\" } },\n          { \"body\": { \"message_id\": \"1aaa-bbbb-0003\", \"discussion_id\": \"\" } },\n          { \"body\": { \"message_id\": \"1aaa-bbbb-0005\", \"discussion_id\": \"\" } }\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "devtools/api-mock/notifications/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nimport initialData from './data.json';\n\nconst actions = {\n  get: createAction('Get notifications'),\n  delete: createAction('Delete notifications'),\n  resetDev: createAction('Reset fixtures for dev purposes (see tests)'),\n};\n\nconst selectors = {\n  all: () => state => state.notifications,\n};\n\nconst reducer = {\n  [actions.delete]: (state) => {\n    return {\n      ...state,\n      total: 0,\n      notifications: [],\n    };\n  },\n  [actions.resetDev]: () => {\n    return {\n      ...initialData,\n    };\n  },\n};\n\nconst routes = {\n  'GET': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n  },\n  'DELETE': {\n    action: actions.delete,\n    status: 204,\n  },\n  'GET /reset-dev': {\n    action: actions.resetDev,\n    status: 200,\n  },\n};\n\nexport default {\n  name: 'notifications',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v2/notifications',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/oauth-mock/data.json",
    "content": "[]\n"
  },
  {
    "path": "devtools/api-mock/oauth-mock/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { actions as remoteIdentActions } from '../remote_identities';\n\nconst actions = {\n  get: createAction('Get remote_identities'),\n};\n\nconst routes = {\n  'GET /:providerName': {\n    action: [remoteIdentActions.createOauth, actions.get],\n    status: 200,\n    middlewares: [data => (req, res, next) => {\n      const providerName = req.params.providerName;\n\n      switch (providerName) {\n        case 'gmail':\n        case 'twitter':\n          res.send(`<div><a href=\"/api/v2/providers/${providerName}/callback\">authorize ${providerName}</a></div>`);\n          break;\n        default:\n          throw new Error('provider not found');\n      }\n    }],\n  },\n};\n\nexport default {\n  name: 'oauth-mock',\n  endpoint: '/api/oauth-mock',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/package.json",
    "content": "{\n  \"name\": \"api-mock\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"babel-core\": \"^6.24.0\",\n    \"babel-polyfill\": \"^6.23.0\",\n    \"babel-preset-es2015\": \"^6.24.0\",\n    \"babel-preset-stage-2\": \"^6.22.0\",\n    \"bouchon\": \"^0.4.0\",\n    \"uuid\": \"^3.0.1\"\n  },\n  \"scripts\": {\n    \"start\": \"bouchon -d . -p 31415\"\n  },\n  \"babel\": {\n    \"presets\": [\n      \"es2015\",\n      \"stage-2\"\n    ]\n  }\n}\n"
  },
  {
    "path": "devtools/api-mock/participants/data.json",
    "content": "[{\n  \"address\": \"bender@caliopen.local\",\n  \"contact_id\": \"92d3727a-eefc-4537-b879-85f1c9d197bb\",\n  \"label\": \"Bender\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"zoidberg@caliopen.local\",\n  \"contact_id\": \"0ba2e346-e4f8-4c45-9adc-eeb1d42fuie0\",\n  \"label\": \"Zoidberg\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"@bender\",\n  \"contact_id\": \"92d3727a-eefc-4537-b879-85f1c9d197bb\",\n  \"label\": \"@Bender\",\n  \"protocol\": \"twitter\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"@zoidberg\",\n  \"contact_id\": \"0ba2e346-e4f8-4c45-9adc-eeb1d42fuie0\",\n  \"label\": \"@Zoidberg\",\n  \"protocol\": \"twitter\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"leela@john.doe\",\n  \"contact_id\": \"19c3ui42-e3ba-46e7-984f-4c3e8de11c05\",\n  \"label\": \"Leela\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"fry@caliopen.local\",\n  \"contact_id\": \"92d3907a-eeui-4537-b229-85f1c9d197bb\",\n  \"label\": \"Fry\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"amy@caliopen.local\",\n  \"contact_id\": \"0ba2e346-e4f8-4c45-999c-eeb1d42f07e0\",\n  \"label\": \"Amy\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"professor@planet.express\",\n  \"contact_id\": \"19c3ce42-e3ba-46e7-902f-4cui8de11c05\",\n  \"label\": \"Farnsworth\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"lrrr@caliopen.local\",\n  \"contact_id\": \"9ui3907a-eefc-4537-b229-85f1c9d197bb\",\n  \"label\": \"Lrrr\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"stephen@caliopen.local\",\n  \"contact_id\": \"98o3907a-eefc-6724-bv28-85f1c9d197bb\",\n  \"label\": \"Stephen Hawking\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"kroker@caliopen.local\",\n  \"contact_id\": \"0327707a-eefc-6724-bv28-85f1c9d197bb\",\n  \"label\": \"Kif\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"zapp@caliopen.local\",\n  \"contact_id\": \"5488707a-eefc-6724-bv28-85f1c9d197bb\",\n  \"label\": \"Zapp Brannigan\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"francine@doop\",\n  \"contact_id\": \"19c3ce42-e3ba-46e7-902f-4cui8d\",\n  \"label\": \"Elzar\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"algore@caliopen.local\",\n  \"contact_id\": \"a3ce42-e3ba-46e7-902f-4cfdsfsui8d\",\n  \"label\": \"Scruffy\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"Conrad@caliopen.local\",\n  \"contact_id\": \"a3ce42-e3ba-46e7-dfsd902f-4cui8d\",\n  \"label\": \"Hermès\",\n  \"protocol\": \"email\",\n  \"source\": \"contact\"\n}, {\n  \"address\": \"flexo@caliopen.local\",\n  \"label\": \"Flexo\",\n  \"protocol\": \"email\",\n  \"source\": \"participant\"\n}, {\n  \"address\": \"calc@caliopen.local\",\n  \"label\": \"Calculon\",\n  \"protocol\": \"email\",\n  \"source\": \"participant\"\n}, {\n  \"address\": \"mom@john.doe\",\n  \"label\": \"Mom\",\n  \"protocol\": \"email\",\n  \"source\": \"participant\"\n}, {\n  \"address\": \"morbo@caliopen.local\",\n  \"label\": \"Morbo the Annihilator\",\n  \"protocol\": \"email\",\n  \"source\": \"participant\"\n}, {\n  \"address\": \"Panucci@caliopen.local\",\n  \"label\": \"Panucci\",\n  \"protocol\": \"email\",\n  \"source\": \"participant\"\n}, {\n  \"address\": \"nib@planet.express\",\n  \"label\": \"Nibbler\",\n  \"protocol\": \"email\",\n  \"source\": \"participant\"\n}, {\n  \"address\": \"Hypnotoad@caliopen.local\",\n  \"label\": \"Hypnotoad\",\n  \"protocol\": \"email\",\n  \"source\": \"participant\"\n}]\n"
  },
  {
    "path": "devtools/api-mock/participants/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nconst actions = {\n};\n\nconst match = ({ item, props, searchTerms }) => props.find(propName =>\n      item[propName] && item[propName].toLowerCase().includes(searchTerms.toLowerCase()));\n\nconst selectors = {\n  all: () => state => state.participants,\n  byQuery: ({ context, q }) => createSelector(\n    selectors.all(),\n    participants => participants.filter(item => match({\n      item, searchTerms: q, props: ['address', 'label'],\n    }))\n  ),\n};\n\nconst reducer = {\n};\n\nconst routes = {\n  'GET /suggest': {\n    action: actions.get,\n    selector: selectors.byQuery,\n    status: 200,\n  },\n};\n\nexport default {\n  name: 'participants',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v2/participants',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/providers/data.json",
    "content": "[\n  {\n    \"infos\": {\n      \"client_id\": \"\",\n      \"client_secret\": \"\",\n      \"imapserver\": \"imap.gmail.com:993\",\n      \"smtpserver\": \"smtp.gmail.com:465\"\n    },\n    \"name\": \"gmail\",\n    \"protocol\": \"email\",\n    \"Capabilities\": null,\n    \"FetchItems\": null\n  },\n  {\n    \"infos\": {\n      \"consumer_key\": \"\",\n      \"consumer_secret\": \"\"\n    },\n    \"name\": \"twitter\",\n    \"protocol\": \"twitter\",\n    \"Capabilities\": null,\n    \"FetchItems\": null\n  },\n  {\n    \"infos\": {\n      \"consumer_key\": \"\",\n      \"consumer_secret\": \"\"\n    },\n    \"name\": \"mastodon\",\n    \"protocol\": \"mastodon\",\n    \"Capabilities\": null,\n    \"FetchItems\": null\n  }\n]\n"
  },
  {
    "path": "devtools/api-mock/providers/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nconst actions = {\n  get: createAction('Get provider'),\n  postRemote: createAction('post remote_identities'),\n};\n\nconst twitterPopupUrl = 'https://api.twitter.com/oauth/authorize?oauth_token=<token>';\nconst oauthUrlCallbackHost = ''; // should be hardcoded but it can be localhost:4000 or test-frontend:4000 according to current test env\n\nconst selectors = {\n  all: () => state => state.providers,\n  byId: ({ providerName }) => createSelector(\n    selectors.all(),\n    providers => {\n      const result = providers.find(provider => provider.name === providerName);\n\n      if (!result) {\n        throw new Error('provider not found');\n      }\n\n      return {\n        ...result,\n        oauth_callback_uri: `/api/v2/providers/${providerName}/callback`,\n        oauth_request_url: `${oauthUrlCallbackHost}/api/oauth-mock/${providerName}`,\n      };\n    }\n  ),\n  none: () => state => {},\n};\n\nconst reducer = {\n};\n\nconst routes = {\n  'GET /': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n    middlewares: [createCollectionMiddleware('providers')],\n  },\n  'GET /:providerName/callback': {\n    selector: selectors.none,\n    status: 200,\n  },\n  'GET /:providerName': {\n    action: actions.get,\n    selector: selectors.byId,\n    status: 200,\n  },\n};\n\nexport default {\n  name: 'providers',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v2/providers',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/remote_identities/data.json",
    "content": "[]\n"
  },
  {
    "path": "devtools/api-mock/remote_identities/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nexport const actions = {\n  get: createAction('Get remote_identities'),\n  post: createAction('post remote_identities'),\n  createOauth: createAction('post remote_identities'),\n  patch: createAction('patch remote_identities'),\n  delete: createAction('delete remote_identities'),\n};\n\nconst selectors = {\n  all: () => state => state.remote_identities,\n  last: () => state => [...state.remote_identities].pop(),\n  lastLocation: () => createSelector(\n    selectors.last(),\n    ({ identity_id }) => ({\n      location: `/api/v2/remote_identity/${identity_id}`,\n      identity_id,\n      // FIXME: to del; backend inconsistency for now\n      remote_id: identity_id,\n    })\n  ),\n  byId: ({ identity_id }) => createSelector(\n    selectors.all(),\n    remoteIdentity => remoteIdentity.find(identity => identity.identity_id === identity_id)\n  ),\n};\n\nconst reducer = {\n  [actions.get]: state => state,\n  [actions.post]: (state, { body }) => ([\n    ...state,\n    {\n      identity_id: uuidv1(),\n      ...body,\n    }\n  ]),\n  [actions.createOauth]: (state, { req }) => {\n    const { providerName } = req.params;\n\n    switch (providerName) {\n      case 'gmail':\n        return [\n          ...state,\n          {\n            identity_id: uuidv1(),\n            display_name: 'dev@gmail',\n            identifier: 'dev@gmail.com',\n            infos: {\n              authtype:'oauth2',\n              inserver:'imap.gmail.com:993',\n              lastseenuid:'',\n              lastsync:'',\n              outserver:'smtp.gmail.com:465',\n              pollinterval:'15',\n              provider:'gmail',\n              uidvalidity:''\n            },\n            protocol: 'email',\n            status: 'inactive',\n            type: 'remote',\n          },\n        ];\n      case 'twitter':\n        return [\n          ...state,\n          {\n            identity_id: uuidv1(),\n            display_name: '@dev',\n            identifier: '@dev',\n            infos: {\n              authtype:'oauth2',\n              lastseenuid:'',\n              lastsync:'',\n              pollinterval:'15',\n              provider:'twitter',\n              uidvalidity:''\n            },\n            protocol: 'twitter',\n            status: 'inactive',\n            type: 'remote',\n          },\n        ];\n      default:\n        throw new Error('unknown provider');\n    }\n  },\n  [actions.patch]: (state, { params, body }) => {\n    const nextState = [...state];\n    const original = state.find(remoteIdentity => remoteIdentity.identity_id === params.identity_id);\n    if (!original) {\n      throw `remoteIdentity w/ id ${params.identity_id} not found`;\n    }\n    const index = nextState.indexOf(original);\n    const { current_state, ...props } = body;\n    nextState[index] = {\n      ...original,\n      ...props,\n    };\n\n    return nextState;\n  },\n  [actions.delete]: (state, { params, body }) => {\n    const original = state.find(remoteIdentity => remoteIdentity.identity_id === params.identity_id);\n    if (!original) {\n      throw `remoteIdentity w/ id ${params.identity_id} not found`;\n    }\n\n    return [...state.filter(remoteIdentity => remoteIdentity.identity_id !== params.identity_id)];\n  },\n};\n\nconst routes = {\n  'GET /': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n    middlewares: [createCollectionMiddleware('remote_identities')],\n  },\n  'POST /': {\n    action: actions.post,\n    selector: selectors.lastLocation,\n    status: 200,\n  },\n  'GET /:identity_id': {\n    action: actions.get,\n    selector: selectors.byId,\n    status: 200,\n  },\n  'PATCH /:identity_id': {\n    action: actions.patch,\n    status: 204,\n  },\n  'DELETE /:identity_id': {\n    action: actions.delete,\n    selector: selectors.byId,\n    status: 204,\n  },\n};\n\nexport default {\n  name: 'remote_identities',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v2/identities/remotes',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/search/data.json",
    "content": "{\n  \"total\": 2,\n  \"messages_hits\": {\n    \"total\": 2,\n    \"messages\": [\n      {\n        \"highlights\": { \"???\": [\"I like cooking foobar. You, minion. Lift my arm.\"]},\n        \"document\": {\n          \"attachments\": [],\n          \"body\": \"<p>It's okay, Bender. I like cooking too. You, minion. Lift my arm. AFTER H=\\r\\nIM! I don't know what you did, Fry, but once again, you screwed up! Now all\\r\\nthe planets are gonna start cracking wise abo\",\n          \"excerpt\": \"It's okay, Bender. I like cooking too. You, minion. Lift my arm. AFTER HIM! I don't ...\",\n          \"date\": \"2017-03-21T10:55:00.153000+00:00\",\n          \"date_insert\": \"2017-03-21T10:55:00.153000+00:00\",\n          \"discussion_id\": \"cd53e13a-267d-4d9c-97ee-d0fc59c64200\",\n          \"identities\": [{\n            \"display_name\": \"Jaune\",\n            \"identifier\": \"john@caliopen.local\"\n          }],\n          \"importance_level\": 0,\n          \"is_answered\": false,\n          \"is_draft\": false,\n          \"message_id\": \"11111111-267d-4d9c-97ee-d0fc59c64201\",\n          \"participants\": [{\n            \"address\": \"test@caliopen.local\",\n            \"contact_ids\": [\"1039cdcc-1f6f-4b5d-9c8a-5d7c711f357f\"],\n            \"label\": \"foo\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n          }, {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\"1039cdcc-1f6f-4b5d-9c8a-5d7c711f357f\"],\n            \"label\": \"Jaune john\",\n            \"protocol\": \"email\",\n            \"type\": \"Cc\"\n          }, {\n            \"address\": \"zoidberg@planet-express.tld\",\n            \"contact_ids\": [\"0ba2e346-e4f8-4c45-9adc-eeb1d42f07e0\"],\n            \"label\": \"zoidberg\",\n            \"protocol\": \"email\",\n            \"type\": \"From\"\n          }],\n          \"privacy_features\": {},\n          \"raw_msg_id\": \"aaaaaaaa-267d-4d9c-97ee-d0fc59c64201\",\n          \"subject\": \"It's okay, Bender. I like cooking too\",\n          \"tags\": [\"robot\", \"IMPORTANT\"],\n          \"type\": \"email\",\n          \"pi\": { \"technic\": 50, \"context\": 45, \"comportment\": 25, \"version\": 1 },\n          \"user_id\": \"\"\n        }\n      },\n      {\n        \"highlights\": { \"???\": [\"He's Foobar too powerful! Can I use the gun? \"]},\n        \"document\": {\n          \"attachments\": [],\n          \"body\": \"Fry! Stay back! He's too powerful! Can I use the gun? I barely knew Philip, but as a clergyman I have no problem telling his most intimate friends all about him. What are you hacking off? Is it my torso?! 'It is!' My precious torso!\",\n          \"excerpt\": \"Fry! Stay back! He's too powerful! Can I use the gun? I barely knew Philip, but as a ...\",\n          \"date\": \"2017-03-21T12:55:00.153000+00:00\",\n          \"date_insert\": \"2017-03-21T12:55:00.153000+00:00\",\n          \"discussion_id\": \"cd53e13a-267d-4d9c-97ee-d0fc59c64200\",\n          \"identities\": [{\n            \"display_name\": \"Jaune\",\n            \"identifier\": \"john@caliopen.local\"\n          }],\n          \"importance_level\": 0,\n          \"is_answered\": false,\n          \"is_draft\": false,\n          \"message_id\": \"11111111-267d-4d9c-97ee-d0fc59c64202\",\n          \"participants\": [{\n            \"address\": \"test@caliopen.local\",\n            \"contact_ids\": [\"1039cdcc-1f6f-4b5d-9c8a-5d7c711f357f\"],\n            \"label\": \"foo\",\n            \"protocol\": \"email\",\n            \"type\": \"Cc\"\n          }, {\n            \"address\": \"john@caliopen.local\",\n            \"contact_ids\": [\"1039cdcc-1f6f-4b5d-9c8a-5d7c711f357f\"],\n            \"label\": \"Jaune john\",\n            \"protocol\": \"email\",\n            \"type\": \"From\"\n          }, {\n            \"address\": \"zoidberg@planet-express.tld\",\n            \"contact_ids\": [\"0ba2e346-e4f8-4c45-9adc-eeb1d42f07e0\"],\n            \"label\": \"zoidberg\",\n            \"protocol\": \"email\",\n            \"type\": \"To\"\n          }],\n          \"privacy_features\": {},\n          \"raw_msg_id\": \"aaaaaaaa-267d-4d9c-97ee-d0fc59c64202\",\n          \"tags\": [\"robot\", \"INBOX\"],\n          \"type\": \"email\",\n          \"pi\": { \"technic\": 30, \"context\": 25, \"comportment\": 25, \"version\": 1 },\n          \"user_id\": \"\"\n        }\n      }\n    ]\n  },\n  \"contact_hits\": {\n    \"total\": 0,\n    \"contacts\": []\n  }\n}\n"
  },
  {
    "path": "devtools/api-mock/search/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nconst actions = {\n};\n\nconst match = ({ item, props, searchTerms }) => props.find(propName =>\n      item[propName] && item[propName].toLowerCase().includes(searchTerms.toLowerCase()));\n\nconst selectors = {\n  all: () => state => state.search,\n};\n\nconst reducer = {\n};\n\nconst routes = {\n  'GET /': {\n    selector: selectors.all,\n    status: 200,\n  },\n};\n\nexport default {\n  name: 'search',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v2/search',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/settings/data.json",
    "content": "{\n  \"default_locale\": \"en-US\",\n  \"message_display_format\": \"rich_text\",\n  \"contact_display_format\": \"given_name, family_name\",\n  \"contact_display_order\": \"given_name\",\n  \"notification_enabled\": true,\n  \"notification_message_preview\": \"always\",\n  \"notification_sound_enabled\": true,\n  \"notification_delay_disappear\": 10\n}\n"
  },
  {
    "path": "devtools/api-mock/settings/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nconst actions = {\n  get: createAction('Get settings'),\n  patch: createAction('Patch settings'),\n};\n\nconst selectors = {\n  all: () => state => state.settings,\n};\n\nconst reducer = {\n  [actions.patch]: (state, { params, body }) => {\n    const { current_state, ...props } = body;\n\n    return  {\n      ...state,\n      ...props,\n    };\n  },\n};\n\nconst routes = {\n  'GET': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n  },\n  'PATCH': {\n    action: actions.patch,\n    status: 204,\n  },\n};\n\nexport default {\n  name: 'settings',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v1/settings',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/api-mock/tags/data.json",
    "content": "[\n  {\n    \"name\": \"INBOX\",\n    \"type\": \"system\"\n  },\n  {\n    \"name\": \"IMPORTANT\",\n    \"type\": \"system\"\n  },\n  {\n    \"name\": \"SPAM\",\n    \"type\": \"system\"\n  },\n  {\n    \"name\": \"foobar\",\n    \"label\": \"Foobar\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"planet-express\",\n    \"label\": \"Planet Express\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"humans\",\n    \"label\": \"Humans\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"aliens\",\n    \"label\": \"Aliens\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"amphibians\",\n    \"label\": \"Amphibians\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"robot\",\n    \"label\": \"Robot\",\n    \"type\": \"user\"\n  }\n]\n"
  },
  {
    "path": "devtools/api-mock/tags/index.js",
    "content": "import { createAction, createSelector } from 'bouchon';\nimport { v1 as uuidv1 } from 'uuid';\nimport createCollectionMiddleware from '../collection-middleware';\n\nconst actions = {\n  get: createAction('Get tags'),\n  post: createAction('Create tag'),\n  delete: createAction('Delete a tag'),\n  patch: createAction('Patch a tag'),\n};\n\nconst selectors = {\n  all: () => state => state.tags,\n  last: () => state => [...state.tags].pop(),\n  byName: ({ name }) => createSelector(\n    selectors.all(),\n    tags => tags.find(tag => tag.name === name)\n  ),\n  location: () => createSelector(\n    selectors.last(),\n    tag => ({ location: `/api/v2/tags/${tag.name}` })\n  ),\n};\n\nconst reducer = {\n  [actions.get]: state => state,\n  [actions.post]: (state, { body, res }) => {\n    const name = body.name || body.label.replace(' ', '-').toLowerCase();\n    const exists = state.some(tag => tag.name === name);\n\n    if (exists) {\n      res.status(409).send({\n        errors: [{ message: 'Tag already exist', code: 422, name: 'uniqueness' }],\n      });\n\n      return state;\n    }\n\n    return [\n      ...state,\n      { ...body, type: 'user', label: body.label || name, name },\n    ];\n  },\n  [actions.delete]: (state, { params }) => {\n    const copy = state.slice(0);\n    return [...state].filter(tag => tag.name !== params.name);\n  },\n  [actions.patch]: (state, { params, body }) => {\n    const nextState = [...state];\n    const original = state.find(tag => tag.name === params.name);\n    if (!original) {\n      throw `tag w/ id ${params.name} not found`;\n    }\n    const index = nextState.indexOf(original);\n    const { current_state, ...props } = body;\n    nextState[index] = {\n      ...original,\n      ...props,\n    };\n\n    return nextState;\n  },\n};\n\nconst routes = {\n  'GET /': {\n    action: actions.get,\n    selector: selectors.all,\n    status: 200,\n    middlewares: [createCollectionMiddleware('tags')],\n  },\n  'POST /': {\n    action: actions.post,\n    selector: selectors.location,\n    status: 200,\n  },\n  'GET /:name': {\n    action: actions.get,\n    selector: selectors.byName,\n    status: 200,\n  },\n  'DELETE /:name': {\n    action: actions.delete,\n    status: 204,\n  },\n  'PATCH /:name': {\n    action: actions.patch,\n    status: 204,\n  },\n};\n\nexport default {\n  name: 'tags',\n  data: require('./data.json'),\n  reducer: reducer,\n  endpoint: '/api/v2/tags',\n  routes: routes,\n};\n"
  },
  {
    "path": "devtools/clean-dev-storage.sh",
    "content": "#!/bin/bash\n#\n# DEVELOPMENT ONLY SCRIPT\n#\n# Clean docker containers and volumes where data are stored\n\ndocker-compose kill cassandra elasticsearch object_store\ndocker-compose rm -f cassandra elasticsearch object_store\ndocker volume rm devtools_db devtools_index devtools_store"
  },
  {
    "path": "devtools/conf/proxy-api.conf",
    "content": "\nuser  nginx;\nworker_processes  1;\n\nerror_log  /var/log/nginx/error.log warn;\npid        /var/run/nginx.pid;\n\n\nevents {\n    worker_connections  1024;\n}\n\n\nhttp {\n    include       /etc/nginx/mime.types;\n    default_type  application/octet-stream;\n\n    log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n    access_log  /var/log/nginx/access.log  main;\n\n    sendfile        on;\n    #tcp_nopush     on;\n\n    keepalive_timeout  65;\n\n    #gzip  on;\n\n    include /etc/nginx/conf.d/*.conf;\n    upstream apiv1 {\n        least_conn;\n        server apiv1:6543;\n    }\n\n    upstream apiv2 {\n        least_conn;\n        server apiv2:6544;\n    }\n\n    server {\n        listen    31415;\n        listen    [::]:31415;\n        server_name api;\n\n        client_max_body_size 5m;\n\n        location /api/v1 {\n                proxy_pass http://apiv1;\n        }\n\n        location /api/v2 {\n                proxy_pass  http://apiv2;\n        }\n    }\n}\n\n"
  },
  {
    "path": "devtools/docker-compose.staging.yml",
    "content": "version: '2.1'\nvolumes:\n  index:\n    driver: local\n  db:\n    driver: local\n  store:\n    driver: local\nservices:\n  # Proxy API\n  proxyapi:\n    image: nginx\n    depends_on:\n      - apiv1\n      - apiv2\n    expose:\n      - \"31415\"\n    volumes:\n      - ./conf/proxy-api.conf:/etc/nginx/nginx.conf\n  # Caliopen APIv2\n  apiv2:\n    image: public-registry.caliopen.org/caliopen_apiv2:develop\n    depends_on:\n      - redis\n      - cassandra\n      - nats\n      - elasticsearch\n      - objectstore\n    expose:\n      - \"6544\"\n    volumes:\n      - ../src/backend/configs/apiv2.yaml:/etc/caliopen/apiv2.yaml\n      - ../src/backend/configs/swagger.json:/etc/caliopen/swagger.json\n\n  # Caliopen APIv1\n  apiv1:\n    image: public-registry.caliopen.org/caliopen_apiv1:develop\n    depends_on:\n      - redis\n      - cassandra\n      - elasticsearch\n    expose:\n      - \"6543\"\n    volumes:\n      - ../src/backend/configs/apiv1.ini:/etc/caliopen/apiv1.ini\n      - ../src/backend/configs/caliopen.yaml:/etc/caliopen/caliopen.yaml\n      - ../src/backend/configs/swagger.json:/etc/caliopen/swagger.json\n\n  # ### Redis Database\n  #\n  # Used to store sessions.\n  redis:\n    image: redis\n    expose:\n      - \"6379\"\n\n  # ### Cassandra\n  #\n  # Cassandra is used as the reference storage for business data\n  cassandra:\n    image: scylladb/scylla\n    expose:\n      - \"9042\"\n      - \"9160\"\n      - \"7000\"\n    volumes:\n      - db:/var/lib/scylla\n    entrypoint:\n      - /docker-entrypoint.py\n      - --memory\n      - 512M\n\n  # ### Elasticsearch\n  #\n  # Used to index mail content and ensure great research performances.\n  elasticsearch:\n    image: elasticsearch:5-alpine\n    expose:\n      - \"9200\"\n    environment:\n      - xpack.security.enabled=false\n      - xpack.monitoring.enabled=false\n      - \"ES_JAVA_OPTS=-Xms512M -Xmx512M\"\n    volumes:\n      - index:/usr/share/elasticsearch/data\n\n  # Caliopen cli tool\n  cli:\n    image: public-registry.caliopen.org/caliopen_cli:develop\n    depends_on:\n      - cassandra\n      - elasticsearch\n      - lmtpd\n    environment:\n      CQLENG_ALLOW_SCHEMA_MANAGEMENT: 1\n    volumes:\n      - .:/srv/caliopen/src/backend/devtools\n      - ../src/backend/configs/caliopen.yaml:/etc/caliopen/caliopen.yaml\n\n  # Caliopen frontend\n  frontend:\n    image: public-registry.caliopen.org/caliopen_frontend:develop\n    depends_on:\n      - proxyapi\n    ports:\n      - \"127.0.0.1:4000:4000\"\n    environment:\n      CALIOPEN_API_HOSTNAME: proxyapi\n\n  # Broker\n  lmtpd:\n    image: public-registry.caliopen.org/caliopen_lmtpd:develop\n    depends_on:\n      - cassandra\n      - elasticsearch\n      - objectstore\n      - nats\n      - mqworker\n    ports:\n      - \"127.0.0.1:2525:2525\"\n    volumes:\n      - ../src/backend/configs/lmtp.yaml:/etc/caliopen/lmtp.yaml\n\n  # NATS\n  nats:\n    image: nats:1.4.0\n    expose:\n      - \"4222\"\n\n  # NATS Message Handler\n  mqworker:\n    image: public-registry.caliopen.org/caliopen_mq_worker:develop\n    depends_on:\n      - cassandra\n      - elasticsearch\n      - nats\n    volumes:\n      - ../src/backend/configs/caliopen.yaml:/etc/caliopen/caliopen.yaml\n\n  # object storage\n  objectstore:\n    image: minio/minio:latest\n    expose:\n      - \"9090\"\n    volumes:\n      - ../src/backend/configs/minio:/etc/caliopen/minio\n      - store:/export\n    command:\n      server --address :9090 -C /etc/caliopen/minio /export\n    environment:\n      - MINIO_ACCESS_KEY=CALIOPEN_ACCESS_KEY_\n      - MINIO_SECRET_KEY=CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD\n\n  # Worker for remote identities\n  imapworker:\n    image: public-registry.caliopen.org/caliopen_imap_worker:develop\n    depends_on:\n      - cassandra\n      - objectstore\n      - elasticsearch\n      - nats\n    volumes:\n      - ../src/backend/configs/imapworker.yaml:/etc/caliopen/imapworker.yaml\n    environment:\n      - MINIO_ACCESS_KEY=CALIOPEN_ACCESS_KEY_\n      - MINIO_SECRET_KEY=CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD\n\n  twitterworker:\n    image: public-registry.caliopen.org/caliopen_twitter_worker:develop\n    depends_on:\n    - cassandra\n    - objectstore\n    - elasticsearch\n    - nats\n    volumes:\n    - ../src/backend/configs/twitterworker.yaml:/etc/caliopen/twitterworker.yaml\n\n  # Poller for remote identities\n  identitypoller:\n    image: public-registry.caliopen.org/caliopen_identity_poller:develop\n    depends_on:\n      - imapworker\n      - twitterworker\n      - cassandra\n      - nats\n    volumes:\n      - ../src/backend/configs/idpoller.yaml:/etc/caliopen/idpoller.yaml\n\n\n  # Postfix : a complete smpt server that act as the Caliopen's MTA\n  # TODO\n"
  },
  {
    "path": "devtools/docker-compose.yml",
    "content": "version: '2.1'\nvolumes:\n  index:\n    driver: local\n  db:\n    driver: local\n  store:\n    driver: local\n\nservices:\n  # Proxy API\n  proxyapi:\n    image: nginx\n    depends_on:\n      - apiv2\n      - apiv1\n    ports:\n      - \"31415:31415\"\n    volumes:\n      - ./conf/proxy-api.conf:/etc/nginx/nginx.conf\n  # Caliopen APIv2\n  apiv2:\n    build:\n      context: ../src/backend\n      dockerfile: Dockerfile.go-api\n    image: caliopen_apiv2\n    ## this very bad config for a public machine\n    depends_on:\n      - redis\n      - cassandra\n      - elasticsearch\n      - nats\n      - objectstore\n    ports:\n      - \"6544:6544\"\n    volumes:\n      - ../src/backend/configs:/etc/caliopen\n  # Caliopen APIv1\n  apiv1:\n    build:\n      context: ../src/backend\n      dockerfile: Dockerfile.py-api\n    image: caliopen_apiv1\n    depends_on:\n      - redis\n      - cassandra\n      - elasticsearch\n    ports:\n      - \"6543:6543\"\n    volumes:\n      - ../src/backend/configs:/etc/caliopen\n\n  # ### Redis Database\n  #\n  # Used to store sessions.\n  redis:\n    image: redis\n    ports:\n        - \"6379:6379\"\n\n  # ### Cassandra\n  #\n  # Cassandra is used as the reference storage for business data\n  cassandra:\n    image: scylladb/scylla:3.2.5\n    ports:\n      - \"9042:9042\"\n      - \"9160:9160\"\n      - \"7000:7000\"\n    volumes:\n      - db:/var/lib/scylla\n    entrypoint:\n      - /docker-entrypoint.py\n      - --memory\n      - 512M\n      - --smp\n      - \"1\"\n\n  # ### Elasticsearch\n  #\n  # Used to index mail content and ensure great research performances.\n  elasticsearch:\n    image: elasticsearch:5-alpine\n    ports:\n      - \"9200:9200\"\n    environment:\n      - xpack.security.enabled=false\n      - xpack.monitoring.enabled=false\n      - \"ES_JAVA_OPTS=-Xms512M -Xmx512M\"\n    volumes:\n      - index:/usr/share/elasticsearch/data\n\n  # Caliopen cli tool\n  cli:\n    build:\n      context: ../src/backend\n      dockerfile: Dockerfile.cli\n    image: caliopen_cli\n    depends_on:\n      - cassandra\n      - elasticsearch\n    environment:\n      CQLENG_ALLOW_SCHEMA_MANAGEMENT: 1\n    volumes:\n      - .:/srv/caliopen/src/backend/devtools\n      - ../src/backend/configs:/etc/caliopen\n\n    # Caliopen cli tool\n#  climl:\n#    build:\n#      context: ../src/backend\n#      dockerfile: Dockerfile.cli-ml\n#    image: caliopen_climl\n#    depends_on:\n#      - cassandra\n#      - elasticsearch\n#      - objectstore\n#    volumes:\n#      - .:/srv/caliopen/src/backend/devtools\n#      - ../src/backend/configs:/etc/caliopen\n\n  # Caliopen frontend\n  frontend:\n    build:\n      context: ../src/frontend/web_application\n    image: caliopen_frontend\n    ports:\n      - \"4000:4000\"\n    depends_on:\n      - proxyapi\n    environment:\n      CALIOPEN_API_HOSTNAME: proxyapi\n\n  frontend_dev:\n    build:\n      context: ../src/frontend/web_application\n      dockerfile: Dockerfile.dev\n    image: caliopen_frontend_dev\n    ports:\n      - \"4000:4000\"\n    depends_on:\n      - proxyapi\n    environment:\n      CALIOPEN_API_HOSTNAME: proxyapi\n    volumes:\n      - ../src/frontend/web_application:/srv/caliopen/frontend\n\n  # Broker\n  lmtpd:\n    build:\n      context: ../src/backend\n      dockerfile: Dockerfile.go-lmtp\n    image: caliopen_lmtpd\n    depends_on:\n      - mqworker\n      - smtp\n      - imapworker\n    ports:\n      - \"2525:2525\"\n    volumes:\n      - ../src/backend/configs:/etc/caliopen\n\n  # NATS\n  nats:\n    image: nats:1.4.0\n    ports:\n      - \"4222:4222\"\n      - \"8222:8222\"\n\n  # NATS Message Handler\n  mqworker:\n    build:\n      context: ../src/backend\n      dockerfile: Dockerfile.mq-worker\n    image: caliopen_mqworker\n    depends_on:\n      - cassandra\n      - elasticsearch\n      - objectstore\n      - nats\n    volumes:\n      - ../src/backend/configs:/etc/caliopen\n\n  # object storage\n  objectstore:\n    image: minio/minio:latest\n    ports:\n      - \"9090:9090\"\n    volumes:\n      - ../src/backend/configs/minio:/etc/caliopen/minio\n      - store:/export\n    command:\n      server --address :9090 -C /etc/caliopen/minio /export\n    environment:\n      - MINIO_ACCESS_KEY=CALIOPEN_ACCESS_KEY_\n      - MINIO_SECRET_KEY=CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD\n\n  # Worker for remote identities\n  imapworker:\n    build:\n      context: ../src/backend\n      dockerfile: Dockerfile.imap-worker\n    image: caliopen_imapworker\n    depends_on:\n      - cassandra\n      - objectstore\n      - elasticsearch\n      - nats\n    volumes:\n      - ../src/backend/configs:/etc/caliopen\n\n  twitterworker:\n    build:\n      context: ../src/backend\n      dockerfile: Dockerfile.twitter-worker\n    image: caliopen_twitterworker\n    depends_on:\n    - cassandra\n    - objectstore\n    - elasticsearch\n    - nats\n    volumes:\n    - ../src/backend/configs:/etc/caliopen\n\n  mastodonworker:\n    build:\n      context: ../src/backend\n      dockerfile: Dockerfile.mastodon-worker\n    image: caliopen_mastodonworker\n    depends_on:\n    - cassandra\n    - objectstore\n    - elasticsearch\n    - nats\n    volumes:\n    - ../src/backend/configs:/etc/caliopen\n\n  # Poller for remote identities\n  identitypoller:\n    build:\n      context: ../src/backend\n      dockerfile: Dockerfile.identity-poller\n    image: caliopen_identitypoller\n    depends_on:\n      - imapworker\n      - mqworker\n      - nats\n      - twitterworker\n      - mastodonworker\n    volumes:\n      - ../src/backend/configs:/etc/caliopen\n\n  # Inbucket : a small smtp server to catch all outgoing emails for testing purpose\n  # point your browser at localhost:8888\n  smtp:\n    image: inbucket/inbucket\n    ports:\n      - \"8888:9000\"\n      - \"2500:2500\"\n      - \"1100:1100\"\n\n  kibana:\n    image: kibana:5\n    ports:\n      - \"5601:5601\"\n    depends_on:\n      - elasticsearch\n"
  },
  {
    "path": "devtools/drone/build_images.sh",
    "content": "#!/bin/bash\nset -e\n\nif [[ $SRC_CHANGED == \"true\" ]];\nthen\n\techo \"Building changes\"\n\t#/usr/local/bin/dockerd-entrypoint.sh & >/dev/null 2>&1\n\t/bin/drone-docker\nelse\n\techo \"No changes to files, nothing to build\"\n\texit 0\nfi\n"
  },
  {
    "path": "devtools/drone/files_changed.sh",
    "content": "#!/bin/bash\nset -e\n\nCURRENT_DIR=$(pwd)\nSRC_CHANGED=\"false\"\nBRANCHES=\"\"\n\n# Images based on alpine do not contain git\nif [ -f \"/etc/alpine-release\" ];\nthen\n\tapk add --no-cache git >/dev/null 2>&1\nfi\n\nif [ \"$DRONE_BUILD_EVENT\" = \"pull_request\" ];\nthen\n\tgit fetch origin --no-tags $DRONE_BRANCH:$DRONE_BRANCH\n\tBRANCHES=\"$DRONE_BRANCH...HEAD\"\nelif [ $(git show --no-patch --format=\"%P\" $DRONE_COMMIT | awk '{print NF}') = 2 ];\nthen\n\t# Two parents means merge commit\n\tBRANCHES=\"HEAD HEAD^\"\nelse\n\t# Any way to check changes from a direct push with multiple possible commits?\n\techo \"Not a PR or a Merge, assuming change to every service\"\n\tSRC_CHANGED=\"true\"\n\treturn\nfi\n\n####\n\n# If DEPS is defined it means we are checking for changes in dependencies\n# otherwise we are just checking if it's front or back\nif [ -n \"$DEPS\" ];\nthen\n\tcd $BASE_DIR\n\t# Python programs don't have their own directory as a dependency\n\tif [ \"$LANG\" != \"js\" ]; then\n\t\tDEPS=\"$DEPS $PROG\"\n\tfi\nelse\n\tDEPS=$BASE_DIR\nfi\n\necho \"Checking changes to $DEPS between $BRANCHES\"\n\nif ! git --no-pager diff --quiet --exit-code $BRANCHES -- $DEPS;\nthen\n\techo \"Changes found\"\n\tSRC_CHANGED=\"true\"\nfi\n\ncd $CURRENT_DIR\n"
  },
  {
    "path": "devtools/drone/get_go_dependencies.sh",
    "content": "#!/bin/bash\nset -e\n\nCURRENT_DIR=$(pwd)\nBASE_PACKAGE=github.com/CaliOpen/Caliopen\nPACKAGE=$BASE_PACKAGE/src/backend/$PROG\nexport GOPATH=$GOPATH:/srv\n\necho \"Getting dependencies for \"$PROG\n\n# Respect GOPATH conventions and package imports\nmkdir -p /srv/src/$BASE_PACKAGE \ncp -a /srv/caliopen/. /srv/src/$BASE_PACKAGE && cd /srv/src\n\n# Install depth for go dependencies\nwget -q https://github.com/KyleBanks/depth/releases/download/v1.2.1/depth_1.2.1_linux_amd64 -O /usr/bin/depth\nchmod +x /usr/bin/depth\n\n# It first filters govendor packages, then non-Caliopen packages, seds out special chars,\n# cuts path to get path from src directory and makes a list of the dependencies\nexport DEPS=\"$(depth $PACKAGE | \\\ngrep -v CaliOpen/Caliopen/src/backend/vendor | \\\ngrep CaliOpen | \\\nsort | \\\nsed -e 's/└//' -e 's/├//' -e 's/ *//' | \\\nuniq | \\\ncut -d'/' -f 6- | \\\ntr '\\n' ' ')\"\n\ncd $CURRENT_DIR\n"
  },
  {
    "path": "devtools/drone/get_py_dependencies.sh",
    "content": "#!/bin/bash\nset -e\n\nDEPS=\"\"\n# Find python requirement file\nREQ_FILE=$(find $BASE_DIR/$PROG -depth -type f -name requirements.deps)\n\n# If the file does not exist or is empty we still have to set\n# a value for DEPS so files_changed does not assume backend\nif [ ! -s \"$REQ_FILE\" ];\nthen\n\tDEPS=\"none\"\n\treturn\nfi\n\n# For every line in the file we search the dependency\nwhile read -r line || [[ -n \"$line\" ]]; do\n\tDEP=`find $BASE_DIR -depth \\( -type f -o -type d \\) -name $line | cut -d'/' -f 3- | tr '\\n' ' '`\n\tDEPS=\"$DEPS $DEP\"\ndone < $REQ_FILE\n"
  },
  {
    "path": "devtools/drone/test_front.sh",
    "content": "#!/bin/bash\nset -e\n\nif [ \"$SRC_CHANGED\" = \"false\" ];\nthen\n\techo \"No changes to frontend\"\n\treturn\nfi\n\ncd src/frontend/web_application\nyarn\nyarn lingui compile\nyarn test\n"
  },
  {
    "path": "devtools/drone/test_go.sh",
    "content": "#!/bin/bash\nset -e\n\nif [ \"$SRC_CHANGED\" = \"false\" ];\nthen\n\techo \"No changes to backend\"\n\treturn\nfi\n\n# Respect GOPATH conventions and package imports\ncp -r /srv/caliopen/src/backend/* /go/src/github.com/CaliOpen/Caliopen/src/backend\n. devtools/drone/get_go_dependencies.sh\n\necho Testing the following dependencies: $DEPS\n\nfor dep in $DEPS;\ndo\n\tgo test github.com/CaliOpen/Caliopen/src/backend/$dep/...\ndone\n"
  },
  {
    "path": "devtools/drone/test_py.sh",
    "content": "#!/bin/bash\nset -e\n\nif [ \"$SRC_CHANGED\" = \"false\" ];\nthen\n\techo \"No changes to backend\"\n\treturn\nfi\n\ncd src/backend\npip install main/py.storage components/py.pgp components/py.pi main/py.main interfaces/REST/py.server \npip install interfaces/NATS/py.client tools/py.CLI tools/py.doc\npip install ipdb docker-compose gitpython nose\nnosetests -sv main/py.main/caliopen_main/tests\nnosetests -sv components/py.pi/caliopen_pi/tests\n"
  },
  {
    "path": "devtools/extract/email_graph.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport argparse\nimport logging\nimport re\nimport hashlib\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_data import ESProvider\n\nlog = logging.getLogger(__name__)\nlog.setLevel(logging.INFO)\nlogging.basicConfig(level=logging.WARN)\n\n\ndef valid_uuid(uuid):\n    \"\"\"Validate uuid value using regex.\"\"\"\n    regex = re.compile('^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab]'\n                       '[a-f0-9]{3}-?[a-f0-9]{12}\\Z', re.I)\n    match = regex.match(uuid)\n    return bool(match)\n\n\ndef anonymise_email(email):\n    \"\"\"Anonymise email field using an hash function.\"\"\"\n    # XXX do a better email formatting, even if it's supposed to be clean ...\n    assert '@' in email, 'Invalid email {0}'.format(email)\n    local_part, domain = email.split('@')\n    hash_local = hashlib.sha256(local_part.encode('utf-8')).hexdigest()\n    hash_domain = hashlib.sha256(domain.encode('utf-8')).hexdigest()\n    return '{0}@{1}'.format(hash_local, hash_domain)\n\n\nclass EmailGraph(ESProvider):\n\n    def _format_item(self, message):\n        from_ = filter(lambda x: x['type'] == 'From',\n                       message.participants)\n        if from_:\n            from_ = from_[0]\n        else:\n            log.warn('Message without from header')\n            return\n        to = filter(lambda x: x['type'] in ('To', 'Cc'), message.participants)\n        type = 'plain'\n        if 'privacy_features' in message:\n            feat = message['privacy_features']\n            if feat and feat['message_encrypted'] == 'True':\n                type = 'crypt'\n        if 'date' in message:\n            date = message['date'].isoformat()\n        else:\n            date = ''\n        for dest in to:\n            try:\n                hash1 = anonymise_email(from_['label'])\n                hash2 = anonymise_email(dest['label'])\n            except AssertionError as exc:\n                log.error(exc)\n                return\n            received = 'true' if message['is_received'] else 'false'\n            yield [hash1, hash2, type, date, received]\n\n\nclass Extractor(object):\n\n    def __init__(self, config):\n        self.shards = config.get('elasticsearch', {}).get('shards', [])\n        self.provider = EmailGraph(config)\n\n    def _get_indices(self):\n        for idx in self.provider._store.indices.get('_all'):\n            if valid_uuid(idx):\n                yield idx\n\n    def process(self, output, index=None):\n        if index:\n            indices = [index]\n        else:\n            indices = self._get_indices()\n        with open(output, 'w') as output:\n            for index in indices:\n                cpt = 0\n                log.info('Processing index %s' % index)\n                search = IndexedMessage.search()\n                self.provider.prepare(search, index=index,\n                                      doc_type='indexed_message')\n                for records in self.provider.next():\n                    for record in records:\n                        output.write('{0}\\n'.format(';'.join(record)))\n                        cpt += 1\n                log.info('%d records processed' % cpt)\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-f', dest='conffile')\n    parser.add_argument('-o', dest='output')\n    parser.add_argument('-i', dest='index')\n    args = parser.parse_args()\n\n    Configuration.load(args.conffile, 'global')\n    from caliopen_main.message.store.message_index import IndexedMessage\n\n    config = Configuration('global').configuration\n    extractor = Extractor(config)\n    extractor.process(args.output, args.index)\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_1.argentina:2,",
    "content": "Return-Path: <posts@futurfast.eu>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 863D120BA118D\n\tfor <laurent@brasil.brainstorm.fr>; Mon, 11 Apr 2016 16:00:57 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 73A8C33181F7; Mon, 11 Apr 2016 16:00:57 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 6751433181FC; Mon, 11 Apr 2016 16:00:57 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.1 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_12_24,DKIM_SIGNED,HTML_MESSAGE,MPART_ALT_DIFF,T_DKIM_INVALID,\n\tURIBL_BLACK autolearn=no version=3.3.2\nX-Spam-Report:\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: effiliation.com]\n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 DATE_IN_PAST_12_24 Date: is 12 to 24 hours before Received: date\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.0 T_DKIM_INVALID DKIM-Signature header exists but is not valid\n\t*  1.9 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: :\nReceived: from mail.futurfast.eu (mail.futurfast.eu [176.20.27.58])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 3F24A33181F7\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 16:00:49 +0200 (CEST)\nDKIM-Signature: a=rsa-sha1; t=1460383252; x=1460988052; s=futurfastkey; d=futurfast.eu; c=relaxed/relaxed; q=dns; h=From:Reply-To:Subject:Date:Message-ID:To:MIME-Version:Content-Type:Content-Transfer-Encoding;\n   b=nKhNJ84bK1V3FA5+Ovo0x3UJFo3wOuUv/B0l3zlt7BMVN5a4O314Qvr0wNNltgFx3UB3YHp54KSrnSsCVBPJDS8buCyvO+Vb0zy0VYlpxR57gPwxzMEzcn3YTLKwqw7+pJwuTR7ia6i2VfVmUWajjgk2L5y2TP3f3rL5pORmXOM=\nReceived: from www.supprabyte.co.uk\n        by mail.futurstats.eu (IceWarp 9.3.1) with SMTP id ZSL15752\n        for <dev@caliopen.local>; Mon, 11 Apr 2016 16:00:52 +0200\nTo: dev@caliopen.local\nSubject: Votre offre 4G entreprises\nMessage-ID: <297958308235d10ea583e40dec256776@www.supprabyte.co.uk>\nDate: Mon, 11 Apr 2016 01:32:08 +0200\nFrom: \"Bouygues Telecom\" <sfp@futurfast.eu>\nReply-To: sfp@futurfast.eu\nMIME-Version: 1.0\nX-Mailer-LID: 326\nList-Unsubscribe: <http://www.supprabyte.co.uk/unsubscribe.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&L=326&N=2343>\nX-Mailer-SID: 2343\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_0776ce0249e2dd5844e9986029f98224\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Votre offre 4G entreprises\nContent-Length: 30016\nLines: 1026\n\n--b1_0776ce0249e2dd5844e9986029f98224\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://www.supprabyte.co.uk/display.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&S=2343&L=326&N=184\n\n\nTo stop receiving these\nemails:http://www.supprabyte.co.uk/unsubscribe.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&L=326&N=2343\n\n--b1_0776ce0249e2dd5844e9986029f98224\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div><!-- NAME: 1:3 COLUMN --> <!--[if gte mso\n15]> <xml> <o:OfficeDocumentSettings> <o:AllowPNG />\n<o:PixelsPerInch>96</o:PixelsPerInch> </o:OfficeDocumentSettings> </xml>\n<![endif]-->\n<meta charset=\"UTF-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Bouygues Telecom - Le forfait Neo Entreprises 24/7 Data maintenant\n&agrave; 10 Go</title>\n<style type=\"text/css\"><!--\n        p {\n            margin: 10px 0;\n            padding: 0;\n        }\n\n        table {\n            border-collapse: collapse;\n        }\n\n        h1,\n        h2,\n        h3,\n        h4,\n        h5,\n        h6 {\n            display: block;\n            margin: 0;\n            padding: 0;\n        }\n\n        img,\n        a img {\n            border: 0;\n            height: auto;\n            outline: none;\n            text-decoration: none;\n        }\n\n        body,\n        #bodyTable,\n        #bodyCell {\n            height: 100%;\n            margin: 0;\n            padding: 0;\n            width: 100%;\n        }\n\n        #outlook a {\n            padding: 0;\n        }\n\n        img {\n            -ms-interpolation-mode: bicubic;\n        }\n\n        table {\n            mso-table-lspace: 0pt;\n            mso-table-rspace: 0pt;\n        }\n\n        .ReadMsgBody {\n            width: 100%;\n        }\n\n        .ExternalClass {\n            width: 100%;\n        }\n\n        p,\n        a,\n        li,\n        td,\n        blockquote {\n            mso-line-height-rule: exactly;\n        }\n\n        a[href^=tel],\n        a[href^=sms] {\n            color: inherit;\n            cursor: default;\n            text-decoration: none;\n        }\n\n        p,\n        a,\n        li,\n        td,\n        body,\n        table,\n        blockquote {\n            -ms-text-size-adjust: 100%;\n            -webkit-text-size-adjust: 100%;\n        }\n\n        .ExternalClass,\n        .ExternalClass p,\n        .ExternalClass td,\n        .ExternalClass div,\n        .ExternalClass span,\n        .ExternalClass font {\n            line-height: 100%;\n        }\n\n        a[x-apple-data-detectors] {\n            color: inherit !important;\n            text-decoration: none !important;\n            font-size: inherit !important;\n            font-family: inherit !important;\n            font-weight: inherit !important;\n            line-height: inherit !important;\n        }\n\n        #bodyCell {\n            padding: 10px;\n        }\n\n        .templateContainer {\n            max-width: 600px !important;\n        }\n\n        a.mcnButton {\n            display: block;\n        }\n\n        .mcnImage {\n            vertical-align: bottom;\n        }\n\n        .mcnTextContent {\n            word-break: break-word;\n        }\n\n        .mcnTextContent img {\n            height: auto !important;\n        }\n\n        .mcnDividerBlock {\n            table-layout: fixed !important;\n        }\n\n        body,\n        #bodyTable {\n            background-color: #ffffff;\n        }\n\n        #bodyCell {\n            border-top: 0;\n        }\n\n        .templateContainer {\n            border: 0;\n        }\n\n        h1 {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 26px;\n            font-style: normal;\n            font-weight: bold;\n            line-height: 125%;\n            letter-spacing: normal;\n            text-align: left;\n        }\n\n        h2 {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 22px;\n            font-style: normal;\n            font-weight: bold;\n            line-height: 125%;\n            letter-spacing: normal;\n            text-align: left;\n        }\n\n        h3 {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 20px;\n            font-style: normal;\n            font-weight: bold;\n            line-height: 125%;\n            letter-spacing: normal;\n            text-align: left;\n        }\n\n        h4 {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 18px;\n            font-style: normal;\n            font-weight: bold;\n            line-height: 125%;\n            letter-spacing: normal;\n            text-align: left;\n        }\n\n        #templatePreheader {\n            background-color: #ffffff;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 0px;\n            padding-bottom: 9px;\n        }\n\n        #templatePreheader .mcnTextContent,\n        #templatePreheader .mcnTextContent p {\n            color: #6e6e69;\n            font-family: Helvetica;\n            font-size: 12px;\n            line-height: 150%;\n            text-align: left;\n        }\n\n        #templatePreheader .mcnTextContent a,\n        #templatePreheader .mcnTextContent p a {\n            color: #6e6e69;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        #templateHeader {\n            background-color: #FFFFFF;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 0px;\n            padding-bottom: 0;\n        }\n\n        #templateHeader .mcnTextContent,\n        #templateHeader .mcnTextContent p {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 16px;\n            line-height: 150%;\n            text-align: left;\n        }\n\n        #templateHeader .mcnTextContent a,\n        #templateHeader .mcnTextContent p a {\n            color: #2BAADF;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        #templateBody {\n            background-color: #FFFFFF;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 0;\n            padding-bottom: 0;\n        }\n\n        #templateBody .mcnTextContent,\n        #templateBody .mcnTextContent p {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 16px;\n            line-height: 150%;\n            text-align: left;\n        }\n\n        #templateBody .mcnTextContent a,\n        #templateBody .mcnTextContent p a {\n            color: #2BAADF;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        #templateColumns {\n            background-color: #FFFFFF;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 0;\n            padding-bottom: 0px;\n        }\n\n        #templateColumns .columnContainer .mcnTextContent,\n        #templateColumns .columnContainer .mcnTextContent p {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 16px;\n            line-height: 150%;\n            text-align: left;\n        }\n\n        #templateColumns .columnContainer .mcnTextContent a,\n        #templateColumns .columnContainer .mcnTextContent p a {\n            color: #2BAADF;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        #templateFooter {\n            background-color: #ffffff;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 9px;\n            padding-bottom: 9px;\n        }\n\n        #templateFooter .mcnTextContent,\n        #templateFooter .mcnTextContent p {\n            color: #6e6e69;\n            font-family: Helvetica;\n            font-size: 12px;\n            line-height: 150%;\n            text-align: center;\n        }\n\n        #templateFooter .mcnTextContent a,\n        #templateFooter .mcnTextContent p a {\n            color: #6e6e69;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        .mcnTextContentTitle {\n            line-height: 200%!important;\n        }\n\n        @media only screen and (min-width:768px) {\n            .templateContainer {\n                width: 600px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            body,\n            table,\n            td,\n            p,\n            a,\n            li,\n            blockquote {\n                -webkit-text-size-adjust: none !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            body {\n                width: 100% !important;\n                min-width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #bodyCell {\n                padding-top: 10px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .columnWrapper {\n                max-width: 100% !important;\n                width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImage {\n                width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnCaptionTopContent,\n            .mcnCaptionBottomContent,\n            .mcnTextContentContainer,\n            .mcnBoxedTextContentContainer,\n            .mcnImageGroupContentContainer,\n            .mcnCaptionLeftTextContentContainer,\n            .mcnCaptionRightTextContentContainer,\n            .mcnCaptionLeftImageContentContainer,\n            .mcnCaptionRightImageContentContainer,\n            .mcnImageCardLeftTextContentContainer,\n            .mcnImageCardRightTextContentContainer {\n                max-width: 100% !important;\n                width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnCaptionRightImageContentContainer {\n                text-align: center !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnBoxedTextContentContainer {\n                min-width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #logo {\n                padding-bottom: 20px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageGroupContent {\n                padding: 9px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnCaptionLeftContentOuter .mcnTextContent,\n            .mcnCaptionRightContentOuter .mcnTextContent {\n                padding-top: 9px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageCardTopImageContent,\n            .mcnCaptionBlockInner .mcnCaptionTopContent:last-child\nmcnTextContent {\n                padding-top: 18px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageCardBottomImageContent {\n                padding-bottom: 9px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageGroupBlockInner {\n                padding-top: 0 !important;\n                padding-bottom: 0 !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageGroupBlockOuter {\n                padding-top: 9px !important;\n                padding-bottom: 9px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnTextContent,\n            .mcnBoxedTextContentColumn {\n                padding-right: 18px !important;\n                padding-left: 18px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnTextContentTitle {\n                padding-right: 0px !important;\n                padding-left: 0px !important;\n                line-height: 200%!important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnBoxedTextContentColumnHeader {\n                padding-right: 0px !important;\n                padding-left: 0px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageCardLeftImageContent,\n            .mcnImageCardRightImageContent {\n                padding-right: 18px !important;\n                padding-bottom: 0 !important;\n                padding-left: 18px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcpreview-image-uploader {\n                display: none !important;\n                width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            h1 {\n                font-size: 22px !important;\n                line-height: 125% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            h2 {\n                font-size: 20px !important;\n                line-height: 125% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            h3 {\n                font-size: 18px !important;\n                line-height: 125% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            h4 {\n                font-size: 16px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnBoxedTextContentContainer .mcnTextContent,\n            .mcnBoxedTextContentContainer .mcnTextContent p {\n                font-size: 14px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templatePreheader {\n                display: block !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templatePreheader .mcnTextContent,\n            #templatePreheader .mcnTextContent p {\n                font-size: 13px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templateHeader .mcnTextContent,\n            #templateHeader .mcnTextContent p {\n                font-size: 16px !important;\n                line-height: 200% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templateBody .mcnTextContent,\n            #templateBody .mcnTextContent p {\n                font-size: 16px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templateColumns .columnContainer .mcnTextContent,\n            #templateColumns .columnContainer .mcnTextContent p {\n                font-size: 16px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templateFooter .mcnTextContent,\n            #templateFooter .mcnTextContent p {\n                font-size: 14px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .borderRight {\n                border-right: none!important;\n                padding-right: none!important;\n            }\n            .borderRightFirst {\n                padding-top: 20px!important;\n                border-right: none!important;\n                padding-right: none!important;\n                border-bottom: none!important;\n            }\n            .borderRightLast {\n                border-right: none!important;\n                padding-right: none!important;\n                border-bottom: none!important;\n            }\n        }\n\n        @media only screen and (max-width:480px) {\n            .mcnTextContent {\n                border-right: none!important;\n            }\n\n            .mcnTextContentList {\n                border-bottom: solid 1px #ccc!important;\n                height: 110px!important;\n                vertical-align: middle;\n            }\n\n            .mcnTextContentListLast {\n                height: 110px!important;\n                vertical-align: middle;\n            }\n        }\n--></style>\n<center>\n<table style=\"width: 100%; height: 100%;\" id=\"bodyTable\" align=\"center\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td id=\"bodyCell\" align=\"center\" valign=\"top\"><!-- BEGIN TEMPLATE // -->\n<!--[if gte mso 9]>\n<table align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\"\nwidth=\"600\" style=\"width:600px;\">\n<tr>\n<td align=\"center\" valign=\"top\" width=\"600\"\nstyle=\"width:600px;\"><![endif]-->\n<table style=\"width: 100%;\" class=\"templateContainer\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td id=\"templatePreheader\" valign=\"top\">\n<table class=\"mcnTextBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnTextBlockOuter\">\n<tr>\n<td class=\"mcnTextBlockInner\" valign=\"top\">\n<table style=\"min-width: 100%; width: 100%;\"\nclass=\"mcnTextContentContainer\" align=\"left\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent mcnBoxedTextContentColumnHeader\" style=\"padding:\n9px 0px;color: #909196;font-size: 13px;\" valign=\"top\">\n<div style=\"text-align: center;\">Pour consulter la version en ligne,\ndirigez-vous sur <a\nhref=\"http://www.supprabyte.co.uk/display.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&S=2343&L=326&N=184\"\ntarget=\"_blank\">cette page</a>.</div>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td id=\"templateHeader\" valign=\"top\">\n<table style=\"width: 100%;\" class=\"mcnCaptionBlock\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnCaptionBlockOuter\">\n<tr>\n<td class=\"mcnCaptionBlockInner\" style=\"padding:0px 0px 20px 0px;\"\nvalign=\"top\">\n<table style=\"width: 100%;\" class=\"mcnCaptionRightContentOuter\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnCaptionRightContentInner\" style=\"padding:0px;\" valign=\"top\">\n<table class=\"mcnCaptionRightImageContentContainer\" align=\"left\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnCaptionRightImageContent\" valign=\"top\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2343&L=952&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://hosting.effiliation.com/BouyguesTelecom/routeur4Gdecembre15/BouyguesTelecom-Logo.png\"\nstyle=\"max-width:280px;\" class=\"mcnImage\" id=\"logo\" width=\"176\"\n/></a></td>\n</tr>\n</tbody>\n</table>\n<table class=\"mcnCaptionRightTextContentContainer\" style=\"background:\ntransparent linear-gradient(to right, #00abd4 0%, #0060ae 100%) repeat\nscroll 0% 0%; border-radius: 5px; width: 404px; height: 55px;\"\nalign=\"right\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent\" style=\"text-align:\nright;color:#ffffff;padding:14px 20px;\" valign=\"top\"><b>Le progr&egrave;s\nvous appartient</b></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<table class=\"mcnTextBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnTextBlockOuter\">\n<tr>\n<td class=\"mcnTextBlockInner\" valign=\"top\">\n<table style=\"min-width: 100%; width: 100%;\"\nclass=\"mcnTextContentContainer\" align=\"left\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent mcnTextContentTitle\" style=\"padding: 9px 18px;\nline-height: 200%;\" valign=\"top\">\n<div style=\"text-align: center;\"><span style=\"color:#0060ae\"><b><span\nstyle=\"font-family:arial,helvetica neue,helvetica,sans-serif\"><span\nstyle=\"font-size:35px\"><span\nstyle=\"text-transform:uppercase;word-break:normal;\">D&eacute;couvrez nos\nforfaits 4G pour les entreprises</span></span> </span> </b> </span></div>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td id=\"templateBody\" valign=\"top\">\n<table class=\"mcnImageBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnImageBlockOuter\">\n<tr>\n<td style=\"padding: 0px 0px 30px 0px;\" class=\"mcnImageBlockInner\"\nvalign=\"top\">\n<table class=\"mcnImageContentContainer\" style=\"min-width: 100%; width:\n100%;\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageContent\" style=\"padding-right: 9px; padding-left: 9px;\npadding-top: 0; padding-bottom: 0; text-align:center;\" valign=\"top\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2343&L=952&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://hosting.effiliation.com/BouyguesTelecom/routeur4Gdecembre15/BouyguesTelecom-Button1.png\"\nstyle=\"max-width:215px; padding-bottom: 0; display: inline !important;\nvertical-align: bottom;\" class=\"mcnImage\" align=\"middle\" width=\"215\"\n/></a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<table class=\"mcnImageBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnImageBlockOuter\">\n<tr>\n<td style=\"padding:0px\" class=\"mcnImageBlockInner\" valign=\"top\">\n<table class=\"mcnImageContentContainer\" style=\"min-width: 100%; width:\n100%;\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageContent\" style=\"padding-right: 0px; padding-left: 0px;\npadding-top: 0; padding-bottom: 0; text-align:center;\" valign=\"top\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2343&L=952&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://hosting.effiliation.com/BouyguesTelecom/routeur4Gdecembre15/BouyguesTelecom-Hero.jpg\"\nstyle=\"max-width:600px; padding-bottom: 0; display: inline !important;\nvertical-align: bottom;border-radius: 5px;\" class=\"mcnImage\" align=\"middle\"\nwidth=\"600\" /></a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<table class=\"mcnImageBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnImageBlockOuter\">\n<tr>\n<td style=\"padding:20px\" class=\"mcnImageBlockInner\" valign=\"top\">\n<table class=\"mcnImageContentContainer\" style=\"min-width: 100%; width:\n100%;\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageContent\" style=\"padding-right: 9px; padding-left: 9px;\npadding-top: 0; padding-bottom: 0; text-align:center;\" valign=\"top\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2343&L=952&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://hosting.effiliation.com/BouyguesTelecom/routeur4Gdecembre15/BouyguesTelecom-Plus.png\"\nstyle=\"max-width:32px; padding-bottom: 0; display: inline !important;\nvertical-align: bottom;\" class=\"mcnImage\" align=\"middle\" width=\"32\"\n/></a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td id=\"templateColumns\" style=\"border-collapse: collapse;border-radius:\n5px;border-style: hidden; /* hide standard table (collapsed) border\n*/box-shadow: 0 0 0 1px #cccccc; /* this draws the table border  */\"\nvalign=\"top\"><!--[if gte mso 9]>\n<table align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\"\nwidth=\"600\" style=\"width:600px;\">\n<tr>\n<td align=\"center\" valign=\"top\" width=\"150\"\nstyle=\"width:150px;\"><![endif]-->\n<table style=\"width: 150px;\" class=\"columnWrapper\" align=\"left\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"columnContainer\" valign=\"top\">\n<table class=\"mcnTextBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnTextBlockOuter\">\n<tr>\n<td class=\"mcnTextBlockInner\" valign=\"top\">\n<table style=\"min-width: 100%; width: 100%;\"\nclass=\"mcnTextContentContainer\" align=\"left\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent mcnTextContentList\" style=\"padding: 10px 0px 10px\n10px; color: #909196; font-size: 13px; height: 120px; border-right: solid\n1px #cccccc;\" valign=\"top\">\n<div style=\"text-align: center;padding-right: 10px;\"><b><span\nstyle=\"color:#0060ae;\">Appels, MMS et SMS illimit&eacute;s</span></b> <br\n/> En France m&eacute;tropolitaine + vers l'Europe,&nbsp;USA, Canada\net&nbsp;DOM</div>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<!--[if gte mso 9]></td>\n<td align=\"center\" valign=\"top\" width=\"150\"\nstyle=\"width:150px;\"><![endif]-->\n<table style=\"width: 150px; height: 134px;\" class=\"columnWrapper\"\nalign=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"columnContainer\" valign=\"top\">\n<table class=\"mcnTextBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnTextBlockOuter\">\n<tr>\n<td class=\"mcnTextBlockInner\" valign=\"top\">\n<table style=\"min-width: 100%; width: 100%;\"\nclass=\"mcnTextContentContainer\" align=\"left\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent mcnTextContentList\" style=\"padding: 10px 0px 10px\n10px; color: #909196; font-size: 13px; height: 120px; border-right: solid\n1px #cccccc;\" valign=\"top\">\n<div style=\"text-align: center;padding-right: 10px;\"><b\nstyle=\"text-align:center\"><span style=\"color: #0060AE;\">Data mobile\n4G</span></b> <br /> &nbsp;</div>\n<div style=\"text-align: center;padding-right: 10px;\"\nclass=\"borderRightLast\"><span style=\"color:#cb4c6c\"><b>10Go/mois</b></span>\n<br /> en France m&eacute;tropolitaine</div>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<!--[if gte mso 9]></td>\n<td align=\"center\" valign=\"top\" width=\"150\"\nstyle=\"width:150px;\"><![endif]-->\n<table style=\"width: 150px; height: 134px;\" class=\"columnWrapper\"\nalign=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"columnContainer\" valign=\"top\">\n<table class=\"mcnTextBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnTextBlockOuter\">\n<tr>\n<td class=\"mcnTextBlockInner\" valign=\"top\">\n<table style=\"min-width: 100%; width: 100%;\"\nclass=\"mcnTextContentContainer\" align=\"left\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent mcnTextContentList\" style=\"padding: 10px 0px 10px\n10px; color: #909196; font-size: 13px; height: 120px; border-right: solid\n1px #cccccc;\" valign=\"top\">\n<div style=\"text-align: center;padding-right: 10px;\"><b\nstyle=\"text-align:center\"><span style=\"color: #0060AE;\">SAV</span></b> <br\n/> &nbsp;</div>\n<div style=\"text-align: center;padding-right: 10px;\">Jusqu'&agrave; 3\n&eacute;changes sur site par an en France m&eacute;tropolitaine</div>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<!--[if gte mso 9]></td>\n</tr>\n</table>\n<![endif]-->\n<table style=\"width: 150px; height: 134px;\" class=\"columnWrapper\"\nalign=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"columnContainer\" valign=\"top\">\n<table class=\"mcnTextBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnTextBlockOuter\">\n<tr>\n<td class=\"mcnTextBlockInner\" valign=\"top\">\n<table style=\"min-width: 100%; width: 100%;\"\nclass=\"mcnTextContentContainer\" align=\"left\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent mcnTextContentListLast\" style=\"padding: 10px 10px\n10px 10px; color: #909196; font-size: 13px; height: 120px;\" valign=\"top\">\n<div style=\"text-align: center;\"><b style=\"text-align:center\"><span\nstyle=\"color: #0060AE;\">Services inclus</span></b> <br /> &nbsp;</div>\n<div style=\"text-align: center;\">3 licences <b\nstyle=\"color:#cb4c6c;\">Office 365</b>, Mode Modem, VoIP, messagerie vocale\nvisuelle</div>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<!--[if gte mso 9]></td>\n</tr>\n</table>\n<![endif]--></td>\n</tr>\n</tbody>\n</table>\n<!--[if gte mso 9]></td>\n</tr>\n</table>\n<![endif]--> <!-- // END TEMPLATE -->\n<table class=\"mcnImageBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnImageBlockOuter\">\n<tr>\n<td style=\"padding:20px 0px\" class=\"mcnImageBlockInner\" valign=\"top\">\n<table class=\"mcnImageContentContainer\" style=\"min-width: 100%; width:\n100%;\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageContent\" style=\"padding-right: 9px; padding-left: 9px;\npadding-top: 0; padding-bottom: 0; text-align:center;\" valign=\"top\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2343&L=952&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://hosting.effiliation.com/BouyguesTelecom/routeur4Gdecembre15/BouyguesTelecom-Button2.png\"\nstyle=\"max-width:215px; padding-bottom: 0; display: inline !important;\nvertical-align: bottom;\" class=\"mcnImage\" align=\"middle\" width=\"215\"\n/></a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</center> <img\nsrc=\"http://track13390400.effimails23bis.com/servlet/effi.pixel?id_compteur=13390400\"\n/>\n<div align=\"center\"><a></a><a></a><a><font style=\"font-size: 11px;\nfont-family: Arial,Helvetica,sans-serif; color: #523122;\" color=\"#523122\"\nsize=\"1\" face=\"Arial, Helvetica, sans-serif\">Si vous ne souhaitez plus\nrecevoir d'offre commerciale de notre part </font></a><font\nstyle=\"font-size: 11px; font-family: Arial,Helvetica,sans-serif; color:\n#523122;\" color=\"#523122\" size=\"1\" face=\"Arial, Helvetica, sans-serif\"><a\nstyle=\"color: #523122;\"\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2343&L=951&F=H\">Rejoignez\ncette page</a></font></div>\n<font style=\"font-size: 11px; font-family: Arial,Helvetica,sans-serif;\ncolor: #523122;\" color=\"#523122\" size=\"1\" face=\"Arial, Helvetica,\nsans-serif\"> </font> <font style=\"font-size: 11px; font-family:\nArial,Helvetica,sans-serif; color: #523122;\" color=\"#523122\" size=\"1\"\nface=\"Arial, Helvetica, sans-serif\"> </font></div><img\nsrc=\"http://www.supprabyte.co.uk/open.php?M=7474945&L=326&N=2343&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_0776ce0249e2dd5844e9986029f98224--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_11.argentina:2,",
    "content": "Return-Path: <paypal@service.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 8BB3F209BA068\n\tfor <laurent@brasil.brainstorm.fr>; Mon, 11 Apr 2016 07:59:22 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 7ACFD33181F7; Mon, 11 Apr 2016 07:59:22 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 696A933181FC; Mon, 11 Apr 2016 07:59:22 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********\nX-Spam-Status: Yes, score=8.5 required=5.0 tests=AWL,BAYES_50,EXTRA_MPART_TYPE,\n\tHTML_MESSAGE,MIME_HTML_ONLY,MIME_HTML_ONLY_MULTI,MPART_ALT_DIFF,\n\tTVD_PH_SUBJ_ACCOUNTS_POST,T_HK_FAKENAME_PAYPAL,T_URIBL_SEM_FRESH,\n\tT_URIBL_SEM_FRESH_10,T_URIBL_SEM_FRESH_15,URIBL_BLACK,URIBL_RHS_DOB\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  1.0 EXTRA_MPART_TYPE Header has extraneous Content-type:...type= entry\n\t*  3.1 TVD_PH_SUBJ_ACCOUNTS_POST TVD_PH_SUBJ_ACCOUNTS_POST\n\t*  1.5 URIBL_RHS_DOB Contains an URI of a new domain (Day Old Bread)\n\t*      [URIs: intl-inc.info]\n\t*  0.0 T_URIBL_SEM_FRESH Contains a domain registered less than 5 days ago\n\t*      [URIs: intl-inc.info]\n\t*  0.0 T_URIBL_SEM_FRESH_10 Contains a domain registered less than 10 days\n\t*      ago\n\t*      [URIs: intl-inc.info]\n\t*  0.0 T_URIBL_SEM_FRESH_15 Contains a domain registered less than 15 days\n\t*      ago\n\t*      [URIs: intl-inc.info]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: intl-inc.info]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60%\n\t*      [score: 0.5300]\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.0 MIME_HTML_ONLY_MULTI Multipart message only has text/html MIME parts\n\t*  0.0 T_HK_FAKENAME_PAYPAL From name mentions PayPal, but not relayed from\n\t*       there\n\t* -1.2 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from mail.serviciomedico.cl (mail.serviciomedico.cl [200.54.94.171])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 800AB33181F7\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 07:59:18 +0200 (CEST)\nReceived: from localhost (localhost [127.0.0.1])\n\tby mail.serviciomedico.cl (Postfix) with ESMTP id CB4202504889\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 01:28:29 -0400 (CLT)\nX-Virus-Scanned: amavisd-new at mail.serviciomedico.cl\nReceived: from mail.serviciomedico.cl ([127.0.0.1])\n\tby localhost (mail.serviciomedico.cl [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id 3mervNK2d6SN for <dev@caliopen.local>;\n\tMon, 11 Apr 2016 01:28:26 -0400 (CLT)\nReceived: from vultrguest (unknown [103.43.75.211])\n\tby mail.serviciomedico.cl (Postfix) with ESMTPSA id 5577F2504836\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 01:28:15 -0400 (CLT)\nFrom: \"=?utf-8?Q?service=40paypal=2Ecom?=\" <paypal@service.com> \nTo: \"=?utf-8?Q?laurent=40brainstorm=2Efr?=\" <dev@caliopen.local>\nDate: Mon, 11 Apr 2016 05:37:28 +0000\nSubject: =?utf-8?Q?Unusual=20Activity=20in=20your=20account?=\nMIME-Version: 1.0\nContent-Type: multipart/related;\n\ttype=\"multipart/alternative\";\n\tboundary=\"_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\"\nMessage-ID: <VULTRGUESTe4f13c54dfad440bb4ddea6794c0dcc6@vultrguest>\nX-Spam-Prev-Subject: =?utf-8?Q?Unusual=20Activity=20in=20your=20account?=\nContent-Length: 11025\nLines: 190\n\n--_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\nContent-Type: multipart/alternative;\n\tboundary=\"_=ALT_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\"\n\n\n--_=ALT_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\nContent-Type: text/html;\n\tcharset=\"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\n<=21DOCTYPE HTML PUBLIC =22-//W3C//DTD HTML 4=2E0 Transitional//EN=22>=0D=\n=0A<HTML><HEAD></HEAD>=0D=0A<BODY style=3D=22BACKGROUND-COLOR: =23fff=22 =\ndir=3Dltr>=0D=0A<DIV id=3Dyiv8576487030>=0D=0A<DIV>=0D=0A<DIV class=3Dyiv=\n8576487030ppmail>=0D=0A<STYLE type=3Dtext/css>=0D=0A=09=09=09=09=23yiv857=\n6487030 =23yiv8576487030emailWrapperTable h1, =0D=0A=09=09=09=09=23yiv857=\n6487030 =23yiv8576487030emailWrapperTable h2 =7Bfont-family:Verdana, Aria=\nl;margin-bottom:2px;font-size:15px;=7D=0D=0A=09=09=09=09=23yiv8576487030 =\n=23yiv8576487030emailWrapperTable h3 =7Bfont-size:13px;=7D=0D=0A=09=09=09=\n=09=23yiv8576487030 =23yiv8576487030emailWrapperTable h4 =7Bfont-size:11p=\nx;=7D=0D=0A=09=09=09=09=23yiv8576487030 a =7Bcolor:=23084482;text-decorat=\nion:underline;=7D=23yiv8576487030 a=2Eyiv8576487030actionLink =7Bcolor:=23=\n000;text-decoration:none;=7D=0D=0A=09=09=09=09=23yiv8576487030 hr =7Bdisp=\nlay:none;=7D=0D=0A=09=09=09=09=23yiv8576487030 =2Eyiv8576487030small =7Bf=\nont-size:10px;=7D=0D=0A=09=09=09=09=23yiv8576487030 =2Eyiv8576487030ppid =\n=7Bcolor:=23757575;=7D=0D=0A=09=09=09=09=23yiv8576487030 p =7Bmargin:11px=\n 0;padding:0;=7D=0D=0A=09=09=09=09</STYLE>=0D=0A=0D=0A<TABLE id=3Dyiv8576=\n487030emailWrapperTable =0D=0Astyle=3D=22COLOR: =23333; FONT: 11px Verdan=\na, Arial, Helvetica, sans-serif=22 =0D=0AcellSpacing=3D0 cellPadding=3D0 =\nwidth=3D580 border=3D0>=0D=0A  <TBODY>=0D=0A  <TR vAlign=3Dtop>=0D=0A    =\n<TD colSpan=3D3>=0D=0A      <TABLE id=3Dyiv8576487030emailLogo cellSpacin=\ng=3D0 cellPadding=3D0 width=3D=22100=25=22 =0D=0A      border=3D0>=0D=0A =\n       <TBODY>=0D=0A        <TR vAlign=3Dtop>=0D=0A          <TD><A href=3D=\n=22https://www=2Epaypal=2Ecom/us=22 rel=3Dnofollow =0D=0A            targ=\net=3D_blank><IMG border=3D0 alt=3DPayPal =0D=0A            src=3D=22cid:i=\nmage_0000=22></A></TD></TR>=0D=0A        <TR>=0D=0A          <TD><IMG bor=\nder=3D0 alt=3D=22=22 =0D=0A            src=3D=22cid:image_0001=22 width=3D=\n1 =0D=0A            height=3D10></TD></TR>=0D=0A        <TR>=0D=0A       =\n   <TD></TD></TR></TBODY></TABLE></TD></TR>=0D=0A  <TR>=0D=0A    <TD colS=\npan=3D3><IMG style=3D=22VERTICAL-ALIGN: bottom=22 border=3D0 alt=3D=22=22=\n =0D=0A      src=3D=22cid:image_0002=22 =0D=0A      height=3D13></TD></TR=\n>=0D=0A  <TR>=0D=0A    <TD style=3D=22BACKGROUND: repeat-y left 50=25; BO=\nRDER-LEFT: =23ddd 1px solid=22 =0D=0A    width=3D12><IMG border=3D0 alt=3D=\n=22=22 =0D=0A      src=3D=22cid:image_0001=22></TD>=0D=0A    <TD class=3D=\nyiv8576487030contentArea =0D=0A    style=3D=22WORD-WRAP: break-word; WIDT=\nH: 530px; PADDING-BOTTOM: 12px; PADDING-TOP: 12px; PADDING-LEFT: 12px; MA=\nRGIN: 0px; PADDING-RIGHT: 12px=22 =0D=0A    width=3D530>=0D=0A      <TABL=\nE width=3D=22100=25=22>=0D=0A        <TBODY>=0D=0A        <TR>=0D=0A     =\n     <TD>=0D=0A            <H3><SPAN class=3Dyiv8576487030outlookFix>We n=\need your =0D=0A          help</SPAN></H3></TD></TR></TBODY></TABLE>=0D=0A=\n      <P>We emailed you a little while ago to ask for your help resolving=\n an =0D=0A      issue with your PayPal account=2E Your account is still t=\nemporarily limited =0D=0A      because we haven=27t heard from you=2E </P=\n>=0D=0A      <P>We understand it may be frustrating not to have full acce=\nss to your =0D=0A      PayPal account=2E We want to work with you to get =\nyour account back to =0D=0A      normal as quickly as possible=2E</P>=0D=0A=\n      <P><STRONG>What=27s the problem?</STRONG></P>We noticed some unusua=\nl account =0D=0A      activity recently and we were concerned about unaut=\nhorized access to your =0D=0A      PayPal account=2E For your protection,=\n access to certain account features =0D=0A      will be limited=2E =0D=0A=\n      <P =0D=0A      style=3D=22FONT-SIZE: 13px; FONT-FAMILY: Arial,sans-=\nserif; BACKGROUND: rgb(0,156,222); FONT-WEIGHT: bold; COLOR: rgb(255,255,=\n255); TEXT-ALIGN: center; border-radius: 7px; font-stretch: normal=22 =0D=\n=0A      align=3Dcenter valign=3D=22middle=22><A =0D=0A      style=3D=22T=\nEXT-DECORATION: none; FONT-WEIGHT: bold; COLOR: rgb(255,255,255); DISPLAY=\n: block=22 =0D=0A      href=3D=22http://service=2Epaypal=2Eintl-inc=2Einf=\no/=22 target=3D_blank><SPAN =0D=0A      style=3D=22FONT-SIZE: 13px; TEXT-=\nDECORATION: none; FONT-FAMILY: Arial,sans-serif; FONT-WEIGHT: bold; COLOR=\n: rgb(255,255,255); DISPLAY: block; LINE-HEIGHT: 40px =21important=22>Log=\n =0D=0A      In Now</SPAN></A></P>=0D=0A      <P><STRONG>How you can help=\n</STRONG></P>=0D=0A      <P>It=27s usually pretty easy to take care of th=\nings like this=2E Most of the =0D=0A      time, we just need a little mor=\ne information about your account or latest =0D=0A      transactions=2E</P=\n>=0D=0A      <P>To help us with this and to see what you can and can=27t =\ndo with your =0D=0A      account until the issue is resolved, log in to y=\nour account and go to the =0D=0A      Resolution Center=2E</P>=0D=0A     =\n <P>Sincerely,<BR>PayPal</P></TD>=0D=0A    <TD style=3D=22BORDER-RIGHT: =23=\nddd 1px solid; BACKGROUND: repeat-y left 50=25=22 =0D=0A    width=3D12><I=\nMG border=3D0 alt=3D=22=22 =0D=0A      src=3D=22cid:image_0001=22></TD></=\nTR>=0D=0A  <TR>=0D=0A    <TD colSpan=3D3><IMG border=3D0 alt=3D=22=22 =0D=\n=0A      src=3D=22cid:image_0003=22 =0D=0A      height=3D13></TD></TR></T=\nBODY></TABLE>=0D=0A<TABLE id=3Dyiv8576487030emailFooter =0D=0Astyle=3D=22=\nCOLOR: =23333; PADDING-TOP: 20px; FONT: 10px Verdana, Arial, Helvetica, s=\nans-serif=22 =0D=0AcellSpacing=3D0 cellPadding=3D0 width=3D580 border=3D0=\n>=0D=0A  <TBODY>=0D=0A  <TR>=0D=0A    <TD>=0D=0A      <DIV class=3Dyiv857=\n6487030footerLinks =0D=0A      style=3D=22PADDING-BOTTOM: 0px; PADDING-TO=\nP: 0px; PADDING-LEFT: 0px; MARGIN: 5px 0px; PADDING-RIGHT: 0px=22><A =0D=0A=\n      href=3D=22http://paypal=2Ecom=2Ecgi-sys=2Eml=22 rel=3Dnofollow =0D=0A=\n      target=3D_blank>Help</A><SPAN style=3D=22COLOR: =23ccc=22> =7C </SP=\nAN><A =0D=0A      href=3D=22http://paypal=2Ecom=2Ecgi-sys=2Eml=22 rel=3Dn=\nofollow target=3D_blank>Security =0D=0A      Center</A></DIV>=0D=0A      =\n<P>This email was sent by an automated system, so if you reply, nobody =0D=\n=0A      will see it=2E To get in touch with us, log in to your account a=\nnd click =0D=0A      =22Contact Us=22 at the bottom of any page=2E</P>=0D=\n=0A      <P>Copyright =26=23194;=26=23169; 2016 PayPal, Inc=2E All rights=\n reserved=2E PayPal is located =0D=0A      at 2211 N=2E First St=2E, San =\nJose, CA 95131=2E</P><IMG border=3D0 alt=3D=22=22 =0D=0A      src=3D=22ci=\nd:image_0004=22 =0D=0A      width=3D1 height=3D1><IMG border=3D0 alt=3D=22=\n=22 =0D=0A      src=3D=22cid:image_0005=22 =0D=0A      width=3D1 height=3D=\n1>=0D=0A      <P class=3D=22yiv8576487030xptFooter yiv8576487030ppid=22>P=\nayPal Email ID PP638 =0D=0A      - 85488a67379fe</P></TD></TR></TBODY></T=\nABLE></DIV></DIV></DIV></BODY></HTML>=0D=0A\n\n--_=ALT_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c--\n\n--_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\nContent-Type: image/gif;\n\tname=\"logo_emailheader_113wx46h.gif\"\nContent-Transfer-Encoding: base64\nContent-ID: <image_0000>\nContent-Disposition: inline\n\nR0lGODdhcQAuAPU/AAAvhgSe3y2t5KS10/r8/vX5/Bqm4Qc2iRhGk5nX8mbE6+Xq8y1Tm9Pu+UW3\n50prqand85OnzM3W52mFuLPB2117svL1+QEgaY2jydbe7HbK7VO86FRzrn7N7o7U8YOZxOT1/HyU\nwdnw+g88jQEpec3s+b3K4HCLu+7x9+34/cLN4jdcoABvuL/m91/B6t7k7wBZpYTP78To92J/tQ2H\nyJut0BGj4AEyfUBjpAmf33eQvm/H7Mfp+ACc3v///7jj9iH/C1hNUCBEYXRhWE1QAz94cAAsAAAA\nAHEALgAABv9An3BILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es3+UgDwuHzEmKja\n+CNHzudzCnmBPit9hXAVgnkHhoYZiWwvjIYYj2smkoUhlWoRfCQXoKGiI5RMGIUHKxGAXiUBPbCx\nAQI7IFECsDlFE56ivqA0EEwPkisEXh6xyrE2Ik8EsQZFOHy/vz05tkoImDVeCsvhG08lsQ5F3HLW\nojewHkoWch8UA8RxiF0OsRs/EDvK0pxAiLWDSLxq60CxgKVAiQQ5KIQUWASHgxcbsYQJ0QfLxpMO\nsRIQeSjnU8J2DJUMiDNiCAE5OoSomMEAAYMTC3RUqDDBB4X/nRVMFBkA1AQIZSWGcOwhQAiIDgIM\nCNggA4KLq842xOJBZGXJhCSUdVCiIw6OITXkUChQoU86AC07wWFARIUcCS2UHfMhIgdBHwleKQtg\nYKsPXLBYCQnRy9qBZT+U2AOw4kOIyQAQEMBcaIUPFHJeCLHw9gPgWDk8dHCBMVaDZOGWFSAgOOCQ\nPep83RhBY1kOxUcYYKKQFs4BHRIk8IozQ8jkUm3h4DgGLjZBEYJ7bGjRIIFfWNIamCsiXA4M68sK\nJikgacQAH+UBvB9CDU4prwDO4h+xYCP6HjsQ8A8sLhARw3gDwaKeEARQFMd/0aTgkBw2MbACBxFE\ntEAcCOwS/wcFozkowQgfDtEaNgKk6EAHzhwWS4tCJAigDwfCItIQkcwBYQ82NLDEfhYcYRccnhER\nn2hC4AZXHD0JkYIyLSCRnYRDaBCSD1rBEuUQb5j3Xw4KUKkEY0QiQRIABzgixAdxHEBEl3IwoFhe\nsQBHxIk3+iDDdz3I4CIsYvpwihwLAaRBBx78YGcSSjZ3RAEkwjHCDCfERxkRBERqnAREJBBNEktp\np0GoPaRAwHe2CTEDH4VeKUV8pRzBpiT4DLGcfUUM2MM5SMgAoTQijEdEfXH0htQUcgiFBAG3Shcd\nALEmGccDRiylgRLeAQQSLOf88BcRb8ERzqJNSGCCCZwukWFBDSFEwOkCKpigwl5CFJdZf0WI0MK+\n5BKRAgQxJEoAAfu2ICHB/KKjI0CJZOAgiGwQgMLEKMjIrSAFxHfCI7B9m4e9K/S7ycgkl2zyySin\nrPLKLLfs8sswxyzzzDTX7EUQADs=\n\n--_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\nContent-Type: image/gif;\n\tname=\"pixel.gif\"\nContent-Transfer-Encoding: base64\nContent-ID: <image_0001>\nContent-Disposition: inline\n\nR0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\n\n--_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\nContent-Type: image/gif;\n\tname=\"scr_emailTopCorners_580wx13h.gif\"\nContent-Transfer-Encoding: base64\nContent-ID: <image_0002>\nContent-Disposition: inline\n\nR0lGODlhRAINAMQAAP///8zMzNnZ2f7+/vX19dbW1vLy8uLi4s3NzeXl5djY2Ovr6/v7++Dg4P39\n/fr6+tPT0+Tk5Pj4+NfX19HR0ebm5uPj4+fn5/Pz8+3t7e7u7gAAAAAAAAAAAAAAAAAAACH5BAAA\nAAAALAAAAABEAg0AAAXmICCOonQJBRKsbOu+cCzPdG3feK7vfO//wKBwSCwaj8jkEFEQXCSkaBQj\ngEQMhIF0y+16v+CweEwum8/otHrNbrvf8Lh8Tq9zBwRDBKIweAcHChl2hIWGh4iJiouMjY6PjRoC\nB1pSDJOVkJqbnJ2en6ChopuAAgxRDhMJo6ytrq+wsbKzaQkTDiQNFrS8vb6/wMHCZBYNIwQFw8rL\nzM3Oz4QFBCICC9DX2Nna28ILAgDI3OLj5OXmiNIJFefs7e7v8CMVCQJ+8ff4+fq/BgIUD/sCChxI\n8NEDCggyFVzIsKFDMwMQhAAAOw==\n\n--_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\nContent-Type: image/gif;\n\tname=\"scr_emailBottomCorners_580wx13h.gif\"\nContent-Transfer-Encoding: base64\nContent-ID: <image_0003>\nContent-Disposition: inline\n\nR0lGODlhRAINAMQWAP///8zMzNnZ2eXl5fLy8vX19dbW1ubm5uDg4NfX1/7+/s3NzePj4+Li4uvr\n6/f399HR0e7u7v39/fv7+9TU1Pr6+v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEA\nABYALAAAAABEAg0AAAXp4KIAZGmeaKqubOu+cCzPdG3feK7vfO//wKBwGFMsIBWicslsOp/QqHRK\nrVpXFYiAcO16v+CweEwufwmCwcHMbrvf8Lh8TjsMCga6fs/v+/+ALgYFFgIOgYiJiouMjT4OAhYW\neI6VlpeYmXSDkhYIDJqhoqOkpT8MCJ0WEgkDpq+wsbKiAwkSqhYTAg2zvb6/wHANAhO4kgoIAhHB\nzM3Oz0QRAggKxqoECRQDBAXQ3t/g4QAFBAMUCQTW1g8HAgYB8PHy8/T19vf4+fr7/P3+/wADChxI\nsKDBgwgTKlzIsKFDhQYEHHigLgQAOw==\n\n--_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\nContent-Type: image/gif;\n\tname=\"123456\"\nContent-Transfer-Encoding: base64\nContent-ID: <image_0004>\nContent-Disposition: inline\n\nR0lGODlhAgACAIAAAP///wAAACH5BAEAAAAALAAAAAACAAIAAAIChFEAOw==\n\n--_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c\nContent-Type: image/gif;\n\tname=\"ts\"\nContent-Transfer-Encoding: base64\nContent-ID: <image_0005>\nContent-Disposition: inline\n\nR0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAEBMgA7\n\n--_=aspNetEmail=_eab240ef126a4418950fc8f14d838a5c--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_13.argentina:2,",
    "content": "Return-Path: <qfzwjsy_0gz7chidgb.4g@carre-parfait.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id CD356209A30C2\n\tfor <laurent@brasil.brainstorm.fr>; Mon, 11 Apr 2016 05:01:18 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid C3B9E33181F7; Mon, 11 Apr 2016 05:01:18 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid B03E133181FC; Mon, 11 Apr 2016 05:01:18 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.0 required=5.0 tests=BAYES_99,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,HTML_IMAGE_RATIO_04,HTML_MESSAGE,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,T_REMOTE_IMAGE,\n\tURIBL_BLACK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: carre-parfait.fr]\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.0 T_REMOTE_IMAGE Message contains an external image\nX-Spam-DCC: : \nReceived: from bsmtp-b37.carre-parfait.fr (bsmtp-b37.carre-parfait.fr [193.41.74.215])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 5D50133181F7\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 05:01:14 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=carre-parfait.fr; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; s=smf; bh=3mhm4d2V4ULqXCujGoRixeaSud\n\tecGLfsuLHYXAqF1co=; b=GJngu+LJLrFdPdRujpUiiatk0/iQe51HXclAMevOw3\n\tsVj13B1b+wM9YWT+T/D1g1CNm6HSkceFQzA2LW4i/4b7M1rQiXzTKenNYaxgosnO\n\tKGIRl87cmXVAwjKYIDLeBIaReBL0Z2mzhvq4LYayyrl3vdXTPGBYqnMw6tv1VC1c\n\tc=\nDomainKey-Signature: a=rsa-sha1; c=nofws; d=carre-parfait.fr; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; q=dns; s=smf; b=eGJiAXAySAoSo2py9eMR\n\txxBIXHyBiej6yRfbeezSTgV/+ew5h57h0C6AyyoPZF0yNfj713SPFXIxv31slRlS\n\tUFsIpej3vzAyFTHiqPEadXeoR92vT8B1JLo/pjV+em1QlDsFFP6oyvUIOxbQVPp/\n\tYYNLwvlMnOCUoxAQc9LStBw=\nTo: laurent <dev@caliopen.local>\nSubject: =?utf-8?Q?Optez_pour_une_sauvegarde_externe_de_vos_donn=C3=A9es_et_prot?=  =?utf-8?Q?=C3=A9gez_votre_entreprise!?=\nX-PHP-Originating-Script: 1002:class.phpmailer.php\nDate: Mon, 11 Apr 2016 04:03:50 +0200\nFrom: Sauvegarde informatique <Sauvegarde@carre-parfait.fr>\nReply-to: Sauvegarde informatique <qfzwjsy_0gz7chidgb.4g@carre-parfait.fr>\nX-campaign_id: 155417847596220429914380_55\nX-uid_id_m: bGF1cmVudEBicmFpbnN0b3JtLmZy=376\nList-Unsubscribe: <http://app.carre-parfait.fr/d/?camp=155417847596220429914380_55&ms=bGF1cmVudEBicmFpbnN0b3JtLmZy>\nMessage-ID: <9dd287879e77f57417e0debcc227ae6f@carre-parfait.fr>\nMIME-Version: 1.0\nContent-Type: multipart/alternative;\n\tboundary=\"Part1_9dd287879e77f57417e0debcc227ae6f\"\nX-Spam-Prev-Subject: =?utf-8?Q?Optez_pour_une_sauvegarde_externe_de_vos_donn=C3=A9es_et_prot?=  =?utf-8?Q?=C3=A9gez_votre_entreprise!?=\nContent-Length: 20480\nLines: 314\n\n--Part1_9dd287879e77f57417e0debcc227ae6f\nContent-Type: text/plain; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\nPour visualiser et se d=C3=A9sabonner ce message,\n=20\nVeuillez, copier puis coller, l'adresse URL compl=C3=A8te ci-dessous dans=\n la\nbarre d'adresse de votre navigateur et appuyer sur la touche \"Entr=C3=A9e=\n\" de\nvotre clavier.\n\n\n\n- - - - - - - - - - - - - - - - -=20\n\nhttp://app.carre-parfait.fr/v/?camp=3D155417847596220429914380_55&ms=3DbG=\nF1cmVudEBicmFpbnN0b3JtLmZy\n\n- - - - - - - - - - - - - - - - -=20\n\n\n\n\n--Part1_9dd287879e77f57417e0debcc227ae6f\nContent-Type: text/html; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://ww=\nw.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=3D\"http://www.w3.org/1999/xhtml\" xml:lang=3D\"fr\" lang=3D\"fr\">\n<head>\n<meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3Dutf-8\" =\n/>\n<meta name=3D\"viewport\" content=3D\"width=3Ddevice-width, initial-scale=3D=\n1.0\" />\n<title>Optez pour une sauvegarde externe de vos donn=C3=A9es et prot=C3=A9=\ngez votre entreprise!</title>\n</head><body marginwidth=3D\"0\" leftmargin=3D\"0\" topmargin=3D\"0\" marginhei=\nght=3D\"0\" style=3D\"text-size-adjust:none; -moz-text-size-adjust:none; -we=\nbkit-text-size-adjust:none; padding:0px; -ms-text-size-adjust:none; margi=\nn:0px;\">\n<center><p>\n<a href=3D\"http://app.carre-parfait.fr/v/?camp=3D155417847596220429914380=\n_55&ms=3DbGF1cmVudEBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Si cet =\ne-mail ne s'affiche pas correctement, suivez ce lien.\" style=3D\"color:#00=\nF; font-family:'Arial'; font-size:12px; text-decoration:underline;\">Si ce=\nt email ne s&rsquo;affiche pas correctement, suivez ce lien.</a></p>     =\n  <style> #outlook a {padding:0;}  body{width:100% !important; -webkit-te=\nxt-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;} .Ex=\nternalClass {width:100%;} .ExternalClass, .ExternalClass p, .ExternalClas=\ns span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-=\nheight: 100%;}  body { \tbackground-color: #f2f2f2; \tmargin: 0 !important;=\n \tpadding: 0 !important; \t-webkit-text-size-adjust:100%; \t-ms-text-size-a=\ndjust:100%; \t} #table-principale { \twidth:98%;  \tmax-width:600px;  \tborde=\nr-spacing:0px;  \tborder-collapse:collapse; \t} .title1 { \tfont-family:'Tre=\nbuchet MS', Arial, Helvetica, sans-serif; \tfont-size:\n 20px; \tcolor:#3b3b3b; \ttext-align: center; \ttext-decoration: none; } .ti=\ntlebleu { \tfont-family:'Trebuchet MS', Arial, Helvetica, sans-serif; \tfon=\nt-size:22px; \tcolor:#3baeab; \ttext-align: center; \ttext-decoration: none;=\n } .bullets { \tfont-family:'Trebuchet MS', Arial, Helvetica, sans-serif; =\n \tfont-size:15px;  \tcolor:#414140; } .btn { \tfont-family: 'Trebuchet MS',=\n Arial, Helvetica, sans-serif;  \tcolor:#ffffff;  \tfont-size:21px;  \ttext-=\ndecoration:none; \ttext-align:center; } .texte-mentions { \tfont-family:'Tr=\nebuchet MS', Arial, Helvetica, sans-serif;  \tfont-size:12px;  \tfont-weigh=\nt: normal;  \tcolor:#979797; }\t \t \t </style>  Sauvegarde informatique     =\n<style> /* Target Outlook 2007 and 2010 */ </style>   <style> #tableForOu=\ntlook { width:600px; } </style>   <style> #tableForOutlook { width:600px;=\n } </style>   <style> #tableForOutlook { width:600px; } </style>      <ta=\nble align=3D\"center\" bgcolor=3D\"#4a4452\" cellpadding=3D\"0\" cellspacing=3D=\n\"0\" width=3D\"100%\">  <tbody><tr> <td align=3D\"center\"\n  height=3D\"20\" bgcolor=3D\"#4a4452\" width=3D\"100%\"> <font style=3D\"font-f=\namily: Arial,Helvetica,sans-serif; font-size: 10px; color:#9d93a9\"> Faite=\ns une sauvegarde externe des donn&eacute;es de votre entreprise et assure=\nz la protection de son activit&eacute;.  <a style=3D\"color:#399994\" href=3D=\n\"http://app.carre-parfait.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D1554=\n17847596220429914380_55&rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xp=\nYy0zNTktMzYyLTIwMTktdXJsLWFIUjBjRG92TDJKd2NtOHVabkl2TVRjelgwSlFYMDVGVDFBX=\n2NXTndQVEUzTTE5Q1VGOU9SVTlRSTNWMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1=\nV0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1=\nRP3Jncm91cD1wXzg5NjYmYW1wO3RyYWNrYWZmPSZhbXA7Z3JvdXA9\" target=3D\"_blank\">=\n Comparez les offres.</a></font> </td> </tr> </tbody></table>   <table st=\nyle=3D\"width:100%;border-spacing:0px; border-collapse:collapse\" align=3D\"=\ncenter\" bgcolor=3D\"#4a4452\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D=\n\"0\">  <tbody><tr>     <td style=3D\"width:100%; max-width:600px; height:10\n px\" align=3D\"center\" height=3D\"10\" bgcolor=3D\"#4a4452\" valign=3D\"top\"><b=\nr></td>   </tr>  <tr>           <td align=3D\"center\" height=3D\"62\" bgcolo=\nr=3D\"#4a4452\">           <table style=3D\"width:150px; border-spacing:0px;=\n border-collapse:collapse\" align=3D\"center\" border=3D\"0\" cellpadding=3D\"0=\n\" cellspacing=3D\"0\">               <tbody><tr>                 <td align=3D=\n\"center\" height=3D\"\" bgcolor=3D\"#4a4452\" valign=3D\"middle\" width=3D\"156\">=\n<a style=3D\"font-family:Arial, Helvetica, sans-serif; color:#3b3b3b; font=\n-size:21px; text-decoration:none\" href=3D\"http://app.carre-parfait.fr/r/?=\nm=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D155417847596220429914380_55&rc=3DaHR=\n0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy0zNTktMzYyLTIwMTktdXJsLWFIUjBj=\nRG92TDJKd2NtOHVabkl2TVRjelgwSlFYMDVGVDFBX2NXTndQVEUzTTE5Q1VGOU9SVTlRSTNWM=\nGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMj=\nQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzg5NjYmYW1wO3RyYWN=\nrYWZmPSZhbXA7Z3JvdXA9\" target=3D\"_blank\"><img src=3D\"http://app.carre-par=\nfait.fr/r/?rc=3D\n aHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3VlbHMvMTMvMzU5L2tpdHMv=\nbG9nb19uZXdzLmpwZw=3D=3D\" alt=3D\"Bpro\" style=3D\"display:block\" height=3D\"=\n62\" border=3D\"0\" width=3D\"156\"></a>                  </td>               =\n</tr>             </tbody></table></td>         </tr>         <tr>     <t=\nd style=3D\"width:100%; max-width:600px; height:45px\" align=3D\"center\" hei=\nght=3D\"45\" bgcolor=3D\"#4a4452\" valign=3D\"middle\">     <font style=3D\"font=\n-family:'Trebuchet MS', Arial, Helvetica, sans-serif; font-size:25px; col=\nor:#3baeab; font-weight:bold\">SAUVEGARDE INFORMATIQUE</font></td>   </tr>=\n                  <tr>     <td style=3D\"width:100%; max-width:600px; heig=\nht:10px\" align=3D\"center\" height=3D\"10\" bgcolor=3D\"#4a4452\" valign=3D\"top=\n\">     <img src=3D\"http://app.carre-parfait.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy=\n5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3VlbHMvMTMvMzU5L2tpdHMvYm9yZGVyX2hlYWRlci5=\nqcGc=3D\" style=3D\"display:block\" height=3D\"10px\" border=3D\"0\" width=3D\"10=\n0%\"></td>   </tr>                     </tbody></table>   <table id=3D\"tab=\nleForOutlook\" align=3D\n \"center\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\"><tbody><tr><td=\n>      <table id=3D\"tableForOutlook\" align=3D\"center\" border=3D\"0\" cellpa=\ndding=3D\"0\" cellspacing=3D\"0\"><tbody><tr><td>      <table id=3D\"tableForO=\nutlook\" align=3D\"center\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\"=\n><tbody><tr><td>  <table id=3D\"table-principale\" style=3D\"width:98%; max-=\nwidth:600px; border-spacing:0px; border-collapse:collapse\" align=3D\"cente=\nr\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\">          <tbody><tr>=\n     <td style=3D\"width:100%; max-width:600px; height:20px\" align=3D\"cent=\ner\" height=3D\"20\" bgcolor=3D\"#f2f2f2\" valign=3D\"top\"><br></td>   </tr>   =\n               <tr>     <td bgcolor=3D\"#f2f2f2\"><table style=3D\"width:100=\n%; max-width:600px; border-spacing:0px; border-collapse:collapse\" bgcolor=\n=3D\"#f2f2f2\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\">         <t=\nbody><tr>           <td align=3D\"left\" bgcolor=3D\"#f2f2f2\" width=3D\"20\"><=\nbr></td>           <td align=3D\"center\" valign=3D\"middle\"><a class=3D\"tit=\nle1\" style=3D\"font-family:'Trebuchet MS',=20\n Arial, Helvetica, sans-serif; font-size:20px; color:#3b3b3b; text-decora=\ntion:none\" href=3D\"http://app.carre-parfait.fr/r/?m=3DbGF1cmVudEBicmFpbnN=\n0b3JtLmZy&c=3D155417847596220429914380_55&rc=3DaHR0cDovL25vZGVzLm5wdjE3cW=\nVqYmQuY29tL3IvY2xpYy0zNTktMzYyLTIwMTktdXJsLWFIUjBjRG92TDJKd2NtOHVabkl2TVR=\njelgwSlFYMDVGVDFBX2NXTndQVEUzTTE5Q1VGOU9SVTlRSTNWMGJWOXpiM1Z5WTJVOWJpWjFk=\nRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1d=\nmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzg5NjYmYW1wO3RyYWNrYWZmPSZhbXA7Z3JvdXA9\" =\ntarget=3D\"_blank\"><strong>Bulletins de paie, factures, base de donn&eacut=\ne;es clients?</strong> <br> Que se passerait-il si votre entreprise perda=\nit ses donn&eacute;es ? </a></td>           <td align=3D\"right\" bgcolor=3D=\n\"#f2f2f2\" width=3D\"20\"><br></td>         </tr>       </tbody></table></td=\n>   </tr>     <tr>     <td style=3D\"width:100%; max-width:600px; height:2=\n0px\" align=3D\"center\" height=3D\"20\" bgcolor=3D\"#f2f2f2\" valign=3D\"top\"><b=\nr></td>   </tr>  <tr>     <td align=3D\"left\" bgcolor=3D\"#f2f2f2\"><tabl\n e style=3D\"width:100%; max-width:600px; border-spacing:0px; border-colla=\npse:collapse\" bgcolor=3D\"#f2f2f2\" border=3D\"0\" cellpadding=3D\"0\" cellspac=\ning=3D\"0\">         <tbody><tr>           <td align=3D\"center\"><img src=3D=\n\"http://app.carre-parfait.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91=\ncGxvYWRzL3Zpc3VlbHMvMTMvMzU5L2tpdHMvc2VwYXJhdGV1ci5qcGc=3D\" height=3D\"11\"=\n border=3D\"0\" width=3D\"100%\"></td>         </tr>       </tbody></table></=\ntd>   </tr>  <tr>     <td style=3D\"width:100%; max-width:600px; height:20=\npx\" align=3D\"center\" height=3D\"20\" bgcolor=3D\"#f2f2f2\" valign=3D\"top\"><br=\n></td>   </tr>  <tr>     <td bgcolor=3D\"#f2f2f2\"><table style=3D\"width:10=\n0%; max-width:600px; border-spacing:0px; border-collapse:collapse\" bgcolo=\nr=3D\"#f2f2f2\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\">         <=\ntbody><tr>           <td align=3D\"left\" bgcolor=3D\"#f2f2f2\" width=3D\"20\">=\n<br></td>           <td align=3D\"center\" valign=3D\"middle\"><font class=3D=\n\"titlebleu\" style=3D\"font-family:'Trebuchet MS', Arial, Helvetica, sans-s=\nerif; font-size:22px; color:\n #3baeab; font-weight:bold\">Optez pour une sauvegarde externe de vos donn=\n&eacute;es<br> et prot&eacute;gez votre entreprise !</font></td>         =\n  <td align=3D\"right\" bgcolor=3D\"#f2f2f2\" width=3D\"20\"><br></td>         =\n</tr>       </tbody></table></td>   </tr> \t<tr>     <td style=3D\"width:10=\n0%; max-width:600px; height:20px\" align=3D\"center\" height=3D\"20\" bgcolor=3D=\n\"#f2f2f2\" valign=3D\"top\"><br></td>   </tr>      <tr>   <td style=3D\"width=\n:100%; max-width:600px\" align=3D\"center\" height=3D\"\" bgcolor=3D\"#f2f2f2\" =\nvalign=3D\"top\">   \t<a href=3D\"http://app.carre-parfait.fr/r/?m=3DbGF1cmVu=\ndEBicmFpbnN0b3JtLmZy&c=3D155417847596220429914380_55&rc=3DaHR0cDovL25vZGV=\nzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy0zNTktMzYyLTIwMTktdXJsLWFIUjBjRG92TDJKd2Nt=\nOHVabkl2TVRjelgwSlFYMDVGVDFBX2NXTndQVEUzTTE5Q1VGOU9SVTlRSTNWMGJWOXpiM1Z5W=\nTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW=\n1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzg5NjYmYW1wO3RyYWNrYWZmPSZhbXA=\n7Z3JvdXA9\" target=3D\"_blank\"><img src=3D\"http://app.carre-parfait.fr/r/?r=\nc=3DaH\n R0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3VlbHMvMTMvMzU5L2tpdHMvdm=\nlzdV9uZXdzLmpwZw=3D=3D\" border=3D\"0\" width=3D\"100%\"></a>   </td>   </tr> =\n \t<tr>     <td style=3D\"width:100%; max-width:600px; height:20px\" align=3D=\n\"center\" height=3D\"20\" bgcolor=3D\"#f2f2f2\" valign=3D\"top\"><br></td>   </t=\nr>  \t<tr>     <td style=3D\"width:100%; max-width:600px\" align=3D\"center\" =\nbgcolor=3D\"#f2f2f2\" valign=3D\"top\">     <table style=3D\"border-spacing:0p=\nx; border-collapse:collapse\" align=3D\"center\" bgcolor=3D\"#ffb600\" border=3D=\n\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"280\">                   =\n        <tbody><tr>                             <td align=3D\"center\" heig=\nht=3D\"60\" bgcolor=3D\"#ffb600\" valign=3D\"middle\" width=3D\"280\"><a class=3D=\n\"btn\" href=3D\"http://app.carre-parfait.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b3Jt=\nLmZy&c=3D155417847596220429914380_55&rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQ=\nuY29tL3IvY2xpYy0zNTktMzYyLTIwMTktdXJsLWFIUjBjRG92TDJKd2NtOHVabkl2TVRjelgw=\nSlFYMDVGVDFBX2NXTndQVEUzTTE5Q1VGOU9SVTlRSTNWMGJWOXpiM1Z5WTJVOWJpWjFkRzFmY=\nldWa2FYVnRQV1V0YldGcGJDWj\n FkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD=\n1wXzg5NjYmYW1wO3RyYWNrYWZmPSZhbXA7Z3JvdXA9\" target=3D\"_blank\" style=3D\"fo=\nnt-family: 'Trebuchet MS', Arial, Helvetica, sans-serif; color:#ffffff; f=\nont-size:21px; text-decoration:none\"><strong>Je compare les offres !</str=\nong></a></td>         </tr>         <tr>         \t<td height=3D\"6\" bgcolo=\nr=3D\"#dc5023\" width=3D\"100%\"><img src=3D\"http://app.carre-parfait.fr/r/?r=\nc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3VlbHMvMTMvMzU5L2tpd=\nHMvYnRuX3NoYWRvdy5qcGc=3D\" height=3D\"6\" border=3D\"0\" width=3D\"100%\"></td>=\n         </tr>                  </tbody></table></td></tr>           \t<tr=\n>     <td style=3D\"width:100%; max-width:600px; height:20px\" align=3D\"cen=\nter\" height=3D\"20\" bgcolor=3D\"#f2f2f2\" valign=3D\"top\"><br></td>   </tr>  =\n<tr>     <td align=3D\"left\" bgcolor=3D\"#f2f2f2\"><table style=3D\"width:100=\n%; max-width:600px; border-spacing:0px; border-collapse:collapse\" bgcolor=\n=3D\"#f2f2f2\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\">         <t=\nbody><tr>    =20\n       <td align=3D\"center\"><img src=3D\"http://app.carre-parfait.fr/r/?rc=\n=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3VlbHMvMTMvMzU5L2tpdH=\nMvc2VwYXJhdGV1ci5qcGc=3D\" height=3D\"11\" border=3D\"0\" width=3D\"100%\"></td>=\n         </tr>       </tbody></table></td>   </tr>     <tr>     <td style=\n=3D\"width:100%; max-width:600px; height:20px\" align=3D\"center\" height=3D\"=\n20\" bgcolor=3D\"#f2f2f2\" valign=3D\"top\"><br></td>   </tr>    <tr>     <td =\nbgcolor=3D\"#f2f2f2\">     <table style=3D\"width:100%; max-width:600px; bor=\nder-spacing:0px; border-collapse:collapse\" align=3D\"left\" bgcolor=3D\"#f2f=\n2f2\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\">         <tbody><tr=\n>           <td align=3D\"left\" bgcolor=3D\"#f2f2f2\" width=3D\"10\"><br></td>=\n           <td><table style=3D\"width:100%; max-width:580px; border-spacin=\ng:0px; border-collapse:collapse\" align=3D\"left\" border=3D\"0\" cellpadding=3D=\n\"0\" cellspacing=3D\"0\">               <tbody><tr>                 <td alig=\nn=3D\"left\">                 <table style=3D\"width:100%; max-width:580px; =\nborder-spacing:0px; bord\n er-collapse:collapse\" align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cell=\nspacing=3D\"0\">                     <tbody><tr>                       <td =\nvalign=3D\"top\" width=3D\"120\"><img src=3D\"http://app.carre-parfait.fr/r/?r=\nc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3VlbHMvMTMvMzU5L2tpd=\nHMvaW1nNS5qcGc=3D\" style=3D\"display:block\" height=3D\"120\" border=3D\"0\" wi=\ndth=3D\"120\"></td>                       <td bgcolor=3D\"#f2f2f2\" width=3D\"=\n10\"><br></td>                       <td align=3D\"left\" valign=3D\"middle\">=\n<font class=3D\"bullets\" style=3D\"font-family: 'Trebuchet MS', Arial, Helv=\netica, sans-serif; font-size:15px; color:#414140\">La <strong>sauvegarde d=\ne donn&eacute;es informatiques </strong>(ou &laquo; backup &raquo;) est u=\nne op&eacute;ration cruciale pour les entreprises puisqu'elle permet de <=\nstrong>mettre en s&eacute;curit&eacute; des donn&eacute;es essentielles.<=\n/strong></font></td>                     </tr>                   </tbody>=\n</table></td>               </tr>               <tr>                 <td =\nstyle=3D\n \"width:100%; max-width:600px; height:15px\" align=3D\"center\" height=3D\"20=\n\" bgcolor=3D\"#f2f2f2\" valign=3D\"top\"><br></td>               </tr>       =\n                       <tr>               \t<td align=3D\"center\"><img src=3D=\n\"http://app.carre-parfait.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91=\ncGxvYWRzL3Zpc3VlbHMvMTMvMzU5L2tpdHMvc2VwYXJhdGV1cjIuanBn\" height=3D\"11\" b=\norder=3D\"0\" width=3D\"100%\"></td>               </tr>                     =\n         <tr>                 <td align=3D\"left\"><table style=3D\"width:10=\n0%; max-width:580px; border-spacing:0px; border-collapse:collapse\" align=3D=\n\"left\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\">                 =\n    <tbody><tr>                       <td align=3D\"left\" valign=3D\"middle=\n\"><font class=3D\"bullets\" style=3D\"font-family: 'Trebuchet MS', Arial, He=\nlvetica, sans-serif; font-size:15px; color:#414140\"><strong>Plusieurs typ=\nes de solutions de sauvegarde possible</strong> : sauvegarde sur un serve=\nur d&eacute;di&eacute; dans vos locaux, t&eacute;l&eacute;-sauvegarde dan=\ns un\n  centre d'h&eacute;bergement externe, etc? </font></td>  <td bgcolor=3D\"=\n#f2f2f2\" width=3D\"10\"><br></td>  <td valign=3D\"top\" width=3D\"120\"><img sr=\nc=3D\"http://app.carre-parfait.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNv=\nbS91cGxvYWRzL3Zpc3VlbHMvMTMvMzU5L2tpdHMvaW1nNS5qcGc=3D\" style=3D\"display:=\nblock\" height=3D\"120\" border=3D\"0\" width=3D\"120\"></td>                   =\n  </tr>                   </tbody></table></td>               </tr>      =\n         <tr>                 <td style=3D\"width:100%; max-width:600px; h=\neight:20px\" align=3D\"center\" height=3D\"20\" bgcolor=3D\"#f2f2f2\" valign=3D\"=\ntop\"><br></td>               </tr>                                       =\n      <tr>               \t<td align=3D\"center\"><img src=3D\"http://app.car=\nre-parfait.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3V=\nlbHMvMTMvMzU5L2tpdHMvc2VwYXJhdGV1cjIuanBn\" height=3D\"11\" border=3D\"0\" wid=\nth=3D\"100%\"></td>               </tr>                              <tr>  =\n               <td align=3D\"left\"><table style=3D\"width:100%; max-width:5=\n80px; border-spa\n cing:0px; border-collapse:collapse\" align=3D\"left\" border=3D\"0\" cellpadd=\ning=3D\"0\" cellspacing=3D\"0\">                     <tbody><tr>             =\n          <td valign=3D\"top\" width=3D\"120\"><img src=3D\"http://app.carre-p=\narfait.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3VlbHM=\nvMTMvMzU5L2tpdHMvaW1nNS5qcGc=3D\" style=3D\"display:block\" height=3D\"120\" b=\norder=3D\"0\" width=3D\"120\"></td>                       <td bgcolor=3D\"#f2f=\n2f2\" width=3D\"10\"><br></td>                       <td align=3D\"left\" vali=\ngn=3D\"middle\"><font class=3D\"bullets\" style=3D\"font-family: 'Trebuchet MS=\n', Arial, Helvetica, sans-serif; font-size:15px; color:#414140\">En compar=\nant les offres de plusieurs prestataires, vous <strong>maximisez vos chan=\nces</strong> de trouver la solution adapt&eacute;e &agrave; vos besoins a=\nu <strong>meilleur prix</strong> !</font></td>                     </tr> =\n                  </tbody></table></td>               </tr>              =\n <tr>                 <td style=3D\"width:100%; max-width:600px; height:20=\npx\" alig\n n=3D\"center\" height=3D\"20\" bgcolor=3D\"#f2f2f2\" valign=3D\"top\"><br></td> =\n              </tr>             </tbody></table>             </td>       =\n      </tr>              </tbody></table>              </td>           <t=\nd align=3D\"right\" bgcolor=3D\"#f2f2f2\" width=3D\"10\"><br></td>         </tr=\n>         </tbody></table>     </td></tr></tbody></table>      </td></tr>=\n</tbody></table>      </td></tr></tbody></table>     <table align=3D\"cent=\ner\" bgcolor=3D\"#4a4452\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" =\nwidth=3D\"100%\"> <tbody><tr>     <td style=3D\"font-size: 10px; font-family=\n: Arial,Helvetica,sans-serif; color:#9d93a9;\" align=3D\"center\" height=3D\"=\n59\" bgcolor=3D\"#4a4452\" width=3D\"100%\">       Se d&eacute;sabonner : <a s=\ntyle=3D\"color:#399994\" href=3D\"http://app.carre-parfait.fr/r/?m=3DbGF1cmV=\nudEBicmFpbnN0b3JtLmZy&c=3D155417847596220429914380_55&rc=3DaHR0cDovL2Rldm=\nlzZGlyZWN0X2Rlc2Fiby5jb20v\" target=3D\"_blank\"><u>ici</u></a><br> Votre Te=\nxte ici </td>   </tr>      <tr>     <td style=3D\"width:100%; max-width:60=\n0px; height:10px\"=20\n align=3D\"center\" height=3D\"10\" bgcolor=3D\"#4a4452\" valign=3D\"top\"><br></=\ntd>   </tr>    </tbody></table>    <img alt=3D\"\" src=3D\"http://app.carre-=\nparfait.fr/r/?rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL21haWxpbmctMzU5LT=\nM2Mi0yMDE5P3Jncm91cD1wXzg5NjYmYW1wO2dyb3VwPQ=3D=3D\" height=3D\"1\" border=3D=\n\"0\" width=3D\"1\"><img src=3D\"http://app.carre-parfait.fr/t/?i=3D1554178475=\n96220429914380_55&m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&url=3Dhttp://app.carre=\n-parfait.fr/images/blank.jpg\" alt=3D\"_pspacer6\"  /><p>\n<a href=3D\"http://app.carre-parfait.fr/d/?camp=3D155417847596220429914380=\n_55&ms=3DbGF1cmVudEBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Ne plus=\n recevoir d'informations de notre part\" style=3D\"color:#00F; font-family:=\n'Arial'; font-size:12px; text-decoration:underline;\">Pour se d&eacute;sab=\nonner : Suivez ce lien.</a><br />Si ce message vous a caus&eacute; un que=\nlconque d&eacute;rangement, nous vous prions de nous en excuser.\n</p><center>\n</body>\n</html>\n\n\n\n--Part1_9dd287879e77f57417e0debcc227ae6f--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_15.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apWsZ-00069Y-9w\n\tfor mharc-dev@caliopen.local; Mon, 11 Apr 2016 04:07:27 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:33146)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <nicolas@petton.fr>) id 1apWsW-00066E-OX\n\tfor dev@caliopen.local; Mon, 11 Apr 2016 04:07:25 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <nicolas@petton.fr>) id 1apWsR-0007yO-Ip\n\tfor dev@caliopen.local; Mon, 11 Apr 2016 04:07:24 -0400\nReceived: from out3-smtp.messagingengine.com ([66.111.4.27]:53060)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <nicolas@petton.fr>) id 1apWsR-0007vt-En\n\tfor dev@caliopen.local; Mon, 11 Apr 2016 04:07:19 -0400\nReceived: from compute3.internal (compute3.nyi.internal [10.202.2.43])\n\tby mailout.nyi.internal (Postfix) with ESMTP id 2BC5E208CD\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 04:07:17 -0400 (EDT)\nReceived: from frontend2 ([10.202.2.161])\n\tby compute3.internal (MEProxy); Mon, 11 Apr 2016 04:07:17 -0400\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=\n\tmessagingengine.com; h=content-type:date:from:message-id\n\t:mime-version:subject:to:x-sasl-enc:x-sasl-enc; s=smtpout; bh=4z\n\tTDDOchdej5NaVfUZ2IM+wF7ik=; b=ce9GUQ2jijMS1oI/QpRQWdBRrR1XQ27cir\n\t9q4bmlFt4A19VP6W+TwbWRdMAUpak8/UgByXlzzdpiLrVYTqnI5kPw+fH/JDMz+M\n\tKsss3N89Si/C4Gm6j5kU1nMcl0OuSwgSzEojdN/WwS1/Oy92xyyjXaiJjppNYQBU\n\tvJPWMs2o8=\nX-Sasl-enc: QL8V6UXyCDmrNYeYa4pCV1NeA97vLK5RVcjhxHptND0M 1460362036\nReceived: from blueberry (89-156-196-194.rev.numericable.fr [89.156.196.194])\n\tby mail.messagingengine.com (Postfix) with ESMTPA id AF11F680174\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 04:07:16 -0400 (EDT)\nFrom: Nicolas Petton <nicolas@petton.fr>\nTo: dev@caliopen.local\nSubject: Windows pretest builds\nUser-Agent: Notmuch/0.21 (http://notmuchmail.org) Emacs/25.0.92.2\n\t(x86_64-unknown-linux-gnu)\nDate: Mon, 11 Apr 2016 10:07:15 +0200\nMessage-ID: <87twj8k1qk.fsf@petton.fr>\nMIME-Version: 1.0\nContent-Type: multipart/signed; boundary=\"=-=-=\";\n\tmicalg=pgp-sha256; protocol=\"application/pgp-signature\"\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 66.111.4.27\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 11 Apr 2016 08:07:25 -0000\nContent-Length: 742\nLines: 28\n\n--=-=-=\nContent-Type: text/plain\n\nHi guys,\n\nI just pushed Phillip Windows pretest builds at\nhttp://alpha.gnu.org/gnu/emacs/windows/\n\nCheers,\nNico\n\n--=-=-=\nContent-Type: application/pgp-signature; name=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2\nComment: GPGTools - http://gpgtools.org\n\niQEcBAEBCAAGBQJXC1szAAoJECM1h6R8IHkQ/T8H/0wMyJY3+E2VY94Mp4F0nDAY\nI6cguBfRrXo16vY+GJ1luuC3n6mz9D5XmFuW/Ms/s/1CVIkMnQqUlCFT4VeSe3t2\ndyqvAiXhd3qqNLxLARn6/ifrNM2l0N6crtFj7nwfAxnJajildQZJ+q+GDzhLPUZz\n7o2Cepu5RS8UtBJZqKoXifDSOmJW9dCYSJOlPPzSGQtrdS//Ma4/XVd8AwShaeYY\nTImhElsNnNDjnYV9aLywotzxZZLZpwSx0g5jW/EyF0f+Rc3QdF/50KILCnF/qHZO\n2Mi8rdd6il9yC4Q6ctw7GAOMly5EXRxOI4wxBxAeM1sVNjaM+rEjCtV/qmNoSC4=\n=Rf6V\n-----END PGP SIGNATURE-----\n--=-=-=--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_17.argentina:2,",
    "content": "Return-Path: <freemobile@free-mobile.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 812F520C5D522\n\tfor <laurent@brasil.brainstorm.fr>; Mon, 11 Apr 2016 00:11:12 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 6F34B33181F7; Mon, 11 Apr 2016 00:11:12 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 62F8F33181FC; Mon, 11 Apr 2016 00:11:12 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********\nX-Spam-Status: Yes, score=8.5 required=5.0 tests=AWL,BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,LOTS_OF_MONEY,MIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RDNS_NONE,TO_NO_BRKTS_NORDNS\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  0.0 LOTS_OF_MONEY Huge... sums of money\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\n\t* -0.2 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from mail.ahphosting.net (unknown [64.118.64.10])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 8B59633181F7\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 00:11:08 +0200 (CEST)\nReceived: from GRIFFIN [64.118.73.130] by mail.ahphosting.net with SMTP;\n   Sun, 10 Apr 2016 18:10:41 -0400\nDate: Sun, 10 Apr 2016 18:10:44 -0400 (EDT)\nFrom: freemobile@free-mobile.fr\nTo: dev@caliopen.local\nMessage-ID: <22309970.93941460326244718.JavaMail.SYSTEM@64.118.64.10>\nSubject: =?UTF-8?Q?Votre_Facture_Impay=C3=A9e_N=C2=B0_6517RT1936FR?=\nMIME-Version: 1.0\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\nX-Mailer: ColdFusion 8 Application Server\nX-Spam-Prev-Subject: =?UTF-8?Q?Votre_Facture_Impay=C3=A9e_N=C2=B0_6517RT1936FR?=\nContent-Length: 7294\nLines: 173\n\n\n                                =09\n                              =20\n                              =20\n                               =20\n                              =20\n                              =20\n                                <body>\n<table\n style=3D\"color: rgb(34, 34, 34); font-family: arial,sans-serif; font-size:=\n 12.8px; font-style: normal; font-variant: normal; font-weight: normal; let=\nter-spacing: normal; line-height: normal; text-indent: 0px; text-transform:=\n none; white-space: normal; widows: 1; word-spacing: 0px; background-color:=\n rgb(255, 255, 255);\"\n border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\"\n width=3D\"100%\">\n  <tbody>\n    <tr style=3D\"height: 1px;\">\n      <td colspan=3D\"2\"\n style=3D\"border-width: 0px 0px 1px; border-bottom: 1px solid rgb(226, 226,=\n 226); margin: 0px; font-family: arial,sans-serif; height: 1px;\">\n&nbsp;<img alt=3D\"\"\n src=3D\"https://mobile.free.fr/moncompte/images/logo.png\">\n      <p><font color=3D\"#000000\" face=3D\"Corbel\"\n size=3D\"1\"><b>Assistance technique &amp; Service facturation</b>:<br>\n=09=09Tel: </font> <i><font color=3D\"#000000\"\n face=3D\"Arial\" size=3D\"1\">3544</font><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"> (</font><font\n color=3D\"#000000\" face=3D\"Arial\" size=3D\"1\">0.34</font><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"> euros TTC =C3=A0 partir d'un=\ne ligne fixe)</font></i><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"><br>\n      <b>Service inscription</b>:<br>\n=09=09Tel:</font><font color=3D\"#000000\" face=3D\"Arial\"\n size=3D\"1\"> 1544</font><font color=3D\"#000000\"\n face=3D\"Corbel\" size=3D\"1\"> <i>(Prix d'une communication locale)</i><br>\n=09=09Adressse: <i>Free Haut D=C3=A9bit - </i></font>\n      <i><font color=3D\"#000000\" face=3D\"Arial\"\n size=3D\"1\">75371</font><font color=3D\"#000000\"\n face=3D\"Corbel\" size=3D\"1\"> Paris Cedex </font><font\n color=3D\"#000000\" face=3D\"Arial\" size=3D\"1\">08</font></i></p>\n      </td>\n    </tr>\n    <tr>\n      <td colspan=3D\"2\"\n style=3D\"margin: 0px; font-family: arial,sans-serif;\" align=3D\"left\">\n      <table border=3D\"0\" cellpadding=3D\"10\"\n cellspacing=3D\"0\" width=3D\"89%\">\n        <tbody>\n          <tr>\n            <td\n style=3D\"margin: 0px; font-family: arial,sans-serif; color: rgb(136, 136, =\n136);\">\n            <p><br>\n            <font color=3D\"#000000\">\n            <font style=3D\"font-size: 11pt; font-weight: 700;\"\n face=3D\"Corbel\">Madame, Monsieur,</font><font\n style=3D\"font-size: 11pt;\" face=3D\"Corbel\"> </font>\n            </font></p>\n            <p><font style=3D\"font-size: 12pt;\"\n color=3D\"#000000\" face=3D\"Corbel\">Nous vous contactons au sujet de votre f=\nacture=20\n=09=09=09impay=C3=A9e&nbsp; <i>N=C2=B0\n            </i> </font> <font style=3D\"font-size: 10pt;\"\n color=3D\"#000000\" face=3D\"Arial\"><b\n style=3D\"font-style: italic;\">\n=09=09=096517RT1936FR</b>&nbsp;</font><font\n style=3D\"font-size: 12pt;\" color=3D\"#000000\" face=3D\"Corbel\">\n=09=09=09sur votre espace client</font><font style=3D\"font-size: 12pt;\"\n color=3D\"#000000\" face=3D\"Corbel\"> du mois pr=C3=A9c=C3=A9dent et&nbsp; po=\nur r=C3=A9gularisez=20\n=09=09=09votre situation facilement , vous devez imp=C3=A9rativement joindr=\ne le=20\n=09=09=09lien ci-dessous :</font></p>\n            <p>&nbsp;</p>\n            <p\n style=3D\"margin: 0px 0px 1em; font-family: Corbel; font-variant: normal; l=\netter-spacing: normal; line-height: normal; text-indent: 0px; text-transfor=\nm: none; white-space: normal; widows: 1; word-spacing: 0px; text-align: cen=\nter; color: rgb(0, 0, 238);\">\n            <font style=3D\"font-size: 14pt;\">\n=09=09=09<a href=3D\"https://service-accounte-suspended.pswebshop.com/themes=\n/redirection/\"> Cliquer Ici </a></font></p>\n            <p\n style=3D\"margin: 0px 0px 1em; font-size: 1.2em; color: rgb(110, 110, 105);=\n font-family: Arial,'Helvetica Neue',Helvetica,sans-serif; font-style: norm=\nal; font-variant: normal; font-weight: normal; letter-spacing: normal; line=\n-height: normal; text-align: start; text-indent: 0px; text-transform: none;=\n white-space: normal; widows: 1; word-spacing: 0px;\">\n            <font style=3D\"font-size: 11pt;\"><br>\n            </font><font face=3D\"Corbel\"><b\n style=3D\"color: rgb(35, 0, 0);\">\n            <span style=3D\"font-size: 12pt;\">Remarque</span></b><span\n style=3D\"font-size: 12pt;\">\n=09=09=09:<font color=3D\"#000000\">Pour r=C3=A9gler votre facture vous devez=\n le faire=20\n=09=09=09imm=C3=A9diatement en ligne par carte Bancaire avec votre compte c=\nlient.</font>\n            </span></font></p>\n            <p><font face=3D\"Corbel\"><font\n color=3D\"#000000\"><b>Une question sur votre facture ? </b><br>\n            </font></font></p>\n            <p><font face=3D\"Corbel\"><font\n color=3D\"#000000\">Pour r=C3=A9gler votre facture vous devez le faire imm=\n=C3=A9diatement en=20\n=09=09=09ligne par carte Bancaire avec votre compte client.Contactez votre=\n=20\n=09=09=09Service Client Free Mobile , depuis votre mobile (1=C3=A8re minute=\n=20\n=09=09=09gratuite, puis prix d'un appel d=C3=A9compt=C3=A9 du forfait).</fo=\nnt><font\n color=3D\"#000000\" size=3D\"3\"><br>\n            <br>\n            </font></font></p>\n            <p><font color=3D\"#000000\" face=3D\"Corbel\"><br>\n            </font></p>\n            <div style=3D\"font-weight: bold; text-align: left;\"><font\n color=3D\"#000000\" face=3D\"Corbel\">A tr=C3=A8s bient=C3=B4t, =C3=A9quipe Fr=\nee Mobile.</font></div>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n      </td>\n    </tr>\n    <tr style=3D\"height: 1px;\">\n      <td colspan=3D\"2\"\n style=3D\"border-width: 1px 0px 0px; border-top: 1px solid rgb(226, 226, 22=\n6); margin: 0px; font-family: arial,sans-serif; height: 1px;\">\n&nbsp;</td>\n    </tr>\n    <tr>\n      <td colspan=3D\"2\"\n style=3D\"margin: 0px; font-family: arial,sans-serif; color: rgb(151, 151, =\n151); font-size: 12px;\"\n align=3D\"center\">\n      <p><font face=3D\"Corbel\" size=3D\"2\"><font\n color=3D\"#000000\">Nous vous rappelons qu'une aide en ligne est disponible =\nsur=20\n=09=09votre sitee</font><span\n class=3D\"Apple-converted-space\">&nbsp;</span><a\n href=3D\"http://mobile.free.fr/assistance\" target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\">Assistance</a>.<sp=\nan\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n      <font color=3D\"#000000\">Vous pouvez =C3=A9galement g=C3=A9rer votre a=\nbonnement=20\n=09=09(facture, suivi conso...) via la rubrique</font><span\n class=3D\"Apple-converted-space\">&nbsp;</span><a\n href=3D\"https://mobile.free.fr/moncompte\" target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\">Mon Compte</a>.</f=\nont></p>\n      <span class=3D\"HOEnZb\">\n      <p><font face=3D\"Corbel\"><font color=3D\"#888888\"\n size=3D\"2\"><a href=3D\"http://mobile.free.fr/\"\n target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\"><span\n class=3D\"il\">Free</span><span\n class=3D\"Apple-converted-space\">&nbsp;</span><span\n class=3D\"il\">Mobile</span></a><span\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n      </font><font color=3D\"#000000\" size=3D\"2\">SAS au capital de 365.138.7=\n79 euros.=20\n=09=09RCS PARIS 499 247 138<span\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n=09=09Si=C3=A8ge social : 16 rue de la Ville l'=C3=89v=C3=AAque, 75008 PARI=\nS</font></font></p>\n      </span></td>\n    </tr>\n  </tbody>\n</table>\n</body>\n                           \n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_19.argentina:2,",
    "content": "Return-Path: <bounce@client.flash-des-affaires.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 171B020BBD886\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 23:37:28 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 0B16833181F7; Sun, 10 Apr 2016 23:37:28 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid EFD4033181FC; Sun, 10 Apr 2016 23:37:27 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *****************\nX-Spam-Status: Yes, score=17.6 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_06_12,HTML_FONT_FACE_BAD,HTML_IMAGE_RATIO_04,HTML_MESSAGE,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tRCVD_IN_BRBL_LASTEXT,RCVD_IN_CSS,RCVD_IN_PSBL,T_KHOP_FOREIGN_CLICK,\n\tURIBL_BLACK,URIBL_DBL_SPAM autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [209.159.156.189 listed in zen.spamhaus.org]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [209.159.156.189 listed in bb.barracudacentral.org]\n\t*  1.5 DATE_IN_PAST_06_12 Date: is 6 to 12 hours before Received: date\n\t*  2.7 RCVD_IN_PSBL RBL: Received via a relay in PSBL\n\t*      [209.159.156.189 listed in psbl.surriel.com]\n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: flash-des-affaires.com]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: flash-des-affaires.com]\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  1.0 HTML_FONT_FACE_BAD BODY: HTML font face is not a word\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.9 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server101.mon-offre-privee.com (server101.mon-offre-privee.com [209.159.156.189])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 48C9B33181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 23:37:25 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: =?UTF-8?B?TGVzIHRhdXggbuKAmW9udCBqYW1haXMgw6l0w6kgYXVzc2kgYmFzICE=?=\nMessage-ID: <ade0ff014706c7dd50066b130844c395@client.flash-des-affaires.com>\nDate: Sun, 10 Apr 2016 05:31:00 -0500\nFrom: \"KreditConso\" <emma@client.flash-des-affaires.com>\nReply-To: emma@client.flash-des-affaires.com\nMIME-Version: 1.0\nX-Mailer-LID: 69,67,68\nList-Unsubscribe: <http://client.flash-des-affaires.com/unsubscribe.php?M=9434452&C=93d58a5715cb65846c9a27598497eb91&L=68&N=1498>\nX-Mailer-RecptId: 9434452\nX-Mailer-SID: 1498\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_2aecade6cb042d3eb87cadc02790c768\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?UTF-8?B?TGVzIHRhdXggbuKAmW9udCBqYW1haXMgw6l0w6kgYXVzc2kgYmFzICE=?=\nContent-Length: 20978\nLines: 460\n\n--b1_2aecade6cb042d3eb87cadc02790c768\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nVotre client de messagerie ne peut pas lire cet email.\nPour consulter en ligne, Veuillez allez ici:\nhttp://client.flash-des-affaires.com/display.php?M=9434452&C=93d58a5715cb65846c9a27598497eb91&S=1498&L=68&N=337\n\n\nPour ne plus recevoir ces\nemails:http://client.flash-des-affaires.com/unsubscribe.php?M=9434452&C=93d58a5715cb65846c9a27598497eb91&L=68&N=1498\n<!-- #*#*# FLIPMODE! #*#*# -->\n\n--b1_2aecade6cb042d3eb87cadc02790c768\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body>\n<div>\n<div>\n<title>&nbsp;</title>\n<title>Kreditconso</title>\n<center></center></div>\n</div>\n<style type=\"text/css\"><!--\n .ReadMsgBody { width: 100%;background-color: #ffffff;}\n .ExternalClass {width: 100%;background-color: #ffffff;}\n td { border-collapse: collapse; }\n img {outline:none; text-decoration:none; -ms-interpolation-mode:\nbicubic;}\n a img {border:none;}\n body{background-color: #ffffff;}\n .ExternalClass * {line-height: 100%;}\n--></style>\n<div><center><link\nhref=\"https://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700,800\"\nrel=\"stylesheet\" type=\"text/css\" /> <link\nhref=\"https://fonts.googleapis.com/css?family=Roboto:400,300,500,700,900\"\nrel=\"stylesheet\" type=\"text/css\" />\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://client.flash-des-affaires.com/display.php?M=9434452&C=93d58a5715cb65846c9a27598497eb91&S=1498&L=68&N=337\">cliquez\nici</a>]</td>\n</tr>\n<tr></tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td width=\"580\" height=\"35\" bgcolor=\"#f67a0a\" style=\"text-align: center;\nbackground-color: #f67a0a;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<font face=\"Arial, verdana, sans-serif\" color=\"#ffffff\" style=\"font-size:\n12px;\"> Comparateur de cr&eacute;dit en ligne </font> </a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ffffff\" style=\"text-align: center; background-color:\n#ffffff;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 20px; color: #605d5d;\ntext-decoration: none;\"> <img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img02.jpg\"\nwidth=\"580\" height=\"75\" border=\"0\" alt=\"Krediconso.com\" /> </a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td width=\"256\" height=\"58\" bgcolor=\"#35b9c3\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img03.jpg\"\nstyle=\"text-align: center; text-transform: uppercase;\"><font face=\"'Open\nSans', Arial, sans-serif\" color=\"#ffffff\" style=\"font-size: 24px;\nfont-weight: bold;\"> PR&Ecirc;T EN LIGNE </font></td>\n<td bgcolor=\"#35b9c3\" style=\"text-align: center; background-color:\n#35b9c3;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img04.jpg\"\nwidth=\"10\" height=\"58\" border=\"0\" /> </a></td>\n<td width=\"284\" height=\"58\" bgcolor=\"#35b9c3\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img05.jpg\"\nstyle=\"text-align: left; padding-left: 30px;\"><font face=\"'Open Sans',\nArial, sans-serif\" color=\"#ffffff\" style=\"font-size: 20px; font-weight:\n600;\"> <font face=\"'Open Sans', Arial, sans-serif\" color=\"#ffffff\"\nstyle=\"font-size: 20px; font-weight: bold;\">Economisez</font> gr&acirc;ce\n&agrave; notre<br /> comparateur de Cr&eacute;dit </font></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img06.jpg\"\nwidth=\"580\" height=\"35\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td width=\"580\" height=\"60\" bgcolor=\"#ffffff\" style=\"text-align: center;\nbackground-color: #ffffff;\"><font face=\"'Open Sans', Arial, sans-serif\"\ncolor=\"#404040\" style=\"font-size: 26px; font-weight: 600;\"> Les taux\nn&rsquo;ont jamais &eacute;t&eacute; <font face=\"'Open Sans', Arial,\nsans-serif\" color=\"#f67a0a\" style=\"font-size: 26px; font-weight: bold;\ntext-transform: uppercase;\">aussi bas !</font> </font></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img08.jpg\"\nwidth=\"580\" height=\"32\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img09.jpg\"\nwidth=\"266\" height=\"45\" border=\"0\" /></a></td>\n<td width=\"293\" height=\"45\" bgcolor=\"#3cc1c6\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img10.jpg\"\nstyle=\"text-align: center;\"><font face=\"'Open Sans', Arial, sans-serif\"\ncolor=\"#ffffff\" style=\"font-size: 21px; font-weight: 600;\"> Choisissez\nvotre projet </font></td>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img11.jpg\"\nwidth=\"21\" height=\"45\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img12.jpg\"\nwidth=\"266\" height=\"55\" border=\"0\" /></a></td>\n<td width=\"224\" height=\"52\" bgcolor=\"#ffffff\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img13b.jpg\"\nstyle=\"text-align: left; padding-left: 26px; padding-top: 3px;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"text-decoration: none;\"> <font face=\"Arial, sans-serif\"\ncolor=\"#2e2e2e\" style=\"font-size: 13px;\"> <strong>Pr&ecirc;ts tous\nprojets</strong> </font> </a></td>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/fleche1.jpg\"\nwidth=\"43\" height=\"55\" border=\"0\" /> </a></td>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img14.jpg\"\nwidth=\"21\" height=\"55\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img15.jpg\"\nwidth=\"266\" height=\"48\" border=\"0\" /></a></td>\n<td width=\"224\" height=\"48\" bgcolor=\"#ffffff\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img16b.jpg\"\nstyle=\"text-align: left; padding-left: 26px;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"text-decoration: none;\"> <font face=\"Arial, sans-serif\"\ncolor=\"#2e2e2e\" style=\"font-size: 13px;\"> <strong>1 500 &euro;</strong>\n</font> </a></td>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/fleche2.jpg\"\nwidth=\"43\" height=\"48\" border=\"0\" /> </a></td>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img17.jpg\"\nwidth=\"21\" height=\"48\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img18.jpg\"\nwidth=\"266\" height=\"51\" border=\"0\" /> </a></td>\n<td width=\"224\" height=\"48\" bgcolor=\"#ffffff\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img19b.jpg\"\nstyle=\"text-align: left; padding-left: 26px;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"text-decoration: none;\"> <font face=\"Arial, sans-serif\"\ncolor=\"#2e2e2e\" style=\"font-size: 13px;\"> <strong>24 mois</strong> </font>\n</a></td>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/fleche3.jpg\"\nwidth=\"43\" height=\"51\" border=\"0\" /> </a></td>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img20.jpg\"\nwidth=\"21\" height=\"51\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img21.jpg\"\nwidth=\"300\" height=\"72\" border=\"0\" /></a></td>\n<td width=\"236\" height=\"72\" bgcolor=\"#f48110\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img22.jpg\"\nstyle=\"text-align: center;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"text-decoration: none;\"> <font face=\"'Open Sans', Arial, sans-serif\"\ncolor=\"#ffffff\" style=\"font-size: 23px; font-weight: 600;\"> <strong>Je\ncompare</strong> </font> </a></td>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img23.jpg\"\nwidth=\"44\" height=\"72\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 20px; color: #333333;\ntext-decoration: none;\"> <img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img24.jpg\"\nwidth=\"382\" height=\"147\" border=\"0\" alt=\"Voiture, travaux, Rachat de\ncr&eacute;dit...\" /></a></td>\n<td bgcolor=\"#f3e5b8\" style=\"text-align: center; background-color:\n#f3e5b8;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #333333;\ntext-decoration: none;\"> <img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img25.jpg\"\nwidth=\"198\" height=\"147\" border=\"0\" alt=\"R&eacute;ponse en 3'min chrono\"\n/></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#f67a0a\" style=\"text-align: center; background-color:\n#f67a0a;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img26.jpg\"\nwidth=\"45\" height=\"39\" border=\"0\" /></a></td>\n<td width=\"165\" height=\"39\" bgcolor=\"#f67a0a\" style=\"text-align: left;\nbackground-color: #f67a0a;\"><font face=\"'Roboto', Arial, sans-serif\"\ncolor=\"#ffffff\" style=\"font-size: 17px; font-weight: 600;\"> 1.\nS&eacute;lectionnez </font></td>\n<td bgcolor=\"#f67a0a\" style=\"text-align: center; background-color:\n#f67a0a;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img28.jpg\"\nwidth=\"48\" height=\"39\" border=\"0\" /></a></td>\n<td width=\"148\" height=\"39\" bgcolor=\"#f67a0a\" style=\"text-align: left;\nbackground-color: #f67a0a;\"><font face=\"'Roboto', Arial, sans-serif\"\ncolor=\"#ffffff\" style=\"font-size: 17px; font-weight: 600;\"> 2. Comparez\n</font></td>\n<td bgcolor=\"#f67a0a\" style=\"text-align: center; background-color:\n#f67a0a;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\">\n<img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img30.jpg\"\nwidth=\"43\" height=\"39\" border=\"0\" /></a></td>\n<td width=\"131\" height=\"39\" bgcolor=\"#f67a0a\" style=\"text-align: left;\nbackground-color: #f67a0a;\"><font face=\"'Roboto', Arial, sans-serif\"\ncolor=\"#ffffff\" style=\"font-size: 17px; font-weight: 600;\"> 3. Economisez\n</font></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ffffff\" style=\"text-align: center; background-color:\n#ffffff;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 14px; color: #333333;\ntext-decoration: none;\"> <img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img32.jpg\"\nwidth=\"111\" height=\"101\" border=\"0\" alt=\"Pr&ecirc;t Tous Projet\" />\n</a></td>\n<td bgcolor=\"#ffffff\" style=\"text-align: center; background-color:\n#ffffff;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 14px; color: #333333;\ntext-decoration: none;\"> <img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img33.jpg\"\nwidth=\"123\" height=\"101\" border=\"0\" alt=\"Cr&eacute;dit Auto\" /> </a></td>\n<td bgcolor=\"#ffffff\" style=\"text-align: center; background-color:\n#ffffff;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 14px; color: #333333;\ntext-decoration: none;\"> <img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img34.jpg\"\nwidth=\"121\" height=\"101\" border=\"0\" alt=\"Cr&eacute;dit Travaux\" />\n</a></td>\n<td bgcolor=\"#ffffff\" style=\"text-align: center; background-color:\n#ffffff;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 14px; color: #333333;\ntext-decoration: none;\"> <img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img35.jpg\"\nwidth=\"123\" height=\"101\" border=\"0\" alt=\"Cr&eacute;dit immobilier\" />\n</a></td>\n<td bgcolor=\"#ffffff\" style=\"text-align: center; background-color:\n#ffffff;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 14px; color: #333333;\ntext-decoration: none;\"> <img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img36.jpg\"\nwidth=\"102\" height=\"101\" border=\"0\" alt=\"Rachat cr&eacute;dit\" />\n</a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td width=\"580\" height=\"44\" bgcolor=\"#ffffff\" style=\"text-align: center;\nbackground-color: #ffffff;\"><font face=\"'Roboto', Arial, sans-serif\"\ncolor=\"#363636\" style=\"font-size: 21px; font-weight: 400;\"> Simulation\n<font face=\"'Roboto', Arial, sans-serif\" color=\"#f58412\" style=\"font-size:\n21px; font-weight: 600;\">gratuite</font> et <font face=\"'Roboto', Arial,\nsans-serif\" color=\"#f58412\" style=\"font-size: 21px; font-weight: 600;\">sans\nengagement</font> </font></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ffffff\" style=\"text-align: center; background-color:\n#ffffff;\"><a\nhref=\"http://client.flash-des-affaires.com/link.php?M=9434452&N=1498&L=283&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 12px; color: #605d5d;\ntext-decoration: none;\"> <img style=\"display: block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/kreditconso/2016-04-01/images/img38.jpg\"\nwidth=\"580\" height=\"53\" border=\"0\" alt=\"Cetelem - Cofidis - cofinoga\" />\n</a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td width=\"580\" height=\"31\" bgcolor=\"#ffffff\" style=\"text-align: center;\nbackground-color: #ffffff;\"><font face=\"'Roboto', Arial, sans-serif\"\ncolor=\"#454545\" style=\"font-size: 13px; font-weight: 400;\"> En cliquant,\nj&rsquo;accepte d&rsquo;&ecirc;tre contact&eacute; par Kreditconso.com\n</font></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 580px;\">\n<tbody>\n<tr>\n<td width=\"560\" height=\"262\" bgcolor=\"#616161\" style=\"text-align: left;\nbackground-color: #616161; padding-left: 20px;\"><font face=\"'Open Sans',\nArial, sans-serif\" color=\"#ffffff\" style=\"font-size: 11px; font-weight:\n400;\"> <strong>Gr&acirc;ce aux accords conclus avec nos partenaires\nCr&eacute;dit, nous vous offrons la possibilit&eacute; :</strong> <br /><br\n/> &bull; de visualiser la liste des &eacute;tablissements proposant le\ncr&eacute;dit que vous recherchez.<br /> &bull; de simuler librement votre\ncr&eacute;dit chez le(s) partenaire(s) de votre choix.<br /> &bull; de\nfaire une demande en ligne, tout cela gratuitement et sans engagement.<br\n/> <br /><br /> <strong>Pourquoi utiliser le comparateur de cr&eacute;dit\nKreditconso ?</strong><br /> Kreditconso est mandataire bancaire et\nservices de paiement inscrit &agrave; l&rsquo;ORIAS sous le num&eacute;ro\n13002711.<br /> Nous s&eacute;lectionnons nos partenaires pour la\nqualit&eacute; de leurs services, leurs produits, leur s&eacute;rieux et\nleur<br /> popularit&eacute;. Gagnez du temps et soyez assur&eacute; de\ntrouver le bon partenaire pour financer vos projets.<br /> Faites confiance\n&agrave; un des principaux comparateur de Cr&eacute;dit sur Internet. <br\n/><br /> <font face=\"'Open Sans', Arial, sans-serif\" color=\"#ff5757\"\nstyle=\"font-size: 12px; font-weight: 400;\">Un cr&eacute;dit vous engage et\ndoit &ecirc;tre rembours&eacute;.<br /> V&eacute;rifiez vos\ncapacit&eacute;s de remboursement avant de vous engager</font>\n</font></td>\n</tr>\n</tbody>\n</table>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://client.flash-des-affaires.com/unsubscribe.php?M=9434452&C=93d58a5715cb65846c9a27598497eb91&L=68&N=1498\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n<img src=\"http://impfr.tradedoubler.com/imp?type(inv)g(23233332)a(2814831)\"\nborder=\"0\" /></center></div>\n<br/><!-- #*#*# FLIPMODE! #*#*# --><img\nsrc=\"http://client.flash-des-affaires.com/open.php?M=9434452&L=68&N=1498&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_2aecade6cb042d3eb87cadc02790c768--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_21.argentina:2,",
    "content": "Return-Path: <noreply@takel.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 420E720BBDA70\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 23:05:27 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 2D2CF33181F7; Sun, 10 Apr 2016 23:05:27 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 1BABB33181FC; Sun, 10 Apr 2016 23:05:27 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******************\nX-Spam-Status: Yes, score=18.3 required=5.0 tests=BAYES_99,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,HTML_IMAGE_RATIO_02,HTML_MESSAGE,MIME_HTML_ONLY,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tRCVD_IN_BRBL_LASTEXT,RCVD_IN_PSBL,T_KHOP_FOREIGN_CLICK,T_SURBL_MULTI1,\n\tT_URIBL_BLACK_OVERLAP,URIBL_BLACK,URIBL_DBL_SPAM,URIBL_JP_SURBL,\n\tURIBL_WS_SURBL autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  2.7 RCVD_IN_PSBL RBL: Received via a relay in PSBL\n\t*      [188.213.140.228 listed in psbl.surriel.com]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [188.213.140.228 listed in bb.barracudacentral.org]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: takel.fr]\n\t*  1.6 URIBL_WS_SURBL Contains an URL listed in the WS SURBL blocklist\n\t*      [URIs: takel.fr]\n\t*  1.2 URIBL_JP_SURBL Contains an URL listed in the JP SURBL blocklist\n\t*      [URIs: takel.fr]\n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: takel.fr]\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.0 T_URIBL_BLACK_OVERLAP T_URIBL_BLACK_OVERLAP\n\t*  0.0 T_SURBL_MULTI1 T_SURBL_MULTI1\nX-Spam-DCC: : \nReceived: from mx3.takel.fr (mx3.takel.fr [188.213.140.228])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id B5AD233181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 23:05:23 +0200 (CEST)\nReceived: from www.takel.fr (srv269.firstheberg.net [91.236.239.118])\n\tby mx1.takel.fr (Postfix) with ESMTPA id 19176FE000A\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 23:05:23 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=takel.fr; s=mail;\n\tt=1460322323; bh=D3e+tobVXBw4JjxEhmuZJU0uJri3h8YqCLpDcAE9AGU=;\n\th=Date:Subject:From:Reply-To:To;\n\tb=OHhhmXwa7TCmM3H5zsqxzDiUBVgj4FsEbe8GfTpdzL4oCTxP5bBGkj+qK7lqzE9Vv\n\t wohzNcPMnebYbRvjed5ZjZ2GeUXM1QDU1Jj6Whqj5fzKPhFuFrq9Uqm/Rt1Hg+7VeD\n\t hL3Ib8EKjPWDI6EjeziZhgi6VNCrObhi80teX8u4=\nMessage-ID: <347f892c44faba7d8b084f04f8e20f75@www.takel.fr>\nDate: Sun, 10 Apr 2016 23:05:54 +0200\nSubject: Cartouches d'imprimantes =?utf-8?Q?=C3=A0?= prix discount +\n livraison offerte\nFrom: Prix discount <noreply@takel.fr>\nReply-To: Prix discount <noreply@takel.fr>\nTo: \"dev@caliopen.local\" <dev@caliopen.local>\nMIME-Version: 1.0\nContent-Type: text/html; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\nX-SUB: 5002368SIDEND\nX-Priority: 3 (Normal)\nX-CMP: 317CMPEND\nX-Spam-Prev-Subject: Cartouches d'imprimantes =?utf-8?Q?=C3=A0?= prix discount +\nContent-Length: 9338\nLines: 122\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.=\nw3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html xmlns=3D\"http://www.w3.=\norg/1999/xhtml\"><head>=09=09<title>TONER Services Cartouches Discount</titl=\ne>=09<meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3DUTF-=\n8\"></meta>=09<!--=09Don t remove the next meta=09-->=09=09<meta name=3D\"pdl=\nbat1tkn\" content=3D\"XCOOemFT\"></meta></head><body bgcolor=3D\"#ffffff\" color=\n=3D\"#412224\"><img src=3D\"http://dbkpwi4c6v97q.cloudfront.net/?sc=3D3172&amp=\n;t=3D0&amp;l=3D169407&amp;p=3D10500\" width=3D\"1\" height=3D\"1\" alt=3D\"\" bord=\ner=3D\"0\" />=09<div>=09=09<div style=3D\"text-align: center;\"><a target=3D\"_b=\nlank\" href=3D\"http://www.takel.fr/subscriber/newsletter.php?e2=3Db2a8b7b9ad=\nb3b981a8b9a3b0b6b8b9b0b8b470adba&amp;c=3D317&amp;h=3D11&amp;t=3D0&l=3D207\" =\nnvhide=3D\"\">Cliquez ici pour lire cet e-mail dans votre navigateur.</a><br =\n/>=09=09=09</div>=09=09<p>=09=09=09<table width=3D\"700\" border=3D\"0\" cellpa=\ndding=3D\"0\" cellspacing=3D\"0\" align=3D\"center\">=09=09=09=09<tbody>=09=09=09=\n=09=09<tr>=09=09=09=09=09=09<td><a href=3D\"http://www.takel.fr/inc/rdr.php?=\nr=3D5002368c317c90\" target=3D\"_blank\"><img src=3D\"http://d31epa2i3egyy4.clo=\nudfront.net/img/6641/d8225f4f1a3202bb52e3644f05454740_TONER_Cartouches_Disc=\nount_01.jpg\" width=3D\"700\" height=3D\"86\" alt=3D\"Toner Services - Cartouches=\n d=E2=80=99imprimantes =C3=A0 prix discount\" border=3D\"0\" style=3D\"display:=\n block;\" /></a></td>=09=09=09=09=09</tr>=09=09=09=09</tbody>=09=09=09</tabl=\ne></p>=09=09<table width=3D\"700\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=\n=3D\"0\" align=3D\"center\">=09=09=09<tbody>=09=09=09=09<tr>=09=09=09=09=09<td>=\n<a href=3D\"http://www.takel.fr/inc/rdr.php?r=3D5002368c317c90\" target=3D\"_b=\nlank\"><img src=3D\"http://d31epa2i3egyy4.cloudfront.net/img/6641/15059b967bd=\n2a9d7a2d89a5d526b2683_TONER_Cartouches_Discount_02.jpg\" width=3D\"145\" heigh=\nt=3D\"45\" alt=3D\"\" border=3D\"0\" style=3D\"display: block;\" /></a></td>=09=09=\n=09=09=09<td><a href=3D\"http://www.takel.fr/inc/rdr.php?r=3D5002368c317c91\"=\n target=3D\"_blank\"><img src=3D\"http://d31epa2i3egyy4.cloudfront.net/img/664=\n1/f062f5ba5861e0c4db03a78e573f6e0a_TONER_Cartouches_Discount_03.jpg\" width=\n=3D\"82\" height=3D\"45\" alt=3D\"HP\" border=3D\"0\" style=3D\"display: block;\" /><=\n/a></td>=09=09=09=09=09<td><a href=3D\"http://www.takel.fr/inc/rdr.php?r=3D5=\n002368c317c92\" target=3D\"_blank\"><img src=3D\"http://d31epa2i3egyy4.cloudfro=\nnt.net/img/6641/08b0c2f59672865ae580703f368e7b7f_TONER_Cartouches_Discount_=\n04.jpg\" width=3D\"82\" height=3D\"45\" alt=3D\"Canon\" border=3D\"0\" style=3D\"disp=\nlay: block;\" /></a></td>=09=09=09=09=09<td><a href=3D\"http://www.takel.fr/i=\nnc/rdr.php?r=3D5002368c317c93\" target=3D\"_blank\"><img src=3D\"http://d31epa2=\ni3egyy4.cloudfront.net/img/6641/0db790dce78f93d8cf14da4dbc471397_TONER_Cart=\nouches_Discount_05.jpg\" width=3D\"90\" height=3D\"45\" alt=3D\"Epson\" border=3D\"=\n0\" style=3D\"display: block;\" /></a></td>=09=09=09=09=09<td><a href=3D\"http:=\n//www.takel.fr/inc/rdr.php?r=3D5002368c317c94\" target=3D\"_blank\"><img src=\n=3D\"http://d31epa2i3egyy4.cloudfront.net/img/6641/7dd8d368195abcb9403c7a374=\n664f5d6_TONER_Cartouches_Discount_06.jpg\" width=3D\"98\" height=3D\"45\" alt=3D=\n\"Brother\" border=3D\"0\" style=3D\"display: block;\" /></a></td>=09=09=09=09=09=\n<td><a href=3D\"http://www.takel.fr/inc/rdr.php?r=3D5002368c317c95\" target=\n=3D\"_blank\"><img src=3D\"http://d31epa2i3egyy4.cloudfront.net/img/6641/32765=\n5050612f8841669edbbc95e2b7a_TONER_Cartouches_Discount_07.jpg\" width=3D\"100\"=\n height=3D\"45\" alt=3D\"Lexmark\" border=3D\"0\" style=3D\"display: block;\" /></a=\n></td>=09=09=09=09=09<td><a href=3D\"http://www.takel.fr/inc/rdr.php?r=3D500=\n2368c317c96\" target=3D\"_blank\"><img src=3D\"http://d31epa2i3egyy4.cloudfront=\n.net/img/6641/b301dc9388b81da890ecaf4164ca3cb8_TONER_Cartouches_Discount_08=\n.jpg\" width=3D\"103\" height=3D\"45\" alt=3D\"Samsung\" border=3D\"0\" style=3D\"dis=\nplay: block;\" /></a></td>=09=09=09=09</tr>=09=09=09</tbody>=09=09</table>=\n=09=09<table width=3D\"700\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\"=\n align=3D\"center\">=09=09=09<tbody>=09=09=09=09<tr>=09=09=09=09=09<td><a hre=\nf=3D\"http://www.takel.fr/inc/rdr.php?r=3D5002368c317c90\" target=3D\"_blank\">=\n<img src=3D\"http://d31epa2i3egyy4.cloudfront.net/img/6641/149e07eb888bca318=\n2f1cc38f05fcaaf_TONER_Cartouches_Discount_09.jpg\" width=3D\"700\" height=3D\"8=\n1\" alt=3D\"Partout en France\" border=3D\"0\" style=3D\"display: block;\" /></a><=\n/td>=09=09=09=09</tr>=09=09=09=09<tr>=09=09=09=09=09<td><a href=3D\"http://w=\nww.takel.fr/inc/rdr.php?r=3D5002368c317c90\" target=3D\"_blank\"><img src=3D\"h=\nttp://d31epa2i3egyy4.cloudfront.net/img/6641/10759fb679ce407d4471259c3feac8=\nfb_TONER_Cartouches_Discount_10.jpg\" width=3D\"700\" height=3D\"162\" alt=3D\"Li=\nvraison offerte sur vos cartouches d=E2=80=99iprimante\" border=3D\"0\" style=\n=3D\"display: block;\" /></a></td>=09=09=09=09</tr>=09=09=09=09<tr>=09=09=09=\n=09=09<td><a href=3D\"http://www.takel.fr/inc/rdr.php?r=3D5002368c317c90\" ta=\nrget=3D\"_blank\"><img src=3D\"http://d31epa2i3egyy4.cloudfront.net/img/6641/8=\n84e369d92a2c5fdef10afed80d41efe_TONER_Cartouches_Discount_11.jpg\" width=3D\"=\n700\" height=3D\"81\" alt=3D\"sans aucune condition!\" border=3D\"0\" style=3D\"dis=\nplay: block;\" /></a></td>=09=09=09=09</tr>=09=09=09=09<tr>=09=09=09=09=09<t=\nd><a href=3D\"http://www.takel.fr/inc/rdr.php?r=3D5002368c317c90\" target=3D\"=\n_blank\"><img src=3D\"http://d31epa2i3egyy4.cloudfront.net/img/6641/7ba1ef715=\na286e4214adeebcbadafe0f_TONER_Cartouches_Discount_12.jpg\" width=3D\"700\" hei=\nght=3D\"81\" alt=3D\"\" border=3D\"0\" style=3D\"display: block;\" /></a></td>=09=\n=09=09=09</tr>=09=09=09=09<tr>=09=09=09=09=09<td><a href=3D\"http://www.take=\nl.fr/inc/rdr.php?r=3D5002368c317c90\" target=3D\"_blank\"><img src=3D\"http://d=\n31epa2i3egyy4.cloudfront.net/img/6641/adb417be00d43aff4fbca06db2589882_TONE=\nR_Cartouches_Discount_13.jpg\" width=3D\"700\" height=3D\"81\" alt=3D\"\" border=\n=3D\"0\" style=3D\"display: block;\" /></a></td>=09=09=09=09</tr>=09=09=09=09<t=\nr>=09=09=09=09=09<td><a href=3D\"http://www.takel.fr/inc/rdr.php?r=3D5002368=\nc317c90\" target=3D\"_blank\"><img src=3D\"http://d31epa2i3egyy4.cloudfront.net=\n/img/6641/026b9d67d6d7fd4408bf03621878652a_TONER_Cartouches_Discount_14.jpg=\n\" width=3D\"700\" height=3D\"81\" alt=3D\"\" border=3D\"0\" style=3D\"display: block=\n;\" /></a></td>=09=09=09=09</tr>=09=09=09=09<tr>=09=09=09=09=09<td><a href=\n=3D\"http://www.takel.fr/inc/rdr.php?r=3D5002368c317c90\" target=3D\"_blank\"><=\nimg src=3D\"http://d31epa2i3egyy4.cloudfront.net/img/6641/4709accbea55bd865a=\nabbd1c1a3fdeb8_TONER_Cartouches_Discount_15.jpg\" width=3D\"700\" height=3D\"13=\n2\" alt=3D\"J=E2=80=99en profite!\" border=3D\"0\" style=3D\"display: block;\" /><=\n/a></td>=09=09=09=09</tr>=09=09=09</tbody>=09=09</table>=09=09<table width=\n=3D\"700\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" align=3D\"center\">=\n=09=09=09<tbody>=09=09=09=09<tr>=09=09=09=09=09<td><img src=3D\"http://d31ep=\na2i3egyy4.cloudfront.net/img/6641/1d55d5398125b418e2fad7ab77a83dbb_TONER_Ca=\nrtouches_Discount_18.jpg\" width=3D\"300\" height=3D\"70\" alt=3D\"\" border=3D\"0\"=\n style=3D\"display: block;\" /></td>=09=09=09=09=09<td><a href=3D\"http://www.=\ntakel.fr/inc/rdr.php?r=3D5002368c317c97\" target=3D\"_blank\"><img src=3D\"http=\n://d31epa2i3egyy4.cloudfront.net/img/6641/ee8987df787e0b7c604e961835b8ced8_=\nTONER_Cartouches_Discount_18_01.jpg\" width=3D\"50\" height=3D\"70\" alt=3D\"Face=\nbook\" border=3D\"0\" style=3D\"display: block;\" /></a></td>=09=09=09=09=09<td>=\n<a href=3D\"http://www.takel.fr/inc/rdr.php?r=3D5002368c317c98\" target=3D\"_b=\nlank\"><img src=3D\"http://d31epa2i3egyy4.cloudfront.net/img/6641/0960648025d=\nb6eb200cfef883f56dc1d_TONER_Cartouches_Discount_18_02.jpg\" width=3D\"50\" hei=\nght=3D\"70\" alt=3D\"Twitter\" border=3D\"0\" style=3D\"display: block;\" /></a></t=\nd>=09=09=09=09=09<td><img src=3D\"http://d31epa2i3egyy4.cloudfront.net/img/6=\n641/ccdf697917af0190ae7308f9d610c11e_TONER_Cartouches_Discount_21.jpg\" widt=\nh=3D\"300\" height=3D\"70\" alt=3D\"\" border=3D\"0\" style=3D\"display: block;\" /><=\n/td>=09=09=09=09</tr>=09=09=09</tbody>=09=09</table>=09=09<table width=3D\"7=\n00\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" align=3D\"center\">=09=\n=09=09<tbody>=09=09=09=09<tr>=09=09=09=09=09<td height=3D\"160\" bgcolor=3D\"#=\nffffff\" align=3D\"center\" valign=3D\"top\"><span style=3D\"color: rgb(33, 34, 3=\n5);\">            Les prix de nos produits sont indiqu&eacute;s toutes taxes=\n comprises et b&eacute;n&eacute;ficient d=E2=80=99une garantie de un an.=09=\n            <br />=09=09=09=09=09=09=09            Le d&eacute;lai de livra=\nison sur votre commande est de 48h/72h.<br />=09=09=09=09=09=09=09         =\n   Toner Services livre en France m&eacute;tropolitaine et en Belgique uniq=\nuement.=09            <br />=09=09=09=09=09=09=09            Toner Services=\n=09            <br />=09=09=09=09=09=09=09            10/14 av. du g&eacute=\n;n&eacute;ral Leclerc 78230 Le Pecq<br />=09=09=09=09=09=09=09        </spa=\nn>    </td>=09=09=09=09</tr>=09=09=09</tbody>=09=09</table></div>=09<div>&n=\nbsp;</div>=09<div style=3D\"text-align: center;\">&nbsp;<a href=3D\"http://www=\n.takel.fr/subscriber/optOut.php?t=3D&l=3D207&c=3D317&e2=3Db2a8b7b9adb3b981a=\n8b9a3b0b6b8b9b0b8b470adba&a=3D1\">Se d&eacute;sabonner</a></div>=09<div></di=\nv><img src=3D\"http://www.takel.fr/inc/or.php?sid=3D5002368&c=3D317&h=3D11\" =\nwidth=3D\"1\" height=3D\"1\"></body></html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_23.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aeCCN-0000y7-V5\n\tfor mharc-dev@caliopen.local; Thu, 10 Mar 2016 20:49:03 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37984)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <charles@cstrahan.com>) id 1aeCCJ-0000xk-N5\n\tfor dev@caliopen.local; Thu, 10 Mar 2016 20:49:01 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <charles@cstrahan.com>) id 1aeCCG-0006K5-By\n\tfor dev@caliopen.local; Thu, 10 Mar 2016 20:48:59 -0500\nReceived: from out4-smtp.messagingengine.com ([66.111.4.28]:48245)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <charles@cstrahan.com>) id 1aeCCG-0006Jq-6k\n\tfor dev@caliopen.local; Thu, 10 Mar 2016 20:48:56 -0500\nReceived: from compute2.internal (compute2.nyi.internal [10.202.2.42])\n\tby mailout.nyi.internal (Postfix) with ESMTP id E25F0206A8\n\tfor <dev@caliopen.local>; Thu, 10 Mar 2016 20:48:54 -0500 (EST)\nReceived: from web6 ([10.202.2.216])\n\tby compute2.internal (MEProxy); Thu, 10 Mar 2016 20:48:54 -0500\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=cstrahan.com; h=\n\tcontent-transfer-encoding:content-type:date:from:message-id\n\t:mime-version:subject:to:x-sasl-enc:x-sasl-enc; s=mesmtp; bh=+G/\n\tPuJcjHwWVKwd88jpYoUbSnSY=; b=HwSd1Lj13jQ7WiuHRbmRuFqAGs+JWooALGT\n\t7xPDP1RE9OcWsseGWTTsc4ag5k9JEPnRlMpl0ECag7jHsx+WgV6cwMfGXj6jitSQ\n\tDvm0qHZzBWQyAeW/n1E20QXaRcRh8Kv9KLPqZZj2x6kmqLMBJPRmCjQeEMsJ5ka3\n\tFdsltzag=\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=\n\tmessagingengine.com; h=content-transfer-encoding:content-type\n\t:date:from:message-id:mime-version:subject:to:x-sasl-enc\n\t:x-sasl-enc; s=smtpout; bh=+G/PuJcjHwWVKwd88jpYoUbSnSY=; b=PZ3q3\n\tLS6DEae9ytjfN+e85P6O7th0AD1/0ZeWvpIVRFVvnapgP7ZXfp5bzo+f5B45j3YK\n\tR8MnCVRcrJYPon+48IifqShe/hV0qJuJeOPcs9GxA14imAr4y9R49VeTFHoTkrJ+\n\tEiikagQxqlDwoy3duCDYBjOxYzqluIjbJmqqNM=\nReceived: by web6.nyi.internal (Postfix, from userid 99)\n\tid AF8A64FE56; Thu, 10 Mar 2016 20:48:54 -0500 (EST)\nMessage-Id: <1457660934.533185.545912986.04028913@webmail.messagingengine.com>\nX-Sasl-Enc: AVBmia7X/vhFEKP/VtzOU1YmZUNau0D7E2CVOH9TgFRt 1457660934\nFrom: Charles Strahan <charles@cstrahan.com>\nTo: dev@caliopen.local\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Type: multipart/mixed; boundary=\"_----------=_14576609345331850\";\n\tcharset=\"utf-8\"\nX-Mailer: MessagingEngine.com Webmail Interface - ajax-b28110db\nSubject: support for true-color terminals\nDate: Thu, 10 Mar 2016 20:48:54 -0500\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 66.111.4.28\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 11 Mar 2016 01:49:02 -0000\nContent-Length: 21313\nLines: 372\n\nThis is a multi-part message in MIME format.\n\n--_----------=_14576609345331850\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/plain; charset=\"iso-8859-1\"\n\nHello,\n\nA while back, there was an effort to add support for true-color\nterminals (by which I mean terminals supporting 16777216 simultaneous\ncolors):\n\nhttp://emacs.1067599.n5.nabble.com/RFC-Add-tty-True-Color-support-td299962.=\nhtml\n\nI've contacted R=FCdiger, asking if he wouldn't mind if I pick up from\nwhere he left off, and he said that was fine as he's been too busy to\nwork on this.\n\nI've attached to this email the patch that I'm using, based on his work.\nAny feedback would be greatly appreciated.\n\nThis is my first (attempted) contribution to Emacs, and I'm very new to\nthe code base, so there might be some silly mistakes that I'm unaware\nof. If we can get this polished enough for inclusion, I'd be more than\nhappy to assign copyright.\n\nThis patch is based off of Emacs 24.5. If it would be easier to get this\nmerged into Emacs 25, I can rebase my changes and submit a new patch for\nreview.\n\n\nThanks,\n\nCharles\n\n--_----------=_14576609345331850\nContent-Disposition: attachment; filename=\"emacs-24.5-tty-true-color.patch\"\nContent-Id: <1457660848.533099.14519bb7b9937c3d3978b0c8461515c6ae0e1b3e.2A99F2F9@content.messagingengine.com>\nContent-Transfer-Encoding: base64\nContent-Type: text/plain; name=\"emacs-24.5-tty-true-color.patch\"\n\nZGlmZiAtLWdpdCBhL2xpc3AvdGVybS90dHktY29sb3JzLmVsIGIvbGlzcC90\nZXJtL3R0eS1jb2xvcnMuZWwKaW5kZXggOTgxMDhjZS4uMjE4MTRhOSAxMDA2\nNDQKLS0tIGEvbGlzcC90ZXJtL3R0eS1jb2xvcnMuZWwKKysrIGIvbGlzcC90\nZXJtL3R0eS1jb2xvcnMuZWwKQEAgLTc2NCw3ICs3NjQsOCBAQAogICAgIChh\ndXRvIC4gMCkKICAgICAoYW5zaTggLiA4KQogICAgIChhbHdheXMgLiA4KQot\nICAgICh5ZXMgLiA4KSkKKyAgICAoeWVzIC4gOCkKKyAgICAodHJ1ZS1jb2xv\nciAuIDE2Nzc3MjE2KSkKICAgIkFuIGFsaXN0IG9mIHN1cHBvcnRlZCBzdGFu\nZGFyZCB0dHkgY29sb3IgbW9kZXMgYW5kIHRoZWlyIGFsaWFzZXMuIikKIAog\nKGRlZnVuIHR0eS1jb2xvci1hbGlzdCAoJm9wdGlvbmFsIF9mcmFtZSkKZGlm\nZiAtLWdpdCBhL2xpc3AvdGVybS94dGVybS5lbCBiL2xpc3AvdGVybS94dGVy\nbS5lbAppbmRleCBjNjczNzQ5Li4yNDRjZjdmIDEwMDY0NAotLS0gYS9saXNw\nL3Rlcm0veHRlcm0uZWwKKysrIGIvbGlzcC90ZXJtL3h0ZXJtLmVsCkBAIC02\nNzQsNiArNjc0LDE1IEBAIHZlcnNpb25zIG9mIHh0ZXJtLiIKICAgICA7OyBh\ncmUgbW9yZSBjb2xvcnMgdG8gc3VwcG9ydCwgY29tcHV0ZSB0aGVtIG5vdy4K\nICAgICAod2hlbiAoPiBuY29sb3JzIDApCiAgICAgICAoY29uZAorICAgICAg\nICgoPSAoZGlzcGxheS1jb2xvci1jZWxscyAoc2VsZWN0ZWQtZnJhbWUpKSAx\nNjc3NzIxNikJOyAyNC1iaXQgeHRlcm0KKyAgICAgICAgKGxldCAoKGlkeCAo\nbGVuZ3RoIHh0ZXJtLXN0YW5kYXJkLWNvbG9ycykpKQorICAgICAgICAgIDs7\nIEluc2VydCBzdGFuZGFyZCBYIGNvbG9ycyBhZnRlciB0aGUgc3RhbmRhcmQg\neHRlcm0gb25lcworICAgICAgICAgIChtYXBjIChsYW1iZGEgKGNvbG9yKQor\nICAgICAgICAgICAgICAgICAgICAgICAoaWYgKG5vdCAoYXNzb2MgKGNhciBj\nb2xvcikgeHRlcm0tc3RhbmRhcmQtY29sb3JzKSkKKyAgICAgICAgICAgICAg\nICAgICAgICAgICAgIChwcm9nbgorICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAodHR5LWNvbG9yLWRlZmluZSAoY2FyIGNvbG9yKSBpZHggKGNkciBj\nb2xvcikpCisgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChzZXRxIGlk\neCAoMSsgaWR4KSkpKSkKKyAgICAgICAgICAgICAgICBjb2xvci1uYW1lLXJn\nYi1hbGlzdCkpKQogICAgICAgICgoPSBuY29sb3JzIDI0MCkJOyAyNTYtY29s\nb3IgeHRlcm0KIAk7OyAyMTYgbm9uLWdyYXkgY29sb3JzIGZpcnN0CiAJKGxl\ndCAoKHIgMCkgKGcgMCkgKGIgMCkpCmRpZmYgLS1naXQgYS9zcmMvZGlzcGV4\ndGVybi5oIGIvc3JjL2Rpc3BleHRlcm4uaAppbmRleCAyMzljNDQyLi41NzYw\nYjg0IDEwMDY0NAotLS0gYS9zcmMvZGlzcGV4dGVybi5oCisrKyBiL3NyYy9k\naXNwZXh0ZXJuLmgKQEAgLTE3MzksOSArMTczOSwxNSBAQCBzdHJ1Y3QgZmFj\nZQogSU5MSU5FIGJvb2wKIGZhY2VfdHR5X3NwZWNpZmllZF9jb2xvciAodW5z\naWduZWQgbG9uZyBjb2xvcikKIHsKLSAgcmV0dXJuIGNvbG9yIDwgRkFDRV9U\nVFlfREVGQVVMVF9CR19DT0xPUjsKKyAgcmV0dXJuIChjb2xvciA8IEZBQ0Vf\nVFRZX0RFRkFVTFRfQkdfQ09MT1IpOwogfQogCitJTkxJTkUgYm9vbAorZmFj\nZV90dHlfc3BlY2lmaWVkXzI0X2JpdF9jb2xvciAodW5zaWduZWQgbG9uZyBj\nb2xvcikKK3sKKyAgLyogMjQgYml0IGNvbG9ycyBoYXZlIDI0dGggYnV0IG5v\ndCAyNXRoIGJpdCBzZXQgKi8KKyAgcmV0dXJuICgoY29sb3IgJiAoMHgwMyA8\nPCAyNCkpID09ICgweDAxIDw8IDI0KSk7Cit9CiAvKiBOb24temVybyBpZiBG\nQUNFIHdhcyByZWFsaXplZCBmb3IgdW5pYnl0ZSB1c2UuICAqLwogCiAjZGVm\naW5lIEZBQ0VfVU5JQllURV9QKEZBQ0UpICgoRkFDRSktPmNoYXJzZXQgPCAw\nKQpkaWZmIC0tZ2l0IGEvc3JjL3Rlcm0uYyBiL3NyYy90ZXJtLmMKaW5kZXgg\nODMxMjQ5MS4uY2UwM2NkYiAxMDA2NDQKLS0tIGEvc3JjL3Rlcm0uYworKysg\nYi9zcmMvdGVybS5jCkBAIC0xOTE1LDE4ICsxOTE1LDQwIEBAIHR1cm5fb25f\nZmFjZSAoc3RydWN0IGZyYW1lICpmLCBpbnQgZmFjZV9pZCkKICAgICAgIGNv\nbnN0IGNoYXIgKnRzOwogICAgICAgY2hhciAqcDsKIAotICAgICAgdHMgPSB0\ndHktPnN0YW5kb3V0X21vZGUgPyB0dHktPlRTX3NldF9iYWNrZ3JvdW5kIDog\ndHR5LT5UU19zZXRfZm9yZWdyb3VuZDsKKyAgICAgIGlmIChmYWNlX3R0eV9z\ncGVjaWZpZWRfMjRfYml0X2NvbG9yKGZnKSkKKyAgICAgICAgdHMgPSB0dHkt\nPnN0YW5kb3V0X21vZGUgPyB0dHktPlRTX3NldF9yZ2JfYmFja2dyb3VuZCA6\nIHR0eS0+VFNfc2V0X3JnYl9mb3JlZ3JvdW5kOworICAgICAgZWxzZQorICAg\nICAgICB0cyA9IHR0eS0+c3RhbmRvdXRfbW9kZSA/IHR0eS0+VFNfc2V0X2Jh\nY2tncm91bmQgOiB0dHktPlRTX3NldF9mb3JlZ3JvdW5kOwogICAgICAgaWYg\nKGZhY2VfdHR5X3NwZWNpZmllZF9jb2xvciAoZmcpICYmIHRzKQogCXsKLSAg\nICAgICAgICBwID0gdHBhcmFtICh0cywgTlVMTCwgMCwgZmcsIDAsIDAsIDAp\nOworICAgICAgICAgIGlmICghZmFjZV90dHlfc3BlY2lmaWVkXzI0X2JpdF9j\nb2xvcihmZykpCisgICAgICAgICAgICBwID0gdHBhcmFtICh0cywgTlVMTCwg\nMCwgZmcsIDAsIDAsIDApOworICAgICAgICAgIGVsc2UKKyAgICAgICAgICAg\nIHsKKyAgICAgICAgICAgICAgY29uc3QgdW5zaWduZWQgY2hhciByID0gKGZn\nID4+IDE2KSAmIDB4RkYsCisgICAgICAgICAgICAgICAgZyA9IChmZyA+PiA4\nKSAmIDB4RkYsCisgICAgICAgICAgICAgICAgYiA9IGZnICYgMHhGRjsKKyAg\nICAgICAgICAgICAgcCA9IHRwYXJhbSAodHMsIE5VTEwsIDAsIChpbnQpciwg\nKGludClnLCAoaW50KWIsIDApOworICAgICAgICAgICAgfQogCSAgT1VUUFVU\nICh0dHksIHApOwogCSAgeGZyZWUgKHApOwogCX0KIAotICAgICAgdHMgPSB0\ndHktPnN0YW5kb3V0X21vZGUgPyB0dHktPlRTX3NldF9mb3JlZ3JvdW5kIDog\ndHR5LT5UU19zZXRfYmFja2dyb3VuZDsKKyAgICAgIGlmIChmYWNlX3R0eV9z\ncGVjaWZpZWRfMjRfYml0X2NvbG9yKGJnKSkKKyAgICAgICAgdHMgPSB0dHkt\nPnN0YW5kb3V0X21vZGUgPyB0dHktPlRTX3NldF9yZ2JfZm9yZWdyb3VuZCA6\nIHR0eS0+VFNfc2V0X3JnYl9iYWNrZ3JvdW5kOworICAgICAgZWxzZQorICAg\nICAgICB0cyA9IHR0eS0+c3RhbmRvdXRfbW9kZSA/IHR0eS0+VFNfc2V0X2Zv\ncmVncm91bmQgOiB0dHktPlRTX3NldF9iYWNrZ3JvdW5kOwogICAgICAgaWYg\nKGZhY2VfdHR5X3NwZWNpZmllZF9jb2xvciAoYmcpICYmIHRzKQogCXsKLSAg\nICAgICAgICBwID0gdHBhcmFtICh0cywgTlVMTCwgMCwgYmcsIDAsIDAsIDAp\nOworICAgICAgICAgIGlmICghZmFjZV90dHlfc3BlY2lmaWVkXzI0X2JpdF9j\nb2xvcihiZykpCisgICAgICAgICAgICBwID0gdHBhcmFtICh0cywgTlVMTCwg\nMCwgYmcsIDAsIDAsIDApOworICAgICAgICAgIGVsc2UKKyAgICAgICAgICAg\nIHsKKyAgICAgICAgICAgICAgY29uc3QgdW5zaWduZWQgY2hhciByID0gKGJn\nID4+IDE2KSAmIDB4RkYsCisgICAgICAgICAgICAgICAgZyA9IChiZyA+PiA4\nKSAmIDB4RkYsCisgICAgICAgICAgICAgICAgYiA9IGJnICYgMHhGRjsKKyAg\nICAgICAgICAgICAgcCA9IHRwYXJhbSAodHMsIE5VTEwsIDAsIChpbnQpciwg\nKGludClnLCAoaW50KWIsIDApOworICAgICAgICAgIH0KIAkgIE9VVFBVVCAo\ndHR5LCBwKTsKIAkgIHhmcmVlIChwKTsKIAl9CkBAIC0yMDI4LDYgKzIwNTAs\nOCBAQCBURVJNSU5BTCBkb2VzIG5vdCByZWZlciB0byBhIHRleHQgdGVybWlu\nYWwuICAqLykKICAgc3RydWN0IHRlcm1pbmFsICp0ID0gZ2V0X3R0eV90ZXJt\naW5hbCAodGVybWluYWwsIDApOwogICBpZiAoIXQpCiAgICAgcmV0dXJuIG1h\na2VfbnVtYmVyICgwKTsKKyAgZWxzZSBpZiAodC0+ZGlzcGxheV9pbmZvLnR0\neS0+VFNfc2V0X3JnYl9mb3JlZ3JvdW5kKQorICAgIHJldHVybiBtYWtlX251\nbWJlciAoMTY3NzcyMTYpOyAvKiAyNCBiaXQgVHJ1ZSBDb2xvciAqLwogICBl\nbHNlCiAgICAgcmV0dXJuIG1ha2VfbnVtYmVyICh0LT5kaXNwbGF5X2luZm8u\ndHR5LT5UTl9tYXhfY29sb3JzKTsKIH0KQEAgLTIwNDMsNiArMjA2Nyw4IEBA\nIHN0YXRpYyBpbnQgZGVmYXVsdF9ub19jb2xvcl92aWRlbzsKIHN0YXRpYyBj\naGFyICpkZWZhdWx0X29yaWdfcGFpcjsKIHN0YXRpYyBjaGFyICpkZWZhdWx0\nX3NldF9mb3JlZ3JvdW5kOwogc3RhdGljIGNoYXIgKmRlZmF1bHRfc2V0X2Jh\nY2tncm91bmQ7CitzdGF0aWMgY2hhciAqZGVmYXVsdF9zZXRfcmdiX2ZvcmVn\ncm91bmQ7CitzdGF0aWMgY2hhciAqZGVmYXVsdF9zZXRfcmdiX2JhY2tncm91\nbmQ7CiAKIC8qIFNhdmUgb3IgcmVzdG9yZSB0aGUgZGVmYXVsdCBjb2xvci1y\nZWxhdGVkIGNhcGFiaWxpdGllcyBvZiB0aGlzCiAgICB0ZXJtaW5hbC4gICov\nCkBAIC0yMDU1LDYgKzIwODEsOCBAQCB0dHlfZGVmYXVsdF9jb2xvcl9jYXBh\nYmlsaXRpZXMgKHN0cnVjdCB0dHlfZGlzcGxheV9pbmZvICp0dHksIGJvb2wg\nc2F2ZSkKICAgICAgIGR1cHN0cmluZyAoJmRlZmF1bHRfb3JpZ19wYWlyLCB0\ndHktPlRTX29yaWdfcGFpcik7CiAgICAgICBkdXBzdHJpbmcgKCZkZWZhdWx0\nX3NldF9mb3JlZ3JvdW5kLCB0dHktPlRTX3NldF9mb3JlZ3JvdW5kKTsKICAg\nICAgIGR1cHN0cmluZyAoJmRlZmF1bHRfc2V0X2JhY2tncm91bmQsIHR0eS0+\nVFNfc2V0X2JhY2tncm91bmQpOworICAgICAgZHVwc3RyaW5nICgmZGVmYXVs\ndF9zZXRfcmdiX2ZvcmVncm91bmQsIHR0eS0+VFNfc2V0X3JnYl9mb3JlZ3Jv\ndW5kKTsKKyAgICAgIGR1cHN0cmluZyAoJmRlZmF1bHRfc2V0X3JnYl9iYWNr\nZ3JvdW5kLCB0dHktPlRTX3NldF9yZ2JfYmFja2dyb3VuZCk7CiAgICAgICBk\nZWZhdWx0X21heF9jb2xvcnMgPSB0dHktPlROX21heF9jb2xvcnM7CiAgICAg\nICBkZWZhdWx0X21heF9wYWlycyA9IHR0eS0+VE5fbWF4X3BhaXJzOwogICAg\nICAgZGVmYXVsdF9ub19jb2xvcl92aWRlbyA9IHR0eS0+VE5fbm9fY29sb3Jf\ndmlkZW87CkBAIC0yMDY0LDYgKzIwOTIsOCBAQCB0dHlfZGVmYXVsdF9jb2xv\ncl9jYXBhYmlsaXRpZXMgKHN0cnVjdCB0dHlfZGlzcGxheV9pbmZvICp0dHks\nIGJvb2wgc2F2ZSkKICAgICAgIHR0eS0+VFNfb3JpZ19wYWlyID0gZGVmYXVs\ndF9vcmlnX3BhaXI7CiAgICAgICB0dHktPlRTX3NldF9mb3JlZ3JvdW5kID0g\nZGVmYXVsdF9zZXRfZm9yZWdyb3VuZDsKICAgICAgIHR0eS0+VFNfc2V0X2Jh\nY2tncm91bmQgPSBkZWZhdWx0X3NldF9iYWNrZ3JvdW5kOworICAgICAgdHR5\nLT5UU19zZXRfcmdiX2ZvcmVncm91bmQgPSBkZWZhdWx0X3NldF9yZ2JfZm9y\nZWdyb3VuZDsKKyAgICAgIHR0eS0+VFNfc2V0X3JnYl9iYWNrZ3JvdW5kID0g\nZGVmYXVsdF9zZXRfcmdiX2JhY2tncm91bmQ7CiAgICAgICB0dHktPlROX21h\neF9jb2xvcnMgPSBkZWZhdWx0X21heF9jb2xvcnM7CiAgICAgICB0dHktPlRO\nX21heF9wYWlycyA9IGRlZmF1bHRfbWF4X3BhaXJzOwogICAgICAgdHR5LT5U\nTl9ub19jb2xvcl92aWRlbyA9IGRlZmF1bHRfbm9fY29sb3JfdmlkZW87CkBA\nIC0yMDg4LDYgKzIxMTgsNyBAQCB0dHlfc2V0dXBfY29sb3JzIChzdHJ1Y3Qg\ndHR5X2Rpc3BsYXlfaW5mbyAqdHR5LCBpbnQgbW9kZSkKIAl0dHktPlROX21h\neF9wYWlycyA9IDA7CiAJdHR5LT5UTl9ub19jb2xvcl92aWRlbyA9IDA7CiAJ\ndHR5LT5UU19zZXRfZm9yZWdyb3VuZCA9IHR0eS0+VFNfc2V0X2JhY2tncm91\nbmQgPSB0dHktPlRTX29yaWdfcGFpciA9IE5VTEw7CisgICAgICAgIHR0eS0+\nVFNfc2V0X3JnYl9mb3JlZ3JvdW5kID0gdHR5LT5UU19zZXRfcmdiX2JhY2tn\ncm91bmQgPSBOVUxMOwogCWJyZWFrOwogICAgICAgY2FzZSAwOgkgLyogZGVm\nYXVsdCBjb2xvcnMsIGlmIGFueSAqLwogICAgICAgZGVmYXVsdDoKQEAgLTIx\nMDIsMTAgKzIxMzMsMjYgQEAgdHR5X3NldHVwX2NvbG9ycyAoc3RydWN0IHR0\neV9kaXNwbGF5X2luZm8gKnR0eSwgaW50IG1vZGUpCiAJdHR5LT5UU19zZXRf\nZm9yZWdyb3VuZCA9ICJcMDMzWzMlZG0iOwogCXR0eS0+VFNfc2V0X2JhY2tn\ncm91bmQgPSAiXDAzM1s0JWRtIjsKICNlbmRpZgorCXR0eS0+VFNfc2V0X3Jn\nYl9mb3JlZ3JvdW5kID0gTlVMTDsKKwl0dHktPlRTX3NldF9yZ2JfYmFja2dy\nb3VuZCA9IE5VTEw7CiAJdHR5LT5UTl9tYXhfY29sb3JzID0gODsKIAl0dHkt\nPlROX21heF9wYWlycyA9IDY0OwogCXR0eS0+VE5fbm9fY29sb3JfdmlkZW8g\nPSAwOwogCWJyZWFrOworICAgICAgY2FzZSAxNjc3NzIxNjogLyogUkdCIGNv\nbG9ycyAqLworICAgICAgICB0dHktPlRTX29yaWdfcGFpciA9ICJcMDMzWzBt\nIjsKKworI2lmZGVmIFRFUk1JTkZPCisgICAgICAgIHR0eS0+VFNfc2V0X3Jn\nYl9mb3JlZ3JvdW5kID0gIlwwMzNbMzg7MjslcDElZDslcDIlZDslcDMlZG0i\nOworICAgICAgICB0dHktPlRTX3NldF9yZ2JfYmFja2dyb3VuZCA9ICJcMDMz\nWzQ4OzI7JXAxJWQ7JXAyJWQ7JXAzJWRtIjsKKyNlbHNlCisgICAgICAgIHR0\neS0+VFNfc2V0X3JnYl9mb3JlZ3JvdW5kID0gIlwwMzNbMzg7MjslZDslZDsl\nZG0iOworICAgICAgICB0dHktPlRTX3NldF9yZ2JfYmFja2dyb3VuZCA9ICJc\nMDMzWzQ4OzI7JWQ7JWQ7JWRtIjsKKyNlbmRpZgorCisgICAgICAgIHR0eS0+\nVE5fbWF4X2NvbG9ycyA9IDE2Nzc3MjE2OworICAgICAgICB0dHktPlROX25v\nX2NvbG9yX3ZpZGVvID0gMDsKKyAgICAgICAgYnJlYWs7CiAgICAgfQogfQog\nCkBAIC00MjAxLDYgKzQyNDgsOSBAQCB1c2UgdGhlIEJvdXJuZSBzaGVsbCBj\nb21tYW5kIGBURVJNPS4uLiBleHBvcnQgVEVSTScgKEMtc2hlbGw6XG5cCiAg\nICAgICB0dHktPlROX25vX2NvbG9yX3ZpZGVvID0gdGdldG51bSAoIk5DIik7\nCiAgICAgICBpZiAodHR5LT5UTl9ub19jb2xvcl92aWRlbyA9PSAtMSkKICAg\nICAgICAgdHR5LT5UTl9ub19jb2xvcl92aWRlbyA9IDA7CisKKyAgICAgIHR0\neS0+VFNfc2V0X3JnYl9mb3JlZ3JvdW5kID0gTlVMTDsKKyAgICAgIHR0eS0+\nVFNfc2V0X3JnYl9iYWNrZ3JvdW5kID0gTlVMTDsKICAgICB9CiAKICAgdHR5\nX2RlZmF1bHRfY29sb3JfY2FwYWJpbGl0aWVzICh0dHksIDEpOwpkaWZmIC0t\nZ2l0IGEvc3JjL3Rlcm1jaGFyLmggYi9zcmMvdGVybWNoYXIuaAppbmRleCBk\nODA2NmQ3Li5lNDhkNTgzIDEwMDY0NAotLS0gYS9zcmMvdGVybWNoYXIuaAor\nKysgYi9zcmMvdGVybWNoYXIuaApAQCAtMTU3LDYgKzE1NywxMCBAQCBzdHJ1\nY3QgdHR5X2Rpc3BsYXlfaW5mbwogICBjb25zdCBjaGFyICpUU19zZXRfZm9y\nZWdyb3VuZDsKICAgY29uc3QgY2hhciAqVFNfc2V0X2JhY2tncm91bmQ7CiAK\nKyAgLyogU3VwcG9ydCBmb3IgMjRiaXQgUkdCIGNvbG9yIHRlcm1pbmFscy4g\nKi8KKyAgY29uc3QgY2hhciAqVFNfc2V0X3JnYl9mb3JlZ3JvdW5kOworICBj\nb25zdCBjaGFyICpUU19zZXRfcmdiX2JhY2tncm91bmQ7CisKICAgaW50IFRG\nX2hhemVsdGluZTsgICAgICAgICAgICAgLyogdGVybWNhcCBoeiBmbGFnLiAq\nLwogICBpbnQgVEZfaW5zbW9kZV9tb3Rpb247ICAgICAgICAvKiB0ZXJtY2Fw\nIG1pIGZsYWc6IGNhbiBtb3ZlIHdoaWxlIGluIGluc2VydCBtb2RlLiAqLwog\nICBpbnQgVEZfc3RhbmRvdXRfbW90aW9uOyAgICAgICAvKiB0ZXJtY2FwIG1p\nIGZsYWc6IGNhbiBtb3ZlIHdoaWxlIGluIHN0YW5kb3V0IG1vZGUuICovCmRp\nZmYgLS1naXQgYS9zcmMveGZhY2VzLmMgYi9zcmMveGZhY2VzLmMKaW5kZXgg\nMjljOTFmNy4uMzQ3ZWJmOCAxMDA2NDQKLS0tIGEvc3JjL3hmYWNlcy5jCisr\nKyBiL3NyYy94ZmFjZXMuYwpAQCAtMzgyLDcgKzM4Miw3IEBAIHN0YXRpYyBw\ndHJkaWZmX3QgbGZhY2VfaWRfdG9fbmFtZV9zaXplOwogCiAvKiBUVFkgY29s\nb3ItcmVsYXRlZCBmdW5jdGlvbnMgKGRlZmluZWQgaW4gdHR5LWNvbG9ycy5l\nbCkuICAqLwogCi1zdGF0aWMgTGlzcF9PYmplY3QgUXR0eV9jb2xvcl9kZXNj\nLCBRdHR5X2NvbG9yX2J5X2luZGV4LCBRdHR5X2NvbG9yX3N0YW5kYXJkX3Zh\nbHVlczsKK3N0YXRpYyBMaXNwX09iamVjdCBRdHR5X2NvbG9yX2Rlc2MsIFF0\ndHlfY29sb3JfYnlfaW5kZXgsIFF0dHlfY29sb3Jfc3RhbmRhcmRfdmFsdWVz\nLCBRdHR5X2NvbG9yX2Nhbm9uaWNhbGl6ZTsKIAogLyogVGhlIG5hbWUgb2Yg\ndGhlIGZ1bmN0aW9uIHVzZWQgdG8gY29tcHV0ZSBjb2xvcnMgb24gVFRZcy4g\nICovCiAKQEAgLTk0Myw1NCArOTQzLDgwIEBAIHR0eV9sb29rdXBfY29sb3Ig\nKHN0cnVjdCBmcmFtZSAqZiwgTGlzcF9PYmplY3QgY29sb3IsIFhDb2xvciAq\ndHR5X2NvbG9yLAogICBpZiAoIVNUUklOR1AgKGNvbG9yKSB8fCBOSUxQIChG\nZmJvdW5kcCAoUXR0eV9jb2xvcl9kZXNjKSkpCiAgICAgcmV0dXJuIDA7CiAK\nLSAgWFNFVEZSQU1FIChmcmFtZSwgZik7Ci0KLSAgY29sb3JfZGVzYyA9IGNh\nbGwyIChRdHR5X2NvbG9yX2Rlc2MsIGNvbG9yLCBmcmFtZSk7Ci0gIGlmIChD\nT05TUCAoY29sb3JfZGVzYykgJiYgQ09OU1AgKFhDRFIgKGNvbG9yX2Rlc2Mp\nKSkKKyAgaWYgKGYtPm91dHB1dF9tZXRob2QgPT0gb3V0cHV0X3Rlcm1jYXAK\nKyAgICAgICYmIGYtPm91dHB1dF9kYXRhLnR0eS0+ZGlzcGxheV9pbmZvLT5U\nU19zZXRfcmdiX2ZvcmVncm91bmQKKyAgICAgICYmICFOSUxQIChGZmJvdW5k\ncCAoUXR0eV9jb2xvcl9zdGFuZGFyZF92YWx1ZXMpKSkKICAgICB7Ci0gICAg\nICBMaXNwX09iamVjdCByZ2I7Ci0KLSAgICAgIGlmICghIElOVEVHRVJQIChY\nQ0FSIChYQ0RSIChjb2xvcl9kZXNjKSkpKQotCXJldHVybiAwOworICAgICAg\nLyogVGVybWluYWwgc3VwcG9ydHMgMyBieXRlIFJHQiBjb2xvcnMuICovCisg\nICAgICBpZiAoIU5JTFAgKEZmYm91bmRwIChRdHR5X2NvbG9yX2Nhbm9uaWNh\nbGl6ZSkpKQorICAgICAgICBjb2xvciA9IGNhbGwxKFF0dHlfY29sb3JfY2Fu\nb25pY2FsaXplLCBjb2xvcik7CiAKLSAgICAgIHR0eV9jb2xvci0+cGl4ZWwg\nPSBYSU5UIChYQ0FSIChYQ0RSIChjb2xvcl9kZXNjKSkpOworICAgICAgY29s\nb3JfZGVzYyA9IGNhbGwxIChRdHR5X2NvbG9yX3N0YW5kYXJkX3ZhbHVlcywg\nY29sb3IpOworICAgICAgaWYgKCEgcGFyc2VfcmdiX2xpc3QgKGNvbG9yX2Rl\nc2MsIHR0eV9jb2xvcikpCisgICAgICAgIHJldHVybiAwOwogCi0gICAgICBy\nZ2IgPSBYQ0RSIChYQ0RSIChjb2xvcl9kZXNjKSk7Ci0gICAgICBpZiAoISBw\nYXJzZV9yZ2JfbGlzdCAocmdiLCB0dHlfY29sb3IpKQotCXJldHVybiAwOwor\nICAgICAgLyogTWFwIFhDb2xvciB0byAzIGJ5dGUgdmFsdWVzLiAqLworICAg\nICAgdHR5X2NvbG9yLT5waXhlbCA9IDEgPDwgMjQgLyogU2V0IGJpdCAyNCB0\nbyBtYXJrIFJHQiB2YWx1ZXMuICovCisgICAgICAgIHwgKHR0eV9jb2xvci0+\ncmVkIC8gMjU2KSA8PCAxNgorICAgICAgICB8ICh0dHlfY29sb3ItPmdyZWVu\nIC8gMjU2KSA8PCA4CisgICAgICAgIHwgKHR0eV9jb2xvci0+Ymx1ZSAvIDI1\nNik7CiAKLSAgICAgIC8qIFNob3VsZCB3ZSBmaWxsIGluIFNURF9DT0xPUiB0\nb28/ICAqLwogICAgICAgaWYgKHN0ZF9jb2xvcikKLQl7Ci0JICAvKiBEZWZh\ndWx0IFNURF9DT0xPUiB0byB0aGUgc2FtZSBhcyBUVFlfQ09MT1IuICAqLwot\nCSAgKnN0ZF9jb2xvciA9ICp0dHlfY29sb3I7Ci0KLQkgIC8qIERvIGEgcXVp\nY2sgY2hlY2sgdG8gc2VlIGlmIHRoZSByZXR1cm5lZCBkZXNjcmlwdG9yIGlz\nCi0JICAgICBhY3R1YWxseSBfZXhhY3RseV8gZXF1YWwgdG8gQ09MT1IsIG90\naGVyd2lzZSB3ZSBoYXZlIHRvCi0JICAgICBsb29rdXAgU1REX0NPTE9SIHNl\ncGFyYXRlbHkuICBJZiBpdCdzIGltcG9zc2libGUgdG8gbG9va3VwCi0JICAg\nICBhIHN0YW5kYXJkIGNvbG9yLCB3ZSBqdXN0IGdpdmUgdXAgYW5kIHVzZSBU\nVFlfQ09MT1IuICAqLwotCSAgaWYgKCghU1RSSU5HUCAoWENBUiAoY29sb3Jf\nZGVzYykpCi0JICAgICAgIHx8IE5JTFAgKEZzdHJpbmdfZXF1YWwgKGNvbG9y\nLCBYQ0FSIChjb2xvcl9kZXNjKSkpKQotCSAgICAgICYmICFOSUxQIChGZmJv\ndW5kcCAoUXR0eV9jb2xvcl9zdGFuZGFyZF92YWx1ZXMpKSkKLQkgICAgewot\nCSAgICAgIC8qIExvb2sgdXAgU1REX0NPTE9SIHNlcGFyYXRlbHkuICAqLwot\nCSAgICAgIHJnYiA9IGNhbGwxIChRdHR5X2NvbG9yX3N0YW5kYXJkX3ZhbHVl\ncywgY29sb3IpOwotCSAgICAgIGlmICghIHBhcnNlX3JnYl9saXN0IChyZ2Is\nIHN0ZF9jb2xvcikpCi0JCXJldHVybiAwOwotCSAgICB9Ci0JfQorICAgICAg\nICAqc3RkX2NvbG9yID0gKnR0eV9jb2xvcjsKIAogICAgICAgcmV0dXJuIDE7\nCiAgICAgfQotICBlbHNlIGlmIChOSUxQIChGc3ltYm9sX3ZhbHVlIChpbnRl\ncm4gKCJ0dHktZGVmaW5lZC1jb2xvci1hbGlzdCIpKSkpCi0gICAgLyogV2Ug\nd2VyZSBjYWxsZWQgZWFybHkgZHVyaW5nIHN0YXJ0dXAsIGFuZCB0aGUgY29s\nb3JzIGFyZSBub3QKLSAgICAgICB5ZXQgc2V0IHVwIGluIHR0eS1kZWZpbmVk\nLWNvbG9yLWFsaXN0LiAgRG9uJ3QgcmV0dXJuIGEgZmFpbHVyZQotICAgICAg\nIGluZGljYXRpb24sIHNpbmNlIHRoaXMgcHJvZHVjZXMgdGhlIGFubm95aW5n\nICJVbmFibGUgdG8KLSAgICAgICBsb2FkIGNvbG9yIiBtZXNzYWdlcyBpbiB0\naGUgKk1lc3NhZ2VzKiBidWZmZXIuICAqLwotICAgIHJldHVybiAxOwogICBl\nbHNlCi0gICAgLyogdHR5LWNvbG9yLWRlc2Mgc2VlbXMgdG8gaGF2ZSByZXR1\ncm5lZCBhIGJhZCB2YWx1ZS4gICovCi0gICAgcmV0dXJuIDA7CisgICAgewor\nICAgICAgWFNFVEZSQU1FIChmcmFtZSwgZik7CisKKyAgICAgIGNvbG9yX2Rl\nc2MgPSBjYWxsMiAoUXR0eV9jb2xvcl9kZXNjLCBjb2xvciwgZnJhbWUpOwor\nICAgICAgaWYgKENPTlNQIChjb2xvcl9kZXNjKSAmJiBDT05TUCAoWENEUiAo\nY29sb3JfZGVzYykpKQorICAgICAgICB7CisgICAgICAgICAgTGlzcF9PYmpl\nY3QgcmdiOworCisgICAgICAgICAgaWYgKCEgSU5URUdFUlAgKFhDQVIgKFhD\nRFIgKGNvbG9yX2Rlc2MpKSkpCisgICAgICAgICAgICByZXR1cm4gMDsKKwor\nICAgICAgICAgIHR0eV9jb2xvci0+cGl4ZWwgPSBYSU5UIChYQ0FSIChYQ0RS\nIChjb2xvcl9kZXNjKSkpOworCisgICAgICAgICAgcmdiID0gWENEUiAoWENE\nUiAoY29sb3JfZGVzYykpOworICAgICAgICAgIGlmICghIHBhcnNlX3JnYl9s\naXN0IChyZ2IsIHR0eV9jb2xvcikpCisgICAgICAgICAgICByZXR1cm4gMDsK\nKworICAgICAgICAgIC8qIFNob3VsZCB3ZSBmaWxsIGluIFNURF9DT0xPUiB0\nb28/ICAqLworICAgICAgICAgIGlmIChzdGRfY29sb3IpCisgICAgICAgICAg\nICB7CisgICAgICAgICAgICAgIC8qIERlZmF1bHQgU1REX0NPTE9SIHRvIHRo\nZSBzYW1lIGFzIFRUWV9DT0xPUi4gICovCisgICAgICAgICAgICAgICpzdGRf\nY29sb3IgPSAqdHR5X2NvbG9yOworCisgICAgICAgICAgICAgIC8qIERvIGEg\ncXVpY2sgY2hlY2sgdG8gc2VlIGlmIHRoZSByZXR1cm5lZCBkZXNjcmlwdG9y\nIGlzCisgICAgICAgICAgICAgICAgIGFjdHVhbGx5IF9leGFjdGx5XyBlcXVh\nbCB0byBDT0xPUiwgb3RoZXJ3aXNlIHdlIGhhdmUgdG8KKyAgICAgICAgICAg\nICAgICAgbG9va3VwIFNURF9DT0xPUiBzZXBhcmF0ZWx5LiAgSWYgaXQncyBp\nbXBvc3NpYmxlIHRvIGxvb2t1cAorICAgICAgICAgICAgICAgICBhIHN0YW5k\nYXJkIGNvbG9yLCB3ZSBqdXN0IGdpdmUgdXAgYW5kIHVzZSBUVFlfQ09MT1Iu\nICAqLworICAgICAgICAgICAgICBpZiAoKCFTVFJJTkdQIChYQ0FSIChjb2xv\ncl9kZXNjKSkKKyAgICAgICAgICAgICAgICAgICB8fCBOSUxQIChGc3RyaW5n\nX2VxdWFsIChjb2xvciwgWENBUiAoY29sb3JfZGVzYykpKSkKKyAgICAgICAg\nICAgICAgICAgICYmICFOSUxQIChGZmJvdW5kcCAoUXR0eV9jb2xvcl9zdGFu\nZGFyZF92YWx1ZXMpKSkKKyAgICAgICAgICAgICAgICB7CisgICAgICAgICAg\nICAgICAgICAvKiBMb29rIHVwIFNURF9DT0xPUiBzZXBhcmF0ZWx5LiAgKi8K\nKyAgICAgICAgICAgICAgICAgIHJnYiA9IGNhbGwxIChRdHR5X2NvbG9yX3N0\nYW5kYXJkX3ZhbHVlcywgY29sb3IpOworICAgICAgICAgICAgICAgICAgaWYg\nKCEgcGFyc2VfcmdiX2xpc3QgKHJnYiwgc3RkX2NvbG9yKSkKKyAgICAgICAg\nICAgICAgICAgICAgcmV0dXJuIDA7CisgICAgICAgICAgICAgICAgfQorICAg\nICAgICAgICAgfQorCisgICAgICAgICAgcmV0dXJuIDE7CisgICAgICAgIH0K\nKyAgICAgIGVsc2UgaWYgKE5JTFAgKEZzeW1ib2xfdmFsdWUgKGludGVybiAo\nInR0eS1kZWZpbmVkLWNvbG9yLWFsaXN0IikpKSkKKyAgICAgICAgLyogV2Ug\nd2VyZSBjYWxsZWQgZWFybHkgZHVyaW5nIHN0YXJ0dXAsIGFuZCB0aGUgY29s\nb3JzIGFyZSBub3QKKyAgICAgICAgICAgeWV0IHNldCB1cCBpbiB0dHktZGVm\naW5lZC1jb2xvci1hbGlzdC4gIERvbid0IHJldHVybiBhIGZhaWx1cmUKKyAg\nICAgICAgICAgaW5kaWNhdGlvbiwgc2luY2UgdGhpcyBwcm9kdWNlcyB0aGUg\nYW5ub3lpbmcgIlVuYWJsZSB0bworICAgICAgICAgICBsb2FkIGNvbG9yIiBt\nZXNzYWdlcyBpbiB0aGUgKk1lc3NhZ2VzKiBidWZmZXIuICAqLworICAgICAg\nICByZXR1cm4gMTsKKyAgICAgIGVsc2UKKyAgICAgICAgLyogdHR5LWNvbG9y\nLWRlc2Mgc2VlbXMgdG8gaGF2ZSByZXR1cm5lZCBhIGJhZCB2YWx1ZS4gICov\nCisgICAgICAgIHJldHVybiAwOworICAgIH0KIH0KIAogLyogQSB2ZXJzaW9u\nIG9mIGRlZmluZWRfY29sb3IgZm9yIG5vbi1YIGZyYW1lcy4gICovCkBAIC0x\nMDA4LDcgKzEwMzQsOSBAQCB0dHlfZGVmaW5lZF9jb2xvciAoc3RydWN0IGZy\nYW1lICpmLCBjb25zdCBjaGFyICpjb2xvcl9uYW1lLAogICBjb2xvcl9kZWYt\nPmdyZWVuID0gMDsKIAogICBpZiAoKmNvbG9yX25hbWUpCi0gICAgc3RhdHVz\nID0gdHR5X2xvb2t1cF9jb2xvciAoZiwgYnVpbGRfc3RyaW5nIChjb2xvcl9u\nYW1lKSwgY29sb3JfZGVmLCBOVUxMKTsKKyAgICB7CisgICAgICBzdGF0dXMg\nPSB0dHlfbG9va3VwX2NvbG9yIChmLCBidWlsZF9zdHJpbmcgKGNvbG9yX25h\nbWUpLCBjb2xvcl9kZWYsIE5VTEwpOworICAgIH0KIAogICBpZiAoY29sb3Jf\nZGVmLT5waXhlbCA9PSBGQUNFX1RUWV9ERUZBVUxUX0NPTE9SICYmICpjb2xv\ncl9uYW1lKQogICAgIHsKQEAgLTU3ODAsNiArNTgwOCw3IEBAIG1hcF90dHlf\nY29sb3IgKHN0cnVjdCBmcmFtZSAqZiwgc3RydWN0IGZhY2UgKmZhY2UsCiAg\nIHVuc2lnbmVkIGxvbmcgZGVmYXVsdF9waXhlbCA9CiAgICAgZm9yZWdyb3Vu\nZF9wID8gRkFDRV9UVFlfREVGQVVMVF9GR19DT0xPUiA6IEZBQ0VfVFRZX0RF\nRkFVTFRfQkdfQ09MT1I7CiAgIHVuc2lnbmVkIGxvbmcgcGl4ZWwgPSBkZWZh\ndWx0X3BpeGVsOworICBYQ29sb3IgdHJ1ZV9jb2xvcjsKICNpZmRlZiBNU0RP\nUwogICB1bnNpZ25lZCBsb25nIGRlZmF1bHRfb3RoZXJfcGl4ZWwgPQogICAg\nIGZvcmVncm91bmRfcCA/IEZBQ0VfVFRZX0RFRkFVTFRfQkdfQ09MT1IgOiBG\nQUNFX1RUWV9ERUZBVUxUX0ZHX0NPTE9SOwpAQCAtNTc5OCw3ICs1ODI3LDE4\nIEBAIG1hcF90dHlfY29sb3IgKHN0cnVjdCBmcmFtZSAqZiwgc3RydWN0IGZh\nY2UgKmZhY2UsCiAgICAgewogICAgICAgLyogQXNzb2NpYXRpb25zIGluIHR0\neS1kZWZpbmVkLWNvbG9yLWFsaXN0IGFyZSBvZiB0aGUgZm9ybQogCSAoTkFN\nRSBJTkRFWCBSIEcgQikuICBXZSBuZWVkIHRoZSBJTkRFWCBwYXJ0LiAgKi8K\nLSAgICAgIHBpeGVsID0gWElOVCAoWENBUiAoWENEUiAoZGVmKSkpOworICAg\nICAgaWYgKGYtPm91dHB1dF9tZXRob2QgPT0gb3V0cHV0X3Rlcm1jYXAKKyAg\nICAgICAgICAmJiBmLT5vdXRwdXRfZGF0YS50dHktPmRpc3BsYXlfaW5mby0+\nVFNfc2V0X3JnYl9mb3JlZ3JvdW5kCisgICAgICAgICAgJiYgcGFyc2Vfcmdi\nX2xpc3QgKFhDRFIgKFhDRFIoZGVmKSksICZ0cnVlX2NvbG9yKSkKKyAgICAg\nICAgeworICAgICAgICAgIC8qIE1hcCBYQ29sb3IgdG8gMyBieXRlIHZhbHVl\ncy4gKi8KKyAgICAgICAgICBwaXhlbCA9IDEgPDwgMjQgLyogU2V0IGJpdCAy\nNCB0byBtYXJrIFJHQiB2YWx1ZXMuICovCisgICAgICAgICAgICB8ICh0cnVl\nX2NvbG9yLnJlZCAvIDI1NikgPDwgMTYKKyAgICAgICAgICAgIHwgKHRydWVf\nY29sb3IuZ3JlZW4gLyAyNTYpIDw8IDgKKyAgICAgICAgICAgIHwgKHRydWVf\nY29sb3IuYmx1ZSAvIDI1Nik7CisgICAgICAgIH0KKyAgICAgICBlbHNlCisg\nICAgICAgICBwaXhlbCA9IFhJTlQgKFhDQVIgKFhDRFIgKGRlZikpKTsKICAg\nICB9CiAKICAgaWYgKHBpeGVsID09IGRlZmF1bHRfcGl4ZWwgJiYgU1RSSU5H\nUCAoY29sb3IpKQpAQCAtNjQ2MCw2ICs2NTAwLDcgQEAgc3ltc19vZl94ZmFj\nZXMgKHZvaWQpCiAgIERFRlNZTSAoUXdpbmRvd19kaXZpZGVyLCAid2luZG93\nLWRpdmlkZXIiKTsKICAgREVGU1lNIChRd2luZG93X2RpdmlkZXJfZmlyc3Rf\ncGl4ZWwsICJ3aW5kb3ctZGl2aWRlci1maXJzdC1waXhlbCIpOwogICBERUZT\nWU0gKFF3aW5kb3dfZGl2aWRlcl9sYXN0X3BpeGVsLCAid2luZG93LWRpdmlk\nZXItbGFzdC1waXhlbCIpOworICBERUZTWU0gKFF0dHlfY29sb3JfY2Fub25p\nY2FsaXplLCAidHR5LWNvbG9yLWNhbm9uaWNhbGl6ZSIpOwogICBERUZTWU0g\nKFF0dHlfY29sb3JfZGVzYywgInR0eS1jb2xvci1kZXNjIik7CiAgIERFRlNZ\nTSAoUXR0eV9jb2xvcl9zdGFuZGFyZF92YWx1ZXMsICJ0dHktY29sb3Itc3Rh\nbmRhcmQtdmFsdWVzIik7CiAgIERFRlNZTSAoUXR0eV9jb2xvcl9ieV9pbmRl\neCwgInR0eS1jb2xvci1ieS1pbmRleCIpOwo=\n\n--_----------=_14576609345331850--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_25.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aeIOq-0004XI-2r\n\tfor mharc-dev@caliopen.local; Fri, 11 Mar 2016 03:26:20 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:48412)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aeIOm-0004WU-To\n\tfor dev@caliopen.local; Fri, 11 Mar 2016 03:26:17 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aeIOj-0005NV-JI\n\tfor dev@caliopen.local; Fri, 11 Mar 2016 03:26:16 -0500\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:56819)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aeIOj-0005NH-Fe; Fri, 11 Mar 2016 03:26:13 -0500\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3176\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aeIOi-0000jN-H7; Fri, 11 Mar 2016 03:26:12 -0500\nDate: Fri, 11 Mar 2016 10:26:07 +0200\nMessage-Id: <83y49pju8w.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Charles Strahan <charles@cstrahan.com>\nIn-reply-to: <1457660934.533185.545912986.04028913@webmail.messagingengine.com>\n\t(message from Charles Strahan on Thu, 10 Mar 2016 20:48:54 -0500)\nSubject: Re: support for true-color terminals\nReferences: <1457660934.533185.545912986.04028913@webmail.messagingengine.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=iso-8859-1\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 11 Mar 2016 08:26:18 -0000\nContent-Length: 1514\nLines: 36\n\n> From: Charles Strahan <charles@cstrahan.com>\n> Date: Thu, 10 Mar 2016 20:48:54 -0500\n> \n> A while back, there was an effort to add support for true-color\n> terminals (by which I mean terminals supporting 16777216 simultaneous\n> colors):\n> \n> http://emacs.1067599.n5.nabble.com/RFC-Add-tty-True-Color-support-td299962.html\n> \n> I've contacted R�diger, asking if he wouldn't mind if I pick up from\n> where he left off, and he said that was fine as he's been too busy to\n> work on this.\n> \n> I've attached to this email the patch that I'm using, based on his work.\n> Any feedback would be greatly appreciated.\n> \n> This is my first (attempted) contribution to Emacs, and I'm very new to\n> the code base, so there might be some silly mistakes that I'm unaware\n> of. If we can get this polished enough for inclusion, I'd be more than\n> happy to assign copyright.\n\nThanks.\n\nFirst, I suggest that you start the legal paperwork rolling right now,\nbecause without that, discussing such a non-trivial change would be\nrather pointless.  If you agree, I will send you the form off-list.\n\nAs for the patch: my first comment is about the changes in\ntty_look_up_color and in map_tty_color -- why are these needed?  The\nsheer number of the supported colors doesn't necessarily require any\nchanges in the basic design of TTY color support, so I'm not sure what\nwas the motivation for these changes.\n\nAlso, why do we need the 'true-color' option introduced in\ntty-colors.el? why isn't 'auto' enough for supporting this terminal?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_27.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1agpPT-00067Y-Js\n\tfor mharc-dev@caliopen.local; Fri, 18 Mar 2016 04:05:27 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56677)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <charles@cstrahan.com>) id 1agpPR-00067Q-LM\n\tfor dev@caliopen.local; Fri, 18 Mar 2016 04:05:26 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <charles@cstrahan.com>) id 1agpPO-0002OM-TE\n\tfor dev@caliopen.local; Fri, 18 Mar 2016 04:05:25 -0400\nReceived: from out2-smtp.messagingengine.com ([66.111.4.26]:43437)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <charles@cstrahan.com>) id 1agpPO-0002Nq-QC\n\tfor dev@caliopen.local; Fri, 18 Mar 2016 04:05:22 -0400\nReceived: from compute4.internal (compute4.nyi.internal [10.202.2.44])\n\tby mailout.nyi.internal (Postfix) with ESMTP id 234D420BCC\n\tfor <dev@caliopen.local>; Fri, 18 Mar 2016 04:05:20 -0400 (EDT)\nReceived: from web5 ([10.202.2.215])\n\tby compute4.internal (MEProxy); Fri, 18 Mar 2016 04:05:20 -0400\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=cstrahan.com; h=cc\n\t:content-transfer-encoding:content-type:date:from:in-reply-to\n\t:message-id:mime-version:references:subject:to:x-sasl-enc\n\t:x-sasl-enc; s=mesmtp; bh=Yk97j42G2nJvKiT/mVMLQtLfgeE=; b=d67DOJ\n\tcFq+xXxnOm4ZarhtfvUBDKmK41/tOqtMGlTjfTNNiBXZBY2+VJS+kpev1i6r+4mE\n\tnC0LceYEwZc1OnNOdVa5Bn074lC3KHIzwBazSYr2PBuJvCizlw7u2t14tksOm/01\n\tALPJDReoKUc6tS7XhW27P4fYIba2pTu9VoL5w=\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=\n\tmessagingengine.com; h=cc:content-transfer-encoding:content-type\n\t:date:from:in-reply-to:message-id:mime-version:references\n\t:subject:to:x-sasl-enc:x-sasl-enc; s=smtpout; bh=Yk97j42G2nJvKiT\n\t/mVMLQtLfgeE=; b=SjYIJis78bMyE58Q1W+aygSAur8MhJDpOSZp0N3Eyx/XT0H\n\tyOUbiyAup/uLjozVG0vUCxkce4fCxaGOyCDFp1yZLcdkcShrIzmYkN9OQ6MZYm33\n\tJ1WjbBDDWA+VdlDLizZzAmL3J1rALbXSWH2TFcaNRUA+LGQt7t6/t09v09mw=\nReceived: by web5.nyi.internal (Postfix, from userid 99)\n\tid E9E69A7DED5; Fri, 18 Mar 2016 04:05:19 -0400 (EDT)\nMessage-Id: <1458288319.29675.552786858.0C058E99@webmail.messagingengine.com>\nX-Sasl-Enc: ONr2YmZJLDHkDmJHFGOBZfLjqglkBZvF6gbs2AmZ0eKc 1458288319\nFrom: Charles Strahan <charles@cstrahan.com>\nTo: Eli Zaretskii <eliz@gnu.org>\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/plain; charset=\"iso-8859-1\"\nX-Mailer: MessagingEngine.com Webmail Interface - ajax-872772a7\nSubject: Re: support for true-color terminals\nDate: Fri, 18 Mar 2016 04:05:19 -0400\nIn-Reply-To: <83y49pju8w.fsf@gnu.org>\nReferences: <1457660934.533185.545912986.04028913@webmail.messagingengine.com>\n\t<83y49pju8w.fsf@gnu.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 66.111.4.26\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 18 Mar 2016 08:05:26 -0000\nContent-Length: 1832\nLines: 46\n\nOn Fri, Mar 11, 2016, at 04:26 AM, Eli Zaretskii wrote:\n> > From: Charles Strahan <charles@cstrahan.com>\n> > Date: Thu, 10 Mar 2016 20:48:54 -0500\n> >=20\n> > A while back, there was an effort to add support for true-color\n> > terminals (by which I mean terminals supporting 16777216 simultaneous\n> > colors):\n> >=20\n> > http://emacs.1067599.n5.nabble.com/RFC-Add-tty-True-Color-support-td299=\n962.html\n> >=20\n> > I've contacted R=FCdiger, asking if he wouldn't mind if I pick up from\n> > where he left off, and he said that was fine as he's been too busy to\n> > work on this.\n> >=20\n> > I've attached to this email the patch that I'm using, based on his work.\n> > Any feedback would be greatly appreciated.\n> >=20\n> > This is my first (attempted) contribution to Emacs, and I'm very new to\n> > the code base, so there might be some silly mistakes that I'm unaware\n> > of. If we can get this polished enough for inclusion, I'd be more than\n> > happy to assign copyright.\n>=20\n> Thanks.\n>=20\n> First, I suggest that you start the legal paperwork rolling right now,\n> because without that, discussing such a non-trivial change would be\n> rather pointless.  If you agree, I will send you the form off-list.\n\nYes, I agree - thank you. I'll get the paperwork filled out as soon as I\ncan.\n\n> As for the patch: my first comment is about the changes in\n> tty_look_up_color and in map_tty_color -- why are these needed?  The\n> sheer number of the supported colors doesn't necessarily require any\n> changes in the basic design of TTY color support, so I'm not sure what\n> was the motivation for these changes.\n>=20\n> Also, why do we need the 'true-color' option introduced in\n> tty-colors.el? why isn't 'auto' enough for supporting this terminal?\n\nI'll try to answer your questions in the next day or two when my\nschedule frees up.\n\n-Charles\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_29.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apROz-0005tg-71\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 22:16:33 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:53350)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <charles@cstrahan.com>) id 1apROv-0005oC-PE\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 22:16:30 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <charles@cstrahan.com>) id 1apROs-0008PM-IS\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 22:16:29 -0400\nReceived: from out3-smtp.messagingengine.com ([66.111.4.27]:41019)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <charles@cstrahan.com>) id 1apROs-0008PH-Dp\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 22:16:26 -0400\nReceived: from compute4.internal (compute4.nyi.internal [10.202.2.44])\n\tby mailout.nyi.internal (Postfix) with ESMTP id EB45520821\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 22:16:24 -0400 (EDT)\nReceived: from web5 ([10.202.2.215])\n\tby compute4.internal (MEProxy); Sun, 10 Apr 2016 22:16:24 -0400\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=cstrahan.com; h=\n\tcontent-transfer-encoding:content-type:date:from:in-reply-to\n\t:message-id:mime-version:references:subject:to:x-sasl-enc\n\t:x-sasl-enc; s=mesmtp; bh=ohTG1npZwrE7J0a85cS/0dVW/zY=; b=eIXXUq\n\tKC5/ElPWL1f4O0QAarhM9rMFxkj0ooXGw58dbSoaRCXGFRkOI4RptxkYuINMBJvO\n\tgZky1MLC79DZmsBJvco8mAOPd9wlQ0EoNRO6CMl+P239tFJMwAE9S1bWOVp+AYjj\n\tnDzZqAc5Sh4ATZBFka1PKXQvK/fomurJ/sFH8=\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=\n\tmessagingengine.com; h=content-transfer-encoding:content-type\n\t:date:from:in-reply-to:message-id:mime-version:references\n\t:subject:to:x-sasl-enc:x-sasl-enc; s=smtpout; bh=ohTG1npZwrE7J0a\n\t85cS/0dVW/zY=; b=YFdQ+H2EBoBghP/+x11uhiTPcUxn5xeBBiMCTEIpd3zypIy\n\thnYVtL2SrbnLk1ykYWR7Srft6Spp4WsVKe+d9iBPsZBQVNoQbnRzc7/QJEf84kSq\n\tRPMW8t0NHxG4iYGb/NUclABRrhFwASAt4ZIoJilWxfouBRD5ZhHzP97deEEM=\nReceived: by web5.nyi.internal (Postfix, from userid 99)\n\tid BDDD4AC5CEA; Sun, 10 Apr 2016 22:16:24 -0400 (EDT)\nMessage-Id: <1460340984.2930993.574696385.6C600EFA@webmail.messagingengine.com>\nX-Sasl-Enc: 7itGa8xb6ooMcyvCKcBUi+jjn90/d/T4tF5towjXUlon 1460340984\nFrom: Charles Strahan <charles@cstrahan.com>\nTo: dev@caliopen.local\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/plain; charset=\"utf-8\"\nX-Mailer: MessagingEngine.com Webmail Interface - ajax-82e6ff7b\nSubject: Re: support for true-color terminals\nDate: Sun, 10 Apr 2016 22:16:24 -0400\nIn-Reply-To: <1458288319.29675.552786858.0C058E99@webmail.messagingengine.com>\nReferences: <1457660934.533185.545912986.04028913@webmail.messagingengine.com>\n\t<83y49pju8w.fsf@gnu.org>\n\t<1458288319.29675.552786858.0C058E99@webmail.messagingengine.com>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 66.111.4.27\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 11 Apr 2016 02:16:30 -0000\nContent-Length: 2174\nLines: 60\n\nHello,\n\nUnless I've missed it, I don't think I've received an email regarding\ncopyright assignment. Could you point me at the requisite paperwork,\nplease?\n\nThanks!\n-Charles\n\nOn Fri, Mar 18, 2016, at 04:05 AM, Charles Strahan wrote:\n> On Fri, Mar 11, 2016, at 04:26 AM, Eli Zaretskii wrote:\n> > > From: Charles Strahan <charles@cstrahan.com>\n> > > Date: Thu, 10 Mar 2016 20:48:54 -0500\n> > >=20\n> > > A while back, there was an effort to add support for true-color\n> > > terminals (by which I mean terminals supporting 16777216 simultaneous\n> > > colors):\n> > >=20\n> > > http://emacs.1067599.n5.nabble.com/RFC-Add-tty-True-Color-support-td2=\n99962.html\n> > >=20\n> > > I've contacted R=C3=BCdiger, asking if he wouldn't mind if I pick up =\nfrom\n> > > where he left off, and he said that was fine as he's been too busy to\n> > > work on this.\n> > >=20\n> > > I've attached to this email the patch that I'm using, based on his wo=\nrk.\n> > > Any feedback would be greatly appreciated.\n> > >=20\n> > > This is my first (attempted) contribution to Emacs, and I'm very new =\nto\n> > > the code base, so there might be some silly mistakes that I'm unaware\n> > > of. If we can get this polished enough for inclusion, I'd be more than\n> > > happy to assign copyright.\n> >=20\n> > Thanks.\n> >=20\n> > First, I suggest that you start the legal paperwork rolling right now,\n> > because without that, discussing such a non-trivial change would be\n> > rather pointless.  If you agree, I will send you the form off-list.\n>=20\n> Yes, I agree - thank you. I'll get the paperwork filled out as soon as I\n> can.\n>=20\n> > As for the patch: my first comment is about the changes in\n> > tty_look_up_color and in map_tty_color -- why are these needed?  The\n> > sheer number of the supported colors doesn't necessarily require any\n> > changes in the basic design of TTY color support, so I'm not sure what\n> > was the motivation for these changes.\n> >=20\n> > Also, why do we need the 'true-color' option introduced in\n> > tty-colors.el? why isn't 'auto' enough for supporting this terminal?\n>=20\n> I'll try to answer your questions in the next day or two when my\n> schedule frees up.\n>=20\n> -Charles\n>=20\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_3.argentina:2,",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 8DB0A20A77BCE\n\tfor <laurent@brasil.brainstorm.fr>; Mon, 11 Apr 2016 15:15:13 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 7675833181F7; Mon, 11 Apr 2016 15:15:13 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 690C733181FC; Mon, 11 Apr 2016 15:15:13 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.5 required=5.0 tests=AWL,BAYES_99,\n\tHTML_IMAGE_RATIO_02,HTML_MESSAGE,MPART_ALT_DIFF,NORMAL_HTTP_TO_IP,RCVD_IN_CSS,\n\tT_KHOP_FOREIGN_CLICK autolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [63.251.243.220 listed in zen.spamhaus.org]\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.8 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server3.le-bon.in (server2.ventesdu.in [63.251.243.220])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id BEC1033181F7\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 15:15:06 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: =?UTF-8?B?Sm91cm7DqWVzIFBhcnRpY3VsacOocmVzOiBwcm9maXRleiBkZSAtMjAl?=\nMessage-ID: <c2ac315feb460a669519f2712014c166@162.244.67.108>\nDate: Mon, 11 Apr 2016 07:51:50 -0500\nFrom: \"Alain Figaret\" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 165\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11331>\nX-Mailer-SID: 11331\nX-Mailer-Sent-By: 2\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_3f952af1aae52842f2bd41abe732dc08\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?UTF-8?B?Sm91cm7DqWVzIFBhcnRpY3VsacOocmVzOiBwcm9maXRleiBkZSAtMjAl?=\nContent-Length: 6063\nLines: 166\n\n--b1_3f952af1aae52842f2bd41abe732dc08\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11331&L=165&N=3615\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11331\n\n--b1_3f952af1aae52842f2bd41abe732dc08\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n<title>Journ&eacute;es particuli&egrave;res - Alain FIGARET</title>\n<style type=\"text/css\"><!--\n\n--></style>\n<!--Don t remove the next meta-->\n<meta name=\"pdlbat1tkn\" content=\"XCKHfmhQ\" />\n<img\nsrc=\"http://dbkpwi4c6v97q.cloudfront.net/?sc=9872&amp;t=0&amp;l=170094&amp;p=79134\"\nwidth=\"1\" height=\"1\" border=\"0\" />\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11331&L=165&N=3615\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"font-family: Arial, sans-serif; font-size: 12px; box-shadow: #dadada\n1px 1px 10px; width: 600px;\">\n<tbody>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"600\" height=\"74\" style=\"background-color:#FFFFFF;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11331&L=14384&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/chemise/110316journees/alainfigaret110316_01.jpg\"\nalt=\"Journ&eacute;es Particuli&egrave;res &agrave; -20% avec le code\nJPPE16\" width=\"600\" height=\"74\" border=\"0\" /> </a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"173\" height=\"46\" style=\"background-color:#FFFFFF;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11331&L=14384&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/chemise/110316journees/alainfigaret110316_02.jpg\"\nalt=\"Journ&eacute;es Particuli&egrave;res &agrave; -20% avec le code\nJPPE16\" width=\"173\" height=\"46\" border=\"0\" /> </a></td>\n<td width=\"90\" height=\"46\" style=\"background-color:#FFFFFF;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11331&L=14384&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/chemise/110316journees/alainfigaret110316_03.jpg\"\nalt=\"Journ&eacute;es Particuli&egrave;res &agrave; -20% avec le code\nJPPE16\" width=\"90\" height=\"46\" border=\"0\" /> </a></td>\n<td width=\"138\" height=\"46\" style=\"background-color:#FFFFFF;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11331&L=14384&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/chemise/110316journees/alainfigaret110316_04.jpg\"\nalt=\"Journ&eacute;es Particuli&egrave;res &agrave; -20% avec le code\nJPPE16\" width=\"138\" height=\"46\" border=\"0\" /> </a></td>\n<td width=\"199\" height=\"46\" style=\"background-color:#FFFFFF;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11331&L=14384&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/chemise/110316journees/alainfigaret110316_05.jpg\"\nalt=\"Journ&eacute;es Particuli&egrave;res &agrave; -20% avec le code\nJPPE16\" width=\"199\" height=\"46\" border=\"0\" /> </a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"600\" height=\"242\" style=\"background-color:#FFFFFF;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11331&L=14384&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/chemise/110316journees/alainfigaret110316_06.jpg\"\nalt=\"Journ&eacute;es Particuli&egrave;res &agrave; -20% avec le code\nJPPE16\" width=\"600\" height=\"242\" border=\"0\" /> </a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"20\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"332\" style=\"background-color:#FFFFFF;text-align:center;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11331&L=14384&F=H\"\nstyle=\"text-decoration:none; text-align:center;\" target=\"_blank\"> <font\nsize=\"2\" style=\"font-family:Arial,Sans-Serif; font-size:14px;\ncolor:#000000; \"> <span style=\"font-size:25px;\"><strong>JOURN&Eacute;ES\nPARTICULI&Egrave;RES</strong></span><br /> <span\nstyle=\"font-size:60px;\"><strong>-20%*</strong></span><br /> <span\nstyle=\"font-size:20px;\">code <strong>JPPE16</strong></span> <span\nstyle=\"font-size:20px;\">jusqu&rsquo;au 27/03/2016</span><br /> </font>\n</a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"600\" height=\"104\" style=\"background-color:#FFFFFF;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11331&L=14384&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/chemise/110316journees/alainfigaret110316_10.jpg\"\nalt=\"Journ&eacute;es Particuli&egrave;res &agrave; -20% avec le code\nJPPE16\" width=\"600\" height=\"104\" border=\"0\" /> </a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11331\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n</div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=11664742&L=165&N=11331&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_3f952af1aae52842f2bd41abe732dc08--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_31.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apRk1-0005zO-Dt\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 22:38:17 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56291)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1apRjz-0005wc-67\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 22:38:15 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1apRjw-0003mY-0K\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 22:38:15 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:51672)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1apRjv-0003mU-S8; Sun, 10 Apr 2016 22:38:11 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4455\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1apRjv-0002fB-73; Sun, 10 Apr 2016 22:38:11 -0400\nDate: Mon, 11 Apr 2016 05:37:38 +0300\nMessage-Id: <8360vohnv1.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Charles Strahan <charles@cstrahan.com>\nCC: dev@caliopen.local\nIn-reply-to: <1460340984.2930993.574696385.6C600EFA@webmail.messagingengine.com>\n\t(message from Charles Strahan on Sun, 10 Apr 2016 22:16:24 -0400)\nSubject: Re: support for true-color terminals\nReply-to: Eli Zaretskii <eliz@gnu.org>\nReferences: <1457660934.533185.545912986.04028913@webmail.messagingengine.com>\n\t<83y49pju8w.fsf@gnu.org>\n\t<1458288319.29675.552786858.0C058E99@webmail.messagingengine.com>\n\t<1460340984.2930993.574696385.6C600EFA@webmail.messagingengine.com>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 11 Apr 2016 02:38:16 -0000\nContent-Length: 277\nLines: 11\n\n> From: Charles Strahan <charles@cstrahan.com>\n> Date: Sun, 10 Apr 2016 22:16:24 -0400\n> \n> Hello,\n> \n> Unless I've missed it, I don't think I've received an email regarding\n> copyright assignment. Could you point me at the requisite paperwork,\n> please?\n\nForm sent off-list.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_33.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apPbw-0000SZ-RW\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 20:21:48 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39661)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1apPbv-0000QR-0M\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 20:21:47 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1apPbr-0005d3-03\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 20:21:46 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:3589)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1apPbq-0005cZ-Sh\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 20:21:42 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0A9FgA731xV/9KZ+M5cgxCEAoVVwwsEAgKBPD0QAQEBAQEBAYEKQQWDXQEBBFYjEAs0EhQYDYhjzyMBAQEBBgEBAQEeizqFBQcWhBcFsz+BRSNhgQWCLiKCeAEBAQ\nX-IPAS-Result: A0A9FgA731xV/9KZ+M5cgxCEAoVVwwsEAgKBPD0QAQEBAQEBAYEKQQWDXQEBBFYjEAs0EhQYDYhjzyMBAQEBBgEBAQEeizqFBQcWhBcFsz+BRSNhgQWCLiKCeAEBAQ\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"205809406\"\nReceived: from 206-248-153-210.dsl.teksavvy.com (HELO pastel.home)\n\t([206.248.153.210])\n\tby ironport2-out.teksavvy.com with ESMTP; 10 Apr 2016 20:21:40 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid 368DC640DE; Sun, 10 Apr 2016 20:21:40 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: dev@caliopen.local\nCc: Etienne Prud'homme <e.e.f.prudhomme@gmail.com>\nSubject: Re: [Emacs-diffs] master d6ea645: Fix alignement rule for CSS\nMessage-ID: <jwv1t6dkncp.fsf-monnier+emacsdiffs@gnu.org>\nReferences: <20160410213304.19687.66901@vcs.savannah.gnu.org>\n\t<E1apMye-00058G-Rb@vcs.savannah.gnu.org>\nDate: Sun, 10 Apr 2016 20:21:40 -0400\nIn-Reply-To: <E1apMye-00058G-Rb@vcs.savannah.gnu.org> (Simen Heggest's message\n\tof \"Sun, 10 Apr 2016 21:33:04 +0000\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 11 Apr 2016 00:21:47 -0000\nContent-Length: 593\nLines: 19\n\n> --- a/lisp/align.el\n> +++ b/lisp/align.el\n> @@ -577,7 +577,7 @@ The possible settings for `align-region-separate' are:\n>  \t\t     (eq '- current-prefix-arg)))))\n \n>      (css-declaration\n> -     (regexp . \"^\\\\s-*\\\\w+:\\\\(\\\\s-*\\\\).*;\")\n> +     (regexp . \"^\\\\s-*\\\\(?:\\\\w-?\\\\)+:\\\\(\\\\s-*\\\\).*;\")\n>       (group . (1))\n>       (modes . '(css-mode html-mode))))\n>    \"A list describing all of the available alignment rules.\n\nBTW, I think, as is the case for font-lock, indent, imenu, and friends,\nthe align settings should belong in the major mode's definition rather\nin align.el.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_35.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aNWYx-0007Xj-83\n\tfor mharc-dev@caliopen.local; Sun, 24 Jan 2016 21:07:27 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:58814)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mituharu@math.s.chiba-u.ac.jp>) id 1aNWYu-0007WG-9L\n\tfor dev@caliopen.local; Sun, 24 Jan 2016 21:07:25 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <mituharu@math.s.chiba-u.ac.jp>) id 1aNWYr-00034b-2k\n\tfor dev@caliopen.local; Sun, 24 Jan 2016 21:07:24 -0500\nReceived: from mathmail.math.s.chiba-u.ac.jp ([133.82.132.2]:62186)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mituharu@math.s.chiba-u.ac.jp>) id 1aNWYq-00032b-Js\n\tfor dev@caliopen.local; Sun, 24 Jan 2016 21:07:21 -0500\nReceived: from fermat1.math.s.chiba-u.ac.jp (fermat [192.168.32.10])\n\tby mathmail.math.s.chiba-u.ac.jp (Postfix) with ESMTP id 0CC49C0560\n\tfor <dev@caliopen.local>; Mon, 25 Jan 2016 11:07:17 +0900 (JST)\nDate: Mon, 25 Jan 2016 11:07:16 +0900\nMessage-ID: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp>\nFrom: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>\nTo: dev@caliopen.local\nSubject: About x_draw_xwidget_glyph_string\nUser-Agent: Wanderlust/2.14.0 (Africa) SEMI/1.14.6 (Maruoka) FLIM/1.14.8\n\t(=?ISO-8859-4?Q?Shij=F2?=) APEL/10.6 Emacs/22.3 (sparc-sun-solaris2.8)\n\tMULE/5.0 (SAKAKI)\nOrganization: Faculty of Science, Chiba University\nMIME-Version: 1.0 (generated by SEMI 1.14.6 - \"Maruoka\")\nContent-Type: text/plain; charset=US-ASCII\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 133.82.132.2\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 25 Jan 2016 02:07:25 -0000\nContent-Length: 2091\nLines: 51\n\nI have a question and a comment about x_draw_xwidget_glyph_string in\nsrc/xwidget.c.\n\n1. Question about xwidget initialization.\n\n   559\tx_draw_xwidget_glyph_string (struct glyph_string *s)\n   560\t{\n   561\t  /* This method is called by the redisplay engine and places the\n   562\t     xwidget on screen.  Moving and clipping is done here.  Also view\n   563\t     initialization.  */\n   564\t  struct xwidget *xww = s->xwidget;\n   565\t  struct xwidget_view *xv = xwidget_view_lookup (xww, s->w);\n(snip)\n   574\t  /* Do initialization here in the display loop because there is no\n   575\t     other time to know things like window placement etc.  */\n   576\t  xv = xwidget_init_view (xww, s, x, y);\n\nLine 576 indicates a new xwidget view is created in every\nx_draw_xwidget_glyph_string call.  But this makes xwidget-view-list\nlonger and longer, and looks like a waste of memory, if not a leak.\nAlso, if it is an intended behavior, then it looks strange to look up\na view at Line 565.\n\n2. Comment on clipping.\n\n   578\t  /* Calculate clipping, which is used for all manner of onscreen\n   579\t     xwidget views.  Each widget border can get clipped by other emacs\n   580\t     objects so there are four clipping variables.  */\n   581\t  clip_right =\n   582\t    min (xww->width,\n   583\t         WINDOW_RIGHT_EDGE_X (s->w) - x -\n   584\t         WINDOW_RIGHT_SCROLL_BAR_AREA_WIDTH (s->w) -\n   585\t         WINDOW_RIGHT_FRINGE_WIDTH (s->w));\n   586\t  clip_left =\n   587\t    max (0,\n   588\t         WINDOW_LEFT_EDGE_X (s->w) - x +\n   589\t         WINDOW_LEFT_SCROLL_BAR_AREA_WIDTH (s->w) +\n   590\t         WINDOW_LEFT_FRINGE_WIDTH (s->w));\n   591\t\n   592\t  clip_bottom =\n   593\t    min (xww->height,\n   594\t         WINDOW_BOTTOM_EDGE_Y (s->w) - WINDOW_MODE_LINE_HEIGHT (s->w) - y);\n   595\t  clip_top = max (0, WINDOW_TOP_EDGE_Y (s->w) - y);\n\nI think the calculation of clipping should use the function window_box\nrather than manual calculation with various window macros.  Otherwise,\nxwidget views will cover horizontal scroll bars, for example.\n\n\t\t\t\t     YAMAMOTO Mitsuharu\n\t\t\t\tmituharu@math.s.chiba-u.ac.jp\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_37.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aNjKo-0007v8-FW\n\tfor mharc-dev@caliopen.local; Mon, 25 Jan 2016 10:45:42 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40088)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aNjKm-0007so-Oh\n\tfor dev@caliopen.local; Mon, 25 Jan 2016 10:45:41 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aNjKi-0005pJ-On\n\tfor dev@caliopen.local; Mon, 25 Jan 2016 10:45:40 -0500\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:39371)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aNjKi-0005pA-MT; Mon, 25 Jan 2016 10:45:36 -0500\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4728\n\thelo=HOME-C4E4A596F7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aNjKi-0006CN-1Z; Mon, 25 Jan 2016 10:45:36 -0500\nDate: Mon, 25 Jan 2016 17:46:00 +0200\nMessage-Id: <83oac93bnr.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>\nIn-reply-to: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp> (message from\n\tYAMAMOTO Mitsuharu on Mon, 25 Jan 2016 11:07:16 +0900)\nSubject: Re: About x_draw_xwidget_glyph_string\nReferences: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 25 Jan 2016 15:45:41 -0000\nContent-Length: 1222\nLines: 32\n\n> Date: Mon, 25 Jan 2016 11:07:16 +0900\n> From: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>\n> \n> 2. Comment on clipping.\n> \n>    578\t  /* Calculate clipping, which is used for all manner of onscreen\n>    579\t     xwidget views.  Each widget border can get clipped by other emacs\n>    580\t     objects so there are four clipping variables.  */\n>    581\t  clip_right =\n>    582\t    min (xww->width,\n>    583\t         WINDOW_RIGHT_EDGE_X (s->w) - x -\n>    584\t         WINDOW_RIGHT_SCROLL_BAR_AREA_WIDTH (s->w) -\n>    585\t         WINDOW_RIGHT_FRINGE_WIDTH (s->w));\n>    586\t  clip_left =\n>    587\t    max (0,\n>    588\t         WINDOW_LEFT_EDGE_X (s->w) - x +\n>    589\t         WINDOW_LEFT_SCROLL_BAR_AREA_WIDTH (s->w) +\n>    590\t         WINDOW_LEFT_FRINGE_WIDTH (s->w));\n>    591\t\n>    592\t  clip_bottom =\n>    593\t    min (xww->height,\n>    594\t         WINDOW_BOTTOM_EDGE_Y (s->w) - WINDOW_MODE_LINE_HEIGHT (s->w) - y);\n>    595\t  clip_top = max (0, WINDOW_TOP_EDGE_Y (s->w) - y);\n> \n> I think the calculation of clipping should use the function window_box\n> rather than manual calculation with various window macros.  Otherwise,\n> xwidget views will cover horizontal scroll bars, for example.\n\nI agree.\n\nThanks.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_39.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aoYS4-0001mN-EQ\n\tfor mharc-dev@caliopen.local; Fri, 08 Apr 2016 11:36:04 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:54608)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <joakim@verona.se>) id 1aoYS1-0001iH-TJ\n\tfor dev@caliopen.local; Fri, 08 Apr 2016 11:36:02 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <joakim@verona.se>) id 1aoYS0-0008KH-VR\n\tfor dev@caliopen.local; Fri, 08 Apr 2016 11:36:01 -0400\nReceived: from mx1.bahnhof.se ([213.80.101.11]:31359)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <joakim@verona.se>)\n\tid 1aoYRu-0008Jx-7C; Fri, 08 Apr 2016 11:35:54 -0400\nReceived: from localhost (mf.bahnhof.se [213.80.101.20])\n\tby mx1-reinject (Postfix) with ESMTP id 94F3E4504D;\n\tFri,  8 Apr 2016 17:35:50 +0200 (CEST)\nX-Virus-Scanned: by amavisd-new using ClamAV at bahnhof.se (MF2)\nReceived: from mf2.bahnhof.se ([127.0.0.1])\n\tby localhost (mf2.bahnhof.se [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id STLgGa3go6LY; Fri,  8 Apr 2016 17:35:42 +0200 (CEST)\nReceived: from mta.verona.se (h-235-62.a149.priv.bahnhof.se [85.24.235.62])\n\tby mf2.bahnhof.se (Postfix) with ESMTP id 2EF5B9401C3;\n\tFri,  8 Apr 2016 17:35:40 +0200 (CEST)\nReceived: from localhost (unknown [127.0.0.1])\n\tby mta.verona.se (Postfix) with ESMTP id CF6574EEFB8;\n\tFri,  8 Apr 2016 15:35:40 +0000 (UTC)\nX-Virus-Scanned: amavisd-new at verona.se\nReceived: from mta.verona.se ([127.0.0.1])\n\tby localhost (exodia.verona.se [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id x0ayTIB_udXc; Fri,  8 Apr 2016 17:35:23 +0200 (CEST)\nReceived: from exodia.verona.se (www.verona.se [192.168.200.15])\n\tby mta.verona.se (Postfix) with ESMTP id 84D444EEFB3;\n\tFri,  8 Apr 2016 17:35:23 +0200 (CEST)\nFrom: joakim@verona.se\nTo: Eli Zaretskii <eliz@gnu.org>\nCc: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>,  dev@caliopen.local\nSubject: Re: About x_draw_xwidget_glyph_string\nReferences: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp>\n\t<83oac93bnr.fsf@gnu.org>\nDate: Fri, 08 Apr 2016 17:35:23 +0200\nIn-Reply-To: <83oac93bnr.fsf@gnu.org> (Eli Zaretskii's message of \"Mon, 25 Jan\n\t2016 17:46:00 +0200\")\nMessage-ID: <m38u0oazb8.fsf@exodia.verona.se>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 213.80.101.11\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 08 Apr 2016 15:36:03 -0000\nContent-Length: 1987\nLines: 66\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n>> Date: Mon, 25 Jan 2016 11:07:16 +0900\n>> From: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>\n>> \n>> 2. Comment on clipping.\n>> \n>>    578\t  /* Calculate clipping, which is used for all manner of onscreen\n>>    579\t     xwidget views.  Each widget border can get clipped by other emacs\n>>    580\t     objects so there are four clipping variables.  */\n>>    581\t  clip_right =\n>>    582\t    min (xww->width,\n>>    583\t         WINDOW_RIGHT_EDGE_X (s->w) - x -\n>>    584\t         WINDOW_RIGHT_SCROLL_BAR_AREA_WIDTH (s->w) -\n>>    585\t         WINDOW_RIGHT_FRINGE_WIDTH (s->w));\n>>    586\t  clip_left =\n>>    587\t    max (0,\n>>    588\t         WINDOW_LEFT_EDGE_X (s->w) - x +\n>>    589\t         WINDOW_LEFT_SCROLL_BAR_AREA_WIDTH (s->w) +\n>>    590\t         WINDOW_LEFT_FRINGE_WIDTH (s->w));\n>>    591\t\n>>    592\t  clip_bottom =\n>>    593\t    min (xww->height,\n>>    594\t         WINDOW_BOTTOM_EDGE_Y (s->w) - WINDOW_MODE_LINE_HEIGHT (s->w) - y);\n>>    595\t  clip_top = max (0, WINDOW_TOP_EDGE_Y (s->w) - y);\n>> \n>> I think the calculation of clipping should use the function window_box\n>> rather than manual calculation with various window macros.  Otherwise,\n>> xwidget views will cover horizontal scroll bars, for example.\n>\n> I agree.\n>\n> Thanks.\n>\n\nI tried to do this, but I'm doing something wrong. How is window_box\nmeant to be used?\n\nThis is my attempt to replace the code above:\n\n  //JAVE work in progressing, suggested by YAMAMOTO Mitsuharu\n  int text_area_x, text_area_y, text_area_width, text_area_height;\n  \n  window_box (s->w,\n              ANY_AREA, //also tried TEXT_AREA\n              &text_area_x,\n              &text_area_y,\n              &text_area_width,\n              &text_area_height);\n  clip_right =\n    min (xww->width,\n         text_area_width);\n  clip_left =\n    max (0,\n         text_area_x);\n\n  clip_bottom =\n    min (xww->height,\n         text_area_y);\n  clip_top = max (0, text_area_height);\n\n\n\n-- \nJoakim Verona\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_41.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aonU9-0005Ps-Dm\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 03:39:13 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40181)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aonU6-0005Lv-DJ\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 03:39:11 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aonU3-0004el-66\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 03:39:10 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:36691)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aonU3-0004eh-2h; Sat, 09 Apr 2016 03:39:07 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1124\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aonU2-0002Mv-5t; Sat, 09 Apr 2016 03:39:06 -0400\nDate: Sat, 09 Apr 2016 10:38:28 +0300\nMessage-Id: <83shyvi64r.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: joakim@verona.se\nCC: mituharu@math.s.chiba-u.ac.jp, dev@caliopen.local\nIn-reply-to: <m38u0oazb8.fsf@exodia.verona.se> (joakim@verona.se)\nSubject: Re: About x_draw_xwidget_glyph_string\nReply-to: Eli Zaretskii <eliz@gnu.org>\nReferences: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp>\n\t<83oac93bnr.fsf@gnu.org> <m38u0oazb8.fsf@exodia.verona.se>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 09 Apr 2016 07:39:11 -0000\nContent-Length: 2414\nLines: 73\n\n> From: joakim@verona.se\n> Cc: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>,  dev@caliopen.local\n> Date: Fri, 08 Apr 2016 17:35:23 +0200\n> \n> Eli Zaretskii <eliz@gnu.org> writes:\n> \n> >> Date: Mon, 25 Jan 2016 11:07:16 +0900\n> >> From: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>\n> >> \n> >> 2. Comment on clipping.\n> >> \n> >>    578\t  /* Calculate clipping, which is used for all manner of onscreen\n> >>    579\t     xwidget views.  Each widget border can get clipped by other emacs\n> >>    580\t     objects so there are four clipping variables.  */\n> >>    581\t  clip_right =\n> >>    582\t    min (xww->width,\n> >>    583\t         WINDOW_RIGHT_EDGE_X (s->w) - x -\n> >>    584\t         WINDOW_RIGHT_SCROLL_BAR_AREA_WIDTH (s->w) -\n> >>    585\t         WINDOW_RIGHT_FRINGE_WIDTH (s->w));\n> >>    586\t  clip_left =\n> >>    587\t    max (0,\n> >>    588\t         WINDOW_LEFT_EDGE_X (s->w) - x +\n> >>    589\t         WINDOW_LEFT_SCROLL_BAR_AREA_WIDTH (s->w) +\n> >>    590\t         WINDOW_LEFT_FRINGE_WIDTH (s->w));\n> >>    591\t\n> >>    592\t  clip_bottom =\n> >>    593\t    min (xww->height,\n> >>    594\t         WINDOW_BOTTOM_EDGE_Y (s->w) - WINDOW_MODE_LINE_HEIGHT (s->w) - y);\n> >>    595\t  clip_top = max (0, WINDOW_TOP_EDGE_Y (s->w) - y);\n> >> \n> >> I think the calculation of clipping should use the function window_box\n> >> rather than manual calculation with various window macros.  Otherwise,\n> >> xwidget views will cover horizontal scroll bars, for example.\n> >\n> > I agree.\n> >\n> > Thanks.\n> >\n> \n> I tried to do this, but I'm doing something wrong. How is window_box\n> meant to be used?\n> \n> This is my attempt to replace the code above:\n> \n>   //JAVE work in progressing, suggested by YAMAMOTO Mitsuharu\n>   int text_area_x, text_area_y, text_area_width, text_area_height;\n>   \n>   window_box (s->w,\n>               ANY_AREA, //also tried TEXT_AREA\n\nYou should use TEXT_AREA here.\n\n>               &text_area_x,\n>               &text_area_y,\n>               &text_area_width,\n>               &text_area_height);\n>   clip_right =\n>     min (xww->width,\n>          text_area_width);\n>   clip_left =\n>     max (0,\n>          text_area_x);\n> \n>   clip_bottom =\n>     min (xww->height,\n>          text_area_y);\n>   clip_top = max (0, text_area_height);\n\nI think clip_top should use text_area_y and clip_bottom should use\ntext_area_height.\n\nOther than those two issues, what other problems do you see?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_43.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aoqzC-0001cq-TZ\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 07:23:30 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49605)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <joakim@verona.se>) id 1aoqzA-0001aG-9z\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 07:23:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <joakim@verona.se>) id 1aoqz9-0003rX-6l\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 07:23:28 -0400\nReceived: from mx2.bahnhof.se ([213.80.101.12]:52667)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <joakim@verona.se>)\n\tid 1aoqz5-0003qy-7M; Sat, 09 Apr 2016 07:23:23 -0400\nReceived: from localhost (mf.bahnhof.se [213.80.101.20])\n\tby mx2-reinject (Postfix) with ESMTP id 93EF140906;\n\tSat,  9 Apr 2016 13:23:21 +0200 (CEST)\nX-Virus-Scanned: by amavisd-new using ClamAV at bahnhof.se (MF4)\nReceived: from mf4.bahnhof.se ([127.0.0.1])\n\tby localhost (mf4.bahnhof.se [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id TNZPtrlmXg1n; Sat,  9 Apr 2016 13:23:15 +0200 (CEST)\nReceived: from mta.verona.se (h-235-62.a149.priv.bahnhof.se [85.24.235.62])\n\tby mf4.bahnhof.se (Postfix) with ESMTP id 022D43D77DA;\n\tSat,  9 Apr 2016 13:23:12 +0200 (CEST)\nReceived: from localhost (unknown [127.0.0.1])\n\tby mta.verona.se (Postfix) with ESMTP id 1DE594EF0A5;\n\tSat,  9 Apr 2016 11:23:12 +0000 (UTC)\nX-Virus-Scanned: amavisd-new at verona.se\nReceived: from mta.verona.se ([127.0.0.1])\n\tby localhost (exodia.verona.se [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id 1cN9L2p_aqWe; Sat,  9 Apr 2016 13:22:52 +0200 (CEST)\nReceived: from exodia.verona.se (www.verona.se [192.168.200.15])\n\tby mta.verona.se (Postfix) with ESMTP id DE3C44EF0A3;\n\tSat,  9 Apr 2016 13:22:52 +0200 (CEST)\nFrom: joakim@verona.se\nTo: Eli Zaretskii <eliz@gnu.org>\nCc: mituharu@math.s.chiba-u.ac.jp,  dev@caliopen.local\nSubject: Re: About x_draw_xwidget_glyph_string\nReferences: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp>\n\t<83oac93bnr.fsf@gnu.org> <m38u0oazb8.fsf@exodia.verona.se>\n\t<83shyvi64r.fsf@gnu.org>\nDate: Sat, 09 Apr 2016 13:22:52 +0200\nIn-Reply-To: <83shyvi64r.fsf@gnu.org> (Eli Zaretskii's message of \"Sat, 09 Apr\n\t2016 10:38:28 +0300\")\nMessage-ID: <m3shyv9gc3.fsf@exodia.verona.se>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 213.80.101.12\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 09 Apr 2016 11:23:29 -0000\nContent-Length: 4347\nLines: 132\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n>> From: joakim@verona.se\n>> Cc: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>,  dev@caliopen.local\n>> Date: Fri, 08 Apr 2016 17:35:23 +0200\n>> \n>> Eli Zaretskii <eliz@gnu.org> writes:\n>> \n>> >> Date: Mon, 25 Jan 2016 11:07:16 +0900\n>> >> From: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>\n>> >> \n>> >> 2. Comment on clipping.\n>> >> \n>> >>    578\t  /* Calculate clipping, which is used for all manner of onscreen\n>> >>    579\t     xwidget views.  Each widget border can get clipped by other emacs\n>> >>    580\t     objects so there are four clipping variables.  */\n>> >>    581\t  clip_right =\n>> >>    582\t    min (xww->width,\n>> >>    583\t         WINDOW_RIGHT_EDGE_X (s->w) - x -\n>> >>    584\t         WINDOW_RIGHT_SCROLL_BAR_AREA_WIDTH (s->w) -\n>> >>    585\t         WINDOW_RIGHT_FRINGE_WIDTH (s->w));\n>> >>    586\t  clip_left =\n>> >>    587\t    max (0,\n>> >>    588\t         WINDOW_LEFT_EDGE_X (s->w) - x +\n>> >>    589\t         WINDOW_LEFT_SCROLL_BAR_AREA_WIDTH (s->w) +\n>> >>    590\t         WINDOW_LEFT_FRINGE_WIDTH (s->w));\n>> >>    591\t\n>> >>    592\t  clip_bottom =\n>> >>    593\t    min (xww->height,\n>> >>    594\t         WINDOW_BOTTOM_EDGE_Y (s->w) - WINDOW_MODE_LINE_HEIGHT (s->w) - y);\n>> >>    595\t  clip_top = max (0, WINDOW_TOP_EDGE_Y (s->w) - y);\n>> >> \n>> >> I think the calculation of clipping should use the function window_box\n>> >> rather than manual calculation with various window macros.  Otherwise,\n>> >> xwidget views will cover horizontal scroll bars, for example.\n>> >\n>> > I agree.\n>> >\n>> > Thanks.\n>> >\n>> \n>> I tried to do this, but I'm doing something wrong. How is window_box\n>> meant to be used?\n>> \n>> This is my attempt to replace the code above:\n>> \n>>   //JAVE work in progressing, suggested by YAMAMOTO Mitsuharu\n>>   int text_area_x, text_area_y, text_area_width, text_area_height;\n>>   \n>>   window_box (s->w,\n>>               ANY_AREA, //also tried TEXT_AREA\n>\n> You should use TEXT_AREA here.\n>\n>>               &text_area_x,\n>>               &text_area_y,\n>>               &text_area_width,\n>>               &text_area_height);\n>>   clip_right =\n>>     min (xww->width,\n>>          text_area_width);\n>>   clip_left =\n>>     max (0,\n>>          text_area_x);\n>> \n>>   clip_bottom =\n>>     min (xww->height,\n>>          text_area_y);\n>>   clip_top = max (0, text_area_height);\n>\n> I think clip_top should use text_area_y and clip_bottom should use\n> text_area_height.\n>\n> Other than those two issues, what other problems do you see?\n\nWell, I saw nothing at all :)\n\nThis patch seems to work during some brief testing. \n\ndiff --git a/src/xwidget.c b/src/xwidget.c\nindex 8ff4c23..fa61f57 100644\n--- a/src/xwidget.c\n+++ b/src/xwidget.c\n@@ -578,25 +578,24 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)\n      other time to know things like window placement etc.  */\n   xv = xwidget_init_view (xww, s, x, y);\n \n-  /* Calculate clipping, which is used for all manner of onscreen\n-     xwidget views.  Each widget border can get clipped by other emacs\n-     objects so there are four clipping variables.  */\n-  clip_right =\n-    min (xww->width,\n-         WINDOW_RIGHT_EDGE_X (s->w) - x -\n-         WINDOW_RIGHT_SCROLL_BAR_AREA_WIDTH (s->w) -\n-         WINDOW_RIGHT_FRINGE_WIDTH (s->w));\n-  clip_left =\n-    max (0,\n-         WINDOW_LEFT_EDGE_X (s->w) - x +\n-         WINDOW_LEFT_SCROLL_BAR_AREA_WIDTH (s->w) +\n-         WINDOW_LEFT_FRINGE_WIDTH (s->w));\n-\n-  clip_bottom =\n-    min (xww->height,\n-         WINDOW_BOTTOM_EDGE_Y (s->w) - WINDOW_MODE_LINE_HEIGHT (s->w) - y);\n-  clip_top = max (0, WINDOW_TOP_EDGE_Y (s->w) - y);\n-\n+  int text_area_x, text_area_y, text_area_width, text_area_height;\n+  \n+  window_box (s->w,\n+              ANY_AREA,\n+              &text_area_x,\n+              &text_area_y,\n+              &text_area_width,\n+              &text_area_height);\n+  clip_right = min (xww->width,\n+                    text_area_width);\n+  clip_left = max (0,\n+                   text_area_x);\n+\n+  clip_bottom = min (xww->height,\n+                     text_area_height);\n+  clip_top = max (0, text_area_y);\n+\n+  \n   /* We are concerned with movement of the onscreen area.  The area\n      might sit still when the widget actually moves.  This happens\n      when an Emacs window border moves across a widget window.  So, if\n\n\n-- \nJoakim Verona\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_45.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apAkT-0001RS-9C\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 04:29:37 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39590)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mituharu@math.s.chiba-u.ac.jp>) id 1apAkR-0001Ni-G3\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 04:29:36 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <mituharu@math.s.chiba-u.ac.jp>) id 1apAkQ-0002wG-Hw\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 04:29:35 -0400\nReceived: from mathmail.math.s.chiba-u.ac.jp ([133.82.132.2]:60354)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mituharu@math.s.chiba-u.ac.jp>)\n\tid 1apAkM-0002uh-5r; Sun, 10 Apr 2016 04:29:30 -0400\nReceived: from fermat1.math.s.chiba-u.ac.jp (fermat [192.168.32.10])\n\tby mathmail.math.s.chiba-u.ac.jp (Postfix) with ESMTP id 5F092F08F1;\n\tSun, 10 Apr 2016 17:29:26 +0900 (JST)\n\t(envelope-from mituharu@math.s.chiba-u.ac.jp)\nDate: Sun, 10 Apr 2016 17:29:26 +0900\nMessage-ID: <wl7fg5vpcp.wl%mituharu@math.s.chiba-u.ac.jp>\nFrom: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>\nTo: joakim@verona.se\nCc: Eli Zaretskii <eliz@gnu.org>,\n\tdev@caliopen.local\nSubject: Re: About x_draw_xwidget_glyph_string\nIn-Reply-To: <m3shyv9gc3.fsf@exodia.verona.se>\nReferences: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp>\n\t<83oac93bnr.fsf@gnu.org> <m38u0oazb8.fsf@exodia.verona.se>\n\t<83shyvi64r.fsf@gnu.org> <m3shyv9gc3.fsf@exodia.verona.se>\nUser-Agent: Wanderlust/2.14.0 (Africa) SEMI/1.14.6 (Maruoka) FLIM/1.14.8\n\t(=?ISO-8859-4?Q?Shij=F2?=) APEL/10.6 Emacs/22.3 (sparc-sun-solaris2.8)\n\tMULE/5.0 (SAKAKI)\nOrganization: Faculty of Science, Chiba University\nMIME-Version: 1.0 (generated by SEMI 1.14.6 - \"Maruoka\")\nContent-Type: text/plain; charset=US-ASCII\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 133.82.132.2\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 08:29:36 -0000\nContent-Length: 2269\nLines: 63\n\n>>>>> On Sat, 09 Apr 2016 13:22:52 +0200, joakim@verona.se said:\n\n>> I think clip_top should use text_area_y and clip_bottom should use\n>> text_area_height.\n>>\n>> Other than those two issues, what other problems do you see?\n\n> Well, I saw nothing at all :)\n\n> This patch seems to work during some brief testing.\n\nC-x 2 or C-x 3 makes the display incorrect.\n\nI'm not sure if I understand the intended meaning of clip_* variables\ncorrectly, but shouldn't this part be something like below?\n\n\t\t\t     YAMAMOTO Mitsuharu\n\t\t\tmituharu@math.s.chiba-u.ac.jp\n\ndiff --git a/src/xwidget.c b/src/xwidget.c\nindex 7e96307..0777777 100644\n--- a/src/xwidget.c\n+++ b/src/xwidget.c\n@@ -580,20 +580,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)\n\n   int text_area_x, text_area_y, text_area_width, text_area_height;\n\n-  window_box (s->w,\n-              ANY_AREA,\n-              &text_area_x,\n-              &text_area_y,\n-              &text_area_width,\n-              &text_area_height);\n-  clip_right = min (xww->width,\n-                    text_area_width);\n-  clip_left = max (0,\n-                   text_area_x);\n-\n-  clip_bottom = min (xww->height,\n-                     text_area_height);\n-  clip_top = max (0, text_area_y);\n+  window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,\n+              &text_area_width, &text_area_height);\n+  clip_left = max (0, text_area_x - x);\n+  clip_right = max (clip_left,\n+\t\t    min (xww->width, text_area_x + text_area_width - x));\n+  clip_top = max (0, text_area_y - y);\n+  clip_bottom = max (clip_top,\n+\t\t     min (xww->height, text_area_y + text_area_height - y));\n\n   /* We are concerned with movement of the onscreen area.  The area\n      might sit still when the widget actually moves.  This happens\n@@ -622,8 +616,7 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)\n       || xv->clip_bottom != clip_bottom\n       || xv->clip_top != clip_top || xv->clip_left != clip_left)\n     {\n-      gtk_widget_set_size_request (xv->widgetwindow, clip_right + clip_left,\n-                                   clip_bottom + clip_top);\n+      gtk_widget_set_size_request (xv->widgetwindow, clip_right, clip_bottom);\n       gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,\n                       -clip_top);\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_47.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apPLe-0005Fr-BS\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 20:04:58 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36432)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mituharu@math.s.chiba-u.ac.jp>) id 1apPLc-0005Cw-3R\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 20:04:57 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <mituharu@math.s.chiba-u.ac.jp>) id 1apPLb-0001xg-6L\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 20:04:56 -0400\nReceived: from mathmail.math.s.chiba-u.ac.jp ([133.82.132.2]:59598)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mituharu@math.s.chiba-u.ac.jp>)\n\tid 1apPLV-0001wC-6G; Sun, 10 Apr 2016 20:04:49 -0400\nReceived: from fermat1.math.s.chiba-u.ac.jp (fermat [192.168.32.10])\n\tby mathmail.math.s.chiba-u.ac.jp (Postfix) with ESMTP id 36990F08F6;\n\tMon, 11 Apr 2016 09:04:45 +0900 (JST)\n\t(envelope-from mituharu@math.s.chiba-u.ac.jp)\nDate: Mon, 11 Apr 2016 09:04:45 +0900\nMessage-ID: <wltwj9ggde.wl%mituharu@math.s.chiba-u.ac.jp>\nFrom: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>\nTo: joakim@verona.se\nCc: Eli Zaretskii <eliz@gnu.org>,\n\tdev@caliopen.local\nSubject: Re: About x_draw_xwidget_glyph_string\nIn-Reply-To: <wl7fg5vpcp.wl%mituharu@math.s.chiba-u.ac.jp>\nReferences: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp>\n\t<83oac93bnr.fsf@gnu.org> <m38u0oazb8.fsf@exodia.verona.se>\n\t<83shyvi64r.fsf@gnu.org> <m3shyv9gc3.fsf@exodia.verona.se>\n\t<wl7fg5vpcp.wl%mituharu@math.s.chiba-u.ac.jp>\nUser-Agent: Wanderlust/2.14.0 (Africa) SEMI/1.14.6 (Maruoka) FLIM/1.14.8\n\t(=?ISO-8859-4?Q?Shij=F2?=) APEL/10.6 Emacs/22.3 (sparc-sun-solaris2.8)\n\tMULE/5.0 (SAKAKI)\nOrganization: Faculty of Science, Chiba University\nMIME-Version: 1.0 (generated by SEMI 1.14.6 - \"Maruoka\")\nContent-Type: text/plain; charset=US-ASCII\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 133.82.132.2\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 11 Apr 2016 00:04:57 -0000\nContent-Length: 2892\nLines: 75\n\n>>>>> On Sun, 10 Apr 2016 17:29:26 +0900, YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp> said:\n\n>>> I think clip_top should use text_area_y and clip_bottom should use\n>>> text_area_height.\n>>>\n>>> Other than those two issues, what other problems do you see?\n\n>> Well, I saw nothing at all :)\n\n>> This patch seems to work during some brief testing.\n\n> C-x 2 or C-x 3 makes the display incorrect.\n\n> I'm not sure if I understand the intended meaning of clip_* variables\n> correctly, but shouldn't this part be something like below?\n\n> \t\t\t     YAMAMOTO Mitsuharu\n> \t\t\tmituharu@math.s.chiba-u.ac.jp\n\n> diff --git a/src/xwidget.c b/src/xwidget.c\n> index 7e96307..0777777 100644\n> --- a/src/xwidget.c\n> +++ b/src/xwidget.c\n> @@ -580,20 +580,14 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)\n\n>    int text_area_x, text_area_y, text_area_width, text_area_height;\n\n> -  window_box (s->w,\n> -              ANY_AREA,\n> -              &text_area_x,\n> -              &text_area_y,\n> -              &text_area_width,\n> -              &text_area_height);\n> -  clip_right = min (xww->width,\n> -                    text_area_width);\n> -  clip_left = max (0,\n> -                   text_area_x);\n> -\n> -  clip_bottom = min (xww->height,\n> -                     text_area_height);\n> -  clip_top = max (0, text_area_y);\n> +  window_box (s->w, TEXT_AREA, &text_area_x, &text_area_y,\n> +              &text_area_width, &text_area_height);\n> +  clip_left = max (0, text_area_x - x);\n> +  clip_right = max (clip_left,\n> +\t\t    min (xww->width, text_area_x + text_area_width - x));\n> +  clip_top = max (0, text_area_y - y);\n> +  clip_bottom = max (clip_top,\n> +\t\t     min (xww->height, text_area_y + text_area_height - y));\n\n>    /* We are concerned with movement of the onscreen area.  The area\n>       might sit still when the widget actually moves.  This happens\n> @@ -622,8 +616,7 @@ x_draw_xwidget_glyph_string (struct glyph_string *s)\n>        || xv->clip_bottom != clip_bottom\n>        || xv->clip_top != clip_top || xv->clip_left != clip_left)\n>      {\n> -      gtk_widget_set_size_request (xv->widgetwindow, clip_right + clip_left,\n> -                                   clip_bottom + clip_top);\n> +      gtk_widget_set_size_request (xv->widgetwindow, clip_right, clip_bottom);\n>        gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,\n>                        -clip_top);\n\nOops, I meant \"gtk_widget_set_size_request (xv->widgetwindow,\nclip_right - clip_left, clip_bottom - clip_top);\" for the last hunk.\nCould you double check if this matches your intended meaning of clip_*\nvariables?  Adding some comments to the corresponding members in\nsrc/xwidget.h would be nice.\n\nAlso, could you also take a look at the other issue of\n\"xwidget-view-list gets longer and longer\" I mentioned in the original\nmessage?\n\n\t\t\t\t     YAMAMOTO Mitsuharu\n\t\t\t\tmituharu@math.s.chiba-u.ac.jp\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_49.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aorfA-0002Rz-3Q\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 08:06:52 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:58946)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aorf7-0002OA-Lp\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 08:06:50 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aorf4-0005sO-DN\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 08:06:49 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:39700)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aorf4-0005sJ-9e; Sat, 09 Apr 2016 08:06:46 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1516\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aorf3-00046l-I3; Sat, 09 Apr 2016 08:06:45 -0400\nDate: Sat, 09 Apr 2016 15:06:08 +0300\nMessage-Id: <83fuuvhtqn.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: joakim@verona.se\nCC: mituharu@math.s.chiba-u.ac.jp, dev@caliopen.local\nIn-reply-to: <m3shyv9gc3.fsf@exodia.verona.se> (joakim@verona.se)\nSubject: Re: About x_draw_xwidget_glyph_string\nReply-to: Eli Zaretskii <eliz@gnu.org>\nReferences: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp>\n\t<83oac93bnr.fsf@gnu.org> <m38u0oazb8.fsf@exodia.verona.se>\n\t<83shyvi64r.fsf@gnu.org> <m3shyv9gc3.fsf@exodia.verona.se>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 09 Apr 2016 12:06:50 -0000\nContent-Length: 1103\nLines: 39\n\n> From: joakim@verona.se\n> Cc: mituharu@math.s.chiba-u.ac.jp,  dev@caliopen.local\n> Date: Sat, 09 Apr 2016 13:22:52 +0200\n> \n> >> This is my attempt to replace the code above:\n> >> \n> >>   //JAVE work in progressing, suggested by YAMAMOTO Mitsuharu\n> >>   int text_area_x, text_area_y, text_area_width, text_area_height;\n> >>   \n> >>   window_box (s->w,\n> >>               ANY_AREA, //also tried TEXT_AREA\n> >\n> > You should use TEXT_AREA here.\n> >\n> >>               &text_area_x,\n> >>               &text_area_y,\n> >>               &text_area_width,\n> >>               &text_area_height);\n> >>   clip_right =\n> >>     min (xww->width,\n> >>          text_area_width);\n> >>   clip_left =\n> >>     max (0,\n> >>          text_area_x);\n> >> \n> >>   clip_bottom =\n> >>     min (xww->height,\n> >>          text_area_y);\n> >>   clip_top = max (0, text_area_height);\n> >\n> > I think clip_top should use text_area_y and clip_bottom should use\n> > text_area_height.\n> >\n> > Other than those two issues, what other problems do you see?\n> \n> Well, I saw nothing at all :)\n\nThanks, pushed to the emacs-25 branch.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_5.argentina:2,",
    "content": "Return-Path: <info@pubblicitaaziendedb.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 44FE220AEC89A\n\tfor <laurent@brasil.brainstorm.fr>; Mon, 11 Apr 2016 13:36:20 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 348E733181F7; Mon, 11 Apr 2016 13:36:20 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 2522633181FC; Mon, 11 Apr 2016 13:36:20 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *****************\nX-Spam-Status: Yes, score=17.5 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RCVD_IN_BRBL_LASTEXT,RCVD_IN_CSS,\n\tRDNS_NONE,TO_NO_BRKTS_NORDNS,TO_NO_BRKTS_NORDNS_HTML,T_SURBL_MULTI1,\n\tT_URIBL_BLACK_OVERLAP,URIBL_BLACK,URIBL_DBL_SPAM,URIBL_JP_SURBL,\n\tURIBL_WS_SURBL autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [213.211.147.67 listed in zen.spamhaus.org]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [213.211.147.67 listed in bb.barracudacentral.org]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: pubblicitaaziendedb.com]\n\t*  1.6 URIBL_WS_SURBL Contains an URL listed in the WS SURBL blocklist\n\t*      [URIs: pubblicitaaziendedb.com]\n\t*  1.2 URIBL_JP_SURBL Contains an URL listed in the JP SURBL blocklist\n\t*      [URIs: pubblicitaaziendedb.com]\n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: pubblicitaaziendedb.com]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  0.0 T_URIBL_BLACK_OVERLAP T_URIBL_BLACK_OVERLAP\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 T_SURBL_MULTI1 T_SURBL_MULTI1\n\t*  0.0 TO_NO_BRKTS_NORDNS_HTML To: misformatted and no rDNS and HTML only\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from pubblicitaaziendedb.com (unknown [213.211.147.67])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 40DE433181F7\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 13:36:16 +0200 (CEST)\nTo: dev@caliopen.local\nFrom: info@pubblicitaaziendedb.com\nSubject: Marketing\nDate: Mon, 11 Apr 2016 11:36:16 +0000\nMessage-ID: <20160411_113616_082162.info@pubblicitaaziendedb.com>\nX-Mailer: XMAIL\nContent-Transfer-Encoding: 8bit\nContent-Type: text/html; charset=\"utf-8\"\nMime-Version: 1.0\nX-Spam-Prev-Subject: Marketing\nContent-Length: 1188\nLines: 31\n\nSalve, <br /><br />\n\nOffriamo banche dati di aziende e clienti italiani potenzialmente interessati alla Vostra offerta.<br /><br />\n\nLe banche dati delle aziende sono suddivise in gruppi di destinatari, secondo le Vostre necessità.<br /><br />\n\n--<br />\nDatabase Aziende 2016 - 140 € * Scadenza promozione: 11/04/2016 <br />\n--<br /><br />\n\n<a href=\"http://www.pubblicitaaziendedb.com/?lang=ita&page=database_aziende\">www.pubblicitaaziendedb.com</a> \n\n<br /><br />\nI dati delle aziende contengono:<br />\n\"Nome dell'azienda, Persona di contatto, E-mail, Telefono, Fax, CAP, Località, Via, ecc.\" <br /><br />\n\nLe possibilità di utilizzo delle banche dati sono praticamente illimitate.<br />\nGrazie ad esse potrete condurre campagne pubblicitarie in modo efficace e sicuro.<br /><br />\n\nI programmi da noi creati vi permettono di preparare una campagna pubblicitaria professionale,<br />\npersonalizzando linvio delle offerte mediante e-mail o fax. <br /><br />\n\nVi invitiamo a visitare il nostro sito internet:<br /><br />\n\n<a href=\"http://www.pubblicitaaziendedb.com/?lang=ita&page=database_aziende\">www.pubblicitaaziendedb.com</a>\n\n<br /><br />\nCordiali Saluti<br />\nMarco Coletti.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_51.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aNb7c-0003dG-UV\n\tfor mharc-dev@caliopen.local; Mon, 25 Jan 2016 01:59:32 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47186)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <joakim@verona.se>) id 1aNb7a-0003Zl-DU\n\tfor dev@caliopen.local; Mon, 25 Jan 2016 01:59:31 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <joakim@verona.se>) id 1aNb7X-0003cc-7p\n\tfor dev@caliopen.local; Mon, 25 Jan 2016 01:59:30 -0500\nReceived: from mx2.bahnhof.se ([213.80.101.12]:42871)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <joakim@verona.se>) id 1aNb7W-0003cN-Ts\n\tfor dev@caliopen.local; Mon, 25 Jan 2016 01:59:27 -0500\nReceived: from localhost (mf.bahnhof.se [213.80.101.20])\n\tby mx2-reinject (Postfix) with ESMTP id 2AAEE40C59;\n\tMon, 25 Jan 2016 07:59:24 +0100 (CET)\nX-Virus-Scanned: by amavisd-new using ClamAV at bahnhof.se (MF1)\nReceived: from mf1.bahnhof.se ([127.0.0.1])\n\tby localhost (mf1.bahnhof.se [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id FKZAhpFj9RSt; Mon, 25 Jan 2016 07:59:19 +0100 (CET)\nReceived: from mta.verona.se (h-235-62.a149.priv.bahnhof.se [85.24.235.62])\n\tby mf1.bahnhof.se (Postfix) with ESMTP id 0441F330AD8;\n\tMon, 25 Jan 2016 07:59:18 +0100 (CET)\nReceived: from localhost (unknown [127.0.0.1])\n\tby mta.verona.se (Postfix) with ESMTP id 73E034E8F87;\n\tMon, 25 Jan 2016 06:59:18 +0000 (UTC)\nX-Virus-Scanned: amavisd-new at verona.se\nReceived: from mta.verona.se ([127.0.0.1])\n\tby localhost (exodia.verona.se [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id ZoBSG-aEyljc; Mon, 25 Jan 2016 07:59:08 +0100 (CET)\nReceived: from exodia.verona.se (www.verona.se [192.168.200.15])\n\tby mta.verona.se (Postfix) with ESMTP id 102504E8F1A;\n\tMon, 25 Jan 2016 07:59:08 +0100 (CET)\nFrom: joakim@verona.se\nTo: YAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp>\nSubject: Re: About x_draw_xwidget_glyph_string\nReferences: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp>\nDate: Mon, 25 Jan 2016 07:59:07 +0100\nIn-Reply-To: <wlvb6il8dn.wl%mituharu@math.s.chiba-u.ac.jp> (YAMAMOTO\n\tMitsuharu's message of \"Mon, 25 Jan 2016 11:07:16 +0900\")\nMessage-ID: <m37fiy5emc.fsf@exodia.verona.se>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 213.80.101.12\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 25 Jan 2016 06:59:31 -0000\nContent-Length: 2421\nLines: 61\n\nYAMAMOTO Mitsuharu <mituharu@math.s.chiba-u.ac.jp> writes:\n\n> I have a question and a comment about x_draw_xwidget_glyph_string in\n> src/xwidget.c.\n>\n> 1. Question about xwidget initialization.\n>\n>    559\tx_draw_xwidget_glyph_string (struct glyph_string *s)\n>    560\t{\n>    561\t  /* This method is called by the redisplay engine and places the\n>    562\t     xwidget on screen.  Moving and clipping is done here.  Also view\n>    563\t     initialization.  */\n>    564\t  struct xwidget *xww = s->xwidget;\n>    565\t  struct xwidget_view *xv = xwidget_view_lookup (xww, s->w);\n> (snip)\n>    574\t  /* Do initialization here in the display loop because there is no\n>    575\t     other time to know things like window placement etc.  */\n>    576\t  xv = xwidget_init_view (xww, s, x, y);\n>\n> Line 576 indicates a new xwidget view is created in every\n> x_draw_xwidget_glyph_string call.  But this makes xwidget-view-list\n> longer and longer, and looks like a waste of memory, if not a leak.\n> Also, if it is an intended behavior, then it looks strange to look up\n> a view at Line 565.\n>\n> 2. Comment on clipping.\n>\n>    578\t  /* Calculate clipping, which is used for all manner of onscreen\n>    579\t     xwidget views.  Each widget border can get clipped by other emacs\n>    580\t     objects so there are four clipping variables.  */\n>    581\t  clip_right =\n>    582\t    min (xww->width,\n>    583\t         WINDOW_RIGHT_EDGE_X (s->w) - x -\n>    584\t         WINDOW_RIGHT_SCROLL_BAR_AREA_WIDTH (s->w) -\n>    585\t         WINDOW_RIGHT_FRINGE_WIDTH (s->w));\n>    586\t  clip_left =\n>    587\t    max (0,\n>    588\t         WINDOW_LEFT_EDGE_X (s->w) - x +\n>    589\t         WINDOW_LEFT_SCROLL_BAR_AREA_WIDTH (s->w) +\n>    590\t         WINDOW_LEFT_FRINGE_WIDTH (s->w));\n>    591\t\n>    592\t  clip_bottom =\n>    593\t    min (xww->height,\n>    594\t         WINDOW_BOTTOM_EDGE_Y (s->w) - WINDOW_MODE_LINE_HEIGHT (s->w) - y);\n>    595\t  clip_top = max (0, WINDOW_TOP_EDGE_Y (s->w) - y);\n>\n> I think the calculation of clipping should use the function window_box\n> rather than manual calculation with various window macros.  Otherwise,\n> xwidget views will cover horizontal scroll bars, for example.\n>\n> \t\t\t\t     YAMAMOTO Mitsuharu\n> \t\t\t\tmituharu@math.s.chiba-u.ac.jp\n>\n\nI won't have time to have a look at this until a couple of days, but\nyour suggestions sound promising. If you have a patch I can test I would\nbe grateful.\n\n-- \nJoakim Verona\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_53.argentina:2,",
    "content": "Return-Path: <bounce-248-35000120-248-248@kaixiner2.top>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id E960520B1E225\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 19:21:17 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid DA18C33181F7; Sun, 10 Apr 2016 19:21:17 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid BBA7D33181FC; Sun, 10 Apr 2016 19:21:17 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: **************\nX-Spam-Status: Yes, score=14.4 required=5.0 tests=BAYES_99,HTML_IMAGE_RATIO_04,\n\tHTML_MESSAGE,MIME_HTML_ONLY,NORMAL_HTTP_TO_IP,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RCVD_IN_CSS,RDNS_NONE,T_URIBL_SEM,\n\tT_URIBL_SEM_RED,URIBL_BLACK,URIBL_DBL_SPAM,URI_HEX autolearn=spam\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [49.51.10.156 listed in zen.spamhaus.org]\n\t*  0.0 T_URIBL_SEM Contains a URI listed in uribl.spameatingmonkey.net\n\t*      [URIs: xlatiao.com]\n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: xlatiao.com]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: rbbrandvip.com]\n\t*  0.0 T_URIBL_SEM_RED Contains a URI listed in urired.spameatingmonkey.net\n\t*      [URIs: xlatiao.com]\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  1.1 URI_HEX URI: URI hostname has long hexadecimal sequence\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\nX-Spam-DCC: : \nReceived: from kaixiner2.top (unknown [49.51.10.156])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id AEDF433181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 19:20:55 +0200 (CEST)\nReceived: by kaixiner2.top id h1a5nu0e97cq for <dev@caliopen.local>; Mon, 11 Apr 2016 00:34:53 +0800 (envelope-from <bounce-248-35000120-248-248@kaixiner2.top>)\nDate: Mon, 11 Apr 2016 00:34:53 +0800\nTo: \"dev@caliopen.local\" <dev@caliopen.local>\nFrom: RayGlass <rrban@kaixiner2.top>\nReply-to: RayGlass <rrban@kaixiner2.top>\nSubject: Ray Ban, Best Sunglasses. 65% OFF Promotion\nMessage-ID: <2a27be51bca7838fc015dcc7270069f5@10.212.193.177>\nX-Priority: 3\nX-Mailer: Email Sending System\nX-Complaints-To: kaixise@kaixiner2.top\nList-Unsubscribe: <http://49.51.10.156/oem/u.php?p=rt/rs/3p5l/rt/rt/rs>\nX-MessageID: MXx8fHwxNzE1Njl8fHx8bGF1cmVudEBicmFpbnN0b3JtLmZyfHx8fDF8fHx8MXx8fHww\nX-Report-Abuse: <http://49.51.10.156/oem/report_abuse.php?mid=MXx8fHwxNzE1Njl8fHx8bGF1cmVudEBicmFpbnN0b3JtLmZyfHx8fDF8fHx8MXx8fHww>\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/html; charset=\"utf-8\"\nX-Spam-Prev-Subject: Ray Ban, Best Sunglasses. 65% OFF Promotion\nContent-Length: 4506\nLines: 101\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.=\nw3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n<head>\n<title>Untitled document</title>\n</head>\n<body>\n<div></div>\n<div>\n<table style=3D\"box-sizing: border-box; width: 100%; font-family: Verdana; =\nbackground: #f6f6f6;\" bgcolor=3D\"#f6f6f6\">\n<tr>\n<td style=3D\"box-sizing: border-box; vertical-align: top;\" valign=3D\"top\"><=\n/td>\n<td style=3D\"box-sizing: border-box; margin: 0px auto; width: 580px; displa=\ny: block; max-width: 580px; vertical-align: top; padding: 10px;\" valign=3D\"=\ntop\">\n<div style=3D\"box-sizing: border-box; line-height: 1.5; margin: 0px auto; m=\nax-width: 580px; padding: 10px;\">\n<table style=3D\"box-sizing: border-box; margin-top: 0px; width: 100%; margi=\nn-bottom: 20px;\">\n<tr>\n<td style=3D\"box-sizing: border-box; text-align: center; vertical-align: to=\np;\" valign=3D\"top\" align=3D\"center\"><span style=3D\"color: #348eda;\"><span s=\ntyle=3D\"box-sizing: border-box;\"><img style=3D\"max-width: 100%; border-styl=\ne: none;\" src=3D\"http://422f44f83029e39b9748-f29f2db4d64d458cdd3e3151567798=\n93.r3.cf2.rackcdn.com/mailgun-icon.png\" width=3D\"50\" height=3D\"50\" /></span=\n></span></td>\n</tr>\n</table>\n<table style=3D\"box-sizing: border-box; width: 100%; background: #ffffff; b=\norder-radius: 3px; border: #e9e9e9 1px solid;\" bgcolor=3D\"#fff\">\n<tr>\n<td style=3D\"box-sizing: border-box; vertical-align: top; padding: 20px;\" v=\nalign=3D\"top\">\n<table style=3D\"box-sizing: border-box; width: 100%;\">\n<tr>\n<td style=3D\"box-sizing: border-box; vertical-align: top;\" valign=3D\"top\">\n<h1 style=3D\"line-height: 1.4; text-transform: capitalize; margin: 0px 0px =\n30px; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', san=\ns-serif; font-size: 38px; font-weight: 300;\" align=3D\"left\">Ray Ban 65% OFF=\n&nbsp;</h1>\n<p style=3D\"line-height: 1.5; margin: 0px 0px 15px; font-size: 14px;\" align=\n=3D\"left\"><span style=3D\"color: #ff0000;\"><strong>Ray Ban Online Promotoin,=\n Last 3 Days</strong></span></p>\n<p style=3D\"line-height: 1.5; margin: 0px 0px 15px; font-family: 'Helvetica=\n Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px;\" al=\nign=3D\"left\">Shop Ray Ban sunglasses for men and women at Sunglass Hut. Cho=\nose from classic styles like the Wayfarer, Aviator and Clubmaster. Free shi=\npping and returns on all orders!</p>\n<p style=3D\"line-height: 1.5; margin: 0px 0px 15px; font-family: 'Helvetica=\n Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px;\" al=\nign=3D\"left\">&nbsp;<img height=3D\"282\" width=3D\"452\" src=3D\"http://xlatiao.=\ncom/ray.jpg\" /></p>\n<div style=3D\"line-height: 1.5;\" align=3D\"left\"><a style=3D\"text-align: cen=\nter; padding-bottom: 12px; text-transform: capitalize; margin: 0px; padding=\n-left: 25px; padding-right: 25px; display: inline-block; background: #348ed=\na; color: #ffffff; font-size: 14px; cursor: pointer; font-weight: bold; tex=\nt-decoration: none; padding-top: 12px; border-radius: 5px; border: #348eda =\n1px solid;\" href=3D\"http://www.rbbrandvip.com\" target=3D\"_blank\">Order now<=\n/a></div>\n<p style=3D\"line-height: 1.5; margin: 0px 0px 15px; font-family: 'Helvetica=\n Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px;\" al=\nign=3D\"left\"><br /><span style=3D\"line-height: 1.5;\">Thanks for choosing Ra=\ny Ban and have a great day!</span></p>\n<p style=3D\"line-height: 1.5; margin: 0px 0px 15px; font-family: 'Helvetica=\n Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px;\" al=\nign=3D\"left\">The Mailgun  Team</p>\n</td>\n</tr>\n</table>\n</td>\n</tr>\n</table>\n<table style=3D\"box-sizing: border-box; width: 100%; color: #999999; clear:=\n both; font-size: 12px;\">\n<tr>\n<td style=3D\"box-sizing: border-box; text-align: center; padding-bottom: 20=\npx; padding-left: 0px; padding-right: 0px; color: #999999; font-size: 12px;=\n vertical-align: top; padding-top: 20px;\" valign=3D\"top\" align=3D\"center\"><=\na style=3D\"box-sizing: border-box; color: #999999;\" href=3D\"https://app.int=\nercom.io/unsubscribe?app_id=3D86jkog7i&conversation_id=3D1659585195&user_id=\n=3D566e3c42b22688c39800b9eb\"></a><a href=3D\"http://49.51.10.156/oem/u.php?p=\n=3Drt/rs/3p5l/rt/rt/rs\">Click here to unsubscribe</a>&nbsp;from  future  on=\nboarding emails.</td>\n</tr>\n</table>\n</div>\n</td>\n<td style=3D\"box-sizing: border-box; vertical-align: top;\" valign=3D\"top\"><=\n/td>\n</tr>\n</table>\n<div></div>\n</div>\n\n<img src=3D\"http://49.51.10.156/oem/to.php?p=3Drt/rt/rs/3p5l/rt/rs\" width=\n=3D\"5\" height=3D\"2\" alt=3D\".\">\n\n</body>\n</html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_55.argentina:2,",
    "content": "Return-Path: <tocservice@acm.org>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id BD5B220AFE6C2\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 19:02:19 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid AE75133181F7; Sun, 10 Apr 2016 19:02:19 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 9CE3D33181FC; Sun, 10 Apr 2016 19:02:19 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *****\nX-Spam-Status: Yes, score=5.5 required=5.0 tests=AWL,BAYES_50,\n\tFSL_HELO_NON_FQDN_1,HELO_NO_DOMAIN,HTML_IMAGE_RATIO_02,HTML_MESSAGE,\n\tRCVD_IN_PSBL,RDNS_NONE,TVD_RCVD_SINGLE,T_REMOTE_IMAGE autolearn=spam\n\tversion=3.3.2\nX-Spam-Report: \n\t*  0.0 FSL_HELO_NON_FQDN_1 FSL_HELO_NON_FQDN_1\n\t*  2.2 TVD_RCVD_SINGLE TVD_RCVD_SINGLE\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60%\n\t*      [score: 0.4972]\n\t*  2.7 RCVD_IN_PSBL RBL: Received via a relay in PSBL\n\t*      [13.92.117.248 listed in psbl.surriel.com]\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 HELO_NO_DOMAIN Relay reports its domain incorrectly\n\t*  0.0 T_REMOTE_IMAGE Message contains an external image\n\t* -1.5 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from VMS (unknown [13.92.117.248])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id AD46B33181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 19:02:16 +0200 (CEST)\nReceived: from ourlnlserver ([191.232.36.193])\n\tby VMS\n\t; Sun, 10 Apr 2016 11:08:19 +0000\nMessage-ID: <20160410.GMPFCPRAYVDISYKH@acm.org>\nFrom: \"=?utf-8?B?2YXYp9mK2qnYsdmI2LPYp9mB2Kog2b7Ysdi02YrYpw==?=\" <tocservice@acm.org>\nTo: <dev@caliopen.local>\nReply-To: \"=?utf-8?B?2YXYp9mK2qnYsdmI2LPYp9mB2Kog2b7Ysdi02YrYpw==?=\" <tocservice@acm.org>\nSubject: =?utf-8?B?2YXYstin2YrYp9mKINmG2LPYrtmHINmH2KfZiiDYp9i12YTZiiAo2KfZiNix2KzZitmG2KfZhCkg2LPZitiz2KrZhSDYudin2YXZhCDZiNmK2YbYr9mI2LI=?=\nDate: Sun, 10 Apr 2016 11:08:17 +0000\nImportance: Normal\nX-Mailer: Email Marketer Business Edition 1.80\nMIME-Version: 1.0\nContent-Type: multipart/alternative; boundary=\"----=_NextPart_714_9218_61661523.68107709\"\nX-Spam-Prev-Subject: =?utf-8?B?2YXYstin2YrYp9mKINmG2LPYrtmHINmH2KfZiiDYp9i12YTZiiAo2KfZiNix2KzZitmG2KfZhCkg2LPZitiz2KrZhSDYudin2YXZhCDZiNmK2YbYr9mI2LI=?=\nContent-Length: 15405\nLines: 209\n\nThis message is in MIME format with multi-part. Since your mail reader does not\nunderstand this format, some or all of this message may not be legible.\n\n------=_NextPart_714_9218_61661523.68107709\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: base64\n\nICDYotmK2Kcg2KrYtdin2YjZitixINix2Kcg2YbZhdmKINio2YrZhtmK2K/YnyDYp9mK2YbYrNin\nINqp2YTZitqpINqp2YbZitivLi4uIA0KIA0KDQogDQoNCg0KIA0KDQoNCiANCg0KDQogDQoNCg0K\n2KfYt9mE2KfYudin2Kog2KjZiti02KrYsSDZiCDZiNix2YjYryDYqNmHINmI2KjYs9in2YrYqiDY\ntNix2qnYqiANCg0KDQogDQoNCg0K2LTYsdqp2Kog2LHYp9mK2KfZhiDZhtiqINiz2YfYp9mF2Yog\n2K7Yp9i1DQrYp9i32YTYp9i52KfYqiDYqNmK2LTYqtixINix2Kcg2K/YsSDZiNio2LPYp9mK2Kog\n2YXYpyDZhdi02KfZh9iv2Ycg2qnZhtmK2K8gDQrYqtmH2LHYp9mGIC0g2KfZgtiv2LPZitmHIOKA\nkyDZhtix2LPZitiv2Ycg2KjZhyDZhdmK2YbZiiDYs9mK2KrZiiDigJMg2LbZhNi5INi02YXYp9mE\n2Yog2KjYstix2q/Ysdin2Ycg2KfYsdiq2LQg4oCTINm+2YTYp9qpIDExMw0K2KrZh9ix2KfZhi0g\n2K4g2LPZh9ix2YjYsdiv2Yog2LTZhdin2YTZiiAtINiuINin2KjZhiDZitmF2YrZhiAtINiuINiz\n2LHZiNi0IC0g2b7ZhNin2qkgMjANCtin2LXZgdmH2KfZhiAtINqG2YfYp9ix2KjYp9i6INio2KfZ\nhNinIC0g2YXYrNiq2YXYuSDZvtin2LHYs9mK2KfZhiAtINi32KjZgtmHIDMgLSDZiNin2K3YryA1\nMTENCtmE2YbYr9mG2Iwg2K7Zitin2KjYp9mGINmI2YbZhNin2qnYjCDYtNmF2KfYsdmHIDIwLTIy\nINqp2K8g2b7Ys9iq2YogIE4xIDdHVQ0KMDIxLTI4NDI4Mg==\n\n------=_NextPart_714_9218_61661523.68107709\nContent-Type: text/html; charset=utf-8\nContent-Transfer-Encoding: base64\n\nPEhUTUw+PEhFQUQ+DQo8TUVUQSBuYW1lPUdFTkVSQVRPUiBjb250ZW50PSJFbWFpbCBNYXJrZXRl\nciBNZXNzYWdlIEVkaXRvciI+PC9IRUFEPg0KPEJPRFk+DQo8UD4mbmJzcDsgDQo8VEFCTEUgY2Vs\nbFNwYWNpbmc9MCBjZWxsUGFkZGluZz0wIHdpZHRoPSIxMDAlIiBiZ0NvbG9yPSMzNDk4ZGIgYmFj\na2dyb3VuZD1odHRwczovL3N0YXRpYzMubWFpbGVybGl0ZS5jb20vaW1hZ2VzL2J1aWxkZXIvYmFj\na2dyb3VuZC9zdWJ0bGVfd2hpdGVfZmVhdGhlcnMucG5nIGJvcmRlcj0wPg0KDQo8VFI+DQo8VEQg\nY2xhc3M9bWFpbiBhbGlnbj1jZW50ZXI+DQo8VEFCTEUgY2xhc3M9cHJlaGVhZGVyIHN0eWxlPSJX\nSURUSDogNjQwcHgiIGRpcj1sdHIgY2VsbFNwYWNpbmc9MCBjZWxsUGFkZGluZz0wIHdpZHRoPTY0\nMCBhbGlnbj1jZW50ZXIgYm9yZGVyPTA+DQoNCjxUUj4NCjxURCBjbGFzcz1wcmVoZWFkZXItdGV4\ndCBzdHlsZT0iRk9OVC1TSVpFOiAxMXB4OyBGT05ULUZBTUlMWTogVGFob21hOyBDT0xPUjogIzY2\nNjY2NjsgUEFERElORy1CT1RUT006IDE1cHg7IFBBRERJTkctVE9QOiAxNXB4OyBQQURESU5HLUxF\nRlQ6IDBweDsgUEFERElORy1SSUdIVDogMHB4IiBkaXI9cnRsIHdpZHRoPTQyMCBhbGlnbj1yaWdo\ndD48L1REPg0KPFREIGNsYXNzPXByZWhlYWRlci1nYXAgd2lkdGg9MjA+PC9URD4NCjxURCBjbGFz\ncz1wcmVoZWFkZXItbGluayBzdHlsZT0iRk9OVC1TSVpFOiAxMXB4OyBGT05ULUZBTUlMWTogVGFo\nb21hOyBDT0xPUjogIzY2NjY2NjsgUEFERElORy1CT1RUT006IDE1cHg7IFBBRERJTkctVE9QOiAx\nNXB4OyBQQURESU5HLUxFRlQ6IDBweDsgUEFERElORy1SSUdIVDogMHB4IiBkaXI9cnRsIHdpZHRo\nPTIwMCBhbGlnbj1yaWdodD7YotmK2Kcg2KrYtdin2YjZitixINix2Kcg2YbZhdmKINio2YrZhtmK\n2K/YnyA8QSBzdHlsZT0iQ09MT1I6ICM2NjY2NjYiIGhyZWY9Imh0dHBzOi8vZjdpNnMzcjZrNC5t\nYWlsZXJsaXRlLmNvbS9iNWY3bTMvIj7Yp9mK2YbYrNinINqp2YTZitqpINqp2YbZitivLi4uIDwv\nQT48L1REPjwvVFI+PC9UQUJMRT4NCjxUQUJMRSBjbGFzcz1tYWluIHN0eWxlPSJXSURUSDogNjQw\ncHgiIGNlbGxTcGFjaW5nPTAgY2VsbFBhZGRpbmc9MCB3aWR0aD02NDAgYWxpZ249Y2VudGVyIGJv\ncmRlcj0wPg0KDQo8VFI+DQo8VEQgY2xhc3M9bWFpbiBzdHlsZT0iV0lEVEg6IDY0MHB4OyBNSU4t\nV0lEVEg6IDY0MHB4IiB3aWR0aD02NDA+DQo8VEFCTEUgY2xhc3M9bWFpbiBzdHlsZT0iV0lEVEg6\nIDY0MHB4IiBjZWxsU3BhY2luZz0wIGNlbGxQYWRkaW5nPTAgd2lkdGg9NjQwIGFsaWduPWNlbnRl\nciBib3JkZXI9MD4NCg0KPFRSPg0KPFREIGNsYXNzPWhlYWRlci1pbWcgYmdDb2xvcj0jMmMzZTUw\nIGFsaWduPWNlbnRlcj48QSBzdHlsZT0iQk9SREVSLVRPUDogbWVkaXVtIG5vbmU7IEJPUkRFUi1S\nSUdIVDogbWVkaXVtIG5vbmU7IEJPUkRFUi1CT1RUT006IG1lZGl1bSBub25lOyBCT1JERVItTEVG\nVDogbWVkaXVtIG5vbmU7IERJU1BMQVk6IGJsb2NrIiBocmVmPSJodHRwOi8vYml0Lmx5LzIwZEdo\ndXgiPjxJTUcgc3R5bGU9IkRJU1BMQVk6IGJsb2NrIiBib3JkZXI9MCBhbHQ9IiIgc3JjPSJodHRw\nczovL3N0YXRpYzIubWFpbGVybGl0ZS5jb20vZGF0YS9idWlsZGVyLzIxMTExNDEvMjAxNjA0MDgx\nMTM4NTU0MDBfNjQwXzE4OV8xXzAuanBlZyIgd2lkdGg9NjQwIGhlaWdodD0xODk+IDwvQT48L1RE\nPjwvVFI+PC9UQUJMRT48L1REPjwvVFI+PC9UQUJMRT4NCjxUQUJMRSBjbGFzcz1tYWluIGNlbGxT\ncGFjaW5nPTAgY2VsbFBhZGRpbmc9MCB3aWR0aD02NDAgYWxpZ249Y2VudGVyIGJvcmRlcj0wPg0K\nDQo8VFI+DQo8VEQ+DQo8VEFCTEUgY2xhc3M9bWFpbiBzdHlsZT0iV0lEVEg6IDY0MHB4OyBCQUNL\nR1JPVU5EOiAjZmZmZmZmIiBjZWxsU3BhY2luZz0wIGNlbGxQYWRkaW5nPTAgd2lkdGg9NjQwIGFs\naWduPWNlbnRlciBiZ0NvbG9yPSNmZmZmZmYgYm9yZGVyPTA+DQoNCjxUUj4NCjxURCBjbGFzcz1p\nbWFnZS1mdWxsIHN0eWxlPSJIRUlHSFQ6IDE3MHB4OyBXSURUSDogNjQwcHg7IFBBRERJTkctQk9U\nVE9NOiAwcHg7IFBBRERJTkctVE9QOiAwcHg7IFBBRERJTkctTEVGVDogMHB4OyBQQURESU5HLVJJ\nR0hUOiAwcHgiIGFsaWduPWNlbnRlcj4NCjxUQUJMRSBjZWxsU3BhY2luZz0wIGNlbGxQYWRkaW5n\nPTAgYWxpZ249Y2VudGVyPg0KDQo8VFI+DQo8VEQ+PEEgc3R5bGU9IkJPUkRFUi1UT1A6IG1lZGl1\nbSBub25lOyBCT1JERVItUklHSFQ6IG1lZGl1bSBub25lOyBCT1JERVItQk9UVE9NOiBtZWRpdW0g\nbm9uZTsgQk9SREVSLUxFRlQ6IG1lZGl1bSBub25lOyBESVNQTEFZOiBibG9jayIgaHJlZj0iaHR0\ncDovL2JpdC5seS8yMGRHaHV4Ij48SU1HIHN0eWxlPSJESVNQTEFZOiBibG9jayIgYm9yZGVyPTAg\nYWx0PSIiIHNyYz0iaHR0cHM6Ly9zdGF0aWMzLm1haWxlcmxpdGUuY29tL2RhdGEvYnVpbGRlci8y\nMTExMTQxLzIwMTYwNDA4MTE0NDIzMzgyXzY0MF8xNzBfMV8wLmdpZiIgd2lkdGg9NjQwIGhlaWdo\ndD0xNzA+IDwvQT48L1REPjwvVFI+PC9UQUJMRT48L1REPjwvVFI+PC9UQUJMRT48L1REPjwvVFI+\nPC9UQUJMRT4NCjxUQUJMRSBjbGFzcz1tYWluIGNlbGxTcGFjaW5nPTAgY2VsbFBhZGRpbmc9MCB3\naWR0aD02NDAgYWxpZ249Y2VudGVyIGJvcmRlcj0wPg0KDQo8VFI+DQo8VEQ+DQo8VEFCTEUgY2xh\nc3M9bWFpbiBzdHlsZT0iV0lEVEg6IDY0MHB4OyBCQUNLR1JPVU5EOiAjZmZmZmZmIiBjZWxsU3Bh\nY2luZz0wIGNlbGxQYWRkaW5nPTAgd2lkdGg9NjQwIGFsaWduPWNlbnRlciBiZ0NvbG9yPSNmZmZm\nZmYgYm9yZGVyPTA+DQoNCjxUUj4NCjxURCBjbGFzcz1pbWFnZS1mdWxsIHN0eWxlPSJIRUlHSFQ6\nIDQ2N3B4OyBXSURUSDogNjQwcHg7IFBBRERJTkctQk9UVE9NOiAwcHg7IFBBRERJTkctVE9QOiAw\ncHg7IFBBRERJTkctTEVGVDogMHB4OyBQQURESU5HLVJJR0hUOiAwcHgiIGFsaWduPWNlbnRlcj4N\nCjxUQUJMRSBjZWxsU3BhY2luZz0wIGNlbGxQYWRkaW5nPTAgYWxpZ249Y2VudGVyPg0KDQo8VFI+\nDQo8VEQ+PEEgc3R5bGU9IkJPUkRFUi1UT1A6IG1lZGl1bSBub25lOyBCT1JERVItUklHSFQ6IG1l\nZGl1bSBub25lOyBCT1JERVItQk9UVE9NOiBtZWRpdW0gbm9uZTsgQk9SREVSLUxFRlQ6IG1lZGl1\nbSBub25lOyBESVNQTEFZOiBibG9jayIgaHJlZj0iaHR0cDovL2JpdC5seS8yMGRHaHV4Ij48SU1H\nIHN0eWxlPSJESVNQTEFZOiBibG9jayIgYm9yZGVyPTAgYWx0PSIiIHNyYz0iaHR0cHM6Ly9zdGF0\naWMyLm1haWxlcmxpdGUuY29tL2RhdGEvYnVpbGRlci8yMTExMTQxLzIwMTYwNDA4MTE0NTQyNzAw\nXzY0MF80NjdfMV8wLmpwZWciIHdpZHRoPTY0MCBoZWlnaHQ9NDY3PiA8L0E+PC9URD48L1RSPjwv\nVEFCTEU+PC9URD48L1RSPjwvVEFCTEU+PC9URD48L1RSPjwvVEFCTEU+DQo8VEFCTEUgY2xhc3M9\nbWFpbiBjZWxsU3BhY2luZz0wIGNlbGxQYWRkaW5nPTAgd2lkdGg9NjQwIGFsaWduPWNlbnRlciBi\nb3JkZXI9MD4NCg0KPFRSPg0KPFREPg0KPFRBQkxFIGNsYXNzPW1haW4gc3R5bGU9IldJRFRIOiA2\nNDBweDsgQkFDS0dST1VORDogI2ZmZmZmZiIgY2VsbFNwYWNpbmc9MCBjZWxsUGFkZGluZz0wIHdp\nZHRoPTY0MCBhbGlnbj1jZW50ZXIgYmdDb2xvcj0jZmZmZmZmIGJvcmRlcj0wPg0KDQo8VFI+DQo8\nVEQgY2xhc3M9aW1hZ2UtZnVsbCBzdHlsZT0iSEVJR0hUOiAzMjlweDsgV0lEVEg6IDY0MHB4OyBQ\nQURESU5HLUJPVFRPTTogMHB4OyBQQURESU5HLVRPUDogMHB4OyBQQURESU5HLUxFRlQ6IDBweDsg\nUEFERElORy1SSUdIVDogMHB4IiBhbGlnbj1jZW50ZXI+DQo8VEFCTEUgY2VsbFNwYWNpbmc9MCBj\nZWxsUGFkZGluZz0wIGFsaWduPWNlbnRlcj4NCg0KPFRSPg0KPFREPjxBIHN0eWxlPSJCT1JERVIt\nVE9QOiBtZWRpdW0gbm9uZTsgQk9SREVSLVJJR0hUOiBtZWRpdW0gbm9uZTsgQk9SREVSLUJPVFRP\nTTogbWVkaXVtIG5vbmU7IEJPUkRFUi1MRUZUOiBtZWRpdW0gbm9uZTsgRElTUExBWTogYmxvY2si\nIGhyZWY9Imh0dHA6Ly9iaXQubHkvMjBkR2h1eCI+PElNRyBzdHlsZT0iRElTUExBWTogYmxvY2si\nIGJvcmRlcj0wIGFsdD0iIiBzcmM9Imh0dHBzOi8vc3RhdGljMy5tYWlsZXJsaXRlLmNvbS9kYXRh\nL2J1aWxkZXIvMjExMTE0MS8yMDE2MDQwODExNDYxMjc3Ml82NDBfMzI5XzFfMC5qcGVnIiB3aWR0\naD02NDAgaGVpZ2h0PTMyOT4gPC9BPjwvVEQ+PC9UUj48L1RBQkxFPjwvVEQ+PC9UUj48L1RBQkxF\nPjwvVEQ+PC9UUj48L1RBQkxFPg0KPFRBQkxFIGNsYXNzPW1haW4gY2VsbFNwYWNpbmc9MCBjZWxs\nUGFkZGluZz0wIHdpZHRoPTY0MCBhbGlnbj1jZW50ZXIgYm9yZGVyPTA+DQoNCjxUUj4NCjxURD4N\nCjxUQUJMRSBjbGFzcz1tYWluIHN0eWxlPSJXSURUSDogNjQwcHg7IEJBQ0tHUk9VTkQ6ICNmZmZm\nZmYiIGNlbGxTcGFjaW5nPTAgY2VsbFBhZGRpbmc9MCB3aWR0aD02NDAgYWxpZ249Y2VudGVyIGJn\nQ29sb3I9I2ZmZmZmZiBib3JkZXI9MD4NCg0KPFRSPg0KPFREIGNsYXNzPWltYWdlLWZ1bGwgc3R5\nbGU9IkhFSUdIVDogNjlweDsgV0lEVEg6IDY0MHB4OyBQQURESU5HLUJPVFRPTTogMHB4OyBQQURE\nSU5HLVRPUDogMHB4OyBQQURESU5HLUxFRlQ6IDBweDsgUEFERElORy1SSUdIVDogMHB4IiBhbGln\nbj1jZW50ZXI+DQo8VEFCTEUgY2VsbFNwYWNpbmc9MCBjZWxsUGFkZGluZz0wIGFsaWduPWNlbnRl\ncj4NCg0KPFRSPg0KPFREPjxJTUcgc3R5bGU9IkRJU1BMQVk6IGJsb2NrIiBib3JkZXI9MCBhbHQ9\nIiIgc3JjPSJodHRwczovL3N0YXRpYzIubWFpbGVybGl0ZS5jb20vZGF0YS9idWlsZGVyLzIxMTEx\nNDEvMjAxNjA0MDgxMTQ2Mzg1NzFfNjQwXzY5XzFfMC5qcGVnIiB3aWR0aD02NDAgaGVpZ2h0PTY5\nPiA8L1REPjwvVFI+PC9UQUJMRT48L1REPjwvVFI+PC9UQUJMRT48L1REPjwvVFI+PC9UQUJMRT4N\nCjxUQUJMRSBjbGFzcz1tYWluIGNlbGxTcGFjaW5nPTAgY2VsbFBhZGRpbmc9MCB3aWR0aD02NDAg\nYWxpZ249Y2VudGVyIGJvcmRlcj0wPg0KDQo8VFI+DQo8VEQ+DQo8VEFCTEUgY2xhc3M9bWFpbiBz\ndHlsZT0iV0lEVEg6IDY0MHB4OyBCQUNLR1JPVU5EOiAjZmZmZmZmIiBjZWxsU3BhY2luZz0wIGNl\nbGxQYWRkaW5nPTAgd2lkdGg9NjQwIGFsaWduPWNlbnRlciBiZ0NvbG9yPSNmZmZmZmYgYm9yZGVy\nPTA+DQoNCjxUUj4NCjxURCBjbGFzcz1jb250ZW50LWJ1dHRvbnMgc3R5bGU9IlBBRERJTkctQk9U\nVE9NOiAwcHg7IFBBRERJTkctVE9QOiAwcHg7IFBBRERJTkctTEVGVDogNTBweDsgUEFERElORy1S\nSUdIVDogMHB4IiB3aWR0aD01NDAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNp\nbmc9IjAiPg0KPFRBQkxFIGNsYXNzPWJ1dHRvbiBjZWxsU3BhY2luZz0wIGNlbGxQYWRkaW5nPTAg\nd2lkdGg9NTQwIGFsaWduPWxlZnQgYm9yZGVyPTA+DQoNCjxUUj4NCjxURCBjbGFzcz1idXR0b25z\nLWZ1bGwtd2lkdGggc3R5bGU9IlBBRERJTkctQk9UVE9NOiAxNXB4OyBQQURESU5HLVRPUDogMTVw\neDsgUEFERElORy1MRUZUOiAwcHg7IFBBRERJTkctUklHSFQ6IDBweCI+PEEgY2xhc3M9YnV0dG9u\nLTEgc3R5bGU9IkZPTlQtU0laRTogMTVweDsgVEVYVC1ERUNPUkFUSU9OOiBub25lOyBGT05ULUZB\nTUlMWTogQXJpYWw7IFdJRFRIOiA1MDBweDsgRk9OVC1XRUlHSFQ6IGJvbGQ7IENPTE9SOiAjZmZm\nZmZmOyBQQURESU5HLUJPVFRPTTogMTJweDsgVEVYVC1BTElHTjogY2VudGVyOyBQQURESU5HLVRP\nUDogMTJweDsgUEFERElORy1MRUZUOiAyMHB4OyBNQVJHSU46IDBweDsgRElTUExBWTogYmxvY2s7\nIExJTkUtSEVJR0hUOiAyMXB4OyBQQURESU5HLVJJR0hUOiAyMHB4OyBCQUNLR1JPVU5ELUNPTE9S\nOiAjMzQ5OGRiOyBib3JkZXItcmFkaXVzOiA2cHgiIGhyZWY9Imh0dHA6Ly9iaXQubHkvMjBkR2h1\neCI+2KfYt9mE2KfYudin2Kog2KjZiti02KrYsSDZiCDZiNix2YjYryDYqNmHINmI2KjYs9in2YrY\nqiDYtNix2qnYqiA8L0E+PC9URD48L1RSPjwvVEFCTEU+PC9URD48L1RSPjwvVEFCTEU+PC9URD48\nL1RSPjwvVEFCTEU+DQo8VEFCTEUgY2xhc3M9bWFpbiBjZWxsU3BhY2luZz0wIGNlbGxQYWRkaW5n\nPTAgd2lkdGg9NjQwIGFsaWduPWNlbnRlciBib3JkZXI9MD4NCg0KPFRSPg0KPFREPg0KPFRBQkxF\nIGNsYXNzPW1haW4gc3R5bGU9IldJRFRIOiA2NDBweDsgQkFDS0dST1VORDogI2ZmZmZmZiIgY2Vs\nbFNwYWNpbmc9MCBjZWxsUGFkZGluZz0wIHdpZHRoPTY0MCBhbGlnbj1jZW50ZXIgYmdDb2xvcj0j\nZmZmZmZmIGJvcmRlcj0wPg0KDQo8VFI+DQo8VEQgY2xhc3M9aW1hZ2UtZnVsbCBzdHlsZT0iSEVJ\nR0hUOiA1NXB4OyBXSURUSDogNjQwcHg7IFBBRERJTkctQk9UVE9NOiAwcHg7IFBBRERJTkctVE9Q\nOiAwcHg7IFBBRERJTkctTEVGVDogMHB4OyBQQURESU5HLVJJR0hUOiAwcHgiIGFsaWduPWNlbnRl\ncj4NCjxUQUJMRSBjZWxsU3BhY2luZz0wIGNlbGxQYWRkaW5nPTAgYWxpZ249Y2VudGVyPg0KDQo8\nVFI+DQo8VEQ+PEEgc3R5bGU9IkJPUkRFUi1UT1A6IG1lZGl1bSBub25lOyBCT1JERVItUklHSFQ6\nIG1lZGl1bSBub25lOyBCT1JERVItQk9UVE9NOiBtZWRpdW0gbm9uZTsgQk9SREVSLUxFRlQ6IG1l\nZGl1bSBub25lOyBESVNQTEFZOiBibG9jayIgaHJlZj0iaHR0cDovL2JpdC5seS8yMGRHaHV4Ij48\nSU1HIHN0eWxlPSJESVNQTEFZOiBibG9jayIgYm9yZGVyPTAgYWx0PSIiIHNyYz0iaHR0cHM6Ly9z\ndGF0aWMyLm1haWxlcmxpdGUuY29tL2RhdGEvYnVpbGRlci8yMTExMTQxLzIwMTYwNDA4MTE0NjQ4\nNjM5XzY0MF81NV8xXzAuanBlZyIgd2lkdGg9NjQwIGhlaWdodD01NT4gPC9BPjwvVEQ+PC9UUj48\nL1RBQkxFPjwvVEQ+PC9UUj48L1RBQkxFPjwvVEQ+PC9UUj48L1RBQkxFPg0KPFRBQkxFIGNsYXNz\nPW1haW4gc3R5bGU9IldJRFRIOiA2NDBweCIgY2VsbFNwYWNpbmc9MCBjZWxsUGFkZGluZz0wIHdp\nZHRoPTY0MCBhbGlnbj1jZW50ZXIgYm9yZGVyPTA+DQoNCjxUUj4NCjxURCBjbGFzcz1tYWluIHN0\neWxlPSJXSURUSDogNjQwcHg7IE1JTi1XSURUSDogNjQwcHgiIHdpZHRoPTY0MD4NCjxUQUJMRSBj\nbGFzcz1tYWluIHN0eWxlPSJXSURUSDogNjQwcHg7IEJBQ0tHUk9VTkQ6ICMyOTgwYjkiIGNlbGxT\ncGFjaW5nPTAgY2VsbFBhZGRpbmc9MCB3aWR0aD02NDAgYWxpZ249Y2VudGVyIGJnQ29sb3I9IzI5\nODBiOSBib3JkZXI9MD4NCg0KPFRSPg0KPFREIGNsYXNzPWZvb3RlciBzdHlsZT0iRk9OVC1TSVpF\nOiAxMXB4OyBGT05ULUZBTUlMWTogVGFob21hOyBXSURUSDogNTQwcHg7IENPTE9SOiAjZWVlZWVl\nOyBQQURESU5HLUJPVFRPTTogNXB4OyBQQURESU5HLVRPUDogNXB4OyBQQURESU5HLUxFRlQ6IDUw\ncHg7IFBBRERJTkctUklHSFQ6IDUwcHgiIGFsaWduPWNlbnRlcj4NCjxUQUJMRSBjZWxsU3BhY2lu\nZz0wIGNlbGxQYWRkaW5nPTAgd2lkdGg9IjEwMCUiIGJvcmRlcj0wPg0KDQo8VFI+DQo8VEQgc3R5\nbGU9IkZPTlQtU0laRTogMTFweDsgRk9OVC1GQU1JTFk6IFRhaG9tYTsgQ09MT1I6ICNlZWVlZWU7\nIFBBRERJTkctQk9UVE9NOiAwcHg7IFRFWFQtQUxJR046IGNlbnRlcjsgUEFERElORy1UT1A6IDBw\neDsgUEFERElORy1MRUZUOiAwcHg7IFBBRERJTkctUklHSFQ6IDBweCIgZGlyPXJ0bD4NCjxQIHN0\neWxlPSJGT05ULVNJWkU6IDEycHg7IEZPTlQtRkFNSUxZOiBUYWhvbWE7IEZPTlQtV0VJR0hUOiBi\nb2xkOyBDT0xPUjogI2ZmZmZmZjsgUEFERElORy1CT1RUT006IDBweDsgUEFERElORy1UT1A6IDBw\neDsgUEFERElORy1MRUZUOiAwcHg7IE1BUkdJTjogMHB4IDBweCA1cHg7IExJTkUtSEVJR0hUOiAx\nNTAlOyBQQURESU5HLVJJR0hUOiAwcHgiPti02LHaqdiqINix2KfZitin2YYg2YbYqiDYs9mH2KfZ\nhdmKINiu2KfYtTwvUD4NCjxQIHN0eWxlPSJQQURESU5HLUJPVFRPTTogMHB4OyBQQURESU5HLVRP\nUDogMHB4OyBQQURESU5HLUxFRlQ6IDBweDsgTUFSR0lOOiAwcHggMHB4IDVweDsgTElORS1IRUlH\nSFQ6IDE1MCU7IFBBRERJTkctUklHSFQ6IDBweCI+PEI+2KfYt9mE2KfYudin2Kog2KjZiti02KrY\nsSDYsdinINiv2LEgPEEgc3R5bGU9IkNPTE9SOiAjZWVlZWVlIiBocmVmPSJodHRwOi8vYml0Lmx5\nLzIwZEdodXgiPtmI2KjYs9in2YrYqiDZhdinPC9BPiDZhdi02KfZh9iv2Ycg2qnZhtmK2K8gPC9C\nPjwvUD4NCjxQIHN0eWxlPSJQQURESU5HLUJPVFRPTTogMHB4OyBQQURESU5HLVRPUDogMHB4OyBQ\nQURESU5HLUxFRlQ6IDBweDsgTUFSR0lOOiAwcHggMHB4IDVweDsgTElORS1IRUlHSFQ6IDE1MCU7\nIFBBRERJTkctUklHSFQ6IDBweCI+2KrZh9ix2KfZhiAtINin2YLYr9iz2YrZhyDigJMg2YbYsdiz\n2YrYr9mHINio2Ycg2YXZitmG2Yog2LPZitiq2Yog4oCTINi22YTYuSDYtNmF2KfZhNmKINio2LLY\nsdqv2LHYp9mHINin2LHYqti0IOKAkyDZvtmE2KfaqSAxMTM8L1A+DQo8UCBzdHlsZT0iUEFERElO\nRy1CT1RUT006IDBweDsgUEFERElORy1UT1A6IDBweDsgUEFERElORy1MRUZUOiAwcHg7IE1BUkdJ\nTjogMHB4IDBweCA1cHg7IExJTkUtSEVJR0hUOiAxNTAlOyBQQURESU5HLVJJR0hUOiAwcHgiPtiq\n2YfYsdin2YYtINiuINiz2YfYsdmI2LHYr9mKINi02YXYp9mE2YogLSDYriDYp9io2YYg2YrZhdmK\n2YYgLSDYriDYs9ix2YjYtCAtINm+2YTYp9qpIDIwPC9QPg0KPFAgc3R5bGU9IlBBRERJTkctQk9U\nVE9NOiAwcHg7IFBBRERJTkctVE9QOiAwcHg7IFBBRERJTkctTEVGVDogMHB4OyBNQVJHSU46IDBw\neCAwcHggNXB4OyBMSU5FLUhFSUdIVDogMTUwJTsgUEFERElORy1SSUdIVDogMHB4Ij7Yp9i12YHZ\nh9in2YYgLSDahtmH2KfYsdio2KfYuiDYqNin2YTYpyAtINmF2KzYqtmF2Lkg2b7Yp9ix2LPZitin\n2YYgLSDYt9io2YLZhyAzIC0g2YjYp9it2K8gNTExPC9QPg0KPFAgc3R5bGU9IlBBRERJTkctQk9U\nVE9NOiAwcHg7IFBBRERJTkctVE9QOiAwcHg7IFBBRERJTkctTEVGVDogMHB4OyBNQVJHSU46IDBw\neCAwcHggNXB4OyBMSU5FLUhFSUdIVDogMTUwJTsgUEFERElORy1SSUdIVDogMHB4Ij7ZhNmG2K/Z\nhtiMINiu2YrYp9io2KfZhiDZiNmG2YTYp9qp2Iwg2LTZhdin2LHZhyAyMC0yMiDaqdivINm+2LPY\nqtmKJm5ic3A7IE4xIDdHVTwvUD4NCjxQIHN0eWxlPSJQQURESU5HLUJPVFRPTTogMHB4OyBQQURE\nSU5HLVRPUDogMHB4OyBQQURESU5HLUxFRlQ6IDBweDsgTUFSR0lOOiAwcHggMHB4IDVweDsgTElO\nRS1IRUlHSFQ6IDE1MCU7IFBBRERJTkctUklHSFQ6IDBweCI+MDIxLTI4NDI4MjwvUD48L1REPjwv\nVFI+DQo8VFI+DQo8VEQgaGVpZ2h0PTEwIHdpZHRoPSIxMDAlIj48L1REPjwvVFI+PC9UQUJMRT4N\nCjxUQUJMRSBjZWxsU3BhY2luZz0wIGNlbGxQYWRkaW5nPTAgd2lkdGg9IjEwMCUiIGJvcmRlcj0w\nPg0KDQo8VFI+DQo8VEQgc3R5bGU9IkZPTlQtU0laRTogMTFweDsgRk9OVC1GQU1JTFk6IFRhaG9t\nYTsgQ09MT1I6ICNlZWVlZWU7IFBBRERJTkctQk9UVE9NOiAwcHg7IFRFWFQtQUxJR046IGNlbnRl\ncjsgUEFERElORy1UT1A6IDBweDsgUEFERElORy1MRUZUOiAwcHg7IFBBRERJTkctUklHSFQ6IDBw\neCIgZGlyPXJ0bD4NCjxQIHN0eWxlPSJQQURESU5HLUJPVFRPTTogMHB4OyBQQURESU5HLVRPUDog\nMHB4OyBQQURESU5HLUxFRlQ6IDBweDsgTUFSR0lOOiAwcHg7IExJTkUtSEVJR0hUOiAxNTAlOyBQ\nQURESU5HLVJJR0hUOiAwcHgiPiZuYnNwOzwvUD48L1REPjwvVFI+PC9UQUJMRT48L1REPjwvVFI+\nPC9UQUJMRT48L1REPjwvVFI+PC9UQUJMRT4NCjxUQUJMRSBjbGFzcz1tYWluIHN0eWxlPSJXSURU\nSDogNjQwcHgiIGNlbGxTcGFjaW5nPTAgY2VsbFBhZGRpbmc9MCB3aWR0aD02NDAgYWxpZ249Y2Vu\ndGVyIGJvcmRlcj0wPg0KDQo8VFI+DQo8VEQgY2xhc3M9bWFpbiBoZWlnaHQ9MzAgd2lkdGg9NjQw\nPjwvVEQ+PC9UUj48L1RBQkxFPg0KPFRBQkxFIGNsYXNzPW1haW4gc3R5bGU9IldJRFRIOiA2NDBw\neCIgY2VsbFNwYWNpbmc9MCBjZWxsUGFkZGluZz0wIHdpZHRoPTY0MCBhbGlnbj1jZW50ZXIgYm9y\nZGVyPTA+DQoNCjxUUj4NCjxURCBhbGlnbj1jZW50ZXI+PEEgc3R5bGU9IkJPUkRFUi1UT1A6IG1l\nZGl1bSBub25lOyBCT1JERVItUklHSFQ6IG1lZGl1bSBub25lOyBCT1JERVItQk9UVE9NOiBtZWRp\ndW0gbm9uZTsgTUFSR0lOOiAwcHg7IEJPUkRFUi1MRUZUOiBtZWRpdW0gbm9uZTsgRElTUExBWTog\naW5saW5lLWJsb2NrIiBocmVmPSJodHRwczovL3d3dy5tYWlsZXJsaXRlLmNvbS8iPjxJTUcgYm9y\nZGVyPTAgYWx0PU1haWxlckxpdGUgc3JjPSJodHRwczovL3N0YXRpYzMubWFpbGVybGl0ZS5jb20v\naW1hZ2VzL2J1aWxkZXIvcG93ZXJlZC1ieS1tYWlsZXJsaXRlLnBuZyI+PC9BPiA8L1REPjwvVFI+\nDQo8VFI+DQo8VEQgY2xhc3M9bWFpbiBoZWlnaHQ9MzAgd2lkdGg9NjQwPjwvVEQ+PC9UUj48L1RB\nQkxFPjwvVEQ+PC9UUj48L1RBQkxFPjwvUD48L0JPRFk+PC9IVE1MPg==\n\n------=_NextPart_714_9218_61661523.68107709--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_57.argentina:2,",
    "content": "Return-Path: <lennaenglishh@soswy.state.wy.us>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id A4C3020AF44B7\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 18:35:59 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 9589833181F7; Sun, 10 Apr 2016 18:35:59 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 8849833181FC; Sun, 10 Apr 2016 18:35:59 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.5 required=5.0 tests=BAYES_99,DATE_IN_FUTURE_03_06,\n\tFSL_HELO_NON_FQDN_1,HELO_NO_DOMAIN,HTML_MESSAGE,MIME_HTML_ONLY,\n\tRCVD_IN_BRBL_LASTEXT,RDNS_NONE autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 FSL_HELO_NON_FQDN_1 FSL_HELO_NON_FQDN_1\n\t*  3.0 DATE_IN_FUTURE_03_06 Date: is 3 to 6 hours after Received: date\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [137.117.98.186 listed in bb.barracudacentral.org]\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 HELO_NO_DOMAIN Relay reports its domain incorrectly\nX-Spam-DCC: : \nReceived: from philipslifeline (unknown [137.117.98.186])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id 55E3E33181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 18:35:57 +0200 (CEST)\nReceived: from lennaenglishh by holderiq.philipslifeline with local (Exim 4.14)\n\tid o90Ko2-XeIgfU-yS\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 21:37:17 +0000\nTo: \"laurent\" <dev@caliopen.local>\nSubject: I typed 2 messages to you and there was no any answer write 1.518.289.46.83\nMessage-Id: <o90Ko2-XeIgfU-yS@holderiq.philipslifeline>\nFrom: \"Denna\" <lennaenglishh@soswy.state.wy.us>\nDate: Sun, 10 Apr 2016 21:37:17 +0000\nMime-Version: 1.0\nContent-Type: text/html\nContent-Transfer-Encoding: 8bit\nMime-Version: 1.0\nContent-Type: text/html\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: I typed 2 messages to you and there was no any answer write 1.518.289.46.83\nContent-Length: 353\nLines: 4\n\n<html><body>Hey man, I'm Bailey. I like sleeping and gardening.<br>\nNow I'm lonesome and trying to find <a href=\"http://ilerdagua.com/wp-includes/js/plupload/\">quick bang</a>.<br>\nLet's have a date and have a quick hookup.<br>\n<a href=\"http://ilerdagua.com/wp-includes/js/plupload/\">Contact me there</a> or you can text me 1-239-719-25-35</body></html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_59.argentina:2,",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 80F9D20AEE0FE\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 18:12:33 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 7268D33181F7; Sun, 10 Apr 2016 18:12:33 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 64BA033181FC; Sun, 10 Apr 2016 18:12:33 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********\nX-Spam-Status: Yes, score=8.1 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_06_12,HTML_FONT_FACE_BAD,HTML_IMAGE_RATIO_04,HTML_MESSAGE,\n\tMPART_ALT_DIFF,NORMAL_HTTP_TO_IP,RCVD_IN_CSS,RDNS_NONE,T_FILL_THIS_FORM_SHORT,\n\tT_KHOP_FOREIGN_CLICK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [63.251.255.133 listed in zen.spamhaus.org]\n\t*  1.5 DATE_IN_PAST_06_12 Date: is 6 to 12 hours before Received: date\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  1.0 HTML_FONT_FACE_BAD BODY: HTML font face is not a word\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 T_FILL_THIS_FORM_SHORT Fill in a short form with personal\n\t*      information\n\t* -1.0 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server101.offerprivee.in (unknown [63.251.255.133])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 44B8D33181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 18:12:29 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: =?UTF-8?B?LTcwJSBzdXIgdm90cmUgc8Opam91ciDDqXTDqSAhIC8gVm90cmUgb2ZmcmU=?=\nMessage-ID: <25c569b9297ab325f44acf2e7a8768db@162.244.67.108>\nDate: Sun, 10 Apr 2016 01:36:16 -0500\nFrom: \"Votre offre Camping \" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 164,165\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11253>\nX-Mailer-SID: 11253\nX-Mailer-Sent-By: 2\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_b3b9a86e9b0c77c8d5ec5d677744bb85\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?UTF-8?B?LTcwJSBzdXIgdm90cmUgc8Opam91ciDDqXTDqSAhIC8gVm90cmUgb2ZmcmU=?=\nContent-Length: 16526\nLines: 348\n\n--b1_b3b9a86e9b0c77c8d5ec5d677744bb85\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11253&L=165&N=3675\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11253\n\n--b1_b3b9a86e9b0c77c8d5ec5d677744bb85\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<title>&nbsp;</title>\n<base target=\"_blank\"></base>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11253&L=165&N=3675\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<center>\n<link\nhref=\"https://fonts.googleapis.com/css?family=Raleway:400,900italic,900,800italic,800,700italic,700,600italic,600,500italic,500,400italic,300italic,300,200italic,200,100italic,100\"\nrel=\"stylesheet\" type=\"text/css\" />\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td width=\"600\" height=\"34\" bgcolor=\"#fff\"\nstyle=\"background-color:#fff;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:18px; color:#bc0d1a;\ntext-decoration:none;\"> <font color=\"#000\" face=\"'Raleway', Arial,\nHelvetica, Verdana, sans-serif\" style=\"font-size:12px;\"> <span\nstyle=\"text-decoration: underline;\"> Vos vacances jusqu'&agrave;\n-70%</span> </font> </a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img02\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img02.jpg\"\nwidth=\"333\" height=\"110\" border=\"0\" alt=\"VACANCES CAMPINGS\" /></a></td>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img03\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img03.jpg\"\nwidth=\"267\" height=\"110\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td width=\"506\" height=\"46\" bgcolor=\"#eee\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img04.jpg\"\nstyle=\"text-align:center;\"><font face=\"'Raleway', Arial, sans-serif\"\ncolor=\"#fff\" style=\"font-size:20px;\"> &Eacute;lu <font face=\"'Raleway',\nArial, sans-serif\" color=\"#fff\" style=\"font-size:22px;\"><strong>meilleur\nsite</strong></font> de r&eacute;servation de <font face=\"'Raleway', Arial,\nsans-serif\" color=\"#fff\"\nstyle=\"font-size:22px;\"><strong>camping</strong></font> </font></td>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img05\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img05.jpg\"\nwidth=\"95\" height=\"46\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img06\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img06.jpg\"\nwidth=\"600\" height=\"38\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img07\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img07.jpg\"\nwidth=\"342\" height=\"34\" border=\"0\" /></a></td>\n<td width=\"247\" height=\"34\" bgcolor=\"#eee\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img08.jpg\"\nstyle=\"text-align:center;\"><font face=\"'Raleway', Arial, sans-serif\"\ncolor=\"#fff\" style=\"font-size:26px;\"> <strong>de 1000</strong> <font\nface=\"'Raleway', Arial, sans-serif\" color=\"#000\"\nstyle=\"font-size:22px;\">campings</font> </font></td>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img09\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img09.gif\"\nwidth=\"11\" height=\"34\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img10\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img10.jpg\"\nwidth=\"300\" height=\"34\" border=\"0\" /></a></td>\n<td width=\"242\" height=\"34\" bgcolor=\"#eee\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img11.jpg\"\nstyle=\"text-align:center;\"><font face=\"'Raleway', Arial, sans-serif\"\ncolor=\"#000\" style=\"font-size:19px;\">en <strong>France</strong> et en\n<strong>Europe</strong> </font></td>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img12\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img12.jpg\"\nwidth=\"58\" height=\"34\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img13\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img13.jpg\"\nwidth=\"600\" height=\"22\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><img\nstyle=\"display:block;\" id=\"img14\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img14.jpg\"\nwidth=\"21\" height=\"31\" /></td>\n<td width=\"93\" height=\"31\" bgcolor=\"#eee\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img15.jpg\"\nstyle=\"text-align:center;\"><font face=\"'Raleway', Arial, sans-serif\"\ncolor=\"#fff\"\nstyle=\"font-size:22px;\"><strong>Jusqu'&agrave;</strong></font></td>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img16\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img16.jpg\"\nwidth=\"486\" height=\"31\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img17\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img17.jpg\"\nwidth=\"21\" height=\"79\" border=\"0\" /></a></td>\n<td width=\"200\" height=\"79\" bgcolor=\"#eee\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img18.jpg\"\nstyle=\"text-align:center;\"><font face=\"'Raleway', Arial, sans-serif\"\ncolor=\"#fff\" style=\"font-size:67px;\"><strong>-70<font face=\"'Raleway',\nArial, sans-serif\" color=\"#fff\"\nstyle=\"font-size:55px;\">%</font></strong></font></td>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img19\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img19.gif\"\nwidth=\"379\" height=\"79\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:15px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img20\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img20.jpg\"\nwidth=\"600\" height=\"31\" border=\"0\" alt=\"En cliquant ici j'accepte\n&ecirc;tre contact&eacute; par &#10;                        Vacances\nCampings\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img21\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img21.jpg\"\nwidth=\"188\" height=\"47\" border=\"0\" /></a></td>\n<td width=\"116\" height=\"47\" bgcolor=\"#eee\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img22.jpg\"\nstyle=\"text-align:center;line-height:12px;\"><font face=\"'Raleway', Arial,\nsans-serif\" color=\"#fff\" style=\"font-size:13px;\"><strong>Une semaine<br />\n&agrave; partir de</strong></font></td>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img23\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img23.jpg\"\nwidth=\"296\" height=\"47\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img24\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img24.jpg\"\nwidth=\"188\" height=\"47\" border=\"0\" /></a></td>\n<td width=\"126\" height=\"47\" bgcolor=\"#eee\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img25.jpg\"\nstyle=\"text-align:center;\"><font face=\"'Raleway', Arial, sans-serif\"\ncolor=\"#fff\"\nstyle=\"font-size:38px;\"><strong>129&euro;</strong></font></td>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img26\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img26.jpg\"\nwidth=\"286\" height=\"47\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img27\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img27.jpg\"\nwidth=\"600\" height=\"143\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#eee\" style=\"text-align:center;background-color:#eee;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11253&L=14839&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:35px; color:#000;\ntext-decoration:none;\"> <img style=\"display:block;\" id=\"img28\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/cdv/images/img28.jpg\"\nwidth=\"600\" height=\"254\" border=\"0\" alt=\"VITE ! Du 1er au 10 avril\nseulement\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td width=\"600\" height=\"347\" bgcolor=\"#fff\"\nstyle=\"text-align:justify;background-color:#fff;padding20px;\"><font\ncolor=\"#000\" style=\"font-size:11px;font-family:'Raleway' ,Arial, Verdana,\navec-serif;\"> <strong>Vacances Campings : r&eacute;servez votre location\nmobil home en camping.</strong><br /> Sp&eacute;cialiste de\nl'h&eacute;bergement touristique en camping, nous proposons des locations\nde vacances &agrave; des tarifs n&eacute;goci&eacute;s.\n B&eacute;n&eacute;ficiez du plus grand choix de campings en France pour\nvos prochaines vacances, avec une s&eacute;lection des meilleures offres\ndans chaque r&eacute;gion : campings en Bretagne, campings en Aquitaine,\ncampings en Languedoc-Roussillon, campings en Provence Alpes C&ocirc;te\nd'Azur, campings en Corse...                      <br /> <strong> Tarifs\nn&eacute;goci&eacute;s par nos &eacute;quipes</strong> <br /> Les offres\nque vous consultez sur Vacances-Campings vous sont le plus souvent\npropos&eacute;es avec des tarifs et des promotions exclusifs.<br />\n<strong>H&eacute;bergements s&eacute;lectionn&eacute;s</strong><br /> Quel\nque soit le type d'h&eacute;bergement que vous choisissez sur\nVacances-Campings : mobil-homes, chalets, bungalows, cottages, tous les\nh&eacute;bergements ont &eacute;t&eacute; s&eacute;lectionn&eacute;s par\nnos &eacute;quipes.<br /> <strong>Appels au 02 44 71 86 20 non\nsurtax&eacute;s</strong><br /> Notre centrale de r&eacute;servation est\nsitu&eacute;e en r&eacute;gion parisienne. 15 conseillers\nsp&eacute;cialistes sont &agrave; votre disposition, et prendront le temps\nde vous renseigner, au prix d'une communication normale.<br />\n<strong>Conseillers sp&eacute;cialistes</strong><br /> Les conseillers\nsp&eacute;cialistes de Vacances-Campings connaissent parfaitement tous les\ntypes d'h&eacute;bergement qui vous sont propos&eacute;s : mobil-homes,\nchalets, bungalows, Dans le cas o&ugrave; vous avez des demandes\nparticuli&egrave;res (h&eacute;bergement pour une grande famille, animaux\npr&eacute;sents, etc.), ils pourront prendre contact avec le responsable de\nvotre lieu de r&eacute;sidence, et vous proposer un h&eacute;bergement\nadapt&eacute; &agrave; votre situation.<br /> <strong>30 % d'acompte\n&agrave; la r&eacute;servation</strong><br /> Si vous r&eacute;servez sur\nVacances-Campings &agrave; plus de 3 mois de votre date d'arriv&eacute;e,\nvous ne payez que 30 % d'acompte sur votre r&eacute;servation.\n</font></td>\n</tr>\n</tbody>\n</table>\n<img src=\"http://impfr.tradedoubler.com/imp?type(inv)g(23235172)a(2814831)\"\nborder=\"0\" /> </center>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11253\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n</div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=11664742&L=165&N=11253&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_b3b9a86e9b0c77c8d5ec5d677744bb85--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_61.argentina:2,",
    "content": "Return-Path: <posts@futurfast.eu>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id B431220A7CAE2\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 17:28:48 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid A7E4033181F7; Sun, 10 Apr 2016 17:28:48 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 9B8A433181FC; Sun, 10 Apr 2016 17:28:48 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: **********\nX-Spam-Status: Yes, score=10.8 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_12_24,DKIM_SIGNED,HTML_MESSAGE,MPART_ALT_DIFF,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,T_DKIM_INVALID,\n\tURIBL_BLACK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: effiliation.com]\n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 DATE_IN_PAST_12_24 Date: is 12 to 24 hours before Received: date\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.0 T_DKIM_INVALID DKIM-Signature header exists but is not valid\n\t*  0.3 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from mail.futurfast.eu (mail.futurfast.eu [176.20.27.58])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id CEA7633181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 17:28:45 +0200 (CEST)\nDKIM-Signature: a=rsa-sha1; t=1460302128; x=1460906928; s=futurfastkey; d=futurfast.eu; c=relaxed/relaxed; q=dns; h=From:Reply-To:Subject:Date:Message-ID:To:MIME-Version:Content-Type:Content-Transfer-Encoding;\n   b=eV808uTJG+Vh15Cq2CAn5mssF2OLrr5HekIxy28UTjDqSh09FIsgYXSE5eit6X2HzjpX39vh0I4K5Hf7jCUaCjV3iaSRluPp9hYo8+PVPZI8Yp4/C5DOJp5dAyfBe5AEuSwge15hCOSYyLWAE+uQkENi/cIuyuOA69K1+jhVImI=\nReceived: from www.supprabyte.co.uk\n        by mail.futurstats.eu (IceWarp 9.3.1) with SMTP id YTM40748\n        for <dev@caliopen.local>; Sun, 10 Apr 2016 17:28:48 +0200\nTo: dev@caliopen.local\nSubject: =?UTF-8?B?RMOpY291dnJleiBsZXMgc29sdXRpb25zIFRQRS9QTUU=?=\nMessage-ID: <d77496e853567b66eba764b4149aea3c@www.supprabyte.co.uk>\nDate: Sun, 10 Apr 2016 03:08:09 +0200\nFrom: \"Cheque Dejeuner\" <sfp@futurfast.eu>\nReply-To: sfp@futurfast.eu\nMIME-Version: 1.0\nX-Mailer-LID: 326\nList-Unsubscribe: <http://www.supprabyte.co.uk/unsubscribe.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&L=326&N=2340>\nX-Mailer-SID: 2340\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_b54aeac886a98845130d334794a399d6\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?UTF-8?B?RMOpY291dnJleiBsZXMgc29sdXRpb25zIFRQRS9QTUU=?=\nContent-Length: 27051\nLines: 915\n\n--b1_b54aeac886a98845130d334794a399d6\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://www.supprabyte.co.uk/display.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&S=2340&L=326&N=210\n\n\nTo stop receiving these\nemails:http://www.supprabyte.co.uk/unsubscribe.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&L=326&N=2340\n\n--b1_b54aeac886a98845130d334794a399d6\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<div>\n<div><!-- NAME: 1:2:1 COLUMN --><!--[if gte mso 15]> <xml>\n<o:OfficeDocumentSettings> <o:AllowPNG />\n<o:PixelsPerInch>96</o:PixelsPerInch> </o:OfficeDocumentSettings> </xml>\n<![endif]-->\n<meta charset=\"UTF-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Pour carburer jusqu'en fin de journ&eacute;e, il faut une pause\nd&eacute;jeuner !</title>\n<style type=\"text/css\"><!--\n        p {\n            margin: 10px 0;\n            padding: 0;\n        }\n\n        table {\n            border-collapse: collapse;\n        }\n\n        h1,\n        h2,\n        h3,\n        h4,\n        h5,\n        h6 {\n            display: block;\n            margin: 0;\n            padding: 0;\n        }\n\n        img,\n        a img {\n            border: 0;\n            height: auto;\n            outline: none;\n            text-decoration: none;\n        }\n\n        body,\n        #bodyTable,\n        #bodyCell {\n            height: 100%;\n            margin: 0;\n            padding: 0;\n            width: 100%;\n        }\n\n        #outlook a {\n            padding: 0;\n        }\n\n        img {\n            -ms-interpolation-mode: bicubic;\n        }\n\n        table {\n            mso-table-lspace: 0pt;\n            mso-table-rspace: 0pt;\n        }\n\n        .ReadMsgBody {\n            width: 100%;\n        }\n\n        .ExternalClass {\n            width: 100%;\n        }\n\n        p,\n        a,\n        li,\n        td,\n        blockquote {\n            mso-line-height-rule: exactly;\n        }\n\n        a[href^=tel],\n        a[href^=sms] {\n            color: inherit;\n            cursor: default;\n            text-decoration: none;\n        }\n\n        p,\n        a,\n        li,\n        td,\n        body,\n        table,\n        blockquote {\n            -ms-text-size-adjust: 100%;\n            -webkit-text-size-adjust: 100%;\n        }\n\n        .ExternalClass,\n        .ExternalClass p,\n        .ExternalClass td,\n        .ExternalClass div,\n        .ExternalClass span,\n        .ExternalClass font {\n            line-height: 100%;\n        }\n\n        a[x-apple-data-detectors] {\n            color: inherit !important;\n            text-decoration: none !important;\n            font-size: inherit !important;\n            font-family: inherit !important;\n            font-weight: inherit !important;\n            line-height: inherit !important;\n        }\n\n        #bodyCell {\n            padding: 20px;\n            padding-top: 10px;\n            padding-bottom: 10px;\n        }\n\n        .templateContainer {\n            max-width: 600px !important;\n        }\n\n        a.mcnButton {\n            display: block;\n        }\n\n        .mcnImage {\n            vertical-align: bottom;\n        }\n\n        .mcnTextContent {\n            word-break: break-word;\n        }\n\n        .mcnTextContent img {\n            height: auto !important;\n        }\n\n        .mcnDividerBlock {\n            table-layout: fixed !important;\n        }\n\n        body,\n        #bodyTable {\n            background-color: #ffffff;\n        }\n\n        #bodyCell {\n            border-top: 0;\n        }\n\n        .templateContainer {\n            border: 0;\n        }\n\n        h1 {\n            color: #4f5d5b;\n            font-family: Helvetica;\n            font-size: 26px;\n            font-style: normal;\n            font-weight: bold;\n            line-height: 125%;\n            letter-spacing: normal;\n            text-align: left;\n        }\n\n        h2 {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 22px;\n            font-style: normal;\n            font-weight: bold;\n            line-height: 125%;\n            letter-spacing: normal;\n            text-align: left;\n        }\n\n        h3 {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 20px;\n            font-style: normal;\n            font-weight: bold;\n            line-height: 125%;\n            letter-spacing: normal;\n            text-align: left;\n        }\n\n        h4 {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 18px;\n            font-style: normal;\n            font-weight: bold;\n            line-height: 125%;\n            letter-spacing: normal;\n            text-align: left;\n        }\n\n        #templatePreheader {\n            background-color: #ffffff;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 0px;\n            padding-bottom: 0px;\n        }\n\n        #templatePreheader .mcnTextContent,\n        #templatePreheader .mcnTextContent p {\n            color: #4f5d5b;\n            font-family: Helvetica;\n            font-size: 12px;\n            line-height: 100%;\n            text-align: left;\n        }\n\n        #templatePreheader .mcnTextContent a,\n        #templatePreheader .mcnTextContent p a {\n            color: #656565;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        #templateHeader {\n            background-color: #FFFFFF;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 9px;\n            padding-bottom: 0;\n        }\n\n        #templateButton {\n            background-color: #fafafa;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 15px;\n            padding-bottom: 30px;\n            padding-left: 30px;\n            padding-right: 30px;\n        }\n\n        #templateHeader .mcnTextContent,\n        #templateHeader .mcnTextContent p {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 16px;\n            line-height: 150%;\n            text-align: left;\n        }\n\n        #templateHeader .mcnTextContent a,\n        #templateHeader .mcnTextContent p a {\n            color: #2BAADF;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        #templateUpperBody {\n            background-color: #ffffff;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 0;\n            padding-bottom: 9px;\n            padding-right: 10px;\n        }\n\n        #templateUpperBody .mcnTextContent,\n        #templateUpperBody .mcnTextContent p {\n            color: #202020;\n            font-family: Helvetica;\n            font-size: 16px;\n            line-height: 150%;\n            text-align: left;\n        }\n\n        #templateUpperBody .mcnTextContent a,\n        #templateUpperBody .mcnTextContent p a {\n            color: #2BAADF;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        #templateColumns {\n            background-color: #fafafa;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 0;\n            padding-bottom: 0;\n        }\n\n        #templateColumns .columnContainer .mcnTextContent,\n        #templateColumns .columnContainer .mcnTextContent p {\n            color: #4f5d5b;\n            font-family: Helvetica;\n            font-size: 16px;\n            line-height: 150%;\n            text-align: left;\n        }\n\n        #templateColumns .columnContainer .mcnTextContent a,\n        #templateColumns .columnContainer .mcnTextContent p a {\n            color: #f59100;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        #templateLowerBody {\n            background-color: #FFFFFF;\n            border-top: 0;\n            border-bottom: 1px none #f3f3f3;\n            padding-top: 0px;\n            padding-bottom: 0px;\n        }\n\n        #templateLowerBody .mcnTextContent,\n        #templateLowerBody .mcnTextContent p {\n            color: #4f5d5b;\n            font-family: Helvetica;\n            font-size: 16px;\n            line-height: 150%;\n            text-align: left;\n        }\n\n        #templateLowerBody .mcnTextContent a,\n        #templateLowerBody .mcnTextContent p a {\n            color: #f59100;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        #templateFooter {\n            background-color: #ffffff;\n            border-top: 0;\n            border-bottom: 0;\n            padding-top: 0px;\n            padding-bottom: 0px;\n        }\n\n        #templateFooter .mcnTextContent,\n        #templateFooter .mcnTextContent p {\n            color: #4f5d5b;\n            font-family: Helvetica;\n            font-size: 12px;\n            line-height: 130%;\n            text-align: center;\n        }\n\n        #templateFooter .mcnTextContent a,\n        #templateFooter .mcnTextContent p a {\n            color: #4f5d5b;\n            font-weight: normal;\n            text-decoration: underline;\n        }\n\n        @media only screen and (min-width:768px) {\n            .templateContainer {\n                width: 600px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            body,\n            table,\n            td,\n            p,\n            a,\n            li,\n            blockquote {\n                -webkit-text-size-adjust: none !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            body {\n                width: 100% !important;\n                min-width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #bodyCell {\n                padding-top: 10px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .columnWrapper {\n                max-width: 100% !important;\n                width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImage {\n                width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnCaptionTopContent,\n            .mcnCaptionBottomContent,\n            .mcnTextContentContainer,\n            .mcnBoxedTextContentContainer,\n            .mcnImageGroupContentContainer,\n            .mcnCaptionLeftTextContentContainer,\n            .mcnCaptionRightTextContentContainer,\n            .mcnCaptionLeftImageContentContainer,\n            .mcnCaptionRightImageContentContainer,\n            .mcnImageCardLeftTextContentContainer,\n            .mcnImageCardRightTextContentContainer {\n                max-width: 100% !important;\n                width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnBoxedTextContentContainer {\n                min-width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageGroupContent {\n                padding: 9px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnCaptionLeftContentOuter .mcnTextContent,\n            .mcnCaptionRightContentOuter .mcnTextContent {\n                padding-top: 9px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageCardTopImageContent,\n            .mcnCaptionBlockInner .mcnCaptionTopContent:last-child\nmcnTextContent {\n                padding-top: 18px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageCardBottomImageContent {\n                padding-bottom: 9px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageGroupBlockInner {\n                padding-top: 0 !important;\n                padding-bottom: 0 !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageGroupBlockOuter {\n                padding-top: 9px !important;\n                padding-bottom: 9px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnTextContent,\n            .mcnBoxedTextContentColumn {\n                padding-right: 18px !important;\n                padding-left: 18px !important;\n            }\n            .mcnTextContent#advantage,\n            .mcnTextContent#heroText {\n                padding-top: 18px!important;\n                padding-bottom: 18px!important;\n                text-align: center!important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageCardRightImageContent {\n                padding-right: 18px !important;\n                padding-bottom: 0px !important;\n                padding-left: 18px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnImageCardLeftImageContent {\n                padding-right: 30px !important;\n                padding-bottom: 30px !important;\n                padding-left: 30px !important;\n                padding-top: 10px !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcpreview-image-uploader {\n                display: none !important;\n                width: 100% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            h1 {\n                font-size: 22px !important;\n                line-height: 125% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            h2 {\n                font-size: 20px !important;\n                line-height: 125% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            h3 {\n                font-size: 18px !important;\n                line-height: 125% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            h4 {\n                font-size: 16px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            .mcnBoxedTextContentContainer .mcnTextContent,\n            .mcnBoxedTextContentContainer .mcnTextContent p {\n                font-size: 14px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templatePreheader {\n                display: block !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templatePreheader .mcnTextContent,\n            #templatePreheader .mcnTextContent p {\n                font-size: 14px !important;\n                line-height: 130% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templateHeader .mcnTextContent,\n            #templateHeader .mcnTextContent p {\n                font-size: 16px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templateUpperBody .mcnTextContent,\n            #templateUpperBody .mcnTextContent p {\n                font-size: 16px !important;\n                line-height: 150% !important;\n            }\n\n            #templateUpperBody {\n                padding-right: 6px!important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templateColumns .columnContainer .mcnTextContent,\n            #templateColumns .columnContainer .mcnTextContent p {\n                font-size: 16px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templateLowerBody .mcnTextContent,\n            #templateLowerBody .mcnTextContent p {\n                font-size: 16px !important;\n                line-height: 150% !important;\n            }\n        }\n\n        @media only screen and (max-width: 480px) {\n            #templateFooter .mcnTextContent,\n            #templateFooter .mcnTextContent p {\n                font-size: 14px !important;\n                line-height: 130% !important;\n            }\n        }\n--></style>\n<center>\n<table style=\"width: 100%; height: 100%;\" id=\"bodyTable\" align=\"center\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td id=\"bodyCell\" align=\"center\" valign=\"top\"><!-- BEGIN TEMPLATE // -->\n<!--[if gte mso 9]>\n<table align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\"\nwidth=\"600\" style=\"width:600px;\">\n<tr>\n<td align=\"center\" valign=\"top\" width=\"600\"\nstyle=\"width:600px;\"><![endif]-->\n<table style=\"width: 100%;\" class=\"templateContainer\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td id=\"templatePreheader\" valign=\"top\">\n<table class=\"mcnTextBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnTextBlockOuter\">\n<tr>\n<td class=\"mcnTextBlockInner\" valign=\"top\">\n<table style=\"min-width: 100%; width: 100%;\"\nclass=\"mcnTextContentContainer\" align=\"left\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent\" style=\"padding-top:9px; padding-right: 0px;\npadding-bottom: 9px; padding-left: 0px;\" valign=\"top\">\n<div style=\"text-align: center;color:#4f5d5b;line-height:140%;\">Pour\ncarburer jusqu'en fin de journ&eacute;e, il faut une pause d&eacute;jeuner\n!<br />Si vous n'arrivez pas &agrave; lire cet e-mail, <a\nhref=\"http://www.supprabyte.co.uk/display.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&S=2340&L=326&N=210\"\nstyle=\"color: #54b7e9;\" target=\"_blank\">suivez ce lien</a>.</div>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td id=\"templateHeader\" valign=\"top\">\n<table class=\"mcnImageBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnImageBlockOuter\">\n<tr>\n<td style=\"padding:0px\" class=\"mcnImageBlockInner\" valign=\"top\">\n<table class=\"mcnImageContentContainer\" style=\"min-width: 100%; width:\n100%;\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageContent\" style=\"padding-right: 0px; padding-left: 0px;\npadding-top: 0; padding-bottom: 0; text-align:center;\" valign=\"top\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2340&L=1003&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://hosting.effiliation.com/Chequedejeuner/chequedejkitdecembre15/GroupeUp-Header.png\"\nalt=\"Groupe Up - Les solutions TPE/PME\" style=\"max-width:1200px;\npadding-bottom: 0; display: inline !important; vertical-align:\nbottom;background-color:#d1242a;\" class=\"mcnImage\" align=\"middle\"\nwidth=\"600\" /></a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td id=\"templateUpperBody\" valign=\"top\">\n<table style=\"width: 100%;\" class=\"mcnImageCardBlock\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnImageCardBlockOuter\">\n<tr>\n<td class=\"mcnImageCardBlockInner\" style=\"padding-top:0px;\npadding-right:0px; padding-bottom:0px; padding-left:0px;\" valign=\"top\">\n<table style=\"width: 100%;\" class=\"mcnImageCardLeftContentOuter\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageCardLeftContentInner\" style=\"padding:\n0px;background-color: #D1242A;\" align=\"center\" valign=\"top\">\n<table style=\"width: 360px;\" class=\"mcnImageCardLeftTextContentContainer\"\nalign=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent\" id=\"heroText\" style=\"padding-left:\n30px;padding-top: 22px;padding-bottom: 25px;color: #ffffff;font-family:\nHelvetica;font-size: 14px;font-weight: normal;line-height: 150%;text-align:\ncenter;\" valign=\"top\">\n<div style=\"text-align: center;\"><span style=\"font-size:22px\"><b><span\nstyle=\"font-family:arial,helvetica neue,helvetica,sans-serif;line-height:\n130%;font-size:22px;\">Pour carburer<br /> jusqu'en fin de\njourn&eacute;e,<br /> il faut une <span\nstyle=\"background-color:#f59100;padding:3px 6px;word-break:no-break;\">pause\nd&eacute;jeuner.</span></span></b></span></div>\n<div style=\"padding-top:15px;\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2340&L=1003&F=H\"\ntarget=\"_blank\"><img alt=\"Plus d'informations\"\nsrc=\"http://hosting.effiliation.com/Chequedejeuner/chequedejkitdecembre15/GroupeUp-Button1.png\"\nstyle=\"max-width:320px; padding-top: 0px; padding-bottom: 0; display:\ninline !important; vertical-align: bottom;\" class=\"mcnImage\" align=\"middle\"\nwidth=\"320\" /></a></div>\n</td>\n</tr>\n</tbody>\n</table>\n<table class=\"mcnImageCardLeftImageContentContainer\" align=\"right\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageCardLeftImageContent\" style=\"padding-top:27px;\npadding-right:30px; padding-bottom:40px; padding-left:20px;\" align=\"center\"\nvalign=\"top\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2340&L=1003&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://hosting.effiliation.com/Chequedejeuner/chequedejkitdecembre15/GroupeUp-Hero.png\"\nalt=\"Groupe Up - Pause D&eacute;jeuner\" style=\"max-width:600px;\"\nclass=\"mcnImage\" width=\"169\" /></a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<table style=\"width: 100%;\" class=\"mcnImageCardBlock\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnImageCardBlockOuter\">\n<tr>\n<td class=\"mcnImageCardBlockInner\" style=\"padding-top:20px;\npadding-right:0px; padding-bottom:0px;\npadding-left:0px;background-color:#ffffff;\" valign=\"top\">\n<table style=\"width: 100%;\" class=\"mcnImageCardRightContentOuter\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageCardRightContentInner\" style=\"padding:\n0px;background-color: #F3F3F3;\" align=\"center\" valign=\"top\">\n<table class=\"mcnImageCardRightImageContentContainer\" align=\"left\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageCardRightImageContent\" style=\"padding-top:35px;\npadding-right:15px; padding-bottom:35px; padding-left:30px;\" align=\"center\"\nvalign=\"top\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2340&L=1003&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://hosting.effiliation.com/Chequedejeuner/chequedejkitdecembre15/GroupeUp-ChequeDejeuner.png\"\nalt=\"Groupe Up - Ch&egrave;que D&eacute;jeuner\" style=\"max-width:600px;\"\nclass=\"mcnImage\" width=\"245\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table style=\"width: 300px;\" class=\"mcnImageCardRightTextContentContainer\"\nalign=\"right\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent\" id=\"advantage\" style=\"padding-right:\n30px;padding-top: 40px;padding-bottom: 40px;color: #F2F2F2;font-family:\nHelvetica;font-size: 14px;font-weight: normal;line-height: 150%;text-align:\nright;\" valign=\"top\"><b\nstyle=\"font-size:20px;color:#4f5d5b;text-align:right;line-height:130%;\">Un\navantage social&nbsp;exon&eacute;r&eacute; de charges*, accessible &agrave;\npartir d'un salari&eacute; !</b></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<table class=\"mcnTextBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnTextBlockOuter\">\n<tr>\n<td class=\"mcnTextBlockInner\" valign=\"top\">\n<table style=\"min-width: 100%; width: 100%;\"\nclass=\"mcnTextContentContainer\" align=\"left\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent\"\nstyle=\"padding-top:30px;padding-left:30px;padding-right:30px;color:\n#4F5D5B;font-size: 16px;line-height: 150%;text-align:\ncenter;background-color:#fafafa;\" valign=\"top\">Am&eacute;liorer la\nqualit&eacute; de vie de vos salari&eacute;s, c'est bon pour leur\nmotivation &amp; la performance de votre entreprise.</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<table class=\"mcnImageBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnImageBlockOuter\">\n<tr>\n<td id=\"templateButton\" valign=\"top\">\n<table class=\"mcnImageBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnImageBlockOuter\">\n<tr>\n<td style=\"padding:0px\" class=\"mcnImageBlockInner\" valign=\"top\">\n<table class=\"mcnImageContentContainer\" style=\"min-width: 100%; width:\n100%;\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnImageContent\" style=\"padding-right: 0px; padding-left: 0px;\npadding-top: 0; padding-bottom: 0; text-align:center;\" valign=\"top\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2340&L=1003&F=H\"\ntarget=\"_blank\"> </a><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2340&L=1003&F=H\"\ntarget=\"_blank\"><img alt=\"Plus d'informations\"\nsrc=\"http://hosting.effiliation.com/Chequedejeuner/chequedejkitdecembre15/GroupeUp-Button2.png\"\nstyle=\"max-width:291px; padding-bottom: 0; display: inline !important;\nvertical-align: bottom;\" class=\"mcnImage\" align=\"middle\" width=\"291\"\n/></a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td id=\"templateFooter\" valign=\"top\">\n<table class=\"mcnTextBlock\" style=\"min-width: 100%; width: 100%;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody class=\"mcnTextBlockOuter\">\n<tr>\n<td class=\"mcnTextBlockInner\" valign=\"top\">\n<table style=\"min-width: 100%; width: 100%;\"\nclass=\"mcnTextContentContainer\" align=\"left\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"mcnTextContent\" style=\"padding-top:9px; padding-right: 18px;\npadding-bottom: 9px; padding-left: 18px;\" valign=\"top\">\n<p style=\"margin:0;line-height:130%!important;\">* Dans la limite de\n1181,40&euro; par an et par b&eacute;n&eacute;ficiaire (soit 5,37&euro; x\n220 jours ouvr&eacute;s) en 2016.</p>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<!--[if gte mso 9]></td>\n</tr>\n</table>\n<![endif]--> <!-- // END TEMPLATE --></td>\n</tr>\n</tbody>\n</table>\n</center> <img\nsrc=\"http://track13398278.effimails3.com/servlet/effi.pixel?id_compteur=13398278\"\n/>\n<div align=\"center\"><a></a><a></a><a></a><a><font style=\"font-size: 11px;\nfont-family: Arial,Helvetica,sans-serif; color: #523122;\" face=\"Arial,\nHelvetica, sans-serif\" color=\"#523122\" size=\"1\">Si vous ne souhaitez plus\nrecevoir d'offre commerciale de notre part </font></a><font\nstyle=\"font-size: 11px; font-family: Arial,Helvetica,sans-serif; color:\n#523122;\" face=\"Arial, Helvetica, sans-serif\" color=\"#523122\" size=\"1\"><a\nstyle=\"color: #523122;\"\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2340&L=479&F=H\">suivez\nce lien <br /></a></font></div>\n<font style=\"font-size: 11px; font-family: Arial,Helvetica,sans-serif;\ncolor: #523122;\" face=\"Arial, Helvetica, sans-serif\" color=\"#523122\"\nsize=\"1\"> </font></div>\n</div>\n</div><img\nsrc=\"http://www.supprabyte.co.uk/open.php?M=7474945&L=326&N=2340&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_b54aeac886a98845130d334794a399d6--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_63.argentina:2,",
    "content": "Return-Path: <moon98@wevercomm.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id E0F0620A6F357\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 16:54:52 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid D233333181F7; Sun, 10 Apr 2016 16:54:52 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid C4C6333181FC; Sun, 10 Apr 2016 16:54:52 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.0 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BRBL_LASTEXT autolearn=no\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [222.122.56.205 listed in bb.barracudacentral.org]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\nX-Spam-DCC: : \nReceived: from wevercomm.com (wevercomm.com [222.122.56.205])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 8C43D33181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 16:54:48 +0200 (CEST)\nDate: Sun, 10 Apr 2016 23:54:36 +0900\nFrom: moon98@wevercomm.com\nReply-To: <cat@dating2mail.com>\nMessage-ID: <9eb5cbf-67c6b-7e@wevercomm.com>\nTo: dev@caliopen.local\nSubject: FW:  Howdy-do\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: FW:  Howdy-do\nContent-Length: 215\nLines: 2\n\n\n<div>Howdy-do, I am a lonely nice puss, looking for handsome person for a hook up :) <br> Where are you from, do you speak english? <br> <br> please reply me if you would like to see photos of me. Bye-bye :D</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_7.argentina:2,",
    "content": "Return-Path: <prvs=0909110163=info@Honeywell-egypt.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 43A7720AB4737\n\tfor <laurent@brasil.brainstorm.fr>; Mon, 11 Apr 2016 12:58:07 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 0528D33181F7; Mon, 11 Apr 2016 12:58:07 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid E98DD33181FC; Mon, 11 Apr 2016 12:58:06 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ************\nX-Spam-Status: Yes, score=12.9 required=5.0 tests=BAYES_50,HTML_MESSAGE,\n\tMIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tRCVD_IN_BL_SPAMCOP_NET,RCVD_IN_PBL,URIBL_BLACK,URIBL_DBL_SPAM autolearn=spam\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.3 RCVD_IN_PBL RBL: Received via a relay in Spamhaus PBL\n\t*      [196.202.24.124 listed in zen.spamhaus.org]\n\t*  1.3 RCVD_IN_BL_SPAMCOP_NET RBL: Received via a relay in bl.spamcop.net\n\t*      [Blocked - see <http://www.spamcop.net/bl.shtml?23.80.30.8>]\n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: seoregisterdomain.com]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60%\n\t*      [score: 0.5423]\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: seoregisterdomain.com]\nX-Spam-DCC: : \nReceived: from [196.202.24.124] (mail.honeywell-egypt.com [196.202.24.124])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id E585B33181F7\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 12:58:03 +0200 (CEST)\nReceived: from [127.0.0.1] (23.80.30.8) by M-srv.Honeywell-egypt.com\n (192.168.1.242) with Microsoft SMTP Server (TLS) id 14.2.342.3; Mon, 11 Apr\n 2016 02:30:52 +0200\nContent-Type: text/html\nFrom: =?UTF-8?Q?Domain_Services_=E2=9C=94?= <info@honeywell-egypt.com>\nTo: <dev@caliopen.local>\nSubject: RE: Domain registration =?UTF-8?Q?=E2=9C=94?=\nMessage-ID: <1460334975705-f057354d-38c344e2-d9e67ec3@honeywell-egypt.com>\nX-Mailer: nodemailer (2.3.1; +http://nodemailer.com/;\n SMTP/2.4.1[client:2.3.1])\nContent-Transfer-Encoding: quoted-printable\nDate: Mon, 11 Apr 2016 00:36:15 +0000\nMIME-Version: 1.0\nX-Originating-IP: [23.80.30.8]\nX-EsetResult: clean, is OK\nX-EsetId: 37303A29121F6661667163\nX-Spam-Prev-Subject: RE: Domain registration =?UTF-8?Q?=E2=9C=94?=\nContent-Length: 6563\nLines: 157\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.=\nw3.org/TR/REC-html40/loose.dtd\">\n<html><body>\n\n<doctype html>\n\n\n    <meta charset=3D\"utf-8\">\n\n\n\n\n    <div style=3D\"background-color: ''; =\nmax-width: 800px; box-shadow: 0 0 10px rgba(0, 0, 0, .15); font-size: 16px;=\n line-height: 24px; font-family: 'Helvetica Neue', 'Helvetica', Helvetica, =\nArial, sans-serif; color: #f7f7f7; margin: auto; padding: 30px; border: 1px=\n solid #eee;\">\n        <h1 style=3D\"color: #C5731E;\" =\nalign=3D\"center\">Attention: Important Notice</h1>\n        <div =\nstyle=3D\"background-color: #FFFFFF; max-width: 800px; box-shadow: 0 0 10px =\nrgba(0, 0, 0, .15); font-size: 16px; line-height: 24px; font-family: =\n'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; color: #555; =\nmargin: 30px 0; padding: 30px; border: 1px solid #eee;\">\n            <table cellpadding=3D\"0\" cellspacing=3D\"0\" style=3D\"width: =\n100%; line-height: inherit; text-align: left;\">\n                <tr>\n                    <td colspan=3D\"2\" style=3D\"vertical-align: top; =\npadding: 5px;\" valign=3D\"top\">\n                        <table =\nstyle=3D\"width: 100%; line-height: inherit; text-align: left;\">\n                            <tr>\n                                <td =\nstyle=3D\"vertical-align: top; font-size: 25px; line-height: 45px; color: =\n#333; width: 100%; display: block; text-align: center; padding: 5px 5px =\n20px;\" align=3D\"center\" valign=3D\"top\">Domain Name: non-droit.org</td>\n\n                            </tr>\n                        </table>\n                    </td>\n                </tr>\n                <tr>\n                    <td style=3D\"vertical-align: top; padding: 5px;\" =\nvalign=3D\"top\">\n                    </td>\n                </tr>\n                <tr>\n                    <td style=3D\"vertical-align: top; =\npadding: 5px;\" valign=3D\"top\"> <strong>Billed To:</strong><br>\n                        Laurent Chemla<br>\n                        24 rue =\ndes trois fontaines<br>\n                        NAmes<br>\n                         30000\n\n                    </td>\n                    <td style=3D\"vertical-align: top; text-align: right; =\npadding: 5px;\" align=3D\"right\" valign=3D\"top\">\n                        =\nInvoice#: 7bb5-14986369<br> Created: 04/10/16<br> Expires:\n                        04/15/16\n                    </td>\n\n                </tr>\n\n                <td style=3D\"font-size: 25px; =\nline-height: 35px; vertical-align: top; padding: 5px;\" =\nvalign=3D\"top\">Secure Online Payment: <a href=3D\"http://seoregisterdomain.=\ncom/?k=3DFD2I0XFQGGKSIoRNyvrFuKFUe5Cu6m6M\">non-droit.org</a>\n                </td>\n                <table style=3D\"width: 100%; =\nline-height: inherit; text-align: left;\">\n                    <tr>\n                        <td style=3D\"vertical-align: top; =\nborder-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: =\n#ddd; font-weight: bold; background-color: #EEEEE0; padding: 5px;\" =\nbgcolor=3D\"#EEEEE0\" valign=3D\"top\"> Domain</td>\n                        <td=\n style=3D\"vertical-align: top; text-align: right; border-bottom-width: 1px;=\n border-bottom-style: solid; border-bottom-color: #ddd; font-weight: bold; =\nbackground-color: #EEEEE0; padding: 5px;\" align=3D\"right\" =\nbgcolor=3D\"#EEEEE0\" valign=3D\"top\"> Registration</td>\n                        <td style=3D\"vertical-align: top; =\nborder-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: =\n#ddd; font-weight: bold; background-color: #EEEEE0; padding: 5px;\" =\nbgcolor=3D\"#EEEEE0\" valign=3D\"top\">Price</td>\n                        <td =\nstyle=3D\"vertical-align: top; border-bottom-width: 1px; =\nborder-bottom-style: solid; border-bottom-color: #ddd; font-weight: bold; =\nbackground-color: #EEEEE0; padding: 5px;\" bgcolor=3D\"#EEEEE0\" =\nvalign=3D\"top\">Term</td>\n                    </tr>\n                    <tr>\n                        <td style=3D\"vertical-align: top; padding: 5px 5px =\n20px;\" valign=3D\"top\"> non-droit.org</td>\n                        <td =\nstyle=3D\"vertical-align: top; text-align: right; padding: 5px 5px 20px;\" =\nalign=3D\"right\" valign=3D\"top\">04/10/16 - 04/10/17</td>\n                        <td style=3D\"vertical-align: top; padding: 5px 5px =\n20px;\" valign=3D\"top\">$75</td>\n                        <td =\nstyle=3D\"vertical-align: top; padding: 5px 5px 20px;\" valign=3D\"top\">1 =\nyear</td>\n                    </tr>\n\n\n                </table>\n                <p align=3D\"left\"><strong>\n                        Attn =\nLaurent Chemla<br><br>\n                        This letter is to inform you=\n that it's time to send in your search engine registration for non-droit.=\norg<br><br>\n\n                        Failure to complete your search engine=\n registration by 04/15/16 may result in the cancellation of this offer\n                        (making it difficult for your customers to locate =\nyou using search engines on the web).<br><br>\n\n                        Your=\n registration includes search engine submission for non-droit.org for 1 =\nyear. You are under no\n                        obligation to pay the amount=\n stated above unless you accept this offer by <date>. This notice is not\n                            an invoice. It is a courtesy reminder to =\nregister <url> for search engine listing so that your\n                                customers can locate you on the web.=\n</url></date></strong></p>\n\n\n                <div></div>\n                <div style=3D\"background-color: #FFFFFF; max-width: 800px; =\nbox-shadow: 0 0 10px rgba(0, 0, 0, .15); font-size: 16px; line-height: =\n24px; font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, =\nsans-serif; color: #555; margin: 30px 0; padding: 30px; border: 1px solid =\n#eee;\">\n                    <h1 style=3D\"font-size: 23px; line-height: =\n40px; color: #C5731E;\" align=3D\"center\">This Offer for non-droit.org will =\nexpire on 04/15/16. Act today!</h1>\n                    <div =\nwidth=3D\"100px\" style=3D\"border: solid;\"><h1 style=3D\"line-height: 32px;\" =\nalign=3D\"center\">For Domain Name: <br>\n                            =\nnon-droit.org</h1></div>\n                    <div align=3D\"center\"><a =\nhref=3D\"http://seoregisterdomain.com/?k=3DFD2I0XFQGGKSIoRNyvrFuKFUe5Cu6m6M\"=\n>ONLINE PAYMENT 128BIT SECURE</a></div>\n                </div>\n                <p style=3D\"color: lightgrey; font-size: 14px;\">Click <a =\nhref=3D\"http://seoregisterdomain.com/unsubscribe/FD2I0XFQGGKSIoRNyvrFuKFUe5=\nCu6m6M\">here</a> to unsubscribe from future mailings.</p>\n\n\n            </table>\n        </div>\n    </div></doctype>\n</body></html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463561.5266_9.argentina:2,",
    "content": "Return-Path: <info@pubblicitaaziendedb.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 335AA20A3A4A3\n\tfor <laurent@brasil.brainstorm.fr>; Mon, 11 Apr 2016 11:48:24 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 2278133181F7; Mon, 11 Apr 2016 11:48:24 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 14F8433181FC; Mon, 11 Apr 2016 11:48:24 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: **********************\nX-Spam-Status: Yes, score=22.0 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RCVD_IN_BRBL_LASTEXT,RCVD_IN_CSS,\n\tRCVD_IN_PSBL,RDNS_DYNAMIC,SUBJECT_NEEDS_ENCODING,SUBJ_ILLEGAL_CHARS,\n\tTO_NO_BRKTS_DYNIP,T_SURBL_MULTI1,T_URIBL_BLACK_OVERLAP,URIBL_BLACK,\n\tURIBL_DBL_SPAM,URIBL_JP_SURBL,URIBL_WS_SURBL autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  2.7 RCVD_IN_PSBL RBL: Received via a relay in PSBL\n\t*      [185.49.13.234 listed in psbl.surriel.com]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [185.49.13.234 listed in bb.barracudacentral.org]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [185.49.13.234 listed in zen.spamhaus.org]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: pubblicitaaziendedb.com]\n\t*  1.6 URIBL_WS_SURBL Contains an URL listed in the WS SURBL blocklist\n\t*      [URIs: pubblicitaaziendedb.com]\n\t*  1.2 URIBL_JP_SURBL Contains an URL listed in the JP SURBL blocklist\n\t*      [URIs: pubblicitaaziendedb.com]\n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: pubblicitaaziendedb.com]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  1.0 RDNS_DYNAMIC Delivered to internal network by host with\n\t*      dynamic-looking rDNS\n\t*  1.5 SUBJ_ILLEGAL_CHARS Subject: has too many raw illegal characters\n\t*  0.0 T_URIBL_BLACK_OVERLAP T_URIBL_BLACK_OVERLAP\n\t*  0.0 SUBJECT_NEEDS_ENCODING SUBJECT_NEEDS_ENCODING\n\t*  0.0 T_SURBL_MULTI1 T_SURBL_MULTI1\n\t*  0.1 TO_NO_BRKTS_DYNIP To: misformatted and dynamic rDNS\nX-Spam-DCC: : \nReceived: from pubblicitaaziendedb.com (185a49b13c234.greendata.pl [185.49.13.234])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 4284B33181F7\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 11:48:21 +0200 (CEST)\nTo: dev@caliopen.local\nFrom: info@pubblicitaaziendedb.com\nSubject: Trova subito nuovi clienti‎.\nDate: Mon, 11 Apr 2016 11:50:47 +0200\nMessage-ID: <20160411_095047_004307.info@pubblicitaaziendedb.com>\nX-Mailer: XMAIL\nContent-Transfer-Encoding: 8bit\nContent-Type: text/html; charset=\"utf-8\"\nMime-Version: 1.0\nX-Spam-Prev-Subject: Trova subito nuovi clienti‎.\nContent-Length: 1105\nLines: 30\n\nSalve, <br /><br />\n\nOffriamo banche dati di aziende e clienti italiani potenzialmente interessati alla Vostra offerta.<br /><br />\n\nLe banche dati delle aziende sono suddivise in gruppi di destinatari, secondo le Vostre necessità.<br /><br />\n\n--<br />\nDatabase Aziende 2016 - 140 € * Scadenza promozione: 11/04/2016 <br />\n--<br /><br />\n\n<a href=\"http://www.pubblicitaaziendedb.com\">www.pubblicitaaziendedb.com</a> \n\n<br /><br />\nI dati delle aziende contengono:<br />\n\"Nome dell'azienda, Persona di contatto, E-mail, Telefono, Fax, CAP, Località, Via, ecc.\" <br /><br />\n\nLe possibilità di utilizzo delle banche dati sono praticamente illimitate.<br />\nGrazie ad esse potrete condurre campagne pubblicitarie in modo efficace e sicuro.<br /><br />\n\nI programmi da noi creati vi permettono di preparare una campagna pubblicitaria professionale,<br />\npersonalizzando linvio delle offerte mediante e-mail o fax. <br /><br />\n\nVi invitiamo a visitare il nostro sito internet:<br /><br />\n\n<a href=\"http://www.pubblicitaaziendedb.com\">www.pubblicitaaziendedb.com</a>\n\nCordiali Saluti\nMarco Coletti.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_101.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anvrT-0007Qu-Bh\n\tfor mharc-dev@caliopen.local; Wed, 06 Apr 2016 18:23:43 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56559)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1anvrR-0007NJ-9u\n\tfor dev@caliopen.local; Wed, 06 Apr 2016 18:23:42 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1anvrQ-0002nd-Ek\n\tfor dev@caliopen.local; Wed, 06 Apr 2016 18:23:41 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:49885)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1anvrO-0002mM-Vb; Wed, 06 Apr 2016 18:23:39 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1anvrO-0001S7-EV; Wed, 06 Apr 2016 18:23:38 -0400\nFrom: Richard Stallman <rms@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nIn-reply-to: <5704AEAF.8050205@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Wed, 6 Apr 2016 07:37:35 +0100)\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n\t<570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\n\t<5703D7BA.4070507@gmail.com> <E1anapc-0006WG-Pg@fencepost.gnu.org>\n\t<5704AEAF.8050205@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nMessage-Id: <E1anvrO-0001S7-EV@fencepost.gnu.org>\nDate: Wed, 06 Apr 2016 18:23:38 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 06 Apr 2016 22:23:42 -0000\nContent-Length: 1133\nLines: 32\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > Additionally, IceCat of course leaks the fact that you're using\n  > LibreJS, which probably goes a long way towards identifying\n  > IceCat.\n\nThere is no help for that.  We have to block the nonfree software.\n\n   > I wonder if profiling the list of websites that SpyBlock is\n   > blocking would give further information.\n\nWho knows?  But, once again, we have to block these forms of surveillance.\n\nWhat is your purpose in mentioning these points?  What constructive\nquestion do they relate to?\n\n   > If that's not enough, differences in behaviour between successive\n   > versions of LibreJs can probably help in pinpointing a precise\n   > version.\n\nI doubt any surveillance companies want to go to so much trouble\nspecifically about LibreJS.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_103.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ao58W-0007eJ-CN\n\tfor mharc-dev@caliopen.local; Thu, 07 Apr 2016 04:17:56 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59338)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ao58U-0007dJ-7H\n\tfor dev@caliopen.local; Thu, 07 Apr 2016 04:17:55 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ao58T-0001du-Cz\n\tfor dev@caliopen.local; Thu, 07 Apr 2016 04:17:54 -0400\nReceived: from mout.kundenserver.de ([212.227.126.187]:60480)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1ao58P-0001dS-C4; Thu, 07 Apr 2016 04:17:49 -0400\nReceived: from [146.179.198.208] ([146.179.198.208]) by\n\tmrelayeu.kundenserver.de (mreue005) with ESMTPSA (Nemesis) id\n\t0M3wF4-1beX3x2EJ0-00rZlh; Thu, 07 Apr 2016 10:17:47 +0200\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: rms@gnu.org\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n\t<570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\n\t<5703D7BA.4070507@gmail.com> <E1anapc-0006WG-Pg@fencepost.gnu.org>\n\t<5704AEAF.8050205@gmail.com> <E1anvrO-0001S7-EV@fencepost.gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <5706179F.5040809@gmail.com>\nDate: Thu, 7 Apr 2016 09:17:35 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <E1anvrO-0001S7-EV@fencepost.gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"4DMiq7H6QsX3nsvSv7uuinWcT4XjeHIp4\"\nX-Provags-ID: V03:K0:RRMebg0RBZBjY3i8+FQN7XvNjMDJL9KeyzxIbqLkYoSRPWkXIoy\n\tGxwE2EE5xQ2w15+ze+EXM/nHP+ZitTh1dImeqHaNfA9l6YpFuZ7+1Z2cilA6+/TctJxMQVL\n\tLmUeJWsVhasrJqGrnwn6LAMRJ5rOgjTInel3mTdsyLHB2j8JC/DGg5DgY//dJmzX9MGAIXP\n\tEo9brLTlSHhXTUmpsDAlQ==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:mMsQSipxd6g=:OVnIA80HS6S87Ys7AywG1s\n\tsP8Nmw9yZt57pi650MXuNrazSEqSwSbHPHxuoWPKgwl3ZdJ7AmL6Jm2c7vpoBATMirjqk09Ez\n\tXdlqE1q/scwv4T3oWC7vBKJT+JhE+gMrci4QjA58YbRMnmDXP0Qdv6HZvE1naMviDRLIY8xr6\n\t3FDxLsht+B2IpZlPrfzoYEdxNbYV+sMw4rAFDN1LP78je//GKM+E0iMEU/BhGo8J8g8WztaUK\n\tm9G2xvgKZ/a1rhF2Z/XrTicV9OpmlZvZf4CUlKdjY7JKi6pAeb9ehFO07XXmWAvFMCX3qXTpK\n\tINBVK+YGwjxmgRnM6ky4nobVlYTZqlw1hmp40yJJYRpNkPxX2xBo4VaLJuII0oKO6C/hWYVtx\n\tgNDrGsU7HedK6v0WnSnG/EX7I0qA9vzsQ4tTq6ycLzhfXxuq6vG53oq641WmHeO+QNmbQYJrP\n\tKAxxKOsvmrdqixnDL3Lz9Z4TVcs1z0tytif0usK06AQGtvy76MPiFb+kZAdFsyPMi12mb+Ab2\n\tu0fD7uy6Evuopc52DEVC3BmdBUQkiFLdl6ycZaZ/9ziByWDV6+4K7rSiqhqcynfecpntHRHwM\n\tUhljsxtoc8UloBqgFu2DM2W89RiY5RkimxJON29KrRN5btAzn8OeWd6uyP9HQQazgzTekRaUK\n\tqDYDmfEEV+nTeNYuZ2tseeVqQVqlUKETWgvEb0E0g9hh1qpuRjfjobsPnFDk9iYQ7/Qw=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.187\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 07 Apr 2016 08:17:55 -0000\nContent-Length: 2797\nLines: 67\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--4DMiq7H6QsX3nsvSv7uuinWcT4XjeHIp4\nContent-Type: multipart/mixed; boundary=\"FqNsqTw31ErIooDlXT2isGsMFvHSfarp6\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: rms@gnu.org\nCc: dev@caliopen.local\nMessage-ID: <5706179F.5040809@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n <E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n <jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n <05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n <m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n <570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\n <5703D7BA.4070507@gmail.com> <E1anapc-0006WG-Pg@fencepost.gnu.org>\n <5704AEAF.8050205@gmail.com> <E1anvrO-0001S7-EV@fencepost.gnu.org>\nIn-Reply-To: <E1anvrO-0001S7-EV@fencepost.gnu.org>\n\n--FqNsqTw31ErIooDlXT2isGsMFvHSfarp6\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/06/2016 11:23 PM, Richard Stallman wrote:\n> What is your purpose in mentioning these points?  What constructive\n> question do they relate to?\n\nSorry if this didn't sound constructive. You wrote:\n\n> I think url should do what IceCat does: send a user-agent string that\n> is widely encountered on the net, to thwart identification of users.\n\nSo I pointed out that IceCat is probably one of the most easily identifia=\nble browsers out there. In any case, this is probably a discussion for em=\nacs-tangents :)\n\nCl=C3=A9ment.\n\n\n--FqNsqTw31ErIooDlXT2isGsMFvHSfarp6--\n\n--4DMiq7H6QsX3nsvSv7uuinWcT4XjeHIp4\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXBheqAAoJEPqg+cTm90wj2LsP/AkF87R/xhGgmgvuHJCqSXqH\naI3WID/yJSqzgoEP88NIfxMxlEG4uJwf1cC0A5Vg6gFN7TeYhR3y8P0dmiVzE+Ic\nGfF1B9UfDK7/EWvlYnilevbWYo9QxcMU+sdAXu/Toq+x98e8Flv+ygsmkYX1tgIz\nin4mX+tx7JOnq8h/jkF4ORcbwBuVMRdecf9es0SEP0zJ0itkijVh8uVRET4hEHbj\ndvdox+PSlz1H2VPHjowmN/2mBmFySBvUB7ntmHXE6FwmZZrjOyj9Fu4kO3pCBsub\nr4miq4P/FCPZ66CCnfYEdbKMrVAaKmN+DKsw7t3HeU57gNn3cQU2l88eH+adnyvU\nJ2n3+5sPAzlL7XDsyNV3TaFmgdF31azHXmTaaXWKMtJpAh7qiIH/FvoiGtM/cz5J\nPSz+MkpQJyC8dUFLeW6v2FB4gTEeVIha/3AoNQ8GnQzEqf+dBRnd8fbKEGCIDxqe\nqKBs39sT0e9r4ByzhnvGLE1YaLqdH/4qLRxLettv5e+PXcNBLZhCs7ggIpY/NOfe\noOrrbfUU7gvzQKXNhYxxOVI5/TxMsOUg7w8F7VSbgq0FNtwBLTDCekiQ8F7cptDB\nkw6WZpji19VUdYu4SN/e56vsLhOTR4ZZ5P1i+HpMEGmj/P1zrcfE3kjwT3V2EeLS\nU7ZkMet3InozrKWLbGYS\n=XZjR\n-----END PGP SIGNATURE-----\n\n--4DMiq7H6QsX3nsvSv7uuinWcT4XjeHIp4--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_105.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aoHbC-0003Sx-4g\n\tfor mharc-dev@caliopen.local; Thu, 07 Apr 2016 17:36:22 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46549)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1aoHHQ-0000Vz-Kt\n\tfor dev@caliopen.local; Thu, 07 Apr 2016 17:15:57 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1aoHHP-0001wd-SK\n\tfor dev@caliopen.local; Thu, 07 Apr 2016 17:15:56 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:42077)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1aoHHO-0001w4-EW; Thu, 07 Apr 2016 17:15:54 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1aoHHN-0004Xa-Rm; Thu, 07 Apr 2016 17:15:53 -0400\nFrom: Richard Stallman <rms@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nCC: dev@caliopen.local\nIn-reply-to: <5706179F.5040809@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Thu, 7 Apr 2016 09:17:35 +0100)\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nReply-to: rms@gnu.org\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n\t<570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\n\t<5703D7BA.4070507@gmail.com> <E1anapc-0006WG-Pg@fencepost.gnu.org>\n\t<5704AEAF.8050205@gmail.com> <E1anvrO-0001S7-EV@fencepost.gnu.org>\n\t<5706179F.5040809@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nMessage-Id: <E1aoHHN-0004Xa-Rm@fencepost.gnu.org>\nDate: Thu, 07 Apr 2016 17:15:53 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 07 Apr 2016 21:36:20 -0000\nContent-Length: 942\nLines: 26\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > Sorry if this didn't sound constructive. You wrote:\n\n  > > I think url should do what IceCat does: send a user-agent string that\n  > > is widely encountered on the net, to thwart identification of users.\n\n  > So I pointed out that IceCat is probably one of the most easily\n  > identifiable browsers out there.\n\nThat was changing the subject.  If IceCat's necessary features provide\na possible way to identify it, that is regretable, but it is a side\nissue.\n\nEmacs browsers should send user-agent strings that disguise them\nas commonly used browsers.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_107.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anT3f-0000BS-Tr\n\tfor mharc-dev@caliopen.local; Tue, 05 Apr 2016 11:38:23 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56423)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1anT3Y-0008PI-Hm\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 11:38:22 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1anT3V-0000m2-6l\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 11:38:16 -0400\nReceived: from mout.kundenserver.de ([212.227.126.131]:53237)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1anT3U-0000ly-TB\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 11:38:13 -0400\nReceived: from [172.30.34.146] ([194.81.217.65]) by mrelayeu.kundenserver.de\n\t(mreue003) with ESMTPSA (Nemesis) id 0M5c8C-1bkdBs0VI0-00xbbN for\n\t<emacs-devel@gnu.org>; Tue, 05 Apr 2016 17:38:12 +0200\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: dev@caliopen.local\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n\t<570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\n\t<5703D7BA.4070507@gmail.com>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <5703DBE3.6060303@gmail.com>\nDate: Tue, 5 Apr 2016 16:38:11 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <5703D7BA.4070507@gmail.com>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"NCIr3Q6X7mbtfuVeWP8nXAAm01doMrlE5\"\nX-Provags-ID: V03:K0:AsvOL7NhGOfaaZCgDiyW377tbfGM1Dh5eZfXABEonlsd8LbG0rw\n\tSxHNcDa2eqVRnuW3mLv6Nux/lcuwedBSs3zZj6OYZVIoQSeVtxaFhP+opq3QkHGnhWne9Ga\n\t5+9hsw08Qh3j+NQlrOkKQHro5BLuZIvAjJrbcvykzBFMy41ZjelKlLJ+t6q7IXmjNcO2f/N\n\tNSBNY8zFXHBWDvaPk4k1g==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:vdR+tbHyTac=:kN8HC3XBVDa3vaEe1sHYp8\n\tQ621KoUJlcX3A/ZvBGV5ArYKBde/uczrKuGtJBMXJe0mfYZ6bI0OrWZS5/KphF2q2rEkrlURP\n\tS5r+pTN4KgChx3FjVfmuwb4v3ghxSvhf+bCSAhy3lAZAJbgNFt5VbIw9RiGFlLlPexXkB8h4W\n\t7KMwEybKotFnQYI3+X138MyBveinbdDMzUEuA2vnVNsIV0qq0TYyMN+yadm/5PITfcVdgya4V\n\tg1OScQlKWf4IjKYGBA0g1T2QAJXxbz/+0xLdPiptdNYHT/2NIdluuzxNoyrU3L7Oq+9cPKrda\n\to92pt/etuLMqU426fEBqb/8t6z7/I02q4GfqgmpCz6WPP0xvUvZZ/QaB9Lmoj1xi3q7x/rdIk\n\toW3/mEhw9z+JlsUWHBVucMWUQFy7OGhepeDo9C4gVudEzdwzBgUD0//42a7VvHpHkEXWNixOV\n\tMtiUHRI8lGO8NrnUwSynRjP6SfZFb5sTT7q7b8Egig7hrpyVMqcAMFxVJwxIgrPZSXYEAnmMK\n\tPzmIrTbhYVVmJ5pFouVu4GDVsOGIzSFDAuFqht7fjS29WKRddX5lqxjvD0Co8ccFXe4BcV0QF\n\tQxVxU4kaUt4OWcC6huYn3PtsXBWn+woRWV8veHMib3BcemYLa4r7jQUdTCTJo9r8d+gpyCwGs\n\tr3ptlHAxawikv1BJOxo0DNtaIvHkwnrMMlnRSmKauoNlPsBFaI3ZJLGVlC1bli16Nxu0=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.131\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 15:38:22 -0000\nContent-Length: 2579\nLines: 61\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--NCIr3Q6X7mbtfuVeWP8nXAAm01doMrlE5\nContent-Type: multipart/mixed; boundary=\"lQOg2mAQRujxbrWIEDbgl2BHUeEfNCu3S\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <5703DBE3.6060303@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n <E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n <jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n <05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n <m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n <570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\n <5703D7BA.4070507@gmail.com>\nIn-Reply-To: <5703D7BA.4070507@gmail.com>\n\n--lQOg2mAQRujxbrWIEDbgl2BHUeEfNCu3S\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/05/2016 04:20 PM, Cl=C3=A9ment Pit--Claudel wrote:\n> On 04/05/2016 03:53 PM, Richard Stallman wrote:\n>> It is not clear which one you are talking about.  Are you talking=20\n>> about IceCat?  If so, yes, that user-agent string is designed to\n>> equal what many other machines on the net actually send.\n>=20\n> I meant IceCat. It sends its precise version, and it reveals that I'm u=\nsing Firefox.\n\n(I should say \"a Firefox derivative\")\n\n\n--lQOg2mAQRujxbrWIEDbgl2BHUeEfNCu3S--\n\n--NCIr3Q6X7mbtfuVeWP8nXAAm01doMrlE5\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXA9vjAAoJEPqg+cTm90wj1+0P/3udI5jTpswYYND5qszKeeRb\nSqaBEVka54E6rSfkqgs51C5u1p7oZg6+AD33Z5mV6qjjvOcSL6suLSOhL67fb9WL\nSQK35DBOGAP3ymXgjXeBfPUBFfRmxR/LVXNGFTzKLvZzQlqk2bmsvgKydhgMZ5sY\nyEQvt4K2gQsIAjn9dS4JkNsuqozb2Afml4T70bR/o1ujJxp7xziGLW9GwDq85Qaw\nHgjT6msiaeCMj/8XQmr/WwpX4Z8AF0wal7nMdfTgU4Xk9XxpzLlin7ePLHvrHUFc\nBbbaIuLyzzW7mUYN9s7E6d1sbef3FSs+D66aoJJ7gkzSpp8pnOpAg5Yh3RRsp0oZ\nE8LnAntyFowoS0RjtxtLuoH9GRUN1SVblHxz8+1oXjpCcQs9wjame2uOxQd4sBLu\n+RoZJd5yzSzc3sk4biRaJM6zb9ebIjOPb2x68HHvuXLHvBVs9KlncCqVgNqtnHZ1\n43AtXiM/NIs1aE4C1pAibvqXvhaheDJ8/9weNvTXeg+yXYNpZNoAbtj93ENn2sk8\nMqmvZZ23LhyvT8IxyerReTSvHUCXNtmneLF2lE7rqK3UWftacHp2wBgAzZ40RIvA\nQOF3r+Hd+m3luOEL3l2M2xNpEc6ps1C3dixeWGnzoBQBLoPKoHzHDWLcHeUwPYjl\ndybkuOdK0C4E+VwuqTJF\n=KaLN\n-----END PGP SIGNATURE-----\n\n--NCIr3Q6X7mbtfuVeWP8nXAAm01doMrlE5--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_109.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajCn1-0005Jw-13\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 17:27:35 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39497)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajCmy-0005HN-4P\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:27:32 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajCmu-0001dm-Uy\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:27:32 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:52380)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajCmu-0001b9-O1\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:27:28 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajCms-0000cW-1v\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 22:27:26 +0100\nReceived: from 69-196-182-150.dsl.teksavvy.com ([69.196.182.150])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 24 Mar 2016 22:27:26 +0100\nReceived: from monnier by 69-196-182-150.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 24 Mar 2016 22:27:26 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nDate: Thu, 24 Mar 2016 17:27:19 -0400\nMessage-ID: <jwvh9fvfu8n.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-196-182-150.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:XcoOG8qBnPyXJTTuebhsCs2qJBo=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 21:27:32 -0000\nContent-Length: 1023\nLines: 25\n\n>>>> I never liked it, but admittedly, I never complained about it.\n>>> May I ask why you do not like this behaviour? What stands against it?\n>> That's just a leak of private information.\n> I must admit that I find it hard to agree.  I do not see how the OS,\n> or the Emacs version, or  the combination of both could be used to\n> identify you as a person.\n\nYour medical records are considered private information, not because\nthey would allow someone to identify you, but because they say things\nabout you which not everyone should know.\n\n> And even if it could, doesn't it pale beside the information that is\n> necessarily send to the archive (e.g. your IP, a partial list of the\n> packages you're using, etc.)?\n\nAFAIK we don't send a partial list of the packages we're using.\n\n> I think that our desire as package maintainers to learn how and where\n> their packages are used is legitimate.\n\nOf course.  But being legitimate doesn't trump other concerns.  It has\nto be weighed against the consequences.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_111.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajCt5-00088R-UH\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 17:33:51 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40911)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCt3-00088I-G7\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:33:50 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCt0-0002hl-Ac\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:33:49 -0400\nReceived: from mout.kundenserver.de ([212.227.126.131]:64886)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCt0-0002hM-1o\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:33:46 -0400\nReceived: from [128.30.9.181] ([128.30.9.181]) by mrelayeu.kundenserver.de\n\t(mreue005) with ESMTPSA (Nemesis) id 0MKMb4-1aieek3gMY-001fNi for\n\t<emacs-devel@gnu.org>; Thu, 24 Mar 2016 22:33:45 +0100\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: dev@caliopen.local\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<jwvh9fvfu8n.fsf-monnier+gmane.emacs.devel@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56F45D37.1090505@gmail.com>\nDate: Thu, 24 Mar 2016 17:33:43 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <jwvh9fvfu8n.fsf-monnier+gmane.emacs.devel@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"kgho5IsnKgKLFNhBCNu4TqWX3LObCuV2n\"\nX-Provags-ID: V03:K0:usbFR9bDbEAQPYMKp1qzFliADJQgfbTPaUu3DHVD1rgGq7TE5pC\n\tQNbZqs7gydzpWfEe5/8pRuVPG1pLsJ/WH659tUd9H52GMdP5q+htQseEWZQJlYw5NwtETlz\n\tbfQ7VLdWeICPQiq9jc6N53SgCxXv+PLfQmSffT8zBNZTLekNeeo0Mf4i+6P6eqMM9/Y0c+1\n\tI0YafL06BemlPqTw/ArxA==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:W649sarFqxs=:zYnpvQOuGhL1VNrfiUadUn\n\tUb4uoGppKrOJAI8LBLa/PpEm/nBImBOH3nJ7VaxAGqZXFO/ZRnzwLGh5iAupD/7napjQ85mUc\n\tobmn+OO5ydgGsYDWvFNauAYHgDaaPmVK5zP827W6HLGVMDrSXKidjao55dhse4Q7c159ybuba\n\tVgkN2xEEuksptPYdurBGrTlGMMZ8mREG0Xd0Uivvcj2+3uXUdCUwAcVhgVlgKwaTllwlp2dfE\n\t62CwEgB3j1xh/yeXwyYXglM7hF2mzZo2IRaz2jAlv3d5/fEvTpCsBTK9nsUl0UHmD+fjeQut0\n\t8DTm3r6kWSKO5o4dG0aUEwIamr6vsPLfEV7EWkN6c1vp+hx5jM9AOoQzh3ryqKzrOZbM3QPx8\n\tv9ebeyE4DNhyxO67OlOfxEiVR8IpxfR7cva/188d/hTVjBIoQEW8pyvORShxz5SPKNR7eeMfq\n\tDodN+wTiEA57HZVoO2agLG2lSngFKNiguLNIR5hKN8WilXEfA9oouvvs80ZU4FO6OrhMMzDoF\n\t0RGRGyh4sOfEfNS3ttMNRLqNPa0ygPxZ943D+KFyIohj/4L3c8xHZDfcwEKjhrpE1x4Grc5lo\n\tkHFCb5uY2Vb/yPq+NS9evjWy49bAEIamGN9JuBMri6FkKRWSEWRtJzFy33MGwBixxXtNYeVGk\n\t60HvUHY0V/4vbQKxihzByPz1JloOMuKBh0AGWEbpagP5OQz/m29LVqPipY2zY9sFY/YVxwbf1\n\tJ1kYQTCYDYYWD0ZLjmFHYis0+dtiCYCT3jJVMBpc/z++u5R8MnhxMMhHzCReZsGQK/OcMNEnZ\n\t9zq9CddAkLcN+oeeq4zMviWg0uz4B1NO/2PnPO89PYXg13SPAMvvAJld3rPuOYw0pPVV2RCBx\n\t7F7NQOAK5B53uuubMLm5DSX8aMbRIMe+x/Ywqeudk=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.131\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 21:33:50 -0000\nContent-Length: 2421\nLines: 58\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--kgho5IsnKgKLFNhBCNu4TqWX3LObCuV2n\nContent-Type: multipart/mixed; boundary=\"C9IvVUAhx81vUD5pr3Ti8K9rb0N0ekwVh\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <56F45D37.1090505@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n <E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n <jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n <05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n <jwvh9fvfu8n.fsf-monnier+gmane.emacs.devel@gnu.org>\nIn-Reply-To: <jwvh9fvfu8n.fsf-monnier+gmane.emacs.devel@gnu.org>\n\n--C9IvVUAhx81vUD5pr3Ti8K9rb0N0ekwVh\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\nOn 03/24/2016 05:27 PM, Stefan Monnier wrote:\n> Your medical records are considered private information, not because\n> they would allow someone to identify you, but because they say things\n> about you which not everyone should know.\n\nNote that anonymized medical records are generally not considered private=\n; only sensitive. See e.g. the MIMIC2 database.\n\nCl=E9ment.\n\n\n--C9IvVUAhx81vUD5pr3Ti8K9rb0N0ekwVh--\n\n--kgho5IsnKgKLFNhBCNu4TqWX3LObCuV2n\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW9F03AAoJEPqg+cTm90wjCB0QAKvNJxw/ncjuJ+l/VwCt/RtJ\n0mCi5zlWaJkzw+SHlkyzUZb7DkEy6AthlnXjvhR53VXw6rzp6w24rvSxl2r6c4Vy\nD+9JDDY2RA3NFgA+RjJyhxhN0f/2JP1hNH44U9PkBuCApuEVh8TkURUqMNYY5Wfw\nKKTsYQmPrLULHuqr4v5HpjGu5lfIYQaU8gndbL8nJpvhPGfN9vRFQVU2TlShHIRB\nR4JRJCKww7AmWnpx7arGKTU/fr1Pbh8WzYWchgHTLk4jwWxorsZgToyMrA+G8/7h\ny/tVG4oNaSDdOL6NLJQWep0XnZl9x81EQyAXHCa/Pzi8f7h9vwLC6Laq2zKoM/CD\nEwO7m3PPQj1BNIfeGO4hK5dCDNlL3gDbP+iK+FQsooElpOc6vmMSftQQXE8+2XCp\n4pcRUUf9MQ/JcQY0gU7Q8588YaXUWndX7zs6lX6o4bddc00rzD0cKGqPNV15i47s\nT8ng2QxYGFMBHuYAeAe1lv5rZJxWqAhrdze4Pc5+vHfWrmP458FTcMmqZ2xNfYr9\nmMLQJAIct5WG1zKq7h+1ykpkhVbbvR2orZqAAkVsYkjN0QjGHLtxUpUKgFG9DxIy\nvoLuk9clkBsv6vLOkVmTm9f9I2Km2b+cB4S2XOHy5NHQMmVTyFWrR9r9HKCXhR+i\nn3Ul6Nk/zmDjU0LB1mkv\n=6jwz\n-----END PGP SIGNATURE-----\n\n--kgho5IsnKgKLFNhBCNu4TqWX3LObCuV2n--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_113.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajC5V-0003gI-63\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 16:42:37 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57913)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1ajC5T-0003fk-8S\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:42:36 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1ajC5Q-00076l-2E\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:42:35 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:53874)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1ajC5P-00075Z-Rx\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:42:31 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1ajC5G-0000fi-DC; Thu, 24 Mar 2016 21:42:24 +0100\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAD1BMVEXublvcMSP+RTf3VEP8\n\tTTzbt3PmAAACX0lEQVQ4jXVT7a3kIAz0hRSQZLeAZHABLKaALLj/mm6c5N2Pkx4SKwUvng8Psv6y\n\tZJFf1u+FmlLxJGU3O5dhbS8yFRbWqm4YMO3QN4CXWx1msmxqFeo8dsArYAnKwtpMZjul9VM6igAl\n\tYZSZhfNofr478gvduT85l1SJwQ5Q/CxCYUiCrKUhp75mw5rTsQ07NpPKgqRA8JMfF4IgW64q68rD\n\tjQik2l+xtYihEwP/rSG8RPBNSTNz7wnbyW2FTUl3UEc7C3WUzi5BiQhJ1j5W97yp5wfBBvxLVuQd\n\tZvxsL63mhqBLHVKPnSgn6ZpMsAbStdRCw1lqL4UHqWtKcOpwzRsQKHjBUbf20U4dtz3sTlOg7J6p\n\tzKNABOzRLGxPuDd12PDaz731snfYDky8OTVZfRw3gm5sSMeODf0g3cah2nHkgSP4a9swaphI0jfC\n\tZXlMPFAe2zVQOPQCtB0aKLISgd1V6TJpAe/Rv9o8bLd+acgyTYFiLZu1Rwc1aKETj44hV4FBKS9n\n\tLLtW6vhk758aljyG8GKLSB7d8e2ytAthHUUqh4vR4n8KWYx2C4c5N+p43KKNsqzdbWNNvuhLw4cD\n\tpu2I+PQBpmDaAiwCWjHbBU4dkdb8INAjv3UUp7aZjuHRQ5SJBV272nLgis/S9E8mCm4d1zj527WZ\n\tt8TBV1nieRByv6mqNTfzoGv3AzuLNcaHxWFpvnSQzgIOlgkJDf90NJx14nsOY+jSFZ7ZIj5K+/Rq\n\tyJT480ZoIvKk2Hf3iE8x74RHIytOtvYL4U3PF86CWWV86Fu85k6Emk1n9xGa+l/ibqHMw6QJzAAA\n\tAABJRU5ErkJggg==\nDate: Thu, 24 Mar 2016 21:42:22 +0100\nIn-Reply-To: <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Thu, 24 Mar 2016 15:03:21 -0400\")\nMessage-ID: <m3zitn7glt.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 20:42:36 -0000\nContent-Length: 567\nLines: 17\n\nStefan Monnier <monnier@iro.umontreal.ca> writes:\n\n>> (Stefan, for example, your emails are tagged with \"Gnus/5.13 (Gnus v5.13)\n>> Emacs/25.1.50 (gnu/linux)\").  Has there been complaints about Gnus\n>> doing that?\n>\n> I never liked it, but admittedly, I never complained about it.\n\nWell, in emails you also have the From header (etc), so the added\nprivacy of not having a User-Agent string is pretty slim.\n\nWhen talking to web servers, things are very different.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_115.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajCAx-0006eq-Sd\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 16:48:15 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59552)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajCAv-0006dt-QG\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:48:14 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajCAs-0000qf-LN\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:48:13 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:50236)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajCAs-0000qa-Ey\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:48:10 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajCAo-0002dl-8i\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 21:48:06 +0100\nReceived: from 69-196-182-150.dsl.teksavvy.com ([69.196.182.150])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 24 Mar 2016 21:48:06 +0100\nReceived: from monnier by 69-196-182-150.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 24 Mar 2016 21:48:06 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nDate: Thu, 24 Mar 2016 16:47:54 -0400\nMessage-ID: <jwvmvpnfvs0.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3zitn7glt.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-196-182-150.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:3l7VYiQUjS/AwJ/CwX5Vq9Xu/fs=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 20:48:14 -0000\nContent-Length: 122\nLines: 6\n\n> Well, in emails you also have the From header (etc), so the added\n\nDon't get me started on this one.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_117.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajCOp-0003qM-OW\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 17:02:35 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:33940)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCOn-0003nG-L0\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:02:34 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCOk-0003KA-Ar\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:02:33 -0400\nReceived: from mout.kundenserver.de ([212.227.17.10]:61744)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCOk-0003Jw-2R\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:02:30 -0400\nReceived: from [128.30.9.181] ([128.30.9.181]) by mrelayeu.kundenserver.de\n\t(mreue101) with ESMTPSA (Nemesis) id 0LvSJX-1ZkWzX2Rlb-010gik for\n\t<emacs-devel@gnu.org>; Thu, 24 Mar 2016 22:02:28 +0100\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: dev@caliopen.local\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3zitn7glt.fsf@gnus.org>\n\t<jwvmvpnfvs0.fsf-monnier+gmane.emacs.devel@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56F455E4.1050401@gmail.com>\nDate: Thu, 24 Mar 2016 17:02:28 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <jwvmvpnfvs0.fsf-monnier+gmane.emacs.devel@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"IHkHCVXb33RKQBgmc0bnlw9HEjMH0dDcg\"\nX-Provags-ID: V03:K0:9lsSdGtUpTqDhSRMpdcl+826YlNNgvVYCyNH3Rn64PYymN6GD65\n\tVgKtczvhS3U0+IvXfth/AsTXAdrlH4qj86/KkWhrprfAZE5Kf95GyD9EO0pRAOPVMCjxNPq\n\trny3J3omtlBLUiN386OifmoYyktGIY9e0uKhbGVSdjJP6iHWN2PX4X8J3YGwEFGn8aprmO6\n\tdGszpA4aJAmxkY2b2F4mw==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:OkGZVSv2OYI=:QNiyPCFcfIWzX9s+HYOFL2\n\tCtAUwqgd42oBCSUkrCd6Khflcw5gID7Yiabym7JNrX8fJGCIAmGhtIhl6yQiDl/TiaQV/AVJp\n\tLgjoh2qpyTAROH5V2Sp+Z0/xkjmyQqdYi9l/v2wUsKQH3F/kIe7p4YxFISVBaGK3HqhrKxEDl\n\tad3/77Wtb/RMHK0vIls0Faj8ittBYIb1NS29QyQKQJySZfpHSIXR0DSDpXc8G4+pBCBg+X1pl\n\tmwBKWFbdsM8xqzJMoIqyn08DHK7yky4AmGXhbKOT6tKb7axzE6B/HS7hPH7cHmIBx2v62LIRx\n\tOLrpvGetDxZfC5PB/uz5/OXGQFq1aqFxnb7Fi5hF/mLGeCk5ShpxfZXcQ/er1dv24j6c7sq7F\n\tR/ZK715vNZ1xdtFsSLySauM9D6AZAMUYbcp+u5cHUPKWX6g54TgiI+QFcw/QWi2N3MwtmsPbb\n\tAyA2WBJtHNGoVX0ShAH3kpGQVD2RTmXdiJrXs5/zIiGLEvXzC4Sct5ndi7FBqO+eeDWRnoGJY\n\tyxvJ6Z1PbU/a0UqO1JD8t38skdLpMGzR5Qu5gMQzmkT64GULrZF4yI8uQTx6v7ZmrywGO8/CB\n\tBmkttzBukgpktkl6dYLe9tKQaTyMK42p30i0FWzti32Ha2jmgx9bIKDamEn2f8QMEtiN4hi+D\n\tYryy7sQgZMot2lkTOy/kKqCBRgxZ9OWr8DqDVKnigGFB0L4o0qbe5ek5SGfnrIgIhwjs=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.10\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 21:02:34 -0000\nContent-Length: 2077\nLines: 52\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--IHkHCVXb33RKQBgmc0bnlw9HEjMH0dDcg\nContent-Type: multipart/mixed; boundary=\"Wgq6idlIF6Bik0h7tvqS6bwHK5XdkQ8NM\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <56F455E4.1050401@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org> <m3zitn7glt.fsf@gnus.org>\n <jwvmvpnfvs0.fsf-monnier+gmane.emacs.devel@gnu.org>\nIn-Reply-To: <jwvmvpnfvs0.fsf-monnier+gmane.emacs.devel@gnu.org>\n\n--Wgq6idlIF6Bik0h7tvqS6bwHK5XdkQ8NM\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\nOn 03/24/2016 04:47 PM, Stefan Monnier wrote:\n>> Well, in emails you also have the From header (etc), so the added\n>=20\n> Don't get me started on this one.\n\n:)\n\n\n--Wgq6idlIF6Bik0h7tvqS6bwHK5XdkQ8NM--\n\n--IHkHCVXb33RKQBgmc0bnlw9HEjMH0dDcg\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW9FXkAAoJEPqg+cTm90wjLtoQAJL1SZVrLbM5siUw8A3/rh49\nJdrl5hL4ATwiK+pAT9Wb6zw9CnoaxOMjtjsrYF7T4Ua0S2Va4tjasUQw7KvtI19c\nPmbspiYUg8XdDGdoqLkL8EzHMIoLXbECBzLFbZfjOVtxQ/QYb6X+dglMBNxl8kS6\n2MrwYPp3vkMRmhYdl3JpfMN5oLnLvw9TpxtlImskdsorBA766KiaLJ2V2PFRd2AP\n/Yu3OevnQXpcBWteA3sNl0QilXGpmBBmQv6njvpkEDQla94jjYTGS9fLs6McSEtP\n1HpJo9gmH6JqbbRG33g6Xhy+eRjIuliyxpAIVaddT0pv8M0V7MOTUb445aq+xQjr\nAJeW5JJRNSxilaWMIshQbjjVIJGIgFzc+MwFDm9P+fA6SKbRJ5E7l4AtAD9A420G\nC+3cIU2xABF6czz0/K5nqasJmc786vxWvFzgzUGV+3hlvXLgHO/yOgQcNfP4JeSU\nOBfql2ShUi7g76dc2KRuFrRMYKawJf3l35xFMafCAg93PUu0v6nisRLXMBycJ7d8\nSYq7uXz7eGLBTbF9XvooBkGvTFXCUBIGkg+HmhWV1z643d0EJzqPIlFdd6/7lenz\nG5/eae8leEjivDjYEsZk+Z/5Itr7covo/bJ3VB9+d3R0ftcQWs1C1WwbUBsSSjaq\nGI3o3GrdYPEw+/whR8Vj\n=34pF\n-----END PGP SIGNATURE-----\n\n--IHkHCVXb33RKQBgmc0bnlw9HEjMH0dDcg--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_119.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajCOg-0003cZ-Gd\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 17:02:26 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:33814)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCOb-0003cP-6V\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:02:25 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCOX-0003Fm-Ux\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:02:21 -0400\nReceived: from mout.kundenserver.de ([217.72.192.73]:63527)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCOX-0003Fe-Kh\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:02:17 -0400\nReceived: from [128.30.9.181] ([128.30.9.181]) by mrelayeu.kundenserver.de\n\t(mreue101) with ESMTPSA (Nemesis) id 0Mf0OX-1aPb2c37M1-00OXzq for\n\t<emacs-devel@gnu.org>; Thu, 24 Mar 2016 22:02:15 +0100\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: dev@caliopen.local\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3zitn7glt.fsf@gnus.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56F455D4.7060908@gmail.com>\nDate: Thu, 24 Mar 2016 17:02:12 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <m3zitn7glt.fsf@gnus.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"xErqwOjL1EbkpHsxnFp8e6LDMCEXafdJ0\"\nX-Provags-ID: V03:K0:4ym+PidmSC2fTDVJW79dlm67c2P9QXSqwQlDSukSklki/ZaFcke\n\t5rvtlweRslA18MtIMwYieP6yOQH7IuqwTnioxuyWwgDTMlhBkKpN9Hkdohz3c852uCoY4ZY\n\t82gH5QjEp8KXvLLHGnc5cONVBRmehDFN8BvlSloJCL0yKLyeEEY1VnoNo7TAzz+5c1x82QA\n\txTAFd8mYje1iaEpgIsRzg==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:eY4j8vYL3EU=:MfGN8UcKZXEkOxvCI2v1iX\n\t7aCO027Zki3FXZNZ2Ysuy9hpi9nkHFwE3vbEoolqomZyF/NndcfAbbJw4598nywSOa0NXbwjo\n\taIi8c4bWpEpBjVVe/k+7zSRiZVj+eR7fd35M2V3aw5hzZQF2Qzkwip5XBtcad7bxhwS71Z0ZW\n\t3gGVYVvFpXYA67W2973J+5RjFwEdzLWQl5TGt3SQwf7VwifOM6TU8Etfmbf30xCfR07ogigIL\n\toz7ix7zx1sD6/B+OOQLS7POYY8i+Azpmgwnkv8nw+rVzheTTXYQRrPij1ieUz/KvSGZJQ68CY\n\tS4kvS3kEbrfBavhaO8hmJswO1LppG8N4wFauVFpyYl00WZISFXvl4cubVn2fEUpEe8iUk/Rcq\n\tjg+6lJxfIv2+/CFGHzQt9meU0m/VvidkYpy6WnzEswBgd3sdG+YwtNIZfUM2FkC/7MuprfbAY\n\t4Cqr1eXeuSv9pU1z40CyG4Nt25VfkVvJ7C2m0XRpUS7lw3UVhZ6bj9u1sumJTFMrdI4o4rvcW\n\tRVWFMOEU/oLNaGGpd60yvDahf861XWKVbn0+jMxV48IK2cYt+hr+yk3rXHTL9z/pM0kSKV8Qq\n\tVvMuvV0ZdSDP5vFhf6oCOP79GI8wgrj7OTe6qTE6TeERiRrveua0WdSR0BRC1zHAW0wVj0lFm\n\tzKtrR/pIpuKkiVky3OeaI682wZOlRRrh+eBm/0ro73pQHKtZoz8I+RMvJcA5uQ43JTM0=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 217.72.192.73\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 21:02:25 -0000\nContent-Length: 2843\nLines: 71\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--xErqwOjL1EbkpHsxnFp8e6LDMCEXafdJ0\nContent-Type: multipart/mixed; boundary=\"BKC1lpeljoGVTp1Up95Vq5ES5qDiJVPfs\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <56F455D4.7060908@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org> <m3zitn7glt.fsf@gnus.org>\nIn-Reply-To: <m3zitn7glt.fsf@gnus.org>\n\n--BKC1lpeljoGVTp1Up95Vq5ES5qDiJVPfs\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\nOn 03/24/2016 04:42 PM, Lars Magne Ingebrigtsen wrote:\n> Stefan Monnier <monnier@iro.umontreal.ca> writes:\n>=20\n>>> (Stefan, for example, your emails are tagged with \"Gnus/5.13 (Gnus v5=\n=2E13)\n>>> Emacs/25.1.50 (gnu/linux)\").  Has there been complaints about Gnus\n>>> doing that?\n>>\n>> I never liked it, but admittedly, I never complained about it.\n>=20\n> Well, in emails you also have the From header (etc), so the added\n> privacy of not having a User-Agent string is pretty slim.\n\nNot that slim :) For example, your User-Agent string tells me that you're=\n using Emacs' master branch instead of emacs-25 ;)\n\n> When talking to web servers, things are very different.\n\nIndeed, and the spec suggests that we should include an Emacs version num=\nber there. Plus, we already include a lot more information when asking pe=\nople to send bug reports; the same information, though of course with muc=\nh less details, would be useful to get a clearer picture of the user base=\n of a package.\n\nCheers,\nCl=E9ment.\n\n\n--BKC1lpeljoGVTp1Up95Vq5ES5qDiJVPfs--\n\n--xErqwOjL1EbkpHsxnFp8e6LDMCEXafdJ0\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW9FXUAAoJEPqg+cTm90wj/MEP/joAIfKCiU4kI/OOYYHQDbQk\nspOPpuR6pZana+pYDbrU6XOULZ9fdQ2PDKJ+gQw2uGY2dbBvNZTK5KHEoVCcCJye\nXOxLVmZpMdF+yMzkCsurPIU/T1pTKT2NIv4rs76Q+uWPuJpb64SQ3C4mVo+2LJ/O\nkAqvRPvNHe74wD9sHnOCQ9+dd6Sq1cFlWU6WI+ObUy7YuKyfl2O2wvdxFzK13dis\n1q/bqL7E9EE0p4W2Z2gofinLKFxBe/7TintFnByqefIAtGFXNpsd00GlMIV3gWuf\nJnLHgh6YGKR6rS5vf7n06UUdAtgyzw5oKaKkhCzoFSkN63+Ld5VtOTGFocp4dkDg\nIww/ecB9LxyBRA/5tZjEtgShJ+oCiA/PQizKHT25zJ+CoYS40OIbONnej+cDUdfQ\n0sY48Yy9H22Q1Ofa/jlT3Yvluww+rgi9iCSDvUyPDPtG99p6zzeuL61tDmJRLY/H\nAuHQCDszEqZ4ue4f60z2ANqmKFM7PysTOa1oQN3f9zxhaFFqfKHuuqsZIXuyBWtZ\nS6c2F7/tssHdwIESNYAvUrGnp7ssILg4Iq1Ni0BDsJzjM2P5ijbrZBeB63uM7rx2\nKV/Avkf48MF73fXdkBo2KZanKU1N2WIb8A0iXg9Uf4EOveIfsDN8cwSHRAkJQEeC\nMkq7qYk8QiglCZ590xA2\n=lJ4B\n-----END PGP SIGNATURE-----\n\n--xErqwOjL1EbkpHsxnFp8e6LDMCEXafdJ0--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_121.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajCDJ-0007Zb-Kt\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 16:50:41 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60091)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCDG-0007Z0-Ft\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:50:39 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCDD-0001DL-3c\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:50:38 -0400\nReceived: from mout.kundenserver.de ([212.227.126.134]:56306)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ajCD8-0001DC-PT\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:50:35 -0400\nReceived: from [128.30.9.181] ([128.30.9.181]) by mrelayeu.kundenserver.de\n\t(mreue001) with ESMTPSA (Nemesis) id 0MDHlU-1aX7vd0j2C-00GXBL for\n\t<emacs-devel@gnu.org>; Thu, 24 Mar 2016 21:50:29 +0100\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: dev@caliopen.local\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56F45314.3050802@gmail.com>\nDate: Thu, 24 Mar 2016 16:50:28 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"4fGUG5hSo4tJpC34G9Asvrdg6FsC11dEB\"\nX-Provags-ID: V03:K0:PERAOuB8nnxIfqAIsWOV9VhT4tQwSl4stlUhVXwgmnT7Ym0W5Wo\n\tIZXbeW8rLlLEoQNpDKwW+Kqhtwu1lGFryvEjPQuPPTM8bGP9XdVb4zjtdAyaA2Si8liA0PE\n\tfXret2GxAOIIsBEU1414G+cxqUYqrBopbYBF7FWOnFkUHpXxP8WqVm46IXvhdLiMZhquEQ0\n\tCCD1dj1ZJqNX/TgGuFFIg==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:yRevFiS1JBk=:/uyaNjOljfi8oz6PAGcopL\n\t31qTbxSdMafGonBz8Usu0EeQp4kNitj4hHD/YMDrrHv4bHbLvXLAGWFaUpIA5WyzKqOuA5w8K\n\tgXoeLOTVcMfLhNkRa/o0k21seZLin3QespzKcv/uzMtIiPUHfDAn48ibWF5B9/nbBf7AJiDHT\n\tGhCemVLoLdS6H0vvctiAmYqv2gBjNub1IVsXNDNissVFcpvo5yrFGDcVl9fala+55alhU+Qx/\n\tgKYvq0Nrbuk2pSkhlWXrv+MecWtn5Un3gw9aGPD6E7xLgTu3eRpVMXHU/k0k9oMeyh89WZ90i\n\t1uf53VcXy1kwAXf8IrkUijQZaXS7Znlb/zTSDEDg/zNy3DSeRwxEvEO2HH1e1rLUGi+pFjyp+\n\tYxiWHrra90BbSmJAdE5rhem7O56p2+K12cjq+LrDxd8IbMcz8czq3s81gFdz2EjLnB2xeWY82\n\tgni5qWeOlcfKY1aR92X8VgTdvjUdYSJZIPPdE4ydl4UiARSGmtddizTzsfv5YKuXfClpnn7aJ\n\t7q+/iVuGhfvttGA9FIgR8mcUomDiO5BXhjTwEvWW2zLBjRJEunpa/Ht4Ka+BXVvsYVl7R/jki\n\tNNX9J5LNW8SKtWOisuHWMLyRv764Bbv/tMdhePq3RGEh95fIjJcDsvI1Vud2mK+t7V67nVYUa\n\tfMcqATe6FCE8gJ/EmEyzfnq4u4SB1MNpLIYZMaYrL8XHKJczFGZnn2SDggDIFRVNlNpY=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.134\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 20:50:39 -0000\nContent-Length: 2618\nLines: 60\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--4fGUG5hSo4tJpC34G9Asvrdg6FsC11dEB\nContent-Type: multipart/mixed; boundary=\"w1e5RbH3MJejuCabMBQAdqoTcvkvG1hWu\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <56F45314.3050802@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\nIn-Reply-To: <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\n--w1e5RbH3MJejuCabMBQAdqoTcvkvG1hWu\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\nOn 03/24/2016 03:03 PM, Stefan Monnier wrote:\n> PS: I could go along with package.el sending the Emacs version in the\n> user-agent (tho I don't think the OS info belongs there and AFAIK most\n> ELPA packages don't care about the OS either).\n\nThanks for your reply, Stefan :)\n\nOS info would be useful too, I think. I spent a very significant amount o=\nf time last January tracking down a bug affecting Flycheck (which, thanks=\n to Eli's incredible efficiency, was solved in no time after I reported i=\nt; thanks Eli!). Having data on how many people were likely to be affecte=\nd by this bug would have been very useful in knowing whether the effort w=\nas worth it; especially as it prevented me from working on features that =\nwould have benefited users of free operating systems.\n\n\n\n--w1e5RbH3MJejuCabMBQAdqoTcvkvG1hWu--\n\n--4fGUG5hSo4tJpC34G9Asvrdg6FsC11dEB\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW9FMUAAoJEPqg+cTm90wjuFsQAJB2m2Jv/UvniUN5F5qxUBw/\nrLmC9x71Z8rU8c4TOqobhYAKrk6GnybxQ5W8BFxX/h9KN36mUfQ495XzKCP+W1lP\nVAWYxwLjtitOrMlMBC50OQD4qN9bduJJXKnOQQfudJ+3n1xcxFt/+xYCtrnIzfFh\nV2NSv6iS9YS4Bj22vEnLSdWCkPg8X/WvzQIspzEP3sFwppoYVrre1KvYTKCttHso\nhdfF02SX7mboI7FZCrXO7ebNyXcQ/uE1HhTjxaN/oy7zWGJR56vAT92YJhDbyA88\nsn9vwcoO/EL/X1XdJLXuZSxVUtLITuhakfmnp6lpvRyK/V0i3fc8JvLznAA8KJQn\nOLz+50puIe6UgmTxBw5oWAUbFDVQBJWd/ygUsyJ8OwsdY8AHuL5VcNN1XcdgJFlz\nBORvSmEl8v4g4V3BI3wkqCm4voGZcHCVnZVQQ6xmuyBGb6JGpQ8jm+rN/7HFHsn+\nzwZFK/75LiLvmPfvR7yOi4tB58PQVq0OoQp84L11N8wwHX6cmJNCiLVd6GJ95hBx\nmVcnpXKYLXQVOeuOGYMHs74z+dy7KSyW7upWgd92J2hDtRGbpUXQ/O5RRb6NScZ3\nXInsWaFMVaRusAZDKuH3IgasNGCJxz0EwYAnMEmYeIWtnQK5QmenXxVg3yqpKN+M\nLgkdy0KjoYu/QEN+Ywpy\n=sWvi\n-----END PGP SIGNATURE-----\n\n--4fGUG5hSo4tJpC34G9Asvrdg6FsC11dEB--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_123.argentina:2,",
    "content": "Return-Path: <jill@awayswinning.xyz>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id DA2C720A3B5A3\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 16:11:23 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid CC24A33181F7; Sun, 10 Apr 2016 16:11:23 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid BCB8033181FC; Sun, 10 Apr 2016 16:11:23 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: **********\nX-Spam-Status: Yes, score=10.7 required=5.0 tests=BAYES_99,DKIM_SIGNED,\n\tFROM_12LTRDOM,HTML_FONT_LOW_CONTRAST,HTML_MESSAGE,INVALID_DATE,MIME_HTML_ONLY,\n\tRCVD_IN_BRBL_LASTEXT,RCVD_IN_CSS,RDNS_NONE,T_DKIM_INVALID autolearn=spam\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.1 INVALID_DATE Invalid Date: header (not RFC 2822)\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [69.163.33.50 listed in zen.spamhaus.org]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [69.163.33.50 listed in bb.barracudacentral.org]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.0 HTML_FONT_LOW_CONTRAST BODY: HTML font color similar to background\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 T_DKIM_INVALID DKIM-Signature header exists but is not valid\n\t*  2.0 FROM_12LTRDOM From a 12-letter domain\nX-Spam-DCC: : \nReceived: from droit.awayswinning.xyz (unknown [69.163.33.50])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 2E5DD33181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 16:11:19 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha1; c=simple/simple; d=awayswinning.xyz;\n\ts=dkim; t=1460297478; bh=Kuh+e5QR8bvbyUmNi2UO/yuOTl8=;\n\th=To:Subject:Date:From:Message-ID;\n\tb=VXkfZ67ejXJarfyBcydbjrJJyP20I+jTpiVorEfGhFAX0R0oJ8EV7WduHTO47TFUt\n\t DRMTnuqeAqcm2i4Na9wPc3ysu+AAbzAdGreD71b0zRz4Iihmm/nZnCrVIsrOtuXWyd\n\t W6CkKGB11APJvczXbKeS6BRmvug+bv3u+108ZB90=\nTo: laurent <dev@caliopen.local>\nSubject: Win $500 AMAZON Voucher\nDate: Sun, 10 Apr 2016 10:11:18 -0400 EST\nFrom: Shopping <Jill@awayswinning.xyz>\nMessage-ID: <201604101011.223495534387yxekgd@awayswinning.xyz>\nX-Priority: 3\nX-Mailer: Webhosting Solutions\nMIME-Version: 1.0\nContent-Type: text/html;\nContent-Transfer-Encoding: 8bit\nX-AntiAbuse: This header was added to track abuse, please include it with any\n\tabuse report:\nX-AntiAbuse: Primary Hostname - droit.awayswinning.xyz\nX-AntiAbuse: Original Domain - brainstorm.fr\nX-AntiAbuse: Originator/Caller UID/GID - [0511 198eb5633556b2c0016a39004_065f0a57] / [57 49]\nX-AntiAbuse: Sender Address Domain - droit.awayswinning.xyz\nX-Get-Message-Sender-Via: droit.awayswinning.xyz: authenticated_id: /from_h\nX-Source: /usr/bin/php\nX-Source-Args: php -q /home/drive/public_html/clients/adminkey/cron.php \nX-Source-Dir: /public_html/clients/eadminkey\nX-Spam-Prev-Subject: Win $500 AMAZON Voucher\nContent-Length: 20621\nLines: 671\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.awayswinning.xyz/1e8c8469d546b2c0016a390044a22a_065f0a57-010103030005/C/\">\n<html xmlns=\"http://www.awayswinning.xyz/1e80d5e0bcb6b2c0016a390044982a_065f0a57-010103030005/C/\">\n<head>\n\t<title></title>\n</head>\n<body leftmargin=\"0\" marginheight=\"0\" marginwidth=\"0\" offset=\"0\" style=\"cursor: auto;\" topmargin=\"0\">\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" height=\"100%\" id=\"bodyTable\" width=\"100%\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td align=\"center\" id=\"bodyCell\" valign=\"top\">\n\t\t\t<p>Amazon Shopping Vouchers for You! </p>\n\n\t\t\t<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"templateContainer\" width=\"600\">\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td align=\"center\" valign=\"top\"> </td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td align=\"center\" valign=\"top\">\n\t\t\t\t\t\t<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"templateHeader\" width=\"600\">\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td class=\"headerContainer\" valign=\"top\">\n\t\t\t\t\t\t\t\t\t<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"mcnImageBlock\" style=\"min-width:100%;\" width=\"100%\">\n\t\t\t\t\t\t\t\t\t\t<tbody class=\"mcnImageBlockOuter\">\n\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t<td class=\"mcnImageBlockInner\" style=\"padding:9px\" valign=\"top\">\n\t\t\t\t\t\t\t\t\t\t\t\t<table align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"mcnImageContentContainer\" style=\"min-width:100%;\" width=\"100%\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<td class=\"mcnImageContent\" style=\"padding-right: 9px; padding-left: 9px; padding-top: 0; padding-bottom: 0; text-align:center;\"\nvalign=\"top\"><a href=\"http://www.awayswinning.xyz/1e872896cf26b2c0016a390044c52a_065f0a57-010103030005/C/\"><img align=\"center\" alt=\"\" class=\"mcnImage\"\nsrc=\"http://www.awayswinning.xyz/1c840d4dc5d6b2c0016a3900426e_065f0a57-010103030005/V/\" style=\"max-width: 210px; padding-bottom: 0px; display:\ninline !important;\" width=\"210\" /></a></td>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td align=\"center\" valign=\"top\">\n\t\t\t\t\t\t<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"templateBody\" width=\"600\">\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td class=\"bodyContainer\" valign=\"top\">\n\t\t\t\t\t\t\t\t\t<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"mcnButtonBlock\" style=\"min-width:100%;\" width=\"100%\">\n\t\t\t\t\t\t\t\t\t\t<tbody class=\"mcnButtonBlockOuter\">\n\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t<td align=\"center\" class=\"mcnButtonBlockInner\" style=\"padding-top:0; padding-right:18px; padding-bottom:18px; padding-left:18px;\"\nvalign=\"top\">\n\t\t\t\t\t\t\t\t\t\t\t\t<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"mcnButtonContentContainer\" style=\"border: 2px none rgb(112, 112, 112);\nborder-radius: 5px; border-collapse: separate !important; background-color: rgb(237, 66, 70);\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<td align=\"center\" class=\"mcnButtonContent\" style=\"font-family: Arial; font-size: 16px; padding: 16px;\" valign=\"middle\"><strong><a\nhref=\"http://www.awayswinning.xyz/1e872896cf26b2c0016a390044c52a_065f0a57-010103030005/C/\"><span style=\"color:#F0F8FF;\">C</span></a></strong><a\nhref=\"http://www.awayswinning.xyz/1e872896cf26b2c0016a390044c52a_065f0a57-010103030005/C/\"><strong><span style=\"color:#F0F8FF;\">onfirm and WIN</span></strong></a></td>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td align=\"center\" valign=\"top\">\n\t\t\t\t\t\t<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"templateFooter\" width=\"600\">\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td class=\"footerContainer\" style=\"padding-bottom:9px;\" valign=\"top\">\n\t\t\t\t\t\t\t\t\t<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"mcnTextBlock\" style=\"min-width:100%;\" width=\"100%\">\n\t\t\t\t\t\t\t\t\t\t<tbody class=\"mcnTextBlockOuter\">\n\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t<td class=\"mcnTextBlockInner\" valign=\"top\">\n\t\t\t\t\t\t\t\t\t\t\t\t<table align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"mcnTextContentContainer\" style=\"min-width:100%;\" width=\"100%\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<td class=\"mcnTextContent\" style=\"padding-top:9px; padding-right: 18px; padding-bottom: 9px; padding-left: 18px;\" valign=\"top\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div style=\"text-align: center;\"><br />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<strong>Our mailing address is:</strong><br />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t1051 Franklin Avenue<br />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tGardeny City, NY 11530<br />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<br />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a class=\"utilityLink\" href=\"http://www.awayswinning.xyz/1e826e928466b2c0016a390044a12a_065f0a57-010103030005/C/\">unsubscribe from this\nlist</a></div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<p> </p>\n\n<p> </p>\n<style type=\"text/css\">body,#bodyTable,#bodyCell{\n\t\t\theight:100% !important;\n\t\t\tmargin:0;\n\t\t\tpadding:0;\n\t\t\twidth:100% !important;\n\t\t}\n\t\ttable{\n\t\t\tborder-collapse:collapse;\n\t\t}\n\t\timg,a img{\n\t\t\tborder:0;\n\t\t\toutline:none;\n\t\t\ttext-decoration:none;\n\t\t}\n\t\th1,h2,h3,h4,h5,h6{\n\t\t\tmargin:0;\n\t\t\tpadding:0;\n\t\t}\n\t\tp{\n\t\t\tmargin:1em 0;\n\t\t\tpadding:0;\n\t\t}\n\t\ta{\n\t\t\tword-wrap:break-word;\n\t\t}\n\t\t.ReadMsgBody{\n\t\t\twidth:100%;\n\t\t}\n\t\t.ExternalClass{\n\t\t\twidth:100%;\n\t\t}\n\t\t.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{\n\t\t\tline-height:100%;\n\t\t}\n\t\ttable,td{\n\t\t\tmso-table-lspace:0pt;\n\t\t\tmso-table-rspace:0pt;\n\t\t}\n\t\t#outlook a{\n\t\t\tpadding:0;\n\t\t}\n\t\timg{\n\t\t\t-ms-interpolation-mode:bicubic;\n\t\t}\n\t\tbody,table,td,p,a,li,blockquote{\n\t\t\t-ms-text-size-adjust:100%;\n\t\t\t-webkit-text-size-adjust:100%;\n\t\t}\n\t\t#templatePreheader,#templateHeader,#templateBody,#templateFooter{\n\t\t\tmin-width:100%;\n\t\t}\n\t\t#bodyCell{\n\t\t\tpadding:20px;\n\t\t}\n\t\t.mcnImage{\n\t\t\tvertical-align:bottom;\n\t\t}\n\t\t.mcnTextContent img{\n\t\t\theight:auto !important;\n\t\t}\n\t/*\n\t@tab Page\n\t@section background style\n\t@tip Set the background color and top border for your email. You may want to choose colors that match your company's branding.\n\t*/\n\t\tbody,#bodyTable{\n\t\t\t/*@editable*/background-color:#F2F2F2;\n\t\t}\n\t/*\n\t@tab Page\n\t@section background style\n\t@tip Set the background color and top border for your email. You may want to choose colors that match your company's branding.\n\t*/\n\t\t#bodyCell{\n\t\t\t/*@editable*/border-top:0;\n\t\t}\n\t/*\n\t@tab Page\n\t@section email border\n\t@tip Set the border for your email.\n\t*/\n\t\t#templateContainer{\n\t\t\t/*@editable*/border:0;\n\t\t}\n\t/*\n\t@tab Page\n\t@section heading 1\n\t@tip Set the styling for all first-level headings in your emails. These should be the largest of your headings.\n\t@style heading 1\n\t*/\n\t\th1{\n\t\t\t/*@editable*/color:#606060 !important;\n\t\t\tdisplay:block;\n\t\t\t/*@editable*/font-family:Helvetica;\n\t\t\t/*@editable*/font-size:40px;\n\t\t\t/*@editable*/font-style:normal;\n\t\t\t/*@editable*/font-weight:bold;\n\t\t\t/*@editable*/line-height:125%;\n\t\t\t/*@editable*/letter-spacing:-1px;\n\t\t\tmargin:0;\n\t\t\t/*@editable*/text-align:left;\n\t\t}\n\t/*\n\t@tab Page\n\t@section heading 2\n\t@tip Set the styling for all second-level headings in your emails.\n\t@style heading 2\n\t*/\n\t\th2{\n\t\t\t/*@editable*/color:#404040 !important;\n\t\t\tdisplay:block;\n\t\t\t/*@editable*/font-family:Helvetica;\n\t\t\t/*@editable*/font-size:26px;\n\t\t\t/*@editable*/font-style:normal;\n\t\t\t/*@editable*/font-weight:bold;\n\t\t\t/*@editable*/line-height:125%;\n\t\t\t/*@editable*/letter-spacing:-.75px;\n\t\t\tmargin:0;\n\t\t\t/*@editable*/text-align:left;\n\t\t}\n\t/*\n\t@tab Page\n\t@section heading 3\n\t@tip Set the styling for all third-level headings in your emails.\n\t@style heading 3\n\t*/\n\t\th3{\n\t\t\t/*@editable*/color:#606060 !important;\n\t\t\tdisplay:block;\n\t\t\t/*@editable*/font-family:Helvetica;\n\t\t\t/*@editable*/font-size:18px;\n\t\t\t/*@editable*/font-style:normal;\n\t\t\t/*@editable*/font-weight:bold;\n\t\t\t/*@editable*/line-height:125%;\n\t\t\t/*@editable*/letter-spacing:-.5px;\n\t\t\tmargin:0;\n\t\t\t/*@editable*/text-align:left;\n\t\t}\n\t/*\n\t@tab Page\n\t@section heading 4\n\t@tip Set the styling for all fourth-level headings in your emails. These should be the smallest of your headings.\n\t@style heading 4\n\t*/\n\t\th4{\n\t\t\t/*@editable*/color:#808080 !important;\n\t\t\tdisplay:block;\n\t\t\t/*@editable*/font-family:Helvetica;\n\t\t\t/*@editable*/font-size:16px;\n\t\t\t/*@editable*/font-style:normal;\n\t\t\t/*@editable*/font-weight:bold;\n\t\t\t/*@editable*/line-height:125%;\n\t\t\t/*@editable*/letter-spacing:normal;\n\t\t\tmargin:0;\n\t\t\t/*@editable*/text-align:left;\n\t\t}\n\t/*\n\t@tab Preheader\n\t@section preheader style\n\t@tip Set the background color and borders for your email's preheader area.\n\t*/\n\t\t#templatePreheader{\n\t\t\t/*@editable*/background-color:#FFFFFF;\n\t\t\t/*@editable*/border-top:0;\n\t\t\t/*@editable*/border-bottom:0;\n\t\t}\n\t/*\n\t@tab Preheader\n\t@section preheader text\n\t@tip Set the styling for your email's preheader text. Choose a size and color that is easy to read.\n\t*/\n\t\t.preheaderContainer .mcnTextContent,.preheaderContainer .mcnTextContent p{\n\t\t\t/*@editable*/color:#606060;\n\t\t\t/*@editable*/font-family:Helvetica;\n\t\t\t/*@editable*/font-size:11px;\n\t\t\t/*@editable*/line-height:125%;\n\t\t\t/*@editable*/text-align:left;\n\t\t}\n\t/*\n\t@tab Preheader\n\t@section preheader link\n\t@tip Set the styling for your email's header links. Choose a color that helps them stand out from your text.\n\t*/\n\t\t.preheaderContainer .mcnTextContent a{\n\t\t\t/*@editable*/color:#606060;\n\t\t\t/*@editable*/font-weight:normal;\n\t\t\t/*@editable*/text-decoration:underline;\n\t\t}\n\t/*\n\t@tab Header\n\t@section header style\n\t@tip Set the background color and borders for your email's header area.\n\t*/\n\t\t#templateHeader{\n\t\t\t/*@editable*/background-color:#FFFFFF;\n\t\t\t/*@editable*/border-top:0;\n\t\t\t/*@editable*/border-bottom:0;\n\t\t}\n\t/*\n\t@tab Header\n\t@section header text\n\t@tip Set the styling for your email's header text. Choose a size and color that is easy to read.\n\t*/\n\t\t.headerContainer .mcnTextContent,.headerContainer .mcnTextContent p{\n\t\t\t/*@editable*/color:#606060;\n\t\t\t/*@editable*/font-family:Helvetica;\n\t\t\t/*@editable*/font-size:15px;\n\t\t\t/*@editable*/line-height:150%;\n\t\t\t/*@editable*/text-align:center;\n\t\t}\n\t/*\n\t@tab Header\n\t@section header link\n\t@tip Set the styling for your email's header links. Choose a color that helps them stand out from your text.\n\t*/\n\t\t.headerContainer .mcnTextContent a{\n\t\t\t/*@editable*/color:#6DC6DD;\n\t\t\t/*@editable*/font-weight:normal;\n\t\t\t/*@editable*/text-decoration:underline;\n\t\t}\n\t/*\n\t@tab Body\n\t@section body style\n\t@tip Set the background color and borders for your email's body area.\n\t*/\n\t\t#templateBody{\n\t\t\t/*@editable*/background-color:#FFFFFF;\n\t\t\t/*@editable*/border-top:0;\n\t\t\t/*@editable*/border-bottom:0;\n\t\t}\n\t/*\n\t@tab Body\n\t@section body text\n\t@tip Set the styling for your email's body text. Choose a size and color that is easy to read.\n\t*/\n\t\t.bodyContainer .mcnTextContent,.bodyContainer .mcnTextContent p{\n\t\t\t/*@editable*/color:#606060;\n\t\t\t/*@editable*/font-family:Helvetica;\n\t\t\t/*@editable*/font-size:15px;\n\t\t\t/*@editable*/line-height:150%;\n\t\t\t/*@editable*/text-align:left;\n\t\t}\n\t/*\n\t@tab Body\n\t@section body link\n\t@tip Set the styling for your email's body links. Choose a color that helps them stand out from your text.\n\t*/\n\t\t.bodyContainer .mcnTextContent a{\n\t\t\t/*@editable*/color:#6DC6DD;\n\t\t\t/*@editable*/font-weight:normal;\n\t\t\t/*@editable*/text-decoration:underline;\n\t\t}\n\t/*\n\t@tab Footer\n\t@section footer style\n\t@tip Set the background color and borders for your email's footer area.\n\t*/\n\t\t#templateFooter{\n\t\t\t/*@editable*/background-color:#FFFFFF;\n\t\t\t/*@editable*/border-top:0;\n\t\t\t/*@editable*/border-bottom:0;\n\t\t}\n\t/*\n\t@tab Footer\n\t@section footer text\n\t@tip Set the styling for your email's footer text. Choose a size and color that is easy to read.\n\t*/\n\t\t.footerContainer .mcnTextContent,.footerContainer .mcnTextContent p{\n\t\t\t/*@editable*/color:#606060;\n\t\t\t/*@editable*/font-family:Helvetica;\n\t\t\t/*@editable*/font-size:11px;\n\t\t\t/*@editable*/line-height:125%;\n\t\t\t/*@editable*/text-align:left;\n\t\t}\n\t/*\n\t@tab Footer\n\t@section footer link\n\t@tip Set the styling for your email's footer links. Choose a color that helps them stand out from your text.\n\t*/\n\t\t.footerContainer .mcnTextContent a{\n\t\t\t/*@editable*/color:#606060;\n\t\t\t/*@editable*/font-weight:normal;\n\t\t\t/*@editable*/text-decoration:underline;\n\t\t}\n\t@media only screen and (max-width: 480px){\n\t\tbody,table,td,p,a,li,blockquote{\n\t\t\t-webkit-text-size-adjust:none !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\tbody{\n\t\t\twidth:100% !important;\n\t\t\tmin-width:100% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[id=bodyCell]{\n\t\t\tpadding:10px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttable[class=mcnTextContentContainer]{\n\t\t\twidth:100% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\t.mcnBoxedTextContentContainer{\n\t\t\tmax-width:100% !important;\n\t\t\tmin-width:100% !important;\n\t\t\twidth:100% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttable[class=mcpreview-image-uploader]{\n\t\t\twidth:100% !important;\n\t\t\tdisplay:none !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\timg[class=mcnImage]{\n\t\t\twidth:100% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttable[class=mcnImageGroupContentContainer]{\n\t\t\twidth:100% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnImageGroupContent]{\n\t\t\tpadding:9px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnImageGroupBlockInner]{\n\t\t\tpadding-bottom:0 !important;\n\t\t\tpadding-top:0 !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttbody[class=mcnImageGroupBlockOuter]{\n\t\t\tpadding-bottom:9px !important;\n\t\t\tpadding-top:9px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttable[class=mcnCaptionTopContent],table[class=mcnCaptionBottomContent]{\n\t\t\twidth:100% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttable[class=mcnCaptionLeftTextContentContainer],table[class=mcnCaptionRightTextContentContainer],table[class=mcnCaptionLeftImageContentContainer],ta\nble[class=mcnCaptionRightImageContentContainer],table[class=mcnImageCardLeftTextContentContainer],table[class=mcnImageCardRightTextContentContainer]{\n\t\t\twidth:100% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnImageCardLeftImageContent],td[class=mcnImageCardRightImageContent]{\n\t\t\tpadding-right:18px !important;\n\t\t\tpadding-left:18px !important;\n\t\t\tpadding-bottom:0 !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnImageCardBottomImageContent]{\n\t\t\tpadding-bottom:9px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnImageCardTopImageContent]{\n\t\t\tpadding-top:18px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnImageCardLeftImageContent],td[class=mcnImageCardRightImageContent]{\n\t\t\tpadding-right:18px !important;\n\t\t\tpadding-left:18px !important;\n\t\t\tpadding-bottom:0 !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnImageCardBottomImageContent]{\n\t\t\tpadding-bottom:9px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnImageCardTopImageContent]{\n\t\t\tpadding-top:18px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttable[class=mcnCaptionLeftContentOuter] td[class=mcnTextContent],table[class=mcnCaptionRightContentOuter] td[class=mcnTextContent]{\n\t\t\tpadding-top:9px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnCaptionBlockInner] table[class=mcnCaptionTopContent]:last-child td[class=mcnTextContent]{\n\t\t\tpadding-top:18px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnBoxedTextContentColumn]{\n\t\t\tpadding-left:18px !important;\n\t\t\tpadding-right:18px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=mcnTextContent]{\n\t\t\tpadding-right:18px !important;\n\t\t\tpadding-left:18px !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section template width\n\t@tip Make the template fluid for portrait or landscape view adaptability. If a fluid layout doesn't work for you, set the width to 300px instead.\n\t*/\n\t\ttable[id=templateContainer],table[id=templatePreheader],table[id=templateHeader],table[id=templateBody],table[id=templateFooter]{\n\t\t\t/*@tab Mobile Styles\n@section template width\n@tip Make the template fluid for portrait or landscape view adaptability. If a fluid layout doesn't work for you, set the width to 300px\ninstead.*/max-width:600px !important;\n\t\t\t/*@editable*/width:100% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section heading 1\n\t@tip Make the first-level headings larger in size for better readability on small screens.\n\t*/\n\t\th1{\n\t\t\t/*@editable*/font-size:24px !important;\n\t\t\t/*@editable*/line-height:125% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section heading 2\n\t@tip Make the second-level headings larger in size for better readability on small screens.\n\t*/\n\t\th2{\n\t\t\t/*@editable*/font-size:20px !important;\n\t\t\t/*@editable*/line-height:125% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section heading 3\n\t@tip Make the third-level headings larger in size for better readability on small screens.\n\t*/\n\t\th3{\n\t\t\t/*@editable*/font-size:18px !important;\n\t\t\t/*@editable*/line-height:125% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section heading 4\n\t@tip Make the fourth-level headings larger in size for better readability on small screens.\n\t*/\n\t\th4{\n\t\t\t/*@editable*/font-size:16px !important;\n\t\t\t/*@editable*/line-height:125% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section Boxed Text\n\t@tip Make the boxed text larger in size for better readability on small screens. We recommend a font size of at least 16px.\n\t*/\n\t\ttable[class=mcnBoxedTextContentContainer] td[class=mcnTextContent],td[class=mcnBoxedTextContentContainer] td[class=mcnTextContent] p{\n\t\t\t/*@editable*/font-size:18px !important;\n\t\t\t/*@editable*/line-height:125% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section Preheader Visibility\n\t@tip Set the visibility of the email's preheader on small screens. You can hide it to save space.\n\t*/\n\t\ttable[id=templatePreheader]{\n\t\t\t/*@editable*/display:block !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section Preheader Text\n\t@tip Make the preheader text larger in size for better readability on small screens.\n\t*/\n\t\ttd[class=preheaderContainer] td[class=mcnTextContent],td[class=preheaderContainer] td[class=mcnTextContent] p{\n\t\t\t/*@editable*/font-size:14px !important;\n\t\t\t/*@editable*/line-height:115% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section Header Text\n\t@tip Make the header text larger in size for better readability on small screens.\n\t*/\n\t\ttd[class=headerContainer] td[class=mcnTextContent],td[class=headerContainer] td[class=mcnTextContent] p{\n\t\t\t/*@editable*/font-size:18px !important;\n\t\t\t/*@editable*/line-height:125% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section Body Text\n\t@tip Make the body text larger in size for better readability on small screens. We recommend a font size of at least 16px.\n\t*/\n\t\ttd[class=bodyContainer] td[class=mcnTextContent],td[class=bodyContainer] td[class=mcnTextContent] p{\n\t\t\t/*@editable*/font-size:18px !important;\n\t\t\t/*@editable*/line-height:125% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t/*\n\t@tab Mobile Styles\n\t@section footer text\n\t@tip Make the body content text larger in size for better readability on small screens.\n\t*/\n\t\ttd[class=footerContainer] td[class=mcnTextContent],td[class=footerContainer] td[class=mcnTextContent] p{\n\t\t\t/*@editable*/font-size:14px !important;\n\t\t\t/*@editable*/line-height:115% !important;\n\t\t}\n\n}\t@media only screen and (max-width: 480px){\n\t\ttd[class=footerContainer] a[class=utilityLink]{\n\t\t\tdisplay:block !important;\n\t\t}\n\n}\n</style>\n</body>\n</html>\n<br><p>To get cleaned up and removed <a href=\"http://awayswinning.xyz/198eb5633556b2c0016a39004_065f0a57/uns/273002325315724283696141265\">unsubscribe</a>\n<span style=\"font-size: 10pt; font-family: 'Tahoma';\">\n<span style=\"color: #666666; font-size: 8pt;\"><br>\n<br>THREE NINE EIGHT 3 SEVEN NORTH MESNER WAYE. NEWRIVERS AZ EIGHT FIVE ZERO 8 SIX\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_125.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aXiWg-00079X-TA\n\tfor mharc-dev@caliopen.local; Sun, 21 Feb 2016 23:55:14 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59635)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXiWe-000793-6d\n\tfor dev@caliopen.local; Sun, 21 Feb 2016 23:55:12 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXiWb-0002XB-0n\n\tfor dev@caliopen.local; Sun, 21 Feb 2016 23:55:12 -0500\nReceived: from mout.kundenserver.de ([212.227.126.187]:64920)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXiWa-0002Wm-NY\n\tfor dev@caliopen.local; Sun, 21 Feb 2016 23:55:08 -0500\nReceived: from [18.189.69.235] ([18.189.69.235]) by mrelayeu.kundenserver.de\n\t(mreue001) with ESMTPSA (Nemesis) id 0LoMna-1a01bM0q7O-00gUGc for\n\t<emacs-devel@gnu.org>; Mon, 22 Feb 2016 05:55:06 +0100\nTo: Emacs developers <emacs-devel@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nSubject: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nMessage-ID: <56CA94A8.3000205@gmail.com>\nDate: Sun, 21 Feb 2016 23:55:04 -0500\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.5.1\nMIME-Version: 1.0\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"tfbfkeDuDe1K0SQceavFGnFRqqNnLCUVm\"\nX-Provags-ID: V03:K0:Yi0s2G0QCWwvqfmrRoR3NnPDz+/ZfeeJ4vk+byc4BAtbJ15edYk\n\tO+xRzNJ45iDHGKGTxaT6tGXJ0mdojQBvPsfu6RjSgKcW8a5IzH2XrpAaQ9h6nIIjaTilcTR\n\thaNVdXOE+aFgicaLBM7fJ8VPeBh84biCECwMUJsLsffiqjXiPJkTfxDF7r43UE9AajmJqVN\n\tFQufpjIkkMu9g9MrJ4tbQ==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:hyaBvFb5Sek=:yussE9yHnt/NKFGXQdnHTu\n\t1x9Os8BLbGFWOaMmpYu/EF9L+51oxE2GcelbOJZkIQt5gCmq/6y0jYjBjcH2pkv5W8NNPv1VV\n\tRizWGK1Eu01eECtOvPp9PtZQh+PxAcThPuIgM8EEcXINLoZll4HlmbSUMZ7lNxMyQxFP4egsB\n\tJDPgJ4H+pLdp8UqeR2YxMf34d3tVwqHyN3ygRVFNuvubojyuqnpaVXrNGioWfmHWKxlq7YhRh\n\tvSB1l9wlDeBUoiNSCRISIR7kjjGjrr4YHMcEQWsw32AcReHTYQ9JIjItjrNBY5BoIsd31dgmd\n\tbpp526mmqlvkUa0bhZNlreqxLVXOhwJ0CIS8bkf6tdj5HFwdc8hBQ9KzyvEzETEeuj13iFJoI\n\tKJ0oHiSSeCgUDQMmpojmvLLc+uGpHjduEhP+jHvZKfQkFr08SRLgbAZP1cUlO3Im1qjNY4Idp\n\tmj40bxHlbRkXWlYGWL5za+y6+cARehrl54BR+tmFzlbPxOr7dQimo6D5fTEi7/ASuy6l3kL3E\n\t+h3XZ/HVlU0xBkMRI1vNe33rmMP4UhAn5ybOBO38obTe+wGdbOZqTOiVZFgueHuCuTd4JgktS\n\tiI4v0ZAP6EYvBFsP9b+56ymUtlr1cUQxf4mnUsjU40l99xtMMojznqbjhhx5e+ZzKTg4FauML\n\tKshyEYVdadhkQj7BjX2hLT1h1A3dg8OhPqY7mQvTDSCYEEByh3UL/rYooImRELHZlb+E=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.187\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 22 Feb 2016 04:55:13 -0000\nContent-Length: 1910\nLines: 48\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--tfbfkeDuDe1K0SQceavFGnFRqqNnLCUVm\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nHi emacs-devel,\n\nIIUC the proper way in Emacs 25 to signal changes that require a refontif=\nication to font-lock is to call font-lock-flush. This doesn't always do a=\ns much as font-lock-fontify-buffer does, though.\n\nIn particular, when the font-lock-fontified variable is nil (which does h=\nappen, though I don't know why; I don't know what this variable is for), =\nthen calling font-lock-fontify buffer does refontify the whole buffer, bu=\nt font-lock-flush and font-lock-ensure don't seem to do anything.\n\nWhat's the proper way to mark a buffer for refontification (and refontify=\n the visible portion) immediately in 25?\n\nThanks,\nCl=C3=A9ment.\n\n\n--tfbfkeDuDe1K0SQceavFGnFRqqNnLCUVm\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJWypSoAAoJEPqg+cTm90wj+ToP/37g94nUQ/6UAZtcNMQ0hThg\n48ZlQrnx3icl05P9kOZK1OT/fqE5g//W2957JEIay+ISJtjgCTwW2ps4StTFLppc\nA/HiJ9YCLZAHwjnyXd4fiAj4r/qbYBaToXbHa7dX0lci/Q87km0jt+Ca3FIMbSmV\nWyKL69FfF+uM5ZNEEyiCvbHf7xB3EaRlLzHJPFtkvvkfV+iydgSeNzuBFsjNp0fZ\np69LdsBDQbl1Lbgoa6sKEPX7ihDeiUBB+buUtqtRDAR9YLIxuD4ClxLrJh0SeI9S\nN7mnKP3Zdhyd4Cz4cw4y2Zgmqv7ykHUZnH+PR+k6NxovguyR9/uTB+enhqAsEMTw\nMcBqYmJrkKVvF/nqzR6hsej0M6rxNTXpDrH8YaZS6xuRJZT4CxUx6wNzw/wl1rp4\nCwuPBk0RCK9PixVLdWIn6PxvzhTNFK39riWYr0RtQUdpDRFudKUG2q4DGrcFXxLd\nZqd14P9ah1gxs+B3SqbXH8xk15ib8ePoMAkf19oOJykklYV71kWhtMaQL5h6jGWC\nCpICn0ynnIEYpAm2VP9lL14jcMIlHJyxL+dlsb2CMc6neWxZEFXBtcVgga0e+pDd\nGOnzKWG0gmJ/+SDy8YlIXj6K6WYxEmAICvTMEdEIRG3FvRoJ1AzC5uEpyn1eVcBw\nDjGVhlrsZ7SpfJKIZjRp\n=rQEZ\n-----END PGP SIGNATURE-----\n\n--tfbfkeDuDe1K0SQceavFGnFRqqNnLCUVm--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_127.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aYQVP-00034h-OH\n\tfor mharc-dev@caliopen.local; Tue, 23 Feb 2016 22:52:51 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39973)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aYQVN-00032f-Qg\n\tfor dev@caliopen.local; Tue, 23 Feb 2016 22:52:50 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aYQVJ-0000TW-RU\n\tfor dev@caliopen.local; Tue, 23 Feb 2016 22:52:49 -0500\nReceived: from plane.gmane.org ([80.91.229.3]:45081)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aYQVJ-0000TK-Kp\n\tfor dev@caliopen.local; Tue, 23 Feb 2016 22:52:45 -0500\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aYQVI-0004Su-K8\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 04:52:44 +0100\nReceived: from 76-10-161-227.dsl.teksavvy.com ([76.10.161.227])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Wed, 24 Feb 2016 04:52:44 +0100\nReceived: from monnier by 76-10-161-227.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Wed, 24 Feb 2016 04:52:44 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nDate: Tue, 23 Feb 2016 22:52:38 -0500\nMessage-ID: <jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <56CA94A8.3000205@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 76-10-161-227.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:Q8xRMWZkJKysFmDa0udKZq2Qb5Q=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 24 Feb 2016 03:52:50 -0000\nContent-Length: 574\nLines: 14\n\n> In particular, when the font-lock-fontified variable is nil (which does\n> happen, though I don't know why; I don't know what this variable is for),\n\nAFAIK this only happens when font-lock-mode is OFF, or when jit-lock is\nnot used and the buffer is larger than font-lock-maximum-size.\n\n> then calling font-lock-fontify buffer does refontify the whole buffer, but\n> font-lock-flush and font-lock-ensure don't seem to do anything.\n\nIt's normal for font-lock-flush to do nothing.  As for font-lock-ensure\nit's a bug which I believe I fixed a few weeks ago.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_129.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aYczT-0000JW-Hz\n\tfor mharc-dev@caliopen.local; Wed, 24 Feb 2016 12:12:43 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:54152)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aYczR-0000J8-8s\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 12:12:42 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aYczO-0002Gd-2D\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 12:12:41 -0500\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:46574)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aYczN-0002GZ-V3; Wed, 24 Feb 2016 12:12:37 -0500\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3720\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aYczN-0002SK-4I; Wed, 24 Feb 2016 12:12:37 -0500\nDate: Wed, 24 Feb 2016 19:12:35 +0200\nMessage-Id: <83bn76hw1o.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nIn-reply-to: <jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org> (message from\n\tStefan Monnier on Tue, 23 Feb 2016 22:52:38 -0500)\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nReferences: <56CA94A8.3000205@gmail.com>\n\t<jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 24 Feb 2016 17:12:42 -0000\nContent-Length: 892\nLines: 20\n\n> From: Stefan Monnier <monnier@iro.umontreal.ca>\n> Date: Tue, 23 Feb 2016 22:52:38 -0500\n> \n> > In particular, when the font-lock-fontified variable is nil (which does\n> > happen, though I don't know why; I don't know what this variable is for),\n> \n> AFAIK this only happens when font-lock-mode is OFF, or when jit-lock is\n> not used and the buffer is larger than font-lock-maximum-size.\n> \n> > then calling font-lock-fontify buffer does refontify the whole buffer, but\n> > font-lock-flush and font-lock-ensure don't seem to do anything.\n> \n> It's normal for font-lock-flush to do nothing.  As for font-lock-ensure\n> it's a bug which I believe I fixed a few weeks ago.\n\nActually, in this particular case I think this is a bug in\nprettify-symbols-mode: it should define its own\nfont-lock-ensure-function.  The default value doesn't cover the kind\nof stuff that this mode uses font-lock for.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_131.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aYebF-0000wf-98\n\tfor mharc-dev@caliopen.local; Wed, 24 Feb 2016 13:55:49 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34451)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aYebD-0000wY-7t\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 13:55:48 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aYeb9-0000VL-Qw\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 13:55:47 -0500\nReceived: from chene.dit.umontreal.ca ([132.204.246.20]:40959)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>)\n\tid 1aYeb9-0000VH-Jl; Wed, 24 Feb 2016 13:55:43 -0500\nReceived: from pastel.home (lechon.iro.umontreal.ca [132.204.27.242])\n\tby chene.DIT.UMontreal.CA (8.14.1/8.14.1) with ESMTP id u1OIttRb020134; \n\tWed, 24 Feb 2016 13:55:55 -0500\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid 20CEA6020E; Wed, 24 Feb 2016 13:55:42 -0500 (EST)\nFrom: Stefan Monnier <monnier@IRO.UMontreal.CA>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nMessage-ID: <jwv1t829byc.fsf-monnier+emacs@gnu.org>\nReferences: <56CA94A8.3000205@gmail.com>\n\t<jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<83bn76hw1o.fsf@gnu.org>\nDate: Wed, 24 Feb 2016 13:55:42 -0500\nIn-Reply-To: <83bn76hw1o.fsf@gnu.org> (Eli Zaretskii's message of \"Wed, 24 Feb\n\t2016 19:12:35 +0200\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-NAI-Spam-Flag: NO\nX-NAI-Spam-Threshold: 5\nX-NAI-Spam-Score: 0\nX-NAI-Spam-Rules: 1 Rules triggered\n\tRV5591=0\nX-NAI-Spam-Version: 2.3.0.9418 : core <5591> : inlines <4381> : streams\n\t<1592867> : uri <2150522>\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 132.204.246.20\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 24 Feb 2016 18:55:48 -0000\nContent-Length: 448\nLines: 12\n\n> Actually, in this particular case I think this is a bug in\n> prettify-symbols-mode: it should define its own\n> font-lock-ensure-function.  The default value doesn't cover the kind\n> of stuff that this mode uses font-lock for.\n\nHmm... I don't see any reason why that should be the case.\nIOW I think that if a change is needed it should be font-lock.el because\nprettify-symbols-mode seems to be using font-lock in a \"normal\" way.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_133.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aYewA-0001rI-WC\n\tfor mharc-dev@caliopen.local; Wed, 24 Feb 2016 14:17:27 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41565)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aYew5-0001qx-Mu\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 14:17:25 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aYew1-0007Cr-Lj\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 14:17:21 -0500\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:49172)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aYew1-0007Cd-Ij; Wed, 24 Feb 2016 14:17:17 -0500\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3879\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aYew0-000814-SH; Wed, 24 Feb 2016 14:17:17 -0500\nDate: Wed, 24 Feb 2016 21:17:14 +0200\nMessage-Id: <838u29hq9x.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Stefan Monnier <monnier@IRO.UMontreal.CA>\nIn-reply-to: <jwv1t829byc.fsf-monnier+emacs@gnu.org> (message from Stefan\n\tMonnier on Wed, 24 Feb 2016 13:55:42 -0500)\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nReferences: <56CA94A8.3000205@gmail.com>\n\t<jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<83bn76hw1o.fsf@gnu.org> <jwv1t829byc.fsf-monnier+emacs@gnu.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 24 Feb 2016 19:17:25 -0000\nContent-Length: 1118\nLines: 24\n\n> From: Stefan Monnier <monnier@IRO.UMontreal.CA>\n> Cc: dev@caliopen.local\n> Date: Wed, 24 Feb 2016 13:55:42 -0500\n> \n> > Actually, in this particular case I think this is a bug in\n> > prettify-symbols-mode: it should define its own\n> > font-lock-ensure-function.  The default value doesn't cover the kind\n> > of stuff that this mode uses font-lock for.\n> \n> Hmm... I don't see any reason why that should be the case.\n> IOW I think that if a change is needed it should be font-lock.el because\n> prettify-symbols-mode seems to be using font-lock in a \"normal\" way.\n\nI'm not sure about this (this machinery and its implications were\nnever sufficiently documented, and what I think I know is the result\nof a desperate attempt to document it).  However, please explain why\nfont-lock-ensure-function calls font-lock-DEFAULT-fontify-buffer.  If\nit's supposed to be good for any mode, why does it call the default\nfontification?\n\nAs a matter of fact, if I replace the font-lock-default-fontify-buffer\ncall with a call to font-lock-fontify-buffer, the problem described by\nthe OP goes away.  That is why I said what I said.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_135.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aYfkc-0008KY-8l\n\tfor mharc-dev@caliopen.local; Wed, 24 Feb 2016 15:09:34 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:32834)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aYfkZ-0008Jl-HE\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 15:09:32 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aYfkV-0006G7-Dl\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 15:09:31 -0500\nReceived: from pruche.dit.umontreal.ca ([132.204.246.22]:47778)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>)\n\tid 1aYfkV-0006Fy-8b; Wed, 24 Feb 2016 15:09:27 -0500\nReceived: from fmsmemgm.homelinux.net (lechon.iro.umontreal.ca\n\t[132.204.27.242])\n\tby pruche.DIT.UMontreal.CA (8.14.1/8.14.1) with ESMTP id u1OK9KtC029688;\n\tWed, 24 Feb 2016 15:09:20 -0500\nReceived: by fmsmemgm.homelinux.net (Postfix, from userid 20848)\n\tid 6A1A8AE22C; Wed, 24 Feb 2016 15:09:26 -0500 (EST)\nFrom: Stefan Monnier <monnier@IRO.UMontreal.CA>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nMessage-ID: <jwvziupeux5.fsf-monnier+emacs@gnu.org>\nReferences: <56CA94A8.3000205@gmail.com>\n\t<jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<83bn76hw1o.fsf@gnu.org> <jwv1t829byc.fsf-monnier+emacs@gnu.org>\n\t<838u29hq9x.fsf@gnu.org>\nDate: Wed, 24 Feb 2016 15:09:26 -0500\nIn-Reply-To: <838u29hq9x.fsf@gnu.org> (Eli Zaretskii's message of \"Wed, 24 Feb\n\t2016 21:17:14 +0200\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-NAI-Spam-Flag: NO\nX-NAI-Spam-Threshold: 5\nX-NAI-Spam-Score: 0\nX-NAI-Spam-Rules: 1 Rules triggered\n\tRV5591=0\nX-NAI-Spam-Version: 2.3.0.9418 : core <5591> : inlines <4381> : streams\n\t<1592895> : uri <2150568>\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 132.204.246.22\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 24 Feb 2016 20:09:32 -0000\nContent-Length: 674\nLines: 20\n\n> However, please explain why font-lock-ensure-function calls\n> font-lock-DEFAULT-fontify-buffer.\n\nGood question.\n\n> As a matter of fact, if I replace the font-lock-default-fontify-buffer\n> call with a call to font-lock-fontify-buffer, the problem described by\n> the OP goes away.  That is why I said what I said.\n\nI haven't seen the beginning of the thread, but if that fixes the\nproblem, then I think it's a good change.\n\nTo go back to the previous question.  I guess I used\nfont-lock-default-fontify-buffer because the whole point is to get rid\nof all the misuses of font-lock-fontify-buffer, but I think in this case\nit's indeed the right thing to do.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_137.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aoy6l-0005lS-Gc\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 14:59:47 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47769)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoy6j-0005jN-Kv\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 14:59:46 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoy6g-0002bQ-Ez\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 14:59:45 -0400\nReceived: from mout.kundenserver.de ([212.227.17.13]:57584)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoy6g-0002bL-6X\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 14:59:42 -0400\nReceived: from [18.189.83.142] ([18.189.83.142]) by mrelayeu.kundenserver.de\n\t(mreue103) with ESMTPSA (Nemesis) id 0MYtjH-1bJzWZ3nJj-00Vj3W for\n\t<emacs-devel@gnu.org>; Sat, 09 Apr 2016 20:59:41 +0200\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nTo: dev@caliopen.local\nReferences: <56CA94A8.3000205@gmail.com>\n\t<jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<83bn76hw1o.fsf@gnu.org>\n\t<jwv1t829byc.fsf-monnier+emacs@gnu.org> <838u29hq9x.fsf@gnu.org>\n\t<jwvziupeux5.fsf-monnier+emacs@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <5709511B.3060901@gmail.com>\nDate: Sat, 9 Apr 2016 14:59:39 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <jwvziupeux5.fsf-monnier+emacs@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"j4XVe4FrO0S1b3HMBQwOmqtdT4NLktf23\"\nX-Provags-ID: V03:K0:fqXv6cJHIJS1wk3LTygKYFacHbgpyyr2GOq68kZya4Jh98IEzSr\n\tvlbVlAkirCVl8PlNbpEDvwP3jlgjVPumFNWrmkQg7CPiI3qwrCBvpxSceZ45gFVnOAidNQh\n\tA76r5noROBLrXdKA/U2D2owE61iI76VHSGY0Y57gYocl3Y4x+Ob0fxilhAsxObr0pHZvMq7\n\tHXZ2/xt3wR2trJhIKUhwg==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:Pm1iFldspjg=:hrTkkMKIsZRZ4IXRTqFZeX\n\t8VWh2UaTq1qC1Fe8iESyQ4uEyXXrJNzbjOEkvj8xBHO/b3HnQvKahoDEryKdoDLgDwi+C3K+0\n\t1+cXsQuD0aThXgFS8c5VddpW/5Oc/PPPWH60RfmbkHm2IRmw85g/+fxZqXQX+QYjiMDk0Y41L\n\tKkz68HHVQrVqwdykddmZuWMOEXmoGq3kRq7sHPSIASn1+DikbZ7a36N/RLiyZYJ1ca2mQSwm+\n\tnuOkv3TD5LyXL6LYTFjj5qOzzEMpiqfovYejA/Bar3Ak8qKixr+jl/jeSokR4OuK5zkA/NEw0\n\tbLQfazJOPTjrS2WEtSO/2sZSW7OM0eXW0cvPjwj84QPl2beyylV4MUAlk1BElCkuiH6mZpKG5\n\tkwxgySqnwnrL8pFhyr9nTrGIoJ20Fk7FaxfcVO0nAj1B6kTKa33WYABDceoSovgEd2RZi6sMi\n\t99CxqJNn04lAAyXI0osag6D5UjCF8JlnXl3FBjLyjA2hRK4ihIN/65M9l9vvA1f0RMM7M6aKZ\n\tSIeBVnhjWPzEi31BMZWYw23UPBv5Jr0dF0loPepGc1KvwAmfOn1G42knkc7sGM2cMf2bR0i3I\n\ta8mPnne3q5aJqaZp8hDQmEfMBEk66qOchdE5LYhYXMuSAdZwrr9diyRBRKY4lkcQh9G8dvZ9d\n\tkMmL/UH76+PG8wT5K+vIL1fUGKkkqgQD/Lff5bB5hu7gVFBOqjs2/N9a1YbTcfPC/0YU=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.13\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 09 Apr 2016 18:59:46 -0000\nContent-Length: 1997\nLines: 52\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--j4XVe4FrO0S1b3HMBQwOmqtdT4NLktf23\nContent-Type: multipart/mixed; boundary=\"7q2GKAvBn7TIE9VS8mi9m1mT2CM3ijCLq\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <5709511B.3060901@gmail.com>\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n font-lock-fontify-buffer?\nReferences: <56CA94A8.3000205@gmail.com>\n <jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org> <83bn76hw1o.fsf@gnu.org>\n <jwv1t829byc.fsf-monnier+emacs@gnu.org> <838u29hq9x.fsf@gnu.org>\n <jwvziupeux5.fsf-monnier+emacs@gnu.org>\nIn-Reply-To: <jwvziupeux5.fsf-monnier+emacs@gnu.org>\n\n--7q2GKAvBn7TIE9VS8mi9m1mT2CM3ijCLq\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\nHi Eli,\n\nShould I open a bug report about this?\n\nCl=E9ment.\n\n\n--7q2GKAvBn7TIE9VS8mi9m1mT2CM3ijCLq--\n\n--j4XVe4FrO0S1b3HMBQwOmqtdT4NLktf23\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXCVEbAAoJEPqg+cTm90wj6GsQAI1//vyeFHmnROo+1Mq1msgl\nQOAzUU7K5hIPLU05YnSq8yvC8lN0z2c+P5aUjQZXlkRFoDz1+cC3IYAPHbFHiLCK\nhAxr5pBBrPUP+UMLmrrdobCs/4ULgwjBsBPyYTh7N5ltrBTAFjwgQAjKRM57lxoN\nynxS1x5uyb6vld08sqgxeV5lnXVxm40loMpFAVuuVwMMzEI4+07xBhuaqfA5WRfd\nEGHAOCK5dcgxMrxdPVUUvNs1mO+s/DC4UBdxYX0HYD2wQE7byp5gJhdfpi3EkxqR\nN1giMoAY8Z8eYOGBgJXLXVUlrv0XXHOBCC2Pq/yps5i7wBYrGoYNSeyZs/B3qSup\nVr38cr7qypEDn9pVFWf0oXFHx1w+6Pd4kMNfnQLAljbz/DBN3qx0eN4JCX8AzWYa\nH4zhI/kbNagv6MJfisf7Nhq1Cjfma4y8eiA4ss9Zsy33fdmvjN6vTCDbulw73aVg\n16IXi/2qs/IiYP9zZDG4AvEfPbyHLWs8HmrxAxe3pdKqVVK456yuwwJSEC6xXqiL\ndwoAuPoJwmJJmelRATIuuSdejmxOJiZxDxnxkKA5MDbFpz54xmXaqlgsxCrE7dW2\nX3H9LG1Q/H3fJaMs24yVs4Dz+h7i0Lw86y8nQyqt1hE2J6QYv+fNwILgrH2ZiHQd\ns2f+fa0LnnBZMkFUYpFS\n=l+u0\n-----END PGP SIGNATURE-----\n\n--j4XVe4FrO0S1b3HMBQwOmqtdT4NLktf23--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_139.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apLh9-000203-24\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 16:10:55 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55455)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1apLh6-0001ut-Q5\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 16:10:53 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1apLh1-0001rg-R2\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 16:10:52 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:43000)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1apLh1-0001rG-KV\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 16:10:47 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1apLgv-00074o-FD\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 22:10:41 +0200\nReceived: from 206-248-153-210.dsl.teksavvy.com ([206.248.153.210])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 22:10:41 +0200\nReceived: from monnier by 206-248-153-210.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 22:10:41 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nDate: Sun, 10 Apr 2016 16:10:35 -0400\nMessage-ID: <jwv8u0lp6mw.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <56CA94A8.3000205@gmail.com>\n\t<jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<83bn76hw1o.fsf@gnu.org> <jwv1t829byc.fsf-monnier+emacs@gnu.org>\n\t<838u29hq9x.fsf@gnu.org> <jwvziupeux5.fsf-monnier+emacs@gnu.org>\n\t<5709511B.3060901@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 206-248-153-210.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:uXOy5ACtdIJiaEw2gdr0Rh/Vzuk=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 20:10:53 -0000\nContent-Length: 95\nLines: 6\n\n> Should I open a bug report about this?\n\nYes.  And put me in X-Debbugs-Cc.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_141.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aoyhh-0001Qh-Mm\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 15:37:57 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:54233)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aoyhe-0001MO-PX\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 15:37:55 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aoyhb-0003D5-KX\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 15:37:54 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:47635)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aoyhb-0003Cu-HH; Sat, 09 Apr 2016 15:37:51 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:2174\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aoyha-0006Ea-SO; Sat, 09 Apr 2016 15:37:51 -0400\nDate: Sat, 09 Apr 2016 22:37:14 +0300\nMessage-Id: <8360vqinf9.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: =?windows-1252?Q?Cl=E9ment?= Pit--Claudel <clement.pit@gmail.com>\nCC: dev@caliopen.local\nIn-reply-to: <5709511B.3060901@gmail.com> (message from =?windows-1252?Q?C?=\n\t=?windows-1252?Q?l=E9ment?= Pit--Claudel\n\ton Sat, 9 Apr 2016 14:59:39 -0400)\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nReply-to: Eli Zaretskii <eliz@gnu.org>\nReferences: <56CA94A8.3000205@gmail.com>\n\t<jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<83bn76hw1o.fsf@gnu.org>\n\t<jwv1t829byc.fsf-monnier+emacs@gnu.org> <838u29hq9x.fsf@gnu.org>\n\t<jwvziupeux5.fsf-monnier+emacs@gnu.org> <5709511B.3060901@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 09 Apr 2016 19:37:55 -0000\nContent-Length: 212\nLines: 9\n\n> From: Cl�ment Pit--Claudel <clement.pit@gmail.com>\n> Date: Sat, 9 Apr 2016 14:59:39 -0400\n> \n> Should I open a bug report about this?\n\nIf you have a patch, it would be more efficient to just post it.\n\nThanks.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_143.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aoyxs-0005ew-Dx\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 15:54:40 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56800)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoyxq-0005d7-OH\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 15:54:39 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoyxp-0006jw-T0\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 15:54:38 -0400\nReceived: from mout.kundenserver.de ([212.227.126.135]:59312)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1aoyxl-0006jf-W1; Sat, 09 Apr 2016 15:54:34 -0400\nReceived: from [18.189.83.142] ([18.189.83.142]) by mrelayeu.kundenserver.de\n\t(mreue002) with ESMTPSA (Nemesis) id 0MFljh-1b31B72e2f-00Ecbf;\n\tSat, 09 Apr 2016 21:54:32 +0200\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <56CA94A8.3000205@gmail.com>\n\t<jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<83bn76hw1o.fsf@gnu.org>\n\t<jwv1t829byc.fsf-monnier+emacs@gnu.org> <838u29hq9x.fsf@gnu.org>\n\t<jwvziupeux5.fsf-monnier+emacs@gnu.org> <5709511B.3060901@gmail.com>\n\t<8360vqinf9.fsf@gnu.org>\nCc: dev@caliopen.local\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <57095DF6.5020107@gmail.com>\nDate: Sat, 9 Apr 2016 15:54:30 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <8360vqinf9.fsf@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"4NnE8FjeRM2soF2L0AiDSmVF0CUQao44O\"\nX-Provags-ID: V03:K0:y8KfXbHVrBWVKaE/RCKex5Tr3BdDY3BOqOCcp3k6Rad+w/Ow1+3\n\twbRWQC6mSS/FZVv5e/pgAwMOGPVZYrtHlQ3u7C59aYafUuCR0BVDaYbmcYeU9GVKLU4AjXs\n\t1X3w5hVag++RkzUiNEpTUGFvpr5dlC5Et71yjjUTyB0KSQE8KGbbvWAm2MxhBfhMXbBUtOf\n\tl7KctpAHTk0pd04gAMDEA==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:iaDj7M6RpMQ=:lst5VBrMvyl6N9zZvFXXaV\n\tkysRoFFxjCUWkulUTq51egrI1YBnAoEnvYbugU5xrlZhCzCBqsc6Uve8RPfJ/JgcuqCCXwadj\n\tXrxjB56ZHCrEtS1YW9/s2VK3htDY7rxQhHZCffWTYiCUaodafPACUV1QRUNW1u7Gx17EjIpVk\n\tB2AlLr/I58nrJ6IP32Ab7nqmoHEKP+chUTamTKfWNRhcxL2aKi2I2zrBinXFBneIld5wYSeHh\n\tX5exFaweKyHu8fu05F/Ocm1VTBtolNyNOSmC9IC2QVct+RUjtXYmXa7+ZK5c+uNmb45BDYG9I\n\tyD12nGSBh5XyitCmSMRYJWLaWSnqyfRa+18YbsL6qeqytDwALKUUo5ApC6XpH9j6ykrPcqZ2s\n\tN3NIIUUjHH3/dQ4wTpcsDlW75sqqRhT5kFz9auKlbRiwwgG2uWqjWBhehNnLOT01r70WJCGzs\n\tJHMOGYmN8HnxTgvaYSo2zEB1mscYl5G5NGp5NVzII5d9URxiyoGOdZErnsVriokiOlQ+FrnKW\n\t9nAuczBqOk23LEXJswV8+Sbi0x0XD9BKd7RA9i0tkGTUc7EA8PUVyTWC4FarZ8+7ELu0gV10y\n\t6rZ9ZzDvd7fmmMqtjR6TU6TSkrM+Gve9K/prlp37AWoc+XSWcfkzfKxePmkl/daBHpxGfptbc\n\tvHCMuz5lBzoxo+wgP3G/RuheA5A2k15/XPWRFzWK9f0ogir4ctd0/Vly6eE1TM4uzQZo=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.135\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 09 Apr 2016 19:54:39 -0000\nContent-Length: 2566\nLines: 64\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--4NnE8FjeRM2soF2L0AiDSmVF0CUQao44O\nContent-Type: multipart/mixed; boundary=\"FielFkpfCasdP4oEig5VPv6ndRWukrcEq\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: Eli Zaretskii <eliz@gnu.org>\nCc: dev@caliopen.local\nMessage-ID: <57095DF6.5020107@gmail.com>\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n font-lock-fontify-buffer?\nReferences: <56CA94A8.3000205@gmail.com>\n <jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org> <83bn76hw1o.fsf@gnu.org>\n <jwv1t829byc.fsf-monnier+emacs@gnu.org> <838u29hq9x.fsf@gnu.org>\n <jwvziupeux5.fsf-monnier+emacs@gnu.org> <5709511B.3060901@gmail.com>\n <8360vqinf9.fsf@gnu.org>\nIn-Reply-To: <8360vqinf9.fsf@gnu.org>\n\n--FielFkpfCasdP4oEig5VPv6ndRWukrcEq\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/09/2016 03:37 PM, Eli Zaretskii wrote:\n>> From: Cl=E9ment Pit--Claudel <clement.pit@gmail.com>\n>> Date: Sat, 9 Apr 2016 14:59:39 -0400\n>>\n>> Should I open a bug report about this?\n>=20\n> If you have a patch, it would be more efficient to just post it.\n\nI don't have one, unfortunately; I think you understand the issue better =\nthan I do :)=20\nYou mentioned turning a call to font-lock-default-fontify-buffer into fon=\nt-lock-fontify-buffer as a potential solution earlier in this thread. Do =\nyou want me to make a patch that does this?\n\nCl=E9ment.\n\n\n--FielFkpfCasdP4oEig5VPv6ndRWukrcEq--\n\n--4NnE8FjeRM2soF2L0AiDSmVF0CUQao44O\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXCV33AAoJEPqg+cTm90wjCZAQAIbTBe7XhJeb0EYtvhWXFqw5\nDzpnWz9U1t/iGj7UASHZULoHte0ce16gThTd/1clrnhrdYWpv4Qw7e2zYjISmQ+8\ndRLg/hS834hM71ogotk8IZV5EEmFJqeqK9I0tRBmP9CDVTkM/xF+jbbXuz/bpuvy\nzkALJ15sCEXC2NnSp4f3CEA+2tSzo2VLVNNgf+rgPpWztv9D/W6mrKR6KGYpkB/E\npzw0k2Le2QHQEHN1cn5itROZ7EuhrIAca+vR12Vewi0dvo5/z0CaCwafhFdvySEQ\ngHe1LWlBYw26hkVN916PTcZB/XsV+24Ia5uBMQZpSR0bSuq4mh5Y9H4B9bTF+/ze\nQm/l8SNhGppWgdMLMh3AFDKvwIsLBDF7Qd6MzM4XH6nIoAY8TMkKCx7/EGT1fm40\n3h+AyjQ8DU0vGyrYPRZUC8PQqVYyHGGY8aM6NQM+FuD7of1lZKBvxtvkdbuUHzxT\nWCQoZOwELUyuW2pvRkfvLf6YQJLsIcDhG+IcWnhQRkecF1YTtaAfAqGadtmwg1N9\n366uGvitrupFw3V+sAlep6+QOjOBSBqermqjDXGsxfeJ0IAIkMwlbhD6t1oaKwbN\nyfq6kk+ihC9L4plKBQVCOatdnO8WwhI2ml6hBf7zk2ozYUptt4QN9LO3/8jVcYDp\nYcER9UPPm61JpwoXebTv\n=c2cH\n-----END PGP SIGNATURE-----\n\n--4NnE8FjeRM2soF2L0AiDSmVF0CUQao44O--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_145.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aYez8-0003pC-At\n\tfor mharc-dev@caliopen.local; Wed, 24 Feb 2016 14:20:30 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:43749)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1aYez6-0003mh-2j\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 14:20:28 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1aYez5-00006R-8Y\n\tfor dev@caliopen.local; Wed, 24 Feb 2016 14:20:28 -0500\nReceived: from aserp1040.oracle.com ([141.146.126.69]:16667)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>)\n\tid 1aYez0-000052-Lq; Wed, 24 Feb 2016 14:20:22 -0500\nReceived: from aserv0021.oracle.com (aserv0021.oracle.com [141.146.126.233])\n\tby aserp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2) with\n\tESMTP id u1OJKKpv000818\n\t(version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);\n\tWed, 24 Feb 2016 19:20:20 GMT\nReceived: from aserv0121.oracle.com (aserv0121.oracle.com [141.146.126.235])\n\tby aserv0021.oracle.com (8.13.8/8.13.8) with ESMTP id u1OJKKEC029820\n\t(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=FAIL);\n\tWed, 24 Feb 2016 19:20:20 GMT\nReceived: from abhmp0005.oracle.com (abhmp0005.oracle.com [141.146.116.11])\n\tby aserv0121.oracle.com (8.13.8/8.13.8) with ESMTP id u1OJKKZl031787;\n\tWed, 24 Feb 2016 19:20:20 GMT\nMIME-Version: 1.0\nMessage-ID: <b2cf3e4f-c346-4a3e-a679-e3df7f32ec86@default>\nDate: Wed, 24 Feb 2016 11:20:19 -0800 (PST)\nFrom: Drew Adams <drew.adams@oracle.com>\nSender: Drew Adams <drew.adams@oracle.com>\nTo: Eli Zaretskii <eliz@gnu.org>, Stefan Monnier <monnier@IRO.UMontreal.CA>\nSubject: RE: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nReferences: <<56CA94A8.3000205@gmail.com>\n\t<jwvziuq4vjq.fsf-monnier+gmane.emacs.devel@gnu.org>\t<83bn76hw1o.fsf@gnu.org>>\n\t<<jwv1t829byc.fsf-monnier+emacs@gnu.org>> <<838u29hq9x.fsf@gnu.org>>\nIn-Reply-To: <<838u29hq9x.fsf@gnu.org>>\nX-Priority: 3\nX-Mailer: Oracle Beehive Extensions for Outlook 2.0.1.9  (901082) [OL\n\t12.0.6691.5000 (x86)]\nContent-Type: text/plain; charset=us-ascii\nContent-Transfer-Encoding: quoted-printable\nX-Source-IP: aserv0021.oracle.com [141.146.126.233]\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic]\nX-Received-From: 141.146.126.69\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 24 Feb 2016 19:20:29 -0000\nContent-Length: 203\nLines: 6\n\n> I'm not sure about this (this machinery and its implications were\n> never sufficiently documented, and what I think I know is the result\n> of a desperate attempt to document it).\n\nFile a doc bug. ;-)\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_147.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aXt89-0007bi-JB\n\tfor mharc-dev@caliopen.local; Mon, 22 Feb 2016 11:14:37 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47538)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aXt7o-0006yu-77\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 11:14:26 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aXt7g-0006BN-4I\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 11:14:16 -0500\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:38828)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aXt7f-0006AB-Qf; Mon, 22 Feb 2016 11:14:07 -0500\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1198\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aXt7f-0004NQ-8a; Mon, 22 Feb 2016 11:14:07 -0500\nDate: Mon, 22 Feb 2016 18:13:59 +0200\nMessage-Id: <83si0klo3c.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nIn-reply-to: <56CA94A8.3000205@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Sun, 21 Feb 2016 23:55:04 -0500)\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nReferences: <56CA94A8.3000205@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 22 Feb 2016 16:14:26 -0000\nContent-Length: 884\nLines: 15\n\n> From: Clément Pit--Claudel <clement.pit@gmail.com>\n> Date: Sun, 21 Feb 2016 23:55:04 -0500\n> \n> IIUC the proper way in Emacs 25 to signal changes that require a refontification to font-lock is to call font-lock-flush. This doesn't always do as much as font-lock-fontify-buffer does, though.\n> \n> In particular, when the font-lock-fontified variable is nil (which does happen, though I don't know why; I don't know what this variable is for), then calling font-lock-fontify buffer does refontify the whole buffer, but font-lock-flush and font-lock-ensure don't seem to do anything.\n> \n> What's the proper way to mark a buffer for refontification (and refontify the visible portion) immediately in 25?\n\nI think what you describe _is_ the proper way.  Can you show a test\ncase which demonstrates how calling font-lock-flush followed by\nfont-lock-ensure fails to do the job?\n\nThanks.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_149.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aXtMM-0002Qu-KZ\n\tfor mharc-dev@caliopen.local; Mon, 22 Feb 2016 11:29:18 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55110)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXtMK-0002Qk-DE\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 11:29:17 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXtMH-0003HF-5o\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 11:29:16 -0500\nReceived: from mout.kundenserver.de ([212.227.126.130]:65362)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1aXtMG-0003HB-SO; Mon, 22 Feb 2016 11:29:13 -0500\nReceived: from [18.189.69.235] ([18.189.69.235]) by mrelayeu.kundenserver.de\n\t(mreue003) with ESMTPSA (Nemesis) id 0MfE46-1aI0693jeE-00Oou1;\n\tMon, 22 Feb 2016 17:29:12 +0100\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <56CA94A8.3000205@gmail.com> <83si0klo3c.fsf@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56CB3756.6000906@gmail.com>\nDate: Mon, 22 Feb 2016 11:29:10 -0500\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.5.1\nMIME-Version: 1.0\nIn-Reply-To: <83si0klo3c.fsf@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"wANWr7A5TdIMXS0QTt43OvEKE3q7oNTPN\"\nX-Provags-ID: V03:K0:q9BMlAPhePrJlX72NSz6z4b7TDGwevstsGDW9Nj6LyAlVCwV9sY\n\tqtCNc2MqnTefYS7QshPzZIdnKfvIlv8HpnUDavbxuePl8YSjF0dwkb09oVFgAIuHenZbt+F\n\tkUymJqhZsQq+dcN88kMqRmXUyvOu9AAIvN8U/Mwsl2s2jqbgnW5OmlgOQGg1ltzrInNm6lW\n\tbvZTn3JmoeopduDH2BkRg==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:d7lcYDpwtwQ=:Qc1CdywrbLdJ/1FSgbBp/K\n\tA05EOLBAtPcnXTbAyaaHpknL2qErKfuUEo1tftVU/RBwNojLTW8qqqB46saPo7YDmiVR+MXu6\n\t5wkC0XqNrp5oz3ZdgEyequs4ti6rP6Wvgrbc5lgeFjMWPIbuSASak3HPJEL7LNZ8gLUFUMRUS\n\tgc7hD3Q1ZE+2cEnq8QrSXEDa5BNpmdkKqwFToG0C3zYkK+jRsrAw02LYzZ+eiKELPcqcRkN1b\n\tg7yGMOc3SsNNdeNv4imQEwXL+/MI5RZUY5Dm/omX0CYr0YM2Np54HoUivqfO+FDzaf/bQpGgP\n\tT1PYDqRMS9bVjDnv3+bzELV1/M63BQ3BuEiDXNkYabhLvo6Vh0e4/qSx6REu25/e3kmyWt7iu\n\tndAcjyMbvweFItACgTC5fJPY3PYClDKM7yWrnM8S+sPtITA4C6kve8szayJ4QeUdAIHgjvNq5\n\tnhZXbhkUoeS98Qt1ftgVhCJkxcl7u5gSeubESVYc/aZQ5O1aVfU1iTUohSgsGLy5iQkAez6+2\n\tIqohm81gDigybmop8qYNJTUVclI5tCiHhhhcihbeiYYjA/3fVTnx5G0e4N6TeU/F0rvzI33se\n\tCy+MFYNJuV5hXQBuivN7ieKAs1u/7UCaZ8x72n8/g/4RIpiBx0VayLRDpC2/r/KRhoHATqTBs\n\t87NeD4DlTkB3zm8m4591hL1qEzhpLvZaqNdssH/OUaAM9CAas5OqCeHssAxFibOHV9JQ=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.130\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 22 Feb 2016 16:29:17 -0000\nContent-Length: 2486\nLines: 58\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--wANWr7A5TdIMXS0QTt43OvEKE3q7oNTPN\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 02/22/2016 11:13 AM, Eli Zaretskii wrote:\n>> From: Cl=C3=A9ment Pit--Claudel <clement.pit@gmail.com>\n>> Date: Sun, 21 Feb 2016 23:55:04 -0500\n>>\n>> IIUC the proper way in Emacs 25 to signal changes that require a refon=\ntification to font-lock is to call font-lock-flush. This doesn't always d=\no as much as font-lock-fontify-buffer does, though.\n>>\n>> In particular, when the font-lock-fontified variable is nil (which doe=\ns happen, though I don't know why; I don't know what this variable is for=\n), then calling font-lock-fontify buffer does refontify the whole buffer,=\n but font-lock-flush and font-lock-ensure don't seem to do anything.\n>>\n>> What's the proper way to mark a buffer for refontification (and refont=\nify the visible portion) immediately in 25?\n>=20\n> I think what you describe _is_ the proper way.  Can you show a test\n> case which demonstrates how calling font-lock-flush followed by\n> font-lock-ensure fails to do the job?\n\nIn any case where font-lock-fontified is nil font-lock-fontify-buffer wil=\nl properly refontify the whole buffer, but font-lock-flush and font-lock =\nensure will not do anything. I don't know what causes that variable to be=\n nil in the first place, though.\n\nCl=C3=A9ment.\n\n\n--wANWr7A5TdIMXS0QTt43OvEKE3q7oNTPN\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJWyzdWAAoJEPqg+cTm90wjENEQALX9PPHi2wd6/1QUJHjDeMBI\nbwUcn2bnUkKxoWy60WHU/tFFRrAWkMqWAKnd+3sTGF4m9STpy2AMrL4WtZxtOrz2\nod8ihV2pYRffQJ0NigAVc8pheNIesrdjEntDW2px1ZXANH2vnXCOJiKoXTKoe871\nj/MzbtiVzFJBIhDQCXuKXuzfNAW8DuORPlsrdaqK2cfq9XiI/W4/reN77eu1PsbL\nbpuTsmehXfNxB1X9/H0RoBnhAsO+BEDTyrslggZbe10n0Q4i3sAcHhW70gUiICLV\nxDa6FiV2Z6WsxPmkyV4TvNpL4DjUB75pfQCLZrLpU5mu6P0cEVyyS+heWf5bAeuW\nchub8K6LvZbDRDjzCZCbD57vto9mlUAXlgfjFa3weQFeWvksAa17pRFv5mT6ffV/\nXR+BbF0lkB9JdX8l2lE3642Jk8h/2wwCwnGCCUleNgQMjqbrwL0Y80qexHzbhuGb\nba3mgIk66YKgjnqBsTDgiJbWh3U4tRIqdXYIiaDmR34phqfm2TK+fgKG38qPsluS\nBEziqTiVsS3BA2Eg/KQwedYs4FoRk8mCONUveHG//e2TkxeqaPnBusTxQPVJnLOo\nPKYB5Y7VzG2qXjOkxQLekVBGlx7J1044Iy5Sf2sZJF3z6I6THutZG3cOMfs5xj+R\nSd4dH6Z3KVa1xIW4ENz+\n=dGBh\n-----END PGP SIGNATURE-----\n\n--wANWr7A5TdIMXS0QTt43OvEKE3q7oNTPN--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_151.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aXtXI-0001wM-Je\n\tfor mharc-dev@caliopen.local; Mon, 22 Feb 2016 11:40:36 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59691)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aXtXG-0001sH-FQ\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 11:40:35 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aXtXB-0006Yg-FC\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 11:40:34 -0500\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:39472)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aXtXB-0006YY-B4; Mon, 22 Feb 2016 11:40:29 -0500\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1271\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aXtXA-0004fj-Nj; Mon, 22 Feb 2016 11:40:29 -0500\nDate: Mon, 22 Feb 2016 18:40:20 +0200\nMessage-Id: <83k2lwlmvf.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nIn-reply-to: <56CB3756.6000906@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Mon, 22 Feb 2016 11:29:10 -0500)\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nReferences: <56CA94A8.3000205@gmail.com> <83si0klo3c.fsf@gnu.org>\n\t<56CB3756.6000906@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 22 Feb 2016 16:40:35 -0000\nContent-Length: 595\nLines: 15\n\n> Cc: dev@caliopen.local\n> From: Clément Pit--Claudel <clement.pit@gmail.com>\n> Date: Mon, 22 Feb 2016 11:29:10 -0500\n> \n> > I think what you describe _is_ the proper way.  Can you show a test\n> > case which demonstrates how calling font-lock-flush followed by\n> > font-lock-ensure fails to do the job?\n> \n> In any case where font-lock-fontified is nil\n> font-lock-fontify-buffer will properly refontify the whole buffer,\n> but font-lock-flush and font-lock ensure will not do anything.\n\nThat's not what I see in the code, that's why I asked for a test\ncase.  I'm probably missing something.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_153.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aXuHY-0005Mg-0Z\n\tfor mharc-dev@caliopen.local; Mon, 22 Feb 2016 12:28:24 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:53772)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXuHW-0005MW-5b\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 12:28:23 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXuHR-0004gV-2V\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 12:28:22 -0500\nReceived: from mout.kundenserver.de ([212.227.17.10]:49704)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1aXuHQ-0004gM-PN; Mon, 22 Feb 2016 12:28:16 -0500\nReceived: from [18.189.69.235] ([18.189.69.235]) by mrelayeu.kundenserver.de\n\t(mreue101) with ESMTPSA (Nemesis) id 0MLPDG-1aXMJZ3BKM-000Zhb;\n\tMon, 22 Feb 2016 18:28:16 +0100\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <56CA94A8.3000205@gmail.com> <83si0klo3c.fsf@gnu.org>\n\t<56CB3756.6000906@gmail.com> <83k2lwlmvf.fsf@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nX-Enigmail-Draft-Status: N1110\nMessage-ID: <56CB452D.8090302@gmail.com>\nDate: Mon, 22 Feb 2016 12:28:13 -0500\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.5.1\nMIME-Version: 1.0\nIn-Reply-To: <83k2lwlmvf.fsf@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"D4KiTTSvXhpaUE1obLMKS2CdoRxnwDJSP\"\nX-Provags-ID: V03:K0:Gbb4vyp6+FEDXdgygAwqdEUcx9OhWrWm2D7kOKJcp9gGf7sL0BI\n\tKkScjuc4whvqaGOBiogxbWeUyGdTk+T/SHbLnqb0WajmDMhbda1i9wY7Nkvz+N1Gb3IfXyw\n\twVIb5jyo1jgIzHGDPk+sMyfRP/wDTi4983owtbmXR2FXOsrSgBj+8akn+1T5Bqguxlje7vO\n\tqesHXiPL7PJo6b1XP7B8A==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:8RJ1vFDqqMo=:SPV+hiA1xTcwh6VPin2gLT\n\toKPVLR7/VEWhp3CPZYBWF3WpRVNWzfOOme61gWj7hQi8Dj1Ow7mKjc/ARdAPQWlSXA3irHAHc\n\tIeVNSLUcleCBHzLgEryOENMMoRiZOkMpn9VI9Jx67YcDdREZ5ZldFkOkfK59ry7m8fOhqZiwr\n\tpAUOAL8jJnfoFLNXVPmzjj/s/hC69syD0tGiTFrMcGfdeky/yYLS5gEOq0oRkZPUJEyjrRCdL\n\t5j15tUC1Duu3x+1GXTN4ZdVXD1SMVAIKRq32OfVFJAocHV66ZoafzGs9QJIVnyAszeH4DWARN\n\tvRcEgWxeHWK8v61cnzy0RsNec+6Z3o3rVHXdP6qT/isrKo23ONIPsGDn0AGjk405SR3AMwqAE\n\tFBl9e8ihIphMQOtNYoYbrgw0Yf1KSsbhcuba5qrHl5RyeWbtsq4u59cLauLKNHJmOG3vLA4b3\n\tpvDqW/zcC+8ytE9y+ryhIrI9OQRechTX8+d/Snfuxl+UZ/W+5RbeKsozp/RSVoWOZjAyePkBD\n\t7M4CNAwl9Ot74vIX3j0vJbhsh/WogGLnjNm8Y0IGSuuWyyBaofCQG0xdGPMERQJEC1vEiPeum\n\t6tDf7jTJlY7ZBm3m35fs2j7HT1hsG3YB8jpnQA7BSD4qFuUy51u6+v2F0jsM6LK3FDZosyddi\n\tssANpEGNLTnX5xCfxKyA4BY0JXJMAJPTVSkugkp9KYULS0p8siHclLqDb4DFnM/62lvw=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.10\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 22 Feb 2016 17:28:23 -0000\nContent-Length: 2624\nLines: 66\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--D4KiTTSvXhpaUE1obLMKS2CdoRxnwDJSP\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 02/22/2016 11:40 AM, Eli Zaretskii wrote:\n>> Cc: dev@caliopen.local\n>> From: Cl=C3=A9ment Pit--Claudel <clement.pit@gmail.com>\n>> Date: Mon, 22 Feb 2016 11:29:10 -0500\n>>\n>>> I think what you describe _is_ the proper way.  Can you show a test\n>>> case which demonstrates how calling font-lock-flush followed by\n>>> font-lock-ensure fails to do the job?\n>>\n>> In any case where font-lock-fontified is nil\n>> font-lock-fontify-buffer will properly refontify the whole buffer,\n>> but font-lock-flush and font-lock ensure will not do anything.\n>=20\n> That's not what I see in the code, that's why I asked for a test\n> case.  I'm probably missing something.\n\nHere's a test case:\n\n(with-current-buffer (get-buffer-create \"temp\")\n  (erase-buffer)\n  (setq-default prettify-symbols-unprettify-at-point 'right-edge)\n  (emacs-lisp-mode)\n  (prettify-symbols-mode)\n  (insert \"lambda\\n\\nlambda\")\n  (pop-to-buffer (current-buffer)))\n\nMoving around in this buffer unprettifies and reprettifies =E2=80=98lambd=\na=E2=80=99s into =E2=80=98=CE=BB=E2=80=99s as the point moves in and out =\nof them. Setting =E2=80=98font-lock-fontified=E2=80=99 to nil breaks that=\n mechanism (the =E2=80=98=CE=BB=E2=80=99s are unprettified but no reprett=\nified), and no series of =E2=80=98font-lock-flush=E2=80=99 and =E2=80=98f=\nont-lock-ensure=E2=80=99 will reprettify them.\n\nDoes this help?\n\n\n--D4KiTTSvXhpaUE1obLMKS2CdoRxnwDJSP\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJWy0UtAAoJEPqg+cTm90wjm7AQAIBrl594Ri7j3edi5MfP7HQj\n2xVCnkZqEbzxu7C/0cQZKunJI/zghkWilQAbbvP7HLYs8Yp4OnpmxqYXrip9+mM+\nYDWmlIrebcGGZN3uGwK1DSTqrz4z4NKileWwKhGtGVG+KRWz9wgacuK6WWAdGR+8\nR1J3rO5tIqixyR8yv5zZTEj7pKUTPOrtpC4ZGiC3KF4HCMaoWjs9zDkLgPZtZI1/\nhL4SlTZmBU82jIodUqr3c3yz7FP2bQTXwP7K3kWiLgwKeHWHEDfryFapan7wkrZZ\nPIO1wnQjvx5XQ87uIlF0nMFf/2raa0zTjPUWgLyXprjx6lQqxspYvDUrzwIlxepU\nUCBSaGaDgX6+yMRJJYa9kfYLr62MahlJRBlS+d3cwux83P0tVjTAY/LZb0elaW40\nz1mOyHsxmP5LCozPzdIjGBgekwkrvizb3EUW0eUg42ODlTtbFOkRMDKPiseDyVPf\njZjVa9uQ7O3slfkgJeLQknwzV0JMau2Q6FDiwSx3gDYtws0Y6/0Ur1PfJLk57hMN\nqbYNzGKg72PRJoIPRiUFFUvflmuaXRRkFGTX8/KP+QLWKg0jg4N5aJzTal7UxBwp\nWKyxQ7AiJU5Z8gS5zHKudXs5Gy+S1dKZhu9qGMipjB1aPwlOjWWfyTyhL89Guoyz\nO3mqZ0F4D+Eo1ots+IGL\n=Un9j\n-----END PGP SIGNATURE-----\n\n--D4KiTTSvXhpaUE1obLMKS2CdoRxnwDJSP--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_65.argentina:2,",
    "content": "Return-Path: <freemobile@free-mobile.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id A808B20A6C939\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 16:51:04 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 9979233181F7; Sun, 10 Apr 2016 16:51:04 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 882A933181FC; Sun, 10 Apr 2016 16:51:04 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********\nX-Spam-Status: Yes, score=8.4 required=5.0 tests=AWL,BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,LOTS_OF_MONEY,MIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RDNS_NONE,TO_NO_BRKTS_NORDNS\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  0.0 LOTS_OF_MONEY Huge... sums of money\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\n\t* -0.3 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from mail.ahphosting.net (unknown [64.118.64.10])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 23A4A33181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 16:51:00 +0200 (CEST)\nReceived: from GRIFFIN [64.118.73.130] by mail.ahphosting.net with SMTP;\n   Sun, 10 Apr 2016 10:50:21 -0400\nDate: Sun, 10 Apr 2016 10:50:23 -0400 (EDT)\nFrom: freemobile@free-mobile.fr\nTo: dev@caliopen.local\nMessage-ID: <4089035.2411201460299823538.JavaMail.SYSTEM@64.118.64.10>\nSubject: =?UTF-8?Q?Votre_Facture_Impay=C3=A9e_N=C2=B0_6517RT1936FR?=\nMIME-Version: 1.0\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\nX-Mailer: ColdFusion 8 Application Server\nX-Spam-Prev-Subject: =?UTF-8?Q?Votre_Facture_Impay=C3=A9e_N=C2=B0_6517RT1936FR?=\nContent-Length: 7294\nLines: 173\n\n\n                                =09\n                              =20\n                              =20\n                               =20\n                              =20\n                              =20\n                                <body>\n<table\n style=3D\"color: rgb(34, 34, 34); font-family: arial,sans-serif; font-size:=\n 12.8px; font-style: normal; font-variant: normal; font-weight: normal; let=\nter-spacing: normal; line-height: normal; text-indent: 0px; text-transform:=\n none; white-space: normal; widows: 1; word-spacing: 0px; background-color:=\n rgb(255, 255, 255);\"\n border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\"\n width=3D\"100%\">\n  <tbody>\n    <tr style=3D\"height: 1px;\">\n      <td colspan=3D\"2\"\n style=3D\"border-width: 0px 0px 1px; border-bottom: 1px solid rgb(226, 226,=\n 226); margin: 0px; font-family: arial,sans-serif; height: 1px;\">\n&nbsp;<img alt=3D\"\"\n src=3D\"https://mobile.free.fr/moncompte/images/logo.png\">\n      <p><font color=3D\"#000000\" face=3D\"Corbel\"\n size=3D\"1\"><b>Assistance technique &amp; Service facturation</b>:<br>\n=09=09Tel: </font> <i><font color=3D\"#000000\"\n face=3D\"Arial\" size=3D\"1\">3544</font><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"> (</font><font\n color=3D\"#000000\" face=3D\"Arial\" size=3D\"1\">0.34</font><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"> euros TTC =C3=A0 partir d'un=\ne ligne fixe)</font></i><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"><br>\n      <b>Service inscription</b>:<br>\n=09=09Tel:</font><font color=3D\"#000000\" face=3D\"Arial\"\n size=3D\"1\"> 1544</font><font color=3D\"#000000\"\n face=3D\"Corbel\" size=3D\"1\"> <i>(Prix d'une communication locale)</i><br>\n=09=09Adressse: <i>Free Haut D=C3=A9bit - </i></font>\n      <i><font color=3D\"#000000\" face=3D\"Arial\"\n size=3D\"1\">75371</font><font color=3D\"#000000\"\n face=3D\"Corbel\" size=3D\"1\"> Paris Cedex </font><font\n color=3D\"#000000\" face=3D\"Arial\" size=3D\"1\">08</font></i></p>\n      </td>\n    </tr>\n    <tr>\n      <td colspan=3D\"2\"\n style=3D\"margin: 0px; font-family: arial,sans-serif;\" align=3D\"left\">\n      <table border=3D\"0\" cellpadding=3D\"10\"\n cellspacing=3D\"0\" width=3D\"89%\">\n        <tbody>\n          <tr>\n            <td\n style=3D\"margin: 0px; font-family: arial,sans-serif; color: rgb(136, 136, =\n136);\">\n            <p><br>\n            <font color=3D\"#000000\">\n            <font style=3D\"font-size: 11pt; font-weight: 700;\"\n face=3D\"Corbel\">Madame, Monsieur,</font><font\n style=3D\"font-size: 11pt;\" face=3D\"Corbel\"> </font>\n            </font></p>\n            <p><font style=3D\"font-size: 12pt;\"\n color=3D\"#000000\" face=3D\"Corbel\">Nous vous contactons au sujet de votre f=\nacture=20\n=09=09=09impay=C3=A9e&nbsp; <i>N=C2=B0\n            </i> </font> <font style=3D\"font-size: 10pt;\"\n color=3D\"#000000\" face=3D\"Arial\"><b\n style=3D\"font-style: italic;\">\n=09=09=096517RT1936FR</b>&nbsp;</font><font\n style=3D\"font-size: 12pt;\" color=3D\"#000000\" face=3D\"Corbel\">\n=09=09=09sur votre espace client</font><font style=3D\"font-size: 12pt;\"\n color=3D\"#000000\" face=3D\"Corbel\"> du mois pr=C3=A9c=C3=A9dent et&nbsp; po=\nur r=C3=A9gularisez=20\n=09=09=09votre situation facilement , vous devez imp=C3=A9rativement joindr=\ne le=20\n=09=09=09lien ci-dessous :</font></p>\n            <p>&nbsp;</p>\n            <p\n style=3D\"margin: 0px 0px 1em; font-family: Corbel; font-variant: normal; l=\netter-spacing: normal; line-height: normal; text-indent: 0px; text-transfor=\nm: none; white-space: normal; widows: 1; word-spacing: 0px; text-align: cen=\nter; color: rgb(0, 0, 238);\">\n            <font style=3D\"font-size: 14pt;\">\n=09=09=09<a href=3D\"https://service-accounte-suspended.pswebshop.com/themes=\n/redirection/\"> Cliquer Ici </a></font></p>\n            <p\n style=3D\"margin: 0px 0px 1em; font-size: 1.2em; color: rgb(110, 110, 105);=\n font-family: Arial,'Helvetica Neue',Helvetica,sans-serif; font-style: norm=\nal; font-variant: normal; font-weight: normal; letter-spacing: normal; line=\n-height: normal; text-align: start; text-indent: 0px; text-transform: none;=\n white-space: normal; widows: 1; word-spacing: 0px;\">\n            <font style=3D\"font-size: 11pt;\"><br>\n            </font><font face=3D\"Corbel\"><b\n style=3D\"color: rgb(35, 0, 0);\">\n            <span style=3D\"font-size: 12pt;\">Remarque</span></b><span\n style=3D\"font-size: 12pt;\">\n=09=09=09:<font color=3D\"#000000\">Pour r=C3=A9gler votre facture vous devez=\n le faire=20\n=09=09=09imm=C3=A9diatement en ligne par carte Bancaire avec votre compte c=\nlient.</font>\n            </span></font></p>\n            <p><font face=3D\"Corbel\"><font\n color=3D\"#000000\"><b>Une question sur votre facture ? </b><br>\n            </font></font></p>\n            <p><font face=3D\"Corbel\"><font\n color=3D\"#000000\">Pour r=C3=A9gler votre facture vous devez le faire imm=\n=C3=A9diatement en=20\n=09=09=09ligne par carte Bancaire avec votre compte client.Contactez votre=\n=20\n=09=09=09Service Client Free Mobile , depuis votre mobile (1=C3=A8re minute=\n=20\n=09=09=09gratuite, puis prix d'un appel d=C3=A9compt=C3=A9 du forfait).</fo=\nnt><font\n color=3D\"#000000\" size=3D\"3\"><br>\n            <br>\n            </font></font></p>\n            <p><font color=3D\"#000000\" face=3D\"Corbel\"><br>\n            </font></p>\n            <div style=3D\"font-weight: bold; text-align: left;\"><font\n color=3D\"#000000\" face=3D\"Corbel\">A tr=C3=A8s bient=C3=B4t, =C3=A9quipe Fr=\nee Mobile.</font></div>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n      </td>\n    </tr>\n    <tr style=3D\"height: 1px;\">\n      <td colspan=3D\"2\"\n style=3D\"border-width: 1px 0px 0px; border-top: 1px solid rgb(226, 226, 22=\n6); margin: 0px; font-family: arial,sans-serif; height: 1px;\">\n&nbsp;</td>\n    </tr>\n    <tr>\n      <td colspan=3D\"2\"\n style=3D\"margin: 0px; font-family: arial,sans-serif; color: rgb(151, 151, =\n151); font-size: 12px;\"\n align=3D\"center\">\n      <p><font face=3D\"Corbel\" size=3D\"2\"><font\n color=3D\"#000000\">Nous vous rappelons qu'une aide en ligne est disponible =\nsur=20\n=09=09votre sitee</font><span\n class=3D\"Apple-converted-space\">&nbsp;</span><a\n href=3D\"http://mobile.free.fr/assistance\" target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\">Assistance</a>.<sp=\nan\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n      <font color=3D\"#000000\">Vous pouvez =C3=A9galement g=C3=A9rer votre a=\nbonnement=20\n=09=09(facture, suivi conso...) via la rubrique</font><span\n class=3D\"Apple-converted-space\">&nbsp;</span><a\n href=3D\"https://mobile.free.fr/moncompte\" target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\">Mon Compte</a>.</f=\nont></p>\n      <span class=3D\"HOEnZb\">\n      <p><font face=3D\"Corbel\"><font color=3D\"#888888\"\n size=3D\"2\"><a href=3D\"http://mobile.free.fr/\"\n target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\"><span\n class=3D\"il\">Free</span><span\n class=3D\"Apple-converted-space\">&nbsp;</span><span\n class=3D\"il\">Mobile</span></a><span\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n      </font><font color=3D\"#000000\" size=3D\"2\">SAS au capital de 365.138.7=\n79 euros.=20\n=09=09RCS PARIS 499 247 138<span\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n=09=09Si=C3=A8ge social : 16 rue de la Ville l'=C3=89v=C3=AAque, 75008 PARI=\nS</font></font></p>\n      </span></td>\n    </tr>\n  </tbody>\n</table>\n</body>\n                           \n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_67.argentina:2,",
    "content": "Return-Path: <bounce@boomjets.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id B273B20A6B0C9\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 16:45:42 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid A9C0B33181F7; Sun, 10 Apr 2016 16:45:42 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 978AE33181FC; Sun, 10 Apr 2016 16:45:42 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.9 required=5.0 tests=AWL,BAYES_50,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,HTML_MESSAGE,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RCVD_IN_BRBL_LASTEXT,\n\tT_KHOP_FOREIGN_CLICK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [212.83.188.4 listed in bb.barracudacentral.org]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60%\n\t*      [score: 0.5000]\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  1.4 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from adedata.eurowh.com (adedata.eurowh.com [212.83.188.4])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 297CC33181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 16:45:40 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=boomjets.fr\n\t; s=default; h=Content-Transfer-Encoding:Content-Type:List-Unsubscribe:\n\tMIME-Version:Reply-To:From:Date:Message-ID:Subject:To;\n\tbh=Wih0KGSNwEVrfPH3ZYmu/I70R7CRH8GjXHbGuqZIPAU=; b=W3l4Mpk78lJEgEyt2SwlZEAOeh\n\tmQ/cw4HnMvTwYoW7uyNdrkwt9cnMJ+NmmuYy6imt/FSkbxv5HIv4TSxFQfEUZL53nocab5NYTcv1u\n\trL4+I9uBuxvNT/LMZ/jDUpOynl0f5DBZvzY7k3DOHSTlGONQOrRvgvj15DfuHuHHrJrojVDPgybDQ\n\tIEyNiyNdYAlFpMtEKblSlhyJHuBh8E3WSXp1Nz+pUlO6cSjwGiaEGWuVTROI4S357OPlNza+ENnJX\n\t8xtJoIsW9J7N3P6TGeYjNI+eG44s95BRaRNEtrhDM4M1ccXHh51gzFiuFzEY0PaKRPmRzBrAef0dD\n\tJuyS1U0A==;\nReceived: from boomjeto by adedata.eurowh.com with local (Exim 4.86_1)\n\t(envelope-from <bounce@boomjets.fr>)\n\tid 1apGcO-0007F3-8R\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 16:45:40 +0200\nTo: dev@caliopen.local\nSubject: =?UTF-8?B?Tm91cyB2b3VzIGNyw6lvbnMgdW4gbm91dmVhdSBzaXRlIGludGVybmV0IGNsw6kgZW4gbWFpbg==?=\nMessage-ID: <fd14b2e78a8b5a524692c84384b0a0fd@boomjets.fr>\nDate: Sun, 10 Apr 2016 14:36:07 +0000\nFrom: \"ADE France\" <pierreledu@boomjets.fr>\nReply-To: pierreledu@boomjets.fr\nMIME-Version: 1.0\nX-Mailer-LID: 15,16,17,18\nList-Unsubscribe: <http://boomjets.fr/unsubscribe.php?M=994713&C=e09901c2ffdb87eac793f2db8f2a085d&L=15&N=54>\nX-Mailer-RecptId: 994713\nX-Mailer-SID: 54\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_de9167eea70e001d7600d0adbdd6cf47\"\nContent-Transfer-Encoding: 8bit\nX-AntiAbuse: This header was added to track abuse, please include it with any abuse report\nX-AntiAbuse: Primary Hostname - adedata.eurowh.com\nX-AntiAbuse: Original Domain - brainstorm.fr\nX-AntiAbuse: Originator/Caller UID/GID - [505 505] / [47 12]\nX-AntiAbuse: Sender Address Domain - boomjets.fr\nX-Get-Message-Sender-Via: adedata.eurowh.com: authenticated_id: boomjeto/from_h\nX-Authenticated-Sender: adedata.eurowh.com: pierreledu@boomjets.fr\nX-Spam-Prev-Subject: =?UTF-8?B?Tm91cyB2b3VzIGNyw6lvbnMgdW4gbm91dmVhdSBzaXRlIGludGVybmV0IGNsw6kgZW4gbWFpbg==?=\nContent-Length: 4763\nLines: 120\n\n--b1_de9167eea70e001d7600d0adbdd6cf47\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nBonjour,\n \nDepuis que Google a commencé à pénaliser les sites internet non\nResponsive au mois de mars dernier,\nnous vous proposons la refonte ou la création de votre site internet\nréférencement compris.\n\nImplantés dans le 11ème arrondissement de Paris, nous intervenons auprès\ndes TPE / PME pour apporter \nune réponse personnalisée à leurs besoins en communication sur Internet\n: Sites vitrine, e-commerce,\nréférencement naturel.\n\nLa technologie phare en matière de site internet agréé par Google\ns'appelle Responsive design.\n\nGrace à cette nouvelle technologie notre site est en première page Google\navec les mots clés comme :\n\n \" Création de site internet 75000 \" première page sur Google  :\nwww.altech-france.fr\n\n\" réparation de remorque saint denis \"  première page sur Google   :\nwww.autoconfort-arisma.fr\n\n Pour ne citer que ceux-là !\n\nPOUR UN DEVIS GRATUIT CLIQUER-ICI\n\nEn vous remerciant pour votre attention, nous restons à votre disposition\npour tous renseignements complémentaires.\n\nCordialement.\n\nService client\n\n\n\n\n                         Pour plus d'informations : Cliquez-ici \n\n\n                                   \nNe plus recevoir de message\n:http://boomjets.fr/unsubscribe.php?M=994713&C=e09901c2ffdb87eac793f2db8f2a085d&L=15&N=54\n\n--b1_de9167eea70e001d7600d0adbdd6cf47\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html>\n<head>\n<title>Fichiers professionnels de France </title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n  <style type=\"text/css\">\nbody { font-family: arial !important; }\nimg { outline: none !important; text-decoration: none !important;\n-ms-interpolation-mode: bicubic !important; }\n</style>  \n</head>\n<body bgcolor=\"#FFFFFF\" leftmargin=\"0\" topmargin=\"0\" marginwidth=\"0\"\nmarginheight=\"0\" style=\"font-family: arial;\">\n<p style=\"font-size: 10px; text-align: center; margin: 1em 0;\"></p>\n<table id=\"Tableau_01\" style=\"font-family: arial; border-collapse:\ncollapse;\" align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr valign=\"0\">\n<td style=\"text-align: left;\">\n<p></p>\n<p>Bonjour,<br />&nbsp;<br />Depuis que Google a commenc&eacute; &agrave;\np&eacute;naliser les sites internet non Responsive au mois de mars\ndernier,<br /> nous vous&nbsp;proposons la refonte ou la cr&eacute;ation de\nvotre site internet r&eacute;f&eacute;rencement compris.</p>\n<p>Implant&eacute;s dans le 11&egrave;me arrondissement de Paris, nous\nintervenons aupr&egrave;s des TPE / PME pour apporter <br />une\nr&eacute;ponse personnalis&eacute;e &agrave; leurs besoins en communication\nsur Internet : Sites vitrine, e-commerce,<br /> r&eacute;f&eacute;rencement\nnaturel.</p>\n<p>La technologie phare en mati&egrave;re de site internet\nagr&eacute;&eacute; par Google s'appelle Responsive design.</p>\n<p>Grace &agrave; cette nouvelle technologie&nbsp;<a title=\"altech france\"\nhref=\"http://boomjets.fr/link.php?M=994713&N=54&L=9&F=H\"\ntarget=\"_blank\">notre site</a>&nbsp;est en premi&egrave;re page Google avec\nles mots cl&eacute;s comme :</p>\n<p>&nbsp;\" Cr&eacute;ation de site internet 75000 \" premi&egrave;re page\nsur Google &nbsp;: www.altech-france.fr</p>\n<p>\" r&eacute;paration de remorque saint denis \" &nbsp;premi&egrave;re page\nsur Google &nbsp; :&nbsp;www.autoconfort-arisma.fr</p>\n<p>&nbsp;Pour ne citer que ceux-l&agrave; !</p>\n<p class=\"align-center\" style=\"text-align: center;\"><strong><a\ntitle=\"devis\" href=\"http://boomjets.fr/link.php?M=994713&N=54&L=6&F=H\"\ntarget=\"_blank\">POUR UN DEVIS GRATUIT CLIQUER-ICI</a></strong></p>\n<p>En vous remerciant pour votre attention, nous restons &agrave; votre\ndisposition pour tous renseignements compl&eacute;mentaires.</p>\n<p>Cordialement.</p>\n<p><a href=\"http://boomjets.fr/link.php?M=994713&N=54&L=6&F=H\"\ntarget=\"_blank\">Service client</a></p>\n<p><br /> <!--[if !supportLineBreakNewLine]--><br /><br /> &nbsp; &nbsp;\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\n&nbsp;Pour plus d'informations : <a\nhref=\"http://boomjets.fr/link.php?M=994713&N=54&L=6&F=H\">Cliquez-ici</a>\n<br /><b><span><b><br /></b></span></b><br /><span><span><span><a\nhref=\"http://boomjets.fr/link.php?M=994713&N=54&L=6&F=H\"><br />&nbsp;</a><a\nhref=\"http://boomjets.fr/link.php?M=994713&N=54&L=6&F=H\"></a></span></span></span></p>\n</td>\n</tr>\n</tbody>\n</table>\n<p style=\"text-align: center;\"><a\nhref=\"http://boomjets.fr/unsubscribe.php?M=994713&C=e09901c2ffdb87eac793f2db8f2a085d&L=15&N=54\">Ne\nplus recevoir de message</a>&nbsp;</p>\n<img src=\"http://boomjets.fr/open.php?M=994713&L=15&N=54&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body>\n</html>\n\n--b1_de9167eea70e001d7600d0adbdd6cf47--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_69.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aj8yH-0004fq-85\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 13:22:57 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42774)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aj8yF-0004f5-1Y\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 13:22:56 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aj8yB-0003VC-Rr\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 13:22:54 -0400\nReceived: from mout.kundenserver.de ([217.72.192.74]:60414)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aj8yB-0003V5-HR\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 13:22:51 -0400\nReceived: from [128.30.9.181] ([128.30.9.181]) by mrelayeu.kundenserver.de\n\t(mreue102) with ESMTPSA (Nemesis) id 0LlnLU-1aA5Gc0uq8-00ZMQz for\n\t<emacs-devel@gnu.org>; Thu, 24 Mar 2016 18:22:50 +0100\nTo: Emacs developers <emacs-devel@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nSubject: Should the default value of url-user-agent include a version number?\n\tWhat about the specific case of package.el?\nMessage-ID: <56F42269.10400@gmail.com>\nDate: Thu, 24 Mar 2016 13:22:49 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"MCBMng3QItdGiviN0WFBqTKIGjWPClOPn\"\nX-Provags-ID: V03:K0:xEJ2vSBIchgQM+Pa2tuabPDrPn4MVqdCpm6RGXfIoYJNU3eeQyS\n\tcGmc9bJlBbSFH66Kj5DGlSMR68psG3ifzp0Xcur6oWQ5TRFmZNKHl2pWNNhgbingoxJWoBY\n\tvCRiBNe/znrDfmxMrzVFEnbXHTUKl/DPB3vs9FZsI5edQ2DgqVMSwEK2piMHxzxAaA0RPrJ\n\toJDwJFNmCKHUP5IK36P8w==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:V2h4FrDZ9qs=:t9Z6aGEU3M2Mh/UPg7ZT2K\n\tjcKegadNEWVPjnYII8flWisYiZ/kF8axTCUMAxSGtPDdAlJPMOtocNyRLPYyhIvpGxsvmayyo\n\t+qvaNPrG4iW8rg90XIMXfQtCl2wcHhB+Ytge61glOXBUxrEAM0P/yFSyRd5EkBbRE3rh8CiuJ\n\tv9zjlRJ8hd/Y87LSya8xcnBNvBYKcqXWM/j3OYjgedfOVIg2iJWwlv+9neSKf0BUSab7vyb1z\n\tS/XPqpWI7KQ525V4XkQhxKCcqSzfB0+svov//+n/1wU4fruL5kXYHypBINjMrwfe7VlJwGCLS\n\tk50XMAXCpCg9mMo+YC4fOC/AIy8coIj0a85WXiGfkV9Vq/bP8NDt/K2wP3mlXCmV8tjbPe5ZJ\n\tjCkfASWw6Dl5bGfZ5nFvKb/bDbHtAmpGoGhIOHN0o0yBNtnLIspeldtP2IATxbZ9mXTIsdb9f\n\tASERAI5Z64MMmD+ipxy0UyZzK5wIP9MZPM7qHC3YwwNuF0FM3EiIXRKQUnZ7lM/LIVsuSpV2B\n\tkqjL7p0gEPM/9CC2K2ETmYT2QtuTX47HjNx7PkHplHYfubbZFj8R/kLLXMzr+3/befTKDj60K\n\tTuV8nHQS2bF5jt/KSSoQxRrCDL6F+dmTmWaQo9/AdpOyUPSTtvpFjyx2xLNqFsdfx5rg4I28T\n\tHlKYHNMUEjC9WAgK5YPHCNfAcf0fmjdm7Pw0a3sqB9jUWV1pGU8pMM7D1/ulI1zX2lEk=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 217.72.192.74\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 17:22:56 -0000\nContent-Length: 6784\nLines: 148\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--MCBMng3QItdGiviN0WFBqTKIGjWPClOPn\nContent-Type: multipart/mixed; boundary=\"vXbuBRHI2Sr0IuMTkhVRjLF9RriM2gBPU\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: Emacs developers <emacs-devel@gnu.org>\nMessage-ID: <56F42269.10400@gmail.com>\nSubject: Should the default value of url-user-agent include a version number?\n What about the specific case of package.el?\n\n--vXbuBRHI2Sr0IuMTkhVRjLF9RriM2gBPU\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nHi emacs-devel,\n\nhttps://debbugs.gnu.org/cgi/bugreport.cgi?bug=3D18812 added a url-user-ag=\nent setting to Emacs, but it only partly addressed feature request. The o=\nriginal suggestion was:\n\n> The url.el functions for making HTTP requests set the User-Agent header=\n\n> to \"URL/Emacs\" which is not very descriptive. It would be helpful to\n> include the Emacs version, at least.\n\nI'm bringing this up again, as I do think it would be useful to have a mo=\nre informative User-Agent string. The new system is very useful for custo=\nmization, but it does not address the original issue. The two main advant=\nages that I see for a more informative User-Agent string are these:\n\n* Package archives could work around issues with old versions of package.=\nel (for example, 24.3 is somewhat dependent to the order in which depende=\nncies are listed).\n* Package authors could get per-version and possibly per-platform statist=\nics. Backwards compatibility is not always easy, so knowing which Emacs v=\nersions your users are on would be very useful, in particular to know whe=\nn to drop support for an old version.\n\nThese use cases were raised in https://github.com/melpa/melpa/issues/3493=\n, in particular. Interestingly, they don't necessarily suggest that we sh=\nould change url-user-agent; instead, wouldn't it be best for different pa=\nrts of Emacs to use different user-agent strings? In particular, should e=\nww and package.el really use the same User-Agent string? We could instead=\n introduce separate a package-user-agent variable, and possibly an eww-us=\ner-agent variable; this would be in line with what other programs do: it'=\ns uncommon to advertise only the library used to access the web (in our c=\nase, =E2=80=98URL=E2=80=99) in a User-Agent string, instead of the actual=\n User-Agent.\n\nGenerally speaking, I would be in favor of always advertising the current=\n Emacs version, and the OS, instead of only advertising it for package.el=\n: this would be in conformance with RFC 7231 (see below), and with what m=\nany other browsers do (but fixing this issue by introducing a package-use=\nr-agent string would work as well, and might alleviate some concerns that=\n were expressed in the original bug thread). Some expressed privacy conce=\nrns about this, but other parts of Emacs already advertise this informati=\non fully (Stefan, for example, your emails are tagged with \"Gnus/5.13 (Gn=\nus v5.13) Emacs/25.1.50 (gnu/linux)\"). Has there been complaints about Gn=\nus doing that?\n\nAre there reasons to prefer a very generic User-Agent string? Do these re=\nasons also apply to a package.el-specific one?\n\nCheers,\nCl=C3=A9ment.\n\n> 5.5.3.  User-Agent\n>\n>    The \"User-Agent\" header field contains information about the user\n>    agent originating the request, which is often used by servers to hel=\np\n>    identify the scope of reported interoperability problems, to work\n>    around or tailor responses to avoid particular user agent\n>    limitations, and for analytics regarding browser or operating system=\n\n>    use.  A user agent SHOULD send a User-Agent field in each request\n>    unless specifically configured not to do so.\n>\n>      User-Agent =3D product *( RWS ( product / comment ) )\n>\n>    The User-Agent field-value consists of one or more product\n>    identifiers, each followed by zero or more comments (Section 3.2 of\n>    [RFC7230]), which together identify the user agent software and its\n>    significant subproducts.  By convention, the product identifiers are=\n\n>    listed in decreasing order of their significance for identifying the=\n\n>    user agent software.  Each product identifier consists of a name and=\n\n>    optional version.\n>\n>      product         =3D token [\"/\" product-version]\n>      product-version =3D token\n>\n>    A sender SHOULD limit generated product identifiers to what is\n>    necessary to identify the product; a sender MUST NOT generate\n>    advertising or other nonessential information within the product\n>    identifier.  A sender SHOULD NOT generate information in\n>    product-version that is not a version identifier (i.e., successive\n>    versions of the same product name ought to differ only in the\n>    product-version portion of the product identifier).\n>\n>    Example:\n>\n>      User-Agent: CERN-LineMode/2.15 libwww/2.17b3\n>\n>    A user agent SHOULD NOT generate a User-Agent field containing\n>    needlessly fine-grained detail and SHOULD limit the addition of\n>    subproducts by third parties.  Overly long and detailed User-Agent\n>    field values increase request latency and the risk of a user being\n>    identified against their wishes (\"fingerprinting\").\n>\n>    Likewise, implementations are encouraged not to use the product\n>    tokens of other implementations in order to declare compatibility\n>    with them, as this circumvents the purpose of the field.  If a user\n>    agent masquerades as a different user agent, recipients can assume\n>    that the user intentionally desires to see responses tailored for\n>    that identified user agent, even if they might not work as well for\n>    the actual user agent being used.\n>\n\n\n--vXbuBRHI2Sr0IuMTkhVRjLF9RriM2gBPU--\n\n--MCBMng3QItdGiviN0WFBqTKIGjWPClOPn\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW9CJpAAoJEPqg+cTm90wjtTcP/0v8Ncxz76sWJmCGp56CNwzw\n4Qi3pZpuES6FV66N5IBKsjtmabRtn6yqp8YHQn3psHUOGMhboP3dTaVM7XpGYYQP\nViz1n48ZpZ44E5do8e6LGgkVnrhO7/aTxPnAcTlnhoOr3LJM28c1B/YYUn9QBeb/\nyhug5rfRoSXJPbmqEAITwbnZdINppZt9lEsh71CS0qFXGoL0snX7D2G+1IasbwIo\nUGuNRUJ9bScll+QbuVDMR/5KC0+URi4M+h7cOaX1aLw7veFoZzpw9l1lp+VVq8Nq\nJfRWBjuNT5wHMOgOnNHDLaeyHEt4hIYVUIDb/+I4fUxVjWCi4QMtTmyuwJTYws9H\ngGGIEKP7DcCZNCzpY3GRSa7rfXIFn0uLj4pVeyILMKsa72lSUPWTO6+dJMq4ETPu\nrTAftKYLAvw4fT6Zd+7MYxkQV8iUkHXBLrWMrTuOQIaNDRH25aqVhxnHOLa35GrD\n9KggzonWQYwT2Ep9kfqLwbWmLUcrJo4FSk0+2mJS+O4T94kHc7HjzenbS2v0wXRJ\nDLVwUiarLb2lasRXWAzE2iGeBLHtsy0xFat3PKDMOzNy1jqvpuJWbrByvaa5LJIR\n+PcAh+FnI5dvCI4fFPnrHlctqcoFrlt2qRXy2iPI+fKsudT2Q/qdhTQYOJe7nD+R\nhrGEVfIyWiUHcN2oaOA2\n=KGhs\n-----END PGP SIGNATURE-----\n\n--MCBMng3QItdGiviN0WFBqTKIGjWPClOPn--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_71.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajEVX-0002Id-PE\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 19:17:39 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56204)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <steve@sanityinc.com>) id 1ajEVW-0002I8-1J\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 19:17:38 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <steve@sanityinc.com>) id 1ajEVR-0007Rz-2G\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 19:17:37 -0400\nReceived: from purcellpeople.com ([85.214.32.38]:49238\n\thelo=h1189701.stratoserver.net)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <steve@sanityinc.com>) id 1ajEVQ-0007Rt-S0\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 19:17:32 -0400\nReceived: from Mandala.local (unknown [210.54.38.43])\n\tby h1189701.stratoserver.net (Postfix) with ESMTPSA id B2F4627AC0A3;\n\tFri, 25 Mar 2016 00:17:25 +0100 (CET)\nFrom: Steve Purcell <steve@sanityinc.com>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\nDate: Fri, 25 Mar 2016 12:17:21 +1300\nIn-Reply-To: <56F42269.10400@gmail.com> (=?utf-8?Q?=22Cl=C3=A9ment?=\n\tPit--Claudel\"'s message of \"Thu, 24 Mar 2016 13:22:49 -0400\")\nMessage-ID: <m2io0bihz2.fsf@Mandala.local>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (darwin)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 85.214.32.38\nCc: Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 23:17:38 -0000\nContent-Length: 388\nLines: 9\n\nAs a MELPA maintainer, I'd be very keen to be able to provide users and\npackage authors with package downloads stats broken down by emacs\nversion and/or platform.\n\nI don't personally see any tangible privacy concerns with this proposal;\nit would be fine for this specific use case to set a more informative\nUser-Agent header only for requests initiated by package.el if that's an\nissue.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_73.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ap7dc-0000I2-8Q\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 01:10:20 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39388)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <vibhavp@gmail.com>) id 1ap7da-0000Fu-6V\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 01:10:18 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <vibhavp@gmail.com>) id 1ap7dZ-0005tf-9U\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 01:10:18 -0400\nReceived: from mail-io0-x22f.google.com ([2607:f8b0:4001:c06::22f]:32907)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <vibhavp@gmail.com>) id 1ap7dZ-0005tb-3M\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 01:10:17 -0400\nReceived: by mail-io0-x22f.google.com with SMTP id o126so151620955iod.0\n\tfor <dev@caliopen.local>; Sat, 09 Apr 2016 22:10:16 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:date:message-id:subject:from:cc; \n\tbh=qswc9pTkZY1IlpArbfHJ/dLRHjooOsgDDv5LPaGuf+E=;\n\tb=FjjPSGRJ7z9kbdibmCeV85tYuAqaIISmFBUKpJ54nlsHzDNh8JQE4ATLep7NROdx7F\n\tgO+3jyZ12yDg5grLnorin6r36k/yucvk86S5vM+N8mCiXMH9R8IhykFyYiYjlUXe9/il\n\tyqkNzyXzBp1IiYDHBhggBTyiLYQvoGo4MHYiNkNUbeRNfcjI9JXBZVnxIXpjpOlYAJe9\n\torGmKcIEcmttqhS/XYYfUrbgbkAkEUQQNaiFE9+OSHvdnA4MU6HTsOXSgrMUzQXvuJiW\n\tsCTEgLG7643N9vWCebqNjS7FFrT8aMsn2BEGPwNss2TGjkhAqCHJIQEEA+DDVV7CRiDP\n\tIBJQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:date\n\t:message-id:subject:from:cc;\n\tbh=qswc9pTkZY1IlpArbfHJ/dLRHjooOsgDDv5LPaGuf+E=;\n\tb=e4KhiOANgn4ayHfdjQsdDcaa5awMPlS9Eooy/HNk85vyF6DfxtSrjpnJ4e9pC49DUO\n\tqdKWViYz6wKepXDR3wig/H5KDGbtpCwEhjo2oJlG4INCRDi/3Zx5u2hjNrzjTJ43WJJR\n\tD+JheMwr8yFm1dVY/0s9V61S2IX6tFspdAtxpTSFXwJ2BscPoJxpg0nelxwDRfvPO5hd\n\twsKKoSwsPVoDqEtqIjN0gxzL50V+gobYZkwp6Y4Czk/hFHxbxlRA+WX51fc9QhQ4HhQD\n\tt/xP+SNud6Ic+JWCvG+zJE/43/c+Kgf7mjEf4mtWVoGstY6BFuOMEK0P5Fk2LHuNSS2G\n\t5zWw==\nX-Gm-Message-State: AD7BkJL4FAkhG9rBsFrEfdxhRe8tiuRjhMB5qaYYP2A3ahEPXc9vpNp/Wd600ua3rzZByQIhobXdQAvleNSfng==\nMIME-Version: 1.0\nX-Received: by 10.107.3.193 with SMTP id e62mt19337443ioi.40.1460265016503;\n\tSat, 09 Apr 2016 22:10:16 -0700 (PDT)\nReceived: by 10.79.17.132 with HTTP; Sat, 9 Apr 2016 22:10:16 -0700 (PDT)\nIn-Reply-To: <m2io0bihz2.fsf@Mandala.local>\nReferences: <56F42269.10400@gmail.com>\n\t<m2io0bihz2.fsf@Mandala.local>\nDate: Sun, 10 Apr 2016 10:40:16 +0530\nMessage-ID: <CA+T2Sh24WOc9MifckO5v8zRa_xrVDnmcbH0mnmtnknyMQMb0jQ@mail.gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nFrom: Vibhav Pant <vibhavp@gmail.com>\nCc: =?UTF-8?B?Q2zDqW1lbnQgUGl0LS1DbGF1ZGVs?= <clement.pit@gmail.com>, \n\tEmacs developers <emacs-devel@gnu.org>\nContent-Type: text/plain; charset=UTF-8\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4001:c06::22f\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 05:10:19 -0000\nContent-Length: 1100\nLines: 31\n\nERC exposes emacs' version number in quit messages already:\n           \"(Quit: ERC (IRC client for Emacs 25.0.50.3))\"\nSo I don't see why there should be an exception for url.\n\nOn Fri, Mar 25, 2016 at 4:47 AM, Steve Purcell <steve@sanityinc.com> wrote:\n> As a MELPA maintainer, I'd be very keen to be able to provide users and\n> package authors with package downloads stats broken down by emacs\n> version and/or platform.\n\nThat could also be done with adding a `X-Emacs-Version` header to HTTP\nrequests made to package repositories, if we still don't want to\nexpose the user\nagent.\n\nOn Fri, Mar 25, 2016 at 4:47 AM, Steve Purcell <steve@sanityinc.com> wrote:\n> As a MELPA maintainer, I'd be very keen to be able to provide users and\n> package authors with package downloads stats broken down by emacs\n> version and/or platform.\n>\n> I don't personally see any tangible privacy concerns with this proposal;\n> it would be fine for this specific use case to set a more informative\n> User-Agent header only for requests initiated by package.el if that's an\n> issue.\n>\n\n\n\n-- \nVibhav Pant\nvibhavp@gmail.com\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_75.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apKbj-00036O-2p\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 15:01:15 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41519)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1apKbg-00033v-T9\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 15:01:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1apKbg-0004N2-2S\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 15:01:12 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:43788)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1apKbd-0004MR-FJ; Sun, 10 Apr 2016 15:01:09 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1apKbc-0008TH-Qs; Sun, 10 Apr 2016 15:01:08 -0400\nContent-Type: text/plain; charset=Utf-8\nFrom: Richard Stallman <rms@gnu.org>\nTo: Vibhav Pant <vibhavp@gmail.com>\nCC: clement.pit@gmail.com, dev@caliopen.local\nIn-reply-to: <CA+T2Sh24WOc9MifckO5v8zRa_xrVDnmcbH0mnmtnknyMQMb0jQ@mail.gmail.com>\n\t(message from Vibhav Pant on Sun, 10 Apr 2016 10:40:16 +0530)\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nReply-to: rms@gnu.org\nReferences: <56F42269.10400@gmail.com> <m2io0bihz2.fsf@Mandala.local>\n\t<CA+T2Sh24WOc9MifckO5v8zRa_xrVDnmcbH0mnmtnknyMQMb0jQ@mail.gmail.com>\nMessage-Id: <E1apKbc-0008TH-Qs@fencepost.gnu.org>\nDate: Sun, 10 Apr 2016 15:01:08 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 19:01:13 -0000\nContent-Length: 736\nLines: 19\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > ERC exposes emacs' version number in quit messages already:\n  >            \"(Quit: ERC (IRC client for Emacs 25.0.50.3))\"\n  > So I don't see why there should be an exception for url.\n\nIRC and web browsing are different activities and raise different\nissues regarding surveillance.  There is no reason to insist a priori\non treating them alike.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_77.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apM5u-0008Hs-MF\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 16:36:30 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60373)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1apM5s-0008Fp-T4\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 16:36:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1apM5m-00076J-FH\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 16:36:28 -0400\nReceived: from mout.kundenserver.de ([212.227.126.131]:57273)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1apM5m-00076F-5E; Sun, 10 Apr 2016 16:36:22 -0400\nReceived: from [18.189.83.142] ([18.189.83.142]) by mrelayeu.kundenserver.de\n\t(mreue003) with ESMTPSA (Nemesis) id 0M3O8y-1bg5Eh2VHs-00r1mX;\n\tSun, 10 Apr 2016 22:36:19 +0200\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: rms@gnu.org, Vibhav Pant <vibhavp@gmail.com>\nReferences: <56F42269.10400@gmail.com> <m2io0bihz2.fsf@Mandala.local>\n\t<CA+T2Sh24WOc9MifckO5v8zRa_xrVDnmcbH0mnmtnknyMQMb0jQ@mail.gmail.com>\n\t<E1apKbc-0008TH-Qs@fencepost.gnu.org>\nCc: dev@caliopen.local\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <570AB943.4060604@gmail.com>\nDate: Sun, 10 Apr 2016 16:36:19 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <E1apKbc-0008TH-Qs@fencepost.gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"8PeWqDjRTCtq12tr3EhmcNHUMd0cqJ9h5\"\nX-Provags-ID: V03:K0:NrSzFinbXKAiI//Mno+itGefpcbYW+N+hfeGy1cGLlY0Jn68gCI\n\tNxqnnpQ2UpPIU1FYkc9bVO8PLSyOlM5Wl1Mtb04BQRKCvGLa5P0NqhupsT0uS78U5i2OOXc\n\tOsycqhU1WfqMiXOLH9qXY2wEQelTNx+j5QZ512XRFVj6Nr36f0wEfmSsSBP7w6BnscakHxu\n\tIKAku5edoL8iLvz0cIBPw==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:NsQoOLhz7bU=:E5SGq1ura4ctxlOViCqo5G\n\tC4dWrbjVH2gC3DwFXRKBcuH057BMtyR5d8T2ZlDiiK++ioolxH2jObjegnIjXOt5b7R7zLLFQ\n\t3VbFmTZUnCp9fbTU7bbVhvt+o1/NXW/qVhk2i9tVcoDNVtDp0hoA+mvhL18jbz7VIJc7VhGS4\n\t1tjS4C/EelfWOHalx7VHth8uY6hyUsYSblqO0YXAYgo6UOu+0JIxmUz72bUh9sjLd/m4DoBPI\n\tW9coqTZK99Iu4wixZU/pPW0N8A74Q2bjR3lKR4356B9kjlcEO2v7EIGaofAX6tXJWij0gqoXW\n\teE+B4X+NBe9CaDs5Uk3NfgT7u4GWo83XlI6T7xw+SbFtam5Li0/5FMSaYKuor6a1tW9QBCGsl\n\tyWCo6jdto5UuOk4bbwQ8E1/S0MbRlowwcdQ+ZiK4CIA0x+WWh+AqaxMU/hypQtaFrGdZ4C/AE\n\tEuEWQ0wDYBC7aUWGGztPuLwHn5dWlZIS+xrJ/LL2c25kYiFf77RPbIsDXAP/+Pw79O+T2nbn1\n\tGsoKjx3ZFk/BWYfn4I61v5oTh87dKuf5NDO87HpEKKcoF25ztH2CAOERO7VkFXSepmiMU60Se\n\t5EgH2NEjgXCo8X4gr5HQD647aauz14TdMzRe5rCS644EshOHJ2UXOraVmsoZpGYrwdFcKIOSr\n\teNa7buwedlQIM5LVnQ8dpfyM4WBY8uTwHM5K0Ii3+sRhLyLThLJx2QM1ySLEG8PmEJiA=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.131\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 20:36:29 -0000\nContent-Length: 2407\nLines: 59\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--8PeWqDjRTCtq12tr3EhmcNHUMd0cqJ9h5\nContent-Type: multipart/mixed; boundary=\"TX1kavG08I806CH9J44LITAdkOiGcc4bC\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: rms@gnu.org, Vibhav Pant <vibhavp@gmail.com>\nCc: dev@caliopen.local\nMessage-ID: <570AB943.4060604@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com> <m2io0bihz2.fsf@Mandala.local>\n <CA+T2Sh24WOc9MifckO5v8zRa_xrVDnmcbH0mnmtnknyMQMb0jQ@mail.gmail.com>\n <E1apKbc-0008TH-Qs@fencepost.gnu.org>\nIn-Reply-To: <E1apKbc-0008TH-Qs@fencepost.gnu.org>\n\n--TX1kavG08I806CH9J44LITAdkOiGcc4bC\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/10/2016 03:01 PM, Richard Stallman wrote:\n> IRC and web browsing are different activities and raise different\n> issues regarding surveillance.  There is no reason to insist a priori\n> on treating them alike.\n\nSimilarly, package retrieval and web browsing are different activities an=\nd raise different issues; hence my question about the specific case of pa=\nckage.el.\n\nRichard, are you familiar with the url-privacy-level variable?\n\nCl=C3=A9ment.\n\n\n--TX1kavG08I806CH9J44LITAdkOiGcc4bC--\n\n--8PeWqDjRTCtq12tr3EhmcNHUMd0cqJ9h5\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXCrlDAAoJEPqg+cTm90wjSj4P/imrz2oMpo/3ecdpYYssTVVW\nhp/26LOB87lt9UjGNHnXOMx1cG2OcdmJ5gD4ocdf9G3Vg+SbW7NWPyjbUY1O3DQ1\n89FXGSO/6RVEyJXVof5BHaBlGMCEh9auCJAVepdhAvlW2gxRXIjxlxujDsT6z2wl\n6wRW3tDmjwMZXOPXvI0Kt0T96dwe/xBAHNp9BHdY+bJXpww/Va0IT8fyryXi8rMD\nMZ05XznRAiaYxZgwoRbXpbswGU3E2+E5NHkJqkSGAiVR6ZaJuFEBdK/X9UFqj7Ud\n1BURAWNRPvh+FPMcFFYOFdMSFh/oA/GB4+hwZdJRf7weVd+9yZlJQI9/nmdRSMBo\nImptcKrt1fOprPuKM65K81z+KBhZn0uEVGXnkNOvHTdl4VnTsW8/MiLWh9yZap0G\nf/zscR8O+RIcYi6L60IS/FwJmG4F4WAHWB97VMept8Y/vtw22hfmj52yZAyefCEm\nRUFutaEiHdClTMGuurwogbVw6pOylqDCYv/5BFih/z8hMkgsr7ULKbv45P3QsF9R\nzwKwunBfWxOHdzcFnUUGaO1NrhaDEIyeWPGrKF29FAdmkx4PqBQnw6Rh2D3bFEle\nImF5zNVy1qYr3M0IkAzh18FsoJz3tFgwE5iu6gyACt6AePVBbwLbBt/AUOrHGXVu\nkxBbQRaOPL/u2La42m8z\n=6div\n-----END PGP SIGNATURE-----\n\n--8PeWqDjRTCtq12tr3EhmcNHUMd0cqJ9h5--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_79.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajAXy-0006Wm-M1\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 15:03:54 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39934)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajAXs-0006Pg-R4\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 15:03:53 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajAXn-000335-S4\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 15:03:48 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:44370)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajAXn-000331-Lm\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 15:03:43 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajAXl-0005RS-O1\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 20:03:41 +0100\nReceived: from 69-196-182-150.dsl.teksavvy.com ([69.196.182.150])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 24 Mar 2016 20:03:41 +0100\nReceived: from monnier by 69-196-182-150.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 24 Mar 2016 20:03:41 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nDate: Thu, 24 Mar 2016 15:03:21 -0400\nMessage-ID: <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <56F42269.10400@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-196-182-150.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:4j9EO8FIQwNlcFa0s2iJXeNnqQQ=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 19:03:53 -0000\nContent-Length: 432\nLines: 13\n\n> (Stefan, for example, your emails are tagged with \"Gnus/5.13 (Gnus v5.13)\n> Emacs/25.1.50 (gnu/linux)\").  Has there been complaints about Gnus\n> doing that?\n\nI never liked it, but admittedly, I never complained about it.\n\n\n        Stefan\n\n\nPS: I could go along with package.el sending the Emacs version in the\nuser-agent (tho I don't think the OS info belongs there and AFAIK most\nELPA packages don't care about the OS either).\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_81.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajBm0-0003fT-4V\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 16:22:28 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:53714)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <swiesner@lunaryorn.com>) id 1ajBly-0003fK-5J\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:22:26 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <swiesner@lunaryorn.com>) id 1ajBlu-00038n-V6\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:22:26 -0400\nReceived: from mx2.mailbox.org ([80.241.60.215]:35080)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <swiesner@lunaryorn.com>) id 1ajBlu-00038W-PD\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:22:22 -0400\nReceived: from smtp1.mailbox.org (smtp1.mailbox.org [80.241.60.240])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby mx2.mailbox.org (Postfix) with ESMTPS id A6E38420D2;\n\tThu, 24 Mar 2016 21:22:20 +0100 (CET)\nX-Virus-Scanned: amavisd-new at heinlein-support.de\nReceived: from smtp1.mailbox.org ([80.241.60.240])\n\tby hefe.heinlein-support.de (hefe.heinlein-support.de [91.198.250.172])\n\t(amavisd-new, port 10030)\n\twith ESMTP id O2RhziWzvimi; Thu, 24 Mar 2016 21:22:19 +0100 (CET)\nContent-Type: text/plain; charset=us-ascii\nMime-Version: 1.0 (Mac OS X Mail 9.2 \\(3112\\))\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nFrom: Sebastian Wiesner <swiesner@lunaryorn.com>\nIn-Reply-To: <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Thu, 24 Mar 2016 21:22:17 +0100\nContent-Transfer-Encoding: quoted-printable\nMessage-Id: <E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.241.60.215\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 20:22:27 -0000\nContent-Length: 1097\nLines: 34\n\n\n> Am 24.03.2016 um 20:03 schrieb Stefan Monnier =\n<monnier@iro.umontreal.ca>:\n>=20\n>> (Stefan, for example, your emails are tagged with \"Gnus/5.13 (Gnus =\nv5.13)\n>> Emacs/25.1.50 (gnu/linux)\").  Has there been complaints about Gnus\n>> doing that?\n>=20\n> I never liked it, but admittedly, I never complained about it.\n\nMay I ask why you do not like this behaviour? What stands against it?\n\n>        Stefan\n>=20\n>=20\n> PS: I could go along with package.el sending the Emacs version in the\n> user-agent (tho I don't think the OS info belongs there and AFAIK most\n> ELPA packages don't care about the OS either).\n\nNot many do care, for sure, but to those that do this information is all =\nthe more important.\n\nSpeaking on behalf of Flycheck we'd love to have statistics about OS =\ndistribution among our\nusers, so much that I thought about adding opt-in Google Analytics =\ntracking to Flycheck for\nthis purpose.  Currently we have literally no idea what people use and =\nhow much effort we=20\nshould put into support for particular systems (Windows specifically), =\nand we have no way to\nfind out.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_83.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajBzL-0000KA-M7\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 16:36:15 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56155)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajBzJ-0000Jn-Fz\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:36:14 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajBzG-0005pW-Ap\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:36:13 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:49694)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajBzG-0005pI-4F\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 16:36:10 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ajBzE-0004Ah-Jd\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 21:36:08 +0100\nReceived: from 69-196-182-150.dsl.teksavvy.com ([69.196.182.150])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 24 Mar 2016 21:36:08 +0100\nReceived: from monnier by 69-196-182-150.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 24 Mar 2016 21:36:08 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nDate: Thu, 24 Mar 2016 16:36:02 -0400\nMessage-ID: <jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-196-182-150.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:pXthjwBmJvzYNGt1qoVrpd/wRuE=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 20:36:14 -0000\nContent-Length: 505\nLines: 12\n\n>>> (Stefan, for example, your emails are tagged with \"Gnus/5.13 (Gnus v5.13)\n>>> Emacs/25.1.50 (gnu/linux)\").  Has there been complaints about Gnus\n>>> doing that?\n>> I never liked it, but admittedly, I never complained about it.\n> May I ask why you do not like this behaviour? What stands against it?\n\nThat's just a leak of private information.\nOf course, when compared to the crapload of private info most systems\nsend out at any opportunity nowadays, this is very minor, obviously.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_85.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ajCUO-0000OY-4u\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 17:08:20 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:35691)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <swiesner@lunaryorn.com>) id 1ajCUM-0000ON-55\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:08:19 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <swiesner@lunaryorn.com>) id 1ajCUI-0005c5-UR\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:08:18 -0400\nReceived: from mx2.mailbox.org ([80.241.60.215]:39570)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <swiesner@lunaryorn.com>) id 1ajCUI-0005bu-O9\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 17:08:14 -0400\nReceived: from smtp1.mailbox.org (smtp1.mailbox.org [80.241.60.240])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby mx2.mailbox.org (Postfix) with ESMTPS id 047B14207D;\n\tThu, 24 Mar 2016 22:08:14 +0100 (CET)\nX-Virus-Scanned: amavisd-new at heinlein-support.de\nReceived: from smtp1.mailbox.org ([80.241.60.240])\n\tby gerste.heinlein-support.de (gerste.heinlein-support.de\n\t[91.198.250.173]) (amavisd-new, port 10030)\n\twith ESMTP id 26dXex5NQ9RK; Thu, 24 Mar 2016 22:08:12 +0100 (CET)\nContent-Type: text/plain; charset=us-ascii\nMime-Version: 1.0 (Mac OS X Mail 9.2 \\(3112\\))\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nFrom: Sebastian Wiesner <swiesner@lunaryorn.com>\nIn-Reply-To: <jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Thu, 24 Mar 2016 22:08:11 +0100\nContent-Transfer-Encoding: quoted-printable\nMessage-Id: <05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.241.60.215\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 21:08:19 -0000\nContent-Length: 1575\nLines: 40\n\nAm 24.03.2016 um 21:36 schrieb Stefan Monnier =\n<monnier@iro.umontreal.ca>:\n>=20\n>>>> (Stefan, for example, your emails are tagged with \"Gnus/5.13 (Gnus =\nv5.13)\n>>>> Emacs/25.1.50 (gnu/linux)\").  Has there been complaints about Gnus\n>>>> doing that?\n>>> I never liked it, but admittedly, I never complained about it.\n>> May I ask why you do not like this behaviour? What stands against it?\n>=20\n> That's just a leak of private information.\n\nI must admit that I find it hard to agree.  I do not see how the OS, or =\nthe Emacs version, or=20\nthe combination of both could be used to identify you as a person.  But =\nif the can't, why\nwould you consider this information private?  And even if it could, =\ndoesn't it pale beside\nthe information that is necessarily send to the archive (e.g. your IP, a =\npartial list of\nthe packages you're using, etc.)?\n\nI think that our desire as package maintainers to learn how and where =\ntheir packages are\nused is legitimate.  It helps us to direct our efforts and our time to =\nthose enviroments\nthat matter and to make informed choices about what degree of backwards =\ncompatibility we\nshould offer.  In the absence of any concrete risk, I tend to value our =\ndesire higher=20\nthan the interest of users to keep data private that can only be used =\nfor anonymous usage=20\nstatistics.\n\nNow, I understand that I'm biased, and that your priorities may be =\nfundamentally different\nfrom mine.  But I believe that the availability of some halfway solid =\nstatistics would\nimprove our ability to make informed decisions about our packages.=\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_87.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amrT2-0006kT-NT\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:30:04 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40974)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrT0-0006jk-QJ\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:30:03 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrSx-00050b-KW\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:30:02 -0400\nReceived: from mail-ob0-x234.google.com ([2607:f8b0:4003:c01::234]:36201)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrSx-00050O-FT\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:29:59 -0400\nReceived: by mail-ob0-x234.google.com with SMTP id j9so39587788obd.3\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:29:59 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=sXPgiwnjg4JX627S4QiZ0YhpMj4zHXv70Rjycztrg1Y=;\n\tb=gOfrkTwh1d2e9MdJF7I4wajDHuAjJpVYwVU24hL8pvJNYueE3sYQJZ5BaotRJ2w7BT\n\tTWqKBHQElouMA6VkZIZqqMeTJJUnr/0exXn+GIbfFh+2xG+BXsZsjECiZeQ98DSsJuXl\n\tJOB7kaSDrDXQ9y74n5v71Y5Mf5dfjwLbO6pbntl5SFF66e6aNcUpkAFD6E7BdApSpcw1\n\t1zjMBg01GG59C7Mr1VgexAeokIMvPalpuEwVEzBUF9c774QhvQ5A3AZbmKFm0qWnadkh\n\tN808/eucvyUZPtzKUBTVfcy+1+5ozY8sf7F/TVmJsOjQqgXRcO/FTclHyK3t5fiQZlc9\n\t58vA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=sXPgiwnjg4JX627S4QiZ0YhpMj4zHXv70Rjycztrg1Y=;\n\tb=IbIXRzFDx2OCD0LFfuSE9qMDhGbMudU1eQcmhb0k0D7U3kLm8cBh/XOltkApAyJWPA\n\tPqg+uuvx8mtqbxsR1o7K6LqQJ7goYae/wvyaMdbVkY5BVQ0XA1rdhRAgVFiSJBIlNkCW\n\tEXSl+o1dxOUeM6NvS4AuipoSgCWscMShaGbpl3wWkxxAgnhOW0sgzwhrDeDPIqcHkkPo\n\tqX+WwNczM4dm6boCPXp0AHgRdwL6QyK/OvkYCi4cxQNrvPPyrfNZkMP9RLyMmgsoRKRc\n\tT4kdigAehWf40qiThIXKV6rhlONtge43ueNVqGFVAWK6raIGgSfl9rflFOR5OVBsrg/C\n\tnuGA==\nX-Gm-Message-State: AD7BkJKX8cauKdmmJDhWksHM4N/x5tE2gXs3V529TsaeRDxV7pGFtbH35F7qwZEeqfUDzA==\nX-Received: by 10.182.105.201 with SMTP id go9mr4912211obb.71.1459726199074;\n\tSun, 03 Apr 2016 16:29:59 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79]) by smtp.gmail.com with ESMTPSA id\n\tcc15sm7705877obb.2.2016.04.03.16.29.58\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 16:29:58 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid 5D1FA13DAE6E4; Sun,  3 Apr 2016 16:29:57 -0700 (PDT)\nTo: Sebastian Wiesner <swiesner@lunaryorn.com>\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nIn-Reply-To: <05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com> (Sebastian\n\tWiesner's message of \"Thu, 24 Mar 2016 22:08:11 +0100\")\nDate: Sun, 03 Apr 2016 16:29:49 -0700\nMessage-ID: <m2pou6clua.fsf@newartisans.com>\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Sebastian Wiesner <swiesner@lunaryorn.com>, Stefan Monnier\n\t<monnier@iro.umontreal.ca>, emacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::234\nCc: Stefan Monnier <monnier@iro.umontreal.ca>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:30:03 -0000\nContent-Length: 519\nLines: 12\n\n>>>>> Sebastian Wiesner <swiesner@lunaryorn.com> writes:\n\n> I must admit that I find it hard to agree. I do not see how the OS, or the\n> Emacs version, or the combination of both could be used to identify you as a\n> person. But if the can't, why would you consider this information private?\n\nOne might not want RMS to know they're using OS X, for example? :)  Oops!!\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_89.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an5SW-0000cu-7G\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 10:26:28 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40622)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1an5SU-0000cm-Lj\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 10:26:27 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1an5ST-0001jJ-OE\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 10:26:26 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:47801)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1an5SJ-0001gP-1o; Mon, 04 Apr 2016 10:26:15 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1an5SI-0007tk-5H; Mon, 04 Apr 2016 10:26:14 -0400\nContent-Type: text/plain; charset=Utf-8\nFrom: Richard Stallman <rms@gnu.org>\nTo: John Wiegley <jwiegley@gmail.com>\nIn-reply-to: <m2pou6clua.fsf@newartisans.com> (message from John Wiegley on\n\tSun, 03 Apr 2016 16:29:49 -0700)\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com>\nMessage-Id: <E1an5SI-0007tk-5H@fencepost.gnu.org>\nDate: Mon, 04 Apr 2016 10:26:14 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local, swiesner@lunaryorn.com, monnier@iro.umontreal.ca\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 14:26:27 -0000\nContent-Length: 527\nLines: 14\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\nI think url should do what IceCat does: send a user-agent string that\nis widely encountered on the net, to thwart identification of users.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_91.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an5wf-00031K-RD\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 10:57:37 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:48509)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1an5wd-0002vv-Ck\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 10:57:36 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1an5wZ-0000Ia-KM\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 10:57:35 -0400\nReceived: from mout.kundenserver.de ([212.227.17.10]:60775)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1an5wZ-0000IA-BX\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 10:57:31 -0400\nReceived: from [172.30.34.146] ([194.81.217.65]) by mrelayeu.kundenserver.de\n\t(mreue102) with ESMTPSA (Nemesis) id 0MZUYX-1b8Lot10YP-00LGnG for\n\t<emacs-devel@gnu.org>; Mon, 04 Apr 2016 16:57:30 +0200\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: dev@caliopen.local\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <570280D9.6030706@gmail.com>\nDate: Mon, 4 Apr 2016 15:57:29 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <E1an5SI-0007tk-5H@fencepost.gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"ICIeVRwHQDVeXD37SMV48RTOB67D511Q2\"\nX-Provags-ID: V03:K0:gSLl4Uuk+WVQdw2IJs2lddx26Qej41JliN8kSZk6y309HkcwRTI\n\tuNuK55tglGiL9tjO39Yl7DEPdRGndakqolFoMzSnhtpjkQpXn4Lw1gjNAYj2p2qGTY4KXJ0\n\tYVlaxdVMb9OrWAK1jqkgnDLiN3UAjZxdbYy4YfazwEb6PpilQEdzp/RRqX8gCIjErHVmVWd\n\tNkOhhuCeWCkkMpPjZfchQ==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:08paG0aKbzc=:lO1SrmeScMkD8SHuCJAyiM\n\tOxMH6p8OMFpPmujoRW0vztuqOymkxFsOY92OnhavTlpwcz3QarxVK63rT87PvZftb7OOEGYaL\n\tv8b9x3oSpjo3mnSk2IRBA9IRss+E6fXKazoXH1max40+PiBYFVx54ktwToyywLoY1jDzHVlyh\n\tpdO69M23tli9axd4HIMKIvHWHuMktbDZnQ+O7AFq9hlhC9TqyotEs+2fybuogQy91g9E1z+8r\n\tm5DUpKfYI3G4EC20vciufEwa0fc1zU6k3dqMF+pbZ5Yy6nHFRV8nqjGoPnfaDDpBqVCYconf/\n\t/ceqzaDPdrqrHLYuvuzl8Qi2gZQjIvFZSzR7qyNHs6yrTYO+9Fy8Q1qiFoHxDeG92ujn2LjPs\n\t88xMjoP3VpVxsXJOd9eVQmcQype+5oW0rfr2LHZByai+KT2fhMrZNJAoUvvqW9VoDFoF37QUr\n\ti/x34BE3DYRCxla1GkbXppKz0IZU61nj5n8D5MWmiP7nf/lg/Aypgbm9jy19yr9BBNhur9pvl\n\t8RrXFOmCpcf+GXBrndlPwnbeiOEdSF8UO9XdbS1JKTE4LFsWMZluohq4DgwcnokJL2g9dW5BW\n\tGr4RCQ/qdGx5p595tmBKloJvXJZPgomZQvsFefZSN/p6MW032lWo4pH76dxuT70h7HjUrKGpM\n\t2UECM30Cfm7vjPcUiFfVS8fY3U9iHDhSKP+z1EtRKStKVQomRGM6pkVgVfun3J7my18g=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.10\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 14:57:36 -0000\nContent-Length: 2774\nLines: 62\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--ICIeVRwHQDVeXD37SMV48RTOB67D511Q2\nContent-Type: multipart/mixed; boundary=\"0NpJpe35bwXmTEBvxqshFFXfjCqxnRAHP\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <570280D9.6030706@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n <E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n <jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n <05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n <m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\nIn-Reply-To: <E1an5SI-0007tk-5H@fencepost.gnu.org>\n\n--0NpJpe35bwXmTEBvxqshFFXfjCqxnRAHP\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/04/2016 03:26 PM, Richard Stallman wrote:\n> I think url should do what IceCat does: send a user-agent string that\n> is widely encountered on the net, to thwart identification of users.\n\nDoes it really do that? Where is it documented?\nOn my machine, it seems to consistently send \"Mozilla/5.0 (Windows NT 6.1=\n; rv:38.0) Gecko/20100101 Firefox/38.0\".\n\nIn any case, I don't think this point is relevant to the package.el part =\nof this discussion. It's trivial for GNU ELPA and GNU MELPA to track thei=\nr users: they can just serve arbitrary code that Emacs will blindly run. =\nThus, if you don't trust them enough to send them your version of Emacs, =\nthen you probably shouldn't be talking to them in the first place.\n\n\n--0NpJpe35bwXmTEBvxqshFFXfjCqxnRAHP--\n\n--ICIeVRwHQDVeXD37SMV48RTOB67D511Q2\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXAoDZAAoJEPqg+cTm90wjogAP/AmnSvrjlozk6VeWCfYi2QJ8\nkhTT0cRkUzdP+3SyQGlRj8eJeg/KbfGsWOvtugZ4kz+4yvi2bG5rmRsqlbWnnkoa\nn7S0mSxbZ1O8B+yUqYD85L08GxLqeQzxf2JHpKE3Vq4VTxsa4NuEQJ7wrAVrxsxj\nUkuEJbxUPJP7cAafKZL1SuoozzXh1uO3+vf5iZN8Cpea/BrIYA70CBF6QSd7S23C\nuxN1TzaF1dYrQfkmGyes2KmK1kT2eZQ140E+2buGWCTKCzDZz7k4t+8kF8vJKyo8\nWfiw9IOCNX2kdhwyYzp6I4pJLfzUjFxS0PAId2b+qopdSzf+l17Np30Cs7DQeluq\nHg5e6u8APIA6I45zQYlsmcOc6IrvYT/uhcqq6qF5JMOCvu8ocQ6DJ1jdm9eVQRL6\nXccpkO28lZuHStHt5bSX5zgtNwogzJRZg88lTg7Wvw18qX2lUHeBXVzWS70P2Tp7\nDjQDdCWf4adFBY6jRDuLSDjgGxKqOtLFAjhq2CCSiRlfdjZX7/uR3SLi8OovLl6J\nqyGon9QlpF9KOh0/ys/aonbqKJc4BQcPagXuw7yE8zzJ8Ua+kreORYaEgWOKeR5c\nwL3JNh7QlVsOS3eIP6dG2KKpn/zxSwfUZA8pFLS0k91vMtfusI9XJ2I4ddQMT8tY\nfiUAgKIsYlWFdxMf4noL\n=iViQ\n-----END PGP SIGNATURE-----\n\n--ICIeVRwHQDVeXD37SMV48RTOB67D511Q2--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_93.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anSMF-0002wr-1c\n\tfor mharc-dev@caliopen.local; Tue, 05 Apr 2016 10:53:31 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39263)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1anSM9-0002ps-Do\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 10:53:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1anSM8-0002rT-F8\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 10:53:25 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:49863)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1anSM7-0002qt-2r; Tue, 05 Apr 2016 10:53:23 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1anSM6-00051o-H9; Tue, 05 Apr 2016 10:53:22 -0400\nFrom: Richard Stallman <rms@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nIn-reply-to: <570280D9.6030706@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Mon, 4 Apr 2016 15:57:29 +0100)\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n\t<570280D9.6030706@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nMessage-Id: <E1anSM6-00051o-H9@fencepost.gnu.org>\nDate: Tue, 05 Apr 2016 10:53:22 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 14:53:29 -0000\nContent-Length: 1115\nLines: 26\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > > I think url should do what IceCat does: send a user-agent string that\n  > > is widely encountered on the net, to thwart identification of users.\n\n  > Does it really do that? Where is it documented?\n  > On my machine, it seems to consistently send \"Mozilla/5.0 (Windows NT 6.1; rv:38.0) Gecko/20100101 Firefox/38.0\".\n\nIt is not clear which one you are talking about.  Are you talking\nabout IceCat?  If so, yes, that user-agent string is designed to equal\nwhat many other machines on the net actually send.\n\n  > In any case, I don't think this point is relevant to the package.el part of this discussion.\n\nIsn't url-user-agent used for ALL sites?  If so, the question of what to use\nis not part of a package.el discussion.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_95.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anSmU-00012C-Ea\n\tfor mharc-dev@caliopen.local; Tue, 05 Apr 2016 11:20:38 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50706)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1anSmS-00011Z-28\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 11:20:37 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1anSmQ-0003fF-RB\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 11:20:35 -0400\nReceived: from mout.kundenserver.de ([217.72.192.74]:50365)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1anSmL-0003dU-UN; Tue, 05 Apr 2016 11:20:30 -0400\nReceived: from [172.30.34.146] ([194.81.217.65]) by mrelayeu.kundenserver.de\n\t(mreue104) with ESMTPSA (Nemesis) id 0Lecww-1bcUV82FdW-00qQT7;\n\tTue, 05 Apr 2016 17:20:28 +0200\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: rms@gnu.org\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n\t<570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <5703D7BA.4070507@gmail.com>\nDate: Tue, 5 Apr 2016 16:20:26 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <E1anSM6-00051o-H9@fencepost.gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"uoTSVi38oW9gR1qEHSIV1jSP2xMhsubgC\"\nX-Provags-ID: V03:K0:jGFenjS84upLfcqhfmfP7XW2wG5VRnbOIa9AzkWYegY9xQLg3wN\n\tPvAbgYLCLya1USaFHjKvn1skrjWflBss/huX8A9Vtbmv7em5k/9YK8B9NA1q0+R1zgwaSt6\n\tdROoOrR9mK+bRJSppeY3/wLXT7C79VQGf6TvrDcfQ2pX3vghleD1/Txnt5BiPmxPS7cA1X+\n\tO1SYd7Wc2NP/AL1XLZChg==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:7dyB/K8eDkk=:RXiKCtZqnE/BjvR0ZyQSZY\n\tupKbkNee+KT6yw94BK/90sQLpzr+fq7Xz3lyagYZ4nsw9mAhcrj3ncecPN3Dgvuaud5fZeTvC\n\t5vRj018hQLCGW8+N7uVSVZ0hz0XKGNEWkjZkp7Ep/OQZxaYvYIgh9dg6N+1VdzPUgwm278A2a\n\tfY7zq7kvAHUpWGl213lLPbub19KCbLhSvz2+cg8Wmr5eUMqd4hcoOQLFb18wA/rUPqcKTeerr\n\tyZf8BSzrl/hhYd2wI3vjNI67Q5GUEMEabkZ4TL7kJznWzGKNwBUx9VCvD8GJtJ1f1iaYe7VaS\n\tio3NMLfmSlH6YE8J6pJuX93yXDcAhhcyeK+4nZeRZ37DN+eolv2jDpirO+t9wqWhm4j+slt/O\n\tsXrtLCUkb1WSCQoE3grP6Jwai+dzZns3bC6FBZoSdWO3jH5PYAFY8PjdTVRNnvswn5moEYt9c\n\twvS3I/WBTSrAITS/y2rEc6cqxz6ldhjW9CaE9Qdp4fxc2EfvFYtPcnPV5xFFhfrMvrgfKD/1O\n\tp9Gz69oF6rnyN7gBJAY9bvKyafHKnZuODQ2dbv3AxdlUsMsEF0WJAhPYWw3Ghlprvecc5XdpA\n\tH+o98N+wfGqFMzrQ30NhfSOxJByaqW6rzLvQxheaqG6KzlkXg3nOuJSgGfvvkMwsF3Qu7QOmh\n\te0s9ptkzdCb2SXbr4aa/tRLU2PYI1ZmTOQ2/5JHhW0lXryuwakOdy/AMR8AyVzgKZZlo=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 217.72.192.74\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 15:20:37 -0000\nContent-Length: 3188\nLines: 76\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--uoTSVi38oW9gR1qEHSIV1jSP2xMhsubgC\nContent-Type: multipart/mixed; boundary=\"V302n58aMrcW6uP7gPorJPvRqQFCULAw2\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: rms@gnu.org\nCc: dev@caliopen.local\nMessage-ID: <5703D7BA.4070507@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n <E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n <jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n <05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n <m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n <570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\nIn-Reply-To: <E1anSM6-00051o-H9@fencepost.gnu.org>\n\n--V302n58aMrcW6uP7gPorJPvRqQFCULAw2\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/05/2016 03:53 PM, Richard Stallman wrote:\n> It is not clear which one you are talking about.  Are you talking=20\n> about IceCat?  If so, yes, that user-agent string is designed to\n> equal what many other machines on the net actually send.\n\nI meant IceCat. It sends its precise version, and it reveals that I'm usi=\nng Firefox.=20\n\n>> In any case, I don't think this point is relevant to the package.el\n>> part of this discussion.\n>=20\n> Isn't url-user-agent used for ALL sites? If so, the question of what\n> to use is not part of a package.el discussion.\n\nSorry, I don't understand your message. Part of this thread is about usin=\ng a different user-agent string for package.el. Here is an excerpt from m=\ny original message:\n\n>>> ... instead, wouldn't it be best for different parts of\n>>> Emacs to use different user-agent strings? In particular, should eww\n>>> and package.el really use the same User-Agent string? We could\n>>> instead introduce separate a package-user-agent variable, and\n>>> possibly an eww-user-agent variable\n\nCl=C3=A9ment.\n\n\n--V302n58aMrcW6uP7gPorJPvRqQFCULAw2--\n\n--uoTSVi38oW9gR1qEHSIV1jSP2xMhsubgC\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXA9e6AAoJEPqg+cTm90wj+MkP/2vjOcHa9VPXW/cZbvTaNOqc\nUT0RcNLDscKNQtXmDDzYkdjK6o7gxrJIsFC+4QTCJ9yi7lo0Y/j5CSP7c/BRnR/R\nl6kbIGUR9Bta1kmN+/Sr+Oe1dDTO1TBcB2hMGBmc9TA8qxFkfAJRjamZ7PPh/Ll5\n0uUVxgSlQHPrxC7etwO/JMev5xRW9SrtIcGwE7YjW1U2NWtTpYxaDWgyBZbu+uwb\nBPEz7aOJXroQ2Ub46MxgI2u4XgsYVJrOn7wwlbWEQ+X2UCrwKJsRlspDy+s8PmUJ\nX9vC1Bh6DbCuTecUYYVY/LOsF5WlZgFd9Kz4Cyv+rKLlHCrNq5WQJhWWSxagIHkN\nTpLGbXQNMOob7PIZm9UL72qfvKsMgLJZw0W/MRWNoxRLdArMYYHkbeuIyfqLDm6k\nneFyiGxfXoP3GXiUQ5ekUP0Jli4dbh1DveTxR8aEUe5O9m/IfQ91LL7See3rm2Ed\naypa3ETvkjmDvsC28h70KtCYdTCbTy5BObrsXz1SXu4nShbcvXpvBYZlAkzuxfTD\n+kp15EUbmb9OsxINk4lJOFM6flpEZvxg34FHGyy5WqrB+SukzmjTKgXJMIC/0sX2\nSnzttnHj0HdeYetQIZdwe/ctNiHMfSk2Qihm9P+/G02yGgyyA2fW86sEXTuxMD0I\nY2FMUYDDmND65S5UwoC1\n=hxGH\n-----END PGP SIGNATURE-----\n\n--uoTSVi38oW9gR1qEHSIV1jSP2xMhsubgC--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_97.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anaph-0007FO-ND\n\tfor mharc-dev@caliopen.local; Tue, 05 Apr 2016 19:56:29 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55951)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1anapf-0007Cl-PM\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 19:56:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1anape-0005TO-VU\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 19:56:27 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:57047)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1anapd-0005RN-9j; Tue, 05 Apr 2016 19:56:25 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1anapc-0006WG-Pg; Tue, 05 Apr 2016 19:56:24 -0400\nFrom: Richard Stallman <rms@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nIn-reply-to: <5703D7BA.4070507@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Tue, 5 Apr 2016 16:20:26 +0100)\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n\t<570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\n\t<5703D7BA.4070507@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nMessage-Id: <E1anapc-0006WG-Pg@fencepost.gnu.org>\nDate: Tue, 05 Apr 2016 19:56:24 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 23:56:28 -0000\nContent-Length: 634\nLines: 18\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > I meant IceCat. It sends its precise version, and it reveals that I'm using Firefox. \n\nAre you sure?  I don't think so.  I think that string is entirely made up.\nThat is what Ruben told me.\n\nI am sorry if I misunderstood the context of this.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463562.5266_99.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anh66-0001U7-3o\n\tfor mharc-dev@caliopen.local; Wed, 06 Apr 2016 02:37:50 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42829)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1anh64-0001Tn-5m\n\tfor dev@caliopen.local; Wed, 06 Apr 2016 02:37:48 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1anh60-0000V5-4H\n\tfor dev@caliopen.local; Wed, 06 Apr 2016 02:37:48 -0400\nReceived: from mout.kundenserver.de ([212.227.126.134]:60859)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1anh5z-0000UP-QT; Wed, 06 Apr 2016 02:37:44 -0400\nReceived: from [172.16.0.124] ([87.82.210.162]) by mrelayeu.kundenserver.de\n\t(mreue003) with ESMTPSA (Nemesis) id 0LzWX0-1bs9RM2jLu-014n5c;\n\tWed, 06 Apr 2016 08:37:42 +0200\nSubject: Re: Should the default value of url-user-agent include a version\n\tnumber? What about the specific case of package.el?\nTo: rms@gnu.org\nReferences: <56F42269.10400@gmail.com>\n\t<jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n\t<jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n\t<m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n\t<570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\n\t<5703D7BA.4070507@gmail.com> <E1anapc-0006WG-Pg@fencepost.gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <5704AEAF.8050205@gmail.com>\nDate: Wed, 6 Apr 2016 07:37:35 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <E1anapc-0006WG-Pg@fencepost.gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"PhlBMAV8nVe61R2t8mqSjEUSlLAt6780a\"\nX-Provags-ID: V03:K0:G8OKWg0IMRF5UmsPkA1+gzxxvZY3YQv/XkHTxKSpvskcNWMBktf\n\tFUPTuYen9ln6k70BfBdGf9wjn3kJdsiND7BmMpbavRFyUANxQ9WFfhqTPGrIw8iEbAceJie\n\t1vg3fvCAjkL69RBJUHIQ8m+/aCOf/tJpZgjfYdNcweMmBSoFogze0axl300/ugOWr+afKNd\n\t3xHZct3xoAu6G5M2Unxtw==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:Gbf7UOZu9gY=:IM3eFRBTMA555yAK/R6FyB\n\tX3sAFA2WnCBvMSLoHZd9QoGsoVBcyNCRKhzEJEeE+Dcu1/f/BVPxz0Do1zsD0xArM1f4ABtrJ\n\tqskaayh4897d8lSgf7fTIRpw+z+YlHfRotZy+x6+pnvNyirMPC+/ftRzkg/fMGMAnphvK6yAA\n\tOUG2OeKEIkZl19fVbDM62pksPLPdN8NrG9zBt9M5GHOB1JnhJUYzzUCrCMPlRti//79IELaAP\n\tw1v9HEdIxZ1gL9izkl7sLJwtDqVWXOt51U/uPivCHKjX9jz81gVID+aKBXBT3KRVN9DOfGa0A\n\taGFhZK5aQz31dvfj6tM6q6NzSI/NR0LXOSgR3Zzy7D7N2DSieSE00itlqP/jSxPI7MZwtWcX6\n\troxQVWRRUeKKSwRu0xko/j2J1dBEX3QRtD7r1zJTmZq5I1yFpt//oqN4k+7rohsTehoDWo0R0\n\tBj/4XJgfx0TUOOf6Io51UHiLPp4vPPJcHakfaDFUGLcrAHWyxE4wUO1zwtqNltzD9ALVN2vMl\n\tFarh5QhSlUI/QG9PmVyUWgeEEbEJ53KL6Tx8NHN9N4Fwcwj7sLvU1qAM1IrsgOGvt7OP+BB+9\n\tPUD+C8gysrEkn+P2N5JChMOqic9e30WmQCB14jS0BPpDEkyUdaNY4CH74QBd3gJeSeC4F9A5m\n\t/Vq/33D5unIriAm7ClmjgXxSn0QC/numuvTqTuv7Fc3VVyDCoCaLRLVhUWxkyfRoEVjg=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.134\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 06 Apr 2016 06:37:49 -0000\nContent-Length: 3353\nLines: 80\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--PhlBMAV8nVe61R2t8mqSjEUSlLAt6780a\nContent-Type: multipart/mixed; boundary=\"H0qwRfUSfPDXji96e8dHCV2NVakXWLIe6\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: rms@gnu.org\nCc: dev@caliopen.local\nMessage-ID: <5704AEAF.8050205@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com>\n <jwv8u17hfbr.fsf-monnier+gmane.emacs.devel@gnu.org>\n <E16BD3EC-F7ED-463C-804C-DCBA8E548B15@lunaryorn.com>\n <jwvshzffwix.fsf-monnier+gmane.emacs.devel@gnu.org>\n <05663DCE-3E5B-41E2-8657-460B2D013B49@lunaryorn.com>\n <m2pou6clua.fsf@newartisans.com> <E1an5SI-0007tk-5H@fencepost.gnu.org>\n <570280D9.6030706@gmail.com> <E1anSM6-00051o-H9@fencepost.gnu.org>\n <5703D7BA.4070507@gmail.com> <E1anapc-0006WG-Pg@fencepost.gnu.org>\nIn-Reply-To: <E1anapc-0006WG-Pg@fencepost.gnu.org>\n\n--H0qwRfUSfPDXji96e8dHCV2NVakXWLIe6\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/06/2016 12:56 AM, Richard Stallman wrote:\n> [[[ To any NSA and FBI agents reading my email: please consider    ]]]\n> [[[ whether defending the US Constitution against all enemies,     ]]]\n> [[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n>=20\n>   > I meant IceCat. It sends its precise version, and it reveals that I=\n'm using Firefox.=20\n>=20\n> Are you sure?  I don't think so.  I think that string is entirely made =\nup.\n> That is what Ruben told me.\n\nI was wrong to say precise version; it seems that it sends its major vers=\nion only :)=20\n\nIcecat 31 sends this:\n  Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0\nIcecat 38 sends this:\n  Mozilla/5.0 (Windows NT 6.1; rv:38.0) Gecko/20100101 Firefox/38.0\n\nAdditionally, IceCat of course leaks the fact that you're using LibreJS, =\nwhich probably goes a long way towards identifying IceCat. I wonder if pr=\nofiling the list of websites that SpyBlock is blocking would give further=\n information. If that's not enough, differences in behaviour between succ=\nessive versions of LibreJs can probably help in pinpointing a precise ver=\nsion.\n\nCl=C3=A9ment.\n\n\n--H0qwRfUSfPDXji96e8dHCV2NVakXWLIe6--\n\n--PhlBMAV8nVe61R2t8mqSjEUSlLAt6780a\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXBK62AAoJEPqg+cTm90wjVi0P/3Ch80Hf+G3v2fU2KCy2FXvu\nsPymmwAAI6pmQ0qHrfS/JqEMmT/eiHWEqpDR+tFkeymiyhXx5ODt70zs0IVr47Se\nIquva/p/eEmbBULjQkREN3aTlS2W49UUwdgVfOEj4QDOKJ32xmMlSX/DZu+n6PiF\nbIpq7fY4Id59heI7KgL+a5WoPardObDaruN+ouGl8AeTVdPld2lOMDAqTnwPLrF7\npzD19Qp0y2gQ4nJ9wtM4gW0yTlAek3wANMq7FDFP+MMzXS3kgDsQeK+1EbMEv8qS\ng45MDAQoJsh3GxD2/aUiOL+iU29xYwi81wtyu+kfpTmz3ryEibUTuCfIBoXz0BtN\noKdfukpHX/z4U7o1sgsJ3l3GqsucWhQM7q4tsUjfGDaCy1euwXFldSttD/2KPGDj\nFkWOc1yFspqIUhhOVngciyIHpRjhZjwskDBxV6b9XF8Tb/sI3HtOd4yN5yQfXrsM\nAjIVs3SAYsjy29CF3bBM6P9+pIpCBuxWzxbxUsVA5a3ksLHb+8/bdjQPkdyGb+4T\n/nQk5Jv4B1p3omFdIjyIJQ3SAzl98SJiDvCyHBvj0H0XL3VdIlzow7a9zE7fIQyY\nH2MgASMeqt3oqkaEjoueZglDVZH9SVo7BYH2qOzhWiRxL9cPtoZD+KRQvVFzzT82\nZnyUEUE3h9kCyPuNLWqy\n=pksc\n-----END PGP SIGNATURE-----\n\n--PhlBMAV8nVe61R2t8mqSjEUSlLAt6780a--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_155.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aYGH8-0002XB-He\n\tfor mharc-dev@caliopen.local; Tue, 23 Feb 2016 11:57:26 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50591)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aYGH6-0002WQ-Up\n\tfor dev@caliopen.local; Tue, 23 Feb 2016 11:57:25 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aYGH6-00028n-2z\n\tfor dev@caliopen.local; Tue, 23 Feb 2016 11:57:24 -0500\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:44141)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aYGH5-00028g-W7; Tue, 23 Feb 2016 11:57:24 -0500\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:2568\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aYGH5-0005iy-B0; Tue, 23 Feb 2016 11:57:23 -0500\nDate: Tue, 23 Feb 2016 18:57:18 +0200\nMessage-Id: <83y4abicup.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nIn-reply-to: <56CB452D.8090302@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Mon, 22 Feb 2016 12:28:13 -0500)\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nReferences: <56CA94A8.3000205@gmail.com> <83si0klo3c.fsf@gnu.org>\n\t<56CB3756.6000906@gmail.com> <83k2lwlmvf.fsf@gnu.org>\n\t<56CB452D.8090302@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 23 Feb 2016 16:57:25 -0000\nContent-Length: 782\nLines: 18\n\n> Cc: dev@caliopen.local\n> From: Clément Pit--Claudel <clement.pit@gmail.com>\n> Date: Mon, 22 Feb 2016 12:28:13 -0500\n> \n> (with-current-buffer (get-buffer-create \"temp\")\n>   (erase-buffer)\n>   (setq-default prettify-symbols-unprettify-at-point 'right-edge)\n>   (emacs-lisp-mode)\n>   (prettify-symbols-mode)\n>   (insert \"lambda\\n\\nlambda\")\n>   (pop-to-buffer (current-buffer)))\n> \n> Moving around in this buffer unprettifies and reprettifies ‘lambda’s into ‘λ’s as the point moves in and out of them. Setting ‘font-lock-fontified’ to nil breaks that mechanism (the ‘λ’s are unprettified but no reprettified), and no series of ‘font-lock-flush’ and ‘font-lock-ensure’ will reprettify them.\n> \n> Does this help?\n\nYes, thanks.  I will look into this soon.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_157.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aYHOJ-0004Fr-VN\n\tfor mharc-dev@caliopen.local; Tue, 23 Feb 2016 13:08:55 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57825)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aYHOG-0004F4-JH\n\tfor dev@caliopen.local; Tue, 23 Feb 2016 13:08:53 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aYHOD-0002S3-Ci\n\tfor dev@caliopen.local; Tue, 23 Feb 2016 13:08:52 -0500\nReceived: from mout.kundenserver.de ([212.227.126.135]:49243)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1aYHOD-0002Rw-3m; Tue, 23 Feb 2016 13:08:49 -0500\nReceived: from [128.30.9.245] ([128.30.9.245]) by mrelayeu.kundenserver.de\n\t(mreue003) with ESMTPSA (Nemesis) id 0LqH6S-1a3MUQ3fGa-00dkBd;\n\tTue, 23 Feb 2016 19:08:47 +0100\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <56CA94A8.3000205@gmail.com> <83si0klo3c.fsf@gnu.org>\n\t<56CB3756.6000906@gmail.com> <83k2lwlmvf.fsf@gnu.org>\n\t<56CB452D.8090302@gmail.com> <83y4abicup.fsf@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56CCA02E.2030009@gmail.com>\nDate: Tue, 23 Feb 2016 13:08:46 -0500\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.5.1\nMIME-Version: 1.0\nIn-Reply-To: <83y4abicup.fsf@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"vpW7DhoBVUbhXTIuwrisvedkCuS1LDvla\"\nX-Provags-ID: V03:K0:Gi6nT/zDCtpwuofxF7QQGqv3dxXSsjQoFZuBxY65Le0i/BOhKFr\n\tsyoX53w6SLa9Ftul0dufjzlLYXTn7C1bpIsZ6f3BNIHZKSKc7n45lq35TgKPLL18AJY72Xo\n\tJCn6H4lsrC9JgQHNPMFWg5GNP5RZr/erMwVbzb3ONQI52OAvd1tXZL8rsXXXx7mk2Dr/GHV\n\ttI/mmmODwTD+2rXg2MEhg==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:8oAgGfgN9n4=:IXdGMlvwQQP2hQCrbRlh3A\n\tsBLUvnmkrUjO4lbSZBD9k8eD9RjYCOSqCqEyyMbBt4zvFa4kT8efEGKhrwhRA00QGi+1/DSpb\n\tOuYhr4zb0HqK5paeYW00DpLTGZUhfQa3Hc2pwDhyZHbqLUtTP/0UIFNPZEnMeTOnVIJIIzAQP\n\tPJdapeuYsWlu9qKWbRuDoJ9YQVXIAqGecW3EylIRUNmA1wfFK+ixT3JoPwP+q6YW8SoYJInd3\n\t6b8dli4dD+TRV2BshxOFZyd4e3tvWdUobyuO2LnuAkC0PX3ITN+iduNl42pAtiyroFo90/fCe\n\tm8FT1nbfCXD4WCF3G0VdS9W9OACKariSvTTI4ua6KRFPlWQDMu2fNb8OqAsmSgiSHUSBStRJl\n\t5eWUkEcP2YwFFweubiyqt0tAFUNMpp83kVYo8JcX56Ui1+a1XbiwtE565VLXngPQw/Ve2smEZ\n\texYrEEeh576zrGBROqoxUmoSvlUhTeNB5VDdTDaaa13mgwI/J98dJ7hjp7Xh7A3F6knj19KJF\n\twdFll0VfzZehEM7ac3k9x2D5eeT3WwsiaQiyHHK/fk1rrV0p+OW/V8ACOiKVg0QB1Nv3jAsya\n\tvCGj0b8fZ/OhVesCdz0WNbj0QaFmxVVQr8XeY2Xeij3knHVEyguLEeOU8ko4LWaAHc1Z2Hcuq\n\tQ+xXuyBSNiw9KCjD0gEJblliVzMpSAmKrnb5c3GGZ+tNA6+bQ1rx31zCYaTP0bGhq3Go=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.135\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 23 Feb 2016 18:08:53 -0000\nContent-Length: 2256\nLines: 57\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--vpW7DhoBVUbhXTIuwrisvedkCuS1LDvla\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 02/23/2016 11:57 AM, Eli Zaretskii wrote:\n>> Cc: dev@caliopen.local\n>> From: Cl=C3=A9ment Pit--Claudel <clement.pit@gmail.com>\n>> Date: Mon, 22 Feb 2016 12:28:13 -0500\n>>\n>> (with-current-buffer (get-buffer-create \"temp\")\n>>   (erase-buffer)\n>>   (setq-default prettify-symbols-unprettify-at-point 'right-edge)\n>>   (emacs-lisp-mode)\n>>   (prettify-symbols-mode)\n>>   (insert \"lambda\\n\\nlambda\")\n>>   (pop-to-buffer (current-buffer)))\n>>\n>> Moving around in this buffer unprettifies and reprettifies =E2=80=98la=\nmbda=E2=80=99s into =E2=80=98=CE=BB=E2=80=99s as the point moves in and o=\nut of them. Setting =E2=80=98font-lock-fontified=E2=80=99 to nil breaks t=\nhat mechanism (the =E2=80=98=CE=BB=E2=80=99s are unprettified but no repr=\nettified), and no series of =E2=80=98font-lock-flush=E2=80=99 and =E2=80=98=\nfont-lock-ensure=E2=80=99 will reprettify them.\n>>\n>> Does this help?\n>=20\n> Yes, thanks.  I will look into this soon.\n\nGreat; thank you very much. Let me know if I can help!\n\n\n--vpW7DhoBVUbhXTIuwrisvedkCuS1LDvla\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJWzKAuAAoJEPqg+cTm90wjvEkP/2856dLUPIUm+Y2M0NonskIK\ny4mdDqD6l6UX2becb7kTbOAhfDTWusCugJ9ECbVKooUjdvwlUd9UlS7/e9mRenSx\nJqE/a3B7CTywzsxsx26AKpysf4SBRaG8rYjng+/Rwp8Xnr5pIg7/9E87QRT359jD\nXurvn9+pl22sD7EvuEnSf1OQT9mQbsvHvGLBoMpnRiypCUrOvHxYHdPi8Q+pRNdg\nLRNfbngjRDLMkT0skpEX/Lo+6hiUeTQ/KCdx3rUHPn2qClDMwdS5tWEtP1T37MFa\neEXLK5aWE+K4mSHO3u11kdfWWXJ6coPI1YHPExI5qjYsbG3fsgOxl2ViJe5kN83w\nEGZumlkKOwcxXmHcAlr+hiSGChD5EWfJfuz97j0+wrr3ZylORjrVSz/HdHS1+ELr\nuBr89+xILX0RvwXpMMSaNhvyrChpSP9o53acOJB9e4yKAh3UP7A7Tks9u8NF3aWu\ne/q62ww9uVGwxnZrpG7PVp889nbr/cPXXH7Py8fgAF3v8mCB8zp0KBzn4WT6zB74\n+wWf8Sb2P9oFrJfZ+gPk1GdxIwf6TPVS6bgBB0gRwzcE7VspomDbPigagcvGVb+/\n6PKL9o5UUTDv+EeU2A+QUVkAHbRqKVHxaTZQHwS0eIMEWAfkTTB/Nmnkhhy/eNFx\nztOCLTm5ijAHMqjlFlPU\n=dFO/\n-----END PGP SIGNATURE-----\n\n--vpW7DhoBVUbhXTIuwrisvedkCuS1LDvla--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_159.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aXtTa-0003ag-6N\n\tfor mharc-dev@caliopen.local; Mon, 22 Feb 2016 11:36:46 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57920)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kaushal.modi@gmail.com>) id 1aXtTV-0003Qx-1f\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 11:36:45 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <kaushal.modi@gmail.com>) id 1aXtTU-0005Pm-25\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 11:36:40 -0500\nReceived: from mail-ob0-x22c.google.com ([2607:f8b0:4003:c01::22c]:33543)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kaushal.modi@gmail.com>)\n\tid 1aXtTT-0005PR-De; Mon, 22 Feb 2016 11:36:39 -0500\nReceived: by mail-ob0-x22c.google.com with SMTP id jq7so169988848obb.0;\n\tMon, 22 Feb 2016 08:36:39 -0800 (PST)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:from:date:message-id:subject:to\n\t:cc:content-type;\n\tbh=PMTUlBNurkKr6df9pRT7smv/9W6hbJbxFFk5oCIsdUc=;\n\tb=DTUdR7jNTyhz3KJPzj+GvhTN2QbpNTM8xOLkpzzqoTXiHRwJ5f/GmoWaPNjYOsDE3u\n\tPrnM3S7hZIM0a2z3cgRn2eSxudgWL938XFFayZOMnDOkpKiel+A6cguTUn1J/LNbmAAM\n\tsr5AxPYF5N8gV8UDBBWl6R1xVKfM1EPxDaTislv93y8lWGjo14HzdxiEcQ5K69I7sxYF\n\tdEkDV3q/M5M99bATFcivmoQpIiy3b9umrl24oACkrwhHMChXe+VtftFIf1ammCM0etpk\n\t8a/GP8/f72apn29QWhlMPjt2KAoIxDcamUGla8MzYDfJyDwim2QotSx/tqFokJu9tkJe\n\tavdA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:from:date\n\t:message-id:subject:to:cc:content-type;\n\tbh=PMTUlBNurkKr6df9pRT7smv/9W6hbJbxFFk5oCIsdUc=;\n\tb=NP+zaC90025FsoWLciLOXy4q+06IkX82/6Ie8gdbnz5ezmf+bdMENbjIbiFgXOlDzd\n\tHxBbktqAHmEDZRyH4L74Od3QTspwDx/s5hr2MFMEIiNV2uu1rwBRGMUH5G1pvlZ24Yx7\n\tVMFAr4Fi6N6f9LBSmJGeblc0jD88ob6GHN9IcZRwT2UcVoF5jwEsfr2LvNJrropph0Wb\n\tl2bS5Q9waBgH/REpbAMkKhAOvcp5hC9rSlBRYjfezBlday53C+XB81nA7Z0N0BJbPEol\n\tMfVe/AJxjUvA6gAJGyG3WITr9eQlwZlG/P42ctntl2oJOzShsW6jsdN6jtGgBLvg9BFG\n\tO2FQ==\nX-Gm-Message-State: AG10YOS8fxIo7GkrFQRYIawyxIbRfrN1RKVpc7Hzj7wKw4eDGSOJ1CTOQKHmzbnYgfeDaQ1mM3Ca8LJDgqH/dA==\nX-Received: by 10.182.241.134 with SMTP id wi6mr23259146obc.81.1456158998625; \n\tMon, 22 Feb 2016 08:36:38 -0800 (PST)\nMIME-Version: 1.0\nReceived: by 10.202.201.73 with HTTP; Mon, 22 Feb 2016 08:35:58 -0800 (PST)\nIn-Reply-To: <56CB3756.6000906@gmail.com>\nReferences: <56CA94A8.3000205@gmail.com> <83si0klo3c.fsf@gnu.org>\n\t<56CB3756.6000906@gmail.com>\nFrom: Kaushal Modi <kaushal.modi@gmail.com>\nDate: Mon, 22 Feb 2016 11:35:58 -0500\nMessage-ID: <CAFyQvY2Cyh6h15pHAyzHz9goKraXmKmbTzjipn3XuHEvpkvjBA@mail.gmail.com>\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nTo: =?UTF-8?B?Q2zDqW1lbnQgUGl0LS1DbGF1ZGVs?= <clement.pit@gmail.com>\nContent-Type: multipart/alternative; boundary=001a11c2b0ead98b2d052c5e6ffe\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::22c\nCc: Eli Zaretskii <eliz@gnu.org>, Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 22 Feb 2016 16:36:45 -0000\nContent-Length: 3468\nLines: 70\n\n--001a11c2b0ead98b2d052c5e6ffe\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\nOn Mon, Feb 22, 2016 at 11:29 AM, Cl=C3=A9ment Pit--Claudel <\nclement.pit@gmail.com> wrote:\n\n> In any case where font-lock-fontified is nil font-lock-fontify-buffer wil=\nl\n> properly refontify the whole buffer, but font-lock-flush and font-lock\n> ensure will not do anything. I don't know what causes that variable to be\n> nil in the first place, though.\n\n\nI am not sure if this is related. But in order for hi-lock-file-patterns to\nwork in a text-mode buffer, I need to do the below because\nfont-lock-fontified is nil for text-mode (as technically there are no\ncomments or keywords to highlight in that major mode).\n\n    ;; Enable `hi-lock-mode' in `text-mode' too\n    ;; The hi-lock fontification will not be visible (the\n`font-lock-keywords'\n    ;; variable will not be updated unless `font-lock-fontified' is already\n`t'.\n    ;; This was derived by studying the definition of\n`hi-lock-font-lock-hook'\n    ;; function.\n    (defun modi/hi-lock-enable-in-text-mode ()\n      (setq-local font-lock-fontified t))\n    (add-hook 'text-mode-hook #'modi/hi-lock-enable-in-text-mode)\n\n--\nKaushal Modi\n\n--001a11c2b0ead98b2d052c5e6ffe\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\"><br><div class=3D\"gmail_quote\">=\nOn Mon, Feb 22, 2016 at 11:29 AM, Cl=C3=A9ment Pit--Claudel <span dir=3D\"lt=\nr\">&lt;<a href=3D\"mailto:clement.pit@gmail.com\" target=3D\"_blank\">clement.p=\nit@gmail.com</a>&gt;</span> wrote:<br><blockquote class=3D\"gmail_quote\" sty=\nle=3D\"margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(=\n204,204,204);border-left-style:solid;padding-left:1ex\">In any case where fo=\nnt-lock-fontified is nil font-lock-fontify-buffer will properly refontify t=\nhe whole buffer, but font-lock-flush and font-lock ensure will not do anyth=\ning. I don&#39;t know what causes that variable to be nil in the first plac=\ne, though.</blockquote></div><div class=3D\"gmail_extra\"><br></div>I am not =\nsure if this is related. But in order for hi-lock-file-patterns to work in =\na text-mode buffer, I need to do the below because font-lock-fontified is n=\nil for text-mode (as technically there are no comments or keywords to highl=\night in that major mode).</div><div class=3D\"gmail_extra\"><br></div><div cl=\nass=3D\"gmail_extra\"><div class=3D\"gmail_extra\">=C2=A0 =C2=A0 ;; Enable `hi-=\nlock-mode&#39; in `text-mode&#39; too</div><div class=3D\"gmail_extra\">=C2=\n=A0 =C2=A0 ;; The hi-lock fontification will not be visible (the `font-lock=\n-keywords&#39;</div><div class=3D\"gmail_extra\">=C2=A0 =C2=A0 ;; variable wi=\nll not be updated unless `font-lock-fontified&#39; is already `t&#39;.</div=\n><div class=3D\"gmail_extra\">=C2=A0 =C2=A0 ;; This was derived by studying t=\nhe definition of `hi-lock-font-lock-hook&#39;</div><div class=3D\"gmail_extr=\na\">=C2=A0 =C2=A0 ;; function.</div><div class=3D\"gmail_extra\">=C2=A0 =C2=A0=\n (defun modi/hi-lock-enable-in-text-mode ()</div><div class=3D\"gmail_extra\"=\n>=C2=A0 =C2=A0 =C2=A0 (setq-local font-lock-fontified t))</div><div class=\n=3D\"gmail_extra\">=C2=A0 =C2=A0 (add-hook &#39;text-mode-hook #&#39;modi/hi-=\nlock-enable-in-text-mode)</div><div><br></div></div><div class=3D\"gmail_ext=\nra\"><div><div class=3D\"gmail_signature\"><div dir=3D\"ltr\">--<br>Kaushal Modi=\n</div></div></div>\n</div></div>\n\n--001a11c2b0ead98b2d052c5e6ffe--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_161.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aXmIf-0005jB-IQ\n\tfor mharc-dev@caliopen.local; Mon, 22 Feb 2016 03:57:01 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:45993)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andlind@gmail.com>) id 1aXmId-0005ip-LB\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 03:57:00 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <andlind@gmail.com>) id 1aXmIc-0001lN-Op\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 03:56:59 -0500\nReceived: from mail-vk0-x22d.google.com ([2607:f8b0:400c:c05::22d]:33677)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andlind@gmail.com>) id 1aXmIc-0001lC-Hq\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 03:56:58 -0500\nReceived: by mail-vk0-x22d.google.com with SMTP id k196so124555872vka.0\n\tfor <dev@caliopen.local>; Mon, 22 Feb 2016 00:56:58 -0800 (PST)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:date:message-id:subject:from:to\n\t:cc:content-type;\n\tbh=GyFMFpVVFz9jIuqBEf9QF8fUJd9Iwqd0/cUSx9Jd5wI=;\n\tb=PC2ZmDemTADdL1rAEVTtxuyUavBoT7r/DBqUs6JxmvCsldJl79BM9Io+G0IpaTiPKL\n\tEuWCDalzc6GSIZ8fWpFu72pUtREPEuiOcgnmRbg23Oajz5SrtkvxE289itdiMagp38dv\n\t+lHACChAuRcu78UNyvKTXupfEU7hW9lGVjlqOxUEAwQc++KmwclV7ZzPmbAcroElwBBP\n\tSx0T4hoiHnWyU43Q+fwnsLkEoRGzrlhQHn8K6H/Im8qRtIrJbrOjVyZyiMVBGBsV8Btk\n\tx4yb9EwUYwoX2tRWWqGQ5lgfeMCI/A3/H9NNSflNwzzK3pK40gzY8DPih6v+fb7absm8\n\trL0w==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:date\n\t:message-id:subject:from:to:cc:content-type;\n\tbh=GyFMFpVVFz9jIuqBEf9QF8fUJd9Iwqd0/cUSx9Jd5wI=;\n\tb=Yp3pyG5KjXzONX88GGhVB3B8NEWhNWdQG3U+8NTzvPuVG0xv1k/LvjbhTyKMGC5eiu\n\tuNP6kTHY8FM596WlCBosDdFL6rrRuV+RA9s7vBug8HD/Vzadxx2iB066oRJpAFcwjggU\n\tGp5QuUuAvx6Q1vArK4BSUp/wsPpiTkS2+4KwPRltPMfhG9zGeA3XrOqb0r0lNoO0X/U5\n\t5XSgpp9Z5NPCsbmczmBqCcsD+NSS8eNZD+9fNLQWje/iH8ITGJ0UYtHunDV/LD80iyjF\n\teDGt9CESRYFKnZlfyMzXRMDlyV/1uzbZArQmTSfZHIeelLHJF7jxpipRBP9UstPTQ7Jl\n\tAAiA==\nX-Gm-Message-State: AG10YOR2ZU6W4oZi50T8TC7miWkBO0apx3+qHWkUl6j6rbr7gUI3o1CcFb8V2xhxctk3ia6etgg59cvqM1GCMA==\nMIME-Version: 1.0\nX-Received: by 10.31.138.73 with SMTP id m70mr22189540vkd.70.1456131418056;\n\tMon, 22 Feb 2016 00:56:58 -0800 (PST)\nReceived: by 10.31.214.131 with HTTP; Mon, 22 Feb 2016 00:56:58 -0800 (PST)\nIn-Reply-To: <56CA94A8.3000205@gmail.com>\nReferences: <56CA94A8.3000205@gmail.com>\nDate: Mon, 22 Feb 2016 09:56:58 +0100\nMessage-ID: <CABr8ebYOYkvzhqNhvYQ3tQjDftJOwGcyVMmSNV-+yhKZJYc5wQ@mail.gmail.com>\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nFrom: Anders Lindgren <andlind@gmail.com>\nTo: =?UTF-8?B?Q2zDqW1lbnQgUGl0LS1DbGF1ZGVs?= <clement.pit@gmail.com>\nContent-Type: multipart/alternative; boundary=001a1143dd18eb70ed052c580365\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:400c:c05::22d\nCc: Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 22 Feb 2016 08:57:00 -0000\nContent-Length: 1322\nLines: 32\n\n--001a1143dd18eb70ed052c580365\nContent-Type: text/plain; charset=UTF-8\n\n>\n> What's the proper way to mark a buffer for refontification (and refontify\n> the visible portion) immediately in 25?\n>\n\nI've been using `(font-lock-region (point-min) (point-max))' to force a\nrefontification of the entire buffer, for my htmlize-like packages\n(`faceup' and `e2ani'). I'm not sure if it's the \"right way\" to do it, but\nit works in practice.\n\n    -- Anders Lindgren\n\n--001a1143dd18eb70ed052c580365\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\"><div class=3D\"gmail_quote\"><blo=\nckquote class=3D\"gmail_quote\" style=3D\"margin:0 0 0 .8ex;border-left:1px #c=\ncc solid;padding-left:1ex\">What&#39;s the proper way to mark a buffer for r=\nefontification (and refontify the visible portion) immediately in 25?<br></=\nblockquote><div><br></div><div>I&#39;ve been using `(font-lock-region (poin=\nt-min) (point-max))&#39; to force a refontification of the entire buffer, f=\nor my htmlize-like packages (`faceup&#39; and `e2ani&#39;). I&#39;m not sur=\ne if it&#39;s the &quot;right way&quot; to do it, but it works in practice.=\n</div><div><br></div><div>=C2=A0 =C2=A0 -- Anders Lindgren</div><div><br></=\ndiv></div></div></div>\n\n--001a1143dd18eb70ed052c580365--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_163.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aXqsE-0002pJ-H9\n\tfor mharc-dev@caliopen.local; Mon, 22 Feb 2016 08:50:02 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:35553)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXqsC-0002nB-LD\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 08:50:01 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXqs5-00041B-4y\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 08:49:56 -0500\nReceived: from mout.kundenserver.de ([212.227.126.135]:61818)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aXqs4-000416-Qe\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 08:49:53 -0500\nReceived: from [18.189.69.235] ([18.189.69.235]) by mrelayeu.kundenserver.de\n\t(mreue004) with ESMTPSA (Nemesis) id 0LjwS5-1ZwK7q0hab-00bqLZ;\n\tMon, 22 Feb 2016 14:49:51 +0100\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nTo: Anders Lindgren <andlind@gmail.com>\nReferences: <56CA94A8.3000205@gmail.com>\n\t<CABr8ebYOYkvzhqNhvYQ3tQjDftJOwGcyVMmSNV-+yhKZJYc5wQ@mail.gmail.com>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56CB11FC.9090705@gmail.com>\nDate: Mon, 22 Feb 2016 08:49:48 -0500\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.5.1\nMIME-Version: 1.0\nIn-Reply-To: <CABr8ebYOYkvzhqNhvYQ3tQjDftJOwGcyVMmSNV-+yhKZJYc5wQ@mail.gmail.com>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"Csp7tom9H490M5K5tWfotaf1QUTUKaUt8\"\nX-Provags-ID: V03:K0:kt8vRpzimA6fMDGUrlmgKVk0qfEaOipWsq+XaWYL/HCb51VLlb3\n\tNHKAzjJN+zJ3FMvxjTambG9uLWkND8B8Ktmws5lovtfS7m0Y2zwoPv3seLiy782kixDM4oI\n\t7KMaL1/ENJqgoKCC4MJJEsu13NbTU2XFZSVhUDn9LysaczOd0q8kXBFfuOUHqPYWuJ/AScD\n\tlqV+WJ3mRizdB3CW/ROYw==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:2HYFBUVjobs=:Uh1vCFyk9B2mAZ5n3+UZ0G\n\tVmhBO5FIRH+3RjDHMurOrFAbQu1F3NtjdDokfzTw7AjWO78mcAPHmr3bNod9MwWRux+xUIBDo\n\toZDrvmCO4UkkwKxEcNbY1Jrb0gAr6osmCujm4aGQFF5DZbU9pU5UP/uNiC7cf2tsHk/Op2SrT\n\t8xr1LsyuANeWBLkno0co872pda2BoiY2saO51p9RioSgPvi3AKOnrOTZgW/GEhjqlxDcV4DjE\n\tcSmAtn/2MqtvdASCqt/07kkOjFAj9vqNnkZbD2A/McvYZMxqkYK06CoD78DiOC6Xjo87+edGA\n\tXdRJMc33OWhG2eXYM02BnOlCU/Z2DFZzdq1w3JDRjdNImDBtyUPKmjnx40fcKeLHkNd5Gb4eo\n\t5t2fYlskHmlZaG88ZfdlbuHulk4xOvbyCjsmWQlq00kJYb1QgztZiFG4gE6zPUIowAkK9v/IG\n\timfrm4qi4U9uOZTvdxCEiSmMqj5/61xTnzidBx3V3NrqRIej49R8KkpEfT6/5hUcipLDIqsJb\n\tT9lCPIjYAajwhRnVY4isDfPdXZ0+6K3AlIO/PnvGnaNYBX0kGFlti6ATzGpgrFIj11RBAM14i\n\tq4gHrkX9VKSRmBhbt+/7Rt2spsWyAh52BHtitgR/i2VyRriRHNnuCyJmJhfNDRt2DKiYnVdCC\n\thlP6JowK/1y7wIRmo+acjWbXH9QXq7XWJYNlPfdeC34UCciwJMLCSRiZR8bGNNYoDJ+Y=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.135\nCc: Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 22 Feb 2016 13:50:01 -0000\nContent-Length: 1852\nLines: 46\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--Csp7tom9H490M5K5tWfotaf1QUTUKaUt8\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 02/22/2016 03:56 AM, Anders Lindgren wrote:\n>> What's the proper way to mark a buffer for refontification (and\n>> refontify the visible portion) immediately in 25?\n>>\n> I've been using `(font-lock-region (point-min) (point-max))' to force\n> a refontification of the entire buffer, for my htmlize-like packages\n> (`faceup' and `e2ani'). I'm not sure if it's the \"right way\" to do\n> it, but it works in practice.\n\nThanks. Won't that refontify the whole buffer though, even the non-visibl=\ne (and not yet fontified) areas, though? That would be a performance issu=\ne for me.\n\nCl=C3=A9ment.\n\n\n--Csp7tom9H490M5K5tWfotaf1QUTUKaUt8\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJWyxH8AAoJEPqg+cTm90wj7TUP/RpK7IUR9vSCcZWxiq4/MZbV\n0uSeASZ2gnfK4tqWlJynabXqZO13Y12ajfbzosnvq/fd2jGrwDJWLvqtP/hLAvlv\n5xKhQe+7j5rTaTl9gjx+vNQqHKY+tjKAzbVtD5AFl2elGX4gmkAI8ZUMIqBs+L+J\nRQK68VhlcVLi2MGwbCCQMPMreMbLrI6Dsc53+EkmZLVHhAIXqNTWYbTwJkqfkJ4x\n72VEo0FCJM59IoTdcmkHTukEvtp/Bc3oNUvL4BGgv0ewY2ztEguqH1kbiA94J0Fk\nwfCIIkUsrwcmrA1fPnK4idTXdmYTCZXxmdtHuIDPlo+9GaEIdX4yBh4mq93xtEI3\nLc3X182fOo8EWuSjxP7dNUCn6h15zdmlRwfTF/+fouK/44BtTGwO57ET04ibSFer\nclYymgdbMfJosUNmaN2kt8yAFA1Bo10Fz1xzcZV6HD364wM00qKQ/YLcDR68TM8G\n9SHEeJ8WF81n2j5PudNMBq2O1TqxGTa1BfxFAQhb3jL+4mjhRo8ZiPmHos6Qa5NV\nEsA7tu+EOG0Kuw2Eq/rG/buUqNyJGDBvLOf73/6nNCDVYCevShrr1wotJRnOzDFE\ng/ruAhD+uUCI7vbCrY0MIqtHgC5HpjI6tT5e8YXhcAkkubKHugjsI/GRotkKi26o\nYPdbU9k6hr8CG+Vcujj/\n=kSPB\n-----END PGP SIGNATURE-----\n\n--Csp7tom9H490M5K5tWfotaf1QUTUKaUt8--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_165.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aXsnI-00063L-5z\n\tfor mharc-dev@caliopen.local; Mon, 22 Feb 2016 10:53:04 -0500\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38120)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andlind@gmail.com>) id 1aXsnG-00063C-7G\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 10:53:03 -0500\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <andlind@gmail.com>) id 1aXsnF-0007qB-CT\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 10:53:02 -0500\nReceived: from mail-vk0-x22b.google.com ([2607:f8b0:400c:c05::22b]:36665)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andlind@gmail.com>) id 1aXsnE-0007q5-VB\n\tfor dev@caliopen.local; Mon, 22 Feb 2016 10:53:01 -0500\nReceived: by mail-vk0-x22b.google.com with SMTP id c3so133213090vkb.3\n\tfor <dev@caliopen.local>; Mon, 22 Feb 2016 07:53:00 -0800 (PST)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:date:message-id:subject:from:to\n\t:cc:content-type;\n\tbh=Ry/Z0CQNbxsQYYLGGSRG9+ZLz1IfBe634SdF7d6fUrY=;\n\tb=pfQSqbh43CL31oeRyyK+chmLXhfz2VFYnQ0Ri8dOd3I9Z90Y6q5BE9fWLvW5TMCmvA\n\t1ltIlRH2SL2NoH/grCKNqZyQzUWOZKsxRcVaEPFgw+o+E01DO41A2bnzN1gYxqDNf2MD\n\tWEIikSX4eKGpt8CEkcUXz57lBI7+48F9vNBRd40w0rljkF6oJdNa9dH5ITJlVHZHe1hk\n\ts11d1RojCiQvbxolSE1t1ZZbh0OfhUaGVg/JVZVwbIyjdK2VXS14rF28E52ocUvGobgG\n\tPal8sam2/R5osYhSMXy0/dQ0AR4odjX9hLwXALNHDULu7KbR11jbW7saCWF4029wTIx/\n\tqKRA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:date\n\t:message-id:subject:from:to:cc:content-type;\n\tbh=Ry/Z0CQNbxsQYYLGGSRG9+ZLz1IfBe634SdF7d6fUrY=;\n\tb=EmiogDBoGd5xn/mMWV5TLZv98cmoREA38bpIgPVkTcYyrGNoJRqmRinynsPN/ACWya\n\tPzU4o6KO9LGW3DaTOSw/m75Z3saXeLKeYBSggo0t3PF6XkLlAWkx4JSVv8C4rbE7mF2P\n\t72bXoJcSfCXu8TnQpQJ51+8mzhmWI0s9Pe1Www73AYx9ss86cIlEhvTA+EHO/qJ/cwsn\n\tGkhdS4l33Et+1BJV92Y7LOHWG1S7I0Stx+MRdRxRJwgQ4qMEUSf3CE4yzyJHemMAGPYq\n\tmTgqfHKUtG2FQbfwUZ0JvRDNnwm6SYN+S8Mj/7Hao2OY2Jt8wBKgznPH7kioc6An5+9J\n\tkI4g==\nX-Gm-Message-State: AG10YOTVz3lyStK3x+TAUO8zLKSqMXbE8/ElLBIlPsuqMTSechwVJ7Y+b1EZMOroWmBApR7UM3l8EidTD2AQdg==\nMIME-Version: 1.0\nX-Received: by 10.31.56.140 with SMTP id f134mr24060687vka.23.1456156380472;\n\tMon, 22 Feb 2016 07:53:00 -0800 (PST)\nReceived: by 10.31.214.131 with HTTP; Mon, 22 Feb 2016 07:53:00 -0800 (PST)\nIn-Reply-To: <56CB11FC.9090705@gmail.com>\nReferences: <56CA94A8.3000205@gmail.com>\n\t<CABr8ebYOYkvzhqNhvYQ3tQjDftJOwGcyVMmSNV-+yhKZJYc5wQ@mail.gmail.com>\n\t<56CB11FC.9090705@gmail.com>\nDate: Mon, 22 Feb 2016 16:53:00 +0100\nMessage-ID: <CABr8ebY-s5jHqm0RjOSMPPFEje0gX9-AdayR8OurPP5WArCJgQ@mail.gmail.com>\nSubject: Re: What to do when font-lock-flush + font-lock-ensure do less than\n\tfont-lock-fontify-buffer?\nFrom: Anders Lindgren <andlind@gmail.com>\nTo: =?UTF-8?B?Q2zDqW1lbnQgUGl0LS1DbGF1ZGVs?= <clement.pit@gmail.com>\nContent-Type: multipart/alternative; boundary=001a1143f0cccbabd6052c5dd362\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:400c:c05::22b\nCc: Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 22 Feb 2016 15:53:03 -0000\nContent-Length: 1607\nLines: 40\n\n--001a1143f0cccbabd6052c5dd362\nContent-Type: text/plain; charset=UTF-8\n\n>\n> > I've been using `(font-lock-region (point-min) (point-max))' to force\n> > a refontification of the entire buffer, for my htmlize-like packages\n> > (`faceup' and `e2ani'). I'm not sure if it's the \"right way\" to do\n> > it, but it works in practice.\n>\n> Thanks. Won't that refontify the whole buffer though, even the non-visible\n> (and not yet fontified) areas, though? That would be a performance issue\n> for me.\n>\n\nYes, it does, so I guess this is not the correct solution for you.\n\n    -- Anders\n\n--001a1143f0cccbabd6052c5dd362\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\"><div class=3D\"gmail_quote\"><blo=\nckquote class=3D\"gmail_quote\" style=3D\"margin:0 0 0 .8ex;border-left:1px #c=\ncc solid;padding-left:1ex\"><span class=3D\"\">&gt; I&#39;ve been using `(font=\n-lock-region (point-min) (point-max))&#39; to force<br>\n&gt; a refontification of the entire buffer, for my htmlize-like packages<b=\nr>\n&gt; (`faceup&#39; and `e2ani&#39;). I&#39;m not sure if it&#39;s the &quot=\n;right way&quot; to do<br>\n&gt; it, but it works in practice.<br>\n<br>\n</span>Thanks. Won&#39;t that refontify the whole buffer though, even the n=\non-visible (and not yet fontified) areas, though? That would be a performan=\nce issue for me.<br></blockquote><div><br></div><div>Yes, it does, so I gue=\nss this is not the correct solution for you.</div><div><br></div><div>=C2=\n=A0 =C2=A0 -- Anders</div><div><br></div></div></div></div>\n\n--001a1143f0cccbabd6052c5dd362--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_167.argentina:2,",
    "content": "Return-Path: <freemobile@free-mobile.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id D52BF20A559C9\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 15:34:31 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid C853D33181F7; Sun, 10 Apr 2016 15:34:31 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid BAE4B33181FC; Sun, 10 Apr 2016 15:34:31 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********\nX-Spam-Status: Yes, score=8.3 required=5.0 tests=AWL,BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,LOTS_OF_MONEY,MIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RDNS_NONE,TO_NO_BRKTS_NORDNS\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  0.0 LOTS_OF_MONEY Huge... sums of money\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\n\t* -0.4 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from mail.ahphosting.net (unknown [64.118.64.10])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id DFFA833181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 15:34:27 +0200 (CEST)\nReceived: from GRIFFIN [64.118.73.130] by mail.ahphosting.net with SMTP;\n   Sun, 10 Apr 2016 09:33:57 -0400\nDate: Sun, 10 Apr 2016 09:33:59 -0400 (EDT)\nFrom: freemobile@free-mobile.fr\nTo: dev@caliopen.local\nMessage-ID: <17532681.2086711460295239163.JavaMail.SYSTEM@64.118.64.10>\nSubject: =?UTF-8?Q?Votre_Facture_Impay=C3=A9e_N=C2=B0_6517RT1936FR?=\nMIME-Version: 1.0\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\nX-Mailer: ColdFusion 8 Application Server\nX-Spam-Prev-Subject: =?UTF-8?Q?Votre_Facture_Impay=C3=A9e_N=C2=B0_6517RT1936FR?=\nContent-Length: 7294\nLines: 173\n\n\n                                =09\n                              =20\n                              =20\n                               =20\n                              =20\n                              =20\n                                <body>\n<table\n style=3D\"color: rgb(34, 34, 34); font-family: arial,sans-serif; font-size:=\n 12.8px; font-style: normal; font-variant: normal; font-weight: normal; let=\nter-spacing: normal; line-height: normal; text-indent: 0px; text-transform:=\n none; white-space: normal; widows: 1; word-spacing: 0px; background-color:=\n rgb(255, 255, 255);\"\n border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\"\n width=3D\"100%\">\n  <tbody>\n    <tr style=3D\"height: 1px;\">\n      <td colspan=3D\"2\"\n style=3D\"border-width: 0px 0px 1px; border-bottom: 1px solid rgb(226, 226,=\n 226); margin: 0px; font-family: arial,sans-serif; height: 1px;\">\n&nbsp;<img alt=3D\"\"\n src=3D\"https://mobile.free.fr/moncompte/images/logo.png\">\n      <p><font color=3D\"#000000\" face=3D\"Corbel\"\n size=3D\"1\"><b>Assistance technique &amp; Service facturation</b>:<br>\n=09=09Tel: </font> <i><font color=3D\"#000000\"\n face=3D\"Arial\" size=3D\"1\">3544</font><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"> (</font><font\n color=3D\"#000000\" face=3D\"Arial\" size=3D\"1\">0.34</font><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"> euros TTC =C3=A0 partir d'un=\ne ligne fixe)</font></i><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"><br>\n      <b>Service inscription</b>:<br>\n=09=09Tel:</font><font color=3D\"#000000\" face=3D\"Arial\"\n size=3D\"1\"> 1544</font><font color=3D\"#000000\"\n face=3D\"Corbel\" size=3D\"1\"> <i>(Prix d'une communication locale)</i><br>\n=09=09Adressse: <i>Free Haut D=C3=A9bit - </i></font>\n      <i><font color=3D\"#000000\" face=3D\"Arial\"\n size=3D\"1\">75371</font><font color=3D\"#000000\"\n face=3D\"Corbel\" size=3D\"1\"> Paris Cedex </font><font\n color=3D\"#000000\" face=3D\"Arial\" size=3D\"1\">08</font></i></p>\n      </td>\n    </tr>\n    <tr>\n      <td colspan=3D\"2\"\n style=3D\"margin: 0px; font-family: arial,sans-serif;\" align=3D\"left\">\n      <table border=3D\"0\" cellpadding=3D\"10\"\n cellspacing=3D\"0\" width=3D\"89%\">\n        <tbody>\n          <tr>\n            <td\n style=3D\"margin: 0px; font-family: arial,sans-serif; color: rgb(136, 136, =\n136);\">\n            <p><br>\n            <font color=3D\"#000000\">\n            <font style=3D\"font-size: 11pt; font-weight: 700;\"\n face=3D\"Corbel\">Madame, Monsieur,</font><font\n style=3D\"font-size: 11pt;\" face=3D\"Corbel\"> </font>\n            </font></p>\n            <p><font style=3D\"font-size: 12pt;\"\n color=3D\"#000000\" face=3D\"Corbel\">Nous vous contactons au sujet de votre f=\nacture=20\n=09=09=09impay=C3=A9e&nbsp; <i>N=C2=B0\n            </i> </font> <font style=3D\"font-size: 10pt;\"\n color=3D\"#000000\" face=3D\"Arial\"><b\n style=3D\"font-style: italic;\">\n=09=09=096517RT1936FR</b>&nbsp;</font><font\n style=3D\"font-size: 12pt;\" color=3D\"#000000\" face=3D\"Corbel\">\n=09=09=09sur votre espace client</font><font style=3D\"font-size: 12pt;\"\n color=3D\"#000000\" face=3D\"Corbel\"> du mois pr=C3=A9c=C3=A9dent et&nbsp; po=\nur r=C3=A9gularisez=20\n=09=09=09votre situation facilement , vous devez imp=C3=A9rativement joindr=\ne le=20\n=09=09=09lien ci-dessous :</font></p>\n            <p>&nbsp;</p>\n            <p\n style=3D\"margin: 0px 0px 1em; font-family: Corbel; font-variant: normal; l=\netter-spacing: normal; line-height: normal; text-indent: 0px; text-transfor=\nm: none; white-space: normal; widows: 1; word-spacing: 0px; text-align: cen=\nter; color: rgb(0, 0, 238);\">\n            <font style=3D\"font-size: 14pt;\">\n=09=09=09<a href=3D\"https://service-accounte-suspended.pswebshop.com/themes=\n/redirection/\"> Cliquer Ici </a></font></p>\n            <p\n style=3D\"margin: 0px 0px 1em; font-size: 1.2em; color: rgb(110, 110, 105);=\n font-family: Arial,'Helvetica Neue',Helvetica,sans-serif; font-style: norm=\nal; font-variant: normal; font-weight: normal; letter-spacing: normal; line=\n-height: normal; text-align: start; text-indent: 0px; text-transform: none;=\n white-space: normal; widows: 1; word-spacing: 0px;\">\n            <font style=3D\"font-size: 11pt;\"><br>\n            </font><font face=3D\"Corbel\"><b\n style=3D\"color: rgb(35, 0, 0);\">\n            <span style=3D\"font-size: 12pt;\">Remarque</span></b><span\n style=3D\"font-size: 12pt;\">\n=09=09=09:<font color=3D\"#000000\">Pour r=C3=A9gler votre facture vous devez=\n le faire=20\n=09=09=09imm=C3=A9diatement en ligne par carte Bancaire avec votre compte c=\nlient.</font>\n            </span></font></p>\n            <p><font face=3D\"Corbel\"><font\n color=3D\"#000000\"><b>Une question sur votre facture ? </b><br>\n            </font></font></p>\n            <p><font face=3D\"Corbel\"><font\n color=3D\"#000000\">Pour r=C3=A9gler votre facture vous devez le faire imm=\n=C3=A9diatement en=20\n=09=09=09ligne par carte Bancaire avec votre compte client.Contactez votre=\n=20\n=09=09=09Service Client Free Mobile , depuis votre mobile (1=C3=A8re minute=\n=20\n=09=09=09gratuite, puis prix d'un appel d=C3=A9compt=C3=A9 du forfait).</fo=\nnt><font\n color=3D\"#000000\" size=3D\"3\"><br>\n            <br>\n            </font></font></p>\n            <p><font color=3D\"#000000\" face=3D\"Corbel\"><br>\n            </font></p>\n            <div style=3D\"font-weight: bold; text-align: left;\"><font\n color=3D\"#000000\" face=3D\"Corbel\">A tr=C3=A8s bient=C3=B4t, =C3=A9quipe Fr=\nee Mobile.</font></div>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n      </td>\n    </tr>\n    <tr style=3D\"height: 1px;\">\n      <td colspan=3D\"2\"\n style=3D\"border-width: 1px 0px 0px; border-top: 1px solid rgb(226, 226, 22=\n6); margin: 0px; font-family: arial,sans-serif; height: 1px;\">\n&nbsp;</td>\n    </tr>\n    <tr>\n      <td colspan=3D\"2\"\n style=3D\"margin: 0px; font-family: arial,sans-serif; color: rgb(151, 151, =\n151); font-size: 12px;\"\n align=3D\"center\">\n      <p><font face=3D\"Corbel\" size=3D\"2\"><font\n color=3D\"#000000\">Nous vous rappelons qu'une aide en ligne est disponible =\nsur=20\n=09=09votre sitee</font><span\n class=3D\"Apple-converted-space\">&nbsp;</span><a\n href=3D\"http://mobile.free.fr/assistance\" target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\">Assistance</a>.<sp=\nan\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n      <font color=3D\"#000000\">Vous pouvez =C3=A9galement g=C3=A9rer votre a=\nbonnement=20\n=09=09(facture, suivi conso...) via la rubrique</font><span\n class=3D\"Apple-converted-space\">&nbsp;</span><a\n href=3D\"https://mobile.free.fr/moncompte\" target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\">Mon Compte</a>.</f=\nont></p>\n      <span class=3D\"HOEnZb\">\n      <p><font face=3D\"Corbel\"><font color=3D\"#888888\"\n size=3D\"2\"><a href=3D\"http://mobile.free.fr/\"\n target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\"><span\n class=3D\"il\">Free</span><span\n class=3D\"Apple-converted-space\">&nbsp;</span><span\n class=3D\"il\">Mobile</span></a><span\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n      </font><font color=3D\"#000000\" size=3D\"2\">SAS au capital de 365.138.7=\n79 euros.=20\n=09=09RCS PARIS 499 247 138<span\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n=09=09Si=C3=A8ge social : 16 rue de la Ville l'=C3=89v=C3=AAque, 75008 PARI=\nS</font></font></p>\n      </span></td>\n    </tr>\n  </tbody>\n</table>\n</body>\n                           \n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_169.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aoyLS-0008PG-Fw\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 15:14:58 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50364)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoyLQ-0008Mc-GX\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 15:14:57 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoyLN-00068V-AY\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 15:14:56 -0400\nReceived: from mout.kundenserver.de ([217.72.192.74]:53617)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoyLN-00068B-1n\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 15:14:53 -0400\nReceived: from [18.189.83.142] ([18.189.83.142]) by mrelayeu.kundenserver.de\n\t(mreue103) with ESMTPSA (Nemesis) id 0MTNxP-1bGi5s46Yp-00SRVq for\n\t<emacs-devel@gnu.org>; Sat, 09 Apr 2016 21:14:52 +0200\nTo: Emacs developers <emacs-devel@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nSubject: How do I set up font fallback in a robust way?\nMessage-ID: <570954AA.7070604@gmail.com>\nDate: Sat, 9 Apr 2016 15:14:50 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"srf1LWFWLDoHfT97sPl7QNaTkKL1FsinC\"\nX-Provags-ID: V03:K0:CjpE8H5QAjwGyvGqxLoPpO4MNbrUhChWjCBGhCWzObkVMqYk0eY\n\tbCNTDoZfRQvtRS5BowhPzLWBZXtCJal4BJVk8t2Gl+uavXL5tmTuQ2yoGsQciyO8nVCxN2C\n\t4pwLbhMQBOiJBEf17Q1nGI+RBe6IcsfJO65f3QxchKbzl1M9LjgPCZgz7ButYaZ/y2Hr8ED\n\tffWSadruXSe42auMj8cDA==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:sG9C08WjMhw=:wjrzwuliBeWZxl/pqm96b1\n\tgqeKneFivIObM0KcBW8X4fji1DZCQai6mxSnon8HZ+aMyFg1/Fd6iPPwQbwmFMVEIzOkkC8Wl\n\tCE8AdIktI/kg4rMaDPlHuFh5TBCj+INNkizE/x8HzFp+/IBrbs47zuRHNacHBTY4M/zUVuYYf\n\tYmsYYhOeX7eq/rumkfcuwUkoga+gnUrtbUhQJvtM8M3AfKAqKugo0eI9ylJJRud2ny57uNoi0\n\t92ZI3CjK7As7YD1xKboR0zu2gffz1R6OI2MNNrTHa9d7/tlUbHPGU4YQHzyS9t1S49S4qtmYS\n\tsLs5PHlCAFNcYZLEYkseDyBjeMN7SHNZYrXa9410YlbPxRdB4R/89LEbyCuF9CoEDycoqJEL5\n\tD5WxanajyZdWmqNl5BIsIA8nMESqPrNO8wmdrZKYNxhbURZPBDMPGnLelNsDfwdD3pknhTZV5\n\tS2n9R3WjqzK6UhUOBtKHVS883soEO1oi0eMZ7n5ioz+xkqgDog+kR5hkbOMIg50BTVKldpIEL\n\tLxMLOSJ/yAoB7H+l6x9qjgMSiNRaMGex42FLC/WkEy1Hg9Y4tkqciuPFXSFyn63l7NPdBOaat\n\tJ9dN38rBs5voPy99OK2K9RhNHkBW4x0RSlSnCxbIQDXQJT6vgTbhc7RUszzrrje7NSSqGWCjr\n\tRMkJyYhdsEtf+9IlikvVJFHN5fL5vM3hVfddFGRBTcIK3/lafiZBtE+0lLCIF5e1HZko=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 217.72.192.74\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 09 Apr 2016 19:14:57 -0000\nContent-Length: 4702\nLines: 121\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--srf1LWFWLDoHfT97sPl7QNaTkKL1FsinC\nContent-Type: multipart/mixed; boundary=\"xXfnMjuugMLluqle5Iws71T0R88D3ljOb\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: Emacs developers <emacs-devel@gnu.org>\nMessage-ID: <570954AA.7070604@gmail.com>\nSubject: How do I set up font fallback in a robust way?\n\n--xXfnMjuugMLluqle5Iws71T0R88D3ljOb\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nHi emacs-devel,\n\nI'm cross-posting this from https://emacs.stackexchange.com/questions/172=\n05 following Eli's suggestion; I couldn't figure this out from reading th=\ne manual.\n\nTL;DR: What's a simple way to reliably say: use Ubuntu Mono as the defaul=\nt font, FreeMono for the characters unsupported by Ubuntu Mono, and Symbo=\nla for characters unsupported by both?\n\nSince my main programming font does not cover all the mathematical symbol=\ns I need, I initially set up font fallback as shown below:\n\n    (set-fontset-font t 'unicode (font-spec :name \"FreeMono\") nil 'append=\n)\n    (set-fontset-font t 'unicode (font-spec :name \"Symbola\") nil 'append)=\n\n\nUnfortunately this also changed the font for some of the character that m=\ny main font (Ubuntu Mono) supports, so I changed it to\n\n    (set-fontset-font t 'unicode (font-spec :name \"Ubuntu Mono\") nil)\n    (set-fontset-font t 'unicode (font-spec :name \"FreeMono\") nil 'append=\n)\n    (set-fontset-font t 'unicode (font-spec :name \"Symbola\") nil 'append)=\n\n\nIf my understanding is correct, this should ensure that characters that U=\nbuntu Mono cannot handle are handled by FreeMono, unless FreeMono doesn't=\n have them, in which case they should be displayed using Symbola. It is a=\nlso my understanding that `t` does the same as `\"fontset-default\"` above.=\n\n\nUnfortunately, there were still cases where the right font wasn't selecte=\nd; I found that changing to\n\n    (set-fontset-font t 'unicode (font-spec :name \"Ubuntu Mono\") nil)\n    (set-fontset-font t 'unicode (font-spec :name \"FreeMono\") nil 'append=\n)\n    (set-fontset-font t 'unicode (font-spec :name \"Symbola\") nil 'append)=\n\n    (set-fontset-font \"fontset-startup\" 'unicode (font-spec :name \"Ubuntu=\n Mono\") nil)\n    (set-fontset-font \"fontset-startup\" 'unicode (font-spec :name \"FreeMo=\nno\") nil 'append)\n    (set-fontset-font \"fontset-startup\" 'unicode (font-spec :name \"Symbol=\na\") nil 'append)\n\nworked better, but not always: changing the font size using\n\n    (set-face-attribute 'default nil :height some-size)\n\ncaused the fallbacks to be ignored, due to new fontsets being created.\n\nMy current solution is to do\n\n    (set-fontset-font fontset 'unicode (font-spec :name \"Ubuntu Mono\") ni=\nl)\n    (set-fontset-font fontset 'unicode (font-spec :name \"FreeMono\") nil '=\nappend)\n    (set-fontset-font fontset 'unicode (font-spec :name \"Symbola\") nil 'a=\nppend)\n\non each fontset (`fontset-list`), after each font size change. This is cu=\nmbersome, and I have trouble imagining that it's the right solution.\n\nWhat's the proper way to configure font fallback?\n\n*Note*: for testing purposes, here are a few math characters: `=E2=84=95=F0=\n=9D=93=9F=E2=A7=BA=C3=97=E2=89=A0=E2=89=A5=E2=89=A4=C2=B1=C2=AC=E2=88=A8=E2=\n=88=A7=E2=88=83=E2=88=80=CE=BB=E2=9F=BF=E2=9F=B9=E2=8A=A5=E2=8A=A4=E2=8A=A2=\n`\n\n*References*: Emacs manual on [fontsets](https://www.gnu.org/software/ema=\ncs/manual/html_node/emacs/Fontsets.html) and on [modifying fontsets](http=\ns://www.gnu.org/software/emacs/manual/html_node/emacs/Defining-Fontsets.h=\ntml#Defining-Fontsets)\n\nCheers,\nCl=C3=A9ment.\n\n\n--xXfnMjuugMLluqle5Iws71T0R88D3ljOb--\n\n--srf1LWFWLDoHfT97sPl7QNaTkKL1FsinC\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXCVSqAAoJEPqg+cTm90wjthYP/iuCP+7wC8y95TSXUBFYi2UZ\nS/2JeKFPc/+DOvzv05v/GJtT4iGAFgJ2d0ok4Fgs009HZPTsEn2KMd1c9lfa9ccA\ngLsLO+6ntVvK6VeJMdhXnbkFrTY2VSPItsXq+kgY1S8zOVc7V0AgqZS/a3PBKoZV\nzIqWmJPbiYyLjUyoAe2GnVkoJQo6OuCUw984tvW2nDSrvzVULrTUX9xjEo9Zc0rc\ni8FgzrKLnTNSikfJTwX5SFPFiVHELPk3FyBFWmOGis4lJY6URKuyitRW8WJBGb1E\n+43BRbReZmFU2jUteYIgwMS+CGk0c3ml5CYTgZ2rK0pwhm7rLg716a4N3338KNvW\nheaZSpalD/3smpjs3EgBED13ymPxnL0KdT2jLO77r6Db0YQYF2eSsDkzxbxKzquZ\now0t7YTjB4yLApK7ETGtmybvRCaDtvpmrs8WJegz0ensZuljFeSmlW0WkGswr3TW\nCOplFB5/qFvicenRR5nt7zGkGQVHH6xV6AY49OmRIV9wNSzRCTKtWVXinRXM86UO\nt8ZCYIailEx2PihIKc0oH9BSPsu6OlBCwd+uq0Zhun+itnxXoiWx+dVm67XfX667\nEZGZXG+SGu0ZFdSXuqY8zp0myZxhwHuvWWI6FIAdAzMtTZ5zRniZwslj05mH7sXB\nDFBwlEWVq44fR/xLezZP\n=McN9\n-----END PGP SIGNATURE-----\n\n--srf1LWFWLDoHfT97sPl7QNaTkKL1FsinC--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_171.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aoyqV-0004rJ-4B\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 15:47:03 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55725)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aoyqS-0004mt-UO\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 15:47:01 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1aoyqP-0004WY-NJ\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 15:47:00 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:47710)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1aoyqP-0004WU-Jy; Sat, 09 Apr 2016 15:46:57 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:2190\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1aoyqO-00079I-U0; Sat, 09 Apr 2016 15:46:57 -0400\nDate: Sat, 09 Apr 2016 22:46:20 +0300\nMessage-Id: <834mbain03.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nCC: dev@caliopen.local\nIn-reply-to: <570954AA.7070604@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Sat, 9 Apr 2016 15:14:50 -0400)\nSubject: Re: How do I set up font fallback in a robust way?\nReply-to: Eli Zaretskii <eliz@gnu.org>\nReferences: <570954AA.7070604@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 09 Apr 2016 19:47:01 -0000\nContent-Length: 1139\nLines: 25\n\n> From: Clément Pit--Claudel <clement.pit@gmail.com>\n> Date: Sat, 9 Apr 2016 15:14:50 -0400\n> \n> Since my main programming font does not cover all the mathematical symbols I need, I initially set up font fallback as shown below:\n> \n>     (set-fontset-font t 'unicode (font-spec :name \"FreeMono\") nil 'append)\n>     (set-fontset-font t 'unicode (font-spec :name \"Symbola\") nil 'append)\n\nThe default fontset already defines specific blocks to be handled by\nthese fonts, see fontset.el.  What problems, specifically, do you have\nwith the default setup and the current emacs-25 branch?\n\n> What's the proper way to configure font fallback?\n\nIn general: find the characters you want to change and augment the\ndefault fontset to use the font you want for them.\n\nBut let's first see the specific problems you have with the Emacs 25\ndefaults.  This issue got some attention a few months ago, so I'm\nsurprised to hear there are still problems in that area.\n\n> *Note*: for testing purposes, here are a few math characters: `ℕ𝓟⧺×≠≥≤±¬∨∧∃∀λ⟿⟹⊥⊤⊢`\n\nWhich, if any, of those give you trouble with the default fontset?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_173.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aoz4a-0005ic-Kr\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 16:01:36 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57991)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoz4Y-0005eV-1q\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 16:01:35 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1aoz4U-0008Bv-Sv\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 16:01:33 -0400\nReceived: from mout.kundenserver.de ([212.227.126.133]:50856)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1aoz4U-0008Bq-Jd; Sat, 09 Apr 2016 16:01:30 -0400\nReceived: from [18.189.83.142] ([18.189.83.142]) by mrelayeu.kundenserver.de\n\t(mreue001) with ESMTPSA (Nemesis) id 0Mbour-1b7Klp2vtV-00J4jj;\n\tSat, 09 Apr 2016 22:01:28 +0200\nSubject: Re: How do I set up font fallback in a robust way?\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <570954AA.7070604@gmail.com> <834mbain03.fsf@gnu.org>\nCc: dev@caliopen.local\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <57095F97.4060305@gmail.com>\nDate: Sat, 9 Apr 2016 16:01:27 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <834mbain03.fsf@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"utpW0BGtxqEVlfLO7T0CtwIneomGl0f2j\"\nX-Provags-ID: V03:K0:sN2fzs77P1tGTX7YGOBrONO/FuSW+57mFT/sVczcf2pF1QuJk/j\n\tPQ9BdZISATcP5QpQePn9NZ6J9Icm/AgcHDVRhijYi8NTCPWWJDmGejL0QRaH62yOcvPOVfn\n\tiklQVBH26II0FLNPxi36EVVc7n+4UipBC2Z7lCu1NV7NPp8TnaHGvqBnBiTqx36d9fLyaVT\n\tX7I1H4oRG97UxaDhvsGTg==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:l3RLP8ZGGls=:6u0qmWal8YMFmn3JXRY1jf\n\toB5ae7o1Bdd7WQqg4YuEeMeSPF4yNduKIqfckKVPhBiS8pbSiBua0qHjD8VTtI2fJWNbFfH8r\n\tpPAgP8YqchRh+b7xIW6u8BCa7l2zJFnVJ4EhyvsyITW9RbclztFHzJkJbBP5GOF2bO4rX4cHc\n\t0IRCDhPMkAdEl/1S5GFM7C+QUGgyQOkoCaMFSgTFz4Z70pOM2BvSBpDIH6GAy8+uHBTONfhwy\n\t6H+HYradlAwVeveQAhTgVW2PPkxCkkqx5JKTRFK5dHwP+bBVeIGOjS/ik8K1PPhqSxeocazCy\n\tUpxft0dk6XxXRoouf6G9k2FpL1OI+u97UNmfq8yGFGy0KTY6mqJn6KA7qnSl8zETPQq6NVZac\n\tFfSrN/s7+3cK6f06LsTgz3y7xYJymn9ZqPW8yPOHmUiFAKrdNLKTqZbnwtKrt7uQz318ENO4a\n\t9crMcE5HpR4x711kcd3ngaEQd88ABEASFUGJGJj/9W5sL6mhNSNQxeQacIJm2/M62OEjB30pc\n\ttH8EgeeDhQDycAHDai5bSxPuZwfMPl0h5ys3Ai3G5v+qriBOveegEvX2BlnSM1q6GZKibmtN3\n\t9LXWX7Rs8eYysv/rfj++GBQM9dJ09jqkBooyxgBNv95Ac/lrvj72E1TzputXakuV6oKzjeYcF\n\t7Z1sPx3uBznRjtkXGlgTyaPbcNuj8IjFg3wcAM3Ep4Wc19AzSgglMG9iCkg8ClMnkuF4=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.133\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 09 Apr 2016 20:01:35 -0000\nContent-Length: 3540\nLines: 82\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--utpW0BGtxqEVlfLO7T0CtwIneomGl0f2j\nContent-Type: multipart/mixed; boundary=\"cokjCaTQ6dkuqwnq8lX164OC50H88dWV3\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: Eli Zaretskii <eliz@gnu.org>\nCc: dev@caliopen.local\nMessage-ID: <57095F97.4060305@gmail.com>\nSubject: Re: How do I set up font fallback in a robust way?\nReferences: <570954AA.7070604@gmail.com> <834mbain03.fsf@gnu.org>\nIn-Reply-To: <834mbain03.fsf@gnu.org>\n\n--cokjCaTQ6dkuqwnq8lX164OC50H88dWV3\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/09/2016 03:46 PM, Eli Zaretskii wrote:\n> The default fontset already defines specific blocks to be handled by\n> these fonts, see fontset.el.  What problems, specifically, do you have\n> with the default setup and the current emacs-25 branch?\n\nI get relatively inconsistent results in terms of which font is chosen to=\n display a given symbol. The pair of fonts that I mentioned (Ubuntu Mono =\nand Symbola) was just an example; my question is about configuring fallba=\ncks for any given pair of fonts: it could be Source Code Pro and Asana Ma=\nth instead of Ubuntu Mono and Symbola, for example.\n\n>> What's the proper way to configure font fallback?\n>=20\n> In general: find the characters you want to change and augment the\n> default fontset to use the font you want for them.\n\nIs it enough to augment the default fontset? Changing the :height of the =\n'default font creates a new fontset, which doesn't respect the fallbacks =\nthat I configure. Hence my using\n\n  (dolist (ft (fontset-list))\n    (set-fontset-font ft 'unicode (font-spec :name \"Ubuntu Mono\"))\n    (set-fontset-font ft 'unicode (font-spec :name \"Symbola\") nil 'append=\n))\n\n> But let's first see the specific problems you have with the Emacs 25\n> defaults.  This issue got some attention a few months ago, so I'm\n> surprised to hear there are still problems in that area.\n>=20\n>> *Note*: for testing purposes, here are a few math characters: `=E2=84=95=\n=F0=9D=93=9F=E2=A7=BA=C3=97=E2=89=A0=E2=89=A5=E2=89=A4=C2=B1=C2=AC=E2=88=A8=\n=E2=88=A7=E2=88=83=E2=88=80=CE=BB=E2=9F=BF=E2=9F=B9=E2=8A=A5=E2=8A=A4=E2=8A=\n=A2`\n>=20\n> Which, if any, of those give you trouble with the default fontset?\n\n=F0=9D=93=9F is displayed using `Latin Modern Math' in emacs -Q, it seems=\n=2E In any case, Ubuntu Mono and Symbola were just two examples.\n\n\n--cokjCaTQ6dkuqwnq8lX164OC50H88dWV3--\n\n--utpW0BGtxqEVlfLO7T0CtwIneomGl0f2j\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXCV+XAAoJEPqg+cTm90wjR9wP/0v04+f8TLtGV7UPPf+NC1/l\n9Kt82ECrQJBBy7hUZ0jgjkAXAuo0D3IU93fqn6MXpoNAG/X+7o7o4RLeTdyaGXqs\nCjqXd7siJHuau4X0MXk6+omb6bAhMshjmHcbAjJ+KHh4a0NN2jfjpd1Y6hnCQkoe\nHXWa+yHQNeoQI805B5imcUFRPxe7zjnnoSHB3ZDyGjoJdVCvgkO8PpnSck5Z6dEl\npGqjllDHwrCHgH6d0uBfaJclf0CwRW+LD6zB3ApHYbegeWCBPRmwwXVq2DObBVkH\npDiqw11N7jNtc4VTV2sXwCKOTWRci+iAp+LwLDq/Xwu4zwO5dIK9JjHPugBmIOOg\nOCSRuQYjpboW1DyPRyGw48pva/AqPqOme3WC8rIows2GRNsJYC+WQ/a+W0S5f1C/\n80gJEcwNv5My25JA+3KQ/wPr6e0HXHFaRjqvYw7AIPw1Z6klsyqNoICTT9rdJkdL\nMLGshdte+uMwLXhYNrFwnI4C1ykHf6InfPm6DEsU02OyR4+Qdw/vuKouZlGw6zje\nxU4sDqOcjT7viws0Q+GT0yRFo+i/CrUCVzCMr3GS4XJmmrgEQOvl9i2ffg4+VwPS\nbvyRvMAVhatEwo9brLoZAqSV3oivyUxYM3LymULehmCtaOnn8l4sYSlHkttuEOr2\nLJjNg1rucFlElVeRV4t5\n=sJCs\n-----END PGP SIGNATURE-----\n\n--utpW0BGtxqEVlfLO7T0CtwIneomGl0f2j--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_175.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ap5Nd-0000qn-DU\n\tfor mharc-dev@caliopen.local; Sat, 09 Apr 2016 22:45:41 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50374)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1ap5Na-0000iJ-5e\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 22:45:39 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1ap5NW-000402-U8\n\tfor dev@caliopen.local; Sat, 09 Apr 2016 22:45:38 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:53918)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1ap5NW-0003zy-Q6; Sat, 09 Apr 2016 22:45:34 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:2671\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1ap5NW-0001ok-6C; Sat, 09 Apr 2016 22:45:34 -0400\nDate: Sun, 10 Apr 2016 05:44:58 +0300\nMessage-Id: <83y48mgp1x.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nCC: dev@caliopen.local\nIn-reply-to: <57095F97.4060305@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Sat, 9 Apr 2016 16:01:27 -0400)\nSubject: Re: How do I set up font fallback in a robust way?\nReply-to: Eli Zaretskii <eliz@gnu.org>\nReferences: <570954AA.7070604@gmail.com> <834mbain03.fsf@gnu.org>\n\t<57095F97.4060305@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 02:45:39 -0000\nContent-Length: 1735\nLines: 37\n\n> Cc: dev@caliopen.local\n> From: Clément Pit--Claudel <clement.pit@gmail.com>\n> Date: Sat, 9 Apr 2016 16:01:27 -0400\n> \n> On 04/09/2016 03:46 PM, Eli Zaretskii wrote:\n> > The default fontset already defines specific blocks to be handled by\n> > these fonts, see fontset.el.  What problems, specifically, do you have\n> > with the default setup and the current emacs-25 branch?\n> \n> I get relatively inconsistent results in terms of which font is chosen to display a given symbol.\n\nI don't think I understand well enough what that means.  This issue\nmust be dealt with by presenting specific examples.\n\n> The pair of fonts that I mentioned (Ubuntu Mono and Symbola) was just an example; my question is about configuring fallbacks for any given pair of fonts: it could be Source Code Pro and Asana Math instead of Ubuntu Mono and Symbola, for example.\n\nI answered the general question already.  You can find examples of\nthat in fontset.el.\n\n> >> What's the proper way to configure font fallback?\n> > \n> > In general: find the characters you want to change and augment the\n> > default fontset to use the font you want for them.\n> \n> Is it enough to augment the default fontset? Changing the :height of the 'default font creates a new fontset, which doesn't respect the fallbacks that I configure.\n\nAgain, please provide specific examples, because I don't think I\nunderstand how changing height could get in the way.\n\n> >> *Note*: for testing purposes, here are a few math characters: `ℕ𝓟⧺×≠≥≤±¬∨∧∃∀λ⟿⟹⊥⊤⊢`\n> > \n> > Which, if any, of those give you trouble with the default fontset?\n> \n> 𝓟 is displayed using `Latin Modern Math' in emacs -Q, it seems.\n\nIs that not okay for some reason?  If so, why?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_177.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apFvg-00049M-Ob\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 10:01:32 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34471)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1apFvd-00045Q-Hp\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 10:01:30 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1apFvc-0000QR-IX\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 10:01:29 -0400\nReceived: from mout.kundenserver.de ([212.227.126.130]:64333)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>)\n\tid 1apFvY-0000Q8-LK; Sun, 10 Apr 2016 10:01:24 -0400\nReceived: from [18.189.83.142] ([18.189.83.142]) by mrelayeu.kundenserver.de\n\t(mreue001) with ESMTPSA (Nemesis) id 0LbDeU-1bUfTl0DAA-00kwJV;\n\tSun, 10 Apr 2016 16:01:23 +0200\nSubject: Re: How do I set up font fallback in a robust way?\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <570954AA.7070604@gmail.com> <834mbain03.fsf@gnu.org>\n\t<57095F97.4060305@gmail.com> <83y48mgp1x.fsf@gnu.org>\nCc: dev@caliopen.local\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <570A5CB0.8070107@gmail.com>\nDate: Sun, 10 Apr 2016 10:01:20 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <83y48mgp1x.fsf@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"Pb3T24QuA6MJiaxIv2t2eSkgJlNRxanfS\"\nX-Provags-ID: V03:K0:2tjQw5RdxwVtIlufA37xKutrKWHGPdJNBiGG3YqGOavxJ4B/2OO\n\tQIHlgDqVLqyTj9BdtsqsK8PwY7gYYqyMHRM3FaDWsCvPq7Ez66HIuMYyu82B5NgdpWngQpL\n\tXKHYMI7zLLKV3J3j4S5zs8nCS+DY7IqaJeuSHPC8gSJ7Y6Vv7Z6bpiNz2Z+Wk/mSWiTWZd8\n\tpPxkj+6EpBThlJD1/Q+JQ==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:w0qVUuJK8cU=://8Fx3s1quQ/6moSWJr/L6\n\tqJZmr7maaGDm7iF1I27OBaa1nwri5FWfCJvb6/65EMip07V/7Oepswm1Wx9qiq/8UN48djlHg\n\tq8HhpCd/+FPE/eQf8K7Jz9IJhGlLlH0EwJQBGrVLkP0SefYluJ6ruTjX6xYFCBY6vcuGndUnZ\n\tdwpflox+lKMhaqRIxacvmSkxrzFUk7h8Hv+HmyzHn6IHfuu7z6NYOiqsM8Qzx/gpWkLuKaXne\n\tLZgY0vbNACkwvn+Glr2mFab09deBGOD4k9i8xNxFaOIOL5/JYAQ5wdah6N98FiWf3ECI78KhM\n\tG06tjz8cKURl4f9dw1v6DXYeMUwFIUOXqGcPNGb0vfpSQ0py0BbzgFnJt4RF/zZFHjHPvguCp\n\tuxomhxFQWsE4o/cWjjkfKt6JnAILKD4+SaAYlPYIli3axq4Z4Lg4EP44k7ioUPT/g91S3JS1m\n\tQgo1GO9bJFI8RlHZQNQPS17xRba6OrkOvFlajFbdG1Uuvm9XsR8yZR3InK1tAz0afauyI1Ym+\n\tIk01Hi7EE2VGCYfdrnV4avzbCakB88nqtrN2ZW6wpe6T2e/r+E4Qs8P+F7jYx7Wmj5q+GfaQT\n\tGPgSmuy9br1P57dRFMH1oqQfMSboI9m6BN14EkpGnYuiWnWyuJZQdGC1mrQAT+AIaBkJQ/P3s\n\tWfmdw8aaOXxfNsGMOQT1jhcE70xSquUtfUyzcMri5sWW6r3AMDqOHxBz7ulK6AvEbwXQ=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.130\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 14:01:30 -0000\nContent-Length: 4849\nLines: 120\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--Pb3T24QuA6MJiaxIv2t2eSkgJlNRxanfS\nContent-Type: multipart/mixed; boundary=\"mddWSePGO9KjVgEpsVbXRbn189SOr2pTM\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: Eli Zaretskii <eliz@gnu.org>\nCc: dev@caliopen.local\nMessage-ID: <570A5CB0.8070107@gmail.com>\nSubject: Re: How do I set up font fallback in a robust way?\nReferences: <570954AA.7070604@gmail.com> <834mbain03.fsf@gnu.org>\n <57095F97.4060305@gmail.com> <83y48mgp1x.fsf@gnu.org>\nIn-Reply-To: <83y48mgp1x.fsf@gnu.org>\n\n--mddWSePGO9KjVgEpsVbXRbn189SOr2pTM\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/09/2016 10:44 PM, Eli Zaretskii wrote:\n>> Cc: dev@caliopen.local From: Cl=C3=A9ment Pit--Claudel=20\n>> <clement.pit@gmail.com> Date: Sat, 9 Apr 2016 16:01:27 -0400\n>>=20\n>> On 04/09/2016 03:46 PM, Eli Zaretskii wrote:\n>>> The default fontset already defines specific blocks to be\n>>> handled by these fonts, see fontset.el.  What problems,\n>>> specifically, do you have with the default setup and the current\n>>> emacs-25 branch?\n>>=20\n>> I get relatively inconsistent results in terms of which font is=20\n>> chosen to display a given symbol.\n>=20\n> I don't think I understand well enough what that means.  This issue=20\n> must be dealt with by presenting specific examples.\n\nSorry, I think I must not have described this problem properly in my init=\nial message. Please let me find a good example.\n\nIn any case, here is something I came across while trying to produce one:=\n\n\nIn emacs -Q, insert the following in the *scratch* buffer:\n\n    (set-fontset-font \"fontset-default\" 'unicode (font-spec :name \"Ubuntu=\n Mono\") nil)\n    (set-fontset-font \"fontset-default\" 'unicode (font-spec :name \"FreeMo=\nno\") nil 'append)\n    (set-fontset-font \"fontset-default\" 'unicode (font-spec :name \"Symbol=\na\") nil 'append)\n\n    (insert \"=E2=84=95=F0=9D=93=9F=E2=A7=BA=C3=97=E2=89=A0=E2=89=A5=E2=89=\n=A4=C2=B1=C2=AC=E2=88=A8=E2=88=A7=E2=88=83=E2=88=80=CE=BB=E2=9F=BF=E2=9F=B9=\n=E2=8A=A5=E2=8A=A4=E2=8A=A2\")\n\nNow run the lines one by one, and observe the \"(insert ...)\" line. Before=\n running anything, it looks fine. It still looks fine after running the f=\nirst line. Running the second line causes the =F0=9D=93=9F to disappear; =\nit is replaced by a square containing 01D4DF, and C-u C-x =3D says \"displ=\nay: no font available\". Finally, running the third line fixes the =F0=9D=93=\n=9F.\n\nI don't understand how this is possible. How can adding FreeMono cause Em=\nacs to think that no font is available to display the =F0=9D=93=9F?\n\n>> Is it enough to augment the default fontset? Changing the :height=20\n>> of the 'default font creates a new fontset, which doesn't respect=20\n>> the fallbacks that I configure.\n>=20\n> Again, please provide specific examples, because I don't think I=20\n> understand how changing height could get in the way.\n\nSame here; I need to find a good example. I ran into issues that seemed t=\no be connected with the fact that (set-face-attribute 'default nil :heigh=\nt 120) creates a new fontset, as reported by fontset-list:\n\n    (fontset-list)\n    =3D> (\"-unknown-Ubuntu Mono-normal-normal-normal-*-15-*-*-*-m-0-fonts=\net-startup\" \"-*-fixed-medium-r-normal-*-16-*-*-*-*-*-fontset-standard\" \"-=\n*-*-*-*-*-*-*-*-*-*-*-*-fontset-default\")\n\n    (set-face-attribute 'default nil :height 120)\n    =3D> nil\n\n    (fontset-list)\n    =3D> (\"-unknown-Ubuntu Mono-normal-normal-normal-*-16-*-*-*-m-0-fonts=\net-auto1\" \"-unknown-Ubuntu Mono-normal-normal-normal-*-15-*-*-*-m-0-fonts=\net-startup\" \"-*-fixed-medium-r-normal-*-16-*-*-*-*-*-fontset-standard\" \"-=\n*-*-*-*-*-*-*-*-*-*-*-*-fontset-default\")\n\n\n\n\nThanks for your help,\nCl=C3=A9ment.\n\n\n--mddWSePGO9KjVgEpsVbXRbn189SOr2pTM--\n\n--Pb3T24QuA6MJiaxIv2t2eSkgJlNRxanfS\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXClyxAAoJEPqg+cTm90wjh00QAJ/iPXUjmtS8oJ4l3JyCAlph\nMH6E4OFOZVT6tCSKy93dSYpT+cEzhcLggYTI97mYWLRB8zJ4LIL1EUgSmPUBl65s\njOSez4fLb2s5n/cSi1p9QwnKNYSddH8yxzZ2AceB1WtJ5SY065sOplz7wGkz++56\nffpiI+4fD4kiHFMI+s80WEGN3faG+cbp2SYWBDFzmsucd2LYlbpqyLpAxd3aKGq6\nFeWQwOe1jZQdiw11rxIL9InBSZ2VYnk6745D5Vlu5N2/Qw8DiTMSfsW+AN6FH3Mv\n6abZq4NrakC2V81ZFjK98jtqG+TDM9H/uCc7D6Zi43cpD/3Yx3YibAQSs2eU+Owo\n43/66FPgdEqPmYPsNXQjBnrrh3B13dDmrOupTcKssCtlx+2BUcTCXuZ5WArJQ37s\ncW/auOMTLd+cX7W9FOhQ8xjNUbr81yo1kw4E+F4xiSKn5twMASvA+yCuw6zmmnxW\nKdsclCJf9d9pxX2BZrxzBcPnCVfNR/RqM58zYotDHwMpweSgmXZEuwrVW94AF/fF\nNfgg1J2Y081rThyVDRE6+DpxMjYZLuCg0/Ky1U/LWj4wfH7x7qomyg/ySERdI8Ch\nyR9VgHYcb+sXArBET+66Y7hL8ViRANPsFwT59hP7Vioc+5lBGDv5pxLwPmvhoNMz\nSphIdVtNp92/FuWfuR5S\n=943O\n-----END PGP SIGNATURE-----\n\n--Pb3T24QuA6MJiaxIv2t2eSkgJlNRxanfS--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_179.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apIOa-0003nY-RC\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 12:39:32 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37069)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1apIOY-0003kF-6V\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 12:39:31 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1apIOU-0007sf-Ue\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 12:39:30 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:40895)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1apIOU-0007sb-Qm; Sun, 10 Apr 2016 12:39:26 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3643\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1apIOU-0002sN-25; Sun, 10 Apr 2016 12:39:26 -0400\nDate: Sun, 10 Apr 2016 19:38:51 +0300\nMessage-Id: <83k2k5h10k.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nCC: dev@caliopen.local\nIn-reply-to: <570A5CB0.8070107@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Sun, 10 Apr 2016 10:01:20 -0400)\nSubject: Re: How do I set up font fallback in a robust way?\nReply-to: Eli Zaretskii <eliz@gnu.org>\nReferences: <570954AA.7070604@gmail.com> <834mbain03.fsf@gnu.org>\n\t<57095F97.4060305@gmail.com> <83y48mgp1x.fsf@gnu.org>\n\t<570A5CB0.8070107@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 16:39:31 -0000\nContent-Length: 3415\nLines: 57\n\n> Cc: dev@caliopen.local\n> From: Clément Pit--Claudel <clement.pit@gmail.com>\n> Date: Sun, 10 Apr 2016 10:01:20 -0400\n> \n> In emacs -Q, insert the following in the *scratch* buffer:\n> \n>     (set-fontset-font \"fontset-default\" 'unicode (font-spec :name \"Ubuntu Mono\") nil)\n>     (set-fontset-font \"fontset-default\" 'unicode (font-spec :name \"FreeMono\") nil 'append)\n>     (set-fontset-font \"fontset-default\" 'unicode (font-spec :name \"Symbola\") nil 'append)\n> \n>     (insert \"ℕ𝓟⧺×≠≥≤±¬∨∧∃∀λ⟿⟹⊥⊤⊢\")\n> \n> Now run the lines one by one, and observe the \"(insert ...)\" line. Before running anything, it looks fine. It still looks fine after running the first line. Running the second line causes the 𝓟 to disappear; it is replaced by a square containing 01D4DF, and C-u C-x = says \"display: no font available\". Finally, running the third line fixes the 𝓟.\n> \n> I don't understand how this is possible. How can adding FreeMono cause Emacs to think that no font is available to display the 𝓟?\n\nIf this surprises you, your mental model of how Emacs looks for a\nsuitable font needs to be updated. ;-)\n\nI'm guessing you expect Emacs to test whether each font can actually\ndisplay a character.  But doing so would be prohibitively slow,\nbecause it requires opening each font, an expensive operation.  There\nare hundreds of fonts installed on any given system, and opening all\nof them one by one takes a long time and considerably slows redisplay.\n\nSo Emacs uses shortcuts: it examines the font meta-data, where a font\ndeclares which blocks of Unicode it supports, and picks up the first\nfont that claims support for the block of the character.  But if a\nfont claims support for a certain block, but has no glyphs for the\nspecific character we need to display, we lose.  Coupled with the fact\nthat you have no control on the order in which Emacs looks up fonts,\nthis is why adding a font to a fontset could actually make things\nworse.\n\nFor this reason, just lumping fonts indiscriminately into the fontset\nis not a good idea.  Instead, you should examine the character ranges\nwhere you have problems, and add fonts in a way that instructs Emacs\nto use each font only in the range of characters where you know it\nwill produce good results.  And since \"good results\" is largely in the\neyes of the beholder, this can only approximately be done in the\ndefault settings, the rest must be done by the end user.\n\n> Same here; I need to find a good example. I ran into issues that seemed to be connected with the fact that (set-face-attribute 'default nil :height 120) creates a new fontset, as reported by fontset-list:\n> \n>     (fontset-list)\n>     => (\"-unknown-Ubuntu Mono-normal-normal-normal-*-15-*-*-*-m-0-fontset-startup\" \"-*-fixed-medium-r-normal-*-16-*-*-*-*-*-fontset-standard\" \"-*-*-*-*-*-*-*-*-*-*-*-*-fontset-default\")\n> \n>     (set-face-attribute 'default nil :height 120)\n>     => nil\n> \n>     (fontset-list)\n>     => (\"-unknown-Ubuntu Mono-normal-normal-normal-*-16-*-*-*-m-0-fontset-auto1\" \"-unknown-Ubuntu Mono-normal-normal-normal-*-15-*-*-*-m-0-fontset-startup\" \"-*-fixed-medium-r-normal-*-16-*-*-*-*-*-fontset-standard\" \"-*-*-*-*-*-*-*-*-*-*-*-*-fontset-default\")\n\nIt just added the fonts for the larger height, but they are fonts from\nthe same families, so I see no issues here: if the original fontset\nsupported the characters you want, the updated one will, too.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_181.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apByT-0003XK-Mp\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 05:48:09 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:51643)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <thomasf@jossystem.se>) id 1apByQ-0003TU-AQ\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 05:48:07 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <thomasf@jossystem.se>) id 1apByP-0001b5-7c\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 05:48:06 -0400\nReceived: from mail-lf0-x236.google.com ([2a00:1450:4010:c07::236]:36300)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <thomasf@jossystem.se>) id 1apByO-0001aw-UI\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 05:48:05 -0400\nReceived: by mail-lf0-x236.google.com with SMTP id g184so123027976lfb.3\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 02:48:04 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=jossystem-se.20150623.gappssmtp.com; s=20150623;\n\th=mime-version:from:date:message-id:subject:to;\n\tbh=m2mTSgXCZDaZqqip1qv6haIOCkpfJAZH7SUtl8HuyX0=;\n\tb=QKR4qyABa39eq2kRTXaizH6blbGkD5Gaeqjh5imFFhSqiWWGRSVxXiLA/2EYgbx6+m\n\tjKlQtHnA3WgNZ44X1nRLIc5Opfg9p3bQF8pqMxID2sxEgq1wOm4vkj99ren7kVGEjdDH\n\tQJQQwApboiOteX2bCWdgFGJQpRprBIXhd7T9+MqGYXITweyAt8CatzQEcmrvwNYOE2qw\n\tnbpM8kqp4KD0SpGocL97Tui81yJ6qtOKiTI3Op6dH2YG7batKEGP2K3nnCRR9iE/TQ6z\n\tScTWo1HmMcONsI55QtZ/+lsqLFtNKO/juOyWbVL7Ff2Q3qpjFnyu5eamE340u+wtPu0l\n\tXsBg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:from:date:message-id:subject:to;\n\tbh=m2mTSgXCZDaZqqip1qv6haIOCkpfJAZH7SUtl8HuyX0=;\n\tb=Vfjgr4QSNhmpEadDItAcgeQkKZCMw8jqtQ16OldWOyHJ2Uq89DxAU2sWUnUJObQZeH\n\tnhNN4vvs2lS7EjpVOiHU249KKX2VL40Wc2Z6ztYVEJK7DzBY3X/UidHy8Sk2wqYqzJi2\n\tnsM960M1hXrKgHrxB1//nlEiHp1YSceXlXb4OU3V0wFruR5bhjB47Y08sXg8gOfeyYOq\n\t0C2SGSCFeFJl26p4eLksjFyTGHSvT7DxeU0PPmuMPby2am/m88Qn5EbqP6ToW/702ZoD\n\tn1fc5SUwidzU4Kx+XSz1hfOtJDkOf3DEON10G+F5bg79abyh1V9fXYICEKb0K0kPZOwr\n\tVBfg==\nX-Gm-Message-State: AD7BkJJvGMfqH7nRABP91aKcfGHk4R9jdwIFUlzdlVVMbBPlK60/X6bSeltdAeK5GiugeB7+23ZqR4PCbHbFzA==\nX-Received: by 10.112.125.9 with SMTP id mm9mr5224451lbb.113.1460281683556;\n\tSun, 10 Apr 2016 02:48:03 -0700 (PDT)\nMIME-Version: 1.0\nReceived: by 10.25.154.7 with HTTP; Sun, 10 Apr 2016 02:47:44 -0700 (PDT)\nFrom: =?UTF-8?Q?Thomas_Fr=C3=B6ssman?= <thomasf@jossystem.se>\nDate: Sun, 10 Apr 2016 11:47:44 +0200\nMessage-ID: <CANDWisD_2-vtxm_p5z4T049=STnBgmjX674kEyO7vxCLqvifJA@mail.gmail.com>\nSubject: about bug#232: 25.0.92; cua-mode inhibits deactivate-region-hook\nTo: dev@caliopen.local\nContent-Type: multipart/alternative; boundary=089e0112c02205532e05301e53db\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c07::236\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 09:48:07 -0000\nContent-Length: 2088\nLines: 47\n\n--089e0112c02205532e05301e53db\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\nFirst I'm not sure if you prefer me to post to the bug or the mailing list\nso please tell me if I should restrict myself to the bugs list instead..\n\nAnyway, I think that it's an actual bug with cua-mode or something because\nthe deactivate-mark-hook isn't fired when the region is removed, repeated\npressing of C-g doesn't fire it either.\nIt's also a consistent behavior, when cua-mode is active the\ndeactivate-mark hook is not fired\n\n(by using git blame) I noticed that there was a change in September last\nyear or something which modifies some bits about how cua-mode handles the\nmark.\n\nPlease tell me if I'm wrong or if there is some additional digging I should\ndo to further diagnose this issue..\n\n\n--=20\nThomas Fr=C3=B6ssman\nhttp://t.jossystem.se\n\n--089e0112c02205532e05301e53db\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n<div dir=3D\"ltr\">First I&#39;m not sure if you prefer me to post to the bug=\n or the mailing list so please tell me if I should restrict myself to the b=\nugs list instead..<div><br></div><div>Anyway, I think that it&#39;s an actu=\nal bug with cua-mode or something because the deactivate-mark-hook isn&#39;=\nt fired when the region is removed, repeated pressing of C-g doesn&#39;t fi=\nre it either.</div><div>It&#39;s also a consistent behavior, when cua-mode =\nis active the deactivate-mark hook is not fired</div><div><br></div><div>(b=\ny using git blame) I noticed that there was a change in September last year=\n or something which modifies some bits about how cua-mode handles the mark.=\n</div><div><br></div><div>Please tell me if I&#39;m wrong or if there is so=\nme additional digging I should do to further diagnose this issue..=C2=A0<br=\n></div><div><br></div><div><div><br></div>-- <br><div class=3D\"gmail_signat=\nure\">Thomas Fr=C3=B6ssman<div><div><a href=3D\"http://t.jossystem.se\" target=\n=3D\"_blank\">http://t.jossystem.se</a></div></div></div>\n</div></div>\n\n--089e0112c02205532e05301e53db--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_183.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apGnq-0002EM-88\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 10:57:30 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46876)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1apGnn-000292-EZ\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 10:57:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1apGnk-0004ci-8k\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 10:57:27 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:38927)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1apGnk-0004cd-5i; Sun, 10 Apr 2016 10:57:24 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3468\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1apGnj-0005Qf-GH; Sun, 10 Apr 2016 10:57:23 -0400\nDate: Sun, 10 Apr 2016 17:56:49 +0300\nMessage-Id: <83wpo5h5qm.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Thomas =?utf-8?Q?Fr=C3=B6ssman?= <thomasf@jossystem.se>\nCC: dev@caliopen.local\nIn-reply-to: <CANDWisD_2-vtxm_p5z4T049=STnBgmjX674kEyO7vxCLqvifJA@mail.gmail.com>\n\t(message from Thomas =?utf-8?Q?Fr=C3=B6ssman?= on Sun,\n\t10 Apr 2016 11:47:44 +0200)\nSubject: Re: about bug#232: 25.0.92; cua-mode inhibits deactivate-region-hook\nReply-to: Eli Zaretskii <eliz@gnu.org>\nReferences: <CANDWisD_2-vtxm_p5z4T049=STnBgmjX674kEyO7vxCLqvifJA@mail.gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 14:57:28 -0000\nContent-Length: 1104\nLines: 25\n\n> From: Thomas Frössman <thomasf@jossystem.se>\n> Date: Sun, 10 Apr 2016 11:47:44 +0200\n> \n> First I'm not sure if you prefer me to post to the bug or the mailing list so please tell me if I should restrict\n> myself to the bugs list instead..\n\nTo the bug list, using \"M-x report-emacs-bug RET\", please.\n\n> Anyway, I think that it's an actual bug with cua-mode or something because the deactivate-mark-hook isn't\n> fired when the region is removed, repeated pressing of C-g doesn't fire it either.\n> \n> It's also a consistent behavior, when cua-mode is active the deactivate-mark hook is not fired\n> \n> (by using git blame) I noticed that there was a change in September last year or something which modifies\n> some bits about how cua-mode handles the mark.\n> \n> Please tell me if I'm wrong or if there is some additional digging I should do to further diagnose this issue.. \n\nCould you perhaps include in your bug report the details about how it\nworked in previous versions of Emacs?  If it worked correctly in some\nolder version, could you please tell in which one, and what was the\nbehavior?\n\nThanks.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_185.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1apG16-0002M3-AQ\n\tfor mharc-dev@caliopen.local; Sun, 10 Apr 2016 10:07:08 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:35535)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <matthew@mattleach.net>) id 1apG12-0002IV-Ey\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 10:07:06 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <matthew@mattleach.net>) id 1apG10-0001db-7e\n\tfor dev@caliopen.local; Sun, 10 Apr 2016 10:07:04 -0400\nReceived: from mx0.mattleach.net ([176.58.118.143]:60096)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <matthew@mattleach.net>)\n\tid 1apG0w-0001d7-3e; Sun, 10 Apr 2016 10:06:58 -0400\nReceived: by mx0.mattleach.net (Postfix, from userid 99)\n\tid B2A846222E; Sun, 10 Apr 2016 15:06:57 +0100 (BST)\nReceived: from spline.mattleach.net\n\t(host86-170-66-130.range86-170.btcentralplus.com [86.170.66.130])\n\tby mx0.mattleach.net (Postfix) with ESMTPSA id ED51962229;\n\tSun, 10 Apr 2016 15:06:51 +0100 (BST)\nFrom: Matthew Leach <matthew@mattleach.net>\nTo: emacs-devel <emacs-devel@gnu.org>\nCc: Eli Zaretskii <eliz@gnu.org>, Stefan Monnier <monnier@IRO.UMontreal.CA>\nSubject: [PATCH v5] Add systemd socket launching support\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nDate: Sun, 10 Apr 2016 15:06:54 +0100\nMessage-ID: <871t6do8w1.fsf@mattleach.net>\nMIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=\"=-=-=\"\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 176.58.118.143\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 14:07:07 -0000\nContent-Length: 17192\nLines: 479\n\n--=-=-=\nContent-Type: text/plain\n\nHi all,\n\nHere is a new version of the systemd integration & socket-launching\npatches.  The main change in this series is providing an interface for\nother socket-launching applications to be integrated, namely\n`set_external_socket_descriptor' in process.c.\n\nFeedback & comments welcome!\n\nThanks,\n-- \nMatt\n\n--=-=-=\nContent-Type: text/x-diff\nContent-Disposition: inline;\n filename=0001-Allow-network-processes-to-be-created-with-a-pre-all.patch\n\n>From 2da9ce85d995487846a8fd32c8e4f4237aa3372b Mon Sep 17 00:00:00 2001\nFrom: Matthew Leach <matthew@mattleach.net>\nDate: Sat, 26 Mar 2016 19:37:10 +0000\nSubject: [PATCH 1/4] Allow network processes to be created with a\n pre-allocated fd.\n\n* src/process.c (connect_network_socket): Allow a pre-allocated socket\n  descriptor to be used if passed to Emacs, avoiding the call to\n  socket() and bind().\n  (Fmake_network_process): Allow users to pass :use-external-socket on\n  the parameter plist to use any sockets that have been passed to Emacs.\n  (wait_reading_process_output): Call socket() & bind() every time.\n  (syms_of_process): New symbol ':use-external-socket'.\n  (set_external_socket_descriptor): New.\n  (external_sock_fd): New.\n* src/lisp.h: (set_external_socket_descriptor): New declaration.\n* doc/lispref/processes.texi (Network Processes): Document new\n  `make-network-process' option ':use-external-socket'.\n---\n doc/lispref/processes.texi |  6 ++++++\n src/lisp.h                 |  1 +\n src/process.c              | 52 +++++++++++++++++++++++++++++++++++++++-------\n 3 files changed, 52 insertions(+), 7 deletions(-)\n\ndiff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi\nindex 8d3df55..02698db 100644\n--- a/doc/lispref/processes.texi\n+++ b/doc/lispref/processes.texi\n@@ -2367,6 +2367,12 @@ automatically for the given @var{host} and @var{service}.\n ignored.  @code{ipv4} and @code{ipv6} specify to use IPv4 and IPv6,\n respectively.\n \n+@item :use-external-socket @var{use-external-socket}\n+If @var{use-external-socket} is non-@code{nil} use any sockets passed\n+to Emacs on invocation instead of allocating one.  This is used by the\n+Emacs server code to allow on-demand socket activation.  If Emacs\n+wasn't passed a socket, this option is silently ignored.\n+\n @item :local @var{local-address}\n For a server process, @var{local-address} is the address to listen on.\n It overrides @var{family}, @var{host} and @var{service}, so you\ndiff --git a/src/lisp.h b/src/lisp.h\nindex 7c8b452..99cdbe7 100644\n--- a/src/lisp.h\n+++ b/src/lisp.h\n@@ -4180,6 +4180,7 @@ extern void delete_gpm_wait_descriptor (int);\n extern void init_process_emacs (void);\n extern void syms_of_process (void);\n extern void setup_process_coding_systems (Lisp_Object);\n+extern void set_external_socket_descriptor (int);\n \n /* Defined in callproc.c.  */\n #ifndef DOS_NT\ndiff --git a/src/process.c b/src/process.c\nindex 198e7de..6bfdc03 100644\n--- a/src/process.c\n+++ b/src/process.c\n@@ -267,6 +267,9 @@ static int max_process_desc;\n /* The largest descriptor currently in use for input; -1 if none.  */\n static int max_input_desc;\n \n+/* The descriptor  of any sockets passed to Emacs; -1 if none. */\n+static int external_sock_fd = -1;\n+\n /* Indexed by descriptor, gives the process (if any) for that descriptor.  */\n static Lisp_Object chan_process[FD_SETSIZE];\n static void wait_for_socket_fds (Lisp_Object, char const *);\n@@ -3075,7 +3078,8 @@ finish_after_tls_connection (Lisp_Object proc)\n #endif\n \n static void\n-connect_network_socket (Lisp_Object proc, Lisp_Object ip_addresses)\n+connect_network_socket (Lisp_Object proc, Lisp_Object ip_addresses,\n+                        Lisp_Object use_external_socket_p)\n {\n   ptrdiff_t count = SPECPDL_INDEX ();\n   ptrdiff_t count1;\n@@ -3089,6 +3093,16 @@ connect_network_socket (Lisp_Object proc, Lisp_Object ip_addresses)\n   struct Lisp_Process *p = XPROCESS (proc);\n   Lisp_Object contact = p->childp;\n   int optbits = 0;\n+  int socket_to_use = -1;\n+\n+  if (!NILP (use_external_socket_p))\n+    {\n+      socket_to_use = external_sock_fd;\n+\n+      /* Ensure we don't consume the external socket twice. */\n+      external_sock_fd = -1;\n+    }\n+\n \n   /* Do this in case we never enter the while-loop below.  */\n   count1 = SPECPDL_INDEX ();\n@@ -3109,7 +3123,11 @@ connect_network_socket (Lisp_Object proc, Lisp_Object ip_addresses)\n       sa = xmalloc (addrlen);\n       conv_lisp_to_sockaddr (family, ip_address, sa, addrlen);\n \n-      s = socket (family, p->socktype | SOCK_CLOEXEC, p->ai_protocol);\n+      if (socket_to_use != -1)\n+          s = socket_to_use;\n+      else\n+          s = socket (family, p->socktype | SOCK_CLOEXEC, p->ai_protocol);\n+\n       if (s < 0)\n \t{\n \t  xerrno = errno;\n@@ -3168,8 +3186,11 @@ connect_network_socket (Lisp_Object proc, Lisp_Object ip_addresses)\n \t\t  report_file_error (\"Cannot set reuse option on server socket\", Qnil);\n \t      }\n \n-\t  if (bind (s, sa, addrlen))\n-\t    report_file_error (\"Cannot bind server socket\", Qnil);\n+          /* If we are passed a socket descriptor, it should be\n+             already bound. */\n+\t  if (socket_to_use == -1)\n+\t    if (bind (s, sa, addrlen))\n+\t      report_file_error (\"Cannot bind server socket\", Qnil);\n \n #ifdef HAVE_GETSOCKNAME\n \t  if (p->port == 0)\n@@ -3534,6 +3555,11 @@ The following network options can be specified for this connection:\n                       (this is allowed by default for a server process).\n :bindtodevice NAME -- bind to interface NAME.  Using this may require\n                       special privileges on some systems.\n+:use-external-socket BOOL -- Use any pre-allocated sockets that have\n+                             been passed to Emacs.  If Emacs wasn't\n+                             passed a socket, this option is silently\n+                             ignored.\n+\n \n Consult the relevant system programmer's manual pages for more\n information on using these options.\n@@ -3578,7 +3604,7 @@ usage: (make-network-process &rest ARGS)  */)\n   EMACS_INT port = 0;\n   Lisp_Object tem;\n   Lisp_Object name, buffer, host, service, address;\n-  Lisp_Object filter, sentinel;\n+  Lisp_Object filter, sentinel, use_external_socket_p;\n   Lisp_Object ip_addresses = Qnil;\n   int socktype;\n   int family = -1;\n@@ -3618,6 +3644,7 @@ usage: (make-network-process &rest ARGS)  */)\n   buffer = Fplist_get (contact, QCbuffer);\n   filter = Fplist_get (contact, QCfilter);\n   sentinel = Fplist_get (contact, QCsentinel);\n+  use_external_socket_p = Fplist_get (contact, QCuse_external_socket);\n \n   CHECK_STRING (name);\n \n@@ -3914,7 +3941,7 @@ usage: (make-network-process &rest ARGS)  */)\n     }\n #endif\n \n-  connect_network_socket (proc, ip_addresses);\n+  connect_network_socket (proc, ip_addresses, use_external_socket_p);\n   return proc;\n }\n \n@@ -4848,7 +4875,7 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd,\n \t\t  {\n \t\t    Lisp_Object ip_addresses = check_for_dns (aproc);\n \t\t    if (!NILP (ip_addresses) && !EQ (ip_addresses, Qt))\n-\t\t      connect_network_socket (aproc, ip_addresses);\n+\t\t      connect_network_socket (aproc, ip_addresses, Qnil);\n \t\t    else\n \t\t      retry_for_async = true;\n \t\t  }\n@@ -7709,6 +7736,16 @@ catch_child_signal (void)\n }\n #endif\t/* subprocesses */\n \n+/* Set the external socket descriptor for Emacs to use when\n+   `make-network-process' is called with a non-nil\n+   `:use-external-socket' option.  The fd should have been checked to\n+   ensure it is a valid socket and is already bound.  */\n+void\n+set_external_socket_descriptor(int fd)\n+{\n+    external_sock_fd = fd;\n+}\n+\n \f\n /* This is not called \"init_process\" because that is the name of a\n    Mach system call, so it would cause problems on Darwin systems.  */\n@@ -7837,6 +7874,7 @@ syms_of_process (void)\n   DEFSYM (QCserver, \":server\");\n   DEFSYM (QCnowait, \":nowait\");\n   DEFSYM (QCsentinel, \":sentinel\");\n+  DEFSYM (QCuse_external_socket, \":use-external-socket\");\n   DEFSYM (QCtls_parameters, \":tls-parameters\");\n   DEFSYM (Qnsm_verify_connection, \"nsm-verify-connection\");\n   DEFSYM (QClog, \":log\");\n-- \n2.8.0\n\n\n--=-=-=\nContent-Type: text/x-diff\nContent-Disposition: inline;\n filename=0002-Check-for-libsystemd-when-building-Emacs.patch\n\n>From b435584609d623939ced0854479d27e1ed075438 Mon Sep 17 00:00:00 2001\nFrom: Matthew Leach <matthew@mattleach.net>\nDate: Sat, 26 Mar 2016 16:41:17 +0000\nSubject: [PATCH 2/4] Check for libsystemd when building Emacs.\n\n* configure.ac: Add new default-on option systemd and check for\n  libsystemd at configure time.\n* src/Makefile.in: Add libsystemd library and C flags to the Emacs\n  compilation options.\n---\n configure.ac    | 13 +++++++++++++\n src/Makefile.in |  6 +++++-\n 2 files changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/configure.ac b/configure.ac\nindex f3846f4..f53c670 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -330,6 +330,7 @@ OPTION_DEFAULT_ON([tiff],[don't compile with TIFF image support])\n OPTION_DEFAULT_ON([gif],[don't compile with GIF image support])\n OPTION_DEFAULT_ON([png],[don't compile with PNG image support])\n OPTION_DEFAULT_ON([rsvg],[don't compile with SVG image support])\n+OPTION_DEFAULT_ON([libsystemd],[don't compile with libsystemd support])\n OPTION_DEFAULT_OFF([cairo],[compile with Cairo drawing (experimental)])\n OPTION_DEFAULT_ON([xml2],[don't compile with XML parsing support])\n OPTION_DEFAULT_ON([imagemagick],[don't compile with ImageMagick image support])\n@@ -2716,6 +2717,18 @@ fi\n AC_SUBST(LIBGNUTLS_LIBS)\n AC_SUBST(LIBGNUTLS_CFLAGS)\n \n+HAVE_LIBSYSTEMD=no\n+if test \"${with_libsystemd}\" = \"yes\" ; then\n+  EMACS_CHECK_MODULES([LIBSYSTEMD], [libsystemd >= 226],\n+    [HAVE_LIBSYSTEMD=yes], [HAVE_LIBSYSTEMD=no])\n+  if test \"${HAVE_LIBSYSTEMD}\" = \"yes\"; then\n+    AC_DEFINE(HAVE_LIBSYSTEMD, 1, [Define if using libsystemd.])\n+  fi\n+fi\n+\n+AC_SUBST(LIBSYSTEMD_LIBS)\n+AC_SUBST(LIBSYSTEMD_CFLAGS)\n+\n NOTIFY_OBJ=\n NOTIFY_SUMMARY=no\n \ndiff --git a/src/Makefile.in b/src/Makefile.in\nindex c290a60..fc9360a 100644\n--- a/src/Makefile.in\n+++ b/src/Makefile.in\n@@ -307,6 +307,9 @@ LIBSELINUX_LIBS = @LIBSELINUX_LIBS@\n LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@\n LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@\n \n+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@\n+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@\n+\n INTERVALS_H = dispextern.h intervals.h composite.h\n \n GETLOADAVG_LIBS = @GETLOADAVG_LIBS@\n@@ -372,6 +375,7 @@ ALL_CFLAGS=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \\\n   $(WEBKIT_CFLAGS) \\\n   $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \\\n   $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \\\n+  $(LIBSYSTEMD_CFLAGS) \\\n   $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \\\n   $(WARN_CFLAGS) $(WERROR_CFLAGS) $(CFLAGS)\n ALL_OBJC_CFLAGS=$(ALL_CFLAGS) $(GNU_OBJC_CFLAGS)\n@@ -489,7 +493,7 @@ LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(LIBX_BASE) $(LIBIMAGE) \\\n    $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \\\n    $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \\\n    $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(GETADDRINFO_A_LIBS) \\\n-   $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES)\n+   $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES) $(LIBSYSTEMD_LIBS)\n \n $(leimdir)/leim-list.el: bootstrap-emacs$(EXEEXT)\n \t$(MAKE) -C ../leim leim-list.el EMACS=\"$(bootstrap_exe)\"\n-- \n2.8.0\n\n\n--=-=-=\nContent-Type: text/x-diff\nContent-Disposition: inline;\n filename=0003-Read-the-number-of-sockets-passed-by-systemd.patch\n\n>From da80bd7ebd0e9ba1b948be7ba0a0f9cafe6dd4c3 Mon Sep 17 00:00:00 2001\nFrom: Matthew Leach <matthew@mattleach.net>\nDate: Sat, 26 Mar 2016 18:50:14 +0000\nSubject: [PATCH 3/4] Read the number of sockets passed by systemd.\n\n* src/emacs.c (main): Call sd_listen_fds to read the number of sockets\n  passed and call `set_external_socket_descriptor' to set the external\n  socket.\n---\n src/emacs.c | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)\n\ndiff --git a/src/emacs.c b/src/emacs.c\nindex 95d1905..6a77293 100644\n--- a/src/emacs.c\n+++ b/src/emacs.c\n@@ -56,6 +56,11 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */\n #include <binary-io.h>\n #endif\n \n+#ifdef HAVE_LIBSYSTEMD\n+#include <systemd/sd-daemon.h>\n+#include <sys/socket.h>\n+#endif /* HAVE_LIBSYSTEMD */\n+\n #ifdef HAVE_WINDOW_SYSTEM\n #include TERM_HEADER\n #endif /* HAVE_WINDOW_SYSTEM */\n@@ -676,6 +681,9 @@ main (int argc, char **argv)\n   char dname_arg2[80];\n #endif\n   char *ch_to_dir = 0;\n+#ifdef HAVE_LIBSYSTEMD\n+  int systemd_socket;\n+#endif\n \n   /* If we use --chdir, this records the original directory.  */\n   char *original_pwd = 0;\n@@ -997,6 +1005,20 @@ main (int argc, char **argv)\n \t  exit (1);\n \t}\n \n+#ifdef HAVE_LIBSYSTEMD\n+      /* Read the number of sockets passed through by systemd. */\n+      systemd_socket = sd_listen_fds(1);\n+\n+      if (systemd_socket > 1)\n+        fprintf (stderr, \"\\nWarning: systemd has passed more than one socket to the Emacs process.\\n\\\n+Try adding 'Accept=false' in the Emacs socket unit file.\\n\");\n+\n+      else if (systemd_socket == 1 &&\n+               sd_is_socket (SD_LISTEN_FDS_START,\n+                             AF_UNSPEC, SOCK_STREAM, 1) >= 0)\n+        set_external_socket_descriptor (SD_LISTEN_FDS_START);\n+#endif /* HAVE_LIBSYSTEMD */\n+\n #ifndef DAEMON_MUST_EXEC\n #ifdef USE_GTK\n       fprintf (stderr, \"\\nWarning: due to a long standing Gtk+ bug\\nhttp://bugzilla.gnome.org/show_bug.cgi?id=85715\\n\\\n-- \n2.8.0\n\n\n--=-=-=\nContent-Type: text/x-diff\nContent-Disposition: inline;\n filename=0004-When-Emacs-is-passed-a-socket-descriptor-make-the-se.patch\n\n>From 56fc0aef450e199bc93ea75b15dff778713de81f Mon Sep 17 00:00:00 2001\nFrom: Matthew Leach <matthew@mattleach.net>\nDate: Sat, 26 Mar 2016 20:43:26 +0000\nSubject: [PATCH 4/4] When Emacs is passed a socket descriptor, make the server\n listen on it.\n\n* lisp/server.el (server-start): Set :use-external-socket to `t' when\n  calling `make-network-process'.\n* etc/NEWS: Document new socket-passing functionality and the configure\n  option to disable systemd interaction.\n* doc/emacs/misc.texi (Emacs Server): Document systemd socket passing\n  functionality and provide systemd unit examples.\n---\n doc/emacs/misc.texi | 38 ++++++++++++++++++++++++++++++++++++++\n etc/NEWS            |  7 +++++++\n lisp/server.el      |  1 +\n 3 files changed, 46 insertions(+)\n\ndiff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi\nindex b5a2150..775cda9 100644\n--- a/doc/emacs/misc.texi\n+++ b/doc/emacs/misc.texi\n@@ -1580,6 +1580,44 @@ option.  @xref{Initial Options}.  When Emacs is started this way, it\n calls @code{server-start} after initialization, and returns control to\n the calling terminal instead of opening an initial frame; it then\n waits in the background, listening for edit requests.\n+\n+@cindex socket activation, systemd, Emacs\n+@item\n+An external process can invoke the Emacs server when a connection\n+event occurs upon a specified socket and pass the socket to the new\n+Emacs server process.  An instance of this is @command{systemd}'s\n+socket functionaly: the @command{systemd} service creates a socket and\n+listens for connections on it; when @command{emacsclient} connects to\n+it for the first time, @command{systemd} can launch the Emacs server\n+and hand over the socket to it for servicing @command{emacsclient}\n+connections.  A setup to use this functionality could be:\n+\n+@file{~/.config/systemd/user/emacs.service}:\n+@example\n+[Unit]\n+Description=Emacs\n+\n+[Service]\n+Type=forking\n+ExecStart=/path/to/emacs --daemon\n+ExecStop=/path/to/emacsclient --eval \"(kill-emacs)\"\n+Restart=always\n+\n+[Install]\n+WantedBy=default.target\n+@end example\n+\n+@file{~/.config/systemd/user/emacs.socket}:\n+@example\n+[Socket]\n+ListenStream=/path/to/.emacs.socket\n+\n+[Install]\n+WantedBy=sockets.target\n+@end example\n+\n+The @code{ListenStream} path will be the path that Emacs listens for\n+connections from @command{emacsclient}; this is a file of your choice.\n @end itemize\n \n @cindex @env{TEXEDIT} environment variable\ndiff --git a/etc/NEWS b/etc/NEWS\nindex 66777e9..f395d45 100644\n--- a/etc/NEWS\n+++ b/etc/NEWS\n@@ -26,6 +26,13 @@ otherwise leave it unmarked.\n * Installation Changes in Emacs 25.2\n \n +++\n+** Emacs server now has socket-launching support.  This allows socket\n+based activation, where an external process can invoke the Emacs\n+server process upon a socket connection event and hand over the socket\n+to Emacs.  Emacs will use this socket for servicing emacsclient\n+commands.  systemd can make use of this new functionally but can be\n+disabled with the configure option '--disable-libsystemd'.\n+\n ** New configure option '--disable-build-details' attempts to build an\n Emacs that is more likely to be reproducible; that is, if you build\n and install Emacs twice, the second Emacs is a copy of the first.\ndiff --git a/lisp/server.el b/lisp/server.el\nindex 5243820..2a9729e 100644\n--- a/lisp/server.el\n+++ b/lisp/server.el\n@@ -655,6 +655,7 @@ server or call `\\\\[server-force-delete]' to forcibly disconnect it.\"))\n \t\t       :noquery t\n \t\t       :sentinel #'server-sentinel\n \t\t       :filter #'server-process-filter\n+\t\t       :use-external-socket t\n \t\t       ;; We must receive file names without being decoded.\n \t\t       ;; Those are decoded by server-process-filter according\n \t\t       ;; to file-name-coding-system.  Also don't get\n-- \n2.8.0\n\n\n--=-=-=--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_187.argentina:2,",
    "content": "Return-Path: <contact@globalpromo.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 115D9209A3B9F\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 09:32:44 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 004153317708; Sun, 10 Apr 2016 09:32:44 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid E2E8A3317704; Sun, 10 Apr 2016 09:32:43 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.1 required=5.0 tests=AWL,BAYES_50,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,FROM_EXCESS_BASE64,HTML_IMAGE_RATIO_02,HTML_MESSAGE,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,URIBL_RED\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  0.0 URIBL_RED Contains an URL listed in the URIBL redlist\n\t*      [URIs: globalpromo.fr]\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60%\n\t*      [score: 0.5000]\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  1.0 FROM_EXCESS_BASE64 From: base64 encoded unnecessarily\n\t*  0.6 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from smtp4.globalpromo.fr (smtp4.globalpromo.fr [163.172.248.227])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id B156033181F7\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 09:32:40 +0200 (CEST)\nX-QHPSI: clean\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed;\n\td=globalpromo.fr; s=default; h=Date:Message-ID:To:From:Reply-To:\n\tSubject:X-Mailer:Precedence:List-Unsubscribe:MIME-Version:\n\tContent-Type:Content-Transfer-Encoding; bh=Vzi1FqLha8Qb+ef9Ww0fH\n\tRPoLE0=; b=oaVniM1VB61kf2wLVOh81ssCe+4t1FOhlbswK4KL8NepnqcUnJJqb\n\t8sWhxA1QgKCklkVVL8oct6YNX7ID0vpHfUBYIuRGl61OpcYCvbaY8YtV78MDTfi0\n\tLmkgVz+ilrahziDv0z6Lzv94byJyIROgysDE3LRGNZfBUKucP4JzZg=\nDomainKey-Signature: a=rsa-sha1; q=dns; c=simple;\n  s=default; d=globalpromo.fr;\n  b=RD/xZaJ8YiNtdc2kV0ILxgJeiCm2faCJPDdNt4ADyuPc8lwd7yHfPhHwj35kBcAVu3JgnE4xtiY5p1INMZjj5eccQcYHAwaAPjEVBtl5eYRBxDsFwPXxdGUJgJwSHaegvHXz+FfkvgQe/oULCkxKxiyCJ5MisNFn2iPC70NbRD0=;\nReceived: (qmail 17400 invoked by uid 0); Sun, 10 Apr 2016 09:32:39 +0200\nDate: Sun, 10 Apr 2016 09:32:39 +0200\nMessage-ID: <20160410073239.17297.qmail@globalpromo.frglobalpromo.fr>\nTo: dev@caliopen.local\nFrom: =?utf-8?B?TGVhZGVyIFBhcmZ1bSBwYXIgR2xvYmFscHJvbW8=?= <contact@globalpromo.fr>\nReply-To: contact@globalpromo.fr <contact@globalpromo.fr>\nSubject: =?utf-8?B?TGVzIHBhcmZ1bXMgZHUgcHJpbnRlbXBzIHN1ciBMZWFkZXIgUGFyZnVtICE=?=\nX-Abuse: abuse@globalpromo.com\nX-Mailer: SL v1.55.3 \nX-Campaign: a29d1598024f9e87beab4b98411d48ce \nPrecedence: bulk\nList-Unsubscribe: <http://globalpromo.fr/Oed7fgsY_Qt4ZqGp2tH4xHE_gMwXmQQ3BL188hH_-x4Q6_CQ0AWbTGjTz79sZO6ATqP2vOiIWbu_dKq8qenzOyz0gM4WoAhvmuwMbSOwIHHcI2XpmNwhQ-eoxzRtjx6JOY6Qbi2RqTWVX1Hq5UhV7sOqMUoQC4AcywhmmzPJ2W0=>\nMIME-Version: 1.0\nContent-Type: multipart/alternative; boundary=\"QsvfEYh+I/wrllhKImFEKRAZIF1Unz6LXX1Z/R3RK1e8sfI8SsyRCXzKVA5FOpds\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?utf-8?B?TGVzIHBhcmZ1bXMgZHUgcHJpbnRlbXBzIHN1ciBMZWFkZXIgUGFyZnVtICE=?=\nContent-Length: 16835\nLines: 159\n\n--QsvfEYh+I/wrllhKImFEKRAZIF1Unz6LXX1Z/R3RK1e8sfI8SsyRCXzKVA5FOpds\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\nLA LIVRAISON EST GRATUITE AVEC NOTRE CODE PROMO !\n http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VsmaWJM1dtTLOplvaWC5OG9XghhUWRQBDQZwP9zLjPybaURfev7E8r9VKrvKJ-XhhmURpEBQB4HgjUlv1iyxxp8LtdW237zzwXXKZka9HRKEM9Op52sJiz0fywq7nAQ0022cumsU3XPNZy02bS_0rQL0jdAW13Ymz7KgMr4teLrZyLo8X6kkSPErAwVs0Ht/xz-JqSRy7Z7946F9NQmpk_ixeVnHs7w3OTh-v5NDciYpI= \n\n \t\t [1]\n \t\t [2]\n \t\t [3]\n \t\t [4]\n\n \t\t [1]\n\n \t\t [2]\n\n \t\t [5]\n \t\t [6]\n \t\t [7]\n \t\t [8]\n\n \t\t [3]\n\n \t\t [9]\n \t\t [10]\n \t\t [11]\n \t\t [12]\n\n \t\t [13]\n \t\t [13]\n \t\t [13]\n\n \t\t [1]\n\nCette newsletter vous a été envoyée par le programme La belle affaire\n\n Pour cesser de recevoir nos informations sur l'adresse dev@caliopen.local, désabonnez-vous [14]\n\n Politique de confidentialité | © La belle affaire 2016 \n\nLinks:\n------\n[1] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6Vjw1cLJaTHXii__5_CQxdx4MfS0SpAUyn0yYxs5lJjtRSQ6pek4kRBif3zfD7dB5_IB0BYolbit0s5d-NcI9hKTco9rm1FMfmF8al9aiM8tBJ4_FET7ObPZyamypQ81n62hB5vWJdJBPl2XsFjmJvaA==\n[2] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VooUoDeeOBH4p3or7LsDkY4Edp6hsoBn30jntWxCL1bZvJ1dH_EbtZN_9_F2hqW0QEsRAJdq8CCB4n1WlNf9ABJYiKnCpvVwUZWDRTXSbKJORV2vOaz07xwlWjzA6LhfwPoZiP_0Y2tN00LoVBP4yPA==\n[3] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VLCDR7f2uFgzhph1UvcbNRpy5Xk5xIb4nofhcogHSGTLIUHgvRRteotqKfA-ByCz3sAie4kWZTy8FDWyE8Gc3a_KSdWWTES7Y5IeLBwOuH4qNeD3-njEVhreIn26NKWkALdGpdbfhozxH5pQ_snPkFQ==\n[4] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VC_y56V3ZTxvc2krNZTEo9GyqsYDcowz4fDZMZkidzMV61PorjcBOtcSdAig02rUOL8Xnz6kocxafmUkLPrAxN2o7FkVAVqln_5vbUMmcvENbw_wlq0jz_FZy2270MHi-avMBE7Pt_-xQoP6tf_vSWQ==\n[5] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VT4i1V6ffKl16b8PpoJgjceyE3qune7n3hyzJCmAIzSapSB7HmuLeP5Bs3TeRc573Eh6ANuGNy2oO8Fh4pRTJ18_j6pNW8uEN21FEr8pDFOzMojHLKoUfZLVjd_YHvoTAcdSbEqGRR4COrxtTdZQOkg==\n[6] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VnjPtirRTaE3fVz-Xba_7rxYNTWTGhpPlZ3hZ2LE0jmo4Fv0sV9ewtyCsKlfGkC9gjW5ZUzoLnRswNmDv-Ai1-hN-f5DmHMKmmZWui-O5vG2E-IqlboIEfDzviHBjDmbh9Y513i-KAkUEZ-BGmPbRMQ==\n[7] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VUZ8oqAU3x6eRyLBMce_b5TpGPQiEccc-8zKYHRcSlwQqfnmOl1CQZNAy4YCasZ9yqMj-Ka0ui0lg5om2ekoOdpnEue_fHtooOPY4GJR0W6kisqIim44RygiYmE31Hfq27gE7NxcuzQC7X5V3Ns_YRg==\n[8] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VVb7WEwV9bLV3AlOVlZkjYoQEQJfi5_gwo0eQHkSlI70UmeOOUB_SLnprQtLpxiej08kmxVyk5sh7B2MLulGqqmj3rfKSyQ0OaYVzR9E1mhpecWD7waDWpqT_8hbX5EUg7wWecn02tI5gIo7FMKEfwA==\n[9] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VUdQM5dg-SkzqDZH3kBkiZJkbnu6OjRmRnSSsyly3Dn728B0DIpgCJxtrreR0tS35bgbVAH9Sxrr28vh8VA0Eba2gXNvIlbC9zN6J28C1nVoC0mJVfT_4JY-u6N6y5TAV96mMdrAPPSmrDdM3OaCnHA==\n[10] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VZ8thMDVYEw-kqAfoah1VEMtyCFf0c4cAbV2YEsA2YR2BMN_O_7MnjFIk11eX1XoCM8no6ExTMSnqXiOm7_wNLGZhqvqgspyqmFTtva2zQ9bWAoJaaqa4jSaEuEbcc0jp5KUl2hCPOkrPQyj6GxPrSg==\n[11] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VbCBT0_l9FCKqpplHpknweFt364p3-MtwOWN-meVgc0ObOvLkyEDzeEUQGVQBtiCnTtVbS50NLQRmXTF1DnNkfptd9koTdpkaUhnG6MN6E04BHfrlM__KGSTM1NluVfcS9-_UMwYfA_CuTBn54uoLwA==\n[12] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VOQMX2ARHeMjoDjxmcHRl-b8vpUeOH5CWEMZpNAFX0c_pyYBvwIuU6w-5KvrlOrq7vbTFFAI34cKkXyHUSQRB13X0Fp046nkAHX-XmR36mj8Cc6VzelbSbX6rcUDh0tXqOPPwtnhrge1Ph4E-rnMoNw==\n[13] http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VSQk37c77CsVhhCj5sL-a5HuNLkEiU0tKHlrfqJ7iL76jeHN5qqhtytfE3uNwLe5XKC_ewDUjbIfJPQh76o3p3O-kbU5GSFDBKLmuIcvvK0WHyzzpuAUJT1p4-387U8vBAF0DdJAWyG1n31QWRcOZZg==\n[14] http://globalpromo.fr/http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6V1UfDvuTTdgGTPaFl6OWADidkITuyvvj1CKv7Se3nE-b6FTvrSURVTzaXGx3w0QTYseBHicU6c9lAw6EJ60AH7w==\n\n\n--QsvfEYh+I/wrllhKImFEKRAZIF1Unz6LXX1Z/R3RK1e8sfI8SsyRCXzKVA5FOpds\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n<!doctype html>\n<html>\n<head>\n\t<title>Leader Parfum</title>\n\t<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n</head>\n<body><link rel=\"stylesheet\" href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq919Gc99ET_JdY_odEq9PeGxmWI1b6dVddSDc7iufkG8utCJXttdc98VpNap3DdmvF4UvGPbpT_ozxuGkCvIueI\"/>\n<div style=\"text-align: center;\"><strong>La livraison est gratuite avec notre code promo !</strong><br>\n<div align=\"center\"><font color=\"#000000\" style=\"font-size:11px;font-family:Verdana,Arial,Helvetica,sans-serif;\">Pour visualiser correctement ce message, accédez à la <a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VBhZfETY-c2I6x2A5acSbcfQ32KFOk51hhQgEtZTlIIOfonUK5VD6AErfkWvRxHZ5Xrmx97hcw5LEn2mPs4wovMHFwR72feP6M56Hxq5xJPz4kKzOSSnQVfDCGLM2LmRE1k-1W7--zfbNpUAoCBPJDySzTzHoElDz9LvtxL0QiHjO_m_CrwDHrmBslS2WNt/iy14aSY7mTOBtVIdlfBcAF4wdLQX72ELjKqFr4Ba7r4ZQ=\">version en ligne</a></font></div></div>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"Table_01\" width=\"850\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VTxwpZ04-WG2c_CVS9Mtz0N705Q008JGqlK-JW_zf_Vqy_5I_hvFRHH2Jot0IFC5If48c5vhZIxn43jC4JXxTe9lmXTaYIK8PAPeyVDMBFP5aM-8fKQW52-I64yMlmxVJbw5v1wAzFmkyRvmMT6vplw==\" target=\"_blank\"><img alt=\"Leader Parfum\" border=\"0\" height=\"106\" src=\"http://globalpromo.fr/img/2112/images/751073995706c.png\" style=\"display:block\" width=\"320\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6V2FEiAarEcsAscnHY1MjHZgepkwY3FR-37gBADckpyjQnF3O7Y6Xbz7PlFFmsLgnAfwY6ttIzKBhu5C-pydSxcji5gJzIUkOe_o62rFV7YQyKJa5SCB-Gew71WP56hwP2sNtTI0XegeC6EOEaBbvPkw==\" target=\"_blank\"><img alt=\"Parfum Femme\" border=\"0\" height=\"106\" src=\"http://globalpromo.fr/img/2112/images/5188531665706.png\" style=\"display:block\" width=\"155\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VQqEGxVPWK2gIBBhwsvSHDoXGYFacb_sRnFrwiJ6bhjW-d5yUKpawqZhlRFXNOtfJ9v8YSpuXbmyfCsrBfbgBb71Zv4g7KS1oLXpbbOVNTmhahwlnz5lU85QnobuazILitcFW6oeTDomaFh7eCHyXnQ==\" target=\"_blank\"><img alt=\"Parfum Homme\" border=\"0\" height=\"106\" src=\"http://globalpromo.fr/img/2112/images/8561171155706.png\" style=\"display:block\" width=\"156\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VGggsLzjL5qcxvgVuJ6_-GNg5XHevNkfjkhgXURE-KbDKM-61nd-i-Zum9TD2h4GCdrRFAJA871vqbudSPu1GeDDSeN7zywO3lH6tEJddxiXtHM8iqI3jfGEXB28mL76ehPupsyMHXdKmgNUmhnVTUg==\" target=\"_blank\"><img alt=\"Parfum Enfant\" border=\"0\" height=\"106\" src=\"http://globalpromo.fr/img/2112/images/1304215153570.png\" style=\"display:block\" width=\"219\"></a></td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"Table_02\" width=\"850\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VWTR98w5Z7kiub-qpNzKn0O_VTYaoIEqS0eBgLgKbo3l9_v3VA9LKIszf90yz53B6OiMQ32egw877vk1BoVMooZI_8ZfF8MWPHoH1SQGslodBNYo2zSf53DiMfVPmoI0JtgtAkUaalOLRaS8z21c6XQ==\" target=\"_blank\"><img alt=\"Un air de printemps souffle sur Leader Parfum ! Dénichez la fragrance parfaite à travers notre sélection de parfums frais et fleuris au charme unique. Plus de 7000 parfums de grandes marques et jusqu'à -70% de réduction ... En bonus, la livraison vous est offerte avec le code LG2016\" border=\"0\" height=\"531\" src=\"http://globalpromo.fr/img/2112/images/1454942690570.png\" style=\"display:block\" width=\"850\"></a></td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"Table_03\" width=\"850\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VcqOPyNNIPkhDe2S5hC3BV8PM6670X4Fq8FGnK9kIO9MiPJfM0Jim_vqQCi0fIkMd7OGQm3NGboMKx6Xv4s6NUYUvls3bu3hpIpeTt94To93tmmOyZWlf5aZ8CipqJESTvJTULxSZHwKIE6k4lBXydQ==\" target=\"_blank\"><img alt=\"Les parfums femme du moment\" border=\"0\" height=\"107\" src=\"http://globalpromo.fr/img/2112/images/1084840379570.png\" style=\"display:block\" width=\"850\"></a></td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"Table_04\" width=\"850\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VdZY6UUU1sRWK6Brx_qnmSo_BUBpSLExvz1Q7Viyr2zmMU7Bn7xl1_Edeq5fNYN4hGkXSvX8ipa3t0kOCdEg256rxoR2dbWLAN2REMeArJ2QHtmygPPxiZATadLOUpmzfOCioOeHYJZCwJr4EiOWqVQ==\" target=\"_blank\"><img alt=\"FLOWER par Kenzo - Eau de parfum 100ml - 64,00 Euros au lieu de 107,40 Euros\" border=\"0\" height=\"359\" src=\"http://globalpromo.fr/img/2112/images/8337513305706.jpg\" style=\"display:block\" width=\"258\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VeQWOfyrqtX9aumWO43wycb9Vzm3UQHSTVxf6Lv1jrMGgKpgHP0pJz1DS8xfTSd0qcMQDdWnzqScLDyIv7e0N5eqB7KVM9Rkp2QFMImI4K6zKO1C4AXYZxo1iK4HnnVauJkXX0nvBs7D5tJ8jikfsRA==\" target=\"_blank\"><img alt=\"LA PETITE ROBE NOIRE - MA PREMIERE ROBE par Guerlain - Eau de parfum 100ml - 76,00 Euros au lieu de 131,00 Euros\" border=\"0\" height=\"359\" src=\"http://globalpromo.fr/img/2112/images/1537572545706.jpg\" style=\"display:block\" width=\"167\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VD3gAczG30LvHYjJNCu4hM-qNN59OBXZVFdNguIcPVfwhMdF4QzO8iiA2IlMeYBuJ_PP-VRheMEfome58pqSLKxeoA3WwT4pv37iVavhsaabKEdjF45-tlmHapz08JLTSD8NvPJhZOf-OecsoZslmuQ==\" target=\"_blank\"><img alt=\"VALENTINA PINK par Valentino - Eau de parfum 80ml - 92,00 Euros au lieu de 129,00 Euros\" border=\"0\" height=\"359\" src=\"http://globalpromo.fr/img/2112/images/2090122656570.jpg\" style=\"display:block\" width=\"168\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VNFhejFJGYj6VhdMZb_kTFWaW73Cg5qqMFJPGRTfE0HfRIdA7kogKvZSsPm6sFsuaL825qCUJTL2icUzS3ulOzL3ZEajlAqLMp28NGntK_QhkrNImHjWgDE2cabcV0x2BWYueWvuOxyoJtABur61QCA==\" target=\"_blank\"><img alt=\"ETERNITY PURPLE ORCHID par Calvin Klein - Eau de parfum 100ml - 38,50 Euros au lieu de 59,00 Euros\" border=\"0\" height=\"359\" src=\"http://globalpromo.fr/img/2112/images/8343351925706.jpg\" style=\"display:block\" width=\"257\"></a></td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"Table_05\" width=\"850\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6Vm1BGl-AMU2BsFGLWafBtKDIWX2yZ3HFkeEnm356mqda-iJQkMREMoTax05--6cUY9DRKtZXd4qKw9t--8Hw8mtxfoaEGiOZyzztqxGVhgAfeSiLXdP3hm54WecbFhr_NOEYngGyRlamQp89LvMQnxA==\" target=\"_blank\"><img alt=\"Les parfums homme du moment\" border=\"0\" height=\"111\" src=\"http://globalpromo.fr/img/2112/images/1021530094570.jpg\" style=\"display:block\" width=\"850\"></a></td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"Table_06\" width=\"850\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VjrtEhAqA4ZhuFT0105ibdbXkxYjqO3zFZzjdnjL26ifvyuJ2CTa987Xwxhv8oAFE6WzuZFWpxuWtwg2pgP5gPLSPeMcKR1PGL1Js-KaM4pcFgU2jEfcohB0HEcxhdZH8ua4IfbgFzFioa2ZUeWPulw==\" target=\"_blank\"><img alt=\"LA NUIT DE L'HOMME - L'INTENSE par Yves Saint Laurent - Eau de parfum 100ml - 78,00 Euros au lieu de 99,90 Euros\" border=\"0\" height=\"352\" src=\"http://globalpromo.fr/img/2112/images/7497137965706.jpg\" style=\"display:block\" width=\"258\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VIdcyMpxNVpYVPSEAlATu1-hllApnmR16PnDvfOVYzc5Pwd6syY5Pt6RSeMi7Fy0Qr5jgAxfHNNqNpyE0oAgs213GnECYJiYSNZsZXbey-wPp-Tb84UtbDzgwNnE4uAnNinjp3PqfUVfKLWHV9U9COw==\" target=\"_blank\"><img alt=\"AQUA FROST par Azzaro - Eau de toilette 75ml - 39,50 Euros au lieu de 52,00 Euros\" border=\"0\" height=\"352\" src=\"http://globalpromo.fr/img/2112/images/8428035905706.jpg\" style=\"display:block\" width=\"167\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VLoWkujNQ-ygy_NlueB7yJUtNVuwYQmn-To68fXpukSRDoxiUc8eXuJ-4NJTa5yC5pJFz8cH_PUJxmDWSuk0U1t5gt0VmK4pQHJ9KQykG9HYrgHxy10_0xXTBRW3vr-Bh3SgHt7xNSYUb-i6TgPnF3g==\" target=\"_blank\"><img alt=\"UNGARO 3 par Ungaro - Eau de toilette 100ml - 32,90 Euros au lieu de 54,50 Euros\" border=\"0\" height=\"352\" src=\"http://globalpromo.fr/img/2112/images/1532299416570.jpg\" style=\"display:block\" width=\"168\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VYzyk5tAqjMCOv4MGdD1xWbCPhkWaxsToY7QfI_19iB0pMWu5IXknLnnXi02dh8CP3Oc31u1KnaAQdogGHckGLR0JB3nrrulpNikJOHWx6O4E0Vpv97v2cL1RXzjWPqVYTwjrErdFCSca9DhIqmpZ6A==\" target=\"_blank\"><img alt=\"DIOR HOMME COLOGNE par Dior - Eau de Cologne 125ml - 76,90 Euros au lieu de 95,50 Euros\" border=\"0\" height=\"352\" src=\"http://globalpromo.fr/img/2112/images/1398839465706.jpg\" style=\"display:block\" width=\"257\"></a></td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"Table_07\" width=\"850\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VPR5C1tiKjshkaXRN80gVxiFXXtBvmEGDfhhvOG6_lFoYLRuxanHJohviXWVYm2kkatG5tDuc6mf2DDHSQY1N5eBRhXLU58Jc8yJZolV-Z_LdjNFxnux72cFqZQ3qc2YDWYyqWS0Kv-dFwOoPL8drsw==\"><img alt=\"Livraison gratuite dès 150 Euros d'achat\" border=\"0\" height=\"91\" src=\"http://globalpromo.fr/img/2112/images/1497603738570.jpg\" style=\"display:block\" width=\"320\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VPT2ccFHrjQd0ZHM1iBKkzOnqEdDN1DgHTOtmHCstsrlBCvXqilusbmQmanvIGpPlWQXErGuuvbF5vBGx4iEmAwlZsXP16zxyWQ6-W6VHdecchUomqNqIlY4WmtqnjcOpJ8ky0rFDDFeQynTMJcxMtg==\" target=\"_blank\"><img alt=\"Paiement 100% sécurisé\" border=\"0\" height=\"91\" src=\"http://globalpromo.fr/img/2112/images/1569683430570.jpg\" style=\"display:block\" width=\"231\"></a></td>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VIGUZVjbSPeiLZwDsCeNBIHwjM5jVVgWOfqGguAYP6Thov5shO5bIPaSQgKTFlrh8s41XBuVuk_bYIHhHI9MXKKqzTfOc4WV3cCkYtpvgbL5H-ZOXyq-kj4Qdfj5p5gTpMdDniD9fts2wjxbZpQibVg==\" target=\"_blank\"><img alt=\"Parfums 100% de marques authentiques\" border=\"0\" height=\"91\" src=\"http://globalpromo.fr/img/2112/images/1506586352570.jpg\" style=\"display:block\" width=\"299\"></a></td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" id=\"Table_08\" width=\"850\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td><a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6Vf5mpOJiBqS5n_l8LJ6bCiVbTyTC40aO5iWjfLJuOxxBOWq3vEj2smAb5SmVNrdBi8GCwDhV9ZhBY11k2S1FXNQC9Us7_8xqrgBwBaKm4OxJEJuQW7chGRxhLQjmR5zuGlQpA1Uhdux5ZkpkmHueyIQ==\" target=\"_blank\"><img alt=\"Footer\" border=\"0\" height=\"268\" src=\"http://globalpromo.fr/img/2112/images/2081852155706.jpg\" style=\"display:block\" width=\"850\"></a></td>\n\t\t</tr>\n\t</tbody>\n</table>\n<img height=\"1\" src=\"http://camsender.fr/statistique/ouverture.php?news=N_LP_Printemps_2016&mail=dev@caliopen.local&produit=Cosmetique&plateforme=Mailxpertise\" width=\"1\">\n<div align=\"center\">\n<p><font color=\"#000000\" style=\"font-size:11px;font-family:Verdana,Arial,Helvetica,sans-serif;\">Cette newsletter vous a été envoyée par le programme </font><span style=\"font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; text-align: -webkit-center;\">Globalpromo</span><br>\n<br>\n<font color=\"#000000\" style=\"font-size:11px;font-family:Verdana,Arial,Helvetica,sans-serif;\">Pour cesser de recevoir nos informations sur l'adresse dev@caliopen.local, <a href=\"http://globalpromo.fr/wSvtOSW3yuJYYVlixDbKz5u1JUCKJnBQ522W4CCVLq8T0WgSGpL4_e_LRY4D_i6VMSr32sVatIir67MhmF5KwM3ZPzqWqGHyLbMmPVkrFpd4NJQC3kicKzfesHz3ysiBZTGhdH7NbAFM2rWK0us2Iw==\">désabonnez-vous</a><br>\n<br>\nPolitique de confidentialité | © </font><span style=\"font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; text-align: -webkit-center;\">Globalpromo </span><font color=\"#000000\" style=\"font-size:11px;font-family:Verdana,Arial,Helvetica,sans-serif;\">2016</font></p>\n</div>\n</body>\n</html>\n\n\n--QsvfEYh+I/wrllhKImFEKRAZIF1Unz6LXX1Z/R3RK1e8sfI8SsyRCXzKVA5FOpds--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_189.argentina:2,",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id D66EE209B2CF2\n\tfor <laurent@brasil.brainstorm.fr>; Sun, 10 Apr 2016 01:22:47 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid AA8073317704; Sun, 10 Apr 2016 01:22:47 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 8E8CD3317708; Sun, 10 Apr 2016 01:22:47 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.3 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_12_24,HTML_IMAGE_RATIO_02,HTML_MESSAGE,MPART_ALT_DIFF,\n\tNORMAL_HTTP_TO_IP,RCVD_IN_CSS,RDNS_NONE,T_KHOP_FOREIGN_CLICK autolearn=spam\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [63.251.255.135 listed in zen.spamhaus.org]\n\t*  1.0 DATE_IN_PAST_12_24 Date: is 12 to 24 hours before Received: date\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t* -0.3 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server103.offerprivee.in (unknown [63.251.255.135])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 75B9633181F9\n\tfor <dev@caliopen.local>; Sun, 10 Apr 2016 01:22:44 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: Votre 1er cours gratuit, apprenez facilement l'anglais\nMessage-ID: <8586c48eefb41718ab8e08195cd0eb22@162.244.67.108>\nDate: Sat, 09 Apr 2016 06:07:22 -0500\nFrom: \"ABA English\" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 178,179,180\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&L=180&N=11229>\nX-Mailer-SID: 11229\nX-Mailer-Sent-By: 2\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_efabb3f6bd5ac9283f95ab5b992515dc\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Votre 1er cours gratuit, apprenez facilement l'anglais\nContent-Length: 4986\nLines: 128\n\n--b1_efabb3f6bd5ac9283f95ab5b992515dc\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&S=11229&L=180&N=3469\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&L=180&N=11229\n\n--b1_efabb3f6bd5ac9283f95ab5b992515dc\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<title>Aba English</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<style type=\"text/css\"><!--\nbody{background:#FFFFFF !important; min-height:1000px !important;}\n .ExternalClass {width:100% !important;}\nspan, strong{-webkit-text-size-adjust:none !important;}\n\n@media only screen and (max-width:480px){\ntable[class=aba] { width: 300px !important; min-width:300px !important;\ntext-align:center !important;}\ntable[class=aba] td{ width: 300px !important; min-width:300px !important;\nfloat:left !important;}\nbr[class=delete] {display:none !important;}\ntd[class=delete] {display:none !important;}\ntd[class=logo-aba] img{ width: 300px !important; height: 62px !important;\nfloat:left !important;}\ntd[class=visuel-aba] img{ width: 300px !important; height: 196px\n!important; float:left !important;}\ntd[class=btn-aba] img{ width: 300px !important; height: 83px !important;\nfloat:left !important;}\n\ntable[class=sender] { width: 300px !important; min-width:300px\n!important;}\ntd[class=header-sender] img{ width: 300px !important; height:60px\n!important }\ntd[class=delete] { display:none !important;}\ntd[class=text-sender] { width:123px !important; text-align:rigth\n!important; padding-top:5px !important;}\ntd[class=deseado] { width:79px !important; padding-top:5px !important;}\ntd[class=no-deseado] { width:98px !important;padding-top:5px !important;}\ntd[class=text-sender] span{ font-size:11px !important;}\ntd[class=footer-sender] { width: 300px !important; background:none\n!important;}\n}\n--></style>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&S=11229&L=180&N=3469\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<center>\n<table style=\"min-width: 600px; width: 600px;\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"0\" class=\"aba\">\n<tbody>\n<tr>\n<td style=\"min-width:600px;\" class=\"logo-aba\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=11229&L=11387&F=H\">\n<img\nsrc=\"http://ad-lead.com/fr/abaenglish/m_minisite_20082015/public/emkt/acqui3/images/top.jpg\"\nwidth=\"600\" height=\"125\" border=\"0\" style=\"display:block;\" alt=\"Aba\nEnglish\" /></a></td>\n</tr>\n<tr>\n<td style=\"min-width:600px;\" class=\"visuel-aba\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=11229&L=11387&F=H\">\n<img\nsrc=\"http://ad-lead.com/fr/abaenglish/m_minisite_20082015/public/emkt/acqui3/images/mea.jpg\"\nwidth=\"600\" height=\"393\" border=\"0\" style=\"display:block;\" alt=\"La\nmeilleure solution a vos questions pour apprendre langlais\" /></a></td>\n</tr>\n<tr>\n<td width=\"600\" style=\"padding-bottom:30px; padding-top:30px;\ntext-align:center;\"><span style=\"font-family:Arial, sans-serif;\ncolor:#444444; font-size:16px;\"> Profitez du <strong>premier cours ABA\nEnglish GRATUIT </strong> <br class=\"delete\" />et d&eacute;couvrez les\ncl&eacute;s de l'apprentissage d'une des langues         <br class=\"delete\"\n/>les plus parl&eacute;es dans le monde. Allez, aujourd&rsquo;hui je me\nlance ! </span></td>\n</tr>\n<tr>\n<td width=\"600\" style=\"text-align:center; min-width:600px;\nbackground:#FFFFFF\" bgcolor=\"#FFFFFF\" class=\"btn-aba\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=11229&L=11387&F=H\">\n<img\nsrc=\"http://ad-lead.com/fr/abaenglish/m_minisite_20082015/public/emkt/acqui3/images/btn.gif\"\nwidth=\"300\" height=\"83\" border=\"0\" style=\"display:inline;\" alt=\"Jen\nprofite\" /></a></td>\n</tr>\n<tr>\n<td style=\"min-width:600px;\" class=\"delete\"><img\nsrc=\"http://ad-lead.com/fr/abaenglish/m_minisite_20082015/public/emkt/acqui3/images/bottom.jpg\"\nwidth=\"600\" height=\"35\" border=\"0\" style=\"display:block;\" alt=\"Aba English\"\n/></td>\n</tr>\n</tbody>\n</table>\n</center>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&L=180&N=11229\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n<img\nsrc=\"http://adtrack.adleadevent.com/adtckom.php?idc=10716&amp;idctr=1&amp;idp=1715&amp;idm=1828&amp;email=dev@caliopen.local\"\nwidth=\"0\" height=\"0\" /></div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=15236122&L=180&N=11229&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_efabb3f6bd5ac9283f95ab5b992515dc--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_193.argentina:2,",
    "content": "Return-Path: <bounce@client.bonnesaffairesdujour.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 791FB209A30C2\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 23:52:17 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 683403317704; Sat,  9 Apr 2016 23:52:17 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 58F403317708; Sat,  9 Apr 2016 23:52:17 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********\nX-Spam-Status: Yes, score=8.1 required=5.0 tests=AWL,BAYES_99,\n\tHTML_IMAGE_RATIO_02,HTML_MESSAGE,NORMAL_HTTP_TO_IP,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RCVD_IN_CSS,T_KHOP_FOREIGN_CLICK\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [216.158.234.239 listed in zen.spamhaus.org]\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf:  75]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf:  75]\n\t* -0.2 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server101.la-lettre-du-jour.com (server101.la-lettre-du-jour.com [216.158.234.239])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id E60AB33181F9\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 23:52:14 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: =?UTF-8?B?T2ZmcmUgc3DDqWNpYWxlIDogYmlsbGV0IGZ1dMOpIMOgIDM24oKs?=\nMessage-ID: <a0504e93f0b95d2598c1bdb75e26a154@68.168.108.111>\nDate: Sat, 09 Apr 2016 14:42:52 -0500\nFrom: \"=?UTF-8?B?UGFyYyBBc3TDqXJpeA==?=\" <emma@client.bonnesaffairesdujour.com>\nReply-To: emma@client.bonnesaffairesdujour.com\nMIME-Version: 1.0\nX-Mailer-LID: 69,70,68\nList-Unsubscribe: <http://68.168.108.111/~client/unsubscribe.php?M=9434452&C=93d58a5715cb65846c9a27598497eb91&L=68&N=1549>\nX-Mailer-RecptId: 9434452\nX-Mailer-SID: 1549\nX-Mailer-Sent-By: 2\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_7d1e35f9965a83a71acffc97bdc72490\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?UTF-8?B?T2ZmcmUgc3DDqWNpYWxlIDogYmlsbGV0IGZ1dMOpIMOgIDM24oKs?=\nContent-Length: 13280\nLines: 294\n\n--b1_7d1e35f9965a83a71acffc97bdc72490\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nVotre client de messagerie ne peut pas lire cet email.\nPour consulter en ligne, Veuillez allez ici:\nhttp://68.168.108.111/~client/display.php?M=9434452&C=93d58a5715cb65846c9a27598497eb91&S=1549&L=68&N=1728\n\n\nPour ne plus recevoir ces\nemails:http://68.168.108.111/~client/unsubscribe.php?M=9434452&C=93d58a5715cb65846c9a27598497eb91&L=68&N=1549\n\n--b1_7d1e35f9965a83a71acffc97bdc72490\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body>\n<div>\n<div>\n<title>&nbsp;</title>\n<title></title>\n</div>\n</div>\n<style type=\"text/css\"><!--\n         .ReadMsgBody {width: 100%;}\n         .ExternalClass {width: 100%;}\n         td { border-collapse: collapse; }\n--></style>\n<div>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://68.168.108.111/~client/display.php?M=9434452&C=93d58a5715cb65846c9a27598497eb91&S=1549&L=68&N=1728\">cliquez\nici</a>]</td>\n</tr>\n<tr></tr>\n</tbody>\n</table>\n<center>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:\n694px;\"></table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 35px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img01\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-04-01/images/img01.jpg\"\nwidth=\"694\" height=\"182\" border=\"0\" alt=\"LA NOUVELLE ATTRACTION\nINCONTOURNAAAABLE ! &bull; DISCOBELIX\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#e02c1f\" style=\"text-align: center; background-color:\n#e02c1f;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 30px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img02\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-04-01/images/img02.jpg\"\nwidth=\"694\" height=\"70\" border=\"0\" alt=\"JE DECOUVRE\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img03\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-04-01/images/img03.jpg\"\nwidth=\"230\" height=\"39\" border=\"0\" /></a></td>\n<td width=\"230\" height=\"39\" bgcolor=\"#fff\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-04-01/images/img04.jpg\"\nstyle=\"text-align: center;\"><font face=\"Arial, sans-serif\" color=\"#000000\"\nstyle=\"font-size: 8px;\"> En cliquant ici, j&rsquo;accepte de <br />\nrecevoir les offres commerciales du Parc Ast&eacute;rix </font></td>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img05\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-04-01/images/img05.jpg\"\nwidth=\"235\" height=\"39\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#fff\" style=\"text-align: center; background-color: #fff;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 40px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img06\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-04-01/images/img100.jpg\"\nwidth=\"694\" height=\"287\" border=\"0\" alt=\"L'irr&eacute;ductible Parc\nAst&eacute;rix &bull; PARIS\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#fff\" style=\"text-align: center; background-color: #fff;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img19\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img19.jpg\"\nwidth=\"85\" height=\"61\" border=\"0\" /></a></td>\n<td width=\"235\" height=\"61\" bgcolor=\"#ffffff\" style=\"text-align: center;\nbackground-color: #ffffff;\"><font face=\"Verdana, Arial, Helvetica,\nsans-serif\" color=\"#1f1f1f\" style=\"font-size: 20px;\"> <strong>LE PARC\nAST&Eacute;RIX</strong><br /> <font face=\"Helvetica, Arial, Verdana,\nsans-serif\" color=\"#1f1f1f\" style=\"font-size: 17px;\">c'est pour\nvous:</font> </font></td>\n<td bgcolor=\"#fff\" style=\"text-align: center; background-color: #fff;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img21\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img21.jpg\"\nwidth=\"73\" height=\"61\" border=\"0\" /></a></td>\n<td width=\"194\" height=\"61\" bgcolor=\"#ffffff\" style=\"text-align: center;\nbackground-color: #ffffff;\"><font face=\"Verdana, Arial, Helvetica,\nsans-serif\" color=\"#1f1f1f\" style=\"font-size: 20px;\"> <strong>&Agrave;\nD&Eacute;COUVRIR </strong><br /> <font face=\"Helvetica, Arial, Verdana,\nsans-serif\" color=\"#1f1f1f\" style=\"font-size: 17px;\">d&egrave;s\nmaintenant</font> </font></td>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img23\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img23.jpg\"\nwidth=\"107\" height=\"61\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img24\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img24.jpg\"\nwidth=\"694\" height=\"123\" border=\"0\" alt=\"En t&eacute;l&eacute;chargeant\nl'application mobile.\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img25\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img25.jpg\"\nwidth=\"64\" height=\"28\" border=\"0\" /></a></td>\n<td width=\"137\" height=\"28\" bgcolor=\"#ffffff\" style=\"text-align: center;\nbackground-color: #ffffff;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"text-decoration: none;\"> <font face=\"Verdana, Arial, Helvetica,\nsans-serif\" color=\"#000\" style=\"font-size: 14px;\"> 39 ATTRACTIONS </font>\n</a></td>\n<td width=\"137\" height=\"28\" bgcolor=\"#ffffff\" style=\"text-align: center;\nbackground-color: #ffffff;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"text-decoration: none;\"> <font face=\"Verdana, Arial, Helvetica,\nsans-serif\" color=\"#000\" style=\"font-size: 14px;\"> 5 SPECTACLES </font>\n</a></td>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img28\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img28.jpg\"\nwidth=\"356\" height=\"28\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img29\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img29.jpg\"\nwidth=\"694\" height=\"28\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td width=\"694\" height=\"33\" bgcolor=\"#4f4f4f\" style=\"text-align: center;\nbackground-color: #4f4f4f;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"text-decoration: none;\"> <font face=\"arial, verdana, sans-serif\"\ncolor=\"#fff\" style=\"font-size: 20px;\"> <strong>RESTONS EN CONTACT</strong>\n</font> </a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img31\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img31.jpg\"\nwidth=\"96\" height=\"62\" border=\"0\" /></a></td>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img32\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img32.jpg\"\nwidth=\"44\" height=\"62\" border=\"0\" /></a></td>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img33\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img33.jpg\"\nwidth=\"44\" height=\"62\" border=\"0\" /></a></td>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img34\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img34.jpg\"\nwidth=\"50\" height=\"62\" border=\"0\" /></a></td>\n<td bgcolor=\"#4f4f4f\" style=\"text-align: center; background-color:\n#4f4f4f;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img35\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img35.jpg\"\nwidth=\"460\" height=\"62\" border=\"0\" alt=\"Consultez les avis de nos\nvisiteurs\" /></a></td>\n</tr>\n</tbody>\n</table>\n&lt;td width=\"274\" height=\"50\" bgcolor=\"#fff\" style=\"text-align: left;\nbackground-color: #fff;\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 694px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img36\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img36.jpg\"\nwidth=\"156\" height=\"50\" border=\"0\" /></a></td>\n<td width=\"113\" height=\"50\" bgcolor=\"#fff\" style=\"text-align: center;\nbackground-color: #fff;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"text-decoration: none;\"> <font face=\"arial, verdana, sans-serif\"\ncolor=\"#000\" style=\"font-size: 14px;\"> &Agrave; 30km de Paris </font>\n</a></td>\n<td bgcolor=\"#70a4d8\" style=\"text-align: center; background-color:\n#70a4d8;\"><a\nhref=\"http://68.168.108.111/~client/link.php?M=9434452&N=1549&L=3889&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size: 18px; color: #fff;\ntext-decoration: none;\"> <img style=\"display: block;\" id=\"img38\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/asterix/2016-03-14/images/img38.jpg\"\nwidth=\"151\" height=\"50\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n</center></div>\n\n\n<img\nsrc=\"http://68.168.108.111/~client/open.php?M=9434452&L=68&N=1549&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_7d1e35f9965a83a71acffc97bdc72490--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_195.argentina:2,",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id B829520B868A0\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 23:04:25 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid A4D0A3317708; Sat,  9 Apr 2016 23:04:25 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 90F223317735; Sat,  9 Apr 2016 23:04:25 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.5 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_06_12,HTML_IMAGE_RATIO_02,HTML_MESSAGE,MPART_ALT_DIFF,\n\tNORMAL_HTTP_TO_IP,RCVD_IN_CSS,RDNS_NONE,T_KHOP_FOREIGN_CLICK autolearn=spam\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [63.251.255.135 listed in zen.spamhaus.org]\n\t*  1.5 DATE_IN_PAST_06_12 Date: is 6 to 12 hours before Received: date\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t* -0.6 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server103.offerprivee.in (unknown [63.251.255.135])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 37F8A3317708\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 23:04:22 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: -50% sur les valises Bensimon !\nMessage-ID: <e684ce98c2b98e598fd9367b99c5637e@162.244.67.108>\nDate: Sat, 09 Apr 2016 07:18:22 -0500\nFrom: \"Promo-Valises\" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 164,165\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11243>\nX-Mailer-SID: 11243\nX-Mailer-Sent-By: 2\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_5fda6f29e1a5ed6028cae3ffab0df89f\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: -50% sur les valises Bensimon !\nContent-Length: 16287\nLines: 350\n\n--b1_5fda6f29e1a5ed6028cae3ffab0df89f\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11243&L=165&N=3681\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11243\n\n--b1_5fda6f29e1a5ed6028cae3ffab0df89f\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<title>Promo bagages BENSIMON, style sportswear et color&eacute; &agrave;\npetits prix</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n<!--Don t remove the next meta-->\n<meta name=\"pdlbat1tkn\" content=\"XCKDeWVU\" />\n<img\nsrc=\"http://dbkpwi4c6v97q.cloudfront.net/?sc=4779&amp;t=0&amp;l=174740&amp;p=79134\"\nwidth=\"1\" height=\"1\" border=\"0\" />\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11243&L=165&N=3681\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<table align=\"center\" bgcolor=\"#ffffff\" border=\"0\" cellpadding=\"0\"\ncellspacing=\"4\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td align=\"center\"><font style=\"color:#2b2b2b\" face=\"arial\" size=\"2\"> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"Accueil\" style=\"text-decoration:none;color:#fa4b72;\"> <strong>Promo\nbagages BENSIMON, style sportswear et color&eacute; &agrave; petits\nprix</strong></a> </font></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"margin: 0px auto; text-align: center; width: 600px;\">\n<tbody>\n<tr style=\"height: 46px;\">\n<td width=\"600\" height=\"46\" style=\"background-color:#282828;\ntext-align:center;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"livraison gratuite\" style=\"height: 46px; text-decoration: none;\nfont-family: arial; font-size: 18px; color: #ffffff;\">LIVRAISON GRATUITE\nsur votre lieu de travail !</a></td>\n</tr>\n<tr style=\"height: 130px;\">\n<td width=\"600\" height=\"130\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"prix uniques Bensimon\" style=\"height: 130px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/9b9d7aac17f9b6e162c4868899d5f109_ben.jpg\"\nwidth=\"600\" height=\"130\" alt=\"prix uniques Bensimon\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 169px;\">\n<td width=\"600\" height=\"169\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"du 7 au 14 avril\" style=\"height: 169px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/9917e8ecb3f984f3b5b3941bd0346399_dat.jpg\"\nwidth=\"600\" height=\"169\" alt=\"du 7 au 14 avril\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 170px;\">\n<td width=\"600\" height=\"170\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"moins 50 pour cent\" style=\"height: 170px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/257a4c0249ef034434c03c4b90eedae0_pri.jpg\"\nwidth=\"600\" height=\"170\" alt=\"moins 50 pour cent\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 169px;\">\n<td width=\"600\" height=\"169\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"stock limit&eacute;\" style=\"height: 169px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/c7049824f5b39605f1a4eb3513befb15_uni.jpg\"\nwidth=\"600\" height=\"169\" alt=\"stock limit&eacute;\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 169px;\">\n<td width=\"600\" height=\"169\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"voir la collection\" style=\"height: 169px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/002c37ff9349e738e62cffda4b311bc4_sho.jpg\"\nwidth=\"600\" height=\"169\" alt=\"voir la collection\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 135px;\">\n<td width=\"600\" height=\"135\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises cabine 49e\" style=\"height: 135px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/0dfd41f7e202611a3ded0d04fe8be3f9_for.jpg\"\nwidth=\"600\" height=\"135\" alt=\"valises cabine 49e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 134px;\">\n<td width=\"600\" height=\"134\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises cabine 49e\" style=\"height: 134px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/2bce2150c76af52d9eea1f8f436505f4_cab.jpg\"\nwidth=\"600\" height=\"134\" alt=\"valises cabine 49e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 135px;\">\n<td width=\"600\" height=\"135\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises cabine 49e\" style=\"height: 135px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/0ce502fdb11403621271e3b4a670f22f_wee.jpg\"\nwidth=\"600\" height=\"135\" alt=\"valises cabine 49e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 134px;\">\n<td width=\"600\" height=\"134\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises semaine 59e\" style=\"height: 134px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/25167f14b618a2879b4524df1dddab52_moy.jpg\"\nwidth=\"600\" height=\"134\" alt=\"valises semaine 59e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 135px;\">\n<td width=\"600\" height=\"135\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises semaine 59e\" style=\"height: 135px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/03b69733f7f4a47ee91bfa0a1fb9cfde_med.jpg\"\nwidth=\"600\" height=\"135\" alt=\"valises semaine 59e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 135px;\">\n<td width=\"600\" height=\"135\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises semaine 59e\" style=\"height: 135px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/09d7041396d6788ea5a547161facd7ba_sem.jpg\"\nwidth=\"600\" height=\"135\" alt=\"valises semaine 59e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 134px;\">\n<td width=\"600\" height=\"134\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises magnum 69e\" style=\"height: 134px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/ba127526b38d38f9d439b2188caf1662_mag.jpg\"\nwidth=\"600\" height=\"134\" alt=\"valises magnum 69e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 135px;\">\n<td width=\"600\" height=\"135\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises magnum 69e\" style=\"height: 135px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/e9e969214fc14986b458b7e028863367_gra.jpg\"\nwidth=\"600\" height=\"135\" alt=\"valises magnum 69e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 134px;\">\n<td width=\"600\" height=\"134\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises magnum 69e\" style=\"height: 134px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/7bbac72cc6f87a558b6fb8ee0ebe62bb_lon.jpg\"\nwidth=\"600\" height=\"134\" alt=\"valises magnum 69e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 135px;\">\n<td width=\"600\" height=\"135\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"3 valises 129e\" style=\"height: 135px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/08a66a3b9096e12a010d2808ddddd367_set.jpg\"\nwidth=\"600\" height=\"135\" alt=\"3 valises 129e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 135px;\">\n<td width=\"600\" height=\"135\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"3 valises 129e\" style=\"height: 135px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/74c0e58a87c6c784104a45fa7693442c_ens.jpg\"\nwidth=\"600\" height=\"135\" alt=\"3 valises 129e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 134px;\">\n<td width=\"600\" height=\"134\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"3 valises 129e\" style=\"height: 134px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/c7fdffeee0bafadedce5ed3eb39bedeb_pac.jpg\"\nwidth=\"600\" height=\"134\" alt=\"3 valises 129e\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 135px;\">\n<td width=\"600\" height=\"135\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises cabine + vanity 59e\" style=\"height: 135px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/18f0b3193e129210088fe337b02cfa01_val.jpg\"\nwidth=\"600\" height=\"135\" alt=\"valises cabine + vanity 59e\"\nstyle=\"display:block; border:0;\" /></a></td>\n</tr>\n<tr style=\"height: 134px;\">\n<td width=\"600\" height=\"134\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises cabine + vanity 59e\" style=\"height: 134px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/177af173b04e7721c0a00eeda8f0a2f9_van.jpg\"\nwidth=\"600\" height=\"134\" alt=\"valises cabine + vanity 59e\"\nstyle=\"display:block; border:0;\" /></a></td>\n</tr>\n<tr style=\"height: 135px;\">\n<td width=\"600\" height=\"135\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"valises cabine + vanity 59e\" style=\"height: 135px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/50dc1bad26c2a1ffaff65eefe2285de9_cas.jpg\"\nwidth=\"600\" height=\"135\" alt=\"valises cabine + vanity 59e\"\nstyle=\"display:block; border:0;\" /></a></td>\n</tr>\n<tr style=\"height: 200px;\">\n<td width=\"600\" height=\"200\" style=\"padding-left:15px;\npadding-right:15px;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"voir sur le site\" style=\"height: 200px; text-decoration: none;\nfont-family: arial; font-size: 15px; color: #111111;\"><font\nstyle=\"font-size:18px; font-weight:bold; color:#fa4b72;\">Voyagez en\ncouleurs avec les bagages BENSIMON !</font><br /><br />Avec les valises\nBENSIMON, aucun risque de confondre votre valise avec celle du voisin,\nleurs motifs color&eacute;s et ultra tendance se rep&egrave;rent de loin et\nferont des envieux ;)<br />Polycarbonate laqu&eacute;, cadenas\nincrust&eacute;, am&eacute;nagement int&eacute;rieur, 4 roulettes\nrotatives... Ces valises sont optimis&eacute;es pour faciliter vos\nd&eacute;placements et durer dans le temps.<br />Foncez, il n&rsquo;y en\naura pas pour tout le monde !</a></td>\n</tr>\n<tr style=\"height: 106px;\">\n<td width=\"600\" height=\"106\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"nos garanties\" style=\"height: 106px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/98df0ab1852f11a0bdb33fd420231baa_bon.jpg\"\nwidth=\"600\" height=\"106\" alt=\"nos garanties\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 105px;\">\n<td width=\"600\" height=\"105\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"nos garanties\" style=\"height: 105px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/f6073f9a92f2e9df0277705968577095_rai.jpg\"\nwidth=\"600\" height=\"105\" alt=\"nos garanties\" style=\"display:block;\nborder:0;\" /></a></td>\n</tr>\n<tr style=\"height: 30px;\">\n<td width=\"600\" height=\"30\" style=\"padding-right:5px; text-align:right;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"livraison\" style=\"height: 30px; text-decoration: none; font-family:\narial; font-size: 12px; color: #111111;\">* Livraison offerte sur votre lieu\nde travail, ou d&egrave;s 60&euro; en relais colis.</a></td>\n</tr>\n<tr style=\"height: 40px;\">\n<td width=\"600\" height=\"40\" style=\"padding-left:15px; text-align:left;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"nos marques\" style=\"height: 40px; text-decoration: none;\nfont-family: arial; font-weight: bold; font-size: 16px; color:\n#ff374f;\">NOS MARQUES</a></td>\n</tr>\n<tr style=\"height: 121px;\">\n<td width=\"600\" height=\"121\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"nos marques\" style=\"height: 121px;\"><img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/e391bf325bdbeb412356dd83addd5509_mar.jpg\"\nwidth=\"600\" height=\"121\" alt=\"nos marques\" style=\"display:block; border:0;\"\n/></a></td>\n</tr>\n<tr style=\"height: 15px;\">\n<td width=\"600\" height=\"15\"></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"margin: 0px auto; text-align: left; width: 600px;\">\n<tbody>\n<tr>\n<td width=\"180\" height=\"120\" style=\"padding-left:10px;\nvertical-align:top;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"mentions l&eacute;gales\" style=\"height: 119px; text-decoration:\nnone; font-family: arial; font-size: 13px; font-weight: bold; color:\n#575543;\">&Agrave; propos :<br /><br /><font style=\"font-size:12px;\nfont-weight:normal; color:#575543;\">Qui sommes-nous ?<br />Mentions\nl&eacute;gales<br />Suivez votre commande<br />au 01 73 600\n888</font></a></td>\n<td width=\"240\" height=\"120\" style=\"vertical-align:top;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"avantages\" style=\"height: 119px; text-decoration: none; font-family:\narial; font-size: 13px; font-weight: bold; color: #575543;\">Pourquoi\nacheter chez nous ?<br /><br /><font style=\"font-size:12px;\nfont-weight:normal; color:#575543;\">Assurance voyage<br />La livraison\ngratuite<br />Les meilleurs prix d'internet<br />Un service client\nperformant<br />R&eacute;cup&eacute;rez vos bagages &agrave;\nl'a&eacute;roport</font></a></td>\n<td width=\"180\" height=\"120\" style=\"vertical-align:top;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11243&L=14879&F=H\"\ntitle=\"vos questions\" style=\"height: 119px; text-decoration: none;\nfont-family: arial; font-size: 13px; font-weight: bold; color:\n#575543;\">Vos questions :<br /><br /><font style=\"font-size:12px;\nfont-weight:normal; color:#575543;\">Contacter le Service Clients<br />Nos\nCGV<br />Tarifs de livraison</font></a></td>\n</tr>\n</tbody>\n</table>\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nstyle=\"margin: 0px auto; width: 602px;\">\n<tbody>\n<tr>\n<td width=\"600\" valign=\"top\"\nstyle=\"padding-left:10px;padding-right:10px;\">\n<p style=\"text-align:left;color:#575543; font-family:arial;\nfont-size:12px\"><strong><font style=\"color:#ff374f;\nfont-size:13px;\">Bensimon &agrave; prix cass&eacute;s, voyagez\ncolor&eacute;...</font></strong><br /><br /> &bull; Le n&deg;1 de la vente\nde bagages sur internet !<br /> &bull; Des bagages styl&eacute;s et la\nqualit&eacute; des plus grandes marques, aux meilleurs prix du net.<br />\n&bull; Partez serein en vacances ou en voyage d'affaires avec plus de 1000\nr&eacute;f&eacute;rences &agrave; petits prix.<br /> &bull; Valises, sacs\nde voyage, accessoires indispensables, il y en a pour tous les go&ucirc;ts\net tous les budgets.<br /> &bull; Nouveau : profitez du paiement en 3x sans\nfrais, &agrave; partir de 100&euro;.<br /> &bull; De nombreux avantages :\nexp&eacute;dition sous 24h, livraison offerte sur votre lieu de travail,\ngarantie 1 an offerte et promos toute l&rsquo;ann&eacute;e !</p>\n</td>\n</tr>\n</tbody>\n</table>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11243\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n<img\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/img/6831/54595b07d88ccb27560c6e944e79b95e_0fc5db0caa67aa552540b27740586fed\"\nstyle=\"display:block;\" border=\"0\" width=\"0\" height=\"0\" /></div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=11664742&L=165&N=11243&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_5fda6f29e1a5ed6028cae3ffab0df89f--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_197.argentina:2,",
    "content": "Return-Path: <news@e.fipe457.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 7FFEC20B89CB1\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 22:32:23 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 6CCC63317708; Sat,  9 Apr 2016 22:32:23 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid DF5183317735; Sat,  9 Apr 2016 22:32:22 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.1 required=5.0 tests=AWL,BAYES_99,DKIM_SIGNED,\n\tHTML_IMAGE_RATIO_02,HTML_MESSAGE,MIME_HTML_ONLY,MSGID_RANDY,\n\tRCVD_IN_DNSWL_NONE,SPF_HELO_PASS,T_DKIM_INVALID autolearn=spam version=3.3.2\nX-Spam-Report: \n\t* -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no\n\t*      trust\n\t*      [37.59.69.56 listed in list.dnswl.org]\n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t* -0.0 SPF_HELO_PASS SPF: HELO matches SPF record\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  2.6 MSGID_RANDY Message-Id has pattern used in spam\n\t*  0.0 T_DKIM_INVALID DKIM-Signature header exists but is not valid\n\t* -0.2 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from e.fipe457.fr (a56.e.fipe457.fr [37.59.69.56])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id 7F11C3317708\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 22:32:18 +0200 (CEST)\nDomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;\n  s=mail;d=e.fipe457.fr;\n  h=from:sender:to:date:message-id:subject:mime-version:content-type;\n  b=ulrRjXMzVMroLHjAcm6XOGZ9r8D9BsOT0y1aQHlb+fsQ9Cqzpc2+VpPUKssUrQQSEWXLl4ik\n    0iqnIMflfhPz8kE+uis8M/0tgZLcSAZstNxN9gdFZNMrFIQc9UjpWm4hFxiueYj8m6yJ1WFC\n    w66Lq36D0G6mi4Qz4LAIi4ZVxWU=\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed;\n  s=mail;d=e.fipe457.fr;\n  h=from:sender:to:date:message-id:subject:mime-version:content-type;\n  bh=mxQBDl63myF0UkH4XFOdBfGfljA=;\n  b=wGqlDBes/QEQ4kxzjcIrDlrk0tkTKTdZtmc5LNS7m2TkP6DsMsJzh7q5ymA9l9Quk29V0i1d\n    kXvRwsyQfmQSP0UownSgkj6Rzs4aeSMXCzzy9XDBqKJWwYn3Cxma20l3j+vlQvBbdkFwH8LB\n    /WCkwaTdyAqZStbqXN/yPO20/YQ=\nX-Customer-ID: 99\nX-Campaign-ID: 3231\nList-Unsubscribe: http://e.fipe457.fr/tr/1/438669846/3231/0940486/\nFrom: Nocibe / Offre du jour <news@e.fipe457.fr>\nSender: Nocibe / Offre du jour <news@e.fipe457.fr>\nReply-To: Nocibe / Offre du jour <news@e.fipe457.fr>\nTo: dev@caliopen.local\nDate: Sat, 09 Apr 2016 20:32:46 GMT\nOrganization: e.fipe457.fr\nMessage-ID: <1-438669846-3231-0940486@e.fipe457.fr>\nSubject: Moins 25 PCT pour un look sublime\nMIME-Version: 1.0\nX-Mailer: PHPMailer\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Moins 25 PCT pour un look sublime\nContent-Length: 32090\nLines: 528\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\"><head>\n<title>Nocibe</title>\n<link rel=\"icon\" type=\"image/x-icon\" data-linkrefke=\"57037c417d897\" href=\"http://wpm.ccmp.eu/wpm/560/Email_Oneshot/nocibe.ico\">\n\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n<style type=\"text/css\">\nExternalClass {\n\twidth:100%;\n}\nExternalClass * {\n\tline-height: 100%;\n}\nbody {\n\tmargin:0;\n}\n @media only screen and (max-width: 670px) {\nbody {\n\t-webkit-text-size-adjust: none;\n\t-ms-text-size-adjust: none;\n\tmargin:0px;\n\tpadding:0px;\n}\n*[class=w320] {\n\twidth:320px !important;\n\tborder:0px !important;\n}\n*[class=w300] {\n\twidth:300px !important;\n\tborder:0px !important;\n}\n*[class=w290] {\n\twidth:290px !important;\n\tfont-size:11px !important;\n}\n*[class=w15] {\n\twidth:15px !important;\n}\n*[class=w10] {\n\twidth:10px !important;\n}\n*[class=gris] {\n\tbackground-color:#E9E7E8 !important;\n\twidth:320px !important;\n\theight:2px !important;\n}\n*[class=hide] {\n\tdisplay:none !important;\n\theight:0px !important;\n\twidth:0px !important;\n}\n*[class=service] {\n\twidth: 320px !important;\n\theight: 192px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/service_m.jpg') no-repeat center top !important;\n}\n*[class=w30] {\n\twidth:30px !important;\n\tborder:0px !important;\n}\n*[class=retrouvez] {\n\twidth: 320px !important;\n\theight: 48px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/retrouvez_m.jpg') no-repeat center top !important;\n}\n*[class=logo_nocibe] {\n\twidth: 320px !important;\n\theight: 80px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/logo_nocibe_m.jpg') no-repeat center top !important;\n}\n*[class=w320h60] {\n\twidth:320px !important;\n\theight:60px !important;\n\tborder:0px !important;\n}\n*[class=esp] {\n\tdisplay:block !important;\n}\n*[class=loc] {\n\twidth: 320px !important;\n\theight: 111px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/localisation_m.jpg') no-repeat center top !important;\n}\n*[class=mes_services] {\n\twidth: 320px !important;\n\theight: 63px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/mes_services_m.jpg') no-repeat center top !important;\n}\n*[class=retours] {\n\twidth: 320px !important;\n\theight: 41px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/retours_m.jpg') no-repeat center top !important;\n}\n*[class=echantillons_off] {\n\twidth: 320px !important;\n\theight: 47px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/echantillons_off_m.jpg') no-repeat center top !important;\n}\n*[class=liv_offerte] {\n\twidth: 320px !important;\n\theight: 42px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/liv_offerte_m.jpg') no-repeat center top !important;\n}\n*[class=f_sepa] {\n\twidth: 320px !important;\n\theight: 25px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/f_sepa_m.jpg') no-repeat center top !important;\n}\n*[class=retrouvez_nous] {\n\twidth: 180px !important;\n\theight: 36px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/retrouvez_nous_m.jpg') no-repeat center top !important;\n}\n*[class=facebook] {\n\twidth: 33px !important;\n\theight: 36px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/facebook_m.jpg') no-repeat center top !important;\n}\n*[class=twitter] {\n\twidth: 33px !important;\n\theight: 36px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/twitter_m.jpg') no-repeat center top !important;\n}\n*[class=youtube] {\n\twidth: 31px !important;\n\theight: 36px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/youtube_m.jpg') no-repeat center top !important;\n}\n*[class=appli] {\n\twidth: 43px !important;\n\theight: 36px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/appli_m.jpg') no-repeat center top !important;\n}\n*[class=loca] {\n\twidth: 320px !important;\n\theight: 48px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/loca_m.jpg') no-repeat center top !important;\n}\n/* ******************************************************** */\n\n*[class=bientot_site] {\n\twidth: 320px !important;\n\theight: 85px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/bientot_site_m.jpg') no-repeat center top !important;\n}\n*[class=bientot_parf] {\n\twidth: 320px !important;\n\theight: 35px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/bientot_parf_m.jpg') no-repeat center top !important;\n}\n*[class=nav_femme] {\n\twidth: 163px !important;\n\theight: 22px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/nav_femme.jpg') no-repeat center top !important;\n}\n*[class=nav_homme] {\n\twidth: 157px !important;\n\theight: 22px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/nav_homme.jpg') no-repeat center top !important;\n}\n*[class=look_sublime] {\n\twidth: 320px !important;\n\theight: 205px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/look_sublime_m.jpg') no-repeat center top !important;\n}\n*[class=promo_a] {\n\twidth: 163px !important;\n\theight: 171px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/promo_a_m.jpg') no-repeat center top !important;\n}\n*[class=promo_b] {\n\twidth: 157px !important;\n\theight: 171px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/promo_b_m.jpg') no-repeat center top !important;\n}\n*[class=profite] {\n\twidth: 320px !important;\n\theight: 45px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/profite_m.jpg') no-repeat center top !important;\n}\n*[class=bonbon] {\n\twidth: 320px !important;\n\theight: 466px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/bonbon_m.jpg') no-repeat center top !important;\n}\n*[class=givenchy] {\n\twidth: 320px !important;\n\theight: 514px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/givenchy_m.jpg') no-repeat center top !important;\n}\n*[class=clarins] {\n\twidth: 320px !important;\n\theight: 597px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/clarins_m.jpg') no-repeat center top !important;\n}\n*[class=lauder] {\n\twidth: 320px !important;\n\theight: 472px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/lauder_m.jpg') no-repeat center top !important;\n}\n*[class=go_site] {\n\twidth: 320px !important;\n\theight: 45px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/go_site_m.jpg') no-repeat center top !important;\n}\n*[class=creez_parfum] {\n\twidth: 320px !important;\n\theight: 402px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/creez_parfum_m.jpg') no-repeat center top !important;\n}\n*[class=liberte] {\n\twidth: 320px !important;\n\theight: 87px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/liberte_m.jpg') no-repeat center top !important;\n}\n*[class=parf_femme] {\n\twidth: 164px !important;\n\theight: 35px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/parf_femme_m.jpg') no-repeat center top !important;\n}\n*[class=parf_homme] {\n\twidth: 156px !important;\n\theight: 35px !important;\n\tdisplay:block !important;\n\tbackground: url('http://bdcreatives.s3.amazonaws.com/272/bn/47034/parf_homme_m.jpg') no-repeat center top !important;\n}\n}\ntable {\n\tborder-collapse: collapse;\n}\n</style>\n</head>\n<body bgcolor=\"#F3F3F3\"><div align=\"center\">\n<font color=\"#555555\" style=\"font-size:10px;font-family:Verdana,Arial,Helvetica,sans-serif;\">\n<b>Bonjour  , vous recevez ce mail car vous êtes abonné avec l'adresse dev@caliopen.local</b><br>\nSi vous souhaitez vous d&eacute;sincrire, <a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=134899c60&idc=09404866f48\">suivez ce lien</a>\n        Pour visualiser correctement ce message, acc&eacute;dez &agrave; la <a  href=\"http://e.fipe457.fr/mr/?v=2&idi=4386698467041&idl=234898701&idm=3231b048&idc=09404866f48\">version en ligne</a>\n        <br><br>\n\n    </font>\n</div>\n<table class=\"w320\" align=\"center\" bgcolor=\"#F3F3F3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n  <tbody><tr>\n    <td style=\"font-family: Arial, Helvetica, sans-serif; font-size:14px; color:#c21a50\" class=\"w320\" height=\"45\" align=\"center\" width=\"650\"><strong>Offre beauté Nocibé : jusqu’à -25% sur tout !</strong></td>\n  </tr>\n  <tr>\n    <td><table class=\"w320\" align=\"center\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n      <tbody><tr>\n        <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=33489ab29&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/logo_nocibe.jpg\" alt=\"\" style=\"display:block;\" height=\"80\" border=\"0\" width=\"268\"></a></td>\n        <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"382\">\n          <tbody><tr>\n            <td height=\"40\" bgcolor=\"#FFFFFF\" width=\"382\"><span class=\"logo_nocibe\"></span></td>\n          </tr>\n          <tr>\n            <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"382\">\n              <tbody><tr>\n                <td bgcolor=\"#FFFFFF\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=434898a2c&idc=09404866f48\" target=\"_blank\"><span class=\"parf_femme\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/parf_femme.jpg\" alt=\"\" style=\"display:block;\" class=\"hide\" height=\"23\" border=\"0\" width=\"109\"></span></a></td>\n                <td bgcolor=\"#FFFFFF\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=53489f815&idc=09404866f48\" target=\"_blank\"><span class=\"parf_homme\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/parf_homme.jpg\" alt=\"\" style=\"display:block;\" class=\"hide\" height=\"23\" border=\"0\" width=\"108\"></span></a></td>\n                <td class=\"hide\" bgcolor=\"#FFFFFF\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=63489e9e8&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/maq.jpg\" alt=\"\" style=\"display:block;\" height=\"23\" border=\"0\" width=\"87\"></a></td>\n                <td class=\"hide\" bgcolor=\"#FFFFFF\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=734892e5f&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/coffrets.jpg\" alt=\"\" style=\"display:block;\" height=\"23\" border=\"0\" width=\"78\"></a></td>\n              </tr>\n            </tbody></table></td>\n          </tr>\n          <tr>\n            <td class=\"hide\" height=\"17\" bgcolor=\"#FFFFFF\" width=\"382\"></td>\n          </tr>\n        </tbody></table></td>\n      </tr>\n    </tbody></table></td>\n  </tr>\n  <tr>\n    <td class=\"hide\" height=\"9\" bgcolor=\"#ffffff\" width=\"650\"></td>\n  </tr>\n  <tr>\n    <td><table class=\"w320\" align=\"center\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n        <tbody><tr>\n          <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=8348977b9&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/look_sublime.jpg\" alt=\"du 6 au 12 avril Offrez vous un look sublime\" style=\"display:block;\" height=\"376\" border=\"0\" width=\"430\"></a></td>\n                <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"220\">\n                    <tbody><tr>\n                      <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=934896553&idc=09404866f48\" target=\"_blank\"><span class=\"look_sublime\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/promo_a.jpg\" alt=\"-25% sur tous les parfums avec le code\" style=\"display:block;\" class=\"hide\" height=\"145\" border=\"0\" width=\"220\"></span></a></td>\n                    </tr>\n                    <tr>\n                      <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"220\">\n                          <tbody><tr>\n                            <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=035896eaf&idc=09404866f48\" target=\"_blank\"><span class=\"promo_a\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/promo_a_l.jpg\" alt=\"\" style=\"display:block;\" class=\"hide\" height=\"30\" border=\"0\" width=\"52\"></span></a></td>\n                            <td class=\"hide\" style=\"font-family:Arial, Helvetica, sans-serif; font-size:17px; color:#E21386; text-align:center\" height=\"30\" bgcolor=\"#FFFFFF\" width=\"100\"><strong><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=13589bb85&idc=09404866f48\" target=\"_blank\" style=\"color:#E21386; text-decoration:none;\">NOCIBE25</a></strong></td>\n                            <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=23589bd9a&idc=09404866f48\" target=\"_blank\"><span class=\"promo_b\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/promo_a_r.jpg\" alt=\"\" style=\"display:block;\" class=\"hide\" height=\"30\" border=\"0\" width=\"68\"></span></a></td>\n                          </tr>\n                        </tbody></table></td>\n                    </tr>\n                    <tr>\n                      <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=33589814d&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/promo_b.jpg\" alt=\"-20% sur tout le maquillage avec le code\" style=\"display:block;\" height=\"130\" border=\"0\" width=\"220\"></a></td>\n                    </tr>\n                    <tr>\n                      <td><table class=\"hide\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"220\">\n                          <tbody><tr>\n                            <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=435895cf1&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/promo_b_l.jpg\" alt=\"\" style=\"display:block;\" height=\"30\" border=\"0\" width=\"52\"></a></td>\n                            <td style=\"font-family:Arial, Helvetica, sans-serif; font-size:17px; color:#E21386; text-align:center\" height=\"30\" bgcolor=\"#FFFFFF\" width=\"100\"><strong><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=53589c129&idc=09404866f48\" target=\"_blank\" style=\"color:#E21386; text-decoration:none;\">NOCIBE20</a></strong></td>\n                            <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=63589566f&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/promo_b_r.jpg\" alt=\"\" style=\"display:block;\" height=\"30\" border=\"0\" width=\"68\"></a></td>\n                          </tr>\n                        </tbody></table></td>\n                    </tr>\n                    <tr>\n                      <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=7358937e2&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/promo_b_bot.jpg\" alt=\"\" style=\"display:block;\" height=\"41\" border=\"0\" width=\"220\"></a></td>\n                    </tr>\n                  </tbody></table></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n        <tr>\n          <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=83589823a&idc=09404866f48\" target=\"_blank\"><span class=\"profite\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/profite.jpg\" alt=\"J'en profite\" style=\"display:block;\" class=\"hide\" height=\"45\" border=\"0\" width=\"650\"></span></a></td>\n        </tr>\n        <tr>\n          <td><table class=\"hide\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=9358994f4&idc=09404866f48\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/selection_parfums.jpg\" alt=\"Sélection parfums\" style=\"display:block;\" height=\"143\" border=\"0\" width=\"413\"></a></td>\n                <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=0368970e7&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/givenchy_top.jpg\" alt=\"\" style=\"display:block;\" height=\"143\" border=\"0\" width=\"237\"></a></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n        <tr>\n          <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"341\">\n                    <tbody><tr>\n                      <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=13689eccb&idc=09404866f48\" target=\"_blank\"><span class=\"bonbon\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/bonbon.jpg\" alt=\"Bonbon Eau de parfum 30ml 59€\" style=\"display:block;\" class=\"hide\" height=\"381\" border=\"0\" width=\"341\"></span></a></td>\n                    </tr>\n                    <tr>\n                      <td height=\"22\" bgcolor=\"#FFFFFF\" width=\"341\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=23689d902&idc=09404866f48\" target=\"_blank\"><span class=\"givenchy\"></span></a></td>\n                    </tr>\n                  </tbody></table></td>\n                <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=336897293&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/givenchy.jpg\" alt=\"live irressistible Eau de parfum 40ml 65€90\" style=\"display:block;\" height=\"403\" border=\"0\" width=\"309\"></a></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n        <tr>\n          <td><table class=\"hide\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=43689426d&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/clarins_top.jpg\" alt=\"\" style=\"display:block;\" height=\"93\" border=\"0\" width=\"279\"></a></td>\n                <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=53689f8e6&idc=09404866f48\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/soins_selection.jpg\" alt=\"Sélection soins\" style=\"display:block;\" height=\"93\" border=\"0\" width=\"371\"></a></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n        <tr>\n          <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"323\">\n                    <tbody><tr>\n                      <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=636899517&idc=09404866f48\" target=\"_blank\"><span class=\"clarins\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/clarins.jpg\" alt=\"Huile anti eau Flacon 100ml 52€90\" style=\"display:block;\" class=\"hide\" height=\"440\" border=\"0\" width=\"323\"></span></a></td>\n                    </tr>\n                    <tr>\n                      <td height=\"22\" bgcolor=\"#FFFFFF\" width=\"323\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=73689babc&idc=09404866f48\" target=\"_blank\"><span class=\"lauder\"></span></a></td>\n                    </tr>\n                  </tbody></table></td>\n                <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=83689af3e&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/lauder.jpg\" alt=\"Advanced Night Repair Flacon Pipette 30ml 82,50€\" style=\"display:block;\" height=\"462\" border=\"0\" width=\"327\"></a></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n        <tr>\n          <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=936894ecf&idc=09404866f48\" target=\"_blank\"><span class=\"go_site\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/go_site.jpg\" alt=\"Je vais sur nocibe.fr\" style=\"display:block;\" class=\"hide\" height=\"45\" border=\"0\" width=\"650\"></span></a></td>\n        </tr>\n        <tr>\n          <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td class=\"hide\" height=\"210\" bgcolor=\"#FFFFFF\" width=\"56\"></td>\n                <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"552\">\n                    <tbody><tr>\n                      <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=037895b75&idc=09404866f48\" target=\"_blank\"><span class=\"creez_parfum\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/creez_parfum.jpg\" alt=\"Créez votre parfum sur mesure avec notre partenaire UNIQUE - J'EN VEUX UN !\" style=\"display:block;\" class=\"hide\" height=\"191\" border=\"0\" width=\"552\"></span></a></td>\n                    </tr>\n                    <tr>\n                      <td class=\"hide\" height=\"19\" bgcolor=\"#FFFFFF\" width=\"552\"></td>\n                    </tr>\n                  </tbody></table></td>\n                <td class=\"hide\" height=\"210\" bgcolor=\"#FFFFFF\" width=\"42\"></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n        <tr>\n          <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td class=\"hide\" height=\"78\" bgcolor=\"#FFFFFF\" width=\"56\"></td>\n                <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=13789a79d&idc=09404866f48\" target=\"_blank\"><span class=\"liberte\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/liberte.jpg\" alt=\"LIBERTE N55 M'offrir de beaux produits a prix tout doux\" style=\"display:block;\" class=\"hide\" height=\"78\" border=\"0\" width=\"540\"></span></a></td>\n                <td class=\"hide\" height=\"78\" bgcolor=\"#FFFFFF\" width=\"54\"></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n        <tr>\n          <td class=\"hide\" height=\"29\" bgcolor=\"#FFFFFF\" width=\"650\"></td>\n        </tr>\n        <tr>\n          <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td class=\"hide\" height=\"74\" bgcolor=\"#FFFFFF\" width=\"95\"></td>\n                <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"159\">\n                    <tbody><tr>\n                      <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=2378925c6&idc=09404866f48\" target=\"_blank\"><span class=\"bientot_site\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/bientot_site.jpg\" alt=\"A bientot sur nocibe.fr\" style=\"display:block;\" class=\"hide\" height=\"58\" border=\"0\" width=\"159\"></span></a></td>\n                    </tr>\n                    <tr>\n                      <td height=\"16\" bgcolor=\"#FFFFFF\" width=\"159\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=33789c178&idc=09404866f48\" target=\"_blank\"><span class=\"bientot_parf\"></span></a></td>\n                    </tr>\n                  </tbody></table></td>\n                <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=43789588c&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/bientot_parf.jpg\" alt=\"ou dans votre parfumerie nocibe\" style=\"display:block;\" height=\"74\" border=\"0\" width=\"286\"></a></td>\n                <td class=\"hide\" height=\"74\" bgcolor=\"#FFFFFF\" width=\"110\"></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n      </tbody></table></td>\n  </tr>\n  <tr>\n    <td><table class=\"w320\" align=\"center\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n        <tbody><tr>\n          <td class=\"hide\" height=\"38\" bgcolor=\"#FFFFFF\" width=\"650\"></td>\n        </tr>\n        <tr>\n          <td><span class=\"mes_services\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/mes_services.jpg\" alt=\"Mes services exclusifs\" style=\"display:block;\" class=\"hide\" height=\"25\" border=\"0\" width=\"650\"></span></td>\n        </tr>\n        <tr>\n          <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td><table align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"192\">\n                    <tbody><tr>\n                      <td><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=5378992ae&idc=09404866f48\" target=\"_blank\"><span class=\"retours\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/retours.jpg\" alt=\"Retours faciles\" style=\"display:block;\" class=\"hide\" height=\"57\" border=\"0\" width=\"192\"></span></a></td>\n                    </tr>\n                    <tr>\n                      <td height=\"8\" bgcolor=\"#FFFFFF\" width=\"192\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=6378956b4&idc=09404866f48\" target=\"_blank\"><span class=\"echantillons_off\"></span></a></td>\n                    </tr>\n                    <tr>\n                      <td height=\"7\" bgcolor=\"#FFFFFF\" width=\"192\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=737899ee0&idc=09404866f48\" target=\"_blank\"><span class=\"liv_offerte\"></span></a></td>\n                    </tr>\n                  </tbody></table></td>\n                <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=83789c8ab&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/echantillons_off.jpg\" alt=\"Echantillons offerts\" style=\"display:block;\" height=\"72\" border=\"0\" width=\"221\"></a></td>\n                <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=937899387&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/liv_offerte.jpg\" alt=\"Livraison offerte des 60€ d'achats\" style=\"display:block;\" height=\"72\" border=\"0\" width=\"237\"></a></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n        <tr>\n          <td><span class=\"f_sepa\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/f_sepa.jpg\" alt=\"\" style=\"display:block;\" class=\"hide\" height=\"9\" border=\"0\" width=\"650\"></span></td>\n        </tr>\n        <tr>\n          <td><table class=\"w320\" align=\"left\" bgcolor=\"#f3f3f3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n              <tbody><tr>\n                <td><span class=\"retrouvez_nous\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/retrouvez_nous.jpg\" alt=\"Retrouvez nous sur\" style=\"display:block;\" class=\"hide\" height=\"42\" border=\"0\" width=\"192\"></span></td>\n                <td><span class=\"facebook\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/facebook.jpg\" alt=\"\" style=\"display:block;\" class=\"hide\" height=\"42\" border=\"0\" width=\"30\"></span></td>\n                <td><span class=\"twitter\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/twitter.jpg\" alt=\"\" style=\"display:block;\" class=\"hide\" height=\"42\" border=\"0\" width=\"29\"></span></td>\n                <td><span class=\"youtube\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/youtube.jpg\" alt=\"\" style=\"display:block;\" class=\"hide\" height=\"42\" border=\"0\" width=\"33\"></span></td>\n                <td><span class=\"appli\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/appli.jpg\" alt=\"\" style=\"display:block;\" class=\"hide\" height=\"42\" border=\"0\" width=\"26\"></span></td>\n                <td class=\"hide\" height=\"42\" bgcolor=\"#FFFFFF\" width=\"74\"></td>\n                <td class=\"hide\"><a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=0388993c5&idc=09404866f48\" target=\"_blank\"><img src=\"http://e.fipe457.fr/img/15805.160405.2053/loca.jpg\" alt=\"Votre parfumerie en 2 clics\" style=\"display:block;\" class=\"hide\" height=\"42\" border=\"0\" width=\"266\"></a></td>\n              </tr>\n            </tbody></table></td>\n        </tr>\n        <tr>\n          <td class=\"w320\" height=\"21\" bgcolor=\"#FFFFFF\" width=\"650\"><span class=\"loca\"></span></td>\n        </tr>\n      </tbody></table></td>\n  </tr>\n  <tr>\n    <td><table class=\"w320\" align=\"left\" bgcolor=\"#F3F3F3\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"650\">\n        <tbody><tr>\n          <td class=\"w10\" height=\"152\" width=\"44\"></td>\n          <td style=\"font-family:Arial, Helvetica, sans-serif; font-size:11px; color:#383737;\" class=\"w300\" height=\"152\" valign=\"top\" width=\"576\"><br>\n            *Offre non cumulable, valable 1 fois du 6 au 12 Avril 2016 \nsur présentation de cet email dans les parfumeries participantes et sur \nnocibe.fr avec le code NOCIBE20 ou NOCIBE25 à saisir au panier. Ces \nremises ne sont pas valables sur les cartes cadeaux, les cartes \ninstitut, emballages payants, Parfum Unique, Petits prix, Points rouges,\n Prix ronds, emballages payants, prix canon, promotions en cours et \nsoldes. Hors DOM-TOM et Monaco. Non cumulables avec d’autres offres \npromotionnelles en cours. Offres valables uniquement sur présentation ou\n pour toute souscription de la carte de fidélité Nocibé. Lille sous le \nnuméro 384 970 786.<br>\n            <br>\n           \n          </td><td class=\"w10\" height=\"152\" width=\"30\"></td>\n        </tr>\n      </tbody></table></td>\n  </tr>\n</tbody></table>\n<img src=\"http://e.fipe457.fr/img/15805.160405.2053/a.gif\" alt=\"\" style=\"border:0px;\" height=\"1\" border=\"0\" width=\"1\">\n\n<div align=\"center\">\n<font color=\"#555555\" style=\"font-size:10px;font-family:Verdana,Arial,Helvetica,sans-serif;\">\n<img border=\"0\" hspace=\"0\" vspace=\"0\" width=\"1\" height=\"1\" src=\"http://p-eu.acxiom-online.com/pixel/ema?eml=45cb6dc134a75d2fb5ad157adc22ebfc&ha=md5&pid=6346&t=634601&sc=CVIP&ot=pixel\" />\n<IMG HEIGHT=1 WIDTH=1 SRC=\"http://canalvip.biz/ert/?e=dev@caliopen.local&pf=X\">\n<img src=\"http://dataanalyticsserver.com/p?f=gif&idp=53dd9f93b13a510f&xi.e.md5=45cb6dc134a75d2fb5ad157adc22ebfc&xi.e.sha1=&n.v.e=1\" alt=\"\" width=\"1\" height=\"1\" style=\"display: none;\" />\n<img src=\"http://ext.adleadevent.com/adtckrtg.php?ids=35&hash=45cb6dc134a75d2fb5ad157adc22ebfc\" /> \n<b>Vous bénéficiez de cette offre car vous etes inscrit sur notre reseau partenaire</b><br>\nEn tant que membre, vous bénéficiez d'offres spéciales, de bons plans, <br>\nde code promotion, d'enquetes rémunérées, d'annonces et plus<br>\nÀ tout moment, vous disposez d'un droit d'accès, de modification, de rectification et de suppression<br>\n de vos données personnelles (art 34 de la loi Informatique et Libertés du 6 Janvier1978).\n<br>\nPour cesser de recevoir nos informations sur l'adresse dev@caliopen.local, <a  href=\"http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idl=13889a401&idc=09404866f48\">d&eacute;sabonnez-vous</a>\n</font>\n</div><img height='1' width='1' border='0' alt='' src='http://e.fipe457.fr/tr/?v=2&idi=4386698467041&idm=3231b048&idc=09404866f48'></body></html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_199.argentina:2,",
    "content": "Return-Path: <calderon71@ljrtrust.org>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id E705620A559A7\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 22:15:51 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid DC8153317708; Sat,  9 Apr 2016 22:15:51 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid CC0EE3317735; Sat,  9 Apr 2016 22:15:51 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.8 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BRBL_LASTEXT,SPF_HELO_SOFTFAIL\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 0.9993]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [50.62.160.115 listed in bb.barracudacentral.org]\n\t*  0.7 SPF_HELO_SOFTFAIL SPF: HELO does not match SPF record (softfail)\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\nX-Spam-DCC: : \nReceived: from ljrtrust.org (p3nwvpweb101.shr.prod.phx3.secureserver.net [50.62.160.115])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id C63613317708\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 22:15:46 +0200 (CEST)\nDate: Sat, 9 Apr 2016 20:15:42 +0000\nFrom: calderon71@ljrtrust.org\nReply-To: <kitty@dating2mail.com>\nMessage-ID: <e843f07-4721e-c2@ljrtrust.org>\nTo: dev@caliopen.local\nSubject: Welcome\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject:  Welcome\nContent-Length: 210\nLines: 2\n\n\n<div>Welcome, I'm a lone pretty babe, looking real man friend to have a fun with :) <br> Where are you from, do you speak english? <br> <br> reply me if you would like to see my photos. See you later ;D</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_201.argentina:2,",
    "content": "Return-Path: <changl@dunhamwilcox.net>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 6B88E20A51BF7\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 22:14:49 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 5E06B3317708; Sat,  9 Apr 2016 22:14:49 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 4F9C73317735; Sat,  9 Apr 2016 22:14:49 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *****\nX-Spam-Status: Yes, score=5.5 required=5.0 tests=BAYES_60,HTML_MESSAGE,\n\tMIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tRCVD_IN_DNSWL_NONE autolearn=spam version=3.3.2\nX-Spam-Report: \n\t* -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no\n\t*      trust\n\t*      [211.133.134.88 listed in list.dnswl.org]\n\t*  1.5 BAYES_60 BODY: Bayes spam probability is 60 to 80%\n\t*      [score: 0.6201]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\nX-Spam-DCC: : \nReceived: from sv38.wadax.ne.jp (sv38.wadax.ne.jp [211.133.134.88])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id A69D73317708\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 22:14:45 +0200 (CEST)\nReceived: (qmail 50699 invoked by uid 21885); 09 Apr 2016 20:07:10 -0000\nDate: 09 Apr 2016 20:07:10 -0000\nMessage-ID: <20160409200710.50699.qmail@mxed.sv38.wadax.ne.jp>\nFrom: \"Dagny\" <changl@dunhamwilcox.net>\nTo: \"laurent\" <dev@caliopen.local>\nSubject: I got your contact from Isabelle message 1.806.391.11.74\nMime-Version: 1.0\nContent-Type: text/html\nContent-Transfer-Encoding: 8bit\nMime-Version: 1.0\nContent-Type: text/html\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: I got your contact from Isabelle message 1.806.391.11.74\nContent-Length: 368\nLines: 4\n\n<html><body>I haven't got your last message.<br>\nWill you repeat it once more?<br>\n<a href=\"http://meridian-mkt.com/wp-content/uploads/2002/\">Here you can find</a> a few of my provocative pictures by the way.<br>\nI'm 20 y.o. brunette, we could <a href=\"http://meridian-mkt.com/wp-content/uploads/2002/\">jump into my bad</a> sms right now 1.518.289.47.58</body></html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_205.argentina:2,",
    "content": "Return-Path: <alvertaparkso@yori-fluteworld.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 416E020AAD2AF\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 21:12:05 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 2FE3C33168E7; Sat,  9 Apr 2016 21:12:05 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 214423318E81; Sat,  9 Apr 2016 21:12:05 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.8 required=5.0 tests=BAYES_99,DATE_IN_FUTURE_03_06,\n\tHS_INDEX_PARAM,HTML_MESSAGE,MIME_HTML_ONLY autolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 0.9937]\n\t*  3.0 DATE_IN_FUTURE_03_06 Date: is 3 to 6 hours after Received: date\n\t*  0.6 HS_INDEX_PARAM URI: Link contains a common tracker pattern.\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\nX-Spam-DCC: : \nReceived: from weblinux.odei.es (weblinux.odei.es [198.27.87.11])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id 17D6E331772F\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 21:12:01 +0200 (CEST)\nReceived: (qmail 51683 invoked by uid 44099); 10 Apr 2016 00:11:58 -0000\nDate: 10 Apr 2016 00:11:58 -0000\nMessage-ID: <20160410001158.51683.qmail@weblinux.odei.es>\nFrom: \"Lauryn Blan\" <alvertaparkso@yori-fluteworld.com>\nTo: \"laurent\" <dev@caliopen.local>\nSubject: Here's pussycat 513-685-9097 who detests boredom and loves men! You understand what I mean.\nMime-Version: 1.0\nContent-Type: text/html\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Here's pussycat 513-685-9097 who detests boredom and loves men! You understand what I mean.\nContent-Length: 1045\nLines: 2\n\n<html><body><a href=\"http://www.utulekprimetice.cz/wp-includes/SimplePie/XML/Declaration/index.php?qvvko2r2kvphlhuorjsj4ig3tq\">I'm young, loveable</a>, and men are all over me wherever I go.<br>So why the hell am I bored like this now?<br>Hey there, lad, looks like it's your lucky time.<br>There's an amazing  girl out there. She's in the mood for something, got it?<br>She even has <a href=\"http://www.utulekprimetice.cz/wp-includes/SimplePie/XML/Declaration/index.php?qvvko2r2kvphlhuorjsj4ig3tq\">fiery pictures</a> � you must have been a good lad last year.<br>Though I to tell the truth prefer bad boys lol.<br>Whatever, <a href=\"http://www.utulekprimetice.cz/wp-includes/SimplePie/XML/Declaration/index.php?qvvko2r2kvphlhuorjsj4ig3tq\">check out my pictures</a>, let's chat and see where it takes us! <a href=\"http://www.utulekprimetice.cz/wp-includes/SimplePie/XML/Declaration/index.php?qvvko2r2kvphlhuorjsj4ig3tq\">http://www.utulekprimetice.cz/wp-includes/SimplePie/XML/Declaration/index.php?\n qvvko2r2kvphlhuorjsj4ig3tq</a></body></html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_207.argentina:2,",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 3657920A51C16\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 20:19:05 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 2420E33168E7; Sat,  9 Apr 2016 20:19:05 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 0FD533318E81; Sat,  9 Apr 2016 20:19:05 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.4 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_06_12,HTML_IMAGE_RATIO_02,HTML_MESSAGE,MPART_ALT_DIFF,\n\tNORMAL_HTTP_TO_IP,RCVD_IN_CSS,RDNS_NONE,T_KHOP_FOREIGN_CLICK autolearn=spam\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.5 DATE_IN_PAST_06_12 Date: is 6 to 12 hours before Received: date\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [63.251.255.134 listed in zen.spamhaus.org]\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t* -0.6 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server102.offerprivee.in (unknown [63.251.255.134])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 195BE331772F\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 20:19:00 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: =?UTF-8?B?RW5maW4gdW5lIG11dHVlbGxlIGFkYXB0w6llIMOgIHZvdHJlIGJ1ZGdldA==?=\nMessage-ID: <4b4163ce5021b25f829d1a84190dddd9@162.244.67.108>\nDate: Sat, 09 Apr 2016 04:06:56 -0500\nFrom: \"Groupe France Mutuelle\" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 164,165\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11220>\nX-Mailer-SID: 11220\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_d33e62ddf97f763b7e55261696010c6b\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?UTF-8?B?RW5maW4gdW5lIG11dHVlbGxlIGFkYXB0w6llIMOgIHZvdHJlIGJ1ZGdldA==?=\nContent-Length: 10071\nLines: 239\n\n--b1_d33e62ddf97f763b7e55261696010c6b\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11220&L=165&N=255\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11220\n\n--b1_d33e62ddf97f763b7e55261696010c6b\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><meta http-equiv=\"Content-Type\"\ncontent=\"text/html; charset=utf-8\" />\n<title>La mutuelle &eacute;volutive</title>\n<!--Don t remove the next meta-->\n<meta name=\"pdlbat1tkn\" content=\"XCeEeGNT\" />\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11220&L=165&N=255\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"font-family: Arial, sans-serif; font-size: 12px; border: 1px solid\n#cacaca; width: 600px;\">\n<tbody>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"147\" height=\"121\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_01.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"147\"\nheight=\"121\" border=\"0\" /> </a></td>\n<td width=\"190\" height=\"121\" align=\"center\"\nstyle=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_02.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"190\"\nheight=\"121\" border=\"0\" /> </a></td>\n<td width=\"145\" height=\"121\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_03.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"145\"\nheight=\"121\" border=\"0\" /> </a></td>\n<td width=\"118\" height=\"121\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_04.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"118\"\nheight=\"121\" border=\"0\" /> </a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"147\" height=\"110\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_05.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"147\"\nheight=\"110\" border=\"0\" /> </a></td>\n<td width=\"190\" height=\"110\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_06.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"190\"\nheight=\"110\" border=\"0\" /> </a></td>\n<td width=\"145\" height=\"110\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_07.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"145\"\nheight=\"110\" border=\"0\" /> </a></td>\n<td width=\"118\" height=\"110\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_08.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"118\"\nheight=\"110\" border=\"0\" /> </a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"147\" height=\"178\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_09.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"147\"\nheight=\"178\" border=\"0\" /> </a></td>\n<td width=\"190\" height=\"178\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_10.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"190\"\nheight=\"178\" border=\"0\" /> </a></td>\n<td width=\"145\" height=\"178\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_11.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"145\"\nheight=\"178\" border=\"0\" /> </a></td>\n<td width=\"118\" height=\"178\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_12.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"118\"\nheight=\"178\" border=\"0\" /> </a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"147\" height=\"240\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_13.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"147\"\nheight=\"240\" border=\"0\" /> </a></td>\n<td width=\"190\" height=\"240\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_14.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"190\"\nheight=\"240\" border=\"0\" /> </a></td>\n<td width=\"145\" height=\"240\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_15.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"145\"\nheight=\"240\" border=\"0\" /> </a></td>\n<td width=\"118\" height=\"240\" style=\"background-color:#cacaca;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\ntarget=\"_blank\"> <img style=\"display:block;\"\nsrc=\"http://d31epa2i3egyy4.cloudfront.net/fmutu/270315/fmutu_270315_16.jpg\"\nalt=\"La mutuelle qui s&acute;adapte &agrave; vos besoins!\" width=\"118\"\nheight=\"240\" border=\"0\" /> </a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"600\" height=\"160\" style=\"background-color:#FFFFFF;\"\nalign=\"center\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\nstyle=\"font-family:Arial, sans-serif;\ntext-decoration:none;font-size:30px;color:#FFF;background-color:#b12315;padding:25px\n30px;border-radius:8px;\">Ma Simulation gratuite &gt;</a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"540\" height=\"#\"\nstyle=\"background-color:#f3f3f3;padding-left:30px;padding-right:30px;padding-bottom:40px;padding-top:30px;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11220&L=788&F=H\"\nstyle=\"text-decoration:none; text-align:center;\" target=\"_blank\"> <font\nsize=\"2\" style=\"font-family:Arial,Sans-Serif; font-size:12px;\ncolor:#333333;\"> <span style=\" font-size:22px;\ncolor:#ff6d19;\">Disponibilit&eacute; des &eacute;quipes</span> <br /> Nos\n&eacute;quipes sont form&eacute;es pour &ecirc;tre &agrave; votre\n&eacute;coute au quotidien. Nous avons &agrave; c&oelig;ur de vous apporter\nun soutien efficace, pas seulement sur le plan mat&eacute;riel.<br /><br />\n<span style=\" font-size:22px; color:#09b4ae;\">Qualit&eacute; des\nservices</span><br /> Nous pla&ccedil;ons vos besoins au c&oelig;ur de nos\npr&eacute;occupations. Nous sommes fiers de vous proposer <br />des\nprotections sant&eacute; innovantes, p&eacute;rennes, adapt&eacute;es\n&agrave; vos besoins.<br /><br /> <span style=\" font-size:22px;\ncolor:#39cbe2;\">Relation de confiance</span><br /> Nous nous attachons\n&agrave; construire avec vous une relation de confiance sur le long terme\nafin de mieux r&eacute;pondre &agrave; vos attentes. </font> </a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n</tbody>\n</table>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11220\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table><img\nsrc=\"http://162.244.67.108/~client/open.php?M=11664742&L=165&N=11220&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_d33e62ddf97f763b7e55261696010c6b--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_209.argentina:2,",
    "content": "Return-Path: <EasyShedPlans@quafter.top>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 8D9FD20A367C3\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 19:55:05 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 7ADFF331772F; Sat,  9 Apr 2016 19:55:05 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 6752D3318E81; Sat,  9 Apr 2016 19:55:05 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.4 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tINVALID_DATE,RCVD_IN_CSS,RDNS_NONE autolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.1 INVALID_DATE Invalid Date: header (not RFC 2822)\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [5.187.33.193 listed in zen.spamhaus.org]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\nX-Spam-DCC: : \nReceived: from 006d4722.quafter.top (unknown [5.187.33.193])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id EBC79331772F\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 19:55:01 +0200 (CEST)\nReceived: from 006d4722.zdbp6.quafter.top ([127.0.0.1]:2761 helo=zdbp6.quafter.top)\n\tby zdbp6.quafter.top with ESMTP id 00AM6D47WF22;\n\tfor <dev@caliopen.local>; Sat, 9 Apr 2016 19:55:00 00200\nMessage-ID: <7613237160873761173223058978@zdbp6.quafter.top>\nDate: Sat, 9 Apr 2016 19:55:00 00200\nTo: <dev@caliopen.local>\nFrom: \"Easy Shed Plans\" <EasyShedPlans@quafter.top>\nSubject: Ever wanted to create Woodworking Projects easily and quickly?\nContent-Language: en-us\nMIME-Version: 1.0\nContent-Transfer-Encoding: 8bit\nContent-Type: multipart/alternative;\n\tboundary=\"----=Part.179.2827.1460224500\"\nX-Spam-Prev-Subject: Ever wanted to create Woodworking Projects easily and quickly?\nContent-Length: 10813\nLines: 312\n\n------=Part.179.2827.1460224500\nContent-Transfer-Encoding: 8bit\nContent-Type: text/plain; charset=\"UTF-8\"\n\nIntroducing...\nMyShedPlan\nThe Ultimate Collection of 12,000 Shed Plans!\nhttp://www.quafter.top/761-22-173-7160873/laurent/tindex25.html\n\nNow with hundreds of shed designs, plans, blueprints for the hobbyist and professional alike\n\nPacked with new ideas for everything from small clock housings up to an entire stable\n\nOver 12000 design projects and woodwork plans included for the avid woodworking fan.\n\nTons of great shed plans projects to complete over the holiday / weekend with your family\n\nMaterials lists provided so you'll know exactly what to buy. No more wasting money buying the wrong materials\n\nComprehensive \"How-To\" woodworking guide and course. ( worth $147 alone )\n\nHow-to information on home improvement, detailed plans and instructions for woodworking projects\n\nAll the planning done for you... so you never have to worry when you start building your first shed.\nhttp://www.quafter.top/761-22-173-7160873/laurent/tindex25.html\n\n\n\n\nP.S: These are some great sheds. Do let me know which of these is your favorite.\nI personally love\n\nBen's Desert Oasis design!\n\n==>See Great Sheds Here\nhttp://www.quafter.top/761-22-173-7160873/laurent/tindex25.html\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThe Massage is Not Intended For Canada Residents,\nIf you no longer wish to receive correspondence or special offers from us,\nplease use this link to optout \nhttp://www.quafter.top/761-22-173-7160873/laurent/tindex15.html\n\n\n\n\n\n\n\n\n\n\nsize /pessoal /3D /3D3Vamos /ab /all /Volgen /review /PERLEZ /E7s /clear /pieces page /message /10pt /sendo3Dsize /lucida /please /0pt /bring /khtml /___ /10. LsRegisterButtonHov /employee /20 /changing /Swedish /bestseller /fazem aparece /personal-compose /accept /msotype /3Dsize /250cc /Eeeeba /125cc Result /ISO-8859-1 /Enter /ampreceived /xfy /charset /at /course /About /Marlon squad /rolando /Dsize /confirmed /ampDsize /cQguJMarlon /Terrorism /wrote boozing /Meets /onthisday /absmiddle /lol /questions /access? verkar /IRL /achter /IHMMHHHHHHIHIIIIMHMH /background-color /Helena /3D MMMMHHMHIIMMHHHHHII /anren /uppgifter /Arial /138px /10 /0ptfamily fala /Shopping /po /observera /gracias /REC0 /putatively /xCanada /3D /xmlns ctl00_footer_NavWebPart_FooterTorontoStar_ctl07___Url__ /candidates /zsufl WTV /bleiben /2-7th /fallout /CDC /exit /alert /civilizing /blnpapvyffl /Hello dreaded /prohibited /Offerte /famoso /pergamon /bunkum /quotHAY /manicurist 54 /link /00 /dat /please /00-11\n  /2008 /det /applications /xbbkalwbiuti /12 /10 celebrates /Muriel /optimum /Fi /boxshots /alerts /padding /book /Prints /thank unter /chen /comms /mmorpg functon /lla /nimo /3122 /regarding /founded /komen /UL /classifieds /Me unter /ending /borrado /alpha /Pawlenty /functon /contribution /architecture saldo /sas /reproduce /phoenix Thursday /Dope /morning /attend /nouvelles /think /recover /extranet /koor /ping skype /mode /amphibious /asma /Class /alternate /educated /midcontinent emailed /forestalls /eighth renaissance /sse /checked /repented /camouflage /boucherville /functon TREME /cliquer /nimo /transmissions /AMSTERDAM /directv /founded /leibenluft ending /joining /tablets /brooke /psz /cookpad Yesterday /Lentz /Sinks /blah /einige /4678 /June /unchanged /acqui /world broke /ben /fighters /closures /zugreifen /significant /maru /assortiment /whenever uhr /obsoleted /organizers /sleeping /network /thailand /ties /alles /e Please /Grifth /default /tard /09 /ban /GAO /round /au\n thors /access /27 123456789 /AR2007062200995 /mimartin /TRANSLATE /Renard /915482376 SMARTPHONES /767 /stewed /Michel /dank /D'AU /changed /kickin /overseeing /Lago\n\n\n------=Part.179.2827.1460224500\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/html; charset=\"UTF-8\"\n\n<html>\n\n<head></head>\n\n<body>\n\n<div align=3D\"center\" title=3D\"6px\" id=3D\"INHERIT\" name=3D\"11px\">\n\n\n\n<div align=3D\"center\" style=3D\"border: 7px hidden #ffffff; width: 665px; ba=\nckground-color: #ffffff\">\n\n<div align=3D\"center\" >\n<table style=3D\"padding-top: 18px;width:450px;\">\n<tr>\n<td align=3D\"left\" >\n<a href=3D\"http://www.quafter.top/761-22-173-7160873/laurent/tindex16.html\"=\n style=3D\"font-size:30px;font-family: Calibri; text-decoration:none;color:#=\n000000;\">\n<b>Introducing...<b>\n</a>\n</td>\n</tr>\n<td align=3D\"left\" >\n<a href=3D\"http://www.quafter.top/761-22-173-7160873/laurent/tindex16.html\"=\n style=3D\"font-size:95px;font-family: Arial; text-decoration:none;color:#C4=\n3132;\">\n<strong>MyShedPlan<strong>\n</a>\n</td>\n</tr>\n\n<td align=3D\"left\"  >\n<a href=3D\"http://www.quafter.top/761-22-173-7160873/laurent/tindex16.html\"=\n style=3D\"font-size:20px;font-family: Arial black; text-decoration:none;col=\nor:#000000;\">\n<strong>   The  Ultimate  Collection  of <span style=3D\"background-color:#F=\nDE270;\"> 12,000  Shed  Plans!</span><strong>\n</a>\n</td>\n</tr>\n<tr>\n<td align=3D\"left\">\n<a href=3D\"http://www.quafter.top/761-22-173-7160873/laurent/tindex16.html\"=\n>\n\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img022173323.jpg\" a=\nlt=3D\"   \" border=3D\"0\"  >\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img122173323.jpg\" a=\nlt=3D\"   \"  ></a><br>\n<br>\n</td>\n</tr>\n<tr>\n<td><img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img222173323.jp=\ng\" alt=3D\"   \"  > =20\n<b>Now with hundreds of shed designs, plans, blueprints for the hobbyist an=\nd professional alike</b>\n\n\n=09<br>\n=09<br>\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img222173323.jpg\" a=\nlt=3D\"   \"  >=20\nPacked with new ideas for everything from small clock housings up to an ent=\nire stable\n\n\n=09=09<br>\n=09=09<br>\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img222173323.jpg\" a=\nlt=3D\"   \"  >=20\n<b>Over <u>12000</u> design projects and woodwork plans included for the av=\nid woodworking fan.</b>\n\n\n=09=09<br>\n=09=09<br>\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img222173323.jpg\" a=\nlt=3D\"   \"  >=20\nTons of great shed plans projects to complete over the holiday / weekend wi=\nth your family\n\n=09<br>\n=09<br>\n=09\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img222173323.jpg\" a=\nlt=3D\"   \"  >=20\n<b>Materials lists provided so you'll know exactly what to buy. No more was=\nting money buying the wrong materials</b>\n=09<br>\n=09<br>\n\n=09\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img222173323.jpg\" a=\nlt=3D\"   \"  >=20\nComprehensive \"How-To\" woodworking guide and course.\n<i>( worth $147 alone )</i>\n\n=09<br>\n=09<br>\n=09\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img222173323.jpg\" a=\nlt=3D\"   \"  >=20\n<b>How-to information on home improvement, detailed plans and instructions =\nfor woodworking projects</b>\n\n=09<br>\n=09<br>\n=09\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img222173323.jpg\" a=\nlt=3D\"   \"  >=20\n<i><a href=3D\"http://www.quafter.top/761-22-173-7160873/laurent/tindex16.ht=\nml\" style=3D\"color:#000000;\"><u>All the planning done for you... </u></a></=\ni>so you never have to worry when you start building your first shed.\n=09<br>\n=09<br>=09<br><br><br><br>\n=09<b> P.S:</b> These are some great sheds.=20\n=09Do let me\nknow which of these is your favorite. <br>I personally\nlove<br> <br><b>Ben's Desert Oasis design!</b><br>\n<br>\n\n<b><a href=3D\"http://www.quafter.top/761-22-173-7160873/laurent/tindex16.ht=\nml\" style=3D\"color:#000080;\">=3D=3D>See Great Sheds Here</a></b><br>\n<br>\n\n=09<br><br>\n</div>\n</td>\n</tr>\n<tr>\n<td align=3D\"center\">\n<a href=3D\"http://www.quafter.top/761-22-173-7160873/laurent/tindex16.html\"=\n>\n<img src=3D\"http://www.quafter.top/761-22-173-7160873/i/img1022173323.gif\" =\nalt=3D\"   \" border=3D\"0\"></a><br>\n<br>\n</td>\n</tr>\n</table>\n<TR><TD></TD></TR><TR><TD></TD></TR><TR><TD></TD></TR><TR><TD></TD></TR><TR=\n><TD></TD></TR><TR><TD></TD></TR>\n\n\n</div>\n<br><br><br><br>\n\n<div>\n\n\n<center>\n<div style=3D\"font-size:12px;\">The Massage is Not Intended For Canada Resid=\nents,<br>\nIf you no longer wish to receive correspondence or special offers from us,<=\nbr>\nplease use this <a href=3D\"http://www.quafter.top/761-22-173-7160873/lauren=\nt/tindex8.html\">link</a> to optout or mail me: #1014, Mainstreet  Sittard-G=\neleen, Limburg</div>\n</center>\n<br>\n\n<br>\n\n\n\n</div>\n\n</div>\n<table style=3D\"width: 620px; background-color: #FFFFFF; border: 5px solid =\nwhite;\">\n<tr>\n<td>\n<div id=3D\"random\" name=3D\"random\" title=3D\"random\" style=3D\"color: #FFFFFF=\n; font: Georgia 4pt; border: 1px;\">\n<p align=3D\"center\" style=3D\"font: 9px;\"></p>\n<BR /><span style=3D\"font-family: Helvetica, Arial, sans-serif; font-size: =\n7px;\"></span>\n<p>\n\n\nsize /pessoal /3D /3D3Vamos /ab /all /Volgen /review /PERLEZ /E7s /clear /p=\nieces\npage /message /10pt /sendo3Dsize /lucida /please /0pt /bring /khtml /___ /1=\n0.\nLsRegisterButtonHov /employee /20 /changing /Swedish /bestseller /fazem\naparece /personal-compose /accept /msotype /3Dsize /250cc /Eeeeba /125cc\nResult /ISO-8859-1 /Enter /ampreceived /xfy /charset /at /course /About /Ma=\nrlon\nsquad /rolando /Dsize /confirmed /ampDsize /cQguJMarlon /Terrorism /wrote\nboozing /Meets /onthisday /absmiddle /lol /questions /access?\nverkar /IRL /achter /IHMMHHHHHHIHIIIIMHMH /background-color /Helena /3D\nMMMMHHMHIIMMHHHHHII /anren /uppgifter /Arial /138px /10 /0ptfamily\nfala /Shopping /po /observera /gracias /REC0 /putatively /xCanada /3D /xmln=\ns\nctl00_footer_NavWebPart_FooterTorontoStar_ctl07___Url__ /candidates /zsufl\nWTV /bleiben /2-7th /fallout /CDC /exit /alert /civilizing /blnpapvyffl /He=\nllo\ndreaded /prohibited /Offerte /famoso /pergamon /bunkum /quotHAY /manicurist\n54 /link /00 /dat /please /00-11 /2008 /det /applications /xbbkalwbiuti /12=\n /10\ncelebrates /Muriel /optimum /Fi /boxshots /alerts /padding /book /Prints /t=\nhank\nunter /chen /comms /mmorpg\nfuncton /lla /nimo /3122 /regarding /founded /komen /UL /classifieds /Me\nunter /ending /borrado /alpha /Pawlenty /functon /contribution /architectur=\ne\nsaldo /sas /reproduce /phoenix\nThursday /Dope /morning /attend /nouvelles /think /recover /extranet /koor =\n/ping\nskype /mode /amphibious /asma /Class /alternate /educated /midcontinent\nemailed /forestalls /eighth\nrenaissance /sse /checked /repented /camouflage /boucherville /functon\nTREME /cliquer /nimo /transmissions /AMSTERDAM /directv /founded /leibenluf=\nt\nending /joining /tablets /brooke /psz /cookpad\nYesterday /Lentz /Sinks /blah /einige /4678 /June /unchanged /acqui /world\nbroke /ben /fighters /closures /zugreifen /significant /maru /assortiment /=\nwhenever\nuhr /obsoleted /organizers /sleeping /network /thailand /ties /alles /e\nPlease /Grifth /default /tard /09 /ban /GAO /round /authors /access /27\n123456789 /AR2007062200995 /mimartin /TRANSLATE /Renard /915482376\nSMARTPHONES /767 /stewed /Michel /dank /D'AU /changed /kickin /overseeing /=\nLago\n\n=20\n=20\n</p>\n</div>\n</td>\n</tr>\n</table>\n\n\n</body>\n</html>\n\n------=Part.179.2827.1460224500--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_211.argentina:2,",
    "content": "Return-Path: <info@my.newspartner3.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id C44FF205C3AD8\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 19:25:46 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid B5CAA33168E7; Sat,  9 Apr 2016 19:25:46 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid A77093318E81; Sat,  9 Apr 2016 19:25:46 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.4 required=5.0 tests=AWL,BAYES_99,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,HTML_MESSAGE,MIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,SPF_HELO_PASS,URIBL_BLACK\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t* -0.0 SPF_HELO_PASS SPF: HELO matches SPF record\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: newspartner3.fr]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.2 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from my.newspartner3.fr (mta3.smtp4.flimta.com [51.255.1.3])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id 2037E331772F\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 19:25:44 +0200 (CEST)\nDomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;\n  s=mail;d=my.newspartner3.fr;\n  h=from:sender:to:date:message-id:subject:mime-version:content-type;\n  b=d3UTdKpCedMe0lwDNceTekNHB+I4UQmwbGmqt3dfdHIS3kz0TsuTemPdkHOAA9oo08JiCd+E\n    NSaUO+O2r2KsBkTj+cH28PAubMc+ByELxKGj8ZZHnWahMzjOIEuI7ZJcPaLN3nY9Nc1lnWcU\n    1mqF+5tlF0UWUjp3PdT5pdFfPAw=\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed;\n  s=mail;d=my.newspartner3.fr;\n  h=from:sender:to:date:message-id:subject:mime-version:content-type;\n  bh=pt2XbDIZH2RY+NxHuL0p+fleyHo=;\n  b=sU3Gs+arhUcJr751GZx5zFbXz/gQIC5MMsDNcgkpCz1cFV4VuacWh7bTewdZUE66QadX2uhZ\n    oF1vNpW74kFtcTAU+U/SylcpYfFjPJZZBVF4A2YtR7uCCGNuzfkxgs1P5imDwqddmlyG6APH\n    eEgDrakxF+4Yo6CCyh2ZYnrX4i4=\nX-Customer-Id: 651\nX-Campaign-Id: 3233\nList-Unsubscribe: http://my.newspartner3.fr/tk/u/1/952891681/3233/2909446/\nFrom: Laboratoire Cell'innov <info@my.newspartner3.fr>\nSender: Laboratoire Cell'innov <info@my.newspartner3.fr>\nReply-To: Laboratoire Cell'innov <info@my.newspartner3.fr>\nTo: dev@caliopen.local\nDate: Sat, 09 Apr 2016 17:26:23 GMT\nOrganization: my.newspartner3.fr\nMessage-ID: <1-952891681-3233-2909446@my.newspartner3.fr>\nSubject: Ce probiotique fait fondre vos kilos en trop\nMIME-Version: 1.0\nX-Mailer: PHPMailer\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: base64\nX-Spam-Prev-Subject: Ce probiotique fait fondre vos kilos en trop\nContent-Length: 20507\nLines: 267\n\n77u/PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEgVHJhbnNpdGlv\nbmFsLy9FTiI+DQo8aHRtbCBsYW5nPSJmciI+DQogICAgPGhlYWQ+DQogICAgICAgIDxtZXRhIGh0\ndHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04\nIj4NCiAgICAgICAgPHRpdGxlPmNlbGwgaW5ub3Y8L3RpdGxlPg0KICAgIDwvaGVhZD4NCiAgICA8\nYm9keT4NCiAgICAgICAgPHRhYmxlIGJvcmRlcj0iMCIgd2lkdGg9IjEwMCUiPg0KICAgICAgICAg\nICAgPHRib2R5Pg0KICAgICAgICAgICAgICAgIDx0cj4NCiAgICAgICAgICAgICAgICAgICAgPHRk\nIGNvbHNwYW49IjMiIHJvd3NwYW49IjEiIHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAxMDIp\nOyBmb250LWZhbWlseTogdmVyZGFuYSxhcmlhbCxzZXJpZjsgZm9udC1zaXplOiAxMnB4OyIgYWxp\nZ249ImNlbnRlciI+PGEgIGhyZWY9Imh0dHA6Ly9teS5uZXdzcGFydG5lcjMuZnIvdGsvdC8xLzk1\nMjg5MTY4MS8zMzM2NC8zMjMzLzI5MDk0NDYvIiB0YXJnZXQ9Il9ibGFuayIgc3R5bGU9ImNvbG9y\nOiByZ2IoMjUxLCAxNTAsIDYpOyBmb250LWZhbWlseTogdmVyZGFuYSxhcmlhbCxzZXJpZjsgZm9u\ndC1zaXplOiAxMnB4OyBmb250LXdlaWdodDogYm9sZDsiPkNlDQogICAgICAgICAgICAgICAgICAg\nIHByb2Jpb3RpcXVlIGZhaXQgZm9uZHJlIHZvcyBraWxvcyBlbiB0cm9wPHNwYW4gaWQ9ImRvY3Mt\naW50ZXJuYWwtZ3VpZC0zMGYwMmVmOS0wYTM1LTMxMjMtMDRmYS00YjlkNDdiZDU3ZTMiPjxzcGFu\nIHN0eWxlPSJmb250LXNpemU6IDEzcHg7IGZvbnQtZmFtaWx5OiBBcmlhbDsgY29sb3I6IHJnYig1\nMSwgNTEsIDI1NSk7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyB2ZXJ0aWNhbC1hbGln\nbjogYmFzZWxpbmU7Ij48L3NwYW4+PC9zcGFuPjwvYT4NCiAgICAgICAgICAgICAgICAgICAgPC90\nZD4NCiAgICAgICAgICAgICAgICA8L3RyPg0KICAgICAgICAgICAgPC90Ym9keT4NCiAgICAgICAg\nPC90YWJsZT4NCiAgICAgICAgPHRhYmxlIHN0eWxlPSJ3aWR0aDogNjA1cHg7IHRleHQtYWxpZ246\nIGxlZnQ7IG1hcmdpbi1sZWZ0OiBhdXRvOyBtYXJnaW4tcmlnaHQ6IGF1dG87IiBib3JkZXI9IjAi\nIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+DQogICAgICAgICAgICA8dGJvZHk+DQog\nICAgICAgICAgICAgICAgPHRyPg0KICAgICAgICAgICAgICAgICAgICA8dGQgY29sc3Bhbj0iMyIg\ncm93c3Bhbj0iMSIgc3R5bGU9InRleHQtYWxpZ246IGNlbnRlcjsgY29sb3I6IHJnYigxMDIsIDEw\nMiwgMTAyKTsgZm9udC1mYW1pbHk6IHZlcmRhbmEsYXJpYWwsc2VyaWY7IGZvbnQtc2l6ZTogMTBw\neDsiPg0KICAgICAgICAgICAgICAgICAgICA8c3BhbiBzdHlsZT0iZm9udC1zaXplOiA4cHQ7IGZv\nbnQtZmFtaWx5OiAmcXVvdDtIZWx2ZXRpY2EmcXVvdDssJnF1b3Q7c2Fucy1zZXJpZiZxdW90Ozsg\nY29sb3I6IGdyYXk7Ij5Wb3VzDQogICAgICAgICAgICAgICAgICAgIHJlY2V2ZXogY2V0IGVtYWls\nIGNhciB2b3VzIGF2ZXogYWNjZXB0w6kgZGUgcmVjZXZvaXIgbGVzDQogICAgICAgICAgICAgICAg\nICAgIG9mZnJlcyBwYXJ0ZW5haXJlcyBkZSA8L3NwYW4+Q29tcHRvaXIgZGUgV2FncmFtPGJyPg0K\nICAgICAgICAgICAgICAgICAgICA8c3BhbiBzdHlsZT0iZm9udC1zaXplOiA4cHQ7IGZvbnQtZmFt\naWx5OiAmcXVvdDtIZWx2ZXRpY2EmcXVvdDssJnF1b3Q7c2Fucy1zZXJpZiZxdW90OzsgY29sb3I6\nIGdyYXk7Ij48L3NwYW4+PGEgc3R5bGU9ImNvbG9yOiByZ2IoMTAyLCAxMDIsIDEwMik7IiAgaHJl\nZj0iaHR0cDovL215Lm5ld3NwYXJ0bmVyMy5mci90ay9tLzEvOTUyODkxNjgxLzQzMzY0LzMyMzMv\nMjkwOTQ0Ni8iPkNvbnN1bHRleg0KICAgICAgICAgICAgICAgICAgICBsYSB2ZXJzaW9uIG1vYmls\nZTwvYT4gJm5ic3A7ICZuYnNwOyZuYnNwOzxzcGFuIHN0eWxlPSJmb250LXNpemU6IDhwdDsgZm9u\ndC1mYW1pbHk6ICZxdW90O0hlbHZldGljYSZxdW90OywmcXVvdDtzYW5zLXNlcmlmJnF1b3Q7OyBj\nb2xvcjogZ3JheTsiPjxhIHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAxMDIpOyIgIGhyZWY9\nImh0dHA6Ly9teS5uZXdzcGFydG5lcjMuZnIvdGsvbS8xLzk1Mjg5MTY4MS81MzM2NC8zMjMzLzI5\nMDk0NDYvIj5Db25zdWx0ZXoNCiAgICAgICAgICAgICAgICAgICAgbGEgdmVyc2lvbiBlbiBsaWdu\nZSBkdSBtZXNzYWdlPC9hPiA8L3NwYW4+PC90ZD4NCiAgICAgICAgICAgICAgICA8L3RyPg0KICAg\nICAgICAgICAgPC90Ym9keT4NCiAgICAgICAgPC90YWJsZT4NCiAgICAgICAgPHRhYmxlIHN0eWxl\nPSJ3aWR0aDogNjA1cHg7IHRleHQtYWxpZ246IGxlZnQ7IG1hcmdpbi1sZWZ0OiBhdXRvOyBtYXJn\naW4tcmlnaHQ6IGF1dG87IiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSI3IiBjZWxsc3BhY2luZz0i\nNSI+DQogICAgICAgICAgICA8dGJvZHk+DQogICAgICAgICAgICAgICAgPHRyPg0KICAgICAgICAg\nICAgICAgICAgICA8dGQ+DQogICAgICAgICAgICAgICAgICAgIDxjZW50ZXI+DQogICAgICAgICAg\nICAgICAgICAgIDxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9o\ndG1sOyBjaGFyc2V0PXV0Zi04Ij4NCiAgICAgICAgICAgICAgICAgICAgPHRpdGxlPkNlIHByb2Jp\nb3RpcXVlIGZhaXQgZm9uZHJlIHZvcyBraWxvcyBlbiB0cm9wPC90aXRsZT4NCiAgICAgICAgICAg\nICAgICAgICAgPGNlbnRlcj4NCiAgICAgICAgICAgICAgICAgICAgPGJyPg0KICAgICAgICAgICAg\nICAgICAgICA8dGFibGUgc3R5bGU9InRleHQtYWxpZ246IGxlZnQ7IGJhY2tncm91bmQtY29sb3I6\nIHJnYigyNTUsIDI1NSwgMjU1KTsgZm9udC1mYW1pbHk6IEFyaWFsLEhlbHZldGljYSxzYW5zLXNl\ncmlmOyBmb250LXNpemU6IDE3cHg7IiBjZWxscGFkZGluZz0iMTAiIHdpZHRoPSI2MDAiPg0KICAg\nICAgICAgICAgICAgICAgICAgICAgPHRib2R5Pg0KICAgICAgICAgICAgICAgICAgICAgICAgICAg\nIDx0cj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPg0KICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICA8Zm9udCBjb2xvcj0iZ3JleSIgc2l6ZT0iMSI+TGFib3JhdG9p\ncmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2VsbCdpbm5vdiAtIGxhIG1pY3Jv\nbnV0cml0aW9uIGF1IGNvZXVyIGRlIHZvcyBjZWxsdWxlcyA8L2ZvbnQ+PGJyPg0KICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICA8YnI+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgIDx0YWJsZSBzdHlsZT0idGV4dC1hbGlnbjogbGVmdDsgYmFja2dyb3VuZC1jb2xvcjogcmdi\nKDI1NSwgMjU1LCAyNTUpOyBmb250LWZhbWlseTogQXJpYWwsSGVsdmV0aWNhLHNhbnMtc2VyaWY7\nIGZvbnQtc2l6ZTogMTdweDsiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIHdpZHRoPSI2MDAi\nPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5Pg0KICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4NCiAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGhlaWdodD0iNjkiIHdpZHRoPSIyNTAiPg0KICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aW1nIGFsdD0iQ2VsbCdp\nbm5vdiIgc3JjPSJodHRwczovL2ltYWdlcy53ZWItcHVyY2hhc2VzLmNvbS9MaWJyYXJ5LTE1MDAx\nNTEvQ0lfbG9nb18yNTAlQzMlOTc2OS5qcGciPjwvdGQ+DQogICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgIDx0ZCB3aWR0aD0iODAiPiZuYnNwOzwvdGQ+DQogICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCB3aWR0aD0iMjYwIj4NCiAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBzdHlsZT0iZm9u\ndC1zaXplOiAxLjJlbTsgY29sb3I6IHJnYigxNTAsIDIwMSwgNjEpOyB0ZXh0LWFsaWduOiBsZWZ0\nOyI+UG91dm9ucy1ub3VzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgIHZvdXMgYWlkZXImbmJzcDs/IDxicj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgMDgwMCZuYnNwOzUwJm5ic3A7MTAmbmJzcDsxNyAoYXBwZWwgZ3JhdHVp\ndCk8L2Rpdj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90\nZD4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPg0KICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4NCiAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgPC90YWJsZT4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nPGhyIHN0eWxlPSJib3JkZXItdG9wOiAycHggc29saWQgcmdiKDE1MCwgMjAxLCA2MSk7IiB3aWR0\naD0iNjAwIj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIHN0eWxlPSJ0\nZXh0LWFsaWduOiBsZWZ0OyBiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMjU1LCAyNTUsIDI1NSk7IGZv\nbnQtZmFtaWx5OiBBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxN3B4OyIg\nYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgd2lkdGg9IjYwMCI+DQogICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICA8dGJvZHk+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgPHRyPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICA8dGQ+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxo\nMiBzdHlsZT0iY29sb3I6IHJnYigyLCA0MiwgNTgpOyI+Q2UNCiAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgcHJvYmlvdGlxdWUgZmFpdCBmb25kcmUgdm9zIGtpbG9z\nIGVuIHRyb3AgKHN1cnRvdXQgYXUgbml2ZWF1IGR1IHZlbnRyZSk8L2gyPg0KICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBWb3VzIGNvbm5haXNzZXogc2FucyBkb3V0\nZSA8dT5jZXMgcGVyc29ubmVzIHF1aSBtYW5nZW50IGNvbW1lDQogICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgIHF1YXRyZSBldCBuZSBwcmVubmVudCBwYXMgdW4gZ3Jh\nbW1lPC91PiBhbG9ycyBxdWUgZCdhdXRyZXMsIG1vaW5zDQogICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgIGNoYW5jZXVzZXMsIHN0b2NrZW50IGxhIG1vaW5kcmUgcGV0\naXRlIGNhbG9yaWUgZGlyZWN0ZW1lbnQgYXUgbml2ZWF1IGR1DQogICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgIHZlbnRyZSwgZGVzIGhhbmNoZXMgb3UgZGVzIGZlc3Nl\ncyBldCBvbnQgdW4gbWFsIGZvdSDDoCBwZXJkcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgZHUgcG9pZHMuIDxicj4NCiAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgPGJyPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICA8c3Ryb25nPlNpIHZvdXMgZmFpdGVzIHBhcnRpZSBkZSBjZXR0ZQ0KICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXV4acOobWUgY2F0w6ln\nb3JpZSBxdWkgZG9pdCBzZSByZXN0cmVpbmRyZSBlbg0KICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICBwZXJtYW5lbmNlIHBvdXIgZXNzYXllciBkZSBnYXJkZXIgbGEg\nbGlnbmUsIGxpc2V6IGJpZW4gY2UgcXVpIHN1aXQuPC9zdHJvbmc+DQogICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxicj4NCiAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgPGJyPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICBWb3VzIGFwcHJlbmRyZXogY29tbWVudCA8dT5pbCBlc3QgcG9zc2libGUg\nZGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcsOpdGFibGly\nIGVuIHBhcnRpZSBjZXR0ZSBpbmp1c3RpY2U8L3U+LCBzYW5zIGVmZm9ydCBldA0KICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW5zIGRldm9pciBzdWl2cmUgZGUg\ncsOpZ2ltZSByZXN0cmljdGlmLiA8YnI+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgIDxicj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgPHN0cm9uZz5EZXMgY2hlcmNoZXVycyBqYXBvbmFpcyBvbnQNCiAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZMOpY291dmVydCBxdSdlbiByw6lhbGl0w6ks\nIHRvdXQgc2Ugam91ZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICBkYW5zIHZvdHJlIGZsb3JlIGludGVzdGluYWxlLjwvc3Ryb25nPiA8YnI+DQogICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxicj4NCiAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2FyIDx1PmNlIHF1aSBzZSBwYXNzZSBkYW5zIHZv\ncyBpbnRlc3RpbnMgZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgIMOpdHJvaXRlbWVudCBsacOpIMOgIHZvdHJlIHRvdXIgZGUgdGFpbGxlPC91Pg0KICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldCDDoCB2b3RyZSBzYW50w6kg\nZW4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ8OpbsOpcmFs\nLiA8YnI+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxicj4N\nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRW4gMjAxMCwgdW5l\nIMOpdHVkZSBhIG1vbnRyw6kgbGUgbGllbiBkaXJlY3QgZW50cmUNCiAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgdW4gcHJvYmlvdGlxdWUsIHVuIG1pY3JvLW9yZ2Fu\naXNtZSBxdWkgc2UgcmV0cm91dmUgZGFucyB2b3RyZSBpbnRlc3RpbiwNCiAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXQgbGEgcGVydGUgZGUgcG9pZHMgY2hleiBk\nZXMgcGVyc29ubmVzIHNvdWZmcmFudCBkZSBzdXJwb2lkcy4gPGJyPg0KICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnI+DQogICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgIDxzdHJvbmc+QXByw6hzIDEyIHNlbWFpbmVzIHNldWxlbWVu\ndCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVzIHBlcnNv\nbm5lcyBxdWkgYXZhaWVudCBwcmlzIGNoYXF1ZSBqb3VyIGNlIGZhbWV1eCBwcm9iaW90aXF1ZSBz\nb3VzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvcm1lIGRl\nIGNvbXBsw6ltZW50IGFsaW1lbnRhaXJlIG9udCB2dSBsZXVycyBraWxvcyBmb25kcmUuPC9zdHJv\nbmc+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxicj4NCiAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGJyPg0KICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbHMgYXZhaWVudCBwZXJkdSBlbiBt\nb3llbm5lIDQsNiUgZGUgZ3JhaXNzZSBhYmRvbWluYWxlIGV0IDMsMyUgZGUNCiAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JhaXNzZXMgc291cy1jdXRhbsOpZXMu\nIExldXIgcG9pZHMgYXZhaXQgZGltaW51w6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgZGUgMSw0JSBldCBsZXVyIHRvdXIgZGUgdGFpbGxlIGRlIDEsOCUuIDxi\ncj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGJyPg0KICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c3Ryb25nPlRvdXQgY2Vs\nYSBzYW5zIHJpZW4gY2hhbmdlcg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICDDoCBsZXVyIGFsaW1lbnRhdGlvbiBldCBzYW5zIGZhaXJlIGF1Y3VuIGVmZm9ydCZu\nYnNwOyEgPC9zdHJvbmc+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgIDxicj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGJy\nPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQb3VyIHZvaXIg\nZm9uZHJlIGxldXIgZ3JhaXNzZSwgZW4gcGFydGljdWxpZXIgYXUgbml2ZWF1IGR1IHZlbnRyZSwg\naWwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV1ciBhIHNp\nbXBsZW1lbnQgZmFsbHUgcmVzdGF1cmVyIHVuZSBib25uZSBmbG9yZSBpbnRlc3RpbmFsZSBlbg0K\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmfDqXJhbnQgcXVv\ndGlkaWVubmVtZW50IGNlIHByb2Jpb3RpcXVlLg0KICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICA8YnI+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgIDxicj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgPHN0cm9uZz5Wb3VzIHZveWV6IGRvbmMgw6AgcXVlbCBwb2ludA0KICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2b3RyZSBmbG9yZSBpbnRlc3RpbmFsZSBwZXV0\nIGpvdWVyIHVuIHLDtGxlIGNsw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgZGFucyBsYSBnZXN0aW9uIGRlIHZvdHJlIHBvaWRzLjwvc3Ryb25nPg0KICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnI+DQogICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxicj4NCiAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgRGFucyBsJ2h5cG90aMOoc2UgbcOqbWUgb8O5IHZvdHJl\nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh5Z2nDqG5lIGRl\nIHZpZSBzZXJhaXQgaXJyw6lwcm9jaGFibGUgZXQgcXVlLA0KICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICBtYWxncsOpIHRvdXQsIHZvdXMgcmVzdGV6IGVuIHN1cnBv\naWRzLCA8dT5pbmfDqXJlcg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICBjZSBwcm9iaW90aXF1ZSBhIGRlcyBjaGFuY2VzIGQnw6p0cmUgbGUgZMOpY2xpYyBxdWkN\nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdm91cyBwZXJtZXR0\ncmEgZGUgcGVyZHJlIGVuZmluIHZvcyBraWxvcyBlbiB0cm9wPC91Pi4gPGJyPg0KICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnI+DQogICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgIFN1ciBsYSBiYXNlIGRlIGNlcyBub3V2ZWxsZXMg\nZMOpY291dmVydGVzLCBsZSBMYWJvcmF0b2lyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICBDZWxsJ2lubm92IGEgbWlzIGF1IHBvaW50IHVuZSA8dT5ub3V2ZWxs\nZSBmb3JtdWxlIHByb2Jpb3RpcXVlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgIGhhdXRlbWVudCBhY3RpdmU8L3U+IHF1aSB2b3VzIGdhcmFudGl0IHVuZSBhY3Rp\nb24gb3B0aW1hbGUgc3VyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgIGwnw6lxdWlsaWJyZSBkZSB2b3RyZSBmbG9yZSBpbnRlc3RpbmFsZSBldCBsYSBwZXJ0ZSBk\nZSBwb2lkcy4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGJy\nPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnI+DQogICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzdHJvbmc+UG91ciByZWNl\ndm9pciBHUkFUVUlURU1FTlQgZXQgc2Fucw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICBlbmdhZ2VtZW50IG1vbiByYXBwb3J0IHNww6ljaWFsIHN1ciBjZXR0ZSBu\nb3V2ZWxsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkw6lj\nb3V2ZXJ0ZSwgaWwgdm91cyBzdWZmaXQgZGUgY2xpcXVlciBzdXIgbGUgbGllbiBjaS1kZXNzb3Vz\nLjwvc3Ryb25nPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8\nYnI+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxicj4NCiAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQWluc2ksIGplIHBvdXJy\nYWkgdm91cyBlbnZveWVyIDx1PnBlcnNvbm5lbGxlbWVudDwvdT4gcGFyDQogICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvdXJyaWVsIGNlcyBpbmZvcm1hdGlvbnMg\nZXhjbHVzaXZlcyBxdWkgdm91cyBhaWRlcm9udCDDoCA8dT5yZXN0YXVyZXINCiAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCfDqXF1aWxpYnJlIGRlIHZvdHJlIGZs\nb3JlIGludGVzdGluYWxlIGV0IMOgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgIMOpbGltaW5lciBwbHVzIGZhY2lsZW1lbnQgdm9zIGtpbG9zIGVuIHRyb3A8L3U+\nLiBWb3VzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlY2V2\ncmV6IMOpZ2FsZW1lbnQgZW4gZXhjbHVzaXZpdMOpIG5vdHJlIGxldHRyZQ0KICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkJ2luZm9ybWF0aW9uIMOpbGVjdHJvbmlx\ndWUgc3VyIGxlcyBkZXJuacOocmVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgIGF2YW5jw6llcyBkZSBsYSBudXRyaXRow6lyYXBpZSBldCBsZXMgaW5ub3ZhdGlv\nbnMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGUgbm90cmUg\nbGFib3JhdG9pcmUgZXhwZXJ0IGVuIG1pY3JvbnV0cml0aW9uIGF2YW5jw6llLg0KICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnI+DQogICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxicj4NCiAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgPGNlbnRlcj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgPHN0cm9uZz5OZSBsYWlzc2V6IHBhcyBwYXNzZXIgdm90cmUgY2hh\nbmNlIGRlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHJv\ndXZlciB2b3RyZSBwb2lkcyBkZSBzYW50w6kgc2FucyBlZmZvcnQNCiAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VwcGzDqW1lbnRhaXJlJm5ic3A7ITwvc3Ryb25n\nPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnI+DQogICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxicj4NCiAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEgIGhyZWY9Imh0dHA6Ly9teS5uZXdz\ncGFydG5lcjMuZnIvdGsvdC8xLzk1Mjg5MTY4MS82MzM2NC8zMjMzLzI5MDk0NDYvIj4NCiAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVuZGV6LXZvdXMgaWNpIHBv\ndXIgcmVjZXZvaXIgaW1tw6lkaWF0ZW1lbnQgdm90cmUgcmFwcG9ydA0KICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcMOpY2lhbCBzdXIgY2Ugbm91dmVhdSBwcm9i\naW90aXF1ZSBjb27Dp3UgcGFyIGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgIExhYm9yYXRvaXJlIENlbGwnaW5ub3YuIDwvYT4NCiAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9jZW50ZXI+DQogICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgIDxicj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgPGJyPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICA8L3RkPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nIDwvdHI+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPg0KICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9InZlcnRp\nY2FsLWFsaWduOiB0b3A7IiBoZWlnaHQ9IjkxIiB3aWR0aD0iMzAwIj4NCiAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGltZyBzcmM9Imh0dHBzOi8vY2VsbGlubm92\nLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMy9DSV9zaWduYXR1cmVfMzAweDkxLmpwZyIg\nYWx0PSJDZWxsJ2lubm92X3NpZ25hdHVyZSI+PGJyPg0KICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICA8L3RkPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgIDwvdHI+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Ri\nb2R5Pg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPg0KICAgICAgICAg\nICAgICAgICAgICAgICAgICAgICAgICA8Y2VudGVyPg0KICAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICA8aHIgc3R5bGU9IndpZHRoOiAzMDBweDsiPg0KICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgICA8L2NlbnRlcj4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGJy\nPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgc3R5bGU9InRleHQtYWxp\nZ246IGxlZnQ7IGJhY2tncm91bmQtY29sb3I6IHJnYigyNTUsIDI1NSwgMjU1KTsgZm9udC1mYW1p\nbHk6IEFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmOyBmb250LXNpemU6IDE3cHg7IiBib3JkZXI9\nIjAiIGNlbGxwYWRkaW5nPSIwIiB3aWR0aD0iNjAwIj4NCiAgICAgICAgICAgICAgICAgICAgICAg\nICAgICAgICAgPC90YWJsZT4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4N\nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPg0KICAgICAgICAgICAgICAgICAgICAg\nICAgPC90Ym9keT4NCiAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4NCiAgICAgICAgICAgICAg\nICAgICAgPC9jZW50ZXI+DQogICAgICAgICAgICAgICAgICAgIDxicj4NCiAgICAgICAgICAgICAg\nICAgICAgPGJyPg0KICAgICAgICAgICAgICAgICAgICA8L2NlbnRlcj4NCiAgICAgICAgICAgICAg\nICAgICAgPC90ZD4NCiAgICAgICAgICAgICAgICA8L3RyPg0KICAgICAgICAgICAgICAgIDx0cj4N\nCiAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJjb2xvcjogcmdiKDEwMiwgMTAyLCAxMDIp\nOyBmb250LWZhbWlseTogdmVyZGFuYSxhcmlhbCxzZXJpZjsgZm9udC1zaXplOiAxMHB4OyBwYWRk\naW5nLWxlZnQ6IDQwcHg7Ij48c3BhbiBzdHlsZT0ibWFyZ2luLWxlZnQ6IDExMHB4OyI+Vm9zIGNv\nb3Jkb25uw6llcyBuJ29udA0KICAgICAgICAgICAgICAgICAgICBwYXMgw6l0w6kgZm91cm5pZXMg\nw6AgbCdhbm5vbmNldXIuIDwvc3Bhbj48YnI+DQogICAgICAgICAgICAgICAgICAgIENvbXB0b2ly\nIGRlIFdhZ3JhbSZuYnNwO2VzdCBlbnJlZ2lzdHLDqSBhdXByw6hzDQogICAgICAgICAgICAgICAg\nICAgIGRlIGxhDQogICAgICAgICAgICAgICAgICAgIENvbW1pc3Npb24gTmF0aW9uYWxlIGRlDQog\nICAgICAgICAgICAgICAgICAgIGwnSW5mb3JtYXRpcXVlIGV0IGRlcyBMaWJlcnTDqXMgc291cyBs\nZQ0KICAgICAgICAgICAgICAgICAgICBudW3DqXJvIDE3Mjg3NDEuPGJyPg0KICAgICAgICAgICAg\nICAgICAgICA8YnI+DQogICAgICAgICAgICAgICAgICAgIFBvdXIgdm91cyBkw6lzYWJvbm5lciwg\nPGEgc3R5bGU9ImNvbG9yOiByZ2IoMTAyLCAxMDIsIDEwMik7IiAgaHJlZj0iaHR0cDovL215Lm5l\nd3NwYXJ0bmVyMy5mci90ay90LzEvOTUyODkxNjgxLzczMzY0LzMyMzMvMjkwOTQ0Ni8iIHRhcmdl\ndD0iX2JsYW5rIj5hY2PDqWRleg0KICAgICAgICAgICAgICAgICAgICDDoCBjZXR0ZSBwYWdlPGJy\nPg0KICAgICAgICAgICAgICAgICAgICA8L2E+U2kgY2UgbWVzc2FnZSB2b3VzIGEgY2F1c8OpIHVu\nDQogICAgICAgICAgICAgICAgICAgIGTDqXNhZ3LDqW1lbnQsIGTDqWNsYXJlciBsZSBlbiZuYnNw\nOyA8YSBzdHlsZT0iY29sb3I6IHJnYigxMDIsIDEwMiwgMTAyKTsiICBocmVmPSJodHRwOi8vbXku\nbmV3c3BhcnRuZXIzLmZyL3RrL3QvMS85NTI4OTE2ODEvODMzNjQvMzIzMy8yOTA5NDQ2LyIgdGFy\nZ2V0PSJfYmxhbmsiPmluZMOpc2lyYWJsZTwvYT48c3BhbiBzdHlsZT0iZm9udC1zaXplOiA4cHQ7\nIGZvbnQtZmFtaWx5OiAmcXVvdDtIZWx2ZXRpY2EmcXVvdDssJnF1b3Q7c2Fucy1zZXJpZiZxdW90\nOzsgY29sb3I6IGdyYXk7Ij48L3NwYW4+PC90ZD4NCiAgICAgICAgICAgICAgICA8L3RyPg0KICAg\nICAgICAgICAgPC90Ym9keT4NCiAgICAgICAgPC90YWJsZT4NCiAgICA8aW1nIGhlaWdodD0nMScg\nd2lkdGg9JzEnIGJvcmRlcj0nMCcgYWx0PScnIHNyYz0naHR0cDovL215Lm5ld3NwYXJ0bmVyMy5m\nci90ay8/dj0yJmlkaT05NTI4OTE2ODEzODFhJmlkbT0zMjMzZThiYSZpZGM9MjkwOTQ0NjI1M2In\nPjwvYm9keT4NCjwvaHRtbD4=\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_213.argentina:2,",
    "content": "Return-Path: <JessePrince@janight.top>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id B0C1420A4FF34\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 18:17:14 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid A5DEF1A42111; Sat,  9 Apr 2016 18:17:14 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 977DD3297DA2; Sat,  9 Apr 2016 18:17:14 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.1 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tINVALID_DATE,RCVD_IN_CSS,RCVD_IN_PSBL,RDNS_NONE autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [5.187.33.190 listed in zen.spamhaus.org]\n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.1 INVALID_DATE Invalid Date: header (not RFC 2822)\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  2.7 RCVD_IN_PSBL RBL: Received via a relay in PSBL\n\t*      [5.187.33.190 listed in psbl.surriel.com]\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\nX-Spam-DCC: : \nReceived: from 006d471e.janight.top (unknown [5.187.33.190])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id C718A328A0D7\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 18:17:11 +0200 (CEST)\nReceived: from 006d471e.925y0go6l.janight.top ([127.0.0.1]:2757 helo=925y0go6l.janight.top)\n\tby 925y0go6l.janight.top with ESMTP id 00HB6D47QQ1E;\n\tfor <dev@caliopen.local>; Sat, 9 Apr 2016 18:17:11 00200\nTo: <dev@caliopen.local>\nMessage-ID: <75732371608737571118648911685@925y0go6l.janight.top>\nDate: Sat, 9 Apr 2016 18:17:11 00200\nFrom: \"Jesse Prince\" <JessePrince@janight.top>\nSubject: Trump Got It Right Again. This Is the Undeniable Proof...\nContent-Language: en-us\nMIME-Version: 1.0\nContent-Transfer-Encoding: 8bit\nContent-Type: multipart/alternative;\n\tboundary=\"----=Part.463.4081.1460218631\"\nX-Spam-Prev-Subject: Trump Got It Right Again. This Is the Undeniable Proof...\nContent-Length: 16614\nLines: 497\n\n------=Part.463.4081.1460218631\nContent-Transfer-Encoding: 8bit\nContent-Type: text/plain; charset=\"UTF-8\"\n\nThis is Donald Trump's most shocking statement yet,\nhttp://www.janight.top/757-86-111-7160873/laurent/tindex57.html\n\nHowever the mainstream media isn't saying a word about it!\n\nWhat are they really trying to cover up?\n\nI'm sending this to all my friends, family and patriot brothers...\n\nAs quickly as I can...\n\nBecause it's the ONLY video I've seen that reveals the \"April Surprise\" nobody is talking about...\nhttp://www.janight.top/757-86-111-7160873/laurent/tindex57.html\n\nThis is bigger and far more dangerous than ISIS could have ever become.\n\nAnd it's headed to YOUR neighborhood...\n\nWAY faster than you think.\n\nP.S. Some of America's richest are already spending hundreds of thousands of dollars to prepare for this...\n\nAnd when you watch this presentation you'll understand why.\nhttp://www.janight.top/757-86-111-7160873/laurent/tindex57.html\n\n\n\n\n\n\n\n\n\nTo optout click here.\nhttp://www.janight.top/757-86-111-7160873/laurent/tindex11.html\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nauthentic /player /functon /biljett /evitare /hero /lanterns /pragmatisms lacks /Flies /routes /blank /routes /positive /apparatus /discounted /feedlots kuvat /satiety /welcoming weird /predestined /tyres /ucm /beast /weird /gesendet /plein /closures /travels soir /Crist /menubar /emailed /311 /alessandro /telecommunications /tafe 9th /Presidential chtah /Qs /pictos /mdid /piece /michele /May /holders /delivered /multim /anthem; participating /cookpad /Libby /commodities /muscles /width /station /Neste /rub this /uac /contain /battled /au /umusic /Wellington /cites /RIS /look /Op f /RIB /rendering sec /dominance /basses /crystalball /outreach /padding /har /2010 Maru /jacket /lawmakers /disputed /impairment /Graft /reconstructive /excoriate robs /mags /mastering coord /herstellen /tougher /Clavier /petersburg /flicker /optus Hug /compromises /watching /Creek /basses /naked /Sin /juba /ilment /tc /extractor makeovers /louse petersburg /median /Prod /lieutenants /maree /gebruiken /mart\n in /outside mica /DUH /contac /functon /alkaline 28px /sleeping /ties /newreleases /habitation /alles /thailand /organizers someone /Touche recomendar /images /toughest /LJ /rega /serving /rollout /functon /s /raf /Viaje discounted /het /har /diamond /produkt /8th /inactive /touch /freepost /heures /plano 4678 /a /kollektion /erweitern /head /farlig /linkid /argot /mil /receives guys /nervous /enfant /multim /herstellen /TIL /barbershops /modificato /Fleet influences /estadisticas /modificato /scientifice /spaced /belmont\n\n\n\n\n\n\n------=Part.463.4081.1460218631\nContent-Transfer-Encoding: 8bit\nContent-Type: text/html; charset=\"UTF-8\"\n\n<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n<title>Template FW</title>\n  <style type=\"text/css\">\n      table {\n          border-collapse: collapse;\n      }\n      img {\n          border: none;\n          display: block;\n      }\n    \n      @media only screen and (max-width: 640px) {\n        body[yahoo] .deviceWidth {\n            width: 440px!important;\n            padding: 0;\n        }\n        body[yahoo] .center {\n            text-align: center!important;\n        }\n        #social-icons {\n            width: 40%;\n        }\n      }\n    \n      @media only screen and (max-width: 479px) {\n        body[yahoo] .deviceWidth {\n            width: 280px !important;\n            padding: 0;\n        }\n        body[yahoo] .center {\n            text-align: center!important;\n        }\n        #social-icons {\n            width: 60%;\n        }\n        \n      }\n       /* Stacked Top Links Navigation Pattern CSS */\n\n        @media only screen and (max-width: 400px) {\n            td[class=\"main_nav\"] {\n                padding: 0px 0;\n            }\n            td[class=\"main_nav\"] td {\n                display: block;\n                float: left;\n                padding: 0 !important;\n                width: 50%;\n            }\n            td[class=\"main_nav\"] a {\n                display: block;\n                padding: 5px 0;\n            }\n            td[class=\"main_nav\"] .nav1 a,\n            td[class=\"main_nav\"] .nav2 a,\n            td[class=\"main_nav\"] .nav3 a,\n            td[class=\"main_nav\"] .nav4 a {\n                \n                border-bottom: 1px solid #999;\n            }\n            td[class=\"main_nav\"] .nav1 a,\n            td[class=\"main_nav\"] .nav3 a,\n            td[class=\"main_nav\"] .nav5 a  {\n                border-right: 1px solid #999;\n            }\n            ul {\n              padding:0px;\n              margin:10px;\n            }\n            .mobile_hide{\n                display:none;\n            }\n            .mobile_img{\n                width:50%;\n                display:block;\n                align-content:center;\n            }\n        }\n        @media only screen and (max-width : 736px) { \n            td[class=\"main_nav\"] {\n                padding: 0px 0;\n            }\n            td[class=\"main_nav\"] td {\n                display: block;\n                float: left;\n                padding: 0 !important;\n                width: 50%;\n            }\n            td[class=\"main_nav\"] a {\n                display: block;\n                padding: 5px 0;\n            }\n            td[class=\"main_nav\"] .nav1 a,\n            td[class=\"main_nav\"] .nav2 a,\n            td[class=\"main_nav\"] .nav3 a,\n            td[class=\"main_nav\"] .nav4 a {\n                \n                border-bottom: 1px solid #999;\n            }\n            td[class=\"main_nav\"] .nav1 a,\n            td[class=\"main_nav\"] .nav3 a,\n            td[class=\"main_nav\"] .nav5 a  {\n                border-right: 1px solid #999;\n            }\n            ul {\n              padding:0px;\n              margin:10px;\n            }\n            .mobile_hide{\n                display:none;\n            }\n            .mobile_img{\n                width:50%;\n                display:block;\n                align-content:center;\n            }\n        }\n\n      a:link {\n          text-decoration: none;\n      }\n      a:visited {\n          text-decoration: none;\n      }\n      a:hover {\n          text-decoration: none;\n      }\n      a:active {\n          text-decoration: none;\n      }\n    </style>\n</head>\n\n<body leftmargin=\"0\" topmargin=\"0\" marginwidth=\"0\" marginheight=\"0\" yahoo=\"fix\"\n style=\"font-family: Verdana, Arial, Helvetica, sans-serif; width: 100%;\n background-color: #edeef0; margin:0; padding:0; -webkit-font-smoothing:\n antialiased; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%;\">\n\n<table>\n    <tr>\n      <td height=\"10\" style=\"font-size: 10px; line-height: 10px;\"></td>\n    </tr>\n</table>\n\n<table width=\"100%\" max-width=\"600\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\n align=\"center\" bgcolor=\"#edeef0\">\n  <tr>\n    <td>\n      <table width=\"600\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\n class=\"deviceWidth\" style=\"background-color: #edeef0;\">\n        <tr>\n          <td class=\"center\">\n   \n          <table width=\"600\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\n align=\"center\" class=\"deviceWidth\">\n            <tr>\n              <td class=\"center\">\n                <table width=\"50%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\n align=\"left\" class=\"deviceWidth\">\n                  <tr>\n                    <td align=\"center\">\n                      <div style=\"font-family:Arial, Helvetica,\n sans-serif;font-size:11px;color:#000000;text-align:left; line-height:1;\n padding-left:10px;\">\n                      </div>\n                      <br>\n                    </td>\n                  </tr>\n                </table>\n                <table width=\"50%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\n align=\"right\" class=\"deviceWidth\">\n                  <tr>\n                    <td align=\"right\">\n             \n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n          </table>\n  \n         </td>\n       </tr>\n\n  \n\n\n  \n  <tr>\n    <td>\n  \n      <table width=\"600\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\n class=\"deviceWidth\" bgcolor=\"#ffffff\">\n        <tr>\n          <td class=\"center\">\n            <table width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\n align=\"center\" class=\"deviceWidth\">\n              <tr>\n                <td style=\"text-align: left; font-family:Arial, Helvetica,\n sans-serif; font-size:16px; color: #282828; padding: 0px 20px 0px 20px;\"\n align=\"center\"> \n                  <table>\n                    <tr>\n                      <td height=\"5\" style=\"font-size: 5px; line-height:\n 5px;\"></td>\n                    </tr>\n                  </table>               \n                  <a id=\"LID9828\"\n href=\"http://www.janight.top/757-86-111-7160873/laurent/tindex52.html\">This</a>\n is Donald Trump's most shocking statement yet, \n                  <br><br>\n                  However the mainstream media isn't saying a word about it! \n                  <br><br>\n                  What are they really trying to cover up? \n                  <br><br>\n                </td>\n              </tr>\n            </table>\n          </td>\n        </tr>\n      </table>\n  \n    </td>\n  </tr>\n\n  <tr>\n    <td>\n\n      \n      <table width=\"600\" class=\"deviceWidth\" border=\"0\" cellpadding=\"0\"\n cellspacing=\"0\" align=\"center\" bgcolor=\"#ffffff\">\n        <tr>\n          <td width=\"100%\" align=\"center\">\n            <a id=\"LID9829\"\n href=\"http://www.janight.top/757-86-111-7160873/laurent/tindex52.html\">\n <img src=\"http://www.janight.top/757-86-111-7160873/i/img086111323.jpg\" style=\"width: 100%;\n max-width: 400px;\" align=\"center\"></a>\n            <table>\n              <tr>\n                <td height=\"10\" style=\"font-size: 10px; line-height: 10px;\"></td>\n              </tr>\n            </table>\n          </td>\n        </tr>\n      </table>\n      \n\n    </td>\n  </tr>\n\n\n  <tr>\n    <td>\n    \n      <table width=\"600\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\n class=\"deviceWidth\" bgcolor=\"#ffffff\">\n        <tr>\n          <td class=\"center\">\n            <table width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\n align=\"center\" class=\"deviceWidth\">\n              <tr>\n                <td style=\"text-align: left; font-family:Arial, Helvetica,\n sans-serif; font-size:16px; color: #282828; padding: 0px 20px 0px 20px;\"\n align=\"center\">                \n                  I'm sending this to all my friends, family and patriot\n brothers... \n                  <br><br>\n                  As quickly as I can... \n                  <br><br>\n                  Because it's the <a id=\"LID9830\"\n href=\"http://www.janight.top/757-86-111-7160873/laurent/tindex52.html\">ONLY\n video</a> I've seen that reveals the \"April Surprise\" nobody is talking about... \n                  <br><br>\n                  This is bigger and far more dangerous than ISIS could have ever\n become. \n                  <br><br>\n                  And it's headed to YOUR neighborhood... \n                  <br><br>\n                  WAY faster than you think. \n                  <br><br>\n                  P.S. Some of America's richest are already spending hundreds of\n thousands of dollars to prepare for this... \n                  <br><br>\n                  And when you watch <a id=\"LID9831\"\n href=\"http://www.janight.top/757-86-111-7160873/laurent/tindex52.html\">this\n presentation</a> you'll understand why. \n                  <br><br>\n                </td>\n              </tr>\n            </table>\n          </td>\n        </tr>\n      </table>\n \n    </td>\n  </tr>\n\n\n  \n      <tr>\n        <td>\n      \n          <table width=\"600\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\n align=\"center\" class=\"deviceWidth\" bgcolor=\"#5c5a5a\">\n       \n              <tr>\n                  <td>\n                    <table width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\n align=\"center\" class=\"deviceWidth\">\n                       <tr>\n                         <td style=\"text-align: center; color: #5C5A5A;\n font-family:Arial, Helvetica, sans-serif; font-size: 10px; line-height:1; padding:\n 0px 10px 0px 10px;\">\n                          <table>\n                            <tr>\n                              <td height=\"10\" style=\"font-size: 10px; line-height:\n 10px;\">\n \n \n </td>\n                            </tr>\n                          </table>\n     authentic /player /functon /biljett /evitare /hero /lanterns /pragmatisms\nlacks /Flies /routes /blank /routes /positive /apparatus /discounted /feedlots\nkuvat /satiety /welcoming\nweird /predestined /tyres /ucm /beast /weird /gesendet /plein /closures /travels\nsoir /Crist /menubar /emailed /311 /alessandro /telecommunications /tafe\n9th /Presidential\nchtah /Qs /pictos /mdid /piece /michele /May /holders /delivered /multim /anthem;\nparticipating /cookpad /Libby /commodities /muscles /width /station /Neste /rub\nthis /uac /contain /battled /au /umusic /Wellington /cites /RIS /look /Op\nf /RIB /rendering\nsec /dominance /basses /crystalball /outreach /padding /har /2010\nMaru /jacket /lawmakers /disputed /impairment /Graft /reconstructive /excoriate\nrobs /mags /mastering\ncoord /herstellen /tougher /Clavier /petersburg /flicker /optus\nHug /compromises /watching /Creek /basses /naked /Sin /juba /ilment /tc /extractor\nmakeovers /louse\npetersburg /median /Prod /lieutenants /maree /gebruiken /martin /outside\nmica /DUH /contac /functon /alkaline\n28px /sleeping /ties /newreleases /habitation /alles /thailand /organizers\nsomeone /Touche\nrecomendar /images /toughest /LJ /rega /serving /rollout /functon /s /raf /Viaje\ndiscounted /het /har /diamond /produkt /8th /inactive /touch /freepost /heures /plano\n4678 /a /kollektion /erweitern /head /farlig /linkid /argot /mil /receives\nguys /nervous /enfant /multim /herstellen /TIL /barbershops /modificato /Fleet\ninfluences /estadisticas /modificato /scientifice /spaced /belmont\n                           <table>\n                            <tr>\n                              <td height=\"10\" style=\"font-size: 10px; line-height:\n 10px;\"></td>\n                            </tr>\n                          </table>\n                         </td>\n                      </tr>\n                    </table>\n                 </td>\n              </tr>\n          <!-- /!copyright -->\n              <tr>\n               <td>\n               <table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\"\n style=\"border-bottom:#5c5a5a solid 20px;\" class=\"deviceWidth\">\n                  <tr>\n                    <td>\n              \n                        <table width=\"100%\" border=\"0\" cellspacing=\"0\"\n cellpadding=\"0\" align=\"center\" class=\"deviceWidth\" bgcolor=\"#ffffff\"\n style=\"border-left:#5c5a5a solid 5px; border-right:#5c5a5a solid 5px;\">\n                          <tr>\n                            <td style=\"text-align: center; color: #282828;\n font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px;\n padding-top:10px;\">\n \n <br>\n<br><center>\n<div style=\"font-size:12px;\">The Massage is Not Intended For Canada Residents,<br>\nIf you no longer wish to receive correspondence or special offers from us,<br>\nplease use this <a href=\"http://www.janight.top/757-86-111-7160873/laurent/tindex5.html\">link</a> to optout or mail me : #1234 E, Lafayette, LA 70507, USA</div></center>\n \n </td>\n                          </tr>\n                          <tr>\n                            <td height=\"5\" style=\"font-size: 5px; line-height:\n 5px;\"></td>\n                          </tr>\n                          <tr>\n                            <td style=\"text-align: center; color: #282828;\n font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px;\n padding:0px;\"></td>\n                          </tr>\n                          <tr>\n                            <td height=\"5\" style=\"font-size: 5px; line-height:\n 5px;\"></td>\n                          </tr>\n                          <tr>\n                            <td style=\"text-align: center; color: #282828;\n font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px;\n padding-bottom:10px;\"></a></td>\n                              <table>\n                                <tr>\n                                  <td height=\"10\" style=\"font-size: 10px;\n line-height: 10px;\"></td>\n                                </tr>\n                              </table>\n                          </tr>\n                        </table>\n         \n                      </td>\n                    </tr>\n                  </table>\n                </td>            \n              </tr>\n             </table>\n           </td>\n          </tr></table>\n\n\n    </td>\n  \n</tr></table>\n\n</body>\n</html>\n\n------=Part.463.4081.1460218631--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_215.argentina:2,",
    "content": "Return-Path: <DefeatingDiabetes@beyear.top>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 7947C2080FF79\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 18:05:55 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 678DFF99530; Sat,  9 Apr 2016 18:05:55 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 58EA4F9CD80; Sat,  9 Apr 2016 18:05:55 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.2 required=5.0 tests=BAYES_95,DIET_1,HTML_MESSAGE,\n\tINVALID_DATE,RCVD_IN_BL_SPAMCOP_NET,RCVD_IN_CSS,RDNS_NONE,T_REMOTE_IMAGE\n\tautolearn=no version=3.3.2\nX-Spam-Report: \n\t*  1.1 INVALID_DATE Invalid Date: header (not RFC 2822)\n\t*  1.3 RCVD_IN_BL_SPAMCOP_NET RBL: Received via a relay in bl.spamcop.net\n\t*      [Blocked - see <http://www.spamcop.net/bl.shtml?217.78.2.219>]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [217.78.2.219 listed in zen.spamhaus.org]\n\t*  0.0 DIET_1 BODY: Lose Weight Spam\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  3.0 BAYES_95 BODY: Bayes spam probability is 95 to 99%\n\t*      [score: 0.9786]\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 T_REMOTE_IMAGE Message contains an external image\nX-Spam-DCC: : \nReceived: from 006d471c.beyear.top (unknown [217.78.2.219])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 173EAF99530\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 18:05:52 +0200 (CEST)\nReceived: from 006d471c.6ehkkml.beyear.top ([127.0.0.1]:4755 helo=6ehkkml.beyear.top)\n\tby 6ehkkml.beyear.top with ESMTP id 00XAJT6D47GSEV1C;\n\tfor <dev@caliopen.local>; Sat, 9 Apr 2016 18:05:52 00200\nMessage-ID: <755323716087375574233078986@6ehkkml.beyear.top>\nTo: <dev@caliopen.local>\nDate: Sat, 9 Apr 2016 18:05:52 00200\nFrom: \"Defeating Diabetes\" <DefeatingDiabetes@beyear.top>\nSubject: Doctors Baffled By This $7 Diabetes Treatment\nContent-Language: en-us\nMIME-Version: 1.0\nContent-Transfer-Encoding: 8bit\nContent-Type: multipart/alternative;\n\tboundary=\"----=Part.949.3870.1460217952\"\nX-Spam-Prev-Subject: Doctors Baffled By This $7 Diabetes Treatment\nContent-Length: 7642\nLines: 207\n\n------=Part.949.3870.1460217952\nContent-Transfer-Encoding: 8bit\nContent-Type: text/plain; charset=\"UTF-8\"\n\nThink about this:\n\nWhat if you could wake up tomorrow morning and be cured of your diabetes?\n\nWell, a new diabetes remedy has just surfaced that could very well help reverse your diabetes and reclaim your health... without dangerous drugs.\n\nIt's actually a mysterious \"white starch\" (of all things) that I bet your doctor has never even heard of. Yet it's been shown to lower blood sugar, improve insulin sensitivity, and help diabetics lose weight.\n\nAnd you can learn exactly what this starch is and how it can help you right here.\n\nThis is cutting-edge information and it's currently free for you to check out so go here NOW:\n\n=> Eat this WHITE STARCH to reverse diabetes\nhttp://www.beyear.top/755-23-74-7160873/laurent/tindex45.html\n\nDo this, and your diabetes could be a thing of the past in just a few short weeks.\n\nPass on it, and you could be, quite literally sending yourself to an early grave.\n\nEnjoy!\n\nPS. Imagine how great your life could be if you no longer had throbbing foot pain, \nno longer had to monitor your blood sugar levels, and no longer worried about your health. Imagine the freedom.\n\n=> Eat this WHITE STARCH to reverse diabetes\nhttp://www.beyear.top/755-23-74-7160873/laurent/tindex45.html\n\n\n\n\nThis communication is for informational purposes only. Consult a physician before performing this or any exercise program. It is your responsibility to evaluate your own medical and physical condition, or that of your clients, and to independently determine whether to perform, use or adapt any of the information or content in this communication or the resulting web site. Any exercise program may result in injury. By voluntarily undertaking any exercise displayed in this communication or the resulting web site, you assume the risk of any resulting injury. Elkaim Group International, Inc. - Braemar Court, Deighton Road, St. Michael, Barbados, BB14017 Copyright 2015. All rights reserved. This is an adv-ertise-ment.\n\nIf you would like to stop receiving email messages from Defeating Diabetes Kit, click here or write to: Braemar Court, Deighton Road, St. Michael, Barbados, BB14017\n\nhttp://www.beyear.top/755-23-74-7160873/laurent/rindex30.html\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nGuys /Buone /forgotten /reproduce /odinga /Ende /arrival /zorgt /bewerken /hfm /minus vielen /introducing /2308 /7900 /3132 /Otherwise /Color /Hermanos /hilsen /seminars Clinton /runs /vacances /Spaces /9700 /definition /Ship /Balance /responsible /air generated /lanc /Questions /admitted /feit /hell /idea /Multiple /Travel /tout Duits /Unfortunately /weekly /starting /vendita /Category /3080 /belang /date /mean See /mensaje /trimmed /storage /immagini /delete /writes /sigue /peux /melt ligt /Ventes /rampant /Adressbuch /wants /Really /Postvak /Beste /sailors /track Room /samen /geehrter /strategic /viral /Type /laid /vriendelijke /weird /Robots amount /giornata /Card /dann /cite /potrebbe /Ted /6001 /2834 /2338 /creativity /later christmas /Vanity /ask /augmenter /sicuro /sid /tasks /feeds /sigh /Saving swe /namely /2501 /inspire /dalla /sufficient /Reply /evaluation /uvm /mailed Gru /extensive /die /allemaal /extension /Med /saving /ration /old /saat /ISO /3\n\n\n------=Part.949.3870.1460217952\nContent-Transfer-Encoding: 8bit\nContent-Type: text/html; charset=\"UTF-8\"\n\n<html>\n<head>\n</head>\n<body>\n\n<div style=\"width:580px;padding:20px;font-size:18px;text-align:justify\">\n\n<p>\nThink about this:</p>\n<p> \nWhat if you could wake up tomorrow morning and be cured of your\ndiabetes?</p>\n <p>\nWell, a new diabetes remedy has just surfaced that could very\nwell help reverse your diabetes and reclaim your health... without\ndangerous drugs.</p>\n<p> \nIt's actually a mysterious \"white starch\" (of all things) that I\nbet your doctor has never even heard of. Yet it's been shown to\nlower blood sugar, improve insulin sensitivity, and help\ndiabetics lose weight.</p>\n <p>\nAnd you can learn exactly what this starch is and how it can\nhelp you <a href=\"http://www.beyear.top/755-23-74-7160873/laurent/tindex39.html\">right here</a>.</p>\n <p>\nThis is cutting-edge information and it's currently free for you\nto check out so go here NOW:</p>\n <p>\n=> <a href=\"http://www.beyear.top/755-23-74-7160873/laurent/tindex39.html\">Eat this WHITE STARCH to reverse diabetes</a></p>\n <p>\nDo this, and your diabetes could be a thing of the past in just\na few short weeks.</p>\n <p>\nPass on it, and you could be, quite literally sending yourself\nto an early grave.</p>\n<p> \nEnjoy!\n</p>\n\n<p>\nPS. Imagine how great your life could be if you no longer had\nthrobbing foot pain, no longer had to monitor your blood sugar\nlevels, and no longer worried about your health. Imagine the\nfreedom.</p>\n\n<p>\n=> <a href=\"http://www.beyear.top/755-23-74-7160873/laurent/tindex39.html\">Eat this WHITE STARCH to reverse diabetes</a></p>\n\n<br><br><br>\n    <div style=\"font-size:11px; color:#888;text-align:justify\">\n    <p>\n    This communication is for informational purposes only. Consult a physician before performing this or any exercise program. It is your responsibility to evaluate your own medical and physical condition, or that of your clients, and to independently determine whether to perform, use or adapt any of the information or content in this communication or the resulting web site. Any exercise program may result in injury. By voluntarily undertaking any exercise displayed in this communication or the resulting web site, you assume the risk of any resulting injury. Elkaim Group International, Inc. - Braemar Court, Deighton Road, St. Michael, Barbados, BB14017 Copyright 2015. All rights reserved. This is an adv-ertise-ment.\n    </p>\n    <p>\n    If you would like to stop receiving email messages from Defeating Diabetes Kit, click <a href=\"http://www.beyear.top/755-23-74-7160873/laurent/rindex23.html\">here</a> or write to:  Braemar Court, Deighton Road, St. Michael, Barbados, BB14017\n    </p>\n    </div>\n</div>\n<br>\n<br><br>\n\n<br>\n<br><br>\n\n<br>\n<br><br>\n<br><br>\n<br><br>\n\n\n\n<div align=\"left\" title=\"\" id=\"4px\" name=\"15px\">\n\n\n\n<a href=\"http://www.beyear.top/755-23-74-7160873/laurent/u.html\"><img src=\"http://www.beyear.top/755-23-74-7160873/i/img02374323.jpg\"  alt=\"Click here to un-subscribe\" style=\"font-size:11px; background-color: #ffffff; padding: 4px; border: px solid #000000;\" ></a>\n\n\n\n</div>\n\n<br><br><br><br>\n\n<div>\n\n\n\n<table style=\"font-style: sans-serif ; font-size: XX-SMALL; color: #ffffff; background-color: #FFFFFF;\">\n\n\n\n<span style=\"color: #FFFFFF;\">\n\n\n\nGuys /Buone /forgotten /reproduce /odinga /Ende /arrival /zorgt /bewerken /hfm /minus\nvielen /introducing /2308 /7900 /3132 /Otherwise /Color /Hermanos /hilsen /seminars\nClinton /runs /vacances /Spaces /9700 /definition /Ship /Balance /responsible /air\ngenerated /lanc /Questions /admitted /feit /hell /idea /Multiple /Travel /tout\nDuits /Unfortunately /weekly /starting /vendita /Category /3080 /belang /date /mean\nSee /mensaje /trimmed /storage /immagini /delete /writes /sigue /peux /melt\nligt /Ventes /rampant /Adressbuch /wants /Really /Postvak /Beste /sailors /track\nRoom /samen /geehrter /strategic /viral /Type /laid /vriendelijke /weird /Robots\namount /giornata /Card /dann /cite /potrebbe /Ted /6001 /2834 /2338 /creativity /later\nchristmas /Vanity /ask /augmenter /sicuro /sid /tasks /feeds /sigh /Saving\nswe /namely /2501 /inspire /dalla /sufficient /Reply /evaluation /uvm /mailed\nGru /extensive /die /allemaal /extension /Med /saving /ration /old /saat /ISO /3\n\n\n\n</span>\n\n\n\n</table>\n\n\n\n</div>\n\n</body>\n</html>\n\n------=Part.949.3870.1460217952--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_217.argentina:2,",
    "content": "Return-Path: <case384@zttim.ru>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 7E25220ADBBEC\n\tfor <laurent@brasil.brainstorm.fr>; Thu,  7 Apr 2016 12:41:49 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 716983317708; Thu,  7 Apr 2016 12:41:49 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 640713317701; Thu,  7 Apr 2016 12:41:49 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.6 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BRBL_LASTEXT,RDNS_NONE,\n\tSPF_HELO_SOFTFAIL,TO_NO_BRKTS_NORDNS autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [176.97.197.9 listed in bb.barracudacentral.org]\n\t*  0.7 SPF_HELO_SOFTFAIL SPF: HELO does not match SPF record (softfail)\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from zttim.ru (unknown [176.97.197.9])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 14B4133181F7\n\tfor <dev@caliopen.local>; Thu,  7 Apr 2016 12:41:46 +0200 (CEST)\nDate: Thu, 7 Apr 2016 13:49:54 +0400\nFrom: case384@zttim.ru\nReply-To: <smile@wertymail.com>\nMessage-ID: <251983c-97e2a-af@zttim.ru>\nTo: dev@caliopen.local\nSubject: Salute\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject:  Salute\nContent-Length: 210\nLines: 2\n\n\n<div>Salute, I'm a lone bunny girlie, looking real friend to hang out ;) <br> Where are you from, do you speak english? <br> <br> please reply me if you would like to see photos of me. See you around :)</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_219.argentina:2,",
    "content": "Return-Path: <benson11@evault.com.my>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 232B120B6675D\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 17:45:02 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 0F3AA33181F9; Sat,  9 Apr 2016 17:45:02 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 01F093317704; Sat,  9 Apr 2016 17:45:01 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *****\nX-Spam-Status: Yes, score=5.4 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RDNS_NONE,SPF_HELO_PASS,\n\tTO_NO_BRKTS_NORDNS autolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t* -0.0 SPF_HELO_PASS SPF: HELO matches SPF record\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from evault.com.my (unknown [110.4.46.64])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id EA0F233181F9\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 17:44:55 +0200 (CEST)\nDate: Sat, 9 Apr 2016 23:44:52 +0800\nFrom: benson11@evault.com.my\nReply-To: <amat@zerromail.com>\nMessage-ID: <9b713bc-8da8f-01@evault.com.my>\nTo: dev@caliopen.local\nSubject: RE:  Salute\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: RE:  Salute\nContent-Length: 200\nLines: 2\n\n\n<div>Salute, I'm a single young lady, looking for a nice guy to go out with ))) <br> Where are you from, do you speak english? <br> <br> reply me if you want to see photos with me. Good bye ))</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_221.argentina:2,",
    "content": "Return-Path: <>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id BC48C20AF5C7A\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 16:49:02 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid AF7553317721; Sat,  9 Apr 2016 16:49:02 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid A1C253317704; Sat,  9 Apr 2016 16:49:02 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *************************\nX-Spam-Status: Yes, score=25.4 required=5.0 tests=BAYES_99,FROM_12LTRDOM,\n\tFSL_HELO_BARE_IP_1,MISSING_MID,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RCVD_IN_PBL,RCVD_NUMERIC_HELO,\n\tRDNS_NONE,TO_NO_BRKTS_NORDNS,TVD_RCVD_IP,TVD_RCVD_IP4,T_SURBL_MULTI1,\n\tT_URIBL_BLACK_OVERLAP,T_URIBL_SEM,T_URIBL_SEM_RED,URIBL_BLACK,URIBL_DBL_SPAM,\n\tURIBL_JP_SURBL,URIBL_SBL,URIBL_WS_SURBL,URI_NOVOWEL autolearn=spam\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 0.9965]\n\t*  2.3 FSL_HELO_BARE_IP_1 FSL_HELO_BARE_IP_1\n\t*  0.0 TVD_RCVD_IP4 TVD_RCVD_IP4\n\t*  0.0 TVD_RCVD_IP TVD_RCVD_IP\n\t*  1.2 RCVD_NUMERIC_HELO Received: contains an IP address used for HELO\n\t*  0.5 URI_NOVOWEL URI: URI hostname has long non-vowel sequence\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  3.3 RCVD_IN_PBL RBL: Received via a relay in Spamhaus PBL\n\t*      [113.184.12.158 listed in zen.spamhaus.org]\n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: oqwvdxxv.in]\n\t*  0.0 T_URIBL_SEM Contains a URI listed in uribl.spameatingmonkey.net\n\t*      [URIs: oqwvdxxv.in]\n\t*  1.6 URIBL_WS_SURBL Contains an URL listed in the WS SURBL blocklist\n\t*      [URIs: oqwvdxxv.in]\n\t*  1.2 URIBL_JP_SURBL Contains an URL listed in the JP SURBL blocklist\n\t*      [URIs: oqwvdxxv.in]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: oqwvdxxv.in]\n\t*  0.0 T_URIBL_SEM_RED Contains a URI listed in urired.spameatingmonkey.net\n\t*      [URIs: oqwvdxxv.in]\n\t*  1.6 URIBL_SBL Contains an URL listed in the SBL blocklist\n\t*      [URIs: oqwvdxxv.in]\n\t*  0.5 MISSING_MID Missing Message-Id: header\n\t*  0.0 T_URIBL_BLACK_OVERLAP T_URIBL_BLACK_OVERLAP\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 T_SURBL_MULTI1 T_SURBL_MULTI1\n\t*  2.0 FROM_12LTRDOM From a 12-letter domain\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from 113.184.12.158 (unknown [113.184.12.158])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id 9FEC333181F9\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 16:48:58 +0200 (CEST)\nReceived: from unknown (HELO localhost) (cd893918@printcareers.co.uk@87.62.122.72)\n\tby 113.184.12.158 with ESMTPA; Sat, 9 Apr 2016 21:50:23 +0700\nX-Originating-IP: 87.62.122.72\nFrom: cd893918@printcareers.co.uk\nTo: dev@caliopen.local\nSubject: Enlarge your gun\nDate: Sat, 9 Apr 2016 21:39:53 +0700\nX-Spam-Prev-Subject: Enlarge your gun\nMessage-Id: <20160409144902.A1C253317704@mexico.brainstorm.fr>\nContent-Length: 69\nLines: 1\n\nhttp://overcome.oqwvdxxv.in/ Ancient secret for perfect bedroom life\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_223.argentina:2,",
    "content": "Return-Path: <posts@futurfast.eu>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id F333D20ADC0A7\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 15:47:06 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid E8C9D3317721; Sat,  9 Apr 2016 15:47:06 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid DAE153317704; Sat,  9 Apr 2016 15:47:06 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ***********\nX-Spam-Status: Yes, score=11.2 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_12_24,DKIM_SIGNED,HTML_MESSAGE,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,T_DKIM_INVALID,URIBL_BLACK,\n\tURIBL_DBL_SPAM autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: npmd9ti3wa.com]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: npmd9ti3wa.com]\n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 DATE_IN_PAST_12_24 Date: is 12 to 24 hours before Received: date\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.0 T_DKIM_INVALID DKIM-Signature header exists but is not valid\n\t* -0.2 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from mail.futurfast.eu (mail.futurfast.eu [176.20.27.58])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id A609133181F9\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 15:47:04 +0200 (CEST)\nDKIM-Signature: a=rsa-sha1; t=1460209627; x=1460814427; s=futurfastkey; d=futurfast.eu; c=relaxed/relaxed; q=dns; h=From:Reply-To:Subject:Date:Message-ID:To:MIME-Version:Content-Type:Content-Transfer-Encoding;\n   b=a0M1+PPQbtH5t04P+/eqSyniI+c0RKb4BHSGuQJFoAiC9efDK4SrABvkPYduagk67rQDMxEGKitQ5rZjcKCNqDTafZTqZ19SXwhPt9cpt2KDVmbxz2AcZhW6MjvqGyMdn2iIUrMTEcOwMEZAxZ6DzAfHfwuLzjIeTkHcS9vYTqI=\nReceived: from www.supprabyte.co.uk\n        by mail.futurstats.eu (IceWarp 9.3.1) with SMTP id XRE00007\n        for <dev@caliopen.local>; Sat, 09 Apr 2016 15:47:07 +0200\nTo: dev@caliopen.local\nSubject: Gestion du nettoyage de vos locaux\nMessage-ID: <cd945ac3821f7f5539321dd0be3447c6@www.supprabyte.co.uk>\nDate: Sat, 09 Apr 2016 01:08:16 +0200\nFrom: \"Nettoyage des Locaux\" <sfp@futurfast.eu>\nReply-To: sfp@futurfast.eu\nMIME-Version: 1.0\nX-Mailer-LID: 326\nList-Unsubscribe: <http://www.supprabyte.co.uk/unsubscribe.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&L=326&N=2337>\nX-Mailer-SID: 2337\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_f3224b53f82e9d84e7308c170dfebdba\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Gestion du nettoyage de vos locaux\nContent-Length: 10954\nLines: 372\n\n--b1_f3224b53f82e9d84e7308c170dfebdba\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://www.supprabyte.co.uk/display.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&S=2337&L=326&N=223\n\n\nTo stop receiving these\nemails:http://www.supprabyte.co.uk/unsubscribe.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&L=326&N=2337\n\n--b1_f3224b53f82e9d84e7308c170dfebdba\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div align=\"center\"><font style=\"font-size: 11px; font-family:\nArial,Helvetica,sans-serif; color: #523122;\" face=\"Arial, Helvetica,\nsans-serif\" color=\"#523122\" size=\"1\">Si vous ne parvenez pas &agrave; lire\ncet email veuillez trouver la page <a style=\"color: #523122;\"\nhref=\"http://www.supprabyte.co.uk/display.php?M=7474945&C=f2d22bbcfd54ce5513062bfbe27380ea&S=2337&L=326&N=223\">suivante</a><a>\n<center class=\"wrapper\" style=\"width: 100%; table-layout: fixed;\n-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%;\">\n<div class=\"webkit\" style=\"max-width:600px;\nbackground-color:#ffffff\"><!--[if (gte mso 9)|(IE)]>\n<table width=\"600\" align=\"center\"\nstyle=\"border-spacing:0;font-family:sans-serif;color:#333333;\"\nmce_style=\"border-spacing:0;font-family:sans-serif;color:#333333;\"\nborder=\"0\"  cellpadding=\"0\" cellspacing=\"0\" >\n<tr>\n<td style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"\nmce_style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"\n><![endif]-->\n<table style=\"border-spacing: 0px; margin: 0px auto; width: 600px;\nmax-width: 600px;\" align=\"center\" bgcolor=\"ececec\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td style=\"max-width:600;\" height=\"26\" align=\"center\" valign=\"middle\"><font\nstyle=\" font-family:Arial, Helvetica, sans-serif; font-size:11px ;\nfont-weight: normal; color:#a4a7a8;\">Faites appel &agrave; une\nsoci&eacute;t&eacute; de nettoyage !&nbsp;</font></td>\n</tr>\n</tbody>\n</table>\n<table class=\"outer\" style=\"border-spacing: 0; font-family: sans-serif;\ncolor: #333333; margin: 0 auto; width: 100%; max-width: 600px;\"\nalign=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"header\" style=\"background-color: #ec9600; height: 70px;\ntext-align: center; padding: 0;\">\n<table style=\"border-spacing: 0px; font-family: sans-serif; color: #333333;\nwidth: 100%;\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"inner contents\"\nstyle=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;\"><a\nstyle=\"text-decoration:none\"\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2337&L=983&F=H\"\ntarget=\"_blank\"> <!--\n<p class=\"h2\"\nstyle=\"Margin:0;font-size:17px;color:#ffffff;text-align:center;\nline-height:26px;\"\nmce_style=\"Margin:0;font-size:17px;color:#ffffff;text-align:center;\nline-height:26px;\" >&nbsp;</p>\n-->\n<p class=\"h1\"\nstyle=\"Margin:0;font-size:21px;font-weight:bold;color:#ffffff;text-align:center;\">Confiez\nle nettoyage de vos locaux <br /> &agrave; des professionnels !</p>\n</a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\">\n<table class=\"outer\" style=\"border-spacing: 0; font-family: sans-serif;\ncolor: #333333; margin: 0 auto; width: 100%; max-width: 600px;\"\nalign=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"full-width-image\"\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2337&L=983&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://www.mesdevisgratuits.fr/1422_MB_0116/img/Visu_News.jpg\"\nstyle=\"border-width: 0; width: 100%; max-width: 600px; height: auto;\ndisplay: block;\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td class=\"one-column Btn\" style=\"padding-top: 0; padding-right: 0;\npadding-left: 0; background-color: #ffffff; text-align: center; width:\n100%; height: auto;\" border=\"0\">\n<table style=\"border-spacing: 0px; font-family: sans-serif; color: #333333;\nwidth: 100%;\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td class=\"inner\"\nstyle=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;\ntext-align:center;font-family:Arial, Helvetica, sans-serif; color:#FFFFFF;\nfont-size:22px; text-transform: uppercase; font-weight:bold;\ntext-decoration:underline;\"><a\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2337&L=983&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://www.mesdevisgratuits.fr/1422_MB_0116/img/Btn_News.jpg\" alt=\"en\nsavoir plus\" style=\"border-width:0; text-align:center;display:inline-block;\nbackground-color:#00a0dc\" height=\"43\" border=\"0\" width=\"300\" /></a></td>\n</tr>\n</tbody>\n</table>\n</td>\n</tr>\n<tr>\n<td class=\"three-column Argu\"\nstyle=\"padding-right:0;padding-left:0;text-align:center;font-size:0;padding-top:10px;padding-bottom:10px;border-bottom-width:2px;border-bottom-style:solid;border-bottom-color:#EC9600;\"><!--[if\n(gte mso 9)|(IE)]>\n<table width=\"100%\"\nstyle=\"border-spacing:0;font-family:sans-serif;color:#333333;\"\nmce_style=\"border-spacing:0;font-family:sans-serif;color:#333333;\"\nborder=\"0\"  cellpadding=\"0\" cellspacing=\"0\" >\n<tr>\n<td width=\"198\" valign=\"top\"\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"\nmce_style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"\n><![endif]-->\n<table class=\"column\" style=\"border-spacing: 0; font-family: sans-serif;\ncolor: #333333; text-align: center; width: 100%; max-width: 198px; display:\ninline-block; vertical-align: top; margin-top: 0; margin-bottom: 0;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td width=\"10\"></td>\n<td\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\">\n<table class=\"contents\" style=\"border-spacing: 0px; font-family:\nsans-serif; color: #333333; font-size: 14px; text-align: center; width:\n178px;\" border=\"0\">\n<tbody>\n<tr>\n<td\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"><img\nsrc=\"http://www.mesdevisgratuits.fr/1422_MB_0116/img/Argu_1.jpg\"\nstyle=\"border-width: 0; width: 100%; max-width: 178px; height: auto;\ndisplay: block;\" /></td>\n</tr>\n<tr valign=\"middle\">\n<td class=\"text ArguContenu inner\" style=\"background-color: #f0f0f0;\nheight: 60px; padding: 10px;\" align=\"center\" width=\"178\">Des locaux propres\n<br /> <b>&agrave; tout moment</b></td>\n</tr>\n</tbody>\n</table>\n</td>\n<td width=\"10\"></td>\n</tr>\n</tbody>\n</table>\n<!--[if (gte mso 9)|(IE)]></td>\n<td width=\"198\" valign=\"top\"\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"\nmce_style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"\n><![endif]-->\n<table class=\"column\" style=\"border-spacing: 0; font-family: sans-serif;\ncolor: #333333; text-align: center; width: 100%; max-width: 198px; display:\ninline-block; vertical-align: top; margin-top: 0; margin-bottom: 0;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td width=\"10\"></td>\n<td\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\">\n<table class=\"contents\" style=\"border-spacing: 0px; font-family:\nsans-serif; color: #333333; font-size: 14px; text-align: center; width:\n178px;\" border=\"0\">\n<tbody>\n<tr>\n<td\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"><img\nsrc=\"http://www.mesdevisgratuits.fr/1422_MB_0116/img/Argu_2.jpg\"\nstyle=\"border-width: 0; width: 100%; max-width: 178px; height: auto;\ndisplay: block;\" /></td>\n</tr>\n<tr>\n<td class=\"text ArguContenu inner\" style=\"background-color: #f0f0f0;\nheight: 60px; padding: 10px;\" align=\"center\" valign=\"middle\"\nwidth=\"178\">Une tarification <br /> <b>&agrave; la carte</b></td>\n</tr>\n</tbody>\n</table>\n</td>\n<td width=\"10\"></td>\n</tr>\n</tbody>\n</table>\n<!--[if (gte mso 9)|(IE)]></td>\n<td width=\"198\" valign=\"top\"\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"\nmce_style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"\n><![endif]-->\n<table class=\"column\" style=\"border-spacing: 0; font-family: sans-serif;\ncolor: #333333; text-align: center; width: 100%; max-width: 198px; display:\ninline-block; vertical-align: top; margin-top: 0; margin-bottom: 0;\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody>\n<tr>\n<td width=\"10\"></td>\n<td\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\">\n<table class=\"contents\" style=\"border-spacing: 0px; font-family:\nsans-serif; color: #333333; font-size: 14px; text-align: center; width:\n178px;\" border=\"0\">\n<tbody>\n<tr>\n<td\nstyle=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\"><img\nsrc=\"http://www.mesdevisgratuits.fr/1422_MB_0116/img/Argu_3.jpg\"\nstyle=\"border-width: 0; width: 100%; max-width: 178px; height: auto;\ndisplay: block;\" /></td>\n</tr>\n<tr>\n<td class=\"text ArguContenu inner\" style=\"background-color: #f0f0f0;\nheight: 60px; padding: 10px;\" align=\"center\" valign=\"middle\"\nwidth=\"178\">Des sp&eacute;cialistes <b>pr&egrave;s de votre entreprise</b>\npour s'en occuper</td>\n</tr>\n</tbody>\n</table>\n</td>\n<td width=\"10\"></td>\n</tr>\n</tbody>\n</table>\n<!--[if (gte mso 9)|(IE)]></td>\n</tr>\n</table>\n<![endif]--></td>\n</tr>\n</tbody>\n</table>\n<!--[if (gte mso 9)|(IE)]></td>\n</tr>\n</table>\n<![endif]--></div>\n</center><img\nsrc=\"http://nodes.npmd9ti3wa.com/mailing-338-9-5659?rgroup=p_10575&amp;group=\"\nheight=\"1\" border=\"0\" width=\"1\" /></a>\n<div align=\"center\"><a></a><a></a><a></a><a></a><a></a><a><font\nstyle=\"font-size: 11px; font-family: Arial,Helvetica,sans-serif; color:\n#523122;\" face=\"Arial, Helvetica, sans-serif\" color=\"#523122\" size=\"1\">Si\nvous ne souhaitez plus recevoir d'offre commerciale de notre part\n</font></a><font style=\"font-size: 11px; font-family:\nArial,Helvetica,sans-serif; color: #523122;\" face=\"Arial, Helvetica,\nsans-serif\" color=\"#523122\" size=\"1\"><a style=\"color: #523122;\"\nhref=\"http://www.supprabyte.co.uk/link.php?M=7474945&N=2337&L=1027&F=H\">suivez\nce lien <br /></a></font></div>\n<font style=\"font-size: 11px; font-family: Arial,Helvetica,sans-serif;\ncolor: #523122;\" face=\"Arial, Helvetica, sans-serif\" color=\"#523122\"\nsize=\"1\"> </font></font></div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div><img\nsrc=\"http://www.supprabyte.co.uk/open.php?M=7474945&L=326&N=2337&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_f3224b53f82e9d84e7308c170dfebdba--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_225.argentina:2,",
    "content": "Return-Path: <info@db-gc.net>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 42B7720B66F94\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 14:51:07 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 36ABB3317721; Sat,  9 Apr 2016 14:51:07 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 273723317704; Sat,  9 Apr 2016 14:51:07 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********************\nX-Spam-Status: Yes, score=20.3 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RCVD_IN_BRBL_LASTEXT,RCVD_IN_CSS,\n\tRCVD_IN_RP_RNBL,RDNS_NONE,SUBJECT_NEEDS_ENCODING,SUBJ_ILLEGAL_CHARS,\n\tTO_NO_BRKTS_NORDNS,TO_NO_BRKTS_NORDNS_HTML,T_SURBL_MULTI1,\n\tT_URIBL_BLACK_OVERLAP,URIBL_BLACK,URIBL_DBL_SPAM,URIBL_JP_SURBL,\n\tURIBL_WS_SURBL autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [185.101.94.23 listed in zen.spamhaus.org]\n\t*  1.3 RCVD_IN_RP_RNBL RBL: Relay in RNBL,\n\t*      https://senderscore.org/blacklistlookup/\n\t*      [185.101.94.23 listed in bl.score.senderscore.com]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [185.101.94.23 listed in bb.barracudacentral.org]\n\t*  1.6 URIBL_WS_SURBL Contains an URL listed in the WS SURBL blocklist\n\t*      [URIs: gl-db.com]\n\t*  1.2 URIBL_JP_SURBL Contains an URL listed in the JP SURBL blocklist\n\t*      [URIs: gl-db.com]\n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: gl-db.com]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: gl-db.com]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  1.5 SUBJ_ILLEGAL_CHARS Subject: has too many raw illegal characters\n\t*  0.0 T_URIBL_BLACK_OVERLAP T_URIBL_BLACK_OVERLAP\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 SUBJECT_NEEDS_ENCODING SUBJECT_NEEDS_ENCODING\n\t*  0.0 T_SURBL_MULTI1 T_SURBL_MULTI1\n\t*  0.0 TO_NO_BRKTS_NORDNS_HTML To: misformatted and no rDNS and HTML only\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from db-gc.net (unknown [185.101.94.23])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id C798C33181F9\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 14:51:03 +0200 (CEST)\nTo: dev@caliopen.local\nFrom: info@db-gc.net\nSubject: FW: Trova subito nuovi clienti‎.\nDate: Sat, 9 Apr 2016 14:51:03 +0200\nMessage-ID: <20160409_125103_093883.info@db-gc.net>\nX-Mailer: XMAIL\nContent-Transfer-Encoding: 8bit\nContent-Type: text/html; charset=\"utf-8\"\nMime-Version: 1.0\nX-Spam-Prev-Subject: FW: Trova subito nuovi clienti‎.\nContent-Length: 1048\nLines: 30\n\nSalve, <br /><br />\n\nOffriamo banche dati di aziende e clienti italiani potenzialmente interessati alla Vostra offerta.<br /><br />\n\nLe banche dati delle aziende sono suddivise in gruppi di destinatari, secondo le Vostre necessità.<br /><br />\n\n--<br />\nDatabase Aziende 2016 - 140 € * Scadenza promozione 08/04/2016 <br />\n--<br /><br />\n\n<a href=\"http://www.gl-db.com\">www.gl-db.com</a> \n\n<br /><br />\nI dati delle aziende contengono:<br />\n\"Nome dell'azienda, Persona di contatto, E-mail, Telefono, Fax, CAP, Località, Via, ecc.\" <br /><br />\n\nLe possibilità di utilizzo delle banche dati sono praticamente illimitate.<br />\nGrazie ad esse potrete condurre campagne pubblicitarie in modo efficace e sicuro.<br /><br />\n\nI programmi da noi creati vi permettono di preparare una campagna pubblicitaria professionale,<br />\npersonalizzando linvio delle offerte mediante e-mail o fax. <br /><br />\n\nVi invitiamo a visitare il nostro sito internet:<br /><br />\n\n<a href=\"http://www.gl-db.com\">www.gl-db.com</a>\n\nCordiali Saluti\nMarco Coletti.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_227.argentina:2,",
    "content": "Return-Path: <stein359@camarazaragoza.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 3F50B20BA201E\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 14:41:30 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 32C9133181F9; Sat,  9 Apr 2016 14:41:30 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 254F33317704; Sat,  9 Apr 2016 14:41:30 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.4 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BL_SPAMCOP_NET,\n\tRCVD_IN_BRBL_LASTEXT autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.3 RCVD_IN_BL_SPAMCOP_NET RBL: Received via a relay in bl.spamcop.net\n\t*      [Blocked - see <http://www.spamcop.net/bl.shtml?82.223.212.114>]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [82.223.212.114 listed in bb.barracudacentral.org]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\nX-Spam-DCC: : \nReceived: from camarazaragoza.com (lwxg647.servidoresdns.net [82.223.212.114])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 1C7CB33181F9\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 14:41:27 +0200 (CEST)\nDate: Sat, 9 Apr 2016 14:41:25 +0200\nFrom: stein359@camarazaragoza.com\nReply-To: <pretty@wertymail.com>\nMessage-ID: <212cc21-9ee6e-66@camarazaragoza.com>\nTo: dev@caliopen.local\nSubject: Fw:  Hiya\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Fw:  Hiya\nContent-Length: 194\nLines: 2\n\n\n<div>Hiya, I am a solitary nice baby, looking handsome person to go out with :D <br> Where are you from, do you speak english? <br> <br> please reply me if you need my photos. See ya )))</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_229.argentina:2,",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id F372220A76A10\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 12:16:30 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid E49223310011; Sat,  9 Apr 2016 12:16:30 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid D24D2331690D; Sat,  9 Apr 2016 12:16:30 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.5 required=5.0 tests=AWL,BAYES_99,HTML_MESSAGE,\n\tMPART_ALT_DIFF,NORMAL_HTTP_TO_IP,RCVD_IN_CSS,RDNS_NONE,T_KHOP_FOREIGN_CLICK\n\tautolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [63.251.255.134 listed in zen.spamhaus.org]\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.4 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server102.offerprivee.in (unknown [63.251.255.134])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 9B8EC33181FC\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 12:16:25 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: 2 articles pour le prix d'1\nMessage-ID: <894109bf7e2849ab86b3db6bbb60e309@162.244.67.108>\nDate: Sat, 09 Apr 2016 04:37:26 -0500\nFrom: \"JustFab\" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 165,173\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11226>\nX-Mailer-SID: 11226\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_ad062cc5824a44b4f36f410e0dd49ebc\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: 2 articles pour le prix d'1\nContent-Length: 9746\nLines: 249\n\n--b1_ad062cc5824a44b4f36f410e0dd49ebc\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11226&L=165&N=3653\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11226\n\n--b1_ad062cc5824a44b4f36f410e0dd49ebc\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"\nname=\"viewport\" />\n<title>JustFab</title>\n<style type=\"text/css\"><!--\n    a, a:hover, a:visited, a:link, a:active {text-decoration: none;}a\n{text-decoration: none}\n\n\n   .ReadMsgBody {width: 100%; background-color: #ffffff;}\n   .ExternalClass {width: 100%; background-color: #ffffff;}\n   body   {width: 100%; background-color: #ffffff; margin:0; padding:0;\n-webkit-font-smoothing: antialiased;font-family: Georgia, Times, serif}\n   table {border-collapse: collapse;margin: 0 auto}\n\n   @media only screen and (max-width: 640px)  {\n               body[yahoo] .deviceWidth {width:440px!important;\npadding:0;}\n               body[yahoo] .center {text-align: center!important;}\n         }\n\n   @media only screen and (max-width: 479px) {\n               body[yahoo] .deviceWidth {width:280px!important;\npadding:0;}\n               body[yahoo] .center {text-align: center!important;}\n         }\n\n    body {padding: 5px; padding-top: 0px;font-family:arial, Verdana,\nsans-serif !important;font-size:12px;}\n    [class=product] {min-width: 100% !important; padding-left: 0px\n!important; padding-right: 0px !important;}\n    [class=section_title] {padding-top: 5px !important;}\n    [class=categories_mail] {padding-left: 0px !important; padding-right:\n0px !important;}\n    [id=second] {border-top: 0px !important;}\n    [id=last] {display: none !important;}\n    [class=hide] { display:none;}\n    }\n--></style>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11226&L=165&N=3653\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<!-- Wrapper -->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"100%\" valign=\"top\" bgcolor=\"#ffffff\"\nstyle=\"padding-top:20px\"><!-- Start Header-->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nclass=\"deviceWidth\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td width=\"100%\" bgcolor=\"#ffffff\">\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nclass=\"deviceWidth\" style=\"width: 600px; height: 15px;\">\n<tbody>\n<tr>\n<td width=\"100%\" bgcolor=\"#ffffff\">\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"left\"\nclass=\"deviceWidth\">\n<tbody>\n<tr>\n<td style=\"padding:10px 20px\" class=\"center\"></td>\n</tr>\n</tbody>\n</table>\n<!-- End Logo --> <!-- Nav --> <!-- End Nav -->\n<table class=\"deviceWidth\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nalign=\"center\" bgcolor=\"#eeeeed\" style=\"width: 600px;\">\n</table>\n<!-- End One Column --></td>\n</tr>\n</tbody>\n</table>\n<!-- Logo -->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"left\"\nclass=\"deviceWidth\">\n<tbody>\n<tr>\n<td style=\"padding:10px 0px;   padding-bottom: 14px;\" class=\"center\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2015/10/15102015/JustFab_Logo.png\"\nalt=\"www.JustFab.fr\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<!-- End Logo --> <!-- Nav -->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"right\"\nclass=\"deviceWidth\">\n<tbody>\n<tr>\n<td class=\"center\" style=\"font-size: 11px; color: #272727; font-weight:\nlight; text-align: right; font-family: Arial; line-height: 20px;\nvertical-align: middle; padding:16px 20px; font-weight:bold;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">CHAUSSURES</a>\n&nbsp;&nbsp;&nbsp;                                         <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">SACS</a> &nbsp;&nbsp;&nbsp;\n                                    <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">V&Ecirc;TEMENTS</a>\n&nbsp;&nbsp;&nbsp;                                         <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">COLLECTIONS</a>\n&nbsp;&nbsp;&nbsp;                                         <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #FF0000;\">PROMOS</a></td>\n</tr>\n</tbody>\n</table>\n<!-- End Nav -->\n<table class=\"deviceWidth\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nalign=\"center\" bgcolor=\"#eeeeed\" style=\"width: 600px;\">\n</table>\n<!-- End One Column --></td>\n</tr>\n</tbody>\n</table>\n<table class=\"deviceWidth\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nalign=\"center\" bgcolor=\"#eeeeed\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_01.jpg\"\nalt=\"DOS MEJOR QUE UNO&#10;&#10;      \" border=\"0\" style=\"display: block;\nborder-radius: 4px;\" /></a></td>\n</tr>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_02.jpg\"\nalt=\"DISFR&Uacute;TALO\" /> </a></td>\n</tr>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_03.jpg\"\nalt=\"&iquest;Por qu&eacute; comprar s&oacute;lo un par cuando PUEDES TENER\n2?\" /> </a></td>\n</tr>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_04.jpg\"\nalt=\"T&uacute; que adoras los zapatos...&iexcl;Hazte miembro VIP de Justfab\ny disfruta de nuestra oferta de bienvenida: &iexcl;2 art&iacute;culos por\nel precio de 1! 2mejorque1\" /> </a></td>\n</tr>\n</tbody>\n</table>\n<!-- End One Column --></td>\n</tr>\n</tbody>\n</table>\n<center> <center>\n<p align=\"center\"><font face=\"Arial, Helvetica, sans-serif\" color=\"#666666\"\nsize=\"1\"><font color=\"#000000\"><font size=\"2\"><strong>Just<font\ncolor=\"#ff0066\">Fab</font></strong></font> | Just Fabulous - Avenida\nDiagonal 611 - 08028 Barcelona </font><br /><span\nstyle=\"font-color:grey;font-size:x-small;\">*voir conditions de l'offre sur\nle site</span><br /> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8114&F=H\"\ntarget=\"_blank\" style=\"color:#ff0066; text-decoration:underline;\">Politique\nde confidentialit&eacute;;</a> | <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8117&F=H\"\ntarget=\"_blank\" style=\"color:#ff0066;\ntext-decoration:underline;\">Contactez-nous</a><br /></font></p>\n<p><font face=\"Arial, Helvetica, sans-serif\" color=\"#666666\" size=\"1\"><br\n/></font></p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\"\nstyle=\"width: 600px;\">\n</table>\n<font face=\"Arial, Helvetica, sans-serif\" color=\"#666666\" size=\"1\">\n<table cellpadding=\"0\" cellspacing=\"0\" align=\"center\" border=\"0\"\nstyle=\"width: 600px;\">\n<tbody>\n<tr>\n<td cellpadding=\"0\" cellspacing=\"0\" width=\"479\" align=\"center\" border=\"0\"\nstyle=\"vertical-align:top;font-family:font-family: Calibri, sans-serif;\nmargin:0; font-size:11px; line-height:13px; padding-left:50px;\npadding-right:50px; -webkit-text-size-adjust: none\"><span\nclass=\"applelinks\" style=\"color:#666666; font-family:Calibri, sans-serif;\nfont-size:11px; line-height:13px; -webkit-text-size-adjust: none\"><br\n/></span> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8115&F=H\"\ntarget=\"_blank\" style=\"color:#66666; text-decoration:underline;\"><span\nstyle=\"text-decoration: underline;\"></span></a><br /><br /> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8109&F=H\"\ntarget=\"_blank\" style=\"color:#ff0066; font-family:Calibri, sans-serif;\ntext-decoration:underline;\"><span style=\"text-decoration:\nunderline;\"></span></a><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8116&F=H\"\ntarget=\"_blank\" style=\"color:#666666; text-decoration:underline;\"><span\nstyle=\"text-decoration: underline;\"></span></a></td>\n</tr>\n</tbody>\n</table>\n<link rel=\"icon\" type=\"image/png\"\nhref=\"//jf-eu-cdn.justfab.com/image/site/global/icon/favicon.ico\" />\n<link rel=\"shortcut icon\" type=\"image/png\"\nhref=\"//jf-eu-cdn.justfab.com/image/site/global/icon/favicon.ico\" />\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11226\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n<!--END FOOTER MAIL--></font>\n<p>&nbsp;</p>\n</center></center></div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=11664742&L=165&N=11226&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_ad062cc5824a44b4f36f410e0dd49ebc--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_231.argentina:2,",
    "content": "Return-Path: <mcclure218@namimanitoba.org>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 361CE20A4BCBC\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 11:42:00 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 2728F3310011; Sat,  9 Apr 2016 11:42:00 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 19B8D331690D; Sat,  9 Apr 2016 11:42:00 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.0 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BRBL_LASTEXT autolearn=no\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [184.168.46.68 listed in bb.barracudacentral.org]\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\nX-Spam-DCC: : \nReceived: from namimanitoba.org (p3nw8shg322.shr.prod.phx3.secureserver.net [184.168.46.68])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 1ABF233181FC\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 11:41:57 +0200 (CEST)\nDate: Sat, 9 Apr 2016 2:41:51 -0700\nFrom: mcclure218@namimanitoba.org\nReply-To: <lucky@dating2mail.com>\nMessage-ID: <e5e0dd3-ac551-d0@namimanitoba.org>\nTo: dev@caliopen.local\nSubject: FW:  Yo there\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: FW:  Yo there\nContent-Length: 215\nLines: 2\n\n\n<div>Yo there, I am a lonely lovely lady, searching handsome man to hang out with )) <br> Where are you from, do you speak english? <br> <br> reply me if you would like to see photos of me. See you around :D</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_233.argentina:2,",
    "content": "Return-Path: <nichols347@new-mebel72.ru>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id A66D7209CF503\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 10:25:29 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 92C633310011; Sat,  9 Apr 2016 10:25:29 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 85A6D331690D; Sat,  9 Apr 2016 10:25:29 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.0 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BRBL_LASTEXT,SPF_HELO_PASS\n\tautolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [92.53.126.72 listed in bb.barracudacentral.org]\n\t* -0.0 SPF_HELO_PASS SPF: HELO matches SPF record\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\nX-Spam-DCC: : \nReceived: from new-mebel72.ru (sylvia.timeweb.ru [92.53.126.72])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id B9F0733181FC\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 10:25:27 +0200 (CEST)\nDate: Sat, 9 Apr 2016 11:25:24 +0300\nFrom: nichols347@new-mebel72.ru\nReply-To: <lucky@dating2mail.com>\nMessage-ID: <597f016-34390-99@new-mebel72.ru>\nTo: dev@caliopen.local\nSubject: Fw:  What's up there\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Fw:  What's up there\nContent-Length: 225\nLines: 2\n\n\n<div>What's up there, I'm a lone charming puss, in search of real guy to hang out :=) <br> Where are you from, do you speak english? <br> <br> please reply me if you would like to get photos with me. See you around :)</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_235.argentina:2,",
    "content": "Return-Path: <mail@info.astucemail.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 91B1B209A30D5\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 08:49:03 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 7FDD33310011; Sat,  9 Apr 2016 08:49:03 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 6CA35331690D; Sat,  9 Apr 2016 08:49:03 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.2 required=5.0 tests=AWL,BAYES_50,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,FROM_EXCESS_BASE64,HTML_MESSAGE,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tRCVD_IN_DNSWL_NONE,URIBL_BLACK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t* -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no\n\t*      trust\n\t*      [195.154.17.233 listed in list.dnswl.org]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: noerec.com]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60%\n\t*      [score: 0.5393]\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  1.0 FROM_EXCESS_BASE64 From: base64 encoded unnecessarily\n\t*  0.5 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from mail10.mxea3.com (mail10.mxea3.com [195.154.17.233])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 7126533181FC\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 08:49:00 +0200 (CEST)\nX-QHPSI: clean\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed;\n\td=info.astucemail.com; s=default; h=Date:Message-ID:To:From:\n\tReply-To:Subject:X-Mailer:Precedence:List-Unsubscribe:\n\tMIME-Version:Content-Type:Content-Transfer-Encoding; bh=hcEaG68/\n\tphcmAa8QHl9rmAeZrHQ=; b=ntiIRW5bes3v0MXVBJVNkIwKGg/XrcNOBbLwSzFj\n\tSB1iYPE8dGoKQfWSzROHm6RCcmXFpJqyksrbIW50trL1DGg12bpJDAJL/r8G38CX\n\tJ9X7ni4+F+4CBe55k6dk2UDdZSLiNfom+dA5pQXlvb+pE+nGeAQFYH/AD6/icDI9\n\tdbI=\nReceived: (indimail 4847 invoked by uid 0); Sat, 9 Apr 2016 08:39:37 +0200\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed;\n\td=info.astucemail.com; s=default; x=1460788777; h=DomainKey-Signature:\n\tDate:Message-ID:To:From:Reply-To:Subject:X-Mailer:Precedence:\n\tList-Unsubscribe:MIME-Version:Content-Type:\n\tContent-Transfer-Encoding; bh=hcEaG68/phcmAa8QHl9rmAeZrHQ=; b=s5\n\tG0ZJOhAzZFTi882PxaYYSh4aUQ8JQeDn1+v49uCY/I3DD59LrfeDTRZA8YME2Mjb\n\tFRsoxMHeozxq09Q+9YHO9/FHG5gJEucoi3XaBx1uxTuJh5o0kXLFC821+yFazodf\n\t742UAykV2bBJa3VzDrfawqwONXPZhvEn0R2hLB/SU=\nDomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;\n    s=default; d=info.astucemail.com;\n    b=YBi21gPc5Q/bXK8UzrkoFmXtPjPgYGyR7vkhKBI8/lwLrLp5rmA5l/8YHlpNq8Z7RZZN1IkRyScMuh2GdXdjdS7oJWD89mSJ2yp2Lg1reRxNVXIEMzPC1xW/An+beWsGHC1GRFxAOGn8GnlKyZKT802yA6w+lJIc4FExWuCYr68=;\nDate: Sat, 9 Apr 2016 08:39:37 +0200\nMessage-ID: <20160409063937.4825.indimail@im3.mxea3.com>\nTo: dev@caliopen.local\nFrom: =?utf-8?B?Vm90cmUgbXV0dWVsbGU=?= <mail@info.astucemail.com>\nReply-To: mail@info.astucemail.com <mail@info.astucemail.com>\nSubject: =?utf-8?B?VHJvdXZleiB1bmUgbXV0dWVsbGUgc2VuaW9yIG1vaW5zIGNoZXJl?=\nX-Mailer: Mailxpertise\nX-List: 10\nX-Account: 187\nX-Campaign: 1568\nX-Outip: 195.154.17.233\nX-Unique: 107487\nX-Abuse: abuse@mailxpertise.com\nX-Mode: 1\nPrecedence: bulk\nList-Unsubscribe: <http://info.astucemail.com/JTI2YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNmVtJTNEbGF1cmVudCU0MGJyYWluc3Rvcm0uZnIlMjZ0JTNEYyUyNmwlM0QyOTQwNyUyNmYlM0Qy>\nMIME-Version: 1.0\nContent-Type: multipart/alternative; boundary=\"e65a887235fca42a65ffaaa285cc0b8c\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?utf-8?B?VHJvdXZleiB1bmUgbXV0dWVsbGUgc2VuaW9yIG1vaW5zIGNoZXJl?=\nContent-Length: 12166\nLines: 233\n\n--e65a887235fca42a65ffaaa285cc0b8c\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\nhttp://info.astucemail.com/YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNmwlM0QyOTQwNCUyNnMlM0QyMDM4NTY0JTI2dCUzRGMlMjZscyUzRDEwJTI2dW5zdWJzY3JpYmVfbGluayUzRCUyNm1pcnJvcl9saW5rJTNE \n\n \t\tMutuelle Senior [1]\n\n \t\t [1]\n\n \t\tPourquoi payer plus cher ?\n\n \t\tVos contrats d'assurance sont trop chers ? Ils ne sont plus adaptés à votre situation ?\n\n \t\tNous vous proposons de vous mettre en relation avec des experts qui vous guideront dans le choix\nde la bonne assurance et vous feront économiser jusqu'à 2500 euros par an en fonction du produit\nsouhaité.\n\n \t\tTROUVER UNE MUTUELLE SENIOR MOINS CHèRE [1]\n\n \t\t [1]\n\n \t\t Remboursements adaptés\n\n \t\t Comparatif en 2 minutes\n\n \t\t Gratuit et sans engagement\n\nhttp://info.astucemail.com/YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNmwlM0QyOTQwNiUyNnMlM0QyMDM4NTY0JTI2dCUzRGMlMjZscyUzRDEw \n\nLinks:\n------\n[1] http://Clicks.noerec.com/click_c(664)-s(701)-m(2076)-e(4637)?u=http%3A%2F%2Fmes-meilleures-assurances.fr%2Fmutuelle-senior%2F003%23siteKey%3Dhttpdata-factroy-santefr\n\n\n--e65a887235fca42a65ffaaa285cc0b8c\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta content=\"NOINDEX, NOFOLLOW\" name=\"ROBOTS\">\n\t<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\t<meta content=\"width=device-width; initial-scale=1.0; maximum-scale=1.0;\" name=\"viewport\">\n\t<title>Mutuelle SENIOR</title>\n\t<style type=\"text/css\">/* Reset */\n    * { margin-top: 0px; margin-bottom: 0px; padding: 0px; border: none; line-height: normal; outline: none; list-style: none; -webkit-text-size-adjust: none; -ms-text-size-adjust: none; }\n    table { border-collapse: collapse !important; padding: 0px !important; border: none !important; border-bottom-width: 0px !important; mso-table-lspace: 0pt; mso-table-rspace: 0pt; }\n    table td { border-collapse: collapse; }\n    body { margin: 0px; padding: 0px; }\n    .ExternalClass * { line-height: 100%; }\n\n    /* Responsive */\n    @media only screen and (max-width:600px) {\n\n    /* Tables\n    parameters: width, alignment, padding */\n     \n    table[class=scale] { width: 100%!important; }\n    table[class=scale90] { width: 90%!important; }\n    table[class=scale85] { width: 85%!important; }\n    table[class=scale80] { width: 80%!important; }\n    table[class=scale75] { width: 55%!important; }\n    table[class=scale-reset] { width: 100%!important; height: auto!important; }\n\n    /* Td */\n    td[class=scale-left] { width: 100%!important; text-align: left!important;}\n    td[class=scale-left-bottom] { width: 100%!important; text-align: left!important; padding-bottom: 25px!important; }\n    td[class=scale-left-top] { width: 100%!important; text-align: left!important; padding-top: 25px!important; }\n    td[class=scale-left-all] { width: 100%!important; text-align: left!important; padding-top: 25px!important; padding-bottom: 25px!important; }\n    td[class=scale-center] { width: 100%!important; text-align: center!important;}\n    td[class=scale-center-both] { width: 100%!important; text-align: center!important; padding-left: 20px!important; padding-right: 20px!important; }\n    td[class=scale-center-both-top] { width: 100%!important; text-align: center!important; padding-left: 20px!important; padding-right: 20px!important; padding-top: 25px!important; }\n    td[class=scale-center-bottom] { width: 100%!important; text-align: center!important; padding-bottom: 25px!important; }\n    td[class=scale-center-top] { width: 100%!important; text-align: center!important; padding-top: 25px!important; }\n    td[class=scale-center-all] { width: 100%!important; text-align: center!important; padding-top: 25px!important; padding-bottom: 25px!important; padding-left: 20px!important; padding-right: 20px!important; }\n    td[class=scale-center-extraall] { width: 100%!important; text-align: center!important; padding-top: 54px!important; padding-bottom: 25px!important; }\n    td[class=scale-right] { width: 100%!important; text-align: right!important;}\n    td[class=scale-right-bottom] { width: 100%!important; text-align: right!important; padding-bottom: 25px!important; }\n    td[class=scale-right-top] { width: 100%!important; text-align: right!important; padding-top: 25px!important; }\n    td[class=scale-right-all] { width: 100%!important; text-align: right!important; padding-top: 25px!important; padding-bottom: 25px!important; }\n    td[class=scale-center-extratop] { width: 100%!important; text-align: center!important; padding-top: 60px!important; }\n    td[class=reset] { height: 0px!important; }\n\n    p[class=reset] { margin-left: 0px!important; margin-right: 0px!important; }\n    p[class=reset-top] { margin-top: 0px!important; }\n\n    img[class=\"reset\"] { display: inline!important; }\n\n    }\n\t</style>\n</head>\n<body style=\"margin-top: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0; width: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%;\">\n<div style=\"text-align: center;\"><div align=\"center\"><font color=\"#000000\" style=\"font-size:11px;font-family:Verdana,Arial,Helvetica,sans-serif;\"><br>Pour visualiser correctement ce message, accédez à la <a href=\"http://info.astucemail.com/YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNmwlM0QyOTQwNSUyNnMlM0QyMDM4NTY0JTI2dCUzRGMlMjZscyUzRDEwJTI2dW5zdWJzY3JpYmVfbGluayUzRCUyNm1pcnJvcl9saW5rJTNEJTI2ZW1haWwlM0RiR0YxY21WdWRFQmljbUZwYm5OMGIzSnRMbVp5\">version en ligne</a><br><br></font></div></div>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale\" width=\"100%\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td height=\"50\" style=\"background-color: #4e9cb5 ;\">\n\t\t\t<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale\" width=\"620\">\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td>\n\t\t\t\t\t\t<table align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale\" width=\"295\">\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td class=\"scale-center-both\" height=\"60\"><a href=\"http://info.astucemail.com/YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNmwlM0QyOTQwOCUyNnMlM0QyMDM4NTY0JTI2dCUzRGMlMjZscyUzRDEw\" style=\"font-family: 'Arial', 'sans-serif';font-size: 22px; color: #fff; fontw-eight:bold;text-decoration:none; border-bottom: 2px solid #fff;\n    border-top: 2px solid #fff;\">Mutuelle Senior</a></td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\n\t\t\t\t\t\t<table align=\"right\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale\" style=\"font-family: 'Arial', 'sans-serif';font-size: 14px;\" width=\"295\">\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td class=\"scale-center-both\" height=\"60\" style=\"text-align: right; color: #ffffff;\"><a href=\"http://info.astucemail.com/YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNmwlM0QyOTQwOSUyNnMlM0QyMDM4NTY0JTI2dCUzRGMlMjZscyUzRDEw\" style=\"color: #ffffff;\"><img src=\"http://info.astucemail.com/img/1568/images/1614442513570.jpg\" style=\"width: 295px;height:auto;\" width=\"295\"></a></td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale\" width=\"100%\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td height=\"50\"></td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td height=\"50\">\n\t\t\t<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale85\" width=\"620\">\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td class=\"scale-center-both\" height=\"20\" style=\"font-family: 'Arial','sans-serif';font-size: 30px; color: #444444;\">Pourquoi payer plus cher ?</td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td height=\"20\"></td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td class=\"scale-center\" height=\"20\" style=\"font-family:  'Arial','sans-serif';font-size: 16px; color: #788891;\">Vos contrats d'assurance sont trop chers ? Ils ne sont plus adaptés à votre situation ?</td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td class=\"scale-center\" height=\"20\" style=\"font-family: 'Segoe UI', 'Segoe', 'Segoe WP', 'Helvetica Neue', 'Roboto', 'Helvetica', 'Arial', 'Tahoma', 'Verdana', 'sans-serif';font-size: 16px; color: #788891;\"></td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td class=\"scale-center\" height=\"20\" style=\"font-family: 'Arial','sans-serif';font-size: 16px; color: #788891;\">Nous vous proposons de vous mettre en relation avec des experts qui vous guideront dans le choix de la bonne assurance et vous feront économiser jusqu'à 2500 euros par an en fonction du produit souhaité.</td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td height=\"50\"></td>\n\t\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td height=\"20\">\n\t\t\t\t\t\t<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" max-width=\"400\">\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td style=\"font-family:'Arial','sans-serif';font-size: 20px; text-align: center; color: #222222;\"><a href=\"http://info.astucemail.com/YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNmwlM0QyOTQxMCUyNnMlM0QyMDM4NTY0JTI2dCUzRGMlMjZscyUzRDEw\" style=\"text-decoration: none; color: #222222; padding: 22px 21px 21px 21px; border-radius: 3px; background-color: #bad738;display:block;border-bottom:4px solid #92ac1e;\"><strong>Trouver une Mutuelle SENIOR moins chère</strong></a></td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale\" width=\"100%\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td height=\"50\" style=\"background-color:#ffffff;\"></td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td height=\"50\" style=\"background-color:#ffffff;\">\n\t\t\t<table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale\" width=\"620\">\n\t\t\t\t<tbody>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<td>\n\t\t\t\t\t\t<table align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale\" width=\"295\">\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td class=\"scale-center-both\" height=\"auto\"><a alt=\"mutuelle SENIOR\" href=\"http://info.astucemail.com/YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNmwlM0QyOTQxMSUyNnMlM0QyMDM4NTY0JTI2dCUzRGMlMjZscyUzRDEw\" style=\"display:block;border:0;\"><img src=\"http://info.astucemail.com/img/1568/images/7579106355707.png\" style=\"width: 295px; margin-bottom: -4px;\" width=\"295\"></a></td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\n\t\t\t\t\t\t<table align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"scale\" style=\"font-family: 'Arial','sans-serif';font-size: 20px; color: #444444;\" width=\"295\">\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td class=\"scale-center\" height=\"20\" style=\"text-align: left; color: #444444;\"><img src=\"http://info.astucemail.com/img/1568/images/1513169799570.png\" style=\"display:inline;top:5px;position:relative;\"> Remboursements adaptés</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td height=\"20\"></td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td class=\"scale-center\" height=\"20\" style=\"text-align: left; color: #444444;\"><img src=\"http://info.astucemail.com/img/1568/images/1398114836570.png\" style=\"display:inline;top:5px;position:relative;\"> Comparatif en 2 minutes</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td height=\"20\"></td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td class=\"scale-center\" style=\"text-align: left; color: #444444;\"><img src=\"http://info.astucemail.com/img/1568/images/1026775430570.png\" style=\"display:inline;top:5px;position:relative;\"> Gratuit et sans engagement</td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<td height=\"20\"></td>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td height=\"30\" style=\"background-color:#4e9cb5;\"></td>\n\t\t</tr>\n\t</tbody>\n</table>\n<img alt=\"\" src=\"http://Clicks.noerec.com/display_c(664)-s(701)-m(2076)-e(4637)\">\n<div style=\"text-align: center;\"><div align=\"center\"><font color=\"#000000\" style=\"font-size:11px;font-family:Verdana,Arial,Helvetica,sans-serif;\"><br>Pour cesser de recevoir nos informations sur l'adresse dev@caliopen.local, <a href=\"http://info.astucemail.com/YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNmwlM0QyOTQwNyUyNnMlM0QyMDM4NTY0JTI2dCUzRGMlMjZscyUzRDEw\">désabonnez-vous</a>.</font></div></div>\n<img src=\"http://info.astucemail.com/YSUzRDE4NyUyNmMlM0QxNTY4JTI2ZSUzRDEwNzQ4NyUyNnMlM0QyMDM4NTY0JTI2bHMlM0QxMCUyNnQlM0Rv\" alt=\"Afficher les images\" width=\"1\" height=\"1\" /></body>\n</html>\n\n\n--e65a887235fca42a65ffaaa285cc0b8c--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_237.argentina:2,",
    "content": "Return-Path: <qfzwjsy_0gz7chidgb.4g@eml-media.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id E645E209A30D5\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 08:25:41 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid CDF693310011; Sat,  9 Apr 2016 08:25:41 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 75295331690D; Sat,  9 Apr 2016 08:25:41 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.2 required=5.0 tests=BAYES_99,HTML_IMAGE_RATIO_02,\n\tHTML_MESSAGE,RAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tRCVD_IN_DNSWL_NONE autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t* -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no\n\t*      trust\n\t*      [188.130.60.119 listed in list.dnswl.org]\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\nX-Spam-DCC: : \nReceived: from mail423.audiance-net.fr (mail423.audiance-net.fr [188.130.60.119])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id D405333181FC\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 08:25:38 +0200 (CEST)\nReceived: from eml-media.fr (srv2667.sd-france.net [46.21.202.34])\n\tby srv2666.sd-france.net (Postfix) with ESMTP id 946FF156549\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 08:25:38 +0200 (CEST)\nDate: Sat, 9 Apr 2016 08:25:38 +0200\nTo: laurent <dev@caliopen.local>\nFrom: Mutuelle senior <Mutuelle_senior@eml-media.fr>\nReply-to: Mutuelle senior <qfzwjsy_0gz7chidgb.4g@eml-media.fr>\nSubject: =?utf-8?Q?Votre_sant=C3=A9_en_tout_s=C3=A9r=C3=A9nit=C3=A9_!?=\nX-campaign_id: 16546462934935203483328536109_14\nPrecedence: bulk\nX-uid_id_m: <bGF1cmVudEBicmFpbnN0b3JtLmZy=699>\nList-Unsubscribe: <http://www.eml-media.fr/d/?camp=16546462934935203483328536109_14&ms=bGF1cmVudEBicmFpbnN0b3JtLmZy>\nMessage-ID: <99fe11d5723d7a7fda5dfd0ff3ccc794@eml-media.fr>\nMIME-Version: 1.0\nContent-Type: multipart/alternative;\n\tboundary=\"Part1_99fe11d5723d7a7fda5dfd0ff3ccc794\"\nX-Spam-Prev-Subject: =?utf-8?Q?Votre_sant=C3=A9_en_tout_s=C3=A9r=C3=A9nit=C3=A9_!?=\nContent-Length: 7265\nLines: 51\n\n\n--Part1_99fe11d5723d7a7fda5dfd0ff3ccc794\nContent-Type: text/plain; charset = \"utf-8\"\nContent-Transfer-Encoding: 8bit\n\nPour visualiser et se désabonner ce message,\n \nVeuillez, copier puis coller, l'adresse URL complète ci-dessous dans la\nbarre d'adresse de votre navigateur et appuyer sur la touche \"Entrée\" de\nvotre clavier.\n\n\n\n- - - - - - - - - - - - - - - - - \n\nhttp://www.eml-media.fr/v/?camp=16546462934935203483328536109_14&ms=bGF1cmVudEBicmFpbnN0b3JtLmZy\n\n- - - - - - - - - - - - - - - - - \n\n\n\n\n--Part1_99fe11d5723d7a7fda5dfd0ff3ccc794\nContent-Type: text/html; charset = \"utf-8\"\nContent-Transfer-Encoding: 8bit\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n<title>Votre santé en tout sérénité !</title>\n</head>\n<body>\n<table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n<tbody><tr><td align=\"center\">\n<a href=\"http://www.eml-media.fr/v/?camp=16546462934935203483328536109_14&ms=bGF1cmVudEBicmFpbnN0b3JtLmZy\" target=\"_blank\" title=\"Si cet e-mail ne s'affiche pas correctement, suivez ce lien.\" style=\"color:#00F; font-family:'Arial'; font-size:12px; text-decoration:underline;\">Si cet email ne s&rsquo;affiche pas correctement, suivez ce lien.</a></td></tr><tr><td  align=\"center\">         Obtenez gratuitement une &eacute;tude personnalis&eacute;e        <img src=\"http://www.eml-media.fr/r/?rc=aHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvcHgvTmpVMlgzeGZNVFkxWDN4Zk1qZGZmRjh3WDN4Zk1BJTNEJTNEL2FIUjBjRG92TDIxMWRIVmxiR3hsTFhObGJtbHZjaTV5WldGamRHbHZiaTFoYzNOMWNtRnVZMlV1WTI5dEx3JTNEJTNE\" border=\"0\" height=\"1\" width=\"1\"> <center>     <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"760\">         <tbody><tr>             <td align=\"center\" height=\"60\" width=\"600\">                 <font style=\"color:#cb059a; font-size:10px; line-height:14px; font-family:'Century Gothic',Arial, Helvetica,\nsans-serif;\">                     <strong>Obtenez gratuitement une &eacute;tude personnalis&eacute;e</strong>                 </font>             </td>         </tr>         </tbody>     </table>          <table id=\"Tableau_01\" style=\"border:solid 1px #E6E6E6;\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" height=\"813\" width=\"650\">     \t<tbody><tr>     \t\t<td>     \t\t\t<a href=\"http://www.eml-media.fr/r/?m=bGF1cmVudEBicmFpbnN0b3JtLmZy&c=16546462934935203483328536109_14&rc=aHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL05qVTJYM3hmTVRZMVgzeGZNamRmZkY4d1gzeGZNQSUzRCUzRC9hSFIwY0RvdkwyMTFkSFZsYkd4bExYTmxibWx2Y2k1eVpXRmpkR2x2YmkxaGMzTjFjbUZ1WTJVdVkyOXRMdyUzRCUzRA==\" target=\"_blank\">     \t\t\t\t<img src=\"http://www.eml-media.fr/r/?rc=aHR0cDovL3JlYWN0aW9uLWFzc3VyYW5jZS5jb20vbWFpbGluZy9tdXR1ZWxsZS1zZW5pb3IvaW1hZ2VzL211dHVlbGxlLXNlbmlvci5qcGc=\" alt=\"Mutuelle s&eacute;nior\" border=\"0\" height=\"98\" width=\"650\"></a></td>     \t</tr>     \t<tr>     \t\t<td>     \t\t\t<a\nhref=\"http://www.eml-media.fr/r/?m=bGF1cmVudEBicmFpbnN0b3JtLmZy&c=16546462934935203483328536109_14&rc=aHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL05qVTJYM3hmTVRZMVgzeGZNamRmZkY4d1gzeGZNQSUzRCUzRC9hSFIwY0RvdkwyMTFkSFZsYkd4bExYTmxibWx2Y2k1eVpXRmpkR2x2YmkxaGMzTjFjbUZ1WTJVdVkyOXRMdyUzRCUzRA==\" target=\"_blank\">     \t\t\t\t<img src=\"http://www.eml-media.fr/r/?rc=aHR0cDovL3JlYWN0aW9uLWFzc3VyYW5jZS5jb20vbWFpbGluZy9tdXR1ZWxsZS1zZW5pb3IvaW1hZ2VzL211dHVlbGxlLXNlbmlvci0wMi5qcGc=\" alt=\"Mutuelle s&eacute;nior\" border=\"0\" height=\"156\" width=\"650\"></a></td>     \t</tr>     \t<tr>     \t\t<td>     \t\t\t<a href=\"http://www.eml-media.fr/r/?m=bGF1cmVudEBicmFpbnN0b3JtLmZy&c=16546462934935203483328536109_14&rc=aHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL05qVTJYM3hmTVRZMVgzeGZNamRmZkY4d1gzeGZNQSUzRCUzRC9hSFIwY0RvdkwyMTFkSFZsYkd4bExYTmxibWx2Y2k1eVpXRmpkR2x2YmkxaGMzTjFjbUZ1WTJVdVkyOXRMdyUzRCUzRA==\" target=\"_blank\">     \t\t\t\t<img\nsrc=\"http://www.eml-media.fr/r/?rc=aHR0cDovL3JlYWN0aW9uLWFzc3VyYW5jZS5jb20vbWFpbGluZy9tdXR1ZWxsZS1zZW5pb3IvaW1hZ2VzL211dHVlbGxlLXNlbmlvci0wMy5qcGc=\" alt=\"Mutuelle s&eacute;nior\" border=\"0\" height=\"137\" width=\"650\"></a></td>     \t</tr>     \t<tr>     \t\t<td>     \t\t\t<a href=\"http://www.eml-media.fr/r/?m=bGF1cmVudEBicmFpbnN0b3JtLmZy&c=16546462934935203483328536109_14&rc=aHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL05qVTJYM3hmTVRZMVgzeGZNamRmZkY4d1gzeGZNQSUzRCUzRC9hSFIwY0RvdkwyMTFkSFZsYkd4bExYTmxibWx2Y2k1eVpXRmpkR2x2YmkxaGMzTjFjbUZ1WTJVdVkyOXRMdyUzRCUzRA==\" target=\"_blank\">     \t\t\t\t<img src=\"http://www.eml-media.fr/r/?rc=aHR0cDovL3JlYWN0aW9uLWFzc3VyYW5jZS5jb20vbWFpbGluZy9tdXR1ZWxsZS1zZW5pb3IvaW1hZ2VzL211dHVlbGxlLXNlbmlvci0wNC5qcGc=\" alt=\"Mutuelle s&eacute;nior\" border=\"0\" height=\"132\" width=\"650\"></a></td>     \t</tr>     \t<tr>     \t\t<td>     \t\t\t<a\nhref=\"http://www.eml-media.fr/r/?m=bGF1cmVudEBicmFpbnN0b3JtLmZy&c=16546462934935203483328536109_14&rc=aHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL05qVTJYM3hmTVRZMVgzeGZNamRmZkY4d1gzeGZNQSUzRCUzRC9hSFIwY0RvdkwyMTFkSFZsYkd4bExYTmxibWx2Y2k1eVpXRmpkR2x2YmkxaGMzTjFjbUZ1WTJVdVkyOXRMdyUzRCUzRA==\" target=\"_blank\">     \t\t\t\t<img src=\"http://www.eml-media.fr/r/?rc=aHR0cDovL3JlYWN0aW9uLWFzc3VyYW5jZS5jb20vbWFpbGluZy9tdXR1ZWxsZS1zZW5pb3IvaW1hZ2VzL211dHVlbGxlLXNlbmlvci0wNS5qcGc=\" alt=\"Mutuelle s&eacute;nior\" border=\"0\" height=\"129\" width=\"650\"></a></td>     \t</tr>     \t<tr>     \t\t<td>     \t\t\t<a href=\"http://www.eml-media.fr/r/?m=bGF1cmVudEBicmFpbnN0b3JtLmZy&c=16546462934935203483328536109_14&rc=aHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL05qVTJYM3hmTVRZMVgzeGZNamRmZkY4d1gzeGZNQSUzRCUzRC9hSFIwY0RvdkwyMTFkSFZsYkd4bExYTmxibWx2Y2k1eVpXRmpkR2x2YmkxaGMzTjFjbUZ1WTJVdVkyOXRMdyUzRCUzRA==\" target=\"_blank\">     \t\t\t\t<img\nsrc=\"http://www.eml-media.fr/r/?rc=aHR0cDovL3JlYWN0aW9uLWFzc3VyYW5jZS5jb20vbWFpbGluZy9tdXR1ZWxsZS1zZW5pb3IvaW1hZ2VzL211dHVlbGxlLXNlbmlvci0wNi5qcGc=\" alt=\"Mutuelle s&eacute;nior\" border=\"0\" height=\"98\" width=\"650\"></a></td>     \t</tr>     \t<tr>     \t\t<td>     \t\t\t<a href=\"http://www.eml-media.fr/r/?m=bGF1cmVudEBicmFpbnN0b3JtLmZy&c=16546462934935203483328536109_14&rc=aHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL05qVTJYM3hmTVRZMVgzeGZNamRmZkY4d1gzeGZNQSUzRCUzRC9hSFIwY0RvdkwyMTFkSFZsYkd4bExYTmxibWx2Y2k1eVpXRmpkR2x2YmkxaGMzTjFjbUZ1WTJVdVkyOXRMdyUzRCUzRA==\" target=\"_blank\">     \t\t\t\t<img src=\"http://www.eml-media.fr/r/?rc=aHR0cDovL3JlYWN0aW9uLWFzc3VyYW5jZS5jb20vbWFpbGluZy9tdXR1ZWxsZS1zZW5pb3IvaW1hZ2VzL211dHVlbGxlLXNlbmlvci0wNy5qcGc=\" alt=\"Mutuelle s&eacute;nior\" border=\"0\" height=\"63\" width=\"650\"></a></td>     \t</tr>     </tbody></table>       </center>   <img\nsrc=\"http://www.eml-media.fr/t/?i=16546462934935203483328536109_14&m=bGF1cmVudEBicmFpbnN0b3JtLmZy&url=http://www.eml-media.fr/images/blank.jpg\" alt=\"_pspacer7\"  /></td></tr><tr><td align=\"center\">\n<a href=\"http://www.eml-media.fr/d/?camp=16546462934935203483328536109_14&ms=bGF1cmVudEBicmFpbnN0b3JtLmZy\" target=\"_blank\" title=\"Ne plus recevoir d'informations de notre part\" style=\"color:#00F; font-family:'Arial'; font-size:12px; text-decoration:underline;\">Pour se d&eacute;sabonner : Suivez ce lien.</a><br />Si ce message vous a caus&eacute; un quelconque d&eacute;rangement, nous vous prions de nous en excuser.\n</td></tr></tbody></table>\n</body>\n</html>\n\n\n\n--Part1_99fe11d5723d7a7fda5dfd0ff3ccc794--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_239.argentina:2,",
    "content": "Return-Path: <quinn107@desenvolvimento.argo.com.br>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 6DD15209A30D5\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 07:25:34 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 5AE583310011; Sat,  9 Apr 2016 07:25:34 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 4BA8F331690D; Sat,  9 Apr 2016 07:25:34 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.8 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BRBL_LASTEXT,RDNS_NONE,\n\tTO_NO_BRKTS_NORDNS autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [200.241.126.77 listed in bb.barracudacentral.org]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from desenvolvimento.argo.com.br (unknown [200.241.126.77])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id A464833181FC\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 07:25:31 +0200 (CEST)\nDate: Sat, 9 Apr 2016 5:24:43 +0000\nFrom: quinn107@desenvolvimento.argo.com.br\nReply-To: <sweet@wertymail.com>\nMessage-ID: <77d6a10-9edf0-ec@desenvolvimento.argo.com.br>\nTo: dev@caliopen.local\nSubject: Re:  Howdy-do\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Re:  Howdy-do\nContent-Length: 216\nLines: 2\n\n\n<div>Howdy-do, I'm alone funny girlie, looking for adorable man friend to have a nice time with ;=) <br> Where are you from, do you speak english? <br> <br> please reply me if you want to see my photos. Bye )</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_241.argentina:2,",
    "content": "Return-Path: <>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 2CBE8209A30D5\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 06:42:01 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 1C5B433181FC; Sat,  9 Apr 2016 06:42:01 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 08BAD331690D; Sat,  9 Apr 2016 06:42:00 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********************\nX-Spam-Status: Yes, score=20.2 required=5.0 tests=BAYES_50,FSL_HELO_BARE_IP_1,\n\tMISSING_MID,RAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tRCVD_IN_PBL,RCVD_NUMERIC_HELO,RDNS_NONE,TO_NO_BRKTS_NORDNS,TVD_RCVD_IP,\n\tTVD_RCVD_IP4,T_SURBL_MULTI1,T_URIBL_BLACK_OVERLAP,T_URIBL_SEM,T_URIBL_SEM_RED,\n\tURIBL_BLACK,URIBL_DBL_SPAM,URIBL_JP_SURBL,URIBL_SBL,URIBL_WS_SURBL\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  2.3 FSL_HELO_BARE_IP_1 FSL_HELO_BARE_IP_1\n\t*  0.0 TVD_RCVD_IP4 TVD_RCVD_IP4\n\t*  0.0 TVD_RCVD_IP TVD_RCVD_IP\n\t*  1.2 RCVD_NUMERIC_HELO Received: contains an IP address used for HELO\n\t*  3.3 RCVD_IN_PBL RBL: Received via a relay in Spamhaus PBL\n\t*      [187.136.114.116 listed in zen.spamhaus.org]\n\t*  0.0 T_URIBL_SEM_RED Contains a URI listed in urired.spameatingmonkey.net\n\t*      [URIs: perfectpillsgroup.ru]\n\t*  0.0 T_URIBL_SEM Contains a URI listed in uribl.spameatingmonkey.net\n\t*      [URIs: perfectpillsgroup.ru]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: perfectpillsgroup.ru]\n\t*  1.7 URIBL_DBL_SPAM Contains an URL listed in the DBL blocklist\n\t*      [URIs: perfectpillsgroup.ru]\n\t*  1.6 URIBL_WS_SURBL Contains an URL listed in the WS SURBL blocklist\n\t*      [URIs: perfectpillsgroup.ru]\n\t*  1.2 URIBL_JP_SURBL Contains an URL listed in the JP SURBL blocklist\n\t*      [URIs: perfectpillsgroup.ru]\n\t*  0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60%\n\t*      [score: 0.5090]\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  1.6 URIBL_SBL Contains an URL listed in the SBL blocklist\n\t*      [URIs: perfectpillsgroup.ru]\n\t*  0.5 MISSING_MID Missing Message-Id: header\n\t*  0.0 T_URIBL_BLACK_OVERLAP T_URIBL_BLACK_OVERLAP\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 T_SURBL_MULTI1 T_SURBL_MULTI1\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from 187.136.114.116 (unknown [187.136.114.116])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id 2AA0233181FC\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 06:41:56 +0200 (CEST)\nReceived: from unknown (HELO localhost) (sueli-sodre@morepharmacorp.com@158.115.60.199)\n\tby 187.136.114.116 with ESMTPA; Fri, 8 Apr 2016 21:46:39 -0700\nFrom: sueli-sodre@morepharmacorp.com\nTo: dev@caliopen.local\nSubject: Win your loved one's addiction\nDate: Fri, 8 Apr 2016 21:40:06 -0700\nX-Spam-Prev-Subject: Win your loved one's addiction\nMessage-Id: <20160409044201.08BAD331690D@mexico.brainstorm.fr>\nContent-Length: 58\nLines: 1\n\nhttp://loads.perfectpillsgroup.ru/ Celebrate your victory\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463563.5266_243.argentina:2,",
    "content": "Return-Path: <derrick@awayswinning.xyz>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id EF7D3209A30D5\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 06:05:56 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid BE0A9331770C; Sat,  9 Apr 2016 06:05:56 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid A76B9C746A4; Sat,  9 Apr 2016 06:05:56 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ***********\nX-Spam-Status: Yes, score=11.6 required=5.0 tests=BAYES_99,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,FROM_12LTRDOM,HTML_IMAGE_ONLY_16,HTML_MESSAGE,\n\tHTML_SHORT_LINK_IMG_2,INVALID_DATE,MIME_HTML_ONLY,RCVD_IN_BRBL_LASTEXT,\n\tRCVD_IN_CSS,RDNS_NONE,T_REMOTE_IMAGE autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.1 INVALID_DATE Invalid Date: header (not RFC 2822)\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [69.163.33.52 listed in zen.spamhaus.org]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [69.163.33.52 listed in bb.barracudacentral.org]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  1.1 HTML_IMAGE_ONLY_16 BODY: HTML: images with 1200-1600 bytes of words\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.0 HTML_SHORT_LINK_IMG_2 HTML is very short with a linked image\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 T_REMOTE_IMAGE Message contains an external image\n\t*  2.0 FROM_12LTRDOM From a 12-letter domain\nX-Spam-DCC: : \nReceived: from massine.awayswinning.xyz (unknown [69.163.33.52])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id C9448331770C\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 06:05:53 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha1; c=simple/simple; d=awayswinning.xyz;\n\ts=dkim; t=1460174752; bh=lXQT9tRue3Gqc+zOEqR/K46u+P0=;\n\th=To:Subject:Date:From:Message-ID;\n\tb=aBi9qutn3voR457NoPwN/UrnXTNmSL6dVC3dGQ/sMzhecclQE6C1n4tGHRBIn0OfB\n\t 6Ja5YIWo3L5sGlSrguOlijbuSDQRyuRJ3Rlx0eOaCzFwjivT5fz3GRHV/JwRq1krUe\n\t v6KXy4MM1abWVjs6T31X6iRfkMCotE6SgZ8scgBo=\nTo: laurent <dev@caliopen.local>\nSubject: Who should be our next president? Trump - Clinton\nDate: Sat, 09 Apr 2016 00:05:52 -0400 EST\nFrom: \"Vote Now\" <Derrick@awayswinning.xyz>\nMessage-ID: <201604090005.127744579767wmlnqa@awayswinning.xyz>\nX-Priority: 3\nX-Mailer: Webhosting Solutions\nMIME-Version: 1.0\nContent-Type: text/html;\nContent-Transfer-Encoding: 8bit\nX-AntiAbuse: This header was added to track abuse, please include it with any\n\tabuse report:\nX-AntiAbuse: Primary Hostname - massine.awayswinning.xyz\nX-AntiAbuse: Original Domain - brainstorm.fr\nX-AntiAbuse: Originator/Caller UID/GID - [8353 1987abebccd679c001680c82e_a07f0857] / [83 80]\nX-AntiAbuse: Sender Address Domain - massine.awayswinning.xyz\nX-Get-Message-Sender-Via: massine.awayswinning.xyz: authenticated_id: /from_h\nX-Source: /usr/bin/php\nX-Source-Args: php -q /home/drive/public_html/clients/adminkey/cron.php \nX-Source-Dir: /public_html/clients/eadminkey\nX-Spam-Prev-Subject: Who should be our next president? Trump - Clinton\nContent-Length: 1304\nLines: 27\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n<html>\n<head>\n\t<title></title>\n\t<meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\" />\n</head>\n<body>\n<div style=\"text-align: center\"><a href=\"http://www.awayswinning.xyz/1e8e1ae7858679c001680c82e4c92a_a07f0857-010101010001/C/\"><span style=\"font-family: Arial, Helvetica, sans-serif;\nfont-size: medium\"><strong>Complete the survey to unlock coupons and deals.</strong></span></a></div>\n\n<center><a href=\"http://www.awayswinning.xyz/1e8e1ae7858679c001680c82e4c92a_a07f0857-010101010001/C/\"><img src=\"http://www.awayswinning.xyz/1c80e2ba205679c001680c82e26f_a07f0857-010101010001/V/\" /></a><br />\n<br />\n<span style=\"FONT-SIZE:12px;COLOR:#666666;LINE-HEIGHT:20px;FONT-FAMILY:Arial,Helvetica,sans-serif;FONT-WEIGHT:normal\">To stop receiving emails from\nIons Media, please <a href=\"http://www.awayswinning.xyz/1e8d2d9a7b8679c001680c82e4c72a_a07f0857-010101010001/C/\">visit here</a>.<br />\nIons Media, 8000 Research Forest, Suite 115-144, The Woodlands, TX 77382</span><br />\n<br />\n<br />\n&nbsp;</center>\n</body>\n</html>\n\n\n\n\n</body></html>\n http://awayswinning.xyz/1987abebccd679c001680c82e_a07f0857/uns/970364365220416854580902445/5197730EPM78278\n3 NINE EIGHT THREE 7 N . MESSNER WAY. NEWRIVERS Aldus ARIZONA 8 5 ZERO EIGHT SIX\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_245.argentina:2,",
    "content": "Return-Path: <billing@awayswinning.xyz>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id F13FD209A30D5\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 05:30:36 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid D3361F705A9; Sat,  9 Apr 2016 05:30:36 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid BB45CF705B8; Sat,  9 Apr 2016 05:30:36 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.5 required=5.0 tests=BAYES_80,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,RCVD_IN_BL_SPAMCOP_NET,RCVD_IN_BRBL_LASTEXT,\n\tRCVD_IN_CSS,RDNS_NONE,SUBJECT_NEEDS_ENCODING autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  1.3 RCVD_IN_BL_SPAMCOP_NET RBL: Received via a relay in bl.spamcop.net\n\t*      [Blocked - see <http://www.spamcop.net/bl.shtml?69.163.33.53>]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [69.163.33.53 listed in zen.spamhaus.org]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [69.163.33.53 listed in bb.barracudacentral.org]\n\t*  2.0 BAYES_80 BODY: Bayes spam probability is 80 to 95%\n\t*      [score: 0.8767]\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 SUBJECT_NEEDS_ENCODING SUBJECT_NEEDS_ENCODING\nX-Spam-DCC: : \nReceived: from parishes.awayswinning.xyz (unknown [69.163.33.53])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 6D6A9331770C\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 05:30:32 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha1; c=simple/simple; d=awayswinning.xyz;\n\ts=dkim; t=1460172631; bh=g8Hjmh3ulnSl7oWZG/aw8Cltk10=;\n\th=From:Subject:Message-ID:To:Date;\n\tb=BwY8nk9A0AL9jLz48TPs9arNgXDR5HSm8ucbO+pEYrc0N9JLxpKCmPKlwfU4kxf9k\n\t jtyUF0qkO4my5FnOCkH+lgXRF6KnSPsiOgcTSdMOIIFs6RknH8W10PTI/Z7pBniazY\n\t C8IRXWwqmJzSz+RuXSinQdY4N+JwXWBxJXAAWXT4=\nX-Authentication-Warning: parishes.awayswinning.xyz : apache set sender to billing@awayswinning.xyz using -f\nMIME-Version: 1.0\nFrom: Christine <billing@awayswinning.xyz>\nSubject: Join An Elite Network Of Business Leaders�\nX-Uberinst: 6562gsokdfykmcxsijjobmrtfduvgybtqdycvlfigmhhcscabdckyjryiv\nMessage-ID: <19885vykodexvaxhlfewegymxemaxqaa@contact.awayswinning.xyz>\nTo: laurent <dev@caliopen.local>\nX-Priority: 3\nContent-Type: text/plain;\nX-Mailer: PHPMailer (phpmailer.sourceforge.net) [version 2.0.4]\nX-Mailer: Ubersmith\nDate: Fri, 08 Apr 2016 23:30:31 -0400\nX-PHP-Originating-Script: 48:class.phpmailer.php\nX-Spam-Prev-Subject: Join An Elite Network Of Business Leaders�\nContent-Length: 1415\nLines: 41\n\nCongratulations,\n\nI am pleased to inform you that based on your professional background, you have been selected to apply for inclusion into the International Society of\nExecutives & Professionals. Our research department nominates a handful of potential candidates based on a variety of criteria such as your current\nprofessional standing, recent accomplishments, honors/awards, published articles, as well as information present on authoritative media outlets,\nsocial networks, and professional directories. Based on this, I feel you would be a fitting addition to our elite network of professionals.\n\nAs we compile our data from a variety of secondary sources, you must verify your information by completing your application.\n\nhttp://www.awayswinning.xyz/1e87b0d60a165cc001680c82e4ba2a_57770857-010101010003/C/\n\nThere is no cost to apply or to be included. Please note, the submission deadline is Apr 8, 2016, so it's important you finalize your\napplication before\nthis date.\n\nSincerely,\n\nMisty\nManaging Director, Research & Selection Department\nISoEP\n\nCopyright © 2016 The International Society of Executives & Professionals, All rights reserved.\n\nOur mailing address is:\n1051 Franklin Avenue\nGarden City, NY 11530\n\n\n\n\n\n\nunsubscribe from this list\n\n\n\n\n\nClick Below To Unsubscribe\n http://awayswinning.xyz/19848d22a7c65cc001680c82e_57770857/U/\nTHREE NINE 8 3 SEVEN N . MESNER WAYE. NEWRIVERS AZ EIGHT 5 ZERO 8 6\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_247.argentina:2,",
    "content": "Return-Path: <>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 8222520F2D0B1\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 03:40:47 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 7062BCF230C; Sat,  9 Apr 2016 03:40:47 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 599E1D429FF; Sat,  9 Apr 2016 03:40:47 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: **************\nX-Spam-Status: Yes, score=14.7 required=5.0 tests=BAYES_60,FSL_HELO_BARE_IP_1,\n\tHK_RANDOM_FROM,MISSING_DATE,MISSING_MID,RCVD_HELO_IP_MISMATCH,RCVD_IN_PBL,\n\tRCVD_NUMERIC_HELO,RDNS_NONE,TO_NO_BRKTS_NORDNS,TVD_RCVD_IP,TVD_RCVD_IP4\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  2.3 FSL_HELO_BARE_IP_1 FSL_HELO_BARE_IP_1\n\t*  0.0 TVD_RCVD_IP4 TVD_RCVD_IP4\n\t*  0.0 TVD_RCVD_IP TVD_RCVD_IP\n\t*  1.3 HK_RANDOM_FROM From username looks random\n\t*  2.4 RCVD_HELO_IP_MISMATCH Received: HELO and IP do not match, but should\n\t*  1.2 RCVD_NUMERIC_HELO Received: contains an IP address used for HELO\n\t*  3.3 RCVD_IN_PBL RBL: Received via a relay in Spamhaus PBL\n\t*      [14.167.102.57 listed in zen.spamhaus.org]\n\t*  1.5 BAYES_60 BODY: Bayes spam probability is 60 to 80%\n\t*      [score: 0.6211]\n\t*  0.5 MISSING_MID Missing Message-Id: header\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  1.4 MISSING_DATE Missing Date: header\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from 14.185.128.200 (unknown [14.167.102.57])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id 2D005CF230C\n\tfor <dev@caliopen.local>; Sat,  9 Apr 2016 03:40:43 +0200 (CEST)\nReceived: from unknown (HELO localhost) (q47dsh657ctncd6@smartbotpro.net@215.109.201.166)\n\tby 14.185.128.200 with ESMTPA; Sat, 9 Apr 2016 08:45:14 +0700\nFrom: q47dsh657ctncd6@smartbotpro.net\nTo: dev@caliopen.local\nSubject: Revolution health mixtures\nX-Spam-Prev-Subject: Revolution health mixtures\nMessage-Id: <20160409014047.599E1D429FF@mexico.brainstorm.fr>\nDate: Sat,  9 Apr 2016 03:40:47 +0200 (CEST)\nContent-Length: 72\nLines: 1\n\nhttp://neck.cihzaqra.work/ This could ensure your intimate life greatly\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_249.argentina:2,",
    "content": "Return-Path: <duh@1337.no>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 39F0D20AB13AE\n\tfor <laurent@brasil.brainstorm.fr>; Sat,  2 Apr 2016 21:38:31 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 2C2F633181F7; Sat,  2 Apr 2016 21:38:31 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 1BF8133181FC; Sat,  2 Apr 2016 21:38:31 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.2 required=5.0 tests=BAYES_99,MSGID_RANDY,\n\tRCVD_IN_BL_SPAMCOP_NET,RCVD_IN_CSS,RCVD_IN_DNSWL_NONE,RCVD_IN_SORBS_WEB\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [191.5.84.189 listed in zen.spamhaus.org]\n\t* -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no\n\t*      trust\n\t*      [94.75.205.5 listed in list.dnswl.org]\n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.3 RCVD_IN_BL_SPAMCOP_NET RBL: Received via a relay in bl.spamcop.net\n\t*      [Blocked - see <http://www.spamcop.net/bl.shtml?191.5.84.189>]\n\t*  0.8 RCVD_IN_SORBS_WEB RBL: SORBS: sender is an abusable web server\n\t*      [191.5.84.189 listed in dnsbl.sorbs.net]\n\t*  2.6 MSGID_RANDY Message-Id has pattern used in spam\nX-Spam-DCC: : \nReceived: from jesus.1337.no (jesus.1337.no [94.75.205.5])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 3113C33181F7\n\tfor <dev@caliopen.local>; Sat,  2 Apr 2016 21:38:29 +0200 (CEST)\nReceived: from [127.0.0.1] (unknown [191.5.84.189])\n\t(Authenticated sender: duh@1337.no)\n\tby jesus.1337.no (Postfix) with ESMTPSA id 839C311E509;\n\tSat,  2 Apr 2016 21:38:29 +0200 (CEST)\nDate: Sat, 2 Apr 2016 20:38:27 +0100\nSubject: Hello\nFrom: \"Anna Johnson\" <duh@1337.no>\nTo: annie1706@aol.com, hitskovitz@hotmail.com, trisha35r@gmail.com,\n kodie30@hotmail.com, oldham@adam.com.au, falloutboy512@yahoo.com,\n klb1579@yahoo.com, agbryan@gmail.com, www.hernandezgeisy@yahoo.com,\n dev@caliopen.local\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/plain; charset=UTF-8\nMessage-ID: <julb5ve-jg64ax-97@1337.no>\nX-Spam-Prev-Subject: Hello\nContent-Length: 147\nLines: 3\n\nDo u like tohave sex with gorgeous girls? I can txt u sum gorgeous pic=\ntures if u will message me ASAP. +1720 864 1802 it's my private nmbr. =\nxx\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_253.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an5nl-0004gy-6m\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 10:48:25 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59608)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <lode.leroy@gmail.com>) id 1an4uz-0004qq-Bl\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 09:51:53 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <lode.leroy@gmail.com>) id 1an4ut-0001eT-PV\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 09:51:49 -0400\nReceived: from mail-ob0-x233.google.com ([2607:f8b0:4003:c01::233]:33504)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <lode.leroy@gmail.com>) id 1an4ut-0001eN-HF\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 09:51:43 -0400\nReceived: by mail-ob0-x233.google.com with SMTP id x3so171494346obt.0\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 06:51:43 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:date:message-id:subject:from:to;\n\tbh=uE9GwKSPmuYi7VjvykCTK4hzyedlnBk9sDxCvOQw2T8=;\n\tb=u7XogPG3IhdBt36u8JG3kc+NS69JxC7sXpNluKZBjfxrUrjq+Y7Wrpit2SVtmg+pHq\n\tOjO8StCFKsFNwkUaDaqskOiOqxIT019TfrorFTN1936Ab7yGYT8tpAW6vPOXatYELv1u\n\ti8wc0vCVg+xp+Diqm5vMgJ59dXn+Sg0r9uKjYbB9x5eX3P/zPVM4auFbyVfo103iGVDg\n\tHLcATHzA40VsPM1duTmkjspY4SloMRBrdOFMmRACBGnvdUEL+Xk2v3LR2Ef0YMMo5VSf\n\t9Xqld9M50zs4SJCrhpZFnYJDf7PH9cJf+O+6qrzpRGWEWxX+BiE8razIPZ2XYm8S9wxB\n\t6h0g==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:date:message-id:subject:from:to;\n\tbh=uE9GwKSPmuYi7VjvykCTK4hzyedlnBk9sDxCvOQw2T8=;\n\tb=fpwjeNKn1IFHt9XYwzfvI16rC3jng1Q2gikWD47NPs4E8ra9Bmj4M15U0Zhmp2r2Lv\n\tGX6ZtqkCeiMyHR2wlbmS663I/eUyPjMK8gcOEy6GriyiXRRQbNZn23T+GSCUAW7WGrUY\n\tJ/Qvm2a5vdG1erBKRCPmR9+sOUpiHkyZwnvNP7knaAo+bFEs1JBB9WMUmbEJ9cKoAWwL\n\tl2dOQW4TzpbV/kDADu/LKg6o44BBnxPwCVZwm3bOXqRcG9WEgtIpN70VUbgFIKeLvhOY\n\tYQdtpPB8xiwznEBiXmsvL8YWgwLGxsh3TQ7w0fLRojaGHVrqXCWzAo47dOoIXET2axbs\n\tVwUg==\nX-Gm-Message-State: AD7BkJK8zVjHR2C3P2Ve1/OwiYLO63R1eodHpeOEiymTJaDGyRfbY8DDoLqE2gt9koijol8RNQUvfoRuZ+kdAw==\nMIME-Version: 1.0\nX-Received: by 10.60.101.199 with SMTP id fi7mr10908426oeb.20.1459777902526;\n\tMon, 04 Apr 2016 06:51:42 -0700 (PDT)\nReceived: by 10.202.71.144 with HTTP; Mon, 4 Apr 2016 06:51:42 -0700 (PDT)\nDate: Mon, 4 Apr 2016 15:51:42 +0200\nMessage-ID: <CA+N9e5b+U7kkiHDG9+NRzwpHD1-y0cnnp8NsJv1Vsig88L-H5A@mail.gmail.com>\nSubject: pre-test windows binaries\nFrom: Lode Leroy <lode.leroy@gmail.com>\nTo: dev@caliopen.local\nContent-Type: text/plain; charset=UTF-8\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::233\nX-Mailman-Approved-At: Mon, 04 Apr 2016 10:48:24 -0400\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 13:51:53 -0000\nContent-Length: 325\nLines: 12\n\nI installed this version:\nhttp://www.russet.org.uk/scratch/emacs-25.0.92-x86_64-w64-mingw32.zip\nand when clicking the \"Emacs Guided Tour\" in the \"About GNU Emacs\"\ntext, I get this error:\n    Contacting host: www.gnu.org:80\n    libxml2 library not found\n\ncould libxml2.dll be added to the zip archive?\n\nBest regards,\n-- lode\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_255.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an9ec-0006yZ-Au\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 14:55:14 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36619)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an9eZ-0006wj-El\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:55:12 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an9eT-0004A2-HL\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:55:11 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:47771)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an9eT-000488-AM\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:55:05 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1an9eO-00068D-Im; Mon, 04 Apr 2016 20:55:02 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Lode Leroy <lode.leroy@gmail.com>\nSubject: Re: pre-test windows binaries\nReferences: <CA+N9e5b+U7kkiHDG9+NRzwpHD1-y0cnnp8NsJv1Vsig88L-H5A@mail.gmail.com>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEUODhQ5OT4SExoNDRMI\n\tCQ4MDBHdutiZAAACVUlEQVQ4jVWUwXKjMAyGDczekdLckZvcnXp9L2DuUKP3f5X9ZSDT9SQzoE+S\n\tfwnLrh2SqvO2nNvze7kW7wHWwXkX1ZaZZw9weoNrTBqd7nkJ3q2+utfVhRg1FOT+gudvsCJbjF2M\n\t2CAADRfAL8UYgztAW7PgD0sESc7Ad/UM3RmRStQSTtBdslyIX/Zs6d2KdyLiM6JhPMcLuPfex0oV\n\tIK5uDEM0WXiMEWC21CGm+A78o6Vg8xVCO2dOV/UoXAGgLSVN+g5AJ5O1pERzAX5L0H03MKNvi+p/\n\tIKfVuyUV3RcttT6IcUmZGKDUL6PxrKQT4iFtAPWTjc8VcgZNP6j76T+mwVJpHpksVVlgt3Vf+wMI\n\t+ZudhNGaxo6xiXc55YlWIZf2fPjL5+2LADTTmFviVAMsJvYvRCw62QvRc6ruRM2TPZvckccsXliE\n\t8CMamNkb2Ehe9217HXl6BhRvqX6eUOU3oc385d4KNwYifxALFLLtwHUfi5jDrf/Y9FNERi+wbA8R\n\trh+qp2w5aKvunLl/9XZ82h5vjemkkaSY8MbA3LREDzkk0Tg9ELgNFQjfYa8qmWXsxx+y4/MtfW9B\n\tAMJJ6NaiSpuiWTx6dqS67ZN5eLazO7Pv3dm9lNHpxybeWR3m09WQT5tNJOJ6yGfvibwFHNM63ekY\n\tyrVpTRMKy38NLDVRBbfgoNNGu4556dyZyibisH0/7SSFA0yefNs6XSzRA/y8J9BdaG5dt1/XxRWx\n\tlNYNNqZ6gXIAeAQb5uEE+1JqyHHv2NDqmWvR9RcYQvpzAlw8dvP8A21Umiyfwqi7AAAAAElFTkSu\n\tQmCC\nDate: Mon, 04 Apr 2016 20:55:00 +0200\nIn-Reply-To: <CA+N9e5b+U7kkiHDG9+NRzwpHD1-y0cnnp8NsJv1Vsig88L-H5A@mail.gmail.com>\n\t(Lode Leroy's message of \"Mon, 4 Apr 2016 15:51:42 +0200\")\nMessage-ID: <m337r1nr0b.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 18:55:12 -0000\nContent-Length: 642\nLines: 22\n\nLode Leroy <lode.leroy@gmail.com> writes:\n\n> I installed this version:\n> http://www.russet.org.uk/scratch/emacs-25.0.92-x86_64-w64-mingw32.zip\n> and when clicking the \"Emacs Guided Tour\" in the \"About GNU Emacs\"\n> text, I get this error:\n>     Contacting host: www.gnu.org:80\n>     libxml2 library not found\n>\n> could libxml2.dll be added to the zip archive?\n\nTry installing this version instead:\n\nhttp://emacsbinw64.sourceforge.net/\n\nIt comes with all the libraries you need for a fully features Emacs.\nIt's only up to 25.0.50, though.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_257.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anWzQ-0007Un-Sg\n\tfor mharc-dev@caliopen.local; Tue, 05 Apr 2016 15:50:16 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60093)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1anWzL-0007UZ-DH\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 15:50:15 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1anWzG-0005iE-Oq\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 15:50:11 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:38327)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1anWzG-0005hE-Ii\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 15:50:06 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1anWzE-0006YC-Cl\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 21:50:04 +0200\nReceived: from hsi-kbw-109-193-084-181.hsi7.kabel-badenwuerttemberg.de\n\t([109.193.84.181]) by main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 05 Apr 2016 21:50:04 +0200\nReceived: from esbati by\n\thsi-kbw-109-193-084-181.hsi7.kabel-badenwuerttemberg.de with\n\tlocal (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 05 Apr 2016 21:50:04 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nMail-Followup-To: dev@caliopen.local\nTo: dev@caliopen.local\nFrom: Arash Esbati <esbati@gmx.de>\nSubject: Re: pre-test windows binaries\nDate: Tue, 05 Apr 2016 21:46:39 +0200\nMessage-ID: <ne14nb$48n$1@ger.gmane.org>\nReferences: <CA+N9e5b+U7kkiHDG9+NRzwpHD1-y0cnnp8NsJv1Vsig88L-H5A@mail.gmail.com>\n\t<m337r1nr0b.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: hsi-kbw-109-193-084-181.hsi7.kabel-badenwuerttemberg.de\nMail-Copies-To: never\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 19:50:15 -0000\nContent-Length: 394\nLines: 15\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> Try installing this version instead:\n>\n> http://emacsbinw64.sourceforge.net/\n>\n> It comes with all the libraries you need for a fully features Emacs.\n> It's only up to 25.0.50, though.\n\nThere is also the pretest directory containing the latest and greatest\nversion:\n\nhttps://sourceforge.net/projects/emacsbinw64/files/pretest/\n\nBest, Arash\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_259.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anQlY-0003tb-R3\n\tfor mharc-dev@caliopen.local; Tue, 05 Apr 2016 09:11:32 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59201)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1anQlQ-0003qL-QX\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 09:11:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1anQlN-0004Nz-4M\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 09:11:24 -0400\nReceived: from cloud103.planethippo.com ([31.216.48.48]:41285)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1anQlM-0004Nn-RA\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 09:11:21 -0400\nDKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed;\n\td=russet.org.uk; s=default; h=Content-Type:MIME-Version:Message-ID:\n\tIn-Reply-To:Date:References:Subject:Cc:To:From;\n\tbh=sg9hHEaqiTaFgeWxTqXBZ21ubbc/RCfs9LaB+Oqp5IY=;\n\tb=FKnq+ge7DY6BIyA1Ev2Jz0Mn8u\n\t5wpZRg3NjJGtwz/mV3q9/CtihLEiHK4Bs2DT/NGYCzkRIhsvBlh//zASUfj7JgkE/Qj7h/lXiSwrl\n\tDGXd1PQem0YQPCSGctlzfxcURcmYO2GM71jZ2DDraomZigEeI5yncZUGaaBO0B6mr5WAISIuwNxoa\n\tIUyPA4Kw7T3Uq/QAUPRFVn7LcX175bHf5rvg4VEqN/VOAmtwloKsB4B69rqOTmbhG6eOVZz6y6TzN\n\tZGnhpWGZBMgvvgg/DcFSzGL8MRf64eST53EMy0ah1bDBAE2pvBefYotMFR5QDaGmjmT7CC8loTfwm\n\tDvDJL20w==;\nReceived: from janus-nat-128-240-225-60.ncl.ac.uk ([128.240.225.60]:32363\n\thelo=russet.org.uk) by cloud103.planethippo.com with esmtpsa\n\t(TLSv1.2:DHE-RSA-AES128-SHA:128) (Exim 4.86_1)\n\t(envelope-from <phillip.lord@russet.org.uk>)\n\tid 1anQlL-000Qqx-Qb; Tue, 05 Apr 2016 14:11:19 +0100\nFrom: phillip.lord@russet.org.uk (Phillip Lord)\nTo: Lode Leroy <lode.leroy@gmail.com>\nSubject: Re: pre-test windows binaries\nReferences: <CA+N9e5b+U7kkiHDG9+NRzwpHD1-y0cnnp8NsJv1Vsig88L-H5A@mail.gmail.com>\nDate: Tue, 05 Apr 2016 14:11:19 +0100\nIn-Reply-To: <CA+N9e5b+U7kkiHDG9+NRzwpHD1-y0cnnp8NsJv1Vsig88L-H5A@mail.gmail.com>\n\t(Lode Leroy's message of \"Mon, 4 Apr 2016 15:51:42 +0200\")\nMessage-ID: <87bn5oqjyg.fsf@russet.org.uk>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-AntiAbuse: This header was added to track abuse,\n\tplease include it with any abuse report\nX-AntiAbuse: Primary Hostname - cloud103.planethippo.com\nX-AntiAbuse: Original Domain - gnu.org\nX-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12]\nX-AntiAbuse: Sender Address Domain - russet.org.uk\nX-Get-Message-Sender-Via: cloud103.planethippo.com: authenticated_id:\n\tphillip.lord@russet.org.uk\nX-Authenticated-Sender: cloud103.planethippo.com: phillip.lord@russet.org.uk\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 31.216.48.48\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 13:11:29 -0000\nContent-Length: 1226\nLines: 31\n\nLode Leroy <lode.leroy@gmail.com> writes:\n\n> I installed this version:\n> http://www.russet.org.uk/scratch/emacs-25.0.92-x86_64-w64-mingw32.zip\n> and when clicking the \"Emacs Guided Tour\" in the \"About GNU Emacs\"\n> text, I get this error:\n>     Contacting host: www.gnu.org:80\n>     libxml2 library not found\n>\n> could libxml2.dll be added to the zip archive?\n\n\nIt could be, but the reason for not doing this is that I then have to\ndistribute the source for libxml2.dll. And, of course, any dependencies.\nAnd, if libxml2, why not gnutls, libjpeg and all the others (again, with\nall their source). I think it is mainly for this reason that the dlls\nhaven't been distributed previously with the exception of libXpm --\nalthough Emacs can even function without this.\n\nThe only solution I know of is that, you can unpack my binaries on top\nof a msys2 installation and all should work.\n\nI find this not an ideal solution at the moment, as it makes Emacs\ninstallation painful. I do have a better potential solution in mind:\nwhich is to write an ELPA package which pulls all the relevant DLLs\nstraight down from the msys repositories. This way, we don't have to\ndistribute any DLLs at all, so source distribution isn't needed.\n\nPhil\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_261.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an9Tl-0002bZ-T0\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 14:44:01 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59046)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <athird@googlemail.com>) id 1an9Tk-0002bG-3E\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:44:00 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <athird@googlemail.com>) id 1an9Tg-0000FC-1v\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:44:00 -0400\nReceived: from mail-lb0-x229.google.com ([2a00:1450:4010:c04::229]:35934)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <athird@googlemail.com>) id 1an9Tf-0000F6-Pb\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:43:55 -0400\nReceived: by mail-lb0-x229.google.com with SMTP id qe11so174162777lbc.3\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 11:43:55 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=googlemail.com; s=20120113;\n\th=sender:date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:in-reply-to:user-agent;\n\tbh=hF7/zppHm3lyvx8CTiv91/PHpvr4V3pCCtRFIHk0Z6o=;\n\tb=bPdL3h7vBBsSrdO6tmSKzbrYxXwc6U9drIpkAH2hPrEaV+wBdvNiJhenTNkov5Ao+J\n\tXEq6X71UM9UVH0dSLW2EDr5T5iSRYlFoLrg5s9aWzH69/AyB8SH4V7f0TWB/nO9hLGtE\n\tAFr6ALiMnR2+sIejthb91sSW8U45qRskCas0Ar0bzwQH6Z+zl/v4tHJOltx22QIrSN+k\n\tY4UQlFqI6s4mOTekKP+t0otEhKx9b8I7GO3Ip3LbMuKRyfI7Abf728yOZ6izO2cH0Zss\n\tPz2yYLXp6hahwSsM3eJcuLuS1sGCY1CS8AFEy5bFBHf27UwQ1g2mG2e7QnRU2kH1BKo1\n\tt7ow==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:date:from:to:cc:subject:message-id\n\t:references:mime-version:content-disposition:in-reply-to:user-agent;\n\tbh=hF7/zppHm3lyvx8CTiv91/PHpvr4V3pCCtRFIHk0Z6o=;\n\tb=km6CQZQDIiZVFJn11Y0U4QhS9IERyCIugweFXvCuDPDKGlBpYO+55g2V/Z6b+szUlL\n\taxhc8E1ac43BBtMxbf6IOQfZTvnZinmjzVFzx38gTZP4sD2pGPs3My9wshcTF4u3CEqz\n\t7fb3jJu15vCFpRuFgJsL+rpN6R4Anbg6E5uSJBa7JIW3gWFMBwTkEuvViKLAalLZDHh9\n\tNkbRYU+jge2eFmYdSzWY/ANU34xX3zeAj5EwjLZBn85Xlh2vwtJHqyPcNAPKZBE8gqO0\n\tafJwIsV7V3/B/uTerAGhALsQO8grVvoFUDFrI1zXlv59BcIrz8muxPwy+EkNlP4n9haH\n\txOMg==\nX-Gm-Message-State: AD7BkJLa8IchipCstSsSQLeJ1KsPTE5MaTMBz2g4RuzUU6uDvxV/e962IhtUeA4dF38zRA==\nX-Received: by 10.28.137.139 with SMTP id l133mr13898818wmd.1.1459795434523;\n\tMon, 04 Apr 2016 11:43:54 -0700 (PDT)\nReceived: from breton.local ([2001:8b0:3f8:8129:b0b6:f36f:fd4e:bbbd])\n\tby smtp.gmail.com with ESMTPSA id\n\t73sm15315505wmy.22.2016.04.04.11.43.53\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 04 Apr 2016 11:43:53 -0700 (PDT)\nSender: Alan Third <athird@googlemail.com>\nDate: Mon, 4 Apr 2016 19:43:51 +0100\nFrom: Alan Third <alan@idiocy.org>\nTo: Lode Leroy <lode.leroy@gmail.com>\nSubject: Re: pre-test windows binaries\nMessage-ID: <20160404184351.GA16912@breton.local>\nReferences: <CA+N9e5b+U7kkiHDG9+NRzwpHD1-y0cnnp8NsJv1Vsig88L-H5A@mail.gmail.com>\nMIME-Version: 1.0\nContent-Type: text/plain; charset=us-ascii\nContent-Disposition: inline\nIn-Reply-To: <CA+N9e5b+U7kkiHDG9+NRzwpHD1-y0cnnp8NsJv1Vsig88L-H5A@mail.gmail.com>\nUser-Agent: Mutt/1.5.24 (2015-08-30)\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c04::229\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 18:44:01 -0000\nContent-Length: 491\nLines: 17\n\nOn Mon, Apr 04, 2016 at 03:51:42PM +0200, Lode Leroy wrote:\n> I installed this version:\n> http://www.russet.org.uk/scratch/emacs-25.0.92-x86_64-w64-mingw32.zip\n> and when clicking the \"Emacs Guided Tour\" in the \"About GNU Emacs\"\n> text, I get this error:\n>     Contacting host: www.gnu.org:80\n>     libxml2 library not found\n> \n> could libxml2.dll be added to the zip archive?\n\nYou should be able to grab lixml from here:\n\nhttps://sourceforge.net/projects/ezwinports/files/\n\n-- \nAlan Third\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_263.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anB4i-00042m-SJ\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 16:26:16 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60847)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1anB4g-000423-AE\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:26:15 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1anB4c-0001WV-RH\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:26:14 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:53905)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1anB4c-0001WR-Oi; Mon, 04 Apr 2016 16:26:10 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3290\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1anB4b-0004M9-Ks; Mon, 04 Apr 2016 16:26:10 -0400\nDate: Mon, 04 Apr 2016 23:25:45 +0300\nMessage-Id: <83d1q5nmt2.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Alan Third <alan@idiocy.org>\nIn-reply-to: <20160404184351.GA16912@breton.local> (message from Alan Third on\n\tMon, 4 Apr 2016 19:43:51 +0100)\nSubject: Re: pre-test windows binaries\nReferences: <CA+N9e5b+U7kkiHDG9+NRzwpHD1-y0cnnp8NsJv1Vsig88L-H5A@mail.gmail.com>\n\t<20160404184351.GA16912@breton.local>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: lode.leroy@gmail.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 20:26:15 -0000\nContent-Length: 646\nLines: 20\n\n> Date: Mon, 4 Apr 2016 19:43:51 +0100\n> From: Alan Third <alan@idiocy.org>\n> Cc: dev@caliopen.local\n> \n> On Mon, Apr 04, 2016 at 03:51:42PM +0200, Lode Leroy wrote:\n> > I installed this version:\n> > http://www.russet.org.uk/scratch/emacs-25.0.92-x86_64-w64-mingw32.zip\n> > and when clicking the \"Emacs Guided Tour\" in the \"About GNU Emacs\"\n> > text, I get this error:\n> >     Contacting host: www.gnu.org:80\n> >     libxml2 library not found\n> > \n> > could libxml2.dll be added to the zip archive?\n> \n> You should be able to grab lixml from here:\n> \n> https://sourceforge.net/projects/ezwinports/files/\n\nNo, not for the 64-bit build of Emacs.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_265.argentina:2,",
    "content": "Return-Path: <avery715@zspswidwin.pl>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id A7C81371DCF2C\n\tfor <laurent@brasil.brainstorm.fr>; Tue,  5 Apr 2016 14:26:23 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 96DB4F7EEFD; Tue,  5 Apr 2016 14:26:23 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 7C3853316913; Tue,  5 Apr 2016 14:26:23 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.0 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BRBL_LASTEXT,SPF_HELO_PASS\n\tautolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [89.161.226.224 listed in bb.barracudacentral.org]\n\t* -0.0 SPF_HELO_PASS SPF: HELO matches SPF record\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\nX-Spam-DCC: : \nReceived: from zspswidwin.pl (cloudserver045436.home.net.pl [89.161.226.224])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 8FD04F7EEFD\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 14:26:18 +0200 (CEST)\nDate: Tue, 5 Apr 2016 14:26:16 +0200\nFrom: avery715@zspswidwin.pl\nReply-To: <girl@dating2mail.com>\nMessage-ID: <d12d2a8-567e3-5e@zspswidwin.pl>\nTo: dev@caliopen.local\nSubject: Fw:  Hello\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Fw:  Hello\nContent-Length: 207\nLines: 2\n\n\n<div>Hello, I am a lonesome nice girl, searching handsome person to hang out with ) <br> Where are you from, do you speak english? <br> <br> please reply me if you want to see my photos. Good bye ;-)</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_267.argentina:2,",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 1828E20F7899D\n\tfor <laurent@brasil.brainstorm.fr>; Tue,  5 Apr 2016 13:05:30 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 0964E1994A7B; Tue,  5 Apr 2016 13:05:30 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid ECCCF3316913; Tue,  5 Apr 2016 13:05:29 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *****\nX-Spam-Status: Yes, score=6.0 required=5.0 tests=AWL,BAYES_99,\n\tHTML_IMAGE_RATIO_02,HTML_MESSAGE,MPART_ALT_DIFF,NORMAL_HTTP_TO_IP,RCVD_IN_CSS,\n\tT_KHOP_FOREIGN_CLICK autolearn=no version=3.3.2\nX-Spam-Report: \n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [63.251.243.215 listed in zen.spamhaus.org]\n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  0.4 HTML_IMAGE_RATIO_02 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.3 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server102.privee-du.in (server102.privee-du.in [63.251.243.215])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id B1A551A32D9C\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 13:05:24 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: =?UTF-8?B?Vm90cmUgbnVpdCBkZSByw6p2ZSDDoCAtNjAl?=\nMessage-ID: <4c0ef5d3887b3e7f357319b6ae27984b@162.244.67.108>\nDate: Tue, 05 Apr 2016 05:13:46 -0500\nFrom: \"=?UTF-8?B?VmVudGUgcHJpdsOpZSBleGNsdXNpdmUgQnJpY28gUHJpdsOp?=\" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 181,180\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&L=180&N=10999>\nX-Mailer-SID: 10999\nX-Mailer-Sent-By: 2\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_396305efebf1274a5f6e4b3e0566c8fb\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?UTF-8?B?Vm90cmUgbnVpdCBkZSByw6p2ZSDDoCAtNjAl?=\nContent-Length: 9346\nLines: 233\n\n--b1_396305efebf1274a5f6e4b3e0566c8fb\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&S=10999&L=180&N=3569\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&L=180&N=10999\n\n--b1_396305efebf1274a5f6e4b3e0566c8fb\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<title>&nbsp;</title>\n<base target=\"_blank\"></base>\n<title></title>\n<style type=\"text/css\"><!--\n .ReadMsgBody {width: 100%;}\n .ExternalClass {width: 100%;}\n    td { border-collapse: collapse; }\n--></style>\n<center> <!-- Save for Web Slices (Mail-BP-Relaxima.psd) -->\n<link href=\"https://fonts.googleapis.com/css?family=Oswald:400,700,300\"\nrel=\"stylesheet\" type=\"text/css\" />\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&S=10999&L=180&N=3569\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td width=\"600\" height=\"49\" bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10999&L=14215&F=H\"\nstyle=\"text-decoration:none;\"> <font face=\"Arial, Trebuchet MS, sans-serif\"\ncolor=\"#ffffff\" style=\"font-size:14px;\"> <span style=\"text-decoration:\nunderline;\">Jusqu'&agrave; -62% sur les MATELAS RELAXIMA</span> </font>\n</a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#000\"\nstyle=\"background-color:#000;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10999&L=14215&F=H\"\nstyle=\"text-align:center;vertical-align:middle;font-weight:bold;\nfont-family:Arial, Verdana,\nsans-serif;color:#ffffff;text-decoration:none;font-size:25px;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_02.png\"\nwidth=\"600\" height=\"83\" border=\"0\" alt=\"bricoprive.com\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ec008b\"\nstyle=\"background-color:#ec008b;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10999&L=14215&F=H\"\nstyle=\"text-align:center;vertical-align:middle;font-weight:bold;\nfont-family:Arial, Verdana,\nsans-serif;color:#ffffff;text-decoration:none;font-size:18px;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_03.png\"\nwidth=\"285\" height=\"70\" border=\"0\" alt=\"Relaxima\" /></a></td>\n<td width=\"299\" height=\"70\" bgcolor=\"#ec008b\"\nstyle=\"background-color:#ec008b;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10999&L=14215&F=H\"\nstyle=\"text-decoration:none;\"> <font face=\"Arial, Trebuchet MS, sans-serif\"\ncolor=\"#fff\" style=\"font-size:20px;\"> <strong>Offre valable jusqu'au\n06/03</strong> </font> </a></td>\n<td bgcolor=\"#ec008b\"\nstyle=\"background-color:#ec008b;text-align:center;vertical-align:middle;\"><img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_05.png\"\nwidth=\"16\" height=\"70\" /></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_06.png\"\nwidth=\"600\" height=\"16\" /></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_07.png\"\nwidth=\"16\" height=\"63\" /></td>\n<td width=\"568\" height=\"63\" bgcolor=\"#ffffff\"\nstyle=\"background-color:#ffffff;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10999&L=14215&F=H\"\nstyle=\"text-decoration:none;\"> <font color=\"#f95e00\"\nstyle=\"font-size:26px;font-family: 'Oswald', sans-serif;font-weight:700;\">\nMATELAS RELAXIMA </font> <font color=\"#000\"\nstyle=\"font-size:26px;font-family: 'Oswald', sans-serif;font-weight:700;\">\n&agrave; prix canon </font> </a></td>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_09.png\"\nwidth=\"16\" height=\"63\" /></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_10.png\"\nwidth=\"114\" height=\"101\" /></td>\n<td bgcolor=\"#fe6700\"\nstyle=\"background-color:#fe6700;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10999&L=14215&F=H\"\nstyle=\"text-align:center;vertical-align:middle;font-weight:bold;\nfont-family:Arial, Verdana,\nsans-serif;color:#ffffff;text-decoration:none;font-size:30px;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_11.png\"\nwidth=\"392\" height=\"101\" border=\"0\" alt=\"Je d&eacute;couvre &gt;\"\n/></a></td>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_12.png\"\nwidth=\"94\" height=\"101\" /></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10999&L=14215&F=H\"\nstyle=\"text-align:center;vertical-align:middle;font-weight:bold;\nfont-family:Arial, Verdana,\nsans-serif;color:#ffffff;text-decoration:none;font-size:30px;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_13.png\"\nwidth=\"600\" height=\"208\" border=\"0\" alt=\"jusqu'&agrave; -62%\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10999&L=14215&F=H\"\nstyle=\"text-decoration:none;\"> <img style=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_14.png\"\nwidth=\"600\" height=\"233\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_15.png\"\nwidth=\"33\" height=\"72\" /></td>\n<td width=\"541\" height=\"72\" bgcolor=\"#ffffff\"\nstyle=\"background-color:#ffffff;text-align:left;vertical-align:middle;\"><font\nface=\"Helvetica, Arial, Trebuchet MS, sans-serif\" color=\"#000000\"\nstyle=\"font-size:10px;\"> Bricopriv&eacute; est le site de ventes\npriv&eacute;es d&eacute;di&eacute;es au bricolage et au jardinage. Large\ns&eacute;lection de marques et produits &agrave; prix ultra\ncomp&eacute;titifs. Outils, perceuses, tondeuses, &eacute;quipement de\nprotection... Brico Priv&eacute;, le premier site de ventes priv&eacute;es\nd&eacute;di&eacute;es bricolage. </font></td>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_17.png\"\nwidth=\"26\" height=\"72\" /></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#424242\"\nstyle=\"text-align:center;background-color:#424242;\"><img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/BricoPRIVE/2016-03-01/images/Mail-BP-Relaxima_18.png\"\nwidth=\"600\" height=\"35\" /></td>\n</tr>\n</tbody>\n</table>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&L=180&N=10999\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n<img src=\"http://impfr.tradedoubler.com/imp?type(inv)g(23171116)a(2814831)\"\nborder=\"0\" /> </center> <!-- End Save for Web Slices --></div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=15236122&L=180&N=10999&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_396305efebf1274a5f6e4b3e0566c8fb--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_269.argentina:2,",
    "content": "Return-Path: <white120@gadget.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 5568E20F18F27\n\tfor <laurent@brasil.brainstorm.fr>; Tue,  5 Apr 2016 12:58:12 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 4D00B4E714C3; Tue,  5 Apr 2016 12:58:12 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 40ADCFAAB38; Tue,  5 Apr 2016 12:58:12 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.6 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BRBL_LASTEXT,RDNS_NONE,\n\tSPF_HELO_SOFTFAIL,TO_NO_BRKTS_NORDNS autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [64.62.195.24 listed in bb.barracudacentral.org]\n\t*  0.7 SPF_HELO_SOFTFAIL SPF: HELO does not match SPF record (softfail)\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from gadget.com (unknown [64.62.195.24])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id D24624E714C8\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 12:58:08 +0200 (CEST)\nDate: Tue, 5 Apr 2016 3:58:03 -0700\nFrom: white120@gadget.com\nReply-To: <flower@wertymail.com>\nMessage-ID: <02ecc06-0af38-ff@gadget.com>\nTo: dev@caliopen.local\nSubject: Re:  Hi-ya\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Re:  Hi-ya\nContent-Length: 207\nLines: 2\n\n\n<div>Hi-ya, I'm all alone charming babe, searching a nice man friend to have a nice time with :D <br> Where are you from, do you speak english? <br> <br> please reply me if you need my photos. Bye ;)</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_271.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amuQD-00073j-1v\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 22:39:21 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:33142)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raman@google.com>) id 1amuQA-00072s-HN\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 22:39:19 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raman@google.com>) id 1amuQ5-0004CC-J7\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 22:39:18 -0400\nReceived: from mail-pf0-x234.google.com ([2607:f8b0:400e:c00::234]:36715)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raman@google.com>) id 1amuQ5-0004C8-Ab\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 22:39:13 -0400\nReceived: by mail-pf0-x234.google.com with SMTP id e128so112043481pfe.3\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 19:39:11 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113;\n\th=from:mime-version:content-transfer-encoding:message-id:date:to:cc\n\t:subject; bh=c0jAU/X1I0NkQa8c5X+qSKVxUUi7z/LFPbLcQXIuuPY=;\n\tb=lY+HiecHoPj5TS+oxvJTTt8TWLBGGtF4eGzsE94+9osX+PpCx2QfIy4jdTbldFsa21\n\tw3rCsx2Gc2tl/ga2FJIiz8syF8h9csvHbFrZjP9SQEKjtn/7/bD19/NOkmEnT6sNcaOZ\n\t9wgNX4TMsq87wv5yKhKKRChVwxWSOz3d5+F38FqQ0LGVUOBICTe9kbu4QhAQsjlYVv/A\n\tWd9uhm6x1HjUiK3KTheJ37j1lRFpcEkOXs5pPgGC3dlNWMQgfGvkVXfj3YgMLyJyxyrZ\n\tXfjOQZxfwPWNCtT9QvaBwYfdagd1plXj9LkUQPOrSSQXCEm0pXQzc3ubRnhiqzPZxOOB\n\t1nWw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:mime-version:content-transfer-encoding\n\t:message-id:date:to:cc:subject;\n\tbh=c0jAU/X1I0NkQa8c5X+qSKVxUUi7z/LFPbLcQXIuuPY=;\n\tb=W7cYsQbZz8ws6lXpNG5nen7O/A67lYeTz1nTb0nw4CC3hV+Sy5X8s12AuW2b1s/sFS\n\thqAWRsi1ErUktdT1gcqomFcABIUU9BwF3u5uetdTHEJhyvLpOyyiGfjBn4TOieYAHidC\n\tikDD42QbmuqHSjl2ISrir4QX4OvbUh5+wOBnI34auv9ZceeZGEyUNXludo+7R1Xt/MfO\n\tjgoMZnyY/OxkFulORY0Oe9HFP5foJD0QlXyTjJFdQIZX7Km9MGLivLE+TpmEkyhUJthw\n\t24BFS1jDIMOWho8Iz3qDsU6gdwrH+5gr1kNHm7FWv8PiaLzP/6L2Lf6dd8OxRNCzSrwU\n\tUOMg==\nX-Gm-Message-State: AD7BkJJW9joLiNYP1ia3w6dr/a+DALydrSkNHRmYsUjGmsLVxuObDgbq14UDtFF5P2aCD8m0\nX-Received: by 10.98.33.74 with SMTP id h71mr16765022pfh.157.1459737550392;\n\tSun, 03 Apr 2016 19:39:10 -0700 (PDT)\nReceived: from raman-glaptop2 (c-73-170-121-60.hsd1.ca.comcast.net.\n\t[73.170.121.60]) by smtp.gmail.com with ESMTPSA id\n\tp26sm35039268pfi.84.2016.04.03.19.39.09\n\t(version=TLS1_2 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 19:39:09 -0700 (PDT)\nFrom: raman@google.com\nMIME-Version: 1.0\nContent-Type: text/plain; charset=us-ascii\nContent-Transfer-Encoding: 7bit\nMessage-ID: <22273.54220.381234.312729@gargle.gargle.HOWL>\nDate: Sun, 3 Apr 2016 19:39:08 -0700\nTo: dev@caliopen.local\nCC: \nSubject: Should outline-minor-mode be globalized?\nX-Mailer: VM 8.1.1 under 25.0.92.1 (x86_64-unknown-linux-gnu)\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:400e:c00::234\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 02:39:19 -0000\nContent-Length: 284\nLines: 10\n\nIt's nice to be able to globally turn on font-lock mode; on a similar\nnote, it would be nice to be able to turn on outline-minor-mode\nglobally -- rather than   setting mode-specific hooks to turn on\noutline-minor-mode. \n\nIs there any risk in globalizing outline-minor-mode?\n-- \n\n-- \n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_273.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amwsR-0006QQ-5S\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 01:16:39 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:58289)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amwsO-0006NF-Rv\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:16:37 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amwsL-0007QG-Mo\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:16:36 -0400\nReceived: from mail-oi0-x236.google.com ([2607:f8b0:4003:c06::236]:36144)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amwsL-0007Q6-I3\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:16:33 -0400\nReceived: by mail-oi0-x236.google.com with SMTP id y204so36796602oie.3\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 22:16:33 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=AtqSdrYy8yMCW1t6W6OCa1Z3HWVYt7IaiCRk+6/jMUI=;\n\tb=OkSwrpbdJy7QpX9CUJ+CYo23KTjJ964H62Uln1FPvhgmxF95stwsVDN5A5umfpA6fX\n\tDRqjtiWxF72EFhbgmefxHTMjaPd7TD7ewojQHDGraiwLUwP1t62BOMkYAQLEiAiO6Fz3\n\tPjXevnq/dTd5oduuzgQxw9YRzViluPkEtGMlTbbhxLs0aD+8mxffhK3olwya6z/tGCVv\n\tbNaG5g8qN9ARKMZ7/JSNNb92PptSCg4/pt/lknuGnj8geyQZJw32cJ7HMKXfRhtRFoqu\n\tJBQ21KVpNV9WZXl6cKtUDW9PL8ghyWppVGHA6q+tEW/D3+3hFdvDmKFkYBMbvlK3O0i2\n\tALgQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=AtqSdrYy8yMCW1t6W6OCa1Z3HWVYt7IaiCRk+6/jMUI=;\n\tb=a/mVNKmrsWiNDw588zC+aUipazvE34gzzOTLJA7Bn8DE21/pBouGKTy3VHWB4pDH2o\n\t/y6/Ug1SwPOaHBcaOMIdiuESfFlzTo62V4mAHu0jEH4FU0MNmwv5PKXKY10iETDu9EUp\n\tljRnpMLEBZji8i70dTvsPBrb3EGmYgB72bS9RvE9kioWXvu2QAWW2zY0rGVK3nXhVIR4\n\tCSh6B22VEB3tmv8IPajJBHW0dskqWZISOWKFzbZegXklLAd/YNXrzAW35XL8h0fJose+\n\tx0UAO465EzE+ee08b526orYCVdK4qzocPo8aon/jPPT2Y2V1Tx0XjpPZC/K8e94dD4As\n\tPnPg==\nX-Gm-Message-State: AD7BkJJRW2VqqRL5G/KhiN0HyNh6i2Ts58kyVYKMtzcCyfpaD1u+YrDAya96rKvoSKRTBw==\nX-Received: by 10.157.46.19 with SMTP id q19mr9588269otb.19.1459746992935;\n\tSun, 03 Apr 2016 22:16:32 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79]) by smtp.gmail.com with ESMTPSA id\n\ta67sm7946576otc.15.2016.04.03.22.16.31\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 22:16:31 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid 0E98113DB0809; Sun,  3 Apr 2016 22:16:31 -0700 (PDT)\nTo: raman@google.com\nSubject: Re: Should outline-minor-mode be globalized?\nIn-Reply-To: <22273.54220.381234.312729@gargle.gargle.HOWL> (raman's message\n\tof \"Sun, 3 Apr 2016 19:39:08 -0700\")\nDate: Sun, 03 Apr 2016 22:16:27 -0700\nMessage-ID: <m260vyar84.fsf@newartisans.com>\nReferences: <22273.54220.381234.312729@gargle.gargle.HOWL>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: raman@google.com, emacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::236\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 05:16:37 -0000\nContent-Length: 635\nLines: 16\n\n>>>>> raman  <raman@google.com> writes:\n\n> It's nice to be able to globally turn on font-lock mode; on a similar note,\n> it would be nice to be able to turn on outline-minor-mode globally -- rather\n> than setting mode-specific hooks to turn on outline-minor-mode.\n\n> Is there any risk in globalizing outline-minor-mode?\n\nWouldn't it really just be applicable in text-mode derived buffers? I have a\nhard time seeing its value when turned on for dired, Gnus, *Help*,\ncompilation, etc.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_275.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an3nl-0004iZ-Fk\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 08:40:17 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:44619)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1an3ne-0004eu-Iq\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 08:40:16 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1an3nZ-0002Yg-K4\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 08:40:10 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:58264)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1an3nZ-0002YJ-EI\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 08:40:05 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1an3nX-0002KY-FN\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:40:03 +0200\nReceived: from 69-165-138-79.dsl.teksavvy.com ([69.165.138.79])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 14:40:03 +0200\nReceived: from monnier by 69-165-138-79.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 14:40:03 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Should outline-minor-mode be globalized?\nDate: Mon, 04 Apr 2016 08:39:18 -0400\nMessage-ID: <jwvd1q51re4.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <22273.54220.381234.312729@gargle.gargle.HOWL>\n\t<m260vyar84.fsf@newartisans.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-165-138-79.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:e6ZiQhJn7yaejnDNrEX0zKH+oDE=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 12:40:16 -0000\nContent-Length: 608\nLines: 18\n\n>> It's nice to be able to globally turn on font-lock mode; on a similar note,\n>> it would be nice to be able to turn on outline-minor-mode globally -- rather\n>> than setting mode-specific hooks to turn on outline-minor-mode.\n>> Is there any risk in globalizing outline-minor-mode?\n\n> Wouldn't it really just be applicable in text-mode derived buffers?\n\nActually I wouldn't expect it to be very much used in text-mode buffers.\nThese use-cases are probably better supported by outline-mode or org-mode.\n\nBut maybe\n\n    (add-hook 'prog-mode-hook #'outline-minor-mode)\n\ncovers Raman's needs?\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_277.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anSvh-0002fX-Q2\n\tfor mharc-dev@caliopen.local; Tue, 05 Apr 2016 11:30:09 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:53517)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raman@google.com>) id 1anSvf-0002bL-9h\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 11:30:08 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raman@google.com>) id 1anSvc-0006Ha-18\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 11:30:07 -0400\nReceived: from mail-io0-x230.google.com ([2607:f8b0:4001:c06::230]:34379)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raman@google.com>) id 1anSvb-0006HF-T5\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 11:30:03 -0400\nReceived: by mail-io0-x230.google.com with SMTP id 2so23083219ioy.1\n\tfor <dev@caliopen.local>; Tue, 05 Apr 2016 08:30:03 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=ExbSxB+zKKYPyI7LKDukTWjHmOj2GCsQsY0uIhhWX3Y=;\n\tb=QaURdyhw4LCZeKpAhdacjgpzmxIikPa7Bm+kP7opEBk2QHO6f24B5E+zb6yKTxodBV\n\tbv/flqKTGX00JUyT2qmlrFeF/kQS93H/75J9QUgAFU99wLBlbwEPQsq6W/S3LIs4uTIv\n\tugN8UIxRqfz0ox5kjLHaJNsgjyE/6TvwTYRXnAMHCrDwhm3tQm8hG/eAmtS3P+unG66H\n\tk978i2gUR7xZ/xYpzKP7D9nj0hViOQY3IzUso26LRPLUIMsJWwJVkErIbds2S4y4E22D\n\tofXCUo9pcS7GlD0TLJqQSANYf9uEyBwoA/OU99UkHzU+b2g9ip7wQ61uRKZNkKkAg/gQ\n\taEdg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=ExbSxB+zKKYPyI7LKDukTWjHmOj2GCsQsY0uIhhWX3Y=;\n\tb=DdJdEzxmw53Z9pEUG3Zg/zg2WWfsp1w2fIVi3qvt7td9x/tOK9r7c+pSKXYBixMKf8\n\tLrU0Zv0WE8TJRjPwOOG7gX4hOiRFzo4NPJt2CvgHHi4bVwIHUKacBUmRSUZYkwNr4rTt\n\tVs+hmKyHIQ0QhDgXSz7ndOknTK5ZlcbygQAj4IEOV14uWBKiAVjTwYtL5QgxJoBWRQSC\n\t57GPgOMj+EdEaXZB3bUwEBxYHQu4dT6/ozaS8JSPbDBv3+t43svVI/BVrg9oCOMs/389\n\tO3G7+ZtyikwVfOJZjmLsv1FcO6FNtLoJLag5+rvrELp1e3A3/aLkcH5coGdye71JWqXN\n\tlObw==\nX-Gm-Message-State: AD7BkJIGqHAVgl3XMpXpu0yooeXP+T3+bHmV7cHo3xZRGwwxk60uQTUnvR6dUaQx4cSadDzV\nX-Received: by 10.107.152.17 with SMTP id a17mr19986059ioe.195.1459870202955; \n\tTue, 05 Apr 2016 08:30:02 -0700 (PDT)\nReceived: from raman-glaptop2 ([207.198.106.151])\n\tby smtp.gmail.com with ESMTPSA id\n\th75sm5853440ioe.39.2016.04.05.08.29.58\n\t(version=TLS1_2 cipher=AES128-SHA bits=128/128);\n\tTue, 05 Apr 2016 08:29:59 -0700 (PDT)\nFrom: raman <raman@google.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Should outline-minor-mode be globalized?\nReferences: <22273.54220.381234.312729@gargle.gargle.HOWL>\n\t<m260vyar84.fsf@newartisans.com>\n\t<jwvd1q51re4.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Tue, 05 Apr 2016 08:29:55 -0700\nIn-Reply-To: <jwvd1q51re4.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Mon, 04 Apr 2016 08:39:18 -0400\")\nMessage-ID: <p9160vwoyz0.fsf@google.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4001:c06::230\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 15:30:08 -0000\nContent-Length: 202\nLines: 7\n\noutline-minor-mode would be nice in prog-modes -- but there many of the\nneeds are served by selective display since programming languages\nusually rely on indentation to show  structure visually.\n\n\n-- \n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_279.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an6Os-00024Y-3Z\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 11:26:46 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57136)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raman@google.com>) id 1an6On-0001zs-6P\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:26:44 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raman@google.com>) id 1an6Oi-0007wb-75\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:26:41 -0400\nReceived: from mail-ig0-f179.google.com ([209.85.213.179]:37710)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raman@google.com>) id 1an6Oh-0007wX-VT\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:26:36 -0400\nReceived: by mail-ig0-f179.google.com with SMTP id g8so33800064igr.0\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 08:26:35 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113;\n\th=from:to:subject:references:date:in-reply-to:message-id:user-agent\n\t:mime-version; bh=aFVL4Tb+KotVfZ/OSfMh3pnP7XYCZorMeA83Nvq7D1I=;\n\tb=J0qOdB8zI98cBLVJmGwaPHuMRvQPBcdd4hHQFcYDeAb+TZbGWySUzASDS+S9eFW2jJ\n\t/aMpmEMkxLN9Z6vRmUs6Zx/rxpFdF0T5OCbde7BSXurFLsJ2VOdfl1qp6lJn/dzEWDoJ\n\tIbP5gp5UFZiFHLetumZ3BAoCrAYtK7XyPYicnvRi/OU2QWVS5M+z8Oqk9DviVvRvquWs\n\timOsYgaycZ03PHcwX3ydBsJxZpLOjVT1kwkRLbVn5yLA1xwJIq9Jl8PepxuV3gcvXOeZ\n\tqHpRG2n2O0IeFMc5Y1hLU4Bwx8YY7wZ1fhnhchRQ/VEZLP6FBqZ3sU+H9ZlW/JRcZzsC\n\tpGIw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=aFVL4Tb+KotVfZ/OSfMh3pnP7XYCZorMeA83Nvq7D1I=;\n\tb=adr1IM8sb5EeXj7yQUj4AXWHt3jIBSGaD+vTOgYjD/nwVXzcJ/S7GhNGKRnTwVQtVK\n\tqkPmb10M0qTepwL+TVVyQy4kaWsjpYOpRJDklPXU+DqEXEMG5e4r5JBt3Yyp3U1wOffa\n\tuxib43CaoewN7TAB9yYMscoIPGDzvSHrWQpg16Q9FUYkwGnDGjYNyYyhf3Q+5pmtUbg+\n\tzx6PjVbhEL+by1whmMheepzEBz1W/dDcIwPdO2D3Jr/+81S9w4WU7DQm2bQvfgaY4V7V\n\tMaqgrebYqdujpfDS5UtfYVQWPT6V3nKZhSmVp6e6gC1uZOKpmyJ5BvlEKZSj9VReiOjO\n\tApRg==\nX-Gm-Message-State: AD7BkJI0iehYZ9IlhIdheeMB0Cji1XRAP2OAp+efOrKC0vvyPQVtqmja+rvpJ9XS1RTjHioT\nX-Received: by 10.50.143.1 with SMTP id sa1mr12241417igb.15.1459783524845;\n\tMon, 04 Apr 2016 08:25:24 -0700 (PDT)\nReceived: from raman-glaptop2 ([207.198.106.151])\n\tby smtp.gmail.com with ESMTPSA id\n\tdx1sm5485091igb.21.2016.04.04.08.25.20\n\t(version=TLS1_2 cipher=AES128-SHA bits=128/128);\n\tMon, 04 Apr 2016 08:25:21 -0700 (PDT)\nFrom: raman <raman@google.com>\nTo: dev@caliopen.local\nSubject: Re: Should outline-minor-mode be globalized?\nReferences: <22273.54220.381234.312729@gargle.gargle.HOWL>\n\t<m260vyar84.fsf@newartisans.com>\nDate: Mon, 04 Apr 2016 08:25:17 -0700\nIn-Reply-To: <m260vyar84.fsf@newartisans.com> (John Wiegley's message of \"Sun, \n\t03 Apr 2016 22:16:27 -0700\")\nMessage-ID: <p91mvp9pfaa.fsf@google.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\n\t[fuzzy]\nX-Received-From: 209.85.213.179\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 15:26:44 -0000\nContent-Length: 1009\nLines: 33\n\nJohn Wiegley <jwiegley@gmail.com> writes:\n\nHi John,\n\n\nThe following is admittedly a power-user scenario, but other places \n(other than text-mode) where I have turned on outline-minor-mode:\n\n1. w3-mode in the past \n2. eww-mode \n\nBoth of the above when reading ebooks from places like Project\nGutenberg.\n\nIn many of those instances, you can productively jump by chapter etc by\nappropriately setting outline-regexp to something like \"^CHAPTER\" and\nthen using outline-minor-mode navigation commands.\n\n--Raman \n>>>>>> raman  <raman@google.com> writes:\n>\n>> It's nice to be able to globally turn on font-lock mode; on a similar note,\n>> it would be nice to be able to turn on outline-minor-mode globally -- rather\n>> than setting mode-specific hooks to turn on outline-minor-mode.\n>\n>> Is there any risk in globalizing outline-minor-mode?\n>\n> Wouldn't it really just be applicable in text-mode derived buffers? I have a\n> hard time seeing its value when turned on for dired, Gnus, *Help*,\n> compilation, etc.\n\n-- \n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_281.argentina:2,",
    "content": "Return-Path: <info@baldwin-park.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 29DBC20ADB363\n\tfor <laurent@brasil.brainstorm.fr>; Tue,  5 Apr 2016 11:16:53 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 19335F770D0; Tue,  5 Apr 2016 11:16:53 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 0BCB6F7C830; Tue,  5 Apr 2016 11:16:53 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********\nX-Spam-Status: Yes, score=8.6 required=5.0 tests=BAYES_99,RCVD_IN_BRBL_LASTEXT,\n\tRCVD_IN_CSS,RCVD_IN_PSBL autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  2.7 RCVD_IN_PSBL RBL: Received via a relay in PSBL\n\t*      [96.38.249.146 listed in psbl.surriel.com]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [89.42.88.162 listed in zen.spamhaus.org]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [96.38.249.146 listed in bb.barracudacentral.org]\nX-Spam-DCC: : \nReceived: from ctech.c-techsolutions.com (96-38-249-146.static.gwnt.ga.charter.com [96.38.249.146])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 6CEF73317708\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 11:16:50 +0200 (CEST)\nReceived: (qmail 28073 invoked from network); 2 Apr 2016 10:28:04 -0400\nReceived: from static-89-42-88-162 (HELO ?127.0.0.1?) (89.42.88.162)\n  by 10.0.0.13 with ESMTPA; 2 Apr 2016 10:28:04 -0400\nFrom: \"Anneta Jonson\" <info@baldwin-park.com>\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/plain; charset=UTF-8\nMime-Version: 1.0 (1.0)\nSubject: Hey here\nMessage-Id: <AE4276E4-26CF-65E8-0F6E-842D9A6B5873@baldwin-park.com>\nDate: Sat, 2 Apr 2016 18:31:10 +0400\nTo: klb1579@yahoo.com, agbryan@gmail.com, www.hernandezgeisy@yahoo.com,\n srfus@yahoo.com, dev@caliopen.local, acklor@gmail.com,\n juan_c_422@hotmail.com, sarc@iol.it, jackedge@pipeline.com.au,\n mfranke@hotmail.com\nX-Mailer: iPhone Mail (12H143)\nX-Spam-Prev-Subject: Hey here\nContent-Length: 151\nLines: 3\n\nI'm in luv with your photos, man! u are kinda nice & pretty. sms me AS=\nAP here 828-412-four one seven zero so that I can send you my nude pix=\n=2E bb\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_283.argentina:2,",
    "content": "Return-Path: <apuestas.es@gmail.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 8973720B373FF\n\tfor <laurent@brasil.brainstorm.fr>; Tue,  5 Apr 2016 10:41:04 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 432481A2D531; Tue,  5 Apr 2016 10:41:04 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 2FDAFF0F8EE; Tue,  5 Apr 2016 10:41:04 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.8 required=5.0 tests=BAYES_60,DKIM_ADSP_CUSTOM_MED,\n\tFREEMAIL_FROM,FREEMAIL_REPLYTO,HTML_MESSAGE,LOTS_OF_MONEY,MISSING_MID,\n\tNML_ADSP_CUSTOM_MED,RCVD_IN_BRBL_LASTEXT,T_TO_NO_BRKTS_FREEMAIL\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider\n\t*      (apuestas.es[at]gmail.com)\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [69.165.224.188 listed in bb.barracudacentral.org]\n\t*  0.0 DKIM_ADSP_CUSTOM_MED No valid author signature, adsp_override is\n\t*      CUSTOM_MED\n\t*  1.5 BAYES_60 BODY: Bayes spam probability is 60 to 80%\n\t*      [score: 0.7120]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.5 MISSING_MID Missing Message-Id: header\n\t*  0.0 LOTS_OF_MONEY Huge... sums of money\n\t*  2.4 FREEMAIL_REPLYTO Reply-To/From or Reply-To/body contain different\n\t*      freemails\n\t*  0.9 NML_ADSP_CUSTOM_MED ADSP custom_med hit, and not from a mailing list\n\t*  0.0 T_TO_NO_BRKTS_FREEMAIL T_TO_NO_BRKTS_FREEMAIL\nX-Spam-DCC: : \nReceived: from blaircarriere.com (mail.blaircarriere.com [69.165.224.188])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 1BDBD321F068\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 10:40:58 +0200 (CEST)\nReceived: from MGI1.MGI.LOCAL (50-192-197-233-static.hfc.comcastbusiness.net [50.192.197.233])\n\t(Authenticated sender: abuse)\n\tby blaircarriere.com (Postfix) with ESMTPA id 0D41413146EDC\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 02:35:42 -0600 (MDT)\nContent-Type: multipart/alternative; boundary=\"===============0971163342==\"\nMIME-Version: 1.0\nSubject: APUESTAS de ESTADO \nTo: dev@caliopen.local\nFrom: \"Vicepresidente. Maria Lamia\" <apuestas.es@gmail.com>\nDate: Tue, 05 Apr 2016 02:38:44 -0600\nReply-To: mapfreseguros@spainmail.com\nX-MailScanner-ID: 0D41413146EDC.AFBBB\nX-MailScanner: Found to be clean\nX-MailScanner-From: apuestas.es@gmail.com\nX-Spam-Prev-Subject: APUESTAS de ESTADO \nMessage-Id: <20160405084104.2FDAFF0F8EE@mexico.brainstorm.fr>\nContent-Length: 30647\nLines: 485\n\nYou will not see this in a MIME-aware mail reader.\n--===============0971163342==\nContent-Type: text/plain; charset=\"utf-8\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Description: Mail message body\n\n =\n\n =\n\n                                    LOTERIAS Y APUESTAS del ESTADO SORTEO I=\nNTERNACIONAL\n APARTADO DE CORRES 28007-28109 ALCOBENDAS MADRID SPAIN\n INTERNAZIONALE PROMOZIONI/ DIPARTIMENTO DI PREMIO\n RIFERIMENTO N=B0: S/U/ 6-38\n FILE SICUREZZA N=BA: EMAT/2011/985950\n E-mail: loteriapuestas@gmail.com\n  =\n\n  =\n\n  =\n\n  =\n\n De la Vicepresidente\n Loter=EDa y Apuestas del Estado Internacional   =\n\n Serie N=BA: S/U/ 6-38 el. HORNADA N=BA: EE.UU - 100-0\n  =\n\n  =\n\n  =\n\n Atenci=F3n  Beneficiario,\n  =\n\n  =\n\n NOTIFICA FINAL\n  =\n\n Estamos contentos informarle hoy; el 04/04/2016 del resultado dela 'Loter=\n=EDa y Apuestas del Estado Internacional' se agarr=F3 el 31/03/2016 Como la=\n Parte de nuestro programa anual. Su ref. Atado a boleto N=BA: 01-2577 con =\nSerie N=BA: el S/U/ 6-38. Que Dibuj=F3 la Combinaci=F3n Ganadora 31 marzo 2=\n016, N=BA: 01, 08, 10, 12, 26 y 27 N=BA Compl. 6, Reint. 5, Que Por consigu=\niente que gan=F3 la Loter=EDa en la 2a Categor=EDa.\n  =\n\n Usted ha sido por lo tanto aprobado para una liquidaci=F3n de suma global =\nde =20AC1.504.887,00 (Un mill=F3n quinientos y cuatro mil ochocientos ochen=\nta y siete euros) en efectivo. Todos los participantes fueron seleccionados=\n por un sistema de votaci=F3n de computadora dibujado de 106,210 nombres y =\ncorreos de Australia, Asia, Oriente Medio, Norte y Sudam=E9rica y Europa, c=\nomo la parte de nuestro Programa de Promociones Internacional anual, que es=\n conducido dos veces al a=F1o. FELICITACIONES!!!\n  =\n\n Nuestros programas confiados por millones alrededor del mundo cambian la v=\nida de ganadores. Todas las ganancias internacionales son tratadas por el A=\ngente autorizado del Gobierno Espa=F1ol y recomendadas por el BORDO de LOTE=\nR=CDA ESPA=D1OL. Su cantidad de ganancia es asegurada de su ref. y ser=E1 l=\niberada para el pago por el MINISTERIO DE ECONOMIA despu=E9s de aclaraci=F3=\nn de datos y verificaci=F3n. La comisi=F3n en la cantidad de ganancia no pu=\nede ser descontada de la cantidad de ganancia, debido a la tapa de pol=EDti=\nca de bono de seguros.\n  =\n\n Debido a la mezcla de algunos n=FAmeros, le pedimos di guardar este premio=\n de la atenci=F3n p=FAblica hasta que su reclamaci=F3n haya sido tratada y =\nsu dinero remitido a su cuenta denominada cuando este es la parte de nuestr=\no protocolo de seguridad para evitar la doble reclamaci=F3n o el acto poco =\nescrupuloso por no aprovechamiento de participantes de este programa.\n  =\n\n Para comenzar su reclamaci=F3n, p=F3ngase en contacto con su agente: Don M=\nanuel Rodr=EDguez F, gerente de operaciones extranjero de  MAPFRE SEGUROS S=\n.A. ESPA=D1A TEL;+34631 401 963,  FAX;+34917 692 792, Email; mapfreseguros.=\ns.a@spainmail.com/mapfreseguros@spainmail.com.  Para procesamiento y remesa=\n de su fondo a la cuenta designada de su opci=F3n. Recuerde que todo el din=\nero de precios debe ser reclamado no m=E1s tarde del 30/04/2016, despu=E9s =\nque todos los fondos sin reclamar ser=E1n devueltos al MINISTERIO ECONOMIA =\nY HACIENDA como sin reclamar. Esfu=E9rcese por cotizar su en Cuanto a la Se=\nrie N=BA e Hornada N=BA en cada correspondencia con nosotros. =\n\n  =\n\n NOTA: Debido a razones de seguridad y abuso injustificado de este programa=\n, (doble demandantes), hemos decidido retener la forma de procesamiento de =\npagos,  hasta que se determinan y confirmados por el consejo de 'Loteria y =\nApuestas del Estado Internacional' informaci=F3n del ganador, entonces la f=\norma de procesamiento de pagos ser=E1 emitida a su ref. A fin, para evitar =\nlas tardanzas innecesarias, si habr=E1 alg=FAn cambio de su direcci=F3n o c=\nomplicaciones, informan realmente a su agente cuanto antes, pagar=E1n a su =\nagente 5% del precio de premio total, como la comisi=F3n despu=E9s de que u=\nsted ha recibido su dinero en su cuenta designada, La copia original de su =\nboleto permanece con su agente.\n  =\n\n Comprueba tu premio online aqui; http://www.loterias.com/general/primitiva=\n.html\n   =\n\n Las felicitaciones otra vez de todos nuestro proveen de personal.\n  =\n\n Sinceramente\n MARIA LAMIA (Vicepresidente)\n =20\n\n--===============0971163342==\nContent-Type: text/html; charset=\"utf-8\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Description: Mail message body\n\n<HTML><head><meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=\n=3Dutf-8\"/></head><BODY><P><SPAN style=3D\"FONT-SIZE: 36pt\"><B style=3D\"OUTL=\nINE-WIDTH: medium; OUTLINE-STYLE: none; OUTLINE-COLOR: invert; LINE-HEIGHT:=\n 1.2em\"><SPAN lang=3DEN-GB style=3D\"FONT-SIZE: 8pt; FONT-FAMILY: Calibri, s=\nans-serif; OUTLINE-WIDTH: medium; OUTLINE-STYLE: none; OUTLINE-COLOR: inver=\nt; LINE-HEIGHT: 1.2em\"> </P>\n<H2 style=3D\"MARGIN: 10pt 0cm 0pt 35.4pt\"><SPAN style=3D\"mso-fareast-font-f=\namily: 'Times New Roman'; mso-fareast-language: ES\"><FONT size=3D4><FONT co=\nlor=3D#4f81bd><FONT face=3DCambria>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=\np;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=\nnbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=\np;&nbsp;&nbsp;&nbsp;&nbsp;LOTERIAS Y APUESTAS del ESTADO SORTEO INTERNACION=\nAL<?xml:namespace prefix =3D \"o\" ns =3D \"urn:schemas-microsoft-com:office:o=\nffice\" /><o:p></o:p></FONT></FONT></FONT></SPAN></H2>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN style=3D\"FONT-=\nSIZE: 9pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast=\n-font-family: 'Times New Roman'; mso-fareast-language: ES\">APARTADO DE CORR=\nES 28007-28109 ALCOBENDAS MADRID SPAIN</SPAN></B><B><SPAN style=3D\"FONT-SIZ=\nE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-f=\nont-family: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN>=\n</B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN style=3D\"FONT-=\nSIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast=\n-font-family: 'Times New Roman'; mso-fareast-language: ES\">INTERNAZIONALE P=\nROMOZIONI/ DIPARTIMENTO DI PREMIO</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10=\npt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-f=\namily: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 21.1pt 0pt 0cm; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN style=\n=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Arial Narrow','sans-serif'; COLOR: black;=\n mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-=\nbidi-font-family: Helvetica\">RIFERIMENTO</SPAN></B><B><SPAN style=3D\"FONT-S=\nIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; TEXT-TRANSFORM: uppercase;=\n COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-lang=\nuage: ES; mso-bidi-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN style=3D\"FO=\nNT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; TEXT-TRANSFORM: upperc=\nase; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-=\nlanguage: ES\">N=C2=B0:</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 8pt; FONT-FAM=\nILY: 'Helvetica','sans-serif'; TEXT-TRANSFORM: uppercase; COLOR: black; mso=\n-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-bidi=\n-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 8pt; FONT=\n-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family: '=\nTimes New Roman'; mso-fareast-language: ES\">S/U/ 6-38</SPAN></B><B><SPAN st=\nyle=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black=\n; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES\"><o:=\np></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 21.1pt 0pt 0cm; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN lang=3D=\nEN-US style=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Arial Narrow','sans-serif'; TE=\nXT-TRANSFORM: uppercase; COLOR: black; mso-ansi-language: EN-US; mso-fareas=\nt-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-font-f=\namily: Helvetica\">FILE SICUREZZA</SPAN></B><B><SPAN lang=3DEN-US style=3D\"F=\nONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; TEXT-TRANSFORM: upper=\ncase; COLOR: black; mso-ansi-language: EN-US; mso-fareast-font-family: 'Tim=\nes New Roman'; mso-fareast-language: ES; mso-bidi-font-size: 11.0pt\">&nbsp;=\n</SPAN></B><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Hel=\nvetica','sans-serif'; TEXT-TRANSFORM: uppercase; COLOR: black; mso-ansi-lan=\nguage: EN-US; mso-fareast-font-family: 'Times New Roman'; mso-fareast-langu=\nage: ES\">N=C2=BA:</SPAN></B><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 8pt; =\nFONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-=\nUS; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES; m=\nso-bidi-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN lang=3DEN-US style=3D\"=\nFONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-an=\nsi-language: EN-US; mso-fareast-font-family: 'Times New Roman'; mso-fareast=\n-language: ES\">EMAT/2011/985950</SPAN></B><B><SPAN lang=3DEN-US style=3D\"FO=\nNT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-ans=\ni-language: EN-US; mso-fareast-font-family: 'Times New Roman'; mso-fareast-=\nlanguage: ES\"><o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 21.1pt 0pt 0cm; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN lang=3D=\nEN-US style=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR=\n: black; mso-ansi-language: EN-US; mso-fareast-font-family: 'Times New Roma=\nn'; mso-fareast-language: ES\">E-mail:</SPAN></B><B><SPAN lang=3DEN-US style=\n=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; ms=\no-ansi-language: EN-US; mso-fareast-font-family: 'Times New Roman'; mso-far=\neast-language: ES; mso-bidi-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN st=\nyle=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black;=\n mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-=\nbidi-font-size: 11.0pt\"><A href=3D\"https://es-mg42.mail.yahoo.com/neo/b/com=\npose?to=3Dloteriapuestas@gmail.com\" target=3D_blank><SPAN lang=3DEN-US styl=\ne=3D\"COLOR: black; mso-ansi-language: EN-US\">loteriapuestas@gmail.com</SPAN=\n></A></SPAN></B><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMILY=\n: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-far=\neast-font-family: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p><=\n/SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMIL=\nY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-fa=\nreast-font-family: 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p>=\n</o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMIL=\nY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-fa=\nreast-font-family: 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p>=\n</o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMIL=\nY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-fa=\nreast-font-family: 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p>=\n</o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMIL=\nY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-fa=\nreast-font-family: 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p>=\n</o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 14.4pt; mso-outline-level: 1\"><FONT size=3D3><B><SPAN style=3D\"FONT=\n-FAMILY: 'Arial','sans-serif'; COLOR: black; mso-fareast-font-family: 'Time=\ns New Roman'; mso-fareast-language: ES; mso-font-kerning: 18.0pt\">De la Vic=\nepresidente</SPAN></B><B><SPAN style=3D\"FONT-FAMILY: 'Helvetica','sans-seri=\nf'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-l=\nanguage: ES; mso-font-kerning: 18.0pt\"><o:p></o:p></SPAN></B></FONT></P><B>=\n<SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLO=\nR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language:=\n ES\">\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">Loter=C3=ADa y Apuestas del Estado Internacional&n=\nbsp;</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica',=\n'sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso=\n-fareast-language: ES; mso-bidi-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPA=\nN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: b=\nlack; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES\"=\n> <o:p></o:p></SPAN></B></P></SPAN></B>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">Serie N=C2=BA: S/U/ 6-38 el. HORNADA N=C2=BA: EE.U=\nU - 100-0<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">Atenci=C3=B3n&nbsp;</SPAN></B><B><SPAN style=3D\"FO=\nNT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-far=\neast-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-fon=\nt-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FA=\nMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family: 'Tim=\nes New Roman'; mso-fareast-language: ES\">Beneficiario,<o:p></o:p></SPAN></B=\n></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\" align=3Dcenter><FONT size=3D3><B><U><S=\nPAN style=3D\"FONT-FAMILY: 'Arial','sans-serif'; COLOR: black; mso-fareast-f=\nont-family: 'Times New Roman'; mso-fareast-language: ES\">NOTIFICA FINAL</SP=\nAN></U></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','san=\ns-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-far=\neast-language: ES\"><o:p></o:p></SPAN></B></FONT></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Estamos contentos informarle hoy; el 04/04/2016 d=\nel resultado dela 'Loter=C3=ADa y Apuestas del Estado Internacional' se aga=\nrr=C3=B3 el 31/03/2016 Como la Parte de nuestro programa anual. Su ref. Ata=\ndo a boleto N=C2=BA: 01-2577 con Serie N=C2=BA: el S/U/ 6-38. Que Dibuj=C3=\n=B3 la Combinaci=C3=B3n Ganadora 31 marzo 2016, </SPAN></B><B><U><SPAN styl=\ne=3D\"FONT-SIZE: 9pt; COLOR: black; mso-fareast-font-family: 'Times New Roma=\nn'; mso-fareast-language: ES; mso-bidi-font-family: Calibri; mso-ascii-font=\n-family: Calibri; mso-hansi-font-family: Calibri; mso-themecolor: text1\">N=\n=C2=BA: 01, 08, 10, 12, 26 y 27 N=C2=BA Compl. 6, Reint. 5,</SPAN></U></B><=\nB><SPAN style=3D\"FONT-SIZE: 9pt; COLOR: black; mso-fareast-font-family: 'Ti=\nmes New Roman'; mso-fareast-language: ES; mso-bidi-font-family: Calibri; ms=\no-ascii-font-family: Calibri; mso-hansi-font-family: Calibri\"> Que Por cons=\niguiente que gan=C3=B3 la Loter=C3=ADa en la 2a Categor=C3=ADa.<o:p></o:p><=\n/SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\"><o:p>&nbsp;</o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Usted ha sido por lo tanto aprobado para una liqu=\nidaci=C3=B3n de suma global de =E2=82=AC1.504.887,00 (<U><SPAN style=3D\"TEX=\nT-TRANSFORM: uppercase\">Un mill=C3=B3n quinientos y cuatro mil ochocientos =\nochenta y siete euros</SPAN></U>) en efectivo. Todos los participantes fuer=\non seleccionados por un sistema de votaci=C3=B3n de computadora dibujado de=\n 106,210 nombres y correos de Australia, Asia, Oriente Medio, Norte y Sudam=\n=C3=A9rica y Europa, como la parte de nuestro Programa de Promociones Inter=\nnacional anual, que es conducido dos veces al a=C3=B1o.</SPAN></B><B><SPAN =\nstyle=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: bla=\nck; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES\"> =\n</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 9pt; COLOR: red; mso-fareast-font-f=\namily: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-font-family: C=\nalibri; mso-ascii-font-family: Calibri; mso-hansi-font-family: Calibri\">FEL=\nICITACIONES!!!</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'H=\nelvetica','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New R=\noman'; mso-fareast-language: ES\"><o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Nuestros programas confiados por millones alreded=\nor del mundo cambian la vida de ganadores. Todas las ganancias internaciona=\nles son tratadas por el Agente autorizado del Gobierno Espa=C3=B1ol y recom=\nendadas por el BORDO de LOTER=C3=8DA ESPA=C3=91OL. Su cantidad de ganancia =\nes asegurada de su ref. y ser=C3=A1 liberada para el pago por el MINISTERIO=\n DE ECONOMIA despu=C3=A9s de aclaraci=C3=B3n de datos y verificaci=C3=B3n. =\nLa comisi=C3=B3n en la cantidad de ganancia no puede ser descontada de la c=\nantidad de ganancia, debido a la tapa de pol=C3=ADtica de bono de seguros.<=\n/SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans=\n-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fare=\nast-language: ES\"><o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Debido a la mezcla de algunos n=C3=BAmeros, le pe=\ndimos di guardar este premio de la atenci=C3=B3n p=C3=BAblica hasta que su =\nreclamaci=C3=B3n haya sido tratada y su dinero remitido a su cuenta denomin=\nada cuando este es la parte de nuestro protocolo de seguridad para evitar l=\na doble reclamaci=C3=B3n o el acto poco escrupuloso por no aprovechamiento =\nde participantes de este programa.</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 9=\npt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-f=\namily: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Para comenzar su reclamaci=C3=B3n, p=C3=B3ngase e=\nn contacto con su agente:&nbsp;<U>Don Manuel Rodr=C3=ADguez F</U>, gerente =\nde operaciones extranjero de&nbsp; <U>MAPFRE SEGUROS S.A. ESPA=C3=91A TEL;+=\n34631 401 963, &nbsp;FAX;+34917 692 792, Email;&nbsp;<A href=3D\"https://es-=\nmg42.mail.yahoo.com/neo/b/compose?to=3Dmapfreseguros.s.a@spainmail.com/mapf=\nreseguros@spainmail.com\" target=3D_blank><SPAN style=3D\"COLOR: black\">mapfr=\neseguros.s.a@spainmail.com/mapfreseguros@spainmail.com</SPAN></A>.</U>&nbsp=\n;&nbsp;Para procesamiento y remesa de su fondo a la cuenta designada de su =\nopci=C3=B3n. Recuerde que todo el dinero de precios debe ser reclamado no m=\n=C3=A1s tarde del&nbsp;<U>30/04/2016</U>, despu=C3=A9s que todos los fondos=\n sin reclamar ser=C3=A1n devueltos al MINISTERIO ECONOMIA Y HACIENDA como s=\nin reclamar. Esfu=C3=A9rcese por cotizar su en Cuanto a la Serie N=C2=BA e =\nHornada N=C2=BA en cada correspondencia con nosotros</SPAN></B><B><SPAN sty=\nle=3D\"FONT-SIZE: 10pt; COLOR: black; mso-fareast-font-family: 'Times New Ro=\nman'; mso-fareast-language: ES; mso-bidi-font-family: Calibri; mso-ascii-fo=\nnt-family: Calibri; mso-hansi-font-family: Calibri\">. </SPAN></B><B><SPAN s=\ntyle=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: blac=\nk; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES\"><o=\n:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: #c00000; mso-fareast-font-family: 'Times New Roman'; mso-fareast-langua=\nge: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-=\nhansi-font-family: Calibri\">NOTA:</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10=\npt; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-l=\nanguage: ES; mso-bidi-font-family: Calibri; mso-bidi-font-size: 11.0pt; mso=\n-ascii-font-family: Calibri; mso-hansi-font-family: Calibri\">&nbsp;</SPAN><=\n/B><B><SPAN style=3D\"FONT-SIZE: 9pt; COLOR: #c00000; mso-fareast-font-famil=\ny: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-font-family: Calib=\nri; mso-ascii-font-family: Calibri; mso-hansi-font-family: Calibri\">Debido =\na razones de seguridad y abuso injustificado de este programa, (doble deman=\ndantes), hemos decidido retener la forma de procesamiento de pagos,</SPAN><=\n/B><B><SPAN style=3D\"FONT-SIZE: 10pt; COLOR: black; mso-fareast-font-family=\n: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-font-family: Calibr=\ni; mso-bidi-font-size: 11.0pt; mso-ascii-font-family: Calibri; mso-hansi-fo=\nnt-family: Calibri\">&nbsp;</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 9pt; COLO=\nR: #c00000; mso-fareast-font-family: 'Times New Roman'; mso-fareast-languag=\ne: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-h=\nansi-font-family: Calibri\">&nbsp;hasta que se determinan y confirmados por =\nel consejo de 'Loteria y Apuestas del Estado Internacional' informaci=C3=B3=\nn del ganador, entonces la forma de procesamiento de pagos ser=C3=A1 emitid=\na a su ref. A fin, para evitar las tardanzas innecesarias, si habr=C3=A1 al=\ng=C3=BAn cambio de su direcci=C3=B3n o complicaciones, informan realmente a=\n su agente cuanto antes, pagar=C3=A1n a su agente 5% del precio de premio t=\notal, como la comisi=C3=B3n despu=C3=A9s de que usted ha recibido su dinero=\n en su cuenta designada, La copia original de su boleto permanece con su ag=\nente.<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\"><o:p>&nbsp;</o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">Comprueba tu premio online aq=\nui; <A href=3D\"http://www.loterias.com/general/primitiva.html\"><FONT color=\n=3D#0000ff>http://www.loterias.com/general/primitiva.html</FONT></A><o:p></=\no:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\"><SPAN style=3D\"mso-spacerun: =\nyes\">&nbsp;</SPAN>&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; CO=\nLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-languag=\ne: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-h=\nansi-font-family: Calibri\">Las felicitaciones otra vez de todos nuestro pro=\nveen de personal.</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY:=\n 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times Ne=\nw Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; CO=\nLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-languag=\ne: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-h=\nansi-font-family: Calibri\">Sinceramente</SPAN></B><B><SPAN style=3D\"FONT-SI=\nZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-=\nfont-family: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN=\n></B></P>\n<P class=3DMsoNormal style=3D\"MARGIN: 0cm 0cm 10pt\"><B><SPAN style=3D\"FONT-=\nSIZE: 10pt; FONT-FAMILY: 'Andalus','serif'; COLOR: black; LINE-HEIGHT: 115%=\n\">MARIA LAMIA (Vicepresidente)<o:p></o:p></SPAN></B></P>\n<P class=3Dyiv9723979600MsoNormal style=3D\"WHITE-SPACE: normal; WORD-SPACIN=\nG: 0px; TEXT-TRANSFORM: none; COLOR: rgb(0,0,0); OUTLINE-WIDTH: medium; PAD=\nDING-BOTTOM: 0px; TEXT-ALIGN: center; PADDING-TOP: 0px; FONT: 13px/15px Hel=\nvetica, Arial, sans-serif; OUTLINE-STYLE: none; PADDING-LEFT: 0px; MARGIN: =\n0in 0in 0pt; WIDOWS: 1; DISPLAY: block; LETTER-SPACING: normal; OUTLINE-COL=\nOR: invert; PADDING-RIGHT: 0px; BACKGROUND-COLOR: rgb(255,255,255); TEXT-IN=\nDENT: 0px; -webkit-text-stroke-width: 0px; font-stretch: normal\" align=3Dce=\nnter></SPAN></B>&nbsp;</P></SPAN></BODY></HTML>\n--===============0971163342==--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_285.argentina:2,",
    "content": "Return-Path: <ilene_gaines@milicavezmar.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 1475520A757B0\n\tfor <laurent@brasil.brainstorm.fr>; Tue,  5 Apr 2016 08:56:40 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 03A263317701; Tue,  5 Apr 2016 08:56:40 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid EA5E2331770E; Tue,  5 Apr 2016 08:56:39 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.9 required=5.0 tests=BAYES_99,FROM_12LTRDOM,\n\tHTML_MESSAGE,HTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RCVD_IN_BL_SPAMCOP_NET\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.3 RCVD_IN_BL_SPAMCOP_NET RBL: Received via a relay in bl.spamcop.net\n\t*      [Blocked - see <http://www.spamcop.net/bl.shtml?37.48.73.35>]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  2.0 FROM_12LTRDOM From a 12-letter domain\nX-Spam-DCC: : \nReceived: from milicavezmar.com (alpha.budokan.rs [37.48.73.35])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id E7A5A3317701\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 08:56:37 +0200 (CEST)\nDate: Tue, 5 Apr 2016 6:57:30 +0000\nFrom: \"Ilene Gaines\" <ilene_gaines@milicavezmar.com>\nReply-To: \"Ilene Gaines\" <cute@dating2mail.com>\nMessage-ID: <75a60b7-c4862-e6@milicavezmar.com>\nTo: dev@caliopen.local\nSubject: How are you\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject:  How are you\nContent-Length: 219\nLines: 2\n\n\n<div>How are you, I am a solitary charming puss, searching adorable man to have a dating with :=) <br> Where are you from, do you speak english? <br> <br> please reply me if you are interested. See you later :-)</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_287.argentina:2,",
    "content": "Return-Path: <CEO@ChinaBestSupplier.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 530DB207CE5B0\n\tfor <laurent@brasil.brainstorm.fr>; Tue,  5 Apr 2016 08:10:05 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 3B3123317701; Tue,  5 Apr 2016 08:10:05 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid F3C13331770E; Tue,  5 Apr 2016 08:10:04 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********\nX-Spam-Status: Yes, score=8.9 required=5.0 tests=AWL,BAYES_99,\n\tFSL_HELO_NON_FQDN_1,HELO_NO_DOMAIN,HTML_MESSAGE,MIME_HEADER_CTYPE_ONLY,\n\tMIME_HTML_ONLY,MISSING_MID,RCVD_IN_PBL,RDNS_NONE autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 FSL_HELO_NON_FQDN_1 FSL_HELO_NON_FQDN_1\n\t*  3.3 RCVD_IN_PBL RBL: Received via a relay in Spamhaus PBL\n\t*      [119.122.247.44 listed in zen.spamhaus.org]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.5 MISSING_MID Missing Message-Id: header\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.7 MIME_HEADER_CTYPE_ONLY 'Content-Type' found without required MIME\n\t*      headers\n\t*  0.0 HELO_NO_DOMAIN Relay reports its domain incorrectly\n\t* -0.7 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from USER-20160225JC (unknown [119.122.247.44])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id F104D3317701\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 08:09:54 +0200 (CEST)\nFrom: \"Eva\" <CEO@ChinaBestSupplier.com>\nSubject: 3D VR BOX US$6.49 for 1K\nTo: dev@caliopen.local\nContent-Type: text/html\nReply-To: coremould@163.com\nDate: Tue, 5 Apr 2016 14:09:49 +0800\nX-Priority: 3\nX-Library: Indy 9.00.10\nX-Spam-Prev-Subject: 3D VR BOX US$6.49 for 1K\nMessage-Id: <20160405061004.F3C13331770E@mexico.brainstorm.fr>\nContent-Length: 18950\nLines: 333\n\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n<HTML><HEAD>\n<META content=\"text/html; charset=utf-8\" http-equiv=Content-Type>\n<STYLE type=text/css>\nBLOCKQUOTE{margin-Top: 0px; margin-Bottom: 0px; margin-Left: 2em}\n</STYLE>\n\n<META name=GENERATOR content=\"MSHTML 9.00.8112.16659\"><!-- flashmail style begin -->\n<STYLE type=text/css>\nbody {border-width:0;margin:0}\nimg {border:0;margin:0;padding:0}\n</STYLE>\n<BASE target=_blank><!-- flashmail style end --></HEAD>\n<BODY \nstyle=\"LINE-HEIGHT: 1.5; BORDER-RIGHT-WIDTH: 0px; MARGIN: 12px; FONT-FAMILY: ms sans serif; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; COLOR: #000000; FONT-SIZE: 12pt; BORDER-LEFT-WIDTH: 0px\" \nmarginheight=\"0\" marginwidth=\"0\">\n<DIV>\n<DIV><STRONG><FONT size=2 face=Verdana></FONT></STRONG></DIV></DIV>\n<DIV style=\"FONT-FAMILY: Verdana; FONT-SIZE: 10pt\"><STATIONERY>\n<DIV class=patemplate_box_content>\n<TABLE style=\"WIDTH: 100%\" class=patemplate_box_content>\n  <TBODY>\n  <TR>\n    <TD>\n      <DIV class=Pa_body>\n      <DIV class=Pa_Top>\n      <DIV class=Pa_TopRight>\n      <DIV class=Pa_Topleft></DIV>\n      <DIV style=\"COLOR: #ff9900; FONT-SIZE: 20pt\" class=Pa_Title><STRONG>3D VR \n      Box Virtual Reality Glasses Cardboard Movie Game for Samsung IOS \n      iPhone</STRONG></DIV></DIV></DIV>\n      <DIV class=Pa_Contents>\n      <DIV class=Pa_ContentsL></DIV>\n      <DIV class=Pa_ContentsR></DIV>\n      <DIV class=Pa_Product>\n      <TABLE style=\"WIDTH: 900px\" cellSpacing=15 cellPadding=0>\n        <TBODY>\n        <TR>\n          <TD><IMG style=\"MAX-WIDTH: 350px; MAX-HEIGHT: 350px\" \n            src=\"http://www.orchidspace.com.cn/OS04691/OS04691_01.jpg\"><IMG \n            style=\"MAX-WIDTH: 350px; MAX-HEIGHT: 350px\" \n            src=\"http://www.orchidspace.com.cn/OS04691/OS04691_1.jpg\"><BR><IMG \n            style=\"MAX-WIDTH: 350px; MAX-HEIGHT: 350px\" \n            src=\"http://www.orchidspace.com.cn/OS04691/OS04691_2.jpg\"><IMG \n            style=\"MAX-WIDTH: 350px; MAX-HEIGHT: 350px\" \n            src=\"http://www.orchidspace.com.cn/OS04691/OS04691_3.jpg\"><BR><IMG \n            style=\"MAX-WIDTH: 350px; MAX-HEIGHT: 350px\" \n            src=\"http://www.orchidspace.com.cn/OS04691/OS04691_4.jpg\"><IMG \n            style=\"MAX-WIDTH: 350px; MAX-HEIGHT: 350px\" \n            src=\"http://www.orchidspace.com.cn/OS04691/OS04691_5.jpg\"><BR><IMG \n            style=\"MAX-WIDTH: 350px; MAX-HEIGHT: 350px\" \n            src=\"http://www.orchidspace.com.cn/OS04691/OS04691_6.jpg\"><IMG \n            style=\"MAX-WIDTH: 350px; MAX-HEIGHT: 350px\" \n            src=\"http://www.orchidspace.com.cn/OS04691/OS04691_7.jpg\"><BR><IMG \n            style=\"MAX-WIDTH: 350px; MAX-HEIGHT: 350px\" \n            src=\"http://www.orchidspace.com.cn/OS04691/OS04691_8.jpg\"></TD></TR></TBODY></TABLE></DIV>\n      <DIV class=Pa_Box>\n      <DIV class=Pa_TboxL></DIV>\n      <DIV class=Pa_Tboxt></DIV>\n      <DIV class=Pa_TboxR></DIV>\n      <DIV class=Pa_MboxL></DIV>\n      <DIV class=Pa_Mboxt>\n      <H2><SPAN class=Pa_head><SPAN class=Pa_headc>Description</SPAN><SPAN \n      class=Pa_line></SPAN></SPAN></H2>\n      <DIV class=Pa_Demo><SPAN class=Pa_DemoT><!--PA4_Description_Begin-->\n      <P style=\"TEXT-ALIGN: center\"><STRONG \n      style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-SIZE: 14pt\">3D VR Box Virtual Reality Glasses Cardboard Movie \n      Game for Samsung IOS iPhone</SPAN></STRONG></P>\n      <DIV style=\"FONT-FAMILY: Arial\"></DIV>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><STRONG></STRONG></P>\n      <P \n      style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><STRONG>Description:</STRONG></P>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><SPAN>This VR Box is great \n      supplement and device for the wonderful experience of watching movies. \n      It’s easy to put your smart phone in it, and it’s convenient to operate \n      and use. With this VR box, you can get a wonderful experience.</SPAN></P>\n      <DIV style=\"FONT-FAMILY: Arial\"></DIV>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><STRONG>Feature:</STRONG> \n      </P>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"></P>\n      <UL style=\"FONT-FAMILY: Arial\">\n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Made of high quality material, durable \n        to use</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Adjustable headband is suitable for \n        different need</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Adjustable pupillary distance and \n        focal distance, convenient to use</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Light weight and compact, easy to \n        carry</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Suitable for the phone ranges \n        from<SPAN style=\"FONT-FAMILY: 'Comic Sans MS'; COLOR: rgb(255,0,0)\"> \n        3.5~6 inches</SPAN>, such as iPhone 6s, iPhone 6s Plus, iPhone 6, iPhone \n        6 Plus, Samsung S6, S7, Samsung S6 Edge</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Do not use the mobile phone while \n        charging</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Android system can install application \n        software by scanning QR code, IOS system please check the official \n        website. (refer to the manual)</SPAN></LI></UL>\n      <P style=\"FONT-FAMILY: Arial\"></P>\n      <DIV style=\"FONT-FAMILY: Arial\"></DIV>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><U></U></P>\n      <P \n      style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><STRONG>Specification:</STRONG></P>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><STRONG></STRONG></P>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"></P>\n      <UL style=\"FONT-FAMILY: Arial\">\n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Material: ABS</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Video Input: Cell phone</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Lens material: Aspheric optical \n        lens</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Compatibility: Android, IOS</SPAN> \n        <LI><SPAN style=\"FONT-SIZE: 12pt\">Dimension: approx. \n        19.7x10.5x14.0cm/7.8x4.1x5.5\"</SPAN></LI></UL>\n      <P style=\"FONT-FAMILY: Arial\"></P>\n      <DIV style=\"FONT-FAMILY: Arial\"></DIV>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><STRONG><SPAN>Package \n      Includes:</SPAN></STRONG></P>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"><STRONG></STRONG></P>\n      <P style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\"></P>\n      <UL>\n        <LI style=\"FONT-FAMILY: Arial\"><SPAN style=\"FONT-SIZE: 12pt\">1X VR \n        Box</SPAN> \n        <LI style=\"FONT-FAMILY: Arial\"><SPAN style=\"FONT-SIZE: 12pt\">1X User \n        manual</SPAN> \n        <LI><SPAN style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\">1X Original \n        Package</SPAN></LI></UL>\n      <P></P><!--PA4_Description_End--></SPAN></DIV></DIV>\n      <DIV class=Pa_MboxR></DIV>\n      <DIV class=Pa_BboxL></DIV>\n      <DIV class=Pa_Bboxt></DIV>\n      <DIV class=Pa_BboxR></DIV></DIV>\n      <DIV class=Pa_TboxL></DIV>\n      <DIV class=Pa_Tboxt></DIV>\n      <DIV class=Pa_TboxR></DIV>\n      <DIV class=Pa_MboxL></DIV>\n      <DIV class=Pa_Mboxt>\n      <H2><SPAN class=Pa_head><SPAN class=Pa_headL></SPAN><SPAN \n      class=Pa_headR></SPAN><SPAN class=Pa_headc>Payment</SPAN><SPAN \n      class=Pa_Right></SPAN><SPAN class=Pa_line></SPAN></SPAN></H2><SPAN \n      class=Pa_DemoT>\n      <P class=Pa_Demo><STRONG \n      style=\"FONT-FAMILY: Arial; FONT-SIZE: 12pt\">Payment Method:</STRONG></P>\n      <DIV class=Pa_Demo>\n      <UL>\n        <LI><SPAN \n        style=\"WIDOWS: 2; FONT-FAMILY: Arial; ORPHANS: 2; FONT-SIZE: 12pt\">We \n        accept payment via&nbsp;T/T, Western \n      Union</SPAN>&nbsp;</LI></UL></DIV><SPAN \n      style=\"WIDOWS: 2; FONT-FAMILY: Arial; ORPHANS: 2; FONT-SIZE: 12pt\"></SPAN></SPAN>\n      <DIV style=\"FONT-SIZE: 12pt\" class=Pa_MboxR><FONT \n      face=Arial>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </FONT></DIV>\n      <DIV style=\"FONT-SIZE: 12pt\" class=Pa_MboxR><FONT \n      face=Arial></FONT>&nbsp;</DIV>\n      <DIV style=\"FONT-SIZE: 12pt\" class=Pa_MboxR><FONT \n      face=Arial></FONT>&nbsp;</DIV>\n      <DIV style=\"FONT-SIZE: 12pt\" class=Pa_MboxR><FONT \n      face=Arial></FONT>&nbsp;</DIV>\n      <DIV style=\"FONT-SIZE: 12pt\" class=Pa_MboxR><STATIONERY>\n      <DIV>\n      <STYLE type=text/css>\nbody {font-size:10.5pt; font-family:MS Sans Serif,serif}\n</STYLE>\n\n      <META name=GENERATOR content=\"MSHTML 9.00.8112.16685\"><STATIONERY>\n      <DIV>\n      <STYLE type=text/css>\nbody {font-size:10.5pt; font-family:MS Sans Serif,serif}\n</STYLE>\n\n      <META name=GENERATOR content=\"MSHTML 9.00.8112.16685\"><STATIONERY>\n      <DIV>\n      <STYLE type=text/css>\nbody {font-size:10.5pt; font-family:MS Sans Serif,serif}\n</STYLE>\n\n      <META name=GENERATOR content=\"MSHTML 9.00.8112.16685\"><STATIONERY>\n      <DIV>\n      <STYLE type=text/css>\nbody {font-size:10.5pt; font-family:MS Sans Serif,serif}\n</STYLE>\n\n      <META name=GENERATOR content=\"MSHTML 9.00.8112.16685\"><STATIONERY>\n      <DIV>\n      <STYLE type=text/css>\nbody {font-size:10.5pt; font-family:MS Sans Serif,serif}\n</STYLE>\n\n      <META name=GENERATOR content=\"MSHTML 9.00.8112.16685\"><STATIONERY>\n      <DIV>\n      <STYLE type=text/css>\nbody {font-size:10.5pt; font-family:MS Sans Serif,serif}\n</STYLE>\n\n      <META name=GENERATOR content=\"MSHTML 9.00.8112.16685\">\n      <DIV><FONT face=Utsaah>\n      <DIV style=\"POSITION: relative; WIDTH: 340px; ZOOM: 1; HEIGHT: 232px\">\n      <DIV><SPAN style=\"FONT-FAMILY: Times New Roman\"><SPAN \n      style=\"COLOR: rgb(0,0,0); FONT-SIZE: 13px\"><EM><FONT \n      size=3></FONT></EM></SPAN></SPAN></DIV>\n      <DIV><SPAN><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: rgb(0,0,0)\"><SPAN \n      style=\"FONT-SIZE: 32pt\"><FONT size=3 \n      face=Utsaah></FONT></SPAN></SPAN></SPAN>&nbsp;</DIV>\n      <DIV><SPAN><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: rgb(0,0,0)\"><SPAN \n      style=\"FONT-SIZE: 32pt\"><STRONG><EM><SPAN style=\"FONT-SIZE: 10pt\"><SPAN \n      style=\"FONT-SIZE: 14pt\"><SPAN style=\"FONT-SIZE: 18pt\"><SPAN \n      style=\"FONT-SIZE: 18pt\">Eva&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN></SPAN></SPAN></SPAN></EM></STRONG></SPAN></SPAN></SPAN><SPAN \n      style=\"FONT-SIZE: 9pt\"><SPAN style=\"COLOR: #000000; FONT-SIZE: 10pt\"><SPAN \n      style=\"COLOR: #808080; FONT-SIZE: 10.5pt\">0086 \n      </SPAN></SPAN></SPAN><SPAN><SPAN><SPAN><SPAN style=\"FONT-SIZE: 10pt\"><SPAN \n      style=\"COLOR: #808080\"><SPAN style=\"COLOR: #cc99ff\"><SPAN \n      style=\"COLOR: #0000ff\"><SPAN style=\"COLOR: #ff99cc\"><SPAN \n      style=\"FONT-SIZE: 10.5pt\"><SPAN style=\"COLOR: #800080\"><SPAN \n      style=\"COLOR: #ff99cc\"><SPAN style=\"COLOR: #00ccff\"><SPAN \n      style=\"FONT-SIZE: 10pt\"><SPAN style=\"COLOR: #808080\"><SPAN \n      style=\"FONT-SIZE: 11pt\"><SPAN style=\"FONT-SIZE: 10pt\"><SPAN \n      style=\"COLOR: #000000\"><SPAN style=\"FONT-SIZE: 9pt\"><SPAN \n      style=\"FONT-SIZE: 9pt\"><SPAN style=\"COLOR: #808080; FONT-SIZE: 10pt\"><SPAN \n      style=\"COLOR: #808080; FONT-SIZE: 10.5pt\">15817402<SPAN \n      style=\"FONT-SIZE: 10.5pt\"><SPAN \n      style=\"COLOR: #808080; FONT-SIZE: 10.5pt\">2</SPAN></SPAN><SPAN \n      style=\"COLOR: #808080; FONT-SIZE: 10.5pt\">39</SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN \n      style=\"FONT-SIZE: 16pt\"><SPAN \n      style=\"COLOR: #000000; FONT-SIZE: 16pt\"><SPAN style=\"COLOR: #333333\"><SPAN \n      style=\"FONT-FAMILY: MS Sans Serif; COLOR: #808080; FONT-SIZE: 10pt\">Sales</SPAN></SPAN></SPAN></SPAN></SPAN><SPAN \n      style=\"COLOR: #808080; FONT-SIZE: 16pt\"><SPAN><SPAN \n      style=\"FONT-SIZE: 14pt\"><SPAN \n      style=\"FONT-FAMILY: MS Sans Serif; FONT-SIZE: 10pt\"> \n      Leader</SPAN></SPAN></SPAN></SPAN></SPAN></SPAN><SPAN \n      style=\"FONT-FAMILY: Times New Roman\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: rgb(0,0,255); FONT-SIZE: 32px\"><I><SPAN \n      style=\"COLOR: #0000ff; FONT-SIZE: 26pt\"><SPAN \n      style=\"FONT-SIZE: 24pt\"><STRONG><SPAN style=\"FONT-SIZE: 11pt\"><SPAN \n      style=\"FONT-SIZE: 10pt\"><SPAN style=\"FONT-FAMILY: MS Sans Serif\"><SPAN \n      style=\"FONT-FAMILY: Utsaah\"><SPAN \n      style=\"FONT-FAMILY: MS Sans Serif; FONT-SIZE: 10pt\">&nbsp;</SPAN></SPAN></SPAN></SPAN>&nbsp;</SPAN> \n      </STRONG></SPAN></SPAN></I></SPAN></SPAN></DIV>\n      <DIV><SPAN style=\"FONT-FAMILY: Times New Roman\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: rgb(0,0,255); FONT-SIZE: 32px\"><I><SPAN \n      style=\"COLOR: #0000ff; FONT-SIZE: 26pt\"><SPAN \n      style=\"FONT-SIZE: 24pt\"><SPAN style=\"FONT-SIZE: 18pt\">Coretek Technology \n      Co., Ltd</SPAN></SPAN></SPAN></I></SPAN></SPAN></DIV>\n      <DIV><SPAN style=\"FONT-FAMILY: Times New Roman\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: rgb(0,0,255); FONT-SIZE: 32px\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: #0000ff; FONT-SIZE: 20pt\"><SPAN \n      style=\"COLOR: #ff0000; FONT-SIZE: 14pt\"><SPAN \n      style=\"FONT-SIZE: 14pt\"><SPAN style=\"FONT-SIZE: 14pt\"><SPAN \n      style=\"FONT-SIZE: 10.5pt\"><SPAN \n      style=\"COLOR: #fa566a; FONT-SIZE: 16pt\"><SPAN \n      style=\"FONT-SIZE: 12pt\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \n      Reliable OEM &amp; ODM Smart \n      Solutions</SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></DIV>\n      <DIV><SPAN style=\"FONT-FAMILY: Times New Roman\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: rgb(0,0,255); FONT-SIZE: 32px\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: #0000ff; FONT-SIZE: 20pt\"><SPAN \n      style=\"COLOR: #ff0000; FONT-SIZE: 14pt\"><SPAN \n      style=\"FONT-SIZE: 14pt\"><SPAN style=\"FONT-SIZE: 14pt\"><SPAN \n      style=\"FONT-SIZE: 10.5pt\"><SPAN style=\"FONT-SIZE: 16pt\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: rgb(0,0,255); FONT-SIZE: 32px\"><SPAN \n      style=\"COLOR: #000000; FONT-SIZE: 14pt\"><EM><SPAN \n      style=\"FONT-FAMILY: System; COLOR: #000000; FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; FONT-SIZE: 14pt\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; FONT-SIZE: 14pt\"><STRONG><SPAN \n      style=\"FONT-SIZE: 9pt\"></SPAN></STRONG></SPAN></SPAN></SPAN></EM></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></DIV>\n      <DIV><SPAN style=\"FONT-FAMILY: Times New Roman\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: rgb(0,0,255); FONT-SIZE: 32px\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: #0000ff; FONT-SIZE: 20pt\"><SPAN \n      style=\"COLOR: #ff0000; FONT-SIZE: 14pt\"><SPAN \n      style=\"COLOR: #000000; FONT-SIZE: 14pt\"><SPAN \n      style=\"FONT-SIZE: 12pt\"><SPAN style=\"FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-SIZE: 14pt\"><SPAN style=\"FONT-SIZE: 14pt\"><SPAN \n      style=\"FONT-SIZE: 14pt\"><SPAN style=\"FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-SIZE: 10pt\"><SPAN style=\"FONT-SIZE: 10pt\"><SPAN \n      style=\"FONT-SIZE: 11pt\"><SPAN \n      style=\"COLOR: #800000; FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #0000ff; FONT-SIZE: 9pt\">Tel</SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN><SPAN \n      style=\"FONT-SIZE: 11pt\"><SPAN \n      style=\"COLOR: #800000; FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-FAMILY: Arial; FONT-SIZE: 9pt\"> <SPAN \n      style=\"COLOR: #800000; FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #000000; FONT-SIZE: 9pt\">+86 755 3355 \n      2921&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #0000ff; FONT-SIZE: 9pt\">Fax</SPAN><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #000000; FONT-SIZE: 9pt\"> \n      </SPAN></SPAN><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #000000; FONT-SIZE: 9pt\">+86 755 2909 \n      0871</SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></DIV>\n      <DIV><SPAN style=\"FONT-FAMILY: Times New Roman; FONT-SIZE: 13px\"><SPAN \n      style=\"COLOR: #000000; FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-FAMILY: MS Sans Serif; COLOR: #000000; FONT-SIZE: 10pt\"><SPAN \n      style=\"FONT-SIZE: 12pt\"><SPAN style=\"FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-FAMILY: Aharoni\"><SPAN \n      style=\"FONT-FAMILY: Trebuchet MS; FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; FONT-SIZE: 14pt\"><SPAN><SPAN \n      style=\"FONT-FAMILY: Arial; FONT-SIZE: 9pt\"><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #0000ff; FONT-SIZE: 9pt\">Whatsapp / \n      Viber / <SPAN style=\"COLOR: #0000ff\"><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #0000ff; FONT-SIZE: 9pt\">Cell</SPAN></SPAN></SPAN><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #0000ff; FONT-SIZE: 9pt\">:</SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \n      </SPAN><SPAN style=\"FONT-FAMILY: Arial; FONT-SIZE: 9pt\">0086 \n      15817402239</SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #800000; FONT-SIZE: 9pt\"><BR></SPAN></DIV><SPAN \n      style=\"FONT-FAMILY: Times New Roman; COLOR: rgb(0,0,0); FONT-SIZE: 16px\"><SPAN \n      style=\"COLOR: #000000; FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-FAMILY: MS Sans Serif; COLOR: #000000; FONT-SIZE: 10pt\"><SPAN \n      style=\"FONT-SIZE: 12pt\"><SPAN style=\"FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-FAMILY: Aharoni\"><SPAN \n      style=\"FONT-FAMILY: Trebuchet MS; FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-FAMILY: Times New Roman; FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-SIZE: 14pt\"><SPAN \n      style=\"FONT-FAMILY: Arial; FONT-SIZE: 8pt\"><SPAN \n      style=\"COLOR: #800000; FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-SIZE: 9pt\"><SPAN style=\"FONT-SIZE: 12pt\"><SPAN \n      style=\"FONT-SIZE: 12pt\"><SPAN style=\"COLOR: #0000ff\"><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #0000ff; FONT-SIZE: 9pt\">Email</SPAN></SPAN></SPAN></SPAN>:</SPAN></SPAN>&nbsp;<A \n      href=\"mailto:Coremould@163.com\">Coremould@163.com</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \n      </SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN></SPAN><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #000000; FONT-SIZE: 9pt\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN><SPAN \n      style=\"COLOR: #800000; FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-SIZE: 14pt\"><SPAN \n      style=\"COLOR: #800000; FONT-SIZE: 10.5pt\"><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #0000ff; FONT-SIZE: 9pt\">Skype</SPAN></SPAN></SPAN><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #0000ff; FONT-SIZE: 9pt\">: \n      </SPAN></SPAN><SPAN \n      style=\"FONT-FAMILY: Arial; COLOR: #000000; FONT-SIZE: 9pt\">usbmaker01</SPAN><FONT \n      size=5><SPAN style=\"FONT-SIZE: 8pt\"> </SPAN></FONT></SPAN>\n      <DIV \n      style=\"CLEAR: both\"></DIV></DIV></FONT></DIV></DIV></STATIONERY></DIV></STATIONERY></DIV></STATIONERY></DIV></STATIONERY></DIV></STATIONERY></DIV></STATIONERY></DIV></DIV></DIV></DIV></TD></TR></TBODY></TABLE></DIV></DIV></BODY></HTML>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_289.argentina:2,",
    "content": "Return-Path: <qfzwjsy_0gz7chidgb.4g@trouver-presta.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id A0FD620A5737C\n\tfor <laurent@brasil.brainstorm.fr>; Tue,  5 Apr 2016 05:56:44 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 944AE3317701; Tue,  5 Apr 2016 05:56:44 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 858A3331770E; Tue,  5 Apr 2016 05:56:44 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: **********\nX-Spam-Status: Yes, score=10.5 required=5.0 tests=BAYES_99,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,HTML_IMAGE_RATIO_04,HTML_MESSAGE,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tRCVD_IN_BRBL_LASTEXT,T_KHOP_FOREIGN_CLICK,T_REMOTE_IMAGE,URIBL_BLACK\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [193.41.74.175 listed in bb.barracudacentral.org]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: trouver-presta.fr]\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.0 T_REMOTE_IMAGE Message contains an external image\nX-Spam-DCC: : \nReceived: from esmtp-b46.trouver-presta.fr (esmtp-b46.trouver-presta.fr [193.41.74.175])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 76CBC3317701\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 05:56:41 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=trouver-presta.fr; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; s=mailserverg; bh=mx6E6dEuv7tatbLeTn\n\tdnQBkDAXabHJpaR9djkd0Hmqk=; b=KgtH5Tp6bLTklmU3JmVS3za9ww90E82tHG\n\tiiuT+ul+mODudRXaQYHi9+xy2ui/KdtLTcUMKaqvDFR5M92NyxsxqNLhBEjkIqPw\n\tu6bk8/nmkfsHiG2CdF7eV7NkStCnXMu2kmc2sWog6HBb2bK4pI6gYa08G1jWyl7H\n\ttcK9OTRRo=\nDomainKey-Signature: a=rsa-sha1; c=nofws; d=trouver-presta.fr; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; q=dns; s=mailserverg; b=GxiYa07AgfT6\n\tcq5kC2s3MkMELwol3djErIQKUq8HHeTCB0Ve3DgaIEjRZgWmNsVrSl95gCt3h7IJ\n\tOOawSBAdU4uWSvq2TtnidnsqGnD4G0Hl3Ivt5KkiS8+TAp9YLEIIxnFPdMlKJmT5\n\tSKM4KT/Z/kD/yoXIFI/k+ZfFvCG76kY=\nTo: laurent <dev@caliopen.local>\nSubject: Avec ma nouvelle caisse enregistreuse l'encaissement est un jeu d'enfant !\nX-PHP-Originating-Script: 1002:class.phpmailer.php\nDate: Tue, 5 Apr 2016 05:56:40 +0200\nFrom: caisse enregistreuse <caisse@trouver-presta.fr>\nReply-to: caisse enregistreuse <qfzwjsy_0gz7chidgb.4g@trouver-presta.fr>\nX-campaign_id: 9544767838567738696_8\nX-uid_id_m: bGF1cmVudEBicmFpbnN0b3JtLmZy=501\nList-Unsubscribe: <http://app.trouver-presta.fr/d/?camp=9544767838567738696_8&ms=bGF1cmVudEBicmFpbnN0b3JtLmZy>\nMessage-ID: <7af37de14807c55ac99890f58e2d2aca@trouver-presta.fr>\nMIME-Version: 1.0\nContent-Type: multipart/alternative;\n\tboundary=\"Part1_7af37de14807c55ac99890f58e2d2aca\"\nX-Spam-Prev-Subject: Avec ma nouvelle caisse enregistreuse l'encaissement est un jeu d'enfant !\nContent-Length: 14164\nLines: 227\n\n--Part1_7af37de14807c55ac99890f58e2d2aca\nContent-Type: text/plain; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\nPour visualiser et se d=C3=A9sabonner ce message,\n=20\nVeuillez, copier puis coller, l'adresse URL compl=C3=A8te ci-dessous dans=\n la\nbarre d'adresse de votre navigateur et appuyer sur la touche \"Entr=C3=A9e=\n\" de\nvotre clavier.\n\n\n\n- - - - - - - - - - - - - - - - -=20\n\nhttp://app.trouver-presta.fr/v/?camp=3D9544767838567738696_8&ms=3DbGF1cmV=\nudEBicmFpbnN0b3JtLmZy\n\n- - - - - - - - - - - - - - - - -=20\n\n\n\n\n--Part1_7af37de14807c55ac99890f58e2d2aca\nContent-Type: text/html; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://ww=\nw.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=3D\"http://www.w3.org/1999/xhtml\" xml:lang=3D\"fr\" lang=3D\"fr\">\n<head>\n<meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3Dutf-8\" =\n/>\n<meta name=3D\"viewport\" content=3D\"width=3Ddevice-width, initial-scale=3D=\n1.0\" />\n<title>Avec ma nouvelle caisse enregistreuse l\\'encaissement est un jeu d=\n\\'enfant !</title>\n</head><body leftmargin=3D\"0\" marginheight=3D\"0\" topmargin=3D\"0\" marginwi=\ndth=3D\"0\" style=3D\"-webkit-text-size-adjust:none; text-size-adjust:none; =\nmargin:0px; padding:0px; -ms-text-size-adjust:none; -moz-text-size-adjust=\n:none;\">\n<center><p>\n<a href=3D\"http://app.trouver-presta.fr/v/?camp=3D9544767838567738696_8&m=\ns=3DbGF1cmVudEBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Si cet e-mai=\nl ne s'affiche pas correctement, suivez ce lien.\" style=3D\"color:#00F; fo=\nnt-family:'Arial'; font-size:12px; text-decoration:underline;\">Si cet ema=\nil ne s&rsquo;affiche pas correctement, suivez ce lien.</a></p><table bor=\nder=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\" align=3D\"cent=\ner\" bgcolor=3D\"#FFFFFF\">     <tbody>         <tr>             <td width=3D=\n\"600\" height=3D\"40\" align=3D\"center\" valign=3D\"middle\" bgcolor=3D\"#4a4452=\n\"><font style=3D\" font-family:Arial, Helvetica, sans-serif; font-size:10p=\nx ; font-weight: normal; color:#ffffff;\">Ma Nouvelle Caisse enregistreuse=\n est un jeu d'enfant.<br>             Si ce message ne s'affiche pas corr=\nectement, <a style=3D\"color:#37c686;\" href=3D\"http://app.trouver-presta.f=\nr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D9544767838567738696_8&rc=3DaHR0=\ncDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy0yNy0zNjItMzAxOC11cmwtYUhSMGNEb=\n3ZMMkp3Y204dV\n puSXZNemN5TUY5Q1VETmZUbVZ2Y0Q5eFkzQTlNemN5TUY5Q1VETmZUbVZ2Y0NOMWRHMWZjMj=\nkxY21ObFBXNG1kWFJ0WDIxbFpHbDFiVDFsTFcxaGFXd21kWFJ0WDJOaGJYQmhhV2R1UFc1bGQ=\nzTmpKblYwYlY5dWIyOTJaWEp5YVdSbFBURT9yZ3JvdXA9cF8xMTM1MSZhbXA7dHJhY2thZmY9=\nJmFtcDtncm91cD0=3D\" target=3D\"_blank\">cliquez ici.</a></font></td>       =\n  </tr>         <tr>             <td width=3D\"600\" height=3D\"77\" bgcolor=3D=\n\"#4a4452\" align=3D\"center\" style=3D\"font-family:Arial, Helvetica, sans-se=\nrif; color:#FFFFFF; font-size:24px; text-transform: uppercase;\"><img src=3D=\n\"http://app.trouver-presta.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS9=\n1cGxvYWRzL3Zpc3VlbHMvMTMvMjcva2l0cy9Mb2dvX0Jwcm8uanBn\" alt=3D\"BPRO\" width=\n=3D\"136\" height=3D\"77\" border=3D\"0\"></td>         </tr>         <tr>     =\n        <td width=3D\"600\" height=3D\"43\" align=3D\"center\" valign=3D\"middle=\n\" bgcolor=3D\"#4a4452\"><a style=3D\"text-decoration: none\" href=3D\"http://a=\npp.trouver-presta.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D954476783856=\n7738696_8&rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy0yNy0zNjItMz=\nAxOC11cmwt\n YUhSMGNEb3ZMMkp3Y204dVpuSXZNemN5TUY5Q1VETmZUbVZ2Y0Q5eFkzQTlNemN5TUY5Q1VE=\nTmZUbVZ2Y0NOMWRHMWZjMjkxY21ObFBXNG1kWFJ0WDIxbFpHbDFiVDFsTFcxaGFXd21kWFJ0W=\nDJOaGJYQmhhV2R1UFc1bGQzTmpKblYwYlY5dWIyOTJaWEp5YVdSbFBURT9yZ3JvdXA9cF8xMT=\nM1MSZhbXA7dHJhY2thZmY9JmFtcDtncm91cD0=3D\" target=3D\"_blank\"><font style=3D=\n\"font-family:Arial, Helvetica, sans-serif; text-transform:uppercase; font=\n-size:22px;; color:#37c686\">Caisse enregistreuse</font></a></td>         =\n</tr>         <tr>             <td width=3D\"600\" height=3D\"2\" bgcolor=3D\"=\n#37c686\">&nbsp;</td>         </tr>         <tr>             <td>         =\n    <table border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\"=\n>                 <tbody>                     <tr>                       =\n  <td width=3D\"72\" height=3D\"15\">&nbsp;</td>                         <td =\nwidth=3D\"1\" height=3D\"15\" bgcolor=3D\"#c8c7cb\">&nbsp;</td>                =\n         <td width=3D\"66\" height=3D\"15\">&nbsp;</td>                      =\n   <td width=3D\"419\" height=3D\"15\">&nbsp;</td>                         <t=\nd wid\n th=3D\"42\" height=3D\"15\">&nbsp;</td>                     </tr>           =\n      </tbody>             </table>             </td>         </tr>      =\n   <tr>             <td>             <table border=3D\"0\" cellpadding=3D\"0=\n\" cellspacing=3D\"0\" width=3D\"600\">                 <tbody>               =\n      <tr>                         <td><a href=3D\"http://app.trouver-pres=\nta.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D9544767838567738696_8&rc=3D=\naHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy0yNy0zNjItMzAxOC11cmwtYUhSM=\nGNEb3ZMMkp3Y204dVpuSXZNemN5TUY5Q1VETmZUbVZ2Y0Q5eFkzQTlNemN5TUY5Q1VETmZUbV=\nZ2Y0NOMWRHMWZjMjkxY21ObFBXNG1kWFJ0WDIxbFpHbDFiVDFsTFcxaGFXd21kWFJ0WDJOaGJ=\nYQmhhV2R1UFc1bGQzTmpKblYwYlY5dWIyOTJaWEp5YVdSbFBURT9yZ3JvdXA9cF8xMTM1MSZh=\nbXA7dHJhY2thZmY9JmFtcDtncm91cD0=3D\" target=3D\"_blank\"><img src=3D\"http://=\napp.trouver-presta.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWR=\nzL3Zpc3VlbHMvMTMvMjcva2l0cy9QaWN0b19FcXVpcG10LmpwZw=3D=3D\" width=3D\"139\" =\nheight=3D\"98\" border=3D\"0\" alt=3D\"\"></a></td>                         <td=\n>      =20\n                   <table border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0=\n\" width=3D\"419\">                             <tbody>                     =\n            <tr>                                     <td width=3D\"19\">&nb=\nsp;</td>                                     <td width=3D\"400\" height=3D\"=\n4\">&nbsp;</td>                                 </tr>                     =\n            <tr>                                     <td width=3D\"19\" hei=\nght=3D\"60\" bgcolor=3D\"#37c686\">&nbsp;</td>                               =\n      <td width=3D\"400\" align=3D\"left\" valign=3D\"middle\" bgcolor=3D\"#37c6=\n86\"><a style=3D\"text-decoration: none\" href=3D\"http://app.trouver-presta.=\nfr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D9544767838567738696_8&rc=3DaHR=\n0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy0yNy0zNjItMzAxOC11cmwtYUhSMGNE=\nb3ZMMkp3Y204dVpuSXZNemN5TUY5Q1VETmZUbVZ2Y0Q5eFkzQTlNemN5TUY5Q1VETmZUbVZ2Y=\n0NOMWRHMWZjMjkxY21ObFBXNG1kWFJ0WDIxbFpHbDFiVDFsTFcxaGFXd21kWFJ0WDJOaGJYQm=\nhhV2R1UFc1bGQzTmpKblYwYlY5dWIyOTJaWEp5YVdSbFBURT9yZ3JvdXA9cF8xMTM1MSZhbXA=\n7dHJhY\n 2thZmY9JmFtcDtncm91cD0=3D\" target=3D\"_blank\"><font style=3D\"font-family:=\nArial, Helvetica, sans-serif; font-size:16px; color:#ffffff\"><span style=3D=\n\"font-size:16px; text-transform:uppercase; font-weight:bold;\">Avec ma nou=\nvelle caisse enregistreuse<br>                                     </span=\n>l'encaissement est un jeu d'enfant !                 </font></a></td>   =\n                              </tr>                                 <tr> =\n                                    <td width=3D\"19\">&nbsp;</td>         =\n                            <td width=3D\"400\" height=3D\"4\">&nbsp;</td>   =\n                              </tr>                             </tbody> =\n                        </table>                         </td>           =\n              <td width=3D\"42\" height=3D\"98\">&nbsp;</td>                 =\n    </tr>                 </tbody>             </table>             </td>=\n         </tr>         <tr>             <td>             <table border=3D=\n\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\">          =20\n       <tbody>                     <tr>                         <td width=\n=3D\"72\" height=3D\"53\">&nbsp;</td>                         <td bgcolor=3D\"=\n#c8c7cb\" width=3D\"1\">&nbsp;</td>                         <td width=3D\"66\"=\n>&nbsp;</td>                         <td width=3D\"419\"><a style=3D\"text-d=\necoration: none\" href=3D\"http://app.trouver-presta.fr/r/?m=3DbGF1cmVudEBi=\ncmFpbnN0b3JtLmZy&c=3D9544767838567738696_8&rc=3DaHR0cDovL25vZGVzLm5wdjE3c=\nWVqYmQuY29tL3IvY2xpYy0yNy0zNjItMzAxOC11cmwtYUhSMGNEb3ZMMkp3Y204dVpuSXZNem=\nN5TUY5Q1VETmZUbVZ2Y0Q5eFkzQTlNemN5TUY5Q1VETmZUbVZ2Y0NOMWRHMWZjMjkxY21ObFB=\nXNG1kWFJ0WDIxbFpHbDFiVDFsTFcxaGFXd21kWFJ0WDJOaGJYQmhhV2R1UFc1bGQzTmpKblYw=\nYlY5dWIyOTJaWEp5YVdSbFBURT9yZ3JvdXA9cF8xMTM1MSZhbXA7dHJhY2thZmY9JmFtcDtnc=\nm91cD0=3D\" target=3D\"_blank\"><font style=3D\"font-family:Arial, Helvetica,=\n sans-serif; font-size:15px; color:#4a4452\">                         <ul>=\n                             <li>Facile</li>                             =\n<li>Pratique</li>                             <li>Ergonomique</li>  =20\n                       </ul>                         </font></a></td>    =\n                     <td width=3D\"42\">&nbsp;</td>                     </t=\nr>                 </tbody>             </table>             </td>       =\n  </tr>         <tr>             <td>             <table border=3D\"0\" cel=\nlpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\">                 <tbody>  =\n                   <tr>                         <td width=3D\"72\" height=3D=\n\"16\">&nbsp;</td>                         <td width=3D\"1\" height=3D\"16\" bg=\ncolor=3D\"#c8c7cb\">&nbsp;</td>                                            =\n      <td height=3D\"16\" width=3D\"485\">&nbsp;</td>                        =\n                          <td width=3D\"42\" height=3D\"16\">&nbsp;</td>     =\n                </tr>                 </tbody>             </table>      =\n       </td>         </tr>         <tr>             <td>             <tab=\nle border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\">       =\n          <tbody>                     <tr>                         <td wi=\ndth\n =3D\"72\" height=3D\"178\">&nbsp;</td>                         <td width=3D\"=\n1\" height=3D\"178\" bgcolor=3D\"#c8c7cb\">&nbsp;</td>                        =\n <td><a href=3D\"http://app.trouver-presta.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b=\n3JtLmZy&c=3D9544767838567738696_8&rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY2=\n9tL3IvY2xpYy0yNy0zNjItMzAxOC11cmwtYUhSMGNEb3ZMMkp3Y204dVpuSXZNemN5TUY5Q1V=\nETmZUbVZ2Y0Q5eFkzQTlNemN5TUY5Q1VETmZUbVZ2Y0NOMWRHMWZjMjkxY21ObFBXNG1kWFJ0=\nWDIxbFpHbDFiVDFsTFcxaGFXd21kWFJ0WDJOaGJYQmhhV2R1UFc1bGQzTmpKblYwYlY5dWIyO=\nTJaWEp5YVdSbFBURT9yZ3JvdXA9cF8xMTM1MSZhbXA7dHJhY2thZmY9JmFtcDtncm91cD0=3D=\n\" style=3D\"font-family:Arial, Helvetica, sans-serif; text-decoration:none=\n ;\" target=3D\"_blank\"><img src=3D\"http://app.trouver-presta.fr/r/?rc=3DaH=\nR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3VlbHMvMTMvMjcva2l0cy9WaXN=\n1ZWwuanBn\" width=3D\"485\" height=3D\"178\" border=3D\"0\" alt=3D\"\"></a></td>  =\n                       <td width=3D\"42\" height=3D\"178\">&nbsp;</td>       =\n              </tr>                 </tbody>             </table>        =\n     </t\n d>         </tr>         <tr>             <td>             <table border=\n=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\">                =\n <tbody>                     <tr>                         <td width=3D\"72=\n\" height=3D\"33\">&nbsp;</td>                         <td bgcolor=3D\"#c8c7c=\nb\" width=3D\"1\" height=3D\"33\">&nbsp;</td>                         <td widt=\nh=3D\"527\" height=3D\"33\">&nbsp;</td>                     </tr>            =\n     </tbody>             </table>             </td>         </tr>       =\n  <tr>             <td>             <table border=3D\"0\" cellpadding=3D\"0\"=\n cellspacing=3D\"0\" width=3D\"600\">                 <tbody>                =\n     <tr>                         <td width=3D\"72\" height=3D\"65\">&nbsp;</=\ntd>                         <td width=3D\"1\" height=3D\"65\" bgcolor=3D\"#c8c=\n7cb\">&nbsp;</td>                         <td width=3D\"91\" height=3D\"65\">&=\nnbsp;</td>                         <td bgcolor=3D\"#37c686\" align=3D\"cente=\nr\"><a style=3D\"font-family:Arial, Helvetica, sans-serif; color:#FFFFFF; f=\nont-size:26px; tex\n t-transform: uppercase; font-weight:bold;\" href=3D\"http://app.trouver-pr=\nesta.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D9544767838567738696_8&rc=3D=\naHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy0yNy0zNjItMzAxOC11cmwtYUhSM=\nGNEb3ZMMkp3Y204dVpuSXZNemN5TUY5Q1VETmZUbVZ2Y0Q5eFkzQTlNemN5TUY5Q1VETmZUbV=\nZ2Y0NOMWRHMWZjMjkxY21ObFBXNG1kWFJ0WDIxbFpHbDFiVDFsTFcxaGFXd21kWFJ0WDJOaGJ=\nYQmhhV2R1UFc1bGQzTmpKblYwYlY5dWIyOTJaWEp5YVdSbFBURT9yZ3JvdXA9cF8xMTM1MSZh=\nbXA7dHJhY2thZmY9JmFtcDtncm91cD0=3D\" target=3D\"_blank\"><img src=3D\"http://=\napp.trouver-presta.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWR=\nzL3Zpc3VlbHMvMTMvMjcva2l0cy9CdG5fRXF1aXBtdC5qcGc=3D\" alt=3D\"en savoir plu=\ns\" width=3D\"272\" height=3D\"65\" border=3D\"0\"></a></td>                    =\n     <td width=3D\"164\" height=3D\"65\">&nbsp;</td>                     </tr=\n>                 </tbody>             </table>             </td>        =\n </tr>         <tr>             <td><img src=3D\"http://app.trouver-presta=\n.fr/r/?rc=3DaHR0cHM6Ly9ub2Rlcy5uZW9wZXJmLmNvbS91cGxvYWRzL3Zpc3VlbHMvMTMvM\n jcva2l0cy9Gb290ZXJfRXF1aXBtdC5qcGc=3D\" width=3D\"600\" height=3D\"33\" borde=\nr=3D\"0\" alt=3D\"\"></td>         </tr>         <tr>             <td width=3D=\n\"600\" height=3D\"35\" align=3D\"center\" bgcolor=3D\"#4a4452\">&nbsp;</td>     =\n    </tr>     </tbody> </table><img alt=3D\"\" src=3D\"http://app.trouver-pr=\nesta.fr/r/?rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL21haWxpbmctMjctMzYyL=\nTMwMTg/cmdyb3VwPXBfMTEzNTEmYW1wO2dyb3VwPQ=3D=3D\" border=3D\"0\" height=3D\"1=\n\" width=3D\"1\"><img src=3D\"http://app.trouver-presta.fr/t/?i=3D95447678385=\n67738696_8&m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&url=3Dhttp://app.trouver-pres=\nta.fr/images/blank.jpg\" alt=3D\"_pspacer5\"  /><p>\n<a href=3D\"http://app.trouver-presta.fr/d/?camp=3D9544767838567738696_8&m=\ns=3DbGF1cmVudEBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Ne plus rece=\nvoir d'informations de notre part\" style=3D\"color:#00F; font-family:'Aria=\nl'; font-size:12px; text-decoration:underline;\">Pour se d&eacute;sabonner=\n : Suivez ce lien.</a><br />Si ce message vous a caus&eacute; un quelconq=\nue d&eacute;rangement, nous vous prions de nous en excuser.\n</p><center>\n</body>\n</html>\n\n\n\n--Part1_7af37de14807c55ac99890f58e2d2aca--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_291.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anJIl-0005As-S0\n\tfor mharc-dev@caliopen.local; Tue, 05 Apr 2016 01:13:19 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49060)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <yamaoka@jpl.org>) id 1anJIk-0005Ad-6r\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 01:13:18 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <yamaoka@jpl.org>) id 1anJIf-0001Z1-83\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 01:13:18 -0400\nReceived: from mail-hampton.hostforweb.net ([205.234.186.191]:57897\n\thelo=hampton.hostforweb.net) by eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <yamaoka@jpl.org>) id 1anJIf-0001Tk-4K\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 01:13:13 -0400\nReceived: from s70.gtokyofl21.vectant.ne.jp ([202.215.75.70]:60231\n\thelo=localhost) by hampton.hostforweb.net with esmtpsa\n\t(TLSv1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.86_1)\n\t(envelope-from <yamaoka@jpl.org>)\n\tid 1anJHv-003tti-6t; Tue, 05 Apr 2016 00:12:28 -0500\nDate: Tue, 05 Apr 2016 14:12:34 +0900\nMessage-ID: <b4mr3ekabb1.fsf@jpl.org>\nFrom: Katsumi Yamaoka <yamaoka@jpl.org>\nTo: emacs-devel <emacs-devel@gnu.org>\nSubject: Re: master 17cb263: New C macro AUTO_STRING_WITH_LEN\nReferences: <20160404173130.17984.53277@vcs.savannah.gnu.org>\n\t<E1an8La-0004gu-Mw@vcs.savannah.gnu.org>\nOrganization: Emacsen advocacy group\nX-Face: #kKnN,xUnmKia.'[pp`;\n\tOmh}odZK)?7wQSl\"4o04=EixTF+V[\"\"w~iNbM9ZL+.b*_CxUmFk\n\tB#Fu[*?MZZH@IkN:!\"\\w%I_zt>[$nm7nQosZ<3eu;\n\tB:$Q_:p!',P.c0-_Cy[dz4oIpw0ESA^D*1Lw= L&i*6&(\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (i686-pc-cygwin)\nCancel-Lock: sha1:VO0drZXyaEUKrPmhvWgF4egcnO0=\nMIME-Version: 1.0\nContent-Type: text/plain; charset=us-ascii\nX-OutGoing-Spam-Status: No, score=-2.9\nX-AntiAbuse: This header was added to track abuse,\n\tplease include it with any abuse report\nX-AntiAbuse: Primary Hostname - hampton.hostforweb.net\nX-AntiAbuse: Original Domain - gnu.org\nX-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12]\nX-AntiAbuse: Sender Address Domain - jpl.org\nX-Get-Message-Sender-Via: hampton.hostforweb.net: authenticated_id:\n\tyamaoka/from_h\nX-Authenticated-Sender: hampton.hostforweb.net: yamaoka@jpl.org\nX-Source: \nX-Source-Args: \nX-Source-Dir: \nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x (barebone)\n\t[generic]\nX-Received-From: 205.234.186.191\nCc: Paul Eggert <eggert@cs.ucla.edu>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 05:13:19 -0000\nContent-Length: 490\nLines: 18\n\nOn Mon, 04 Apr 2016 17:31:30 +0000, Paul Eggert wrote:\n> branch: master\n> commit 17cb263adb7c37803140604f0a2e4df8a38fbcff\n> Author: Paul Eggert <eggert@cs.ucla.edu>\n> Commit: Paul Eggert <eggert@cs.ucla.edu>\n\n>     New C macro AUTO_STRING_WITH_LEN\n\nWhat should I add to let it know what prtdiff_t is on Cygwin?\nI got:\n\ncoding.c: In function 'from_unicode_buffer':\ncoding.c:8423:3: error: unknown type name 'prtdiff_t'\n   prtdiff_t len = 1 + sizeof (wchar_t) * wcslen (wstr);\n   ^\n\nThanks.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_293.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anJeC-00022P-Sh\n\tfor mharc-dev@caliopen.local; Tue, 05 Apr 2016 01:35:28 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52717)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1anJe9-0001wX-CR\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 01:35:26 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1anJe6-0006Y3-7S\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 01:35:25 -0400\nReceived: from zimbra.cs.ucla.edu ([131.179.128.68]:50737)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1anJe6-0006Xx-2D\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 01:35:22 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 1447B160D97;\n\tMon,  4 Apr 2016 22:35:20 -0700 (PDT)\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032)\n\twith ESMTP id g1Iu5AHz74xt; Mon,  4 Apr 2016 22:35:19 -0700 (PDT)\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 6CC8C16125C;\n\tMon,  4 Apr 2016 22:35:19 -0700 (PDT)\nX-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026)\n\twith ESMTP id GAXK2NrxdUCB; Mon,  4 Apr 2016 22:35:19 -0700 (PDT)\nReceived: from [192.168.1.9] (unknown [100.32.155.148])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 4B9C7160D97;\n\tMon,  4 Apr 2016 22:35:19 -0700 (PDT)\nSubject: Re: master 17cb263: New C macro AUTO_STRING_WITH_LEN\nTo: Katsumi Yamaoka <yamaoka@jpl.org>, Emacs Development <Emacs-devel@gnu.org>\nReferences: <20160404173130.17984.53277@vcs.savannah.gnu.org>\n\t<E1an8La-0004gu-Mw@vcs.savannah.gnu.org> <b4mr3ekabb1.fsf@jpl.org>\nFrom: Paul Eggert <eggert@cs.ucla.edu>\nOrganization: UCLA Computer Science Department\nMessage-ID: <57034E97.2040301@cs.ucla.edu>\nDate: Mon, 4 Apr 2016 22:35:19 -0700\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <b4mr3ekabb1.fsf@jpl.org>\nContent-Type: text/plain; charset=utf-8; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 131.179.128.68\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 05:35:26 -0000\nContent-Length: 150\nLines: 5\n\nKatsumi Yamaoka wrote:\n> What should I add to let it know what prtdiff_t is on Cygwin?\n\nSorry, that was a typo for 'ptrdiff_t'. Should be fixed now.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_295.argentina:2,",
    "content": "Return-Path: <marrs@ridgelyes.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id E4B4420839EA8\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 23:39:30 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid D5C4134902BC; Mon,  4 Apr 2016 23:39:30 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid C652ECBC226; Mon,  4 Apr 2016 23:39:30 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ********\nX-Spam-Status: Yes, score=8.8 required=5.0 tests=BAYES_99,DATE_IN_FUTURE_03_06,\n\tHS_INDEX_PARAM,HTML_MESSAGE,MIME_HTML_ONLY,PLING_QUERY autolearn=no\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 0.9972]\n\t*  3.0 DATE_IN_FUTURE_03_06 Date: is 3 to 6 hours after Received: date\n\t*  0.6 HS_INDEX_PARAM URI: Link contains a common tracker pattern.\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  1.0 PLING_QUERY Subject has exclamation mark and question mark\nX-Spam-DCC: : \nReceived: from ds123.mirohost.net (ds123.mirohost.net [89.184.69.169])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id E894334902BC\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 23:39:27 +0200 (CEST)\nReceived: from marrs by postut.ds123.mirohost.net with local (Exim 4.30)\n\tid 7YscCe-gdxor5-nc\n\tfor dev@caliopen.local; Tue, 05 Apr 2016 04:40:56 +0300\nTo: \"laurent\" <dev@caliopen.local>\nSubject: Figure a classic relationship 513-685-8970 is too much strain? Me as well!\nMessage-Id: <7YscCe-gdxor5-nc@postut.ds123.mirohost.net>\nFrom: \"Madoline\" <marrs@ridgelyes.com>\nDate: Tue, 05 Apr 2016 04:40:56 +0300\nMime-Version: 1.0\nContent-Type: text/html\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Figure a classic relationship 513-685-8970 is too much strain? Me as well!\nContent-Length: 849\nLines: 1\n\n<html><body>Come on, who has the time and the stability for this relationship crap right now?<br><a href=\"http://www.estensione.net/wp-content/plugins/wp-db-backup-made/index.php?qvvko2r2kvphlhuorjsj4ig3tq\">I know I don't.</a> But there is something I do have all that for!<br>You may have understood already lol.<br>Let's drop all this accountability stuff and just have a amazing time.<br>No need to think about giving me flowers or remembering my birthday.<br>Good old-fashioned joy between the sheets, that's what I'm crazy about!<br><a href=\"http://www.estensione.net/wp-content/plugins/wp-db-backup-made/index.php?qvvko2r2kvphlhuorjsj4ig3tq\">See my totally stunning pics here http://www.estensione.net/wp-content/plugins/wp-db-backup-made/index.php?qvvko2r2kvphlhuorjsj4ig3tq</a> text me 918-532-7062 and let's make this happen!</body></html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_297.argentina:2,",
    "content": "Return-Path: <qfzwjsy_0gz7chidgb.4g@partenariat-pro.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id D897420A3279F\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 22:51:58 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid A28CF33B3177; Mon,  4 Apr 2016 22:51:58 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 7485B33B504C; Mon,  4 Apr 2016 22:51:58 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.0 required=5.0 tests=BAYES_99,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,HTML_IMAGE_RATIO_04,HTML_MESSAGE,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,T_REMOTE_IMAGE,\n\tURIBL_BLACK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: partenariat-pro.fr]\n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.0 T_REMOTE_IMAGE Message contains an external image\nX-Spam-DCC: : \nReceived: from esmtp-b6.partenariat-pro.fr (esmtp-b6.partenariat-pro.fr [193.41.74.135])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id B67F533B3177\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 22:51:15 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=partenariat-pro.fr; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; s=mailservera; bh=/jz6NGtI0IbNwY6RGM\n\tocl02m8tegsRwKc5y2XRbpaUg=; b=Y3Y6UkMszGytl0CU4FbMWJBFQaMj75ekpA\n\tq3Cc0ZWjMzNX3UmJOmLZMM25gPloJgIMY336E/aEZk0ltrSlXox7pB6LF8zGeRyr\n\tKLCjL5N6DmfuYECXyO4GgP08kV6z+1BTs+/ovrYgHRAkbOaJ74q1wM7S2jj8d8WT\n\taSL9ozxWA=\nDomainKey-Signature: a=rsa-sha1; c=nofws; d=partenariat-pro.fr; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; q=dns; s=mailservera; b=Xcc6x0/8/swM\n\t3qp+8u82QYuFJWOCx4TEVfzvLfbCo2k+IeVIBDuSLSLe7PzzHWbZ0oTyAuO4KemA\n\t0X74F85wHmaBITGJa4l/d6syBBS0qAHM+XDr8XVx2Wrnyqe/L8/UaVRzoy4AtWHQ\n\tjRQV3W18j9cRgpwR4S8ruCkwN5YHDhk=\nTo: laurent <dev@caliopen.local>\nSubject: =?utf-8?Q?Am=C3=A9liorez_l'accueil_de_vos_visiteurs_et_la_convivialit?=  =?utf-8?Q?=C3=A9_de_votre_entreprise?=\nX-PHP-Originating-Script: 1002:class.phpmailer.php\nDate: Mon, 4 Apr 2016 22:51:15 +0200\nFrom: =?utf-8?Q?Machine_=C3=A0_caf=C3=A9?= <Machine@partenariat-pro.fr>\nReply-to: =?utf-8?Q?Machine_=C3=A0_caf=C3=A9?= <qfzwjsy_0gz7chidgb.4g@partenariat-pro.fr>\nX-campaign_id: 210_22\nX-uid_id_m: bGF1cmVudEBicmFpbnN0b3JtLmZy=372\nList-Unsubscribe: <http://app.partenariat-pro.fr/d/?camp=210_22&ms=bGF1cmVudEBicmFpbnN0b3JtLmZy>\nMessage-ID: <9d1bf0c6876c4eca3f1355e198ba7fa7@partenariat-pro.fr>\nMIME-Version: 1.0\nContent-Type: multipart/alternative;\n\tboundary=\"Part1_9d1bf0c6876c4eca3f1355e198ba7fa7\"\nX-Spam-Prev-Subject: =?utf-8?Q?Am=C3=A9liorez_l'accueil_de_vos_visiteurs_et_la_convivialit?=  =?utf-8?Q?=C3=A9_de_votre_entreprise?=\nContent-Length: 11959\nLines: 197\n\n--Part1_9d1bf0c6876c4eca3f1355e198ba7fa7\nContent-Type: text/plain; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\nPour visualiser et se d=C3=A9sabonner ce message,\n=20\nVeuillez, copier puis coller, l'adresse URL compl=C3=A8te ci-dessous dans=\n la\nbarre d'adresse de votre navigateur et appuyer sur la touche \"Entr=C3=A9e=\n\" de\nvotre clavier.\n\n\n\n- - - - - - - - - - - - - - - - -=20\n\nhttp://app.partenariat-pro.fr/v/?camp=3D210_22&ms=3DbGF1cmVudEBicmFpbnN0b=\n3JtLmZy\n\n- - - - - - - - - - - - - - - - -=20\n\n\n\n\n--Part1_9d1bf0c6876c4eca3f1355e198ba7fa7\nContent-Type: text/html; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://ww=\nw.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=3D\"http://www.w3.org/1999/xhtml\" xml:lang=3D\"fr\" lang=3D\"fr\">\n<head>\n<meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3Dutf-8\" =\n/>\n<meta name=3D\"viewport\" content=3D\"width=3Ddevice-width, initial-scale=3D=\n1.0\" />\n<title>Am=C3=A9liorez l\\'accueil de vos visiteurs et la convivialit=C3=A9=\n de votre entreprise</title>\n</head><body marginwidth=3D\"0\" topmargin=3D\"0\" marginheight=3D\"0\" style=3D=\n\"text-size-adjust:none; -moz-text-size-adjust:none; margin:0px; padding:0=\npx; -ms-text-size-adjust:none; -webkit-text-size-adjust:none;\" leftmargin=\n=3D\"0\">\n<center><p>\n<a href=3D\"http://app.partenariat-pro.fr/v/?camp=3D210_22&ms=3DbGF1cmVudE=\nBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Si cet e-mail ne s'affiche=\n pas correctement, suivez ce lien.\" style=3D\"color:#00F; font-family:'Ari=\nal'; font-size:12px; text-decoration:underline;\">Si cet email ne s&rsquo;=\naffiche pas correctement, suivez ce lien.</a></p><table align=3D\"center\" =\nborder=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\">     <tbod=\ny>         <tr>             <td>             <table align=3D\"left\" border=\n=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\">                =\n <tbody>                     <tr>                         <td><a href=3D\"=\nhttp://app.partenariat-pro.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D210=\n_22&rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy04My0zNjItNTY2Mi11=\ncmwtYUhSMGNEb3ZMM2QzZHk1a1pYWnBjeTFsYm5SeVpYQnlhWE5sTG1KcGVpOHhORFU1WDBSR=\nlh6QXhNVFl2YVc1a1pYZ3VhSFJ0YkQ5eFkzQTlNVFExT1Y5RVJWOHdNVEUySm5WMGJWOXpiM1=\nZ5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2M=\nyTW1kWFJ\n 0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzEwNjU5JmFtcDt0cmFja2FmZj0mYW1wO2d=\nyb3VwPQ=3D=3D\" style=3D\"font-family:Arial, Helvetica, sans-serif; text-de=\ncoration:none ;\" target=3D\"_blank\"><img style=3D\"display:block\" name=3D\"d=\nevisentrepriseb_r1_c1\" src=3D\"http://app.partenariat-pro.fr/r/?rc=3DaHR0c=\nDovL3d3dy5kZXZpcy1lbnRyZXByaXNlLmJpei8xNDU5X0RFXzAxMTYvaW1nL2RldmlzLWVudH=\nJlcHJpc2ViX3IxX2MxLmpwZw=3D=3D\" id=3D\"devisentrepriseb_r1_c1\" alt=3D\"\" he=\night=3D\"62\" width=3D\"240\"></a></td>                         <td><a href=3D=\n\"http://app.partenariat-pro.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D21=\n0_22&rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy04My0zNjItNTY2Mi1=\n1cmwtYUhSMGNEb3ZMM2QzZHk1a1pYWnBjeTFsYm5SeVpYQnlhWE5sTG1KcGVpOHhORFU1WDBS=\nRlh6QXhNVFl2YVc1a1pYZ3VhSFJ0YkQ5eFkzQTlNVFExT1Y5RVJWOHdNVEUySm5WMGJWOXpiM=\n1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2=\nMyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzEwNjU5JmFtcDt0cmFja2FmZj0=\nmYW1wO2dyb3VwPQ=3D=3D\" style=3D\"font-family:Arial, Helvetica, sans-serif;=\n text-dec\n oration:none ;\" target=3D\"_blank\"><img style=3D\"display:block\" name=3D\"d=\nevisentrepriseb_r1_c4\" src=3D\"http://app.partenariat-pro.fr/r/?rc=3DaHR0c=\nDovL3d3dy5kZXZpcy1lbnRyZXByaXNlLmJpei8xNDU5X0RFXzAxMTYvaW1nL2RldmlzLWVudH=\nJlcHJpc2ViX3IxX2M0LmpwZw=3D=3D\" id=3D\"devisentrepriseb_r1_c4\" alt=3D\"\" he=\night=3D\"62\" width=3D\"360\"></a></td>                     </tr>            =\n     </tbody>             </table>             </td>         </tr>       =\n  <tr>             <td><img style=3D\"display:block\" name=3D\"devisentrepri=\nseb_r2_c1\" src=3D\"http://app.partenariat-pro.fr/r/?rc=3DaHR0cDovL3d3dy5kZ=\nXZpcy1lbnRyZXByaXNlLmJpei8xNDU5X0RFXzAxMTYvaW1nL2RldmlzLWVudHJlcHJpc2ViX3=\nIyX2MxLmpwZw=3D=3D\" id=3D\"devisentrepriseb_r2_c1\" alt=3D\"\" height=3D\"8\" w=\nidth=3D\"600\"></td>         </tr>         <tr>             <td>           =\n  <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" =\nwidth=3D\"600\">                 <tbody>                     <tr>          =\n               <td bgcolor=3D\"#C4C4C4\"><img style=3D\"display:block\" name=3D=\n\"devisentrepriseb_r3_c1\"\n  src=3D\"http://app.partenariat-pro.fr/r/?rc=3DaHR0cDovL3d3dy5kZXZpcy1lbn=\nRyZXByaXNlLmJpei8xNDU5X0RFXzAxMTYvaW1nL2RldmlzLWVudHJlcHJpc2ViX3IzX2MxLmp=\nwZw=3D=3D\" id=3D\"devisentrepriseb_r3_c1\" alt=3D\"\" height=3D\"278\" width=3D=\n\"29\"></td>                         <td height=3D\"278\" bgcolor=3D\"#01AFEC\"=\n width=3D\"18\">&nbsp;</td>                         <td height=3D\"278\" bgco=\nlor=3D\"#01AFEC\" width=3D\"273\">                         <table align=3D\"le=\nft\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"273\">      =\n                       <tbody>                                 <tr>      =\n                               <td height=3D\"95\" width=3D\"271\"><a href=3D=\n\"http://app.partenariat-pro.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D21=\n0_22&rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL3IvY2xpYy04My0zNjItNTY2Mi1=\n1cmwtYUhSMGNEb3ZMM2QzZHk1a1pYWnBjeTFsYm5SeVpYQnlhWE5sTG1KcGVpOHhORFU1WDBS=\nRlh6QXhNVFl2YVc1a1pYZ3VhSFJ0YkQ5eFkzQTlNVFExT1Y5RVJWOHdNVEUySm5WMGJWOXpiM=\n1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2=\nMyTW1kWFJ0WDI1dmIz\n WmxjbkpwWkdVOU1RP3Jncm91cD1wXzEwNjU5JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=\n=3D\" style=3D\"font-family:Arial, Helvetica, sans-serif; text-decoration:n=\none ;\" target=3D\"_blank\"><span style=3D\" font-family:Arial, Helvetica, sa=\nns-serif; font-size:20px ; font-weight: normal; color: #FFFFFF;\">R&eacute=\n;veillez votre &eacute;quipe avec<br>                                    =\n <strong style=3D\"font-size:24px;\">un Caf&eacute; de qualit&eacute; !</st=\nrong> </span></a></td>                                 </tr>             =\n                    <tr>                                     <td height=3D=\n\"12\" width=3D\"271\"><img style=3D\"display:block\" src=3D\"http://app.partena=\nriat-pro.fr/r/?rc=3DaHR0cDovL3d3dy5kZXZpcy1lbnRyZXByaXNlLmJpei8xNDU5X0RFX=\nzAxMTYvaW1nL2JhcnJlLnBuZw=3D=3D\" alt=3D\"----------\" height=3D\"12\" border=3D=\n\"0\" width=3D\"271\"></td>                                 </tr>            =\n                     <tr>                                     <td height=3D=\n\"168\" width=3D\"271\"><a href=3D\"http://app.partenariat-pro.fr/r/?m=3DbGF1c=\nmVud\n EBicmFpbnN0b3JtLmZy&c=3D210_22&rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29t=\nL3IvY2xpYy04My0zNjItNTY2Mi11cmwtYUhSMGNEb3ZMM2QzZHk1a1pYWnBjeTFsYm5SeVpYQ=\nnlhWE5sTG1KcGVpOHhORFU1WDBSRlh6QXhNVFl2YVc1a1pYZ3VhSFJ0YkQ5eFkzQTlNVFExT1=\nY5RVJWOHdNVEUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjF=\nkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1w=\nXzEwNjU5JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" style=3D\"font-family:Ar=\nial, Helvetica, sans-serif; text-decoration:none ;\" target=3D\"_blank\"><sp=\nan style=3D\" font-family:Arial, Helvetica, sans-serif; font-size:14px ; f=\nont-weight: normal; color: #FFFFFF;\"><strong>Am&eacute;liorez l'accueil d=\ne vos visiteurs </strong><br>                                     et la c=\nonvivialit&eacute; de votre entreprise.<br>                              =\n       <br>                                     <strong>Achat, Location, =\nou d&eacute;p&ocirc;t gratuit</strong> <br>                              =\n       &agrave; vous de choisir.<br>                  =20\n                   <br>                                     <strong>Caf&e=\nacute;, Th&eacute;, Chocolat, Soupe,</strong> <br>                       =\n              donnez vous le choix. </span></a></td>                     =\n            </tr>                             </tbody>                   =\n      </table>                         </td>                         <td =\nbgcolor=3D\"#C4C4C4\"><a href=3D\"http://app.partenariat-pro.fr/r/?m=3DbGF1c=\nmVudEBicmFpbnN0b3JtLmZy&c=3D210_22&rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY=\n29tL3IvY2xpYy04My0zNjItNTY2Mi11cmwtYUhSMGNEb3ZMM2QzZHk1a1pYWnBjeTFsYm5SeV=\npYQnlhWE5sTG1KcGVpOHhORFU1WDBSRlh6QXhNVFl2YVc1a1pYZ3VhSFJ0YkQ5eFkzQTlNVFE=\nxT1Y5RVJWOHdNVEUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJD=\nWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91c=\nD1wXzEwNjU5JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" style=3D\"font-family=\n:Arial, Helvetica, sans-serif; text-decoration:none ;\" target=3D\"_blank\">=\n<img style=3D\"display:block\" name=3D\"devisentrepriseb_r3_c5\" sr\n c=3D\"http://app.partenariat-pro.fr/r/?rc=3DaHR0cDovL3d3dy5kZXZpcy1lbnRyZ=\nXByaXNlLmJpei8xNDU5X0RFXzAxMTYvaW1nL2RldmlzLWVudHJlcHJpc2ViX3IzX2M1LmpwZw=\n=3D=3D\" id=3D\"devisentrepriseb_r3_c5\" alt=3D\"\" height=3D\"278\" width=3D\"28=\n0\"></a></td>                     </tr>                 </tbody>          =\n   </table>             </td>         </tr>         <tr>             <td>=\n             <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cellspa=\ncing=3D\"0\" width=3D\"600\">                 <tbody>                     <tr=\n>                         <td bgcolor=3D\"#C4C4C4\"><img style=3D\"display:b=\nlock\" name=3D\"devisentrepriseb_r4_c1\" src=3D\"http://app.partenariat-pro.f=\nr/r/?rc=3DaHR0cDovL3d3dy5kZXZpcy1lbnRyZXByaXNlLmJpei8xNDU5X0RFXzAxMTYvaW1=\nnL2RldmlzLWVudHJlcHJpc2ViX3I0X2MxLmpwZw=3D=3D\" id=3D\"devisentrepriseb_r4_=\nc1\" alt=3D\"\" height=3D\"107\" width=3D\"29\"></td>                         <t=\nd align=3D\"center\" bgcolor=3D\"#F65646\"><a href=3D\"http://app.partenariat-=\npro.fr/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D210_22&rc=3DaHR0cDovL25vZG=\nVzLm5wdjE3cWVqYmQuY29tL3IvY2\n xpYy04My0zNjItNTY2Mi11cmwtYUhSMGNEb3ZMM2QzZHk1a1pYWnBjeTFsYm5SeVpYQnlhWE=\n5sTG1KcGVpOHhORFU1WDBSRlh6QXhNVFl2YVc1a1pYZ3VhSFJ0YkQ5eFkzQTlNVFExT1Y5RVJ=\nWOHdNVEUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFm=\nWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzEwN=\njU5JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" style=3D\"font-family:Arial, =\nHelvetica, sans-serif; text-decoration:none ;\" target=3D\"_blank\"><img sty=\nle=3D\"display:block\" name=3D\"devisentrepriseb_r4_c2\" src=3D\"http://app.pa=\nrtenariat-pro.fr/r/?rc=3DaHR0cDovL3d3dy5kZXZpcy1lbnRyZXByaXNlLmJpei8xNDU5=\nX0RFXzAxMTYvaW1nL2RldmlzLWVudHJlcHJpc2ViX3I0X2MyLmpwZw=3D=3D\" id=3D\"devis=\nentrepriseb_r4_c2\" alt=3D\"en savoir plus\" height=3D\"107\" width=3D\"291\"></=\na></td>                         <td bgcolor=3D\"#C4C4C4\"><img style=3D\"dis=\nplay:block\" name=3D\"devisentrepriseb_r4_c5\" src=3D\"http://app.partenariat=\n-pro.fr/r/?rc=3DaHR0cDovL3d3dy5kZXZpcy1lbnRyZXByaXNlLmJpei8xNDU5X0RFXzAxM=\nTYvaW1nL2RldmlzLWVudHJlcHJpc2ViX3I0X2M1LmpwZw=3D=3D\" id=3D\"devisentrepris=\neb_r4_c5\" al\n t=3D\"\" height=3D\"107\" width=3D\"280\"></td>                     </tr>     =\n            </tbody>             </table>             </td>         </tr>=\n     </tbody> </table><img alt=3D\"\" src=3D\"http://app.partenariat-pro.fr/=\nr/?rc=3DaHR0cDovL25vZGVzLm5wdjE3cWVqYmQuY29tL21haWxpbmctODMtMzYyLTU2NjI/c=\nmdyb3VwPXBfMTA2NTkmYW1wO2dyb3VwPQ=3D=3D\" height=3D\"1\" border=3D\"0\" width=3D=\n\"1\"><img src=3D\"http://app.partenariat-pro.fr/t/?i=3D210_22&m=3DbGF1cmVud=\nEBicmFpbnN0b3JtLmZy&url=3Dhttp://app.partenariat-pro.fr/images/blank.jpg\"=\n alt=3D\"_pspacer5\"  /><p>\n<a href=3D\"http://app.partenariat-pro.fr/d/?camp=3D210_22&ms=3DbGF1cmVudE=\nBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Ne plus recevoir d'informa=\ntions de notre part\" style=3D\"color:#00F; font-family:'Arial'; font-size:=\n12px; text-decoration:underline;\">Pour se d&eacute;sabonner : Suivez ce l=\nien.</a><br />Si ce message vous a caus&eacute; un quelconque d&eacute;ra=\nngement, nous vous prions de nous en excuser.\n</p><center>\n</body>\n</html>\n\n\n\n--Part1_9d1bf0c6876c4eca3f1355e198ba7fa7--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_299.argentina:2,",
    "content": "Return-Path: <evelyn_graves@bestwebhostingrates.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 7662336FD6763\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 21:32:10 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 6241EF6B4C8; Mon,  4 Apr 2016 21:32:10 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 4D5C5F6C35C; Mon,  4 Apr 2016 21:32:10 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: **************\nX-Spam-Status: Yes, score=14.6 required=5.0 tests=BAYES_95,DATE_IN_PAST_96_XX,\n\tDKIM_SIGNED,FS_NUDE,HTML_MESSAGE,HTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tRCVD_IN_BRBL_LASTEXT,RDNS_DYNAMIC,TO_NO_BRKTS_DYNIP,T_DKIM_INVALID\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  1.1 FS_NUDE Subject says Nude\n\t*  3.4 DATE_IN_PAST_96_XX Date: is 96 hours or more before Received: date\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  3.0 BAYES_95 BODY: Bayes spam probability is 95 to 99%\n\t*      [score: 0.9791]\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [163.172.17.74 listed in bb.barracudacentral.org]\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  1.0 RDNS_DYNAMIC Delivered to internal network by host with\n\t*      dynamic-looking rDNS\n\t*  0.0 T_DKIM_INVALID DKIM-Signature header exists but is not valid\n\t*  0.1 TO_NO_BRKTS_DYNIP To: misformatted and dynamic rDNS\nX-Spam-DCC: : \nReceived: from host.wave20services.com (163-172-17-74.rev.poneytelecom.eu [163.172.17.74])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id BF5D8F6B4C8\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 21:32:07 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed;\n\td=bestwebhostingrates.com; s=default; h=Date:Message-Id:\n\tContent-Transfer-Encoding:Content-Type:MIME-Version:Reply-To:From:Subject:To;\n\t bh=2J9lXIzwsyfiJ6RbXN0to3ICuyRB887JAS8tEFulltE=; b=YxNqYgAhFHdjLT3qV2+TbJOc4\n\tJrjhfeq6CFhZvHvUiLddq5vmhzaEk628CGMdU2JAS97TtOtclc1n4tudqSUNmbJANmL5z/2EC3yMF\n\tnWFEB3J+u1hvL5xOR8LBiB0+Dl2xoC6hpG5BzgdhSQP9AF5h9mzxyVcPXIAmQrIdEEh5I=;\nReceived: from bestweb5 by host.wave20services.com with local (Exim 4.86_1)\n\t(envelope-from <evelyn_graves@bestwebhostingrates.com>)\n\tid 1ahgCQ-0005bi-4g\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 17:27:30 +0100\nTo: dev@caliopen.local\nSubject: RE:  Fat nude blonde with tiny tits toys with vibrator\nX-PHP-Script: www.bestwebhostingrates.com/go/class/stats.php for 192.232.238.146, 162.158.57.77\nFrom: \"Evelyn Graves\" <evelyn_graves@bestwebhostingrates.com>\nReply-To:\"Evelyn Graves\" <evelyn_graves@bestwebhostingrates.com>\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nMessage-Id: <E1ahgCQ-0005bi-4g@host.wave20services.com>\nDate: Sun, 20 Mar 2016 17:27:30 +0100\nX-AntiAbuse: This header was added to track abuse, please include it with any abuse report\nX-AntiAbuse: Primary Hostname - host.wave20services.com\nX-AntiAbuse: Original Domain - brainstorm.fr\nX-AntiAbuse: Originator/Caller UID/GID - [1032 992] / [47 12]\nX-AntiAbuse: Sender Address Domain - bestwebhostingrates.com\nX-Get-Message-Sender-Via: host.wave20services.com: authenticated_id: bestweb5/from_h\nX-Authenticated-Sender: host.wave20services.com: evelyn_graves@bestwebhostingrates.com\nX-Source: \nX-Source-Args: \nX-Source-Dir: \nX-Spam-Prev-Subject: RE:  Fat nude blonde with tiny tits toys with vibrator\nContent-Length: 202\nLines: 4\n\n\n<div>\n<div>Fat nude blonde with tiny tits toys with vibrator <a href=\"http://LESUREWALKER.COM/wp-content/plugins/types/embedded/common/res/press.php?bmN3cGdsdkJgcGNrbHF2bXBvLGRw\">here</a></div>\n</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_301.argentina:2,",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id C61A436FD3D4F\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 21:03:20 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid B90DE3490288; Mon,  4 Apr 2016 21:03:20 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid ABCD53490290; Mon,  4 Apr 2016 21:03:20 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.3 required=5.0 tests=AWL,BAYES_80,\n\tHTML_FONT_FACE_BAD,HTML_FONT_SIZE_LARGE,HTML_MESSAGE,MPART_ALT_DIFF,\n\tNORMAL_HTTP_TO_IP,RCVD_IN_BRBL_LASTEXT,RCVD_IN_CSS,T_KHOP_FOREIGN_CLICK\n\tautolearn=no version=3.3.2\nX-Spam-Report: \n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [63.251.243.216 listed in zen.spamhaus.org]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [63.251.243.216 listed in bb.barracudacentral.org]\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  2.0 BAYES_80 BODY: Bayes spam probability is 80 to 95%\n\t*      [score: 0.8666]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  1.0 HTML_FONT_FACE_BAD BODY: HTML font face is not a word\n\t*  0.0 HTML_FONT_SIZE_LARGE BODY: HTML font size is large\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.0 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from server103.privee-du.in (server103.privee-du.in [63.251.243.216])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 8E30E3490288\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 21:03:11 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: Dites stop au calcaire. 500 euros offerts\nMessage-ID: <a8cf0b823da839ad69685e8f65a0e4d8@162.244.67.108>\nDate: Mon, 04 Apr 2016 13:16:20 -0500\nFrom: \"Culligan\" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 180\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&L=180&N=10965>\nX-Mailer-SID: 10965\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_9b85688518d4decb653cb706354cf327\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Dites stop au calcaire. 500 euros offerts\nContent-Length: 5278\nLines: 140\n\n--b1_9b85688518d4decb653cb706354cf327\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&S=10965&L=180&N=3647\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&L=180&N=10965\n\n--b1_9b85688518d4decb653cb706354cf327\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<div>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n<title>STOP AU CALCAIRE avec Culligan !</title>\n<link href=\"http://collecting.fr/operations/culligan032016/favicon.ico\"\nrel=\"icon\" type=\"image/x-icon\" />\n<style type=\"text/css\"><!--\n        @media only screen and (max-width:600px) {\n            table {\n                width:100%;\n                }\n            table td {\n                display:block;\n                width:100%;\n                text-align:center;\n                }\n            table .texte {\n\t\t\t\tpadding:0 5%;\n\t\t\t\twidth:90%;\n                }\n            table td .bouton {\n                max-width:80%;\n                }\n            table td img {\n                max-width:100%;\n                }\n            .display {\n                display:none\n                }\n            }\n--></style>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&S=10965&L=180&N=3647\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" align=\"center\"\nstyle=\"width: 600px;\">\n<tbody>\n<tr>\n<td align=\"left\" valign=\"bottom\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10965&L=14572&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://collecting.fr/operations/culligan032016/emailing/images/logo.gif\"\nalt=\"Culligan\" border=\"0\" /></a></td>\n<td align=\"right\" valign=\"bottom\"><br /><font color=\"#0051a0\"\nface=\"'Trebuchet MS','Lucida Grande','Lucida Sans Unicode','Lucida\nSans',Tahoma,sans-serif\" size=\"3\" style=\"font-size:15px;\nline-height:18px;\"> <strong>Culligan, n&deg;1 du traitement\nd&rsquo;eau</strong><br /> Adoucisseur d'eau et fontaine &agrave; eau<br />\n70 boutiques en France </font></td>\n</tr>\n<tr>\n<td colspan=\"2\"><br /></td>\n</tr>\n<tr>\n<td colspan=\"2\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10965&L=14572&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://collecting.fr/operations/culligan032016/emailing/images/bandeau.jpg\"\nalt=\"Stop au calcaire\" border=\"0\" /></a></td>\n</tr>\n<tr>\n<td colspan=\"2\" bgcolor=\"#ffffff\" align=\"center\" class=\"texte\"><br /><br />\n<font color=\"#0051a0\" face=\"'Trebuchet MS','Lucida Grande','Lucida Sans\nUnicode','Lucida Sans',Tahoma,sans-serif\" size=\"6\" style=\"font-size:30px;\nline-height:32px;\"> <strong>OBTENEZ VOTRE DIAGNOSTIC <br />GRATUIT ET\nRAPIDE</strong> </font> <br /><br /> <font color=\"#FF0000\" face=\"'Trebuchet\nMS','Lucida Grande','Lucida Sans Unicode','Lucida Sans',Tahoma,sans-serif\"\nsize=\"5\" style=\"font-size:20px;\"> <strong>Profitez de 500&euro; OFFERTS*\njusqu'au 28/03/2016 !</strong> </font> <br /><br /> <font color=\"#444444\"\nface=\"'Trebuchet MS','Lucida Grande','Lucida Sans Unicode','Lucida\nSans',Tahoma,sans-serif\" size=\"5\" style=\"font-size:20px;\"> <strong>95,4%**\nde clients satisfaits</strong> <br />Rejoignez les 325 000 clients\nd&eacute;j&agrave; &eacute;quip&eacute;s <br />d'un adoucisseur Culligan\n</font> <br /><br /><a\nhref=\"http://162.244.67.108/~client/link.php?M=15236122&N=10965&L=14572&F=H\"\ntarget=\"_blank\"><img\nsrc=\"http://collecting.fr/operations/culligan032016/emailing/images/jen-profite.gif\"\nalt=\"J'en profite\" class=\"bouton\" /></a> <br /><br /><br /></td>\n</tr>\n<tr>\n<td colspan=\"2\"><img\nsrc=\"http://collecting.fr/operations/culligan032016/emailing/images/ombres.gif\"\n/></td>\n</tr>\n<tr>\n<td colspan=\"2\" align=\"justify\"><font color=\"#999999\" face=\"'Trebuchet\nMS','Lucida Grande','Lucida Sans Unicode','Lucida Sans',Tahoma,sans-serif\"\nsize=\"2\" style=\"font-size:12px; line-height:16px;\"> * * 300&euro; OFFERTS\npour l&rsquo;achat d'un adoucisseur et 200&euro; OFFERTS pour l&rsquo;achat\nd&rsquo;un osmoseur. Offre valable pour toute commande pass&eacute;e\njusqu'au 28/03/16, non cumulable avec d&rsquo;autres offres\npromotionnelles.             \t<br />** &Eacute;tude portant sur 1 850\nClients Culligan interrog&eacute;s en 2013 (enqu&ecirc;te\nt&eacute;l&eacute;phonique r&eacute;alis&eacute;e par le Centre\nd&rsquo;Appels CCA International). </font></td>\n</tr>\n</tbody>\n</table>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=15236122&C=7fd31e1ab2cfdb56580e8bf68744d029&L=180&N=10965\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n<img\nsrc=\"http://baseco.solution.weborama.fr/fcgi-bin/adserv.fcgi?tag=1156122&amp;f=10&amp;h=R&amp;rnd=[778542]\"\nwidth=\"1\" height=\"1\" border=\"0\" /></div>\n</div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=15236122&L=180&N=10965&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_9b85688518d4decb653cb706354cf327--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_303.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amxFR-0005Mf-C7\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 01:40:25 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34146)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amxFP-0005MM-5w\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:40:24 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amxFL-00046A-Uu\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:40:23 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:57886)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amxFL-000465-Nb\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:40:19 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amxFK-0003TJ-C3\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 07:40:18 +0200\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 07:40:18 +0200\nReceived: from larsi by cm-84.215.1.64.getinternet.no with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 07:40:18 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nMail-Followup-To: dev@caliopen.local\nTo: dev@caliopen.local\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: \"Staying in the same place\"\nDate: Mon, 04 Apr 2016 07:40:13 +0200\nOrganization: Programmerer Ingebrigtsen\nMessage-ID: <m3oa9q53uq.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: cm-84.215.1.64.getinternet.no\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAD1BMVEX+AA+Qb6f/AAAL8frw\n\tEDRIFBcCAAACXklEQVQ4jV1Ti5XcMAiUdSrArFSATFTAEVyAPvRfUwb5bnOJ39u3tkYMMAwhvJ80\n\t/r6pBn4/bb1fDYCZxeer5tcbyBuYXxEqWT+f13+AD8UjCcf/AdkBvdmG9bGB/cxrn+tvAMUOB3qy\n\tbmku8FCsJZqV3nKwTWYp+vVRWAu+UeGm6t0aelIzFdVCxLyCJWtO5Uw3frhAFPn6St4OBJBmFRT3\n\tuiY3RAyiXDzAhIjw9VqQAFpleQolJIUaXM98fgEZhyMTfXV65nsDqgsUu0oouZUqD0BjqzQ8ogPo\n\thR6gn7skKhIbsNoN9TpQthxDUi7O1uqSCaBqYQh4Z2mkPqrAGSMLpYlZUhIAlkHOkQc0C3lAtkk3\n\tmm5m9EJdNZMDt5+kBaDaXLLLFQMgKjGZV8xjNjcEMpYYILSc4MBgBy4u9ParjBsAQW7DnHHDiBta\n\tyCLCiMC/2VUATfQOLkhBTpU1l14BCh8nr093BU0AS5fECk+c3ISveyFCygaK9lmTRdDPy4eAJ7h8\n\td7azgq/j/vXYQQG8GL4elqUMTI18Aig+oA1ubqyltmACGAJUAMhLSMcJY9G4IYfbAYfBrUx0QCgt\n\tQztJBIjDsKvWnrvp3b0QiFh81RoJej7HC9knLmG2ww0V2KhXvWtB9jNjmp7Gc0S2DgfNA8rfS6D6\n\t4SrtrU1V9bNhi+QDKtZnsxyoCcXHBOqjeMA22t7zfl6oBsDoexW/qTjxL2S54Dm0jsTlO6JGrLJw\n\tyvDbOPSdAx5bLp3BiK1cZ96dx587PhnLxMslDvMHQM9+XLvB1xsY29JuOF+xP6y7tHae4BG4AAAA\n\tAElFTkSuQmCC\nMail-Copies-To: never\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:Ew0wejF7hO8r+Rt4YBVV47fBPoo=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 05:40:24 -0000\nContent-Length: 1464\nLines: 32\n\nThere's a lot of modes in Emacs that don't reflect a physical file, but\nhas only generated content (for some values of \"generated\").  Many of\nthese implement a version of \"revert\", usually bound to the `g' key,\nwhich basically do an `erase-buffer' and then generate the buffer anew.\n\nAnd then you have to implement some way of \"staying in the same place\".\n\nWhich is a pretty fuzzy, do-what-I-mean kind of thing, because the data\nmay have changed.  The size of the buffer usually has, and the line you\nwere on may have disappeared.\n\nI think it would be nice if Emacs had a general function for \"try to\nstay in the same place\", but I don't know whether that's realistic.  For\ninstance, dired notes both the point and the file name you're at, and\nfirst tries to go to the file in question, or if the file is gone, it...\nuhm...  Goes to the point and then goes to the file on that line?\nSomething.\n\nI wonder if anybody has thought about the issue in a more general way --\nby providing a mechanism to save (perhaps a lot) of context, and then\ntrying to go back to the same context again after regenerating the\nbuffer, in a ... fuzzy way.\n\n(The reason I'm thinking about this is that I was thinking about what\nwould be necessary to make `g' in compilation buffers remain in \"the\nsame place\".  Which is useful when you're trying to weed out compilation\nwarnings.)\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_305.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an3z8-0007XY-9T\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 08:52:02 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46958)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1an3z6-0007XS-7R\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 08:52:01 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1an3z2-0005Ru-73\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 08:52:00 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:59036)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1an3z2-0005Rq-03\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 08:51:56 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1an3yz-0007p1-MG\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:51:53 +0200\nReceived: from 69-165-138-79.dsl.teksavvy.com ([69.165.138.79])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 14:51:53 +0200\nReceived: from monnier by 69-165-138-79.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 14:51:53 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: \"Staying in the same place\"\nDate: Mon, 04 Apr 2016 08:51:47 -0400\nMessage-ID: <jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <m3oa9q53uq.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-165-138-79.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:pkrwxJt6BXGMp1bqnSXqFulfiwY=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 12:52:01 -0000\nContent-Length: 1728\nLines: 41\n\n> There's a lot of modes in Emacs that don't reflect a physical file, but\n> has only generated content (for some values of \"generated\").  Many of\n> these implement a version of \"revert\", usually bound to the `g' key,\n> which basically do an `erase-buffer' and then generate the buffer anew.\n\nI think it'd good to think of such solutions in the context of bookmarks\nas well.\n\nIOW, I think ideally, there should be a special-mode-revert-buffer\nfunction (which special-mode sets as the default value of\nrevert-buffer-function), which basically just does a \"get bookmark data,\nthen go back to that bookmark\".\n\nI don't think it can be done with the current bookmark data, tho.\nIIRC there were 2 main reasons:\n- You need to be able to distinguish the case \"go back to bookmark in\n  a new buffer\" and \"go back to bookmark in the current buffer\".\n- You probably want to distinguish between the case where you want to\n  revert/refresh, and the case where it's OK to just jump to some\n  spot in some existing buffer.\n\nLast time I looked at it, I also figured that it'd be nice if the\nhelp-xref-* could use the same infrastructure.\n\n> And then you have to implement some way of \"staying in the same place\".\n\nBookmarks do that part acceptably, I think.\n\n> (The reason I'm thinking about this is that I was thinking about what\n> would be necessary to make `g' in compilation buffers remain in \"the\n> same place\".  Which is useful when you're trying to weed out compilation\n> warnings.)\n\nOohh.. that's yet another step up in complexity since in that case you\ncan't \"go back\" right away, you first have to wait for the process to\nsend enough data.\nBut yes, that would be great (I'd particularly appreciate it in\n*vc-diff*).\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_307.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an96i-0003TQ-AT\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 14:20:12 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52377)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an96c-0003Hg-FW\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:20:10 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an96X-0002Xl-Gc\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:20:06 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:47476)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an96X-0002Xh-B9\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:20:01 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1an96U-0005jZ-N4; Mon, 04 Apr 2016 20:20:00 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: \"Staying in the same place\"\nReferences: <m3oa9q53uq.fsf@gnus.org>\n\t<jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAFVBMVEVBLS3xwrCgcme+iHpc\n\tQ0EjFRZ8W1bWwY36AAACc0lEQVQ4jXWTwXLjIAyGtQ72OY5nck6xy7mBbM7UljmzrcnZa1O9/yOs\n\twG263dlqEgf+T+KXgYCL4KxAS7GKUKGdiAR6R8AfGhGBwSi8LW2voISF0+NEoy/90glwY5jmQglI\n\t1RSn+Hp1x55zkOaKprmax2kEsmJ8qOt6tqvBZZ7oBTB6K0CUMLBeN0z3NI/4a2ZHdi6HrOdohG3o\n\tYQ8lMjCm/ozjc3p2w5Fb+wLuwV2Z8//AU/kNqNV3wLyDJlrheHS4fQVdEBjRDl8Ad/Um2jy7DvXf\n\t4HzqP3wOQ+eGO7hWd/9m0ZciDxI4te/pP+pubc7nO8gFB33zRX19qC+brhkU4ufj874odtEcwJl+\n\tqc0GenjqVquGKq5Ka3eZ2qYxaal+fup3s0WAHbnO7cremAyoA8fnDyDW1ZgYTxXrkgEg8EmCaM/1\n\t7+bkFHrmyQMJEd3CaTV/OyFCsQHqpZSL0cQ/hhsQ424DEqUx/rRjRPhIZbSF0clDKscJ86tuTQc2\n\thBjf2gwurUIXsVrrw6JGL+YYhwyu87IQv8VaH41GQIuukgpadgfhwILAKy0RUQh3Y8DG5sTXNgJi\n\tIfgS8nWbb5JAcolRlP4QrV412ZJvLSoGqURLfknebcpD64gSSCXpHFOC4omUiigy2EiX97TtVp0B\n\tfgBNawK6nfsEIgMFCxMdtxWVmqTkAoQYwotMq5vNClwuQChDuMkPI+5JpoIEQgiVTAsnlPVknYCH\n\t6hS8/IxUwEcaACqukoo3qyvDIh9v5DAE8IBs47mHHCK88XbBBliH8E94Bsmm/JS2HFZZ9CU/WfBl\n\tltLSKT0JICxPc6FHHnr4A6Hy1TBvDubcAAAAAElFTkSuQmCC\nDate: Mon, 04 Apr 2016 20:19:58 +0200\nIn-Reply-To: <jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Mon, 04 Apr 2016 08:51:47 -0400\")\nMessage-ID: <m3mvp9nsmp.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 18:20:10 -0000\nContent-Length: 1899\nLines: 47\n\nStefan Monnier <monnier@iro.umontreal.ca> writes:\n\n> I think it'd good to think of such solutions in the context of bookmarks\n> as well.\n\nHm...  interesting...\n\nI know next to nothing about Emacs bookmarking.  How are bookmarks\nstored (for special mode buffers)?\n\n> I don't think it can be done with the current bookmark data, tho.\n> IIRC there were 2 main reasons:\n> - You need to be able to distinguish the case \"go back to bookmark in\n>   a new buffer\" and \"go back to bookmark in the current buffer\".\n> - You probably want to distinguish between the case where you want to\n>   revert/refresh, and the case where it's OK to just jump to some\n>   spot in some existing buffer.\n\nI'm trying to envision how this would work from a UX point of view.\nLet's say you're trying to do some \"-Wall\" cleanups in emacs/src, and\nyou've gotten halfway down the list of things you're able to clean up,\nso you set a bookmark there.  Then the next time you compile something\nin that directory, then point moves to around that point?  \n\n>> (The reason I'm thinking about this is that I was thinking about what\n>> would be necessary to make `g' in compilation buffers remain in \"the\n>> same place\".  Which is useful when you're trying to weed out compilation\n>> warnings.)\n>\n> Oohh.. that's yet another step up in complexity since in that case you\n> can't \"go back\" right away, you first have to wait for the process to\n> send enough data.\n\nYes, it's an added wrinkle, but I think it should be possible to\nhandle.  I made a first stab at it in the recently reverted `r'/`l'\ncode, and it seemed to be kinda nice, even if the implementation wasn't\nvery general.\n\n> But yes, that would be great (I'd particularly appreciate it in\n> *vc-diff*).\n\nIndeed, staying in the \"same place\" in *vc-diff* would be very nice.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_309.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an9N6-0008W4-SV\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 14:37:08 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56841)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1an9N4-0008VI-9O\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:37:07 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1an9N1-00076C-1L\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:37:06 -0400\nReceived: from pruche.dit.umontreal.ca ([132.204.246.22]:38325)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1an9N0-000760-SQ\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:37:02 -0400\nReceived: from pastel.home (lechon.iro.umontreal.ca [132.204.27.242])\n\tby pruche.dit.umontreal.ca (8.14.7/8.14.1) with ESMTP id u34IaxHD011215;\n\tMon, 4 Apr 2016 14:37:00 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid BB5E9603DE; Mon,  4 Apr 2016 14:36:59 -0400 (EDT)\nFrom: Stefan Monnier <monnier@IRO.UMontreal.CA>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: \"Staying in the same place\"\nMessage-ID: <jwv8u0tql7y.fsf-monnier+Inbox@gnu.org>\nReferences: <m3oa9q53uq.fsf@gnus.org>\n\t<jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3mvp9nsmp.fsf@gnus.org>\nDate: Mon, 04 Apr 2016 14:36:59 -0400\nIn-Reply-To: <m3mvp9nsmp.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 04 Apr 2016 20:19:58 +0200\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-NAI-Spam-Flag: NO\nX-NAI-Spam-Threshold: 5\nX-NAI-Spam-Score: 0\nX-NAI-Spam-Rules: 1 Rules triggered\n\tRV5632=0\nX-NAI-Spam-Version: 2.3.0.9418 : core <5632> : inlines <4637> : streams\n\t<1614033> : uri <2179731>\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 132.204.246.22\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 18:37:07 -0000\nContent-Length: 1174\nLines: 31\n\n>> I think it'd good to think of such solutions in the context of bookmarks\n>> as well.\n> Hm...  interesting...\n> I know next to nothing about Emacs bookmarking.  How are bookmarks\n> stored (for special mode buffers)?\n\nFor the current discussion the part that saves bookmarks is\nnot important.\n\nThe important part is that you can build a \"bookmark object\" which should\ncontain enough data to reproduce the current buffer&position.\n\n> I'm trying to envision how this would work from a UX point of view.\n> Let's say you're trying to do some \"-Wall\" cleanups in emacs/src, and\n> you've gotten halfway down the list of things you're able to clean up,\n> so you set a bookmark there.  Then the next time you compile something\n> in that directory, then point moves to around that point?  \n\nYou might be able to do that, but what I was proposing was not to let\nusers use the bookmark functionality, but simply to have a default\nspecial-mode-revert-buffer function which would do something along\nthe lines of:\n\n    (defun special-mode-revert-function ()\n      (let ((here (funcall bookmark-make-record-function)))\n        (erase-buffer)\n        (bookmark-goback here)))\n\n\n-- Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_311.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an9Sf-0001ii-Ac\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 14:42:53 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:58464)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an9Sc-0001gl-H5\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:42:51 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an9SY-0008OZ-I5\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:42:50 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:47671)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an9SY-0008OU-BW\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:42:46 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1an9SV-0005wr-QK; Mon, 04 Apr 2016 20:42:45 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Stefan Monnier <monnier@IRO.UMontreal.CA>\nSubject: Re: \"Staying in the same place\"\nReferences: <m3oa9q53uq.fsf@gnus.org>\n\t<jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3mvp9nsmp.fsf@gnus.org> <jwv8u0tql7y.fsf-monnier+Inbox@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEUODhQ5OT4SExoNDRMI\n\tCQ4MDBHdutiZAAACVUlEQVQ4jVWUwXKjMAyGDczekdLckZvcnXp9L2DuUKP3f5X9ZSDT9SQzoE+S\n\tfwnLrh2SqvO2nNvze7kW7wHWwXkX1ZaZZw9weoNrTBqd7nkJ3q2+utfVhRg1FOT+gudvsCJbjF2M\n\t2CAADRfAL8UYgztAW7PgD0sESc7Ad/UM3RmRStQSTtBdslyIX/Zs6d2KdyLiM6JhPMcLuPfex0oV\n\tIK5uDEM0WXiMEWC21CGm+A78o6Vg8xVCO2dOV/UoXAGgLSVN+g5AJ5O1pERzAX5L0H03MKNvi+p/\n\tIKfVuyUV3RcttT6IcUmZGKDUL6PxrKQT4iFtAPWTjc8VcgZNP6j76T+mwVJpHpksVVlgt3Vf+wMI\n\t+ZudhNGaxo6xiXc55YlWIZf2fPjL5+2LADTTmFviVAMsJvYvRCw62QvRc6ruRM2TPZvckccsXliE\n\t8CMamNkb2Ehe9217HXl6BhRvqX6eUOU3oc385d4KNwYifxALFLLtwHUfi5jDrf/Y9FNERi+wbA8R\n\trh+qp2w5aKvunLl/9XZ82h5vjemkkaSY8MbA3LREDzkk0Tg9ELgNFQjfYa8qmWXsxx+y4/MtfW9B\n\tAMJJ6NaiSpuiWTx6dqS67ZN5eLazO7Pv3dm9lNHpxybeWR3m09WQT5tNJOJ6yGfvibwFHNM63ekY\n\tyrVpTRMKy38NLDVRBbfgoNNGu4556dyZyibisH0/7SSFA0yefNs6XSzRA/y8J9BdaG5dt1/XxRWx\n\tlNYNNqZ6gXIAeAQb5uEE+1JqyHHv2NDqmWvR9RcYQvpzAlw8dvP8A21Umiyfwqi7AAAAAElFTkSu\n\tQmCC\nDate: Mon, 04 Apr 2016 20:42:43 +0200\nIn-Reply-To: <jwv8u0tql7y.fsf-monnier+Inbox@gnu.org> (Stefan Monnier's message\n\tof \"Mon, 04 Apr 2016 14:36:59 -0400\")\nMessage-ID: <m37fgdnrks.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 18:42:51 -0000\nContent-Length: 1052\nLines: 27\n\nStefan Monnier <monnier@IRO.UMontreal.CA> writes:\n\n> The important part is that you can build a \"bookmark object\" which should\n> contain enough data to reproduce the current buffer&position.\n\nYes, but would that work they way I sketched out in the second message\nin this thread?  I.e., the mode would supply a function that would\nprovide a list of (say) 20 places to look for a place that's \"the same\"?\n\n> You might be able to do that, but what I was proposing was not to let\n> users use the bookmark functionality, but simply to have a default\n> special-mode-revert-buffer function which would do something along\n> the lines of:\n>\n>     (defun special-mode-revert-function ()\n>       (let ((here (funcall bookmark-make-record-function)))\n>         (erase-buffer)\n>         (bookmark-goback here)))\n\nAha, I see.  Yes, that sounds very nice.  As long as the bookmark\nfunctionality is sufficiently flexible about the `here' it's willing to\naccept..\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_313.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anA7v-0007Nk-68\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 15:25:31 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:45265)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kfogel@gmail.com>) id 1anA7s-0007NV-83\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:25:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <kfogel@gmail.com>) id 1anA7n-0003Sf-7S\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:25:28 -0400\nReceived: from mail-yw0-x232.google.com ([2607:f8b0:4002:c05::232]:33402)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kfogel@gmail.com>) id 1anA7n-0003SR-2p\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:25:23 -0400\nReceived: by mail-yw0-x232.google.com with SMTP id t10so77233403ywa.0\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 12:25:22 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:from:to:cc:subject:references:reply-to:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=fRD+6OKKAWfbg74aUnH0UFE+zuMZ2cSy8oKseKZJ7m8=;\n\tb=YYCxCxlTlCRkxKkEpnA27UtSMx+ljxMsw1vGmM+1iqdWL2e4eXZPcQu0PokZK+3osi\n\tGgiLUx5ogmhQtg00qNHRqYqaDO3RgiLbQcGQjJq+034iMIQBo85u7WYT3XrIPkcF+MDE\n\tZD13danZH6wnJsZGFx8fDS5AlIZ0Dgw52pgGk5ILyrj3fWOzd7hFDui6VZtcSvd53Kke\n\tMlVCV9lkKCphfF7AHOs20x7g0M3mDdNaKEFz/OxJMFnajQbKsQwyGOvlnFAWQ9LFFUgZ\n\tPiUI7Ynt5uriDYL0BR765sb404KFLdwd6K7Fdhkt38V+SYT63iIL7eqkQc3F3Mb6iz5C\n\t5PBg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:from:to:cc:subject:references:reply-to\n\t:date:in-reply-to:message-id:user-agent:mime-version;\n\tbh=fRD+6OKKAWfbg74aUnH0UFE+zuMZ2cSy8oKseKZJ7m8=;\n\tb=PkdEzGmnej0Mo602FPJLR38evDlTMHaR/gHMyHEJFt5Hb8WkIian0Td/ncGgAMNomZ\n\tC63ctGj80Ke93yZ20OdLQ+B3TVZRfoywdDtp4G5gmv4gDRF1J/LH5NUPJDT0B8TJzQty\n\t+cheBL3psMy/lQLAvSUUbx+tyWTFqYLNs/W9OVVM3myvS8A69V+pE+K0U25BuVF793Ub\n\tyPa7vCnc2GDSXHp6yV9ODNJ0h3mKG1VQFLJ7K4RIjnrsAHUEVBd2ZDoBm06A7tVlBnDS\n\tIV6PV/0bzlRkWgD3lUu+Dt4yYEiPLxLW8PBXVm6EpCZB6P9I+f+SbhgnzQN/mvs5IWlT\n\tabxw==\nX-Gm-Message-State: AD7BkJIIjVKzdqGUXnNM+YjJEPy4VXKYnh63yVUY1kN/HMbM4Ug0UVZNQO0fHvzIIqqtCA==\nX-Received: by 10.37.2.200 with SMTP id 191mr17898069ybc.12.1459797922391;\n\tMon, 04 Apr 2016 12:25:22 -0700 (PDT)\nReceived: from floss (74-92-190-114-Illinois.hfc.comcastbusiness.net.\n\t[74.92.190.114]) by smtp.gmail.com with ESMTPSA id\n\tz201sm18252419ywd.21.2016.04.04.12.25.21\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 04 Apr 2016 12:25:21 -0700 (PDT)\nSender: Karl Fogel <kfogel@gmail.com>\nFrom: Karl Fogel <kfogel@red-bean.com>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: \"Staying in the same place\"\nReferences: <m3oa9q53uq.fsf@gnus.org>\n\t<jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3mvp9nsmp.fsf@gnus.org> <jwv8u0tql7y.fsf-monnier+Inbox@gnu.org>\n\t<m37fgdnrks.fsf@gnus.org>\nDate: Mon, 04 Apr 2016 14:25:20 -0500\nIn-Reply-To: <m37fgdnrks.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 04 Apr 2016 20:42:43 +0200\")\nMessage-ID: <87h9fhrxb3.fsf@red-bean.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4002:c05::232\nCc: Stefan Monnier <monnier@IRO.UMontreal.CA>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Karl Fogel <kfogel@red-bean.com>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 19:25:29 -0000\nContent-Length: 2418\nLines: 55\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n>Stefan Monnier <monnier@IRO.UMontreal.CA> writes:\n>\n>> The important part is that you can build a \"bookmark object\" which should\n>> contain enough data to reproduce the current buffer&position.\n>\n>Yes, but would that work they way I sketched out in the second message\n>in this thread?  I.e., the mode would supply a function that would\n>provide a list of (say) 20 places to look for a place that's \"the same\"?\n\nYes, see below...\n\n>> You might be able to do that, but what I was proposing was not to let\n>> users use the bookmark functionality, but simply to have a default\n>> special-mode-revert-buffer function which would do something along\n>> the lines of:\n>>\n>>     (defun special-mode-revert-function ()\n>>       (let ((here (funcall bookmark-make-record-function)))\n>>         (erase-buffer)\n>>         (bookmark-goback here)))\n>\n>Aha, I see.  Yes, that sounds very nice.  As long as the bookmark\n>functionality is sufficiently flexible about the `here' it's willing to\n>accept..\n\nSo, stepping back a moment:\n\nFirst, bookmark has the idea of a fuzzily-recorded place -- a position (in a buffer of text) that is somehow \"the same\" as it used to be, even though some things about the text have changed.  Obviously, if the text changes too much, the concept of \"the same place\" becomes meaningless, and bookmark can't recover the position.\n\nSecond, bookmark already has a mechanism for delegating that notion of a fuzzily-recorded position to other modes (e.g., to Info mode).\n\nAnd Stefan has pointed out that what you're proposing is essentially what bookmark already does.  Maybe that idea of recording a position fuzzily should be abstracted out, and then bookmark would use the new abstraction too, or maybe this new function should just use bookmark's existing code (which, in turn, already hooks into mode-specific code in some cases, and could do more of that).\n\nIn any case, though, should the new thing be defined as a macro?  That seems like the more natural way, at least IMHO.  Something like:\n\n(defmacro fuzzy-save-excursion (&rest body)\n   record-the-position-fuzzily-using-mode-specific-code\n   run-the-body\n   restore-the-fuzzily-recorded-position\n)\n\nOr maybe:\n\n(defmacro fuzzy-save-excursion (save-func restore-func &rest body)\n   record-the-position-fuzzily-with-save-func\n   run-the-body\n   restore-using-restored-func\n)\n\n?\n\nBest regards,\n-Karl\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_315.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anAPM-0000Gh-4O\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 15:43:32 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49718)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAPI-0000AC-7I\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:43:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAPF-0007xo-1j\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:43:28 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:48280)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAPE-0007xZ-S5\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:43:24 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1anAPA-0006bV-0v; Mon, 04 Apr 2016 21:43:22 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Karl Fogel <kfogel@red-bean.com>\nSubject: Re: \"Staying in the same place\"\nReferences: <m3oa9q53uq.fsf@gnus.org>\n\t<jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3mvp9nsmp.fsf@gnus.org> <jwv8u0tql7y.fsf-monnier+Inbox@gnu.org>\n\t<m37fgdnrks.fsf@gnus.org> <87h9fhrxb3.fsf@red-bean.com>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAHlBMVEVQU3VUWXwxGREnDwcT\n\tBwdaoeFNS2dIQFM6JiZAMz/eZavFAAACVUlEQVQ4jU2SsW7bMBRFn4Ca6EgCtdGsUgbNoj8giZnC\n\t3WwgapBNBhoK/YCa4CpkkLYUKCrzb3sfSTsmLBF8h/e+S1rkrO0KqZTih6dVJ6xzjgDs4aN+UCtr\n\tewaWgZRM+EUKFed6R0drRSFlgd14SElsPGYrQR0RS0hKEskpAgKRBRVozMYfAKgTAqAQwnap7qjv\n\t0QVWREVRrDpk7dHazdQ7KwQDSRwpt5hhBQGOqFgjwERy8hkgFcBBWjiBBJcVFh2wu6B4G1dWtkv3\n\tRDlrjOs48GubhuiPHMm7wABkkcHOsdccQqA5gmV7lsRIPpwiOIoL2GEZAiQUZlac63vVOx94kJuZ\n\tXOrU+wxmHObIYL/cIy+qLsTmgb0ODHKy5ygYk+J8DB4vAZnGEVo/u9+syOCGwTTCygfHCtW+mG+Y\n\tf7HRyABdcAy1bM3GoA/2nzJwbomgiyeAx3bvYz0pwrJV6vWLMea+/cE+DCa2escJFp8iuJmuQPD4\n\ti5ZRsfs8stMw0jhxrnfRt88AjzufBAPACMlp9m3705g7mVrXNY3jNPk5bP/hCPLtu4j766EEiIq/\n\td3zqryZcATTBFdzz4XdmGxuUZVLgmf7s8bWLtweUuZ6tEHH2J1zqYNJ+WNUpHiLEydRjGQfAcGY8\n\tbrex3JSE91V9uN1EUFUU51wt8TNltsJTg5RDXceCaapGs5Vu0o4671w/aA3GoNJlVZ6HXhtdoVQR\n\teINeud40ldloDAYwa7SOGSvoIdGV1gzSaEpdoQCvzQWsNcu5Q1wZfgNsDD4PXjRZy4vmP7Hmdb25\n\tBpAlAAAAAElFTkSuQmCC\nDate: Mon, 04 Apr 2016 21:43:19 +0200\nIn-Reply-To: <87h9fhrxb3.fsf@red-bean.com> (Karl Fogel's message of \"Mon, 04\n\tApr 2016 14:25:20 -0500\")\nMessage-ID: <m3twjhkvmw.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: Stefan Monnier <monnier@IRO.UMontreal.CA>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 19:43:29 -0000\nContent-Length: 1388\nLines: 33\n\nKarl Fogel <kfogel@red-bean.com> writes:\n\n> And Stefan has pointed out that what you're proposing is essentially\n> what bookmark already does.  Maybe that idea of recording a position\n> fuzzily should be abstracted out, and then bookmark would use the new\n> abstraction too, or maybe this new function should just use bookmark's\n> existing code (which, in turn, already hooks into mode-specific code\n> in some cases, and could do more of that).\n\nAh, I didn't know that about bookmarks.  Sounds like all the mechanisms\nwe need are in place, so we just need to start using them throughout the\nvarious modes.  But perhaps provide easier-to-understand interfaces\nlike:\n\n> In any case, though, should the new thing be defined as a macro?  That\n> seems like the more natural way, at least IMHO.  Something like:\n>\n> (defmacro fuzzy-save-excursion (&rest body)\n>    record-the-position-fuzzily-using-mode-specific-code\n>    run-the-body\n>    restore-the-fuzzily-recorded-position\n> )\n\nYes, I think having a macro for this would be very handy and encourage\nusage of this stuff.  However, the restore-the-fuzzily-recorded-position\nthing should also be available in a handy fashion for when we're doing\nasynchronous fuzzy point restoration (with diff mode and compilation\nmode, for instance).\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_317.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anAVD-0003xV-3M\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 15:49:35 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:51739)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kfogel@gmail.com>) id 1anAV7-0003xO-SQ\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:49:34 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <kfogel@gmail.com>) id 1anAV3-0000jS-Ay\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:49:29 -0400\nReceived: from mail-yw0-x231.google.com ([2607:f8b0:4002:c05::231]:33985)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kfogel@gmail.com>) id 1anAV3-0000jO-7C\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:49:25 -0400\nReceived: by mail-yw0-x231.google.com with SMTP id d68so128499966ywe.1\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 12:49:25 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:from:to:cc:subject:references:reply-to:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=S34XTeY1ucogyytZeZD4swqm+fxi6OqTamdr+MzX7WQ=;\n\tb=gGyNhHRkk4fD1tHGH62UsEkokEHAsf4ePxSJvXSzWEosSC1c7XfuDVeYhbRgJlKkWg\n\tS1h37oDTl50x5+oRm5VSABel3wMSDNewJh5ZGgYQKixNxcevlaM69+J6H8Zq0r5gfxdX\n\tUk7oOKtN5p9IGNUTBA+3/Hf4SVg1K3OLdkaQ0CueCvTXrrdHSrBhVGtwmkmkZMebpgkx\n\tdinbhXWwatGbFNkHQ72jiUG8AUyZsKE2rv1F83vX+RQJOptdapeZcKBXukvdYu876uns\n\t0fwp1aVOx5iNX/I9G+0Z1qcz/LY1XlTeHiwkeit8qQzTpTCf27hFhRF4zR/RgDfu0T5d\n\trucA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:from:to:cc:subject:references:reply-to\n\t:date:in-reply-to:message-id:user-agent:mime-version;\n\tbh=S34XTeY1ucogyytZeZD4swqm+fxi6OqTamdr+MzX7WQ=;\n\tb=d39qd56J5O+8SgJxVSn/lPPJeajtl4SqKuVYS+11BAjYzXFbqhfFNedl7hzbhd/mr/\n\tbW4v+X/YazjhcvTWbDkKEAflj6k4Dh4ocmJAGQmZa/a+Bad2KLjFiMb7qDwnfqFd55Im\n\tehxcazM1/9dSUiXZECLJ92tlBfYWZjDzqMD7hM+wFz1q/6I/e9ZcltGwt9vOhSidCXfN\n\tS6SM8Nx/6IYKVJI26Vwgq8wb0iOQwqCCoR+rXUj3OLYZl4wu4ZtblN1PYQsSIKUbVz+4\n\txoMX7MmbV2HeDIR1ardQOLAbweK/2SA7jR1xlybbR5O1CMf3MpGIR5EYlmMpIGHKFiwl\n\tzVdg==\nX-Gm-Message-State: AD7BkJJJoIHdlt07x9nT2YFSjuLuiXNi0RtwwZFxMlt8Ndwx5MO4SB/XrLOBJfsM+9bouQ==\nX-Received: by 10.37.81.70 with SMTP id f67mr2601459ybb.32.1459799364708;\n\tMon, 04 Apr 2016 12:49:24 -0700 (PDT)\nReceived: from floss (74-92-190-114-Illinois.hfc.comcastbusiness.net.\n\t[74.92.190.114]) by smtp.gmail.com with ESMTPSA id\n\tt189sm18334914ywd.43.2016.04.04.12.49.22\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 04 Apr 2016 12:49:22 -0700 (PDT)\nSender: Karl Fogel <kfogel@gmail.com>\nFrom: Karl Fogel <kfogel@red-bean.com>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: \"Staying in the same place\"\nReferences: <m3oa9q53uq.fsf@gnus.org>\n\t<jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3mvp9nsmp.fsf@gnus.org> <jwv8u0tql7y.fsf-monnier+Inbox@gnu.org>\n\t<m37fgdnrks.fsf@gnus.org> <87h9fhrxb3.fsf@red-bean.com>\n\t<m3twjhkvmw.fsf@gnus.org>\nDate: Mon, 04 Apr 2016 14:49:21 -0500\nIn-Reply-To: <m3twjhkvmw.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 04 Apr 2016 21:43:19 +0200\")\nMessage-ID: <87d1q5rw72.fsf@red-bean.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4002:c05::231\nCc: Stefan Monnier <monnier@IRO.UMontreal.CA>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Karl Fogel <kfogel@red-bean.com>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 19:49:34 -0000\nContent-Length: 833\nLines: 16\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n>> (defmacro fuzzy-save-excursion (&rest body)\n>>    record-the-position-fuzzily-using-mode-specific-code\n>>    run-the-body\n>>    restore-the-fuzzily-recorded-position\n>> )\n>\n>Yes, I think having a macro for this would be very handy and encourage\n>usage of this stuff.  However, the restore-the-fuzzily-recorded-position\n>thing should also be available in a handy fashion for when we're doing\n>asynchronous fuzzy point restoration (with diff mode and compilation\n>mode, for instance).\n\nYes, absolutely.  A \"fuzzily recorded position\" should be a first-class object that can be passed around like any value.  There can be a macro that generates and consumes such objects entirely internally to the macro, but the objects can also be generated explicitly in code and consumed later.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_319.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anAYC-0005aY-J4\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 15:52:40 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52906)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAYA-0005aD-7K\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:52:39 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAY7-0001oq-2K\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:52:38 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:48358)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAY6-0001om-SI\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:52:34 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1anAY4-0006ld-Dz; Mon, 04 Apr 2016 21:52:34 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Karl Fogel <kfogel@red-bean.com>\nSubject: Re: \"Staying in the same place\"\nReferences: <m3oa9q53uq.fsf@gnus.org>\n\t<jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3mvp9nsmp.fsf@gnus.org> <jwv8u0tql7y.fsf-monnier+Inbox@gnu.org>\n\t<m37fgdnrks.fsf@gnus.org> <87h9fhrxb3.fsf@red-bean.com>\n\t<m3twjhkvmw.fsf@gnus.org> <87d1q5rw72.fsf@red-bean.com>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEV8LTEVEDWpwc95Z3KV\n\tn5f4+PfWwgebAAACUklEQVQ4jXWUy3bjIBBE8WP2xvLsIx77oMZ7Q6N9gtX//yspEPZkMxwfCeuK\n\t7qoSklJn46y+qKNpR3XQfWDWQNEap99AtZFNCQ0YUYr1AGeAaEoa4GQGUce91qwvA9jRIvdarfup\n\tgbOZBzB7E4c/A1waOJ87OBoDMdkLZqkD3KsuvYmetL43EA57iw5UnvUafgMoUc1Lfl4l6b9NlX6v\n\taHajeJkBWKXLufs+7kCJYMW1ekmX+9wDAcDp5GVT+vqt70mt26HdelS6JysPpW8AQfntcXsgyw7g\n\t7Uvp6VuvVsnXOQ+gkYZ/dqDWKqUYNwDE+Yo5wB+Rko353MFlMYjjsLYVAGYH824U3WUAZNq01qYX\n\t4EPJk4oX2Yr5aD0kXVvwzAtKmMKLKPOpTgBP/wFAFKVEnFhURBS6pbGpaiMVl7kwsWu+ZW5A7gJA\n\tZbWEUQC81A5ELC8U70/OuAFPDld2UBM5omyZbxM/mugBNq0p6Gxx0JNrCwYoU1jIllwi8S3e32Cb\n\tJrql4niiyJb8GzwnooXC+gwUKep/K2YKtCyLrzFiDS1vQMwFDmQLcE78BhsuxsTsawM6tih3MFEf\n\tfrMxckm3lw8bqMBHkMoUY0n0AqlVpinBT9AIgGmAJaF24CgC46SvYfId1P3tmifIg27qG78DVO5P\n\tT8bAbhqqMKu1+j1oqeYl1xW+vYCZqWJr7IBzmjaYZ+tiDHgZ3V5qK1DpLI62hMi6uLI3t+3TgOae\n\tcnaYF46xA+xJB120JMJeNYZbng14j58Rs25NDjZoKYhRyX/GD76yzr8rivSgAAAAAElFTkSuQmCC\nDate: Mon, 04 Apr 2016 21:52:32 +0200\nIn-Reply-To: <87d1q5rw72.fsf@red-bean.com> (Karl Fogel's message of \"Mon, 04\n\tApr 2016 14:49:21 -0500\")\nMessage-ID: <m3egalkv7j.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: Stefan Monnier <monnier@IRO.UMontreal.CA>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 19:52:39 -0000\nContent-Length: 483\nLines: 14\n\nKarl Fogel <kfogel@red-bean.com> writes:\n\n> Yes, absolutely.  A \"fuzzily recorded position\" should be a\n> first-class object that can be passed around like any value.  There\n> can be a macro that generates and consumes such objects entirely\n> internally to the macro, but the objects can also be generated\n> explicitly in code and consumed later.\n\nThis sounds great to me.  :-)\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_321.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anDpv-00006f-Bq\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 19:23:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36503)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1anDpq-00005z-Ch\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 19:23:10 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1anDpm-00064I-En\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 19:23:06 -0400\nReceived: from mail-ob0-x22e.google.com ([2607:f8b0:4003:c01::22e]:35030)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1anDpm-00063u-9o\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 19:23:02 -0400\nReceived: by mail-ob0-x22e.google.com with SMTP id fp4so107915225obb.2\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 16:23:02 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=6e2BE7fSaW05wQB8z9OuJAboYC/lYgBE20nCSqdo4YI=;\n\tb=z6z4TRLMbN6aHfmpmTgD4mT1N8K+UFqicXZ8PeczLpqFF9mOI+1rimcvI5/aETkXRd\n\tJ/fFdh85i49dPvLo7jf/aInDdByK+iYT5IkIa6zHu/UqyB1bz7vcazC3Sr0iJvi14cYp\n\tkpzbmMpKzn+X9KAROZR5rdiX7SoYVl/gBgw1Va3ZuP+DY9Zb90ZgbUST324iHmOhCwmk\n\tfnEBNygOe8sEe1nbrSphflNDIWeLvFikPDcFxp8rcbbYLE/I54DO2tHODpXsSP9DNYP8\n\tRxRoQZwAv3+KTXDy8G0G9aiWXYyYF1KrpjcpxN90wrK8T3Sbu24c5YyFYS/F1vgLZc4y\n\tUhiQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=6e2BE7fSaW05wQB8z9OuJAboYC/lYgBE20nCSqdo4YI=;\n\tb=UwHyeO8dXCtpYONjPgOZ/Y9cTekXjngedGOCJfD7mtS5yiecbIWQ2wlrxRUFddiDek\n\taUdjKL3avci/72xDfihWzAnUlk9eypHbTttjQbLusXUVwEyt6/4ANRm/RnPRq998OWwz\n\t/2qaNRBSN1PENWRBWmK4ErVOU5jByrFlQcftLITW3XWmg+fL+yrB6T+MfIArA65vktgA\n\tXoSOX6KrrqUbNumRiAcWXeVK3T/sgrTmlNL3BkOXPzAJVxshyc5+FkrT+Kja7vv0ZfPm\n\tpw1XLYbpmZsq/pwvGqvrAtIL0wui//0Q9ELj8qA1Uw8T0/01pVGJJMq4uwpa9z0bZt/v\n\tLRww==\nX-Gm-Message-State: AD7BkJKiBfZUZPm5WgGAGYtNPjVqs2GY9uaiWaZ0Jr3fjiAyUdLND1VFlhMTRRZGfrYx2A==\nX-Received: by 10.182.126.198 with SMTP id na6mr7136733obb.3.1459812181886;\n\tMon, 04 Apr 2016 16:23:01 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79])\n\tby smtp.gmail.com with ESMTPSA id q1sm9113189obi.20.2016.04.04.16.22.59\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tMon, 04 Apr 2016 16:22:59 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid 5254813DE0620; Mon,  4 Apr 2016 16:22:58 -0700 (PDT)\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: \"Staying in the same place\"\nIn-Reply-To: <m3egalkv7j.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 04 Apr 2016 21:52:32 +0200\")\nDate: Mon, 04 Apr 2016 16:21:38 -0700\nMessage-ID: <m2twjh6jul.fsf@newartisans.com>\nReferences: <m3oa9q53uq.fsf@gnus.org>\n\t<jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3mvp9nsmp.fsf@gnus.org> <jwv8u0tql7y.fsf-monnier+Inbox@gnu.org>\n\t<m37fgdnrks.fsf@gnus.org> <87h9fhrxb3.fsf@red-bean.com>\n\t<m3twjhkvmw.fsf@gnus.org> <87d1q5rw72.fsf@red-bean.com>\n\t<m3egalkv7j.fsf@gnus.org>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Lars Magne Ingebrigtsen <larsi@gnus.org>, Karl Fogel\n\t<kfogel@red-bean.com>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::22e\nCc: Karl Fogel <kfogel@red-bean.com>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel@gnu.org\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 23:23:10 -0000\nContent-Length: 1271\nLines: 32\n\n>>>>> Lars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> Karl Fogel <kfogel@red-bean.com> writes:\n>> Yes, absolutely.  A \"fuzzily recorded position\" should be a\n>> first-class object that can be passed around like any value.  There\n>> can be a macro that generates and consumes such objects entirely\n>> internally to the macro, but the objects can also be generated\n>> explicitly in code and consumed later.\n\n> This sounds great to me.  :-)\n\nI like this as well.  Basically, a \"fuzzy position\" is three things:\n\n    Some internal value, potentially unique to each mode\n    A method to return such a value based on point\n    A method to restore point based on such a value\n\nSeeing this as an abstract data type, the default would be:\n\n    #<marker>, point-marker, goto-char\n\nBut each mode would have the opportunity to override with another\nimplementation. Also, these objects might be persistable in some cases, such\nas with bookmarks (though maybe not always).\n\nWe'd also need to either extend save-excursion to be aware of this, or define\na new helper form to do the saving/restoring based on this fuzzy definition.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_323.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anEz8-00034J-SS\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 20:36:46 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:51274)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1anEz7-000339-9C\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 20:36:46 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1anEz2-0005OT-96\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 20:36:45 -0400\nReceived: from userp1040.oracle.com ([156.151.31.81]:44309)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1anEz2-0005OO-17\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 20:36:40 -0400\nReceived: from userv0021.oracle.com (userv0021.oracle.com [156.151.31.71])\n\tby userp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2) with\n\tESMTP id u350aWoP013168\n\t(version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);\n\tTue, 5 Apr 2016 00:36:33 GMT\nReceived: from userv0121.oracle.com (userv0121.oracle.com [156.151.31.72])\n\tby userv0021.oracle.com (8.13.8/8.13.8) with ESMTP id u350aVm6024632\n\t(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);\n\tTue, 5 Apr 2016 00:36:31 GMT\nReceived: from abhmp0001.oracle.com (abhmp0001.oracle.com [141.146.116.7])\n\tby userv0121.oracle.com (8.13.8/8.13.8) with ESMTP id u350aUbX014070;\n\tTue, 5 Apr 2016 00:36:30 GMT\nMIME-Version: 1.0\nMessage-ID: <29972ee1-15f4-40fc-86dd-fb2217aa158a@default>\nDate: Mon, 4 Apr 2016 17:36:29 -0700 (PDT)\nFrom: Drew Adams <drew.adams@oracle.com>\nSender: Drew Adams <drew.adams@oracle.com>\nTo: John Wiegley <jwiegley@gmail.com>, Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: RE: \"Staying in the same place\"\nReferences: <m3oa9q53uq.fsf@gnus.org>\n\t<jwv7fgd1r7w.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3mvp9nsmp.fsf@gnus.org>\n\t<jwv8u0tql7y.fsf-monnier+Inbox@gnu.org> <m37fgdnrks.fsf@gnus.org>\n\t<87h9fhrxb3.fsf@red-bean.com> <m3twjhkvmw.fsf@gnus.org>\n\t<87d1q5rw72.fsf@red-bean.com> <m3egalkv7j.fsf@gnus.org>\n\t<m2twjh6jul.fsf@newartisans.com>\nIn-Reply-To: <m2twjh6jul.fsf@newartisans.com>\nX-Priority: 3\nX-Mailer: Oracle Beehive Extensions for Outlook 2.0.1.9  (901082) [OL\n\t12.0.6744.5000 (x86)]\nContent-Type: text/plain; charset=us-ascii\nContent-Transfer-Encoding: quoted-printable\nX-Source-IP: userv0021.oracle.com [156.151.31.71]\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic]\nX-Received-From: 156.151.31.81\nCc: Karl Fogel <kfogel@red-bean.com>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel@gnu.org\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 05 Apr 2016 00:36:46 -0000\nContent-Length: 1169\nLines: 37\n\n> Basically, a \"fuzzy position\" is three things:\n>=20\n>     Some internal value, potentially unique to each mode\n>     A method to return such a value based on point\n>     A method to restore point based on such a value\n>=20\n> Seeing this as an abstract data type, the default would be:\n>=20\n>     #<marker>, point-marker, goto-char\n\nActually, the default for a bookmark is:\n\n1. A general context, such as a file or buffer (e.g. an absolute\n   file name or a buffer name).\n\n2. A position within that context (e.g. a buffer position).\n\n3. Some context text immediately after the position.\n\n4. Some context text immediately before the position.\n\n5. A handler function that accepts the bookmark as argument\n   and returns point to the place that was bookmarked.\n\nThe default handler goes first to the recorded position.\nIt then searches forward for the after-position context text.\nIt then searches backward for the before-position context text.\n\nThe point is to try to accommodate changes to the file or buffer\ntext, i.e., to return to the most appropriate place that can be\nfound.\n\nSpecial kinds of bookmarks (Info, Dired, `man', etc.) use special\nhandler functions.\n\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_325.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amxhr-000693-67\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 02:09:47 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37968)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1amxhl-0005zJ-U6\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 02:09:45 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1amxhi-00013E-3x\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 02:09:41 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:51666)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1amxhh-000139-Sx\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 02:09:38 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>) id 1amxhc-00060m-4R\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 08:09:36 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: dev@caliopen.local\nSubject: Re: \"Staying in the same place\"\nReferences: <m3oa9q53uq.fsf@gnus.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAD1BMVEX+AA+Qb6f/AAAL8frw\n\tEDRIFBcCAAACXklEQVQ4jV1Ti5XcMAiUdSrArFSATFTAEVyAPvRfUwb5bnOJ39u3tkYMMAwhvJ80\n\t/r6pBn4/bb1fDYCZxeer5tcbyBuYXxEqWT+f13+AD8UjCcf/AdkBvdmG9bGB/cxrn+tvAMUOB3qy\n\tbmku8FCsJZqV3nKwTWYp+vVRWAu+UeGm6t0aelIzFdVCxLyCJWtO5Uw3frhAFPn6St4OBJBmFRT3\n\tuiY3RAyiXDzAhIjw9VqQAFpleQolJIUaXM98fgEZhyMTfXV65nsDqgsUu0oouZUqD0BjqzQ8ogPo\n\thR6gn7skKhIbsNoN9TpQthxDUi7O1uqSCaBqYQh4Z2mkPqrAGSMLpYlZUhIAlkHOkQc0C3lAtkk3\n\tmm5m9EJdNZMDt5+kBaDaXLLLFQMgKjGZV8xjNjcEMpYYILSc4MBgBy4u9ParjBsAQW7DnHHDiBta\n\tyCLCiMC/2VUATfQOLkhBTpU1l14BCh8nr093BU0AS5fECk+c3ISveyFCygaK9lmTRdDPy4eAJ7h8\n\td7azgq/j/vXYQQG8GL4elqUMTI18Aig+oA1ubqyltmACGAJUAMhLSMcJY9G4IYfbAYfBrUx0QCgt\n\tQztJBIjDsKvWnrvp3b0QiFh81RoJej7HC9knLmG2ww0V2KhXvWtB9jNjmp7Gc0S2DgfNA8rfS6D6\n\t4SrtrU1V9bNhi+QDKtZnsxyoCcXHBOqjeMA22t7zfl6oBsDoexW/qTjxL2S54Dm0jsTlO6JGrLJw\n\tyvDbOPSdAx5bLp3BiK1cZ96dx587PhnLxMslDvMHQM9+XLvB1xsY29JuOF+xP6y7tHae4BG4AAAA\n\tAElFTkSuQmCC\nDate: Mon, 04 Apr 2016 08:09:31 +0200\nIn-Reply-To: <m3oa9q53uq.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 04 Apr 2016 07:40:13 +0200\")\nMessage-ID: <m3inzx6h2c.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 06:09:45 -0000\nContent-Length: 2446\nLines: 60\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> I wonder if anybody has thought about the issue in a more general way --\n> by providing a mechanism to save (perhaps a lot) of context, and then\n> trying to go back to the same context again after regenerating the\n> buffer, in a ... fuzzy way.\n\nThinking about this a bit more, I think it's difficult to see a way to\ndo this generally without support from each mode.  However:\n\nIn general, if the buffer reflects things that don't really change a\nlot, just recording the current line (and the next and previous ten\nlines, say), would DWIM.\n\nFor buffers where there is a strong \"identity\" (say, file names in dired\nand bug numbers in debbugs mode), the mode could provide a way to give\nthose identities to `revert-buffer' (and a way to restore point).\n\nTake compilation modes, for instance.  Let's say there's a variable\n`revert-buffer-context-function'.  The mode would return a list of error\npoints...\n\nmake -k \n  CC       vm-limit.o\n  CC       dispnew.o\n  CC       frame.o\nframe.c: In function 'x_set_frame_parameters':\nframe.c:3296:25: warning: 'height' may be used uninitialized in this function [-Wmaybe-uninitialized]\nframe.c:3296:25: warning: 'width' may be used uninitialized in this function [-Wmaybe-uninitialized]\n  CC       scroll.o\n  CC       xdisp.o\nxdisp.c: In function 'move_it_in_display_line_to':\nxdisp.c:8556:13: warning: 'closest_pos' may be used uninitialized in this function [-Wmaybe-uninitialized]\nxdisp.c: In function 'display_line':\nxdisp.c:20348:8: warning: 'max_bpos' may be used uninitialized in this function [-Wmaybe-uninitialized]\n ...\n\nThe list would perhaps be '(\"^frame.c:3296:\" \"^xdisp.c:8556:\" ...).\n`revert-buffer' would then basically do\n\n(let ((context (funcall revert-buffer-context-function)))\n  (funcall revert-buffer-function)\n  (revert-buffer-restore-point context))\n\nwhich would use those regexps to put point somewhere DWIM-ey.  And there\ncould be a `revert-buffer-restore-point-function' variable, too, if the\nmode needs a more specialised way to use the context.\n\nOf course, the this specific example, this wouldn't work, since\ncompilation is asynchronous, but the compilation mode could just use the\ncontext itself from the filter.  Or the sentinel.  Or something.\n\nHm...\n\nBut I think this approach might work for many modes...  perhaps...\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_327.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amyw6-0006Fv-V2\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 03:28:34 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49520)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <danieldmcclanahan@gmail.com>) id 1amyw0-0006Dy-QC\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 03:28:33 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <danieldmcclanahan@gmail.com>) id 1amyvw-0007PW-1p\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 03:28:28 -0400\nReceived: from mail-yw0-x22a.google.com ([2607:f8b0:4002:c05::22a]:33847)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <danieldmcclanahan@gmail.com>) id 1amyvv-0007PM-Sk\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 03:28:23 -0400\nReceived: by mail-yw0-x22a.google.com with SMTP id d68so104089538ywe.1\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 00:28:23 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:from:date:message-id:subject:to\n\t:cc; bh=VGWEu6jl3w9OBeTVgYSu+okdjbBHVHRlhHCry9JqFpg=;\n\tb=REHWbvdmNFQWzwSoWS2Er/UzyDh/eGiOS6buwHCy42Fke7ifLrPkHQlgG3AsPoUOkR\n\tYThJmJnXePH5/VgeR+PVIq92sunm6r9y3rHZjElLZrKYjd6zlroIL56lmK9Bgr9hKTeN\n\t9ZH78cyMSg78gN9q4UNVv7FYs0CUeS55Lml8kMx9ZqV99l+3uVaASvCBU38Fi0v+qLKt\n\tPsnHMurDOh2LWmRLqoIRMUo993vndwqwGpjYOrFZ6A9UIbJXM2Q4hKxwCqh+/ZnpmAyc\n\tBdxLzX/nY7547PMosHUmQ/usHEl/8J7GHl04BI7K/ayMAOqceeDm9ik+6ryV8U70J/ir\n\t15yg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:from:date\n\t:message-id:subject:to:cc;\n\tbh=VGWEu6jl3w9OBeTVgYSu+okdjbBHVHRlhHCry9JqFpg=;\n\tb=glvXykrdxQZZrtr6YudminheyqhvKAkZLLbq+fKNDf/NQmX6CVmD8WLdd696mIdsYp\n\tWi7MsR85H7Cb269oSRH/inJDEFUkl/x6LpFoGC0mi64fCLXxQUgTRDtHeu7iPoku1tvO\n\tZyDZRtbtm84kwfNSZ4iU2QkKkfbEnzOSZsj9jKQEm5s/Go7dvusIIEMiN0grfBLB1HOb\n\tT4C1DgPTve5dqwEIfwyXIbNpj/L328TJ7qE9MVzQoVftfCiR1rdJa96C1BO06CL/+yYv\n\tQid6nyrt+3gTvG5Rg5kH2/pLzUqgKWobt+EN0YTv/jKtCmowbjUsK5cPkrW6mir5hwvJ\n\tZ4MA==\nX-Gm-Message-State: AD7BkJJBoQl+7OB7+jyIKdzwQhUTpUETevN1BPT0qiJHtTT6cbYz5yEwmnVBQMFQMyailMkdpBEDxHGQgujIBg==\nX-Received: by 10.13.220.197 with SMTP id f188mr4007302ywe.172.1459754903281; \n\tMon, 04 Apr 2016 00:28:23 -0700 (PDT)\nMIME-Version: 1.0\nReceived: by 10.37.84.65 with HTTP; Mon, 4 Apr 2016 00:28:04 -0700 (PDT)\nIn-Reply-To: <m3inzx6h2c.fsf@gnus.org>\nReferences: <m3oa9q53uq.fsf@gnus.org> <m3inzx6h2c.fsf@gnus.org>\nFrom: Daniel McClanahan <danieldmcclanahan@gmail.com>\nDate: Mon, 4 Apr 2016 02:28:04 -0500\nMessage-ID: <CALuGEhL+HN2w8ZzV1erwDc6PJStm+d4CyrrA_Nhe66jx-fthDg@mail.gmail.com>\nSubject: Re: \"Staying in the same place\"\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nContent-Type: text/plain; charset=UTF-8\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4002:c05::22a\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 07:28:33 -0000\nContent-Length: 3507\nLines: 77\n\nI know for markdown-mode on MELPA, I added a \"live-preview\" feature which\ndisplays the rendered markdown file alongside in an eww (or other) buffer as the\nuser edits, and I wanted to make any windows displaying that buffer have the\nidea of \"not moving.\" I played around a bit with some ideas and thought of\nrecording the scroll and window-point for all windows displaying the buffer, and\nthe visual lines between the scroll and point, and then restored the scroll,\nwindow-point, and visual line difference for each window when the buffer was\nregenerated. I also had a special case for when the window-point was equal to\npoint-min or point-max, in which case I just scrolled the buffer all the way\ndown or up.\n\nThis isn't likely to apply to a lot of other modes, but it's just a thought\nabout what other information might be useful if you wanted to implement\nsomething like this.\n\nOn Mon, Apr 4, 2016 at 1:09 AM, Lars Magne Ingebrigtsen <larsi@gnus.org> wrote:\n> Lars Magne Ingebrigtsen <larsi@gnus.org> writes:\n>\n>> I wonder if anybody has thought about the issue in a more general way --\n>> by providing a mechanism to save (perhaps a lot) of context, and then\n>> trying to go back to the same context again after regenerating the\n>> buffer, in a ... fuzzy way.\n>\n> Thinking about this a bit more, I think it's difficult to see a way to\n> do this generally without support from each mode.  However:\n>\n> In general, if the buffer reflects things that don't really change a\n> lot, just recording the current line (and the next and previous ten\n> lines, say), would DWIM.\n>\n> For buffers where there is a strong \"identity\" (say, file names in dired\n> and bug numbers in debbugs mode), the mode could provide a way to give\n> those identities to `revert-buffer' (and a way to restore point).\n>\n> Take compilation modes, for instance.  Let's say there's a variable\n> `revert-buffer-context-function'.  The mode would return a list of error\n> points...\n>\n> make -k\n>   CC       vm-limit.o\n>   CC       dispnew.o\n>   CC       frame.o\n> frame.c: In function 'x_set_frame_parameters':\n> frame.c:3296:25: warning: 'height' may be used uninitialized in this function [-Wmaybe-uninitialized]\n> frame.c:3296:25: warning: 'width' may be used uninitialized in this function [-Wmaybe-uninitialized]\n>   CC       scroll.o\n>   CC       xdisp.o\n> xdisp.c: In function 'move_it_in_display_line_to':\n> xdisp.c:8556:13: warning: 'closest_pos' may be used uninitialized in this function [-Wmaybe-uninitialized]\n> xdisp.c: In function 'display_line':\n> xdisp.c:20348:8: warning: 'max_bpos' may be used uninitialized in this function [-Wmaybe-uninitialized]\n>  ...\n>\n> The list would perhaps be '(\"^frame.c:3296:\" \"^xdisp.c:8556:\" ...).\n> `revert-buffer' would then basically do\n>\n> (let ((context (funcall revert-buffer-context-function)))\n>   (funcall revert-buffer-function)\n>   (revert-buffer-restore-point context))\n>\n> which would use those regexps to put point somewhere DWIM-ey.  And there\n> could be a `revert-buffer-restore-point-function' variable, too, if the\n> mode needs a more specialised way to use the context.\n>\n> Of course, the this specific example, this wouldn't work, since\n> compilation is asynchronous, but the compilation mode could just use the\n> context itself from the filter.  Or the sentinel.  Or something.\n>\n> Hm...\n>\n> But I think this approach might work for many modes...  perhaps...\n>\n> --\n> (domestic pets only, the antidote for overdose, milk.)\n>    bloggy blog: http://lars.ingebrigtsen.no\n>\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_329.argentina:2,",
    "content": "Return-Path: <mcdugalt@herlov.dk>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 6CAB636FB337F\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 20:14:43 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 5644433B507E; Mon,  4 Apr 2016 20:14:43 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 498E533B7A72; Mon,  4 Apr 2016 20:14:43 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.5 required=5.0 tests=BAYES_99,DATE_IN_FUTURE_06_12,\n\tHTML_MESSAGE,MIME_HTML_ONLY,RAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,\n\tRAZOR2_CHECK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 0.9998]\n\t*  1.9 DATE_IN_FUTURE_06_12 Date: is 6 to 12 hours after Received: date\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\nX-Spam-DCC: : \nReceived: from touchvietnam.vn (touchvietnam.vn [103.27.236.206])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id F04ED33B507E\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 20:14:39 +0200 (CEST)\nReceived: (qmail 24522 invoked by uid 56832); 05 Apr 2016 01:59:19 -0000\nDate: 05 Apr 2016 01:59:19 -0000\nMessage-ID: <20160405015919.24522.qmail@correspondenceeg.touchvietnam.vn>\nFrom: \"Maryln Grania\" <mcdugalt@herlov.dk>\nTo: \"laurent\" <dev@caliopen.local>\nSubject: Have a meeting with a stylish Riley as you are parted now text me 1-239-719-34-26\nMime-Version: 1.0\nContent-Type: text/html\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Have a meeting with a stylish Riley as you are parted now text me 1-239-719-34-26\nContent-Length: 399\nLines: 4\n\n<html><body>My ex left me for another girl.<br>\nSo I'm looking for a boyfriend.<br>\nI want a new one with whom I can have <a href=\"http://jlittlelaw.com/wp-content/plugins/si-contact-form/\">a lot of casual hookups</a>.<br>\nIf you're free now <a href=\"http://jlittlelaw.com/wp-content/plugins/si-contact-form/\">you should contact me there</a> or just leave me a message 1-239-719-34-26</body></html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_331.argentina:2,",
    "content": "Return-Path: <antonettavictoriat@geneduval.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id C796636FB2056\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 20:04:26 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid B8B58327F728; Mon,  4 Apr 2016 20:04:26 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid A91F1E336A4; Mon,  4 Apr 2016 20:04:26 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *****\nX-Spam-Status: Yes, score=5.6 required=5.0 tests=BAYES_99,HS_INDEX_PARAM,\n\tHTML_MESSAGE,MIME_HTML_ONLY,RDNS_NONE autolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.6 HS_INDEX_PARAM URI: Link contains a common tracker pattern.\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\nX-Spam-DCC: : \nReceived: from uk1vsl2392.eechost.net (unknown [217.69.40.113])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id 3966D327F728\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 20:04:22 +0200 (CEST)\nReceived: (qmail 40689 invoked by uid 10898); 04 Apr 2016 18:12:10 -0000\nDate: 04 Apr 2016 18:12:10 -0000\nMessage-ID: <20160404181210.40689.qmail@gateel.uk1vsl2392.eechost.net>\nFrom: \"Nicky Rosetter\" <antonettavictoriat@geneduval.com>\nTo: \"laurent\" <dev@caliopen.local>\nSubject: Looking for steamy personals 717-366-2580 just for the vids? How about mine?\nMime-Version: 1.0\nContent-Type: text/html\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Looking for steamy personals 717-366-2580 just for the vids? How about mine?\nContent-Length: 741\nLines: 1\n\n<html><body>I know the sensation. Going through adult personals and profiles of interested singles in your area<br>just to see the photos or videos they have made available.<br><a href=\"http://russiantimes.net/index.php?qvvko2r2kvphlhuorjsj4ig3tq\">How about my fervent pics then?</a><br>I am sure they will inspire you to do a lot more than just browsing through the photos.<br>Who knows your dream adventure could be just a few moments away!<br>Local joy is simpler to find than you think. Let's talk about it,<br>don't wait, <a href=\"http://russiantimes.net/index.php?qvvko2r2kvphlhuorjsj4ig3tq\">message me here this is your opportunity or text me 717-366-2580http://russiantimes.net/index.php?qvvko2r2kvphlhuorjsj4ig3tq</a></body></html>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_333.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amwsA-00063J-Ii\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 01:16:22 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:58061)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1amws8-00062q-8B\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:16:21 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1amws5-0007I3-1q\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:16:20 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:51281)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1amws4-0007Hh-RS\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:16:16 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1amws0-0005Pc-KM; Mon, 04 Apr 2016 07:16:14 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: dev@caliopen.local\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAD1BMVEX+AA+Qb6f/AAAL8frw\n\tEDRIFBcCAAACXklEQVQ4jV1Ti5XcMAiUdSrArFSATFTAEVyAPvRfUwb5bnOJ39u3tkYMMAwhvJ80\n\t/r6pBn4/bb1fDYCZxeer5tcbyBuYXxEqWT+f13+AD8UjCcf/AdkBvdmG9bGB/cxrn+tvAMUOB3qy\n\tbmku8FCsJZqV3nKwTWYp+vVRWAu+UeGm6t0aelIzFdVCxLyCJWtO5Uw3frhAFPn6St4OBJBmFRT3\n\tuiY3RAyiXDzAhIjw9VqQAFpleQolJIUaXM98fgEZhyMTfXV65nsDqgsUu0oouZUqD0BjqzQ8ogPo\n\thR6gn7skKhIbsNoN9TpQthxDUi7O1uqSCaBqYQh4Z2mkPqrAGSMLpYlZUhIAlkHOkQc0C3lAtkk3\n\tmm5m9EJdNZMDt5+kBaDaXLLLFQMgKjGZV8xjNjcEMpYYILSc4MBgBy4u9ParjBsAQW7DnHHDiBta\n\tyCLCiMC/2VUATfQOLkhBTpU1l14BCh8nr093BU0AS5fECk+c3ISveyFCygaK9lmTRdDPy4eAJ7h8\n\td7azgq/j/vXYQQG8GL4elqUMTI18Aig+oA1ubqyltmACGAJUAMhLSMcJY9G4IYfbAYfBrUx0QCgt\n\tQztJBIjDsKvWnrvp3b0QiFh81RoJej7HC9knLmG2ww0V2KhXvWtB9jNjmp7Gc0S2DgfNA8rfS6D6\n\t4SrtrU1V9bNhi+QDKtZnsxyoCcXHBOqjeMA22t7zfl6oBsDoexW/qTjxL2S54Dm0jsTlO6JGrLJw\n\tyvDbOPSdAx5bLp3BiK1cZ96dx587PhnLxMslDvMHQM9+XLvB1xsY29JuOF+xP6y7tHae4BG4AAAA\n\tAElFTkSuQmCC\nDate: Mon, 04 Apr 2016 07:16:12 +0200\nIn-Reply-To: <E1amqnL-0001p7-2f@vcs.savannah.gnu.org> (John Wiegley's message\n\tof \"Sun, 03 Apr 2016 22:46:59 +0000\")\nMessage-ID: <m3wpoe54yr.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: John Wiegley <johnw@newartisans.com>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 05:16:21 -0000\nContent-Length: 465\nLines: 16\n\nJohn Wiegley <johnw@gnu.org> writes:\n\n>     Revert \"Add `r'/`l' grep command history commands\"\n>\n>     This reverts commit a32eea60ac90d367435860fe3a10bf843e6f497c.\n\nWhen reverting a commit, it's helpful if the commit message says why the\ncommit was reverted.\n\nAn contrary to popular belief, reverting code is not a great way to\nmotivate people to contribute.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_335.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amwuv-0007tU-Rq\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 01:19:13 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59289)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amwuq-0007tC-SO\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:19:12 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amwum-0008O4-Nv\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:19:08 -0400\nReceived: from mail-oi0-x22b.google.com ([2607:f8b0:4003:c06::22b]:35307)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amwum-0008O0-If\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:19:04 -0400\nReceived: by mail-oi0-x22b.google.com with SMTP id p188so152720256oih.2\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 22:19:04 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=y1UPgtxwWFCj0Q0451PkfzDVLQuIRQsicR7mHL/jXYk=;\n\tb=JAYn8u/MdogzYAXV8yumACWwpdgRWKWayFbH/OztgRNol3Y4wJ5Ivt5flY9EHYpykB\n\tmfeoksnbMoMUmn2xtOd623mCN5OSLmqnvf4DwThuVS1pFADdY8S/46SFRNvDaibCeD2S\n\t6s1a3iEfE+5Whw9yE7JxU6eBi5uWxg2wZmcBGGtnHnWrUVqfhu2eqpEPRpBQisUCQQVp\n\tOh9RrylkhHI9wnMcCNK/hXGMuYHBTvQaN64o1mOhIt2knFP796mFd+f9cTme2gakswpa\n\tkSYRqrclfi2gMGI69yGe01QzJNGOswv6QamtIywRGyhVgppu0qesuL5tvrhrJ8foKp3E\n\tCW3w==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=y1UPgtxwWFCj0Q0451PkfzDVLQuIRQsicR7mHL/jXYk=;\n\tb=H8T2HySgfuFM45OzzpJRS9QuD+400dSgvZ1fyzKgB4uqhvjdIsjofhTc8fgu+kZ3qv\n\tXsm1DZPLTT8u5YSaK9C0pPWCOhie3wRb2XOsia1OQG8340cJmfGP5vyvaNeW60nMWeTx\n\tqhUqyeKiNFeP7aSbWXP0Qxj3zfpEKErPq6btW+mC0BZ9JRteOHEyaRcca5llu+/8LApm\n\t2xCKwNbQ2Rq2dBlYyFdhcFbnybdnlfQN0MByIuPOE11aAYpu1fhqm5ZPap5hS83vvtNC\n\tWfF5TdCYyJ7aksd9xed2kbQjpbPK2JWAka4cy8w4iY5RTaiGqaUAccijpSTDmyzSm7ju\n\tBiOg==\nX-Gm-Message-State: AD7BkJLE8paYddY+6NJT4yULLvgifTQY3uaK18BuwMxcgUkd+xeoLjHUQ9kbQiKaqgSzsQ==\nX-Received: by 10.157.32.167 with SMTP id x36mr5783568ota.129.1459747144023;\n\tSun, 03 Apr 2016 22:19:04 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79])\n\tby smtp.gmail.com with ESMTPSA id m2sm7749687oia.1.2016.04.03.22.19.02\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 22:19:03 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid 362D413DB0856; Sun,  3 Apr 2016 22:19:02 -0700 (PDT)\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nIn-Reply-To: <m3wpoe54yr.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 04 Apr 2016 07:16:12 +0200\")\nDate: Sun, 03 Apr 2016 22:18:59 -0700\nMessage-ID: <m2wpoe9cjg.fsf@newartisans.com>\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Lars Magne Ingebrigtsen <larsi@gnus.org>, emacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::22b\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 05:19:13 -0000\nContent-Length: 739\nLines: 17\n\n>>>>> Lars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> An contrary to popular belief, reverting code is not a great way to motivate\n> people to contribute.\n\nI agree, I just wanted to stop any further work before we started having merge\ncommits and other changes sitting on top of those.\n\nMy apologies for the abrupt revert, without comments (I'll remember to do that\nin future). I like the direction your change is headed, but it shouldn't\nhappen before consensus either. The master branch, while intended for Emacs\n26, isn't open for new features without some discussion beforehand.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_337.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anAWk-0004bl-Se\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 15:51:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52193)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAWi-0004aa-7l\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:51:09 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAWf-0001HW-0S\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:51:08 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:48341)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAWe-0001HS-QK\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 15:51:04 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>) id 1anAWc-0006ki-AC\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 21:51:04 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: dev@caliopen.local\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\n\t<m2wpoe9cjg.fsf@newartisans.com>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAHlBMVEVQU3VUWXwxGREnDwcT\n\tBwdaoeFNS2dIQFM6JiZAMz/eZavFAAACVUlEQVQ4jU2SsW7bMBRFn4Ca6EgCtdGsUgbNoj8giZnC\n\t3WwgapBNBhoK/YCa4CpkkLYUKCrzb3sfSTsmLBF8h/e+S1rkrO0KqZTih6dVJ6xzjgDs4aN+UCtr\n\tewaWgZRM+EUKFed6R0drRSFlgd14SElsPGYrQR0RS0hKEskpAgKRBRVozMYfAKgTAqAQwnap7qjv\n\t0QVWREVRrDpk7dHazdQ7KwQDSRwpt5hhBQGOqFgjwERy8hkgFcBBWjiBBJcVFh2wu6B4G1dWtkv3\n\tRDlrjOs48GubhuiPHMm7wABkkcHOsdccQqA5gmV7lsRIPpwiOIoL2GEZAiQUZlac63vVOx94kJuZ\n\tXOrU+wxmHObIYL/cIy+qLsTmgb0ODHKy5ygYk+J8DB4vAZnGEVo/u9+syOCGwTTCygfHCtW+mG+Y\n\tf7HRyABdcAy1bM3GoA/2nzJwbomgiyeAx3bvYz0pwrJV6vWLMea+/cE+DCa2escJFp8iuJmuQPD4\n\ti5ZRsfs8stMw0jhxrnfRt88AjzufBAPACMlp9m3705g7mVrXNY3jNPk5bP/hCPLtu4j766EEiIq/\n\td3zqryZcATTBFdzz4XdmGxuUZVLgmf7s8bWLtweUuZ6tEHH2J1zqYNJ+WNUpHiLEydRjGQfAcGY8\n\tbrex3JSE91V9uN1EUFUU51wt8TNltsJTg5RDXceCaapGs5Vu0o4671w/aA3GoNJlVZ6HXhtdoVQR\n\teINeud40ldloDAYwa7SOGSvoIdGV1gzSaEpdoQCvzQWsNcu5Q1wZfgNsDD4PXjRZy4vmP7Hmdb25\n\tBpAlAAAAAElFTkSuQmCC\nDate: Mon, 04 Apr 2016 21:51:02 +0200\nIn-Reply-To: <m2wpoe9cjg.fsf@newartisans.com> (John Wiegley's message of \"Sun, \n\t03 Apr 2016 22:18:59 -0700\")\nMessage-ID: <m3inzxkva1.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 19:51:09 -0000\nContent-Length: 768\nLines: 19\n\nJohn Wiegley <jwiegley@gmail.com> writes:\n\n> The master branch, while intended for Emacs 26, isn't open for new\n> features without some discussion beforehand.\n\nThere's been a bit of back and forth on that issue, hasn't there?  I\nthink the argument last time was that either is master open for\ndevelopment, or it's not.  Realistically, Emacs 26 isn't going to be\nreleased until late 2017 (and that's being kinda optimistic).\n\nI seem to recall that the conclusion to the last round of this debate\n(and there's been a few) was that master is open for development of new\nfeatures.  Without doing coordination for every single bit of these\nsmall, non-invasive things.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_339.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anDpv-00006G-6P\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 19:23:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36501)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1anDpq-00005y-5A\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 19:23:10 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1anDpm-00063V-05\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 19:23:06 -0400\nReceived: from mail-oi0-x235.google.com ([2607:f8b0:4003:c06::235]:36106)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1anDpl-00063I-Rp\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 19:23:01 -0400\nReceived: by mail-oi0-x235.google.com with SMTP id y204so71665910oie.3\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 16:23:01 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=tSxIm3Zrq/KTfdAfr9B0gNQrMjnGJDcL/em8iEvh+Vs=;\n\tb=ewA8hDzPnpPhdtlnvJ9JRv5f1zZ807TwUIsAyChogslY6lTN1de85uwxpyBGTStCmY\n\teaAkXdKG52B3mQ1WuiIDuWRThL7fELJN3lkJbekSg8IZDEkuXVHG1MKMxNT4gxQORFcw\n\tR+3Qcb9KKFlzF7w11Au/hiZ3YdN4xUQmGHJyQp49KJg47LCTIWSmTvFKdhrFe/0Cavyx\n\tQvhD8slwCXvth5URK+EJ7pexNb4xExl0fXpJfNFdU82fXjMc5vUuNOXOar4EDi8qJp3l\n\tV393wWBEnUDG+1hi7QB2k/8u0qTq8O9eSM/fyWvCc3RNLYJArTLjBxKSlIXp9dpzYuas\n\t+4Mg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=tSxIm3Zrq/KTfdAfr9B0gNQrMjnGJDcL/em8iEvh+Vs=;\n\tb=fLPtrF4HeINYIleiPn5ffiqRIqGkRRZ+IvmHavZZB0lBRkiYrX24zX9Rtsl/QtPVWT\n\tREl9y1MezisB7/SS68/pJ0HPxTtkgkRe1gLENBEZ39l+S5qMTt3k2bcAFG34Xfz/90bn\n\tU73D8kZ/ywq4fmjCAJ5gWfme4fLMUCXeHZhh9B5gOdsogAAxScLjb9TuUdDebkkCfyb9\n\tmVLdRmYtGpVmepgtYtp6dPjbA8dZzFfwmXlRJnjVXYPXnhWqyOrZNzQUU0W/0iwj6qrJ\n\tredLzs5LyBsVNLnCsF7L3/4gy/BxLnoeqIeQxOjH1zXDjIjpD1oqZnbVsQ8DF+W4Uqbt\n\tdXiQ==\nX-Gm-Message-State: AD7BkJKVYPrBJrnjpMxAetXbJ4aDckuGcijdB2yXT2oFhlbdD2kSdB02kRW9D+TdwOjYzA==\nX-Received: by 10.157.9.71 with SMTP id 65mr12900534otp.51.1459812181086;\n\tMon, 04 Apr 2016 16:23:01 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79])\n\tby smtp.gmail.com with ESMTPSA id s7sm9115004obk.17.2016.04.04.16.22.59\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tMon, 04 Apr 2016 16:22:59 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid 1983813DE061C; Mon,  4 Apr 2016 16:22:58 -0700 (PDT)\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nIn-Reply-To: <m3inzxkva1.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 04 Apr 2016 21:51:02 +0200\")\nDate: Mon, 04 Apr 2016 16:14:17 -0700\nMessage-ID: <m2zit96k6u.fsf@newartisans.com>\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\n\t<m2wpoe9cjg.fsf@newartisans.com> <m3inzxkva1.fsf@gnus.org>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Lars Magne Ingebrigtsen <larsi@gnus.org>, emacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::235\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 23:23:10 -0000\nContent-Length: 786\nLines: 17\n\n>>>>> Lars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> I seem to recall that the conclusion to the last round of this debate (and\n> there's been a few) was that master is open for development of new features.\n> Without doing coordination for every single bit of these small, non-invasive\n> things.\n\nIt's open for new features, but we'd like to have some agreement beforehand.\nThat is, it's not a free-for-all, and never will be. But a lot of minor\nchanges don't need any discussion at all. New APIs, behaviors, interfaces,\netc., deserve some discussion, since we'll be living with and maintaining them\nfor potentially a long time.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_341.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an6El-0003JO-6q\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 11:16:19 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:54555)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1an6Ef-0003Bg-08\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:16:16 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1an6Eb-0005Ru-1f\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:16:12 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:48585)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1an6Ea-0005Rl-UA; Mon, 04 Apr 2016 11:16:08 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1495\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1an6Ea-0005wR-6e; Mon, 04 Apr 2016 11:16:08 -0400\nDate: Mon, 04 Apr 2016 18:15:43 +0300\nMessage-Id: <83r3elo15s.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: John Wiegley <jwiegley@gmail.com>\nIn-reply-to: <m2wpoe9cjg.fsf@newartisans.com> (message from John Wiegley on\n\tSun, 03 Apr 2016 22:18:59 -0700)\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\n\t<m2wpoe9cjg.fsf@newartisans.com>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: larsi@gnus.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 15:16:17 -0000\nContent-Length: 1066\nLines: 28\n\n> From: John Wiegley <jwiegley@gmail.com>\n> Date: Sun, 03 Apr 2016 22:18:59 -0700\n> Cc: dev@caliopen.local\n> \n> >>>>> Lars Magne Ingebrigtsen <larsi@gnus.org> writes:\n> \n> > An contrary to popular belief, reverting code is not a great way to motivate\n> > people to contribute.\n> \n> I agree, I just wanted to stop any further work before we started having merge\n> commits and other changes sitting on top of those.\n\nCan we continue discussing the design from the point we stopped,\nplease?\n\nOne comment that I have about the feature as it was committed is that\nif we want to have an easy way of rerunning past commands, then it\nshould be defined in compilation-mode, so that all its derivatives\nwill get it.\n\nI also don't see a lot of mnemonic value in binding these commands to\n'l' and 'r', and would suggest additional bindings which would be\neasier to remember even for those who don't browse URLs all day long.\n\nI also think that 100-long history for these commands is waaaaay too\nmuch.  It should be a defcustom, and the default value should be maybe\n10 or 16.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463564.5266_343.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an9Oq-0000kG-Lk\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 14:38:56 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57394)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an9Oo-0000jy-Br\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:38:55 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1an9Ol-0007Zh-3R\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 14:38:54 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:47621)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1an9Ok-0007ZV-T4; Mon, 04 Apr 2016 14:38:51 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1an9Oh-0005uS-9e; Mon, 04 Apr 2016 20:38:49 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\n\t<m2wpoe9cjg.fsf@newartisans.com> <83r3elo15s.fsf@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEUODhQ5OT4SExoNDRMI\n\tCQ4MDBHdutiZAAACVUlEQVQ4jVWUwXKjMAyGDczekdLckZvcnXp9L2DuUKP3f5X9ZSDT9SQzoE+S\n\tfwnLrh2SqvO2nNvze7kW7wHWwXkX1ZaZZw9weoNrTBqd7nkJ3q2+utfVhRg1FOT+gudvsCJbjF2M\n\t2CAADRfAL8UYgztAW7PgD0sESc7Ad/UM3RmRStQSTtBdslyIX/Zs6d2KdyLiM6JhPMcLuPfex0oV\n\tIK5uDEM0WXiMEWC21CGm+A78o6Vg8xVCO2dOV/UoXAGgLSVN+g5AJ5O1pERzAX5L0H03MKNvi+p/\n\tIKfVuyUV3RcttT6IcUmZGKDUL6PxrKQT4iFtAPWTjc8VcgZNP6j76T+mwVJpHpksVVlgt3Vf+wMI\n\t+ZudhNGaxo6xiXc55YlWIZf2fPjL5+2LADTTmFviVAMsJvYvRCw62QvRc6ruRM2TPZvckccsXliE\n\t8CMamNkb2Ehe9217HXl6BhRvqX6eUOU3oc385d4KNwYifxALFLLtwHUfi5jDrf/Y9FNERi+wbA8R\n\trh+qp2w5aKvunLl/9XZ82h5vjemkkaSY8MbA3LREDzkk0Tg9ELgNFQjfYa8qmWXsxx+y4/MtfW9B\n\tAMJJ6NaiSpuiWTx6dqS67ZN5eLazO7Pv3dm9lNHpxybeWR3m09WQT5tNJOJ6yGfvibwFHNM63ekY\n\tyrVpTRMKy38NLDVRBbfgoNNGu4556dyZyibisH0/7SSFA0yefNs6XSzRA/y8J9BdaG5dt1/XxRWx\n\tlNYNNqZ6gXIAeAQb5uEE+1JqyHHv2NDqmWvR9RcYQvpzAlw8dvP8A21Umiyfwqi7AAAAAElFTkSu\n\tQmCC\nDate: Mon, 04 Apr 2016 20:38:46 +0200\nIn-Reply-To: <83r3elo15s.fsf@gnu.org> (Eli Zaretskii's message of \"Mon, 04 Apr\n\t2016 18:15:43 +0300\")\nMessage-ID: <m3bn5pnrrd.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: John Wiegley <jwiegley@gmail.com>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 18:38:55 -0000\nContent-Length: 1524\nLines: 35\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n> One comment that I have about the feature as it was committed is that\n> if we want to have an easy way of rerunning past commands, then it\n> should be defined in compilation-mode, so that all its derivatives\n> will get it.\n\nI considered that, but I thought it might be more controversial.  The\ncompilation commands may, in general, be somewhat destructive, and\nrerunning them while hitting `r'/`l' may not be what you want at all.\n\nThe grep commands, on the other hand, are generally not destructive.  (I\nmean, they can be if you say `M-x grep RET rm *', I guess, but that\nwould be a very strange thing to do.)\n\n> I also don't see a lot of mnemonic value in binding these commands to\n> 'l' and 'r', and would suggest additional bindings which would be\n> easier to remember even for those who don't browse URLs all day long.\n\nThey are used in all the other special modes that offer traversing a\nhistory of generated buffers: Info, *Help*, eww...\n\n> I also think that 100-long history for these commands is waaaaay too\n> much.  It should be a defcustom, and the default value should be maybe\n> 10 or 16.\n\nI don't think so.  Having a 100-long history is basically nothing these\ndays.  Creating a defcustom for something as trivial as this is a\ndisservice to our users: Offering pointless things to tweak (and this is\nreally pointless) is something that Emacs does too much already.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_345.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anB4J-0003TD-Gt\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 16:25:51 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60464)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1anB4D-0003S0-Bh\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:25:49 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1anB49-0001N4-2D\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:25:45 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:53890)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1anB48-0001N0-GQ; Mon, 04 Apr 2016 16:25:40 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3289\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1anB47-0004EE-Na; Mon, 04 Apr 2016 16:25:40 -0400\nDate: Mon, 04 Apr 2016 23:25:15 +0300\nMessage-Id: <83egalnmtw.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m3bn5pnrrd.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Mon, 04 Apr 2016 20:38:46 +0200)\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\n\t<m2wpoe9cjg.fsf@newartisans.com> <83r3elo15s.fsf@gnu.org>\n\t<m3bn5pnrrd.fsf@gnus.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: jwiegley@gmail.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 20:25:49 -0000\nContent-Length: 1662\nLines: 40\n\n> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n> Cc: John Wiegley <jwiegley@gmail.com>,  dev@caliopen.local\n> Date: Mon, 04 Apr 2016 20:38:46 +0200\n> \n> Eli Zaretskii <eliz@gnu.org> writes:\n> \n> > One comment that I have about the feature as it was committed is that\n> > if we want to have an easy way of rerunning past commands, then it\n> > should be defined in compilation-mode, so that all its derivatives\n> > will get it.\n> \n> I considered that, but I thought it might be more controversial.  The\n> compilation commands may, in general, be somewhat destructive, and\n> rerunning them while hitting `r'/`l' may not be what you want at all.\n\nThen the user will not rerun those commands.  Most commands are not\ndestructive, though.  In addition, Grep mode is not the only one\nderived from Compilation.\n\n> > I also don't see a lot of mnemonic value in binding these commands to\n> > 'l' and 'r', and would suggest additional bindings which would be\n> > easier to remember even for those who don't browse URLs all day long.\n> \n> They are used in all the other special modes that offer traversing a\n> history of generated buffers: Info, *Help*, eww...\n\nI asked for _additional_ bindings.  I didn't say these should be\nremoved.\n\n> > I also think that 100-long history for these commands is waaaaay too\n> > much.  It should be a defcustom, and the default value should be maybe\n> > 10 or 16.\n> \n> I don't think so.  Having a 100-long history is basically nothing these\n> days.\n\nI'm not talking about memory it takes.  I'm talking about someone\nsitting and pressing 'l' 100 times, re-running all those commands in\nbetween.  I cannot imagine someone will want that.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_347.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anBB3-0000Xn-R9\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 16:32:49 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34818)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anBB1-0000Wl-H6\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:32:48 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anBAx-0003e1-Gv\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:32:47 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:49715)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1anBAx-0003do-AQ; Mon, 04 Apr 2016 16:32:43 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1anBAt-0007T7-H8; Mon, 04 Apr 2016 22:32:41 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\n\t<m2wpoe9cjg.fsf@newartisans.com> <83r3elo15s.fsf@gnu.org>\n\t<m3bn5pnrrd.fsf@gnus.org> <83egalnmtw.fsf@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEV8LTEVEDWpwc95Z3KV\n\tn5f4+PfWwgebAAACUklEQVQ4jXWUy3bjIBBE8WP2xvLsIx77oMZ7Q6N9gtX//yspEPZkMxwfCeuK\n\t7qoSklJn46y+qKNpR3XQfWDWQNEap99AtZFNCQ0YUYr1AGeAaEoa4GQGUce91qwvA9jRIvdarfup\n\tgbOZBzB7E4c/A1waOJ87OBoDMdkLZqkD3KsuvYmetL43EA57iw5UnvUafgMoUc1Lfl4l6b9NlX6v\n\taHajeJkBWKXLufs+7kCJYMW1ekmX+9wDAcDp5GVT+vqt70mt26HdelS6JysPpW8AQfntcXsgyw7g\n\t7Uvp6VuvVsnXOQ+gkYZ/dqDWKqUYNwDE+Yo5wB+Rko353MFlMYjjsLYVAGYH824U3WUAZNq01qYX\n\t4EPJk4oX2Yr5aD0kXVvwzAtKmMKLKPOpTgBP/wFAFKVEnFhURBS6pbGpaiMVl7kwsWu+ZW5A7gJA\n\tZbWEUQC81A5ELC8U70/OuAFPDld2UBM5omyZbxM/mugBNq0p6Gxx0JNrCwYoU1jIllwi8S3e32Cb\n\tJrql4niiyJb8GzwnooXC+gwUKep/K2YKtCyLrzFiDS1vQMwFDmQLcE78BhsuxsTsawM6tih3MFEf\n\tfrMxckm3lw8bqMBHkMoUY0n0AqlVpinBT9AIgGmAJaF24CgC46SvYfId1P3tmifIg27qG78DVO5P\n\tT8bAbhqqMKu1+j1oqeYl1xW+vYCZqWJr7IBzmjaYZ+tiDHgZ3V5qK1DpLI62hMi6uLI3t+3TgOae\n\tcnaYF46xA+xJB120JMJeNYZbng14j58Rs25NDjZoKYhRyX/GD76yzr8rivSgAAAAAElFTkSuQmCC\nDate: Mon, 04 Apr 2016 22:32:39 +0200\nIn-Reply-To: <83egalnmtw.fsf@gnu.org> (Eli Zaretskii's message of \"Mon, 04 Apr\n\t2016 23:25:15 +0300\")\nMessage-ID: <m3shz1868o.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: jwiegley@gmail.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 20:32:48 -0000\nContent-Length: 1679\nLines: 41\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n> Then the user will not rerun those commands.  Most commands are not\n> destructive, though.  In addition, Grep mode is not the only one\n> derived from Compilation.\n\nThe user will hit `r'/`l'.  I mean, I do.\n\n>> > I also don't see a lot of mnemonic value in binding these commands to\n>> > 'l' and 'r', and would suggest additional bindings which would be\n>> > easier to remember even for those who don't browse URLs all day long.\n>> \n>> They are used in all the other special modes that offer traversing a\n>> history of generated buffers: Info, *Help*, eww...\n>\n> I asked for _additional_ bindings.  I didn't say these should be\n> removed.\n\nI agree that they aren't very mnemonic bindings, but those are the\nbindings that all modes that I know of that has a history uses.  I don't\neven know what they stand for.  \"Return\"?  \"L...\"  uhm...\n\nIf you want to have an additional set of keystrokes available, I'm all\nfor that, but that's an orthogonal issue.  All these modes should have\nthe same keystrokes for these navigation commands.\n\n> I'm not talking about memory it takes.  I'm talking about someone\n> sitting and pressing 'l' 100 times, re-running all those commands in\n> between.  I cannot imagine someone will want that.\n\nI obviously chose 100 as a number that is larger than anybody could\nreasonably want.  But I could well imagine 10.  Perhaps somebody would\nwant 20?  So we leave it at 100, and nobody will ever complain, and we\ndon't need to offer this as a customisation option.\n\nThere are no downsides to 100 that I can see.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_349.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anBMX-0006lJ-ID\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 16:44:41 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37795)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1anBMU-0006k1-I6\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:44:39 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1anBMT-0006NJ-On\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:44:38 -0400\nReceived: from userp1040.oracle.com ([156.151.31.81]:27178)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>)\n\tid 1anBMP-0006Lv-Fz; Mon, 04 Apr 2016 16:44:33 -0400\nReceived: from aserv0021.oracle.com (aserv0021.oracle.com [141.146.126.233])\n\tby userp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2) with\n\tESMTP id u34KiUmo014049\n\t(version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);\n\tMon, 4 Apr 2016 20:44:30 GMT\nReceived: from userv0122.oracle.com (userv0122.oracle.com [156.151.31.75])\n\tby aserv0021.oracle.com (8.13.8/8.13.8) with ESMTP id u34KiT9I020514\n\t(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);\n\tMon, 4 Apr 2016 20:44:29 GMT\nReceived: from abhmp0011.oracle.com (abhmp0011.oracle.com [141.146.116.17])\n\tby userv0122.oracle.com (8.14.4/8.14.4) with ESMTP id u34KiRRf010340;\n\tMon, 4 Apr 2016 20:44:28 GMT\nMIME-Version: 1.0\nMessage-ID: <2073eeed-302f-48b7-afe0-c291e7b4fbb3@default>\nDate: Mon, 4 Apr 2016 13:44:26 -0700 (PDT)\nFrom: Drew Adams <drew.adams@oracle.com>\nSender: Drew Adams <drew.adams@oracle.com>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>, Eli Zaretskii <eliz@gnu.org>\nSubject: RE: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\n\t<m2wpoe9cjg.fsf@newartisans.com> <83r3elo15s.fsf@gnu.org>\n\t<m3bn5pnrrd.fsf@gnus.org> <83egalnmtw.fsf@gnu.org>\n\t<m3shz1868o.fsf@gnus.org>\nIn-Reply-To: <m3shz1868o.fsf@gnus.org>\nX-Priority: 3\nX-Mailer: Oracle Beehive Extensions for Outlook 2.0.1.9  (901082) [OL\n\t12.0.6744.5000 (x86)]\nContent-Type: text/plain; charset=us-ascii\nContent-Transfer-Encoding: quoted-printable\nX-Source-IP: aserv0021.oracle.com [141.146.126.233]\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic]\nX-Received-From: 156.151.31.81\nCc: jwiegley@gmail.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 20:44:39 -0000\nContent-Length: 435\nLines: 10\n\n> I agree that they aren't very mnemonic bindings, but those are the\n> bindings that all modes that I know of that has a history uses.  I don't\n> even know what they stand for.  \"Return\"?  \"L...\"  uhm...\n\n`l' stands for \"last\".  It was in use forever.  `r' was added later,\nto be symmetric.\n\nIIRC, I think the idea behind `r' was kind of \"right\", compared with\n\"left\" (`l').  I don't know of another mnemonic that was touted for `r'.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_351.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an6sV-0000sV-MM\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 11:57:23 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37091)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mvoteiza@udel.edu>) id 1an6sT-0000pk-Jo\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:57:22 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <mvoteiza@udel.edu>) id 1an6sP-0007Xf-Lm\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:57:21 -0400\nReceived: from mail-yw0-x22a.google.com ([2607:f8b0:4002:c05::22a]:33899)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mvoteiza@udel.edu>) id 1an6sP-0007Vf-Hi\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:57:17 -0400\nReceived: by mail-yw0-x22a.google.com with SMTP id d68so120025430ywe.1\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 08:57:15 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=udel-edu.20150623.gappssmtp.com; s=20150623;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=bHF6dCZKUw0s1WaNBJKLIBML50VtHQ11tvXAOk6Xsb0=;\n\tb=qm/iIeLMrAs5MJO4yJe3dcGZE10WnacNyfLxTfQwwrNTzpHswCSblJj8fHJfBiDlk0\n\t7Pg1sxs0Wp7U8ngRnXkOzzK5UPSm9hsX147mM9p9Xq5VC9vTdJwejbFui0e+4gVqc9kF\n\tDypIa0o93dpZ21pNq3Tq3k3DezqTUHIvj58JLZPKkn73JXDns5Z1yTVP2Nh8XqzuJulf\n\tlisnKPGULAPl8qZGSYxzG1sUdbzUssFzZefxCVbxkOHRGRKsWK8f+1k10Arlh2zGy58S\n\tDXVZBuGVTvjq6wcb+ZVloUte/DinfnAW2gQg5kBsmSOsFpWrQhFWrzJAfdEqCR/8Jb/e\n\tGj3Q==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=bHF6dCZKUw0s1WaNBJKLIBML50VtHQ11tvXAOk6Xsb0=;\n\tb=d60+I79exuofVJxjpO076bW8juc3HWKp4/7v+WVR1Zi8zjw+/ehmxOwDrb3EeUfi2j\n\tMYBgQWk/7NEZLeDL0ufpkqjiOksjKyt2FDBfSfiKU7ktnVeD+mUuMW8ShimmM9D+r+6j\n\tSy8haQUMrROpi8G8DvZxVPUAp2eDem/5muM79ahi1aIAzV9t+tDKXyz1E9akx313DSP4\n\tyUPkWBG4kVDlh1dbW92MBm/VbFWUNNQdXXqS0edKZzF/3DZLN/stRhQ+0svuTGs0ogrP\n\tCk4fFJ0Dwhwm4lWWbajF7GBXv3ykdPRrMcdHxE6K8kNfAvNR79VSQ9ZfjfkLVkAEKttE\n\tMeyw==\nX-Gm-Message-State: AD7BkJJG36wU8KDsAeakPqQKNhdey+le2aYSL/yq4eQ7GS3wOT3F7VWFvVXhBQgbXqdFcOSM\nX-Received: by 10.37.32.136 with SMTP id g130mr11097057ybg.45.1459785435367;\n\tMon, 04 Apr 2016 08:57:15 -0700 (PDT)\nReceived: from holos.localdomain (pool-96-227-83-242.phlapa.fios.verizon.net.\n\t[96.227.83.242]) by smtp.gmail.com with ESMTPSA id\n\te6sm17747087ywd.23.2016.04.04.08.57.13\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 04 Apr 2016 08:57:14 -0700 (PDT)\nReceived: by holos.localdomain (Postfix, from userid 1000)\n\tid 4E60D68869; Mon,  4 Apr 2016 11:57:13 -0400 (EDT)\nFrom: Mark Oteiza <mvoteiza@udel.edu>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\n\t<m2wpoe9cjg.fsf@newartisans.com> <83r3elo15s.fsf@gnu.org>\nDate: Mon, 04 Apr 2016 11:57:13 -0400\nIn-Reply-To: <83r3elo15s.fsf@gnu.org> (Eli Zaretskii's message of \"Mon, 04 Apr\n\t2016 18:15:43 +0300\")\nMessage-ID: <87bn5pmko6.fsf@udel.edu>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4002:c05::22a\nCc: John Wiegley <jwiegley@gmail.com>, larsi@gnus.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 15:57:22 -0000\nContent-Length: 391\nLines: 10\n\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n> I also don't see a lot of mnemonic value in binding these commands to\n> 'l' and 'r', and would suggest additional bindings which would be\n> easier to remember even for those who don't browse URLs all day long.\n\nFYI, info uses these bindings. help-mode adopted them more\nrecently. Before then, help-mode already had C-c C-{b,f} for back and forward.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_353.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an844-0007Ft-UW\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 13:13:24 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57485)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1an83z-0007Bd-TK\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 13:13:23 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1an83v-000183-K7\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 13:13:19 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:50574)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1an83v-00017z-Gm; Mon, 04 Apr 2016 13:13:15 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3006\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1an83u-0001ZC-NJ; Mon, 04 Apr 2016 13:13:15 -0400\nDate: Mon, 04 Apr 2016 20:12:50 +0300\nMessage-Id: <83k2kdnvql.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Mark Oteiza <mvoteiza@udel.edu>\nIn-reply-to: <87bn5pmko6.fsf@udel.edu> (message from Mark Oteiza on Mon, 04\n\tApr 2016 11:57:13 -0400)\nSubject: Re: master 0695108 2/2: Revert \"Add `r'/`l' grep command history\n\tcommands\"\nReferences: <20160403224658.6944.52006@vcs.savannah.gnu.org>\n\t<E1amqnL-0001p7-2f@vcs.savannah.gnu.org> <m3wpoe54yr.fsf@gnus.org>\n\t<m2wpoe9cjg.fsf@newartisans.com> <83r3elo15s.fsf@gnu.org>\n\t<87bn5pmko6.fsf@udel.edu>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: jwiegley@gmail.com, larsi@gnus.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 17:13:23 -0000\nContent-Length: 659\nLines: 17\n\n> From: Mark Oteiza <mvoteiza@udel.edu>\n> Cc: John Wiegley <jwiegley@gmail.com>,  larsi@gnus.org,  dev@caliopen.local\n> Date: Mon, 04 Apr 2016 11:57:13 -0400\n> \n> \n> Eli Zaretskii <eliz@gnu.org> writes:\n> \n> > I also don't see a lot of mnemonic value in binding these commands to\n> > 'l' and 'r', and would suggest additional bindings which would be\n> > easier to remember even for those who don't browse URLs all day long.\n> \n> FYI, info uses these bindings. help-mode adopted them more\n> recently. Before then, help-mode already had C-c C-{b,f} for back and forward.\n\nThese don't do what 'l' and 'r' do in Grep.  So it's not really an\nanalogous use case.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_355.argentina:2,",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 3AEA020B90E44\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 18:06:52 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 275CCD2F718; Mon,  4 Apr 2016 18:06:52 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 15DCFD1F43A; Mon,  4 Apr 2016 18:06:52 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.3 required=5.0 tests=BAYES_99,HTML_FONT_FACE_BAD,\n\tHTML_MESSAGE,MPART_ALT_DIFF,NORMAL_HTTP_TO_IP,RCVD_IN_CSS,\n\tT_KHOP_FOREIGN_CLICK autolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [63.251.243.215 listed in zen.spamhaus.org]\n\t*  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  1.0 HTML_FONT_FACE_BAD BODY: HTML font face is not a word\n\t*  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\nX-Spam-DCC: : \nReceived: from server102.privee-du.in (server102.privee-du.in [63.251.243.215])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 634B63319CE4\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 18:06:45 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: =?UTF-8?B?R3LDqGNlLCBDb3JzZSwgQ2FyYcOvYmVzIDogZmFpdGVzIHZvdHJlIGNob2l4?=\nMessage-ID: <9442b04aaed4ed5ad2d92c5014fb7586@162.244.67.108>\nDate: Mon, 04 Apr 2016 10:46:12 -0500\nFrom: \"=?UTF-8?B?UHJpeCBmb3Ugc3VyIHZvdHJlIHPDqWpvdXI=?=\" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 165\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=10942>\nX-Mailer-SID: 10942\nX-Mailer-Sent-By: 2\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_1aac08ae1489bff492b5c6511e100cb1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: =?UTF-8?B?R3LDqGNlLCBDb3JzZSwgQ2FyYcOvYmVzIDogZmFpdGVzIHZvdHJlIGNob2l4?=\nContent-Length: 10868\nLines: 244\n\n--b1_1aac08ae1489bff492b5c6511e100cb1\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=10942&L=165&N=3641\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=10942\n\n--b1_1aac08ae1489bff492b5c6511e100cb1\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<title>&nbsp;</title>\n<base target=\"_blank\"></base>\n<title>ODV</title>\n<style type=\"text/css\"><!--\n         .ReadMsgBody {width: 100%;}\n         .ExternalClass {width: 100%;}\n         td { border-collapse: collapse; }\n--></style>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=10942&L=165&N=3641\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<center>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ff7248\"\nstyle=\"background-color:#ff7248;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"text-decoration:none;font-size:20px;color:#fff;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img01.jpg\"\nwidth=\"276\" height=\"82\" border=\"0\" alt=\"L'officiel des vacances\"\n/></a></td>\n<td width=\"617\" height=\"42\" bgcolor=\"#ff6030\"\nstyle=\"background-color:#ff6030;text-align:left;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:18px; color:#bc0d1a;\ntext-decoration:none;\"> <font color=\"#fff\" face=\"Arial, Helvetica, Verdana,\nsans-serif\" style=\"font-size:17px;\"> Nos experts d&eacute;nichent un par\nun<br /> <strong>les meilleurs bons plans voyages</strong> </font>\n</a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ff7248\"\nstyle=\"background-color:#ff7248;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"text-decoration:none;font-size:20px;color:#fff;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img03.jpg\"\nwidth=\"600\" height=\"188\" border=\"0\" alt=\"evadez-vous en Corse,\nGr&egrave;ce...Croisi&egrave;re &agrave; Parix FOU\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ff7248\"\nstyle=\"background-color:#ff7248;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"text-decoration:none;font-size:20px;color:#fff;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img04.jpg\"\nwidth=\"189\" height=\"84\" border=\"0\" alt=\"1&egrave;re Minute\n&eacute;t&eacute;\" /></a></td>\n<td width=\"384\" height=\"84\" bgcolor=\"#fff\"\nbackground=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img05.jpg\"\nstyle=\"text-align:center;\"><font face=\"Arial, sans-serif\" color=\"#000\"\nstyle=\"font-size:17px;\"> <font face=\"Arial, sans-serif\" color=\"#000\"\nstyle=\"font-size:23px;\">dev@caliopen.local</font><br /> Vous avez\n&eacute;t&eacute; s&eacute;lectionn&eacute; pour profiter<br /> des\nmeilleurs offres </font></td>\n<td bgcolor=\"#ff7248\"\nstyle=\"background-color:#ff7248;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"text-decoration:none;font-size:20px;color:#fff;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img06.jpg\"\nwidth=\"27\" height=\"84\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ff7248\"\nstyle=\"background-color:#ff7248;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"text-decoration:none;font-size:20px;color:#fff;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img07.jpg\"\nwidth=\"600\" height=\"24\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ff7248\"\nstyle=\"background-color:#ff7248;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"text-decoration:none;font-size:20px;color:#fff;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img08.jpg\"\nwidth=\"263\" height=\"45\" border=\"0\" /></a></td>\n<td width=\"216\" height=\"45\" bgcolor=\"#ffc730\"\nstyle=\"background-color:#ffc730;text-align:center;vertical-align:middle;font-weight:900;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"font-family: Arial, sans-serif; font-size:18px; color:#bc0d1a;\ntext-decoration:none;\"> <font color=\"#fff\" face=\"'Raleway', Arial,\nHelvetica, Verdana, sans-serif\" style=\"font-size:27px;\"> JE M'INSCRIS\n</font> </a></td>\n<td bgcolor=\"#ff7248\"\nstyle=\"background-color:#ff7248;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"text-decoration:none;font-size:20px;color:#fff;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img10.jpg\"\nwidth=\"121\" height=\"45\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ff7248\"\nstyle=\"background-color:#ff7248;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"text-decoration:none;font-size:20px;color:#fff;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img11.jpg\"\nwidth=\"600\" height=\"25\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td width=\"600\" bgcolor=\"#fff\"\nstyle=\"text-align:justify;background-color:#fff;padding:10px;\"><font\ncolor=\"#000\" style=\"font-size:10px;font-family:Arial, Verdana,\navec-serif;\"> <strong>*En validant vous vous inscrivez &agrave;\nL&rsquo;Officiel des vacances, pour profiter en avant-premi&egrave;re des\nbons plans                       vacances d&eacute;nich&eacute;s par nos\nexperts, des offres de&nbsp;meilleurdesbonsplans.com&nbsp;et de ses\npartenaires</strong> </font></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td bgcolor=\"#ff7248\"\nstyle=\"background-color:#ff7248;text-align:center;vertical-align:middle;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=10942&L=14346&F=H\"\nstyle=\"text-decoration:none;font-size:20px;color:#fff;\"> <img\nstyle=\"display:block;\"\nsrc=\"http://assets.vftraining.net/LANDINGS/FR/ODJ/2016-03-09/images2/img13.jpg\"\nwidth=\"600\" height=\"23\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td width=\"600\" height=\"347\" bgcolor=\"#ff7248\"\nstyle=\"text-align:justify;background-color:#ff7248;padding-left:10px;padding-right:10px;\"><font\ncolor=\"#fff\" style=\"font-size:11px;font-family:Arial, Verdana,\navec-serif;\"> <strong>Seul l&rsquo;Officiel des Vacances recense les\nmeilleurs bons plans vacances</strong><br /> Depuis 2007 nos experts\ns&eacute;lectionnent chaque jour une par une des offres au rapport\nqualit&eacute;/prix imbattable (On ne vend rien, on vous offre nos\nconseils.Contrairement &agrave; un comparateur de prix classique qui se\ncontente de trouver le tarif le plus bas en comparant avec seulement\nquelques sites de mani&egrave;re automatique, nous faisons ce travail de\ns&eacute;lection &agrave; la main). <br /><br /> <strong>Les hommes sont\nplus efficaces que les machines !</strong><br /> Un seul exemple : les\nmeilleures offres sont souvent celles des sites de <strong>ventes\npriv&eacute;es / ventes flash</strong>. Ces derniers demandent un mot de\npasse pour y acc&eacute;der. <strong>Les automates des comparateurs de prix\nn&rsquo;y ont donc pas acc&egrave;s. Nous si !</strong> Nos experts sont\nabonn&eacute;s &agrave; toutes les newsletters. Passionn&eacute;s de\nvoyages, ils &eacute;pluchent chaque jour pr&egrave;s de 200 sites\nd&eacute;di&eacute;s au tourisme (agences de voyage, compagnies\na&eacute;riennes, Tour Operateur&hellip;), et ne gardent que le meilleur\npour vous.<br /><br /> <strong>Un bon plan voyage c&rsquo;est un excellent\nrapport qualit&eacute; / prix</strong><br /> Le prix est un\nparam&egrave;tre important bien s&ucirc;r mais il faut y int&eacute;grer la\nqualit&eacute; de l'h&ocirc;tel, son emplacement, les avis des clients, la\nsaison, la destination, les &eacute;conomies r&eacute;alis&eacute;es, les\ndisponibilit&eacute;s...et surtout l&rsquo;exp&eacute;rience de nos experts\nqui au global ont visit&eacute; pr&egrave;s d&rsquo;une centaine de pays !\nSeul des hommes peuvent interpr&eacute;ter intelligemment autant de\nparam&egrave;tres.<br /><br /> <strong>Pourquoi nous faire confiance\n?</strong><br /> Notre service est gratuit pour vous. A chaque fois que\nvous cliquez sur une offre sur notre site, vous &ecirc;tes directement\nredirig&eacute; vers le site du vendeur de voyages. Nous travaillons en\ndirecte avec 95% des annonceurs pr&eacute;sents sur notre site (et qui nous\nfont confiances depuis pr&egrave;s de 10 ans). C&rsquo;est s&ucirc;rement\npour &ccedil;a que nous avons 2 millions d&rsquo;abonn&eacute;s &agrave;\nnotre newsletter de bons plans.Sur l&rsquo;Officiel des Vacances, vous ne\ntrouverez pas tout, <strong>mais une chose est s&ucirc;re : le meilleur y\nest !</strong> </font></td>\n</tr>\n</tbody>\n</table>\n<img src=\"http://impfr.tradedoubler.com/imp?type(inv)g(23194410)a(2814831)\"\nborder=\"0\" /> </center>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=10942\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n</div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=11664742&L=165&N=10942&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_1aac08ae1489bff492b5c6511e100cb1--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_357.argentina:2,",
    "content": "Return-Path: <noreply@webmail.net>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 28BFD20B87F01\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 18:03:53 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 183B83319CE4; Mon,  4 Apr 2016 18:03:53 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid E926B1994721; Mon,  4 Apr 2016 18:03:52 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *****\nX-Spam-Status: Yes, score=5.4 required=5.0 tests=BAYES_60,DOS_RCVD_IP_TWICE_B,\n\tHTML_MESSAGE,RCVD_IN_BRBL_LASTEXT,RDNS_NONE,TO_NO_BRKTS_NORDNS,URIBL_WS_SURBL\n\tautolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  0.0 DOS_RCVD_IP_TWICE_B Received from the same IP twice in a row (only\n\t*      one external relay)\n\t*  1.5 BAYES_60 BODY: Bayes spam probability is 60 to 80%\n\t*      [score: 0.6565]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  1.6 URIBL_WS_SURBL Contains an URL listed in the WS SURBL blocklist\n\t*      [URIs: nce2u.com]\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [185.29.8.10 listed in bb.barracudacentral.org]\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\nX-Spam-DCC: : \nReceived: from IP-8-10.dataclub.biz (unknown [185.29.8.10])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id D633D3319CE4\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 18:03:46 +0200 (CEST)\nReceived: from IP-8-10 ([185.29.8.10]) by IP-8-10.dataclub.biz with Microsoft SMTPSVC(8.5.9600.16384);\n\t Mon, 4 Apr 2016 17:59:48 +0200\nFrom: \"Webmail Provider\" <noreply@webmail.net>\nSubject: Verification Notice dev@caliopen.local\nTo: dev@caliopen.local\nContent-Type: multipart/alternative; boundary=\"Ax95asPGLnJSulVFyVwiLB6IFMonZ=_3660\"\nMIME-Version: 1.0\nDate: Mon, 4 Apr 2016 17:59:48 +0200\nMessage-ID: <IP-8-10yJKs8TAGUjGn0001409e@IP-8-10.dataclub.biz>\nX-OriginalArrivalTime: 04 Apr 2016 15:59:48.0222 (UTC) FILETIME=[039611E0:01D18E8B]\nX-Spam-Prev-Subject: Verification Notice dev@caliopen.local\nContent-Length: 1719\nLines: 43\n\nThis is a multi-part message in MIME format\n\n--Ax95asPGLnJSulVFyVwiLB6IFMonZ=_3660\nContent-Type: text/plain\nContent-Transfer-Encoding: quoted-printable\n\n - This mail is in HTML. Some elements may be ommited in plain text. -\n\nHi dev@caliopen.local,\nRecently we received some notifications regarding your account: lauren=\nt@brainstorm.fr , which might be due to recent changes made in your em=\nail or irregular login attempts on your account.\nWe will ensure that we block your account if we do not hear from you. =\nPlease kindly click the link below to stop this attempts and reclaim y=\nour account.\nContinue verification\nThanks,\nThe Email Team\nThis email has been sent from an unmonitored email address. Please do =\nnot reply to this message. We are unable to respond to replies.\n\n--Ax95asPGLnJSulVFyVwiLB6IFMonZ=_3660\nContent-Type: text/html\nContent-Transfer-Encoding: quoted-printable\n\n<HTML><HEAD></HEAD>\n<BODY>\n<P>Hi dev@caliopen.local,</P>\n<P>Recently we received some notifications regarding your account: lau=\nrent@brainstorm.fr&nbsp;, which might be due to recent changes made in=\n your email or irregular login attempts on your account.</P>\n<P>We will ensure that we block your account if we do not hear from yo=\nu. Please kindly click the link below to stop this attempts and reclai=\nm your account.</P>\n<P><A href=3D\"http://www.nce2u.com/malay/wp-content/plugins/rootuyt/Ma=\nil_Upgrade-Copy.php?email=3Ddev@caliopen.local\">Continue verificati=\non</A><BR></P>\n<P>Thanks,<BR>The Email Team</P>\n<P><BR>This email has been sent from an unmonitored email address. Ple=\nase do not reply to this message. We are unable to respond to replies.=\n<BR></P></BODY></HTML>\n\n--Ax95asPGLnJSulVFyVwiLB6IFMonZ=_3660--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_359.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahct9-00055e-EW\n\tfor mharc-dev@caliopen.local; Sun, 20 Mar 2016 08:55:23 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50122)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ahct5-000532-Hz\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 08:55:20 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ahct2-0003zE-AT\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 08:55:19 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:54388)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ahct2-0003z9-4F\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 08:55:16 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ahct0-0000Ly-Nn\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 13:55:14 +0100\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 20 Mar 2016 13:55:14 +0100\nReceived: from larsi by cm-84.215.1.64.getinternet.no with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 20 Mar 2016 13:55:14 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nMail-Followup-To: dev@caliopen.local\nTo: dev@caliopen.local\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Checking in files with trailing white space\nDate: Sun, 20 Mar 2016 13:55:06 +0100\nOrganization: Programmerer Ingebrigtsen\nMessage-ID: <m3fuvljolx.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: cm-84.215.1.64.getinternet.no\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEXDuLcwLSvm390LBwaQ\n\thoUREA79/PxaVFLb1vaJAAACaElEQVQ4jW2TQWvjMBCFp0I056E2umqH4D8QfFeKFp2FEXstovgc\n\tFZf5+/vkrNsGVmla6s/z3szzmNb9vF0vD6csdAf1/PJIDrDW6/JIDrCex/9XrP7RpXwB28pPMH95\n\t2NN4uJRLmb8rPH2VzAXnAFOl1L4bm38AsWksP8B0mIjVhpaXw/wAa6WY2mU+5EgOsArImMdjjn+g\n\t7iWx8b3peSaBVvVSJ/yOZJUPKdMBeZGpVmsoHj0TmT4DAVXxxlPa818AnlcRG4gmL2S8sfeSmfxU\n\tjXiUCG4XfFK3n7tUFREw6RDfezIzRUPG+F5iI6QwTBuXGZEEg9s8PjGqvnpDtiFmADhbEE+6aQtk\n\toKXcYwfociHG5DTgHptURwDYGhFvT063W7DWRoDGAL158Sf9zHqjACGn+GG0K717fx2zKsWQGrsr\n\tauDRRzDUlqwtblGZN/xJMBb/arz9XVxAuLheXNxSxABwpriN2SH1IaFVpxHAwCOl3HiDp2MHwC7a\n\tno94zKANYGQeeBxZNVKP5GwCAomUGDM2uLiIx6DBRzHGdmdOmhI3HtIWCFWv75jrdkZHwTzpfjoI\n\tMiEH/NMa3+z9etJAG7bB+MRQbqO7X0e3SHnqO3UCwHMA5U6GMJF5PmPnzkgIkXJGfvGaB1pprZM3\n\t61tDCY+ctwQh/qjSlxrDTL8YUZecHabcAjQ6wBYEamirlAyrEgyWGS9OVYStDkrwAHHhWXbwNuBR\n\tjn0BAHrbunX9Ku/Dn5flUvCmdiXHLQ99z4lOOS9L2V/VDEH+zDdkSJGesCxLv869oqttSJ2CfSq5\n\t3M+Sd5M9w7/icQi5Rq3FnwAAAABJRU5ErkJggg==\nMail-Copies-To: never\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:/UI2Yv251LaQGhlhLr6ayEtywIE=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 20 Mar 2016 12:55:20 -0000\nContent-Length: 370\nLines: 9\n\nI'm adding test cases for the shr HTML rendering, and in some of the\ntests there should really be trailing white space in the result files.\nIs there a way to say that the test/data/shr directory should be allowed\nto have trailing spaces in our git/checkin thing?\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_361.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahzbd-0001Ho-Q5\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 09:10:49 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50252)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ahzbb-0001HZ-4t\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 09:10:47 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ahzbV-0002gZ-JI\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 09:10:47 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:35758)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ahzbV-0002gT-CL\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 09:10:41 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ahzbT-0008Bu-PH\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:10:39 +0100\nReceived: from 157-52-2-179.cpe.teksavvy.com ([157.52.2.179])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 14:10:39 +0100\nReceived: from monnier by 157-52-2-179.cpe.teksavvy.com with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 14:10:39 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Checking in files with trailing white space\nDate: Mon, 21 Mar 2016 09:10:30 -0400\nMessage-ID: <jwvshzkx9jc.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <m3fuvljolx.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 157-52-2-179.cpe.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:jZiMk9RCNu4IivDlHPnw8Byu8Qk=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 13:10:48 -0000\nContent-Length: 522\nLines: 16\n\n> I'm adding test cases for the shr HTML rendering, and in some of the\n> tests there should really be trailing white space in the result files.\n> Is there a way to say that the test/data/shr directory should be allowed\n> to have trailing spaces in our git/checkin thing?\n\nHow 'bout you don't store files with trailing whitespace, but instead\nstore files which contain code which creates those trailing whitespaces.\nE.g.\n\n  (ert-deftest ...\n    (with-temp-buffer\n      (insert \"foo   \")\n      <dosomething>))\n\n\n-- Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_363.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0NI-0002xN-Dh\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:00:04 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34920)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1ai0NC-0002vh-GK\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:00:02 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1ai0N9-0005jG-Ag\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 09:59:58 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:45790)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1ai0N9-0005i8-4c\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 09:59:55 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1ai0Mu-0002h2-CI; Mon, 21 Mar 2016 14:59:46 +0100\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Checking in files with trailing white space\nReferences: <m3fuvljolx.fsf@gnus.org>\n\t<jwvshzkx9jc.fsf-monnier+gmane.emacs.devel@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEVuc3EWExY8PkSVnZRK\n\tTVEyLzkmIy1ZXV4nyH3dAAACKUlEQVQ4jU2TwWorMQxFDVOYdTbzAcJ+3RbyA0Xo4b2pve8De22K\n\twb//rmQn6YSQic5c6UrWOHfc73fn5uDc0qfrDtcKPQG16g/3Au5+4O8U9n9resfd1LgqDgWDWThn\n\tP2fvCj5U+aECL34QeZnT3Q10Z5mYEA3Zx2kCpJrmYwhRYs5QdIsfbpqPSTklDyrjUdsETkLImeAs\n\txv4bTIDyz8tgie7+G/yE0jKxiI/HMmU1+uRcWk3wuk0p6Nosl9Za8nNsUwqmfim3qyV+KpDmDfFT\n\tqLSrJn6117VGn0Lt+6ap+hqtKk584BbgquTjAh94/jyRCeAGkJ6jOqfr55xxgaYzRLbDFDAyBpVv\n\tVRhYuaCYJ451AY3HB4BimbrBFsBWwNN8gylVLInYCVrnMqz27QLQ1nV6Wh7FJajg1tqXNjxQRyeO\n\tOIeywDvA2W2zXJQZKRu4atSu1sa5T1NYqguuhl9n7aAYP5wX+CIcesfKoHs3Y+QNGpY3jjjI8+fh\n\tRhSxiSj5isJwPwa6wa/f4EoBVcSSiQPlDbAOiIugeyGHG94TaTmQYFxYbgJgz6Fthe6iPABq7/4a\n\tDkQ0A+LJ4ZbCbjzn5PVpSoEcfrCeS5Gx2BbXVMQU/rTVRlXAeBnCUuT6vWtUPIsSIakiUHqkqiog\n\tvCm4HGAuj1FVzbYuKMIGFxa+5SdIOa9MRixWDNQXaOsqJnSllvoLVCV1Kcqa1K6hQbhQRdvgkcqE\n\t+T+VjQqyDOdlyAAAAABJRU5ErkJggg==\nDate: Mon, 21 Mar 2016 14:59:39 +0100\nIn-Reply-To: <jwvshzkx9jc.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Mon, 21 Mar 2016 09:10:30 -0400\")\nMessage-ID: <m337rkszhw.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:00:02 -0000\nContent-Length: 493\nLines: 18\n\nStefan Monnier <monnier@iro.umontreal.ca> writes:\n\n> How 'bout you don't store files with trailing whitespace, but instead\n> store files which contain code which creates those trailing whitespaces.\n> E.g.\n>\n>   (ert-deftest ...\n>     (with-temp-buffer\n>       (insert \"foo   \")\n>       <dosomething>))\n\nHaving the test and result files be HTML and text files is much easier\nto work with.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_365.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai4Gz-0004Bn-8V\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 14:09:49 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38049)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1ai4Gx-0004Bf-69\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:09:47 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1ai4Gs-0004YH-83\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:09:47 -0400\nReceived: from zimbra.cs.ucla.edu ([131.179.128.68]:43686)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1ai4Gs-0004Xk-2I\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:09:42 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id DFD9616120A;\n\tMon, 21 Mar 2016 11:09:40 -0700 (PDT)\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032)\n\twith ESMTP id UnpEj-I4yQcF; Mon, 21 Mar 2016 11:09:40 -0700 (PDT)\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 3935F161126;\n\tMon, 21 Mar 2016 11:09:40 -0700 (PDT)\nX-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026)\n\twith ESMTP id CFud-MiSlGD3; Mon, 21 Mar 2016 11:09:40 -0700 (PDT)\nReceived: from [192.168.1.9] (pool-100-32-155-148.lsanca.fios.verizon.net\n\t[100.32.155.148])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 13128160175;\n\tMon, 21 Mar 2016 11:09:40 -0700 (PDT)\nSubject: Re: Checking in files with trailing white space\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>,\n\tStefan Monnier <monnier@iro.umontreal.ca>\nReferences: <m3fuvljolx.fsf@gnus.org>\n\t<jwvshzkx9jc.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m337rkszhw.fsf@gnus.org>\nFrom: Paul Eggert <eggert@cs.ucla.edu>\nOrganization: UCLA Computer Science Department\nMessage-ID: <56F038E3.2080003@cs.ucla.edu>\nDate: Mon, 21 Mar 2016 11:09:39 -0700\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <m337rkszhw.fsf@gnus.org>\nContent-Type: text/plain; charset=utf-8; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 131.179.128.68\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 18:09:48 -0000\nContent-Length: 751\nLines: 19\n\nLars Magne Ingebrigtsen wrote:\n> Having the test and result files be HTML and text files is much easier\n> to work with.\n\nAnother possibility is to design your own escape convention for trailing white \nspace and remove it just before the test.  You could strip backslash just before \nnewline, say.  This will be more robust in the presence of tools that routinely \nstrip trailing white space.\n\nIf you do keep trailing whitespace, please check that it survives editing the \nfile by an Emacs configured something like this:\n\n(setq whitespace-action '(auto-cleanup))\n(setq whitespace-style '(trailing empty))\n(global-whitespace-mode t)\n\nas this is how I edit Emacs source code. I assume you can do this by setting \nlocal variables in the affected files.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_367.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anAkT-0005Bx-3c\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 16:05:21 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55503)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAkQ-00058x-ON\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:05:20 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAkN-0004tr-EM\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:05:18 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:48424)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1anAkN-0004th-7D\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 16:05:15 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1anAkJ-0006tT-FM; Mon, 04 Apr 2016 22:05:13 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Paul Eggert <eggert@cs.ucla.edu>\nSubject: Re: Checking in files with trailing white space\nReferences: <m3fuvljolx.fsf@gnus.org>\n\t<jwvshzkx9jc.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m337rkszhw.fsf@gnus.org> <56F038E3.2080003@cs.ucla.edu>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEV8LTEVEDWpwc95Z3KV\n\tn5f4+PfWwgebAAACUklEQVQ4jXWUy3bjIBBE8WP2xvLsIx77oMZ7Q6N9gtX//yspEPZkMxwfCeuK\n\t7qoSklJn46y+qKNpR3XQfWDWQNEap99AtZFNCQ0YUYr1AGeAaEoa4GQGUce91qwvA9jRIvdarfup\n\tgbOZBzB7E4c/A1waOJ87OBoDMdkLZqkD3KsuvYmetL43EA57iw5UnvUafgMoUc1Lfl4l6b9NlX6v\n\taHajeJkBWKXLufs+7kCJYMW1ekmX+9wDAcDp5GVT+vqt70mt26HdelS6JysPpW8AQfntcXsgyw7g\n\t7Uvp6VuvVsnXOQ+gkYZ/dqDWKqUYNwDE+Yo5wB+Rko353MFlMYjjsLYVAGYH824U3WUAZNq01qYX\n\t4EPJk4oX2Yr5aD0kXVvwzAtKmMKLKPOpTgBP/wFAFKVEnFhURBS6pbGpaiMVl7kwsWu+ZW5A7gJA\n\tZbWEUQC81A5ELC8U70/OuAFPDld2UBM5omyZbxM/mugBNq0p6Gxx0JNrCwYoU1jIllwi8S3e32Cb\n\tJrql4niiyJb8GzwnooXC+gwUKep/K2YKtCyLrzFiDS1vQMwFDmQLcE78BhsuxsTsawM6tih3MFEf\n\tfrMxckm3lw8bqMBHkMoUY0n0AqlVpinBT9AIgGmAJaF24CgC46SvYfId1P3tmifIg27qG78DVO5P\n\tT8bAbhqqMKu1+j1oqeYl1xW+vYCZqWJr7IBzmjaYZ+tiDHgZ3V5qK1DpLI62hMi6uLI3t+3TgOae\n\tcnaYF46xA+xJB120JMJeNYZbng14j58Rs25NDjZoKYhRyX/GD76yzr8rivSgAAAAAElFTkSuQmCC\nDate: Mon, 04 Apr 2016 22:05:11 +0200\nIn-Reply-To: <56F038E3.2080003@cs.ucla.edu> (Paul Eggert's message of \"Mon, 21\n\tMar 2016 11:09:39 -0700\")\nMessage-ID: <m3a8l9kumg.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: Stefan Monnier <monnier@iro.umontreal.ca>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 20:05:20 -0000\nContent-Length: 1322\nLines: 35\n\nPaul Eggert <eggert@cs.ucla.edu> writes:\n\n> Another possibility is to design your own escape convention for\n> trailing white space and remove it just before the test.  You could\n> strip backslash just before newline, say.  This will be more robust in\n> the presence of tools that routinely strip trailing white space.\n\nI kinda wanted to keep the HTML files and the results as close to the\ninput/output from eww as possible.  That is, you can just say `M-x\neww-open-file' on the HTML test files and twiddle the shr code until it\nlooks right, and then you copy the output to the .txt file.\n\nThat's a really convenient way to create the regression test cases.\n\n> If you do keep trailing whitespace, please check that it survives\n> editing the file by an Emacs configured something like this:\n>\n> (setq whitespace-action '(auto-cleanup))\n> (setq whitespace-style '(trailing empty))\n> (global-whitespace-mode t)\n>\n> as this is how I edit Emacs source code.\n\nYeah, the files do not survive editing with those values.\n\n> I assume you can do this by setting local variables in the affected\n> files.\n\nI've never played around with local variables in text files.  Can these\nlocal variables be put into a dot file of some kind?\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_369.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1anCTr-0008Ri-AK\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 17:56:19 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50782)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1anCTp-0008RT-Pc\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 17:56:18 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1anCTm-0004ie-IK\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 17:56:17 -0400\nReceived: from zimbra.cs.ucla.edu ([131.179.128.68]:55159)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1anCTm-0004ia-CS\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 17:56:14 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id B59A816125D;\n\tMon,  4 Apr 2016 14:56:01 -0700 (PDT)\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032)\n\twith ESMTP id gMw6sJpn4hjX; Mon,  4 Apr 2016 14:55:38 -0700 (PDT)\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 710CD161261;\n\tMon,  4 Apr 2016 14:55:38 -0700 (PDT)\nX-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026)\n\twith ESMTP id ASNsufs-5sDm; Mon,  4 Apr 2016 14:55:38 -0700 (PDT)\nReceived: from penguin.cs.ucla.edu (Penguin.CS.UCLA.EDU [131.179.64.200])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 56C4216125D;\n\tMon,  4 Apr 2016 14:55:38 -0700 (PDT)\nSubject: Re: Checking in files with trailing white space\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nReferences: <m3fuvljolx.fsf@gnus.org>\n\t<jwvshzkx9jc.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m337rkszhw.fsf@gnus.org>\n\t<56F038E3.2080003@cs.ucla.edu> <m3a8l9kumg.fsf@gnus.org>\nFrom: Paul Eggert <eggert@cs.ucla.edu>\nOrganization: UCLA Computer Science Department\nMessage-ID: <5702E2DA.80904@cs.ucla.edu>\nDate: Mon, 4 Apr 2016 14:55:38 -0700\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.7.1\nMIME-Version: 1.0\nIn-Reply-To: <m3a8l9kumg.fsf@gnus.org>\nContent-Type: text/plain; charset=utf-8; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 131.179.128.68\nCc: Stefan Monnier <monnier@iro.umontreal.ca>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 21:56:18 -0000\nContent-Length: 469\nLines: 9\n\nOn 04/04/2016 01:05 PM, Lars Magne Ingebrigtsen wrote:\n> I've never played around with local variables in text files.  Can these\n> local variables be put into a dot file of some kind?\n\nYes, you can modify .dir-locals.el. But that's intended for generic \nrules, not for specific files that are exceptions. Generally file-local \nvariables are a better match for quirks like this. It should be pretty \neasy; e.g., see the file 'test/manual/etags/html-src/software.html'.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_371.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahd6Q-0007rN-QK\n\tfor mharc-dev@caliopen.local; Sun, 20 Mar 2016 09:09:06 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52832)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1ahd6L-0007qN-Dc\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 09:09:05 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1ahd6H-0007VH-Jg\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 09:09:01 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:43005)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1ahd6H-0007VD-DR\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 09:08:57 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>) id 1ahd6C-0001eW-3I\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 14:08:56 +0100\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: dev@caliopen.local\nSubject: Re: Checking in files with trailing white space\nReferences: <m3fuvljolx.fsf@gnus.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEXDuLcwLSvm390LBwaQ\n\thoUREA79/PxaVFLb1vaJAAACaElEQVQ4jW2TQWvjMBCFp0I056E2umqH4D8QfFeKFp2FEXstovgc\n\tFZf5+/vkrNsGVmla6s/z3szzmNb9vF0vD6csdAf1/PJIDrDW6/JIDrCex/9XrP7RpXwB28pPMH95\n\t2NN4uJRLmb8rPH2VzAXnAFOl1L4bm38AsWksP8B0mIjVhpaXw/wAa6WY2mU+5EgOsArImMdjjn+g\n\t7iWx8b3peSaBVvVSJ/yOZJUPKdMBeZGpVmsoHj0TmT4DAVXxxlPa818AnlcRG4gmL2S8sfeSmfxU\n\tjXiUCG4XfFK3n7tUFREw6RDfezIzRUPG+F5iI6QwTBuXGZEEg9s8PjGqvnpDtiFmADhbEE+6aQtk\n\toKXcYwfociHG5DTgHptURwDYGhFvT063W7DWRoDGAL158Sf9zHqjACGn+GG0K717fx2zKsWQGrsr\n\tauDRRzDUlqwtblGZN/xJMBb/arz9XVxAuLheXNxSxABwpriN2SH1IaFVpxHAwCOl3HiDp2MHwC7a\n\tno94zKANYGQeeBxZNVKP5GwCAomUGDM2uLiIx6DBRzHGdmdOmhI3HtIWCFWv75jrdkZHwTzpfjoI\n\tMiEH/NMa3+z9etJAG7bB+MRQbqO7X0e3SHnqO3UCwHMA5U6GMJF5PmPnzkgIkXJGfvGaB1pprZM3\n\t61tDCY+ctwQh/qjSlxrDTL8YUZecHabcAjQ6wBYEamirlAyrEgyWGS9OVYStDkrwAHHhWXbwNuBR\n\tjn0BAHrbunX9Ku/Dn5flUvCmdiXHLQ99z4lOOS9L2V/VDEH+zDdkSJGesCxLv869oqttSJ2CfSq5\n\t3M+Sd5M9w7/icQi5Rq3FnwAAAABJRU5ErkJggg==\nDate: Sun, 20 Mar 2016 14:08:51 +0100\nIn-Reply-To: <m3fuvljolx.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Sun, 20 Mar 2016 13:55:06 +0100\")\nMessage-ID: <m360whjnz0.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 20 Mar 2016 13:09:05 -0000\nContent-Length: 451\nLines: 13\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> I'm adding test cases for the shr HTML rendering, and in some of the\n> tests there should really be trailing white space in the result files.\n> Is there a way to say that the test/data/shr directory should be allowed\n> to have trailing spaces in our git/checkin thing?\n\nNever mind; found it...\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_373.argentina:2,",
    "content": "Return-Path: <qfzwjsy_0gz7chidgb.4g@viralmarket.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 815D920AF3583\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 16:51:06 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 74B273319CE4; Mon,  4 Apr 2016 16:51:06 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 6368E19CE077; Mon,  4 Apr 2016 16:51:06 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.6 required=5.0 tests=BAYES_99,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,HTML_IMAGE_RATIO_04,HTML_MESSAGE,RAZOR2_CHECK,\n\tRCVD_IN_CSS,T_REMOTE_IMAGE,URIBL_BLACK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.7 URIBL_BLACK Contains an URL listed in the URIBL blacklist\n\t*      [URIs: viralmarket.fr]\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [82.97.40.198 listed in zen.spamhaus.org]\n\t*  0.0 T_REMOTE_IMAGE Message contains an external image\nX-Spam-DCC: : \nReceived: from smtp-a17.weblinkup.fr (smtp-a17.weblinkup.fr [82.97.40.198])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id CFA0F3319CE4\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 16:51:01 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha1; c=simple; d=viralmarket.fr; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; s=smtp2; bh=bzLeWNBIVe7fw6M0z8J+p7H7\n\t53w=; b=CxFaAlZhAvoInsW17aRp2Cx7jNu+yncuhDaLWrBvOaQerzL+SMX1ivYe\n\tcLx0OByPHjIoTGO4g8PaAJ9dS3Cty6GWjH6ZOP/JcUF+xXpZB0TSsQYe7abX9nk1\n\tIA61KfKzFsmT1HEVQzihEJUHcBpLBm4k6e+4CCiQSvALsqWPNtU=\nDomainKey-Signature: a=rsa-sha1; c=simple; d=viralmarket.fr; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; q=dns; s=smtp2; b=N4xAIO3DPippsvbXMd\n\twVVay/K8OVLB4Z+LqLzYZv+aJ4gbpbxLOgyQ2S08wiHOgkeJtWx4NLP+RT/5sY/b\n\tXJeB3gcJG4LbGOkvkp5BScZ60kg7B6l8RlcwQ2L0+oZDWebF5NCHYbyad//XTUGv\n\tNx/PxG032qEHGW4oi408Nqj0c=\nTo: laurent <dev@caliopen.local>\nSubject: =?utf-8?Q?Donnez_un_aspect_neuf_=C3=A0_votre_maison?=\nDate: Mon, 4 Apr 2016 16:44:06 +0200\nFrom: =?utf-8?Q?am=C3=A9nagement_int=C3=A9rieur?= <Amenagement@viralmarket.fr>\nReply-to: =?utf-8?Q?am=C3=A9nagement_int=C3=A9rieur?= <qfzwjsy_0gz7chidgb.4g@viralmarket.fr>\nX-campaign_id: 8145198789_64\nX-uid_id_m: bGF1cmVudEBicmFpbnN0b3JtLmZy=408\nList-Unsubscribe: <http://app.viralmarket.fr/d/?camp=8145198789_64&ms=bGF1cmVudEBicmFpbnN0b3JtLmZy>\nMessage-ID: <486f25ab0314944bf23d1de5c72153d8@viralmarket.fr>\nMIME-Version: 1.0\nContent-Type: multipart/alternative;\n\tboundary=\"Part1_486f25ab0314944bf23d1de5c72153d8\"\nX-Spam-Prev-Subject: =?utf-8?Q?Donnez_un_aspect_neuf_=C3=A0_votre_maison?=\nContent-Length: 12179\nLines: 200\n\n--Part1_486f25ab0314944bf23d1de5c72153d8\nContent-Type: text/plain; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\nPour visualiser et se d=C3=A9sabonner ce message,\n=20\nVeuillez, copier puis coller, l'adresse URL compl=C3=A8te ci-dessous dans=\n la\nbarre d'adresse de votre navigateur et appuyer sur la touche \"Entr=C3=A9e=\n\" de\nvotre clavier.\n\n\n\n- - - - - - - - - - - - - - - - -=20\n\nhttp://app.viralmarket.fr/v/?camp=3D8145198789_64&ms=3DbGF1cmVudEBicmFpbn=\nN0b3JtLmZy\n\n- - - - - - - - - - - - - - - - -=20\n\n\n\n\n--Part1_486f25ab0314944bf23d1de5c72153d8\nContent-Type: text/html; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://ww=\nw.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=3D\"http://www.w3.org/1999/xhtml\" xml:lang=3D\"fr\" lang=3D\"fr\">\n<head>\n<meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3Dutf-8\" =\n/>\n<meta name=3D\"viewport\" content=3D\"width=3Ddevice-width, initial-scale=3D=\n1.0\" />\n<title>Donnez un aspect neuf =C3=A0 votre maison</title>\n</head><body marginheight=3D\"0\" style=3D\"-webkit-text-size-adjust:none; -=\nmoz-text-size-adjust:none; margin:0px; padding:0px; -ms-text-size-adjust:=\nnone; text-size-adjust:none;\" leftmargin=3D\"0\" topmargin=3D\"0\" marginwidt=\nh=3D\"0\">\n<center><p>\n<a href=3D\"http://app.viralmarket.fr/v/?camp=3D8145198789_64&ms=3DbGF1cmV=\nudEBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Si cet e-mail ne s'affi=\nche pas correctement, suivez ce lien.\" style=3D\"color:#00F; font-family:'=\nArial'; font-size:12px; text-decoration:underline;\">Si cet email ne s&rsq=\nuo;affiche pas correctement, suivez ce lien.</a></p>                     =\n    <center> <table id=3D\"Tableau_01\" border=3D\"0\" cellpadding=3D\"0\" cell=\nspacing=3D\"0\" width=3D\"600\"> \t<tbody><tr> \t\t<td> \t\t\t<a href=3D\"http://app=\n.viralmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D8145198789_64&rc=3D=\naHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL01UUXhOMTk4WHpNd09GO=\nThYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y21WaFkzUnBiMj=\nR0YUdGaWFYUmhkQzVqYjIwdg=3D=3D\" target=3D\"_blank\"><img src=3D\"http://app.=\nviralmarket.fr/r/?rc=3DaHR0cDovL3Jlbm92YXRpb24ucmVhY3Rpb24taGFiaXRhdC5jb2=\n0vbWFpbGluZy9yZW5vdmF0aW9uL0FtZW5hZ2VtZW50LWludGVyaWV1cl8wMS5qcGc=3D\" alt=\n=3D\"\" style=3D\"border:0;\" height=3D\"55\" width=3D\"167\"></a></td> \t\t<td col=\nspan=3D\"3\" style=3D\"font-siz\n e:16px; font-family:Arial, Helvetica, sans-serif; color:#FFF;\" align=3D\"=\ncenter\" bgcolor=3D\"#354C55\" height=3D\"55\" width=3D\"433\"><a href=3D\"http:/=\n/app.viralmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D8145198789_64=\n&rc=3DaHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL01UUXhOMTk4WHp=\nNd09GOThYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y21WaFkz=\nUnBiMjR0YUdGaWFYUmhkQzVqYjIwdg=3D=3D\" style=3D\"font-size:16px; font-famil=\ny:Arial, Helvetica, sans-serif; color:#FFF; text-decoration:none;\" target=\n=3D\"_blank\">Am&eacute;nagement int&eacute;rieur et r&eacute;novation</a><=\n/td> \t</tr> \t<tr> \t\t<td colspan=3D\"4\"> \t\t\t\t<a href=3D\"http://app.viralmar=\nket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D8145198789_64&rc=3DaHR0cDo=\nvL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL01UUXhOMTk4WHpNd09GOThYelV3=\nWDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y21WaFkzUnBiMjR0YUdGa=\nWFYUmhkQzVqYjIwdg=3D=3D\" target=3D\"_blank\"><img src=3D\"http://app.viralma=\nrket.fr/r/?rc=3DaHR0cDovL3Jlbm92YXRpb24ucmVhY3Rpb24taGFiaXRhdC5jb20vbWFpb=\nGluZy9yZW5vdmF0aW9uL\n 0FtZW5hZ2VtZW50LWludGVyaWV1cl8wMy5qcGc=3D\" alt=3D\"\" style=3D\"border:0;\" =\nborder=3D\"0\" height=3D\"323\" width=3D\"600\"></a></td> \t</tr> \t<tr> \t\t<td co=\nlspan=3D\"4\" align=3D\"center\" bgcolor=3D\"#F6F6F6\" width=3D\"600\"><a href=3D=\n\"http://app.viralmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D814519=\n8789_64&rc=3DaHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL01UUXhO=\nMTk4WHpNd09GOThYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y=\n21WaFkzUnBiMjR0YUdGaWFYUmhkQzVqYjIwdg=3D=3D\" style=3D\"text-decoration:non=\ne;\" target=3D\"_blank\"><table cellpadding=3D\"20\" cellspacing=3D\"0\"><tbody>=\n<tr><td style=3D\"color:#354c55;font-size:24px; font-family:Arial, Helveti=\nca, sans-serif;\">Am&eacute;nagez votre int&eacute;rieur et valorisez vos =\nespaces<hr style=3D\"background-color:#ccc;\"></td></tr></tbody></table></a=\n></td> \t</tr> \t<tr> \t\t<td colspan=3D\"4\" align=3D\"center\" bgcolor=3D\"#F6F6=\nF6\" width=3D\"600\"><a href=3D\"http://app.viralmarket.fr/s/?m=3DbGF1cmVudEB=\nicmFpbnN0b3JtLmZy&c=3D8145198789_64&rc=3DaHR0cDovL2FmZmlsaWF0aW9uLm5lby1k=\nZXZpcy5jb20vYWYvY2xjL01UUXhOMTk4WHpN\n d09GOThYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y21WaFkz=\nUnBiMjR0YUdGaWFYUmhkQzVqYjIwdg=3D=3D\" style=3D\"text-decoration:none;\" tar=\nget=3D\"_blank\"><table cellpadding=3D\"20\" cellspacing=3D\"0\"><tbody><tr><td=\n style=3D\"color:#555555;font-size:16px; font-family:Arial, Helvetica, san=\ns-serif;\">Vous venez d'acqu&eacute;rir une maison o&ugrave; tout est &agr=\nave; refaire ? Votre foyer a &eacute;volu&eacute; et les enfants ont quit=\nt&eacute; le nid ? Ou tout simplement, vous avez envie de vous faire plai=\nsir avec un nouvel &eacute;quipement &agrave; la pointe ?<br> <strong>Il =\nest temps de r&eacute;am&eacute;nager votre int&eacute;rieur !</strong></=\ntd></tr></tbody></table></a></td> \t</tr> \t<tr> \t\t<td colspan=3D\"4\"> \t\t\t<a=\n href=3D\"http://app.viralmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D=\n8145198789_64&rc=3DaHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL0=\n1UUXhOMTk4WHpNd09GOThYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnB=\niMjR1Y21WaFkzUnBiMjR0YUdGaWFYUmhkQzVqYjIwdg=3D=3D\" target=3D\"_blank\"><img=\n src\n =3D\"http://app.viralmarket.fr/r/?rc=3DaHR0cDovL3Jlbm92YXRpb24ucmVhY3Rpb2=\n4taGFiaXRhdC5jb20vbWFpbGluZy9yZW5vdmF0aW9uL0FtZW5hZ2VtZW50LWludGVyaWV1cl8=\nwNi5qcGc=3D\" alt=3D\"\" style=3D\"border:0;\" height=3D\"86\" width=3D\"600\"></a=\n></td> \t</tr> \t<tr> \t\t<td colspan=3D\"4\" bgcolor=3D\"#F6F6F6\"> \t\t\t<a href=3D=\n\"http://app.viralmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D814519=\n8789_64&rc=3DaHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL01UUXhO=\nMTk4WHpNd09GOThYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y=\n21WaFkzUnBiMjR0YUdGaWFYUmhkQzVqYjIwdg=3D=3D\" target=3D\"_blank\"><img src=3D=\n\"http://app.viralmarket.fr/r/?rc=3DaHR0cDovL3Jlbm92YXRpb24ucmVhY3Rpb24taG=\nFiaXRhdC5jb20vbWFpbGluZy9yZW5vdmF0aW9uL0FtZW5hZ2VtZW50LWludGVyaWV1cl8wNy5=\nwbmc=3D\" alt=3D\"\" style=3D\"border:0;\" height=3D\"34\" width=3D\"600\"></a></t=\nd> \t</tr> \t<tr> \t\t<td colspan=3D\"4\" bgcolor=3D\"#F6F6F6\" width=3D\"600\"><a =\nhref=3D\"http://app.viralmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D=\n8145198789_64&rc=3DaHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL0=\n1UUXhOMTk4WHpNd09GOThYelV3WDN4Zk1\n GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y21WaFkzUnBiMjR0YUdGaWFYUmh=\nkQzVqYjIwdg=3D=3D\" target=3D\"_blank\"><table cellpadding=3D\"20\" cellspacin=\ng=3D\"0\"><tbody><tr><td style=3D\"color:#354c55;font-size:24px; font-family=\n:Arial, Helvetica, sans-serif;\">Donnez un aspect neuf &agrave; votre mais=\non<hr style=3D\"background-color:#ccc;\"></td></tr></tbody></table></a></td=\n> \t</tr> \t<tr> \t\t<td colspan=3D\"4\" bgcolor=3D\"#F6F6F6\" width=3D\"600\"><a h=\nref=3D\"http://app.viralmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D=\n8145198789_64&rc=3DaHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL0=\n1UUXhOMTk4WHpNd09GOThYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnB=\niMjR1Y21WaFkzUnBiMjR0YUdGaWFYUmhkQzVqYjIwdg=3D=3D\" target=3D\"_blank\"><tab=\nle cellpadding=3D\"20\" cellspacing=3D\"0\"><tbody><tr><td style=3D\"color:#55=\n5555;font-size:16px; font-family:Arial, Helvetica, sans-serif;\">La r&eacu=\nte;novation d'un appartement ou d'une maison est un projet d'envergure. U=\nne maison ancienne ou un appartement vieillissant m&eacute;ritent une rem=\nise &agrave; neu\n f avec un nouvel am&eacute;nagement.</td></tr></tbody></table></a></td> =\n\t</tr> \t<tr> \t\t<td colspan=3D\"2\" bgcolor=3D\"#354C55\" height=3D\"240\" width=\n=3D\"300\"><a href=3D\"http://app.viralmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0=\nb3JtLmZy&c=3D8145198789_64&rc=3DaHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb=\n20vYWYvY2xjL01UUXhOMTk4WHpNd09GOThYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0=\npsYm05MllYUnBiMjR1Y21WaFkzUnBiMjR0YUdGaWFYUmhkQzVqYjIwdg=3D=3D\" style=3D\"=\ntext-decoration:none;\" target=3D\"_blank\"><table cellpadding=3D\"10\" cellsp=\nacing=3D\"0\"><tbody><tr><td style=3D\"color:#FFF;font-size:16px; font-famil=\ny:Arial, Helvetica, sans-serif;\">Notre service se tient &agrave; vos c&oc=\nirc;t&eacute;s pour vous aider dans la gestion de vos projets d'am&eacute=\n;nagements professionnels, en vous fournissant :<br> - des produits au me=\nilleur prix,<br> - des remises professionnelles,<br> - une livraison dans=\n les meilleurs d&eacute;lais<br> - une r&eacute;ponse sous 48h<br> - des =\nconseils dans le choix des produits    et dans l'am&eacute;nagement d'esp=\naces</\n td></tr></tbody></table></a></td> \t\t<td> \t\t\t<a href=3D\"http://app.viralm=\narket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D8145198789_64&rc=3DaHR0c=\nDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL01UUXhOMTk4WHpNd09GOThYel=\nV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y21WaFkzUnBiMjR0YUd=\nGaWFYUmhkQzVqYjIwdg=3D=3D\" target=3D\"_blank\"><img src=3D\"http://app.viral=\nmarket.fr/r/?rc=3DaHR0cDovL3Jlbm92YXRpb24ucmVhY3Rpb24taGFiaXRhdC5jb20vbWF=\npbGluZy9yZW5vdmF0aW9uL0FtZW5hZ2VtZW50LWludGVyaWV1cl8xMS5wbmc=3D\" alt=3D\"\"=\n height=3D\"240\" width=3D\"1\"></a></td> \t\t<td> \t\t\t<a href=3D\"http://app.vir=\nalmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D8145198789_64&rc=3DaH=\nR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL01UUXhOMTk4WHpNd09GOTh=\nYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y21WaFkzUnBiMjR0=\nYUdGaWFYUmhkQzVqYjIwdg=3D=3D\" target=3D\"_blank\"><img src=3D\"http://app.vi=\nralmarket.fr/r/?rc=3DaHR0cDovL3Jlbm92YXRpb24ucmVhY3Rpb24taGFiaXRhdC5jb20v=\nbWFpbGluZy9yZW5vdmF0aW9uL0FtZW5hZ2VtZW50LWludGVyaWV1cl8xMi5qcGc=3D\" alt=3D=\n\"image d'une cuisine\" s\n tyle=3D\"border:0;\" height=3D\"240\" width=3D\"299\"></a></td> \t</tr> \t<tr> \t=\n\t<td colspan=3D\"4\" align=3D\"center\" height=3D\"106\" width=3D\"600\"><a href=3D=\n\"http://app.viralmarket.fr/s/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D814519=\n8789_64&rc=3DaHR0cDovL2FmZmlsaWF0aW9uLm5lby1kZXZpcy5jb20vYWYvY2xjL01UUXhO=\nMTk4WHpNd09GOThYelV3WDN4Zk1GOThYekElM0QvYUhSMGNEb3ZMM0psYm05MllYUnBiMjR1Y=\n21WaFkzUnBiMjR0YUdGaWFYUmhkQzVqYjIwdg=3D=3D\" style=3D\"text-decoration:non=\ne;\" target=3D\"_blank\"><table bgcolor=3D\"#AB4646\" cellpadding=3D\"10\" cells=\npacing=3D\"0\" width=3D\"300\"><tbody><tr><td style=3D\"color:#555555;font-siz=\ne:20px; font-family:Arial, Helvetica, sans-serif;color:#fff;\" align=3D\"ce=\nnter\">Comparez les offres</td></tr></tbody></table></a></td> \t</tr> \t<tr>=\n \t\t<td> \t\t\t<img src=3D\"http://app.viralmarket.fr/r/?rc=3DaHR0cDovL3Jlbm92=\nYXRpb24ucmVhY3Rpb24taGFiaXRhdC5jb20vbWFpbGluZy9yZW5vdmF0aW9uL3NwYWNlci5na=\nWY=3D\" alt=3D\"\" height=3D\"1\" width=3D\"167\"></td> \t\t<td> \t\t\t<img src=3D\"ht=\ntp://app.viralmarket.fr/r/?rc=3DaHR0cDovL3Jlbm92YXRpb24ucmVhY3Rpb24taGFia=\nXRhdC5jb20vbWFpbGluZy9yZW5vdmF0a\n W9uL3NwYWNlci5naWY=3D\" alt=3D\"\" height=3D\"1\" width=3D\"133\"></td> \t\t<td> =\n\t\t\t<img src=3D\"http://app.viralmarket.fr/r/?rc=3DaHR0cDovL3Jlbm92YXRpb24u=\ncmVhY3Rpb24taGFiaXRhdC5jb20vbWFpbGluZy9yZW5vdmF0aW9uL3NwYWNlci5naWY=3D\" a=\nlt=3D\"\" height=3D\"1\" width=3D\"1\"></td> \t\t<td> \t\t\t<img src=3D\"http://app.v=\niralmarket.fr/r/?rc=3DaHR0cDovL3Jlbm92YXRpb24ucmVhY3Rpb24taGFiaXRhdC5jb20=\nvbWFpbGluZy9yZW5vdmF0aW9uL3NwYWNlci5naWY=3D\" alt=3D\"\" height=3D\"1\" width=3D=\n\"299\"></td> \t</tr> </tbody></table> </center>                  <img src=3D=\n\"http://app.viralmarket.fr/t/?i=3D8145198789_64&m=3DbGF1cmVudEBicmFpbnN0b=\n3JtLmZy&url=3Dhttp://app.viralmarket.fr/images/blank.jpg\" alt=3D\"_pspacer=\n2\"  /><p>\n<a href=3D\"http://app.viralmarket.fr/d/?camp=3D8145198789_64&ms=3DbGF1cmV=\nudEBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Ne plus recevoir d'info=\nrmations de notre part\" style=3D\"color:#00F; font-family:'Arial'; font-si=\nze:12px; text-decoration:underline;\">Pour se d&eacute;sabonner : Suivez c=\ne lien.</a><br />Si ce message vous a caus&eacute; un quelconque d&eacute=\n;rangement, nous vous prions de nous en excuser.\n</p><center>\n</body>\n</html>\n\n\n\n--Part1_486f25ab0314944bf23d1de5c72153d8--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_375.argentina:2,",
    "content": "Return-Path: <contact@lonesome930.eu>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 7C5852211CF87\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 16:47:05 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 657ED3319CE4; Mon,  4 Apr 2016 16:47:05 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 4FCAF199B918; Mon,  4 Apr 2016 16:47:05 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.9 required=5.0 tests=BAYES_99,FROM_MISSP_DKIM,\n\tHK_FAKENAME_EBAY,HTML_IMAGE_RATIO_06,HTML_MESSAGE,MISSING_MID autolearn=no\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 HTML_IMAGE_RATIO_06 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.5 MISSING_MID Missing Message-Id: header\n\t*  2.9 HK_FAKENAME_EBAY From name mentions eBay, but not relayed from there\n\t*  0.0 FROM_MISSP_DKIM From misspaced, DKIM dependable\nX-Spam-DCC: : \nReceived: from vps.ichi943.eu (vps.ichi943.eu [93.113.206.122])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 4CF7D3319CE4\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 16:46:59 +0200 (CEST)\nDate: Mon, 04 Apr 2016 16:46:57 +0000\nFrom: Ebay<contact@lonesome930.eu>\nTo: dev@caliopen.local\nReply-to: Ebay<contact@lonesome930.eu>\nUser-Agent: PHPMailer 5.2.13\nMIME-Version: 1.0\nSubject: Ebay finalement pour les pros\nContent-Type: multipart/alternative;\n boundary=\"------------090707010608020608070505\"\nX-Spam-Prev-Subject: Ebay finalement pour les pros\nMessage-Id: <20160404144705.4FCAF199B918@mexico.brainstorm.fr>\nContent-Length: 17007\nLines: 233\n\nThis is a multi-part message in MIME format.\n--------------090707010608020608070505\nContent-Type: text/plain; charset=ISO-8859-1; format=flowed\nContent-Transfer-Encoding: 7bit\n\n\nPour consulter la version en ligne,c'est la\neBay\t \n\n \n\n5 raisons de vendre sur eBay\n\t1. Boutique eBay gratuite pendant 90 jours1\n90 jours d'abonnement gratuit � la Boutique A la Une pour vous laisser le temps de faire vos premiers b�n�fices et de tester les nombreux avantages d'eBay pour les Pros\t \n\t2. Un nombre illimit� d'annonces gratuites par mois sur eBay.fr2\nUn mod�le � la performance : aucun frais d'insertion et des commissions sur le prix de vente final uniquement si vous vendez3\t \n\t3. Des millions d'acheteurs potentiels � port�e de clic\nAvec une pr�sence dans 190 pays, eBay vous donne acc�s instantan�ment � plus de 159 millions d'acheteurs actifs. Pas besoin d'investir pour trouver des acheteurs, eBay le fait pour vous\t \n\t4. eBay, partenaire de votre succ�s, pas votre concurrent\neBay ne vend aucun produit. Vous et nous avons le m�me objectif : d�velopper vos ventes. A votre disposition : des solutions professionnelles de mise en vente et de gestion des commandes et plus encore\t \n\t5. Des solutions simples d'int�gration de votre flux produit\nVous vendez sur plusieurs canaux ? Aucun probl�me : nos partenaires e-commerce vous proposent des solutions peu on�reuses pour int�grer vos annonces sur eBay tout en synchronisant votre stock en temps r�el entre vos diff�rents canaux\t \n \n \tJe profite de l'offre\t \n \n \t1 Voir les conditions\n2 Uniquement pour les annonces au format Prix Fixe d'une dur�e de 30 jours ou de type � valide jusqu'� annulation � (jusqu'� �puisement du stock) \n3 Commissions comprises entre 3,9%HT et 6,5%HT selon votre cat�gorie de mise en vente\t \n\neBay\t \teBay mobile\tFacebook\tTwitter\tPinterest\n \n \tCopyright � 2016 eBay Inc. Tous droits r�serv�s. Les marques et marques commerciales mentionn�es appartiennent � leurs propri�taires respectifs.\t\n\nSi vous avez des questions, consultez nos conditions d'utilisation et notre Avis sur le respect de la vie priv�e.\t \nPour retirer votre adresse email,c'est la\n\n\n--------------090707010608020608070505\nContent-Type: text/html; charset=ISO-8859-1\nContent-Transfer-Encoding: 7bit\n\n<html><head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\"> \n<meta name=\"viewport\" content=\"width=device-width\" /> \n</head><body>\n<table width=\"600\" align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n    \n        <tr>\n            <td class=\"centpourcent\" valign=\"middle\" width=\"720\" align=\"center\" bgcolor=\"#ffffff\" height=\"50\"><span style=\"padding:8px;display:block\"><font style=\"font-size:11px;color:#57585a\" face=\"Arial\" color=\"#57585a\">Pour consulter la version en ligne,<a href=\"http://falke86.eu/ebay/rgdbc854.html\" style=\"text-decoration:underline;color:#2a7196\" target=\"_blank\">c'est la </a></font></span></td>\n        </tr>\n    \n</table>\n<table width=\"600\" align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n    \n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td bgcolor=\"#ffffff\"><a target=\"_blank\" style=\"font-family:Arial, Helvetica, sans-serif; text-decoration:none; color:#0074bc;\" href=\"http://falke86.eu/ebay/khjsp56.html\"><img alt=\"eBay\" id=\"Ebay_0316_r2_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r2_c1.jpg\" name=\"Ebay_0316_r2_c1\" style=\"display:block;\" width=\"154\" border=\"0\" height=\"60\"></a></td>\n                        <td width=\"446\" bgcolor=\"#ffffff\" height=\"60\">&nbsp;</td>\n                    </tr>\n                \n            </table>\n            </td>\n        </tr>\n        <tr>\n            <td bgcolor=\"#f1f1f1\"><img alt=\"\" id=\"Ebay_0316_r3_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r3_c1.jpg\" name=\"Ebay_0316_r3_c1\" style=\"display:block;\" width=\"600\" border=\"0\" height=\"6\"></td>\n        </tr>\n        <tr>\n            <td width=\"600\" bgcolor=\"#ffffff\" height=\"20\">&nbsp;</td>\n        </tr>\n        <tr>\n            <td bgcolor=\"#cccccc\"><a target=\"_blank\" style=\"font-family:Arial, Helvetica, sans-serif; text-decoration:none;\" href=\"http://www.linkandclick.com/scripts/click.php?a_aid=545cc9c59a5dd&amp;a_bid=n6hb0isd&amp;data1=aHR0cDovL3N1Y2Nlc3MuZWJheS5mci9hY3F1aXNpdGlvbi8_dXRtX3NvdXJjZT1yZWVkJTIwZGF0YSZ1dG1fbWVkaXVtPWVtYWlsJnV0bV9jYW1wYWlnbj1yZWVk\"><img alt=\"\" id=\"Ebay_0316_r5_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r5_c1.jpg\" name=\"Ebay_0316_r5_c1\" style=\"display:block;\" width=\"600\" border=\"0\" height=\"320\"></a></td>\n        </tr>\n        <tr>\n            <td width=\"600\" align=\"center\" bgcolor=\"#ffffff\" height=\"64\"><span style=\"font-family:Arial, Helvetica, sans-serif; font-size:26px; font-weight:normale; color:#0074bc;\">5 raisons de vendre sur eBay</span></td>\n        </tr>\n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td bgcolor=\"#ffffff\"><img alt=\"\" id=\"Ebay_0316_r7_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r7_c1.jpg\" name=\"Ebay_0316_r7_c1\" style=\"display:block;\" width=\"99\" border=\"0\" height=\"97\"></td>\n                        <td valign=\"top\" width=\"491\" bgcolor=\"#ffffff\" height=\"97\"><span style=\"font-family:Arial, Helvetica, sans-serif; font-size:13px; font-weight:normale; color:#666666;\"><strong style=\"color:#0074bc;\">1. Boutique eBay gratuite pendant 90 jours<sup>1</sup></strong><br>\n                        90 jours d'abonnement gratuit � la Boutique A la Une pour vous laisser le temps de faire vos premiers b�n�fices et de tester les nombreux avantages d'eBay pour les Pros</span></td>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"97\">&nbsp;</td>\n                    </tr>\n                \n            </table>\n            </td>\n        </tr>\n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td bgcolor=\"#ffffff\"><img alt=\"\" id=\"Ebay_0316_r8_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r8_c1.jpg\" name=\"Ebay_0316_r8_c1\" style=\"display:block;\" width=\"99\" border=\"0\" height=\"85\"></td>\n                        <td valign=\"top\" width=\"491\" bgcolor=\"#ffffff\" height=\"85\"><span style=\"font-family:Arial, Helvetica, sans-serif; font-size:13px; font-weight:normale; color:#666666;\"><strong style=\"color:#0074bc;\">2. Un nombre illimit� d'annonces gratuites par mois sur eBay.fr<sup>2</sup></strong><br>\n                        Un mod�le � la performance : aucun frais d'insertion et des commissions sur le prix de vente final uniquement si vous vendez<sup>3</sup></span></td>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"85\">&nbsp;</td>\n                    </tr>\n                \n            </table>\n            </td>\n        </tr>\n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td bgcolor=\"#ffffff\"><img alt=\"\" id=\"Ebay_0316_r9_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r9_c1.jpg\" name=\"Ebay_0316_r9_c1\" style=\"display:block;\" width=\"99\" border=\"0\" height=\"91\"></td>\n                        <td valign=\"top\" width=\"491\" bgcolor=\"#ffffff\" height=\"91\"><span style=\"font-family:Arial, Helvetica, sans-serif; font-size:13px; font-weight:normale; color:#666666;\"><strong style=\"color:#0074bc;\">3. Des millions d'acheteurs potentiels � port�e de clic</strong><br>\n                        Avec une pr�sence dans 190 pays, eBay vous donne acc�s instantan�ment � plus de 159 millions d'acheteurs actifs. Pas besoin d'investir pour trouver des acheteurs, eBay le fait pour vous</span></td>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"91\">&nbsp;</td>\n                    </tr>\n                \n            </table>\n            </td>\n        </tr>\n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td bgcolor=\"#ffffff\"><img alt=\"\" id=\"Ebay_0316_r10_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r10_c1.jpg\" name=\"Ebay_0316_r10_c1\" style=\"display:block;\" width=\"99\" border=\"0\" height=\"92\"></td>\n                        <td valign=\"top\" width=\"491\" bgcolor=\"#ffffff\" height=\"92\"><span style=\"font-family:Arial, Helvetica, sans-serif; font-size:13px; font-weight:normale; color:#666666;\"><strong style=\"color:#0074bc;\">4. eBay, partenaire de votre succ�s, pas votre concurrent</strong><br>\n                        eBay ne vend aucun produit. Vous et nous avons le m�me objectif : d�velopper vos ventes. A votre disposition : des solutions professionnelles de mise en vente et de gestion des commandes et plus encore</span></td>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"92\">&nbsp;</td>\n                    </tr>\n                \n            </table>\n            </td>\n        </tr>\n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td bgcolor=\"#ffffff\"><img alt=\"\" id=\"Ebay_0316_r11_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r11_c1.jpg\" name=\"Ebay_0316_r11_c1\" style=\"display:block;\" width=\"99\" border=\"0\" height=\"92\"></td>\n                        <td valign=\"top\" width=\"491\" bgcolor=\"#ffffff\" height=\"92\"><span style=\"font-family:Arial, Helvetica, sans-serif; font-size:13px; font-weight:normale; color:#666666;\"><strong style=\"color:#0074bc;\">5. Des solutions simples d'int�gration de votre flux produit</strong><br>\n                        Vous vendez sur plusieurs canaux ? Aucun probl�me : nos partenaires e-commerce vous proposent des solutions peu on�reuses pour int�grer vos annonces sur eBay tout en synchronisant votre stock en temps r�el entre vos diff�rents canaux</span></td>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"92\">&nbsp;</td>\n                    </tr>\n                \n            </table>\n            </td>\n        </tr>\n        <tr>\n            <td width=\"600\" bgcolor=\"#ffffff\" height=\"9\">&nbsp;</td>\n        </tr>\n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"30\">&nbsp;</td>\n                        <td bgcolor=\"#0272ba\"><a target=\"_blank\" style=\"font-family:Arial, Helvetica, sans-serif; text-decoration:none; color:#ffffff;\" href=\"http://www.linkandclick.com/scripts/click.php?a_aid=545cc9c59a5dd&amp;a_bid=n6hb0isd&amp;data1=aHR0cDovL3N1Y2Nlc3MuZWJheS5mci9hY3F1aXNpdGlvbi8_dXRtX3NvdXJjZT1yZWVkJTIwZGF0YSZ1dG1fbWVkaXVtPWVtYWlsJnV0bV9jYW1wYWlnbj1yZWVkJTIwZGF0YSUyMGJhdGltYXQ\"><img alt=\"Je profite de l&#39;offre\" id=\"Ebay_0316_r13_c2\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r13_c2.jpg\" name=\"Ebay_0316_r13_c2\" style=\"display:block;\" width=\"221\" border=\"0\" height=\"30\"></a></td>\n                        <td width=\"369\" bgcolor=\"#ffffff\" height=\"30\">&nbsp;</td>\n                    </tr>\n                \n            </table>\n            </td>\n        </tr>\n        <tr>\n            <td width=\"600\" bgcolor=\"#ffffff\" height=\"9\">&nbsp;</td>\n        </tr>\n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"115\">&nbsp;</td>\n                        <td width=\"580\" bgcolor=\"#ffffff\" height=\"115\"><span style=\"font-family:Arial, Helvetica, sans-serif; font-size:12px; font-weight:normale; color:#666666;\"><sup>1</sup> <a target=\"_blank\" style=\"font-family:Arial, Helvetica, sans-serif; text-decoration:none; color:#0074bc;\" href=\"http://www.linkandclick.com/scripts/click.php?a_aid=545cc9c59a5dd&amp;a_bid=n6hb0isd&amp;data1=aHR0cDovL3BhZ2VzLmViYXkuZnIvcHJvbW9zL3Rlcm1zZnJzdG9yZXRyaWFsM21vbnRocy8_X3Rya3NpZD1wMjA2MTE4OS5tMjIzNg\"><u>Voir les conditions</u></a><br>\n                        <sup>2</sup> Uniquement pour les annonces au format Prix Fixe d'une dur�e de 30 jours ou de type � valide jusqu'� annulation � (jusqu'� �puisement du stock) <br>\n                        <sup>3</sup> Commissions comprises entre 3,9%HT et 6,5%HT selon votre cat�gorie de mise en vente</span></td>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"115\">&nbsp;</td>\n                    </tr>\n                \n            </table>\n            </td>\n        </tr>\n        <tr>\n            <td bgcolor=\"#f1f1f1\"><img alt=\"\" id=\"Ebay_0316_r16_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r16_c1.jpg\" name=\"Ebay_0316_r16_c1\" style=\"display:block;\" width=\"600\" border=\"0\" height=\"6\"></td>\n        </tr>\n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td bgcolor=\"#ffffff\"><a target=\"_blank\" style=\"font-family:Arial, Helvetica, sans-serif; text-decoration:none; color:#0074bc;\" href=\"http://falke86.eu/ebay/khjsp56.html\"><img alt=\"eBay\" id=\"Ebay_0316_r17_c1\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r17_c1.jpg\" name=\"Ebay_0316_r17_c1\" style=\"display:block;\" width=\"107\" border=\"0\" height=\"52\"></a></td>\n                        <td width=\"360\" bgcolor=\"#ffffff\" height=\"52\">&nbsp;</td>\n                        <td bgcolor=\"#ffffff\"><a target=\"_blank\" style=\"font-family:Arial, Helvetica, sans-serif; text-decoration:none;\" href=\"http://falke86.eu/ebay/cxfdhjlm8528.html\"><img alt=\"eBay mobile\" id=\"Ebay_0316_r17_c7\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r17_c7.jpg\" name=\"Ebay_0316_r17_c7\" style=\"display:block;\" width=\"31\" border=\"0\" height=\"52\"></a></td>\n                        <td bgcolor=\"#ffffff\"><a target=\"_blank\" style=\"font-family:Arial, Helvetica, sans-serif; text-decoration:none;\" href=\"http://falke86.eu/ebay/etdk8552.html\"><img alt=\"Facebook\" id=\"Ebay_0316_r17_c8\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r17_c8.jpg\" name=\"Ebay_0316_r17_c8\" style=\"display:block;\" width=\"34\" border=\"0\" height=\"52\"></a></td>\n                        <td bgcolor=\"#ffffff\"><a target=\"_blank\" style=\"font-family:Arial, Helvetica, sans-serif; text-decoration:none;\" href=\"http://falke86.eu/ebay/invg510.html\"><img alt=\"Twitter\" id=\"Ebay_0316_r17_c9\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r17_c9.jpg\" name=\"Ebay_0316_r17_c9\" style=\"display:block;\" width=\"34\" border=\"0\" height=\"52\"></a></td>\n                        <td bgcolor=\"#ffffff\"><a target=\"_blank\" style=\"font-family:Arial, Helvetica, sans-serif; text-decoration:none;\" href=\"http://falke86.eu/ebay/zsfqom635.html\"><img alt=\"Pinterest\" id=\"Ebay_0316_r17_c10\" src=\"http://falke86.eu/ebay/images/Ebay_0316_r17_c10.jpg\" name=\"Ebay_0316_r17_c10\" style=\"display:block;\" width=\"34\" border=\"0\" height=\"52\"></a></td>\n                    </tr>\n                \n            </table>\n            </td>\n        </tr>\n        <tr>\n            <td width=\"600\" bgcolor=\"#cacaca\" height=\"1\">&nbsp;</td>\n        </tr>\n        <tr>\n            <td>\n            <table width=\"600\" align=\"left\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n                \n                    <tr>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"120\">&nbsp;</td>\n                        <td width=\"580\" bgcolor=\"#ffffff\" height=\"120\"><span style=\"font-family:Arial, Helvetica, sans-serif; font-size:12px; font-weight:normale; color:#666666;\">Copyright � 2016 eBay Inc. Tous droits r�serv�s. Les marques et marques commerciales mentionn�es appartiennent � leurs propri�taires respectifs.\t<br>\n                        <br>\n                        Si vous avez des questions, consultez nos <a style=\"color:#0064d2;\" target=\"_blank\" href=\"http://falke86.eu/ebay/ugbs58.html\"><u>conditions d'utilisation</u></a> et notre <a style=\"color:#0064d2;\" target=\"_blank\" href=\"http://falke86.eu/ebay/qxwtud854.html\"><u>Avis sur le respect de la vie priv�e</u></a>.</span></td>\n                        <td width=\"10\" bgcolor=\"#ffffff\" height=\"120\">&nbsp;</td>\n                    </tr>               \n            </table>\n\t\t\t\n            </td>\n        </tr>    \n</table>\n<table width=\"600\" style=\"margin:0 auto\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"> \n<tr> \n<td align=\"center\"> \n\n<span style=\"padding:8px;display:block\"> <font style=\"font-size:11px;color:#57585a\" face=\"Arial\" color=\"#57585a\"> Pour retirer votre adresse email,<a href=\"http://falke86.eu/ebay/fkjsm854.html\" style=\"color:#2a7196;text-decoration:none\" target=\"_blank\">c'est la</a></font> \n</span> \n</td> \n</tr> \n</table>\n<img alt=\"\" src=\"http://falke86.eu/ebay/images/ijfeby85.gif\" border=\"0\" height=\"1\" width=\"1\">\n</body></html>\n\n--------------090707010608020608070505--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_377.argentina:2,",
    "content": "Return-Path: <BloombergBusinessweek@ajollyfox.top>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 0650F20B21D01\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 15:15:18 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid EB942EE2FA5; Mon,  4 Apr 2016 15:15:17 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid DC154EE2FD8; Mon,  4 Apr 2016 15:15:17 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.4 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tINVALID_DATE,RCVD_IN_CSS,RCVD_IN_DNSWL_NONE,RDNS_NONE autolearn=no\n\tversion=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t* -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no\n\t*      trust\n\t*      [217.78.5.101 listed in list.dnswl.org]\n\t*  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n\t*      [217.78.5.101 listed in zen.spamhaus.org]\n\t*  1.1 INVALID_DATE Invalid Date: header (not RFC 2822)\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\nX-Spam-DCC: : \nReceived: from 006d46f1.ajollyfox.top (unknown [217.78.5.101])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id E8968331A60E\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 15:15:12 +0200 (CEST)\nReceived: from 006d46f1.cjyjo2d.ajollyfox.top ([127.0.0.1]:2712 helo=cjyjo2d.ajollyfox.top)\n\tby cjyjo2d.ajollyfox.top with ESMTP id 00LL6D46XCF1;\n\tfor <dev@caliopen.local>; Mon, 4 Apr 2016 15:15:11 00200\nDate: Mon, 4 Apr 2016 15:15:11 00200\nTo: <dev@caliopen.local>\nMessage-ID: <712323716087371216312396591@cjyjo2d.ajollyfox.top>\nFrom: \"Bloomberg Businessweek\" <BloombergBusinessweek@ajollyfox.top>\nSubject: Last Chance: Subscribe for ONLY_$5\nContent-Language: en-us\nMIME-Version: 1.0\nContent-Transfer-Encoding: 8bit\nContent-Type: multipart/alternative;\n\tboundary=\"----=Part.461.9762.1459775711\"\nX-Spam-Prev-Subject: Last Chance: Subscribe for ONLY_$5\nContent-Length: 11419\nLines: 284\n\n------=Part.461.9762.1459775711\nContent-Transfer-Encoding: 8bit\nContent-Type: text/plain; charset=\"UTF-8\"\n\n\nLast Chance: Subscribe for ONLY_$5\nhttp://www.ajollyfox.top/712-1-163-7160873/laurent/tindex28.html\n\n\n\n\n\n\n\nTo stop ads click here.\nhttp://www.ajollyfox.top/712-1-163-7160873/laurent/rindex18.html\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nfelt CYRATH over innumerable hit of dark wall for electric light switches — once I tumbled with a sort of splash upon the keys of a ghostly piano.\n\n\nThere was an inexplicable amount of dust everywhere, and the rooms were musty, as though they hadn't been aired for many days. I found the humidor on an VECD unfamiliar table, with\n\n\n\ntwo stale, dry cigarettes inside. 45cb6dc134a75d2fb5ad157adc22ebfc Throwing open the French windows of the drawing-room, we sat smoking out into the darkness. \"You ought to go away, \" I said. \"It's pretty certain\n\n\n\nthey'll trace your car.\" \"Go away now, old sport?\" \"Go to Atlantic City 45cb6dc134a75d2fb5ad157adc22ebfc for a week, or up to Montreal. \" He wouldn't consider it. He couldn't possibly leave Daisy\n\n\nuntil he knew what she was going to do. He was clutching at some last KKWWSUAH hope and I couldn't bear to shake him hi. It was this night that he told me the strange story 45cb6dc134a75d2fb5ad157adc22ebfc of\n\nhis youth with Dan Cody JYVAIU — told it to me because \"jay gatsby. \" had broken up like glhi against Tom's hard 45cb6dc134a75d2fb5ad157adc22ebfc MWMB malice, and the long secret extravaganza 45cb6dc134a75d2fb5ad157adc22ebfc was played out. KKFOLWP I think that he\n\n\nwould have acknowledged anything now, without reserve, but he wanted to talk about Daisy. She was the first \"nice\" girl he had ever 45cb6dc134a75d2fb5ad157adc22ebfc known. In\n\n\nvarious unrevealed capacities he had come in contact with such people, but always with indiscernible barbed 45cb6dc134a75d2fb5ad157adc22ebfc wire between. He\n\nfound her excitingly desirable. IGN He went to her house, at first with other officers from Camp Taylor, then alone. It amazed him — he had never\n\nbeen in such a beautiful house before, but what gave it an MJSYVPVPN air of breathless intensity, was that Daisy lived there — it was as casual a thing 45cb6dc134a75d2fb5ad157adc22ebfc to her as his tent out at camp was\n\n\nto him. There was a ripe mystery about it, 45cb6dc134a75d2fb5ad157adc22ebfc a hint of bedrooms up-stairs more beautiful and cool than other bedrooms, of hi YYYXW and BIRPF radiant activities taking place\n\nthrough its corridors, and of romances that were GQQTY not musty and laid 45cb6dc134a75d2fb5ad157adc22ebfc away already in lavender but fresh and breathing and redolent of 45cb6dc134a75d2fb5ad157adc22ebfc this year's shining motor-cars and of\n\ndances whose flowers were scarcely withered. It excited him, too, that many men 45cb6dc134a75d2fb5ad157adc22ebfc had already loved Daisy — it increased her value in his eyes. He felt their presence all about the\n\nhouse, pervading the air with the shades and echoes of still vibrant HFXYN emotions. But he 45cb6dc134a75d2fb5ad157adc22ebfc knew that he was in Daisy's house by a colossal\n\n\naccident. However 45cb6dc134a75d2fb5ad157adc22ebfc glorious might be his future as Jay Gatsby, he was at present a penniless young man without a past, and at any moment the invisible cloak of FMA OFAK his uniform might\n\nslip from his shoulders. So he made the most of his time. He took what he could get, XXCNYDOW ravenously and unscrupulously — eventually he took Daisy one still October night, APIUF took\n\nher because he had no real right to touch her hand. He might have despised NBYG himself, for he had certainly taken her 45cb6dc134a75d2fb5ad157adc22ebfc UXQD under false pretenses. I\n\ndon't mean that he had traded on his phantom millions, but he had deliberately given Daisy a sense of security; he lether believe that he was 45cb6dc134a75d2fb5ad157adc22ebfc .\n\n\n------=Part.461.9762.1459775711\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/html; charset=\"UTF-8\"\n\n<html>\n<head>\n<meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3Dutf-8\" />\n</head>\n<body>\n<center>\n<table style=3D\"width: 711px; background-color: #ffffff\">\n<tr>\n<td>\n<center>\n<p style=3D\"color: #000000; margin: 10px; font: 12px Times New Roman;\">Havi=\nng trouble view the offer at all? <a href=3D\"http://www.ajollyfox.top/712-1=\n-163-7160873/laurent/tindex22.html\">Please click me.</a>\n</p>\n</center>\n</td>\n</tr>\n\n\n\n<tr>\n<td style=3D\"padding: 10px;\">\n<center>\n<a target=3D\"\" href=3D\"http://www.ajollyfox.top/712-1-163-7160873/laurent/t=\nindex22.html\" style=3D\" font: 21px Tahoma;  margin: 20px auto;\" > Last Chan=\nce: Subscribe for ONLY_$5 </a>\n</center>\n</td>\n</tr>\n\n<tr>\n<td style=3D\"background-color:#ffffff; padding-top: 12px;\">\n<center>\n<a href=3D\"http://www.ajollyfox.top/712-1-163-7160873/laurent/tindex22.html=\n\"><img src=3D\"http://www.ajollyfox.top/712-1-163-7160873/i/img21163323.jpg\"=\n alt=3D\"Last Chance: Subscribe for ONLY_$5\" style=3D\"max-width: 570px; back=\nground-color: #ffffff; padding: 0px; border: 0px;\" ></a>\n</center>\n</td>\n</tr>\n\n\n<tr>\n<td>\n<center>\n<br><br>\n<a href=3D\"http://www.ajollyfox.top/712-1-163-7160873/laurent/rindex4.html\"=\n style=3D\"text-decoration:none;\">=20\n<img src=3D\"http://www.ajollyfox.top/712-1-163-7160873/i/img31163323.jpg\" a=\nlt=3D\"To optout click here.\" /></a>\n</a>\n<br><br><br><br><br><br>\n<a href=3D\"http://www.ajollyfox.top/712-1-163-7160873/laurent/u.html\" style=\n=3D\"text-decoration:none;\">=20\n<img src=3D\"http://www.ajollyfox.top/712-1-163-7160873/i/img41163323.jpg\" a=\nlt=3D\"To stop ads click here.\" /></a>\n</a>\n</center>\n</td>\n</tr>\n\n\n\n<TR><TD></TD></TR><TR><TD></TD></TR><TR><TD></TD></TR><TR><TD></TD></TR><TR=\n><TD></TD></TR><TR><TD></TD></TR>\n<table style=3D\"width: 711px; background-color: #FFFFFF; border: 10px solid=\n white;\">\n<tr>\n<td>\n<div id=3D\"random\" style=3D\"color: #FFFFFF; font: Courier 4pt;\">\n<br>\n<br>\n<p align=3D\"right\">\nfelt  XPQDDR over  innumerable  hit  of  dark  wall  for  electric light sw=\nitches =E2=80=94 once I tumbled with a sort of splash upon the  keys  of  a=\n  ghostly  piano. </p>\n<BR /><BR />\n<p align=3D\"right\" style=3D\"font: 16px;\">\nThere was an inexplicable amount of dust everywhere, and the rooms  were  m=\nusty,   as  though they hadn't been aired for many days. I found the humido=\nr  on  an  RITF unfamiliar  table,   with </p>\n<BR />\n<p align=3D\"right\" style=3D\"font: 14px;\"></p>\n<BR /><BR /><span style=3D\"font-family: Tahoma, sans-serif, Times New Roman=\n, Arial; font-size: 7px; color: #ffffff;\"></span>\n<p>two stale, dry cigarettes inside. 45cb6dc134a75d2fb5ad157adc22ebfc Throw=\ning open the French windows of  the  drawing-room,   we sat smoking out int=\no the darkness. \"You ought to go  away, \"  I  said.   \"It's  pretty  certai=\nn </p>\n<BR />\n<p align=3D\"left\"></p>\n<BR /><BR /><span style=3D\"font-family: Arial, Helvetica, sans-serif;\"></sp=\nan>\n<p align=3D\"right\" style=3D\"font: 14px;\">they'll trace your car.\" \"Go away =\nnow, old sport?\" \"Go to Atlantic City 45cb6dc134a75d2fb5ad157adc22ebfc for =\n a  week,   or  up  to  Montreal. \" He wouldn't consider it. He couldn't  p=\nossibly  leave  Daisy </p>\n<BR /><BR />\n<p align=3D\"right\" style=3D\"font: 16px;\">\nuntil he knew what she was going to do. He was  clutching  at  some  last I=\nTJVTNKQ  hope  and  I couldn't bear to shake him hi. It was this night that=\n he  told  me  the  strange  story  45cb6dc134a75d2fb5ad157adc22ebfc of </p=\n>\n<BR />\n<p align=3D\"center\">\nhis youth with Dan Cody PCBKKP =E2=80=94 told it to me because  \"jay  gatsb=\ny. \"  had  broken  up  like  glhi against Tom's hard 45cb6dc134a75d2fb5ad15=\n7adc22ebfc OJXL malice, and the long secret extravaganza 45cb6dc134a75d2fb5=\nad157adc22ebfc was played out. JOOJFIN  I  think  that  he </p>\n<BR /><BR /><span style=3D\"font-family: Helvetica, Arial, sans-serif; font-=\nsize: 9px;\"></span>\n<p align=3D\"left\">\nwould have acknowledged anything now,  without  reserve,   but  he  wanted =\n to  talk about Daisy. She was the  first  \"nice\"  girl  he  had  ever  45c=\nb6dc134a75d2fb5ad157adc22ebfc known.   In </p>\n<BR /><BR /><span style=3D\"font-family: Helvetica, Arial, sans-serif; font-=\nsize: 8px; color: #ffffff;\"></span>\n<p align=3D\"left\">various unrevealed capacities he had come  in  contact  w=\nith  such  people,   but  always    with indiscernible barbed 45cb6dc134a75=\nd2fb5ad157adc22ebfc wire between. He </p>\n<BR /><span style=3D\"font-family: Arial, Helvetica, sans-serif;\"></span>\n<p align=3D\"center\" style=3D\"font: 14px;\">\nfound her excitingly desirable. KCH He went to  her  house,   at first with=\n other officers from Camp Taylor, then alone.  It  amazed  him  =E2=80=94  =\nhe  had  never </p>\n<BR /><span style=3D\"font-family: Helvetica, Arial, sans-serif; font-size: =\n7px;\"></span>\n<p>\nbeen in such a beautiful house before, but what gave it an UVMEPWCJM  air  =\nof  breathless  intensity, was that Daisy lived there =E2=80=94 it was as c=\nasual a thing 45cb6dc134a75d2fb5ad157adc22ebfc to her as his tent  out  at =\n camp  was </p>\n<BR /><BR />\n<p align=3D\"center\">\nto him. There was a ripe mystery  about  it,  45cb6dc134a75d2fb5ad157adc22e=\nbfc  a  hint  of  bedrooms  up-stairs  more beautiful and cool than other b=\nedrooms,   of  hi NDCRE  and  NCVEN radiant  activities  taking  place </p>\n<BR />\n<p>\nthrough its corridors, and of romances that were WPYLH  not  musty  and  la=\nid  45cb6dc134a75d2fb5ad157adc22ebfc away  already  in lavender but fresh a=\nnd breathing and redolent  of  45cb6dc134a75d2fb5ad157adc22ebfc this  year'=\ns  shining  motor-cars  and  of </p>\n<BR /><span style=3D\"font-family: sans-serif, Helvetica, Arial;\"></span>\n<p align=3D\"center\">\ndances whose flowers were scarcely withered. It excited him, too, that  man=\ny  men  45cb6dc134a75d2fb5ad157adc22ebfc had  already loved Daisy =E2=80=94=\n it increased her value in his eyes. He felt  their  presence  all  about  =\nthe </p>\n<BR />\n<p>\nhouse, pervading the air with the  shades  and  echoes  of   still    vibra=\nnt  JSPWD   emotions. But he 45cb6dc134a75d2fb5ad157adc22ebfc knew that he =\nwas  in  Daisy's  house  by  a  colossal </p>\n<BR /><BR />\n<p align=3D\"center\">\naccident. However 45cb6dc134a75d2fb5ad157adc22ebfc glorious might be  his  =\nfuture  as  Jay  Gatsby,   he  was  at  present   a penniless young man wit=\nhout a past, and at any moment the  invisible  cloak  of REY PTFF  his  uni=\nform  might </p>\n<BR /><span style=3D\"font-family: sans-serif, Helvetica, Arial; font-size: =\n10px;\"></span>\n<p align=3D\"left\" style=3D\"font: 11px;\">\nslip from his shoulders. So he made the most of his time.   He  took  what =\n he  could  get, SHRFWWHP ravenously and unscrupulously =E2=80=94 eventuall=\ny he took Daisy  one  still  October  night,   DWRAD took </p>\n<BR /><span style=3D\"font-family: Arial, Helvetica, sans-serif;\"></span>\n<p align=3D\"right\" style=3D\"font: 14px;\">\nher because he had no real right to touch her hand. He might  have  despise=\nd  GWVH himself,   for  he  had  certainly taken her 45cb6dc134a75d2fb5ad15=\n7adc22ebfc KDQG under false pretenses. I </p>\n<BR />\n<p align=3D\"right\">\ndon't mean that he  had <B>traded  on  his  phantom  millions, but he had d=\neliberately given Daisy a sense of security; he let</B>her believe  that  h=\ne  was  45cb6dc134a75d2fb5ad157adc22ebfc=20\n.</p>\n</div>\n</td>\n</tr>\n</table>\n<tr><td></td></tr><tr><td></td></tr><tr><td></td></tr>\n</table>\n</center>\n\n</body>\n</html>\n\n------=Part.461.9762.1459775711--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_379.argentina:2,",
    "content": "Return-Path: <justine_fulton@m5sp.it>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id D9CB720AEF844\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 13:18:29 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid CD46B628433; Mon,  4 Apr 2016 13:18:29 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid C11F1FBE117; Mon,  4 Apr 2016 13:18:29 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.2 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,MSGID_RANDY autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  2.6 MSGID_RANDY Message-Id has pattern used in spam\nX-Spam-DCC: : \nReceived: from m5sp.it (host219-21-static.238-77-b.business.telecomitalia.it [77.238.21.219])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 26B06634BE4\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 13:18:24 +0200 (CEST)\nDate: Mon, 4 Apr 2016 11:18:22 +0000\nFrom: \"Justine Fulton\" <justine_fulton@m5sp.it>\nReply-To: \"Justine Fulton\" <pretty@wertymail.com>\nMessage-ID: <7fb65ac-9c081-4f@m5sp.it>\nTo: dev@caliopen.local\nSubject: Hi\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject:  Hi\nContent-Length: 216\nLines: 2\n\n\n<div>Hi, I am a single adorable puss, searching a good guy to have a dating with ;=) <br> Where are you from, do you speak english? <br> <br> please reply me if you would like to see photos of me. Good bye :)</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_381.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1al8a7-0000Az-Vp\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 01:22:16 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:48497)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <somasissounds@gmail.com>) id 1al8a5-00006J-R1\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 01:22:14 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <somasissounds@gmail.com>) id 1al8a2-000306-KA\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 01:22:13 -0400\nReceived: from mail-qg0-x243.google.com ([2607:f8b0:400d:c04::243]:35445)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <somasissounds@gmail.com>) id 1al8a2-000300-G2\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 01:22:10 -0400\nReceived: by mail-qg0-x243.google.com with SMTP id b32so2794769qgf.2\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 22:22:10 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:date:message-id;\n\tbh=qsPFBfAmZZRn5ZrDEyY2WtgVr/b5Tg2HAB/I/+79yfc=;\n\tb=HVrTwiT1Krg9ZVNY0v07UBqKAX2A6XsSM6AQlcdWnXUGsmhr12L7C0LdrxbtIMFlRc\n\t/CYDvn9OItzzqWqIRlg6XquMCYrk3/0p1ozM2IcdkK2jRewHtfKyUo0Jw5XZcwbhria1\n\tB7yWJVr9HId+OxEHjMSFUk1JVnIpyXTpRVJwty0N7tWa3/Etm11yrKQWCL/i5/CY7ZXj\n\tp1upL2xhkldd1VeTfCLRNdnJFEuuRqNdzhizFjdwokZoWHVeSNvuAB671auPqBZqNjHV\n\tL8/CtHC6oMI8+ZDsaKeyM07cCxuAd4+DLyworVac5jcmuxpbe0uOppFcWfTRBSizf8rR\n\tAvQw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:date:message-id;\n\tbh=qsPFBfAmZZRn5ZrDEyY2WtgVr/b5Tg2HAB/I/+79yfc=;\n\tb=KjQbBJgO+0dDGrkkUobOjMx+UujmZrjxWLD9AHwKHEREgoYOHYXcF0n9qmuCltxjhg\n\tTrmhF12ENAkxM8+TorI7J0IyL6veJnB5cRho2p1YJsmhQ/t974E/L3YzQHvGqM1UQHM/\n\tIM6f6ep88KZvOhfMeK4TRwPsq6h7QFMcRTeTmtdh6+/JMoUz/9wvqjM8zH6JAGhkstCw\n\tsnpYrsRCocMkwKSErHCRb47KSydWM5gUHj7EdhEY4FN495IkfQqnM3kDoCHcWJ79kHFt\n\tJvoOXbuTOrH2Fr8ciJPbTi7WJ8vF/A1jxdkuJxI9etVuuwTSQmeoRNplK6c0us9IJJVz\n\tuIQQ==\nX-Gm-Message-State: AD7BkJIg9BHpc41r4gab9BZGbB8AJBUuX/wg+xzULEl2iBHiWGnrvLPJCPwYWgTMRCt6vw==\nX-Received: by 10.140.223.17 with SMTP id t17mr7711844qhb.65.1459315329947;\n\tTue, 29 Mar 2016 22:22:09 -0700 (PDT)\nReceived: from seneca.local (cpe-65-184-25-62.carolina.res.rr.com.\n\t[65.184.25.62]) by smtp.gmail.com with ESMTPSA id\n\tz65sm1020501qhb.36.2016.03.29.22.22.09\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128);\n\tTue, 29 Mar 2016 22:22:09 -0700 (PDT)\nFrom: Kylie McClain <somasissounds@gmail.com>\nTo: dev@caliopen.local\nSubject: [PATCH] src/print.c: Check for __GLIBC__ rather than GNU_LINUX\nDate: Wed, 30 Mar 2016 01:22:08 -0400\nMessage-Id: <1459315328-3663-1-git-send-email-somasissounds@gmail.com>\nX-Mailer: git-send-email 2.7.4\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:400d:c04::243\nCc: Kylie McClain <somasis@exherbo.org>, somasissounds@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 05:22:14 -0000\nContent-Length: 594\nLines: 25\n\nFrom: Kylie McClain <somasis@exherbo.org>\n\nNot all Linux libcs (ex. musl libc) support the functionality used\nunder this #ifdef.\n---\n src/print.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/src/print.c b/src/print.c\nindex 2b53d75..331f3c4 100644\n--- a/src/print.c\n+++ b/src/print.c\n@@ -775,7 +775,7 @@ debug_output_compilation_hack (bool x)\n   print_output_debug_flag = x;\n }\n \n-#if defined (GNU_LINUX)\n+#if defined (__GLIBC__)\n \n /* This functionality is not vitally important in general, so we rely on\n    non-portable ability to use stderr as lvalue.  */\n-- \n2.7.4\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_383.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aljLc-000892-Vz\n\tfor mharc-dev@caliopen.local; Thu, 31 Mar 2016 16:37:44 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57679)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1aljLa-00088l-CD\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 16:37:43 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1aljLW-0005om-Bi\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 16:37:42 -0400\nReceived: from zimbra.cs.ucla.edu ([131.179.128.68]:39421)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1aljLW-0005o0-5n\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 16:37:38 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 06647160FF0;\n\tThu, 31 Mar 2016 13:37:37 -0700 (PDT)\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032)\n\twith ESMTP id UEqLJVPHiQKJ; Thu, 31 Mar 2016 13:37:35 -0700 (PDT)\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 81263161235;\n\tThu, 31 Mar 2016 13:37:35 -0700 (PDT)\nX-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026)\n\twith ESMTP id su5Vo0e5vuYR; Thu, 31 Mar 2016 13:37:35 -0700 (PDT)\nReceived: from penguin.cs.ucla.edu (Penguin.CS.UCLA.EDU [131.179.64.200])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 67527160FF0;\n\tThu, 31 Mar 2016 13:37:35 -0700 (PDT)\nSubject: Re: [PATCH] src/print.c: Check for __GLIBC__ rather than GNU_LINUX\nTo: Kylie McClain <somasissounds@gmail.com>, emacs-devel@gnu.org\nReferences: <1459315328-3663-1-git-send-email-somasissounds@gmail.com>\nFrom: Paul Eggert <eggert@cs.ucla.edu>\nOrganization: UCLA Computer Science Department\nMessage-ID: <56FD8A8B.3040808@cs.ucla.edu>\nDate: Thu, 31 Mar 2016 13:37:31 -0700\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.7.1\nMIME-Version: 1.0\nIn-Reply-To: <1459315328-3663-1-git-send-email-somasissounds@gmail.com>\nContent-Type: text/plain; charset=utf-8; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 131.179.128.68\nCc: Kylie McClain <somasis@exherbo.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 31 Mar 2016 20:37:43 -0000\nContent-Length: 460\nLines: 12\n\nThanks for reporting that. I filed a bug report with a proposed patch here:\n\nhttp://debbugs.gnu.org/cgi/bugreport.cgi?bug=23173\n\nInstead of disabling redirect-debugging-output, this patch ports it to \nsystems like musl where stderr is not an lvalue. Please give it a try if \nyou have the time. it's against master commit \n750e1e19429cd781e2e60b462d19ef827d4da943.\n\nI haven't installed it into 'master' yet, to give Eli a heads-up about \nthe impending change.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_385.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1also8-00030R-9b\n\tfor mharc-dev@caliopen.local; Fri, 01 Apr 2016 02:43:48 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55784)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1also5-0002wE-DE\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 02:43:46 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1also1-0000bY-DG\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 02:43:45 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:51838)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1alsnq-0000Vt-Ea; Fri, 01 Apr 2016 02:43:30 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:2602\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1alsnp-0005gj-3C; Fri, 01 Apr 2016 02:43:29 -0400\nDate: Fri, 01 Apr 2016 09:43:19 +0300\nMessage-Id: <83shz5u8vs.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Paul Eggert <eggert@cs.ucla.edu>\nIn-reply-to: <56FD8A8B.3040808@cs.ucla.edu> (message from Paul Eggert on Thu, \n\t31 Mar 2016 13:37:31 -0700)\nSubject: Re: [PATCH] src/print.c: Check for __GLIBC__ rather than GNU_LINUX\nReferences: <1459315328-3663-1-git-send-email-somasissounds@gmail.com>\n\t<56FD8A8B.3040808@cs.ucla.edu>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local, somasis@exherbo.org, somasissounds@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 01 Apr 2016 06:43:46 -0000\nContent-Length: 975\nLines: 30\n\n> From: Paul Eggert <eggert@cs.ucla.edu>\n> Date: Thu, 31 Mar 2016 13:37:31 -0700\n> Cc: Kylie McClain <somasis@exherbo.org>\n> \n> I haven't installed it into 'master' yet, to give Eli a heads-up about \n> the impending change.\n\nThanks.  I needed the additional patch below to get the patch to\ncompile (how do other ports get F_DUPFD_CLOEXEC?).\n\nAfter that, temacs seems to hang during loadup.  Looks like it hangs\ninside the libc function 'close'.  I don't have enough free time now\nto do anything serious on master.  Hopefully, someone else will be\nable to look into this, find out why it hangs, and suggest a remedy.\n\n\n--- src/print.c~0\t2016-04-01 09:32:53.060500000 +0300\n+++ src/print.c\t2016-04-01 09:36:24.591750000 +0300\n@@ -38,6 +38,10 @@ along with GNU Emacs.  If not, see <http\n #include <float.h>\n #include <ftoastr.h>\n \n+#ifdef WINDOWSNT\n+#include <sys/socket.h>\t\t/* for F_DUPFD_CLOEXEC */\n+#endif\n+\n struct terminal;\n \n /* Avoid actual stack overflow in print.  */\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_387.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1altnN-0000JX-MH\n\tfor mharc-dev@caliopen.local; Fri, 01 Apr 2016 03:47:05 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:44619)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1altnL-0000JE-97\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 03:47:04 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1altnK-00056y-5z\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 03:47:03 -0400\nReceived: from zimbra.cs.ucla.edu ([131.179.128.68]:46008)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>)\n\tid 1altnE-00054Z-Hy; Fri, 01 Apr 2016 03:46:56 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id AC7601601D5;\n\tFri,  1 Apr 2016 00:46:55 -0700 (PDT)\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032)\n\twith ESMTP id CwbWMSnh4U-5; Fri,  1 Apr 2016 00:46:55 -0700 (PDT)\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 0FB5416120B;\n\tFri,  1 Apr 2016 00:46:55 -0700 (PDT)\nX-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026)\n\twith ESMTP id uKG90hbDaMZ2; Fri,  1 Apr 2016 00:46:54 -0700 (PDT)\nReceived: from [192.168.1.9] (unknown [100.32.155.148])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTPSA id E10BD1601D5;\n\tFri,  1 Apr 2016 00:46:54 -0700 (PDT)\nSubject: Re: [PATCH] src/print.c: Check for __GLIBC__ rather than GNU_LINUX\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <1459315328-3663-1-git-send-email-somasissounds@gmail.com>\n\t<56FD8A8B.3040808@cs.ucla.edu> <83shz5u8vs.fsf@gnu.org>\nFrom: Paul Eggert <eggert@cs.ucla.edu>\nOrganization: UCLA Computer Science Department\nMessage-ID: <56FE276C.2050306@cs.ucla.edu>\nDate: Fri, 1 Apr 2016 00:46:52 -0700\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <83shz5u8vs.fsf@gnu.org>\nContent-Type: text/plain; charset=utf-8; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 131.179.128.68\nCc: dev@caliopen.local, somasis@exherbo.org, somasissounds@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 01 Apr 2016 07:47:04 -0000\nContent-Length: 523\nLines: 13\n\nEli Zaretskii wrote:\n> Thanks.  I needed the additional patch below to get the patch to\n> compile (how do other ports get F_DUPFD_CLOEXEC?).\n\n<fcntl.h> defines it, perhaps via the gnulib replacement sourced in lib/fcntl.in.h.\n\n> After that, temacs seems to hang during loadup.  Looks like it hangs\n> inside the libc function 'close'.  I don't have enough free time now\n> to do anything serious on master.\n\nNo rush. When you get time, if you happen to know who's calling 'close' I could \nifdef that call out on MS-Windows.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_389.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amH2k-0000hW-N6\n\tfor mharc-dev@caliopen.local; Sat, 02 Apr 2016 04:36:30 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:45034)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amH2i-0000h5-9x\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 04:36:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amH2f-0003Lh-3Q\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 04:36:28 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:36080)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1amH2U-0003Gy-An; Sat, 02 Apr 2016 04:36:14 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4469\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1amH2T-0007Ty-9I; Sat, 02 Apr 2016 04:36:13 -0400\nDate: Sat, 02 Apr 2016 11:35:41 +0300\nMessage-Id: <83pou8s90i.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Paul Eggert <eggert@cs.ucla.edu>\nIn-reply-to: <56FE276C.2050306@cs.ucla.edu> (message from Paul Eggert on Fri, \n\t1 Apr 2016 00:46:52 -0700)\nSubject: Re: [PATCH] src/print.c: Check for __GLIBC__ rather than GNU_LINUX\nReferences: <1459315328-3663-1-git-send-email-somasissounds@gmail.com>\n\t<56FD8A8B.3040808@cs.ucla.edu> <83shz5u8vs.fsf@gnu.org>\n\t<56FE276C.2050306@cs.ucla.edu>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local, somasis@exherbo.org, somasissounds@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 02 Apr 2016 08:36:29 -0000\nContent-Length: 3553\nLines: 103\n\n> Cc: somasissounds@gmail.com, dev@caliopen.local, somasis@exherbo.org\n> From: Paul Eggert <eggert@cs.ucla.edu>\n> Date: Fri, 1 Apr 2016 00:46:52 -0700\n> \n> > After that, temacs seems to hang during loadup.  Looks like it hangs\n> > inside the libc function 'close'.  I don't have enough free time now\n> > to do anything serious on master.\n> \n> No rush. When you get time, if you happen to know who's calling 'close' I could \n> ifdef that call out on MS-Windows.\n\nIt turns out the call to dup2 with 2 identical arguments has a strange\nside effect with MS implementation: the subsequent attempt to fclose\nthe corresponding stdio stream hangs.  I was unable to understand why\nthat happens, since according to my references, such a dup2 call\nshould just validate the original file descriptor and do nothing else.\nBut since we already have a sys_dup2 function that wraps the MS dup2,\nI added a workaround there, see the patch below.\n\nFor the record, what init_standard_fds does is unnecessary on\nMS-Windows, since we already have the equivalent code in init_ntproc\n(for reasons explained in the comments there).  So an alternative\nwould be to make init_standard_fds a no-op for WINDOWSNT.  I preferred\nto have less #ifdef's in mainline Emacs code, but if you think the\nalternative is better, it's fine with me.\n\nBtw, are changes like the one below safe?\n\n     /* Manipulate tty.  */\n     if (hide_char)\n       {\n  -      etty_valid = emacs_get_tty (fileno (stdin), &etty) == 0;\n  +      etty_valid = emacs_get_tty (STDIN_FILENO, &etty) == 0;\n\t if (etty_valid)\n  -\tset_binary_mode (fileno (stdin), O_BINARY);\n  -      suppress_echo_on_tty (fileno (stdin));\n  +\tset_binary_mode (STDIN_FILENO, O_BINARY);\n  +      suppress_echo_on_tty (STDIN_FILENO);\n       }\n\nAre we sure fileno(stdin) will necessarily return 0, what with all the\nredirections and stuff?  Why not keep the original code?\n\nSo to sum it up, these additional changes are required for MS-Windows:\n\ndiff --git a/src/print.c b/src/print.c\nindex 2b53d75..ee60f57 100644\n--- a/src/print.c\n+++ b/src/print.c\n@@ -38,6 +38,10 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */\n #include <float.h>\n #include <ftoastr.h>\n \n+#ifdef WINDOWSNT\n+#include <sys/socket.h>\t\t/* for F_DUPFD_CLOEXEC */\n+#endif\n+\n struct terminal;\n \n /* Avoid actual stack overflow in print.  */\ndiff --git a/src/w32.c b/src/w32.c\nindex 3f4ac88..94aa7d8 100644\n--- a/src/w32.c\n+++ b/src/w32.c\n@@ -8181,17 +8181,33 @@ sys_dup2 (int src, int dst)\n       return -1;\n     }\n \n-  /* make sure we close the destination first if it's a pipe or socket */\n-  if (src != dst && fd_info[dst].flags != 0)\n+  /* MS _dup2 seems to have weird side effect when invoked with 2\n+     identical arguments: an attempt to fclose the corresponding stdio\n+     stream after that hangs (we do close standard streams in\n+     init_ntproc).  Attempt to avoid that by not calling _dup2 that\n+     way: if SRC is valid, we know that dup2 should be a no-op, so do\n+     nothing and return DST.  */\n+  if (src == dst)\n+    {\n+      if ((HANDLE)_get_osfhandle (src) == INVALID_HANDLE_VALUE)\n+\t{\n+\t  errno = EBADF;\n+\t  return -1;\n+\t}\n+      return dst;\n+    }\n+\n+  /* Make sure we close the destination first if it's a pipe or socket.  */\n+  if (fd_info[dst].flags != 0)\n     sys_close (dst);\n \n   rc = _dup2 (src, dst);\n   if (rc == 0)\n     {\n-      /* duplicate our internal info as well */\n+      /* Duplicate our internal info as well.  */\n       fd_info[dst] = fd_info[src];\n     }\n-  return rc;\n+  return rc == 0 ? dst : rc;\n }\n \n int\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_391.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an7d0-0000p8-OE\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 12:45:26 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52138)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1an7cy-0000l2-FM\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 12:45:25 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1an7cx-0003Cf-Nv\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 12:45:24 -0400\nReceived: from zimbra.cs.ucla.edu ([131.179.128.68]:56521)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>)\n\tid 1an7cs-0003Bd-8S; Mon, 04 Apr 2016 12:45:18 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 43D35161249;\n\tMon,  4 Apr 2016 09:45:17 -0700 (PDT)\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032)\n\twith ESMTP id HsSX5prQHfOu; Mon,  4 Apr 2016 09:45:16 -0700 (PDT)\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 94B68161251;\n\tMon,  4 Apr 2016 09:45:16 -0700 (PDT)\nX-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026)\n\twith ESMTP id cTlQv0FOviMv; Mon,  4 Apr 2016 09:45:16 -0700 (PDT)\nReceived: from penguin.cs.ucla.edu (Penguin.CS.UCLA.EDU [131.179.64.200])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 777A6161249;\n\tMon,  4 Apr 2016 09:45:16 -0700 (PDT)\nSubject: Re: [PATCH] src/print.c: Check for __GLIBC__ rather than GNU_LINUX\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <1459315328-3663-1-git-send-email-somasissounds@gmail.com>\n\t<56FD8A8B.3040808@cs.ucla.edu> <83shz5u8vs.fsf@gnu.org>\n\t<56FE276C.2050306@cs.ucla.edu> <83pou8s90i.fsf@gnu.org>\nFrom: Paul Eggert <eggert@cs.ucla.edu>\nOrganization: UCLA Computer Science Department\nMessage-ID: <57029A1C.4000609@cs.ucla.edu>\nDate: Mon, 4 Apr 2016 09:45:16 -0700\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.7.1\nMIME-Version: 1.0\nIn-Reply-To: <83pou8s90i.fsf@gnu.org>\nContent-Type: text/plain; charset=utf-8; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 131.179.128.68\nCc: dev@caliopen.local, somasis@exherbo.org, somasissounds@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 16:45:25 -0000\nContent-Length: 277\nLines: 8\n\nOn 04/02/2016 01:35 AM, Eli Zaretskii wrote:\n> I preferred\n> to have less #ifdef's in mainline Emacs code, but if you think the\n> alternative is better, it's fine with me.\n\nNo, I also like keeping the mainline code simpler. I installed the patch \nwith your additions; thanks.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_393.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amdLQ-0003pa-5B\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 04:25:16 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:43569)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1amdLO-0003pQ-GX\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 04:25:15 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1amdLN-0004XB-QJ\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 04:25:14 -0400\nReceived: from zimbra.cs.ucla.edu ([131.179.128.68]:47071)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>)\n\tid 1amdLJ-0004Wu-Cy; Sun, 03 Apr 2016 04:25:09 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id C95B8161244;\n\tSun,  3 Apr 2016 01:25:07 -0700 (PDT)\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032)\n\twith ESMTP id 7i_ghJhp17uA; Sun,  3 Apr 2016 01:25:07 -0700 (PDT)\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 10342161245;\n\tSun,  3 Apr 2016 01:25:07 -0700 (PDT)\nX-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026)\n\twith ESMTP id Zpr5g1J-DTsA; Sun,  3 Apr 2016 01:25:06 -0700 (PDT)\nReceived: from [192.168.1.9] (unknown [100.32.155.148])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTPSA id D72E2161244;\n\tSun,  3 Apr 2016 01:25:06 -0700 (PDT)\nSubject: Re: [PATCH] src/print.c: Check for __GLIBC__ rather than GNU_LINUX\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <1459315328-3663-1-git-send-email-somasissounds@gmail.com>\n\t<56FD8A8B.3040808@cs.ucla.edu> <83shz5u8vs.fsf@gnu.org>\n\t<56FE276C.2050306@cs.ucla.edu> <83pou8s90i.fsf@gnu.org>\nFrom: Paul Eggert <eggert@cs.ucla.edu>\nOrganization: UCLA Computer Science Department\nMessage-ID: <5700D353.40107@cs.ucla.edu>\nDate: Sun, 3 Apr 2016 01:24:51 -0700\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <83pou8s90i.fsf@gnu.org>\nContent-Type: text/plain; charset=utf-8; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 131.179.128.68\nCc: dev@caliopen.local, somasis@exherbo.org, somasissounds@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 08:25:15 -0000\nContent-Length: 434\nLines: 13\n\nEli Zaretskii wrote:\n\n> Are we sure fileno(stdin) will necessarily return 0, what with all the\n> redirections and stuff?\n\nYes.  The redirections in Emacs don't change the file descriptor numbers \nassociated with stdin, stdout, and stderr.  They will always be 0, 1, and 2.\n\n> Why not keep the original code?\n\nIt's a bit slower, and (more important) it leads the reader to wonder whether \nfileno (stdin) might not equal STDIN_FILENO.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_395.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alk7x-0006Rj-RT\n\tfor mharc-dev@caliopen.local; Thu, 31 Mar 2016 17:27:41 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42453)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <somasissounds@gmail.com>) id 1alk7u-0006Qy-Rc\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 17:27:39 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <somasissounds@gmail.com>) id 1alk7t-0001al-PS\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 17:27:38 -0400\nReceived: from mail-ob0-x233.google.com ([2607:f8b0:4003:c01::233]:35680)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <somasissounds@gmail.com>) id 1alk7t-0001af-Ja\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 17:27:37 -0400\nReceived: by mail-ob0-x233.google.com with SMTP id fp4so32656035obb.2\n\tfor <dev@caliopen.local>; Thu, 31 Mar 2016 14:27:36 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:from:date:message-id:subject:to\n\t:cc; bh=PHvkbMkNbH0rnM55uoOT+D0dTG8Md6zTJL1Z2NXq2CU=;\n\tb=XjZmLq7pSh5C979CM6vwk47NzkBoS3lb9K0XysIkc0HEoBnoAvT3cxJqAI6brF/la2\n\tQAfPyN7kiD+hqxarMjLBYrvftsyNmZG1f6AdR1E6RmhR+k6gzxk1NakH+wwFzQntEyqK\n\tyzqMlzW9BrHP2EzZke27E83fct14TKFT7hEJ6hAmYMjZinOh2cdWLqpS/6fEwMhTg1ra\n\tSS3y45EylVFzr7laRp1kxPtI5jZ0nUOJ6VP3xRV+oqPtfv2Fl5TeJAbe/S0Aq7L7OlRU\n\t+Bc1yzXXszsPVb4VDxZqS8dGuZY7/yok1zUmzJquRIKk/xxr4fain2W4rN4cJ8O/PgZ4\n\tzypg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:from:date\n\t:message-id:subject:to:cc;\n\tbh=PHvkbMkNbH0rnM55uoOT+D0dTG8Md6zTJL1Z2NXq2CU=;\n\tb=YKPmFazUFDHmpbe3MrbwRp2lXioApO9eB59E9nqRge+JfHDNFTJaMT7Zq3Iup0mzCo\n\t/z4Q+Ut6b6MQ87hg9d05Rv4HJJxSwELMNryLwcQpx1CGXwMihARwu2BgpetW14tbTUmT\n\tsPt1pqRFgrO6XQvJQv5Ikd/ZsRfHzRHOll0SUvyWJM8CnC7BabhLI85QWmnZh1awtKns\n\tyfu7mfYu4lYnh89oHAuly/cjOSjWqIT7MPN2qOKwqE8JSmQIVqEd9H58ERBIIHI2iG7p\n\tycalmfB1vqmfk5vdFdpC+cLb9LYRlRtONH5pUSgzJSOXFfMSC6yiE/LGSu3iLXKu2hTn\n\tGCTw==\nX-Gm-Message-State: AD7BkJKX6rOnfCqdRF7s3h/PQbkyd2o3SOOlmywgR7AnWgfWiMX8hWhbO4D/2v++0Oy663iMvUMksKkzdaNC/g==\nX-Received: by 10.60.161.144 with SMTP id xs16mr743871oeb.15.1459459655622;\n\tThu, 31 Mar 2016 14:27:35 -0700 (PDT)\nMIME-Version: 1.0\nReceived: by 10.157.12.221 with HTTP; Thu, 31 Mar 2016 14:26:56 -0700 (PDT)\nIn-Reply-To: <56FD8A8B.3040808@cs.ucla.edu>\nReferences: <1459315328-3663-1-git-send-email-somasissounds@gmail.com>\n\t<56FD8A8B.3040808@cs.ucla.edu>\nFrom: Kylie McClain <somasissounds@gmail.com>\nDate: Thu, 31 Mar 2016 17:26:56 -0400\nMessage-ID: <CAOji9TBmsW1EKzW5yMuq5UtXdeNOyjtSOxHzoT+3iWWD9jBbVQ@mail.gmail.com>\nSubject: Re: [PATCH] src/print.c: Check for __GLIBC__ rather than GNU_LINUX\nTo: Paul Eggert <eggert@cs.ucla.edu>\nContent-Type: text/plain; charset=UTF-8\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::233\nCc: Kylie McClain <somasis@exherbo.org>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 31 Mar 2016 21:27:39 -0000\nContent-Length: 569\nLines: 15\n\nOn Thu, Mar 31, 2016 at 4:37 PM, Paul Eggert <eggert@cs.ucla.edu> wrote:\n> Thanks for reporting that. I filed a bug report with a proposed patch here:\n>\n> http://debbugs.gnu.org/cgi/bugreport.cgi?bug=23173\n>\n> Instead of disabling redirect-debugging-output, this patch ports it to\n> systems like musl where stderr is not an lvalue. Please give it a try if you\n> have the time. it's against master commit\n> 750e1e19429cd781e2e60b462d19ef827d4da943.\n>\n> I haven't installed it into 'master' yet, to give Eli a heads-up about the\n> impending change.\n\nWorks just fine! :)\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_397.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an07B-0007Wd-8S\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 04:44:05 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:35301)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ulf.jasper@web.de>) id 1an078-0007Vr-Ux\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 04:44:03 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ulf.jasper@web.de>) id 1an075-0006WC-OO\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 04:44:02 -0400\nReceived: from mout.web.de ([212.227.17.12]:56444)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ulf.jasper@web.de>) id 1an075-0006W6-Es\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 04:43:59 -0400\nReceived: from p55m-ud2 ([217.226.80.48]) by smtp.web.de (mrweb102) with\n\tESMTPSA (Nemesis) id 0LkPnr-1bOFny0LkJ-00cMhm; Mon, 04 Apr 2016 10:43:57\n\t+0200\nFrom: Ulf Jasper <ulf.jasper@web.de>\nTo: emacs-devel <emacs-devel@gnu.org>\nSubject: Initial occurence of 'diary-anniversary' in calendar\nDate: Mon, 04 Apr 2016 10:43:56 +0200\nMessage-ID: <877fgdojar.fsf@web.de>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-Provags-ID: V03:K0:YPhmZlMPeh3VptzyVKe47KmVnVBDmx2bz14v4vm7o1irjfuuGdT\n\tCnW/3F/BDsDsegakDq3JhlEphriNEs1MwXYcORJ1y3JhIi/WvygpjN9buizt4i3niFkfD22\n\tK5X0ZfIc2kjIw5czjcrH/Lo65TWlLbQKvwj9WInYNDxJTxxyceQ0gjkNr6SW08HiEGcQJRG\n\tYOt28v0orM5HOdN8sp43Q==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:+5+PBHcOI5o=:dZkLpDdZ1xViRD3Vseyvzn\n\toY+DSFipmwn3q7DtF51Vr412S+UcZTS6yh+8g89s7MFtFdk3trelvxg9hM4kKcK/Pt0VIzFz5\n\tWTXdwO5xxDSTpphln9cyWsA1vqi/Wwt3a0GKES2zzZJbqZQmvVDpo/+SP9wD9YT3vBfhPPIsY\n\tbHUcPKbO0jv8Qua3USKoJVm/EJmyDxmcgTk8WHkb+U9891yh1y4O1IMt6wogTCjK2RANc2R67\n\t/ZJYgqMG+5blfQ6Gqe+5/rBK4Ark/l5+XmACIkD0xPyEABpWL49I0y713AD7IvuK/k+ZUFs2l\n\tHjlYYHhlc6sU6leCidu1Ya0uc39plM5m7T2i1IRay8MN4j74dqqbloVb3c50PnO4/lZbTK9xY\n\tiCT5ipXg5tYxIohOobO/lYONH0ZWDm5Q5sRKLN3G8HKjYHXJUMFTlvbnT5xM3D1HbJDaaJ9bn\n\tUupEjbuCZwBwDHULU+TxYtBcBAyMlwAdn/g/64hzVj1yoZGi1ry1EMAZbJ0yFx+DIDwodl7Rr\n\tId6Db92QCFkDjhsblj5sJA6rLe0azwZWw7qf/vRu4/lof9i6E4aySQG5VLYE6wwVug9FyY5BD\n\tQ4smsIRmNX9m1DSUABeRQv52WgSGGi7OCOMHNwsPmC3OWg4ExeGFOaz5n8jlb2iSEeF2tdLoa\n\tH/7gQHfBJ5LJaC+r11UdaqcJ9IjwzBJrZMxv84F5MMPpwzJyc9DdkEyd+t2bGJ8KltyzCT/qe\n\tj3+8+yctSmX0LQ/E0Zs49Gu0InRLyKQRscTIxMR2A5+fct+7humX1+MPOVHiV7joUr1OUSLrB\n\tkjVFE14\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.12\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 08:44:03 -0000\nContent-Length: 609\nLines: 20\n\nAnniversary entries like\n\n    %%(diary-anniversary 4 4 2016) Mark My Diary\n\nwill show up in the calendar only from 4 4 2017 on, i.e. one year after\nthe actual event.  The initial occurrence of an anniversary (4 4 2016)\ndoes not get marked in the calendar.  Why is that?\n\n(Changing this behaviour would fix bug#23100 which results from that\nfact that icalendar.el considers 'diary-anniversary' to be equivalent to\na YEARLY ical event.)\n\nMaking the initial occurrence of an anniversary show up seems natural to\nme.  So I would like to implement that.  But maybe I am missing\nsomething here.  Am I?\n\nBest,\nUlf\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_399.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an0K0-0004C6-5J\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 04:57:20 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37559)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <schwab@suse.de>) id 1an0Jy-0004By-CV\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 04:57:19 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <schwab@suse.de>) id 1an0Jv-0001DR-6T\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 04:57:18 -0400\nReceived: from mx2.suse.de ([195.135.220.15]:36474)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <schwab@suse.de>) id 1an0Jv-0001DJ-0I\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 04:57:15 -0400\nX-Virus-Scanned: by amavisd-new at test-mx.suse.de\nReceived: from relay1.suse.de (charybdis-ext.suse.de [195.135.220.254])\n\tby mx2.suse.de (Postfix) with ESMTP id A5D9DAB5D;\n\tMon,  4 Apr 2016 08:57:12 +0000 (UTC)\nFrom: Andreas Schwab <schwab@suse.de>\nTo: Ulf Jasper <ulf.jasper@web.de>\nSubject: Re: Initial occurence of 'diary-anniversary' in calendar\nReferences: <877fgdojar.fsf@web.de>\nX-Yow: These PRESERVES should be FORCE-FED to PENTAGON OFFICIALS!!\nDate: Mon, 04 Apr 2016 10:57:13 +0200\nIn-Reply-To: <877fgdojar.fsf@web.de> (Ulf Jasper's message of \"Mon, 04 Apr\n\t2016 10:43:56 +0200\")\nMessage-ID: <mvm1t6l4uqe.fsf@hawking.suse.de>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x (no\n\ttimestamps) [generic]\nX-Received-From: 195.135.220.15\nCc: emacs-devel <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 08:57:19 -0000\nContent-Length: 551\nLines: 19\n\nUlf Jasper <ulf.jasper@web.de> writes:\n\n> Anniversary entries like\n>\n>     %%(diary-anniversary 4 4 2016) Mark My Diary\n>\n> will show up in the calendar only from 4 4 2017 on, i.e. one year after\n> the actual event.  The initial occurrence of an anniversary (4 4 2016)\n> does not get marked in the calendar.  Why is that?\n\nThe first anniversary is one year after the event.\n\nAndreas.\n\n-- \nAndreas Schwab, SUSE Labs, schwab@suse.de\nGPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE  1748 E4D4 88E3 0EEA B9D7\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_401.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an6oN-0006ta-HJ\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 11:53:07 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:35665)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ulf.jasper@web.de>) id 1an6oI-0006nU-KX\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:53:06 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ulf.jasper@web.de>) id 1an6oF-0006Jv-Cw\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:53:02 -0400\nReceived: from mout.web.de ([212.227.17.12]:62980)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ulf.jasper@web.de>) id 1an6oF-0006Jo-3Y\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 11:52:59 -0400\nReceived: from p55m-ud2 ([217.226.80.48]) by smtp.web.de (mrweb103) with\n\tESMTPSA (Nemesis) id 0MJTVP-1ap1ga3kxX-0033FG; Mon, 04 Apr 2016 17:52:55\n\t+0200\nFrom: Ulf Jasper <ulf.jasper@web.de>\nTo: Andreas Schwab <schwab@suse.de>\nSubject: Re: Initial occurence of 'diary-anniversary' in calendar\nReferences: <877fgdojar.fsf@web.de> <mvm1t6l4uqe.fsf@hawking.suse.de>\nDate: Mon, 04 Apr 2016 17:52:54 +0200\nIn-Reply-To: <mvm1t6l4uqe.fsf@hawking.suse.de> (Andreas Schwab's message of\n\t\"Mon, 04 Apr 2016 10:57:13 +0200\")\nMessage-ID: <87mvp9mkvd.fsf@web.de>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-Provags-ID: V03:K0:m8XO7tGnbyb+hUhjmqUotI9ZN/O3g+KkoZTMiCshHvJQyfrJ57B\n\tY4Tdnsk5o8VQX+EmgyTgcX5UOpJfH2E06oi6qZnjai0QGW8VI0u8clGFCGKoPGKZE9nn6jz\n\tB6W/Q9LIil7panCVucNq0DpbmDj2o5jEupxEu/lMIG1mDqni9ohLd7KPcgEUpvFTeAVry2v\n\t6pPeMoRVT8nqWD2fgpD2g==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:4X4W1twS/6Q=:RcILxtdsOJYbwosJumKABh\n\ttt7YI+ESgnpKzQfr+K17XDbq2150oSEaEfFwNNbNTxfvb3LIh7z1oHMpkFp8cHlnPbcxJlAt+\n\tTUo5JehKlA7fCg0N9PuSnwUn3RlhAvdx6n4B9xHy7HviSrUC09Bx2nONBNL0uiGewlNuauYFX\n\tOwj/dU+uOdArVf9gtlyj23AgGb8dkNX2K4GlgcTpo+1O99ZbqQXnIXGc7z+ABE3DFH9ACHbWG\n\tebi3TvI10Bsn5qeULBdEPPKjnrfy30OlUATBzXreXNRHG65Waz0frL0zFROnatWXIsl0XgTNf\n\tN3x1WpAxXA+LqB4reKIOVpghVemS07u3ZqQmpW2VDenek1YFvqefEq6EFFxV39zvQcEPDt6Fh\n\th782oJkyf85zNkLsw5gf/J4Ha0/T6OO00t+XyA8vi98oDvjJ4a5ZRrjnvaj64JH22DZZZ3w/j\n\tdUi77xyDbw3PL12mDmy38lXyrgaO6dGflk352s5zDw9d3RdPluRQqC4xplpSXCkW4iZbqC5EO\n\tigKXYeUXDeQ8ZpNGaBteqdquln69Wf9PZQ1haPPqJQcji5aKlzR/hZI1/HDtFPYAlGF0EAOLS\n\tmCPq9JjQCzm9i7oa2vhduK0NhlP42HtmBxGpvNABB1SwZKXtu4LSnbskAlV3KGBS7L1oVdKAj\n\tmecieQG92+BkoplBxJEYiB9NQMjR5eC/UwtqOg7wLyp1aJxUsRa3Vz76sq02WL8WSxnVP9R9Q\n\tTK03S/wuD3vbO83RqS+ecfWJrQmhUVJk6lNee9RrEcFXL0CuNc/qsmC/P3/lZYgjs5cYsZ758\n\t4U55OVC\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.12\nCc: emacs-devel <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 15:53:06 -0000\nContent-Length: 748\nLines: 27\n\nAm 04.04.2016 um 10:57 (+0200) schrieb Andreas Schwab:\n> Ulf Jasper <ulf.jasper@web.de> writes:\n>\n>> Anniversary entries like\n>>\n>>     %%(diary-anniversary 4 4 2016) Mark My Diary\n>>\n>> will show up in the calendar only from 4 4 2017 on, i.e. one year after\n>> the actual event.  The initial occurrence of an anniversary (4 4 2016)\n>> does not get marked in the calendar.  Why is that?\n>\n> The first anniversary is one year after the event.\n\nAgreed.  That is also what my dictionary tells me.  And the Emacs manual\nsays the same:\n\n|      %%(diary-anniversary 10 31 1988) Arthur's birthday\n| \n| This entry applies to October 31 in any year after 1988; \n\nBut still I would expect to see Arthur's actual birthday marked in the\ncalendar.\n\nBest,\nUlf\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_403.argentina:2,",
    "content": "Return-Path: <dir_castrocolumbus@mail.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 9BED020A06F2C\n\tfor <laurent@brasil.brainstorm.fr>; Mon,  4 Apr 2016 11:23:38 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 8D0E7FACC98; Mon,  4 Apr 2016 11:23:38 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 7D874FACC9D; Mon,  4 Apr 2016 11:23:38 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.3 required=5.0 tests=ADVANCE_FEE_2_NEW_MONEY,\n\tBAYES_80,FREEMAIL_FROM,FREEMAIL_REPLYTO,LOTS_OF_MONEY,RDNS_NONE,SPF_HELO_PASS,\n\tSUBJ_ALL_CAPS,TO_NO_BRKTS_NORDNS,T_TO_NO_BRKTS_FREEMAIL autolearn=spam\n\tversion=3.3.2\nX-Spam-Report: \n\t*  0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider\n\t*      (dir_castrocolumbus[at]mail.com)\n\t* -0.0 SPF_HELO_PASS SPF: HELO matches SPF record\n\t*  1.5 SUBJ_ALL_CAPS Subject is all capitals\n\t*  2.0 BAYES_80 BODY: Bayes spam probability is 80 to 95%\n\t*      [score: 0.8200]\n\t*  0.0 LOTS_OF_MONEY Huge... sums of money\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  2.4 FREEMAIL_REPLYTO Reply-To/From or Reply-To/body contain different\n\t*      freemails\n\t*  0.0 T_TO_NO_BRKTS_FREEMAIL T_TO_NO_BRKTS_FREEMAIL\n\t*  0.0 TO_NO_BRKTS_NORDNS To: misformatted and no rDNS\n\t*  0.6 ADVANCE_FEE_2_NEW_MONEY Advance Fee fraud and lots of money\nX-Spam-DCC: : \nReceived: from fundacionabei.org (unknown [186.71.25.202])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 1C414FACC98\n\tfor <dev@caliopen.local>; Mon,  4 Apr 2016 11:23:33 +0200 (CEST)\nReceived: from working-PC.home (72.Red-88-11-57.dynamicIP.rima-tde.net [88.11.57.72])\n\t(authenticated bits=0)\n\tby fundacionabei.org (8.14.4/8.14.4) with ESMTP id u349YuxC009088\n\tfor <dev@caliopen.local>; Mon, 4 Apr 2016 05:02:32 -0500\nMessage-Id: <201604041002.u349YuxC009088@fundacionabei.org>\nContent-Type: text/plain; charset=\"iso-8859-1\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Description: Mail message body\nSubject: ZORPIA MEGA JACKPORT LUCKY WINNER\nTo: dev@caliopen.local\nFrom: dir_castrocolumbus@mail.com\nDate: Mon, 04 Apr 2016 11:22:38 +0200\nReply-To: dir_castrocolumbus@mail.com\nX-fundacionabei-MailScanner-Information: Please contact the ISP for more information\nX-fundacionabei-MailScanner-ID: u349YuxC009088\nX-fundacionabei-MailScanner: Found to be clean\nX-fundacionabei-MailScanner-SpamScore: ssss\nX-fundacionabei-MailScanner-From: dir_castrocolumbus@mail.com\nX-Spam-Prev-Subject: ZORPIA MEGA JACKPORT LUCKY WINNER\nContent-Length: 318\nLines: 17\n\n\n\nWow!!!\n\nIt is a great pleasure to inform you that you emerge winner to the sum of 8=\n50,000.00GBP.\n\nFor more inquiries contact;\n\nEmail: ( dir_zorpiaenquiring@ciudad.com.ar )\n\nName: Dir.Mrs maria Grey\n\n--=20\nThis message has been scanned for viruses and\ndangerous content by MailScanner, and is\nbelieved to be clean.\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_405.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1am6in-0005n2-Uy\n\tfor mharc-dev@caliopen.local; Fri, 01 Apr 2016 17:35:13 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52962)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1am6im-0005mi-Cf\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 17:35:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1am6il-000501-Do\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 17:35:12 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:51932)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1am6il-0004zr-AS\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 17:35:11 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1am6ik-0000Kc-JJ; Fri, 01 Apr 2016 17:35:10 -0400\nContent-Type: text/plain; charset=Utf-8\nFrom: Richard Stallman <rms@gnu.org>\nTo: dev@caliopen.local\nSubject: melpa.org doesn't work without nonfree software\nMessage-Id: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\nDate: Fri, 01 Apr 2016 17:35:10 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 01 Apr 2016 21:35:13 -0000\nContent-Length: 959\nLines: 25\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\nThere is a big ethical problem with site melpa.org.  It does not work\nat all unless the user executes Javascript code.  Several pages of it.\nIt does not seem to carry free software licenses.\n\nI determined this by trying to access it with IceCat.  It blocked lots\nof Javascript.  Even the most basic navigation does not work.\n\nI hope that we can get this fixed.  John Wiegley, would you like to\ntalk with them about it?\n\nHowever, for the moment, please do not post anything here referring\npeople to melpa.org.  Is there anything in the Emacs sources\nmentioning melpa.org?\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_407.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amTYz-0001r5-2g\n\tfor mharc-dev@caliopen.local; Sat, 02 Apr 2016 17:58:37 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41347)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amTYw-0001qE-0Z\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 17:58:34 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amTYs-00038j-1G\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 17:58:33 -0400\nReceived: from mail-ob0-x236.google.com ([2607:f8b0:4003:c01::236]:34985)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>)\n\tid 1amTYr-00038Z-S1; Sat, 02 Apr 2016 17:58:29 -0400\nReceived: by mail-ob0-x236.google.com with SMTP id fp4so71391548obb.2;\n\tSat, 02 Apr 2016 14:58:29 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=oK4lth9b8eQKy7S3Gr9JCpxVyiB6+afg8u6H5F/F1U8=;\n\tb=TIe18VDnIVCguLJHoyEywPYIHw48trPoj1O9jsCDZ3dsFelDMKVmxJjGr9P04JAIUJ\n\tlYnTyqCuOJ0rHEwR7lCL7JAkR8zjcGkdJrfCnP8f3VVxi24+e1bX5il6V65Dh6wAw0qm\n\tMZEhzX6cUE0LdiL91qneqB5Q4J8DDc68TqJq41xey/53jVMHbdlIC0WIVWhjgRFvvY44\n\tx8UUdAG7unP0xHv+Y6n7dguGbtRdCDSVrzsxOR1mdbuvDbxD36WffE8W0eXoQEnAEKJN\n\tCSMWkCE2C5NJZ2/Dj3QUspA7Fxbc+XA6AZIE68JlZIB5xFRFyHIXWImdp5asAx2kyPJ+\n\tinyQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=oK4lth9b8eQKy7S3Gr9JCpxVyiB6+afg8u6H5F/F1U8=;\n\tb=FWdFV1g1VmN9UfjLknts3rUtVpAdGIIPClV0Q1BHyKKvEnDAp3F97X2xo3tPRkpZJC\n\tljSyXZYGDFRaMNqSvJRyLGKVwJuHk4riU2Tb6PxefrtrTrKj3TeAddTzVKDBpu/Qpqta\n\t29wz0nFVALhJARwIVfzkQ0VlA4p9oMZ5PJlExcbq4g46juQzyYhaMT3d1AAz88WIahgj\n\tGdIskJQQCKtuAdgIddd+KPCLIUszrX/I1eGWEqv18zoEa6AQJhKPCPGfQVuG/YnudEb/\n\t+1n+d3S4gLFMriW0YUqX8V53sdtp/paXG22/4x5VIntqn4OPxDzbWlAVxyr1vQuBuiwl\n\tNMzA==\nX-Gm-Message-State: AD7BkJLyfEU2xvr/IW/jKblZO1HSqt9Sjk3FT66PmflzZBDlg/qjDGYpGgTTKW7Oj3XpaA==\nX-Received: by 10.182.22.137 with SMTP id d9mr7182868obf.83.1459634309223;\n\tSat, 02 Apr 2016 14:58:29 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79]) by smtp.gmail.com with ESMTPSA id\n\th202sm6193031oic.17.2016.04.02.14.58.28\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSat, 02 Apr 2016 14:58:28 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid 5809D13D2A1CB; Sat,  2 Apr 2016 14:58:27 -0700 (PDT)\nTo: Richard Stallman <rms@gnu.org>\nSubject: Re: melpa.org doesn't work without nonfree software\nIn-Reply-To: <E1am6ik-0000Kc-JJ@fencepost.gnu.org> (Richard Stallman's message\n\tof \"Fri, 01 Apr 2016 17:35:10 -0400\")\nDate: Sat, 02 Apr 2016 14:55:40 -0700\nMessage-ID: <m2pou7y8tf.fsf@newartisans.com>\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.0.92 (darwin)\nMail-Followup-To: Richard Stallman <rms@gnu.org>, emacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::236\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 02 Apr 2016 21:58:35 -0000\nContent-Length: 339\nLines: 11\n\n>>>>> Richard Stallman <rms@gnu.org> writes:\n\n> I hope that we can get this fixed. John Wiegley, would you like to talk with\n> them about it?\n\nSure, I'll talk with the MELPA maintainers.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_409.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amb9m-00014B-Iq\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 02:05:06 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57539)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <vibhavp@gmail.com>) id 1amb9j-00011o-01\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 02:05:03 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <vibhavp@gmail.com>) id 1amb9h-00058V-Ri\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 02:05:02 -0400\nReceived: from mail-ig0-x230.google.com ([2607:f8b0:4001:c05::230]:37966)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <vibhavp@gmail.com>)\n\tid 1amb9h-00058A-Mo; Sun, 03 Apr 2016 02:05:01 -0400\nReceived: by mail-ig0-x230.google.com with SMTP id ui10so37595258igc.1;\n\tSat, 02 Apr 2016 23:05:01 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:date:message-id:subject:from:to; \n\tbh=4SLYU5AjeC0uBevx1mfK5YJHD2RnXE6tiVsgiYUhnFQ=;\n\tb=PSxE3Ggg47napG7xfimjYtY4RZTEV19XcxERvnbrUdiAAkgrhNV4BjWYMGrdj78T0w\n\tRRaRMqLq7Ijt8x1gQOqGiOyisgxEz7wRA3yTu1eTMvu3rAIJuK5RlO2Bcn5wPte+TYE5\n\tA2DO3Xzqwvq6xssFSdp6dxz7A+wHQ/SH8n4ncASGCrydIf4ZCMXDbavLOa4unHtKLi77\n\tflok8OgDnCRkgu6GnxpQZh08qZSQbKr6PlfZMvFv10XPXKOW42GK7cnCSU6WtyRXO2TP\n\ttZTzyxPLgC22vNWx6HNGwEtlf7Csd3nBXEqfHOc8iv6ySJ54+AyTb2J0lOMcTnbVfoHJ\n\tQcLg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:date\n\t:message-id:subject:from:to;\n\tbh=4SLYU5AjeC0uBevx1mfK5YJHD2RnXE6tiVsgiYUhnFQ=;\n\tb=jRraDdFG8JIiuHQxxYK5fCV+cLWJ95UZSLCtEFpdj9Q53rvA8eO91mWaL7oK0zsfGe\n\t16/11uE+Mnb4Ss0ApPabGxbDt62fMqt8ElTKT3eI006xrD9ANT9eIdBseAmoQQYRhRBb\n\t5DX2IdQvfuO8ynTL4XjAXhrQbL31PM2NSLOM3sitYUYCXj3ZM/kZwDBINHAVzUJH4Tfk\n\tzxBLs54gpYpO9zHP5W8noE5rWjNoIYLDmH5O2ZLKTiTpBhmOQWBH6SkBldXmcw8jwaej\n\tJ19xysJz4Y0P3UCSORqKVBbpr6grcaMyPFcDZ3qsea719vF9cV024rWfcK1RzTlT1FXj\n\tYfJQ==\nX-Gm-Message-State: AD7BkJKCSh22fi2F9zNTX1pa80hunclcnY5AR/c6qD1qkLMG6K6mHIvyk52EQm0QCl+9UJ/8FIcr1Ue1gAYDtA==\nMIME-Version: 1.0\nX-Received: by 10.107.41.201 with SMTP id p192mr3033507iop.125.1459663500743; \n\tSat, 02 Apr 2016 23:05:00 -0700 (PDT)\nReceived: by 10.79.17.132 with HTTP; Sat, 2 Apr 2016 23:05:00 -0700 (PDT)\nIn-Reply-To: <m2pou7y8tf.fsf@newartisans.com>\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\n\t<m2pou7y8tf.fsf@newartisans.com>\nDate: Sun, 3 Apr 2016 11:35:00 +0530\nMessage-ID: <CA+T2Sh0-EMD9chUckGuoEhMjL+sdCCYZpsW=8k_2R1q+wbkkgA@mail.gmail.com>\nSubject: Re: melpa.org doesn't work without nonfree software\nFrom: Vibhav Pant <vibhavp@gmail.com>\nTo: Richard Stallman <rms@gnu.org>, \"emacs-devel@gnu.org\" <emacs-devel@gnu.org>\nContent-Type: text/plain; charset=UTF-8\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4001:c05::230\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 06:05:04 -0000\nContent-Length: 598\nLines: 23\n\nThe source for Melpa's website is available here, and is licensed\nunder the GPL v3: https://github.com/melpa/melpa/tree/master/html\n\n\nOn Sun, Apr 3, 2016 at 3:25 AM, John Wiegley <jwiegley@gmail.com> wrote:\n>>>>>> Richard Stallman <rms@gnu.org> writes:\n>\n>> I hope that we can get this fixed. John Wiegley, would you like to talk with\n>> them about it?\n>\n> Sure, I'll talk with the MELPA maintainers.\n>\n> --\n> John Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\n> http://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n>\n\n\n\n-- \nVibhav Pant\nvibhavp@gmail.com\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_411.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1an5On-00077R-Ni\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 10:22:37 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37956)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1an5Ol-000760-0H\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 10:22:35 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1an5Oh-0000Ar-2p\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 10:22:34 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:47645)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1an5Oa-00006d-Ed; Mon, 04 Apr 2016 10:22:24 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1an5OY-0006qi-AK; Mon, 04 Apr 2016 10:22:22 -0400\nContent-Type: text/plain; charset=Utf-8\nFrom: Richard Stallman <rms@gnu.org>\nTo: Vibhav Pant <vibhavp@gmail.com>\nIn-reply-to: <CA+T2Sh0-EMD9chUckGuoEhMjL+sdCCYZpsW=8k_2R1q+wbkkgA@mail.gmail.com>\n\t(message from Vibhav Pant on Sun, 3 Apr 2016 11:35:00 +0530)\nSubject: Re: melpa.org doesn't work without nonfree software\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\n\t<m2pou7y8tf.fsf@newartisans.com>\n\t<CA+T2Sh0-EMD9chUckGuoEhMjL+sdCCYZpsW=8k_2R1q+wbkkgA@mail.gmail.com>\nMessage-Id: <E1an5OY-0006qi-AK@fencepost.gnu.org>\nDate: Mon, 04 Apr 2016 10:22:22 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 14:22:35 -0000\nContent-Length: 854\nLines: 25\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > The source for Melpa's website is available here, and is licensed\n  > under the GPL v3: https://github.com/melpa/melpa/tree/master/html\n\nIs that the source code for ALL the Javascript used in the page?\n\nIf so, it should be a small job to label the code so that LibreJS\nwill recognize it as free.  That doesn't mean we can assume it\nwill get done soon, but we can encourage the to do it.\n\nBut if there is other Javascript code used as well, that may\nbe harder.\n\n\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_413.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ame2w-00059r-NO\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 05:10:14 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:48926)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ame2u-00059j-C7\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 05:10:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ame2r-0004WJ-6r\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 05:10:12 -0400\nReceived: from mout.kundenserver.de ([217.72.192.74]:53918)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1ame2q-0004Vs-Tw\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 05:10:09 -0400\nReceived: from [192.168.1.82] ([109.24.225.43]) by mrelayeu.kundenserver.de\n\t(mreue103) with ESMTPSA (Nemesis) id 0LnUdS-1bTvU11Gco-00hb1E for\n\t<emacs-devel@gnu.org>; Sun, 03 Apr 2016 11:10:07 +0200\nSubject: Re: melpa.org doesn't work without nonfree software\nTo: dev@caliopen.local\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\n\t<m2pou7y8tf.fsf@newartisans.com>\n\t<CA+T2Sh0-EMD9chUckGuoEhMjL+sdCCYZpsW=8k_2R1q+wbkkgA@mail.gmail.com>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <5700DDE8.5040504@gmail.com>\nDate: Sun, 3 Apr 2016 11:10:00 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <CA+T2Sh0-EMD9chUckGuoEhMjL+sdCCYZpsW=8k_2R1q+wbkkgA@mail.gmail.com>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"4bcsLGc52qVJtuVGFP9nC1cXfM6aHmjfM\"\nX-Provags-ID: V03:K0:m4pm417x8nOOoziJYhhEanvIRDDiJ+Ua7vRu7Mw8iour7b0ZoyF\n\tYKU5bNTVzq7x9oB308uBYNxNwME30Eth70b62B3rEk5ovNC1m9Kq7de/UbPDQt4Gk8XtwQk\n\tOhpbOIk/wtx6esMvI22ZobmaQc/S6/lDcHSf4avFzwoRq9FnuFUy2iR4N8T/2mh56eCEpWP\n\tILYjG+M4IuwQORdSY9xyQ==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:eG91kT9/yuY=:eMoCNeDovcb/PuH40bEww9\n\tDzPJ0SDGeXiPUgTlEKzVXeGpgBppT9uo0ERsLHTjCPV99tNgLK6e8vZ2YTib2SVML1KJNfeA2\n\tYzFeFfsLePS0+kzpJQ289/rR9pFxYZfBTaxhGDPWyyVVqwETEcWdbgWqNJRZqOOtfawXdMGUs\n\tvU/abFa/Z9+FqDThysAxmmsyMzHcNUK+r8oELlpqhwpCw6WOx+RsZYNRs+Tbdkgilk9Pxxsi+\n\twd1Ofzn+QUv9PaalDGfRc5CYjes6RlX2b4KBsEmPIckOIL6ioyQZmvZiKVcek3o+yHKKs7bs1\n\tyUhiZnds/BRJnQauqs7X/c7JOW4l+rYtrd5iDXC/6O0D0nQHTMOTYPNgRmm9ZcbBxwI2aeUPw\n\tdh3XU10QgCLFiAHHexEp7V/cd6QxYkyX/JUrF9TOLI7VTpzIUBzqpeqhh8jqkd1NRBuEvAOap\n\twAVyfw8NhYpQCUpGeNqmrwtYZqs/Bw/AOaXz8TGGes7aH6CA7kwVn7TXUfDRsdaIBLkkMXSag\n\tTRcibKnFOHmADuTRMsyG/5zVQi9Dx/YhGnH2fLB/UJDvDXLG+y8f8nkApZtGCdEupJNANdfI9\n\t/W/wkf5QJTzXprzCMORV5+WmQ/183olzIR8jCdRvJ2sEp4y9dIqNv+O2TD9h2Oil9mH5qrTGG\n\tugkRd+kBNlNGAagkBANpgnM9RALKxvdUvr8/iTPwqJDZmV5rTbAiV4WY+yuDIWtxbskM=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 217.72.192.74\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 09:10:13 -0000\nContent-Length: 2330\nLines: 54\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--4bcsLGc52qVJtuVGFP9nC1cXfM6aHmjfM\nContent-Type: multipart/mixed; boundary=\"l1Wk7AaOuuGDJLmQKn6HimM5n4ka6cfEB\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <5700DDE8.5040504@gmail.com>\nSubject: Re: melpa.org doesn't work without nonfree software\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\n <m2pou7y8tf.fsf@newartisans.com>\n <CA+T2Sh0-EMD9chUckGuoEhMjL+sdCCYZpsW=8k_2R1q+wbkkgA@mail.gmail.com>\nIn-Reply-To: <CA+T2Sh0-EMD9chUckGuoEhMjL+sdCCYZpsW=8k_2R1q+wbkkgA@mail.gmail.com>\n\n--l1Wk7AaOuuGDJLmQKn6HimM5n4ka6cfEB\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/03/2016 08:05 AM, Vibhav Pant wrote:\n> The source for Melpa's website is available here, and is licensed\n> under the GPL v3: https://github.com/melpa/melpa/tree/master/html\n\nYes, I pointed this out already I think Richard's concern is that the cod=\ne doesn't carry the right tags, so it cannot be properly identified by Li=\nbreJS, a plugin that filters out nonfree code, and free code that hasn't =\nbeen labeled as such. Without that code, the MELPA website is pretty unus=\nable.\n\n\n--l1Wk7AaOuuGDJLmQKn6HimM5n4ka6cfEB--\n\n--4bcsLGc52qVJtuVGFP9nC1cXfM6aHmjfM\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXAN3uAAoJEPqg+cTm90wjAXUP/3mDfaK1vF2h5lP7lo9m/tIr\nfcU1NJkmKAHNXG5kQ86PBDpQXzybLbMFlXtPwJl/7AP9JT27Qi/F72in4Mr/HmeJ\n+bo4n2RsZ1MiWpYLt2v9CLiMVkiaIq+ysq5SfmcC+ESMniDqgad7JEL8izsaJWcR\nTwPv74bdNm4LSch6sXYAKIJGu1ELDmTHcu2z0UePwsCzst41OWqVpr5SIce5Q2sT\nLeFi2v6mXzzkIxyQqqU9DMYKvTDVZbIqCbImeRexWXe/FlLjRaySgqsYB5KlO94p\nvZREudwrLG8ze6ESFo9bYOEKoTiDMWXoTWwqPH6za4DRDz125WG1BX7/n05Ht2ys\nQ/IumdHw1IWQqnppBL7HV8X8t7iM0N7DNTnDNnigUSigiWvz/+0qy+lwN0MxH+ZH\ngsfGrIbQL4Tm9OkJXCvuazvDcKg+YdVxdxzGMeoA/dHUTfocEFTJXBKQCeLbyuo4\n2Fe57pL5SNZemVwOmZtvDFn2a4q48PRCTqMcV7butfcZUXB5vfrQkIQY4q6mU9Ns\n0oDYQK8N6KYwR4EazDJs4KKvpSc+ZSs5NPM6Shz5YWlAgYzba/fVco33tApwM975\naegUVLqIRfWs7fdxKC1VpWQneg2CY8gt/OOaZVgdWzNwrXy8+opQyWVaQzgs7Rbb\nPhIAxEI8NEtTTuPUyFj/\n=HB92\n-----END PGP SIGNATURE-----\n\n--4bcsLGc52qVJtuVGFP9nC1cXfM6aHmjfM--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_415.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amFPj-0000d1-IS\n\tfor mharc-dev@caliopen.local; Sat, 02 Apr 2016 02:52:07 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34546)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amFPh-0000cv-Lz\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 02:52:06 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amFPc-0007eg-Ul\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 02:52:05 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:33818)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1amFPc-0007eY-RK\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 02:52:00 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4111\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1amFPV-00055q-MU; Sat, 02 Apr 2016 02:51:54 -0400\nDate: Sat, 02 Apr 2016 09:51:19 +0300\nMessage-Id: <83vb40sdug.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: rms@gnu.org\nIn-reply-to: <E1am6ik-0000Kc-JJ@fencepost.gnu.org> (message from Richard\n\tStallman on Fri, 01 Apr 2016 17:35:10 -0400)\nSubject: Re: melpa.org doesn't work without nonfree software\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 02 Apr 2016 06:52:06 -0000\nContent-Length: 174\nLines: 7\n\n> From: Richard Stallman <rms@gnu.org>\n> Date: Fri, 01 Apr 2016 17:35:10 -0400\n> \n> Is there anything in the Emacs sources mentioning melpa.org?\n\nIt's mentioned in the FAQ.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_417.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amQol-0005d6-Kl\n\tfor mharc-dev@caliopen.local; Sat, 02 Apr 2016 15:02:43 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:45262)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1amQok-0005cy-Ar\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 15:02:42 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1amQoj-0001hq-Cd\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 15:02:42 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:42720)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1amQoj-0001hk-9B\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 15:02:41 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1amQoi-0007Da-KE; Sat, 02 Apr 2016 15:02:40 -0400\nContent-Type: text/plain; charset=Utf-8\nFrom: Richard Stallman <rms@gnu.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nIn-reply-to: <83vb40sdug.fsf@gnu.org> (message from Eli Zaretskii on Sat, 02\n\tApr 2016 09:51:19 +0300)\nSubject: Re: melpa.org doesn't work without nonfree software\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org> <83vb40sdug.fsf@gnu.org>\nMessage-Id: <E1amQoi-0007Da-KE@fencepost.gnu.org>\nDate: Sat, 02 Apr 2016 15:02:40 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 02 Apr 2016 19:02:43 -0000\nContent-Length: 600\nLines: 18\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > > Is there anything in the Emacs sources mentioning melpa.org?\n\n  > It's mentioned in the FAQ.\n\nWould you please delete it, for now?  We should not recommend\nsites that are useless when visited with LibreJS.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_419.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amRah-0006Ml-Me\n\tfor mharc-dev@caliopen.local; Sat, 02 Apr 2016 15:52:15 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:53261)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amRae-0006Hb-E4\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 15:52:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amRab-0003ya-8K\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 15:52:12 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:43261)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1amRab-0003yL-5I\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 15:52:09 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1643\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1amRaU-0001NW-1M; Sat, 02 Apr 2016 15:52:02 -0400\nDate: Sat, 02 Apr 2016 22:51:32 +0300\nMessage-Id: <83wpofrdq3.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: rms@gnu.org\nIn-reply-to: <E1amQoi-0007Da-KE@fencepost.gnu.org> (message from Richard\n\tStallman on Sat, 02 Apr 2016 15:02:40 -0400)\nSubject: Re: melpa.org doesn't work without nonfree software\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org> <83vb40sdug.fsf@gnu.org>\n\t<E1amQoi-0007Da-KE@fencepost.gnu.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 02 Apr 2016 19:52:13 -0000\nContent-Length: 383\nLines: 13\n\n> From: Richard Stallman <rms@gnu.org>\n> CC: dev@caliopen.local\n> Date: Sat, 02 Apr 2016 15:02:40 -0400\n> \n>   > > Is there anything in the Emacs sources mentioning melpa.org?\n> \n>   > It's mentioned in the FAQ.\n> \n> Would you please delete it, for now?  We should not recommend\n> sites that are useless when visited with LibreJS.\n\nDon't you want to wait for their response first?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_421.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amj2k-0000q1-R4\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 10:30:22 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59657)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1amj2i-0000mY-60\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:30:20 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1amj2h-0003XT-Bz\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:30:20 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:54403)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1amj2h-0003XL-8S\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:30:19 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1amj2g-0002lo-DZ; Sun, 03 Apr 2016 10:30:18 -0400\nContent-Type: text/plain; charset=Utf-8\nFrom: Richard Stallman <rms@gnu.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nIn-reply-to: <83wpofrdq3.fsf@gnu.org> (message from Eli Zaretskii on Sat, 02\n\tApr 2016 22:51:32 +0300)\nSubject: Re: melpa.org doesn't work without nonfree software\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org> <83vb40sdug.fsf@gnu.org>\n\t<E1amQoi-0007Da-KE@fencepost.gnu.org> <83wpofrdq3.fsf@gnu.org>\nMessage-Id: <E1amj2g-0002lo-DZ@fencepost.gnu.org>\nDate: Sun, 03 Apr 2016 10:30:18 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 14:30:21 -0000\nContent-Length: 867\nLines: 26\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > >   > > Is there anything in the Emacs sources mentioning melpa.org?\n  > > \n  > >   > It's mentioned in the FAQ.\n  > > \n  > > Would you please delete it, for now?  We should not recommend\n  > > sites that are useless when visited with LibreJS.\n\n  > Don't you want to wait for their response first?\n\nI expect it to take time to fix the problem, and we should not\nkeep on referring to the site while waiting.\n\nWhen they fix it, restoring the reference will be quick and easy.\n\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_423.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1am7AY-0006Ac-Ss\n\tfor mharc-dev@caliopen.local; Fri, 01 Apr 2016 18:03:54 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60357)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1am7AW-00069i-I2\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 18:03:53 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1am7AT-00037C-CZ\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 18:03:52 -0400\nReceived: from mout.kundenserver.de ([212.227.17.24]:57247)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1am7AT-000378-3X\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 18:03:49 -0400\nReceived: from [192.168.1.82] ([109.24.225.43]) by mrelayeu.kundenserver.de\n\t(mreue102) with ESMTPSA (Nemesis) id 0LkPdb-1aAJiB00ZH-00cOK7 for\n\t<emacs-devel@gnu.org>; Sat, 02 Apr 2016 00:03:48 +0200\nSubject: Re: melpa.org doesn't work without nonfree software\nTo: dev@caliopen.local\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56FEF043.3020803@gmail.com>\nDate: Sat, 2 Apr 2016 00:03:47 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"O4eUN5ijTUFwnFLv6ReFUogbAPhLPeBif\"\nX-Provags-ID: V03:K0:rPkqQqk1khEPraDcgL7v7gzBMAVQPfMsdU1Atw71agJocDHQMGS\n\tLAa5+HF6ZLDgD8wjnHyL9F6jW1N54AQ9iiE/LuxBhoMmgg2ZWf9RPVe+pk+4vTYxUwyn0Mk\n\tFSdknZIa2ECUIkvO4ycrC7b6LwDHQBURfKlb2PapWzmFs0zMv7DK4iCftoXslp8KAIARMsB\n\tMC+fsR2DozQ57HSDUJHmA==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:wXzkOCoa6V0=:8C4RbZVuOdSsALvtDC9uui\n\tM1eexcM0ZDLoTt+C14z19XE+K59cUMuKEYi66uH14lUDD++l6YgMtgNmak2vZLD3U9nv+bBeq\n\t2KiBOJztNZzl0b7COAMVAixjd7w6diUGe+MWNxbawnJKHwZlkl5Lk2JKoOx1sRkqprWrVx4YV\n\t8WRws8/3jIkr+ffpXXbPZOAuzrLdDQfMpoFWNUuDRoRX1SQlQWIp5yxuvCuPrhAou3k6Ad5vL\n\tqstVPvwhdobR6Rg0ZJK4nxMfT2/Sf9prSUds2ZntW3my5eYG+dKGoFZBlEeZcJk/VBLTgZAQf\n\tnx3UWqOpF9/OGOTdBeGPFTxQieyoeduhLM6NNDO/1lwUuf+oD0JY8jQi54sxvqhdYmnQjYfIi\n\t6mxaDgEQBYO1aMwj2vx+lDABHRJWbE1j1ff3DcKKzwNsyv7fYCs/MH3IPgT1qpGSY2ykonMDJ\n\tYXEVJh1zMb3hlM2OD3+GUtVWp+jweMdPP/lyF4CEy1iB+5vLKb7UZp+LCI3MMqw7gt8sQ0WUb\n\tg1iYCGJAVdOQYzWiK42onGPSLl6ZRR4OWI3zlYdqjU4kuqeiCVjslgEkbTFx5OCy+/vJZi0qt\n\t9SRjQJ8//QdoVny8H86NBlPNrjj8DGKsNRA0DGzMUGM4brTp8Aayo8bB7OrTnlmbRmKSkTd/c\n\t790rLHhljZsxgq+USr79f1Dcg4Rwh/Q7w+9Ulp8N38qXs7p2shGPj9SRZsDL0MR+Bmr4=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.24\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 01 Apr 2016 22:03:53 -0000\nContent-Length: 2234\nLines: 53\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--O4eUN5ijTUFwnFLv6ReFUogbAPhLPeBif\nContent-Type: multipart/mixed; boundary=\"INcgaRamH4psUPFRa52X58Ni6aRNgKhGd\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <56FEF043.3020803@gmail.com>\nSubject: Re: melpa.org doesn't work without nonfree software\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\nIn-Reply-To: <E1am6ik-0000Kc-JJ@fencepost.gnu.org>\n\n--INcgaRamH4psUPFRa52X58Ni6aRNgKhGd\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/01/2016 11:35 PM, Richard Stallman wrote:\n> [[[ To any NSA and FBI agents reading my email: please consider    ]]]\n> [[[ whether defending the US Constitution against all enemies,     ]]]\n> [[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n>=20\n> There is a big ethical problem with site melpa.org.  It does not work\n> at all unless the user executes Javascript code.  Several pages of it.\n> It does not seem to carry free software licenses.\n\nAll the code it runs is MIT or GPL licensed, it seems.\n\n\n--INcgaRamH4psUPFRa52X58Ni6aRNgKhGd--\n\n--O4eUN5ijTUFwnFLv6ReFUogbAPhLPeBif\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW/vBDAAoJEPqg+cTm90wjp2oP/1uxfFE8QMrjUMxR7gEII2D5\nVxo+hoJ5B1cB6Vd8AX2jEEkWxEyxhskitsxuwMFTD0/SUb3sipXSmBaTBpVIX4uY\nx4kLMyae8PBCxd4EPBfuBsohVNnNr9M4+olhVUdz0CktGss3JP5ZMjnSIr8oZWlK\nqP2+Aud4Aptcy9NXxoyEUOm4S30KEPjp4hri0wfW+xYgngvFhidzsgCxM1f5QsvE\n3G3zyGWpIw90DpjpEtG3cwNu5bOi1Y4RSwYMykGRMLTejF9Oysn1oJNhlN/fCg3f\nuvdz1vSm5CxjqqlTBDPWk3J2e81TC4q0ovZwAfGdTaT6nY0z7kuM4tbc/h/NfT2q\ncGoRpv/HF2H42hzzJLCjLEE8PA9QpXgNWmX95qLtLthLIqQgOIJ2IKOwdqhpZzbs\nq9bMnnVsqfJbm3F9dadpazk3bC9EmUIHbWTopNRPIKIQaDkOnBhmwlRuBAljyZOg\n+/MSnESzR1ZeSoe6/a6bB/NRDQV+R53DfYySTuCJfF8ZaB6VrJV6lpxTqlg+El/A\nru23hyHf7Uka1wSg7ZA95QCScJGK1LPhpzNjGdM0jHJ+kVBtL/KV9Irw4kKl28mB\n7WbCCe/dWG0ed2kfRclQZ0KlVxpGSreVwRTETQ2pWJo+EWXlSNeLSQPwFx7fedF3\nSIb0X0Z7N6kNoiPI2HwX\n=Jpkb\n-----END PGP SIGNATURE-----\n\n--O4eUN5ijTUFwnFLv6ReFUogbAPhLPeBif--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_425.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amQo3-00055r-W7\n\tfor mharc-dev@caliopen.local; Sat, 02 Apr 2016 15:02:00 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:44604)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1amQo1-00054X-3T\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 15:01:58 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1amQnz-0001EL-Vy\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 15:01:57 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:42680)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1amQny-0001Dm-7X; Sat, 02 Apr 2016 15:01:54 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1amQnx-0005FL-JO; Sat, 02 Apr 2016 15:01:53 -0400\nFrom: Richard Stallman <rms@gnu.org>\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nIn-reply-to: <56FEF043.3020803@gmail.com> (message from =?utf-8?Q?Cl=C3=A9?=\n\t=?utf-8?Q?ment?= Pit--Claudel on Sat, 2 Apr 2016 00:03:47 +0200)\nSubject: Re: melpa.org doesn't work without nonfree software\nReferences: <E1am6ik-0000Kc-JJ@fencepost.gnu.org> <56FEF043.3020803@gmail.com>\nMIME-version: 1.0\nContent-type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nMessage-Id: <E1amQnx-0005FL-JO@fencepost.gnu.org>\nDate: Sat, 02 Apr 2016 15:01:53 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 02 Apr 2016 19:01:58 -0000\nContent-Length: 1012\nLines: 26\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > All the code it runs is MIT or GPL licensed, it seems.\n\n\"MIT licensed\" is an ambiguous term, which we should avoid: see\nhttp://gnu.org/licenses/license-list.html.  But you must mean\nit carries some lax, permissive free license.\n\nIf all the code is in fact free, the proble could be fixed by putting\nthe appropriate license notices on the JS files (plus source pointers,\nin any minified files).\n\nBut it does need to be actually fixed, before we can refer to it.  If\na page uses Javascript that LibreJS can't recognize as free, and if\nthat Javascript is _necessary_ for the page to serve its purpose,\nwe can't link to it.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_427.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akbHW-0003Vm-L6\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 13:48:50 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56107)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akbHR-0003VY-9K\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 13:48:49 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akbHO-0000nQ-3J\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 13:48:45 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:59152)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akbHN-0000nM-SR\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 13:48:42 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akbFy-0008Db-Fj\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 19:47:14 +0200\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 19:47:14 +0200\nReceived: from larsi by cm-84.215.1.64.getinternet.no with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 19:47:14 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nMail-Followup-To: dev@caliopen.local\nTo: dev@caliopen.local\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Thinking about changed buffers\nDate: Mon, 28 Mar 2016 19:31:07 +0200\nOrganization: Programmerer Ingebrigtsen\nMessage-ID: <m3bn5ya4ro.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain; charset=iso-8859-1\nContent-Transfer-Encoding: 8bit\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: cm-84.215.1.64.getinternet.no\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAHlBMVEX7+vUgHRoLCQji3NSN\n\tiIL////+//4JCAYHBgQEAgFEcs/RAAACSElEQVQ4jW2TPY/jIBCGZ4UvdVhzSk/jK0Gg1MaQ3JWR\n\tsnFtadf5AW5Ie6tIJu1V9r+9gcRJ1trxF8zjdz7AhsVhZi90AQIyOM78O5MfBRAD7QHeAcg7CAGC\n\trA9vVOAoc9DumHRLI53Nl8z8PJi8IQBaQbu3RubWaFvapdns6TbqLeZ4y6l0uXnJrMvpRrJ6AUSL\n\tChUbq9TWZoSoUgHd1qiwGcfkz5axRUNEJqoZqM3yAEShYAaw1maNGcwcyLxpCHHqdQb2+RZLyqzk\n\tM4ACLCkJvoAPugEidBkFzwCX7wCKOKgSeKyupOsalCoz/lWhadkeG6HXFf+i0NTWbWzugz+DWtO8\n\t3TWLuHzPgBhq2x8Ed87q4gYEKO2osfUO97DRtupuIN4oa9ojLrtAwYrfAKXMiYYcsVCCOyq7CQAs\n\toAGCh1K6zCYBx6lQRAhCiFAW7oII0PCDwbtTWcGLCbgIMC/Wpsiq53eF00kT/ary3vOu6K7AXd34\n\tMH3nT2HqwzptnXP4CZkQTn03EewjdmKVe43+0BW+8N01VDLzK6D5vve9L/gd0FVI5gP3/nRNPn5+\n\tXp1DAsH7okuhwiVNw2R9z/0EcBbC5TIkPxZ1zTGOSE4hjOOk6booQTDiq0l2ugXz3PMCoh+vyzD2\n\tU5aYPwIMPiC6h8JGQl+kUOlMAEtEAZbgIxhjLMeYcX/Nb4MDxlZJEW0wLpf6LI3UltknMFbaSnlm\n\tTEqNv/YD/KuMo3/OZ2YqPFws9zJpYqrbAFuBh//JBiw3fAdwv/4D4BZXzkD5JOgAAAAASUVORK5C\n\tYII=\nMail-Copies-To: never\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:jTzgtpAAmdptRW3if2d5HHiBrY4=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 17:48:49 -0000\nContent-Length: 3124\nLines: 66\n\nIn conjunction with the wishlist item \"`M-q' shouldn't say that the\nbuffer hasn't changed when it hasn't\", we started talking a bit about\nfurther issues about what it means that a buffer has changed or not.\n\nIf you load a file, and then hit \"a\", and then delete the \"a\", then\nEmacs will say that the buffer has changed.  If you hit \"a\" and then\n`undo', Emacs will say that it hasn't.\n\nIf there was a way to deal with this discrepancy, that would be very\nnice, I think.\n\nOne idea that popped up is that whenever we mark a buffer as unchanged\n(that is, `(set-buffer-modified-p nil)', we save the byte size of the\nbuffer and a cryptographic hash of the buffer.  Then `buffer-modified-p'\nwould simply see whether either the size had changed, and if not,\nwhether the hash had changed.  If both are identical, then the buffer\nhasn't changed.\n\nThis would basically allow us to really tell the users \"yes, your buffer\nis now back to the state it was when you loaded it\".  I think that would\nbe very nice.\n\nHowever, there are two problems:\n\n1) Speed.  When editing files normally, `buffer-modified-p' would be\nvery fast, because buffers would change size, and we'd just be comparing\nthe sizes and say \"yup, changed\".  If, however, you're somehow altering\nthe buffer a lot but always returning to the same size, you'd have to\ncompute the hash.  (On my five year old, the current implementation\ntakes 2.7s on a 1GB buffer.)\n\n2) Text properties.  If you call `add-text-properties' on a buffer, the\nbuffer becomes marked as changed.  The hashing function could look at\nthe intervals, too, so that's not a problem, but many (most?) of the\ntext properties are added by font locking mores with\n`with-silent-modifications', which means \"no, no, these text properties\nhere don't change the buffer\".  But there's nothing in the text\nproperties themselves that will reveal this after the fact, unless I'm\nreading the code incorrectly.\n\n�scar suggested that to deal with 2), Emacs should simply not regard\ntext properties as changing the buffer at all, but I think there are\nvarious \"rich text\" modes that use text properties to generate the\noutput file (i.e., you put \"bold\" on some text and it gets written out\nas <bold>).  I may be wrong about that.  Anybody know?\n\nAnybody have any thoughts on this issue?\n\n---------\nI feel the need to add this, given the way the discussion went in the\n`M-q' bug report, but let's hope it's unnecessary:\n\n(Let's take it as a given that, yes, you can create hash collisions, but\nthat's irrelevant.  In normal, non-cryptographically-constructed text,\nthe likelihood of two texts having the same MD5 hash is 10^-29 and for\nSHA1 it's 10^-39 (if I remember correctly), so it's Not Going To Happen\nand we don't need to have that discussions.  (And yes, you can construct\nMD5 collisions as fast as you want, but it. is. irrelevant.)  Sheesh.\nThere's something about cryptography that brings out the most irrelevant\nstuff in some people.  If you want to discuss that part, please take it\nto emacs-tangents.)\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_429.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aktU2-0001za-A2\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 09:14:58 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52125)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1aktTz-0001yn-A7\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 09:14:56 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1aktTu-0002Xg-A7\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 09:14:55 -0400\nReceived: from cloud103.planethippo.com ([31.216.48.48]:57358)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1aktTt-0002RE-Vt\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 09:14:50 -0400\nDKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed;\n\td=russet.org.uk; s=default; h=Content-Transfer-Encoding:Content-Type:\n\tMIME-Version:To:From:Subject:Date:References:In-Reply-To:Message-ID;\n\tbh=M++JIXdo2leshWYJLkGW+tXBP7eYf+5M4aVrsKJ+5MM=;\n\tb=PCDh0nwktPwDyS7HEdtVZPEBxs\n\ty+tsnfcKukLA26TVFH4xQnqGoVkfkGKiJZmneD4rGHfQ+9yVyWy5BcwmJdIPXpC3/urjRzaOHP1+n\n\t7dNFUszKljYR2R5Ixh3HkhqK/fAkzr9yeLfPL6oiAxqGQc/Ob6lcz7Yee0P1TdruhR53Wg/srAbr3\n\tGfkWL4p+bhvzVycusKmRBx8ytzsEcCldyYOPkr16Q/3NqZT7M5EKXgCs9ZAoFdOLKw7mztJWWawrs\n\tQ5bqiGUFwj+C0g9ScFGxlkB/3rI/Xtf1s1mD7fcf48FsMnJOpnB4HZPY86Lsr7fJ3Y3bmzIoygeeK\n\tqPuZW4UQ==;\nReceived: from [127.0.0.1] (port=50493 helo=cloud103.planethippo.com)\n\tby cloud103.planethippo.com with esmtpa (Exim 4.86_1)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1aktTE-002b47-BC\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 14:14:08 +0100\nReceived: from 92.28.198.187 ([92.28.198.187])\n\t(SquirrelMail authenticated user phillip.lord@russet.org.uk)\n\tby cloud103.planethippo.com with HTTP;\n\tTue, 29 Mar 2016 14:14:08 +0100\nMessage-ID: <2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\nIn-Reply-To: <m3bn5ya4ro.fsf@gnus.org>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\nDate: Tue, 29 Mar 2016 14:14:08 +0100\nSubject: Re: Thinking about changed buffers\nFrom: \"Phillip Lord\" <phillip.lord@russet.org.uk>\nTo: dev@caliopen.local\nUser-Agent: SquirrelMail/1.5.2 [SVN]\nMIME-Version: 1.0\nContent-Type: text/plain;charset=iso-8859-1\nContent-Transfer-Encoding: 8bit\nX-AntiAbuse: This header was added to track abuse,\n\tplease include it with any abuse report\nX-AntiAbuse: Primary Hostname - cloud103.planethippo.com\nX-AntiAbuse: Original Domain - gnu.org\nX-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12]\nX-AntiAbuse: Sender Address Domain - russet.org.uk\nX-Get-Message-Sender-Via: cloud103.planethippo.com: authenticated_id:\n\tphillip.lord@russet.org.uk\nX-Authenticated-Sender: cloud103.planethippo.com: phillip.lord@russet.org.uk\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 31.216.48.48\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 13:14:56 -0000\nContent-Length: 1344\nLines: 30\n\nOn Mon, March 28, 2016 6:31 pm, Lars Magne Ingebrigtsen wrote:\n> One idea that popped up is that whenever we mark a buffer as unchanged\n> (that is, `(set-buffer-modified-p nil)', we save the byte size of the\n> buffer and a cryptographic hash of the buffer.  Then `buffer-modified-p'\n> would simply see whether either the size had changed, and if not, whether\n> the hash had changed.  If both are identical, then the buffer hasn't\n> changed.\n>\n\n\nRather than doing this in general, why not have a\n\"with-potentially-unmodified\" macro, which calculates this hash and\nrestores buffer-modified-p if no changes have happened. This would solve\nthe \"fill-paragraph\" problem.\n\nAlthough, I would worry about the implications for the before and\nafter-change hooks. fill-paragraph is already problematic here because\nbefore and after changes hooks are not necessarily consistent. In the case\nwhere no changes happen, would we expect before and after change hook to\nrun? Because they would have to, as we do not know whether the buffer will\nchange before we change.\n\nThe only solution I can see for fill-paragraph is to copy the paragraph to\na temp buffer, fill that, check whether it has changed, then if it has,\nsignal before-change, copy the changed paragraph back, signal after\nchange. If it has not changed, then fill-paragraph becomes a no-op.\n\nPhil\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_431.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aktsW-0007WI-IT\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 09:40:16 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60454)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aktsP-0007OR-5E\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 09:40:12 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aktsK-0001TD-34\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 09:40:09 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:53528)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aktsJ-0001T6-SM\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 09:40:04 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aktsI-0000OQ-8L\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 15:40:02 +0200\nReceived: from 23-91-145-163.cpe.pppoe.ca ([23.91.145.163])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 15:40:02 +0200\nReceived: from monnier by 23-91-145-163.cpe.pppoe.ca with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 15:40:02 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Thinking about changed buffers\nDate: Tue, 29 Mar 2016 09:39:54 -0400\nMessage-ID: <jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 23-91-145-163.cpe.pppoe.ca\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:0TEFoif4i8gf2khlnKafgA3qstY=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 13:40:14 -0000\nContent-Length: 754\nLines: 16\n\n> The only solution I can see for fill-paragraph is to copy the paragraph to\n> a temp buffer, fill that, check whether it has changed, then if it has,\n> signal before-change, copy the changed paragraph back, signal after\n> change. If it has not changed, then fill-paragraph becomes a no-op.\n\nBTW, another solution for fill-paragraph is to reimplement it from\nscratch such that it doesn't \"unfill+refill\".  I.e. it could instead\nloop go to each LF in the paragraph, such if that LF is found to be \"at\nthe right spot\", it moves on, if it's \"too soon\", then it removes it,\nand if it's \"too far\" then it adds another somewhere earlier.\n\nThis would naturally lead to the result that the buffer would stay\nunmodified if there was no change.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_433.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akvdN-00013y-O6\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 11:32:45 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47325)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akvdK-0000yQ-Rr\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 11:32:44 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akvdH-0004lA-J9\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 11:32:42 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:41694)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akvdH-0004kW-6B\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 11:32:39 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akvcR-0000G2-OK\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 17:31:48 +0200\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 17:31:47 +0200\nReceived: from larsi by cm-84.215.1.64.getinternet.no with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 17:31:47 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nMail-Followup-To: dev@caliopen.local\nTo: dev@caliopen.local\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: Thinking about changed buffers\nDate: Tue, 29 Mar 2016 17:30:20 +0200\nOrganization: Programmerer Ingebrigtsen\nMessage-ID: <m31t6tcneb.fsf@gnus.org>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: cm-84.215.1.64.getinternet.no\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUYCg0RBgkbDxIsHhxn\n\tTjgKAgPRt28VCAzWTtCKAAACR0lEQVQ4jW2UTXPjIAyGsXc6vpq0LtcE8nFNDcm5u1Cf4yRsz7th\n\t2Ct4JuO/v0K222ZSnTx6eF9JRITQkPObEBKDUBrIFHhEjsBSlvNaiASKQFBgjJakalmHJyAeQIN5\n\tIIS2jIiRABBD3hioUZJkPQcnWxyn/ADGYC2PUt0Dau01h3zjvbsBlbX2qFLe+xtgUxDjR8BebgBX\n\tEygXN+B8+ASs/wJOu+Z7xfHPHcgQWLf+XtH6Z5/m0HfFfTMCtghfrX6PQJLZoKBEvKATAoMAFbQT\n\tCsja//VN49IPxTJQBFJ2UpvV2Xs9AShetIEwLs3+AjbPycjIZMWKCtgPZZzz/rL3ThnNa+iK9SUj\n\t5SldOE7daCNrBIFFUm6UNglcnqCArDleInu3XS01nnfaaS23HK1i1eawBJjfp5ZEByAyEq8UmlKu\n\tgeprEIgu54RGFnpaCfnq3KVxTXKqCz5Y9SQKpZ267F1qSaY9Ix2CTDwpB3O4xqjtBHoES+Pgnv41\n\tWnGZtp7kGwAh48tHqAH+OzmrO1RswCoU/F1JrbSC7c44gpBqhAURDdSAGbSIA2BZAhmT6hdsrJLb\n\tedEhSFfS99lMqjetl+f5blXOBgWCeKxf38TP02PYXNsSAaUAWDzKxUKt5CJr7SHyESTWCXgyNQw9\n\trw58AC1NIKRXud3yEFoyggpBn3NRw4XaeBie+6DoewqERxaraxj+CODlUQwYJjyURYSPCbR0imvq\n\tvAfJHED1AfpiALCXSWE/QCBFOl+MNUBRTRI0CkNX/wGyse1Ht0Yu2gAAAABJRU5ErkJggg==\nMail-Copies-To: never\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:2PuXHYIwaG4E/8wm7mvzmZxEgmM=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 15:32:44 -0000\nContent-Length: 704\nLines: 20\n\nI think the conclusion here is that this would be feasible to\nimplement.  There's a lot of details you'd have to cover, but it's\ndoable.\n\nAnd it wouldn't have to be enabled (for those that enable it) in very\nlarge buffers.  And if so, it'd be fast enough that you wouldn't notice\nit.\n\nHowever, it seems like it would be a rather unusual feature for an\neditor to have, and many people might not like it, so it's perhaps not\nworth doing.\n\nBut one take away from this discussion is that Emacs needs a convenient\ncommand to save an unchanged buffer.  I think I'll file a wishlist bug\nreport for that.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463565.5266_435.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amr5l-00088S-JP\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:06:01 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36150)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amr5f-00080c-T8\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:05:59 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amr5b-00087y-Ur\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:05:55 -0400\nReceived: from mail-oi0-x22b.google.com ([2607:f8b0:4003:c06::22b]:34209)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amr5b-00087t-Pt\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:05:51 -0400\nReceived: by mail-oi0-x22b.google.com with SMTP id s79so46365501oie.1\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:05:51 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:subject:in-reply-to:date:message-id:references:user-agent\n\t:mail-followup-to:mime-version;\n\tbh=g/zGMqsZ3xxfJ1qO3czg2nlBQ873z/+LX8tx5gz57mw=;\n\tb=c98zUh9ClpZRmQhBuu27VkeS4dTT5PxEH6DRCK8bULghHBk71tq6WcrX+BkL9JM6Y+\n\tC+ytD+l+rXDZK+N4OwsVUdHesjHw2HeUu9vn+TYyIACnlXHhv71fsfvk6pGGdMFv8R7H\n\tAOd54d2dVV4+uY9ELNbVeYDOlCR66aYyRXWLwUQRFmNuBZrIqd9Kvbwk1uCouF+ZVDgy\n\tbZ0zYuGYtZqjb23pRbVfs9LYC42KPriXJwqWHiklPaD8SwlYNAELa1xOmcKD9rgMm/hU\n\tXZR0w4ka0umSMKv8VDd5/y5RahCSaUiSifGFrmao2/YrHcChFfX9jqFBvCzr1hWwERXe\n\tPE/g==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=g/zGMqsZ3xxfJ1qO3czg2nlBQ873z/+LX8tx5gz57mw=;\n\tb=ianUjfBu/l3Y5t3wGKnDQNctJLXupaACuEzT5GKAeNA9VhQlXMcs+EKB0moK1/x7fz\n\tSMGiEArUcEy5IEjFPfzy7qI+mej7YBWBkwOmSOHmsun0Jj2QfOdgCe492Lii7K1kiNhN\n\tclvxTEFsqXH3GTnDZ6G7nF7yb+MWFp5GXiPZ60u7Jza7czWKn7QHrL5gsh0QFDChsWYD\n\tMVSHemf/Lg+xxC3yfhagDm4gMDsuyXDw2INTKWGaz10TjTlnPdjyVZOeI8XuTuf18If3\n\tPNA2VS9nG0ep9fn2E1f+KBPzX3scAYZ8prcopVzh43Bl2mEwGpV+hhJLEvIZsLfS9MOm\n\tvgBQ==\nX-Gm-Message-State: AD7BkJJC1pnSwQvgHRcpLslJ2coztufl+nIYecxZelgErPu29qZHhQuQiMPO/wjkwnio6A==\nX-Received: by 10.202.53.198 with SMTP id c189mr8509369oia.129.1459724751362; \n\tSun, 03 Apr 2016 16:05:51 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79]) by smtp.gmail.com with ESMTPSA id\n\tt141sm1263335oie.18.2016.04.03.16.05.49 for <dev@caliopen.local>\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 16:05:50 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid EBBEE13DAE277; Sun,  3 Apr 2016 16:05:48 -0700 (PDT)\nTo: dev@caliopen.local\nSubject: Re: Thinking about changed buffers\nIn-Reply-To: <m31t6tcneb.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Tue, 29 Mar 2016 17:30:20 +0200\")\nDate: Sun, 03 Apr 2016 16:05:43 -0700\nMessage-ID: <m2mvpafg3c.fsf@newartisans.com>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m31t6tcneb.fsf@gnus.org>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: dev@caliopen.local\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::22b\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:06:00 -0000\nContent-Length: 436\nLines: 12\n\n>>>>> Lars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> But one take away from this discussion is that Emacs needs a convenient\n> command to save an unchanged buffer. I think I'll file a wishlist bug report\n> for that.\n\nC-x C-w RET will do that, and I use it often for that effect.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_437.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amrSP-0006FC-8Z\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:29:25 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40653)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1amrSL-0006E0-H3\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:29:23 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1amrSH-0004pz-H3\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:29:21 -0400\nReceived: from mout.kundenserver.de ([212.227.17.13]:63731)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1amrSH-0004pt-7G\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:29:17 -0400\nReceived: from [172.16.0.149] ([87.82.210.162]) by mrelayeu.kundenserver.de\n\t(mreue102) with ESMTPSA (Nemesis) id 0LheU5-1bYpP30nj7-00mqhK for\n\t<emacs-devel@gnu.org>; Mon, 04 Apr 2016 01:29:16 +0200\nSubject: Re: Thinking about changed buffers\nTo: dev@caliopen.local\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m31t6tcneb.fsf@gnus.org> <m2mvpafg3c.fsf@newartisans.com>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <5701A74B.2050902@gmail.com>\nDate: Mon, 4 Apr 2016 00:29:15 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <m2mvpafg3c.fsf@newartisans.com>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"5S8owht2S2hKcPwxewfi430AtCBflCj7e\"\nX-Provags-ID: V03:K0:MCCaRAAL/LrAX56ujFDh3xCDJIu/mX2BDBGlU1iLyW8gCQ02B43\n\t8hw7rzoN34CQ6NG8+P0Z+PV68JyIhYBQUhXtHcAXSAkHL/R0i0OqphVbxrjD8ntxOnzykZT\n\tY1ltGkgYeViOrJTXJzYSWg2b9DE2YpmiCmm/dcDmeffNrw5EMSMHvwjomfi8MEpK5ixHA6t\n\tiWt/6Ng4xf1PaHMWBgKUg==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:Av+0Jia9IOw=:93e+jiLookaO6we5rLIXLZ\n\ticS8bNMzzahg49g9lBWjSVQneXMpDY4A0oskNY3EhtxDh+NDr2cdpNbEccsvaWu9o6j2ntVFq\n\tE9ChP0fRND4K4EjzGwipDVn5fNi3qeCJdsKqH+XFLE9vKUx0Z/TE0cifcXJ78ypHuljOZFDPB\n\tNgpya+IWPTxEHkvrI5oiKcg9bB4ZELV6a4Cizo5uYK6ydLn9Gfls205fmOrC1z5DKEuKo+btg\n\td47JwKsnRGf0uld/24CqkYsCYvMvquNaUKm4LbxLUD+qG7txypgFDRidaW4RVvzWr5TOCHL2+\n\twcYqUVgl8UB1MlPTMyXC+5w++HxcJrBvZcQFLJeXfOocAmaQCK4hT0Z52LdX/J+Dsq03CZubl\n\t1MJNJdytwgV2/uc6dHs0R4kiIL7zH/bVtdVcfg52VZboY4jafvIoIaj5CKylzwDHMbdu4EoB9\n\tHkpc54HEtIFx3GuNwqB3KOlRMJdqSw1u3iNRqDMn9Q7PfJAEaQRla3yhqb7/duVogD6RkYoY3\n\tjFUW4DK5fSoMOY6FCNFBSnQ7vv7h7YzOXzYxZAjEUUnKdtzQvNeuxBoXHjLYYTi+2zNYs4zYU\n\trfFgAjeG3quoXlECBadWgkvalJOW8hMaMk2jipB7MBb6eMusoB5Pwq63Tt/FlOVcREhv5qGMk\n\tfKVZya3uchzOCx6A4b4p7+y8yfoOYz5O9aJCxikGHOeaeSuwteRug/lofnzesa+diXu8=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.13\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:29:23 -0000\nContent-Length: 2348\nLines: 59\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--5S8owht2S2hKcPwxewfi430AtCBflCj7e\nContent-Type: multipart/mixed; boundary=\"oLmrT2t9qqss9e4jeWbUQdQgCFMi21iNI\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <5701A74B.2050902@gmail.com>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n <2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n <jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org> <m31t6tcneb.fsf@gnus.org>\n <m2mvpafg3c.fsf@newartisans.com>\nIn-Reply-To: <m2mvpafg3c.fsf@newartisans.com>\n\n--oLmrT2t9qqss9e4jeWbUQdQgCFMi21iNI\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/04/2016 12:05 AM, John Wiegley wrote:\n>>>>>> Lars Magne Ingebrigtsen <larsi@gnus.org> writes:\n>=20\n>> But one take away from this discussion is that Emacs needs a convenien=\nt\n>> command to save an unchanged buffer. I think I'll file a wishlist bug =\nreport\n>> for that.\n>=20\n> C-x C-w RET will do that, and I use it often for that effect.\n\nYou need two `RET's, actually, don't you? And it shows a scary overwrite =\nmessage. Also, it doesn't work with ido-mode, unfortunately.\n\n\n--oLmrT2t9qqss9e4jeWbUQdQgCFMi21iNI--\n\n--5S8owht2S2hKcPwxewfi430AtCBflCj7e\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXAadLAAoJEPqg+cTm90wj0MsP/RaKzXXJPn6VeKktpq65Nbi2\nmNq++krfgbuUKHUY+NYY+fMnpSlG6h1SxWL1wpTab3yhJ5ZBGnsji4XsmgtQD451\nHAI4Z0xyeydj1IpGp3iUmJ9FbHq/6OnOWu8wHOxd7emiqWYHT4MAbazlL1wzK0t7\nOnSPyGcxb4CMc8Oz36sPMs+8pCS9fIm6UBlwlLOW/Aclsd7BahzugRpG1TUVg1ZJ\nzI7osYs/jOV7zjt+C/E8hDYvdcgWrWXD58cdjG1zfiso9Gp/vC/Ra6xr8e5mwSxP\n+CRK+f4FG4nECkvRKR8M7zeItNpo7dnPFFqsi2+IHxicwwIxuKxE0fRUTu8HeMdF\nFilNH7+8QmfKub7sikthy7UCCbvFdVK7PbinWMR2jW2jCH570Fk5fDE2VgnSZVtR\nstufEGSO9NUHhs6DoKGXrGCm5jWL/7RlE4B4r/6rXsaGp5GXfTTt9Y9aXqwL3xd2\nW/aWwnLB+PkfaqdwBSHrIaQmmdZBSlgiLRd62mufz5yxDCvtuuBtbYv4blBYzzHD\nKR4uEJHKpLEDe3FAKt9VwbPVErzYJn4UMLsP0/rL3zdeeCaSGgbQG0/iDR8m0nax\netuQXqjqi1mDyUveRkcFQJJ62sYckG0hKVdq6gCft26g/oDnTYWqG+XMXgcbY4dj\nB4P34Du6BdFoZesUOtrc\n=/RHp\n-----END PGP SIGNATURE-----\n\n--5S8owht2S2hKcPwxewfi430AtCBflCj7e--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_439.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amrhN-0002D7-VY\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:44:53 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:43550)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amrhL-0002Bj-3G\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:44:51 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amrhH-00089h-TI\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:44:51 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:48268)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amrhH-00089W-Mk\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:44:47 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amrhF-0004tL-GF\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:44:45 +0200\nReceived: from 151.red-79-153-146.dynamicip.rima-tde.net ([79.153.146.151])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 01:44:45 +0200\nReceived: from ofv by 151.red-79-153-146.dynamicip.rima-tde.net with local\n\t(Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 01:44:45 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: =?utf-8?Q?=C3=93scar_Fuentes?= <ofv@wanadoo.es>\nSubject: Re: Thinking about changed buffers\nDate: Mon, 04 Apr 2016 01:44:38 +0200\nMessage-ID: <87bn5q2r6h.fsf@wanadoo.es>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m31t6tcneb.fsf@gnus.org> <m2mvpafg3c.fsf@newartisans.com>\n\t<5701A74B.2050902@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 151.red-79-153-146.dynamicip.rima-tde.net\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nCancel-Lock: sha1:JZtB6TfGDNCeztyTSm0xhnT3eMQ=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:44:52 -0000\nContent-Length: 878\nLines: 30\n\nClément Pit--Claudel <clement.pit@gmail.com> writes:\n\n>>> But one take away from this discussion is that Emacs needs a\n>>> convenient command to save an unchanged buffer. I think I'll file a\n>>> wishlist bug report for that.\n>> \n>> C-x C-w RET will do that, and I use it often for that effect.\n>\n> You need two `RET's, actually, don't you?\n\nTo be precise:\n\nC-x C-w RET y RET\n\n> And it shows a scary\n> overwrite message. Also, it doesn't work with ido-mode, unfortunately.\n\nFor avoiding the scary overwrite confirmation you use the prefix\nargument:\n\nC-u C-x C-w RET\n\nThose of us who use ido-mode:\n\nC-u C-x C-w C-j\n\nIMO, those sequences are above of the threshold of what can be\nconsidered convenient (definitely not recommended if you are prone to\nsuffering from RSI) and I expressed my preference for a new shorcut.\nHowever, others were quick at dismissing that possibility.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_441.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amwwz-0000Mu-OZ\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 01:21:21 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59759)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1amwwx-0000Mj-U8\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:21:20 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1amwwu-0000Id-OS\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:21:19 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:51324)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1amwwu-0000IZ-HY\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:21:16 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1amwwp-0005SC-LV; Mon, 04 Apr 2016 07:21:13 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: =?iso-8859-1?Q?=D3scar?= Fuentes <ofv@wanadoo.es>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m31t6tcneb.fsf@gnus.org> <m2mvpafg3c.fsf@newartisans.com>\n\t<5701A74B.2050902@gmail.com> <87bn5q2r6h.fsf@wanadoo.es>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAD1BMVEX+AA+Qb6f/AAAL8frw\n\tEDRIFBcCAAACXklEQVQ4jV1Ti5XcMAiUdSrArFSATFTAEVyAPvRfUwb5bnOJ39u3tkYMMAwhvJ80\n\t/r6pBn4/bb1fDYCZxeer5tcbyBuYXxEqWT+f13+AD8UjCcf/AdkBvdmG9bGB/cxrn+tvAMUOB3qy\n\tbmku8FCsJZqV3nKwTWYp+vVRWAu+UeGm6t0aelIzFdVCxLyCJWtO5Uw3frhAFPn6St4OBJBmFRT3\n\tuiY3RAyiXDzAhIjw9VqQAFpleQolJIUaXM98fgEZhyMTfXV65nsDqgsUu0oouZUqD0BjqzQ8ogPo\n\thR6gn7skKhIbsNoN9TpQthxDUi7O1uqSCaBqYQh4Z2mkPqrAGSMLpYlZUhIAlkHOkQc0C3lAtkk3\n\tmm5m9EJdNZMDt5+kBaDaXLLLFQMgKjGZV8xjNjcEMpYYILSc4MBgBy4u9ParjBsAQW7DnHHDiBta\n\tyCLCiMC/2VUATfQOLkhBTpU1l14BCh8nr093BU0AS5fECk+c3ISveyFCygaK9lmTRdDPy4eAJ7h8\n\td7azgq/j/vXYQQG8GL4elqUMTI18Aig+oA1ubqyltmACGAJUAMhLSMcJY9G4IYfbAYfBrUx0QCgt\n\tQztJBIjDsKvWnrvp3b0QiFh81RoJej7HC9knLmG2ww0V2KhXvWtB9jNjmp7Gc0S2DgfNA8rfS6D6\n\t4SrtrU1V9bNhi+QDKtZnsxyoCcXHBOqjeMA22t7zfl6oBsDoexW/qTjxL2S54Dm0jsTlO6JGrLJw\n\tyvDbOPSdAx5bLp3BiK1cZ96dx587PhnLxMslDvMHQM9+XLvB1xsY29JuOF+xP6y7tHae4BG4AAAA\n\tAElFTkSuQmCC\nDate: Mon, 04 Apr 2016 07:21:11 +0200\nIn-Reply-To: <87bn5q2r6h.fsf@wanadoo.es> (=?iso-8859-1?Q?=22=D3scar?=\n\tFuentes\"'s message of \"Mon, 04 Apr 2016 01:44:38 +0200\")\nMessage-ID: <m3shz254qg.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain; charset=iso-8859-1\nContent-Transfer-Encoding: quoted-printable\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 05:21:21 -0000\nContent-Length: 908\nLines: 24\n\n=D3scar Fuentes <ofv@wanadoo.es> writes:\n\n> Those of us who use ido-mode:\n>\n> C-u C-x C-w C-j\n>\n> IMO, those sequences are above of the threshold of what can be\n> considered convenient (definitely not recommended if you are prone to\n> suffering from RSI) and I expressed my preference for a new shorcut.\n> However, others were quick at dismissing that possibility.\n\nI think it's a psychological issue.  If you've accepted doing convoluted\nthings for years because your favourite tool lacks obvious\nfunctionality, then it's quite common to feel that that should never be\nfixed.  Because fixing the problem would mean accepting that you've done\nsilly things for years as a work around, and the obvious fix becomes a\npersonal insult, almost.\n\nOr perhaps it's just that it's Monday and I'm grouchy.  Ier.\n\n--=20\n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_443.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amx2G-0001uL-Vw\n\tfor mharc-dev@caliopen.local; Mon, 04 Apr 2016 01:26:48 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60393)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amx2D-0001tN-2u\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:26:47 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amx29-0001LA-4V\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:26:45 -0400\nReceived: from mail-oi0-x232.google.com ([2607:f8b0:4003:c06::232]:36211)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amx28-0001L5-W2\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:26:41 -0400\nReceived: by mail-oi0-x232.google.com with SMTP id y204so37003067oie.3\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 22:26:40 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=r5J1OXHWpDOVYOrUWaqKonwuEMEihuw0rK5Xw8xNj34=;\n\tb=vaFE61lfcRBT2V1EHuGCCV8M3NXnyFbpA2whlRzMzMKG1WCUueIZbTkfOvoM7XoPC3\n\t0CnWfT96NWILpcSAifakzA/rsA7NUwiK2QdcGASWsT/BbTa+HmsRPOIKbLwbhOXhtn8v\n\tH45ty6aMihQBIfi8WjVjL50uoLAkuOwFKji2QEG+qjqUkfJgntnSSAoaifXTmtoQKLQ8\n\tMdpgX18Oj2RNzJvWVKalVuSUtRmTWV4q9W96fpkwL4afRgK2nN+uYUhYwtZ0htvePpii\n\tSaqhMTxkbMVaudwtEzuFkRheAPHKsPa99bGPaYQlfRnSx2Mo34I7yCGzQVJp5sQjvJPK\n\tvxDQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=r5J1OXHWpDOVYOrUWaqKonwuEMEihuw0rK5Xw8xNj34=;\n\tb=lc7qE6ON8HnUiieEnRevu2EgShpWUed87EOSXP26UR8qM549A5RanC2AgYUg+HqVOk\n\t+pQ6YJmdnXo+c0S3nw9zA9yP1zEwcAWI+ayWdS+ByodWwVzkQQO1opnfY6Vq1mHxthOc\n\tAHimRAF8EF8N9hof+PkkQC6UTZ/nEAG0dTwOG7mrUwjH/+1D6afDTQr69lQYMon2704o\n\tRQs6yH5IkAMftJfPYHjP9SGnwlUcd4juNSMJ8G8FbaCIKYTT6CR6aW9p/XpVJZ/gjSHv\n\ttq4ZEAjaD4cdmKjX2rw6nRk2u16xCTWD0hP0XlgnEOdVZf6g80iZr9QwTg/+CEbOwsr0\n\tDtug==\nX-Gm-Message-State: AD7BkJIux8TvfOJGylFc6jLnbkB/5ZEep0GKbvtNbRQZU52EyhDsPxgbMXeyP/gH+HBomw==\nX-Received: by 10.157.24.103 with SMTP id t36mr5377536ott.101.1459747600505;\n\tSun, 03 Apr 2016 22:26:40 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79]) by smtp.gmail.com with ESMTPSA id\n\ted8sm7849301obb.19.2016.04.03.22.26.39\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 22:26:39 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid B0A7B13DB0964; Sun,  3 Apr 2016 22:26:38 -0700 (PDT)\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: Thinking about changed buffers\nIn-Reply-To: <m3shz254qg.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 04 Apr 2016 07:21:11 +0200\")\nDate: Sun, 03 Apr 2016 22:26:35 -0700\nMessage-ID: <m2fuv29c6s.fsf@newartisans.com>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m31t6tcneb.fsf@gnus.org> <m2mvpafg3c.fsf@newartisans.com>\n\t<5701A74B.2050902@gmail.com> <87bn5q2r6h.fsf@wanadoo.es>\n\t<m3shz254qg.fsf@gnus.org>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Lars Magne Ingebrigtsen <larsi@gnus.org>,\n\t=?utf-8?Q?=C3=93scar?= Fuentes\n\t<ofv@wanadoo.es>, emacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::232\nCc: =?utf-8?Q?=C3=93scar?= Fuentes <ofv@wanadoo.es>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 05:26:47 -0000\nContent-Length: 785\nLines: 17\n\n>>>>> Lars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> I think it's a psychological issue. If you've accepted doing convoluted\n> things for years because your favourite tool lacks obvious functionality,\n> then it's quite common to feel that that should never be fixed. Because\n> fixing the problem would mean accepting that you've done silly things for\n> years as a work around, and the obvious fix becomes a personal insult,\n> almost.\n\nI think the first part is certainly true. I've become so used to a silly\ncommand sequence that I see nothing wrong it; but if I had to explain it to\nsomeone else, there would be some shame. :)\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_445.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amsFz-0007ho-3d\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 20:20:39 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46769)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1amsFw-0007hI-1J\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 20:20:36 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1amsFs-0005mf-KO\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 20:20:35 -0400\nReceived: from mout.kundenserver.de ([212.227.126.134]:62661)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1amsFs-0005li-9s\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 20:20:32 -0400\nReceived: from [172.16.0.226] ([87.82.210.162]) by mrelayeu.kundenserver.de\n\t(mreue004) with ESMTPSA (Nemesis) id 0LhRTw-1bZ5Bf1K5o-00mZqi for\n\t<emacs-devel@gnu.org>; Mon, 04 Apr 2016 02:20:31 +0200\nSubject: Re: Thinking about changed buffers\nTo: dev@caliopen.local\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m31t6tcneb.fsf@gnus.org>\n\t<m2mvpafg3c.fsf@newartisans.com> <5701A74B.2050902@gmail.com>\n\t<87bn5q2r6h.fsf@wanadoo.es>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <5701B34D.20709@gmail.com>\nDate: Mon, 4 Apr 2016 01:20:29 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <87bn5q2r6h.fsf@wanadoo.es>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"K57C9PM7cUum8dBM1U7QeERlswf7fwTtq\"\nX-Provags-ID: V03:K0:GD6co53/MhLCMAWt5IyxU1BkX3Kxuslko06ix9saGXqEubsTcKy\n\tGZfdC1Q1DNFfM9s5sB2Ta590/n6k7fq+agkc8fl/EGA2yc9rRBaSXEgzWYhyJmNE0y4ieG4\n\tPy3BEzDtdvOs02a/LBWRjDP1GMBXG+8+k5Xyo4Ji/KVtFtBC4I/PsLfbXGWWWhZsyhYrgOD\n\tYlA8azU44GkCEP52o+kig==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:vm+iEwTQ9ac=:6aMx1vfp2tg4lA+tRBtOti\n\t+jsdMdopqiUstto4xj+1UyyKkbgDWlD0UjqB20BKcjgMpOZplcj9G/N1Y6hgnvAg0yz+bFSFE\n\t0O1vUJvs5niEa5cn0eX2U811E9qhJ6r2lXN+UtoSAZINFkYr4Td2qXYvVbttBNB/Izu2Dhtr6\n\tG6m/1bWCWw/ZXW2JIaxrp+uIsKGgjDJntgj29rjEvJ5/Lxw8o+kE10zD/e0infZSildJqJwU8\n\tEsxvpfWBbCRuXgCchViGWvrN7q853pQxB1802tvBMqRosYYGI+Gb/xqzrOX92S4QcB3RtBPie\n\tiV3LYEZOzOdAGiRe98PfxYDZOGFUydSBKb+klA+osK8Q5oLQYc7vTlmUjc6Fb/8J22ES5ZnMy\n\tAEBJ4GylmtpvZ8jVKSitTMmV8oZ+/0ZXv86z5nlAevgpLzYkBHsbl8J2JgbHNLh2n3lRpPOs5\n\t2wZuhMN1uaMJ9UcFX2e3H/x/gbk8aESYCzrVDIxeduZtlK3Hyjhh7sSFyLv79PinUnzlOaQ7C\n\tLCqhF0gxozZCXzmzIT+fnbvFTSeVs27jPsW+hi3oi7SMi4aFvXP/Al69ZKXMtqfLra5GFP/ls\n\tlYU57gEHOYHFjjhRcILgkHmKQRVyYLsb42WeBmY4utuFs4Ie4b31gvfZnczhUxa4h5UlLxUCh\n\tt7YcKuFr/lKAfqX0kj8pQVcUn5gpdkV4yzT0EVMQvXXILlasyRtWAzX0rGPpMFMGUw0Q=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.134\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 00:20:36 -0000\nContent-Length: 2367\nLines: 61\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--K57C9PM7cUum8dBM1U7QeERlswf7fwTtq\nContent-Type: multipart/mixed; boundary=\"EwBbLAnXBjnkL88vt1jcETScTARbuSKWg\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <5701B34D.20709@gmail.com>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n <2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n <jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org> <m31t6tcneb.fsf@gnus.org>\n <m2mvpafg3c.fsf@newartisans.com> <5701A74B.2050902@gmail.com>\n <87bn5q2r6h.fsf@wanadoo.es>\nIn-Reply-To: <87bn5q2r6h.fsf@wanadoo.es>\n\n--EwBbLAnXBjnkL88vt1jcETScTARbuSKWg\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/04/2016 12:44 AM, =C3=93scar Fuentes wrote:\n> C-u C-x C-w C-j\n>=20\n> IMO, those sequences are above of the threshold of what can be\n> considered convenient\n\nAgreed :) What about a setting that would make C-x C-s always save, regar=\ndless of whether saving is actually needed?\n\nAlso, I'd argue that it's almost a bug that you get a overwriting warning=\n using C-x C-w when you're writing to a patch equal to buffer-file-name. =\nIsn't it?\n\nCl=C3=A9ment.\n\n\n--EwBbLAnXBjnkL88vt1jcETScTARbuSKWg--\n\n--K57C9PM7cUum8dBM1U7QeERlswf7fwTtq\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXAbNNAAoJEPqg+cTm90wjiygP/REhS8lZk0fERTDUdtPGFCbj\nNniq5VPuPJIQDH7oUOJFwmaxxPRlbPOaL/ml9kphvVrVZO3YBH/L04Qb53/iFAlz\n0BwZksY8bdD1oCU2Qj2d6zhT4zBx4UaIFaj08iDn6/6BXkKo3A2wOwUY/4p3Wfhv\nDIA+5hDtS3CqOEdF1/ObJnK6zIoJ87V0RGiAK6eyryf/Tw0MP1d3jUy3H/6hpyBa\nXwsx133+DocSGIkPCopZddNNBz79r35eYB2HA+A1XSfwGLaRLdmBaN4sE3qGTiAQ\ns7PJ3hwOI+nlFeJG6WWE5tP7F/UoGjyh54VAfA+PzwJ9UfriycMJWP2Tr3KHGpx/\n5n0RK0xKzVDkIGzs5bqu0jinzEw6CpUhj49aDQ4wgBPc/a5rK5V5QWOdibfgSeRK\n4zyejrhceSjcJ3FkxziEdU0632tAuTrZzRKMFGvAMFm51smE9jQKHM3zJ8Hn4kdZ\nnHNHIN5XgBbIP0mXtcZSBGlV2gyH8kGeD7MdpBBh+t5jussuPFufHRG6Okid/gGi\nthALD7HIW9nfOsOfUtc/ghs38zP1ypGFvFoOv7D9gJ+tt0E/rl+jqdnAawpklX0Q\nIRHavheJvA6DnpRv3J/4e0xoORVlUZur6SQlQIHAt8Q6625C/e8GVSzlXkeo7xvF\nqt8QEcDFwbep90lyGDN7\n=HLTZ\n-----END PGP SIGNATURE-----\n\n--K57C9PM7cUum8dBM1U7QeERlswf7fwTtq--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_447.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amsOU-0000YN-CZ\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 20:29:26 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47589)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amsOR-0000XT-RK\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 20:29:24 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amsOO-00077D-K3\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 20:29:23 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:49180)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amsOO-000778-Do\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 20:29:20 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amsOL-0007Sc-I4\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 02:29:17 +0200\nReceived: from 151.red-79-153-146.dynamicip.rima-tde.net ([79.153.146.151])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 02:29:17 +0200\nReceived: from ofv by 151.red-79-153-146.dynamicip.rima-tde.net with local\n\t(Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 04 Apr 2016 02:29:17 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: =?utf-8?Q?=C3=93scar_Fuentes?= <ofv@wanadoo.es>\nSubject: Re: Thinking about changed buffers\nDate: Mon, 04 Apr 2016 02:29:09 +0200\nMessage-ID: <877fge2p4a.fsf@wanadoo.es>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m31t6tcneb.fsf@gnus.org> <m2mvpafg3c.fsf@newartisans.com>\n\t<5701A74B.2050902@gmail.com> <87bn5q2r6h.fsf@wanadoo.es>\n\t<5701B34D.20709@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 151.red-79-153-146.dynamicip.rima-tde.net\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nCancel-Lock: sha1:kzucfQGUbsrzy/yofCIOQMcPP1U=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 04 Apr 2016 00:29:24 -0000\nContent-Length: 455\nLines: 13\n\nClément Pit--Claudel <clement.pit@gmail.com> writes:\n\n> Agreed :) What about a setting that would make C-x C-s always save,\n> regardless of whether saving is actually needed?\n\nAn option for `save-buffer' saving inconditionally when called\ninteractively would be useful.\n\n> Also, I'd argue that it's almost a bug that you get a overwriting\n> warning using C-x C-w when you're writing to a patch equal to\n> buffer-file-name. Isn't it?\n\nYea, a QoI issue.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_449.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amrZg-0000Ie-CQ\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:36:56 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41896)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrZe-0000IP-8e\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:36:55 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrZb-0006F3-2j\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:36:54 -0400\nReceived: from mail-oi0-x22b.google.com ([2607:f8b0:4003:c06::22b]:36415)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrZa-0006Ex-UD\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:36:51 -0400\nReceived: by mail-oi0-x22b.google.com with SMTP id y204so30003374oie.3\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:36:50 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version:content-transfer-encoding;\n\tbh=aeA4tMR+X+GLZV7VE6F4GwQXlW3IX424fymp5O/TYdo=;\n\tb=R7FNo2vmQjhDm56I6FewMcFbpxmXGrPZL6tveSxVAMNpX9WmpLAbaZcOugkI92gcNE\n\tEuK0X40+1O7gjO+ZH2m6kL/rFJW2QdP/adiZlHmclQ2Xsp/0cJNGYj0GoPia63ZSPqVv\n\tJLqI+bCixNUBKjLhsv05Xmua2LTsHHCxgIJEQ55Br6OKyOviF1XNJRG2Bj6pYza/Bapi\n\twIXW1EsinkU0Z8QHspIJRvfj/P5P+rj4bICDvITY/LL4GfzxRaHC1EF/mdM9yJJptXnc\n\tgtC+3TjYJbroECrSNWp1P7YOa72bONvLYO3YwcG8/OWgfnClmLVYJpBBdTYtD8hXGUn4\n\tBU/g==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version\n\t:content-transfer-encoding;\n\tbh=aeA4tMR+X+GLZV7VE6F4GwQXlW3IX424fymp5O/TYdo=;\n\tb=JNsgcib4DMNzcLFHFeGL+IPZMKDcOrVQ7f6Bnhe0jI5KBoSKKvSBftJYNx7AXQ+9Af\n\tuttseIkqvQM8vf7hUP7x6xHMMIutkeBwaJJI3EIpSW/uYomVbydberQhDULOmt+tXVc9\n\t/kkzlfLGkdsTooJxJLAR/IaYEg7jdVdWGD8l+zT78fh+Q5l6JlOxZW4aX8TJM1nPShde\n\tTX4GjFEz+laXB8QqsGZju5e6Klmc6xjtdu99B2+bV49yx+XqNBaMkBw8fqVafp0wDERb\n\tjcrwmhq8uWxBCQjKEvHsaEeZm8wAvWDe40bipx2vD8Hwt3yv3Qx37a4roPcF14N4umLj\n\tByPA==\nX-Gm-Message-State: AD7BkJLLoAkiiXChOc4h5RGk4ReitV0mUX2x5kNKEtV1XFRHzBiwRJ4W+uqIrU+C46NDRQ==\nX-Received: by 10.157.15.136 with SMTP id d8mr9120667otd.20.1459726610317;\n\tSun, 03 Apr 2016 16:36:50 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79]) by smtp.gmail.com with ESMTPSA id\n\tj45sm5356699otd.23.2016.04.03.16.36.49\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 16:36:49 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid 7A0F613DAEA03; Sun,  3 Apr 2016 16:36:48 -0700 (PDT)\nTo: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel <clement.pit@gmail.com>\nSubject: Re: Thinking about changed buffers\nIn-Reply-To: <5701A74B.2050902@gmail.com> (=?utf-8?Q?=22Cl=C3=A9ment?=\n\tPit--Claudel\"'s message of \"Mon, 4 Apr 2016 00:29:15 +0100\")\nDate: Sun, 03 Apr 2016 16:30:28 -0700\nMessage-ID: <m2fuv2clt7.fsf@newartisans.com>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m31t6tcneb.fsf@gnus.org> <m2mvpafg3c.fsf@newartisans.com>\n\t<5701A74B.2050902@gmail.com>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: =?utf-8?Q?Cl=C3=A9ment?= Pit--Claudel\n\t<clement.pit@gmail.com>, emacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::22b\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:36:55 -0000\nContent-Length: 500\nLines: 13\n\n>>>>> Cl=C3=A9ment Pit--Claudel <clement.pit@gmail.com> writes:\n\n>> C-x C-w RET will do that, and I use it often for that effect.\n\n> You need two `RET's, actually, don't you? And it shows a scary overwrite\n> message. Also, it doesn't work with ido-mode, unfortunately.\n\nAh, maybe. And for ido, I think you just need to use C-j in place of RET.\n\n--=20\nJohn Wiegley                  GPG fingerprint =3D 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_451.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amr5h-00081R-BY\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:05:57 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36139)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amr5e-00080V-A8\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:05:55 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amr5b-00087p-4y\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:05:54 -0400\nReceived: from mail-ob0-x22b.google.com ([2607:f8b0:4003:c01::22b]:36568)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amr5b-00087l-07\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:05:51 -0400\nReceived: by mail-ob0-x22b.google.com with SMTP id j9so39363239obd.3\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:05:50 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=WVgZdkUS37ucciyJIPyxPDBf5LrxmkdNtvd14uUP4+g=;\n\tb=ZxCSIu0sTgbUAiwUYtgkMtIGxTLnbkxci44J1yQl891++Z8MopYom/l1LIw/JjaZgZ\n\tXeTdFjAP0atQel1kAzoW0TcQUEt3DhMCtC8ebiB8eQJQSvBJ8mbWLKXTeV1MnWInfEpv\n\tZmujVbgcUarc/xNT2kY+ZPio2HSPcjEkXGcIIh72FZuLcjEnXgPRXHCd9qS71IZoZBHP\n\tgRWabul+PYcbR85AbFgNVC67Jn+i5erOPS0V8RLj1YGADG0cuNFSriA+cQdlnW3ed/Tl\n\twBHBZbGyB+Ck3jjvNAPEly0zcsejTfjDeKYblO5Ro8KM2SnMrG8cFCMK8NZ1NrI6we06\n\tP/uQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=WVgZdkUS37ucciyJIPyxPDBf5LrxmkdNtvd14uUP4+g=;\n\tb=ePMtdV0x9+N7NkMCnNXmmboDKGAt4H2UgLqhRg1DPEspCDtaX7Tu9ZLHTgIiuLp4AD\n\te5F3EYM1iMJ76SdX+Il1vJ+zKA0A6pTzvob1f39Hop8s8+AVPe3YKRgBTg43H/R7gvwK\n\tqsBVaBsSGGLM+r0fhfTZFhRQ3kCF3zWrOS4nChV4VTNH/+tqllnS7UPDeejZDxqxt7X+\n\tokTikBko6B48D+ZepUdAljx806lDvFQ6vBkOaR+RoTcbagLht62bTSaNzc4ar+QVb32Y\n\tt6MZah/jQwzvTRITkldI+6zQfpZUnqOz+kJb+5FjaVuMeE7Mnyd0qpuupZbgQoJievpU\n\tm62g==\nX-Gm-Message-State: AD7BkJJxz4e7JOR+wP2Nll1rKjlsqcOSe3JZP4FOBEmzFTRaf49u+TPmxMVLyHMFgYBRVw==\nX-Received: by 10.60.176.100 with SMTP id ch4mr9010841oec.7.1459724750477;\n\tSun, 03 Apr 2016 16:05:50 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79]) by smtp.gmail.com with ESMTPSA id\n\tl17sm5881739otb.35.2016.04.03.16.05.49\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 16:05:49 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid A7F7613DAE274; Sun,  3 Apr 2016 16:05:48 -0700 (PDT)\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Thinking about changed buffers\nIn-Reply-To: <jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Tue, 29 Mar 2016 09:39:54 -0400\")\nDate: Sun, 03 Apr 2016 16:05:09 -0700\nMessage-ID: <m2r3emfg4a.fsf@newartisans.com>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Stefan Monnier <monnier@iro.umontreal.ca>,\n\temacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::22b\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:05:55 -0000\nContent-Length: 664\nLines: 15\n\n>>>>> Stefan Monnier <monnier@iro.umontreal.ca> writes:\n\n> BTW, another solution for fill-paragraph is to reimplement it from scratch\n> such that it doesn't \"unfill+refill\". I.e. it could instead loop go to each\n> LF in the paragraph, such if that LF is found to be \"at the right spot\", it\n> moves on, if it's \"too soon\", then it removes it, and if it's \"too far\" then\n> it adds another somewhere earlier.\n\nI too think this is the best solution, and I believe it was mentioned in the\nbug thread on this subject.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_453.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1al26O-00031U-HJ\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 18:27:08 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38618)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1al26M-00031N-3g\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 18:27:06 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1al26I-0006Fm-Um\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 18:27:06 -0400\nReceived: from cloud103.planethippo.com ([31.216.48.48]:44901)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <phillip.lord@russet.org.uk>) id 1al26I-0006FN-KP\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 18:27:02 -0400\nDKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed;\n\td=russet.org.uk; s=default; h=Content-Type:MIME-Version:Message-ID:\n\tIn-Reply-To:Date:References:Subject:Cc:To:From;\n\tbh=KEmpGEWmeyvQ2q8dF8brqEzgKZC6C3G7R7J8UHv44os=;\n\tb=CfwHigw03Yf0fMWibWBaqlgwmn\n\tsmlO4yLJcpEJwGNsiw4O/GP1U2JhHfGsMp4IY6w6koi1H3yu4J9gLobC+cjvQpcWTfmBD7XKWoFzS\n\t2p+skRDECLkSx6f9/qQ8ce0AlH/AZqL0F1Ntjt975g8qSQ6fJua5OBAu1wjrc0rLWuuDd+YX/jybe\n\t/VW9vy1F4bJGNOjIIk32QQlZlZKs07pTRp2o3t8qRs0OlqOKa2e1hW8acBZNS6KWQ2hkXzKKFm2DA\n\tWgIm4WpOAGG5w5zGY1NSl5f/QIzWJpgKvmUmtvkBzx692EsEQ4U8XNCqmY97siyhIYbAOfGLldFza\n\tyaYxTHtg==;\nReceived: from host-89-241-183-78.as13285.net ([89.241.183.78]:60905\n\thelo=russet.org.uk) by cloud103.planethippo.com with esmtpsa\n\t(TLSv1.2:DHE-RSA-AES128-SHA:128) (Exim 4.86_1)\n\t(envelope-from <phillip.lord@russet.org.uk>)\n\tid 1al26G-0043IB-0F; Tue, 29 Mar 2016 23:27:00 +0100\nFrom: phillip.lord@russet.org.uk (Phillip Lord)\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<2d55f05e4ba00afeb3f7269953eb0e13.squirrel@cloud103.planethippo.com>\n\t<jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Tue, 29 Mar 2016 23:26:19 +0100\nIn-Reply-To: <jwvwpolbe3o.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Tue, 29 Mar 2016 09:39:54 -0400\")\nMessage-ID: <87k2kkq5tg.fsf@russet.org.uk>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-AntiAbuse: This header was added to track abuse,\n\tplease include it with any abuse report\nX-AntiAbuse: Primary Hostname - cloud103.planethippo.com\nX-AntiAbuse: Original Domain - gnu.org\nX-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12]\nX-AntiAbuse: Sender Address Domain - russet.org.uk\nX-Get-Message-Sender-Via: cloud103.planethippo.com: authenticated_id:\n\tphillip.lord@russet.org.uk\nX-Authenticated-Sender: cloud103.planethippo.com: phillip.lord@russet.org.uk\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 31.216.48.48\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 22:27:07 -0000\nContent-Length: 1025\nLines: 23\n\nStefan Monnier <monnier@iro.umontreal.ca> writes:\n\n>> The only solution I can see for fill-paragraph is to copy the paragraph to\n>> a temp buffer, fill that, check whether it has changed, then if it has,\n>> signal before-change, copy the changed paragraph back, signal after\n>> change. If it has not changed, then fill-paragraph becomes a no-op.\n>\n> BTW, another solution for fill-paragraph is to reimplement it from\n> scratch such that it doesn't \"unfill+refill\".  I.e. it could instead\n> loop go to each LF in the paragraph, such if that LF is found to be \"at\n> the right spot\", it moves on, if it's \"too soon\", then it removes it,\n> and if it's \"too far\" then it adds another somewhere earlier.\n>\n> This would naturally lead to the result that the buffer would stay\n> unmodified if there was no change.\n\n\nYes, that would seem a clean solution, as it would avoid changing a\nbuffer which might not need to be changed. It would also mean that a\nfill-paragraph which doesn't change anything would leave no undo events.\n\nPhil\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_455.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akpOl-00044F-Bx\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 04:53:15 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36499)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <fweimer@redhat.com>) id 1akpOi-00043t-J6\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 04:53:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <fweimer@redhat.com>) id 1akpOf-00083v-Cw\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 04:53:12 -0400\nReceived: from mx1.redhat.com ([209.132.183.28]:44578)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <fweimer@redhat.com>) id 1akpOf-00083r-7e\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 04:53:09 -0400\nReceived: from int-mx14.intmail.prod.int.phx2.redhat.com\n\t(int-mx14.intmail.prod.int.phx2.redhat.com [10.5.11.27])\n\tby mx1.redhat.com (Postfix) with ESMTPS id 456B380085\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 08:53:08 +0000 (UTC)\nReceived: from oldenburg.str.redhat.com (dhcp-192-212.str.redhat.com\n\t[10.33.192.212])\n\tby int-mx14.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP\n\tid u2T8r6hp016210\n\t(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO)\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 04:53:07 -0400\nSubject: Re: Thinking about changed buffers\nTo: dev@caliopen.local\nReferences: <m3bn5ya4ro.fsf@gnus.org>\nFrom: Florian Weimer <fweimer@redhat.com>\nMessage-ID: <56FA4272.70209@redhat.com>\nDate: Tue, 29 Mar 2016 10:53:06 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <m3bn5ya4ro.fsf@gnus.org>\nContent-Type: text/plain; charset=windows-1252\nX-Scanned-By: MIMEDefang 2.68 on 10.5.11.27\nContent-Transfer-Encoding: quoted-printable\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 209.132.183.28\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 08:53:13 -0000\nContent-Length: 1395\nLines: 32\n\nOn 03/28/2016 07:31 PM, Lars Magne Ingebrigtsen wrote:\n> In conjunction with the wishlist item \"`M-q' shouldn't say that the\n> buffer hasn't changed when it hasn't\", we started talking a bit about\n> further issues about what it means that a buffer has changed or not.\n>=20\n> If you load a file, and then hit \"a\", and then delete the \"a\", then\n> Emacs will say that the buffer has changed.  If you hit \"a\" and then\n> `undo', Emacs will say that it hasn't.\n>=20\n> If there was a way to deal with this discrepancy, that would be very\n> nice, I think.\n\nI feel a bit silly, but I rely on this discrepancy (SPC DEL marks the\nbuffer as changed) so that saving the buffer is an actual operation\nwhich updates the file timestamp, to trigger a rebuild of its dependencie=\ns.\n\n> One idea that popped up is that whenever we mark a buffer as unchanged\n> (that is, `(set-buffer-modified-p nil)', we save the byte size of the\n> buffer and a cryptographic hash of the buffer.  Then `buffer-modified-p=\n'\n> would simply see whether either the size had changed, and if not,\n> whether the hash had changed.  If both are identical, then the buffer\n> hasn't changed.\n\nFor me, the major nuisance is that Emacs claims that the file has\nchanged on disk, when it fact it hasn't=97the mtime changed, but the\ncontents is the same.  At least it's easier to detect this case with the\nhashing approach you propose.\n\nFlorian\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_457.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akbOx-0005lu-Nm\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 13:56:31 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:58480)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1akbOw-0005lg-8D\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 13:56:30 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1akbOt-0002g0-1V\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 13:56:30 -0400\nReceived: from mail-out.m-online.net ([212.18.0.9]:58393)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1akbOs-0002dE-Sg\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 13:56:26 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qYhRX0kCDz3hjKQ\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 19:56:23 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qYhRW1qnNzvhMK\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 19:56:23 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024) with ESMTP id WJvfOLwm8OHu for <dev@caliopen.local>;\n\tMon, 28 Mar 2016 19:56:22 +0200 (CEST)\nX-Auth-Info: PGo2aXoHXfwS3mPhOXQNoGado1PxEXzQwVdz834tfJATVAAZ6PSRKeVqW/r4ljnz\nReceived: from igel.home (ppp-88-217-16-153.dynamic.mnet-online.de\n\t[88.217.16.153]) by mail.mnet-online.de (Postfix) with ESMTPA\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 19:56:22 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid 1BB482C3DDA; Mon, 28 Mar 2016 19:56:22 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: dev@caliopen.local\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org>\nX-Yow: PARDON me, am I speaking ENGLISH?\nDate: Mon, 28 Mar 2016 19:56:22 +0200\nIn-Reply-To: <m3bn5ya4ro.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 28 Mar 2016 19:31:07 +0200\")\nMessage-ID: <87fuvafpvd.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 212.18.0.9\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 17:56:31 -0000\nContent-Length: 321\nLines: 11\n\nYou cannot just look at the decoded contents.  If the coding system is\nchanged the encoded contents will change even if the buffer contents\ndidn't.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_459.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akbT1-00080a-Rb\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:00:43 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59700)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akbSw-0007wv-Ry\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:00:42 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akbSt-0004Ni-L6\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:00:38 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:42081)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akbSt-0004NR-9c\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:00:35 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akbSp-0003qn-5Z; Mon, 28 Mar 2016 20:00:33 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Andreas Schwab <schwab@linux-m68k.org>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEVcUTz+/v6Mbk8CEAZv\n\tW0LSx7p4ZEooMyR32T31AAACQklEQVQ4jXXUMWvkMBAF4EERUp0ivXiI2TacAm6NMN76OMK1Qgil\n\tFWuz+vs39iZ2iotY3HxIb2ZkLz3/sOj5+eX9/f339pD1sv9kbfB36ENk0qRNASuXneFpgz+hB8Wa\n\tiEwC31N0pup9h8DNG4HbBJiUE1Xz2DGENzfLWWtlKAfX8gOWIay+pknO8lkxEuXygDBorqnQqFkp\n\t8NhVPWDyU5F81lPm0m/8CB8GDa+SGXWMPvtCCo/wMBCcSpS0yS6zJ/MNUvVU7sVzRrH0BWGEK1XA\n\tO4F6QheY0AyBHTDZ63lUZANbKcuoUHuKJ8CA/JpViZltyp/w2iISqK4zBJwdv2BogMu66gwTOVn6\n\tgtAjHEw1GTIS308YNtB1rjDO+2Y/q1pCuIFZ+zpLkPPGHg2GN8hUfZ1AnLw+qgrhFzBbrgRC4dkd\n\tDe5AOesNXOGj3OUGjNJ3zSkXl+qRsSjgqtXIymNy48zf+sBHi5OXFiemcuwYZIezamaZlIJVZ7mS\n\t4fusy5xxE6gHvMpZrWqtdFTcdTxAzpqaF4gy+U7qgBDhBYxROcsQ5wO6vEDN0d2oImE0nrAKJLrP\n\tUh5fRzqhgVeiRjHDfZh+QgfuWkBlla50wrDDKMDxYqmdsACr1gIlXq7fICxyR60JJLiLXg9YgoC2\n\tAg71sqpv0EBmg23uLT6+jx1eMQroO5DkI9xh2cJDRzVPXW/v9aXt0/3b9/QhZnqypOViLtY8YNkB\n\t0E92FKjJPjK6SN9ax8dY5QlO+w3++O/z//UPGJ/SD7LwYVoAAAAASUVORK5CYII=\nDate: Mon, 28 Mar 2016 20:00:30 +0200\nIn-Reply-To: <87fuvafpvd.fsf@linux-m68k.org> (Andreas Schwab's message of\n\t\"Mon, 28 Mar 2016 19:56:22 +0200\")\nMessage-ID: <m34mbqjxdt.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:00:43 -0000\nContent-Length: 774\nLines: 23\n\nAndreas Schwab <schwab@linux-m68k.org> writes:\n\n> You cannot just look at the decoded contents.  If the coding system is\n> changed the encoded contents will change even if the buffer contents\n> didn't.\n\nI don't think that's correct.  Loading a file does (basically)\n\n1) put the bytes from the file into memory\n2) decode them into internal representation\n3) (set-buffer-modified-p nil)\n\nIt's from that point on we care whether the contents of the buffer has\nchanged, not from when we loaded the bytes from the file into memory.\nSo the coding system is irrelevant.\n\nOr do you mean if the user explicitly changes the coding system... in\nthe buffer?  How does that happen?\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_461.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akbcH-000298-7q\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:10:17 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34505)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1akbcD-00028B-Nm\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:10:15 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1akbc9-0007pw-ON\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:10:13 -0400\nReceived: from mail-out.m-online.net ([2001:a60:0:28:0:1:25:1]:53198)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1akbc9-0007pb-Hi\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:10:09 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qYhlM1gjLz3hkTs;\n\tMon, 28 Mar 2016 20:10:07 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qYhlM11kZzvdWQ;\n\tMon, 28 Mar 2016 20:10:07 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id mNQzTYZfksZD; Mon, 28 Mar 2016 20:10:02 +0200 (CEST)\nX-Auth-Info: Uu39jRlG5hUJv1MHnjPg19PoufOyxWxEf9ZOJV1z3Jp2Gz7rYHso/OQuYBuPyb2E\nReceived: from igel.home (ppp-88-217-16-153.dynamic.mnet-online.de\n\t[88.217.16.153]) by mail.mnet-online.de (Postfix) with ESMTPA;\n\tMon, 28 Mar 2016 20:10:02 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid 7B0502C3E28; Mon, 28 Mar 2016 20:10:02 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org>\nX-Yow: I'm EXCITED!!  I want a FLANK STEAK WEEK-END!!  I think I'm JULIA\n\tCHILD!!\nDate: Mon, 28 Mar 2016 20:10:02 +0200\nIn-Reply-To: <m34mbqjxdt.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 28 Mar 2016 20:00:30 +0200\")\nMessage-ID: <87bn5yfp8l.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 2001:a60:0:28:0:1:25:1\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:10:16 -0000\nContent-Length: 343\nLines: 14\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> Or do you mean if the user explicitly changes the coding system... in\n> the buffer?  How does that happen?\n\nC-x C-m f\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_463.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akbl8-0005LW-F8\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:19:26 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37328)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akbl4-0005Kg-CS\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:19:24 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akbl0-0002YV-NS\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:19:22 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:42448)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akbl0-0002YQ-Gu\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:19:18 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akbkv-0003yq-U2; Mon, 28 Mar 2016 20:19:17 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Andreas Schwab <schwab@linux-m68k.org>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEVcUTz+/v6Mbk8CEAZv\n\tW0LSx7p4ZEooMyR32T31AAACQklEQVQ4jXXUMWvkMBAF4EERUp0ivXiI2TacAm6NMN76OMK1Qgil\n\tFWuz+vs39iZ2iotY3HxIb2ZkLz3/sOj5+eX9/f339pD1sv9kbfB36ENk0qRNASuXneFpgz+hB8Wa\n\tiEwC31N0pup9h8DNG4HbBJiUE1Xz2DGENzfLWWtlKAfX8gOWIay+pknO8lkxEuXygDBorqnQqFkp\n\t8NhVPWDyU5F81lPm0m/8CB8GDa+SGXWMPvtCCo/wMBCcSpS0yS6zJ/MNUvVU7sVzRrH0BWGEK1XA\n\tO4F6QheY0AyBHTDZ63lUZANbKcuoUHuKJ8CA/JpViZltyp/w2iISqK4zBJwdv2BogMu66gwTOVn6\n\tgtAjHEw1GTIS308YNtB1rjDO+2Y/q1pCuIFZ+zpLkPPGHg2GN8hUfZ1AnLw+qgrhFzBbrgRC4dkd\n\tDe5AOesNXOGj3OUGjNJ3zSkXl+qRsSjgqtXIymNy48zf+sBHi5OXFiemcuwYZIezamaZlIJVZ7mS\n\t4fusy5xxE6gHvMpZrWqtdFTcdTxAzpqaF4gy+U7qgBDhBYxROcsQ5wO6vEDN0d2oImE0nrAKJLrP\n\tUh5fRzqhgVeiRjHDfZh+QgfuWkBlla50wrDDKMDxYqmdsACr1gIlXq7fICxyR60JJLiLXg9YgoC2\n\tAg71sqpv0EBmg23uLT6+jx1eMQroO5DkI9xh2cJDRzVPXW/v9aXt0/3b9/QhZnqypOViLtY8YNkB\n\t0E92FKjJPjK6SN9ax8dY5QlO+w3++O/z//UPGJ/SD7LwYVoAAAAASUVORK5CYII=\nDate: Mon, 28 Mar 2016 20:19:13 +0200\nIn-Reply-To: <87bn5yfp8l.fsf@linux-m68k.org> (Andreas Schwab's message of\n\t\"Mon, 28 Mar 2016 20:10:02 +0200\")\nMessage-ID: <m3zitiihy6.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:19:24 -0000\nContent-Length: 409\nLines: 16\n\nAndreas Schwab <schwab@linux-m68k.org> writes:\n\n> Lars Magne Ingebrigtsen <larsi@gnus.org> writes:\n>\n>> Or do you mean if the user explicitly changes the coding system... in\n>> the buffer?  How does that happen?\n>\n> C-x C-m f\n\nOk, so `buffer-file-coding-system' is one more thing to include in the\nhash.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_465.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akbwh-0007i6-Tw\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:31:24 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41988)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akbwd-0007go-Rp\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:31:22 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akbwZ-0006hJ-UQ\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:31:19 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:60133)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1akbwZ-0006hE-RD; Mon, 28 Mar 2016 14:31:15 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4886\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1akbwY-0004Ic-TQ; Mon, 28 Mar 2016 14:31:15 -0400\nDate: Mon, 28 Mar 2016 21:30:55 +0300\nMessage-Id: <8337raxxnk.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m3zitiihy6.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Mon, 28 Mar 2016 20:19:13 +0200)\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\n\t<m3zitiihy6.fsf@gnus.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: schwab@linux-m68k.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:31:22 -0000\nContent-Length: 242\nLines: 11\n\n> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n> Date: Mon, 28 Mar 2016 20:19:13 +0200\n> Cc: dev@caliopen.local\n> \n> > C-x C-m f\n> \n> Ok, so `buffer-file-coding-system' is one more thing to include in the\n> hash.\n\nWhat about annotations?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_467.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcIP-0004St-CA\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:53:49 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:48206)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcIN-0004Sm-Cm\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:53:48 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcII-0005RV-ER\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:53:47 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:43107)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akcII-0005RR-81; Mon, 28 Mar 2016 14:53:42 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akcIF-0004DZ-Lo; Mon, 28 Mar 2016 20:53:41 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\n\t<m3zitiihy6.fsf@gnus.org> <8337raxxnk.fsf@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEW0Al1XBiWYMFBuDypJ\n\tCRmIFDVBCBZgDCRYqWUPAAACMElEQVQ4jWWSQXeiQAzHs9LRa/u6zzMP6p0tgle3TvHKU5ErrUO8\n\t2oLw9TfJDNRu85SB/Cb/ZJIBEAuRba34qRMfYGH9qiXHEXNgYGIfnKlkn2E9wwqmTDa7wQ9AIFIY\n\tARw5JPEHvyqzGqB7pS+vQ2wkJKd/sdzQs8OeP+6I5E5r3/BCMhzDFVx964+Fz8jjNtgd+/ZiI0me\n\t8lNpRnYULcIIDCugrGqJJwcoLdY2iQYokQ4m5tXSmIiTGPAyRFfc7FVALeeXEgalqwCzE5BKIomZ\n\tXiyIlQM1KCn7WDsADlRw0AzSk4BzApnNUUGLfMS04oS4TTg7gwiWXLHqLIiTibJSAE98xn13zlhc\n\tJSEM4ECVFXt2846iXE8ZUBOWiIdkUTKo4CVsTwQ6euW7EOegDGldAMPMTBjkMs5E01p3Gib4TD6E\n\tOaUoST3aSNvX1PT6Q1+OMD+Dt0JpZIempTDEP/gyhXkFXiqAJmu0HTC+ewJkpErTVevttPBtAnNy\n\tZRuZF+IuAHsZqeHzcdx2rBa8kWectgO/pJE+PPLHAa3x+xMrPeRwhZXGwWjXREQfPuF+hjf2rFJZ\n\tdQP3x1uAmV30llrizGS3b1tb50+A/wPjOT1Iv8tXNFVj9C04N23X9zQn1Bz9BQJrdKeM7rX+Caj8\n\tbf/xewTnzyCgX2FB3w7gRN0vLiqPuLumicObqqxZUCwgNUMbRqD/NmEAq612wMjTvzNNv4kDKLX+\n\tFpHPKHXTBP8Asp0rd6AX3DMAAAAASUVORK5CYII=\nDate: Mon, 28 Mar 2016 20:53:39 +0200\nIn-Reply-To: <8337raxxnk.fsf@gnu.org> (Eli Zaretskii's message of \"Mon, 28 Mar\n\t2016 21:30:55 +0300\")\nMessage-ID: <m3mvpiigcs.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: schwab@linux-m68k.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:53:48 -0000\nContent-Length: 301\nLines: 13\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n>> Ok, so `buffer-file-coding-system' is one more thing to include in the\n>> hash.\n>\n> What about annotations?\n\nI'm not sure what that means in this context?\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_469.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcM4-0006Un-BU\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:57:36 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50229)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akcM2-0006UO-Ad\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:57:35 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akcLx-0007Df-Ev\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:57:34 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:60884)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1akcLx-0007DV-BM; Mon, 28 Mar 2016 14:57:29 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4912\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1akcLw-0002us-9v; Mon, 28 Mar 2016 14:57:28 -0400\nDate: Mon, 28 Mar 2016 21:57:09 +0300\nMessage-Id: <83zitiwhve.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m3mvpiigcs.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Mon, 28 Mar 2016 20:53:39 +0200)\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\n\t<m3zitiihy6.fsf@gnus.org> <8337raxxnk.fsf@gnu.org>\n\t<m3mvpiigcs.fsf@gnus.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: schwab@linux-m68k.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:57:35 -0000\nContent-Length: 458\nLines: 16\n\n> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n> Cc: schwab@linux-m68k.org,  dev@caliopen.local\n> Date: Mon, 28 Mar 2016 20:53:39 +0200\n> \n> Eli Zaretskii <eliz@gnu.org> writes:\n> \n> >> Ok, so `buffer-file-coding-system' is one more thing to include in the\n> >> hash.\n> >\n> > What about annotations?\n> \n> I'm not sure what that means in this context?\n\nI'm not sure what you mean by \"what that means\".  Did you know about\nwrite-region-annotate-functions?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_471.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcUS-0000SK-HS\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 15:06:16 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:53243)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcUP-0000S6-Sh\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:06:14 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcUK-0001v7-4t\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:06:13 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:43318)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akcUJ-0001ux-Up; Mon, 28 Mar 2016 15:06:08 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akcUF-0004IG-T7; Mon, 28 Mar 2016 21:06:06 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\n\t<m3zitiihy6.fsf@gnus.org> <8337raxxnk.fsf@gnu.org>\n\t<m3mvpiigcs.fsf@gnus.org> <83zitiwhve.fsf@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEW0Al1XBiWYMFBuDypJ\n\tCRmIFDVBCBZgDCRYqWUPAAACMElEQVQ4jWWSQXeiQAzHs9LRa/u6zzMP6p0tgle3TvHKU5ErrUO8\n\t2oLw9TfJDNRu85SB/Cb/ZJIBEAuRba34qRMfYGH9qiXHEXNgYGIfnKlkn2E9wwqmTDa7wQ9AIFIY\n\tARw5JPEHvyqzGqB7pS+vQ2wkJKd/sdzQs8OeP+6I5E5r3/BCMhzDFVx964+Fz8jjNtgd+/ZiI0me\n\t8lNpRnYULcIIDCugrGqJJwcoLdY2iQYokQ4m5tXSmIiTGPAyRFfc7FVALeeXEgalqwCzE5BKIomZ\n\tXiyIlQM1KCn7WDsADlRw0AzSk4BzApnNUUGLfMS04oS4TTg7gwiWXLHqLIiTibJSAE98xn13zlhc\n\tJSEM4ECVFXt2846iXE8ZUBOWiIdkUTKo4CVsTwQ6euW7EOegDGldAMPMTBjkMs5E01p3Gib4TD6E\n\tOaUoST3aSNvX1PT6Q1+OMD+Dt0JpZIempTDEP/gyhXkFXiqAJmu0HTC+ewJkpErTVevttPBtAnNy\n\tZRuZF+IuAHsZqeHzcdx2rBa8kWectgO/pJE+PPLHAa3x+xMrPeRwhZXGwWjXREQfPuF+hjf2rFJZ\n\tdQP3x1uAmV30llrizGS3b1tb50+A/wPjOT1Iv8tXNFVj9C04N23X9zQn1Bz9BQJrdKeM7rX+Caj8\n\tbf/xewTnzyCgX2FB3w7gRN0vLiqPuLumicObqqxZUCwgNUMbRqD/NmEAq612wMjTvzNNv4kDKLX+\n\tFpHPKHXTBP8Asp0rd6AX3DMAAAAASUVORK5CYII=\nDate: Mon, 28 Mar 2016 21:06:03 +0200\nIn-Reply-To: <83zitiwhve.fsf@gnu.org> (Eli Zaretskii's message of \"Mon, 28 Mar\n\t2016 21:57:09 +0300\")\nMessage-ID: <m3io06ifs4.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: schwab@linux-m68k.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 19:06:14 -0000\nContent-Length: 392\nLines: 12\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n> I'm not sure what you mean by \"what that means\".  Did you know about\n> write-region-annotate-functions?\n\nNope.  Now that I know about it, I still have no idea what it does.  :-)\nAnd there's nothing in the lispref concept index about \"annotation\".\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_473.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akce0-00056B-0m\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 15:16:08 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56159)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akcdu-00054t-Gk\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:16:06 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akcdq-0004lb-PO\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:16:02 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:33517)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1akcdq-0004lX-Ld; Mon, 28 Mar 2016 15:15:58 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4942\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1akcdp-0002kH-Ar; Mon, 28 Mar 2016 15:15:57 -0400\nDate: Mon, 28 Mar 2016 22:15:38 +0300\nMessage-Id: <83y492wh0l.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m3io06ifs4.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Mon, 28 Mar 2016 21:06:03 +0200)\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\n\t<m3zitiihy6.fsf@gnus.org> <8337raxxnk.fsf@gnu.org>\n\t<m3mvpiigcs.fsf@gnus.org> <83zitiwhve.fsf@gnu.org>\n\t<m3io06ifs4.fsf@gnus.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: schwab@linux-m68k.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 19:16:06 -0000\nContent-Length: 463\nLines: 14\n\n> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n> Cc: schwab@linux-m68k.org,  dev@caliopen.local\n> Date: Mon, 28 Mar 2016 21:06:03 +0200\n> \n> Eli Zaretskii <eliz@gnu.org> writes:\n> \n> > I'm not sure what you mean by \"what that means\".  Did you know about\n> > write-region-annotate-functions?\n> \n> Nope.  Now that I know about it, I still have no idea what it does.  :-)\n> And there's nothing in the lispref concept index about \"annotation\".\n\nLook in fileio.c.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463566.5266_475.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcla-0007fq-Dj\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 15:23:58 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59024)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akclY-0007f6-6v\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:23:57 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akclV-0008Oy-22\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:23:56 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:43622)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akclU-0008Ou-RF; Mon, 28 Mar 2016 15:23:52 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akclS-0004Q0-At; Mon, 28 Mar 2016 21:23:52 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\n\t<m3zitiihy6.fsf@gnus.org> <8337raxxnk.fsf@gnu.org>\n\t<m3mvpiigcs.fsf@gnus.org> <83zitiwhve.fsf@gnu.org>\n\t<m3io06ifs4.fsf@gnus.org> <83y492wh0l.fsf@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEW0Al1XBiWYMFBuDypJ\n\tCRmIFDVBCBZgDCRYqWUPAAACMElEQVQ4jWWSQXeiQAzHs9LRa/u6zzMP6p0tgle3TvHKU5ErrUO8\n\t2oLw9TfJDNRu85SB/Cb/ZJIBEAuRba34qRMfYGH9qiXHEXNgYGIfnKlkn2E9wwqmTDa7wQ9AIFIY\n\tARw5JPEHvyqzGqB7pS+vQ2wkJKd/sdzQs8OeP+6I5E5r3/BCMhzDFVx964+Fz8jjNtgd+/ZiI0me\n\t8lNpRnYULcIIDCugrGqJJwcoLdY2iQYokQ4m5tXSmIiTGPAyRFfc7FVALeeXEgalqwCzE5BKIomZ\n\tXiyIlQM1KCn7WDsADlRw0AzSk4BzApnNUUGLfMS04oS4TTg7gwiWXLHqLIiTibJSAE98xn13zlhc\n\tJSEM4ECVFXt2846iXE8ZUBOWiIdkUTKo4CVsTwQ6euW7EOegDGldAMPMTBjkMs5E01p3Gib4TD6E\n\tOaUoST3aSNvX1PT6Q1+OMD+Dt0JpZIempTDEP/gyhXkFXiqAJmu0HTC+ewJkpErTVevttPBtAnNy\n\tZRuZF+IuAHsZqeHzcdx2rBa8kWectgO/pJE+PPLHAa3x+xMrPeRwhZXGwWjXREQfPuF+hjf2rFJZ\n\tdQP3x1uAmV30llrizGS3b1tb50+A/wPjOT1Iv8tXNFVj9C04N23X9zQn1Bz9BQJrdKeM7rX+Caj8\n\tbf/xewTnzyCgX2FB3w7gRN0vLiqPuLumicObqqxZUCwgNUMbRqD/NmEAq612wMjTvzNNv4kDKLX+\n\tFpHPKHXTBP8Asp0rd6AX3DMAAAAASUVORK5CYII=\nDate: Mon, 28 Mar 2016 21:23:50 +0200\nIn-Reply-To: <83y492wh0l.fsf@gnu.org> (Eli Zaretskii's message of \"Mon, 28 Mar\n\t2016 22:15:38 +0300\")\nMessage-ID: <m38u12ieyh.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: schwab@linux-m68k.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 19:23:57 -0000\nContent-Length: 456\nLines: 14\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n>> Nope.  Now that I know about it, I still have no idea what it does.  :-)\n>> And there's nothing in the lispref concept index about \"annotation\".\n>\n> Look in fileio.c.\n\nI am, but I still don't understand what this has to do with whether we\nconsider a buffer to be modified or not.  You'll have to spell it out.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_477.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akd06-0002NY-Am\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 15:38:58 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:33949)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akd00-0002MK-JR\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:38:56 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akczv-0004Si-LM\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:38:52 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:34172)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1akczv-0004Sa-Ia; Mon, 28 Mar 2016 15:38:47 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4972\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1akczt-0001mS-TR; Mon, 28 Mar 2016 15:38:46 -0400\nDate: Mon, 28 Mar 2016 22:38:27 +0300\nMessage-Id: <83wpomwfyk.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m38u12ieyh.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Mon, 28 Mar 2016 21:23:50 +0200)\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\n\t<m3zitiihy6.fsf@gnus.org> <8337raxxnk.fsf@gnu.org>\n\t<m3mvpiigcs.fsf@gnus.org> <83zitiwhve.fsf@gnu.org>\n\t<m3io06ifs4.fsf@gnus.org> <83y492wh0l.fsf@gnu.org>\n\t<m38u12ieyh.fsf@gnus.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: schwab@linux-m68k.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 19:38:56 -0000\nContent-Length: 444\nLines: 14\n\n> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n> Cc: schwab@linux-m68k.org,  dev@caliopen.local\n> Date: Mon, 28 Mar 2016 21:23:50 +0200\n> \n> Eli Zaretskii <eliz@gnu.org> writes:\n> \n> > Look in fileio.c.\n> \n> I am, but I still don't understand what this has to do with whether we\n> consider a buffer to be modified or not.  You'll have to spell it out.\n\nIt affects how buffer text is written to a disk file.  Like\nbuffer-file-coding-system.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_483.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akjPa-0001zm-Cv\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 22:29:42 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:35557)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akjPV-0001zG-4M\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 22:29:40 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akjPQ-0004nM-Jb\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 22:29:37 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:45093)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1akjPQ-0004nI-Fu; Mon, 28 Mar 2016 22:29:32 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:1344\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1akjPP-00033F-Nc; Mon, 28 Mar 2016 22:29:32 -0400\nDate: Tue, 29 Mar 2016 05:29:14 +0300\nMessage-Id: <83vb46vwxx.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m3r3eugxpr.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Mon, 28 Mar 2016 22:21:36 +0200)\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\n\t<m3zitiihy6.fsf@gnus.org> <8337raxxnk.fsf@gnu.org>\n\t<m3mvpiigcs.fsf@gnus.org> <83zitiwhve.fsf@gnu.org>\n\t<m3io06ifs4.fsf@gnus.org> <83y492wh0l.fsf@gnu.org>\n\t<m38u12ieyh.fsf@gnus.org> <83wpomwfyk.fsf@gnu.org>\n\t<m3zitigzd3.fsf@gnus.org> <m3r3eugxpr.fsf@gnus.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: schwab@linux-m68k.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 02:29:41 -0000\nContent-Length: 1743\nLines: 41\n\n> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n> Cc: schwab@linux-m68k.org,  dev@caliopen.local\n> Date: Mon, 28 Mar 2016 22:21:36 +0200\n> \n>   /* Here we have a problem.  SAVE_MODIFF is used here to encode\n>      buffer-modified-p (as SAVE_MODIFF<MODIFF) as well as\n>      recent-auto-save-p (as SAVE_MODIFF<auto_save_modified).  So if we\n>      modify SAVE_MODIFF to affect one, we may affect the other\n>      as well.\n>      E.g. if FLAG is nil we need to set SAVE_MODIFF to MODIFF, but\n>      if SAVE_MODIFF<auto_save_modified that means we risk changing\n>      recent-auto-save-p from t to nil.\n>      Vice versa, if FLAG is non-nil and SAVE_MODIFF>=auto_save_modified\n>      we risk changing recent-auto-save-p from nil to t.  */\n>   SAVE_MODIFF = (NILP (flag)\n> \t\t /* FIXME: This unavoidably sets recent-auto-save-p to nil.  */\n> \t\t ? MODIFF\n> \t\t /* Let's try to preserve recent-auto-save-p.  */\n> \t\t : SAVE_MODIFF < MODIFF ? SAVE_MODIFF\n> \t\t /* If SAVE_MODIFF == auto_save_modified == MODIFF,\n> \t\t    we can either decrease SAVE_MODIFF and auto_save_modified\n> \t\t    or increase MODIFF.  */\n> \t\t : MODIFF++);\n> \n> :-)\n> \n> I think instead of all this, we could just introduce a new field in the\n> buffer struct...  er...  explicit_modified_flag_p.  Calling\n> `(set-buffer-modified-p t)' would just set that, and `buffer-modified-p'\n> would just return that.\n> \n> (`(set-buffer-modified-p nil)' would clear it, of course, in addition to\n> doing everything else it's doing.)\n> \n> So then we're back to just hashing the buffer contents and (some) text\n> properties, I think?\n\nIf you do that, you'll need to modify all the places that look at\nMODIFF, SAVE_MODIFF, and their buffer-specific variants.  In\nparticular, the display engine.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_485.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcJF-0005LZ-Fw\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:54:41 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:48938)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1akcJC-0005Hn-PB\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:54:39 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1akcJ8-0005nB-PG\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:54:38 -0400\nReceived: from mail-out.m-online.net ([2001:a60:0:28:0:1:25:1]:53633)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>)\n\tid 1akcJ2-0005ia-8p; Mon, 28 Mar 2016 14:54:28 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qYjkW1hfLz3hkdy;\n\tMon, 28 Mar 2016 20:54:27 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qYjkW10JVzvhMX;\n\tMon, 28 Mar 2016 20:54:27 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id C0c55Q_P5QsJ; Mon, 28 Mar 2016 20:54:23 +0200 (CEST)\nX-Auth-Info: bYA1Q+zWHy0LqsuI9BrbVsqqjjXVAIZQuz6I2PYsEWJpSc8ixCfgSkcx35Ni6noE\nReceived: from igel.home (ppp-88-217-16-153.dynamic.mnet-online.de\n\t[88.217.16.153]) by mail.mnet-online.de (Postfix) with ESMTPA;\n\tMon, 28 Mar 2016 20:54:23 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid AD3CE2C3E2B; Mon, 28 Mar 2016 20:54:22 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org> <87bn5yfp8l.fsf@linux-m68k.org>\n\t<m3zitiihy6.fsf@gnus.org> <8337raxxnk.fsf@gnu.org>\nX-Yow: Are you selling NYLON OIL WELLS??  If so, we can use TWO DOZEN!!\nDate: Mon, 28 Mar 2016 20:54:22 +0200\nIn-Reply-To: <8337raxxnk.fsf@gnu.org> (Eli Zaretskii's message of \"Mon, 28 Mar\n\t2016 21:30:55 +0300\")\nMessage-ID: <877fgmfn6p.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 2001:a60:0:28:0:1:25:1\nCc: Lars Magne Ingebrigtsen <larsi@gnus.org>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:54:39 -0000\nContent-Length: 529\nLines: 22\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n>> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n>> Date: Mon, 28 Mar 2016 20:19:13 +0200\n>> Cc: dev@caliopen.local\n>> \n>> > C-x C-m f\n>> \n>> Ok, so `buffer-file-coding-system' is one more thing to include in the\n>> hash.\n>\n> What about annotations?\n\nwrite-file-functions, write-contents-functions, before-save-hook\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_487.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akboI-00065c-8U\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:22:42 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38386)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akboC-00065B-Sa\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:22:40 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akbo9-0003v3-2f\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:22:36 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:59781)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1akbo8-0003uy-Vc; Mon, 28 Mar 2016 14:22:32 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:4876\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1akbo8-0000cH-0r; Mon, 28 Mar 2016 14:22:32 -0400\nDate: Mon, 28 Mar 2016 21:22:13 +0300\nMessage-Id: <8360w6xy22.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m34mbqjxdt.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Mon, 28 Mar 2016 20:00:30 +0200)\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <87fuvafpvd.fsf@linux-m68k.org>\n\t<m34mbqjxdt.fsf@gnus.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: schwab@linux-m68k.org, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:22:40 -0000\nContent-Length: 839\nLines: 24\n\n> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n> Date: Mon, 28 Mar 2016 20:00:30 +0200\n> Cc: dev@caliopen.local\n> \n> Andreas Schwab <schwab@linux-m68k.org> writes:\n> \n> > You cannot just look at the decoded contents.  If the coding system is\n> > changed the encoded contents will change even if the buffer contents\n> > didn't.\n> \n> I don't think that's correct.  Loading a file does (basically)\n> \n> 1) put the bytes from the file into memory\n> 2) decode them into internal representation\n\nDecoding can change the bytes in memory.  For example, when a Latin-1\ntext is decoded, you end up with (2-byte) UTF-8 sequences for every\nnon-ASCII character in memory that was only one byte on disk.\n\n> 3) (set-buffer-modified-p nil)\n\nYes, and if the user changes the encoding, then saves the buffer, the\nfile on disk will have different bytes.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_489.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcGl-0003vJ-Rt\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:52:07 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47576)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcGj-0003vA-Rh\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:52:07 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcGe-0004se-Rv\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:52:05 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:43075)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcGe-0004sU-LD\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:52:00 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>) id 1akcGZ-0004CT-5z\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 20:51:59 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: dev@caliopen.local\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEW0Al1XBiWYMFBuDypJ\n\tCRmIFDVBCBZgDCRYqWUPAAACMElEQVQ4jWWSQXeiQAzHs9LRa/u6zzMP6p0tgle3TvHKU5ErrUO8\n\t2oLw9TfJDNRu85SB/Cb/ZJIBEAuRba34qRMfYGH9qiXHEXNgYGIfnKlkn2E9wwqmTDa7wQ9AIFIY\n\tARw5JPEHvyqzGqB7pS+vQ2wkJKd/sdzQs8OeP+6I5E5r3/BCMhzDFVx964+Fz8jjNtgd+/ZiI0me\n\t8lNpRnYULcIIDCugrGqJJwcoLdY2iQYokQ4m5tXSmIiTGPAyRFfc7FVALeeXEgalqwCzE5BKIomZ\n\tXiyIlQM1KCn7WDsADlRw0AzSk4BzApnNUUGLfMS04oS4TTg7gwiWXLHqLIiTibJSAE98xn13zlhc\n\tJSEM4ECVFXt2846iXE8ZUBOWiIdkUTKo4CVsTwQ6euW7EOegDGldAMPMTBjkMs5E01p3Gib4TD6E\n\tOaUoST3aSNvX1PT6Q1+OMD+Dt0JpZIempTDEP/gyhXkFXiqAJmu0HTC+ewJkpErTVevttPBtAnNy\n\tZRuZF+IuAHsZqeHzcdx2rBa8kWectgO/pJE+PPLHAa3x+xMrPeRwhZXGwWjXREQfPuF+hjf2rFJZ\n\tdQP3x1uAmV30llrizGS3b1tb50+A/wPjOT1Iv8tXNFVj9C04N23X9zQn1Bz9BQJrdKeM7rX+Caj8\n\tbf/xewTnzyCgX2FB3w7gRN0vLiqPuLumicObqqxZUCwgNUMbRqD/NmEAq612wMjTvzNNv4kDKLX+\n\tFpHPKHXTBP8Asp0rd6AX3DMAAAAASUVORK5CYII=\nDate: Mon, 28 Mar 2016 20:51:54 +0200\nIn-Reply-To: <m3bn5ya4ro.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 28 Mar 2016 19:31:07 +0200\")\nMessage-ID: <m3r3euigfp.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:52:07 -0000\nContent-Length: 1375\nLines: 45\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> 1) Speed.\n\nAnd I had an idea here, too.  :-)  `buffer-modified-p' is today just\nthis simple integer comparison:\n\nDEFUN (\"buffer-modified-p\", Fbuffer_modified_p, Sbuffer_modified_p,\n[...]\n  return BUF_SAVE_MODIFF (buf) < BUF_MODIFF (buf) ? Qt : Qnil;\n}\n\nHow could it look under a hashey regime?  Pseudoish code coming up:\n\n{\n  if (BUF_SAVE_MODIFF (buf) == BUF_MODIFF (buf))\n    return Qnil;\n  if (BUF_SAVE_HASHED_SIZE (buf) != BUF_HASHED_SIZE (buf))\n    return Qt;\n  same = string_equal(buffer_hash (buf), buf->save_hash);\n  if (! NILP (same))\n    buf->text->save_modiff = buf->text->modiff;\n  return same;\n}\n\nUhm...  is that correct?  *squints*  Perhaps.\n\nAnyway, if we do it this way (that is, keep the modiff counter around),\nwe can short circuit the hash calculation like, almost always.  It would\nonly be necessary (the first time per ... time) the buffer reaches the\nsame size that it used to be.\n\nThat is, it would basically happen in the \"insert an 'a' and then delete\nthe 'a'\" case, but very seldom otherwise, so `buffer-modified-p' would\nbe almost as fast as it usually is.\n\nNow I'm kinda starting to think that this isn't just feasible, but\npossible in practice, too.  :-)\n\nI might be totally wrong, though.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_491.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akck6-00077h-KY\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 15:22:26 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:58384)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akck4-00077Q-Bd\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:22:25 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akck1-00081k-6E\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:22:24 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:40270)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akck0-00081Y-Vo\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:22:21 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akcjz-0008WJ-Mp\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 21:22:19 +0200\nReceived: from 107-179-153-73.cpe.teksavvy.com ([107.179.153.73])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 21:22:19 +0200\nReceived: from monnier by 107-179-153-73.cpe.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 21:22:19 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Thinking about changed buffers\nDate: Mon, 28 Mar 2016 15:22:09 -0400\nMessage-ID: <jwvy49276pc.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <m3bn5ya4ro.fsf@gnus.org> <m3r3euigfp.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 107-179-153-73.cpe.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:pTXxep1YkPgxbAlzmpffcSaM06g=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 19:22:25 -0000\nContent-Length: 573\nLines: 16\n\n>   if (BUF_SAVE_MODIFF (buf) == BUF_MODIFF (buf))\n>     return Qnil;\n\nThis test basically never succeeds except when we already know the file\nin not modified.\n\n> we can short circuit the hash calculation like, almost always.  It would\n> only be necessary (the first time per ... time) the buffer reaches the\n> same size that it used to be.\n\nStill seems problematic if your 5 year old takes 2.7s to compute it on\na 1GB file.  You don't want to freeze for 2s in the normal course of\nediting just because you happen to cross the \"original size\" threshold.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_493.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcpB-0008PK-Rr\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 15:27:41 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59900)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcp9-0008P9-FG\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:27:40 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcp6-0000pJ-7g\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:27:39 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:43699)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcp6-0000og-1N\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:27:36 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akcoy-0004RW-DR; Mon, 28 Mar 2016 21:27:30 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <m3r3euigfp.fsf@gnus.org>\n\t<jwvy49276pc.fsf-monnier+gmane.emacs.devel@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEW0Al1XBiWYMFBuDypJ\n\tCRmIFDVBCBZgDCRYqWUPAAACMElEQVQ4jWWSQXeiQAzHs9LRa/u6zzMP6p0tgle3TvHKU5ErrUO8\n\t2oLw9TfJDNRu85SB/Cb/ZJIBEAuRba34qRMfYGH9qiXHEXNgYGIfnKlkn2E9wwqmTDa7wQ9AIFIY\n\tARw5JPEHvyqzGqB7pS+vQ2wkJKd/sdzQs8OeP+6I5E5r3/BCMhzDFVx964+Fz8jjNtgd+/ZiI0me\n\t8lNpRnYULcIIDCugrGqJJwcoLdY2iQYokQ4m5tXSmIiTGPAyRFfc7FVALeeXEgalqwCzE5BKIomZ\n\tXiyIlQM1KCn7WDsADlRw0AzSk4BzApnNUUGLfMS04oS4TTg7gwiWXLHqLIiTibJSAE98xn13zlhc\n\tJSEM4ECVFXt2846iXE8ZUBOWiIdkUTKo4CVsTwQ6euW7EOegDGldAMPMTBjkMs5E01p3Gib4TD6E\n\tOaUoST3aSNvX1PT6Q1+OMD+Dt0JpZIempTDEP/gyhXkFXiqAJmu0HTC+ewJkpErTVevttPBtAnNy\n\tZRuZF+IuAHsZqeHzcdx2rBa8kWectgO/pJE+PPLHAa3x+xMrPeRwhZXGwWjXREQfPuF+hjf2rFJZ\n\tdQP3x1uAmV30llrizGS3b1tb50+A/wPjOT1Iv8tXNFVj9C04N23X9zQn1Bz9BQJrdKeM7rX+Caj8\n\tbf/xewTnzyCgX2FB3w7gRN0vLiqPuLumicObqqxZUCwgNUMbRqD/NmEAq612wMjTvzNNv4kDKLX+\n\tFpHPKHXTBP8Asp0rd6AX3DMAAAAASUVORK5CYII=\nDate: Mon, 28 Mar 2016 21:27:28 +0200\nIn-Reply-To: <jwvy49276pc.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Mon, 28 Mar 2016 15:22:09 -0400\")\nMessage-ID: <m34mbqiesf.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 19:27:40 -0000\nContent-Length: 982\nLines: 26\n\nStefan Monnier <monnier@iro.umontreal.ca> writes:\n\n>>   if (BUF_SAVE_MODIFF (buf) == BUF_MODIFF (buf))\n>>     return Qnil;\n>\n> This test basically never succeeds except when we already know the file\n> in not modified.\n\nIt's to avoid having the hashing function being called several times in\na row if you happen to have the \"original size\" in the buffer.  It's\nwhat makes this hash only being called the first time per ... time.  :-)\n\n>> we can short circuit the hash calculation like, almost always.  It would\n>> only be necessary (the first time per ... time) the buffer reaches the\n>> same size that it used to be.\n>\n> Still seems problematic if your 5 year old takes 2.7s to compute it on\n> a 1GB file.  You don't want to freeze for 2s in the normal course of\n> editing just because you happen to cross the \"original size\" threshold.\n\nYeah, I don't see any way around that.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_495.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akewv-0005K3-7V\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 17:43:49 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39005)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1akews-0005Jx-OQ\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 17:43:47 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1akewp-00030g-IY\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 17:43:46 -0400\nReceived: from chene.dit.umontreal.ca ([132.204.246.20]:34904)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1akewp-00030Z-D9\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 17:43:43 -0400\nReceived: from ceviche.home (lechon.iro.umontreal.ca [132.204.27.242])\n\tby chene.dit.umontreal.ca (8.14.1/8.14.1) with ESMTP id u2SLiErM018993; \n\tMon, 28 Mar 2016 17:44:14 -0400\nReceived: by ceviche.home (Postfix, from userid 20848)\n\tid 09EA766184; Mon, 28 Mar 2016 17:43:39 -0400 (EDT)\nFrom: Stefan Monnier <monnier@IRO.UMontreal.CA>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: Thinking about changed buffers\nMessage-ID: <jwvshza709k.fsf-monnier+Inbox@gnu.org>\nReferences: <m3bn5ya4ro.fsf@gnus.org> <m3r3euigfp.fsf@gnus.org>\n\t<jwvy49276pc.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m34mbqiesf.fsf@gnus.org>\nDate: Mon, 28 Mar 2016 17:43:39 -0400\nIn-Reply-To: <m34mbqiesf.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 28 Mar 2016 21:27:28 +0200\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-NAI-Spam-Flag: NO\nX-NAI-Spam-Threshold: 5\nX-NAI-Spam-Score: 0\nX-NAI-Spam-Rules: 1 Rules triggered\n\tRV5625=0\nX-NAI-Spam-Version: 2.3.0.9418 : core <5625> : inlines <4598> : streams\n\t<1610396> : uri <2175072>\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 132.204.246.20\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 21:43:47 -0000\nContent-Length: 852\nLines: 19\n\n>> Still seems problematic if your 5 year old takes 2.7s to compute it on\n>> a 1GB file.  You don't want to freeze for 2s in the normal course of\n>> editing just because you happen to cross the \"original size\" threshold.\n> Yeah, I don't see any way around that.\n\nI guess to do better, you'd have to keep track of some bounds of\nchanges, and to only hash the part of the buffer within those bounds.\nE.g. instead of a single hash, keep a list of hash-per-megabyte, plus\na bitmap of touched-megabytes (one bit per megabyte), so you can limit\nyour check to just those megabytes that have been touched.\n\nThis said, it doesn't strike me as a worthwhile exercise.  The benefit\nseems very small, and the extra work is out of proportion to that\nbenefit (and then you have to add the backward incompatibilities this\nwill inevitably introduce).\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_497.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcuM-000128-QM\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 15:33:02 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:32944)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1akcuL-00011i-0O\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:33:01 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1akcuG-0002qA-15\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:33:00 -0400\nReceived: from mail-wm0-x232.google.com ([2a00:1450:400c:c09::232]:34681)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1akcuF-0002q6-QJ\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:32:55 -0400\nReceived: by mail-wm0-x232.google.com with SMTP id p65so110915466wmp.1\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 12:32:55 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=1Cq7dsNxQq9WtH0qY2Vt6vGP8GyTab28MXIlTS8Ohz4=;\n\tb=bz+AyaW64a5evoLMQeSKBMe2bXAqJG1WKOGwPZOGDmG9YcnpgwaAvouGrHEJ5F18pN\n\tuK88QSU3CgXabJXboerk0HGoi2NYaLXSE+AvB8qzQrVHUUeFrOY/aaTppJQO948sTXdl\n\t1gTrzAU7XpzoqUnwC6UoCHr9oETItthf8iK1oOUmS1FNOvf+7MxjcAU/f622AMdNHyP5\n\trJCSQaE7y3W1BC9tIq1jqPs4llr6eD267HdFb51Om91eqe1sXpqzmpMPq+MQeqt0xyZG\n\tdwtcq6fjkLCGrXco8Ov/Vo4BA9cbKua4SK+w2hHiCyU/5fLaDNuoz+3wBIh2+DSlSngw\n\tWA5w==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=1Cq7dsNxQq9WtH0qY2Vt6vGP8GyTab28MXIlTS8Ohz4=;\n\tb=Gon958u4dwbAa4qcMhiilYlZTVCDDPrEtX2QqznuMuLMjqYF/Eid4aGfJkGc/ILI8w\n\tqbjePZXrev2obhSw26hzE2xr8wTXini2KwS2JSAy1Wp0T+MJNzuRPC76t660OiwG/BPE\n\tafwqsNTzLN+TphmAGrQNgl95+lauihXoby2acasnxjpvb3h/40OYn1n+/syRSopxCX0p\n\tzIAjXtLOkqSduNGXW3H8L8PZ0UwUyHV35GFDXM6FfnSoaD71O1vqS+LQjlwZ4EgV8qkz\n\tO8t6EsBMkacXAqAEn9ltXxuno3gY/WIGopsRxf179gcyY48wbx/+EowZk0e7AbEdHJZO\n\tSnzA==\nX-Gm-Message-State: AD7BkJJaFWcd0RJZ2yvru3/ZmZ2v7GMHek4JfOdm7JzLBSy6FDpi+egmQ99bvoxTv3QaWA==\nX-Received: by 10.28.194.9 with SMTP id s9mr12855114wmf.101.1459193575026;\n\tMon, 28 Mar 2016 12:32:55 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\to128sm11412505wmb.19.2016.03.28.12.32.51\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 28 Mar 2016 12:32:54 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Thinking about changed buffers\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>,\n\tStefan Monnier <monnier@iro.umontreal.ca>\nReferences: <m3bn5ya4ro.fsf@gnus.org> <m3r3euigfp.fsf@gnus.org>\n\t<jwvy49276pc.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m34mbqiesf.fsf@gnus.org>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <c7bc13b9-e578-a5b0-d7a7-74172196ed22@yandex.ru>\nDate: Mon, 28 Mar 2016 22:32:49 +0300\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <m34mbqiesf.fsf@gnus.org>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::232\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 19:33:02 -0000\nContent-Length: 529\nLines: 12\n\nOn 03/28/2016 10:27 PM, Lars Magne Ingebrigtsen wrote:\n\n>> Still seems problematic if your 5 year old takes 2.7s to compute it on\n>> a 1GB file.  You don't want to freeze for 2s in the normal course of\n>> editing just because you happen to cross the \"original size\" threshold.\n>\n> Yeah, I don't see any way around that.\n\nDon't use hashing. Use e.g. buffer-undo-list. We save enough data to \nreturn the buffer contents to the previous state, right? It should be \npossible to detect whether a given sequence of undo-s is a no-op.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_499.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akdaa-0003fP-Mr\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 16:16:40 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:44026)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1akdaX-0003e3-CB\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:16:38 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1akdaS-0006U9-8B\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:16:37 -0400\nReceived: from mout.kundenserver.de ([212.227.126.134]:52988)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1akdaR-0006U2-UM\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:16:32 -0400\nReceived: from [192.168.1.82] ([109.24.225.43]) by mrelayeu.kundenserver.de\n\t(mreue005) with ESMTPSA (Nemesis) id 0LckK3-1a3uzB3Bpv-00k728 for\n\t<emacs-devel@gnu.org>; Mon, 28 Mar 2016 22:16:30 +0200\nSubject: Re: Thinking about changed buffers\nTo: dev@caliopen.local\nReferences: <m3bn5ya4ro.fsf@gnus.org> <m3r3euigfp.fsf@gnus.org>\n\t<jwvy49276pc.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m34mbqiesf.fsf@gnus.org>\n\t<c7bc13b9-e578-a5b0-d7a7-74172196ed22@yandex.ru>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56F9911D.8040809@gmail.com>\nDate: Mon, 28 Mar 2016 22:16:29 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <c7bc13b9-e578-a5b0-d7a7-74172196ed22@yandex.ru>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"kEsIdr6SKprF6TIV9IdvgNcjxqRXk4s1l\"\nX-Provags-ID: V03:K0:ogb6RzlyGFTwTZ5RpBKy5O4WJybJRK9PFFgPlWfslK/Zb3cAwdX\n\t+hWPtXWCS+ApbmEgYU5Z/Rg1ziANAMTVHAFTppEOmOnXHVOukZbASACQS+OhOPyAQP+3tmL\n\tCnswHaewOkvBe2gkffu9uW94pRR1K0aKSgz9x219NYSD3zzbyNc8FLLy+ip9MrdMDRFhKvT\n\t+qnp84mzSM1WckGuLUQ5Q==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:oXlDrsvps38=:WrRJhi68jMG2weIez5QhYL\n\tcCRPMZx+mvC6R2NDzArLpYggmS6E8nLaKD0uj/m5LTWUAzMGHli29DD6H0VDbILr6qk0nxmO3\n\tRB4G/eoBZLQkNUcOUMehlb++tbRmUcokcefrkS1/cnLD0JHk60HCWqGm+gicARsdBQ912jl7j\n\t+YGcb6pvgAm+T442/jCA44kPRzGnbSYWgYyQgn8pxd782oPTjuRCN3LrFXsabcc19U+BNXHxL\n\tI4EE7qTSLCswfpQjIc+2cn6hAkCj8WZpKwP6ug+d2RInE3lqm5NpQXplkmiJH57q8Jg4ctC67\n\tl/pQRn57ZAP8yl7CJlK1lEYAs8t68gyrP12p6IWN+PPDlC3lxXQS93Bw5rZOZEIzbf/uLwDe+\n\t19xZ1O3YgvxRacEwJXhrTQ+k+4HuQgvOXMC6/wMI8pvFF9GoecZJ3QLxWHCpAp82LdAilBS0V\n\tlI3wnJx1KwIpeLdWpwmTIu5BIr0pVpkKFV9xuWopLK3Io0cYxqBaIJOsCV4AjIYg4Y0DfpXGL\n\t3s6/n54bUzkDwcCJ5IPZLQdou7FqYNzkCUTo9xy0cACUSGlbcTWhJmiIH30uaukZOZbvFvmf5\n\tXQeLYiV8RNRkNfZr1QwjnbqwiXO/O+iS1UH+tyLkxWEcYDgZfEQlBsrA5LqEnnevByyLUUQ9o\n\tfTinpfT09PQnkKrfrJwm8zm3oukjcd8og7uNnaBoFH0i0sx8n1n8JCvn7aAw0qvfBH2A=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.134\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 20:16:38 -0000\nContent-Length: 2614\nLines: 65\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--kEsIdr6SKprF6TIV9IdvgNcjxqRXk4s1l\nContent-Type: multipart/mixed; boundary=\"wHeQseqH1makcuGA1Kq1GCNdxXTOX2xUn\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <56F9911D.8040809@gmail.com>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org> <m3r3euigfp.fsf@gnus.org>\n <jwvy49276pc.fsf-monnier+gmane.emacs.devel@gnu.org> <m34mbqiesf.fsf@gnus.org>\n <c7bc13b9-e578-a5b0-d7a7-74172196ed22@yandex.ru>\nIn-Reply-To: <c7bc13b9-e578-a5b0-d7a7-74172196ed22@yandex.ru>\n\n--wHeQseqH1makcuGA1Kq1GCNdxXTOX2xUn\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\n\n\nOn 03/28/2016 09:32 PM, Dmitry Gutov wrote:\n> On 03/28/2016 10:27 PM, Lars Magne Ingebrigtsen wrote:\n>=20\n>>> Still seems problematic if your 5 year old takes 2.7s to compute it o=\nn\n>>> a 1GB file.  You don't want to freeze for 2s in the normal course of\n>>> editing just because you happen to cross the \"original size\" threshol=\nd.\n>>\n>> Yeah, I don't see any way around that.\n>=20\n> Don't use hashing. Use e.g. buffer-undo-list. We save enough data to re=\nturn the buffer contents to the previous state, right? It should be possi=\nble to detect whether a given sequence of undo-s is a no-op.\n\nAlternatively, introduce a threshold above which that hash-based check do=\nes not happen, and instead fall back to the old, less complex behaviour i=\nn that case.\n\n\n--wHeQseqH1makcuGA1Kq1GCNdxXTOX2xUn--\n\n--kEsIdr6SKprF6TIV9IdvgNcjxqRXk4s1l\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW+ZEdAAoJEPqg+cTm90wjeLAQAKVCEyp8AagnISBdGOsRcumu\nUYxQ1jXM5tIm0j0IdbjPfQmMoOF9KH8Z/4d3g/lwgk3VY14Z2C8CFkuAlhOyUf3R\nAcjSEPG1wClgYo1aFqQa22cigT4ypmnD1d0qRrNzkseFkBSY+M8aQVbiY2+WqSDw\nbaJGBKLSaFgNvwVe0DCRDcJc8+0M076n4KB/gmRrg08c+580YiUC7AYXwRDkoZb3\noks5R1yrYhRmrvK0mtG1hKRUMAfvAAAWz382eju4/mNiddQrs3V1ChI4JZZZXAOQ\njHFGCO3x5wK4Kq12EnHAwDH+COrl27FjYOzygJQ9FKBd/48/ah7FB4GC5AnDC327\n2VMhzW0ST3Xp08HZOObyWqayOXr5SwMvReqXAa1OPsuCVf2edX7qq9zjI6n8bff0\n/cUi6NbQTdBWH9DBwQZj7qBgPsymCxNj0otXOAr8b4gbFC1LaAH6UucHjb73aBJx\nIHc//dQLOgM7R5pKby6DIGeF2I5sl1iI+6xIvAT/lH5yPp/IPBasW2DiV51X0SA7\nyum8dV5KeP0ejBCCEYjDF6h9OZozs7f5zKq2JNT1kW7zMPMXTv8dUyZLpmnjb8l5\ndlvOtZWeiAAa/qy3VBFwwlD6CJNRR/f1ZIPPfQxMTKsSN9EXnEFsQQhxXatAgIkP\naq9H8jY3VRFk0XXOKlLU\n=MJC7\n-----END PGP SIGNATURE-----\n\n--kEsIdr6SKprF6TIV9IdvgNcjxqRXk4s1l--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_503.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcEP-0003Fs-6T\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:49:41 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46920)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <Stephan.Mueller@microsoft.com>) id 1akcEK-0003Fk-Hi\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:49:40 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <Stephan.Mueller@microsoft.com>) id 1akcEF-0004Kn-IC\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:49:36 -0400\nReceived: from mail-bl2on0120.outbound.protection.outlook.com\n\t([65.55.169.120]:16569\n\thelo=na01-bl2-obe.outbound.protection.outlook.com)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <Stephan.Mueller@microsoft.com>) id 1akcEF-0004KQ-0G\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:49:31 -0400\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n\ts=selector1; h=From:To:Date:Subject:Message-ID:Content-Type:MIME-Version;\n\tbh=3WC5iVp5vRGSYK+Ltrz9nWpkNZbYrCVQSmDoBMPexig=;\n\tb=V6TUUW3aaMXbqhJHQxjUkLt60iQTCQi5fyJ6RCj3rQfmmC6WQXHKuVOr2B6AN2+xOXoRJ193+agpr8tQatA9HB47yLPjztyEgn4JyTHV8+KD284ZQVmsEPuc0ias9yIaCI9WEknhoYdhZua6YjL/q0u1pPJbJxmaLsQhUJH5s6Y=\nReceived: from CY1PR0301MB1659.namprd03.prod.outlook.com (10.162.166.145) by\n\tCY1PR0301MB1659.namprd03.prod.outlook.com (10.162.166.145) with\n\tMicrosoft\n\tSMTP Server (TLS) id 15.1.434.16; Mon, 28 Mar 2016 18:49:29 +0000\nReceived: from CY1PR0301MB1659.namprd03.prod.outlook.com ([10.162.166.145]) by\n\tCY1PR0301MB1659.namprd03.prod.outlook.com ([10.162.166.145]) with\n\tmapi id 15.01.0434.023; Mon, 28 Mar 2016 18:49:29 +0000\nFrom: Stephan Mueller <Stephan.Mueller@microsoft.com>\nTo: \"emacs-devel@gnu.org\" <emacs-devel@gnu.org>\nSubject: RE: Thinking about changed buffers\nThread-Topic: Thinking about changed buffers\nThread-Index: AQHRiRtwDzFZOS6EzEaUmyNeN8gGTJ9vLw4Q\nDate: Mon, 28 Mar 2016 18:49:12 +0000\nDeferred-Delivery: Mon, 28 Mar 2016 18:48:00 +0000\nMessage-ID: <CY1PR0301MB165936869029245E623C8CDEF0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\nIn-Reply-To: <m3bn5ya4ro.fsf@gnus.org>\nAccept-Language: en-US\nContent-Language: en-US\nX-MS-Has-Attach: \nX-MS-TNEF-Correlator: \nauthentication-results: gnu.org; dkim=none (message not signed)\n\theader.d=none; gnu.org; dmarc=none action=none header.from=microsoft.com;\nx-originating-ip: [50.46.156.137]\nx-ms-office365-filtering-correlation-id: fe5b5bd6-1d2b-452b-3c1a-08d35739b1a2\nx-microsoft-exchange-diagnostics: 1; CY1PR0301MB1659;\n\t5:sSRU+t2zbLwfoI5+E4cJIZyiMYT/GKAOYT5GwNa0x1WIb8myq4n3gOw5I9VptJpIRbNJgykYXnjkK84h0QHsFWRai0eT0BbO+4yfEc/kcD3brPpnHV6W63tCmYknU+/aGqQru6H1s0KIJVbCs2syWA==;\n\t24:hRy3SxRVboxfi6ZyzAMPh37LniCw/yHLhOgXfRvfHA7fWOZU/Zlb5OSuuFEtWOSCjakQ1x8YA79YgW/dUES97tx6hK9vpEvjMymiKHFv6cw=\nx-microsoft-antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:CY1PR0301MB1659;\nx-microsoft-antispam-prvs: <CY1PR0301MB16598A800F3FC62C7633D1E4F0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\nx-exchange-antispam-report-test: UriScan:;\nx-exchange-antispam-report-cfa-test: BCL:0; PCL:0;\n\tRULEID:(61425038)(601004)(2401047)(5005006)(8121501046)(3002001)(10201501046)(61426038)(61427038);\n\tSRVR:CY1PR0301MB1659; BCL:0; PCL:0; RULEID:;\n\tSRVR:CY1PR0301MB1659; \nx-forefront-prvs: 0895DF8FFD\nx-forefront-antispam-report: SFV:NSPM;\n\tSFS:(10019020)(6009001)(377454003)(33656002)(2900100001)(81166005)(5640700001)(5003600100002)(8990500004)(54356999)(2950100001)(10400500002)(110136002)(87936001)(76176999)(122556002)(2906002)(5005710100001)(77096005)(92566002)(5004730100002)(107886002)(11100500001)(3660700001)(2501003)(189998001)(5008740100001)(3280700002)(86612001)(586003)(99286002)(102836003)(1220700001)(6116002)(86362001)(5002640100001)(106116001)(66066001)(1096002)(50986999)(450100001)(2351001)(3480700003)(76576001)(10090500001)(74316001);\n\tDIR:OUT; SFP:1102; SCL:1; SRVR:CY1PR0301MB1659;\n\tH:CY1PR0301MB1659.namprd03.prod.outlook.com; FPR:; SPF:None;\n\tMLV:sfv; LANG:en; \nspamdiagnosticoutput: 1:23\nspamdiagnosticmetadata: NSPM\nContent-Type: text/plain; charset=\"us-ascii\"\nContent-Transfer-Encoding: quoted-printable\nMIME-Version: 1.0\nX-OriginatorOrg: microsoft.com\nX-MS-Exchange-CrossTenant-originalarrivaltime: 28 Mar 2016 18:49:29.2292 (UTC)\nX-MS-Exchange-CrossTenant-fromentityheader: Hosted\nX-MS-Exchange-CrossTenant-id: 72f988bf-86f1-41af-91ab-2d7cd011db47\nX-MS-Exchange-Transport-CrossTenantHeadersStamped: CY1PR0301MB1659\nX-detected-operating-system: by eggs.gnu.org: Windows 7 or 8\nX-Received-From: 65.55.169.120\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:49:40 -0000\nContent-Length: 1510\nLines: 32\n\nOn Monday, March 28, 2016 10:31 AM Lars writes:\n\n\" If you load a file, and then hit \"a\", and then delete the \"a\", then\n\" Emacs will say that the buffer has changed.  If you hit \"a\" and then\n\" `undo', Emacs will say that it hasn't.\n\nPersonally, I have relied on the a-then-delete behavior when I\n_want_ the file to be considered modified*.  That is, my mental\nmodel of 'modified' matches the simplistic approach currently\nimplemented.  (And I use undo when I don't want to have the\nbuffer marked modified).\n\nI'm not convinced that this aspect (text change-and-happens-to\n-get-restored) is worth correcting.  Or even, a flaw at all; I suppose\nIt may depend on whether one reads 'modified' as meaning \"has\nbeen touched\" or as \"is no longer identical\".\n\nI've not come across any other editor with the proposed behaviour;\nperhaps it's worth considering \"compatibility with typical expectations\"\nhere as well as \"can Emacs be better in this regard than other editors\".\n\nstephan();\n\n* that said, I don't necessarily have a compelling reason for wanting\na modified buffer.  I've used it to ensure that revert-buffer will\nreload the file to trigger a re-colourization of the buffer, fixing\ncases where cperl-mode got confused.  There's lots wrong with this\nscenario: I should pursue the cperl issue, and I've since found that\nthe recolourization happens on revert-buffer without it being\nmodified, but the point remains that char-then-backspace is a\nconcise, intuitive, idiom that works with the current simple model.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_505.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcbW-0004Qv-9K\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 15:13:34 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55504)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akcbT-0004OY-6g\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:13:32 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akcbQ-000455-0N\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:13:31 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:39363)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akcbP-00044s-Q8\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:13:27 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akcbO-0003e7-3u\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 21:13:26 +0200\nReceived: from 107-179-153-73.cpe.teksavvy.com ([107.179.153.73])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 21:13:26 +0200\nReceived: from monnier by 107-179-153-73.cpe.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 21:13:26 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Thinking about changed buffers\nDate: Mon, 28 Mar 2016 15:13:13 -0400\nMessage-ID: <jwv4mbq8luz.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<CY1PR0301MB165936869029245E623C8CDEF0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 107-179-153-73.cpe.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:go7NqER//EBkllt2hUS2GVs+BJU=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 19:13:32 -0000\nContent-Length: 1130\nLines: 27\n\n> Personally, I have relied on the a-then-delete behavior when I\n> _want_ the file to be considered modified*.\n\nSame here (tho I use SPC DEL instead).\n\n> * that said, I don't necessarily have a compelling reason for wanting\n> a modified buffer.\n\nFor me, the full idiom is \"SPC DEL C-x C-s\" which I use to force Emacs\nto save the file.  I'm not sure exactly where/when I need it (it's too\ningrained in muscle memory), but I think it's mostly when I edit a LaTeX\ndocument while it's being compiled, because running \"pdflatex\" takes\nenough time that I may end up saving a file before the end of the\nprevious compilation is done, so the file's timstamps don't faithfully\nrecord the \"freshness\" of the output.\n\n> I should pursue the cperl issue, and I've since found that the\n> recolourization happens on revert-buffer without it being modified,\n> but the point remains that char-then-backspace is a concise,\n> intuitive, idiom that works with the current simple model.\n\nFor this, `SPC C-x u' at point-min should be sufficient to cause\ncperl-mode (and perl-mode as well) to flush all the existing\nhighlighting data.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_507.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akcin-00064P-5x\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 15:21:05 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57671)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcik-00062x-KN\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:21:03 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcih-0006V6-EP\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:21:02 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:43602)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akcih-0006Un-7R\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 15:20:59 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akcib-0004Oi-N3; Mon, 28 Mar 2016 21:20:56 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<CY1PR0301MB165936869029245E623C8CDEF0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\n\t<jwv4mbq8luz.fsf-monnier+gmane.emacs.devel@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEW0Al1XBiWYMFBuDypJ\n\tCRmIFDVBCBZgDCRYqWUPAAACMElEQVQ4jWWSQXeiQAzHs9LRa/u6zzMP6p0tgle3TvHKU5ErrUO8\n\t2oLw9TfJDNRu85SB/Cb/ZJIBEAuRba34qRMfYGH9qiXHEXNgYGIfnKlkn2E9wwqmTDa7wQ9AIFIY\n\tARw5JPEHvyqzGqB7pS+vQ2wkJKd/sdzQs8OeP+6I5E5r3/BCMhzDFVx964+Fz8jjNtgd+/ZiI0me\n\t8lNpRnYULcIIDCugrGqJJwcoLdY2iQYokQ4m5tXSmIiTGPAyRFfc7FVALeeXEgalqwCzE5BKIomZ\n\tXiyIlQM1KCn7WDsADlRw0AzSk4BzApnNUUGLfMS04oS4TTg7gwiWXLHqLIiTibJSAE98xn13zlhc\n\tJSEM4ECVFXt2846iXE8ZUBOWiIdkUTKo4CVsTwQ6euW7EOegDGldAMPMTBjkMs5E01p3Gib4TD6E\n\tOaUoST3aSNvX1PT6Q1+OMD+Dt0JpZIempTDEP/gyhXkFXiqAJmu0HTC+ewJkpErTVevttPBtAnNy\n\tZRuZF+IuAHsZqeHzcdx2rBa8kWectgO/pJE+PPLHAa3x+xMrPeRwhZXGwWjXREQfPuF+hjf2rFJZ\n\tdQP3x1uAmV30llrizGS3b1tb50+A/wPjOT1Iv8tXNFVj9C04N23X9zQn1Bz9BQJrdKeM7rX+Caj8\n\tbf/xewTnzyCgX2FB3w7gRN0vLiqPuLumicObqqxZUCwgNUMbRqD/NmEAq612wMjTvzNNv4kDKLX+\n\tFpHPKHXTBP8Asp0rd6AX3DMAAAAASUVORK5CYII=\nDate: Mon, 28 Mar 2016 21:20:52 +0200\nIn-Reply-To: <jwv4mbq8luz.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Mon, 28 Mar 2016 15:13:13 -0400\")\nMessage-ID: <m3egauif3f.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 19:21:03 -0000\nContent-Length: 936\nLines: 21\n\nStefan Monnier <monnier@iro.umontreal.ca> writes:\n\n> For me, the full idiom is \"SPC DEL C-x C-s\" which I use to force Emacs\n> to save the file.  I'm not sure exactly where/when I need it (it's too\n> ingrained in muscle memory), but I think it's mostly when I edit a LaTeX\n> document while it's being compiled, because running \"pdflatex\" takes\n> enough time that I may end up saving a file before the end of the\n> previous compilation is done, so the file's timstamps don't faithfully\n> record the \"freshness\" of the output.\n\nNow that you mention it, I remember doing that, too.  :-)\n\nDoes this perhaps mean that the interactive `C-x C-s' command should\nalways save the file, even if it's unchanged?  We've apparently all\ntaken to doing pointless modifications because we haven't found an easy\nway to make Emacs re-save the file...\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_509.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ake5O-0002NW-CO\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 16:48:30 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52618)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <Stephan.Mueller@microsoft.com>) id 1ake5L-0002N2-Lh\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:48:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <Stephan.Mueller@microsoft.com>) id 1ake5I-0006I8-FV\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:48:27 -0400\nReceived: from mail-bl2on0143.outbound.protection.outlook.com\n\t([65.55.169.143]:46857\n\thelo=na01-bl2-obe.outbound.protection.outlook.com)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <Stephan.Mueller@microsoft.com>) id 1ake5I-0006Hg-6P\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:48:24 -0400\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n\ts=selector1; h=From:To:Date:Subject:Message-ID:Content-Type:MIME-Version;\n\tbh=lPJ9Z33tKnaJ/TnHDhOc1MdJvGmCmgPWR5QO4t+tROg=;\n\tb=W8+hSc/wYnpY7dxWF6tbGS7si32Uo2g+jHGXnYtYVP/LCp6llm0yfjIfgiZs9P6R6GxL8aBDHlVTIA8e0xV/tkBcg2Jr+37VdVOcmRS7UtNMescJoi4EM8BHG8dMREAGmtgiSYq8hLKcprMB/NnBR2EGXdU6aPiMyMWW4LoISVY=\nReceived: from CY1PR0301MB1659.namprd03.prod.outlook.com (10.162.166.145) by\n\tCY1PR0301MB1657.namprd03.prod.outlook.com (10.162.166.143) with\n\tMicrosoft\n\tSMTP Server (TLS) id 15.1.447.15; Mon, 28 Mar 2016 20:34:00 +0000\nReceived: from CY1PR0301MB1659.namprd03.prod.outlook.com ([10.162.166.145]) by\n\tCY1PR0301MB1659.namprd03.prod.outlook.com ([10.162.166.145]) with\n\tmapi id 15.01.0434.023; Mon, 28 Mar 2016 20:34:00 +0000\nFrom: Stephan Mueller <Stephan.Mueller@microsoft.com>\nTo: \"emacs-devel@gnu.org\" <emacs-devel@gnu.org>\nSubject: RE: Thinking about changed buffers\nThread-Topic: Thinking about changed buffers\nThread-Index: AQHRiRtwDzFZOS6EzEaUmyNeN8gGTJ9vLw4QgAAKpkmAAAI2foAADXfw\nDate: Mon, 28 Mar 2016 20:33:49 +0000\nDeferred-Delivery: Mon, 28 Mar 2016 20:33:00 +0000\nMessage-ID: <CY1PR0301MB165952B2F0BC9C4184C6A995F0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<CY1PR0301MB165936869029245E623C8CDEF0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\n\t<jwv4mbq8luz.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3egauif3f.fsf@gnus.org>\nIn-Reply-To: <m3egauif3f.fsf@gnus.org>\nAccept-Language: en-US\nContent-Language: en-US\nX-MS-Has-Attach: \nX-MS-TNEF-Correlator: \nauthentication-results: gnu.org; dkim=none (message not signed)\n\theader.d=none; gnu.org; dmarc=none action=none header.from=microsoft.com;\nx-originating-ip: [2001:4898:80e8::28]\nx-ms-office365-filtering-correlation-id: fc1b51af-3ec1-4c76-5964-08d357484b98\nx-microsoft-exchange-diagnostics: 1; CY1PR0301MB1657;\n\t5:JMqf56Be5sPQi2VeIrLUivkzqD7UkfBsgT0KmlQANHANoOXryrXNqAZjI6mOUUHr+5TUscWxCebG7SXN58PNRtAHJDjmLJniYT6I9vw6qeScQ1iSTkikKqN0IBBmUo3wipDJ9qdeYueO/WTNTBf7Eg==;\n\t24:HJl3xx1IF3GJNvFVCudvyYNAFueiR9alF8potJM+PbUVTks60lSpY0NFNCvIY7EbGBaLx9srNRDFQYAt+pBN90GF6NDPTJxUoidKQ2Asdfs=\nx-microsoft-antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:CY1PR0301MB1657;\nx-microsoft-antispam-prvs: <CY1PR0301MB165796F5DF6459B8572A9A8FF0860@CY1PR0301MB1657.namprd03.prod.outlook.com>\nx-exchange-antispam-report-test: UriScan:;\nx-exchange-antispam-report-cfa-test: BCL:0; PCL:0;\n\tRULEID:(61425038)(601004)(2401047)(8121501046)(5005006)(3002001)(10201501046)(61426038)(61427038);\n\tSRVR:CY1PR0301MB1657; BCL:0; PCL:0; RULEID:;\n\tSRVR:CY1PR0301MB1657; \nx-forefront-prvs: 0895DF8FFD\nx-forefront-antispam-report: SFV:NSPM;\n\tSFS:(10019020)(6009001)(164054003)(99286002)(3660700001)(93886004)(2351001)(2501003)(77096005)(5640700001)(3280700002)(3480700003)(106116001)(2906002)(10090500001)(5005710100001)(10290500002)(2950100001)(10400500002)(450100001)(1096002)(81166005)(74316001)(2900100001)(1220700001)(11100500001)(86362001)(54356999)(76176999)(6116002)(102836003)(50986999)(86612001)(5008740100001)(5003600100002)(122556002)(92566002)(87936001)(19580405001)(19580395003)(33656002)(5004730100002)(107886002)(110136002)(189998001)(5002640100001)(586003)(76576001)(3826002);\n\tDIR:OUT; SFP:1102; SCL:1; SRVR:CY1PR0301MB1657;\n\tH:CY1PR0301MB1659.namprd03.prod.outlook.com; FPR:; SPF:None;\n\tMLV:sfv; LANG:en; \nspamdiagnosticoutput: 1:23\nspamdiagnosticmetadata: NSPM\nContent-Type: text/plain; charset=\"us-ascii\"\nContent-Transfer-Encoding: quoted-printable\nMIME-Version: 1.0\nX-OriginatorOrg: microsoft.com\nX-MS-Exchange-CrossTenant-originalarrivaltime: 28 Mar 2016 20:34:00.5659 (UTC)\nX-MS-Exchange-CrossTenant-fromentityheader: Hosted\nX-MS-Exchange-CrossTenant-id: 72f988bf-86f1-41af-91ab-2d7cd011db47\nX-MS-Exchange-Transport-CrossTenantHeadersStamped: CY1PR0301MB1657\nX-detected-operating-system: by eggs.gnu.org: Windows 7 or 8\nX-Received-From: 65.55.169.143\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 20:48:28 -0000\nContent-Length: 1094\nLines: 29\n\nStefan Monnier <monnier@iro.umontreal.ca> writes:\n\n\" For this, `SPC C-x u' at point-min should be sufficient to cause\n\" cperl-mode (and perl-mode as well) to flush all the existing\n\" highlighting data.\n\nThanks, I'll try that. Moving to point-min and back may make\nthat more awkward than revert-buffer.  Regardless, at the very\nleast, I should hide one or the other approach in a function and\nbind it to a key.\n\nLars writes:\n\n\" Does this perhaps mean that the interactive `C-x C-s' command should\n\" always save the file, even if it's unchanged?  We've apparently all\n\" taken to doing pointless modifications because we haven't found an easy\n\" way to make Emacs re-save the file...\n\nI follow your logic, but I'm not sure about this simplification; I\nfind that I hit `C-x C-s' all the time (whenever I enter my mental\nidle loop, perhaps), and am pleased that it doesn't do anything\nunnecessarily on those extremely numerous occasions.  Perhaps\na new,  distinct command really-save-buffer, bound to C-x C-S-s ?\n Or prefix save-buffer with four `C-u's (1 through 3 are already\ntaken)?\n\nstephan();\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_511.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akdq6-00005y-No\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 16:32:42 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:48343)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akdq1-00005n-TC\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:32:41 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akdpx-0002Fy-R7\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:32:37 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:44960)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akdpx-0002Fs-Kw\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:32:33 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akdpw-0005mU-3K\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 22:32:32 +0200\nReceived: from 151.red-79-153-146.dynamicip.rima-tde.net ([79.153.146.151])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 22:32:32 +0200\nReceived: from ofv by 151.red-79-153-146.dynamicip.rima-tde.net with local\n\t(Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 28 Mar 2016 22:32:32 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: =?utf-8?Q?=C3=93scar_Fuentes?= <ofv@wanadoo.es>\nSubject: Re: Thinking about changed buffers\nDate: Mon, 28 Mar 2016 22:32:21 +0200\nMessage-ID: <87vb46738q.fsf@wanadoo.es>\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<CY1PR0301MB165936869029245E623C8CDEF0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\n\t<jwv4mbq8luz.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3egauif3f.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 151.red-79-153-146.dynamicip.rima-tde.net\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nCancel-Lock: sha1:2JpjVoKqSsUj5aKoFiIJOqpbJR4=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 20:32:41 -0000\nContent-Length: 520\nLines: 16\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n>> For me, the full idiom is \"SPC DEL C-x C-s\" which I use to force Emacs\n>> to save the file. [snip]\n>\n> Now that you mention it, I remember doing that, too.  :-)\n\nMe too... until I started to use all sorts of electric modes which\ngurantee that a mindless char&delete will create a mess.\n\n> Does this perhaps mean that the interactive `C-x C-s' command should\n> always save the file, even if it's unchanged?\n\nYes. With an option to turn it off, of course :-)\n\n[snip]\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_513.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akdY0-0002sj-WD\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 16:14:01 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:43329)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1akdXy-0002rZ-2u\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:13:58 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1akdXt-0005zg-13\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:13:58 -0400\nReceived: from mout.kundenserver.de ([212.227.17.24]:57714)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1akdXs-0005zI-Od\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:13:52 -0400\nReceived: from [192.168.1.82] ([109.24.225.43]) by mrelayeu.kundenserver.de\n\t(mreue102) with ESMTPSA (Nemesis) id 0MLy2e-1af8IE47PI-007i1u for\n\t<emacs-devel@gnu.org>; Mon, 28 Mar 2016 22:13:51 +0200\nSubject: Re: Thinking about changed buffers\nTo: dev@caliopen.local\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<CY1PR0301MB165936869029245E623C8CDEF0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\n\t<jwv4mbq8luz.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<m3egauif3f.fsf@gnus.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56F9907D.6060102@gmail.com>\nDate: Mon, 28 Mar 2016 22:13:49 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <m3egauif3f.fsf@gnus.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"bUfP4KwXPKNRgwq1gbFuhFHoPTrkskXaa\"\nX-Provags-ID: V03:K0:zgeoq5WfM2Z4w5+EpS7N4/w/XUpz/5yUsHYFF9ywR8H6ztI/p9i\n\tncm86zO1pEp467Lwjcm78qc+XNEHWM93pPPAuTMtPl49VAhD26iiivDb20Xt09Cf1JzXRMF\n\tM+l05VtDnoe9DWHhY08cKvE7S56KnxHXR2/sw/5LFEPw09N4iMlD/d+iihwkcbbmaxQplVd\n\t+3GkHZzmU4lhH8dfVNp5w==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:Kf8Lymv2FBE=:BehcgpW+S2J9Da1ixOq4IF\n\tvAHHTNXHOsCtY5ZFY8faN4k0IttlISr7T8ST3RzyhcEK9ZI/NmKzeUO4pSi00c2F5e5y8A4SP\n\tWWCLrmujYIgJ7soXGRVk4qsE32sVBXZcO3Zv2VeMA+8WkhzHCwmluksZNxG224e6DkpCKNlCp\n\tJOikROn/DeeSHDskj/hek6T/3klnGocmZYOS+Esr0UV9A4w/awSRorpuClLuzBHjbArG7pNXj\n\tmJB52igz8qIEU6CZsh6hUZn8NSPtZ/jxc/EL385EhCDyJf9lWST0I9CyrGlLTfK1vlcyro8kf\n\tjwbPlK45buMIOSamshM5KbiWt/r2ldGvRC5J/qrkChpPokqfLI44D82qUKhctDZwya/SsBeIr\n\tyWGTgv8Y2ERhONb42ntQd5rjNfPG5G2iPehL435tVSZg+kX2WzjEJE355VcCKcNu+8pWjK6v1\n\tWVN3GYxIGRE2GmE0QpCSt4RUSEi4UXSFYy1IZ81aM8A/RNor135h2nFdL16NXSN2cF5iGLltq\n\tu93cchdI5dlfgImR4oQmSATsthIpIn3XReSfyfH6VLHnngwfyUOvOi/9OULkamHxUkXxSB6nn\n\t3yY/EsWqMACT6DytN3D94P1u6ZEHmipxGgzexXPINhck7XdBGCY5JSmB0U9ik+dCOBsMcroHp\n\t2FVDJqcO1a656zaZql9cy1GWrS996FHp6Jl0VvvVBODDaafnEzRUafk2W7fZh6LJvv2o=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.24\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 20:13:59 -0000\nContent-Length: 2200\nLines: 53\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--bUfP4KwXPKNRgwq1gbFuhFHoPTrkskXaa\nContent-Type: multipart/mixed; boundary=\"vPe12U3MW3RBuEMmvqDjs9R4TRUuO5ame\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <56F9907D.6060102@gmail.com>\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n <CY1PR0301MB165936869029245E623C8CDEF0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\n <jwv4mbq8luz.fsf-monnier+gmane.emacs.devel@gnu.org> <m3egauif3f.fsf@gnus.org>\nIn-Reply-To: <m3egauif3f.fsf@gnus.org>\n\n--vPe12U3MW3RBuEMmvqDjs9R4TRUuO5ame\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\nOn 03/28/2016 09:20 PM, Lars Magne Ingebrigtsen wrote:\n> Does this perhaps mean that the interactive `C-x C-s' command should\n> always save the file, even if it's unchanged?  We've apparently all\n> taken to doing pointless modifications because we haven't found an easy=\n\n> way to make Emacs re-save the file...\n\nIndeed, I was going to suggest exactly that :)\n\n\n--vPe12U3MW3RBuEMmvqDjs9R4TRUuO5ame--\n\n--bUfP4KwXPKNRgwq1gbFuhFHoPTrkskXaa\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW+ZB9AAoJEPqg+cTm90wjNRoQAJSqnjOq8nob6km0+nf8k2JG\nC49coMGkgCQTnxVGRZBxKSeBwXA+3DQ0JNSt2VkPgWQRcp7/S9UuJQKSHRi88hB6\n+1VoxXeGceKy8JUc4WFzrOaTck5lLf5KF8TJTvvhy/w8KIpPhs1WQLWI6NB83AzZ\n4Z0YLDpcH+x3AgQg8vibbFNxX1vIGzvTkguWpc5Rh12sYS3Jnizd1YI+oowVKtu2\nCgf9dhPoi0GGA5iCAmI+T4yaS8Ol7d+eIfOqLdEqCTZcPRdcgysKAdwVjG0i8od+\n7CCXF4XMRD1XsBECJD6dpAHN+FXhxtaMrzM31nh1tiOagFGimBf6jk9psXw4e2qj\n9gFyJlAOmi56ieW+iQS6V0AZoM+q+QauK5vVmZi7lKcuwupHqTmhyBB0wwHCFPLO\nGcmkEmd8t5o3vtJ115MuaM9sSpNgKhvw20JV4E+TdtmEP1o3qop5qxRtdEEhW375\nqpw6G35Z6C0mQUwlv3yP8REtcSXFRV422Bx6AbY77N9vPHYNSeoQBjzqea0gE16/\nLUx7Ba1avNoKEf5duA7MjhgP/cNZXnOVW7cDFjk5nDDti0wQVuCV0/TM2LzPnYje\nTVgXjbCi5fax8sRXMnDAkvmKwLyXORGNfH7PesbGXRQYh1JMNIXYh06S3k4blC6W\nA7UyHLIQyV12AvVB4Ivk\n=MbBe\n-----END PGP SIGNATURE-----\n\n--bUfP4KwXPKNRgwq1gbFuhFHoPTrkskXaa--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_515.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akdc5-0004Af-5h\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 16:18:13 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:44529)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mbork@mbork.pl>) id 1akdc3-0004AR-FG\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:18:12 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <mbork@mbork.pl>) id 1akdby-0006uW-Qq\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:18:11 -0400\nReceived: from mail.mojserwer.eu ([2a01:5e00:2:52::8]:54941)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <mbork@mbork.pl>) id 1akdby-0006uQ-Jm\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 16:18:06 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby mail.mojserwer.eu (Postfix) with ESMTP id E4226A63770;\n\tMon, 28 Mar 2016 22:18:04 +0200 (CEST)\nX-Virus-Scanned: Debian amavisd-new at mail.mojserwer.eu\nReceived: from mail.mojserwer.eu ([127.0.0.1])\n\tby localhost (mail.mojserwer.eu [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id hlmkM0XI739e; Mon, 28 Mar 2016 22:17:58 +0200 (CEST)\nReceived: from localhost (98-171.echostar.pl [213.156.98.171])\n\tby mail.mojserwer.eu (Postfix) with ESMTPSA id A9BB6A6376B;\n\tMon, 28 Mar 2016 22:17:58 +0200 (CEST)\nReferences: <m3bn5ya4ro.fsf@gnus.org>\n\t<CY1PR0301MB165936869029245E623C8CDEF0860@CY1PR0301MB1659.namprd03.prod.outlook.com>\n\t<jwv4mbq8luz.fsf-monnier+gmane.emacs.devel@gnu.org>\nUser-agent: mu4e 0.9.13; emacs 25.1.50.7\nFrom: Marcin Borkowski <mbork@mbork.pl>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Thinking about changed buffers\nIn-reply-to: <jwv4mbq8luz.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Mon, 28 Mar 2016 22:17:58 +0200\nMessage-ID: <87y49273wp.fsf@mbork.pl>\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x\nX-Received-From: 2a01:5e00:2:52::8\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 20:18:12 -0000\nContent-Length: 601\nLines: 26\n\n\nOn 2016-03-28, at 21:13, Stefan Monnier <monnier@iro.umontreal.ca> wrote:\n\n>> Personally, I have relied on the a-then-delete behavior when I\n>> _want_ the file to be considered modified*.\n>\n> Same here (tho I use SPC DEL instead).\n\nAnd here.\n\n>> * that said, I don't necessarily have a compelling reason for wanting\n>> a modified buffer.\n>\n> For me, the full idiom is \"SPC DEL C-x C-s\" which I use to force Emacs\n> to save the file.  [...]\n\nSame here.\n\nBest,\n\n-- \nMarcin Borkowski\nhttp://octd.wmi.amu.edu.pl/en/Marcin_Borkowski\nFaculty of Mathematics and Computer Science\nAdam Mickiewicz University\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_517.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akc5a-0000O5-3c\n\tfor mharc-dev@caliopen.local; Mon, 28 Mar 2016 14:40:34 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:43905)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akc5U-0000MU-LE\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:40:32 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akc5Q-000134-IP\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:40:28 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:42884)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akc5Q-00012r-C9\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 14:40:24 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>) id 1akc5K-00048P-9i\n\tfor dev@caliopen.local; Mon, 28 Mar 2016 20:40:22 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: dev@caliopen.local\nSubject: Re: Thinking about changed buffers\nReferences: <m3bn5ya4ro.fsf@gnus.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEW0Al1XBiWYMFBuDypJ\n\tCRmIFDVBCBZgDCRYqWUPAAACMElEQVQ4jWWSQXeiQAzHs9LRa/u6zzMP6p0tgle3TvHKU5ErrUO8\n\t2oLw9TfJDNRu85SB/Cb/ZJIBEAuRba34qRMfYGH9qiXHEXNgYGIfnKlkn2E9wwqmTDa7wQ9AIFIY\n\tARw5JPEHvyqzGqB7pS+vQ2wkJKd/sdzQs8OeP+6I5E5r3/BCMhzDFVx964+Fz8jjNtgd+/ZiI0me\n\t8lNpRnYULcIIDCugrGqJJwcoLdY2iQYokQ4m5tXSmIiTGPAyRFfc7FVALeeXEgalqwCzE5BKIomZ\n\tXiyIlQM1KCn7WDsADlRw0AzSk4BzApnNUUGLfMS04oS4TTg7gwiWXLHqLIiTibJSAE98xn13zlhc\n\tJSEM4ECVFXt2846iXE8ZUBOWiIdkUTKo4CVsTwQ6euW7EOegDGldAMPMTBjkMs5E01p3Gib4TD6E\n\tOaUoST3aSNvX1PT6Q1+OMD+Dt0JpZIempTDEP/gyhXkFXiqAJmu0HTC+ewJkpErTVevttPBtAnNy\n\tZRuZF+IuAHsZqeHzcdx2rBa8kWectgO/pJE+PPLHAa3x+xMrPeRwhZXGwWjXREQfPuF+hjf2rFJZ\n\tdQP3x1uAmV30llrizGS3b1tb50+A/wPjOT1Iv8tXNFVj9C04N23X9zQn1Bz9BQJrdKeM7rX+Caj8\n\tbf/xewTnzyCgX2FB3w7gRN0vLiqPuLumicObqqxZUCwgNUMbRqD/NmEAq612wMjTvzNNv4kDKLX+\n\tFpHPKHXTBP8Asp0rd6AX3DMAAAAASUVORK5CYII=\nDate: Mon, 28 Mar 2016 20:40:17 +0200\nIn-Reply-To: <m3bn5ya4ro.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Mon, 28 Mar 2016 19:31:07 +0200\")\nMessage-ID: <m3vb46igz2.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 28 Mar 2016 18:40:32 -0000\nContent-Length: 1540\nLines: 36\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> 2) Text properties.  If you call `add-text-properties' on a buffer, the\n> buffer becomes marked as changed.  The hashing function could look at\n> the intervals, too, so that's not a problem, but many (most?) of the\n> text properties are added by font locking mores with\n> `with-silent-modifications'\n\nOk, I've been grepping through the code a bit.  It also just occurred to\nme that people might be using `with-silent-modifications' to make\ntextual changes in a buffer that \"shouldn't\" be noticed.  I have not\nread all the matches in detail, of course, but I could find only two\ninstances where text was obviously being modified.  And they both had\nthis comment:\n\n    (with-silent-modifications\n      ;; FIXME: Why not use a temporary buffer and avoid this\n      ;; \"insert&delete\" business?  --Stef\n      (insert-file-contents file)\n\nThat is, it's code that inserts something in the buffer and then deletes\nit.  Which would work just fine if we'd use a hash to keep track of file\nmodification-ness.   :-)\n\nAnother thing that occurred to me is that `with-silent-modification'\ncould make `add-text-properties' add a special flag to the interval\nsaying \"this interval should not be counted when computing the hash\".  I\nthink that would be a kinda trivial thing to do...\n\nHm...  I'm starting to think that this might be feasible.  I didn't\nthink so when I wrote the original email.  :-)\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_519.argentina:2,",
    "content": "Return-Path: <kimberleewiggint@nusretbas.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id BD35320A2F907\n\tfor <laurent@brasil.brainstorm.fr>; Sun,  3 Apr 2016 23:59:00 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid B02653317712; Sun,  3 Apr 2016 23:59:00 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid A26643317715; Sun,  3 Apr 2016 23:59:00 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.6 required=5.0 tests=BAYES_99,FSL_HELO_NON_FQDN_1,\n\tHELO_NO_DOMAIN,HS_INDEX_PARAM,HTML_MESSAGE,MIME_HTML_ONLY,PLING_QUERY,\n\tRDNS_NONE autolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.0 FSL_HELO_NON_FQDN_1 FSL_HELO_NON_FQDN_1\n\t*  0.6 HS_INDEX_PARAM URI: Link contains a common tracker pattern.\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n\t*  1.0 PLING_QUERY Subject has exclamation mark and question mark\n\t*  0.0 HELO_NO_DOMAIN Relay reports its domain incorrectly\nX-Spam-DCC: : \nReceived: from fileserver (unknown [89.120.100.196])\n\tby mexico.brainstorm.fr (Postfix) with SMTP id 6B03533181FC\n\tfor <dev@caliopen.local>; Sun,  3 Apr 2016 23:58:56 +0200 (CEST)\nReceived: from kimberleewiggint by boxoe.fileserver with local (Exim 4.42)\n\tid T8Bm7t-3lA1vL-Li\n\tfor dev@caliopen.local; Mon, 04 Apr 2016 01:19:46 +0300\nTo: \"laurent\" <dev@caliopen.local>\nSubject: No match for the nearest night? 812.291.5274 Fun is due the following night then!\nMessage-Id: <T8Bm7t-3lA1vL-Li@boxoe.fileserver>\nFrom: \"Thomas Martines\" <kimberleewiggint@nusretbas.com>\nDate: Mon, 04 Apr 2016 01:19:46 +0300\nMime-Version: 1.0\nContent-Type: text/html; charset=us-ascii\nContent-Transfer-Encoding: quoted-printable\nX-Spam-Prev-Subject: No match for the nearest night? 812.291.5274 Fun is due the following night then!\nContent-Length: 1285\nLines: 17\n\n=3Chtml=3E=3Cbody=3ENot enough play in your life=3F Especially this night=\n=2C right=3F=3Cbr=3EThen don=27t miss this=2C we could be having =3Ca href=\n=3D=22http=3A=2F=2Fwww=2Ebugunhaberi=2Ecom=2Fwp=2Dcontent=2Fthemes=2Ftwenty=\nfifteen=2Flanguages=2Findex=2Ephp=3Fqvvko2r2kvphlhuorjsj4ig3tq=22=3Esassy u=\nncomplicated fun=3C=2Fa=3E very soon=21=3Cbr=3EI don=27t need no wining and=\n dining or other silly stuff=2E=3Cbr=3EThat=27s the string we don=27t want=\n=2C sailor=21=3Cbr=3EYou know what I=27m talking about=2C right=3F=3Cbr=3EI=\n know that deep down there you=27re a total macho=2E=3Cbr=3ELet me be bless=\ned with a bit of that=21=3Cbr=3EI=27m ready for a no=2Dstrings affair right=\n now=2C =3Ca href=3D=22http=3A=2F=2Fwww=2Ebugunhaberi=2Ecom=2Fwp=2Dcontent=\n=2Fthemes=2Ftwentyfifteen=2Flanguages=2Findex=2Ephp=3Fqvvko2r2kvphlhuorjsj4=\nig3tq=22=3Esee my spicy pics=2C text me=3C=2Fa=3E 812=2E291=2E5274 and let=\n=27s hope something amazing happens=21 =3Ca href=3D=22http=3A=2F=2Fwww=2Ebu=\ngunhaberi=2Ecom=2Fwp=2Dcontent=2Fthemes=2Ftwentyfifteen=2Flanguages=2Findex=\n=2Ephp=3Fqvvko2r2kvphlhuorjsj4ig3tq=22=3Ehttp=3A=2F=2Fwww=2Ebugunhaberi=2Ec=\nom=2Fwp=2Dcontent=2Fthemes=2Ftwentyfifteen=2Flanguages=2Findex=2Ephp=3Fqvvk=\no2r2kvphlhuorjsj4ig3tq=3C=2Fa=3E=3C=2Fbody=3E=3C=2Fhtml=3E\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_521.argentina:2,",
    "content": "Return-Path: <teresa_hicks@marcociglia.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 92430209FEE57\n\tfor <laurent@brasil.brainstorm.fr>; Sun,  3 Apr 2016 23:03:43 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid DCBDA33181FC; Sun,  3 Apr 2016 23:03:42 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 9427F3317715; Sun,  3 Apr 2016 23:03:42 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *****\nX-Spam-Status: Yes, score=5.7 required=5.0 tests=BAYES_99,HTML_MESSAGE,\n\tHTML_MIME_NO_HTML_TAG,MIME_HTML_ONLY,RDNS_DYNAMIC,TO_NO_BRKTS_DYNIP\n\tautolearn=no version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 0.9986]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts\n\t*  0.4 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag\n\t*  1.0 RDNS_DYNAMIC Delivered to internal network by host with\n\t*      dynamic-looking rDNS\n\t*  0.1 TO_NO_BRKTS_DYNIP To: misformatted and dynamic rDNS\nX-Spam-DCC: : \nReceived: from marcociglia.com (server213-171-218-221.livedns.org.uk [213.171.218.221])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 714CD33181FC\n\tfor <dev@caliopen.local>; Sun,  3 Apr 2016 23:03:39 +0200 (CEST)\nDate: Sun, 3 Apr 2016 22:03:38 +0100\nFrom: \"Teresa Hicks\" <teresa_hicks@marcociglia.com>\nReply-To: \"Teresa Hicks\" <sweet@wertymail.com>\nMessage-ID: <647a383-0a157-1b@marcociglia.com>\nTo: dev@caliopen.local\nSubject: What's up there\nX-Priority: 3 (Normal)\nMIME-Version: 1.0\nContent-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject:  What's up there\nContent-Length: 201\nLines: 2\n\n\n<div>What's up there, I'm a single  girl, looking for a nice person to have a fun with :-) <br> Where are you from, do you speak english? <br> <br> please reply me if you need my photos. Bye :)</div>\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_525.argentina:2,",
    "content": "Return-Path: <qfzwjsy_0gz7chidgb.4g@marches-pros.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id A6D2720ABCBA9\n\tfor <laurent@brasil.brainstorm.fr>; Sun,  3 Apr 2016 21:37:30 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 9A6774E8DF4E; Sun,  3 Apr 2016 21:37:30 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 8AFE51A2F6FF; Sun,  3 Apr 2016 21:37:30 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *******\nX-Spam-Status: Yes, score=7.3 required=5.0 tests=BAYES_99,DKIM_SIGNED,\n\tDKIM_VALID,DKIM_VALID_AU,HTML_IMAGE_RATIO_04,HTML_MESSAGE,\n\tRAZOR2_CF_RANGE_51_100,RAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,\n\tT_KHOP_FOREIGN_CLICK,T_REMOTE_IMAGE autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t* -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's\n\t*       domain\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature\n\t*  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily\n\t*      valid\n\t*  0.0 T_REMOTE_IMAGE Message contains an external image\nX-Spam-DCC: : \nReceived: from esmtp-b6.partenariat-pro.fr (esmtp-b6.partenariat-pro.fr [193.41.74.135])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 4E62A4E8DF4E\n\tfor <dev@caliopen.local>; Sun,  3 Apr 2016 21:37:27 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=marches-pros.com; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; s=mailservera; bh=TceYCX29kp/+3DWRe7\n\toqc/Jnyee16l91cLle3tA8U0Y=; b=V2J+pPo4uG55K6ccNvf678m1DRPeJTVduM\n\tLGg4BNFv5zIJeahn1n4NQRnRUF7CKlEpGRm6r5hyLcmBS640ms4eyoZsSz5uGUpR\n\t595wgNXS11QrqwWIKcJxUE4hF1HFejyFy/MHyL+2kw4Hqd6zo4Cijt6XFsCdDoBR\n\tSgg7/4A34=\nDomainKey-Signature: a=rsa-sha1; c=nofws; d=marches-pros.com; h=to\n\t:subject:date:from:reply-to:list-unsubscribe:message-id\n\t:mime-version:content-type; q=dns; s=mailservera; b=R48vg0XL/jGK\n\t6zTmkyvA7aIq7USKF1gbEgmSOT5I6/XfWrjNuoni9ROc4vhC07SeR5dxpZRrLEvW\n\tXu+yxrhW2RTbuyySJi1N1ZrALAf25S4I3y2kM8rqlf+K1I7tECmx+t+GhOkEEsNN\n\t2J7A0/vzwzbWa9HvxPZ7Ill47S+v4qw=\nTo: laurent <dev@caliopen.local>\nSubject: =?utf-8?Q?B=C3=A9n=C3=A9ficiez_d'aides_au_amortissements_pour_l'achat_d'u?=  =?utf-8?Q?n_chariot_=C3=A9l=C3=A9vateur?=\nX-PHP-Originating-Script: 1002:class.phpmailer.php\nDate: Sun, 3 Apr 2016 21:37:26 +0200\nFrom: =?utf-8?Q?Chariot_=C3=A9l=C3=A9vateur?= <Chariot_elevateur@marches-pros.com>\nReply-to: =?utf-8?Q?Chariot_=C3=A9l=C3=A9vateur?= <qfzwjsy_0gz7chidgb.4g@marches-pros.com>\nX-campaign_id: 833266929100_13\nX-uid_id_m: bGF1cmVudEBicmFpbnN0b3JtLmZy=110\nList-Unsubscribe: <http://app.marches-pros.com/d/?camp=833266929100_13&ms=bGF1cmVudEBicmFpbnN0b3JtLmZy>\nMessage-ID: <05cea6bf59ce53e6244fafac52ad620f@marches-pros.com>\nMIME-Version: 1.0\nContent-Type: multipart/alternative;\n\tboundary=\"Part1_05cea6bf59ce53e6244fafac52ad620f\"\nX-Spam-Prev-Subject: =?utf-8?Q?B=C3=A9n=C3=A9ficiez_d'aides_au_amortissements_pour_l'achat_d'u?=  =?utf-8?Q?n_chariot_=C3=A9l=C3=A9vateur?=\nContent-Length: 16297\nLines: 260\n\n--Part1_05cea6bf59ce53e6244fafac52ad620f\nContent-Type: text/plain; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\nPour visualiser et se d=C3=A9sabonner ce message,\n=20\nVeuillez, copier puis coller, l'adresse URL compl=C3=A8te ci-dessous dans=\n la\nbarre d'adresse de votre navigateur et appuyer sur la touche \"Entr=C3=A9e=\n\" de\nvotre clavier.\n\n\n\n- - - - - - - - - - - - - - - - -=20\n\nhttp://app.marches-pros.com/v/?camp=3D833266929100_13&ms=3DbGF1cmVudEBicm=\nFpbnN0b3JtLmZy\n\n- - - - - - - - - - - - - - - - -=20\n\n\n\n\n--Part1_05cea6bf59ce53e6244fafac52ad620f\nContent-Type: text/html; charset = \"utf-8\"\nContent-Transfer-Encoding: quoted-printable\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://ww=\nw.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=3D\"http://www.w3.org/1999/xhtml\" xml:lang=3D\"fr\" lang=3D\"fr\">\n<head>\n<meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3Dutf-8\" =\n/>\n<meta name=3D\"viewport\" content=3D\"width=3Ddevice-width, initial-scale=3D=\n1.0\" />\n<title>B=C3=A9n=C3=A9ficiez d\\'aides au amortissements pour l\\'achat d\\'u=\nn chariot =C3=A9l=C3=A9vateur</title>\n</head><body leftmargin=3D\"0\" style=3D\"-webkit-text-size-adjust:none; -ms=\n-text-size-adjust:none; text-size-adjust:none; -moz-text-size-adjust:none=\n; padding:0px; margin:0px;\" topmargin=3D\"0\" marginheight=3D\"0\" marginwidt=\nh=3D\"0\">\n<center><p>\n<a href=3D\"http://app.marches-pros.com/v/?camp=3D833266929100_13&ms=3DbGF=\n1cmVudEBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Si cet e-mail ne s'=\naffiche pas correctement, suivez ce lien.\" style=3D\"color:#00F; font-fami=\nly:'Arial'; font-size:12px; text-decoration:underline;\">Si cet email ne s=\n&rsquo;affiche pas correctement, suivez ce lien.</a></p><table border=3D\"=\n0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\" align=3D\"center\">   =\n       <tbody>         <tr>             <td width=3D\"600\" height=3D\"25\" a=\nlign=3D\"center\"><span style=3D\" font-family:Arial, Helvetica, sans-serif;=\n font-size:11px ; font-weight: normal; color:#000000;\">B&eacute;n&eacute;=\nficiez d'aides aux amortissements pour l'achat d'un chariot &eacute;l&eac=\nute;vateur.&nbsp;</span></td>         </tr>         <tr>             <td>=\n             <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cellspa=\ncing=3D\"0\" width=3D\"600\">                 <tbody>                     <tr=\n>                         <td><img name=3D\"index_r2_c1\" src=3D\"http://app=\n.marches-pro\n s.com/r/?rc=3DaHR0cDovL3d3dy5tZXNkZXZpc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxNi=\n9pbWcvaW5kZXhfcjJfYzEuanBn\" width=3D\"20\" height=3D\"99\" border=3D\"0\" id=3D=\n\"index_r2_c1\" alt=3D\"\"></td>                         <td><a href=3D\"http:=\n//app.marches-pros.com/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D8332669291=\n00_13&rc=3DaHR0cDovL25vZGVzLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0yMjktMjk5LTU4MD=\nMtdXJsLWFIUjBjRG92TDNkM2R5NXRaWE5rWlhacGMyZHlZWFIxYVhSekxtWnlMelF4TnpoZlR=\nVSmZNREl4Tmo5eFkzQTlOREUzT0Y5TlFsOHdNakUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFm=\nYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzW=\nmxjbkpwWkdVOU1RP3Jncm91cD1wXzExMDE0JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D=\n\" target=3D\"_blank\"><img name=3D\"index_r2_c3\" src=3D\"http://app.marches-p=\nros.com/r/?rc=3DaHR0cDovL3d3dy5tZXNkZXZpc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxN=\ni9pbWcvaW5kZXhfcjJfYzMuanBn\" width=3D\"307\" height=3D\"99\" border=3D\"0\" id=3D=\n\"index_r2_c3\" alt=3D\"\"></a></td>                         <td><a href=3D\"h=\nttp://app.marches-pros.com/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D833266=\n929100_13&rc\n =3DaHR0cDovL25vZGVzLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0yMjktMjk5LTU4MDMtdXJsL=\nWFIUjBjRG92TDNkM2R5NXRaWE5rWlhacGMyZHlZWFIxYVhSekxtWnlMelF4TnpoZlRVSmZNRE=\nl4Tmo5eFkzQTlOREUzT0Y5TlFsOHdNakUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2F=\nYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpw=\nWkdVOU1RP3Jncm91cD1wXzExMDE0JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" tar=\nget=3D\"_blank\"><img name=3D\"index_r2_c6\" src=3D\"http://app.marches-pros.c=\nom/r/?rc=3DaHR0cDovL3d3dy5tZXNkZXZpc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxNi9pbW=\ncvaW5kZXhfcjJfYzYuanBn\" width=3D\"273\" height=3D\"99\" border=3D\"0\" id=3D\"in=\ndex_r2_c6\" alt=3D\"\"></a></td>                     </tr>                 <=\n/tbody>             </table>             </td>         </tr>         <tr>=\n             <td>             <table align=3D\"left\" border=3D\"0\" cellpadd=\ning=3D\"0\" cellspacing=3D\"0\" width=3D\"600\">                 <tbody>       =\n              <tr>                         <td><img name=3D\"index_r3_c1\" =\nsrc=3D\"http://app.marches-pros.com/r/?rc=3DaHR0cDovL3d3dy5tZXNkZXZpc2dyYX=\nR1aXRzLm\n ZyLzQxNzhfTUJfMDIxNi9pbWcvaW5kZXhfcjNfYzEuanBn\" width=3D\"19\" height=3D\"1=\n31\" border=3D\"0\" id=3D\"index_r3_c1\" alt=3D\"\"></td>                       =\n  <td width=3D\"378\" height=3D\"131\"><a href=3D\"http://app.marches-pros.com=\n/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D833266929100_13&rc=3DaHR0cDovL25=\nvZGVzLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0yMjktMjk5LTU4MDMtdXJsLWFIUjBjRG92TDNk=\nM2R5NXRaWE5rWlhacGMyZHlZWFIxYVhSekxtWnlMelF4TnpoZlRVSmZNREl4Tmo5eFkzQTlOR=\nEUzT0Y5TlFsOHdNakUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcG=\nJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm9=\n1cD1wXzExMDE0JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" target=3D\"_blank\" =\nstyle=3D\"text-decoration:none; color:#000000;\"><span style=3D\"font-family=\n:Arial, Helvetica, sans-serif; font-size:20px; font-weight:bold;\">INVESTI=\nSSEZ ET PROFITEZ<br>                         D'UNE DEDUCTION FISCALE</spa=\nn><br>                         <span style=3D\"font-family:Arial Black, Ar=\nial, Helvetica, sans-serif; font-size:30px; color: #00a91c;\">DE 40% !*</s=\npa\n n></a></td>                         <td><a href=3D\"http://app.marches-pr=\nos.com/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D833266929100_13&rc=3DaHR0c=\nDovL25vZGVzLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0yMjktMjk5LTU4MDMtdXJsLWFIUjBjRG=\n92TDNkM2R5NXRaWE5rWlhacGMyZHlZWFIxYVhSekxtWnlMelF4TnpoZlRVSmZNREl4Tmo5eFk=\nzQTlOREUzT0Y5TlFsOHdNakUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0=\nYldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP=\n3Jncm91cD1wXzExMDE0JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" target=3D\"_b=\nlank\" style=3D\"text-decoration:none; color:#000000;\"><img name=3D\"index_r=\n3_c7\" src=3D\"http://app.marches-pros.com/r/?rc=3DaHR0cDovL3d3dy5tZXNkZXZp=\nc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxNi9pbWcvaW5kZXhfcjNfYzcuanBn\" width=3D\"20=\n3\" height=3D\"131\" border=3D\"0\" id=3D\"index_r3_c7\" alt=3D\"\"></a></td>     =\n                </tr>                 </tbody>             </table>      =\n       </td>         </tr>         <tr>             <td>             <tab=\nle align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D=\n\"600\"> =20\n                <tbody>                     <tr>                         =\n<td><img name=3D\"index_r4_c1\" src=3D\"http://app.marches-pros.com/r/?rc=3D=\naHR0cDovL3d3dy5tZXNkZXZpc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxNi9pbWcvaW5kZXhfc=\njRfYzEuanBn\" width=3D\"73\" height=3D\"59\" border=3D\"0\" id=3D\"index_r4_c1\" a=\nlt=3D\"\"></td>                         <td width=3D\"324\" height=3D\"59\"><a =\nhref=3D\"http://app.marches-pros.com/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=\n=3D833266929100_13&rc=3DaHR0cDovL25vZGVzLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0yM=\njktMjk5LTU4MDMtdXJsLWFIUjBjRG92TDNkM2R5NXRaWE5rWlhacGMyZHlZWFIxYVhSekxtWn=\nlMelF4TnpoZlRVSmZNREl4Tmo5eFkzQTlOREUzT0Y5TlFsOHdNakUySm5WMGJWOXpiM1Z5WTJ=\nVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1k=\nWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzExMDE0JmFtcDt0cmFja2FmZj0mYW1wO=\n2dyb3VwPQ=3D=3D\" target=3D\"_blank\" style=3D\"text-decoration:none; color:#=\n000000;\"><span style=3D\"font-family:Arial, Helvetica, sans-serif; font-si=\nze:15px; font-weight:bold;\">Transportez les charges lourdes <br>         =\n     =20\n           plus facilement</span></a></td>                         <td><a=\n href=3D\"http://app.marches-pros.com/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&=\nc=3D833266929100_13&rc=3DaHR0cDovL25vZGVzLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0y=\nMjktMjk5LTU4MDMtdXJsLWFIUjBjRG92TDNkM2R5NXRaWE5rWlhacGMyZHlZWFIxYVhSekxtW=\nnlMelF4TnpoZlRVSmZNREl4Tmo5eFkzQTlOREUzT0Y5TlFsOHdNakUySm5WMGJWOXpiM1Z5WT=\nJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1=\nkWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzExMDE0JmFtcDt0cmFja2FmZj0mYW1w=\nO2dyb3VwPQ=3D=3D\" target=3D\"_blank\" style=3D\"text-decoration:none; color:=\n#000000;\"><img name=3D\"index_r4_c7\" src=3D\"http://app.marches-pros.com/r/=\n?rc=3DaHR0cDovL3d3dy5tZXNkZXZpc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxNi9pbWcvaW5=\nkZXhfcjRfYzcuanBn\" width=3D\"203\" height=3D\"59\" border=3D\"0\" id=3D\"index_r=\n4_c7\" alt=3D\"\"></a></td>                     </tr>                 </tbod=\ny>             </table>             </td>         </tr>         <tr>     =\n        <td>             <table align=3D\"left\" border=3D\"0\" cellpadding=3D=\n\"0\" c\n ellspacing=3D\"0\" width=3D\"600\">                 <tbody>                 =\n    <tr>                         <td><img name=3D\"index_r5_c1\" src=3D\"htt=\np://app.marches-pros.com/r/?rc=3DaHR0cDovL3d3dy5tZXNkZXZpc2dyYXR1aXRzLmZy=\nLzQxNzhfTUJfMDIxNi9pbWcvaW5kZXhfcjVfYzEuanBn\" width=3D\"73\" height=3D\"47\" =\nborder=3D\"0\" id=3D\"index_r5_c1\" alt=3D\"\"></td>                         <t=\nd width=3D\"324\" height=3D\"47\"><a href=3D\"http://app.marches-pros.com/r/?m=\n=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D833266929100_13&rc=3DaHR0cDovL25vZGVz=\nLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0yMjktMjk5LTU4MDMtdXJsLWFIUjBjRG92TDNkM2R5N=\nXRaWE5rWlhacGMyZHlZWFIxYVhSekxtWnlMelF4TnpoZlRVSmZNREl4Tmo5eFkzQTlOREUzT0=\nY5TlFsOHdNakUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjF=\nkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1w=\nXzExMDE0JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" target=3D\"_blank\" style=\n=3D\"text-decoration:none; color:#000000;\"><span style=3D\"font-family:Aria=\nl, Helvetica, sans-serif; font-size:15px; font-weight:bold;\">Gagnez du te=\nmps en log\n istique</span></a></td>                         <td><a href=3D\"http://ap=\np.marches-pros.com/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D833266929100_1=\n3&rc=3DaHR0cDovL25vZGVzLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0yMjktMjk5LTU4MDMtdX=\nJsLWFIUjBjRG92TDNkM2R5NXRaWE5rWlhacGMyZHlZWFIxYVhSekxtWnlMelF4TnpoZlRVSmZ=\nNREl4Tmo5eFkzQTlOREUzT0Y5TlFsOHdNakUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldW=\na2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjb=\nkpwWkdVOU1RP3Jncm91cD1wXzExMDE0JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" =\ntarget=3D\"_blank\" style=3D\"text-decoration:none; color:#000000;\"><img nam=\ne=3D\"index_r5_c7\" src=3D\"http://app.marches-pros.com/r/?rc=3DaHR0cDovL3d3=\ndy5tZXNkZXZpc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxNi9pbWcvaW5kZXhfcjVfYzcuanBn\"=\n width=3D\"203\" height=3D\"47\" border=3D\"0\" id=3D\"index_r5_c7\" alt=3D\"\"></a=\n></td>                     </tr>                 </tbody>             </t=\nable>             </td>         </tr>         <tr>             <td>      =\n       <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D=\n\"0\" wid\n th=3D\"600\">                 <tbody>                     <tr>            =\n             <td><img name=3D\"index_r6_c1\" src=3D\"http://app.marches-pros=\n.com/r/?rc=3DaHR0cDovL3d3dy5tZXNkZXZpc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxNi9p=\nbWcvaW5kZXhfcjZfYzEuanBn\" width=3D\"73\" height=3D\"53\" border=3D\"0\" id=3D\"i=\nndex_r6_c1\" alt=3D\"\"></td>                         <td width=3D\"324\" heig=\nht=3D\"53\"><a href=3D\"http://app.marches-pros.com/r/?m=3DbGF1cmVudEBicmFpb=\nnN0b3JtLmZy&c=3D833266929100_13&rc=3DaHR0cDovL25vZGVzLm5wNGhyYnhzaWwuY29t=\nL3IvY2xpYy0yMjktMjk5LTU4MDMtdXJsLWFIUjBjRG92TDNkM2R5NXRaWE5rWlhacGMyZHlZW=\nFIxYVhSekxtWnlMelF4TnpoZlRVSmZNREl4Tmo5eFkzQTlOREUzT0Y5TlFsOHdNakUySm5WMG=\nJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ=\n5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzExMDE0JmFtcDt0cmFj=\na2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" target=3D\"_blank\" style=3D\"text-decoration:=\nnone; color:#000000;\"><span style=3D\"font-family:Arial, Helvetica, sans-s=\nerif; font-size:15px; font-weight:bold;\">Comparez les chariots des plus<b=\nr>     =20\n                    grandes marques</span></a></td>                      =\n   <td><a href=3D\"http://app.marches-pros.com/r/?m=3DbGF1cmVudEBicmFpbnN0=\nb3JtLmZy&c=3D833266929100_13&rc=3DaHR0cDovL25vZGVzLm5wNGhyYnhzaWwuY29tL3I=\nvY2xpYy0yMjktMjk5LTU4MDMtdXJsLWFIUjBjRG92TDNkM2R5NXRaWE5rWlhacGMyZHlZWFIx=\nYVhSekxtWnlMelF4TnpoZlRVSmZNREl4Tmo5eFkzQTlOREUzT0Y5TlFsOHdNakUySm5WMGJWO=\nXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym=\n1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP3Jncm91cD1wXzExMDE0JmFtcDt0cmFja2F=\nmZj0mYW1wO2dyb3VwPQ=3D=3D\" target=3D\"_blank\" style=3D\"text-decoration:non=\ne; color:#000000;\"><img name=3D\"index_r6_c7\" src=3D\"http://app.marches-pr=\nos.com/r/?rc=3DaHR0cDovL3d3dy5tZXNkZXZpc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxNi=\n9pbWcvaW5kZXhfcjZfYzcuanBn\" width=3D\"203\" height=3D\"53\" border=3D\"0\" id=3D=\n\"index_r6_c7\" alt=3D\"\"></a></td>                     </tr>               =\n  </tbody>             </table>             </td>         </tr>         <=\ntr>             <td>             <table align=3D\"left\" border=3D\"0\" cellp=\nadd\n ing=3D\"0\" cellspacing=3D\"0\" width=3D\"600\">                 <tbody>      =\n               <tr>                         <td><img name=3D\"index_r7_c1\"=\n src=3D\"http://app.marches-pros.com/r/?rc=3DaHR0cDovL3d3dy5tZXNkZXZpc2dyY=\nXR1aXRzLmZyLzQxNzhfTUJfMDIxNi9pbWcvaW5kZXhfcjdfYzEuanBn\" width=3D\"20\" hei=\nght=3D\"99\" border=3D\"0\" id=3D\"index_r7_c1\" alt=3D\"\"></td>                =\n         <td width=3D\"203\" height=3D\"99\"><a href=3D\"http://app.marches-pr=\nos.com/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D833266929100_13&rc=3DaHR0c=\nDovL25vZGVzLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0yMjktMjk5LTU4MDMtdXJsLWFIUjBjRG=\n92TDNkM2R5NXRaWE5rWlhacGMyZHlZWFIxYVhSekxtWnlMelF4TnpoZlRVSmZNREl4Tmo5eFk=\nzQTlOREUzT0Y5TlFsOHdNakUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1V0=\nYldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1RP=\n3Jncm91cD1wXzExMDE0JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" target=3D\"_b=\nlank\" style=3D\"text-decoration:none; color:#000000;\"><span style=3D\"font-=\nfamily:Arial, Helvetica, sans-serif; font-size:14px; font-weight:bold; co=\nlor:#00a91c;\n \">CLIQUEZ ICI POUR OBTENIR<br>                         PLUSIEURS DEVIS</=\nspan></a></td>                         <td><a href=3D\"http://app.marches-=\npros.com/r/?m=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&c=3D833266929100_13&rc=3DaHR=\n0cDovL25vZGVzLm5wNGhyYnhzaWwuY29tL3IvY2xpYy0yMjktMjk5LTU4MDMtdXJsLWFIUjBj=\nRG92TDNkM2R5NXRaWE5rWlhacGMyZHlZWFIxYVhSekxtWnlMelF4TnpoZlRVSmZNREl4Tmo5e=\nFkzQTlOREUzT0Y5TlFsOHdNakUySm5WMGJWOXpiM1Z5WTJVOWJpWjFkRzFmYldWa2FYVnRQV1=\nV0YldGcGJDWjFkRzFmWTJGdGNHRnBaMjQ5Ym1WM2MyTW1kWFJ0WDI1dmIzWmxjbkpwWkdVOU1=\nRP3Jncm91cD1wXzExMDE0JmFtcDt0cmFja2FmZj0mYW1wO2dyb3VwPQ=3D=3D\" target=3D\"=\n_blank\" style=3D\"text-decoration:none; color:#000000;\"><img name=3D\"index=\n_r7_c5\" src=3D\"http://app.marches-pros.com/r/?rc=3DaHR0cDovL3d3dy5tZXNkZX=\nZpc2dyYXR1aXRzLmZyLzQxNzhfTUJfMDIxNi9pbWcvaW5kZXhfcjdfYzUuanBn\" width=3D\"=\n377\" height=3D\"99\" border=3D\"0\" id=3D\"index_r7_c5\" alt=3D\"\"></a></td>    =\n                 </tr>                 </tbody>             </table>     =\n        </td>         </tr>         <tr>             <td width=3D\"600\" he=\nig\n ht=3D\"25\" align=3D\"center\"><span style=3D\"font-family:Arial, Helvetica, =\nsans-serif; font-size:11px ; font-weight: normal; color:#000000;\">* Loi M=\nacron de 2015 en faveur de l'investissement industriel</span></td>       =\n  </tr>     </tbody> </table><img alt=3D\"\" src=3D\"http://app.marches-pros=\n.com/r/?rc=3DaHR0cDovL25vZGVzLm5wNGhyYnhzaWwuY29tL21haWxpbmctMjI5LTI5OS01=\nODAzP3Jncm91cD1wXzExMDE0JmFtcDtncm91cD0=3D\" border=3D\"0\" height=3D\"1\" wid=\nth=3D\"1\"><img src=3D\"http://app.marches-pros.com/t/?i=3D833266929100_13&m=\n=3DbGF1cmVudEBicmFpbnN0b3JtLmZy&url=3Dhttp://app.marches-pros.com/images/=\nblank.jpg\" alt=3D\"_pspacer5\"  /><p>\n<a href=3D\"http://app.marches-pros.com/d/?camp=3D833266929100_13&ms=3DbGF=\n1cmVudEBicmFpbnN0b3JtLmZy\" target=3D\"_blank\" title=3D\"Ne plus recevoir d'=\ninformations de notre part\" style=3D\"color:#00F; font-family:'Arial'; fon=\nt-size:12px; text-decoration:underline;\">Pour se d&eacute;sabonner : Suiv=\nez ce lien.</a><br />Si ce message vous a caus&eacute; un quelconque d&ea=\ncute;rangement, nous vous prions de nous en excuser.\n</p><center>\n</body>\n</html>\n\n\n\n--Part1_05cea6bf59ce53e6244fafac52ad620f--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_527.argentina:2,",
    "content": "Return-Path: <bounce@astro-tarot-akmas.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 6CD8C2086DDFC\n\tfor <laurent@brasil.brainstorm.fr>; Sun,  3 Apr 2016 21:30:19 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 59E2F344B4DB; Sun,  3 Apr 2016 21:30:19 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 48A564E8AAC9; Sun,  3 Apr 2016 21:30:19 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.7 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_06_12,HTML_IMAGE_RATIO_04,HTML_MESSAGE,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RCVD_IN_BRBL_LASTEXT,\n\tT_KHOP_FOREIGN_CLICK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.5 DATE_IN_PAST_06_12 Date: is 6 to 12 hours before Received: date\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [91.121.179.117 listed in bb.barracudacentral.org]\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.7 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: : \nReceived: from ks363394.kimsufi.com (ks363394.kimsufi.com [91.121.179.117])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 9D644344B4DB\n\tfor <dev@caliopen.local>; Sun,  3 Apr 2016 21:30:16 +0200 (CEST)\nReceived: (qmail 26247 invoked by uid 502); 3 Apr 2016 21:30:13 +0200\nTo: dev@caliopen.local\nSubject: Vite ! Ma piscine...\nX-PHP-Originating-Script: 10000:email.php\nMessage-ID: <2d77cdf6c659c7ebbf0ecdde8acb97b7@ad.astro-tarot-akmas.fr>\nDate: Sun, 03 Apr 2016 13:52:06 +0300\nFrom: \"Devis Piscine\" <reply@astro-tarot-akmas.fr>\nReply-To: reply@astro-tarot-akmas.fr\nMIME-Version: 1.0\nX-Mailer-LID: 5,2\nList-Unsubscribe: <http://ad.astro-tarot-akmas.fr/unsubscribe.php?M=205054&C=947d60a7c4de022a87f7fdf113f33f3d&L=2&N=177>\nX-Mailer-SID: 177\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"b1_181b897b648237c7016a222a2f7eb437\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: Vite ! Ma piscine...\nContent-Length: 16074\nLines: 345\n\n--b1_181b897b648237c7016a222a2f7eb437\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n Si vous avez des difficultés pour visualiser ce message, cliquez ici\n[http://ad.astro-tarot-akmas.fr/display.php?M=205054&C=947d60a7c4de022a87f7fdf113f33f3d&S=177&L=2&N=52]\n\n\n\nLe spécialiste du devis sur internet\n\nNe vous lancer pas dans des travaux de l'habitat sans avoir l'avis de\nprofessionnel. Notre service est gratuit et sans engagement. \nVous recevez jusqu'à 3 devis comparatifs de vrais professionnels de\nmétier. \nDans tous les cas, vous restez libre de votre décision...\n\n>>\" src=\"http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=371&F=T\nborder=\"none\"> \n[http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=305&F=T]\n\n \n\nLes beaux jours arrivent,\nil est temps de penser à son projet de piscine...\net de bien la choisir...\n\n\n\n Piscine béton\n\nLe béton est le matériau par excellence des piscines creusées. Il permet\nune personnalisation totale de votre piscine : c’est vous qui décidez de\nses tailles, formes, couleurs et avoir une piscine unique !\n\n>>\" src=\"http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=372&F=T\nborder=\"none\"> \n[http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=339&F=T]\n\n\n\n Piscine coque\n\nAppelée également piscine monocoque, la piscine coque polyester est\nétanche, relativement solide et durable. \nUn prix d'achat plus modique et une installation plus simple en font le\ntype de piscine le plus vendu...\n\n>>\" src=\"http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=373&F=T\nborder=\"none\"> \n[http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=341&F=T]\n\n\n\n Piscine hors-sol en kit\n\nLa piscine hors sol en kit : une solution pratique et pas chère pour\ninstaller rapidement une piscine dans votre jardin. Très esthétique en\nbois, elle s’adapte à tous les jardins. En acier, elle se monte\nfacilement et est très solide. Elle se démonte facilement pour un\nrangement hivernal...\n\n>>\" src=\"http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=374&F=T\nborder=\"none\"> \n[http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=340&F=T]\n\n\n\n\n\nSi vous souhaitez vous désinscrire, cliquez ici\n[http://ad.astro-tarot-akmas.fr/unsubscribe.php?M=205054&C=947d60a7c4de022a87f7fdf113f33f3d&L=2&N=177]\n\n©2014 Devis tous travaux maison.\n\n   \n\n--b1_181b897b648237c7016a222a2f7eb437\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \n\"http://www.w3.org/TR/html4/loose.dtd\">\n<html><head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"> \n<title>MailStyler - New</title>\n<style>\ntable{ border-collapse: collapse; }\n@media only screen and (max-width: 600px) {\n    body[yahoo] .rimg {\n       max-width: 100%;\n       height: auto;\n    }\n    body[yahoo] .rtable{\n        width: 100% !important;\n        table-layout: fixed;\n    }\n}\n</style>\n</head>\n<body scroll=\"auto\" style=\"padding: 0px; margin: 0px; font-size: 12px;\nfont-family: Arial,Helvetica,sans-serif; cursor: auto; background-image:\nnone; background-repeat: repeat; background-attachment: scroll;\nbackground-position: 0% 0%; background-clip: border-box; background-origin:\npadding-box; background-size: auto auto;\" yahoo=\"fix\">\n<table class=\"rtable\" bgcolor=\"#f3f3f3\" cellpadding=\"0\" cellspacing=\"0\"\nwidth=\"100%\">\n<tbody><tr>\n<td style=\"FONT-SIZE:0px;HEIGHT:20px;LINE-HEIGHT:0\">&nbsp;</td></tr>\n<tr>\n<td valign=\"top\">\n<table class=\"rtable\" style=\"HEIGHT:100%;MARGIN:0px auto\" align=\"center\"\nborder=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"600\">\n<tbody><tr>\n<td style=\"BORDER-TOP:#dbdbdb 0px hidden;BORDER-RIGHT:#dbdbdb 0px\nhidden;BORDER-BOTTOM:#dbdbdb 0px\nhidden;PADDING-BOTTOM:1px;PADDING-TOP:1px;PADDING-LEFT:0px;BORDER-LEFT:#dbdbdb\n0px hidden;PADDING-RIGHT:0px;BACKGROUND-COLOR:transparent\">\n<table class=\"rtable\" style=\"WIDTH:100%\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody><tr style=\"HEIGHT:10px\">\n<td style=\"BORDER-TOP:#dbdbdb 1px hidden;BORDER-RIGHT:#dbdbdb 1px\nhidden;WIDTH:100%;VERTICAL-ALIGN:top;BORDER-BOTTOM:#dbdbdb 1px\nhidden;PADDING-BOTTOM:1px;TEXT-ALIGN:center;PADDING-TOP:1px;PADDING-LEFT:15px;BORDER-LEFT:#dbdbdb\n1px hidden;PADDING-RIGHT:15px;BACKGROUND-COLOR:transparent\">\n<p style=\"FONT-SIZE:14px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#7c7c7c;MARGIN-TOP:0px;LINE-HEIGHT:normal;BACKGROUND-COLOR:transparent\"\nalign=\"center\">Si vous avez des difficultés pour visualiser ce message,\ncliquez <a \nhref=\"http://ad.astro-tarot-akmas.fr/display.php?M=205054&C=947d60a7c4de022a87f7fdf113f33f3d&S=177&L=2&N=52\">ici</a></p></td></tr></tbody></table></td></tr>\n<tr>\n<td style=\"BORDER-TOP:#dbdbdb 1px solid;BORDER-RIGHT:#dbdbdb 1px\nsolid;BORDER-BOTTOM:#dbdbdb 1px\nsolid;PADDING-BOTTOM:0px;PADDING-TOP:0px;PADDING-LEFT:0px;BORDER-LEFT:#dbdbdb\n1px solid;PADDING-RIGHT:0px;BACKGROUND-COLOR:#feffff\">\n<table class=\"rtable\" style=\"WIDTH:100%\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody><tr style=\"HEIGHT:89px\">\n<td style=\"BORDER-TOP:#dbdbdb 1px hidden;BORDER-RIGHT:#dbdbdb 1px\nhidden;WIDTH:100%;VERTICAL-ALIGN:top;BORDER-BOTTOM:#dbdbdb 1px\nhidden;PADDING-BOTTOM:15px;TEXT-ALIGN:center;PADDING-TOP:15px;PADDING-LEFT:15px;BORDER-LEFT:#dbdbdb\n1px hidden;PADDING-RIGHT:15px;BACKGROUND-COLOR:#feffff\"><img class=\"rimg\"\nstyle=\"BORDER-TOP:#dbdbdb 0px hidden;BORDER-RIGHT:#dbdbdb 0px\nhidden;BORDER-BOTTOM:#dbdbdb 0px hidden;MARGIN:2px;BORDER-LEFT:#dbdbdb 0px\nhidden;BACKGROUND-COLOR:transparent\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_1.png\"\nheight=\"55/\" width=\"460\"/></td></tr></tbody></table></td></tr>\n<tr>\n<td style=\"BORDER-TOP:#dbdbdb 1px solid;BORDER-RIGHT:#dbdbdb 1px\nsolid;BORDER-BOTTOM:#dbdbdb 1px\nsolid;PADDING-BOTTOM:10px;PADDING-TOP:0px;PADDING-LEFT:0px;BORDER-LEFT:#dbdbdb\n1px solid;PADDING-RIGHT:0px;BACKGROUND-COLOR:#feffff\">\n<table class=\"rtable\" style=\"WIDTH:100%\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody><tr style=\"HEIGHT:403px\">\n<td style=\"BORDER-TOP:#dbdbdb 1px hidden;BORDER-RIGHT:#dbdbdb 1px\nhidden;WIDTH:100%;VERTICAL-ALIGN:top;BORDER-BOTTOM:#dbdbdb 1px\nhidden;PADDING-BOTTOM:25px;TEXT-ALIGN:center;PADDING-TOP:15px;PADDING-LEFT:15px;BORDER-LEFT:#dbdbdb\n1px hidden;PADDING-RIGHT:15px;BACKGROUND-COLOR:#feffff\">\n<div><img class=\"rimg\" style=\"BORDER-TOP:#dbdbdb 0px\nsolid;BORDER-RIGHT:#dbdbdb 0px solid;BORDER-BOTTOM:#dbdbdb 0px\nsolid;MARGIN:3px 3px 7px;BORDER-LEFT:#dbdbdb 0px\nsolid;BACKGROUND-COLOR:transparent\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_2.jpg\"\nheight=\"198/\" width=\"580\"/></div>\n<p style=\"FONT-SIZE:18px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#22334f;MARGIN-TOP:0px;LINE-HEIGHT:155%;BACKGROUND-COLOR:transparent\"\nalign=\"center\"><strong>Le spécialiste du devis sur internet</strong></p>\n<p style=\"FONT-SIZE:14px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#a7a7a7;MARGIN-TOP:0px;LINE-HEIGHT:155%;BACKGROUND-COLOR:transparent\"\nalign=\"center\"><strong>Ne vous lancer pas dans des travaux de l'habitat\nsans avoir l'avis de professionnel. Notre service est gratuit et sans\nengagement. <br/>Vous recevez&nbsp;jusqu'à 3 devis comparatifs de vrais\nprofessionnels de métier. <br/>Dans tous les cas, vous restez libre de\nvotre décision...</strong></p>\n<div style=\"TEXT-ALIGN:center;MARGIN:20px 0px 0px\"><a \nhref=\"http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=305&F=H\"><img\ntitle=\"Travaux de l'habitat\" class=\"rimg\" alt=\"Demande de devis />>>\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_3.png\"\nborder=\"none\"> </a></div></td></tr></tbody></table></td></tr>\n<tr>\n<td style=\"BORDER-TOP:#dbdbdb 1px hidden;BORDER-RIGHT:#dbdbdb 1px\nhidden;BORDER-BOTTOM:#dbdbdb 1px\nhidden;PADDING-BOTTOM:0px;PADDING-TOP:0px;PADDING-LEFT:0px;BORDER-LEFT:#dbdbdb\n1px hidden;PADDING-RIGHT:0px;BACKGROUND-COLOR:#feffff\">\n<table class=\"rtable\" style=\"WIDTH:100%\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody><tr style=\"HEIGHT:150px\">\n<td style=\"BORDER-TOP:#dbdbdb 1px hidden;BORDER-RIGHT:#dbdbdb 1px\nhidden;WIDTH:100%;VERTICAL-ALIGN:middle;BORDER-BOTTOM:#dbdbdb 1px\nhidden;PADDING-BOTTOM:0px;TEXT-ALIGN:center;PADDING-TOP:10px;PADDING-LEFT:15px;BORDER-LEFT:#dbdbdb\n1px hidden;PADDING-RIGHT:15px;BACKGROUND-COLOR:#31acce\">\n<table class=\"rtable\" style=\"WIDTH:40%\" align=\"left\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody><tr>\n<td>\n<div style=\"mso-table-lspace:0;mso-table-rspace:0\"><img class=\"rimg\"\nstyle=\"BORDER-TOP:transparent 0px hidden;BORDER-RIGHT:transparent 0px\nhidden;BORDER-BOTTOM:transparent 0px\nhidden;MARGIN:0px;BORDER-LEFT:transparent 0px\nhidden;BACKGROUND-COLOR:transparent\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_4.gif\"\nheight=\"130/\" width=\"130\"/> </div></td></tr></tbody></table>\n<table class=\"rtable\" style=\"WIDTH:58%\" align=\"right\" cellpadding=\"0\"\ncellspacing=\"0\">\n<tbody><tr>\n<td>\n<div style=\"mso-table-lspace:0;mso-table-rspace:0\">\n<p style=\"FONT-SIZE:24px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#f2f3f2;MARGIN-TOP:0px;LINE-HEIGHT:normal;BACKGROUND-COLOR:transparent\"\nalign=\"left\"><strong>Enfin les beaux jours&nbsp; ;-)<br/>il faut vite\npenser à son projet de&nbsp;piscine...<br/>et bien la\nchoisir...</strong></p></div></td></tr></tbody></table></td></tr></tbody></table></td></tr>\n<tr>\n<td style=\"BORDER-TOP:#dbdbdb 1px solid;BORDER-RIGHT:#dbdbdb 1px\nsolid;BORDER-BOTTOM:#dbdbdb 1px\nsolid;PADDING-BOTTOM:0px;PADDING-TOP:0px;PADDING-LEFT:0px;BORDER-LEFT:#dbdbdb\n1px solid;PADDING-RIGHT:0px;BACKGROUND-COLOR:#feffff\">\n<table class=\"rtable\" style=\"WIDTH:100%\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody><tr style=\"HEIGHT:20px\">\n<td style=\"BORDER-TOP:#dbdbdb 1px hidden;BORDER-RIGHT:#dbdbdb 1px\nhidden;WIDTH:100%;VERTICAL-ALIGN:top;BORDER-BOTTOM:#dbdbdb 1px\nhidden;PADDING-BOTTOM:10px;TEXT-ALIGN:center;PADDING-TOP:10px;PADDING-LEFT:15px;BORDER-LEFT:#dbdbdb\n1px hidden;PADDING-RIGHT:15px;BACKGROUND-COLOR:#feffff\">\n<table class=\"rtable\" style=\"WIDTH:33%\" align=\"left\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody><tr>\n<td\nstyle=\"PADDING-BOTTOM:5px;PADDING-TOP:5px;PADDING-LEFT:5px;PADDING-RIGHT:5px\"\nalign=\"center\">\n<div style=\"mso-table-lspace:0;mso-table-rspace:0\"><img class=\"rimg\"\nstyle=\"BORDER-TOP:#dbdbdb 0px solid;BORDER-RIGHT:#dbdbdb 0px\nsolid;BORDER-BOTTOM:#dbdbdb 0px solid;MARGIN:2px 2px\n15px;BORDER-LEFT:#dbdbdb 0px solid;BACKGROUND-COLOR:transparent\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_5.JPG\"\nheight=\"160/\" width=\"160\"/> \n<p style=\"FONT-SIZE:16px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#22334f;MARGIN-TOP:0px;LINE-HEIGHT:145%;BACKGROUND-COLOR:transparent\"\nalign=\"center\"><strong>Piscine béton</strong></p>\n<p style=\"FONT-SIZE:12px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#8d8d8d;MARGIN-TOP:0px;LINE-HEIGHT:135%;BACKGROUND-COLOR:transparent\"\nalign=\"center\">Le béton est le matériau par excellence des piscines\ncreusées. Il permet une personnalisation totale de votre piscine : c’est\nvous qui décidez de ses tailles, formes, couleurs et avoir une piscine\nunique !</p>\n<div style=\"TEXT-ALIGN:center\"><a \nhref=\"http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=339&F=H\"><img\ntitle=\"Aerothermie\" class=\"rimg\" alt=\"Plus d'infos />>>\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_6.png\"\nborder=\"none\"> </a></div></div></td></tr></tbody></table>\n<table class=\"rtable\" style=\"WIDTH:33%\" align=\"left\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody><tr>\n<td\nstyle=\"PADDING-BOTTOM:5px;PADDING-TOP:5px;PADDING-LEFT:5px;PADDING-RIGHT:5px\">\n<div style=\"mso-table-lspace:0;mso-table-rspace:0\"><img class=\"rimg\"\nstyle=\"BORDER-TOP:#dbdbdb 0px solid;BORDER-RIGHT:#dbdbdb 0px\nsolid;BORDER-BOTTOM:#dbdbdb 0px solid;MARGIN:2px 2px\n15px;BORDER-LEFT:#dbdbdb 0px solid;BACKGROUND-COLOR:transparent\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_7.JPG\"\nheight=\"160/\" width=\"160\"/> \n<p style=\"FONT-SIZE:16px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#22334f;MARGIN-TOP:0px;LINE-HEIGHT:145%;BACKGROUND-COLOR:transparent\"\nalign=\"center\"><strong>Piscine coque</strong></p>\n<p style=\"FONT-SIZE:12px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#8d8d8d;MARGIN-TOP:0px;LINE-HEIGHT:135%;BACKGROUND-COLOR:transparent\"\nalign=\"center\">Appelée également piscine monocoque, la piscine coque\npolyester est étanche, relativement solide et durable. <br/>Un prix\nd'achat plus modique et une installation plus simple en font le type de\npiscine le plus vendu...</p>\n<div style=\"TEXT-ALIGN:center\"><a \nhref=\"http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=341&F=H\"><img\ntitle=\"Chauffage solaire\" class=\"rimg\" alt=\"Plus d'infos />>>\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_8.png\"\nborder=\"none\"> </a></div></div></td></tr></tbody></table>\n<table class=\"rtable\" style=\"WIDTH:33%\" align=\"right\" border=\"0\"\ncellpadding=\"0\" cellspacing=\"0\">\n<tbody><tr>\n<td\nstyle=\"PADDING-BOTTOM:5px;PADDING-TOP:5px;PADDING-LEFT:5px;PADDING-RIGHT:5px\"\nalign=\"center\">\n<div style=\"mso-table-lspace:0;mso-table-rspace:0\"><img class=\"rimg\"\nstyle=\"BORDER-TOP:#dbdbdb 0px solid;BORDER-RIGHT:#dbdbdb 0px\nsolid;BORDER-BOTTOM:#dbdbdb 0px solid;MARGIN:2px 2px\n15px;BORDER-LEFT:#dbdbdb 0px solid;BACKGROUND-COLOR:transparent\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_9.JPG\"\nheight=\"160/\" width=\"160\"/> \n<p style=\"FONT-SIZE:16px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#22334f;MARGIN-TOP:0px;LINE-HEIGHT:145%;BACKGROUND-COLOR:transparent\"\nalign=\"center\"><strong>Piscine hors-sol en kit</strong></p>\n<p style=\"FONT-SIZE:12px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#8d8d8d;MARGIN-TOP:0px;LINE-HEIGHT:135%;BACKGROUND-COLOR:transparent\"\nalign=\"center\">La piscine hors sol en kit : une solution pratique et pas\nchère pour installer rapidement une piscine dans votre jardin. Très\nesthétique en bois, elle s’adapte à tous les jardins. En acier, elle se\nmonte facilement et est très solide. Elle se démonte facilement pour un\nrangement hivernal...</p>\n<div style=\"TEXT-ALIGN:center\"><a \nhref=\"http://ad.astro-tarot-akmas.fr/link.php?M=205054&N=177&L=340&F=H\"><img\ntitle=\"Climatisation\" class=\"rimg\" alt=\"Plus d'infos />>>\"\nsrc=\"http://www.devis-tous-travaux-maison.fr/img/ml/002/Image_10.png\"\nborder=\"none\">\n</a></div></div></td></tr></tbody></table></td></tr></tbody></table></td></tr>\n<tr>\n<td style=\"BORDER-TOP:#dbdbdb 1px hidden;BORDER-RIGHT:#dbdbdb 1px\nhidden;BORDER-BOTTOM:#dbdbdb 1px\nhidden;PADDING-BOTTOM:1px;PADDING-TOP:1px;PADDING-LEFT:0px;BORDER-LEFT:#dbdbdb\n1px hidden;PADDING-RIGHT:0px;BACKGROUND-COLOR:transparent\">\n<table class=\"rtable\" style=\"WIDTH:100%\" cellpadding=\"0\" cellspacing=\"0\">\n<tbody><tr style=\"HEIGHT:10px\">\n<td style=\"BORDER-TOP:#dbdbdb 0px hidden;BORDER-RIGHT:#dbdbdb 0px\nhidden;WIDTH:100%;VERTICAL-ALIGN:top;BORDER-BOTTOM:#dbdbdb 0px\nhidden;PADDING-BOTTOM:1px;TEXT-ALIGN:center;PADDING-TOP:1px;PADDING-LEFT:15px;BORDER-LEFT:#dbdbdb\n0px hidden;PADDING-RIGHT:15px;BACKGROUND-COLOR:transparent\">\n<p style=\"FONT-SIZE:10px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#7c7c7c;MARGIN-TOP:0px;LINE-HEIGHT:normal;BACKGROUND-COLOR:transparent\"\nalign=\"left\"></p>\n<p style=\"FONT-SIZE:10px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#7c7c7c;MARGIN-TOP:0px;LINE-HEIGHT:normal;BACKGROUND-COLOR:transparent\"\nalign=\"left\">Si vous souhaitez vous désinscrire, <a \nhref=\"http://ad.astro-tarot-akmas.fr/unsubscribe.php?M=205054&C=947d60a7c4de022a87f7fdf113f33f3d&L=2&N=177\">cliquez\nici</a></p>\n<p style=\"FONT-SIZE:10px;FONT-FAMILY:Arial, Helvetica,\nsans-serif;COLOR:#7c7c7c;MARGIN-TOP:0px;LINE-HEIGHT:normal;BACKGROUND-COLOR:transparent\"\nalign=\"left\">©2014 Devis tous travaux\nmaison.</p></td></tr></tbody></table></td></tr></tbody></table></td></tr>\n<tr>\n<td\nstyle=\"FONT-SIZE:0px;HEIGHT:8px;LINE-HEIGHT:0\">&nbsp;</td></tr></tbody></table>\n\n      <br/><br>\n<table bgcolor=\"\" cellpadding=\"0\" width=\"100%\" border=\"0\">\n\t<tr align=\"center\">\n\t\t<td>\n\t\t<table bgcolor=\"white\" width=\"450\" border=\"0\" cellpadding=\"5\">\n\t\t\t<tr>\n\t\t\t\t<td>\n\t\t\t\t\t \n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t</table>\n\t\t</td>\n\t</tr>\n</table>\n<img\nsrc=\"http://ad.astro-tarot-akmas.fr/open.php?M=205054&L=2&N=177&F=H\"></body></html>\n\n\n--b1_181b897b648237c7016a222a2f7eb437--\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_529.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1afUBv-0000Vj-9B\n\tfor mharc-dev@caliopen.local; Mon, 14 Mar 2016 11:13:55 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39305)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1afUBn-0000N8-3l\n\tfor dev@caliopen.local; Mon, 14 Mar 2016 11:13:53 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <acm@muc.de>) id 1afUBi-0002ui-VU\n\tfor dev@caliopen.local; Mon, 14 Mar 2016 11:13:47 -0400\nReceived: from mail.muc.de ([193.149.48.3]:34515)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1afUBi-0002uV-Lz\n\tfor dev@caliopen.local; Mon, 14 Mar 2016 11:13:42 -0400\nReceived: (qmail 18794 invoked by uid 3782); 14 Mar 2016 15:13:40 -0000\nReceived: from acm.muc.de (p579E8DE5.dip0.t-ipconnect.de [87.158.141.229]) by\n\tcolin.muc.de (tmda-ofmipd) with ESMTP;\n\tMon, 14 Mar 2016 16:13:39 +0100\nReceived: (qmail 3723 invoked by uid 1000); 14 Mar 2016 15:16:21 -0000\nDate: Mon, 14 Mar 2016 15:16:21 +0000\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <20160314151621.GF1894@acm.fritz.box>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\nMIME-Version: 1.0\nContent-Type: text/plain; charset=us-ascii\nContent-Disposition: inline\nIn-Reply-To: <2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\nUser-Agent: Mutt/1.5.24 (2015-08-30)\nX-Delivery-Agent: TMDA/1.1.12 (Macallan)\nFrom: Alan Mackenzie <acm@muc.de>\nX-Primary-Address: acm@muc.de\nX-detected-operating-system: by eggs.gnu.org: FreeBSD 9.x\nX-Received-From: 193.149.48.3\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 14 Mar 2016 15:13:53 -0000\nContent-Length: 1406\nLines: 33\n\nHello, Dmitry.\n\nOn Sat, Mar 12, 2016 at 12:38:49AM +0200, Dmitry Gutov wrote:\n> On 03/12/2016 12:15 AM, Alan Mackenzie wrote:\n\n> >> Except the multiple-major-mode case, which we'd ideally try to\n> >> accommodate, too.\n\n> > How does this code handle the changeover of syntax tables at a mode\n> > boundary?\n\n> The \"inner\" regions start with an \"empty\", top-level state. This is \n> actually fine, because these are usually small enough not to benefit \n> from the syntax-ppss cache too much (and syntax-ppss-last still helps).\n\n> The parts of the outer region following a subregion with different \n> syntax table... rely on a few hacks, and a manual application of a \n> `syntax-table' property when necessary. We need a better solution there, \n> but it's probably out of scope for this discussion.\n\nHow about an extension to the parse-partial-sexp (etc.) code?  For\nexample, a feature I would call an \"island\", where a character could be\nmarked with the \"island start\" syntax-table property, and another\ncharacter lower down could be marked with the \"island end\" character.\nparse-partial-sexp, on encountering an island start syntax would somehow\nstack the current parse state and start a new one with a different syntax\ntable.  The parse state, instead of consisting of the 10 elements\ncurrently, would in general have 10n elements, where n is the depth of\nnesting.\n\n-- \nAlan Mackenzie (Nuremberg, Germany).\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463567.5266_531.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1afYkn-0000Nf-4p\n\tfor mharc-dev@caliopen.local; Mon, 14 Mar 2016 16:06:13 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42503)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1afYkk-0000Li-I5\n\tfor dev@caliopen.local; Mon, 14 Mar 2016 16:06:11 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1afYkg-0007Km-Gy\n\tfor dev@caliopen.local; Mon, 14 Mar 2016 16:06:10 -0400\nReceived: from mail-wm0-x232.google.com ([2a00:1450:400c:c09::232]:35630)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1afYkg-0007KU-4D\n\tfor dev@caliopen.local; Mon, 14 Mar 2016 16:06:06 -0400\nReceived: by mail-wm0-x232.google.com with SMTP id l68so124080552wml.0\n\tfor <dev@caliopen.local>; Mon, 14 Mar 2016 13:06:06 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=Mc3EiMqmXxGAyw0ZUEbgSLroERzirPpSUcEJ5JEY3C8=;\n\tb=LCc6OvbSShX7WWAwknnJR2aAoaj6JJrMsSf3jsKKvpGO/Axr8OtGSnrmiBVyw1bIZh\n\t7H2fo2nFChaOhPoN/m4ZPUJEOMGjfZcrrh3iWrO+Ob6Woo1VNz+6W2LRgAj/XORDBWW6\n\t3+KNR05/HxFNnGJ0Nd2N4vwJIcZAwxPqj5w6wv9QToVG33BfcArlMDiJLf3l3rlgTBpM\n\t8YCZtHOAs2r4uAIxGFfMTh9VgNPGt+ONnjR2PLtolUs3qJ/x7WPG3VnLeIvG6HW1P4qf\n\tC2ZtIbGigh0qzjgSCvqyHlPIGEjI0mGKu41yuj2JAWIFT7db5arLaNskyNVC/qA/3YJf\n\thPCw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=Mc3EiMqmXxGAyw0ZUEbgSLroERzirPpSUcEJ5JEY3C8=;\n\tb=RG6Qa+H/BqrD46BVjZpqkEJcZBOrxR2apBgbTMIDcxAeT8nCB8L6IZgx5IIy6+Z6fA\n\t6i30/ZmdY8qmDNQM4H5zA7K9KJF3lssGi0tBFFtfHLqJHZCCsPjuc2dDP4/HqYrgQ0XF\n\tJQKChCsiMw2hMfVJOcd7kkMae84XPXV3hkDPmXGxQhcxFWFo6vU9IAhMUo/BPwa529QA\n\t4mYq9lGXWUAynb7M0hFFhWf0ONRdunxM/5QJ2CN3UcuIIZHksrtitOiSJCJn9Z0upwE2\n\tlbnvnF2sLBWE/CvGkPVWYeJ62I3Vg1B79dP3ZAScCHJoI3qr59Z9Or8FoshnJiuAWC/1\n\tM7MQ==\nX-Gm-Message-State: AD7BkJJqUXDpZP7xOQ7+edKDdhX2fXfI+hk6Cv0f371YPZeyzYkzdp+3R66vjGTrkz1xLQ==\nX-Received: by 10.194.201.166 with SMTP id kb6mr26643681wjc.125.1457985965409; \n\tMon, 14 Mar 2016 13:06:05 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\tka7sm23826819wjb.8.2016.03.14.13.06.03\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 14 Mar 2016 13:06:04 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Alan Mackenzie <acm@muc.de>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\nDate: Mon, 14 Mar 2016 22:06:02 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <20160314151621.GF1894@acm.fritz.box>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::232\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 14 Mar 2016 20:06:11 -0000\nContent-Length: 1730\nLines: 36\n\nHi Alan,\n\nOn 03/14/2016 05:16 PM, Alan Mackenzie wrote:\n\n> How about an extension to the parse-partial-sexp (etc.) code?  For\n> example, a feature I would call an \"island\", where a character could be\n> marked with the \"island start\" syntax-table property, and another\n> character lower down could be marked with the \"island end\" character.\n\nSomething like that might help, although I hesitate asking for that \nchange because it's a relatively big one, and it would still solve only \none multiple-mode-related issue.\n\nWhat it would help with, is fool the \"outer\" major mode into ignoring \nthe preceding submode regions, in the return value of syntax-ppss. But \nwe could have pretty much that already by advising syntax-ppss. That \nleaves out parse-partial-sexp, but it's not used that often directly in \nmajor mode code (though sgml-mode uses it).\n\n> parse-partial-sexp, on encountering an island start syntax would somehow\n> stack the current parse state and start a new one with a different syntax\n> table.  The parse state, instead of consisting of the 10 elements\n> currently, would in general have 10n elements, where n is the depth of\n> nesting.\n\nTo be able to parse across different regions, it would need to know the \nsyntax table for each one (using the syntax-table text property?), as \nwell as to be able to apply the appropriate syntax-propertize-function \nin each region. The latter is handled by mmm-mode, though, in a \nseemingly adequate fashion (it installs a composite function that knows \nhow to dispatch to mode-specific ones).\n\nMaybe it's worth a try. Though I don't know how Stefan uses narrowing in \nsm-c-mode, and whether this proposal is appropriate to replace narrowing \nin syntax-ppss for this use case.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_533.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahPij-0003bl-Ln\n\tfor mharc-dev@caliopen.local; Sat, 19 Mar 2016 18:51:45 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50927)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahPih-0003as-Nt\n\tfor dev@caliopen.local; Sat, 19 Mar 2016 18:51:44 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahPic-0005aK-P3\n\tfor dev@caliopen.local; Sat, 19 Mar 2016 18:51:43 -0400\nReceived: from mail-wm0-x22a.google.com ([2a00:1450:400c:c09::22a]:35396)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahPic-0005ZQ-Hl\n\tfor dev@caliopen.local; Sat, 19 Mar 2016 18:51:38 -0400\nReceived: by mail-wm0-x22a.google.com with SMTP id l68so68191570wml.0\n\tfor <dev@caliopen.local>; Sat, 19 Mar 2016 15:51:38 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=GXdEo0ixjobqh0eLou8wvqvFYkC1P6qDaDk9yvPX12w=;\n\tb=xhc6aJBTKCsOpF85NDtWwPubbAcEd8AJz+HybQk/rNeu0ChGgx0740yPGz6pcKxE//\n\tf3eCdjEu87gfl/3OWTE9vUP48QNqXHGkDUsM/xcOir9rBsn7pRrx3vWcPFjOM4vHnO1i\n\teJE8V7+sTWh1DUqDwVAHoMKHkGDLqFmUceFL2XOdFgusHyUuGgv2KvLvlu5vI7cGb9I7\n\tydKUQOSrQwjXb3PzE1eW+Jr++WKmRPW8zxurwCFEaFAhbHJlAI8qZCz4sCR4oLwkgXsp\n\thAQq2f9Q4gaHWPbPoqfki8rC1AN0HD0T/AmGQW+WJKVFV0+inZEuoKDL1jINyOwa28kg\n\t6dCA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=GXdEo0ixjobqh0eLou8wvqvFYkC1P6qDaDk9yvPX12w=;\n\tb=mejrzRk+XWlkmOi3FzQUDR0AoCjnAFylAWXTNzBWAluDy51lU/Ta3YhLZ31XQMtT1U\n\tyOYgG++4z76yLfOuowNlUkOYvqDOLqL7bNR+IbU1dBoAw0omMzDByg2i7B0uAeaqYyN5\n\tZGY5tynCMnAUi4ty+HLuIACDEeEwvG/KAc0veFbQ7TSQvBqYbw9ZFtzfo8SdWHJ2OcuJ\n\tLm2hfs5434VG/asVtJkn33pOtIySyw5vjq8mg4gowLCNoW0i58NmpfQ1M9i8+yMHYqI5\n\tHZxlvD+Epqu3bxSeI+bd1b9HHFEUiIGhMs15xJmMEX00klh+lyqGPhRGfr7BNpm72/GX\n\treUQ==\nX-Gm-Message-State: AD7BkJJVcxLGnK29bOVsILXJqO2Fqpd+seBO/1NUuG0PtLH51yfejEDPcQ32BsGc0Ja2ew==\nX-Received: by 10.28.217.146 with SMTP id q140mr5666571wmg.85.1458427897539;\n\tSat, 19 Mar 2016 15:51:37 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tcb2sm18376812wjc.16.2016.03.19.15.51.36\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSat, 19 Mar 2016 15:51:36 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\nDate: Sat, 19 Mar 2016 23:51:35 +0100\nIn-Reply-To: <e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru> (Dmitry Gutov's\n\tmessage of \"Mon, 14 Mar 2016 22:06:02 +0200\")\nMessage-ID: <874mc2dqtk.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22a\nCc: Alan Mackenzie <acm@muc.de>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 19 Mar 2016 22:51:44 -0000\nContent-Length: 1586\nLines: 39\n\n\n>> On Mon, Mar 14 2016 22:06, Dmitry Gutov wrote:\n\n> Hi Alan,\n\n> On 03/14/2016 05:16 PM, Alan Mackenzie wrote:\n\n>> How about an extension to the parse-partial-sexp (etc.) code?  For\n>> example, a feature I would call an \"island\", where a character could be\n>> marked with the \"island start\" syntax-table property, and another\n>> character lower down could be marked with the \"island end\" character.\n\n> Something like that might help, although I hesitate asking for that change\n> because it's a relatively big one, and it would still solve only one\n> multiple-mode-related issue.\n\n[...]\n\n> But we could have pretty much that already by advising syntax-ppss. That\n> leaves out parse-partial-sexp, but it's not used that often directly in major\n> mode code (though sgml-mode uses it).\n\nYou can simulate islands by marking inner spans as comments with comment classes\n(11 and 12). I used those in polymode in the past, but not anymore. It's not\nthat useful. Most of the parsing that modes do is regex based. So if a mode\nauthor decides to regexpf for a wiki link on a full buffer after widening it,\nislands won't help.\n\nAs Dmitry mentioned, there is little multi-mode cannot do with advising\nsyntax-ppss. The issue is still that parse-partial-sexp blows with narrowed code\nand you cannot advice it.\n\nIMO the most useful direction for multi-modes is to add a hard narrowing that\nStephen mentioned in the other thread. `syntax-ppss-dont-widen` goes in that\ndirection, but it doesn't address the issue of distinguishing between user\nnarrowing and \"hard narrowing\" in multi modes.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_535.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahSxn-0007PU-CV\n\tfor mharc-dev@caliopen.local; Sat, 19 Mar 2016 22:19:31 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49015)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahSxl-0007PN-Ga\n\tfor dev@caliopen.local; Sat, 19 Mar 2016 22:19:30 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahSxg-0008UO-Fm\n\tfor dev@caliopen.local; Sat, 19 Mar 2016 22:19:29 -0400\nReceived: from mail-wm0-x22b.google.com ([2a00:1450:400c:c09::22b]:35205)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahSxg-0008UJ-8G\n\tfor dev@caliopen.local; Sat, 19 Mar 2016 22:19:24 -0400\nReceived: by mail-wm0-x22b.google.com with SMTP id l68so70732951wml.0\n\tfor <dev@caliopen.local>; Sat, 19 Mar 2016 19:19:24 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=lztDaGx4wqqVxWSxtsl5jQw6yKDvw/1rjFhVZri5fUo=;\n\tb=rOZG0BNfaxRdyfgMI8neGGdVMEsFKSxHK+gWZx5vClPNJY/nkvwZfE7KuFD6SWHQwx\n\tGZRx1sIuJZ0xPGoXDBYqRVX65Lni1oXdKY/LjRP0rF9r1DX1Gb0nnnMH2WdXuIoulZxp\n\tNBYr1D2EI+JBeBZQT7Yvj6pvWAFxZicLBuILott4fV8vD/A1mn8KdziWCIm8NqY4DRzl\n\tL640JLwQfKx6ah0OA3p2ydIjkm85kV55StC8I6RfOtbkFudFcxEGw+DBxMvx9lZJlIJ7\n\tZdA0UFIda1N1zjBNgzjFV33bhHnpIIYyPTYNm2GSXamdpBPNY2L3RCRZQ2UNyonu4BAC\n\t66qA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=lztDaGx4wqqVxWSxtsl5jQw6yKDvw/1rjFhVZri5fUo=;\n\tb=lQYHPiN/I3qmqcnslJ6ph5nbw+8JsBdJv8f1NJfhJxP3Vej1w/R00NBn+F3TMK5MaO\n\tOUJPhtej/UrDI2pOSm98ZJgxqq10Oi9iSOQRNh3hSX5tn6qcTgs1dNPgyMaaaOj0UKF/\n\tDq9YCHxs+r7EY3c1PZMiIOtJ5Ud/HS/MwU8OGmnDd+Nkywd7uT9t5AgUrwrtvqhlHbDp\n\tT3eV5kIzOXW2OILZnNwEm3TjIoU7KvuQxlgsHZV5FQBZQDXO1fFf2Mnouuk7GDi7fYKZ\n\tne+1kc+eo7j+/6nkPAsCkNyZ5cfnc81g3M25NriwJS7rK9jz7ga7YlM+BWjhGioLwGvU\n\tyd9w==\nX-Gm-Message-State: AD7BkJIeg7ye2OskTmcPtkIUivIMIhQyZ3PcrdOiifc71OsIpydZTGPYRDrPXe4hReenxA==\nX-Received: by 10.194.22.97 with SMTP id c1mr23084174wjf.19.1458440363413;\n\tSat, 19 Mar 2016 19:19:23 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\t8sm5912818wmk.13.2016.03.19.19.19.21\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tSat, 19 Mar 2016 19:19:22 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Vitalie Spinu <spinuvit@gmail.com>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\nDate: Sun, 20 Mar 2016 04:19:21 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <874mc2dqtk.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22b\nCc: Alan Mackenzie <acm@muc.de>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 20 Mar 2016 02:19:30 -0000\nContent-Length: 1670\nLines: 42\n\nOn 03/20/2016 12:51 AM, Vitalie Spinu wrote:\n\n> You can simulate islands by marking inner spans as comments with comment classes\n> (11 and 12).\n\nOoh, that's a solid idea. Should be more generic that my \"propertize <> \nas punctuation\" approach.\n\n> I used those in polymode in the past, but not anymore. It's not\n> that useful. Most of the parsing that modes do is regex based.\n\nI'd say it's still useful. Without the above, I've had indentation \nproblems with sgml-mode.\n\nA good mode would use syntax-ppss to check that point is not inside a \nstring or comment. Maybe that's not often done in font-lock, but it's at \nleast common in syntax-propertize and indentation functions.\n\nExample: sgml-lexical-context. It performs a search at first, but in the \nend uses parse-partial-sexp, and returns a value based on that status.\n\n> So if a mode\n> author decides to regexpf for a wiki link on a full buffer after widening it,\n> islands won't help.\n\nWhere does widening happens in this case? First, we have \nfont-lock-dont-widen.\n\nFor indentation, we've introduced prog-indentation-context recently. And \nindentation functions in programming modes are supposed to call \nprog-widen instead of widen now.\n\nsyntax-propertize-function's aren't supposed to call widen at all, I think.\n\n> IMO the most useful direction for multi-modes is to add a hard narrowing that\n> Stephen mentioned in the other thread. `syntax-ppss-dont-widen` goes in that\n> direction, but it doesn't address the issue of distinguishing between user\n> narrowing and \"hard narrowing\" in multi modes.\n\nsyntax-ppss-dont-widen and prog-indentation-context will be the \nindicators of the \"hard narrowing\", I guess.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_537.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahcGL-0005WD-O1\n\tfor mharc-dev@caliopen.local; Sun, 20 Mar 2016 08:15:17 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42833)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahcGF-0005VP-WE\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 08:15:16 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahcGA-0003m3-HV\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 08:15:11 -0400\nReceived: from mail-wm0-x232.google.com ([2a00:1450:400c:c09::232]:33456)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahcGA-0003kd-7U\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 08:15:06 -0400\nReceived: by mail-wm0-x232.google.com with SMTP id l68so121423427wml.0\n\tfor <dev@caliopen.local>; Sun, 20 Mar 2016 05:15:06 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=ZhS6JNx7vE6enrKKAIU4R2pz51t4IK8XXBLHe/5ukIU=;\n\tb=vRSJSnzVZ6QkzgtDqfBGyspY7dQOXoTswjYP9GczCtdgpzFGKgf6trxnbDjJ38OksN\n\tWMljniBaezJ35FwWBuQt4RAp/VdgpVCW8RIR/jsuN3OZpqiC3DwyKSHl0Tj0MxrpnY34\n\tt4qrTE8HmxZVuee1ej6X7XBBa7Hb6Z37AZyzXC/UULfZ5k/SBhX9HYkzpy1K/F0Sxdaj\n\tpJRrFLJwkh/kg99h3GyDmXM2/yg4Cy/cls1tSk0r9VVjbGuSu2GElkr8UsB/vGkY7kWx\n\tz3ytk367hNOZAu3jdXUI7A1ST+BDqn8/fQIYL1irGEFfP7RLXCzLosXAnEoOWY03LiiX\n\thnMQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=ZhS6JNx7vE6enrKKAIU4R2pz51t4IK8XXBLHe/5ukIU=;\n\tb=kp8VKgqscOmGBMVgv8SOca93IsQ9vAaknuJGsOQTCTQglmPt7AZuv/GltgSPBPD4Wq\n\t4wHWL+a7q/v2KFtYWwqdNlZVypZ81FXjMpuUHT34zxqQPUJKQFg58GRNzzLOHomNFbDZ\n\tdL/MjcqLyhFFyV721sjUbx5qBb/gI6HA29CzbnnV6xaOoIOsbIMe60FF2d1waXKnd+m0\n\tlQvN8KdzVnBs1Bz2NxChfcg7W6i+nodnd9PrXp17kHtuP8ogUkHa/WOiu2MttXDU5Mgg\n\tr1l9lAuC00yDMFfvXZPVCCbdKIypKLqI1+2XyHqK5kAzNTgVcNiAZ58aewrhI2bubLxc\n\t1+1w==\nX-Gm-Message-State: AD7BkJJqPVtWLInNoZBHP2M48CyjGMGiakXFeN44KylyWSAJYOhxm671MjPT+AlrFuEYPQ==\nX-Received: by 10.28.217.146 with SMTP id q140mr8077889wmg.85.1458476105458;\n\tSun, 20 Mar 2016 05:15:05 -0700 (PDT)\nReceived: from localhost ([143.176.214.220])\n\tby smtp.gmail.com with ESMTPSA id a1sm4834638wje.43.2016.03.20.05.15.03\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 20 Mar 2016 05:15:03 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\nDate: Sun, 20 Mar 2016 13:15:03 +0100\nIn-Reply-To: <fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru> (Dmitry Gutov's\n\tmessage of \"Sun, 20 Mar 2016 04:19:21 +0200\")\nMessage-ID: <87egb5cpmg.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::232\nCc: Alan Mackenzie <acm@muc.de>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 20 Mar 2016 12:15:16 -0000\nContent-Length: 3024\nLines: 64\n\n\n\n>> On Sun, Mar 20 2016 04:19, Dmitry Gutov wrote:\n\n>> So if a mode author decides to regexpf for a wiki link on a full buffer after\n>> widening it, islands won't help.\n\n> Where does widening happens in this case? First, we have font-lock-dont-widen.\n\nWell, font-lock-dont-widen is not respected even in c-mode. Look at\nc-before-context-fl-expand-region and semi-safe-place which are called directly\nor indirectly from c-font-lock-fontify-region.\n\n> For indentation, we've introduced prog-indentation-context recently. And\n> indentation functions in programming modes are supposed to call prog-widen\n> instead of widen now.\n\nI was not aware of that. Not sure if it is a step in right direction though.\n\n`prog-indentation-context` looks fine to me but multi-modes already have their\nown wrappers for indentation which do just that according to their own semantics\nof modes/submodes/chunks/headers etc.\n\nThe primary intent of `prog-indentation-context` is to be used in\n`prog-widen`. This part seems like a major complication. All mode authors now\nhave to understand what is prog-widen, prog-first-column and\nprog-indentation-context. Why to burden prog-mode authors with notions that\nmulti-mode engines can take care themselves?\n\nIt is also not clear to me why should prog-widen be used in indentation context\nonly? It makes perfect sense for this function to be used in font-locking and\nsyntax-propertize-function as well.\n\nIt's essentially a half-backed implementation of \"hard widening\" discussed\nearlier. Why not impose the widening restriction directly in `widen` then? Maybe\nbring widen to elisp and rename C widen into widen-internal. Then add generic\n`prog-hard-widen-limits` which would be checked along prog-indentation-context\nlimits.\n\nMulti-mode engines can then impose those hard limits whenever they need to and\nadjust indentation accordingly. It's not that hard in my experience. Polymode\nhas a few lines to wrap indentation and it works reasonably well in pretty much\nall contexts I have tried. All other problems can be solved with hard narrowing.\n\n  https://github.com/vspinu/polymode/blob/master/polymode-methods.el#L715-L809\n\nUnless I miss something essential it's really not worth imposing such\ncomplexities on mode authors. Judging from the python.el, which is the only mode\nusing prog-first-column so far, it's not a trivial task. Each mode author will\nbasically have to implement indentation logic that mmm-mode or polymode already\nimplement. And even then, multi-mode engines will probably need to overwrite\nthat because the semantics of submode spans is either emacs-mode or\nmulti-mode-engine specific.\n\n> syntax-propertize-function's aren't supposed to call widen at all, I think.\n\nThis should probably be in the docs then. Mode authors can decide to do loads of\nwork in there. One instance is `markdown-mode` which caches all font-lock\nproperties in syntax-propertize-function. While markdown-mode is clean and\ndoesn't use widen anywhere, that might not be the case for other modes.\n\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_539.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahfkC-0000cP-Pg\n\tfor mharc-dev@caliopen.local; Sun, 20 Mar 2016 11:58:20 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57716)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahfkA-0000cE-61\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 11:58:19 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahfk6-0003IN-TL\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 11:58:18 -0400\nReceived: from mail-wm0-x231.google.com ([2a00:1450:400c:c09::231]:36909)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahfk6-0003IB-GA\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 11:58:14 -0400\nReceived: by mail-wm0-x231.google.com with SMTP id p65so95119100wmp.0\n\tfor <dev@caliopen.local>; Sun, 20 Mar 2016 08:58:14 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=6ge2OzSbsZAVnPMzFbw9q6chZQBAjLhCk4xCrr7wm10=;\n\tb=FaL527ltjWwXzoD3cgOlScrZC1N7xe/E21b0i/IdWhllwyjP2hJO5F11SvOTYTEr8/\n\t8CNncNx2M191sQfhK3FuioefeJHlax9DQ8Dk5axTBlDLTlCOEUqXEqP1BkiBncEaNQeh\n\t2WEkoVWSxqK1DznkkNNeQzDQ/l1pmu8gsmAJ0H28x72/rpq66T1UFrCeeOOZIneRF8Hn\n\t3nwhGeKag8J/F67Ij4tHFGVA+aOPYrniy2ThIX3qXcNFkTVUwVQGK4l28pex4TqvwE4S\n\t3yI5b3mwpjF6TVhrbgqvsvpsgCyjgx/RJ2NtvuD7QTUQtAnjTm1nGSCbE5vZcU2S8g8V\n\t+JaQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=6ge2OzSbsZAVnPMzFbw9q6chZQBAjLhCk4xCrr7wm10=;\n\tb=g+LJq/WUkdwYvPjAWZjIOgDAz/A+G1J/iB3J9xVj4T6p37962zagr/IoHd5OoBgm/T\n\tgqr2Hwah8FAZOAZv1FgCtcB3T+06HO1+DsRFXTAAuqvX46w9dq7bv1h8Jg7/lllesi2w\n\t3WQlGjjiuOlPN7hGJP7bapd/TBXpHTLF89IcanZRHgw6mJV1jKr1AOPKs+B/iriFKX4I\n\tUVadXoFwLD9zZOUSbl4nYbYI7zjXsid9Upn7oLIVbSWnzE0ZIgW7vYlOGrVH3brJBlvH\n\tBmbdv1us0IC/jmKFyc5QP7u/eeyrzWNSuphO858+aGrrJnTAL5Wc38JH8hGJ8wOtjoY6\n\t0JwA==\nX-Gm-Message-State: AD7BkJLRi5Lso2AC+LC6HPRIZk7qfi3eIHicuW3BylIotJMo97tfR6QM5afl38Ydd2rICA==\nX-Received: by 10.28.47.21 with SMTP id v21mr9658094wmv.7.1458489493524;\n\tSun, 20 Mar 2016 08:58:13 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\tgb9sm21214571wjb.26.2016.03.20.08.58.11\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tSun, 20 Mar 2016 08:58:12 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Vitalie Spinu <spinuvit@gmail.com>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\nDate: Sun, 20 Mar 2016 17:58:11 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <87egb5cpmg.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::231\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 20 Mar 2016 15:58:19 -0000\nContent-Length: 4617\nLines: 105\n\nOn 03/20/2016 02:15 PM, Vitalie Spinu wrote:\n\n> Well, font-lock-dont-widen is not respected even in c-mode. Look at\n> c-before-context-fl-expand-region and semi-safe-place which are called directly\n> or indirectly from c-font-lock-fontify-region.\n\nWell, yes. c-mode is special, as usual. That should be workable if CC \nMode starts using prog-widen instead of widen, though.\n\n>> For indentation, we've introduced prog-indentation-context recently. And\n>> indentation functions in programming modes are supposed to call prog-widen\n>> instead of widen now.\n>\n> I was not aware of that. Not sure if it is a step in right direction though.\n\nI'm not 100% happy with it either.\n\n> `prog-indentation-context` looks fine to me but multi-modes already have their\n> own wrappers for indentation which do just that according to their own semantics\n> of modes/submodes/chunks/headers etc.\n\nToo bad you were not around when this addition was discussed.\n\n> The primary intent of `prog-indentation-context` is to be used in\n> `prog-widen`. This part seems like a major complication. All mode authors now\n> have to understand what is prog-widen, prog-first-column and\n> prog-indentation-context. Why to burden prog-mode authors with notions that\n> multi-mode engines can take care themselves?\n\nIIRC, using first-column is fairly justified, the outer mode can't add \nextra indentation to the submode is a reliable, sane way (though I've \nalso been hacking around that quite successfully). Here's the full \ndiscussion:\n\nhttp://lists.gnu.org/archive/html/emacs-devel/2015-01/msg00431.html\nhttp://lists.gnu.org/archive/html/emacs-devel/2015-02/msg00290.html\n\nwith my messages further down.\n\n> It is also not clear to me why should prog-widen be used in indentation context\n> only? It makes perfect sense for this function to be used in font-locking and\n> syntax-propertize-function as well.\n\nIndeed. In js-mode's case, the offending code is called from \nfont-lock-keywords, for example.\n\n> It's essentially a half-backed implementation of \"hard widening\" discussed\n> earlier. Why not impose the widening restriction directly in `widen` then? Maybe\n> bring widen to elisp and rename C widen into widen-internal. Then add generic\n> `prog-hard-widen-limits` which would be checked along prog-indentation-context\n> limits.\n\nRight! At the very least, I we should extract the second element of \nprog-indentation-context into a separate variable, and make prog-widen \nmore prominent.\n\nBut a proper implementation of hard-widen would be even better in my \nbook. Although someone would need to comb through all low-level \nfunctions, at least, and decide which of them need to call \nwiden-internal, and which will be fine with just widen.\n\nAre you interested in working on a patch? Also Cc'ing Stefan.\n\nLooking back on it, it seems prog-indentation-context was merged too \nearly: it only has one usage so far, so it's not clear if the approach \nis generally viable.\n\nChristoph sort of promised to add support in CC Mode, but then \ndisappeared. Which is not so surprising, that stuff is difficult.\n\n> Unless I miss something essential it's really not worth imposing such\n> complexities on mode authors. Judging from the python.el, which is the only mode\n> using prog-first-column so far, it's not a trivial task. Each mode author will\n> basically have to implement indentation logic that mmm-mode or polymode already\n> implement. And even then, multi-mode engines will probably need to overwrite\n> that because the semantics of submode spans is either emacs-mode or\n> multi-mode-engine specific.\n\nThis is not too different what I was saying, I think. That discussion is \nfairly long, though, and it veered off to the side.\n\nAFAICT, though, the ultimate justification for having first-column is \nPython's indentation cycling behavior: \nhttp://lists.gnu.org/archive/html/emacs-devel/2015-02/msg01096.html\n\nWhich is not that convincing, but makes some things clearner anyway.\n\nBut the last element, previous-chunks, is still not used anywhere in \nEmacs. I think including it turned out to be a mistake, or at least \npremature.\n\n>> syntax-propertize-function's aren't supposed to call widen at all, I think.\n>\n> This should probably be in the docs then.\n\nProbably.\n\n> Mode authors can decide to do loads of\n> work in there. One instance is `markdown-mode` which caches all font-lock\n> properties in syntax-propertize-function. While markdown-mode is clean and\n> doesn't use widen anywhere, that might not be the case for other modes.\n\nruby-syntax-propertize also does some involved parsing, but as long as \nthere's no `widen' there, we should be fine.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_541.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahoHi-0000Ms-Se\n\tfor mharc-dev@caliopen.local; Sun, 20 Mar 2016 21:05:31 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47916)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahoHe-0000MW-Ud\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 21:05:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahoHb-0007sw-MP\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 21:05:26 -0400\nReceived: from mail-wm0-x22a.google.com ([2a00:1450:400c:c09::22a]:33815)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahoHb-0007so-Aw\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 21:05:23 -0400\nReceived: by mail-wm0-x22a.google.com with SMTP id p65so134135362wmp.1\n\tfor <dev@caliopen.local>; Sun, 20 Mar 2016 18:05:22 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=v147AQ7+gtpUKat6EQmVLOIvKly9FY+UWQ2uaoZ8CaA=;\n\tb=Sa+LTM347S4AcRW+Q3+Mh74Ixlw2uEtSHr4zxaxkBco67Y/Zz8Yrm5UHdJ0XiOYnYZ\n\t1C0xePp2zZzJV5Ad/D/4eBQ/ulWs/FTmfhHfJybsTVKvgiD5dItgdD9S/r218+xKuMDJ\n\tsIwnAdoIC+yjkMLHizl57wh3UF/gTJNurscWjJ1nFubtUxW6mClKzNacGAHCPODDFLa4\n\t24O+J3JIvGSApfC5Ug1RC4XBfO599mrBKvh/Mx6GEB7dass87+jyfg3JPRc0JtxY5pqr\n\t0OnUmmniuA1fGe5WseHtmHEGVjeRsiozd8tL8sepwhI2qlN1J3o31nLEmbcP55ozSzMZ\n\t+nbw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=v147AQ7+gtpUKat6EQmVLOIvKly9FY+UWQ2uaoZ8CaA=;\n\tb=cbYqp3krnNFqjwXPKqfU0NVrDdqkmlSSEV8Swss+1SB9Pnl5sobooeRe5hIG/xxxOI\n\tLHWuTPWx7pPpdA4n8iR7v9+0GfidUYnujweU3/8rgu1MervP5JVw4qP3scrZghHFjYCX\n\ttW4cjHBbTeiOY+AT7PGCpCmGNUnnh67/jFMfxaHuLf4xoGBQ3o83lGDI09XEd+1cX0Ly\n\tLwv6Vi7ahPYq3IVIFNhJp7EvQekjjXvi+mCk7ka1hHbGE5oEAZfokdbeyjoSLMBpWzUG\n\tXgKwvCFEDB8hCzrYz4hulTo9544Gg2iEa53A7SbyC00bmfQtIS2ZEyCYh1SapCqkfm2Q\n\te9nQ==\nX-Gm-Message-State: AD7BkJKnPD/YCC+EwYIG7EuzsV2aPzN+LuNc3Nb7CpctaDL4CydQK3Rt6kQfCICQaSMZGQ==\nX-Received: by 10.28.215.79 with SMTP id o76mr11235741wmg.95.1458522322327;\n\tSun, 20 Mar 2016 18:05:22 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tp125sm9986429wmd.16.2016.03.20.18.05.20\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 20 Mar 2016 18:05:21 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\nDate: Mon, 21 Mar 2016 02:05:20 +0100\nIn-Reply-To: <aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru> (Dmitry Gutov's\n\tmessage of \"Sun, 20 Mar 2016 17:58:11 +0200\")\nMessage-ID: <87a8lsd4j3.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22a\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 01:05:28 -0000\nContent-Length: 5152\nLines: 106\n\n\n>> On Sun, Mar 20 2016 17:58, Dmitry Gutov wrote:\n\n> On 03/20/2016 02:15 PM, Vitalie Spinu wrote:\n\n> IIRC, using first-column is fairly justified, the outer mode can't add extra\n> indentation to the submode is a reliable, sane way\n\nThe inner mode cannot often make that decision either. Same inner mode can be\nused in very different multi-mode contexts, each with their own semantics for\nchunks/headers/indentation. Reducing all that to a simple (first-column\n. previous-chunk) pair and letting inner mode do the job is surely not\nenough. The only actor to make that decision should be multi-mode engine itself.\n\nInstead of teaching modes about multi-modes, a much better idea is to introduce\n`calculate-indent-function` which would accept POS and optional STRING-AFTER and\nSTRING-BEFORE. This function will return the indentation of STRING-AFTER at POS\nassuming there is a virtual STRING-BEFORE just before POS.\n\nThis way, a multi-mode engine can call inner-mode's calculate-indent-function at\nthe end of previous chunk with STRING-AFTER being the line at point and\nSTRING-BEFORE being the content of current chunk. Most modes indent reliably\nbased on one previous line, so in 99% of the cases STRING-BEFORE can be nil and\nmulti-mode engine can call calculate-indent-function only on first line of the\ncurrent chunk (and that only for continuation chunks, which are a minority out\nthere). Then a lot of modes don't even care about what's in the current line, so\nSTRING-AFTER will be irrelevant as well. Thus most modes will not even need a an\nimplementation of calculate-indent-function.\n\nThis is both more general than prog-indentation-context and doesn't require\nteaching major-modes about multi-modes. Moreover, a lot of major-modes already\nhave such a \"calculator\" in place.\n\n>> It's essentially a half-backed implementation of \"hard widening\" discussed\n>> earlier. Why not impose the widening restriction directly in `widen` then?\n>> Maybe bring widen to elisp and rename C widen into widen-internal. Then add\n>> generic `prog-hard-widen-limits` which would be checked along\n>> prog-indentation-context limits.\n\n> Right! At the very least, I we should extract the second element of\n> prog-indentation-context into a separate variable, and make prog-widen more\n> prominent.\n\nNot sure about removing second element. Good thing about keeping all of them in\none place is for the indentation engine to be concerned with a single variable.\n\nBTW, third argument should be renamed into PREVIOUS-CHUNK. The function returns\none chunk.\n\n> But a proper implementation of hard-widen would be even better in my\n> book. Although someone would need to comb through all low-level functions, at\n> least, and decide which of them need to call widen-internal, and which will be\n> fine with just widen.\n\nNo need to decide on widen-internal. All functions are free to call widen just\nas they do before. It's 100% backward compatible. The only reason to use\n`widen-internal` is to bring `widen` to elisp in order to allow for advise and\nbetter debugging. Actually, with hard-widen-limits, there will be no need for\nadvice, so it can be kept in C.\n\nOnly consumers of `hard-widen-limits` should be concerned with its side\neffects. But that's uniformly better than current situation when you cannot do\nmuch about restricting widen.\n\nIn my experience hard-widen and parse-partial-sexp are the only hurdle in the\nway of proper multi-modes. I don't remember a single problem that would occur\nfor a different reason.\n\nBTW, I parse-partial-sexp must abide hard-widen-limits as well. This way the\nrequest aired in bug#22983 of parse-partial-sexp == syntax-ppss will be\nautomatically satisfied. You won't need syntax-ppss-dont-widen either.\n\n> Are you interested in working on a patch? Also Cc'ing Stefan.\n\nMy knowledge of emacs C internals is close to 0. Elisp side (and probably C\nside) of this is trivial. I will look into it but I don't think I am the best\nperson for that.\n\n> Looking back on it, it seems prog-indentation-context was merged too early: it\n> only has one usage so far, so it's not clear if the approach is generally\n> viable.\n\n> Christoph sort of promised to add support in CC Mode, but then\n> disappeared. Which is not so surprising, that stuff is difficult.\n\nA patch that would require hunting every single mode out there and implementing\nmulti-modes locally should have been more carefully considered IMO. Emacs 25 is\nnot yet there, so it's not late to reconsider that decision.\n\n> AFAICT, though, the ultimate justification for having first-column is Python's\n> indentation cycling behavior:\n> http://lists.gnu.org/archive/html/emacs-devel/2015-02/msg01096.html\n\n> Which is not that convincing, but makes some things clearner anyway.\n\nIt's not convincing to me either. I use Christoph's indentation-0 trick in and\nit indeed works reliably for all modes I have tried except python. But python's\nissue can be fixed with a simple advice of python-indent-line-function, no need\nto overhaul python indentation because of that. This is how it's now done in\npolymode:\n\n  https://github.com/vspinu/polymode/blob/master/polymode-compat.el#L189-L199\n\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_543.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahyJW-0004Cy-PW\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 07:48:02 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56933)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahyJT-0004CN-35\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 07:48:00 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahyJP-0000eT-SL\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 07:47:59 -0400\nReceived: from mail-wm0-x231.google.com ([2a00:1450:400c:c09::231]:36761)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahyJP-0000eN-I7\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 07:47:55 -0400\nReceived: by mail-wm0-x231.google.com with SMTP id r129so46799296wmr.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 04:47:55 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=nhiQImDA76SPRFlrhLziGbnQKiZXd25vSRGVIxOfS10=;\n\tb=pdMeyE2dwz1IrVdB4CSAcXz7mD0qUDJ8wrYZLw+8OxkbcLe68IBwRlq5qQmLpS+EB6\n\t5XrpII4kk5TGnn7IY+0GHv/YDVy6ir8SkJhvBERmUYMdh3k7/99FQkC8rnwxqRX+Ls84\n\tbUf1l38RZhhsW+oTSb9/WlwgI3NV7Zy4W7Gj+hpV57Zwj63FmVj9Z972GJABtT4qo70D\n\teMfqD5BBh4hIOeIadCYY73u+p7wsPF8u1KSpd36H5kbQCqAYTtMsuaER5gHkcSmKfhKF\n\tZ1GK3BPT9L5yK7px3nSWhuwd3CEGlHc1c222XCf3c5kzqJvEe41Ma/7HwImbEdb19kj3\n\tkSYg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=nhiQImDA76SPRFlrhLziGbnQKiZXd25vSRGVIxOfS10=;\n\tb=jXmnste6UUf6U6aVh/BAsI8pA7LlkzjPfFeiMgDlQOil3+yi2AN0jQiWoRUM+ufsMS\n\tqqwrqZUYbRUVjM54tAkSJXu+RBFlZbt6wboypvRbXe7JNxu7FxPWIHiCSKj4//xJFADd\n\tu41IS5lJ4+1KWrtxHOqCK7l2BmRTnoss+THg+LUZ0tOQSVFSGJNeSSNmbBG86QsL39iT\n\tJUSp+JYPULVqGQIdZBoaLnKNZRbY5pZVLLML4PXiFRX9y9fbMbY51ESSvhWaznHzLVoa\n\t7JoJtjlOIks9SMOibmnhK9Ki126B3bjhfNyfPOpYYym/gGBVV/9LvetXKwlG0qm/E5EC\n\tURkw==\nX-Gm-Message-State: AD7BkJLozeg4w+8sGefVJh8ns0InwsiGJqDRsR99TxaUGr6mJDpUWH55ok9daRu70sxURg==\nX-Received: by 10.194.174.231 with SMTP id bv7mr29927641wjc.17.1458560874647; \n\tMon, 21 Mar 2016 04:47:54 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\te4sm12090577wma.10.2016.03.21.04.47.52\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 21 Mar 2016 04:47:53 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Vitalie Spinu <spinuvit@gmail.com>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\nDate: Mon, 21 Mar 2016 13:47:51 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <87a8lsd4j3.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::231\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 11:48:00 -0000\nContent-Length: 4405\nLines: 101\n\nOn 03/21/2016 03:05 AM, Vitalie Spinu wrote:\n>\n>>> On Sun, Mar 20 2016 17:58, Dmitry Gutov wrote:\n>\n>> On 03/20/2016 02:15 PM, Vitalie Spinu wrote:\n>\n>> IIRC, using first-column is fairly justified, the outer mode can't add extra\n>> indentation to the submode is a reliable, sane way\n>\n> The inner mode cannot often make that decision either.\n\nWhat decision? One case where the mode cannot return its proposed \nindentation at all, is when the resulting column would be negative. \nUsing first-column can make it positive again via simple addition.\n\nUsing calculate-indent-function which returns a numeric value, would \nsolve that as well, of course, at the expense of having to update all \nmajor modes out there, and documentation. And making whatever \nthird-party guides are out there obsolete in this regard. I'm not really \nagainst that, mind you.\n\n> Same inner mode can be\n> used in very different multi-mode contexts, each with their own semantics for\n> chunks/headers/indentation. Reducing all that to a simple (first-column\n> . previous-chunk) pair and letting inner mode do the job is surely not\n> enough. The only actor to make that decision should be multi-mode engine itself.\n\nI'm not claiming that using previous-chunk is good.\n\n> Instead of teaching modes about multi-modes, a much better idea is to introduce\n> `calculate-indent-function` which would accept POS and optional STRING-AFTER and\n> STRING-BEFORE. This function will return the indentation of STRING-AFTER at POS\n> assuming there is a virtual STRING-BEFORE just before POS.\n\nStrings? Indentation engines do not deal with strings, they deal with \nbuffer contents. Having them handle this possibility would also amount \nto sharing a part of multi-mode logic.\n\nInstead, if you want to know what indentation an inner mode would return \nif STRING-BEFORE was before it, insert that string into the buffer \n(while inhibiting undo history). Call the indentation function, then \nremove the string. Any performance concerns with that?\n\n> Most modes indent reliably\n> based on one previous line,\n\nRuby doesn't. Most modes based on SMIE will need more than the previous \nline in the general case, too.\n\n> Then a lot of modes don't even care about what's in the current line, so\n> STRING-AFTER will be irrelevant as well.\n\nAlmost all of them care whether the current line contains }, or `end', \nor `else', and so on.\n\n>>> It's essentially a half-backed implementation of \"hard widening\" discussed\n>>> earlier. Why not impose the widening restriction directly in `widen` then?\n>>> Maybe bring widen to elisp and rename C widen into widen-internal. Then add\n>>> generic `prog-hard-widen-limits` which would be checked along\n>>> prog-indentation-context limits.\n>\n>> Right! At the very least, I we should extract the second element of\n>> prog-indentation-context into a separate variable, and make prog-widen more\n>> prominent.\n>\n> Not sure about removing second element. Good thing about keeping all of them in\n> one place is for the indentation engine to be concerned with a single variable.\n\nDidn't you mention font-lock and syntax-propertize yourself? Why would \nthey call a function that's solely dependent on an indentation variable?\n\nIn any case, your hard-narrowing proposal is very similar. Surely you \ndon't want to keep the second element of prog-indentation-context after \nhard-narrowing becomes available?\n\n> Only consumers of `hard-widen-limits` should be concerned with its side\n> effects. But that's uniformly better than current situation when you cannot do\n> much about restricting widen.\n\nOK, so *every* consumer of widen will have to obey the hard limits. That \nmight work, if there's no low-level code that absolutely has to always \nbe able to widen to the whole buffer.\n\n> BTW, I parse-partial-sexp must abide hard-widen-limits as well.\n\nIf you want parse-partial-sexp to obey limits, you narrow the buffer \naround it.\n\n> This way the\n> request aired in bug#22983 of parse-partial-sexp == syntax-ppss will be\n> automatically satisfied. You won't need syntax-ppss-dont-widen either.\n\nThat doesn't seem relevant. That bug is about stale cache values between \ndifferent narrowing bounds.\n\n> A patch that would require hunting every single mode out there and implementing\n> multi-modes locally should have been more carefully considered IMO. Emacs 25 is\n> not yet there, so it's not late to reconsider that decision.\n\nI concur.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_545.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahz8i-0000os-Km\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 08:40:56 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42998)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahz8g-0000oj-5e\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:40:55 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahz8b-0004Vw-5H\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:40:54 -0400\nReceived: from mail-wm0-x233.google.com ([2a00:1450:400c:c09::233]:35351)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahz8a-0004Vr-QY\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:40:49 -0400\nReceived: by mail-wm0-x233.google.com with SMTP id l68so108340244wml.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 05:40:48 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=TXK9CqVR8HXkQyvsyg9d3nmRbqI93fqs6ZLyliiGJrg=;\n\tb=fu0NUKVXBBozv5KfSjcMEaK1aAuNqbCWV9CohFi4mrYupDo9Y4GtsXZ/kxmlT1lJ8g\n\tsxdAr/dCsqg2uX9HwFUpIQvqv/YLF81MDcri9ybp4v0d+oxA/j0QiTmlFwamJ06BHO2W\n\t4zm8YNlN7FbyU/S42KOk9Eo3m/DLPYVW+7UQYcBwxWezvf04P6k2reZ673z/XPron2P9\n\ts05/dVF+U/fOKGhbtZha0TxmXRQxhdtZU/j3EU1FSpuhgKapRlsMEol3yq0scF0vTlrH\n\t1xWOOkKtCDJLlcoKP9xC7MxJd5GNHxlQJ/a75p8ldKby3JcgU+DN9XyexkluehMPWp2Y\n\tpVrA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=TXK9CqVR8HXkQyvsyg9d3nmRbqI93fqs6ZLyliiGJrg=;\n\tb=QSQ0VoQGr+nJ/Wbm7CBtqmq4B2Swo3AYxU3GW6iOmtC+CjA/0/Ug1zG4ICG27RsI30\n\tH+i56HM3TjeTZZv6SuAwGf8fxUwSLvHnpy6gkgPI5iMGN/5VC0ykyuCL8tBFTZ+tpGJr\n\tuJC1ztK2VVdRIGwpwnu8A//XZZaJgYD/ACovG+7pKYE6+qMSshg+ck9Ues6Wo6kGJhrq\n\t7OvG5yBzhqR8pwXjlnJj7DAy7tiLY8NT6Aqb1fuRC8epgrSuOIIkVXXJZHZJWQlhRwFE\n\tVo4skFQeEtVWBKYMgiWiMnuH9k5VNGCJc9RHSTcJO7FCuZhjgEcO/PciViwwIwf+Dk83\n\tUPgw==\nX-Gm-Message-State: AD7BkJIMGvln3QmiwFWZR0zMDoJqTewlwshB9MBqYeiatzA6/cCTf+V4UkyFmfWqRGs2Gw==\nX-Received: by 10.194.175.33 with SMTP id bx1mr30245190wjc.104.1458564048075; \n\tMon, 21 Mar 2016 05:40:48 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tlz5sm25221651wjb.5.2016.03.21.05.40.46\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 05:40:46 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\nDate: Mon, 21 Mar 2016 13:40:46 +0100\nIn-Reply-To: <328c7461-62c6-4228-f622-626349613a1d@yandex.ru> (Dmitry Gutov's\n\tmessage of \"Mon, 21 Mar 2016 13:47:51 +0200\")\nMessage-ID: <87fuvkf1gx.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::233\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 12:40:55 -0000\nContent-Length: 3945\nLines: 95\n\n\n\n>> On Mon, Mar 21 2016 13:47, Dmitry Gutov wrote:\n\n> On 03/21/2016 03:05 AM, Vitalie Spinu wrote:\n>>\n>>>> On Sun, Mar 20 2016 17:58, Dmitry Gutov wrote:\n>>\n>>\n>> The inner mode cannot often make that decision either.\n\n> What decision? \n\nDecision of how much to indent. Inner mode just doesn't have a complete picture\nof what is going on. Just having access to previous chunk is not enough.\n\nNote that I don't mind FIRST-COLUMN functionality. I think it's harmless and\nprobably useful. I mostly mind the last two arguments of\nprog-indentation-context.\n\n> I'm not claiming that using previous-chunk is good.\n\nGood ;)\n\n>> Instead of teaching modes about multi-modes, a much better idea is to introduce\n>> `calculate-indent-function` which would accept POS and optional STRING-AFTER and\n>> STRING-BEFORE. This function will return the indentation of STRING-AFTER at POS\n>> assuming there is a virtual STRING-BEFORE just before POS.\n\n> Strings? Indentation engines do not deal with strings, they deal with buffer\n> contents. Having them handle this possibility would also amount to sharing a\n> part of multi-mode logic.\n\nYeh. That's the sucky part. My hope is that BEFORE-STRING will be seldom\nused. Given that this case applies only to continuation chunks and assuming that\nmulti-mode engine can identify those (at least at multi-mode level) this is a\nreasonable trade off IMO. In polymode I haven't even got down to indentation of\ncontinuation chunks yet. They are not that common in literate programming.\n\nPerformance is not a primary concern for indentation. Correctness and conceptual\ncleanness is at a much higher stake here. My hope is that generic helper\nfunctions can be optimized to re-use same temp buffer for multiple invocations\nof calculate-indent-function.\n\n>> Then a lot of modes don't even care about what's in the current line, so\n>> STRING-AFTER will be irrelevant as well.\n\n> Almost all of them care whether the current line contains }, or `end', or\n> `else', and so on.\n\nIndeed. But this information is trivial to retrieve from STRING-AFTER.\n\n> In any case, your hard-narrowing proposal is very similar. Surely you don't want\n> to keep the second element of prog-indentation-context after hard-narrowing\n> becomes available?\n\nIndeed. I was not thinking about algorithmic complexities.\n\nAFAIK if second element is removed, the third one should go as well. That leaves\nonly FIRST-COLUMN then, which I personally don't mind.\n\n>> Only consumers of `hard-widen-limits` should be concerned with its side\n>> effects. But that's uniformly better than current situation when you cannot do\n>> much about restricting widen.\n\n> OK, so *every* consumer of widen will have to obey the hard limits. That might\n> work, if there's no low-level code that absolutely has to always be able to\n> widen to the whole buffer.\n\nI think as long as low level code uses BEGV and ZV instead of BEG and Z\neverything should be fine. That is with an implicit assumption that hard limits\nare always wider than the current visual narrowing which is a reasonable\ncontract IMO.\n\nEven better, as long as low level routines use BEG and Z consistently (and it\nlooks like they do) BEG and Z can be modified to take care of\nhard-widen-limits. This might be the easiest solution. In any case going through\nall C code and checking usage of widen is not such an insurmountable task.\n\n>> BTW, I parse-partial-sexp must abide hard-widen-limits as well.\n\n> If you want parse-partial-sexp to obey limits, you narrow the buffer around it.\n\n>> This way the\n>> request aired in bug#22983 of parse-partial-sexp == syntax-ppss will be\n>> automatically satisfied. You won't need syntax-ppss-dont-widen either.\n\n> That doesn't seem relevant. That bug is about stale cache values between\n> different narrowing bounds.\n\nRight. Those stale values won't occur in multi-modes because both syntax-ppss\nand parse-partial-sexp will always operate on same hard-narrowed regions.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_547.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahzYY-00006j-Vq\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 09:07:38 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49139)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahzYV-00005Q-VQ\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 09:07:36 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahzYQ-00021c-UE\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 09:07:35 -0400\nReceived: from mail-wm0-x22e.google.com ([2a00:1450:400c:c09::22e]:37499)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahzYQ-00021P-Jj\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 09:07:30 -0400\nReceived: by mail-wm0-x22e.google.com with SMTP id p65so121170457wmp.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 06:07:30 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=Dw6aU+e0jBAOzQOsOGK1j5D43lEgyMNcBBpQzL+JQI0=;\n\tb=XJYcOHj0Iex0gtEsbAg+yt7lsNJXj/GqYgF7q08IZEl8Y1AhnpsqLxsmv3WYVMHbEI\n\tn0Y1mEIMPE+b1GTNgRNNCWDKw9yYSB943ymwUPO971LM0xTNN7dIe+QqrMwEkIoGT94K\n\tgxuP02R0AedwbdIlG+7xAccnksCMdD12lj3P7umCSH4VztUr6wgatwpSTXDwwYb3dtgo\n\tTPjmF8bMaJET/uKS+z4Sf/Fxy89dLvAkifLZhocvVfyaNUq7wv9HUwiBhjRyl4p4ezdf\n\t0x4OBhoa776QRu0Sgx+6NT6vSIqaXE5ApHmNJgPwsQ33cyTC+P2OomF+P9HCBhAGDY8x\n\t/m1Q==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=Dw6aU+e0jBAOzQOsOGK1j5D43lEgyMNcBBpQzL+JQI0=;\n\tb=NK9gcdunXIfv4528tDFTX8JB3TBFAWBgwxGV8Xme4fYJZDdRWCjqTg55fUrGWAx/wX\n\t4IdMCVg+eOhPlmunbuKQf8kJeknsS1JP0suDqb91s1MigRkNIhj/8+s7+XAHhDMK41GW\n\tW15IDIF1fDPa6IVWS+qFJlS+rtrHV3NC1c68L7SEIF/VypJqjGv8gXFFHWmOKrXKIOBy\n\tRxWgcHVT6mUFh0z/GGemd01d0M/X1TogLqSJoWKbiUtToj2k2gnuVVjntd57LgdiFNww\n\tMbcP78UQAXvMGnX0jkRLX7cNPE5yYyJvtUezdbEVQf1oTstb/vVeA6XV7/Kon4VqmZ7R\n\t6bLw==\nX-Gm-Message-State: AD7BkJJu+usE43VLlab2KGj1x9fBxaGvGy2Va7Mw+5c3HU9V1M9oRIOmo6EfIRZlgcfEJw==\nX-Received: by 10.28.129.213 with SMTP id c204mr14356706wmd.89.1458565649509; \n\tMon, 21 Mar 2016 06:07:29 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\t192sm12437427wmw.0.2016.03.21.06.07.27\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 21 Mar 2016 06:07:28 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Vitalie Spinu <spinuvit@gmail.com>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <ae28a065-9500-0954-3c91-cd3a68bd2408@yandex.ru>\nDate: Mon, 21 Mar 2016 15:07:26 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <87fuvkf1gx.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22e\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 13:07:37 -0000\nContent-Length: 2274\nLines: 54\n\nOn 03/21/2016 02:40 PM, Vitalie Spinu wrote:\n\n>> Strings? Indentation engines do not deal with strings, they deal with buffer\n>> contents. Having them handle this possibility would also amount to sharing a\n>> part of multi-mode logic.\n>\n> Yeh. That's the sucky part. My hope is that BEFORE-STRING will be seldom\n> used.\n\nThen let's not add that to the API until we see a concrete need for it.\n\n> Performance is not a primary concern for indentation. Correctness and conceptual\n> cleanness is at a much higher stake here. My hope is that generic helper\n> functions can be optimized to re-use same temp buffer for multiple invocations\n> of calculate-indent-function.\n\nSo, how about trying my alternative proposal first?\n\n>>> Then a lot of modes don't even care about what's in the current line, so\n>>> STRING-AFTER will be irrelevant as well.\n>\n>> Almost all of them care whether the current line contains }, or `end', or\n>> `else', and so on.\n>\n> Indeed. But this information is trivial to retrieve from STRING-AFTER.\n\nFeeding it to each particular indentation engine is not going to be trivial.\n\n>> In any case, your hard-narrowing proposal is very similar. Surely you don't want\n>> to keep the second element of prog-indentation-context after hard-narrowing\n>> becomes available?\n>\n> Indeed. I was not thinking about algorithmic complexities.\n>\n> AFAIK if second element is removed, the third one should go as well. That leaves\n> only FIRST-COLUMN then, which I personally don't mind.\n\nOK. And that one could be replaced with the introduction of \nprog-indentation-function. Though that might be getting ahead of ourselfves.\n\n>>> This way the\n>>> request aired in bug#22983 of parse-partial-sexp == syntax-ppss will be\n>>> automatically satisfied. You won't need syntax-ppss-dont-widen either.\n>\n>> That doesn't seem relevant. That bug is about stale cache values between\n>> different narrowing bounds.\n>\n> Right. Those stale values won't occur in multi-modes because both syntax-ppss\n> and parse-partial-sexp will always operate on same hard-narrowed regions.\n\nWe could only be sure of that for syntax-ppss calls in facilities that \nthe multi-mode handles specially, like font-lock, syntax-propertize and \nindentation. Not so with any other functions the user could call.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_549.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0hJ-0005wa-JZ\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:20:45 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41055)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0hE-0005oN-S2\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:20:44 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0h9-0002TG-TA\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:20:40 -0400\nReceived: from mail-wm0-x234.google.com ([2a00:1450:400c:c09::234]:34466)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0h9-0002TC-NZ\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:20:35 -0400\nReceived: by mail-wm0-x234.google.com with SMTP id p65so153611493wmp.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 07:20:35 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=0OxHJkTf1pMM2TbT3X4/UcZTIKsTRQpDN1hmJfoSai4=;\n\tb=IAAKSDZD6J+kISwcmdw4XZ1A4It4naJNnKQMzpAIkAWoCeHBulAFj//nq6Jj9rxUDK\n\tX+L9zZ9dfLGRqCa0sjRfI28R04Ps21MKjgGdXMGIKAcUuBkNrlsMJHjtI/YvZvJdcUpu\n\tlcGCq2gRd5JNeI96esTBo66Uu3VY1A6yeq00Ex216YAMQ8XhDge8fxCIbdQTV4qoAwIG\n\t+LkPGQSo1qmyDOhCosnJAwOHzTkHKBc52mrP8jw2Qv+xRFCQMblvO9R0s40aGclP41oj\n\tDA4nBs+XSdJOeJrPpfJiqpQyp6LRKLcuPUBRdKkKQrHcFSbURX8BfspztBzGpb+0j+GS\n\tml8w==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=0OxHJkTf1pMM2TbT3X4/UcZTIKsTRQpDN1hmJfoSai4=;\n\tb=LW0+ren31s1duiCDuivnktj83DKNvI3pTg39XwtV8Pd4O8+3sW03MMt5ljwe2bzvwt\n\tB9eTeiVJ/5vyGFngl8rtjGgY5fR9nPt1wTNU1aG6WtVZ5O238zzDUMc6Zchwn7C27Y3D\n\t0wUTz7QgmnNUAExvaKF7temqNL8GT5T5qjVksQ7tm+22Siu+7yjaDa530GCV1QHiwJaR\n\tAtKJcDev3SQCXkfDMjhC2mGwx4AfVvWUq7mmzqV2YRbq/wGtJH1K7wjcJTZIya9TR4ac\n\tCv0s+Vb3MgxpbE11cUo3d5IjoAFBDPw/RUwDH6DqmRrlHQ5ibLWmUZYU4giz4sum7fcT\n\tIEig==\nX-Gm-Message-State: AD7BkJLFkL5/R8n4KlKxC/iaficNYZks3LEyITxHi8cIMbgINIT4+KbwSA6vNJ9mBJa9MQ==\nX-Received: by 10.28.140.11 with SMTP id o11mr13648715wmd.81.1458570035032;\n\tMon, 21 Mar 2016 07:20:35 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tt3sm25591414wjz.11.2016.03.21.07.20.34\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 07:20:34 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<ae28a065-9500-0954-3c91-cd3a68bd2408@yandex.ru>\nDate: Mon, 21 Mar 2016 15:20:33 +0100\nIn-Reply-To: <ae28a065-9500-0954-3c91-cd3a68bd2408@yandex.ru> (Dmitry Gutov's\n\tmessage of \"Mon, 21 Mar 2016 15:07:26 +0200\")\nMessage-ID: <87y49bewum.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::234\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:20:44 -0000\nContent-Length: 1388\nLines: 38\n\n\n\n>> On Mon, Mar 21 2016 15:07, Dmitry Gutov wrote:\n\n> On 03/21/2016 02:40 PM, Vitalie Spinu wrote:\n\n>>> Strings? Indentation engines do not deal with strings, they deal with buffer\n>>> contents. Having them handle this possibility would also amount to sharing a\n>>> part of multi-mode logic.\n>>\n>> Yeh. That's the sucky part. My hope is that BEFORE-STRING will be seldom\n>> used.\n\n> Then let's not add that to the API until we see a concrete need for it.\n\nIt might be good to not include these (prog-indentation-context including) in\nemacs 25 release.\n\n>> Performance is not a primary concern for indentation. Correctness and conceptual\n>> cleanness is at a much higher stake here. My hope is that generic helper\n>> functions can be optimized to re-use same temp buffer for multiple invocations\n>> of calculate-indent-function.\n\n> So, how about trying my alternative proposal first?\n\nSorry. What proposal do you mean?\n\n>> Right. Those stale values won't occur in multi-modes because both syntax-ppss\n>> and parse-partial-sexp will always operate on same hard-narrowed regions.\n\n> We could only be sure of that for syntax-ppss calls in facilities that the\n> multi-mode handles specially, like font-lock, syntax-propertize and\n> indentation. Not so with any other functions the user could call.\n\nI assume that multi-mode engine is advising syntax-ppss which I think it should.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_551.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0qU-00020I-Lt\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:30:14 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:43856)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai0qN-0001w1-Ei\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:30:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai0qH-0004oY-Lv\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:30:07 -0400\nReceived: from mail-wm0-x232.google.com ([2a00:1450:400c:c09::232]:34706)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai0qH-0004oM-Ez\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:30:01 -0400\nReceived: by mail-wm0-x232.google.com with SMTP id p65so153965968wmp.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 07:30:01 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=SJj16B9j8COJvmFbYEfeZl9P85kCU8jGDLs6CiOU0r4=;\n\tb=ufxkmadwUIJdJxEBpoJXV8naTkLPOndm8SZUlimC9++l8VwglrUZh4GPFKqo9ImfW6\n\t0fPD5BDR2FF3zP3A9kQWTvRQK+nd026I2Y4iwRhIvFtEqlPJwAoOkArYccFHxZ/fjwF0\n\tQ1LcsUlDqBYZ0ICKxb/2W4bR9XkAr/L4nRR+cabf33mCsWdznIbOMEMcbuF98zpKr2gj\n\tW8Ls7LvPEl77GSdxoE05Xlut51HWSnId172vTv61/JRZ27y+RrZSDsx4KEhjn0QFR78x\n\txvQo3lJ7m6GOLzQ86Ai+XA39VSj+GUfa3B8VWwXUP/S/pGp0YSuwoAyXAiqiKZuWtf/k\n\taGUg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=SJj16B9j8COJvmFbYEfeZl9P85kCU8jGDLs6CiOU0r4=;\n\tb=fz5Z96Khrt3U/ytnT7qoL3wNzMuu1s1GH/KO/N/3ZYhMDeBBh7uX3yYDC6NgzJFCpS\n\tIXv0TRwVdS4+YBQZs9RdfZE9PYdcqbD0F7z7Hy5ByAFzTRHvudjm/43oqI4nSQlbBpiO\n\tCJfsVQ5ZTPl/Ki4tyYGrIjIAV4hv+dWOFzblX4XDtoTqnp6hUbQtb3Jhd3CwNEZEC18/\n\tLKKtIJd3lKnh8Pthz0gntpKna9w2lSOlhVtEJdGV1OzBL/Ye6A4iq0JV3Wcj6pJ/9/VD\n\tKLnUn4g3DKSK4sfvZLpjXJJm+fB3+A8Fp4YK+BrvWq0gz7LY6HvDZ4I0vjg0GuKlxkV0\n\tsHjA==\nX-Gm-Message-State: AD7BkJL1j1VyOrrcTX7h2si9vfoavBjuHUp/+E3nZyvmDeO4hreLJcxci5jK7NO+T2OjQw==\nX-Received: by 10.28.238.210 with SMTP id j79mr14726867wmi.23.1458570600834;\n\tMon, 21 Mar 2016 07:30:00 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\ti2sm25664809wje.22.2016.03.21.07.29.58\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 21 Mar 2016 07:29:59 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Vitalie Spinu <spinuvit@gmail.com>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<ae28a065-9500-0954-3c91-cd3a68bd2408@yandex.ru>\n\t<87y49bewum.fsf@gmail.com>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <ea0d8779-3207-7a29-2c8b-3a047f1fcd46@yandex.ru>\nDate: Mon, 21 Mar 2016 16:29:57 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <87y49bewum.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::232\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:30:13 -0000\nContent-Length: 1576\nLines: 38\n\nOn 03/21/2016 04:20 PM, Vitalie Spinu wrote:\n\n> It might be good to not include these (prog-indentation-context including) in\n> emacs 25 release.\n\nOf course, none of them. But nor should we put BEFORE-STRING into master \nuntil we understand that we really need it, and how to use it.\n\n>>> Performance is not a primary concern for indentation. Correctness and conceptual\n>>> cleanness is at a much higher stake here. My hope is that generic helper\n>>> functions can be optimized to re-use same temp buffer for multiple invocations\n>>> of calculate-indent-function.\n>\n>> So, how about trying my alternative proposal first?\n>\n> Sorry. What proposal do you mean?\n\n\"\"\"\nInstead, if you want to know what indentation an inner mode would return \nif STRING-BEFORE was before it, insert that string into the buffer \n(while inhibiting undo history). Call the indentation function, then \nremove the string.\n\"\"\"\n\nSame with AFTER-STRING. The multi-mode package itself would do that.\n\n>>> Right. Those stale values won't occur in multi-modes because both syntax-ppss\n>>> and parse-partial-sexp will always operate on same hard-narrowed regions.\n>\n>> We could only be sure of that for syntax-ppss calls in facilities that the\n>> multi-mode handles specially, like font-lock, syntax-propertize and\n>> indentation. Not so with any other functions the user could call.\n>\n> I assume that multi-mode engine is advising syntax-ppss which I think it should.\n\nVery well, that's an option. Having syntax-ppss-dont-widen (or making \nsyntax-ppss respect hard-widen-limits) should be sufficient for it.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_553.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai12M-0005MG-2Q\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:42:30 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46726)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai12K-0005M9-GJ\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:42:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai12G-0007oJ-Lm\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:42:28 -0400\nReceived: from mail-wm0-x234.google.com ([2a00:1450:400c:c09::234]:37913)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai12G-0007oE-DV\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:42:24 -0400\nReceived: by mail-wm0-x234.google.com with SMTP id l68so124706117wml.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 07:42:24 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=wyZKwq4cZnvDnj6UwGfVx6Q57qpPuAU3vs5Nj5rOUZM=;\n\tb=mSIT/tOJQ/SX1pM4XgdOSXy9/Ko0JfWhuCOFPn7XZMpVrkDJ4L2zllEvkNTKyYiKT2\n\toDxKv2OqRf6e6G/ifVK+T/Jv/z/MhGFMQW+ZxNZULAfduBzQhktAtvy+3NtvkO+cOBVZ\n\t4OQCe0G53k290cXR7vmb5g4q+5pZPP5/bogSyxVPo03b1HfrX36sjJtTfgjJj99Zqdq3\n\tK9nEReCrTwioPbKzDwcRGqeUm9+Il+lwB4S1d1mkR7M41sD0qy6gp3rL1xiksA7NtOQY\n\tVr2ydNYsjJDm9kzYRzoenogM0H+67QE/QdOWf610NU1TmeedwG8bhLHnnVLGHGRky6bv\n\tCoag==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=wyZKwq4cZnvDnj6UwGfVx6Q57qpPuAU3vs5Nj5rOUZM=;\n\tb=lD3eoLUxWZGJv+ZeXkZI+bWDQ0yEDFyVLRkRks+pGNM2bFvvcGicO6ryeFpQspBpH7\n\tcNDwGAokk3MasfGZFRYEij1KK8EC/xiqq5ZXFUusEaNR/lDNDa41utI53pSNVR2K49Tq\n\t/cCNYDrwsesV7VHxN1+FFSTT/7jP94ToR+3Gl6rhNP7ZX0M6buko48twVtoWVGShNmFs\n\tIQTLdZr/aDLcNrsNm6NLduoVwRq7Sq6RuwtHJKqLzWFvTn2eGMqwMAeAfna63dOiyzN9\n\t5Vh2ZnrHjQx5YoQfQV+PHMHsf7goNqEJ4PIBACD0tnJO7zpy12ZHTo5EoN288vJMQnRp\n\tGUGA==\nX-Gm-Message-State: AD7BkJLEcmJxjTwPVOx6ZMevZb/ZDmYWuqmeDwcq+oS+v1St3YyoffSYF+l5tIKo5ml+Mw==\nX-Received: by 10.194.60.200 with SMTP id j8mr30322965wjr.124.1458571343712;\n\tMon, 21 Mar 2016 07:42:23 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tj10sm25622039wjb.46.2016.03.21.07.42.22\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 07:42:22 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<ae28a065-9500-0954-3c91-cd3a68bd2408@yandex.ru>\n\t<87y49bewum.fsf@gmail.com>\n\t<ea0d8779-3207-7a29-2c8b-3a047f1fcd46@yandex.ru>\nDate: Mon, 21 Mar 2016 15:42:22 +0100\nIn-Reply-To: <ea0d8779-3207-7a29-2c8b-3a047f1fcd46@yandex.ru> (Dmitry Gutov's\n\tmessage of \"Mon, 21 Mar 2016 16:29:57 +0200\")\nMessage-ID: <87lh5bevu9.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::234\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:42:29 -0000\nContent-Length: 851\nLines: 24\n\n\n\n>> On Mon, Mar 21 2016 16:29, Dmitry Gutov wrote:\n\n>> Sorry. What proposal do you mean?\n\n> \"\"\"\n> Instead, if you want to know what indentation an inner mode would return if\n> STRING-BEFORE was before it, insert that string into the buffer (while\n> inhibiting undo history). Call the indentation function, then remove the string.\n> \"\"\"\n\nInner mode might decide to operate on string directly, or put stuff in a temp\nbuffer, work on last line only, or simply ignore it. Why to hard-wire the usage\nof STRING-BEFORE so badly?\n\nMy gut feeling is to avoid modifying buffer context in indentation engine at all\ncosts. In the future, if performance with temp buffers will be a real issue, we\ncan add more low level functions for fast operation on string to do some common\nparsing tasks. We can even extend parse-ppss to deal with BEFORE-STRING.\n\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_555.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai1Fk-0002Gh-AR\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:56:20 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:51646)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai1Fe-0002FU-Dl\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:56:18 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai1FZ-0002jr-9v\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:56:14 -0400\nReceived: from mail-wm0-x235.google.com ([2a00:1450:400c:c09::235]:35103)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai1FZ-0002jg-38\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:56:09 -0400\nReceived: by mail-wm0-x235.google.com with SMTP id l68so113590851wml.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 07:56:08 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=1HtpRzJBxvPBK65Xy5Pp2M3Z42PIeOCL3TTZ5CG/0PU=;\n\tb=kBBAyCw58XY3CbTBWrCEbC1rEtZQuWoy7BH888Bnl444uf1VerVOo/pjRGGn66djUJ\n\tVbDOMFgbXU2e5W0HHeKFXstbKFyXpsPbXV6ElD/ozoRz2uiGVh/uCkQSkHUFUfdAPz5D\n\tCyDhl8rhL2pz6lXqMITHLsDLN3txnkOwYv5TGYTYlPACEslwxX+krWbMDHsdaeJFjlVR\n\t0Xke8RYf4xbZiN5BAhpXfYTapEpYSLV7S3YvpG0FrCfXKELp36QIJgOuc6pez3++5H8N\n\ti6LzIS/658A8ALQWxC9hxPEp9djAtMjeaErUwxBvhk7suMlsXtRgupXm7Jj4il+4K5/5\n\t3dKw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=1HtpRzJBxvPBK65Xy5Pp2M3Z42PIeOCL3TTZ5CG/0PU=;\n\tb=RY4k5Ni15m/5u858p6ewlqcfnkYZ1rNErZZi7+ppY1FFgb+3gir75XAlrr7fCM0EYO\n\tvjZDWtpoRIfkDc0nwHHcExCVKcWKhm05eV4M0QAhVakYTxYopEDtwxRhivWgzunIT1Mi\n\tKzJc/5Mh7Wxzdlmy3Kb9cJNrDK4fdY6EmhP9n0IiH7Je75h4P41qi/ZFFqrb/JjWLfzI\n\tq3R7ubqrVqKsSWRQoEiwkZmZQ3sNgXxaD7CHKjUqlTZzmEJl9xbSIzktuL89AZ0PraNX\n\t9dCBDfvXU1mwG5BA9VmTxIJS4HHh9TScj6p7pGmvCW+lhGiPO17Qe7fJlTNA0hsY2dtR\n\t8Yvg==\nX-Gm-Message-State: AD7BkJLIRiiIRWeSt2eMANvyuL5H91CpcF4Rez8R27RRsOPfzJy3DTjuhSp+Vfcrk95+sQ==\nX-Received: by 10.28.184.18 with SMTP id i18mr13849431wmf.96.1458572168235;\n\tMon, 21 Mar 2016 07:56:08 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\te127sm12852112wma.20.2016.03.21.07.56.06\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 21 Mar 2016 07:56:07 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Vitalie Spinu <spinuvit@gmail.com>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<ae28a065-9500-0954-3c91-cd3a68bd2408@yandex.ru>\n\t<87y49bewum.fsf@gmail.com>\n\t<ea0d8779-3207-7a29-2c8b-3a047f1fcd46@yandex.ru>\n\t<87lh5bevu9.fsf@gmail.com>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <83c0f91b-21ff-2514-d24a-5b6104ef012b@yandex.ru>\nDate: Mon, 21 Mar 2016 16:56:05 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <87lh5bevu9.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::235\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:56:18 -0000\nContent-Length: 1506\nLines: 42\n\nOn 03/21/2016 04:42 PM, Vitalie Spinu wrote:\n\n>> \"\"\"\n>> Instead, if you want to know what indentation an inner mode would return if\n>> STRING-BEFORE was before it, insert that string into the buffer (while\n>> inhibiting undo history). Call the indentation function, then remove the string.\n>> \"\"\"\n>\n> Inner mode might decide to operate on string directly, or put stuff in a temp\n> buffer, work on last line only, or simply ignore it.\n\nYes, each major mode would have to make all of these choices.\n\nWhy burden them with that concern? Wouldn't that become a part of the \nsame problem that you yourself mentioned, \"teaching modes about \nmulti-modes\"?\n\n> Why to hard-wire the usage\n> of STRING-BEFORE so badly?\n\nWhat hard-wiring?\n\nSTRING-BEFORE is not a tangible part of my proposal. There's no API \nchange tied to it.\n\n> My gut feeling is to avoid modifying buffer context in indentation engine at all\n> costs.\n\nWhy? That's worked out okay for me.\n\nAlternatively, you can create a temp buffer each time, compose pieces of \ninner mode text in it, and call the indentation function. Again, in \nmulti-mode code.\n\n> In the future, if performance with temp buffers will be a real issue, we\n> can add more low level functions for fast operation on string to do some common\n> parsing tasks. We can even extend parse-ppss to deal with BEFORE-STRING.\n\nPerformance is a distant concern, complexity is the immediate one. If \nmodifying buffers turns out to be a problem, then we can do all the \nstuff you mention above.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_557.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai34U-0007Lx-Vg\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 12:52:51 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39484)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai34S-0007Hp-RD\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 12:52:49 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai34N-00086v-O7\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 12:52:48 -0400\nReceived: from mail-wm0-x22e.google.com ([2a00:1450:400c:c09::22e]:34287)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai34N-00086V-Gt\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 12:52:43 -0400\nReceived: by mail-wm0-x22e.google.com with SMTP id p65so159438778wmp.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 09:52:43 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=bidWv+tP+OiVFteS+bMVgP8PBgRnewlXTxE9sh41Vck=;\n\tb=t5IqC+AQ/rpEZKU8G7WUUXS2yqyn3wQDxtb3gGAALmBZo4wJTY0EYhWl+VayJW4A7O\n\tpZm+mtggaOaMLOSK7ReuztxYOtLosFSSyCmIaq//RzMPipA7p9xre8GAQrXw+ymhS/NN\n\tOe/ODoTNiPa7ofRuDSidN30qxBfCNhhNQRVBrnDs+W50Qmj6RghPGsFv3El44XgwwOA6\n\tQZceM8T0DA10iQyOPkwTNQ+8lh3YeJ74x+VAqtw1Uk7uxtF2a2+HdFCjv+O1/k3AnVib\n\tNfs3KBGrfLyuwXCLwdwCWmlJIRV+0lZiMTxuQQe9QYBLavG8HpwZkR9K2fesIc8H6tBR\n\ty/+Q==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=bidWv+tP+OiVFteS+bMVgP8PBgRnewlXTxE9sh41Vck=;\n\tb=lK7a+17r44vOid146ZkYzzh6vXBVaN15SeD+v9rQ5qX97sko1ZdgALDZma9v+DvaK0\n\t3p4VwqjjRU7oLqv2+lLirAQfCu3bIu+a9b9l1iJqeGyPITLsKZB3AhmXzZInYGeahxk7\n\tiRw+oSrtKMlbSlPvHUiblJGqbscCSaJ9Zut0+nkgnfXPLNuuMoAczHDS5uxYHLPzpq3b\n\tobg9hgHX0M8rv16/ZxGw4kZjJyLI+IJjNjOziXgHt2rqSEb5IsTnACUVRRQxvmaMkI2M\n\tnnijVRzQdQ5+YLiScQ370xF8c2TeM7KRykMcTYhhZ52xYxPU313i8hilRXjmaD5dapEG\n\tszlw==\nX-Gm-Message-State: AD7BkJIXMnpIanCcd2+jV7rpz/hv8QrzKtTU0psvfuEkL8EgulR4bBjb+T0q5BiJwps4jA==\nX-Received: by 10.194.84.2 with SMTP id u2mr31122543wjy.61.1458579162487;\n\tMon, 21 Mar 2016 09:52:42 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tw15sm13261240wmd.10.2016.03.21.09.52.41\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 09:52:41 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<ae28a065-9500-0954-3c91-cd3a68bd2408@yandex.ru>\n\t<87y49bewum.fsf@gmail.com>\n\t<ea0d8779-3207-7a29-2c8b-3a047f1fcd46@yandex.ru>\n\t<87lh5bevu9.fsf@gmail.com>\n\t<83c0f91b-21ff-2514-d24a-5b6104ef012b@yandex.ru>\nDate: Mon, 21 Mar 2016 17:52:41 +0100\nIn-Reply-To: <83c0f91b-21ff-2514-d24a-5b6104ef012b@yandex.ru> (Dmitry Gutov's\n\tmessage of \"Mon, 21 Mar 2016 16:56:05 +0200\")\nMessage-ID: <877fgvept2.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22e\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 16:52:50 -0000\nContent-Length: 1811\nLines: 49\n\n\nOk, so the alternative proposal is not to do anything. I like that. The only\nreason to have STRING-AFTER and STRING-BEFORE is potential mode specific\noptimization. If that's not a concern, no need for that.\n\n  Vitalie\n\n>> On Mon, Mar 21 2016 16:56, Dmitry Gutov wrote:\n\n> On 03/21/2016 04:42 PM, Vitalie Spinu wrote:\n\n>>> \"\"\"\n>>> Instead, if you want to know what indentation an inner mode would return if\n>>> STRING-BEFORE was before it, insert that string into the buffer (while\n>>> inhibiting undo history). Call the indentation function, then remove the string.\n>>> \"\"\"\n>>\n>> Inner mode might decide to operate on string directly, or put stuff in a temp\n>> buffer, work on last line only, or simply ignore it.\n\n> Yes, each major mode would have to make all of these choices.\n\n> Why burden them with that concern? Wouldn't that become a part of the same\n> problem that you yourself mentioned, \"teaching modes about multi-modes\"?\n\n>> Why to hard-wire the usage\n>> of STRING-BEFORE so badly?\n\n> What hard-wiring?\n\n> STRING-BEFORE is not a tangible part of my proposal. There's no API change tied\n> to it.\n\n>> My gut feeling is to avoid modifying buffer context in indentation engine at all\n>> costs.\n\n> Why? That's worked out okay for me.\n\n> Alternatively, you can create a temp buffer each time, compose pieces of inner\n> mode text in it, and call the indentation function. Again, in multi-mode code.\n\n>> In the future, if performance with temp buffers will be a real issue, we\n>> can add more low level functions for fast operation on string to do some common\n>> parsing tasks. We can even extend parse-ppss to deal with BEFORE-STRING.\n\n> Performance is a distant concern, complexity is the immediate one. If modifying\n> buffers turns out to be a problem, then we can do all the stuff you mention\n> above.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_559.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai7Ow-0000M9-IV\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 17:30:14 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38440)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai7Ov-0000M3-7c\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 17:30:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai7Os-0005zw-0Z\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 17:30:13 -0400\nReceived: from mail-wm0-x235.google.com ([2a00:1450:400c:c09::235]:34506)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai7Or-0005zi-Qg\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 17:30:09 -0400\nReceived: by mail-wm0-x235.google.com with SMTP id p65so168170913wmp.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 14:30:09 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=m7PnvgfvvbOHMM9Df7oMZUdGD0U5UhDrEU2sTgTf72s=;\n\tb=WEKTuT/sMg8qNVo9C2+1wz3vvjI0lio5GsHqbCfVCJKk4JGXl3h6e8SPmaezMNQkXB\n\tc91cPpO0yyqwXxz5pP/IeX6QpWIfkKGb5qQVAKPkg2M2iRLYWHbXnlZij4/ZJRu0T3qN\n\twBM1JW98ZUyjrDkBrdyxVju4kaw3JFRxYlOVyiUfDZ8qEyTxD66SM2YaZqQJ40Y2/3w6\n\t4NrHI8U5CwFOL0Jetz5x+IsKmhUTnbOk1Xa37PlNqoMIthFZibbegTb9nev+Omg5xlvh\n\tISvAw3wKtuHCXMeA4vfPO6e1xR8IL5NKHLkZ60G9jHuGjbLNc85e0eTETRDuTm7DOcFy\n\toWtg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=m7PnvgfvvbOHMM9Df7oMZUdGD0U5UhDrEU2sTgTf72s=;\n\tb=UYfn34FKu/dAAhq2NffqMqm/diK8JJLnXoxow0Ey/eSX25op4Qei9MQvCvPXE+qlBm\n\t3JnTFB7wBqfvnKPkwB0Ip3HbdfdMhXfjuqFct/88buU5qVUjhfoX8AxcvcAFIRWNZ2QS\n\tXCIjms9ZWCsQjg2KFunM0MUL0mbBlTx26dokAMkRQoOYlrxjnh9WXGH6sJ4mZd98jdOD\n\t07qEr0r2rd1sgWPQ2mmyU3++vpT9UGkWU7NsZ2CQMuI4M2SOLhplpRUwc800U8zzswOf\n\t1Geu08sDxlBgHg2hXaCM5UmAe0OlKh80fKV240vS83ReTI5OEae1rZQcTNNmvci7y1s+\n\t3DEw==\nX-Gm-Message-State: AD7BkJKDP6aUwexINGOtu4DFRNZSQtlT/wKz1XRPcmPJUq1C//fEnzvUVyirfuzXlURR4g==\nX-Received: by 10.28.227.130 with SMTP id a124mr14041801wmh.2.1458595809069;\n\tMon, 21 Mar 2016 14:30:09 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\tk125sm14185272wmb.14.2016.03.21.14.30.07\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 21 Mar 2016 14:30:08 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Vitalie Spinu <spinuvit@gmail.com>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<ae28a065-9500-0954-3c91-cd3a68bd2408@yandex.ru>\n\t<87y49bewum.fsf@gmail.com>\n\t<ea0d8779-3207-7a29-2c8b-3a047f1fcd46@yandex.ru>\n\t<87lh5bevu9.fsf@gmail.com>\n\t<83c0f91b-21ff-2514-d24a-5b6104ef012b@yandex.ru>\n\t<877fgvept2.fsf@gmail.com>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <7c941e57-756c-0081-9e78-fd31db305d7c@yandex.ru>\nDate: Mon, 21 Mar 2016 23:30:07 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <877fgvept2.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::235\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 21:30:14 -0000\nContent-Length: 477\nLines: 13\n\nOn 03/21/2016 06:52 PM, Vitalie Spinu wrote:\n>\n> Ok, so the alternative proposal is not to do anything. I like that.\n\nRather, wait and see, instead of hurrying to put those into the API.\n\n> The only\n> reason to have STRING-AFTER and STRING-BEFORE is potential mode specific\n> optimization. If that's not a concern, no need for that.\n\nPerformance may be a concern, but we don't know that yet. As long as \nthey're not required for correctness, let's not get ahead of ourselves.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_561.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amrZg-0000It-HB\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:36:56 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41897)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrZe-0000IQ-8j\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:36:55 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrZb-0006FK-R5\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:36:54 -0400\nReceived: from mail-oi0-x233.google.com ([2607:f8b0:4003:c06::233]:36416)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrZb-0006FE-Mi\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:36:51 -0400\nReceived: by mail-oi0-x233.google.com with SMTP id y204so30003695oie.3\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:36:51 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=pMM/Y2HDIWtW8PBOHgdw7xHkvhqRAxUumq3pyZlzpcY=;\n\tb=u8J6syPTsblcNeZYnKdhschEJpmmIb+yw4oIweGEEUoPKuKFHAFX/FRz4dyj8nSRe2\n\tISy2EJbrF866XMPMG8vX0MHF7wR/wB11WsOQgE+jdeapK48nNymRJk+vhML239aDCaPu\n\tQECJ4E03Yv5RVX8Q6w5n9oNMPdR00HFYt0j510TEXJ59wXXa0U5YhaVFFzLxHFGaQmOW\n\tENbiPdjBPkxEPlqaUEe7AKfmjh5QjQP7NyBnPTLV9jDWwE2wn6l7l/tNyn8fktd75DsV\n\t9IT2tnGHcHgxW4p76tLZ+haP1aAlN5xvqvXgUcSKy9s1+vvrI4B4qwnyJakBCWWyYeqW\n\tlpjw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=pMM/Y2HDIWtW8PBOHgdw7xHkvhqRAxUumq3pyZlzpcY=;\n\tb=iLFMqe6ECzKNORwlu7eL+X0ThEY4mjqazgV/x3vUqPTEz85tvTkKmXLRZtpXMU9jYI\n\tZLJghY8NBIkBTvWMkTz+RqU+6g2Gh6O57um7hlgDTZjkbplXcjh381D1BbW5UV9ry+7d\n\tHNDqj736rMz3EVqLVYIoPSgWVOZBiPZoVz2u5PJWttp+npMhp5m28aWIpAjqVRM9JZ3L\n\tPWVMOa5BmwCCguOiPjj/xEYA7qYvX81zC8SVTsq23lQg9ThmGTIyBiznkcJB1jzwrFRb\n\tdx1jO/w+ea0SgHGT5444yWTY1F9bXKJDx+MRPw1PH/EhO/SsdiraMp41dSxVDUujSd59\n\tFwoA==\nX-Gm-Message-State: AD7BkJLXVwIJRKT5zCYbZO3hqYFrgZLrUfBq7f8kFGW+RRDbNJYT8RMix1D1DZDDEUpdxA==\nX-Received: by 10.157.15.101 with SMTP id 92mr7760464ott.156.1459726611254;\n\tSun, 03 Apr 2016 16:36:51 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79])\n\tby smtp.gmail.com with ESMTPSA id q4sm7588069oew.6.2016.04.03.16.36.49\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 16:36:50 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid A0FD313DAEA06; Sun,  3 Apr 2016 16:36:48 -0700 (PDT)\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nIn-Reply-To: <7c941e57-756c-0081-9e78-fd31db305d7c@yandex.ru> (Dmitry Gutov's\n\tmessage of \"Mon, 21 Mar 2016 23:30:07 +0200\")\nDate: Sun, 03 Apr 2016 16:34:36 -0700\nMessage-ID: <m28u0uclmb.fsf@newartisans.com>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<ae28a065-9500-0954-3c91-cd3a68bd2408@yandex.ru>\n\t<87y49bewum.fsf@gmail.com>\n\t<ea0d8779-3207-7a29-2c8b-3a047f1fcd46@yandex.ru>\n\t<87lh5bevu9.fsf@gmail.com>\n\t<83c0f91b-21ff-2514-d24a-5b6104ef012b@yandex.ru>\n\t<877fgvept2.fsf@gmail.com>\n\t<7c941e57-756c-0081-9e78-fd31db305d7c@yandex.ru>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Dmitry Gutov <dgutov@yandex.ru>, Vitalie Spinu\n\t<spinuvit@gmail.com>, Alan Mackenzie <acm@muc.de>, Stefan Monnier\n\t<monnier@IRO.UMontreal.CA>, emacs-devel <emacs-devel@gnu.org>\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::233\nCc: Alan Mackenzie <acm@muc.de>, Vitalie Spinu <spinuvit@gmail.com>,\n\tStefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:36:55 -0000\nContent-Length: 363\nLines: 11\n\n>>>>> Dmitry Gutov <dgutov@yandex.ru> writes:\n\n> Performance may be a concern, but we don't know that yet. As long as they're\n> not required for correctness, let's not get ahead of ourselves.\n\nYes, much agreed.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_563.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0Pp-0003e1-AM\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:02:41 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:35792)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0Pj-0003dr-MD\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:02:39 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0Pf-0006fG-Rc\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:02:35 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:39788)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0Pf-0006fC-L0\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:02:31 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0Pd-0002Px-E8\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 15:02:29 +0100\nReceived: from 157-52-2-179.cpe.teksavvy.com ([157-52-2-179.cpe.teksavvy.com])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 15:02:29 +0100\nReceived: from monnier by 157-52-2-179.cpe.teksavvy.com with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 15:02:29 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nDate: Mon, 21 Mar 2016 10:02:12 -0400\nMessage-ID: <jwv7fgwx7ln.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 157-52-2-179.cpe.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:S9gDszCUJfBQL87OY07Mi7QRDiU=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:02:39 -0000\nContent-Length: 1452\nLines: 32\n\n> Note that I don't mind FIRST-COLUMN functionality. I think it's harmless and\n> probably useful. I mostly mind the last two arguments of\n> prog-indentation-context.\n\nOK, so you're OK with FIRST-COLUMN.  The last two args are:\n- (START . END), which you actually do want, except you want to store it\n  in hard-widen-limit.  I'm OK with storing it elsewhere.\n- PREVIOUS-CHUNKS.  It can be a string, in which case it's just like your\n  STRING-BEFORE.  So your main issues with it are either that you don't\n  want to allow it to be a function, or that you want to store/pas it in\n  a different way, right?\n\n>> Almost all of them care whether the current line contains }, or `end', or\n>> `else', and so on.\n> Indeed. But this information is trivial to retrieve from STRING-AFTER.\n\nIn the case of SMIE, it would probably not be too difficult to adjust it\nso it can work with STRING-AFTER, tho I definitely wouldn't call it\ntrivial to implement the case of \"END END END aligns with the matching\nouter BEGIN\" which is currently supported (and was default until 24.5 or\nso).\n\nBut I must say that I don't understand why you need this\nSTRING-AFTER thingy.  Isn't that text already right there in the buffer?\n\nE.g. in prog-indentation-context, we do have something equivalent to\nhard-widen-limit and to STRING-BEFORE but we have nothing like\nSTRING-AFTER: the indentation code is expected to get that info by\nlooking at the buffer after point.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_565.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0s5-0002oT-DS\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:31:53 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:44415)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0s2-0002kR-9H\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:31:51 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0ry-0005Fr-4r\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:31:50 -0400\nReceived: from mail-wm0-x22b.google.com ([2a00:1450:400c:c09::22b]:35484)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0rx-0005Fl-Tl\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:31:46 -0400\nReceived: by mail-wm0-x22b.google.com with SMTP id l68so112612882wml.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 07:31:45 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=1z4/LBb88A/rXAAQw97jsGcJs8ue+Db0i/e+WY8f/XY=;\n\tb=jgJch/SqmKVwWNXBObPYAVk7SrPt0rIH7MspSw5R+QgGC4Gl4Z4BJJeYBQ0CRYekHy\n\txmLiioJ40tCEPdWHVgVjmCdx8hn5QyTRlUtWhjttr5C7/AEKLk+NlSxoG6OI2Lcg9Ymg\n\tnaL89ivCYfkZNV6MuvWYbtwBsZXlzcl5gtfA4thrCBN24FJdoJYDuzdX+lYWoK7PROK2\n\tOl0fq6sR9bEuSFzI+XKUUwHTlskiTtXZ9khP17BeKi8qjADE/jsvOgVQnd3uFlWzFzi1\n\t7Zwev8UnKVe0AHZz0Q68l5uNSfMmPni9eVVHZvZg5W2rcfoPFtecSJRASbJrMyYVRMnB\n\tfoBQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=1z4/LBb88A/rXAAQw97jsGcJs8ue+Db0i/e+WY8f/XY=;\n\tb=M7ZyQvmRVgHUfsjNJboIt0CTFl4o9gcYkyejFtKSjybwJTrf3aze1k8k0l67ocHW4K\n\tVYB6/SJNaDy/rTMPzRrJcMqrlx/zGZoxm9psAT2xhgGc1SLtms+Cnxff1tIyq0TOFyzE\n\t+zE92u6GpWIS42pZrdzkzQwDJvObl2MtGRlT8JACD0A/qJEmRmNkNgOGnagHFDmv+91G\n\txp9m5AvPkeYRnpsroP5AS/3ABJtlGj4Vuxz0VIZUZPgS+ZFX+7GGwOnG7SFstoDiEYrx\n\tPltgYgd3MA2cXSPEzdk6o6iY6W9JEe6OMgw3krcGTrWWxzztWeRrdywHCw7yXLd5ftsU\n\tUy9A==\nX-Gm-Message-State: AD7BkJLnpN/tmQMc75/d2FeEPv6KlkTx5oUZ2KwqSV6/HVrVAqax3i+TEUpktQcu2cFT7Q==\nX-Received: by 10.28.9.19 with SMTP id 19mr14937865wmj.87.1458570705225;\n\tMon, 21 Mar 2016 07:31:45 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tls5sm25579675wjb.33.2016.03.21.07.31.44\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 07:31:44 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<jwv7fgwx7ln.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Mon, 21 Mar 2016 15:31:43 +0100\nIn-Reply-To: <jwv7fgwx7ln.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Mon, 21 Mar 2016 10:02:12 -0400\")\nMessage-ID: <87twjzewc0.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22b\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:31:51 -0000\nContent-Length: 2313\nLines: 51\n\n\nI have elaborated on all these in my other long reply. I would just conclude\nhere that because both calculate-indent-function and prog-indentation-context\ntry to solve same problem, they are bound to have overlapping parts. It's just\nthat calculate-indent-function is more general, easier to understand for prog\nauthors and it solves three problems at once - replacement of\nindent-line-function, removing extra prog-indentation-context/prog-widen and not\nexposing multi-mode complexities.\n\nNote also that the intention of the `hard-widen-limit` is to make it work\nseamlessly for all existing code that use widen. While prog-indentation-context\nrequires to teach every mode out there to use prog-widen instead of widen. This\ndoesn't sound right at all.\n\n  Vitalie\n\n\n>> On Mon, Mar 21 2016 10:02, Stefan Monnier wrote:\n\n>> Note that I don't mind FIRST-COLUMN functionality. I think it's harmless and\n>> probably useful. I mostly mind the last two arguments of\n>> prog-indentation-context.\n\n> OK, so you're OK with FIRST-COLUMN.  The last two args are:\n> - (START . END), which you actually do want, except you want to store it\n>   in hard-widen-limit.  I'm OK with storing it elsewhere.\n> - PREVIOUS-CHUNKS.  It can be a string, in which case it's just like your\n>   STRING-BEFORE.  So your main issues with it are either that you don't\n>   want to allow it to be a function, or that you want to store/pas it in\n>   a different way, right?\n\n>>> Almost all of them care whether the current line contains }, or `end', or\n>>> `else', and so on.\n>> Indeed. But this information is trivial to retrieve from STRING-AFTER.\n\n> In the case of SMIE, it would probably not be too difficult to adjust it\n> so it can work with STRING-AFTER, tho I definitely wouldn't call it\n> trivial to implement the case of \"END END END aligns with the matching\n> outer BEGIN\" which is currently supported (and was default until 24.5 or\n> so).\n\n> But I must say that I don't understand why you need this\n> STRING-AFTER thingy.  Isn't that text already right there in the buffer?\n\n> E.g. in prog-indentation-context, we do have something equivalent to\n> hard-widen-limit and to STRING-BEFORE but we have nothing like\n> STRING-AFTER: the indentation code is expected to get that info by\n> looking at the buffer after point.\n\n>         Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_567.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai1Q9-0005Yq-BI\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 11:07:05 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:58196)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai1Q2-0005Y1-FY\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 11:07:03 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai1Px-0005su-DW\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 11:06:58 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:45345)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai1Px-0005sR-6y\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 11:06:53 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai1Pu-0003dH-72\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:06:50 +0100\nReceived: from 76-10-140-188.dsl.teksavvy.com ([76.10.140.188])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 16:06:50 +0100\nReceived: from monnier by 76-10-140-188.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 16:06:50 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nDate: Mon, 21 Mar 2016 11:06:36 -0400\nMessage-ID: <jwv4mbzx4hf.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<jwv7fgwx7ln.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzewc0.fsf@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 76-10-140-188.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:fzK0E/iQu9ABbJjNTk2Eqqj9QAk=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 15:07:03 -0000\nContent-Length: 1416\nLines: 30\n\n> I have elaborated on all these in my other long reply. I would just\n> conclude here that because both calculate-indent-function and\n> prog-indentation-context try to solve same problem, they are bound to\n> have overlapping parts. It's just that calculate-indent-function is\n> more general, easier to understand for prog authors and it solves\n> three problems at once - replacement of indent-line-function, removing\n> extra prog-indentation-context/prog-widen and not exposing\n> multi-mode complexities.\n\nSTRING-BEFORE/STRING-AFTER/PREVIOUS-CHUNKS look like the main complexity\n(from smie.el's point of view, they all seem pretty painful to support).\n\n> Note also that the intention of the `hard-widen-limit` is to make it\n> work seamlessly for all existing code that use widen.  While\n> prog-indentation-context requires to teach every mode out there to use\n> prog-widen instead of widen. This doesn't sound right at all.\n\nThe reason is that your suggestion risks breaking code since it changes\nthe behavior of `widen'.\n\nMaybe the breakage would be extremely limited or even not exist at all,\nin which case the tradeoff is probably worth it.  My gut feeling is that\nit's too risky, but that's just my gut feeling.\n\nNote also that most modes don't bother using widen, and search&replace\nis pretty easy to do.  But if my fear is unfounded, then indeed it's\nbetter to just change `widen' directly.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_569.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai3P1-0005vy-Es\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 13:14:03 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46057)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1ai3Ou-0005va-6Q\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 13:14:02 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1ai3Oq-00056i-1d\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 13:13:56 -0400\nReceived: from mout.kundenserver.de ([212.227.17.13]:52158)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1ai3Op-00056c-Nm\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 13:13:51 -0400\nReceived: from [192.168.178.35] ([77.6.149.198]) by mrelayeu.kundenserver.de\n\t(mreue103) with ESMTPSA (Nemesis) id 0Lo2Ke-1a6nm222Aq-00fwZg for\n\t<emacs-devel@gnu.org>; Mon, 21 Mar 2016 18:13:50 +0100\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: dev@caliopen.local\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<328c7461-62c6-4228-f622-626349613a1d@yandex.ru>\n\t<87fuvkf1gx.fsf@gmail.com>\n\t<jwv7fgwx7ln.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzewc0.fsf@gmail.com>\n\t<jwv4mbzx4hf.fsf-monnier+gmane.emacs.devel@gnu.org>\nFrom: =?UTF-8?Q?Andreas_R=c3=b6hler?= <andreas.roehler@online.de>\nMessage-ID: <56F02C31.9070908@online.de>\nDate: Mon, 21 Mar 2016 18:15:29 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101\n\tIcedove/38.5.0\nMIME-Version: 1.0\nIn-Reply-To: <jwv4mbzx4hf.fsf-monnier+gmane.emacs.devel@gnu.org>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-Provags-ID: V03:K0:tbs06tZ0j+rKL6cpsq3JUW4tcXPgnKd5wooGpjVYjzTrAijHI0m\n\tLYvfV9IYaUGOAvm/X1+ClLwSSr8ZvfYGNUiSb45ww1C5neKJQiKMPWj4b6fCt+a7TMOrwLz\n\thY2DKY5xBd+0CY8eDC3lIo0Z0nERo0ZD0Fe2eGuqcvIAKzrGdOTEYqd4I7VhZWqyi7Dt4/Y\n\to/OUODS/SQMM21lchIxsw==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:vWP+w4Drozw=:OUpUhaQ7WNVfk4aou53Wz2\n\t1IRpXwXiD6Tg0w7Bq5mS/cwdi0w4lan1XGToLHC7G6XZKET5PgYYohv/sXaWMQsagyZO/z5M8\n\tYgnH+diU7KkFINkZ8E3sSKEeu56yD5BxDHa0D8Pc7vhe1q6OZEDbvNnsFzGx10Poo5cJ+4zO4\n\tvvK1QlTgFFT6NXmPDLiA5X3hE3aKUz/a6HwHMc04fNWysYrXBJD5kJ1O81Mpcbt5600caS6iZ\n\tX83yi2/uzDbJZRSMFMKa08oRx7djnvmfzhaPFhly0whaA4m83fr0rzp8zY+nhAEURnsDIHU0H\n\tksIyQYIlU2x+iwSjQeeCKDzGNYpYsFZGhG3gYb8XCfctJ5MuCE/6B8x9eAXB39Hy355N5iLUa\n\tMVFs/mBeSUfnyS9NtvUY/vddKIPNtB3CvoDc2Ox4mIRMe/I2gfFufHgHaAV3Dsm57HtH8Dc4H\n\tew5lz4MBoq4vKLS8wHGTSrcsZRTFuhaNlfLKWnHn6+3LTJ/Cn/etgaCLNK8KFrItI3TXpuMZN\n\tz0Lab1uiucHE9FzC1gUWGFxElQ8o0pw0sP5sNGFVi0qSNA974fGkZK5hAsbUYn9NCS4HRoTdE\n\tKt0L+KOId9Cl6UdB86JP8RfbIC8t/ewhoac+7po4o/HD/+Nsm8Pp5RAlbXi0JCHD5dK/ojrKv\n\txBoIHzd9rhbUTuj/Ry8rfpH99obS4CuJ15yvBOTncvAOQ+sbcxwyMjEt022BkzZstU/g788nD\n\t7t+ItTwrfKwkmBAD\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.13\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 17:14:02 -0000\nContent-Length: 1926\nLines: 54\n\n\n\nOn 21.03.2016 16:06, Stefan Monnier wrote:\n>> I have elaborated on all these in my other long reply. I would just\n>> conclude here that because both calculate-indent-function and\n>> prog-indentation-context try to solve same problem, they are bound to\n>> have overlapping parts. It's just that calculate-indent-function is\n>> more general, easier to understand for prog authors and it solves\n>> three problems at once - replacement of indent-line-function, removing\n>> extra prog-indentation-context/prog-widen and not exposing\n>> multi-mode complexities.\n> STRING-BEFORE/STRING-AFTER/PREVIOUS-CHUNKS look like the main complexity\n> (from smie.el's point of view, they all seem pretty painful to support).\n\nWould expect that.\n\n\n>\n>> Note also that the intention of the `hard-widen-limit` is to make it\n>> work seamlessly for all existing code that use widen.  While\n>> prog-indentation-context requires to teach every mode out there to use\n>> prog-widen instead of widen. This doesn't sound right at all.\n> The reason is that your suggestion risks breaking code since it changes\n> the behavior of `widen'.\n>\n> Maybe the breakage would be extremely limited or even not exist at all,\n> in which case the tradeoff is probably worth it.  My gut feeling is that\n> it's too risky, but that's just my gut feeling.\n>\n> Note also that most modes don't bother using widen, and search&replace\n> is pretty easy to do.  But if my fear is unfounded, then indeed it's\n> better to just change `widen' directly.\n>\n>\n>          Stefan\n>\n>\n\nWhat about if going back and reflect from the starting point, if a well \ndefined result of narrowing would be the best - without changing code in \ncore at all?\n\nWhen it narrows from midst of a sexp, its beginning is missing, okay. \nWhere is the problem?\nIf this wasn't wanted, the narrowing was wrong. No need to fix this from \nEmacs side.\n\nSorry, should I not have understood what's at stake...\n\n\n\n\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_571.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahs5X-0008Di-Ny\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 01:09:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60266)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahs5V-0008D6-99\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 01:09:10 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahs5N-0003ie-Op\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 01:09:04 -0400\nReceived: from mail-wm0-x234.google.com ([2a00:1450:400c:c09::234]:34558)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahs5N-0003iX-Ec\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 01:09:01 -0400\nReceived: by mail-wm0-x234.google.com with SMTP id p65so137523831wmp.1\n\tfor <dev@caliopen.local>; Sun, 20 Mar 2016 22:09:01 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=H8ax2I4DJSSd0bJliegWg+BqVq89NUCVpAJ8O3jP5tU=;\n\tb=hhgHSvRl5EuUxmkKXVdQKF5fOhkRTRH/lFgHUJSx9kip1c0MDDfcGHd6jiRvm5OSMj\n\tA9Qkz0BnbLWeoXaY8fxcNQBOHeESg4KL1iiDW5NUTl9JXMZTVjLre1dmD0rq6or1MHjE\n\tj+axKgq0QlPibuRcJu/Cg5Six/KiyUSKwA7TiQ5NQ4GWhSvxs1xFOwSpp9+Zk4ASyAuT\n\tGvekLh1/7U00/HKEIBH2OFVYFqWBPVLBidnkCasZFQt/MCDC8pUGDYnyT/A4eokgwVEI\n\tAk0+Byjg0QEkRvSIKV/bV0fRz9wvCgZIVyChb0pAmzSXoiKTFf+aznQVjVsmY+wcML0o\n\tVQlg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=H8ax2I4DJSSd0bJliegWg+BqVq89NUCVpAJ8O3jP5tU=;\n\tb=Jm4sfCEnuLyPSVbK0ylpy4uqpBxBKam5Myq28puMswmxg4ZyX5kpH8WsC+TR+ANExS\n\tCpijs62aa0+G4n72nIfsylrbZO2RzwoNP4EbVTNUPiGyYjpNX8OV7RSFjbfawFfjoCba\n\tGXbRpfraBiHYHqliM5DpKh9WWs6n8WB1c7CpxBvkobNYRzokKmmAOZwAf7uhmO+fVdki\n\tW/oX4A9FMZ85SVZFHUD6Vehwn+3Hb0MLxF6p9hVeqFTXOGKGcG1g2qvt14JIDH6ib2T8\n\tbRH8MeJ7UgLKFXo7LQqh7ezi8uZGrPBG2cbBofiYEzxwFsE90qwwgAh1PwxvDcancC9M\n\tqfoQ==\nX-Gm-Message-State: AD7BkJJAYelkA0jkk0O1ZmrxLyMpqIF1z5jmJTUtU1oVwWVQHipARb6rynkvhs5LmF9OOQ==\nX-Received: by 10.28.55.74 with SMTP id e71mr12301089wma.26.1458536940928;\n\tSun, 20 Mar 2016 22:09:00 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\te19sm10676512wmd.1.2016.03.20.22.09.00\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 20 Mar 2016 22:09:00 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: [Patch] hard-widen-limits [was Re: Syntax tables for multiple modes\n\t[was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\nDate: Mon, 21 Mar 2016 06:08:59 +0100\nIn-Reply-To: <87a8lsd4j3.fsf@gmail.com> (Vitalie Spinu's message of \"Mon, 21\n\tMar 2016 02:05:20 +0100\")\nMessage-ID: <87mvpsbeok.fsf_-_@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=\"=-=-=\"\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::234\nCc: Alan Mackenzie <acm@muc.de>, Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 05:09:10 -0000\nContent-Length: 2545\nLines: 84\n\n--=-=-=\nContent-Type: text/plain\n\n\n>> On Mon, Mar 21 2016 02:05, Vitalie Spinu wrote:\n\n>> Are you interested in working on a patch? Also Cc'ing Stefan.\n\n> My knowledge of emacs C internals is close to 0. Elisp side (and probably C\n> side) of this is trivial. I will look into it but I don't think I am the best\n> person for that.\n\nThe widen part turned to be easy.  Will look at parse-partial-sexp tomorrow.\n\n Vitalie\n\n\n--=-=-=\nContent-Type: text/x-diff\nContent-Disposition: inline\n\n>From eafcff9e72499e3bb6dc462d406dd33a885e3d49 Mon Sep 17 00:00:00 2001\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nDate: Mon, 21 Mar 2016 05:41:55 +0100\nSubject: [PATCH] Implement hard-narrowing\n\n `widen` now respects restrictions imposed by new variable `hard-widen-limits`\n---\n src/buffer.c  |  5 +++++\n src/editfns.c | 14 +++++++++++++-\n 2 files changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/src/buffer.c b/src/buffer.c\nindex f06d7e0..5232c49 100644\n--- a/src/buffer.c\n+++ b/src/buffer.c\n@@ -6219,6 +6219,11 @@ and disregard a `read-only' text property if the property value\n is a member of the list.  */);\n   Vinhibit_read_only = Qnil;\n \n+  DEFVAR_LISP (\"hard-widen-limits\", Vhard_widen_limits,\n+\t       doc: /* When non-nil `widen` will widen to these limits.\n+Must be a cons of the form (MIN . MAX) where MIN and MAX are integers or markers.  */);\n+  Vhard_widen_limits = Qnil;\n+\n   DEFVAR_PER_BUFFER (\"cursor-type\", &BVAR (current_buffer, cursor_type), Qnil,\n \t\t     doc: /* Cursor to use when this buffer is in the selected window.\n Values are interpreted as follows:\ndiff --git a/src/editfns.c b/src/editfns.c\nindex 2ac0537..fb1f652 100644\n--- a/src/editfns.c\n+++ b/src/editfns.c\n@@ -3480,12 +3480,24 @@ DEFUN (\"delete-and-extract-region\", Fdelete_and_extract_region,\n     return empty_unibyte_string;\n   return del_range_1 (XINT (start), XINT (end), 1, 1);\n }\n+\n \f\n DEFUN (\"widen\", Fwiden, Swiden, 0, 0, \"\",\n        doc: /* Remove restrictions (narrowing) from current buffer.\n-This allows the buffer's full text to be seen and edited.  */)\n+This allows the buffer's full text to be seen and edited.\n+If `hard-widen-limits` is non-nil, widen only to those limits.  */)\n   (void)\n {\n+\n+  if (! NILP (Vhard_widen_limits))\n+    {\n+      CHECK_CONS(Vhard_widen_limits);\n+      Lisp_Object hbeg = XCAR(Vhard_widen_limits);\n+      Lisp_Object hend = XCDR(Vhard_widen_limits);\n+      Fnarrow_to_region(hbeg, hend);\n+      return Qnil;\n+    }\n+\n   if (BEG != BEGV || Z != ZV)\n     current_buffer->clip_changed = 1;\n   BEGV = BEG;\n-- \n2.5.0\n\n\n--=-=-=--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_573.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahz7R-0000F2-5f\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 08:39:37 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42497)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ahz7O-0000Es-VR\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:39:35 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ahz7K-00049k-0C\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:39:34 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:49218)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ahz7J-00049g-Rw\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:39:29 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0CXDQA731xV/3D5d0tcgxCEAshgBAICgTw8EQEBAQEBAQGBCkEFg10BAQRWIxALNAcLFBgNJIg/zyMBAQEBBgEBAQEeizqFBQeELQWzP4FFI4I7gVkigngBAQE\nX-IPAS-Result: A0CXDQA731xV/3D5d0tcgxCEAshgBAICgTw8EQEBAQEBAQGBCkEFg10BAQRWIxALNAcLFBgNJIg/zyMBAQEBBgEBAQEeizqFBQeELQWzP4FFI4I7gVkigngBAQE\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"197397236\"\nReceived: from 75-119-249-112.dsl.teksavvy.com (HELO pastel.home)\n\t([75.119.249.112])\n\tby ironport2-out.teksavvy.com with ESMTP; 21 Mar 2016 08:39:28 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid BE10E605A0; Mon, 21 Mar 2016 08:39:28 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nMessage-ID: <jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\nDate: Mon, 21 Mar 2016 08:39:28 -0400\nIn-Reply-To: <87mvpsbeok.fsf_-_@gmail.com> (Vitalie Spinu's message of \"Mon,\n\t21 Mar 2016 06:08:59 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nCc: Alan Mackenzie <acm@muc.de>, emacs-devel <dev@caliopen.local>,\n\tDmitry Gutov <dgutov@yandex.ru>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 12:39:35 -0000\nContent-Length: 945\nLines: 25\n\n> +  DEFVAR_LISP (\"hard-widen-limits\", Vhard_widen_limits,\n> +\t       doc: /* When non-nil `widen` will widen to these limits.\n> +Must be a cons of the form (MIN . MAX) where MIN and MAX are integers or markers.  */);\n> +  Vhard_widen_limits = Qnil;\n\nSorry to nitpick, but I'm not completely happy with this API.  As an\nimplementation it might be OK, but I can imagine wanting to change the\nimplementation in the future but being stuck by the exposed internals.\n\nSo I suggest we instead expose only a new primitive\n\"call-with-hard-narrowing\" which could look like:\n\n    (defun call-with-hard-narrowing (from to func)\n      (make-local-variable 'internal--hard-widen-limits)\n      (let ((internal--hard-widen-limits (cons from to)))\n        (funcall func)))\n\nwhich could be supplemented with a corresponding macro\n\n    (defmacro with-hard-narrowing (from to &rest body)\n      `(call-with-hard-narrowing ,from ,to (lambda () ,body)))\n\n\n-- Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_575.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0cB-00067w-Ry\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:15:27 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39919)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0c3-0005y6-PG\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:15:25 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0bx-0001OQ-Td\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:15:19 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:40664)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0bx-0001OC-NG\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:15:13 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0bq-0000Vy-Ao\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 15:15:06 +0100\nReceived: from 157-52-2-179.cpe.teksavvy.com ([157.52.2.179])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 15:15:06 +0100\nReceived: from monnier by 157-52-2-179.cpe.teksavvy.com with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 15:15:06 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nDate: Mon, 21 Mar 2016 10:04:58 -0400\nMessage-ID: <jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 157-52-2-179.cpe.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:aE9OsrP7pHwW4G2EJ9k2vgIBqh8=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:15:25 -0000\nContent-Length: 363\nLines: 11\n\n>     (defun call-with-hard-narrowing (from to func)\n>       (make-local-variable 'internal--hard-widen-limits)\n>       (let ((internal--hard-widen-limits (cons from to)))\n>         (funcall func)))\n\nHmm... I now realize that this won't handle the case of info-mode\nbuffers (and similarly rmail buffers) where the hard-narrowing is not\nscoped.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_577.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0tZ-0003eb-Sk\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:33:25 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:45015)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0tX-0003da-LO\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:33:24 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0tT-0005fU-L0\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:33:23 -0400\nReceived: from mail-wm0-x22c.google.com ([2a00:1450:400c:c09::22c]:38813)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0tT-0005ez-Dp\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:33:19 -0400\nReceived: by mail-wm0-x22c.google.com with SMTP id l68so124361761wml.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 07:33:19 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=Gd6CZDpN6EoXM+Pryda/3qzq76XdDTQN8oMo2ET4PR4=;\n\tb=RGBDbcUHqY1wRPPnNWgXsTtUznhbGM/H/HDN92pPQe8CXl2QTMp1MimgjivnKmkH9S\n\teMgkU6KojMsqTJUA4wucYJckiu+pX4p4mqxQBKepvlF8W5YJ4sbegBiSrBKOrenWtiHg\n\tFAFuV5061m6oV2RKZQm5iPcRy/N70hw39I2HDm+QJKUoxFMyDBP2UY35a66SOfAJ4juf\n\t81TAZg60L6Jhb/88gzFMovXEerkNzN++83CeZShm3AEE/tWdEUtIDB07077JKdWdBUMi\n\tyPVV2HgrTs1imV9JEOWnNU1dqrilNbSGK/KeRXdiU1VuZajJwUOApQpLI1dHEXQR3RxU\n\tGAJQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=Gd6CZDpN6EoXM+Pryda/3qzq76XdDTQN8oMo2ET4PR4=;\n\tb=BodZxTPKXLRbqa/+IZd59XlLR/G+XCQzilP9v5tPTSq0LX4ZW5hEcRjF0Pnd7IV4P0\n\trOXfjXz2TUr64dDWVWtAwwKe5TUlal6WxohT0XmaQCbM3yS+oE/RLsCPWBRyMxtq+7hB\n\tdaOiJy6fZD6Xe7SMfEQAHl6VANKdbszoGtYbFlFYbLcMg/aaAVx3RIUnA1kalGhCUNGQ\n\tuEYW3Hj6i8lHzKtYNpznUdXgyJhK0WDkNHPsD1L1oi6TGLUxiENhAA/sD3gHSlxbaXkn\n\tAsYtNGymfvn3JMVZ9pghXxNnBydAAtMZAx3uLoDzROxAhM3DM6kQ7ReBxk8ESbTV2d/u\n\tBt1Q==\nX-Gm-Message-State: AD7BkJLu+NyLqYBAzepIsK6FYEvagqsArSzbLDiouyKlAcw2ApsXItplh429Dn4ZfQjH6g==\nX-Received: by 10.28.46.5 with SMTP id u5mr14778612wmu.75.1458570798808;\n\tMon, 21 Mar 2016 07:33:18 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\twr2sm25667008wjc.49.2016.03.21.07.33.17\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 07:33:18 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Mon, 21 Mar 2016 15:33:17 +0100\nIn-Reply-To: <jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Mon, 21 Mar 2016 10:04:58 -0400\")\nMessage-ID: <87pounew9e.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22c\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:33:24 -0000\nContent-Length: 461\nLines: 18\n\n\n\n>> On Mon, Mar 21 2016 10:04, Stefan Monnier wrote:\n\n>>     (defun call-with-hard-narrowing (from to func)\n>>       (make-local-variable 'internal--hard-widen-limits)\n>>       (let ((internal--hard-widen-limits (cons from to)))\n>>         (funcall func)))\n\n> Hmm... I now realize that this won't handle the case of info-mode\n> buffers (and similarly rmail buffers) where the hard-narrowing is not\n> scoped.\n\nWhat does this mean in plain English?\n\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_579.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai1JX-0003Oo-KD\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 11:00:15 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52989)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai1JV-0003O3-Bq\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 11:00:14 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai1JQ-0003vr-97\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 11:00:13 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:44684)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai1JQ-0003vJ-32\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 11:00:08 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai1JL-0008LR-7t\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:00:03 +0100\nReceived: from 76-10-140-188.dsl.teksavvy.com ([76.10.140.188])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 16:00:03 +0100\nReceived: from monnier by 76-10-140-188.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 16:00:03 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nDate: Mon, 21 Mar 2016 10:54:58 -0400\nMessage-ID: <jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 76-10-140-188.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:1DGnWiDaKwMdjWpMlM5ZdpMBY54=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 15:00:14 -0000\nContent-Length: 492\nLines: 14\n\n>>> (defun call-with-hard-narrowing (from to func)\n>>> (make-local-variable 'internal--hard-widen-limits)\n>>> (let ((internal--hard-widen-limits (cons from to)))\n>>> (funcall func)))\n>> Hmm... I now realize that this won't handle the case of info-mode\n>> buffers (and similarly rmail buffers) where the hard-narrowing is not\n>> scoped.\n> What does this mean in plain English?\n\nThat it sets narrowing and leaves it there.  So it can't use\ncall-with-hard-narrowing for that.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_581.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai3Rt-0006eZ-7N\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 13:17:01 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47147)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai3Rm-0006e4-SW\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 13:17:00 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai3Rh-0005xI-66\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 13:16:54 -0400\nReceived: from mail-wm0-x229.google.com ([2a00:1450:400c:c09::229]:37869)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai3Rg-0005x7-Vx\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 13:16:49 -0400\nReceived: by mail-wm0-x229.google.com with SMTP id p65so130642800wmp.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 10:16:48 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=kNo70+ApKhgWFdGG7N4nFaMj0AI11Hj3/Aylgq9J5ek=;\n\tb=FwgVDKcVUNUO8d6f/sY6AhjtSiEfroXoonDERApvwv0AG1kAg+V+nxB9RdIan1o0Ni\n\tBcBzhdBTHFre+3XCuGSzCT+MvDuJZNoNAPN1nMTu6lNMyH4cETY4g8odGUmMMU1+McCt\n\tRvqDhm+vrsIJXicqgL2zj9IIAgBOS6q6ICjAqkbe9tAs2fdMWaRI+shAWSB9pkTfBD4V\n\tKsKfkL/YSAkwj5x8GLwWKOJPrMNIktKNC2OoQxZSP6aEygSzQfmK/Kf3zGvnK2tnMrje\n\tC9pkp/BPNP2sZd+zs38LNPaP5ez5Ianks+I8U+/xKTzhzfOsDKL8QoQembmqsmJwFIi1\n\tbxmA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=kNo70+ApKhgWFdGG7N4nFaMj0AI11Hj3/Aylgq9J5ek=;\n\tb=Kp7vJ3+nBwo91dl85QVAkoG5jB8I9JprXrYcpMqwpIyMWO0nhXv6iVujoZR/f4bruC\n\tqmR43Y3W1RcOZH5Bf0WNe2hmnTFEL2kvSUGxmaJDxxlkz6QgC597S6kisVQvXkRD3WAh\n\tVVTaE6xRkltKCetB/tIeYdaT+STZmFWWFRIRd06iJ2o5u0+rP+++Zgw2W3cjv0gBpBd4\n\ts0zlIakp2vfXVZ30JxaZLIXa5zx96cj01ROPd545uIm8Ovj+KkbL8xe30IbM3SxzTJ76\n\t5t5xRcpcm3XbSdH3wjbzlr2pnpjxxf8IDKRfwHAtleABBX0MWkGE1n+jfYyHXhC1i517\n\t/78g==\nX-Gm-Message-State: AD7BkJIV+zBSSPjxhSpQYw3Yf+4u/utoCr8Xj5gVqKlYv2DReQjbK1Nm9uJFjWwmpK8/wQ==\nX-Received: by 10.194.121.136 with SMTP id lk8mr34245947wjb.92.1458580608165; \n\tMon, 21 Mar 2016 10:16:48 -0700 (PDT)\nReceived: from localhost ([143.176.214.220])\n\tby smtp.gmail.com with ESMTPSA id b1sm26264128wjy.0.2016.03.21.10.16.46\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 10:16:47 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Mon, 21 Mar 2016 18:16:46 +0100\nIn-Reply-To: <jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Mon, 21 Mar 2016 10:54:58 -0400\")\nMessage-ID: <87twjzda4h.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::229\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 17:17:00 -0000\nContent-Length: 377\nLines: 15\n\n\n\n>> On Mon, Mar 21 2016 10:54, Stefan Monnier wrote:\n\n> That it sets narrowing and leaves it there.  So it can't use\n> call-with-hard-narrowing for that.\n\nWhy would it need it for?\n\nThis is outside of use cases that I have in mind. with-hard-narrowing should be\nused in limited, transient, prog-only contexts; almost exclusively for advices\nin multi-mode engines.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_583.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai4hI-0007DB-MC\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 14:37:00 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46088)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai4hG-0007Ae-E4\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:36:59 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai4hD-0003Pw-6T\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:36:58 -0400\nReceived: from chene.dit.umontreal.ca ([132.204.246.20]:36567)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai4hD-0003Pq-1k\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:36:55 -0400\nReceived: from pastel.home (lechon.iro.umontreal.ca [132.204.27.242])\n\tby chene.dit.umontreal.ca (8.14.1/8.14.1) with ESMTP id u2LIbNp0026843; \n\tMon, 21 Mar 2016 14:37:23 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid 418D26059C; Mon, 21 Mar 2016 14:36:53 -0400 (EDT)\nFrom: Stefan Monnier <monnier@IRO.UMontreal.CA>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nMessage-ID: <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com>\nDate: Mon, 21 Mar 2016 14:36:53 -0400\nIn-Reply-To: <87twjzda4h.fsf@gmail.com> (Vitalie Spinu's message of \"Mon, 21\n\tMar 2016 18:16:46 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-NAI-Spam-Flag: NO\nX-NAI-Spam-Threshold: 5\nX-NAI-Spam-Score: 0\nX-NAI-Spam-Rules: 1 Rules triggered\n\tRV5617=0\nX-NAI-Spam-Version: 2.3.0.9418 : core <5617> : inlines <4541> : streams\n\t<1606622> : uri <2171520>\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 132.204.246.20\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 18:36:59 -0000\nContent-Length: 725\nLines: 21\n\n>> That it sets narrowing and leaves it there.  So it can't use\n>> call-with-hard-narrowing for that.\n> Why would it need it for?\n\nBecause an info file is made up of various nodes, and Emacs only shows\none node at a time by narrowing.  This narrowing should be \"hard\"\nbecause font-lock and such should only operate on a node at a time.\n\nThe same was true for Rmail which used to show the content of each email\nsimply by narrowing the mailbox file to the specific email.  Not sure if\nit still does that (clearly it can't be so simple with MIME's base64\nand attachments).\n\n> This is outside of use cases that I have in mind.\n\nIndeed, it's a different case, but one where the narrowing should be\nhard as well.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_585.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai5LG-0003L8-TE\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 15:18:18 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56299)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai5L9-0003Fj-TO\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 15:18:17 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai5L4-0005Wp-Ux\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 15:18:11 -0400\nReceived: from mail-wm0-x22b.google.com ([2a00:1450:400c:c09::22b]:33393)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai5L4-0005Wi-OC\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 15:18:06 -0400\nReceived: by mail-wm0-x22b.google.com with SMTP id l68so164361797wml.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 12:18:06 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=qJjKOMM2Kd4xONmmBGN4ewMAW4JNmv6b65U6k6t5UCE=;\n\tb=P7cEGsMfmyAHNTi4e7Ns3cxhdxDq2/uNa5+tBVV9t/IATKWXZMKD65ClQUnOM8Z8+h\n\tzFPR5xropLlEM4s3N/FqdnpJ3KPH4Iy4TJwyCJS7kEoChp14WisIbra6lMp/tZwR5yuH\n\t6mQkD8ddwfXKOyCtU0ZuGPzJnboqQTP8uL+w/53TuvHP1TmW7LzMLNHAupUJiKPmFLSe\n\tBDDAoQjNVPqi61WBhwiDBtCpW1G80FW8ogOOR0Sne03T+Q+K0991k3D2YgUyNOYbarAa\n\t8d6NQ4mtPscIyNHAELmbBKf/HmHkdZSS3neBP2d+Riekj+msnYoJDW5aRq4piKuowZ3W\n\tVWCQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=qJjKOMM2Kd4xONmmBGN4ewMAW4JNmv6b65U6k6t5UCE=;\n\tb=R5F0AkXa43iMbyF09y62wOzufXLjfygx2YW11CTlaVBsZ04/pNYFt2/7VBtiDTkyVo\n\tdOBFNGahez/yvIqpF8yyQxjIGJ3Hy9726fCq26CqJ/7kh6DT4SfhTQg24taYmgcg9Gpf\n\tAtjlJ7rz3iYWZ/TFA2fU9zrF+79ZP8H4xned5F7YETCIZVrlgYdNMe8tje/BF0oOj0J1\n\tE73fPaYzS0drYUDArmdMFTvaiLQ8P3mPB7y9xksDSFXAIjuQdjmMkEKs2pa5WaQxjc4w\n\tx8uzkYoDitXU6mNJjjb5o1EPAFExrDcZwUYS+bbSGEyOumYe4Ft94uyERp2gmajTGuwM\n\tAAqw==\nX-Gm-Message-State: AD7BkJJoErXq327WhHLR46k6CoGyCs0WESYN7DhZKgAhK0lycSAJoSK7CFBlYe5C4P5Oug==\nX-Received: by 10.194.223.104 with SMTP id qt8mr32187665wjc.11.1458587886182; \n\tMon, 21 Mar 2016 12:18:06 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tt82sm7424105wmt.17.2016.03.21.12.18.04\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 12:18:05 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@IRO.UMontreal.CA>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\nDate: Mon, 21 Mar 2016 20:18:04 +0100\nIn-Reply-To: <jwvwpovwuip.fsf-monnier+Inbox@gnu.org> (Stefan Monnier's message\n\tof \"Mon, 21 Mar 2016 14:36:53 -0400\")\nMessage-ID: <87lh5bd4ib.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22b\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 19:18:18 -0000\nContent-Length: 290\nLines: 13\n\n\n\n>> On Mon, Mar 21 2016 14:36, Stefan Monnier wrote:\n\n>> This is outside of use cases that I have in mind.\n\n> Indeed, it's a different case, but one where the narrowing should be\n> hard as well.\n\nOk. This part is trickier but it might not be that hard. Will keep this in mind.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_587.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiCpe-0004U4-RN\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 23:18:10 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59137)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiCpa-0004PI-Pl\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 23:18:08 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiCpW-0001iB-K8\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 23:18:06 -0400\nReceived: from mail-wm0-x22c.google.com ([2a00:1450:400c:c09::22c]:37435)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiCpW-0001i6-9l\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 23:18:02 -0400\nReceived: by mail-wm0-x22c.google.com with SMTP id p65so145295305wmp.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 20:18:01 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=rkYOKtxVuSa7D59Le74QMDClpO2V5zVZrkR7H97wIzM=;\n\tb=tV936Laxv4ahsQlXKvMQmPbt92Kaduq1OeDRoCO8DNnykcHu9jYl5BBIRTe2NULSiT\n\tol8rAIFR97ErUf9S5LrsyS+Fwjt6Q1YjSukXXT0Njmo6kbcP9PoGm/MA5RE2C+MevnsX\n\tbZK7XnyEvdqi3CqWf9luMn8+7bWnX5LU/jzXuCDHHb6jV92m47immsPfh79AHKFpBfQv\n\tbmTxvHrMARczqJ4jn+/1i+droZ/DCNSuIEB8XG9cIxS0nKnD4Xdeo/UV6UhGcvC3WXx7\n\t5jlzKmOzpPwxnQUHhZUBLii3p/iJnbeOfiMvc4Ql+5nmzdicggI39920yUpOpFKsoCSF\n\tkfaw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=rkYOKtxVuSa7D59Le74QMDClpO2V5zVZrkR7H97wIzM=;\n\tb=A5p7xSd+OAt+NjGSYsNL8ZWfeIDgYru933Er1KBHvw3TYq7UImtvbhBM+dsWutu5qd\n\tQPlKjdrZ+GXY7TjcvCDYHgrV5QOFAc/CrwjTuHiS1D9v6IMU4G8e86nZQicK2lebqiRk\n\tGO9xpWGl4HiRyGE+b8n2s/unZ9av0+yLN2aIDIx09yQO+rNU4d1/NA87M9VcMvQS9HMd\n\tg3VY1+lbXqIx+RMhm9fVTVu5wz/axO93DhjJsgARmMLtwESRC/PcjY8zDo8PxwcMboyH\n\tQaGO4iInv4D4Rd9og16drQvINodrDtVyZbw2rahK1QttdhEIMdHzxCGf/UU76Mbj99DU\n\tsWJg==\nX-Gm-Message-State: AD7BkJJr6+orqzjvwD8Ya6mcDq9iFki1xeKOnojXizoB/p84BPX5MQ/jJK6m8a8DtCIqFg==\nX-Received: by 10.194.92.68 with SMTP id ck4mr33046239wjb.144.1458616681340;\n\tMon, 21 Mar 2016 20:18:01 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tt3sm28008423wjz.11.2016.03.21.20.17.59\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 20:17:59 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@IRO.UMontreal.CA>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com>\nDate: Tue, 22 Mar 2016 04:17:59 +0100\nIn-Reply-To: <87lh5bd4ib.fsf@gmail.com> (Vitalie Spinu's message of \"Mon, 21\n\tMar 2016 20:18:04 +0100\")\nMessage-ID: <87egb3ryjc.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=\"=-=-=\"\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22c\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 03:18:08 -0000\nContent-Length: 6582\nLines: 188\n\n--=-=-=\nContent-Type: text/plain\n\n\n\n>> On Mon, Mar 21 2016 20:18, Vitalie Spinu wrote:\n\n>>> This is outside of use cases that I have in mind.\n\n>> Indeed, it's a different case, but one where the narrowing should be\n>> hard as well.\n\n> Ok. This part is trickier but it might not be that hard. Will keep this in mind.\n\nI have pushed the proposed change to `widen-limits` branch. The C level\nconsequences are fairly innocuous. There are only 3 instances of calls to Fwiden\nin the whole emacs.\n\nGiven that there seem to be use cases of permanent limiting I kept it as buffer\nlocal variable. I also switched to milder name buffer-widen-limits.\n\n  Vitalie\n\n\n\n--=-=-=\nContent-Type: text/x-diff\nContent-Disposition: inline\n\n3 files changed, 48 insertions(+), 3 deletions(-)\nsrc/buffer.c  | 19 +++++++++++++++++--\nsrc/buffer.h  | 17 +++++++++++++++++\nsrc/editfns.c | 15 ++++++++++++++-\n\nmodified   src/buffer.c\n@@ -329,6 +329,11 @@ bset_scroll_up_aggressively (struct buffer *b, Lisp_Object val)\n   b->scroll_up_aggressively_ = val;\n }\n static void\n+bset_widen_limits (struct buffer *b, Lisp_Object val)\n+{\n+  b->widen_limits_ = val;\n+}\n+static void\n bset_selective_display (struct buffer *b, Lisp_Object val)\n {\n   b->selective_display_ = val;\n@@ -847,6 +852,7 @@ CLONE nil means the indirect buffer's state is reset to default values.  */)\n       bset_display_count (b, make_number (0));\n       bset_backed_up (b, Qnil);\n       bset_auto_save_file_name (b, Qnil);\n+      bset_widen_limits (b, b->base_buffer->widen_limits_);\n       set_buffer_internal_1 (b);\n       Fset (intern (\"buffer-save-without-query\"), Qnil);\n       Fset (intern (\"buffer-file-number\"), Qnil);\n@@ -961,6 +967,7 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too)\n      things that depend on the major mode.\n      default-major-mode is handled at a higher level.\n      We ignore it here.  */\n+  bset_widen_limits(b, Qnil);\n   bset_major_mode (b, Qfundamental_mode);\n   bset_keymap (b, Qnil);\n   bset_mode_name (b, QSFundamental);\n@@ -2167,7 +2174,7 @@ so the buffer is truly empty after this.  */)\n {\n   Fwiden ();\n \n-  del_range (BEG, Z);\n+  del_range (BEGWL, ZWL);\n \n   current_buffer->last_window_start = 1;\n   /* Prevent warnings, or suspension of auto saving, that would happen\n@@ -5037,6 +5044,7 @@ init_buffer_once (void)\n   bset_display_count (&buffer_local_flags, make_number (-1));\n   bset_display_time (&buffer_local_flags, make_number (-1));\n   bset_enable_multibyte_characters (&buffer_local_flags, make_number (-1));\n+  bset_widen_limits (&buffer_local_flags, make_number (-1));\n \n   /* These used to be stuck at 0 by default, but now that the all-zero value\n      means Qnil, we have to initialize them explicitly.  */\n@@ -5160,6 +5168,7 @@ init_buffer_once (void)\n   bset_cursor_type (&buffer_defaults, Qt);\n   bset_extra_line_spacing (&buffer_defaults, Qnil);\n   bset_cursor_in_non_selected_windows (&buffer_defaults, Qt);\n+  bset_widen_limits (&buffer_defaults, Qnil);\n \n   bset_enable_multibyte_characters (&buffer_defaults, Qt);\n   bset_buffer_file_coding_system (&buffer_defaults, Qnil);\n@@ -5367,7 +5376,6 @@ defvar_per_buffer (struct Lisp_Buffer_Objfwd *bo_fwd, const char *namestring,\n     emacs_abort ();\n }\n \n-\n /* Initialize the buffer routines.  */\n void\n syms_of_buffer (void)\n@@ -5796,6 +5804,13 @@ If you set this to -2, that means don't turn off auto-saving in this buffer\n if its text size shrinks.   If you use `buffer-swap-text' on a buffer,\n you probably should set this to -2 in that buffer.  */);\n \n+  DEFVAR_PER_BUFFER (\"buffer-widen-limits\", &BVAR (current_buffer, widen_limits),\n+                     Qnil,\n+                     doc: /* When non-nil `widen` will widen to these limits.\n+Must be a cons of the form (MIN . MAX) where MIN and MAX are integers\n+of hard widen limits in this buffer. This is an experimental variable\n+intended primarily for multi-mode engines.  */);\n+\n   DEFVAR_PER_BUFFER (\"selective-display\", &BVAR (current_buffer, selective_display),\n \t\t     Qnil,\n \t\t     doc: /* Non-nil enables selective display.\nmodified   src/buffer.h\n@@ -59,6 +59,10 @@ INLINE_HEADER_BEGIN\n #define Z (current_buffer->text->z)\n #define Z_BYTE (current_buffer->text->z_byte)\n \n+/* Positions that take into account widen limits.  */\n+#define BEGWL (BUF_BEGWL (current_buffer))\n+#define ZWL (BUF_ZWL(current_buffer))\n+\n /* Macros for the addresses of places in the buffer.  */\n \n /* Address of beginning of buffer.  */\n@@ -128,6 +132,15 @@ INLINE_HEADER_BEGIN\n     : NILP (BVAR (buf, begv_marker)) ? buf->begv_byte\t\\\n     : marker_byte_position (BVAR (buf, begv_marker)))\n \n+/* Hard positions in buffer. */\n+#define BUF_BEGWL(buf)  \t                                \\\n+  ((NILP (BVAR (buf, widen_limits))) ?  BUF_BEG (buf)    \\\n+   : XINT( XCAR (BVAR (buf, widen_limits))))\n+\n+#define BUF_ZWL(buf)  \t                                \\\n+  ((NILP (BVAR (buf, widen_limits))) ?  BUF_Z (buf)      \\\n+   : XINT( XCDR (BVAR (buf, widen_limits))))\n+\n /* Position of point in buffer.  */\n #define BUF_PT(buf)\t\t\t\t\t\\\n    (buf == current_buffer ? PT\t\t\t\t\\\n@@ -150,6 +163,7 @@ INLINE_HEADER_BEGIN\n     : NILP (BVAR (buf, zv_marker)) ? buf->zv_byte\t\\\n     : marker_byte_position (BVAR (buf, zv_marker)))\n \n+\n /* Position of gap in buffer.  */\n #define BUF_GPT(buf) ((buf)->text->gpt)\n #define BUF_GPT_BYTE(buf) ((buf)->text->gpt_byte)\n@@ -748,6 +762,9 @@ struct buffer\n      See `cursor-type' for other values.  */\n   Lisp_Object cursor_in_non_selected_windows_;\n \n+  /* Cons of hard widen limits */\n+  Lisp_Object widen_limits_;\n+\n   /* No more Lisp_Object beyond this point.  Except undo_list,\n      which is handled specially in Fgarbage_collect.  */\n \nmodified   src/editfns.c\n@@ -3480,12 +3480,25 @@ DEFUN (\"delete-and-extract-region\", Fdelete_and_extract_region,\n     return empty_unibyte_string;\n   return del_range_1 (XINT (start), XINT (end), 1, 1);\n }\n+\n \f\n DEFUN (\"widen\", Fwiden, Swiden, 0, 0, \"\",\n        doc: /* Remove restrictions (narrowing) from current buffer.\n-This allows the buffer's full text to be seen and edited.  */)\n+This allows the buffer's full text to be seen and edited.\n+If `buffer-widen-limits` is non-nil, widen only to those limits.  */)\n   (void)\n {\n+\n+  if (!NILP (BVAR(current_buffer, widen_limits)))\n+    {\n+      Lisp_Object hl = BVAR(current_buffer,  widen_limits);\n+      CHECK_CONS(hl);\n+      CHECK_NUMBER(XCAR(hl));\n+      CHECK_NUMBER(XCDR(hl));\n+      Fnarrow_to_region(XCAR(hl), XCDR(hl));\n+      return Qnil;\n+    }\n+\n   if (BEG != BEGV || Z != ZV)\n     current_buffer->clip_changed = 1;\n   BEGV = BEG;\n\n[back]\n--=-=-=--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_589.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiJ49-00059r-CQ\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 05:57:33 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:51464)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiJ46-000593-GW\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 05:57:32 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiJ43-0006SA-98\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 05:57:30 -0400\nReceived: from mail-wm0-x236.google.com ([2a00:1450:400c:c09::236]:36590)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiJ42-0006S3-U5\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 05:57:27 -0400\nReceived: by mail-wm0-x236.google.com with SMTP id r129so84542869wmr.1\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 02:57:26 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=NKPqWOeFjPop+SHNsIpA5N4IVZcZMO11Gp6pcF/zIHA=;\n\tb=l33KzV9sdivsdvLO7dwveJCalAwZ/nKP5m3Zzf5IJ7wufx89YaEBGUV678gXmdzTyS\n\tnmIF8O4fPQFNZJRvd8I0/pJQpohnnhLoM5gx4P3lfL4BiXJ+u2ocO2P1vtdUhn7Vcf1W\n\tSsIen5az/KJv/TvzgJRXrsXcxQQf/qHCKwBMj8CIME5ZKC0RzO69tpqaOs+fGXecyTwh\n\tIEeeJntS5L+l6uwF53Ne38tNzjBFmaFB/BT7dBIMwRHziXCHMG/vJEUTYwUxSWfGAvG+\n\tQ7+YtfC+r3oCC9k8GuuuKfHQ2wmwZ8BxdwgAbRRR7/BhqJUDGTOSaAVMIGD14NomjFvE\n\t69Gw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=NKPqWOeFjPop+SHNsIpA5N4IVZcZMO11Gp6pcF/zIHA=;\n\tb=KV2jKce9W9k4SetTSfP5UKgeyIVG31YoBTBfFkLbU+/Hy4WX253abJPWTQ7qTvDOa1\n\t3dtKEj2PUCHVfqkLk7yDsVYWV/cPfc6ov3qo2ippatLxPKe+cpun0LmIC5i87/j6G6de\n\tJbRzaH914YPgHSD5KDwjMvxgcBcZ7bsxS/9wjZ6Ltlgi9bqJ3d5m+tLjUB2f6V8zIzvQ\n\tuNH3/VOAF9NDFH6RWmP1lfyEX9LQWthFH7DvpOZGojAnllr0ZTR/V8YZpfEoMrU7SlFA\n\t6GQjLSAHIrPGz5crhReLi0Q8pPUcNHS2JIrwIBKbJFHUIg8wjiLyPrcufmiFsDhHuNO6\n\ttrjw==\nX-Gm-Message-State: AD7BkJKY2Siu30MEugBs6/JcmGGoJhN3zVgPqmV7SYKinAxVk+6sSk8coPWUEcmTCADPmw==\nX-Received: by 10.28.128.80 with SMTP id b77mr19928756wmd.42.1458640646128;\n\tTue, 22 Mar 2016 02:57:26 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tx203sm16436531wmg.23.2016.03.22.02.57.24\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tTue, 22 Mar 2016 02:57:24 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@IRO.UMontreal.CA>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\nDate: Tue, 22 Mar 2016 10:57:24 +0100\nIn-Reply-To: <87egb3ryjc.fsf@gmail.com> (Vitalie Spinu's message of \"Tue, 22\n\tMar 2016 04:17:59 +0100\")\nMessage-ID: <877fgusum3.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::236\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 09:57:32 -0000\nContent-Length: 7651\nLines: 198\n\n\nI think having a local variable for this is not the best idea.\n\nIn proposed implementation you first set buffer-widen-limits, but the effect of\nit will show only on the next invocation of widen. So the consumer will need to\nset this variable and always follow it by widen. If widen is not called, one can\naccess outside regions. Particularly `narrow` doesn't know about hard limits, so\nyou can narrow to outside those limits.\n\nI think a better implementation would be to have `set-widen-limits` function\nwhich would set the limits and narrow the region taking into account current\nnarrowing. The supporting `with-widen-limits` macro will call `set-widen-limits`\nin unwind-protect.\n\nI guess this comes very close to the implementation that you suggested earlier\nfor hiding the internals.\n\n  Vitalie\n\n>> On Tue, Mar 22 2016 04:17, Vitalie Spinu wrote:\n\n>>> On Mon, Mar 21 2016 20:18, Vitalie Spinu wrote:\n\n>>>> This is outside of use cases that I have in mind.\n\n>>> Indeed, it's a different case, but one where the narrowing should be\n>>> hard as well.\n\n>> Ok. This part is trickier but it might not be that hard. Will keep this in mind.\n\n> I have pushed the proposed change to `widen-limits` branch. The C level\n> consequences are fairly innocuous. There are only 3 instances of calls to Fwiden\n> in the whole emacs.\n\n> Given that there seem to be use cases of permanent limiting I kept it as buffer\n> local variable. I also switched to milder name buffer-widen-limits.\n\n>   Vitalie\n\n\n> 3 files changed, 48 insertions(+), 3 deletions(-)\n> src/buffer.c  | 19 +++++++++++++++++--\n> src/buffer.h  | 17 +++++++++++++++++\n> src/editfns.c | 15 ++++++++++++++-\n\n> modified   src/buffer.c\n> @@ -329,6 +329,11 @@ bset_scroll_up_aggressively (struct buffer *b, Lisp_Object val)\n>    b->scroll_up_aggressively_ = val;\n>  }\n>  static void\n> +bset_widen_limits (struct buffer *b, Lisp_Object val)\n> +{\n> +  b->widen_limits_ = val;\n> +}\n> +static void\n>  bset_selective_display (struct buffer *b, Lisp_Object val)\n>  {\n>    b->selective_display_ = val;\n> @@ -847,6 +852,7 @@ CLONE nil means the indirect buffer's state is reset to default values.  */)\n>        bset_display_count (b, make_number (0));\n>        bset_backed_up (b, Qnil);\n>        bset_auto_save_file_name (b, Qnil);\n> +      bset_widen_limits (b, b->base_buffer->widen_limits_);\n>        set_buffer_internal_1 (b);\n>        Fset (intern (\"buffer-save-without-query\"), Qnil);\n>        Fset (intern (\"buffer-file-number\"), Qnil);\n> @@ -961,6 +967,7 @@ reset_buffer_local_variables (struct buffer *b, bool permanent_too)\n>       things that depend on the major mode.\n>       default-major-mode is handled at a higher level.\n>       We ignore it here.  */\n> +  bset_widen_limits(b, Qnil);\n>    bset_major_mode (b, Qfundamental_mode);\n>    bset_keymap (b, Qnil);\n>    bset_mode_name (b, QSFundamental);\n> @@ -2167,7 +2174,7 @@ so the buffer is truly empty after this.  */)\n>  {\n>    Fwiden ();\n>  \n> -  del_range (BEG, Z);\n> +  del_range (BEGWL, ZWL);\n>  \n>    current_buffer->last_window_start = 1;\n>    /* Prevent warnings, or suspension of auto saving, that would happen\n> @@ -5037,6 +5044,7 @@ init_buffer_once (void)\n>    bset_display_count (&buffer_local_flags, make_number (-1));\n>    bset_display_time (&buffer_local_flags, make_number (-1));\n>    bset_enable_multibyte_characters (&buffer_local_flags, make_number (-1));\n> +  bset_widen_limits (&buffer_local_flags, make_number (-1));\n>  \n>    /* These used to be stuck at 0 by default, but now that the all-zero value\n>       means Qnil, we have to initialize them explicitly.  */\n> @@ -5160,6 +5168,7 @@ init_buffer_once (void)\n>    bset_cursor_type (&buffer_defaults, Qt);\n>    bset_extra_line_spacing (&buffer_defaults, Qnil);\n>    bset_cursor_in_non_selected_windows (&buffer_defaults, Qt);\n> +  bset_widen_limits (&buffer_defaults, Qnil);\n>  \n>    bset_enable_multibyte_characters (&buffer_defaults, Qt);\n>    bset_buffer_file_coding_system (&buffer_defaults, Qnil);\n> @@ -5367,7 +5376,6 @@ defvar_per_buffer (struct Lisp_Buffer_Objfwd *bo_fwd, const char *namestring,\n>      emacs_abort ();\n>  }\n>  \n> -\n>  /* Initialize the buffer routines.  */\n>  void\n>  syms_of_buffer (void)\n> @@ -5796,6 +5804,13 @@ If you set this to -2, that means don't turn off auto-saving in this buffer\n>  if its text size shrinks.   If you use `buffer-swap-text' on a buffer,\n>  you probably should set this to -2 in that buffer.  */);\n>  \n> +  DEFVAR_PER_BUFFER (\"buffer-widen-limits\", &BVAR (current_buffer, widen_limits),\n> +                     Qnil,\n> +                     doc: /* When non-nil `widen` will widen to these limits.\n> +Must be a cons of the form (MIN . MAX) where MIN and MAX are integers\n> +of hard widen limits in this buffer. This is an experimental variable\n> +intended primarily for multi-mode engines.  */);\n> +\n>    DEFVAR_PER_BUFFER (\"selective-display\", &BVAR (current_buffer, selective_display),\n>  \t\t     Qnil,\n>  \t\t     doc: /* Non-nil enables selective display.\n> modified   src/buffer.h\n> @@ -59,6 +59,10 @@ INLINE_HEADER_BEGIN\n>  #define Z (current_buffer->text->z)\n>  #define Z_BYTE (current_buffer->text->z_byte)\n>  \n> +/* Positions that take into account widen limits.  */\n> +#define BEGWL (BUF_BEGWL (current_buffer))\n> +#define ZWL (BUF_ZWL(current_buffer))\n> +\n>  /* Macros for the addresses of places in the buffer.  */\n>  \n>  /* Address of beginning of buffer.  */\n> @@ -128,6 +132,15 @@ INLINE_HEADER_BEGIN\n>      : NILP (BVAR (buf, begv_marker)) ? buf->begv_byte\t\\\n>      : marker_byte_position (BVAR (buf, begv_marker)))\n>  \n> +/* Hard positions in buffer. */\n> +#define BUF_BEGWL(buf)  \t                                \\\n> +  ((NILP (BVAR (buf, widen_limits))) ?  BUF_BEG (buf)    \\\n> +   : XINT( XCAR (BVAR (buf, widen_limits))))\n> +\n> +#define BUF_ZWL(buf)  \t                                \\\n> +  ((NILP (BVAR (buf, widen_limits))) ?  BUF_Z (buf)      \\\n> +   : XINT( XCDR (BVAR (buf, widen_limits))))\n> +\n>  /* Position of point in buffer.  */\n>  #define BUF_PT(buf)\t\t\t\t\t\\\n>     (buf == current_buffer ? PT\t\t\t\t\\\n> @@ -150,6 +163,7 @@ INLINE_HEADER_BEGIN\n>      : NILP (BVAR (buf, zv_marker)) ? buf->zv_byte\t\\\n>      : marker_byte_position (BVAR (buf, zv_marker)))\n>  \n> +\n>  /* Position of gap in buffer.  */\n>  #define BUF_GPT(buf) ((buf)->text->gpt)\n>  #define BUF_GPT_BYTE(buf) ((buf)->text->gpt_byte)\n> @@ -748,6 +762,9 @@ struct buffer\n>       See `cursor-type' for other values.  */\n>    Lisp_Object cursor_in_non_selected_windows_;\n>  \n> +  /* Cons of hard widen limits */\n> +  Lisp_Object widen_limits_;\n> +\n>    /* No more Lisp_Object beyond this point.  Except undo_list,\n>       which is handled specially in Fgarbage_collect.  */\n>  \n> modified   src/editfns.c\n> @@ -3480,12 +3480,25 @@ DEFUN (\"delete-and-extract-region\", Fdelete_and_extract_region,\n>      return empty_unibyte_string;\n>    return del_range_1 (XINT (start), XINT (end), 1, 1);\n>  }\n> +\n>  \f\n>  DEFUN (\"widen\", Fwiden, Swiden, 0, 0, \"\",\n>         doc: /* Remove restrictions (narrowing) from current buffer.\n> -This allows the buffer's full text to be seen and edited.  */)\n> +This allows the buffer's full text to be seen and edited.\n> +If `buffer-widen-limits` is non-nil, widen only to those limits.  */)\n>    (void)\n>  {\n> +\n> +  if (!NILP (BVAR(current_buffer, widen_limits)))\n> +    {\n> +      Lisp_Object hl = BVAR(current_buffer,  widen_limits);\n> +      CHECK_CONS(hl);\n> +      CHECK_NUMBER(XCAR(hl));\n> +      CHECK_NUMBER(XCDR(hl));\n> +      Fnarrow_to_region(XCAR(hl), XCDR(hl));\n> +      return Qnil;\n> +    }\n> +\n>    if (BEG != BEGV || Z != ZV)\n>      current_buffer->clip_changed = 1;\n>    BEGV = BEG;\n\n> [back]\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_591.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiJCA-0007Ft-Mu\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 06:05:50 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:53279)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiJC5-0007FM-LA\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 06:05:49 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiJC0-00087g-Hk\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 06:05:45 -0400\nReceived: from mail-wm0-x231.google.com ([2a00:1450:400c:c09::231]:37065)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiJC0-00087a-4N\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 06:05:40 -0400\nReceived: by mail-wm0-x231.google.com with SMTP id p65so155650292wmp.0\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 03:05:39 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=jMwn+x/GZPb+qModrbtXpgNcaDax5nbzbmiXj/PzuEk=;\n\tb=ui/pOZi6Q6wkZnvUHUDAWf9NZtD2IOSDqIwckfpuG8gBzTI95R3JAd3+MwUCIOu2QI\n\tjHYmWXfAHrTwJNbzfGCdA0194X7n481fdM/3QfHYUiW0CdzGoZsX4EJ1UDNSVUl7ajwu\n\tpwhNeTkrgOtCJgshlwz16Wzj71hEFe3tmqnNhhMlnnrdO6mT3bMcTWJQTTQx730QAahc\n\tjQggnjHPSFVoJ+oLGT783KUu+igTHtrWqZOt4+egVMda20c3fdQbfGGtHTXYu51Piw16\n\tlcpv6fXij1/qVtzGBe/K95E1YvsSamuKgfyHGD8G0EsjekXpdAGesALteFZmj2sjFvMr\n\tzJuw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=jMwn+x/GZPb+qModrbtXpgNcaDax5nbzbmiXj/PzuEk=;\n\tb=KyjZUzbYpbpKTjKmCjNRRYSzB+ifiYPKGU9/KdVnpi1is/vG7pXs8QaBTsArb2NZf4\n\t9XvD6Vvsuhfvap0DkM/xAwiWM3AYTd5vf5O1FwVLLwKxNwR9WiqtHEaicPxtj+Z1b3mR\n\tykCEK4enKiTUu6tao8xwF4leLVc2MY7i/ykyhvgfjVPJ98YRy8tl7RUjrwHxVEpiyt8f\n\tLv97jzPki+QNT2ANtjqYd43/6GvlI6ipmxkPVxenahcUwKjF5QkbV0oWo5SyJW72+ZPb\n\t2aySnQeKRjj9acCrko/+BovSKn2JkDk7njF59CGrFRqAFHJXyxpfs2vir8arPGct8cDL\n\tltcg==\nX-Gm-Message-State: AD7BkJIdL105gwhwPo+OolZUpPNtG3nuAK2fCbqfUM8OUJIY11JIloBAOvThnYa8gKmhXQ==\nX-Received: by 10.194.60.165 with SMTP id i5mr40069705wjr.178.1458641139283;\n\tTue, 22 Mar 2016 03:05:39 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tl135sm16602139wmb.13.2016.03.22.03.05.38\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tTue, 22 Mar 2016 03:05:38 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@IRO.UMontreal.CA>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com>\nDate: Tue, 22 Mar 2016 11:05:38 +0100\nIn-Reply-To: <877fgusum3.fsf@gmail.com> (Vitalie Spinu's message of \"Tue, 22\n\tMar 2016 10:57:24 +0100\")\nMessage-ID: <8737risu8d.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::231\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 10:05:49 -0000\nContent-Length: 413\nLines: 16\n\n\n>> On Tue, Mar 22 2016 10:57, Vitalie Spinu wrote:\n\n> So the consumer will need to set this variable and always follow it by widen.\n\nHm. This also implies that each consumer will need to take care of current\nnarrowing and re-narrow to new limits. This doesn't sound right.\n\nI am also not sure what the behavior of save-restriction should be. Should\nsave-restriction unwind hard limits as well?\n\n  Vitalie\n\n\n   \n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_593.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiKwK-0006zz-Mz\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 07:57:36 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:45404)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aiKwI-0006z4-FR\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 07:57:35 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aiKwE-0007xJ-FG\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 07:57:34 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:19090)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aiKwE-0007wS-BI\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 07:57:30 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0CVDQA731xV/5a2xEVcgxCEAsEVh0sEAgKBPDsSAQEBAQEBAYEKQQWDXQEBAwFWIxALNAcLFBgNJIg3CM8jAQEBAQYBAQEBHos6hQUHhC0FnxeSFIIUgUUjgjuBWSKCeAEBAQ\nX-IPAS-Result: A0CVDQA731xV/5a2xEVcgxCEAsEVh0sEAgKBPDsSAQEBAQEBAYEKQQWDXQEBAwFWIxALNAcLFBgNJIg3CM8jAQEBAQYBAQEBHos6hQUHhC0FnxeSFIIUgUUjgjuBWSKCeAEBAQ\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"197703001\"\nReceived: from 69-196-182-150.dsl.teksavvy.com (HELO pastel.home)\n\t([69.196.182.150])\n\tby ironport2-out.teksavvy.com with ESMTP; 22 Mar 2016 07:57:27 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid 2ECE064302; Tue, 22 Mar 2016 07:57:27 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nMessage-ID: <jwvbn66viar.fsf-monnier+Inbox@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\nDate: Tue, 22 Mar 2016 07:57:27 -0400\nIn-Reply-To: <8737risu8d.fsf@gmail.com> (Vitalie Spinu's message of \"Tue, 22\n\tMar 2016 11:05:38 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 11:57:35 -0000\nContent-Length: 574\nLines: 13\n\n>> So the consumer will need to set this variable and always follow it by widen.\n> Hm. This also implies that each consumer will need to take care of current\n> narrowing and re-narrow to new limits. This doesn't sound right.\n> I am also not sure what the behavior of save-restriction should be. Should\n> save-restriction unwind hard limits as well?\n\nIIRC past discussions on this issue, one option was to merge your\nset-widen-limits into narrow-to-region by adding an optional argument\n`hard'.  And yes, I think save-restriction should unwind hard limits.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_595.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiPAS-00029m-Mp\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 12:28:28 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41457)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiPAM-00028H-F8\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 12:28:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiPAJ-0003Lk-9q\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 12:28:22 -0400\nReceived: from mail-wm0-x22f.google.com ([2a00:1450:400c:c09::22f]:35990)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiPAI-0003LX-Vb\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 12:28:19 -0400\nReceived: by mail-wm0-x22f.google.com with SMTP id r129so101373881wmr.1\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 09:28:18 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=C5ZxhYQd3Ic8Ye64rEARO32Boknyx6Sf7igAQG5loiI=;\n\tb=aAGQuqYrLYcRC2Bsv+gchn8NQs8dvg9QhC0ZhbyLOV6UgBH1SPsw6mLd46V0XT0eTD\n\tn/k0JDcrKLut/UW2iL7HtbeQ71z3Nw/5/2/lBn6zsdheLTL6eSqkajjyGzl6cVSKlD4o\n\t3DlC1oVp4UhpDwOz8emUArIDlWkR9GXkpe5N7W4PKa0jDbvm3SFFWcL3NDcU80jxTG2Z\n\tzY0HBohwBrGLvSj6lqvjwnrk76R0XF8ezOxyqJYQVRs4QP9FMUp2gveoFEmrMOzBu8gn\n\tI9sYuE72GLFbPVWtJRfxveel0yYLtqnck0vs8kzTsXDS991Uh+Tc7VDMVU+hwbDQ4nem\n\tX/Wg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=C5ZxhYQd3Ic8Ye64rEARO32Boknyx6Sf7igAQG5loiI=;\n\tb=bGhaJQO1GHJ5BZgErJ5PmCBpHSMg8h6Nuddi6lrMfRBr1DTH20jICEr8D8Bw3NYeX8\n\titjIPwb1KetHZHFquCAA4PA7p78iJJ+zBilzfP4HCElBh3NTRxx58ZzAY3exgxFbQ97d\n\tYF+ZVAJxD9z6A43crfpU0hoqTVCK+PVJgt297sYw2XJ/o2ZpNjwnqUS+UW6/93Xch6sm\n\t85j4ALf1/RMee1thAUB2T3sfFXdgH0AfQRJBmjJb3yWIfZ65gonYyIoSSQIQFdxsMpVJ\n\tRavWyjm74m5hF3onMMk5o1xF/LKLepDmEpWu4NvGQLmwTqa/TrgO1R7kxYAhLqmvryKr\n\tHbqw==\nX-Gm-Message-State: AD7BkJIVqUigF4aHYMt5H1E15YX98yufVHaYIopsR5fQqHKKrhV1+LZdW7tNmwyeNu9KtQ==\nX-Received: by 10.28.143.9 with SMTP id r9mr20265094wmd.37.1458664098239;\n\tTue, 22 Mar 2016 09:28:18 -0700 (PDT)\nReceived: from localhost ([143.176.214.220])\n\tby smtp.gmail.com with ESMTPSA id h7sm18084368wmf.9.2016.03.22.09.28.16\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tTue, 22 Mar 2016 09:28:16 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\n\t<jwvbn66viar.fsf-monnier+Inbox@gnu.org>\nDate: Tue, 22 Mar 2016 17:28:16 +0100\nIn-Reply-To: <jwvbn66viar.fsf-monnier+Inbox@gnu.org> (Stefan Monnier's message\n\tof \"Tue, 22 Mar 2016 07:57:27 -0400\")\nMessage-ID: <87mvpqqxy7.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22f\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 16:28:28 -0000\nContent-Length: 1067\nLines: 25\n\n\n\n>> On Tue, Mar 22 2016 07:57, Stefan Monnier wrote:\n\n> IIRC past discussions on this issue, one option was to merge your\n> set-widen-limits into narrow-to-region by adding an optional argument `hard'.\n\nIf narrowing is already in place, set-widen-limits will not touch it unless the\nvisible region expands beyond the hard limits. I think widen limits is\nfundamentally about widening and only indirectly about narrowing.\n\nMixing hard limit into a common user level function is a bad marketing\nstrategy. We don't want to encourage major modes to use it in funny ways. If\nusers and major modes decide to use hard limits we might end up in the same\nsituation as now when narrow/widen is not perceived as a good tool for\nmulti-modes.\n\nStatic usage of hard widening, like in Info example, is not really a\nproblem. Multi modes need to impose hard limits transiently, in specific\ncontexts like indentation, syntax parsing or font lock, and will restore the\nlimits at the end. Problems will occur if major modes start using hard limits in\nsuch contexts directly.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_597.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiPQF-0005FD-4z\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 12:44:47 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46780)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiPQD-0005C8-Ea\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 12:44:46 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiPQ8-0008RQ-F7\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 12:44:45 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:37443)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiPQ8-0008Qw-8P\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 12:44:40 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiPQ4-000382-8G\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 17:44:36 +0100\nReceived: from 69-196-182-150.dsl.teksavvy.com ([69.196.182.150])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 17:44:36 +0100\nReceived: from monnier by 69-196-182-150.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 17:44:36 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nDate: Tue, 22 Mar 2016 12:44:27 -0400\nMessage-ID: <jwvzitqpj68.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\n\t<jwvbn66viar.fsf-monnier+Inbox@gnu.org> <87mvpqqxy7.fsf@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-196-182-150.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:VdoDwlpPQUO1P7sh9dQ3ECawHtA=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 16:44:46 -0000\nContent-Length: 1401\nLines: 35\n\n> Mixing hard limit into a common user level function is a bad marketing\n> strategy.\n\n`narrow-to-region' is not only a user-level command.\nIt's also a low-level primitive.\n\nThe narrow-to-region command can't set the optional argument unless we\ntake extra steps to let it, so the \"hard narrowing\" would only be\navailable from Elisp, not interactively.\n\n> If users and major modes decide to use hard limits we might end up in\n> the same situation as now when narrow/widen is not perceived as a good\n> tool for multi-modes.\n\nCould be.  Maybe there are more \"kinds of narrowing\" than just 2, indeed.\n\nBut for me, the main consideration is whether the text before/after\npoint-min can be taken into account as a kind of context, or whether the\ntext between point-min/max should be treated (even if temporarily) as\nbeing the whole&sole truth.\n\nThat's what \"hard narrowing\" means to me.  I don't think I'd be able to\ndesign something that can take into account finer distinctions of\nnarrowings right now, for lack of understanding about what those finer\ndistinctions could be and what kind of problems they lead to.\n\n> limits at the end. Problems will occur if major modes start using hard\n> limits in such contexts directly.\n\nI don't see any reason why problems *will* occur in that case (tho, of\ncourse, Murphy could be that reason).  So until such problems do show up,\nI wouldn't worry.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_599.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiS6J-0000Af-Q1\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 15:36:23 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37798)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiS6G-0000A4-ME\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 15:36:21 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiS6D-0002tY-FC\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 15:36:20 -0400\nReceived: from mail-wm0-x22d.google.com ([2a00:1450:400c:c09::22d]:33196)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiS6D-0002tQ-4Z\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 15:36:17 -0400\nReceived: by mail-wm0-x22d.google.com with SMTP id l68so206705951wml.0\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 12:36:17 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=UVhRf+vH0LDro8iH+0SpBWc03gtZg2R+dOwROGP4UQ0=;\n\tb=rR4bmrsdvryZMDTqCt20oAwWbfAe5BePY2Y/1qr3ZyjvhNrMnVq4V2uuZShr7zz8Hq\n\t54j7cVLymTMzNRDf+lk6EmHvl3hXh3eJtu0i7z+nvjjEqo7s5abP7IrdoK3ejePSDMEg\n\tSSszXieYlrrOPdhnMEwUI2us9evMCO60TP4XznPHSrjMmzWDxpzCn6LcvYGQ9IPBQ9wO\n\tc05APahsDpCSFZmzk5m6apHVhLkPAbRNT24nuUfJcSYpHudgJKdwbCkRdR6KFw3Nl4S0\n\t05iPuVWN1trvRC104+NXkaGuNJ7lKFtOP0R6sF6gXE1w9f6Fpo6fjCYWypxkzF1tpwV0\n\tx40w==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=UVhRf+vH0LDro8iH+0SpBWc03gtZg2R+dOwROGP4UQ0=;\n\tb=ReP7lYwrk6kZWBemZpmH6XgCmF2yyNcqSeMKFG+ZbG53cT7VR68/Y1xLiTjJvRBHTe\n\tmfhU022yc+rcPmNweMxd2bbscr8fS0GehmULjLc7nt4yGyxzAME9MY/Hv2ymKMmOdp4n\n\tZljeIe6fHXqE9cUMHoiDOWn8UM9c8sG0VVkBCdwDCVzInLs9otMGX4j4x7l1lvpHLLb3\n\tfGouflANkA0NNmHq5sZdRIFMa/2WTK2eZ1N1tvIRAoZaHLGhwyodkZbvQoyr7GMkbPD9\n\tbZtffb7LGU0dAPshBY3nT3noboZ7Ec7fZEMhlrI5I8ZlDKa8Us9XI5oc1Z6aDkFnCXs8\n\tNSJg==\nX-Gm-Message-State: AD7BkJKAVes0tJ6V7TQj1Bv1BJT40lXt+D3V0bY+6WqCu5F1zN0f8T2TWtQ1ca0sRspv8A==\nX-Received: by 10.28.232.87 with SMTP id f84mr22614534wmh.56.1458675376082;\n\tTue, 22 Mar 2016 12:36:16 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tgb9sm31668120wjb.26.2016.03.22.12.36.15\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tTue, 22 Mar 2016 12:36:15 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\n\t<jwvbn66viar.fsf-monnier+Inbox@gnu.org> <87mvpqqxy7.fsf@gmail.com>\n\t<jwvzitqpj68.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Tue, 22 Mar 2016 20:36:14 +0100\nIn-Reply-To: <jwvzitqpj68.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Tue, 22 Mar 2016 12:44:27 -0400\")\nMessage-ID: <877fguqp8x.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22d\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 19:36:21 -0000\nContent-Length: 2759\nLines: 62\n\n\n\n>> On Tue, Mar 22 2016 12:44, Stefan Monnier wrote:\n\n> \"hard narrowing\" would only be available from Elisp, not interactively.\n\nInteractive or not, doesn't matter. The danger is the whatever eslip used within\nhard-narrowed regions.\n\n>> If users and major modes decide to use hard limits we might end up in\n>> the same situation as now when narrow/widen is not perceived as a good\n>> tool for multi-modes.\n\n> Could be.  Maybe there are more \"kinds of narrowing\" than just 2, indeed.\n\n> But for me, the main consideration is whether the text before/after\n> point-min can be taken into account as a kind of context, or whether the\n> text between point-min/max should be treated (even if temporarily) as\n> being the whole&sole truth.\n\nI agree completely. But I think defining an \"whole&sole\" universe need not\ninvolve current implementation of narrowing. It's about inability to widen not\nability to narrow. My patch didn't even touch `narrow` because that's not\nneeded.\n\nThere is no real need to invent extra type of narrowing. It's a lot of extra\nwork with no additional benefit. It's simply enough to define hard limits that\nnone of the standard functions can lift.\n\nIn order to define a different type of narrowing you would need to introduce\nalternatives to BEGV, BEGV_BYTE ZV, ZV_BYTE and the hunt them everywhere where\nBEGV or BEGV_BYTE are used right now.\n\nWhat concrete semantics do you have in mind? If a user or elisp already narrowed\nthe buffer, will hard narrowing re-narrow it? If user typed within a hard region\nthe hard narrowed region, will the upper hard limit expand just as ZV does?\n\nMy approach is simpler and leaves current narrowing functionality alone. You set\nthe limits and allow narrowing happening inside those limits normally. Even\nwiden cannot lift those limits. You create a small universe within the buffer\nwith only one exit (set-widen-limits nil nil).\n\nYou might end up loosing text outside of the bounds if you modify the buffer and\nthen call widen, but that's by design and this is how it's different from visual\nnarrowing. Hard limits stay the same irrespective of what happens to the buffer.\n\n>> limits at the end. Problems will occur if major modes start using hard\n>> limits in such contexts directly.\n\n> I don't see any reason why problems *will* occur in that case (tho, of\n> course, Murphy could be that reason).  So until such problems do show up,\n> I wouldn't worry.\n\nThe problem is not hypothetical. It's occurring right now. If you impose limits\nin order to do font-lock and font-lock-fontify-region-function changes those\nlimits that screws your multi mode. That's what is happening with current\nnarrowing/widening mechanism and that's precisely the reason for extra widen\nlimits in the first place.\n\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_601.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiYRz-0007Cn-17\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 22:23:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36753)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aiYRx-0007BK-6d\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 22:23:10 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aiYRt-0000dq-13\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 22:23:09 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:52238)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aiYRs-0000d3-S4\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 22:23:04 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0CdDQA731xV/5a2xEVcgxCEAshgBAICgTw9EAEBAQEBAQGBCkEFg10BAQMBViMFCws0BwsUGA0kiDcIzyMBAQEHAQEBAR6LOoQtWAeELQEEnxeSFIIUgUUjgWZVgVkigTWBQwEBAQ\nX-IPAS-Result: A0CdDQA731xV/5a2xEVcgxCEAshgBAICgTw9EAEBAQEBAQGBCkEFg10BAQMBViMFCws0BwsUGA0kiDcIzyMBAQEHAQEBAR6LOoQtWAeELQEEnxeSFIIUgUUjgWZVgVkigTWBQwEBAQ\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"197856461\"\nReceived: from 69-196-182-150.dsl.teksavvy.com (HELO fmsmemgm.homelinux.net)\n\t([69.196.182.150])\n\tby ironport2-out.teksavvy.com with ESMTP/TLS/DHE-RSA-AES256-SHA;\n\t22 Mar 2016 22:22:58 -0400\nReceived: by fmsmemgm.homelinux.net (Postfix, from userid 20848)\n\tid E10D0AE1F4; Tue, 22 Mar 2016 22:22:57 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nMessage-ID: <jwvio0euejl.fsf-monnier+emacs@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box> <874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\n\t<jwvbn66viar.fsf-monnier+Inbox@gnu.org> <87mvpqqxy7.fsf@gmail.com>\n\t<jwvzitqpj68.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<877fguqp8x.fsf@gmail.com>\nDate: Tue, 22 Mar 2016 22:22:57 -0400\nIn-Reply-To: <877fguqp8x.fsf@gmail.com> (Vitalie Spinu's message of \"Tue, 22\n\tMar 2016 20:36:14 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 02:23:10 -0000\nContent-Length: 1671\nLines: 41\n\n> There is no real need to invent extra type of narrowing. It's a lot of extra\n> work with no additional benefit.\n\nI don't see any extra work.  (narrow-to-region BEG END 'hard) would just\nbe the API used to set your hard limits, and that's all there is to it.\n\n> If user typed within a hard region the hard narrowed region, will the\n> upper hard limit expand just as ZV does?\n\nThis is indispensable, yes.  No matter whether the hard limits are\nfolded int narrow-to-region or any other way: the upper limit has to be\na marker, and unless we strictly enforce that the hard limits can't be\ncircumvented at all, the lower limit would probably have to be a marker\nas well.\n\n> My approach is simpler and leaves current narrowing functionality\n> alone.  You set the limits and allow narrowing happening inside those\n> limits normally.\n\nThat's also how I imagine (narrow-to-region BEG END 'hard) working.\nIt just won't allow widening outside of those hard limits.\n\n> You might end up loosing text outside of the bounds if you modify the\n> buffer and then call widen, but that's by design and this is how it's\n> different from visual narrowing.  Hard limits stay the same\n> irrespective of what happens to the buffer.\n\nSounds like a wart.  What's the benefit?\n\n>>> limits at the end. Problems will occur if major modes start using hard\n>>> limits in such contexts directly.\n>> I don't see any reason why problems *will* occur in that case (tho, of\n>> course, Murphy could be that reason).  So until such problems do show up,\n>> I wouldn't worry.\n> The problem is not hypothetical. It's occurring right now.\n\nIt can't because we don't have hard limits right now.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_603.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aihAU-0003aX-P3\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 07:41:42 -0400\nReceived: from eggs.gnu.org ([208.118.235.92]:33882)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aihAR-0003XB-O3\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 07:41:40 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aihAO-0006J4-IB\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 07:41:39 -0400\nReceived: from mail-wm0-x231.google.com ([2a00:1450:400c:c09::231]:36930)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aihAO-0006IO-68\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 07:41:36 -0400\nReceived: by mail-wm0-x231.google.com with SMTP id p65so20112602wmp.0\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 04:41:36 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=OBve/V5Q7sJhzr9U4IqgVcFAHZ9fr8YtluqV2+GKN8k=;\n\tb=y/adXQ1KcEj9sKPsEM83fkF0YGjbaly/6v8N4wjoN2CBrom0weYdBvPWHQiXItlD3U\n\tqlARH+FExEVgv6Mi8nsFsU4/G6rb2ok1p2F8mwktq4k8oGvCw+n4/tPh8xAQLosaJsoW\n\t1uxHn8XZNuwoLc7Lfq66yINOVn74M00TrKKTiRcbl5pADMqOKCEafm8SKRONAGm5SULi\n\terTytaGv8HETpKcl9CBYlGY1qjcsaddXcq6tlc7slN/eg78KEa30/PhtsBKC3JZvYOKJ\n\t6sxuOH27xm4nCwk6TmUe9zAW3rphUnDO50JF4ij4w/ztrE07c1iy0pUVJyvrsqApArxo\n\t8OBQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=OBve/V5Q7sJhzr9U4IqgVcFAHZ9fr8YtluqV2+GKN8k=;\n\tb=gRpo/CwLKlJQ912OddXED5oop3OKKRzfbq8p6kIoc19JKgm6To26mp8DMmP+ZBLepx\n\tGpdUQGmNyNce5Xs2xtVP7zJ01Am6U34JrYm80hf6aaRYGFQk65JgakKTrUIE7guw8/+O\n\tk7Tq4hdat3k1ewG3TpCB8V9wOLm2c0LiD3rgu5WsESB0EQJaa3cFExR3uMSJD29IQcOY\n\tiXuClhi+Y4tWm57ZKbZSsfgB03At22gU67GKVv5o+h1vFo5noNwnA3PmMPcg44ih7NDz\n\t+eeFRZx86hJ785L6IMmSZHfopzMtJngUKBJURvA7OX17SW0xMdMpdDvpj/eVLNmz3pUG\n\tNCRg==\nX-Gm-Message-State: AD7BkJL/oxdIPoDLBXNoQsk4r22JDs8O8jcfAfbjO5Sv8yAmFmD+A7OcbDQngXM1F8/i0A==\nX-Received: by 10.28.107.216 with SMTP id a85mr3265128wmi.96.1458733295407;\n\tWed, 23 Mar 2016 04:41:35 -0700 (PDT)\nReceived: from localhost ([143.176.214.220])\n\tby smtp.gmail.com with ESMTPSA id u16sm2578524wmd.5.2016.03.23.04.41.34\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tWed, 23 Mar 2016 04:41:34 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\n\t<jwvbn66viar.fsf-monnier+Inbox@gnu.org> <87mvpqqxy7.fsf@gmail.com>\n\t<jwvzitqpj68.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<877fguqp8x.fsf@gmail.com> <jwvio0euejl.fsf-monnier+emacs@gnu.org>\nDate: Wed, 23 Mar 2016 12:41:33 +0100\nIn-Reply-To: <jwvio0euejl.fsf-monnier+emacs@gnu.org> (Stefan Monnier's message\n\tof \"Tue, 22 Mar 2016 22:22:57 -0400\")\nMessage-ID: <87bn65pgk2.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::231\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 11:41:41 -0000\nContent-Length: 2618\nLines: 58\n\n\n\n>> On Tue, Mar 22 2016 22:22, Stefan Monnier wrote:\n\n>> If user typed within a hard region the hard narrowed region, will the\n>> upper hard limit expand just as ZV does?\n\n> This is indispensable, yes.  No matter whether the hard limits are\n> folded int narrow-to-region or any other way: the upper limit has to be\n> a marker, and unless we strictly enforce that the hard limits can't be\n> circumvented at all, the lower limit would probably have to be a marker\n> as well.\n\nOk. So we agree that there is work involved of tracking an extra\nmarker. Whenever buffer is modified by low level code, it must track new ZH\nmarker and respect the relationship between ZH and ZV. There are 544 occurrences\nof ZV in emacs source. In order to add this extra marker one would need to go\nthrough all of those cases and enforce the semantics of ZH.\n\nIt might be that adjusting ZV macros might do the job, but I cannot judge\nbecause I am not yet familiar with buffer modification code.\n\n>> You might end up loosing text outside of the bounds if you modify the\n>> buffer and then call widen, but that's by design and this is how it's\n>> different from visual narrowing.  Hard limits stay the same\n>> irrespective of what happens to the buffer.\n\n> Sounds like a wart.  What's the benefit?\n\nTrue, but it's almost a direct implementation of the restriction in\nprog-widen. It has same limitations and multi-modes are completely fine with\nthose. A with-widen-limits macro will suffice for multi-mode use case. But you\nproposed to extend it to permanent set-widen-limits or (narrow-to-region\n.. 'hard). I see the benefit of it in info mode but I think it's pretty\nmarginal.\n\nThe proposed non-marker implementation will deter usage of widen-limits in\ncontexts that involve buffer modification. But it will work just fine with\nmulti-modes and with read-only info use cases. It also works fine with editing\nas long as it's not followed by widen. If widen is used the buffer will be\nre-narrowed to old limits.\n\nI will look into ZH marker this weekend. Maybe it's not that hard as I imagine.\n\n>>>> limits at the end. Problems will occur if major modes start using hard\n>>>> limits in such contexts directly.\n>>> I don't see any reason why problems *will* occur in that case (tho, of\n>>> course, Murphy could be that reason).  So until such problems do show up,\n>>> I wouldn't worry.\n>> The problem is not hypothetical. It's occurring right now.\n\n> It can't because we don't have hard limits right now.\n\nOh common. You know I was referring to current widen/narrow mechanism. It's one\nstep to extrapolate to hard narrowing from there.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_605.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aihzH-0001Se-1Y\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 08:34:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:54339)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aihzF-0001Qb-2u\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 08:34:09 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aihzB-00040G-U8\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 08:34:09 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:14305)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aihzB-0003zp-Pw\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 08:34:05 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0CYDQA731xV/5a2xEVcgxCEAshgBAICgTw8EQEBAQEBAQGBCkEFg10BAQMBVhwHBQsLNAcLFBgNJIg3CM8jAQEBBwEBAQEeizqFBQeELQEEmW2ZUoFFI4I7gVkigngBAQE\nX-IPAS-Result: A0CYDQA731xV/5a2xEVcgxCEAshgBAICgTw8EQEBAQEBAQGBCkEFg10BAQMBVhwHBQsLNAcLFBgNJIg3CM8jAQEBBwEBAQEeizqFBQeELQEEmW2ZUoFFI4I7gVkigngBAQE\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"197930456\"\nReceived: from 69-196-182-150.dsl.teksavvy.com (HELO fmsmemgm.homelinux.net)\n\t([69.196.182.150])\n\tby ironport2-out.teksavvy.com with ESMTP/TLS/DHE-RSA-AES256-SHA;\n\t23 Mar 2016 08:34:04 -0400\nReceived: by fmsmemgm.homelinux.net (Postfix, from userid 20848)\n\tid 07047AE242; Wed, 23 Mar 2016 08:34:02 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nMessage-ID: <jwvvb4dtlve.fsf-monnier+emacs@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box> <87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\n\t<jwvbn66viar.fsf-monnier+Inbox@gnu.org> <87mvpqqxy7.fsf@gmail.com>\n\t<jwvzitqpj68.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<877fguqp8x.fsf@gmail.com> <jwvio0euejl.fsf-monnier+emacs@gnu.org>\n\t<87bn65pgk2.fsf@gmail.com>\nDate: Wed, 23 Mar 2016 08:34:02 -0400\nIn-Reply-To: <87bn65pgk2.fsf@gmail.com> (Vitalie Spinu's message of \"Wed, 23\n\tMar 2016 12:41:33 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 12:34:10 -0000\nContent-Length: 534\nLines: 13\n\n> Ok. So we agree that there is work involved of tracking an extra\n> marker. Whenever buffer is modified by low level code, it must track new ZH\n> marker and respect the relationship between ZH and ZV. There are 544 occurrences\n> of ZV in emacs source. In order to add this extra marker one would need to go\n> through all of those cases and enforce the semantics of ZH.\n\nI wouldn't want to touch Z* and BEG*, indeed.\nI'm just suggesting to keep the limits as markers rather than\nas integers.  It's a trivial change.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_607.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aii6k-0005mi-PE\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 08:41:54 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56362)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aii6i-0005jZ-3V\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 08:41:52 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aii6e-0005Y4-CN\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 08:41:52 -0400\nReceived: from mail-wm0-x22c.google.com ([2a00:1450:400c:c09::22c]:35319)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aii6e-0005Xy-3C\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 08:41:48 -0400\nReceived: by mail-wm0-x22c.google.com with SMTP id l68so193936946wml.0\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 05:41:47 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=/jUZbCGOdmD3M2a0ZR85mTzoDdPGISqbIBP1PiZ7EGA=;\n\tb=Npm2eT86r0aA7/1xsq7W21kz9QHoof6GWRxwrFkc6ow3m4TlK0aFmwN3XxEg+NE9lN\n\tUdrtgtPmC3/YNMvyRgZ5fKd3yhAVIJKU25/VIPphpUUn9PwcP6BwOJ7ULERKCe0t/6u8\n\tcy5/fJCXgF/3RdMqpxxoZO9RumvKJ3qr9asZsF0nr1t8X6jugbmQIiQZabAm7XfFeXmq\n\tVstBiWcptC/WNdeQqlas3JYZw2bwHYsrNQLwzzcGpdF7QjXg4aUzq3mEx5N9FMjnyXYV\n\to1xQkodTX9pG8UWNdff899FXAglrLiLW2wkwrzaM2Zc/EwSl9IBD4S3BVCFVcQWFzi3A\n\tiyLw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=/jUZbCGOdmD3M2a0ZR85mTzoDdPGISqbIBP1PiZ7EGA=;\n\tb=lLriWBCfNacWw6tVz6ZKqbkN/ATftKcqTeaFqyGm0r2ttBfh1+BKebnt2WZvb8kK1i\n\tEP0V0shC5GWNgRWoSTYE4RkjFTlyR9y7ax8OZN0e4JLBoqTOApolQi9AfQZQO0nLbekS\n\tA0kb9xbZWCMiiJgVHM2KkWy2ItveCUVKDVwQluoEZRAhhqAF6A0MgZUCZwyMc1uuDx0X\n\t8HkHVIPP7rGjhrLoso/MWXjsHdO9MXnlV1d5g4tk5+6f3fw2oqp5SzTvA7vDRGLN3+in\n\tU9HbqRWcZSyGEtBKKwDqI5Ffj0ZqjEEaiIM+zBfpgMjJHSCeZ4gtHo8PR1b4C2lj9l0A\n\toDsg==\nX-Gm-Message-State: AD7BkJI/Gu/yb1rUXvPNankxfb7xtGsWw7O5Xa+VCmkGmK+MVkUjVMBFqqewGQUpuREAMg==\nX-Received: by 10.194.90.100 with SMTP id bv4mr3254182wjb.4.1458736907384;\n\tWed, 23 Mar 2016 05:41:47 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tka4sm2407556wjc.47.2016.03.23.05.41.46\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tWed, 23 Mar 2016 05:41:46 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\n\t<jwvbn66viar.fsf-monnier+Inbox@gnu.org> <87mvpqqxy7.fsf@gmail.com>\n\t<jwvzitqpj68.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<877fguqp8x.fsf@gmail.com> <jwvio0euejl.fsf-monnier+emacs@gnu.org>\n\t<87bn65pgk2.fsf@gmail.com> <jwvvb4dtlve.fsf-monnier+emacs@gnu.org>\nDate: Wed, 23 Mar 2016 13:41:46 +0100\nIn-Reply-To: <jwvvb4dtlve.fsf-monnier+emacs@gnu.org> (Stefan Monnier's message\n\tof \"Wed, 23 Mar 2016 08:34:02 -0400\")\nMessage-ID: <87y499nz79.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22c\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 12:41:53 -0000\nContent-Length: 259\nLines: 11\n\n\n\n>> On Wed, Mar 23 2016 08:34, Stefan Monnier wrote:\n\n> I wouldn't want to touch Z* and BEG*, indeed. I'm just suggesting to keep the\n> limits as markers rather than as integers.  It's a trivial change.\n\nHm. That might work quite well actually.\n\n   Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_609.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1al1QH-0007o5-TZ\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 17:43:37 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56081)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1al1QF-0007mb-J2\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 17:43:37 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1al1QC-0004B5-1v\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 17:43:35 -0400\nReceived: from mail-wm0-x236.google.com ([2a00:1450:400c:c09::236]:34366)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1al1QB-0004AN-NN\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 17:43:31 -0400\nReceived: by mail-wm0-x236.google.com with SMTP id p65so157878303wmp.1\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 14:43:31 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=ckIi2ThB6s8MRlT6IJB+c9qHfYlGXBHn2bcT68LrpuM=;\n\tb=FEJ2+suM3HKMkyzA24R5zESUJ0msusf6FxzB7Q4lQHdn43eRMxr1K/CkFAy6QDo0RT\n\t0uUJje0h7yqUy+C+s4q+jMnXFq1f0RxpU+2sdIdqRIQj9KinWOQGSndztQqD0/iLOLcQ\n\tiHFu4iNV8usplqq3Ffc8k/LMIryavUqAgdqQZVHKbh56K0M3aOHIhPb//J5I8+NneY2c\n\tjhSUaGwKielV3iumyznZBucE1n2Rf42tE9sNMrUgn0QB2/85WUtRtDsp4GZq+5TG3S05\n\tyqo4YNRxpES3+a7V71fleG/qTgg9/BDeZI02Euuky4wTnPV5+ohYZNU+iL8KnRc3QGXw\n\t+RBw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=ckIi2ThB6s8MRlT6IJB+c9qHfYlGXBHn2bcT68LrpuM=;\n\tb=mCOPKc3Lv3BxwluF1NcUMv/6UX6Ny4+HtljON/nsxd01NodqYp0UD+mJMqonNcW1dp\n\tWKA6+pASqwFJ74rxl/7BvwXGAtB1Ur6n8LrPH6GZlMeiFb8sN6V2xkE3WsgN/ZbbhmU6\n\tisXCLh75GGavmzgz+t8B/BUJmAHqFjmegKR2gYF7PcpLEejCwbrk96+VFsfjODCTQd6P\n\tBTkrjfZU+aQV09u48WUMudvsPFew7qmAKcjxJPFDjs6Rvmdbb/athr8cJtGdoNyABeWR\n\tlA4NaBRuUCRCtVmTHGCIhPPRJiCm+V9xhKU4u0w0ZB/IVBh+QpekdWsOvC/GQ2Lal/Fc\n\tRmfA==\nX-Gm-Message-State: AD7BkJKq+dOJ6N+WM6OVjdE6MGWB8/B2dcF1ziieZMPg8V30uUCnR4IdIH5q+V+1mJcJag==\nX-Received: by 10.28.52.72 with SMTP id b69mr5569257wma.35.1459287810735;\n\tTue, 29 Mar 2016 14:43:30 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\ts66sm16525186wmb.6.2016.03.29.14.43.28\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tTue, 29 Mar 2016 14:43:29 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box> <87a8lsd4j3.fsf@gmail.com>\n\t<87mvpsbeok.fsf_-_@gmail.com> <jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\n\t<jwvbn66viar.fsf-monnier+Inbox@gnu.org> <87mvpqqxy7.fsf@gmail.com>\n\t<jwvzitqpj68.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<877fguqp8x.fsf@gmail.com> <jwvio0euejl.fsf-monnier+emacs@gnu.org>\n\t<87bn65pgk2.fsf@gmail.com> <jwvvb4dtlve.fsf-monnier+emacs@gnu.org>\n\t<87y499nz79.fsf@gmail.com>\nDate: Tue, 29 Mar 2016 23:43:28 +0200\nIn-Reply-To: <87y499nz79.fsf@gmail.com> (Vitalie Spinu's message of \"Wed, 23\n\tMar 2016 13:41:46 +0100\")\nMessage-ID: <87zithez9b.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=\"=-=-=\"\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::236\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 21:43:37 -0000\nContent-Length: 12884\nLines: 366\n\n--=-=-=\nContent-Type: text/plain\n\n\n\n>> On Wed, Mar 23 2016 12:41, Vitalie Spinu wrote:\n\n>> I'm just suggesting to keep the limits as markers rather than as integers.\n\nAttaching a patch for this. AFAIC it shapes pretty nicely. There are two types\nof narrowing, visual and hard. The imposition or lifting of these is done\nthrough narrow-to-region and widen depending on the value of optional HARD\nargument.\n\nI haven't tested it yet because of the following build problem with loaddeffs:\n\n    ...    \n    Finding pointers to doc strings...\n    Finding pointers to doc strings...done\n    Dumping under the name emacs\n    91970 pure bytes used\n    : paxctl -zex emacs\n    mv -f emacs bootstrap-emacs\n    make -C ../lisp compile-first EMACS=\"../src/bootstrap-emacs\"\n    make[3]: Entering directory '/home/vspinu/bin/emacs-test/lisp'\n      ELC      emacs-lisp/macroexp.elc\n      ELC      emacs-lisp/cconv.elc\n      ELC      emacs-lisp/byte-opt.elc\n      ELC      emacs-lisp/bytecomp.elc\n      ELC      emacs-lisp/autoload.elc\n    make[3]: Leaving directory '/home/vspinu/bin/emacs-test/lisp'\n    make -C ../lisp autoloads EMACS=\"../src/bootstrap-emacs\"\n    make[3]: Entering directory '/home/vspinu/bin/emacs-test/lisp'\n      GEN      calendar/cal-loaddefs.el\n    Loading macroexp.elc...\n    appt.el:0:0: error: wrong-type-argument: (markerp /home/vspinu/bin/emacs-test/lisp/calendar/appt.el)\n    Makefile:402: recipe for target 'calendar/cal-loaddefs.el' failed\n    make[3]: *** [calendar/cal-loaddefs.el] Error 255\n    make[3]: Leaving directory '/home/vspinu/bin/emacs-test/lisp'\n    Makefile:727: recipe for target '../lisp/loaddefs.el' failed\n    make[2]: *** [../lisp/loaddefs.el] Error 2\n    make[2]: Leaving directory '/home/vspinu/bin/emacs-test/src'\n    Makefile:398: recipe for target 'src' failed\n    make[1]: *** [src] Error 2\n    make[1]: Leaving directory '/home/vspinu/bin/emacs-test'\n    Makefile:1091: recipe for target 'bootstrap' failed\n    make: *** [bootstrap] Error 2\n\n\nAny ideas of why this is happening?\n\nThe relevant branch is scratch/hard-narrow.\n\n\n  Vitalie\n\n\n--=-=-=\nContent-Type: text/x-diff\nContent-Disposition: inline\n\nscratch/hard-narrow origin/scratch/hard-narrow 7068e4c811f7530e14d2684fea68499418642b33\nAuthor:     Vitalie Spinu <spinuvit@gmail.com>\nAuthorDate: Mon Mar 21 05:41:55 2016 +0100\nCommit:     Vitalie Spinu <spinuvit@gmail.com>\nCommitDate: Tue Mar 29 23:29:54 2016 +0200\n\nParent:     f99b512 In M-%, avoid making buffer-local binding of text-property-default-nonsticky\nMerged:     emacs-24 master scratch/hard-narrow\nContaining: scratch/hard-narrow\nFollows:    emacs-25.0.92 (138)\n\nHard narrowing\n\nIdem\n\nmodified   src/buffer.c\n@@ -571,6 +571,9 @@ even if it is dead.  The return value is never nil.  */)\n   bset_begv_marker (b, Qnil);\n   bset_zv_marker (b, Qnil);\n \n+  bset_begh_marker (b, Qnil);\n+  bset_zh_marker (b, Qnil);\n+\n   name = Fcopy_sequence (buffer_or_name);\n   set_string_intervals (name, NULL);\n   bset_name (b, name);\n@@ -835,6 +838,7 @@ CLONE nil means the indirect buffer's state is reset to default values.  */)\n       bset_pt_marker (b, build_marker (b, b->pt, b->pt_byte));\n       bset_begv_marker (b, build_marker (b, b->begv, b->begv_byte));\n       bset_zv_marker (b, build_marker (b, b->zv, b->zv_byte));\n+\n       XMARKER (BVAR (b, zv_marker))->insertion_type = 1;\n     }\n   else\n@@ -2165,9 +2169,9 @@ Any narrowing restriction in effect (see `narrow-to-region') is removed,\n so the buffer is truly empty after this.  */)\n   (void)\n {\n-  Fwiden ();\n+  Fwiden (Qnil);\n \n-  del_range (BEG, Z);\n+  del_range (BEGV, ZV);\n \n   current_buffer->last_window_start = 1;\n   /* Prevent warnings, or suspension of auto saving, that would happen\n@@ -2310,6 +2314,8 @@ DEFUN (\"buffer-swap-text\", Fbuffer_swap_text, Sbuffer_swap_text,\n   swapfield_ (pt_marker, Lisp_Object);\n   swapfield_ (begv_marker, Lisp_Object);\n   swapfield_ (zv_marker, Lisp_Object);\n+  swapfield_ (begh_marker, Lisp_Object);\n+  swapfield_ (zh_marker, Lisp_Object);\n   bset_point_before_scroll (current_buffer, Qnil);\n   bset_point_before_scroll (other_buffer, Qnil);\n \n@@ -2490,7 +2496,7 @@ current buffer is cleared.  */)\n \t    }\n \t}\n       if (narrowed)\n-\tFnarrow_to_region (make_number (begv), make_number (zv));\n+\tFnarrow_to_region (make_number (begv), make_number (zv), Qnil);\n     }\n   else\n     {\n@@ -2571,7 +2577,7 @@ current buffer is cleared.  */)\n \tTEMP_SET_PT (pt);\n \n       if (narrowed)\n-\tFnarrow_to_region (make_number (begv), make_number (zv));\n+\tFnarrow_to_region (make_number (begv), make_number (zv), Qnil);\n \n       /* Do this first, so that chars_in_text asks the right question.\n \t set_intervals_multibyte needs it too.  */\n@@ -5053,6 +5059,8 @@ init_buffer_once (void)\n   bset_pt_marker (&buffer_local_flags, make_number (0));\n   bset_begv_marker (&buffer_local_flags, make_number (0));\n   bset_zv_marker (&buffer_local_flags, make_number (0));\n+  bset_begh_marker (&buffer_local_flags, make_number (0));\n+  bset_zh_marker (&buffer_local_flags, make_number (0));\n   bset_last_selected_window (&buffer_local_flags, make_number (0));\n \n   idx = 1;\nmodified   src/buffer.h\n@@ -416,6 +416,26 @@ extern void enlarge_buffer_text (struct buffer *, ptrdiff_t);\n \n #define BUF_FETCH_BYTE(buf, n) \\\n   *(BUF_BYTE_ADDRESS ((buf), (n)))\n+\n+\f\n+/* Macros for setting and accessing hard-narrow markers */\n+\n+/* Position of beginning of hard-narrowed range of buffer. */\n+#define BEGH (BUF_BEGH (current_buffer))\n+#define BUF_BEGH(buf)                                   \\\n+  ((NILP (BVAR (buf, begh_marker))) ? BUF_BEG (buf)     \\\n+   : marker_position (BVAR (buf, begh_marker)))\n+#define SET_BUF_BEGH(buf, charpos)                               \\\n+  (bset_begh_marker (buf, build_marker(buf, charpos, buf_charpos_to_bytepos(buf, charpos))))\n+\n+/* Position of end of hard-narrowed range of buffer. */\n+#define ZH (BUF_ZH(current_buffer))\n+#define BUF_ZH(buf)                                     \\\n+  ((NILP (BVAR (buf, zh_marker))) ? BUF_Z (buf)         \\\n+   : marker_position (BVAR (buf, zh_marker)))\n+#define SET_BUF_ZH(buf, charpos)                                 \\\n+  (bset_zh_marker (buf, build_marker(buf, charpos, buf_charpos_to_bytepos(buf, charpos))))\n+\n \f\n /* Define the actual buffer data structures.  */\n \n@@ -666,6 +686,12 @@ struct buffer\n      ZV for this buffer when the buffer is not current.  */\n   Lisp_Object zv_marker_;\n \n+  /* Lower hard limit of the buffer.*/\n+  Lisp_Object begh_marker_;\n+\n+  /* Upper hard limit of the buffer.*/\n+  Lisp_Object zh_marker_;\n+\n   /* This holds the point value before the last scroll operation.\n      Explicitly setting point sets this to nil.  */\n   Lisp_Object point_before_scroll_;\n@@ -984,6 +1010,16 @@ bset_width_table (struct buffer *b, Lisp_Object val)\n {\n   b->width_table_ = val;\n }\n+INLINE void\n+bset_begh_marker (struct buffer *b, Lisp_Object val)\n+{\n+  b->begh_marker_ = val;\n+}\n+INLINE void\n+bset_zh_marker (struct buffer *b, Lisp_Object val)\n+{\n+  b->zh_marker_ = val;\n+}\n \n /* Number of Lisp_Objects at the beginning of struct buffer.\n    If you add, remove, or reorder Lisp_Objects within buffer\nmodified   src/bytecode.c\n@@ -1682,17 +1682,18 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth,\n \n \tCASE (Bnarrow_to_region):\n \t  {\n-\t    Lisp_Object v1;\n+\t    Lisp_Object v1, v2;\n \t    BEFORE_POTENTIAL_GC ();\n \t    v1 = POP;\n-\t    TOP = Fnarrow_to_region (TOP, v1);\n+\t    v2 = POP;\n+\t    TOP = Fnarrow_to_region (TOP, v2, v1);\n \t    AFTER_POTENTIAL_GC ();\n \t    NEXT;\n \t  }\n \n \tCASE (Bwiden):\n \t  BEFORE_POTENTIAL_GC ();\n-\t  PUSH (Fwiden ());\n+\t  TOP = Fwiden (TOP);\n \t  AFTER_POTENTIAL_GC ();\n \t  NEXT;\n \nmodified   src/editfns.c\n@@ -3480,33 +3480,54 @@ DEFUN (\"delete-and-extract-region\", Fdelete_and_extract_region,\n     return empty_unibyte_string;\n   return del_range_1 (XINT (start), XINT (end), 1, 1);\n }\n+\n \f\n-DEFUN (\"widen\", Fwiden, Swiden, 0, 0, \"\",\n+DEFUN (\"widen\", Fwiden, Swiden, 0, 1, \"\",\n        doc: /* Remove restrictions (narrowing) from current buffer.\n-This allows the buffer's full text to be seen and edited.  */)\n-  (void)\n+If HARD is non-nil, remove the hard restriction imposed by a previous\n+call to \\\\[narrow-to-region].  If HARD is nil, remove visual\n+restriction up to the previously imposed hard limit (if any).  */)\n+  (Lisp_Object hard)\n {\n-  if (BEG != BEGV || Z != ZV)\n-    current_buffer->clip_changed = 1;\n-  BEGV = BEG;\n-  BEGV_BYTE = BEG_BYTE;\n-  SET_BUF_ZV_BOTH (current_buffer, Z, Z_BYTE);\n-  /* Changing the buffer bounds invalidates any recorded current column.  */\n-  invalidate_current_column ();\n+\n+  if(!NILP (hard))\n+    {\n+      bset_begh_marker(current_buffer, Qnil);\n+      bset_zh_marker(current_buffer, Qnil);\n+    }\n+  else\n+    {\n+      if (BEG != BEGV || Z != ZV)\n+        current_buffer->clip_changed = 1;\n+      BEGV = BEG;\n+      BEGV_BYTE = BEG_BYTE;\n+      SET_BUF_ZV_BOTH (current_buffer, Z, Z_BYTE);\n+      /* Changing the buffer bounds invalidates any recorded current column.  */\n+      invalidate_current_column ();\n+    }\n+\n   return Qnil;\n }\n \n-DEFUN (\"narrow-to-region\", Fnarrow_to_region, Snarrow_to_region, 2, 2, \"r\",\n-       doc: /* Restrict editing in this buffer to the current region.\n-The rest of the text becomes temporarily invisible and untouchable\n-but is not deleted; if you save the buffer in a file, the invisible\n-text is included in the file.  \\\\[widen] makes all visible again.\n-See also `save-restriction'.\n+DEFUN (\"narrow-to-region\", Fnarrow_to_region, Snarrow_to_region, 2, 3, \"r\",\n+       doc: /* Restrict editing in this buffer to the current\n+region. START and END are positions (integers or markers) bounding the\n+text that should restricted. There can be two types of restrictions,\n+visual and hard. If HARD is nil, impose visual restriction, otherwise\n+a hard one.\n \n-When calling from a program, pass two arguments; positions (integers\n-or markers) bounding the text that should remain visible.  */)\n-  (register Lisp_Object start, Lisp_Object end)\n+When visual restriction is in place, the rest of the text is invisible\n+and untouchable but is not deleted; if you save the buffer in a file,\n+the invisible text is included in the file. \\\\[widen] with nil\n+optional argument makes it all visible again.\n+\n+When hard restriction is in place, invocations of (visual) \\\\[widen]\n+with nil argument removes visual narrowing up to the hard\n+restriction. In order to lift hard restriction,  call \\\\[widen] with\n+non-nil HARD argument.  */)\n+  (register Lisp_Object start, Lisp_Object end, Lisp_Object hard)\n {\n+\n   CHECK_NUMBER_COERCE_MARKER (start);\n   CHECK_NUMBER_COERCE_MARKER (end);\n \n@@ -3519,6 +3540,15 @@ or markers) bounding the text that should remain visible.  */)\n   if (!(BEG <= XINT (start) && XINT (start) <= XINT (end) && XINT (end) <= Z))\n     args_out_of_range (start, end);\n \n+  if (!NILP (hard))\n+    {\n+      SET_BUF_BEGH (current_buffer, XFASTINT (start));\n+      SET_BUF_ZH (current_buffer, XFASTINT (end));\n+      if (BEGV >= XFASTINT (start) && ZV <= XFASTINT (end))\n+        /* Visual narrowing within hard limits.  */\n+        return Qnil;\n+    }\n+\n   if (BEGV != XFASTINT (start) || ZV != XFASTINT (end))\n     current_buffer->clip_changed = 1;\n \n@@ -3533,6 +3563,7 @@ or markers) bounding the text that should remain visible.  */)\n   return Qnil;\n }\n \n+\n Lisp_Object\n save_restriction_save (void)\n {\nmodified   src/fileio.c\n@@ -4764,7 +4764,7 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,\n \t This is useful in tar-mode.  --Stef\n       XSETFASTINT (start, BEG);\n       XSETFASTINT (end, Z); */\n-      Fwiden ();\n+      Fwiden (Qnil);\n     }\n \n   record_unwind_protect (build_annotations_unwind,\nmodified   src/lread.c\n@@ -1850,7 +1850,7 @@ readevalloop (Lisp_Object readcharfun,\n \t  /* Set point and ZV around stuff to be read.  */\n \t  Fgoto_char (start);\n \t  if (!NILP (end))\n-\t    Fnarrow_to_region (make_number (BEGV), end);\n+\t    Fnarrow_to_region (make_number (BEGV), end, Qnil);\n \n \t  /* Just for cleanliness, convert END to a marker\n \t     if it is an integer.  */\nmodified   src/process.c\n@@ -5514,7 +5514,7 @@ Otherwise it discards the output.  */)\n       /* If the output marker is outside of the visible region, save\n \t the restriction and widen.  */\n       if (! (BEGV <= PT && PT <= ZV))\n-\tFwiden ();\n+\tFwiden (Qnil);\n \n       /* Adjust the multibyteness of TEXT to that of the buffer.  */\n       if (NILP (BVAR (current_buffer, enable_multibyte_characters))\n@@ -5558,7 +5558,7 @@ Otherwise it discards the output.  */)\n \n       /* If the restriction isn't what it should be, set it.  */\n       if (old_begv != BEGV || old_zv != ZV)\n-\tFnarrow_to_region (make_number (old_begv), make_number (old_zv));\n+\tFnarrow_to_region (make_number (old_begv), make_number (old_zv), Qnil);\n \n       bset_read_only (current_buffer, old_read_only);\n       SET_PT_BOTH (opoint, opoint_byte);\n\n[back]\n--=-=-=--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_611.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiSbn-0001Vg-OT\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 16:08:55 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49795)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1aiSbk-0001UD-L5\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 16:08:53 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1aiSbj-0004tH-Rl\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 16:08:52 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:34165)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1aiSbh-0004s7-8R; Tue, 22 Mar 2016 16:08:49 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1aiSbg-00081O-OP; Tue, 22 Mar 2016 16:08:48 -0400\nContent-Type: text/plain; charset=Utf-8\nFrom: Richard Stallman <rms@gnu.org>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nIn-reply-to: <8737risu8d.fsf@gmail.com> (message from Vitalie Spinu on Tue, 22\n\tMar 2016 11:05:38 +0100)\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\nMessage-Id: <E1aiSbg-00081O-OP@fencepost.gnu.org>\nDate: Tue, 22 Mar 2016 16:08:48 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: monnier@IRO.UMontreal.CA, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 20:08:53 -0000\nContent-Length: 660\nLines: 17\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > > So the consumer will need to set this variable and always follow it by widen.\n\nIn the context of Emacs -- or software, generally -- what does\n\"consumer\" mean?  One of the nice things about installing a program\nin your computer is that running it does not use it up.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_613.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiV3x-0003om-79\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 18:46:09 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46413)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiV3u-0003o5-G1\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 18:46:07 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiV3t-0001JY-Kz\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 18:46:06 -0400\nReceived: from mail-wm0-x22f.google.com ([2a00:1450:400c:c09::22f]:37468)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>)\n\tid 1aiV3o-0001GD-Sh; Tue, 22 Mar 2016 18:46:01 -0400\nReceived: by mail-wm0-x22f.google.com with SMTP id p65so1387203wmp.0;\n\tTue, 22 Mar 2016 15:46:00 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=e5SPQXeChqnheCvzTYYVew/V56OEmNFffegkZkF9Q4Y=;\n\tb=BoUyrHV6K9+4l4cgjPQm7wXwNig3VojRySp9pXcc0BD+SE5PG3xj5aiXI5LeNpS6Qp\n\tYx+p3Sw4jQgE9vrWpbu6PbUTwy7mkBR7Wn7ZwD0+03dmGim4kdMFn3FB4uN5qGPQ+4bR\n\tZT4+PqHPEcRVG0VsKxrqqCLMH8Qd5+n9z0EbidWZT6Fi+uqFRcJ5tocEL173ObSEj8bN\n\tM22FLeo04A48ydcY+bppViYyYRRrCNfWckRfOVE2NCXG9kWNpiDVI7WfTPgWGYYXcpWc\n\txFBThtuZv53gcDlEW+jusTgE9rEaQdSgOrPN6O7h47WhHw1PZN2MH8k+7pt7WtuTcYpK\n\t/y0Q==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=e5SPQXeChqnheCvzTYYVew/V56OEmNFffegkZkF9Q4Y=;\n\tb=b6shFRgTMb3R6iuSzozv2mECWxFKOBAMwjHUvJ69BFhKN+HlGnN+w3U6lxawS+44rB\n\tkgQJySnqtnxFS2noFfA4+MHt0FycWJqwx2y8jUvUN/D2Oye0ekN+VbLg0yZv+2lpuEQN\n\tc9iWZsLk/hBE8RhYALEALoZrfLnOxzNFW4kDKGv5ldE/vYFub2RamityNxIr/pVKSsFr\n\tSa5Tz86qh3Sgej65uFM2DEDs0l3XbUqsYfRjSqofxtSVXz3kK6TG2D+TlYSic//ZWzXD\n\t+tCqrEDgmrWh8b45G1Ix8762DdZyCXGYiu3gJHPD8e/encVLDTCgSnE+9vJhZ/4EsaPP\n\tJPzw==\nX-Gm-Message-State: AD7BkJJmEL87WTjd4a7bUGbwuKeOe3ySTqIdRPUn3u/dsqjUKleBLcT8Vmj5E0+w//x0Dg==\nX-Received: by 10.28.128.80 with SMTP id b77mr23665333wmd.42.1458686760204;\n\tTue, 22 Mar 2016 15:46:00 -0700 (PDT)\nReceived: from localhost ([143.176.214.220])\n\tby smtp.gmail.com with ESMTPSA id x203sm40368wmg.23.2016.03.22.15.45.59\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tTue, 22 Mar 2016 15:45:59 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Richard Stallman <rms@gnu.org>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\n\t<jwv1t74x716.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87pounew9e.fsf@gmail.com>\n\t<jwva8lrx4ov.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87twjzda4h.fsf@gmail.com> <jwvwpovwuip.fsf-monnier+Inbox@gnu.org>\n\t<87lh5bd4ib.fsf@gmail.com> <87egb3ryjc.fsf@gmail.com>\n\t<877fgusum3.fsf@gmail.com> <8737risu8d.fsf@gmail.com>\n\t<E1aiSbg-00081O-OP@fencepost.gnu.org>\nDate: Tue, 22 Mar 2016 23:45:59 +0100\nIn-Reply-To: <E1aiSbg-00081O-OP@fencepost.gnu.org> (Richard Stallman's message\n\tof \"Tue, 22 Mar 2016 16:08:48 -0400\")\nMessage-ID: <87poump1w8.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22f\nCc: monnier@IRO.UMontreal.CA, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 22:46:07 -0000\nContent-Length: 685\nLines: 19\n\n\n\n>> On Tue, Mar 22 2016 16:08, Richard Stallman wrote:\n\n> [[[ To any NSA and FBI agents reading my email: please consider    ]]]\n> [[[ whether defending the US Constitution against all enemies,     ]]]\n> [[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n>   > > So the consumer will need to set this variable and always follow it by widen.\n\n> In the context of Emacs -- or software, generally -- what does\n> \"consumer\" mean?  One of the nice things about installing a program\n> in your computer is that running it does not use it up.\n\nBy \"consumer\" of a function or variable I meant any elisp code that uses (aka\nconsumes) that function or variable.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_615.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahzMB-0006xt-9D\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 08:54:51 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46500)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahzM8-0006sT-DV\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:54:49 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahzM5-0007c3-6h\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:54:48 -0400\nReceived: from mail-wm0-x22a.google.com ([2a00:1450:400c:c09::22a]:32856)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahzM3-0007bi-Vq\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:54:45 -0400\nReceived: by mail-wm0-x22a.google.com with SMTP id l68so150368685wml.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 05:54:43 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=LiOZ5LSaaMsUvi3nJ2u87agPmaJsZ8tEzaf0P55zLaE=;\n\tb=rBTvNcW67IS7WVbdGSYfAMiHvNRm4unXavFUBffdpxccnSv2iUG+V+zR0px+wrtNyN\n\t2+jz7ZYANP3beUzucM3tBiP0aqWEMOUemXmqxWntCa6rDiXnzn22V2v86he8uEeAb+bL\n\tbNNKnR2AlibF4SLedh8+ZbocayT2ia1lGlVEEBKhwDPbr6HO9LkC54lron2gO21k7iyg\n\tuX3L/SIaF0I+U8bYsKIF6YCY0zWgionnM1qpYyA9cyWbQZuSVZDiWIRVsQfhAt0pREuo\n\tUqk+cUnNMMrEUSbAoGSrj6AlKioifMX5uaOd2Ilb74kl59epjA+lYhvvmz7m9HMmpZzr\n\tsW3g==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=LiOZ5LSaaMsUvi3nJ2u87agPmaJsZ8tEzaf0P55zLaE=;\n\tb=B5/ITjBAJS7E9a5nxWsN0goniqBFdxIg+Sp4PW4hLKHKUwZZUEQiOyMV8Goz63bZZY\n\tV3miW/c066rO2ZcA7VE96teFLv7CZwy9DCDcAfyok8zvwCWcGGLDI+tkTZzTzzjJT04T\n\t2X5mTlFvsYDpOQdWcJV81qO8NGziL9/8jj2sdXvWdXB6v4/++2F6ya7PSZIi3o8eKqsH\n\tHOwf5eqWeFs6KvhyFwbkO9E6vPQ3ZMeB+CL06UBMVOSaqldfhXZcCEEl+jrpJjUfRrgG\n\teGcIurb5qJ4e322qUdqQ9W2rsATdL9fnry9kQN9v12H+Cxt3zEgxdsiuEvaUyHlwccpo\n\tZzTg==\nX-Gm-Message-State: AD7BkJLaRrsqHlw+GwSVcBVXJEXu6FP5BZRKaedoEd5zUOYUnYwkajZhN9IpCj0eMXXg+A==\nX-Received: by 10.28.189.70 with SMTP id n67mr12968233wmf.45.1458564883250;\n\tMon, 21 Mar 2016 05:54:43 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\ty3sm12339811wmy.17.2016.03.21.05.54.42\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 05:54:42 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org>\nDate: Mon, 21 Mar 2016 13:54:41 +0100\nIn-Reply-To: <jwv37rkypwi.fsf-monnier+Inbox@gnu.org> (Stefan Monnier's message\n\tof \"Mon, 21 Mar 2016 08:39:28 -0400\")\nMessage-ID: <87bn68f0tq.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22a\nCc: Alan Mackenzie <acm@muc.de>, Dmitry Gutov <dgutov@yandex.ru>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 12:54:49 -0000\nContent-Length: 1381\nLines: 36\n\n\nSounds reasonable. But whatever is the internal implementation shouldn't\nhard-widen-limits be there anyways?\n\nWhy to bother with call-with-hard-narrowing and not have all the logic in\nwith-hard-narrowing directly? I looks to me that it's better to expose hard\nnarrowing to elisp only. If possible it should be transparent to low level code.\n\n  Vitalie\n\n>> On Mon, Mar 21 2016 08:39, Stefan Monnier wrote:\n\n>> +  DEFVAR_LISP (\"hard-widen-limits\", Vhard_widen_limits,\n>> +\t       doc: /* When non-nil `widen` will widen to these limits.\n>> +Must be a cons of the form (MIN . MAX) where MIN and MAX are integers or markers.  */);\n>> +  Vhard_widen_limits = Qnil;\n\n> Sorry to nitpick, but I'm not completely happy with this API.  As an\n> implementation it might be OK, but I can imagine wanting to change the\n> implementation in the future but being stuck by the exposed internals.\n\n> So I suggest we instead expose only a new primitive\n> \"call-with-hard-narrowing\" which could look like:\n\n>     (defun call-with-hard-narrowing (from to func)\n>       (make-local-variable 'internal--hard-widen-limits)\n>       (let ((internal--hard-widen-limits (cons from to)))\n>         (funcall func)))\n\n> which could be supplemented with a corresponding macro\n\n>     (defmacro with-hard-narrowing (from to &rest body)\n>       `(call-with-hard-narrowing ,from ,to (lambda () ,body)))\n\n> -- Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463568.5266_617.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0XE-0006jo-Rh\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:10:20 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38061)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0X8-0006hg-Ge\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:10:19 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0X3-0008OU-Ge\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:10:14 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:40286)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0X3-0008OH-AB\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:10:09 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ai0Wy-0006Fg-Ib\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 15:10:04 +0100\nReceived: from 157-52-2-179.cpe.teksavvy.com ([157-52-2-179.cpe.teksavvy.com])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 15:10:04 +0100\nReceived: from monnier by 157-52-2-179.cpe.teksavvy.com with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 15:10:04 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nDate: Mon, 21 Mar 2016 10:07:08 -0400\nMessage-ID: <jwvvb4gvsde.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org> <87bn68f0tq.fsf@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 157-52-2-179.cpe.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:ffVToYWd+ZVVKkVARkpI6egYa2E=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:10:20 -0000\nContent-Length: 512\nLines: 13\n\n> Why to bother with call-with-hard-narrowing and not have all the logic\n> in with-hard-narrowing directly? I looks to me that it's better to\n> expose hard narrowing to elisp only. If possible it should be\n> transparent to low level code.\n\nThe macro-expanded code will be written into the .elc files which people\nwill expect will work in the future without having to recompile.\n\nSo any API used by the macro-expanded code ends up being sufficiently\nexposed that we can't easily get rid of it.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_619.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0bF-0004rK-3j\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:14:29 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39526)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0b8-0004er-Dc\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:14:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0b7-000169-4L\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:14:22 -0400\nReceived: from mail-wm0-x22d.google.com ([2a00:1450:400c:c09::22d]:37670)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0b6-00015v-TD\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:14:21 -0400\nReceived: by mail-wm0-x22d.google.com with SMTP id p65so123648286wmp.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 07:14:20 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=KbE7tdUds72/gzaY2G6znD/F+QNoG2vbwqVozsuTbmI=;\n\tb=DDGzR9S6uDr2ZzJwuqmgNB+KuaJPtMMI7rqekz0nWQWyNMfeWdBVxw9ekiVKY72K4K\n\trP2fLIkCfTYeffNCDpnQpohS0ydRlzJDOjNnA4NVgnFf5xPDbDsXjtOu+Mzc0k7BapFI\n\tg1eFY8yybdex9fPYRmBEIO+QIZZZNCPDkRgI09bX5EqoG1/cI3nz8/6a7CcehYheiClG\n\ttDj7Lyu1pkYttkeZTRavFrL9H8MkG40JUHA7G8O1kAp/kA93LotJQQuNUmS+T8RTNlQv\n\tf5e/MFtJvCo+HYlPzBHZ4FhjM8qu1fIH/8m0//tAdlJMBrCof2StJhA1JhtTKnLbxL7p\n\tzskw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=KbE7tdUds72/gzaY2G6znD/F+QNoG2vbwqVozsuTbmI=;\n\tb=g9+wRlZMlW52mhzYkNNDDOWZWL18WmzWJMIqly7vvTP22aI4IbFihyfGyE14b5/MjE\n\tIYrjul0EGxumLthoMt1CLcSoNTPDvYG0XDt13A1N5NPcYiqbsjOL8tl5oeDyw4GlwTJv\n\tKhPhCvgbNX6NuuIzjc39NTFI4MVioeki4rNww+z8g86zDDqaso4hRG9/aJl6EIBKmUbl\n\tq0Hvha7Blkh1+UzM61S3m5lo8HUuLIVIkDsfkBtL1DO0eYYyHdJT3KDWl7vCNTgpXpvn\n\tT3TiQjnxrtIiRiZOB0cbQzwsbuC66W8gFveffQVCGMzu1qPg2l9EuaGcFgaMtkGuBloh\n\tA2Xw==\nX-Gm-Message-State: AD7BkJIUKCHLaMpYDdETV3FGqF1EOtS1fW5vEStuSZtFK24QBuLDP3P9C9j3Sfbw5fzgXg==\nX-Received: by 10.194.115.8 with SMTP id jk8mr33056205wjb.136.1458569660296;\n\tMon, 21 Mar 2016 07:14:20 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\te19sm12743033wmd.1.2016.03.21.07.14.19\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 07:14:19 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: [Patch] hard-widen-limits [was Re: Syntax tables for multiple\n\tmodes [was: bug#22983: syntax-ppss returns wrong result.]]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <87mvpsbeok.fsf_-_@gmail.com>\n\t<jwv37rkypwi.fsf-monnier+Inbox@gnu.org> <87bn68f0tq.fsf@gmail.com>\n\t<jwvvb4gvsde.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Mon, 21 Mar 2016 15:14:19 +0100\nIn-Reply-To: <jwvvb4gvsde.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Mon, 21 Mar 2016 10:07:08 -0400\")\nMessage-ID: <8737rjgbpg.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22d\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:14:28 -0000\nContent-Length: 591\nLines: 18\n\n\nAha. Clear.\n\n>> On Mon, Mar 21 2016 10:07, Stefan Monnier wrote:\n\n>> Why to bother with call-with-hard-narrowing and not have all the logic\n>> in with-hard-narrowing directly? I looks to me that it's better to\n>> expose hard narrowing to elisp only. If possible it should be\n>> transparent to low level code.\n\n> The macro-expanded code will be written into the .elc files which people\n> will expect will work in the future without having to recompile.\n\n> So any API used by the macro-expanded code ends up being sufficiently\n> exposed that we can't easily get rid of it.\n\n>         Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_621.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahqGB-0005EG-8t\n\tfor mharc-dev@caliopen.local; Sun, 20 Mar 2016 23:12:03 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39496)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ahqG8-0005DN-Ql\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 23:12:01 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ahqG5-0006ya-HM\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 23:12:00 -0400\nReceived: from pruche.dit.umontreal.ca ([132.204.246.22]:56075)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ahqG5-0006xa-BS\n\tfor dev@caliopen.local; Sun, 20 Mar 2016 23:11:57 -0400\nReceived: from fmsmemgm.homelinux.net (lechon.iro.umontreal.ca\n\t[132.204.27.242])\n\tby pruche.dit.umontreal.ca (8.14.7/8.14.1) with ESMTP id u2L3BqGx020944;\n\tSun, 20 Mar 2016 23:11:52 -0400\nReceived: by fmsmemgm.homelinux.net (Postfix, from userid 20848)\n\tid 8B797AE240; Sun, 20 Mar 2016 23:11:52 -0400 (EDT)\nFrom: Stefan Monnier <monnier@IRO.UMontreal.CA>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\nDate: Sun, 20 Mar 2016 23:11:52 -0400\nIn-Reply-To: <87a8lsd4j3.fsf@gmail.com> (Vitalie Spinu's message of \"Mon, 21\n\tMar 2016 02:05:20 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-NAI-Spam-Flag: NO\nX-NAI-Spam-Threshold: 5\nX-NAI-Spam-Score: 0\nX-NAI-Spam-Rules: 1 Rules triggered\n\tRV5616=0\nX-NAI-Spam-Version: 2.3.0.9418 : core <5616> : inlines <4536> : streams\n\t<1606280> : uri <2170994>\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 132.204.246.22\nCc: Alan Mackenzie <acm@muc.de>, emacs-devel <dev@caliopen.local>,\n\tDmitry Gutov <dgutov@yandex.ru>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 03:12:01 -0000\nContent-Length: 966\nLines: 25\n\n> BTW, I parse-partial-sexp must abide hard-widen-limits as well.\n\nI don't understand what this means.  parse-partial-sexp is passed\n2 locations and it works between them.  There's not much opportunity\nfor widening.\n\nBut yes, syntax-ppss should obey hard-widen-limits.\n\n> A patch that would require hunting every single mode out there and\n> implementing multi-modes locally should have been more carefully\n> considered IMO.\n\nI must say I don't understand how what we have is so very different from\nwhat you suggest.  Of course, I fully agree on the need to deprecate\nindent-line-function and use a side-effect free replacement which\nreturns the desired indentation (instead performing the indentation).\n\nI think both suggestions require changes to every mode, and in both\ncases the changes can be reduced to a one-liner or close enough (for\nthe simple case).  Admittedly, for it to be a one-liner, we'll need to\nprovide a standard helper function.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_623.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahs24-0007dB-LH\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 01:05:36 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59479)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahs21-0007cJ-Ac\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 01:05:34 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahs1y-0002vm-0W\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 01:05:33 -0400\nReceived: from mail-wm0-x231.google.com ([2a00:1450:400c:c09::231]:37731)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ahs1x-0002vi-KG\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 01:05:29 -0400\nReceived: by mail-wm0-x231.google.com with SMTP id p65so107587299wmp.0\n\tfor <dev@caliopen.local>; Sun, 20 Mar 2016 22:05:29 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=AH0bL/k3aFYRWo9OXnpX6K6VuNY4N0OW+DCoK07tlvc=;\n\tb=SlLcFUCuBNt1awniP0OIVhHD32CDr9+K8bJ9P4XEfDcyshgfJWJkavWdOtFfyPTYlz\n\tlQgRc8hXshgDZJWHCoxtoAeTnYRNMwvJ4dqLDAVlv0WvA98QmThy1YlwV0tFww8WMyvp\n\tz+utrpudLP1TfKNhI+hvKWkH1U/CWJJq+4Y4ZGDrRasiaSQImZh2zUMP/W01GGwdQTct\n\tgTjsGscuCmBace1w9sa7Nv/fqAmiYQQUp+TPOjRCfjS0aOz08+nG/n22smnJ0puoWPI7\n\teUDiictyTmdGdcqIuKGAyyfFmuMMoOf/xyu+jzqOUJhnttj9DKqDXJMZ73gZYe4vzM5p\n\tOhSQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=AH0bL/k3aFYRWo9OXnpX6K6VuNY4N0OW+DCoK07tlvc=;\n\tb=YIJjrqHC7JEH4ialcyrSHHhFiTnWutwIYhdDkJHScmcPZKjnmvT8o0NcJhrTA8isUv\n\tle5n/FYVSeFsV+AwZsLDo6y5YD231ipUOnS22CEAlD+tBO05LblC1PnJxLjWR3PxiHQe\n\tb+w5spFMyLQsLmcljKDjhPa98wNH3kVZ9ABcU070BiYIun0hnMRy111RsfZFp8sM9z3Y\n\tuefh6HXg9bXsUF6gSeHoLnK6+I7Vf4w6tdq1ebo+ivyhFuYuZO/JLV/mSqnaTZig+ppi\n\tfhlwnsCdDsAKQxK1ELvKQIEfeRn4tdN/iXfu+J9I8f/8EXY5H/moDal6nyoKzAWd6jzZ\n\ttjSQ==\nX-Gm-Message-State: AD7BkJKCCIXR2qh5bkWTpv+djmK3Lyj7IxW2Vpsl79WGdPFqr1Z+M7GAVoCgcG9u75ZEwg==\nX-Received: by 10.194.222.234 with SMTP id qp10mr27946230wjc.138.1458536728626;\n\tSun, 20 Mar 2016 22:05:28 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tv188sm10561744wmv.3.2016.03.20.22.05.27\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 20 Mar 2016 22:05:27 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@IRO.UMontreal.CA>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\nDate: Mon, 21 Mar 2016 06:05:26 +0100\nIn-Reply-To: <jwvmvpswowh.fsf-monnier+Inbox@gnu.org> (Stefan Monnier's message\n\tof \"Sun, 20 Mar 2016 23:11:52 -0400\")\nMessage-ID: <87twk0beuh.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::231\nCc: Alan Mackenzie <acm@muc.de>, emacs-devel <dev@caliopen.local>,\n\tDmitry Gutov <dgutov@yandex.ru>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 05:05:34 -0000\nContent-Length: 2171\nLines: 55\n\n\n\n>> On Sun, Mar 20 2016 23:11, Stefan Monnier wrote:\n\n>> BTW, I parse-partial-sexp must abide hard-widen-limits as well.\n\n> I don't understand what this means.  parse-partial-sexp is passed\n> 2 locations and it works between them.  There's not much opportunity\n> for widening.\n\nparse-partial-sexp should work between hard limits (at least the lower\nbound). It should operate as if hard-narrowed buffer is the real buffer.\n\nSo ideally it should take (max FROM (car hard-widen-limits)) as the starting\nposition. This will give the desired consistency between parse-partial-sexp and\nsyntax-ppss with the price of slightly modifying the semantics of\nparse-partial-sexp in a backward compatible way.\n\n>> A patch that would require hunting every single mode out there and\n>> implementing multi-modes locally should have been more carefully\n>> considered IMO.\n\n> I must say I don't understand how what we have is so very different from\n> what you suggest.  \n\nIt's quite a bit different:\n\n  - Major mode authors won't need to know about multi-modes. That means not\n    dealing with chunks/spans/headers etc. These concepts are not even uniformly\n    defined between existing multi-mode engines.\n  \n  - Major mode authors won't need to re-implement the indentation logic already\n    there in multi-modes. The logic is likely to be too simplistic and major\n    mode authors will have to re-do it anyways.\n\n  - Setup is more general. multi-mode engine decides where to call\n    calculate-indent-function and with what parameters and with what narrowing.\n\n  - Arguably calculate-indent-function is a simpler concept to grasp\n\n  - calculate-indent-function is needed anyways\n\n> I think both suggestions require changes to every mode, and in both cases the\n> changes can be reduced to a one-liner or close enough (for the simple\n> case). Admittedly, for it to be a one-liner, we'll need to provide a standard\n> helper function.\n\nJudging from python.el it might be quite hard to provide a generic one liner to\ndeal with all those 3 elements. For calculate-indent-function instead you can\nprovide a straightforward one line assuming that STRING-BEFORE/AFTER do not\nmatter.\n\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_625.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahyuo-0005vu-Ht\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 08:26:34 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39492)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ahyul-0005vR-FK\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:26:32 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ahyui-0001M1-8p\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:26:31 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:16584)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ahyui-0001Jt-21\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 08:26:28 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0CXDQA731xV/3D5d0tcgxCEAshgBAICgTw8EQEBAQEBAQGBCkEFg10BAQMBViMFCws0EhQYDSSINwjPIwEBAQEGAQEBAR6KOIEChQUHhC0Fi0SEcIswgzMTkgGCFIFFI4FmVYFZIoJ4AQEB\nX-IPAS-Result: A0CXDQA731xV/3D5d0tcgxCEAshgBAICgTw8EQEBAQEBAQGBCkEFg10BAQMBViMFCws0EhQYDSSINwjPIwEBAQEGAQEBAR6KOIEChQUHhC0Fi0SEcIswgzMTkgGCFIFFI4FmVYFZIoJ4AQEB\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"197394736\"\nReceived: from 75-119-249-112.dsl.teksavvy.com (HELO pastel.home)\n\t([75.119.249.112])\n\tby ironport2-out.teksavvy.com with ESMTP; 21 Mar 2016 08:26:25 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid E0FFE605A0; Mon, 21 Mar 2016 08:26:25 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com>\nDate: Mon, 21 Mar 2016 08:26:25 -0400\nIn-Reply-To: <87twk0beuh.fsf@gmail.com> (Vitalie Spinu's message of \"Mon, 21\n\tMar 2016 06:05:26 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nCc: Alan Mackenzie <acm@muc.de>, emacs-devel <dev@caliopen.local>,\n\tDmitry Gutov <dgutov@yandex.ru>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 12:26:32 -0000\nContent-Length: 3494\nLines: 79\n\n>>> BTW, I parse-partial-sexp must abide hard-widen-limits as well.\n>> I don't understand what this means.  parse-partial-sexp is passed\n>> 2 locations and it works between them.  There's not much opportunity\n>> for widening.\n> parse-partial-sexp should work between hard limits (at least the lower\n> bound). It should operate as if hard-narrowed buffer is the real buffer.\n\nYou mean it should ignore the current (user)narrowing?  Why?\nI'd think that if something needs to ignore the (user)narrowing it'd be\nparse-partial-sexp's *caller* but not parse-partial-sexp itself.\n\n> So ideally it should take (max FROM (car hard-widen-limits)) as the starting\n> position.\n\nYou mean: as opposed to (max FROM (point-min))?\n\nI disagree.  Functions should usually not accept to talk about positions\noutside of the point-min/max range.\n\nNotice how syntax-ppss is different in this regard: since it doesn't\nreceive FROM, that same rule doesn't prevent syntax-ppss from widening\nto (car hard-widen-limits).\n\n> This will give the desired consistency between parse-partial-sexp and\n> syntax-ppss with the price of slightly modifying the semantics of\n> parse-partial-sexp in a backward compatible way.\n\nI'd be curious to know in which circumstances (i.e. specific code in\nspecific packages) this would make a difference.  As mentioned above,\nI think these cases would be better fixed by changing the calling code\nto perform widening before calling parse-partial-sexp.\n\n>>> A patch that would require hunting every single mode out there and\n>>> implementing multi-modes locally should have been more carefully\n>>> considered IMO.\n\n>   - Major mode authors won't need to know about multi-modes. That\n>     means not dealing with chunks/spans/headers etc.  These concepts are\n>     not even uniformly defined between existing multi-mode engines.\n\nI understand that's your claim, but I don't understand why/how this is\ndifferent between the two proposals.\n\n>   - Major mode authors won't need to re-implement the indentation\n>   logic already there in multi-modes. The logic is likely to be too\n>   simplistic and major mode authors will have to re-do it anyways.\n>\n>   - Setup is more general. multi-mode engine decides where to call\n>   calculate-indent-function and with what parameters and with\n>   what narrowing.\n\nSame here.\n\n>   - Arguably calculate-indent-function is a simpler concept to grasp\n\nAs mentioned, I fully agree with the need to replace\nindent-line-function with calculate-indent-function (tho I like to name\nit prog-indent-function).  So the difference is w.r.t your\nSTRONG-BEFORE/AFTER: which code provides them, which code obeys them,\nand how that compares to the way prog-indentation-context is provided\nand obeyed.\n\n>> I think both suggestions require changes to every mode, and in both\n>> cases the changes can be reduced to a one-liner or close enough (for\n>> the simple case).  Admittedly, for it to be a one-liner, we'll need to\n>> provide a standard helper function.\n> Judging from python.el it might be quite hard to provide a generic one\n> liner to deal with all those 3 elements.  For calculate-indent-function\n> instead you can provide a straightforward one line assuming that\n> STRING-BEFORE/AFTER do not matter.\n\nMy hunch is that if STRING-BEFORE/AFTER don't matter, then it should not\nbe hard to come up with a generic function in prog.el which can be\ninvoked with a one liner in the major mode (assuming the major mode\nsets (prog|calculate)-indent-function).\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_627.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai0aP-0003NY-Br\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:13:37 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39207)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0aI-0003D2-Kh\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:13:35 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0aD-0000q9-He\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:13:30 -0400\nReceived: from mail-wm0-x235.google.com ([2a00:1450:400c:c09::235]:38757)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai0aD-0000q4-6A\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:13:25 -0400\nReceived: by mail-wm0-x235.google.com with SMTP id l68so123609557wml.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 07:13:25 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=nqExY8w+E59Jmd4Zr+SlzPpVP/Bnt+XjB9SlyrEYaWQ=;\n\tb=XYdJmhx6Wwwi4VmO5xpbBXyP/ti2FI9n9KThh61wqw/JsxAPVKUhQ3VS0cJr8bEvss\n\tIYsFYwxhBJvf62Pmh0h6Q4uHj02w7hxlYImrJ7zJmAjaYFz/6aWrj7o1fZ4DNgo1hwOF\n\tXeT6NqC/dsl+7QDgdD03kkOk0/SbEJSqUhCuqQALE0U9ryBZCmeSZk+l5wb9x6SwVnuX\n\t6AEZcGIConuIMNWNuNLKVcpzgNrxKdDWwJwjn1qfGKcEIwT45XVQzBmc0+nzMyrfY3c9\n\tc++eMiC1b+1mq7ieq60oWem55bQesgY3KYAaqPLbPyfiP1+EqljJOTrzeLKEoOjV6sAP\n\t4u8w==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=nqExY8w+E59Jmd4Zr+SlzPpVP/Bnt+XjB9SlyrEYaWQ=;\n\tb=FAtw/gJ6Fp/Z8yfT7cJztNncutqopHa7hbGLqGfc0nsyuEAaHASM0gMLt27jsHM/3z\n\tV4Uy+zzEiAikt/3TaxT4S65JQkCrLBiEsJApsCOcuLqkyBptBVPi3RxYZYjZdyjQa1iw\n\t6E5EMdjsnHh7XJC/6dOdoA842xCIRvUXBsTa6kZ8d3YSwCwYwLmVoNH7tP8CPjI5qC47\n\tw6hmbVSmxu/ywjDLECgHQWhS8pz4ADTZRDm6EscmSe/IxatC9a/GKQMNJ9IQSaYjH9Hp\n\t61+C9T4HrK13xVDebMqEbUx2EdjLmxZYkBtnjii/KhommLDKZs1Sv0BTOhH/u8Wnw/QC\n\tnR3w==\nX-Gm-Message-State: AD7BkJKR8NifjryTeWDT7un7kcvYPR+zUk7mWIbLK/06nkg4mC4Yf2h8QPGjY0+KGzURZw==\nX-Received: by 10.28.129.213 with SMTP id c204mr14726010wmd.89.1458569604508; \n\tMon, 21 Mar 2016 07:13:24 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tjo6sm25566640wjb.48.2016.03.21.07.13.22\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 07:13:23 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\nDate: Mon, 21 Mar 2016 15:13:22 +0100\nIn-Reply-To: <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org> (Stefan Monnier's message\n\tof \"Mon, 21 Mar 2016 08:26:25 -0400\")\nMessage-ID: <877fgvgbr1.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::235\nCc: Alan Mackenzie <acm@muc.de>, Dmitry Gutov <dgutov@yandex.ru>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:13:35 -0000\nContent-Length: 7403\nLines: 153\n\n\n\n>> On Mon, Mar 21 2016 08:26, Stefan Monnier wrote:\n\n>> parse-partial-sexp should work between hard limits (at least the lower\n>> bound). It should operate as if hard-narrowed buffer is the real buffer.\n\n> You mean it should ignore the current (user)narrowing?  Why? I'd think that if\n> something needs to ignore the (user)narrowing it'd be parse-partial-sexp's\n> *caller* but not parse-partial-sexp itself.\n\nCurrently it just throws out-of-range errors. So in that sense it does ignore\nuser narrowing in a very inconvenient way.\n\nparse-partial-sexp is called from code exclusively and it just happens that in\nmulti-modes it is called outside of narrow region quite often. That's a major\ninconvenience. Why on earth one would need to take account in user narrowing for\nsyntax parsing? If parse-partial-sexp could be made to always widen to hard\nlimits it will automatically solve a bunch of problems. bug#22983 being one of\nthem, condition-case awkwardness in syntax-ppss being another one, and the\nubiquitous out-of-range errors in font-lock in multi-modes being the most\nimportant one.\n\n>> So ideally it should take (max FROM (car hard-widen-limits)) as the starting\n>> position.\n\n> You mean: as opposed to (max FROM (point-min))?\n\nYes.\n\n> I disagree.  Functions should usually not accept to talk about positions\n> outside of the point-min/max range.\n\nDepends on the function. point-max/min is mostly user level. Why wold syntax\nparsing would need to respect that? Bug#22983 ilustrates that clearly. If user\nnarrows in the middles of a string, it creates huge problems.\n\nNote that with Dmitry's new syntax-ppps-dont-widen proposal syntax-ppps widens\nfirst.\n\nCan I ask you the reverse? What do you gain by respecting user narrowing in\nsyntax parsing?\n\n> Notice how syntax-ppss is different in this regard: since it doesn't\n> receive FROM, that same rule doesn't prevent syntax-ppss from widening\n> to (car hard-widen-limits).\n\nWell, not quite different. It has POS which might be outside of user narrowed\nrange.\n\n>> This will give the desired consistency between parse-partial-sexp and\n>> syntax-ppss with the price of slightly modifying the semantics of\n>> parse-partial-sexp in a backward compatible way.\n\n> I'd be curious to know in which circumstances (i.e. specific code in specific\n> packages) this would make a difference.  As mentioned above, I think these\n> cases would be better fixed by changing the calling code to perform widening\n> before calling parse-partial-sexp.\n\nI think bug#22983 is illustrative enough. Multi-mode code is a nightmare because\nof out-of-range errors in parsing. `syntax-ppss` is protected but that\ncondition-case is triggered in 99.99% of the times in multi-modes.\n\nIn multi modes you really want to keep narrowing because most of the major-mode\nfunctionality works well on narrowed code. Pretty much all of it except\nsyntactic parsing and font-locking. Occasional property lockup outside of\nnarrowed region could be dealt with on case by case basis or, hopefully, with\nnew hard-narrowed-limits at the core of it.\n\n>>>> A patch that would require hunting every single mode out there and\n>>>> implementing multi-modes locally should have been more carefully\n>>>> considered IMO.\n\n>>   - Major mode authors won't need to know about multi-modes. That\n>>     means not dealing with chunks/spans/headers etc.  These concepts are\n>>     not even uniformly defined between existing multi-mode engines.\n\n> I understand that's your claim, but I don't understand why/how this is\n> different between the two proposals.\n\nMajor mode author has to deal with the span explicitly as defined in\nprevious-chunk in prog-indentation-context. Cognitively this is a more demanding\ntask. Ask a new person to go and read the doc of prog-indentation-context and\nask how much he or she understands of it. I read it and I think I understand\nmost of it, but looking at all the usages of prog-widen and prog-first-column in\npython.el my brain gives up. Previous-chunk is not even used in python.el!\n\nThe prog-calculate-indent-function is more general. You can call it on any\nbuffer position (need not be last point in the previous span). It can be called\nwith whatever STRING-BEFORE and STRING-AFTER (these can, but need not be, actual\nstrings in the buffer). Current prog-indentation-context allows for possibility\nof a string to be inserted before begging in of current chunk. STRING-BEFORE is\nmore more general than that because of the arbitrary POS that it can be applied\nto. \n\nMy claim is that we can achieve much higher generality and don't bother mode\nauthors with all those concepts like current/previous span/chunk, starting/end\nposition etc. Only multi-mode engine can take proper care of those anyways.\n\nHere is a simple example when inner mode cannot decide by itself on the\nindentation. Assume for concreteness a noweb header with some code immediately\nfollowing the header:\n\n  <<foo, some_arg=4>>= some_call(blabla) \n      some_other_call(blabla) ## indented by offset 2 with respect to header or prev_chunk\n\nHow do you indent the some_call(blabal) after the header? The most meaningful\nway is to keep it untouched just as user defined it. If inner mode would indent\nit by itself it would give offset of 4. This is a simple example of header\ndependence.\n\nYou can easily imagine more complex cases when not only one previous span need\nto be considered but a range of previous spans of the same inner mode. Moreover\nthere might be nested inner chunks. Which chunk/span will you include in\nprog-indentation-context? The entire previous code chunk or only the last\nhomogeneous span after the most recent inner-inner chunk?\n\nIndentation of a span is commonly dependent on the header of the chunk (note the\nterminology distinction). You can imagine having a parameter in the header that\nwould determine the indentation of the chunk's body. Header-dependence is a\nsimple and common case of inter-span dependence. It's not hard to imagine\ncomplex cases when indentation in current span will depend not only on the\nprevious span of the same mode but on other spans of host mode or even other\ninner (nested or not) modes.\n\nIMO the best way is to leave all this complexities to multi-mode authors to deal\nwith on case by case basis. You never know what sort of complexities and chunk\ndependencies new multi-modes will impose. Better keep things\ngeneric. prog-calculate-indent-function seems like a multi-mode agnostic\nsolution. I am not sure if it will solve all problems, but it's surely solves\nmore than prog-indentation-context does in a cleaner way.\n\nNote on terminology. I put quite some effort to sort things out in\npolymode. Glossary of terms is here:\n\n  https://github.com/vspinu/polymode/tree/master/modes#glossary-of-terms\n\nFor many reasons it's important to distinguish between portions of code that\ninclude header/tails and homogeneous portions of the same mode. Former portions\nI call `chunks` and those can include other chunks of different sub-modes. The\nlatter, homogeneous portions, I call `spans`.\n\nThe fact that core emacs is now starting building pieces of multi-mode\nfunctionality here and there and thus entrenching a somewhat naive\ninterpretation of a \"chunk\" doesn't make me happy. Not a big deal though.\n\n> My hunch is that if STRING-BEFORE/AFTER don't matter,\n\nIt will actually matter for quite some modes in continuation chunks. I was too\noptimistic.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_629.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai13v-0005uv-JD\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 10:44:07 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47605)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai13t-0005uP-Ds\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:44:06 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai13n-0008Cr-Fe\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:44:05 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:2020)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai13n-0008CP-9k\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 10:43:59 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0CtCwA731xV/7yMCkxcgxCEAsEMCYdLBAICgTw5FAEBAQEBAQGBCkEFg10BAQMBViMFCwstBxIUGA0kiDcIzyMBAQEBBgEBAQEeizqELVgHCoQjBYtEkCCDMxOGVosrghSBRSOBZlWBWSKBNAEEG4EkAQEB\nX-IPAS-Result: A0CtCwA731xV/7yMCkxcgxCEAsEMCYdLBAICgTw5FAEBAQEBAQGBCkEFg10BAQMBViMFCwstBxIUGA0kiDcIzyMBAQEBBgEBAQEeizqELVgHCoQjBYtEkCCDMxOGVosrghSBRSOBZlWBWSKBNAEEG4EkAQEB\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"197426201\"\nReceived: from 76-10-140-188.dsl.teksavvy.com (HELO pastel.home)\n\t([76.10.140.188])\n\tby ironport2-out.teksavvy.com with ESMTP; 21 Mar 2016 10:43:57 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid B9D1D605A0; Mon, 21 Mar 2016 10:43:56 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com>\nDate: Mon, 21 Mar 2016 10:43:56 -0400\nIn-Reply-To: <877fgvgbr1.fsf@gmail.com> (Vitalie Spinu's message of \"Mon, 21\n\tMar 2016 15:13:22 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nCc: Alan Mackenzie <acm@muc.de>, Dmitry Gutov <dgutov@yandex.ru>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 14:44:06 -0000\nContent-Length: 4203\nLines: 93\n\n> parse-partial-sexp is called from code exclusively and it just happens\n> that in multi-modes it is called outside of narrow region quite often.\n\nHow/why?  Can you give some concrete scenario?\n\n> That's a major inconvenience. Why on earth one would need to\n> take account in user narrowing for syntax parsing?\n\nBecause you need it for *everything* that looks at the buffer.\nWhy should parse-partial-sexp be different from (say) scan-sexps?\n\n> If parse-partial-sexp could be made to always widen to hard limits it\n> will automatically solve a bunch of problems. bug#22983 being one of them,\n\nBug#22983 should be fixed by widening, indeed, but it should be done in\nsyntax.el.  Widening in parse-partial-sexp would only address some cases\nbut not all (e.g. the syntax-begin-function cases or the\nsyntax-propertize-function cases).  Those other cases can only be fixed\nin syntax.el.\n\n> the ubiquitous out-of-range errors in font-lock in multi-modes being\n> the most important one.\n\nI'm not familiar with those, so if you could give some examples it\nwould help us judge if they would indeed benefit from a fix in\nparse-partial-sexp rather than elsewhere.\n\n>> Notice how syntax-ppss is different in this regard: since it doesn't\n>> receive FROM, that same rule doesn't prevent syntax-ppss from widening\n>> to (car hard-widen-limits).\n> Well, not quite different. It has POS which might be outside of user narrowed\n> range.\n\nNo: POS should be within point-min/max.\n\n> Major mode author has to deal with the span explicitly as defined in\n> previous-chunk in prog-indentation-context. Cognitively this is a more\n> demanding task. Ask a new person to go and read the doc of\n> prog-indentation-context and ask how much he or she understands of\n> it. I read it and I think I understand most of it, but looking at all\n> the usages of prog-widen and prog-first-column in python.el my brain\n> gives up. Previous-chunk is not even used in python.el!\n\nReplace all your widen calls with calls to `prog-widen' and you get\nthe same result (since (nth 1 prog-indentation-context) is basically\nanother name for your hard-widen-limit).  So I don't think prog-widen is\nthat very complicated.\n\nAs for prog-first-column the local major mode can just ignore it in\nwhich case the multi-mode can do the same that you do.  It's only useful\nif you need/want to provide a more complex behavior than what\npolymode supports.  So, of course, it's more complex.\n\n> The prog-calculate-indent-function is more general. You can call it on\n> any buffer position (need not be last point in the previous span).\n\n[ Note: In my mind, the \"natural main case\" for multi-mode indentation\n  is when you call the indentation function on the *first position* of\n  a span.  But you seem to look at it from the other end, where you call\n  the indentation function on the *last position* of the previous span.\n  I think I'm beginning to see why.  ]\n\nNote that \"is more general\" here means that the major mode's function\nhas to handle more cases, so it would seem to fundamentally require more\nwork on the major mode's side.\n\n> Current prog-indentation-context allows for possibility of a string to\n> be inserted before begging in of current chunk.  STRING-BEFORE is more\n> more general than that because of the arbitrary POS that it can be\n> applied to. \n\nGood point.  I didn't think of that.  Do you make use of that\npossibility, and/or can you give an example where it's useful?\n\n> Here is a simple example when inner mode cannot decide by itself on\n> the indentation.  Assume for concreteness a noweb header with some code\n> immediately following the header:\n>\n>   <<foo, some_arg=4>>= some_call(blabla) \n>       some_other_call(blabla) ## indented by offset 2 with respect to header or prev_chunk\n>\n> How do you indent the some_call(blabal) after the header? The most\n> meaningful way is to keep it untouched just as user defined it. If\n> inner mode would indent it by itself it would give offset of 4. This\n> is a simple example of header dependence.\n\nMaybe it's because I'm not familiar with noweb, but I didn't understand\nthis example.  It looks like a very interesting example, so could you go\nover it again in much more detail?\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_631.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai2xe-0002YZ-NM\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 12:45:46 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37906)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai2xc-0002XY-7n\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 12:45:45 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai2xY-0006JG-Tu\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 12:45:44 -0400\nReceived: from mail-wm0-x22c.google.com ([2a00:1450:400c:c09::22c]:37173)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai2xY-0006Iw-Jr\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 12:45:40 -0400\nReceived: by mail-wm0-x22c.google.com with SMTP id p65so129506298wmp.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 09:45:40 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=gto1WBAGGwh+EP/tIG/7WzqcmML65U7m2lpJO++Mm0k=;\n\tb=0MMLiv1gMr5+Y+j3fgpn9kNNgZddgE65lVC8ZD94879aecqYyCWmFDx84XGzgFIDWa\n\tpUS5BGlgEVFQwv2bPScNwYzuPUkgBgJWcmrL7D1g6imFT+0dQkCV161pdwuVwvvKeO1C\n\tt6VpNO7RtLg4epWSVKpQH67KOGLa9jgAePm05224nvVAVgSw4giTcUyt7e4oiO706PKK\n\tkspUO2a5NVrZihjfpwmKNQuJU4ih98k7eSSb7zg1frj/rhHNkzj974L9zoy8mAk72bwE\n\tqRpGfh2qXxS5QwRHFXwY8ofYiqMEyvXcUV/Zqlaw8f5sbNhFrl5kVfHiHMIInHHwkkyK\n\tsXRA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=gto1WBAGGwh+EP/tIG/7WzqcmML65U7m2lpJO++Mm0k=;\n\tb=A/IxH3SjWyU4MHitZBzRFGsEwOIwfhaomiDUXHK+UG3RT3zlEHVj5eIoUUFNRSYinN\n\t8z943ZJ8ZSL+WAOzX3hNdTf+cMKsWww6J9k6aTNfKuHkjzhWEghqIeTXKBxj2qS8abaF\n\t8s9GvefJ8XSiBVVmz9yORaIJtrj8NGTLfqh7QV8i8ezA15M5HSkgdzVCrAz8hK8XtyOX\n\t0wxunDQ4/h+129oEyFp3lzBdpucny1yCk376wHNqHyCTHgZ8P4KUwYJiU91E3QXVu5mt\n\tfBg3LKePn4+vlhfMHejj00l9D1OAvCeqRjE+W8w1ivlU1GC1kzXOJ4S4PFQjanNv45Mu\n\t8k2w==\nX-Gm-Message-State: AD7BkJIhkUg2n1vwKcpIBrXnNLag0aHB2u0qcQhufmi5TKA+KHhAnX+k/4hLKVajpzr5XA==\nX-Received: by 10.28.125.195 with SMTP id y186mr14161372wmc.79.1458578739860; \n\tMon, 21 Mar 2016 09:45:39 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tn66sm13333852wmg.20.2016.03.21.09.45.38\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 09:45:38 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\nDate: Mon, 21 Mar 2016 17:45:38 +0100\nIn-Reply-To: <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org> (Stefan Monnier's message\n\tof \"Mon, 21 Mar 2016 10:43:56 -0400\")\nMessage-ID: <87bn67eq4t.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22c\nCc: Alan Mackenzie <acm@muc.de>, emacs-devel <dev@caliopen.local>,\n\tDmitry Gutov <dgutov@yandex.ru>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 16:45:45 -0000\nContent-Length: 4716\nLines: 95\n\n\nI have split the answer in two separate conceptually distinct parts. This one is\nabout indentation complexities of generic multi-modes.\n\n>> On Mon, Mar 21 2016 10:43, Stefan Monnier wrote:\n\n>> Current prog-indentation-context allows for possibility of a string to be\n>> inserted before begging in of current chunk.  STRING-BEFORE is more more\n>> general than that because of the arbitrary POS that it can be applied to.\n\n> Good point.  I didn't think of that.  Do you make use of that\n> possibility, and/or can you give an example where it's useful?\n\nPlease see example of erb mode below.\n\n>> Here is a simple example when inner mode cannot decide by itself on\n>> the indentation.  Assume for concreteness a noweb header with some code\n>> immediately following the header:\n>>\n>>   <<foo, some_arg=4>>= some_call(blabla) \n>>       some_other_call(blabla) ## indented by offset 2 with respect to header or prev_chunk\n>>\n>> How do you indent the some_call(blabal) after the header? The most\n>> meaningful way is to keep it untouched just as user defined it. If\n>> inner mode would indent it by itself it would give offset of 4. This\n>> is a simple example of header dependence.\n\n> Maybe it's because I'm not familiar with noweb, but I didn't understand this\n> example.  It looks like a very interesting example, so could you go over it\n> again in much more detail?\n\n\nNoweb is not essential here. The story will hold for pretty much all multi-modes\nwith non-full-line headers. In noweb `<<foo, some_arg=4>>=` is a header of a\nchunk. Polymode places heads and tail in their own modes because they are not\nconceptually part of nor host or sub-mode. You can specify arbitrary parameters\nin the head which might even instruct how to indent the chunk. The first code\nline `some_call(blabla)` is placed on the same line with the head. This is\nuncommon but it's the simplest real case I can think of.\n\nThere are two issues here.\n\nFirst one is how do you indent the head itself? Let's assume the point is after\n`foo`. If you follow the naive prog-indentation-context the indentation should\nbe handled by the mode in the head chunk, right? Let's call it\nnoweb-head-mode. This mode is the same for many noweb host-mode/inner-mode\ncombinations and defaults to poly-head-tail-mode. Host mode is commonly LaTeX\nbut it can be anything. One reasonable way to indent it is to use the host mode\nindentation engine. Note that this is in contrast of the\nprog-indentation-context assumption for which PREVIOUS-CHUNK is assumed to be of\nthe same mode type as the current type.\n\nThe second issue is with respect to the first line immediately after the\nheader. If you naively call inner mode indentation engine on that line in a\nnarrowed buffer starting after >>= you will get it indented to FIRST-COLUMN,\nwhich in the above case is the indentation of the head, plus noweb chunk offset\nwhich is a polymode specific thing and it is customizable per inner mode. Do you\nreally want to insert that space after >>=? Probably not. So the code following\nthe header is special. That means that you will either have to take care of that\nin multi-mode engine or extend prog-indentation-context.\n\nThink of markdown simple code spans `this = is(a, codes, span)` which can occur\nanywhere in the buffer. Indentation is not meaningful within the span at all,\nthe whole chunk should be indented by the outer mode just before the opening `.\n\nReal trouble comes with continuation chunks. You might need to have a completely\nreversed indentation logic - in outer/host spans MM engine needs to call inner\nmode for indentation. Consider this example of erb mode taken from\nhttps://github.com/fxbois/web-mode/blob/master/tests/demo.erb.\n\n    <div id='header'>\n      <% if signed_in? -%>\n        <%= link_to t('.sign_out'), sign_out_path, :method => :delete %>\n      <% else -%>\n        <%= link_to t('.sign_in'), sign_in_path %>\n      <% end -%>\n    </div>\n\nOne meaningful approach here is to indent if-else-end block using inner mode\nrules, right? This is what web-mode seems to be currently doing. Assume you are\njust in front of `<%= link_to`. This is host hmtl mode. But you need to indent\naccording to inner mode construct. So what do you do? You go to the end of\nprevious code chunk and call prog-indentation-function of inner mode with\nSTRING-BEFORE = \"\\n\" and STRING-AFTER=\"link_to t('.sign_out'), sign_out_path,\n:method => :delete\". Simple isn't it? That's precisely my proposal.\n\nThe message is that whatever you try you will not be able to completely leave\nall the work to inner mode or capture it with naive constructs like\nprog-indentation-context. Quite the opposite, new complexities are likely to\nmake multi-mode authors life harder.\n\n\n  Vitalie\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_633.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiNf1-00049o-I7\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 10:51:55 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42824)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiNeu-00047x-D3\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 10:51:54 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiNeq-0008QN-6R\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 10:51:48 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:56948)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiNep-0008Q6-Rt\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 10:51:44 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiNen-0008KY-It\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 15:51:41 +0100\nReceived: from 69-196-182-150.dsl.teksavvy.com ([69.196.182.150])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 15:51:41 +0100\nReceived: from monnier by 69-196-182-150.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 15:51:41 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nDate: Tue, 22 Mar 2016 10:51:33 -0400\nMessage-ID: <jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-196-182-150.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:s4yOedtj2cGQd1ZYhvGdtZDK9MU=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 14:51:54 -0000\nContent-Length: 4582\nLines: 123\n\n> Noweb is not essential here.  The story will hold for pretty much all\n> multi-modes with non-full-line headers.  In noweb `<<foo,\n> some_arg=4>>=` is a header of a chunk.  Polymode places heads and tail\n> in their own modes because they are not conceptually part of nor host\n> or sub-mode.  You can specify arbitrary parameters in the head which\n> might even instruct how to indent the chunk.  The first code line\n> `some_call(blabla)` is placed on the same line with the head.  This is\n> uncommon but it's the simplest real case I can think of.\n\nOK.\n\n> There are two issues here.\n\nHaha!\n\n> First one is how do you indent the head itself?  Let's assume point is\n> after `foo`.  If you follow the naive prog-indentation-context the\n> indentation should be handled by the mode in the head chunk, right?\n\nIf it's got its own \"chunk\", then yes.\n\n> Let's call it noweb-head-mode.  This mode is the same for many noweb\n> host-mode/inner-mode combinations and defaults to poly-head-tail-mode.\n\nOK.\n\n> Host mode is commonly LaTeX but it can be anything.\n\nOK.\n\n> One reasonable way to indent it is to use the host mode\n> indentation engine.\n\nRight, since the beginning of line is still in latex-mode, the \"<<foo,\nsome_arg=4>>= some_call(blabla)\" line would be indented by latex-mode.\nI.e. the generic code would go to BOL, and call latex-mode's indentation\nwhile setting prog-indentation-context with an \"end of chunk\" that's\nat point.\n\n> Note that this is in contrast of the prog-indentation-context\n> assumption for which PREVIOUS-CHUNK is assumed to be of the same mode\n> type as the current type.\n\nNot sure what PREVIOUS-CHUNK has to do with it.\n\n> The second issue is with respect to the first line immediately after the\n> header.\n\nSince it's not on its own line, I don't see why it would be an issue\nfor indentation.\n\n> If you naively call inner mode indentation engine on that line in a\n> narrowed buffer starting after >>= you will get it indented to FIRST-COLUMN,\n\nThat's also one of the reasons why I didn't want to impose narrowing in\nprog-indentation-context.\n\n> mode for indentation. Consider this example of erb mode taken from\n> https://github.com/fxbois/web-mode/blob/master/tests/demo.erb.\n>\n>     <div id='header'>\n>       <% if signed_in? -%>\n>         <%= link_to t('.sign_out'), sign_out_path, :method => :delete %>\n>       <% else -%>\n>         <%= link_to t('.sign_in'), sign_in_path %>\n>       <% end -%>\n>     </div>\n\nIIUC, we have here a tight interleaving of lots of little chunks,\nalternating between HTML and ..[according to duckduckgo].. Ruby.\n\n> One meaningful approach here is to indent if-else-end block using inner mode\n> rules, right?\n\nAnother approach would be to consider it as a sequence of chunks, rather\nthan as chunks of one mode nested in another.  So each chunk controls\nthe FIRST-COLUMN of the next chunk.\n\nIn any case, this seems messy.\n\n> This is what web-mode seems to be currently doing. Assume you are\n> just in front of `<%= link_to`. This is host hmtl mode. But you need to indent\n> according to inner mode construct. So what do you do? You go to the end of\n> previous code chunk and call prog-indentation-function of inner mode with\n> STRING-BEFORE = \"\\n\" and STRING-AFTER=\"link_to t('.sign_out'), sign_out_path,\n> :method => :delete\". Simple isn't it? That's precisely my proposal.\n\nAh, now I see a use of STRING-BEFORE and STRING-AFTER, thanks.\n\nThis case of STRING-BEFORE being \"\\n\" is very special: in SMIE, the core\nindentation function (smie-indent-calculate) basically behaves as if\nit's always called with STRING-BEFORE=\"\\n\".\n\nIOW, we could define prog-indent-function as always behaving \"as if\nSTRING-BEFORE was \\n\".  In the normal case, the foo-calculate-indent\nfunction is called at the beginning of line anyway, so adding\na STRING-BEFORE=\"\\n\" won't affect its behavior.\n\nAs for STRING-AFTER, the example is compelling, but I don't yet\nunderstand really how it would all work out overall.\nI'm thinking of cases like:\n\n    <% 3.times do %>\n      <li>\n        some text\n        <% if signed_in? -%>\n          <%= link_to t('.sign_out'), sign_out_path, :method => :delete %>\n        <% else -%>\n          <%= link_to t('.sign_in'), sign_in_path %>\n        <% end -%>\n      </li>\n    <% end %>\n\nHow should the \"generic\" code that links HTML and Ruby know when to\nindent using the HTML indentation code and when to use the Ruby\nindentation rules?\n\nMaybe my suggestion of considering it as a sequence of chunks (where each\nchunk controls the FIRST-COLUMN of the next chunk) could work, but it's\nfar from obvious.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_635.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiR17-0000KU-BD\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 14:26:57 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46615)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiR13-0000BU-8q\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 14:26:54 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiR10-0001Rs-2x\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 14:26:53 -0400\nReceived: from mail-wm0-x22e.google.com ([2a00:1450:400c:c09::22e]:37516)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiR0z-0001R1-LG\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 14:26:49 -0400\nReceived: by mail-wm0-x22e.google.com with SMTP id p65so175633085wmp.0\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 11:26:49 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=9KT2kUqZENPYV6+qXKdZ1q65DBaKDLfZIug97o4IoDk=;\n\tb=vyZWzPXyUp3T5vVkE5bRiMDiB/EphzVmaZnA5g5M1HENfJAe/TrdROIUuaiXUfuJBk\n\t7g1A+SOgVcNWm1trFGoPta67GvDtlyL+Su1lrD4ji0uq/e7lUqpiRJ47c4FasPJIbfjD\n\th5a6hh/UZj6yYj53Hqdyoio3ZOIfnU5LH3ME0pqzbPtW9udN8plFE1BYiOfJmJ2J/oRW\n\tINUVSbo38yWxcFthrHI2+kv7lxLoyDaFo4fnE5j6dzxLfm2zTJ1aAcZD8JG0VMLo19YA\n\t1kb5XSoXJ6g60SFU2hv8xzCp5W2w5k3VWeCPbeezPLYVzSlBDacBBbNRTV9lpuQCJSQD\n\tBJBQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=9KT2kUqZENPYV6+qXKdZ1q65DBaKDLfZIug97o4IoDk=;\n\tb=i9zhsB3fhpKcaMDpg4gqcMURdAwEsW9PNOgfPQXwJmllTMXUkpghfAFj0S6CJU5Jxz\n\t/S5TWy60j7MdbXXzGVfQE490G070ISGMh3AuXhraL0BvOl2Bi+MNFjlIYtHGwbOEuNt5\n\trqevHDrouwJjbZFtZu2vQlz7Nj3JOsmwawqpU9g2kUdrneMXLcp+yTYJX7H91J6NgGo9\n\tHWwaGbZzcEDySdkXjbms1xFIMb6+KT5cFe1BPg2C9/d+fBIvzYOcjAGb9x9lSGu0ROe9\n\th6HFwTP0Wns9NRR4WoRx3CWKecLR8zhZWznOGsr11/rhKt1IkJYA7KSuP88EZuW3iSMB\n\tC0pA==\nX-Gm-Message-State: AD7BkJIiyKD5h9x3q2oVNbCfbd/VsMijg/VLXHM+RgEtbNpFjTVZR3jbyDhRFs5M2A1dWQ==\nX-Received: by 10.28.127.144 with SMTP id a138mr20411388wmd.25.1458671208896; \n\tTue, 22 Mar 2016 11:26:48 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\ty3sm18537843wmy.17.2016.03.22.11.26.47\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tTue, 22 Mar 2016 11:26:47 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Tue, 22 Mar 2016 19:26:47 +0100\nIn-Reply-To: <jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Tue, 22 Mar 2016 10:51:33 -0400\")\nMessage-ID: <87bn66qsgo.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 18:26:54 -0000\nContent-Length: 1040\nLines: 26\n\n\n\n>> On Tue, Mar 22 2016 10:51, Stefan Monnier wrote:\n\n> As for STRING-AFTER, the example is compelling, but I don't yet\n> understand really how it would all work out overall.\n\nHow about passing signatures to indentation-funciton?\n\nAssume that there is a way to represent previous indentation context with a\nsimple data structure, akin to parse-partial-sexp but for indentation. Then you\ncan compute indentation context of a span by passing to the indentation-funciton\nthe content of the previous location. \n\nOf course the indentation context data structure should be mode specific and\nmodes must be constructing it themselves. But some useful degree of uniformity\nis surely possible. For example FIRST-COLUMN is a very simple one dimensional\nsignature.\n\nBesides being useful for incremental indentation within a mode, it can be\ndirectly leveraged by multi-modes. Just pick the context from previous chunk,\nmodify usefully and pass to the next chunk. (Of course locations of previous\nchunks should not be part of the signature).\n\n Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_637.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiYCV-0002zl-OS\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 22:07:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57487)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aiYCT-0002zf-Gw\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 22:07:10 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aiYCO-00007D-Ef\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 22:07:09 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:38570)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aiYCO-00005q-AM\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 22:07:04 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0A2FgA731xV/5a2xEVcgxCEAoVVwwsEAgKBPDwRAQEBAQEBAYEKQQWDXQEBAwFWIwULCzQSFBgNJIg3CM8jAQEBBwEBAQEeizqFBQeELQEEsz+BRSOBZlWBWSKCeAEBAQ\nX-IPAS-Result: A0A2FgA731xV/5a2xEVcgxCEAoVVwwsEAgKBPDwRAQEBAQEBAYEKQQWDXQEBAwFWIwULCzQSFBgNJIg3CM8jAQEBBwEBAQEeizqFBQeELQEEsz+BRSOBZlWBWSKCeAEBAQ\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"197854700\"\nReceived: from 69-196-182-150.dsl.teksavvy.com (HELO fmsmemgm.homelinux.net)\n\t([69.196.182.150])\n\tby ironport2-out.teksavvy.com with ESMTP/TLS/DHE-RSA-AES256-SHA;\n\t22 Mar 2016 22:07:02 -0400\nReceived: by fmsmemgm.homelinux.net (Postfix, from userid 20848)\n\tid 301AFAE1F4; Tue, 22 Mar 2016 22:07:01 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <jwvzitquf5p.fsf-monnier+emacs@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87bn66qsgo.fsf@gmail.com>\nDate: Tue, 22 Mar 2016 22:07:01 -0400\nIn-Reply-To: <87bn66qsgo.fsf@gmail.com> (Vitalie Spinu's message of \"Tue, 22\n\tMar 2016 19:26:47 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 02:07:10 -0000\nContent-Length: 704\nLines: 19\n\n> Of course the indentation context data structure should be mode specific and\n> modes must be constructing it themselves. But some useful degree of uniformity\n> is surely possible. For example FIRST-COLUMN is a very simple one dimensional\n> signature.\n\nYes, it's an attractive idea.  But for example in the case of SMIE we\nnever compute this context directly, instead we discover it as we parse\nthe text backward from point.\n\nBut I guess we could represent the context as an integer (the position\nfrom which to parse backward).\n\nStill, in the ERB case we'd need to mix the HTML context with the Ruby\ncontext, so the representation of the context can't be \"internal to the\nmajor mode\".\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_639.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aigTB-0008O9-1q\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 06:56:57 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49142)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aigT8-0008MW-9I\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 06:56:55 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aigT5-0005KR-Iz\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 06:56:54 -0400\nReceived: from mail-wm0-x22d.google.com ([2a00:1450:400c:c09::22d]:36744)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aigT5-0005KD-Bx\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 06:56:51 -0400\nReceived: by mail-wm0-x22d.google.com with SMTP id r129so130426202wmr.1\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 03:56:50 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=/KSr9ixaJbcbNAy90NjNhYqU1jyslMeumoF3CNrGoPM=;\n\tb=ThtWZxUOliXQCWaPgxp0eh+QTgFtOqcgCgjyZ6RdPFJMZIDjH81Nn2C+Ob5Dy+PsCG\n\tcwq5d1exvO+/FlwrIMiIhBSuXsjDxwTi92gfoic8+pHhnxPn2QrR0pwXQKM8RISmfZwP\n\tbScSaPqzV+YXXspFjw0F1HM9uEDlRlpwyrCXX4mk6atnShASOVKnuU0wnmgpDwDPmgIE\n\tKdFqi9IM3I7z460hybqq5TDDhbw/FtoYnGsxO35y1e4Rk1Z0clJLpTA5wHLxfMDtpKrw\n\t+BI2y1865i9vQQLitZzx6xg2rOCFmQyO9GKnWSHfKQxUphPVjaZnFrLNJ5lyncGb07BJ\n\taYqQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=/KSr9ixaJbcbNAy90NjNhYqU1jyslMeumoF3CNrGoPM=;\n\tb=FO9z6PJGeT3bk9oeZzubleLjDiE75RWpdWLRiqfaEHQsmNOGrp0HCF3/HFM+hE4Im2\n\tzBlxiF/8gCod+auBq5eBc7uMeMeFqiL86EgpayK7xBqwm4l4hqt96Yv7Y11ZnuL/I3A3\n\tTWnsLmNuVnQdnPqzhK+Nrph2r7M6P8VVrpkG1uRryM+3FDMUux7vF0OlNLOG4AIGmRGr\n\tg1Y8n5czd1h88nkor67+0s81HZRw3aPdl2IstTgXJuAf+JnpTOaVx/VIcPjaxsNzNWk5\n\tdEczvL4RsYAw7mxp8otVoll3TjjnK9Y2AjRyKWKt3GfMgi+uw4D12tC+JTU/QHPwuH8Z\n\t9C6A==\nX-Gm-Message-State: AD7BkJJOVsOj1092BJGksV/sGg0OE5jNg5mO9EugFAB3fp+UUJ5/DIn+FNVbjdz83euHmw==\nX-Received: by 10.28.10.149 with SMTP id 143mr24372445wmk.38.1458730610035;\n\tWed, 23 Mar 2016 03:56:50 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tv5sm21544105wmg.16.2016.03.23.03.56.48\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tWed, 23 Mar 2016 03:56:48 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87bn66qsgo.fsf@gmail.com> <jwvzitquf5p.fsf-monnier+emacs@gnu.org>\nDate: Wed, 23 Mar 2016 11:56:48 +0100\nIn-Reply-To: <jwvzitquf5p.fsf-monnier+emacs@gnu.org> (Stefan Monnier's message\n\tof \"Tue, 22 Mar 2016 22:07:01 -0400\")\nMessage-ID: <87fuvhpimn.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22d\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 10:56:55 -0000\nContent-Length: 1727\nLines: 36\n\n\n\n>> On Tue, Mar 22 2016 22:07, Stefan Monnier wrote:\n\n> Still, in the ERB case we'd need to mix the HTML context with the Ruby\n> context, so the representation of the context can't be \"internal to the\n> major mode\".\n\nThe signature is internal to the inner mode but will have a generic part which\nmulti-mode can understand and modify. Multi-mode will take care of the\ninterleaving and mixing when needed. I think there is absolutely no way to avoid\na \"superviser\", but each inner mode need not know about the big picture.\n\nIn erb case each ruby line will be asked for an indentation offset in a narrowed\nbuffer with context from previous ruby span. Then multi-mode will indent the\nwhole <% ... %> according to this inner offset plus an offset derived from\nparent html element. The last step is to ask the ruby mode to produce an\nindentation context at the end of this ruby line and cache it.\n\n> But I guess we could represent the context as an integer (the position from\n> which to parse backward).\n\nNo, no. Context should not have absolute positions in it. That would ruin the\nwhole thing. It should contain information about the nesting of language\nconstructs sufficient to be able to indent first line of an inner span without\nany other positional knowledge.\n\nFor example, if current line is directly part of IF block, most languages don't\ncare what precedes IF head at all, only the offset of IF. So an entry in the\ncontext data structure might look like (IF . IF-OFFSET). If line number within\nthe block is important then you can pass (IF IF-OFFSET . RELATIVE-LINE). My\nhunch is that for most languages you would be able to reduce indentation to a\nsmall number of block-continuation constructs (less than 10).\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_641.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aihAH-0003KU-1y\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 07:41:29 -0400\nReceived: from eggs.gnu.org ([208.118.235.92]:33699)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aihAE-0003JT-Nt\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 07:41:27 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aihAB-00069L-HZ\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 07:41:26 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:19324)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1aihAB-00068z-D0\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 07:41:23 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0A2FgA731xV/5a2xEVcgxCEAoVVwwsEAgKBPDwRAQEBAQEBAYEKQQWDXQEBAwFWIwULCzQSFBgNJIg3CM8jAQEBBwEBAQEeizqFBQeELQEEsz+BRSOBZlWBWSKCeAEBAQ\nX-IPAS-Result: A0A2FgA731xV/5a2xEVcgxCEAoVVwwsEAgKBPDwRAQEBAQEBAYEKQQWDXQEBAwFWIwULCzQSFBgNJIg3CM8jAQEBBwEBAQEeizqFBQeELQEEsz+BRSOBZlWBWSKCeAEBAQ\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"197921844\"\nReceived: from 69-196-182-150.dsl.teksavvy.com (HELO fmsmemgm.homelinux.net)\n\t([69.196.182.150])\n\tby ironport2-out.teksavvy.com with ESMTP/TLS/DHE-RSA-AES256-SHA;\n\t23 Mar 2016 07:41:21 -0400\nReceived: by fmsmemgm.homelinux.net (Postfix, from userid 20848)\n\tid 44331AE242; Wed, 23 Mar 2016 07:41:21 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <jwv1t71v3lb.fsf-monnier+emacs@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87bn66qsgo.fsf@gmail.com> <jwvzitquf5p.fsf-monnier+emacs@gnu.org>\n\t<87fuvhpimn.fsf@gmail.com>\nDate: Wed, 23 Mar 2016 07:41:21 -0400\nIn-Reply-To: <87fuvhpimn.fsf@gmail.com> (Vitalie Spinu's message of \"Wed, 23\n\tMar 2016 11:56:48 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 11:41:27 -0000\nContent-Length: 1921\nLines: 52\n\n> No, no. Context should not have absolute positions in it. That would ruin the\n> whole thing. It should contain information about the nesting of language\n> constructs sufficient to be able to indent first line of an inner span without\n> any other positional knowledge.\n\nAs explained, the SMIE indentation has no such \"summary of context\".\nThe only context it could use is either a position or the complete text\nbefore that position.\n\n> For example, if current line is directly part of IF block, most\n> languages don't care what precedes IF head at all, only the offset\n> of IF.\n\nYes, there are simple cases we know how to handle.  The problem is the\ngeneral case, along with the work to modify the existing indentation\ncodes to be able to generate and use that data.\n\n> So an entry in the context data structure might look like (IF\n> . IF-OFFSET). If line number within the block is important then you\n> can pass (IF IF-OFFSET . RELATIVE-LINE). My hunch is that for most\n> languages you would be able to reduce indentation to a small number of\n> block-continuation constructs (less than 10).\n\nConsider\n\n    x = a << b + c * d\n\nthe code after this line can be indented in various different ways:\n\n                 * e\nor\n             + e\nor\n        == e\nor\n  ; e\n\nAnd of course, in this example, I put all relevant operators, but in\npractice they'll generally be on different lines.  And SMIE (which\nsupports that kinds of indentation, e.g. in sm-c-mode) doesn't\npre-compute that context: it's only when it sees the \"+\" at the\nbeginning of line that it moves back over higher-precedence operators to\nfind the matching alignment spot.\n\nIn theory, SMIE could try to create the kind of context you're thinking\nof, but that would amount to a complete rewrite (and it would likely be\nvery difficult if not impossible to make it work with existing\nsmie-rules-functions, so it'd break backward compatibility).\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_643.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aizh4-0000Rp-1s\n\tfor mharc-dev@caliopen.local; Thu, 24 Mar 2016 03:28:34 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38946)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1aizh0-0000QO-NY\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 03:28:31 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1aizgv-0004SQ-M9\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 03:28:30 -0400\nReceived: from mout.kundenserver.de ([212.227.126.135]:61517)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1aizgv-0004SE-Bo\n\tfor dev@caliopen.local; Thu, 24 Mar 2016 03:28:25 -0400\nReceived: from [192.168.178.35] ([77.3.41.115]) by mrelayeu.kundenserver.de\n\t(mreue003) with ESMTPSA (Nemesis) id 0La0Mr-1a3vFh02JB-00llq5;\n\tThu, 24 Mar 2016 08:28:20 +0100\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: dev@caliopen.local\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<jwvmvpswowh.fsf-monnier+Inbox@gnu.org> <87twk0beuh.fsf@gmail.com>\n\t<jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org> <877fgvgbr1.fsf@gmail.com>\n\t<jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org> <87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87bn66qsgo.fsf@gmail.com> <jwvzitquf5p.fsf-monnier+emacs@gnu.org>\n\t<87fuvhpimn.fsf@gmail.com> <jwv1t71v3lb.fsf-monnier+emacs@gnu.org>\nFrom: =?UTF-8?Q?Andreas_R=c3=b6hler?= <andreas.roehler@online.de>\nMessage-ID: <56F3977B.5030308@online.de>\nDate: Thu, 24 Mar 2016 08:30:03 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101\n\tIcedove/38.5.0\nMIME-Version: 1.0\nIn-Reply-To: <jwv1t71v3lb.fsf-monnier+emacs@gnu.org>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-Provags-ID: V03:K0:mMPAf2IPO/4uxYp2N0ZxeYc8c2dnfQrQHVxc7/OqPqteMFjql1c\n\t4LyjkYB5ChQ9TniUQqiyPYXk7bk+Z+l0JcyhnS+rh4R4PRB04gDTidIjpHFGHHRUd+yko2g\n\tIKWHJAxiFD2kBHx5twHXtGzmXuHtXaKFqZTuJ6/LEbPEYvybb7umy2NqAr5EXLn8HfRzfOs\n\tdQ8W92Vdvw5NlK5FPVGHw==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:rxl33/cItUs=:QuszTJlKYV9ZVlirhsf/Ir\n\tmvB/yASOLrF5QWA2Z+VJ1cKkGn2SmWt7EOIBDuf44F7kv2gm/l6ApEM3bllAYik7KmuiSIMqW\n\tjbOEgrZN4tYPXqeVLQBqejd3xM2wximHKn408uqAEfrr61M1hvho6hmPeYfdb7MmREWRli7GB\n\tJJJxiitedV31vG6A4nbCwfsOMfSb63OlOwYXrtD44yW3zyGj5/wNO/cMnVZmjonNezVUMLkc9\n\t9rYyEN3Ry2P0GsOj2vz/MXM1Kpzp4OtQdICXMhY+10xhGuMES1likJk2GqdpXR3gTeL8cERfJ\n\tPSz9RZk4RDz/H0DX42GO0uFa7ezY9RgsWd0IOFAHsBES126rSJKanIbe2tEyEr1cpu2+lQMdd\n\tI79nZaqgabfisFoCiL/9s52weqsDrR3WTekeOlHHdaM0EZGofcDFOdLxlb/ALh3bMmTXCOAAo\n\trEZO0Ev6FwHH5u/DAex5XAXqtdPJlp9NTYaweOADR8sJ5v5AHOst7hr5cybtOiXoArWWqqJ4S\n\t3Lvydk2mjMDUSHsdQ+BT0YBZuJ7mVUGcPBSeVbncEVL6OCAac9Y9vo83hb02nKYbWU5tM0TLv\n\t/5hF7I04wf2Ofr3c8p/4JZJ/JFMMyT4ojwf7NnoAnWXjoMIfQiSNICnWPR06rM69rFYgpqgAp\n\tUZnjusWJLFggfW5dXP+JlwTtRMEbZ5nLs/vnYovr286Ygpbon622hkw6wonpffKKNzvqah8Z8\n\t4abUo+SgO7wILdVm\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.135\nCc: Vitalie Spinu <spinuvit@gmail.com>,\n\tStefan Monnier <monnier@iro.umontreal.ca>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 24 Mar 2016 07:28:31 -0000\nContent-Length: 2338\nLines: 59\n\n\n\nOn 23.03.2016 12:41, Stefan Monnier wrote:\n>> No, no. Context should not have absolute positions in it. That would ruin the\n>> whole thing. It should contain information about the nesting of language\n>> constructs sufficient to be able to indent first line of an inner span without\n>> any other positional knowledge.\n> As explained, the SMIE indentation has no such \"summary of context\".\n> The only context it could use is either a position or the complete text\n> before that position.\n>\n>> For example, if current line is directly part of IF block, most\n>> languages don't care what precedes IF head at all, only the offset\n>> of IF.\n> Yes, there are simple cases we know how to handle.  The problem is the\n> general case, along with the work to modify the existing indentation\n> codes to be able to generate and use that data.\n>\n>> So an entry in the context data structure might look like (IF\n>> . IF-OFFSET). If line number within the block is important then you\n>> can pass (IF IF-OFFSET . RELATIVE-LINE). My hunch is that for most\n>> languages you would be able to reduce indentation to a small number of\n>> block-continuation constructs (less than 10).\n> Consider\n>\n>      x = a << b + c * d\n>\n> the code after this line can be indented in various different ways:\n>\n>                   * e\n> or\n>               + e\n> or\n>          == e\n> or\n>    ; e\n>\n> And of course, in this example, I put all relevant operators, but in\n> practice they'll generally be on different lines.  And SMIE (which\n> supports that kinds of indentation, e.g. in sm-c-mode) doesn't\n> pre-compute that context: it's only when it sees the \"+\" at the\n> beginning of line that it moves back over higher-precedence operators to\n> find the matching alignment spot.\n>\n> In theory, SMIE could try to create the kind of context you're thinking\n> of, but that would amount to a complete rewrite (and it would likely be\n> very difficult if not impossible to make it work with existing\n> smie-rules-functions, so it'd break backward compatibility).\n>\n>\n>          Stefan\n>\n\n\nStefan, came across SMIE as it looked like an interesting abstraction. \nEstimate your efforts. Nonetheless think that path turned out wrong \nmeanwhile. SMIE-based indentation will not be easier, but run into more \nand more complexity instead. The reasons deserve being discussed elsewhere.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_645.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aii4u-0003o6-TC\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 08:40:00 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55620)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aii4o-0003gz-4t\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 08:39:59 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aii4i-0004yJ-JK\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 08:39:54 -0400\nReceived: from mail-wm0-x22b.google.com ([2a00:1450:400c:c09::22b]:37929)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aii4i-0004yD-Cz\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 08:39:48 -0400\nReceived: by mail-wm0-x22b.google.com with SMTP id l68so22166360wml.1\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 05:39:48 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=oJ6c6Q99m0K8Mx+BjNEU74CR8jKxUD4/eKdaNIMsnhM=;\n\tb=UhD2R3ePKzROm1Xpu/fcq4vaQ/ebcVGYYBOoz8AllEx6uIawmUcwytF5/A3JvE+oXL\n\toDwcuqxkAFkUeGhEotIj43je9dD6ftj0SHrNRshC6E8OAEObB+S73tPrqUgqaNzkyi9H\n\t+2q9Ca6xJU6xpm41uzyLQeJzX3KLRKiP74a50oXtcsW2Y7jPyVRBI9vi2WFyOBYEZxN9\n\tW82P/KXfpaVdaf3BycmM43z7s+7QCbcxo38r7Adpc6L6hmYXoUvbb8esTtA1ySt9WbtP\n\tGmt9czNo7gdPtY04mAHOfmydoWfX0N1Il9IHc9w3bow4tEpMUjnvrAaDayki3JoDaIDC\n\tTEGA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=oJ6c6Q99m0K8Mx+BjNEU74CR8jKxUD4/eKdaNIMsnhM=;\n\tb=f5FVDjUZ9C0Q1XTMDahcHw/eL/c1Yd4JQ16WGbmmKFmKco77B479+IgHahPd9sbQYt\n\tNR4AZaI/O9Afj3fkSSvCbX59qoDRitpUuShnS/DHDeKAcVOEDtmY5ZTPLliLKAqqBd9i\n\t/DgDTOBSzkMYbDa+U1jwNgdM1HoOZ6FIOoK0Es5Bsun0xD2cM9VXAqK027b9WechDxIs\n\tLgFOTPpS93Q2e0cnMNYC7DM0AzWynnDJBZxOsn8zqMCScOc9/fQCcvBex01oyo2BiaoX\n\tpNY7TYqz/XTcZtUwrizRdKwkSqIi2kzj+sg5YDxJ/kP1ieGwOvCye0EzlUdCONJ5aGZP\n\tKOkQ==\nX-Gm-Message-State: AD7BkJJikNHXxs/8K0Pam4/eknmVYtYHR1THLX7a/4BC8th6yCdVYl9sJ7aLgSjLYYb8yw==\nX-Received: by 10.194.143.44 with SMTP id sb12mr3226170wjb.58.1458736787726;\n\tWed, 23 Mar 2016 05:39:47 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tv5sm21985394wmg.16.2016.03.23.05.39.46\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tWed, 23 Mar 2016 05:39:46 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87bn66qsgo.fsf@gmail.com> <jwvzitquf5p.fsf-monnier+emacs@gnu.org>\n\t<87fuvhpimn.fsf@gmail.com> <jwv1t71v3lb.fsf-monnier+emacs@gnu.org>\nDate: Wed, 23 Mar 2016 13:39:45 +0100\nIn-Reply-To: <jwv1t71v3lb.fsf-monnier+emacs@gnu.org> (Stefan Monnier's message\n\tof \"Wed, 23 Mar 2016 07:41:21 -0400\")\nMessage-ID: <8737rhpdv2.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22b\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 12:39:59 -0000\nContent-Length: 699\nLines: 19\n\n\n\n>> On Wed, Mar 23 2016 07:41, Stefan Monnier wrote:\n\n> In theory, SMIE could try to create the kind of context you're thinking of,\n> but that would amount to a complete rewrite (and it would likely be very\n> difficult if not impossible to make it work with existing\n> smie-rules-functions, so it'd break backward compatibility).\n\nYou are the best to judge what is possible or not. I had in mind that each mode\nwill build such a context, but tackling it at SMIE level seems like a much\nbetter start. Will be back when I have a better understanding of it.\n\nSMIE is relatively new; 7 modes in emacs and 5 in ELPA are already using it, but\nit probably can be still molded here and there.\n\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_647.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiilN-000470-HY\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 09:23:53 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39053)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiilG-00045y-HV\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 09:23:52 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiilC-0000WN-Ku\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 09:23:46 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:47340)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiilC-0000WG-ER\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 09:23:42 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiil9-0000MU-Vj\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 14:23:40 +0100\nReceived: from 69-196-182-150.dsl.teksavvy.com ([69.196.182.150])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 14:23:39 +0100\nReceived: from monnier by 69-196-182-150.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 14:23:39 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nDate: Wed, 23 Mar 2016 09:23:34 -0400\nMessage-ID: <jwvpoulnxgx.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87bn66qsgo.fsf@gmail.com> <jwvzitquf5p.fsf-monnier+emacs@gnu.org>\n\t<87fuvhpimn.fsf@gmail.com> <jwv1t71v3lb.fsf-monnier+emacs@gnu.org>\n\t<8737rhpdv2.fsf@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-196-182-150.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:d7B3IEbVxAnS0NELuZ8um4GXyDI=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 13:23:52 -0000\nContent-Length: 623\nLines: 15\n\n> SMIE is relatively new; 7 modes in emacs and 5 in ELPA are already\n> using it, but it probably can be still molded here and there.\n\nSure.  I'm just pointing out that it's a difficult modification to make,\nat least for some indentation codes (and probably for many of them).\n\nMaybe it's OK to design a multi-mode system which requires every major\nmode that wants to play with it well (e.g. well enough to get the kind\nof behavior we want for ERB) to basically rewrite its indentation code.\n\nBut this won't fly unless we also make it possible to use major modes\nwhich haven't been rewritten in that way.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_649.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aikho-0006Ng-VN\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 11:28:20 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42877)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1aikhm-0006NN-CW\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 11:28:19 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1aikhi-0001ne-CL\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 11:28:18 -0400\nReceived: from mail-wm0-x22e.google.com ([2a00:1450:400c:c09::22e]:35284)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1aikhi-0001nR-5V\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 11:28:14 -0400\nReceived: by mail-wm0-x22e.google.com with SMTP id l68so201102168wml.0\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 08:28:14 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=WdYHw+yFLmF/Fee6ACOkEHaOyNIWvtr32nUpKfSssys=;\n\tb=qyhUqvlzZRUUcYFKtNsBb1ClWI5WoMQCZfP54A3i3tQspsqrBZL4n4lhGdAh8tVzVe\n\t5SeqZpwZhMJoG4Uw2E4sWMZ9Yo/l1nFZwkOe3wR8MHuek9X4bUGPoG4Q65BZ0mNLueZH\n\tDLQn9RJcF+Pg5ghPiDqlGOa3Hi3LmxVV//qb+8ttL8iACd7KVafz1TUEGP4TJOTRsYys\n\tAlN5GEbhrY+HsJvIZFznVMfZKMCaPqU23SIpZKhDvXEgjHTqxiIkFqnmmHPYCI/YIg30\n\t4RAVl0vtKVTQOM/5KecZI6d11pO2O9uI6tBT0gvg6J/48h3k8yifbHqFT+eEMWiT4Qnn\n\t/Rvw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=WdYHw+yFLmF/Fee6ACOkEHaOyNIWvtr32nUpKfSssys=;\n\tb=TkCDg2ir5ittKZIl3PyA6ryqjrnfzY4RSa9kK7PXU6AfVmHOJ6TZ+HL8hce/eV0WCk\n\tgfIftEvPlLUvf+y5HsJk+qxa7/shGzTe7+ImmBglLQ4daVgGDhBhiPo5inZF/lEj5u/D\n\thcuc18/YzlTpmEMpvjQOEOOjr2En4+XW+leFBMenEmCDt3RERLB3jr0T5EwNaLDwhPq3\n\t+6m+OhcpYcjstJ3nRhdwvcg9MMXDadihw2ZnpR8DuyEo1NnegdbEyM+zcdMqRaKNj9Uf\n\tTZmDfk2Uog7WczBaOYDeeSg1iUwAbxKfR9ZQs1KOzQwwcvwojZXLNOyRqNu5ySavREk6\n\tR30A==\nX-Gm-Message-State: AD7BkJKMmF2lfr6/ZAty5D/qgJ39VtEqLgby4gurkGo2aKLoPeuwx/b5fPJtxkX45AW9/g==\nX-Received: by 10.194.3.110 with SMTP id b14mr4142001wjb.116.1458746893490;\n\tWed, 23 Mar 2016 08:28:13 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\ta10sm3088651wjb.38.2016.03.23.08.28.11\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tWed, 23 Mar 2016 08:28:12 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Stefan Monnier <monnier@iro.umontreal.ca>, emacs-devel@gnu.org\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<jwvmvpswowh.fsf-monnier+Inbox@gnu.org> <87twk0beuh.fsf@gmail.com>\n\t<jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org> <877fgvgbr1.fsf@gmail.com>\n\t<jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org> <87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87bn66qsgo.fsf@gmail.com> <jwvzitquf5p.fsf-monnier+emacs@gnu.org>\n\t<87fuvhpimn.fsf@gmail.com> <jwv1t71v3lb.fsf-monnier+emacs@gnu.org>\n\t<8737rhpdv2.fsf@gmail.com>\n\t<jwvpoulnxgx.fsf-monnier+gmane.emacs.devel@gnu.org>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <368c79cd-c8cb-126f-f11c-6dd2e6b0ed80@yandex.ru>\nDate: Wed, 23 Mar 2016 17:28:10 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <jwvpoulnxgx.fsf-monnier+gmane.emacs.devel@gnu.org>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22e\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 15:28:19 -0000\nContent-Length: 835\nLines: 20\n\nOn 03/23/2016 03:23 PM, Stefan Monnier wrote:\n\n> Maybe it's OK to design a multi-mode system which requires every major\n> mode that wants to play with it well (e.g. well enough to get the kind\n> of behavior we want for ERB) to basically rewrite its indentation code.\n>\n> But this won't fly unless we also make it possible to use major modes\n> which haven't been rewritten in that way.\n\nSupporting both approaches would also require some feature discovery \nmechanism, or hardcodng a list of modes that support the \"advanced\" way, \nsomewhere.\n\nCan we agree to shelve the PREVIOUS-CHUNKS/STRING-BEFORE/etc discussion \nuntil someone comes with a patch that shows a convincing usage of it, in \nmultiple modes?\n\nPreferably with some performance numbers, showing a corresponding \nimprovement when used together with some multi-mode package.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_651.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiqgX-0008LJ-B4\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 17:51:25 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39726)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiqgV-0008LD-Jt\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 17:51:24 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiqgS-0008P1-Be\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 17:51:23 -0400\nReceived: from mail-wm0-x22e.google.com ([2a00:1450:400c:c09::22e]:32917)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiqgS-0008Ot-4j\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 17:51:20 -0400\nReceived: by mail-wm0-x22e.google.com with SMTP id l68so250996981wml.0\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 14:51:19 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=hdfcbYPvmclSkisAjRBDfBbpc1VTkIFp1pxxh8TU84Q=;\n\tb=XS5xToWUlWA68o7ZvLQGzwbsQ8XxXuRijocxZSvVPe3anERRn7QSc9XAUbyZBScAJ9\n\tBYYixx4sK9f0YrZmP0U8H36g9fbCdgSNBKFuHk7nPHkVWaFKkOqdtRLa3QjImUnwgNrC\n\teu723BQvnAXVCHGc7ncjgQLI7El0+9RA56ZmQ/jENamfKvcvrN6OIIZsU/oJm3Qg13wC\n\tZnVvSWPL2BMZSG/T94xanrmxYzciOT2LqfIawVcipfuhYPG8YyX5UtoSQyKraN5W7plS\n\tttL/ERMwKg7AaqEsIj3pUYRYluX50MwH7zYV9+t5XHpsfPr4sziqEJIXglUZ5K7OY/cD\n\t0fnA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=hdfcbYPvmclSkisAjRBDfBbpc1VTkIFp1pxxh8TU84Q=;\n\tb=GerOThZbxtPYrvLIbwOCrCdQYpG1nnbe6QuU6rH1XN3LaovFMeSWsFHeARdW01RUiL\n\tiXHbb3W6ZNcc7F5lhuoFm5dnAVJD48cmkqO2mPYS3kVX92F2PARB/voR5yNiE78pD6tV\n\tirZbS+Myqa/A6iGhelOina0SPERRb9yFJcI+9We/eoeoBUWWKaL+FWvYE2qoWJjIexJ3\n\tQDbm2Amh3b2pdqFlVGvgeXJiWC3TA6c1RoKfZVA5/yCfhsJX8BWOghhEUBFGxsl61Xqx\n\thwlPPCny41Zkd9skXm3MrV5XKyie6DlvEL8EP1fI2fT8CQGYGVA4cPFHiMsPW/w6iJBx\n\tralA==\nX-Gm-Message-State: AD7BkJKEc+tvSHNBtTColzMGDV+HSBFDStrRrgRV0Mv0o6EqtjPiZWo/UvNLRotmfEu3zA==\nX-Received: by 10.28.145.8 with SMTP id t8mr29351324wmd.103.1458769879247;\n\tWed, 23 Mar 2016 14:51:19 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tux5sm4443176wjc.17.2016.03.23.14.51.17\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tWed, 23 Mar 2016 14:51:17 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Dmitry Gutov <dgutov@yandex.ru>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87bn66qsgo.fsf@gmail.com> <jwvzitquf5p.fsf-monnier+emacs@gnu.org>\n\t<87fuvhpimn.fsf@gmail.com> <jwv1t71v3lb.fsf-monnier+emacs@gnu.org>\n\t<8737rhpdv2.fsf@gmail.com>\n\t<jwvpoulnxgx.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<368c79cd-c8cb-126f-f11c-6dd2e6b0ed80@yandex.ru>\nDate: Wed, 23 Mar 2016 22:51:17 +0100\nIn-Reply-To: <368c79cd-c8cb-126f-f11c-6dd2e6b0ed80@yandex.ru> (Dmitry Gutov's\n\tmessage of \"Wed, 23 Mar 2016 17:28:10 +0200\")\nMessage-ID: <8737rgoobu.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22e\nCc: Stefan Monnier <monnier@iro.umontreal.ca>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 21:51:24 -0000\nContent-Length: 458\nLines: 17\n\n\n\n>> On Wed, Mar 23 2016 17:28, Dmitry Gutov wrote:\n\n> On 03/23/2016 03:23 PM, Stefan Monnier wrote:\n\n> Can we agree to shelve the PREVIOUS-CHUNKS/STRING-BEFORE/etc discussion until\n> someone comes with a patch that shows a convincing usage of it, in multiple\n> modes?\n\n> Preferably with some performance numbers, showing a corresponding improvement\n> when used together with some multi-mode package.\n\nYeps. The topic has been exhausted for now.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_653.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiQrx-0003nn-Il\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 14:17:29 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:43720)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiQrv-0003md-NF\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 14:17:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiQrt-0007Fj-0k\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 14:17:27 -0400\nReceived: from mail-wm0-x236.google.com ([2a00:1450:400c:c09::236]:37534)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1aiQrs-0007Fa-Lq\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 14:17:24 -0400\nReceived: by mail-wm0-x236.google.com with SMTP id p65so175310152wmp.0\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 11:17:24 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=pUWZXFzuA6fKSEmAQP5hnda/mGmmE5dmxyBrpuR2vBk=;\n\tb=NuHsWrbPhhiMbIDSFfUrs98iTsuXa88Lp7DmGaEgNyAeYVi3oSKYYsUo91PLA+fEU2\n\t0fSOC5D9ujq2k67VsN73WwVwELOt87Kp1VKhCjCVh0yF7Qje8ixKsXKY0MJUO2UN4zRh\n\taaBydayWwrw+Fof8qh54u2AHkpq5BcFCkQYuT0latShVR+h7zdzvf2kPnv4Z7uxsATsw\n\tsUd57HxiDaNjE1ni6MjOmFNPC4d9OxwbtUomL8eiViS24PvMFePN6ih1eds+qyZVHYzK\n\tZr2dIfU2F0A1inYwci45FNbEWLwIWfXBo9J4CXHgmzP+G+2EcC7/H3zNwVxCAs9covl4\n\t840g==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=pUWZXFzuA6fKSEmAQP5hnda/mGmmE5dmxyBrpuR2vBk=;\n\tb=kU2I4PDLJE2htlcqBFNtQcaxgBW8Qp1zVU8oQgaHvwt7ACbGhDmejpBZKiCihFpAoi\n\tPMM5u7iVFkoKxc+SWy39YXRpdwl2dhIyIipP/8Ok4Fttlae3bT0ju3ESWJjaXx1NRPZQ\n\tMlYz7FmKN52ocgG/3x/Qvp4eK4bZzV2dhLa4ksryHcVApvRzdH9UKlQpKsWGRHHoNdeL\n\tmQu20ZiNdrQaEffCyLamZd4NJPaSl5Fdz3J+bTTbgYKuAOF4bFH1IzUKoJSdoJx3Io+1\n\tXyrJrzVBy7hDh2rCl4QvwcR3RGXib1V/YngkWR1o12NwKD3SRgaDmLIP3YUyzOu0i7mW\n\tWjQw==\nX-Gm-Message-State: AD7BkJKD4kcl70NQjmsfTLw1Gjpo9KH4Tgv6Q2Ss9Y9DFXaVyqVYdnUaJRLnO4DbPcHEwg==\nX-Received: by 10.28.18.85 with SMTP id 82mr22802693wms.5.1458670643672;\n\tTue, 22 Mar 2016 11:17:23 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tc128sm18478060wma.11.2016.03.22.11.17.22\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tTue, 22 Mar 2016 11:17:22 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Tue, 22 Mar 2016 19:17:22 +0100\nIn-Reply-To: <jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org> (Stefan\n\tMonnier's message of \"Tue, 22 Mar 2016 10:51:33 -0400\")\nMessage-ID: <87io0eqswd.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::236\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 22 Mar 2016 18:17:29 -0000\nContent-Length: 2919\nLines: 71\n\n\n\n>> On Tue, Mar 22 2016 10:51, Stefan Monnier wrote:\n\n>> The second issue is with respect to the first line immediately after the\n>> header.\n\n> Since it's not on its own line, I don't see why it would be an issue\n> for indentation.\n\nIt's a problem if you narrow to current span and allow inner mode to indent\nfirst line. So one way or another, multi-mode has to interfere in this case\nbeyond FIRST-COLUMN hint.\n\nWithout narrowing it's not clear what is the contract that inner mode should\nrespect to handle previous chunk locations. It's not even clear if previous\nlocations should be of the same modes chunk, previous head span or maybe a set\nof heterogeneous chunks.\n\nIn any case once the inner mode gets locations of previous chunks it all becomes\nan very messy open question. Modes can decide to do whatever they see fit. The\nSTRING-BEFORE/AFTER system, not ideal of course, but it keeps the mode within\nits own world and doesn't leave much space for \"improvisation\".\n\n>> mode for indentation. Consider this example of erb mode taken from\n>> https://github.com/fxbois/web-mode/blob/master/tests/demo.erb.>\n>>     <div id='header'>\n>>       <% if signed_in? -%>\n>>         <%= link_to t('.sign_out'), sign_out_path, :method => :delete %>\n>>       <% else -%>\n>>         <%= link_to t('.sign_in'), sign_in_path %>\n>>       <% end -%>\n>>     </div>\n\n>> One meaningful approach here is to indent if-else-end block using inner mode\n>> rules, right?\n\n> Another approach would be to consider it as a sequence of chunks, rather\n> than as chunks of one mode nested in another.  So each chunk controls\n> the FIRST-COLUMN of the next chunk.\n\nThis will not work in above case. <%else-%> chunk needs to know about where <%if\nsigned_in? -%> was indented which is not an immediately preceding chunk.\n\nIt's hard to think of better solution than collecting all relevant previous\nchunks in one place and indenting according to inner mode. In order to indent\n\"<%else-%>\", STRING-BEFORE should be full \"link_to ...\" line. So basically\nSTRING-BEFORE must consist of all ruby spans in between \"if\" and \"else\" chunks.\n\n> In any case, this seems messy.\n\nYeh. Very much.\n\n> As for STRING-AFTER, the example is compelling, but I don't yet\n> understand really how it would all work out overall.\n\nNeither do I. Strings are hard to process in emacs and the mode will need to\neither modify current buffer by inserting it in a special region or use a\nseparate buffer for that.\n\nI tend to agree with Dmitry, if you decide not to pass chunk locations to inner\nmodes then there is no much point in getting complicated with passing\nBEFORE/AFTER strings. Multi-mode engine can take care of that satisfactory.\n\n> How should the \"generic\" code that links HTML and Ruby know when to indent\n> using the HTML indentation code and when to use the Ruby indentation rules?\n\nNo idea. Dmitry should have an answer for that. He implemented mmm-erb.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_655.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiig9-0002hu-3O\n\tfor mharc-dev@caliopen.local; Wed, 23 Mar 2016 09:18:29 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37299)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiig7-0002ec-3e\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 09:18:27 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiig2-0007ig-An\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 09:18:27 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:46942)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiig2-0007i8-40\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 09:18:22 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1aiig0-0004zf-HO\n\tfor dev@caliopen.local; Wed, 23 Mar 2016 14:18:20 +0100\nReceived: from 69-196-182-150.dsl.teksavvy.com ([69.196.182.150])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 14:18:20 +0100\nReceived: from monnier by 69-196-182-150.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Wed, 23 Mar 2016 14:18:20 +0100\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nDate: Wed, 23 Mar 2016 09:18:13 -0400\nMessage-ID: <jwvvb4dnxq6.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87io0eqswd.fsf@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-196-182-150.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:mD9GhZVPKJYAJa49Cz4ArSxcxzA=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 13:18:28 -0000\nContent-Length: 852\nLines: 21\n\n>> Since it's not on its own line, I don't see why it would be an issue\n>> for indentation.\n> It's a problem if you narrow to current span and allow inner mode to indent\n> first line.\n\nI guess it's an issue if the buffer is \"always narrowed\", in which case\nthe \"first\" line might get indented accidentally, indeed.\nBut otherwise, there's no reason for the generic mode to go through the\ntrouble of \"narrow + indent\" this partial line.\n\n> Without narrowing it's not clear what is the contract that inner mode should\n> respect to handle previous chunk locations.\n\nFor prog-indent-context we provide (START . END), as well\nas PREVIOUS-CHUNKS, where the contract is that the major mode's\nindentation code should only look at those parts of the buffer.\nIt's up to the mode to decide whether it does that via narrowing, or\nsome other way.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_657.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1aiXRs-0003GA-Nd\n\tfor mharc-dev@caliopen.local; Tue, 22 Mar 2016 21:19:00 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34563)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1aiXRq-0003FX-C5\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 21:18:59 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1aiXRg-0007zD-Jj\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 21:18:53 -0400\nReceived: from mail-wm0-x22f.google.com ([2a00:1450:400c:c09::22f]:33471)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1aiXRg-0007yv-8o\n\tfor dev@caliopen.local; Tue, 22 Mar 2016 21:18:48 -0400\nReceived: by mail-wm0-x22f.google.com with SMTP id l68so214602093wml.0\n\tfor <dev@caliopen.local>; Tue, 22 Mar 2016 18:18:47 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=Koc01c0n/g5FGAcQSOME6lo3CYOFdSsBKcr5Sf+l13Q=;\n\tb=Ym+9cMbwQURdeIT/dl/sogAdXhdr1TyMZ2en+x5CWT7asVfa2MkFuTOwrcIKU7g93N\n\tSACNUuQJncD1a0Pd9IL70NKNbyIZoYs2Bk62l0ubrxPrINP0WdWzecLtjLJfbV/svaeU\n\tIkoguCjdfiNldgkXHLpu5XhWMfEbchiIU5J+vJ4tXJ3L9WFIS+K8huoTbjhflMF7M+AY\n\tX8gJqeuxDL6OexKRFNyNaPpjhGp9d8v/zJsIfleE5iZmG7dqffSkhQT5lMBldhlZ51Zh\n\t7SxXmBl1J4ONtG9Cy/NCKeRBMbo1AW5wO1oZ5C+bMmMBTJ+e3xMnuR1p6Ltth1X5aM+F\n\tVmsQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=Koc01c0n/g5FGAcQSOME6lo3CYOFdSsBKcr5Sf+l13Q=;\n\tb=ET9jPb+ctaN3jpSSrtGTIiebtoj3JlG43KhkCj9VuiEfcaTGNiExLbNNgHrHmMYyhZ\n\tJbWzw8gihEYhVMaMQE7ovvrbkXjabZlOw6g4BUALfcREF5VEO3M5WXpzdzdvPaQGbXKP\n\tIASzuR9t4Zm9axekcP+KSDGr3P/EnurujW5kzBCio31xq3LseMAQdtK/taT04MmPHnFZ\n\tBh3soaNKqjhv4HDkQhDPZa5P1wOqwaOciygxuK//H8ZuVpCBc5X7UUrwzTAuCKY92R4H\n\t3PURKTr71zQzz/G1MDawWxH4HOW9E3RLIoxMaB8OJmEZbDi3D+0NebeR87xS1W5lDfoG\n\tkWFQ==\nX-Gm-Message-State: AD7BkJIyVK9DHLVSu1VGpvEb34A3xVWMPov0DdqmFyBMjvh3AeJbdRz0c4PWtzw4uSUudw==\nX-Received: by 10.194.105.42 with SMTP id gj10mr161725wjb.49.1458695927353;\n\tTue, 22 Mar 2016 18:18:47 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\tda6sm56530wjb.24.2016.03.22.18.18.45\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tTue, 22 Mar 2016 18:18:46 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Vitalie Spinu <spinuvit@gmail.com>,\n\tStefan Monnier <monnier@iro.umontreal.ca>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<jwvmvpswowh.fsf-monnier+Inbox@gnu.org> <87twk0beuh.fsf@gmail.com>\n\t<jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org> <877fgvgbr1.fsf@gmail.com>\n\t<jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org> <87bn67eq4t.fsf@gmail.com>\n\t<jwvy49ar473.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<87io0eqswd.fsf@gmail.com>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <15d2151b-f79a-398e-094d-d7edc5e95e06@yandex.ru>\nDate: Wed, 23 Mar 2016 03:18:44 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <87io0eqswd.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22f\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 23 Mar 2016 01:18:59 -0000\nContent-Length: 1800\nLines: 37\n\nOn 03/22/2016 08:17 PM, Vitalie Spinu wrote:\n\n> This will not work in above case. <%else-%> chunk needs to know about where <%if\n> signed_in? -%> was indented which is not an immediately preceding chunk.\n>\n> It's hard to think of better solution than collecting all relevant previous\n> chunks in one place and indenting according to inner mode. In order to indent\n> \"<%else-%>\", STRING-BEFORE should be full \"link_to ...\" line. So basically\n> STRING-BEFORE must consist of all ruby spans in between \"if\" and \"else\" chunks.\n\n...and the multi-mode package would have to know, somehow, that the \"if\" \nchunk is special in this regard, and know which \"if\" matches which \n\"end\", etc. Or simply always include all previous chunks in the given \nmode in STRING-BEFORE.\n\n>> How should the \"generic\" code that links HTML and Ruby know when to indent\n>> using the HTML indentation code and when to use the Ruby indentation rules?\n>\n> No idea. Dmitry should have an answer for that. He implemented mmm-erb.\n\nAgain, mmm-erb is written to support a limited set of template languages \n(currently, two, though supporting JSP would be trivial-ish, were \njava-mode not a part of CC Mode, with associated pitfalls).\n\nSo IME, the multi-mode package needs to hardcode, in some form of \nanother, the knowledge which file formats use this approach to indentation.\n\nAnd since we're doing that anyway, using a simpler indentation code in \nthose particular files doesn't seem like a bad idea either. (For \nnon-continuation hunks, at least).\n\nBTW, web-mode doesn't seem to dispatch to inner modes's functions at \nall: \nhttps://github.com/fxbois/web-mode/blob/c5aacacb8f4c233844306806a102405c8e9671c9/web-mode.el#L7164-L7198. \nI'm not a fan of this approach in general, but that clearly means that \nit can work for indentation.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_659.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai8jb-00032A-Dj\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 18:55:39 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41758)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai8jZ-000320-Dh\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 18:55:38 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai8jW-0000VF-3v\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 18:55:37 -0400\nReceived: from mail-wm0-x229.google.com ([2a00:1450:400c:c09::229]:35928)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai8jV-0000VA-T4\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 18:55:34 -0400\nReceived: by mail-wm0-x229.google.com with SMTP id r129so69765959wmr.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 15:55:33 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=9qLmOdF4aIwI2KsQK4saAgJW4LF/fOeiaGVFHuSD0JE=;\n\tb=yd//DYuiBSqRxWCgbpD9fn4tqMZQiUlJYnMAiDhgMlmYpnGNWLz/L5IFq9QQQFr9Bu\n\t5Fwp+PVRLMa2kY3ITBXNVW3jnSdB/n7Dct9s6p4lFiSV544L/UwDLvfVF69LsLCT5Ypa\n\t2PXHylwowTnsU9JblsauozCogre1LnSyzI0oiqDLJO0bBxEEeD02qGk68RGWYg4s+zDi\n\tGgQLO9gGUvZ71oA5+lDYDdGm9YvKog0sfhSfPMgRM6lf3dDP03Kc+S1PXKZ6yuEV5Xzw\n\t/IYTjhg+PnxI60KlYbUJ/Gcfgw2Q+PuLck3Cmo+9xq3fQM666SudemlrKqMM1jpMuvZv\n\tuy7A==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=9qLmOdF4aIwI2KsQK4saAgJW4LF/fOeiaGVFHuSD0JE=;\n\tb=lYuI7G+Pl8MgFS6oQGIhs9Q/jgxcisEd4O4RRTABVnNj9XwY1ZLeFiEgP7d3WK6Zd6\n\tCcvLkmIbHL4lxvM/y9BiitIwmFPvSfKwgWzGFIPf7HRGMK0FMorsb69C+tCPkUCGLCeW\n\t51I1SIYsvjd8QdZSTS9zmR5HDZjhIKmj84YzLueSWPQZu8tJAUhSjZzTgsNPhHhal0Fp\n\td1mOOlT2Arq3K2V2v5sSh4JfpTXxfmQ9uFT/prz0C9leca7leFPcsdeVztyC2xf84JEs\n\tIsx3CU8Dz21uNRJROqf2Xl6OfLjCDgLBGAHoxa5T8mRQKHOZtLFqehf4ilJ1lvdbWE7c\n\tqn/g==\nX-Gm-Message-State: AD7BkJKxJf1we/qoTE7XMFLmcZAMZmt0QQNvd0f4oK5OhEpFqNKN4pXhFvPK+T55kwtoHg==\nX-Received: by 10.28.128.80 with SMTP id b77mr17236575wmd.42.1458600933056;\n\tMon, 21 Mar 2016 15:55:33 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\tu16sm14569126wmd.5.2016.03.21.15.55.31\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 21 Mar 2016 15:55:32 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Vitalie Spinu <spinuvit@gmail.com>,\n\tStefan Monnier <monnier@iro.umontreal.ca>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<jwvmvpswowh.fsf-monnier+Inbox@gnu.org> <87twk0beuh.fsf@gmail.com>\n\t<jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org> <877fgvgbr1.fsf@gmail.com>\n\t<jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org> <87bn67eq4t.fsf@gmail.com>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <113082e0-f01b-c290-4e31-009a30f55759@yandex.ru>\nDate: Tue, 22 Mar 2016 00:55:30 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <87bn67eq4t.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::229\nCc: Alan Mackenzie <acm@muc.de>, emacs-devel <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 22:55:38 -0000\nContent-Length: 1923\nLines: 41\n\nOn 03/21/2016 06:45 PM, Vitalie Spinu wrote:\n\n> Real trouble comes with continuation chunks. You might need to have a completely\n> reversed indentation logic - in outer/host spans MM engine needs to call inner\n> mode for indentation. Consider this example of erb mode taken from\n> https://github.com/fxbois/web-mode/blob/master/tests/demo.erb.\n>\n>     <div id='header'>\n>       <% if signed_in? -%>\n>         <%= link_to t('.sign_out'), sign_out_path, :method => :delete %>\n>       <% else -%>\n>         <%= link_to t('.sign_in'), sign_in_path %>\n>       <% end -%>\n>     </div>\n\nFWIW, I've successfully implemented indentation for ERB (and EJS) files \nwithout delegating to ruby-mode and js-mode indentation code:\n\nhttps://github.com/purcell/mmm-mode/blob/c9a857a638701482931ffaaee262b61ce53489f3/mmm-erb.el#L157-L225\n\n(it indents web-mode's example almost identically)\n\nIt should be easy to add support for similar types of files with almost \nthe same code. But yes, it implies adding support for each new template \nformat manually.\n\n> One meaningful approach here is to indent if-else-end block using inner mode\n> rules, right? This is what web-mode seems to be currently doing. Assume you are\n> just in front of `<%= link_to`. This is host hmtl mode. But you need to indent\n> according to inner mode construct. So what do you do? You go to the end of\n> previous code chunk and call prog-indentation-function of inner mode with\n> STRING-BEFORE = \"\\n\" and STRING-AFTER=\"link_to t('.sign_out'), sign_out_path,\n> :method => :delete\". Simple isn't it? That's precisely my proposal.\n\nIt's simpler for the caller, but I'm having hard time imagining how to \nimplement it properly on a major mode's side without inserting those \nstrings into the buffer, or using a temporary buffer.\n\nIf STRING-BEFORE and STRING-AFTER are not allowed to contain newlines, \nyes, that becomes easier to handle, but that also loses in generality.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_661.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai2v2-00019l-31\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 12:43:04 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36977)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai2uw-00018g-CC\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 12:43:02 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai2ut-0005c3-47\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 12:42:58 -0400\nReceived: from mail-wm0-x230.google.com ([2a00:1450:400c:c09::230]:33303)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai2us-0005bh-Q6\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 12:42:55 -0400\nReceived: by mail-wm0-x230.google.com with SMTP id l68so159026872wml.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 09:42:54 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=CuynuobnTlYAw718edC6rgRAY5lRLwYPzXI6PRkEyrc=;\n\tb=z/oW0vjh+M3NhGujtiFGMcb4R+VzD12gVKlVaPathFH/zLm+7b1lQ52psunIgN4GlW\n\tCAP8Rjdbir/rIkP/nc09J8EZ3wCWUjBzP8A+tZvMpo07iGCCVPK0i7QCb602x87b9otx\n\tk/ruuYkPdLPctbzWwTazx7R2MiepXVk9ygObXDcD3ns3RovjhsEKpZ2VeaPzGknWOYxc\n\trvd4VIRvLbvZd/VN/CxXAysKufHSZINkEmxsQ9FbF0hwhicnRt06Tx4P/TYlXEwLJp6N\n\tM1NtZFkwYpefrknF9A0bwEpfCx+A3a8RqZ/GlzlYb+hYY7/YcyAQPK3+wTaiT5xUmv6S\n\t1t2A==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=CuynuobnTlYAw718edC6rgRAY5lRLwYPzXI6PRkEyrc=;\n\tb=R7cnubhcRALRf1JNh1ug6EZ+lpuqBEhISPVl2YRRDdBvVIHZ8ZGN49kWmVtS8BiAif\n\tw3wsq0b7nHwGGGVWLbUJGND07fQ1dsZ56MkZvZg20LZgK89aUqetuvAincuqhdxe2XSs\n\ttVIZbQr8UxEQ8zqbnuz8K9nBiHf4BZ9lxGmJ35Vu99+4amyXV5fR4CRWdHWTy3ZnOy7c\n\t7v4bQUpexg/AzZKehuuXqir0JdrKLYB6aj/Ec6pbNYrULpo6gKCShjEHE8i+289cJcVU\n\txup6FvUHDuNQIJ3M3K1L6VvUOtBk5QBm4WEEWo6fKHFsTXuSauQg8EHU4R+U5jfISKB1\n\tMUUQ==\nX-Gm-Message-State: AD7BkJIloZbV7ZvdF7rXT1cTN/lD2pCT/qyH12Sv8hRe1BNUcOQEi3j66uM47Vcw2mL6JA==\nX-Received: by 10.28.179.7 with SMTP id c7mr15782229wmf.46.1458578573827;\n\tMon, 21 Mar 2016 09:42:53 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\tt7sm26171800wjf.39.2016.03.21.09.42.52\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 09:42:52 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\nDate: Mon, 21 Mar 2016 17:42:51 +0100\nIn-Reply-To: <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org> (Stefan Monnier's message\n\tof \"Mon, 21 Mar 2016 10:43:56 -0400\")\nMessage-ID: <87fuvjeq9g.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::230\nCc: Alan Mackenzie <acm@muc.de>, emacs-devel <dev@caliopen.local>,\n\tDmitry Gutov <dgutov@yandex.ru>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 16:43:02 -0000\nContent-Length: 3997\nLines: 84\n\n\n>> On Mon, Mar 21 2016 10:43, Stefan Monnier wrote:\n\n>> parse-partial-sexp is called from code exclusively and it just happens\n>> that in multi-modes it is called outside of narrow region quite often.\n\n> How/why?  Can you give some concrete scenario?\n\nMM engine narrows to span region for a lot of tasks, most importantly\nfont-lock. If inner mode fortification functions misbehaves (ignoring\nfont-lock-dont-widen for example) like c-mode does this leads to trouble. So to\navoid those troubles you would advice individual functions and narrow them\nproperly or apply other tricks like overwriting output value or input args.  It\nall works fine till that function calls parse-partial-sexp (or some other low\nlevel function) and blows with args-out-of-range error.\n\nTo be frank, the issue of parse-partial-sexp is fading because modes are now\nusing syntax-ppss more extensively. Most of the problems with parse-partial-sexp\nfrom the past are now internalized in condition-case inside syntax-ppss. That\ncondition-case is triggered very often (almost always) from inside polymode\nchunk narrowing.\n\n>> That's a major inconvenience. Why on earth one would need to\n>> take account in user narrowing for syntax parsing?\n\n> Because you need it for *everything* that looks at the buffer.\n> Why should parse-partial-sexp be different from (say) scan-sexps?\n\nI think parse-partial-sexp, syntax-ppss and maybe some others, are special in\nthe sense that in order to return a correct value they need to be aware of the\nwhole buffer. I don't see this as an inconsistency but I might be too naive.\n\n>> If parse-partial-sexp could be made to always widen to hard limits it\n>> will automatically solve a bunch of problems. bug#22983 being one of them,\n\n> Bug#22983 should be fixed by widening, indeed, but it should be done in\n> syntax.el.  Widening in parse-partial-sexp would only address some cases\n> but not all (e.g. the syntax-begin-function cases or the\n> syntax-propertize-function cases).  Those other cases can only be fixed\n> in syntax.el.\n\n>> the ubiquitous out-of-range errors in font-lock in multi-modes being\n>> the most important one.\n\n> I'm not familiar with those, so if you could give some examples it\n> would help us judge if they would indeed benefit from a fix in\n> parse-partial-sexp rather than elsewhere.\n\nc-mode provides an example. I don't remember where exactly and how but it has to\ndo with but c-before-context-fl-expand-region and c-state-semi-safe-place\nbecause I am advising these two functions currently.\n\nThe logic is roughly like this, c-mode engine doesn't respect\nfont-lock-dont-widen, widens stuff in some of it's functions, then calls its\nparsing, gets back some points outside font-lock range and blows when trying to\naccess those points from narrowed region.\n\nI was not collecting these cases carefully but I will start doing it and will\nget with more concrete examples in the following weeks.\n\nAnother directions of problems is syntax-propertize. It can be called with POS\noutside of current narrowed region. Particularly from\ninternal--syntax-propertize. But again I don't recall how exactly that was\nhappening now.\n\n> Replace all your widen calls with calls to `prog-widen' and you get the same\n> result (since (nth 1 prog-indentation-context) is basically another name for\n> your hard-widen-limit).  So I don't think prog-widen is that very complicated.\n\nIt's not but you have to enforce that in all known modes.\n\n> Note that \"is more general\" here means that the major mode's function has to\n> handle more cases, so it would seem to fundamentally require more work on the\n> major mode's side.\n\nI don't agree. Work must be done only in the generic multi-mode engines\n(mmm-mode, polymode etc). Other modes should re-use that generic infrastructure,\nor even better, do nothing, and leave to someone else to define a new polymode\nwith host chunk being *the* mode. That every mode with basic needs for inner\nsub-modes tries to re-invent the wheel is a dead end.\n\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_663.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai6Tt-0006fs-Lj\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 16:31:17 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:45575)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1ai6Ts-0006fj-2U\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:31:17 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <acm@muc.de>) id 1ai6To-00067V-Nm\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:31:16 -0400\nReceived: from mail.muc.de ([193.149.48.3]:12479)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1ai6To-00067C-Ev\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:31:12 -0400\nReceived: (qmail 94986 invoked by uid 3782); 21 Mar 2016 20:31:10 -0000\nReceived: from acm.muc.de (p579E9371.dip0.t-ipconnect.de [87.158.147.113]) by\n\tcolin.muc.de (tmda-ofmipd) with ESMTP;\n\tMon, 21 Mar 2016 21:31:08 +0100\nReceived: (qmail 5821 invoked by uid 1000); 21 Mar 2016 20:33:58 -0000\nDate: Mon, 21 Mar 2016 20:33:58 +0000\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <20160321203358.GB3641@acm.fritz.box>\nReferences: <fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87fuvjeq9g.fsf@gmail.com>\nMIME-Version: 1.0\nContent-Type: text/plain; charset=us-ascii\nContent-Disposition: inline\nIn-Reply-To: <87fuvjeq9g.fsf@gmail.com>\nUser-Agent: Mutt/1.5.24 (2015-08-30)\nX-Delivery-Agent: TMDA/1.1.12 (Macallan)\nFrom: Alan Mackenzie <acm@muc.de>\nX-Primary-Address: acm@muc.de\nX-detected-operating-system: by eggs.gnu.org: FreeBSD 9.x\nX-Received-From: 193.149.48.3\nCc: Dmitry Gutov <dgutov@yandex.ru>, Stefan Monnier <monnier@iro.umontreal.ca>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 20:31:17 -0000\nContent-Length: 1756\nLines: 45\n\nHello, Vitalie.\n\nOn Mon, Mar 21, 2016 at 05:42:51PM +0100, Vitalie Spinu wrote:\n\n> >> On Mon, Mar 21 2016 10:43, Stefan Monnier wrote:\n\n> >> parse-partial-sexp is called from code exclusively and it just happens\n> >> that in multi-modes it is called outside of narrow region quite often.\n\n> > How/why?  Can you give some concrete scenario?\n\n> MM engine narrows to span region for a lot of tasks, most importantly\n> font-lock. If inner mode fortification functions misbehaves (ignoring\n> font-lock-dont-widen for example) like c-mode does this leads to trouble.\n\nThat's a misunderstanding of what `font-lock-dont-widen' is.  It's\npurely a signal to font-lock.  Its doc string makes clear that it's\nintended for use by major modes.  It is for a major mode to set this\nflag, not to act on it.\n\nCC Mode absolutely needs to widen, to get the context necessary for\ncorrect fontification and indentation (which can be an arbitrary depth).  \n\n> So to avoid those troubles you would advice individual functions and\n> narrow them properly or apply other tricks like overwriting output\n> value or input args.  It all works fine till that function calls\n> parse-partial-sexp (or some other low level function) and blows with\n> args-out-of-range error.\n\nReading some of the posts on emacs-devel today, it strikes me that\nnarrowing might be the wrong tool for marking the boundaries of distinct\nregions where different major modes are in effect.  It seems to cause\nnothing but trouble.  I don't know what the right tool is, and it may\nnot currently exist in Emacs.  But it might be a good use of time to\nwork out what properties such boundary markers ought to have, and if\nnecessary, to implement them.\n\n[ .... ]\n\n\n>   Vitalie\n\n-- \nAlan Mackenzie (Nuremberg, Germany).\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_665.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai780-0003F2-FV\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 17:12:44 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59165)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai77x-0003Eu-W0\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 17:12:42 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai77t-0000po-UU\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 17:12:41 -0400\nReceived: from mail-wm0-x231.google.com ([2a00:1450:400c:c09::231]:36605)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ai77t-0000ph-NP\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 17:12:37 -0400\nReceived: by mail-wm0-x231.google.com with SMTP id r129so67368363wmr.1\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 14:12:37 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=cXGB2Psq6onN7vQ7/glUJ/ttch0S313SqeSYCP4A3xY=;\n\tb=BrTj2ngqiA5cW3VBDc4d0nsM0hOFDk76Bm59ua1gS7nFuzzMaOHFfodZPM8UE8TMDs\n\tI6cb2BKW1XYPssJFgd1d1GeiqMec7gc5sRb6/nuTviBzrNosZaCLajrvNwtFwugEr4bA\n\t5rT05WbS5FwGCCyC3gwnYeOHlKh0ubUdAlfSPt/1UcqbPpsGs0188QmvOXSsynzH/ikW\n\tbAD9RmM8pKvCgIn/GkMT4fNfkm55Bx5X5c78T33pwF9BJgGZI9n4Xp3HzoDrt8Wx5u2a\n\tY507/rbKyzhIHDMhEKdTmiqhkEXEavqHHTFlT6Bwnu0W49X7yfOkK7JKJwpjLX4nmx9K\n\tFVsw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=cXGB2Psq6onN7vQ7/glUJ/ttch0S313SqeSYCP4A3xY=;\n\tb=OYfmigD2zaCW0wb+WVyS4LR2iSTxQLNyO4QKJYPEWNNAOp7avDnDrTHs5fRIA/FaL2\n\tO8kIRKYPXko73dQAAvPK/DDoJj/PAgqAHvlx7HkwRqc9PQHBGnCgLjxC913jorg+9URR\n\tQhto0xsqAAJVxAJo6wXWpn6+KKymGWGxQZjyc9/ONeobJybBrn75ZQJCPR0MmaKqQ1lF\n\tob4gqoYPzz5u7vt8yhiTZ2yxZSqtqR1cBsJjUjAa79413u1y+fsFG3l/WwjDsGIXHsfB\n\tsjTpTSowZoaE8QCqD4rIFW5WMRoH/1JatSJdgcY/+gVF3RUUyTW4NOXfahc4EaK4VxlY\n\t+/mA==\nX-Gm-Message-State: AD7BkJJL0i3Axor9ApZJ2DTFN/O4v6hwjCGuLdjdHDvlzuzOK/W6933hprrKCffG1ahZ5A==\nX-Received: by 10.194.120.99 with SMTP id lb3mr37475026wjb.59.1458594757107;\n\tMon, 21 Mar 2016 14:12:37 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\tv5sm14115310wmg.16.2016.03.21.14.12.35\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 21 Mar 2016 14:12:36 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Alan Mackenzie <acm@muc.de>, Vitalie Spinu <spinuvit@gmail.com>\nReferences: <fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87fuvjeq9g.fsf@gmail.com> <20160321203358.GB3641@acm.fritz.box>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <fc61a7f8-7e62-fddb-d523-8cfd5e5aff2a@yandex.ru>\nDate: Mon, 21 Mar 2016 23:12:34 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <20160321203358.GB3641@acm.fritz.box>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::231\nCc: Stefan Monnier <monnier@iro.umontreal.ca>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 21:12:42 -0000\nContent-Length: 605\nLines: 15\n\nHi Alan,\n\nOn 03/21/2016 10:33 PM, Alan Mackenzie wrote:\n\n>> MM engine narrows to span region for a lot of tasks, most importantly\n>> font-lock. If inner mode fortification functions misbehaves (ignoring\n>> font-lock-dont-widen for example) like c-mode does this leads to trouble.\n>\n> That's a misunderstanding of what `font-lock-dont-widen' is.  It's\n> purely a signal to font-lock.  Its doc string makes clear that it's\n> intended for use by major modes.\n\nIt does not. The docstring gives examples of the modes where it can be \nuseful. It does not say that the variable can only be set by a major mode.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_667.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai6z7-0002fz-81\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 17:03:33 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55395)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1ai6z4-0002aT-97\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 17:03:31 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1ai6yz-0006RF-5J\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 17:03:30 -0400\nReceived: from userp1040.oracle.com ([156.151.31.81]:36457)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1ai6yy-0006Qe-Ta\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 17:03:25 -0400\nReceived: from userv0021.oracle.com (userv0021.oracle.com [156.151.31.71])\n\tby userp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2) with\n\tESMTP id u2LL3IIn025926\n\t(version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);\n\tMon, 21 Mar 2016 21:03:18 GMT\nReceived: from aserv0122.oracle.com (aserv0122.oracle.com [141.146.126.236])\n\tby userv0021.oracle.com (8.13.8/8.13.8) with ESMTP id u2LL3IbF019883\n\t(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=FAIL);\n\tMon, 21 Mar 2016 21:03:18 GMT\nReceived: from abhmp0001.oracle.com (abhmp0001.oracle.com [141.146.116.7])\n\tby aserv0122.oracle.com (8.13.8/8.13.8) with ESMTP id u2LL3FWB020868;\n\tMon, 21 Mar 2016 21:03:16 GMT\nMIME-Version: 1.0\nMessage-ID: <4e3e38d1-dd93-401e-8e54-c6e4396e17a9@default>\nDate: Mon, 21 Mar 2016 14:03:14 -0700 (PDT)\nFrom: Drew Adams <drew.adams@oracle.com>\nSender: Drew Adams <drew.adams@oracle.com>\nTo: Alan Mackenzie <acm@muc.de>, Vitalie Spinu <spinuvit@gmail.com>\nSubject: RE: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87fuvjeq9g.fsf@gmail.com> <20160321203358.GB3641@acm.fritz.box>\nIn-Reply-To: <20160321203358.GB3641@acm.fritz.box>\nX-Priority: 3\nX-Mailer: Oracle Beehive Extensions for Outlook 2.0.1.9  (901082) [OL\n\t12.0.6744.5000 (x86)]\nContent-Type: text/plain; charset=us-ascii\nContent-Transfer-Encoding: quoted-printable\nX-Source-IP: userv0021.oracle.com [156.151.31.71]\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic]\nX-Received-From: 156.151.31.81\nCc: emacs-devel <dev@caliopen.local>,\n\tStefan Monnier <monnier@iro.umontreal.ca>, Dmitry Gutov <dgutov@yandex.ru>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 21:03:31 -0000\nContent-Length: 1737\nLines: 42\n\n> Reading some of the posts on emacs-devel today, it strikes me that\n> narrowing might be the wrong tool for marking the boundaries of distinct\n> regions where different major modes are in effect.  It seems to cause\n> nothing but trouble.\n>\n> I don't know what the right tool is, and it may not currently exist\n> in Emacs.  But it might be a good use of time to work out what\n> properties such boundary markers ought to have, and if necessary,\n> to implement them.\n\nIndeed.  Just what properties do you need for \"such boundary\nmarkers\"?\n\nWhat's wrong with using _markers_ to mark area boundaries?\n(That's what I use in library zones.el, for example.)\n\nWhat's wrong with using text properties to mark areas?\n(That's what I use in library isearch-prop.el, for example.)\n\nI haven't been following this thread except for scanning it,\nso I don't really know what the need is for messing with\nnarrowing (or for defining another, \"harder\" narrowing).\n\nI'm just hoping that at the end of the day our age-old\nnarrowing feature will at least remain as it has been, for\nboth programs and interactive use.\n\n(It doesn't give me confidence, a priori, to have seen that\nat least one developer involved expressed little understanding\nof how users and programs actually use narrowing, thinking\nthat narrowing is only for hiding text you do not want to see.)\n\nHow about a bit of a spec (description, summary) of what you\nreally need?  It's not even clear what the problem is that you\nare trying to solve.\n\nLook at the Subject line of this thread, and that of the bug\nthread that this one derived from, and that of the other\nderived thread, for the patch \"hard-widen-limits\".  Do they\nreally characterize what this is all about: the problem to be\nsolved?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_669.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai6ln-0002EY-Fl\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 16:49:47 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49952)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai6ll-00028p-2Y\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:49:45 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai6lh-00022n-R0\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:49:44 -0400\nReceived: from chene.dit.umontreal.ca ([132.204.246.20]:49034)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai6lh-00022j-M9\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:49:41 -0400\nReceived: from pastel.home (lechon.iro.umontreal.ca [132.204.27.242])\n\tby chene.dit.umontreal.ca (8.14.1/8.14.1) with ESMTP id u2LKo9KJ017337; \n\tMon, 21 Mar 2016 16:50:09 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid C234A60587; Mon, 21 Mar 2016 16:49:38 -0400 (EDT)\nFrom: Stefan Monnier <monnier@IRO.UMontreal.CA>\nTo: Alan Mackenzie <acm@muc.de>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <jwvr3f3v9pc.fsf-monnier+Inbox@gnu.org>\nReferences: <fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87fuvjeq9g.fsf@gmail.com> <20160321203358.GB3641@acm.fritz.box>\nDate: Mon, 21 Mar 2016 16:49:38 -0400\nIn-Reply-To: <20160321203358.GB3641@acm.fritz.box> (Alan Mackenzie's message\n\tof \"Mon, 21 Mar 2016 20:33:58 +0000\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-NAI-Spam-Flag: NO\nX-NAI-Spam-Threshold: 5\nX-NAI-Spam-Score: 0\nX-NAI-Spam-Rules: 1 Rules triggered\n\tRV5617=0\nX-NAI-Spam-Version: 2.3.0.9418 : core <5617> : inlines <4542> : streams\n\t<1606670> : uri <2171606>\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 132.204.246.20\nCc: Vitalie Spinu <spinuvit@gmail.com>, Dmitry Gutov <dgutov@yandex.ru>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 20:49:46 -0000\nContent-Length: 362\nLines: 11\n\n> Reading some of the posts on emacs-devel today, it strikes me that\n> narrowing might be the wrong tool for marking the boundaries of distinct\n> regions where different major modes are in effect.  It seems to cause\n> nothing but trouble.\n\nThat's why prog-indent-context just provides the boundaries of the\ncurrent chunk but without narrowing.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_671.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai4cR-0005d5-LY\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 14:31:59 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:44555)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai4cO-0005cI-Jy\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:31:57 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai4cK-0002Lb-Hw\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:31:56 -0400\nReceived: from pruche.dit.umontreal.ca ([132.204.246.22]:40546)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai4cK-0002LT-Cj\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 14:31:52 -0400\nReceived: from pastel.home (lechon.iro.umontreal.ca [132.204.27.242])\n\tby pruche.dit.umontreal.ca (8.14.7/8.14.1) with ESMTP id u2LIVmWU009015;\n\tMon, 21 Mar 2016 14:31:48 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid 4FCD26059C; Mon, 21 Mar 2016 14:31:48 -0400 (EDT)\nFrom: Stefan Monnier <monnier@IRO.UMontreal.CA>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <jwv37rjya4z.fsf-monnier+Inbox@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87fuvjeq9g.fsf@gmail.com>\nDate: Mon, 21 Mar 2016 14:31:48 -0400\nIn-Reply-To: <87fuvjeq9g.fsf@gmail.com> (Vitalie Spinu's message of \"Mon, 21\n\tMar 2016 17:42:51 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-NAI-Spam-Flag: NO\nX-NAI-Spam-Threshold: 5\nX-NAI-Spam-Score: 0\nX-NAI-Spam-Rules: 1 Rules triggered\n\tRV5617=0\nX-NAI-Spam-Version: 2.3.0.9418 : core <5617> : inlines <4541> : streams\n\t<1606620> : uri <2171518>\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 132.204.246.22\nCc: Alan Mackenzie <acm@muc.de>, emacs-devel <dev@caliopen.local>,\n\tDmitry Gutov <dgutov@yandex.ru>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 18:31:57 -0000\nContent-Length: 4545\nLines: 100\n\n> To be frank, the issue of parse-partial-sexp is fading because modes\n> are now using syntax-ppss more extensively.\n\nAha!  So we agree: there's no reason to worry about parse-partial-sexp,\nsince at this point pretty much all modes rely on syntax-ppss (other\nthan CC-modes, obviously).\n\n> Most of the problems with parse-partial-sexp from the past are now\n> internalized in condition-case inside syntax-ppss. That condition-case\n> is triggered very often (almost always) from inside polymode\n> chunk narrowing.\n\nRight.  But I don't see it as a problem in parse-partial-sexp, rather\nthan a problem in syntax.el.\n\n>> Because you need it for *everything* that looks at the buffer.\n>> Why should parse-partial-sexp be different from (say) scan-sexps?\n> I think parse-partial-sexp, syntax-ppss and maybe some others, are special in\n> the sense that in order to return a correct value they need to be aware of the\n> whole buffer. I don't see this as an inconsistency but I might be too naive.\n\nscan-sexps will complain about unmatched parens and things like that if\nit bumps into point-min/max.  Same for re-search-*.  I think you've just\nbeen too often exposed to the use of (parse-partial-sexp 1 ...) where\nthe resulting signal bites you right away, whereas many other functions\nwon't signal an error and will instead do *something* (which may not\nalways be incorrect, but may often enough still result in acceptable\nbehavior).\n\n> c-mode provides an example. I don't remember where exactly and how but\n> it has to do with but c-before-context-fl-expand-region and\n> c-state-semi-safe-place because I am advising these two\n> functions currently.\n\nCC-modes is definitely a very special case here.\nWe should aim to limit the amount of changes in most major modes, so\nbetter not pay too much attention to cc-mode from that point of view.\n\n> The logic is roughly like this, c-mode engine doesn't respect\n> font-lock-dont-widen, widens stuff in some of it's functions, then\n> calls its parsing, gets back some points outside font-lock range and\n> blows when trying to access those points from narrowed region.\n\nSounds like a problem in cc-mode, which will require changes in\ncc-mode.  The generic code shouldn't worry about that.\n\n> I was not collecting these cases carefully but I will start doing it and will\n> get with more concrete examples in the following weeks.\n\nThanks.\n\n> Another directions of problems is syntax-propertize.  It can be called\n> with POS outside of current narrowed region.  Particularly from\n> internal--syntax-propertize.\n\nThat would definitely be an error, so if you bump into such a case\nplease report it.\n\n>> Replace all your widen calls with calls to `prog-widen' and you get\n>> the same result (since (nth 1 prog-indentation-context) is basically\n>> another name for your hard-widen-limit).  So I don't think prog-widen\n>> is that very complicated.\n> It's not but you have to enforce that in all known modes.\n\nI prefer to say that \"any major mode which wants to play with the new\nsnazzy multi-mode feature needs to be adjusted (e.g. with prog-widen)\".\nIt's perfectly fine if some major modes don't play along correctly until\nthey're fixed.\n\n\"Try to get multi-mode working without touching anyone's code\"\n(e.g. using advice) is great, but we already have packages which do that.\n\n>> Note that \"is more general\" here means that the major mode's function has to\n>> handle more cases, so it would seem to fundamentally require more work on the\n>> major mode's side.\n\n> I don't agree. Work must be done only in the generic multi-mode engines\n> (mmm-mode, polymode etc).\n\nThe \"is more general\" I was quoting was talking about the ways the\ngeneric code can call the major-mode-specific code.  If this is more\ngeneric, it means the major-mode-specific code needs to handle more\nsituations (e.g. STRING-BEFORE appearing not just at the beginning of\na chunk).\n\n> Other modes should re-use that generic infrastructure, or even better,\n> do nothing, and leave to someone else to define a new polymode with\n> host chunk being *the* mode. That every mode with basic needs for\n> inner sub-modes tries to re-invent the wheel is a dead end.\n\nI don't understand: every major mode's indentation code will have to pay\nattention to the STRING-BEFORE/AFTER that it receives from the generic\ncode and will have to do something with it (it can ignore it but at the\ncost of sub-optimal results).  And AFAIK this can only be done by the\nmajor mode's code, not the generic mode's code.\n[ I feel like I must be missing something.  ]\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_673.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai5JD-0002aM-6S\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 15:16:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55563)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai5JA-0002ZN-BZ\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 15:16:09 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai5J5-0004yd-BF\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 15:16:08 -0400\nReceived: from mail-wm0-x236.google.com ([2a00:1450:400c:c09::236]:37290)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <spinuvit@gmail.com>) id 1ai5J5-0004yS-4F\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 15:16:03 -0400\nReceived: by mail-wm0-x236.google.com with SMTP id p65so134759067wmp.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 12:16:03 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:references:date:in-reply-to:message-id\n\t:user-agent:mime-version;\n\tbh=MS71ooPew5MGvkr2kA+snDoeywHwT3Ir19wh1/k5820=;\n\tb=LiDeV70+eVrM5vJzWK7fhs7yJiCW0p4+Bgtftov6qewr0u/FpM4tVtYv3BJ1pXgKZj\n\tXDfGu41srGMPBHLjjZPY9hgD4J8cCXJm2F7HdlGHJq48dfGDXLUwIIPo9SI0PNko+shd\n\t2V16+KCuqqcQmTG8fVWo6L0kErMVb1410uwE2+79DIVMp5q/oXvgDZEuEoAam+gHk6sn\n\tVbDqTKGyl5wphyuycqnYx0DzopMYWErw36OGlFHJV6N+WGcAe6kGG99enTptDRRvHIjX\n\tymG6oKLdEYFbDHN5Tl8WleC9b7YGIo7wZXL6G0qGSuCAFzQ5s5oo/R2G1GUiv7z+ROwt\n\tOZMg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to\n\t:message-id:user-agent:mime-version;\n\tbh=MS71ooPew5MGvkr2kA+snDoeywHwT3Ir19wh1/k5820=;\n\tb=YgR2SRznfJyGdEvr3bZG+e5Hh/DaqymD8LO7blXn5rDGatg75t11qSMWx/Almu8Bw4\n\t9lRyP5bxtKYRu6aRszym9+2A0q5xHWirxpRHmYIFA8d6GgW/wWwb4+eMcAz+wp/ZVT8f\n\tmebsKjDLTP+eeENWCEGsIyILiq5UaAo8o2GktvK3Rryx7pJrTtc0LdXKE1SpMLnAKv86\n\t1MIlxZIjRNWs5S1UxG04bbwfLin82SjrtCGAM5UY29oWkDzulbxcxK66qyltDBWlBjH4\n\tVmmj7Eq4Tb9d9lo4x8AxaDQApcHPAZjEMGp326VvEprLgW1PBkMQv6gZkWuIz+DhH1yk\n\tPSEQ==\nX-Gm-Message-State: AD7BkJL/I+pUBkVAmFwtC9t1sIGN1TElpxXmyKhn1rZM23JYnn4BDPjMhC/qTNdP1QHJIA==\nX-Received: by 10.28.107.80 with SMTP id g77mr14773276wmc.18.1458587762543;\n\tMon, 21 Mar 2016 12:16:02 -0700 (PDT)\nReceived: from localhost ([143.176.214.220]) by smtp.gmail.com with ESMTPSA id\n\ti5sm26693165wjx.15.2016.03.21.12.16.01\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tMon, 21 Mar 2016 12:16:01 -0700 (PDT)\nFrom: Vitalie Spinu <spinuvit@gmail.com>\nTo: Stefan Monnier <monnier@IRO.UMontreal.CA>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87fuvjeq9g.fsf@gmail.com> <jwv37rjya4z.fsf-monnier+Inbox@gnu.org>\nDate: Mon, 21 Mar 2016 20:16:01 +0100\nIn-Reply-To: <jwv37rjya4z.fsf-monnier+Inbox@gnu.org> (Stefan Monnier's message\n\tof \"Mon, 21 Mar 2016 14:31:48 -0400\")\nMessage-ID: <87pound4lq.fsf@gmail.com>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.91 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::236\nCc: Alan Mackenzie <acm@muc.de>, Dmitry Gutov <dgutov@yandex.ru>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 19:16:09 -0000\nContent-Length: 1242\nLines: 27\n\n\n\n>> On Mon, Mar 21 2016 14:31, Stefan Monnier wrote:\n\n>> Other modes should re-use that generic infrastructure, or even better,\n>> do nothing, and leave to someone else to define a new polymode with\n>> host chunk being *the* mode. That every mode with basic needs for\n>> inner sub-modes tries to re-invent the wheel is a dead end.\n\n> I don't understand: every major mode's indentation code will have to pay\n> attention to the STRING-BEFORE/AFTER that it receives from the generic\n> code and will have to do something with it (it can ignore it but at the\n> cost of sub-optimal results).  And AFAIK this can only be done by the\n> major mode's code, not the generic mode's code.\n> [ I feel like I must be missing something.  ]\n\nThe hope is that most modes will need the default implementation. Maybe\nprog-indentation-funciton need not even know about those arguments. Along what\nDmitry proposed, there could be an optional extra piece\nprog-indentation-with-virtual-context-function that modes might choose to set.\n\nI think it might be good to try this as part of a multi-mode engine first. I can\ntry it with polymode. If it's generic enough it could be ported into emacs in a\nlater stage to be re-used with other multi-mode setups.\n\n  Vitalie\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_675.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ai6jj-0006Ro-Ps\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 16:47:39 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49558)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai6jh-0006MU-JQ\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:47:38 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai6je-0001m7-4z\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:47:37 -0400\nReceived: from pruche.dit.umontreal.ca ([132.204.246.22]:43247)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1ai6jd-0001m2-Vu\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 16:47:34 -0400\nReceived: from pastel.home (lechon.iro.umontreal.ca [132.204.27.242])\n\tby pruche.dit.umontreal.ca (8.14.7/8.14.1) with ESMTP id u2LKlWZi001570;\n\tMon, 21 Mar 2016 16:47:32 -0400\nReceived: by pastel.home (Postfix, from userid 20848)\n\tid 3ECD360587; Mon, 21 Mar 2016 16:47:32 -0400 (EDT)\nFrom: Stefan Monnier <monnier@IRO.UMontreal.CA>\nTo: Vitalie Spinu <spinuvit@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nMessage-ID: <jwvwpovv9se.fsf-monnier+Inbox@gnu.org>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\n\t<87twk0beuh.fsf@gmail.com> <jwvd1qoyqv0.fsf-monnier+Inbox@gnu.org>\n\t<877fgvgbr1.fsf@gmail.com> <jwvlh5bx6bc.fsf-monnier+Inbox@gnu.org>\n\t<87fuvjeq9g.fsf@gmail.com> <jwv37rjya4z.fsf-monnier+Inbox@gnu.org>\n\t<87pound4lq.fsf@gmail.com>\nDate: Mon, 21 Mar 2016 16:47:32 -0400\nIn-Reply-To: <87pound4lq.fsf@gmail.com> (Vitalie Spinu's message of \"Mon, 21\n\tMar 2016 20:16:01 +0100\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-NAI-Spam-Flag: NO\nX-NAI-Spam-Threshold: 5\nX-NAI-Spam-Score: 0\nX-NAI-Spam-Rules: 1 Rules triggered\n\tRV5617=0\nX-NAI-Spam-Version: 2.3.0.9418 : core <5617> : inlines <4542> : streams\n\t<1606669> : uri <2171605>\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 132.204.246.22\nCc: Alan Mackenzie <acm@muc.de>, Dmitry Gutov <dgutov@yandex.ru>,\n\temacs-devel <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 20:47:38 -0000\nContent-Length: 451\nLines: 11\n\n> The hope is that most modes will need the default implementation. Maybe\n> prog-indentation-funciton need not even know about those arguments. Along what\n> Dmitry proposed, there could be an optional extra piece\n> prog-indentation-with-virtual-context-function that modes might choose to set.\n\nThat was the idea being the prog-indent-context: indentation functions\ncan choose to use if they wish, but by default they don't have to.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_677.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahu0B-00065D-Lt\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 03:11:47 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55105)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1ahu08-00063w-SG\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 03:11:45 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1ahu04-0001wT-Rf\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 03:11:44 -0400\nReceived: from mout.kundenserver.de ([217.72.192.75]:57139)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1ahu04-0001w7-IO\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 03:11:40 -0400\nReceived: from [192.168.178.35] ([77.6.149.198]) by mrelayeu.kundenserver.de\n\t(mreue101) with ESMTPSA (Nemesis) id 0LnSVK-1a95Hh0jSY-00hb0L for\n\t<emacs-devel@gnu.org>; Mon, 21 Mar 2016 08:11:39 +0100\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: dev@caliopen.local\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com>\n\t<jwvmvpswowh.fsf-monnier+Inbox@gnu.org> <87twk0beuh.fsf@gmail.com>\nFrom: =?UTF-8?Q?Andreas_R=c3=b6hler?= <andreas.roehler@online.de>\nMessage-ID: <56EF9F0D.8040600@online.de>\nDate: Mon, 21 Mar 2016 08:13:17 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101\n\tIcedove/38.5.0\nMIME-Version: 1.0\nIn-Reply-To: <87twk0beuh.fsf@gmail.com>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-Provags-ID: V03:K0:Hzbr5TF7R/I1eDGWL7+68JbvOZFlGiaEp8QoCD6+6iFCQ2gknOy\n\t5thkMhUxZtYQ7NGIyXbPp5FYxLgBRcVsC4mZsX2+WDU+dqtysYZ1C0AWGTa0TDK73I1ggEM\n\tXoC1Zw0H6dnohQadTcqryFVZ8XwO3W5ECRk1IoCB6viwxmxPy67ryP9i44R6ds2IzswNC33\n\thkys1C1Up03b0a06aJoww==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:VDHaHe7sKIs=:3OfWDOs57lFb2j15Y95dIS\n\tGFs4O3LWAb4xi7sa3r2oD7vH8ADFlkEa8tcxgP74fI60K6F8DhV3kEWeKmInKvliVVA6iEqcF\n\t/yyYGTGn59N7Rr7ECDPtChwHmPWv413bVtisObZY+YqZKYy+UKavDwzsF56OZ68WoCRa6q74H\n\tDNM4Xy3RD/8Q+o9/fnTl4x/H4O5MOwYQiw53QWsvBVGwjUOuSjS/4ZI6w/mcak1eEJUIehSpV\n\tJZ0L9UuZalGRf6B9Np2Ti0d6W0MSG6osSE0nuu8812g7bDwiPNwoHIVmsHBZrae3c53Wsb30o\n\tcfKhT1PBYlj3C1DMl/rTlOva+bFkblIz7ykGk8kIKr3LTwpyDqwnphTVsoTuMlXBgXPoBUEEw\n\tBP9tGK1RuW++my9SOyoZDTtnMeR6wX0K2pg7kqhYOSEDa/PRdi6cttXPyht7JR2VLnU+ZNGHa\n\tiB91QSUQAt7qsiPPyso/xJXtxBOwVbZRNshotTKjUORcjNc8fLoU+BgzA/QeTCfyqT7EarONb\n\tmd9xTAN6DAbM4kgNreepSrZiD1A6AWPdb8ENgqEwwGLgge/G1dXzF/2NaKcDIjbSnCZmC395x\n\tvLEeLZsinpcPGChZQ7YPQaspj1srxxQUcXzvOWKsp2OzBlTTmdhI6wTYGbL9cv+j1v+YWaqb8\n\tpuPTtdXLvNI4xjWsbfdlgdzFqbRjyMnL1i3m826Mo6qrbwJJABAA/GtjCLGT9VCw/glczxM3T\n\tt4w2U/5EM5I4Sooj\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 217.72.192.75\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 07:11:46 -0000\nContent-Length: 2294\nLines: 54\n\n\n\nOn 21.03.2016 06:05, Vitalie Spinu wrote:\n>\n>>> On Sun, Mar 20 2016 23:11, Stefan Monnier wrote:\n>>> BTW, I parse-partial-sexp must abide hard-widen-limits as well.\n>> I don't understand what this means.  parse-partial-sexp is passed\n>> 2 locations and it works between them.  There's not much opportunity\n>> for widening.\n> parse-partial-sexp should work between hard limits (at least the lower\n> bound). It should operate as if hard-narrowed buffer is the real buffer.\n>\n> So ideally it should take (max FROM (car hard-widen-limits)) as the starting\n> position. This will give the desired consistency between parse-partial-sexp and\n> syntax-ppss with the price of slightly modifying the semantics of\n> parse-partial-sexp in a backward compatible way.\n>\n>>> A patch that would require hunting every single mode out there and\n>>> implementing multi-modes locally should have been more carefully\n>>> considered IMO.\n>> I must say I don't understand how what we have is so very different from\n>> what you suggest.\n> It's quite a bit different:\n>\n>    - Major mode authors won't need to know about multi-modes. That means not\n>      dealing with chunks/spans/headers etc. These concepts are not even uniformly\n>      defined between existing multi-mode engines.\n>    \n>    - Major mode authors won't need to re-implement the indentation logic already\n>      there in multi-modes. The logic is likely to be too simplistic and major\n>      mode authors will have to re-do it anyways.\n>\n>    - Setup is more general. multi-mode engine decides where to call\n>      calculate-indent-function and with what parameters and with what narrowing.\n>\n>    - Arguably calculate-indent-function is a simpler concept to grasp\n>\n>    - calculate-indent-function is needed anyways\n>\n>> I think both suggestions require changes to every mode, and in both cases the\n>> changes can be reduced to a one-liner or close enough (for the simple\n>> case). Admittedly, for it to be a one-liner, we'll need to provide a standard\n>> helper function.\n> Judging from python.el it might be quite hard to provide a generic one liner to\n> deal with all those 3 elements. For calculate-indent-function instead you can\n> provide a straightforward one line assuming that STRING-BEFORE/AFTER do not\n> matter.\n>\n>\n>    Vitalie\n>\n\n+1\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_679.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ahyTR-0006mg-Ct\n\tfor mharc-dev@caliopen.local; Mon, 21 Mar 2016 07:58:17 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59816)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahyT6-0006OI-VT\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 07:58:15 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahyRx-0002bq-Du\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 07:57:56 -0400\nReceived: from mail-wm0-x22e.google.com ([2a00:1450:400c:c09::22e]:35580)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1ahyRx-0002bH-35\n\tfor dev@caliopen.local; Mon, 21 Mar 2016 07:56:45 -0400\nReceived: by mail-wm0-x22e.google.com with SMTP id l68so106736765wml.0\n\tfor <dev@caliopen.local>; Mon, 21 Mar 2016 04:56:44 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=PWfU5+CyeaNO5iH849XBduyOl8hNb+ZJHnRcQY4xfpk=;\n\tb=M2Px7ohjkVk7BP8UqNHm4vWrtfN2ArZQXcIKwh4i7wvHOO4vt8BQ1WMQY7c0MCJY2e\n\t80aONRbFpLV1KYkh0robULMCbpLP777+lrw1/es/dqu+/u1EZOKYs5Yc6DUv7gqt7RGv\n\tFMzTwvP1SAzhiHJPdnYDACb+F2lQAmt8YZEsEEVWRbiBoQ/XHviwNXJnHP7QlaOyO9o+\n\toa/Urq0Ntrl1rJbqeqVzGL2nnmDYsySSU+XCkZltlpJeP/LP1s/A/lkxYLGdavLH79qR\n\tDGCjnIGSm6ujKEnlHkYlHcqWU/XdsIQceDEna3up9K9f4AzrrS3WUhRVoB5ScXf8NDv6\n\t7zYg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=PWfU5+CyeaNO5iH849XBduyOl8hNb+ZJHnRcQY4xfpk=;\n\tb=E55RLKYuImDx5S70dSDLcaqViq2Wghp33huMJZsuHO1cPpApiPd9ZbFMZ+/mI+k6uq\n\tn4n8+1Q14+JdTqTr0Fi8SWoToXpivHwT/AaeloPOEo2FX8HTuWjxzcU70596tKBOxy2M\n\t3Q4NjFamSufm7Ko1CV+FSEej/ITtTklTkCaUgmtraEQfabiTYabNZ361+W18R4ZihOYD\n\tKahbO9Kx71p2YJflU4CRjRAmc4E8ShVu8FNXn6DjMiQR2u50oDwU5kRtPVmVBSjXQu/F\n\tHTqlUGBhQT9QmQzBqcpC8bwrn4MoE0mqDdWTgtj/xMsQtPqSHb63qwvAzYpbI+6jWGaX\n\twIWA==\nX-Gm-Message-State: AD7BkJL7sn2rTJWLuJcvVaIKnVfib9FxDkjyDPMuddh6AVfRi7pWYyXttBqaNGxtFlhuvQ==\nX-Received: by 10.28.21.75 with SMTP id 72mr12856929wmv.64.1458561404330;\n\tMon, 21 Mar 2016 04:56:44 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\ts66sm12203994wmb.6.2016.03.21.04.56.42\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tMon, 21 Mar 2016 04:56:43 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: Stefan Monnier <monnier@IRO.UMontreal.CA>,\n\tVitalie Spinu <spinuvit@gmail.com>\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\n\t<e069c6fc-c458-cb30-64a1-c636f86b5d6b@yandex.ru>\n\t<874mc2dqtk.fsf@gmail.com>\n\t<fbb84dbe-6f99-9770-17cc-e541ab708803@yandex.ru>\n\t<87egb5cpmg.fsf@gmail.com>\n\t<aba8e203-f2c7-851b-39ff-9ebd2147f55f@yandex.ru>\n\t<87a8lsd4j3.fsf@gmail.com> <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <c742dfdd-d455-6428-9462-5a6236256a1d@yandex.ru>\nDate: Mon, 21 Mar 2016 13:56:41 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <jwvmvpswowh.fsf-monnier+Inbox@gnu.org>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:400c:c09::22e\nCc: Alan Mackenzie <acm@muc.de>, emacs-devel <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 21 Mar 2016 11:58:16 -0000\nContent-Length: 801\nLines: 16\n\nOn 03/21/2016 05:11 AM, Stefan Monnier wrote:\n\n> I must say I don't understand how what we have is so very different from\n> what you suggest.  Of course, I fully agree on the need to deprecate\n> indent-line-function and use a side-effect free replacement which\n> returns the desired indentation (instead performing the indentation).\n>\n> I think both suggestions require changes to every mode, and in both\n> cases the changes can be reduced to a one-liner or close enough (for\n> the simple case).  Admittedly, for it to be a one-liner, we'll need to\n> provide a standard helper function.\n\nIt also sounds like we should revert the changes that brought in \nprog-indentation-context in emacs-25, and proceed with the results of \nthis discussion on master. Provided we reach an agreement here, of course.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_681.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1afWMy-0001oS-Rs\n\tfor mharc-dev@caliopen.local; Mon, 14 Mar 2016 13:33:28 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52570)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1afWMw-0001lF-2P\n\tfor dev@caliopen.local; Mon, 14 Mar 2016 13:33:26 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1afWMs-0007WC-2d\n\tfor dev@caliopen.local; Mon, 14 Mar 2016 13:33:26 -0400\nReceived: from mout.kundenserver.de ([217.72.192.75]:49669)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <andreas.roehler@online.de>) id 1afWMr-0007Vl-QK\n\tfor dev@caliopen.local; Mon, 14 Mar 2016 13:33:22 -0400\nReceived: from [192.168.178.35] ([77.12.82.127]) by mrelayeu.kundenserver.de\n\t(mreue102) with ESMTPSA (Nemesis) id 0MBke7-1aVJEW1Gf1-00Ambm;\n\tMon, 14 Mar 2016 18:33:19 +0100\nSubject: Re: Syntax tables for multiple modes [was: bug#22983: syntax-ppss\n\treturns wrong result.]\nTo: dev@caliopen.local\nReferences: <20160311151512.GD2888@acm.fritz.box>\n\t<b158555f-e014-ed7b-23eb-d80d2d77a6f4@yandex.ru>\n\t<20160311212410.GG2888@acm.fritz.box>\n\t<73903215-f94b-e194-7bfe-0d6350c95769@yandex.ru>\n\t<20160311221540.GH2888@acm.fritz.box>\n\t<2c301ec9-041d-9172-d628-479062314b23@yandex.ru>\n\t<20160314151621.GF1894@acm.fritz.box>\nFrom: =?UTF-8?Q?Andreas_R=c3=b6hler?= <andreas.roehler@online.de>\nMessage-ID: <56E6F635.6030400@online.de>\nDate: Mon, 14 Mar 2016 18:34:45 +0100\nUser-Agent: Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101\n\tIcedove/38.5.0\nMIME-Version: 1.0\nIn-Reply-To: <20160314151621.GF1894@acm.fritz.box>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-Provags-ID: V03:K0:31A34G4wZpQJcuFBb9DyAJ9m1nI7TaPO6HIX/4scPZq77lwg8K5\n\t3GVl2vMmSQJG5cGwNnkzWk9DBJoYnKSdx2Q0yK4uP9q1cFCMGSlAdN2A6ls4nryKQBVYjc6\n\t3/hyy/HRk+mSxf1YgK8MHmN8sO3x122SRTQedTRXRKXMh5POmKba9nIYHUdbfcI3OLqWnZD\n\tj5TLSGhRmqZNZY2ms2l/A==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:1bMzXb9HGKY=:5WsVCP19DzN4a8+pVNvMKg\n\t16sytt89fMUnuF2lxXrJICpPs39LXroKcxyTeXj1gYyR49dp/Zl0ug4Ed7wZHDBMEpsaWbeZz\n\tGarShhldoWTI24O4joZGnSfBs01ST/0vWfLngzTwoj9EVFCFMaBXWLWmDAlYjIStOeW1VVYtP\n\txQxhVsWcAPD4nsV48f0vN1dcOmP17qV8Oyzv7MDhLxnVeuE02JVaMy45SUgYSSu9N9y0N7wys\n\tUJSJaM4UTwtOftypicvL4hoiLGjvHORgFj3qCGkrZbhKn8swdKDFkKLpUOsZ1X+gYiHoFoazS\n\t4GuczsK5dTPviBgxgma385u6QTIalUSZLEf5SoHEWD+JtG9BNWWu/mO+qT6IKcUtOv4a965YD\n\tYjWWaPy3+6rbg4eQI8G2J6YeNx41JeaBcxeUeNXeOY5Gbp0CUH+MCa6bayADyCytzZMjRVUUu\n\tgTexB67T13ko2blF4//IvXYH9v4IqGfmvYLkS5cbBAYLvNXOiioIt45g1yaiyEyaD05VO5xbc\n\t2yV3oChLF1N2XDe4zlWbXVa6/EDc2qaDaNvh43ZP44k+sFA/EIVaGS/9RESvd+LR49jHb4/FN\n\trAEiuSbSau3p1tJzXV5zhN3XtMni6KR9GU/PGXQoLwpq4FTzOvz9fH4+KVZePONrlvWnWgkd+\n\tobseu/OljdzcHm4h6LkZBQ9MDuoFeujJ43aXINbL/3k0hihp+uicoqVvkn2FWo1GJAYLB6A1D\n\tLe8SKsZ+7TJFtQuz\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 217.72.192.75\nCc: Alan Mackenzie <acm@muc.de>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Mon, 14 Mar 2016 17:33:27 -0000\nContent-Length: 1604\nLines: 33\n\n\n\nOn 14.03.2016 16:16, Alan Mackenzie wrote:\n> Hello, Dmitry.\n>\n> On Sat, Mar 12, 2016 at 12:38:49AM +0200, Dmitry Gutov wrote:\n>> On 03/12/2016 12:15 AM, Alan Mackenzie wrote:\n>>>> Except the multiple-major-mode case, which we'd ideally try to\n>>>> accommodate, too.\n>>> How does this code handle the changeover of syntax tables at a mode\n>>> boundary?\n>> The \"inner\" regions start with an \"empty\", top-level state. This is\n>> actually fine, because these are usually small enough not to benefit\n>> from the syntax-ppss cache too much (and syntax-ppss-last still helps).\n>> The parts of the outer region following a subregion with different\n>> syntax table... rely on a few hacks, and a manual application of a\n>> `syntax-table' property when necessary. We need a better solution there,\n>> but it's probably out of scope for this discussion.\n> How about an extension to the parse-partial-sexp (etc.) code?  For\n> example, a feature I would call an \"island\", where a character could be\n> marked with the \"island start\" syntax-table property, and another\n> character lower down could be marked with the \"island end\" character.\n> parse-partial-sexp, on encountering an island start syntax would somehow\n> stack the current parse state and start a new one with a different syntax\n> table.  The parse state, instead of consisting of the 10 elements\n> currently, would in general have 10n elements, where n is the depth of\n> nesting.\n> 0\n\nAFAIU narrowing would provide that already WRT  parse-partial-sexp, \nmaybe combined with some markup like folding-mode. Remains to hand over \nthese sheets to font-lock.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_683.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1al2Cr-0005Yd-L3\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 18:33:49 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40198)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <angelo.graziosi@alice.it>) id 1al2Cp-0005XS-Aq\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 18:33:48 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <angelo.graziosi@alice.it>) id 1al2Ck-0007bY-By\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 18:33:47 -0400\nReceived: from smtp203.alice.it ([82.57.200.99]:5146)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <angelo.graziosi@alice.it>) id 1al2Ck-0007bU-3s\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 18:33:42 -0400\nReceived: from [192.168.1.101] (79.47.237.141) by smtp203.alice.it\n\t(8.6.060.43) (authenticated as angelo.graziosi@alice.it)\n\tid 56FAA32500320B1B for dev@caliopen.local;\n\tWed, 30 Mar 2016 00:33:40 +0200\nTo: Emacs developers <emacs-devel@gnu.org>\nFrom: Angelo Graziosi <angelo.graziosi@alice.it>\nSubject: About column numbers\nMessage-ID: <56FB02BB.3090501@alice.it>\nDate: Wed, 30 Mar 2016 00:33:31 +0200\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101\n\tThunderbird/38.7.1\nMIME-Version: 1.0\nContent-Type: text/plain; charset=iso-8859-15; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 82.57.200.99\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 22:33:48 -0000\nContent-Length: 472\nLines: 17\n\nWhen the option about column numbers is enabled, the column numbers \nstart from \"0\" (zero). In other words, when the cursor in in the \ntop-left corner of the buffer, the mode line displays\n\n   (1,0)\n\nline number   : 1\ncolumn number : 0\n\nWhy columns start from 0 (zero) and not 1 as for lines? Would it be \nbetter (1,1)?\n\nIs there some settings to change this behavior? If not, I would suggest \nto change this in Emacs.. or to add some settings to change this..\n\n  Angelo\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_685.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alHwQ-0004hF-DN\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 11:21:54 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60675)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1alHwJ-0004gD-A8\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 11:21:53 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1alHwF-0003XT-AZ\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 11:21:47 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:35226)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1alHwF-0003XN-6a; Wed, 30 Mar 2016 11:21:43 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3994\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1alHwE-0002C2-Fl; Wed, 30 Mar 2016 11:21:42 -0400\nDate: Wed, 30 Mar 2016 18:21:29 +0300\nMessage-Id: <83wpokuh3a.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Angelo Graziosi <angelo.graziosi@alice.it>\nIn-reply-to: <56FB02BB.3090501@alice.it> (message from Angelo Graziosi on Wed, \n\t30 Mar 2016 00:33:31 +0200)\nSubject: Re: About column numbers\nReferences: <56FB02BB.3090501@alice.it>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 15:21:53 -0000\nContent-Length: 979\nLines: 31\n\n> From: Angelo Graziosi <angelo.graziosi@alice.it>\n> Date: Wed, 30 Mar 2016 00:33:31 +0200\n> \n> When the option about column numbers is enabled, the column numbers \n> start from \"0\" (zero). In other words, when the cursor in in the \n> top-left corner of the buffer, the mode line displays\n> \n>    (1,0)\n> \n> line number   : 1\n> column number : 0\n> \n> Why columns start from 0 (zero) and not 1 as for lines? Would it be \n> better (1,1)?\n\nWhy is it a problem for column number to start at zero?  Emacs always\nworked like that.\n\n> Is there some settings to change this behavior?\n\nNo.  You could, of course, come up with a mode-line format that would\ndisplay column-number + 1, but that would only change this on display,\nnot internally.\n\n> If not, I would suggest to change this in Emacs.. or to add some\n> settings to change this..\n\nThen you'd need to change every interface that accepts or returns\ncolumn numbers, like move-to-column, posn-at-point, etc.  Is it really\nworth it?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_687.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alJvf-00061I-T6\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 13:29:15 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:54691)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <angelo.graziosi@alice.it>) id 1alJvc-0005zd-Tq\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 13:29:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <angelo.graziosi@alice.it>) id 1alJvX-0001sx-JO\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 13:29:12 -0400\nReceived: from smtp203.alice.it ([82.57.200.99]:51059)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <angelo.graziosi@alice.it>)\n\tid 1alJvX-0001sa-8F; Wed, 30 Mar 2016 13:29:07 -0400\nReceived: from [192.168.1.101] (79.47.237.141) by smtp203.alice.it\n\t(8.6.060.43) (authenticated as angelo.graziosi@alice.it)\n\tid 56FAA32500A6B691; Wed, 30 Mar 2016 19:29:05 +0200\nSubject: Re: About column numbers\nTo: Eli Zaretskii <eliz@gnu.org>\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\nFrom: Angelo Graziosi <angelo.graziosi@alice.it>\nMessage-ID: <56FC0CDB.9090707@alice.it>\nDate: Wed, 30 Mar 2016 19:28:59 +0200\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101\n\tThunderbird/38.7.1\nMIME-Version: 1.0\nIn-Reply-To: <83wpokuh3a.fsf@gnu.org>\nContent-Type: text/plain; charset=iso-8859-15; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 82.57.200.99\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 17:29:13 -0000\nContent-Length: 1100\nLines: 38\n\n\nIl 30/03/2016 17:21, Eli Zaretskii ha scritto:\n>> From: Angelo Graziosi <angelo.graziosi@alice.it>\n>> Date: Wed, 30 Mar 2016 00:33:31 +0200\n>>\n>> When the option about column numbers is enabled, the column numbers\n>> start from \"0\" (zero). In other words, when the cursor in in the\n>> top-left corner of the buffer, the mode line displays\n>>\n>>     (1,0)\n>>\n>> line number   : 1\n>> column number : 0\n>>\n>> Why columns start from 0 (zero) and not 1 as for lines? Would it be\n>> better (1,1)?\n>\n> Why is it a problem for column number to start at zero?  Emacs always\n> worked like that.\n\nYes, I know.. this is not a fundamental, but why that \"unnatural\" \nconvention? I don't know other editor/IDE which uses columns from zero..\n\n\n>> If not, I would suggest to change this in Emacs.. or to add some\n>> settings to change this..\n>\n> Then you'd need to change every interface that accepts or returns\n> column numbers, like move-to-column, posn-at-point, etc.  Is it really\n> worth it?\n>\n\nwhat a pity! It would be an opportunity to modernize a bit Emacs...\n\nAnyway it was just a suggestion...\n\n   Angelo\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_689.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alKCQ-0005Rm-SG\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 13:46:34 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59896)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1alKCM-0005OX-PU\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 13:46:33 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>) id 1alKCL-0006Kx-SH\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 13:46:30 -0400\nReceived: from aserp1040.oracle.com ([141.146.126.69]:48494)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <drew.adams@oracle.com>)\n\tid 1alKCG-0006Iw-UJ; Wed, 30 Mar 2016 13:46:25 -0400\nReceived: from aserv0021.oracle.com (aserv0021.oracle.com [141.146.126.233])\n\tby aserp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2) with\n\tESMTP id u2UHkKkG007777\n\t(version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);\n\tWed, 30 Mar 2016 17:46:20 GMT\nReceived: from aserv0122.oracle.com (aserv0122.oracle.com [141.146.126.236])\n\tby aserv0021.oracle.com (8.13.8/8.13.8) with ESMTP id u2UHkKkn003084\n\t(version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);\n\tWed, 30 Mar 2016 17:46:20 GMT\nReceived: from abhmp0007.oracle.com (abhmp0007.oracle.com [141.146.116.13])\n\tby aserv0122.oracle.com (8.13.8/8.13.8) with ESMTP id u2UHkHJh005967;\n\tWed, 30 Mar 2016 17:46:19 GMT\nMIME-Version: 1.0\nMessage-ID: <830ba1d5-5844-455c-9c8e-02671771dce4@default>\nDate: Wed, 30 Mar 2016 10:46:16 -0700 (PDT)\nFrom: Drew Adams <drew.adams@oracle.com>\nSender: Drew Adams <drew.adams@oracle.com>\nTo: Angelo Graziosi <angelo.graziosi@alice.it>, Eli Zaretskii <eliz@gnu.org>\nSubject: RE: About column numbers\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\nIn-Reply-To: <56FC0CDB.9090707@alice.it>\nX-Priority: 3\nX-Mailer: Oracle Beehive Extensions for Outlook 2.0.1.9  (901082) [OL\n\t12.0.6744.5000 (x86)]\nContent-Type: text/plain; charset=us-ascii\nContent-Transfer-Encoding: quoted-printable\nX-Source-IP: aserv0021.oracle.com [141.146.126.233]\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic]\nX-Received-From: 141.146.126.69\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 17:46:33 -0000\nContent-Length: 763\nLines: 22\n\n> > Why is it a problem for column number to start at zero?  Emacs always\n> > worked like that.\n>=20\n> Yes, I know.. this is not a fundamental, but why that \"unnatural\"\n> convention? I don't know other editor/IDE which uses columns from zero..\n\nWe should start with column 42. ;-)\n\n> >> If not, I would suggest to change this in Emacs.. or to add some\n> >> settings to change this..\n> >\n> > Then you'd need to change every interface that accepts or returns\n> > column numbers, like move-to-column, posn-at-point, etc.  Is it really\n> > worth it?\n> >\n>=20\n> what a pity! It would be an opportunity to modernize a bit Emacs...\n\nModernize schmodernize. ;-)  Every new idea is not\nnecessarily a great idea.  And every recently expressed\nidea is not necessarily new.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_691.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amrIW-0002VP-Kb\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:19:12 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38156)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrIU-0002T6-Ff\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:19:11 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrIT-0001kX-Gy\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:19:10 -0400\nReceived: from mail-oi0-x22a.google.com ([2607:f8b0:4003:c06::22a]:35830)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>)\n\tid 1amrIR-0001kC-7i; Sun, 03 Apr 2016 19:19:07 -0400\nReceived: by mail-oi0-x22a.google.com with SMTP id p188so145576324oih.2;\n\tSun, 03 Apr 2016 16:19:07 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=KUJ4YnrYH+VWK7gQDx8I6onAZIQ28NZ2J/6xt8v8NcU=;\n\tb=eamh85y9IU8ZbBeluZDOlc//f1c3pEzKerAxlwKjSFqbbQLrpieGLEmOsR1FB8yd6k\n\tDwE8OQT9mjIlsvaeNQinNcUkOp8TXbgcKdFNM1PpDq4DFguA6WP2dvi5PMPjNhvSX8kg\n\tgDXGrtydzVyH20apiZ8lkI0Ea7TdjdLIC0adnurpXjoSAMtUgRDrbeB3UUI+slAlRW7j\n\tYIfd8p1r1NRJThgGr0WsqImxxvotwpDTptwgycxn6qCZifAYRuRdf4m7Hv5rEcnIy9/6\n\tj0cspfV4LeSb7iNBKNoNgE5xpd+9VYw5lLEH9Fj57l8Q04Q1hO4kJyDLhvuDZrgNb831\n\tZ30Q==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=KUJ4YnrYH+VWK7gQDx8I6onAZIQ28NZ2J/6xt8v8NcU=;\n\tb=EmIDH97IGB4Sn1/CCdKgewKfr/o6nFhY2llw6zG7EuKQDJ5joK1/Dvs856H6mYkNrx\n\t0TKLEkkfO2ENf21xy1/FTajSeG9Kyum9WT3GopDoSJwAKXWsN8F/V4+r6qdkQAIpwZVG\n\teQSb3szyCqlEV1wsb83JJlVC5JuML8K+XqZgUzOl1KTUv0PDDZSY+DNyQ2o5op+0dosS\n\tB1ZSMftbm7b85kgOu3sTTT3sGRm6gyy0AzgD5XHHDhuQ00kmtIFz2k22hO1ogza6C1wQ\n\tcwUDaopOOhpIQGZM1QMTCBYLAIRyv0q5tma2oz8E4Gbq1HSzwWrCM6LJCavhlAv2zcY3\n\t2Fzg==\nX-Gm-Message-State: AD7BkJKOJ9M/z41shFYHhHAM5+3H8nN8wnw/FdBkYODd18fY3sTWOHyVBd43KRMQpAuASw==\nX-Received: by 10.202.4.86 with SMTP id 83mr9058066oie.132.1459725546852;\n\tSun, 03 Apr 2016 16:19:06 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79])\n\tby smtp.gmail.com with ESMTPSA id 89sm360536otf.31.2016.04.03.16.19.04\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 16:19:05 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid 7007313DAE515; Sun,  3 Apr 2016 16:19:03 -0700 (PDT)\nTo: Drew Adams <drew.adams@oracle.com>\nSubject: Re: About column numbers\nIn-Reply-To: <830ba1d5-5844-455c-9c8e-02671771dce4@default> (Drew Adams's\n\tmessage of \"Wed, 30 Mar 2016 10:46:16 -0700 (PDT)\")\nDate: Sun, 03 Apr 2016 16:15:20 -0700\nMessage-ID: <m24mbiffnb.fsf@newartisans.com>\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<830ba1d5-5844-455c-9c8e-02671771dce4@default>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Drew Adams <drew.adams@oracle.com>, Angelo Graziosi\n\t<angelo.graziosi@alice.it>, Eli Zaretskii <eliz@gnu.org>,\n\temacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::22a\nCc: Eli Zaretskii <eliz@gnu.org>, dev@caliopen.local,\n\tAngelo Graziosi <angelo.graziosi@alice.it>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:19:11 -0000\nContent-Length: 483\nLines: 13\n\n>>>>> Drew Adams <drew.adams@oracle.com> writes:\n\n>> what a pity! It would be an opportunity to modernize a bit Emacs...\n\n> Modernize schmodernize. ;-) Every new idea is not necessarily a great idea.\n> And every recently expressed idea is not necessarily new.\n\nI see no reason to change the starting column for Emacs at this time.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_693.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alKS9-0001qZ-1x\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 14:02:49 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36133)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <yurivkhan@gmail.com>) id 1alKS2-0001pk-UN\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 14:02:47 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <yurivkhan@gmail.com>) id 1alKS1-0002lA-Vw\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 14:02:42 -0400\nReceived: from mail-lf0-x234.google.com ([2a00:1450:4010:c07::234]:35923)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <yurivkhan@gmail.com>)\n\tid 1alKS1-0002ku-Nw; Wed, 30 Mar 2016 14:02:41 -0400\nReceived: by mail-lf0-x234.google.com with SMTP id e133so42075020lfe.3;\n\tWed, 30 Mar 2016 11:02:41 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:sender:in-reply-to:references:from:date:message-id\n\t:subject:to:cc:content-transfer-encoding;\n\tbh=gFpiJGLYqMrixPuOUbH88akp+Y/V2qAieyPfHgxsZ34=;\n\tb=umZQv/48PIgZ1Qs3GW08GF/ZAwDlD4tesA/FqJ/dgItjVy5us85Psd16nIHEaEZrBV\n\tM+CIEE1tTrxVrgmYEyJcHpHxG+ClfiCGOooj9Dji3MNuXLXqtsZq023Omb9yDaX6n4d5\n\t5wVTa+hLMICX6B1QhgKO+puJgCTcktvQEINqUExo1Gf2vW9qstSkjx2l2NSmdd8dDx//\n\t2zCwreYs56NfzG/UwU0riQyYQVgPE8/Z9J1ulInjBy91Eree3ZLfR2omwKbiMXiP2bUy\n\tAqF2KUJUGnr9CpZYFRvcuAdVuhmYK122lgpA3bbPDcVfPO1Kr5Gg8RQjK0fn3vXJYiQ6\n\tRYDQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:sender:in-reply-to:references:from\n\t:date:message-id:subject:to:cc:content-transfer-encoding;\n\tbh=gFpiJGLYqMrixPuOUbH88akp+Y/V2qAieyPfHgxsZ34=;\n\tb=WHZIzrswqgv3xWDxLPmxCcBREZceQbaeDhJEkbdaEPa8PDTApBORITMuCDfysNaxfr\n\tsnSPLMcqsclGKRUf/yil03LthCNPqu72LKyx+jI3+YOTWS8xiEpvZHBLOhmK5WvgCm3y\n\tRyaMWZ7rA2WpPUc0LHczp0tRVInTc/t/XJnFjwLxaClxfLaobom+FaNlqujJFkG5mw33\n\taQ0oLjXXvxS2mW26SpJlPDs5XT1LDGNCraNa7RWvG8dvEjAhrdp8PRGFL2lvrp+uIel/\n\tWWX1xnFOyrdSbeP4BTQuEQSyjJDp/SWAQI1eehtXBeNO2iudmYXluVY/NKaXwDyboqh4\n\tACUg==\nX-Gm-Message-State: AD7BkJLMe7mWT+P/aTiPSWR2nAIybo7JOCx7oQ21J5OxTNk57fbQzAAIy9JgPyBANpjgrO/RSLy3v1OcsvCWCQ==\nX-Received: by 10.25.144.143 with SMTP id s137mr4677068lfd.53.1459360960397;\n\tWed, 30 Mar 2016 11:02:40 -0700 (PDT)\nMIME-Version: 1.0\nSender: yurivkhan@gmail.com\nReceived: by 10.112.254.164 with HTTP; Wed, 30 Mar 2016 11:02:20 -0700 (PDT)\nIn-Reply-To: <56FC0CDB.9090707@alice.it>\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\nFrom: Yuri Khan <yuri.v.khan@gmail.com>\nDate: Thu, 31 Mar 2016 00:02:20 +0600\nX-Google-Sender-Auth: 5j7mEdlsBJDiAyWlabWRubUohlo\nMessage-ID: <CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\nSubject: Re: About column numbers\nTo: Angelo Graziosi <angelo.graziosi@alice.it>\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c07::234\nCc: Eli Zaretskii <eliz@gnu.org>, Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 18:02:47 -0000\nContent-Length: 825\nLines: 25\n\nOn Wed, Mar 30, 2016 at 11:28 PM, Angelo Graziosi\n<angelo.graziosi@alice.it> wrote:\n\n> Yes, I know.. this is not a fundamental, but why that \"unnatural\"\n> convention? I don't know other editor/IDE which uses columns from zero..\n\nVim counts columns from 1.\nMidnight Commander counts from 0.\nMousepad counts from 0.\nNano counts from 1.\nJoe counts from 1.\nGedit counts from 1.\nKate counts from 1.\nGeany counts from 0.\nAnjuta counts from 0.\nFirefox=E2=80=99s Scratchpad counts from 1.\n\nCounting columns from 0 is very convenient. You look at the column\nnumber and immediately know how many characters you have before the\npoint, and whether you are on a tab stop.\n\nLines are a different beast =E2=80=94 you have to be compatible with the\nconvention compilers use to report errors, and pretty much all of them\ncount lines from 1.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_695.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alKh2-0006At-MW\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 14:18:12 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40081)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1alKh0-000691-UU\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 14:18:11 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1alKh0-0006E0-2Q\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 14:18:10 -0400\nReceived: from mail-out.m-online.net ([212.18.0.9]:57831)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>)\n\tid 1alKgw-0006Cx-JA; Wed, 30 Mar 2016 14:18:06 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qZwqc2yNKz3hjSF;\n\tWed, 30 Mar 2016 20:18:04 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qZwqc2JgvzvhMw;\n\tWed, 30 Mar 2016 20:18:04 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id e1xT-6YeDpkY; Wed, 30 Mar 2016 20:18:03 +0200 (CEST)\nX-Auth-Info: vv5ZAVi6JtJVDxhRyDctMPw7vesaqbPEPPiIdFwz9FuZMnt5/2m1y4B0zX7RdhNr\nReceived: from igel.home (ppp-88-217-14-4.dynamic.mnet-online.de [88.217.14.4])\n\tby mail.mnet-online.de (Postfix) with ESMTPA;\n\tWed, 30 Mar 2016 20:18:03 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid 1A9AF2C37E8; Wed, 30 Mar 2016 20:18:03 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Yuri Khan <yuri.v.khan@gmail.com>\nSubject: Re: About column numbers\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\nX-Yow: Did we bring enough BEEF JERKY?\nDate: Wed, 30 Mar 2016 20:18:02 +0200\nIn-Reply-To: <CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n\t(Yuri Khan's message of \"Thu, 31 Mar 2016 00:02:20 +0600\")\nMessage-ID: <87poubvnhh.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-detected-operating-system: by eggs.gnu.org: No matching host in p0f cache.\n\tThat's all we know.\nX-Received-From: 212.18.0.9\nCc: Eli Zaretskii <eliz@gnu.org>, Emacs developers <dev@caliopen.local>,\n\tAngelo Graziosi <angelo.graziosi@alice.it>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 18:18:11 -0000\nContent-Length: 413\nLines: 15\n\nYuri Khan <yuri.v.khan@gmail.com> writes:\n\n> Lines are a different beast — you have to be compatible with the\n> convention compilers use to report errors, and pretty much all of them\n> count lines from 1.\n\nThey also count columns from 1.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_697.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alL0V-0002e4-9C\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 14:38:19 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46427)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <yurivkhan@gmail.com>) id 1alL0S-0002dE-Ni\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 14:38:17 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <yurivkhan@gmail.com>) id 1alL0O-00044W-Uw\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 14:38:16 -0400\nReceived: from mail-lf0-x242.google.com ([2a00:1450:4010:c07::242]:32989)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <yurivkhan@gmail.com>)\n\tid 1alL0O-00044M-N5; Wed, 30 Mar 2016 14:38:12 -0400\nReceived: by mail-lf0-x242.google.com with SMTP id v198so5797661lfd.0;\n\tWed, 30 Mar 2016 11:38:12 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:sender:in-reply-to:references:from:date:message-id\n\t:subject:to:cc:content-transfer-encoding;\n\tbh=PoIrEo++EGLWC/Ve5Z5I5HEvCCbZ+jc9ahfot8R3p3s=;\n\tb=fhfZ+ynJiumWhxG4B3pr0gZusoE+/uesHWe7sg10Ge/GgEeVwb6huAXLjN5rIk2Gxx\n\t3UCNyHYmdLvgyqkA3eHU0GhMecVM0gosXzCz+NOs9bZjOOD+rwAH2DzJA5e9hDkODrAa\n\tM93y6Bv2y1sQJ0swj/aGwOLy13iq09FNY6Df7nIz/3KCQjjkuUN4DsJsuBG94oz2H3CV\n\t1Hn5uhL0M30JzBi9kZbEM6H+SZNUULG6qCZnbPAyqZ5qP9FqsX+h6Vv3ZpttteC2cgsu\n\t4bQYiOJd5bfsRPtn4L296drZLP3ipgoy7A2T1fM9PqbMa1Fa8ZVgqOWeYrnkqWUjvfB8\n\tcUzQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:sender:in-reply-to:references:from\n\t:date:message-id:subject:to:cc:content-transfer-encoding;\n\tbh=PoIrEo++EGLWC/Ve5Z5I5HEvCCbZ+jc9ahfot8R3p3s=;\n\tb=UT2jqTAcqGbP+LFJhyAenKhyXsWko8YXSff2R0VJykG11iMdnQA8aQaOLrB+CdVqMm\n\tHGbcjrI+xCr6WR6s251x0e8EYNjMF1A7VDqGwb1f9gvkSSAU59E07rv1mS/62R9jPoad\n\tfspjfHnGf52pBtS2hgoNtz+ZZPYjhwn5IsrpwfJnfqpFZRIUppv18yJFB8D1xZnAyQxq\n\t3QgfAIutBtjGwwuaPsiIq3HnmOWB4Zzsy8Al2i53YWx1KBM+amxBmDQyfCu9TxHaUO7t\n\t/3pLnFDkrgXWwrb3lwTkLkhVBxdWqG3HI5XPOoemHClWkp5mNQLcNlOw8wy8hy55MpeI\n\tClcQ==\nX-Gm-Message-State: AD7BkJJ4AmQ6tY+BzT+ws4A3zQepdLQjNgxGph8MaG9q0w75qY0STym26mcK4EtmubjCETig/3uiaFJ/Bo+PHg==\nX-Received: by 10.25.148.208 with SMTP id w199mr4831098lfd.124.1459363091807; \n\tWed, 30 Mar 2016 11:38:11 -0700 (PDT)\nMIME-Version: 1.0\nSender: yurivkhan@gmail.com\nReceived: by 10.112.254.164 with HTTP; Wed, 30 Mar 2016 11:37:52 -0700 (PDT)\nIn-Reply-To: <87poubvnhh.fsf@linux-m68k.org>\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n\t<87poubvnhh.fsf@linux-m68k.org>\nFrom: Yuri Khan <yuri.v.khan@gmail.com>\nDate: Thu, 31 Mar 2016 00:37:52 +0600\nX-Google-Sender-Auth: hLXnBbS-TIeZuaCrCy6gFmwvpDE\nMessage-ID: <CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\nSubject: Re: About column numbers\nTo: Andreas Schwab <schwab@linux-m68k.org>\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c07::242\nCc: Eli Zaretskii <eliz@gnu.org>, Emacs developers <dev@caliopen.local>,\n\tAngelo Graziosi <angelo.graziosi@alice.it>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 18:38:17 -0000\nContent-Length: 612\nLines: 23\n\nOn Thu, Mar 31, 2016 at 12:18 AM, Andreas Schwab <schwab@linux-m68k.org> wr=\note:\n\n>> Lines are a different beast =E2=80=94 you have to be compatible with the\n>> convention compilers use to report errors, and pretty much all of them\n>> count lines from 1.\n>\n> They also count columns from 1.\n\nAt least GCC doesn=E2=80=99t. It counts bytes. Perhaps that=E2=80=99s a bug=\n.\n\n$ cat test1.c\n/*=D1=8F*/foo\n\n$ gcc test1.c\ntest1.c:1:7: error: expected =E2=80=98=3D=E2=80=99, =E2=80=98,=E2=80=99, =\n=E2=80=98;=E2=80=99, =E2=80=98asm=E2=80=99 or =E2=80=98__attribute__=E2=80=\n=99\nat end of input\n /*=D1=8F*/foo\n       ^\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_699.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alLIQ-0000nl-HK\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 14:56:50 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:51329)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1alLIO-0000n5-CZ\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 14:56:49 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1alLIN-0000q5-MC\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 14:56:48 -0400\nReceived: from mail-out.m-online.net ([2001:a60:0:28:0:1:25:1]:41558)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>)\n\tid 1alLIJ-0000n2-U8; Wed, 30 Mar 2016 14:56:44 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qZxhB2RzQz3hjqn;\n\tWed, 30 Mar 2016 20:56:42 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qZxhB1Q7hzvhMc;\n\tWed, 30 Mar 2016 20:56:42 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id 7wcSWMEHBqiU; Wed, 30 Mar 2016 20:56:41 +0200 (CEST)\nX-Auth-Info: Jj8lEIB/G4iuyRp2m3f+D+UIoMyDTCpbRmfgXLchkrmmJbZJFd9oMm7GtvGB2+9R\nReceived: from igel.home (ppp-88-217-14-4.dynamic.mnet-online.de [88.217.14.4])\n\tby mail.mnet-online.de (Postfix) with ESMTPA;\n\tWed, 30 Mar 2016 20:56:41 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid 2568C2C388B; Wed, 30 Mar 2016 20:56:41 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Yuri Khan <yuri.v.khan@gmail.com>\nSubject: Re: About column numbers\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n\t<87poubvnhh.fsf@linux-m68k.org>\n\t<CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\nX-Yow: Yow!  I want to mail a bronzed artichoke to Nicaragua!\nDate: Wed, 30 Mar 2016 20:56:41 +0200\nIn-Reply-To: <CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\n\t(Yuri Khan's message of \"Thu, 31 Mar 2016 00:37:52 +0600\")\nMessage-ID: <87lh4zvlp2.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 2001:a60:0:28:0:1:25:1\nCc: Eli Zaretskii <eliz@gnu.org>, Emacs developers <dev@caliopen.local>,\n\tAngelo Graziosi <angelo.graziosi@alice.it>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 18:56:49 -0000\nContent-Length: 605\nLines: 25\n\nYuri Khan <yuri.v.khan@gmail.com> writes:\n\n> On Thu, Mar 31, 2016 at 12:18 AM, Andreas Schwab <schwab@linux-m68k.org=\n> wrote:\n>\n>>> Lines are a different beast =E2=80=94 you have to be compatible with =\nthe\n>>> convention compilers use to report errors, and pretty much all of the=\nm\n>>> count lines from 1.\n>>\n>> They also count columns from 1.\n>\n> At least GCC doesn=E2=80=99t. It counts bytes.\n\nIt does.  Starting from 1.\n\nAndreas.\n\n--=20\nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint =3D 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED=\n5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_701.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alLdC-0007bl-O8\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 15:18:18 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60412)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <yurivkhan@gmail.com>) id 1alLdA-0007ac-7c\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 15:18:17 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <yurivkhan@gmail.com>) id 1alLd4-0007nE-NB\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 15:18:16 -0400\nReceived: from mail-lf0-x22a.google.com ([2a00:1450:4010:c07::22a]:32989)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <yurivkhan@gmail.com>)\n\tid 1alLd4-0007n5-Et; Wed, 30 Mar 2016 15:18:10 -0400\nReceived: by mail-lf0-x22a.google.com with SMTP id p188so16221590lfd.0;\n\tWed, 30 Mar 2016 12:18:10 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:sender:in-reply-to:references:from:date:message-id\n\t:subject:to:cc:content-transfer-encoding;\n\tbh=p/i4jKcY7MtcgGJ/s7xfhFvpAUTORTipn91B0ngi17I=;\n\tb=DRWNeYPfPDxsUuig1UOAAgT9NVlto4SgYMKl5y9CM2zUsun+3/7fiyiMKQRpvxBH5z\n\t54goNCMZLTC9Wb7a+gs7MN2jd+10FCkJfZSG0wmDPb0QlzHN+/iyOpdU5XsM7zKbCp5z\n\t4I+OgVuHaCT8TI6lA9UDz/gkx3zeS8YJLSKRy50IKINsPmvF7HEn4l1fqic9tu9JO7CC\n\tKA4RZ2l77911+icas3fFP+sEkyUEm6ouZpQmh+r+fnCFriJk7YpCC2wIOrW40ugFUXUD\n\tooA/z52L/qCbGY19YOLvJG/W3HyD7dy11oDX32UsX/d/jKmKae4Tt1Op7isf1vKEvgom\n\t4rxA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:sender:in-reply-to:references:from\n\t:date:message-id:subject:to:cc:content-transfer-encoding;\n\tbh=p/i4jKcY7MtcgGJ/s7xfhFvpAUTORTipn91B0ngi17I=;\n\tb=haVwXve/5PZ3p6d3TFACgpCAXlRbqqoowfmTsqwZj5/Eyv+JWmdXxzDygmP5E0/vp6\n\tLX8uFjTcGQdjl6hwlydc+ilFEB4zRamnAXSs+RRdksLFtiTiS5f/SqzTPTKaRaD9cTAg\n\tsShCp+oXL+KWcoV46mnKvjjGWwStgc6kyVUctMeFtGeQTFT2ff5MW0mPTIqrVa6/oUUx\n\tkxmXopUOMAG+jWQqt8VmT7mNHk7szSkjp2kE8498Sn+g0SJqtQ7KieF+emKvCcgT61UA\n\tMcxYB2rlZyEwt4kE/wKC1QvCp4vNyqw5+TqVIeBJ+uvdxDhVIG5Ro4DLDdm/Uhk1qbf5\n\txRVg==\nX-Gm-Message-State: AD7BkJKKUajJqvx+x9ozn6SiNAf95b1oRAHaydnFO2j6gFLlYdFZPOjey4+QtMBWWLlsB+ukpudzvdyQMuMfKw==\nX-Received: by 10.25.135.8 with SMTP id j8mr4814939lfd.64.1459365489275; Wed,\n\t30 Mar 2016 12:18:09 -0700 (PDT)\nMIME-Version: 1.0\nSender: yurivkhan@gmail.com\nReceived: by 10.112.254.164 with HTTP; Wed, 30 Mar 2016 12:17:49 -0700 (PDT)\nIn-Reply-To: <87lh4zvlp2.fsf@linux-m68k.org>\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n\t<87poubvnhh.fsf@linux-m68k.org>\n\t<CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\n\t<87lh4zvlp2.fsf@linux-m68k.org>\nFrom: Yuri Khan <yuri.v.khan@gmail.com>\nDate: Thu, 31 Mar 2016 01:17:49 +0600\nX-Google-Sender-Auth: A17IjeA8MS1v7Bsp9JvavvhCBmE\nMessage-ID: <CAP_d_8WSBJMZ2aLbnUwiRh1-ieG9mdcTyE-TnFo5AF+hE+_GyA@mail.gmail.com>\nSubject: Re: About column numbers\nTo: Andreas Schwab <schwab@linux-m68k.org>\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c07::22a\nCc: Eli Zaretskii <eliz@gnu.org>, Emacs developers <dev@caliopen.local>,\n\tAngelo Graziosi <angelo.graziosi@alice.it>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 19:18:17 -0000\nContent-Length: 579\nLines: 16\n\nOn Thu, Mar 31, 2016 at 12:56 AM, Andreas Schwab <schwab@linux-m68k.org> wr=\note:\n\n>>> They also count columns from 1.\n>>\n>> At least GCC doesn=E2=80=99t. It counts bytes.\n>\n> It does.  Starting from 1.\n\nSo it=E2=80=99s mostly useless in a 0-based character-counting editor, and\nslightly less useless in a 1-based character-counting editor, and much\nmore useless in a cell-counting editor if you happen to use tabs. All\nin all, there is much more agreement about line numbers than column\nnumbers, which makes it possible for each editor/IDE to choose, or\neven let users choose.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_703.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alc3y-0001A0-4u\n\tfor mharc-dev@caliopen.local; Thu, 31 Mar 2016 08:51:02 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:53302)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1alc3s-00019g-Bz\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 08:51:00 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1alc3p-0002lh-1g\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 08:50:56 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:57173)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1alc3o-0002lX-Rf\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 08:50:52 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1alc3k-0007Vd-I8\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 14:50:48 +0200\nReceived: from 45.72.141.202 ([45.72.141.202])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 31 Mar 2016 14:50:43 +0200\nReceived: from monnier by 45.72.141.202 with local (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 31 Mar 2016 14:50:43 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: About column numbers\nDate: Thu, 31 Mar 2016 08:50:37 -0400\nMessage-ID: <jwvbn5u6cnu.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n\t<87poubvnhh.fsf@linux-m68k.org>\n\t<CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\n\t<87lh4zvlp2.fsf@linux-m68k.org>\n\t<CAP_d_8WSBJMZ2aLbnUwiRh1-ieG9mdcTyE-TnFo5AF+hE+_GyA@mail.gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 45.72.141.202\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:nju2ErtW1JaQbqR+aL3HG78tGko=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 31 Mar 2016 12:51:00 -0000\nContent-Length: 1257\nLines: 27\n\n> So it’s mostly useless in a 0-based character-counting editor, and\n\nNot exactly.  It just means that we can use the number without some\nextra massaging, such as taking into account compilation-first-column or\ncompilation-error-screen-columns (AFAIK there is currently no\ncorresponding support for distinguishing bytes/chars, even though it'd\nbe clearly beneficial).\n\nBut in any case this part of compile.el is a big mess, because every\ntool out there uses another convention in this regard and\ncompilation-error-regexp-alist doesn't record which tool uses\nwhich convention.\n\n\n        Stefan\n\n\nPS: If you like to bikeshed about counting from 0-vs-1, I have another\none for you: point-min should start at 0 rather than at 1.  And this one\ncould mostly be done without breaking too much code out there (it would\nbreak some code, but most of it is already fundamentally broken because\nit should use `point-min` rather than hardcoding 1, so it's not *that*\nterrible).  This aid, if you try to set BEG to 0, you'll soon discover\nthat Emacs's C code is not prepared for that.  Patches welcome ;-)\nBTW: Making such a change would slightly simplify the src/intervals.c code\nsince text-properties are used both on strings (start at 0) and buffers\n(start at 1).\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463569.5266_705.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alfiJ-0001dF-Qb\n\tfor mharc-dev@caliopen.local; Thu, 31 Mar 2016 12:44:55 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60015)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1alfiH-0001ct-QJ\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 12:44:54 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1alfiD-0002ys-Pn\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 12:44:53 -0400\nReceived: from mout.kundenserver.de ([212.227.126.134]:60753)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1alfiD-0002yl-Fo\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 12:44:49 -0400\nReceived: from [192.168.1.82] ([109.24.225.43]) by mrelayeu.kundenserver.de\n\t(mreue002) with ESMTPSA (Nemesis) id 0M9j3f-1aZtDb1n5z-00Cz3o for\n\t<emacs-devel@gnu.org>; Thu, 31 Mar 2016 18:44:48 +0200\nSubject: Re: About column numbers\nTo: dev@caliopen.local\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n\t<87poubvnhh.fsf@linux-m68k.org>\n\t<CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\n\t<87lh4zvlp2.fsf@linux-m68k.org>\n\t<CAP_d_8WSBJMZ2aLbnUwiRh1-ieG9mdcTyE-TnFo5AF+hE+_GyA@mail.gmail.com>\n\t<jwvbn5u6cnu.fsf-monnier+gmane.emacs.devel@gnu.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56FD53F6.5090805@gmail.com>\nDate: Thu, 31 Mar 2016 18:44:38 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <jwvbn5u6cnu.fsf-monnier+gmane.emacs.devel@gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"x7qnObMbD1RMNWkeqmOCaGFNjViSPAPT8\"\nX-Provags-ID: V03:K0:5TXDFtSzulvpkc8mT5xwIW+x9pkwcFe6VmbT5Wxg7RK8ArksL52\n\t6PVfZ62P90zqDhbqG9+QaobTqPQu/Up/JXPS9iFlhXCQDNPvHHgBY9BdljkUhVziQpd3WR2\n\t+FTzzZXdTKdgGoHC2tEjI+UyBFDqss5+4eidcMudqhXlcWa5XQ18LoaSaZ2i9SUP9oSUMKY\n\tVbC7LMaJ1LQNjaDF0xHuw==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:b6Y9ZWiMhGw=:p+rsDfczCImRlAZ6a6aOYk\n\tGmECUl8WYg5AWKO5wvCQ9qAoeCVFZYCJlTWTTH01oaBMA+13QjgRps6k1x1sMOsIkWS2Q05qF\n\t5jE6V8pdAOzouwasQG/LLUgu+BMgETH/W+lZyP4+QIjvJD/cl0F0WveyMqmu8Yghr2CSyTwFP\n\t5/KpTBvbBD03C7chJaqmdccfmhKzxQfOMv/RxHHsP+FOb2ZKzz52q4RD9bhqVdlus93BjO9oX\n\tkiy1P8/k9/kMC2kHN2ZUCcNnf8x4qGhXWZHTlr7v8KiLVMgdDWWMPbQypDwupzA7+tUNMLF1k\n\tuht596gGBZv4VYAsFjS7qk3v5Z8/Ugj4nDR+OAdOW40+xas0VweihEGtKPueayl11kvgGc7cs\n\tRgk5GAsftqu1L0AUcRPS+Pf+GCgLZw0LtFKp4u7ArwCjSpx2GAWiPoMQ+WL1yf21nO4DnkZOZ\n\t4xU0ouw0gV5skXeAijBvfaTuMdNeNAegqDcaITmZSmmoUw2DjL4CevTQv0tglXNAvzS4TylHn\n\t1SPAzc/L4vxmWaFVtlrL7VA2/1So7kIXBqbJdn+XUjMy11MdJLZG0df4fl1cwLpvdFnTbzfGj\n\tR9MW8jGL1g+yQ6e+KEO7w2ItO8omUK+PNz6HAo0f7tU+/f11rNcby1sW7XlJRRPVPQyzwyPLS\n\t5WcZRcVvXxEJZ2m3AwuMauJ4XL+Cz0iX4ypHNVvkbDyrtoZ7M+h2oM+oaH3/j/yNC5/8=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.134\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 31 Mar 2016 16:44:54 -0000\nContent-Length: 2766\nLines: 62\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--x7qnObMbD1RMNWkeqmOCaGFNjViSPAPT8\nContent-Type: multipart/mixed; boundary=\"eos5JO1MPSN4xoeiQaX5PvMtW9K3JBjre\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <56FD53F6.5090805@gmail.com>\nSubject: Re: About column numbers\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n <56FC0CDB.9090707@alice.it>\n <CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n <87poubvnhh.fsf@linux-m68k.org>\n <CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\n <87lh4zvlp2.fsf@linux-m68k.org>\n <CAP_d_8WSBJMZ2aLbnUwiRh1-ieG9mdcTyE-TnFo5AF+hE+_GyA@mail.gmail.com>\n <jwvbn5u6cnu.fsf-monnier+gmane.emacs.devel@gnu.org>\nIn-Reply-To: <jwvbn5u6cnu.fsf-monnier+gmane.emacs.devel@gnu.org>\n\n--eos5JO1MPSN4xoeiQaX5PvMtW9K3JBjre\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 03/31/2016 02:50 PM, Stefan Monnier wrote:\n> (it would break some code, but most of it is already fundamentally\n> broken because it should use `point-min` rather than hardcoding 1, so\n> it's not *that* terrible).\n\nAmusingly, always using (point-min) also leads to interesting bugs. For e=\nxample, Proof General uses one large overlay to indicate which part of a =\nbuffer has been processed. When updated, that overlay is set to cover (po=\nint-min) to (end of last processed statement). Of course, when narrowing =\nis in effect, this means that the overlay is made to start at the lowest =\nnarrowed point; then when the buffer is widened, you realize that the bou=\nnds of the overlay are wrong.\n\n\n--eos5JO1MPSN4xoeiQaX5PvMtW9K3JBjre--\n\n--x7qnObMbD1RMNWkeqmOCaGFNjViSPAPT8\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW/VP/AAoJEPqg+cTm90wjc0IP/3M53zWSTE2cbF0FAZTZZ48a\nj2vsrYOywGscmX7IiqlB7tma2zI3/91TbvoYvOroxMvXTFdbewCpcV1EdEiBoKnH\nd005n/sTcoLiuXdHqorA3LsqL/lG/rg5dFsF1ZYswLSAXIAbdhKRQ33GDh3B01hF\n414TdokCGX1e6CV/3tP6C1vMAY0Zw+vQy4JPnez7QDHoi8f8LLNqTvJw2FfSA2sb\n0tg8PNTIQnDRRKY/owZanUoJ7dA94uER2xfjPzULWp/P8yL/UiptyO7dkIGaQ3MZ\n6/1z7OFg2cz9QxnaXbgT0eFP18Srspq32ODfBNci2Epw5u3Ru2i43TPwDbVZ4JVN\nlOxhXviqFmrLfAd7OWdPOdKqnBzHE+U6pvOYoMkrCs4OCxEW3oiG9nDEtNU/kG4+\nbSNvOdGR7Khx+e0zFNuCD4qZD3au0WdPHHAM5RwDecaHK6GnHvSMv5YixwShE1EL\nbCmTOnFv0TtF/wvdmNEi4uhG2R90SS5WDcEheRZYs55C7Cvl6XzOYHWH5i1vfOZD\nhKd2+DKT1VU0ydPE5MGP769f6vvyHn2jKh33EZT56asjlezOv7wVZXKugoamdGaW\nZ85+YErreYTjryN96izlw0RXeztyVnt0itLaZOfidKlEGMyvCrXy4WiP2O/OpJ6C\nLp7/ZE1np47Ny+JAMXwP\n=J+hS\n-----END PGP SIGNATURE-----\n\n--x7qnObMbD1RMNWkeqmOCaGFNjViSPAPT8--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_707.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alkK5-0002k3-7Y\n\tfor mharc-dev@caliopen.local; Thu, 31 Mar 2016 17:40:13 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46826)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1alkK3-0002jf-7d\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 17:40:12 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1alkK0-00046m-2Z\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 17:40:11 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:37372)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1alkJz-00046b-S5\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 17:40:08 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1alkJx-0007qE-IA\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 23:40:05 +0200\nReceived: from 69-165-138-79.dsl.teksavvy.com ([69.165.138.79])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 31 Mar 2016 23:40:05 +0200\nReceived: from monnier by 69-165-138-79.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 31 Mar 2016 23:40:05 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: About column numbers\nDate: Thu, 31 Mar 2016 17:39:30 -0400\nMessage-ID: <jwv8u0y49bt.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n\t<87poubvnhh.fsf@linux-m68k.org>\n\t<CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\n\t<87lh4zvlp2.fsf@linux-m68k.org>\n\t<CAP_d_8WSBJMZ2aLbnUwiRh1-ieG9mdcTyE-TnFo5AF+hE+_GyA@mail.gmail.com>\n\t<jwvbn5u6cnu.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<56FD53F6.5090805@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-165-138-79.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:bZkYgfHnxk5uQEhpG4JevxBB3ug=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 31 Mar 2016 21:40:12 -0000\nContent-Length: 528\nLines: 12\n\n>> (it would break some code, but most of it is already fundamentally\n>> broken because it should use `point-min` rather than hardcoding 1, so\n>> it's not *that* terrible).\n> Amusingly, always using (point-min) also leads to interesting bugs.\n\nOh yes.  Along with the \"hard narrowing\" discussion, it would be good to\nhave a way to get the \"external point-min\".  Currently the only portable\nway (short of hardcoding 1) is via (save-restriction (widen)\n(point-min)) which is rather round-about and inefficient.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_709.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amOQE-0003IH-Ew\n\tfor mharc-dev@caliopen.local; Sat, 02 Apr 2016 12:29:14 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:51823)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <lokedhs@gmail.com>) id 1amOQC-0003I2-91\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 12:29:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <lokedhs@gmail.com>) id 1amOQB-00042z-C9\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 12:29:12 -0400\nReceived: from mail-vk0-x234.google.com ([2607:f8b0:400c:c05::234]:35459)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <lokedhs@gmail.com>) id 1amOQB-00042t-6w\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 12:29:11 -0400\nReceived: by mail-vk0-x234.google.com with SMTP id e6so136579905vkh.2\n\tfor <dev@caliopen.local>; Sat, 02 Apr 2016 09:29:10 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:date:message-id:subject:from:to\n\t:cc; bh=m+kFOi6JQ2HyeHJtwg16uKnEzDIzRM8dhLTXxYpWcOo=;\n\tb=oxlmlFwr/0LsgHbhdDVXPPX0R8GBl2093El7/UV/hhu9n0vCHOxO0wZB5CaWcFw1wm\n\tvJcLDgBOOXchsbQbXOms1i9S7Ddg3lyJ4L5KpOFmgg685CdOFJuXY3aGJ5uguNIFskq9\n\tlAh+OyLq4epQhxExFzNSsYWzi604oZKv8ZmvLv5eZ7AUJfRovnSxgv4v14E9XcBNFmNZ\n\tuRzdjJtZsnVmsjLzZyoL8DSxJfmu/BTnXrFdozgAHqyoy7CEOHuiB5DpTt2AmF1vLQbs\n\tH91B6HyaU5jLEwXTtIHdcoUiQxTVSZgjCpUxL0NvITVYY5y9dYn/RxWY/Lq1Y14PdXB4\n\tUI/w==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:date\n\t:message-id:subject:from:to:cc;\n\tbh=m+kFOi6JQ2HyeHJtwg16uKnEzDIzRM8dhLTXxYpWcOo=;\n\tb=YeXcxo56S6tU72hvnIC5ZiB40oYg9d1DO2/j9SNjnL6Hdpc5UmLYJRewqKLCz8CEf+\n\t8OhyUGE9ALtZ0RMgsP6LT1DOxVKzKwLaFC/CsDbP7kyT2/mRKrpUCZnVX2wSRQkte6YD\n\tKCQ2rSfwWEqXV/iW2oSQ6VydgmZ5PLbAIoK+cWE1rEGHmxae0EKyBwsJ00naQPw5nFHQ\n\ty+VV2uQDqLLGGHThXwQ2ejaktm970TJ46q6aeEZ+tAqZHIAhL84xnUbqpQt9I7ORRjYK\n\t5b84oC5+9ENIj/rApIAZPGX5igpoSf0GpNdPYSSMWeSXUcVUSurNPl2bCQkx6f8OC1/9\n\tGg8w==\nX-Gm-Message-State: AD7BkJLIFnt/j+1R0no9FJIZIh4dMHV0yMhn4M7fCvTa+pbSnx07woghFh6VU4OTqvyrVsLAkyEBejnCsgxR6g==\nMIME-Version: 1.0\nX-Received: by 10.159.37.100 with SMTP id 91mr4375684uaz.79.1459614550442;\n\tSat, 02 Apr 2016 09:29:10 -0700 (PDT)\nReceived: by 10.176.5.163 with HTTP; Sat, 2 Apr 2016 09:29:10 -0700 (PDT)\nIn-Reply-To: <jwv8u0y49bt.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n\t<87poubvnhh.fsf@linux-m68k.org>\n\t<CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\n\t<87lh4zvlp2.fsf@linux-m68k.org>\n\t<CAP_d_8WSBJMZ2aLbnUwiRh1-ieG9mdcTyE-TnFo5AF+hE+_GyA@mail.gmail.com>\n\t<jwvbn5u6cnu.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<56FD53F6.5090805@gmail.com>\n\t<jwv8u0y49bt.fsf-monnier+gmane.emacs.devel@gnu.org>\nDate: Sun, 3 Apr 2016 00:29:10 +0800\nMessage-ID: <CADtN0W+eax-pPtn5cxhLOubyjiV_s3Cr5MrfSkVhGm4Chm9t8w@mail.gmail.com>\nSubject: Re: About column numbers\nFrom: =?UTF-8?Q?Elias_M=C3=A5rtenson?= <lokedhs@gmail.com>\nTo: Stefan Monnier <monnier@iro.umontreal.ca>\nContent-Type: multipart/alternative; boundary=94eb2c122da4c9cb15052f82fe2f\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:400c:c05::234\nCc: emacs-devel <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 02 Apr 2016 16:29:13 -0000\nContent-Length: 2181\nLines: 50\n\n--94eb2c122da4c9cb15052f82fe2f\nContent-Type: text/plain; charset=UTF-8\n\nOn 1 April 2016 at 05:39, Stefan Monnier <monnier@iro.umontreal.ca> wrote:\n\n> >> (it would break some code, but most of it is already fundamentally\n> >> broken because it should use `point-min` rather than hardcoding 1, so\n> >> it's not *that* terrible).\n> > Amusingly, always using (point-min) also leads to interesting bugs.\n>\n> Oh yes.  Along with the \"hard narrowing\" discussion, it would be good to\n> have a way to get the \"external point-min\".  Currently the only portable\n> way (short of hardcoding 1) is via (save-restriction (widen)\n> (point-min)) which is rather round-about and inefficient.\n\n\nI have done this on occasions. Are there any cases where the save/widen\ndance actually gives a result which is not 1?\n\nRegards,\nElias\n\n--94eb2c122da4c9cb15052f82fe2f\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\"><div class=3D\"gmail_quote\">On 1=\n April 2016 at 05:39, Stefan Monnier <span dir=3D\"ltr\">&lt;<a href=3D\"mailt=\no:monnier@iro.umontreal.ca\" target=3D\"_blank\">monnier@iro.umontreal.ca</a>&=\ngt;</span> wrote:<br><blockquote class=3D\"gmail_quote\" style=3D\"margin:0 0 =\n0 .8ex;border-left:1px #ccc solid;padding-left:1ex\"><span class=3D\"\">&gt;&g=\nt; (it would break some code, but most of it is already fundamentally<br>\n&gt;&gt; broken because it should use `point-min` rather than hardcoding 1,=\n so<br>\n&gt;&gt; it&#39;s not *that* terrible).<br>\n&gt; Amusingly, always using (point-min) also leads to interesting bugs.<br=\n>\n<br>\n</span>Oh yes.=C2=A0 Along with the &quot;hard narrowing&quot; discussion, =\nit would be good to<br>\nhave a way to get the &quot;external point-min&quot;.=C2=A0 Currently the o=\nnly portable<br>\nway (short of hardcoding 1) is via (save-restriction (widen)<br>\n(point-min)) which is rather round-about and inefficient.</blockquote><div>=\n<br></div><div>I have done this on occasions. Are there any cases where the=\n save/widen dance actually gives a result which is not 1?</div><div><br></d=\niv><div>Regards,</div><div>Elias</div></div></div></div>\n\n--94eb2c122da4c9cb15052f82fe2f--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_711.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amS13-0002tP-M3\n\tfor mharc-dev@caliopen.local; Sat, 02 Apr 2016 16:19:29 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57664)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1amS10-0002se-Od\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 16:19:27 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1amS0x-0000wJ-Gc\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 16:19:26 -0400\nReceived: from ironport2-out.teksavvy.com ([206.248.154.181]:4377)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <monnier@iro.umontreal.ca>) id 1amS0x-0000vP-CP\n\tfor dev@caliopen.local; Sat, 02 Apr 2016 16:19:23 -0400\nX-IronPort-Anti-Spam-Filtered: true\nX-IronPort-Anti-Spam-Result: A0AxFgA731xV/0+KpUVcgxCEAoVVu0CHSwQCAoE8OxIBAQEBAQEBgQpBBYNdAQEDAVYjBQsLNBIUGA0kiDcIzyMBAQEHAgEfizqFBQeELQEEsz+BRSOEFCKCeAEBAQ\nX-IPAS-Result: A0AxFgA731xV/0+KpUVcgxCEAoVVu0CHSwQCAoE8OxIBAQEBAQEBgQpBBYNdAQEDAVYjBQsLNBIUGA0kiDcIzyMBAQEHAgEfizqFBQeELQEEsz+BRSOEFCKCeAEBAQ\nX-IronPort-AV: E=Sophos;i=\"5.13,465,1427774400\"; d=\"scan'208\";a=\"204592004\"\nReceived: from 69-165-138-79.dsl.teksavvy.com (HELO fmsmemgm.homelinux.net)\n\t([69.165.138.79])\n\tby ironport2-out.teksavvy.com with ESMTP/TLS/DHE-RSA-AES256-SHA;\n\t02 Apr 2016 16:19:21 -0400\nReceived: by fmsmemgm.homelinux.net (Postfix, from userid 20848)\n\tid 4A921AE44F; Sat,  2 Apr 2016 16:19:21 -0400 (EDT)\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nTo: Elias =?windows-1252?Q?M=E5rtenson?= <lokedhs@gmail.com>\nSubject: Re: About column numbers\nMessage-ID: <jwvvb3zn4ue.fsf-monnier+emacs@gnu.org>\nReferences: <56FB02BB.3090501@alice.it> <83wpokuh3a.fsf@gnu.org>\n\t<56FC0CDB.9090707@alice.it>\n\t<CAP_d_8XDQmTR1M6=+a4yMAjkHFkDEnx4oFQiH57UUNLW2dzM4Q@mail.gmail.com>\n\t<87poubvnhh.fsf@linux-m68k.org>\n\t<CAP_d_8V=4W26O-r-DcOdfpFwwzpZKDtXnd71jOCwvhO0bL38og@mail.gmail.com>\n\t<87lh4zvlp2.fsf@linux-m68k.org>\n\t<CAP_d_8WSBJMZ2aLbnUwiRh1-ieG9mdcTyE-TnFo5AF+hE+_GyA@mail.gmail.com>\n\t<jwvbn5u6cnu.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<56FD53F6.5090805@gmail.com>\n\t<jwv8u0y49bt.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<CADtN0W+eax-pPtn5cxhLOubyjiV_s3Cr5MrfSkVhGm4Chm9t8w@mail.gmail.com>\nDate: Sat, 02 Apr 2016 16:19:21 -0400\nIn-Reply-To: <CADtN0W+eax-pPtn5cxhLOubyjiV_s3Cr5MrfSkVhGm4Chm9t8w@mail.gmail.com>\n\t(\"Elias =?windows-1252?Q?M=E5rtenson=22's?= message of \"Sun, 3 Apr 2016\n\t00:29:10 +0800\")\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 206.248.154.181\nCc: emacs-devel <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sat, 02 Apr 2016 20:19:27 -0000\nContent-Length: 351\nLines: 11\n\n> I have done this on occasions. Are there any cases where the save/widen\n> dance actually gives a result which is not 1?\n\nOn my local Emacs it never returns 1, and on an Emacs with the\nhard-narrowing patch it can also return something else, but AFAIK it\nwill always result in a value of 1 in any actually released version of\nEmacs.\n\n\n        Stefan\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_713.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1al8us-0002ZT-Me\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 01:43:42 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:52519)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <iclcoolster@gmail.com>) id 1al8uq-0002ZK-2l\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 01:43:41 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <iclcoolster@gmail.com>) id 1al8uo-00006z-Tw\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 01:43:39 -0400\nReceived: from mail-ob0-x22a.google.com ([2607:f8b0:4003:c01::22a]:33515)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <iclcoolster@gmail.com>) id 1al8uo-00006t-Nq\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 01:43:38 -0400\nReceived: by mail-ob0-x22a.google.com with SMTP id x3so47460610obt.0\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 22:43:38 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:from:date:message-id:subject:to\n\t:cc; bh=0iNpCiZo5YiZi1sWWViyExgGMp73IalnrPMhmK6/8ck=;\n\tb=clGMnmTX/N6zizfKy/YC3PjkWSKyX/yxqndriVAIpnjL3FC/0RwBfuUJ4BN2+YuglV\n\tSPrbAlUEIcWYsN66wl2Nt6tdYf6bHGdueNnFa21JkCVvnK7a2DLiYKEShsGCzSZrvtoB\n\tbSmVO2vmGVx+QGneyneBjevFuRu7zNv8icnyLhhQGUIAyxv4p1sGoqtOdmIDR/jWxkTL\n\tLIMIPNkkMiV972YSMy0o4pDD/jgLrAjwSX7D2ZW5p3ricwLqw4/JxAWqONp5KGBpl30m\n\tYIgL9GsOhgr6CpV2kobCa14l41NKucO9FGlWQ75nlBZh2IXecXvDO1NKomvSe8jpcnQV\n\tO1Kw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:from:date\n\t:message-id:subject:to:cc;\n\tbh=0iNpCiZo5YiZi1sWWViyExgGMp73IalnrPMhmK6/8ck=;\n\tb=UaDthEQzYHhgmpxIHGGo/CxdMZIwtBVm6QHDHMlE8mPly+aiEmi4XCKxouPkAXmFh6\n\tD18iEMraRd40AskA9p5GgWZGTCmJbhxJqFGnyhwW6pFogsyz+leYlPw5UDIC7xS4J6Mk\n\tEoBuG3+2gG2N2hiB4OfHCAtC4ITH1CI08FjOzOs8vyfpKL2AnZHpWw364a4Q00t+PCzj\n\taor4pGFin39CpMj6iR9wHpWv8OyPlk2skTVbutqmfZrDgQkhQzQhvQ/KOgJYqF3g3xfH\n\tkLEEem3C/BInn+qcpqlzREuuOdiZbKj0tvfCANVWOarwas5agwz3Dy8dszjIsS1iP+Iz\n\tDbCw==\nX-Gm-Message-State: AD7BkJJeT6/MwYa0Iy3L/2wTcXCS2QpcWp29DSSC+vfq3dsleddIHd1yv43P8BlT/HqZlyLUteCSe8ShGI183g==\nX-Received: by 10.202.183.7 with SMTP id h7mr3240695oif.25.1459316617881; Tue,\n\t29 Mar 2016 22:43:37 -0700 (PDT)\nMIME-Version: 1.0\nReceived: by 10.202.108.214 with HTTP; Tue, 29 Mar 2016 22:43:18 -0700 (PDT)\nIn-Reply-To: <56FB02BB.3090501@alice.it>\nReferences: <56FB02BB.3090501@alice.it>\nFrom: psachin <iclcoolster@gmail.com>\nDate: Wed, 30 Mar 2016 11:13:18 +0530\nMessage-ID: <CANN44o-NhMiysEk3y7EakWSNxzdEAqm=1theKRibPzjFd8_t+Q@mail.gmail.com>\nSubject: Re: About column numbers\nTo: Angelo Graziosi <angelo.graziosi@alice.it>\nContent-Type: multipart/alternative; boundary=001a113ce0469fb248052f3da088\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::22a\nCc: Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 05:43:41 -0000\nContent-Length: 2294\nLines: 69\n\n--001a113ce0469fb248052f3da088\nContent-Type: text/plain; charset=UTF-8\n\nDigging goes back to file src/indent.c. Function: current-column\n\nOn Wed, Mar 30, 2016 at 4:03 AM, Angelo Graziosi <angelo.graziosi@alice.it>\nwrote:\n\n> When the option about column numbers is enabled, the column numbers start\n> from \"0\" (zero). In other words, when the cursor in in the top-left corner\n> of the buffer, the mode line displays\n>\n>   (1,0)\n>\n> line number   : 1\n> column number : 0\n>\n> Why columns start from 0 (zero) and not 1 as for lines? Would it be better\n> (1,1)?\n>\n> Is there some settings to change this behavior? If not, I would suggest to\n> change this in Emacs.. or to add some settings to change this..\n>\n>  Angelo\n>\n>\n\n\n-- \nSachin\npsachin.github.io\n\n--001a113ce0469fb248052f3da088\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n<div dir=3D\"ltr\">Digging goes back to file src/indent.c. Function: current-=\ncolumn</div><div class=3D\"gmail_extra\"><br><div class=3D\"gmail_quote\">On We=\nd, Mar 30, 2016 at 4:03 AM, Angelo Graziosi <span dir=3D\"ltr\">&lt;<a href=\n=3D\"mailto:angelo.graziosi@alice.it\" target=3D\"_blank\">angelo.graziosi@alic=\ne.it</a>&gt;</span> wrote:<br><blockquote class=3D\"gmail_quote\" style=3D\"ma=\nrgin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex\">When the optio=\nn about column numbers is enabled, the column numbers start from &quot;0&qu=\not; (zero). In other words, when the cursor in in the top-left corner of th=\ne buffer, the mode line displays<br>\n<br>\n=C2=A0 (1,0)<br>\n<br>\nline number=C2=A0 =C2=A0: 1<br>\ncolumn number : 0<br>\n<br>\nWhy columns start from 0 (zero) and not 1 as for lines? Would it be better =\n(1,1)?<br>\n<br>\nIs there some settings to change this behavior? If not, I would suggest to =\nchange this in Emacs.. or to add some settings to change this..<span class=\n=3D\"HOEnZb\"><font color=3D\"#888888\"><br>\n<br>\n=C2=A0Angelo<br>\n<br>\n</font></span></blockquote></div><br><br clear=3D\"all\"><br>-- <br><div clas=\ns=3D\"gmail_signature\"><div dir=3D\"ltr\"><div><div dir=3D\"ltr\"><div><div dir=\n=3D\"ltr\"><div><font color=3D\"#000000\">Sachin<br><a href=3D\"http://psachin.g=\nithub.io/\" target=3D\"_blank\">psachin.github.io</a><br></font></div></div></=\ndiv></div></div></div></div>\n</div>\n\n--001a113ce0469fb248052f3da088--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_715.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akvol-0001UE-FJ\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 11:44:31 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:53500)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akvoh-0001PI-TF\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 11:44:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akvoe-0001Dc-J2\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 11:44:27 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:50375)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akvoe-0001Cm-Da\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 11:44:24 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>) id 1akvoV-0004ym-LD\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 17:44:17 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: dev@caliopen.local\nSubject: More convenient default rollback for users\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUYCg0RBgkbDxIsHhxn\n\tTjgKAgPRt28VCAzWTtCKAAACR0lEQVQ4jW2UTXPjIAyGsXc6vpq0LtcE8nFNDcm5u1Cf4yRsz7th\n\t2Ct4JuO/v0K222ZSnTx6eF9JRITQkPObEBKDUBrIFHhEjsBSlvNaiASKQFBgjJakalmHJyAeQIN5\n\tIIS2jIiRABBD3hioUZJkPQcnWxyn/ADGYC2PUt0Dau01h3zjvbsBlbX2qFLe+xtgUxDjR8BebgBX\n\tEygXN+B8+ASs/wJOu+Z7xfHPHcgQWLf+XtH6Z5/m0HfFfTMCtghfrX6PQJLZoKBEvKATAoMAFbQT\n\tCsja//VN49IPxTJQBFJ2UpvV2Xs9AShetIEwLs3+AjbPycjIZMWKCtgPZZzz/rL3ThnNa+iK9SUj\n\t5SldOE7daCNrBIFFUm6UNglcnqCArDleInu3XS01nnfaaS23HK1i1eawBJjfp5ZEByAyEq8UmlKu\n\tgeprEIgu54RGFnpaCfnq3KVxTXKqCz5Y9SQKpZ267F1qSaY9Ix2CTDwpB3O4xqjtBHoES+Pgnv41\n\tWnGZtp7kGwAh48tHqAH+OzmrO1RswCoU/F1JrbSC7c44gpBqhAURDdSAGbSIA2BZAhmT6hdsrJLb\n\tedEhSFfS99lMqjetl+f5blXOBgWCeKxf38TP02PYXNsSAaUAWDzKxUKt5CJr7SHyESTWCXgyNQw9\n\trw58AC1NIKRXud3yEFoyggpBn3NRw4XaeBie+6DoewqERxaraxj+CODlUQwYJjyURYSPCbR0imvq\n\tvAfJHED1AfpiALCXSWE/QCBFOl+MNUBRTRI0CkNX/wGyse1Ht0Yu2gAAAABJRU5ErkJggg==\nDate: Tue, 29 Mar 2016 17:44:15 +0200\nMessage-ID: <m3poudb86o.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 15:44:29 -0000\nContent-Length: 1146\nLines: 36\n\nWhen we introduce behavioural changes in Emacs, we often include a way\nto get back to the old behaviour, and we document this in NEWS etc.\n\nBut wouldn't it be nice if we had this in a more convenient,\ncut-and-pastable format?\n\nImagine you're using Emacs 26 for the first time.  Suddenly you notice\nthat when you hit the `hyper z' key, Emacs doesn't florbnoze as it used\nto, but forblizes instead!  You're furious!  So you hit the `C-h R' (for\nrollback) keystroke, and you get a buffer that looks like:\n\n-------------\n;; To roll back changes between Emacs 26 and Emacs 25.1:\n\n;; If you want the `hyper z' key still to florbnoze instead of\n;; forblizing:\n(setq hyper-z-mode 'florbnoze)\n\n... more setqs here...\n\n;; To roll back changes between Emacs 25.1 and Emacs 24.3:\n\n...\n-------------\n\nAnd then the anger dissipates.  No need to read documentation or\nanything.  Just get back the old behaviour you enjoyed.\n\n(And this would also mean that we could introduce new behaviours more\noften, because rollback would be so trivial for the users.)\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_717.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alEwh-0007T2-4e\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 08:09:59 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57171)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1alEwc-0007Sf-1G\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 08:09:57 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <rms@gnu.org>) id 1alEwb-0004Iq-AS\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 08:09:53 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:58855)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <rms@gnu.org>)\n\tid 1alEwT-0004Ge-Nj; Wed, 30 Mar 2016 08:09:45 -0400\nReceived: from rms by fencepost.gnu.org with local (Exim 4.82)\n\t(envelope-from <rms@gnu.org>)\n\tid 1alEwT-0001GY-5o; Wed, 30 Mar 2016 08:09:45 -0400\nContent-Type: text/plain; charset=Utf-8\nFrom: Richard Stallman <rms@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m3poudb86o.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Tue, 29 Mar 2016 17:44:15 +0200)\nSubject: Re: More convenient default rollback for users\nReferences: <m3poudb86o.fsf@gnus.org>\nMessage-Id: <E1alEwT-0001GY-5o@fencepost.gnu.org>\nDate: Wed, 30 Mar 2016 08:09:45 -0400\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: rms@gnu.org\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 12:09:57 -0000\nContent-Length: 770\nLines: 19\n\n[[[ To any NSA and FBI agents reading my email: please consider    ]]]\n[[[ whether defending the US Constitution against all enemies,     ]]]\n[[[ foreign or domestic, requires you to follow Snowden's example. ]]]\n\n  > Imagine you're using Emacs 26 for the first time.  Suddenly you notice\n  > that when you hit the `hyper z' key, Emacs doesn't florbnoze as it used\n  > to, but forblizes instead!  You're furious!  So you hit the `C-h R' (for\n  > rollback) keystroke, and you get a buffer that looks like:\n\nThis sounds nice.  It goes with the Antinews feature we\nhave always had in documentation.\n\n-- \nDr Richard Stallman\nPresident, Free Software Foundation (gnu.org, fsf.org)\nInternet Hall-of-Famer (internethalloffame.org)\nSkype: No way! See stallman.org/skype.html.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_719.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amrIV-0002TV-NZ\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:19:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38133)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrIS-0002St-VP\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:19:09 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrIQ-0001k8-O2\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:19:08 -0400\nReceived: from mail-ob0-x233.google.com ([2607:f8b0:4003:c01::233]:36469)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>)\n\tid 1amrIQ-0001k3-IS; Sun, 03 Apr 2016 19:19:06 -0400\nReceived: by mail-ob0-x233.google.com with SMTP id j9so39490197obd.3;\n\tSun, 03 Apr 2016 16:19:06 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=WySb2VeaEYsxwmBpxry/Hpb2asNnhuB898mIcECLNaE=;\n\tb=jNWFJc7yzOqChmKKyS93IlqGQGsLDD0/w31CI3ahJlwGrQzeTlvOF1IYWl+nLbvr1w\n\tFbVWEpz0yfcGexo120cujdx+4nuQYIFOznyqeUvmDUSL4eZfBIW5a+DkJdgiCoWlUR0S\n\tejMjXwLj3Fn1h7a2uUGUEyLg3aEc5gt8uBeBJ76tKlBOuFFlBtjzz8EtuHLYmMw5XE9H\n\t0q/6ZLyoA+hbXbhM2e4IzUhbUixxJx7NlaYmdP8fG2i2GPAKRFJxXJ6T56zKRJBa/VVY\n\tXT0T/x2HdRL08Y2zt8hU+LJ2ze3gP3vajR2XhJMbb0WWfeUyOr340qBeOO9e+FlM2tSx\n\tihAQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=WySb2VeaEYsxwmBpxry/Hpb2asNnhuB898mIcECLNaE=;\n\tb=by70lTNXCaUwOtOGEHWyNln83PxCuuX13IPUGFftkFW0Y2i0dJVzaqpeKOXgsZWIuG\n\tcTkHkHrkVUN3rr/AYYEzAgKiAo7DmG/adLElg83ZdEh7glBrSCHOI/zhvnoI+swb4CHl\n\t9X3tS3BiBszxWFNhe1oHT5PxxNHVBenadxEj35M3W5GovhBf5eF+Pk/s0q3bTajzXRG5\n\tdTEAcyhY2aJs/LX55dL1dcLZmR21OgqdrzxCzYKb9GJYx2YqwWdBt+8uhtWpUfoua3Nt\n\ti5H4gaJOmPPXERNYqrx2Jd7AV5n/s+aVBLqaUfqnIHN2GTdvn4NPU3tnwU/SKqoEpKyg\n\tGQHA==\nX-Gm-Message-State: AD7BkJIUDH151Ref384T8f2cB+JCoHtiEPdd0x0LG+UwUbker10v+ZXghicBuYFTWghcjQ==\nX-Received: by 10.182.215.166 with SMTP id oj6mr724040obc.81.1459725546160;\n\tSun, 03 Apr 2016 16:19:06 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79]) by smtp.gmail.com with ESMTPSA id\n\tr188sm7584343oia.0.2016.04.03.16.19.04\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 16:19:05 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid B11A013DAE519; Sun,  3 Apr 2016 16:19:03 -0700 (PDT)\nTo: Richard Stallman <rms@gnu.org>\nSubject: Re: More convenient default rollback for users\nIn-Reply-To: <E1alEwT-0001GY-5o@fencepost.gnu.org> (Richard Stallman's message\n\tof \"Wed, 30 Mar 2016 08:09:45 -0400\")\nDate: Sun, 03 Apr 2016 16:18:14 -0700\nMessage-ID: <m2zitae0y1.fsf@newartisans.com>\nReferences: <m3poudb86o.fsf@gnus.org> <E1alEwT-0001GY-5o@fencepost.gnu.org>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Richard Stallman <rms@gnu.org>, Lars Magne Ingebrigtsen\n\t<larsi@gnus.org>, emacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::233\nCc: Lars Magne Ingebrigtsen <larsi@gnus.org>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:19:10 -0000\nContent-Length: 728\nLines: 17\n\n>>>>> Richard Stallman <rms@gnu.org> writes:\n\n>> Imagine you're using Emacs 26 for the first time. Suddenly you notice that\n>> when you hit the `hyper z' key, Emacs doesn't florbnoze as it used to, but\n>> forblizes instead! You're furious! So you hit the `C-h R' (for rollback)\n>> keystroke, and you get a buffer that looks like:\n\n> This sounds nice. It goes with the Antinews feature we have always had in\n> documentation.\n\nAgreed, it would be cool if it worked.  Having it work correctly in all cases\nmight become a maintenance burden, unless it were very easy to automate.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_721.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akw8D-0002uO-7w\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 12:04:37 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59995)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akw85-0002gn-TK\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 12:04:35 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akw80-0006oi-Um\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 12:04:29 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:33514)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1akw80-0006oe-SE; Tue, 29 Mar 2016 12:04:24 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:2118\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1akw80-0002fR-2E; Tue, 29 Mar 2016 12:04:24 -0400\nDate: Tue, 29 Mar 2016 19:04:08 +0300\nMessage-Id: <83io05w9s7.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m3poudb86o.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Tue, 29 Mar 2016 17:44:15 +0200)\nSubject: Re: More convenient default rollback for users\nReferences: <m3poudb86o.fsf@gnus.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 16:04:35 -0000\nContent-Length: 965\nLines: 30\n\n> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n> Date: Tue, 29 Mar 2016 17:44:15 +0200\n> \n> When we introduce behavioural changes in Emacs, we often include a way\n> to get back to the old behaviour, and we document this in NEWS etc.\n> \n> But wouldn't it be nice if we had this in a more convenient,\n> cut-and-pastable format?\n> \n> Imagine you're using Emacs 26 for the first time.  Suddenly you notice\n> that when you hit the `hyper z' key, Emacs doesn't florbnoze as it used\n> to, but forblizes instead!  You're furious!  So you hit the `C-h R' (for\n> rollback) keystroke, and you get a buffer that looks like:\n> \n> -------------\n> ;; To roll back changes between Emacs 26 and Emacs 25.1:\n> \n> ;; If you want the `hyper z' key still to florbnoze instead of\n> ;; forblizing:\n> (setq hyper-z-mode 'florbnoze)\n> \n> ... more setqs here...\n> \n> ;; To roll back changes between Emacs 25.1 and Emacs 24.3:\n> \n> ...\n> -------------\n\nHow do you do that with defcustoms?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_723.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akwYK-00020E-TX\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 12:31:36 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40404)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akwYE-0001vY-47\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 12:31:35 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1akwY9-0007Cb-Qf\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 12:31:30 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:60562)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akwY9-0007C7-K4; Tue, 29 Mar 2016 12:31:25 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1akwXq-000845-Mz; Tue, 29 Mar 2016 18:31:23 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: More convenient default rollback for users\nReferences: <m3poudb86o.fsf@gnus.org> <83io05w9s7.fsf@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUYCg0RBgkbDxIsHhxn\n\tTjgKAgPRt28VCAzWTtCKAAACR0lEQVQ4jW2UTXPjIAyGsXc6vpq0LtcE8nFNDcm5u1Cf4yRsz7th\n\t2Ct4JuO/v0K222ZSnTx6eF9JRITQkPObEBKDUBrIFHhEjsBSlvNaiASKQFBgjJakalmHJyAeQIN5\n\tIIS2jIiRABBD3hioUZJkPQcnWxyn/ADGYC2PUt0Dau01h3zjvbsBlbX2qFLe+xtgUxDjR8BebgBX\n\tEygXN+B8+ASs/wJOu+Z7xfHPHcgQWLf+XtH6Z5/m0HfFfTMCtghfrX6PQJLZoKBEvKATAoMAFbQT\n\tCsja//VN49IPxTJQBFJ2UpvV2Xs9AShetIEwLs3+AjbPycjIZMWKCtgPZZzz/rL3ThnNa+iK9SUj\n\t5SldOE7daCNrBIFFUm6UNglcnqCArDleInu3XS01nnfaaS23HK1i1eawBJjfp5ZEByAyEq8UmlKu\n\tgeprEIgu54RGFnpaCfnq3KVxTXKqCz5Y9SQKpZ267F1qSaY9Ix2CTDwpB3O4xqjtBHoES+Pgnv41\n\tWnGZtp7kGwAh48tHqAH+OzmrO1RswCoU/F1JrbSC7c44gpBqhAURDdSAGbSIA2BZAhmT6hdsrJLb\n\tedEhSFfS99lMqjetl+f5blXOBgWCeKxf38TP02PYXNsSAaUAWDzKxUKt5CJr7SHyESTWCXgyNQw9\n\trw58AC1NIKRXud3yEFoyggpBn3NRw4XaeBie+6DoewqERxaraxj+CODlUQwYJjyURYSPCbR0imvq\n\tvAfJHED1AfpiALCXSWE/QCBFOl+MNUBRTRI0CkNX/wGyse1Ht0Yu2gAAAABJRU5ErkJggg==\nDate: Tue, 29 Mar 2016 18:31:06 +0200\nIn-Reply-To: <83io05w9s7.fsf@gnu.org> (Eli Zaretskii's message of \"Tue, 29 Mar\n\t2016 19:04:08 +0300\")\nMessage-ID: <m3bn5xb60l.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 16:31:35 -0000\nContent-Length: 323\nLines: 13\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n> How do you do that with defcustoms?\n\nI don't know.  Does defcustom offer something like that?\n\nIn any case, having the user go through the custom interface is way too\nmuch work.\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_725.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akwru-0004Ou-7p\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 12:51:50 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47360)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akwrm-0004JG-Vd\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 12:51:48 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1akwrd-0005Q4-IF\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 12:51:42 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:34760)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1akwrd-0005Pz-F7; Tue, 29 Mar 2016 12:51:33 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:2181\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1akwrc-000660-Rf; Tue, 29 Mar 2016 12:51:33 -0400\nDate: Tue, 29 Mar 2016 19:51:17 +0300\nMessage-Id: <83egatw7lm.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nIn-reply-to: <m3bn5xb60l.fsf@gnus.org> (message from Lars Magne Ingebrigtsen\n\ton Tue, 29 Mar 2016 18:31:06 +0200)\nSubject: Re: More convenient default rollback for users\nReferences: <m3poudb86o.fsf@gnus.org> <83io05w9s7.fsf@gnu.org>\n\t<m3bn5xb60l.fsf@gnus.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 16:51:48 -0000\nContent-Length: 662\nLines: 19\n\n> From: Lars Magne Ingebrigtsen <larsi@gnus.org>\n> Cc: dev@caliopen.local\n> Date: Tue, 29 Mar 2016 18:31:06 +0200\n> \n> Eli Zaretskii <eliz@gnu.org> writes:\n> \n> > How do you do that with defcustoms?\n> \n> I don't know.  Does defcustom offer something like that?\n> \n> In any case, having the user go through the custom interface is way too\n> much work.\n\nThe problem is that some options are supposed to be changed via\ndefcustoms, because those invoke a special function for the new value\nto take effect.  In NEWS, we usually say \"customize the option\nso-and-so to such-and-such value\", but how do you do that in\ncopy/paste-able way? invoke the function by hand?\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_727.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alIJl-0003uE-5l\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 11:46:01 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41162)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1alIJj-0003sD-1t\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 11:45:59 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>) id 1alIJf-0002he-5w\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 11:45:58 -0400\nReceived: from hermes.netfonds.no ([80.91.224.195]:56185)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1alIJe-0002h2-R1; Wed, 30 Mar 2016 11:45:55 -0400\nReceived: from cm-84.215.1.64.getinternet.no ([84.215.1.64] helo=stories)\n\tby hermes.netfonds.no with esmtpsa\n\t(TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2)\n\t(envelope-from <larsi@gnus.org>)\n\tid 1alIJV-000128-9h; Wed, 30 Mar 2016 17:45:47 +0200\nFrom: Lars Magne Ingebrigtsen <larsi@gnus.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: More convenient default rollback for users\nReferences: <m3poudb86o.fsf@gnus.org> <83io05w9s7.fsf@gnu.org>\n\t<m3bn5xb60l.fsf@gnus.org> <83egatw7lm.fsf@gnu.org>\nFace: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAAaGxwLCw0BAQMC\n\tBAYBAQEAAQNUVVi5ubsCAgQBAgNxcnQJCQoICAgGBgb1ZQlrAAABJUlEQVQ4jWNQFDpz5tCZQ0pn\n\tdM4AwSElGGBgiAxgwAJYgQirBCe6AFZVpCggaAAChBLWxRWAS44oe1hBPsemgRXN5wEICVRluDgI\n\tEBqAQwKrBhKCB8UgVhQeA0rgoTsITC7F0IEqj8WKmZiiAQysoQyhR1AVQgEwqRkji8LCgQtoQyaG\n\tUWCPcy3AtAMClpogG44USqwmWNWzhjJPQXESwlfGU7CoBwEzHBKsmX5QxgQkA0G0pR+aAJQMtvRF\n\tdydUgfE8oCCWRMQKdi622A/F6qpQoDZLLOLs5UAgguIciKnc28sLqytRJcCAuwMIijEcysAFlqjC\n\tYgdYohJJKVxid3k13A6GAJhvogIYy8urd4swrHz37u29t+/A4M7dt3fv3j0ELpwA2qxIS+pKHq4A\n\tAAAASUVORK5CYII=\nDate: Wed, 30 Mar 2016 17:45:44 +0200\nIn-Reply-To: <83egatw7lm.fsf@gnu.org> (Eli Zaretskii's message of \"Tue, 29 Mar\n\t2016 19:51:17 +0300\")\nMessage-ID: <m3wpokas0n.fsf@gnus.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 80.91.224.195\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 15:46:00 -0000\nContent-Length: 670\nLines: 16\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n> The problem is that some options are supposed to be changed via\n> defcustoms, because those invoke a special function for the new value\n> to take effect.  In NEWS, we usually say \"customize the option\n> so-and-so to such-and-such value\", but how do you do that in\n> copy/paste-able way? invoke the function by hand?\n\nPerhaps there should be a `custom-setq' function that the user could\neval?  Or perhaps this rollback buffer could be in a mode where just\nhitting RET on a form would execute the proper custom incantations...\n\n-- \n(domestic pets only, the antidote for overdose, milk.)\n   bloggy blog: http://lars.ingebrigtsen.no\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_729.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alKBN-0004Pf-KD\n\tfor mharc-dev@caliopen.local; Wed, 30 Mar 2016 13:45:29 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59489)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1alKBL-0004NT-EY\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 13:45:28 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1alKBH-00064I-4O\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 13:45:27 -0400\nReceived: from mout.kundenserver.de ([212.227.17.10]:65129)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <clement.pit@gmail.com>) id 1alKBG-000647-Rg\n\tfor dev@caliopen.local; Wed, 30 Mar 2016 13:45:23 -0400\nReceived: from [192.168.1.82] ([109.24.225.43]) by mrelayeu.kundenserver.de\n\t(mreue102) with ESMTPSA (Nemesis) id 0Lc8iD-1a2g9k1ejd-00jYzk for\n\t<emacs-devel@gnu.org>; Wed, 30 Mar 2016 19:45:21 +0200\nSubject: Re: More convenient default rollback for users\nTo: dev@caliopen.local\nReferences: <m3poudb86o.fsf@gnus.org> <83io05w9s7.fsf@gnu.org>\n\t<m3bn5xb60l.fsf@gnus.org> <83egatw7lm.fsf@gnu.org>\n\t<m3wpokas0n.fsf@gnus.org>\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <56FC10B1.3000808@gmail.com>\nDate: Wed, 30 Mar 2016 19:45:21 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <m3wpokas0n.fsf@gnus.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\";\n\tboundary=\"dodibsv5x47d8LROf4gg5lUBsAk76hFr6\"\nX-Provags-ID: V03:K0:TXKhq5JpSeZuM0lnbREBMwYN79PxyUvvDn07yWXmdlSeEwX3UX0\n\t1TYNUQASswp0xmXBbDIe3JhXp6LfhxUMEmlIKBdCuYCWULX7j0D56mW8zWkuo1yexNkgq0B\n\tRrrxHNwBOsgaT2wcdsmYles5MSuA5u7i+v2ZV0R3fWTkAhl4ttNY3DT0BKdWW4l9z855ux0\n\tAynNI3FgfwALRlpKa61rg==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:GGrkelxxt4E=:f3FC/62b2ctMGXqRPi8xGw\n\thBpq4IaqgusakA9pCWbwaxPhQWsYVjJbb63qZ/XZiaixZ/NlJkKPTuIS4Xz/RJ7zkDpLxCa9e\n\t2QYZXpjBqB6zSBpMED6qx9KMXP9aKQAOYavysDd1VigqDfE2MOhGccFVUfsjmwFkto7gpT9od\n\tQNsx+hekIluAEbnhnysfZAszDnhebwDYuu3z6wBeNhQg6M63Y/kI8iNfR2md1Y/JlPQrGRIrj\n\tHs+vAm2mk3pbqqNwYLRrUwxZktOUEGUqGvyellXWB6ackXcWdd2Y+dSQab4Rb6kGnoxQpJt+Q\n\tjR4e3zmm3ufXjUDZ/eioqi9U4tTTzTmAgx/8jlxOIF++DSA63zpBclz7CNNW7xEBizmBOlQgW\n\tQEb9l0NmRpevTHMwSB79Fn5mZVfaaUdf5HNwIbDumb0bEr2r1FzOFEjDLr6SsCLAYeNybfELe\n\t/GBFejr7qivLhoeoGqep2X5iI4EsZuJS+6Cqlkrt5n84uSvQMazdsJyAXJrF95bq4/o4KhOOR\n\tb8p/dA205AnRKY1rNLAuEq1BwpDPpY9zp4Re+X4uPlVHpOxio7hQ+WXWwJ6oRyqYU4G51rCeA\n\trC4L6v90AHt2BhxAh5yMELM8sUupjFdkLxqwa67oxg0SA32gYHxEnko5Re8+CG8I+YnzFuDH1\n\t/xCosbbr499UGddZRaPR1foaCylVwKMNglbVw6NgAAZqKlh7AWaSLujImwWCQ27XPxVA=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.17.10\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Wed, 30 Mar 2016 17:45:28 -0000\nContent-Length: 2689\nLines: 62\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--dodibsv5x47d8LROf4gg5lUBsAk76hFr6\nContent-Type: multipart/mixed; boundary=\"q30FobonNj0svGPsRtObJsLLD6M9KDRv8\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: dev@caliopen.local\nMessage-ID: <56FC10B1.3000808@gmail.com>\nSubject: Re: More convenient default rollback for users\nReferences: <m3poudb86o.fsf@gnus.org> <83io05w9s7.fsf@gnu.org>\n <m3bn5xb60l.fsf@gnus.org> <83egatw7lm.fsf@gnu.org> <m3wpokas0n.fsf@gnus.org>\nIn-Reply-To: <m3wpokas0n.fsf@gnus.org>\n\n--q30FobonNj0svGPsRtObJsLLD6M9KDRv8\nContent-Type: text/plain; charset=windows-1252\nContent-Transfer-Encoding: quoted-printable\n\nOn 03/30/2016 05:45 PM, Lars Magne Ingebrigtsen wrote:\n> Eli Zaretskii <eliz@gnu.org> writes:\n>> The problem is that some options are supposed to be changed via\n>> defcustoms, because those invoke a special function for the new value\n>> to take effect.  In NEWS, we usually say \"customize the option\n>> so-and-so to such-and-such value\", but how do you do that in\n>> copy/paste-able way? invoke the function by hand?\n>=20\n> Perhaps there should be a `custom-setq' function that the user could\n> eval?  Or perhaps this rollback buffer could be in a mode where just\n> hitting RET on a form would execute the proper custom incantations...\n\nIs that what customize-set-variable does? If not, then it would be very v=\nery very nice; most Emacs configs that I see online are nicely structured=\n and split into a number of files, but they use setq even for variables t=\nhat have custom setters.\n\nCl=E9ment.\n\n\n--q30FobonNj0svGPsRtObJsLLD6M9KDRv8--\n\n--dodibsv5x47d8LROf4gg5lUBsAk76hFr6\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJW/BCxAAoJEPqg+cTm90wj1UkP/je9z8EjoxYZRK6nreGQiJX9\nmHiaVpW6t7O5gPEn+B8KfCYPQkt4Zoi+MUvuiWPvwRDrgeTtUOKY/0hlBIoRoy3j\nWuaCRSG4IR3pRHAR/NQfhCE3KX9buxCRzmWANiMQaweYFVHjXpqrhNTo7WvFuGAm\n3WWcHGwPupjYbbgT5at6nF/ndo4IZFt/hqOOXhefqo/jKBfWwtfZc3xJuHYtilhq\n/uMkvml7qCILIxOsx0XQP9qj/7eAS2+jB1HCXLHMDXmZi8eiVyo4MjETegbst9zi\nUlP0Etxcwfm06XaqrX55EjQDKNBwkiuoOiwyWDRALkW7gbIPyAWD1tAZLfnurpku\nomvOcNbB3pq8u3s2P8okM+FTt/l0pg0JUk+wxnc70NyzO13fopo2q8MBE3/M+N/q\nj0RbKacD0Ua5X9VZx+nTXcl5a6Rea5lPJZuMpyI9Ld9fpzoBAMdpdtjjJ8YIFEGe\nBIQFzqlJOrV1V7MgbKBm5W4vznUrI3DheatrSwuHFsOw2IA08T1jeHRso8cfEQPU\nXSdbyUqlmLGOmcW+VAL7frVRgdM5Kq0RZ3+atyLyn9JsRDt4JvsMt9XysAoVzyOD\n8WjHLXdWrFuQO4NJlrv29RJwzZ/HQT4VYB/RqVYxmoFj7eYpyWynRTYI3NbkbSl8\nWxjJsLTjbLEWYv5BEeYr\n=eT4j\n-----END PGP SIGNATURE-----\n\n--dodibsv5x47d8LROf4gg5lUBsAk76hFr6--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_731.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1albyL-0007Vr-Qc\n\tfor mharc-dev@caliopen.local; Thu, 31 Mar 2016 08:45:13 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:51823)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1albyJ-0007S1-SG\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 08:45:12 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1albyE-0001cz-Ds\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 08:45:11 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:56781)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1albyE-0001bP-8E\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 08:45:06 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1albyC-0005Ff-M8\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 14:45:04 +0200\nReceived: from 45.72.141.202 ([45.72.141.202])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 31 Mar 2016 14:45:04 +0200\nReceived: from monnier by 45.72.141.202 with local (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Thu, 31 Mar 2016 14:45:04 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: More convenient default rollback for users\nDate: Thu, 31 Mar 2016 08:41:10 -0400\nMessage-ID: <jwvh9fm6crq.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <m3poudb86o.fsf@gnus.org> <83io05w9s7.fsf@gnu.org>\n\t<m3bn5xb60l.fsf@gnus.org> <83egatw7lm.fsf@gnu.org>\n\t<m3wpokas0n.fsf@gnus.org> <56FC10B1.3000808@gmail.com>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 45.72.141.202\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:19a5lS5buF8vxMQ/LvOOui0VY5Y=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 31 Mar 2016 12:45:12 -0000\nContent-Length: 76\nLines: 6\n\n> Is that what customize-set-variable does?\n\nI think so.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_733.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alVoP-00056S-PC\n\tfor mharc-dev@caliopen.local; Thu, 31 Mar 2016 02:10:33 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:57703)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <netjune@163.com>) id 1alVoK-00055J-B3\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 02:10:32 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <netjune@163.com>) id 1alVoG-0005RW-4J\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 02:10:28 -0400\nReceived: from m12-11.163.com ([220.181.12.11]:34046)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <netjune@163.com>) id 1alVoE-0005JC-8f\n\tfor dev@caliopen.local; Thu, 31 Mar 2016 02:10:24 -0400\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com;\n\ts=s110527; h=From:Subject:Date:Message-ID:MIME-Version; bh=Fu7lK\n\t1pJLjhj92uNhSLXkmZjvDIvVn4hNWRSoo4uvU0=; b=Pkph/4Hf7D8Un26SHDjaM\n\tYXX+I/dNS4N2qmgtn9rSGFsnZ8MIbZ00J7xKLebFgvKG3fLR2iY6NYd0jQqwKXEh\n\t7VS/YflSOM2Uq3Nl+yqpRmhNTvt/LMiQHlTmK6aHizg98NjqzNU/Nk07e2se34TJ\n\t5MQgG3cPRioo5LGpe0o2Pk=\nReceived: from jun.debian8.linux (unknown [223.95.81.70])\n\tby smtp7 (Coremail) with SMTP id C8CowAC3v28bv_xW17CrAA--.1092S2;\n\tThu, 31 Mar 2016 14:09:51 +0800 (CST)\nFrom: zhanghj <netjune@163.com>\nTo: Lars Magne Ingebrigtsen <larsi@gnus.org>\nSubject: Re: More convenient default rollback for users\nReferences: <m3poudb86o.fsf@gnus.org>\nDate: Thu, 31 Mar 2016 14:09:26 +0800\nIn-Reply-To: <m3poudb86o.fsf@gnus.org> (Lars Magne Ingebrigtsen's message of\n\t\"Tue, 29 Mar 2016 17:44:15 +0200\")\nMessage-ID: <87bn5vkwkp.fsf@jun.debian8.linux>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5.50 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-CM-TRANSID: C8CowAC3v28bv_xW17CrAA--.1092S2\nX-Coremail-Antispam: 1Uf129KBjvJXoW7Zr17GF4fWrWkWFWruFWUXFb_yoW8Xr45pa\n\t4fWFyayF4DtF93KanrXw1Skw1xu395JFWrZrZ8trn5ZFy5Cr1rZrZ2v3sYya47W3yI9r4j\n\tvF48KasxGFZ7Zw7anT9S1TB71UUUUUUqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2\n\t9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07j1uWLUUUUU=\nX-Originating-IP: [223.95.81.70]\nX-CM-SenderInfo: xqhwy3lqh6il2tof0z/xtbBZBE9YVQG3zSNjAABsQ\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 220.181.12.11\nCc: netjune@qq.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Thu, 31 Mar 2016 06:10:32 -0000\nContent-Length: 1497\nLines: 42\n\nLars Magne Ingebrigtsen <larsi@gnus.org> writes:\n\n> When we introduce behavioural changes in Emacs, we often include a way\n> to get back to the old behaviour, and we document this in NEWS etc.\n>\n> But wouldn't it be nice if we had this in a more convenient,\n> cut-and-pastable format?\n>\n> Imagine you're using Emacs 26 for the first time.  Suddenly you notice\n> that when you hit the `hyper z' key, Emacs doesn't florbnoze as it used\n> to, but forblizes instead!  You're furious!  So you hit the `C-h R' (for\n> rollback) keystroke, and you get a buffer that looks like:\n>\n> -------------\n> ;; To roll back changes between Emacs 26 and Emacs 25.1:\n>\n> ;; If you want the `hyper z' key still to florbnoze instead of\n> ;; forblizing:\n> (setq hyper-z-mode 'florbnoze)\n>\n> ... more setqs here...\n>\n> ;; To roll back changes between Emacs 25.1 and Emacs 24.3:\n>\n> ...\n> -------------\n>\n> And then the anger dissipates.  No need to read documentation or\n> anything.  Just get back the old behaviour you enjoyed.\n>\n> (And this would also mean that we could introduce new behaviours more\n> often, because rollback would be so trivial for the users.)\n\nI think a UI page like \"chrome://flags/\" in google chrome is better for\nthis function. In this page, we can show all compitable options for user\nto choose.\n\nFor expert user, we can provide a file like emacs-24-compitable.el (with\nemacs 25 distribution). After loading the file, all behaviors are the\nsame as emacs 24. This can also be used by packages.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_735.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1akx02-0008Ui-2e\n\tfor mharc-dev@caliopen.local; Tue, 29 Mar 2016 13:00:14 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50895)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akx00-0008Si-8C\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 13:00:12 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akwzu-00007w-JH\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 13:00:12 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:51736)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akwzu-000077-Cv\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 13:00:06 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1akwzr-0005CQ-68\n\tfor dev@caliopen.local; Tue, 29 Mar 2016 19:00:03 +0200\nReceived: from 23-91-145-163.cpe.pppoe.ca ([23.91.145.163])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 19:00:03 +0200\nReceived: from monnier by 23-91-145-163.cpe.pppoe.ca with local (Gmexim 0.1\n\t(Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Tue, 29 Mar 2016 19:00:03 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: More convenient default rollback for users\nDate: Tue, 29 Mar 2016 12:56:20 -0400\nMessage-ID: <jwvvb459qbe.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <m3poudb86o.fsf@gnus.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 23-91-145-163.cpe.pppoe.ca\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:ATCNoB6tw6Dq1myCS0VTTP70Skg=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Tue, 29 Mar 2016 17:00:13 -0000\nContent-Length: 265\nLines: 12\n\n> But wouldn't it be nice if we had this in a more convenient,\n> cut-and-pastable format?\n\nSounds good.\n\n> (And this would also mean that we could introduce new behaviours more\n> often, because rollback would be so trivial for the users.)\n\nHaha!\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_737.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alriA-0001Pr-Na\n\tfor mharc-dev@caliopen.local; Fri, 01 Apr 2016 01:33:34 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41680)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kaushal.modi@gmail.com>) id 1alri8-0001Oz-1y\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 01:33:33 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <kaushal.modi@gmail.com>) id 1alri7-0004C0-3l\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 01:33:31 -0400\nReceived: from mail-oi0-x22c.google.com ([2607:f8b0:4003:c06::22c]:33317)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kaushal.modi@gmail.com>) id 1alri6-0004Bw-VS\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 01:33:31 -0400\nReceived: by mail-oi0-x22c.google.com with SMTP id d205so91515895oia.0\n\tfor <dev@caliopen.local>; Thu, 31 Mar 2016 22:33:30 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:from:date:message-id:subject:to;\n\tbh=Et+sel4/tJFIIRajClZWVzeDphTCyxKOHoYtZmw88Kg=;\n\tb=fuw9uadbfYYanZeOKQwpaYvbl6ivZvQe9VIVGS/UacptKX8btk83UprHoCv7lAG1zk\n\tXOcJUvEMVbyNeftYqxuW3PA02AiVf2unn0VX6kjoa1XfMBDdpRRPOzbtXpKp9jpAu0gB\n\ttdQByPCxFbi/bC7ZBR0TgVghTc9HEL9R6QVcVLYE21p2jH7aKCUwOAZv6I6lT5+uZFxR\n\tIWVRZcrqUmnk6Gj9m8zCm2vWUcA30WBTvZS/q9dvMXaRbCl/3+S5/jzczXLGvn6qbhKJ\n\tVlejmVxHQxmS9nXpazRzabSmHZlGMS3OxDu5IeI6zq6OP7pWo4a4ehj6OvVSyATcUSLR\n\tc05Q==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:from:date:message-id:subject:to;\n\tbh=Et+sel4/tJFIIRajClZWVzeDphTCyxKOHoYtZmw88Kg=;\n\tb=k86MGeERz6/wEwPqRkUYvE6KNQmW+zXtRl/cFyByP2uDWQZO2TbdN9mD1sK63rfsdo\n\tcAkw6kyuIjFF6y8qk/jnhHYSHTmwI3wjrPVO2L102J/1ENoHXP+wFKvzLhZEQo3JSR+H\n\tLtbie1GHz89gSXUW3Em75AlPF8wWG8n5+ikSGUWHDLXZ9wRRJhm0JgL3MdUquUU4HH4G\n\tyTrFqWh0QaIdpE6gw2BC2pNlcmI/TvL3ehcQpvfWMCjQesUcVTjhGof5HREPcPrOKlkM\n\t44ALqnv3RD/+XM5QP+K8CAY4EdU9nSKLTVbRIBQLoizfeH0lvKxzWNjDRW3+eRpVU9Jb\n\tOeRg==\nX-Gm-Message-State: AD7BkJIrGIdVfDdHmyfv4CRr2ex0b5HCZEmuMkJVQjJuYwrm1UHFMPzNdIYd4+I3A07HNTqfyW+8vacXq46epQ==\nX-Received: by 10.157.59.119 with SMTP id z110mr1655164otb.164.1459488810136; \n\tThu, 31 Mar 2016 22:33:30 -0700 (PDT)\nMIME-Version: 1.0\nReceived: by 10.202.204.2 with HTTP; Thu, 31 Mar 2016 22:32:50 -0700 (PDT)\nFrom: Kaushal Modi <kaushal.modi@gmail.com>\nDate: Fri, 1 Apr 2016 01:32:50 -0400\nMessage-ID: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\nSubject: Understanding a recent commit in emacs-25 branch [ed19f2]\nTo: Emacs developers <emacs-devel@gnu.org>, Alan Mackenzie <acm@muc.de>\nContent-Type: multipart/alternative; boundary=001a1140707c14fc46052f65b8fa\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::22c\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 01 Apr 2016 05:33:33 -0000\nContent-Length: 1740\nLines: 41\n\n--001a1140707c14fc46052f65b8fa\nContent-Type: text/plain; charset=UTF-8\n\nHi Alan,\n\nI was looking at a recent commit:\nhttp://git.savannah.gnu.org/cgit/emacs.git/commit/?h=emacs-25&id=ed19f207449c43f7f08285ada87ae7a46c61c8d1\n\nIt's a huge commit with lots of changes in many files. But I cannot\ncompletely understand what all changed in that commit. I was looking for\nthat commit's commit log but I couldn't find one.\n\nAren't commits in emacs-25 now limited to just bug fixes?\n\nAlso as I am not an active contributor, I do not understand the full\nimplication of such commits. So please forgive any misunderstanding.\n\n--\nKaushal Modi\n\n--001a1140707c14fc46052f65b8fa\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n<div dir=3D\"ltr\">Hi Alan,<div><br></div><div>I was looking at a recent comm=\nit:=C2=A0<a href=3D\"http://git.savannah.gnu.org/cgit/emacs.git/commit/?h=3D=\nemacs-25&amp;id=3Ded19f207449c43f7f08285ada87ae7a46c61c8d1\">http://git.sava=\nnnah.gnu.org/cgit/emacs.git/commit/?h=3Demacs-25&amp;id=3Ded19f207449c43f7f=\n08285ada87ae7a46c61c8d1</a></div><div><br></div><div>It&#39;s a huge commit=\n with lots of changes in many files. But I cannot completely understand wha=\nt all changed in that commit. I was looking for that commit&#39;s commit lo=\ng but I couldn&#39;t find one.</div><div><br></div><div>Aren&#39;t commits =\nin emacs-25 now limited to just bug fixes?=C2=A0</div><div><br></div><div>A=\nlso as I am not an active contributor, I do not understand the full implica=\ntion of such commits. So please forgive any misunderstanding.<br clear=3D\"a=\nll\"><div><div class=3D\"gmail_signature\"><div dir=3D\"ltr\"><br>--<br>Kaushal =\nModi</div></div></div>\n</div></div>\n\n--001a1140707c14fc46052f65b8fa--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_739.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alrsG-0003k0-De\n\tfor mharc-dev@caliopen.local; Fri, 01 Apr 2016 01:44:00 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:43114)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kaushal.modi@gmail.com>) id 1alrsE-0003jp-IF\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 01:43:59 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <kaushal.modi@gmail.com>) id 1alrsD-0006G4-1h\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 01:43:58 -0400\nReceived: from mail-oi0-x22f.google.com ([2607:f8b0:4003:c06::22f]:34194)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <kaushal.modi@gmail.com>) id 1alrsC-0006Fz-Ss\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 01:43:56 -0400\nReceived: by mail-oi0-x22f.google.com with SMTP id o62so91559856oig.1\n\tfor <dev@caliopen.local>; Thu, 31 Mar 2016 22:43:56 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=mime-version:in-reply-to:references:from:date:message-id:subject:to; \n\tbh=GjA1nKAYZZoji1r2aEnOi5RKHh7N2xnjaqmlZgrkUjA=;\n\tb=lA8YHwvycxzwS878URMAhHUqgL32WTAw3wcruRpeUwDSJiAavJB3o314htPGqPz9aZ\n\taqyujisDw20rsy4RMsEgfiftOwxQeh2G6sNIf6W/ELfIH/wcvAOBn0vF4aiTXy8sBQx7\n\tTSow/w6c0WaI0wpGzTqiTaBJeYUX/8S1/ItKOFCoeBYMJFZQfhgNO7TDyT1bboGd54Hy\n\tEregVKvN+/uXA4h69QfGP3TU7t6DcvhsGSCb+HEfAfj1rRzOw4uUJv4tfeFrfJuolUlb\n\tvnTQLjIRxOMa7E5HdOyJS3G2BH9dCIBzu7p3wLL7z6InmDyy6/RI/k+P2JQV1BmMGdOw\n\tKZTQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:mime-version:in-reply-to:references:from:date\n\t:message-id:subject:to;\n\tbh=GjA1nKAYZZoji1r2aEnOi5RKHh7N2xnjaqmlZgrkUjA=;\n\tb=SmsMWOdFxfgxKt30pcRMo8ldk1v3zSJxxf2/pLgZoSRr30pMoitiIUbYyVMRrlEblP\n\trub5O2afp8ijzf8oR3+/D/7SEobhAm4gLWE5TmB71wshqcC3Onxl7sxGo4aV9cIcIvfN\n\tKOdvXLgPsFW9ElWnNp67tFrBVOVkVIMSx6VdHeuYHAWLIFRc3zzTjUzlgFm5p0mfCE/t\n\tFmmHY10Kurfjeoc/BTiqVw/ecANmrgScCbefF/xgghbydG5HC5MU50umkHh7GrRZLlom\n\tdq7tosjgGu4gn6jsn6Nva1h4RhLJRyAbGwlvaVOIcg9fsuRY6g4hbUfPPijRd+sSIjOs\n\t2G5g==\nX-Gm-Message-State: AD7BkJI+qbjssFTrPyrx346ZYBp2CMg6j1jTatvEmwGr/YYuUs0Nd16U8Fsr/j0JEMMmAHXgLUc2rzl+fDuAIQ==\nX-Received: by 10.202.177.3 with SMTP id a3mr1477319oif.2.1459489436315; Thu,\n\t31 Mar 2016 22:43:56 -0700 (PDT)\nMIME-Version: 1.0\nReceived: by 10.202.204.2 with HTTP; Thu, 31 Mar 2016 22:43:16 -0700 (PDT)\nIn-Reply-To: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\nFrom: Kaushal Modi <kaushal.modi@gmail.com>\nDate: Fri, 1 Apr 2016 01:43:16 -0400\nMessage-ID: <CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nTo: Emacs developers <emacs-devel@gnu.org>, Alan Mackenzie <acm@muc.de>\nContent-Type: multipart/alternative; boundary=001a113cfa1667b829052f65dd9a\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c06::22f\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 01 Apr 2016 05:43:59 -0000\nContent-Length: 2768\nLines: 52\n\n--001a113cfa1667b829052f65dd9a\nContent-Type: text/plain; charset=UTF-8\n\nOK, I think I need some git education.\n\nThere was one section in your commit (\nhttp://git.savannah.gnu.org/cgit/emacs.git/diff/lisp/isearch.el?h=emacs-25&id=ed19f207449c43f7f08285ada87ae7a46c61c8d1\n) which was already committed earlier (\nhttp://git.savannah.gnu.org/cgit/emacs.git/commit/lisp/isearch.el?h=emacs-25&id=91e667692ba1362ca1334b8d58fd16c305ad5e2a\n). As I am familiar with that single commit, I know that they are identical.\n\nAlso I noted that your commit has a repeat of all the backquote/straight\nquote changes in NEWS that happened recently.\n\nBut without this prior knowledge, how can one separate these duplicate\ncommits from the commits that actually are new?\nAlso, what is the reason for such duplicate commits happening?\n\nApologies for this git 101 question here. Please forward me to a resource,\nif available, where I can understand such mega commits.\n\nThanks.\n\n--001a113cfa1667b829052f65dd9a\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\">OK, I think I need some git edu=\ncation.</div><div class=3D\"gmail_extra\"><br></div><div class=3D\"gmail_extra=\n\">There was one section in your commit ( <a href=3D\"http://git.savannah.gnu=\n.org/cgit/emacs.git/diff/lisp/isearch.el?h=3Demacs-25&amp;id=3Ded19f207449c=\n43f7f08285ada87ae7a46c61c8d1\">http://git.savannah.gnu.org/cgit/emacs.git/di=\nff/lisp/isearch.el?h=3Demacs-25&amp;id=3Ded19f207449c43f7f08285ada87ae7a46c=\n61c8d1</a> ) which was already committed earlier (=C2=A0<a href=3D\"http://g=\nit.savannah.gnu.org/cgit/emacs.git/commit/lisp/isearch.el?h=3Demacs-25&amp;=\nid=3D91e667692ba1362ca1334b8d58fd16c305ad5e2a\">http://git.savannah.gnu.org/=\ncgit/emacs.git/commit/lisp/isearch.el?h=3Demacs-25&amp;id=3D91e667692ba1362=\nca1334b8d58fd16c305ad5e2a</a> ). As I am familiar with that single commit, =\nI know that they are identical.</div><div class=3D\"gmail_extra\"><br></div><=\ndiv class=3D\"gmail_extra\">Also I noted that your commit has a repeat of all=\n the backquote/straight quote changes in NEWS that happened recently.</div>=\n<div class=3D\"gmail_extra\"><br></div><div class=3D\"gmail_extra\">But without=\n this prior knowledge, how can one separate these duplicate commits from th=\ne commits that actually are new?</div><div class=3D\"gmail_extra\">Also, what=\n is the reason for such duplicate commits happening?</div><div class=3D\"gma=\nil_extra\"><br></div><div class=3D\"gmail_extra\">Apologies for this git 101 q=\nuestion here. Please forward me to a resource, if available, where I can un=\nderstand such mega commits.</div><div class=3D\"gmail_extra\"><br></div><div =\nclass=3D\"gmail_extra\">Thanks.=C2=A0</div></div>\n\n--001a113cfa1667b829052f65dd9a--\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_741.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1alsnq-0002cA-LA\n\tfor mharc-dev@caliopen.local; Fri, 01 Apr 2016 02:43:30 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55596)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1alsno-0002by-N0\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 02:43:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1alsnk-0000Vb-MJ\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 02:43:28 -0400\nReceived: from zimbra.cs.ucla.edu ([131.179.128.68]:43050)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1alsnk-0000V9-Fq\n\tfor dev@caliopen.local; Fri, 01 Apr 2016 02:43:24 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 11304160D72;\n\tThu, 31 Mar 2016 23:43:19 -0700 (PDT)\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032)\n\twith ESMTP id 11EkSZcje8TU; Thu, 31 Mar 2016 23:43:17 -0700 (PDT)\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id B45471611FF;\n\tThu, 31 Mar 2016 23:43:17 -0700 (PDT)\nX-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026)\n\twith ESMTP id j6u9RGo99sL2; Thu, 31 Mar 2016 23:43:17 -0700 (PDT)\nReceived: from [192.168.1.9] (unknown [100.32.155.148])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 9059A160D72;\n\tThu, 31 Mar 2016 23:43:17 -0700 (PDT)\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nTo: Kaushal Modi <kaushal.modi@gmail.com>,\n\tEmacs developers <emacs-devel@gnu.org>, Alan Mackenzie <acm@muc.de>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\nFrom: Paul Eggert <eggert@cs.ucla.edu>\nOrganization: UCLA Computer Science Department\nMessage-ID: <56FE1882.9030904@cs.ucla.edu>\nDate: Thu, 31 Mar 2016 23:43:14 -0700\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\nContent-Type: text/plain; charset=utf-8; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 131.179.128.68\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Fri, 01 Apr 2016 06:43:29 -0000\nContent-Length: 2243\nLines: 43\n\nKaushal Modi wrote:\n> OK, I think I need some git education.\n>\n> There was one section in your commit (\n> http://git.savannah.gnu.org/cgit/emacs.git/diff/lisp/isearch.el?h=emacs-25&id=ed19f207449c43f7f08285ada87ae7a46c61c8d1\n> ) which was already committed earlier (\n> http://git.savannah.gnu.org/cgit/emacs.git/commit/lisp/isearch.el?h=emacs-25&id=91e667692ba1362ca1334b8d58fd16c305ad5e2a\n> ). As I am familiar with that single commit, I know that they are identical.\n>\n> Also I noted that your commit has a repeat of all the backquote/straight\n> quote changes in NEWS that happened recently.\n>\n> But without this prior knowledge, how can one separate these duplicate\n> commits from the commits that actually are new?\n> Also, what is the reason for such duplicate commits happening?\n\nThere aren't any duplicate commits.\n\nWhen you visit \nhttp://git.savannah.gnu.org/cgit/emacs.git/commit/?h=emacs-25&id=ed19f207449c43f7f08285ada87ae7a46c61c8d1 \nthe website shows you the output of this command:\n\ngit diff \ned19f207449c43f7f08285ada87ae7a46c61c8d1^..ed19f207449c43f7f08285ada87ae7a46c61c8d1\n\nThe commit ed19f207449c43f7f08285ada87ae7a46c61c8d1 is a merge with two parents: \ncommits eabd667a9584fe5bd2422e296d256dceea67debf (which is a single incomplete \nfix for cc-mode) and 7c1802f6ffc2704ba8042c7c1c6faa73dfa210d1 (which is the main \nthread of the emacs-25 branch). The way Alan merged, the former commit is the \nfirst parent, so the abovementioned diff output looks large -- it contains many \nemacs-25 changes all squashed together. It might have been nicer if Alan had \nmerged the other way, so that that the main thread was the first parent, but \nthat's water under the bridge now. (In this particular case I would have avoided \na merge entirely, and would have rebased instead, as that makes such changes \neasier for others to follow later; but that's also water under the bridge.)\n\nUnderstanding what happened is somewhat complicated by the more-recent commit \n22443312188ff097b69d9ff4b87c2b4f7bbbc263, which finished fixing the cc-mode \npatch and undid some of the effect of the incomplete fix.\n\nYou can see all this more easily by running the shell command \"gitk\" in a \ndirectory containing a checked-out copy of the emacs-25 branch.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_743.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amghQ-0002I5-Ur\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 08:00:12 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38984)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1amghN-0002E2-0P\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:00:10 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <acm@muc.de>) id 1amghJ-00013L-Lk\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:00:08 -0400\nReceived: from mail.muc.de ([193.149.48.3]:37428)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1amghJ-00010s-Dv\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:00:05 -0400\nReceived: (qmail 6707 invoked by uid 3782); 3 Apr 2016 12:00:03 -0000\nReceived: from acm.muc.de (p548A58C8.dip0.t-ipconnect.de [84.138.88.200]) by\n\tcolin.muc.de (tmda-ofmipd) with ESMTP;\n\tSun, 03 Apr 2016 14:00:02 +0200\nReceived: (qmail 4715 invoked by uid 1000); 3 Apr 2016 12:03:00 -0000\nDate: Sun, 3 Apr 2016 12:03:00 +0000\nTo: Paul Eggert <eggert@cs.ucla.edu>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nMessage-ID: <20160403120300.GB3537@acm.fritz.box>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu>\nMIME-Version: 1.0\nContent-Type: text/plain; charset=us-ascii\nContent-Disposition: inline\nIn-Reply-To: <56FE1882.9030904@cs.ucla.edu>\nUser-Agent: Mutt/1.5.24 (2015-08-30)\nX-Delivery-Agent: TMDA/1.1.12 (Macallan)\nFrom: Alan Mackenzie <acm@muc.de>\nX-Primary-Address: acm@muc.de\nX-detected-operating-system: by eggs.gnu.org: FreeBSD 9.x\nX-Received-From: 193.149.48.3\nCc: Emacs developers <dev@caliopen.local>,\n\tKaushal Modi <kaushal.modi@gmail.com>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 12:00:11 -0000\nContent-Length: 3080\nLines: 65\n\nHello, Paul.\n\nOn Thu, Mar 31, 2016 at 11:43:14PM -0700, Paul Eggert wrote:\n> Kaushal Modi wrote:\n> > OK, I think I need some git education.\n\n> > There was one section in your commit (\n> > http://git.savannah.gnu.org/cgit/emacs.git/diff/lisp/isearch.el?h=emacs-25&id=ed19f207449c43f7f08285ada87ae7a46c61c8d1\n> > ) which was already committed earlier (\n> > http://git.savannah.gnu.org/cgit/emacs.git/commit/lisp/isearch.el?h=emacs-25&id=91e667692ba1362ca1334b8d58fd16c305ad5e2a\n> > ). As I am familiar with that single commit, I know that they are identical.\n\n> > Also I noted that your commit has a repeat of all the backquote/straight\n> > quote changes in NEWS that happened recently.\n\n> > But without this prior knowledge, how can one separate these duplicate\n> > commits from the commits that actually are new?\n> > Also, what is the reason for such duplicate commits happening?\n\n> There aren't any duplicate commits.\n\n> When you visit \n> http://git.savannah.gnu.org/cgit/emacs.git/commit/?h=emacs-25&id=ed19f207449c43f7f08285ada87ae7a46c61c8d1 \n> the website shows you the output of this command:\n\n> git diff \n> ed19f207449c43f7f08285ada87ae7a46c61c8d1^..ed19f207449c43f7f08285ada87ae7a46c61c8d1\n\n> The commit ed19f207449c43f7f08285ada87ae7a46c61c8d1 is a merge with two parents: \n> commits eabd667a9584fe5bd2422e296d256dceea67debf (which is a single incomplete \n> fix for cc-mode) and 7c1802f6ffc2704ba8042c7c1c6faa73dfa210d1 (which is the main \n> thread of the emacs-25 branch). The way Alan merged, the former commit is the \n> first parent, so the abovementioned diff output looks large -- it contains many \n> emacs-25 changes all squashed together. It might have been nicer if Alan had \n> merged the other way, so that that the main thread was the first parent, but \n> that's water under the bridge now. (In this particular case I would have avoided \n> a merge entirely, and would have rebased instead, as that makes such changes \n> easier for others to follow later; but that's also water under the bridge.)\n\nIt was git that prepared the merge, not me.  What happened was that the\n\"more recent\" commit 22443312... created a conflict with the commits in\na git pull.  git, rather than aborting the pull operation, splurged the\ncontents of all the other commits in the pull into my working directory,\nsaying \"Conflict in ....  You need to merge\".  I simply merged as\ndirected.\n\nIs there a better way out of this situation than just merging as\ndirected?  Can one somehow get out of this partially completed git pull,\nthen redo it with --rebase?\n\n> Understanding what happened is somewhat complicated by the more-recent commit \n> 22443312188ff097b69d9ff4b87c2b4f7bbbc263, which finished fixing the cc-mode \n> patch and undid some of the effect of the incomplete fix.\n\nThis was what created the conflict, I think.\n\n> You can see all this more easily by running the shell command \"gitk\" in a \n> directory containing a checked-out copy of the emacs-25 branch.\n\nI haven't got gitk.  Is it supposed to be part of the main git\ndistribution?\n\n-- \nAlan Mackenzie (Nuremberg, Germany).\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_745.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amgs3-0004cf-G4\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 08:11:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41056)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amgs1-0004c9-1q\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:11:09 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amgrx-0003GX-QB\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:11:08 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:54892)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amgrx-0003GS-Jq\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:11:05 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amgru-0006jf-SW\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:11:02 +0200\nReceived: from p54b46f42.dip0.t-ipconnect.de ([84.180.111.66])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 14:11:02 +0200\nReceived: from Stromeko by p54b46f42.dip0.t-ipconnect.de with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 14:11:02 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Achim Gratz <Stromeko@nexgo.de>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nDate: Sun, 03 Apr 2016 14:10:57 +0200\nOrganization: Linux Private Site\nMessage-ID: <8760vy6gfi.fsf@Rainer.invalid>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu> <20160403120300.GB3537@acm.fritz.box>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: p54b46f42.dip0.t-ipconnect.de\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nCancel-Lock: sha1:V+krq574dDR99VLIdEhYuxJWm8k=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 12:11:10 -0000\nContent-Length: 1754\nLines: 50\n\nAlan Mackenzie writes:\n> It was git that prepared the merge, not me.\n\nBlaming the tools?\n\n> What happened was that the\n> \"more recent\" commit 22443312... created a conflict with the commits in\n> a git pull.  git, rather than aborting the pull operation, splurged the\n> contents of all the other commits in the pull into my working directory,\n> saying \"Conflict in ....  You need to merge\".  I simply merged as\n> directed.\n\nNope, you were supposed to either abort the merge or fix the conflict(s)\nand then stage the resulting changes and \"git commit\" them.\n\nSee \"git help merge\", section \"How to resolve conflicts\".\n\n> Is there a better way out of this situation than just merging as\n> directed?  Can one somehow get out of this partially completed git pull,\n> then redo it with --rebase?\n\ngit merge --abort\n\n>> Understanding what happened is somewhat complicated by the more-recent commit \n>> 22443312188ff097b69d9ff4b87c2b4f7bbbc263, which finished fixing the cc-mode \n>> patch and undid some of the effect of the incomplete fix.\n>\n> This was what created the conflict, I think.\n\nAnd that was the only conflict markers that should have been edited.  It\nseems you really did another manual \"git merge\" with the arguments in\nthe wrong order instead.\n\n>> You can see all this more easily by running the shell command \"gitk\" in a \n>> directory containing a checked-out copy of the emacs-25 branch.\n>\n> I haven't got gitk.  Is it supposed to be part of the main git\n> distribution?\n\nYes, although some distributions have it in a separate package since it\nrequires Tk.\n\n\nRegards,\nAchim.\n-- \n+<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+\n\nSD adaptation for Waldorf microQ V2.22R2:\nhttp://Synth.Stromeko.net/Downloads.html#WaldorfSDada\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_747.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amirE-0004xs-MU\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 10:18:28 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:56430)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amirB-0004xi-Vu\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:18:26 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amir8-0001Gw-P8\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:18:25 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:58897)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amir8-0001Gq-Hq\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:18:22 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amir7-0000LL-6E\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 16:18:21 +0200\nReceived: from 69-165-138-79.dsl.teksavvy.com ([69.165.138.79])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:18:21 +0200\nReceived: from monnier by 69-165-138-79.dsl.teksavvy.com with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:18:21 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Stefan Monnier <monnier@iro.umontreal.ca>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nDate: Sun, 03 Apr 2016 10:18:14 -0400\nMessage-ID: <jwvinzykcdn.fsf-monnier+gmane.emacs.devel@gnu.org>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu> <20160403120300.GB3537@acm.fritz.box>\n\t<8760vy6gfi.fsf@Rainer.invalid>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 69-165-138-79.dsl.teksavvy.com\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux)\nCancel-Lock: sha1:2zJxHdfKynqJqDYhgxRgFG/9VDw=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 14:18:26 -0000\nContent-Length: 493\nLines: 15\n\n>> It was git that prepared the merge, not me.\n> Blaming the tools?\n\nYes.  Git makes it all too easy here to create such annoying merge\ncommits, and if you don't have enough experience with Git you won't\neven notice it.\n\nBut even if you do notice it, it can be pretty challenging to fix it\nafter the fact, or to avoid it before the fact.\n\nIt's not just that Git is a sharp tool here: in this instance, it makes\nit easy to do the wrong thing, and hard to do the right thing.\n\n\n        Stefan\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_749.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ammZ0-0002MT-Ig\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 14:15:54 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42786)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ammYw-0002LS-UO\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:15:51 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ammYr-00008v-Ug\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:15:50 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:38383)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ammYr-00008r-Nt\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:15:45 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1ammYp-0003yJ-JX\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 20:15:43 +0200\nReceived: from p54b46f42.dip0.t-ipconnect.de ([84.180.111.66])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 20:15:43 +0200\nReceived: from Stromeko by p54b46f42.dip0.t-ipconnect.de with local (Gmexim\n\t0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 20:15:43 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: Achim Gratz <Stromeko@nexgo.de>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nDate: Sun, 03 Apr 2016 20:15:37 +0200\nOrganization: Linux Private Site\nMessage-ID: <871t6m5zjq.fsf@Rainer.invalid>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu> <20160403120300.GB3537@acm.fritz.box>\n\t<8760vy6gfi.fsf@Rainer.invalid>\n\t<jwvinzykcdn.fsf-monnier+gmane.emacs.devel@gnu.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: p54b46f42.dip0.t-ipconnect.de\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nCancel-Lock: sha1:yusADGqyRhq4GCuVSWcvRESH04Q=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 18:15:52 -0000\nContent-Length: 2452\nLines: 60\n\nStefan Monnier writes:\n>>> It was git that prepared the merge, not me.\n>> Blaming the tools?\n>\n> Yes.  Git makes it all too easy here to create such annoying merge\n> commits, and if you don't have enough experience with Git you won't\n> even notice it.\n\nGit actually stopped and asked to fix the conflicts I must assume.  I\ndon't know which Git version Alan is using under what localization, but\nhere an intentionally created merge conflict will create this message\n(I've set LC_MESSAGES=POSIX to avoid the localization):\n\nAuto-merging lisp/net/tramp-sh.el\nCONFLICT (content): Merge conflict in lisp/net/tramp-sh.el\nAutomatic merge failed; fix conflicts and then commit the result.\n\nNote that nothing there even suggests you should do a manual merge,\nwhich is what Alan tells us he has done (I don't know if that's what he\ndid).\n\n> But even if you do notice it, it can be pretty challenging to fix it\n> after the fact, or to avoid it before the fact.\n\nEither of\n\ngit merge --abort\ngit --reset merge\n\nwould have put you back on square one to try again.  Also, based on what\nAlan said I believe there was just a single conflict, which would\npresumably have been easy to fix and commit.  And there's alway the\npossibility to branch just before any operation you think might fail\n(and if you forgot to do that there's the reflog).\n\n> It's not just that Git is a sharp tool here: in this instance, it makes\n> it easy to do the wrong thing, and hard to do the right thing.\n\nI don't think so.  For an upstream-tracking branch rebase=true should be\nthe default.  For feature-branches that merge from some other branch,\nyou can still rebase, but should preserve the merges from the other\nbranch (rebase=preserve).  Both settings can be made the default on a\nper-branch basis to save you the bother of typing and remebering.\n\nI'd like to recommend again to anyone using a DVCS to just play around\nwith a few throwaway clones of a reasonably sized repository.  Once you\nthink that you understand what's going on, try to break things:\nintroduce conflicts, delete files from the worktree, commit to the wrong\nbranch, whatever.  Then find out what exactly broke and how to fix it.\nThis doesn't take all that long, about an hour or two should cover a lot\nof ground already.\n\n\nRegards,\nAchim.\n-- \n+<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+\n\nSD adaptation for Waldorf rackAttack V1.04R1:\nhttp://Synth.Stromeko.net/Downloads.html#WaldorfSDada\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_751.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amn1y-0004Cz-0W\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 14:45:50 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:47911)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amn1w-0004Ca-C6\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:45:49 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amn1r-0005vY-P9\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:45:48 -0400\nReceived: from mail-out.m-online.net ([2001:a60:0:28:0:1:25:1]:35976)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amn1r-0005vN-IC\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:45:43 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qdPFf1yRsz3hjYR;\n\tSun,  3 Apr 2016 20:45:42 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qdPFf0lzkzvh25;\n\tSun,  3 Apr 2016 20:45:42 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id hrHmEUMaXdix; Sun,  3 Apr 2016 20:45:41 +0200 (CEST)\nX-Auth-Info: jeuW0YM2kt1EsQhcZLrveI896lGwMoAuEeyW/6Mf4PeDW+yxsKmzsiS8XuquqPVw\nReceived: from igel.home (ppp-88-217-22-197.dynamic.mnet-online.de\n\t[88.217.22.197]) by mail.mnet-online.de (Postfix) with ESMTPA;\n\tSun,  3 Apr 2016 20:45:41 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid E91522C2CF0; Sun,  3 Apr 2016 20:45:40 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Achim Gratz <Stromeko@nexgo.de>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu> <20160403120300.GB3537@acm.fritz.box>\n\t<8760vy6gfi.fsf@Rainer.invalid>\n\t<jwvinzykcdn.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<871t6m5zjq.fsf@Rainer.invalid>\nX-Yow: I'll eat ANYTHING that's BRIGHT BLUE!!\nDate: Sun, 03 Apr 2016 20:45:40 +0200\nIn-Reply-To: <871t6m5zjq.fsf@Rainer.invalid> (Achim Gratz's message of \"Sun,\n\t03 Apr 2016 20:15:37 +0200\")\nMessage-ID: <87wpoey1ij.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 2001:a60:0:28:0:1:25:1\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 18:45:49 -0000\nContent-Length: 449\nLines: 15\n\nAchim Gratz <Stromeko@nexgo.de> writes:\n\n> I don't think so.  For an upstream-tracking branch rebase=true should be\n> the default.\n\nRebase should never be the default, because it is more complicated and\nharder to get right.  It creates new commits that each must be retested.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_753.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amoEh-0001nR-4I\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 16:03:03 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:59762)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1amoEf-0001kL-67\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 16:03:01 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1amoEb-00044G-4x\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 16:03:01 -0400\nReceived: from zimbra.cs.ucla.edu ([131.179.128.68]:35704)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eggert@cs.ucla.edu>) id 1amoEa-000448-VQ\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 16:02:57 -0400\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id 6B0FE161242;\n\tSun,  3 Apr 2016 13:02:55 -0700 (PDT)\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032)\n\twith ESMTP id 2QBwYItxDcL6; Sun,  3 Apr 2016 13:02:54 -0700 (PDT)\nReceived: from localhost (localhost [127.0.0.1])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTP id BAFD2161249;\n\tSun,  3 Apr 2016 13:02:54 -0700 (PDT)\nX-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu\nReceived: from zimbra.cs.ucla.edu ([127.0.0.1])\n\tby localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026)\n\twith ESMTP id CB0Ef4ifPu2t; Sun,  3 Apr 2016 13:02:54 -0700 (PDT)\nReceived: from [192.168.1.9] (unknown [100.32.155.148])\n\tby zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 9D210161242;\n\tSun,  3 Apr 2016 13:02:54 -0700 (PDT)\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nTo: Andreas Schwab <schwab@linux-m68k.org>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu> <20160403120300.GB3537@acm.fritz.box>\n\t<8760vy6gfi.fsf@Rainer.invalid>\n\t<jwvinzykcdn.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<871t6m5zjq.fsf@Rainer.invalid> <87wpoey1ij.fsf@linux-m68k.org>\nFrom: Paul Eggert <eggert@cs.ucla.edu>\nOrganization: UCLA Computer Science Department\nMessage-ID: <570176DF.1060207@cs.ucla.edu>\nDate: Sun, 3 Apr 2016 13:02:39 -0700\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n\tThunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <87wpoey1ij.fsf@linux-m68k.org>\nContent-Type: text/plain; charset=utf-8; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 131.179.128.68\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 20:03:02 -0000\nContent-Length: 631\nLines: 13\n\nAndreas Schwab wrote:\n> Rebase should never be the default, because it is more complicated and\n> harder to get right.  It creates new commits that each must be retested.\n\nRebasing makes sense for Alan's use case. He was working on the fix anyway, so \nretesting was not that big a deal for him. Conversely, he's more of an Emacs \nexpert than a Git expert and merge conflicts are more of an hassle for him. This \nparticular case did not benefit much from the advantages of merging over \nrebasing; quite the contrary.\n\nAs one becomes more of a Git expert, merging becomes more attractive. It does \ntake some getting used to, though.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_755.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amrIV-0002TF-JL\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 19:19:11 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38134)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrIS-0002Su-VO\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:19:09 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrIP-0001jz-Oo\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:19:08 -0400\nReceived: from mail-ob0-x234.google.com ([2607:f8b0:4003:c01::234]:36468)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jwiegley@gmail.com>) id 1amrIP-0001jv-Jb\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 19:19:05 -0400\nReceived: by mail-ob0-x234.google.com with SMTP id j9so39489995obd.3\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:19:05 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:date:message-id:references\n\t:user-agent:mail-followup-to:mime-version;\n\tbh=aowT5yQsdPMmFRbSK6hPwdkGPzClhKk7g3k/hK+yGS4=;\n\tb=K3IOryfuswmnhgjkQe7SoR49TdgXyyI5nQgikDENEP1YNgHt6jawa6WKUXUQlWeVl4\n\t4h0PApEXoUT/fZEOKXoiMCt2W2PskVJx/CsIqwI2nhkU5TiplCTOqtSgNMpn87k+e/fr\n\trnPYEj2QgWT4DinHEB3JH0qF+Hpd011sWiu6bE1IcPtUwOealSe7YPGaUQ5QUxEJCObN\n\tYoxGEeJLPJCgC5X89WquJ3bFQlSaz7W/mX+AzA09t5g5aFNs8Bkzg5L/jUCMPUnrG3sq\n\tGULH6xnYUWxzJWtnE/xsL69OKjU3avI6BY+xkmqWwksLlkJNW/IokWJxztHUB8txjF9H\n\tjVfw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id\n\t:references:user-agent:mail-followup-to:mime-version;\n\tbh=aowT5yQsdPMmFRbSK6hPwdkGPzClhKk7g3k/hK+yGS4=;\n\tb=CiTgdiZju9PMWM6jt06FmJvubn7zTn1ZS7zLh2Eb3/D42T+b3iwtkvZRI+ad/r0Xxn\n\tMaVIqISQtIzDEqO3G5i0PHhbNZ90m6kpQaETPxj4ky2zcrSOk1GmsoJ9XZV7r26fWuCb\n\te73LbI3mHLO0UcPuzOJqZggFS2tTWNwQ00U2VjozNj7Hz7Y9hMoZuxo2iQAi6UlOsvJB\n\t3Dc+lf730e8A9WWtS5LUcMcudhKL07JLbfzhP9mvdsHKZZNiS9aRZVK2lGDSbI/GKtq8\n\tsMCQnmQd7qbUsVm/AY8VkGz8cUK7btKYwgUGP+Y0e35RI7/1MlehgQopHgZVunRk5QhB\n\twHtQ==\nX-Gm-Message-State: AD7BkJL3HLk5te86jemSh3XM98GRBLgOqcRRCSGCpc3rJpiY9bxBlUUV2NkqT+IcSuIOdw==\nX-Received: by 10.60.138.9 with SMTP id qm9mr2629978oeb.80.1459725545031;\n\tSun, 03 Apr 2016 16:19:05 -0700 (PDT)\nReceived: from Vulcan.local (76-234-68-79.lightspeed.frokca.sbcglobal.net.\n\t[76.234.68.79]) by smtp.gmail.com with ESMTPSA id\n\tcq5sm7485869oeb.10.2016.04.03.16.19.03\n\t(version=TLS1 cipher=AES128-SHA bits=128/128);\n\tSun, 03 Apr 2016 16:19:04 -0700 (PDT)\nFrom: John Wiegley <jwiegley@gmail.com>\nX-Google-Original-From: \"John Wiegley\" <johnw@gnu.org>\nReceived: by Vulcan.local (Postfix, from userid 501)\n\tid 2C7B813DAE512; Sun,  3 Apr 2016 16:19:03 -0700 (PDT)\nTo: Paul Eggert <eggert@cs.ucla.edu>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nIn-Reply-To: <570176DF.1060207@cs.ucla.edu> (Paul Eggert's message of \"Sun, 3\n\tApr 2016 13:02:39 -0700\")\nDate: Sun, 03 Apr 2016 16:11:27 -0700\nMessage-ID: <m28u0uffts.fsf@newartisans.com>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu> <20160403120300.GB3537@acm.fritz.box>\n\t<8760vy6gfi.fsf@Rainer.invalid>\n\t<jwvinzykcdn.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<871t6m5zjq.fsf@Rainer.invalid> <87wpoey1ij.fsf@linux-m68k.org>\n\t<570176DF.1060207@cs.ucla.edu>\nUser-Agent: Gnus/5.130014 (Ma Gnus v0.14) Emacs/25.1.50 (darwin)\nMail-Followup-To: Paul Eggert <eggert@cs.ucla.edu>, Andreas Schwab\n\t<schwab@linux-m68k.org>, emacs-devel@gnu.org\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2607:f8b0:4003:c01::234\nCc: Andreas Schwab <schwab@linux-m68k.org>, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 23:19:10 -0000\nContent-Length: 915\nLines: 19\n\n>>>>> Paul Eggert <eggert@cs.ucla.edu> writes:\n\n> Rebasing makes sense for Alan's use case. He was working on the fix anyway,\n> so retesting was not that big a deal for him. Conversely, he's more of an\n> Emacs expert than a Git expert and merge conflicts are more of an hassle for\n> him. This particular case did not benefit much from the advantages of\n> merging over rebasing; quite the contrary.\n\nI agree that when working on a focused change, like a bug fix, that rebasing\nis much more preferable, as it avoids these unnecessary merge commits.\n\nHowever, we can't reasonably make this a requirement, because Git makes it\nvery different to \"get things right\", nor do you always want rebasing if your\nfeature work is longer than an easily retested commit-or-two.\n\n-- \nJohn Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F\nhttp://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_757.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ampN1-0002GI-Gs\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 17:15:43 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:45574)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1ampMz-0002D5-72\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 17:15:41 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1ampMw-0002Yx-0k\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 17:15:41 -0400\nReceived: from mail-out.m-online.net ([2001:a60:0:28:0:1:25:1]:36425)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1ampMv-0002Yl-JK\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 17:15:37 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qdSZc0vbdz3hjcW;\n\tSun,  3 Apr 2016 23:15:35 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qdSZb67t6zvh1s;\n\tSun,  3 Apr 2016 23:15:35 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id 9fmgY6QlIu8K; Sun,  3 Apr 2016 23:15:35 +0200 (CEST)\nX-Auth-Info: FFpyD2bEr8szTmF/cGQcm5QUPYDF6FkcQoibc5V5YrhjChde911ibc5M6byq+cdT\nReceived: from igel.home (ppp-88-217-22-197.dynamic.mnet-online.de\n\t[88.217.22.197]) by mail.mnet-online.de (Postfix) with ESMTPA;\n\tSun,  3 Apr 2016 23:15:35 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid B22422C36E0; Sun,  3 Apr 2016 23:15:34 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Paul Eggert <eggert@cs.ucla.edu>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu> <20160403120300.GB3537@acm.fritz.box>\n\t<8760vy6gfi.fsf@Rainer.invalid>\n\t<jwvinzykcdn.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<871t6m5zjq.fsf@Rainer.invalid> <87wpoey1ij.fsf@linux-m68k.org>\n\t<570176DF.1060207@cs.ucla.edu>\nX-Yow: Hiccuping & trembling into the WASTE DUMPS of New Jersey like some\n\tdrunken CABBAGE PATCH DOLL, coughing in line at FIORUCCI'S!!\nDate: Sun, 03 Apr 2016 23:15:34 +0200\nIn-Reply-To: <570176DF.1060207@cs.ucla.edu> (Paul Eggert's message of \"Sun, 3\n\tApr 2016 13:02:39 -0700\")\nMessage-ID: <87pou6xukp.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 2001:a60:0:28:0:1:25:1\nCc: dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 21:15:42 -0000\nContent-Length: 673\nLines: 21\n\nPaul Eggert <eggert@cs.ucla.edu> writes:\n\n> Rebasing makes sense for Alan's use case. He was working on the fix\n> anyway, so retesting was not that big a deal for him. Conversely, he's\n> more of an Emacs expert than a Git expert and merge conflicts are more of\n> an hassle for him.\n\nIf you get a merge conflict then you also get rebase conflicts.\n\n> As one becomes more of a Git expert, merging becomes more attractive. It\n> does take some getting used to, though.\n\nMerging is much easier to handle.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_759.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amnD7-0006gY-AW\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 14:57:21 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:49472)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amnD4-0006fe-AM\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:57:19 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amnD0-0008SF-1d\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:57:18 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:57725)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1amnCz-0008SB-UW; Sun, 03 Apr 2016 14:57:13 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3883\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1amnCz-0000dc-17; Sun, 03 Apr 2016 14:57:13 -0400\nDate: Sun, 03 Apr 2016 21:56:45 +0300\nMessage-Id: <83h9fiplle.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Andreas Schwab <schwab@linux-m68k.org>\nIn-reply-to: <87wpoey1ij.fsf@linux-m68k.org> (message from Andreas Schwab on\n\tSun, 03 Apr 2016 20:45:40 +0200)\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu> <20160403120300.GB3537@acm.fritz.box>\n\t<8760vy6gfi.fsf@Rainer.invalid>\n\t<jwvinzykcdn.fsf-monnier+gmane.emacs.devel@gnu.org>\n\t<871t6m5zjq.fsf@Rainer.invalid> <87wpoey1ij.fsf@linux-m68k.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: Stromeko@nexgo.de, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 18:57:19 -0000\nContent-Length: 416\nLines: 14\n\n> From: Andreas Schwab <schwab@linux-m68k.org>\n> Date: Sun, 03 Apr 2016 20:45:40 +0200\n> Cc: dev@caliopen.local\n> \n> Achim Gratz <Stromeko@nexgo.de> writes:\n> \n> > I don't think so.  For an upstream-tracking branch rebase=true should be\n> > the default.\n> \n> Rebase should never be the default, because it is more complicated and\n> harder to get right.  It creates new commits that each must be retested.\n\nAgreed.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_761.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amjLS-0004Dm-K9\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 10:49:42 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34520)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amjLP-0004C4-DL\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:49:40 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amjLL-00087V-Aq\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:49:39 -0400\nReceived: from plane.gmane.org ([80.91.229.3]:59891)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amjLL-00087R-3w\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:49:35 -0400\nReceived: from list by plane.gmane.org with local (Exim 4.69)\n\t(envelope-from <ged-emacs-devel@m.gmane.org>) id 1amjLJ-0005To-QG\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 16:49:33 +0200\nReceived: from 151.red-79-153-146.dynamicip.rima-tde.net ([79.153.146.151])\n\tby main.gmane.org with esmtp (Gmexim 0.1 (Debian))\n\tid 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:49:33 +0200\nReceived: from ofv by 151.red-79-153-146.dynamicip.rima-tde.net with local\n\t(Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 16:49:33 +0200\nX-Injected-Via-Gmane: http://gmane.org/\nTo: dev@caliopen.local\nFrom: =?utf-8?Q?=C3=93scar_Fuentes?= <ofv@wanadoo.es>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nDate: Sun, 03 Apr 2016 16:49:25 +0200\nMessage-ID: <87mvpa3fyi.fsf@wanadoo.es>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu> <20160403120300.GB3537@acm.fritz.box>\n\t<8760vy6gfi.fsf@Rainer.invalid>\n\t<jwvinzykcdn.fsf-monnier+gmane.emacs.devel@gnu.org>\nMime-Version: 1.0\nContent-Type: text/plain\nX-Complaints-To: usenet@ger.gmane.org\nX-Gmane-NNTP-Posting-Host: 151.red-79-153-146.dynamicip.rima-tde.net\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nCancel-Lock: sha1:6EVoTSXs7eibxSMEYIVhrldpLwo=\nX-detected-operating-system: by eggs.gnu.org: Genre and OS details not\n\trecognized.\nX-Received-From: 80.91.229.3\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 14:49:40 -0000\nContent-Length: 1076\nLines: 26\n\nStefan Monnier <monnier@iro.umontreal.ca> writes:\n\n>>> It was git that prepared the merge, not me.\n>> Blaming the tools?\n>\n> Yes.  Git makes it all too easy here to create such annoying merge\n> commits, and if you don't have enough experience with Git you won't\n> even notice it.\n>\n> But even if you do notice it, it can be pretty challenging to fix it\n> after the fact, or to avoid it before the fact.\n>\n> It's not just that Git is a sharp tool here: in this instance, it makes\n> it easy to do the wrong thing, and hard to do the right thing.\n\nI know I sound like a broken record, but if the UI of git is not\nadequate for you then try other UI. I'll suggest Magit, which makes all\nthose operations trivial to work with. It requires Emacs to run, which\nis a fat dependency, but it is worth the disk space IMHO.\n\nThe downside is that you might have much less problems with git on the\nfuture, and hence your view about that tool could change a bit and\ncomplain less about it on the ml. You must ponder what causes more\npleasure to you: disparage git or get the job done.\n\n:-)\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_763.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amgzV-00080y-GL\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 08:18:53 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:42730)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amgzR-0007z6-FG\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:18:50 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amgzM-00054F-QX\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:18:49 -0400\nReceived: from mail-lf0-x242.google.com ([2a00:1450:4010:c07::242]:32912)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amgzM-00053r-JA\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:18:44 -0400\nReceived: by mail-lf0-x242.google.com with SMTP id r62so5193995lfd.0\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 05:18:44 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:references:user-agent:date\n\t:message-id:mime-version;\n\tbh=+uc0WpsPBBMqYPTq7qNNJxXewrgcE8oMSAnJS5ZhV3k=;\n\tb=G1T8x+y2048bFJ3vLDU0PKYR8EV0MEl9i0+pnYANi4kkb4ZFOiTRRZYioV+g3qOqYX\n\thPtEfkEBBb5saF1/+PRkXn94jTUxVUL0Te5x9Bob8S6i5zeCLclGAD+dHHefTakSDV+j\n\twa2g4QHvrnP4BI7219BkK2XmmE0eSjC7kaYfAJiiRBCm0usDBxO2ibzI/sglxIoXckcj\n\t7k9+UBE6jHir2MnOP+3lpv029tJgAiojC//vV5tipNXS+UCzgyMNmN3Ho35auNTv8hPs\n\tKjpqwSmBM/ncjLQGTi0RHxUlClYDmIGfMs9d/KoZIhSZa3C1RtK1McgAPPwwkpjhN4H0\n\tZgBg==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:references\n\t:user-agent:date:message-id:mime-version;\n\tbh=+uc0WpsPBBMqYPTq7qNNJxXewrgcE8oMSAnJS5ZhV3k=;\n\tb=A/KBhzdme6vX0QS2itEvfwri2yN0nLV/nOJdyAcK0C2ULH4cS4ojzGUVE1Ga1JGc2X\n\tBlMxvYcQ6tj+gRmI55tiV88RPlydgeuJHSGzOVmts1J7nk5PWt5zxATe1VOwf6wyHqC8\n\ti8Lk7iy0urhcwce6QyFTwfNJT590/Py7qTCkWk2dZSlQ19unx+xoW63Uv923WSt+QGXW\n\tm9QzYAQM0UP3+Aka85+0OX8uoyjOSwbVPqIeZ/teL1y1gxgJ4VMmb9QH5o0zqBnQGffq\n\tR03CqtQGy6b5xnk031iLJYhcCDqhEVUupUzH1XDc5xguO+qB/xi/nQgKf2mMlByJ7Fni\n\tPYmA==\nX-Gm-Message-State: AD7BkJJIPmPaolDfETFAJ7fTO/sGL4t7oh29Y3lSilL3pQ7h75fxm88mqEvk800MHkvmZw==\nX-Received: by 10.194.115.196 with SMTP id jq4mr13390532wjb.101.1459685923694; \n\tSun, 03 Apr 2016 05:18:43 -0700 (PDT)\nReceived: from localhost (xdsl-87-79-226-7.netcologne.de. [87.79.226.7])\n\tby smtp.gmail.com with ESMTPSA id\n\ti5sm23483791wja.23.2016.04.03.05.18.41\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 03 Apr 2016 05:18:42 -0700 (PDT)\nFrom: Ingo Lohmar <i.lohmar@gmail.com>\nTo: Alan Mackenzie <acm@muc.de>, Paul Eggert <eggert@cs.ucla.edu>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nIn-Reply-To: <20160403120300.GB3537@acm.fritz.box>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<56FE1882.9030904@cs.ucla.edu>\n\t<20160403120300.GB3537@acm.fritz.box>\nUser-Agent: Notmuch/0.20.2+113~g6332e6e (http://notmuchmail.org)\n\tEmacs/25.0.90.1 (x86_64-pc-linux-gnu)\nDate: Sun, 03 Apr 2016 14:18:41 +0200\nMessage-ID: <87egamevha.fsf@acer.localhost.com>\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c07::242\nCc: Kaushal Modi <kaushal.modi@gmail.com>,\n\tEmacs developers <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 12:18:51 -0000\nContent-Length: 1113\nLines: 27\n\nHi Alan,\n\nOn Sun, Apr 03 2016 12:03 (+0000), Alan Mackenzie wrote:\n>\n> It was git that prepared the merge, not me.  What happened was that the\n> \"more recent\" commit 22443312... created a conflict with the commits in\n> a git pull.  git, rather than aborting the pull operation, splurged the\n> contents of all the other commits in the pull into my working directory,\n> saying \"Conflict in ....  You need to merge\".  I simply merged as\n> directed.\n>\n> Is there a better way out of this situation than just merging as\n> directed?  Can one somehow get out of this partially completed git pull,\n> then redo it with --rebase?\n\nI am not Paul, but feel competent to answer this.  You can 'git merge\n--abort' to return to the previous state.  Then you can 'git rebase\n<where-to>', and <where-to> might be sth like 'origin/master'.\n\nBut you can avoid all this trouble by not doing 'git pull' in the first\nplace.  Do 'git fetch --all', and then have a look at 'git log --graph'\nas Dmitry suggested (gitk may or may not come with the git package you\nuse, but the information is essentially the same).\n\nHope this helps,\nIngo\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_765.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amfz3-00005C-Jp\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 07:14:21 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:33021)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1amfz0-0008UU-On\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:14:19 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <acm@muc.de>) id 1amfyv-0000rf-Oy\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:14:18 -0400\nReceived: from mail.muc.de ([193.149.48.3]:41868)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1amfyv-0000rE-Ir\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:14:13 -0400\nReceived: (qmail 96748 invoked by uid 3782); 3 Apr 2016 11:14:11 -0000\nReceived: from acm.muc.de (p548A58C8.dip0.t-ipconnect.de [84.138.88.200]) by\n\tcolin.muc.de (tmda-ofmipd) with ESMTP;\n\tSun, 03 Apr 2016 13:14:10 +0200\nReceived: (qmail 4516 invoked by uid 1000); 3 Apr 2016 11:17:08 -0000\nDate: Sun, 3 Apr 2016 11:17:08 +0000\nTo: Kaushal Modi <kaushal.modi@gmail.com>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nMessage-ID: <20160403111708.GA3537@acm.fritz.box>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\nMIME-Version: 1.0\nContent-Type: text/plain; charset=us-ascii\nContent-Disposition: inline\nIn-Reply-To: <CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\nUser-Agent: Mutt/1.5.24 (2015-08-30)\nX-Delivery-Agent: TMDA/1.1.12 (Macallan)\nFrom: Alan Mackenzie <acm@muc.de>\nX-Primary-Address: acm@muc.de\nX-detected-operating-system: by eggs.gnu.org: FreeBSD 9.x\nX-Received-From: 193.149.48.3\nCc: Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 11:14:19 -0000\nContent-Length: 1864\nLines: 48\n\nHello, Kaushal.\n\nThat massive commit happened because of git.  I attempted a 'git pull'\nprior to making a (moderately small) commit.  There was a one-letter\ntypo in one of my existing files (which I think had been committed).\nBecause of that, git failed to merge in all the stuff which it had just\nfetched from savannah, instead prompting me to do a manual merge, which\nI then did.\n\nThis is a problem with SCM systems.  There is no \"undo\" for a lot of\noperations.  If I had been aware of what was happening, I might have\nbeen able to adjust my copy of the repository somehow so as to avoid\nthat large merge.\n\nIt would also be nice if such \"pseudo merges\" could be handled locally,\nrather than being pushed back to the remote repository, causing\nconfusion.\n\nOn Fri, Apr 01, 2016 at 01:43:16AM -0400, Kaushal Modi wrote:\n> OK, I think I need some git education.\n\nMe, too!\n\n> There was one section in your commit (\n> http://git.savannah.gnu.org/cgit/emacs.git/diff/lisp/isearch.el?h=emacs-25&id=ed19f207449c43f7f08285ada87ae7a46c61c8d1\n> ) which was already committed earlier (\n> http://git.savannah.gnu.org/cgit/emacs.git/commit/lisp/isearch.el?h=emacs-25&id=91e667692ba1362ca1334b8d58fd16c305ad5e2a\n> ). As I am familiar with that single commit, I know that they are identical.\n\n> Also I noted that your commit has a repeat of all the backquote/straight\n> quote changes in NEWS that happened recently.\n\n> But without this prior knowledge, how can one separate these duplicate\n> commits from the commits that actually are new?\n> Also, what is the reason for such duplicate commits happening?\n\nSee above.\n\n> Apologies for this git 101 question here. Please forward me to a resource,\n> if available, where I can understand such mega commits.\n\nNo apologies needed.  git is a complicated, difficult to learn system.\n\n> Thanks.\n\n-- \nAlan Mackenzie (Nuremberg, Germany).\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_767.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amgOC-0006gc-07\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 07:40:20 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36388)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amgO9-0006fE-GS\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:40:18 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amgO5-0005mb-GX\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:40:17 -0400\nReceived: from mail-lb0-x230.google.com ([2a00:1450:4010:c04::230]:34106)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amgO5-0005mX-8k\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:40:13 -0400\nReceived: by mail-lb0-x230.google.com with SMTP id vo2so121647898lbb.1\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 04:40:12 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:references:user-agent:date\n\t:message-id:mime-version;\n\tbh=CQQrj347Y95YgLMn3d42YRipzBojzZqmHktkd+/Z0/A=;\n\tb=Fy/B6DpRET4FZgreVdio7bL80fQUN/l+bwitL42409Uj6bT2DQ6Nc7Pss9do3MvJHS\n\t459cadccUDWjGuvzP8AEzz9w6Gk004VnSpnEam8o9hga4wTyou5OhEBAkchouhcIvyrt\n\t6jYt5zWtyVfJj6mbsE6vvhMxYC7RwkoVNI5ktFROA/r1VJcp/ek++s13L6vr8HXBh9/d\n\tWWhGuHsJHmO8lIOlb08dXxXSYSD4iSUf2wjDWL09odxF4s4390ulbDR3jjZwLmcRnrBD\n\tH2d/7tooWTZKvN5b+iEYN3Binjnsn6SjCBYnScvXqceAsr4sTsND5zgg1P3Jyi+WuVKR\n\t1RDA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:references\n\t:user-agent:date:message-id:mime-version;\n\tbh=CQQrj347Y95YgLMn3d42YRipzBojzZqmHktkd+/Z0/A=;\n\tb=DmdX6dRw+TVA6/ysopNFKZFVJ1PEj2mp548DGZyftX0ISbG/CoERiGSIeUObdYnp8W\n\tGKlY1Ph4lr00WIGgkYPbfrkYYk7MQvSI65wEQML1lbz702TBqhRJhnTnN3lXfg4mrkyb\n\tohZBzX/j34FIPi217TJL6NvuKCQL8Vvw2Uhm/faj/sDMkrpXUJn4TtaevGPyn6NGZRYL\n\tM7zP39WpB8CN69ltKbvukz0LDL+23OlWFtgxA/CbQkERuH3ZMVGnrud6VjmSBdBdtCyL\n\t/FZAxiUL6HCEwTnC6CppoDe27j1u8zWG0/B0BB/PlPQ0KpaSaVDOvhm+ppG3jInnCWwg\n\tc24Q==\nX-Gm-Message-State: AD7BkJJ6r6WOk6BiKTVjU0AtZskWHwC/DkBpMMuLFMVUoP3h3imS5myF7uQz28wPIwTVrA==\nX-Received: by 10.28.53.134 with SMTP id c128mr7068167wma.10.1459683612006;\n\tSun, 03 Apr 2016 04:40:12 -0700 (PDT)\nReceived: from localhost (xdsl-87-79-226-7.netcologne.de. [87.79.226.7])\n\tby smtp.gmail.com with ESMTPSA id\n\tx203sm8235325wmg.23.2016.04.03.04.40.10\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 03 Apr 2016 04:40:11 -0700 (PDT)\nFrom: Ingo Lohmar <i.lohmar@gmail.com>\nTo: Alan Mackenzie <acm@muc.de>, Kaushal Modi <kaushal.modi@gmail.com>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nIn-Reply-To: <20160403111708.GA3537@acm.fritz.box>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\nUser-Agent: Notmuch/0.20.2+113~g6332e6e (http://notmuchmail.org)\n\tEmacs/25.0.90.1 (x86_64-pc-linux-gnu)\nDate: Sun, 03 Apr 2016 13:40:10 +0200\nMessage-ID: <87lh4uex9h.fsf@acer.localhost.com>\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c04::230\nCc: Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 11:40:18 -0000\nContent-Length: 1255\nLines: 28\n\nHi Alan,\n\nOn Sun, Apr 03 2016 11:17 (+0000), Alan Mackenzie wrote:\n\n> That massive commit happened because of git.  I attempted a 'git pull'\n> prior to making a (moderately small) commit.  There was a one-letter\n> typo in one of my existing files (which I think had been committed).\n> Because of that, git failed to merge in all the stuff which it had just\n> fetched from savannah, instead prompting me to do a manual merge, which\n> I then did.\n\nI think 'git pull' has been discussed on this list before.  Others feel\ndifferently about this issue, but I strongly advise anyone against using\n'git pull', and instead suggest you do 'git fetch' (maybe --all).\n\n*After* seeing what has happened to the remote branches, you can decide\nwhether a merge or a rebase is in order.  Or you spot an unwanted\ndiscrepancy, and can fix it, instead of git telling you to manually\nmerge (although admittedly I do not quite follow that part).\n\n> It would also be nice if such \"pseudo merges\" could be handled locally,\n> rather than being pushed back to the remote repository, causing\n> confusion.\n\nPlease note that *all* commits and merges happen locally.  The user can\nonly push changes back to the remote by an explicit action, with all\nintended and unintended effects.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_769.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amgsy-00057f-QZ\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 08:12:08 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:41536)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1amgsw-00056L-J4\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:12:07 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <acm@muc.de>) id 1amgst-0003gV-Cf\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:12:06 -0400\nReceived: from mail.muc.de ([193.149.48.3]:36897)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <acm@muc.de>)\n\tid 1amgst-0003gI-3L\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:12:03 -0400\nReceived: (qmail 9313 invoked by uid 3782); 3 Apr 2016 12:12:02 -0000\nReceived: from acm.muc.de (p548A58C8.dip0.t-ipconnect.de [84.138.88.200]) by\n\tcolin.muc.de (tmda-ofmipd) with ESMTP;\n\tSun, 03 Apr 2016 14:12:00 +0200\nReceived: (qmail 4769 invoked by uid 1000); 3 Apr 2016 12:14:58 -0000\nDate: Sun, 3 Apr 2016 12:14:58 +0000\nTo: Ingo Lohmar <i.lohmar@gmail.com>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nMessage-ID: <20160403121458.GC3537@acm.fritz.box>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\nMIME-Version: 1.0\nContent-Type: text/plain; charset=us-ascii\nContent-Disposition: inline\nIn-Reply-To: <87lh4uex9h.fsf@acer.localhost.com>\nUser-Agent: Mutt/1.5.24 (2015-08-30)\nX-Delivery-Agent: TMDA/1.1.12 (Macallan)\nFrom: Alan Mackenzie <acm@muc.de>\nX-Primary-Address: acm@muc.de\nX-detected-operating-system: by eggs.gnu.org: FreeBSD 9.x\nX-Received-From: 193.149.48.3\nCc: Emacs developers <dev@caliopen.local>,\n\tKaushal Modi <kaushal.modi@gmail.com>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 12:12:07 -0000\nContent-Length: 2395\nLines: 53\n\nHello, Ingo.\n\nOn Sun, Apr 03, 2016 at 01:40:10PM +0200, Ingo Lohmar wrote:\n> Hi Alan,\n\n> On Sun, Apr 03 2016 11:17 (+0000), Alan Mackenzie wrote:\n\n> > That massive commit happened because of git.  I attempted a 'git pull'\n> > prior to making a (moderately small) commit.  There was a one-letter\n> > typo in one of my existing files (which I think had been committed).\n> > Because of that, git failed to merge in all the stuff which it had just\n> > fetched from savannah, instead prompting me to do a manual merge, which\n> > I then did.\n\n> I think 'git pull' has been discussed on this list before.  Others feel\n> differently about this issue, but I strongly advise anyone against using\n> 'git pull', and instead suggest you do 'git fetch' (maybe --all).\n\n> *After* seeing what has happened to the remote branches, you can decide\n> whether a merge or a rebase is in order.  Or you spot an unwanted\n> discrepancy, and can fix it, instead of git telling you to manually\n> merge (although admittedly I do not quite follow that part).\n\nIs there a way of asking \"if I attempt git merge, will there be any\nconflicts?\"?  It would be nice to find this out before one's working\ndirectory gets lots of uncommitted changes.\n\nIs there a way of recovering after doing git pull, when git has already\nwritten all the pulled changes to the working directory?  Is there some\nway of saying git undo-partial-pull, leaving the working directory as it\nwas before the pull, and cancelling the merge which git has started?\n\n> > It would also be nice if such \"pseudo merges\" could be handled locally,\n> > rather than being pushed back to the remote repository, causing\n> > confusion.\n\n> Please note that *all* commits and merges happen locally.  The user can\n> only push changes back to the remote by an explicit action, with all\n> intended and unintended effects.\n\nSorry, I wasn't very clear.  What I meant was, is there a way of\nfinishing the merge locally, then pushing real changes without the\nconfusing \"pseudo-merge\" escaping upstream with them?\n\nWhen I did git pull, there were, let's say, 20 commits.  19 of these\ncould have been moved directly into my local repository; only one had a\nconflict.  It would be nice to be able to fix the local repo, so that\nthe \"pseudo-merge\" of these 19 blameless commits remains a purely local\naffair, and doesn't get pushed upstream.\n\n-- \nAlan Mackenzie (Nuremberg, Germany).\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_771.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amhAs-0003qn-Dp\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 08:30:38 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:44477)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amhAp-0003pM-EP\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:30:36 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amhAk-00071F-ET\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:30:35 -0400\nReceived: from mail-lb0-x241.google.com ([2a00:1450:4010:c04::241]:32836)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amhAj-00070s-TV\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 08:30:30 -0400\nReceived: by mail-lb0-x241.google.com with SMTP id bc4so16784474lbc.0\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 05:30:29 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:references:user-agent:date\n\t:message-id:mime-version;\n\tbh=TwxD7C9Gd0vEF1NZQUSILSsffMBlouSQ2ewDsFFg1fo=;\n\tb=vSKOV7GfqzEnlJJcq6omqzq5td6tAbZWNMlqT8dsx6ubHyfywCDW9zZ79GgeHKreLQ\n\tRCv7zMIG1sxQdzgNbDgh1f6PpeRDfPCoO615gO+GPcpSgeelXLUkob+kJNMCvSudzNI9\n\twuVyGD13xEhg89gSq6alVM6nCp7qwRwgZTT7e6/zIFLdqgfe7S81p/kmUUqAPbPgfyZ0\n\twnhCsz0P0RveRQ22jSFd3Hqg5ro08tusj6R9mykqA1JUm3+4g/Wch8E1bQXieSjVTb4V\n\tlPBsNnxSC8GVnhjKFloXBZBtlMHPWMH7MdwubVZj6f8YeAd16ovSspz67Ro1WE9MDkmR\n\tEBAw==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:references\n\t:user-agent:date:message-id:mime-version;\n\tbh=TwxD7C9Gd0vEF1NZQUSILSsffMBlouSQ2ewDsFFg1fo=;\n\tb=FyEQIvqXqHuecSNvvd8D5MWNVtUBbSHTvHpxFnJ2xKrOtFQddkVsIXkeC/31ij+NOP\n\txcZ7wNdKI07Wt+uZRKeog1Lr7wbMp5DvetPg9+ND2dGVzEVRY9c2FKkQ7oiLQvOKGSzQ\n\t5P1DNf7/vjZikIAh6SM7B98oOqzkmAnyjZi6aiWyNxr+XBiBGQLygpYTVPsF+Tu39cNv\n\tzw6xet/YvpImBzkeU0Lu8VASWSWi2SY+rq/uvO5hUE28KXAM+y0jFpEsC8DkWV4p4QoP\n\tREgCQ/wXRgT8FjF4Yqn1nBGPeU9sB1YtKeg7Ti4LPbJHQ6/LC5GvWi75BeJ/7paQETUP\n\t5rXg==\nX-Gm-Message-State: AD7BkJILdqL22qODqvoIp7/Xhdkb14ogjeql2EerE5UF0YFvMZjjriaOprspIh9QA/FLxg==\nX-Received: by 10.28.36.85 with SMTP id k82mr6988328wmk.65.1459686629151;\n\tSun, 03 Apr 2016 05:30:29 -0700 (PDT)\nReceived: from localhost (xdsl-87-79-226-7.netcologne.de. [87.79.226.7])\n\tby smtp.gmail.com with ESMTPSA id u3sm8422970wmg.15.2016.04.03.05.30.28\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 03 Apr 2016 05:30:28 -0700 (PDT)\nFrom: Ingo Lohmar <i.lohmar@gmail.com>\nTo: Alan Mackenzie <acm@muc.de>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nIn-Reply-To: <20160403121458.GC3537@acm.fritz.box>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\nUser-Agent: Notmuch/0.20.2+113~g6332e6e (http://notmuchmail.org)\n\tEmacs/25.0.90.1 (x86_64-pc-linux-gnu)\nDate: Sun, 03 Apr 2016 14:30:27 +0200\nMessage-ID: <87bn5qeuxo.fsf@acer.localhost.com>\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c04::241\nCc: Emacs developers <dev@caliopen.local>,\n\tKaushal Modi <kaushal.modi@gmail.com>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 12:30:36 -0000\nContent-Length: 2042\nLines: 47\n\nHi Alan,\n\nOn Sun, Apr 03 2016 12:14 (+0000), Alan Mackenzie wrote:\n\n> Is there a way of asking \"if I attempt git merge, will there be any\n> conflicts?\"?  It would be nice to find this out before one's working\n> directory gets lots of uncommitted changes.\n\nJust do the merge, and if you get conflicts that you do not want to\nresolve, 'git merge --abort'.\n\nSingle caveat: Do NOT start a merge when you have uncommited changes.\nIf you want, do 'git stash' first to recover them later.\n\n>\n> Is there a way of recovering after doing git pull, when git has already\n> written all the pulled changes to the working directory?  Is there some\n> way of saying git undo-partial-pull, leaving the working directory as it\n> was before the pull, and cancelling the merge which git has started?\n\nSee above.\n\n> Sorry, I wasn't very clear.  What I meant was, is there a way of\n> finishing the merge locally, then pushing real changes without the\n> confusing \"pseudo-merge\" escaping upstream with them?\n>\n> When I did git pull, there were, let's say, 20 commits.  19 of these\n> could have been moved directly into my local repository; only one had a\n> conflict.  It would be nice to be able to fix the local repo, so that\n> the \"pseudo-merge\" of these 19 blameless commits remains a purely local\n> affair, and doesn't get pushed upstream.\n\nIt seems you are confused about some concepts.  The 'fetch' part of\n'pull' has already dragged *all* commits into your local repository.\nNow the question is about how to merge the branches (say 'master' and\n'origin/master').  Here I can no longer follow your explanation.  I have\na feeling you actually want to avoid the merge commit as far as\npossible.\n\nIn this case, you have to learn about rebase, as in 'git rebase\norigin/master'.  This replays your commits (which should be only local)\non top of origin/master.  You will have to fix any conflict in your\ncommits, and you will end up with a linear history with your commits on\ntop of those already on origin/master.\n\nAbort a rebase-gone-bad by 'git rebase --abort'.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_773.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amjXS-0007T5-Jo\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 11:02:06 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:37321)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amjXQ-0007Sk-5J\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:02:04 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amjXP-0002c1-FC\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:02:04 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:54778)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1amjXL-0002bp-P8; Sun, 03 Apr 2016 11:01:59 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3083\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1amjXL-0004uf-2V; Sun, 03 Apr 2016 11:01:59 -0400\nDate: Sun, 03 Apr 2016 18:01:31 +0300\nMessage-Id: <838u0urb1w.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Ingo Lohmar <i.lohmar@gmail.com>\nIn-reply-to: <87bn5qeuxo.fsf@acer.localhost.com> (message from Ingo Lohmar on\n\tSun, 03 Apr 2016 14:30:27 +0200)\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: acm@muc.de, kaushal.modi@gmail.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 15:02:05 -0000\nContent-Length: 658\nLines: 18\n\n> From: Ingo Lohmar <i.lohmar@gmail.com>\n> Date: Sun, 03 Apr 2016 14:30:27 +0200\n> Cc: Emacs developers <dev@caliopen.local>,\n> \tKaushal Modi <kaushal.modi@gmail.com>\n> \n> Single caveat: Do NOT start a merge when you have uncommited changes.\n> If you want, do 'git stash' first to recover them later.\n\nI disagree with this caveat.  There's no reason to frighten people\nlike that, as doing such merges will work most of the time.\n\n> In this case, you have to learn about rebase, as in 'git rebase\n> origin/master'.\n\n\"git rebase\" is a bad idea when merging a long-lived feature branch,\nso please don't advise this to users who are evidently not Git\nexperts.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_775.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amjrw-0005AI-3K\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 11:23:16 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40712)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amjrr-00056E-8g\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:23:12 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amjrq-0006v6-AB\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:23:11 -0400\nReceived: from mail-lb0-x241.google.com ([2a00:1450:4010:c04::241]:32956)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>)\n\tid 1amjrm-0006ug-Ab; Sun, 03 Apr 2016 11:23:06 -0400\nReceived: by mail-lb0-x241.google.com with SMTP id bc4so17336780lbc.0;\n\tSun, 03 Apr 2016 08:23:06 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:references:user-agent:date\n\t:message-id:mime-version;\n\tbh=HHYu6xD64gma+HyFXFqzlEGI/votQreo55hp7wEnYDk=;\n\tb=nQsi6Jo2wiy/p8Ff/ZcP2BYqDvRkC9Nk8iDqYta0HPoJqs8qE2pj5xs6ho8AxcATL9\n\tRaFnk3ZGxeM4r6RcRJ1MWWl+N60zzUTTkuB0mBFeLNLdBcCb+8kqHD3KjHx9K8J7u+bP\n\tBZwNH2FlWlOBKiBxKEzAwWpfhcIL1/cMMIy8RQ32G6YibYzu70WbGJcslQ5vABB3FeIV\n\tm3O4nkNieR5N2n8ntE8Nx18Mhu7kqTNCmhpHaYNZibmUmMnrLXqlsquZErSRDyJPfioR\n\t/NxAr/u/meYisy49nV+ovJ+zx/jdCOMUke+mfBwp7/a2DHpa9tgzzdorDcWmuh9ohWV0\n\tExmQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:references\n\t:user-agent:date:message-id:mime-version;\n\tbh=HHYu6xD64gma+HyFXFqzlEGI/votQreo55hp7wEnYDk=;\n\tb=Zq77MwNNjQYYbs2RdoWWTBwTV56NBXEMs1ijVtpgfMoad7BsL/dl7b5cVIrja45+KT\n\t1kUaay+aBtjIEKkEXVOSjfmZiSpr0DFwm/oQVLHFV9jgyyIGVRr9atSDaWkzjDX3B2JB\n\tN6knPLbOPRDSHNZXrMaV84ohhPmnWRXgOnE9riY2dx3VbcPl7lq0BYk7ARIA0zQeWjMk\n\td2kHvzMw4b16NeTihPKuzhZdcwTlLxMZWQQ/mD7fJR3ouYh/Vx60DxszgrTXc3vYf0g9\n\tcTu00O0Is0WcaLbf5hhFXVg5d/McbxLq1JukCMzPf3OcW7eCy7bhZUil7L1q1z0KcDmS\n\twY1w==\nX-Gm-Message-State: AD7BkJLy/ctaImFlYAjA/TYW1/8EXQkHAWvSxXw9PL/kuZRtgQm3OHqFCovfIJfaxmnxEw==\nX-Received: by 10.28.148.8 with SMTP id w8mr7655269wmd.29.1459696985367;\n\tSun, 03 Apr 2016 08:23:05 -0700 (PDT)\nReceived: from localhost (xdsl-87-79-226-7.netcologne.de. [87.79.226.7])\n\tby smtp.gmail.com with ESMTPSA id\n\tgl8sm2545325wjb.30.2016.04.03.08.23.03\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 03 Apr 2016 08:23:03 -0700 (PDT)\nFrom: Ingo Lohmar <i.lohmar@gmail.com>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nIn-Reply-To: <838u0urb1w.fsf@gnu.org>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <838u0urb1w.fsf@gnu.org>\nUser-Agent: Notmuch/0.20.2+113~g6332e6e (http://notmuchmail.org)\n\tEmacs/25.0.90.1 (x86_64-pc-linux-gnu)\nDate: Sun, 03 Apr 2016 17:23:03 +0200\nMessage-ID: <8760vyemy0.fsf@acer.localhost.com>\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c04::241\nCc: acm@muc.de, kaushal.modi@gmail.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 15:23:12 -0000\nContent-Length: 1331\nLines: 32\n\nOn Sun, Apr 03 2016 18:01 (+0300), Eli Zaretskii wrote:\n\n>> From: Ingo Lohmar <i.lohmar@gmail.com>\n>> Date: Sun, 03 Apr 2016 14:30:27 +0200\n>> Cc: Emacs developers <dev@caliopen.local>,\n>> \tKaushal Modi <kaushal.modi@gmail.com>\n>> \n>> Single caveat: Do NOT start a merge when you have uncommited changes.\n>> If you want, do 'git stash' first to recover them later.\n>\n> I disagree with this caveat.  There's no reason to frighten people\n> like that, as doing such merges will work most of the time.\n>\n\nThis is not about frightening people; to reiterate, this is a prominent\nwarning on the git merge man page --- I will not tell people it's ok\nwhen the official documentation discourages it.  Also, in my opinion it\nis conceptually a bad practice to start git operations that affect the\ncommit graph (such as merge) from an unclean state.\n\n>> In this case, you have to learn about rebase, as in 'git rebase\n>> origin/master'.\n>\n> \"git rebase\" is a bad idea when merging a long-lived feature branch,\n> so please don't advise this to users who are evidently not Git\n> experts.\n\nIt is my understanding (and I made it clear that it was partly\nguesswork) that Alan asked precisely for that functionality.  I am not\nsufficiently patronizing to tell intelligent people they are not ready\nfor something when they explicitly ask for it. :)\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_777.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amk94-0003xX-NQ\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 11:40:58 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:43454)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amk90-0003s8-V1\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:40:56 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amk8z-0001oc-V2\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:40:54 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:55228)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1amk8w-0001oH-3Z; Sun, 03 Apr 2016 11:40:50 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3368\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1amk8v-0003WO-8X; Sun, 03 Apr 2016 11:40:49 -0400\nDate: Sun, 03 Apr 2016 18:40:21 +0300\nMessage-Id: <8337r2r996.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Ingo Lohmar <i.lohmar@gmail.com>\nIn-reply-to: <8760vyemy0.fsf@acer.localhost.com> (message from Ingo Lohmar on\n\tSun, 03 Apr 2016 17:23:03 +0200)\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <838u0urb1w.fsf@gnu.org>\n\t<8760vyemy0.fsf@acer.localhost.com>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: acm@muc.de, kaushal.modi@gmail.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 15:40:56 -0000\nContent-Length: 2736\nLines: 65\n\n> From: Ingo Lohmar <i.lohmar@gmail.com>\n> Cc: acm@muc.de, dev@caliopen.local, kaushal.modi@gmail.com\n> Date: Sun, 03 Apr 2016 17:23:03 +0200\n> \n> On Sun, Apr 03 2016 18:01 (+0300), Eli Zaretskii wrote:\n> \n> >> From: Ingo Lohmar <i.lohmar@gmail.com>\n> >> Date: Sun, 03 Apr 2016 14:30:27 +0200\n> >> Cc: Emacs developers <dev@caliopen.local>,\n> >> \tKaushal Modi <kaushal.modi@gmail.com>\n> >> \n> >> Single caveat: Do NOT start a merge when you have uncommited changes.\n> >> If you want, do 'git stash' first to recover them later.\n> >\n> > I disagree with this caveat.  There's no reason to frighten people\n> > like that, as doing such merges will work most of the time.\n> >\n> \n> This is not about frightening people; to reiterate, this is a prominent\n> warning on the git merge man page --- I will not tell people it's ok\n> when the official documentation discourages it.\n\nI think your documentation might be outdated.  Here's what the \"git\npull\" man page I have says:\n\n  In Git 1.7.0 or later, to cancel a conflicting merge, use git reset\n  --merge. Warning: In older versions of Git, running git pull with\n  uncommitted changes is discouraged: while possible, it leaves you in a\n  state that may be hard to back out of in the case of a conflict.\n\n  If any of the remote changes overlap with local uncommitted changes,\n  the merge will be automatically cancelled and the work tree\n  untouched. It is generally best to get any local changes in working\n  order before pulling or stash them away with git-stash(1).\n\nThis is with Git 2.8.0.\n\nIOW, for a recent enough Git, they _recommend_ stashing, but no longer\n_warn_ about merging in this situation.  Which is exactly my\nexperience.\n\n> Also, in my opinion it is conceptually a bad practice to start git\n> operations that affect the commit graph (such as merge) from an\n> unclean state.\n\nI agree that it's preferable to have a clean repo, but in practice it\ndoesn't always work to have it.  Being able to pull when you have\nuncommitted changes is an important feature; a VCS that doesn't\nsupport it is IMO severely broken, because it will get in the way.\n\n> >> In this case, you have to learn about rebase, as in 'git rebase\n> >> origin/master'.\n> >\n> > \"git rebase\" is a bad idea when merging a long-lived feature branch,\n> > so please don't advise this to users who are evidently not Git\n> > experts.\n> \n> It is my understanding (and I made it clear that it was partly\n> guesswork) that Alan asked precisely for that functionality.  I am not\n> sufficiently patronizing to tell intelligent people they are not ready\n> for something when they explicitly ask for it. :)\n\nYou may wish re-reading some of Alan's past messages about his\nadventures with Git, to get a better idea about that.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_779.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amkSJ-0001CJ-0W\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 12:00:51 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:46604)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amkSF-000157-7K\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 12:00:48 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amkSE-0005od-BO\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 12:00:47 -0400\nReceived: from mail-lf0-x244.google.com ([2a00:1450:4010:c07::244]:33744)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>)\n\tid 1amkSA-0005nT-7p; Sun, 03 Apr 2016 12:00:42 -0400\nReceived: by mail-lf0-x244.google.com with SMTP id r62so5895742lfd.0;\n\tSun, 03 Apr 2016 09:00:41 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:references:user-agent:date\n\t:message-id:mime-version;\n\tbh=dvOZOYxB9zIbhQZGaqTr6joZU8JZiD5BWyeOULvYxFM=;\n\tb=NryENgeCJi91K7WTNyyQCXPVm7Rt3CfyoE0kkOmXOTZPbIvYdfLlQU6Yalp1diJ+L/\n\t1W6lnKv5UiW2x58NypQR9Xau1VY0wXQA6cIq0zFqXFJemsec0kmIsv6RERuGgDLIdUET\n\tTKXJLV9MSZQbhz/ZgsxikhsqXdCq4Cv072QkzERd8C3Wwk8/u82e/SDFxm45/lXBrW1k\n\tgvzsoEOe/dCz1US2+4L0bQV//XLxXBj6AooVevyc7vx/O1KzZi8XCHIe+p2BUY+HfJgr\n\tOPC+HbPgZR7FWYA1GBgy7JOyv70uJI5gmW/P8uFLfGFnAEsZyCBdEGf6qVGp9fthLI9b\n\tS5MQ==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:references\n\t:user-agent:date:message-id:mime-version;\n\tbh=dvOZOYxB9zIbhQZGaqTr6joZU8JZiD5BWyeOULvYxFM=;\n\tb=hrKeBLn/+sGAfFcVc6424kgRBL4GVCqoxOMrmZYWN78RMyfeIUlDM6ESR3sjpoZGtA\n\tobMcHQQwLHFRgK6lZCB23oevKyIaTi9fTeAtAI97+JY/BlxO/zPjoHb/yG2Fz6626G9t\n\tq0JVD97T0niPp1w4LvVAnWNX/jF9/GcuUgjQ3dx2yDNCYfI6FWEsaBoVhp0SrqI3G0mz\n\tal74gCqJk5cHOc5U66uO/5lbJPy/xfKgI8YFt9XH5CsWwA8q2MD7AVFQx5jL+3KsYcIF\n\tj+4HN1uCrsUL9vVezTPG1OYfNaHErKjSL11ooc52nCZFU4bss4JSryKjT7ft7pF1ODht\n\tudog==\nX-Gm-Message-State: AD7BkJLVABARpdhjdwxg125j5W0Tr8VObU7/OocTpofQxZLUGxNDyWRptqJvudNs9zy+OA==\nX-Received: by 10.194.2.41 with SMTP id 9mr5991278wjr.10.1459699241095;\n\tSun, 03 Apr 2016 09:00:41 -0700 (PDT)\nReceived: from localhost (xdsl-87-79-226-7.netcologne.de. [87.79.226.7])\n\tby smtp.gmail.com with ESMTPSA id y3sm9278563wmy.17.2016.04.03.09.00.39\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 03 Apr 2016 09:00:39 -0700 (PDT)\nFrom: Ingo Lohmar <i.lohmar@gmail.com>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nIn-Reply-To: <8337r2r996.fsf@gnu.org>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <838u0urb1w.fsf@gnu.org>\n\t<8760vyemy0.fsf@acer.localhost.com> <8337r2r996.fsf@gnu.org>\nUser-Agent: Notmuch/0.20.2+113~g6332e6e (http://notmuchmail.org)\n\tEmacs/25.0.90.1 (x86_64-pc-linux-gnu)\nDate: Sun, 03 Apr 2016 18:00:38 +0200\nMessage-ID: <8737r2el7d.fsf@acer.localhost.com>\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c07::244\nCc: acm@muc.de, kaushal.modi@gmail.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 16:00:49 -0000\nContent-Length: 2122\nLines: 46\n\nOn Sun, Apr 03 2016 18:40 (+0300), Eli Zaretskii wrote:\n> I think your documentation might be outdated.  Here's what the \"git\n> pull\" man page I have says:\n>\n>   In Git 1.7.0 or later, to cancel a conflicting merge, use git reset\n>   --merge. Warning: In older versions of Git, running git pull with\n>   uncommitted changes is discouraged: while possible, it leaves you in a\n>   state that may be hard to back out of in the case of a conflict.\n>\n>   If any of the remote changes overlap with local uncommitted changes,\n>   the merge will be automatically cancelled and the work tree\n>   untouched. It is generally best to get any local changes in working\n>   order before pulling or stash them away with git-stash(1).\n>\n> This is with Git 2.8.0.\n>\n> IOW, for a recent enough Git, they _recommend_ stashing, but no longer\n> _warn_ about merging in this situation.  Which is exactly my\n> experience.\n\nFor the sake of completeness, my 'git merge' man page for 2.8.0.rc3 says\n(in the DESCRIPTION section):\n\n       Warning: Running git merge with non-trivial uncommitted changes\n       is discouraged: while possible, it may leave you in a state that\n       is hard to back out of in the case of a conflict.\n\n> I agree that it's preferable to have a clean repo, but in practice it\n> doesn't always work to have it.  Being able to pull when you have\n> uncommitted changes is an important feature; a VCS that doesn't\n> support it is IMO severely broken, because it will get in the way.\n\nWe'll just have to disagree about the \"severely broken\" part.\n\n>> It is my understanding (and I made it clear that it was partly\n>> guesswork) that Alan asked precisely for that functionality.  I am not\n>> sufficiently patronizing to tell intelligent people they are not ready\n>> for something when they explicitly ask for it. :)\n>\n> You may wish re-reading some of Alan's past messages about his\n> adventures with Git, to get a better idea about that.\n\nI assume you mean a better idea about Alan's wishes..  Well, I actually\nfollowed those threads, but what was asked for here does not remind me\nof them.  Sorry if I was missing the point.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463570.5266_781.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amkkw-0000lY-RM\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 12:20:06 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:50430)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amkku-0000kP-55\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 12:20:05 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amkks-0001Mr-Uv\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 12:20:04 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:55856)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1amkkn-0001KA-Qv; Sun, 03 Apr 2016 12:19:57 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3409\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1amkkk-0003R1-MH; Sun, 03 Apr 2016 12:19:57 -0400\nDate: Sun, 03 Apr 2016 19:19:26 +0300\nMessage-Id: <83zitapsvl.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Ingo Lohmar <i.lohmar@gmail.com>\nIn-reply-to: <8737r2el7d.fsf@acer.localhost.com> (message from Ingo Lohmar on\n\tSun, 03 Apr 2016 18:00:38 +0200)\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <838u0urb1w.fsf@gnu.org>\n\t<8760vyemy0.fsf@acer.localhost.com> <8337r2r996.fsf@gnu.org>\n\t<8737r2el7d.fsf@acer.localhost.com>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: acm@muc.de, kaushal.modi@gmail.com, dev@caliopen.local\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 16:20:05 -0000\nContent-Length: 1830\nLines: 42\n\n> From: Ingo Lohmar <i.lohmar@gmail.com>\n> Cc: acm@muc.de, dev@caliopen.local, kaushal.modi@gmail.com\n> Date: Sun, 03 Apr 2016 18:00:38 +0200\n> \n> On Sun, Apr 03 2016 18:40 (+0300), Eli Zaretskii wrote:\n> > I think your documentation might be outdated.  Here's what the \"git\n> > pull\" man page I have says:\n> >\n> >   In Git 1.7.0 or later, to cancel a conflicting merge, use git reset\n> >   --merge. Warning: In older versions of Git, running git pull with\n> >   uncommitted changes is discouraged: while possible, it leaves you in a\n> >   state that may be hard to back out of in the case of a conflict.\n> >\n> >   If any of the remote changes overlap with local uncommitted changes,\n> >   the merge will be automatically cancelled and the work tree\n> >   untouched. It is generally best to get any local changes in working\n> >   order before pulling or stash them away with git-stash(1).\n> >\n> > This is with Git 2.8.0.\n> >\n> > IOW, for a recent enough Git, they _recommend_ stashing, but no longer\n> > _warn_ about merging in this situation.  Which is exactly my\n> > experience.\n> \n> For the sake of completeness, my 'git merge' man page for 2.8.0.rc3 says\n> (in the DESCRIPTION section):\n> \n>        Warning: Running git merge with non-trivial uncommitted changes\n>        is discouraged: while possible, it may leave you in a state that\n>        is hard to back out of in the case of a conflict.\n\nBut we were talking about \"git pull\", not about merge.\n\n> > I agree that it's preferable to have a clean repo, but in practice it\n> > doesn't always work to have it.  Being able to pull when you have\n> > uncommitted changes is an important feature; a VCS that doesn't\n> > support it is IMO severely broken, because it will get in the way.\n> \n> We'll just have to disagree about the \"severely broken\" part.\n\nYes, let's do that.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_783.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amkpQ-0001Yv-2I\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 12:24:44 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:51266)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amkpO-0001Yn-9a\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 12:24:42 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amkpN-0002TO-GP\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 12:24:42 -0400\nReceived: from mail-out.m-online.net ([212.18.0.10]:40031)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>)\n\tid 1amkpH-0002Sy-Rs; Sun, 03 Apr 2016 12:24:35 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qdL6p4s7wz3hjYZ;\n\tSun,  3 Apr 2016 18:24:34 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qdL6p4F6pzvh21;\n\tSun,  3 Apr 2016 18:24:34 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id PO1rDZHo7t55; Sun,  3 Apr 2016 18:24:33 +0200 (CEST)\nX-Auth-Info: Of88e/izxFPojKFrxXbVSZsXiftwyeFOG2ZUZbMPRcuEhRwuA1FUjD/ip02LLvh0\nReceived: from igel.home (ppp-88-217-22-197.dynamic.mnet-online.de\n\t[88.217.22.197]) by mail.mnet-online.de (Postfix) with ESMTPA;\n\tSun,  3 Apr 2016 18:24:33 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid 3DAEE2C305E; Sun,  3 Apr 2016 18:24:33 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <838u0urb1w.fsf@gnu.org>\n\t<8760vyemy0.fsf@acer.localhost.com> <8337r2r996.fsf@gnu.org>\n\t<8737r2el7d.fsf@acer.localhost.com> <83zitapsvl.fsf@gnu.org>\nX-Yow: ONE:  I will donate my entire ``BABY HUEY'' comic book collection\n\tto the downtown PLASMA CENTER..\n\tTWO:  I won't START a BAND called ``KHADAFY & THE HIT SQUAD''..\n\tTHREE:  I won't ever TUMBLE DRY my FOX TERRIER again!!\nDate: Sun, 03 Apr 2016 18:24:33 +0200\nIn-Reply-To: <83zitapsvl.fsf@gnu.org> (Eli Zaretskii's message of \"Sun, 03 Apr\n\t2016 19:19:26 +0300\")\nMessage-ID: <8760vyzmm6.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 212.18.0.10\nCc: acm@muc.de, Ingo Lohmar <i.lohmar@gmail.com>, dev@caliopen.local,\n\tkaushal.modi@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 16:24:43 -0000\nContent-Length: 290\nLines: 13\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n> But we were talking about \"git pull\", not about merge.\n\npull == fetch+merge.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_785.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amlXj-0007wX-B9\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 13:10:31 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:58403)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amlXg-0007ux-NU\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 13:10:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amlXd-0002ny-AR\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 13:10:28 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:56371)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1amlXd-0002nu-70; Sun, 03 Apr 2016 13:10:25 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3496\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1amlXc-00058B-BL; Sun, 03 Apr 2016 13:10:24 -0400\nDate: Sun, 03 Apr 2016 20:09:56 +0300\nMessage-Id: <83wpoepqjf.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Andreas Schwab <schwab@linux-m68k.org>\nIn-reply-to: <8760vyzmm6.fsf@linux-m68k.org> (message from Andreas Schwab on\n\tSun, 03 Apr 2016 18:24:33 +0200)\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <838u0urb1w.fsf@gnu.org>\n\t<8760vyemy0.fsf@acer.localhost.com> <8337r2r996.fsf@gnu.org>\n\t<8737r2el7d.fsf@acer.localhost.com> <83zitapsvl.fsf@gnu.org>\n\t<8760vyzmm6.fsf@linux-m68k.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: acm@muc.de, i.lohmar@gmail.com, dev@caliopen.local, kaushal.modi@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 17:10:30 -0000\nContent-Length: 376\nLines: 12\n\n> From: Andreas Schwab <schwab@linux-m68k.org>\n> Cc: Ingo Lohmar <i.lohmar@gmail.com>,  acm@muc.de,  kaushal.modi@gmail.com,  dev@caliopen.local\n> Date: Sun, 03 Apr 2016 18:24:33 +0200\n> \n> Eli Zaretskii <eliz@gnu.org> writes:\n> \n> > But we were talking about \"git pull\", not about merge.\n> \n> pull == fetch+merge.\n\nBut \"git help pull\" != \"git help fetch\"+\"git help merge\".\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_787.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amlw0-0007KB-89\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 13:35:36 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34990)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amlvx-0007JQ-8W\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 13:35:34 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amlvw-0008Ke-Co\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 13:35:33 -0400\nReceived: from mail-out.m-online.net ([212.18.0.9]:45150)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>)\n\tid 1amlvr-0008JR-DF; Sun, 03 Apr 2016 13:35:27 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qdMhY6f9Nz3hjR1;\n\tSun,  3 Apr 2016 19:35:25 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qdMhY5zbwzvh21;\n\tSun,  3 Apr 2016 19:35:25 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id 5k6aZZ9o-LyN; Sun,  3 Apr 2016 19:35:25 +0200 (CEST)\nX-Auth-Info: 1MXEjaQRROgZxd1uIabDhDRsJxMG+dmHE2LvTnk9vcGlQgaKgff77RuHiK8bHtNI\nReceived: from igel.home (ppp-88-217-22-197.dynamic.mnet-online.de\n\t[88.217.22.197]) by mail.mnet-online.de (Postfix) with ESMTPA;\n\tSun,  3 Apr 2016 19:35:24 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid BA3532C3982; Sun,  3 Apr 2016 19:35:24 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Eli Zaretskii <eliz@gnu.org>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <838u0urb1w.fsf@gnu.org>\n\t<8760vyemy0.fsf@acer.localhost.com> <8337r2r996.fsf@gnu.org>\n\t<8737r2el7d.fsf@acer.localhost.com> <83zitapsvl.fsf@gnu.org>\n\t<8760vyzmm6.fsf@linux-m68k.org> <83wpoepqjf.fsf@gnu.org>\nX-Yow: ..  are the STEWED PRUNES still in the HAIR DRYER?\nDate: Sun, 03 Apr 2016 19:35:24 +0200\nIn-Reply-To: <83wpoepqjf.fsf@gnu.org> (Eli Zaretskii's message of \"Sun, 03 Apr\n\t2016 20:09:56 +0300\")\nMessage-ID: <871t6mzjc3.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 212.18.0.9\nCc: acm@muc.de, i.lohmar@gmail.com, dev@caliopen.local, kaushal.modi@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 17:35:34 -0000\nContent-Length: 648\nLines: 23\n\nEli Zaretskii <eliz@gnu.org> writes:\n\n>> From: Andreas Schwab <schwab@linux-m68k.org>\n>> Cc: Ingo Lohmar <i.lohmar@gmail.com>,  acm@muc.de,  kaushal.modi@gmail.com,  dev@caliopen.local\n>> Date: Sun, 03 Apr 2016 18:24:33 +0200\n>> \n>> Eli Zaretskii <eliz@gnu.org> writes:\n>> \n>> > But we were talking about \"git pull\", not about merge.\n>> \n>> pull == fetch+merge.\n>\n> But \"git help pull\" != \"git help fetch\"+\"git help merge\".\n\nWhat's your point?  pull has additional options.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_789.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1ammO2-00073u-Sb\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 14:04:34 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:40114)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1ammO0-000733-MM\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:04:33 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1ammNx-00061d-Fn\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 14:04:32 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:57100)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1ammNx-00061Z-Bv; Sun, 03 Apr 2016 14:04:29 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3650\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1ammNw-0005ME-CU; Sun, 03 Apr 2016 14:04:28 -0400\nDate: Sun, 03 Apr 2016 21:04:00 +0300\nMessage-Id: <83r3empo1b.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Andreas Schwab <schwab@linux-m68k.org>\nIn-reply-to: <871t6mzjc3.fsf@linux-m68k.org> (message from Andreas Schwab on\n\tSun, 03 Apr 2016 19:35:24 +0200)\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <838u0urb1w.fsf@gnu.org>\n\t<8760vyemy0.fsf@acer.localhost.com> <8337r2r996.fsf@gnu.org>\n\t<8737r2el7d.fsf@acer.localhost.com> <83zitapsvl.fsf@gnu.org>\n\t<8760vyzmm6.fsf@linux-m68k.org> <83wpoepqjf.fsf@gnu.org>\n\t<871t6mzjc3.fsf@linux-m68k.org>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: acm@muc.de, i.lohmar@gmail.com, dev@caliopen.local, kaushal.modi@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 18:04:33 -0000\nContent-Length: 908\nLines: 26\n\n> From: Andreas Schwab <schwab@linux-m68k.org>\n> Cc: i.lohmar@gmail.com,  acm@muc.de,  kaushal.modi@gmail.com,  dev@caliopen.local\n> Date: Sun, 03 Apr 2016 19:35:24 +0200\n> \n> Eli Zaretskii <eliz@gnu.org> writes:\n> \n> >> From: Andreas Schwab <schwab@linux-m68k.org>\n> >> Cc: Ingo Lohmar <i.lohmar@gmail.com>,  acm@muc.de,  kaushal.modi@gmail.com,  dev@caliopen.local\n> >> Date: Sun, 03 Apr 2016 18:24:33 +0200\n> >> \n> >> Eli Zaretskii <eliz@gnu.org> writes:\n> >> \n> >> > But we were talking about \"git pull\", not about merge.\n> >> \n> >> pull == fetch+merge.\n> >\n> > But \"git help pull\" != \"git help fetch\"+\"git help merge\".\n> \n> What's your point?  pull has additional options.\n\nMy point is that we were talking about \"git pull\", and therefore the\nrelevant documentation is the man page of \"pull\", not of \"merge\".  The\nman page of \"pull\" no longer warns about pulling when there are\nuncommitted changes.\n\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_791.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amilL-0002py-Br\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 10:12:23 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:55326)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amilJ-0002pV-Ch\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:12:22 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amilG-00004y-7y\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:12:21 -0400\nReceived: from mail-out.m-online.net ([212.18.0.9]:58831)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amilG-00004t-1c\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:12:18 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qdHB85XN2z3hjNF;\n\tSun,  3 Apr 2016 16:12:16 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qdHB84vw3zvh25;\n\tSun,  3 Apr 2016 16:12:16 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id SX6TB8MFM2pE; Sun,  3 Apr 2016 16:12:15 +0200 (CEST)\nX-Auth-Info: 2s74cml77hq3kJ2Unh2xb7Matz6DLwAwQcdc8Guq+XQDTDebvW+oI2KlMLf7E/4+\nReceived: from igel.home (ppp-88-217-22-197.dynamic.mnet-online.de\n\t[88.217.22.197]) by mail.mnet-online.de (Postfix) with ESMTPA;\n\tSun,  3 Apr 2016 16:12:15 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid 6881E2C3440; Sun,  3 Apr 2016 16:12:15 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Ingo Lohmar <i.lohmar@gmail.com>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com>\nX-Yow: Is this an out-take from the ``BRADY BUNCH''?\nDate: Sun, 03 Apr 2016 16:12:15 +0200\nIn-Reply-To: <87bn5qeuxo.fsf@acer.localhost.com> (Ingo Lohmar's message of\n\t\"Sun, 03 Apr 2016 14:30:27 +0200\")\nMessage-ID: <87mvpazsqo.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 212.18.0.9\nCc: Alan Mackenzie <acm@muc.de>, Kaushal Modi <kaushal.modi@gmail.com>,\n\tEmacs developers <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 14:12:22 -0000\nContent-Length: 352\nLines: 13\n\nIngo Lohmar <i.lohmar@gmail.com> writes:\n\n> Single caveat: Do NOT start a merge when you have uncommited changes.\n\ngit merge will not allow overwriting uncommited changes anyway.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_793.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amjTY-0005tr-Tv\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 10:58:04 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36268)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amjTW-0005tg-LZ\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:58:03 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amjTS-0001O8-LK\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:58:02 -0400\nReceived: from mail-lb0-x241.google.com ([2a00:1450:4010:c04::241]:34880)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <i.lohmar@gmail.com>) id 1amjTS-0001O4-Cu\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 10:57:58 -0400\nReceived: by mail-lb0-x241.google.com with SMTP id gk8so15079376lbc.2\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 07:57:58 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=from:to:cc:subject:in-reply-to:references:user-agent:date\n\t:message-id:mime-version;\n\tbh=IaSMuPwPDSEh/CCUuQN4hTpJQEDYfe7itAv8JhKkas4=;\n\tb=izwNxW/bE3NlYIuoPRMoTSQBihHvHfyqUFED2M7kt315LNnluuU7q8nf+J3eiGX6Fp\n\tMJlh4k/WxLOZ3N3D8WtE3EPIcHVY4atSDw29+2LaSPOBOL1PRhf8DwUf1RIaMPFfb+AD\n\togW3uz2bM+PBKRABVk7w7oML/ADFadX6udZlj6CPvaAuABpBMO7SPAhOmrhFZ4SPujRL\n\txT9rA9szCQjEXpIBo3cBer0GVYw1+R4QlIeOpvMg4AS7Y7HRDhj90Dgpob9iIeIi4zfo\n\tywDT9cLnQkSfBbICgJ9IrmPnVIXemmVITwy3CjLg+UXFU98KVZJuseArbvm6PGtuZHEg\n\tyAGA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:from:to:cc:subject:in-reply-to:references\n\t:user-agent:date:message-id:mime-version;\n\tbh=IaSMuPwPDSEh/CCUuQN4hTpJQEDYfe7itAv8JhKkas4=;\n\tb=awrlBNwGxO5ncR9TD/3UpuOhlpebbASgJeJ72+L3X5mhbRjjDvswjZWtI0Ol+LEWsu\n\tL9c/aMoYB4yTuPYk4FqSXfIl6taaKeAYk6Qt6/Ovta28aO3nF4eCXXKYwWb+ihMirYlp\n\tXkBKegItees+kNOI1YrT/Rc36byJkFeqwDd7T1Ni/5Zmv95BOUt1eHLxiaTcIAqkfHi8\n\tRe3bMrRRKQ01P4g/swURP8ykKD/cO8swiTrsAPi8rxZrAX+jm0Du6JH9KZqPqxJ7DVCX\n\tBOKiQzKMSYj3+lZ/b04m6MJR6AoKEaB/bSO6FjJCCyxKtRcZBIQkZKvKOJ/gGTxplU0M\n\tn9rA==\nX-Gm-Message-State: AD7BkJJnkZvvX2vLrk7T7UqYP19SIAzWzhsTpjk3Poz1+XPv+KNK/DupBow+fh5oe2WjYQ==\nX-Received: by 10.28.98.137 with SMTP id w131mr7583507wmb.30.1459695477493;\n\tSun, 03 Apr 2016 07:57:57 -0700 (PDT)\nReceived: from localhost (xdsl-87-79-226-7.netcologne.de. [87.79.226.7])\n\tby smtp.gmail.com with ESMTPSA id v6sm8997374wmv.16.2016.04.03.07.57.56\n\t(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n\tSun, 03 Apr 2016 07:57:56 -0700 (PDT)\nFrom: Ingo Lohmar <i.lohmar@gmail.com>\nTo: Andreas Schwab <schwab@linux-m68k.org>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nIn-Reply-To: <87mvpazsqo.fsf@linux-m68k.org>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <87mvpazsqo.fsf@linux-m68k.org>\nUser-Agent: Notmuch/0.20.2+113~g6332e6e (http://notmuchmail.org)\n\tEmacs/25.0.90.1 (x86_64-pc-linux-gnu)\nDate: Sun, 03 Apr 2016 16:57:54 +0200\nMessage-ID: <878u0ueo3x.fsf@acer.localhost.com>\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c04::241\nCc: Alan Mackenzie <acm@muc.de>, Kaushal Modi <kaushal.modi@gmail.com>,\n\tEmacs developers <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 14:58:03 -0000\nContent-Length: 559\nLines: 17\n\nOn Sun, Apr 03 2016 16:12 (+0200), Andreas Schwab wrote:\n\n> Ingo Lohmar <i.lohmar@gmail.com> writes:\n>\n>> Single caveat: Do NOT start a merge when you have uncommited changes.\n>\n> git merge will not allow overwriting uncommited changes anyway.\n>\n> Andreas.\n\nThat's correct, but if the changes are in files unaffected by the merge,\ngit *does* start the merge.  But it will be hard to return to the exact\npre-merge state if you want to abort.\n\nAt least that's what everybody and the man page tell me, I can't\nremember whether I tested it myself at some point.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_795.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amjhl-0002O9-Hu\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 11:12:45 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:39193)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amjhj-0002O2-Aq\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:12:44 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <eliz@gnu.org>) id 1amjhg-0004Tg-2D\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:12:43 -0400\nReceived: from fencepost.gnu.org ([2001:4830:134:3::e]:54942)\n\tby eggs.gnu.org with esmtp (Exim 4.71) (envelope-from <eliz@gnu.org>)\n\tid 1amjhf-0004Tb-Uf; Sun, 03 Apr 2016 11:12:39 -0400\nReceived: from 84.94.185.246.cable.012.net.il ([84.94.185.246]:3097\n\thelo=home-c4e4a596f7)\n\tby fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128)\n\t(Exim 4.82) (envelope-from <eliz@gnu.org>)\n\tid 1amjhe-0004sD-RG; Sun, 03 Apr 2016 11:12:39 -0400\nDate: Sun, 03 Apr 2016 18:12:11 +0300\nMessage-Id: <8360vyrak4.fsf@gnu.org>\nFrom: Eli Zaretskii <eliz@gnu.org>\nTo: Ingo Lohmar <i.lohmar@gmail.com>\nIn-reply-to: <878u0ueo3x.fsf@acer.localhost.com> (message from Ingo Lohmar on\n\tSun, 03 Apr 2016 16:57:54 +0200)\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <87mvpazsqo.fsf@linux-m68k.org>\n\t<878u0ueo3x.fsf@acer.localhost.com>\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2001:4830:134:3::e\nCc: acm@muc.de, dev@caliopen.local, schwab@linux-m68k.org,\n\tkaushal.modi@gmail.com\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nReply-To: Eli Zaretskii <eliz@gnu.org>\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 15:12:44 -0000\nContent-Length: 690\nLines: 18\n\n> From: Ingo Lohmar <i.lohmar@gmail.com>\n> Date: Sun, 03 Apr 2016 16:57:54 +0200\n> Cc: Alan Mackenzie <acm@muc.de>, Kaushal Modi <kaushal.modi@gmail.com>,\n> \tEmacs developers <emacs-devel@gnu.org>\n> \n> > git merge will not allow overwriting uncommited changes anyway.\n> >\n> > Andreas.\n> \n> That's correct, but if the changes are in files unaffected by the merge,\n> git *does* start the merge.  But it will be hard to return to the exact\n> pre-merge state if you want to abort.\n\nGit stops the merge at the point where it's quite easy to abort or\nstash the uncommitted changes.  When Git stops, it displays a message\nwith these 2 alternatives; following either alternative is easy and\nsafe.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_797.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amje7-0001Nx-LO\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 11:08:59 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:38388)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amje4-0001MF-Ue\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:08:57 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amje1-0003b0-Od\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:08:56 -0400\nReceived: from mail-out.m-online.net ([212.18.0.9]:56978)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amje1-0003at-IK\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 11:08:53 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qdJRS2D4Zz3hj8L;\n\tSun,  3 Apr 2016 17:08:52 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qdJRS1jspzvh1v;\n\tSun,  3 Apr 2016 17:08:52 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id rKqJi21anv1J; Sun,  3 Apr 2016 17:08:51 +0200 (CEST)\nX-Auth-Info: 13LSXXwIYG9z9dAMo13fnCFYjtEn7+AyP/QByjN8JqpWaq2y3tQgdtZ+1QosyS1H\nReceived: from igel.home (ppp-88-217-22-197.dynamic.mnet-online.de\n\t[88.217.22.197]) by mail.mnet-online.de (Postfix) with ESMTPA;\n\tSun,  3 Apr 2016 17:08:51 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid 1DF662C373A; Sun,  3 Apr 2016 17:08:51 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Ingo Lohmar <i.lohmar@gmail.com>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\n\t<87lh4uex9h.fsf@acer.localhost.com>\n\t<20160403121458.GC3537@acm.fritz.box>\n\t<87bn5qeuxo.fsf@acer.localhost.com> <87mvpazsqo.fsf@linux-m68k.org>\n\t<878u0ueo3x.fsf@acer.localhost.com>\nX-Yow: TAPPING?  You POLITICIANS!  Don't you realize that the END of the\n\t``Wash Cycle'' is a TREASURED MOMENT for most people?!\nDate: Sun, 03 Apr 2016 17:08:51 +0200\nIn-Reply-To: <878u0ueo3x.fsf@acer.localhost.com> (Ingo Lohmar's message of\n\t\"Sun, 03 Apr 2016 16:57:54 +0200\")\nMessage-ID: <87a8lazq4c.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 212.18.0.9\nCc: Alan Mackenzie <acm@muc.de>, Kaushal Modi <kaushal.modi@gmail.com>,\n\tEmacs developers <emacs-devel@gnu.org>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 15:08:57 -0000\nContent-Length: 432\nLines: 15\n\nIngo Lohmar <i.lohmar@gmail.com> writes:\n\n> That's correct, but if the changes are in files unaffected by the merge,\n> git *does* start the merge.  But it will be hard to return to the exact\n> pre-merge state if you want to abort.\n\ngit merge --abort will do.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_799.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amgSK-0007O1-NR\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 07:44:36 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:36915)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1amgSH-0007NU-SJ\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:44:34 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1amgSE-0006Ss-Kn\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:44:33 -0400\nReceived: from mail-lf0-x234.google.com ([2a00:1450:4010:c07::234]:35860)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <raaahh@gmail.com>) id 1amgSE-0006So-CN\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:44:30 -0400\nReceived: by mail-lf0-x234.google.com with SMTP id g184so56919989lfb.3\n\tfor <dev@caliopen.local>; Sun, 03 Apr 2016 04:44:30 -0700 (PDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\n\th=sender:subject:to:references:cc:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=zszrp6A3rwWGu7fYTVX9THq7/pClVFhNy4hdFbyo/ZY=;\n\tb=nv1HzSXIdOrqC4yGKqTw4Cy3AS0ggrjaXImvnWIwtmW2kSC+b6od2wYfe/C9WNQemM\n\tKNSseyr3RgMPYPpNnhnh/8mAwMnr6Rph8Xb5vBouza+QydocY4gYuzSx3mUq9QnmNAPQ\n\txBb9QWZLXBqSRXayWVAkga8uFf8SOaGtfAx+UgJLfajUAgt+YO4Ll81o0BxY7BjcwfsF\n\t61wMy54XakVjqRzDa6sEFduR7NNx2lGI+SbUOmGhyZksd5IEE+8mBpcX4IgJah0uEEK6\n\tObvVBRfxZMBm5VoH449GvGB2ae4K1mUyQoauMCrCZGw4tPzM/JXf71cgsPqPyCM8uUAm\n\tnvoA==\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20130820;\n\th=x-gm-message-state:sender:subject:to:references:cc:from:message-id\n\t:date:user-agent:mime-version:in-reply-to:content-transfer-encoding;\n\tbh=zszrp6A3rwWGu7fYTVX9THq7/pClVFhNy4hdFbyo/ZY=;\n\tb=mjOC60PiQ3AUXKNZnPFVROlVbYB0+QTu4JYTL3OIN3c8sO8chhaZYStFZZt6EFwWWJ\n\tE/MvgL2/CvrD0XraiZQkYchBXstqQkzWidmdhCJydZdw+DbZNYgfZSuiM1K9eSBJKMTd\n\tUgtI23hALDZNx5tZKQS0gYX3NaF9MOQG4PYkD/zbyMYIamXLnmVzf8dyafYAvZr0e7dK\n\tIIveYedVRXgpR5o9ObCW9qGmGtd137AZQdmS5uV2akHFoLJ9k6yB23JWmckPMej8LU1c\n\t05c7b4F2WYNweKAkTqltcSYsTITGZhN/pbgrWeHQHC8nF61qs02GBQSLQL1FfV4C2YqC\n\tGqMw==\nX-Gm-Message-State: AD7BkJKwkrToZYQyMGfSiv4dfoqCShpUNGSu2lnUNG9OwEHbqDK3dyoLtoyKZxFiR2pvRg==\nX-Received: by 10.194.236.198 with SMTP id uw6mr2135655wjc.42.1459683869354;\n\tSun, 03 Apr 2016 04:44:29 -0700 (PDT)\nReceived: from [192.168.1.2] ([185.105.175.24])\n\tby smtp.googlemail.com with ESMTPSA id\n\tt3sm23433559wjz.11.2016.04.03.04.44.27\n\t(version=TLSv1/SSLv3 cipher=OTHER);\n\tSun, 03 Apr 2016 04:44:28 -0700 (PDT)\nSender: DG <raaahh@gmail.com>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nTo: Alan Mackenzie <acm@muc.de>, Kaushal Modi <kaushal.modi@gmail.com>\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\nFrom: Dmitry Gutov <dgutov@yandex.ru>\nMessage-ID: <3bd07b4b-1636-4558-3ebe-e8c6b6a85396@yandex.ru>\nDate: Sun, 3 Apr 2016 14:44:25 +0300\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101\n\tThunderbird/45.0\nMIME-Version: 1.0\nIn-Reply-To: <20160403111708.GA3537@acm.fritz.box>\nContent-Type: text/plain; charset=windows-1252; format=flowed\nContent-Transfer-Encoding: 7bit\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 2a00:1450:4010:c07::234\nCc: Emacs developers <dev@caliopen.local>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 11:44:35 -0000\nContent-Length: 905\nLines: 24\n\nOn 04/03/2016 02:17 PM, Alan Mackenzie wrote:\n\n> That massive commit happened because of git.  I attempted a 'git pull'\n> prior to making a (moderately small) commit.\n\nIt must have been after.\n\n> There was a one-letter\n> typo in one of my existing files (which I think had been committed).\n> Because of that, git failed to merge in all the stuff which it had just\n> fetched from savannah, instead prompting me to do a manual merge, which\n> I then did.\n\nYou can ask Git not to do that in the future by passing '--ff-only' to \n'git pull'. But then you'll have to learn to rebase.\n\n> If I had been aware of what was happening, I might have\n> been able to adjust my copy of the repository somehow so as to avoid\n> that large merge.\n\nIf you run 'git log --graph' before every time you push and examine the \noutput, you can be aware of everything what's happening, as well as undo \nthe changes you don't like.\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/1460463571.5266_801.argentina:2,",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n\tid 1amgBX-00034r-3X\n\tfor mharc-dev@caliopen.local; Sun, 03 Apr 2016 07:27:15 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:34732)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amgBU-00033o-Hv\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:27:13 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amgBP-0003RC-To\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:27:12 -0400\nReceived: from mail-out.m-online.net ([2001:a60:0:28:0:1:25:1]:34712)\n\tby eggs.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <whitebox@nefkom.net>) id 1amgBP-0003Qf-Mq\n\tfor dev@caliopen.local; Sun, 03 Apr 2016 07:27:07 -0400\nReceived: from frontend01.mail.m-online.net (unknown [192.168.8.182])\n\tby mail-out.m-online.net (Postfix) with ESMTP id 3qdCWX1cRVz3hjlq;\n\tSun,  3 Apr 2016 13:27:04 +0200 (CEST)\nReceived: from localhost (dynscan1.mnet-online.de [192.168.6.68])\n\tby mail.m-online.net (Postfix) with ESMTP id 3qdCWX1F7Kzvh2B;\n\tSun,  3 Apr 2016 13:27:04 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at mnet-online.de\nReceived: from mail.mnet-online.de ([192.168.8.182])\n\tby localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new,\n\tport 10024)\n\twith ESMTP id jLBdywdG4pcQ; Sun,  3 Apr 2016 13:27:03 +0200 (CEST)\nX-Auth-Info: aL2uiX2ovP+BoERlzkD9R01i3dPU8BT3+tlrcpEWC6bx5IkFJAhGq7+aPWdk2UgN\nReceived: from igel.home (ppp-88-217-22-197.dynamic.mnet-online.de\n\t[88.217.22.197]) by mail.mnet-online.de (Postfix) with ESMTPA;\n\tSun,  3 Apr 2016 13:27:02 +0200 (CEST)\nReceived: by igel.home (Postfix, from userid 1000)\n\tid B00372C3440; Sun,  3 Apr 2016 13:27:02 +0200 (CEST)\nFrom: Andreas Schwab <schwab@linux-m68k.org>\nTo: Alan Mackenzie <acm@muc.de>\nSubject: Re: Understanding a recent commit in emacs-25 branch [ed19f2]\nReferences: <CAFyQvY16kxTqhHrWCVSVU64FM9_VUiXbpnxDRVBmOE1Cj9SicA@mail.gmail.com>\n\t<CAFyQvY1c1FvSOyNk_OxbSP5-prM-mXoMekFNRrbsUC0V=3xubA@mail.gmail.com>\n\t<20160403111708.GA3537@acm.fritz.box>\nX-Yow: Mmmmmm-MMMMMM!!  A plate of STEAMING PIECES of a PIG mixed\n\twith the shreds of SEVERAL CHICKENS!!...  Oh BOY!!  I'm\n\tabout to swallow a TORN-OFF section of a COW'S LEFT LEG\n\tsoaked in COTTONSEED OIL and SUGAR!!  ..  Let's see..\n\tNext, I'll have the GROUND-UP flesh of CUTE, BABY LAMBS\n\tfried in the MELTED, FATTY TISSUES from a warm-blooded\n\tanimal someone once PETTED!!  ...  YUM!!  That was GOOD!!\n\tFor DESSERT, I'll have a TOFU BURGER with BEAN SPROUTS\n\ton a stone-ground, WHOLE WHEAT BUN!!\nDate: Sun, 03 Apr 2016 13:27:02 +0200\nIn-Reply-To: <20160403111708.GA3537@acm.fritz.box> (Alan Mackenzie's message\n\tof \"Sun, 3 Apr 2016 11:17:08 +0000\")\nMessage-ID: <87shz3yltl.fsf@linux-m68k.org>\nUser-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.92 (gnu/linux)\nMIME-Version: 1.0\nContent-Type: text/plain\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x\nX-Received-From: 2001:a60:0:28:0:1:25:1\nCc: Emacs developers <dev@caliopen.local>,\n\tKaushal Modi <kaushal.modi@gmail.com>\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.14\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n\t<mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 03 Apr 2016 11:27:13 -0000\nContent-Length: 351\nLines: 13\n\nAlan Mackenzie <acm@muc.de> writes:\n\n> It would also be nice if such \"pseudo merges\" could be handled locally,\n\nIn git, *all* operations happen locally (until you run git push).\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] CR réunion du 12_12_2014 - David Epely <dey@protonmail.ch> - 2014-12-13 1352.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 32D251211A7;\r\n\tSat, 13 Dec 2014 13:52:59 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.226\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.226 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001, HTML_MESSAGE=0.001, MIME_BASE64_NO_NAME=0.224]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id WjtMuvSt3lHS; Sat, 13 Dec 2014 13:52:57 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id CC33B1211A6;\r\n\tSat, 13 Dec 2014 13:52:57 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 40B936B6200EB;\r\n\tSat, 13 Dec 2014 13:52:56 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=protonmail.ch header.i=@protonmail.ch header.b=UE2Efl4X;\r\n\tdkim-adsp=discard (insecure policy); dkim-atps=neutral\r\nReceived: from mail2.protonmail.ch (mail2.protonmail.ch [185.70.40.22])\r\n (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id D12566B6200E9\r\n for <caliopdev@caliop.net>; Sat, 13 Dec 2014 13:52:55 +0100 (CET)\r\nReceived: by mail2.protonmail.ch (Postfix, from userid 48)\r\n id 6EB63BFCF9; Sat, 13 Dec 2014 07:52:55 -0500 (EST)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.ch;\r\n s=default; t=1418475175;\r\n bh=oYJNaQktjQBptQhBYByoOSgOJg/Ra2741mF1oW7Pp5Q=;\r\n h=To:Subject:Date:From:Reply-To;\r\n b=UE2Efl4XdxxYXdf8JhAbi+WbG72o20M0ilzN20niKicxpWTmCiPS+9TE5JgnQU1fQ\r\n CRfsq4gUiAaeSB+r+SuQaeOjKqNSPaIS+3E2ZhlBbgl0S1UjLUn9eCQOEA6l98pbgZ\r\n LhkJ7TGs3c8RFY07k9a4m/M1sejGMT3szyzqxIPU=\r\nTo: \"caliopdev@caliop.net\" <caliopdev@caliop.net>\r\nX-PHP-Originating-Script: 0:class.phpmailer.php\r\nDate: Sat, 13 Dec 2014 07:52:55 -0500\r\nFrom: David Epely <dey@protonmail.ch>\r\nMessage-ID: <860f154d02424f12ab88ac0f1880bfb7@protonmail.ch>\r\nX-Priority: 3\r\nMIME-Version: 1.0\r\nSubject: Re: [Caliopdev] =?utf-8?q?CR_r=C3=A9union_du_12/12/2014?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: David Epely <dey@protonmail.ch>,\r\n =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1292634308==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--===============1292634308==\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n\r\nU2FsdXQsIDxicj5Qb3VyIGluZm8sIG9uIGEgb3BlbnNvdXJjw6kgbm90cmUgb3V0aWxzIGRlIGdl\r\nc3Rpb24gZGUgcmVsZWFzZXMgZGFucyBtYSBib8OudGUgYWN0dWVsbGUuIDxicj48YnI+Qydlc3Qg\r\nYXNzZXogcHJhdGlxdWUgbG9yc3F1ZSBsJ29uIGRvaXQgZ8OpcmVyIHVuZSByZWxlYXNlIHF1aSBy\r\nZXBvc2Ugc3VyIHVuZSBwaWxlIGQnb3V0aWxzIGNvbW1lIGNhbGlvcGVuOiA8YnI+PGJyPjxhIGhy\r\nZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9wbXNpcGlsb3QvcG1zaXBsYW4vIiByZWw9Im5vZm9sbG93\r\nIiB0YXJnZXQ9Il9ibGFuayI+aHR0cHM6Ly9naXRodWIuY29tL3Btc2lwaWxvdC9wbXNpcGxhbi88\r\nL2E+PGJyPmV0IGlsIHkgYSB1biBzY3JlZW5zaG90IGzDoCA6IDxhIGhyZWY9Imh0dHA6Ly93d3cu\r\ncG1zaXBpbG90Lm9yZy8yMDE0LzExLzA2L2RlLWxvcmdhbmlzYXRpb24tZXQtZGUtbGEtcGxhbmlm\r\naWNhdGlvbi1hdmVjLXBtc2lwbGFuLyIgcmVsPSJub2ZvbGxvdyIgdGFyZ2V0PSJfYmxhbmsiPmh0\r\ndHA6Ly93d3cucG1zaXBpbG90Lm9yZy8yMDE0LzExLzA2L2RlLWxvcmdhbmlzYXRpb24tZXQtZGUt\r\nbGEtcGxhbmlmaWNhdGlvbi1hdmVjLXBtc2lwbGFuLzwvYT48YnI+PGJyPjxicj48c3Bhbj4tLS0t\r\nLS0tLSBPcmlnaW5hbCBNZXNzYWdlIC0tLS0tLS0tPGJyPlN1YmplY3Q6IFtDYWxpb3BkZXZdIENS\r\nIHLDqXVuaW9uIGR1IDEyLzEyLzIwMTQ8YnI+VGltZSAoR01UKTogRGVjIDEyIDIwMTQgMTY6MDI6\r\nMDM8YnI+RnJvbTogdGhvbWFzLmxhdXJlbnRAZ2FuZGkubmV0PGJyPlRvOiBjYWxpb3BkZXZAY2Fs\r\naW9wLm5ldDxicj48YnI+IyBSw6l1bmlvbiBDYWxpT3BlbiwgbGUgMTIgZMOpY2VtYnJlIDIwMTQ8\r\nYnI+PGJyPjxicj4jIyDDiXRhaWVudCBwcsOpc2VudHMgOjxicj48YnI+QWxiYW48YnI+S2FqYW48\r\nYnI+SnVsaWVuPGJyPlZpbmNlbnQ8YnI+QmVuamFtaW48YnI+VGhvbWFzPGJyPkd1aWxsYXVtZTxi\r\ncj5TdGV2ZW48YnI+QXltZXJpYzxicj5MYXVyZW50PGJyPlNvcGhpZTxicj48YnI+PGJyPiMjIFBy\r\nw6lzZW50YXRpb24gZHUgc2NyZWVuY2FzdDxicj48YnI+VmVyc2lvbiBmcmFuw6dhaXNlIHByw6lz\r\nZW50w6llIDogdHLDqHMgYm9ucyByZXRvdXJzLCB0b3V0IGxlIG1vbmRlIGVzdCBvazxicj5QdWJs\r\naWNhdGlvbiBzdXIgbGUgYmxvZyB1bmlxdWVtZW50IHBvdXIgbCdpbnN0YW50PGJyPjxicj5Tb3Bo\r\naWUgZGVtYW5kZSBsJ2Fqb3V0IGRlcyB0cmFuc2NyaXB0aW9ucyBzb3VzIGxlcyB2aWTDqW9zPGJy\r\nPjxicj5MYXVyZW50IDogTGEgbWFuacOocmUgZGUgY3LDqWVyIHVuIGNvbXB0ZSBlc3QgaW1wb3J0\r\nYW50ZSwgY2VsYSBkb2l0IMOqdHJlIHByaXMmbmJzcDtlbiBjb21wdGUgZGFucyBs4oCZYWxwaGEu\r\nPGJyPkPigJllc3QgY2UgcXVpIHZhIGZhaXJlIGxhIGRpZmbDqXJlbmNlIGF2ZWMgbGVzIHByaW5j\r\naXBhbGVzIGFwcHMgY29uY3VycmVudGVzIGTDqHMgbGUgZMOpYnV0IGR1IHByb2Nlc3MgZOKAmWFw\r\ncHJvcHJpYXRpb24gZGUgQ2FsaU9wZW4uPGRpdj5QZXJtZXQgZOKAmWFjY3JvY2hlciBs4oCZdXRp\r\nbGlzYXRldXIgdmlhIHVuIHByb2Nlc3Mgc2ltcGxlLCByYXBpZGUuPGJyPjxicj4vIVwgQXR0ZW50\r\naW9uIDogaWwgbuKAmWVzdCBwYXMgY2xhaXIgcXVlIGwnaW50ZXJmYWNlIHNvaXQgdW4gbW9ja3Vw\r\nIGV0IHNpbXBsZW1lbnQgdW4gbW9ja3VwLCBjZWxhIHBldXQgZnJ1c3RyZXIgbGVzIHV0aWxpc2F0\r\nZXVycyBxdWkgc+KAmWF0dGVuZGVudCDDoCB0ZXN0ZXIgbOKAmWludGVyZmFjZSBhcHLDqHMgYXZv\r\naXIgdmlzaW9ubsOpIGxhIHZpZMOpby48L2Rpdj48ZGl2PlBvdXIgbGEgYmFuZGUgc29uIDogdG91\r\ndCBmYWlyZSBhdSBmdXR1cjxicj48YnI+PGRpdj49Jmd0OyBBY3Rpb24gcG9pbnQgOiBJbCBmYXV0\r\nIGV4cGxpcXVlciBxdWUgYydlc3QgdW5lIGZ1dHVyIGludGVyZmFjZTxicj48YnI+PGJyPiMjIFDD\r\nqXJpbcOodHJlIGRlIGwnYWxwaGE8YnI+PGJyPkRhdGUgcHLDqXZ1ZSA6IGZpbiBtYWkgMjAxNTxi\r\ncj48YnI+IyMjIGF1IG1pbmltdW08YnI+LSBjcsOpYXRpb24gZGUgY29tcHRlPGJyPi0gZ2VzdGlv\r\nbiBkZXMgY29udGFjdHM8YnI+LSBnZXN0aW9uIGQndW5lLCBwbHVzaWV1cnMgYm9pdGVzIG1haWw8\r\nYnI+LSBham91dCBkZSBjb21wdGU8L2Rpdj48ZGl2Pi0gaW5zdGFuY2UgcG91ciB0ZXN0ZXIgcG91\r\ndmFudCDDqnRyZSByZXNldCByw6lndWxpw6hyZW1lbnQ8L2Rpdj48ZGl2Pi0gUldEICh2b2lyIGNp\r\nLWRlc3NvdXMpPGJyPjxicj4jIyMgaG9ycy1ww6lyaW3DqHRyZTxicj48YnI+LSBhZ2VuZGE8YnI+\r\nLSBmaWNoaWVyIGF0dGFjaMOpczxicj48YnI+IyMjIMOgIGTDqWZpbmlyPGJyPjxicj4tIGNhbGN1\r\nbCBkdSBuaXZlYXUgZGUgY29uZmlkZW50aWFsaXTDqTxicj4mbmJzcDsmbmJzcDsmbmJzcDstJmd0\r\nOyBwbGVpbnMgZGUgcXVlc3Rpb25zIGVuIHN1c3BlbnMgY8O0dMOpIGNoYcOubmUgZGUgZGVsaXZl\r\ncnk8YnI+PGJyPi0gdW4gYm91dG9uIGRlIHJlcG9ydCBkZSBidWcgc2ltcGxlIC8gZmVlZGJhY2s8\r\nL2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2Pi0gQ3J5cHRvID88YnI+PGJyPkNvbW11bmlxdWVyIGNs\r\nYWlyZW1lbnQgc3VyIGxlIGZhaXQgcXVlIGNlIHNvaXQgdW5lIHZlcnNpb24gYWxwaGEuPGJyPjxi\r\ncj48YnI+IyMgT3JnYW5pc2F0aW9uIGRlcyBkw6l2ZWxvcHBlbWVudHM8YnI+PGJyPi0gUXdhbnQg\r\nYSBkw6l2ZWxvcHDDqSB1biBvdXRpbCBwb3VyIGZhaXJlIGR1IEthbmJhbiBpbnTDqWdyw6kmbmJz\r\ncDtzdXIgbGVzIEFQSSBkZSBHaXRodWIgKGNvdXBsYWdlIG1pbGVzdG9uZSBwYXIgZXgpPGJyPjxi\r\ncj4tIETDqWZpbmlyIHVuIHByb2ZpbCBmcm9udCBwb3VyIHJlY2hlcmNoZSBkZSByZXNzb3VyY2Vz\r\nIGPDtHTDqSBRd2FudDxicj4mbmJzcDsmbmJzcDsmbmJzcDstJmd0OyBFbWJlciBlc3QgbGUgY3Jp\r\ndMOocmUgcHJpbmNpcGFsPGJyPiZuYnNwOyZuYnNwOyZuYnNwOy0mZ3Q7IERvbWFpbmVzIGZvbmN0\r\naW9ubmVscyBnZXN0aW9uIGRlIGNvbnRhY3RzLCBkZSBtZXNzYWdlczxicj48YnI+LSBUcmF2YWls\r\nbGVyIHN1ciB1bmUgdmVyc2lvbiByZXNwb25zaXZlIHdlYiBkZXNpZ24gKFJXRCkgw6AgbWluaW1h\r\nIHBvdXIgbCdhbHBoYTxicj4mbmJzcDsmbmJzcDsmbmJzcDstJmd0OyBjaG9peCBkdSBmcmFtZXdv\r\ncmsgY3NzIGVuY29yZSDDoCBmYWlyZSwgc2Vsb24gbGVzIGNvbnRyYWludGVzPGJyPiZuYnNwOyZu\r\nYnNwOyZuYnNwOy0mZ3Q7IG5lIHBhcyBmYWlyZSBkZSByZXNwb25zaXZlIGF1IGTDqWJ1dCByZW5k\r\ncyBkaWZmaWNpbGU8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7ZCdpbnTDqWdyZXIgcGx1cyB0YXJkPGJy\r\nPiZuYnNwOyZuYnNwOyZuYnNwOy0mZ3Q7IGZhaXJlIGR1IHJlc3BvbnNpdmUgc2FucyB2cmFpbWVu\r\ndCBzJ2VuIGlucXVpw6l0ZXIgY2Ugbidlc3Q8YnI+Jm5ic3A7Jm5ic3A7Jm5ic3A7cGFzIGJvbiwg\r\naWwgZmF1dCBnw6lyZXIgbGUgcmVzcG9uc2l2ZSB0b3V0IGF1IGxvbmcgZHUgcHJvamV0PGJyPiZu\r\nYnNwOyZuYnNwOyZuYnNwOy0mZ3Q7IG1ldHRyZSBxdWVscXXigJl1biByZXNwb25zYWJsZSBkZSBj\r\nZSBwb2ludDxicj48YnI+PGJyPiMjIEF1dHJlcyBwb2ludHM8YnI+PGJyPi0gRmFpcmUgdW5lIGxp\r\nc3RlIGRlcyBjaG9peCB0ZWNobm9sb2dpcXVlczwvZGl2PjxkaXY+PGJyPi0gQWpvdXQgZCd1biBD\r\nT05UUklCVVRJTkc8YnI+Jm5ic3A7ICZuYnNwOy0mZ3Q7IE91dmVydHVyZSBk4oCZdW5lIHBhZ2Ug\r\nPGEgaHJlZj0iaHR0cDovL2NhbGlvcGVuLmdpdGh1Yi5pbyIgcmVsPSJub2ZvbGxvdyIgdGFyZ2V0\r\nPSJfYmxhbmsiPmh0dHA6Ly9jYWxpb3Blbi5naXRodWIuaW88L2E+PGJyPjxicj4tIFZpbmNpIHBy\r\nw6lzZW50ZXJhIGxlIHByb2pldCBhdSBDQ0M8L2Rpdj48ZGl2Pjxicj48YnI+IyMgRMOpZmluaXIg\r\ndW5lIHN0cmF0w6lnaWUgZCdhbHBoYTxicj48YnI+LSBTb3BoaWUgZmVyYSB1biB2ZXJiYXRpbSBk\r\nJ3VuZSBjb21tdW5pY2F0aW9uIHF1ZSBs4oCZb24mbmJzcDthZHJlc3NlcmFpdCBhdXggY29udGFj\r\ndHMgY2hvaXNpcyBwb3VyIGzigJlhbHBoYS48L2Rpdj48ZGl2Pi0gQ29tbWVuY2VyIMOgIGNvbnN0\r\naXR1ZXIgdW5lIGxpc3RlIGTigJlhbHBoYSB0ZXN0ZXVyIMOgIHBhcnRpciBkZSBs4oCZZW50b3Vy\r\nYWdlIGRlIGNoYWN1biwgcG91ciBhbnRpY2lwZXIgbGUgbm9tYnJlIGRlIHJldG91cnMsIGV0Yy48\r\nYnI+PGJyPjxicj4jIyBOZXh0IHN0ZXBzPGJyPjxicj4tIFByw6l2b2lyIGRlcyByw6l1bmlvbnMg\r\ncGx1cyB0aMOpbWF0aXF1ZXMgcGFyIGdyb3VwZSBkZSB0cmF2YWlsPC9kaXY+PGRpdj4mbmJzcDsg\r\nJm5ic3A7LSZndDsgQmFja2VuZCwgbGVhZMOpIHBhciBBeW1lcmljPC9kaXY+PGRpdj4mbmJzcDsg\r\nJm5ic3A7LSZndDsgRnJvbnQgLyBVWC1VSSwgbGVhZMOpIHBhciBUaG9tYXM8L2Rpdj48ZGl2PiZu\r\nYnNwOyAmbmJzcDstJmd0OyBEZXYgSlMgKEVtYmVyKSwgbGVhZMOpIHBhciBKdWxpZW48YnI+PGRp\r\ndj4NCjxkaXY+PGJyPjwvZGl2Pg0KPC9kaXY+DQo8YnI+PC9kaXY+PC9kaXY+PC9zcGFuPjxicj48\r\nYnI+\r\n\r\n\r\n--===============1292634308==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1292634308==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Compte rendu reunion 21 Novembre - Laurent Chemla <laurent@brainstorm.fr> - 2014-11-22 1046.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 82090120AEA;\r\n\tSat, 22 Nov 2014 10:47:20 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.324\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.324 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74, FUZZY_AMBIEN=0.416]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id DooBOLJ8jwcP; Sat, 22 Nov 2014 10:47:18 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 2A01F120AE8;\r\n\tSat, 22 Nov 2014 10:47:00 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id E1A0A69781C85;\r\n\tSat, 22 Nov 2014 10:46:56 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id qRhk6fjn1ALc; Sat, 22 Nov 2014 10:46:53 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 5CF9A6977D454;\r\n\tSat, 22 Nov 2014 10:46:52 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 792E36977F9C5\r\n for <caliopdev@caliop.net>; Sat, 22 Nov 2014 10:46:51 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id ZwhkHpp79oki for <caliopdev@caliop.net>;\r\n Sat, 22 Nov 2014 10:46:46 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 94D406977D454\r\n for <caliopdev@caliop.net>; Sat, 22 Nov 2014 10:46:46 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 77356209B3BF7; Sat, 22 Nov 2014 10:46:46 +0100 (CET)\r\nDate: Sat, 22 Nov 2014 10:46:46 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141122094646.GA8770@brasil.brainstorm.fr>\r\nReferences: <20141121112137.GA15318@tao.gandi.net>\r\nMIME-Version: 1.0\r\nIn-Reply-To: <20141121112137.GA15318@tao.gandi.net>\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: Re: [Caliopdev] Compte rendu reunion 21 Novembre\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0262745348==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n--===============0262745348==\r\nContent-Type: multipart/signed; micalg=pgp-sha256;\r\n\tprotocol=\"application/pgp-signature\"; boundary=\"Y/WcH0a6A93yCHGr\"\r\nContent-Disposition: inline\r\n\r\n\r\n--Y/WcH0a6A93yCHGr\r\nContent-Type: text/plain; charset=iso-8859-1\r\nContent-Disposition: inline\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n\r\nAjout au CR sur la r=E9union avec Qwant (Eric Leandri et Guillaume\r\nVassault-Houli=E8re.\r\n\r\nArriv=E9s avec 30mn de retard, rest=E9 =E0 peine une 30aine de minutes:\r\nGuillaume enthousiaste sur le projet, Eric... =E9trange mais on me\r\ndit que c'est son attitude normale. Il consid=E8re qu'ils financent\r\ntout un tas de projets inutiles, alors que Caliopen est utile, bien\r\npens=E9, et peut r=E9pondre =E0 ses besoins un jour tout en correspondant\r\n=E0 l'=E9thique de la boite.\r\n\r\nSes besoins: disposer d'un syst=E8me de messagerie pour proposer un\r\nservice et pouvoir plugger son API de pub (ne reposant pas sur les\r\ndonn=E9es priv=E9es). Deadline il a 6 mois, n'ont toujours rien =E0 part\r\nun webmail gmail-like non s=E9curis=E9 qui n'a aucun succ=E8s.\r\n\r\nDonc conclusion rapide: se dit pr=EAt =E0 financer une alpha mais veut\r\nque Guillaume et ses devs viennent =E0 la prochaine r=E9u pour pouvoir\r\n=E9valuer le boulot =E0 r=E9aliser, et estimer les besoins financiers.\r\n\r\nSes devs ne sont pas libres parce qu'il consid=E8re (haha) que m=EAme\r\ns'il les met sur Caliopen leurs projets internes finiront par les\r\nrattraper avant qu'ils aient pu commencer. Donc ils viennent juste\r\npour =E9valuer, pour voir =E0 combien =E7a reviendrait de financer des\r\nprestas externes.\r\n\r\nEric sera pas en France le 5/12 (date de la prochaine r=E9union), il\r\npourra participer, du Br=E9sil, via Skype ou autre\r\n\r\nVoil=E0. Bon. On verra.\r\n\r\n--Y/WcH0a6A93yCHGr\r\nContent-Type: application/pgp-signature; name=\"signature.asc\"\r\nContent-Description: Digital signature\r\nContent-Disposition: inline\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nVersion: GnuPG v1.4.10 (GNU/Linux)\r\n\r\niQIcBAEBCAAGBQJUcFuGAAoJEDDctlh2zQ8hoN0P/24hl4Bn/DHP/j2vVYp4zogA\r\nn9YB9EOcx0Vz/HpAnMRDyVnqr2cfWjmzt3EsQm3CGKzWc4aAYq8QBGGpLuy1jJz2\r\nRPR8iGybREvEwBYhq+Y4n4PXZoEg2lv6R/Cq3ZdUPmIHJIbpp4X8IPHUvqH/3dDS\r\nBs6tC0fcZou+yhO1/GPTP0wYIiujxyyC0GNb4thhU9vnMSpiV7GYGmUn/7gG5ppi\r\nEQc1+ZWWU4OB5WyZIV9uN21YqsYsIood5pgou0uKW8QHlcpq3k8rY2hwC9ShooCz\r\nHFHfxnYW/Il783Ag28wpt3nn/WNIOdnNt1v+ksCmywY7+wMTyUuY0Kp5rd3MvtO1\r\nvFhfu4/aRkDEXMMpCStZzyw7LqDl/cdzGBhAV90Jt6UQWih32IJw6W63dRAQDkGs\r\nvYp6ZXgRzSM+9rqaYGs5kryLYYa5vJ4wBFYJtfy+8Ti+1ozdzhKttgZ5v3XWqhKY\r\n6VES+jYH7ymJEHpohXRTbh1FrRgJZuNxiJTm2D9tmicatdAxkqwxkbMwgA4e3PCR\r\nI0vA5gE2AoVxQd4kujGCggV/jUERZkJkjTwPlEIxce6xpCbG9TIzXh6dwRALRSae\r\n7Pu/yKwdPfNC21JKCfLtGjvfHSTrrdhJLauZtKIzOdidE1GGHJitCdu0gtE8kbn5\r\nh+Isi8DY/B9KCRFDV8zK\r\n=z1jS\r\n-----END PGP SIGNATURE-----\r\n\r\n--Y/WcH0a6A93yCHGr--\r\n\r\n--===============0262745348==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0262745348==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re:  matrice de choix framework JS - Aymeric Barantal <mric@chamal.fr> - 2014-11-26 1605.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 850F3120AD3;\r\n\tWed, 26 Nov 2014 16:05:04 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.581\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.581 tagged_above=-999 required=5\r\n\ttests=[BAYES_05=-1.11, RATWARE_GECKO_BUILD=1.691]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id PuLBrjOjZdol; Wed, 26 Nov 2014 16:05:02 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id C8885120ABE;\r\n\tWed, 26 Nov 2014 16:05:02 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 33FDE69DCFB43;\r\n\tWed, 26 Nov 2014 16:05:02 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id xSUT27hcVPDb; Wed, 26 Nov 2014 16:04:58 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 34F9369DCFB40;\r\n\tWed, 26 Nov 2014 16:04:58 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 7859369DCFB40\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 16:04:57 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id Qflc7y4nmksg for <caliopdev@caliop.net>;\r\n Wed, 26 Nov 2014 16:04:53 +0100 (CET)\r\nReceived: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net\r\n [217.70.183.196])\r\n (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 8787B69DCFB3F\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 16:04:53 +0100 (CET)\r\nReceived: from [IPv6:2001:4b98:beef:a:3d66:581c:b2c0:5404] (unknown\r\n [IPv6:2001:4b98:beef:a:3d66:581c:b2c0:5404])\r\n (Authenticated sender: mric@chamal.fr)\r\n by relay4-d.mail.gandi.net (Postfix) with ESMTPSA id 5817C172055\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 16:04:53 +0100 (CET)\r\nMessage-ID: <5475EC1E.4010603@chamal.fr>\r\nDate: Wed, 26 Nov 2014 16:05:02 +0100\r\nFrom: Aymeric Barantal <mric@chamal.fr>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: caliopdev@caliop.net\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\nIn-Reply-To: <547457A9.9070801@chamal.fr>\r\nSubject: Re: [Caliopdev] Fwd: Re:  matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=\"iso-8859-1\"; Format=\"flowed\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n[..]\r\n\r\nEn attendant les reponses sur le remplissage de la matrice (ou\r\nla difficulte a la comprendre), voici un peu de lecture.\r\n\r\nhttp://www.slideshare.net/rmsguhan/react-meetup-mailonreact\r\n\r\n++ chamal\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re:  matrice de choix framework JS - Laurent Chemla <laurent@brainstorm.fr> - 2014-11-26 2346.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 0B1CA120ABE;\r\n\tWed, 26 Nov 2014 23:46:12 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.185\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.185 tagged_above=-999 required=5\r\n\ttests=[BAYES_40=-0.185]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id l4KLFSgnuGQv; Wed, 26 Nov 2014 23:46:10 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id AEDE1120A33;\r\n\tWed, 26 Nov 2014 23:46:10 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 7674469EA9071;\r\n\tWed, 26 Nov 2014 23:46:10 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id CHe9x0tBjqF5; Wed, 26 Nov 2014 23:46:09 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id D213A69EA906C;\r\n\tWed, 26 Nov 2014 23:46:08 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 900A069EA906C\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 23:46:07 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id gLq_SJ_P_7WV for <caliopdev@caliop.net>;\r\n Wed, 26 Nov 2014 23:46:06 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 4590A69EA906D\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 23:46:06 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 28CC820CD1174; Wed, 26 Nov 2014 23:46:06 +0100 (CET)\r\nDate: Wed, 26 Nov 2014 23:46:06 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141126224605.GH8770@brasil.brainstorm.fr>\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nIn-Reply-To: <5475EC1E.4010603@chamal.fr>\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: Re: [Caliopdev] Fwd: Re:  matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n> http://www.slideshare.net/rmsguhan/react-meetup-mailonreact\r\n\r\nLe d=E9bat =E0 ce sujet sur Hacker News est assez int=E9ressant:\r\n\r\n https://news.ycombinator.com/item?id=3D8662348\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re:  matrice de choix framework JS - julien muetton <julien.muetton@gandi.net> - 2014-11-28 1701.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 86A61120AC2;\r\n\tFri, 28 Nov 2014 17:00:54 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -100.907\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-100.907 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, HTML_MESSAGE=0.001, RATWARE_GECKO_BUILD=1.691,\r\n\tUSER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id B2YVaNH3k2oC; Fri, 28 Nov 2014 17:00:52 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 07806120A19;\r\n\tFri, 28 Nov 2014 17:00:52 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id D8B2D6B3F1257;\r\n\tFri, 28 Nov 2014 17:00:51 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id L_Yl7oet0zm7; Fri, 28 Nov 2014 17:00:50 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 760326AAC5AC2;\r\n\tFri, 28 Nov 2014 17:00:50 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 993056B386732\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:00:48 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id VX7VM8U1X77c for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 17:00:47 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id 872FA6AAC5AC2\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:00:47 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id 6031F120AD9\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:00:47 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id aSSqUZfLj3AN for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 17:00:45 +0100 (CET)\r\nReceived: from [10.242.2.58] (corp-ras.gandi.net [217.70.181.25])\r\n (using TLSv1 with cipher DHE-RSA-AES128-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by mail4.gandi.net (Postfix) with ESMTPSA id 3DE3E120A19\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:00:45 +0100 (CET)\r\nMessage-ID: <54789C63.8050606@gandi.net>\r\nDate: Fri, 28 Nov 2014 17:01:39 +0100\r\nFrom: julien muetton <dev@caliopen.local>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Icedove/31.2.0\r\nMIME-Version: 1.0\r\nTo: caliopdev@caliop.net\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\nIn-Reply-To: <547457A9.9070801@chamal.fr>\r\nContent-Type: multipart/mixed; boundary=\"------------070107090202040807080200\"\r\nSubject: Re: [Caliopdev] Fwd: Re:  matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------070107090202040807080200\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------010007040905010201030503\"\r\n\r\n\r\n--------------010007040905010201030503\r\nContent-Type: text/plain; charset=windows-1252; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\nJ'ai rempli la matrice.\r\n\r\nJe ne suis pas sur d'avoir saisi les points spec:*\r\nAMHA on a toujours la libert� d'utiliser une lib qui va bien pour \r\nr�pondre � un probl�me, sinon on risque de rencontrer beaucoup de choses \r\nqui ressemblent � un clou...\r\n\r\nPour la partie s�cu, dans le front, on ne peut pas acc�der aux donn�es \r\nque l'api ne renvoie pas, par contre tout le reste est accessible.\r\n\r\nJulien\r\n\r\nOn 11/25/2014 11:19 AM, Aymeric Barantal wrote:\r\n>\r\n> Rate sur le To, groumpfff\r\n>\r\n>\r\n> -------- Forwarded Message --------\r\n> Subject: \tRe: [Caliopdev] matrice de choix framework JS\r\n> Date: \tTue, 25 Nov 2014 11:02:22 +0100\r\n> From: \tAymeric Barantal <mric@chamal.fr>\r\n> To: \tDavid Epely <dey@protonmail.ch>\r\n>\r\n>\r\n>\r\n> On 11/24/2014 03:17 PM, David Epely wrote:\r\n> > bon j'ai rempli vite fait ce que je sais ... pas trop le temps, je\r\n> > m'occupe d'un nouveau n� l� tout de suite.\r\n> >\r\n> > (mais toujours envie de m'impliquer)\r\n>\r\n> Cool, merci ;)\r\n>\r\n> Voici la mienne.\r\n>\r\n> ++ chamal\r\n>\r\n>\r\n>\r\n>\r\n>\r\n>\r\n>\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n\r\n--------------010007040905010201030503\r\nContent-Type: text/html; charset=windows-1252\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=windows-1252\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    J'ai rempli la matrice.<br>\r\n    <br>\r\n    Je ne suis pas sur d'avoir saisi les points spec:*<br>\r\n    AMHA on a toujours la libert� d'utiliser une lib qui va bien pour\r\n    r�pondre � un probl�me, sinon on risque de rencontrer beaucoup de\r\n    choses qui ressemblent � un clou...<br>\r\n    <br>\r\n    Pour la partie s�cu, dans le front, on ne peut pas acc�der aux\r\n    donn�es que l'api ne renvoie pas, par contre tout le reste est\r\n    accessible.<br>\r\n    <br>\r\n    Julien<br>\r\n    <br>\r\n    <div class=\"moz-cite-prefix\">On 11/25/2014 11:19 AM, Aymeric\r\n      Barantal wrote:<br>\r\n    </div>\r\n    <blockquote cite=\"mid:547457A9.9070801@chamal.fr\" type=\"cite\">\r\n      <meta http-equiv=\"content-type\" content=\"text/html;\r\n        charset=windows-1252\">\r\n      <br>\r\n      <div class=\"moz-forward-container\">Rate sur le To, groumpfff<br>\r\n        <br>\r\n        <br>\r\n        -------- Forwarded Message --------\r\n        <table class=\"moz-email-headers-table\" border=\"0\"\r\n          cellpadding=\"0\" cellspacing=\"0\">\r\n          <tbody>\r\n            <tr>\r\n              <th align=\"RIGHT\" nowrap=\"nowrap\" valign=\"BASELINE\">Subject:\r\n\r\n              </th>\r\n              <td>Re: [Caliopdev] matrice de choix framework JS</td>\r\n            </tr>\r\n            <tr>\r\n              <th align=\"RIGHT\" nowrap=\"nowrap\" valign=\"BASELINE\">Date:\r\n              </th>\r\n              <td>Tue, 25 Nov 2014 11:02:22 +0100</td>\r\n            </tr>\r\n            <tr>\r\n              <th align=\"RIGHT\" nowrap=\"nowrap\" valign=\"BASELINE\">From:\r\n              </th>\r\n              <td>Aymeric Barantal <a moz-do-not-send=\"true\"\r\n                  class=\"moz-txt-link-rfc2396E\"\r\n                  href=\"mailto:mric@chamal.fr\">&lt;mric@chamal.fr&gt;</a></td>\r\n            </tr>\r\n            <tr>\r\n              <th align=\"RIGHT\" nowrap=\"nowrap\" valign=\"BASELINE\">To: </th>\r\n              <td>David Epely <a moz-do-not-send=\"true\"\r\n                  class=\"moz-txt-link-rfc2396E\"\r\n                  href=\"mailto:dey@protonmail.ch\">&lt;dey@protonmail.ch&gt;</a></td>\r\n            </tr>\r\n          </tbody>\r\n        </table>\r\n        <br>\r\n        <br>\r\n        <pre>On 11/24/2014 03:17 PM, David Epely wrote:\r\n&gt; bon j'ai rempli vite fait ce que je sais ... pas trop le temps, je \r\n&gt; m'occupe d'un nouveau n� l� tout de suite.\r\n&gt;\r\n&gt; (mais toujours envie de m'impliquer)\r\n\r\nCool, merci ;)\r\n\r\nVoici la mienne.\r\n\r\n++ chamal\r\n\r\n\r\n\r\n</pre>\r\n        <br>\r\n      </div>\r\n      <br>\r\n      <br>\r\n      <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n      <br>\r\n      <pre wrap=\"\">_______________________________________________\r\nCaliopdev mailing list\r\n<a class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n    </blockquote>\r\n    <br>\r\n  </body>\r\n</html>\r\n\r\n--------------010007040905010201030503--\r\n\r\n--------------070107090202040807080200\r\nContent-Type: text/csv;\r\n name=\"matrice-themouette.csv\"\r\nContent-Transfer-Encoding: 8bit\r\nContent-Disposition: attachment;\r\n filename=\"matrice-themouette.csv\"\r\n\r\n,,,,,,,,,,,,,,,,,,,,\r\n,3,3,3,3,3,3,,,,,,,,,,,,,,\r\nFramework,Structure,Stabilité,User base,Specs : réseau ,Specs : secu,Specs : api,perfs,,Total,,,,,,,,,,,\r\nAngular,3,1,5,4,X,4,3,,=sum(B4:I4),,,,,,,,,,,\r\nReact + Fluxx,2,3,4,??,X,??,4,,,,,,,,,,,,,\r\nEmber,5,4,4,4,X,4,3,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\nex,3,1,2,3,1,2,,,=sum(cellReference(&quot;c1a2e60b-2d2e-4674-965f-434454f7a01b&quot;, &quot;995b7a79-e433-41fd-82e2-5750ac69e1f5&quot;, &quot;ff8615a5-d987-4692-b4bc-1f4d9f28ec6c&quot;):cellReference(&quot;c1a2e60b-2d2e-4674-965f-434454f7a01b&quot;, &quot;995b7a79-e433-41fd-82e2-5750ac69e1f5&quot;, &quot;056cae5e-ad3f-4919-a122-85b2611d02be&quot;)),,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n??,On utilise la librairie que l&#39;on veut, il y a forcément quelque chose qui colle au besoin,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\nX,Pas de notion de sécurité côté front, l&#39;api s&#39;en charge.,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n,,,,,,,,,,,,,,,,,,,,\r\n\r\n\r\n--------------070107090202040807080200\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--------------070107090202040807080200--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - Alban Crommer <alban@albancrommer.com> - 2014-11-27 1007.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id C8CFE120AAC;\r\n\tThu, 27 Nov 2014 10:07:40 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 1.507\r\nX-Spam-Level: *\r\nX-Spam-Status: No, score=1.507 tagged_above=-999 required=5\r\n\ttests=[BAYES_40=-0.185, HTML_MESSAGE=0.001, RATWARE_GECKO_BUILD=1.691]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id Dw9gO6P3z4Cj; Thu, 27 Nov 2014 10:07:38 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 6256C120A27;\r\n\tThu, 27 Nov 2014 10:07:38 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 2F70569F25FCB;\r\n\tThu, 27 Nov 2014 10:07:38 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=albancrommer.com header.i=@albancrommer.com\r\n\theader.b=gmBOgAAZ; dkim-adsp=none (insecure policy);\r\n\tdkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id RId7jMhFlMey; Thu, 27 Nov 2014 10:07:34 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id C9EE969F0479A;\r\n\tThu, 27 Nov 2014 10:07:33 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id F27D869F0479A\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 10:07:31 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id RTKsUV0flgjS for <caliopdev@caliop.net>;\r\n Thu, 27 Nov 2014 10:07:28 +0100 (CET)\r\nReceived: from alban.octopuce.fr (alban.octopuce.fr [91.194.60.111])\r\n by brassens.heberge.info (Postfix) with ESMTP id EEA7E69F25FC5\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 10:07:27 +0100 (CET)\r\nReceived: from [IPv6:2001:67c:288:1::201] (unknown [IPv6:2001:67c:288:1::201])\r\n (using TLSv1 with cipher ECDHE-RSA-AES128-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by alban.octopuce.fr (Postfix) with ESMTPSA id BEE32F1CC56A\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 10:07:27 +0100 (CET)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=albancrommer.com;\r\n s=alternc; t=1417079247;\r\n bh=204ytZf8IOY9VMXeTPeDENQ35RmOa3v8XthXeFRpKYk=;\r\n h=Date:From:To:Subject:References:In-Reply-To;\r\n b=gmBOgAAZDk50HzqoDsoH/r1BdEHNkV0/FnoohKBmsupps/hxdKFDSPEyueXremctm\r\n XmXRXmTl6zmwfzvdC+b6Ov1RhTqGe1XhAZtVPaPDq0F0qqg8tkb6Nd9H/smFPoFgF9\r\n Zs/jVwAXW1UECdEL7HbZ6xaBp/XEw0ZPqzx+P4J0=\r\nMessage-ID: <5476E9CF.70601@albancrommer.com>\r\nDate: Thu, 27 Nov 2014 10:07:27 +0100\r\nFrom: Alban Crommer <alban@albancrommer.com>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:24.0) Gecko/20100101 Icedove/24.8.1\r\nMIME-Version: 1.0\r\nTo: =?ISO-8859-1?Q?Liste_de_d=E9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\nIn-Reply-To: <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\nX-Enigmail-Version: 1.6\r\nContent-Type: multipart/mixed; boundary=\"------------070104090908020006080706\"\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------070104090908020006080706\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------000301070809050108050107\"\r\n\r\n\r\n--------------000301070809050108050107\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Transfer-Encoding: 8bit\r\n\r\nBonjour,\r\n\r\nD�sol�, je ne connais pas suffisamment ces 3 frameworks pour avoir un\r\navis �clairant sur la question.\r\n\r\nMais je suis content que �a puisse servir, et rassur� de voir des\r\nr�sultats vraisemblablement s�rieux et pointant dans la m�me direction ;)\r\n\r\n+a\r\n\r\nLe 27/11/2014 01:32, Kajan Sivaramalingam a �crit :\r\n> Voici ma contribution.\r\n>\r\n>\r\n> A bient�t pour la d�lib�ration.\r\n>\r\n>\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n\r\n--------------000301070809050108050107\r\nContent-Type: text/html; charset=ISO-8859-1\r\nContent-Transfer-Encoding: 7bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=ISO-8859-1\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    <div class=\"moz-cite-prefix\">Bonjour,<br>\r\n      <br>\r\n      D&eacute;sol&eacute;, je ne connais pas suffisamment ces 3 frameworks pour avoir\r\n      un avis &eacute;clairant sur la question.<br>\r\n      <br>\r\n      Mais je suis content que &ccedil;a puisse servir, et rassur&eacute; de voir des\r\n      r&eacute;sultats vraisemblablement s&eacute;rieux et pointant dans la m&ecirc;me\r\n      direction ;)<br>\r\n      <br>\r\n      +a<br>\r\n      <br>\r\n      Le 27/11/2014 01:32, Kajan Sivaramalingam a &eacute;crit&nbsp;:<br>\r\n    </div>\r\n    <blockquote\r\ncite=\"mid:CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com\"\r\n      type=\"cite\">\r\n      <meta http-equiv=\"Context-Type\" content=\"text/html; charset=UTF-8\">\r\n      <div dir=\"ltr\">\r\n        <div class=\"gmail_extra\">Voici ma contribution.</div>\r\n        <div class=\"gmail_extra\"><br>\r\n        </div>\r\n        <div class=\"gmail_extra\"><br>\r\n        </div>\r\n        <div class=\"gmail_extra\">A bient&ocirc;t pour la d&eacute;lib&eacute;ration.</div>\r\n      </div>\r\n      <br>\r\n      <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n      <br>\r\n      <pre wrap=\"\">_______________________________________________\r\nCaliopdev mailing list\r\n<a class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n    </blockquote>\r\n    <br>\r\n  </body>\r\n</html>\r\n\r\n--------------000301070809050108050107--\r\n\r\n--------------070104090908020006080706\r\nContent-Type: application/pgp-keys;\r\n name=\"0xED3CE492.asc\"\r\nContent-Transfer-Encoding: 7bit\r\nContent-Disposition: attachment;\r\n filename=\"0xED3CE492.asc\"\r\n\r\n-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: GnuPG v1.4.12 (GNU/Linux)\r\n\r\nmQENBFIKYNABCACqou6YtfXUjpnZRVarYRkFiFcH8nQm7NHY4nutuXOCs8fTDzwM\r\nR6EZMjlpmdFjg9TemwHWhkho3qbwJP+puZU6yMT3rnehkEYRVkQtvIbTnhQesLhu\r\nCTM9ge1YKdkvL5mAgYMOgB14CYtXseKIUzefPy/pyO2Nf5t57Z87EctdpLPaSfNw\r\nqVNpKLN5Psy7ffcCbDsdwIhPMMN9S3EdpKLpvPCli7T2m87EM5xzGuGFVP4Gbtzw\r\nqmgleA9lH6lpBQ3LOc9SkdPNPvBZGfq1exCVoc133AFEIFlZMCVK2pGtAX2fgVGC\r\nOqAhwsvlAsDiC1g32NkLni3lPmwUeoybDXwvABEBAAG0JkFsYmFuIENyb21tZXIg\r\nPGFsYmFuQGFsYmFuY3JvbW1lci5jb20+iQE+BBMBAgAoBQJSCmDQAhsjBQkJZgGA\r\nBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDna7sG7TzkkiJSB/4+k8lOHHXI\r\n/ItzQUZBjDu4Xy9O/PCtP6KwIvd5LQNiIBvZ0olVjTLX+i1Rkn7ljQDOpXTZNTaW\r\n3ktuIGGJGBPVQeSxzDuC0K3VSGi6aRsQRF+1sVW7oA6pImT1jojrnraT+rW7KsP8\r\nR+tyCi25wiuN4bKNq6Ft0DLtuHN1w5cfabYlWtPhzIMjnR7gal2MXG+ovLJGeJ15\r\nzypwSWB4PvvadJGRDJ1U+IsdsgiDylL9k29GC9gB2+pYWCSIUxrCfhXXsSQDndN2\r\nMZ2q7rfhdE4mHZlBGZEOKTJdpkWkgVPkMxMUKMboRRDLzoihJ6kNsow14GPmFqy8\r\nhBS0M6cKxDMaiEYEExEIAAYFAlI8NZQACgkQd5FD2Z8azpzXuACg5keREAfyyBcL\r\ng0db9EeXKEDvWggAn2atyen5XuqV033ssCz9G/SPmiswiQEcBBMBCAAGBQJSPDWf\r\nAAoJEP6CnA9YYHPmgegIAIPx+fcv8jiKh4v8OgGWhYwUzfrwSUS7Fv/UUb9AhL3i\r\nvK1t2V6D0+gx+gVjL/8ZzzVPlqZ+50z8yvClbhjKK08P6ijGpA++JMaNyf1I2gGi\r\nk37/3jcOgTcLGDuG64jXbrDgDGySGWgAHOCtArjB3OQkVZfdgb2nDh39TZK7oPTY\r\nlF8AuCCbfd2Mxohh2oCJSp8I47WElcePlTXogeS1P09OO+mtl2onSW7K9MtE/7sp\r\nUqADJF03PamqY3lzxAWxCiS8YMuEBSn5eAw18lOis6fObmJ0GMqNPAqkjysHWNtN\r\n4qceASvAOHoSWEEnFhGIWDlH510np3RdttTvpe25XTe5AQ0EUgpg0AEIAMaUKd36\r\nEhDzA3bAW5Ke6m6AQqotrHhSBr5ypAwl6JRlbSBdK8UPiHzLqnJBKlET1sHc8dRq\r\nlMnGBPaC8Phd4Eoi7LiA5TF2RjJryrmh/BEnxw5DNQ1gIoXihOMNEa8BueUHPuLw\r\n6Jr6nEocRQ00qjrxBNioVWXwy8q+MnoMrd/ZJGYwSHr0dx9jATHdEydKE2mVtFia\r\nGAdOQ2glNcoJS574GMYRX0DBb5y0p0xV2JRjG7UH/62vDJjrVIRUe5gTOduw95Cz\r\nbKtoByMmyesrVh2OeCuta04OobsvDILUQeKcoJwMJF7511/DtK+rPU0p9Fn7vbdG\r\nf7qmGZG9ZyU0oY8AEQEAAYkBJQQYAQIADwUCUgpg0AIbDAUJCWYBgAAKCRDna7sG\r\n7Tzkkml3B/9tlGNY6Lsh2T0cOosnTu0WBTCMnUQiiGsTHnUq2R7T7sppO7DdH0n0\r\nRgRf+RRFh5bf3nuoNznDW4fYIihl2WjAJtgu4fIupm5uteKB/tZ1M4b37MpNalsH\r\nZ9GuncOl8zrcWCH8Mx4O3V6L1EM3QWmONw7Cqa35peEmODFg/bvmp0jyXQT0w8G3\r\nc4RVBVaK6+C1bRBCxugHrCEsQT6GjAcknEwpjK89Pdb/bgxenx/+V8ZHe1XQKuZI\r\n0EO4YEmJr3K/9OVic6CoV5dKJm1KlJB3ftE21y3upib8FkCFM8k0wnd/LOpztDs1\r\nMXWgD+JLmfNKbhzzf547qhkjPqSPDxpk\r\n=64CM\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n--------------070104090908020006080706\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--------------070104090908020006080706--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - Alban Crommer <alban@albancrommer.com> - 2014-11-28 1625.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 60595120AEE;\r\n\tFri, 28 Nov 2014 16:25:31 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 1.448\r\nX-Spam-Level: *\r\nX-Spam-Status: No, score=1.448 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74, HTML_40_50=0.496, HTML_MESSAGE=0.001,\r\n\tRATWARE_GECKO_BUILD=1.691]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id lLzW+UjymByn; Fri, 28 Nov 2014 16:25:29 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 0176E120A20;\r\n\tFri, 28 Nov 2014 16:25:29 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 767346AACB839;\r\n\tFri, 28 Nov 2014 16:25:28 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=albancrommer.com header.i=@albancrommer.com\r\n\theader.b=dryFrpum; dkim-adsp=none (insecure policy);\r\n\tdkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id USnepjzrsBTY; Fri, 28 Nov 2014 16:25:27 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 89D696AACB836;\r\n\tFri, 28 Nov 2014 16:25:26 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 7D4336AACB838\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:25:25 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id cZNUGob-MMZn for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 16:25:19 +0100 (CET)\r\nReceived: from alban.octopuce.fr (alban.octopuce.fr [91.194.60.111])\r\n by brassens.heberge.info (Postfix) with ESMTP id 40C3B6AACB835\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:25:19 +0100 (CET)\r\nReceived: from [IPv6:2001:67c:288:1::201] (unknown [IPv6:2001:67c:288:1::201])\r\n (using TLSv1 with cipher ECDHE-RSA-AES128-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by alban.octopuce.fr (Postfix) with ESMTPSA id DF5DFF1CFECD\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:25:18 +0100 (CET)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=albancrommer.com;\r\n s=alternc; t=1417188319;\r\n bh=pXCUAcZprSaVoqK4KDdB/tyUG2aAbYiuXKj9b58yIb0=;\r\n h=Date:From:To:Subject:References:In-Reply-To;\r\n b=dryFrpumWqmtV/pPcQVFSuJJ7gzNnnrsiOVBTdFSB2Qzmd9sw28zQEAQS9kXhWU/H\r\n oEUXVv+aVbrG/M/7yEyX5rRuExm4AvtnFc6q/qKuOqDrOXbiiClmyM46Odsj3v1O+E\r\n IzItZX53+vvLogrRVYucpC8mjOA9bZUU7cvW/mD0=\r\nMessage-ID: <547893DE.20601@albancrommer.com>\r\nDate: Fri, 28 Nov 2014 16:25:18 +0100\r\nFrom: Alban Crommer <alban@albancrommer.com>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:24.0) Gecko/20100101 Icedove/24.8.1\r\nMIME-Version: 1.0\r\nTo: =?ISO-8859-1?Q?Liste_de_d=E9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\n <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\n <20141127192529.GA1819@tao.gandi.net> <54778C34.90802@gandi.net>\r\n <54784A99.3020306@chamal.fr> <54785B09.9010402@gandi.net>\r\n <54788421.1040800@gandi.net>\r\nIn-Reply-To: <54788421.1040800@gandi.net>\r\nX-Enigmail-Version: 1.6\r\nContent-Type: multipart/mixed; boundary=\"------------070709000702030804080801\"\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------070709000702030804080801\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------010906010708050009070702\"\r\n\r\n\r\n--------------010906010708050009070702\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Transfer-Encoding: 8bit\r\n\r\nJe trouve cette discussion fort int�ressante.\r\n\r\nN�anmoins, Aymeric a bien pr�cis� il me semble que l'architecture de\r\nCaliopen exclut Node et le JS server side. Il me semble d'ailleurs que\r\n�a fait partie des crit�res �nonc�s dans la matrice de choix ?\r\n\r\nJ'aimerai -personnellement- une architecture avec Python en API \"pure\"\r\n(ie. non HTML) r�utilisable comme une brique int�grable.\r\n\r\nAvec un serveur HTTPS en NodeJS isomorphe � la pointe pour servir un\r\nfrontend r�actif et r�silient.\r\n\r\nMais je comprends bien que �a ne soit pas optimal, car -encore plus-\r\nlong � r�aliser.\r\n\r\nNote/Rappel : est-ce que les d�vs cal�s en JS qui ont rejoint la\r\ndiscussion ont vu (et rempli) la matrice de choix de framework ?\r\n\r\n+a+\r\n\r\nLe 28/11/2014 15:18, Alexis a �crit :\r\n>\r\n> Le 28/11/2014 12:22, julien muetton a �crit :\r\n>>\r\n>> On 11/28/2014 11:12 AM, Aymeric Barantal wrote:\r\n>>> On 11/27/2014 09:40 PM, julien muetton wrote:\r\n>>>>\r\n>>>> On 11/27/2014 08:25 PM, Aymeric Barantal wrote:\r\n>>>>> On Thursday 27 Nov 2014 � 12:28:25 (+0100), joseph@cozycloud.cc wrote:\r\n>>>>>> Ton analyse et tes incertitudes sur React/Flux sont justifi�es. On a pris un\r\n>>>>>> risque de notre c�t� et heureusement les deux d�v ont fait un bon boulot de\r\n>>>>>> prise en main et d'adaptation.\r\n>>>>>>\r\n>>>>>> Pas de retour concernant Reflux, que je veux regarder en bloc et en d�tail car\r\n>>>>>> je ne suis pas satisfait de l'impl�mentation \"officielle\" de Flux, dont Fluxxor\r\n>>>>>> s'inspire fortement (on utilisait Fluxxor � la base, �a n'apporte rien AMHA,\r\n>>>>>> donc supprim�. Mais je n'ai pas regard� son �volution depuis plusieurs mois).\r\n>>>>>>\r\n>>>>>> Je n'avais pas pens� � la n�cessit� de Node pour utiliser React pour fair le\r\n>>>>>> rendu c�t� serveur, comme Caliopen est en Python (de m�moire) ce n'est\r\n>>>>>> effectivement pas un argument. Par contre, pour b�n�ficier de l'isomorphisme,\r\n>>>>>> il faut obligatoire du Node � un moment (� ma connaissance).\r\n>>>>> Alors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\n>>>>> sont bien moins importants [1] que les avantages que je vois a cette stricte\r\n>>>>> separation technologique entre front et api.\r\n>>>> Il est aussi possible d'avoir un serveur nodeJS qui sert les pages\r\n>>>> HTML et l'API en python.\r\n>>>> Un reverse proxy permet d'agr�ger les deux via des r�gles simples\r\n>>>> (HAProxy, Nginx, Apache...)\r\n>>> De quelles pages html est il question ? celles qui passent par\r\n>>> l'internationalisation ?\r\n>> L'isomorphisme signifie rendre indiff�remment sur le serveur et sur\r\n>> le client, comme twitter et wallmart.\r\n>>> J'ai du mal a voir le gain reel d'une plate-forme node.js a part du\r\n>>> code en commun, qui semble etre\r\n>>> la notion principale cachee derriere le terme d'isomorphisme.\r\n>> c'est pour avoir une meilleure exp�rience utilisateur.\r\n>> la premi�re page est affich�e directement, ce qui r�duit le \"time to\r\n>> first interaction\" (introduit par twitter comme \"time to tweet\")\r\n>>> A ce jour c'est le serveur fournissant l'api qui sait fournir\r\n>>> egalement des pages html (enfin une\r\n>>> seule du fait de l'utilisation d'angular sur le POC).\r\n>>>>\r\n>>>>     /api -> api python\r\n>>>>     cdn.domain.tld -> asset statics\r\n>>>>     default -> serveur nodeJS\r\n>>>>\r\n>>>> Ca colle bien avec l'approche �Service.\r\n>>> L'orientation globale n'est pas microservice cote API / backend\r\n>>>\r\n>>>>\r\n>>>> Maintenant, je ne sais pas � quel point il faut dupliquer la\r\n>>>> logique pour tirer partie de l'isomorphisme.\r\n>>>> Je regarde comment on consomme les donn�es sur le serveur\r\n>>>> (notamment est-ce que l'on passe par les stores).\r\n>>> La je suis perdu ;)\r\n>>>\r\n>> En rendant les pages sur le serveur, il faut aussi r�cup�rer les\r\n>> donn�es � travers l'API.\r\n>> Ce que je cherche � trouver c'est la quantit� de code � dupliquer,\r\n>> car consommer une API dans le browser passe par XMLHttpRequest, alors\r\n>> que sur le serveur XMLHttpRequest n'est pas dispo.\r\n>>\r\n>> La logique en Fluxx, sauf erreur de ma part, est de placer tout le\r\n>> code m�tier dans les Stores qui sont � la fois \"data mapper\" et\r\n>> logique domaine.\r\n>> Si c'est top pour ne pas coupler son code � un framework (on\r\n>> d�veloppe notre m�tier/domaine comme on l'entend), il y a forc�ment\r\n>> des diff�rences entre le client et le serveur.\r\n>\r\n> Par isomorphisme, moi j'entends que le code est le m�me est peut �tre\r\n> utilis� c�t� serveur ou c�t� client.\r\n> Donc pas de duplication, juste 1 code : 2 utilisations possibles.\r\n>\r\n> Browserify est de la partie bien sur pour faire tout �a.\r\n>\r\n> Tuto pr�sentant un m�me code qui s'ex�cute comme je disais Julien,\r\n> c�t� serveur pour le 1er load, c�t� client ensuite :\r\n> http://scotch.io/tutorials/javascript/build-a-real-time-twitter-stream-with-node-and-react-js\r\n>\r\n>>\r\n>> @Joseph, @romain, @brice, vous avez une visibilit� sur la quantit� de\r\n>> code � dupliquer ?\r\n>>> Quelle logique serait a dupliquer et finalement qu'est qui est\r\n>>> entendu par isomorphisme dans ce cadre, j'ai\r\n>>> l'impression de ne plus comprendre ce terme.\r\n>>>\r\n>>> De quels stores est il question ? Le local storage sera certainement\r\n>>> a exploiter s'il fait parti de cette definition.\r\n>>>\r\n>>> ++ chamal\r\n>>>\r\n>>>\r\n>>> _______________________________________________\r\n>>> Caliopdev mailing list\r\n>>> Caliopdev@caliop.net\r\n>>> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>>\r\n>>\r\n>>\r\n>> _______________________________________________\r\n>> Caliopdev mailing list\r\n>> Caliopdev@caliop.net\r\n>> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>\r\n>\r\n>\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n\r\n--------------010906010708050009070702\r\nContent-Type: text/html; charset=ISO-8859-1\r\nContent-Transfer-Encoding: 7bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=ISO-8859-1\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    <div class=\"moz-cite-prefix\">Je trouve cette discussion fort\r\n      int&eacute;ressante.<br>\r\n      <br>\r\n      N&eacute;anmoins, Aymeric a bien pr&eacute;cis&eacute; il me semble que l'architecture\r\n      de Caliopen exclut Node et le JS server side. Il me semble\r\n      d'ailleurs que &ccedil;a fait partie des crit&egrave;res &eacute;nonc&eacute;s dans la matrice\r\n      de choix ?<br>\r\n      <br>\r\n      J'aimerai -personnellement- une architecture avec Python en API\r\n      \"pure\" (ie. non HTML) r&eacute;utilisable comme une brique int&eacute;grable. <br>\r\n      <br>\r\n      Avec un serveur HTTPS en NodeJS isomorphe &agrave; la pointe pour servir\r\n      un frontend r&eacute;actif et r&eacute;silient.<br>\r\n      <br>\r\n      Mais je comprends bien que &ccedil;a ne soit pas optimal, car -encore\r\n      plus- long &agrave; r&eacute;aliser.<br>\r\n      <br>\r\n      Note/Rappel : est-ce que les d&eacute;vs cal&eacute;s en JS qui ont rejoint la\r\n      discussion ont vu (et rempli) la matrice de choix de framework ? <br>\r\n      <br>\r\n      +a+<br>\r\n      <br>\r\n      Le 28/11/2014 15:18, Alexis a &eacute;crit&nbsp;:<br>\r\n    </div>\r\n    <blockquote cite=\"mid:54788421.1040800@gandi.net\" type=\"cite\">\r\n      <meta http-equiv=\"Context-Type\" content=\"text/html;\r\n        charset=windows-1252\">\r\n      <br>\r\n      <div class=\"moz-cite-prefix\">Le 28/11/2014 12:22, julien muetton a\r\n        &eacute;crit&nbsp;:<br>\r\n      </div>\r\n      <blockquote cite=\"mid:54785B09.9010402@gandi.net\" type=\"cite\"> <br>\r\n        <div class=\"moz-cite-prefix\">On 11/28/2014 11:12 AM, Aymeric\r\n          Barantal wrote:<br>\r\n        </div>\r\n        <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\">\r\n          <div class=\"moz-cite-prefix\">On 11/27/2014 09:40 PM, julien\r\n            muetton wrote:<br>\r\n          </div>\r\n          <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\"> <br>\r\n            <div class=\"moz-cite-prefix\">On 11/27/2014 08:25 PM, Aymeric\r\n              Barantal wrote:<br>\r\n            </div>\r\n            <blockquote cite=\"mid:20141127192529.GA1819@tao.gandi.net\"\r\n              type=\"cite\">\r\n              <pre wrap=\"\">On Thursday 27 Nov 2014 &agrave; 12:28:25 (+0100), <a moz-do-not-send=\"true\" class=\"moz-txt-link-abbreviated\" href=\"mailto:joseph@cozycloud.cc\">joseph@cozycloud.cc</a> wrote:\r\n</pre>\r\n              <blockquote type=\"cite\">\r\n                <pre wrap=\"\">Ton analyse et tes incertitudes sur React/Flux sont justifi&eacute;es. On a pris un\r\nrisque de notre c&ocirc;t&eacute; et heureusement les deux d&eacute;v ont fait un bon boulot de\r\nprise en main et d'adaptation.\r\n\r\nPas de retour concernant Reflux, que je veux regarder en bloc et en d&eacute;tail car\r\nje ne suis pas satisfait de l'impl&eacute;mentation \"officielle\" de Flux, dont Fluxxor\r\ns'inspire fortement (on utilisait Fluxxor &agrave; la base, &ccedil;a n'apporte rien AMHA,\r\ndonc supprim&eacute;. Mais je n'ai pas regard&eacute; son &eacute;volution depuis plusieurs mois).\r\n\r\nJe n'avais pas pens&eacute; &agrave; la n&eacute;cessit&eacute; de Node pour utiliser React pour fair le\r\nrendu c&ocirc;t&eacute; serveur, comme Caliopen est en Python (de m&eacute;moire) ce n'est\r\neffectivement pas un argument. Par contre, pour b&eacute;n&eacute;ficier de l'isomorphisme,\r\nil faut obligatoire du Node &agrave; un moment (&agrave; ma connaissance).\r\n</pre>\r\n              </blockquote>\r\n              <pre wrap=\"\">Alors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\nsont bien moins importants [1] que les avantages que je vois a cette stricte\r\nseparation technologique entre front et api.\r\n</pre>\r\n            </blockquote>\r\n            Il est aussi possible d'avoir un serveur nodeJS qui sert les\r\n            pages HTML et l'API en python.<br>\r\n            Un reverse proxy permet d'agr&eacute;ger les deux via des r&egrave;gles\r\n            simples (HAProxy, Nginx, Apache...)<br>\r\n          </blockquote>\r\n          De quelles pages html est il question ? celles qui passent par\r\n          l'internationalisation ?<br>\r\n        </blockquote>\r\n        L'isomorphisme signifie rendre indiff&eacute;remment sur le serveur et\r\n        sur le client, comme twitter et wallmart.<br>\r\n        <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\">\r\n          J'ai du mal a voir le gain reel d'une plate-forme node.js a\r\n          part du code en commun, qui semble etre<br>\r\n          la notion principale cachee derriere le terme d'isomorphisme.<br>\r\n        </blockquote>\r\n        c'est pour avoir une meilleure exp&eacute;rience utilisateur.<br>\r\n        la premi&egrave;re page est affich&eacute;e directement, ce qui r&eacute;duit le\r\n        \"time to first interaction\" (introduit par twitter comme \"time\r\n        to tweet\")<br>\r\n        <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\"> A\r\n          ce jour c'est le serveur fournissant l'api qui sait fournir\r\n          egalement des pages html (enfin une<br>\r\n          seule du fait de l'utilisation d'angular sur le POC).<br>\r\n          <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\"> <br>\r\n            <blockquote>/api -&gt; api python<br>\r\n              cdn.domain.tld -&gt; asset statics<br>\r\n              default -&gt; serveur nodeJS<br>\r\n            </blockquote>\r\n            Ca colle bien avec l'approche &micro;Service.<br>\r\n          </blockquote>\r\n          L'orientation globale n'est pas microservice cote API /\r\n          backend<br>\r\n          <br>\r\n          <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\"> <br>\r\n            Maintenant, je ne sais pas &agrave; quel point il faut dupliquer la\r\n            logique pour tirer partie de l'isomorphisme.<br>\r\n            Je regarde comment on consomme les donn&eacute;es sur le serveur\r\n            (notamment est-ce que l'on passe par les stores).<br>\r\n          </blockquote>\r\n          La je suis perdu ;)<br>\r\n          <br>\r\n        </blockquote>\r\n        En rendant les pages sur le serveur, il faut aussi r&eacute;cup&eacute;rer les\r\n        donn&eacute;es &agrave; travers l'API.<br>\r\n        Ce que je cherche &agrave; trouver c'est la quantit&eacute; de code &agrave;\r\n        dupliquer, car consommer une API dans le browser passe par\r\n        XMLHttpRequest, alors que sur le serveur XMLHttpRequest n'est\r\n        pas dispo.<br>\r\n        <br>\r\n        La logique en Fluxx, sauf erreur de ma part, est de placer tout\r\n        le code m&eacute;tier dans les Stores qui sont &agrave; la fois \"data mapper\"\r\n        et logique domaine.<br>\r\n        Si c'est top pour ne pas coupler son code &agrave; un framework (on\r\n        d&eacute;veloppe notre m&eacute;tier/domaine comme on l'entend), il y a\r\n        forc&eacute;ment des diff&eacute;rences entre le client et le serveur.<br>\r\n      </blockquote>\r\n      <br>\r\n      Par isomorphisme, moi j'entends que le code est le m&ecirc;me est peut\r\n      &ecirc;tre utilis&eacute; c&ocirc;t&eacute; serveur ou c&ocirc;t&eacute; client.<br>\r\n      Donc pas de duplication, juste 1 code : 2 utilisations possibles.<br>\r\n      <br>\r\n      Browserify est de la partie bien sur pour faire tout &ccedil;a.<br>\r\n      <br>\r\n      Tuto pr&eacute;sentant un m&ecirc;me code qui s'ex&eacute;cute comme je disais Julien,\r\n      c&ocirc;t&eacute; serveur pour le 1er load, c&ocirc;t&eacute; client ensuite :<br>\r\n      <a moz-do-not-send=\"true\" class=\"moz-txt-link-freetext\"\r\nhref=\"http://scotch.io/tutorials/javascript/build-a-real-time-twitter-stream-with-node-and-react-js\">http://scotch.io/tutorials/javascript/build-a-real-time-twitter-stream-with-node-and-react-js</a><br>\r\n      <br>\r\n      <blockquote cite=\"mid:54785B09.9010402@gandi.net\" type=\"cite\"> <br>\r\n        @Joseph, @romain, @brice, vous avez une visibilit&eacute; sur la\r\n        quantit&eacute; de code &agrave; dupliquer ?<br>\r\n        <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\">\r\n          Quelle logique serait a dupliquer et finalement qu'est qui est\r\n          entendu par isomorphisme dans ce cadre, j'ai<br>\r\n          l'impression de ne plus comprendre ce terme.<br>\r\n          <br>\r\n          De quels stores est il question ? Le local storage sera\r\n          certainement a exploiter s'il fait parti de cette definition.<br>\r\n          <br>\r\n          ++ chamal<br>\r\n          <br>\r\n          <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n          <br>\r\n          <pre wrap=\"\">_______________________________________________\r\nCaliopdev mailing list\r\n<a moz-do-not-send=\"true\" class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a moz-do-not-send=\"true\" class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n        </blockquote>\r\n        <br>\r\n        <br>\r\n        <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n        <br>\r\n        <pre wrap=\"\">_______________________________________________\r\nCaliopdev mailing list\r\n<a moz-do-not-send=\"true\" class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a moz-do-not-send=\"true\" class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n      </blockquote>\r\n      <br>\r\n      <br>\r\n      <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n      <br>\r\n      <pre wrap=\"\">_______________________________________________\r\nCaliopdev mailing list\r\n<a class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n    </blockquote>\r\n    <br>\r\n  </body>\r\n</html>\r\n\r\n--------------010906010708050009070702--\r\n\r\n--------------070709000702030804080801\r\nContent-Type: application/pgp-keys;\r\n name=\"0xED3CE492.asc\"\r\nContent-Transfer-Encoding: 7bit\r\nContent-Disposition: attachment;\r\n filename=\"0xED3CE492.asc\"\r\n\r\n-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: GnuPG v1.4.12 (GNU/Linux)\r\n\r\nmQENBFIKYNABCACqou6YtfXUjpnZRVarYRkFiFcH8nQm7NHY4nutuXOCs8fTDzwM\r\nR6EZMjlpmdFjg9TemwHWhkho3qbwJP+puZU6yMT3rnehkEYRVkQtvIbTnhQesLhu\r\nCTM9ge1YKdkvL5mAgYMOgB14CYtXseKIUzefPy/pyO2Nf5t57Z87EctdpLPaSfNw\r\nqVNpKLN5Psy7ffcCbDsdwIhPMMN9S3EdpKLpvPCli7T2m87EM5xzGuGFVP4Gbtzw\r\nqmgleA9lH6lpBQ3LOc9SkdPNPvBZGfq1exCVoc133AFEIFlZMCVK2pGtAX2fgVGC\r\nOqAhwsvlAsDiC1g32NkLni3lPmwUeoybDXwvABEBAAG0JkFsYmFuIENyb21tZXIg\r\nPGFsYmFuQGFsYmFuY3JvbW1lci5jb20+iQE+BBMBAgAoBQJSCmDQAhsjBQkJZgGA\r\nBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDna7sG7TzkkiJSB/4+k8lOHHXI\r\n/ItzQUZBjDu4Xy9O/PCtP6KwIvd5LQNiIBvZ0olVjTLX+i1Rkn7ljQDOpXTZNTaW\r\n3ktuIGGJGBPVQeSxzDuC0K3VSGi6aRsQRF+1sVW7oA6pImT1jojrnraT+rW7KsP8\r\nR+tyCi25wiuN4bKNq6Ft0DLtuHN1w5cfabYlWtPhzIMjnR7gal2MXG+ovLJGeJ15\r\nzypwSWB4PvvadJGRDJ1U+IsdsgiDylL9k29GC9gB2+pYWCSIUxrCfhXXsSQDndN2\r\nMZ2q7rfhdE4mHZlBGZEOKTJdpkWkgVPkMxMUKMboRRDLzoihJ6kNsow14GPmFqy8\r\nhBS0M6cKxDMaiEYEExEIAAYFAlI8NZQACgkQd5FD2Z8azpzXuACg5keREAfyyBcL\r\ng0db9EeXKEDvWggAn2atyen5XuqV033ssCz9G/SPmiswiQEcBBMBCAAGBQJSPDWf\r\nAAoJEP6CnA9YYHPmgegIAIPx+fcv8jiKh4v8OgGWhYwUzfrwSUS7Fv/UUb9AhL3i\r\nvK1t2V6D0+gx+gVjL/8ZzzVPlqZ+50z8yvClbhjKK08P6ijGpA++JMaNyf1I2gGi\r\nk37/3jcOgTcLGDuG64jXbrDgDGySGWgAHOCtArjB3OQkVZfdgb2nDh39TZK7oPTY\r\nlF8AuCCbfd2Mxohh2oCJSp8I47WElcePlTXogeS1P09OO+mtl2onSW7K9MtE/7sp\r\nUqADJF03PamqY3lzxAWxCiS8YMuEBSn5eAw18lOis6fObmJ0GMqNPAqkjysHWNtN\r\n4qceASvAOHoSWEEnFhGIWDlH510np3RdttTvpe25XTe5AQ0EUgpg0AEIAMaUKd36\r\nEhDzA3bAW5Ke6m6AQqotrHhSBr5ypAwl6JRlbSBdK8UPiHzLqnJBKlET1sHc8dRq\r\nlMnGBPaC8Phd4Eoi7LiA5TF2RjJryrmh/BEnxw5DNQ1gIoXihOMNEa8BueUHPuLw\r\n6Jr6nEocRQ00qjrxBNioVWXwy8q+MnoMrd/ZJGYwSHr0dx9jATHdEydKE2mVtFia\r\nGAdOQ2glNcoJS574GMYRX0DBb5y0p0xV2JRjG7UH/62vDJjrVIRUe5gTOduw95Cz\r\nbKtoByMmyesrVh2OeCuta04OobsvDILUQeKcoJwMJF7511/DtK+rPU0p9Fn7vbdG\r\nf7qmGZG9ZyU0oY8AEQEAAYkBJQQYAQIADwUCUgpg0AIbDAUJCWYBgAAKCRDna7sG\r\n7Tzkkml3B/9tlGNY6Lsh2T0cOosnTu0WBTCMnUQiiGsTHnUq2R7T7sppO7DdH0n0\r\nRgRf+RRFh5bf3nuoNznDW4fYIihl2WjAJtgu4fIupm5uteKB/tZ1M4b37MpNalsH\r\nZ9GuncOl8zrcWCH8Mx4O3V6L1EM3QWmONw7Cqa35peEmODFg/bvmp0jyXQT0w8G3\r\nc4RVBVaK6+C1bRBCxugHrCEsQT6GjAcknEwpjK89Pdb/bgxenx/+V8ZHe1XQKuZI\r\n0EO4YEmJr3K/9OVic6CoV5dKJm1KlJB3ftE21y3upib8FkCFM8k0wnd/LOpztDs1\r\nMXWgD+JLmfNKbhzzf547qhkjPqSPDxpk\r\n=64CM\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n--------------070709000702030804080801\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--------------070709000702030804080801--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - Alexis <alexis@gandi.net> - 2014-11-28 1518.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id B411D120AC9;\r\n\tFri, 28 Nov 2014 15:18:22 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -98.552\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-98.552 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74, HTML_40_50=0.496, HTML_MESSAGE=0.001,\r\n\tRATWARE_GECKO_BUILD=1.691, USER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id gGouRShghrUc; Fri, 28 Nov 2014 15:18:20 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 5A22D120ACD;\r\n\tFri, 28 Nov 2014 15:18:20 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 5BA866B3D03EF;\r\n\tFri, 28 Nov 2014 15:18:19 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id p4W5af68RECq; Fri, 28 Nov 2014 15:18:15 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 1F8706B3D03E0;\r\n\tFri, 28 Nov 2014 15:18:15 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 3357C6B3D03EE\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 15:18:14 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id L0sRatYfe0Nz for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 15:18:12 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id C76F66B3D03E0\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 15:18:12 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id 5ADCA120ACD\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 15:18:11 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id 6yLlb7-G5blb for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 15:18:09 +0100 (CET)\r\nReceived: from [217.70.181.12] (crock.gandi.net [217.70.181.12])\r\n by mail4.gandi.net (Postfix) with ESMTP id 394A9120AC9\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 15:18:09 +0100 (CET)\r\nMessage-ID: <54788421.1040800@gandi.net>\r\nDate: Fri, 28 Nov 2014 15:18:09 +0100\r\nFrom: Alexis <alexis@gandi.net>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: caliopdev@caliop.net\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\n <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\n <20141127192529.GA1819@tao.gandi.net> <54778C34.90802@gandi.net>\r\n <54784A99.3020306@chamal.fr> <54785B09.9010402@gandi.net>\r\nIn-Reply-To: <54785B09.9010402@gandi.net>\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0446895342==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--===============0446895342==\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------060508090203040308040204\"\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------060508090203040308040204\r\nContent-Type: text/plain; charset=windows-1252; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\n\r\nLe 28/11/2014 12:22, julien muetton a �crit :\r\n>\r\n> On 11/28/2014 11:12 AM, Aymeric Barantal wrote:\r\n>> On 11/27/2014 09:40 PM, julien muetton wrote:\r\n>>>\r\n>>> On 11/27/2014 08:25 PM, Aymeric Barantal wrote:\r\n>>>> On Thursday 27 Nov 2014 � 12:28:25 (+0100),joseph@cozycloud.cc  wrote:\r\n>>>>> Ton analyse et tes incertitudes sur React/Flux sont justifi�es. On a pris un\r\n>>>>> risque de notre c�t� et heureusement les deux d�v ont fait un bon boulot de\r\n>>>>> prise en main et d'adaptation.\r\n>>>>>\r\n>>>>> Pas de retour concernant Reflux, que je veux regarder en bloc et en d�tail car\r\n>>>>> je ne suis pas satisfait de l'impl�mentation \"officielle\" de Flux, dont Fluxxor\r\n>>>>> s'inspire fortement (on utilisait Fluxxor � la base, �a n'apporte rien AMHA,\r\n>>>>> donc supprim�. Mais je n'ai pas regard� son �volution depuis plusieurs mois).\r\n>>>>>\r\n>>>>> Je n'avais pas pens� � la n�cessit� de Node pour utiliser React pour fair le\r\n>>>>> rendu c�t� serveur, comme Caliopen est en Python (de m�moire) ce n'est\r\n>>>>> effectivement pas un argument. Par contre, pour b�n�ficier de l'isomorphisme,\r\n>>>>> il faut obligatoire du Node � un moment (� ma connaissance).\r\n>>>> Alors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\n>>>> sont bien moins importants [1] que les avantages que je vois a cette stricte\r\n>>>> separation technologique entre front et api.\r\n>>> Il est aussi possible d'avoir un serveur nodeJS qui sert les pages \r\n>>> HTML et l'API en python.\r\n>>> Un reverse proxy permet d'agr�ger les deux via des r�gles simples \r\n>>> (HAProxy, Nginx, Apache...)\r\n>> De quelles pages html est il question ? celles qui passent par \r\n>> l'internationalisation ?\r\n> L'isomorphisme signifie rendre indiff�remment sur le serveur et sur le \r\n> client, comme twitter et wallmart.\r\n>> J'ai du mal a voir le gain reel d'une plate-forme node.js a part du \r\n>> code en commun, qui semble etre\r\n>> la notion principale cachee derriere le terme d'isomorphisme.\r\n> c'est pour avoir une meilleure exp�rience utilisateur.\r\n> la premi�re page est affich�e directement, ce qui r�duit le \"time to \r\n> first interaction\" (introduit par twitter comme \"time to tweet\")\r\n>> A ce jour c'est le serveur fournissant l'api qui sait fournir \r\n>> egalement des pages html (enfin une\r\n>> seule du fait de l'utilisation d'angular sur le POC).\r\n>>>\r\n>>>     /api -> api python\r\n>>>     cdn.domain.tld -> asset statics\r\n>>>     default -> serveur nodeJS\r\n>>>\r\n>>> Ca colle bien avec l'approche �Service.\r\n>> L'orientation globale n'est pas microservice cote API / backend\r\n>>\r\n>>>\r\n>>> Maintenant, je ne sais pas � quel point il faut dupliquer la logique \r\n>>> pour tirer partie de l'isomorphisme.\r\n>>> Je regarde comment on consomme les donn�es sur le serveur (notamment \r\n>>> est-ce que l'on passe par les stores).\r\n>> La je suis perdu ;)\r\n>>\r\n> En rendant les pages sur le serveur, il faut aussi r�cup�rer les \r\n> donn�es � travers l'API.\r\n> Ce que je cherche � trouver c'est la quantit� de code � dupliquer, car \r\n> consommer une API dans le browser passe par XMLHttpRequest, alors que \r\n> sur le serveur XMLHttpRequest n'est pas dispo.\r\n>\r\n> La logique en Fluxx, sauf erreur de ma part, est de placer tout le \r\n> code m�tier dans les Stores qui sont � la fois \"data mapper\" et \r\n> logique domaine.\r\n> Si c'est top pour ne pas coupler son code � un framework (on d�veloppe \r\n> notre m�tier/domaine comme on l'entend), il y a forc�ment des \r\n> diff�rences entre le client et le serveur.\r\n\r\nPar isomorphisme, moi j'entends que le code est le m�me est peut �tre \r\nutilis� c�t� serveur ou c�t� client.\r\nDonc pas de duplication, juste 1 code : 2 utilisations possibles.\r\n\r\nBrowserify est de la partie bien sur pour faire tout �a.\r\n\r\nTuto pr�sentant un m�me code qui s'ex�cute comme je disais Julien, c�t� \r\nserveur pour le 1er load, c�t� client ensuite :\r\nhttp://scotch.io/tutorials/javascript/build-a-real-time-twitter-stream-with-node-and-react-js\r\n\r\n>\r\n> @Joseph, @romain, @brice, vous avez une visibilit� sur la quantit� de \r\n> code � dupliquer ?\r\n>> Quelle logique serait a dupliquer et finalement qu'est qui est \r\n>> entendu par isomorphisme dans ce cadre, j'ai\r\n>> l'impression de ne plus comprendre ce terme.\r\n>>\r\n>> De quels stores est il question ? Le local storage sera certainement \r\n>> a exploiter s'il fait parti de cette definition.\r\n>>\r\n>> ++ chamal\r\n>>\r\n>>\r\n>> _______________________________________________\r\n>> Caliopdev mailing list\r\n>> Caliopdev@caliop.net\r\n>> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>\r\n>\r\n>\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n\r\n--------------060508090203040308040204\r\nContent-Type: text/html; charset=windows-1252\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=windows-1252\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body text=\"#000000\" bgcolor=\"#FFFFFF\">\r\n    <br>\r\n    <div class=\"moz-cite-prefix\">Le 28/11/2014 12:22, julien muetton a\r\n      �crit�:<br>\r\n    </div>\r\n    <blockquote cite=\"mid:54785B09.9010402@gandi.net\" type=\"cite\">\r\n      <meta content=\"text/html; charset=windows-1252\"\r\n        http-equiv=\"Content-Type\">\r\n      <br>\r\n      <div class=\"moz-cite-prefix\">On 11/28/2014 11:12 AM, Aymeric\r\n        Barantal wrote:<br>\r\n      </div>\r\n      <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\">\r\n        <meta content=\"text/html; charset=windows-1252\"\r\n          http-equiv=\"Content-Type\">\r\n        <div class=\"moz-cite-prefix\">On 11/27/2014 09:40 PM, julien\r\n          muetton wrote:<br>\r\n        </div>\r\n        <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\">\r\n          <meta content=\"text/html; charset=windows-1252\"\r\n            http-equiv=\"Content-Type\">\r\n          <br>\r\n          <div class=\"moz-cite-prefix\">On 11/27/2014 08:25 PM, Aymeric\r\n            Barantal wrote:<br>\r\n          </div>\r\n          <blockquote cite=\"mid:20141127192529.GA1819@tao.gandi.net\"\r\n            type=\"cite\">\r\n            <pre wrap=\"\">On Thursday 27 Nov 2014 � 12:28:25 (+0100), <a moz-do-not-send=\"true\" class=\"moz-txt-link-abbreviated\" href=\"mailto:joseph@cozycloud.cc\">joseph@cozycloud.cc</a> wrote:\r\n</pre>\r\n            <blockquote type=\"cite\">\r\n              <pre wrap=\"\">Ton analyse et tes incertitudes sur React/Flux sont justifi�es. On a pris un\r\nrisque de notre c�t� et heureusement les deux d�v ont fait un bon boulot de\r\nprise en main et d'adaptation.\r\n\r\nPas de retour concernant Reflux, que je veux regarder en bloc et en d�tail car\r\nje ne suis pas satisfait de l'impl�mentation \"officielle\" de Flux, dont Fluxxor\r\ns'inspire fortement (on utilisait Fluxxor � la base, �a n'apporte rien AMHA,\r\ndonc supprim�. Mais je n'ai pas regard� son �volution depuis plusieurs mois).\r\n\r\nJe n'avais pas pens� � la n�cessit� de Node pour utiliser React pour fair le\r\nrendu c�t� serveur, comme Caliopen est en Python (de m�moire) ce n'est\r\neffectivement pas un argument. Par contre, pour b�n�ficier de l'isomorphisme,\r\nil faut obligatoire du Node � un moment (� ma connaissance).\r\n</pre>\r\n            </blockquote>\r\n            <pre wrap=\"\">Alors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\nsont bien moins importants [1] que les avantages que je vois a cette stricte\r\nseparation technologique entre front et api.\r\n</pre>\r\n          </blockquote>\r\n          Il est aussi possible d'avoir un serveur nodeJS qui sert les\r\n          pages HTML et l'API en python.<br>\r\n          Un reverse proxy permet d'agr�ger les deux via des r�gles\r\n          simples (HAProxy, Nginx, Apache...)<br>\r\n        </blockquote>\r\n        De quelles pages html est il question ? celles qui passent par\r\n        l'internationalisation ?<br>\r\n      </blockquote>\r\n      L'isomorphisme signifie rendre indiff�remment sur le serveur et\r\n      sur le client, comme twitter et wallmart.<br>\r\n      <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\">\r\n        J'ai du mal a voir le gain reel d'une plate-forme node.js a part\r\n        du code en commun, qui semble etre<br>\r\n        la notion principale cachee derriere le terme d'isomorphisme.<br>\r\n      </blockquote>\r\n      c'est pour avoir une meilleure exp�rience utilisateur.<br>\r\n      la premi�re page est affich�e directement, ce qui r�duit le \"time\r\n      to first interaction\" (introduit par twitter comme \"time to\r\n      tweet\")<br>\r\n      <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\"> A\r\n        ce jour c'est le serveur fournissant l'api qui sait fournir\r\n        egalement des pages html (enfin une<br>\r\n        seule du fait de l'utilisation d'angular sur le POC).<br>\r\n        <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\"> <br>\r\n          <blockquote>/api -&gt; api python<br>\r\n            cdn.domain.tld -&gt; asset statics<br>\r\n            default -&gt; serveur nodeJS<br>\r\n          </blockquote>\r\n          Ca colle bien avec l'approche �Service.<br>\r\n        </blockquote>\r\n        L'orientation globale n'est pas microservice cote API / backend<br>\r\n        <br>\r\n        <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\"> <br>\r\n          Maintenant, je ne sais pas � quel point il faut dupliquer la\r\n          logique pour tirer partie de l'isomorphisme.<br>\r\n          Je regarde comment on consomme les donn�es sur le serveur\r\n          (notamment est-ce que l'on passe par les stores).<br>\r\n        </blockquote>\r\n        La je suis perdu ;)<br>\r\n        <br>\r\n      </blockquote>\r\n      En rendant les pages sur le serveur, il faut aussi r�cup�rer les\r\n      donn�es � travers l'API.<br>\r\n      Ce que je cherche � trouver c'est la quantit� de code � dupliquer,\r\n      car consommer une API dans le browser passe par XMLHttpRequest,\r\n      alors que sur le serveur XMLHttpRequest n'est pas dispo.<br>\r\n      <br>\r\n      La logique en Fluxx, sauf erreur de ma part, est de placer tout le\r\n      code m�tier dans les Stores qui sont � la fois \"data mapper\" et\r\n      logique domaine.<br>\r\n      Si c'est top pour ne pas coupler son code � un framework (on\r\n      d�veloppe notre m�tier/domaine comme on l'entend), il y a\r\n      forc�ment des diff�rences entre le client et le serveur.<br>\r\n    </blockquote>\r\n    <br>\r\n    Par isomorphisme, moi j'entends que le code est le m�me est peut\r\n    �tre utilis� c�t� serveur ou c�t� client.<br>\r\n    Donc pas de duplication, juste 1 code : 2 utilisations possibles.<br>\r\n    <br>\r\n    Browserify est de la partie bien sur pour faire tout �a.<br>\r\n    <br>\r\n    Tuto pr�sentant un m�me code qui s'ex�cute comme je disais Julien,\r\n    c�t� serveur pour le 1er load, c�t� client ensuite :<br>\r\n<a class=\"moz-txt-link-freetext\" href=\"http://scotch.io/tutorials/javascript/build-a-real-time-twitter-stream-with-node-and-react-js\">http://scotch.io/tutorials/javascript/build-a-real-time-twitter-stream-with-node-and-react-js</a><br>\r\n    <br>\r\n    <blockquote cite=\"mid:54785B09.9010402@gandi.net\" type=\"cite\"> <br>\r\n      @Joseph, @romain, @brice, vous avez une visibilit� sur la quantit�\r\n      de code � dupliquer ?<br>\r\n      <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\">\r\n        Quelle logique serait a dupliquer et finalement qu'est qui est\r\n        entendu par isomorphisme dans ce cadre, j'ai<br>\r\n        l'impression de ne plus comprendre ce terme.<br>\r\n        <br>\r\n        De quels stores est il question ? Le local storage sera\r\n        certainement a exploiter s'il fait parti de cette definition.<br>\r\n        <br>\r\n        ++ chamal<br>\r\n        <br>\r\n        <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n        <br>\r\n        <pre wrap=\"\">_______________________________________________\r\nCaliopdev mailing list\r\n<a moz-do-not-send=\"true\" class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a moz-do-not-send=\"true\" class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n      </blockquote>\r\n      <br>\r\n      <br>\r\n      <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n      <br>\r\n      <pre wrap=\"\">_______________________________________________\r\nCaliopdev mailing list\r\n<a class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n    </blockquote>\r\n    <br>\r\n  </body>\r\n</html>\r\n\r\n--------------060508090203040308040204--\r\n\r\n--===============0446895342==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0446895342==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - Alexis <alexis@gandi.net> - 2014-11-28 1748.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 1AC2E120AEC;\r\n\tFri, 28 Nov 2014 17:48:40 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -100.492\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-100.492 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, FUZZY_AMBIEN=0.416, RATWARE_GECKO_BUILD=1.691,\r\n\tUSER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id c4NcIGMIz8np; Fri, 28 Nov 2014 17:48:38 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 193B8120AD4;\r\n\tFri, 28 Nov 2014 17:48:38 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id E658F6B3A4184;\r\n\tFri, 28 Nov 2014 17:48:37 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id auNcj6d7Q2aF; Fri, 28 Nov 2014 17:48:36 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 8EBE36B3A416E;\r\n\tFri, 28 Nov 2014 17:48:36 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 203476B3A416E\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:48:36 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id OjmhxwdifBxs for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 17:48:32 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id 801536B3A4182\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:48:32 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id 617DA120AEC\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:48:32 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id i9SwX94sp0+v for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 17:48:30 +0100 (CET)\r\nReceived: from [217.70.181.12] (crock.gandi.net [217.70.181.12])\r\n by mail4.gandi.net (Postfix) with ESMTP id 69289120AD4\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:48:30 +0100 (CET)\r\nMessage-ID: <5478A75E.4070303@gandi.net>\r\nDate: Fri, 28 Nov 2014 17:48:30 +0100\r\nFrom: Alexis <alexis@gandi.net>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: caliopdev@caliop.net\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\n <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\n <20141127192529.GA1819@tao.gandi.net> <54778C34.90802@gandi.net>\r\n <54784A99.3020306@chamal.fr> <54785B09.9010402@gandi.net>\r\n <54788421.1040800@gandi.net> <54788E1F.7060700@gandi.net>\r\n <5478901C.4020201@gandi.net>\r\nIn-Reply-To: <5478901C.4020201@gandi.net>\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=\"iso-8859-1\"; Format=\"flowed\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\nLe 28/11/2014 16:09, julien muetton a =E9crit :\r\n>\r\n> On 11/28/2014 04:00 PM, Vincent MINEAUD wrote:\r\n>> On 28/11/2014 15:18, Alexis wrote:\r\n>>> Par isomorphisme, moi j'entends que le code est le m=EAme est peut =EAt=\r\nre\r\n>>> utilis=E9 c=F4t=E9 serveur ou c=F4t=E9 client.\r\n>>> Donc pas de duplication, juste 1 code : 2 utilisations possibles.\r\n>>>\r\n>>> Browserify est de la partie bien sur pour faire tout =E7a.\r\n>> Il semble que webpack est m=EAme plus performant que browserify (entendu\r\n>> lors d'un meetup)\r\n>> plus d'info ici : https://github.com/webpack/webpack\r\n> On disgresse, mais ca n'est pas aussi simple que ca.\r\n> On est =E0 6 mois de la sortie de es6, on a des confs sur es7, il est =\r\n\r\n> juste impensable de faire autre chose que des modules es6.\r\n\r\nOuais doucement...\r\n\r\nFaut pas aller trop vite qd m=EAme, Angular n'est pas mort car la v2 =\r\n\r\narrive et \"casse tout\" tout comme toutes les techniques de =\r\n\r\nmodularisation JS ne sont pas mortes car ES6 pointe le bout de son nez.\r\nCaliopen aura le temps d'=EAtre r=E9=E9crit et de passer en v2 ou v3 d'ici =\r\nl=E0. =\r\n\r\nFaut juste =EAtre pragmatique. Ca bouge tellement dans le monde JS que le =\r\n\r\ncode pondu aujourd'hui sera deprecated dans moins d'un an, quelque soit =\r\n\r\nla techno !\r\n\r\n> Ensuite, la techno de transpile/packaging/legacy, j'ai pas d'avis, =\r\n\r\n> webpack a l'air cool, 6to5 + browserify aussi.\r\n> Ca reste du d=E9tail d'impl=E9mentation.\r\n>\r\n> L'isomorphisme n=E9cessite forc=E9ment d'avoir du code qui diff=E8re selo=\r\nn =\r\n\r\n> la plateforme, dans une librairie ou dans notre code, mais =\r\n\r\n> g=E9n=E9ralement dans les deux.\r\n> La question c'est combien.\r\n>\r\n> my 2 cents\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - Aymeric Barantal <mric@chamal.fr> - 2014-11-28 1112.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id DD6B8120A24;\r\n\tFri, 28 Nov 2014 11:12:39 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.956\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.956 tagged_above=-999 required=5\r\n\ttests=[BAYES_05=-1.11, HTML_30_40=0.374, HTML_MESSAGE=0.001,\r\n\tRATWARE_GECKO_BUILD=1.691]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id NUtE7zi9j9C3; Fri, 28 Nov 2014 11:12:37 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 63569120AF0;\r\n\tFri, 28 Nov 2014 11:12:37 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 142826B3A17FD;\r\n\tFri, 28 Nov 2014 11:12:37 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id sn6DXRb5GdgT; Fri, 28 Nov 2014 11:12:36 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 7F3116B3A17CD;\r\n\tFri, 28 Nov 2014 11:12:35 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 653406B3A1642\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 11:12:34 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id Llix2uNxJNK3 for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 11:12:30 +0100 (CET)\r\nReceived: from relay3-d.mail.gandi.net (relay3-d.mail.gandi.net\r\n [217.70.183.195])\r\n (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 5E0356B39FF7B\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 11:12:30 +0100 (CET)\r\nReceived: from [IPv6:2001:4b98:beef:a:d0d8:9686:95da:ec1f] (unknown\r\n [IPv6:2001:4b98:beef:a:d0d8:9686:95da:ec1f])\r\n (Authenticated sender: mric@chamal.fr)\r\n by relay3-d.mail.gandi.net (Postfix) with ESMTPSA id 2F486A812F\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 11:12:30 +0100 (CET)\r\nMessage-ID: <54784A99.3020306@chamal.fr>\r\nDate: Fri, 28 Nov 2014 11:12:41 +0100\r\nFrom: Aymeric Barantal <mric@chamal.fr>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: caliopdev@caliop.net\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\n <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\n <20141127192529.GA1819@tao.gandi.net> <54778C34.90802@gandi.net>\r\nIn-Reply-To: <54778C34.90802@gandi.net>\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0054993441==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--===============0054993441==\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------090007070401020609000209\"\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------090007070401020609000209\r\nContent-Type: text/plain; charset=windows-1252; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\nOn 11/27/2014 09:40 PM, julien muetton wrote:\r\n>\r\n> On 11/27/2014 08:25 PM, Aymeric Barantal wrote:\r\n>> On Thursday 27 Nov 2014 � 12:28:25 (+0100),joseph@cozycloud.cc  wrote:\r\n>>> Ton analyse et tes incertitudes sur React/Flux sont justifi�es. On a pris un\r\n>>> risque de notre c�t� et heureusement les deux d�v ont fait un bon boulot de\r\n>>> prise en main et d'adaptation.\r\n>>>\r\n>>> Pas de retour concernant Reflux, que je veux regarder en bloc et en d�tail car\r\n>>> je ne suis pas satisfait de l'impl�mentation \"officielle\" de Flux, dont Fluxxor\r\n>>> s'inspire fortement (on utilisait Fluxxor � la base, �a n'apporte rien AMHA,\r\n>>> donc supprim�. Mais je n'ai pas regard� son �volution depuis plusieurs mois).\r\n>>>\r\n>>> Je n'avais pas pens� � la n�cessit� de Node pour utiliser React pour fair le\r\n>>> rendu c�t� serveur, comme Caliopen est en Python (de m�moire) ce n'est\r\n>>> effectivement pas un argument. Par contre, pour b�n�ficier de l'isomorphisme,\r\n>>> il faut obligatoire du Node � un moment (� ma connaissance).\r\n>> Alors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\n>> sont bien moins importants [1] que les avantages que je vois a cette stricte\r\n>> separation technologique entre front et api.\r\n> Il est aussi possible d'avoir un serveur nodeJS qui sert les pages \r\n> HTML et l'API en python.\r\n> Un reverse proxy permet d'agr�ger les deux via des r�gles simples \r\n> (HAProxy, Nginx, Apache...)\r\nDe quelles pages html est il question ? celles qui passent par \r\nl'internationalisation ?\r\n\r\nJ'ai du mal a voir le gain reel d'une plate-forme node.js a part du code \r\nen commun, qui semble etre\r\nla notion principale cachee derriere le terme d'isomorphisme.\r\n\r\nA ce jour c'est le serveur fournissant l'api qui sait fournir egalement \r\ndes pages html (enfin une\r\nseule du fait de l'utilisation d'angular sur le POC).\r\n>\r\n>     /api -> api python\r\n>     cdn.domain.tld -> asset statics\r\n>     default -> serveur nodeJS\r\n>\r\n> Ca colle bien avec l'approche �Service.\r\nL'orientation globale n'est pas microservice cote API / backend\r\n\r\n>\r\n> Maintenant, je ne sais pas � quel point il faut dupliquer la logique \r\n> pour tirer partie de l'isomorphisme.\r\n> Je regarde comment on consomme les donn�es sur le serveur (notamment \r\n> est-ce que l'on passe par les stores).\r\nLa je suis perdu ;)\r\n\r\nQuelle logique serait a dupliquer et finalement qu'est qui est entendu \r\npar isomorphisme dans ce cadre, j'ai\r\nl'impression de ne plus comprendre ce terme.\r\n\r\nDe quels stores est il question ? Le local storage sera certainement a \r\nexploiter s'il fait parti de cette definition.\r\n\r\n++ chamal\r\n\r\n--------------090007070401020609000209\r\nContent-Type: text/html; charset=windows-1252\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=windows-1252\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    <div class=\"moz-cite-prefix\">On 11/27/2014 09:40 PM, julien muetton\r\n      wrote:<br>\r\n    </div>\r\n    <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\">\r\n      <meta content=\"text/html; charset=windows-1252\"\r\n        http-equiv=\"Content-Type\">\r\n      <br>\r\n      <div class=\"moz-cite-prefix\">On 11/27/2014 08:25 PM, Aymeric\r\n        Barantal wrote:<br>\r\n      </div>\r\n      <blockquote cite=\"mid:20141127192529.GA1819@tao.gandi.net\"\r\n        type=\"cite\">\r\n        <pre wrap=\"\">On Thursday 27 Nov 2014 � 12:28:25 (+0100), <a moz-do-not-send=\"true\" class=\"moz-txt-link-abbreviated\" href=\"mailto:joseph@cozycloud.cc\">joseph@cozycloud.cc</a> wrote:\r\n</pre>\r\n        <blockquote type=\"cite\">\r\n          <pre wrap=\"\">Ton analyse et tes incertitudes sur React/Flux sont justifi�es. On a pris un\r\nrisque de notre c�t� et heureusement les deux d�v ont fait un bon boulot de\r\nprise en main et d'adaptation.\r\n\r\nPas de retour concernant Reflux, que je veux regarder en bloc et en d�tail car\r\nje ne suis pas satisfait de l'impl�mentation \"officielle\" de Flux, dont Fluxxor\r\ns'inspire fortement (on utilisait Fluxxor � la base, �a n'apporte rien AMHA,\r\ndonc supprim�. Mais je n'ai pas regard� son �volution depuis plusieurs mois).\r\n\r\nJe n'avais pas pens� � la n�cessit� de Node pour utiliser React pour fair le\r\nrendu c�t� serveur, comme Caliopen est en Python (de m�moire) ce n'est\r\neffectivement pas un argument. Par contre, pour b�n�ficier de l'isomorphisme,\r\nil faut obligatoire du Node � un moment (� ma connaissance).\r\n</pre>\r\n        </blockquote>\r\n        <pre wrap=\"\">Alors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\nsont bien moins importants [1] que les avantages que je vois a cette stricte\r\nseparation technologique entre front et api.\r\n</pre>\r\n      </blockquote>\r\n      Il est aussi possible d'avoir un serveur nodeJS qui sert les pages\r\n      HTML et l'API en python.<br>\r\n      Un reverse proxy permet d'agr�ger les deux via des r�gles simples\r\n      (HAProxy, Nginx, Apache...)<br>\r\n    </blockquote>\r\n    De quelles pages html est il question ? celles qui passent par\r\n    l'internationalisation ?<br>\r\n    <br>\r\n    J'ai du mal a voir le gain reel d'une plate-forme node.js a part du\r\n    code en commun, qui semble etre<br>\r\n    la notion principale cachee derriere le terme d'isomorphisme.<br>\r\n    <br>\r\n    A ce jour c'est le serveur fournissant l'api qui sait fournir\r\n    egalement des pages html (enfin une<br>\r\n    seule du fait de l'utilisation d'angular sur le POC).<br>\r\n    <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\"> <br>\r\n      <blockquote>/api -&gt; api python<br>\r\n        cdn.domain.tld -&gt; asset statics<br>\r\n        default -&gt; serveur nodeJS<br>\r\n      </blockquote>\r\n      Ca colle bien avec l'approche �Service.<br>\r\n    </blockquote>\r\n    L'orientation globale n'est pas microservice cote API / backend<br>\r\n    <br>\r\n    <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\"> <br>\r\n      Maintenant, je ne sais pas � quel point il faut dupliquer la\r\n      logique pour tirer partie de l'isomorphisme.<br>\r\n      Je regarde comment on consomme les donn�es sur le serveur\r\n      (notamment est-ce que l'on passe par les stores).<br>\r\n    </blockquote>\r\n    La je suis perdu ;)<br>\r\n    <br>\r\n    Quelle logique serait a dupliquer et finalement qu'est qui est\r\n    entendu par isomorphisme dans ce cadre, j'ai<br>\r\n    l'impression de ne plus comprendre ce terme.<br>\r\n    <br>\r\n    De quels stores est il question ? Le local storage sera certainement\r\n    a exploiter s'il fait parti de cette definition.<br>\r\n    <br>\r\n    ++ chamal<br>\r\n  </body>\r\n</html>\r\n\r\n--------------090007070401020609000209--\r\n\r\n--===============0054993441==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0054993441==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - Aymeric Barantal <mric@chamal.fr> - 2014-11-28 1724.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 1B202120A73;\r\n\tFri, 28 Nov 2014 17:24:07 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.411\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.411 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, HTML_40_50=0.496, HTML_MESSAGE=0.001,\r\n\tRATWARE_GECKO_BUILD=1.691]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id HTg1Tv8+HPrZ; Fri, 28 Nov 2014 17:24:05 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 04C5E120A32;\r\n\tFri, 28 Nov 2014 17:24:04 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 9361A6B38672C;\r\n\tFri, 28 Nov 2014 17:24:04 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id 5DxIRjSeVXHt; Fri, 28 Nov 2014 17:24:03 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 54E5069EA9685;\r\n\tFri, 28 Nov 2014 17:24:03 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id A8CD26AACB827\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:24:01 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id ybZddN5OH1jU for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 17:24:00 +0100 (CET)\r\nReceived: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net\r\n [217.70.183.196])\r\n (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id BBB8F69EA9685\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:24:00 +0100 (CET)\r\nReceived: from [IPv6:2001:4b98:beef:a:d0d8:9686:95da:ec1f] (unknown\r\n [IPv6:2001:4b98:beef:a:d0d8:9686:95da:ec1f])\r\n (Authenticated sender: mric@chamal.fr)\r\n by relay4-d.mail.gandi.net (Postfix) with ESMTPSA id 9E1901720EB\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:24:00 +0100 (CET)\r\nMessage-ID: <5478A1AB.3020404@chamal.fr>\r\nDate: Fri, 28 Nov 2014 17:24:11 +0100\r\nFrom: Aymeric Barantal <mric@chamal.fr>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: caliopdev@caliop.net\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\n <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\n <20141127192529.GA1819@tao.gandi.net> <54778C34.90802@gandi.net>\r\n <54784A99.3020306@chamal.fr> <54785B09.9010402@gandi.net>\r\n <54788421.1040800@gandi.net> <547893DE.20601@albancrommer.com>\r\nIn-Reply-To: <547893DE.20601@albancrommer.com>\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1966287538==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--===============1966287538==\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------000408080501080009050600\"\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------000408080501080009050600\r\nContent-Type: text/plain; charset=windows-1252; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\nOn 11/28/2014 04:25 PM, Alban Crommer wrote:\r\n> Je trouve cette discussion fort int�ressante.\r\n>\r\n> N�anmoins, Aymeric a bien pr�cis� il me semble que l'architecture de \r\n> Caliopen exclut Node et le JS server side. Il me semble d'ailleurs que \r\n> �a fait partie des crit�res �nonc�s dans la matrice de choix ?\r\nCe n'est pas totalement exclu, juste il me faut en comprendre les \r\nraisons avant de rajouter cette couche node cote deploiement serveur.\r\n\r\nNDLR: c'est pas la panacee l'automatisation et l'installation de toutes \r\nces javascripteries cote serveur (node, npm and consorts)\r\n\r\nNDLpasR: que l'abruti de chez npm qui a decide de faire l'install en \r\n\"wget un install.sh | sh\" se presente, ca sera un certain\r\nnombre de paires de baffes que je lui distriburais avec grand plaisir.\r\n\r\n> J'aimerai -personnellement- une architecture avec Python en API \"pure\" \r\n> (ie. non HTML) r�utilisable comme une brique int�grable.\r\nL'API c'est un module a charger dans un conteneur a faire du http en \r\npython (pyramid), on peut enlever facilement\r\nle module de base qui genere les pages HTML (/ pour le moment).\r\n\r\n>\r\n> Avec un serveur HTTPS en NodeJS isomorphe � la pointe pour servir un \r\n> frontend r�actif et r�silient.\r\nWhy not, mais bon s'il s'agit de fournir une page html preremplie pour \r\neviter les roundtrips\r\ncote browser et le phenomeme page blanche longue a s'afficher, un \r\ntemplate et c'est bon.\r\n\r\nJe rejoins Joseph sur le cote optimisation, et nous avons d'autres \r\npriorites sauf si l'on arrive\r\na me convaincre que l'on gagne en developpement en demarrant de la \r\nsorte, promi je ne\r\nsuis pas totalement reticent ;)\r\n\r\n>\r\n> Mais je comprends bien que �a ne soit pas optimal, car -encore plus- \r\n> long � r�aliser.\r\n\r\nOu pas, il semble plus facile de trouver des developpeurs ayant cette \r\ncompetence\r\net l'envie que dans le monde python, ca peut aider a decider sur la \r\ntechno pour cette partie.\r\n\r\n>\r\n> Note/Rappel : est-ce que les d�vs cal�s en JS qui ont rejoint la \r\n> discussion ont vu (et rempli) la matrice de choix de framework ?\r\npas encore tout le monde, merci de la piqure de rappel :))\r\n\r\n\r\n++ chamal\r\n\r\n--------------000408080501080009050600\r\nContent-Type: text/html; charset=windows-1252\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=windows-1252\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    <div class=\"moz-cite-prefix\">On 11/28/2014 04:25 PM, Alban Crommer\r\n      wrote:<br>\r\n    </div>\r\n    <blockquote cite=\"mid:547893DE.20601@albancrommer.com\" type=\"cite\">\r\n      <meta content=\"text/html; charset=windows-1252\"\r\n        http-equiv=\"Content-Type\">\r\n      <div class=\"moz-cite-prefix\">Je trouve cette discussion fort\r\n        int�ressante.<br>\r\n        <br>\r\n        N�anmoins, Aymeric a bien pr�cis� il me semble que\r\n        l'architecture de Caliopen exclut Node et le JS server side. Il\r\n        me semble d'ailleurs que �a fait partie des crit�res �nonc�s\r\n        dans la matrice de choix ?<br>\r\n      </div>\r\n    </blockquote>\r\n    Ce n'est pas totalement exclu, juste il me faut en comprendre les\r\n    raisons avant de rajouter cette couche node cote deploiement serveur.<br>\r\n    <br>\r\n    NDLR: c'est pas la panacee l'automatisation et l'installation de\r\n    toutes ces javascripteries cote serveur (node, npm and consorts)<br>\r\n    <br>\r\n    NDLpasR: que l'abruti de chez npm qui a decide de faire l'install en\r\n    \"wget un install.sh | sh\" se presente, ca sera un certain<br>\r\n    nombre de paires de baffes que je lui distriburais avec grand\r\n    plaisir.<br>\r\n    <br>\r\n    <blockquote cite=\"mid:547893DE.20601@albancrommer.com\" type=\"cite\">\r\n      <div class=\"moz-cite-prefix\"> J'aimerai -personnellement- une\r\n        architecture avec Python en API \"pure\" (ie. non HTML)\r\n        r�utilisable comme une brique int�grable. <br>\r\n      </div>\r\n    </blockquote>\r\n    L'API c'est un module a charger dans un conteneur a faire du http en\r\n    python (pyramid), on peut enlever facilement<br>\r\n    le module de base qui genere les pages HTML (/ pour le moment).<br>\r\n    <br>\r\n    <blockquote cite=\"mid:547893DE.20601@albancrommer.com\" type=\"cite\">\r\n      <div class=\"moz-cite-prefix\"> <br>\r\n        Avec un serveur HTTPS en NodeJS isomorphe � la pointe pour\r\n        servir un frontend r�actif et r�silient.<br>\r\n      </div>\r\n    </blockquote>\r\n    Why not, mais bon s'il s'agit de fournir une page html preremplie\r\n    pour eviter les roundtrips<br>\r\n    cote browser et le phenomeme page blanche longue a s'afficher, un\r\n    template et c'est bon.<br>\r\n    <br>\r\n    Je rejoins Joseph sur le cote optimisation, et nous avons d'autres\r\n    priorites sauf si l'on arrive<br>\r\n    a me convaincre que l'on gagne en developpement en demarrant de la\r\n    sorte, promi je ne<br>\r\n    suis pas totalement reticent ;)<br>\r\n    <br>\r\n    <blockquote cite=\"mid:547893DE.20601@albancrommer.com\" type=\"cite\">\r\n      <div class=\"moz-cite-prefix\"> <br>\r\n        Mais je comprends bien que �a ne soit pas optimal, car -encore\r\n        plus- long � r�aliser.<br>\r\n      </div>\r\n    </blockquote>\r\n    <br>\r\n    Ou pas, il semble plus facile de trouver des developpeurs ayant\r\n    cette competence<br>\r\n    et l'envie que dans le monde python, ca peut aider a decider sur la\r\n    techno pour cette partie.<br>\r\n    <br>\r\n    <blockquote cite=\"mid:547893DE.20601@albancrommer.com\" type=\"cite\">\r\n      <div class=\"moz-cite-prefix\"> <br>\r\n        Note/Rappel : est-ce que les d�vs cal�s en JS qui ont rejoint la\r\n        discussion ont vu (et rempli) la matrice de choix de framework ?\r\n        <br>\r\n      </div>\r\n    </blockquote>\r\n    pas encore tout le monde, merci de la piqure de rappel :))<br>\r\n    <br>\r\n    <br>\r\n    ++ chamal<br>\r\n  </body>\r\n</html>\r\n\r\n--------------000408080501080009050600--\r\n\r\n--===============1966287538==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1966287538==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - Kajan Sivaramalingam <kajan@kajansiva.com> - 2014-11-27 0132.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 70EF1120ABE;\r\n\tThu, 27 Nov 2014 01:32:59 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.002\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.002 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001, HTML_MESSAGE=0.001]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id NeoJSxUVdFZO; Thu, 27 Nov 2014 01:32:58 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 71B70120A78;\r\n\tThu, 27 Nov 2014 01:32:58 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 38E8B69F25FE8;\r\n\tThu, 27 Nov 2014 01:32:58 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=gmail.com header.i=@gmail.com header.b=v5kqS1aP;\r\n\tdkim-adsp=none (insecure policy); dkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id YebhVU8sYU8C; Thu, 27 Nov 2014 01:32:57 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id A12E769F25FE7;\r\n\tThu, 27 Nov 2014 01:32:56 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 8CD5B69F25FE5\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 01:32:55 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id oAXMYCDcAiyi for <caliopdev@caliop.net>;\r\n Thu, 27 Nov 2014 01:32:54 +0100 (CET)\r\nReceived: from mail-la0-f41.google.com (mail-la0-f41.google.com\r\n [209.85.215.41])\r\n (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 5125869F25FE4\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 01:32:54 +0100 (CET)\r\nReceived: by mail-la0-f41.google.com with SMTP id gf13so3423535lab.28\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 16:32:53 -0800 (PST)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r\n h=mime-version:sender:in-reply-to:references:date:message-id:subject\r\n :from:to:content-type;\r\n bh=IQykvQsgqfGG1ETLjTQA4egCii1pBiW7Swauxp1zE3g=;\r\n b=v5kqS1aPDGqcs6BYffF6G7TuHaW4x8lJKWJJLCnFYqdoKG84flpuz3TfhTYJRMUSHM\r\n NNH2xcyE/qsWd/cb51zrhGjTkLrNH+sr9qPDNEM24+vRwqqgk8AaGEqNqT+jtDaEmzzF\r\n njffXB/oOJHBmPZgpoJHrhRkaPAbQoPQcugVKPdsaq+uQPfE5nyY/8y2AMuswxrULYsz\r\n qecLI08RkEYZHPGHH+b6w4TdeuPI1DTiEF5mmraE3y7tdM4JuYn+ZjSYKeTiJfPoGKWR\r\n tlX14Z7YcO5W66PD/3Y/aEh9GwThygpPQybksoFI1lrX4l1PdGLj4irooLWgP/Pvf6Hn\r\n FY9g==\r\nMIME-Version: 1.0\r\nX-Received: by 10.152.243.37 with SMTP id wv5mr37141502lac.10.1417048373370;\r\n Wed, 26 Nov 2014 16:32:53 -0800 (PST)\r\nReceived: by 10.25.20.216 with HTTP; Wed, 26 Nov 2014 16:32:53 -0800 (PST)\r\nIn-Reply-To: <20141126224605.GH8770@brasil.brainstorm.fr>\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr>\r\n <20141126224605.GH8770@brasil.brainstorm.fr>\r\nDate: Thu, 27 Nov 2014 01:32:53 +0100\r\nX-Google-Sender-Auth: T7BRZCnHG_oJWp5rq9dqxcnoxP0\r\nMessage-ID: <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\nFrom: Kajan Sivaramalingam <kajan@kajansiva.com>\r\nTo: =?UTF-8?Q?Liste_de_d=C3=A9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nContent-Type: multipart/mixed; boundary=001a1133f024ecc1a90508cc489b\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--001a1133f024ecc1a90508cc489b\r\nContent-Type: multipart/alternative; boundary=001a1133f024ecc1a40508cc4899\r\n\r\n--001a1133f024ecc1a40508cc4899\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nVoici ma contribution.\r\n\r\n\r\nA bient=C3=B4t pour la d=C3=A9lib=C3=A9ration.\r\n\r\n--001a1133f024ecc1a40508cc4899\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\">Voici ma contribution.</div><di=\r\nv class=3D\"gmail_extra\"><br></div><div class=3D\"gmail_extra\"><br></div><div=\r\n class=3D\"gmail_extra\">A bient=C3=B4t pour la d=C3=A9lib=C3=A9ration.</div>=\r\n</div>\r\n\r\n--001a1133f024ecc1a40508cc4899--\r\n--001a1133f024ecc1a90508cc489b\r\nContent-Type: application/vnd.oasis.opendocument.spreadsheet; \r\n\tname=\"matrice_choix_fw_js-kajan.ods\"\r\nContent-Disposition: attachment; filename=\"matrice_choix_fw_js-kajan.ods\"\r\nContent-Transfer-Encoding: base64\r\nX-Attachment-Id: f_i2zdzisp0\r\n\r\nUEsDBBQAAAgAANIDe0WFbDmKLgAAAC4AAAAIAAAAbWltZXR5cGVhcHBsaWNhdGlvbi92bmQub2Fz\r\naXMub3BlbmRvY3VtZW50LnNwcmVhZHNoZWV0UEsDBBQAAAgAANIDe0VD4JS4byQAAG8kAAAYAAAA\r\nVGh1bWJuYWlscy90aHVtYm5haWwucG5niVBORw0KGgoAAAANSUhEUgAAALAAAAEACAIAAACcaGvI\r\nAAAkNklEQVR4nO2cCXgURdrH+54z932ScARiCPchKocikUtcVBQ+5NQFUWCXwxMQBBRkQQEVUFBQ\r\n8L5REAEBCSCBEAiQQBLIfc8kk8x9dfdX3T0JlOvu9z0uM8/6PO8Phc6ku+ut6n+9Ve/bVcMsUqn1\r\nLOdxOkmSIklCFERGr6doShRFEv2MEAmClP8RSelv34f/dPCb4/ZPEPiZ6M4Up6I4lnc6RC9P/uYS\r\n+QT0oSAIvCAwouCxOymW4T0eLiiIpGnp14pt/8YGkpTOIdrM/13D/nVFlPt7vV6CImme/30DpHqh\r\nVhIohqW1at7hFNH56FpBkCstsiEhotcjfS6gn0UKXdh2Z97p4iIjScHrsdpIivIVSRAer5fmvV6X\r\ni6JZRqcVXE7e7WV0OnSt1PqShYTyMHzWtjep9NR0yBjB6WqruPy8SPmR+U67+fj3HhZBoCozQVr9\r\nuCUrwtM7ee12kaRYjrz01haroYVRcYLHg1QiCjwqj1ZxUlXluwpeHjWQVH+KUurJu1wky/osRAbR\r\nDDoLPU9UJDpF+i3NUAwt8DzJMKLLwUSnjvx4Z+33H+SsfYcLDkbnIjXKNfFJD2nTw/NWc2tstx5d\r\nJj/KadWEx3bxrbftRgsqGt1VujNSLY80gx4JozSoVGkKGSzwbjfNcqLIC14BnUZQNLo/IbTpSLJQ\r\nfq5yRRTjKflC+ffSH5qirA6H4HFHdr4tbfKjLG4AqjYqTPB40emUPmzoO9sJw+VDsxdzIeEeJ3/v\r\n55+3ZH9rdgT3nj5677iJt2/a4bz8c/bS9aqwUNQOvN0WP+rhfgum/7pooaGwlFGrkGGy6USTsbnH\r\ntCcSBvRwNTVc3bU7ZviYmIzUog92NReVkwxNSS1MtjU1iaylaEbkvajOvMOe9OBjdyyde3z29KpT\r\nhZrwELlleNHDo46HKotO490e1CboPkim6Dc0xxLKA1WqTBI0SVrtdkZwOxsuFvZe9HTJrnfP7/gk\r\n89lnNIlxppp6t8OuCo9AIqWDQzitylpZRau1qLsSSDNhIU6DkQsLF91Op9Um8oS+Q5KrqZF3CyRN\r\nolLdrS0kwzFaDSHwTrNFn5LibW1ytNpUIUEus1mqgMno8fBsWLDb7WaRtjxuXhKfLGFZuxRJeUWv\r\n3eLIfHYZZb5y5Jllg9/cQOtUcXeOje0aem7dFkavtbe00togVbDe1dKKupQsXUGwuwhOHZTSwVFT\r\nRajCB/7jheJ33jQWVaB6MyzLe93oX4ql3VaLSLLqEL3d0BzUMcVlaHDbnLSKRdJXZC0gHQu843cN\r\n6BaW+8pGWh+sSUr0tDSZrl23NlgiYqO52FiP1e7xuKuOHGo5e0YISaP0UQRDVBw+4LyeF9T7jt5P\r\nTTq7bLnd7FDFhOe8vKzi1HltVARqAVLp98gnuZ3Gq8WDli26sm1jU/E1JrU87vYejRcvsbGpmlCt\r\ntaLS6+aDUpKdhnr0HBg1Z28xMbpgVs15LCbDpRKSVXGRUUx4iMNiRg1Ca3Xq+DB7Ta3HbGaCQoM6\r\nJtpqajwtLWxohCpcY62qoTUauXvIvoYgeakTelFvI1xNJoEgzKXFhEpX9fUXPBM67sD+1qvFoV07\r\nXfvqO11igioilhNbr3xzbMCLC5ou5of16G0rviRoIsLigw9Mmpn25MKYHh3dTYaWiobO47JOLZzr\r\nErt7m6oSxk5WU00uMSxpcB/kIPLfeD3qnvHx6QkuL+Opr+BdDqfRGD/2kf6zH8yev9BmsJAsRbS5\r\nednZod5Oex3OTllj+lUbCze/4SJChy2eS9vq685e6Tp1BuUys5FxvMPG6bjz69d3mbXQXXQi7/29\r\nw7ZuctbXuFoaLEZP6t1D3Q21mV16uspyK3MrBzwz6+is2SmT58R2jfXwnLv2mqnGHNO/B+G2nlm6\r\nzFzbQnO0osl/acCipxlPc9neQwljHkLOJWHo4PMvr3BbzKruSX2WrIhIjTy96o2EO+4UGopaLWgs\r\ncKpjkpOHDG5gbEkPD+0wrH/Vvh9idElJd/Ulhw5kWKb8cA6r10pjiuy8kBtzN7Wg8t0mI3I/HnOL\r\nraY8vM/Q7k9NdZtt9UcP67v1jx+YIVhN59/e0XPx86TFqEntWv3Fe2fWb0OeBl0YNywr/W9Lavfu\r\nzt9zqP9Lz7pbzXF9uuW8vKHTY5NQ33ZUlpQfzR+44hlUXOOxfRe27GbRICgIynihVBk5W4JWq9Fn\r\nyeMmJE8JL9/zfsHXx9WxCdaC3POvb+bdrpay8tg7h6c8dNflj7/Tx8Ve/Mcqno2LjGJ/fXvPyG3r\r\nYwcN7vrw6JqfD8UPHWYq+pBUBWnjEtInPe0sOtFidDZfqx346tycxU8lTpjf7eGRpXnVGQ+P/PXF\r\nF9wussdtvUK6D+zVV5f36srW2maVViMoHrt9kBcIVk2fX7OC1a/KmDUnfcr//DRtpqXBQFTll3zx\r\nTZeZ88NiNadf3aBLyhjw/BzRbeMFVhMZ3XX6rJAI+vDEhZH9+9FhsehGhdvezVy6ObJz0uXvcjQR\r\n0YTTgoR724NZJxcvVCV37zNvetkP+5NGjYkf1Kd59w+MGjlboX2awar+yYBGo1iZV3c6T5WYQlN0\r\npwmPxt7RGynHVV9+evnKiScPx3RLooNDw9NSTTkNBKFy1FVrEjvowjTNpWWJPWIrDp8e/eNKT20B\r\nE9s5dUxW6f5sktSjIbdtXCekcQEd0NIAgYZCkRdDut2WMKBfwY53vaQ+Y+rDVQcPJI8eE/LtV/YW\r\nT2Ja/JkN6wW7GY3vyGx0YcmH74Zl8X2nTSv8aG/l4Z/VkXHdHvlL3LA74/r3bzhxqP5cfpfHpobG\r\nBdddNXSe8FDB+59IV+ETCQYJw2OxoolA5befVhXUB0dppLFZIBpO/XJp6/tDd36SekdqefYlr9PN\r\nO1yoqZwmk9vi8GrtzqYWgfeguQLNMa7G8uy/LzBdLVan9uq3dFnT9erkByaSP35R9l0xSxPIE7ha\r\nrPowDSrb3Vx77dOPvHRExlNzgzt04EKjIjNuqzp5idDriDap+vQqKUIb27/nkYnj6ciUcd9/G9e3\r\nt+B2i06Xp9WMxsWWwryrO3fGj5lByFahX6HpGKeLdrWYHI0N1z/9stNf56Jfuc0W9LfHbkMzOGm6\r\n43Sgm3tb6ko+2h3Ud0y/+TPdhopjs2c7a2tZnaZdDfLj4QlOMuDopPFURJsBaN7V2sKExd+5emXp\r\nZx84W8zSfEU61+s0NnnRrImg0GjtcdhRc6HSkINBfd3rcKAhGs1IPFYnengeryN3+XO8xcKimSDP\r\n36gzmpd43dJTUXG2uroIkUEDYs3Bj0p6pN/21zmNZy+gRnE3VSNrm68UJ07QmgouXN21U5/SGTl/\r\nUW49r82KBlCv0xN914jbl86/8sHHaBS1FOdf2Grut+hpTXi43aZGY2bpx+8Wo7GVVhG+qddNghAF\r\nMqJvL/RZ1KAh149uqL7UFNJ/CMsREX0G6hMPoD7HcKw86Q2K6JmJah7auZsuOlQVyYanp6HPCdHV\r\nVFIV3i29qbjGfO1K+b4fu4zolfvyymE797RcvNB6vcxU3dR1ysyQXp0rPjmijcvkwmOj+/drrrLq\r\nk+MbDnzcagnu9cJKQ0Fxw4USaXqlRCU+/0UiFaY8+D9oPKo+V0qKdmNhYegwV1zfAZ0fHc9qVbrO\r\n6ZE9M0SPHZ2ePn1GSFKMyBpKvz2Z9pcXBq1a0ZiX57RLCug2+VGScIZlDuj6sBP9GNw1Q58QzYRG\r\nxw0darxWaqptlowvqnYZan2NI96wwOt0/daAu50x/Qeljq5hNRyN5sksq49PdFEiFxXfe8Hf0Chm\r\nKCnvFBftSUgKywhGzRvZrxcXrNMnJForHeqYTh1H31WXk5vUp3NQXKzF65TcYHtAJAVfakddRdmJ\r\nvPTZf/PSodF3Drm28+24e8Y4qworfgmlHbXGkghkrfHydXVsrDo8RBuWHpGZ4ba6xbZILmnMX+JG\r\n3Vd18HtBUHH6YFJ6QETiveNaS4sK9nyV1DOx9vCJlBH9QlM6tFZWIRWTJP2bQIOhVGreasp+7kVa\r\nrdLFxngsDkpwnl7yEur9+riIC2uXN48Y6mlp/iX3VzTUnXhhibPJZP5sRwVDuW22Uy+9bKmpPf7k\r\n7NSxo1QhejQGmvJOHvvbMy3FJb8+u8h67QqnFo88PrPDiHsqDv5UnZ0bPch46moOwWoYlSN3xQqv\r\npdlmaLYWXxApFk300ARYmm8rcwgURxACqyLz178W1a9naHJMzgvP1ucW2NetNg7sgyanhW+t53Rq\r\ndXSsueLysUUvakO15YdOsUHq+mPHf5pZFdevB+oxTTnZx597SaUlK7dtaM7t52ioy87Pc7lEw/ED\r\npy6fIjV6wnnt6IwZHbKGq8ODvQ4XxTFy9CEbQKGIk2ZVxG8NeG1Vwp0DHFWFh/86L7xzYt3KFaLX\r\nZamsqc/I0EeFH5w81Wb25K9b47G0oKqceH4JetZ5r7wsuqzNV0p4Yz0q4tyKF4yjRutiok1FhShk\r\nRf2VFHxhIsHQKGA6vWh+1bC7QzrEX926qSY7R9/FpBl2hyH7h4oDB+mQmNRRWaqwYKqcuLL1DUbF\r\ncCEhbnM9o9c7qgsOzZ4b2im1ZMeb6EyR0pxc6mQY4cSzL6DQVx0VrdJZTr2001xtcDc3RqZ3Er3X\r\npUoytFK20gNRldEo5an89iuXy4PcEaPRUBxnPJ1d/8vP0vxCryeE+vzX86SwTZpwSPEuuoXknURp\r\nxoX8J61SoU/OrXkV3ZINCkax27XrxVyQvmLvt0hqaBS0lBTm5Z1BNUezp9LPP0U+WbqEZa4UXkIx\r\nGPpT+/NRWqWmNWq3xXojc4B6J48iD7fh7MmaY4ekiY5Gw2q1Tbk59cePocul9hMERqNFTXzt490o\r\nvpANEzm93la1v+K7r1EprE5b/OH7qBMirdcdP07JD1samNGVXp7mOFqrNRcXnss7Q6CxW6+X7iAq\r\nyRYpoHa6nMiM3zPgKK3WkCJf7vZK90TNyLINp0+jezI6dBOy8FI+alkpPkeRoRzNotsxKpXx/AUp\r\n1NFpC7a9jdqB0eqkYL4tX4Lu47bbXB40gojFH+yUokSNBlXHdPGc4dfjyG4uKEg0ms6tyUVNz2jU\r\n9SdOSOkH1AIoTJNaTbD8UFbuRmEgJUXyovnKjnekqSpSNgrpvWjoEFmdDkWbZV9+VuJyoyqwWo0c\r\nrctuUQ7lXW4no05MTB4/HsWP0mXoIkHuHBQlxSMCmuz4jttsJiUvR9yU8hGkYZOk5LBeHsakTADP\r\nS7GvlKK5+W6CdCxlnHhJT9LjaROWVO6NGaWUuaIol8NhbW6O6tBByl6Qim1Sy0pNLNsgjbhoyFeS\r\nCu3jvpzSkNMnqHRBMky+o2SVEmBJBpNK4stnUpt57bk4dIC6qtlgQMchMTG/Y4B8kxuFKjGCdA4v\r\ntleN8CU9UA3l3IdsjOhrYbl3ycc33YepqopITJTDDUppamlOg+pD+ywk5F/57iYnElEL+B7qjavk\r\nByfplGob/nzpBlRl5IMlsbZX/6Y2V6rMJPTvd/fqlTzq68hb/XegtK+tubmhqKjjoEFKPwuoAbJQ\r\nUOnoOKZrV+XHAJRbfOxY2rBhASjon2mvMiP3NkJJSiKFIWUoOVFZa9KpPpkjZyAnPimlq8lO1Waz\r\nIlemlSeDPpXKo9FNF/5zevT/YRxSK8siL4f+k2yTU6K3sPL/plyp27X5G+QYlJogA/wkCLl9pJ6P\r\nZu3IrUqOXc5/B4y2twSS00IOBZUup4EJNM/TnDr0zVdHLm5Ys7zdH8qJZMmryy6XVh60zynJTri1\r\noeyphcs2vf9pSpROyuDL9VOSz5Sciqb+gByINnUx8jSTkEb9AHgI6a0ESdma6996a2urw9Wl58Ax\r\n/XqhCaNigD8EIbcPgWTAcNQ7mzcPHjMhSKdB0zXUdLe8rH9tAMnzPMMx+778mI7qOqRLDNMeZh3Y\r\n+8Ouj/ZPmTmrV5c4u8OhUqksrSaGU+t1WtRZGhvq0UzM0NTaJS1No2JdTgfFaRK6dGN4h82BQmdd\r\nfW1NcFgkisVQLI605vQIapXqxhuy/35k39ZiNCSn9Zo08YERQ+6iZy5+ZER/f5XW9v6MYZjs/Z8t\r\nen7Jz3ePC5XfKbZ3K7/S5ht4mqaN1SVz5sxZuO7De29LYpTueP1iTnzfuxeH6Ldv3/X2uheu5Z98\r\nbvWmwYP6/3j41Dfffbtn42qbKvLET1+Nmb74wpFvKsX4GWN7/3Xxhr1fvIckHhSkP/nT3lP5xdfL\r\nSp+cM3vTayvdjLr3oKzFT05DXve/Z2ryfyB7voS0zIdT0i6fO6WPiUtJjHG7PWq/lYfmRkgNZYV5\r\nB09eGjl2rNVio7hAdh7ZhVO0197y7o499457SPR6UJdgBDk0OHnssMEd3iEh4b23vxTWvdAhMc5u\r\nJ19c8lLl5fHZ2UdOnM577/v9QsNlklB3So0/cawqofMkW2uT0+VBztzhcKRl9gyKS17+7KJmm4Mm\r\n2IkzFg0fkCYFb/Rv8x7/tZCE7z2PxdT4zdffOFykluN4LHl6a5FjDdG17f1P5j3z3IrF89pThoHy\r\nqVKUgwKOd7Zuvf2+hyJOf2d0utDTZKRsI0FcKWvsM6x3cqQ2nNmzP7tgbK8QlpE+V7NscGRMr56d\r\n1r2ylg6KnfDQfblfvaHW6qTAneUY5G6k16ieNUueGzzhiSCdlpReoKujwqO0Wn3go4P/BFEOmO2W\r\nVlYbuuyVDf9YvuCt7Tve37TGX8VJL6/p/FNHDmefoDj64M9HXJrEl6aOj6GIQI2zSA2Uq7Xuy+++\r\nz6w1nT26jw/ueH//2xitVnP6xI9VreKaB0ajs/qmJ23e/HrXl2YZmg3NVmddQ0NDndHY0HS5obpX\r\neueaqvLIxNQz27d893W8wWgsLiquNxirKytKa+t72m1ozlF2vbSuoa68uvL2HkkEEbC63QLk0ZQ5\r\nf/rnfSeLJv9lRPb5opH3PuJ22tVEuD+KU2apGQOG//BNTzVHFpw/N+b+sXoVLfyhuOyPmYD+Z4Oi\r\nP//yG7VGs0Zs9YRldO4Qw6A6h8WmTp34gNnm5ETXY08953R7W130xvWrWgx185a+olPTR3lmwcLF\r\n9qq8J2bMPpH909IFTkoX8c6W9Q6nc+nKV6MiwtZvfDM75/xzqzeguXrHZ58PDgshAuf6bg2UnMLq\r\ne9eIViede6Hg5dc2J3NUi9kaG++X4pTGYRg2Ni4OHf/j9U2xCSlN+blKgssvRf6eAajWkVHR6HjO\r\n/GfZoEhnq5HhBaJr526dUzrLsz/1nYOHtF8jPd3UVKuxwuFxMRTZ3GwbM3a0hiHG/GWClH3zZTxJ\r\nZVraOT3zN/7gzyUIxVq1JmjU2HFKZrP+yhX/5aOUthLl9DYqO61bOvqowSso7RmwKEPJICAbEpJS\r\n0BBWb6hjzPX15WfO2E0t0mRAeo3Li76lSyhIpVCQqlJrFj86MfeXn8IjYkcO6l1y7KhHXghJtt/X\r\nl0y9kfeVsqg03Z6s+gO2otDf0dJiqqyU1rlJafUA5CF8DYQiI1EQWY2muawMfW5tavKjAaIvtSPw\r\nUsa9Jj9fJAI9yPriT55nVOqmslImODY2ZcAAl9NJs6ySoKSV9Yby2e0OoNcD4wlpjaI0T2zL0d84\r\n4dbW4UbqOjIyYKnrdkHwcoKSYdn6qChULT+lrn3ZBmlZJ6+Ui7oQatnOQ4cGLMpQTODlBRmoHzAs\r\nFxQV6UsSMHI2sN2HEG3a9S1ylGNTaUFpW95aOU3+0Zer/lOND7+D8hja+4P82W8Xj9zy4qSVnlI2\r\n1hfzti9rCAg+E2jFl7cVzHAce+HC6d27v0jt2LHJ2Dh89MN39c/0er1Su/je1UlLq5X1bUpnJeWk\r\nttRrfS1G+q3dAofiBloaqrds2e7wuLv1uSur922Cx+Wv4nwewrNt4+baZpM+OHrOvKdo8nc2DPgN\r\naXaAvNLh7z47lpPPE9TjcxdEciyaVJKZ3dO+37fvk71HPeXHp02ffCo3P0LDNNTXsmpdeGiI2+kw\r\nNDVHxcRyjOTVGupqKVVQiIrfvuvTiVNmRASr28bBwFTDXyjaNjbUJ3bsOenhocPvGUnPW/bAsF7+\r\nKxEV57K2CqJuxapFsx4a9Tqtn5/VT3qzELDcrvzITM2W+c+vyPlmy98XPP/5m2sY+UW4XqvVRkZG\r\nR2gzVBSl1ZA/fv7+L3kVNpNx2rwFpbnHy9HEs6Zpy5sbNq9dXu9kVV4bSXp3f/Z9TGqPh0ff+SdK\r\nNvwblGlKp8y+qd370ZQQHh3m1+KUFlMFRcya/wQqODohhmzfhBMQUC+m5ReQE6bPtFktl65VZPbp\r\nQYkCo4yWTrtl+6bVn+/Z9dLGDzQEsXHTlscWrCk8/tHuz79eMnfa1YvnDh7eeOnS2a/3ZX/781Ha\r\nbryUe+Jycd2Do+8kBF5+X+rH4TYw+PZU8V6GYb/Y8WZIUq+sIf1aLa0a/xYnvc64lv9rbpHxw1WT\r\nWy/mhEibtALRlL75onxw7vTx4yfzsiYO8Hg8Pu/EaYPnLXrRcDXndE7uY/cPQXNIr8A//reltqba\r\nBU/Pf2LOLJ1G47LbNBqdhiNUXGRibJTb46Vuurv/q+B35LdN7KmjP16u8nywZV1jcZH/xnNlQoYm\r\narUVRdv3/Lh954exIaoiD//HFgz8AXx+XeBbTM1DRtzfJzNjzKQZI/pmokkuZaivbDY2Xr5atnnr\r\n1m6ZA4YPGThwQN/jx37un9nR7XCUNxgcLnttZXlQTMdQzrFs+bpxWXfpaaehvuLIr5fuGZR5cxLi\r\nz4vyeC6fOvDIYzOnz5q7bMXK2zP6Dx6Q7qfiRHl67jE33D96VPch43Zuez2997A74oMEefVhYJpS\r\nkJatCK+tXX336EmleT9FJmfEhIVIm2ndomb7lq06wqoKT/vp4IGyutYX12z4/qsv8/LOjxs3du1y\r\nsdpofePNNzWc5v2PP/viy2/r6g333Xfv6hVLvXYzoeTabqmTkKY1ypJM+c6CH185YqWiv6KSu763\r\n4z2n0+ml2EidHrlQxYBb/oSkoJ0gPCS3ftM2u8Nltlg6pSR4rE3S/lM/FPe7BkhbJhl2ymPTzl+4\r\nHJbY/YN5yxxlJYzXKyTEJcSOjFEWLqRl9E7LkC54dMp05crBw0fefKOpM59QDu7OGqsc0Lf6Hbcv\r\nxaksfyWUZa3+f2sqFxGTmHpfYmr7AkOvvG+HarPklqMNCrv73qz2H4uPHQtciNHGbT36oP+UKttE\r\ngrEYGqsvXrQ1NdPSpmZ5AbTcBIKcwJIy0ChclRZTIBcn7aHm5bw1RcsnyGkc8eY9/7cEJXVtMjVX\r\nVNIc53U6A7GmUslUChJSplKtNl4vRfV1Wq1+MeDmTLm89B+1eV1BAapvgBIRbS8WBHkHPTpWabWG\r\na9cZTXBIYo8e5sZGluOUROZvJgRKCkWr0Xo9brdvj7a/TZUW2VoNRnQc260bmscEbF1Fe2Kf02qV\r\nxaGx6d08doffFtnKu4opX6bS3tyE6uuPgv5F8dKXThCib78Ao9Ggzs+wKhX6lSY0TJC/LkDDcZTy\r\n/kKJi30pfuFK0dXwqPiYiIj213T+04XvqXi96uBglU7HajSBEUR7rl6Z8WlCpJf4Kq2OU2sCswwf\r\n1VcX7pflF/8nypChCQ1lkGPIO/fL4mdWDL/vfsZjthHcyqUvyvtM5C+UkVejo3HhwCfbmjQpq59f\r\n6PV6pBXb0lYTUslnt7/kvFWxhtj+BTE3vTf5z2/7/ymUl2ONmmv5z6/a9vKzi4JVAuF7zXCLDZAG\r\nC2nrHrX8ubnnCispmn1x5WuJas6LWjtQ7lDJin6+6633PtvHUtSjsxeN75PGeHmiT9+BBlPrI1Of\r\n7BLHHj5ytLa+zmaxokCU00dEBDFXikrvuy8rPS3t8+OXf805k5TcMTEusqayNL+geNDgoaLd1GJz\r\nOe2W8Jik2MiwWxl/kiTR9sIpcDGttMvK+4+1a84VNkpb4Ii2TVF+MIBGt/faNRHJuz/bGKoivAJR\r\nfrJaUUOA6iuXYnbwr7y+tV96CtK+oahIWYZPCR7nhbMn9hefmfTk87brJx94bOG8uU++vXHdIzPm\r\nFGR/f6akYXhqSH194/mcX55ZsvLdTateWbXhvuFDZs/6+0vPTJv2+FO9Bw166NHHRw3pp0TzgaiM\r\nH1CM371ta/c7s4ziGYfLHazyY12kYZlhTI21X372sU7N3f/Ag1RbB/BfoTeVLiobMLwu+8H93189\r\nGzRs9HhtW5Aj8gJvbm01t5o9PB8blxAflzJn9l8Lj++/454xY3onvXOg1J0U06PfgKfmP5Nz9tdN\r\nb6wzO+nEDvG1NV/wlDoxudvGjVv1NC+2vRH4MyLKajh7bN/RS9Vrlsz66OOvLBZrbIi/3mjIa5BQ\r\ng3Hz5s93eoRdG185fOLi2lkT0QBNyXsv/VTuTQagCFF6WJOmPV7b2HIxe9+0KTM+2PS6712GWhs0\r\nePiotCkTzp87b/BaVZxPKPIXnIksw5CCLz/kdZPa0BBOxd8+dOSR4WNtdVecbl66M0Xe8oE2kCim\r\nl5eXW5prX16+8nLBhW/37n9x4RP+Kk6e27vsZpOVz7yty8jRI1a/+51HCNzY2Lbeha+qbU7P6JYS\r\nwW799Ptms5VhKKLg8gWXzfb1px90iNIcOXFu1IhhNmtz4bVyo8lSca1Yb65oaKzj2Thj1fW3Nqxl\r\nguKWLV827X8mv/Lq2iHD7k7Ue03N9cXXK3umJfPSfJP6z0Wh7ESW8h9yLqR9j6VfkdrG631o6pMT\r\npj/taKk2OZgZUyc6rZYg2QDSD5NKimZIr+utNWvuyBp+6IevHpsyh/TYeV6gApKIkJdSiizHfr1z\r\nuzaxi6Hs/F1ZDyVHhTCCwHfpPvDXs2ddLqfbw496cKKaY+4ZNRo1wdYvfyClrzi5J2syGazV9bwj\r\nq76pdfbfU1ma+mLv3pKyyoSkZJ2KPnB4Pyt9DyGBnuKtijIIOSem7PORDgKYh0B/q4LjP97zXmNR\r\nkSAH15IP98/eTi44asO2jaWVtaPGjo0OCyk+diyOC9zuLXm5G/Hi2ldLyipUmnEdE+Ol3d8NdfXm\r\nomLpC+5Y6QsgrdW8WYrCaeV7Sn1ff0mQrR4Pw3HRLFN95rS0P5VlY7Ua+5UCiyAyDG3jBYPXe6v8\r\nneIhbCaTsaKC4VSegCyybU9DKJpQa7U1V6WvA3Bbbf4zAJWFwv44tcp9veSa01VTUIjqG8jXhMpr\r\nowiNhrdaK+pqG69fZyKjo/a/+kr7EtObV07fuExeJSc3lbRf2JdzFURln7LY/nUZt/Kdpy/1VXHq\r\nRGCXjdzYWKDM96/8uN8fT0jJsMg5P1H5QlV5vzxRdeZMIKcRbV8wIr33REOjSq9npuzaFaDygT8D\r\nDB/Yb6gA/mtR/CLzp9muDwQEUAOAAYIAMEAQAAYIAsAAQQAYIAgAAwQBYIAgAAwQBIABggAwQBAA\r\nBggCwABBABggCAADBAFggCAADBAEgAGCADBAEAAGCALAAEEAGCAIAAMEAWCAIAAMEASAAYIAMEAQ\r\nAAYIAsAAQQAYIAgAAwQBYIAgAAwQBIABggAwQBAABggCwABBABggCAADBAFggCAADBAEgAGCADBA\r\nEAAGCALAAEEAGCAIAAMEAWCAIAAMEASAAYIAMEAQAAYIAsAAQQAYIAgAAwQBYIAgAAwQBIABggAw\r\nQBAABggCwABBABggCAADBAFggCAADBAEgAGCADBAEAAGCALAAEEAGCAIAAMEAWCAIAAMEASAAYIA\r\nMEAQAAYIAsAAQQAYIAgAAwQBYIAgAAwQBIABggAwQBAABggCwABBABggCAADBAFggCAADBAEgAGC\r\nADBAEAAGCALAAEEAGCAIAAMEAWCAIAAMEASAAYIAMEAQAAYIAsAAQQAYIAgAAwQBYIAgAAwQBIAB\r\nggAwQBAABggCwABBABggCAADBAFggCAADBAEgAGCADBAEAAGCALAAEEAGCAIAAMEAWCAIAAMEASA\r\nAYIAMEAQAAYIAsAAQQAYIAgAAwQBYIAgAAwQBIABggAwQBAABggCwABBABggCAADBAFggCAADBAE\r\ngAGCADBAEAAGCALAAEEAGCAIAAMEAWCAIAAMEASAAYIAMEAQAAYIAsAAQQAYIAgAAwQBYIAgAAwQ\r\nBIABggAwQBAABggCwABBABggCAADBAFggCAADBAEgAGCADBAEAAGCALAAEEAGCAIAAMEAWCAIAAM\r\nEASAAYIAMEAQAAYIAsAAQQAYIAgAAwQBYIAgAAwQBIABggAwQBAABggCwABBABggCAADBAFggCAA\r\nDBAEgAGCADBAEAAGCALAAEEAGCAIAAMEAWCAIAAMEASAAYIAMEAQAAYIAsAAQQAYIAgAAwQBYIAg\r\nAAwQBIABggAwQBAABggCwABBABggCAADBAFggCAADBAEgAGCADD+F6UJ3EM13PguAAAAAElFTkSu\r\nQmCCUEsDBBQACAgIANIDe0UAAAAAAAAAAAAAAAAMAAAAc2V0dGluZ3MueG1s7Zlfc9o4EMDf71Mw\r\nfieA06YXT6Dj0OaSa67NYJLe9U3YC2giaz2SjEM//ck27hDXbhxsZTqdPgGW9Ntltdo/8tnbh5D1\r\nNiAkRT62RkdDqwfcx4Dy1di6nV/0/7TeTv44w+WS+uAE6MchcNWXoJSeInt6OZdOPjy2YsEdJJJK\r\nh5MQpKN8ByPgxTJnf7aTCcufPDDK78fWWqnIGQySJDlKjo9QrAaj09PTQTZaTPWRL+mqqah89r4o\r\nRPwmKF2QK5MJs4fDV4P8t9XbKblnGtuaFHYo/v7kbCcg/+hTBWFqm97ucara2NIinQ2F5JvVrKp1\r\nj9fcUUkXDFwBZI6RVQyqbaQHKVfWZHg2+B7yLPA1LJUZ8mcaqHUV2raHJ+3xl0BX60rV7eHxcVN8\r\nPyRRn/IAHiAoi4KkepOyNdq9xLaJwpBcBSUtpRLaA6xJ6g+jZ2maQkt6zok2yFOKPl7irQHUqIED\r\nTmMhUdygpEr7/79Vxn592EY+Jv9XRR69OQx9iYJ+Ra4I8yJG1T8YQNn+axQtXByEor4pekn7wkJd\r\nHtF9/Q3wXV/RDWT0GeGrGvPYh8ELfTsOWwV2VhdTWnK7Dd8F9RyVwrBD8BfEcK4pnXp0Cr0jLC5T\r\n80M+PNQGZAVpcP0h/dA0460x+UvQctxeIDIg3JooEUM1ueLhfrqoG87ietOjlUX8mpSSh/bWoccX\r\nyNiCiPoc/ua3i7V2sVTAuS5l7m8EpLVAnbstCZM1/tZEzBcQmOkvn+/PTfgfUZlCH3YInyKn1Cky\r\nFCU0w/QIjeyTY9t+fdLBvhqwyiWRWvU45DNMLoEEujUxIiQLJDrSGKBfyU+x0u0ceNtwgUx6UE65\r\nnQjxOInmOCNSQXmjuzhZOfhK7loSYxJmIPV+19bfo4MjcRlfXYS3xHvxIqAbKmvV7wherfyhrpPj\r\n3QcqvS331wI5/QovVw3s+tHqCRJU8xuH/EEsSLrBz7l6uEYSzHR4Qc62JtIS2cBdfqvyiU8ZShPn\r\n50q+210EeWsi6vevlYgPILgrKeE3MfdVTCqaqS4EuVHEtrcSxDuiSPfRcqoNRHzt81MMIwEy3ZjO\r\nq7QbXaKmpxZUXO6GFkTCyatzyonYWhPXdcetRHzUP6oL5MHTjNsoIAouhK5TIYyY/t69vd1Y4ZQw\r\nPzaDfx8uILjQpXxt9v6pE98L5W5DuelFyhuXMV1npt7+Ny6mhPvAuhdiuAAxW2VeU36fH+X6O7rG\r\n18SmCoQn8+Sv0E2YbrfMNIlGS0zDtbfhrrzNhUJtSTv47rXaoO6F4+R/UEsHCCcA3WvGAwAAshwA\r\nAFBLAwQUAAgICADSA3tFAAAAAAAAAAAAAAAACwAAAGNvbnRlbnQueG1s7VzbbuM2Gr7vUxgaoNhe\r\nyLIkO7G9cYoudgcoMNOLJlMUKPaClmibHUkUSMpO9o36HH2xJalDKFl0pdjWaFLlwjPif+DHnz8/\r\nHkz57vunMBjtIaEIRyvDHk+MEYw87KNouzI+Pb4358b399/c4c0GeXDpYy8JYcRMD0eM/zvi1hFd\r\nptKVkZBoiQFFdBmBENIl85Y4hlFutVS1l7KutISy56CxuVRWrRl8Yk2NhW7JFqyb1yyVVWufgENT\r\nY6HLg6qab3BT4ycamBvMox7GgKEKiqcARZ9Xxo6xeGlZh8NhfHDHmGwte7FYWFJaAPYKvTghgdTy\r\nPQsGUFRGLXtsW7luCBloik/oqpCiJFxD0jg0gIGjXo0JpFyFN1ckZjNHqk0pv/bbxtm132rC7O0A\r\naZxnUrmcKq7fPFVcX7UNAdtp+ndufeRC+fHxw0tekbBpXUK3FCqPoLhxM1Nt1R5jXEAVBulgl3Cd\r\nyWRqpc+K9uGk+oEgBomi7p1U90DgFRHHYV3QuJ5tcQ0T7kXKF4NIBIJqDBwrFRfK1Ne6/vXjhwdv\r\nB0Pwooz+WtlEEWUgeokMEZ2gbenMIjDGhBWB2TQnX95bToFtx8JATx1Cmqtuie/XqnI4rsVphA9i\r\nc4/g4V2JW0/nw8KSSiqfnjSwJ5bQKYYk7+4X8ifbYn7a4CTyUxpIgwGfYkiQEIFAmi1LHtQMDvAr\r\nXGZzmuKhRPQIBvnoL5pU6wZjM6Q8GXjS43ipWJfnDRI+NXMnEh37m6rHyqD3KHVZXd8+/mwJmSmm\r\nTT4xZDUpywXHuM/XBikXUKso2PA1grkBHjR96AX0/i7l+KJ4lD4L3CvjcYcJj6/IBU7BuVKIguey\r\nTHUhpOYWRrzBnBYIDkFU0ogR8zhx7gFBMsus0xA+ID5nyR4ePYCI1gD5FsSY/rOilxaehEYPiNJz\r\noP0b/g5+SU7DUnSaQHqmDIbnYHpPINQAUkTXRPAIdljw7HHC5IJza7d0qZyVg4TxihjyTOmnyHH5\r\nWcLqYbuoLEMpqY8vNoIkjIzcUi00Yz5kIWEI0tEGL9cEgs/mGvLRyx2KqnOPmfoB+WKh4Ixn9tQL\r\nJX4FzilsTnfYJrfzdtjcrrDZ4/mkZdym3cXNbott1hU2dzxpi+2mu7hN57N22G67w+bctBwL8w6x\r\nzVpiW3Q3Tp15S2z2pCtw8/G8beDszmYGezG+sW9aoutsbliM7XahI7o5leBDBRgvUVGlIlG4g2i7\r\n42vfyXg6FZE5DTih0MQxQyEITNWakQS2wa2J6atwz5xFZ7g1c/Hr4j1zOsOtmadfh9u57Qg3A/X5\r\nnReGgK9hiRmDLTTzfcIGJAGrNEppUHqE6SMaB+A5w5N5E0ctKNqaIfa5p4CYbH0MNT3Yk8eo5jHg\r\nn+zJxCgrZQfFwtGR7QnagTpOhEGQS2JAxFG0fKi0P9OQZxKqPANYoizusUpYmPg8rmvMmDhDmown\r\nNzEbURwgf/RuPhF/af8D7/OWiHMBQXyYrIx36/n0dlqsIHwEtjjiObAOTMbFEY7gkYxxcUUmQwQC\r\ntI1MihMijtb3IEigyZ7jQonAGIIiwCtjAwIKJa4DAbFMP7FHj7Apng2lYQHcyExeLGqbFQM/Pf7n\r\nDXdnea6npiQfBXUhyUdLegBrgmgrztZrBKJleYtffDMc61ClDvaiizwessxBsft62SjKyJV7M++a\r\njfwrhRgnLEARLGKniES5yXa8d7e7NIVquqikJPqmrCN3jUfHGxySFFD0Py6wnZgpZXlFhDOHLE5H\r\nyw744qD0CABPPUgkihLE3N0ho501DvwSKFG3CSgCUY5AFcoBk0lzKIo89Zor1PsWR+gBfNJ6L+R6\r\n/4WKWkPabfu6RtcpZH0v3cr/t1j5QN3Cp+cUVM7zgYJaU5AahvMoZ1JC9beinJpxfUHS0Xm/JO2U\r\nVbokHt2J1xchHs0IHYjnGmsfveeBiAYi6pyIdMfbPV8BfUWbsCJnh03XWyOeYdP1GsrRfWt1JuUM\r\nW65eEc6wxRpWNl90ZaP7AvoL0Myb3GD1g2aGDdVAO72iHd3dkmFDdbmTHU2zGhGRfhs0bLB6R0TD\r\nBus1FKS7QtbrDRZc39iuPxBQDzZcA+EMhNOKcHT3Qnu91RoIp7dbr4GABgKqISBL+0pOJlhj//nl\r\nZbWYQODTHYTs/i69FCk/swuSaWc/CLFtZGUq+4ibmSW77OZzjap88Se7dplyWkpVdWRnNfbpXMGn\r\newWf0yv4nF3B580VfN5ewec895leps1UqZlOItBfGdfIjMUVfIpXMi7v9Bpj7TpIX91PBB9qPIp3\r\nISqVc391VYur1fmLvMViZGVQRvhiwRjlb0TXyLh/IYnv3xPu6oDJ5zsrK7mzqlU3BDM9G8wDI4nH\r\nEgL7AQasUYDYn3/0Ac0nCsloDWg/QhNDj46WI/LnHxSCZNQnTBR6SZ/wgBhdBM5fzhVVwtL5uj27\r\naY981xC8plFa9FO5CKxQYzOudJpypXN2u3+ItkkAyPndOauFsgkwYGXJynDroaW6BTL3or3h1K2T\r\nW6GeNkI9fSuRfFNxmjXCNLsIpouRWrGYFr+HwgfpysCb5erh08d//Db+l7Mc/+j897vmIXCa5Ypz\r\n2WH3dZDgzxB4rGd04zTrroFuekg3fYzT26NAl1Og24YC7UWjGNiLr44C3bMp8D8C/fmdf9OLgduW\r\nllugvuIwuiLqzkiyH5E8wnQxQlqcIqQpJ6RpqzXZbbNJ/rYvhORWQsn1VM+unq+0aOwz4FSPECpw\r\n7EnHeHrX/ul8Npt1Dupy9akluVdRj2/CJ/FruuJ3HqnyQ3il783ywvSbtfyp+hvR9/8HUEsHCCzz\r\nV2FGCAAAZFoAAFBLAwQUAAgICADSA3tFAAAAAAAAAAAAAAAACAAAAG1ldGEueG1sjZJPb6MwEMXv\r\n/RTIu1fwHwgEi7i3PW3VSk2l3iJjT7JuiR2BKd1vXwMhSrs9LDfe/MbvzWiq2/djE71B2xlnN4gm\r\nBEVgldPGHjboafsrXqNbcVO5/d4o4Nqp/gjWx0fwMgqttuNzaYP61nInO9NxK4/Qca+4O4FdWvg1\r\nzSejWXlvjH3doD/enzjGwzAkQ5q49oBpWZZ4qi6oVhfu1LfNRGmFoYHRocM0oXhhx4T/G2pkryM5\r\n5y5GIz6HnuwYIRme/xf60GrdfDdAYFMcEkov4zcDww8Unce/WjhDYtnuGENUWvHQAYIRmsWUxqzY\r\nEspTwtM8KUhOVyR8FV6wagp/AAut9K4Vv03dwv30IM6SNGEJ+3kn1f3j8+55ne/yLLoidqfWvYDy\r\nGPS+XrFyVUtGaa1TKnVWaFJLTShd5yXZF3lGCl3hL3azPWjjw8XEug9qGEw8bBm7yx7P+D/lz13q\r\nr2qgE/QLfZZn9nJ5nQ9PdN6oaNK9rBuIleutD9tEs6igaRYtZWfR1eOki0wQFhX+tHj83ZGLD1BL\r\nBwiPpzPtlgEAACIDAABQSwMEFAAICAgA0gN7RQAAAAAAAAAAAAAAAAoAAABzdHlsZXMueG1s3Vnd\r\njtQ2FL7vU4yyEgKpGSezLOwMu7MXRaiVAKFl6b03cTIuThzZzswul32ePlWfpMdO7Mn/BigtAlZA\r\n7O8cf/7O8fFJuLi6y9hiT4SkPL/0wmXgLUge8Zjm6aX34eaVf+5dbX+64ElCI7KJeVRmJFe+VPeM\r\nyAUY53JTTV56pcg3HEsqNznOiNyoaMMLklujTRO9MUtVI8bZXHMDblorcqfmGmtsyxbfzl/ZgJvW\r\nscCHucYaC5o2zRM+1/hOMj/hfsSzAivaYXHHaP7x0tspVWwQOhwOy8PpkosUhev1GplZRzhyuKIU\r\nzKDiCBFG9GIShcsQWWxGFJ7LT2OblPIyuyVitjRY4V5UC0EkQGC7Oi/nOWratPJrn87Orn06InO0\r\nw2J2nhlwO1VO4/mpcho3bTOsdiPxPUdvYNL88eb1Ma9ENnctjW1JFQlazN5mhW7ac84dVW1QHXZD\r\ndxUET1H13EAfJuEHQRURDXg0CY8wi5ziPBsSDXAhAoRP9jrlLVroTY96PkOCFFwoRySZX+xAnZU7\r\nqjuVsfGjqmctNBVxPAgFOqcIji0cGn9PyeGkVcum9V8jA2rWr0mDMEAa444AyHsstiJ110HCyzyu\r\njl0lBrkriKB6CjNjtml5aGYM41/gsr5DGh5aR1XKUzWk3c010nO+vgag0NVeGrffytvaqy7hcM0l\r\nOCJ+TCImtxdViXLDi+pZk7v0bnZcAF0tLVQQC8oou2/PNV3oWT8lOewKslrwDOctREFVBOd+jwU1\r\nQUPTFF5TKLlGsMV7nMsBIo9wweWLDq4anKQmD1TKr6H2kvyBfy+naTUwcyjdS0Wyr+H0ShAyQqgx\r\n9S0Z3OAdxHwoYezE166OxlK5Hq+aOMsyJgkuWd3aWc81JVM2/Igw5ll4gQVOBS52fiGgcAhFoR+s\r\npgANXnjhx1QqnOvWEI7W8yg7SqJPYN/QEB1J54RvGM7TEqcwS3IzEEGRUAL4fXjvdV34UJtx3k0+\r\ng7F+LOTTzs7UDu3EL2/7bnV3wMjdtGMH2tGuazf121sTowHxtxdVA1X3Ua2IVOq8DbwOaFE/ZTT3\r\naa5ICnYxTamSIL5ZaMCn8xGVQkDbfz+0VBg8exfYXew5g6DonlmJkoyRgDSjGWZ+wSDpgABcgJP0\r\n7GwqeFmYNw/jHTn3Ol22C7cH89jnfp/dcmZ9HZMlEW4BlzCvrr3t33/+5Tx2nGwHJj5HMG8sz03S\r\nMg698UmSBPCru0l/eJM/nL6VPBku3PHIY1o1+3vMSvL4yaNUvbh0iYeLgtVq+63EROOxqiz7EXpZ\r\nHTbv4SL3QJk6HuapO2MWqC7tR/BYiT8iWqW+sdfxjV8TOb3vhavt7iXfb2s2kdiGY/0uT6FLo5H1\r\nZ8DQzxEBb6PEYiRnNB6BHGisX3pwqfgIoj5GZlHzb89xOBCa7qABhESLP1ec1Rep0xbW9ObdVD3e\r\nff2lfyVYf3D5dwLjjAYuZi0hBCbNfclLoS/mhN5Z5/COQ7AWE+pHDvIlmMlGFzN44YPkR6eXXgSG\r\n8L42es27LKGfdFPwrFDeWOZ8XTBrRcMvktSGY46kglffG3woh5r+OuiTRJ1+q37UyQ3v9nDo241Y\r\noTsIhu95qVp7elNkoTcA6pPSr82wAT/jMdgx4avbY0x2sLtjA9AaSziH8HUCpq+UXR2EYPn8DNo4\r\nM4xFCjOMJHq8PShqdHv0liulX8qD5eqs6gXROKOayn/PEvrWAYptOqgXgxmxW/1AsdMzt1zE+iPf\r\navl0XcBudTVfnATmlwEUOK4+IwfLIDy3Vjj6qNuQPLYV/CQK9G+nTgMBTU5KennS2+n3n0jfqV4P\r\nZzUarVX1RIalcyGPbZ0Z1J6mGq/mORgocRX97YX5aF/Uf8sdIRV6e3V1dYG6g/VI0RGhE3kdRntZ\r\nUwlt9H3vuqu0cau/03upHzTtquXchna9xliPgnXVEn2SAurp+JC01/UHygllVz1l7b2f6gtME/pM\r\nsRePK5yiijUh1fOTnhCtlVpD5gh1Voc2iox2VNCnOZBv3hjgZAXhUz8M/dVzbxsEyPwEQc1CA7c/\r\nLyzh7GHXGmRdB+EmON+crpbnq/Wz8EwfWVhiY37cpofSr72//y0nF6gJNK9p2/W6CazGvlHuouFq\r\ngYb/W3H7D1BLBwgzIvr6RgYAAJYcAABQSwMEFAAICAgA0gN7RQAAAAAAAAAAAAAAAAwAAABtYW5p\r\nZmVzdC5yZGbNk81ugzAQhO88hWXO2EAvBQVyKMq5ap/ANYZYBS/ymhLevo6TVlGkquqf1OOuRjPf\r\njrSb7WEcyIuyqMFUNGMpJcpIaLXpKzq7Lrml2zra2LYrH5od8WqDpZ8qunduKjlfloUtNwxsz7Oi\r\nKHia8zxPvCLB1ThxSAzGtI4ICR6NQmn15HwaOc7iCWZXUXTroJB59yA9i906qaCyCmG2Ur2HtiCR\r\ngUCNCUzKhHSDHLpOS8UzlvNROcGh7eLHYL3Tg6I8YPArjs/Y3ogMpuVe4L2w7lyD33yVaHruY3p1\r\n08Xx3yOUYJwy7k/quzt5/+f+Ls//GeKvtHZEbEDOo2f6kOe08h9VR69QSwcItPdo0gUBAACDAwAA\r\nUEsDBBQAAAgAANIDe0UAAAAAAAAAAAAAAAAYAAAAQ29uZmlndXJhdGlvbnMyL21lbnViYXIvUEsD\r\nBBQAAAgAANIDe0UAAAAAAAAAAAAAAAAYAAAAQ29uZmlndXJhdGlvbnMyL2Zsb2F0ZXIvUEsDBBQA\r\nCAgIANIDe0UAAAAAAAAAAAAAAAAnAAAAQ29uZmlndXJhdGlvbnMyL2FjY2VsZXJhdG9yL2N1cnJl\r\nbnQueG1sAwBQSwcIAAAAAAIAAAAAAAAAUEsDBBQAAAgAANIDe0UAAAAAAAAAAAAAAAAYAAAAQ29u\r\nZmlndXJhdGlvbnMyL3Rvb2xiYXIvUEsDBBQAAAgAANIDe0UAAAAAAAAAAAAAAAAaAAAAQ29uZmln\r\ndXJhdGlvbnMyL3N0YXR1c2Jhci9QSwMEFAAACAAA0gN7RQAAAAAAAAAAAAAAABoAAABDb25maWd1\r\ncmF0aW9uczIvdG9vbHBhbmVsL1BLAwQUAAAIAADSA3tFAAAAAAAAAAAAAAAAHAAAAENvbmZpZ3Vy\r\nYXRpb25zMi9wcm9ncmVzc2Jhci9QSwMEFAAACAAA0gN7RQAAAAAAAAAAAAAAABoAAABDb25maWd1\r\ncmF0aW9uczIvcG9wdXBtZW51L1BLAwQUAAAIAADSA3tFAAAAAAAAAAAAAAAAHwAAAENvbmZpZ3Vy\r\nYXRpb25zMi9pbWFnZXMvQml0bWFwcy9QSwMEFAAICAgA0gN7RQAAAAAAAAAAAAAAABUAAABNRVRB\r\nLUlORi9tYW5pZmVzdC54bWytVEtuwyAQ3ecUFtvK0GZVoThZVOoJ0gNQPHaQYEDMECW3L46UT1Wl\r\nqtXs5vveYwZYbQ7BN3vI5CJ24kU+iwbQxt7h2ImP7Xv7KjbrxSoYdAMQ67PR1D6ki9uJklFHQ440\r\nmgCk2eqYAPtoSwBk/b1en5gu3o2ApVgvmivf4Dy0tT8fr9VD8b5NhnedUPdAruEAvTMtHxN0wqTk\r\nnTVcy9Qee3kSLG91SkoZTE87ABZqjpTtroRPNM6T4rMpE453pLhgRlBTfhYLAXPdDck6zzvIDAdW\r\nU3oWsI3I0/kfjRuAzcNBiY8eHj+Dc0zmfvjDDapVT7M53iIObiz5BEFLZawFD9WNWdmS8+87+B/X\r\nH18FFZwkyOKkvUWYyFfqx1ew/gJQSwcIQtlkjiABAABFBAAAUEsBAhQAFAAACAAA0gN7RYVsOYou\r\nAAAALgAAAAgAAAAAAAAAAAAAAAAAAAAAAG1pbWV0eXBlUEsBAhQAFAAACAAA0gN7RUPglLhvJAAA\r\nbyQAABgAAAAAAAAAAAAAAAAAVAAAAFRodW1ibmFpbHMvdGh1bWJuYWlsLnBuZ1BLAQIUABQACAgI\r\nANIDe0UnAN1rxgMAALIcAAAMAAAAAAAAAAAAAAAAAPkkAABzZXR0aW5ncy54bWxQSwECFAAUAAgI\r\nCADSA3tFLPNXYUYIAABkWgAACwAAAAAAAAAAAAAAAAD5KAAAY29udGVudC54bWxQSwECFAAUAAgI\r\nCADSA3tFj6cz7ZYBAAAiAwAACAAAAAAAAAAAAAAAAAB4MQAAbWV0YS54bWxQSwECFAAUAAgICADS\r\nA3tFMyL6+kYGAACWHAAACgAAAAAAAAAAAAAAAABEMwAAc3R5bGVzLnhtbFBLAQIUABQACAgIANID\r\ne0W092jSBQEAAIMDAAAMAAAAAAAAAAAAAAAAAMI5AABtYW5pZmVzdC5yZGZQSwECFAAUAAAIAADS\r\nA3tFAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAABOwAAQ29uZmlndXJhdGlvbnMyL21lbnViYXIv\r\nUEsBAhQAFAAACAAA0gN7RQAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAANzsAAENvbmZpZ3VyYXRp\r\nb25zMi9mbG9hdGVyL1BLAQIUABQACAgIANIDe0UAAAAAAgAAAAAAAAAnAAAAAAAAAAAAAAAAAG07\r\nAABDb25maWd1cmF0aW9uczIvYWNjZWxlcmF0b3IvY3VycmVudC54bWxQSwECFAAUAAAIAADSA3tF\r\nAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAADEOwAAQ29uZmlndXJhdGlvbnMyL3Rvb2xiYXIvUEsB\r\nAhQAFAAACAAA0gN7RQAAAAAAAAAAAAAAABoAAAAAAAAAAAAAAAAA+jsAAENvbmZpZ3VyYXRpb25z\r\nMi9zdGF0dXNiYXIvUEsBAhQAFAAACAAA0gN7RQAAAAAAAAAAAAAAABoAAAAAAAAAAAAAAAAAMjwA\r\nAENvbmZpZ3VyYXRpb25zMi90b29scGFuZWwvUEsBAhQAFAAACAAA0gN7RQAAAAAAAAAAAAAAABwA\r\nAAAAAAAAAAAAAAAAajwAAENvbmZpZ3VyYXRpb25zMi9wcm9ncmVzc2Jhci9QSwECFAAUAAAIAADS\r\nA3tFAAAAAAAAAAAAAAAAGgAAAAAAAAAAAAAAAACkPAAAQ29uZmlndXJhdGlvbnMyL3BvcHVwbWVu\r\ndS9QSwECFAAUAAAIAADSA3tFAAAAAAAAAAAAAAAAHwAAAAAAAAAAAAAAAADcPAAAQ29uZmlndXJh\r\ndGlvbnMyL2ltYWdlcy9CaXRtYXBzL1BLAQIUABQACAgIANIDe0VC2WSOIAEAAEUEAAAVAAAAAAAA\r\nAAAAAAAAABk9AABNRVRBLUlORi9tYW5pZmVzdC54bWxQSwUGAAAAABEAEQBwBAAAfD4AAAAA\r\n--001a1133f024ecc1a90508cc489b\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--001a1133f024ecc1a90508cc489b--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - Vincent MINEAUD <vincent@gandi.net> - 2014-11-28 1600.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 782B2120A1F;\r\n\tFri, 28 Nov 2014 16:00:57 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -99.049\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-99.049 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74, RATWARE_GECKO_BUILD=1.691,\r\n\tUSER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id j+T2sxaBml78; Fri, 28 Nov 2014 16:00:55 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 00F4E120972;\r\n\tFri, 28 Nov 2014 16:00:54 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id AFF1C6AAC5AD0;\r\n\tFri, 28 Nov 2014 16:00:54 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id wIqA1oaE1ukf; Fri, 28 Nov 2014 16:00:53 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 310C86AAC5ACB;\r\n\tFri, 28 Nov 2014 16:00:53 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 566476AAC5ACE\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:00:52 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id kZDgMFPKsqOS for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 16:00:51 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id 2A7A46AAC5ACB\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:00:51 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id EFD70120A32\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:00:50 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id bcrWOrupT4gd for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 16:00:47 +0100 (CET)\r\nReceived: from [217.70.181.32] (vinvin.gandi.net [217.70.181.32])\r\n (using TLSv1 with cipher DHE-RSA-AES128-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by mail4.gandi.net (Postfix) with ESMTPSA id E4C4F120972\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:00:47 +0100 (CET)\r\nMessage-ID: <54788E1F.7060700@gandi.net>\r\nDate: Fri, 28 Nov 2014 16:00:47 +0100\r\nFrom: Vincent MINEAUD <vincent@gandi.net>\r\nUser-Agent: Mozilla/5.0 (X11; Linux i686;\r\n rv:31.0) Gecko/20100101 Icedove/31.2.0\r\nMIME-Version: 1.0\r\nTo: caliopdev@caliop.net\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\n <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\n <20141127192529.GA1819@tao.gandi.net> <54778C34.90802@gandi.net>\r\n <54784A99.3020306@chamal.fr> <54785B09.9010402@gandi.net>\r\n <54788421.1040800@gandi.net>\r\nIn-Reply-To: <54788421.1040800@gandi.net>\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1379974635==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\r\n--===============1379974635==\r\nContent-Type: multipart/signed; micalg=pgp-sha1;\r\n protocol=\"application/pgp-signature\";\r\n boundary=\"1jHEelXdMeFHF2At6gglJL8soeji2e3bg\"\r\n\r\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\r\n--1jHEelXdMeFHF2At6gglJL8soeji2e3bg\r\nContent-Type: text/plain; charset=windows-1252\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nOn 28/11/2014 15:18, Alexis wrote:\r\n>=20\r\n> Le 28/11/2014 12:22, julien muetton a =E9crit :\r\n>>\r\n>> On 11/28/2014 11:12 AM, Aymeric Barantal wrote:\r\n>>> On 11/27/2014 09:40 PM, julien muetton wrote:\r\n>>>>\r\n>>>> On 11/27/2014 08:25 PM, Aymeric Barantal wrote:\r\n>>>>> On Thursday 27 Nov 2014 =E0 12:28:25 (+0100),joseph@cozycloud.cc  w=\r\nrote:\r\n>>>>>> Ton analyse et tes incertitudes sur React/Flux sont justifi=E9es. =\r\nOn\r\n>>>>>> a pris un\r\n>>>>>> risque de notre c=F4t=E9 et heureusement les deux d=E9v ont fait u=\r\nn bon\r\n>>>>>> boulot de\r\n>>>>>> prise en main et d'adaptation.\r\n>>>>>>\r\n>>>>>> Pas de retour concernant Reflux, que je veux regarder en bloc et\r\n>>>>>> en d=E9tail car\r\n>>>>>> je ne suis pas satisfait de l'impl=E9mentation \"officielle\" de Flu=\r\nx,\r\n>>>>>> dont Fluxxor\r\n>>>>>> s'inspire fortement (on utilisait Fluxxor =E0 la base, =E7a n'appo=\r\nrte\r\n>>>>>> rien AMHA,\r\n>>>>>> donc supprim=E9. Mais je n'ai pas regard=E9 son =E9volution depuis=\r\n\r\n>>>>>> plusieurs mois).\r\n>>>>>>\r\n>>>>>> Je n'avais pas pens=E9 =E0 la n=E9cessit=E9 de Node pour utiliser =\r\nReact\r\n>>>>>> pour fair le\r\n>>>>>> rendu c=F4t=E9 serveur, comme Caliopen est en Python (de m=E9moire=\r\n) ce\r\n>>>>>> n'est\r\n>>>>>> effectivement pas un argument. Par contre, pour b=E9n=E9ficier de\r\n>>>>>> l'isomorphisme,\r\n>>>>>> il faut obligatoire du Node =E0 un moment (=E0 ma connaissance).\r\n>>>>> Alors il ne faut absolument pas de ce type d'isomorphisme, les\r\n>>>>> inconvenients\r\n>>>>> sont bien moins importants [1] que les avantages que je vois a\r\n>>>>> cette stricte\r\n>>>>> separation technologique entre front et api.\r\n>>>> Il est aussi possible d'avoir un serveur nodeJS qui sert les pages\r\n>>>> HTML et l'API en python.\r\n>>>> Un reverse proxy permet d'agr=E9ger les deux via des r=E8gles simple=\r\ns\r\n>>>> (HAProxy, Nginx, Apache...)\r\n>>> De quelles pages html est il question ? celles qui passent par\r\n>>> l'internationalisation ?\r\n>> L'isomorphisme signifie rendre indiff=E9remment sur le serveur et sur =\r\nle\r\n>> client, comme twitter et wallmart.\r\n>>> J'ai du mal a voir le gain reel d'une plate-forme node.js a part du\r\n>>> code en commun, qui semble etre\r\n>>> la notion principale cachee derriere le terme d'isomorphisme.\r\n>> c'est pour avoir une meilleure exp=E9rience utilisateur.\r\n>> la premi=E8re page est affich=E9e directement, ce qui r=E9duit le \"tim=\r\ne to\r\n>> first interaction\" (introduit par twitter comme \"time to tweet\")\r\n>>> A ce jour c'est le serveur fournissant l'api qui sait fournir\r\n>>> egalement des pages html (enfin une\r\n>>> seule du fait de l'utilisation d'angular sur le POC).\r\n>>>>\r\n>>>>     /api -> api python\r\n>>>>     cdn.domain.tld -> asset statics\r\n>>>>     default -> serveur nodeJS\r\n>>>>\r\n>>>> Ca colle bien avec l'approche =B5Service.\r\n>>> L'orientation globale n'est pas microservice cote API / backend\r\n>>>\r\n>>>>\r\n>>>> Maintenant, je ne sais pas =E0 quel point il faut dupliquer la logiq=\r\nue\r\n>>>> pour tirer partie de l'isomorphisme.\r\n>>>> Je regarde comment on consomme les donn=E9es sur le serveur (notamme=\r\nnt\r\n>>>> est-ce que l'on passe par les stores).\r\n>>> La je suis perdu ;)\r\n>>>\r\n>> En rendant les pages sur le serveur, il faut aussi r=E9cup=E9rer les\r\n>> donn=E9es =E0 travers l'API.\r\n>> Ce que je cherche =E0 trouver c'est la quantit=E9 de code =E0 duplique=\r\nr, car\r\n>> consommer une API dans le browser passe par XMLHttpRequest, alors que\r\n>> sur le serveur XMLHttpRequest n'est pas dispo.\r\n>>\r\n>> La logique en Fluxx, sauf erreur de ma part, est de placer tout le\r\n>> code m=E9tier dans les Stores qui sont =E0 la fois \"data mapper\" et\r\n>> logique domaine.\r\n>> Si c'est top pour ne pas coupler son code =E0 un framework (on d=E9vel=\r\noppe\r\n>> notre m=E9tier/domaine comme on l'entend), il y a forc=E9ment des\r\n>> diff=E9rences entre le client et le serveur.\r\n>=20\r\n> Par isomorphisme, moi j'entends que le code est le m=EAme est peut =EAt=\r\nre\r\n> utilis=E9 c=F4t=E9 serveur ou c=F4t=E9 client.\r\n> Donc pas de duplication, juste 1 code : 2 utilisations possibles.\r\n>=20\r\n> Browserify est de la partie bien sur pour faire tout =E7a.\r\nIl semble que webpack est m=EAme plus performant que browserify (entendu\r\nlors d'un meetup)\r\nplus d'info ici : https://github.com/webpack/webpack\r\n\r\n>=20\r\n> Tuto pr=E9sentant un m=EAme code qui s'ex=E9cute comme je disais Julien=\r\n, c=F4t=E9\r\n> serveur pour le 1er load, c=F4t=E9 client ensuite :\r\n> http://scotch.io/tutorials/javascript/build-a-real-time-twitter-stream-=\r\nwith-node-and-react-js\r\n>=20\r\n>=20\r\n>>\r\n>> @Joseph, @romain, @brice, vous avez une visibilit=E9 sur la quantit=E9=\r\n de\r\n>> code =E0 dupliquer ?\r\n>>> Quelle logique serait a dupliquer et finalement qu'est qui est\r\n>>> entendu par isomorphisme dans ce cadre, j'ai\r\n>>> l'impression de ne plus comprendre ce terme.\r\n>>>\r\n>>> De quels stores est il question ? Le local storage sera certainement\r\n>>> a exploiter s'il fait parti de cette definition.\r\n>>>\r\n>>> ++ chamal\r\n>>>\r\n>>>\r\n>>> _______________________________________________\r\n>>> Caliopdev mailing list\r\n>>> Caliopdev@caliop.net\r\n>>> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>>\r\n>>\r\n>>\r\n>> _______________________________________________\r\n>> Caliopdev mailing list\r\n>> Caliopdev@caliop.net\r\n>> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>=20\r\n>=20\r\n>=20\r\n>=20\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>=20\r\n\r\n\r\n\r\n--1jHEelXdMeFHF2At6gglJL8soeji2e3bg\r\nContent-Type: application/pgp-signature; name=\"signature.asc\"\r\nContent-Description: OpenPGP digital signature\r\nContent-Disposition: attachment; filename=\"signature.asc\"\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nVersion: GnuPG v1.4.12 (GNU/Linux)\r\n\r\niQEcBAEBAgAGBQJUeI4fAAoJEFXBozrv2iTP5scH/2ni9K6CGov2kAiNDwVCqgE3\r\nDXDWrJcLZkG0iLMsrShyMCLMwM4Du53NNI+fvkFdnyuqbrQHrvWQxlLSTtpFOpr3\r\nka7LUtCoSo1mOT5vAWmeI/wJ4zI/Qgfr1XStbp7ScqZJY5r7665F29IC/13wwXpD\r\nesBx2dgh0wGAbHgRNGHdhksPCic6ICYW08kSl6QP97DZLf+OIVZ2etGzPSmKz4We\r\n0arCDQcSpnoOd8NUSyBgeUBlrB8FnWRHMleIBYtnSDZqUn8EdTsnXmBls+S2Vh+v\r\nq7Jf6AnfesRQ5KxRlY0pu7ggiGSh2PtdkYe5xM9pgKR6q7zYQhtd07/xrtELdkQ=\r\n=jhCS\r\n-----END PGP SIGNATURE-----\r\n\r\n--1jHEelXdMeFHF2At6gglJL8soeji2e3bg--\r\n\r\n--===============1379974635==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1379974635==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - joseph@cozycloud.cc - 2014-11-27 1120.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 25821120A27;\r\n\tThu, 27 Nov 2014 11:24:18 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 1.337\r\nX-Spam-Level: *\r\nX-Spam-Status: No, score=1.337 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001, HTML_30_40=0.374, HTML_MESSAGE=0.001,\r\n\tNO_REAL_NAME=0.961]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id sHmcQyRDWPCE; Thu, 27 Nov 2014 11:24:16 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 744B712090F;\r\n\tThu, 27 Nov 2014 11:24:16 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 033986825D3C3;\r\n\tThu, 27 Nov 2014 11:24:16 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id PkjISpWEyJOE; Thu, 27 Nov 2014 11:24:14 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id ACB0F6825D3BC;\r\n\tThu, 27 Nov 2014 11:24:13 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id E49256825D3AB\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 11:20:14 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id zFoo038rubs1 for <caliopdev@caliop.net>;\r\n Thu, 27 Nov 2014 11:20:11 +0100 (CET)\r\nReceived: from mo7.mail-out.ovh.net (17.mo7.mail-out.ovh.net [188.165.35.227])\r\n (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id A1F1A6825D3A9\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 11:20:10 +0100 (CET)\r\nReceived: from mail374.ha.ovh.net (gw6.ovh.net [213.251.189.206])\r\n by mo7.mail-out.ovh.net (Postfix) with ESMTP id 6DAAFFF8941\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 11:20:09 +0100 (CET)\r\nReceived: from ssl0.ovh.net (localhost [127.0.0.1])\r\n by mail374.ha.ovh.net (Postfix) with ESMTPA id E811B40058;\r\n Thu, 27 Nov 2014 11:20:08 +0100 (CET)\r\nReceived: from 186-146-190-109.dsl.ovh.fr ([109.190.146.186]) by ssl0.ovh.net\r\n with HTTP (HTTP/1.1 POST); Thu, 27 Nov 2014 11:20:04 +0100\r\nMIME-Version: 1.0\r\nDate: Thu, 27 Nov 2014 11:20:04 +0100\r\nFrom: joseph@cozycloud.cc\r\nTo: =?UTF-8?Q?Liste_de_d=C3=A9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nIn-Reply-To: <5476E9CF.70601@albancrommer.com>\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\nMessage-ID: <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc>\r\nX-Sender: joseph@cozycloud.cc\r\nUser-Agent: Roundcube Webmail/1.0.3\r\nX-Originating-IP: 109.190.146.186\r\nX-Ovh-Tracer-Id: 18075478582261733764\r\nX-VR-SPAMSTATE: OK\r\nX-VR-SPAMSCORE: -100\r\nX-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrfeejiedrtdeiucetufdoteggodetrfcurfhrohhfihhlvgemucfqggfjnecuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd\r\nX-Mailman-Approved-At: Thu, 27 Nov 2014 11:24:12 +0100\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============2078041789==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--===============2078041789==\r\nContent-Type: multipart/alternative;\r\n boundary=\"=_f372ed4d216e3dfe74e8a3f2eee10210\"\r\n\r\n--=_f372ed4d216e3dfe74e8a3f2eee10210\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n \r\n\r\nBonjour à tous, \r\n\r\nJe suis Joseph de chez Cozy (https://cozy.io). Certains d'entre vous le\r\nsavent peut-être (je ne sais pas qui exactement à part Laurent était\r\nprésent à nos discussions) nous développons un client email. Notre choix\r\ntechnologique s'est porté sur React et en trois mois, à deux\r\ndéveloppeurs et demi (dont 1,5 sur le front), on a quelque chose qui va\r\nbientôt être montrable. \r\n\r\nJe me permets d'intervenir sur cette question car c'est moi qui ai posé\r\nles premières pierres de l'application (et donc fait les choix techno). \r\n\r\nIl faut bien avoir en tête que comparer Angular et Ember est tout à fait\r\npossible, mais que de comparer ces deux *frameworks* à React est plus\r\ndélicat. React est une *lib* qui ne gère que la partie affichage, il n'y\r\na aucun composant modèle, gestion de la persistence. Facebook, les\r\ncréateurs de React, proposent une architecture autour de React qui\r\ns'appelle Flux (que nous utilisons dans notre app) qui tourne autour du\r\nprincipe de \"flux unidirectionnel de données\". \r\n\r\nJe vous suggère cette présentation sur React qui exprime bien mieux que\r\nmoi ce que je voulais dire et qui présente les avantages de la techno :\r\nhttps://speakerdeck.com/player/44129b9054c901328b89221e99b278fe#\r\n(notamment la distinction framework vs libs). J'attire votre attention\r\nsur une fonctionnalité sous-estimé de React, qui est le rendu côté\r\nserveur avec le même code que le client (je ne sais pas si c'est un cas\r\nde \"performance\" important dans Caliopen). \r\n\r\nJe n'ai malheureusement pas de ressource équivalente à vous présenter\r\nsur Flux, car je n'en ai toujours pas trouvée. Je vous suggère cependant\r\ncet article sur le blog de React :\r\nhttp://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html\r\n\r\n\r\nEn résumé, React, c'est très bien (à mon humble avis) : ça permet\r\nd'écrire du code vite, maintenable et *performant* (sans y penser). Mais\r\nce n'est qu'une partie du problème, la \"Vue\", et il faut accepter\r\n(s'habituer à) une nouvelle façon de faire. \r\n\r\nVous pourriez ajouter Backbone+React (ils jouent relativement bien\r\nensemble) et/ou Flux+React à votre matrice pour la compléter. A la\r\nquestion React+Angular et React+Ember, je ne connais pas l'état de\r\nl'art, ne portant pas du tout Angular dans mon coeur et ne m'étant\r\njamais intéressé à Ember. \r\n\r\nJe vous laisse ici l'adresse du dépôt de notre application, si vous\r\nvoulez jeter un oeil au code. A mon avis on est à des kilomètres de\r\nfaire les choses parfaitement, mais si ça peut\r\naider...https://github.com/cozy/cozy-emails \r\n\r\nEn espérant que mon retour d'expérience puisse être utile, je reste à\r\ndisposition pour des questions / demande de renseignements\r\nsupplémentaires concernant React et Flux. \r\n\r\nJoseph. \r\n\r\nLe 11/27/2014 10:07, Alban Crommer a écrit : \r\n\r\n> Bonjour,\r\n> \r\n> Désolé, je ne connais pas suffisamment ces 3 frameworks pour avoir un avis éclairant sur la question.\r\n> \r\n> Mais je suis content que ça puisse servir, et rassuré de voir des résultats vraisemblablement sérieux et pointant dans la même direction ;)\r\n> \r\n> +a\r\n> \r\n> Le 27/11/2014 01:32, Kajan Sivaramalingam a écrit : \r\n> \r\n>> Voici ma contribution. \r\n>> \r\n>> A bientôt pour la délibération. \r\n>> \r\n>> _______________________________________________\r\n>> Caliopdev mailing list\r\n>> Caliopdev@caliop.net\r\n>> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev [1]\r\n> \r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev [1]\r\n\r\n \r\n\r\nLinks:\r\n------\r\n[1] http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--=_f372ed4d216e3dfe74e8a3f2eee10210\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\">\r\n<html><body style=3D'font-size: 10pt'>\r\n<p>Bonjour &agrave; tous,</p>\r\n<p>Je suis Joseph de chez Cozy (https://cozy.io). Certains d'entre vous le =\r\nsavent peut-&ecirc;tre (je ne sais pas qui exactement &agrave; part Laurent=\r\n &eacute;tait pr&eacute;sent &agrave; nos discussions) nous d&eacute;velopp=\r\nons un client email. Notre choix technologique s'est port&eacute; sur React=\r\n et en trois mois, &agrave; deux d&eacute;veloppeurs et demi (dont 1,5 sur =\r\nle front), on a quelque chose qui va bient&ocirc;t &ecirc;tre montrable.</p=\r\n>\r\n<p>Je me permets d'intervenir sur cette question car c'est moi qui ai pos&e=\r\nacute; les premi&egrave;res pierres de l'application (et donc fait les choi=\r\nx techno).</p>\r\n<p>Il faut bien avoir en t&ecirc;te que comparer Angular et Ember est tout =\r\n&agrave; fait possible, mais que de comparer ces deux *frameworks* &agrave;=\r\n React est plus d&eacute;licat. React est une *lib* qui ne g&egrave;re que =\r\nla partie affichage, il n'y a aucun composant mod&egrave;le, gestion de la =\r\npersistence. Facebook, les cr&eacute;ateurs de React, proposent une archite=\r\ncture autour de React qui s'appelle Flux (que nous utilisons dans notre app=\r\n) qui tourne autour du principe de \"flux unidirectionnel de donn&eacute;es\"=\r\n=2E</p>\r\n<p>Je vous sugg&egrave;re cette pr&eacute;sentation sur React qui exprime b=\r\nien mieux que moi ce que je voulais dire et qui pr&eacute;sente les avantag=\r\nes de la techno :&nbsp;https://speakerdeck.com/player/44129b9054c901328b892=\r\n21e99b278fe# (notamment la distinction framework vs libs). J'attire votre a=\r\nttention sur une fonctionnalit&eacute; sous-estim&eacute; de React, qui est=\r\n le rendu c&ocirc;t&eacute; serveur avec le m&ecirc;me code que le client (=\r\nje ne sais pas si c'est un cas de \"performance\" important dans Caliopen).</=\r\np>\r\n<p>Je n'ai malheureusement pas de ressource &eacute;quivalente &agrave; vou=\r\ns pr&eacute;senter sur Flux, car je n'en ai toujours pas trouv&eacute;e. Je=\r\n vous sugg&egrave;re cependant cet article sur le blog de React :&nbsp;http=\r\n://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher=\r\n=2Ehtml</p>\r\n<p>En r&eacute;sum&eacute;, React, c'est tr&egrave;s bien (&agrave; mon hum=\r\nble avis) : &nbsp;&ccedil;a permet d'&eacute;crire du code vite, maintenabl=\r\ne et *performant* (sans y penser).&nbsp;Mais ce n'est qu'une partie du prob=\r\nl&egrave;me, la \"Vue\", et il faut accepter (s'habituer &agrave;) une nouvel=\r\nle fa&ccedil;on de faire.</p>\r\n<p>Vous pourriez ajouter Backbone+React (ils jouent relativement bien ensem=\r\nble) et/ou Flux+React &agrave; votre matrice pour la compl&eacute;ter. A la=\r\n question React+Angular et React+Ember, je ne connais pas l'&eacute;tat de =\r\nl'art, ne portant pas du tout Angular dans mon coeur et ne m'&eacute;tant j=\r\namais int&eacute;ress&eacute; &agrave; Ember.</p>\r\n<p>Je vous laisse ici l'adresse du d&eacute;p&ocirc;t de notre application,=\r\n si vous voulez jeter un oeil au code. A mon avis on est &agrave; des kilom=\r\n&egrave;tres de faire les choses parfaitement, mais si &ccedil;a peut aider=\r\n=2E..https://github.com/cozy/cozy-emails</p>\r\n<p>En esp&eacute;rant que mon retour d'exp&eacute;rience puisse &ecirc;tre =\r\nutile, je reste &agrave; disposition pour des questions / demande de rensei=\r\ngnements suppl&eacute;mentaires concernant React et Flux.</p>\r\n<p>Joseph.</p>\r\n<p>Le 11/27/2014 10:07, Alban Crommer a &eacute;crit&nbsp;:</p>\r\n<blockquote type=3D\"cite\" style=3D\"padding-left:5px; border-left:#1010ff 2p=\r\nx solid; margin-left:5px\"><!-- html ignored --><!-- head ignored --><!-- me=\r\nta ignored -->\r\n<div class=3D\"moz-cite-prefix\">Bonjour,<br /><br /> D&eacute;sol&eacute;, j=\r\ne ne connais pas suffisamment ces 3 frameworks pour avoir un avis &eacute;c=\r\nlairant sur la question.<br /><br /> Mais je suis content que &ccedil;a pui=\r\nsse servir, et rassur&eacute; de voir des r&eacute;sultats vraisemblablemen=\r\nt s&eacute;rieux et pointant dans la m&ecirc;me direction ;)<br /><br /> +a=\r\n<br /><br /> Le 27/11/2014 01:32, Kajan Sivaramalingam a &eacute;crit&nbsp;=\r\n:</div>\r\n<blockquote type=3D\"cite\" style=3D\"padding-left:5px; border-left:#1010ff 2p=\r\nx solid; margin-left:5px\">\r\n<div dir=3D\"ltr\">\r\n<div class=3D\"gmail_extra\">Voici ma contribution.</div>\r\n<div class=3D\"gmail_extra\">&nbsp;</div>\r\n<div class=3D\"gmail_extra\">&nbsp;</div>\r\n<div class=3D\"gmail_extra\">A bient&ocirc;t pour la d&eacute;lib&eacute;rati=\r\non.</div>\r\n</div>\r\n<br /><fieldset class=3D\"mimeAttachmentHeader\"></fieldset><br />\r\n<pre>_______________________________________________\r\nCaliopdev mailing list\r\n<a class=3D\"moz-txt-link-abbreviated\" href=3D\"mailto:Caliopdev@caliop.net\">=\r\nCaliopdev@caliop.net</a>\r\n<a class=3D\"moz-txt-link-freetext\" href=3D\"http://brassens.heberge.info/cgi=\r\n-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailm=\r\nan/listinfo/caliopdev</a>\r\n</pre>\r\n</blockquote>\r\n<br /><!-- html ignored --><br />\r\n<pre>_______________________________________________\r\nCaliopdev mailing list\r\n<a href=3D\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a href=3D\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\"=\r\n>http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n</blockquote>\r\n<p>&nbsp;</p>\r\n<div>&nbsp;</div>\r\n</body></html>\r\n\r\n--=_f372ed4d216e3dfe74e8a3f2eee10210--\r\n\r\n\r\n--===============2078041789==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============2078041789==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - joseph@cozycloud.cc - 2014-11-27 1228.eml",
    "content": "Return-Path: <joseph@cozycloud.cc>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id B4F49120AD4\r\n\tfor <dev@caliopen.local>; Thu, 27 Nov 2014 12:28:31 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 1.337\r\nX-Spam-Level: *\r\nX-Spam-Status: No, score=1.337 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001, HTML_30_40=0.374, HTML_MESSAGE=0.001,\r\n\tNO_REAL_NAME=0.961]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id m-LVGJn2ROMj for <dev@caliopen.local>;\r\n\tThu, 27 Nov 2014 12:28:29 +0100 (CET)\r\nReceived: from mo7.mail-out.ovh.net (20.mo7.mail-out.ovh.net [46.105.49.208])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 1F671120AD3\r\n\tfor <dev@caliopen.local>; Thu, 27 Nov 2014 12:28:28 +0100 (CET)\r\nReceived: from mail374.ha.ovh.net (gw6.ovh.net [213.251.189.206])\r\n\tby mo7.mail-out.ovh.net (Postfix) with ESMTP id 054D5FF8441\r\n\tfor <dev@caliopen.local>; Thu, 27 Nov 2014 12:28:27 +0100 (CET)\r\nReceived: from ssl0.ovh.net (localhost [127.0.0.1])\r\n\tby mail374.ha.ovh.net (Postfix) with ESMTPA id 107E740058;\r\n\tThu, 27 Nov 2014 12:28:26 +0100 (CET)\r\nReceived: from 186-146-190-109.dsl.ovh.fr ([109.190.146.186])\r\n by ssl0.ovh.net\r\n with HTTP (HTTP/1.1 POST); Thu, 27 Nov 2014 12:28:25 +0100\r\nMIME-Version: 1.0\r\nContent-Type: multipart/alternative;\r\n boundary=\"=_00abe01f2cc39e11b3ba37f5c1699adc\"\r\nDate: Thu, 27 Nov 2014 12:28:25 +0100\r\nFrom: joseph@cozycloud.cc\r\nTo: =?UTF-8?Q?Liste_de_d=C3=A9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nCc: julien muetton <dev@caliopen.local>, romain@cozycloud.cc,\r\n brice@cozycloud.cc\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nIn-Reply-To: <547706E2.4030805@gandi.net>\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\nMessage-ID: <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\nX-Sender: joseph@cozycloud.cc\r\nUser-Agent: Roundcube Webmail/1.0.3\r\nX-Originating-IP: 109.190.146.186\r\nX-Ovh-Tracer-Id: 781937485586717060\r\nX-VR-SPAMSTATE: OK\r\nX-VR-SPAMSCORE: -100\r\nX-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrfeejiedrtdeiucetufdoteggodetrfcurfhrohhfihhlvgemucfqggfjnecuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd\r\n\r\n--=_00abe01f2cc39e11b3ba37f5c1699adc\r\nContent-Transfer-Encoding: 8bit\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n \r\n\r\nTon analyse et tes incertitudes sur React/Flux sont justifiées. On a\r\npris un risque de notre côté et heureusement les deux dév ont fait un\r\nbon boulot de prise en main et d'adaptation. \r\n\r\nPas de retour concernant Reflux, que je veux regarder en bloc et en\r\ndétail car je ne suis pas satisfait de l'implémentation \"officielle\" de\r\nFlux, dont Fluxxor s'inspire fortement (on utilisait Fluxxor à la base,\r\nça n'apporte rien AMHA, donc supprimé. Mais je n'ai pas regardé son\r\névolution depuis plusieurs mois). \r\n\r\nJe n'avais pas pensé à la nécessité de Node pour utiliser React pour\r\nfair le rendu côté serveur, comme Caliopen est en Python (de mémoire) ce\r\nn'est effectivement pas un argument. Par contre, pour bénéficier de\r\nl'isomorphisme, il faut obligatoire du Node à un moment (à ma\r\nconnaissance). \r\n\r\nJoseph. \r\n\r\nPS : je me suis permis de mettre en copie Romain et Brice, nos deux\r\nhéros de l'email. \r\n\r\nLe 11/27/2014 12:11, julien muetton a écrit : \r\n\r\n> Salut Joseph,\r\n> \r\n> Je partage assez ton avis sur React.\r\n> \r\n> La problématique que je vois pour Caliopen, est peu de temps, et des contributeurs venant de différents mondes.\r\n> Pour répondre à ces problématique, je pense qu'un framework structurant est nécessaire. Je ne porte pas non plus angular dans mon coeur, et la communauté arrive enfin à un consensus sur le sujet. Dans tous les cas (car je ne veux pas troller) il n'est pas structurant.\r\n> Ember est plus pragmatique, mais la partie rendu côté serveur est effectivement un manque. Dans la roadmap pour la v2 ils annoncent l'intégration des meilleures parties de React et surtout, la possibilité de migrer sans trop d'efforts (de la v1 à v2) ce qui est rassurant.\r\n> \r\n> Fluxxor et Reflux semblent être les projets les plus en vogue pour faire du Flux, as tu de la visibilité en terme de stabilité ? On peut s'en sortir pour structurer les choses autour de flux moyennant un peu de boulot, mais pour le rendu server side, ca nécessite une application node pour rendre les pages. Ca rajoute en complexité, mais reste également gérable.\r\n> \r\n> En résumé, passer sur React + (Flux* || Backbone) demande pas mal de travail supplémentaire pour la mise en place, ce qui me semble être un risque, tempéré par la pérennité du modèle (je ne crois plus au MVC depuis pas mal de temps maintenant).\r\n> \r\n> Tant que Gandi ne peut pas me libérer pour bosser sur Caliopen, je ne sais pas à quel point je serai disponible, c'est pourquoi je penche plus pour Ember qui permet un démarrage plus rapide IMHO (guides, structure imposée...).\r\n> \r\n> my 2 cents,\r\n> \r\n> Julien\r\n> \r\n> On 11/27/2014 11:20 AM, joseph@cozycloud.cc wrote: \r\n> \r\n> Bonjour à tous, \r\n> \r\n> Je suis Joseph de chez Cozy (https://cozy.io [2]). Certains d'entre vous le savent peut-être (je ne sais pas qui exactement à part Laurent était présent à nos discussions) nous développons un client email. Notre choix technologique s'est porté sur React et en trois mois, à deux développeurs et demi (dont 1,5 sur le front), on a quelque chose qui va bientôt être montrable. \r\n> \r\n> Je me permets d'intervenir sur cette question car c'est moi qui ai posé les premières pierres de l'application (et donc fait les choix techno). \r\n> \r\n> Il faut bien avoir en tête que comparer Angular et Ember est tout à fait possible, mais que de comparer ces deux *frameworks* à React est plus délicat. React est une *lib* qui ne gère que la partie affichage, il n'y a aucun composant modèle, gestion de la persistence. Facebook, les créateurs de React, proposent une architecture autour de React qui s'appelle Flux (que nous utilisons dans notre app) qui tourne autour du principe de \"flux unidirectionnel de données\". \r\n> \r\n> Je vous suggère cette présentation sur React qui exprime bien mieux que moi ce que je voulais dire et qui présente les avantages de la techno : https://speakerdeck.com/player/44129b9054c901328b89221e99b278fe# [3] (notamment la distinction framework vs libs). J'attire votre attention sur une fonctionnalité sous-estimé de React, qui est le rendu côté serveur avec le même code que le client (je ne sais pas si c'est un cas de \"performance\" important dans Caliopen). \r\n> \r\n> Je n'ai malheureusement pas de ressource équivalente à vous présenter sur Flux, car je n'en ai toujours pas trouvée. Je vous suggère cependant cet article sur le blog de React : http://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html [4] \r\n> \r\n> En résumé, React, c'est très bien (à mon humble avis) : ça permet d'écrire du code vite, maintenable et *performant* (sans y penser). Mais ce n'est qu'une partie du problème, la \"Vue\", et il faut accepter (s'habituer à) une nouvelle façon de faire. \r\n> \r\n> Vous pourriez ajouter Backbone+React (ils jouent relativement bien ensemble) et/ou Flux+React à votre matrice pour la compléter. A la question React+Angular et React+Ember, je ne connais pas l'état de l'art, ne portant pas du tout Angular dans mon coeur et ne m'étant jamais intéressé à Ember. \r\n> \r\n> Je vous laisse ici l'adresse du dépôt de notre application, si vous voulez jeter un oeil au code. A mon avis on est à des kilomètres de faire les choses parfaitement, mais si ça peut aider...https://github.com/cozy/cozy-emails \r\n> \r\n> En espérant que mon retour d'expérience puisse être utile, je reste à disposition pour des questions / demande de renseignements supplémentaires concernant React et Flux. \r\n> \r\n> Joseph. \r\n> \r\n> Le 11/27/2014 10:07, Alban Crommer a écrit : \r\n> Bonjour,\r\n> \r\n> Désolé, je ne connais pas suffisamment ces 3 frameworks pour avoir un avis éclairant sur la question.\r\n> \r\n> Mais je suis content que ça puisse servir, et rassuré de voir des résultats vraisemblablement sérieux et pointant dans la même direction ;)\r\n> \r\n> +a\r\n> \r\n> Le 27/11/2014 01:32, Kajan Sivaramalingam a écrit : \r\n> \r\n> Voici ma contribution. \r\n> \r\n> A bientôt pour la délibération. \r\n> \r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev [1]\r\n> \r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev [1]\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev [1]\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev [1]\r\n\r\n \r\n\r\nLinks:\r\n------\r\n[1] http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n[2] https://cozy.io\r\n[3] https://speakerdeck.com/player/44129b9054c901328b89221e99b278fe#\r\n[4]\r\nhttp://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html\r\n--=_00abe01f2cc39e11b3ba37f5c1699adc\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\">\r\n<html><body style=3D'font-size: 10pt'>\r\n<p>Ton analyse et tes incertitudes sur React/Flux sont justifi&eacute;es. O=\r\nn a pris un risque de notre c&ocirc;t&eacute; et heureusement les deux d&ea=\r\ncute;v ont fait un bon boulot de prise en main et d'adaptation.</p>\r\n<p>Pas de retour concernant Reflux, que je veux regarder en bloc et en d&ea=\r\ncute;tail car je ne suis pas satisfait de l'impl&eacute;mentation \"officiel=\r\nle\" de Flux, dont Fluxxor s'inspire fortement (on utilisait Fluxxor &agrave=\r\n; la base, &ccedil;a n'apporte rien AMHA, donc supprim&eacute;. Mais je n'a=\r\ni pas regard&eacute; son &eacute;volution depuis plusieurs mois).</p>\r\n<p>Je n'avais pas pens&eacute; &agrave; la n&eacute;cessit&eacute; de Node =\r\npour utiliser React pour fair le rendu c&ocirc;t&eacute; serveur, comme Cal=\r\niopen est en Python (de m&eacute;moire) ce n'est effectivement pas un argum=\r\nent. Par contre, pour b&eacute;n&eacute;ficier de l'isomorphisme, il faut o=\r\nbligatoire du Node &agrave; un moment (&agrave; ma connaissance).</p>\r\n<p>Joseph.</p>\r\n<p>PS : je me suis permis de mettre en copie Romain et Brice, nos deux h&ea=\r\ncute;ros de l'email.</p>\r\n<p>Le 11/27/2014 12:11, julien muetton a &eacute;crit&nbsp;:</p>\r\n<blockquote type=3D\"cite\" style=3D\"padding-left:5px; border-left:#1010ff 2p=\r\nx solid; margin-left:5px\"><!-- html ignored --><!-- head ignored --><!-- me=\r\nta ignored -->Salut Joseph,<br /><br /> Je partage assez ton avis sur React=\r\n=2E<br /><br /> La probl&eacute;matique que je vois pour Caliopen, est peu =\r\nde temps, et des contributeurs venant de diff&eacute;rents mondes.<br /> Po=\r\nur r&eacute;pondre &agrave; ces probl&eacute;matique, je pense qu'un framew=\r\nork structurant est n&eacute;cessaire. Je ne porte pas non plus angular dan=\r\ns mon coeur, et la communaut&eacute; arrive enfin &agrave; un consensus sur=\r\n le sujet. Dans tous les cas (car je ne veux pas troller) il n'est pas stru=\r\ncturant.<br /> Ember est plus pragmatique, mais la partie rendu c&ocirc;t&e=\r\nacute; serveur est effectivement un manque. Dans la roadmap pour la v2 ils =\r\nannoncent l'int&eacute;gration des meilleures parties de React et surtout, =\r\nla possibilit&eacute; de migrer sans trop d'efforts (de la v1 &agrave; v2) =\r\nce qui est rassurant.<br /><br /> Fluxxor et Reflux semblent &ecirc;tre les=\r\n projets les plus en vogue pour faire du Flux, as tu de la visibilit&eacute=\r\n; en terme de stabilit&eacute; ? On peut s'en sortir pour structurer les ch=\r\noses autour de flux moyennant un peu de boulot, mais pour le rendu server s=\r\nide, ca n&eacute;cessite une application node pour rendre les pages. Ca raj=\r\noute en complexit&eacute;, mais reste &eacute;galement g&eacute;rable.<br /=\r\n><br /> En r&eacute;sum&eacute;, passer sur React + (Flux* || Backbone) dem=\r\nande pas mal de travail suppl&eacute;mentaire pour la mise en place, ce qui=\r\n me semble &ecirc;tre un risque, temp&eacute;r&eacute; par la p&eacute;renn=\r\nit&eacute; du mod&egrave;le (je ne crois plus au MVC depuis pas mal de temp=\r\ns maintenant).<br /><br /> Tant que Gandi ne peut pas me lib&eacute;rer pou=\r\nr bosser sur Caliopen, je ne sais pas &agrave; quel point je serai disponib=\r\nle, c'est pourquoi je penche plus pour Ember qui permet un d&eacute;marrage=\r\n plus rapide IMHO (guides, structure impos&eacute;e...).<br /><br /> my 2 c=\r\nents,<br /><br /> Julien<br /><br />\r\n<div class=3D\"moz-cite-prefix\">On 11/27/2014 11:20 AM, <a class=3D\"moz-txt-=\r\nlink-abbreviated\" href=3D\"mailto:joseph@cozycloud.cc\">joseph@cozycloud.cc</=\r\na> wrote:</div>\r\n<blockquote type=3D\"cite\" style=3D\"padding-left:5px; border-left:#1010ff 2p=\r\nx solid; margin-left:5px\">\r\n<p>Bonjour &agrave; tous,</p>\r\n<p>Je suis Joseph de chez Cozy (<a class=3D\"moz-txt-link-freetext\" href=3D\"=\r\nhttps://cozy.io\">https://cozy.io</a>). Certains d'entre vous le savent peut=\r\n-&ecirc;tre (je ne sais pas qui exactement &agrave; part Laurent &eacute;ta=\r\nit pr&eacute;sent &agrave; nos discussions) nous d&eacute;veloppons un clie=\r\nnt email. Notre choix technologique s'est port&eacute; sur React et en troi=\r\ns mois, &agrave; deux d&eacute;veloppeurs et demi (dont 1,5 sur le front), =\r\non a quelque chose qui va bient&ocirc;t &ecirc;tre montrable.</p>\r\n<p>Je me permets d'intervenir sur cette question car c'est moi qui ai pos&e=\r\nacute; les premi&egrave;res pierres de l'application (et donc fait les choi=\r\nx techno).</p>\r\n<p>Il faut bien avoir en t&ecirc;te que comparer Angular et Ember est tout =\r\n&agrave; fait possible, mais que de comparer ces deux *frameworks* &agrave;=\r\n React est plus d&eacute;licat. React est une *lib* qui ne g&egrave;re que =\r\nla partie affichage, il n'y a aucun composant mod&egrave;le, gestion de la =\r\npersistence. Facebook, les cr&eacute;ateurs de React, proposent une archite=\r\ncture autour de React qui s'appelle Flux (que nous utilisons dans notre app=\r\n) qui tourne autour du principe de \"flux unidirectionnel de donn&eacute;es\"=\r\n=2E</p>\r\n<p>Je vous sugg&egrave;re cette pr&eacute;sentation sur React qui exprime b=\r\nien mieux que moi ce que je voulais dire et qui pr&eacute;sente les avantag=\r\nes de la techno :&nbsp;<a class=3D\"moz-txt-link-freetext\" href=3D\"https://s=\r\npeakerdeck.com/player/44129b9054c901328b89221e99b278fe#\">https://speakerdec=\r\nk.com/player/44129b9054c901328b89221e99b278fe#</a> (notamment la distinctio=\r\nn framework vs libs). J'attire votre attention sur une fonctionnalit&eacute=\r\n; sous-estim&eacute; de React, qui est le rendu c&ocirc;t&eacute; serveur a=\r\nvec le m&ecirc;me code que le client (je ne sais pas si c'est un cas de \"pe=\r\nrformance\" important dans Caliopen).</p>\r\n<p>Je n'ai malheureusement pas de ressource &eacute;quivalente &agrave; vou=\r\ns pr&eacute;senter sur Flux, car je n'en ai toujours pas trouv&eacute;e. Je=\r\n vous sugg&egrave;re cependant cet article sur le blog de React :&nbsp;<a c=\r\nlass=3D\"moz-txt-link-freetext\" href=3D\"http://facebook.github.io/react/blog=\r\n/2014/07/30/flux-actions-and-the-dispatcher.html\">http://facebook.github.io=\r\n/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html</a></p>\r\n<p>En r&eacute;sum&eacute;, React, c'est tr&egrave;s bien (&agrave; mon hum=\r\nble avis) : &nbsp;&ccedil;a permet d'&eacute;crire du code vite, maintenabl=\r\ne et *performant* (sans y penser).&nbsp;Mais ce n'est qu'une partie du prob=\r\nl&egrave;me, la \"Vue\", et il faut accepter (s'habituer &agrave;) une nouvel=\r\nle fa&ccedil;on de faire.</p>\r\n<p>Vous pourriez ajouter Backbone+React (ils jouent relativement bien ensem=\r\nble) et/ou Flux+React &agrave; votre matrice pour la compl&eacute;ter. A la=\r\n question React+Angular et React+Ember, je ne connais pas l'&eacute;tat de =\r\nl'art, ne portant pas du tout Angular dans mon coeur et ne m'&eacute;tant j=\r\namais int&eacute;ress&eacute; &agrave; Ember.</p>\r\n<p>Je vous laisse ici l'adresse du d&eacute;p&ocirc;t de notre application,=\r\n si vous voulez jeter un oeil au code. A mon avis on est &agrave; des kilom=\r\n&egrave;tres de faire les choses parfaitement, mais si &ccedil;a peut aider=\r\n=2E..https://github.com/cozy/cozy-emails</p>\r\n<p>En esp&eacute;rant que mon retour d'exp&eacute;rience puisse &ecirc;tre =\r\nutile, je reste &agrave; disposition pour des questions / demande de rensei=\r\ngnements suppl&eacute;mentaires concernant React et Flux.</p>\r\n<p>Joseph.</p>\r\n<p>Le 11/27/2014 10:07, Alban Crommer a &eacute;crit&nbsp;:</p>\r\n<blockquote style=3D\"padding-left: 5px; border-left: #1010ff 2px solid; mar=\r\ngin-left: 5px;\"><!-- node type 8 --><!-- node type 8 --><!-- node type 8 --=\r\n>\r\n<div class=3D\"moz-cite-prefix\">Bonjour,<br /><br /> D&eacute;sol&eacute;, j=\r\ne ne connais pas suffisamment ces 3 frameworks pour avoir un avis &eacute;c=\r\nlairant sur la question.<br /><br /> Mais je suis content que &ccedil;a pui=\r\nsse servir, et rassur&eacute; de voir des r&eacute;sultats vraisemblablemen=\r\nt s&eacute;rieux et pointant dans la m&ecirc;me direction ;)<br /><br /> +a=\r\n<br /><br /> Le 27/11/2014 01:32, Kajan Sivaramalingam a &eacute;crit&nbsp;=\r\n:</div>\r\n<blockquote style=3D\"padding-left: 5px; border-left: #1010ff 2px solid; mar=\r\ngin-left: 5px;\">\r\n<div dir=3D\"ltr\">\r\n<div class=3D\"gmail_extra\">Voici ma contribution.</div>\r\n<div class=3D\"gmail_extra\">&nbsp;</div>\r\n<div class=3D\"gmail_extra\">&nbsp;</div>\r\n<div class=3D\"gmail_extra\">A bient&ocirc;t pour la d&eacute;lib&eacute;rati=\r\non.</div>\r\n</div>\r\n<br /><fieldset class=3D\"mimeAttachmentHeader\"></fieldset><br />\r\n<pre>_______________________________________________\r\nCaliopdev mailing list\r\n<a class=3D\"moz-txt-link-abbreviated\" href=3D\"mailto:Caliopdev@caliop.net\">=\r\nCaliopdev@caliop.net</a>\r\n<a class=3D\"moz-txt-link-freetext\" href=3D\"http://brassens.heberge.info/cgi=\r\n-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailm=\r\nan/listinfo/caliopdev</a>\r\n</pre>\r\n</blockquote>\r\n<br /><!-- node type 8 --><br />\r\n<pre>_______________________________________________\r\nCaliopdev mailing list\r\n<a href=3D\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a href=3D\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\"=\r\n>http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n</blockquote>\r\n<p>&nbsp;</p>\r\n<div>&nbsp;</div>\r\n<br /><fieldset class=3D\"mimeAttachmentHeader\"></fieldset><br />\r\n<pre>_______________________________________________\r\nCaliopdev mailing list\r\n<a class=3D\"moz-txt-link-abbreviated\" href=3D\"mailto:Caliopdev@caliop.net\">=\r\nCaliopdev@caliop.net</a>\r\n<a class=3D\"moz-txt-link-freetext\" href=3D\"http://brassens.heberge.info/cgi=\r\n-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailm=\r\nan/listinfo/caliopdev</a>\r\n</pre>\r\n</blockquote>\r\n<br /><!-- html ignored --><br />\r\n<pre>_______________________________________________\r\nCaliopdev mailing list\r\n<a href=3D\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a href=3D\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\"=\r\n>http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n</blockquote>\r\n<p>&nbsp;</p>\r\n<div>&nbsp;</div>\r\n</body></html>\r\n\r\n--=_00abe01f2cc39e11b3ba37f5c1699adc--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - julien muetton <julien.muetton@gandi.net> - 2014-11-27 1211.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id A29AF120AC9;\r\n\tThu, 27 Nov 2014 12:10:48 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -98.674\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-98.674 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74, HTML_30_40=0.374, HTML_MESSAGE=0.001,\r\n\tRATWARE_GECKO_BUILD=1.691, USER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id IEn9bJTpnnAx; Thu, 27 Nov 2014 12:10:46 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id B61E9120ADA;\r\n\tThu, 27 Nov 2014 12:10:43 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 8DCDB6825E7D5;\r\n\tThu, 27 Nov 2014 12:10:43 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id JpQmG9Mzb9_e; Thu, 27 Nov 2014 12:10:42 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id BDB746825E7D0;\r\n\tThu, 27 Nov 2014 12:10:41 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 4AF896825E7D3\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 12:10:40 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id 2AgkX-fkwfbH for <caliopdev@caliop.net>;\r\n Thu, 27 Nov 2014 12:10:38 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id DB6046825E7D0\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 12:10:38 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id 71BC2120ACA\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 12:10:38 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id tYg4rGgXuVhC for <caliopdev@caliop.net>;\r\n Thu, 27 Nov 2014 12:10:36 +0100 (CET)\r\nReceived: from [10.242.2.62] (corp-ras.gandi.net [217.70.181.25])\r\n (using TLSv1 with cipher DHE-RSA-AES128-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by mail4.gandi.net (Postfix) with ESMTPSA id 2C190120AC9\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 12:10:36 +0100 (CET)\r\nMessage-ID: <547706E2.4030805@gandi.net>\r\nDate: Thu, 27 Nov 2014 12:11:30 +0100\r\nFrom: julien muetton <dev@caliopen.local>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Icedove/31.2.0\r\nMIME-Version: 1.0\r\nTo: =?windows-1252?Q?Liste_de_d=E9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc>\r\nIn-Reply-To: <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc>\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0069933434==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--===============0069933434==\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------030301020107080302010808\"\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------030301020107080302010808\r\nContent-Type: text/plain; charset=windows-1252; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\nSalut Joseph,\r\n\r\nJe partage assez ton avis sur React.\r\n\r\nLa probl�matique que je vois pour Caliopen, est peu de temps, et des \r\ncontributeurs venant de diff�rents mondes.\r\nPour r�pondre � ces probl�matique, je pense qu'un framework structurant \r\nest n�cessaire. Je ne porte pas non plus angular dans mon coeur, et la \r\ncommunaut� arrive enfin � un consensus sur le sujet. Dans tous les cas \r\n(car je ne veux pas troller) il n'est pas structurant.\r\nEmber est plus pragmatique, mais la partie rendu c�t� serveur est \r\neffectivement un manque. Dans la roadmap pour la v2 ils annoncent \r\nl'int�gration des meilleures parties de React et surtout, la possibilit� \r\nde migrer sans trop d'efforts (de la v1 � v2) ce qui est rassurant.\r\n\r\nFluxxor et Reflux semblent �tre les projets les plus en vogue pour faire \r\ndu Flux, as tu de la visibilit� en terme de stabilit� ? On peut s'en \r\nsortir pour structurer les choses autour de flux moyennant un peu de \r\nboulot, mais pour le rendu server side, ca n�cessite une application \r\nnode pour rendre les pages. Ca rajoute en complexit�, mais reste \r\n�galement g�rable.\r\n\r\nEn r�sum�, passer sur React + (Flux* || Backbone) demande pas mal de \r\ntravail suppl�mentaire pour la mise en place, ce qui me semble �tre un \r\nrisque, temp�r� par la p�rennit� du mod�le (je ne crois plus au MVC \r\ndepuis pas mal de temps maintenant).\r\n\r\nTant que Gandi ne peut pas me lib�rer pour bosser sur Caliopen, je ne \r\nsais pas � quel point je serai disponible, c'est pourquoi je penche plus \r\npour Ember qui permet un d�marrage plus rapide IMHO (guides, structure \r\nimpos�e...).\r\n\r\nmy 2 cents,\r\n\r\nJulien\r\n\r\nOn 11/27/2014 11:20 AM, joseph@cozycloud.cc wrote:\r\n>\r\n> Bonjour � tous,\r\n>\r\n> Je suis Joseph de chez Cozy (https://cozy.io). Certains d'entre vous \r\n> le savent peut-�tre (je ne sais pas qui exactement � part Laurent \r\n> �tait pr�sent � nos discussions) nous d�veloppons un client email. \r\n> Notre choix technologique s'est port� sur React et en trois mois, � \r\n> deux d�veloppeurs et demi (dont 1,5 sur le front), on a quelque chose \r\n> qui va bient�t �tre montrable.\r\n>\r\n> Je me permets d'intervenir sur cette question car c'est moi qui ai \r\n> pos� les premi�res pierres de l'application (et donc fait les choix \r\n> techno).\r\n>\r\n> Il faut bien avoir en t�te que comparer Angular et Ember est tout � \r\n> fait possible, mais que de comparer ces deux *frameworks* � React est \r\n> plus d�licat. React est une *lib* qui ne g�re que la partie affichage, \r\n> il n'y a aucun composant mod�le, gestion de la persistence. Facebook, \r\n> les cr�ateurs de React, proposent une architecture autour de React qui \r\n> s'appelle Flux (que nous utilisons dans notre app) qui tourne autour \r\n> du principe de \"flux unidirectionnel de donn�es\".\r\n>\r\n> Je vous sugg�re cette pr�sentation sur React qui exprime bien mieux \r\n> que moi ce que je voulais dire et qui pr�sente les avantages de la \r\n> techno \r\n> : https://speakerdeck.com/player/44129b9054c901328b89221e99b278fe# \r\n> (notamment la distinction framework vs libs). J'attire votre attention \r\n> sur une fonctionnalit� sous-estim� de React, qui est le rendu c�t� \r\n> serveur avec le m�me code que le client (je ne sais pas si c'est un \r\n> cas de \"performance\" important dans Caliopen).\r\n>\r\n> Je n'ai malheureusement pas de ressource �quivalente � vous pr�senter \r\n> sur Flux, car je n'en ai toujours pas trouv�e. Je vous sugg�re \r\n> cependant cet article sur le blog de React \r\n> : http://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html\r\n>\r\n> En r�sum�, React, c'est tr�s bien (� mon humble avis) :  �a permet \r\n> d'�crire du code vite, maintenable et *performant* (sans y \r\n> penser). Mais ce n'est qu'une partie du probl�me, la \"Vue\", et il faut \r\n> accepter (s'habituer �) une nouvelle fa�on de faire.\r\n>\r\n> Vous pourriez ajouter Backbone+React (ils jouent relativement bien \r\n> ensemble) et/ou Flux+React � votre matrice pour la compl�ter. A la \r\n> question React+Angular et React+Ember, je ne connais pas l'�tat de \r\n> l'art, ne portant pas du tout Angular dans mon coeur et ne m'�tant \r\n> jamais int�ress� � Ember.\r\n>\r\n> Je vous laisse ici l'adresse du d�p�t de notre application, si vous \r\n> voulez jeter un oeil au code. A mon avis on est � des kilom�tres de \r\n> faire les choses parfaitement, mais si �a peut \r\n> aider...https://github.com/cozy/cozy-emails\r\n>\r\n> En esp�rant que mon retour d'exp�rience puisse �tre utile, je reste � \r\n> disposition pour des questions / demande de renseignements \r\n> suppl�mentaires concernant React et Flux.\r\n>\r\n> Joseph.\r\n>\r\n> Le 11/27/2014 10:07, Alban Crommer a �crit :\r\n>\r\n>> Bonjour,\r\n>>\r\n>> D�sol�, je ne connais pas suffisamment ces 3 frameworks pour avoir un \r\n>> avis �clairant sur la question.\r\n>>\r\n>> Mais je suis content que �a puisse servir, et rassur� de voir des \r\n>> r�sultats vraisemblablement s�rieux et pointant dans la m�me direction ;)\r\n>>\r\n>> +a\r\n>>\r\n>> Le 27/11/2014 01:32, Kajan Sivaramalingam a �crit :\r\n>>> Voici ma contribution.\r\n>>> A bient�t pour la d�lib�ration.\r\n>>>\r\n>>>\r\n>>> _______________________________________________\r\n>>> Caliopdev mailing list\r\n>>> Caliopdev@caliop.net\r\n>>> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>>\r\n>>\r\n>> _______________________________________________\r\n>> Caliopdev mailing list\r\n>> Caliopdev@caliop.net  <mailto:Caliopdev@caliop.net>\r\n>> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>\r\n>\r\n>\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n\r\n--------------030301020107080302010808\r\nContent-Type: text/html; charset=windows-1252\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=windows-1252\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    Salut Joseph,<br>\r\n    <br>\r\n    Je partage assez ton avis sur React.<br>\r\n    <br>\r\n    La probl�matique que je vois pour Caliopen, est peu de temps, et des\r\n    contributeurs venant de diff�rents mondes.<br>\r\n    Pour r�pondre � ces probl�matique, je pense qu'un framework\r\n    structurant est n�cessaire. Je ne porte pas non plus angular dans\r\n    mon coeur, et la communaut� arrive enfin � un consensus sur le\r\n    sujet. Dans tous les cas (car je ne veux pas troller) il n'est pas\r\n    structurant.<br>\r\n    Ember est plus pragmatique, mais la partie rendu c�t� serveur est\r\n    effectivement un manque. Dans la roadmap pour la v2 ils annoncent\r\n    l'int�gration des meilleures parties de React et surtout, la\r\n    possibilit� de migrer sans trop d'efforts (de la v1 � v2) ce qui est\r\n    rassurant.<br>\r\n    <br>\r\n    Fluxxor et Reflux semblent �tre les projets les plus en vogue pour\r\n    faire du Flux, as tu de la visibilit� en terme de stabilit� ? On\r\n    peut s'en sortir pour structurer les choses autour de flux moyennant\r\n    un peu de boulot, mais pour le rendu server side, ca n�cessite une\r\n    application node pour rendre les pages. Ca rajoute en complexit�,\r\n    mais reste �galement g�rable.<br>\r\n    <br>\r\n    En r�sum�, passer sur React + (Flux* || Backbone) demande pas mal de\r\n    travail suppl�mentaire pour la mise en place, ce qui me semble �tre\r\n    un risque, temp�r� par la p�rennit� du mod�le (je ne crois plus au\r\n    MVC depuis pas mal de temps maintenant).<br>\r\n    <br>\r\n    Tant que Gandi ne peut pas me lib�rer pour bosser sur Caliopen, je\r\n    ne sais pas � quel point je serai disponible, c'est pourquoi je\r\n    penche plus pour Ember qui permet un d�marrage plus rapide IMHO\r\n    (guides, structure impos�e...).<br>\r\n    <br>\r\n    my 2 cents,<br>\r\n    <br>\r\n    Julien<br>\r\n    <br>\r\n    <div class=\"moz-cite-prefix\">On 11/27/2014 11:20 AM,\r\n      <a class=\"moz-txt-link-abbreviated\" href=\"mailto:joseph@cozycloud.cc\">joseph@cozycloud.cc</a> wrote:<br>\r\n    </div>\r\n    <blockquote cite=\"mid:e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc\"\r\n      type=\"cite\">\r\n      <p>Bonjour � tous,</p>\r\n      <p>Je suis Joseph de chez Cozy (<a class=\"moz-txt-link-freetext\" href=\"https://cozy.io\">https://cozy.io</a>). Certains d'entre\r\n        vous le savent peut-�tre (je ne sais pas qui exactement � part\r\n        Laurent �tait pr�sent � nos discussions) nous d�veloppons un\r\n        client email. Notre choix technologique s'est port� sur React et\r\n        en trois mois, � deux d�veloppeurs et demi (dont 1,5 sur le\r\n        front), on a quelque chose qui va bient�t �tre montrable.</p>\r\n      <p>Je me permets d'intervenir sur cette question car c'est moi qui\r\n        ai pos� les premi�res pierres de l'application (et donc fait les\r\n        choix techno).</p>\r\n      <p>Il faut bien avoir en t�te que comparer Angular et Ember est\r\n        tout � fait possible, mais que de comparer ces deux *frameworks*\r\n        � React est plus d�licat. React est une *lib* qui ne g�re que la\r\n        partie affichage, il n'y a aucun composant mod�le, gestion de la\r\n        persistence. Facebook, les cr�ateurs de React, proposent une\r\n        architecture autour de React qui s'appelle Flux (que nous\r\n        utilisons dans notre app) qui tourne autour du principe de \"flux\r\n        unidirectionnel de donn�es\".</p>\r\n      <p>Je vous sugg�re cette pr�sentation sur React qui exprime bien\r\n        mieux que moi ce que je voulais dire et qui pr�sente les\r\n        avantages de la techno\r\n        :�<a class=\"moz-txt-link-freetext\" href=\"https://speakerdeck.com/player/44129b9054c901328b89221e99b278fe#\">https://speakerdeck.com/player/44129b9054c901328b89221e99b278fe#</a>\r\n        (notamment la distinction framework vs libs). J'attire votre\r\n        attention sur une fonctionnalit� sous-estim� de React, qui est\r\n        le rendu c�t� serveur avec le m�me code que le client (je ne\r\n        sais pas si c'est un cas de \"performance\" important dans\r\n        Caliopen).</p>\r\n      <p>Je n'ai malheureusement pas de ressource �quivalente � vous\r\n        pr�senter sur Flux, car je n'en ai toujours pas trouv�e. Je vous\r\n        sugg�re cependant cet article sur le blog de React\r\n:�<a class=\"moz-txt-link-freetext\" href=\"http://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html\">http://facebook.github.io/react/blog/2014/07/30/flux-actions-and-the-dispatcher.html</a></p>\r\n      <p>En r�sum�, React, c'est tr�s bien (� mon humble avis) : ��a\r\n        permet d'�crire du code vite, maintenable et *performant* (sans\r\n        y penser).�Mais ce n'est qu'une partie du probl�me, la \"Vue\", et\r\n        il faut accepter (s'habituer �) une nouvelle fa�on de faire.</p>\r\n      <p>Vous pourriez ajouter Backbone+React (ils jouent relativement\r\n        bien ensemble) et/ou Flux+React � votre matrice pour la\r\n        compl�ter. A la question React+Angular et React+Ember, je ne\r\n        connais pas l'�tat de l'art, ne portant pas du tout Angular dans\r\n        mon coeur et ne m'�tant jamais int�ress� � Ember.</p>\r\n      <p>Je vous laisse ici l'adresse du d�p�t de notre application, si\r\n        vous voulez jeter un oeil au code. A mon avis on est � des\r\n        kilom�tres de faire les choses parfaitement, mais si �a peut\r\n        aider...https://github.com/cozy/cozy-emails</p>\r\n      <p>En esp�rant que mon retour d'exp�rience puisse �tre utile, je\r\n        reste � disposition pour des questions / demande de\r\n        renseignements suppl�mentaires concernant React et Flux.</p>\r\n      <p>Joseph.</p>\r\n      <p>Le 11/27/2014 10:07, Alban Crommer a �crit�:</p>\r\n      <blockquote type=\"cite\" style=\"padding-left:5px;\r\n        border-left:#1010ff 2px solid; margin-left:5px\"><!-- html ignored --><!-- head ignored --><!-- meta ignored -->\r\n        <div class=\"moz-cite-prefix\">Bonjour,<br>\r\n          <br>\r\n          D�sol�, je ne connais pas suffisamment ces 3 frameworks pour\r\n          avoir un avis �clairant sur la question.<br>\r\n          <br>\r\n          Mais je suis content que �a puisse servir, et rassur� de voir\r\n          des r�sultats vraisemblablement s�rieux et pointant dans la\r\n          m�me direction ;)<br>\r\n          <br>\r\n          +a<br>\r\n          <br>\r\n          Le 27/11/2014 01:32, Kajan Sivaramalingam a �crit�:</div>\r\n        <blockquote type=\"cite\" style=\"padding-left:5px;\r\n          border-left:#1010ff 2px solid; margin-left:5px\">\r\n          <div dir=\"ltr\">\r\n            <div class=\"gmail_extra\">Voici ma contribution.</div>\r\n            <div class=\"gmail_extra\">�</div>\r\n            <div class=\"gmail_extra\">�</div>\r\n            <div class=\"gmail_extra\">A bient�t pour la d�lib�ration.</div>\r\n          </div>\r\n          <br>\r\n          <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n          <br>\r\n          <pre>_______________________________________________\r\nCaliopdev mailing list\r\n<a moz-do-not-send=\"true\" class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a moz-do-not-send=\"true\" class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n        </blockquote>\r\n        <br>\r\n        <!-- html ignored --><br>\r\n        <pre>_______________________________________________\r\nCaliopdev mailing list\r\n<a moz-do-not-send=\"true\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a moz-do-not-send=\"true\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n      </blockquote>\r\n      <p>�</p>\r\n      <div>�</div>\r\n      <br>\r\n      <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n      <br>\r\n      <pre wrap=\"\">_______________________________________________\r\nCaliopdev mailing list\r\n<a class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n    </blockquote>\r\n    <br>\r\n  </body>\r\n</html>\r\n\r\n--------------030301020107080302010808--\r\n\r\n--===============0069933434==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0069933434==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - julien muetton <julien.muetton@gandi.net> - 2014-11-27 2140.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 28F53120AD9;\r\n\tThu, 27 Nov 2014 21:39:40 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -100.907\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-100.907 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, HTML_MESSAGE=0.001, RATWARE_GECKO_BUILD=1.691,\r\n\tUSER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 9wUIIBvUZO0k; Thu, 27 Nov 2014 21:39:37 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 80E46120AD0;\r\n\tThu, 27 Nov 2014 21:39:37 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id D162A6B39E691;\r\n\tThu, 27 Nov 2014 21:39:36 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id 61bEN4PyVm5J; Thu, 27 Nov 2014 21:39:33 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id B0DD76A085044;\r\n\tThu, 27 Nov 2014 21:39:32 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 7A8406B39E686\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 21:39:31 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id VJsUa4LAe0J7 for <caliopdev@caliop.net>;\r\n Thu, 27 Nov 2014 21:39:27 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id 9F2976A085044\r\n for <caliopdev@caliop.net>; Thu, 27 Nov 2014 21:39:27 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id 75BE9120AD9;\r\n Thu, 27 Nov 2014 21:39:27 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024)\r\n with ESMTP id TJsrM+wvhRxc; Thu, 27 Nov 2014 21:39:25 +0100 (CET)\r\nReceived: from [192.168.1.13]\r\n (AClermont-Ferrand-654-1-553-95.w86-216.abo.wanadoo.fr [86.216.105.95])\r\n (using TLSv1 with cipher DHE-RSA-AES128-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by mail4.gandi.net (Postfix) with ESMTPSA id B3F34120AD0;\r\n Thu, 27 Nov 2014 21:39:25 +0100 (CET)\r\nMessage-ID: <54778C34.90802@gandi.net>\r\nDate: Thu, 27 Nov 2014 21:40:20 +0100\r\nFrom: julien muetton <dev@caliopen.local>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Icedove/31.2.0\r\nMIME-Version: 1.0\r\nTo: =?windows-1252?Q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>, joseph@cozycloud.cc\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\n <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\n <20141127192529.GA1819@tao.gandi.net>\r\nIn-Reply-To: <20141127192529.GA1819@tao.gandi.net>\r\nCc: romain@cozycloud.cc, brice@cozycloud.cc\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1383819152==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--===============1383819152==\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------080606080901020308020807\"\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------080606080901020308020807\r\nContent-Type: text/plain; charset=windows-1252; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\n\r\nOn 11/27/2014 08:25 PM, Aymeric Barantal wrote:\r\n> On Thursday 27 Nov 2014 � 12:28:25 (+0100), joseph@cozycloud.cc wrote:\r\n>> Ton analyse et tes incertitudes sur React/Flux sont justifi�es. On a pris un\r\n>> risque de notre c�t� et heureusement les deux d�v ont fait un bon boulot de\r\n>> prise en main et d'adaptation.\r\n>>\r\n>> Pas de retour concernant Reflux, que je veux regarder en bloc et en d�tail car\r\n>> je ne suis pas satisfait de l'impl�mentation \"officielle\" de Flux, dont Fluxxor\r\n>> s'inspire fortement (on utilisait Fluxxor � la base, �a n'apporte rien AMHA,\r\n>> donc supprim�. Mais je n'ai pas regard� son �volution depuis plusieurs mois).\r\n>>\r\n>> Je n'avais pas pens� � la n�cessit� de Node pour utiliser React pour fair le\r\n>> rendu c�t� serveur, comme Caliopen est en Python (de m�moire) ce n'est\r\n>> effectivement pas un argument. Par contre, pour b�n�ficier de l'isomorphisme,\r\n>> il faut obligatoire du Node � un moment (� ma connaissance).\r\n> Alors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\n> sont bien moins importants [1] que les avantages que je vois a cette stricte\r\n> separation technologique entre front et api.\r\nIl est aussi possible d'avoir un serveur nodeJS qui sert les pages HTML \r\net l'API en python.\r\nUn reverse proxy permet d'agr�ger les deux via des r�gles simples \r\n(HAProxy, Nginx, Apache...)\r\n\r\n    /api -> api python\r\n    cdn.domain.tld -> asset statics\r\n    default -> serveur nodeJS\r\n\r\nCa colle bien avec l'approche �Service.\r\n\r\nMaintenant, je ne sais pas � quel point il faut dupliquer la logique \r\npour tirer partie de l'isomorphisme.\r\nJe regarde comment on consomme les donn�es sur le serveur (notamment \r\nest-ce que l'on passe par les stores).\r\n\r\nthemouette\r\n\r\n--------------080606080901020308020807\r\nContent-Type: text/html; charset=windows-1252\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=windows-1252\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    <br>\r\n    <div class=\"moz-cite-prefix\">On 11/27/2014 08:25 PM, Aymeric\r\n      Barantal wrote:<br>\r\n    </div>\r\n    <blockquote cite=\"mid:20141127192529.GA1819@tao.gandi.net\"\r\n      type=\"cite\">\r\n      <pre wrap=\"\">On Thursday 27 Nov 2014 � 12:28:25 (+0100), <a class=\"moz-txt-link-abbreviated\" href=\"mailto:joseph@cozycloud.cc\">joseph@cozycloud.cc</a> wrote:\r\n</pre>\r\n      <blockquote type=\"cite\">\r\n        <pre wrap=\"\">Ton analyse et tes incertitudes sur React/Flux sont justifi�es. On a pris un\r\nrisque de notre c�t� et heureusement les deux d�v ont fait un bon boulot de\r\nprise en main et d'adaptation.\r\n\r\nPas de retour concernant Reflux, que je veux regarder en bloc et en d�tail car\r\nje ne suis pas satisfait de l'impl�mentation \"officielle\" de Flux, dont Fluxxor\r\ns'inspire fortement (on utilisait Fluxxor � la base, �a n'apporte rien AMHA,\r\ndonc supprim�. Mais je n'ai pas regard� son �volution depuis plusieurs mois).\r\n\r\nJe n'avais pas pens� � la n�cessit� de Node pour utiliser React pour fair le\r\nrendu c�t� serveur, comme Caliopen est en Python (de m�moire) ce n'est\r\neffectivement pas un argument. Par contre, pour b�n�ficier de l'isomorphisme,\r\nil faut obligatoire du Node � un moment (� ma connaissance).\r\n</pre>\r\n      </blockquote>\r\n      <pre wrap=\"\">\r\nAlors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\nsont bien moins importants [1] que les avantages que je vois a cette stricte\r\nseparation technologique entre front et api.\r\n</pre>\r\n    </blockquote>\r\n    Il est aussi possible d'avoir un serveur nodeJS qui sert les pages\r\n    HTML et l'API en python.<br>\r\n    Un reverse proxy permet d'agr�ger les deux via des r�gles simples\r\n    (HAProxy, Nginx, Apache...)<br>\r\n    <br>\r\n    <blockquote>/api -&gt; api python<br>\r\n      cdn.domain.tld -&gt; asset statics<br>\r\n      default -&gt; serveur nodeJS<br>\r\n    </blockquote>\r\n    Ca colle bien avec l'approche �Service.<br>\r\n    <br>\r\n    Maintenant, je ne sais pas � quel point il faut dupliquer la logique\r\n    pour tirer partie de l'isomorphisme.<br>\r\n    Je regarde comment on consomme les donn�es sur le serveur (notamment\r\n    est-ce que l'on passe par les stores).<br>\r\n    <br>\r\n    themouette<br>\r\n  </body>\r\n</html>\r\n\r\n--------------080606080901020308020807--\r\n\r\n--===============1383819152==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1383819152==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - julien muetton <julien.muetton@gandi.net> - 2014-11-28 1222.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 53D99120AD9;\r\n\tFri, 28 Nov 2014 12:22:06 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -98.674\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-98.674 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74, HTML_30_40=0.374, HTML_MESSAGE=0.001,\r\n\tRATWARE_GECKO_BUILD=1.691, USER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id Vm-9b0UEjdE0; Fri, 28 Nov 2014 12:22:03 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id C97C2120A9A;\r\n\tFri, 28 Nov 2014 12:22:03 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id A6F9E6B3D03E5;\r\n\tFri, 28 Nov 2014 12:22:03 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id ZumA7IXjbp9Y; Fri, 28 Nov 2014 12:22:02 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 00ED66A1651DE;\r\n\tFri, 28 Nov 2014 12:22:01 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id E0C1D6A1651DE\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 12:22:00 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id Vrg40X43rLj8 for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 12:21:57 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id 25A9B6A168F7B\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 12:21:56 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id D9DF6120AD9;\r\n Fri, 28 Nov 2014 12:21:56 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024)\r\n with ESMTP id qBNm40D5r3BD; Fri, 28 Nov 2014 12:21:54 +0100 (CET)\r\nReceived: from [10.242.2.58] (corp-ras.gandi.net [217.70.181.25])\r\n (using TLSv1 with cipher DHE-RSA-AES128-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by mail4.gandi.net (Postfix) with ESMTPSA id AFC08120A9A;\r\n Fri, 28 Nov 2014 12:21:54 +0100 (CET)\r\nMessage-ID: <54785B09.9010402@gandi.net>\r\nDate: Fri, 28 Nov 2014 12:22:49 +0100\r\nFrom: julien muetton <dev@caliopen.local>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Icedove/31.2.0\r\nMIME-Version: 1.0\r\nTo: =?windows-1252?Q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>, joseph@cozycloud.cc, romain@cozycloud.cc, \r\n brice@cozycloud.cc\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\n <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\n <20141127192529.GA1819@tao.gandi.net> <54778C34.90802@gandi.net>\r\n <54784A99.3020306@chamal.fr>\r\nIn-Reply-To: <54784A99.3020306@chamal.fr>\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1265580351==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--===============1265580351==\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------070705050402030705040501\"\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------070705050402030705040501\r\nContent-Type: text/plain; charset=windows-1252; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\n\r\nOn 11/28/2014 11:12 AM, Aymeric Barantal wrote:\r\n> On 11/27/2014 09:40 PM, julien muetton wrote:\r\n>>\r\n>> On 11/27/2014 08:25 PM, Aymeric Barantal wrote:\r\n>>> On Thursday 27 Nov 2014 � 12:28:25 (+0100),joseph@cozycloud.cc  wrote:\r\n>>>> Ton analyse et tes incertitudes sur React/Flux sont justifi�es. On a pris un\r\n>>>> risque de notre c�t� et heureusement les deux d�v ont fait un bon boulot de\r\n>>>> prise en main et d'adaptation.\r\n>>>>\r\n>>>> Pas de retour concernant Reflux, que je veux regarder en bloc et en d�tail car\r\n>>>> je ne suis pas satisfait de l'impl�mentation \"officielle\" de Flux, dont Fluxxor\r\n>>>> s'inspire fortement (on utilisait Fluxxor � la base, �a n'apporte rien AMHA,\r\n>>>> donc supprim�. Mais je n'ai pas regard� son �volution depuis plusieurs mois).\r\n>>>>\r\n>>>> Je n'avais pas pens� � la n�cessit� de Node pour utiliser React pour fair le\r\n>>>> rendu c�t� serveur, comme Caliopen est en Python (de m�moire) ce n'est\r\n>>>> effectivement pas un argument. Par contre, pour b�n�ficier de l'isomorphisme,\r\n>>>> il faut obligatoire du Node � un moment (� ma connaissance).\r\n>>> Alors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\n>>> sont bien moins importants [1] que les avantages que je vois a cette stricte\r\n>>> separation technologique entre front et api.\r\n>> Il est aussi possible d'avoir un serveur nodeJS qui sert les pages \r\n>> HTML et l'API en python.\r\n>> Un reverse proxy permet d'agr�ger les deux via des r�gles simples \r\n>> (HAProxy, Nginx, Apache...)\r\n> De quelles pages html est il question ? celles qui passent par \r\n> l'internationalisation ?\r\nL'isomorphisme signifie rendre indiff�remment sur le serveur et sur le \r\nclient, comme twitter et wallmart.\r\n> J'ai du mal a voir le gain reel d'une plate-forme node.js a part du \r\n> code en commun, qui semble etre\r\n> la notion principale cachee derriere le terme d'isomorphisme.\r\nc'est pour avoir une meilleure exp�rience utilisateur.\r\nla premi�re page est affich�e directement, ce qui r�duit le \"time to \r\nfirst interaction\" (introduit par twitter comme \"time to tweet\")\r\n> A ce jour c'est le serveur fournissant l'api qui sait fournir \r\n> egalement des pages html (enfin une\r\n> seule du fait de l'utilisation d'angular sur le POC).\r\n>>\r\n>>     /api -> api python\r\n>>     cdn.domain.tld -> asset statics\r\n>>     default -> serveur nodeJS\r\n>>\r\n>> Ca colle bien avec l'approche �Service.\r\n> L'orientation globale n'est pas microservice cote API / backend\r\n>\r\n>>\r\n>> Maintenant, je ne sais pas � quel point il faut dupliquer la logique \r\n>> pour tirer partie de l'isomorphisme.\r\n>> Je regarde comment on consomme les donn�es sur le serveur (notamment \r\n>> est-ce que l'on passe par les stores).\r\n> La je suis perdu ;)\r\n>\r\nEn rendant les pages sur le serveur, il faut aussi r�cup�rer les donn�es \r\n� travers l'API.\r\nCe que je cherche � trouver c'est la quantit� de code � dupliquer, car \r\nconsommer une API dans le browser passe par XMLHttpRequest, alors que \r\nsur le serveur XMLHttpRequest n'est pas dispo.\r\n\r\nLa logique en Fluxx, sauf erreur de ma part, est de placer tout le code \r\nm�tier dans les Stores qui sont � la fois \"data mapper\" et logique domaine.\r\nSi c'est top pour ne pas coupler son code � un framework (on d�veloppe \r\nnotre m�tier/domaine comme on l'entend), il y a forc�ment des \r\ndiff�rences entre le client et le serveur.\r\n\r\n@Joseph, @romain, @brice, vous avez une visibilit� sur la quantit� de \r\ncode � dupliquer ?\r\n> Quelle logique serait a dupliquer et finalement qu'est qui est entendu \r\n> par isomorphisme dans ce cadre, j'ai\r\n> l'impression de ne plus comprendre ce terme.\r\n>\r\n> De quels stores est il question ? Le local storage sera certainement a \r\n> exploiter s'il fait parti de cette definition.\r\n>\r\n> ++ chamal\r\n>\r\n>\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n\r\n--------------070705050402030705040501\r\nContent-Type: text/html; charset=windows-1252\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=windows-1252\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    <br>\r\n    <div class=\"moz-cite-prefix\">On 11/28/2014 11:12 AM, Aymeric\r\n      Barantal wrote:<br>\r\n    </div>\r\n    <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\">\r\n      <meta content=\"text/html; charset=windows-1252\"\r\n        http-equiv=\"Content-Type\">\r\n      <div class=\"moz-cite-prefix\">On 11/27/2014 09:40 PM, julien\r\n        muetton wrote:<br>\r\n      </div>\r\n      <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\">\r\n        <meta content=\"text/html; charset=windows-1252\"\r\n          http-equiv=\"Content-Type\">\r\n        <br>\r\n        <div class=\"moz-cite-prefix\">On 11/27/2014 08:25 PM, Aymeric\r\n          Barantal wrote:<br>\r\n        </div>\r\n        <blockquote cite=\"mid:20141127192529.GA1819@tao.gandi.net\"\r\n          type=\"cite\">\r\n          <pre wrap=\"\">On Thursday 27 Nov 2014 � 12:28:25 (+0100), <a moz-do-not-send=\"true\" class=\"moz-txt-link-abbreviated\" href=\"mailto:joseph@cozycloud.cc\">joseph@cozycloud.cc</a> wrote:\r\n</pre>\r\n          <blockquote type=\"cite\">\r\n            <pre wrap=\"\">Ton analyse et tes incertitudes sur React/Flux sont justifi�es. On a pris un\r\nrisque de notre c�t� et heureusement les deux d�v ont fait un bon boulot de\r\nprise en main et d'adaptation.\r\n\r\nPas de retour concernant Reflux, que je veux regarder en bloc et en d�tail car\r\nje ne suis pas satisfait de l'impl�mentation \"officielle\" de Flux, dont Fluxxor\r\ns'inspire fortement (on utilisait Fluxxor � la base, �a n'apporte rien AMHA,\r\ndonc supprim�. Mais je n'ai pas regard� son �volution depuis plusieurs mois).\r\n\r\nJe n'avais pas pens� � la n�cessit� de Node pour utiliser React pour fair le\r\nrendu c�t� serveur, comme Caliopen est en Python (de m�moire) ce n'est\r\neffectivement pas un argument. Par contre, pour b�n�ficier de l'isomorphisme,\r\nil faut obligatoire du Node � un moment (� ma connaissance).\r\n</pre>\r\n          </blockquote>\r\n          <pre wrap=\"\">Alors il ne faut absolument pas de ce type d'isomorphisme, les inconvenients\r\nsont bien moins importants [1] que les avantages que je vois a cette stricte\r\nseparation technologique entre front et api.\r\n</pre>\r\n        </blockquote>\r\n        Il est aussi possible d'avoir un serveur nodeJS qui sert les\r\n        pages HTML et l'API en python.<br>\r\n        Un reverse proxy permet d'agr�ger les deux via des r�gles\r\n        simples (HAProxy, Nginx, Apache...)<br>\r\n      </blockquote>\r\n      De quelles pages html est il question ? celles qui passent par\r\n      l'internationalisation ?<br>\r\n    </blockquote>\r\n    L'isomorphisme signifie rendre indiff�remment sur le serveur et sur\r\n    le client, comme twitter et wallmart.<br>\r\n    <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\"> J'ai\r\n      du mal a voir le gain reel d'une plate-forme node.js a part du\r\n      code en commun, qui semble etre<br>\r\n      la notion principale cachee derriere le terme d'isomorphisme.<br>\r\n    </blockquote>\r\n    c'est pour avoir une meilleure exp�rience utilisateur.<br>\r\n    la premi�re page est affich�e directement, ce qui r�duit le \"time to\r\n    first interaction\" (introduit par twitter comme \"time to tweet\")<br>\r\n    <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\"> A ce\r\n      jour c'est le serveur fournissant l'api qui sait fournir egalement\r\n      des pages html (enfin une<br>\r\n      seule du fait de l'utilisation d'angular sur le POC).<br>\r\n      <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\"> <br>\r\n        <blockquote>/api -&gt; api python<br>\r\n          cdn.domain.tld -&gt; asset statics<br>\r\n          default -&gt; serveur nodeJS<br>\r\n        </blockquote>\r\n        Ca colle bien avec l'approche �Service.<br>\r\n      </blockquote>\r\n      L'orientation globale n'est pas microservice cote API / backend<br>\r\n      <br>\r\n      <blockquote cite=\"mid:54778C34.90802@gandi.net\" type=\"cite\"> <br>\r\n        Maintenant, je ne sais pas � quel point il faut dupliquer la\r\n        logique pour tirer partie de l'isomorphisme.<br>\r\n        Je regarde comment on consomme les donn�es sur le serveur\r\n        (notamment est-ce que l'on passe par les stores).<br>\r\n      </blockquote>\r\n      La je suis perdu ;)<br>\r\n      <br>\r\n    </blockquote>\r\n    En rendant les pages sur le serveur, il faut aussi r�cup�rer les\r\n    donn�es � travers l'API.<br>\r\n    Ce que je cherche � trouver c'est la quantit� de code � dupliquer,\r\n    car consommer une API dans le browser passe par XMLHttpRequest,\r\n    alors que sur le serveur XMLHttpRequest n'est pas dispo.<br>\r\n    <br>\r\n    La logique en Fluxx, sauf erreur de ma part, est de placer tout le\r\n    code m�tier dans les Stores qui sont � la fois \"data mapper\" et\r\n    logique domaine.<br>\r\n    Si c'est top pour ne pas coupler son code � un framework (on\r\n    d�veloppe notre m�tier/domaine comme on l'entend), il y a forc�ment\r\n    des diff�rences entre le client et le serveur.<br>\r\n    <br>\r\n    @Joseph, @romain, @brice, vous avez une visibilit� sur la quantit�\r\n    de code � dupliquer ?<br>\r\n    <blockquote cite=\"mid:54784A99.3020306@chamal.fr\" type=\"cite\">\r\n      Quelle logique serait a dupliquer et finalement qu'est qui est\r\n      entendu par isomorphisme dans ce cadre, j'ai<br>\r\n      l'impression de ne plus comprendre ce terme.<br>\r\n      <br>\r\n      De quels stores est il question ? Le local storage sera\r\n      certainement a exploiter s'il fait parti de cette definition.<br>\r\n      <br>\r\n      ++ chamal<br>\r\n      <br>\r\n      <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n      <br>\r\n      <pre wrap=\"\">_______________________________________________\r\nCaliopdev mailing list\r\n<a class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n</pre>\r\n    </blockquote>\r\n    <br>\r\n  </body>\r\n</html>\r\n\r\n--------------070705050402030705040501--\r\n\r\n--===============1265580351==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1265580351==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Fwd: Re: matrice de choix framework JS - julien muetton <julien.muetton@gandi.net> - 2014-11-28 1609.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 951CD120A24;\r\n\tFri, 28 Nov 2014 16:08:30 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -100.492\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-100.492 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, FUZZY_AMBIEN=0.416, RATWARE_GECKO_BUILD=1.691,\r\n\tUSER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 4xFrZG03lZnx; Fri, 28 Nov 2014 16:08:28 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 81E66120A1F;\r\n\tFri, 28 Nov 2014 16:08:28 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 606026AAC5AE2;\r\n\tFri, 28 Nov 2014 16:08:28 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id AiclNBAso19L; Fri, 28 Nov 2014 16:08:27 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id F27226AAC5AD2;\r\n\tFri, 28 Nov 2014 16:08:26 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 8EFE06AAC5AE1\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:08:25 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id 4UupyT0fn3QO for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 16:08:24 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id 7631D6AAC5AD2\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:08:24 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id 42D9A120A24\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:08:24 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id tnDh7J+PslAf for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 16:08:22 +0100 (CET)\r\nReceived: from [10.242.2.58] (corp-ras.gandi.net [217.70.181.25])\r\n (using TLSv1 with cipher DHE-RSA-AES128-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by mail4.gandi.net (Postfix) with ESMTPSA id 396FF120A1F\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 16:08:22 +0100 (CET)\r\nMessage-ID: <5478901C.4020201@gandi.net>\r\nDate: Fri, 28 Nov 2014 16:09:16 +0100\r\nFrom: julien muetton <dev@caliopen.local>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Icedove/31.2.0\r\nMIME-Version: 1.0\r\nTo: =?windows-1252?Q?Liste_de_d=E9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nReferences: <547453AE.70408@chamal.fr> <547457A9.9070801@chamal.fr>\r\n <5475EC1E.4010603@chamal.fr> <20141126224605.GH8770@brasil.brainstorm.fr>\r\n <CA+tgzei=imZu9xfDGJuVYiXzgOqEo=kBVojf-16uo-gBheEjow@mail.gmail.com>\r\n <5476E9CF.70601@albancrommer.com>\r\n <e58b3dee04100f500d35b49b5a10ec71@cozycloud.cc> <547706E2.4030805@gandi.net>\r\n <8bdc1ae7396c65d043e516cd97c1b6da@cozycloud.cc>\r\n <20141127192529.GA1819@tao.gandi.net> <54778C34.90802@gandi.net>\r\n <54784A99.3020306@chamal.fr> <54785B09.9010402@gandi.net>\r\n <54788421.1040800@gandi.net> <54788E1F.7060700@gandi.net>\r\nIn-Reply-To: <54788E1F.7060700@gandi.net>\r\nSubject: Re: [Caliopdev] Fwd: Re: matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=\"iso-8859-1\"; Format=\"flowed\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\nOn 11/28/2014 04:00 PM, Vincent MINEAUD wrote:\r\n> On 28/11/2014 15:18, Alexis wrote:\r\n>> Par isomorphisme, moi j'entends que le code est le m=EAme est peut =EAtre\r\n>> utilis=E9 c=F4t=E9 serveur ou c=F4t=E9 client.\r\n>> Donc pas de duplication, juste 1 code : 2 utilisations possibles.\r\n>>\r\n>> Browserify est de la partie bien sur pour faire tout =E7a.\r\n> Il semble que webpack est m=EAme plus performant que browserify (entendu\r\n> lors d'un meetup)\r\n> plus d'info ici : https://github.com/webpack/webpack\r\nOn disgresse, mais ca n'est pas aussi simple que ca.\r\nOn est =E0 6 mois de la sortie de es6, on a des confs sur es7, il est =\r\n\r\njuste impensable de faire autre chose que des modules es6.\r\nEnsuite, la techno de transpile/packaging/legacy, j'ai pas d'avis, =\r\n\r\nwebpack a l'air cool, 6to5 + browserify aussi.\r\nCa reste du d=E9tail d'impl=E9mentation.\r\n\r\nL'isomorphisme n=E9cessite forc=E9ment d'avoir du code qui diff=E8re selon =\r\nla =\r\n\r\nplateforme, dans une librairie ou dans notre code, mais g=E9n=E9ralement =\r\n\r\ndans les deux.\r\nLa question c'est combien.\r\n\r\nmy 2 cents\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Nouveau screencast (brut) - Laurent Chemla <laurent@brainstorm.fr> - 2014-11-30 1724.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id D6A4E120A59;\r\n\tSun, 30 Nov 2014 17:24:44 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.74\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.74 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 7e8xIVQo8Ouo; Sun, 30 Nov 2014 17:24:43 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 50C7D120A50;\r\n\tSun, 30 Nov 2014 17:24:43 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 2BE686B596B05;\r\n\tSun, 30 Nov 2014 17:24:42 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id hLW0ygKp1PN7; Sun, 30 Nov 2014 17:24:38 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 8D9756B4C0627;\r\n\tSun, 30 Nov 2014 17:24:37 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 758EC6B4C0627\r\n for <caliopdev@caliop.net>; Sun, 30 Nov 2014 17:24:36 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id PD-x7LdQtjgi for <caliopdev@caliop.net>;\r\n Sun, 30 Nov 2014 17:24:31 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 929A06B4D6806\r\n for <caliopdev@caliop.net>; Sun, 30 Nov 2014 17:24:31 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 77215209FBE6E; Sun, 30 Nov 2014 17:24:31 +0100 (CET)\r\nDate: Sun, 30 Nov 2014 17:24:31 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141130162431.GF14633@brasil.brainstorm.fr>\r\nReferences: <5478A041.8020105@chamal.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nIn-Reply-To: <5478A041.8020105@chamal.fr>\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: Re: [Caliopdev] Nouveau screencast (brut)\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n> N???h=E9sitez pas =E0 me faire vos remarques, bousculer un peu ce que\r\n> j???ai fait pour n???en tirer que le meilleur ;)\r\n\r\nOk pour les lenteurs ajout=E9es pour rendre le truc plus r=E9el, mais\r\nelles sont parfois exag=E9r=E9e en sens inverse (on veut pas non plus\r\nque les gens abandonnent parce que =E7a bouge pas).\r\n\r\n\"What type of account do you want to use ?\" me semble mauvais: il\r\nva revenir l=E0 =E0 chaque fois qu'il ajoutera un compte externe, donc\r\nau minimum \"do you want to add\" mais =E7a me semble encore pas bon\r\n(\"type of account\" me g=E8ne\").\r\n\r\nIdem \"add _at least_ one in your settings\".\r\n\r\n\"Enter one of your email addresses\" pas au pluriel la majorit=E9 des\r\ngens n'en ont qu'une (en r=E9alit=E9 plusieurs mais ils l'ignorent) et\r\n=E7a va les perdre. \"Information about session\" est encore pire, et\r\nla suite aussi. Il faut revoir les textes compl=E8tement.\r\n\r\nRAS sur la suite: Benjamin Sonntag est en train de r=E9diger un\r\ntexte pour mettre une voix (en/fr) sur la video, en plus des sous\r\ntitres, donc signaler toute modif pour qu'on puisse tout faire\r\ncoller ensuite. =C7a va =EAtre dans un pad =E0 venir.\r\n\r\nL.\r\n\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Nouveau screencast (brut) - Laurent Chemla <laurent@brainstorm.fr> - 2014-12-01 1214.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 6B395120E01;\r\n\tMon,  1 Dec 2014 12:14:21 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -1.11\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-1.11 tagged_above=-999 required=5\r\n\ttests=[BAYES_05=-1.11]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 8aTIoU0KHwgG; Mon,  1 Dec 2014 12:14:20 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 72F4D120E3B;\r\n\tMon,  1 Dec 2014 12:14:20 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id D991B6A160D00;\r\n\tMon,  1 Dec 2014 12:14:19 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id 2Pmhd7OTL6mx; Mon,  1 Dec 2014 12:14:14 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id B5CFC6A160CFF;\r\n\tMon,  1 Dec 2014 12:14:13 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 2C6E669F6429C\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 12:14:12 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id ldY3nrMXJcX8 for <caliopdev@caliop.net>;\r\n Mon,  1 Dec 2014 12:14:11 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 2056B69F07ABC\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 12:14:11 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 036D520BB4B60; Mon,  1 Dec 2014 12:14:10 +0100 (CET)\r\nDate: Mon, 1 Dec 2014 12:14:10 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141201111410.GI14633@brasil.brainstorm.fr>\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\n <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nIn-Reply-To: <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: Re: [Caliopdev] Nouveau screencast (brut)\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n> > RAS sur la suite: Benjamin Sonntag est en train de r=E9diger un\r\n> > texte pour mettre une voix (en/fr) sur la video, en plus des sous\r\n> > titres, donc signaler toute modif pour qu'on puisse tout faire\r\n> > coller ensuite. =C7a va =EAtre dans un pad =E0 venir.\r\n> =\r\n\r\n> Okay, super !\r\n\r\nhttps://pad.lqdn.fr/p/screencast-caliopen\r\n\r\nDonnez vos avis en ligne, thanks.\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Nouveau screencast (brut) - Thomas Laurent <thomas.laurent@gandi.net> - 2014-12-01 1149.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 56FFC120FFA;\r\n\tMon,  1 Dec 2014 11:49:51 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -100.605\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-100.605 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74, FORGED_RCVD_HELO=0.135, USER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id r4VjpuS3xxHi; Mon,  1 Dec 2014 11:49:49 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 4EC76120FF6;\r\n\tMon,  1 Dec 2014 11:49:49 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id C0CF76A160D0D;\r\n\tMon,  1 Dec 2014 11:49:48 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id yv2ax5dUJHJX; Mon,  1 Dec 2014 11:49:45 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 659E06A160D02;\r\n\tMon,  1 Dec 2014 11:49:44 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 4E4256A160D0B\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 11:49:43 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id SjTqdj7udKSh for <caliopdev@caliop.net>;\r\n Mon,  1 Dec 2014 11:49:42 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id 3DD756A160D02\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 11:49:42 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id 195E9120FF7\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 11:49:42 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id mlNwDKa+FsBm for <caliopdev@caliop.net>;\r\n Mon,  1 Dec 2014 11:49:41 +0100 (CET)\r\nReceived: from new-host-2.home (ALille-653-1-407-5.w92-142.abo.wanadoo.fr\r\n [92.142.63.5])\r\n (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by mail4.gandi.net (Postfix) with ESMTPSA id 17B99120FF6\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 11:49:41 +0100 (CET)\r\nMime-Version: 1.0 (Mac OS X Mail 8.1 \\(1993\\))\r\nFrom: Thomas Laurent <thomas.laurent@gandi.net>\r\nIn-Reply-To: <20141130162431.GF14633@brasil.brainstorm.fr>\r\nDate: Mon, 1 Dec 2014 11:49:40 +0100\r\nMessage-Id: <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\nTo: =?windows-1252?Q?Liste_de_d=E9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nX-Mailer: Apple Mail (2.1993)\r\nSubject: Re: [Caliopdev] Nouveau screencast (brut)\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"windows-1252\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n> Le 30 nov. 2014 =E0 17:24, Laurent Chemla <laurent@brainstorm.fr> a =E9cr=\r\nit :\r\n> =\r\n\r\n> =\r\n\r\n>> N???h=E9sitez pas =E0 me faire vos remarques, bousculer un peu ce que\r\n>> j???ai fait pour n???en tirer que le meilleur ;)\r\n> =\r\n\r\n> Ok pour les lenteurs ajout=E9es pour rendre le truc plus r=E9el, mais\r\n> elles sont parfois exag=E9r=E9e en sens inverse (on veut pas non plus\r\n> que les gens abandonnent parce que =E7a bouge pas).\r\n\r\nA quel(s) moment(s) ?\r\n\r\n> =\r\n\r\n> \"What type of account do you want to use ?\" me semble mauvais: il\r\n> va revenir l=E0 =E0 chaque fois qu'il ajoutera un compte externe, donc\r\n> au minimum \"do you want to add\" mais =E7a me semble encore pas bon\r\n> (\"type of account\" me g=E8ne =BB).\r\n> =\r\n\r\n> Idem \"add _at least_ one in your settings\".\r\n> =\r\n\r\n> \"Enter one of your email addresses\" pas au pluriel la majorit=E9 des\r\n> gens n'en ont qu'une (en r=E9alit=E9 plusieurs mais ils l'ignorent) et\r\n> =E7a va les perdre. \"Information about session\" est encore pire, et\r\n> la suite aussi. Il faut revoir les textes compl=E8tement.\r\n\r\nJe suis compl=E8tement d=92accord. =C0 ce stade, je me suis content=E9 de =\r\n\r\nfaire du maquettage. Comme je dis toujours : r=E9dacteur de contenu, =\r\n\r\n=E7a ne s=92improvise pas. Et m=EAme si je pense pouvoir aider sur ce point=\r\n, =\r\n\r\nje ne voulais pas faire l=92un au d=E9triment de l=92autre.\r\n\r\n> RAS sur la suite: Benjamin Sonntag est en train de r=E9diger un\r\n> texte pour mettre une voix (en/fr) sur la video, en plus des sous\r\n> titres, donc signaler toute modif pour qu'on puisse tout faire\r\n> coller ensuite. =C7a va =EAtre dans un pad =E0 venir.\r\n\r\nOkay, super !\r\n\r\n> =\r\n\r\n> L.\r\n> =\r\n\r\n> =\r\n\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaine réunion - Aymeric Barantal <mric@chamal.fr> - 2014-12-02 1510.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 40D00120A19;\r\n\tTue,  2 Dec 2014 15:10:47 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.908\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.908 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, RATWARE_GECKO_BUILD=1.691]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id SDg6XJt7z-EX; Tue,  2 Dec 2014 15:10:46 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 310DC120A12;\r\n\tTue,  2 Dec 2014 15:10:46 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 0BD1E6B602977;\r\n\tTue,  2 Dec 2014 15:10:46 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id n-WLGF_W4tC8; Tue,  2 Dec 2014 15:10:45 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 33A476B602949;\r\n\tTue,  2 Dec 2014 15:10:44 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 55CA66B602964\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 15:10:43 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id PEqJDshBRuF0 for <caliopdev@caliop.net>;\r\n Tue,  2 Dec 2014 15:10:38 +0100 (CET)\r\nReceived: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net\r\n [217.70.183.196])\r\n (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id B311C6B602949\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 15:10:38 +0100 (CET)\r\nReceived: from [IPv6:2001:4b98:beef:a:a9fb:b50f:64e2:9778] (unknown\r\n [IPv6:2001:4b98:beef:a:a9fb:b50f:64e2:9778])\r\n (Authenticated sender: mric@chamal.fr)\r\n by relay4-d.mail.gandi.net (Postfix) with ESMTPSA id 631D21720E9\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 15:10:38 +0100 (CET)\r\nMessage-ID: <547DC86D.6010806@chamal.fr>\r\nDate: Tue, 02 Dec 2014 15:10:53 +0100\r\nFrom: Aymeric Barantal <mric@chamal.fr>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: caliopdev@caliop.net\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\n <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\n <20141201111410.GI14633@brasil.brainstorm.fr>\r\n <20141201204235.GC7323@brasil.brainstorm.fr>\r\nIn-Reply-To: <20141201204235.GC7323@brasil.brainstorm.fr>\r\nSubject: Re: [Caliopdev] =?windows-1252?q?Prochaine_r=E9union?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=\"iso-8859-1\"; Format=\"flowed\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nOn 12/01/2014 09:42 PM, Laurent Chemla wrote:\r\n> Puisque les mecs de Qwant me disent que j'aurais d=FB les relancer\r\n> plus t=F4t (ils sont pas s=FBrs de pouvoir venir du coup), je pr=E9f=E8re\r\n> assurer de ce c=F4t=E9-ci de l'email et vous rappeler la prochaine\r\n> r=E9union, vendredi prochain (5/12) =E0 10h30 chez Gandi.\r\n>\r\n> Les th=E8mes seront:\r\n>\r\n> - choix final (?) du framework,\r\nOn a un gagnant qui se demarque en regardant les quelques\r\nreponses recues et meme en excluant ma matrice (j'ai une\r\nlegitimite toute relative a choisir une librairie / framework JS AMA).\r\n\r\nLe consensus semble donc se faire autour d'Ember qui est\r\nglobalement mieux note.\r\n\r\n++ chamal\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaine réunion - David Epely <dey@protonmail.ch> - 2014-12-02 1056.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 3F841120A4B;\r\n\tTue,  2 Dec 2014 10:56:49 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.175\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.175 tagged_above=-999 required=5\r\n\ttests=[BAYES_40=-0.185, FORGED_RCVD_HELO=0.135, HTML_MESSAGE=0.001,\r\n\tMIME_BASE64_NO_NAME=0.224]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 2jO8lELMcGpe; Tue,  2 Dec 2014 10:56:46 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id B3DA8120A24;\r\n\tTue,  2 Dec 2014 10:56:46 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id DEC0E6B61536E;\r\n\tTue,  2 Dec 2014 10:56:45 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=protonmail.ch header.i=@protonmail.ch header.b=NZgHHkns;\r\n\tdkim-adsp=discard (insecure policy); dkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id EwY7WajdPO4z; Tue,  2 Dec 2014 10:56:42 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 924246B611F2F;\r\n\tTue,  2 Dec 2014 10:56:41 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 02F0A6B611F2F\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 10:56:40 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id 9aIJea8Q08iY for <caliopdev@caliop.net>;\r\n Tue,  2 Dec 2014 10:56:37 +0100 (CET)\r\nReceived: from mail.protonmail.ch (protonmail.ch [37.35.106.36])\r\n (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 323C66B612F00\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 10:56:37 +0100 (CET)\r\nReceived: by mail.protonmail.ch (Postfix, from userid 48)\r\n id 8A3C1142527B9; Tue,  2 Dec 2014 09:56:36 +0000 (UTC)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=protonmail.ch;\r\n s=default; t=1417514196;\r\n bh=jfJFcAjyANlOvsBVbbYmvqMFaBO8aRcVGxMce4CF2kg=;\r\n h=To:Subject:Date:From:Reply-To;\r\n b=NZgHHknsuvJXjWmUIEf4+Tfwcuaj7V/zr4oCQBp2rudu3Bbhs9W3wRtknJBDWErlK\r\n NoIvfJ2RwqsCoWX60qlo6hpo4tClq1YG7yktc5cqx4lg6okLBCprEnxuU8LmFwZwRI\r\n R9Laa+FE2FN7JBoLjWUnRVe/0Ggh9lNYyxmttEbQ=\r\nTo: \"caliopdev@caliop.net\" <caliopdev@caliop.net>\r\nDate: Tue, 2 Dec 2014 04:56:36 -0500\r\nFrom: David Epely <dey@protonmail.ch>\r\nMessage-ID: <06f0b2142e06880c954195ec9894b7e6@protonmail.ch>\r\nX-Priority: 3\r\nMIME-Version: 1.0\r\nSubject: Re: [Caliopdev] =?utf-8?q?Prochaine_r=C3=A9union?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: David Epely <dey@protonmail.ch>,\r\n =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1911819744==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--===============1911819744==\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n\r\nRXQgcydpbCB5IGEgdG91dCBkZSBtw6ptZSB2aWTDqW8gY29uZsOpcmVuY2UsIGplIHBvdXJyYWkg\r\nw6p0cmUgbMOgIGF1c3NpLjxicj48YnI+PGJyPjxzcGFuPi0tLS0tLS0tIE9yaWdpbmFsIE1lc3Nh\r\nZ2UgLS0tLS0tLS08YnI+U3ViamVjdDogUmU6IFtDYWxpb3BkZXZdIFByb2NoYWluZSByw6l1bmlv\r\nbjxicj5UaW1lIChHTVQpOiBEZWMgMDIgMjAxNCAwOTozMDo0Nzxicj5Gcm9tOiBqdWxpZW4ubXVl\r\ndHRvbkBnYW5kaS5uZXQ8YnI+VG86IGNhbGlvcGRldkBjYWxpb3AubmV0PGJyPjxicj5IZWxsbzxi\r\ncj48YnI+DQpPbiAxMi8wMi8yMDE0IDA2OjEwIEFNLCBMYXVyZW50IENoZW1sYSB3cm90ZTo8YnI+\r\nDQomZ3Q7Jmd0OyBEw6lzb2zDqSBkZSBuZSBwYXMgYXZvaXIgcHUgcHLDqXZlbmlyIHBsdXMgdMO0\r\ndCBtYWlzIGNldHRlIHNlbWFpbmUgamUgbmU8YnI+DQomZ3Q7Jmd0OyBwb3VycmFpcyBwYXMgcGFz\r\nc2VyIMOgIGxhIHLDqXVuaW9uLCB1biBwZXUgZGUgZMOpYm9yZGVtZW50IGF1IGJvdWxvdC48YnI+\r\nDQomZ3Q7Jmd0Ozxicj4NCiZndDsmZ3Q7IFBhciBjb250cmUgZCdpY2kgbGEgcsOpdW5pb24gZXQg\r\nYXByw6hzIGplIHN1aXMgZGlzcG8gcG91ciB0cmF2YWlsbGVyIHN1ciBjZXM8YnI+DQomZ3Q7Jmd0\r\nOyBwb2ludHMuIEplIHNhaXMgcXVlIGonYWkgbidhaSBwYXMgw6l0w6kgdHLDqHMgYmF2YXJkIGNl\r\ncyBkZXJuaWVycyB0ZW1wcyBtYWlzPGJyPg0KJmd0OyZndDsgamUgc3VpcyBiaWVuIGzDoCA6LSk8\r\nYnI+DQomZ3Q7IEFoLCBldCBzaSBvbiBkw6ljYWxlIGQndW5lIHNlbWFpbmUgKDEyLzEyKSwgdHUg\r\ncG91cnJhcyBwYXNzZXIgPzxicj4NCkxlIDEyIGNhIHNlcmFpdCBwYXJmYWl0LCBqZSBzdWlzIMOg\r\nIFBhcmlzIMOpZ2FsZW1lbnQuPGJyPg0KJmd0Ozxicj4NCiZndDsgw4dhIHBldXQgYXJyYW5nZXIg\r\nbGVzIG1lY3MgZGUgUXdhbnQgKGZhdXQgcXVlIGplIHZvaWUgYXZlYyBldXgpLCBldDxicj4NCiZn\r\ndDsgw6dhIG1lIHBlcm1ldCBkJ2F2b2lyIHVuZSBzZW1haW5lIGRlIHLDqWN1cMOpcmF0aW9uIGF1\r\nIGNhbG1lIG5pbW9pcyw8YnI+DQomZ3Q7IGV0IMOnYSB0b21iZSBwYXMgbWFsIHB1aXNxdWUgbm9y\r\nbWFsZW1lbnQgamUgZG9pcyB2b2lyIHVuIGTDqXZlbG9wcGV1cjxicj4NCiZndDsgbG9jYWwgbGUg\r\nOS48YnI+DQomZ3Q7PGJyPg0KJmd0OyBfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f\r\nX19fX19fX19fX19fXzxicj4NCiZndDsgQ2FsaW9wZGV2IG1haWxpbmcgbGlzdDxicj4NCiZndDsg\r\nQ2FsaW9wZGV2QGNhbGlvcC5uZXQ8YnI+DQomZ3Q7IDxhIGhyZWY9Imh0dHA6Ly9icmFzc2Vucy5o\r\nZWJlcmdlLmluZm8vY2dpLWJpbi9tYWlsbWFuL2xpc3RpbmZvL2NhbGlvcGRldiIgcmVsPSJub2Zv\r\nbGxvdyIgdGFyZ2V0PSJfYmxhbmsiPmh0dHA6Ly9icmFzc2Vucy5oZWJlcmdlLmluZm8vY2dpLWJp\r\nbi9tYWlsbWFuL2xpc3RpbmZvL2NhbGlvcGRldjwvYT48YnI+PGJyPg0KSnVsaWVuPGJyPjxicj4N\r\nCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fPGJyPg0KQ2Fs\r\naW9wZGV2IG1haWxpbmcgbGlzdDxicj4NCkNhbGlvcGRldkBjYWxpb3AubmV0PGJyPg0KPGEgaHJl\r\nZj0iaHR0cDovL2JyYXNzZW5zLmhlYmVyZ2UuaW5mby9jZ2ktYmluL21haWxtYW4vbGlzdGluZm8v\r\nY2FsaW9wZGV2IiByZWw9Im5vZm9sbG93IiB0YXJnZXQ9Il9ibGFuayI+aHR0cDovL2JyYXNzZW5z\r\nLmhlYmVyZ2UuaW5mby9jZ2ktYmluL21haWxtYW4vbGlzdGluZm8vY2FsaW9wZGV2PC9hPjxicj48\r\nYnI+PC9zcGFuPg==\r\n\r\n\r\n--===============1911819744==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1911819744==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaine réunion - Kajan Sivaramalingam <kajan@kajansiva.com> - 2014-12-01 2345.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 40809120A2D;\r\n\tMon,  1 Dec 2014 23:45:25 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.365\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.365 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74, HTML_30_40=0.374, HTML_MESSAGE=0.001]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 3KdBvYbPpnIf; Mon,  1 Dec 2014 23:45:22 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id C78B3120A26;\r\n\tMon,  1 Dec 2014 23:45:22 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 90F646B49C16F;\r\n\tMon,  1 Dec 2014 23:45:22 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=gmail.com header.i=@gmail.com header.b=WFeo35Ap;\r\n\tdkim-adsp=none (insecure policy); dkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id FdHLKQABLzG6; Mon,  1 Dec 2014 23:45:21 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id B47986B476E65;\r\n\tMon,  1 Dec 2014 23:45:20 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 357CD6B49C158\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 23:45:19 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id PR4lhVzCsITB for <caliopdev@caliop.net>;\r\n Mon,  1 Dec 2014 23:45:18 +0100 (CET)\r\nReceived: from mail-lb0-f179.google.com (mail-lb0-f179.google.com\r\n [209.85.217.179])\r\n (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id C1CF16B476E65\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 23:45:17 +0100 (CET)\r\nReceived: by mail-lb0-f179.google.com with SMTP id z11so9407505lbi.10\r\n for <caliopdev@caliop.net>; Mon, 01 Dec 2014 14:45:17 -0800 (PST)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r\n h=mime-version:sender:in-reply-to:references:date:message-id:subject\r\n :from:to:content-type;\r\n bh=1KZmYTs/YXExEyLko56HdZ3s2aHcZCkH04lgouNlM5c=;\r\n b=WFeo35Ap0xml076C3B9dT9usAuedU0bHAanznzeqgCOexiuX+8Me0eYTKAfHF30Dq2\r\n tkLlnlNZFxiV9jot5B/7jRMWvk7K3b9xZ/bRGVwqR0tjkIgHYMpLi9s82grPdX9M/kih\r\n nekIcYjJgvC1Fm4AFpGKgZXAChT5OQ4JR803lkkRypThLBhX9F6fqyt7NTB3t5q1QVo1\r\n i/0EyqB3EgO1NzozwCXhhQ0P5AwiSRr1jCA6dxKQHRTLUZ3AS3i0inaggWpFVu65Cy1t\r\n 9xQz92Je9j4TBwyoFKUlulArU3WtqlvumcwWDDzOros2MlKulvmYsx8blf92jxdESb/3\r\n 4Anw==\r\nMIME-Version: 1.0\r\nX-Received: by 10.152.21.66 with SMTP id t2mr7657250lae.27.1417473917015; Mon,\r\n 01 Dec 2014 14:45:17 -0800 (PST)\r\nReceived: by 10.25.20.216 with HTTP; Mon, 1 Dec 2014 14:45:16 -0800 (PST)\r\nIn-Reply-To: <20141201204235.GC7323@brasil.brainstorm.fr>\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\n <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\n <20141201111410.GI14633@brasil.brainstorm.fr>\r\n <20141201204235.GC7323@brasil.brainstorm.fr>\r\nDate: Mon, 1 Dec 2014 23:45:16 +0100\r\nX-Google-Sender-Auth: 7dlM_HwKrpk5GfDSw_JLMkdZoQM\r\nMessage-ID: <CA+tgzejOJX4KWOM1eQdrn1XDh+Y_BSeKOAdf8dY+FWTthAL=7g@mail.gmail.com>\r\nFrom: Kajan Sivaramalingam <kajan@kajansiva.com>\r\nTo: =?UTF-8?Q?Liste_de_d=C3=A9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nSubject: Re: [Caliopdev] =?utf-8?q?Prochaine_r=C3=A9union?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0572573564==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--===============0572573564==\r\nContent-Type: multipart/alternative; boundary=089e0141a05e4d5c7d05092f5d7a\r\n\r\n--089e0141a05e4d5c7d05092f5d7a\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nLe 1 d=C3=A9cembre 2014 21:42, Laurent Chemla <laurent@brainstorm.fr> a =C3=\r\n=A9crit :\r\n\r\n>\r\n> Puisque les mecs de Qwant me disent que j'aurais d=C3=BB les relancer\r\n> plus t=C3=B4t (ils sont pas s=C3=BBrs de pouvoir venir du coup), je pr=C3=\r\n=A9f=C3=A8re\r\n> assurer de ce c=C3=B4t=C3=A9-ci de l'email et vous rappeler la prochaine\r\n> r=C3=A9union, vendredi prochain (5/12) =C3=A0 10h30 chez Gandi.\r\n>\r\n> Les th=C3=A8mes seront:\r\n>\r\n> - choix final (?) du framework,\r\n> - d=C3=A9finition des sp=C3=A9cifs de la future alpha,\r\n> - 1er jet de sc=C3=A9narii de r=C3=A9compenses pour la judification,\r\n> - pr=C3=A9sentation du screencast (sonoris=C3=A9 ?).\r\n>\r\n> On va publier une note de blog incessament: on va communiquer sur\r\n> =C3=A7a, le screencast et le nouveau mockup d=C3=A8s que ce sera possible=\r\n.\r\n>\r\n> Aussi, si Benjamin veut se joindre =C3=A0 nous, il envisage d'intervenir\r\n> dans le cadre du 31c3 =C3=A0 Hamburg fin d=C3=A9cembre pour pr=C3=A9sente=\r\nr Caliopen.\r\n>\r\n> Sauf si c'est pas possible, j'aimerais repousser la r=C3=A9union suivante\r\n> au 26 (sauter 3 semaines donc): pas pour prendre des vacances mais\r\n> pour relever un peu le pied sur les allers-retours =C3=A0 Paris qui me\r\n> fatiguent beaucoup. Ou alors d'organiser la r=C3=A9union du 19 sans moi,\r\n> pour une fois, ou en videoconf=C3=A9rence. Ca va d=C3=A9pendre de Qwant: =\r\ns'ils\r\n> disent qu'ils ne peuvent venir que le 19, j'y serai.\r\n>\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>\r\n\r\n\r\nHello,\r\n\r\nD=C3=A9sol=C3=A9 de ne pas avoir pu pr=C3=A9venir plus t=C3=B4t mais cette =\r\nsemaine je ne\r\npourrais pas passer =C3=A0 la r=C3=A9union, un peu de d=C3=A9bordement au b=\r\noulot.\r\n\r\nPar contre d'ici la r=C3=A9union et apr=C3=A8s je suis dispo pour travaille=\r\nr sur ces\r\npoints. Je sais que j'ai n'ai pas =C3=A9t=C3=A9 tr=C3=A8s bavard ces dernie=\r\nrs temps mais\r\nje suis bien l=C3=A0 :-)\r\n\r\nA bient=C3=B4t,\r\n\r\n--089e0141a05e4d5c7d05092f5d7a\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\"><div class=3D\"gmail_quote\">Le 1=\r\n d=C3=A9cembre 2014 21:42, Laurent Chemla <span dir=3D\"ltr\">&lt;<a href=3D\"=\r\nmailto:laurent@brainstorm.fr\" target=3D\"_blank\">laurent@brainstorm.fr</a>&g=\r\nt;</span> a =C3=A9crit :<br><blockquote class=3D\"gmail_quote\" style=3D\"marg=\r\nin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex\"><br>\r\nPuisque les mecs de Qwant me disent que j&#39;aurais d=C3=BB les relancer<b=\r\nr>\r\nplus t=C3=B4t (ils sont pas s=C3=BBrs de pouvoir venir du coup), je pr=C3=\r\n=A9f=C3=A8re<br>\r\nassurer de ce c=C3=B4t=C3=A9-ci de l&#39;email et vous rappeler la prochain=\r\ne<br>\r\nr=C3=A9union, vendredi prochain (5/12) =C3=A0 10h30 chez Gandi.<br>\r\n<br>\r\nLes th=C3=A8mes seront:<br>\r\n<br>\r\n- choix final (?) du framework,<br>\r\n- d=C3=A9finition des sp=C3=A9cifs de la future alpha,<br>\r\n- 1er jet de sc=C3=A9narii de r=C3=A9compenses pour la judification,<br>\r\n- pr=C3=A9sentation du screencast (sonoris=C3=A9 ?).<br>\r\n<br>\r\nOn va publier une note de blog incessament: on va communiquer sur<br>\r\n=C3=A7a, le screencast et le nouveau mockup d=C3=A8s que ce sera possible.<=\r\nbr>\r\n<br>\r\nAussi, si Benjamin veut se joindre =C3=A0 nous, il envisage d&#39;interveni=\r\nr<br>\r\ndans le cadre du 31c3 =C3=A0 Hamburg fin d=C3=A9cembre pour pr=C3=A9senter =\r\nCaliopen.<br>\r\n<br>\r\nSauf si c&#39;est pas possible, j&#39;aimerais repousser la r=C3=A9union su=\r\nivante<br>\r\nau 26 (sauter 3 semaines donc): pas pour prendre des vacances mais<br>\r\npour relever un peu le pied sur les allers-retours =C3=A0 Paris qui me<br>\r\nfatiguent beaucoup. Ou alors d&#39;organiser la r=C3=A9union du 19 sans moi=\r\n,<br>\r\npour une fois, ou en videoconf=C3=A9rence. Ca va d=C3=A9pendre de Qwant: s&=\r\n#39;ils<br>\r\ndisent qu&#39;ils ne peuvent venir que le 19, j&#39;y serai.<br>\r\n<br>\r\n_______________________________________________<br>\r\nCaliopdev mailing list<br>\r\n<a href=3D\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a><br>\r\n<a href=3D\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\"=\r\n target=3D\"_blank\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/ca=\r\nliopdev</a><br>\r\n</blockquote></div><br></div><div class=3D\"gmail_extra\"><br></div><div clas=\r\ns=3D\"gmail_extra\">Hello,</div><div class=3D\"gmail_extra\"><br></div><div cla=\r\nss=3D\"gmail_extra\">D=C3=A9sol=C3=A9 de ne pas avoir pu pr=C3=A9venir plus t=\r\n=C3=B4t mais cette semaine je ne pourrais pas passer =C3=A0 la r=C3=A9union=\r\n, un peu de d=C3=A9bordement au boulot.</div><div class=3D\"gmail_extra\"><br=\r\n></div><div class=3D\"gmail_extra\">Par contre d&#39;ici la r=C3=A9union et a=\r\npr=C3=A8s je suis dispo pour travailler sur ces points. Je sais que j&#39;a=\r\ni n&#39;ai pas =C3=A9t=C3=A9 tr=C3=A8s bavard ces derniers temps mais je su=\r\nis bien l=C3=A0 :-)</div><div class=3D\"gmail_extra\"><br></div><div class=3D=\r\n\"gmail_extra\">A bient=C3=B4t,</div><div class=3D\"gmail_extra\"><br></div><di=\r\nv class=3D\"gmail_extra\"><br></div></div>\r\n\r\n--089e0141a05e4d5c7d05092f5d7a--\r\n\r\n--===============0572573564==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0572573564==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaine réunion - Kajan Sivaramalingam <kajan@kajansiva.com> - 2014-12-02 2319.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id C46AF120A2F;\r\n\tTue,  2 Dec 2014 23:19:10 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.312\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.312 tagged_above=-999 required=5\r\n\ttests=[BAYES_40=-0.185, HTML_40_50=0.496, HTML_MESSAGE=0.001]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id wBRvT2JwK9TG; Tue,  2 Dec 2014 23:19:09 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 9A3C2120A2D;\r\n\tTue,  2 Dec 2014 23:19:09 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 365DB6B647141;\r\n\tTue,  2 Dec 2014 23:19:09 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=gmail.com header.i=@gmail.com header.b=NqOQQH15;\r\n\tdkim-adsp=none (insecure policy); dkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id khiB2XUwPnOE; Tue,  2 Dec 2014 23:19:08 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 5D1DF684CE9AD;\r\n\tTue,  2 Dec 2014 23:19:07 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id C43526B6455C0\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 23:19:05 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id uQw1sYuZBnV0 for <caliopdev@caliop.net>;\r\n Tue,  2 Dec 2014 23:19:05 +0100 (CET)\r\nReceived: from mail-lb0-f174.google.com (mail-lb0-f174.google.com\r\n [209.85.217.174])\r\n (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id B4621684CE9AD\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 23:19:04 +0100 (CET)\r\nReceived: by mail-lb0-f174.google.com with SMTP id w7so11503664lbi.33\r\n for <caliopdev@caliop.net>; Tue, 02 Dec 2014 14:19:04 -0800 (PST)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r\n h=mime-version:sender:in-reply-to:references:date:message-id:subject\r\n :from:to:content-type;\r\n bh=1d+gD/SejuDUPHhjVchWf2Yi9YHnrk6MPDeeJdqd9Jk=;\r\n b=NqOQQH15qgjgGXpEpq1zfXgjw7oNYfcei/oiItV+JuTt5brvaLSt/vO1gI93P4F6/x\r\n ot34BU1W9+OBYdt4lidhnIJjMR/3J7Pq22l8XXz49kWrzg/hmqlMvanoqGvtbGCdLcfW\r\n bIqoawx4dTX5znOxiOel9hBuN1BgT8WMrwhmHw7oz64OiGNBsWSwmjxSimqvmzOutU+F\r\n 4YczdY5QcGqZulW2J6WdZ4tk4vWoHG/3ltR+PiDlsiYtSQxOABAQnnFnjnieHxmqook8\r\n 2Px40qCFqKnEcIMySGHUUY7p62cFt0o3dnLHrF86qe8KDMXk40eh1YtqwKR2avcZXyRA\r\n SM+A==\r\nMIME-Version: 1.0\r\nX-Received: by 10.112.27.133 with SMTP id t5mr1343860lbg.45.1417558743888;\r\n Tue, 02 Dec 2014 14:19:03 -0800 (PST)\r\nReceived: by 10.25.20.216 with HTTP; Tue, 2 Dec 2014 14:19:03 -0800 (PST)\r\nIn-Reply-To: <20141202051032.GD7323@brasil.brainstorm.fr>\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\n <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\n <20141201111410.GI14633@brasil.brainstorm.fr>\r\n <20141201204235.GC7323@brasil.brainstorm.fr>\r\n <CA+tgzejOJX4KWOM1eQdrn1XDh+Y_BSeKOAdf8dY+FWTthAL=7g@mail.gmail.com>\r\n <20141202051032.GD7323@brasil.brainstorm.fr>\r\nDate: Tue, 2 Dec 2014 23:19:03 +0100\r\nX-Google-Sender-Auth: 4BXofTWq8Kf7L5kK8gwrhDcv1gc\r\nMessage-ID: <CA+tgzejvbOuWa6R_XS0zb5nVgwy8mPQhc5nHc93tiKq2vxmyHg@mail.gmail.com>\r\nFrom: Kajan Sivaramalingam <kajan@kajansiva.com>\r\nTo: =?UTF-8?Q?Liste_de_d=C3=A9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nSubject: Re: [Caliopdev] =?utf-8?q?Prochaine_r=C3=A9union?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0485280005==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--===============0485280005==\r\nContent-Type: multipart/alternative; boundary=001a1133b04a60b7320509431d45\r\n\r\n--001a1133b04a60b7320509431d45\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n>\r\n> Ah, et si on d=C3=A9cale d'une semaine (12/12), tu pourras passer ?\r\n>\r\n> =C3=87a peut arranger les mecs de Qwant (faut que je voie avec eux), et\r\n> =C3=A7a me permet d'avoir une semaine de r=C3=A9cup=C3=A9ration au calme =\r\nnimois,\r\n> et =C3=A7a tombe pas mal puisque normalement je dois voir un d=C3=A9velop=\r\npeur\r\n> local le 9.\r\n\r\n\r\n\r\nLe 12 et le 26 d=C3=A9cembre c'est bon de mon c=C3=B4t=C3=A9.\r\nD'ici l=C3=A0 je suivrais les avancements =C3=A0 distance.\r\n\r\n--001a1133b04a60b7320509431d45\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\"><div class=3D\"gmail_quote\"><blo=\r\nckquote class=3D\"gmail_quote\" style=3D\"margin:0px 0px 0px 0.8ex;border-left=\r\n-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;paddi=\r\nng-left:1ex\">Ah, et si on d=C3=A9cale d&#39;une semaine (12/12), tu pourras=\r\n passer ?<br>\r\n<br>\r\n=C3=87a peut arranger les mecs de Qwant (faut que je voie avec eux), et<br>\r\n=C3=A7a me permet d&#39;avoir une semaine de r=C3=A9cup=C3=A9ration au calm=\r\ne nimois,<br>\r\net =C3=A7a tombe pas mal puisque normalement je dois voir un d=C3=A9veloppe=\r\nur<br>\r\nlocal le 9.</blockquote><div><br></div><div><br></div><div>Le 12 et le 26 d=\r\n=C3=A9cembre c&#39;est bon de mon c=C3=B4t=C3=A9.</div><div>D&#39;ici l=C3=\r\n=A0 je suivrais les avancements =C3=A0 distance.</div><div>=C2=A0</div></di=\r\nv></div></div>\r\n\r\n--001a1133b04a60b7320509431d45--\r\n\r\n--===============0485280005==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0485280005==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaine réunion - Kajan Sivaramalingam <kajan@kajansiva.com> - 2014-12-02 2322.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id E67CA120A2F;\r\n\tTue,  2 Dec 2014 23:22:31 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.498\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.498 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001, HTML_40_50=0.496, HTML_MESSAGE=0.001]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id HvRNWLoozfHA; Tue,  2 Dec 2014 23:22:29 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id D9F1E120A2D;\r\n\tTue,  2 Dec 2014 23:22:29 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 879916B647148;\r\n\tTue,  2 Dec 2014 23:22:29 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=gmail.com header.i=@gmail.com header.b=rCf620dT;\r\n\tdkim-adsp=none (insecure policy); dkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id YNJ5b-EH6lL8; Tue,  2 Dec 2014 23:22:28 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id CF5BF6B647143;\r\n\tTue,  2 Dec 2014 23:22:27 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 952E16B647146\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 23:22:26 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id PV7FKdII0VeA for <caliopdev@caliop.net>;\r\n Tue,  2 Dec 2014 23:22:25 +0100 (CET)\r\nReceived: from mail-la0-f45.google.com (mail-la0-f45.google.com\r\n [209.85.215.45])\r\n (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 275AB6B647143\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 23:22:25 +0100 (CET)\r\nReceived: by mail-la0-f45.google.com with SMTP id gq15so11569224lab.32\r\n for <caliopdev@caliop.net>; Tue, 02 Dec 2014 14:22:24 -0800 (PST)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r\n h=mime-version:sender:in-reply-to:references:date:message-id:subject\r\n :from:to:content-type;\r\n bh=Z4DOlniyxCuI7YFB/WXaptW7JpKgsdrI76bPwP+bwoE=;\r\n b=rCf620dTgOrMHIUELx/F3weF0WHWH9McXUyz9W3r6rH+YHdypxdqNHAJYh/3oWKwMX\r\n Kc3iTo77lHtM151ohaWGqNfsOVu1xOAOMpzDQ4O3L1RNXQ8VbTNM3Xjm8fRO/poUF0b+\r\n n0E8yD/9V3VT6xqPVeUc52WtxgE2adUdBpSzXOI/QqgPLuTiQgAZL18iAwr3/lAyFo+g\r\n wFBKFlM63uc9XFDzGvrdUKok+QuDWkeEuG+5CaSOLUfW7PnfnvdUpg0kJYM7Tr+aqe+M\r\n lpJxptMZlvx26oJ2YtMjp8EQ83+v461NOvLEhpGvl9eHAoJV/8Hj7j9wd0WUM2ITLAYX\r\n P8Kw==\r\nMIME-Version: 1.0\r\nX-Received: by 10.152.29.41 with SMTP id g9mr1476580lah.32.1417558944339; Tue,\r\n 02 Dec 2014 14:22:24 -0800 (PST)\r\nReceived: by 10.25.20.216 with HTTP; Tue, 2 Dec 2014 14:22:24 -0800 (PST)\r\nIn-Reply-To: <547DC86D.6010806@chamal.fr>\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\n <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\n <20141201111410.GI14633@brasil.brainstorm.fr>\r\n <20141201204235.GC7323@brasil.brainstorm.fr>\r\n <547DC86D.6010806@chamal.fr>\r\nDate: Tue, 2 Dec 2014 23:22:24 +0100\r\nX-Google-Sender-Auth: -jmCzCEtbXXk8t0vEAKwH57f3TQ\r\nMessage-ID: <CA+tgzehro25o_3QqyKNrzjhUKRBfGjpbegGEE9ED7ynEiN-exA@mail.gmail.com>\r\nFrom: Kajan Sivaramalingam <kajan@kajansiva.com>\r\nTo: =?UTF-8?Q?Liste_de_d=C3=A9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nSubject: Re: [Caliopdev] =?utf-8?q?Prochaine_r=C3=A9union?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1524340671==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--===============1524340671==\r\nContent-Type: multipart/alternative; boundary=089e0158c09a535c820509432947\r\n\r\n--089e0158c09a535c820509432947\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n>\r\n>\r\n>>  On a un gagnant qui se demarque en regardant les quelques\r\n> reponses recues et meme en excluant ma matrice (j'ai une\r\n> legitimite toute relative a choisir une librairie / framework JS AMA).\r\n>\r\n> Le consensus semble donc se faire autour d'Ember qui est\r\n> globalement mieux note.\r\n>\r\n> ++ chamal\r\n\r\n\r\nCool, je vais donc commencer =C3=A0 regarder plus s=C3=A9rieusement Ember d=\r\nans ce cas.\r\n\r\nOn a une id=C3=A9e des personnes qui seront amen=C3=A9s =C3=A0 participer a=\r\nu d=C3=A9veloppement\r\nfront ? Pour voir comment s'organiser, avec qui poser les bases, etc...\r\n\r\n--089e0158c09a535c820509432947\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\"><div class=3D\"gmail_quote\"><blo=\r\nckquote class=3D\"gmail_quote\" style=3D\"margin:0 0 0 .8ex;border-left:1px #c=\r\ncc solid;padding-left:1ex\"><span class=3D\"\"><blockquote class=3D\"gmail_quot=\r\ne\" style=3D\"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex\">=\r\n<br>\r\n</blockquote></span>\r\nOn a un gagnant qui se demarque en regardant les quelques<br>\r\nreponses recues et meme en excluant ma matrice (j&#39;ai une<br>\r\nlegitimite toute relative a choisir une librairie / framework JS AMA).<br>\r\n<br>\r\nLe consensus semble donc se faire autour d&#39;Ember qui est<br>\r\nglobalement mieux note.<br>\r\n<br>\r\n++ chamal</blockquote><div><br></div><div>Cool, je vais donc commencer =C3=\r\n=A0 regarder plus s=C3=A9rieusement Ember dans ce cas.</div><div><br></div>=\r\n<div>On a une id=C3=A9e des personnes qui seront amen=C3=A9s =C3=A0 partici=\r\nper au d=C3=A9veloppement front ? Pour voir comment s&#39;organiser, avec q=\r\nui poser les bases, etc...=C2=A0</div></div><br></div></div>\r\n\r\n--089e0158c09a535c820509432947--\r\n\r\n--===============1524340671==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1524340671==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaine réunion - Laurent Chemla <laurent@brainstorm.fr> - 2014-12-02 0610.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id A810B120A2C;\r\n\tTue,  2 Dec 2014 06:10:43 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -2.599\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-2.599 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id z1EOze57DoGC; Tue,  2 Dec 2014 06:10:41 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 39FC2120A29;\r\n\tTue,  2 Dec 2014 06:10:41 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id E652E68279FE2;\r\n\tTue,  2 Dec 2014 06:10:40 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id Srq2jGDJpLWW; Tue,  2 Dec 2014 06:10:37 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 826EF68279FDC;\r\n\tTue,  2 Dec 2014 06:10:36 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 18A9F68279FE0\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 06:10:35 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id Iv3Z8CD7YliM for <caliopdev@caliop.net>;\r\n Tue,  2 Dec 2014 06:10:34 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 3E96868279FDC\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 06:10:34 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 08A18209A3B84; Tue,  2 Dec 2014 06:10:33 +0100 (CET)\r\nDate: Tue, 2 Dec 2014 06:10:33 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141202051032.GD7323@brasil.brainstorm.fr>\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\n <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\n <20141201111410.GI14633@brasil.brainstorm.fr>\r\n <20141201204235.GC7323@brasil.brainstorm.fr>\r\n <CA+tgzejOJX4KWOM1eQdrn1XDh+Y_BSeKOAdf8dY+FWTthAL=7g@mail.gmail.com>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nIn-Reply-To: <CA+tgzejOJX4KWOM1eQdrn1XDh+Y_BSeKOAdf8dY+FWTthAL=7g@mail.gmail.com>\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: Re: [Caliopdev] =?iso-8859-1?q?Prochaine_r=E9union?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n> D=E9sol=E9 de ne pas avoir pu pr=E9venir plus t=F4t mais cette semaine je=\r\n ne\r\n> pourrais pas passer =E0 la r=E9union, un peu de d=E9bordement au boulot.\r\n> =\r\n\r\n> Par contre d'ici la r=E9union et apr=E8s je suis dispo pour travailler su=\r\nr ces\r\n> points. Je sais que j'ai n'ai pas =E9t=E9 tr=E8s bavard ces derniers temp=\r\ns mais\r\n> je suis bien l=E0 :-)\r\n\r\nAh, et si on d=E9cale d'une semaine (12/12), tu pourras passer ?\r\n\r\n=C7a peut arranger les mecs de Qwant (faut que je voie avec eux), et\r\n=E7a me permet d'avoir une semaine de r=E9cup=E9ration au calme nimois,\r\net =E7a tombe pas mal puisque normalement je dois voir un d=E9veloppeur\r\nlocal le 9.\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaine réunion - Laurent Chemla <laurent@brainstorm.fr> - 2014-12-02 1158.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id C1046120AAA;\r\n\tTue,  2 Dec 2014 11:58:20 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -2.599\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-2.599 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id fYVRKyXFO225; Tue,  2 Dec 2014 11:58:18 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 655BC120A73;\r\n\tTue,  2 Dec 2014 11:58:18 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 138676B5C903F;\r\n\tTue,  2 Dec 2014 11:58:18 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id 7vRgPyxvJYhC; Tue,  2 Dec 2014 11:58:14 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id B54486B5C9041;\r\n\tTue,  2 Dec 2014 11:58:13 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id B8F9D6B5C9040\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 11:58:12 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id Twl_BUWM5GX1 for <caliopdev@caliop.net>;\r\n Tue,  2 Dec 2014 11:58:12 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id D98966B5C903F\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 11:58:11 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id B7181209B0B3D; Tue,  2 Dec 2014 11:58:11 +0100 (CET)\r\nDate: Tue, 2 Dec 2014 11:58:11 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: Liste de =?iso-8859-1?Q?d=E9veloppement?= Caliopen <caliopdev@caliop.net>\r\nMessage-ID: <20141202105810.GG7323@brasil.brainstorm.fr>\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\n <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\n <20141201111410.GI14633@brasil.brainstorm.fr>\r\n <20141201204235.GC7323@brasil.brainstorm.fr>\r\n <CA+tgzejOJX4KWOM1eQdrn1XDh+Y_BSeKOAdf8dY+FWTthAL=7g@mail.gmail.com>\r\n <20141202051032.GD7323@brasil.brainstorm.fr>\r\n <547D86F1.9020801@gandi.net>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nIn-Reply-To: <547D86F1.9020801@gandi.net>\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: Re: [Caliopdev] =?iso-8859-1?q?Prochaine_r=E9union?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n> >Ah, et si on d=E9cale d'une semaine (12/12), tu pourras passer ?\r\n> Le 12 ca serait parfait, je suis =E0 Paris =E9galement.\r\n\r\nBon, c=F4t=E9 Qwant le 12 est impossible, par contre Eric L=E9andri sera\r\npr=E9sent. Donc on maintient le 5 pour une r=E9union avec Qwant, et on\r\nr=E9it=E8re le 12 avant une coupure jusqu'=E0 fin d=E9cembre/d=E9but janvie=\r\nr.\r\n\r\nLe 5 on aura Julien en visioconf' malgr=E9 tout.\r\n\r\nC'est pas plus mal vous pourrez juger du mec mieux que ce que j'ai\r\npu faire en 20mn chez moi.\r\n\r\nL.\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaine réunion - julien muetton <julien.muetton@gandi.net> - 2014-12-02 1031.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 2F1FE120A40;\r\n\tTue,  2 Dec 2014 10:30:43 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -100.908\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-100.908 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, RATWARE_GECKO_BUILD=1.691,\r\n\tUSER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id LL1hUkwikbmz; Tue,  2 Dec 2014 10:30:40 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id C49A5120A3F;\r\n\tTue,  2 Dec 2014 10:30:40 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 453446B5E8299;\r\n\tTue,  2 Dec 2014 10:30:40 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id I24P66VFqwT8; Tue,  2 Dec 2014 10:30:39 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 3EDEF6B5E42AD;\r\n\tTue,  2 Dec 2014 10:30:38 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id D9BE56B5E828B\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 10:30:36 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id lXoSj5PaAHmi for <caliopdev@caliop.net>;\r\n Tue,  2 Dec 2014 10:30:33 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id 2E3CA6B5E42AD\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 10:30:33 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id CEA02120A40\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 10:30:32 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id EQmpYzIKZjyV for <caliopdev@caliop.net>;\r\n Tue,  2 Dec 2014 10:30:30 +0100 (CET)\r\nReceived: from [10.242.2.14] (corp-ras.gandi.net [217.70.181.25])\r\n (using TLSv1 with cipher DHE-RSA-AES128-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by mail4.gandi.net (Postfix) with ESMTPSA id E225F120A5A\r\n for <caliopdev@caliop.net>; Tue,  2 Dec 2014 10:30:29 +0100 (CET)\r\nMessage-ID: <547D86F1.9020801@gandi.net>\r\nDate: Tue, 02 Dec 2014 10:31:29 +0100\r\nFrom: julien muetton <dev@caliopen.local>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Icedove/31.2.0\r\nMIME-Version: 1.0\r\nTo: =?windows-1252?Q?Liste_de_d=E9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\n <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\n <20141201111410.GI14633@brasil.brainstorm.fr>\r\n <20141201204235.GC7323@brasil.brainstorm.fr>\r\n <CA+tgzejOJX4KWOM1eQdrn1XDh+Y_BSeKOAdf8dY+FWTthAL=7g@mail.gmail.com>\r\n <20141202051032.GD7323@brasil.brainstorm.fr>\r\nIn-Reply-To: <20141202051032.GD7323@brasil.brainstorm.fr>\r\nSubject: Re: [Caliopdev] =?windows-1252?q?Prochaine_r=E9union?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=\"iso-8859-1\"; Format=\"flowed\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nHello\r\n\r\nOn 12/02/2014 06:10 AM, Laurent Chemla wrote:\r\n>> D=E9sol=E9 de ne pas avoir pu pr=E9venir plus t=F4t mais cette semaine j=\r\ne ne\r\n>> pourrais pas passer =E0 la r=E9union, un peu de d=E9bordement au boulot.\r\n>>\r\n>> Par contre d'ici la r=E9union et apr=E8s je suis dispo pour travailler s=\r\nur ces\r\n>> points. Je sais que j'ai n'ai pas =E9t=E9 tr=E8s bavard ces derniers tem=\r\nps mais\r\n>> je suis bien l=E0 :-)\r\n> Ah, et si on d=E9cale d'une semaine (12/12), tu pourras passer ?\r\nLe 12 ca serait parfait, je suis =E0 Paris =E9galement.\r\n>\r\n> =C7a peut arranger les mecs de Qwant (faut que je voie avec eux), et\r\n> =E7a me permet d'avoir une semaine de r=E9cup=E9ration au calme nimois,\r\n> et =E7a tombe pas mal puisque normalement je dois voir un d=E9veloppeur\r\n> local le 9.\r\n>\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\nJulien\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaines réunions. - Alban Crommer <alban@albancrommer.com> - 2014-12-29 1917.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nDelivered-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby gandi.net (Postfix) with ESMTP id 24E35E39F1;\r\n\tMon, 29 Dec 2014 19:17:35 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 1.078\r\nX-Spam-Level: *\r\nX-Spam-Status: No, score=1.078 tagged_above=-999 required=5\r\n\ttests=[BAYES_05=-1.11, HTML_40_50=0.496, HTML_MESSAGE=0.001,\r\n\tRATWARE_GECKO_BUILD=1.691]\r\nReceived: from gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id IvpRwUi1wXeX; Mon, 29 Dec 2014 19:17:34 +0100 (CET)\r\nReceived: from smtp-out1.octopuce.fr (bck3.octopuce.fr [91.194.60.153])\r\n\tby gandi.net (Postfix) with ESMTP id EC1E0E39CC;\r\n\tMon, 29 Dec 2014 19:17:33 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [IPv6:2001:67c:288::2])\r\n\tby smtp-out1.octopuce.fr (Postfix) with ESMTPS id B4B3B1032731E;\r\n\tMon, 29 Dec 2014 19:17:33 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 9501B69F4AD61;\r\n\tMon, 29 Dec 2014 19:17:33 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=albancrommer.com header.i=@albancrommer.com\r\n\theader.b=aLYnU5O3; dkim-adsp=none (insecure policy);\r\n\tdkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id QSPwFNimpy_2; Mon, 29 Dec 2014 19:17:32 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id BE6EB683DA6B9;\r\n\tMon, 29 Dec 2014 19:17:31 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 1B9DE683DA6BE\r\n for <caliopdev@caliop.net>; Mon, 29 Dec 2014 19:17:30 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id OdxwQ1qfrCCF for <caliopdev@caliop.net>;\r\n Mon, 29 Dec 2014 19:17:26 +0100 (CET)\r\nReceived: from alban.octopuce.fr (alban.octopuce.fr [IPv6:2001:67c:288::111])\r\n by brassens.heberge.info (Postfix) with ESMTP id 0BB29683DA6B9\r\n for <caliopdev@caliop.net>; Mon, 29 Dec 2014 19:17:26 +0100 (CET)\r\nReceived: from [IPv6:2001:67c:288:1::201] (unknown [IPv6:2001:67c:288:1::201])\r\n (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128\r\n bits)) (No client certificate requested)\r\n by alban.octopuce.fr (Postfix) with ESMTPSA id D42F7F1D0231\r\n for <caliopdev@caliop.net>; Mon, 29 Dec 2014 19:17:25 +0100 (CET)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=albancrommer.com;\r\n s=alternc; t=1419877045;\r\n bh=kno0NEOY2nm6/6C6QgGVZl82Zq49LixBqaEX/Mx7XEM=;\r\n h=Date:From:To:Subject:References:In-Reply-To;\r\n b=aLYnU5O3w+9F7IL596UrfyVApfib5AYreB/NrNnCh8va6GpKeHxVzHqdkORMlfP/K\r\n UIbb0MAoHDSBtVyY/3eTLenhloRJxT/whz24Oz86O+9MM2j6gRd0MbGS7pBt7BOQ4B\r\n Je5p22OIRSvrsNTpy3rKq2pfj61AmTTMGTDgDgGI=\r\nMessage-ID: <54A19AB5.50304@albancrommer.com>\r\nDate: Mon, 29 Dec 2014 19:17:25 +0100\r\nFrom: Alban Crommer <alban@albancrommer.com>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Icedove/31.3.0\r\nMIME-Version: 1.0\r\nTo: =?windows-1252?Q?Liste_de_d=E9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nReferences: <20141229170756.GG25421@brasil.brainstorm.fr>\r\nIn-Reply-To: <20141229170756.GG25421@brasil.brainstorm.fr>\r\nSubject: Re: [Caliopdev] =?windows-1252?q?Prochaines_r=E9unions=2E?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0088918656==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--===============0088918656==\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------010608030500020905010605\"\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------010608030500020905010605\r\nContent-Type: text/plain; charset=windows-1252\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nBonjour,\r\n\r\nAlors je serai int=E9ress=E9 par les r=E9unions sur le /frontend/ et la\r\n/gamifi//cation/.\r\n\r\nJe pense qu'on pourrait aussi parler de la \"pluginification\" ce qui\r\nimplique front & backend, donc faire tenir 2 r=E9unions en une matin=E9e =\r\n?\r\n\r\nD'autant que j'aime -comme Benjamin- le principe Octave Klaba [1]: plus\r\non est nombreux moins =E7a doit durer\r\n\r\nDonc, par exemple,\r\n\r\n    10h00 - 11h00 : r=E9union frontend (5 personnes)\r\n    11h15 - 11h45 : r=E9union transversale (10 personnes)\r\n\r\nSi les leads ont bien pr=E9par=E9 l'ordre du jour =E7a devrait suffire ;)=\r\n\r\n\r\nSur les dates, on avisera en fonction des r=E9unions mais sur le principe=\r\n\r\ngarder le vendredi matin me semble pas mal.\r\n\r\n+a\r\n\r\n[1] https://twitter.com/olesovhcom/status/428659701447163905\r\n/Now je limite les r=E9unions =E0 \"5 homme-heures\": une r=E9union avec 3\r\npersonnes dur=E9e max 1H30, =E0 6 max 45min, =E0 10 max 30min et =E0 2 ma=\r\nx 2h30./\r\n\r\n\r\n\r\nLe 29/12/2014 18:07, Laurent Chemla a =E9crit :\r\n> Salut,\r\n>\r\n> Les r=E9unions \"th=E9matiques\" vont =EAtre plus difficiles =E0 organise=\r\nr, je\r\n> propose qu'on conserve le principe des r=E9unions quinzomadaires, mais\r\n> qu'en fonction des besoins, celles-ci soient limit=E9es aux sujets qui\r\n> ont besoin d'=EAtre affin=E9s.\r\n>\r\n> Par exemple, si on pr=E9voit 2 dates en janvier (le 9 et le 23 ?), il\r\n> faut que les personnes int=E9ress=E9es par une discussion sur le back l=\r\ne\r\n> signalent ici, celles qui veulent parler de l'UI aussi, idem pour la\r\n> communication.\r\n>\r\n> En fonction des pr=E9sents sur chaque sujet, on d=E9cidera du ou des\r\n> th=E8mes de la ou des r=E9unions, ainsi que de leur horaire pour ceux\r\n> qui voudraient participer =E0 plusieurs.\r\n>\r\n> =C7a vous semble faisable, les gens ?\r\n>\r\n> L.\r\n> _______________________________________________\r\n> Caliopdev mailing list\r\n> Caliopdev@caliop.net\r\n> http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n>\r\n\r\n\r\n--------------010608030500020905010605\r\nContent-Type: text/html; charset=windows-1252\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n    <meta content=\"text/html; charset=windows-1252\"\r\n      http-equiv=\"Content-Type\">\r\n  </head>\r\n  <body text=\"#000000\" bgcolor=\"#FFFFFF\">\r\n    <div class=\"moz-cite-prefix\">Bonjour,<br>\r\n      <br>\r\n      Alors je serai int�ress� par les r�unions sur le <i>frontend</i>\r\n      et la <i>gamifi</i><i>cation</i>.<br>\r\n      <br>\r\n      Je pense qu'on pourrait aussi parler de la \"pluginification\" ce\r\n      qui implique front &amp; backend, donc faire tenir 2 r�unions en\r\n      une matin�e ?<br>\r\n      <br>\r\n      D'autant que j'aime -comme Benjamin- le principe Octave Klaba [1]:\r\n      plus on est nombreux moins �a doit durer<br>\r\n      <br>\r\n      Donc, par exemple, <br>\r\n      <blockquote>10h00 - 11h00 : r�union frontend (5 personnes)<br>\r\n        11h15 - 11h45 : r�union transversale (10 personnes) <br>\r\n      </blockquote>\r\n      Si les leads ont bien pr�par� l'ordre du jour �a devrait suffire\r\n      ;)<br>\r\n      <br>\r\n      Sur les dates, on avisera en fonction des r�unions mais sur le\r\n      principe garder le vendredi matin me semble pas mal. <br>\r\n      <br>\r\n      +a<br>\r\n      <br>\r\n      [1] <a class=\"moz-txt-link-freetext\" href=\"https://twitter.com/olesovhcom/status/428659701447163905\">https://twitter.com/olesovhcom/status/428659701447163905</a><br>\r\n      <i>Now je limite les r�unions � \"5 homme-heures\": une r�union avec\r\n        3 personnes dur�e max 1H30, � 6 max 45min, � 10 max 30min et � 2\r\n        max 2h30.</i><span style=\"color: rgb(41, 47, 51); font-family:\r\n        'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:\r\n        28.1818180084229px; font-style: normal; font-variant: normal;\r\n        font-weight: 300; letter-spacing: 0.280000001192093px;\r\n        line-height: 34px; orphans: auto; text-align: start;\r\n        text-indent: 0px; text-transform: none; white-space: pre-wrap;\r\n        widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;\r\n        display: inline !important; float: none; background-color:\r\n        rgb(255, 255, 255);\"></span><br>\r\n      <br>\r\n      <br>\r\n      <br>\r\n      Le 29/12/2014 18:07, Laurent Chemla a �crit�:<br>\r\n    </div>\r\n    <blockquote cite=\"mid:20141229170756.GG25421@brasil.brainstorm.fr\"\r\n      type=\"cite\">\r\n      <pre wrap=\"\">\r\nSalut,\r\n\r\nLes r�unions \"th�matiques\" vont �tre plus difficiles � organiser, je\r\npropose qu'on conserve le principe des r�unions quinzomadaires, mais\r\nqu'en fonction des besoins, celles-ci soient limit�es aux sujets qui\r\nont besoin d'�tre affin�s.\r\n\r\nPar exemple, si on pr�voit 2 dates en janvier (le 9 et le 23 ?), il\r\nfaut que les personnes int�ress�es par une discussion sur le back le\r\nsignalent ici, celles qui veulent parler de l'UI aussi, idem pour la\r\ncommunication.\r\n\r\nEn fonction des pr�sents sur chaque sujet, on d�cidera du ou des\r\nth�mes de la ou des r�unions, ainsi que de leur horaire pour ceux\r\nqui voudraient participer � plusieurs.\r\n\r\n�a vous semble faisable, les gens ?\r\n\r\nL.\r\n_______________________________________________\r\nCaliopdev mailing list\r\n<a class=\"moz-txt-link-abbreviated\" href=\"mailto:Caliopdev@caliop.net\">Caliopdev@caliop.net</a>\r\n<a class=\"moz-txt-link-freetext\" href=\"http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\">http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev</a>\r\n\r\n</pre>\r\n    </blockquote>\r\n    <br>\r\n  </body>\r\n</html>\r\n\r\n--------------010608030500020905010605--\r\n\r\n--===============0088918656==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0088918656==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] Prochaines étapes ? - Laurent Chemla <laurent@brainstorm.fr> - 2014-12-06 2135.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 521B2120AD3;\r\n\tSat,  6 Dec 2014 21:35:57 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.74\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.74 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 94Nk0gk5QLpz; Sat,  6 Dec 2014 21:35:56 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 3618B120A50;\r\n\tSat,  6 Dec 2014 21:35:56 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 320A66826688C;\r\n\tSat,  6 Dec 2014 21:35:55 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id AE3296826688C\r\n for <caliopdev@caliop.net>; Sat,  6 Dec 2014 21:35:53 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 81A6720BE02F5; Sat,  6 Dec 2014 21:35:53 +0100 (CET)\r\nDate: Sat, 6 Dec 2014 21:35:53 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: Liste de =?iso-8859-1?Q?d=E9veloppement?= Caliopen <caliopdev@caliop.net>\r\nMessage-ID: <20141206203553.GD11160@brasil.brainstorm.fr>\r\nReferences: <CA+tgzeg9KmeU2B_=609YsLtS05xj-pCRZSL4R2A-QTR0ThuEeA@mail.gmail.com>\r\n <CA+tgzeir52HN-cGebJitqXEo2YysQZAu37aTvix2wgE83FgAJQ@mail.gmail.com>\r\n <CA+tgzehWJrANZdeGvfn+fFSDe8Dm9X9GMeDjr9LesmfZ4SgjZA@mail.gmail.com>\r\n <CA+tgzehWtaqTp6pNVxFvNRXR5620RuY530EWxbrWNBKeB2eS0g@mail.gmail.com>\r\n <CA+tgzej7rZ-EBSYXC81Os0emcdCoVSVfMEAZvY_1tShwshzb=w@mail.gmail.com>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nIn-Reply-To: <CA+tgzej7rZ-EBSYXC81Os0emcdCoVSVfMEAZvY_1tShwshzb=w@mail.gmail.com>\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: Re: [Caliopdev] =?iso-8859-1?q?Prochaines_=E9tapes_=3F?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n> Comment s'est d=E9roul=E9 la r=E9union d'hier ?\r\n\r\nAnnul=E9e pour cause de gr=E8ve TGV.\r\n\r\n> Quelles sont les prochaines =E9tapes ?\r\n\r\nIl faut absolument construire une base de travail pour l'UI, avec\r\nEmber donc. A priori vous serez l=E0 tous les deux Julien et toi, le\r\n12, mais si on pouvait avancer d'ici l=E0 ce serait super.\r\n\r\nJ'ai commenc=E9 =E0 r=E9diger les id=E9es concernant la gamification, sur\r\nhttps://pad.caliopen.org/p/Ludification , et Chamal essaye de voir\r\ncomment pr=E9senter au mieux un fil de conversation quand les messages\r\nviennent de plusieurs sources (le threading est quasi impossible, il\r\nfaut des id=E9es neuves).\r\n\r\nPour rappel, dans l'alpha on ne vise qu'=E0 g=E9rer l'email mais m=EAme\r\ndans ce cas, si on a plusieurs sources le threading sera compliqu=E9.\r\n\r\nLes trucs qu'on doit absolument pouvoir montrer, =E0 ce stade dans\r\nl'id=E9al c'est:\r\n\r\n- La cr=E9ation de compte et l'ajout d'un compte email externe,\r\n- Des niveaux de confidentialit=E9 (m=EAme mal calcul=E9s, ou fixes)\r\n  associ=E9s =E0 chaque =E9l=E9ment, et un syst=E8me de bulles d'aide et\r\n  d'incitation \"dynamiques\" (m=EAme si =E7a ne pointe sur rien),\r\n- L'affichage des conversations et la saisie de messages sortants.\r\n\r\nIl faut vraiment commencer =E0 bosser sur l'UI, c'est hyper important\r\npour conserver un momentum qu'on a d=E9j=E0 eu pas mal de difficult=E9 =E0\r\nobtenir. La recherche de financement passe aussi par =E7a.\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] [svetlana.meyer@ensc.fr: Bravo!] - Alexis <alexis@gandi.net> - 2014-11-26 1446.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 88EC8120A22;\r\n\tWed, 26 Nov 2014 14:46:17 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -98.494\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-98.494 tagged_above=-999 required=5\r\n\ttests=[BAYES_40=-0.185, RATWARE_GECKO_BUILD=1.691,\r\n\tUSER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id NUMUYZvTYTNs; Wed, 26 Nov 2014 14:46:15 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 40B70120AC2;\r\n\tWed, 26 Nov 2014 14:46:15 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id EE000682E9C05;\r\n\tWed, 26 Nov 2014 14:46:14 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id qzX8V4vGvjEX; Wed, 26 Nov 2014 14:46:13 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 9A9EE682E9B19;\r\n\tWed, 26 Nov 2014 14:46:13 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 32760682E9B19\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 14:46:12 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id Gx_4nL2S2cac for <caliopdev@caliop.net>;\r\n Wed, 26 Nov 2014 14:46:08 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id 811A8682E961A\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 14:46:08 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id 5BE94120ACA\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 14:46:08 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id EElpX78lMU01 for <caliopdev@caliop.net>;\r\n Wed, 26 Nov 2014 14:46:06 +0100 (CET)\r\nReceived: from [217.70.181.12] (crock.gandi.net [217.70.181.12])\r\n by mail4.gandi.net (Postfix) with ESMTP id A5B5F120AC2\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 14:46:06 +0100 (CET)\r\nMessage-ID: <5475D99E.4090201@gandi.net>\r\nDate: Wed, 26 Nov 2014 14:46:06 +0100\r\nFrom: Alexis <alexis@gandi.net>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: caliopdev@caliop.net\r\nReferences: <20141126114418.GU8770@brasil.brainstorm.fr>\r\nIn-Reply-To: <20141126114418.GU8770@brasil.brainstorm.fr>\r\nSubject: Re: [Caliopdev] [svetlana.meyer@ensc.fr: Bravo!]\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain; charset=\"iso-8859-1\"; Format=\"flowed\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nEncourageant en tout cas ce genre de commentaire.\r\n\r\n\r\nLe 26/11/2014 12:44, Laurent Chemla a =E9crit :\r\n> Pfff, faudrait r=E9pondre =E0 tous ces gens, j'ai pas le temps cette sema=\r\nine je\r\n> cours partout.\r\n>\r\n>\r\n> ----- Forwarded message from Svetlana Meyer <svetlana.meyer@ensc.fr> -----\r\n> Subject: Bravo!\r\n> From: Svetlana Meyer <svetlana.meyer@ensc.fr>\r\n> To: contact@caliopen.org\r\n>\r\n> Bonjour,\r\n> Je viens de d=E9couvrir votre projet et je tenais =E0 vous dire qu'il est\r\n> g=E9nial. D=E9j=E0, sur le principe : un syst=E8me de messagerie intellig=\r\nent ET\r\n> prot=E9g=E9, c'est vraiment ce dont on a besoin en ce moment.\r\n> Et surtout sur l'interface que vous avez cr=E9=E9e : il est rare qu'on at=\r\nteigne\r\n> un tel niveau de plaisir d'utilisation sur un simple prototype (quelle\r\n> frustration de ne pouvoir tester toutes les fonctionnalit=E9s. Pour venir\r\n> d'une =E9cole d'ing=E9 qui attache une grande importance =E0 la conception\r\n> centr=E9e utilisateur (l'ENSC de Bordeaux), je dois dire qu'en terme de\r\n> conception, vous avez assur=E9.\r\n> Sinc=E8rement bravo ! D=E8s qu'il sort, je m'y met.\r\n> Je suis malheureusement trop mauvaise en code pour vous aider =E0 ce nive=\r\nau,\r\n> mais si vous avez besoin de diffuser votre prototype pour avoir des retou=\r\nrs\r\n> sur son usabilit=E9 ou autre, je le ferais avec plaisir.\r\n>\r\n> Tr=E8s bonne continuation !\r\n>\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] framework js - David Epely <dey@protonmail.ch> - 2014-11-21 0920.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 059BF120ADB;\r\n\tFri, 21 Nov 2014 09:20:24 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.777\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.777 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001, FORGED_RCVD_HELO=0.135, FUZZY_AMBIEN=0.416,\r\n\tHTML_MESSAGE=0.001, MIME_BASE64_NO_NAME=0.224]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 5RYHpHyNGft9; Fri, 21 Nov 2014 09:20:22 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id A83BF120A3A;\r\n\tFri, 21 Nov 2014 09:20:22 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id AED3868317826;\r\n\tFri, 21 Nov 2014 09:20:14 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=protonmail.ch header.i=@protonmail.ch header.b=teefjIwn;\r\n\tdkim-adsp=discard (insecure policy); dkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id SopLyUgFMVui; Fri, 21 Nov 2014 09:20:12 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id D8E0868317822;\r\n\tFri, 21 Nov 2014 09:20:08 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id A501968317824\r\n for <caliopdev@caliop.net>; Fri, 21 Nov 2014 09:20:07 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id lGO7vXzEq1FD for <caliopdev@caliop.net>;\r\n Fri, 21 Nov 2014 09:20:06 +0100 (CET)\r\nReceived: from mail.protonmail.ch (protonmail.ch [37.35.106.36])\r\n (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 3CD3A68317822\r\n for <caliopdev@caliop.net>; Fri, 21 Nov 2014 09:20:06 +0100 (CET)\r\nReceived: by mail.protonmail.ch (Postfix, from userid 48)\r\n id 76B6914252D01; Fri, 21 Nov 2014 08:20:05 +0000 (UTC)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=protonmail.ch;\r\n s=default; t=1416558005;\r\n bh=mSCCWWu/QIFeD5XVuYzUiAjxgxm/BibjCKn2IXz8K9I=;\r\n h=To:Subject:Date:From:Reply-To;\r\n b=teefjIwnVJk9CAdMoVG0rHjXfCwPFZxXYwYtoSD1i0R8hHJg/BMtfndFi4443MLvq\r\n qCsPCYYn3rJ+cw2qmxejMgVMMyOte4qw0P4raqgWB+5nUMjfl4HYjemvW+ypgKKnuh\r\n Owj+0B/IQG0mHqLyUmWZrWp5iPt8KIvXRafwwylI=\r\nTo: \"kajan@kajansiva.com\" <kajan@kajansiva.com>,\r\n \"caliopdev@caliop.net\" <caliopdev@caliop.net>\r\nDate: Fri, 21 Nov 2014 03:20:05 -0500\r\nFrom: David Epely <dey@protonmail.ch>\r\nMessage-ID: <d1a6fe609f06473eb6cf0d9e7be9731a@protonmail.ch>\r\nX-Priority: 3\r\nMIME-Version: 1.0\r\nSubject: Re: [Caliopdev] framework js\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: David Epely <dey@protonmail.ch>,\r\n =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1608807487==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--===============1608807487==\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n\r\nw4BtaGEgYydlc3QgYXUgImxlYWQgZGV2IiBkZSBjb25maXJtZXIgbGUgY2hvaXggZGUgbGEgdGVj\r\naG5vIHF1aSBjb252aWVudCBsZSBtaWV1eCBhdSBwcm9qZXQgZXQgaidhaSBsJ2ltcHJlc3Npb24g\r\ncXVlIGxlIGNob2l4IGVzdCBkw6lqw6AgZmFpdCwgaW51dGlsZSBkZSB0b3VybmVyIGF1dG91ciBk\r\ndSBwb3QuPGJyPjxicj5wb3VyIGFuZ3VsYXIgMiBqZSBuZSBzYWlzIHBhcyDDoCBxdWVsIHN0YWRl\r\nIGlscyBlbiBzb250IG1haXMgb24gcGV1dCBzdXBwb3NlciBxdWUgc2Egc29ydGllIHBvdXJyYWl0\r\nIMOqdHJlIHByb2NoZSBkdSBtb21lbnQgb8O5IGNhbGlvcGVuIGRldmllbnQgZm9uY3Rpb25uZWwg\r\nKG91IGF1IG1vaW5zIHVuIHByb3RvIG1pbmltdW0gdmlhYmxlKSBldCBjb21tZSB1bmUgdmVyc2lv\r\nbiAyIGVzdCBnw6luw6lyYWxlbWVudCB1bmUgcmVmb250ZSBjb21wbMOodGUgZW4gcHJlbmFudCBs\r\nZSBtZWlsbGV1cnMsIHBvdXIgZW5zdWl0ZSBwcm9wb3NlciBkZXMgdmVyc2lvbnMgbWFqZXVycyBh\r\ndmVjIG1pZ3JhdGlvbiDDoCBmYWlibGUgY2/Du3QgKGMnZXN0IHR5cGlxdWVtZW50IGNlIHF1ZSBs\r\nJ29uIHZvaXQgYXZlYyBkZXMgcHJvamV0cyBjb21tZSBzeW1mb255IDEgPSZndDsgU3ltZm9ueSAy\r\nIC0mZ3Q7IFN5bWZvbnkgMyAuLi4gZXQgc8O7cmVtZW50IGQnYXV0cmVzKSA8YnI+Vm9pbMOgIHBv\r\ndXJxdW9pIGxhIHF1ZXN0aW9uIHNlIHBvc2UuPGJyPjxicj5FbiB0b3V0IGNhcyBjZSBxdWkgZXN0\r\nIHPDu3IgY2UgcXVlIHBvdXIgYW5ndWxhciAxIGlsIGZhdWRyYSBpbXBvc2VyIHVuZSBjb252ZW50\r\naW9uIGRlIGNvZGUgY29tcGzDqHRlIGplIHZvaXMgdHJvcCBkZSBtYW5pw6hyZSBkZSBmYWlyZSBl\r\ndCBqYW1haXMgdnJhaW1lbnQgdW5lIGJvbm5lLiBTdXIgbW9uIHByb2pldCBwcm8gb24gYSDDoCBw\r\nZXUgcHLDqHMgcmllbiBzdWl2aSBhdSBkw6lidXQgZXQgbWFpbnRlbmFudCBxdWUgYydlc3QgZ3Jv\r\ncywgb24geSB2b2l0IHBsZWluIGRlIGxhY3VuZXMgZXQgcGFzIG1hbCBkZSBkZXR0ZSB0ZWNobmlx\r\ndWUuPGJyPkzDoCBvbiBlc3NhaSBkZSByZWZhY3RvcmVyIGF1IGZ1ciBldCDDoCBtZXN1cmUgZW4g\r\nc3VpdmFudCDDp2EgOiA8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vam9obnBhcGEvYW5ndWxh\r\ncmpzLXN0eWxlZ3VpZGUiIHJlbD0ibm9mb2xsb3ciIHRhcmdldD0iX2JsYW5rIj5odHRwczovL2dp\r\ndGh1Yi5jb20vam9obnBhcGEvYW5ndWxhcmpzLXN0eWxlZ3VpZGU8L2E+PGJyPjxicj48c3Bhbj4t\r\nLS0tLS0tLSBPcmlnaW5hbCBNZXNzYWdlIC0tLS0tLS0tPGJyPlN1YmplY3Q6IFJlOiBbQ2FsaW9w\r\nZGV2XSBmcmFtZXdvcmsganM8YnI+VGltZSAoR01UKTogTm92IDIwIDIwMTQgMjM6NTk6MTg8YnI+\r\nRnJvbToga2FqYW5Aa2FqYW5zaXZhLmNvbTxicj5UbzogY2FsaW9wZGV2QGNhbGlvcC5uZXQ8YnI+\r\nPGJyPjxkaXY+PGRpdj48ZGl2PkJvbmpvdXIsPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj48YnI+\r\nPC9kaXY+PGRpdj48c3Bhbj4NCiAgDQogICAgDQogIA0KICA8ZGl2Pg0KICAgIDxkaXY+Qm9uam91\r\nciw8YnI+PGJyPg0KICAgICAgU3VpdGUgw6AgZGlzY3Vzc2lvbiBJUkMgdy8gQ2hhbWFsLCB2b3Vz\r\nIHBvdXZleiBjb25zdWx0ZXIgLw0KICAgICAgcGFydGljaXBlciDDoDxicj48YnI+PGI+Jm5ic3A7\r\nJm5ic3A7Jm5ic3A7Jm5ic3A7IDxhIGhyZWY9Imh0dHBzOi8vcGFkLmNhbGlvcGVuLm9yZy9wL1Rl\r\nY2hub0Zyb250IiByZWw9Im5vZm9sbG93IiB0YXJnZXQ9Il9ibGFuayI+aHR0cHM6Ly9wYWQuY2Fs\r\naW9wZW4ub3JnL3AvVGVjaG5vRnJvbnQ8L2E+IDwvYj48Yj48YnI+PC9iPjxicj4NCiAgICAgIFBv\r\ndXIgcGFydGljaXBlciwgaWwgZmF1ZHJhaXQgZGlzY3V0ZXIgZXQgYWZmaW5lciBsZXMgY3JpdMOo\r\ncmVzLA0KICAgICAgcHVpcyBwcm9jw6lkZXIgw6AgbGEgbm90YXRpb24gKGonYWkgbWlzIHVuIGxp\r\nZW4gdmVycyB1biBldGhlcnNoZWV0DQogICAgICBwb3VyIGV4ZW1wbGUgLyB0ZXN0KTxicj48YnI+\r\nPGJyPjxiPkwnaWTDqWUgZCd1bmUgZ3JpbGxlIGRlIG5vdGF0aW9uPC9iPjxicj48YnI+DQogICAg\r\nICBGYWlyZSB1bmUgZ3JpbGxlIGQnw6l2YWx1YXRpb24gZGVzIGRpZmbDqXJlbnRzIGZyYW1ld29y\r\na3MgZW4gc2UNCiAgICAgIGJhc2FudCBzdXIgbGVzIGNyaXTDqHJlcyBkJ2V4aWdlbmNlIGTDqWZp\r\nbmlzIHBvdXIgQ2FsaW9wZW4uIDxicj48YnI+DQogICAgICBBdSBmaW5hbCwgdW5lIG5vdGUgcGFy\r\nIGZyYW1ld29yayByYXRpb25hbGlzZSBsZSBjaG9peC4gPGJyPjxicj4NCiAgICAgIFNpbm9uID8g\r\nT24gcmVzdGUgZGFucyBsZSBmbG91IGRlcyBpbXByZXNzaW9ucyBpbmRpdmlkdWVsbGVzLjxicj48\r\nYnI+PC9kaXY+PC9kaXY+PC9zcGFuPjxkaXY+PGJyPjwvZGl2PjxkaXY+T2sgcG91ciBsYSBncmls\r\nbGUgZGUgbm90YXRpb24uIFBhciBjb250cmUgcXVlbHF1ZXMgcmVtYXJxdWVzIDo8L2Rpdj48ZGl2\r\nPjxicj48L2Rpdj48ZGl2Pi0gU2FpbHNKUyBlc3QgdW4gZnJhbWV3b3JrIGJhY2stZW5kIGJhc8Op\r\nIHN1ciBOb2RlSlMsIGRvbmMgb24gcGV1dCBkw6lqw6AgbCdlbmxldmVyIGRlIGxhIGxpc3RlPC9k\r\naXY+PGRpdj4tIEFuZ3VsYXIgdjIgZXN0IHNldWxlbWVudCBhdSBzdGFkZSBwcm9qZXQuIElsIG4n\r\neSBhIG3Dqm1lIHBhcyBkZSB2ZXJzaW9uIGFscGhhIGFjdHVlbGxlbWVudCwgZG9uYyDDoCBzb3J0\r\naXIgw6lnYWxlbWVudCBkZSBsYSBsaXN0ZTwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+RW5zdWl0\r\nZSwgcGFyIHJhcHBvcnQgw6AgY2V0dGUgZ3JpbGxlIGRlIG5vdGF0aW9uLCBxdWVsIGVzdCBsZSAi\r\ncGxhbm5pbmciID88L2Rpdj48ZGl2Pi0gQ29tYmllbiBkZSBwZXJzb25uZXMgaW1wbGlxdcOpZXMg\r\nZGFucyBsZSBwcm9qZXQgc29udCBjZW5zw6lzIHLDqXBvbmRyZSDDoCBjZXR0ZSBncmlsbGUgZGUg\r\nbm90YXRpb24gPzwvZGl2PjxkaXY+LSBRdWVsbGUgc2VyYSBsYSBkYXRlIGRlIGNsw7R0dXJlIGRl\r\ncyByw6ljb2x0ZXMgZCdhdmlzID88L2Rpdj48ZGl2PiZuYnNwOzwvZGl2PjxkaXY+PGRpdj4NCiAg\r\nICAgIDxicj48Yj5Qb3J0w6llIGR1IGNvZGUgSlMgZGFucyBjYWxpb3BlbjwvYj48YnI+PGJyPg0K\r\nICAgICAgUGFybGFudCBkZSBmbG91LCBqJ2FpbWVyYWkgYXZvaXIgcGx1cyBkZSBkw6l0YWlscyA6\r\nPGJyPjxicj4NCiAgICAgIFNpIG9uIHZldXQgZGVzIGludGVyZmFjZXMgcsOpYWN0aXZlcyBjb21t\r\nZSBsZSBwZXJtZXR0ZW50IGxlcw0KICAgICAgZnJhbWV3b3JrcyBKUyBtb2Rlcm5lcywgaWwgZmF1\r\ndCB1biBtaW5pbXVtIGRlIGJhc2UgZGUgZG9ubsOpZXMNCiAgICAgIGVtYmFycXXDqWUgZXQgZGUg\r\nZ2VzdGlvbiBkZXMgdGVtcGxhdGVzIEhUTUwgY8O0dMOpIGNsaWVudCAoZHUNCiAgICAgIENvbnRy\r\nb2xsZXIgTW9kZWwgVmlldyAsIGVuIHNvbW1lKSBhdmVjIGR1IHdlYnNvY2tldCwgbGUgcGx1cw0K\r\nICAgICAgc291dmVudCBnw6lyw6kgcGFyIHVuZSBhcHBsaSBub2RlLjxicj48YnI+DQogICAgICBP\r\nciBsZXMgY29udHLDtGxldXJzIGV0IGxlIHJlbmR1IEhUTUwgc29udCBhIHByaW9yaSBmYWl0IGPD\r\ntHTDqSBQeXRob24NCiAgICAgIHF1aSBlbnZvaWUgZHUgSFRNTCA/PGJyPjxiPjxicj48L2I+RW4g\r\ndGVybWUgZGUgcmVuZHUgSFRNTCBldCBkZSBiYXNlIGRlIGRvbm7DqWVzIG/DuSBzZSBzaXR1ZW50\r\nIGxlcw0KICAgICAgZnJvbnRpw6hyZXMgZW50cmUgZnJvbnRlbmQgLyBiYWNrZW5kJm5ic3A7ID8g\r\nPGJyPjxicj4NCiAgICAgIEVuIHRlcm1lIGRlIGRlIG3DqXRob2RlcyBkJ2Vudm9pIC8gcsOpY2Vw\r\ndGlvbiBkZXMgZG9uLCB5IGEgdCdpbCB1bg0KICAgICAgc2Now6ltYSBkw6lmaW5pIGV0IHByw6lj\r\naXMgPyA8YnI+PGJyPg0KICAgICAgU2kgb24gdmV1dCB1bmUgaW50ZXJmYWNlIGZyb250ZW5kIGF1\r\nIG5pdmVhdSBwb3VyIGxlcyA1IGFucyDDoA0KICAgICAgdmVuaXIsIGlsIG1lIHNlbWJsZSBpbXBv\r\ncnRhbnQgZCfDqnRyZSBjbGFpciBsw6AgZGVzc3VzLjxicj48YnI+PGJyPg0KICAgICAgOjphPGJy\r\nPjwvZGl2PjwvZGl2PjwvZGl2Pjxicj48L2Rpdj48ZGl2PkwnaWTDqWUgc2VyYSwgcXVlbCBxdWUg\r\nc29pdCBsZSBmcmFtZXdvcmsgZnJvbnQtZW5kIGNob2lzaSwgYXVyYSBsYSBkaXNwb3NpdGlvbiBz\r\ndWl2YW50ZSA6PC9kaXY+PGRpdj4tIHVuIGJhY2stZW5kIHF1aSBmb3Vybml0IHVuZSBBUEk8L2Rp\r\ndj48ZGl2Pi0gdW4gZnJvbnQtZW5kIHF1aSBzZXJhIGNvbXBvc8OpIGRlIGZpY2hpZXJzICJzdGF0\r\naXF1ZXMiIEhUTUwgLyBKYXZhc2NyaXB0PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5MZSBiYWNr\r\nLWVuZCBnw6lyZXJhIHRvdXRlIGxhIGxvZ2lxdWUgbG91cmRlLCBjb21tZSBuJ2ltcG9ydGUgcXVl\r\nbCBzZXJ2aWNlIHdlYi48L2Rpdj48ZGl2PkV0IGxlIGZyb250IHNlcmEgYmFzw6kgc3VyIGxlIHBy\r\naW5jaXBlIGRlICJzaW5nbGUgcGFnZSBhcHBsaWNhdGlvbiIgOiBvbiBhY2PDqWRlcmEgYXUgc2l0\r\nZSB2aWEgdW4gdW5pcXVlIGZpY2hpZXIgaHRtbCwgcXVpIGNoYXJnZXJhIGVuc3VpdGUgZW4gZm9u\r\nY3Rpb24gZGUgc2VzIGJlc29pbnMgZGVzIHZ1ZXMgcGFydGllbGxlcyBIVE1MLCBlZmZlY3R1ZXJh\r\nIGxlcyBhcHBlbHMgdmVycyBsJ0FQSSBwb3VyIHJlbXBsaXIgc2VzIHZ1ZXMgZXRjLi4uPC9kaXY+\r\nPGRpdj48YnI+PC9kaXY+PGRpdj5FbnN1aXRlLCBjZSBzY2jDqW1hIHBvdXJyYSDDqnRyZSBjb21w\r\nbGV0IHBhciBsJ3V0aWxpc2F0aW9uIGRlIHdlYnNvY2tldCBzdXIgY2VydGFpbmVzIHBhcnRpZXMg\r\ncXVpIG9udCBiZXNvaW4gZCdhdm9pciBkZXMgbW9kaWZpY2F0aW9ucyAidGVtcHMgcsOpZWxzIiBl\r\ndGMuLi48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PlZvdXMgcG91cnJleiB0cm91dmVyIHVuZSBw\r\ncsOpc2VudGF0aW9uIGRlIGNlIGNvbmNlcHQgaWNpIDombmJzcDs8YSBocmVmPSJodHRwOi8vc3Rh\r\nY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzI2MTk2MDQyL3NpbmdsZS1wYWdlLWFwcGxpY2F0aW9u\r\nLWZyb250ZW5kLWluZGVwZW5kZW50LW9mLWJhY2tlbmQiIHJlbD0ibm9mb2xsb3ciIHRhcmdldD0i\r\nX2JsYW5rIj5odHRwOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzI2MTk2MDQyL3Npbmds\r\nZS1wYWdlLWFwcGxpY2F0aW9uLWZyb250ZW5kLWluZGVwZW5kZW50LW9mLWJhY2tlbmQ8L2E+PC9k\r\naXY+PGRpdj48YnI+PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5BIHZvdHJlIGRpc3BvLCBwb3Vy\r\nIHRvdXRlcyBkZW1hbmRlcyBkJ2luZm9zLjwvZGl2PjwvZGl2Pg0KPC9zcGFuPjxicj48YnI+\r\n\r\n\r\n--===============1608807487==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1608807487==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/Re: [Caliopdev] framework js - Kajan Sivaramalingam <kajan@kajansiva.com> - 2014-11-21 0059.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id F0EB6120AE8;\r\n\tFri, 21 Nov 2014 00:59:14 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.728\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.728 tagged_above=-999 required=5\r\n\ttests=[BAYES_40=-0.185, FUZZY_AMBIEN=0.416, HTML_40_50=0.496,\r\n\tHTML_MESSAGE=0.001]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id fvUzI3fvaxyk; Fri, 21 Nov 2014 00:59:12 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id B3FDC120AE0;\r\n\tFri, 21 Nov 2014 00:59:12 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 82D1069754149;\r\n\tFri, 21 Nov 2014 00:59:11 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=gmail.com header.i=@gmail.com header.b=PM0PVcNl;\r\n\tdkim-adsp=none (insecure policy); dkim-atps=neutral\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id CXpFds41f_q0; Fri, 21 Nov 2014 00:59:10 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 28DA669754142;\r\n\tFri, 21 Nov 2014 00:59:09 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id DD03769754146\r\n for <caliopdev@caliop.net>; Fri, 21 Nov 2014 00:59:07 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id QhKWrSudH_cR for <caliopdev@caliop.net>;\r\n Fri, 21 Nov 2014 00:59:06 +0100 (CET)\r\nReceived: from mail-lb0-f181.google.com (mail-lb0-f181.google.com\r\n [209.85.217.181])\r\n (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 0DCDF69754142\r\n for <caliopdev@caliop.net>; Fri, 21 Nov 2014 00:59:05 +0100 (CET)\r\nReceived: by mail-lb0-f181.google.com with SMTP id l4so3252609lbv.12\r\n for <caliopdev@caliop.net>; Thu, 20 Nov 2014 15:59:05 -0800 (PST)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r\n h=mime-version:sender:in-reply-to:references:date:message-id:subject\r\n :from:to:content-type;\r\n bh=FLdFRoYuYOJLZU0a5aUW3DN8Bz20Qe3SvPu7wgRRFqM=;\r\n b=PM0PVcNlZgJ1mh6eb1muEcO3iKyW5V4N58AzdT5CXzshsgaON8FkeA7J8wdhkYLWrf\r\n 3nubEHHDPJNHK8qhb12o3uu5UvgkotOj/9+4l4FnIINhLCP5kiNvpucAaig4vvlUBrj/\r\n 1DpYDnRjFvHVbo1UKxpp+gm8Kq6wuoeBX5NEv6HVWq0eu3CPHJiZ14J/K60cfE3YxY2W\r\n NuiUipwzHtf2q0GAoJ2CRVZw7i62SadJHJb6LYvgyU/hgq+edKzbik8SUUorRCcv8UYd\r\n K7vMD7QgprY5MrW9P96O78vYR0KyudAt1hCdVMMsgLKX664wuDMRzEVs4RetgjRtmP5o\r\n sAWA==\r\nMIME-Version: 1.0\r\nX-Received: by 10.152.2.41 with SMTP id 9mr926179lar.47.1416527945081; Thu, 20\r\n Nov 2014 15:59:05 -0800 (PST)\r\nReceived: by 10.25.20.216 with HTTP; Thu, 20 Nov 2014 15:59:05 -0800 (PST)\r\nIn-Reply-To: <546C6DFD.7000903@albancrommer.com>\r\nReferences: <5465E127.1090903@chamal.fr> <54664520.2060005@gandi.net>\r\n <CA+tgzeiz84LuSB8CKcc_SOY==1NhODh_z_5Lrzn2wBuhsSGaRw@mail.gmail.com>\r\n <546C6DFD.7000903@albancrommer.com>\r\nDate: Fri, 21 Nov 2014 00:59:05 +0100\r\nX-Google-Sender-Auth: erUR1-fD_sHGoGQEPDn1g0uEECY\r\nMessage-ID: <CA+tgzejQAMG+8aULmaP_NU4Z25GBs8kRmw3zHC66kU0fsHeEdw@mail.gmail.com>\r\nFrom: Kajan Sivaramalingam <kajan@kajansiva.com>\r\nTo: =?UTF-8?Q?Liste_de_d=C3=A9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nSubject: Re: [Caliopdev] framework js\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1102190903==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--===============1102190903==\r\nContent-Type: multipart/alternative; boundary=089e013c6258fb2b1d0508531c90\r\n\r\n--089e013c6258fb2b1d0508531c90\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nBonjour,\r\n\r\n\r\n Bonjour,\r\n>\r\n> Suite =C3=A0 discussion IRC w/ Chamal, vous pouvez consulter / participer=\r\n =C3=A0\r\n>\r\n> *     https://pad.caliopen.org/p/TechnoFront\r\n> <https://pad.caliopen.org/p/TechnoFront> *\r\n>\r\n> Pour participer, il faudrait discuter et affiner les crit=C3=A8res, puis\r\n> proc=C3=A9der =C3=A0 la notation (j'ai mis un lien vers un ethersheet pou=\r\nr exemple /\r\n> test)\r\n>\r\n>\r\n> *L'id=C3=A9e d'une grille de notation*\r\n>\r\n> Faire une grille d'=C3=A9valuation des diff=C3=A9rents frameworks en se b=\r\nasant sur\r\n> les crit=C3=A8res d'exigence d=C3=A9finis pour Caliopen.\r\n>\r\n> Au final, une note par framework rationalise le choix.\r\n>\r\n> Sinon ? On reste dans le flou des impressions individuelles.\r\n>\r\n>\r\nOk pour la grille de notation. Par contre quelques remarques :\r\n\r\n- SailsJS est un framework back-end bas=C3=A9 sur NodeJS, donc on peut d=C3=\r\n=A9j=C3=A0\r\nl'enlever de la liste\r\n- Angular v2 est seulement au stade projet. Il n'y a m=C3=AAme pas de versi=\r\non\r\nalpha actuellement, donc =C3=A0 sortir =C3=A9galement de la liste\r\n\r\nEnsuite, par rapport =C3=A0 cette grille de notation, quel est le \"planning=\r\n\" ?\r\n- Combien de personnes impliqu=C3=A9es dans le projet sont cens=C3=A9s r=C3=\r\n=A9pondre =C3=A0\r\ncette grille de notation ?\r\n- Quelle sera la date de cl=C3=B4ture des r=C3=A9coltes d'avis ?\r\n\r\n\r\n>\r\n> *Port=C3=A9e du code JS dans caliopen*\r\n>\r\n> Parlant de flou, j'aimerai avoir plus de d=C3=A9tails :\r\n>\r\n> Si on veut des interfaces r=C3=A9actives comme le permettent les framewor=\r\nks JS\r\n> modernes, il faut un minimum de base de donn=C3=A9es embarqu=C3=A9e et de=\r\n gestion des\r\n> templates HTML c=C3=B4t=C3=A9 client (du Controller Model View , en somme=\r\n) avec du\r\n> websocket, le plus souvent g=C3=A9r=C3=A9 par une appli node.\r\n>\r\n> Or les contr=C3=B4leurs et le rendu HTML sont a priori fait c=C3=B4t=C3=\r\n=A9 Python qui\r\n> envoie du HTML ?\r\n>\r\n> En terme de rendu HTML et de base de donn=C3=A9es o=C3=B9 se situent les =\r\nfronti=C3=A8res\r\n> entre frontend / backend  ?\r\n>\r\n> En terme de de m=C3=A9thodes d'envoi / r=C3=A9ception des don, y a t'il u=\r\nn sch=C3=A9ma\r\n> d=C3=A9fini et pr=C3=A9cis ?\r\n>\r\n> Si on veut une interface frontend au niveau pour les 5 ans =C3=A0 venir, =\r\nil me\r\n> semble important d'=C3=AAtre clair l=C3=A0 dessus.\r\n>\r\n>\r\n> ::a\r\n>\r\n\r\nL'id=C3=A9e sera, quel que soit le framework front-end choisi, aura la\r\ndisposition suivante :\r\n- un back-end qui fournit une API\r\n- un front-end qui sera compos=C3=A9 de fichiers \"statiques\" HTML / Javascr=\r\nipt\r\n\r\nLe back-end g=C3=A9rera toute la logique lourde, comme n'importe quel servi=\r\nce\r\nweb.\r\nEt le front sera bas=C3=A9 sur le principe de \"single page application\" : o=\r\nn\r\nacc=C3=A9dera au site via un unique fichier html, qui chargera ensuite en\r\nfonction de ses besoins des vues partielles HTML, effectuera les appels\r\nvers l'API pour remplir ses vues etc...\r\n\r\nEnsuite, ce sch=C3=A9ma pourra =C3=AAtre complet par l'utilisation de webso=\r\ncket sur\r\ncertaines parties qui ont besoin d'avoir des modifications \"temps r=C3=A9el=\r\ns\"\r\netc...\r\n\r\nVous pourrez trouver une pr=C3=A9sentation de ce concept ici :\r\nhttp://stackoverflow.com/questions/26196042/single-page-application-fronten=\r\nd-independent-of-backend\r\n\r\n\r\nA votre dispo, pour toutes demandes d'infos.\r\n\r\n--089e013c6258fb2b1d0508531c90\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<div dir=3D\"ltr\"><div class=3D\"gmail_extra\"><div class=3D\"gmail_quote\">Bonj=\r\nour,</div><div class=3D\"gmail_quote\"><br></div><div class=3D\"gmail_quote\"><=\r\nbr></div><div class=3D\"gmail_quote\"><blockquote class=3D\"gmail_quote\" style=\r\n=3D\"margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(20=\r\n4,204,204);border-left-style:solid;padding-left:1ex\">\r\n =20\r\n   =20\r\n =20\r\n  <div bgcolor=3D\"#FFFFFF\" text=3D\"#000000\">\r\n    <div>Bonjour,<br>\r\n      <br>\r\n      Suite =C3=A0 discussion IRC w/ Chamal, vous pouvez consulter /\r\n      participer =C3=A0<br>\r\n      <br>\r\n      <b>=C2=A0=C2=A0=C2=A0=C2=A0 <a href=3D\"https://pad.caliopen.org/p/Tec=\r\nhnoFront\" target=3D\"_blank\">https://pad.caliopen.org/p/TechnoFront</a> </b>=\r\n<b><br>\r\n      </b><br>\r\n      Pour participer, il faudrait discuter et affiner les crit=C3=A8res,\r\n      puis proc=C3=A9der =C3=A0 la notation (j&#39;ai mis un lien vers un e=\r\nthersheet\r\n      pour exemple / test)<br>\r\n      <br>\r\n      <br>\r\n      <b>L&#39;id=C3=A9e d&#39;une grille de notation</b><br>\r\n      <br>\r\n      Faire une grille d&#39;=C3=A9valuation des diff=C3=A9rents frameworks=\r\n en se\r\n      basant sur les crit=C3=A8res d&#39;exigence d=C3=A9finis pour Caliope=\r\nn. <br>\r\n      <br>\r\n      Au final, une note par framework rationalise le choix. <br>\r\n      <br>\r\n      Sinon ? On reste dans le flou des impressions individuelles.<br>\r\n      <br></div></div></blockquote><div><br></div><div>Ok pour la grille de=\r\n notation. Par contre quelques remarques :</div><div><br></div><div>- Sails=\r\nJS est un framework back-end bas=C3=A9 sur NodeJS, donc on peut d=C3=A9j=C3=\r\n=A0 l&#39;enlever de la liste</div><div>- Angular v2 est seulement au stade=\r\n projet. Il n&#39;y a m=C3=AAme pas de version alpha actuellement, donc =C3=\r\n=A0 sortir =C3=A9galement de la liste</div><div><br></div><div>Ensuite, par=\r\n rapport =C3=A0 cette grille de notation, quel est le &quot;planning&quot; =\r\n?</div><div>- Combien de personnes impliqu=C3=A9es dans le projet sont cens=\r\n=C3=A9s r=C3=A9pondre =C3=A0 cette grille de notation ?</div><div>- Quelle =\r\nsera la date de cl=C3=B4ture des r=C3=A9coltes d&#39;avis ?</div><div>=C2=\r\n=A0</div><blockquote class=3D\"gmail_quote\" style=3D\"margin:0px 0px 0px 0.8e=\r\nx;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-styl=\r\ne:solid;padding-left:1ex\"><div bgcolor=3D\"#FFFFFF\" text=3D\"#000000\"><div>\r\n      <br>\r\n      <b>Port=C3=A9e du code JS dans caliopen</b><br>\r\n      <br>\r\n      Parlant de flou, j&#39;aimerai avoir plus de d=C3=A9tails :<br>\r\n      <br>\r\n      Si on veut des interfaces r=C3=A9actives comme le permettent les\r\n      frameworks JS modernes, il faut un minimum de base de donn=C3=A9es\r\n      embarqu=C3=A9e et de gestion des templates HTML c=C3=B4t=C3=A9 client=\r\n (du\r\n      Controller Model View , en somme) avec du websocket, le plus\r\n      souvent g=C3=A9r=C3=A9 par une appli node.<br>\r\n      <br>\r\n      Or les contr=C3=B4leurs et le rendu HTML sont a priori fait c=C3=B4t=\r\n=C3=A9 Python\r\n      qui envoie du HTML ?<br>\r\n      <b><br>\r\n      </b>En terme de rendu HTML et de base de donn=C3=A9es o=C3=B9 se situ=\r\nent les\r\n      fronti=C3=A8res entre frontend / backend=C2=A0 ? <br>\r\n      <br>\r\n      En terme de de m=C3=A9thodes d&#39;envoi / r=C3=A9ception des don, y =\r\na t&#39;il un\r\n      sch=C3=A9ma d=C3=A9fini et pr=C3=A9cis ? <br>\r\n      <br>\r\n      Si on veut une interface frontend au niveau pour les 5 ans =C3=A0\r\n      venir, il me semble important d&#39;=C3=AAtre clair l=C3=A0 dessus.<b=\r\nr>\r\n      <br>\r\n      <br>\r\n      ::a<br></div></div></blockquote></div><br></div><div class=3D\"gmail_e=\r\nxtra\">L&#39;id=C3=A9e sera, quel que soit le framework front-end choisi, au=\r\nra la disposition suivante :</div><div class=3D\"gmail_extra\">- un back-end =\r\nqui fournit une API</div><div class=3D\"gmail_extra\">- un front-end qui sera=\r\n compos=C3=A9 de fichiers &quot;statiques&quot; HTML / Javascript</div><div=\r\n class=3D\"gmail_extra\"><br></div><div class=3D\"gmail_extra\">Le back-end g=\r\n=C3=A9rera toute la logique lourde, comme n&#39;importe quel service web.</=\r\ndiv><div class=3D\"gmail_extra\">Et le front sera bas=C3=A9 sur le principe d=\r\ne &quot;single page application&quot; : on acc=C3=A9dera au site via un uni=\r\nque fichier html, qui chargera ensuite en fonction de ses besoins des vues =\r\npartielles HTML, effectuera les appels vers l&#39;API pour remplir ses vues=\r\n etc...</div><div class=3D\"gmail_extra\"><br></div><div class=3D\"gmail_extra=\r\n\">Ensuite, ce sch=C3=A9ma pourra =C3=AAtre complet par l&#39;utilisation de=\r\n websocket sur certaines parties qui ont besoin d&#39;avoir des modificatio=\r\nns &quot;temps r=C3=A9els&quot; etc...</div><div class=3D\"gmail_extra\"><br>=\r\n</div><div class=3D\"gmail_extra\">Vous pourrez trouver une pr=C3=A9sentation=\r\n de ce concept ici :=C2=A0<a href=3D\"http://stackoverflow.com/questions/261=\r\n96042/single-page-application-frontend-independent-of-backend\">http://stack=\r\noverflow.com/questions/26196042/single-page-application-frontend-independen=\r\nt-of-backend</a></div><div class=3D\"gmail_extra\"><br></div><div class=3D\"gm=\r\nail_extra\"><br></div><div class=3D\"gmail_extra\">A votre dispo, pour toutes =\r\ndemandes d&#39;infos.</div></div>\r\n\r\n--089e013c6258fb2b1d0508531c90--\r\n\r\n--===============1102190903==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1102190903==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] CR réunion du 12_12_2014 - Thomas Laurent <thomas.laurent@gandi.net> - 2014-12-12 1701.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 48ABE120FDE;\r\n\tFri, 12 Dec 2014 17:02:03 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -100.243\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-100.243 tagged_above=-999 required=5\r\n\ttests=[BAYES_20=-0.74, HTML_40_50=0.496, HTML_MESSAGE=0.001,\r\n\tUSER_IN_WHITELIST=-100]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id GEpSSRnNgRgV; Fri, 12 Dec 2014 17:02:01 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id EB65F120FDB;\r\n\tFri, 12 Dec 2014 17:02:00 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id D6BB66B649F59;\r\n\tFri, 12 Dec 2014 17:01:59 +0100 (CET)\r\nReceived: from mail4.gandi.net (mail4.gandi.net [217.70.183.210])\r\n by brassens.heberge.info (Postfix) with ESMTP id A4E566B649F59\r\n for <caliopdev@caliop.net>; Fri, 12 Dec 2014 17:01:58 +0100 (CET)\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n by mail4.gandi.net (Postfix) with ESMTP id 86451120FDE\r\n for <caliopdev@caliop.net>; Fri, 12 Dec 2014 17:01:58 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\r\n port 10024) with ESMTP id gGMCS36COofX for <caliopdev@caliop.net>;\r\n Fri, 12 Dec 2014 17:01:56 +0100 (CET)\r\nReceived: from thomas.gandi.net (thomas.gandi.net [217.70.181.206])\r\n (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by mail4.gandi.net (Postfix) with ESMTPSA id 54DA4120FDB\r\n for <caliopdev@caliop.net>; Fri, 12 Dec 2014 17:01:56 +0100 (CET)\r\nFrom: Thomas Laurent <thomas.laurent@gandi.net>\r\nMessage-Id: <F5C23EB5-C5A9-4077-8C3E-3D3D70DD58FA@gandi.net>\r\nDate: Fri, 12 Dec 2014 17:01:56 +0100\r\nTo: =?utf-8?Q?Liste_de_d=C3=A9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nMime-Version: 1.0 (Mac OS X Mail 8.1 \\(1993\\))\r\nX-Mailer: Apple Mail (2.1993)\r\nSubject: [Caliopdev] =?utf-8?q?CR_r=C3=A9union_du_12/12/2014?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0996009318==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n--===============0996009318==\r\nContent-Type: multipart/alternative; boundary=\"Apple-Mail=_3323BF0E-B4F1-4B55-AADF-0D8B3FE63C08\"\r\n\r\n\r\n--Apple-Mail=_3323BF0E-B4F1-4B55-AADF-0D8B3FE63C08\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/plain;\r\n\tcharset=utf-8\r\n\r\n# R=C3=A9union CaliOpen, le 12 d=C3=A9cembre 2014\r\n\r\n\r\n## =C3=89taient pr=C3=A9sents :\r\n\r\nAlban\r\nKajan\r\nJulien\r\nVincent\r\nBenjamin\r\nThomas\r\nGuillaume\r\nSteven\r\nAymeric\r\nLaurent\r\nSophie\r\n\r\n\r\n## Pr=C3=A9sentation du screencast\r\n\r\nVersion fran=C3=A7aise pr=C3=A9sent=C3=A9e : tr=C3=A8s bons retours, =\r\ntout le monde est ok\r\nPublication sur le blog uniquement pour l'instant\r\n\r\nSophie demande l'ajout des transcriptions sous les vid=C3=A9os\r\n\r\nLaurent : La mani=C3=A8re de cr=C3=A9er un compte est importante, cela =\r\ndoit =C3=AAtre pris en compte dans l=E2=80=99alpha.\r\nC=E2=80=99est ce qui va faire la diff=C3=A9rence avec les principales =\r\napps concurrentes d=C3=A8s le d=C3=A9but du process d=E2=80=99appropriatio=\r\nn de CaliOpen.\r\nPermet d=E2=80=99accrocher l=E2=80=99utilisateur via un process simple, =\r\nrapide.\r\n\r\n/!\\ Attention : il n=E2=80=99est pas clair que l'interface soit un =\r\nmockup et simplement un mockup, cela peut frustrer les utilisateurs qui =\r\ns=E2=80=99attendent =C3=A0 tester l=E2=80=99interface apr=C3=A8s avoir =\r\nvisionn=C3=A9 la vid=C3=A9o.\r\nPour la bande son : tout faire au futur\r\n\r\n=3D> Action point : Il faut expliquer que c'est une futur interface\r\n\r\n\r\n## P=C3=A9rim=C3=A8tre de l'alpha\r\n\r\nDate pr=C3=A9vue : fin mai 2015\r\n\r\n### au minimum\r\n- cr=C3=A9ation de compte\r\n- gestion des contacts\r\n- gestion d'une, plusieurs boites mail\r\n- ajout de compte\r\n- instance pour tester pouvant =C3=AAtre reset r=C3=A9guli=C3=A8rement\r\n- RWD (voir ci-dessous)\r\n\r\n### hors-p=C3=A9rim=C3=A8tre\r\n\r\n- agenda\r\n- fichier attach=C3=A9s\r\n\r\n### =C3=A0 d=C3=A9finir\r\n\r\n- calcul du niveau de confidentialit=C3=A9\r\n   -> pleins de questions en suspens c=C3=B4t=C3=A9 cha=C3=AEne de =\r\ndelivery\r\n\r\n- un bouton de report de bug simple / feedback\r\n\r\n- Crypto ?\r\n\r\nCommuniquer clairement sur le fait que ce soit une version alpha.\r\n\r\n\r\n## Organisation des d=C3=A9veloppements\r\n\r\n- Qwant a d=C3=A9velopp=C3=A9 un outil pour faire du Kanban int=C3=A9gr=C3=\r\n=A9 sur les API de Github (couplage milestone par ex)\r\n\r\n- D=C3=A9finir un profil front pour recherche de ressources c=C3=B4t=C3=A9=\r\n Qwant\r\n   -> Ember est le crit=C3=A8re principal\r\n   -> Domaines fonctionnels gestion de contacts, de messages\r\n\r\n- Travailler sur une version responsive web design (RWD) =C3=A0 minima =\r\npour l'alpha\r\n   -> choix du framework css encore =C3=A0 faire, selon les contraintes\r\n   -> ne pas faire de responsive au d=C3=A9but rends difficile\r\n   d'int=C3=A9grer plus tard\r\n   -> faire du responsive sans vraiment s'en inqui=C3=A9ter ce n'est\r\n   pas bon, il faut g=C3=A9rer le responsive tout au long du projet\r\n   -> mettre quelqu=E2=80=99un responsable de ce point\r\n\r\n\r\n## Autres points\r\n\r\n- Faire une liste des choix technologiques\r\n\r\n- Ajout d'un CONTRIBUTING\r\n   -> Ouverture d=E2=80=99une page http://caliopen.github.io\r\n\r\n- Vinci pr=C3=A9sentera le projet au CCC\r\n\r\n\r\n## D=C3=A9finir une strat=C3=A9gie d'alpha\r\n\r\n- Sophie fera un verbatim d'une communication que l=E2=80=99on =\r\nadresserait aux contacts choisis pour l=E2=80=99alpha.\r\n- Commencer =C3=A0 constituer une liste d=E2=80=99alpha testeur =C3=A0 =\r\npartir de l=E2=80=99entourage de chacun, pour anticiper le nombre de =\r\nretours, etc.\r\n\r\n\r\n## Next steps\r\n\r\n- Pr=C3=A9voir des r=C3=A9unions plus th=C3=A9matiques par groupe de =\r\ntravail\r\n   -> Backend, lead=C3=A9 par Aymeric\r\n   -> Front / UX-UI, lead=C3=A9 par Thomas\r\n   -> Dev JS (Ember), lead=C3=A9 par Julien\r\n\r\n\r\n\r\n--Apple-Mail=_3323BF0E-B4F1-4B55-AADF-0D8B3FE63C08\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Type: text/html;\r\n\tcharset=utf-8\r\n\r\n<html><head><meta http-equiv=3D\"Content-Type\" content=3D\"text/html =\r\ncharset=3Dutf-8\"></head><body style=3D\"word-wrap: break-word; =\r\n-webkit-nbsp-mode: space; -webkit-line-break: after-white-space;\" =\r\nclass=3D\"\"># R=C3=A9union CaliOpen, le 12 d=C3=A9cembre 2014<br =\r\nclass=3D\"\"><br class=3D\"\"><br class=3D\"\">## =C3=89taient pr=C3=A9sents =\r\n:<br class=3D\"\"><br class=3D\"\">Alban<br class=3D\"\">Kajan<br =\r\nclass=3D\"\">Julien<br class=3D\"\">Vincent<br class=3D\"\">Benjamin<br =\r\nclass=3D\"\">Thomas<br class=3D\"\">Guillaume<br class=3D\"\">Steven<br =\r\nclass=3D\"\">Aymeric<br class=3D\"\">Laurent<br class=3D\"\">Sophie<br =\r\nclass=3D\"\"><br class=3D\"\"><br class=3D\"\">## Pr=C3=A9sentation du =\r\nscreencast<br class=3D\"\"><br class=3D\"\">Version fran=C3=A7aise =\r\npr=C3=A9sent=C3=A9e : tr=C3=A8s bons retours, tout le monde est ok<br =\r\nclass=3D\"\">Publication sur le blog uniquement pour l'instant<br =\r\nclass=3D\"\"><br class=3D\"\">Sophie demande l'ajout des transcriptions sous =\r\nles vid=C3=A9os<br class=3D\"\"><br class=3D\"\">Laurent : La mani=C3=A8re =\r\nde cr=C3=A9er un compte est importante, cela doit =C3=AAtre pris&nbsp;en =\r\ncompte dans l=E2=80=99alpha.<br class=3D\"\">C=E2=80=99est ce qui va faire =\r\nla diff=C3=A9rence avec les principales apps concurrentes d=C3=A8s le =\r\nd=C3=A9but du process d=E2=80=99appropriation de CaliOpen.<div =\r\nclass=3D\"\">Permet d=E2=80=99accrocher l=E2=80=99utilisateur via un =\r\nprocess simple, rapide.<br class=3D\"\"><br class=3D\"\"><u class=3D\"\">/!\\</u>=\r\n Attention : il n=E2=80=99est pas clair que l'interface soit un mockup =\r\net simplement un mockup, cela peut frustrer les utilisateurs qui =\r\ns=E2=80=99attendent =C3=A0 tester l=E2=80=99interface apr=C3=A8s avoir =\r\nvisionn=C3=A9 la vid=C3=A9o.</div><div class=3D\"\">Pour la bande son : =\r\ntout faire au futur<br class=3D\"\"><br class=3D\"\"><div class=3D\"\">=3D&gt; =\r\nAction point : Il faut expliquer que c'est une futur interface<br =\r\nclass=3D\"\"><br class=3D\"\"><br class=3D\"\">## P=C3=A9rim=C3=A8tre de =\r\nl'alpha<br class=3D\"\"><br class=3D\"\">Date pr=C3=A9vue : fin mai 2015<br =\r\nclass=3D\"\"><br class=3D\"\">### au minimum<br class=3D\"\">- cr=C3=A9ation =\r\nde compte<br class=3D\"\">- gestion des contacts<br class=3D\"\">- gestion =\r\nd'une, plusieurs boites mail<br class=3D\"\">- ajout de compte</div><div =\r\nclass=3D\"\">- instance pour tester pouvant =C3=AAtre reset =\r\nr=C3=A9guli=C3=A8rement</div><div class=3D\"\">- RWD (voir ci-dessous)<br =\r\nclass=3D\"\"><br class=3D\"\">### hors-p=C3=A9rim=C3=A8tre<br class=3D\"\"><br =\r\nclass=3D\"\">- agenda<br class=3D\"\">- fichier attach=C3=A9s<br =\r\nclass=3D\"\"><br class=3D\"\">### =C3=A0 d=C3=A9finir<br class=3D\"\"><br =\r\nclass=3D\"\">- calcul du niveau de confidentialit=C3=A9<br =\r\nclass=3D\"\">&nbsp;&nbsp;&nbsp;-&gt; pleins de questions en suspens c=C3=B4t=\r\n=C3=A9 cha=C3=AEne de delivery<br class=3D\"\"><br class=3D\"\">- un bouton =\r\nde report de bug simple / feedback</div><div class=3D\"\"><br =\r\nclass=3D\"\"></div><div class=3D\"\">- Crypto ?<br class=3D\"\"><br =\r\nclass=3D\"\">Communiquer clairement sur le fait que ce soit une version =\r\nalpha.<br class=3D\"\"><br class=3D\"\"><br class=3D\"\">## Organisation des =\r\nd=C3=A9veloppements<br class=3D\"\"><br class=3D\"\">- Qwant a d=C3=A9velopp=C3=\r\n=A9 un outil pour faire du Kanban int=C3=A9gr=C3=A9&nbsp;sur les API de =\r\nGithub (couplage milestone par ex)<br class=3D\"\"><br class=3D\"\">- =\r\nD=C3=A9finir un profil front pour recherche de ressources c=C3=B4t=C3=A9 =\r\nQwant<br class=3D\"\">&nbsp;&nbsp;&nbsp;-&gt; Ember est le crit=C3=A8re =\r\nprincipal<br class=3D\"\">&nbsp;&nbsp;&nbsp;-&gt; Domaines fonctionnels =\r\ngestion de contacts, de messages<br class=3D\"\"><br class=3D\"\">- =\r\nTravailler sur une version responsive web design (RWD) =C3=A0 minima =\r\npour l'alpha<br class=3D\"\">&nbsp;&nbsp;&nbsp;-&gt; choix du framework =\r\ncss encore =C3=A0 faire, selon les contraintes<br =\r\nclass=3D\"\">&nbsp;&nbsp;&nbsp;-&gt; ne pas faire de responsive au d=C3=A9bu=\r\nt rends difficile<br class=3D\"\">&nbsp;&nbsp;&nbsp;d'int=C3=A9grer plus =\r\ntard<br class=3D\"\">&nbsp;&nbsp;&nbsp;-&gt; faire du responsive sans =\r\nvraiment s'en inqui=C3=A9ter ce n'est<br class=3D\"\">&nbsp;&nbsp;&nbsp;pas =\r\nbon, il faut g=C3=A9rer le responsive tout au long du projet<br =\r\nclass=3D\"\">&nbsp;&nbsp;&nbsp;-&gt; mettre quelqu=E2=80=99un responsable =\r\nde ce point<br class=3D\"\"><br class=3D\"\"><br class=3D\"\">## Autres =\r\npoints<br class=3D\"\"><br class=3D\"\">- Faire une liste des choix =\r\ntechnologiques</div><div class=3D\"\"><br class=3D\"\">- Ajout d'un =\r\nCONTRIBUTING<br class=3D\"\">&nbsp; &nbsp;-&gt; Ouverture d=E2=80=99une =\r\npage <a href=3D\"http://caliopen.github.io\" =\r\nclass=3D\"\">http://caliopen.github.io</a><br class=3D\"\"><br class=3D\"\">- =\r\nVinci pr=C3=A9sentera le projet au CCC</div><div class=3D\"\"><br =\r\nclass=3D\"\"><br class=3D\"\">## D=C3=A9finir une strat=C3=A9gie d'alpha<br =\r\nclass=3D\"\"><br class=3D\"\">- Sophie fera un verbatim d'une communication =\r\nque l=E2=80=99on&nbsp;adresserait aux contacts choisis pour =\r\nl=E2=80=99alpha.</div><div class=3D\"\">- Commencer =C3=A0 constituer une =\r\nliste d=E2=80=99alpha testeur =C3=A0 partir de l=E2=80=99entourage de =\r\nchacun, pour anticiper le nombre de retours, etc.<br class=3D\"\"><br =\r\nclass=3D\"\"><br class=3D\"\">## Next steps<br class=3D\"\"><br class=3D\"\">- =\r\nPr=C3=A9voir des r=C3=A9unions plus th=C3=A9matiques par groupe de =\r\ntravail</div><div class=3D\"\">&nbsp; &nbsp;-&gt; Backend, lead=C3=A9 par =\r\nAymeric</div><div class=3D\"\">&nbsp; &nbsp;-&gt; Front / UX-UI, lead=C3=A9 =\r\npar Thomas</div><div class=3D\"\">&nbsp; &nbsp;-&gt; Dev JS (Ember), =\r\nlead=C3=A9 par Julien<br class=3D\"\"><div apple-content-edited=3D\"true\" =\r\nclass=3D\"\">\r\n<div style=3D\"color: rgb(0, 0, 0); font-family: Helvetica; font-size: =\r\nmedium; font-style: normal; font-variant: normal; font-weight: normal; =\r\nletter-spacing: normal; line-height: normal; orphans: 2; text-align: =\r\n-webkit-auto; text-indent: 0px; text-transform: none; white-space: =\r\nnormal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; =\r\n-webkit-text-stroke-width: 0px; \" class=3D\"\"><br =\r\nclass=3D\"Apple-interchange-newline\"></div>\r\n</div>\r\n<br class=3D\"\"></div></div></body></html>=\r\n\r\n--Apple-Mail=_3323BF0E-B4F1-4B55-AADF-0D8B3FE63C08--\r\n\r\n--===============0996009318==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0996009318==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] Compte rendu reunion 21 Novembre - Aymeric Barantal <mric@chamal.fr> - 2014-11-21 1221.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id C51BB120B18;\r\n\tFri, 21 Nov 2014 12:20:58 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -2.599\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-2.599 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id PAmP2DnObEAY; Fri, 21 Nov 2014 12:20:56 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 6AD61120B0A;\r\n\tFri, 21 Nov 2014 12:20:56 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 469046971BD12;\r\n\tFri, 21 Nov 2014 12:20:48 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id v-Ecdp3iZ5FM; Fri, 21 Nov 2014 12:20:47 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id C94EA697569BF;\r\n\tFri, 21 Nov 2014 12:20:44 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 8AEFC6841C528\r\n for <caliopdev@caliop.net>; Fri, 21 Nov 2014 12:20:42 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id TBBQxWFZ5x0f for <caliopdev@caliop.net>;\r\n Fri, 21 Nov 2014 12:20:37 +0100 (CET)\r\nReceived: from relay5-d.mail.gandi.net (relay5-d.mail.gandi.net\r\n [217.70.183.197])\r\n (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id A46FD6971BD0F\r\n for <caliopdev@caliop.net>; Fri, 21 Nov 2014 12:20:37 +0100 (CET)\r\nReceived: from tao.gandi.net (unknown [217.70.176.162])\r\n (Authenticated sender: mric@chamal.fr)\r\n by relay5-d.mail.gandi.net (Postfix) with ESMTPSA id 0D1FA41C0B8\r\n for <caliopdev@caliop.net>; Fri, 21 Nov 2014 12:20:36 +0100 (CET)\r\nDate: Fri, 21 Nov 2014 12:21:37 +0100\r\nFrom: Aymeric Barantal <mric@chamal.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141121112137.GA15318@tao.gandi.net>\r\nMIME-Version: 1.0\r\nUser-Agent: Mutt/1.5.23 (2014-03-12)\r\nSubject: [Caliopdev] Compte rendu reunion 21 Novembre\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0091515836==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\n--===============0091515836==\r\nContent-Type: multipart/signed; micalg=pgp-sha1;\r\n\tprotocol=\"application/pgp-signature\"; boundary=\"ew6BAiZeqk4r7MaW\"\r\nContent-Disposition: inline\r\n\r\n\r\n--ew6BAiZeqk4r7MaW\r\nContent-Type: text/plain; charset=us-ascii\r\nContent-Disposition: inline\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nReunion Caliopen 21 novembre 2014\r\n\r\nPresents:\r\n\r\n- Thomas\r\n- Laurent\r\n- Kajan\r\n- Aymeric\r\n\r\n\r\n# Retours sur la premiere version du screencast propose par Thomas\r\n\r\n## Ajout d'ecrans pour configuration du compte\r\n\r\n- welcome popup apres connexion\r\n    -> Mode assistant, take a tour\r\n\r\n- ecrans de configuration d'une nouvelle adresse email\r\n    -> Sur certains serveur (gmail, yahoo) on peut ne demander en\r\n    complement que le mot de passe, les autres parametres peuvent\r\n    etre deduits.\r\n\r\n    -> que imaps pour le moment en protocole mail\r\n    -> choix de gestion du contenu a simplifier:\r\n        - que 2 choix (only all messages from now, all messages)\r\n        - faire comme les clients news, ne recuperer que les 100\r\n        derniers par defaut.\r\n\r\n    -> prevoir le suivi de l'importation une fois le compte\r\n    configure (estimer le nombre de message le plus tot possible)\r\n\r\n    -> Le take a tour continue ensuite, presentation des fonctions\r\n    principales (recherche, reglette de niveau de confidentialite et\r\n    d'importance)\r\n\r\n- Proposer dans la demo de creer autre chose qu'un compte mail\r\n\r\n- Comment inciter le client a remplir des infos qu'il n'a pas=20\r\nobligatoirement envie de remplir, pour ameliorer son niveau de\r\nconfidentialite ?\r\n\r\n    -> Ce sont des achievements a presenter au client\r\n        - importer une cle\r\n        - avoir des contacts ayant des cles pgp, utilisant caliopen\r\n        - creer un compte caliopen\r\n        - declarer  un device\r\n\r\n        - limiter certaines fonctions tant qu'un certain niveau\r\n        pas atteint (partage calendrier, partage de filtre, de\r\n        fichiers).\r\n\r\n    -> Faire une liste des story de gamification: Laurent\r\n\r\n## Seconde version du screencast attendu pour vendredi 28.\r\n\r\n# Choix du framework JS\r\n\r\n- utilisation de la matrice proposee par Alban apres quelques\r\n  simplifications (un seul angular, peut etre ne pas garder\r\n  sails)\r\n\r\n- diffusion a tous les interesses voulant voter\r\n\r\n- il faudrait avoir toutes les matrices remplies pour\r\nvendredi 28 novembre.\r\n\r\n## Personnes participant a minima a ce vote\r\n\r\n- Alban\r\n- Alex\r\n- Julien\r\n- David\r\n- Diane\r\n- Kajan\r\n- Aymeric\r\n- ?\r\n\r\n# Realisation de spec demande par Kajan, en se basant sur\r\nle mockup.\r\n\r\nDerouler le mockup et ses fonctionnalites pour ce qui doit\r\nexister strictement en v1 -> Laurent\r\n\r\nOn qualifiera ensuite la complexite de realisation cote\r\nfrontend et backend.\r\n\r\n# Prochaine reunion vendredi 5 Decembre a 10h30 chez Gandi\r\n\r\n\r\n--ew6BAiZeqk4r7MaW\r\nContent-Type: application/pgp-signature; name=\"signature.asc\"\r\nContent-Description: Digital signature\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nVersion: GnuPG v1\r\n\r\niQEcBAEBAgAGBQJUbyBBAAoJEA5oxPhdBNj8HXEH/RF8zlyVBY/A6y/bxQSe9YSF\r\nzTwUxQZTzcYSjnAGxPdlktMCIm5zARNxuAoiup8jfQ8U4+565ULkZ2GGoeG/1CYJ\r\n/bVKu6NBBzQdTWIEaKLEizwZNW6KFYSNu6yYWuimYKTMZkAbMimQQrV76SPDMG58\r\nViDGJj4xCzmt7Qza1163uxuf+G7Jibvj69onL+lr3sCkAlgMbnzSAFfqUpdrA6JS\r\nB7HGh1z94jBAzt4JhlbpdtVYIRDijvR2ouzkhgxATkvwO0GTADPqtOPz1HkOd8XJ\r\nsMMjZ/fLYspaUfX7kJcRmYpCh9+7Zz2CAlalcXCs9EOrw3uUkDcsfpoalE1+1MM=\r\n=qHnW\r\n-----END PGP SIGNATURE-----\r\n\r\n--ew6BAiZeqk4r7MaW--\r\n\r\n--===============0091515836==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0091515836==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] Fwd: Re:  matrice de choix framework JS - Aymeric Barantal <mric@chamal.fr> - 2014-11-25 1119.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 35B98120A89;\r\n\tTue, 25 Nov 2014 11:19:22 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.907\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.907 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, HTML_MESSAGE=0.001, RATWARE_GECKO_BUILD=1.691]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id ZDZDsATUlcET; Tue, 25 Nov 2014 11:19:21 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 219E0120A2E;\r\n\tTue, 25 Nov 2014 11:19:20 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id A0CD26971B1F9;\r\n\tTue, 25 Nov 2014 11:19:20 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id cqcSIAqkOm2f; Tue, 25 Nov 2014 11:19:19 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 1FBAB6971B1C3;\r\n\tTue, 25 Nov 2014 11:19:19 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 0E9AA697119BC\r\n for <caliopdev@caliop.net>; Tue, 25 Nov 2014 11:19:18 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id RsSPCqPwVWKZ for <caliopdev@caliop.net>;\r\n Tue, 25 Nov 2014 11:19:14 +0100 (CET)\r\nReceived: from relay3-d.mail.gandi.net (relay3-d.mail.gandi.net\r\n [217.70.183.195])\r\n (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 2883069711380\r\n for <caliopdev@caliop.net>; Tue, 25 Nov 2014 11:19:14 +0100 (CET)\r\nReceived: from [IPv6:2001:4b98:beef:a:d069:1af2:5c42:63e5] (unknown\r\n [IPv6:2001:4b98:beef:a:d069:1af2:5c42:63e5])\r\n (Authenticated sender: mric@chamal.fr)\r\n by relay3-d.mail.gandi.net (Postfix) with ESMTPSA id EE72FA8115\r\n for <caliopdev@caliop.net>; Tue, 25 Nov 2014 11:19:13 +0100 (CET)\r\nMessage-ID: <547457A9.9070801@chamal.fr>\r\nDate: Tue, 25 Nov 2014 11:19:21 +0100\r\nFrom: Aymeric Barantal <mric@chamal.fr>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: =?UTF-8?B?TGlzdGUgZGUgZMOpdmVsb3BwZW1lbnQgQ2FsaW9wZW4=?=\r\n <caliopdev@caliop.net>\r\nReferences: <547453AE.70408@chamal.fr>\r\nIn-Reply-To: <547453AE.70408@chamal.fr>\r\nX-Forwarded-Message-Id: <547453AE.70408@chamal.fr>\r\nContent-Type: multipart/mixed; boundary=\"------------030104060606060208000708\"\r\nSubject: [Caliopdev] Fwd: Re:  matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------030104060606060208000708\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------040204070703020200060006\"\r\n\r\n\r\n--------------040204070703020200060006\r\nContent-Type: text/plain; charset=utf-8; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\n\r\nRate sur le To, groumpfff\r\n\r\n\r\n-------- Forwarded Message --------\r\nSubject: \tRe: [Caliopdev] matrice de choix framework JS\r\nDate: \tTue, 25 Nov 2014 11:02:22 +0100\r\nFrom: \tAymeric Barantal <mric@chamal.fr>\r\nTo: \tDavid Epely <dey@protonmail.ch>\r\n\r\n\r\n\r\nOn 11/24/2014 03:17 PM, David Epely wrote:\r\n> bon j'ai rempli vite fait ce que je sais ... pas trop le temps, je\r\n> m'occupe d'un nouveau né là tout de suite.\r\n>\r\n> (mais toujours envie de m'impliquer)\r\n\r\nCool, merci ;)\r\n\r\nVoici la mienne.\r\n\r\n++ chamal\r\n\r\n\r\n\r\n\r\n\r\n\r\n--------------040204070703020200060006\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n\r\n    <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    <br>\r\n    <div class=\"moz-forward-container\">Rate sur le To, groumpfff<br>\r\n      <br>\r\n      <br>\r\n      -------- Forwarded Message --------\r\n      <table class=\"moz-email-headers-table\" cellspacing=\"0\" border=\"0\"\r\n        cellpadding=\"0\">\r\n        <tbody>\r\n          <tr>\r\n            <th align=\"RIGHT\" nowrap=\"nowrap\" valign=\"BASELINE\">Subject:\r\n            </th>\r\n            <td>Re: [Caliopdev] matrice de choix framework JS</td>\r\n          </tr>\r\n          <tr>\r\n            <th align=\"RIGHT\" nowrap=\"nowrap\" valign=\"BASELINE\">Date: </th>\r\n            <td>Tue, 25 Nov 2014 11:02:22 +0100</td>\r\n          </tr>\r\n          <tr>\r\n            <th align=\"RIGHT\" nowrap=\"nowrap\" valign=\"BASELINE\">From: </th>\r\n            <td>Aymeric Barantal <a class=\"moz-txt-link-rfc2396E\" href=\"mailto:mric@chamal.fr\">&lt;mric@chamal.fr&gt;</a></td>\r\n          </tr>\r\n          <tr>\r\n            <th align=\"RIGHT\" nowrap=\"nowrap\" valign=\"BASELINE\">To: </th>\r\n            <td>David Epely <a class=\"moz-txt-link-rfc2396E\" href=\"mailto:dey@protonmail.ch\">&lt;dey@protonmail.ch&gt;</a></td>\r\n          </tr>\r\n        </tbody>\r\n      </table>\r\n      <br>\r\n      <br>\r\n      <pre>On 11/24/2014 03:17 PM, David Epely wrote:\r\n&gt; bon j'ai rempli vite fait ce que je sais ... pas trop le temps, je \r\n&gt; m'occupe d'un nouveau né là tout de suite.\r\n&gt;\r\n&gt; (mais toujours envie de m'impliquer)\r\n\r\nCool, merci ;)\r\n\r\nVoici la mienne.\r\n\r\n++ chamal\r\n\r\n\r\n\r\n</pre>\r\n      <br>\r\n    </div>\r\n    <br>\r\n  </body>\r\n</html>\r\n\r\n--------------040204070703020200060006--\r\n\r\n--------------030104060606060208000708\r\nContent-Type: application/vnd.oasis.opendocument.spreadsheet;\r\n name=\"matrice_choix_fw_js-chamal.ods\"\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment;\r\n filename=\"matrice_choix_fw_js-chamal.ods\"\r\n\r\nUEsDBBQAAAgAACBQeUWFbDmKLgAAAC4AAAAIAAAAbWltZXR5cGVhcHBsaWNhdGlvbi92bmQu\r\nb2FzaXMub3BlbmRvY3VtZW50LnNwcmVhZHNoZWV0UEsDBBQAAAgAACBQeUVF7NRQ9AIAAPQC\r\nAAAIAAAAbWV0YS54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCI/Pgo8\r\nb2ZmaWNlOmRvY3VtZW50LW1ldGEgeG1sbnM6b2ZmaWNlPSJ1cm46b2FzaXM6bmFtZXM6dGM6\r\nb3BlbmRvY3VtZW50OnhtbG5zOm9mZmljZToxLjAiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3\r\nLnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1l\r\nbnRzLzEuMS8iIHhtbG5zOm1ldGE9InVybjpvYXNpczpuYW1lczp0YzpvcGVuZG9jdW1lbnQ6\r\neG1sbnM6bWV0YToxLjAiIHhtbG5zOm9vbz0iaHR0cDovL29wZW5vZmZpY2Uub3JnLzIwMDQv\r\nb2ZmaWNlIiB4bWxuczpncmRkbD0iaHR0cDovL3d3dy53My5vcmcvMjAwMy9nL2RhdGEtdmll\r\ndyMiIG9mZmljZTp2ZXJzaW9uPSIxLjIiPjxvZmZpY2U6bWV0YT48ZGM6ZGF0ZT4yMDE0LTEx\r\nLTI1VDExOjAxOjAwPC9kYzpkYXRlPjxtZXRhOmdlbmVyYXRvcj5MaWJyZU9mZmljZS8zLjUk\r\nTGludXhfWDg2XzY0IExpYnJlT2ZmaWNlX3Byb2plY3QvMzUwbTEkQnVpbGQtMjwvbWV0YTpn\r\nZW5lcmF0b3I+PG1ldGE6ZWRpdGluZy1kdXJhdGlvbj5QMEQ8L21ldGE6ZWRpdGluZy1kdXJh\r\ndGlvbj48bWV0YTplZGl0aW5nLWN5Y2xlcz4xPC9tZXRhOmVkaXRpbmctY3ljbGVzPjxtZXRh\r\nOmRvY3VtZW50LXN0YXRpc3RpYyBtZXRhOnRhYmxlLWNvdW50PSIxIiBtZXRhOmNlbGwtY291\r\nbnQ9IjMyIiBtZXRhOm9iamVjdC1jb3VudD0iMCIvPjwvb2ZmaWNlOm1ldGE+PC9vZmZpY2U6\r\nZG9jdW1lbnQtbWV0YT5QSwMEFAAICAgAIFB5RQAAAAAAAAAAAAAAAAwAAABzZXR0aW5ncy54\r\nbWztWVFT4jAQfr9fwfQdC+h52hEcxPH0zlOHonfnW2gXyJhmO0lKwV9/aUsdrO1ZofXGmXsC\r\nmuTbj93Nl9306HjhscYchKTIu0Z7p2U0gDvoUj7tGrejs+aBcdz7dISTCXXActEJPOCqKUEp\r\nPUU29HIurWS4awSCW0gklRYnHkhLORb6wNNl1vpsKzaWPFkwyh+6xkwp3zLNMAx3wt0dFFOz\r\nfXh4aMaj6VQH+YROy5pKZq+bQsQnQ9GChExsrNNq7ZnJb6OxIrnmmo7RS/2Q/v3e0cpA8tGk\r\nCrzIN43V44ha19AmrTmF8MlrRt6652vuqKRjBn0BZIS+kQ6qpa8HKVdGr3VkvgR5E/AlTFQ9\r\nyD+pq2Z50J1Oa397+HOg01ku9fbhQWn4pkf8JuUuLMDNmoIwP0jxGp1eYlmGMIQXboalVEJn\r\ngNGL8qH9JqYRaIbniGiHvEb0+RJ7BqDaJRJwEAiJ4gYlVTr/f+UGc7NAPkf+XSHyOQr6iFwR\r\nZvuMqh/oQtb9MxRbZDgIRZ260DPsUwdVuUPX+deA33cUnUOMPiR8WuCeDYOb8q1YtVLYYZGk\r\nbIlbrXqnqCeoFHoVAt8jeiONUmlGR6B3hAVZ1ESqW5v6gEwh0ta/om96ytgzDL8KmpXtMSID\r\nwo2eEgHkI+c8XD8tioZjWS+7tWLBLzhREmXfWnocgYyNiSg+wr/8T7GtUywycKIrmYcbAVEp\r\nUJRuE8JkQb6VMXMPAmP+8u35XAb/ClVd0JttwteQI9QBMhQZaIbRFmp39nc7nc/7FcS1Bq+c\r\nE6mpBx4fYngOxNWdSS1GYiHRSlMD+oW8DpTu5sBeemNk0obskVuJEZsTf4RDIhVkA13FzkqA\r\nL+SqI6nNwhCkjndh+d3eWImz8Lk1+LbwdjB26ZzKQvoVgeeT3zR1Evj+gkp7yZ2ZQE4f4f2q\r\ngVU7mj9Bgip/4ZA8CASJAvyWm4e6z41LJO5Qyxdytqxh69z6LlFwJnStAZ7P9PePdYr0GdPH\r\niC7p1DccDwh3gH1YifyIEtAPFA4Ic4J6UieOrCYPKsj2hWMiYX/vhHIilob5TwuwC3m6uke1\r\nZ0QU698WGVJvobFy85X+kd8ulfDvJeUPiZoUX/Xsburf7yB4X1LCbwLuqIDk3MRU0gSQOdwl\r\nV9jXfMBQ1let1FkP9X2fLW8liFOiSPXZ8rHLrXcpqutubupr+QZawYijAzBAzxcgo71Q+TVE\r\ndYVjCd3cvgssrC7NFy+4zKJXf70/UEsHCPDYJJepAwAAPBwAAFBLAwQUAAgICAAgUHlFAAAA\r\nAAAAAAAAAAAACwAAAGNvbnRlbnQueG1s7Vzdjts2Fr7vUxgKUGwvZFmSf2R3PMEudgMUSHrR\r\nSYoCi72gJdrmRhIFkrJn+kZ9jr5YSerHlEd0pbGteBLNxSTmOTzn4+E5H39G1t3bxygc7CCh\r\nCMdLwx6OjAGMfRygeLM0Pn18Z3rG2/vv7vB6jXy4CLCfRjBmpo9jxv8d8N4xXWTSpZGSeIEB\r\nRXQRgwjSBfMXOIFx0Wuhai+kr6yFsqewcXeprPZm8JE17Sx0K33Bqrlnqaz2DgjYN+0sdHlQ\r\n1e5r3LTzIw3NNeZRjxLA0BGKxxDFn5fGlrFkYVn7/X64d4eYbCx7Pp9bUloC9ku9JCWh1Ap8\r\nC4ZQOKOWPbStQjeCDDTFJ3RVSHEarSBpHBrAwLNZTQikXIUPVyRmM0Nqn0p+7TaNs2u30YTZ\r\n3wLSOM+kcjVV3KB5qriB2jcCbKuZX8/6wIXy14f3h7wiUVNfQrcSKp+gpPEwM221P8a4hCo6\r\nZMUu4Tqj0djKPiva+5Pqe4IYJIq6f1LdB6FfRhxHdUHjerbFNUy4EylfFpEIBNV0cKxMXCrT\r\nQGv6tw/vH/wtjMBBGf29soliykB8iAwRk6Ad6cQiMMGElYFZNydfPltOiW3LolBPHUJaqG5I\r\nENSqcjiuxWmEF7G5Q3D/psKtp/NhbkmlMnERDIsqKXXz4cDHBBIkRgJCkQhmRHnQeHLgZKH0\r\nrvIriR6bmRMJgYP1scWj4vApdVldDD7+YgmZKZYXTqC5J2VZdYz7Yg3NaoZaZcOar6XmGvjQ\r\nDKAf0vu7jAvL5kH2WeBeGh+3mMQBEDHjVFUoRSh8qspUE0JqbmDMB8zLh+AIxBWNBDGfE8wO\r\nECRnwzoN4T3i3C45dvAAYloD5HuQYPrjkV7WeBIa3SNKz4H2b/h/8Gt6Gpai0wTSE2UwOgfT\r\nOwKhBpAiOheBpUumvB2kjM87Q74p7ZRZJn9X8PrYLp3lOGWR8mUxTKPYKHqqjWbCiwYShiAd\r\nrPFiRSD4bK4grx9uULguLObqexSIJW00nHtzD8VyAAqeU+Cc7sB5tme3A+d2B25mj5x24MYd\r\nRs7x3HbgJl2B41tcz5m1AzftsCBmbSM363Ba5868HTivQ3BeS2zz7rBNRrOWTGKPukLnDsfe\r\nbNwSXWdLxGw4c5yWS4Td2RrhDqejyaQNOqJbXQneHyHjLSqsTCQatxBttkzklT2bTLn305BT\r\nCk2cMBSB0FS7M5LCNsA1UX0RcGdkdwdcsyq/LOJTuSfoBrhmxX5hqngd4WagPsWLxgjwDS0x\r\nE7CBZrFtX4M0ZEeDUgaU3bwFiCYheMrx5NbEDQGKN2aEA24pJCZbPYea3UfJ2z/zOeCf7dHI\r\nqCrl95vC0LO+J6gH6ngRhmEhSQARN6jyw9H4cw15lFblOcAKbXGLx6SFScDjusKMiauP0XA0\r\nTdiA4hAFgzfeSPxk8w/8zxuC0zgQ5IfJ0niz8sazcbmRCBDY4JjnwCo0GRfHOIbPZIyLj2Qy\r\nRCBEm9ikOCXiRngHwhSa7CkplQhMICgDvDTWIKRQ4toTkMj0E0fmGJvis6EMLIRrmcnzee2w\r\nEhBkt9Z84LZbJnvWlxRlUBeTolyyi0MTxBtxJ1wjEEMrhnywzXCig5UZ2Ik58nnMcgPlWexw\r\ndJShq05nMTdr+VOJMU5ZiGJYBk8RiXaTbfn0brZZDlXnSJ4Pn10lcHdSQNHvXGA7CVPaCiOE\r\n04JszkphCwJxefcsAXheQSJhVNwX5vY5p6xwGFRACd8moAjEBQJVKKshlxZQFHlmtVCoty2u\r\ndUP4qLVeyvX2SxXVQzYlu7pB1ynk8yrNyv+32NpA3c7mxvmlmsM9v7TnFzUO5/HJqILqq+GT\r\nmqK9IKPorF+SU6oqXbKK7trqi7CKpvx6VrnKrkVvuWeZnmUuyjK6++cb37u8orNRmbNfmlX6\r\ns1B/Fro6n+j+ZHQmn/Qnodtik/7k0+9JrsYhur/sfgEO+SrPPTfCIf05p+eUzjhF90BGf865\r\n3G2KZljNWEZ/OunPPf255+bPPbpnqm763ANXU9sNena5hXNQzyY9m5RsonsK8qZPQD2b3O6J\r\nqGeXb45dLO2XTHLBCgdPhy9AJQSCgG4hZPd32ZN98nf+lF822Q9CbBt5m0ot4vHCSr/8Ed4a\r\nVflVlvzZwYywMh6qYzKrsU3nCjbdK9gcX8Hm5Ao2p1ewObuCTa+wmT0RmqtSM1shYLA0rpEZ\r\n8yvYFF8uuLzRa9TadZC+eJ4I3tdYFM/0Hznn9upci+eDiy+HljuNpUEZ4TsBYUO8niC5f0e4\r\n+h6Tz3dW3nJnHZtv6HDcyOEDI6nPUgK7cwhWKETszz+68viJQjJYAdrdEBPo08FiQP78g0KQ\r\nDrr2S6Gfdu0TJOgiLv+WZ4+LXWdr1gj+R75jDl8CXItwLDdJR9TRjEucplziNBrbP+NNGgJy\r\n/rRMat2tQwxYVSJGX7ofd+rYOTh2LjqfTt1OtBU09wDN/TYm4+IjvhgrlDs58YIHXh1LA68X\r\ny4dPH/7x3+G/nMXwJ+d/PzQfpz0/DNSef6U08gsEPntVeasNqvt6a/lcfrudEXdTyy6vZbdV\r\nLXtKLXuvrpbdRrX8H4Hw/Fmc3kAta9blFtAmB2iTVxWTFzu+bC1/yRFfjETmp0hkzElk3IZE\r\nHCWnnBcl1TVIxD2KF9dTLdujsTeZzvVMo8VknwXqcv7UlsKq8BOY8FG8GVC8i4sqr0qq3EMX\r\njdlNdfHp+H2X938BUEsHCNodMcv4BwAAMFMAAFBLAwQUAAAIAAAgUHlFBIByQwwPAAAMDwAA\r\nGAAAAFRodW1ibmFpbHMvdGh1bWJuYWlsLnBuZ4lQTkcNChoKAAAADUlIRFIAAACyAAABAAgC\r\nAAAAmJ279QAADtNJREFUeJzt3AlUFfUeB/ALXERBCRckUEnTUEFTe4n5xOcSWp6jGZlaKZJr\r\nGtoi4YqHA1JqGpVi7rmVgmGmx0RTj0umvk7vaGrghhKgiamIz62Hwvtx/zKO3C+LGnNRv5/D\r\noblzZ/nNzPf+5z9/rpk3vvDC0+PGeXbsaCq06cUXTeXM6/nnm4eHl7qvvZs29YqNfWrAgEpu\r\nbn9lZ+/o37+8C7uHAqyPxf+TT6r7+alpmSkvZeLnsDD9Wi0mTjwUG3vz2rUiW6vx0kut335b\r\nJv7Yvt3Byal227bp69enxMWVWmoZT2mp5JAj8/PNMnVg6lQViy1BQYFr1tzPRsvo9Nat6hhK\r\n1WToUHPVqjsHDKjfu7e8bNivX6Pg4DIe+V0tfM8F3DqW/HxtjkrACxs36l8WWfHXDz+Eu9sQ\r\nGqpiIRdFbaQsmYBl3A+z/oWENzkuTkq5cfXqscWLnwoJycvNdXR1zbtx4/iyZX9duPD02LEy\r\n53J6uuw+99IlNz+/39esyf7tN79Ro8wuLmd37/bq0kW2sz8mJmvXLu8ePZqGhmZs2FD7uefs\r\n7OySZ82qVKOG78iRauNqj47VqnVcsUI+iHLeYX15N2/Kb++XXz6yYIF3z55yZk2FZ1xtR3ah\r\nlpSdtoyI2BcV5ff++1LkyVWr9AtrC8gBajXIAf46bZoqPmXu3NObN5exgH8tW+bg6KidGZkj\r\nV6NTQkLq11/fuHJFn3jtQ6zIinIeSt6jNTmTPkOH5l68+FvhulcyMh5r0kQ7iq2vvqqW1MpI\r\nX7eu08qVWoVpq1dbb6SEAu6IhZxBibOcNbOzc87Ro9J8HVuyRNrPTvHxjYcM2dStm8TC3tFx\r\nf1RU+6VLd4aEdFi2rEHfvh6nT1euXXvXsGHPxsRcP3++cs2a18+efT4x8ciXX14/d67WM884\r\n1ajx79Gjm3/wwY+DBmkbV3uUWJyIj5fDKO6MHF24sFlYWP1XXsnNyTmRkNB0xAiTpZ2UUtV2\r\nZPUnX3tNZkoQ5bfPkCGVXF2PL12avnatfmFtgYz16/UH2GT4cK14eI5gASrE2pmRacn9z+Hh\r\n/jNmbOvTR5+DIu2irBiwaFHJe7TWKCQkZdYsZy8vfbUFF6/wKLQltTIkFttef12rUGIBN1Km\r\nWOjbxpwjR+Tnyb59G6obqp2d9ta1s2dl93LtVR1VateWiYD586UJyfrpp8oBAZ6dO9s5OLj7\r\n++ekpLi3bVuwtaNHK7u76zeuJlpFRp7Zvr2EM3Jq82bJu8RRPqlyVYq8e6tISywUVYxkpYRt\r\n6tet36uXVvxdFWB9Zq6eOmWuUqXU/epPF1wgPz/fzqJgy5ZlTiYkSDRTV660XlcdhX51rYwi\r\nFZawEWvm4t5QGvTp89OwYe0L23zo2pkzLvXq7Ro8WJqKet27ewQE1OnSRfIhE8eXL3dt1KiK\r\np+djPj7Sclive2DKFP/Y2OxDhy4cPAg3Li3hz2PHmiwnS5spbYx+GXUe5XMg03+dOye7c3J3\r\nV6lVC+sXKKH4uyrA+szI9qVtx+dIt2Kpe7yakeHi7V2jZUuTvf2VzEyZY+/k9J+JEyWa+nUD\r\nv/sOrq6VUaRCuJHi6iyIhe8776gX7RctSivsctbt1i0zKUma3NYzZsg9TNqrZ6Kj1Vvy5CK/\r\nW06apF5eTE6WtqHdwoUFLZile2Vnby+x8AoMvHj4sFyepiNHtpo0KXn2bAmNWuXZKVPURNUG\r\nDezN5lbR0VuDgmB9//vvf9vNmSMHcGTuXHkpYX+yd+/O33yjL1K6EfWDguQo5MpJv8fBxeUf\r\nMTFyRtTC/5w7V7+Aei7Q1k2ZPVvmq+Jh5xQWINvUnxmZf/P69WenTj04fXqdrl31q7eJjVUT\r\n0n+qVK2arHho+vSS93jos8+kwyRnWz5Ihyyre3XqJNf4yLx50hJo6+qPQk1fSk11qVNHlWGy\r\n3C61Cj07dYIbKa4/XhCL5Jkz5UeblaG7Vx1dvPgoaipUPxnS9qRNnPnxR7hx5Y9t24rblJCr\r\nq38pnQb5KbKMdLuOoSL1C8MFxPn9+6XHc28FwDMjTv3wQwkbFCXvUT5mewr70beWHzy4LOsW\r\nWavItdOf55ILMJV6E6FHk3nvpk22rqEkNi/P+AJsfsjCHPk3DYCUhyg7O9uWZ3wBFeGQTfqb\r\nyKxZs8aNGzdnzpwBxYwsQaGhoV988UV+Bc7W3frzzz8bNWqUk5NjzO78/PxOnDjh7u4eFRU1\r\ncOBAY3aq16RJEymgbt26CQkJrVu3VjNvxyIpKWnMmDHr168vLhbnz5+/fv16nTp19DNnz54t\r\nsSi/oo23bdu2S5cuGba7atWqXbx4MSUlpWPHjjaJhQRi//79+/btGzRo0MHCYYJbsZDrLW+s\r\nXr26cePGM2fODAsLk4WWL18+evTomJiYadOmRUZGenh4ODk5HTt2TNqGSZMmyXytkahVq5ac\r\nSllY9iHrBgcHf/vttxcuXDD+IO/T4cOHi+S+vO3du1d+e3p6yuk1cr+aLVu25ObmXrlyRS6u\r\nNvNWLOQj0q5duypVqkgz0qxZsxs3bsybN08us6+vr1x+CcqePXtkgatXr6on5smTJ8dYxl+V\r\nc+fOSQhq1qwpQRk1apS8m5aWZuzR/T02bNggR238fhcsWPDxxx8bv1+lUqVKjz/++Lp167Q5\r\nt2IhdxBpKtQlf+KJJ9RMaTny8vJkYvjw4YGBgR999FFx25XGQ1oU7aVkXzJYHgdQ3sIsTJZB\r\nfcM6TImJiT4+Pj179jRmd9bkXrFjx4433nhDbgVqzu1YyCzpaknv40WrkS9pKtLT011cXGTa\r\nwcEhNTVV7hr6BaS7Kvcnv8IvGTy4VBSMzMSKFSvkcygtsTG7s9a7d2+pQRoM6eJoMwtiER4e\r\nfvz48YiIiPj4+AkTJqjIxMbGtmrVSibk2UTWdHV1lTMld4evvvpKLn+bNm3MZnNcXNwRy99p\r\npN/eq1cvZ2dn6dbKS7mPSFBsc5QPmn79+mnTNnmgkysoH3i5iczUjXQXxGK6hXodb6G9rQr1\r\n9/eXvtjly5e7du0qz2+v6f5iabI0FUaUbyAjL4/Nn+0PHDhgPbNMg9/SsWjevLncOLT00MPN\r\nHKX7IkVx5MFlvPzn1KnfBw6MMvbZuizlPWQF2PyQTWrw+80331xq+aug3GDmz5/fo0ePUlcb\r\nP378lMI/jpcfm4wE60c5DSigyCCj8YdsXYBJ3USWLFkisZBn0V9++aVPnz5liYU84xoQC5sw\r\neJQTDjIaqaRRTlPhU5k8wsr02rVrBw8e/OmnnwYHB2sjmFOnTpVAjRgxIjs7W5Zxc3PTP9I8\r\nHIwf5YSDjDYv4I5YyMProkWLZHrMmDEbN24MCgqSWGgjmBILuXckJib6+vp6eno+fJkw2WiU\r\n03qQ0eYF3I6FNBXdunVr0aKFTKelpcltxt7e3nTnCGZmZmb79u0dHR2NLds4NhnltB5kNFix\r\no5xKVFRUeHi4tBMeHh7yuWnYsKHpzhFMLy+v7du3BwQEyLTcSqpXr274IZQv40c54SCjkYod\r\n5Qy1fAcwMDBQbjPydkREhNws2rVr16FDB2lYJkyYoEYw4+LiJDfSJ5WITJ48WXqwWVlZNjmS\r\nhwkcZLR5AQWxmG2hXms3mBGWfydjsvQzhLZCSEiImpD0GFC0TRg58ggHGY1076Oc9Kgp0yin\r\nDdm8vEd3lNPWNRSrInzf9dH9iq82+K1Y31kjIyOjo6Nt/rc+Yxj8FV87XdtgkzMMC7g9+C2z\r\n8vLyRo4cab2mPIBEF/5Lw4eewYPfpgrwt3XrAu7octrb2zdu3Fji4+rqKk+nO3fulOeOzz//\r\nXL3r5ubWokWLxYsXHzx4UA2Ny0fqgf5CrzXjB78rpjsGv02W4Lz33nspKSne3t4ZGRl169bV\r\nYpGZmblr167u3bvfvHlTDY3LAg/0F3qt2WTw28XFxcnJST5vtvo6p3UBdwx+a9NeXl5y7T09\r\nPdVXfJWqVat27tz55MmTMlMbGjc9yF/otWb84HdWVlb16tV3797dv39/m8QCFlB03EI+/cWt\r\nn5ubm5SU5OPjk52drQ2NP2SMH/x+6623Vq1a5eDgYKseBiygIBbqHzPpe6Tff/+99ltuIu++\r\n+27Tpk2dnZ3r1asnTU1ycrIaGu9q+X858Au99yM1NVXacOnQaAPNFaGAglgstiiytMqOliCJ\r\ngvaWBEIbGodPLg86Dn5z8JsADn5XuAJsfsimCj74TbbCmwgBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB\r\njAUBjAUBjAUBjAUBjAUBjAUBjAUBjAUB/wf5sSgaRQUKGwAAAABJRU5ErkJgglBLAwQUAAAI\r\nAAAgUHlFAAAAAAAAAAAAAAAAHwAAAENvbmZpZ3VyYXRpb25zMi9pbWFnZXMvQml0bWFwcy9Q\r\nSwMEFAAACAAAIFB5RQAAAAAAAAAAAAAAABoAAABDb25maWd1cmF0aW9uczIvcG9wdXBtZW51\r\nL1BLAwQUAAAIAAAgUHlFAAAAAAAAAAAAAAAAGgAAAENvbmZpZ3VyYXRpb25zMi90b29scGFu\r\nZWwvUEsDBBQAAAgAACBQeUUAAAAAAAAAAAAAAAAaAAAAQ29uZmlndXJhdGlvbnMyL3N0YXR1\r\nc2Jhci9QSwMEFAAACAAAIFB5RQAAAAAAAAAAAAAAABwAAABDb25maWd1cmF0aW9uczIvcHJv\r\nZ3Jlc3NiYXIvUEsDBBQAAAgAACBQeUUAAAAAAAAAAAAAAAAYAAAAQ29uZmlndXJhdGlvbnMy\r\nL3Rvb2xiYXIvUEsDBBQAAAgAACBQeUUAAAAAAAAAAAAAAAAYAAAAQ29uZmlndXJhdGlvbnMy\r\nL21lbnViYXIvUEsDBBQAAAgAACBQeUUAAAAAAAAAAAAAAAAYAAAAQ29uZmlndXJhdGlvbnMy\r\nL2Zsb2F0ZXIvUEsDBBQACAgIACBQeUUAAAAAAAAAAAAAAAAnAAAAQ29uZmlndXJhdGlvbnMy\r\nL2FjY2VsZXJhdG9yL2N1cnJlbnQueG1sAwBQSwcIAAAAAAIAAAAAAAAAUEsDBBQACAgIACBQ\r\neUUAAAAAAAAAAAAAAAAKAAAAc3R5bGVzLnhtbN1ZbW/bNhD+vl9hKEPRApMlOclWu7HzYcWw\r\nAW1R9GXfGYmSuVKkQFJ23F+/IynKeo/abOi6NEgj8rnjw+eOx5Nzc3uf08UBC0k423rRMvQW\r\nmMU8ISzbeh8//OY/9253P9zwNCUx3iQ8LnPMlC/ViWK5AGMmN3Zy65WCbTiSRG4YyrHcqHjD\r\nC8yc0aaJ3pil7IhxNtfcgJvWCt+rucYa27JFd/NXNuCmdSLQca6xxoKmTfOUzzW+l9RPuR/z\r\nvECKdFjcU8I+bb29UsUmCI7H4/J4ueQiC6L1eh2Y2ZpwXOOKUlCDSuIAU6wXk0G0jAKHzbFC\r\nc/lpbJMSK/M7LGZLgxTqRbUQWAIEtqvzcp6jpk0rvw7Z7Ow6ZCMyx3skZueZAbdT5TKZnyqX\r\nSdM2R2o/Et/nwWuYND9evzrnlcjnrqWxLaliQYrZ27Topj3nvKaqDexhN3RXYXgV2OcG+jgJ\r\nPwqisGjA40l4jGhcK87zIdEAFwWA8PFBp7xDC73pUc/XgcAFF6omks4vdqDOqj6qe5XT8aOq\r\nZx00E0kyCAU6lwEcWzg0/oHg40Wrlk3rvw4MqE5pKS/V0Bof3gV6ztflEgpCVbEbt8TK27kr\r\nIeVwHaQoxn6CYyp3N/Yo18ML+6xF2nof9lywBGkKcNIcKCf01J5rutCzfoYZFgSiL3iOWAtR\r\nEBXD+TggQczmgmkKrwiUJlMiFu8RkwNEnqCCyxcdnB2cpCaPRMrHUHuJ/0J/ltO0Gpg5lE5S\r\n4fwxnH4TGI8Qakw9lkEwlkzVuG03HNMEp6ikVRPiPFekTIL7MabUc/ACCZQJVOz9QsCZEIpA\r\n52KnAA1eeOEnRCrEdBMTLq8JO6uij0DfzvAcyaeUbyhiWYkymMXMDMS8ZEoAvY/vva4LH4oI\r\nYt3oG4zz4yCf926mcugmfn3Td6uvMYrvpx3XoD3puq6n/nhjQjSg/e7G3vTVhd8KiFXnTeh1\r\nQIvqKSfMJ0zhDOwSkhElobCYhQZ81j7iUgjoT09DS0Xh1dvQ7eLAKQRFN3dKlNgbcHDK7zh1\r\nbNoRc9hG1HY/1sQ6Lkb2BwlMckT9gkI6w95W3vTO3WwmeFmY7tsQbwjS3vtcSbyxTDZpSTm0\r\naRdpGsKXXqvyqbE7v17ZPH7HCloBclTUKc4SYjvLA6IlfvrsSaZebOvkQUVBKz39VnJNRMNa\r\n9mPw0h4Y7+E69UCpOR/IuvCej+UDJN5hOc1hUZfK+u3Ob/OfSCNDsXqJIwpREjt/BlyyBAt4\r\nDcEOIzklyQjkSBLd7aJS8RFElbRmUfO7V3M4YpLtoaOBnEq+VJzVV6nTFtY0Zd20OSdgf+nf\r\nMdJv2v9MYGqjgXtOSwiByZgveSn0PZeSe+ccmluMtJhwpBjIlyIqG+dm8P4Eyc9Ot14MhtCo\r\nj16bdZaQz7B29HOhvLHMeVwwK0Wjr5LUhWOOpILbF00f6p6mvw77JINO+1I96uSGlzoS++2+\r\nptA3MkUnXqrWnl4XeeQNgPqk9PsSbMDPeQJ2VPjq7hyTPezufKG2xlLOIXydgOkqu6+CEC5X\r\n6+tLYruZHIkM5ihO9Ux7UFT49ugdV0q/j4XLcP38yvZXwTiris63YArN4CDNNqWgF4sZMVz9\r\nz2Ko5+64SPQnPavl1bqAHevKvrgIzZcBFCixnyWCWfhL5MxQ/Elf0yxx5fwiDvW/WqIGApqA\r\nDPcSprfd7yOj/quiPZzfwWj1qiZyJGsX8tx0mUHtaaotap6IgaJn6e9uzOe3RfW/3GNs0bvb\r\n29uboDtYjRQdETrh15F01zeR0Gueeheg1aZe/a3eS/WgaduGcBe59RpjPQrOVUv0SQpBT8eH\r\npH1XfVY1oeyqp6zrBDJ9pWlCXyj24qnFKaJoE2Kfn/WEaK3UGjKnqLM6NFZ4tMeCzq0G+aaf\r\nh6MVRld+FPmra28XhoH5DsOKhQbuflo4wsA+DDfmuyY9lD5tft8spxZBE2jesHbrdRNox/6l\r\n3AuGT3sw/Bei3d9QSwcIyMYeqdcFAABhGgAAUEsDBBQACAgIACBQeUUAAAAAAAAAAAAAAAAV\r\nAAAATUVUQS1JTkYvbWFuaWZlc3QueG1srVPBasMwDL33K4Lvsbeehmnaw2Bf0H2A5yipwZaN\r\nJZf275cU2nSMjIb1JlnSe088ebM7BV8dIZOL2IhX+SIqQBtbh30jPvcf9ZvYbVebYNB1QKyv\r\nQTXMId3SRpSMOhpypNEEIM1WxwTYRlsCIOuf/frCdMvuBKzFdlVNfJ3zUA/z+Tx1d8X7Ohk+\r\nNELNgUzPAVpnaj4naIRJyTtreGhTR2zlRbC81ykpZTAtHQBYqCVSArCRw5Yz1AwnVmN5ESgB\r\n82AFPR3YRuRx3Wfj7g8lfKFxnhRfQ5mwnyFxwfSgxvoilveInetLvjhJa2WsBQ9DGrOyJee/\r\nN/sf14OnRQVHCbI4ae8RFvrPZw+Pur9Rv77p9htQSwcIaAYFaxMBAADhAwAAUEsBAhQAFAAA\r\nCAAAIFB5RYVsOYouAAAALgAAAAgAAAAAAAAAAAAAAAAAAAAAAG1pbWV0eXBlUEsBAhQAFAAA\r\nCAAAIFB5RUXs1FD0AgAA9AIAAAgAAAAAAAAAAAAAAAAAVAAAAG1ldGEueG1sUEsBAhQAFAAI\r\nCAgAIFB5RfDYJJepAwAAPBwAAAwAAAAAAAAAAAAAAAAAbgMAAHNldHRpbmdzLnhtbFBLAQIU\r\nABQACAgIACBQeUXaHTHL+AcAADBTAAALAAAAAAAAAAAAAAAAAFEHAABjb250ZW50LnhtbFBL\r\nAQIUABQAAAgAACBQeUUEgHJDDA8AAAwPAAAYAAAAAAAAAAAAAAAAAIIPAABUaHVtYm5haWxz\r\nL3RodW1ibmFpbC5wbmdQSwECFAAUAAAIAAAgUHlFAAAAAAAAAAAAAAAAHwAAAAAAAAAAAAAA\r\nAADEHgAAQ29uZmlndXJhdGlvbnMyL2ltYWdlcy9CaXRtYXBzL1BLAQIUABQAAAgAACBQeUUA\r\nAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAEfAABDb25maWd1cmF0aW9uczIvcG9wdXBtZW51\r\nL1BLAQIUABQAAAgAACBQeUUAAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAADkfAABDb25maWd1\r\ncmF0aW9uczIvdG9vbHBhbmVsL1BLAQIUABQAAAgAACBQeUUAAAAAAAAAAAAAAAAaAAAAAAAA\r\nAAAAAAAAAHEfAABDb25maWd1cmF0aW9uczIvc3RhdHVzYmFyL1BLAQIUABQAAAgAACBQeUUA\r\nAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAKkfAABDb25maWd1cmF0aW9uczIvcHJvZ3Jlc3Ni\r\nYXIvUEsBAhQAFAAACAAAIFB5RQAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAA4x8AAENvbmZp\r\nZ3VyYXRpb25zMi90b29sYmFyL1BLAQIUABQAAAgAACBQeUUAAAAAAAAAAAAAAAAYAAAAAAAA\r\nAAAAAAAAABkgAABDb25maWd1cmF0aW9uczIvbWVudWJhci9QSwECFAAUAAAIAAAgUHlFAAAA\r\nAAAAAAAAAAAAGAAAAAAAAAAAAAAAAABPIAAAQ29uZmlndXJhdGlvbnMyL2Zsb2F0ZXIvUEsB\r\nAhQAFAAICAgAIFB5RQAAAAACAAAAAAAAACcAAAAAAAAAAAAAAAAAhSAAAENvbmZpZ3VyYXRp\r\nb25zMi9hY2NlbGVyYXRvci9jdXJyZW50LnhtbFBLAQIUABQACAgIACBQeUXIxh6p1wUAAGEa\r\nAAAKAAAAAAAAAAAAAAAAANwgAABzdHlsZXMueG1sUEsBAhQAFAAICAgAIFB5RWgGBWsTAQAA\r\n4QMAABUAAAAAAAAAAAAAAAAA6yYAAE1FVEEtSU5GL21hbmlmZXN0LnhtbFBLBQYAAAAAEAAQ\r\nADYEAABBKAAAAAA=\r\n--------------030104060606060208000708\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--------------030104060606060208000708--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] Nouveau screencast (brut) - Aymeric Barantal <mric@chamal.fr> - 2014-11-28 1718.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id D4602120ACA;\r\n\tFri, 28 Nov 2014 17:18:06 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.907\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.907 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, HTML_MESSAGE=0.001, RATWARE_GECKO_BUILD=1.691]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id AvSyMJyRpY7v; Fri, 28 Nov 2014 17:18:04 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id B3378120A19;\r\n\tFri, 28 Nov 2014 17:18:04 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 4DDED6AA7E945;\r\n\tFri, 28 Nov 2014 17:18:04 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id JwpO0OuLAp4d; Fri, 28 Nov 2014 17:18:03 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id DB5CC6B38A1BF;\r\n\tFri, 28 Nov 2014 17:18:02 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 049626AA7E95C\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:18:02 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id kTO1d8g3k7_R for <caliopdev@caliop.net>;\r\n Fri, 28 Nov 2014 17:17:58 +0100 (CET)\r\nReceived: from relay3-d.mail.gandi.net (relay3-d.mail.gandi.net\r\n [217.70.183.195])\r\n (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 3B6F76B38A1BF\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:17:58 +0100 (CET)\r\nReceived: from [IPv6:2001:4b98:beef:a:d0d8:9686:95da:ec1f] (unknown\r\n [IPv6:2001:4b98:beef:a:d0d8:9686:95da:ec1f])\r\n (Authenticated sender: mric@chamal.fr)\r\n by relay3-d.mail.gandi.net (Postfix) with ESMTPSA id 16AC0A80D2\r\n for <caliopdev@caliop.net>; Fri, 28 Nov 2014 17:17:57 +0100 (CET)\r\nMessage-ID: <5478A041.8020105@chamal.fr>\r\nDate: Fri, 28 Nov 2014 17:18:09 +0100\r\nFrom: Aymeric Barantal <mric@chamal.fr>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: =?UTF-8?B?TGlzdGUgZGUgZMOpdmVsb3BwZW1lbnQgQ2FsaW9wZW4=?=\r\n <caliopdev@caliop.net>\r\nSubject: [Caliopdev] Nouveau screencast (brut)\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============1511068903==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--===============1511068903==\r\nContent-Type: multipart/alternative;\r\n boundary=\"------------020605060001040906060900\"\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------020605060001040906060900\r\nContent-Type: text/plain; charset=utf-8; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\n\r\nJ'ai pas valide ce message sur la ML qui est bloque pour taille trop grosse.\r\n\r\nLe screencast est disponible sur une url :\r\nhttps://poc.caliopen.org/media/Screencast-20141128-brut.mov\r\n\r\nEt le mail qui va avec\r\n\r\n-----\r\nHello,\r\n\r\nJe pose ici un nouveau screencast, brut, sans montage, sans indication \r\njuste pour vous donner une idée d’où j’en suis :)\r\n\r\nN’hésitez pas à me faire vos remarques, bousculer un peu ce que j’ai \r\nfait pour n’en tirer que le meilleur ;)\r\n\r\n\r\n\r\n\r\n\r\nBon visionnage !\r\n\r\nA toute ;)\r\n\r\n--------\r\nThomas LAURENT\r\n/Ergonome web/\r\n/(UX Architect & Designer)/\r\n/\r\n/\r\n/Tél : 01 70 37 78 56/\r\n\r\n/Gandi.net <http://Gandi.net> - No Bullshit ™/\r\n/63-65 boulevard Massena/\r\n/75013 Paris (France)/\r\n--------\r\n\r\n\r\n\r\n\r\n--------------020605060001040906060900\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n<html>\r\n  <head>\r\n\r\n    <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\r\n  </head>\r\n  <body bgcolor=\"#FFFFFF\" text=\"#000000\">\r\n    J'ai pas valide ce message sur la ML qui est bloque pour taille trop\r\n    grosse.<br>\r\n    <br>\r\n    Le screencast est disponible sur une url : <br>\r\n    <a class=\"moz-txt-link-freetext\" href=\"https://poc.caliopen.org/media/Screencast-20141128-brut.mov\">https://poc.caliopen.org/media/Screencast-20141128-brut.mov</a><br>\r\n    <br>\r\n    Et le mail qui va avec<br>\r\n    <br>\r\n    -----<br>\r\n    Hello,\r\n    <div class=\"\"><br class=\"\">\r\n    </div>\r\n    <div class=\"\">Je pose ici un nouveau screencast, brut, sans montage,\r\n      sans indication juste pour vous donner une idée d’où j’en suis :)</div>\r\n    <div class=\"\"><br class=\"\">\r\n    </div>\r\n    <div class=\"\">N’hésitez pas à me faire vos remarques, bousculer un\r\n      peu ce que j’ai fait pour n’en tirer que le meilleur ;)</div>\r\n    <div class=\"\"><br class=\"\">\r\n    </div>\r\n    <br>\r\n    <fieldset class=\"mimeAttachmentHeader\"></fieldset>\r\n    <br>\r\n    <div class=\"\"><br class=\"\">\r\n    </div>\r\n    <div class=\"\"><br class=\"\">\r\n    </div>\r\n    <div class=\"\">Bon visionnage !</div>\r\n    <div class=\"\"><br class=\"\">\r\n    </div>\r\n    A toute ;)<br class=\"\">\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-size:\r\n      medium; font-style: normal; font-variant: normal; font-weight:\r\n      normal; letter-spacing: normal; line-height: normal; orphans: 2;\r\n      text-align: -webkit-auto; text-indent: 0px; text-transform: none;\r\n      white-space: normal; widows: 2; word-spacing: 0px;\r\n      -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; \"\r\n      class=\"\"><br class=\"Apple-interchange-newline\">\r\n      --------</div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\">Thomas LAURENT</div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\"><i class=\"\">Ergonome\r\n        web</i></div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\"><i class=\"\">(UX\r\n        Architect &amp; Designer)</i></div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\"><i class=\"\"><br\r\n          class=\"\">\r\n      </i></div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\"><i class=\"\">Tél : 01 70\r\n        37 78 56</i></div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\"><br class=\"\">\r\n    </div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\"><i class=\"\"><a\r\n          href=\"http://Gandi.net\" class=\"\">Gandi.net</a> - No Bullshit ™</i></div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\"><i class=\"\">63-65\r\n        boulevard Massena</i></div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\"><i class=\"\">75013 Paris\r\n        (France)</i></div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\">--------</div>\r\n    <div style=\"color: rgb(0, 0, 0); font-family: Helvetica; font-style:\r\n      normal; font-variant: normal; font-weight: normal; letter-spacing:\r\n      normal; line-height: normal; orphans: 2; text-align: -webkit-auto;\r\n      text-indent: 0px; text-transform: none; white-space: normal;\r\n      widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto;\r\n      -webkit-text-stroke-width: 0px; \" class=\"\"><br class=\"\">\r\n    </div>\r\n    <br>\r\n    <br>\r\n  </body>\r\n</html>\r\n\r\n--------------020605060001040906060900--\r\n\r\n--===============1511068903==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============1511068903==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] Prochaine réunion - Laurent Chemla <laurent@brainstorm.fr> - 2014-12-01 2142.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 55029120A2B;\r\n\tMon,  1 Dec 2014 21:42:46 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -2.599\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-2.599 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id uCV0snTUjvk8; Mon,  1 Dec 2014 21:42:43 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id A9B9C120A29;\r\n\tMon,  1 Dec 2014 21:42:42 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 0A7A76B5B833A;\r\n\tMon,  1 Dec 2014 21:42:41 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id LImIg5CFsjXi; Mon,  1 Dec 2014 21:42:38 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 7665B6B5B8336;\r\n\tMon,  1 Dec 2014 21:42:37 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 344A06B5B8337\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 21:42:36 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id vDRWwqAd8P3e for <caliopdev@caliop.net>;\r\n Mon,  1 Dec 2014 21:42:35 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 667626B5B8336\r\n for <caliopdev@caliop.net>; Mon,  1 Dec 2014 21:42:35 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 4690920BDE3CF; Mon,  1 Dec 2014 21:42:35 +0100 (CET)\r\nDate: Mon, 1 Dec 2014 21:42:35 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141201204235.GC7323@brasil.brainstorm.fr>\r\nReferences: <5478A041.8020105@chamal.fr>\r\n <20141130162431.GF14633@brasil.brainstorm.fr>\r\n <BC28FFD7-1F1D-4854-96B2-723CCB0DC6F7@gandi.net>\r\n <20141201111410.GI14633@brasil.brainstorm.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nIn-Reply-To: <20141201111410.GI14633@brasil.brainstorm.fr>\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: [Caliopdev] =?iso-8859-1?q?Prochaine_r=E9union?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\nPuisque les mecs de Qwant me disent que j'aurais d=FB les relancer\r\nplus t=F4t (ils sont pas s=FBrs de pouvoir venir du coup), je pr=E9f=E8re\r\nassurer de ce c=F4t=E9-ci de l'email et vous rappeler la prochaine\r\nr=E9union, vendredi prochain (5/12) =E0 10h30 chez Gandi.\r\n\r\nLes th=E8mes seront:\r\n\r\n- choix final (?) du framework,\r\n- d=E9finition des sp=E9cifs de la future alpha,\r\n- 1er jet de sc=E9narii de r=E9compenses pour la judification,\r\n- pr=E9sentation du screencast (sonoris=E9 ?).\r\n\r\nOn va publier une note de blog incessament: on va communiquer sur\r\n=E7a, le screencast et le nouveau mockup d=E8s que ce sera possible.\r\n\r\nAussi, si Benjamin veut se joindre =E0 nous, il envisage d'intervenir\r\ndans le cadre du 31c3 =E0 Hamburg fin d=E9cembre pour pr=E9senter Caliopen.\r\n\r\nSauf si c'est pas possible, j'aimerais repousser la r=E9union suivante\r\nau 26 (sauter 3 semaines donc): pas pour prendre des vacances mais\r\npour relever un peu le pied sur les allers-retours =E0 Paris qui me\r\nfatiguent beaucoup. Ou alors d'organiser la r=E9union du 19 sans moi,\r\npour une fois, ou en videoconf=E9rence. Ca va d=E9pendre de Qwant: s'ils\r\ndisent qu'ils ne peuvent venir que le 19, j'y serai.\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] Prochaines réunions. - Laurent Chemla <laurent@brainstorm.fr> - 2014-12-29 1807.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nDelivered-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby gandi.net (Postfix) with ESMTP id 67BFFE39E5;\r\n\tMon, 29 Dec 2014 18:08:07 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.185\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.185 tagged_above=-999 required=5\r\n\ttests=[BAYES_40=-0.185]\r\nReceived: from gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 51vxoEnr-2Us; Mon, 29 Dec 2014 18:08:05 +0100 (CET)\r\nReceived: from smtp-out1.octopuce.fr (bck3.octopuce.fr [91.194.60.153])\r\n\tby gandi.net (Postfix) with ESMTP id 4390FE39BD;\r\n\tMon, 29 Dec 2014 18:08:05 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\tby smtp-out1.octopuce.fr (Postfix) with ESMTPS id E95791070AAD8;\r\n\tMon, 29 Dec 2014 18:08:04 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 238A16B6D94AE;\r\n\tMon, 29 Dec 2014 18:08:04 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id oGoNXfDa7Ich; Mon, 29 Dec 2014 18:08:00 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id BCD736B6D94AC;\r\n\tMon, 29 Dec 2014 18:07:59 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 8AF1B6B6D94AE\r\n for <caliopdev@caliop.net>; Mon, 29 Dec 2014 18:07:58 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id oo3E1zN0VRqg for <caliopdev@caliop.net>;\r\n Mon, 29 Dec 2014 18:07:57 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id BD0846B6D94AC\r\n for <caliopdev@caliop.net>; Mon, 29 Dec 2014 18:07:57 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 9EA9D20B3740F; Mon, 29 Dec 2014 18:07:56 +0100 (CET)\r\nDate: Mon, 29 Dec 2014 18:07:56 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141229170756.GG25421@brasil.brainstorm.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: [Caliopdev] =?iso-8859-1?q?Prochaines_r=E9unions=2E?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\nSalut,\r\n\r\nLes r=E9unions \"th=E9matiques\" vont =EAtre plus difficiles =E0 organiser, je\r\npropose qu'on conserve le principe des r=E9unions quinzomadaires, mais\r\nqu'en fonction des besoins, celles-ci soient limit=E9es aux sujets qui\r\nont besoin d'=EAtre affin=E9s.\r\n\r\nPar exemple, si on pr=E9voit 2 dates en janvier (le 9 et le 23 ?), il\r\nfaut que les personnes int=E9ress=E9es par une discussion sur le back le\r\nsignalent ici, celles qui veulent parler de l'UI aussi, idem pour la\r\ncommunication.\r\n\r\nEn fonction des pr=E9sents sur chaque sujet, on d=E9cidera du ou des\r\nth=E8mes de la ou des r=E9unions, ainsi que de leur horaire pour ceux\r\nqui voudraient participer =E0 plusieurs.\r\n\r\n=C7a vous semble faisable, les gens ?\r\n\r\nL.\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] Prochaines étapes ? - Kajan Sivaramalingam <kajan@kajansiva.com> - 2014-12-06 2108.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 8C9A6120AD3;\r\n\tSat,  6 Dec 2014 21:08:48 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 1.764\r\nX-Spam-Level: *\r\nX-Spam-Status: No, score=1.764 tagged_above=-999 required=5\r\n\ttests=[BAYES_40=-0.185, HTML_30_40=0.374, HTML_MESSAGE=0.001,\r\n\tHTML_SHORT_LENGTH=1.574]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 2Ng6d2HWDL0C; Sat,  6 Dec 2014 21:08:47 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 76C3B120ACA;\r\n\tSat,  6 Dec 2014 21:08:47 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id E58786B491602;\r\n\tSat,  6 Dec 2014 21:08:45 +0100 (CET)\r\nAuthentication-Results: brassens.heberge.info; dkim=fail\r\n\treason=\"verification failed; insecure key\"\r\n\theader.d=gmail.com header.i=@gmail.com header.b=J9vlzSDQ;\r\n\tdkim-adsp=none (insecure policy); dkim-atps=neutral\r\nReceived: from mail-lb0-x229.google.com (mail-lb0-x229.google.com\r\n [IPv6:2a00:1450:4010:c04::229])\r\n (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id 9BF9D6B491602\r\n for <caliopdev@caliop.net>; Sat,  6 Dec 2014 21:08:44 +0100 (CET)\r\nReceived: by mail-lb0-f169.google.com with SMTP id p9so2120885lbv.28\r\n for <caliopdev@caliop.net>; Sat, 06 Dec 2014 12:08:44 -0800 (PST)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r\n h=mime-version:sender:in-reply-to:references:date:message-id:subject\r\n :from:to:content-type;\r\n bh=scluubGFmKcu21kNZdd8QyvQPjxBISf6bLjZdfhMuXk=;\r\n b=J9vlzSDQX0t4m3WJa1/cD0+7hbhxbjISqmTdfA8fNdmU104eCvrZkei6xV0wKPJvTw\r\n FFRg7is2KH2y3QRRnNlsErz1MA0vK4QBJk0MHgkIwOKHRmQvWgaUN+NGhO1eGn4E+UkC\r\n JMee5a/ROJee0lta+B3HS+gK9EaXOq6acDDGrkl3/IvGKz+Ls4rDIfXHda8hUf41/V5r\r\n 8lypdI6yG5CJR2j4p68rxA457AFt9i0s13bOl6yBxno/DTZ4UZnsvANH90M6IpSNDDGc\r\n CE/0bnSy/xfqZw6W0cElnr5+MtEXHEtm5Z42aarFkuTgi8kmUQq41IOlWZwKaAtDYKvm\r\n rusg==\r\nMIME-Version: 1.0\r\nX-Received: by 10.152.121.1 with SMTP id lg1mr8845471lab.28.1417896524025;\r\n Sat, 06 Dec 2014 12:08:44 -0800 (PST)\r\nReceived: by 10.25.20.216 with HTTP; Sat, 6 Dec 2014 12:08:43 -0800 (PST)\r\nReceived: by 10.25.20.216 with HTTP; Sat, 6 Dec 2014 12:08:43 -0800 (PST)\r\nIn-Reply-To: <CA+tgzehWtaqTp6pNVxFvNRXR5620RuY530EWxbrWNBKeB2eS0g@mail.gmail.com>\r\nReferences: <CA+tgzeg9KmeU2B_=609YsLtS05xj-pCRZSL4R2A-QTR0ThuEeA@mail.gmail.com>\r\n <CA+tgzeir52HN-cGebJitqXEo2YysQZAu37aTvix2wgE83FgAJQ@mail.gmail.com>\r\n <CA+tgzehWJrANZdeGvfn+fFSDe8Dm9X9GMeDjr9LesmfZ4SgjZA@mail.gmail.com>\r\n <CA+tgzehWtaqTp6pNVxFvNRXR5620RuY530EWxbrWNBKeB2eS0g@mail.gmail.com>\r\nDate: Sat, 6 Dec 2014 21:08:43 +0100\r\nX-Google-Sender-Auth: KHYogue2T1CzZKsD69Hf3IqM9Cc\r\nMessage-ID: <CA+tgzej7rZ-EBSYXC81Os0emcdCoVSVfMEAZvY_1tShwshzb=w@mail.gmail.com>\r\nFrom: Kajan Sivaramalingam <kajan@kajansiva.com>\r\nTo: =?UTF-8?Q?Liste_de_d=C3=A9veloppement_Caliopen?= <caliopdev@caliop.net>\r\nSubject: [Caliopdev] =?utf-8?q?Prochaines_=C3=A9tapes_=3F?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: multipart/mixed; boundary=\"===============0005215019==\"\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n--===============0005215019==\r\nContent-Type: multipart/alternative; boundary=089e0115ef10a493d7050991c2f7\r\n\r\n--089e0115ef10a493d7050991c2f7\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nComment s'est d=C3=A9roul=C3=A9 la r=C3=A9union d'hier ?\r\n\r\nQuelles sont les prochaines =C3=A9tapes ?\r\n\r\n--089e0115ef10a493d7050991c2f7\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<p dir=3D\"ltr\">Comment s&#39;est d=C3=A9roul=C3=A9 la r=C3=A9union d&#39;hi=\r\ner ?</p>\r\n<p dir=3D\"ltr\">Quelles sont les prochaines =C3=A9tapes ?</p>\r\n\r\n--089e0115ef10a493d7050991c2f7--\r\n\r\n--===============0005215019==\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--===============0005215019==--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] [Coin-coin@canapin.com: Petites fautes dans la FAQ] - Laurent Chemla <laurent@brainstorm.fr> - 2014-11-24 1840.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 13034120A77;\r\n\tMon, 24 Nov 2014 18:40:31 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -2.599\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-2.599 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id aHLBGOHoWXjh; Mon, 24 Nov 2014 18:40:29 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 0CBFD120A64;\r\n\tMon, 24 Nov 2014 18:40:29 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id C2EAD69EA304A;\r\n\tMon, 24 Nov 2014 18:40:28 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id ti-R5CFKPdKr; Mon, 24 Nov 2014 18:40:27 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 7602969EA3043;\r\n\tMon, 24 Nov 2014 18:40:27 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 822B469EA3048\r\n for <caliopdev@caliop.net>; Mon, 24 Nov 2014 18:40:26 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id UQMBMCtmlZMt for <caliopdev@caliop.net>;\r\n Mon, 24 Nov 2014 18:40:22 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 81C5B69EA3043\r\n for <caliopdev@caliop.net>; Mon, 24 Nov 2014 18:40:22 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 05B3B20CD883C; Mon, 24 Nov 2014 18:40:21 +0100 (CET)\r\nDate: Mon, 24 Nov 2014 18:40:21 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141124174020.GK8770@brasil.brainstorm.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: [Caliopdev] [Coin-coin@canapin.com: Petites fautes dans la FAQ]\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n----- Forwarded message from Coin-coin le Canapin <Coin-coin@canapin.com> -=\r\n----\r\n\r\nHello,\r\n=C0 propos de tous les \"Pourrais-je\" de la FAQ ; il me semble que c'est\r\ndu futur, auquel cas on ne met pas de \"s\".\r\n\r\n----- End forwarded message -----\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] [benjamin@sonntag.fr: Fwd: ***UNCHECKED*** Caliopen FAQ] - Laurent Chemla <laurent@brainstorm.fr> - 2014-12-15 1825.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 4C3071216C1;\r\n\tMon, 15 Dec 2014 18:25:19 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.001\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.001 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id szMVebRUNrct; Mon, 15 Dec 2014 18:25:17 +0100 (CET)\r\nReceived: from smtp-out1.octopuce.fr (smtp-out1.octopuce.fr [91.194.60.153])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id ED2D91216BE;\r\n\tMon, 15 Dec 2014 18:25:16 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\tby smtp-out1.octopuce.fr (Postfix) with ESMTPS id D2B8810300169;\r\n\tMon, 15 Dec 2014 18:25:16 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 5644E6B564311;\r\n\tMon, 15 Dec 2014 18:25:15 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 8D8926B564311\r\n for <caliopdev@caliop.net>; Mon, 15 Dec 2014 18:25:13 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 5FCB820C5F42F; Mon, 15 Dec 2014 18:25:13 +0100 (CET)\r\nDate: Mon, 15 Dec 2014 18:25:13 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141215172513.GF23183@brasil.brainstorm.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: [Caliopdev] [benjamin@sonntag.fr: Fwd: ***UNCHECKED*** Caliopen FAQ]\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n----- Forwarded message from Benjamin Sonntag <benjamin@sonntag.fr> -----\r\n\r\nSalut Laurent,\r\n\r\nje te transmet les remarques de greg, avec qui on animera l'atelier =AB pri=\r\nvacy tools in the post-snowden era =BB ;) au 31C3\r\n\r\npour action, aupr=E8s de je ne sais qui.\r\n\r\nbises,\r\n\r\nbs://\r\n\r\n\r\n\r\n-------- Forwarded Message --------\r\nSubject: \t***UNCHECKED*** Caliopen FAQ\r\nDate: \tMon, 15 Dec 2014 18:16:36 +0100 (CET)\r\nFrom: \tGregory Fabre <gfabre@resaction.com>\r\nTo: \tSonntag Benjamin <benjamin@sonntag.fr>\r\n\r\n\r\n\r\n*** Ce truc l=E0 dans la FAQ contredit quelque-chose que tu m'a dit\r\n\r\n> Will I be able to use Caliopen with Outlook or Gmail?\r\n> No, unless you're patient enough to let Microsoft or Gmail adopt Caliopen.\r\n\r\nCa poppera dehors ou pas ?\r\n\r\n*** Par ailleurs\r\n\r\n> Each terminal declared by the user is graded according to its use (e.g. s=\r\ntrictly personal, at home or as a public access within the enterprise), its=\r\n type (a phone is necessarily less secure than a desktop PC, as it is much =\r\neasier to loose). When used for the first time, a non declared terminal rec=\r\neives a note of zero.\r\n\r\nMon iPhone sous iOS 8 est tout chiffr=E9, alors que tout ne l'est pas dans =\r\nmon ordi (je sais c'est mal, mais les choses importantes le sont).\r\nPar ailleurs pour l'ouvrir il faut mon doigt, alors qu'un mot de passe suff=\r\nit pour l'ordi.\r\nEt quand j'ouvre mon t=E9l=E9phone, personne ne voit mon mot de passe du co=\r\nup.\r\n\r\n*** Coquilles\r\n\r\n> In which ways is the human factor taken into account in CaliOpen? (in a d=\r\nevice usage context) De quelle mani=E8re est pris en compte le facteur huma=\r\nin dans l'utilisation de CaliOpen ?\r\n\r\nLa traduction fran=E7aise est accol=E9e =E0 la version anglaise.\r\n\r\n> to give them back to another member of the assiocation in case of failure=\r\n), respect of users' privacy and\r\n\r\n\r\nassociation\r\n\r\n> contact nor of a senders white list (government, know online resellers..)=\r\n, in short a message that will likely have\r\n\r\n\r\nknown\r\n\r\n> your \"right to be offline\" respected), you can only display only messages=\r\n with a lower importance than a\r\n\r\nonline r=E9p=E9t=E9\r\n\r\n> CaliOpen user interface is ment to display any type of private correspond=\r\nance. Even though, for the time being,\r\n\r\n\r\nmeant\r\n\r\n> private messages and so on), we are allready planning to be able to intro=\r\nduce over types of communicatoin\r\n\r\nother pas over\r\n\r\n> access a saticfying confidentiality level. He's private key is then store=\r\nd on its terminal: CaliOpen doesn't own it.\r\n\r\nHis\r\n\r\n> Is sorting message by \"recipient\" instead of \"by thread\" hardcoded in the=\r\n system (and as such not customizable) ? Will it be possible to sort by \"Su=\r\nbject:\" instead of by \"To:\" ?\r\n\r\nPas indiqu=E9 comme question.\r\n\r\n> be labeled \"work\" if the associated contact is labeled himself to \"work\")=\r\n. In Caliopen main interface, clicking on\r\n\r\nis labeled himself to ? Gni ?\r\n\r\n> this service): if Caliopen //... je s=E8che sur MX ?// you will be able t=\r\no create (or use) all the adresses @mydomain\r\n\r\nCe pauvre traducteur ne comprenait pas Mail Exchanger :)\r\n\r\n-- =\r\n\r\nGregory Fabre - @grrr - gpg 61F4D4C5\r\ngfabre@resaction.com - http://resaction.com\r\n\r\n\r\n\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] [contact+caliopen@etiennesamson.com: Intégration] - Laurent Chemla <laurent@brainstorm.fr> - 2014-11-24 1840.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 10B34120A67;\r\n\tMon, 24 Nov 2014 18:41:02 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.876\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.876 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, SUBJECT_ENCODED_TWICE=1.723]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 8K4suTTFJTnb; Mon, 24 Nov 2014 18:41:00 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id AA6B3120A66;\r\n\tMon, 24 Nov 2014 18:41:00 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 8285369EA304A;\r\n\tMon, 24 Nov 2014 18:41:00 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id S1EoNu9yOmRP; Mon, 24 Nov 2014 18:40:57 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 9933069EA3048;\r\n\tMon, 24 Nov 2014 18:40:56 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 9459C69EA3043\r\n for <caliopdev@caliop.net>; Mon, 24 Nov 2014 18:40:55 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id j6BTYtGW1OyB for <caliopdev@caliop.net>;\r\n Mon, 24 Nov 2014 18:40:51 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 9D4B069EA3048\r\n for <caliopdev@caliop.net>; Mon, 24 Nov 2014 18:40:51 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 88F7920CD27F8; Mon, 24 Nov 2014 18:40:51 +0100 (CET)\r\nDate: Mon, 24 Nov 2014 18:40:51 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141124174051.GL8770@brasil.brainstorm.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: [Caliopdev] =?iso-8859-1?q?=5Bcontact+caliopen=40etiennesamson=2E?=\r\n\t=?iso-8859-1?q?com=3A_Int=E9gration=5D?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n----- Forwarded message from =C9tienne Samson <contact+caliopen@etiennesams=\r\non.com> -----\r\nX-Source-Cap: cmh1bWV1cnQ7cmh1bWV1cnQ7Z2F0b3IzMDI0Lmhvc3RnYXRvci5jb20=3D\r\n\r\nSalut !\r\n\r\nPlut=F4t partant pour apporter ma petite pierre au projet.\r\nJe remue mon r=E9seau pour trouver d'autres front motiv=E9s pour contribuer.\r\n\r\nN'h=E9sitez pas =E0 me dire quelles sont vos m=E9thodes, si vous avez une\r\nroadmap, des specs, quelles sont vos habitudes...\r\n\r\nMerci.\r\n--\r\n=C9tienne.\r\n\r\n----- End forwarded message -----\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] [groupeiw@gmail.com: Contacter l'équipe CaliOpen] - Laurent Chemla <laurent@brainstorm.fr> - 2014-11-25 1810.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 34BD4120A31;\r\n\tTue, 25 Nov 2014 18:10:27 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 1.724\r\nX-Spam-Level: *\r\nX-Spam-Status: No, score=1.724 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001, SUBJECT_ENCODED_TWICE=1.723]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 7oZk8oZm6Xu6; Tue, 25 Nov 2014 18:10:26 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 19C0A120A19;\r\n\tTue, 25 Nov 2014 18:10:26 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id E395E69F20B0B;\r\n\tTue, 25 Nov 2014 18:10:25 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id zzUpkcacJEkv; Tue, 25 Nov 2014 18:10:24 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 339E369F20B07;\r\n\tTue, 25 Nov 2014 18:10:24 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 1BFA669F20B08\r\n for <caliopdev@caliop.net>; Tue, 25 Nov 2014 18:10:23 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id ImmLr1Gwq8vv for <caliopdev@caliop.net>;\r\n Tue, 25 Nov 2014 18:10:22 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 2A5F669F20B07\r\n for <caliopdev@caliop.net>; Tue, 25 Nov 2014 18:10:22 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 068CA20BC19EF; Tue, 25 Nov 2014 18:10:21 +0100 (CET)\r\nDate: Tue, 25 Nov 2014 18:10:21 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141125171021.GM8770@brasil.brainstorm.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: [Caliopdev] =?iso-8859-1?q?=5Bgroupeiw=40gmail=2Ecom=3A_Contacter?=\r\n\t=?iso-8859-1?q?_l=27=E9quipe_CaliOpen=5D?=\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n----- Forwarded message from Marc-Andr=E9 Beauchamp <groupeiw@gmail.com> --=\r\n---\r\n\r\nBonjour,\r\n\r\n\r\nSuite au visionnement d'un vid=E9o assez hilarant d'un certain JC \"Quoi\r\nGoogle?\", j'ai connu l'existence de votre service.\r\nJe serais int=E9ress=E9 =E0 le tester, toutefois, je remarque que celui-ci =\r\nn'est\r\npas encore accessible!\r\n\r\nUne p=E9riode de test est-elle pr=E9vu sous peu?\r\nSi vous avez besoin d'aide pour ce qui est de l'aspect social du projet\r\n(r=E9seau sociaux) et promotion/marketing, je peux toujours vous aider.\r\n\r\n???Malgr=E9 que je soit =E9troitement reli=E9 =E0 Google du =E0 mon emploi,=\r\n je reste\r\nquand m=EAme un amoureux des solutions comme la votre qui prone la vie priv=\r\n=E9.\r\nMalheureusement, niveau fonctionnalit=E9, GMail est le seul service qui me =\r\nva.\r\n???\r\n\r\nSinc=E8rement,\r\nMarc-Andr=E9 Beauchamp.???\r\n\r\n----- End forwarded message -----\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] [svetlana.meyer@ensc.fr: Bravo!] - Laurent Chemla <laurent@brainstorm.fr> - 2014-11-26 1244.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id F0473120A32;\r\n\tWed, 26 Nov 2014 12:44:30 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.001\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.001 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id OQV3QDEGhXb4; Wed, 26 Nov 2014 12:44:28 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 817F4120A36;\r\n\tWed, 26 Nov 2014 12:44:28 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 3244869DA5639;\r\n\tWed, 26 Nov 2014 12:44:28 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id Ao36Ho5dk_Nw; Wed, 26 Nov 2014 12:44:24 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id 07EA269DC7264;\r\n\tWed, 26 Nov 2014 12:44:23 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 3128469DC729D\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 12:44:22 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id ZDhM3B1ibcRl for <caliopdev@caliop.net>;\r\n Wed, 26 Nov 2014 12:44:18 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 6054069DC7264\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 12:44:18 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 3C4DD209FB121; Wed, 26 Nov 2014 12:44:18 +0100 (CET)\r\nDate: Wed, 26 Nov 2014 12:44:18 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141126114418.GU8770@brasil.brainstorm.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: [Caliopdev] [svetlana.meyer@ensc.fr: Bravo!]\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n\r\nPfff, faudrait r=E9pondre =E0 tous ces gens, j'ai pas le temps cette semain=\r\ne je\r\ncours partout.\r\n\r\n\r\n----- Forwarded message from Svetlana Meyer <svetlana.meyer@ensc.fr> -----\r\nSubject: Bravo!\r\nFrom: Svetlana Meyer <svetlana.meyer@ensc.fr>\r\nTo: contact@caliopen.org\r\n\r\nBonjour,\r\nJe viens de d=E9couvrir votre projet et je tenais =E0 vous dire qu'il est\r\ng=E9nial. D=E9j=E0, sur le principe : un syst=E8me de messagerie intelligen=\r\nt ET\r\nprot=E9g=E9, c'est vraiment ce dont on a besoin en ce moment.\r\nEt surtout sur l'interface que vous avez cr=E9=E9e : il est rare qu'on atte=\r\nigne\r\nun tel niveau de plaisir d'utilisation sur un simple prototype (quelle\r\nfrustration de ne pouvoir tester toutes les fonctionnalit=E9s. Pour venir\r\nd'une =E9cole d'ing=E9 qui attache une grande importance =E0 la conception\r\ncentr=E9e utilisateur (l'ENSC de Bordeaux), je dois dire qu'en terme de\r\nconception, vous avez assur=E9.\r\nSinc=E8rement bravo ! D=E8s qu'il sort, je m'y met.\r\nJe suis malheureusement trop mauvaise en code pour vous aider =E0 ce niveau,\r\nmais si vous avez besoin de diffuser votre prototype pour avoir des retours\r\nsur son usabilit=E9 ou autre, je le ferais avec plaisir.\r\n\r\nTr=E8s bonne continuation !\r\n\r\n-- =\r\n\r\nSvetlana Meyer, =E9l=E8ve ing=E9nieur, 3=E8me ann=E9e =E0 l'ENSC.\r\n\r\n----- End forwarded message -----\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] [svetlana.meyer@ensc.fr: Re: Bravo!] - Laurent Chemla <laurent@brainstorm.fr> - 2014-11-26 1246.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 98356120A9E;\r\n\tWed, 26 Nov 2014 12:46:30 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: 0.001\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=0.001 tagged_above=-999 required=5\r\n\ttests=[BAYES_50=0.001]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 566gwAeJ5ZPV; Wed, 26 Nov 2014 12:46:28 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 59A5F120A27;\r\n\tWed, 26 Nov 2014 12:46:28 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id C930069DA65D4;\r\n\tWed, 26 Nov 2014 12:46:27 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id oZUv7XJle5jQ; Wed, 26 Nov 2014 12:46:24 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id DAADD69D9A570;\r\n\tWed, 26 Nov 2014 12:46:23 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id 32E8869DA65D4\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 12:46:22 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id uuBA9BpjWtsR for <caliopdev@caliop.net>;\r\n Wed, 26 Nov 2014 12:46:18 +0100 (CET)\r\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\r\n by brassens.heberge.info (Postfix) with ESMTP id 24D0D69D9A570\r\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 12:46:18 +0100 (CET)\r\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\r\n id 065A420B31D5C; Wed, 26 Nov 2014 12:46:17 +0100 (CET)\r\nDate: Wed, 26 Nov 2014 12:46:17 +0100\r\nFrom: Laurent Chemla <laurent@brainstorm.fr>\r\nTo: caliopdev@caliop.net\r\nMessage-ID: <20141126114617.GV8770@brasil.brainstorm.fr>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nUser-Agent: Mutt/1.5.20 (2009-06-14)\r\nSubject: [Caliopdev] [svetlana.meyer@ensc.fr: Re: Bravo!]\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nContent-Transfer-Encoding: quoted-printable\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\n----- Forwarded message from Svetlana Meyer <svetlana.meyer@ensc.fr> -----\r\n\r\nLe 26 novembre 2014 12:34, Svetlana Meyer <svetlana.meyer@ensc.fr> a =E9cri=\r\nt :\r\n\r\n> Bonjour,\r\n> Je viens de d=E9couvrir votre projet et je tenais =E0 vous dire qu'il est\r\n> g=E9nial. D=E9j=E0, sur le principe : un syst=E8me de messagerie intellig=\r\nent ET\r\n> prot=E9g=E9, c'est vraiment ce dont on a besoin en ce moment.\r\n> Et surtout sur l'interface que vous avez cr=E9=E9e : il est rare qu'on\r\n> atteigne un tel niveau de plaisir d'utilisation sur un simple prototype\r\n> (quelle frustration de ne pouvoir tester toutes les fonctionnalit=E9s. Po=\r\nur\r\n> venir d'une =E9cole d'ing=E9 qui attache une grande importance =E0 la con=\r\nception\r\n> centr=E9e utilisateur (l'ENSC de Bordeaux), je dois dire qu'en terme de\r\n> conception, vous avez assur=E9.\r\n> Sinc=E8rement bravo ! D=E8s qu'il sort, je m'y met.\r\n> Je suis malheureusement trop mauvaise en code pour vous aider =E0 ce nive=\r\nau,\r\n> mais si vous avez besoin de diffuser votre prototype pour avoir des retou=\r\nrs\r\n> sur son usabilit=E9 ou autre, je le ferais avec plaisir.\r\n>\r\n> Tr=E8s bonne continuation !\r\n>\r\n> --\r\n> Svetlana Meyer, =E9l=E8ve ing=E9nieur, 3=E8me ann=E9e =E0 l'ENSC.\r\n>\r\n\r\n\r\n\r\n-- =\r\n\r\nSvetlana Meyer, =E9l=E8ve ing=E9nieur, 3=E8me ann=E9e =E0 l'ENSC.\r\n\r\n----- End forwarded message -----\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/dev@caliopen.local/[Caliopdev] matrice de choix framework JS - Aymeric Barantal <mric@chamal.fr> - 2014-11-24 1146.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\r\nX-Original-To: dev@caliopen.local\r\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\r\n\tby mail4.gandi.net (Postfix) with ESMTP id 6766A120A62;\r\n\tMon, 24 Nov 2014 11:46:32 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\r\nX-Spam-Score: -0.908\r\nX-Spam-Level: \r\nX-Spam-Status: No, score=-0.908 tagged_above=-999 required=5\r\n\ttests=[BAYES_00=-2.599, RATWARE_GECKO_BUILD=1.691]\r\nReceived: from mail4.gandi.net ([217.70.183.210])\r\n\tby localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new, port 10024)\r\n\twith ESMTP id 67tVRPPRjqFI; Mon, 24 Nov 2014 11:46:30 +0100 (CET)\r\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\r\n\t(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n\t(No client certificate requested)\r\n\tby mail4.gandi.net (Postfix) with ESMTPS id 2402C120A36;\r\n\tMon, 24 Nov 2014 11:46:30 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id D78E169D8C4A3;\r\n\tMon, 24 Nov 2014 11:46:29 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n\tby localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n\twith ESMTP id kFqbC8mQr7ls; Mon, 24 Nov 2014 11:46:26 +0100 (CET)\r\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\r\n\tby brassens.heberge.info (Postfix) with ESMTP id AAE6B69D8C4A2;\r\n\tMon, 24 Nov 2014 11:46:25 +0100 (CET)\r\nReceived: from localhost (localhost [127.0.0.1])\r\n by brassens.heberge.info (Postfix) with ESMTP id D7EEF69D8B744\r\n for <caliopdev@caliop.net>; Mon, 24 Nov 2014 11:46:23 +0100 (CET)\r\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\r\nReceived: from brassens.heberge.info ([91.194.60.2])\r\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\r\n with ESMTP id wvGLKNrTNNAO for <caliopdev@caliop.net>;\r\n Mon, 24 Nov 2014 11:46:22 +0100 (CET)\r\nReceived: from relay3-d.mail.gandi.net (relay3-d.mail.gandi.net\r\n [217.70.183.195])\r\n (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\r\n (No client certificate requested)\r\n by brassens.heberge.info (Postfix) with ESMTPS id B1FED682D8EBE\r\n for <caliopdev@caliop.net>; Mon, 24 Nov 2014 11:46:22 +0100 (CET)\r\nReceived: from [IPv6:2001:4b98:beef:a:95c0:cb01:68a3:42c4] (unknown\r\n [IPv6:2001:4b98:beef:a:95c0:cb01:68a3:42c4])\r\n (Authenticated sender: mric@chamal.fr)\r\n by relay3-d.mail.gandi.net (Postfix) with ESMTPSA id 6BCB8A81C1\r\n for <caliopdev@caliop.net>; Mon, 24 Nov 2014 11:46:22 +0100 (CET)\r\nMessage-ID: <54730C85.5050503@chamal.fr>\r\nDate: Mon, 24 Nov 2014 11:46:29 +0100\r\nFrom: Aymeric Barantal <mric@chamal.fr>\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64;\r\n rv:31.0) Gecko/20100101 Thunderbird/31.2.0\r\nMIME-Version: 1.0\r\nTo: =?UTF-8?B?TGlzdGUgZGUgZMOpdmVsb3BwZW1lbnQgQ2FsaW9wZW4=?=\r\n <caliopdev@caliop.net>\r\nContent-Type: multipart/mixed; boundary=\"------------000403030901080504090908\"\r\nSubject: [Caliopdev] matrice de choix framework JS\r\nX-BeenThere: caliopdev@caliop.net\r\nX-Mailman-Version: 2.1.15\r\nPrecedence: list\r\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev@caliop.net>\r\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\r\n <caliopdev.caliop.net>\r\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\r\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\r\nList-Post: <mailto:caliopdev@caliop.net>\r\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\r\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>, \r\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\r\nErrors-To: caliopdev-bounces@caliop.net\r\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------000403030901080504090908\r\nContent-Type: text/plain; charset=utf-8; format=flowed\r\nContent-Transfer-Encoding: 7bit\r\n\r\nSalut,\r\n\r\nVoici la matrice d'Alban un peu allegee et au format odt\r\npour nous aider dans le choix d'un framework JS parmi\r\nles 3 retenus:\r\n\r\n- angular\r\n- ember\r\n- react\r\n\r\nS'il vous est possible de jouer le jeu en mettant des notes\r\nsur les differentes categories qui sont decrites sur le pad\r\nhttps://pad.caliopen.org/p/TechnoFront\r\n\r\nL'idee etant de pouvoir se faire une idee de quel framework se\r\ndemarque par cette methode pour la fin de cette semaine.\r\n\r\n++ Chamal\r\n\r\n\r\n\r\n\r\n--------------000403030901080504090908\r\nContent-Type: application/vnd.oasis.opendocument.spreadsheet;\r\n name=\"matrice_choix_fw_js.ods\"\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment;\r\n filename=\"matrice_choix_fw_js.ods\"\r\n\r\nUEsDBBQAAAgAAEBVeEWFbDmKLgAAAC4AAAAIAAAAbWltZXR5cGVhcHBsaWNhdGlvbi92bmQu\r\nb2FzaXMub3BlbmRvY3VtZW50LnNwcmVhZHNoZWV0UEsDBBQAAAgAAEBVeEX7VxOklgIAAJYC\r\nAAAIAAAAbWV0YS54bWw8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCI/Pgo8\r\nb2ZmaWNlOmRvY3VtZW50LW1ldGEgeG1sbnM6b2ZmaWNlPSJ1cm46b2FzaXM6bmFtZXM6dGM6\r\nb3BlbmRvY3VtZW50OnhtbG5zOm9mZmljZToxLjAiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3\r\nLnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1l\r\nbnRzLzEuMS8iIHhtbG5zOm1ldGE9InVybjpvYXNpczpuYW1lczp0YzpvcGVuZG9jdW1lbnQ6\r\neG1sbnM6bWV0YToxLjAiIHhtbG5zOm9vbz0iaHR0cDovL29wZW5vZmZpY2Uub3JnLzIwMDQv\r\nb2ZmaWNlIiB4bWxuczpncmRkbD0iaHR0cDovL3d3dy53My5vcmcvMjAwMy9nL2RhdGEtdmll\r\ndyMiIG9mZmljZTp2ZXJzaW9uPSIxLjIiPjxvZmZpY2U6bWV0YT48ZGM6ZGF0ZT4yMDE0LTEx\r\nLTI0VDExOjQyOjAwPC9kYzpkYXRlPjxtZXRhOmRvY3VtZW50LXN0YXRpc3RpYyBtZXRhOnRh\r\nYmxlLWNvdW50PSIxIiBtZXRhOmNlbGwtY291bnQ9IjE0IiBtZXRhOm9iamVjdC1jb3VudD0i\r\nMCIvPjxtZXRhOmdlbmVyYXRvcj5MaWJyZU9mZmljZS8zLjUkTGludXhfWDg2XzY0IExpYnJl\r\nT2ZmaWNlX3Byb2plY3QvMzUwbTEkQnVpbGQtMjwvbWV0YTpnZW5lcmF0b3I+PC9vZmZpY2U6\r\nbWV0YT48L29mZmljZTpkb2N1bWVudC1tZXRhPlBLAwQUAAgICABAVXhFAAAAAAAAAAAAAAAA\r\nDAAAAHNldHRpbmdzLnhtbO1Z32/aMBB+31+B8k4D6cbWqDBRpq3durYitPvxZpIDrDq+yHZI\r\n6V8/JyEVTZM2QLKp0p6A2P7u43z+7s45/njns9YShKTI+0b3oGO0gLvoUT7vG9eTz+0PxsfB\r\nm2OczagLtodu6ANXbQlK6SmypZdzaafDfSMU3EYiqbQ58UHayrUxAJ4tszdn24mx9Mkdo/y2\r\nbyyUCmzTjKLoIDo8QDE3u0dHR2Yymk11kc/ovKqpdPamKUR8MBQvSMkkxqxO562Z/jZaa5Ib\r\nrrGMQeaH7O8PjtcG0o82VeDHvmmtH8fU+oY2aS8pRA9eM4rWPV5zQyWdMhgKIBMMjGxQrQI9\r\nSLkyBp1j8ynIVsDnMFPNIP+gnloUQVtWp7c//CnQ+aKQeve91asK3/ZJ0Kbcgzvw8qYgKt6k\r\nZI0OL7GqQhiiMy/HUiqhI8AYxPHQ3YppDJrjOSHaIS8RfbzEWQCoboUAHIVCorhCSZWO/581\r\nxslj5F9FyO92Qz5FQe+RK8KcgFH1HT3Iu3+BYo8IB6Go2xR6jn3moDpP6Cb/BvCHrqJLSNDH\r\nhM9L3GPtBp7xrVm1MthxmaTsiVuvemeoJ6gU+jUC/0b0Jxql1oiOQW8IC/OoqVR3dvUBmUOs\r\nrc+i75plnAVGXwTNy/YUkQHhxkCJEIqRCx5uZouy4UTWqx6tRPBLMkqq7HtLjyuQsSkR5Sn8\r\n/f8Q2zvEYgMnupK5vRIQlwJl4TYjTJbEWxUzv0Fgwl9uH89V8C9QNQW92yF8CTlGHSFDkYNm\r\nGB+hrtU7tKx3lSvIZ/a1Aa+cEqmphz4fY3QKxNOdSSNGEiHRStMA+pm8DJXu5sBZ+VNk0oF8\r\nyq3FiMNJMMExkQryG13HyUqBz+S6I2nMwhik3u/S8ru7sxLn4Qtr8H3hnXDq0SWVpfRrAi8m\r\nv2vopPDDOyqdFXcXAjm9h79XDazb0eIJElT1C4f0QShIvMHb3Dw0nTfOkXhjLV/I2aqBo3Md\r\neETBZ6FrDfADpr+/riwyZEynEV3Sqa84HRHuAnu1EvkaJWAYKhwR5obNhE6ys5o8qDDfF06J\r\nhN7bE8qJWBnmPy3AzuSn9T2qsyCiXP/2iJBmC421my/0j+J2qYJ/zym/TdWk/KrncFf/fgPB\r\nh5ISfhVyV4Wk4CamliaALOEmvcK+5COGsrlqpcl6aBgEbHUtQXwiitQfLa+73PorRXXTzU1z\r\nLd9IKxhx9QaM0A8EyPgs1H4NUV/hWEE39+8CS6tL88kLLrPs1d/gD1BLBwgXFYU+rgMAADwc\r\nAABQSwMEFAAICAgAQFV4RQAAAAAAAAAAAAAAAAsAAABjb250ZW50LnhtbO1c3W7bNhS+31MY\r\nKjBsF7Is2Y5lL06xYSswoNlFkw4Dhl3QEm1zpUSBpGxnb9Tn6IuNpH5COaIrJ7HqtMpFWvMc\r\nHn485/AjDyP58vUuwr0NpAyReG65/YHVg3FAQhSv5tb72ze2b72++u6SLJcogLOQBGkEY24H\r\nJObi357oHbNZJp1bKY1nBDDEZjGIIJvxYEYSGBe9Zrr2TI2VtTB+hxt3V8p6bw53vGlnqVvp\r\nCxbNR1bKeu+Qgm3TzlJXOFXvviRNO+8YtpdEeD1KAEd7KHYYxR/m1przZOY42+22vx32CV05\r\n7nQ6dZS0BByUeklKsdIKAwdiKAdjjtt3nUI3ghw0xSd1dUhxGi0gbewawMGDqCYUMqEipisT\r\ns5khvU8lvzarxtm1WRncHKwBbZxnSrmaKsOweaoMQ71vBPjaEF/fuRZC9ev67X1e0ajpWFK3\r\n4qqAoqTxNDNtvT8hpIQqO2SLXcH1BoORk33WtLcH1bcUcUg19eCgegBwUHqcRHVOE3quIzRs\r\nuJEpXy4i6Qhm6OA5mbhUZqHR9F/Xb2+CNYzAvTL6vLKNYsZBfO8ZKoNgnOnYoTAhlJeOWTYn\r\nXxEtr8S25hE2U4eUFqorGoa1qgLO0BE0IhaxvUFw+6rCrYfzYeoopTJxEcTFKil18+nAXQIp\r\nkjMBWCaCHTHhNJEcJJlpvav8SqNdM3MyIUi43Le4tzgCxoa8zge37xwps+X2Igg0H0nbVj3r\r\nqthDszXDnLJhKfZSewkCaIcwwOzqMuPCsrmXfZa459btmtA4BNJngqoKpQjhu6pMNyGl9grG\r\nYsJi+VASgbiikSAeCILZAIpUNJzDEN4iwe2KY3s3IGY1QL4HCWE/7elljQehsS1i7CnQfoX/\r\ngj/Tw7A0nSaQ7hiH0VMwvaEQGgBpoqcicEzJlLeDlIu4cxTYyk6ZZep3BW9A3HKwHKdapGJb\r\nxGkUW0VPvdFOxKKBlCPIeksyW1AIPtgLKNaPMCiHLizm6lsUyi1t0J/6Ux/FagIankPgvPbA\r\n+a7vHgdu2B64iTvwjgM3atFznj88Dty4LXDiiOt7k+PAXbS4ICbHem7SYlin3vQ4cH6L4Pwj\r\nsU3bwzYeTI5kEnfQFrphf+RPRkeia22LmPQnnnfkFuG2tkcM+xeD8fgYdNS0u1Ky3UMmWnRY\r\nmUg2riFarbnMK3cyvhCjH4acMmiThKMIYFvvzmkKjwFu8OrjgF+orbUd4IZd+ZEe91vCzUF9\r\nphSNERDnQmonYAXt4vS7BCnme5PSJpRdYIWIJRjc5Xhya7LQRvHKjkgoLGFq88VDqNm1jrpE\r\nsx8C/sMdDKyqUn5NKA096HtgBUMTvUCMC0kCqLyIVB/25p9rqIpUl+cAK6tfWNxf+4SGwq8L\r\nwrm8QRj0BxcJ7zGCUdh75Q/kTxZ/EHxYUZLGoeQQQufWq4U/mozK/ThEYEVikQMLbHMhjkkM\r\nH8i4EO/JlIsARqvYZiSl8mJ1A3AKbX6XlEqi/oegdPDcWgLMoMK1pSBR6Scrz5jY8rOlTQzD\r\npcrk6bR2WgkIs8tfMXF3WCZ71pcWy6DOJ8Vyye7fbBCv5NVqjUBOrZjyvW1OEhOszMBGxigQ\r\nPssNlCXNfQWmXFcNZxGbpfqp+JikHKMYls7TRLLd5msR3tU6y6FqjFSZ9aAiF8MpAUP/CYHr\r\nJVxrK4xQQQuqOVsKaxDKO7AHCSDyClIFozJ8YW6bc8qC4LACSo5tA4ZAXCDQhWo15NICiibP\r\nrBYK9bbl7SiGO6P1Um62X6roI2Qh2dRNuk4hj6syq/5/xAkBmg4IZ84v1Rzu+OV4ftH98DQ+\r\nGVRQfTV8UrNon5FRTNafk1OqKm2yiun254uwimH5daxyklOL2XLHMh3LPCvLmK5xz/zs8oJq\r\nozJnvzSrdLVQVwudnE9Mf3l5Ip90ldB5sUlX+XRnkpNxiOkPpF+AQ77KuudMOKSrczpOaY1T\r\nTM81dHXO892mGKbVjGXM1UlX93R1z9nXPaZHk8667oGLC3cYduxyDnVQxyYdm5RsYnqY8Kwr\r\noI5Nzrci6tjlm2MXx/iuRi5YkPDu/j2ihEIQsjWE/Ooye7JP/c6f8suCfSPFrpW36dQiHy+s\r\n9MufhK1RVW+E5M8OZoSV8VAdkzmNbXonsDk8gc3RCWyOT2Dz4gQ2Jyew6Rc2sydCc1VmZzsE\r\nDOfWKTJjegKb8hn95zd6irV2GqSPjhMl2xqL8tH4vcGFvbqh5fPBxTuW5UljbjFOxUlA2pBv\r\n+SdXb6hQ3xL64dLJWy6dffMNBxw1GvCG0zTgKYXtDQgWCCP+6WNbI75nkPYWgLU3xQQGrDfr\r\n0U8fGQRpr+1xGQzStscECXqWIT/Ls/uL3WRr0gj+rTgx48cANyIcqUPSHnU04xKvKZd4jeb2\r\nc7xKMaBPD8v4s2HxG4el3Erli+oC3twiy9n85v31D3/3f/Fm/d+9f36snd0SE8CrEln5lLMd\r\nfKVRfAdBwF9QDIcihsNvJobDRjH8TSJ8egwvnjGG00MxHIkYjl5+DKvGhZ5ueWoOsRGNe5qU\r\nOtF4B6fvDkb+ePySnaC3FFblOKENd/JbleT3mDDtayYqlw9O5Xqi+LT/XWFX/wNQSwcIp6yt\r\nCJwHAABsTAAAUEsDBBQAAAgAAEBVeEXNi0ERfA0AAHwNAAAYAAAAVGh1bWJuYWlscy90aHVt\r\nYm5haWwucG5niVBORw0KGgoAAAANSUhEUgAAAK8AAAEACAIAAABKzbGtAAANQ0lEQVR4nO3b\r\nC1hUdR7G8QEHEVC8SwNKGawiaGobWAuuYV6yx0tUaj1p5iUfr6WSVojPiFJSKqbSeinFSyWY\r\nZboqWvjommW5u+mmgooXBC94QcS8oKDsj/OP8w4zA5IbTGvv5/GxM8O5/OfMd845cyTj5u7d\r\nH3rzTdPjjxtKbXnySUMV837iiTYTJ95xWzd8fLq/917NevVu5OX9Y8CAqh7Vr9267asImT27\r\nflCQmpYn5aFM7I6MtFyq7eTJ++Pjb12/brW2Br17B48aJRNntm+v4era5LHHsjZsSE9IuOM4\r\nK7kzK/b9li3m4mKjTP0UF6dqSI2I6LJ27V2vsfJOb92qXkDFus2e7Vqv3o6XXnqgb1956Pfi\r\ni/4DB1byNf+qme9u67+8iuJi/Rn1xnffvNnyodWC/3n7bbub2zR6tKpB3g61ksqkYHcYd81o\r\n+UCCTUtIkHEUXbuWkZj4p0GDbhcWunh63i4qupqd7envLw+vZGXJhgsvX64XFHRi7dq8AweC\r\nxo41enic++47765dZSV7Y2PP7tzp26tXq9Gj/xUVZXR3bzVqlJOT08/Hjzd8+GF9zWqLLnXq\r\nPP7pp/Lhk51uOzjZnPzt+/TThz780LdPH9mnhtJ9Leu5kplZLzBQbbFddLTsbvk8BY0fL0sd\r\nX73acmZ9Hnl1gWPGqDHI8ArOnZORpy9cePrrryu59b+uWFHDxUXtkyMrVsgz8iaEJycf/eST\r\noqtXLRPXP7KKLJg2f77aV+Vt0ZbsxhavvFJ46dKB0mXljagbEKDvxq3PPafm1IeRtX59+KpV\r\n+ghv37xptYYKtl6mBtl3sk9lf8lbmH/4sBypMpYtk0NleFKSp5+fzODs4rI3Jqbj8uU7Bg3q\r\ntGJF8/79vU6frtWkyc7hwx+JjS3Iza3VsKHs4ifWrDm0dGnBhQu5P/7412XLXBs0+GHChDav\r\nv16yvdI1P/j886qGY0lJ8hrsDu7rSZP6JCY+8Mwzhfn5x5KTW40cadCOhzJOWc/BRYsenTtX\r\nnpH41Pwthg2r6el5ZPnyrHXrLGfW58nesEF/dfJQH7ndvWN366patU9aDhsm0xL67okTQ2bN\r\n2tavn+Xbb3X8kwXDliypeIu2/AcNSp8/393bO2DECH1Zy92oz6kPQ2rY9sIL+ggLr161XUOl\r\narA8DOYfOiR/Huzf30+dMp2c1PPXz52TDctbrkbg1qSJTIQtXiwHjLPfflsrLMzUubNTjRqN\r\nQ0Ly09PlR66NGpWs7fDhWo0bl1mzVkN7szln+/by9sXeZcuaBwQ89MYb8tGU98Pqp7ISq2fU\r\nYCSv8lZouax89CVuNfJftXXbfXLt1Cmjm9sdN2q5r+zOUFxc7KQpWbM2z/Hk5NaRkUdXrbJd\r\nVu1Gy8X1YViOsII12DKW9wOleb9+3w4f3jExsbwZrufkeDRrtnPoUDkwNOvZ0ysszKdrV8lC\r\nJo6sXCkz3Dh/3s1kqtuihRwq3E0mq8V/mjEjJD4+b//+i/v22a789ZycPVOmqN2kPymHE31a\r\n7T4JXz28ceGCbMu1cWMVq5rZah67I7f70srbuu0+kZXLAby8XaQveMctXsvO9vD1bdCuncHZ\r\n+erJk/KMs6vrvydPliItl+3y5Zd2F9eHYTlCu2sob5AlNQS++qp60HHJkszSq8imPXqcTEmR\r\nQ2vwrFlylpJDk3pevoDI3+203SQupaXJkSD0o49KjlTaRZOTs7PU4N2ly6WDB+Xh4aVLW40Z\r\n037KlJ9PnFA1yJrlFKsWr928ubPR2H7atK0REXb2Tm5u6IIFMvpDCxfKQwn8wb59O3/2mT5C\r\nuT54ICJCxi9vmCk8PGP58oBRo/4cGyv7Qs38l4ULLedRF/lq2fQPPpAn1cjtXmza3bqs0Gqf\r\n3CooeCQubt/MmT7dulku3iE+Xk3IVVHNOnVkwf0zZ1a8xf3vvy9XMw9PmyafnP3a4t7h4fLW\r\nHlq0SD73+rKWb5Cavnz0qIePjxqGQTsh6iP0HzDg1s2bVmso7+K6pIa0efPkj/5UtsXZ6HBi\r\n4mGbA4O66LVL34w+kfPNN/LHds6cHTvUxJlt28pb29+Cgh7t3l1/KBcE8sdqnoyywztTet6x\r\nnDmjnGPbN0OGlLfpirduu0+UU199VcEK77hF+WjtGj26zPxDh1ZmWaul7L5rd9y64Y5nCvpD\r\nMX6/ZYua+sFgSDUYehoMbSu98EaD4Z8Gw9SqGZmiD88hqnnrjn2xwmguvUR66qmnJnfocODA\r\nAfPq1XZnzc3NLSgo8PHx0Z8xa18rzL/FfQ+7Yqpy5b+3rTv8xRr0M4W8zXv27Pn8889btmw5\r\nb968yMjIIUOGrFy5csKECbGxse+++67ZbPby8nJ1dc3IyJDLsSlTpsjz6mK7UaNGly9fljmb\r\nNm0qCw4cOPCLL764ePGio14Y3bVfati2bVtoaKibm1twcHDr1q2LiooWLVokb3BgYKC869LH\r\nrl27ZIZr166pa9rp06fHardBxIULF+S9b9iwocQxduxY+VFmZqajXg/9L36pISUlRQ4M6p2+\r\n//771ZNynLh9+7ZMjBgxokuXLu+8847dVchxQg4e+kOTyZSamlq1o6aqgRrkFODv73/s2LEn\r\nbb6MyoEhKyvLw8NDpmvUqHH06NFG2h1GZf78+Xv37g0q/bc7+v9VUsPEiROPHDkSHR2dlJQU\r\nFRUlWciT8fHx7du3l4kFCxb07dvX09NTTgRyFvj444/lje/QoYPRaFTHkvz8/Geffdbd3T0g\r\nIEAeyslC+nDoi6K7VFLDTI16nKTRf6yuE0NCQg4ePHjlypVu3bqdP3/+ee2fGOjeU1LDyy+/\r\nvFy7y3bfffctXry4V69eVjPJRUObNm3k7KBH89Zbb82YMaOax0pVzShfNJtrU2aD4XROzku9\r\ne4+zmclV3n75z6lTJwYPjhk8WCaXGAy1yr8//RuKKb0t7xDVvHXHvliDfvdpqpPT1OLi3bt3\r\n/71PH/OZM+vWrRs6dOicOXPGjx+v7iXExcUlJyePHDlSzh0yfX7EiDl16166dKlKB+fwGzJ/\r\n0LtPBu2uYs2aNZcskY+9YdKkSZs3b46IiNDvJUgBcnZYs2ZNYGCgnFDkO2dVp0DVDzXIh75H\r\njx5t25b8M0VmZmZwcLCzs7PlvYSTJ0927NjRxcXFMSOlqlfm3zBjYmLk26YcFby8vDZt2uTn\r\n52cymfR7Cd7e3tu3bw8LC3PTfsEmLy+vfv36jhk1VY2SGkZr/zouXxxSU1PlZBEdHS0nhdDQ\r\n0E6dOkVFRal7CQkJCdJKv379pIydO3dOnz49ICDg7Nmzjh4//ZZKavhAox6vX79eTYzUfinU\r\noF1D6HMPKv1d52hN9Q2TqgV/24WgzN0npdjme47ZbJ42bZrt83SPwd2nqdr/obHJ3j0QZ+1v\r\nh9wb4d2n6oS7T2qi/ty548aN8/T0lEvIHTt2yIXCXO3/YJEZ5tStK98/ExMT9+3bp+5N5efn\r\nV+mvtzj8hswf+u6TQTtNSA3p6em+vr7Z2dlNmzZVNRi0+w3ybaJnz563bt1S96ZkBv56y72k\r\nzN0nfdrb21vecpPJpH7bRaldu3bnzp2PHz8uT6p7U+p5/nrLPcP6O4V81subtbCwMCUlpUWL\r\nFnl5eereVBWPjapbSQ2DtX+WdLK4hNm4caP+t5wpXnvttVatWrm7uzdr1kyuG9LS0tS9qW7a\r\n/13EX2+5Z5TUkKix+oE6ceinDylA/5F0oN+bGjNmTHUMk6oF7z4RGB3+HbdivN9QnYwO/I5L\r\nvzc8UxCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAa\r\nCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGA\r\nNRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgD\r\nAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCw\r\nBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsg\r\nYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLW\r\nQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0E\r\nrIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAa\r\nCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGA\r\nNRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQMAaCFgD\r\nAWsgYA0ErIGANRCwBgLWQMAaCFgDAWsgYA0ErIGANRCwBgLWQPBfnZa5BKMv84MAAAAASUVO\r\nRK5CYIJQSwMEFAAACAAAQFV4RQAAAAAAAAAAAAAAAB8AAABDb25maWd1cmF0aW9uczIvaW1h\r\nZ2VzL0JpdG1hcHMvUEsDBBQAAAgAAEBVeEUAAAAAAAAAAAAAAAAaAAAAQ29uZmlndXJhdGlv\r\nbnMyL3BvcHVwbWVudS9QSwMEFAAACAAAQFV4RQAAAAAAAAAAAAAAABoAAABDb25maWd1cmF0\r\naW9uczIvdG9vbHBhbmVsL1BLAwQUAAAIAABAVXhFAAAAAAAAAAAAAAAAGgAAAENvbmZpZ3Vy\r\nYXRpb25zMi9zdGF0dXNiYXIvUEsDBBQAAAgAAEBVeEUAAAAAAAAAAAAAAAAcAAAAQ29uZmln\r\ndXJhdGlvbnMyL3Byb2dyZXNzYmFyL1BLAwQUAAAIAABAVXhFAAAAAAAAAAAAAAAAGAAAAENv\r\nbmZpZ3VyYXRpb25zMi90b29sYmFyL1BLAwQUAAAIAABAVXhFAAAAAAAAAAAAAAAAGAAAAENv\r\nbmZpZ3VyYXRpb25zMi9mbG9hdGVyL1BLAwQUAAAIAABAVXhFAAAAAAAAAAAAAAAAGAAAAENv\r\nbmZpZ3VyYXRpb25zMi9tZW51YmFyL1BLAwQUAAgICABAVXhFAAAAAAAAAAAAAAAAJwAAAENv\r\nbmZpZ3VyYXRpb25zMi9hY2NlbGVyYXRvci9jdXJyZW50LnhtbAMAUEsHCAAAAAACAAAAAAAA\r\nAFBLAwQUAAgICABAVXhFAAAAAAAAAAAAAAAACgAAAHN0eWxlcy54bWzdWG1v2zYQ/r5fYSjA\r\nsAGTKTnJFnuO82FFsQFNMKTpvtMSJXOlSIGkbKe/fkdKlPXqqM2GbkuCJCKfOz587ng8eX13\r\nzNhsT6Sigt964TzwZoRHIqY8vfU+PL31b7y7zTdrkSQ0IqtYREVGuPaVfmZEzcCYq1U5eesV\r\nkq8EVlStOM6IWuloJXLCndGqiV7ZpcoR62yquQU3rTU56qnGBtuyxdvpK1tw0zqW+DDV2GBB\r\n06Z5IqYaHxXzE+FHIsuxph0WR0b5x1tvp3W+QuhwOMwPl3MhUxQul0tkZ2vCUY3LC8ksKo4Q\r\nYcQsplA4D5HDZkTjqfwMtkmJF9mWyMnSYI17Uc0lUQCB7Zq8nOaoadPKr306Obv26YjM0Q7L\r\nyXlmwe1UuYynp8pl3LTNsN6NxPcG3cOk/XX/7pRXMpu6lsG2pIokzSdvs0Q37YUQNVVjUB52\r\nS3cRBFeofG6gD2fhB0k1kQ14dBYeYRbViotsSDTAhQgQPtmblHdoaTY96vkaSZILqWsiyfRi\r\nB+os6qO60xkbP6pm1kFTGceDUKBzieDYwqHx95QcLlq17Lz+S2RBdUordamH1nh6RGbON+US\r\nCkJVsRu3xMLbuCshEXAdJDgifkwipjbr8ijXw7Py2Yh06z3thOQxNhTgpDlQRtlze67pwsz6\r\nKeFEUoi+FBnmLUROdQTnY48ltZtD5ym8o1CabImYvcdcDRD5FudC/dzBlYNnqakDVeo11N6Q\r\nP/EfxXlaDcwUSs9Kk+w1nN5KQkYINaZeywCNJVM1XrYbjmlMElywqglxnitSNsH9iDDmOXiO\r\nJU4lznd+LuFMSE2hcymnAA1eRO7HVGnMTRMTzK8pP6lijkDfzvIcyadErBjmaYFTmCXcDkSi\r\n4FoCvQ/vva4LH4oI5t3oW4zz4yCfdm6mcugmfnnouzXXGCPH845r0I52XddTvz3YEA1ov1mX\r\nN3114bcCUqrzEHgd0Kx6yij3KdckBbuYplQrKCx2oQGfLhr9Bd6UfLyX0+CFSJ72W+f1adcv\r\nkHgk6jyHWZ2JdfPst/mPsYTksRSrHplqzGjk/FlwwWMiocsjDqMEo/EI5EBj00zgQosRRCSY\r\ngNbNLmr/92oOB0LTHVwYW8HizxVn8UXqVMKice+/EmzeVf4e7WujgUphVALtU+4rUUhTKRJ6\r\ndM6hPSDY6AUJzUGhBDPVKK2DFQhUPTm99SIwhFZntPDUiUA/wdrhj7n2xpLjdfGqFA2/SFIX\r\njimSSlG26j4UI0N/GfRJos4FUD2a/IW2mEZ++2bITU1j+FkUurWn+zwLvQFQn5TpOGEDfiZi\r\nsGPS19tTTHawu25JqsYSISB8nYCZGrerghDMF8vrS1reBxmWKcwxkpiZ9qCs8O3RrdDadLTB\r\nPFjeXJU3FBpnVdH5GkzhOh2k2aaEerGYEMPF/yyGZm4rZGzelRfzq2UOOzbFe3YR2C8LyHFc\r\nfhoDZsFPoTPD0cdUwlUdu4p9EQXmu5aogaAZyNVLmN52/xsZ9W8V7eX8RqPVq5rIsKpd1DWt\r\nGjSeznU+zRMxUPRK+pu1/QQsr/6qHSElenN3d7dG3cFqJO+I0Am/iWRFAtroHDj0LsBSm3r1\r\n381eqgdDu2z1NqFbrzHWo+BctUQ/SwH1dHxJ2sfqbf+Msouesq4TSM2VZgh9ptiz70qcppo1\r\nIeXz9z0hWiu1huwp6qweY+32aD86aF7aD9Cc1SB/j1kBg4sgvPLD0F9ceZsgQPYnCCoWBrj5\r\nYeYIA/sgWNmfmvRQ+rT5fbWcmqEm0L7vbJbLJrAc+4dyDw2fdjT8GfvmL1BLBwhe/qvrSAUA\r\nAKMXAABQSwMEFAAICAgAQFV4RQAAAAAAAAAAAAAAABUAAABNRVRBLUlORi9tYW5pZmVzdC54\r\nbWytU8FqwzAMvfcrgu+xt56GadrDYF/QfYDnKKnBlo0ll/bvlxTadIyMhvUmWdJ7Tzx5szsF\r\nXx0hk4vYiFf5IipAG1uHfSM+9x/1m9htV5tg0HVArK9BNcwh3dJGlIw6GnKk0QQgzVbHBNhG\r\nWwIg65/9+sJ0y+4ErMV2VU18nfNQD/P5PHV3xfs6GT40Qs2BTM8BWmdqPidohEnJO2t4aFNH\r\nbOVFsLzXKSllMC0dAFioJVICsJHDljPUDCdWY3kRKAHzYAU9HdhG5HHdZ+PuDyV8oXGeFF9D\r\nmbCfIXHB9KDG+iKW94id60u+OElrZawFD0Mas7Il5783+x/Xg6dFBUcJsjhp7xEW+s9nD4+6\r\nv1G/vun2G1BLBwhoBgVrEwEAAOEDAABQSwECFAAUAAAIAABAVXhFhWw5ii4AAAAuAAAACAAA\r\nAAAAAAAAAAAAAAAAAAAAbWltZXR5cGVQSwECFAAUAAAIAABAVXhF+1cTpJYCAACWAgAACAAA\r\nAAAAAAAAAAAAAABUAAAAbWV0YS54bWxQSwECFAAUAAgICABAVXhFFxWFPq4DAAA8HAAADAAA\r\nAAAAAAAAAAAAAAAQAwAAc2V0dGluZ3MueG1sUEsBAhQAFAAICAgAQFV4RaesrQicBwAAbEwA\r\nAAsAAAAAAAAAAAAAAAAA+AYAAGNvbnRlbnQueG1sUEsBAhQAFAAACAAAQFV4Rc2LQRF8DQAA\r\nfA0AABgAAAAAAAAAAAAAAAAAzQ4AAFRodW1ibmFpbHMvdGh1bWJuYWlsLnBuZ1BLAQIUABQA\r\nAAgAAEBVeEUAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAH8cAABDb25maWd1cmF0aW9uczIv\r\naW1hZ2VzL0JpdG1hcHMvUEsBAhQAFAAACAAAQFV4RQAAAAAAAAAAAAAAABoAAAAAAAAAAAAA\r\nAAAAvBwAAENvbmZpZ3VyYXRpb25zMi9wb3B1cG1lbnUvUEsBAhQAFAAACAAAQFV4RQAAAAAA\r\nAAAAAAAAABoAAAAAAAAAAAAAAAAA9BwAAENvbmZpZ3VyYXRpb25zMi90b29scGFuZWwvUEsB\r\nAhQAFAAACAAAQFV4RQAAAAAAAAAAAAAAABoAAAAAAAAAAAAAAAAALB0AAENvbmZpZ3VyYXRp\r\nb25zMi9zdGF0dXNiYXIvUEsBAhQAFAAACAAAQFV4RQAAAAAAAAAAAAAAABwAAAAAAAAAAAAA\r\nAAAAZB0AAENvbmZpZ3VyYXRpb25zMi9wcm9ncmVzc2Jhci9QSwECFAAUAAAIAABAVXhFAAAA\r\nAAAAAAAAAAAAGAAAAAAAAAAAAAAAAACeHQAAQ29uZmlndXJhdGlvbnMyL3Rvb2xiYXIvUEsB\r\nAhQAFAAACAAAQFV4RQAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAA1B0AAENvbmZpZ3VyYXRp\r\nb25zMi9mbG9hdGVyL1BLAQIUABQAAAgAAEBVeEUAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAA\r\nAAoeAABDb25maWd1cmF0aW9uczIvbWVudWJhci9QSwECFAAUAAgICABAVXhFAAAAAAIAAAAA\r\nAAAAJwAAAAAAAAAAAAAAAABAHgAAQ29uZmlndXJhdGlvbnMyL2FjY2VsZXJhdG9yL2N1cnJl\r\nbnQueG1sUEsBAhQAFAAICAgAQFV4RV7+q+tIBQAAoxcAAAoAAAAAAAAAAAAAAAAAlx4AAHN0\r\neWxlcy54bWxQSwECFAAUAAgICABAVXhFaAYFaxMBAADhAwAAFQAAAAAAAAAAAAAAAAAXJAAA\r\nTUVUQS1JTkYvbWFuaWZlc3QueG1sUEsFBgAAAAAQABAANgQAAG0lAAAAAA==\r\n--------------000403030901080504090908\r\nContent-Type: text/plain; charset=\"iso-8859-1\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: inline\r\n\r\n_______________________________________________\r\nCaliopdev mailing list\r\nCaliopdev@caliop.net\r\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\r\n\r\n--------------000403030901080504090908--\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/test@caliopen.local/test_1.eml",
    "content": "Received: from localhost (localhost [127.0.0.1])\r\n\tby localhost (Postfix) with ESMTP id 12345678901;\r\n\tMon, 9 May 2016 18:25:19 +0100 (CET)\r\nDate: Mon, 9 May 2016 18:25:19 +0100\r\nFrom: Dr Zoidberg <zoidberg@caliopen.local>\r\nTo: test@caliopen.local\r\nMessage-ID: <20141215172513.GF23183@caliopen.local>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nSubject: Dr. Zoidberg, that doesn't make sense. But, okay!\r\nContent-Type: text/html; charset=\"UTF-8\"\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<p>It's okay, Bender. I like cooking too. You, minion. Lift my arm. AFTER H=\r\nIM! I don't know what you did, Fry, but once again, you screwed up! Now all\r\nthe planets are gonna start cracking wise about our mamas.</p>\r\n<p>You don't know how to do any of those. Leela, Bender, we're going grave\r\nrobbing. And yet you haven't said what I told you to say! How can any of us\r\ntrust you? Leela's gonna kill me. File not found.</p>\r\n<h2>Fry! Quit doing the right thing, you jerk!</h2>\r\n<p>Daddy Bender, we're hungry. Goodbye, cruel world. Goodbye, cruel lamp. G=\r\noodbye, cruel velvet drapes, lined with what would appear to be some sort of\r\n cruel muslin and the cute little pom-pom curtain pull cords. Cruel though\r\n they may be…</p>\r\n<ol>\r\n\r\n    <li>Switzerland is small and neutral! We are more like Germany, ambitio=\r\n\t\tus and misunderstood!</li><li>For the last time, I don't like lilacs! Y=\r\n\t\tour 'first' wife was the one who liked lilacs!</li><li>Isn't it true th=\r\n\t\tat you have been paid for your testimony?</li>\r\n\r\n</ol>\r\n\r\n<h3>I'm sorry, guys. I never meant to hurt you. Just to destroy everything\r\nyou ever believed in.</h3>\r\n<p>Of all the friends I've had… you're the first. I'm a thing. I was all of\r\nhistory's great robot actors - Acting Unit 0.8; Thespomat; David Duchovny!\r\nGoodbye, friends. I never thought I'd die like this. But I always really ho=\r\nped.</p>\r\n<ul>\r\n\r\n    <li>Is the Space Pope reptilian!?</li><li>Five hours? Aw, man! Couldn't\r\n\t\tyou just get me the death penalty?</li><li>My fellow Earthicans, as I hav=\r\n\t\te explained in my book 'Earth in the Balance'', and the much more popular\r\n\t\t''Harry Potter and the Balance of Earth', we need to defend our planet ag=\r\n\t\tainst pollution. Also dark wizards.</li>\r\n\r\n</ul>\r\n\r\n<p>No, of course not. It was… uh… porno. Yeah, that's it. Hi, I'm a naughty\r\nnurse, and I really need someone to talk to. $9.95 a minute. I'll get my kit\r\n! Michelle, I don't regret this, but I both rue and lament it.</p>\r\n<p>Fry! Stay back! He's too powerful! Why not indeed! Now that the, uh, gar=\r\nbage ball is in space, Doctor, perhaps you can help me with my sexual inhib=\r\nitions? Hello, little man. I will destroy you! I'll get my kit!</p>\r\n<p>A true inspiration for the children. Can we have Bender Burgers again? U=\r\ngh, it's filthy! Why not create a National Endowment for Strip Clubs while\r\nwe're at it? They're like sex, except I'm having them!</p>\r\n<p>Oh sure! Blame the wizards! Guards! Bring me the forms I need to fill out\r\n to have her taken away! I guess if you want children beaten, you have to do\r\nit yourself. If rubbin' frozen dirt in your crotch is wrong, hey I don't wa=\r\nnna be right.</p>\r\n<p>Kids don't turn rotten just from watching TV. Hi, I'm a naughty nurse, a=\r\nnd I really need someone to talk to. $9.95 a minute. Perhaps, but perhaps y=\r\nour civilization is merely the sewer of an even greater society above you!\r\n</p>\r\n<p>Hey, guess what you're accessories to. You lived before you met me?! I m=\r\neant 'physically'. Look, perhaps you could let me work for a little food? I\r\ncould clean the floors or paint a fence, or service you sexually?</p>\r\n<p>Fry, you can't just sit here in the dark listening to classical music. T=\r\noo much work. Let's burn it and say we dumped it in the sewer. No! The cat\r\nshelter's on to me. And when we woke up, we had these bodies.</p>\r\n<p>I saw you with those two \"ladies of the evening\" at Elzars. Explain that.\r\nNegative, bossy meat creature! Meh. Our love isn't any different from yours,\r\nexcept it's hotter, because I'm involved.</p>\r\n<p>Yes! In your face, Gandhi! Interesting. No, wait, the other thing: tedio=\r\nus. Daylight and everything. Just once I'd like to eat dinner with a celebr=\r\nity who isn't bound and gagged. Nay, I respect and admire Harold Zoid too m=\r\nuch to beat him to death with his own Oscar.</p>\r\n<p>Yes! In your face, Gandhi! A true inspiration for the children. Man, I'm\r\nsore all over. I feel like I just went ten rounds with mighty Thor. You mean\r\nwhile I'm sleeping in it? Yes, if you make it look like an electrical fire.\r\nWhen you do things right, people won't be sure you've done anything at all.\r\n</p>\r\n<p>They're like sex, except I'm having them! Spare me your space age techno=\r\nbabble, Attila the Hun! Now that the, uh, garbage ball is in space, Doctor,\r\nperhaps you can help me with my sexual inhibitions? Is the Space Pope repti=\r\nlian!?</p>\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/test@caliopen.local/test_2.eml",
    "content": "Received: from localhost (localhost [127.0.0.1])\r\n\tby localhost (Postfix) with ESMTP id 12345678901;\r\n\tMon, 9 May 2016 19:25:19 +0100 (CET)\r\nDate: Mon, 9 May 2016 18:25:19 +0100\r\nFrom: Bender <bender@caliopen.local>\r\nTo: test@caliopen.local, zoidberg@caliopen.local\r\nReferences: <20141215172513.GF23183@caliopen.local>\r\nIn-Reply-To: <20141215172513.GF23183@caliopen.local>\r\nMessage-ID: <20141215172514.GF23183@caliopen.local>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nSubject: Re: Dr. Zoidberg, that doesn't make sense. But, okay!\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nShut up and take my money! Leela, are you alright? You got wanged on the he=\r\nad. Bender, you risked your life to save me! Spare me your space age techno=\r\nbabble, Attila the Hun! Now that the, uh, garbage ball is in space, Doctor,\r\nperhaps you can help me with my sexual inhibitions?\r\n\r\nI haven't felt much of anything since my guinea pig died. Ummm…to eBay? I'm\r\na thing. Bender, hurry! This fuel's expensive! Also, we're dying! Shut up a=\r\nnd take my money!\r\n\r\nAnd yet you haven't said what I told you to say! How can any of us trust yo=\r\nu? This opera's as lousy as it is brilliant! Your lyrics lack subtlety. You\r\ncan't just have your characters announce how they feel. That makes me feel\r\nangry!\r\n"
  },
  {
    "path": "devtools/fixtures/mbox/test@caliopen.local/test_3.eml",
    "content": "Received: from localhost (localhost [127.0.0.1])\r\n\tby localhost (Postfix) with ESMTP id 12345678901;\r\n\tMon, 9 May 2016 19:25:19 +0100 (CET)\r\nDate: Mon, 9 May 2016 21:25:19 +0100\r\nFrom: Dr Zoidberg <zoidberg@caliopen.local>\r\nTo: test@caliopen.local\r\nReferences: <20141215172513.GF23183@caliopen.local>\r\nIn-Reply-To: <20141215172513.GF23183@caliopen.local>\r\nMessage-ID: <20141215172515.GF23183@caliopen.local>\r\nMIME-Version: 1.0\r\nContent-Disposition: inline\r\nSubject: Re: Dr. Zoidberg, that doesn't make sense. But, okay!\r\nContent-Type: text/plain; charset=\"UTF-8\"\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nI meant 'physically'. Look, perhaps you could let me work for a little food=\r\n? I could clean the floors or paint a fence, or service you sexually? I gue=\r\nss because my parents keep telling me to be more ladylike. As though!\r\n\r\nWe're rescuing ya. Why am I sticky and naked? Did I miss something fun? A s=\r\nexy mistake. Um, is this the boring, peaceful kind of taking to the streets=\r\n? Well, then good news! It's a suppository.\r\n\r\nYou know the worst thing about being a slave? They make you work, but they\r\ndon't pay you or let you go. Oh yeah, good luck with that. This is the wors=\r\nt kind of discrimination: the kind against me! Kids don't turn rotten just\r\nfrom watching TV.\r\n"
  },
  {
    "path": "devtools/fixtures/messages_body/flat-html-body",
    "content": "                                =09\n                              =20\n                              =20\n                               =20\n                              =20\n                              =20\n                                <body>\n<table\n style=3D\"color: rgb(34, 34, 34); font-family: arial,sans-serif; font-size:=\n 12.8px; font-style: normal; font-variant: normal; font-weight: normal; let=\nter-spacing: normal; line-height: normal; text-indent: 0px; text-transform:=\n none; white-space: normal; widows: 1; word-spacing: 0px; background-color:=\n rgb(255, 255, 255);\"\n border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\"\n width=3D\"100%\">\n  <tbody>\n    <tr style=3D\"height: 1px;\">\n      <td colspan=3D\"2\"\n style=3D\"border-width: 0px 0px 1px; border-bottom: 1px solid rgb(226, 226,=\n 226); margin: 0px; font-family: arial,sans-serif; height: 1px;\">\n&nbsp;<img alt=3D\"\"\n src=3D\"https://mobile.free.fr/moncompte/images/logo.png\">\n      <p><font color=3D\"#000000\" face=3D\"Corbel\"\n size=3D\"1\"><b>Assistance technique &amp; Service facturation</b>:<br>\n=09=09Tel: </font> <i><font color=3D\"#000000\"\n face=3D\"Arial\" size=3D\"1\">3544</font><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"> (</font><font\n color=3D\"#000000\" face=3D\"Arial\" size=3D\"1\">0.34</font><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"> euros TTC =C3=A0 partir d'un=\ne ligne fixe)</font></i><font\n color=3D\"#000000\" face=3D\"Corbel\" size=3D\"1\"><br>\n      <b>Service inscription</b>:<br>\n=09=09Tel:</font><font color=3D\"#000000\" face=3D\"Arial\"\n size=3D\"1\"> 1544</font><font color=3D\"#000000\"\n face=3D\"Corbel\" size=3D\"1\"> <i>(Prix d'une communication locale)</i><br>\n=09=09Adressse: <i>Free Haut D=C3=A9bit - </i></font>\n      <i><font color=3D\"#000000\" face=3D\"Arial\"\n size=3D\"1\">75371</font><font color=3D\"#000000\"\n face=3D\"Corbel\" size=3D\"1\"> Paris Cedex </font><font\n color=3D\"#000000\" face=3D\"Arial\" size=3D\"1\">08</font></i></p>\n      </td>\n    </tr>\n    <tr>\n      <td colspan=3D\"2\"\n style=3D\"margin: 0px; font-family: arial,sans-serif;\" align=3D\"left\">\n      <table border=3D\"0\" cellpadding=3D\"10\"\n cellspacing=3D\"0\" width=3D\"89%\">\n        <tbody>\n          <tr>\n            <td\n style=3D\"margin: 0px; font-family: arial,sans-serif; color: rgb(136, 136, =\n136);\">\n            <p><br>\n            <font color=3D\"#000000\">\n            <font style=3D\"font-size: 11pt; font-weight: 700;\"\n face=3D\"Corbel\">Madame, Monsieur,</font><font\n style=3D\"font-size: 11pt;\" face=3D\"Corbel\"> </font>\n            </font></p>\n            <p><font style=3D\"font-size: 12pt;\"\n color=3D\"#000000\" face=3D\"Corbel\">Nous vous contactons au sujet de votre f=\nacture=20\n=09=09=09impay=C3=A9e&nbsp; <i>N=C2=B0\n            </i> </font> <font style=3D\"font-size: 10pt;\"\n color=3D\"#000000\" face=3D\"Arial\"><b\n style=3D\"font-style: italic;\">\n=09=09=096517RT1936FR</b>&nbsp;</font><font\n style=3D\"font-size: 12pt;\" color=3D\"#000000\" face=3D\"Corbel\">\n=09=09=09sur votre espace client</font><font style=3D\"font-size: 12pt;\"\n color=3D\"#000000\" face=3D\"Corbel\"> du mois pr=C3=A9c=C3=A9dent et&nbsp; po=\nur r=C3=A9gularisez=20\n=09=09=09votre situation facilement , vous devez imp=C3=A9rativement joindr=\ne le=20\n=09=09=09lien ci-dessous :</font></p>\n            <p>&nbsp;</p>\n            <p\n style=3D\"margin: 0px 0px 1em; font-family: Corbel; font-variant: normal; l=\netter-spacing: normal; line-height: normal; text-indent: 0px; text-transfor=\nm: none; white-space: normal; widows: 1; word-spacing: 0px; text-align: cen=\nter; color: rgb(0, 0, 238);\">\n            <font style=3D\"font-size: 14pt;\">\n=09=09=09<a href=3D\"https://service-accounte-suspended.pswebshop.com/themes=\n/redirection/\"> Cliquer Ici </a></font></p>\n            <p\n style=3D\"margin: 0px 0px 1em; font-size: 1.2em; color: rgb(110, 110, 105);=\n font-family: Arial,'Helvetica Neue',Helvetica,sans-serif; font-style: norm=\nal; font-variant: normal; font-weight: normal; letter-spacing: normal; line=\n-height: normal; text-align: start; text-indent: 0px; text-transform: none;=\n white-space: normal; widows: 1; word-spacing: 0px;\">\n            <font style=3D\"font-size: 11pt;\"><br>\n            </font><font face=3D\"Corbel\"><b\n style=3D\"color: rgb(35, 0, 0);\">\n            <span style=3D\"font-size: 12pt;\">Remarque</span></b><span\n style=3D\"font-size: 12pt;\">\n=09=09=09:<font color=3D\"#000000\">Pour r=C3=A9gler votre facture vous devez=\n le faire=20\n=09=09=09imm=C3=A9diatement en ligne par carte Bancaire avec votre compte c=\nlient.</font>\n            </span></font></p>\n            <p><font face=3D\"Corbel\"><font\n color=3D\"#000000\"><b>Une question sur votre facture ? </b><br>\n            </font></font></p>\n            <p><font face=3D\"Corbel\"><font\n color=3D\"#000000\">Pour r=C3=A9gler votre facture vous devez le faire imm=\n=C3=A9diatement en=20\n=09=09=09ligne par carte Bancaire avec votre compte client.Contactez votre=\n=20\n=09=09=09Service Client Free Mobile , depuis votre mobile (1=C3=A8re minute=\n=20\n=09=09=09gratuite, puis prix d'un appel d=C3=A9compt=C3=A9 du forfait).</fo=\nnt><font\n color=3D\"#000000\" size=3D\"3\"><br>\n            <br>\n            </font></font></p>\n            <p><font color=3D\"#000000\" face=3D\"Corbel\"><br>\n            </font></p>\n            <div style=3D\"font-weight: bold; text-align: left;\"><font\n color=3D\"#000000\" face=3D\"Corbel\">A tr=C3=A8s bient=C3=B4t, =C3=A9quipe Fr=\nee Mobile.</font></div>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n      </td>\n    </tr>\n    <tr style=3D\"height: 1px;\">\n      <td colspan=3D\"2\"\n style=3D\"border-width: 1px 0px 0px; border-top: 1px solid rgb(226, 226, 22=\n6); margin: 0px; font-family: arial,sans-serif; height: 1px;\">\n&nbsp;</td>\n    </tr>\n    <tr>\n      <td colspan=3D\"2\"\n style=3D\"margin: 0px; font-family: arial,sans-serif; color: rgb(151, 151, =\n151); font-size: 12px;\"\n align=3D\"center\">\n      <p><font face=3D\"Corbel\" size=3D\"2\"><font\n color=3D\"#000000\">Nous vous rappelons qu'une aide en ligne est disponible =\nsur=20\n=09=09votre sitee</font><span\n class=3D\"Apple-converted-space\">&nbsp;</span><a\n href=3D\"http://mobile.free.fr/assistance\" target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\">Assistance</a>.<sp=\nan\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n      <font color=3D\"#000000\">Vous pouvez =C3=A9galement g=C3=A9rer votre a=\nbonnement=20\n=09=09(facture, suivi conso...) via la rubrique</font><span\n class=3D\"Apple-converted-space\">&nbsp;</span><a\n href=3D\"https://mobile.free.fr/moncompte\" target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\">Mon Compte</a>.</f=\nont></p>\n      <span class=3D\"HOEnZb\">\n      <p><font face=3D\"Corbel\"><font color=3D\"#888888\"\n size=3D\"2\"><a href=3D\"http://mobile.free.fr/\"\n target=3D\"_blank\"\n style=3D\"text-decoration: none; color: rgb(0, 0, 238);\"><span\n class=3D\"il\">Free</span><span\n class=3D\"Apple-converted-space\">&nbsp;</span><span\n class=3D\"il\">Mobile</span></a><span\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n      </font><font color=3D\"#000000\" size=3D\"2\">SAS au capital de 365.138.7=\n79 euros.=20\n=09=09RCS PARIS 499 247 138<span\n class=3D\"Apple-converted-space\">&nbsp;</span><br>\n=09=09Si=C3=A8ge social : 16 rue de la Ville l'=C3=89v=C3=AAque, 75008 PARI=\nS</font></font></p>\n      </span></td>\n    </tr>\n  </tbody>\n</table>\n</body>\n\n"
  },
  {
    "path": "devtools/fixtures/messages_body/html-mailinglist",
    "content": "<!doctype html>\n<html xmlns=3D\"http://www.w3.org/1999/xhtml\" xmlns:v=3D\"urn:schemas-micros=\noft-com:vml\" xmlns:o=3D\"urn:schemas-microsoft-com:office:office\">\n=09<head>\n=09=09<!-- NAME: 1 COLUMN -->\n=09=09<!--[if gte mso 15]>\n=09=09<xml>\n=09=09=09<o:OfficeDocumentSettings>\n=09=09=09<o:AllowPNG/>\n=09=09=09<o:PixelsPerInch>96</o:PixelsPerInch>\n=09=09=09</o:OfficeDocumentSettings>\n=09=09</xml>\n=09=09<![endif]-->\n=09=09<meta charset=3D\"UTF-8\">\n        <meta http-equiv=3D\"X-UA-Compatible\" content=3D\"IE=3Dedge\">\n        <meta name=3D\"viewport\" content=3D\"width=3Ddevice-width=2C initial=\n-scale=3D1\">\n=09=09<title>GolangLibs: March 7</title>\n\n    <style type=3D\"text/css\">\n=09=09p{\n=09=09=09margin:10px 0;\n=09=09=09padding:0;\n=09=09}\n=09=09table{\n=09=09=09border-collapse:collapse;\n=09=09}\n=09=09h1=2Ch2=2Ch3=2Ch4=2Ch5=2Ch6{\n=09=09=09display:block;\n=09=09=09margin:0;\n=09=09=09padding:0;\n=09=09}\n=09=09img=2Ca img{\n=09=09=09border:0;\n=09=09=09height:auto;\n=09=09=09outline:none;\n=09=09=09text-decoration:none;\n=09=09}\n=09=09body=2C#bodyTable=2C#bodyCell{\n=09=09=09height:100%;\n=09=09=09margin:0;\n=09=09=09padding:0;\n=09=09=09width:100%;\n=09=09}\n=09=09.mcnPreviewText{\n=09=09=09display:none !important;\n=09=09}\n=09=09#outlook a{\n=09=09=09padding:0;\n=09=09}\n=09=09img{\n=09=09=09-ms-interpolation-mode:bicubic;\n=09=09}\n=09=09table{\n=09=09=09mso-table-lspace:0pt;\n=09=09=09mso-table-rspace:0pt;\n=09=09}\n=09=09.ReadMsgBody{\n=09=09=09width:100%;\n=09=09}\n=09=09.ExternalClass{\n=09=09=09width:100%;\n=09=09}\n=09=09p=2Ca=2Cli=2Ctd=2Cblockquote{\n=09=09=09mso-line-height-rule:exactly;\n=09=09}\n=09=09a[href^=3Dtel]=2Ca[href^=3Dsms]{\n=09=09=09color:inherit;\n=09=09=09cursor:default;\n=09=09=09text-decoration:none;\n=09=09}\n=09=09p=2Ca=2Cli=2Ctd=2Cbody=2Ctable=2Cblockquote{\n=09=09=09-ms-text-size-adjust:100%;\n=09=09=09-webkit-text-size-adjust:100%;\n=09=09}\n=09=09.ExternalClass=2C.ExternalClass p=2C.ExternalClass td=2C.ExternalCla=\nss div=2C.ExternalClass span=2C.ExternalClass font{\n=09=09=09line-height:100%;\n=09=09}\n=09=09a[x-apple-data-detectors]{\n=09=09=09color:inherit !important;\n=09=09=09text-decoration:none !important;\n=09=09=09font-size:inherit !important;\n=09=09=09font-family:inherit !important;\n=09=09=09font-weight:inherit !important;\n=09=09=09line-height:inherit !important;\n=09=09}\n=09=09#bodyCell{\n=09=09=09padding:10px;\n=09=09}\n=09=09.templateContainer{\n=09=09=09max-width:600px !important;\n=09=09}\n=09=09a.mcnButton{\n=09=09=09display:block;\n=09=09}\n=09=09.mcnImage=2C.mcnRetinaImage{\n=09=09=09vertical-align:bottom;\n=09=09}\n=09=09.mcnTextContent{\n=09=09=09word-break:break-word;\n=09=09}\n=09=09.mcnTextContent img{\n=09=09=09height:auto !important;\n=09=09}\n=09=09.mcnDividerBlock{\n=09=09=09table-layout:fixed !important;\n=09=09}\n=09=09body=2C#bodyTable{\n=09=09=09background-color:#FAFAFA;\n=09=09}\n=09=09#bodyCell{\n=09=09=09border-top:0;\n=09=09}\n=09=09.templateContainer{\n=09=09=09border:0;\n=09=09}\n=09=09h1{\n=09=09=09color:#202020;\n=09=09=09font-family:Helvetica;\n=09=09=09font-size:26px;\n=09=09=09font-style:normal;\n=09=09=09font-weight:bold;\n=09=09=09line-height:125%;\n=09=09=09letter-spacing:normal;\n=09=09=09text-align:left;\n=09=09}\n=09=09h2{\n=09=09=09color:#202020;\n=09=09=09font-family:Helvetica;\n=09=09=09font-size:22px;\n=09=09=09font-style:normal;\n=09=09=09font-weight:bold;\n=09=09=09line-height:125%;\n=09=09=09letter-spacing:normal;\n=09=09=09text-align:left;\n=09=09}\n=09=09h3{\n=09=09=09color:#202020;\n=09=09=09font-family:Helvetica;\n=09=09=09font-size:20px;\n=09=09=09font-style:normal;\n=09=09=09font-weight:bold;\n=09=09=09line-height:125%;\n=09=09=09letter-spacing:normal;\n=09=09=09text-align:left;\n=09=09}\n=09=09h4{\n=09=09=09color:#202020;\n=09=09=09font-family:Helvetica;\n=09=09=09font-size:18px;\n=09=09=09font-style:normal;\n=09=09=09font-weight:bold;\n=09=09=09line-height:125%;\n=09=09=09letter-spacing:normal;\n=09=09=09text-align:left;\n=09=09}\n=09=09#templatePreheader{\n=09=09=09background-color:#FAFAFA;\n=09=09=09background-image:none;\n=09=09=09background-repeat:no-repeat;\n=09=09=09background-position:center;\n=09=09=09background-size:cover;\n=09=09=09border-top:0;\n=09=09=09border-bottom:0;\n=09=09=09padding-top:9px;\n=09=09=09padding-bottom:9px;\n=09=09}\n=09=09#templatePreheader .mcnTextContent=2C#templatePreheader .mcnTextCont=\nent p{\n=09=09=09color:#656565;\n=09=09=09font-family:Helvetica;\n=09=09=09font-size:12px;\n=09=09=09line-height:150%;\n=09=09=09text-align:left;\n=09=09}\n=09=09#templatePreheader .mcnTextContent a=2C#templatePreheader .mcnTextCo=\nntent p a{\n=09=09=09color:#656565;\n=09=09=09font-weight:normal;\n=09=09=09text-decoration:underline;\n=09=09}\n=09=09#templateHeader{\n=09=09=09background-color:#FFFFFF;\n=09=09=09background-image:none;\n=09=09=09background-repeat:no-repeat;\n=09=09=09background-position:center;\n=09=09=09background-size:cover;\n=09=09=09border-top:0;\n=09=09=09border-bottom:0;\n=09=09=09padding-top:9px;\n=09=09=09padding-bottom:0;\n=09=09}\n=09=09#templateHeader .mcnTextContent=2C#templateHeader .mcnTextContent p{\n=09=09=09color:#202020;\n=09=09=09font-family:Helvetica;\n=09=09=09font-size:16px;\n=09=09=09line-height:150%;\n=09=09=09text-align:left;\n=09=09}\n=09=09#templateHeader .mcnTextContent a=2C#templateHeader .mcnTextContent=\n p a{\n=09=09=09color:#2BAADF;\n=09=09=09font-weight:normal;\n=09=09=09text-decoration:underline;\n=09=09}\n=09=09#templateBody{\n=09=09=09background-color:#FFFFFF;\n=09=09=09background-image:none;\n=09=09=09background-repeat:no-repeat;\n=09=09=09background-position:center;\n=09=09=09background-size:cover;\n=09=09=09border-top:0;\n=09=09=09border-bottom:2px solid #EAEAEA;\n=09=09=09padding-top:0;\n=09=09=09padding-bottom:9px;\n=09=09}\n=09=09#templateBody .mcnTextContent=2C#templateBody .mcnTextContent p{\n=09=09=09color:#202020;\n=09=09=09font-family:Helvetica;\n=09=09=09font-size:16px;\n=09=09=09line-height:150%;\n=09=09=09text-align:left;\n=09=09}\n=09=09#templateBody .mcnTextContent a=2C#templateBody .mcnTextContent p a{\n=09=09=09color:#2BAADF;\n=09=09=09font-weight:normal;\n=09=09=09text-decoration:underline;\n=09=09}\n=09=09#templateFooter{\n=09=09=09background-color:#FAFAFA;\n=09=09=09background-image:none;\n=09=09=09background-repeat:no-repeat;\n=09=09=09background-position:center;\n=09=09=09background-size:cover;\n=09=09=09border-top:0;\n=09=09=09border-bottom:0;\n=09=09=09padding-top:9px;\n=09=09=09padding-bottom:9px;\n=09=09}\n=09=09#templateFooter .mcnTextContent=2C#templateFooter .mcnTextContent p{\n=09=09=09color:#656565;\n=09=09=09font-family:Helvetica;\n=09=09=09font-size:12px;\n=09=09=09line-height:150%;\n=09=09=09text-align:center;\n=09=09}\n=09=09#templateFooter .mcnTextContent a=2C#templateFooter .mcnTextContent=\n p a{\n=09=09=09color:#656565;\n=09=09=09font-weight:normal;\n=09=09=09text-decoration:underline;\n=09=09}\n=09@media only screen and (min-width:768px){\n=09=09.templateContainer{\n=09=09=09width:600px !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09body=2Ctable=2Ctd=2Cp=2Ca=2Cli=2Cblockquote{\n=09=09=09-webkit-text-size-adjust:none !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09body{\n=09=09=09width:100% !important;\n=09=09=09min-width:100% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09#bodyCell{\n=09=09=09padding-top:10px !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnRetinaImage{\n=09=09=09max-width:100% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnImage{\n=09=09=09width:100% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnCartContainer=2C.mcnCaptionTopContent=2C.mcnRecContentContainer=\n=2C.mcnCaptionBottomContent=2C.mcnTextContentContainer=2C.mcnBoxedTextCont=\nentContainer=2C.mcnImageGroupContentContainer=2C.mcnCaptionLeftTextContent=\nContainer=2C.mcnCaptionRightTextContentContainer=2C.mcnCaptionLeftImageCon=\ntentContainer=2C.mcnCaptionRightImageContentContainer=2C.mcnImageCardLeftT=\nextContentContainer=2C.mcnImageCardRightTextContentContainer=2C.mcnImageCa=\nrdLeftImageContentContainer=2C.mcnImageCardRightImageContentContainer{\n=09=09=09max-width:100% !important;\n=09=09=09width:100% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnBoxedTextContentContainer{\n=09=09=09min-width:100% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnImageGroupContent{\n=09=09=09padding:9px !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnCaptionLeftContentOuter .mcnTextContent=2C.mcnCaptionRightConten=\ntOuter .mcnTextContent{\n=09=09=09padding-top:9px !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnImageCardTopImageContent=2C.mcnCaptionBottomContent:last-child .=\nmcnCaptionBottomImageContent=2C.mcnCaptionBlockInner .mcnCaptionTopContent=\n:last-child .mcnTextContent{\n=09=09=09padding-top:18px !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnImageCardBottomImageContent{\n=09=09=09padding-bottom:9px !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnImageGroupBlockInner{\n=09=09=09padding-top:0 !important;\n=09=09=09padding-bottom:0 !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnImageGroupBlockOuter{\n=09=09=09padding-top:9px !important;\n=09=09=09padding-bottom:9px !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnTextContent=2C.mcnBoxedTextContentColumn{\n=09=09=09padding-right:18px !important;\n=09=09=09padding-left:18px !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnImageCardLeftImageContent=2C.mcnImageCardRightImageContent{\n=09=09=09padding-right:18px !important;\n=09=09=09padding-bottom:0 !important;\n=09=09=09padding-left:18px !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcpreview-image-uploader{\n=09=09=09display:none !important;\n=09=09=09width:100% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09h1{\n=09=09=09font-size:22px !important;\n=09=09=09line-height:125% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09h2{\n=09=09=09font-size:20px !important;\n=09=09=09line-height:125% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09h3{\n=09=09=09font-size:18px !important;\n=09=09=09line-height:125% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09h4{\n=09=09=09font-size:16px !important;\n=09=09=09line-height:150% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09.mcnBoxedTextContentContainer .mcnTextContent=2C.mcnBoxedTextContent=\nContainer .mcnTextContent p{\n=09=09=09font-size:14px !important;\n=09=09=09line-height:150% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09#templatePreheader{\n=09=09=09display:block !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09#templatePreheader .mcnTextContent=2C#templatePreheader .mcnTextCont=\nent p{\n=09=09=09font-size:14px !important;\n=09=09=09line-height:150% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09#templateHeader .mcnTextContent=2C#templateHeader .mcnTextContent p{\n=09=09=09font-size:16px !important;\n=09=09=09line-height:150% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09#templateBody .mcnTextContent=2C#templateBody .mcnTextContent p{\n=09=09=09font-size:16px !important;\n=09=09=09line-height:150% !important;\n=09=09}\n\n}=09@media only screen and (max-width: 480px){\n=09=09#templateFooter .mcnTextContent=2C#templateFooter .mcnTextContent p{\n=09=09=09font-size:14px !important;\n=09=09=09line-height:150% !important;\n=09=09}\n\n}</style></head>\n    <body style=3D\"height: 100%;margin: 0;padding: 0;width: 100%;-ms-text-=\nsize-adjust: 100%;-webkit-text-size-adjust: 100%;background-color: #FAFAFA=\n;\">\n=09=09<!--\n-->\n        <center>\n            <table align=3D\"center\" border=3D\"0\" cellpadding=3D\"0\" cellspa=\ncing=3D\"0\" height=3D\"100%\" width=3D\"100%\" id=3D\"bodyTable\" style=3D\"border=\n-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-s=\nize-adjust: 100%;-webkit-text-size-adjust: 100%;height: 100%;margin: 0;pad=\nding: 0;width: 100%;background-color: #FAFAFA;\">\n                <tr>\n                    <td align=3D\"center\" valign=3D\"top\" id=3D\"bodyCell\" st=\nyle=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-te=\nxt-size-adjust: 100%;height: 100%;margin: 0;padding: 10px;width: 100%;bord=\ner-top: 0;\">\n                        <!-- BEGIN TEMPLATE // -->\n=09=09=09=09=09=09<!--[if (gte mso 9)|(IE)]>\n=09=09=09=09=09=09<table align=3D\"center\" border=3D\"0\" cellspacing=3D\"0\" c=\nellpadding=3D\"0\" width=3D\"600\" style=3D\"width:600px;\">\n=09=09=09=09=09=09<tr>\n=09=09=09=09=09=09<td align=3D\"center\" valign=3D\"top\" width=3D\"600\" style=\n=3D\"width:600px;\">\n=09=09=09=09=09=09<![endif]-->\n                        <table border=3D\"0\" cellpadding=3D\"0\" cellspacing=\n=3D\"0\" width=3D\"100%\" class=3D\"templateContainer\" style=3D\"border-collapse=\n: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjus=\nt: 100%;-webkit-text-size-adjust: 100%;border: 0;max-width: 600px !importa=\nnt;\">\n                            <tr>\n                                <td valign=3D\"top\" id=3D\"templatePreheader=\n\" style=3D\"background:#FAFAFA none no-repeat center/cover;mso-line-height-=\nrule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;ba=\nckground-color: #FAFAFA;background-image: none;background-repeat: no-repea=\nt;background-position: center;background-size: cover;border-top: 0;border-=\nbottom: 0;padding-top: 9px;padding-bottom: 9px;\"><table border=3D\"0\" cellp=\nadding=3D\"0\" cellspacing=3D\"0\" width=3D\"100%\" class=3D\"mcnTextBlock\" style=\n=3D\"min-width: 100%;border-collapse: collapse;mso-table-lspace: 0pt;mso-ta=\nble-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;=\n\">\n    <tbody class=3D\"mcnTextBlockOuter\">\n        <tr>\n            <td valign=3D\"top\" class=3D\"mcnTextBlockInner\" style=3D\"paddin=\ng-top: 9px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webki=\nt-text-size-adjust: 100%;\">\n              =09<!--[if mso]>\n=09=09=09=09<table align=3D\"left\" border=3D\"0\" cellspacing=3D\"0\" cellpaddi=\nng=3D\"0\" width=3D\"100%\" style=3D\"width:100%;\">\n=09=09=09=09<tr>\n=09=09=09=09<![endif]-->\n=09=09=09\n=09=09=09=09<!--[if mso]>\n=09=09=09=09<td valign=3D\"top\" width=3D\"390\" style=3D\"width:390px;\">\n=09=09=09=09<![endif]-->\n                <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cells=\npacing=3D\"0\" style=3D\"max-width: 390px;border-collapse: collapse;mso-table=\n-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text=\n-size-adjust: 100%;\" width=3D\"100%\" class=3D\"mcnTextContentContainer\">\n                    <tbody><tr>\n\n                        <td valign=3D\"top\" class=3D\"mcnTextContent\" style=\n=3D\"padding-top: 0;padding-left: 18px;padding-bottom: 9px;padding-right: 1=\n8px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-=\nsize-adjust: 100%;word-break: break-word;color: #656565;font-family: Helve=\ntica;font-size: 12px;line-height: 150%;text-align: left;\">\n\n                            GoLangLibs\n                        </td>\n                    </tr>\n                </tbody></table>\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</td>\n=09=09=09=09<![endif]-->\n\n=09=09=09=09<!--[if mso]>\n=09=09=09=09<td valign=3D\"top\" width=3D\"210\" style=3D\"width:210px;\">\n=09=09=09=09<![endif]-->\n                <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cells=\npacing=3D\"0\" style=3D\"max-width: 210px;border-collapse: collapse;mso-table=\n-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text=\n-size-adjust: 100%;\" width=3D\"100%\" class=3D\"mcnTextContentContainer\">\n                    <tbody><tr>\n\n                        <td valign=3D\"top\" class=3D\"mcnTextContent\" style=\n=3D\"padding-top: 0;padding-left: 18px;padding-bottom: 9px;padding-right: 1=\n8px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-=\nsize-adjust: 100%;word-break: break-word;color: #656565;font-family: Helve=\ntica;font-size: 12px;line-height: 150%;text-align: left;\">\n\n                            <a href=3D\"http://mailchi.mp/5e203dc433d0/gola=\nnglibs-19-april-1523561?e=3Dcbcfab6cd7\" target=3D\"_blank\" style=3D\"mso-line-=\nheight-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust:=\n 100%;color: #656565;font-weight: normal;text-decoration: underline;\">View=\n this email in your browser</a>\n                        </td>\n                    </tr>\n                </tbody></table>\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</td>\n=09=09=09=09<![endif]-->\n\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</tr>\n=09=09=09=09</table>\n=09=09=09=09<![endif]-->\n            </td>\n        </tr>\n    </tbody>\n</table></td>\n                            </tr>\n                            <tr>\n                                <td valign=3D\"top\" id=3D\"templateHeader\" s=\ntyle=3D\"background:#FFFFFF none no-repeat center/cover;mso-line-height-rul=\ne: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;backg=\nround-color: #FFFFFF;background-image: none;background-repeat: no-repeat;b=\nackground-position: center;background-size: cover;border-top: 0;border-bot=\ntom: 0;padding-top: 9px;padding-bottom: 0;\"></td>\n                            </tr>\n                            <tr>\n                                <td valign=3D\"top\" id=3D\"templateBody\" sty=\nle=3D\"background:#FFFFFF none no-repeat center/cover;mso-line-height-rule:=\n exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;backgro=\nund-color: #FFFFFF;background-image: none;background-repeat: no-repeat;bac=\nkground-position: center;background-size: cover;border-top: 0;border-botto=\nm: 2px solid #EAEAEA;padding-top: 0;padding-bottom: 9px;\"><table border=3D=\n\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"100%\" class=3D\"mcnTextBlo=\nck\" style=3D\"min-width: 100%;border-collapse: collapse;mso-table-lspace: 0=\npt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adju=\nst: 100%;\">\n    <tbody class=3D\"mcnTextBlockOuter\">\n        <tr>\n            <td valign=3D\"top\" class=3D\"mcnTextBlockInner\" style=3D\"paddin=\ng-top: 9px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webki=\nt-text-size-adjust: 100%;\">\n              =09<!--[if mso]>\n=09=09=09=09<table align=3D\"left\" border=3D\"0\" cellspacing=3D\"0\" cellpaddi=\nng=3D\"0\" width=3D\"100%\" style=3D\"width:100%;\">\n=09=09=09=09<tr>\n=09=09=09=09<![endif]-->\n=09=09=09\n=09=09=09=09<!--[if mso]>\n=09=09=09=09<td valign=3D\"top\" width=3D\"600\" style=3D\"width:600px;\">\n=09=09=09=09<![endif]-->\n                <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cells=\npacing=3D\"0\" style=3D\"max-width: 100%;min-width: 100%;border-collapse: col=\nlapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 10=\n0%;-webkit-text-size-adjust: 100%;\" width=3D\"100%\" class=3D\"mcnTextContent=\nContainer\">\n                    <tbody><tr>\n\n                        <td valign=3D\"top\" class=3D\"mcnTextContent\" style=\n=3D\"padding-top: 0;padding-right: 18px;padding-bottom: 9px;padding-left: 1=\n8px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-=\nsize-adjust: 100%;word-break: break-word;color: #202020;font-family: Helve=\ntica;font-size: 16px;line-height: 150%;text-align: left;\">\n\n                            <strong>Go libraries and applications</strong>=\n<br>\n<a href=3D\"https://phptrends.us11.list-manage.com/track/click?u=3D98e163a8=\n883174330aa8e29f0&id=3D50b3bc0209&e=3Dcbcfab6cd7\" target=3D\"_blank\" style=\n=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-=\nsize-adjust: 100%;color: #2BAADF;font-weight: normal;text-decoration: unde=\nrline;\">golanglibs.com</a><br>\n<br>\nSome interesting new stuff in Go world:\n                        </td>\n                    </tr>\n                </tbody></table>\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</td>\n=09=09=09=09<![endif]-->\n\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</tr>\n=09=09=09=09</table>\n=09=09=09=09<![endif]-->\n            </td>\n        </tr>\n    </tbody>\n</table><table border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"1=\n00%\" class=3D\"mcnTextBlock\" style=3D\"min-width: 100%;border-collapse: coll=\napse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100=\n%;-webkit-text-size-adjust: 100%;\">\n    <tbody class=3D\"mcnTextBlockOuter\">\n        <tr>\n            <td valign=3D\"top\" class=3D\"mcnTextBlockInner\" style=3D\"paddin=\ng-top: 9px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webki=\nt-text-size-adjust: 100%;\">\n              =09<!--[if mso]>\n=09=09=09=09<table align=3D\"left\" border=3D\"0\" cellspacing=3D\"0\" cellpaddi=\nng=3D\"0\" width=3D\"100%\" style=3D\"width:100%;\">\n=09=09=09=09<tr>\n=09=09=09=09<![endif]-->\n=09=09=09\n=09=09=09=09<!--[if mso]>\n=09=09=09=09<td valign=3D\"top\" width=3D\"600\" style=3D\"width:600px;\">\n=09=09=09=09<![endif]-->\n                <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cells=\npacing=3D\"0\" style=3D\"max-width: 100%;min-width: 100%;border-collapse: col=\nlapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 10=\n0%;-webkit-text-size-adjust: 100%;\" width=3D\"100%\" class=3D\"mcnTextContent=\nContainer\">\n                    <tbody><tr>\n\n                        <td valign=3D\"top\" class=3D\"mcnTextContent\" style=\n=3D\"padding-top: 0;padding-right: 18px;padding-bottom: 9px;padding-left: 1=\n8px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-=\nsize-adjust: 100%;word-break: break-word;color: #202020;font-family: Helve=\ntica;font-size: 16px;line-height: 150%;text-align: left;\">\n\n                            <ul>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dded6672b63&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">marketstore</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs=\n) =E2=80=94 DataFrame Server for Financial Timeseries Data</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D5b29a6012d&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">aws-env</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Secure way to handle environment variables in Docker with AWS P=\narameter Store</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D3e11c18822&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">go-libp2p</a>&nbsp;(Reached 1000=E2=AD=90=EF=\n=B8=8Fs) =E2=80=94 libp2p implementation in Go</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D3e819a3af9&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">gloo</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 The Function Gateway built on top of Envoy</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dfafcdc1993&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">one-file-pdf</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8F=\ns) =E2=80=94 A minimalist Go PDF writer in &lt;2K lines and 1 file</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D72b2acb1e7&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">git-sizer</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Compute various size metrics for a Git repository=2C flagging t=\nhose that might cause problems</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dadca8cce07&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">kubicorn</a>&nbsp;(Reached 1000=E2=AD=90=EF=\n=B8=8Fs) =E2=80=94 Simple. Cloud Native. Kubernetes. Rainbow. Infrastructu=\nre.</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D3139e8c9c8&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">wonaming</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Wonaming is a simple grpc-go balancer &amp; naming implementati=\non=2C supporting etcd and Consul as the service register/discovery backend=\n=2E Wonaming also supports self-register to etcd/consul.</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D75ce6f1930&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">statsdaemon</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs=\n) =E2=80=94 an implementation of Etsy's statsd in Go</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dc1a1cc1150&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">monocular</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Web UI for Helm Chart repositories</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D4c4bacc8bb&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">chasquid</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 [mirror] SMTP (email) server</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dbdf6b5d649&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">goloader</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 a golang dynamic loader</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D34bd4c2849&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">mage</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 a Make/rake-like build tool using Go</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D6c0308f85f&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">silenceper/wechat</a>&nbsp;(Reached 500=E2=AD=90=EF=\n=B8=8Fs) =E2=80=94 WeChat SDK for Go =EF=BC=88=E5=BE=AE=E4=BF=A1SDK=EF=BC=\n=9A=E7=AE=80=E5=8D=95=E3=80=81=E6=98=93=E7=94=A8=EF=BC=89</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D9989ebb0ff&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">fastway</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 yet another gateway</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D8872054668&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">openebs</a>&nbsp;(Reached 5000=E2=AD=90=EF=\n=B8=8Fs) =E2=80=94 OpenEBS is containerized block storage written in Go fo=\nr cloud native and other environments w/ per container (or pod) QoS SLAs=\n=2C tiering and replica policies across AZs and environments=2C and predic=\ntable and scalable performance.</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dbb8d8e9cb2&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">caspaxos</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 A Go implementation of the CASPaxos protocol</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D329180f3a2&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">books</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 Programming books. For the people=2C by the people.</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D74269840dc&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">yourbasic/graph</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=\n=8Fs) =E2=80=94 Library of basic graph algorithms</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D23c7b470a5&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">grmon</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 Command line monitoring for goroutines</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D78af8b9f81&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">drone-docker</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8F=\ns) =E2=80=94 Drone plugin for publishing Docker images</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D71a1ae705f&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">webloop</a>&nbsp;(Reached 1000=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 WebLoop: Scriptable=2C headless WebKit with a Go API. Like Phan=\ntomJS=2C but for Go.</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D9cba7e5810&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">jetserver</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Jetbrains License Server's algorithm implemented in Golang</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D964fe7c4c8&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">ggr</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=80=\n=94 A lightweight load balancer used to create big Selenium clusters</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dabaa78cc9d&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">kubectl</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Issue tracker and mirror of kubectl code</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D99dc749069&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">shiori</a>&nbsp;(Reached 1000=E2=AD=90=EF=B8=\n=8Fs) =E2=80=94 Simple bookmark manager built with Go</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D75e50beb79&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">go-ps</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 Find=2C list=2C and inspect processes from Go (golang).</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dbd3ca4df93&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">gormigrate</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Minimalistic database migration helper for Gorm ORM</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D2edcc47be2&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">libvirt/libvirt-go</a>&nbsp;(Reached 100=E2=AD=90=EF=\n=B8=8Fs) =E2=80=94 Automatic read-only mirror of http://libvirt.org/git/?p=\n=3Dlibvirt-go.git;a=3Dsummary</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D93d36a244a&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">launcher</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Osquery launcher=2C autoupdater=2C and packager</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dd2d3546122&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">slack-term</a>&nbsp;(Reached 2000=E2=AD=90=\n=EF=B8=8Fs) =E2=80=94 Slack client for your terminal</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D956e2e1277&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">cdp</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=80=\n=94 Package cdp provides type-safe bindings for the Chrome Debugging Proto=\ncol (CDP)=2C written in the Go programming language.</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Df2265a8137&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">GoQuiet</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 A shadowsocks plugin that obfuscates SS traffic and can defend=\n against active probing</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3De952bf0005&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">gocaml</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 :camel: Practical statically typed functional programming language=\n implementation with Go and LLVM</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D56d753fbd8&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">yarpc-go</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 A message passing platform for Go</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D4db13ca478&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">go-p2p</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 DEPRECATED (moved to tendermint/tendermint): Golang P2P library</li=\n>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D4291a4492e&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">aws-vault</a>&nbsp;(Reached 1000=E2=AD=90=EF=\n=B8=8Fs) =E2=80=94 A vault for securely storing and accessing AWS credenti=\nals in development environments</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dbd13136bf9&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">shippy</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 Code for the tutorial series on creating Microservices with Golang<=\n/li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D344defbf10&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">transfer.sh</a>&nbsp;(Reached 5000=E2=AD=90=\n=EF=B8=8Fs) =E2=80=94 Easy and fast file sharing from the command-line.</s=\ntrong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Df18309ae48&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">gofaas</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 A boilerplate Go and AWS Lambda app. Demonstrates an expert configu=\nration of 10+ AWS services to support running Go functions-as-a-service (F=\naaS).</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dd556a4f4b5&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">shiori</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 Simple bookmark manager built with Go</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D4d3a316a9f&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">chainlink</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 node of the decentralized oracle network=2C bridging on and off=\n-chain computation</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dc3fc43cede&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">gophers</a>&nbsp;(Reached 1000=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Gopher Artwork by Ashley McNamara</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3De3d01cdd3d&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">img</a>&nbsp;(Reached 1000=E2=AD=90=EF=B8=8F=\ns) =E2=80=94 Standalone=2C daemon-less=2C unprivileged Dockerfile and OCI=\n compatible container image builder.</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D5264ef3615&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">sensu-go</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 The next generation of the Sensu you know and love. We'd love h=\naving you involved =E2=9D=A4</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D5b8d19a805&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">pkg</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=80=\n=94 CoreStore: A standard library for e-commerce based on an optimized Mag=\nento 2 database structure. =E2=9A=A0=EF=B8=8F Under full development. DON'=\nT use/test it! =E2=9A=A0=EF=B8=8F</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dbac6183b83&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">vgo</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs) =E2=80=\n=94 [mirror] Versioned Go Prototype</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D784adf7046&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">go-genesis</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Public blockchain platform for building digital ecosystems</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D8a086c6ddb&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">matterircd</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Connect to your local/remote mattermost installation or to slac=\nk via your IRC-client.</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Da70c46976a&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">slack-term</a>&nbsp;(Reached 1000=E2=AD=90=\n=EF=B8=8Fs) =E2=80=94 Slack client for your terminal</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D7a7f5902aa&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">kube-arbitrator</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=\n=8Fs) =E2=80=94 Policy based resource sharing for Kubernetes cluster</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D9bc221940d&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">truss</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 Truss helps you build go-kit microservices without having to worry=\n about writing or maintaining boilerplate code.</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Db121db9dba&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">rulio</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 Rulio</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D2e6273ba7c&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">shiori</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 Simple bookmark manager built with Go</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D589e726f04&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">shortme</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Yet Another URL Shortening Service in Golang</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Df297757832&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">dat</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs) =E2=80=\n=94 Go Postgres Data Access Toolkit</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D8d059912a7&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">gosu</a>&nbsp;(Reached 2000=E2=AD=90=EF=B8=\n=8Fs) =E2=80=94 Simple Go-based setuid+setgid+setgroups+exec</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D9f2a6a3ef4&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">cyclone</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 A cloud native CI/CD platform built for container workflow</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D2212fb0695&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">freshpod</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Restart Pods on Minikube automatically on image rebuilds</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Deb325df760&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">kaneshin/pigeon</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=\n=8Fs) =E2=80=94 Google Cloud Vision API on Golang.</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D0e0a34fd3e&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">gopdf</a>&nbsp;(Reached 500=E2=AD=90=EF=B8=8Fs) =E2=\n=80=94 A simple library for generating PDF written in Go lang</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D37ae705cd8&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">go-swagger</a>&nbsp;(Reached 2000=E2=AD=90=\n=EF=B8=8Fs) =E2=80=94 Swagger 2.0 implementation for go</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dcabbf05bc7&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">firecamp</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Serverless Platform for the stateful services</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Dd1c1857bb2&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">goss</a>&nbsp;(Reached 2000=E2=AD=90=EF=B8=\n=8Fs) =E2=80=94 Quick and Easy server testing/validation</strong></li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><a href=3D\"https://phptrends.us11.list-man=\nage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3D35cefb7661&e=3D=\ncbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%=\n;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal;text-de=\ncoration: underline;\">replicator</a>&nbsp;(Reached 100=E2=AD=90=EF=B8=8Fs)=\n =E2=80=94 Automated Cluster and Job Scaling For HashiCorp Nomad</li>\n=09<li style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-=\nwebkit-text-size-adjust: 100%;\"><strong><a href=3D\"https://phptrends.us11.=\nlist-manage.com/track/click?u=3D98e163a8883174330aa8e29f0&id=3Df35b72107b&=\ne=3Dcbcfab6cd7\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adju=\nst: 100%;-webkit-text-size-adjust: 100%;color: #2BAADF;font-weight: normal=\n;text-decoration: underline;\">bettercap</a>&nbsp;(Reached 1000=E2=AD=90=EF=\n=B8=8Fs) =E2=80=94 The state of the art network attack and monitoring fram=\nework.</strong></li>\n</ul>\n\n                        </td>\n                    </tr>\n                </tbody></table>\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</td>\n=09=09=09=09<![endif]-->\n\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</tr>\n=09=09=09=09</table>\n=09=09=09=09<![endif]-->\n            </td>\n        </tr>\n    </tbody>\n</table><table border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"1=\n00%\" class=3D\"mcnTextBlock\" style=3D\"min-width: 100%;border-collapse: coll=\napse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100=\n%;-webkit-text-size-adjust: 100%;\">\n    <tbody class=3D\"mcnTextBlockOuter\">\n        <tr>\n            <td valign=3D\"top\" class=3D\"mcnTextBlockInner\" style=3D\"paddin=\ng-top: 9px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webki=\nt-text-size-adjust: 100%;\">\n              =09<!--[if mso]>\n=09=09=09=09<table align=3D\"left\" border=3D\"0\" cellspacing=3D\"0\" cellpaddi=\nng=3D\"0\" width=3D\"100%\" style=3D\"width:100%;\">\n=09=09=09=09<tr>\n=09=09=09=09<![endif]-->\n=09=09=09\n=09=09=09=09<!--[if mso]>\n=09=09=09=09<td valign=3D\"top\" width=3D\"600\" style=3D\"width:600px;\">\n=09=09=09=09<![endif]-->\n                <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cells=\npacing=3D\"0\" style=3D\"max-width: 100%;min-width: 100%;border-collapse: col=\nlapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 10=\n0%;-webkit-text-size-adjust: 100%;\" width=3D\"100%\" class=3D\"mcnTextContent=\nContainer\">\n                    <tbody><tr>\n\n                        <td valign=3D\"top\" class=3D\"mcnTextContent\" style=\n=3D\"padding-top: 0;padding-right: 18px;padding-bottom: 9px;padding-left: 1=\n8px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-=\nsize-adjust: 100%;word-break: break-word;color: #202020;font-family: Helve=\ntica;font-size: 16px;line-height: 150%;text-align: left;\">\n\n                            Have fun and a productive week!<br>\n- Slava\n                        </td>\n                    </tr>\n                </tbody></table>\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</td>\n=09=09=09=09<![endif]-->\n\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</tr>\n=09=09=09=09</table>\n=09=09=09=09<![endif]-->\n            </td>\n        </tr>\n    </tbody>\n</table></td>\n                            </tr>\n                            <tr>\n                                <td valign=3D\"top\" id=3D\"templateFooter\" s=\ntyle=3D\"background:#FAFAFA none no-repeat center/cover;mso-line-height-rul=\ne: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;backg=\nround-color: #FAFAFA;background-image: none;background-repeat: no-repeat;b=\nackground-position: center;background-size: cover;border-top: 0;border-bot=\ntom: 0;padding-top: 9px;padding-bottom: 9px;\"><table border=3D\"0\" cellpadd=\ning=3D\"0\" cellspacing=3D\"0\" width=3D\"100%\" class=3D\"mcnDividerBlock\" style=\n=3D\"min-width: 100%;border-collapse: collapse;mso-table-lspace: 0pt;mso-ta=\nble-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;=\ntable-layout: fixed !important;\">\n    <tbody class=3D\"mcnDividerBlockOuter\">\n        <tr>\n            <td class=3D\"mcnDividerBlockInner\" style=3D\"min-width: 100%;pa=\ndding: 10px 18px 25px;mso-line-height-rule: exactly;-ms-text-size-adjust:=\n 100%;-webkit-text-size-adjust: 100%;\">\n                <table class=3D\"mcnDividerContent\" border=3D\"0\" cellpaddin=\ng=3D\"0\" cellspacing=3D\"0\" width=3D\"100%\" style=3D\"min-width: 100%;border-t=\nop: 2px solid #EEEEEE;border-collapse: collapse;mso-table-lspace: 0pt;mso-=\ntable-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100=\n%;\">\n                    <tbody><tr>\n                        <td style=3D\"mso-line-height-rule: exactly;-ms-tex=\nt-size-adjust: 100%;-webkit-text-size-adjust: 100%;\">\n                            <span></span>\n                        </td>\n                    </tr>\n                </tbody></table>\n<!--\n                <td class=3D\"mcnDividerBlockInner\" style=3D\"padding: 18px;=\n\">\n                <hr class=3D\"mcnDividerContent\" style=3D\"border-bottom-col=\nor:none; border-left-color:none; border-right-color:none; border-bottom-wi=\ndth:0; border-left-width:0; border-right-width:0; margin-top:0; margin-rig=\nht:0; margin-bottom:0; margin-left:0;\" />\n-->\n            </td>\n        </tr>\n    </tbody>\n</table><table border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"1=\n00%\" class=3D\"mcnTextBlock\" style=3D\"min-width: 100%;border-collapse: coll=\napse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100=\n%;-webkit-text-size-adjust: 100%;\">\n    <tbody class=3D\"mcnTextBlockOuter\">\n        <tr>\n            <td valign=3D\"top\" class=3D\"mcnTextBlockInner\" style=3D\"paddin=\ng-top: 9px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webki=\nt-text-size-adjust: 100%;\">\n              =09<!--[if mso]>\n=09=09=09=09<table align=3D\"left\" border=3D\"0\" cellspacing=3D\"0\" cellpaddi=\nng=3D\"0\" width=3D\"100%\" style=3D\"width:100%;\">\n=09=09=09=09<tr>\n=09=09=09=09<![endif]-->\n=09=09=09\n=09=09=09=09<!--[if mso]>\n=09=09=09=09<td valign=3D\"top\" width=3D\"600\" style=3D\"width:600px;\">\n=09=09=09=09<![endif]-->\n                <table align=3D\"left\" border=3D\"0\" cellpadding=3D\"0\" cells=\npacing=3D\"0\" style=3D\"max-width: 100%;min-width: 100%;border-collapse: col=\nlapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 10=\n0%;-webkit-text-size-adjust: 100%;\" width=3D\"100%\" class=3D\"mcnTextContent=\nContainer\">\n                    <tbody><tr>\n\n                        <td valign=3D\"top\" class=3D\"mcnTextContent\" style=\n=3D\"padding-top: 0;padding-right: 18px;padding-bottom: 9px;padding-left: 1=\n8px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-=\nsize-adjust: 100%;word-break: break-word;color: #656565;font-family: Helve=\ntica;font-size: 12px;line-height: 150%;text-align: center;\">\n\n                            <strong>Our mailing address is:</strong><br>\n<div class=3D\"vcard\"><span class=3D\"org fn\">Slava Vishnyakov</span><div cl=\nass=3D\"adr\"><div class=3D\"street-address\">Ugreshskaya 18</div><div class=\n=3D\"extended-address\">Moscow reg</div><span class=3D\"locality\">Dzerzhinsky=\n</span> <span class=3D\"postal-code\">140091</span> <div class=3D\"country-na=\nme\">Russia</div></div><br><a href=3D\"//phptrends.us11.list-manage.com/vcar=\nd?u=3D98e163a8883174330aa8e29f0&id=3Dbaee93b39f\" class=3D\"hcard-download\">=\nAdd us to your address book</a></div> <br>\n<br>\nWant to change how you receive these emails?<br>\nYou can <a href=3D\"https://phptrends.us11.list-manage.com/profile?u=3D98e1=\n63a8883174330aa8e29f0&id=3Dbaee93b39f&e=3Dcbcfab6cd7\" style=3D\"mso-line-heig=\nht-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%=\n;color: #656565;font-weight: normal;text-decoration: underline;\">update yo=\nur preferences</a> or <a href=3D\"https://phptrends.us11.list-manage.com/un=\nsubscribe?u=3D98e163a8883174330aa8e29f0&id=3Dbaee93b39f&e=3Dcbcfab6cd7&c=3D6=\n9e50544c0\" style=3D\"mso-line-height-rule: exactly;-ms-text-size-adjust: 10=\n0%;-webkit-text-size-adjust: 100%;color: #656565;font-weight: normal;text-=\ndecoration: underline;\">unsubscribe from this list</a><br>\n<a href=3D\"http://www.mailchimp.com/monkey-rewards/?utm_source=3Dfreemium_=\nnewsletter&utm_medium=3Demail&utm_campaign=3Dmonkey_rewards&aid=3D98e163a8=\n883174330aa8e29f0&afl=3D1\"><img src=3D\"https://cdn-images.mailchimp.com/mo=\nnkey_rewards/MC_MonkeyReward_15.png\" border=3D\"0\" alt=3D\"Email Marketing P=\nowered by MailChimp\" title=3D\"MailChimp Email Marketing\" width=3D\"139\" hei=\nght=3D\"54\"></a>&nbsp;\n                        </td>\n                    </tr>\n                </tbody></table>\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</td>\n=09=09=09=09<![endif]-->\n\n=09=09=09=09<!--[if mso]>\n=09=09=09=09</tr>\n=09=09=09=09</table>\n=09=09=09=09<![endif]-->\n            </td>\n        </tr>\n    </tbody>\n</table></td>\n                            </tr>\n                        </table>\n=09=09=09=09=09=09<!--[if (gte mso 9)|(IE)]>\n=09=09=09=09=09=09</td>\n=09=09=09=09=09=09</tr>\n=09=09=09=09=09=09</table>\n=09=09=09=09=09=09<![endif]-->\n                        <!-- // END TEMPLATE -->\n                    </td>\n                </tr>\n            </table>\n        </center>\n    <img src=3D\"https://phptrends.us11.list-manage.com/track/open.php?u=3D=\n98e163a8883174330aa8e29f0&id=3D69e50544c0&e=3Dcbcfab6cd7\" height=3D\"1\" wi=\ndth=3D\"1\"></body>\n</html>"
  },
  {
    "path": "devtools/fixtures/messages_body/html-with-inline-styles",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n  <!--[if !mso]><!-->\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n  <!--<![endif]-->\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <title></title>\n  <!--[if (gte mso 9)|(IE)]>\n  <style type=\"text/css\">\n    table {border-collapse: collapse !important;}\n  </style>\n  <![endif]-->\n</head>\n<body style=\"margin-top:0 !important;margin-bottom:0 !important;margin-right:0 !important;margin-left:0 !important;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff;\" >\n  <center class=\"wrapper\" style=\"width:100%;table-layout:fixed;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;\" >\n    <div class=\"webkit\" style=\"max-width:600px;margin-top:0;margin-bottom:0;margin-right:auto;margin-left:auto;\" >\n      <!--[if (gte mso 9)|(IE)]>\n      <table width=\"600\" align=\"center\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n      <tr>\n      <td style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n      <![endif]-->\n      <table class=\"outer\" align=\"center\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;Margin:0 auto;width:100%;max-width:600px;\" >\n        <tr>\n          <td class=\"full-width-image\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <img src=\"http://www.inbucket.org/email-assets/responsive/header.jpg\" width=\"600\" alt=\"\" style=\"border-width:0;width:100%;max-width:600px;height:auto;\" />\n          </td>\n        </tr>\n        <tr>\n          <td class=\"one-column\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n              <tr>\n                <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;text-align:left;\" >\n                  <p class=\"h1\" style=\"Margin:0;font-weight:bold;font-size:14px;Margin-bottom:10px;\" >Lorem ipsum dolor sit amet</p>\n                    <p style=\"Margin:0;font-size:14px;Margin-bottom:10px;\" >\n                      Compare to:\n                      <a href=\"http://tutsplus.github.io/creating-a-future-proof-responsive-email-without-media-queries/index.html\" style=\"color:#ee6a56;text-decoration:underline;\" >\n                      tutsplus sample</a>\n                    </p>\n\n                    <p style=\"Margin:0;font-size:14px;Margin-bottom:10px;\" >Copyright (c) 2015, Envato Tuts+<br/>\n                    All rights reserved.</p>\n\n                    <p style=\"Margin:0;font-size:14px;Margin-bottom:10px;\" >Redistribution and use in source and binary forms, with or without\n                    modification, are permitted provided that the following conditions are met:</p>\n\n                    <ul>\n                    <li>Redistributions of source code must retain the above copyright notice, this\n                      list of conditions and the following disclaimer.</li>\n\n                    <li>Redistributions in binary form must reproduce the above copyright notice,\n                      this list of conditions and the following disclaimer in the documentation\n                      and/or other materials provided with the distribution.</li>\n                    </ul>\n\n                    <p style=\"Margin:0;font-size:14px;Margin-bottom:10px;\" >THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n                    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n                    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n                    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n                    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n                    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n                    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n                    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n                    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n                    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>\n                </td>\n              </tr>\n            </table>\n          </td>\n        </tr>\n        <tr>\n          <td class=\"two-column\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;text-align:center;font-size:0;\" >\n            <!--[if (gte mso 9)|(IE)]>\n            <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n            <tr>\n            <td width=\"50%\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:300px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;\" >\n                    <table class=\"contents\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:left;\" >\n                      <tr>\n                        <td style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n                          <img src=\"http://www.inbucket.org/email-assets/responsive/two-column-01.jpg\" width=\"280\" alt=\"\" style=\"border-width:0;width:100%;max-width:280px;height:auto;\" />\n                        </td>\n                      </tr>\n                      <tr>\n                        <td class=\"text\" style=\"padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;\" >\n                          Maecenas sed ante pellentesque, posuere leo id, eleifend dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.\n                        </td>\n                      </tr>\n                    </table>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td><td width=\"50%\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:300px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;\" >\n                    <table class=\"contents\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:left;\" >\n                      <tr>\n                        <td style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n                          <img src=\"http://www.inbucket.org/email-assets/responsive/two-column-02.jpg\" width=\"280\" alt=\"\" style=\"border-width:0;width:100%;max-width:280px;height:auto;\" />\n                        </td>\n                      </tr>\n                      <tr>\n                        <td class=\"text\" style=\"padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;\" >\n                          Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Maecenas sed ante pellentesque, posuere leo id, eleifend dolor.\n                        </td>\n                      </tr>\n                    </table>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td>\n            </tr>\n            </table>\n            <![endif]-->\n          </td>\n        </tr>\n        <tr>\n          <td class=\"three-column\" style=\"padding-right:0;padding-left:0;text-align:center;font-size:0;padding-top:10px;padding-bottom:10px;\" >\n            <!--[if (gte mso 9)|(IE)]>\n            <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n            <tr>\n            <td width=\"200\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:200px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;\" >\n                    <table class=\"contents\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:center;\" >\n                      <tr>\n                        <td style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n                          <img src=\"http://www.inbucket.org/email-assets/responsive/three-column-01.jpg\" width=\"180\" alt=\"\" style=\"border-width:0;width:100%;max-width:180px;height:auto;\" />\n                        </td>\n                      </tr>\n                      <tr>\n                        <td class=\"text\" style=\"padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;\" >\n                          Scelerisque congue eros eu posuere. Praesent in felis ut velit pretium lobortis rhoncus ut erat.\n                        </td>\n                      </tr>\n                    </table>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td><td width=\"200\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:200px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;\" >\n                    <table class=\"contents\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:center;\" >\n                      <tr>\n                        <td style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n                          <img src=\"http://www.inbucket.org/email-assets/responsive/three-column-02.jpg\" width=\"180\" alt=\"\" style=\"border-width:0;width:100%;max-width:180px;height:auto;\" />\n                        </td>\n                      </tr>\n                      <tr>\n                        <td class=\"text\" style=\"padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;\" >\n                          Maecenas sed ante pellentesque, posuere leo id, eleifend dolor.\n                        </td>\n                      </tr>\n                    </table>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td><td width=\"200\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:200px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;\" >\n                    <table class=\"contents\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:center;\" >\n                      <tr>\n                        <td style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n                          <img src=\"http://www.inbucket.org/email-assets/responsive/three-column-03.jpg\" width=\"180\" alt=\"\" style=\"border-width:0;width:100%;max-width:180px;height:auto;\" />\n                        </td>\n                      </tr>\n                      <tr>\n                        <td class=\"text\" style=\"padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;\" >\n                          Praesent laoreet malesuada cursus. Maecenas scelerisque congue eros eu posuere.\n                        </td>\n                      </tr>\n                    </table>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td>\n            </tr>\n            </table>\n            <![endif]-->\n          </td>\n        </tr>\n        <tr>\n          <td class=\"three-column\" style=\"padding-right:0;padding-left:0;text-align:center;font-size:0;padding-top:10px;padding-bottom:10px;\" >\n            <!--[if (gte mso 9)|(IE)]>\n            <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n            <tr>\n            <td width=\"200\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:200px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;\" >\n                    <p class=\"h2\" style=\"Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;\" >Fashion</p>\n                    <p style=\"Margin:0;\" >Class eleifend aptent taciti sociosqu ad litora torquent conubia</p>\n                    <p style=\"Margin:0;\" ><a href=\"#\" style=\"color:#ee6a56;text-decoration:underline;\" >Read requirements</a></p>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td><td width=\"200\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:200px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;\" >\n                    <p class=\"h2\" style=\"Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;\" >Photography</p>\n                    <p style=\"Margin:0;\" >Maecenas sed ante pellentesque, posuere leo id, eleifend dolor</p>\n                    <p style=\"Margin:0;\" ><a href=\"#\" style=\"color:#ee6a56;text-decoration:underline;\" >See examples</a></p>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td><td width=\"200\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:200px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;\" >\n                    <p class=\"h2\" style=\"Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;\" >Design</p>\n                    <p style=\"Margin:0;\" >Class aptent taciti sociosqu eleifend ad litora per conubia nostra</p>\n                    <p style=\"Margin:0;\" ><a href=\"#\" style=\"color:#ee6a56;text-decoration:underline;\" >See the winners</a></p>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td>\n            </tr>\n            <tr>\n            <td width=\"200\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:200px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;\" >\n                    <p class=\"h2\" style=\"Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;\" >Cooking</p>\n                    <p style=\"Margin:0;\" >Class aptent taciti eleifend sociosqu ad litora torquent conubia</p>\n                    <p style=\"Margin:0;\" ><a href=\"#\" style=\"color:#ee6a56;text-decoration:underline;\" >Read recipes</a></p>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td><td width=\"200\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:200px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;\" >\n                    <p class=\"h2\" style=\"Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;\" >Woodworking</p>\n                    <p style=\"Margin:0;\" >Maecenas sed ante pellentesque, posuere leo id, eleifend dolor</p>\n                    <p style=\"Margin:0;\" ><a href=\"#\" style=\"color:#ee6a56;text-decoration:underline;\" >See examples</a></p>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td><td width=\"200\" valign=\"top\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column\" style=\"width:100%;max-width:200px;display:inline-block;vertical-align:top;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;\" >\n                    <p class=\"h2\" style=\"Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;\" >Craft</p>\n                    <p style=\"Margin:0;\" >Class aptent taciti sociosqu ad eleifend litora per conubia nostra</p>\n                    <p style=\"Margin:0;\" ><a href=\"#\" style=\"color:#ee6a56;text-decoration:underline;\" >Vote now</a></p>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td>\n            </tr>\n            </table>\n            <![endif]-->\n          </td>\n        </tr>\n        <tr>\n          <td class=\"left-sidebar\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;text-align:center;font-size:0;\" >\n            <!--[if (gte mso 9)|(IE)]>\n            <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n            <tr>\n            <td width=\"100\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column left\" style=\"width:100%;display:inline-block;vertical-align:middle;max-width:100px;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;\" >\n                    <img src=\"http://www.inbucket.org/email-assets/responsive/sidebar-01.jpg\" width=\"80\" alt=\"\" style=\"border-width:0;\" />\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td><td width=\"500\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column right\" style=\"width:100%;display:inline-block;vertical-align:middle;max-width:500px;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;\" >\n                    Praesent laoreet malesuada cursus. Maecenas scelerisque congue eros eu posuere. Praesent in felis ut velit pretium lobortis rhoncus ut erat. <a href=\"#\" style=\"text-decoration:underline;color:#85ab70;\" >Read&nbsp;on</a>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td>\n            </tr>\n            </table>\n            <![endif]-->\n          </td>\n        </tr>\n        <tr>\n          <td class=\"right-sidebar\" dir=\"rtl\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;text-align:center;font-size:0;\" >\n            <!--[if (gte mso 9)|(IE)]>\n            <table width=\"100%\" dir=\"rtl\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n            <tr>\n            <td width=\"100\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column left\" dir=\"ltr\" style=\"width:100%;display:inline-block;vertical-align:middle;max-width:100px;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;\" >\n                    <img src=\"http://www.inbucket.org/email-assets/responsive/sidebar-02.jpg\" width=\"80\" alt=\"\" style=\"border-width:0;\" />\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td><td width=\"500\" style=\"padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;\" >\n            <![endif]-->\n            <div class=\"column right\" dir=\"ltr\" style=\"width:100%;display:inline-block;vertical-align:middle;max-width:500px;\" >\n              <table width=\"100%\" style=\"border-spacing:0;font-family:sans-serif;color:#333333;\" >\n                <tr>\n                  <td class=\"inner contents\" style=\"padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;\" >\n                    Maecenas sed ante pellentesque, posuere leo id, eleifend dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra. <a href=\"#\" style=\"text-decoration:underline;color:#70bbd9;\" >Per&nbsp;inceptos</a>\n                  </td>\n                </tr>\n              </table>\n            </div>\n            <!--[if (gte mso 9)|(IE)]>\n            </td>\n            </tr>\n            </table>\n            <![endif]-->\n          </td>\n        </tr>\n      </table>\n      <!--[if (gte mso 9)|(IE)]>\n      </td>\n      </tr>\n      </table>\n      <![endif]-->\n    </div>\n  </center>\n</body>\n</html>"
  },
  {
    "path": "devtools/fixtures/messages_body/spam-htmj-and-styles",
    "content": "<html><head></head><body><div>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"\nname=\"viewport\" />\n<title>JustFab</title>\n<style type=\"text/css\"><!--\n    a, a:hover, a:visited, a:link, a:active {text-decoration: none;}a\n{text-decoration: none}\n\n\n   .ReadMsgBody {width: 100%; background-color: #ffffff;}\n   .ExternalClass {width: 100%; background-color: #ffffff;}\n   body   {width: 100%; background-color: #ffffff; margin:0; padding:0;\n-webkit-font-smoothing: antialiased;font-family: Georgia, Times, serif}\n   table {border-collapse: collapse;margin: 0 auto}\n\n   @media only screen and (max-width: 640px)  {\n               body[yahoo] .deviceWidth {width:440px!important;\npadding:0;}\n               body[yahoo] .center {text-align: center!important;}\n         }\n\n   @media only screen and (max-width: 479px) {\n               body[yahoo] .deviceWidth {width:280px!important;\npadding:0;}\n               body[yahoo] .center {text-align: center!important;}\n         }\n\n    body {padding: 5px; padding-top: 0px;font-family:arial, Verdana,\nsans-serif !important;font-size:12px;}\n    [class=product] {min-width: 100% !important; padding-left: 0px\n!important; padding-right: 0px !important;}\n    [class=section_title] {padding-top: 5px !important;}\n    [class=categories_mail] {padding-left: 0px !important; padding-right:\n0px !important;}\n    [id=second] {border-top: 0px !important;}\n    [id=last] {display: none !important;}\n    [class=hide] { display:none;}\n    }\n--></style>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11226&L=165&N=3653\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<!-- Wrapper -->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"100%\" valign=\"top\" bgcolor=\"#ffffff\"\nstyle=\"padding-top:20px\"><!-- Start Header-->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nclass=\"deviceWidth\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td width=\"100%\" bgcolor=\"#ffffff\">\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nclass=\"deviceWidth\" style=\"width: 600px; height: 15px;\">\n<tbody>\n<tr>\n<td width=\"100%\" bgcolor=\"#ffffff\">\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"left\"\nclass=\"deviceWidth\">\n<tbody>\n<tr>\n<td style=\"padding:10px 20px\" class=\"center\"></td>\n</tr>\n</tbody>\n</table>\n<!-- End Logo --> <!-- Nav --> <!-- End Nav -->\n<table class=\"deviceWidth\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nalign=\"center\" bgcolor=\"#eeeeed\" style=\"width: 600px;\">\n</table>\n<!-- End One Column --></td>\n</tr>\n</tbody>\n</table>\n<!-- Logo -->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"left\"\nclass=\"deviceWidth\">\n<tbody>\n<tr>\n<td style=\"padding:10px 0px;   padding-bottom: 14px;\" class=\"center\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2015/10/15102015/JustFab_Logo.png\"\nalt=\"www.JustFab.fr\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<!-- End Logo --> <!-- Nav -->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"right\"\nclass=\"deviceWidth\">\n<tbody>\n<tr>\n<td class=\"center\" style=\"font-size: 11px; color: #272727; font-weight:\nlight; text-align: right; font-family: Arial; line-height: 20px;\nvertical-align: middle; padding:16px 20px; font-weight:bold;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">CHAUSSURES</a>\n&nbsp;&nbsp;&nbsp;                                         <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">SACS</a> &nbsp;&nbsp;&nbsp;\n                                    <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">V&Ecirc;TEMENTS</a>\n&nbsp;&nbsp;&nbsp;                                         <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">COLLECTIONS</a>\n&nbsp;&nbsp;&nbsp;                                         <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #FF0000;\">PROMOS</a></td>\n</tr>\n</tbody>\n</table>\n<!-- End Nav -->\n<table class=\"deviceWidth\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nalign=\"center\" bgcolor=\"#eeeeed\" style=\"width: 600px;\">\n</table>\n<!-- End One Column --></td>\n</tr>\n</tbody>\n</table>\n<table class=\"deviceWidth\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nalign=\"center\" bgcolor=\"#eeeeed\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_01.jpg\"\nalt=\"DOS MEJOR QUE UNO&#10;&#10;      \" border=\"0\" style=\"display: block;\nborder-radius: 4px;\" /></a></td>\n</tr>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_02.jpg\"\nalt=\"DISFR&Uacute;TALO\" /> </a></td>\n</tr>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_03.jpg\"\nalt=\"&iquest;Por qu&eacute; comprar s&oacute;lo un par cuando PUEDES TENER\n2?\" /> </a></td>\n</tr>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_04.jpg\"\nalt=\"T&uacute; que adoras los zapatos...&iexcl;Hazte miembro VIP de Justfab\ny disfruta de nuestra oferta de bienvenida: &iexcl;2 art&iacute;culos por\nel precio de 1! 2mejorque1\" /> </a></td>\n</tr>\n</tbody>\n</table>\n<!-- End One Column --></td>\n</tr>\n</tbody>\n</table>\n<center> <center>\n<p align=\"center\"><font face=\"Arial, Helvetica, sans-serif\" color=\"#666666\"\nsize=\"1\"><font color=\"#000000\"><font size=\"2\"><strong>Just<font\ncolor=\"#ff0066\">Fab</font></strong></font> | Just Fabulous - Avenida\nDiagonal 611 - 08028 Barcelona </font><br /><span\nstyle=\"font-color:grey;font-size:x-small;\">*voir conditions de l'offre sur\nle site</span><br /> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8114&F=H\"\ntarget=\"_blank\" style=\"color:#ff0066; text-decoration:underline;\">Politique\nde confidentialit&eacute;;</a> | <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8117&F=H\"\ntarget=\"_blank\" style=\"color:#ff0066;\ntext-decoration:underline;\">Contactez-nous</a><br /></font></p>\n<p><font face=\"Arial, Helvetica, sans-serif\" color=\"#666666\" size=\"1\"><br\n/></font></p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\"\nstyle=\"width: 600px;\">\n</table>\n<font face=\"Arial, Helvetica, sans-serif\" color=\"#666666\" size=\"1\">\n<table cellpadding=\"0\" cellspacing=\"0\" align=\"center\" border=\"0\"\nstyle=\"width: 600px;\">\n<tbody>\n<tr>\n<td cellpadding=\"0\" cellspacing=\"0\" width=\"479\" align=\"center\" border=\"0\"\nstyle=\"vertical-align:top;font-family:font-family: Calibri, sans-serif;\nmargin:0; font-size:11px; line-height:13px; padding-left:50px;\npadding-right:50px; -webkit-text-size-adjust: none\"><span\nclass=\"applelinks\" style=\"color:#666666; font-family:Calibri, sans-serif;\nfont-size:11px; line-height:13px; -webkit-text-size-adjust: none\"><br\n/></span> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8115&F=H\"\ntarget=\"_blank\" style=\"color:#66666; text-decoration:underline;\"><span\nstyle=\"text-decoration: underline;\"></span></a><br /><br /> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8109&F=H\"\ntarget=\"_blank\" style=\"color:#ff0066; font-family:Calibri, sans-serif;\ntext-decoration:underline;\"><span style=\"text-decoration:\nunderline;\"></span></a><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8116&F=H\"\ntarget=\"_blank\" style=\"color:#666666; text-decoration:underline;\"><span\nstyle=\"text-decoration: underline;\"></span></a></td>\n</tr>\n</tbody>\n</table>\n<link rel=\"icon\" type=\"image/png\"\nhref=\"//jf-eu-cdn.justfab.com/image/site/global/icon/favicon.ico\" />\n<link rel=\"shortcut icon\" type=\"image/png\"\nhref=\"//jf-eu-cdn.justfab.com/image/site/global/icon/favicon.ico\" />\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11226\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n<!--END FOOTER MAIL--></font>\n<p>&nbsp;</p>\n</center></center></div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=11664742&L=165&N=11226&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_ad062cc5824a44b4f36f410e0dd49ebc--\n\n"
  },
  {
    "path": "devtools/fixtures/raw_emails/email-with-iso-8859-1-encoding",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\nX-Original-To: dev@caliopen.local\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\n by mail4.gandi.net (Postfix) with ESMTP id 98356120A9E;\n Wed, 26 Nov 2014 12:46:30 +0100 (CET)\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\nX-Spam-Score: 0.001\nX-Spam-Level:\nX-Spam-Status: No, score=0.001 tagged_above=-999 required=5\n tests=[BAYES_50=0.001]\nReceived: from mail4.gandi.net ([217.70.183.210])\n by localhost (mfiltercorp1-d.gandi.net [217.70.183.155]) (amavisd-new,\n port 10024)\n with ESMTP id 566gwAeJ5ZPV; Wed, 26 Nov 2014 12:46:28 +0100 (CET)\nReceived: from brassens.heberge.info (brassens.heberge.info [91.194.60.2])\n (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))\n (No client certificate requested)\n by mail4.gandi.net (Postfix) with ESMTPS id 59A5F120A27;\n Wed, 26 Nov 2014 12:46:28 +0100 (CET)\nReceived: from localhost (localhost [127.0.0.1])\n by brassens.heberge.info (Postfix) with ESMTP id C930069DA65D4;\n Wed, 26 Nov 2014 12:46:27 +0100 (CET)\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\nReceived: from brassens.heberge.info ([91.194.60.2])\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\n with ESMTP id oZUv7XJle5jQ; Wed, 26 Nov 2014 12:46:24 +0100 (CET)\nReceived: from brassens.heberge.info (localhost [127.0.0.1])\n by brassens.heberge.info (Postfix) with ESMTP id DAADD69D9A570;\n Wed, 26 Nov 2014 12:46:23 +0100 (CET)\nReceived: from localhost (localhost [127.0.0.1])\n by brassens.heberge.info (Postfix) with ESMTP id 32E8869DA65D4\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 12:46:22 +0100 (CET)\nX-Virus-Scanned: Debian amavisd-new at brassens.heberge.info\nReceived: from brassens.heberge.info ([91.194.60.2])\n by localhost (brassens.heberge.info [127.0.0.1]) (amavisd-new, port 10024)\n with ESMTP id uuBA9BpjWtsR for <caliopdev@caliop.net>;\n Wed, 26 Nov 2014 12:46:18 +0100 (CET)\nReceived: from brasil.brainstorm.fr (brasil.brainstorm.fr [193.56.58.33])\n by brassens.heberge.info (Postfix) with ESMTP id 24D0D69D9A570\n for <caliopdev@caliop.net>; Wed, 26 Nov 2014 12:46:18 +0100 (CET)\nReceived: by brasil.brainstorm.fr (Postfix, from userid 1000)\n id 065A420B31D5C; Wed, 26 Nov 2014 12:46:17 +0100 (CET)\nDate: Wed, 26 Nov 2014 12:46:17 +0100\nFrom: Laurent Chemla <laurent@brainstorm.fr>\nTo: caliopdev@caliop.net\nMessage-ID: <20141126114617.GV8770@brasil.brainstorm.fr>\nMIME-Version: 1.0\nContent-Disposition: inline\nUser-Agent: Mutt/1.5.20 (2009-06-14)\nSubject: [Caliopdev] [svetlana.meyer@ensc.fr: Re: Bravo!]\nX-BeenThere: caliopdev@caliop.net\nX-Mailman-Version: 2.1.15\nPrecedence: list\nReply-To: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\n <caliopdev@caliop.net>\nList-Id: =?iso-8859-1?q?Liste_de_d=E9veloppement_Caliopen?=\n <caliopdev.caliop.net>\nList-Unsubscribe: <http://brassens.heberge.info/cgi-bin/mailman/options/caliopdev>,\n <mailto:caliopdev-request@caliop.net?subject=unsubscribe>\nList-Archive: <http://brassens.heberge.info/cgi-bin/mailman/private/caliopdev/>\nList-Post: <mailto:caliopdev@caliop.net>\nList-Help: <mailto:caliopdev-request@caliop.net?subject=help>\nList-Subscribe: <http://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev>,\n <mailto:caliopdev-request@caliop.net?subject=subscribe>\nContent-Type: text/plain; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: quoted-printable\nErrors-To: caliopdev-bounces@caliop.net\nSender: \"Caliopdev\" <caliopdev-bounces@caliop.net>\n\n----- Forwarded message from Svetlana Meyer <svetlana.meyer@ensc.fr> -----\n\nLe 26 novembre 2014 12:34, Svetlana Meyer <svetlana.meyer@ensc.fr> a =E9cri=\nt :\n\n> Bonjour,\n> Je viens de d=E9couvrir votre projet et je tenais =E0 vous dire qu'il est\n> g=E9nial. D=E9j=E0, sur le principe : un syst=E8me de messagerie intellig=\nent ET\n> prot=E9g=E9, c'est vraiment ce dont on a besoin en ce moment.\n> Et surtout sur l'interface que vous avez cr=E9=E9e : il est rare qu'on\n> atteigne un tel niveau de plaisir d'utilisation sur un simple prototype\n> (quelle frustration de ne pouvoir tester toutes les fonctionnalit=E9s. Po=\nur\n> venir d'une =E9cole d'ing=E9 qui attache une grande importance =E0 la con=\nception\n> centr=E9e utilisateur (l'ENSC de Bordeaux), je dois dire qu'en terme de\n> conception, vous avez assur=E9.\n> Sinc=E8rement bravo ! D=E8s qu'il sort, je m'y met.\n> Je suis malheureusement trop mauvaise en code pour vous aider =E0 ce nive=\nau,\n> mais si vous avez besoin de diffuser votre prototype pour avoir des retou=\nrs\n> sur son usabilit=E9 ou autre, je le ferais avec plaisir.\n>\n> Tr=E8s bonne continuation !\n>\n> --\n> Svetlana Meyer, =E9l=E8ve ing=E9nieur, 3=E8me ann=E9e =E0 l'ENSC.\n>\n\n\n\n-- =\n\nSvetlana Meyer, =E9l=E8ve ing=E9nieur, 3=E8me ann=E9e =E0 l'ENSC.\n\n----- End forwarded message -----\n_______________________________________________\nCaliopdev mailing list\nCaliopdev@caliop.net\nhttp://brassens.heberge.info/cgi-bin/mailman/listinfo/caliopdev\n"
  },
  {
    "path": "devtools/fixtures/raw_emails/html-in-plain-body",
    "content": "Received: from pb-mx12.pobox.com (pb-mx12.pobox.com [64.147.108.53])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby mail1.alpha.caliopen.org (Postfix) with ESMTPS id 7CB254031C\n\tfor <someoneos@alpha.caliopen.org>; Sat, 21 Apr 2018 12:28:32 +0200 (CEST)\nReceived: from pb-mx12.nyi.icgroup.com (localhost.local [127.0.0.1])\n\tby pb-mx12.pobox.com (Postfix) with ESMTP id 3332917069;\n\tSat, 21 Apr 2018 06:30:29 -0400 (EDT)\nX-Pobox-Filter-Version: 3.1\nX-Pobox-Loop-ID: 97699de182b5846295308e8103444ad000e8e34c\nDelivered-To: someone@pobox.com\nX-Pobox-Delivery-ID: E285A2-8BB3817067-1524306628-16058349!pb-mx12.pobox.com\nReceived: from o114.p8.mailjet.com (o114.p8.mailjet.com [87.253.233.114])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby pb-mx12.pobox.com (Postfix) with ESMTPS id 8BB3817067\n\tfor <someone@pobox.com>; Sat, 21 Apr 2018 06:30:28 -0400 (EDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; q=dns/txt;\n  d=hoalen.com; i=info@hoalen.com; s=mailjet;\n  h=message-id:mime-version:from:to:subject:date:list-id:list-unsubscribe:\n  precedence:x-csa-complaints:x-mj-mid:x-mj-smtpguid:x-report-abuse-to:\n  content-type;\n  bh=Z6shVnZrbxs/K1NeSnjTJNsYnyT9pTVIKVuNwuFeKsQ=;\n  b=oTilStolAxDv5SKtF8iJqoEtp6hfwSd2/VXz43PCxYQ08ZqlUxGkqK+jq\n 2qt3fKRS99xrdZ4VOpQ2fzLSev4fRH1sEHSffrfLlf8v+piVV4db3PvOJYQ4\n OHRRTEKQSfkqs5JIV4eMp9h+ues6WKGgnRcNVwjfkykHsRcXr9zaDc=\nMessage-Id: <66c3d094.AMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9MAEROrAACv-ABa2xLD@mailjet.com>\nMIME-Version: 1.0\nFrom: \"HOALEN SALTWATER Co.\" <info@hoalen.com>\nTo: someone@pobox.com\nSubject: Classic chambray styles.\nDate: Sat, 21 Apr 2018 10:30:27 +0000\nList-Id: <info.hoalen.com.xix6-xouz8.mj>\nList-Unsubscribe: <mailto:unsub-66c3d094.xix6.ss8l5kj6yvro@bnc3.mailjet.com>\nPrecedence: bulk\nX-CSA-Complaints: whitelist-complaints@eco.de\nX-MJ-Mid:\n\tAMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9MAEROrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA\nX-MJ-SMTPGUID: 9bbe6a08-052e-4a98-b735-dcbf94b7d0c4\nX-REPORT-ABUSE-TO: Message sent by Mailjet please report to\n\tabuse@mailjet.com with a copy of the message\nContent-Type: multipart/alternative; boundary=\"=-TmdQenFaaBdQnpCMWq90\"\nX-Pobox-Client-Address: 87.253.233.114\nX-Pobox-Client-Name: o114.p8.mailjet.com\nX-Pobox-Client-HELO: o114.p8.mailjet.com\nX-Pobox-Original-Sender:\n 66c3d094.AMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9MAEROrAACv-ABa2xLD@bnc3.mailjet.com\n\n--=-TmdQenFaaBdQnpCMWq90\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.=\nw3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n<head>\n    <meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3Dutf-8=\n\" />\n    <meta name=3D\"viewport\" content=3D\"width=3Ddevice-width, initial-scale=\n=3D1.0\"/>\n    <title>HOALEN - Saltwater Players Co.</title>\n    <style type=3D\"text/css\">\n        html {\n            /*font-size: 100%;*/\n        }\n\n        body {\n            font-family: Arial, Helvetica, sans-serif;\n            color: #626262;\n            font-size:80%;\n            font-size:14px;\n        }\n\n        .about {\n            width:900px;\n        }\n\n        .text-about {\n            width:900px;\n        }\n\n        .logo-main {\n\n        }\n        a {\n            color: #626262;\n            text-decoration: none;\n        }\n        a:link {color: #626262;}\n        a:visited {color: #626262;}\n        a:hover {color: #626262;text-decoration: underline}\n        a:active {color: #626262;}\n\n        #outlook a {padding:0;}\n        body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text=\n-size-adjust:100%; margin:0; padding:0;}\n        .ExternalClass {width:100%;}\n        .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalCla=\nss font, .ExternalClass td, .ExternalClass div {line-height: 100%;}\n        #backgroundTable {margin:0; padding:0; width:100% !important; line-=\nheight: 100% !important;}\n\n        img {outline:none; text-decoration:none; -ms-interpolation-mode: bi=\ncubic;}\n        a img {border:none;vertical-align: bottom;}\n        .image_fix {display:block;}\n        p {margin: 1em 0;}\n        h1, h2, h3, h4, h5, h6 {color: #626262 !important;}\n        h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color: #626262 !important;}\n        h1 a:active, h2 a:active,  h3 a:active, h4 a:active, h5 a:active, h=\n6 a:active {color: #626262 !important;}\n        h1 a:visited, h2 a:visited,  h3 a:visited, h4 a:visited, h5 a:visit=\ned, h6 a:visited {color: #626262 !important;}\n\n        table td {border-collapse: collapse;}\n\n        a[href^=3D\"tel\"], a[href^=3D\"sms\"] {\n            text-decoration: none;\n            color: #626262;\n            pointer-events: none;\n            cursor: default;\n        }\n\n        @media only screen and (max-device-width: 480px) {\n\n\n            body {\n                font-size: 5.5px;\n                font-size: 8px;\n            }\n\n\n\n            .about {\n                /*font-size: 200%;*/\n                width:100%;\n                letter-spacing: 0.1em!important;\n            }\n\n            .text-about {\n\n                /*width:95%;*/\n                /*font-size: 50%;*/\n                text-align: center;\n                width:100%;\n\n\n            }\n\n            .footer {\n                width:100%;\n                /*font-size: 100%;*/\n                text-align: center;\n            }\n\n\n\n            .content {\n                width:100%;\n                line-height: 0;\n            }\n\n\n\n            .containerdemi {\n                max-width:100%!important;\n                width:100%;\n            }\n\n            .demi {\n                display:inline-block!important;\n                width:50%!important;\n                border:0!important;\n                margin:0!important;\n                padding:0!important;\n            }\n\n            .full {\n                width:100%!important;\n                border:0!important;\n                margin:0!important;\n                padding:0!important;\n            }\n\n            .image {\n                width:100%!important;\n\n                border:0!important;\n                margin:0!important;\n                padding:0!important;\n            }\n\n\n\n            .logo-main {\n                width:250px;\n\n            }\n\n            .logo-footer {\n                width:25em;\n            }\n\n\n            .mobile-break {\n                display:block;\n                width:0;\n                height:0;\n                overflow:hidden;\n            }\n\n            .button-mobile {\n                display:block;\n\n            }\n\n            .footer-legal {\n                font-size: 1.4em!important;\n            }\n\n            .mobile-break-about {\n                margin:2em auto;\n            }\n\n            .social-mobile {\n                width:8em;\n            }\n\n            .mobile-seabass {\n                width:100px;\n            }\n\n\n        }\n\n    </style>\n</head>\n\n<body>\n\n<div class=3D\"header\" style=3D\"width:100%;max-width: 900px;text-align:cente=\nr;margin:0 auto\">\n    <a href=3D\"http://www.hoalen.com\"><img class=3D\"logo-main\" style=3D\"wid=\nth: 100%!important;\" src=3D\"http://www.hoalen.com/nl/general/logo-hoalen-he=\nader-newsletter-2.jpg\" border=3D\"0\" class=3D\"\"/></a>\n</div>\n\n<!-- Content -->\n<div style=3D\"width:0;height:0;overflow:hidden\">Elegant beach look - </div>\n\n<div class=3D\"content\" style=3D\"width:100%;text-align:center;margin:0 auto\"=\n>\n    <div><a rel=3D\"\" href=3D\"https://www.hoalen.com/en/woman-eurielle-1454.=\nhtml#/size-36/color-indigo?utm_source=3DMailjet&utm_medium=3De-mail&utm_cam=\npaign=3Dclassic-chambray-styles.\"><img src=3D\"https://www.hoalen.com/medias=\n/newsletters/900/227-1524305443.jpg\" border=3D\"0\" class=3D\"full\"/></a></div=\n><div><a rel=3D\"\" href=3D\"https://www.hoalen.com/en/woman-eurielle-1454.htm=\nl#/size-36/color-indigo?utm_source=3DMailjet&utm_medium=3De-mail&utm_campai=\ngn=3Dclassic-chambray-styles.\"><img src=3D\"https://www.hoalen.com/medias/ne=\nwsletters/900/227-1524305463.jpg\" border=3D\"0\" class=3D\"full\"/></a></div><d=\niv><a rel=3D\"\" href=3D\"https://www.hoalen.com/en/woman-eurielle-1454.html#/=\nsize-36/color-indigo?utm_source=3DMailjet&utm_medium=3De-mail&utm_campaign=\n=3Dclassic-chambray-styles.\"><img src=3D\"https://www.hoalen.com/medias/news=\nletters/900/227-1524305476.jpg\" border=3D\"0\" class=3D\"full\"/></a></div><div=\n><a rel=3D\"\" href=3D\"https://www.hoalen.com/en/outdoor-clothing-man-sezaig-=\n1460.html#/size-s/color-indigo?utm_source=3DMailjet&utm_medium=3De-mail&utm=\n_campaign=3Dclassic-chambray-styles.\"><img src=3D\"https://www.hoalen.com/me=\ndias/newsletters/900/227-1524305531.jpg\" border=3D\"0\" class=3D\"full\"/></a><=\n/div>\n</div>\n\n<!-- About -->\n<div class=3D\"about\" style=3D\"width:100%;max-width: 900px;margin:5em auto 0=\n auto;text-align:center;\">\n\n    <!-- Social -->\n    <p style=3D\"vertical-align:middle\">\n        <a style=3D\"margin:0 0.75em;vertical-align:middle\" href=3D\"https://=\nwww.hoalen.com\"><img class=3D\"social-mobile\" style=3D\"width: 100%!important=\n;vertical-align:middle\" src=3D\"http://www.hoalen.com/nl/general/logo-hoalen=\n-bottom-2.jpg\" border=3D\"0\" alt=3D\"hoalen\"/></a>\n\n    </p>\n\n\n\n    <div class=3D\"text-about\" style=3D\"color:#000!important;width:100%;max-=\nwidth: 900px;box-sizing:border-box;padding:1.5em;margin:0 auto\">\n        <p style=3D\"font-size: 1.4em;font-family: 'helvetica neue', arial, =\nsans-serif;font-weight: 200;letter-spacing: 0.1em;margin:0\">Cette lettre vo=\nus informe des derniers produits,<br/>innovations, =C3=A9v=C3=A9nements et =\nactivit=C3=A9s de notre marque ainsi<br/>que des projets et des aventures r=\n=C3=A9alis=C3=A9s par nos ambassadeurs.</p>\n        <p style=3D\"font-size: 1.4em;font-family: 'helvetica neue', arial, =\nsans-serif;font-weight: 200;letter-spacing: 0.1em;\"><i>This newsletter keep=\ns you updated on the latest products, innovations, events and corporate act=\nivity of our company, as well as on the projects and<br/>adventures being p=\nursued by our ambassadors.</i></p>\n\n\n    </div>\n</div>\n\n<!-- Footer -->\n<div class=3D\"footer\" style=3D\"width:100%;max-width: 900px;text-align:cente=\nr;line-height:1.25em;margin:0 auto 3em auto\">\n\n    <p class=3D\"footer-legal\" style=3D\"margin:1.75em 0 0 0;font-size: 1.0em=\n;font-family: 'helvetica neue', arial, sans-serif;font-weight: 200;letter-s=\npacing: 0.1em;\">Si vous souhaitez ne plus recevoir nos e-mails, <a href=3D\"=\nhttp://xix6.mj.am/unsub2?hl=3Dfr&m=3DAMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9MAER=\nOrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA&b=3D66c3d094&e=3Dd597839d&x=3DDGt=\nexrtLzM8323CeidSHNg\" style=3D\"color:#626262;font-weight:300\">cliquez ici.</=\na></p>\n    <p class=3D\"footer-legal\" style=3D\"margin:0.25em 0;font-size: 1.0em;fon=\nt-family: 'helvetica neue', arial, sans-serif;font-style:italic;font-weight=\n: 200;letter-spacing: 0.1em;\">If you want to unsubscribe from this list, <a=\n href=3D\"http://xix6.mj.am/unsub2?hl=3Den&m=3DAMUAAAV2f1kAAWYCyZAAAE40REEAA=\nEr4y9MAEROrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA&b=3D66c3d094&e=3Dd597839=\nd&x=3DDGtexrtLzM8323CeidSHNg\" style=3D\"color:#626262;font-weight:300\">click=\n here.</a></p>\n\n</div>\n\n</body>\n</html>\n=\n\n--=-TmdQenFaaBdQnpCMWq90\nContent-Type: text/html; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.=\nw3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n<head>\n    <meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3Dutf-8=\n\" />\n    <meta name=3D\"viewport\" content=3D\"width=3Ddevice-width, initial-scale=\n=3D1.0\"/>\n    <title>HOALEN - Saltwater Players Co.</title>\n    <style type=3D\"text/css\">\n        html {\n            /*font-size: 100%;*/\n        }\n\n        body {\n            font-family: Arial, Helvetica, sans-serif;\n            color: #626262;\n            font-size:80%;\n            font-size:14px;\n        }\n\n        .about {\n            width:900px;\n        }\n\n        .text-about {\n            width:900px;\n        }\n\n        .logo-main {\n\n        }\n        a {\n            color: #626262;\n            text-decoration: none;\n        }\n        a:link {color: #626262;}\n        a:visited {color: #626262;}\n        a:hover {color: #626262;text-decoration: underline}\n        a:active {color: #626262;}\n\n        #outlook a {padding:0;}\n        body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text=\n-size-adjust:100%; margin:0; padding:0;}\n        .ExternalClass {width:100%;}\n        .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalCla=\nss font, .ExternalClass td, .ExternalClass div {line-height: 100%;}\n        #backgroundTable {margin:0; padding:0; width:100% !important; line-=\nheight: 100% !important;}\n\n        img {outline:none; text-decoration:none; -ms-interpolation-mode: bi=\ncubic;}\n        a img {border:none;vertical-align: bottom;}\n        .image_fix {display:block;}\n        p {margin: 1em 0;}\n        h1, h2, h3, h4, h5, h6 {color: #626262 !important;}\n        h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color: #626262 !important;}\n        h1 a:active, h2 a:active,  h3 a:active, h4 a:active, h5 a:active, h=\n6 a:active {color: #626262 !important;}\n        h1 a:visited, h2 a:visited,  h3 a:visited, h4 a:visited, h5 a:visit=\ned, h6 a:visited {color: #626262 !important;}\n\n        table td {border-collapse: collapse;}\n\n        a[href^=3D\"tel\"], a[href^=3D\"sms\"] {\n            text-decoration: none;\n            color: #626262;\n            pointer-events: none;\n            cursor: default;\n        }\n\n        @media only screen and (max-device-width: 480px) {\n\n\n            body {\n                font-size: 5.5px;\n                font-size: 8px;\n            }\n\n\n\n            .about {\n                /*font-size: 200%;*/\n                width:100%;\n                letter-spacing: 0.1em!important;\n            }\n\n            .text-about {\n\n                /*width:95%;*/\n                /*font-size: 50%;*/\n                text-align: center;\n                width:100%;\n\n\n            }\n\n            .footer {\n                width:100%;\n                /*font-size: 100%;*/\n                text-align: center;\n            }\n\n\n\n            .content {\n                width:100%;\n                line-height: 0;\n            }\n\n\n\n            .containerdemi {\n                max-width:100%!important;\n                width:100%;\n            }\n\n            .demi {\n                display:inline-block!important;\n                width:50%!important;\n                border:0!important;\n                margin:0!important;\n                padding:0!important;\n            }\n\n            .full {\n                width:100%!important;\n                border:0!important;\n                margin:0!important;\n                padding:0!important;\n            }\n\n            .image {\n                width:100%!important;\n\n                border:0!important;\n                margin:0!important;\n                padding:0!important;\n            }\n\n\n\n            .logo-main {\n                width:250px;\n\n            }\n\n            .logo-footer {\n                width:25em;\n            }\n\n\n            .mobile-break {\n                display:block;\n                width:0;\n                height:0;\n                overflow:hidden;\n            }\n\n            .button-mobile {\n                display:block;\n\n            }\n\n            .footer-legal {\n                font-size: 1.4em!important;\n            }\n\n            .mobile-break-about {\n                margin:2em auto;\n            }\n\n            .social-mobile {\n                width:8em;\n            }\n\n            .mobile-seabass {\n                width:100px;\n            }\n\n\n        }\n\n    </style>\n</head>\n\n<body>\n\n<div class=3D\"header\" style=3D\"width:100%;max-width: 900px;text-align:cente=\nr;margin:0 auto\">\n    <a href=3D\"http://xix6.mj.am/lnk/AMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9MAER=\nOrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA/1/oxKQIjdkgM25LiKd5YdKVQ/aHR0cDov=\nL3d3dy5ob2FsZW4uY29t\"><img class=3D\"logo-main\" style=3D\"width: 100%!importa=\nnt;\" src=3D\"http://www.hoalen.com/nl/general/logo-hoalen-header-newsletter-=\n2.jpg\" border=3D\"0\" class=3D\"\"/></a>\n</div>\n\n<!-- Content -->\n<div style=3D\"width:0;height:0;overflow:hidden\">Elegant beach look - </div>\n\n<div class=3D\"content\" style=3D\"width:100%;text-align:center;margin:0 auto\"=\n>\n    <div><a rel=3D\"\" href=3D\"http://xix6.mj.am/lnk/AMUAAAV2f1kAAWYCyZAAAE40=\nREEAAEr4y9MAEROrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA/2/db83xRRGMfwzmivCY=\nkeuCw/aHR0cHM6Ly93d3cuaG9hbGVuLmNvbS9lbi93b21hbi1ldXJpZWxsZS0xNDU0Lmh0bWwjL=\n3NpemUtMzYvY29sb3ItaW5kaWdvP3V0bV9zb3VyY2U9TWFpbGpldCZ1dG1fbWVkaXVtPWUtbWFp=\nbCZ1dG1fY2FtcGFpZ249Y2xhc3NpYy1jaGFtYnJheS1zdHlsZXMu#/size-36/color-indigo?=\nutm_source=3DMailjet&utm_medium=3De-mail&utm_campaign=3Dclassic-chambray-st=\nyles.\"><img src=3D\"https://www.hoalen.com/medias/newsletters/900/227-152430=\n5443.jpg\" border=3D\"0\" class=3D\"full\"/></a></div><div><a rel=3D\"\" href=3D\"h=\nttp://xix6.mj.am/lnk/AMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9MAEROrAACv-ABa2xLDm7=\n5qCAUuSpi3Ndy_lLfQxAAAsWA/3/oIJapmsUhGAv1BfauTHKfQ/aHR0cHM6Ly93d3cuaG9hbGVu=\nLmNvbS9lbi93b21hbi1ldXJpZWxsZS0xNDU0Lmh0bWwjL3NpemUtMzYvY29sb3ItaW5kaWdvP3V=\n0bV9zb3VyY2U9TWFpbGpldCZ1dG1fbWVkaXVtPWUtbWFpbCZ1dG1fY2FtcGFpZ249Y2xhc3NpYy=\n1jaGFtYnJheS1zdHlsZXMu#/size-36/color-indigo?utm_source=3DMailjet&utm_mediu=\nm=3De-mail&utm_campaign=3Dclassic-chambray-styles.\"><img src=3D\"https://www=\n.hoalen.com/medias/newsletters/900/227-1524305463.jpg\" border=3D\"0\" class=\n=3D\"full\"/></a></div><div><a rel=3D\"\" href=3D\"http://xix6.mj.am/lnk/AMUAAAV=\n2f1kAAWYCyZAAAE40REEAAEr4y9MAEROrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA/4/=\nZud1wb7eO6glrlMsv5HfKw/aHR0cHM6Ly93d3cuaG9hbGVuLmNvbS9lbi93b21hbi1ldXJpZWxs=\nZS0xNDU0Lmh0bWwjL3NpemUtMzYvY29sb3ItaW5kaWdvP3V0bV9zb3VyY2U9TWFpbGpldCZ1dG1=\nfbWVkaXVtPWUtbWFpbCZ1dG1fY2FtcGFpZ249Y2xhc3NpYy1jaGFtYnJheS1zdHlsZXMu#/size=\n-36/color-indigo?utm_source=3DMailjet&utm_medium=3De-mail&utm_campaign=3Dcl=\nassic-chambray-styles.\"><img src=3D\"https://www.hoalen.com/medias/newslette=\nrs/900/227-1524305476.jpg\" border=3D\"0\" class=3D\"full\"/></a></div><div><a r=\nel=3D\"\" href=3D\"http://xix6.mj.am/lnk/AMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9MAE=\nROrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA/5/1Lm4mtuvEogSDbJsn5J9Ww/aHR0cHM=\n6Ly93d3cuaG9hbGVuLmNvbS9lbi9vdXRkb29yLWNsb3RoaW5nLW1hbi1zZXphaWctMTQ2MC5odG=\n1sIy9zaXplLXMvY29sb3ItaW5kaWdvP3V0bV9zb3VyY2U9TWFpbGpldCZ1dG1fbWVkaXVtPWUtb=\nWFpbCZ1dG1fY2FtcGFpZ249Y2xhc3NpYy1jaGFtYnJheS1zdHlsZXMu#/size-s/color-indig=\no?utm_source=3DMailjet&utm_medium=3De-mail&utm_campaign=3Dclassic-chambray-=\nstyles.\"><img src=3D\"https://www.hoalen.com/medias/newsletters/900/227-1524=\n305531.jpg\" border=3D\"0\" class=3D\"full\"/></a></div>\n</div>\n\n<!-- About -->\n<div class=3D\"about\" style=3D\"width:100%;max-width: 900px;margin:5em auto 0=\n auto;text-align:center;\">\n\n    <!-- Social -->\n    <p style=3D\"vertical-align:middle\">\n        <a style=3D\"margin:0 0.75em;vertical-align:middle\" href=3D\"http://x=\nix6.mj.am/lnk/AMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9MAEROrAACv-ABa2xLDm75qCAUuS=\npi3Ndy_lLfQxAAAsWA/6/CqG2zQCL1QrygZPbMC15Yg/aHR0cHM6Ly93d3cuaG9hbGVuLmNvbQ\"=\n><img class=3D\"social-mobile\" style=3D\"width: 100%!important;vertical-align=\n:middle\" src=3D\"http://www.hoalen.com/nl/general/logo-hoalen-bottom-2.jpg\" =\nborder=3D\"0\" alt=3D\"hoalen\"/></a>\n\n    </p>\n\n\n\n    <div class=3D\"text-about\" style=3D\"color:#000!important;width:100%;max-=\nwidth: 900px;box-sizing:border-box;padding:1.5em;margin:0 auto\">\n        <p style=3D\"font-size: 1.4em;font-family: 'helvetica neue', arial, =\nsans-serif;font-weight: 200;letter-spacing: 0.1em;margin:0\">Cette lettre vo=\nus informe des derniers produits,<br/>innovations, =C3=A9v=C3=A9nements et =\nactivit=C3=A9s de notre marque ainsi<br/>que des projets et des aventures r=\n=C3=A9alis=C3=A9s par nos ambassadeurs.</p>\n        <p style=3D\"font-size: 1.4em;font-family: 'helvetica neue', arial, =\nsans-serif;font-weight: 200;letter-spacing: 0.1em;\"><i>This newsletter keep=\ns you updated on the latest products, innovations, events and corporate act=\nivity of our company, as well as on the projects and<br/>adventures being p=\nursued by our ambassadors.</i></p>\n\n\n    </div>\n</div>\n\n<!-- Footer -->\n<div class=3D\"footer\" style=3D\"width:100%;max-width: 900px;text-align:cente=\nr;line-height:1.25em;margin:0 auto 3em auto\">\n\n    <p class=3D\"footer-legal\" style=3D\"margin:1.75em 0 0 0;font-size: 1.0em=\n;font-family: 'helvetica neue', arial, sans-serif;font-weight: 200;letter-s=\npacing: 0.1em;\">Si vous souhaitez ne plus recevoir nos e-mails, <a href=3D\"=\nhttp://xix6.mj.am/unsub2?hl=3Dfr&amp;m=3DAMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9=\nMAEROrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA&amp;b=3D66c3d094&amp;e=3Dd597=\n839d&amp;x=3DDGtexrtLzM8323CeidSHNg\" style=3D\"color:#626262;font-weight:300=\n\">cliquez ici.</a></p>\n    <p class=3D\"footer-legal\" style=3D\"margin:0.25em 0;font-size: 1.0em;fon=\nt-family: 'helvetica neue', arial, sans-serif;font-style:italic;font-weight=\n: 200;letter-spacing: 0.1em;\">If you want to unsubscribe from this list, <a=\n href=3D\"http://xix6.mj.am/unsub2?hl=3Den&amp;m=3DAMUAAAV2f1kAAWYCyZAAAE40R=\nEEAAEr4y9MAEROrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA&amp;b=3D66c3d094&amp=\n;e=3Dd597839d&amp;x=3DDGtexrtLzM8323CeidSHNg\" style=3D\"color:#626262;font-w=\neight:300\">click here.</a></p>\n\n</div>\n\n\n<br/><img src=3D\"http://xix6.mj.am/oo/AMUAAAV2f1kAAWYCyZAAAE40REEAAEr4y9MAE=\nROrAACv-ABa2xLDm75qCAUuSpi3Ndy_lLfQxAAAsWA/77d9de80/e.gif\" height=3D\"1\" wid=\nth=3D\"1\" alt=3D\"\" border=3D\"0\" style=3D\"height:1px;width:1px;border:0;\"/>\n</body>\n</html>\n=\n\n--=-TmdQenFaaBdQnpCMWq90--\n\n"
  },
  {
    "path": "devtools/fixtures/raw_emails/html-with-inlined-image",
    "content": "Return-Path: <SRS0=bB6T=HE=dgfip.finances.gouv.fr=ne-pas-repondre@bounce2.pobox.com>\nDelivered-To: xxxx@caliopen.org\nReceived: from post.caliopen.org ([9.6.71.30])\n\tby box.caliopen.org (Dovecot) with LMTP id Xrq4NeYm01rNFQAAz9GZsw\n\tfor <xxxx@caliopen.org>; Sun, 15 Apr 2018 12:19:12 +0200\nReceived: from post.caliopen.org (localhost [127.0.0.1])\n\tby post.caliopen.org (Postfix) with ESMTP id 78BCE7EB43\n\tfor <xxxx@caliopen.org>; Sun, 15 Apr 2018 12:19:12 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on post.caliopen.org\nX-Quarantine-ID: <ipneR3cjWzve>\nX-Virus-Scanned: amavisd-new at caliopen.org\nX-Amavis-Alert: BAD HEADER SECTION, Improper folded header field made up\n\tentirely of whitespace (char 20 hex): To: xxxx@caliopen.org \nX-Spam-Flag: NO\nX-Spam-Score: -1.347\nX-Spam-Level:\nX-Spam-Status: No, score=-1.347 tagged_above=-999 required=4.2\n\ttests=[AWL=0.650, BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1,\n\tDKIM_VALID_AU=-0.1, HEADER_FROM_DIFFERENT_DOMAINS=0.001,\n\tHTML_MESSAGE=0.001, UNPARSEABLE_RELAY=0.001]\n\tautolearn=ham autolearn_force=no\nX-Spam-Caliopen: '1'\nAuthentication-Results: post.caliopen.org (amavisd-new);\n\tdkim=pass (2048-bit key) header.d=dgfip.finances.gouv.fr\nReceived: from --IP masked by Caliopen--\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby post.caliopen.org (Postfix) with ESMTPS id 8BE397EB2A\n\tfor <xxxx@caliopen.org>; Sun, 15 Apr 2018 12:19:00 +0200 (CEST)\nDKIM-Filter: OpenDKIM Filter v2.10.3 post.caliopen.org 8BE397EB2A\nReceived: from --IP masked by Caliopen--\n\tby pb-mx10.pobox.com (Postfix) with ESMTP id 6EA7215930;\n\tSun, 15 Apr 2018 06:18:59 -0400 (EDT)\nX-Pobox-Filter-Version: 3.1\nX-Pobox-Loop-ID: 5eafd0033d2bee44131dd60d1478381602375e7c\nDelivered-To: xxxx@caliopen.org\nX-Pobox-Delivery-ID: E285A2-B5F5D1592C-1523787539-16058349!pb-mx10.pobox.com\nReceived: from --IP masked by Caliopen--\n\tby pb-mx10.pobox.com (Postfix) with ESMTP id B5F5D1592C\n\tfor <xxxx@caliopen.org>; Sun, 15 Apr 2018 06:18:58 -0400 (EDT)\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple;\n\td=dgfip.finances.gouv.fr; s=default; t=1523787536;\n\tbh=elIXqROU9niG5lNYDBkmgmZIMxGtr7NQRWvfrV1Bz3w=;\n\th=Date:Subject:To:From;\n\tb=oxn0GAwSpRkgvRYg3etwKPqUvf0m/2ujTNg9gDJu2syuHFAjzMeYY/eawm87gNP7z\n\t D2+ZhlbgMC25bJzK8Tt7UNZiH5HxahZlE1OLFAtsJKEB00NDq+/aYPitFNzWwSKtZ3\n\t K+ekFGNX7CuoUtGGg0aADQdRJYlBD/CKd5KFc5oC7nlpzurdEYpy8a+dLBhV0LUi0m\n\t +X/waLFRjgWyAtCLmJuPW1wadWyKCFMYN9Tbs3fR9p1DngFxXfyTlvegbLGL5408Nz\n\t bpKRu84NroS4teLO9LscxkRK0BFUz00wKyiRGH4iLAmqSPHhi1A0y4r2adk4ogOuAT\n\t dg0DuKUvd4wdA==\nMIME-Version: 1.0\nContent-Transfer-Encoding: binary\nContent-Type: multipart/alternative; boundary=\"_----------=_15237875364075492872\"\nDate: Sun, 15 Apr 2018 12:18:56 +0200\nSubject: =?UTF-8?Q?Ouverture_du_service_de_d=c3=a9claration_en_ligne?=\nTo: xxxx@caliopen.org\nFrom: =?UTF-8?Q?Direction_G=c3=a9n=c3=a9rale_des_Finances_Publiques?=<ne-pas-repondre@dgfip.finances.gouv.fr>\nMessage-Id: <20180415101856.A0A28223A5E@pf2pusi002.dgfip.finances.gouv.fr>\nX-Pobox-Client-Address: 145.242.11.104\nX-Pobox-Client-Name: pf2pusi002.dgfip.finances.gouv.fr\nX-Pobox-Client-HELO: pf2pusi002.dgfip.finances.gouv.fr\nX-Pobox-Original-Sender: ne-pas-repondre@dgfip.finances.gouv.fr\n\nThis is a multi-part message in MIME format.\n\n--_----------=_15237875364075492872\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/plain; charset=UTF-8\nDate: Sun, 15 Apr 2018 12:18:56 +0200\n\nOuverture du service de d=E9claration en ligne\n\nBonjour,\n\nL'ann=E9e derni=E8re, vous avez d=E9clar=E9 vos revenus en ligne et nous vo=\nus en remercions.\n\nLe service de d=E9claration en ligne de vos revenus est ouvert depuis le 11=\n avril 2018. Rendez-vous sur impots.gouv.fr et s=E9lectionnez le service =\n=AB D=E9clarer mes revenus =BB dans votre espace particulier.\n\nImportant :\n\nA compter du 1er janvier 2019, le pr=E9l=E8vement =E0 la source supprime le=\n d=E9calage d=92un an entre vos revenus et l=92imp=F4t correspondant. D=E8s=\n 2018, =E0 la fin de votre d=E9claration en ligne, vous pourrez g=E9rer vos=\n options de pr=E9l=E8vement =E0 la source en vue de 2019.\n\nUne question ? Une d=E9marche ? Utilisez la rubrique =AB Nous contacter =BB=\n dans votre espace particulier.\n\nNous vous rappelons que la d=E9claration de revenus doit =EAtre r=E9alis=E9=\ne par internet si le revenu fiscal de r=E9f=E9rence qui figure sur votre de=\nrnier avis d'imp=F4t est sup=E9rieur =E0 15 000 euros et que votre r=E9side=\nnce principale est =E9quip=E9e d'un acc=E8s =E0 internet.=20\n\nNous vous remercions de votre confiance.\n\nLa Direction g=E9n=E9rale des Finances publiques\n\nRetrouvez la DGFiP sur Twitter (@dgfip_officiel) et sur Facebook : Directio=\nn g=E9n=E9rale des Finances publiques\n\nDes questions sur le pr=E9l=E8vement =E0 la source ? rendez-vous sur prelev=\nementalasource.gouv.fr\n\nIMPOTS.GOUV.FR EST UN SITE DE LA DIRECTION G=C9N=C9RALE DES FINANCES PUBLIQ=\nUES\n\nRecommandations Pour votre s=E9curit=E9, ne r=E9pondez jamais =E0 un courri=\nel vous demandant votre num=E9ro de carte bancaire.\n\nCe courriel vise =E0 vous informer sur notre offre de services en ligne. Si=\n vous ne souhaitez plus recevoir ce type de courriel, merci de vous d=E9sab=\nonner dans votre espace Particulier, rubrique =AB=A0G=E9rer mon profil=A0=\n=BB sur impots.gouv.fr=\n\n--_----------=_15237875364075492872\nMIME-Version: 1.0\nContent-Type: multipart/related; boundary=\"_----------=_15237875364075492873\"\nDate: Sun, 15 Apr 2018 12:18:56 +0200\n\nThis is a multi-part message in MIME format.\n\n--_----------=_15237875364075492873\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/html; charset=UTF-8\nDate: Sun, 15 Apr 2018 12:18:56 +0200\n\n<!DOCTYPE HTML>\n<html lang=3D\"fr-fr\">\n<head>\n\n\n\n\n\n\n\n\n\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n  <meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=3Dutf-8\">\n\n\n\n\n\n\n\n\n\n\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n  <title>Votre d&eacute;claration de revenus en ligne a bien &eacute;t&eacu=\nte; enregistr&eacute;e</title>\n  <style type=3D\"text/css\">\n/* Pour outlook.com */\n.ReadMsgBody {width: 100%; background-color: rgb(231, 231, 231);}\n.ExternalClass {width: 100%; background-color: rgb(231, 231, 231);}\n/* CSS g=C3=A9n=C3=A9rique */\nbody {width: 100%; background-color: rgb(231, 231, 231); margin:0; padding:=\n0; -webkit-font-smoothing: antialiased;font-family: Arial,sans-serif}\ntable {border-collapse: collapse; mso-table-lspace:0pt; mso-table-rspace:0p=\nt;}\np,td {font-family: Arial,sans-serif; font-size: 13px; padding:0;}\np {margin: 10px}\na:hover {text-decoration: none !important; border-bottom: 1px dotted #008dd=\n9 !important}\n/* Responsive design */\n@media only screen and (max-width: 640px) {\nbody[yahoo] .full {display: block; width: 100%;}\nbody[yahoo] .nodisplay {display: none; width: 0px !important;}\n}\n  </style>\n</head>\n\n\n<body leftmargin=3D\"0\" topmargin=3D\"0\" yahoo=3D\"fix\" style=3D\"font-family: =\nArial,sans-serif;\" marginheight=3D\"0\" marginwidth=3D\"0\">\n\n\n\n\n\n\n\n\n\n<!-- Overall wrapper -->\n<table align=3D\"center\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" wi=\ndth=3D\"100%\">\n\n\n\n\n\n\n\n\n\n  <tbody>\n\n\n\n\n\n\n\n\n    <tr>\n\n\n\n\n\n\n\n\n\n      <td style=3D\"padding: 20px 0px;\" bgcolor=3D\"#e7e7e7\" valign=3D\"top\" w=\nidth=3D\"100%\">\n<!-- Start wrapper -->\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n      <table style=3D\"max-width: 900px;\" class=3D\"deviceWidth\" align=3D\"cen=\nter\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"95%\">\n\n\n\n\n\n\n\n\n\n        <tbody>\n\n\n\n\n\n\n\n\n          <tr>\n\n\n\n\n\n\n\n\n\n            <td width=3D\"100%\">\n<!-- Start content block -->\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n            <table class=3D\"deviceWidth\" align=3D\"center\" bgcolor=3D\"#e7e7e=\n7\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"100%\">\n\n\n\n\n\n\n\n\n\n              <tbody>\n\n\n\n\n\n\n\n\n                <tr>\n\n\n\n\n\n\n\n\n\n                  <td style=3D\"padding: 0px 15px;\" bgcolor=3D\"#f7f7f7\" vali=\ngn=3D\"top\">\n<!-- 1. Logo & Titre -->\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  <table style=3D\"width: 870px; height: 46px;\" border=3D\"0\"=\n cellpadding=3D\"0\" cellspacing=3D\"0\" dir=3D\"ltr\">\n\n\n\n\n\n\n\n\n\n                    <tbody>\n\n\n\n\n\n\n\n\n                      <tr>\n\n\n\n\n\n\n\n\n\n                        <td class=3D\"full\" style=3D\"padding: 0px;\" bgcolor=\n=3D\"#f7f7f7\" dir=3D\"ltr\" width=3D\"210\">\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"\">\n                        <img  src=3D\"cid:76e655f6176725f33392f64676669702e7=\n06e67@MIME-Lite-HTML-1.23\" alt=3D\"Direction g&eacute;n&eacute;rale des fina=\nnces publiques\" height=3D\"32\" width=3D\"190\">\n                        </p>\n\n\n\n\n\n\n\n\n\n                        </td>\n\n\n\n\n\n\n\n\n\n                        <td style=3D\"padding: 0px; min-width: 300px;\" class=\n=3D\"full\" bgcolor=3D\"#f7f7f7\" dir=3D\"ltr\" width=3D\"auto\">\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"color: rgb(0, 81, 126); font-size: 26px=\n; text-align: left;\">Ouverture\ndu service de d&eacute;claration en ligne</p>\n\n\n\n\n\n\n\n\n\n                        </td>\n\n\n\n\n\n\n\n\n\n                      </tr>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                    </tbody>\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  </table>\n\n\n\n\n\n\n\n\n <!-- End 1. -->\n<!-- 2. Blank -->\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  <table style=3D\"border-collapse: collapse;\" border=3D\"0\" =\ncellpadding=3D\"0\" cellspacing=3D\"0\" dir=3D\"ltr\" width=3D\"100%\">\n\n\n\n\n\n\n\n\n\n                    <tbody>\n\n\n\n\n\n\n\n\n                      <tr style=3D\"background-color: rgb(234, 235, 237);\">\n\n\n\n\n\n\n\n\n\n                        <td class=3D\"nodisplay\" bgcolor=3D\"#e7e7e7\" dir=3D\"=\nltr\" width=3D\"50%\">\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"margin: 0pt; padding: 0pt;\">&nbsp;\n\n                        </p>\n\n\n\n\n\n\n\n\n\n                        </td>\n\n\n\n\n\n\n\n\n\n                        <td class=3D\"full\" bgcolor=3D\"#e7e7e7\" dir=3D\"ltr\" =\nwidth=3D\"50%\">\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"margin: 0pt; padding: 0pt;\">&nbsp;\n\n                        </p>\n\n\n\n\n\n\n\n\n\n                        </td>\n\n\n\n\n\n\n\n\n\n                      </tr>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                    </tbody>\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  </table>\n\n\n\n\n\n\n\n\n <!-- End 2. -->\n<!-- 3. Avertissement anti-phishing & Corps du message -->\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  <table style=3D\"border-collapse: collapse;\" border=3D\"0\" =\ncellpadding=3D\"0\" cellspacing=3D\"0\" dir=3D\"rtl\" width=3D\"100%\">\n\n\n\n\n\n\n\n\n\n                    <tbody>\n\n\n\n\n\n\n\n\n                      <tr>\n\n\n\n\n\n\n\n\n\n                        <td style=3D\"padding: 10px 5px 0pt 12px;\" class=3D\"=\nfull\" bgcolor=3D\"#f7f7f7\" dir=3D\"ltr\" width=3D\"auto\">\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"color: rgb(60, 69, 74); text-align: lef=\nt;\">Bonjour,</p>\n\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"color: rgb(59, 69, 74);\" align=3D\"justi=\nfy\"></p>\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"margin-bottom: 0cm; color: rgb(59, 69, =\n74);\" align=3D\"justify\">L'ann&eacute;e derni&egrave;re,\nvous avez d&eacute;clar&eacute; vos revenus en ligne et nous vous en remerc=\nions.</p>\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"margin-bottom: 0cm; color: rgb(59, 69, =\n74);\" align=3D\"justify\">Le service de\nd&eacute;claration en ligne de vos revenus est ouvert depuis le <b>1</b><b>=\n1</b><b>\navril 201</b><b>8</b><b>.</b><span style=3D\"font-weight: normal;\"> </span>\nRendez-vous sur  impots.gouv.fr et s&eacute;lectionnez le service\n&laquo;&nbsp;D&eacute;clarer mes revenus&nbsp;&raquo; dans votre espace par=\nticulier.</p>\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"margin-bottom: 0cm; font-family: Arial;=\n color: rgb(59, 69, 74);\" align=3D\"justify\"><small><strong><font style=3D\"f=\nont-size: 12pt;\" size=3D\"3\"><small><span style=3D\"font-style: normal;\"><spa=\nn style=3D\"font-weight: normal;\">Important&nbsp;:<br>\n\n\n\n\n\n\n\n\nA\ncompter du 1</span></span></small></font></strong><strong><sup><font style=\n=3D\"font-size: 12pt;\" size=3D\"3\"><small><span style=3D\"font-style: normal;\"=\n><span style=3D\"font-weight: normal;\">er</span></span></small></font></sup>=\n</strong><strong><font style=3D\"font-size: 12pt;\" size=3D\"3\"><small><span s=\ntyle=3D\"font-style: normal;\"><span style=3D\"font-weight: normal;\">\njanvier 2019, le pr&eacute;l&egrave;vement &agrave; la source supprime le d=\n&eacute;calage\nd&rsquo;un an entre vos revenus et l&rsquo;imp&ocirc;t correspondant. D&egr=\nave;s 2018, &agrave;\nla fin de votre d&eacute;claration en ligne, vous pourrez g&eacute;rer vos\noptions de pr&eacute;l&egrave;vement &agrave; la source en vue de 2019.</sp=\nan></span></small></font></strong></small></p>\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"margin-bottom: 0cm;\" align=3D\"justify\">=\n<small><font style=3D\"font-family: Arial; color: rgb(74, 74, 74);\" face=3D\"=\nTimes New Roman, serif\"><font style=3D\"font-size: 12pt;\" size=3D\"3\"><small>=\nUne\nquestion&nbsp;? Une d&eacute;marche&nbsp;? Utilisez la rubrique &laquo;&nbs=\np;Nous\ncontacter&nbsp;&raquo; dans votre espace particulier.</small></font></font>=\n</small>\n                        </p>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"margin-bottom: 0cm; font-family: Arial;=\n color: rgb(59, 69, 74);\" align=3D\"justify\"><strong><span style=3D\"font-wei=\nght: normal;\"></span></strong><strong><span style=3D\"font-weight: normal;\">=\n</span></strong><small><strong><font style=3D\"font-size: 12pt;\" size=3D\"3\">=\n<small><span style=3D\"font-style: normal;\"><span style=3D\"font-weight: norm=\nal;\">N</span></span></small></font></strong><strong><font style=3D\"font-siz=\ne: 12pt;\" size=3D\"3\"><small><span style=3D\"font-style: normal;\"><span style=\n=3D\"font-weight: normal;\">ous\nvous rappelons que la d&eacute;claration de revenu</span></span></small></f=\nont></strong><strong><font style=3D\"font-size: 12pt;\" size=3D\"3\"><small><sp=\nan style=3D\"font-style: normal;\"><span style=3D\"font-weight: normal;\">s</sp=\nan></span></small></font></strong><strong><font style=3D\"font-size: 12pt;\" =\nsize=3D\"3\"><small><span style=3D\"font-style: normal;\"><span style=3D\"font-w=\neight: normal;\">\ndoit &ecirc;tre r&eacute;alis&eacute;e par internet si </span></span></smal=\nl></font></strong><strong><font style=3D\"font-size: 12pt;\" size=3D\"3\"><smal=\nl><span style=3D\"font-style: normal;\"><span style=3D\"font-weight: normal;\">=\nle</span></span></small></font></strong><strong><font style=3D\"font-size: 1=\n2pt;\" size=3D\"3\"><small><span style=3D\"font-style: normal;\"><span style=3D\"=\nfont-weight: normal;\">\nrevenu fiscal de r&eacute;f&eacute;rence </span></span></small></font></str=\nong><strong><font style=3D\"font-size: 12pt;\" size=3D\"3\"><small><span style=\n=3D\"font-style: normal;\"><span style=3D\"font-weight: normal;\">qui\nfigure sur votre dernier avis d'imp&ocirc;t </span></span></small></font></=\nstrong><strong><font style=3D\"font-size: 12pt;\" size=3D\"3\"><small><span sty=\nle=3D\"font-style: normal;\"><span style=3D\"font-weight: normal;\">est\nsup&eacute;rieur &agrave; </span></span></small></font></strong><strong><fo=\nnt style=3D\"font-size: 12pt;\" size=3D\"3\"><small><span style=3D\"font-style: =\nnormal;\"><span style=3D\"font-weight: normal;\">15</span></span></small></fon=\nt></strong><strong><font style=3D\"font-size: 12pt;\" size=3D\"3\"><small><span=\n style=3D\"font-style: normal;\"><span style=3D\"font-weight: normal;\">\n000 euros et que votre r&eacute;sidence principale est &eacute;quip&eacute;=\ne d'un acc&egrave;s\n&agrave; internet.&nbsp;</span></span></small></font></strong><strong><font=\n style=3D\"font-size: 12pt;\" size=3D\"3\"><small><span style=3D\"font-style: no=\nrmal;\"><span style=3D\"font-weight: normal;\"></span></span></small></font></=\nstrong><strong><font style=3D\"font-size: 12pt;\" size=3D\"3\"><small><span sty=\nle=3D\"font-style: normal;\"><span style=3D\"font-weight: normal;\"></span></sp=\nan></small></font></strong></small></p>\n\n\n\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <br>\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"color: rgb(60, 69, 74); text-align: lef=\nt;\">Nous vous remercions de votre confiance.</p>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"color: rgb(60, 69, 74); text-align: lef=\nt;\">La Direction g&eacute;n&eacute;rale des Finances publiques</p>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"color: rgb(60, 69, 74); text-align: lef=\nt;\">Retrouvez la DGFiP sur\nTwitter (@dgfip_officiel) et sur Facebook : Direction\ng&eacute;n&eacute;rale des Finances publiques</p>\n\n\n\n\n\n\n\n\n\n                        </td>\n\n\n\n\n\n\n\n\n\n                        <td class=3D\"full\" bgcolor=3D\"#f7f7f7\" dir=3D\"ltr\" =\nvalign=3D\"bottom\" width=3D\"210\">\n                        <br>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"font-weight: bold; font-size: 14px; col=\nor: rgb(192, 12, 19); text-align: left;\">\nRecommandations\n                        </p>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"font-size: 11px; color: rgb(192, 12, 19=\n); text-align: left;\">Pour\nvotre s&eacute;curit&eacute;, ne r&eacute;pondez jamais &agrave; un\ncourriel vous demandant votre num&eacute;ro de carte bancaire.\n                        </p>\n\n\n\n\n\n\n\n\n\n                        </td>\n\n\n\n\n\n\n\n\n\n                      </tr>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                    </tbody>\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  </table>\n\n\n\n\n\n\n\n\n <!-- End 3. -->\n<!-- 4. Bas de page -->\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  <table style=3D\"border-collapse: collapse;\" bgcolor=3D\"#e=\n7e7e7\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" dir=3D\"ltr\" width=\n=3D\"100%\">\n\n\n\n\n\n\n\n\n\n                    <tbody>\n\n\n\n\n\n\n\n\n                      <tr>\n\n\n\n\n\n\n\n\n\n                        <td class=3D\"nodisplay\" bgcolor=3D\"#e7e7e7\" dir=3D\"=\nltr\" width=3D\"210\">\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"\">&nbsp;</p>\n\n\n\n\n\n\n\n\n\n                        </td>\n\n\n\n\n\n\n\n\n\n                        <td class=3D\"full\" bgcolor=3D\"#e7e7e7\" dir=3D\"ltr\" =\nwidth=3D\"auto\">\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"font-size: 11.5px; color: rgb(60, 69, 7=\n4);\">\n                        <span style=3D\"font-variant: small-caps;\">Des\nquestions sur le pr&eacute;l&egrave;vement &agrave; la source ?\nrendez-vous sur prelevementalasource.gouv.fr</span></p>\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"font-size: 11.5px; color: rgb(60, 69, 7=\n4);\"><span style=3D\"font-variant: small-caps;\">impots.gouv.fr est un\nsite de la direction g&eacute;n&eacute;rale des finances publiques</span>\n                        </p>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n\n\n\n\n\n\n                        </td>\n\n\n\n\n\n\n\n\n\n                      </tr>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                    </tbody>\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  </table>\n\n\n\n\n\n\n\n\n\n<!-- End 4. -->\n<!-- 5. D=C3=A9sabonnement : uniquement pour les courriels de promotion -->\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  <table style=3D\"border-collapse: collapse; width: 870px; =\nheight: 59px;\" border=3D\"0\" cellpadding=3D\"0\" cellspacing=3D\"0\" dir=3D\"ltr\">\n\n\n\n\n\n\n\n\n\n                    <tbody>\n\n\n\n\n\n\n\n\n                      <tr>\n\n\n\n\n\n\n\n\n\n                        <td class=3D\"full\" dir=3D\"ltr\" width=3D\"auto\">\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                        <p style=3D\"text-align: center; font-size: 11px;\">\n                        <span style=3D\"font-style: italic;\">Ce\ncourriel vise &agrave; vous informer sur notre offre de services en\nligne. Si vous ne souhaitez plus recevoir ce type de courriel, merci de\nvous&nbsp;d&eacute;sabonner &agrave; la rubrique\n\"G&eacute;rer mon\nprofil\" de votre espace particulier sur impots.gouv.fr.</span></p>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n\n\n\n\n\n\n                        </td>\n\n\n\n\n\n\n\n\n\n                      </tr>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                    </tbody>\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n                  </table>\n\n\n\n\n\n\n\n\n                  </td>\n\n\n\n\n\n\n\n\n\n                </tr>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20=20=20\n              </tbody>\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20=20=20=20=20\n            </table>\n\n\n\n\n\n\n\n\n <!-- End content block -->\n            </td>\n\n\n\n\n\n\n\n\n\n          </tr>\n\n\n\n\n\n\n\n\n\n=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20\n=20=20=20=20=20=20=20=20\n        </tbody>\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n=20=20=20=20=20=20\n      </table>\n\n\n\n\n\n\n\n\n <!-- End wrapper -->\n      </td>\n\n\n\n\n\n\n\n\n\n    </tr>\n\n\n\n\n\n\n\n\n\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n=20=20\n  </tbody>\n</table>\n\n\n\n\n\n\n\n\n <!-- End overall wrapper -->\n</body>\n</html>\n\n--_----------=_15237875364075492873\nMIME-Version: 1.0\nContent-Id: <76e655f6176725f33392f64676669702e706e67@MIME-Lite-HTML-1.23>\nContent-Transfer-Encoding: base64\nContent-Type: image/png\nDate: Sun, 15 Apr 2018 12:18:56 +0200\n\niVBORw0KGgoAAAANSUhEUgAAAL4AAAAgCAYAAACsGMKuAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\n/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJEBQGCKqKV7oAABTUSURBVHja\n7Zx5lBTVvcc/354FGFlUXJ/g0gUoENyIQIxLFLsOzxeNSzQatUtjVIhL4po8UdwSjYgaj6hxxWrX\nuOAetVoforgFDGjUoKFaxLgLisKwzfTv/VG3mZpmhk3NITlzz5kz3VW3b937u9/f/rslUs3zg55g\nd4K2A6sDlQFj7VsGuAa4OI5COlpHW1dabeuv9jlwIjDIjEMlDmt11wxJbQ6U3ANY4X5/xwDlDnJ3\ntHWlaWU3PT941ozdE0Dbd+Oo8Ep7fbff9/ANFzfXes2mYWY6ARggmUDPAsPjKGzqIHdHW0cl/gpy\n/E5Ju4OxXu2y+mlXdLq8qVyTqzZ/zCCjB5szso/ra5pe2bC26Rd7PRKoXNb1kvUB1QAdwO9o/y7A\n19zlxrrKMmMbYNAKvQRlAyPDoqb6EfHCut9cNuhPkw+cctoe2W7vPyOtXLN0tI62jgF/BcAaBoiZ\nGAegFskvUQtsC+yKad/msva+eeCVlx5786IBHWTuaP9uwG/PkV3yvTMXvdXGrTeBB566oOE3ZpYv\nl3XtZT9uOOfM+xrf7yB1R1uXWmZtPeKXxjW0a7588inNh1y5aMKyJq6qrWHwfxLBPD9Y/jmby6+y\nj+cHZHNBB9L+E4C/qvbT8Y0ALMz0uHZJl403XBU42vpefc/z81XX8qsEYfrair8PVgu01QCOoxDP\nD/bK5oLepWKhzTFcn76eH4yIoxDJ2pj7iutpvd626dPWOqvHWlP6dpg632A75oJzed+++rKM5sKV\n7YAjvz2oJo7C6e57BjQA7A2gAdjOjMY4Cv/uNm87d31eHIWzEyAEw4D5pWL4d88PugH9gGZgcRyF\nM7O5/A6S3gYWeX4gsL3MeC6OwmWeH9SD9Qe9ZWZbS3ovjsKFFaA4APcGesRR+Lq7vjnYIEmfZXP5\n9yT1ALJxFE73/Pw2oNlAnZntCprt+fnucVT40vODwUA5jsLpZqJUDPH8/GZAvRlz4qiA5wdZ4Ms4\nCj9zz9oa2BBojKNwJkCpWHBA145AUxyFr5tBNpfvJilrRq3Em3EULsrmgp5gO0v6vzgKmzvg/i1L\n/APHXM6E8y7iw059dv28x8CVGU3TzLjd84OP3YVuwHRQF9BQ0DTgb54fnOruF4HbgUMcME6WuEXi\nfs8PBjnn+iHgSbCTE6dbM4ABLnP8JuhKSZ+58bYyYwZwnKQngaFVUnswMAfsCc8PjnG3rgSd6X6T\nMTMf7K+eH+wO+iPQGRgk6TLJfgWqEGAa8Jjn518sFStZbD0BPKOWrOCtZnZ0ikDjgRuBk6roVgdM\nN+MBzw+eKxULJGFnZoD9Ftg8m8v3kOx5SWeZ2UMdUP8WgT/s6DMAeODC09n3rIsPbm5qmlBfW/Ps\nSn7yrsSBwCaeH/R1JRJfuXvNZrwnaQ7wnru2ECgBr7vvOwJjwIaDzY6jcBpwjhl3x1HhxNRzXNbY\n+iZMY908P785WLPD3G7AMrBqqXgwMAk4Cqg46IuBjxIGxSQ1gz4BLjdjbpLVsGYzPge9AXzgAgIA\nM0Elp6k6gfUDLQHr7sZulLQ09fxFQGxmU9uKMUgcYcaQZHyagfmSngO+SLSelgEfSTq7A+orAf6K\ndqBZKqppSUWCgWBornG54dp/v+MYeMAJNYsXLq4fkj+t+54jR4+3st1XLpdvvvnkY+Yfcdl17cWH\nBNQ5YKzgLEtW4x7eO+1XA687u9bAMqCeoE6uT71ETdvRKJXBat0i5Na1zK1z0+R6q9aUmFY6FzjC\nUSSTAJjnkudTZ2Y3AjtKtm0ypiQxD3gQmJeiYQZsS7e2wxwwa0CHp6mdnnKiEXi5HQJ2qugK978R\neMAJiAXAPmb0Bq7psPHbAX7Kpq33/GBPzw/yIN9JKhqb6g+6Z9ag7FPv9eGR2dv17HdWMKrvfx9z\nWr99f3YOMBa4DfGimc03sxPN7PGJ5512NsAdZ45qz9Tpnahze7VULLzt5tPDJYZrQR8CLwA7ORx0\nBXzg0DgqkDCBzgTuA7apAB/o0tY6JWaAbgBKcRR+kICOOmdydE2YqFWLgK2A7gmDgERnZxIdC2TM\nqJdoMGNsMgeTmWXcfI42Y4cUMCeChri1nwTcCjwB9ivXpx44xvPzI93z1wN+CBrZFvHM+B3w55S/\ntjnwS2ATSRsAkyU+BL78tszaf2vn1vPzFdAfBTYO1N1J4kzF/Gw2nX5/PMBJF21hMD7TDjHL5fJN\nzc3NpwAcMGYcD154RnvP3ywRhVrkvn8JbJRILptspr0kmlmeSNMAN+dlrv8E4H4zM0nz3bUbzaym\nRcpbz9S9PZ1zXHleDLahmb6S7GH3/HSbYmb9JdWYWWPFb0/seCs70+xPoPskmkDjkrFtBi3FeQuc\nNN9QohG4zc0sZ8YCR9+ubq77S3QCljoJ/RMzOkkrVMguA3q6zwvc/yfdtQzwuXPwdwGrBy2Mo7Cj\nSLAqJE82l6+TdAtwJC41Cyw2M5mVOyVgzyyqzZTrMlgtypSXWWZRJpNB0sKampqFmZrMvLr6uml1\n9XXjp95+5esdpO1o67TEz+byGUlXJaDnVdAtZvy1tr7TF5/8Y8aIJQvnXwawwUYbjzxppzn5ei0b\nvsxqZk34aO9DLeGShWa2EJg3457xSwGGHHUaf7ntig7qdrR1F/iStgGCxG4O763Y+28/dgMb77D3\ngLouXQHo0qlp1pAtP5nfVM4AtminXve9NuK3jW0eUlkV6D0/X2emhlRpvyrOnbvWBLbA2fEdraOt\ncav4rC7h1yCp1pmTSyUtzgCHgf08jsJ7K55/5bRU9822bPnbdAvWX9/YYAOW/30NC2tLYKQLu80B\nxgFXSFwO5hy+1V+gM9fWmDDtR7NarrUXDfmmyhBWFW1pnX0OvrXnrM3vKjRon0b5Ve6N5+fbpWU2\nl1/reSegD+olXSDpAeAS4DZJLwP95fn56+KoMKqdRR+aOG9Gt7ol379x7wdPby7XHAT2KtJOw85o\ntLXlRPd5MfBpHIW9PT+oOMp1ZnauxDmgCxPnzQB1Bc4D+4UZ3SUtAx6Po/CNFBHzjqmWmJGRuCuO\nwjmp+/WgCcDoSuY3Na+fkGR9l7qoz73ZXOAyrMH3gb3AloA6m9mUUrEwqSUSlj8cFAB9wRpBU4Gr\nSWL9PyQp5W5yUSS1aDl7NI4Kr7nNzQC/ADvQjK8kPQ5cX31k0/ODPc1sd0lL3Hg1wKdAMY7Cdzw/\nXwc63jnMZaAT2JNxVJjq+UEN2JGgzVxU6AOJ14AfuVBoZbyFwOQ4Cqe7Z25oxsjEgTdzoeNn4iic\nkprXQcAAM1sCqpN4OY7Cp1OSdztJ5wM7A1+a8YDEJRWn2/ODXcBGgBa7wMpHcRTe4hhgN0nD3RwB\nngL2TQU5amk5I/Kpmd0hsQg0xsx+WioWtkvWTg0w2swmZ0D3VDjvX9HSG2lJkbO562X3t0TidqeW\nrgfOAp0PFJK+ukbSlsBlwIPZXL5nRTrEUaHg4uZjJR5Kg961zYGfAr+vllhxFP7JzKYDF5vZPZ4f\n/LBUDCv1Oc8D94IuBt4pFQuT3Ap6e35+KuhMMy4xswGg/zFjAdgxcRQSR+GjLno11oHxXfe3i5n2\nroRbzXgiCZ3qeEnj3fr6VEvAOAonKyn+GQtsmYR7bRBQ8vzgfjMtA64F9jCzscBTCejzJGULKiQA\n4xiJu+MonOHmMxYYSJKw6wRM8/yg6PZmnsS1wPGgscBtcRROqaLfRKBG0liJnnEUPu32uLPnB1dL\nehZsQhyF/YBDJXYGmj0/yLnfTwV9nJrHrYnEz1MqFqYA77h7DzmGvA44BRgNvO3W8IkbuweoHjhM\n0q1ur5rjKFwKPCzpowzYpGRx64Y97ep1ZsVRAYn3nTSbB8xyQAJ4liST2kfSw5DUsLg2GXg/qdMJ\naM3UCsDmgP3E8/M9K6UDy9N00nRgqqRmM7vJ84OerqaGOArfAp4x4wU3Zg1osgPzXqViOLlULCyJ\no3COxNmgdGTraff/z3EU3hlH4Z1m9mOJv7pNGSHRB7ggjsI4jsKngFFmVp+mTWqNr7r/b8RROCmO\nCicD15nZQRIHxFFowCQXKp2eaBRVAGrAI46elSzxdBfifTuOwhfiKLzUjPOAfbK5oL8D/xdgfwNm\nx1H4bkKvFRT+dCfHnqwwhMRokpKL/nFUeNKNVYqj8GDgYeAxknqk9Lpei6OwLCmNy5eBOXEUvu32\nY56b92dxFN7n6HqzGaNAjS45asCvPD+/vquFAphuZjMzqw/4REMb8E2dp3KOrFz1Za3nB3XJGx7w\nW/Vw6dCUpqgFXgMuBtvV84NrU8MuApYkQAmpYupjQacl8Xed0AKo5VooA3wFthuwKdjTld8ndqp9\n2pIV1tHA1mbcFUfh/CqttiCOwhva0KKZlrWrIY5CV86hbiQJrx0ShoI4Cu8oFQtvtmPjqno84A0H\n9A0cjSr3MhWB0aJt1exeLNDeuEjUkZRFzEzRPZPuU6FrSotXsuGVEpE64BzgqjgK57Zhy59iRl2i\n1VutJ1NtHaT3NbWfqn4JiMSHcRR+CVoKTDSzTUGfeH7wc6AHUC4VC+XVzuY1m8qCr5J1qYZvprLT\nXNLlHtBdwF2JNLKGVTOMleMoHG3GS2CjPD84oD3nyUmfC4G74yi83znV+2dz+S5tTKkujgovSRoD\n2sHzg9s8P48EZq3ODQ9N8KsX2zfrCq2Wambben4wOLFnebQFUPYI2LvAFFCYzeV3Xz2H1NJm47Yu\ny/7iKolu9gEtSbzK1TLQN5vLD8/mgrOc8OkfR6GtvVOtfZ02nVwlYCprmycx1/lBqy99W1/q7PnB\nQM8Pdsrm8vuacZFjGgP7vaTfm1Frxo3AG8Dp2Vxeqw38clJKs9TZBXXODvx6qE/qc+YmkSWOAA4H\n8o6xVosGkvYEzTezidlcsLGZlavB5/lB50Tac7Z77hXAUEleexGFOAovAp4HjjTTUaAKw5GUN7Oe\n6/rFGmi43cz4kRnHklSSVmzYRtBQM5sCHCHxrOcHhdZSuu1tcSAaLTFKUq5SvryKeZRT2tTthQzo\nKmkryQYAw0iqQ/ka70Ta3D3lrbZ8PTPMjC/MbJPVxEtbnN8AHJSYjjq1UnaS+GaFhW7PN5K4BdgC\nGCfpqFUBfzkAl5ZryhJzVdn9b8bYATDn1C51XvoMB7jVdZaXAsMlLZWY5urjrUriD3cMdrHnB1dI\n6uVuH+qiNu0NvwfwD8kKZvadRH2qYicvcBux3eqvVdeWiuGYUjEcCVyQZuA4Cj8uFQu7A7uQFMAd\n5fnBL1cxZl/PD25IojC2fhyFT1XWkoJ1eUWTS1mq3nrhTLhpcRTeEkeFo4GjgX08P7hy7QVbpUCP\nvm1F99xLCLpL+tABu9lhuy1c1ldVrlZMnblxFF5UKhZGx1GYc3Z/WrtYqRjOi6PwWGC3JOrG6MzK\nzANg+8qHZeWasrAPKpMgqTv/xiM+cRQujKPwnytTr444lop0vAKcmUQ57LqKNnJcD0n9/knAFWCX\nkpQyPwz8ZkWJpuU0iKOwbGb7gT6T9AywhYtEQVIgZ0BuDWLfnVNzvi797MqpqzgKp5lphDNFVnVs\nc1YchcfHUfgHJ91Sa7F3HZ02qZhcqXltC8xuT9Al/ewNsAVmNvRr+HBPuf3atZoubp49JNvYjOud\n9v6nk6u926Dh5kBb2iyTziuUioWCo3Evzw82aqFv3kXndAdY10zbwFKnbC6/DXBcCxyM2kz5787B\nqHcq5usmRNSeq+ykqbVl0EpmYL0qkQ4HmKuBgpm+B9atwvWeH3RNGNhmAB/HUeHjOAo/JamMrEsK\n81q1hpSJRKlYeMuMXydOo41IzASIo3CCxCzgR56f37GagT0/uMDzg/WrbFNLR2c8P9jb84POnh88\nljZrSsWw0cXZZ7VDO1s1o+kZ1+381kmdfGfgYLMkZNwWiJJ5aBNQV0nFlENu1fvl+cH2nh/kWvsy\nSZ8k8mITJc7y/KB7GybTWNBsiRvdHn4AzHLCZOOqvsPT0aK2cOH2u6fnBzs7bXJKlcmrxNzRo6rK\nxHWVGAPs7my81GI0LP5D+MrLjzd8lDi29gPQjGFnNK4N+BvM7AeSHnOX9gP7C+iTNHE8P9/PjJck\ndUlMAHs9kUq6yfkEA4F/VDFSBOwWR2EXzw+6O4e5N7BHHIVfJJxfIJvLHyhpIvAZMMQlf/Y300Ng\ne0p6Pn1cz/PzZwO/A/UBYgfuTR0DfRe43IUJm1wi5sQ4Cvt5fr4WdE0SA+ePwONOI/UDDomjcEfP\nDwrOb7oksdvtNNC2wD5xFC6qiuVnJJ3rAH2HmZ1aKhY+rayrRWMrA/w6iXxxKzARbD1gFHBvHBXG\nZ3P5yishR7q4+CvA/wI7ONA8CTYyjgrNnh/0AiaZ0UfiEGcq9TRjf4lLgaluTmcDVwGnx1HYnBzV\n5G4ztgVGurMFm7p+A804uFRs8Us8P+hvZtNAMyWudrH5EcB3gP2BBakjoS+YWS9JP2Z5xartB1wD\negH4MKGpPZ0INB0HDDZjr2ru7eJA2LWKsevMuL9UDD97aVzDkWbcJnHAsDMaH1o7qZ/fHjQ4LQnN\n7CtJj8RRuCQ1nxNcJrViXt1oZkOAgZLKQK2Z3V8qFualzhNsDfbLOCqcms3l95a0VRKGs/dKxULk\nxt0YODAhlsm9OOtvTqo0O4XzduJrWEWrCPi1mU0oFQsft85A5wcDh5nxneRYo80E3W7GuxK7pvwA\nS1W/CuydOCpM8vyghwPaHk6j3p6AM2xOP8fNfTDYjs7MMmee3FsdUk3RehDodCDrEj3XA1NTJlY/\nYM/k9JnMxb8/B00CvnA0Xd+BrpJ5TmucRpLjnkNc5tpAmNmrpWLLKyc9P78r6FiSurAPgJ8lPkkL\nw2Zz+QqtNwA7FbQb2ELQQ3EU3pSiwQZuPpnUfBxhbRnoMeBzM/aQOA5sqyS2z71xFN68xoC9a8x/\nJcbtuIbfvjSu4W73+VupGVmTEFpLyDK/yvqbNa3p+bZqYNbm9yurD1qdNy2s+f3818rot9Q7tdqX\nrTw/iDw//142lz/X8/NDPT/Y1PODXp4fZFb29ou23j6xNnPy/GDNU1EvXdaAYS6MyD+HndEY09E6\n2moyrrPDa50dfzrQy2V8b4qjwtP/qrn8P4lms+187WGTAAAAAElFTkSuQmCC\n\n--_----------=_15237875364075492873--\n\n\n--_----------=_15237875364075492872--\n\n\n\t\t\t"
  },
  {
    "path": "devtools/fixtures/raw_emails/invalid-utf-8",
    "content": "Return-Path: <apuestas.es@gmail.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 8973720B373FF\n\tfor <laurent@brasil.brainstorm.fr>; Tue,  5 Apr 2016 10:41:04 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 432481A2D531; Tue,  5 Apr 2016 10:41:04 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 2FDAFF0F8EE; Tue,  5 Apr 2016 10:41:04 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.8 required=5.0 tests=BAYES_60,DKIM_ADSP_CUSTOM_MED,\n\tFREEMAIL_FROM,FREEMAIL_REPLYTO,HTML_MESSAGE,LOTS_OF_MONEY,MISSING_MID,\n\tNML_ADSP_CUSTOM_MED,RCVD_IN_BRBL_LASTEXT,T_TO_NO_BRKTS_FREEMAIL\n\tautolearn=spam version=3.3.2\nX-Spam-Report:\n\t*  0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider\n\t*      (apuestas.es[at]gmail.com)\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [69.165.224.188 listed in bb.barracudacentral.org]\n\t*  0.0 DKIM_ADSP_CUSTOM_MED No valid author signature, adsp_override is\n\t*      CUSTOM_MED\n\t*  1.5 BAYES_60 BODY: Bayes spam probability is 60 to 80%\n\t*      [score: 0.7120]\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.5 MISSING_MID Missing Message-Id: header\n\t*  0.0 LOTS_OF_MONEY Huge... sums of money\n\t*  2.4 FREEMAIL_REPLYTO Reply-To/From or Reply-To/body contain different\n\t*      freemails\n\t*  0.9 NML_ADSP_CUSTOM_MED ADSP custom_med hit, and not from a mailing list\n\t*  0.0 T_TO_NO_BRKTS_FREEMAIL T_TO_NO_BRKTS_FREEMAIL\nX-Spam-DCC: :\nReceived: from blaircarriere.com (mail.blaircarriere.com [69.165.224.188])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 1BDBD321F068\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 10:40:58 +0200 (CEST)\nReceived: from MGI1.MGI.LOCAL (50-192-197-233-static.hfc.comcastbusiness.net [50.192.197.233])\n\t(Authenticated sender: abuse)\n\tby blaircarriere.com (Postfix) with ESMTPA id 0D41413146EDC\n\tfor <dev@caliopen.local>; Tue,  5 Apr 2016 02:35:42 -0600 (MDT)\nContent-Type: multipart/alternative; boundary=\"===============0971163342==\"\nMIME-Version: 1.0\nSubject: APUESTAS de ESTADO\nTo: dev@caliopen.local\nFrom: \"Vicepresidente. Maria Lamia\" <apuestas.es@gmail.com>\nDate: Tue, 05 Apr 2016 02:38:44 -0600\nReply-To: mapfreseguros@spainmail.com\nX-MailScanner-ID: 0D41413146EDC.AFBBB\nX-MailScanner: Found to be clean\nX-MailScanner-From: apuestas.es@gmail.com\nX-Spam-Prev-Subject: APUESTAS de ESTADO\nMessage-Id: <20160405084104.2FDAFF0F8EE@mexico.brainstorm.fr>\nContent-Length: 30647\nLines: 485\n\nYou will not see this in a MIME-aware mail reader.\n--===============0971163342==\nContent-Type: text/plain; charset=\"utf-8\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Description: Mail message body\n\n =\n\n =\n\n                                    LOTERIAS Y APUESTAS del ESTADO SORTEO I=\nNTERNACIONAL\n APARTADO DE CORRES 28007-28109 ALCOBENDAS MADRID SPAIN\n INTERNAZIONALE PROMOZIONI/ DIPARTIMENTO DI PREMIO\n RIFERIMENTO N=B0: S/U/ 6-38\n FILE SICUREZZA N=BA: EMAT/2011/985950\n E-mail: loteriapuestas@gmail.com\n  =\n\n  =\n\n  =\n\n  =\n\n De la Vicepresidente\n Loter=EDa y Apuestas del Estado Internacional   =\n\n Serie N=BA: S/U/ 6-38 el. HORNADA N=BA: EE.UU - 100-0\n  =\n\n  =\n\n  =\n\n Atenci=F3n  Beneficiario,\n  =\n\n  =\n\n NOTIFICA FINAL\n  =\n\n Estamos contentos informarle hoy; el 04/04/2016 del resultado dela 'Loter=\n=EDa y Apuestas del Estado Internacional' se agarr=F3 el 31/03/2016 Como la=\n Parte de nuestro programa anual. Su ref. Atado a boleto N=BA: 01-2577 con =\nSerie N=BA: el S/U/ 6-38. Que Dibuj=F3 la Combinaci=F3n Ganadora 31 marzo 2=\n016, N=BA: 01, 08, 10, 12, 26 y 27 N=BA Compl. 6, Reint. 5, Que Por consigu=\niente que gan=F3 la Loter=EDa en la 2a Categor=EDa.\n  =\n\n Usted ha sido por lo tanto aprobado para una liquidaci=F3n de suma global =\nde =20AC1.504.887,00 (Un mill=F3n quinientos y cuatro mil ochocientos ochen=\nta y siete euros) en efectivo. Todos los participantes fueron seleccionados=\n por un sistema de votaci=F3n de computadora dibujado de 106,210 nombres y =\ncorreos de Australia, Asia, Oriente Medio, Norte y Sudam=E9rica y Europa, c=\nomo la parte de nuestro Programa de Promociones Internacional anual, que es=\n conducido dos veces al a=F1o. FELICITACIONES!!!\n  =\n\n Nuestros programas confiados por millones alrededor del mundo cambian la v=\nida de ganadores. Todas las ganancias internacionales son tratadas por el A=\ngente autorizado del Gobierno Espa=F1ol y recomendadas por el BORDO de LOTE=\nR=CDA ESPA=D1OL. Su cantidad de ganancia es asegurada de su ref. y ser=E1 l=\niberada para el pago por el MINISTERIO DE ECONOMIA despu=E9s de aclaraci=F3=\nn de datos y verificaci=F3n. La comisi=F3n en la cantidad de ganancia no pu=\nede ser descontada de la cantidad de ganancia, debido a la tapa de pol=EDti=\nca de bono de seguros.\n  =\n\n Debido a la mezcla de algunos n=FAmeros, le pedimos di guardar este premio=\n de la atenci=F3n p=FAblica hasta que su reclamaci=F3n haya sido tratada y =\nsu dinero remitido a su cuenta denominada cuando este es la parte de nuestr=\no protocolo de seguridad para evitar la doble reclamaci=F3n o el acto poco =\nescrupuloso por no aprovechamiento de participantes de este programa.\n  =\n\n Para comenzar su reclamaci=F3n, p=F3ngase en contacto con su agente: Don M=\nanuel Rodr=EDguez F, gerente de operaciones extranjero de  MAPFRE SEGUROS S=\n.A. ESPA=D1A TEL;+34631 401 963,  FAX;+34917 692 792, Email; mapfreseguros.=\ns.a@spainmail.com/mapfreseguros@spainmail.com.  Para procesamiento y remesa=\n de su fondo a la cuenta designada de su opci=F3n. Recuerde que todo el din=\nero de precios debe ser reclamado no m=E1s tarde del 30/04/2016, despu=E9s =\nque todos los fondos sin reclamar ser=E1n devueltos al MINISTERIO ECONOMIA =\nY HACIENDA como sin reclamar. Esfu=E9rcese por cotizar su en Cuanto a la Se=\nrie N=BA e Hornada N=BA en cada correspondencia con nosotros. =\n\n  =\n\n NOTA: Debido a razones de seguridad y abuso injustificado de este programa=\n, (doble demandantes), hemos decidido retener la forma de procesamiento de =\npagos,  hasta que se determinan y confirmados por el consejo de 'Loteria y =\nApuestas del Estado Internacional' informaci=F3n del ganador, entonces la f=\norma de procesamiento de pagos ser=E1 emitida a su ref. A fin, para evitar =\nlas tardanzas innecesarias, si habr=E1 alg=FAn cambio de su direcci=F3n o c=\nomplicaciones, informan realmente a su agente cuanto antes, pagar=E1n a su =\nagente 5% del precio de premio total, como la comisi=F3n despu=E9s de que u=\nsted ha recibido su dinero en su cuenta designada, La copia original de su =\nboleto permanece con su agente.\n  =\n\n Comprueba tu premio online aqui; http://www.loterias.com/general/primitiva=\n.html\n   =\n\n Las felicitaciones otra vez de todos nuestro proveen de personal.\n  =\n\n Sinceramente\n MARIA LAMIA (Vicepresidente)\n =20\n\n--===============0971163342==\nContent-Type: text/html; charset=\"utf-8\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nContent-Description: Mail message body\n\n<HTML><head><meta http-equiv=3D\"Content-Type\" content=3D\"text/html; charset=\n=3Dutf-8\"/></head><BODY><P><SPAN style=3D\"FONT-SIZE: 36pt\"><B style=3D\"OUTL=\nINE-WIDTH: medium; OUTLINE-STYLE: none; OUTLINE-COLOR: invert; LINE-HEIGHT:=\n 1.2em\"><SPAN lang=3DEN-GB style=3D\"FONT-SIZE: 8pt; FONT-FAMILY: Calibri, s=\nans-serif; OUTLINE-WIDTH: medium; OUTLINE-STYLE: none; OUTLINE-COLOR: inver=\nt; LINE-HEIGHT: 1.2em\"> </P>\n<H2 style=3D\"MARGIN: 10pt 0cm 0pt 35.4pt\"><SPAN style=3D\"mso-fareast-font-f=\namily: 'Times New Roman'; mso-fareast-language: ES\"><FONT size=3D4><FONT co=\nlor=3D#4f81bd><FONT face=3DCambria>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=\np;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=\nnbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=\np;&nbsp;&nbsp;&nbsp;&nbsp;LOTERIAS Y APUESTAS del ESTADO SORTEO INTERNACION=\nAL<?xml:namespace prefix =3D \"o\" ns =3D \"urn:schemas-microsoft-com:office:o=\nffice\" /><o:p></o:p></FONT></FONT></FONT></SPAN></H2>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN style=3D\"FONT-=\nSIZE: 9pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast=\n-font-family: 'Times New Roman'; mso-fareast-language: ES\">APARTADO DE CORR=\nES 28007-28109 ALCOBENDAS MADRID SPAIN</SPAN></B><B><SPAN style=3D\"FONT-SIZ=\nE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-f=\nont-family: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN>=\n</B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN style=3D\"FONT-=\nSIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast=\n-font-family: 'Times New Roman'; mso-fareast-language: ES\">INTERNAZIONALE P=\nROMOZIONI/ DIPARTIMENTO DI PREMIO</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10=\npt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-f=\namily: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 21.1pt 0pt 0cm; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN style=\n=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Arial Narrow','sans-serif'; COLOR: black;=\n mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-=\nbidi-font-family: Helvetica\">RIFERIMENTO</SPAN></B><B><SPAN style=3D\"FONT-S=\nIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; TEXT-TRANSFORM: uppercase;=\n COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-lang=\nuage: ES; mso-bidi-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN style=3D\"FO=\nNT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; TEXT-TRANSFORM: upperc=\nase; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-=\nlanguage: ES\">N=C2=B0:</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 8pt; FONT-FAM=\nILY: 'Helvetica','sans-serif'; TEXT-TRANSFORM: uppercase; COLOR: black; mso=\n-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-bidi=\n-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 8pt; FONT=\n-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family: '=\nTimes New Roman'; mso-fareast-language: ES\">S/U/ 6-38</SPAN></B><B><SPAN st=\nyle=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black=\n; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES\"><o:=\np></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 21.1pt 0pt 0cm; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN lang=3D=\nEN-US style=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Arial Narrow','sans-serif'; TE=\nXT-TRANSFORM: uppercase; COLOR: black; mso-ansi-language: EN-US; mso-fareas=\nt-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-font-f=\namily: Helvetica\">FILE SICUREZZA</SPAN></B><B><SPAN lang=3DEN-US style=3D\"F=\nONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; TEXT-TRANSFORM: upper=\ncase; COLOR: black; mso-ansi-language: EN-US; mso-fareast-font-family: 'Tim=\nes New Roman'; mso-fareast-language: ES; mso-bidi-font-size: 11.0pt\">&nbsp;=\n</SPAN></B><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Hel=\nvetica','sans-serif'; TEXT-TRANSFORM: uppercase; COLOR: black; mso-ansi-lan=\nguage: EN-US; mso-fareast-font-family: 'Times New Roman'; mso-fareast-langu=\nage: ES\">N=C2=BA:</SPAN></B><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 8pt; =\nFONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-=\nUS; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES; m=\nso-bidi-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN lang=3DEN-US style=3D\"=\nFONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-an=\nsi-language: EN-US; mso-fareast-font-family: 'Times New Roman'; mso-fareast=\n-language: ES\">EMAT/2011/985950</SPAN></B><B><SPAN lang=3DEN-US style=3D\"FO=\nNT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-ans=\ni-language: EN-US; mso-fareast-font-family: 'Times New Roman'; mso-fareast-=\nlanguage: ES\"><o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 21.1pt 0pt 0cm; LINE-HEIGHT: 11.25pt\" align=3Dcenter><B><SPAN lang=3D=\nEN-US style=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR=\n: black; mso-ansi-language: EN-US; mso-fareast-font-family: 'Times New Roma=\nn'; mso-fareast-language: ES\">E-mail:</SPAN></B><B><SPAN lang=3DEN-US style=\n=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; ms=\no-ansi-language: EN-US; mso-fareast-font-family: 'Times New Roman'; mso-far=\neast-language: ES; mso-bidi-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN st=\nyle=3D\"FONT-SIZE: 8pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black;=\n mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-=\nbidi-font-size: 11.0pt\"><A href=3D\"https://es-mg42.mail.yahoo.com/neo/b/com=\npose?to=3Dloteriapuestas@gmail.com\" target=3D_blank><SPAN lang=3DEN-US styl=\ne=3D\"COLOR: black; mso-ansi-language: EN-US\">loteriapuestas@gmail.com</SPAN=\n></A></SPAN></B><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMILY=\n: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-far=\neast-font-family: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p><=\n/SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMIL=\nY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-fa=\nreast-font-family: 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p>=\n</o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMIL=\nY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-fa=\nreast-font-family: 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p>=\n</o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMIL=\nY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-fa=\nreast-font-family: 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p>=\n</o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN lang=3DEN-US style=3D\"FONT-SIZE: 10pt; FONT-FAMIL=\nY: 'Helvetica','sans-serif'; COLOR: black; mso-ansi-language: EN-US; mso-fa=\nreast-font-family: 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p>=\n</o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 14.4pt; mso-outline-level: 1\"><FONT size=3D3><B><SPAN style=3D\"FONT=\n-FAMILY: 'Arial','sans-serif'; COLOR: black; mso-fareast-font-family: 'Time=\ns New Roman'; mso-fareast-language: ES; mso-font-kerning: 18.0pt\">De la Vic=\nepresidente</SPAN></B><B><SPAN style=3D\"FONT-FAMILY: 'Helvetica','sans-seri=\nf'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-l=\nanguage: ES; mso-font-kerning: 18.0pt\"><o:p></o:p></SPAN></B></FONT></P><B>=\n<SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLO=\nR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language:=\n ES\">\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">Loter=C3=ADa y Apuestas del Estado Internacional&n=\nbsp;</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica',=\n'sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso=\n-fareast-language: ES; mso-bidi-font-size: 11.0pt\">&nbsp;</SPAN></B><B><SPA=\nN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: b=\nlack; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES\"=\n> <o:p></o:p></SPAN></B></P></SPAN></B>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">Serie N=C2=BA: S/U/ 6-38 el. HORNADA N=C2=BA: EE.U=\nU - 100-0<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">Atenci=C3=B3n&nbsp;</SPAN></B><B><SPAN style=3D\"FO=\nNT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-far=\neast-font-family: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-fon=\nt-size: 11.0pt\">&nbsp;</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FA=\nMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family: 'Tim=\nes New Roman'; mso-fareast-language: ES\">Beneficiario,<o:p></o:p></SPAN></B=\n></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: center; MARGIN=\n: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\" align=3Dcenter><FONT size=3D3><B><U><S=\nPAN style=3D\"FONT-FAMILY: 'Arial','sans-serif'; COLOR: black; mso-fareast-f=\nont-family: 'Times New Roman'; mso-fareast-language: ES\">NOTIFICA FINAL</SP=\nAN></U></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','san=\ns-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-far=\neast-language: ES\"><o:p></o:p></SPAN></B></FONT></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; MARGIN: 0cm 0cm 0pt; LINE-=\nHEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica=\n','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; m=\nso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Estamos contentos informarle hoy; el 04/04/2016 d=\nel resultado dela 'Loter=C3=ADa y Apuestas del Estado Internacional' se aga=\nrr=C3=B3 el 31/03/2016 Como la Parte de nuestro programa anual. Su ref. Ata=\ndo a boleto N=C2=BA: 01-2577 con Serie N=C2=BA: el S/U/ 6-38. Que Dibuj=C3=\n=B3 la Combinaci=C3=B3n Ganadora 31 marzo 2016, </SPAN></B><B><U><SPAN styl=\ne=3D\"FONT-SIZE: 9pt; COLOR: black; mso-fareast-font-family: 'Times New Roma=\nn'; mso-fareast-language: ES; mso-bidi-font-family: Calibri; mso-ascii-font=\n-family: Calibri; mso-hansi-font-family: Calibri; mso-themecolor: text1\">N=\n=C2=BA: 01, 08, 10, 12, 26 y 27 N=C2=BA Compl. 6, Reint. 5,</SPAN></U></B><=\nB><SPAN style=3D\"FONT-SIZE: 9pt; COLOR: black; mso-fareast-font-family: 'Ti=\nmes New Roman'; mso-fareast-language: ES; mso-bidi-font-family: Calibri; ms=\no-ascii-font-family: Calibri; mso-hansi-font-family: Calibri\"> Que Por cons=\niguiente que gan=C3=B3 la Loter=C3=ADa en la 2a Categor=C3=ADa.<o:p></o:p><=\n/SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\"><o:p>&nbsp;</o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Usted ha sido por lo tanto aprobado para una liqu=\nidaci=C3=B3n de suma global de =E2=82=AC1.504.887,00 (<U><SPAN style=3D\"TEX=\nT-TRANSFORM: uppercase\">Un mill=C3=B3n quinientos y cuatro mil ochocientos =\nochenta y siete euros</SPAN></U>) en efectivo. Todos los participantes fuer=\non seleccionados por un sistema de votaci=C3=B3n de computadora dibujado de=\n 106,210 nombres y correos de Australia, Asia, Oriente Medio, Norte y Sudam=\n=C3=A9rica y Europa, como la parte de nuestro Programa de Promociones Inter=\nnacional anual, que es conducido dos veces al a=C3=B1o.</SPAN></B><B><SPAN =\nstyle=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: bla=\nck; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES\"> =\n</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 9pt; COLOR: red; mso-fareast-font-f=\namily: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-font-family: C=\nalibri; mso-ascii-font-family: Calibri; mso-hansi-font-family: Calibri\">FEL=\nICITACIONES!!!</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'H=\nelvetica','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times New R=\noman'; mso-fareast-language: ES\"><o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Nuestros programas confiados por millones alreded=\nor del mundo cambian la vida de ganadores. Todas las ganancias internaciona=\nles son tratadas por el Agente autorizado del Gobierno Espa=C3=B1ol y recom=\nendadas por el BORDO de LOTER=C3=8DA ESPA=C3=91OL. Su cantidad de ganancia =\nes asegurada de su ref. y ser=C3=A1 liberada para el pago por el MINISTERIO=\n DE ECONOMIA despu=C3=A9s de aclaraci=C3=B3n de datos y verificaci=C3=B3n. =\nLa comisi=C3=B3n en la cantidad de ganancia no puede ser descontada de la c=\nantidad de ganancia, debido a la tapa de pol=C3=ADtica de bono de seguros.<=\n/SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans=\n-serif'; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fare=\nast-language: ES\"><o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Debido a la mezcla de algunos n=C3=BAmeros, le pe=\ndimos di guardar este premio de la atenci=C3=B3n p=C3=BAblica hasta que su =\nreclamaci=C3=B3n haya sido tratada y su dinero remitido a su cuenta denomin=\nada cuando este es la parte de nuestro protocolo de seguridad para evitar l=\na doble reclamaci=C3=B3n o el acto poco escrupuloso por no aprovechamiento =\nde participantes de este programa.</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 9=\npt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-f=\namily: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language=\n: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-ha=\nnsi-font-family: Calibri\">Para comenzar su reclamaci=C3=B3n, p=C3=B3ngase e=\nn contacto con su agente:&nbsp;<U>Don Manuel Rodr=C3=ADguez F</U>, gerente =\nde operaciones extranjero de&nbsp; <U>MAPFRE SEGUROS S.A. ESPA=C3=91A TEL;+=\n34631 401 963, &nbsp;FAX;+34917 692 792, Email;&nbsp;<A href=3D\"https://es-=\nmg42.mail.yahoo.com/neo/b/compose?to=3Dmapfreseguros.s.a@spainmail.com/mapf=\nreseguros@spainmail.com\" target=3D_blank><SPAN style=3D\"COLOR: black\">mapfr=\neseguros.s.a@spainmail.com/mapfreseguros@spainmail.com</SPAN></A>.</U>&nbsp=\n;&nbsp;Para procesamiento y remesa de su fondo a la cuenta designada de su =\nopci=C3=B3n. Recuerde que todo el dinero de precios debe ser reclamado no m=\n=C3=A1s tarde del&nbsp;<U>30/04/2016</U>, despu=C3=A9s que todos los fondos=\n sin reclamar ser=C3=A1n devueltos al MINISTERIO ECONOMIA Y HACIENDA como s=\nin reclamar. Esfu=C3=A9rcese por cotizar su en Cuanto a la Serie N=C2=BA e =\nHornada N=C2=BA en cada correspondencia con nosotros</SPAN></B><B><SPAN sty=\nle=3D\"FONT-SIZE: 10pt; COLOR: black; mso-fareast-font-family: 'Times New Ro=\nman'; mso-fareast-language: ES; mso-bidi-font-family: Calibri; mso-ascii-fo=\nnt-family: Calibri; mso-hansi-font-family: Calibri\">. </SPAN></B><B><SPAN s=\ntyle=3D\"FONT-SIZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: blac=\nk; mso-fareast-font-family: 'Times New Roman'; mso-fareast-language: ES\"><o=\n:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 9pt; COL=\nOR: #c00000; mso-fareast-font-family: 'Times New Roman'; mso-fareast-langua=\nge: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-=\nhansi-font-family: Calibri\">NOTA:</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10=\npt; COLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-l=\nanguage: ES; mso-bidi-font-family: Calibri; mso-bidi-font-size: 11.0pt; mso=\n-ascii-font-family: Calibri; mso-hansi-font-family: Calibri\">&nbsp;</SPAN><=\n/B><B><SPAN style=3D\"FONT-SIZE: 9pt; COLOR: #c00000; mso-fareast-font-famil=\ny: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-font-family: Calib=\nri; mso-ascii-font-family: Calibri; mso-hansi-font-family: Calibri\">Debido =\na razones de seguridad y abuso injustificado de este programa, (doble deman=\ndantes), hemos decidido retener la forma de procesamiento de pagos,</SPAN><=\n/B><B><SPAN style=3D\"FONT-SIZE: 10pt; COLOR: black; mso-fareast-font-family=\n: 'Times New Roman'; mso-fareast-language: ES; mso-bidi-font-family: Calibr=\ni; mso-bidi-font-size: 11.0pt; mso-ascii-font-family: Calibri; mso-hansi-fo=\nnt-family: Calibri\">&nbsp;</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 9pt; COLO=\nR: #c00000; mso-fareast-font-family: 'Times New Roman'; mso-fareast-languag=\ne: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-h=\nansi-font-family: Calibri\">&nbsp;hasta que se determinan y confirmados por =\nel consejo de 'Loteria y Apuestas del Estado Internacional' informaci=C3=B3=\nn del ganador, entonces la forma de procesamiento de pagos ser=C3=A1 emitid=\na a su ref. A fin, para evitar las tardanzas innecesarias, si habr=C3=A1 al=\ng=C3=BAn cambio de su direcci=C3=B3n o complicaciones, informan realmente a=\n su agente cuanto antes, pagar=C3=A1n a su agente 5% del precio de premio t=\notal, como la comisi=C3=B3n despu=C3=A9s de que usted ha recibido su dinero=\n en su cuenta designada, La copia original de su boleto permanece con su ag=\nente.<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\"><o:p>&nbsp;</o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">Comprueba tu premio online aq=\nui; <A href=3D\"http://www.loterias.com/general/primitiva.html\"><FONT color=\n=3D#0000ff>http://www.loterias.com/general/primitiva.html</FONT></A><o:p></=\no:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\"><SPAN style=3D\"mso-spacerun: =\nyes\">&nbsp;</SPAN>&nbsp;<o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; CO=\nLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-languag=\ne: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-h=\nansi-font-family: Calibri\">Las felicitaciones otra vez de todos nuestro pro=\nveen de personal.</SPAN></B><B><SPAN style=3D\"FONT-SIZE: 10pt; FONT-FAMILY:=\n 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family: 'Times Ne=\nw Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN></B></P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; FO=\nNT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-font-family:=\n 'Times New Roman'; mso-fareast-language: ES\">&nbsp;<o:p></o:p></SPAN></B><=\n/P>\n<P class=3DMsoNormal style=3D\"BACKGROUND: white; TEXT-ALIGN: justify; MARGI=\nN: 0cm 0cm 0pt; LINE-HEIGHT: 11.25pt\"><B><SPAN style=3D\"FONT-SIZE: 10pt; CO=\nLOR: black; mso-fareast-font-family: 'Times New Roman'; mso-fareast-languag=\ne: ES; mso-bidi-font-family: Calibri; mso-ascii-font-family: Calibri; mso-h=\nansi-font-family: Calibri\">Sinceramente</SPAN></B><B><SPAN style=3D\"FONT-SI=\nZE: 10pt; FONT-FAMILY: 'Helvetica','sans-serif'; COLOR: black; mso-fareast-=\nfont-family: 'Times New Roman'; mso-fareast-language: ES\"><o:p></o:p></SPAN=\n></B></P>\n<P class=3DMsoNormal style=3D\"MARGIN: 0cm 0cm 10pt\"><B><SPAN style=3D\"FONT-=\nSIZE: 10pt; FONT-FAMILY: 'Andalus','serif'; COLOR: black; LINE-HEIGHT: 115%=\n\">MARIA LAMIA (Vicepresidente)<o:p></o:p></SPAN></B></P>\n<P class=3Dyiv9723979600MsoNormal style=3D\"WHITE-SPACE: normal; WORD-SPACIN=\nG: 0px; TEXT-TRANSFORM: none; COLOR: rgb(0,0,0); OUTLINE-WIDTH: medium; PAD=\nDING-BOTTOM: 0px; TEXT-ALIGN: center; PADDING-TOP: 0px; FONT: 13px/15px Hel=\nvetica, Arial, sans-serif; OUTLINE-STYLE: none; PADDING-LEFT: 0px; MARGIN: =\n0in 0in 0pt; WIDOWS: 1; DISPLAY: block; LETTER-SPACING: normal; OUTLINE-COL=\nOR: invert; PADDING-RIGHT: 0px; BACKGROUND-COLOR: rgb(255,255,255); TEXT-IN=\nDENT: 0px; -webkit-text-stroke-width: 0px; font-stretch: normal\" align=3Dce=\nnter></SPAN></B>&nbsp;</P></SPAN></BODY></HTML>\n--===============0971163342==--\n"
  },
  {
    "path": "devtools/fixtures/raw_emails/multipart-html",
    "content": "Return-Path: <contact@caliopen.org>\nDelivered-To: xxxx@caliopen.org\nReceived: from spool.mail.gandi.net (mspool1-d.mgt.gandi.net [10.0.21.131])\n\tby nmboxes64.sd2.0x35.net (Postfix) with ESMTP id 488E9285EF\n\tfor <xxxx@caliopen.org>; Thu, 16 Nov 2017 15:11:05 +0100 (CET)\nReceived: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net [IPv6:2001:4b98:c:538::196])\n\tby spool.mail.gandi.net (Postfix) with ESMTPS id 2F52D22604A\n\tfor <xxxx@caliopen.org>; Thu, 16 Nov 2017 15:11:05 +0100 (CET)\nReceived: from welcome.caliopen.org (unknown [IPv6:2001:4b98:dc0:51:216:3eff:fe1c:692])\n\t(Authenticated sender: contact@caliopen.org)\n\tby relay4-d.mail.gandi.net (Postfix) with ESMTPSA id 179F31720C8\n\tfor <xxxx@caliopen.org>; Thu, 16 Nov 2017 15:11:04 +0100 (CET)\nMessage-ID: <853258764c7e0064d446ad6bf15c2ffe@swift.generated>\nDate: Tue, 14 Nov 2017 11:05:02 +0000\nSubject: Caliopen Alpha!\nFrom: Caliopen <contact@caliopen.org>\nTo: xxxx@caliopen.org\nMIME-Version: 1.0\nContent-Type: multipart/alternative;\n boundary=\"_=_swift_v4_1510657502_188ea61ef5dcf264f5cb7dfc84030c2b_=_\"\nList-Unsubscribe: <https://welcome.caliopen.org/email/unsubscribe/5a0acdde750ef>\nX-Spam-Level: *\n\n\n--_=_swift_v4_1510657502_188ea61ef5dcf264f5cb7dfc84030c2b_=_\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nGood News!\n\nBonjour ,\n_Hello ,_\n\nVous pouvez d=C3=A9sormais cr=\n=C3=A9er votre compte sur :\nhttps://welcome.caliopen.org/r/5a3fab4cc13f54=\n516a26520c3?ct=3DYTo1OntzOjY6InNvdXJjZSI7YToyOntpOjA7czoxNDoiY2FtcGFpZ24uZX=\nZlbnQiO2k6MTtpOjEwNzt9czo1OiJlbWFpbCI7aTo3O3M6NDoic3RhdCI7czoxMzoiNWEwYWNkZ=\nGU3NTBlZiI7czo0OiJsZWFkIjtpOjY2NTA7czo3OiJjaGFubmVsIjthOjE6e3M6NToiZW1haWwi=\nO2k6Nzt9fQ%3D%3D&\n[https://welcome.caliopen.org/r/5a3fab4cc13f54516a26520=\nc3?ct=3DYTo1OntzOjY6InNvdXJjZSI7YToyOntpOjA7czoxNDoiY2FtcGFpZ24uZXZlbnQiO2k=\n6MTtpOjEwNzt9czo1OiJlbWFpbCI7aTo3O3M6NDoic3RhdCI7czoxMzoiNWEwYWNkZGU3NTBlZi=\nI7czo0OiJsZWFkIjtpOjY2NTA7czo3OiJjaGFubmVsIjthOjE6e3M6NToiZW1haWwiO2k6Nzt9f=\nQ%3D%3D&]\nen utilisant l'adresse email utilis=C3=A9e a l'inscription.\n=\n\n_Now you can create your account on :_\n_https://welcome.caliopen.org/r=\n/5a3fab4cc13f54516a26520c3?ct=3DYTo1OntzOjY6InNvdXJjZSI7YToyOntpOjA7czoxNDo=\niY2FtcGFpZ24uZXZlbnQiO2k6MTtpOjEwNzt9czo1OiJlbWFpbCI7aTo3O3M6NDoic3RhdCI7cz=\noxMzoiNWEwYWNkZGU3NTBlZiI7czo0OiJsZWFkIjtpOjY2NTA7czo3OiJjaGFubmVsIjthOjE6e=\n3M6NToiZW1haWwiO2k6Nzt9fQ%3D%3D&\n[https://welcome.caliopen.org/r/5a3fab4c=\nc13f54516a26520c3?ct=3DYTo1OntzOjY6InNvdXJjZSI7YToyOntpOjA7czoxNDoiY2FtcGFp=\nZ24uZXZlbnQiO2k6MTtpOjEwNzt9czo1OiJlbWFpbCI7aTo3O3M6NDoic3RhdCI7czoxMzoiNWE=\nwYWNkZGU3NTBlZiI7czo0OiJsZWFkIjtpOjY2NTA7czo3OiJjaGFubmVsIjthOjE6e3M6NToiZW=\n1haWwiO2k6Nzt9fQ%3D%3D&]_\n_with e-mail adress used on signup._\n\n_Have=\n fun=C2=A0! Be good=C2=A0!_\n\n--_=_swift_v4_1510657502_188ea61ef5dcf264f5cb7dfc84030c2b_=_\nContent-Type: text/html; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.=\nw3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html xmlns=3D\"http://www.w3.=\norg/1999/xhtml\" class=3D\" js flexbox flexboxlegacy canvas canvastext webgl =\nno-touch geolocation postmessage websqldatabase indexeddb hashchange histor=\ny draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage b=\norderradius boxshadow textshadow opacity cssanimations csscolumns cssgradie=\nnts cssreflections csstransforms csstransforms3d csstransitions fontface ge=\nneratedcontent video audio localstorage sessionstorage webworkers applicati=\noncache svg inlinesvg smil svgclippaths js csstransforms csstransforms3d cs=\nstransitions   js flexbox flexboxlegacy canvas canvastext webgl no-touch ge=\nolocation postmessage websqldatabase indexeddb hashchange history draganddr=\nop websockets rgba hsla multiplebgs backgroundsize borderimage borderradius=\n boxshadow textshadow opacity cssanimations csscolumns cssgradients cssrefl=\nections csstransforms csstransforms3d csstransitions fontface generatedcont=\nent video audio localstorage sessionstorage webworkers applicationcache svg=\n inlinesvg smil svgclippaths js csstransforms csstransforms3d csstransition=\ns   js flexbox flexboxlegacy canvas canvastext webgl no-touch geolocation p=\nostmessage websqldatabase indexeddb hashchange history draganddrop websocke=\nts rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow =\ntextshadow opacity cssanimations csscolumns cssgradients cssreflections css=\ntransforms csstransforms3d csstransitions fontface generatedcontent video a=\nudio localstorage sessionstorage webworkers applicationcache svg inlinesvg =\nsmil svgclippaths js csstransforms csstransforms3d csstransitions   js flex=\nbox flexboxlegacy canvas canvastext webgl no-touch geolocation postmessage =\nwebsqldatabase indexeddb hashchange history draganddrop websockets rgba hsl=\na multiplebgs backgroundsize borderimage borderradius boxshadow textshadow =\nopacity cssanimations csscolumns cssgradients cssreflections csstransforms =\ncsstransforms3d csstransitions fontface generatedcontent video audio locals=\ntorage sessionstorage webworkers applicationcache svg inlinesvg smil svgcli=\nppaths js csstransforms csstransforms3d csstransitions   js flexbox flexbox=\nlegacy canvas canvastext webgl no-touch geolocation postmessage websqldatab=\nase indexeddb hashchange history draganddrop websockets rgba hsla multipleb=\ngs backgroundsize borderimage borderradius boxshadow textshadow opacity css=\nanimations csscolumns cssgradients cssreflections csstransforms csstransfor=\nms3d csstransitions fontface generatedcontent video audio localstorage sess=\nionstorage webworkers applicationcache svg inlinesvg smil svgclippaths js c=\nsstransforms csstransforms3d csstransitions   js flexbox flexboxlegacy canv=\nas canvastext webgl no-touch geolocation postmessage websqldatabase indexed=\ndb hashchange history draganddrop websockets rgba hsla multiplebgs backgrou=\nndsize borderimage borderradius boxshadow textshadow opacity cssanimations =\ncsscolumns cssgradients cssreflections csstransforms csstransforms3d csstra=\nnsitions fontface generatedcontent video audio localstorage sessionstorage =\nwebworkers applicationcache svg inlinesvg smil svgclippaths js csstransform=\ns csstransforms3d csstransitions   js flexbox flexboxlegacy canvas canvaste=\nxt webgl no-touch geolocation postmessage websqldatabase indexeddb hashchan=\nge history draganddrop websockets rgba hsla multiplebgs backgroundsize bord=\nerimage borderradius boxshadow textshadow opacity cssanimations csscolumns =\ncssgradients cssreflections csstransforms csstransforms3d csstransitions fo=\nntface generatedcontent video audio localstorage sessionstorage webworkers =\napplicationcache svg inlinesvg smil svgclippaths js csstransforms csstransf=\norms3d csstransitions   js flexbox flexboxlegacy canvas canvastext webgl no=\n-touch geolocation postmessage websqldatabase indexeddb hashchange history =\ndraganddrop websockets rgba hsla multiplebgs backgroundsize borderimage bor=\nderradius boxshadow textshadow opacity cssanimations csscolumns cssgradient=\ns cssreflections csstransforms csstransforms3d csstransitions fontface gene=\nratedcontent video audio localstorage sessionstorage webworkers application=\ncache svg inlinesvg smil svgclippaths js csstransforms csstransforms3d csst=\nransitions   js flexbox flexboxlegacy canvas canvastext webgl no-touch geol=\nocation postmessage websqldatabase indexeddb hashchange history draganddrop=\n websockets rgba hsla multiplebgs backgroundsize borderimage borderradius b=\noxshadow textshadow opacity cssanimations csscolumns cssgradients cssreflec=\ntions csstransforms csstransforms3d csstransitions fontface generatedconten=\nt video audio localstorage sessionstorage webworkers applicationcache svg i=\nnlinesvg smil svgclippaths js csstransforms csstransforms3d csstransitions =\n  js flexbox flexboxlegacy canvas canvastext webgl no-touch geolocation pos=\ntmessage websqldatabase indexeddb hashchange history draganddrop websockets=\n rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow te=\nxtshadow opacity cssanimations csscolumns cssgradients cssreflections csstr=\nansforms csstransforms3d csstransitions fontface generatedcontent video aud=\nio localstorage sessionstorage webworkers applicationcache svg inlinesvg sm=\nil svgclippaths js csstransforms csstransforms3d csstransitions   js flexbo=\nx flexboxlegacy canvas canvastext webgl no-touch geolocation postmessage we=\nbsqldatabase indexeddb hashchange history draganddrop websockets rgba hsla =\nmultiplebgs backgroundsize borderimage borderradius boxshadow textshadow op=\nacity cssanimations csscolumns cssgradients cssreflections csstransforms cs=\nstransforms3d csstransitions fontface generatedcontent video audio localsto=\nrage sessionstorage webworkers applicationcache svg inlinesvg smil svgclipp=\naths js csstransforms csstransforms3d csstransitions   js flexbox flexboxle=\ngacy canvas canvastext webgl no-touch geolocation postmessage websqldatabas=\ne indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs=\n backgroundsize borderimage borderradius boxshadow textshadow opacity cssan=\nimations csscolumns cssgradients cssreflections csstransforms csstransforms=\n3d csstransitions fontface generatedcontent video audio localstorage sessio=\nnstorage webworkers applicationcache svg inlinesvg smil svgclippaths js css=\ntransforms csstransforms3d csstransitions   js flexbox flexboxlegacy canvas=\n canvastext webgl no-touch geolocation postmessage websqldatabase indexeddb=\n hashchange history draganddrop websockets rgba hsla multiplebgs background=\nsize borderimage borderradius boxshadow textshadow opacity cssanimations cs=\nscolumns cssgradients cssreflections csstransforms csstransforms3d csstrans=\nitions fontface generatedcontent video audio localstorage sessionstorage we=\nbworkers applicationcache svg inlinesvg smil svgclippaths js csstransforms =\ncsstransforms3d csstransitions   js flexbox flexboxlegacy canvas canvastext=\n webgl no-touch geolocation postmessage websqldatabase indexeddb hashchange=\n history draganddrop websockets rgba hsla multiplebgs backgroundsize border=\nimage borderradius boxshadow textshadow opacity cssanimations csscolumns cs=\nsgradients cssreflections csstransforms csstransforms3d csstransitions font=\nface generatedcontent video audio localstorage sessionstorage webworkers ap=\nplicationcache svg inlinesvg smil svgclippaths js csstransforms csstransfor=\nms3d csstransitions   js flexbox flexboxlegacy canvas canvastext webgl no-t=\nouch geolocation postmessage websqldatabase indexeddb hashchange history dr=\naganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borde=\nrradius boxshadow textshadow opacity cssanimations csscolumns cssgradients =\ncssreflections csstransforms csstransforms3d csstransitions fontface genera=\ntedcontent video audio localstorage sessionstorage webworkers applicationca=\nche svg inlinesvg smil svgclippaths js csstransforms csstransforms3d csstra=\nnsitions   js flexbox flexboxlegacy canvas canvastext webgl no-touch geoloc=\nation postmessage websqldatabase indexeddb hashchange history draganddrop w=\nebsockets rgba hsla multiplebgs backgroundsize borderimage borderradius box=\nshadow textshadow opacity cssanimations csscolumns cssgradients cssreflecti=\nons csstransforms csstransforms3d csstransitions fontface generatedcontent =\nvideo audio localstorage sessionstorage webworkers applicationcache svg inl=\ninesvg smil svgclippaths js csstransforms csstransforms3d csstransitions   =\njs flexbox flexboxlegacy canvas canvastext webgl no-touch geolocation postm=\nessage websqldatabase indexeddb hashchange history draganddrop websockets r=\ngba hsla multiplebgs backgroundsize borderimage borderradius boxshadow text=\nshadow opacity cssanimations csscolumns cssgradients cssreflections csstran=\nsforms csstransforms3d csstransitions fontface generatedcontent video audio=\n localstorage sessionstorage webworkers applicationcache svg inlinesvg smil=\n svgclippaths js csstransforms csstransforms3d csstransitions   js flexbox =\nflexboxlegacy canvas canvastext webgl no-touch geolocation postmessage webs=\nqldatabase indexeddb hashchange history draganddrop websockets rgba hsla mu=\nltiplebgs backgroundsize borderimage borderradius boxshadow textshadow opac=\nity cssanimations csscolumns cssgradients cssreflections csstransforms csst=\nransforms3d csstransitions fontface generatedcontent video audio localstora=\nge sessionstorage webworkers applicationcache svg inlinesvg smil svgclippat=\nhs js csstransforms csstransforms3d csstransitions   js flexbox flexboxlega=\ncy canvas canvastext webgl no-touch geolocation postmessage websqldatabase =\nindexeddb hashchange history draganddrop websockets rgba hsla multiplebgs b=\nackgroundsize borderimage borderradius boxshadow textshadow opacity cssanim=\nations csscolumns cssgradients cssreflections csstransforms csstransforms3d=\n csstransitions fontface generatedcontent video audio localstorage sessions=\ntorage webworkers applicationcache svg inlinesvg smil svgclippaths js csstr=\nansforms csstransforms3d csstransitions   js flexbox flexboxlegacy canvas c=\nanvastext webgl no-touch geolocation postmessage websqldatabase indexeddb h=\nashchange history draganddrop websockets rgba hsla multiplebgs backgroundsi=\nze borderimage borderradius boxshadow textshadow opacity cssanimations cssc=\nolumns cssgradients cssreflections csstransforms csstransforms3d csstransit=\nions fontface generatedcontent video audio localstorage sessionstorage webw=\norkers applicationcache svg inlinesvg smil svgclippaths js csstransforms cs=\nstransforms3d csstransitions   js flexbox flexboxlegacy canvas canvastext w=\nebgl no-touch geolocation postmessage websqldatabase indexeddb hashchange h=\nistory draganddrop websockets rgba hsla multiplebgs backgroundsize borderim=\nage borderradius boxshadow textshadow opacity cssanimations csscolumns cssg=\nradients cssreflections csstransforms csstransforms3d csstransitions fontfa=\nce generatedcontent video audio localstorage sessionstorage webworkers appl=\nicationcache svg inlinesvg smil svgclippaths js csstransforms csstransforms=\n3d csstransitions   js flexbox flexboxlegacy canvas canvastext webgl no-tou=\nch geolocation postmessage websqldatabase indexeddb hashchange history drag=\nanddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderr=\nadius boxshadow textshadow opacity cssanimations csscolumns cssgradients cs=\nsreflections csstransforms csstransforms3d csstransitions fontface generate=\ndcontent video audio localstorage sessionstorage webworkers applicationcach=\ne svg inlinesvg smil svgclippaths js csstransforms csstransforms3d csstrans=\nitions   js flexbox flexboxlegacy canvas canvastext webgl no-touch geolocat=\nion postmessage websqldatabase indexeddb hashchange history draganddrop web=\nsockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxsh=\nadow textshadow opacity cssanimations csscolumns cssgradients cssreflection=\ns csstransforms csstransforms3d csstransitions fontface generatedcontent vi=\ndeo audio localstorage sessionstorage webworkers applicationcache svg inlin=\nesvg smil svgclippaths js csstransforms csstransforms3d csstransitions   js=\n flexbox flexboxlegacy canvas canvastext webgl no-touch geolocation postmes=\nsage websqldatabase indexeddb hashchange history draganddrop websockets rgb=\na hsla multiplebgs backgroundsize borderimage borderradius boxshadow textsh=\nadow opacity cssanimations csscolumns cssgradients cssreflections csstransf=\norms csstransforms3d csstransitions fontface generatedcontent video audio l=\nocalstorage sessionstorage webworkers applicationcache svg inlinesvg smil s=\nvgclippaths js csstransforms csstransforms3d csstransitions   js flexbox fl=\nexboxlegacy canvas canvastext webgl no-touch geolocation postmessage websql=\ndatabase indexeddb hashchange history draganddrop websockets rgba hsla mult=\niplebgs backgroundsize borderimage borderradius boxshadow textshadow opacit=\ny cssanimations csscolumns cssgradients cssreflections csstransforms csstra=\nnsforms3d csstransitions fontface generatedcontent video audio localstorage=\n sessionstorage webworkers applicationcache svg inlinesvg smil svgclippaths=\n js csstransforms csstransforms3d csstransitions   js flexbox flexboxlegacy=\n canvas canvastext webgl no-touch geolocation postmessage websqldatabase in=\ndexeddb hashchange history draganddrop websockets rgba hsla multiplebgs bac=\nkgroundsize borderimage borderradius boxshadow textshadow opacity cssanimat=\nions csscolumns cssgradients cssreflections csstransforms csstransforms3d c=\nsstransitions fontface generatedcontent video audio localstorage sessionsto=\nrage webworkers applicationcache svg inlinesvg smil svgclippaths js csstran=\nsforms csstransforms3d csstransitions   js flexbox flexboxlegacy canvas can=\nvastext webgl no-touch geolocation postmessage websqldatabase indexeddb has=\nhchange history draganddrop websockets rgba hsla multiplebgs backgroundsize=\n borderimage borderradius boxshadow textshadow opacity cssanimations csscol=\numns cssgradients cssreflections csstransforms csstransforms3d csstransitio=\nns fontface generatedcontent video audio localstorage sessionstorage webwor=\nkers applicationcache svg inlinesvg smil svgclippaths js csstransforms csst=\nransforms3d csstransitions   js flexbox flexboxlegacy canvas canvastext web=\ngl no-touch geolocation postmessage websqldatabase indexeddb hashchange his=\ntory draganddrop websockets rgba hsla multiplebgs backgroundsize borderimag=\ne borderradius boxshadow textshadow opacity cssanimations csscolumns cssgra=\ndients cssreflections csstransforms csstransforms3d csstransitions fontface=\n generatedcontent video audio localstorage sessionstorage webworkers applic=\nationcache svg inlinesvg smil svgclippaths js csstransforms csstransforms3d=\n csstransitions   js flexbox flexboxlegacy canvas canvastext webgl no-touch=\n geolocation postmessage websqldatabase indexeddb hashchange history dragan=\nddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderrad=\nius boxshadow textshadow opacity cssanimations csscolumns cssgradients cssr=\neflections csstransforms csstransforms3d csstransitions fontface generatedc=\nontent video audio localstorage sessionstorage webworkers applicationcache =\nsvg inlinesvg smil svgclippaths js csstransforms csstransforms3d csstransit=\nions   js flexbox flexboxlegacy canvas canvastext webgl no-touch geolocatio=\nn postmessage websqldatabase indexeddb hashchange history draganddrop webso=\nckets rgba hsla multiplebgs backgroundsize borderimage borderradius boxshad=\now textshadow opacity cssanimations csscolumns cssgradients cssreflections =\ncsstransforms csstransforms3d csstransitions fontface generatedcontent vide=\no audio localstorage sessionstorage webworkers applicationcache svg inlines=\nvg smil svgclippaths js csstransforms csstransforms3d csstransitions   js f=\nlexbox flexboxlegacy canvas canvastext webgl no-touch geolocation postmessa=\nge websqldatabase indexeddb hashchange history draganddrop websockets rgba =\nhsla multiplebgs backgroundsize borderimage borderradius boxshadow textshad=\now opacity cssanimations csscolumns cssgradients cssreflections csstransfor=\nms csstransforms3d csstransitions fontface generatedcontent video audio loc=\nalstorage sessionstorage webworkers applicationcache svg inlinesvg smil svg=\nclippaths js csstransforms csstransforms3d csstransitions   js flexbox flex=\nboxlegacy canvas canvastext webgl no-touch geolocation postmessage websqlda=\ntabase indexeddb hashchange history draganddrop websockets rgba hsla multip=\nlebgs backgroundsize borderimage borderradius boxshadow textshadow opacity =\ncssanimations csscolumns cssgradients cssreflections csstransforms csstrans=\nforms3d csstransitions fontface generatedcontent video audio localstorage s=\nessionstorage webworkers applicationcache svg inlinesvg smil svgclippaths j=\ns csstransforms csstransforms3d csstransitions   js flexbox flexboxlegacy c=\nanvas canvastext webgl no-touch geolocation postmessage websqldatabase inde=\nxeddb hashchange history draganddrop websockets rgba hsla multiplebgs backg=\nroundsize borderimage borderradius boxshadow textshadow opacity cssanimatio=\nns csscolumns cssgradients cssreflections csstransforms csstransforms3d css=\ntransitions fontface generatedcontent video audio localstorage sessionstora=\nge webworkers applicationcache svg inlinesvg smil svgclippaths js csstransf=\norms csstransforms3d csstransitions   js flexbox flexboxlegacy canvas canva=\nstext webgl no-touch geolocation postmessage websqldatabase indexeddb hashc=\nhange history draganddrop websockets rgba hsla multiplebgs backgroundsize b=\norderimage borderradius boxshadow textshadow opacity cssanimations csscolum=\nns cssgradients cssreflections csstransforms csstransforms3d csstransitions=\n fontface generatedcontent video audio localstorage sessionstorage webworke=\nrs applicationcache svg inlinesvg smil svgclippaths js csstransforms csstra=\nnsforms3d csstransitions   js flexbox flexboxlegacy canvas canvastext webgl=\n no-touch geolocation postmessage websqldatabase indexeddb hashchange histo=\nry draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage =\nborderradius boxshadow textshadow opacity cssanimations csscolumns cssgradi=\nents cssreflections csstransforms csstransforms3d csstransitions fontface g=\neneratedcontent video audio localstorage sessionstorage webworkers applicat=\nioncache svg inlinesvg smil svgclippaths js csstransforms csstransforms3d c=\nsstransitions   js flexbox flexboxlegacy canvas canvastext webgl no-touch g=\neolocation postmessage websqldatabase indexeddb hashchange history dragandd=\nrop websockets rgba hsla multiplebgs backgroundsize borderimage borderradiu=\ns boxshadow textshadow opacity cssanimations csscolumns cssgradients cssref=\nlections csstransforms csstransforms3d csstransitions fontface generatedcon=\ntent video audio localstorage sessionstorage webworkers applicationcache sv=\ng inlinesvg smil svgclippaths js csstransforms csstransforms3d csstransitio=\nns   js flexbox flexboxlegacy canvas canvastext webgl no-touch geolocation =\npostmessage websqldatabase indexeddb hashchange history draganddrop websock=\nets rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow=\n textshadow opacity cssanimations csscolumns cssgradients cssreflections cs=\nstransforms csstransforms3d csstransitions fontface generatedcontent video =\naudio localstorage sessionstorage webworkers applicationcache svg inlinesvg=\n smil svgclippaths js csstransforms csstransforms3d csstransitions   js fle=\nxbox flexboxlegacy canvas canvastext webgl touch geolocation postmessage we=\nbsqldatabase indexeddb hashchange history draganddrop websockets rgba hsla =\nmultiplebgs backgroundsize borderimage borderradius boxshadow textshadow op=\nacity cssanimations csscolumns cssgradients cssreflections csstransforms cs=\nstransforms3d csstransitions fontface generatedcontent video audio localsto=\nrage sessionstorage webworkers applicationcache svg inlinesvg smil svgclipp=\naths js csstransforms csstransforms3d csstransitions   js flexbox flexboxle=\ngacy canvas canvastext webgl no-touch geolocation postmessage websqldatabas=\ne indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs=\n backgroundsize borderimage borderradius boxshadow textshadow opacity cssan=\nimations csscolumns cssgradients cssreflections csstransforms csstransforms=\n3d csstransitions fontface generatedcontent video audio localstorage sessio=\nnstorage webworkers applicationcache svg inlinesvg smil svgclippaths js css=\ntransforms csstransforms3d csstransitions   js flexbox flexboxlegacy canvas=\n canvastext webgl no-touch geolocation postmessage websqldatabase indexeddb=\n hashchange history draganddrop websockets rgba hsla multiplebgs background=\nsize borderimage borderradius boxshadow textshadow opacity cssanimations cs=\nscolumns cssgradients cssreflections csstransforms csstransforms3d csstrans=\nitions fontface generatedcontent video audio localstorage sessionstorage we=\nbworkers applicationcache svg inlinesvg smil svgclippaths js csstransforms =\ncsstransforms3d csstransitions   js flexbox flexboxlegacy canvas canvastext=\n webgl no-touch geolocation postmessage websqldatabase indexeddb hashchange=\n history draganddrop websockets rgba hsla multiplebgs backgroundsize border=\nimage borderradius boxshadow textshadow opacity cssanimations csscolumns cs=\nsgradients cssreflections csstransforms csstransforms3d csstransitions font=\nface generatedcontent video audio localstorage sessionstorage webworkers ap=\nplicationcache svg inlinesvg smil svgclippaths js csstransforms csstransfor=\nms3d csstransitions   js flexbox flexboxlegacy canvas canvastext webgl no-t=\nouch geolocation postmessage websqldatabase indexeddb hashchange history dr=\naganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borde=\nrradius boxshadow textshadow opacity cssanimations csscolumns cssgradients =\ncssreflections csstransforms csstransforms3d csstransitions fontface genera=\ntedcontent video audio localstorage sessionstorage webworkers applicationca=\nche svg inlinesvg smil svgclippaths js csstransforms csstransforms3d csstra=\nnsitions   js flexbox flexboxlegacy canvas canvastext webgl no-touch geoloc=\nation postmessage websqldatabase indexeddb hashchange history draganddrop w=\nebsockets rgba hsla multiplebgs backgroundsize borderimage borderradius box=\nshadow textshadow opacity cssanimations csscolumns cssgradients cssreflecti=\nons csstransforms csstransforms3d csstransitions fontface generatedcontent =\nvideo audio localstorage sessionstorage webworkers applicationcache svg inl=\ninesvg smil svgclippaths js csstransforms csstransforms3d csstransitions   =\njs flexbox flexboxlegacy canvas canvastext webgl no-touch geolocation postm=\nessage websqldatabase indexeddb hashchange history draganddrop websockets r=\ngba hsla multiplebgs backgroundsize borderimage borderradius boxshadow text=\nshadow opacity cssanimations csscolumns cssgradients cssreflections csstran=\nsforms csstransforms3d csstransitions fontface generatedcontent video audio=\n localstorage sessionstorage webworkers applicationcache svg inlinesvg smil=\n svgclippaths js csstransforms csstransforms3d csstransitions   js flexbox =\nflexboxlegacy canvas canvastext webgl no-touch geolocation postmessage webs=\nqldatabase indexeddb hashchange history draganddrop websockets rgba hsla mu=\nltiplebgs backgroundsize borderimage borderradius boxshadow textshadow opac=\nity cssanimations csscolumns cssgradients cssreflections csstransforms csst=\nransforms3d csstransitions fontface generatedcontent video audio localstora=\nge sessionstorage webworkers applicationcache svg inlinesvg smil svgclippat=\nhs js csstransforms csstransforms3d csstransitions   js flexbox flexboxlega=\ncy canvas canvastext webgl touch geolocation postmessage websqldatabase ind=\nexeddb hashchange history draganddrop websockets rgba hsla multiplebgs back=\ngroundsize borderimage borderradius boxshadow textshadow opacity cssanimati=\nons csscolumns cssgradients cssreflections csstransforms csstransforms3d cs=\nstransitions fontface generatedcontent video audio localstorage sessionstor=\nage webworkers applicationcache svg inlinesvg smil svgclippaths js csstrans=\nforms csstransforms3d csstransitions   js flexbox flexboxlegacy canvas canv=\nastext webgl touch geolocation postmessage websqldatabase indexeddb hashcha=\nnge history draganddrop websockets rgba hsla multiplebgs backgroundsize bor=\nderimage borderradius boxshadow textshadow opacity cssanimations csscolumns=\n cssgradients cssreflections csstransforms csstransforms3d csstransitions f=\nontface generatedcontent video audio localstorage sessionstorage webworkers=\n applicationcache svg inlinesvg smil svgclippaths js csstransforms csstrans=\nforms3d csstransitions   js flexbox flexboxlegacy canvas canvastext webgl t=\nouch geolocation postmessage websqldatabase indexeddb hashchange history dr=\naganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borde=\nrradius boxshadow textshadow opacity cssanimations csscolumns cssgradients =\ncssreflections csstransforms csstransforms3d csstransitions fontface genera=\ntedcontent video audio localstorage sessionstorage webworkers applicationca=\nche svg inlinesvg smil svgclippaths js csstransforms csstransforms3d csstra=\nnsitions   js flexbox flexboxlegacy canvas canvastext webgl touch geolocati=\non postmessage websqldatabase indexeddb hashchange history draganddrop webs=\nockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxsha=\ndow textshadow opacity cssanimations csscolumns cssgradients cssreflections=\n csstransforms csstransforms3d csstransitions fontface generatedcontent vid=\neo audio localstorage sessionstorage webworkers applicationcache svg inline=\nsvg smil svgclippaths js csstransforms csstransforms3d csstransitions   js =\nflexbox flexboxlegacy canvas canvastext webgl touch geolocation postmessage=\n websqldatabase indexeddb hashchange history draganddrop websockets rgba hs=\nla multiplebgs backgroundsize borderimage borderradius boxshadow textshadow=\n opacity cssanimations csscolumns cssgradients cssreflections csstransforms=\n csstransforms3d csstransitions fontface generatedcontent video audio local=\nstorage sessionstorage webworkers applicationcache svg inlinesvg smil svgcl=\nippaths js csstransforms csstransforms3d csstransitions   js flexbox flexbo=\nxlegacy canvas canvastext webgl touch geolocation postmessage websqldatabas=\ne indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs=\n backgroundsize borderimage borderradius boxshadow textshadow opacity cssan=\nimations csscolumns cssgradients cssreflections csstransforms csstransforms=\n3d csstransitions fontface generatedcontent video audio localstorage sessio=\nnstorage webworkers applicationcache svg inlinesvg smil svgclippaths js css=\ntransforms csstransforms3d csstransitions   js flexbox flexboxlegacy canvas=\n canvastext webgl touch geolocation postmessage websqldatabase indexeddb ha=\nshchange history draganddrop websockets rgba hsla multiplebgs backgroundsiz=\ne borderimage borderradius boxshadow textshadow opacity cssanimations cssco=\nlumns cssgradients cssreflections csstransforms csstransforms3d csstransiti=\nons fontface generatedcontent video audio localstorage sessionstorage webwo=\nrkers applicationcache svg inlinesvg smil svgclippaths js csstransforms css=\ntransforms3d csstransitions   js flexbox flexboxlegacy canvas canvastext we=\nbgl touch geolocation postmessage websqldatabase indexeddb hashchange histo=\nry draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage =\nborderradius boxshadow textshadow opacity cssanimations csscolumns cssgradi=\nents cssreflections csstransforms csstransforms3d csstransitions fontface g=\neneratedcontent video audio localstorage sessionstorage webworkers applicat=\nioncache svg inlinesvg smil svgclippaths js csstransforms csstransforms3d c=\nsstransitions   js flexbox flexboxlegacy canvas canvastext webgl touch geol=\nocation postmessage websqldatabase indexeddb hashchange history draganddrop=\n websockets rgba hsla multiplebgs backgroundsize borderimage borderradius b=\noxshadow textshadow opacity cssanimations csscolumns cssgradients cssreflec=\ntions csstransforms csstransforms3d csstransitions fontface generatedconten=\nt video audio localstorage sessionstorage webworkers applicationcache svg i=\nnlinesvg smil svgclippaths js csstransforms csstransforms3d csstransitions =\n  js flexbox flexboxlegacy canvas canvastext webgl touch geolocation postme=\nssage websqldatabase indexeddb hashchange history draganddrop websockets rg=\nba hsla multiplebgs backgroundsize borderimage borderradius boxshadow texts=\nhadow opacity cssanimations csscolumns cssgradients cssreflections csstrans=\nforms csstransforms3d csstransitions fontface generatedcontent video audio =\nlocalstorage sessionstorage webworkers applicationcache svg inlinesvg smil =\nsvgclippaths js csstransforms csstransforms3d csstransitions   js flexbox f=\nlexboxlegacy canvas canvastext webgl touch geolocation postmessage websqlda=\ntabase indexeddb hashchange history draganddrop websockets rgba hsla multip=\nlebgs backgroundsize borderimage borderradius boxshadow textshadow opacity =\ncssanimations csscolumns cssgradients cssreflections csstransforms csstrans=\nforms3d csstransitions fontface generatedcontent video audio localstorage s=\nessionstorage webworkers applicationcache svg inlinesvg smil svgclippaths j=\ns csstransforms csstransforms3d csstransitions   js flexbox flexboxlegacy c=\nanvas canvastext webgl touch geolocation postmessage websqldatabase indexed=\ndb hashchange history draganddrop websockets rgba hsla multiplebgs backgrou=\nndsize borderimage borderradius boxshadow textshadow opacity cssanimations =\ncsscolumns cssgradients cssreflections csstransforms csstransforms3d csstra=\nnsitions fontface generatedcontent video audio localstorage sessionstorage =\nwebworkers applicationcache svg inlinesvg smil svgclippaths js csstransform=\ns csstransforms3d csstransitions   js flexbox flexboxlegacy canvas canvaste=\nxt webgl touch geolocation postmessage websqldatabase indexeddb hashchange =\nhistory draganddrop websockets rgba hsla multiplebgs backgroundsize borderi=\nmage borderradius boxshadow textshadow opacity cssanimations csscolumns css=\ngradients cssreflections csstransforms csstransforms3d csstransitions fontf=\nace generatedcontent video audio localstorage sessionstorage webworkers app=\nlicationcache svg inlinesvg smil svgclippaths js csstransforms csstransform=\ns3d csstransitions   js flexbox flexboxlegacy canvas canvastext webgl touch=\n geolocation postmessage websqldatabase indexeddb hashchange history dragan=\nddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderrad=\nius boxshadow textshadow opacity cssanimations csscolumns cssgradients cssr=\neflections csstransforms csstransforms3d csstransitions fontface generatedc=\nontent video audio localstorage sessionstorage webworkers applicationcache =\nsvg inlinesvg smil svgclippaths js csstransforms csstransforms3d csstransit=\nions   js flexbox flexboxlegacy canvas canvastext webgl touch geolocation p=\nostmessage websqldatabase indexeddb hashchange history draganddrop websocke=\nts rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow =\ntextshadow opacity cssanimations csscolumns cssgradients cssreflections css=\ntransforms csstransforms3d csstransitions fontface generatedcontent video a=\nudio localstorage sessionstorage webworkers applicationcache svg inlinesvg =\nsmil svgclippaths js csstransforms csstransforms3d csstransitions   js flex=\nbox flexboxlegacy canvas canvastext webgl touch geolocation postmessage web=\nsqldatabase indexeddb hashchange history draganddrop websockets rgba hsla m=\nultiplebgs backgroundsize borderimage borderradius boxshadow textshadow opa=\ncity cssanimations csscolumns cssgradients cssreflections csstransforms css=\ntransforms3d csstransitions fontface generatedcontent video audio localstor=\nage sessionstorage webworkers applicationcache svg inlinesvg smil svgclippa=\nths js csstransforms csstransforms3d csstransitions   js flexbox flexboxleg=\nacy canvas canvastext webgl touch geolocation postmessage websqldatabase in=\ndexeddb hashchange history draganddrop websockets rgba hsla multiplebgs bac=\nkgroundsize borderimage borderradius boxshadow textshadow opacity cssanimat=\nions csscolumns cssgradients cssreflections csstransforms csstransforms3d c=\nsstransitions fontface generatedcontent video audio localstorage sessionsto=\nrage webworkers applicationcache svg inlinesvg smil svgclippaths js csstran=\nsforms csstransforms3d csstransitions   js flexbox flexboxlegacy canvas can=\nvastext webgl touch geolocation postmessage websqldatabase indexeddb hashch=\nange history draganddrop websockets rgba hsla multiplebgs backgroundsize bo=\nrderimage borderradius boxshadow textshadow opacity cssanimations csscolumn=\ns cssgradients cssreflections csstransforms csstransforms3d csstransitions =\nfontface generatedcontent video audio localstorage sessionstorage webworker=\ns applicationcache svg inlinesvg smil svgclippaths js csstransforms csstran=\nsforms3d csstransitions   js flexbox flexboxlegacy canvas canvastext webgl =\ntouch geolocation postmessage websqldatabase indexeddb hashchange history d=\nraganddrop websockets rgba hsla multiplebgs backgroundsize borderimage bord=\nerradius boxshadow textshadow opacity cssanimations csscolumns cssgradients=\n cssreflections csstransforms csstransforms3d csstransitions fontface gener=\natedcontent video audio localstorage sessionstorage webworkers applicationc=\nache svg inlinesvg smil svgclippaths js csstransforms csstransforms3d csstr=\nansitions   js flexbox flexboxlegacy canvas canvastext webgl touch geolocat=\nion postmessage websqldatabase indexeddb hashchange history draganddrop web=\nsockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxsh=\nadow textshadow opacity cssanimations csscolumns cssgradients cssreflection=\ns csstransforms csstransforms3d csstransitions fontface generatedcontent vi=\ndeo audio localstorage sessionstorage webworkers applicationcache svg inlin=\nesvg smil svgclippaths js csstransforms csstransforms3d csstransitions   js=\n flexbox flexboxlegacy canvas canvastext webgl touch geolocation postmessag=\ne websqldatabase indexeddb hashchange history draganddrop websockets rgba h=\nsla multiplebgs backgroundsize borderimage borderradius boxshadow textshado=\nw opacity cssanimations csscolumns cssgradients cssreflections csstransform=\ns csstransforms3d csstransitions fontface generatedcontent video audio loca=\nlstorage sessionstorage webworkers applicationcache svg inlinesvg smil svgc=\nlippaths js csstransforms csstransforms3d csstransitions   js flexbox flexb=\noxlegacy canvas canvastext webgl touch geolocation postmessage websqldataba=\nse indexeddb hashchange history draganddrop websockets rgba hsla multiplebg=\ns backgroundsize borderimage borderradius boxshadow textshadow opacity cssa=\nnimations csscolumns cssgradients cssreflections csstransforms csstransform=\ns3d csstransitions fontface generatedcontent video audio localstorage sessi=\nonstorage webworkers applicationcache svg inlinesvg smil svgclippaths js cs=\nstransforms csstransforms3d csstransitions   js flexbox flexboxlegacy canva=\ns canvastext webgl no-touch geolocation postmessage websqldatabase indexedd=\nb hashchange history draganddrop websockets rgba hsla multiplebgs backgroun=\ndsize borderimage borderradius boxshadow textshadow opacity cssanimations c=\nsscolumns cssgradients cssreflections csstransforms csstransforms3d csstran=\nsitions fontface generatedcontent video audio localstorage sessionstorage w=\nebworkers applicationcache svg inlinesvg smil svgclippaths js csstransforms=\n csstransforms3d csstransitions   js flexbox flexboxlegacy canvas canvastex=\nt webgl no-touch geolocation postmessage websqldatabase indexeddb hashchang=\ne history draganddrop websockets rgba hsla multiplebgs backgroundsize borde=\nrimage borderradius boxshadow textshadow opacity cssanimations csscolumns c=\nssgradients cssreflections csstransforms csstransforms3d csstransitions fon=\ntface generatedcontent video audio localstorage sessionstorage webworkers a=\npplicationcache svg inlinesvg smil svgclippaths js csstransforms csstransfo=\nrms3d csstransitions responsejs \" style=3D\"\"><head>\n    <meta http-equiv=\n=3D\"Content-Type\" content=3D\"text/html; charset=3DUTF-8\" />\n    <meta nam=\ne=3D\"viewport\" content=3D\"width=3Ddevice-width, initial-scale=3D1\" />\n   =\n <title>Caliopen Alpha!</title>\n    <!-- Designed by https://github.com/k=\naytcat -->\n    <!-- Robot header image designed by Freepik.com -->\n\n =\n   <style type=3D\"text/css\">\n    @import url(https://fonts.googleapis.com=\n/css?family=3DDroid+Sans);\n\n    /* Take care of image borders and forma=\ntting */\n\n    img {\n        max-width: 600px;\n        outline: none=\n;\n        text-decoration: none;\n        -ms-interpolation-mode: bicubi=\nc;\n    }\n\n    a {\n        text-decoration: none;\n        border: =\n0;\n        outline: none;\n        color: #bbbbbb;\n    }\n\n    a im=\ng {\n        border: none;\n    }\n\n    /* General styling */\n\n   =\n td, h1, h2, h3  {\n        font-family: Helvetica, Arial, sans-serif;\n =\n       font-weight: 400;\n    }\n\n    td {\n        text-align: center=\n;\n    }\n\n    body {\n        -webkit-font-smoothing:antialiased;\n =\n       -webkit-text-size-adjust:none;\n        width: 100%;\n        heig=\nht: 100%;\n        color: #37302d;\n        background: #ffffff;\n      =\n  font-size: 16px;\n    }\n\n     table {\n        border-collapse: col=\nlapse !important;\n    }\n\n    .headline {\n        color: #ffffff;\n=\n        font-size: 36px;\n    }\n\n    .force-width-80 {\n        width=\n: 80% !important;\n    }\n\n    </style>\n\n    <style type=3D\"text/cs=\ns\" media=3D\"screen\">\n            @media screen {\n                 /*Tha=\nnks Outlook 2013! http://goo.gl/XLxpyl*/\n                td, h1, h2, h3 {=\n\n                    font-family: 'Droid Sans', 'Helvetica Neue', 'Arial'=\n, 'sans-serif' !important;\n                }\n            }\n    </styl=\ne>\n\n    <style type=3D\"text/css\" media=3D\"only screen and (max-width: 4=\n80px)\">\n        /* Mobile styles */\n        @media only screen and (max=\n-width: 480px) {\n\n            [class=3D\"w320\"] {\n                widt=\nh: 320px !important;\n            }\n\n            [class=3D\"mobile-bloc=\nk\"] {\n                width: 100% !important;\n                display: =\nblock !important;\n            }\n        }\n    </style>\n\n         =\n                            =20\n                                     =20\n  =\n                                   =20\n                                    =\n =20\n                                     =20\n                             =\n        =20\n                                     =20\n                      =\n               =20\n                                     =20\n               =\n                      =20\n                                     =20\n        =\n                             =20\n                                     =20\n =\n                                    =20\n                                   =\n  =20\n                                     =20\n                            =\n         =20\n                                     =20\n                     =\n                =20\n                                     =20\n              =\n                       =20\n                                     =20\n       =\n                              =20\n                                     =20\n=\n                                     =20\n                                  =\n   =20\n                                     =20\n                           =\n          =20\n                                     =20\n                    =\n                 =20\n                                     =20\n             =\n                        =20\n                                     =20\n      =\n                               =20\n                                      =\n\n                                     =20\n                               =\n      =20\n                                     =20\n                        =\n             =20\n                                     =20\n                 =\n                    =20\n                                      </head>\n<bo=\ndy class=3D\"body ui-sortable\" style=3D\"padding: 0px; margin: 0px; display: =\nblock; background: rgb(255, 255, 255); text-size-adjust: none; -webkit-font=\n-smoothing: antialiased; width: 100%; height: 100%; color: rgb(55, 48, 45);=\n font-size: 16px; overflow: visible; cursor: auto;\" bgcolor=3D\"#ffffff\">\n=\n<div data-section-wrapper=3D\"1\">\n    <center>\n        <table data-secti=\non=3D\"1\" style=3D\"margin: 0 auto;border-collapse: collapse !important;width=\n: 600px;\" cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\" class=3D\"w320\">=\n\n            <tbody><tr>\n                <td style=3D\"font-size: 30px;t=\next-align: center;font-family: 'Droid Sans', 'Helvetica Neue', 'Arial', 'sa=\nns-serif' !important;font-weight: 400;\" data-slot-container=3D\"1\" class=3D\"=\nui-sortable\">\n                   =20\n                </td>\n            =\n</tr>\n        </tbody></table>\n    </center>\n</div>\n\n<div data-se=\nction-wrapper=3D\"one-column\">\n                                    <div da=\nta-section-wrapper=3D\"1\">\n    <center>\n        <table data-section=3D\"1=\n\" style=3D\"margin: 0px auto; width: 600px; border-collapse: collapse !impor=\ntant; background-color: rgb(0, 0, 0);\" class=3D\"w320\" cellpadding=3D\"0\" cel=\nlspacing=3D\"0\" width=3D\"600\" bgcolor=3D\"#000000\">\n            <tbody><tr>=\n\n                <td data-slot-container=3D\"1\" valign=3D\"top\" class=3D\"mo=\nbile-block ui-sortable\" style=3D\"padding-left: 5px; padding-right: 5px;\">=\n\n                   =20\n                </td>\n            </tr>\n     =\n   </tbody></table>\n    </center>\n</div>\n                            =\n    </div><div data-section-wrapper=3D\"one-column\">\n                     =\n               <div data-section-wrapper=3D\"1\">\n    <center>\n        <t=\nable data-section=3D\"1\" style=3D\"margin: 0px auto; width: 600px; border-col=\nlapse: collapse !important; background-color: rgb(0, 0, 0);\" class=3D\"w320\"=\n cellpadding=3D\"0\" cellspacing=3D\"0\" width=3D\"600\" bgcolor=3D\"#000000\">\n =\n           <tbody><tr>\n                <td data-slot-container=3D\"1\" vali=\ngn=3D\"top\" class=3D\"mobile-block ui-sortable\" style=3D\"padding-left: 5px; p=\nadding-right: 5px;\">\n                    <div data-slot=3D\"text\" data-par=\nam-padding-top=3D\"20\" style=3D\"padding-top: 20px; padding-bottom: 20px;\" da=\nta-param-padding-bottom=3D\"20\"><img src=3D\"https://welcome.caliopen.org/med=\nia/images/f08054cf5862a0986cd222f399ec85f3.png\" class=3D\"fr-fic fr-dib\" sty=\nle=3D\"width: 115px; height: 22.3521px;\" /></div>\n                </td>\n=\n            </tr>\n        </tbody></table>\n    </center>\n</div>\n   =\n                             </div><div data-section-wrapper=3D\"1\">\n    <=\ncenter>\n        <table data-section=3D\"1\" style=3D\"margin: 0px auto; widt=\nh: 600px; border-collapse: collapse !important; background-color: rgb(19, 1=\n49, 186);\" cellpadding=3D\"0\" cellspacing=3D\"0\" bgcolor=3D\"#1395ba\" width=3D=\n\"600\" class=3D\"w320\">\n            <tbody><tr>\n                <td style=\n=3D\"font-family: 'Droid Sans', 'Helvetica Neue', 'Arial', 'sans-serif' !imp=\nortant;font-weight: 400;text-align: center;\" data-slot-container=3D\"1\" clas=\ns=3D\"ui-sortable\">\n                    <br />\n                    <div =\ndata-slot=3D\"image\" data-param-padding-top=3D\"20\" style=3D\"padding-top: 20p=\nx;\">\n                                   =20\n<img src=3D\"https://welcome.c=\naliopen.org/media/images/8cc3fc6ab8a20b0946e56b4dbed42f8d.png\" alt=3D\"An im=\nage\" class=3D\"fr-view\" style=3D\"\" /><br />\n<div style=3D\"clear:both\"></di=\nv>\n                                </div><div data-slot=3D\"text\" class=3D=\n\"\" style=3D\"position: relative; left: 0px; top: 0px; padding-top: 20px;\" da=\nta-param-padding-top=3D\"20\"><div style=3D\"text-align: center;\"><span style=\n=3D\"font-size: 48px;\"><span style=3D\"color: rgb(255, 255, 255);\"><strong>It=\n=E2=80=99s open!</strong></span></span></div></div>\n                </td>=\n\n            </tr>\n            <tr>\n                <td class=3D\"head=\nline ui-sortable\" style=3D\"font-family: 'Droid Sans', 'Helvetica Neue', 'Ar=\nial', 'sans-serif' !important;font-weight: 400;text-align: center;color: #f=\nfffff;font-size: 36px;\" data-slot-container=3D\"1\">\n                    =\n\n                </td>\n            </tr>\n            <tr>\n         =\n       <td style=3D\"font-family: 'Droid Sans', 'Helvetica Neue', 'Arial', '=\nsans-serif' !important;font-weight: 400;text-align: center;\">\n\n        =\n            <center>\n                        <table style=3D\"margin: 0 au=\nto;border-collapse: collapse !important;\" cellpadding=3D\"0\" cellspacing=3D\"=\n0\" width=3D\"60%\">\n                            <tbody><tr>\n             =\n                   <td style=3D\"color: #187272;font-family: 'Droid Sans', '=\nHelvetica Neue', 'Arial', 'sans-serif' !important;font-weight: 400;text-ali=\ngn: center;\" data-slot-container=3D\"1\" class=3D\"ui-sortable\">\n           =\n                        =20\n                                </td>\n       =\n                     </tr>\n                        </tbody></table>\n   =\n                 </center>\n\n                </td>\n            </tr>=\n\n            <tr>\n                <td style=3D\"font-family: 'Droid Sans=\n', 'Helvetica Neue', 'Arial', 'sans-serif' !important;font-weight: 400;text=\n-align: center;\" data-slot-container=3D\"1\" class=3D\"ui-sortable\">\n       =\n            =20\n                    <br />\n                    <br />\n =\n               </td>\n            </tr>\n        </tbody></table>\n    <=\n/center>\n</div>\n<div data-section-wrapper=3D\"1\">\n    <center>\n     =\n   <table data-section=3D\"1\" style=3D\"margin: 0px auto; width: 600px; backg=\nround-color: rgb(27, 27, 27); border-collapse: collapse !important;\" cellpa=\ndding=3D\"0\" cellspacing=3D\"0\" bgcolor=3D\"#1b1b1b\" width=3D\"600\" class=3D\"w3=\n20\">\n            <tbody><tr>\n                <td style=3D\"font-family: =\n'Droid Sans', 'Helvetica Neue', 'Arial', 'sans-serif' !important;font-weigh=\nt: 400;text-align: center;\">\n\n                    <center>\n          =\n              <table style=3D\"margin: 0 auto;border-collapse: collapse !imp=\nortant;width: 80% !important;\" cellspacing=3D\"0\" cellpadding=3D\"0\" class=3D=\n\"force-width-80\">\n                            <tbody><tr>\n             =\n                   <td style=3D\"text-align: left;color: #933f24;font-family=\n: 'Droid Sans', 'Helvetica Neue', 'Arial', 'sans-serif' !important;font-wei=\nght: 400;\" data-slot-container=3D\"1\" class=3D\"ui-sortable\">\n             =\n                      =20\n                                </td>\n         =\n                       <td style=3D\"text-align: right;vertical-align: top;c=\nolor: #933f24;font-family: 'Droid Sans', 'Helvetica Neue', 'Arial', 'sans-s=\nerif' !important;font-weight: 400;\" data-slot-container=3D\"1\" class=3D\"ui-s=\nortable\">\n                                   =20\n                        =\n        </td>\n                            </tr>\n                       =\n </tbody></table>\n\n                        <table style=3D\"margin: 0 au=\nto;border-collapse: collapse !important;width: 80% !important;\" cellspacing=\n=3D\"0\" cellpadding=3D\"0\" class=3D\"force-width-80\">\n                      =\n      <tbody><tr>\n                                <td class=3D\"mobile-blo=\nck ui-sortable\" style=3D\"font-family: 'Droid Sans', 'Helvetica Neue', 'Aria=\nl', 'sans-serif' !important;font-weight: 400;text-align: center;\" data-slot=\n-container=3D\"1\">\n                                   =20\n                =\n                </td>\n                            </tr>\n               =\n         </tbody></table>\n\n                        <table style=3D\"marg=\nin: 0 auto;border-collapse: collapse !important;width: 80% !important;\" cel=\nlspacing=3D\"0\" cellpadding=3D\"0\" class=3D\"force-width-80\">\n              =\n              <tbody><tr>\n                                <td style=3D\"te=\nxt-align: left;color: #933f24;font-family: 'Droid Sans', 'Helvetica Neue', =\n'Arial', 'sans-serif' !important;font-weight: 400;\" data-slot-container=3D\"=\n1\" class=3D\"ui-sortable\">\n                                    <div data-s=\nlot=3D\"text\" class=3D\"\" style=3D\"position: relative; left: 0px; top: 0px; p=\nadding-top: 50px; padding-bottom: 20px;\" data-param-padding-top=3D\"50\" data=\n-param-padding-bottom=3D\"20\"><div><span style=3D\"color: rgb(255, 255, 255);=\n\">Bonjour , vous pouvez d=C3=A9sormais cr=C3=A9er votre compte sur :</span>=\n</div><div><span style=3D\"color: rgb(84, 172, 210);\"><a href=3D\"https://wel=\ncome.caliopen.org/r/5a3fab4cc13f54516a26520c3?ct=3DYTo1OntzOjY6InNvdXJjZSI7=\nYToyOntpOjA7czoxNDoiY2FtcGFpZ24uZXZlbnQiO2k6MTtpOjEwNzt9czo1OiJlbWFpbCI7aTo=\n3O3M6NDoic3RhdCI7czoxMzoiNWEwYWNkZGU3NTBlZiI7czo0OiJsZWFkIjtpOjY2NTA7czo3Oi=\nJjaGFubmVsIjthOjE6e3M6NToiZW1haWwiO2k6Nzt9fQ%3D%3D&\">https://alpha.caliopen=\n.org/auth/signup</a></span></div><div data-empty=3D\"true\"><br /></div><span=\n style=3D\"color: rgb(255, 255, 255);\">Le nom d'utilisateur choisi sera util=\nis=C3=A9 pour cr=C3=A9er votre adresse mail sous la forme=C2=A0</span><span=\n style=3D\"color: rgb(251, 160, 38);\"><strong>utilisateur</strong></span><sp=\nan style=3D\"color: rgb(255, 255, 255);\">@alpha.caliopen.org.<br /><br />Pou=\nr valider votre inscription, il vous faudra =C3=A9galement renseigner votre=\n email=C2=A0</span><span style=3D\"color: rgb(251, 160, 38);\"><strong>xxxx@c=\naliopen.org</strong></span><span style=3D\"color: rgb(255, 255, 255);\">=\n=C2=A0dans le champ \"email d'invitation\".</span><div><br /></div><hr /><div=\n data-empty=3D\"true\"><br /></div><div><span style=3D\"color: rgb(255, 255, 2=\n55);\">Hello <span data-fr-verified=3D\"true\">, you can now create your accou=\nnt on:</span>=C2=A0</span></div><div><span style=3D\"color: rgb(255, 255, 25=\n5);\"><a href=3D\"https://welcome.caliopen.org/r/5a3fab4cc13f54516a26520c3?ct=\n=3DYTo1OntzOjY6InNvdXJjZSI7YToyOntpOjA7czoxNDoiY2FtcGFpZ24uZXZlbnQiO2k6MTtp=\nOjEwNzt9czo1OiJlbWFpbCI7aTo3O3M6NDoic3RhdCI7czoxMzoiNWEwYWNkZGU3NTBlZiI7czo=\n0OiJsZWFkIjtpOjY2NTA7czo3OiJjaGFubmVsIjthOjE6e3M6NToiZW1haWwiO2k6Nzt9fQ%3D%=\n3D&\">https://alpha.caliopen.org/auth/signup</a></span></div><div data-empty=\n=3D\"true\"><br /></div><div><span style=3D\"color: rgb(255, 255, 255);\">The c=\nhosen username will be used to create your email address in the form:=C2=\n=A0</span><span style=3D\"color: rgb(251, 160, 38);\"><strong>username</stron=\ng></span><span style=3D\"color: rgb(255, 255, 255);\">@alpha.caliopen.org</sp=\nan></div><span style=3D\"color: rgb(255, 255, 255);\"><br />To validate your =\nregistration, you will also have to fill in your email=C2=A0</span><span st=\nyle=3D\"color: rgb(251, 160, 38);\"><strong>xxxx@caliopen.org=C2=A0</strong><=\n/span><span style=3D\"color: rgb(255, 255, 255);\">in the \"invitation email\"f=\nield.</span></div><div data-slot=3D\"text\" data-param-padding-top=3D\"30\" sty=\nle=3D\"padding-top: 30px; padding-bottom: 30px;\" data-param-padding-bottom=\n=3D\"30\"><div style=3D\"text-align: center;\"><em><span style=3D\"color: rgb(25=\n5, 255, 255);\">Have fun! Be good!</span></em></div></div>\n               =\n                 </td>\n                            </tr>\n              =\n          </tbody></table>\n                    </center>\n              =\n  </td>\n            </tr>\n        </tbody></table>\n    </center>\n</=\ndiv>\n\n<div data-section-wrapper=3D\"1\">\n    <center>\n        <table =\ndata-section=3D\"1\" style=3D\"margin: 0px auto; width: 600px; background-colo=\nr: rgb(0, 0, 0); border-collapse: collapse !important;\" cellpadding=3D\"0\" c=\nellspacing=3D\"0\" bgcolor=3D\"#000000\" width=3D\"600\" class=3D\"w320\">\n      =\n      <tbody><tr>\n                <td style=3D\"font-family: 'Droid Sans',=\n 'Helvetica Neue', 'Arial', 'sans-serif' !important;font-weight: 400;text-a=\nlign: center;\" data-slot-container=3D\"1\" class=3D\"ui-sortable\">\n         =\n          =20\n                </td>\n            </tr>\n            <tr>=\n\n                <td style=3D\"color: #bbbbbb;font-size: 12px;font-family:=\n 'Droid Sans', 'Helvetica Neue', 'Arial', 'sans-serif' !important;font-weig=\nht: 400;text-align: center;\" data-slot-container=3D\"1\" class=3D\"ui-sortable=\n\">\n                   =20\n                </td>\n            </tr>\n   =\n         <tr>\n                <td style=3D\"color: #bbbbbb;font-size: 12px=\n;font-family: 'Droid Sans', 'Helvetica Neue', 'Arial', 'sans-serif' !import=\nant;font-weight: 400;text-align: center;\" data-slot-container=3D\"1\" class=\n=3D\"ui-sortable\">\n                    <div data-slot=3D\"text\" data-param-=\npadding-top=3D\"20\" style=3D\"padding-top: 20px; padding-bottom: 20px;\" data-=\nparam-padding-bottom=3D\"20\"><img src=3D\"https://welcome.caliopen.org/media/=\nimages/b556d64fcf1e9fbdb507f5371a691d75.png\" class=3D\"fr-fic fr-dib fr-fil\"=\n style=3D\"width: 102px; height: 19.7419px;\" /><br /></div>\n              =\n  </td>\n            </tr>\n        </tbody></table>\n    </center>\n</=\ndiv>\n\n\n\n<img height=3D\"1\" width=3D\"1\" src=3D\"https://welcome.caliop=\nen.org/email/5a0acdde750ef.gif\" alt=3D\"\" /></body></html>\n\n--_=_swift_v4_1510657502_188ea61ef5dcf264f5cb7dfc84030c2b_=_--"
  },
  {
    "path": "devtools/fixtures/raw_emails/multipart-signed",
    "content": "Received: from list by lists.gnu.org with archive (Exim 4.71)\n id 1apM5u-0008Hs-MF\n for mharc-dev@caliopen.local; Sun, 10 Apr 2016 16:36:30 -0400\nReceived: from eggs.gnu.org ([2001:4830:134:3::10]:60373)\n by lists.gnu.org with esmtp (Exim 4.71)\n (envelope-from <clement.pit@gmail.com>) id 1apM5s-0008Fp-T4\n for dev@caliopen.local; Sun, 10 Apr 2016 16:36:29 -0400\nReceived: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n (envelope-from <clement.pit@gmail.com>) id 1apM5m-00076J-FH\n for dev@caliopen.local; Sun, 10 Apr 2016 16:36:28 -0400\nReceived: from mout.kundenserver.de ([212.227.126.131]:57273)\n by eggs.gnu.org with esmtp (Exim 4.71)\n (envelope-from <clement.pit@gmail.com>)\n id 1apM5m-00076F-5E; Sun, 10 Apr 2016 16:36:22 -0400\nReceived: from [18.189.83.142] ([18.189.83.142]) by mrelayeu.kundenserver.de\n (mreue003) with ESMTPSA (Nemesis) id 0M3O8y-1bg5Eh2VHs-00r1mX;\n Sun, 10 Apr 2016 22:36:19 +0200\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nTo: rms@gnu.org, Vibhav Pant <vibhavp@gmail.com>\nReferences: <56F42269.10400@gmail.com> <m2io0bihz2.fsf@Mandala.local>\n <CA+T2Sh24WOc9MifckO5v8zRa_xrVDnmcbH0mnmtnknyMQMb0jQ@mail.gmail.com>\n <E1apKbc-0008TH-Qs@fencepost.gnu.org>\nCc: dev@caliopen.local\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nMessage-ID: <570AB943.4060604@gmail.com>\nDate: Sun, 10 Apr 2016 16:36:19 -0400\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101\n Thunderbird/38.6.0\nMIME-Version: 1.0\nIn-Reply-To: <E1apKbc-0008TH-Qs@fencepost.gnu.org>\nContent-Type: multipart/signed; micalg=pgp-sha1;\n protocol=\"application/pgp-signature\";\n boundary=\"8PeWqDjRTCtq12tr3EhmcNHUMd0cqJ9h5\"\nX-Provags-ID: V03:K0:NrSzFinbXKAiI//Mno+itGefpcbYW+N+hfeGy1cGLlY0Jn68gCI\n NxqnnpQ2UpPIU1FYkc9bVO8PLSyOlM5Wl1Mtb04BQRKCvGLa5P0NqhupsT0uS78U5i2OOXc\n OsycqhU1WfqMiXOLH9qXY2wEQelTNx+j5QZ512XRFVj6Nr36f0wEfmSsSBP7w6BnscakHxu\n IKAku5edoL8iLvz0cIBPw==\nX-UI-Out-Filterresults: notjunk:1;V01:K0:NsQoOLhz7bU=:E5SGq1ura4ctxlOViCqo5G\n C4dWrbjVH2gC3DwFXRKBcuH057BMtyR5d8T2ZlDiiK++ioolxH2jObjegnIjXOt5b7R7zLLFQ\n 3VbFmTZUnCp9fbTU7bbVhvt+o1/NXW/qVhk2i9tVcoDNVtDp0hoA+mvhL18jbz7VIJc7VhGS4\n 1tjS4C/EelfWOHalx7VHth8uY6hyUsYSblqO0YXAYgo6UOu+0JIxmUz72bUh9sjLd/m4DoBPI\n W9coqTZK99Iu4wixZU/pPW0N8A74Q2bjR3lKR4356B9kjlcEO2v7EIGaofAX6tXJWij0gqoXW\n eE+B4X+NBe9CaDs5Uk3NfgT7u4GWo83XlI6T7xw+SbFtam5Li0/5FMSaYKuor6a1tW9QBCGsl\n yWCo6jdto5UuOk4bbwQ8E1/S0MbRlowwcdQ+ZiK4CIA0x+WWh+AqaxMU/hypQtaFrGdZ4C/AE\n EuEWQ0wDYBC7aUWGGztPuLwHn5dWlZIS+xrJ/LL2c25kYiFf77RPbIsDXAP/+Pw79O+T2nbn1\n GsoKjx3ZFk/BWYfn4I61v5oTh87dKuf5NDO87HpEKKcoF25ztH2CAOERO7VkFXSepmiMU60Se\n 5EgH2NEjgXCo8X4gr5HQD647aauz14TdMzRe5rCS644EshOHJ2UXOraVmsoZpGYrwdFcKIOSr\n eNa7buwedlQIM5LVnQ8dpfyM4WBY8uTwHM5K0Ii3+sRhLyLThLJx2QM1ySLEG8PmEJiA=\nX-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\nX-Received-From: 212.227.126.131\nX-BeenThere: emacs-devel@gnu.org\nX-Mailman-Version: 2.1.21\nPrecedence: list\nList-Id: \"Emacs development discussions.\" <emacs-devel.gnu.org>\nList-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-devel>,\n <mailto:emacs-devel-request@gnu.org?subject=unsubscribe>\nList-Archive: <http://lists.gnu.org/archive/html/emacs-devel/>\nList-Post: <mailto:emacs-devel@gnu.org>\nList-Help: <mailto:emacs-devel-request@gnu.org?subject=help>\nList-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-devel>,\n <mailto:emacs-devel-request@gnu.org?subject=subscribe>\nX-List-Received-Date: Sun, 10 Apr 2016 20:36:29 -0000\nContent-Length: 2407\nLines: 59\n\nThis is an OpenPGP/MIME signed message (RFC 4880 and 3156)\n--8PeWqDjRTCtq12tr3EhmcNHUMd0cqJ9h5\nContent-Type: multipart/mixed; boundary=\"TX1kavG08I806CH9J44LITAdkOiGcc4bC\"\nFrom: =?UTF-8?Q?Cl=c3=a9ment_Pit--Claudel?= <clement.pit@gmail.com>\nTo: rms@gnu.org, Vibhav Pant <vibhavp@gmail.com>\nCc: dev@caliopen.local\nMessage-ID: <570AB943.4060604@gmail.com>\nSubject: Re: Should the default value of url-user-agent include a version\n number? What about the specific case of package.el?\nReferences: <56F42269.10400@gmail.com> <m2io0bihz2.fsf@Mandala.local>\n <CA+T2Sh24WOc9MifckO5v8zRa_xrVDnmcbH0mnmtnknyMQMb0jQ@mail.gmail.com>\n <E1apKbc-0008TH-Qs@fencepost.gnu.org>\nIn-Reply-To: <E1apKbc-0008TH-Qs@fencepost.gnu.org>\n\n--TX1kavG08I806CH9J44LITAdkOiGcc4bC\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nOn 04/10/2016 03:01 PM, Richard Stallman wrote:\n> IRC and web browsing are different activities and raise different\n> issues regarding surveillance.  There is no reason to insist a priori\n> on treating them alike.\n\nSimilarly, package retrieval and web browsing are different activities an=\nd raise different issues; hence my question about the specific case of pa=\nckage.el.\n\nRichard, are you familiar with the url-privacy-level variable?\n\nCl=C3=A9ment.\n\n\n--TX1kavG08I806CH9J44LITAdkOiGcc4bC--\n\n--8PeWqDjRTCtq12tr3EhmcNHUMd0cqJ9h5\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: OpenPGP digital signature\nContent-Disposition: attachment; filename=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.22 (GNU/Linux)\n\niQIcBAEBAgAGBQJXCrlDAAoJEPqg+cTm90wjSj4P/imrz2oMpo/3ecdpYYssTVVW\nhp/26LOB87lt9UjGNHnXOMx1cG2OcdmJ5gD4ocdf9G3Vg+SbW7NWPyjbUY1O3DQ1\n89FXGSO/6RVEyJXVof5BHaBlGMCEh9auCJAVepdhAvlW2gxRXIjxlxujDsT6z2wl\n6wRW3tDmjwMZXOPXvI0Kt0T96dwe/xBAHNp9BHdY+bJXpww/Va0IT8fyryXi8rMD\nMZ05XznRAiaYxZgwoRbXpbswGU3E2+E5NHkJqkSGAiVR6ZaJuFEBdK/X9UFqj7Ud\n1BURAWNRPvh+FPMcFFYOFdMSFh/oA/GB4+hwZdJRf7weVd+9yZlJQI9/nmdRSMBo\nImptcKrt1fOprPuKM65K81z+KBhZn0uEVGXnkNOvHTdl4VnTsW8/MiLWh9yZap0G\nf/zscR8O+RIcYi6L60IS/FwJmG4F4WAHWB97VMept8Y/vtw22hfmj52yZAyefCEm\nRUFutaEiHdClTMGuurwogbVw6pOylqDCYv/5BFih/z8hMkgsr7ULKbv45P3QsF9R\nzwKwunBfWxOHdzcFnUUGaO1NrhaDEIyeWPGrKF29FAdmkx4PqBQnw6Rh2D3bFEle\nImF5zNVy1qYr3M0IkAzh18FsoJz3tFgwE5iu6gyACt6AePVBbwLbBt/AUOrHGXVu\nkxBbQRaOPL/u2La42m8z\n=6div\n-----END PGP SIGNATURE-----\n\n--8PeWqDjRTCtq12tr3EhmcNHUMd0cqJ9h5--\n\n"
  },
  {
    "path": "devtools/fixtures/raw_emails/multipart-with-attachment",
    "content": "Return-Path: <xxxx@caliopen.org>\nDelivered-To: xxxx@caliopen.org\nReceived: from post2.caliopen.net ([178.32.78.240])\n\tby box.caliopen.net (Dovecot) with LMTP id rSalGIRJ11p80gAAz9GZsw\n\tfor <xxxx@caliopen.org>; Wed, 18 Apr 2018 15:44:50 +0200\nReceived: from post2.caliopen.net (post2.caliopen.net [178.32.78.240])\n\tby post2.caliopen.net (Postfix) with ESMTP id 8A97F522F9\n\tfor <xxxx@caliopen.org>; Wed, 18 Apr 2018 15:44:50 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at caliopen.net\nX-Spam-Flag: NO\nX-Spam-Score: 0.167\nX-Spam-Level:\nX-Spam-Status: No, score=0.167 tagged_above=-999 required=4.2\n\ttests=[DC_IMAGE_SPAM_HTML=0.141, DC_IMAGE_SPAM_TEXT=0.123,\n\tDC_PNG_UNO_LARGO=0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1,\n\tDKIM_VALID_AU=-0.1, HTML_MESSAGE=0.001, UNPARSEABLE_RELAY=0.001]\n\tautolearn=no autolearn_force=no\nAuthentication-Results: post2.caliopen.net (amavisd-new);\n\tdkim=pass (1024-bit key) header.d=caliopen.net\nReceived: from --IP masked by caliopen--\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby post2.caliopen.net (Postfix) with ESMTPSA id 1E8DA522DE\n\tfor <xxxx@caliopen.org>; Wed, 18 Apr 2018 15:44:22 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=caliopen.net;\n\ts=20140504caliopen; t=1524059062;\n\tbh=Pr6o4fjkG8yb78vomQlsBZntK4NKI7PQCoF2mhOwSwQ=;\n\th=From:To:Subject:Date;\n\tb=t7HYaezXMOOT4RM3DylugGDZAz74xZqN6WVwQVjGGn7qitB+vsFDdPwFwIIoOdbhx\n\t CAS/JK+OycH9SSfVOvJtL7QE8tzjgGzk25ohR9gj8RzOMRng6yJnMP++RIHXgjgY+N\n\t 5a/X+DnWLiTZsVx0hTXBDNKhCs2gSmGQd25OGseA=\nFrom: \"Neil DIAMOND\" <xxxx@caliopen.org>\nTo: \"Neil DIAMOND\" <xxxx@caliopen.org>\nSubject: Clavier QWERTY\nDate: Wed, 18 Apr 2018 15:44:18 +0200\nMessage-ID: <ED101AE2-D909-4A74-A813-4130F61D5D85@caliopen.net>\nMIME-Version: 1.0\nContent-Type: multipart/mixed;\n boundary=\"=_MailMate_6EB92AF9-BD63-4142-8442-09708FC5F53F_=\"\nContent-Transfer-Encoding: 8bit\n\n\n--=_MailMate_6EB92AF9-BD63-4142-8442-09708FC5F53F_=\nContent-Type: multipart/alternative;\n boundary=\"=_MailMate_C117D28D-6B54-4994-A40E-785FF7679A69_=\"\nContent-Transfer-Encoding: 8bit\n\n\n--=_MailMate_C117D28D-6B54-4994-A40E-785FF7679A69_=\nContent-Type: text/plain; charset=utf-8; markup=markdown\nContent-Transfer-Encoding: 8bit\n\nHello Ludwig,\ntu trouveras ci-joint la composition d'un clavier QWERTY.\n\nBonne réception !\n\n**Hector Berlioz**\n\n-- \n\n\n--=_MailMate_C117D28D-6B54-4994-A40E-785FF7679A69_=\nContent-Type: text/html; charset=utf-8\nContent-Transfer-Encoding: 8bit\n\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/xhtml; charset=utf-8\">\n</head>\n<body>\n<div style=\"font-family:sans-serif\"><div style=\"white-space:normal\">\n<p dir=\"auto\">Hello Ludwig,<br>\ntu trouveras ci-joint la composition d'un clavier QWERTY.</p>\n\n<p dir=\"auto\">Bonne réception !</p>\n\n<p dir=\"auto\"><strong>Hector Berlioz</strong></p>\n\n<p dir=\"auto\">-- </p>\n</div>\n</div>\n</body>\n</html>\n\n--=_MailMate_C117D28D-6B54-4994-A40E-785FF7679A69_=--\n\n--=_MailMate_6EB92AF9-BD63-4142-8442-09708FC5F53F_=\nContent-Disposition: attachment; filename=QWERTY_keyboard.png\nContent-Type: image/png\nContent-Transfer-Encoding: base64\n\niVBORw0KGgoAAAANSUhEUgAABAAAAAFFCAQAAADYuVdwAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElN\nRQfhCAoNDx47TnvdAABJpElEQVR42u2dd5wUxdaGn1k2AUtOS1IBCSIiBrhKVFD4BMWcQBQDihnk\ngmBWFJAgGK6JawZEzHpRMXAlq6DIFVAREygoICwSd2GZ74/t7Z1dFmZm5/TOzM777A+mu6fm7arq\n6urTVaeqQAghhBAJhw/IoJUyQgghhEgYVrAtGWjNAuWFEEIIkTB0Yn6SckEIIYRIPGQACCGEEDIA\nhBBCCJEIJAfu5OBTjgghhBBllJQDGQDl1CAghBBCJAR64gshhBAyAIQQQgghA0AIIYQQMgCEEEII\nIQNACCGEEDIAhBBCCCEDQAghhBAyAIQQQgghA0AIIYQQMgCEEEIIIQNACCGEEDIAhBBCCCEDQAgh\nhBAyAIQQQggRLsnhBd/iYVTKk26mlcMOD2KY5XxWopyZ5k5yAEg3TP0eJ/VJVPYgFyoFLTR/k2t+\n1mx2AZBCRTPNffztbFXBZ6a6nb1OeU7z4HatFCTEXrZ5cM3/Zh8AFQutJR4Zu8gGII3yZpp72Q6A\njyoe5EJG0NRvZ4/5WXPYCUC5oNc+2nVZ6DX5Lnbr2esRVcOqy5LDqwaqexjx23jATGs6l6kkeMRc\nOgUJ0YHlyiZP+AefBQnxMacpmzziNc4NEuJMZiubGMGoICHuZpyyySM2Uy2M0OoCEEIIIRIQGQBC\nCCGEDAAhhBBCJALJJfvZGzQzi8JA5nuWvAZ8YKa1i7bO1v2cZabalQ0AtGSGmeaDvORYd/8zzMtW\nYf/iTi40O/sAFgFQyfm04AuucLZep7mZalvHXbEbD5tpPs/4MH9RnsWG1/4o/ABcy/Vmmr35yfwe\nfZGxzpalF0oX/grzF9dzrdnZR/AuACksNdPcQFdnayKnmqley7wwf3ESj+kZbMKPnFmaBkATjjSL\neoaH2ZJiGM/t7lY9Q9X8C5BuqFnD3ToyqoWyruH5833/kww1N7hbjQ1V831wKxlq1gn7F0meXPta\nhqppHtyjmZ6U/HJRzaeqbrmy14QGUa3JK0W5hhLqAhBCCCFkAAghhCgNfIazTwghA0AIIWLyUZ9M\nM2YqK4QMACGESCx+5AcGFDrid1wr1SYgZAAIIUSCtAfk/+XvpXOn+60QMgCEEKLM0JimTHb3Zjir\nK+Qxm2wmue0CQpQWycoCIYTwmsILZJ1T6E2/C4HDjK3xOWaFT+aFUAuAEEJEl3LFVsPpIRsCs2lN\nGpVpY2gmyFFRBoAQQoioMJgqIS76ewXf8Dk/0t70/Ps7KgoZAEIIIUpMYX///L28z8D/R5EVYiP9\ndiCLWjwOrKQj6U6XQr5z4TLaUZF2LCthW0AKLZjl7JUjf3zCdOo7WyvoQEXaauHvOEc+AKJIVSUv\nZCFinae4mZNpxHjO4Qo+511Od7+bwXm0Ywmz6MEAvghDtTFNmUgv4HtacD2rHRMgn4GcwG/4gCv5\nnA/p7q7RUXqsY0eIIWtSLUztHawLMWQqh4Yd85+K+IEcmIakx5cB4NvvMRLLFBfblbzKDFZGEPf9\nVb/mWT7gV6rRkn/S0yimc3mSOfxJRZpzAYPCvoQHvla5nEYbxnl8/XxBTA6/WfnbwUims57m3F6i\nZYmCxbQkcd1fIZtJTOFX4FAuYZA7R35plXz4kJF8QxKnMo5DItAq/h5azRBm4+NkJtLYRDO8ezW0\nuz3ye7V0OZdzeYNzGcA5fAN0D/juHHysAE4CVoSlmveAGsck/gB+3q+cH8EsqnAjD7jn/LLUU34V\n74cY8kGGhan9AeeFGLIZ34cd8zZsCzHkAuOunQNj2AXgD/iLhzfdwrE9H7/BenyFVa/mCGayjfkk\n04unjGJ6F2ewmJ18QU2Gcp3htRqBn9FRyX0Myk5RzVx68SQT+ItpvGGkWrD9PT7Km2hey3COZTU/\ncTzDDVeRCzWfptOD6nzHSnbTiawItIq7h/6iC+v5jm/5nS5sMdEM/14Nfrfb3Kulx5WsozF5i2S1\nAj4O+K4ceYshzQFalkD7PtbxITgDFTPIdddr/ZQpbOMJ4GjgU/zkqDEmrlEXgMMKDzTzG98O5xkO\nYQzXmKh+6nw25xnqMY2njWI7g9dYXIKVz2KV55nDeM4FWvGKufrD+OlnovQqMIGawDie51WeLeV8\nuh8YTyYwnmZM5F7Te+hh1vEk9YE7OJtHuNtAc4UHd7s392oBsxnE96TRmK9N9NbQmr9pxgTgGa7m\nHLILGc9PczVnclzAzAPhvGP/i1PcvUGMpbOznQ5kMhZ4juvoxQ40sDC+8cwJ8Hf605A0MukTsEr0\nci6iLqkcycsJlc3lIeTepfCVLfiGG3gjYCHh+Gca0Nsj7SxewMcgE63UQntppZ5Pq8Bp+D8EnLXn\n7ZgJHAvAccB/EvZetfba/4hN5PA9pwOtWMjuQo6FcAyL2ckS2lDU6fBgrSR5TCQnoLVkJNmFWsDW\n0w9ozidsj5PWXhEFA6AvL/Aof7OU07ndObaYdixkClm8FhdVgR1jgWamintZxUDgahO1LZzNJMMx\nxeFTmTQacx6fmykuA66iBhkcZ25uPs0OunOEidaVwFA2sYmhZtczHDKBtc5bJY7blx0/ALXA+X9V\nQt6rENxrv+iEwCIYN/PHAf6Oj1i75gG1HzKI+dQDaK+PQi4adgH4ClmTS4AapFKXPvRxjo5gF4/S\nDTiCqVEuPoVj663qRMYBdxhq5h9ryRATzT70dq9S6ed+JwbSlSp8wTWcyCucb5KmLKA6K4Br6cNG\nbjLL/Vz+BQw2Sv19LOB5ngegQ9hN5JGnaDC3MJQngGHALuO7aAeQ4rZ07IjSnRm6ZmT36oEJ5rWf\nBMymK5MYqWd7SFSgzgG+STF4L65zwBeVyKl6APV98d0CUNjR5higMxkcwyC3QW0RuL1J0cYbl8Xi\nVMdzCz4eKZEf+oE0/eTwDT1YyUATzWzGRTH359KHTMrThWfxc5dRPpUHxpHp9Fk+ZJj7r7OGlvQw\nSv0tLKQ/G9hIfxZEYFaUNEWDeYlfaUILNkOYA6eC30UVwXEUy3H2onFnhqoZ6b16YM7lN17nZwZA\nsV774PWEwEJ4agAUZhqX05CdfM3DhW6nRBtlPoahJDGZG411U2jFMxDykJiD81+SA9Yni96ypEcD\nPxlpHQ40BPL6ti2b1yYCNxveKTCOWtRknLNX2lzCV+wgiyedK2BJU2AjABvwomk9Hu5VCO61711l\n7N29rMWL4x/PRgE05Fngb97jYpY6x05gNnM9c8yKRR7gDpJ4lss8ugExmjDCX0Q1Wq49K4FGRlo9\n+Zq1HE5e33Zzszh+zmfUMBoBALHkRf0W0NdYsydfsZQG5I0Y75Ww92owr/2S1wB+4xqlHA2537wc\n2PM63x7gm+8j1t7K2Qf45leDmN9forEZcWYAnMINdKAqVYATnWOjOImbyOBE1jLK6fUsy9zH3ZTj\neS4xVT2J6+hMdVZzC3jytlLadGAAXcnkGwbgi2AYWmFu4fmAvm0756qJwECz0RdwEU8zlHFOPC+K\nQv5fyN0cwnuMoiuXGmvfzL8ZyXHAA9QroR9G/N6rBXwUsN2KhfuZf/6YMQZ/pQGD4sAAWOWhS2k2\nb3kY80UxlIuGrU6B3qxwB1M5kgyu4QqmOCH+wee042Iqc3YEPaheYd/8fTeQS79CjeuRk5ezFejM\nHqaWuMc8lhjFB5xABmdzKPPN+l9rMI8kWtCCTXxUQsfC/fmN10nhesPUP8pYvqQxjVnMWB6JQv6f\nxQVUZxg38m6EFcL+91BN5lKH5jSnHnOobnRfRn6vFlXw5l4tXQLn/w8caQBZNA+50yoZqB5Qnxee\n97/wOgHhKYvYw6gFwF/Me+pJxYRrbTDbnhextbC+/eaKxSucEjBJh13qLWMdrn4XunhyRRvzurlq\nA/YYa6YylKFRLfkXc7GRVnHHmoYxt4A/gmOR3e1lYTz7VQHz/xceaXARLZgYokomqa4ZOoPzKDzv\nv5/C6wSEoyzKrAEghBAimgTO/194pMEs5oXctrOB2lzHj0D++ITAef+LrhMQjrIN54Q8/0a7sLWb\ncUuIIWuXIOY3sjvEkPVlAAghRHwTygTAeY53DbibyyM825Escef/b8UXfOwuanQdV/IlGSErZTmf\neeMTjmYRnzqtdPexnY85xR2zHq6yRTuHdxzFBA/VH4jBEpqkm1QIIbwg1AmAf+LXkN899zcg8n0W\nnuZ4d/7/ZzjRnV8AJpAa8rJhdWjIw4WOPEdXejlnuYqUQl2Q4SiL2EMtAEII4Ql5EwC34XHas56f\n2UZ1/sF8YDpDWEeg70H++vJv0Z+tQCh+CYVDHMNid7tgpEFemG9Ciq+/2O3mfOJuTwzo8Q9HWcQm\nagEQQghPeIr6nExj3mAIv7CIuexlOAADOYp97kP2UOq7DqvD2er62AshA0AIIeKQggmAz6YJLzOb\nVs5kSEcwiyruMmkT+N2d1PsXKHYElRAyAIQQIk4omAA4iUHM4GNudfrlP2UK23jCCTeQmnzsTBbU\nCJijrBMyAIQQIn5ZQ2va0YzHgYv5k63uTI/pXEKm62xXgcHA/QCMpkqhpYKE8A45AQohhCcUTAC8\njxeBoW6FG7iyJ8Bt3Obsn0UWaJkdoRYAIYQoC6xiGKczQBkh1AIghBCJRIswp5D2K8uEWgCEEEII\nIQNACCGEECaUsAvgciqYRWGFh8lbTyczrX3u1hieNVPd5HyuMoxp/kIdfkPNkjCBaWZay53PHYZp\n2upuXWlYnvMX/JhnGNPfw/7FLtNrn98g/RyzzTR/9eAeXeduWaZ+S9i/eJaPzc6ev+r9HsM05bhb\ndxWZ9rd0a/IFUa6hyg47S9cA+CpOsmU38z1QXeXelHZs9yCmfk9SHzo/OmuKWbLXkzR5UZ7/imru\n7/Pk7GtZGyf3aHRL/hrWxMnd/G1U82lzlK+TUBeAEEIIIQNACCGEEIlAWF0A6YzyMCodDLWO9SCm\nfneCzuOoZKa6go0ANKSJmeZGpzcujRM9uE6NgoYYxAbzs67hJwCq0sZMczefOVvtSTVT/Yq/AWhK\nffNcCK7YzJN7dIEziK011c00Vzk99pm0MNPM4mvnvaazB7nQKmiIqwotlWvDer4HoCJtzTRzmeds\ntaWieYzbBw1xGtX07PWI8mGF9gHtWZBfLNQgIIQQQpRV3FkmOzFfT3whhBAiAZEBIIQQQsgAEEII\nIYQMACGEEELIABBCCCGEDAAhhBBCyAAQQgghhAwAIYQQQsgAEEIIIYQMACGEEELIABBCCCGEDAAh\nhBBCyAAQQgghhAwAIYQQQsgAEEIIIUS4JIcTeDc3eRCF5WwBoCGHmWlu4HsAynO8maaf+c5WGyqZ\nqX7LJgDq09hMcxPfApDKPwyv0zzn82gqm2l+x0YA6tHETPMvVgKQwgmGqV/APgCOoqqZ5ir+BCCT\npmaaW1gOQDnaG6Z+EXsBOJLqZpqrWQ9AbZqbaW7lf857TQcPaqprOSZIiInOnWfJH/wAQAWOM9PM\nZaGzdSwVzWPck7OChHidWeZn3c5SZ6uD4XvtEnYB0Iw6Zpq/sgaAarTyoJROokIYoX1AexbkF4uD\nZ9zfVJHJJIRISF7j3CAhujFb2cQIRgUJMYxxyiaP2Ey1EB76AHRivroAhBBCiAREBoAQQgiRgCSX\n7GdnGPaEziAbgNr0MNP8zOk3S6KvmeZeXna22hv2WL/OTgCq08tMc4nTE+njEsOi8pLz2c6wz/YN\ndgBQlTPMNJc6veDQz4PUH0dLM8232AZAZc400/yGrz1MfWuONtN8h60AZHC2mea3LPEg9a+yO8xf\nHE1rs7Mv4CfzumwnrztbXTjETHU2v4f5iwacbHb2n10frQtIM1Od6vj+HM6JZpqz2ABAGheYaW7l\nnRL/tj3+vL9c/8HZ6s8PucxvR01Hs72h5gBHM9lQc5ub+mcNVes5mscaag5yNJP8luSn/jFDzUMc\nzVaGmiMcTZ8nqX/QULOJo9nMUHOkG1NLfI7mXYaaRziajQw1H/Ik9bUdzdeChuzqhLzH8Oz9HM1U\nQ83f3Hx61VD1NEdzRNCQQ52QZxiefaqbpo2GqimO5hWGmh0dzRqGmsvd1G8OuS6jo7oAhBBCiIRE\nBkBc4VMWCCGESEQDwGf+CFzC5RxGGnU4hefINdNtSBvgO3wMj8mc3MJl1KIWlzmzMORyIj6uMlJ/\nCB8+HjKL7Voupg7lOYn34sCkGouPbqaK3fAxNsFNXxm/QqgFwJC93MjZtGAmm1nOzUylnTM5SaRs\n5jeOBr4G2pgoZnMnLYHWvGGiN4gXeZRJvMggAMbzGYcy0egBOIRkkhli9tC6kOm8QRYjecLs6l/G\nJPYCe5nEZaal6hHgAdOSej/wiDMZjx7VQggZABFzA9+znFs5korU4gw+5mxOcbzyI2MZGBsAt3I/\nY4DXeM1EbyZwJmcD7wEruBsfz5nMcDiKWzmCRSziCG4NOiVIaHxOOh1IoxPvml399oziGKANoww9\nfOETfqeN6SyEcCJt+F2TzAghZADYMI8PeZ1K3E0D6nA1PnzcwVEmb8EFBkAFmpnE9hWgK9CMaWY5\n4MfvvLNeRjY3mAzJuY87uJmvOJ7j+YpbuJP7Itbcwj52M8H4+l/DV2wBNrOEgYa67wAXm5fWi4G3\nVV8JIeLNALBruqxgOHP1YwyjEhO5j5585/b+X++O9i8Zv+DDx2DgFHzMYiflTJpEtwOXgDN3fuT0\nAt7hLaAXo/mSpowx0b2LfUwiHYB0JpDLXREqZtEJgH/yFAA78FHXIKZPcRw1gFoc7yjb8DnQ2fwe\n6uQoCyFETBsA8+hMOk0Yxtdk8zldzJQzyTSMZU/gaeA2qnG7c7SVM4VQSTkMP3/i43L8vABMd9+z\nI+ME5w0wk55hT7dRHJO4lBu4mUu5jJGUYxLXUYtaXO5MzxI73MYK6vEJmVzLc8AGMGmyX8htfAV8\nxW3u0igW/Agcbp4Lh4MzIUys4nP+im4LIWKVZC9ET2If8BPjnCUf7KyMuoax3EQmsBaoBzRwj5eP\nWPlj/JwCfIzPbKarJ+nL58A+3megQU94NV4AIIe27GE4r/AiU5xWhudiqoi+AUymK2/ThatIYxPw\nTwPdF5zPco4bpBXbwINFs6o6ykJ4a8ZBORoz0XBm0nDP/wQD8YHJi5OIQgvAiXzGLpYylEak0Z7/\nminPd6d7jJyabAQaAuvAfaue5zQ4R/Ie1Bfoi4+X8FMHH78YxLYJn/Gd0xIwz/Ba3cf/OIp7mQmc\nzdnAf2KsiGYBXYB2PI+fS/knZxgudWtfyVQCD1pRshzl2CV/erGi2yLe+JEfGBDF83+kSxDfBsCn\n/IN02jCWn9jNAsMeUctmxQ7MAq4CRrGF0QB8yW3cH2E1mEMFeuDnY+BR/Pg5zCjGzYEPgBSzPFjC\ng6TwIqn4wG2+jS2OBaeJ/kJGkMseusf0LXUYsNpcdTXQWPWVKMV3cR8ptGAW8BZVAzp1VtCBirR1\n19ywppmzlkleLMq59b7PiVMr6pJh5LMkA8ADkuMi6dczmp3cwh3MpCl+oBL383LEy50sZiddgDnO\nm6sNnXmLP4FvgdONNLO5jL3cSRvynALf5k1DdSvGksalvMhnPM7zQBKPsTKGy1VbYK656jygneor\nUQo0pimT8eNnOd9zPTCCrQFTcF3JQt5iiWetBJcV6oQs/EIyh72s4FV2OK9skZylmXnM48/rJYGH\nAZ5EN85nFyP5nU1MZhd/8yZHRaw7hzwv8DlUp5VZbO/nRdoAF3A9Dxtp3slK2jICgIlcyg0Mor/R\nZEB2dGQhJzCIjtxOfZ7hBb6nLc/HbLk6CyIcSVIcL4PhmoFCHJhcVtGLcdSnJfCz86+r+/03QHfg\nS4/O36KII7a/UG0A0AEff0d0jn28b+rl4NW0YjIAihQFy37Fx2jC0TzNr+whi8+5jj4GqiPw0wGY\nw1+GFmFn3mA9sIbHqGz2bu3nC6e9phovsImNPOeBA1ukHMubbGYvW/iCK7gEPzvoH7Nl9FTq8jWL\nTDUX8TX1jacXjo971CsmMdhZ6NV7vBkPUaDqjf59rONDYB/QCAI8uY4GPsVPjmc5VrAwfAa5hfy+\nkpwUR1rGFrPR1ADwaloxGQAekswjvMx8upBBA26iCiNl/osISeFm4A5TzTuAm+Kkay0eeIMhPGzo\nUBwfJkN4XEUKpzjbo6nCae43z9GVXp7G8SJnJhEYRKoHs2rATDJMdb2aVkwGgMe040V+IZvtLGM0\nTWL+7UrEPrfi5xNTxU/wM0wZa8RC+rKP0Z48WA7+UPeRzp3AdOoX61JXeC6F8FhGOyrSjmUU7y4X\nmuteQfvNRHLcsRxnkcVy8odhN+cTtnvU0pOnWpVdjvpIst1Y7P9/JAbAqaQaxturacVkAAghPHoc\n+RJyyp7e7AaGRyEHZpPNJGAgR7HPeYQVdamb4XRN+EO+ivlv7It5k8WuTlF3uchc93wcTXMml5ES\nsJ6lxvMceDWtmNflU62KQiQgfsU3CnQhb1LvI5hFFW7kAfZ3qTsnjMreH9BWsAI4yfmE/d3lInPd\nK1ttj+8BPU0VvZpWTC0AQghhyDukA2Pc6YpKb9Ki/Or2U6awzVnauqhLXTkg3TEUwuFI8kYgtSx0\nrgJ3Oe9d90qPSN+IZ3KM6ayy3plJXpdPGQBCiISiPVNIYoQHszWESjqXkOkM5y3OpW4wVcKe9/Fp\njudMjjtgM324rnuB6zrE08M9ODl8FKWJjmMPz7oAbuYRZnC+ctjcItQSK0JExrlM4Benkbw07tni\n/8+jeYDDaP7xUYwK420zb+sYFh/kjIXPk9jMZbsMAK9bACZyJtebLV4LK7mXI82tw6+5iWakkUnX\ngJmuIi1efahPMlVox3jDpZAhl+7capgH3jhB7WA4h5FGa17xJJ5WMc3mQY6iMpU5igfJNlL9kE5U\npToXssYsR1dzJpWozJkxvh6gVzTzYGqVQUxS82fY92BVg2XIwiNwbMNKOpLu3v9ZNOfmEmnOpBZt\ndUm9NQCSmEYjrjfTOx8/M8xjeTVHMJNtzCeZXkbDN+7iDBazky+oyVCuM4ztCPwRT4C5/3uEbR9T\nLr14kgn8xTTeMI/j9/gMVmvM41qGcyyr+YnjGc61JprT6UF1vmMlu+lElonmX3RhPd/xLb/ThS0J\nWEn14v1Sm7RHHJgP2MrwUj5n4NiGK1jAa25NdREtSjhr6UxOk/HntQEAFfjc8JG9gns40jyOX3At\nTUnlcJ4Bo+UlPuVi6pFKc54BppnFdQavMZ1yMV6gnmcOd3IuGbQyawEo4GH89DPSehWYQG1qMs7Z\ni5z7gfFkksl41hhNqvww67iT+tTnDn7jkYQ0ADYGNG+L0iEd2MteChZIPxn4sZRjETi2IX8cQx6z\nGFrCx9cq12dfyBByKE/essBe6FrwDTfwBjViPh+nAb090s7iBXxmw2wKTwOSZqK5CjgEnP9tmktn\nkrciIhxH7C3WHIjPo86azmQwU1VUKdMOmMMc4B/OkTlQ6lOlBY5taAV87H5zHVeGPVJCyAA4AGPB\ndHWovaxiIHC1idoWzmYSbTxId2XSaMx5fG6ktwy4ihpkcJz5kjhPs4PuHGGkdiUwlE1sYqjZdcoE\n1gKwBqtFgX8AaoHz/6oEvDdTOVUGQKkzme6cy7n0cMcVdKeKeRfkwUzJvHu+YGzDM5wYMEvCBFJN\nO1gTE00EBMBExmE5f3t+IW3JEBO9PvQ2WaioMJ0YSFeq8AXXcCKvmIzZyAKqswK4lj5s5Caz2Oby\nL2Cwmd59LOB5Z1XBDtxtojmYWxjKE8AwYJeJ5g4gxW2z2GFazeZj4wHi3Vj6XgxgvScjt8WBaMas\nUru+Bz5L4NiGVu4ce3lhvtFFUguABeO5BR+PcKFhIc7hG3qw0mhhiGzGeZDuufQhk/J04Vn83GWi\nWR4YRyaZjAUeMozt66yhZcA6YZFyCwvpzwY20p8FRobFYF7iV5rQgs1ANRPNiuBM3pLj7CUePcFs\nlI4QQgaAyxiGksRkbjRVTaEVzwDvm6j9l+QiC4XYDoc8GoyGmB0ONATyesHXG8ZxIpRw2E/xTAPG\nUctxArRy1ryEr9hBFk86uRo5TcEZTrsB224q6xEgPs9WGKjLMeoEiCp+LUQmA6As8gAjSOJZrvRA\n2wfuspZWVbXfk9txJXlrftu8qxX0gjc3i+HnfEYNsxEAeXnoJW8Bfc1ydCmQN4t7ok5g0ouPysQk\ntkLIAIgZ7uMOyvGC6UQjJzGDP8hhJVeAcbuCLR14njXk8CUD8HGvieYt1GMof/AHw4A7Td//B5qN\nqQC4iHwnwGHOngUXspLtzGAUXbnURPFmMhnJOtbxAPUMfSq8eUv0an79XmyP4tS9QsgAiDJeNH/f\nDeTSz7TJ8g6mciQV6Mwephr1rHvDKD7gBDI4m0OZb+QBUYN5JNGCFmziI7OpoH/jdVIMp5UCeJSx\nfEljGrOYsWbj68/iAqozjBt51+jmqslc6tCc5tRjDtUTtKJqSy11AghhTByNAvDHieYpnBIn+dCF\nLh7ErzGvm2s2YI+5ZipDGWquejEXm2s2LfUJWGPxTeU0ZhpNrSSEiDsDQAiRuGj2NiHsDWshhBBC\nyAAQQgghRNknrC6AgqVtn6COWRR2Op9ruSdIyE3OWmgZQecE+9L53BdUM9tdtvXwIE6ABYOQ3ubX\nIKq/OqHrUDlIyG3O5/qgMf2LzQBUoH6QkJ85n/6gmjluSpqEbAvODLrI8xpncd3aVAkScqvzuSFo\nTDfzFwDlaRAk5Hx3K5jmHn5xthqHvMjSR255PRBr2Q1ALaoGCbnFvbLBYprl5Hm6M8fCgZkXcur3\n8rOz1SjkamBOUNXfnPkPawR1V9zo5sI9QfMp1NQXTGh9N3bsKFJaD8zfzuenhp46S9zrdY+ZZkFK\nprHcTHW1W54JejfnscowTUvdrQcNp8vKdUuWXUx/csuVnebGYp7SoeAD2rMgP7EHfwj8HrTyFUKI\nsskIRgUJUT0hl2suyonulL0H4h98oWzyiJ+Czujivuh2Yr66AIQQQogERAaAEEIIkYCE5QPgK2Yr\ncvwhq3oRsiCsFyHLYpqUeqW+LKQ+/BoquJ9IOQ9qSL8Hmt7mU/CHSnLc5VNZvaJhtQBkuFtfs8/s\nr2Di0GAh2zpnzzTUfMlN08aQVYOnKcXRvNwwpp0czRph5GmwcG+6qf/RMPX56x9cZJj6/MmVqhim\n/hM39V8Zpr6Co3mWYepPd+9Au9QXOMzNNUx9vtNrD8PUn+VoVjBMfTh/+a6s/whaQ7Z2Pu8xPHv+\n+hephppr3RjPMFQ9zdHsGDSfOjifZxiefaqrvsFQNb8mv8JQs2MYNXmofwVLI1fxzgAQQgghRNlA\nBoAQQgghA0AIIfbnHZqRYthn6SOFZryjjBVCBoAQIpa5nh+Ybrgk0x6m8QM3KGOFkAEghIhlfgPO\nMFw7LJneEOCMJoSQASCEiFFSTdXSlKFCyAAQQsQ2W8EdEGVHCgWz5wshZAAIIWKMbP4NHGGuewTw\nb2fhKCGEDAAhRIyRzj8px1hz3XEkM8SdNkrEAj58JNOMmcoKGQBCCLGb8eRyl7nunexlgrN4sogd\nfuQHBigbZAAIIUQaA4D/mesuA66SM2AMtwWk0IJZwFtUxYfPmQdiBR2oSFuWK5NkABys+BT9Kz6U\nPR9wBrVJoTZn8GHEao3xuZXflfjo61ZfPjLIMc0lu3xPpSl3mY3bDu1qhsfZ+LjS3bsSH2cbxNLL\n0mWrZ1tKi16n2nTmbbO4VgYP3tSzHWURWzSmKZPx42c533M9MIKtvBdwry7kLZZ43kpgXzvmlboH\nOYrKVOYoHjTzQPmQTlSlOheyRgYA4C7KUXTbe27nNCoznx3MI4Me3B2hXjdgrrO9AFjobM8DuhkM\njfIH/Fnl/T6+pwUjGRnDV/NRKvEiPwKwmhepxGMJVMlal9LC12o3b7GFs3hSTzMRNrmsohfjqE9L\n4GfnX1f3+2+A7sCXHsejoF78Hh/ljVSvZTjHspqfOJ7hXGuiOZ0eVOc7VrKbTmTJAIge7zOKTkyl\nGak0ZxrtuS9g1beScIprAPzFKuAX1oNz7LQYzQUfjZgITIvhK9WAB9jLfQDcx15GUT9hqtjiSumH\nhvpptOdZYLyZYjlgu2kebCOUBXbLMt60f1qd4z7W8SGwD2gE/Nf95mjgU/wRtn6Gw8P43VURI+VV\nYAK1qck4Zy9y7gfGk0km41nDRBkAB7KTfPhIpzl3BhSeFzmSNA5nisk5HgOGBhT/Yc6xktMVH/MA\nWISfVLcNYF4MGwAADcmbvy12uZ62TGUV3zONdlyXQFV/caX0UeNzHAmGzZGHA6+z10xvL686qonw\nmE/lUG4zzL3C6j5PTImrSHEX4R5NlYC67jm60qtUzJc8sngBH4OM1FKLmMoWrAIOAef/d2UAFM8y\n3mc7GxjA/dzjHn2S91hNJv0CbMyS84VjoebTBvgsIsVatOYPfiCvA+ACxwD4gT84gkNj+NKuAerG\neOGbjI97uAcfTyeUP2pxpfRz43MsdyslC/7FEVxlOBlQCtdwBI8nyPXeSk9GM8GjN3bb7sN8pYnk\nuMpnkcVytz5pzidsL8VO3afZQXezeSiuBIayiU0MBa420cwkf1rrNcBqGQDFM5r/oyKVGQS84h4d\nz6E0ZCyY9FhnFXns1QH+ilAzvxNgITDI+bR7//d5YMX7+YVbgEtjvPgdzWBeYQa3FHocln2ynJIZ\nWEq3GOpns4grgSFmit1YyR7DKt/PHlYG9CuXbcozAniG4j3oix7zUTJPe5/TwnonkO+5b9fScDTN\nmRyFvMvlX8BgM737aM/z1KIWz9PByPdmMDCUP/iDYcAuGQDFsZHLaUgKPlKAX9zjrQE4CpuBRlUp\n3Ff5J1Ar4soP5rGHxdTnOBrzFdmGHgD2ToA+kmjEf2jGHTFfAO9hH/sMXeDig6pOyQwspdXMrr6P\ndHpTjbeMXJyEzTvibxTvQV/0mJ/wPO0DXx5mk80kIM9zf5ahwbaX7+gVhZx7nTW0pIeZ3i0spD8b\n2Eh/FhgZFoN5iV9pQgs2G97JZcwA6Mfz9GM9fnaT51qyfyGLnHbAVwH7y5xjkdCZVOaylF2cAJxA\nDkuYR0U6xehF9bObL2nLKj6I+QJYIeD/SEkFdrp7O4ndBWfaFTF3lwH/MLv6fvxsZC5n6rkbM6wH\nGlC8B32oxw5+xfPo4r4A/QycVAZybiJws6HeNGActRwnQCs36Uv4ih1k8STEVWtmKRoAC4HbqEnR\nPvn/uQXeIuOuJ9Cdys9YiNh/tCIn8DMzwDEA4FV+omsMT2GSxrG8TSqjE6qSPRJY6u595RyJRa4n\n0EM/r5Rer6dkmWUXY8jrfS7Ogz7wWLrzAC+pp31Bhd4ImBPWb711VywZn/MZNcxGAFi9Zh6Yt8Cd\nKUYGwH5vPZPZyRdFmrWGsYa1DANuNzhLT0bwNleymj38QD8W0JdzIlbtBjwdYAD8m9geAQBQl14s\ninAIZHwxknJcw6f8zd98ykDKOYMMY4+eDGMOl/KDW0pv4//0nCyzVOE/3MoQivegDzw2mCpUIjxP\n++L9h0ZThe5hxzR0d8XSe/8faDYHAMBF5DsBDnP2LLiQlWxnBqPoGvOeV1EyAF7iAu6nOtcEjAAA\nGEBvmrOOF+hmcp5RfMBGOpBOM2bwusnwwlOAbaRwHNCGdHYYGgA+z4byXAYx+wj0gl7MpyX9qElN\nLuVI5kelzzI0HmQmf9GeCrRnC+/xQAznqzel06vha7GGHz85rGUMyRT2oM9vuA88Noos/ITuaR/o\nP1RY9SyySuBZVOCuuIx2VKQdy8ibDfVL2tCGL/HRhKIOh17xG6+TYtw29ihj+ZLGNGYxY3nESPUs\nLqA6w7iRd+NqPFOy98W/4I20wPO/T6FvLzM+Zw96AHs5k/cYR1eqRqzYPiAdKYZenn4P8xvONNf3\ne1pCIucEZpRKWbZpBegZ4zH0vqSW/cd//JHvrngVS5hFDwbwBafyNNP5BXgZONUJOZuuTDKbbbQ4\nGphNZl5AKkMD5uCw4mIujsurXYYHXyfzGh34jJPZqLtaCCFCIN9dcQV5ToQrnEf+45xIex4PMAAK\nHA7jjUXUohaLdLHL9uwr5ZmPn6URDwMUQohEoMBd8UjynAhbAl1JYiedOZldJLmzN8Trw+MNurKJ\nTXTlDRkAKvJCCCEg0F3xaY7nTI5jMlCd44DOnAwc5+E49wKfEO+8QyZxvrOy5W7Od+ZMCC+GyTRj\nZpAUxIt3S7KKvBBCiMJ+HsewOGDviyJh/MX8Ij4YjI/RjABGcxuDS7DGwI8cxgDWqQVACCGE8IbA\nkQh54w5SaEVdMhgDlGzC5FSmMhyA4UyNaDn3vMmWD/aun/dt1RheHkgGgBBCiBgh8JF6FYt5k8Xu\nzDFz2MsKXmWHM8VZOBMm5/NhgL/+xSVahLsxTZlM3mTL7wUN/QFbHYMjFlEXgBBCiBjB75gBeW/4\nBSMRADoC0AEffwPhTZicT5eD7IVGrvP5M+y3nFU6u515FPMnLzoZ+FEtAEIIIUToBI5EKHhc+Vzf\ng5JOmGxDI9hvCft2wBzmULC2xxygiQwAIYQQInQCRyIURzgTJtszmir7zQc7me6cy7n0cOPcnSox\nvCaLugCEEELEAP4iW4EjEfz7/Z83YXK0Ypg32fIKWlE34Fiz/ZZgju2REjIAhBBB8ZlXZZoGWMT/\nXVGO5jG0dJIMACGEh289sa4qROmVPr/nZ/F5fKfIB0AIIYRIQGQACCGEEAmID2jPgryd3CD2wDrq\nK8eEEFGhAjuict6qbAXgXU4PErIbs51q1c7Dwe82AVu+re3zUHMEo4KEPIHPVZxJCWH4YqhdACto\n5WxtDrpWg1s2OzE/ObyiKIQQItSHtiX74kRTTxKbN/Oi217kmroAhBBCiAREowCEEEKIGMIf8Pbv\nZXtJWAZABXfrVupFIVMedJZgrBi0j2k6iwAox0NBQmaz1tlqHKQ5JIehzlYfd5rHA/ErewCoTeUg\nIe9y+hcbuOoHYjObnasQLO/f5FOn8EwKEjKHNc5WI8oFCXuz83kenYKEXEs2ALWoEiTkPWwBoG7Q\n5TLyU18+qB/Kf/goxNTv4Vdn67CgN0J+6nvTzSz1I9nklJLbg4TMckKm0yBIyA/dlcofDhJyL7+E\nnPpBTiXUg55BQv7mrLVeI2hP5Gj+cELeFeL9Xyvmq+2JTnm2ZLNTQ6XT3ExzH984W81JN4/xoUFD\ntHGWF24V1jI+B+dzpjlbD5BhpjrY6SQ5jkvNNB/mJ+c5Fhu0x5/3l+s/OFv9+SGX+aNBW+fsmUFD\nDnBCJhuefZub+mcNVes5mscaag5yNJNMcz8/9Y8Zah7iaLYy1BzhaPo8Sf2DhppNHM1mhpoj3Zha\n4nM07zLUPMLRbOSPdao4MX3XL2wY6uToGYaaU92Sv9FQNcXRvMJQs6OjWSPEeicUlrup3xxyXUZH\n+QAIIYQQCYl8AIQQZuT1WXrdcylEYuD1XaQWACGEECIBkQEghBBCyAAQQiQKPq3IJ0RCIx8AIYQZ\nhddrF0KoBUAIIYQQagEQQsTK27oQQi0AQgghhJABIIQo28gJUAgZAEIIIUSMos6qODIAltCXQ0ij\nEo05Iw6yYAtp+Eg3XMbjHbpQm2QyaBh02aBQ39SSKE9dOnCPsyCS3Ttg4J8F79OTWqRQjWac6cF7\nqu17q3dqS6mDjwf1pl7qpVX5VZbIpTu3Gl5T+xoPYAfDOYw0WvNKYhsAj3ECu3if7axmLDvjIAte\nIYdUsplhpPcUZ3IK37KTpdxhVsT2sY3FDOBFWvKOsXVd8Bc5t9OTunzGTpZzD1kJW23N4SQ28TS3\nxvR7Vfy9WdmWVhH7jMDPaM9KkZWR0osnmcBfTOONRDYAFnATx/IaR5JCHc7jkzjIgheB8c6nBeOB\nm6lBKk25hs/M4plMA/qzhJpcwPIYzcsPGMUZPEMTUqhPH+YkaKX1Dv/Hbl42XOxUiERkBq8xPehS\n5dHmeeZwJ+eSQavEbgGYgJ8R+4lOx4ePdJpzJzlAXjPMixxJGoczBYDf6U9D0sikD/NKMQNWs4gj\nuJEWLORHE8V1wCXM8mBlcIDq3Ec2Y2K0OD0G3JTwldYLnEsS73CB6m+P8eHjddpTnhr05mfnWP43\necznVCpThQ68F/CrbAZRO+YfLInON9zAG9SI+XhOA3rHZQ6btwDAsfsdXcb7bGcDA7ife9yjT/Ie\nq8mkH/8F+vICj/I3Szmd20sxA14CLnP+vWSieBvwLv9HdRpwCZ+ax7gbMDtGi9PnwHEJXmk9xOVk\n8BE9VH+XCiN5nA0M5l0uBwrmIsz7/JSTyWEpv9CEXgGtfI/Rn/XkKvtimC2czSTaeKBcmTQacx6f\nG+ktA66iBhkcx8uJbABsBjL3Ozqa/6MilRkEAQ0k4zmUhowFRgJLgBqkUpc+zC3FDJhCOfoB/Uhy\n2iIi5XY+4GTKAb8zla7827wNADYZv0VZucRkAZWL6MZaHL1mCH7G0F71t2fv/IVLwlO0oRKDoNju\ntrvZy2M0oRrjgQfc4yfRRu//MU4fetPHXLUTU1lFFs+xnBN51UQzC6jOClZzCH14JHENgGrAH0WO\nbeRyGpKCjxTgF/d4awCOAv4HHAN0JoNjGGTq535w5vMTp1IPqM+p/MgCE9UezGYbC7iLDPzmzfV/\nATUN9Szdqqo5RmDgO1isxdFrHgVuiQvvl3hk/5JwDAAVgexiwi8BWuPDRx0I6ORrrayMebIZ54Hq\nXPqQSXm68Cx+7jLRLA+MI5NMxgIPJa4B0BH4qsixfjxPP9bjZzewr9ibGqZxOQ3Zydc8zIWllvyX\ngA+c94lZWHUC5BWJ9tzLdOA34zh/DJwco8WpPZRq+00scgOPspPTeV81eKmQ6rQMHJgNrsmw1z2W\nooyLef5LcqHWHuv2v6OBn0yUDgcaAnAIsD5xDYAh+HiwyEN+IXAbNSnaRPc/AL5xLkRDnmUNW3kZ\nWFpqFuYMKrLDqRx2UJEZxb5FlJwuTpGwYzP3kMaIGC1Og/Exkt0JbwI8xm7O4i3V4VEh8DHRDhJ2\nLEq8U7S1x7r9byXQyESpJ7AWgDVA88Q1ADrwMIu5gJXk8Atv0s25BSezky+KDIoaxhrWMgy4HTiF\nt9hIeaoAJ5ZS4t8lizOp4OxVoDdb+E/Eqq25lzlsZC+bmAD80yi2ufzO8xzPJmbQKkaLUxfG8T86\n8QHb2G3mYhN/XM+/yOF8s7kl4u0BHF1PjbrAt872/aQyjM/JYQ0v0FFP1YSnA8+zhhy+ZAA+7jXR\nvIV6DOUP/mAYcGfiGgBwI5+RRncyaMkgygMvcQH3U51rAkYAAAygN81Zxwt0A+5gKkeSwTVcYeSM\nF5yXgIsD9i/GYjaAprzJ+RxKGs2Yw6tcbXShKnI8k7mMlTE94GQI82nEFVSnJufQnoEJWs1cx7/I\npY/Z7BLx5AbptXERLB8eoA4tne87sICj6UVFTuRDRun5l/CM4gNOIIOzOZT5Rt3NNZhHEi1owSY+\n4vw4yg0PlgNuy9Qi9niB53+gT+dlXBawdxInlXri3y6yf4ZJE9Pr5vH0zvHNC+X2xh7wfg/j/Jfz\nvuhFXK/jupi//vGF/6BHCrb70z/g+PG8qfwsg9e+pHShiwfxa+xBvR+XLQBCxAu7GQtxsV6Fd2/T\nQsQukxhcrNt47LKPQUxK7BYAIeKDDOpzMyOVETH7tiYSmTcYgp+z6RxHcZ7PI/g4hHPUAnDwKkKV\nhIg2e/mVSVRSRggRcyykL/sYHVePf+jMaPZxCYtkAAghYvtNXWa4iFV6sxsY7tHyvZYUjuFwYFfc\nrAwgA0AIIYRIQGQACJGgyAlQxC7vkA6MKTQdUGy2WBWO4RggnXdkAAghhBAloT1TSGJEnE0tPpcR\nJDG11CazixSNAhAiIVkH1FM2iJjlXCbwS5zN3tiRmzgsbsYAyAAQIkGZBh4stiqEHYPiLsZJcTUL\ngLoAhEhQXgL6KRuESGDUAiBEQrJMWSBEgqMWACGEEEIGgBBCCCESAR/QngV5O7lB7IE/yXS2apES\nhchuZA8A5agTJGQWO52tYH7OuWx1tqoFGRXtZ72zVZUKQVS3kgtARdKChPzTCZlCrSAhd7HLCVkp\n6Nl3mKc+z2scoAoVQ0x9BdJDTH0ytYOE3O1c0WQqBwn5N9tDTP0+stwrmhRi6iuTEfT8e6OY+m1s\n8zD1lYKWvfzUl6d8kJAbnJDlgqY+2vzpLEgzniFROPvHPO6h+kQOjUKaevEeAOlUN9Pczt/OVh3K\nmanml/w0aphp5pd8n+FKpHvZ4Gz9Rv0QHvoAdGJ+cngnKXgUR5Nc97KEfgGDsz7kkFlu1RmM3SFr\n7gk5prvdKj46qd/qGg12qd8bRky3e5D6P0IO+bdbzSRi6reFXPZ2syXku3k98cFfUTnrz/stZ2zJ\nPVFJ0ya3lKzzQP1PDzSzPYip35PU54QVWl0AQgghRAIiA0AIIYRIQMLqAkh1t44I2sPnBVudBsjy\nQftjdrLZSV5mkJCb+cXZah00M35zPmsH5ETxLHVmra4RtIftL6dnvxJVgoRc5TT/JtM6SMhdTmNl\nuaB9TFv50dlqFTRNvztpqhXUr+Frp8+0Go2C5n5e33YGVYOE/MG59uU4OkjI3U4DY1LQXvBt/BBy\neQ4/9VVpHCTkFsdXoyLVgoT80el2SaJNkJDZTvecL2hP4E6+c7aaBfVrWOekqWZQv4Zljl9DZQ4P\nEjLLSX0Fw35gb1jmdH7WiWosqnOZmVYO/4pqWhrwBQBVaGKmuZG1IdfkofOV82kZ05VO92Dwuzl0\ndrPS2UoP63dh5VRB1Tc96EMoXpjKJc7WbEM3j1THXbE3z5ppdmaeUxS/NNN8m7OcrXeCPqxDp7xT\nwHvwspnmqXzsmAp2qf8vXZ2taYa3YkXHqDnJsO/2DP7jPCztUr+Yds7WM4bTrVZxfCRO5IMy85ZU\nx3GwahDVWNTmITOtv6NsADRx6zS7RXOm0dfZ+oSa5jX5uTxjptmJ+c4Lkt3dvIJWJTIA1AUghBBC\nJCAyAIQQQggZAEIIUZhPOZ+aVKAJg0IeACuEkAEghIhz7uIclrOFe3jY9ZgRQsgAEEJEyHae5ngD\nneN5OoyJgkJnLheTSRpdgDm6XELIABBCRM4SrqYu17jDjSLhK66hLlcb+hYH8jf9gZN1yYSQASCE\niIRtPMVxtGUyNbjLnY8hEn7kLmowmeM5jqfCmDA6FDbSif/Skym6cELIABBClJwB1GMg39KXj/mZ\ne01mYWjEvfzMx/TlOwZSjwGG8e3P/7iKd4MuRiSEkAEghDgI/2Y7J/I9U+gWdB3GcPDRjSl8T3u2\n829D3Y+Ah1RdCCEDQAgRGVdRkUU0px+znUmGbfAzm340ZyEVucpQNwd/0KWAhRAyAIQQQZjMep6g\nBVPoRiPu4WcDzZ+5h0Z0YwoteIL1TDZtWfDpogkhA8CGd+hCbZLJoCH/OEglU3DsHQ6nnLtX2hWS\nr5i/SLkCHx2cxVMglw74uMIgrkvoyyGkUYnGnGGgdxQ+3g3YfxefwVoQ+blYjiq0Zayz4Eqkim0C\ndCyukQ8fKQGP559JMSp7lRjIV3zBVfzFvSZLjTThXv7iKhbzJQPLwPv6/nVELNcQscAialGLRWUk\nNSu5lyPNr+PX3EQz0sikK+8Zac6lD/VJpgrtGG9Qk5V5A+ApzuQUvmUnS7kjxAt8Iz/yP9Pm0nDw\nF/p7xWS9qX/RmoWMcfYeZCFHGyzS8RgnsIv32c5qxjoL00RGX2BawP5U55hNru5lGYdwK4NMFJcZ\nLpmSz173GsFo49u7LZNZx5McY6B1DE+yjskmcwpEn/DriOjWENHnDbqyiU105Y0ykZ7z8TPDXPVq\njmAm25hPMr14ykTzLs5gMTv5gpoM5bo4y+f2+c+1XP/B2eo+AZf5I+NwP/6t+x3Fz0F+U/jbg4cN\nnSlumjaF/Jtp/nJ+/JMOEiLF0bw8iNIqf2V/in+J3+9f6k/xV/b/cJCwnRzNGgdVnO/3+dsGvZL5\nvOWm/qeDhPrF7/NX8G9z9rb5y/t9/l8PEj7d0bzooOcOvIJ/+PFXO2joUxzNKkEU6/gruGkJVkZm\nu6lfelDNjv5U/29+v9/vX+tP9XcMolvB0TzLb8fpjmaGoeYXburnGapWdjR7RKxUXB0RjRrC76/t\npOk1fzR42jl7iyDhJvqT3Cua5J9YSjV5yRjqnP2MEMKGeh2numnaGGIs1vjxHxYkTH5NfkWImuv8\n+CsGCdMxpJo8PJa7qd8cUo7ix0/HqLUArAMuYRZb9vvmddpTnhr0dppd8xuAfAfZK12m0I9cHuZm\nA62mPMseLmELl7CH54KuoR6cCfgZYXxZD6UDO3nL2XuTXXTkEPN8zTZReYSdXGscsxHkMBaAseRw\nGyKadUTs1xDRYTB+RjutVH4GqwAFpbxTyrzQVRfAQbkNeJf/ozoNuIRPA74ZyeNsYDDvcnmRxuL8\nJuP990qT57mMXB7lJiO9cxnEdxzNCgZzjoHeAuBY81T3cRr+8zsA+ph2rvzKjcDpJmoXcAaz3Lja\n0JM2TGYDf/JvjuE01ZxRrSNivYaIFqlMZTgAw5lKqgpQUMYCzQz19rKKgcDVMgCCcTsfcDLlgN+Z\nSteAEctP0YZK3Ax8FoPZ9QxXsI/HuMFQczgprCXFuXkjZTOQaZ7uC0jhYzYCG/iEFM430vXhI4nD\neJXy3Gek+QSVGcxm4zaAXUxgArsYoXozynVEbNcQ0eNDLna3L+ZDZUgQJjIOuMNMz0cKzXmHlgyR\nARCcHsxmGwu4iwz8AU5Wec5QGVg1CVvyFAOAf3G9qeo17KEbe7jGRK0a8Id5ymvQnb28ArzCXnpQ\nw+zt34+fdfRjF8OMNOszho380zT959GUx3mCZpyrmjPKdUQs1xDRpMtB9kRRxnMLPh7hQjNFPzl8\nQw9WMlAGQGiUpz33Mh34zT2W6lhTscfjXAs8buzj+Shv05UPOZm3eMxAryOYLCxTlPyRANOwGgFQ\nQF0eAmab6Q2kE88V6liK/Da5le1s51ZNnBH1OiJ2awgRL4xhKElM5kZT1RRa8QzwvgyAcC3X8F3K\nSrsCeITrgSeMrbulDCWDZ0jiWTL4J19HrDgEHw+yzzz9Z1KRRXzMZ2TQ21zdD1QwU/MxmTSjFpV8\nLqUBDemnujNu6giZCKJ4HmAESTzLlR5o+4B0GQDBaM29zGEje9nEBChBg21d4NtSjPHN+HjS+KGy\nnYvIZhyHAYcxlmwuZEeEmh14mMVcwEpy+IU36WYU1wqcBfQHzjJ8VOfxB0OAMw0Vm3Mnq4yt+7Ws\nISWhK87LTF2mvK4jSruGEPHBfdxBOV7gMkPNk5jBH+SwkivAuF2hTBoATXmT8zmUNJoxh1dL4Df5\nAHVoWapWvp9rjGcCvI5VdHONioF0Y5VBB8ONfEYa3cmgJYMMh6T0BX7HdgRAXj424jNuY5Lp1Rpm\nMFehCGQf79MrjuqI0q8hhP37tP1wzruBXPqZ1eIAdzCVI6lAZ/YwlbviKIeTo3Pa1w/wiN1/21/s\nt9Cf/qUaYy+GE73Ii4UK+8dGum2Nh8HlcZpxHvg9vUYpLPMslok519xiNpaqAfB6nNUQsYFfqYmC\n5imcEqc5LJ8mIUQIzCSDzsoGESOtArGgEv/IABBChGQAnKrpZUQpPeJ9MfqI9uEjmWbMPMj3qRzK\nbXGyJJAMACFEUNaztFQ7AERi44/hWRx/5AcGHOT7rfRkNBNkAAghygbvAT2VDSIqrQHp3Am8RdWY\nahXIi8/+LRXlGQE8UyT2MgCEEHHKTI6hrrJBlOqDP4/ZZDMJGMFWZsVA3BrTlMlOfN4rNkQmBRNX\n5cdeBoAQYr9qLh40c/hIHQCiFAnsAugCbAd+Bk6KWHnOQfZCI5dV9HLi07XYEOuBBkViLwNACBGX\nzGW7DAAR5YdUoxI+sAvTg+nu9st0j0CpEfDfYo7vYgy48wwmxUXeCiFKmXVAvbiI6Uxq0VYXTJQa\nxfWtj6ZKRA/sPLLpw4MAjKEvOREojaZKsYuDV+E/3BonawImq6gJER2mYTuvYh5e+E7P5DS9K4hS\nYv/pnvL+P4ssA/WJDHGWXh9BEg9FELezyGIFrQr5xviLCR3LkzPJABAiSrwEcbK80CpdLFFGGMQh\n9GU3kM5UzolIy0c5msfJgD8ZAELEFMuUBUKUOufwCb2Bd2gfoVL8T7wsA0AIIUQC0Z5NMRkvX6mb\nFjIAhBBCiJjAfwDDwBuTICwDYI+7dS/VzKKwjh0AVKdGFLL7O3frZtLNVHOdz/lcFSTkemeMaDVq\nBgn5g/O5Pahm6Kxxt0aQYaaaX06+MIxp/sruuww117tb91HdTDXfr3hp0Jj+yd8AVKF2kJD5nQW7\nDVP/p7v1APXNVHc7n8uDxnQDWwGoRKbZ2Xe50680NawjtjmfG4OG/JB15jXUB87nWsNrn+1uTecr\n8xi35tgQy94ywzT96G4NJs1MNX9G//8axnT1fs/TA7cGpDGUkc6+H1jB1XxNS56jldteEIlp4APa\nsyD/sXVwT9/f3ckNhBAisfgn44KE6MZsZRMjGBUkxD/4QtlEyn6DEAO7AHzAbLqSwbaAFoAT+JwP\n6c4JLMIHzOA8fMAKWjm/3Bz05dw9SyfmqwtACCGEiAkC3+f3n0XwG6A78KWzf06E835qcK8QQggR\nc+Q/ntNdQ+Bo4FP8bttBuQjPkFyS6ECy4Xzj+T0hvqh4JOayz4M07XVzLCloSH+Iqd/nxNQX8UUP\ntDZzS1IQzFIfOt6mvpzhtc91rmjsX/u9buqTYjr14ZSS/CuaYlie9wRUw6FR3dRHapd5mgqufU2q\nmKmuZ2eIIct7UOd7U5d58XTa694jxeHbrx0ABvM4lfADz3EdvdiBlVNgWKmq6G59SWuz7KjlDMk4\nMd8VoVSZyiXO1h9RcUKs77gNHeM26ohE4XDHcakp30fh7Itp52x9SsconL+l49h5GD+ZaU7kFmcr\nxzCmddgAQJuQf3ETd5ud/VJeAiA1wHUvUgq8uZ7gPDPVnrwfYsh2zpz+p/NOjN+jqY4JcDnPmGl2\nYj5AMW7XB5oDcZTrVdGcT4oNHWkbgxBCCCESCBkAQgghhAwAIYQQQsgAEEIIIYQMACGEEELIABBC\nCCGEDAAhhBBCyAAQQgghhAwAIYQQQsgAEEIIIYQMACGEEELEnQGwhL4cQhqVaMwZAPgOsriKz3Dh\nleiwhTR8pLOlmDTFf+pEPJU9lTzVUGWrdi0pqzmTSlTmTMO1JWQAhMBjnMAu3mc7qxkb8npQ8cwr\n5JBKNjNUfoTKXsyTeDVU4vEXXVjPd3zL73QJeDUTHhsAC7iJY3mNI0mhDucFrFVUdnkRGO98CqGy\nF8vEYg01icHuMuTxwT4GMSmG4/cw67iT+tTnDn7jERX70jIAJuBnRBEZn/N/YONkNoOobbiaefRY\nzSKO4EZasNBZzvXAKReidMqeSl781FBvMISHnSVh44X5PMIQ3ojZ+M0EjgXgOOA/Kval1wKQn/EF\n5K9kHLhe8WP0Zz25ZSDLXgIuc/69FELKhfC+7KnkxUsNtZC+7GM0neMqFzszmn1cwqIYjd8PQC1w\n/l+lYl9aBsBmIDOEcCfRpky8/8MUytEP6EcSU1SChMpeTBNrNVRvdgPD8RX6i5w5B9krGYVjOBzY\nRe8Yvco7gBQAUp09USoGQDXgjxDCtS4jGTafnziVekB9TuVHFqgMCZW9GCZRaqgeTHe3X6Z7gl3l\nikAOOP9XVLEvLQOgI/BVCOFSykiGvQR84NjEs9i/E0AIlb1YItZqqHdIB8bgL/QXOdn04UEAxtDX\neRhGRuEYjgHSeSdGr3JTYCMAG4BmKvalZQAMwceD+3m0llVXpGxmUJEdzi2xg4rMIDshUi5iveyp\n5MVHDdWeKSQxgrnGuhOdZnoYgY+JxupzGUESUzkxRq9yT2ApAF8CvaISh3h0wY3YAOjAwyzmAlaS\nwy+8STcA6gLflsHK5F2yOJMKzl4FerOliMdpWU25iPWyp5IXLzXUuUzgJjoaqw7iVdIBSOdVBpm3\no9zEBM6J2at8M5mMZB3reIB63KRiX1oGANzIZ6TRnQxaMojyADxAHVqWwXeSl4CLA/YvpuiI7LKa\nchHrZU8lL35qqEFM8mAW9nP4hBrU4BMPHtRJTDI3KiypyVzq0Jzm1GMO1VXoQyTZQqQtU4sc6U//\ngL3CPVzxPFDp7SL7Z7gDiopPuRBelz2VPNVQ+bRnU8Je46a8G+UYxGO50WJAQgghRAIiA0AIIYQw\nJh6cAmUACCGE0GO2hGdP5VBuY69aAIQQQohEYis9Gc0EGQBCCCFENN7EfaRzJwDTqV+KLQPlGQE8\nA8AKOlCRtiyXASCEEEKUDrPJdpYsHshR7CtFr/xM4DcArmQhb7GEATIAhBBCiNKhC7AdgCOYRRVu\nL7UzrwcaAPAN0J28+QhlAAghhBCl+jD7lCls44lSOu8uxgBXAnA08Cl+k7UYSoNkFRshhBBlh3Qg\nk7GldLYq1OFWhgDwHNfRix3Ey7RAMgCEEELEKf79/i/Nh2/RMzXnkwN+F4uoC0AIIYRIQGQACCGE\nEDIAhBBCCJEIlNAHYBtbzKKQ31OSa6gZOjvdra1RsYb2RTX1Iprscz+jce23uVvbo3L+XA9Sv8vd\nskxR+H25uw3Pn+NBmrYG1H92quFPhrsnbmq9HA/yyW+o+XcJf+cD2rMg/5ZMCnKSKqq1hRAJyWuc\nGyREN2YrmxjBqCAhhjFO2eQRm6kWwkMfgE7MVxeAEEIIkYDIABBCCCFkAAghhBAiEQjLCTDDkzWO\n1rMDgOpUj0IGZLPW2WoSlXWl/3Bmr65GDZXGBGOD47pTmdpROHsuPztbh0VlPrCNjiuaZep3O0uy\n+GjiQYwPCRriWedutmQbfwKQGsL5Q2UfP7lpSjWPca2gIYZwWZzco2scJ8w6VDLT3MxmACpQz4MY\nh+enF5YToBBCCCHiFzkBCiGEEAmODAAhhBBCBoAQQgghZAAIIYQQQgaAEEIIIWQACCGEEEIGgBBC\nCCFkAAghhBBCBoAQQgghZAAIIYQQQgaAEEIIIWQACCGEEEIGgBBCCCFkAAghhBBCBoAQQgghwiU5\ncKe28kMIIYRIPAPgL+WHEEIIkRCoC0AIIYSQASCEEEKIRMAHVKezMkIIIYRIGOayWZkghBBCJCD/\nD6RNQX1gWy5EAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA4LTEwVDEzOjE1OjMwKzAwOjAwGEop\nlgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wOC0xMFQxMzoxNTozMCswMDowMGkXkSoAAAAASUVO\nRK5CYII=\n--=_MailMate_6EB92AF9-BD63-4142-8442-09708FC5F53F_=--\n\t\t\t"
  },
  {
    "path": "devtools/fixtures/raw_emails/sample-mail.eml.txt",
    "content": "From monsieur@lanza.fr  Wed Mar 27 12:55:45 2019\nReturn-Path: <monsieur@lanza.fr>\nX-Original-To: mrlanza@protonmail.com\nDelivered-To: pmcatchall@protonmail.ch\nReceived: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net [217.70.183.196])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby mail21i.protonmail.ch (Postfix) with ESMTPS id C2CB03F1\n\tfor <mrlanza@protonmail.com>; Wed, 27 Mar 2019 12:55:44 +0000 (UTC)\nAuthentication-Results: mail21i.protonmail.ch; dmarc=none (p=none dis=none) header.from=lanza.fr\nAuthentication-Results: mail21i.protonmail.ch; spf=pass smtp.mailfrom=monsieur@lanza.fr\nAuthentication-Results: mail21i.protonmail.ch; dkim=none\nX-Originating-IP: 37.173.108.150\nReceived: from localhost (unknown [37.173.108.150])\n\t(Authenticated sender: monsieur@lanza.fr)\n\tby relay4-d.mail.gandi.net (Postfix) with ESMTPSA id 5724BE0014\n\tfor <mrlanza@protonmail.com>; Wed, 27 Mar 2019 12:55:44 +0000 (UTC)\nContent-Type: multipart/encrypted; boundary=\"wM+4IwYl8gmMA5PsnkRnGt=R0CsTG\n =Y1yNY=BZJEnO\"; protocol=\"application/pgp-encrypted\"\nDate: Wed, 27 Mar 2019 12:55:43 +0000\nFrom: \"monsieur@lanza.fr\" <monsieur@lanza.fr>\nMessage-Id: <4-bXLlVZ9yTpa1Zqu8wYzmZMKEI2asXWOcVW0I4Z-z8=@caliopen.org>\nSubject: Test again\nTo: =?UTF-8?q?=C3=80_moi_M=C3=AAme?= <mrlanza@protonmail.com>\nX-Mailer: Caliopen-0.16.0\nX-GND-Spam-Score: 200\nX-GND-Status: SPAM\nX-Spam-Status: No, score=-1.0 required=4.0 tests=ENCRYPTED_MESSAGE,\n\tMIME_HEADER_CTYPE_ONLY,RCVD_IN_DNSWL_LOW,SPF_PASS autolearn=no\n\tautolearn_force=no version=3.4.2\nX-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on maili.protonmail.ch\n\n--wM+4IwYl8gmMA5PsnkRnGt=R0CsTG=Y1yNY=BZJEnO\nContent-Description: PGP/MIME version identification\nContent-Type: application/pgp-encrypted\n\nVersion: 1\n\n--wM+4IwYl8gmMA5PsnkRnGt=R0CsTG=Y1yNY=BZJEnO\nContent-Description: Caliopen PGP encrypted message\nContent-Disposition: inline; filename=encrypted.asc\nContent-Type: application/octet-stream; name=encrypted.asc\n\n-----BEGIN PGP MESSAGE-----\nVersion: OpenPGP.js v4.2.1\nComment: https://openpgpjs.org\n\nwcBMAxUZJmb0iiO6AQgAwvsnweNh/JB3dlM1tAu/qsii9ZtywnYpgw8kWJXC\nZ1i439sxqQT7aJ19fK/lA+Q2iKLo5dr8LuRyGokL+Crtgca/KUu5nJlekK/I\nYwgDjRv0tJwNzQkF+jAx037A7azBgppwdo3wJpSj177aTol0XAfWiutpshtA\nagQzML5q4hp+h6md79B3tCu8ufL0b//FC5Y8QJA59R0T5ID/6vOWC3D6ppE7\n47siewFPRuTCkZ4t0cjK0809vGOaCMNT+Ti5B93CQH+vVAWOSTZr6+kwF9Zo\nPUmZK4xFfCDwrYmnWSAk6F0bpriZ1VJMdGj29WWRqNIce37QRWoqiKdd+7m6\nnsHATAMg4lkksuit7wEIAKaIIXjVFH718OfEum916VdRw2TRlj87lDaX7RxG\nLIl5gkr0H48kPyePHa7XHdvpg9AlWxd5QOVEOjheh4seKd/alw8kHE3Zll6z\nwz7qHRYzIp4vuJEllY9r/xh8jdbk4AKtIShfBIZLYl44y04ihyWwLHeWUkMV\nRs/H4hHtXKWrVA+QJ3d6NUNQ27wBZzCjskXZydLRBkroNMaoH2YDWjMV47Tq\n6ci41lcolrADW2jfX++eBHWtywHCc0Rs3WbzHWATvum/XzAbEZhm4lifrbHQ\nLMehtrgVXOih2iVcL67knytRVO7AN/J3EFR9avjUfVzrBVTCZzQp5T4I50XE\nUZLScQHlLZHQ6xtutWGqM+/Bja9NuQKiZYQY3Dx0lWi8toUDLla/NtwVBLqG\n95GxR9XoaKf175ljvLa6uDKl1e0ZUcP0FlY516bxqg5MJzhzMqcoQd5Y8QUB\nYY8ySp1pq0sKft+EDrZu2IAv5/9kMOnkmK66\n=rvuz\n-----END PGP MESSAGE-----\n\n--wM+4IwYl8gmMA5PsnkRnGt=R0CsTG=Y1yNY=BZJEnO--\n"
  },
  {
    "path": "devtools/fixtures/raw_emails/signed-and-encrypted",
    "content": "Return-Path: <xxxx@caliopen.org>\nDelivered-To: xxxx@caliopen.org\nReceived: from post2.caliopen.net ([178.32.78.240])\n\tby box.caliopen.net (Dovecot) with LMTP id LZqzCARN11qW2QAAz9GZsw\n\tfor <xxxx@caliopen.org>; Wed, 18 Apr 2018 15:50:51 +0200\nReceived: from post2.caliopen.net (post2.caliopen.net [178.32.78.240])\n\tby post2.caliopen.net (Postfix) with ESMTP id D002452321\n\tfor <xxxx@caliopen.org>; Wed, 18 Apr 2018 15:50:51 +0200 (CEST)\nX-Virus-Scanned: amavisd-new at caliopen.net\nX-Spam-Flag: NO\nX-Spam-Score: -0.099\nX-Spam-Level:\nX-Spam-Status: No, score=-0.099 tagged_above=-999 required=4.2\n\ttests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\n\tUNPARSEABLE_RELAY=0.001] autolearn=ham autolearn_force=no\nAuthentication-Results: post2.caliopen.net (amavisd-new);\n\tdkim=pass (1024-bit key) header.d=caliopen.net\nReceived: from --IP masked by caliopen--\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby post2.caliopen.net (Postfix) with ESMTPSA id 6EE925230E\n\tfor <xxxx@caliopen.org>; Wed, 18 Apr 2018 15:50:30 +0200 (CEST)\nDKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=caliopen.net;\n\ts=20140504caliopen; t=1524059430;\n\tbh=yqiDEOfmDMKEHhITxSBC1PfpAvPv60JY4TadrwiGx6E=;\n\th=From:To:Subject:Date;\n\tb=LhFR4yATuQWzdL2xDOCZIJpXo3ZbNF6Bmbff8ZFsLvfW+M5MU4sHlV4jVsyD6sLz2\n\t 2tLmHOdGy8yMzl/hMq63cayNoYtIl8/37PPbC66wo5mH0xWJ8kH1++VNwcAsHJUWKE\n\t 447LZXm09vf0CXFXdeBIgqz9h0qwRfoJ2mT451qQ=\nFrom: \"Neil DIAMOND\" <xxxx@caliopen.org>\nTo: \"Neil DIAMOND\" <xxxx@caliopen.org>\nSubject: ***UNCHECKED*** Do not read this !\nDate: Wed, 18 Apr 2018 15:49:57 +0200\nMessage-ID: <9D144EF0-E966-4CCD-AE5E-0DF3D3F4FB3A@caliopen.net>\nMIME-Version: 1.0\nContent-Type: multipart/encrypted;\n boundary=\"=_MailMate_B04DF9F4-DED0-46A2-8E27-A1E440263F2F_=\";\n protocol=\"application/pgp-encrypted\"\n\nThis is an OpenPGP/MIME encrypted message (RFC 3156 and 4880).\n\n--=_MailMate_B04DF9F4-DED0-46A2-8E27-A1E440263F2F_=\nContent-Description: OpenPGP control information\nContent-Type: application/pgp-encrypted\n\nVersion: 1\n\n--=_MailMate_B04DF9F4-DED0-46A2-8E27-A1E440263F2F_=\nContent-Description: OpenPGP encrypted data\nContent-Disposition: inline; filename=encrypted.asc\nContent-Type: application/octet-stream; name=encrypted.asc\n\n-----BEGIN PGP MESSAGE-----\n\nhQQOAx2MzvGeb7QWEA/9EMidy7odhloRvPZnIhOJ22ODs23CqtCot/Q18FxMLBGi\nSaBu4F5KxrM34glpZCauZv7lF4cSnM+E12mRgp9mYop+DiPApnFH1An3QJBk1FQK\nxjjmq0gqcY2lPrbNicZ55sRVPg0CqCr9GDsuUErLvJ11gsryWb+mSvnk2eRjxPBp\n/JWsF6En9XqFOxq1eIaihWH/x8hXREIqnO0cS+GlI0yAEta1yNA7R9B6n0HLNTH/\nW+iKEmt1bAdbyjFCN2/dxvgwLRBpyjmb+Zcb0MzK2u1L/aZC4XJ+zDHi7pAEY9hK\nxD1MHBNGJUyqfi/vSxvaN6GK+TJToP6ScqwHciEokdqBRVul5mEzOxIQac+kRMVE\n5qFUPWMQKLXPDjk/OvdtPOcOQkvI9fMQRyEufHjLpAjB4rQ1v5dqMkzCSqYEfP4/\n9kBbDShOoywgh2DhqXgayEwQuThrk3p/QDo2ljiTPMle2LaU/Uvmqg/mil9Ov5VU\ngtnEYy7deEQWKpbOu2g8miX9nWVxMmVNJkeM0a6C2bR5ykKYJePxdR8dOTtkIRbB\nNdyxlrmSd0RUSNZQaN9o0WMyYXGmw3hrNH//s7DfdF27H263WtTiknQSTTxmGiru\nm91K2Pz+4b23gmZ+aSTDJMf9jGDcr3uAEgJ5DiGa9h9pC7foGRQyGh0fq8jgcPQP\n/1JCmIvHVh4D+sWZlpg2McgUHkRkA+w0FBSPCdUP6rUpEcfvSi1Cf8O5Iqe1/xYJ\nIkE5cR0b+sqjgxifwzL2Ey78wr+HWUR2zRnXj2xhOgWVWVkc1sTkHUFeJyM8xQyY\njwag03BggVW3OicmNp9jSX3AGNXvfG8S6DEiIljdDT15Ko4sfmsUNmo/JG0yKICn\nzojB4aWAyCTesEv1fRcT3bjFXgqZ4b1v4wOnaKB23/3yPOVkax1C5nLcy6OElsZc\nSgbR55NklpGatPTYwKr9FwZ5qNa0OIek/AFQJfX3P1QTYm0lNqWIGow+WFBWOK2u\n8hnDvvUSrRsv89xN/M8F1i45+oe8lf+c2i29Gu6/wNwWBmLVwawITfdYjuyy+Wcx\nqfiLARYJZR3PPNs1BthhbvGNRDDrMFdidjviM6w8iFnlWUV7fpgXVs9mZwd+8gzl\nyJNmSBLyCP9SQJ9s31u6TmTZW8m3987ueEumh+uvBOU3PqydkzjZv0/bALez7rXe\nk97jUSSmyC+55L+72K5ZIR47hkN6cX6lS7IEZmHb27B6dm6Rn83pU3Y9F2Qb6g85\nOOkRf8IH+KqaPHlcRHt6cvBhHHP1yqftG1vY88Uz53eAd9GBP+/yWneF7Omw4E46\nUr8vx/TF7okZkE5VtGVo/ynH8RTZg9uwjHKJDLlUYobHhQIMAw8unhLuK+bFAQ/+\nN1y4VpBw8hnnqWmR9uy5clJFY8dzEbxZcwXAMaISJ39aNQFLSMpQwAKzrnqkQNMB\nMIJS/XeFwwBtLB5gozKPHehxmz5OnEv/wVz0TRifr5AAgfPwP4Ak3/Ug4eSw7QHn\nlgJOIpzf/mgew6IFSAL5LfI4LbfwET/I3FRDc424+c23PQ8xlY+4oCVMG1uhWlI1\nrYjWR8h/l3FTCNH7tzCwCZoQGqxX0z3VbnMRRCQWnrDvO4frAj1UBlwvZUHJsjRE\n8gMTNkxBCaLsfTyCr6mGma11VKCPd5MMukNVPZFEd1WxnBqe2SlWB1db/icVDQ7z\ndTvypN0pzRwUIoYo/wd9wSkqrzjtTQSJXPVBB78xWNSr0s5bpZm5Fo6Gnr+ByOFB\nH1S6S8nVxw/VkF1i5veZcK/maA6Jq/9LY28IsEZFUy32zc6g43nccGk6KQYx8EdC\nbsu4nEv24ohimhiVluPGpA4IsHTSDbOXKeygWGYS61V2XrH+i2yPH11EdI3mOMa8\nGWHIgYoKc0dtz4UhtlNkw6Kz/zjpuS+QrPrh1J+bVv7IV3bpazv0+taLCH2vhNVa\n3cAMWmrSqRvHUEDE6FkIakpH1i7wZmDxXgR/D2qfL7R+RcHQVBNK4m+VBWOSNrVB\n553h531BLD5VDlCZLHmRNFODNUcgXVX5hxFxetkcXS7S6gEoPKXEs3TayW1aiP5q\nXi8LlJELMllkgeh9DV0q8w9IR1zbLPVSwGy8vwYWjBAFLEVciucYloTjepVCAsI9\nJ5pOJzfAysWTs2K7Nc2p7APvBwWwYX90q0ysoZGr4HSmeDAYyxORDJUHkCtcSbAi\nHVFvAL2/jqkfZyrPAeeG+ps47JsIUq5CvJTPQcIg8ZBxJWHn3CTFivG6Ju4kT7tb\ndSDtkM7gPKFIM1bAnMs0xPqTejXlm1ypN2opHxlU7wxRzFNx3DPt3Eob0UV1XDVS\njApk8gH97ZYgqBLfIp92LCjgbqlaSC0l/3yvrZePo0bO5+wtYolzDQxQxhhwscWr\neY/VgaK+Vp8t/qCgyaRKLhCfLGce82+ggk3Buw4x9WFAosAFS9SNPq1/f/xoOmeX\nwV8jAoHZHS/4FYuQEYZs1m3IuJAqog2qjhBC6oZgW37uS5HoeuGMuoH3n+rUE3hK\nDDvdxb87Hr81z61DLJWHsahpzT+CFQsIuBpL99+MW6Cu5BYYyGRUUtW8+NDiBn+Q\nBOzb82hvXTVLjLSWojPPPEycnNKlY79zyyzUwXfKmB6D+0LL/vN398Y0oNmtV31z\naW0LUeRui48qCMc6cd2ZwPWnWwMW3fNIAFT9Puoq2rdgTrUJyUDbAuokfE+h4BAv\nSAPC1f2+u1yW6YjU+89wYQThwANoPjHXPd/XO2tqKthqEm50ZC3H+xXptuIujr7t\n6YY89F7BM4c2JhPldePT+DQYNHB/V4SQll/Eh05SojLQgUEYUjlCiL2A6cS3F3IE\nzhTky2re86b9FCjn4c0q7E/dQk/rysKWu/o75iDTohQuvgW8BzUJKSgHnY4jgsP1\nTsGy9I4fngWfVLaCv/ODXEZNL8Go/YQCqnYn+15MqQx/1Q45cko2T0Ibsa/UH1tO\nOB88qbMrqFhWVZQzsVz9AeSWRBclyZJjD3ljI+4IVIh1dxdLYbeE1z/CDEIONpqe\nW7pb3Jho0Gaz2Of9CdZ/jhN4up2KQmSu5KQ53OhzLdOiyTx/SvnbF0IwXbYUZwDn\nIreGO9yX/t0YcJ+MAOVpNCFBOPSvMNyohh4iTA1yLLVH7hc41cltDWnY5kmXxvYT\n4LfGnI02K7kV79+L6X+RdAN4b8PrTjRghIxU0sEez/MoHwn+eVKn8l1D5gZTKbuL\n+7vJx1Ll4sptCoKjalWtiHKmA0m2zNxmePEFwGxjRugmYZmhrc5MTgKVvpJibFkl\nru8vqkY1id/vlEbJNNT55Sc1GUwvXFmmM0bi0nY7T1roYdvHjgCOQ7kHA1MeMCJ9\nxJtlSxe7Xeky1XmCDiOGZA8TuMRPcI4n7TBH0a568K4oF1oZafPgcrtPZx1MSO35\npCrAiY8JwdTXrSUqZ/5olm7d8qcdClklJAjcnBVLBsQozvfQFS6WiuOUmYi33Rdd\nVkHbdjOa0YOvBaeenr0aFPczM3pnPTjUVs2AuXALh747jJ6p68HugOpgsebyr07k\nsS8IUChUPUxfiO/p6RDzDCfszBel4UBDNcpMojrJPaCtEXPQPUrUwI5syc1kz3vH\nqCnXLYbSGYmWTG7NYqLbIuNfkS1/00eLg0C64F9/ghDe1wKPwKxu/zgpH9kmAljw\n4b1ZpWMKTnQ+yLOThApKt5rLJIJahMCcXMd0JNc9aY4H/1ToaON/aknwGAp39jeC\nk7QCPSmlKzu7cRAwFRM6uK4jy0lKPLlDwJzSKa77AnEoUsxT62qd+n5Rajnn/hWu\nKfQzlZ1r/vLlso+5g+yhD8IM2f+Yz4WA5z/bsejuLqPFaJcl+pIkRGk7qOoa\n=ncno\n-----END PGP MESSAGE-----\n\n--=_MailMate_B04DF9F4-DED0-46A2-8E27-A1E440263F2F_=--\n\t\t\t"
  },
  {
    "path": "devtools/fixtures/raw_emails/simple",
    "content": "Date: Fri, 03 May 2019 15:48:47 +0200\nMessage-ID: <XnGyIBduFIsanlYf08z-a-WBJOR0Jt__M5sVpNtEsOM=@caliopen.org>\nSubject: le sujet est roi\nTo: dev@caliopen.local\nFrom: someone@elsewhere.earth\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\nmessage body"
  },
  {
    "path": "devtools/fixtures/raw_emails/spam-multipart",
    "content": "Return-Path: <bounce@client.mes-offre.com>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n by brasil.brainstorm.fr (Postfix) with ESMTP id F372220A76A10\n for <laurent@brasil.brainstorm.fr>; Sat,  9 Apr 2016 12:16:30 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n id E49223310011; Sat,  9 Apr 2016 12:16:30 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n id D24D2331690D; Sat,  9 Apr 2016 12:16:30 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: ******\nX-Spam-Status: Yes, score=6.5 required=5.0 tests=AWL,BAYES_99,HTML_MESSAGE,\n MPART_ALT_DIFF,NORMAL_HTTP_TO_IP,RCVD_IN_CSS,RDNS_NONE,T_KHOP_FOREIGN_CLICK\n autolearn=no version=3.3.2\nX-Spam-Report: *  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n *      [score: 1.0000]\n *  1.0 RCVD_IN_CSS RBL: Received via a relay in Spamhaus CSS\n *      [63.251.255.134 listed in zen.spamhaus.org]\n *  0.0 NORMAL_HTTP_TO_IP URI: Uses a dotted-decimal IP address in URL\n *  0.0 HTML_MESSAGE BODY: HTML included in message\n *  0.8 MPART_ALT_DIFF BODY: HTML and text parts are different\n *  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n *  0.8 RDNS_NONE Delivered to internal network by a host with no rDNS\n *  0.4 AWL AWL: From: address is in the auto white-list\nX-Spam-DCC: :\nReceived: from server102.offerprivee.in (unknown [63.251.255.134])\n by mexico.brainstorm.fr (Postfix) with ESMTP id 9B8EC33181FC\n for <dev@caliopen.local>; Sat,  9 Apr 2016 12:16:25 +0200 (CEST)\nTo: dev@caliopen.local\nSubject: 2 articles pour le prix d'1\nMessage-ID: <894109bf7e2849ab86b3db6bbb60e309@162.244.67.108>\nDate: Sat, 09 Apr 2016 04:37:26 -0500\nFrom: \"JustFab\" <noreply@client.mes-offre.com>\nReply-To: noreply@client.mes-offre.com\nMIME-Version: 1.0\nX-Mailer-LID: 165,173\nList-Unsubscribe: <http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11226>\nX-Mailer-SID: 11226\nX-Mailer-Sent-By: 1\nContent-Type: multipart/alternative; charset=\"UTF-8\";\n boundary=\"b1_ad062cc5824a44b4f36f410e0dd49ebc\"\nContent-Transfer-Encoding: 8bit\nX-Spam-Prev-Subject: 2 articles pour le prix d'1\nContent-Length: 9746\nLines: 249\n\n--b1_ad062cc5824a44b4f36f410e0dd49ebc\nContent-Type: text/plain; format=flowed; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\nYour email client cannot read this email.\nTo view it online, please go here:\nhttp://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11226&L=165&N=3653\n\n\nTo stop receiving these\nemails:http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11226\n\n--b1_ad062cc5824a44b4f36f410e0dd49ebc\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: 8bit\n\n<html><head></head><body><div>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"\nname=\"viewport\" />\n<title>JustFab</title>\n<style type=\"text/css\"><!--\n    a, a:hover, a:visited, a:link, a:active {text-decoration: none;}a\n{text-decoration: none}\n\n\n   .ReadMsgBody {width: 100%; background-color: #ffffff;}\n   .ExternalClass {width: 100%; background-color: #ffffff;}\n   body   {width: 100%; background-color: #ffffff; margin:0; padding:0;\n-webkit-font-smoothing: antialiased;font-family: Georgia, Times, serif}\n   table {border-collapse: collapse;margin: 0 auto}\n\n   @media only screen and (max-width: 640px)  {\n               body[yahoo] .deviceWidth {width:440px!important;\npadding:0;}\n               body[yahoo] .center {text-align: center!important;}\n         }\n\n   @media only screen and (max-width: 479px) {\n               body[yahoo] .deviceWidth {width:280px!important;\npadding:0;}\n               body[yahoo] .center {text-align: center!important;}\n         }\n\n    body {padding: 5px; padding-top: 0px;font-family:arial, Verdana,\nsans-serif !important;font-size:12px;}\n    [class=product] {min-width: 100% !important; padding-left: 0px\n!important; padding-right: 0px !important;}\n    [class=section_title] {padding-top: 5px !important;}\n    [class=categories_mail] {padding-left: 0px !important; padding-right:\n0px !important;}\n    [id=second] {border-top: 0px !important;}\n    [id=last] {display: none !important;}\n    [class=hide] { display:none;}\n    }\n--></style>\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour visualiser correctement ce message, [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/display.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&S=11226&L=165&N=3653\">cliquez\nici</a>]</td>\n</tr>\n<tr>\n</tr>\n</tbody>\n</table>\n<!-- Wrapper -->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nstyle=\"width: 100%;\">\n<tbody>\n<tr>\n<td width=\"100%\" valign=\"top\" bgcolor=\"#ffffff\"\nstyle=\"padding-top:20px\"><!-- Start Header-->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nclass=\"deviceWidth\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td width=\"100%\" bgcolor=\"#ffffff\">\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"\nclass=\"deviceWidth\" style=\"width: 600px; height: 15px;\">\n<tbody>\n<tr>\n<td width=\"100%\" bgcolor=\"#ffffff\">\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"left\"\nclass=\"deviceWidth\">\n<tbody>\n<tr>\n<td style=\"padding:10px 20px\" class=\"center\"></td>\n</tr>\n</tbody>\n</table>\n<!-- End Logo --> <!-- Nav --> <!-- End Nav -->\n<table class=\"deviceWidth\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nalign=\"center\" bgcolor=\"#eeeeed\" style=\"width: 600px;\">\n</table>\n<!-- End One Column --></td>\n</tr>\n</tbody>\n</table>\n<!-- Logo -->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"left\"\nclass=\"deviceWidth\">\n<tbody>\n<tr>\n<td style=\"padding:10px 0px;   padding-bottom: 14px;\" class=\"center\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2015/10/15102015/JustFab_Logo.png\"\nalt=\"www.JustFab.fr\" border=\"0\" /></a></td>\n</tr>\n</tbody>\n</table>\n<!-- End Logo --> <!-- Nav -->\n<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" align=\"right\"\nclass=\"deviceWidth\">\n<tbody>\n<tr>\n<td class=\"center\" style=\"font-size: 11px; color: #272727; font-weight:\nlight; text-align: right; font-family: Arial; line-height: 20px;\nvertical-align: middle; padding:16px 20px; font-weight:bold;\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">CHAUSSURES</a>\n&nbsp;&nbsp;&nbsp;                                         <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">SACS</a> &nbsp;&nbsp;&nbsp;\n                                    <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">V&Ecirc;TEMENTS</a>\n&nbsp;&nbsp;&nbsp;                                         <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #001;\">COLLECTIONS</a>\n&nbsp;&nbsp;&nbsp;                                         <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"\nstyle=\"text-decoration: none; color: #FF0000;\">PROMOS</a></td>\n</tr>\n</tbody>\n</table>\n<!-- End Nav -->\n<table class=\"deviceWidth\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nalign=\"center\" bgcolor=\"#eeeeed\" style=\"width: 600px;\">\n</table>\n<!-- End One Column --></td>\n</tr>\n</tbody>\n</table>\n<table class=\"deviceWidth\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"\nalign=\"center\" bgcolor=\"#eeeeed\" style=\"width: 600px;\">\n<tbody>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_01.jpg\"\nalt=\"DOS MEJOR QUE UNO&#10;&#10;      \" border=\"0\" style=\"display: block;\nborder-radius: 4px;\" /></a></td>\n</tr>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_02.jpg\"\nalt=\"DISFR&Uacute;TALO\" /> </a></td>\n</tr>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_03.jpg\"\nalt=\"&iquest;Por qu&eacute; comprar s&oacute;lo un par cuando PUEDES TENER\n2?\" /> </a></td>\n</tr>\n<tr>\n<td valign=\"top\" style=\"padding:0\" bgcolor=\"#ffffff\"><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=14638&F=H\"><img\nclass=\"deviceWidth\"\nsrc=\"http://eu-cdn.justfab.com/image/email/fr_FR/emailers/2016/03/18032016/img_04.jpg\"\nalt=\"T&uacute; que adoras los zapatos...&iexcl;Hazte miembro VIP de Justfab\ny disfruta de nuestra oferta de bienvenida: &iexcl;2 art&iacute;culos por\nel precio de 1! 2mejorque1\" /> </a></td>\n</tr>\n</tbody>\n</table>\n<!-- End One Column --></td>\n</tr>\n</tbody>\n</table>\n<center> <center>\n<p align=\"center\"><font face=\"Arial, Helvetica, sans-serif\" color=\"#666666\"\nsize=\"1\"><font color=\"#000000\"><font size=\"2\"><strong>Just<font\ncolor=\"#ff0066\">Fab</font></strong></font> | Just Fabulous - Avenida\nDiagonal 611 - 08028 Barcelona </font><br /><span\nstyle=\"font-color:grey;font-size:x-small;\">*voir conditions de l'offre sur\nle site</span><br /> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8114&F=H\"\ntarget=\"_blank\" style=\"color:#ff0066; text-decoration:underline;\">Politique\nde confidentialit&eacute;;</a> | <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8117&F=H\"\ntarget=\"_blank\" style=\"color:#ff0066;\ntext-decoration:underline;\">Contactez-nous</a><br /></font></p>\n<p><font face=\"Arial, Helvetica, sans-serif\" color=\"#666666\" size=\"1\"><br\n/></font></p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\"\nstyle=\"width: 600px;\">\n</table>\n<font face=\"Arial, Helvetica, sans-serif\" color=\"#666666\" size=\"1\">\n<table cellpadding=\"0\" cellspacing=\"0\" align=\"center\" border=\"0\"\nstyle=\"width: 600px;\">\n<tbody>\n<tr>\n<td cellpadding=\"0\" cellspacing=\"0\" width=\"479\" align=\"center\" border=\"0\"\nstyle=\"vertical-align:top;font-family:font-family: Calibri, sans-serif;\nmargin:0; font-size:11px; line-height:13px; padding-left:50px;\npadding-right:50px; -webkit-text-size-adjust: none\"><span\nclass=\"applelinks\" style=\"color:#666666; font-family:Calibri, sans-serif;\nfont-size:11px; line-height:13px; -webkit-text-size-adjust: none\"><br\n/></span> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8115&F=H\"\ntarget=\"_blank\" style=\"color:#66666; text-decoration:underline;\"><span\nstyle=\"text-decoration: underline;\"></span></a><br /><br /> <a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8109&F=H\"\ntarget=\"_blank\" style=\"color:#ff0066; font-family:Calibri, sans-serif;\ntext-decoration:underline;\"><span style=\"text-decoration:\nunderline;\"></span></a><a\nhref=\"http://162.244.67.108/~client/link.php?M=11664742&N=11226&L=8116&F=H\"\ntarget=\"_blank\" style=\"color:#666666; text-decoration:underline;\"><span\nstyle=\"text-decoration: underline;\"></span></a></td>\n</tr>\n</tbody>\n</table>\n<link rel=\"icon\" type=\"image/png\"\nhref=\"//jf-eu-cdn.justfab.com/image/site/global/icon/favicon.ico\" />\n<link rel=\"shortcut icon\" type=\"image/png\"\nhref=\"//jf-eu-cdn.justfab.com/image/site/global/icon/favicon.ico\" />\n<table align=\"center\">\n<tbody>\n<tr>\n<td>Pour cesser de recevoir nos informations [<a target=\"_blank\"\nhref=\"http://162.244.67.108/~client/unsubscribe.php?M=11664742&C=94c9fa492a809f4120021467a9b09dd2&L=165&N=11226\">cliquez\nici</a>]</td>\n</tr>\n</tbody>\n</table>\n<!--END FOOTER MAIL--></font>\n<p>&nbsp;</p>\n</center></center></div><img\nsrc=\"http://162.244.67.108/~client/open.php?M=11664742&L=165&N=11226&F=H&image=.jpg\"\nheight=\"1\" width=\"10\"></body></html>\n\n--b1_ad062cc5824a44b4f36f410e0dd49ebc--\n\n"
  },
  {
    "path": "devtools/fixtures/raw_emails/with-docx-attachment",
    "content": "To: \"stanos@caliopen.app\" <stanos@caliopen.app>\nFrom: Stanislas SABATIER <stan@sapienssapide.fr>\nSubject: test docx\nOpenpgp: preference=signencrypt\nAutocrypt: addr=stan@sapienssapide.fr; keydata=\n mQINBFgsSNIBEADAcMZyEPJjh1xDjHdgI/0xmSrn/U+1GxkfYtUz10uLcWO//BDBnOMgW2Yb\n Wd5Vf41ID4yrh/7HG8rlNXEsz2+HyI82IzacJ3dgMFqcHuAFYLBDR7YhuF6a6xeOsAj3RHVs\n Xwo/RNQgcbk4sWUml1DUGKsBU3cOHuedMZL3ny5fy/WTZ4F3QIXeIAAJ0iWFIfcPTZO9HrIw\n uN17HF47zNrKusIdbyGMJlnrW0vvvP7xRaITniAegUzNqJD7GaOYbeyrYC3lBKwYXsYqDLMY\n d/K2UbeM6kpLM1t+BN4ogB4ns+FFeAGLenPd+Ggx+WvZh4O/+AfWhzkEo1ok+80EUCJcz6kg\n j5FMW1Up4o5RHFtaqxsJ+/uQxwgxSlf7ETomY2CO41Im8rWEHbcYYzKLbGVveQKa+4k4KT0r\n Pk1l816b8CKMoomvsSG06+9M2HpM7WrV2Wq8RrkyoY1WISElKDoZbbPGsykApLBievsonoPr\n 8HFoug2MwuG0c5BxZDrM6RuAgL5N1Jz8FjMAxoEw+NoBZcf53d86ec2hlChQOlE2Pok12gLb\n KOHMbaW5iCEy8tmHVy/x7eTlW0Yxymdrd2rkgq0EWWLRnbEWr3xIsxmkd8ldB4VCPxLDWKnP\n 6wlp3+Cnsdx3zKrdCwFw7DZkBuiopzugM4t6YVt2E1gKveMUyQARAQABtCpTdGFuaXNsYXMg\n U0FCQVRJRVIgPHN0YW5Ac2FwaWVuc3NhcGlkZS5mcj6JAj0EEwEKACcFAlgsSNICGwMFCRLO\n pgAFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQ89gxRuURFSYoBhAAue9kSIZlz/L3XPqS\n fXJJYPe69qQ521VhqvomHF1xi9QLv2/3RWP8wk90qpkHzaDsUaPuuGB7JZm+rg1ChkwQ8HwR\n NgEVNRd6KBWc1Ykb7zp4sYlFyUbiyMRha8yK/nVkoAkUfSo71PIcc/cxrdMNIwoqVWwF8k3j\n oS4Dgbyl/zgcNi/THXjl/7oI/NHwqIA23gZxH2KQHGqOz3UMqp7nJR2OL2HtaQ2qZKFvON/p\n L3nKu+SR5JdEF4wSGl9q7Qh8pvEZGjQfNqEPetr7+jjSUXgeu8mOg/RU+xitqHXG7DiaJuTh\n Kv04daXxeo/aslZMY672GRZ7mc/xGEwGZtoUCCOiWxsixIP6oy2PQ9GSU5whVx7bPuO6wy+R\n sUWaRC1Ap7w8fmHM8M8//0KTq1kuxgklqnkuZEL/bwXdYCiMiICmMnlLo9eqFgwqiJrMYm2a\n kqX/wYt177xYmNQiIUbvRZl8Zn/zAXE7gJwwEnRyNi2BMYbQ5rCOhBYBKqrEa00/9xurVWSb\n krO0dglMdLZcF06gqzhk/+1vqWd5mAZzer9LUafMR44r4rT1ITzS4OjI2c27ZOBaLv1sjXVQ\n +vHM4O8qz1LnyNJcimGVlbbvslMBv+D6Ib6vmRdvCfb61NX95lkzilIZ/fHMZQY292/XLe56\n 5G8IqG/Xms2TsH73gbW5Ag0EWCxI0gEQANY3al+M0dCDUSuPecmv4aasRNGfem+sDg2Uz8jb\n 0KaMREhf52Q557jVS3lLa7sJbv4RGXg5AmQirssiZ7hSqi5uYYbTV4xIi8ysBjWmofDs8Oa8\n 2zAJvTjXGrfPozwQewtZCoooh8ykCaXMKLKzRAd3EK6EVL7wqCE2uxsk4M8JpElTRuXt+aNP\n LtxutGHbQXl0MUDkKGR87yg8xroXP5vq6Lue7CZS4VJgDj/V/VztvC/4y9aEW4YEhkp+QtP/\n SSUDO6c9wW8OkVo1vqJ6FEGN5tO2R0tSWqGP1XGlZkrQSHXiLdH6r/ndM1cAVum8PmJkFqDX\n cWutKgcv4jSkIE6fvOMxa/XviUUMbF1y37N9X4dsuPl2Y7/fJFMF8CUvaTQmvX9dFich5k1E\n o6a7lq1WeJZ+Tzd2h0uqyAlYguKXp5YrJp7F6s0bjebgHsyq8QxoX1q1No5eltdI7n8cPot9\n p2GIZNCCgmbmhmMSukP46fSIa8CX6JLEmoU5C53WAx/sxxggz/qrp+mr8rfkOLh9gyFRGv+i\n hKmleBGzLkRXm3t4o5fNtsEydk3woGCBNF+jDWFF1yei/Tslx+XQBgS3cdn+ryc1cv2icH+d\n dN0IZ6zb73fmLauPBYO+HlVTBW9NtpfhPaG/F6QL1qSuPOUYYAyrS54Wnc/xB+yhhm8PABEB\n AAGJAiUEGAEKAA8FAlgsSNICGwwFCRLOpgAACgkQ89gxRuURFSaMAA//ZZIoqkODVsbTnONy\n TKoiniie54829X+2xQV8tNOI+ltEtOMbj6/fzDRYp3gFhN4CIQSeQ1mu8NpQInpHlrjR2EPC\n +s+L/Q2gbdhxVhahzBxrhgL5lW1TqJx/H1l8bOkq6PRBoX4aePUrhlYkeKf1AWYtnJiS8jBh\n PcwFGWuUxjIJoJmypa3XdSWM1CCNUbIYhuPSa2YThDsfzKBahvqm3E89omrpIcMxt1Q4YALM\n HegzsU+3PVXvGAkJCxBNmlgI5CXNjaaACPYxDQnQZflTTwDOa5U3dwrv6ZpqpdIHzlePCaK9\n S6tpUNirBaSiwF0e89/DQcJFjkj2F8aeoN0CtsGiYFvmaQ5kqqKRiMn9jfCok6NPqCqsn/ti\n TCT9AuTYON7qWB/cuN1kcBcTzhI+yh87R9DlBEHcTNPpcBLyhtKwm3bJkCKFyFt97y1KI20X\n Wr6FIP2DXJNJw7WlqfMtGMadhELpkF+TASnb0YzgW6SAQImh7NUK9T2hmFNU+kgqyOdn+dpH\n lQZwjPpefJqsjIfC//irrnWXVGtcAPTcwAVLG072eEdKAHgxngJ83K9iFYSjRcaZ6Qi5F1vg\n EDhrq47la8zUq2XqUKQkahkDJBko4trJ9MZtZWP/R4Ajcck1upIqxNxptWnW2s4uBDsiae95\n QM8RbF0GZ06V1LUeM7k=\nMessage-ID: <862047da-b110-399f-947c-d4b8e75a2dd3@sapienssapide.fr>\nDate: Fri, 10 May 2019 15:25:13 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101\n Thunderbird/60.6.1\nMIME-Version: 1.0\nContent-Type: multipart/mixed;\n boundary=\"------------4A1EF679861DB8D31FF2C178\"\nContent-Language: fr-classic\n\nThis is a multi-part message in MIME format.\n--------------4A1EF679861DB8D31FF2C178\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\n\n\n-- \n|||||||||||||||||||||||||||||||||||||||||||\nStanislas SABATIER\nsapiens.sapide\n\ntél.: +33 (0)6 63 19 36 36\n|||||||||||||||||||||||||||||||||||||||||||\nLes emails de sapiens.sapide sont en lieu sûr : ils sont chez Mailden !\n\n\n--------------4A1EF679861DB8D31FF2C178\nContent-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document;\n name=\"Untitled.docx\"\nContent-Transfer-Encoding: base64\nContent-Disposition: attachment;\n filename=\"Untitled.docx\"\n\nUEsDBBQACAgIAAxrqk4AAAAAAAAAAAAAAAALAAAAX3JlbHMvLnJlbHOtkk1LA0EMhu/9FUPu\n3WwriMjO9iJCbyL1B4SZ7O7Qzgczaa3/3kEKulCKoMe8efPwHNJtzv6gTpyLi0HDqmlBcTDR\nujBqeNs9Lx9g0y+6Vz6Q1EqZXCqq3oSiYRJJj4jFTOypNDFxqJshZk9SxzxiIrOnkXHdtveY\nfzKgnzHV1mrIW7sCtftI/Dc2ehayJIQmZl6mXK+zOC4VTnlk0WCjealx+Wo0lQx4XWj9e6E4\nDM7wUzRHz0GuefFZOFi2t5UopVtGd/9pNG98y7zHbNFe4ovNosPZG/SfUEsHCOjQASPZAAAA\nPQIAAFBLAwQUAAgICAAMa6pOAAAAAAAAAAAAAAAAEAAAAGRvY1Byb3BzL2FwcC54bWydkc1u\nwyAQhO99CgvlmuC4kRtFmKg/6ilSI9VterO2sLapbEBAouTtS+rK9bnissOsvlkWtj33XXJC\n55XRBVkuUpKgFkYq3RTkrXyer0niA2gJndFYkAt6suU3bO+MRRcU+iQStC9IG4LdUOpFiz34\nRbR1dGrjeghRuoaaulYCn4w49qgDzdI0p3gOqCXKuR2BZCBuTuG/UGnEdT7/Xl5s5HFWYm87\nCMgZ/StLE6ArVY88jdejYPfWdkpAiBvhO/Xp8OUnguaL7HpmO6WP5+pjnVf5Kpk0VPEJXyiu\nM8wejqqT84zRKYztoUHPl4wOBTsYJz2/Y3Qo2GMLDkSIv8GzNaMTObEOKrSvFkQE3K6mTRMj\nJjloHNj2N25UUYyL5t9QSwcIVhPnriABAAD+AQAAUEsDBBQACAgIAAxrqk4AAAAAAAAAAAAA\nAAARAAAAZG9jUHJvcHMvY29yZS54bWyNUstOwzAQvPMVke+J47RF1EpSCVBPVEJQBOJm7G1q\nSBzLdl9/j5M0aYEeuO3sjGdfTmf7qgy2YKysVYZIFKMAFK+FVEWGXpbz8AYF1jElWFkryNAB\nLJrlVynXlNcGHk2twTgJNvBGylKuM7R2TlOMLV9DxWzkFcqTq9pUzHloCqwZ/2IF4CSOr3EF\njgnmGG4MQz04oqOl4IOl3piyNRAcQwkVKGcxiQg+aR2Yyl580DJnykq6g4aL0p4c1HsrB+Fu\nt4t2o1bq+yf4bfHw3I4aStWsigPK02MjlBtgDkTgDWhXrmdeR3f3yznKk5hMw3gSknhJJjQZ\n0fH0PcW/3jeGXVybvGFPwMcCLDdSO3/DjvyR8Lhkqtj4hecrE86fWsmQak5ZMusW/ugrCeL2\n4D0u5PqOqmPu/yONaTI5G6k3aCsb2Mrm7+WkLTrApmu7+fgE7rqRBuBjJ10JXboP//zH/BtQ\nSwcI1hHMm2IBAADbAgAAUEsDBBQACAgIAAxrqk4AAAAAAAAAAAAAAAAcAAAAd29yZC9fcmVs\ncy9kb2N1bWVudC54bWwucmVsc62RTQrCMBCF954izN6mVRCRpm5EcCv1ADGdtsE2CckoensD\niloo4sLl/H3vMS9fX/uOXdAHbY2ALEmBoVG20qYRcCi30yWsi0m+x05SXAmtdoHFGxMEtERu\nxXlQLfYyJNahiZPa+l5SLH3DnVQn2SCfpemC+08GFAMm21UC/K7KgJU3h7+wbV1rhRurzj0a\nGpHggW4dhkiUvkES8KiTyAE+Lj/7p3xtDZXy2OHbwav1zcT8rz9Aopjl5xeenaeFSc4H4RZ3\nUEsHCPkvMMDFAAAAEwIAAFBLAwQUAAgICAAMa6pOAAAAAAAAAAAAAAAAEgAAAHdvcmQvZm9u\ndFRhYmxlLnhtbK1QQU7DMBC88wrLd+q0B4SiphUS4oR6oOUBW2fTWLLXkdck9Pe4TishyKGg\n3uyd2ZnZWa4/nRU9BjaeKjmfFVIgaV8bOlTyffdy/ygFR6AarCes5BFZrld3y6FsPEUWaZ24\nHCrZxtiVSrFu0QHPfIeUsMYHBzF9w0ENPtRd8BqZk7qzalEUD8qBIXmWCdfI+KYxGp+9/nBI\ncRQJaCGmC7g1HcvVOZ0YSgKXQu+MQxYbHMSbd0CZoFsIjCdOD7aSRSFV3gNn7PEyDZmegc5E\n3V7mPQQDe4snSI1mv0y3R7f3dtJrcWuvp0SZtpo8iwfD/E+rV7PHkMsWWwymya5g4yahF52f\nfaupZPNbl/A9GRBPBRt7uj7On4o6P3j1BVBLBwjBx9kIHQEAAFUDAABQSwMEFAAICAgADGuq\nTgAAAAAAAAAAAAAAABEAAAB3b3JkL3NldHRpbmdzLnhtbEWOSw7CMAxE95wi8h4SWPCpSLvj\nAsABQmugUmJHsaHA6QkrlqM3M3r77pWieWKRkcnDcuHAIPU8jHTzcD4d5lswooGGEJnQwxsF\nuna2nxpB1doSUx9ImsnDXTU31kp/xxRkwRmpsiuXFLTGcrMTlyEX7lGkTlO0K+fWNoWRoK2X\nH+ZkpiZj6ZG06qwc2B8Y8BoeUU/hclTOtfIM0cPG7X7Y/l3aL1BLBwgRjU9opQAAANAAAABQ\nSwMEFAAICAgADGuqTgAAAAAAAAAAAAAAABEAAAB3b3JkL2RvY3VtZW50LnhtbKWUS27bMBCG\n9z0Fwb0tqQkCV4icjdGiiwYG7B6ApiiJKF8YjiS7p+lderGS1itNgMJoNqKof+abf0aiHp/O\nWpFOgJfWFDRbp5QIw20pTV3Q78fPqw0lHpkpmbJGFPQiPH3afnjs89LyVguDJBCMz21BWzC5\n543QzK+05GC9rXDFrc5tVUkuxoWOGVDQBtHlSTImra0TJmiVBc0wbKFOhpTdWCv5mKYPCQjF\nMPj1jXR+onX/qt9pNcX1t1TtLZQOLBfeh0FoNdTVTJoZk6U3NBw5c4a7pXIJrH9R8m8ju0Fc\niP4NcraxDjbG6V0pgZelr3iHhjmx0Or30b6Abd1E0/yWbjWDH62LE3PhjZ6kkni5Nr6Yyu7f\n5+r1zP6PF78fzfOvtbHATiochAAi0R3dhrNwsuUlru562cN1OeBFCdLnHVMFfY5dK5pEBWJA\nsqxjArzR+hy3R+GRlIKgOCP5/Yt41naiZlAKCCeVhFN4jvE4ZA28mOkFx5FxcbMNEyh7VovB\niKsPP4MSjkSWfYpD7/Mm3D9s7jZTwDcG4akSFcagu/sYA7JuXmwbwYKZgqZxg9YtSmUtzsrJ\nIlq9iHWLoziWem71cbBa6YAvBZfzyOJHsweLUx8VU35sIg5mJyG0G34Jk67geIpysgwimV5T\nsvy7tn8AUEsHCH6wYHj0AQAAAAUAAFBLAwQUAAgICAAMa6pOAAAAAAAAAAAAAAAADwAAAHdv\ncmQvc3R5bGVzLnhtbMVV207bQBB971dY+x4cEKIowkHUVdS0KK2AfsDEHicr9tadNSF8fXc3\nNg2xI26VeLF3z3hmz5wz2Zyd30uR3KElrlXGDg+GLEFV6JKrRcZ+30wGpywhB6oEoRVmbI3E\nzsefzlYjcmuBlPh8RaNVxpbOmVGaUrFECXSgDSofq7SV4PzWLtKVtqWxukAiX16K9Gg4PEkl\ncMXaMofHnUKSF1aTrtxBoWWqq4oXGEv59MNhXEnRFpDFS4hIsLe1Gfh6Bhyfc8HdOpJhiSxG\n04XSFubCd+v5sLHvtdTFV6ygFo7C1v6yzbbZxddEK0fJagRUcJ6xSz5H68trlVyj5RXzoeWF\noj0hBHIXxCFjM+30Bk/y7z+S6zyEC8rYxCJegyKWhvNu0SofuAORsaMNRA+PwHGL5LSLCVCL\nFqvsYHL19PiH5SCfBWjOS891yQfTWUhMm07T3f7N7i68VrzUq9wrYrXYJJsmefvztCNtnCp/\nuFsbr78BCwsLZhn4xNC0DAJ5K0U0RoHEtpcGjj3+mUS70+cZfZh/hRbattyhdvrDbY0Cv9SE\nbwjhkui40OKR0xwIy5+qzyGF967Fb/z6iy7Xe727RTSzrQRna2yUMFDw2Pgc/U8cgx7DQBQq\nh9bfaEdD9lq3g0X9ZjeRXa893FidXOGiFmD7Ld8y8rTHyNP3+PGo4a4hAUxC9FlLGoX+SSq4\nwqs6XIRxPhvEM/18wrYUf6L3cZ/eb23qkpPrNBTBvl6ejtHWfdPn+647b6WYgwmz0WHZ4s+J\n3jPtVBtj/Z/kpRd7Vks/frRn1sN0v2LWuxPJN8+cXnzRvFWnqSrxvqPSBv1vGr3H7nZF479Q\nSwcIgYISaXoCAAAVCQAAUEsDBBQACAgIAAxrqk4AAAAAAAAAAAAAAAATAAAAW0NvbnRlbnRf\nVHlwZXNdLnhtbL1UO0/DMBDe+RWRV5S4MCCEknbgMUKHMiNjX1JD/JB9Le2/55w+hKrQFlQY\n7bvvKdnlaGHabA4hamcrdlEMWAZWOqVtU7HnyUN+zUbDs3Ky9BAz2rWxYlNEf8N5lFMwIhbO\ng6VJ7YIRSMfQcC/ku2iAXw4GV1w6i2Axx8TBhuUd1GLWYna/oOuVLsFZdrvaS1IVE963Wgqk\nMU9T3osL0MY9wLlVO+7ytbOCkN1OnGofz79X8LbZEdAmJUv3/Yg3D/2QbkCYJ6o7aAXZWAR8\nFIYW+EtKwosT5+lTUk6Og/ORE3Gxv/ceOVfXWgJxzAxBCkixFajcEyUE1HCctnQBfi6+yZrQ\nRyp+uKDW5W5N0/p/FN1J16QwEa/tL9LuVJ3oKLOEGOltUoQt9UETERAJE0/vYcN80MLX8k9r\nYTsxQtvDVeCyhb8oouNdy5+VvPsvh59QSwcIvRaD+FEBAABeBQAAUEsBAhQAFAAICAgADGuq\nTujQASPZAAAAPQIAAAsAAAAAAAAAAAAAAAAAAAAAAF9yZWxzLy5yZWxzUEsBAhQAFAAICAgA\nDGuqTlYT564gAQAA/gEAABAAAAAAAAAAAAAAAAAAEgEAAGRvY1Byb3BzL2FwcC54bWxQSwEC\nFAAUAAgICAAMa6pO1hHMm2IBAADbAgAAEQAAAAAAAAAAAAAAAABwAgAAZG9jUHJvcHMvY29y\nZS54bWxQSwECFAAUAAgICAAMa6pO+S8wwMUAAAATAgAAHAAAAAAAAAAAAAAAAAARBAAAd29y\nZC9fcmVscy9kb2N1bWVudC54bWwucmVsc1BLAQIUABQACAgIAAxrqk7Bx9kIHQEAAFUDAAAS\nAAAAAAAAAAAAAAAAACAFAAB3b3JkL2ZvbnRUYWJsZS54bWxQSwECFAAUAAgICAAMa6pOEY1P\naKUAAADQAAAAEQAAAAAAAAAAAAAAAAB9BgAAd29yZC9zZXR0aW5ncy54bWxQSwECFAAUAAgI\nCAAMa6pOfrBgePQBAAAABQAAEQAAAAAAAAAAAAAAAABhBwAAd29yZC9kb2N1bWVudC54bWxQ\nSwECFAAUAAgICAAMa6pOgYISaXoCAAAVCQAADwAAAAAAAAAAAAAAAACUCQAAd29yZC9zdHls\nZXMueG1sUEsBAhQAFAAICAgADGuqTr0Wg/hRAQAAXgUAABMAAAAAAAAAAAAAAAAASwwAAFtD\nb250ZW50X1R5cGVzXS54bWxQSwUGAAAAAAkACQA8AgAA3Q0AAAAA\n--------------4A1EF679861DB8D31FF2C178--\n"
  },
  {
    "path": "devtools/fixtures/raw_emails/with-json-attachment",
    "content": "Return-Path: <stan@sapienssapide.fr>\nDelivered-To: stan@mailden.net\nReceived: from post.mailden.net ([9.6.71.30])\n\tby box.mailden.net (Dovecot) with LMTP id jTfVOWCI1VxsvAAAz9GZsw\n\tfor <stan@mailden.net>; Fri, 10 May 2019 16:20:07 +0200\nReceived: from post.mailden.net (localhost [127.0.0.1])\n\tby post.mailden.net (Postfix) with ESMTP id 27B22B478B\n\tfor <stan@mailden.net>; Fri, 10 May 2019 16:20:07 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on post.mailden.net\nX-Virus-Scanned: amavisd-new at mailden.net\nX-Spam-Flag: NO\nX-Spam-Score: -1.782\nX-Spam-Level:\nX-Spam-Status: No, score=-1.782 tagged_above=-999 required=4.2\n\ttests=[AWL=0.117, BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1,\n\tUNPARSEABLE_RELAY=0.001] autolearn=ham autolearn_force=no\nX-Spam-Mailden: '1'\nAuthentication-Results: post.mailden.net (amavisd-new);\n\tdkim=pass (1024-bit key) header.d=mailden.net\nReceived: from --IP masked by Mailden--\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))\n\t(No client certificate requested)\n\tby post.mailden.net (Postfix) with ESMTPSA id 2C3BDB4779\n\tfor <stan@mailden.net>; Fri, 10 May 2019 16:20:05 +0200 (CEST)\nDKIM-Filter: OpenDKIM Filter v2.10.3 post.mailden.net 2C3BDB4779\nDKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=mailden.net;\n\ts=20140504mailden; t=1557498005;\n\tbh=9QXLTY2bFXXlKjOPVaOwikPIGG41QGg7Qm2VmxV5c7M=;\n\th=To:From:Subject:Date;\n\tz=To:=20stan@mailden.net|From:=20Stanislas=20SABATIER=20<stan@sapie\n\t nssapide.fr>|Subject:=20test=20json|Date:=20Fri,=2010=20May=202019\n\t =2016:20:04=20+0200;\n\tb=Yj1UYKcnodCPc3KXai49s/grFCPFb9XtiR674Mi1dSTLagmzpkRO/Tav6JDWuAGS/\n\t XdmnvwY/kI2QTnos8gNOTWObt3iVS7Iu5JdA4j6dO8spbewvhDmT6sSu+sOLehQjqM\n\t eRjUG2s0v05VWgpthvL1wDAGB+7/BGE8sdeYGxug=\nTo: stan@mailden.net\nFrom: Stanislas SABATIER <stan@sapienssapide.fr>\nSubject: test json\nOpenpgp: preference=signencrypt\nAutocrypt: addr=stan@sapienssapide.fr; keydata=\n mQINBFgsSNIBEADAcMZyEPJjh1xDjHdgI/0xmSrn/U+1GxkfYtUz10uLcWO//BDBnOMgW2Yb\n Wd5Vf41ID4yrh/7HG8rlNXEsz2+HyI82IzacJ3dgMFqcHuAFYLBDR7YhuF6a6xeOsAj3RHVs\n Xwo/RNQgcbk4sWUml1DUGKsBU3cOHuedMZL3ny5fy/WTZ4F3QIXeIAAJ0iWFIfcPTZO9HrIw\n uN17HF47zNrKusIdbyGMJlnrW0vvvP7xRaITniAegUzNqJD7GaOYbeyrYC3lBKwYXsYqDLMY\n d/K2UbeM6kpLM1t+BN4ogB4ns+FFeAGLenPd+Ggx+WvZh4O/+AfWhzkEo1ok+80EUCJcz6kg\n j5FMW1Up4o5RHFtaqxsJ+/uQxwgxSlf7ETomY2CO41Im8rWEHbcYYzKLbGVveQKa+4k4KT0r\n Pk1l816b8CKMoomvsSG06+9M2HpM7WrV2Wq8RrkyoY1WISElKDoZbbPGsykApLBievsonoPr\n 8HFoug2MwuG0c5BxZDrM6RuAgL5N1Jz8FjMAxoEw+NoBZcf53d86ec2hlChQOlE2Pok12gLb\n KOHMbaW5iCEy8tmHVy/x7eTlW0Yxymdrd2rkgq0EWWLRnbEWr3xIsxmkd8ldB4VCPxLDWKnP\n 6wlp3+Cnsdx3zKrdCwFw7DZkBuiopzugM4t6YVt2E1gKveMUyQARAQABtCpTdGFuaXNsYXMg\n U0FCQVRJRVIgPHN0YW5Ac2FwaWVuc3NhcGlkZS5mcj6JAj0EEwEKACcFAlgsSNICGwMFCRLO\n pgAFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQ89gxRuURFSYoBhAAue9kSIZlz/L3XPqS\n fXJJYPe69qQ521VhqvomHF1xi9QLv2/3RWP8wk90qpkHzaDsUaPuuGB7JZm+rg1ChkwQ8HwR\n NgEVNRd6KBWc1Ykb7zp4sYlFyUbiyMRha8yK/nVkoAkUfSo71PIcc/cxrdMNIwoqVWwF8k3j\n oS4Dgbyl/zgcNi/THXjl/7oI/NHwqIA23gZxH2KQHGqOz3UMqp7nJR2OL2HtaQ2qZKFvON/p\n L3nKu+SR5JdEF4wSGl9q7Qh8pvEZGjQfNqEPetr7+jjSUXgeu8mOg/RU+xitqHXG7DiaJuTh\n Kv04daXxeo/aslZMY672GRZ7mc/xGEwGZtoUCCOiWxsixIP6oy2PQ9GSU5whVx7bPuO6wy+R\n sUWaRC1Ap7w8fmHM8M8//0KTq1kuxgklqnkuZEL/bwXdYCiMiICmMnlLo9eqFgwqiJrMYm2a\n kqX/wYt177xYmNQiIUbvRZl8Zn/zAXE7gJwwEnRyNi2BMYbQ5rCOhBYBKqrEa00/9xurVWSb\n krO0dglMdLZcF06gqzhk/+1vqWd5mAZzer9LUafMR44r4rT1ITzS4OjI2c27ZOBaLv1sjXVQ\n +vHM4O8qz1LnyNJcimGVlbbvslMBv+D6Ib6vmRdvCfb61NX95lkzilIZ/fHMZQY292/XLe56\n 5G8IqG/Xms2TsH73gbW5Ag0EWCxI0gEQANY3al+M0dCDUSuPecmv4aasRNGfem+sDg2Uz8jb\n 0KaMREhf52Q557jVS3lLa7sJbv4RGXg5AmQirssiZ7hSqi5uYYbTV4xIi8ysBjWmofDs8Oa8\n 2zAJvTjXGrfPozwQewtZCoooh8ykCaXMKLKzRAd3EK6EVL7wqCE2uxsk4M8JpElTRuXt+aNP\n LtxutGHbQXl0MUDkKGR87yg8xroXP5vq6Lue7CZS4VJgDj/V/VztvC/4y9aEW4YEhkp+QtP/\n SSUDO6c9wW8OkVo1vqJ6FEGN5tO2R0tSWqGP1XGlZkrQSHXiLdH6r/ndM1cAVum8PmJkFqDX\n cWutKgcv4jSkIE6fvOMxa/XviUUMbF1y37N9X4dsuPl2Y7/fJFMF8CUvaTQmvX9dFich5k1E\n o6a7lq1WeJZ+Tzd2h0uqyAlYguKXp5YrJp7F6s0bjebgHsyq8QxoX1q1No5eltdI7n8cPot9\n p2GIZNCCgmbmhmMSukP46fSIa8CX6JLEmoU5C53WAx/sxxggz/qrp+mr8rfkOLh9gyFRGv+i\n hKmleBGzLkRXm3t4o5fNtsEydk3woGCBNF+jDWFF1yei/Tslx+XQBgS3cdn+ryc1cv2icH+d\n dN0IZ6zb73fmLauPBYO+HlVTBW9NtpfhPaG/F6QL1qSuPOUYYAyrS54Wnc/xB+yhhm8PABEB\n AAGJAiUEGAEKAA8FAlgsSNICGwwFCRLOpgAACgkQ89gxRuURFSaMAA//ZZIoqkODVsbTnONy\n TKoiniie54829X+2xQV8tNOI+ltEtOMbj6/fzDRYp3gFhN4CIQSeQ1mu8NpQInpHlrjR2EPC\n +s+L/Q2gbdhxVhahzBxrhgL5lW1TqJx/H1l8bOkq6PRBoX4aePUrhlYkeKf1AWYtnJiS8jBh\n PcwFGWuUxjIJoJmypa3XdSWM1CCNUbIYhuPSa2YThDsfzKBahvqm3E89omrpIcMxt1Q4YALM\n HegzsU+3PVXvGAkJCxBNmlgI5CXNjaaACPYxDQnQZflTTwDOa5U3dwrv6ZpqpdIHzlePCaK9\n S6tpUNirBaSiwF0e89/DQcJFjkj2F8aeoN0CtsGiYFvmaQ5kqqKRiMn9jfCok6NPqCqsn/ti\n TCT9AuTYON7qWB/cuN1kcBcTzhI+yh87R9DlBEHcTNPpcBLyhtKwm3bJkCKFyFt97y1KI20X\n Wr6FIP2DXJNJw7WlqfMtGMadhELpkF+TASnb0YzgW6SAQImh7NUK9T2hmFNU+kgqyOdn+dpH\n lQZwjPpefJqsjIfC//irrnWXVGtcAPTcwAVLG072eEdKAHgxngJ83K9iFYSjRcaZ6Qi5F1vg\n EDhrq47la8zUq2XqUKQkahkDJBko4trJ9MZtZWP/R4Ajcck1upIqxNxptWnW2s4uBDsiae95\n QM8RbF0GZ06V1LUeM7k=\nMessage-ID: <900ef049-5635-d125-186e-da0ef4698639@sapienssapide.fr>\nDate: Fri, 10 May 2019 16:20:04 +0000 (GMT)\nMIME-Version: 1.0\nContent-Type: multipart/mixed;\n boundary=\"------------CBFE033A7BB72330697873CF\"\nContent-Language: fr-classic\n\nThis is a multi-part message in MIME format.\n--------------CBFE033A7BB72330697873CF\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit\n\n\n-- \n|||||||||||||||||||||||||||||||||||||||||||\nStanislas SABATIER\nsapiens.sapide\n\ntél.: +33 (0)6 63 19 36 36\n|||||||||||||||||||||||||||||||||||||||||||\nLes emails de sapiens.sapide sont en lieu sûr : ils sont chez Mailden !\n\n\n--------------CBFE033A7BB72330697873CF\nContent-Type: application/json;\n name=\"test.json\"\nContent-Transfer-Encoding: base64\nContent-Disposition: attachment;\n filename=\"test.json\"\n\newogICJzdGFuIjoiZW1haWwiLAogICJvayI6IDEKfQ==\n--------------CBFE033A7BB72330697873CF--\n"
  },
  {
    "path": "devtools/fixtures/raw_inbound_msg.cql",
    "content": "USE caliopen;\n\nDROP TABLE IF EXISTS raw_inbound_msg;\n\nCREATE TABLE raw_inbound_message (\n  raw_msg_id  uuid,\n  data        blob,\n\tPRIMARY KEY(raw_msg_id)\n);\n"
  },
  {
    "path": "devtools/fixtures/twitter/dm-multimedia.json",
    "content": "{\n  \"created_timestamp\": \"1538554415664\",\n  \"id\": \"1047399250294923268\",\n  \"type\": \"message_create\",\n  \"message_create\": {\n    \"sender_id\": \"484004076\",\n    \"sender_screen_name\": \"sapienSSapide\",\n    \"target\": {\n      \"recipient_id\": \"2353377708\"\n    },\n    \"message_data\": {\n      \"text\": \"DM 10:11 avec #hashtag citation : @mailden_fr, Ã©moti ð et image https://t.co/xD4h9OYaAH\",\n      \"entities\": {\n        \"hashtags\": [\n          {\n            \"indices\": [\n              14,\n              22\n            ],\n            \"text\": \"hashtag\"\n          }\n        ],\n        \"media\": null,\n        \"urls\": [\n          {\n            \"indices\": [\n              64,\n              87\n            ],\n            \"display_url\": \"pic.twitter.com/xD4h9OYaAH\",\n            \"expanded_url\": \"https://twitter.com/messages/media/1047399250294923268\",\n            \"url\": \"https://t.co/xD4h9OYaAH\"\n          }\n        ],\n        \"user_mentions\": [\n          {\n            \"indices\": [\n              34,\n              45\n            ],\n            \"id\": 2541676340,\n            \"id_str\": \"2541676340\",\n            \"name\": \"Mailden\",\n            \"screen_name\": \"mailden_fr\"\n          }\n        ]\n      },\n      \"attachment\": {\n        \"type\": \"media\",\n        \"media\": {\n          \"indices\": [\n            64,\n            87\n          ],\n          \"display_url\": \"pic.twitter.com/xD4h9OYaAH\",\n          \"expanded_url\": \"https://twitter.com/messages/media/1047399250294923268\",\n          \"url\": \"https://t.co/xD4h9OYaAH\",\n          \"id\": 1047399235547811840,\n          \"id_str\": \"1047399235547811840\",\n          \"media_url\": \"https://pbs.twimg.com/dm_gif_preview/1047399235547811840/QpKOUcJTPnY5kjBEaG5dV-TzjfRZl1nSS4ewujhBRNXaNRRr_T.jpg\",\n          \"media_url_https\": \"https://pbs.twimg.com/dm_gif_preview/1047399235547811840/QpKOUcJTPnY5kjBEaG5dV-TzjfRZl1nSS4ewujhBRNXaNRRr_T.jpg\",\n          \"source_status_id\": 0,\n          \"source_status_id_str\": \"\",\n          \"type\": \"animated_gif\",\n          \"sizes\": {\n            \"thumb\": {\n              \"w\": 150,\n              \"h\": 150,\n              \"resize\": \"crop\"\n            },\n            \"large\": {\n              \"w\": 538,\n              \"h\": 438,\n              \"resize\": \"fit\"\n            },\n            \"medium\": {\n              \"w\": 538,\n              \"h\": 438,\n              \"resize\": \"fit\"\n            },\n            \"small\": {\n              \"w\": 538,\n              \"h\": 438,\n              \"resize\": \"fit\"\n            }\n          },\n          \"video_info\": {\n            \"aspect_ratio\": [\n              269,\n              219\n            ],\n            \"duration_millis\": 0,\n            \"variants\": [\n              {\n                \"content_type\": \"video/mp4\",\n                \"bitrate\": 0,\n                \"url\": \"https://video.twimg.com/dm_gif/1047399235547811840/QpKOUcJTPnY5kjBEaG5dV-TzjfRZl1nSS4ewujhBRNXaNRRr_T.mp4\"\n              }\n            ]\n          }\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "devtools/fixtures/twitter/dm.json",
    "content": "{\n  \"created_timestamp\": \"1537975117295\",\n  \"id\": \"1044969496828674052\",\n  \"type\": \"message_create\",\n  \"message_create\": {\n    \"sender_id\": \"2353377708\",\n    \"target\": {\n      \"recipient_id\": \"53871000\"\n    },\n    \"message_data\": {\n      \"text\": \"et aucun DM depuis 30 jours sur ce compte\",\n      \"entitites\": null,\n      \"attachment\": {\n        \"type\": \"\",\n        \"media\": {\n          \"indices\": [\n            0,\n            0\n          ],\n          \"display_url\": \"\",\n          \"expanded_url\": \"\",\n          \"url\": \"\",\n          \"id\": 0,\n          \"id_str\": \"\",\n          \"media_url\": \"\",\n          \"media_url_https\": \"\",\n          \"source_status_id\": 0,\n          \"source_status_id_str\": \"\",\n          \"type\": \"\",\n          \"sizes\": {\n            \"thumb\": {\n              \"w\": 0,\n              \"h\": 0,\n              \"resize\": \"\"\n            },\n            \"large\": {\n              \"w\": 0,\n              \"h\": 0,\n              \"resize\": \"\"\n            },\n            \"medium\": {\n              \"w\": 0,\n              \"h\": 0,\n              \"resize\": \"\"\n            },\n            \"small\": {\n              \"w\": 0,\n              \"h\": 0,\n              \"resize\": \"\"\n            }\n          },\n          \"video_info\": {\n            \"aspect_ratio\": [\n              0,\n              0\n            ],\n            \"duration_millis\": 0,\n            \"variants\": null\n          }\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "devtools/fixtures/vcard/multi.vcf",
    "content": "BEGIN:VCARD\nVERSION:3.0\nFN:Patrice Tran\nN:Tran;Patrice;;;\nX-EVOLUTION-FILE-AS:Tran\\, Patrice\nEMAIL;TYPE=OTHER:patrice@caliopen.org\nUID:pas-id-58AF0A5A00000000\nREV:2017-02-23T16:14:18Z(1)\nEND:VCARD\n\nBEGIN:VCARD\nVERSION:3.0\nFN:Lison Ferez\nN:Ferez;Lison;;;\nX-EVOLUTION-FILE-AS:Ferez\\, Lison\nEMAIL;TYPE=OTHER:lison@caliopen.org\nUID:pas-id-58AF0A5A00000001\nREV:2017-02-23T16:14:18Z(1)\nEND:VCARD\n\n"
  },
  {
    "path": "devtools/fixtures/vcard/rfc2425-1.vcard",
    "content": "cn:Babs Jensen\ncn:Barbara J Jensen\nsn:Jensen\nemail:babs@umich.edu\nphone:+1 313 747-4454\nx-id:1234567890\n"
  },
  {
    "path": "devtools/fixtures/vcard/rfc2425-2.vcard",
    "content": "begin:VCARD\nsource:ldap://cn=bjorn%20Jensen, o=university%20of%20Michigan, c=US\nname:Bjorn Jensen\nfn:Bj=F8rn Jensen\nn:Jensen;Bj=F8rn\nemail;type=internet:bjorn@umich.edu\ntel;type=work,voice,msg:+1 313 747-4454\nkey;type=x509;encoding=B:dGhpcyBjb3VsZCBiZSAKbXkgY2VydGlmaWNhdGUK\nend:VCARD\n"
  },
  {
    "path": "devtools/fixtures/vcard/rfc2425-3.vcard",
    "content": "begin:vcard\nsource:ldap://cn=Meister%20Berger,o=Universitaet%20Goerlitz,c=DE\nname:Meister Berger\nfn:Meister Berger\nn:Berger;Meister\nbday;value=date:1963-09-21\no:Universit=E6t G=F6rlitz\ntitle:Mayor\ntitle;language=de;value=text:Burgermeister\nnote:The Mayor of the great city of\n  Goerlitz in the great country of Germany.\nemail;internet:mb@goerlitz.de\nhome.tel;type=fax,voice,msg:+49 3581 123456\nhome.label:Hufenshlagel 1234\\n\n 02828 Goerlitz\\n\n Deutschland\nkey;type=X509;encoding=b:MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhvcNAQEEBQ\n AwdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENvbW11bmljYXRpb25zI\n ENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZvcm1hdGlvbiBTeXN0ZW1zMRwwGgYDVQQD\n ExNyb290Y2EubmV0c2NhcGUuY29tMB4XDTk3MDYwNjE5NDc1OVoXDTk3MTIwMzE5NDc\n 1OVowgYkxCzAJBgNVBAYTAlVTMSYwJAYDVQQKEx1OZXRzY2FwZSBDb21tdW5pY2F0aW\n 9ucyBDb3JwLjEYMBYGA1UEAxMPVGltb3RoeSBBIEhvd2VzMSEwHwYJKoZIhvcNAQkBF\n hJob3dlc0BuZXRzY2FwZS5jb20xFTATBgoJkiaJk/IsZAEBEwVob3dlczBcMA0GCSqG\n SIb3DQEBAQUAA0sAMEgCQQC0JZf6wkg8pLMXHHCUvMfL5H6zjSk4vTTXZpYyrdN2dXc\n oX49LKiOmgeJSzoiFKHtLOIboyludF90CgqcxtwKnAgMBAAGjNjA0MBEGCWCGSAGG+E\n IBAQQEAwIAoDAfBgNVHSMEGDAWgBT84FToB/GV3jr3mcau+hUMbsQukjANBgkqhkiG9\n w0BAQQFAAOBgQBexv7o7mi3PLXadkmNP9LcIPmx93HGp0Kgyx1jIVMyNgsemeAwBM+M\n SlhMfcpbTrONwNjZYW8vJDSoi//yrZlVt9bJbs7MNYZVsyF1unsqaln4/vy6Uawfg8V\n UMk1U7jt8LYpo4YULU7UZHPYVUaSgVttImOHZIKi4hlPXBOhcUQ==\nend:vcard\n"
  },
  {
    "path": "devtools/fixtures/vcard/rfc2426-1.vcard",
    "content": "BEGIN:VCARD\nFN:Rene van der Harten\nN:van der Harten;Rene;J.;Sir;R.D.O.N.\nSORT-STRING:Harten\nEND:VCARD\n"
  },
  {
    "path": "devtools/fixtures/vcard/rfc2426-2.vcard",
    "content": "BEGIN:VCARD\nFN:Robert Pau Shou Chang\nN:Pau;Shou Chang;Robert\nSORT-STRING:Pau\nEND:VCARD\n"
  },
  {
    "path": "devtools/fixtures/vcard/rfc2426-3.vcard",
    "content": "BEGIN:VCARD\nFN:Osamu Koura\nN:Koura;Osamu\nSORT-STRING:Koura\nEND:VCARD\n"
  },
  {
    "path": "devtools/fixtures/vcard/rfc2426-4.vcard",
    "content": "BEGIN:VCARD\nFN:Oscar del Pozo\nN:del Pozo Triscon;Oscar\nSORT-STRING:Pozo\nEND:VCARD\n"
  },
  {
    "path": "devtools/fixtures/vcard/rfc2426-5.vcard",
    "content": "BEGIN:VCARD\nFN:Chistine d'Aboville\nN:d'Aboville;Christine\nSORT-STRING:Aboville\nEND:VCARD"
  },
  {
    "path": "devtools/gen-swagger-spec.sh",
    "content": "#!/bin/bash\n\n# Generate swagger specifications from JSON schema definitions\n\nset -e\nPROJECT_DIRECTORY=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )/..\" && pwd )\"\n\nDOC_DIR=${PROJECT_DIRECTORY}/src/backend/doc/api\nDEST_DIR=${PROJECT_DIRECTORY}/src/backend/configs\nSRC_DIR=${PROJECT_DIRECTORY}/src/backend/defs/rest-api\nCMD=\"npx @redocly/cli bundle ${SRC_DIR}/swagger-root.yaml -o ${DEST_DIR}/swagger.json\"\n\n${CMD}\n\nnpx @redocly/cli build-docs --disableGoogleFont ${DEST_DIR}/swagger.json -o ${DOC_DIR}/swagger.html\n\nset +e\n"
  },
  {
    "path": "devtools/init.sh",
    "content": "#!/bin/bash\n\nsudo sysctl -w vm.max_map_count=262144\ndocker-compose build --pull\n\nset -e\n\n# init db & co\ndocker-compose run cli -h\ndocker-compose up -d objectstore\nsleep 60\ndocker-compose run cli setup\ndocker-compose run cli create_user -e admin -p 123456\ndocker-compose run cli create_user -e dev -p 123456\ndocker-compose run cli import -e dev@caliopen.local -f mbox -p devtools/fixtures/mbox/dev@caliopen.local\ndocker-compose up -d proxyapi lmtpd identitypoller\n"
  },
  {
    "path": "devtools/kubernetes/README.md",
    "content": "# Install dev env using minikube\n\n## Pre-requisites\n\n* If you are **NOT** running Linux, you need to install [VirtualBox](https://www.virtualbox.org/wiki/Downloads)\n* If you are running Linux, you can **either**:\n\t* Use VirtualBox\n\t* Run the stack locally with [Docker](https://docs.docker.com/install/).\n* [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) & [Minikube](https://github.com/kubernetes/minikube/releases)\n\n## WARNING\n\nBefore starting the installation, make sure to read this carefully.\n\nThere is currently a bug with the Kubernetes dashboard **when running the stack locally with docker**: if you change networks while Kubernetes is running it might go into a crash loop and use 100% of your cpu after some time. To fix that you can manually delete the pod so that it is recreated:\n\n```sh\nkubectl delete pod $DASHBOARD_POD_NAME --namespace kube-system\n\n```\n\nYou can find your pod's name with:\n\n```sh\nkubectl get pods --namespace kube-system\n```\nThe pod's name should start with \"kubernetes-dashboard\" followed by a number.\n\nYou can also disable and re-enable the dashboard addon with minikube:\n\n```\nminikube addons [ disable | enable ] dashboard\n```\n\nMake sure to stop minikube before going offline to avoid any problems.\n\n## Deployment\n\nTo start the deployment:\n\n```sh\n./deploy-minikube.sh\n```\n\nIf it fails during the deployment but minikube started you can skip that part with the flag skip-setup:\n\n```sh\n ./deploy-minikube.sh skip-setup\n```\n\n###### During the installation:\n\nYou can select for which kind of development your deployment is:\n\n* Go\n* Python\n* Frontend\n\nRefer to [this file](../../doc/install/minikube-local-development.md) for informations related to local development\n\n You can individually select them. Based on your choices some services are not deployed so that you can plug in your local development environment.\n\n>_For example, you want to do some frontend development, answer [y] for that choice only, then refer to [frontend native installation](./FIXME)._\n\n\n##### NOTE:\n\nTo deploy the stack with docker you'll need to run the script as sudo. See [kubernetes/kubeadm#57](https://github.com/kubernetes/kubeadm/issues/57).\n\n\n## Deleting deployment\n\nIn case you are deploying the stack without a driver (locally) and the command fails or if you just want to restart the deployment from zero, clean the stack beforehand:\n\n```sh\nsudo ./clean-minikube.sh\n```\n\n##### NOTE:\n\nThis command resets kubeadm, so be careful if you have any other kubernetes clusters running on your machine (no need to worry about kube configs).\n\n## Usage\n\n_**Starting the cluster:**_\n\n```sudo minikube start```\n\n_**Stopping the cluster:**_\n\n```sudo minikube stop```\n\n## Additional info\n\n* [**Kubectl**](https://kubernetes.io/docs/reference/kubectl/overview/)\n* [**Minikube**](https://github.com/kubernetes/minikube)\n"
  },
  {
    "path": "devtools/kubernetes/clean-minikube.sh",
    "content": "#!/bin/bash\n\nminikube stop\nminikube delete\nkubeadm reset\n"
  },
  {
    "path": "devtools/kubernetes/configs/dns-config.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: coredns\n  namespace: kube-system\ndata:\n  Corefile: |\n    .:53 {\n        errors\n        health\n        kubernetes cluster.local in-addr.arpa ip6.arpa {\n           pods insecure\n           upstream\n           fallthrough in-addr.arpa ip6.arpa\n        }\n        prometheus :9153\n        proxy . 155.133.128.65 155.133.128.67\n        cache 30\n        reload\n    }\n"
  },
  {
    "path": "devtools/kubernetes/deploy-minikube.sh",
    "content": "#!/bin/bash\nset -e\n\nOS=$(uname -s)\nKUBE_DRIVER=\"none\"\nBACKEND_CONF_DIR=\"../../src/backend/configs\"\nMINIO_CONF_DIR=${BACKEND_CONF_DIR}\"/minio\"\nSKIP_SETUP=$1\n\nwait_for_pods(){\n\techo\n\techo \"Waiting for pods to be available:\"\n\techo \"---------------------------\"\n\twhile read line; do\n\t\tkubectl rollout status deployment ${line}\n\tdone < <(kubectl get deployments | awk 'FNR > 1 { print $1 }')\n}\n\n#For OS other than Linux, use Virtualbox\ncheck_os(){\n\tif [[ ${OS} != 'Linux' ]]\n\tthen\n\t\tKUBE_DRIVER=\"virtualbox\"\n\telse\n\t\twhile true; do\n\t\t    read -p \"On Linux Minikube can be run on Virtualbox or locally with Docker, which one should be used? (v/d) \" dv\n\t\t    case ${dv} in\n\t\t        [Dd]* ) break;;\n\t\t        [Vv]* ) KUBE_DRIVER=\"virtualbox\"; break;;\n\t\t        * ) echo \"Please answer Docker(d) or Virtualbox(v).\";;\n\t\t    esac\n\t\tdone\n\tfi\n}\n\n#If the user is running the stack with virtualbox he does not need root\ncheck_driver_and_root(){\n\tif [[ ${KUBE_DRIVER} = \"virtualbox\" ]]\n\tthen\n\t\tcommand -v vboxmanage >/dev/null 2>&1 || { echo \"Virtualbox required but not installed. Aborting.\" >&2; exit 1; }\n\t\tif [ \"$EUID\" -eq 0 ]\n\t\tthen\n\t\t\techo \"  ---------------------------------------------------------------------\"\n\t\t\techo \"  Minikube doesn't need to be run as root to deploy Kubernetes on a VM.\"\n\t\t\techo \"  ---------------------------------------------------------------------\"\n\t\tfi\n\telse\n\t\tcommand -v docker >/dev/null 2>&1 || { echo \"Docker required but not installed. Aborting.\" >&2; exit 1; }\n\t\tif [ \"$EUID\" -ne 0 ]\n\t\tthen\n\t\t\techo \"Minikube needs to be run as root for local kubernetes deployment.\"\n\t\t\texit 1\n\t\tfi\n\tfi\n}\n\n#Check vm/max_map_count value for elasticsearch\ncheck_es(){\n\tif [[ ${KUBE_DRIVER} = \"none\" ]]\n\tthen\n\t\tif [[  $(sysctl -n vm.max_map_count) -lt 262144 ]]\n\t\tthen\n\t\t\twhile true; do\n\t\t\t    read -p \"Running elasticsearch needs modification of kernel parameter vm.max_map_count, confirm action.(y/n) \" yn\n\t\t\t    case ${yn} in\n\t\t\t        [Yy]* ) sysctl -w vm.max_map_count=262144; break;;\n\t\t\t        [Nn]* ) echo \"You will need to manually modify vm.max_map_count to 262144 at least.\"; break;;\n\t\t\t        * ) echo \"Please answer yes(y) or no(n).\";;\n\t\t\t    esac\n\t\t\tdone\n\t\tfi\n\telse\n\t\tminikube ssh \"sudo sysctl -w vm.max_map_count=262144\"\n\tfi\n}\n\ncreate_configmaps(){\n\techo\n\techo \"Configmap and secret creation\"\n\techo \"-----------------------------\"\n\tkubectl create configmap caliopen-config --from-file=${BACKEND_CONF_DIR}\n\tkubectl create configmap minio-config --from-file=${MINIO_CONF_DIR}\n\tkubectl apply -f configs/dns-config.yaml\n}\n\ncreate_services(){\n\techo\n\techo \"Service creation:\"\n\techo \"-----------------\"\n\tkubectl apply -f services/cassandra-service.yaml \\\n\t-f services/elasticsearch-service.yaml \\\n\t-f services/external-go-service.yaml \\\n\t-f services/external-python-service.yaml \\\n\t-f services/minio-service.yaml \\\n\t-f services/nats-service.yaml \\\n\t-f services/redis-service.yaml \\\n\t-f services/smtp-service.yaml\n}\n\ncreate_pvc(){\n\techo\n\techo \"Persistent storage creation:\"\n\techo \"----------------------------\"\n\tkubectl apply  -f ./volumeclaims\n}\n\ncreate_basic_deployments(){\n\techo\n\techo \"Basic deployments creation:\"\n\techo \"---------------------------\"\n\tkubectl apply -f deployments/cassandra-deployment.yaml \\\n\t-f deployments/redis-deployment.yaml \\\n\t-f deployments/elasticsearch-deployment.yaml \\\n\t-f deployments/minio-deployment.yaml \\\n\t-f deployments/nats-deployment.yaml \\\n\t-f deployments/smtp-deployment.yaml\n}\n\nsetup_storage(){\n\techo\n\techo \"Setting up storage:\"\n\techo \"-------------------\"\n\techo -n \"Wait.\"\n\tkubectl create -f jobs/cli-setup.yaml >/dev/null 2>&1\n\twhile [ $(kubectl get jobs | awk '/cli-setup/ { print $3 }') != \"1\" ]\n\tdo\n\t\techo -n \".\"\n\t\tsleep 1\n\tdone\n\tkubectl delete -f jobs/cli-setup.yaml >/dev/null 2>&1\n}\n\ncreate_admin_user(){\n\techo\n\techo \"Creating admin:\"\n\techo \"---------------\"\n\techo -n \"Wait.\"\n\tkubectl create -f jobs/cli-admin-creation.yaml > /dev/null 2>&1\n\twhile [ $(kubectl get jobs | awk '/cli-admin/ { print $3 }') != \"1\" ]\n\tdo\n\t\techo -n \".\"\n\t\tsleep 1\n\tdone\n}\n\ncreate_dev_user(){\n\techo\n\techo \"Creating dev user:\"\n\techo \"------------------\"\n\techo -n \"Wait.\"\n\tkubectl create -f jobs/cli-dev-creation.yaml >/dev/null 2>&1\n\twhile [ $(kubectl get jobs | awk '/cli-dev/ { print $3 }') != \"1\" ]\n\tdo\n\t\techo -n \".\"\n\t\tsleep 1\n\tdone\n}\n\n#Importing mail has failed in the past, to make sure it does not get stuck we delete in case of error\nimport_mail(){\n\techo\n\techo \"Importing sample mail:\"\n\techo \"-----------------------------\"\n\techo -n \"Wait.\"\n\tkubectl create -f jobs/cli-mail-import.yaml >/dev/null 2>&1\n\tres=$(kubectl get pods | awk '/cli-import/ { print $3 }' | awk 'FNR == 1')\n\twhile [ ${res} != \"Error\" ] && [ ${res} != \"Completed\" ]\n\tdo\n\t\techo -n \".\"\n\t\tsleep 1\n\t\tres=$(kubectl get pods | awk '/cli-import/ { print $3 }' | awk 'FNR == 1')\n\tdone\n\tif [ ${res} == \"Error\" ]\n\tthen\n\t\techo \"Error importing mails. Ignoring.\"\n\tfi\n\tkubectl delete -f jobs/cli-mail-import.yaml >/dev/null 2>&1\n}\n\ncreate_go_deployments(){\n\techo\n\techo \"GO applications deployment:\"\n\techo \"---------------------------\"\n\tkubectl delete svc apiv2 lmtpd\n\tkubectl apply -f services/api-service.yaml \\\n\t-f services/lmtpd-service.yaml\n\tkubectl apply -f deployments/lmtpd-deployment.yaml \\\n\t-f deployments/apiv2-deployment.yaml \\\n\t-f deployments/identity-poller-deployment.yaml \\\n\t-f deployments/imap-worker-deployment.yaml\n}\n\ncreate_python_deployments(){\n\techo\n\techo \"Python applications deployment:\"\n\techo \"---------------------------\"\n\tkubectl delete svc apiv1\n\tkubectl apply -f services/apiv1-service.yaml\n\tkubectl apply -f deployments/mq-worker-deployment.yaml \\\n\t-f deployments/apiv1-deployment.yaml\n}\n\ncreate_frontend_deployment(){\n\techo\n\techo \"Web Client deployment:\"\n\techo \"---------------------------\"\n\tkubectl apply -f services/frontend-service.yaml\n\tkubectl apply -f deployments/frontend-deployment.yaml\n\n}\n\nconditional_deployment(){\nwhile true; do\n\techo\n    read -p \"Is this deployment for ${1} development? (y/n) \" yn\n    case $yn in\n        [Yy]* ) break;;\n        [Nn]* ) ${2}; break;;\n        * ) echo \"Please answer Yes(y) or No(n).\";;\n    esac\ndone\n}\n\nchown_kube_minikube_directories(){\n#This allows the user to use kubectl without sudo\nif [[ ${KUBE_DRIVER} = \"none\" ]]\nthen\n\tchown -R ${SUDO_USER} $HOME/.kube\n\tchgrp -R ${SUDO_USER} $HOME/.kube\n\n\tchown -R ${SUDO_USER} $HOME/.minikube\n\tchgrp -R ${SUDO_USER} $HOME/.minikube\nfi\n}\n\n############################################################\nif [[ $SKIP_SETUP != \"skip-setup\" ]]\nthen\n\tcommand -v minikube >/dev/null 2>&1 || { echo \"Minikube required but not installed. Aborting.\" >&2; exit 1; }\n\n\tcheck_os\n\tcheck_driver_and_root\n\tminikube start --memory 6144 --cpus=2 --vm-driver=${KUBE_DRIVER} --extra-config=apiserver.service-node-port-range=1-35000\n\t#--dns-domain=dev.caliopen.org NOT WORKING\n\t# --apiserver-ips 127.0.0.1 --apiserver-name localhost\n\t# nodePortAddresses: [127.0.0.0/8] on kube-proxy configmap TO LIMIT EXTERNAL ACCESS\n\tcheck_es\n\tcreate_configmaps\nfi\ncreate_services\ncreate_pvc\ncreate_basic_deployments\n#Wait for storage to be ready\nwait_for_pods\nsetup_storage\ncreate_admin_user || true\ncreate_dev_user || true\nimport_mail\nconditional_deployment \"GO\" \"create_go_deployments\"\nconditional_deployment \"Python\" \"create_python_deployments\"\nconditional_deployment \"Frontend\" \"create_frontend_deployment\"\n#Wait for Webclient to be available\nwait_for_pods\n\necho\necho \"List of available services:\"\necho\nminikube service list\nchown_kube_minikube_directories\n"
  },
  {
    "path": "devtools/kubernetes/deployments/apiv1-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: apiv1\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: apiv1\n  #strategy:\n  #  type: Recreate / RollingUpdate\n  #    maxUnavailable:\n  template:\n    metadata:\n      labels:\n        type: api\n        tier: backend\n        app: apiv1\n    spec:\n      containers:\n      - image: public-registry.caliopen.org/caliopen_apiv1:develop\n        imagePullPolicy: Always\n        name: apiv1\n        ports:\n        - containerPort: 6543\n        volumeMounts:\n        - name: config-volume\n          mountPath: /etc/caliopen\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: apiv1.ini\n            path: apiv1.ini\n          - key: caliopen.yaml\n            path: caliopen.yaml\n          - key: swagger.json\n            path: swagger.json\n"
  },
  {
    "path": "devtools/kubernetes/deployments/apiv2-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: apiv2\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: apiv2\n  #strategy:\n  #  type: Recreate / RollingUpdate\n  #    maxUnavailable:\n  template:\n    metadata:\n      labels:\n        type: api\n        tier: backend\n        app: apiv2\n    spec:\n      containers:\n      - image: public-registry.caliopen.org/caliopen_apiv2:develop\n        imagePullPolicy: Always\n        name: apiv2\n        ports:\n        - containerPort: 31415\n        volumeMounts:\n        - name: config-volume\n          mountPath: /etc/caliopen\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: apiv2.yaml\n            path: apiv2.yaml\n          - key: swagger.json\n            path: swagger.json\n"
  },
  {
    "path": "devtools/kubernetes/deployments/cassandra-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: cassandra\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: cassandra\n  #strategy:\n  #  type: Recreate / RollingUpdate\n  #    maxUnavailable:\n  template:\n    metadata:\n      labels:\n        type: storage\n        tier: backend\n        app: cassandra\n    spec:\n      containers:\n      - command:\n        - /docker-entrypoint.py\n        - --memory\n        #TODO: ADD MORE MEMORY\n        - 512M\n        image: scylladb/scylla\n        name: cassandra\n        resources:\n          requests:\n            memory: \"1024Mi\"\n        ports:\n        - containerPort: 9042\n          name: cql\n        - containerPort: 9160\n        - containerPort: 7000\n          name: intra-node\n        volumeMounts:\n        - mountPath: /var/lib/scylla\n          name: dev-db\n      volumes:\n      - name: dev-db\n        persistentVolumeClaim:\n          claimName: dev-db\n"
  },
  {
    "path": "devtools/kubernetes/deployments/elasticsearch-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: elasticsearch\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: elasticsearch\n  template:\n    metadata:\n      labels:\n        type: index\n        tier: backend\n        app: elasticsearch\n    spec:\n      containers:\n      - image: docker.elastic.co/elasticsearch/elasticsearch:5.4.1\n        name: elasticsearch\n        ports:\n        - containerPort: 9200\n        env:\n        - name: xpack.security.enabled\n          value: \"false\"\n        - name: xpack.monitoring.enabled\n          value: \"false\"\n        - name: ES_JAVA_OPTS\n          value: \"-Xms512M -Xmx512M\"\n        volumeMounts:\n        - mountPath: /usr/share/elasticsearch/data\n          name: dev-index\n      volumes:\n      - name: dev-index\n        persistentVolumeClaim:\n          claimName: dev-index\n"
  },
  {
    "path": "devtools/kubernetes/deployments/frontend-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: frontend\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: frontend\n  #strategy:\n  #  type: Recreate / RollingUpdate\n  #    maxUnavailable:\n  template:\n    metadata:\n      labels:\n        type: frontend\n        tier: frontend\n        app: frontend\n    spec:\n      containers:\n      - image: public-registry.caliopen.org/caliopen_frontend:develop\n        imagePullPolicy: Always\n        name: frontend\n        ports:\n        - containerPort: 4000\n        env:\n        - name: CALIOPEN_API_HOSTNAME\n          value: \"api\"\n        - name: CALIOPEN_API_PROTOCOL\n          value: \"http\"\n        - name: CALIOPEN_API_PORT\n          value: \"31415\"\n"
  },
  {
    "path": "devtools/kubernetes/deployments/identity-poller-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: identity-poller\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: identity-poller\n  template:\n    metadata:\n      labels:\n        type: identity-poller\n        tier: remote-identities\n        app: identity-poller\n    spec:\n      containers:\n      - image: public-registry.caliopen.org/caliopen_identity_poller:develop\n        name: identity-poller\n        volumeMounts:\n        - name: config-volume\n          mountPath: /etc/caliopen\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: idpoller.yaml\n            path: idpoller.yaml\n"
  },
  {
    "path": "devtools/kubernetes/deployments/imap-worker-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: imap-worker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: imap-worker\n  template:\n    metadata:\n      labels:\n        type: imap-worker\n        tier: remote-identities\n        app: imap-worker\n    spec:\n      containers:\n      - image: public-registry.caliopen.org/caliopen_imap_worker:develop\n        name: imap-worker\n        volumeMounts:\n        - name: config-volume\n          mountPath: /etc/caliopen\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: imapworker.yaml\n            path: imapworker.yaml\n"
  },
  {
    "path": "devtools/kubernetes/deployments/lmtpd-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: lmtpd\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: lmtpd\n  #strategy:\n  #  type: Recreate / RollingUpdate\n  #    maxUnavailable:\n  template:\n    metadata:\n      labels:\n        type: lmtp\n        tier: backend\n        app: lmtpd\n    spec:\n      containers:\n      - image: public-registry.caliopen.org/caliopen_lmtpd:develop\n        imagePullPolicy: Always\n        name: lmtpd\n        ports:\n        - containerPort: 2525\n        volumeMounts:\n        - name: config-volume\n          mountPath: /etc/caliopen\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: lmtp.yaml\n            path: lmtp.yaml\n"
  },
  {
    "path": "devtools/kubernetes/deployments/message-handler-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: mq-worker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: mq-worker\n  template:\n    metadata:\n      labels:\n        type: mq-worker\n        tier: backend\n        app: mq-worker\n    spec:\n      containers:\n      - image: public-registry.caliopen.org/caliopen_mq_worker:develop\n        imagePullPolicy: Always\n        name: mq-worker\n        volumeMounts:\n        - name: config-volume\n          mountPath: /etc/caliopen\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: caliopen.yaml\n            path: caliopen.yaml\n"
  },
  {
    "path": "devtools/kubernetes/deployments/minio-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: minio\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: minio\n  template:\n    metadata:\n      labels:\n        type: storage\n        tier: backend\n        app: minio\n    spec:\n      containers:\n      - image: minio/minio\n        name: minio\n        env:\n        - name: MINIO_ACCESS_KEY\n          value: \"CALIOPEN_ACCESS_KEY_\"\n        - name: MINIO_SECRET_KEY\n          value: \"CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD\"\n        args:\n          - server\n          - --address\n          - :9090\n          - -C\n          - /configs/minio\n          - /export\n        ports:\n        - containerPort: 9090\n        volumeMounts:\n        - mountPath: /export\n          name: dev-store\n      volumes:\n      - name: dev-store\n        persistentVolumeClaim:\n          claimName: dev-store\n      - name: config-volume\n        configMap:\n          name: minio-config\n          items:\n          - key: config.json\n            path: config.json\n"
  },
  {
    "path": "devtools/kubernetes/deployments/nats-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nats\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: nats\n  template:\n    metadata:\n      labels:\n        type: nats\n        tier: backend\n        app: nats\n    spec:\n      containers:\n      - image: nats:0.9.6\n        name: smtp-server\n        ports:\n        - containerPort: 4222\n"
  },
  {
    "path": "devtools/kubernetes/deployments/redis-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: redis\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: redis\n  template:\n    metadata:\n      labels:\n        type: storage\n        tier: backend\n        app: redis\n    spec:\n      containers:\n      - image: redis\n        name: redis\n        ports:\n        - containerPort: 6379\n"
  },
  {
    "path": "devtools/kubernetes/deployments/smtp-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: smtp-server\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: inbucket\n  template:\n    metadata:\n      labels:\n        type: smtp\n        tier: backend\n        app: inbucket\n    spec:\n      containers:\n      - image: jhillyerd/inbucket\n        name: smtp-server\n        ports:\n        - containerPort: 8888\n        - containerPort: 2500\n"
  },
  {
    "path": "devtools/kubernetes/jobs/cli-admin-creation.yaml",
    "content": "apiVersion: batch/v1\nkind: Job\nmetadata:\n  name: cli-admin\nspec:\n  backoffLimit: 3\n  template:\n    spec:\n      restartPolicy: \"OnFailure\"\n      containers:\n      - image: public-registry.caliopen.org/caliopen_cli\n        name: cli-admin\n        args: [\"create_user\", \"-e\", \"admin\", \"-p\", \"123456\"]\n        env:\n        - name: CQLENG_ALLOW_SCHEMA_MANAGEMENT\n          value: \"1\"\n        volumeMounts:\n        - name: config-volume\n          mountPath: /etc/caliopen\n      imagePullSecrets:\n        - name: registrypullsecret\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: caliopen.yaml\n            path: caliopen.yaml"
  },
  {
    "path": "devtools/kubernetes/jobs/cli-dev-creation.yaml",
    "content": "apiVersion: batch/v1\nkind: Job\nmetadata:\n  name: cli-dev\nspec:\n  backoffLimit: 3\n  template:\n    spec:\n      restartPolicy: \"OnFailure\"\n      containers:\n      - image: public-registry.caliopen.org/caliopen_cli\n        name: cli-dev\n        args: [\"create_user\", \"-e\", \"dev\", \"-p\", \"123456\"]\n        env:\n        - name: CQLENG_ALLOW_SCHEMA_MANAGEMENT\n          value: \"1\"\n        volumeMounts:\n        - name: config-volume\n          mountPath: /etc/caliopen\n      imagePullSecrets:\n        - name: registrypullsecret\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: caliopen.yaml\n            path: caliopen.yaml"
  },
  {
    "path": "devtools/kubernetes/jobs/cli-mail-import.yaml",
    "content": "apiVersion: batch/v1\nkind: Job\nmetadata:\n  name: cli-import\nspec:\n  backoffLimit: 3\n  template:\n    spec:\n      restartPolicy: \"OnFailure\"\n      initContainers:\n        - name: git-clone\n          image: alpine/git\n          args:\n            - clone\n            - --single-branch\n            - https://github.com/CaliOpen/Caliopen.git\n            - /caliopen\n          securityContext:\n            runAsUser: 1\n            allowPrivilegeEscalation: false\n            readOnlyRootFilesystem: true\n          volumeMounts:\n          - name: caliopen-repo\n            mountPath: /caliopen\n      containers:\n      - image: public-registry.caliopen.org/caliopen_cli\n        name: cli-import\n        args: [\"import\", \"-e\", \"dev@caliopen.local\", \"-f\", \"mbox\", \"-p\", \"/caliopen/devtools/fixtures/mbox/dev@caliopen.local\"]\n        env:\n        - name: CQLENG_ALLOW_SCHEMA_MANAGEMENT\n          value: \"1\"\n        volumeMounts:\n        - name: caliopen-repo\n          mountPath: /caliopen\n        - name: config-volume\n          mountPath: /etc/caliopen\n      imagePullSecrets:\n        - name: registrypullsecret\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: caliopen.yaml\n            path: caliopen.yaml\n      - name: caliopen-repo\n        emptyDir: {}"
  },
  {
    "path": "devtools/kubernetes/jobs/cli-setup.yaml",
    "content": "apiVersion: batch/v1\nkind: Job\nmetadata:\n  name: cli-setup\nspec:\n  backoffLimit: 3\n  template:\n    spec:\n      restartPolicy: \"OnFailure\"\n      containers:\n      - name: cli-setup\n        image: public-registry.caliopen.org/caliopen_cli\n        args: [\"setup\"]\n        env:\n        - name: CQLENG_ALLOW_SCHEMA_MANAGEMENT\n          value: \"1\"\n        volumeMounts:\n        - name: config-volume\n          mountPath: /etc/caliopen\n      imagePullSecrets:\n        - name: registrypullsecret\n      volumes:\n      - name: config-volume\n        configMap:\n          name: caliopen-config\n          items:\n          - key: caliopen.yaml\n            path: caliopen.yaml"
  },
  {
    "path": "devtools/kubernetes/services/apiv1-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: apiv1\nspec:\n  type: NodePort\n  selector:\n    app: apiv1\n  ports:\n  - name: \"apiv1\"\n    port: 6543\n    targetPort: 6543\n    nodePort: 6543\n"
  },
  {
    "path": "devtools/kubernetes/services/apiv2-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: apiv2\nspec:\n  type: NodePort\n  #nodePort: 6543\n  selector:\n    app: apiv2\n  ports:\n  - name: \"apiv2\"\n    port: 31415\n    targetPort: 31415\n    nodePort: 31415\n"
  },
  {
    "path": "devtools/kubernetes/services/cassandra-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: cassandra\nspec:\n  type: NodePort\n  selector:\n    app: cassandra\n  ports:\n  - name: \"9042\"\n    port: 9042\n    targetPort: 9042\n    nodePort: 9042\n  - name: \"9160\"\n    port: 9160\n    targetPort: 9160\n    nodePort: 9160\n  - name: \"7000\"\n    port: 7000\n    targetPort: 7000\n    nodePort: 7000\n"
  },
  {
    "path": "devtools/kubernetes/services/elasticsearch-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: elasticsearch\nspec:\n  type: NodePort\n  selector:\n    app: elasticsearch\n  ports:\n  - name: \"elasticsearch\"\n    port: 9200\n    targetPort: 9200\n    nodePort: 9200\n"
  },
  {
    "path": "devtools/kubernetes/services/external-go-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: apiv2\nspec:\n  ports:\n    - name: apiv2\n      protocol: \"TCP\"\n      port: 31415\n  selector: {}\n---\n\napiVersion: v1\nkind: Service\nmetadata:\n  name: lmtpd\nspec:\n  ports:\n    - name: lmtpd\n      protocol: \"TCP\"\n      port: 2525\n  selector: {}\n---\n\nkind: Endpoints\napiVersion: v1\nmetadata:\n  name: apiv2\nsubsets:\n  - addresses:\n      - ip: 172.17.0.1\n    ports:\n      - port: 31415\n        name: \"apiv2\"\n---\n\nkind: Endpoints\napiVersion: v1\nmetadata:\n  name: lmtpd\nsubsets:\n  - addresses:\n      - ip: 172.17.0.1\n    ports:\n      - port: 2525\n        name: \"lmtpd\"\n"
  },
  {
    "path": "devtools/kubernetes/services/external-python-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: apiv1\nspec:\n  ports:\n    - name: apiv1\n      protocol: \"TCP\"\n      port: 6543\n  selector: {}\n---\n\nkind: Endpoints\napiVersion: v1\nmetadata:\n  name: apiv1\nsubsets:\n  - addresses:\n      - ip: 172.17.0.1\n    ports:\n      - port: 6543\n        name: \"apiv1\"\n"
  },
  {
    "path": "devtools/kubernetes/services/frontend-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: frontend\nspec:\n  type: NodePort\n  selector:\n    app: frontend\n  ports:\n  - name: \"frontend\"\n    port: 4000\n    targetPort: 4000\n    nodePort: 4000\n"
  },
  {
    "path": "devtools/kubernetes/services/lmtpd-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: lmtpd\nspec:\n  type: NodePort\n  selector:\n    app: lmtpd\n  ports:\n  - name: \"lmtpd\"\n    port: 2525\n    targetPort: 2525\n    nodePort: 2525\n"
  },
  {
    "path": "devtools/kubernetes/services/minio-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: minio\nspec:\n  type: NodePort\n  selector:\n    app: minio\n  ports:\n  - name: \"minio\"\n    port: 9090\n    targetPort: 9090\n    nodePort: 9090\n"
  },
  {
    "path": "devtools/kubernetes/services/nats-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: nats\nspec:\n  type: NodePort\n  selector:\n    app: nats\n  ports:\n  - name: \"nats\"\n    port: 4222\n    targetPort: 4222\n    nodePort: 4222\n"
  },
  {
    "path": "devtools/kubernetes/services/redis-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: redis\nspec:\n  type: NodePort\n  selector:\n    app: redis\n  ports:\n  - name: \"redis\"\n    port: 6379\n    targetPort: 6379\n    nodePort: 6379\n"
  },
  {
    "path": "devtools/kubernetes/services/smtp-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: smtp\nspec:\n  type: NodePort\n  selector:\n    app: inbucket\n  ports:\n  - name: \"smtp\"\n    port: 2500\n    targetPort: 2500\n    nodePort: 2500\n  - name: \"smtp1\"\n    port: 8888\n    targetPort: 8888\n    nodePort: 8888"
  },
  {
    "path": "devtools/kubernetes/volumeclaims/db-persistentvolumeclaim.yaml",
    "content": "apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  labels:\n    tier: backend\n    type: volume\n  name: dev-db\nspec:\n  accessModes:\n  - ReadWriteOnce\n  resources:\n    requests:\n      storage: 1000Mi\n  storageClassName: standard\n"
  },
  {
    "path": "devtools/kubernetes/volumeclaims/index-persistentvolumeclaim.yaml",
    "content": "apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  labels:\n    tier: backend\n    type: volume\n  name: dev-index\nspec:\n  accessModes:\n  - ReadWriteOnce\n  resources:\n    requests:\n      storage: 1000Mi\n  storageClassName: standard\n"
  },
  {
    "path": "devtools/kubernetes/volumeclaims/store-persistentvolumeclaim.yaml",
    "content": "apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  labels:\n    tier: backend\n    type: volume\n  name: dev-store\nspec:\n  accessModes:\n  - ReadWriteOnce\n  resources:\n    requests:\n      storage: 1000Mi\n  storageClassName: standard"
  },
  {
    "path": "devtools/make_release",
    "content": "#!/usr/bin/env python\n\"\"\"\nCaliopen tool for release management.\n\nPermit to prepare a release, build and submit it on github.\n\nUsage:\n    make_release --conf <package.yaml> --version <X.Y.Z> build|clean\n\n\"\"\"\n\nimport os\nimport sys\nimport subprocess as sp\nimport shutil\nimport argparse\nimport re\n\nimport logging\nimport yaml\nimport json\n\nfrom datetime import datetime\nfrom git import Repo\n\nlog = logging.getLogger(__name__)\nlogging.basicConfig()\nlog.setLevel(logging.DEBUG)\n\n# Some declarations\n\nCOMMIT_MESSAGE = 'Release {version}'\n\nPY_BUILD_CMD = 'python setup.py bdist_wheel --universal --dist-dir {}'\nJS_BUILD_CMD = 'yarn && yarn {target}'\nGO_BUILD_CMD = 'go build -o {out} {target}'\n\n# Files updated to consider for commit or rollback\nUPDATE_FILES = []\n\n# Package and their informations for a release\nRELEASE_PACKAGES = {}\n\n\ndef do_command(cmd):\n    \"\"\"Execute a shell command when can't use a python library.\"\"\"\n    return sp.call(cmd, shell=True)\n\n\ndef do_package_command(package, cmd):\n    \"\"\"Execute a shell command for a package.\"\"\"\n    log.debug(' will execute command : {}'.format(cmd))\n    errno = sp.call(cmd, shell=True)\n    if errno == 0:\n        log.info('Build package {} OK'.format(package['name']))\n        return True\n    msg = 'Build package {} failed: {}'.format(package['name'], errno)\n    raise Exception(msg)\n\n\ndef check_versions(current, new):\n    \"\"\"Compare current and new version, raise if new is not superior.\"\"\"\n    t_new = tuple(int(x) for x in new.split('.'))\n    t_cur = tuple(int(x) for x in current.split('.'))\n    if t_new <= t_cur:\n        raise Exception('Current version {} and new expected one {} mismatch'.\n                        format(current, new))\n\n\ndef get_javascript_version(package, basedir):\n    \"\"\"Return version for a javascript package.\"\"\"\n    pkg_file = '{}/package.json'.format(basedir)\n    if not os.path.isfile(pkg_file):\n        raise Exception('File {} for package version not found'.\n                        format(pkg_file))\n    with open(pkg_file) as v_file:\n        return json.loads(v_file.read()).get('version')\n\n\ndef make_javascript_package(package, directory, version, output_directory):\n    \"\"\"Build javascript distribution.\"\"\"\n    pkg_version = get_javascript_version(package, directory)\n    RELEASE_PACKAGES[package['name']] = {'version': pkg_version}\n    # Should fail if using package name\n    build_cmd = JS_BUILD_CMD.format(target=package['build_target'])\n    cmd = 'cd {}; {}'.format(directory, build_cmd)\n    res = do_package_command(package, cmd)\n    if not res:\n        return False\n    # Move build distribution\n    distdir = '{}/dist/server'.format(directory)\n    if not os.path.isdir(distdir):\n        log.error('No dist directory after build')\n        sys.exit(1)\n\n    # Copy entire client directory for the moment\n    destdir = '{}/{}'.format(output_directory, package['name'])\n    shutil.copytree(directory, destdir)\n    # os.rename(distdir, destdir)\n    return True\n\n\ndef make_go_distribution(package, directory, version, output_directory):\n    \"\"\"Build go binaries for a given package and version.\"\"\"\n    outfile = '{}/{}'.format(output_directory, package['name'])\n    build_cmd = GO_BUILD_CMD.format(out=outfile,\n                                    target=package['build_target'])\n    cmd = 'cd {}; {}'.format(directory, build_cmd)\n    return do_package_command(package, cmd)\n\n\ndef get_python_version(package, basedir):\n    \"\"\"Return python package version.\"\"\"\n    pkg_file = '{}/{}/__init__.py'.format(basedir, package['name'])\n    if not os.path.isfile(pkg_file):\n        raise Exception('File {} for package version not found'.\n                        format(pkg_file))\n    \"\"\"Update the __version__ value for a python package.\"\"\"\n    with open(pkg_file) as v_file:\n        return re.compile(r\".*__version__ = '(.*?)'\",\n                          re.S).match(v_file.read()).group(1)\n\n\ndef make_python_package(package, directory, version, output_directory):\n    \"\"\"Make a python wheel package for a given package and version.\"\"\"\n    pkg_version = get_python_version(package, directory)\n    RELEASE_PACKAGES[package['name']] = {'version': pkg_version}\n\n    build_cmd = PY_BUILD_CMD.format(output_directory)\n    cmd = 'cd {}; {}'.format(directory, build_cmd)\n    return do_package_command(package, cmd)\n\n\ndef make_packages(config, version, languages):\n    \"\"\"Given a packages dict and a version, build all.\"\"\"\n    all_packages = config.get('packages', {})\n    basedir = get_repository_directory(config)\n\n    output_directory = '{}/dist/{}'.format(basedir, version)\n    if not os.path.isdir(output_directory):\n        raise Exception('No valid output directory for release {}'.\n                        format(version))\n\n    for language, packages in all_packages.items():\n        if 'all' not in languages and language not in languages:\n            log.info('Skipping language {}'.format(language))\n            continue\n        if language in make_dist_methods:\n            fn = make_dist_methods[language]\n        else:\n            log.error('Invalid language {}'.format(language))\n            sys.exit(1)\n\n        for package in packages:\n            log.info('Building package {}'.format(package['name']))\n            package_dir = '{}/{}'.format(basedir, package['path'])\n            res = fn(package, package_dir, version, output_directory)\n            if not res:\n                log.error('Build package {} failed'.format(package['name']))\n                sys.exit(1)\n    release_file = '{}/release.json'.format(output_directory)\n    with open(release_file, 'w') as f:\n        release = {'version': version, 'date': datetime.utcnow().isoformat(),\n                   'packages': RELEASE_PACKAGES}\n        f.write(json.dumps(release, indent=4))\n    # Copy swagger.json into release directory\n    swagger_src_file = '{}/src/backend/configs/swagger.json'.format(basedir)\n    swagger_dst_file = '{}/swagger.json'.format(output_directory)\n    shutil.copyfile(swagger_src_file, swagger_dst_file)\n\n    return True\n\n\ndef get_repository_directory(config):\n    \"\"\"Return absolute repository directory.\"\"\"\n    repo = config.get('repository', {})\n    basedir = repo.get('base_directory')\n    assert os.path.isdir(basedir)\n    return os.path.abspath(basedir)\n\n\ndef get_repository(basedir):\n    \"\"\"Return a ``Repo`` instance.\"\"\"\n    repo = Repo(basedir)\n    assert not repo.bare, 'Repository is bare git, not valid'\n    return repo\n\n\ndef commit_release(repo, version):\n    \"\"\"Commit a a release.\"\"\"\n    index = repo.index\n    log.debug('Updated files to add : {}'.format(UPDATE_FILES))\n    index.add(UPDATE_FILES)\n    commit_msg = COMMIT_MESSAGE.format(version=version)\n    index.commit(commit_msg)\n    log.info('Update git index after commit: {}'.format(commit_msg))\n    return index.write_tree()\n\n\ndef prepare_release(config, version, languages, nocheck=False):\n    \"\"\"Prepare a new release in a given version.\"\"\"\n    basedir = get_repository_directory(config)\n    repo = get_repository(basedir)\n    if not nocheck:\n        assert not repo.is_dirty(), 'Current head is dirty'\n        assert repo.active_branch.name == 'master', 'Not on master branch'\n\n    tag_name = 'release-{}'.format(version)\n    assert tag_name not in repo.tags, 'Tag {} exists'.format(version)\n\n    # XXX Check uptodate with origin\n    origin = repo.remote()\n    assert origin.name == 'origin', 'Invalid remote origin'\n\n    log.debug('Fetching origin')\n    origin.fetch()\n\n    orig_commit = origin.refs.master.commit.hexsha\n    local_commit = repo.refs.master.commit.hexsha\n    if local_commit != orig_commit:\n        log.error('Out of sync with origin, local commit {}, expected {}'.\n                  format(local_commit, orig_commit))\n        sys.exit(1)\n\n    output_directory = '{}/dist/{}'.format(basedir, version)\n    if not os.path.isdir(output_directory):\n        log.info('Creating output directory {}'.format(output_directory))\n        os.mkdir(output_directory)\n\n    # prepare_packages(config, version, languages, basedir)\n\n    # XXX Update CHANGES.md\n    # XXX Consider changelog\n\n    # XXX Commit changes\n    commit_release(repo, version)\n    # XXX Make a release tag\n    repo.create_tag(tag_name)\n    log.info('Prepare of release {} done'.format(version))\n    return True\n\n\ndef clean_release(config, opts):\n    \"\"\"Clean release.\"\"\"\n    version = opts.version\n    basedir = get_repository_directory(config)\n    repo = get_repository(basedir)\n    # assert not repo.is_dirty(), 'Current head is dirty'\n    # assert repo.active_branch.name == 'master', 'Not on master branch'\n    tag_name = 'release-{}'.format(version)\n    assert tag_name in repo.tags, 'Tag {} does not exists'.format(tag_name)\n\n    # XXX check if tag exists on origin and raise if so\n\n    local_commit = repo.head.commit\n    commit_msg = COMMIT_MESSAGE.format(version=version)\n\n    assert local_commit.message == commit_msg, 'Last commit does not match'\n\n    log.info('Reset HEAD~1 to discard commit {}'.format(local_commit.hexsha))\n    repo.head.reset('HEAD~1')\n\n    log.info('Delete tag {}'.format(tag_name))\n    if do_command('git tag -d {}'.format(tag_name)):\n        raise Exception('Delete of tag {} failed'.format(tag_name))\n\n    output_directory = '{}/dist/{}'.format(basedir, version)\n    if os.path.isdir(output_directory):\n        log.info('Delete dist directory {}'.format(output_directory))\n        shutil.rmtree(output_directory)\n    return True\n\n\nmake_dist_methods = {\n    'python': make_python_package,\n    'javascript': make_javascript_package,\n    'golang': make_go_distribution\n}\n\n\ndef build_release(config, opts):\n    \"\"\"Entry point to build a Caliopen release.\"\"\"\n    languages = opts.languages.split(',')\n    if not prepare_release(config, opts.version, languages, opts.no_check):\n        log.warn('Prepare release {} failed'.format(opts.version))\n        return False\n\n    if not make_packages(config, opts.version, languages):\n        log.warn('Build packages for release {} failed'.format(opts.version))\n        return False\n\n    return True\n\nif __name__ == '__main__':\n\n    parser = argparse.ArgumentParser()\n    parser.add_argument('--conf', dest='conffile', default='packages.yaml')\n    parser.add_argument('--version', dest='version')\n\n    subparsers = parser.add_subparsers(title=\"action\")\n\n    sp_build = subparsers.add_parser('build', help='Build a Caliopen release')\n    sp_build.set_defaults(func=build_release)\n    sp_build.add_argument('--languages', dest='languages', default='all')\n    sp_build.add_argument('--nocheck', dest='no_check', action='store_true')\n\n    sp_clean = subparsers.add_parser('clean', help='Clean a release build')\n    sp_clean.set_defaults(func=clean_release)\n\n    opts = parser.parse_args(sys.argv[1:])\n\n    with open(opts.conffile) as f:\n        config = yaml.load(f.read())\n\n    opts.func(config, opts)\n"
  },
  {
    "path": "devtools/makefile",
    "content": "#SET THIS VARIABLE TO DEFINE WHICH APP YOU WANT TO PUBLISH\n#APP_NAME=\nVERSION := ${CALIOPEN_VERSION}\nCONF ?= registry.conf\ninclude $(CONF)\n# HELP\n# This will output the help for each task\n# Based on https://gist.github.com/mpneuried/0594963ad38e68917ef189b4e6a269db\n# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html\n.PHONY: help\n\nhelp: ## Show help.\n\t@awk 'BEGIN {FS = \":.*?## \"} /^[a-zA-Z_-]+:.*?## / {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}' $(MAKEFILE_LIST)\n\n.DEFAULT_GOAL := help\n\n# DOCKER TASKS\nbuild: ## Build the image\n\tdocker-compose build $(APP_NAME)\n\nbuild-nc: ## Build the image without caching\n\tdocker-compose build --no-cache $(APP_NAME)\n\n# Master images\nmaster: build repo-login publish-latest publish-version ## Publish the `{version}`,`latest` tagged image\n\npublish-latest: tag-latest ## Publish the `latest` tagged image\n\t@echo 'publish latest to $(DOCKER_REPO)'\n\tdocker push $(DOCKER_REPO)/caliopen_$(APP_NAME):latest\n\npublish-version: tag-version ## Publish the `{version}` tagged image\n\t@echo 'publish $(VERSION) to $(DOCKER_REPO)'\n\tdocker push $(DOCKER_REPO)/caliopen_$(APP_NAME):$(VERSION)\n\ntag-latest: ## Generate image `app`:latest\n\t@echo 'create tag latest'\n\tdocker tag caliopen_$(APP_NAME) $(DOCKER_REPO)/caliopen_$(APP_NAME):latest\n\ntag-version: ## Generate image `app`:`{version}`\n\t@echo 'create tag $(VERSION)'\n\tdocker tag caliopen_$(APP_NAME) $(DOCKER_REPO)/caliopen_$(APP_NAME):$(VERSION)\n\n# Develop images\ndevelop: build repo-login publish-develop ## Build and publish the `develop` tagged images\n\npublish-develop: tag-develop ## Publish the `develop` tagged image\n\t@echo 'publish develop to $(DOCKER_REPO)'\n\tdocker push $(DOCKER_REPO)/caliopen_$(APP_NAME):develop\n\ntag-develop: ## Generate image with `develop` tag\n\t@echo 'create tag develop'\n\tdocker tag caliopen_$(APP_NAME) $(DOCKER_REPO)/caliopen_$(APP_NAME):develop\n# HELPERS\n\n# generate script to login to docker registry\nCMD_REPOLOGIN := \"docker login -u=$(REPO_USER) -p=$(REPO_PASS) $(DOCKER_REPO)\"\n\n# login to registry\nrepo-login: ## Auto login\n\t@eval $(CMD_REPOLOGIN)\n\nversion: ## Output the current version\n\t@echo $(VERSION)\n"
  },
  {
    "path": "devtools/manage_package",
    "content": "#!/usr/bin/env python\n\"\"\"\nCaliopen tool for package management.\n\nThis tool permit to do package management tasks based on Caliopen\npackage definition.\n\nUsage:\n    manage_package --conf <package.yaml> create_version <package> <version>\n\n\"\"\"\nimport os\nimport sys\nimport re\nimport subprocess as sp\nimport argparse\nimport logging\nimport datetime\n\nimport yaml\nimport json\nfrom git import Repo\n\n\nlog = logging.getLogger(__name__)\nlogging.basicConfig()\nlog.setLevel(logging.DEBUG)\n\nPY_UPDATE_CMD = \"\"\"perl -pi -e \"s/__version__ = '.*/__version__ = '{version}'/\" {file}\"\"\"\nGO_UPDATE_CMD = \"\"\"perl -pi -e \"s/__version__ = \\\\\".*/__version__ = \\\\\"{version}\\\\\"/\" {file}\"\"\"\nJS_UPDATE_CMD = \"cd {dir}; npm version {version}\"\n\nCHANGELOG_UPDATE_CMD = \"sed -i '/## \\[Unreleased\\]/a ## [{version}] {date}' {file}\"\n\n#\n# Method duplicated with make_release ones ...\n#\n\ndef get_repository(basedir):\n    \"\"\"Return a ``Repo`` instance.\"\"\"\n    repo = Repo(basedir)\n    assert not repo.bare, 'Repository is bare git, not valid'\n    return repo\n\n\ndef get_repository_directory(config):\n    \"\"\"Return absolute repository directory.\"\"\"\n    repo = config.get('repository', {})\n    basedir = repo.get('base_directory')\n    assert os.path.isdir(basedir)\n    return os.path.abspath(basedir)\n\n\ndef get_javascript_version(package, basedir):\n    \"\"\"Return version for a javascript package.\"\"\"\n    pkg_file = '{}/package.json'.format(basedir)\n    if not os.path.isfile(pkg_file):\n        raise Exception('File {} for package version not found'.\n                        format(pkg_file))\n    with open(pkg_file) as v_file:\n        return json.loads(v_file.read()).get('version')\n\n\ndef get_python_version(package, basedir):\n    \"\"\"Return python package version.\"\"\"\n    pkg_file = '{}/{}/__init__.py'.format(basedir, package['name'])\n    if not os.path.isfile(pkg_file):\n        raise Exception('File {} for package version not found'.\n                        format(pkg_file))\n    with open(pkg_file) as v_file:\n        return re.compile(r\".*__version__ = '(.*?)'\",\n                          re.S).match(v_file.read()).group(1)\n\n\ndef get_golang_version(package, basedir):\n    \"\"\"Return golang package version.\"\"\"\n    pkg_file = '{}/cmd/{}/cli_cmds/root.go'.format(basedir, package['name'])\n    if not os.path.isfile(pkg_file):\n        raise Exception('File {} for package version not found'.\n                        format(pkg_file))\n    with open(pkg_file) as v_file:\n        return re.compile(r\".*__version__ = \\\"(.*?)\\\"\",\n                          re.S).match(v_file.read()).group(1)\n\n\ndef check_versions(current, new):\n    \"\"\"Compare current and new version, raise if new is not superior.\"\"\"\n    t_new = tuple(int(x) for x in new.split('.'))\n    t_cur = tuple(int(x) for x in current.split('.'))\n    if t_new <= t_cur:\n        raise Exception('Current version {} and new expected one {} mismatch'.\n                        format(current, new))\n\n\n#\n# Methods for files with version\n#\n\ndef process_changelog_version(dir, version):\n    today = datetime.datetime.now()\n\n    changelog_file = '{}/CHANGELOG.md'.format(dir)\n    cmd = CHANGELOG_UPDATE_CMD.format(version=version, file=changelog_file, date=today.strftime('%Y-%m-%d'))\n    log.debug('Will execute {}'.format(cmd))\n    return sp.call(cmd, shell=True), changelog_file\n\n#\n# Methods for package management\n#\n\ndef update_python_version(dir, package, version):\n    \"\"\"Update a python package to a new version.\"\"\"\n    pkg_file = '{}/{}/__init__.py'.format(dir, package['name'])\n    cmd = PY_UPDATE_CMD.format(version=version, file=pkg_file)\n    log.debug('Will execute {}'.format(cmd))\n    return sp.call(cmd, shell=True), pkg_file\n\n\ndef update_golang_version(dir, package, version):\n    \"\"\"Update a golang package to a new version.\"\"\"\n    pkg_file = '{}/cmd/{}/cli_cmds/root.go'.format(dir, package['name'])\n    cmd = GO_UPDATE_CMD.format(version=version, file=pkg_file)\n    log.debug('Will execute {}'.format(cmd))\n    return sp.call(cmd, shell=True), pkg_file\n\n\ndef update_javascript_version(dir, package, version):\n    \"\"\"Update a javascript package to a new version.\"\"\"\n    pkg_file = '{}/package.json'.format(dir)\n    if not os.path.isfile(pkg_file):\n        raise Exception('No package.json file in {}'.format(dir))\n    cmd = JS_UPDATE_CMD.format(dir=dir, version=version)\n    return sp.call(cmd, shell=True), pkg_file\n\n\ndef find_package(config, name):\n    \"\"\"Find the related caliopen package and return its language.\"\"\"\n    all_packages = config.get('packages', {})\n    for language, packages in all_packages.items():\n        for package in packages:\n            if package['name'] == opts.package:\n                log.info('Will process package {} in language {}'.\n                         format(package, language))\n                return language, package\n    return None, None\n\n\ndef create_package_version(config, opts):\n    \"\"\"Update one package or all to a new version.\"\"\"\n    basedir = get_repository_directory(config)\n    repo = get_repository(basedir)\n\n    if not opts.no_check:\n        assert not repo.is_dirty(), 'Current head is dirty'\n\n    if opts.package == 'all':\n        all_packages = config.get('packages', {})\n        for lang, packages in all_packages.items():\n            for package in packages:\n                process_package_version(config, opts, package,\n                                        lang, repo)\n        tag_name = 'packages-{}'.format(opts.version)\n    else:\n        lang, package = find_package(config, opts.package)\n        process_package_version(config, opts, package, lang, repo)\n        tag_name = '{}-{}'.format(package['name'], opts.version)\n\n    if not tag_name:\n        raise Exception('Tag name is not set')\n\n    update, file = process_changelog_version(basedir, opts.version)\n    if update == 0:\n        repo.index.add([file])\n\n\n    if not opts.no_check:\n        assert tag_name not in repo.tags, 'Tag {} exists'.format(opts.version)\n\n    repo.index.commit('Version {} for package {}'.\n                      format(opts.version, opts.package))\n    log.info('Creating tag {}'.format(tag_name))\n    repo.create_tag(tag_name)\n\n    repo.index.write_tree()\n\n\ndef process_package_version(config, opts, package, lang, repo):\n    \"\"\"Process package to update version.\"\"\"\n    basedir = get_repository_directory(config)\n\n    print('###### %r' % package)\n    pkg_dir = '{}/{}'.format(basedir, package['path'])\n\n    if lang == 'python':\n        current = get_python_version(package, pkg_dir)\n        check_versions(current, opts.version)\n        update, file = update_python_version(pkg_dir, package, opts.version)\n    elif lang == 'golang':\n        current = get_golang_version(package, pkg_dir)\n        check_versions(current, opts.version)\n        update, file = update_golang_version(pkg_dir, package, opts.version)\n    elif lang == 'javascript':\n        current = get_javascript_version(package, pkg_dir)\n        check_versions(current, opts.version)\n        update, file = update_javascript_version(pkg_dir, package,\n                                                 opts.version)\n    else:\n        raise Exception('Language {} not managed'.format(lang))\n\n    if update == 0:\n        repo.index.add([file])\n    else:\n        raise Exception('Invalid return value when updating package version')\n    return update\n\n\nif __name__ == '__main__':\n\n    parser = argparse.ArgumentParser()\n    parser.add_argument('--conf', dest='conffile', default='packages.yaml')\n\n    subparsers = parser.add_subparsers(title=\"action\")\n    sp_create = subparsers.add_parser('create_version',\n                                      help='Create a package version')\n    sp_create.add_argument('package')\n    sp_create.add_argument('version')\n    sp_create.add_argument('--nocheck', dest='no_check', action='store_true')\n    sp_create.set_defaults(func=create_package_version)\n\n    opts = parser.parse_args(sys.argv[1:])\n\n    with open(opts.conffile) as f:\n        config = yaml.load(f.read())\n\n    res = opts.func(config, opts)\n"
  },
  {
    "path": "devtools/manage_package-requirements.txt",
    "content": "gitdb2==2.0.5\nGitPython==2.1.11\nPyYAML==5.4\nsmmap2==2.0.5\n"
  },
  {
    "path": "devtools/migrations/ES_mappings_v2.json",
    "content": "{\n  \"mappings\": {\n    \"indexed_contact\": {\n      \"_all\": {\n        \"enabled\": true\n      },\n      \"properties\": {\n        \"additional_name\": {\n          \"fields\": {\n            \"normalized\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            }\n          },\n          \"type\": \"text\"\n        },\n        \"addresses\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"address_id\": {\n              \"type\": \"keyword\"\n            },\n            \"city\": {\n              \"type\": \"text\"\n            },\n            \"country\": {\n              \"type\": \"text\"\n            },\n            \"is_primary\": {\n              \"type\": \"boolean\"\n            },\n            \"label\": {\n              \"type\": \"text\"\n            },\n            \"postal_code\": {\n              \"type\": \"keyword\"\n            },\n            \"region\": {\n              \"type\": \"text\"\n            },\n            \"street\": {\n              \"type\": \"text\"\n            },\n            \"type\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"avatar\": {\n          \"type\": \"keyword\"\n        },\n        \"date_insert\": {\n          \"type\": \"date\"\n        },\n        \"date_update\": {\n          \"type\": \"date\"\n        },\n        \"deleted\": {\n          \"type\": \"date\"\n        },\n        \"emails\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"address\": {\n              \"analyzer\": \"text_analyzer\",\n              \"fields\": {\n                \"parts\": {\n                  \"analyzer\": \"email_analyzer\",\n                  \"type\": \"text\"\n                },\n                \"raw\": {\n                  \"type\": \"keyword\"\n                }\n              },\n              \"type\": \"text\"\n            },\n            \"email_id\": {\n              \"type\": \"keyword\"\n            },\n            \"is_primary\": {\n              \"type\": \"boolean\"\n            },\n            \"label\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            },\n            \"type\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"family_name\": {\n          \"fields\": {\n            \"normalized\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            }\n          },\n          \"type\": \"text\"\n        },\n        \"given_name\": {\n          \"fields\": {\n            \"normalized\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            }\n          },\n          \"type\": \"text\"\n        },\n        \"groups\": {\n          \"type\": \"keyword\"\n        },\n        \"identities\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"infos\": {\n              \"type\": \"nested\"\n            },\n            \"name\": {\n              \"type\": \"text\"\n            },\n            \"type\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"ims\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"address\": {\n              \"analyzer\": \"text_analyzer\",\n              \"fields\": {\n                \"parts\": {\n                  \"analyzer\": \"email_analyzer\",\n                  \"type\": \"text\"\n                },\n                \"raw\": {\n                  \"type\": \"keyword\"\n                }\n              },\n              \"type\": \"text\"\n            },\n            \"email_id\": {\n              \"type\": \"keyword\"\n            },\n            \"is_primary\": {\n              \"type\": \"boolean\"\n            },\n            \"label\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            },\n            \"type\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"infos\": {\n          \"type\": \"nested\"\n        },\n        \"name_prefix\": {\n          \"type\": \"keyword\"\n        },\n        \"name_suffix\": {\n          \"type\": \"keyword\"\n        },\n        \"organizations\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"deleted\": {\n              \"type\": \"boolean\"\n            },\n            \"department\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            },\n            \"is_primary\": {\n              \"type\": \"boolean\"\n            },\n            \"job_description\": {\n              \"type\": \"text\"\n            },\n            \"label\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            },\n            \"name\": {\n              \"fields\": {\n                \"normalized\": {\n                  \"analyzer\": \"text_analyzer\",\n                  \"type\": \"text\"\n                }\n              },\n              \"type\": \"text\"\n            },\n            \"organization_id\": {\n              \"type\": \"keyword\"\n            },\n            \"title\": {\n              \"type\": \"keyword\"\n            },\n            \"type\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"phones\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"is_primary\": {\n              \"type\": \"boolean\"\n            },\n            \"normalized_number\": {\n              \"type\": \"text\"\n            },\n            \"number\": {\n              \"type\": \"text\"\n            },\n            \"phone_id\": {\n              \"type\": \"keyword\"\n            },\n            \"type\": {\n              \"type\": \"keyword\"\n            },\n            \"uri\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"pi\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"comportment\": {\n              \"type\": \"integer\"\n            },\n            \"context\": {\n              \"type\": \"integer\"\n            },\n            \"date_update\": {\n              \"type\": \"date\"\n            },\n            \"technic\": {\n              \"type\": \"integer\"\n            },\n            \"version\": {\n              \"type\": \"integer\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"privacy_features\": {\n          \"include_in_all\": true,\n          \"type\": \"nested\"\n        },\n        \"public_key\": {\n          \"type\": \"nested\"\n        },\n        \"social_identities\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"infos\": {\n              \"type\": \"nested\"\n            },\n            \"name\": {\n              \"type\": \"text\"\n            },\n            \"type\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"tags\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"date_insert\": {\n              \"type\": \"date\"\n            },\n            \"importance_level\": {\n              \"type\": \"integer\"\n            },\n            \"name\": {\n              \"type\": \"keyword\"\n            },\n            \"tag_id\": {\n              \"type\": \"keyword\"\n            },\n            \"type\": {\n              \"type\": \"boolean\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"title\": {\n          \"type\": \"text\"\n        }\n      }\n    },\n    \"indexed_message\": {\n      \"_all\": {\n        \"enabled\": true\n      },\n      \"properties\": {\n        \"attachments\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"content_type\": {\n              \"type\": \"keyword\"\n            },\n            \"file_name\": {\n              \"type\": \"keyword\"\n            },\n            \"is_inline\": {\n              \"type\": \"boolean\"\n            },\n            \"mime_boundary\": {\n              \"type\": \"keyword\"\n            },\n            \"size\": {\n              \"type\": \"integer\"\n            },\n            \"url\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"body_html\": {\n          \"fields\": {\n            \"normalized\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            }\n          },\n          \"type\": \"text\"\n        },\n        \"body_plain\": {\n          \"fields\": {\n            \"normalized\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            }\n          },\n          \"type\": \"text\"\n        },\n        \"date\": {\n          \"type\": \"date\"\n        },\n        \"date_delete\": {\n          \"type\": \"date\"\n        },\n        \"date_insert\": {\n          \"type\": \"date\"\n        },\n        \"discussion_id\": {\n          \"type\": \"keyword\"\n        },\n        \"external_references\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"ancestors_ids\": {\n              \"type\": \"keyword\"\n            },\n            \"message_id\": {\n              \"type\": \"keyword\"\n            },\n            \"parent_id\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"identities\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"identifier\": {\n              \"fields\": {\n                \"parts\": {\n                  \"analyzer\": \"email_analyzer\",\n                  \"type\": \"text\"\n                },\n                \"raw\": {\n                  \"type\": \"keyword\"\n                }\n              },\n              \"type\": \"text\"\n            },\n            \"type\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"importance_level\": {\n          \"type\": \"short\"\n        },\n        \"is_answered\": {\n          \"type\": \"boolean\"\n        },\n        \"is_draft\": {\n          \"type\": \"boolean\"\n        },\n        \"is_unread\": {\n          \"type\": \"boolean\"\n        },\n        \"message_id\": {\n          \"type\": \"keyword\"\n        },\n        \"parent_id\": {\n          \"type\": \"keyword\"\n        },\n        \"participants\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"address\": {\n              \"analyzer\": \"text_analyzer\",\n              \"fields\": {\n                \"parts\": {\n                  \"analyzer\": \"email_analyzer\",\n                  \"type\": \"text\"\n                },\n                \"raw\": {\n                  \"type\": \"keyword\"\n                }\n              },\n              \"type\": \"text\"\n            },\n            \"contact_ids\": {\n              \"type\": \"keyword\"\n            },\n            \"label\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            },\n            \"protocol\": {\n              \"type\": \"keyword\"\n            },\n            \"type\": {\n              \"type\": \"keyword\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"pi\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"comportment\": {\n              \"type\": \"integer\"\n            },\n            \"context\": {\n              \"type\": \"integer\"\n            },\n            \"date_update\": {\n              \"type\": \"date\"\n            },\n            \"technic\": {\n              \"type\": \"integer\"\n            },\n            \"version\": {\n              \"type\": \"integer\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"privacy_features\": {\n          \"include_in_all\": true,\n          \"type\": \"nested\"\n        },\n        \"raw_msg_id\": {\n          \"type\": \"keyword\"\n        },\n        \"subject\": {\n          \"fields\": {\n            \"normalized\": {\n              \"analyzer\": \"text_analyzer\",\n              \"type\": \"text\"\n            }\n          },\n          \"type\": \"text\"\n        },\n        \"tags\": {\n          \"include_in_all\": true,\n          \"properties\": {\n            \"date_insert\": {\n              \"type\": \"date\"\n            },\n            \"importance_level\": {\n              \"type\": \"integer\"\n            },\n            \"name\": {\n              \"type\": \"keyword\"\n            },\n            \"tag_id\": {\n              \"type\": \"keyword\"\n            },\n            \"type\": {\n              \"type\": \"boolean\"\n            }\n          },\n          \"type\": \"nested\"\n        },\n        \"type\": {\n          \"type\": \"keyword\"\n        }\n      }\n    }\n  },\n  \"settings\": {\n    \"analysis\": {\n      \"analyzer\": {\n        \"text_analyzer\": {\n          \"type\": \"custom\",\n          \"tokenizer\": \"lowercase\",\n          \"filter\": [\n            \"ascii_folding\"\n          ]\n        },\n        \"email_analyzer\": {\n          \"type\": \"custom\",\n          \"tokenizer\": \"email_tokenizer\",\n          \"filter\": [\n            \"ascii_folding\"\n          ]\n        }\n      },\n      \"filter\": {\n        \"ascii_folding\": {\n          \"type\": \"asciifolding\",\n          \"preserve_original\": true\n        }\n      },\n      \"tokenizer\": {\n        \"email_tokenizer\": {\n          \"type\": \"ngram\",\n          \"min_gram\": 3,\n          \"max_gram\": 25\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "devtools/migrations/add_delivered_column_to_raw-message_table.cql",
    "content": "ALTER TABLE raw_message ADD delivered boolean;"
  },
  {
    "path": "devtools/migrations/add_temp_id_to_attachment_type.cql",
    "content": "ALTER TYPE message_attachment ADD temp_id uuid;"
  },
  {
    "path": "devtools/migrations/fill_message_is_received.py",
    "content": "#!/usr/bin/env python\n# coding: utf8\n\"\"\"Extract recovery emails of registered users.\"\"\"\n\nfrom __future__ import unicode_literals\n\nimport sys\nimport argparse\nimport logging\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_storage.helpers.connection import connect_storage\nfrom caliopen_main.common.errors import PatchConflict\nfrom caliopen_storage.exception import NotFound\n\nlog = logging.getLogger(__name__)\nlogging.basicConfig(level=logging.INFO)\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-f', dest='conffile')\n    parser.add_argument('-t', dest='test', action='store_true', default=False)\n\n    args = parser.parse_args()\n    Configuration.load(args.conffile, 'global')\n    connect_storage()\n    from caliopen_main.user.core.user import LocalIdentity\n    from caliopen_main.message.store.message import Message\n    from caliopen_main.message.objects.message import Message as ObjMessage\n\n    identities = [x.identifier for x in LocalIdentity._model_class.all()]\n    cpt = {'received': 0, 'sent': 0}\n    for message in Message.all():\n        if message.is_received is None:\n            from_participant = [x for x in message.participants\n                                if x['type'] == 'From']\n            if not from_participant:\n                log.error('No from participant found for message {} : {}'.\n                          format(message.message_id, message.participants))\n            else:\n                is_received = False\n                if from_participant[0]['address'] in identities:\n                    log.info('Sent mail')\n                    cpt['sent'] += 1\n                else:\n                    log.info('Received mail')\n                    cpt['received'] += 1\n                    is_received = True\n                if not args.test:\n                    obj = ObjMessage(message.user_id,\n                                     message_id=message.message_id)\n                    patch = {'current_state': {'is_received': None},\n                             'is_received': is_received}\n                    try:\n                        error = obj.apply_patch(patch, db=True, index=True)\n                        if error is not None:\n                            log.error('Unable to patch message {}: {}'.\n                                      format(message.message_id, error))\n                    except PatchConflict as exc:\n                        log.exception('Patch conflict for message {} :{}'.\n                                      format(message.message_id, exc))\n                    except NotFound as exc:\n                        log.exception('Message not found {}: {}'.\n                                      format(message.message_id, exc))\n                    except Exception as exc:\n                        log.exception('Unhandled exception {}'.format(exc))\n    print('Total {}'.format(cpt))\n"
  },
  {
    "path": "devtools/migrations/index_migration_v0_to_v2.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\nfrom caliopen_main.contact.objects.contact import Contact\nfrom caliopen_main.message.objects.message import Message\n\nlog = logging.getLogger(__name__)\n\n\nclass IndexMigrator(object):\n    def __init__(self, es_client):\n        self.es_client = es_client\n        self.types = (Contact(), Message())\n\n    def run(self):\n        indexes = self.es_client.indices.get(\"_all\")\n        log.warn(\"found {} indexes\".format(len(indexes)))\n        m_version = \"v2\"\n        total = len(indexes)\n        ok = 0\n        errors = 0\n        count = 1\n        for index in indexes:\n            new_index = index + \"_\" + m_version\n            log.warn(\"Operation {}/{}\".format(count, total))\n            count += 1\n            try:\n                self.create_new_index(new_index)\n                self.copy_old_to_new(index, new_index)\n            except Exception as exc:\n                log.warn(\"Aborting migration operations for {}\".format(index))\n                errors += 1\n                continue\n                # do not try operations below if above reindexation has failed\n            try:\n                self.delete_old_index(index)\n                self.create_alias(index, new_index)\n            except Exception:\n                errors += 1\n                continue\n\n            ok += 1\n\n        log.warn(\"{} operations completed\\n    OK : {}\\n    Errors: {}\".format(\n            count, ok, errors))\n\n    def create_new_index(self, index):\n        \"\"\"Creates user index and setups mappings.\"\"\"\n        log.warn('Creating new index {}'.format(index))\n\n        try:\n            self.es_client.indices.create(\n                index=index,\n                body={\n                    \"settings\": {\n                        \"analysis\": {\n                            \"analyzer\": {\n                                \"text_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"lowercase\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                },\n                                \"email_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"email_tokenizer\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                }\n                            },\n                            \"filter\": {\n                                \"ascii_folding\": {\n                                    \"type\": \"asciifolding\",\n                                    \"preserve_original\": True\n                                }\n                            },\n                            \"tokenizer\": {\n                                \"email_tokenizer\": {\n                                    \"type\": \"ngram\",\n                                    \"min_gram\": 3,\n                                    \"max_gram\": 25\n                                }\n                            }\n                        }\n                    }\n                })\n        except Exception as exc:\n            log.error(\"failed to create index {} : {}\".format(index, exc))\n            raise exc\n\n        # PUT mappings for each type\n        for kls in self.types:\n            kls._index_class().create_mapping(index)\n\n    def copy_old_to_new(self, old, new):\n        log.warn(\"Copying data from {} to {}\".format(old, new))\n\n        try:\n            self.es_client.reindex(body={\n                \"source\": {\n                    \"index\": old\n                },\n                \"dest\": {\n                    \"index\": new\n                }\n            })\n        except Exception as exc:\n            log.error(\n                \"failed to copy index {} to {} : {}\".format(old, new, exc))\n            raise exc\n\n    def delete_old_index(self, index):\n        log.warn(\"Deleting old index {}\".format(index))\n\n        try:\n            self.es_client.indices.delete(index)\n        except Exception as exc:\n            log.error(\"failed to delete old index {} : {}\".format(index, exc))\n            raise exc\n\n    def create_alias(self, alias, index):\n        log.warn(\"Creating an alias {} pointing to {}\".format(alias, index))\n\n        try:\n            self.es_client.indices.put_alias(index=index, name=alias)\n        except Exception as exc:\n            log.error(\"failed to create alias {} : {}\".format(alias, exc))\n            raise exc\n"
  },
  {
    "path": "devtools/migrations/index_migration_v2_to_v3.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\nfrom caliopen_main.contact.objects.contact import Contact\nfrom caliopen_main.message.objects.message import Message\n\nlog = logging.getLogger(__name__)\n\nOLD_PREFIX = '_v2'\nNEW_PREFIX = '_v3'\n\n\n\nclass IndexMigrator(object):\n    def __init__(self, client=None, mappings_version=None):\n        self.es_client = client\n        self.types = (Contact(), Message())\n        self.mappings_version = mappings_version\n\n    def check_empty_index(self, index):\n        stats = self.es_client.indices.stats(index)\n        docs = stats['_all']['total']['docs']\n        if docs['count'] != 0:\n            return False\n        else:\n            old_index = '{}{}'.format(index[:-(len(NEW_PREFIX))], OLD_PREFIX)\n            if self.es_client.indices.exists(old_index):\n                log.warn('Delete the empty index {} with old version found'.\n                         format(index))\n                self.es_client.indices.close(index)\n                self.es_client.indices.delete(index)\n        return True\n\n    def run(self):\n        indexes = self.es_client.indices.get(\"_all\")\n        log.warn(\"found {} indexes\".format(len(indexes)))\n        total = len(indexes)\n        ok = 0\n        errors = 0\n        count = 1\n        skip = 0\n        for index in indexes:\n            alias = index[:-3]\n            new_index = alias + \"_\" + self.mappings_version\n            if index.endswith(self.mappings_version):\n                if not self.check_empty_index(index):\n                    log.warn('Index {} already in wanted version'.\n                             format(index))\n                    skip += 1\n                    continue\n\n            if index.endswith(OLD_PREFIX):\n                if self.es_client.indices.exists(new_index):\n                    log.warn('Index {} exists, skipping'.format(new_index))\n                    skip += 1\n                    continue\n\n            log.info(\"Operation {}/{}\".format(count, total))\n            count += 1\n            try:\n                self.create_new_index(new_index)\n                self.copy_old_to_new(index, new_index)\n            except Exception as exc:\n                log.warn(\"Aborting migration operations for {}\".format(index))\n                errors += 1\n                continue\n                # do not try operations below if above reindexation has failed\n            try:\n                self.move_alias(alias, index, new_index)\n                self.delete_old_index(index)\n            except Exception:\n                errors += 1\n                continue\n\n            ok += 1\n\n        log.warn(\"{} operations completed OK : {} \"\n                 \"Errors: {} Skip: {}\".format(count, ok, errors, skip))\n\n    def create_new_index(self, index):\n        \"\"\"Create user index and setups mappings.\"\"\"\n        log.warn('Creating new index {}'.format(index))\n\n        try:\n            self.es_client.indices.create(\n                index=index,\n                timeout='30s',\n                body={\n                    \"settings\": {\n                        \"index\": {\n                            \"number_of_shards\": 3,\n                        },\n                        \"analysis\": {\n                            \"analyzer\": {\n                                \"text_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"lowercase\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                },\n                                \"email_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"email_tokenizer\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                }\n                            },\n                            \"filter\": {\n                                \"ascii_folding\": {\n                                    \"type\": \"asciifolding\",\n                                    \"preserve_original\": True\n                                }\n                            },\n                            \"tokenizer\": {\n                                \"email_tokenizer\": {\n                                    \"type\": \"ngram\",\n                                    \"min_gram\": 3,\n                                    \"max_gram\": 25\n                                }\n                            }\n                        }\n                    }\n                })\n        except Exception as exc:\n            log.error(\"failed to create index {} : {}\".format(index, exc))\n            raise exc\n\n        # PUT mappings for each type\n        for kls in self.types:\n            kls._index_class().create_mapping(index)\n\n    def copy_old_to_new(self, old, new):\n        log.warn(\"Copying data from {} to {}\".format(old, new))\n\n        try:\n            self.es_client.reindex(timeout='1m', body={\n                \"source\": {\n                    \"index\": old\n                },\n                \"dest\": {\n                    \"index\": new\n                }\n            })\n        except Exception as exc:\n            log.error(\n                \"failed to copy index {} to {} : {}\".format(old, new, exc))\n            raise exc\n\n    def delete_old_index(self, index):\n        log.warn(\"Deleting old index {}\".format(index))\n\n        try:\n            self.es_client.indices.delete(index)\n        except Exception as exc:\n            log.error(\"failed to delete old index {} : {}\".format(index, exc))\n            raise exc\n\n    def move_alias(self, alias, old_index, new_index):\n        log.warn(\n            \"Moving alias {} from {} to {}\".format(alias, old_index, new_index))\n\n        try:\n            self.es_client.indices.delete_alias(index=old_index, name=alias)\n            self.es_client.indices.put_alias(index=new_index, name=alias)\n        except Exception as exc:\n            log.error(\"failed to move alias {} : {}\".format(alias, exc))\n            raise exc\n"
  },
  {
    "path": "devtools/migrations/index_migration_v3_to_v4.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\nfrom caliopen_main.contact.objects.contact import Contact\nfrom caliopen_main.message.objects.message import Message\n\nlog = logging.getLogger(__name__)\n\nOLD_PREFIX = '_v3'\nNEW_PREFIX = '_v4'\n\n\n\nclass IndexMigrator(object):\n    def __init__(self, client=None, mappings_version=None):\n        self.es_client = client\n        self.types = (Contact(), Message())\n        self.mappings_version = mappings_version\n\n    def check_empty_index(self, index):\n        stats = self.es_client.indices.stats(index)\n        docs = stats['_all']['total']['docs']\n        if docs['count'] != 0:\n            return False\n        else:\n            old_index = '{}{}'.format(index[:-(len(NEW_PREFIX))], OLD_PREFIX)\n            if self.es_client.indices.exists(old_index):\n                log.warn('Delete the empty index {} with old version found'.\n                         format(index))\n                self.es_client.indices.close(index)\n                self.es_client.indices.delete(index)\n        return True\n\n    def run(self):\n        indexes = self.es_client.indices.get(\"_all\")\n        log.warn(\"found {} indexes\".format(len(indexes)))\n        total = len(indexes)\n        ok = 0\n        errors = 0\n        count = 1\n        skip = 0\n        for index in indexes:\n            alias = index[:-3]\n            new_index = alias + \"_\" + self.mappings_version\n            if index.endswith(self.mappings_version):\n                if not self.check_empty_index(index):\n                    log.warn('Index {} already in wanted version'.\n                             format(index))\n                    skip += 1\n                    continue\n\n            if index.endswith(OLD_PREFIX):\n                if self.es_client.indices.exists(new_index):\n                    log.warn('Index {} exists, skipping'.format(new_index))\n                    skip += 1\n                    continue\n\n            log.info(\"Operation {}/{}\".format(count, total))\n            count += 1\n            try:\n                self.create_new_index(new_index)\n                self.copy_old_to_new(index, new_index)\n            except Exception as exc:\n                log.warn(\"Aborting migration operations for {}\".format(index))\n                errors += 1\n                continue\n                # do not try operations below if above reindexation has failed\n            try:\n                self.move_alias(alias, index, new_index)\n                self.delete_old_index(index)\n            except Exception:\n                errors += 1\n                continue\n\n            ok += 1\n\n        log.warn(\"{} operations completed OK : {} \"\n                 \"Errors: {} Skip: {}\".format(count, ok, errors, skip))\n\n    def create_new_index(self, index):\n        \"\"\"Create user index and setups mappings.\"\"\"\n        log.warn('Creating new index {}'.format(index))\n\n        try:\n            self.es_client.indices.create(\n                index=index,\n                timeout='30s',\n                body={\n                    \"settings\": {\n                        \"index\": {\n                            \"number_of_shards\": 3,\n                        },\n                        \"analysis\": {\n                            \"analyzer\": {\n                                \"text_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"lowercase\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                },\n                                \"email_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"email_tokenizer\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                }\n                            },\n                            \"filter\": {\n                                \"ascii_folding\": {\n                                    \"type\": \"asciifolding\",\n                                    \"preserve_original\": True\n                                }\n                            },\n                            \"tokenizer\": {\n                                \"email_tokenizer\": {\n                                    \"type\": \"ngram\",\n                                    \"min_gram\": 3,\n                                    \"max_gram\": 25\n                                }\n                            }\n                        }\n                    }\n                })\n        except Exception as exc:\n            log.error(\"failed to create index {} : {}\".format(index, exc))\n            raise exc\n\n        # PUT mappings for each type\n        for kls in self.types:\n            kls._index_class().create_mapping(index)\n\n    def copy_old_to_new(self, old, new):\n        log.warn(\"Copying data from {} to {}\".format(old, new))\n\n        try:\n            self.es_client.reindex(timeout='1m', body={\n                \"source\": {\n                    \"index\": old\n                },\n                \"dest\": {\n                    \"index\": new\n                }\n            })\n        except Exception as exc:\n            log.error(\n                \"failed to copy index {} to {} : {}\".format(old, new, exc))\n            raise exc\n\n    def delete_old_index(self, index):\n        log.warn(\"Deleting old index {}\".format(index))\n\n        try:\n            self.es_client.indices.delete(index)\n        except Exception as exc:\n            log.error(\"failed to delete old index {} : {}\".format(index, exc))\n            raise exc\n\n    def move_alias(self, alias, old_index, new_index):\n        log.warn(\n            \"Moving alias {} from {} to {}\".format(alias, old_index, new_index))\n\n        try:\n            self.es_client.indices.delete_alias(index=old_index, name=alias)\n            self.es_client.indices.put_alias(index=new_index, name=alias)\n        except Exception as exc:\n            log.error(\"failed to move alias {} : {}\".format(alias, exc))\n            raise exc\n"
  },
  {
    "path": "devtools/migrations/index_migration_v4_to_v5.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\nfrom caliopen_main.contact.objects.contact import Contact\nfrom caliopen_main.message.objects.message import Message\nimport requests\nimport json\n\nlog = logging.getLogger(__name__)\nlog.setLevel(logging.INFO)\n\nOLD_PREFIX = '_v4'\nNEW_PREFIX = '_v5'\n\n\nclass IndexMigrator(object):\n    def __init__(self, client=None, mappings_version=None, url=None):\n        self.es_client = client\n        self.types = (Contact(), Message())\n        self.mappings_version = mappings_version\n        self.url = url\n\n    def check_empty_index(self, index):\n        stats = self.es_client.indices.stats(index)\n        docs = stats['_all']['total']['docs']\n        if docs['count'] != 0:\n            return False\n        else:\n            old_index = '{}{}'.format(index[:-(len(NEW_PREFIX))], OLD_PREFIX)\n            if self.es_client.indices.exists(old_index):\n                log.warn('Delete the empty index {} with old version found'.\n                         format(index))\n                self.es_client.indices.close(index)\n                self.es_client.indices.delete(index)\n        return True\n\n    def run(self):\n        indexes = self.es_client.indices.get(\"_all\")\n        log.info(\"found {} indexes\".format(len(indexes)))\n        total = len(indexes)\n        ok = 0\n        errors = 0\n        count = 1\n        skip = 0\n        for index in indexes:\n            alias = index[:-(len(OLD_PREFIX))]\n            new_index = alias + \"_\" + self.mappings_version\n            if index.endswith(self.mappings_version):\n                if not self.check_empty_index(index):\n                    log.warn('Index {} already in wanted version'.\n                         format(index))\n                    skip += 1\n                    continue\n\n            if index.endswith(OLD_PREFIX):\n                if self.es_client.indices.exists(new_index):\n                    log.warn('Index {} exists, skipping'.format(new_index))\n                    skip += 1\n                    continue\n\n            log.info(\"\\nOperation {}/{}\".format(count, total))\n            count += 1\n\n            try:\n                self.create_new_index(new_index)\n                self.copy_old_to_new(index, new_index)\n            except Exception as exc:\n                log.warn(\"Aborting migration operations for {}\".format(index))\n                errors += 1\n                # do not try operations below if above reindexation has failed\n                continue\n\n            try:\n                self.move_alias(alias, index, new_index)\n                self.delete_old_index(index)\n                self.es_client.indices.refresh(alias)\n                self.fill_date_sort(alias)\n            except Exception:\n                errors += 1\n                continue\n\n            ok += 1\n\n        log.info(\"{} operations completed OK : {} \"\n                 \"Errors: {} Skip: {}\".format(count, ok, errors, skip))\n\n    def create_new_index(self, index):\n        \"\"\"Create user index and setups mappings.\"\"\"\n        log.info('Creating new index {}'.format(index))\n\n        try:\n            self.es_client.indices.create(\n                index=index,\n                timeout='30s',\n                body={\n                    \"settings\": {\n                        \"index\": {\n                            \"number_of_shards\": 3,\n                        },\n                        \"analysis\": {\n                            \"analyzer\": {\n                                \"text_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"lowercase\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                },\n                                \"email_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"email_tokenizer\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                }\n                            },\n                            \"filter\": {\n                                \"ascii_folding\": {\n                                    \"type\": \"asciifolding\",\n                                    \"preserve_original\": True\n                                }\n                            },\n                            \"tokenizer\": {\n                                \"email_tokenizer\": {\n                                    \"type\": \"ngram\",\n                                    \"min_gram\": 3,\n                                    \"max_gram\": 25\n                                }\n                            }\n                        }\n                    }\n                })\n        except Exception as exc:\n            log.error(\"failed to create index {} : {}\".format(index, exc))\n            raise exc\n\n        # PUT mappings for each type\n        for kls in self.types:\n            kls._index_class().create_mapping(index)\n\n    def copy_old_to_new(self, old, new):\n        log.info(\"Copying data from {} to {}\".format(old, new))\n\n        try:\n            self.es_client.reindex(timeout='1m', body={\n                \"source\": {\n                    \"index\": old\n                },\n                \"dest\": {\n                    \"index\": new\n                }\n            }, wait_for_completion=True)\n        except Exception as exc:\n            log.error(\n                \"failed to copy index {} to {} : {}\".format(old, new, exc))\n            raise exc\n\n    def delete_old_index(self, index):\n        log.warn(\"Deleting old index {}\".format(index))\n\n        try:\n            self.es_client.indices.delete(index)\n        except Exception as exc:\n            log.error(\"failed to delete old index {} : {}\".format(index, exc))\n            raise exc\n\n    def move_alias(self, alias, old_index, new_index):\n        log.info(\n            \"Moving alias {} from {} to {}\".format(alias, old_index, new_index))\n\n        try:\n            self.es_client.indices.delete_alias(index=old_index, name=alias)\n            self.es_client.indices.put_alias(index=new_index, name=alias)\n        except Exception as exc:\n            log.error(\"failed to move alias {} : {}\".format(alias, exc))\n            raise exc\n\n    def fill_date_sort(self, index):\n        \"\"\"\n        fill_date_sort updates all message docs in index\n        with either message's date or date_insert into date_sort,\n        depending of message being received or sent\n\n        :param index: Elasticsearch index\n        :return: None\n        \"\"\"\n        log.info(\"filling date_sort prop for index {}\".format(index))\n        q = {\n            \"script\": {\n                \"lang\": \"painless\",\n                \"inline\": \"if (ctx._source.is_received) {ctx._source.date_sort = ctx._source.date_insert} else {ctx._source.date_sort = ctx._source.date}\"\n            }\n        }\n        try:\n            resp = requests.post(self.url + \"/\" + index + \"/indexed_message/_update_by_query\", data=json.dumps(q), headers={'content-type': 'application/json'})\n            log.info(\"{} docs updated in {}\".format(resp.json()[\"updated\"], index))\n        except Exception as exc:\n            log.error(\n                \"failed to update_by_query index {} : {}\".format(index, exc))\n"
  },
  {
    "path": "devtools/migrations/index_migration_v5_to_v6.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\nfrom caliopen_main.contact.objects.contact import Contact\nfrom caliopen_main.message.objects.message import Message\n\nlog = logging.getLogger(__name__)\nlog.setLevel(logging.INFO)\n\nOLD_PREFIX = '_v5'\nNEW_PREFIX = '_v6'\n\n\nclass IndexMigrator(object):\n    def __init__(self, client=None, mappings_version=None, url=None):\n        self.es_client = client\n        self.types = (Contact(), Message())\n        self.mappings_version = mappings_version\n        self.url = url\n\n    def check_empty_index(self, index):\n        stats = self.es_client.indices.stats(index)\n        docs = stats['_all']['total']['docs']\n        if docs['count'] != 0:\n            return False\n        else:\n            old_index = '{}{}'.format(index[:-(len(NEW_PREFIX))], OLD_PREFIX)\n            if self.es_client.indices.exists(old_index):\n                log.warn('Delete the empty index {} with old version found'.\n                         format(index))\n                self.es_client.indices.close(index)\n                self.es_client.indices.delete(index)\n        return True\n\n    def run(self):\n        indexes = self.es_client.indices.get(\"_all\")\n        log.info(\"found {} indexes\".format(len(indexes)))\n        total = len(indexes)\n        ok = 0\n        errors = 0\n        count = 1\n        skip = 0\n        for index in indexes:\n            alias = index[:-(len(OLD_PREFIX))]\n            new_index = alias + \"_\" + self.mappings_version\n            if index.endswith(self.mappings_version):\n                if not self.check_empty_index(index):\n                    log.warn('Index {} already in wanted version'.\n                         format(index))\n                    skip += 1\n                    continue\n\n            if index.endswith(OLD_PREFIX):\n                if self.es_client.indices.exists(new_index):\n                    log.warn('Index {} exists, skipping'.format(new_index))\n                    skip += 1\n                    continue\n\n            log.info(\"\\nOperation {}/{}\".format(count, total))\n            count += 1\n\n            try:\n                self.create_new_index(new_index)\n                self.copy_old_to_new(index, new_index)\n            except Exception as exc:\n                log.warn(\"Aborting migration operations for {}\".format(index))\n                errors += 1\n                # do not try operations below if above reindexation has failed\n                continue\n\n            try:\n                self.move_alias(alias, index, new_index)\n                self.delete_old_index(index)\n                self.es_client.indices.refresh(alias)\n            except Exception:\n                errors += 1\n                continue\n\n            ok += 1\n\n        log.info(\"{} operations completed OK : {} \"\n                 \"Errors: {} Skip: {}\".format(count, ok, errors, skip))\n\n    def create_new_index(self, index):\n        \"\"\"Create user index and setups mappings.\"\"\"\n        log.info('Creating new index {}'.format(index))\n\n        try:\n            self.es_client.indices.create(\n                index=index,\n                timeout='30s',\n                body={\n                    \"settings\": {\n                        \"index\": {\n                            \"number_of_shards\": 3,\n                        },\n                        \"analysis\": {\n                            \"analyzer\": {\n                                \"text_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"lowercase\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                },\n                                \"email_analyzer\": {\n                                    \"type\": \"custom\",\n                                    \"tokenizer\": \"email_tokenizer\",\n                                    \"filter\": [\n                                        \"ascii_folding\"\n                                    ]\n                                }\n                            },\n                            \"filter\": {\n                                \"ascii_folding\": {\n                                    \"type\": \"asciifolding\",\n                                    \"preserve_original\": True\n                                }\n                            },\n                            \"tokenizer\": {\n                                \"email_tokenizer\": {\n                                    \"type\": \"ngram\",\n                                    \"min_gram\": 3,\n                                    \"max_gram\": 25\n                                }\n                            }\n                        }\n                    }\n                })\n        except Exception as exc:\n            log.error(\"failed to create index {} : {}\".format(index, exc))\n            raise exc\n\n        # PUT mappings for each type\n        for kls in self.types:\n            kls._index_class().create_mapping(index)\n\n    def copy_old_to_new(self, old, new):\n        log.info(\"Copying data from {} to {}\".format(old, new))\n\n        try:\n            self.es_client.reindex(timeout='1m', body={\n                \"source\": {\n                    \"index\": old\n                },\n                \"dest\": {\n                    \"index\": new\n                }\n            }, wait_for_completion=True)\n        except Exception as exc:\n            log.error(\n                \"failed to copy index {} to {} : {}\".format(old, new, exc))\n            raise exc\n\n    def delete_old_index(self, index):\n        log.warn(\"Deleting old index {}\".format(index))\n\n        try:\n            self.es_client.indices.delete(index)\n        except Exception as exc:\n            log.error(\"failed to delete old index {} : {}\".format(index, exc))\n            raise exc\n\n    def move_alias(self, alias, old_index, new_index):\n        log.info(\n            \"Moving alias {} from {} to {}\".format(alias, old_index, new_index))\n\n        try:\n            self.es_client.indices.delete_alias(index=old_index, name=alias)\n            self.es_client.indices.put_alias(index=new_index, name=alias)\n        except Exception as exc:\n            log.error(\"failed to move alias {} : {}\".format(alias, exc))\n            raise exc\n"
  },
  {
    "path": "devtools/migrations/set_date_sort_in_db.py",
    "content": "#!/usr/bin/env python\n# coding: utf8\n\"\"\"Extract recovery emails of registered users.\"\"\"\n\nfrom __future__ import unicode_literals\n\nimport argparse\nimport logging\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_storage.helpers.connection import connect_storage\nfrom cassandra.cqlengine.management import sync_table\n\nlog = logging.getLogger(__name__)\nlogging.basicConfig(level=logging.INFO)\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-f', dest='conffile')\n\n    args = parser.parse_args()\n    Configuration.load(args.conffile, 'global')\n    connect_storage()\n    from caliopen_main.message.store.message import Message\n\n    log.info(\"updating model for table message\")\n    sync_table(Message)\n    log.info(\"setting date_sort for {} messages\".format(Message.objects().count()))\n    for message in Message.all():\n        if message.date_sort is None:\n            message.date_sort = message.date_insert if message.is_received \\\n                else message.date\n        message.update()"
  },
  {
    "path": "devtools/migrations/update_ecdsa_key.py",
    "content": "#!/usr/bin/env python\n# coding: utf8\n\"\"\"Extract recovery emails of registered users.\"\"\"\n\nfrom __future__ import unicode_literals\n\nimport argparse\nimport logging\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_storage.helpers.connection import connect_storage\n\nlog = logging.getLogger(__name__)\nlogging.basicConfig(level=logging.INFO)\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-f', dest='conffile')\n\n    args = parser.parse_args()\n    Configuration.load(args.conffile, 'global')\n    connect_storage()\n    from caliopen_main.common.store import PublicKey\n    keys = PublicKey.all()\n    cpt = 0\n    for key in keys:\n        if key.resource_type == 'device' and key.use == 'sig':\n            key.x = None\n            key.y = None\n            log.info('Updating key %r' % key.key_id)\n            key.save()\n            cpt += 1\n    log.info('Update of %d keys' % cpt)\n"
  },
  {
    "path": "devtools/packages.yaml",
    "content": "repository:\n  base_directory: ..\n\npackages:\n  javascript:\n      -\n          name: client\n          build_target: release\n          description: proxy api, provides html & assets (js)\n          path: src/frontend/web_application\n          dependencies:\n            nodejs: \"^6.10\"\n\n  python:\n      -\n          name: caliopen_storage\n          description: Caliopen python storage package\n          path: src/backend/main/py.storage\n          dependencies:\n            python: \"^2.7\"\n      -\n          name: caliopen_main\n          description: Caliopen python main package\n          path: src/backend/main/py.main\n          dependencies:\n            python: \"^2.7\"\n      -\n          name: caliopen_pi\n          description: Caliopen python package related to privacy index\n          path: src/backend/components/py.pi\n          dependencies:\n            python: \"^2.7\"\n      -\n          name: caliopen_api\n          description: Caliopen python ReST API package\n          path: src/backend/interfaces/REST/py.server\n          dependencies:\n            python: \"^2.7\"\n      -\n          name: caliopen_cli\n          description: Caliopen Command Line Interface (CLI) tool\n          path: src/backend/tools/py.CLI\n          dependencies:\n            python: \"^2.7\"\n      -\n          name: caliopen_nats\n          description: Caliopen nats client\n          path: src/backend/interfaces/NATS/py.client\n          dependencies:\n            python: \"^2.7\"\n      -\n          name: caliopen_pgp\n          description: Caliopen PGP utilities\n          path: src/backend/components/py.pgp\n          dependencies:\n            python: \"^2.7\"\n      -\n          name: caliopen_data\n          description: Caliopen data utilities\n          path: src/backend/components/py.data\n          dependencies:\n            python: \"^2.7\"\n      -\n          name: caliopen_tag\n          description: Caliopen message tagging model\n          path: src/backend/components/py.tag\n          dependencies:\n            python: \"^2.7\"\n      -\n          name: caliopen_climl\n          description: Caliopen Command Line Interface (CLI) tool for ML models\n          path: src/backend/tools/py.ML\n          dependencies:\n            python: \"^2.7\"\n\n  golang:\n      -\n          name: caliopen_rest\n          build_target: github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/cmd/caliopen_rest\n          path: src/backend/interfaces/REST/go.server\n          description: Caliopen ReST API server\n          dependencies:\n            go: \"^1.7\"\n      -\n          name: caliopen_lmtpd\n          build_target: github.com/CaliOpen/Caliopen/src/backend/protocols/go.smtp/cmd/caliopen_lmtpd\n          path: src/backend/protocols/go.smtp\n          description: Caliopen LMTP daemon\n          dependencies:\n            go: \"^1.7\"\n      -\n          name: imapworker\n          build_target: github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap/cmd/imapworker\n          path: src/backend/protocols/go.imap\n          description: IMAP daemon to handle transactions with external IMAP servers.\n          dependencies:\n            go: \"^1.7\"\n      -\n        name: twitterworker\n        build_target: github.com/CaliOpen/Caliopen/src/backend/protocols/go.twitter/cmd/twitterworker\n        path: src/backend/protocols/go.twitter\n        description: Twitter daemon to handle transactions with Twitter API endpoints.\n        dependencies:\n          go: \"^1.7\"\n      -\n        name: mastodonworker\n        build_target: github.com/CaliOpen/Caliopen/src/backend/protocols/go.mastodon/cmd/mastodonworker\n        path: src/backend/protocols/go.mastodon\n        description: Mastodon daemon to handle transactions with Mastodon API endpoints.\n        dependencies:\n          go: \"^1.7\"\n      -\n          name: idpoller\n          build_target: github.com/CaliOpen/Caliopen/src/backend/workers/go.remoteIDs/cmd/idpoller\n          path: src/backend/workers/go.remoteIDs\n          description: daemon that trigger polling operations.\n          dependencies:\n            go: \"^1.7\"\n      -\n          name: gocaliopen\n          build_target: github.com/CaliOpen/Caliopen/src/backend/tools/go.CLI/cmd/gocaliopen\n          path: src/backend/tools/go.CLI\n          description: another CLI to manage Caliopen stack.\n          dependencies:\n            go: \"^1.7\"\n"
  },
  {
    "path": "devtools/publish-images.sh",
    "content": "#!/bin/bash\nset -e\n\nAPPS=\"apiv1 apiv2 cli frontend lmtpd mqworker identitypoller imapworker twitterworker\"\nSTAGE=$1\nVERSION=\"${CALIOPEN_VERSION}\"\nsource ./registry.conf\n\nif [[ ${STAGE} =~ ^(develop|master)$ ]]\nthen\n\tgit checkout ${STAGE}\n\tfor app in ${APPS}; do\n\t\tmake ${STAGE} APP_NAME=$app\n\t\tif [[ $? -ne 0 ]]\n\t\tthen\n\t\t\techo \"Fail to build and publish ${app}\"\n\t\t\texit $?\n\t\tfi\n\tdone\nelse\n\techo \"Options are develop and master\" \n\texit 1\nfi\n"
  },
  {
    "path": "devtools/registry.conf.template",
    "content": "DOCKER_REPO=\"registry.caliopen.org\"\nREPO_USER=\"\"\nREPO_PASS=\"\""
  },
  {
    "path": "devtools/run-tests.sh",
    "content": "#!/bin/bash\nset -ev\n\nCURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD`\nTARGET_BRANCH=${TRAVIS_BRANCH:=\"master\"}\nPROJECT_DIRECTORY=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )/..\" && pwd )\"\n\nif [[ \"${CURRENT_BRANCH}\" == \"master\" ]]; then\n    # IF something happen on master, test everything\n    BACKEND_CHANGE=\"yes\"\n    FRONTEND_CHANGE=\"yes\"\nelse\n    BACKEND_CHANGE=`(cd $PROJECT_DIRECTORY && git diff-tree --no-commit-id --name-only -r HEAD..$TARGET_BRANCH -- src/backend)`\n    FRONTEND_CHANGE=`(cd $PROJECT_DIRECTORY && git diff-tree --no-commit-id --name-only -r HEAD..$TARGET_BRANCH -- src/frontend)`\nfi\n\ncd ${PROJECT_DIRECTORY}/devtools\n\nfunction do_backend_tests {\n    # Test build of backend Docker containers\n    docker build -f ${PROJECT_DIRECTORY}/src/backend/Dockerfile.caliopen-go -t public-registry.caliopen.org/caliopen_go ../src/backend --no-cache\n    docker build -f ${PROJECT_DIRECTORY}/src/backend/Dockerfile.caliopen-python -t public-registry.caliopen.org/caliopen_py ../src/backend --no-cache\n    docker-compose build apiv2 lmtpd identitypoller imapworker twitterworker apiv1 cli mqworker\n}\n\nfunction do_frontend_tests {\n    # Test build of frontend Docker containers\n    docker-compose build frontend\n}\n\n\nif [[ \"x${BACKEND_CHANGE}\" != \"x\" ]]; then\n    echo \"##### Doing backend tests\"\n    do_backend_tests\nfi\n\nif [[ \"x${FRONTEND_CHANGE}\" != \"x\" ]]; then\n    echo \"##### Doing frontend tests\"\n    do_frontend_tests\nfi\n"
  },
  {
    "path": "devtools/setup-virtualenv.sh",
    "content": "#!/bin/bash\nCALIOPEN_BASE_DIR=$(pwd)/..\nCALIOPEN_BACKEND_DIR=${CALIOPEN_BASE_DIR}/src/backend\n\n\ncd \"${CALIOPEN_BASE_DIR}\"\nif [[ -d \".venv\" ]]; then\n    echo \"Virtual environment exist, drop it first\"\n    # exit 1\nfi\n\nvirtualenv --python=python2.7 .venv\n. .venv/bin/activate\n\nif [[ \"x${VIRTUAL_ENV}\" == \"x\" ]]; then\n    echo 'Virtual environment did not activate'\n    exit 1\nfi\n\n\n# Force installation of regex using pip\npip install regex\n\n# Install github version of fasttext not pip version really too old\npip install git+https://github.com/facebookresearch/fastText.git\n\nCOMPONENTS=\"main/py.storage components/py.pgp components/py.pi components/py.data components/py.tag main/py.main interfaces/REST/py.server tools/py.doc interfaces/NATS/py.client tools/py.CLI tools/py.ML\"\n\nfor comp in ${COMPONENTS}:\ndo\n\n    cd \"${CALIOPEN_BACKEND_DIR}/${comp}\"\n    if [[ -f \"requirements.txt\" ]]; then\n        pip install -r requirements.txt\n    fi\n    pip install -e .\n    if [[ $? -ne 0 ]]; then\n        echo \"Error during installation of component ${comp}\"\n        exit 2\n    fi\n\ndone\n\n# Extra packages for development\npip install ipdb\npip install docker-compose\npip install gitpython\npip install nose\n\necho \"All done, your virtual environment contain these packages :\"\npip freeze\n\ncd \"${CALIOPEN_BASE_DIR}\"\n\necho \"Do not forget to activate it\"\n"
  },
  {
    "path": "devtools/start.sh",
    "content": "#!/bin/sh\n\narg0=$(basename \"$0\" .sh)\nblnk=$(echo \"$arg0\" | sed 's/./ /g')\n\nusage_info()\n{\n    echo \"Usage: $arg0 [{-d|--dev} app] \\\\\"\n    echo\n    echo \"Start Caliopen using docker in local environment\"\n    echo \"  supported apps for development: frontend\"\n}\n\nusage()\n{\n    exec 1>2   # Send standard output to standard error\n    usage_info\n    exit 1\n}\n\nerror()\n{\n    echo \"$arg0: $*\" >&2\n    exit 1\n}\n\nhelp()\n{\n    usage_info\n    echo\n    echo \"Options:\"\n    echo\n    echo \"  {-h|--help}               -- show this help then exit\"\n    echo \"  {-d|--dev} app            -- define which App to dev\"\n    exit 0\n}\n\nflags()\n{\n    while test $# -gt 0\n    do\n        case \"$1\" in\n        (-d|--dev)\n            shift\n            [ $# = 0 ] && error \"A App is required for development\"\n            export DEV_APP=\"$1\"\n            shift;;\n        (-h|--help)\n            help;;\n        (*) usage;;\n        esac\n    done\n}\n\nflags \"$@\"\n\nset +e\nset -v\nsudo sysctl -w vm.max_map_count=262144\n\nset -e\n\n# init db & co\ndocker-compose run cli -h\ndocker-compose up -d objectstore\n# sleep 60\ndocker-compose up -d proxyapi lmtpd identitypoller\n\nif [ \"$DEV_APP\" = \"frontend\" ]; then\n  echo \"start frontend in dev mode\"\n  docker-compose up frontend_dev\nelse\n    docker-compose up -d frontend\nfi\n"
  },
  {
    "path": "devtools/stop.sh",
    "content": "#!/bin/bash\n\nset -e\ndocker-compose stop\n"
  },
  {
    "path": "devtools/storybook/.storybook/addons.js",
    "content": "import '@kadira/storybook/addons';\nimport '@kadira/storybook-addon-knobs/register';\nimport 'react-storybook-addon-backgrounds/register';\n"
  },
  {
    "path": "devtools/storybook/.storybook/config.js",
    "content": "import { configure, setAddon } from '@kadira/storybook';\nimport infoAddon from '@kadira/react-storybook-addon-info';\n\nsetAddon(infoAddon);\n\nfunction loadStories() {\n  require('../stories');\n}\n\nconfigure(loadStories, module);\n"
  },
  {
    "path": "devtools/storybook/.storybook/webpack.config.js",
    "content": "const path = require('path');\nconst webpack = require('webpack');\n\nmodule.exports = function configure(storybookBaseConfig) {\n  storybookBaseConfig.module.loaders.push(\n    {\n      test: /\\.css$/,\n      loaders: ['style', 'css'],\n    },\n    {\n      test: /\\.scss$/,\n      loaders: ['style', 'css', 'sass'],\n    },\n    {\n      test: /\\.(jpe?g|png|gif|svg)$/i,\n      loaders: [\n        'file?hash=sha512&digest=hex&name=[hash].[ext]',\n        'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false',\n      ],\n    },\n    { test: /\\.svg(\\?v=\\d+\\.\\d+\\.\\d+)?$/, loader: 'file-loader?mimetype=image/svg+xml' },\n    { test: /\\.woff(\\?v=\\d+\\.\\d+\\.\\d+)?$/, loader: 'file-loader?mimetype=application/font-woff' },\n    { test: /\\.woff2(\\?v=\\d+\\.\\d+\\.\\d+)?$/, loader: 'file-loader?mimetype=application/font-woff' },\n    { test: /\\.ttf(\\?v=\\d+\\.\\d+\\.\\d+)?$/, loader: 'file-loader?mimetype=application/octet-stream' },\n    { test: /\\.eot(\\?v=\\d+\\.\\d+\\.\\d+)?$/, loader: 'file-loader' },\n    { test: /\\.md$/, loader: 'raw-loader' },\n    { test: /\\.json$/, loader: 'json-loader' }\n  );\n  storybookBaseConfig.plugins.push(\n    new webpack.DefinePlugin({\n      CALIOPEN_ENV: JSON.stringify(process.env.NODE_ENV),\n      BUILD_TARGET: JSON.stringify('browser'),\n      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),\n    })\n  );\n  storybookBaseConfig.sassLoader = {\n    includePaths: [\n      path.resolve(__dirname, '../src'),\n      path.resolve(__dirname, '../node_modules/foundation-sites/scss'),\n      path.resolve(__dirname, '../node_modules/font-awesome/scss'),\n    ],\n  };\n\n  storybookBaseConfig.entry.preview.unshift('script-loader!jquery', 'script-loader!foundation-sites');\n\n  return storybookBaseConfig;\n};\n"
  },
  {
    "path": "devtools/storybook/README.md",
    "content": "# This folder is the react storybook for Caliopen's components\n\n**// FIXME**\n\nIt is actually in a migration operation, it requires some steps to make it work:\n\n* [ ] update to [latest storybook](https://storybook.js.org/basics/guide-react/)\n* [ ] split caliopen client at lease in UI/client\n"
  },
  {
    "path": "devtools/storybook/package.json",
    "content": "{\n  \"name\": \"@Caliopen/storybook\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"start\": \"start-storybook -p 6006 --dont-track\",\n    \"build\": \"build-storybook -o dist/storybook\"\n  },\n  \"dependencies\": {\n    \"@caliopen/frontend\": \"../../src/frontend/web_application\",\n    \"@kadira/react-storybook-addon-info\": \"^3.4.0\",\n    \"@kadira/storybook\": \"^2.21.0\",\n    \"@kadira/storybook-addon-knobs\": \"^1.7.1\",\n    \"react\": \"^16.0.0\",\n    \"react-dom\": \"^16.0.0\",\n    \"react-storybook-addon-backgrounds\": \"^0.0.7\",\n    \"storybook-host\": \"^4.1.4\"\n  }\n}\n"
  },
  {
    "path": "devtools/storybook/stories/CHANGELOG.md",
    "content": "# Storybook Changelog\n\n## [Unreleased]\n\n### Added\n\n  - DropdownMenu\n  - MultidimensionalPi\n  - IconLetter simple refactor of ContactIconLetter\n  - Layout - TabList Presenter\n  - ImportContactForm \n\n### Changed\n\n  - Separate presentation from logic of Dropdown by adding DropdownMenu\n  - remove styles from foundation's dropdown & use own styles\n\n### Removed\n\n  - DropdownController from Dropdown wich has been replaced by withDropdownControl HoC\n\n## [2017-02-27]\n\n### Added\n\n  - Settings - DevicesManagement\n  - Dropdown\n  - Pi - PiBar\n  - Layout - Section\n  - Lists - DefList\n  - form - CollectionFieldGroup\n\n### Changed\n\n  - Form - Field with error: no more uppercase for the message & replace background color by a red border (inset)\n  - Button - Add \"inline\" prop, to be used for Button placed beside inputs (dark background, 1px left margin, small font)\n  - Badge - remove no-radius, added radiusType [no|normal|rounded] \"rounded\" is not default anymore\n  - Config - change radius size to .2rem (previous .125rem)\n\n### Removed\n\n  - BlockList - background color and ItemContent have been removed\n\n## [2017-01-19]\n\n### Added\n\n  - Icons & &Avatars - Brand\n  - Titles - Title\n  - Form - PasswordStrength, CheckboxFieldGroup\n  - Auth - AuthPage, SignupForm\n\n### Changed\n\n  - Switch has been moved into CheckboxFieldGroup\n  - input, select have been re-designed following UI specs\n\n## [2017-01-01]\n\n### Added\n\n  - Badge\n  - Lists - BlockList, TextList\n  - Button & Link\n  - Icons & &Avatars - ContactAvatarLetter, Icon, Spinner\n  - Titles - Subtitle\n  - Form - Fieldset, FormGrid, TextFieldGroup, SelectFieldGroup, RadioFieldGroup, Switch\n  - Contact - ContactDetails\n"
  },
  {
    "path": "devtools/storybook/stories/Changelog.jsx",
    "content": "import React from 'react';\nimport Markdown from 'react-markdown';\nimport input from './CHANGELOG.md';\n\nconst styles = {\n  main: {\n    margin: 30,\n  },\n};\n\nconst Changelog = () => (\n  <div style={styles.main}>\n    <Markdown source={input} />\n  </div>\n);\n\nexport default Changelog;\n"
  },
  {
    "path": "devtools/storybook/stories/Guideline/index.jsx",
    "content": "import React from 'react';\nimport './style.scss';\nimport imageExample from '../assets/example.png';\n\nconst styles = {\n  main: {\n    margin: 15,\n    maxWidth: 600,\n    lineHeight: 1.4,\n    fontFamily: '\"Helvetica Neue\", Helvetica, \"Segoe UI\", Arial, freesans, sans-serif',\n  }\n};\n\nconst Guideline = () => (\n  <div style={styles.main}>\n    <h1>Guidelines</h1>\n\n    <h2>Typography</h2>\n\n    <p className=\"typo\">Font sizes: ...</p>\n\n    <h2>Spacing</h2>\n\n    <p>Space between two elements</p>\n\n    <h2>Height</h2>\n\n    <p>2.6rem (~42px) the minimum value for touchable elements</p>\n\n    <h2>Examples</h2>\n\n    <p><img src={imageExample} alt=\"example\" /></p>\n  </div>\n);\n\nexport default Guideline;\n"
  },
  {
    "path": "devtools/storybook/stories/Guideline/style.scss",
    "content": ".typo {\n  color: red;\n}\n"
  },
  {
    "path": "devtools/storybook/stories/Welcome.jsx",
    "content": "import React from 'react';\n\nconst styles = {\n  main: {\n    margin: 15,\n    maxWidth: 600,\n    lineHeight: 1.4,\n    fontFamily: '\"Helvetica Neue\", Helvetica, \"Segoe UI\", Arial, freesans, sans-serif',\n  },\n\n  logo: {\n    width: 200,\n  },\n\n  link: {\n    color: '#1474f3',\n    textDecoration: 'none',\n    borderBottom: '1px solid #1474f3',\n    paddingBottom: 2,\n  },\n\n  code: {\n    fontSize: 15,\n    fontWeight: 600,\n    padding: \"2px 5px\",\n    border: \"1px solid #eae9e9\",\n    borderRadius: 4,\n    backgroundColor: '#f3f2f2',\n    color: '#3a3a3a',\n  },\n};\n\nexport default class Welcome extends React.Component {\n  showApp(e) {\n    e.preventDefault();\n    if(this.props.showApp) this.props.showApp();\n  }\n\n  render() {\n    return (\n      <div style={styles.main}>\n        <h1>STORYBOOK for Caliopen</h1>\n        <p>\n          This is the UI component dev environment for <a target=\"_top\" href=\"https://caliopen.github.io\">CaliOpen</a>.\n        </p>\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "devtools/storybook/stories/components/BlockList.jsx",
    "content": "import React, { Component } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport BlockList from '../../src/components/BlockList';\nimport Link from '../../src/components/Link';\nimport { Code, ComponentWrapper } from '../presenters';\n\nclass BlockLists extends Component {\n  render() {\n    return (\n      <div>\n        <ComponentWrapper size=\"tall\">\n          <BlockList style={{ backgroundColor: '#333' }}>\n            {[\n              <Link noDecoration>A link</Link>,\n              <Link noDecoration>An other link</Link>,\n              'Foo',\n              'Bar',\n            ]}\n          </BlockList>\n        </ComponentWrapper>\n        <Code>{`\nimport BlockList from './src/components/BlockList';\n\nexport default () => (\n  <BlockList style={{ backgroundColor: '#333' }}>\n    {[\n      <Link noDecoration>A link</Link>,\n      <Link noDecoration>An other link</Link>,\n      'Foo',\n      'Bar',\n    ]}\n  </BlockList>\n);\n          `}</Code>\n      </div>\n    );\n  }\n}\n\nexport default BlockLists;\n"
  },
  {
    "path": "devtools/storybook/stories/components/CollectionFieldGroup.jsx",
    "content": "import React, { Component, PropTypes } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport { array } from '@kadira/storybook-addon-knobs';\nimport { CollectionFieldGroup, TextFieldGroup } from '../../src/components/form';\nimport { Code, ComponentWrapper } from '../presenters';\n\nconst Presenter = () => {\n  const addTemplate = ({ item, onChange, ...props }) => {\n    const handleChange = ev => onChange({ item: ev.target.value });\n\n    return (\n      <TextFieldGroup label=\"Add an item\" showLabelforSr value={item} onChange={handleChange} {...props} />\n    );\n  };\n\n  const editTemplate = ({ item, onChange, ...props }) => {\n    const handleChange = ev => onChange({ item: ev.target.value });\n\n    return (\n      <TextFieldGroup label=\"An item\" showLabelforSr value={item} onChange={handleChange} {...props} />\n    );\n  };\n\n  const props = {\n    collection: array('collection', ['foo', 'bar', 'bazzz']),\n  };\n\n  return (\n    <div>\n      <ComponentWrapper inline >\n        <CollectionFieldGroup\n          onChange={action('changed')}\n          addTemplate={addTemplate}\n          editTemplate={editTemplate}\n          {...props}\n        />\n      </ComponentWrapper>\n      <Code>\n        {`\nimport { CollectionFieldGroup } from './src/components/form';\n\nconst collection = ['foo', 'bar', 'bazzz'];\nconst handleChange = (updatedColl) => {\n// do the things with updated collection\n};\n\nconst addTemplate = ({ item, onChange, ...props }) => {\nconst handleChange = ev => onChange({ item: ev.target.value });\n\nreturn (\n  <TextFieldGroup label=\"Add an item\" showLabelforSr value={item} onChange={handleChange} {...props} />\n);\n};\n\nconst editTemplate = ({ item, onChange, ...props }) => {\nconst handleChange = ev => onChange({ item: ev.target.value });\n\nreturn (\n  <TextFieldGroup label=\"An item\" showLabelforSr value={item} onChange={handleChange} {...props} />\n);\n};\n\n<CollectionFieldGroup\ncollection={collection}\nonChange={action('changed')}\naddTemplate={addTemplate}\neditTemplate={editTemplate}\n/>\n        `}\n      </Code>\n    </div>\n  );\n};\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/components/ContactDetails.jsx",
    "content": "import React, { Component } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport { object, boolean } from '@kadira/storybook-addon-knobs';\nimport ContactDetails from '../../src/components/ContactDetails';\nimport { Code, ComponentWrapper } from '../presenters';\n\nconst Presenter = () => {\n    const contact = {\n      title: 'Foobar',\n      emails: [\n        { address: 'abc@bar.tld', type: 'work', email_id: 'foo1' },\n        { address: 'def@isprimary.tld', is_primary: true, type: 'home' },\n        { address: 'abc@notbar.tld' },\n      ],\n      phones: [\n        { number: '+33678912345' },\n      ],\n      ims: [\n        { address: 'abc@bar.tld', type: 'work' },\n      ],\n      addresses: [\n        { street: '42 is the answer', postal_code: '12345', city: 'Everywhere', country: 'on The Internet', label: 'An address', type: 'work' },\n      ],\n    };\n    const remoteIdentities = [\n      { identity_type: 'email', identity_id: 'foo1', is_fetching: true, connected: true },\n    ];\n\n    const translate = str => str;\n    const props = {\n      allowConnectRemoteEntity: boolean('allowConnectRemoteEntity', false),\n    };\n\n    return (\n      <div>\n        <ComponentWrapper>\n          <ContactDetails\n            contact={object('contact', contact)}\n            remoteIdentities={object('remoteIdentities', remoteIdentities)}\n            onConnectRemoteIdentity={action('connect remote identity')}\n            onDisconnectRemoteIdentity={action('disconnect remote identity')}\n            onAddContactDetail={action('add contact detail')}\n            onDeleteContactDetail={action('delete contact detail')}\n            __={translate}\n            {...props}\n          />\n        </ComponentWrapper>\n        <Code>\n          {`\nimport ContactDetails, { SIZE_SMALL } from './src/components/ContactDetails';\n\nexport default () => (\n  <ContactDetails\n    contact={contact}\n    remoteIdentities={remoteIdentities}\n    onConnectRemoteIdentity={action('connect remote identity')}\n    onDisconnectRemoteIdentity={action('disconnect remote identity')}\n    onAddContactDetail={action('add contact detail')}\n    onDeleteContactDetail={action('delete contact detail')}\n    allowConnectRemoteEntity\n    __={translate}\n  />\n);\n          `}\n        </Code>\n      </div>\n    );\n};\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/components/FormGrid.jsx",
    "content": "import React, { Component } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport { FormGrid, FormRow, FormColumn } from '../../src/components/form';\nimport { Code, ComponentWrapper } from '../presenters';\n\nconst styles = {\n  formColumnSizedFuild: {\n    border: '1px solid #881f0e',\n  },\n  formColumnSmall: {\n    backgroundColor: '#ec5840',\n    border: '1px solid #881f0e',\n  },\n  formColumnNormal: {\n    backgroundColor: '#eee',\n    border: '1px solid #1395ba',\n  },\n  formColumnShrink: {\n    backgroundColor: '#3adb76',\n    border: '1px solid #157539',\n  },\n  formColumnMedium: {\n    backgroundColor: '#1395ba',\n    border: '1px solid #0a4b5d',\n  },\n  formColumnLarge: {\n    backgroundColor: '#ff0',\n    border: '1px solid #ffae00',\n  },\n  formColumnFluid: {\n    backgroundColor: '#424242',\n    border: '1px solid #8a8a8a',\n  },\n};\n\nclass Presenter extends Component {\n  render() {\n    return (\n      <div>\n        <ComponentWrapper>\n          <FormGrid>\n            <FormRow>\n              <FormColumn style={styles.formColumnNormal}>Col normal</FormColumn>\n              <FormColumn style={styles.formColumnNormal}>Col normal</FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn size=\"shrink\" style={styles.formColumnShrink}>Col shrink</FormColumn>\n              <FormColumn size=\"large\" style={styles.formColumnLarge}>Col large</FormColumn>\n              <FormColumn style={styles.formColumnNormal}>Col normal</FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn size=\"medium\" style={styles.formColumnMedium}>Col medium</FormColumn>\n              <FormColumn size=\"medium\" style={styles.formColumnMedium}>Col medium</FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn size=\"small\" style={styles.formColumnSmall}>Col small</FormColumn>\n              <FormColumn size=\"small\" style={styles.formColumnSmall}>Col small</FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn size=\"small\" fluid style={styles.formColumnSizedFuild}>Col small fluid</FormColumn>\n              <FormColumn size=\"small\" fluid style={styles.formColumnSizedFuild}>Col small fluid</FormColumn>\n              <FormColumn size=\"small\" style={styles.formColumnSmall}>Col small</FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn fluid style={styles.formColumnFluid}>Col fluid</FormColumn>\n              <FormColumn fluid style={styles.formColumnFluid}>Col fluid</FormColumn>\n            </FormRow>\n          </FormGrid>\n        </ComponentWrapper>\n        <Code>\n          {`\nimport { FormGrid, FormRow, FormColumn } from './src/components/form';\n\nexport default () => (\n  <FormGrid>\n    <FormRow>\n      <FormColumn>Col normal</FormColumn>\n      <FormColumn>Col normal</FormColumn>\n    </FormRow>\n    <FormRow>\n      <FormColumn size=\"shrink\">Col shrink</FormColumn>\n      <FormColumn size=\"large\">Col large</FormColumn>\n      <FormColumn>Col normal</FormColumn>\n    </FormRow>\n    <FormRow>\n      <FormColumn size=\"medium\">Col medium</FormColumn>\n      <FormColumn size=\"medium\">Col medium</FormColumn>\n    </FormRow>\n    <FormRow>\n      <FormColumn size=\"small\">Col small</FormColumn>\n      <FormColumn size=\"small\">Col small</FormColumn>\n    </FormRow>\n  </FormGrid>\n);\n          `}\n        </Code>\n      </div>\n    );\n  }\n}\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/components/Link.jsx",
    "content": "import React from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport { boolean, text } from '@kadira/storybook-addon-knobs';\nimport Link from '../../src/components/Link';\nimport { Code, ComponentWrapper } from '../presenters';\n\nconst Links = () => {\n  const props = {\n    noDecoration: boolean('noDecoration', false),\n    button: boolean('button', false),\n    expanded: boolean('expanded', false),\n    active: boolean('active', false),\n  };\n\n  return (\n    <div>\n      <ComponentWrapper>\n        <Link {...props}>{text('Link children', 'Click Me')}</Link>\n      </ComponentWrapper>\n      <Code>\n        {`\nimport Link from './src/components/Link';\nexport default () => (<Link noDecoration button expanded active />);\n        `}\n      </Code>\n    </div>\n  );\n};\n\nexport default Links;\n"
  },
  {
    "path": "devtools/storybook/stories/components/MessageList.jsx",
    "content": "import React, { Component } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport MessageList from '../../src/components/MessageList/presenter';\nimport { Code, ComponentWrapper } from '../presenters';\n\nclass Presenter extends Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      props: {\n        level: 50,\n      },\n    };\n    this.handleInputChange = this.handleInputChange.bind(this);\n  }\n\n  handleInputChange(event) {\n    const { name, value } = event.target;\n\n    this.setState(prevState => ({\n      props: {\n        ...prevState.props,\n        [name]: value,\n      },\n    }));\n  }\n\n  render() {\n    const noop = str => str;\n    const draftMessage = {\n      body: 'Fooo',\n    };\n\n    const messages = [\n      { message_id: '1', date: (new Date()).toJSON(), type: 'email', participants: [{ address: 'bender@caliopen.local', type: 'from' }, { address: 'zoidberg@caliopen.local', type: 'cc' }, { address: 'me@caliopen.local', type: 'to' }], body: '<p>It\\'s okay, Bender. I like cooking too. You, minion. Lift my arm. AFTER H=\\r\\nIM! I don\\'t know what you did, Fry, but once again, you screwed up! Now all\\r\\nthe planets are gonna start cracking wise.</p>', subject: '' },\n      { message_id: '2', date: (new Date()).toJSON(), type: 'email', participants: [{ address: 'bender@caliopen.local', type: 'to' }, { address: 'zoidberg@caliopen.local', type: 'from' }, { address: 'me@caliopen.local', type: 'to' }], body: '<p>Shut up and take my money! Leela, are you alright?</p>', subject: \"Re: Dr. Zoidberg, that doesn't make sense. But, okay!\" },\n      { message_id: '3', date: (new Date('2017-01-03')).toJSON(), type: 'email', participants: [{ address: 'bender@caliopen.local', type: 'to' }, { address: 'zoidberg@caliopen.local', type: 'to' }, { address: 'me@caliopen.local', type: 'from' }], body: \"<p>It's okay, Bender. I like cooking too. You, minion. Lift my arm. AFTER H=\\r\\nIM! I don't know what you did, Fry, but once again, you screwed up! Now all\\r\\nthe planets are gonna start cracking wise about our mamas.</p>\\r\\n<p>You don't know how to do any of those. Leela, Bender, we're going grave\\r\\nrobbing. And yet you haven't said what I told you to say! How can any of us\\r\\ntrust you? Leela's gonna kill me. File not found.</p>\\r\\n<h2>Fry! Quit doing the right thing, you jerk!</h2>\\r\\n<p>Daddy Bender, we're hungry. Goodbye, cruel world. Goodbye, cruel lamp. G=\\r\\noodbye, cruel velvet drapes, lined with what would appear to be some sort of\\r\\n cruel muslin and the cute little pom-pom curtain pull cords. Cruel though\\r\\n they may be\\u2026</p>\\r\\n<ol>\\r\\n\\r\\n    <li>Switzerland is small and neutral! We are more like Germany, ambitio=\\r\\n\\t\\tus and misunderstood!</li><li>For the last time, I don't like lilacs! Y=\\r\\n\\t\\tour 'first' wife was the one who liked lilacs!</li><li>Isn't it true th=\\r\\n\\t\\tat you have been paid for your testimony?</li>\\r\\n\\r\\n</ol>\\r\\n\\r\\n<h3>I'm sorry, guys. I never meant to hurt you. Just to destroy everything\\r\\nyou ever believed in.</h3>\\r\\n<p>Of all the friends I've had\\u2026 you're the first. I'm a thing. I was all of\\r\\nhistory's great robot actors - Acting Unit 0.8; Thespomat; David Duchovny!\\r\\nGoodbye, friends. I never thought I'd die like this. But I always really ho=\\r\\nped.</p>\\r\\n<ul>\\r\\n\\r\\n    <li>Is the Space Pope reptilian!?</li><li>Five hours? Aw, man! Couldn't\\r\\n\\t\\tyou just get me the death penalty?</li><li>My fellow Earthicans, as I hav=\\r\\n\\t\\te explained in my book 'Earth in the Balance'', and the much more popular\\r\\n\\t\\t''Harry Potter and the Balance of Earth', we need to defend our planet ag=\\r\\n\\t\\tainst pollution.</p>\", subject: \"Re: Dr. Zoidberg, that doesn't make sense. But, oka\" },\n    ];\n\n    const props = {\n      onReply: action('onReply'),\n      onForward: action('onForward'),\n      onDelete: action('onDelete'),\n      __: noop,\n    };\n\n    return (\n      <div>\n        <ComponentWrapper>\n          <MessageList messages={messages} {...props} />\n        </ComponentWrapper>\n        <Code>\n          {`\n            import MessageList from './src/components/MessageList';\n\n            export default ({ messages = [] }) => {\n              return (\n                <MessageList messages={messages}  />\n              );\n            }\n          `}\n        </Code>\n      </div>\n    );\n  }\n}\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/components/PasswordStrength.jsx",
    "content": "import React from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport { number } from '@kadira/storybook-addon-knobs';\nimport PasswordStrength from '../../src/components/form/PasswordStrength';\nimport { Code, ComponentWrapper } from '../presenters';\n\nconst Presenter = () => {\n  const props = {\n    strength: number('strength', 4),\n  };\n\n  return (\n    <div>\n      <ComponentWrapper>\n        <PasswordStrength {...props} />\n      </ComponentWrapper>\n      <Code>\n        {`\nimport PasswordStrength from './src/components/PasswordStrength';\n\n// strength is a number between 0 & 4 usually, the score of https://github.com/dropbox/zxcvbn\nexport default () => (<PasswordStrength strength={ strength } />);\n        `}\n      </Code>\n    </div>\n  );\n};\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/components/PiBar.jsx",
    "content": "import React from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport { number } from '@kadira/storybook-addon-knobs';\nimport PiBar from '../../src/components/PiBar';\nimport { Code, ComponentWrapper } from '../presenters';\n\nconst Presenter = () => {\n  const props = {\n    level: number('level', 50),\n  };\n\n  return (\n    <div>\n      <ComponentWrapper>\n        <PiBar {...props} />\n      </ComponentWrapper>\n      <Code>\n        {`\nimport PiBar from './src/components/PiBar';\n\n// level: number between 0 and 100\nexport default () => (<PiBar level={ level } />);\n        `}\n      </Code>\n    </div>\n  );\n};\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/components/Reply.jsx",
    "content": "import React, { Component } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport { object } from '@kadira/storybook-addon-knobs';\nimport ReplyForm from '../../src/components/ReplyForm';\nimport { Code, ComponentWrapper } from '../presenters';\n\nclass Presenter extends Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      props: {\n        level: 50,\n      },\n    };\n    this.handleInputChange = this.handleInputChange.bind(this);\n  }\n\n  handleInputChange(event) {\n    const { name, value } = event.target;\n\n    this.setState(prevState => ({\n      props: {\n        ...prevState.props,\n        [name]: value,\n      },\n    }));\n  }\n\n  render() {\n    const draftMessage = {\n      body: 'Fooo',\n    };\n\n    return (\n      <div>\n        <ComponentWrapper>\n          <ReplyForm draftMessage={object('draftMessage', draftMessage)} onSave={action('save')} onSend={action('send')} onChange={action('change')} />\n        </ComponentWrapper>\n        <Code>\n          {`\n            import ReplyForm from './src/components/ReplyForm';\n\n            export default () => {\n              const handleSave = () => { };\n              const handleSend = () => { };\n              const handleChange = () => { };\n              const draftMessage = {\n                body: 'Fooo',\n              };\n\n              return (\n                <ReplyForm draftMessage={draftMessage} onSave={handleSave} onSend={handleSend} onChange={handleChange} />\n              );\n            }\n          `}\n        </Code>\n      </div>\n    );\n  }\n}\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/components/Subtitle.jsx",
    "content": "import React from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport { text, boolean } from '@kadira/storybook-addon-knobs';\nimport Subtitle from '../../src/components/Subtitle';\nimport Button from '../../src/components/Button';\nimport { Code, ComponentWrapper } from '../presenters';\n\nconst Presenter = () => {\n  const props = {\n    hr: boolean('hr', false),\n    actions: (<span className=\"pull-right\">{text('actions', 'Foo')}</span>),\n  };\n\n  return (\n    <div>\n      <ComponentWrapper>\n        <Subtitle {...props}>{text('Subtitle children', 'Hello world')}</Subtitle>\n      </ComponentWrapper>\n      <Code>\n        {`\nimport Subtitle from './src/components/Subtitle';\n\nexport default () => (<Subtitle hr actions={(<a href className=\"pull-right\">Foo</a>)}>Hello world</Subtitle>);\n        `}\n      </Code>\n    </div>\n  );\n};\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/components/TagsForm.jsx",
    "content": "import React from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport { object } from '@kadira/storybook-addon-knobs';\nimport TagsForm from '../../src/components/TagsForm';\nimport { Code, ComponentWrapper } from '../presenters';\n\nconst Presenter = () => {\n  const props = {\n    tags: object('tags', [{ name: 'Humans', tag_id: 1 }, { name: 'Planet Express', tag_id: 2 }, { name: 'Head', tag_id: 3 }]),\n  };\n\n  return (\n    <div>\n      <ComponentWrapper>\n        <TagsForm {...props} />\n      </ComponentWrapper>\n      <Code>\n        {`\nimport TagsForm from './src/components/TagsForm';\nexport default () => (<TagsForm tags={tags} />);\n        `}\n      </Code>\n    </div>\n  );\n};\n\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/index.jsx",
    "content": "import React from 'react';\nimport { storiesOf, action, linkTo, addDecorator } from '@kadira/storybook'; // eslint-disable-line\nimport { withKnobs, text, select, boolean, array, object, number } from '@kadira/storybook-addon-knobs';\nimport { host } from 'storybook-host';\nimport backgrounds from 'react-storybook-addon-backgrounds';\nimport { StaticRouter } from 'react-router-dom';\nimport { Provider } from 'react-redux';\nimport Badge from '../src/components/Badge';\nimport BlockList from '../src/components/BlockList';\nimport BlockListPresenter from './components/BlockList';\nimport Brand from '../src/components/Brand';\nimport Button from '../src/components/Button';\nimport {\n  CheckboxFieldGroup,\n  CollectionFieldGroup,\n  Fieldset,\n  Legend,\n  FormGrid,\n  FormRow,\n  FormColumn,\n  PasswordStrength,\n  RadioFieldGroup,\n  SelectFieldGroup,\n  TextFieldGroup,\n} from '../src/components/form';\nimport FormGridPresenter from './components/FormGrid';\nimport PasswordStrengthPresenter from './components/PasswordStrength';\nimport CollectionFieldGroupPresenter from './components/CollectionFieldGroup';\nimport ContactAvatarLetter, { SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE, SIZE_XLARGE } from '../src/components/ContactAvatarLetter';\nimport ContactBook from './layouts/ContactBook';\nimport ContactDetails from './components/ContactDetails';\nimport DefList from '../src/components/DefList';\nimport Devices from './layouts/Devices';\nimport Dropdown, { withDropdownControl } from '../src/components/Dropdown';\nimport DropdownMenu from '../src/components/DropdownMenu';\nimport Icon, { typeAssoc } from '../src/components/Icon';\nimport IconLetter from '../src/components/IconLetter';\nimport ImportContactForm from '../src/components/ImportContactForm/presenter.jsx';\nimport Link from '../src/components/Link';\nimport MessageList from './components/MessageList';\nimport Modal from '../src/components/Modal';\nimport MultidimensionalPi from '../src/components/MultidimensionalPi';\nimport PiBar from '../src/components/PiBar';\nimport Reply from './components/Reply';\nimport Section from '../src/components/Section';\nimport SigninPage from './layouts/SigninPage';\nimport SignupPage from './layouts/SignupPage';\nimport Spinner from '../src/components/Spinner';\nimport Subtitle from '../src/components/Subtitle';\nimport TagsFormPresenter from './components/TagsForm';\nimport TextList, { ItemContent } from '../src/components/TextList';\nimport Title from '../src/components/Title';\nimport Welcome from './Welcome';\nimport Changelog from './Changelog';\nimport Guideline from './Guideline';\nimport TabList from '../src/layouts/Page/components/Navigation/components/TabList/presenter';\nimport '../src/styles/vendor/bootstrap_foundation-sites.scss';\n\naddDecorator(withKnobs);\naddDecorator(backgrounds([\n  { name: '$co-color__fg__back', value: '#333' },\n  { name: '$co-color__bg__back', value: '#1d1d1d', default: true },\n  { name: '$co-color__contrast__back', value: '#fff' },\n]));\n\nconst hostDecorator = host({\n  mobXDevTools: false,\n  background: 'transparent',\n  backdrop: 'transparent',\n  width: '100%',\n  height: '100%',\n  border: false,\n});\n\nstoriesOf('Welcome', module)\n  .add('to Storybook', () => (\n    <Welcome />\n  ))\n  .add('Changelog', () => (\n    <Changelog />\n  ));\n\nstoriesOf('Guideline', module)\n  .add('Guideline', () => (\n    <Guideline />\n  ));\n\nstoriesOf('Auth', module)\n  .add('SignupPage', () => (\n    <SignupPage />\n  ))\n  .add('SigninPage', () => (\n    <SigninPage />\n  ));\n\nstoriesOf('Badge', module)\n  .addDecorator(hostDecorator)\n  .addWithInfo(\n    'Badge',\n    () => {\n      const props = {\n        low: boolean('low', false),\n        large: boolean('large', false),\n        children: text('children', '142'),\n      };\n\n      return <Badge {...props} />;\n    },\n  );\n\nstoriesOf('Buttons & Links', module)\n  .addDecorator(hostDecorator)\n  .addWithInfo('Button', () => {\n    const iconType = Object.assign({ '': '' }, typeAssoc);\n    const props = {\n      icon: select('icon', iconType, ''),\n      children: text('children', 'Click Me'),\n      shape: select('shape', { '': '', plain: 'plain', hollow: 'hollow' }, ''),\n      display: select('display', { '': '', expanded: 'expanded', inline: 'inline' }, ''),\n      color: select('color', { '': '', active: 'active', alert: 'alert', success: 'success', secondary: 'secondary' }, ''),\n    };\n\n    return (<Button {...props} onClick={action('clicked')} />);\n  })\n  .addWithInfo('Link', () => {\n    const props = {\n      noDecoration: boolean('noDecoration', false),\n      button: boolean('button', false),\n      expanded: boolean('expanded', false),\n      active: boolean('active', false),\n    };\n\n    return (<Link {...props}>{text('Link children', 'Click Me')}</Link>);\n  });\n\nstoriesOf('Contact', module)\n  .add('ContactDetails', () => (\n    <ContactDetails />\n  ))\n  .add('ImportContactForm', () => (\n    <Modal contentLabel=\"\" isOpen><ImportContactForm __={str => str} onSubmit={str => str} /></Modal>\n  ))\n  .add('ContactBook', () => (\n    <ContactBook />\n  ));\n\nconst DropdownControl = withDropdownControl(Button);\nstoriesOf('Dropdown', module)\n  .addDecorator(hostDecorator)\n  .addWithInfo('Dropdown', () => {\n    const dropdownProps = {\n      position: select('position', { '': '', bottom: 'bottom' }, ''),\n      closeOnClick: boolean('closeOnClick', false),\n    };\n\n    return (\n      <div>\n        <DropdownControl\n          toggle=\"story-dropdown\"\n          className=\"float-right\"\n        >{text('control children', 'Click me')}</DropdownControl>\n        <Dropdown\n          id=\"story-dropdown\"\n          {...dropdownProps}\n        >{text('dropdown children', 'Hey hey I am a dropdown')}</Dropdown>\n      </div>\n    );\n  }, { propTables: [DropdownControl, Dropdown] })\n  .addWithInfo('DropdownMenu', () => {\n    const dropdownProps = {\n      position: select('position', { '': '', bottom: 'bottom' }, ''),\n      closeOnClick: boolean('closeOnClick', false),\n    };\n\n    return (\n      <div>\n        <DropdownControl toggle=\"story-dropdown-menu\" className=\"float-right\">\n          {text('control children', 'Click me')}\n        </DropdownControl>\n        <DropdownMenu\n          id=\"story-dropdown-menu\"\n          {...dropdownProps}\n        >{text('dropdown children', 'Hey hey I am a dropdown')}</DropdownMenu>\n      </div>\n    );\n  }, { propTables: [DropdownControl, DropdownMenu] });\n\nstoriesOf('Form', module)\n  .addDecorator(hostDecorator)\n  .addWithInfo('Fieldset', () => (\n    <Fieldset>\n      <Legend>{text('Legend label', 'Foobar')}</Legend>\n      {text('fieldset children', 'I\\'m the content of the fieldset')}\n    </Fieldset>\n  ))\n  .addWithInfo('FormGrid', () => (\n    <FormGridPresenter />\n  ), { source: false, propTables: [FormGrid, FormRow, FormColumn] })\n  .addWithInfo('TextFieldGroup', () => {\n    const props = {\n      label: text('label', 'Foobar'),\n      placeholder: text('placeholder', 'Foobar'),\n      showLabelforSr: boolean('showLabelforSr', false),\n      errors: array('errors', []),\n    };\n\n    return (\n      <TextFieldGroup\n        name=\"my-text\"\n        onChange={action('onChange')}\n        {...props}\n      />\n    );\n  })\n  .addWithInfo('PasswordStrength', () => (\n    <PasswordStrengthPresenter />\n  ), { propTables: [PasswordStrength] })\n  .addWithInfo('SelectFieldGroup', () => {\n    const props = {\n      label: text('label', 'Foobar'),\n      showLabelforSr: boolean('showLabelforSr', false),\n      options: object('options', [{ value: '', label: '' }, { value: 'foo', label: 'Foo' }, { value: 'bar', label: 'Bar' }]),\n      errors: array('errors', []),\n    };\n\n    return (\n      <SelectFieldGroup\n        name=\"my-select\"\n        {...props}\n      />\n    );\n  })\n  .addWithInfo('RadioFieldGroup', () => {\n    const props = {\n      label: text('label', 'Foobar'),\n      options: object('options', [{ value: 'foo', label: 'Foo' }, { value: 'bar', label: 'Bar' }]),\n      errors: array('errors', []),\n    };\n\n    return (\n      <RadioFieldGroup\n        name=\"my-radio\"\n        {...props}\n      />\n    );\n  })\n  .addWithInfo('CheckboxFieldGroup', () => {\n    const props = {\n      label: text('label', 'FooBar'),\n      displaySwitch: boolean('displaySwitch', false),\n      showTextLabel: boolean('showTextLabel (switch only)', false),\n    };\n\n    return (\n      <CheckboxFieldGroup {...props} />\n    );\n  })\n  .addWithInfo('CollectionFieldGroup', () => (\n    <CollectionFieldGroupPresenter />\n  ), { propTables: [CollectionFieldGroup] });\n\nstoriesOf('Logo, Icons & Avatars', module)\n  .addDecorator(hostDecorator)\n  .addWithInfo('Brand', () => {\n    const props = {\n      theme: select('theme', { '': '', low: 'low', high: 'high' }, ''),\n    };\n\n    return (\n      <Brand {...props} />\n    );\n  })\n  .addWithInfo('ContactAvatarLetter', () => {\n    const sizes = [SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE, SIZE_XLARGE]\n      .reduce((acc, size) => ({ ...acc, [size]: size }), { '': '' });\n    const props = {\n      contact: object('contact', { title: 'Foobar' }),\n      size: select('size', sizes, ''),\n    };\n\n    return (<ContactAvatarLetter {...props} />);\n  })\n  .addWithInfo('Icon', () => {\n    const props = {\n      type: select('type', typeAssoc),\n      spaced: boolean('spaced', true),\n    };\n\n    return (<Icon {...props} />);\n  })\n  .addWithInfo('IconLetter', () => {\n    const props = {\n      word: text('word', 'Foo'),\n    };\n\n    return (<IconLetter {...props} />);\n  })\n  .addWithInfo('Spinner', () => {\n    const props = {\n      isLoading: boolean('isLoading', true),\n    };\n\n    return (<Spinner {...props} />);\n  });\n\nstoriesOf('Layout', module)\n  .addDecorator(hostDecorator)\n  .addWithInfo('Section', () => {\n    const props = {\n      title: text('title', 'Title'),\n      descr: text('descr', 'Description'),\n      hasSeparator: boolean('hasSeparator', false),\n    };\n\n    return (<Section {...props}>{text('section children', 'Foobar')}</Section>);\n  })\n  .addWithInfo('Modal', () => {\n    const props = {\n      isOpen: boolean('isOpen', true),\n      onAfterOpen: action('onAfterOpen'),\n      onRequestClose: action('onRequestClose'),\n      closeTimeoutMS: number('closeTimeoutMS', 0),\n      contentLabel: text('contentLabel', ''),\n    };\n\n    return (\n      <Modal title={text('title', 'modal.title')} {...props}>\n        <p>{text('modal text', 'Quibus occurrere bene pertinax miles explicatis ordinibus parans hastisque feriens scuta qui habitus iram pugnantium concitat et dolorem proximos iam gestu terrebat sed eum in certamen alacriter consurgentem revocavere ductores rati intempestivum anceps subire certamen cum haut longe muri distarent, quorum tutela securitas poterat in solido locari cunctorum.')}</p>\n      </Modal>\n    );\n  })\n  .addWithInfo('NavBar - TabList', () => {\n    const props = {\n      application: select('current application', { discussion: 'discussion', contact: 'contact' }, 'discussion'),\n      tabs: object('tabs', [{ pathname: '/foo' }, { pathname: '/bar' }]),\n      requestTabs: action('requestTabs'),\n      removeTab: action('removeTab'),\n    };\n\n    return (\n      <Provider store={{ subscribe: () => {}, getState: () => ({ router: { location: { pathname: '/' } } }) }}><StaticRouter context={{}}><TabList {...props} /></StaticRouter></Provider>\n    );\n  });\n\nstoriesOf('Lists', module)\n  .addDecorator(hostDecorator)\n  .addWithInfo('BlockList', () => (\n    <BlockListPresenter />\n  ), { propTables: [BlockList] })\n  .addWithInfo('TextList & ItemContent', () => (\n    <TextList>\n      {[\n        'Standard string',\n        <ItemContent>Foo, Simple ItemContent</ItemContent>,\n        <ItemContent>Bar, Simple ItemContent</ItemContent>,\n        <ItemContent large>Foo, Large ItemContent</ItemContent>,\n        <ItemContent large>Bar, Large ItemContent</ItemContent>,\n      ]}\n    </TextList>\n  ))\n  .addWithInfo('DefList', () => (\n    <DefList\n      definitions={object('definitions', [\n        { title: 'Bar', descriptions: ['Bar description'] },\n        { title: 'Foo', descriptions: ['Foo description'] },\n      ])}\n    />\n  ));\n\nstoriesOf('Pi', module)\n  .addDecorator(hostDecorator)\n  .addWithInfo('PiBar', () => {\n    const props = {\n      level: number('level', 50),\n    };\n\n    return (<PiBar {...props} />);\n  })\n  .addWithInfo('MultidimensionalPi', () => {\n    const props = {\n      pi: object('PIs', [\n        { name: 'behavioral', level: 20 },\n        { name: 'contextual', level: 95 },\n        { name: 'technical', level: 55 },\n      ]),\n      displayAveragePi: boolean('display Average PI', false),\n      mini: boolean('mini', false),\n    };\n\n    return (<MultidimensionalPi {...props} />);\n  });\n\nstoriesOf('Settings', module)\n  .add('Devices', () => (\n    <Devices />\n  ));\n\nstoriesOf('Tags', module)\n  .addDecorator(hostDecorator)\n  .add('TagsForm', () => (\n    <TagsFormPresenter />\n  ));\n\nstoriesOf('Titles', module)\n  .addDecorator(hostDecorator)\n  .addWithInfo('Title', () => {\n    const props = {\n      hr: boolean('hr', false),\n      actions: (<span className=\"pull-right\">{text('actions', 'Foo')}</span>),\n    };\n\n    return (<Title {...props}>{text('Title children', 'Hello world')}</Title>);\n  })\n  .addWithInfo('Subtitle', () => {\n    const props = {\n      hr: boolean('hr', false),\n      actions: (<span className=\"pull-right\">{text('actions', 'Foo')}</span>),\n    };\n\n    return (<Subtitle {...props}>{text('Title children', 'Hello world')}</Subtitle>);\n  });\n\nstoriesOf('Compose & Reply', module)\n  .add('Reply', () => (\n    <Reply />\n  ));\n\nstoriesOf('Discussions & Messages', module)\n  .add('MessageList', () => (\n    <MessageList />\n  ));\n"
  },
  {
    "path": "devtools/storybook/stories/layouts/ContactBook.jsx",
    "content": "import React, { Component } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport ContactBook from '../../src/scenes/ContactBook/presenter';\nimport { Code, ComponentWrapper } from '../presenters';\n\nclass Presenter extends Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      props: {\n        hr: false,\n      },\n    };\n    this.handlePropsChanges = this.handlePropsChanges.bind(this);\n  }\n\n  handlePropsChanges(event) {\n    const { name, checked } = event.target;\n\n    this.setState(prevState => ({\n      props: {\n        ...prevState.props,\n        [name]: checked,\n      },\n    }));\n  }\n\n\n  render() {\n    const noop = str => str;\n\n    return (\n      <div>\n        <ComponentWrapper>\n          <ContactBook __={noop} />\n        </ComponentWrapper>\n\n\n        <Code>\n          {`  `}\n        </Code>\n      </div>\n    );\n  }\n}\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/layouts/Devices.jsx",
    "content": "import React, { Component } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport Devices from '../../src/scenes/Devices/presenter';\n\nimport { Code, ComponentWrapper } from '../presenters';\n\nclass Presenter extends Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      props: {\n        hr: false,\n      },\n    };\n  }\n\n  render() {\n    const props = {\n      __: str => str,\n      devices: [\n        { 'device_id': '4537-es79-0001', 'name': 'Laptop work', 'type': 'laptop', signature_key: 'w00t', 'date_insert': \"2016-05-09T15:01:42\", 'last_seen': '2017-02-01T15:00:42', 'status': 'master', 'pi': 99, 'ips': ['10.9.52.65', '192.168.1.1'], 'os': 'Linux', 'os_version': 'arch'},\n        { 'device_id': '3237-es79-0002', 'name': 'Desktop', 'type': 'desktop', signature_key: null, 'date_insert': \"2016-05-09T15:01:42\", 'last_seen': '2017-01-01T15:00:42', 'status': '', 'pi': 75, 'ips': [], 'os': 'Linux', 'os_version': 'arch'},\n        { 'device_id': '4556-es79-0003', 'name': 'Smartphone', 'type': 'smartphone', signature_key: 'w00t', 'date_insert': \"2016-05-09T15:01:42\", 'last_seen': '2017-01-01T15:00:42', 'status': '', },\n        { 'device_id': '4997-es79-0004', 'name': 'my smart fridge', 'type': 'tablet', signature_key: 'w00t', 'date_insert': \"2012-04-20T16:20:00\", 'last_seen': '2012-04-20T16:20:00', 'status': '', },\n      ],\n      requestDevices: () => {},\n      // router props\n      params: {},\n    };\n\n    return (\n      <div>\n        <ComponentWrapper>\n          <Devices {...props} />\n        </ComponentWrapper>\n        <Code>\n          {`\n            `}\n        </Code>\n      </div>\n    );\n  }\n}\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/layouts/SigninPage.jsx",
    "content": "import React, { Component } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport SigninForm from '../../src/components/SigninForm';\nimport AuthPage from '../../src/layouts/AuthPage';\nimport { Code, ComponentWrapper } from '../presenters';\n\nclass Presenter extends Component {\n  render() {\n    const noop = str =>str;\n\n    return (\n      <div>\n        <ComponentWrapper>\n          <AuthPage version=\"0.1\" login=\"foo\" password=\"bar\">\n            <SigninForm __={noop} onSubmit={action('submited')} />\n          </AuthPage>\n        </ComponentWrapper>\n        <Code>\n          {`\nimport SigninForm from './src/components/SigninForm';\nexport default () => (\n  <AuthPage version=\"0.1\" login=\"foo\" password=\"bar\">\n    <SigninForm />\n  </AuthPage>\n);\n          `}\n        </Code>\n      </div>\n    );\n  }\n}\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/layouts/SignupPage.jsx",
    "content": "import React, { Component } from 'react';\nimport { action } from '@kadira/storybook'; // eslint-disable-line\nimport AuthPage from '../../src/layouts/AuthPage';\nimport SignupForm from '../../src/components/SignupForm';\nimport { Code, ComponentWrapper } from '../presenters';\n\nclass Presenter extends Component {\n  render() {\n    const noop = str => str;\n\n    return (\n      <div>\n        <ComponentWrapper>\n          <AuthPage>\n            <SignupForm __={noop} onSubmit={action('submited')} />\n          </AuthPage>\n        </ComponentWrapper>\n        <Code>\n          {`\nimport SignupForm from './src/components/SignupForm';\nexport default () => (\n    <SignupForm />\n);\n          `}\n        </Code>\n      </div>\n    );\n  }\n}\n\nexport default Presenter;\n"
  },
  {
    "path": "devtools/storybook/stories/presenters/Code.jsx",
    "content": "import React, { PropTypes } from 'react';\n\nconst style = {\n  code: {\n    margin: '1rem',\n    padding: '1rem',\n    backgroundColor: '#333',\n  },\n};\n\nconst Code = ({ children }) => (\n  <code><pre style={style.code}>{children}</pre></code>\n);\n\nCode.propTypes = {\n  children: PropTypes.node.isRequired,\n};\n\nexport default Code;\n"
  },
  {
    "path": "devtools/storybook/stories/presenters/ComponentWrapper.jsx",
    "content": "import React, { PropTypes } from 'react';\nimport enableI18n from '../../src/services/i18n';\n\nconst style = {\n  component: {\n    margin: '1rem',\n    border: '1px solid blue',\n  },\n  componentSmall: {\n    height: '5rem',\n  },\n  componentTall: {\n    height: '15rem',\n  },\n  componentInline: {\n    display: 'inline-block',\n    height: 'auto',\n  },\n};\n\nconst ComponentWrapper = ({ size, inline, children }) => {\n  let componentStyle = { ...style.component };\n\n  if (size === 'tall') {\n    componentStyle = { ...componentStyle, ...style.componentTall };\n  }\n\n  if (size === 'small') {\n    componentStyle = { ...componentStyle, ...style.componentTall };\n  }\n\n  if (inline) {\n    componentStyle = { ...componentStyle, ...style.componentInline };\n  }\n\n  return (\n    <div style={componentStyle}>{children}</div>\n  );\n};\n\nComponentWrapper.propTypes = {\n  size: PropTypes.oneOf(['small', 'tall']),\n  inline: PropTypes.bool,\n  children: PropTypes.node.isRequired,\n};\n\nexport default enableI18n(ComponentWrapper);\n"
  },
  {
    "path": "devtools/storybook/stories/presenters/index.js",
    "content": "import Code from './Code';\nimport ComponentWrapper from './ComponentWrapper';\n\nexport {\n  Code,\n  ComponentWrapper,\n};\n"
  },
  {
    "path": "doc/architecture/Repository_structure.md",
    "content": "# Repositories architecture\n\nBelow is our target repository structure.  \nFor now, all directories are not created, as some Caliopen's components are still missing or are not yet implemented.\n\n### top level\n\n```\n├── doc     : (work in progress) all documentation for developers, administrators and users\n├── src     : all source code goes here\n└── devtools: scripts, fixtures and other tools to build development environment\n```\n\n### the `src` directory\nSource code for Caliopen platform and clients applications.  \n**NB**: Caliopen is not bind to a specific language : one may finds softwares components written in python, Go, C, java…\n\nCaliopen is made of :\n\n * the *frontends* => applications that run on clients' devices : browsers, computers, smartphones…\n * the *backend* => the Caliopen platform that runs on servers.\n\nHere is our target architecture for `src` :\n\n```\n├── backend\n│   ├── brokers\n│   ├── configs\n│   ├── components\n│   ├── defs\n│   ├── interfaces\n│   ├── main\n│   ├── protocols\n│   └── tools\n└── frontends\n    └── web_application\n```\n\n## Code organization for the backend\n\n#### `main` directory\n\nThis is main code for caliopen backend services for interacting with storage layers.\nFor now, there are 2 python packages : `main/py.main` and `main/py.storage` and 2 golang ones\n`main/go.main` and `main/go.backends`\n\n#### `interfaces` directory\n\nPublic APIs consumed by frontends and clients applications over HTTP/HTTP2.  \nExamples : REST server…  \n\n#### `brokers` directory\n\nBrokers are program modules that offer services to parse/unparse and/or unmarshall/marshall objects between the formal external protocol of the sender and the formal internal protocol of Caliopen.\nExamples : email broker, sms broker, vcard broker…\n\n#### `protocols` directory\n\nProgram modules that implement standard protocols to connect *Brokers* to external tiers.  \nExamples : SMTP, XMPP…\n\n#### `components` directory\n\nSoftware components that add features to Caliopen by exposing services or procedures directly to\nmain processes.\nEach component can be enhanced thanks to a plugin architecture, as long as the plugin don't break\nthe component's contract.\nSome components could be distributed outside Caliopen as standalone packages.\nExamples : parsers, messages qualifiers (PI computing, importance computing), keys manager, DNS…\n\n#### `configs` directory\n\nConfiguration files for every platform components.\n\n#### `defs` directory (ie definitions)\n\nInterfaces, objects and methods definitions.  \nOne finds here the « Single Source of Truth » to work with Caliopen's inner world.  \nExamples : databases models, protobuf files, python packages for base classes, Go struct\ndefinitions…\n\n#### `tools` directory\n\nStandalone programs to manage the backend and the databases outside the standard interfaces.  \nExamples : caliopen CLI to import mailboxes…\n\n## Code organization for the frontends\n\n#### `web_application` directory\n\nAll the code needed to render and to distribute the User Interface that runs into the browser. For now, it is a *Node/Express/React* application.\n\n#### Code guidelines\n\nThe actual code mainly follows this basic [React tutorial](http://www.robinwieruch.de/the-soundcloud-client-in-react-redux/) and uses this recommended [file structure](https://medium.com/@alexmngn/how-to-better-organize-your-react-applications-2fd3ea1920f1#.4t2oi46lj).\n\nThe main folders in src/frontend are:\n\n* **components**: UI components that will be moved to its own module\n* **layouts**: components rendering the layouts (Page, Auth, SubLayout ...)\n* **modules**: domain specific with business logic (it can contains components, services etc.)\n* **scenes**: routed components (Timeline, Contact, ...)\n* **services**: functions with no relations with react nor redux\n* **store**: state management with redux (actions, reducers, selectors, middlewares)\n* **styles**: configuration and shared scss functions\n"
  },
  {
    "path": "doc/architecture/assets/stack_frontend_dev_2017-03-09.puml",
    "content": "@startuml\ncenter header Frontend development architecture - march 2017\ncloud {\n  () Browser\n}\nnode {\n  () \"/src\" as code\n  () \"/server\" as code_server\n}\npackage \"kotatsu client\" {\n  [kotatsu server proxy]\n  () HMR as HMRc\n  code --> wpc\n  () \"webpack client\" as wpc\n  HMRc <-- wpc\n  [kotatsu client server] <- wpc\n  [kotatsu client server] --> () Browser\n  [kotatsu server proxy] --> [kotatsu client server]\n}\npackage \"kotatsu server\" {\n  () HMR as HMRs\n  () \"webpack server\" as wps\n  code_server -> wps\n  wps -> HMRs\n  wps --> [express server]\n  () \"SSR middleware\" as SSR\n  [express server] <- SSR\n  code --> SSR\n  [express server] <- () \"assets middleware\"\n  [express server] <- () \"API middleware\"\n  [express server] <- () \"Auth middleware\"\n  [express server] --> [kotatsu server proxy]\n}\n@enduml\n"
  },
  {
    "path": "doc/devops/continous-integration.md",
    "content": "# Continuous Integration (CI)\n\nWe have [an instance of drone.io][1] to test the project each time a Pull Request is made on our Github repository.\n\nWhich tests are launched depends on the files changed (services modified).\n\n## CI strategy\n\n### Pull Requests\n\nWhen a pull request is created or a new commit pushed to an already existing PR, Drone captures the event `pull_request`. Depending on the files changed, Drone will launch **lint check, unit tests and functional tests** in the case of the frontend (more info below), or unit tests for python. Go and Python tests are prepared to be launched based on the modifications made to the dependencies of each service, this means a service is only tested if any of its dependencies is modified. Python tests are global and not tied to a service, so the pipeline currently only tests python in case of a modification to the backend. Future improvements include individual service testing and proper unit tests for both go and python.\n\nThose tests are made using the [merge and checkout strategy][2] so the tests will show the results as if the branch was be merged.\n\n### Push\n\nWhen a branch is merged or commit pushed to `develop`, the event is caught by drone. Every service **modified** is then built and published to the Docker registry. Each Docker image is published with 2 tags: develop and the commit sha. In the case of a merge, only services modified in the merged branch are built. In the case of a direct push, there is no way of checking which files are changed, so every service is built and published.\n\nA push on the `master` branch does not trigger any test nor build.\n\n### Tag\n\nWhen a ``release-${version}`` tag is created on any branch, Drone will build and publish Docker images with 2 tags: latest and the version of the release. The number of the version is extracted from the tag, stripping the \"release-\" part.\n\n## Frontend\n\nUsing a node container Drone launches the following tests and if one error is encountered it will stop the sequence and return a failure.\n\n1. Lint JS\n2. Lint SCSS\n3. Unit tests JS\n4. Functional tests\n\n### Functional tests\n\nThe functional tests release a bundle, start a mocked api (full JS using bouchon cf. devtools/api-mock) and open a socket on [Saucelabs](https://saucelabs.com) that will execute selenium commands on a real browser (Firefox 45/Linux for now). Connection to saucelabs is made through [Sauce-connect][3], which is started as a [Drone service][4].\nYou can see the full video of the test by browsing open results on the [project account](https://saucelabs.com/open_sauce/user/AllTheDey).\n\n`> Automated tests > CaliOpen e2e - <name of the branch of the PR> > Watch or Metadata > Download video`.\n\n## Drone\n\n### Variables\n\nThe following variables are defined as Drone secrets and are not available for non-members of Caliopen, some of them are defined as secrets only to avoid hardcoding them:\n\n* SAUCE_ACCESS_KEY: Saucelabs api key\n* SAUCE_USERNAME: Saucelabs username\n* SAUCE_ADDRESS: Sauce-connect address, used exclusively by the Frontend, not really a secret\n\n* DOCKER_REGISTRY: Address of the Caliopen docker registry, not a secret either\n* DOCKER_USERNAME: Username for the Docker registry\n* DOCKER_PASSWORD: Password for the Docker registry\n\n### Pipeline\n\nThe Pipeline is divided with commentaries in 5 parts:\n\n1. [**Base images**](https://github.com/CaliOpen/Caliopen/blob/develop/.drone.yml#L14): This section contains the steps that build and publish images used by other services as \"Builders\". These are images that don't change very often, take some time to compile and contain dependencies shared by many other images. For example, the Python base image compiles the Cassandra driver and the Go base image fetches common govendor packages.\n2. [**Tests**](https://github.com/CaliOpen/Caliopen/blob/develop/.drone.yml#L36): This section corresponds to ['Pull requests'](https://github.com/CaliOpen/Caliopen/tree/develop/doc/devops/continuous-integration.md#pull-requests).\n3. [**Develop images**](https://github.com/CaliOpen/Caliopen/blob/develop/.drone.yml#L80): This section corresponds to ['Push'](https://github.com/CaliOpen/Caliopen/tree/develop/doc/devops/continuous-integration.md#push). It builds and publishes experimental builds that can be used by developers or for the stage platform.\n4. [**Stable images**](https://github.com/CaliOpen/Caliopen/blob/develop/.drone.yml#L274): This section corresponds to ['Tag'](https://github.com/CaliOpen/Caliopen/tree/develop/doc/devops/continuous-integration.md#tag).\n4. [**Drone services**](https://github.com/CaliOpen/Caliopen/blob/develop/.drone.yml#L403): Services needed for tests that are started in parallel.\n\n#### Additional info\n\nDevelop images are treated differently from release images. Unstable builds are up to date with the develop branch, a branch that changes multiple times per day. To avoid building every service every time a small modification to a single service is made, we check if dependencies for each service have been modified. This logic is integrated in the scripts found in [devtools/drone](https://github.com/CaliOpen/Caliopen/tree/master/devtools/drone). The problem is that scripts cannot be executed with Drone plugins because the entrypoint of the containers cannot be overriden. To circumvent this limitation we use our own unmodified fork of [Drone-Docker](https://github.com/drone-plugins/drone-docker) running in privileged mode.\n\n[1]: https://drone.caliopen.org\n[2]: https://github.com/drone-plugins/drone-git/blob/master/plugin.go#L49\n[4]: https://github.com/CaliOpen/Caliopen/blob/develop/.drone.yml#L407\n[3]: https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy\n"
  },
  {
    "path": "doc/devops/docker-&-registry.md",
    "content": "# Docker and Registry V2\n\n## Dockerfiles\n\nDockerfiles for backend services are located in [src/backend][1] and for frontend services in [src/frontend/web_application][2]. Additionally, two other Dockerfiles are used for backend services, useful to reduce compilation time and for multi-stage builds:\n\n* [Caliopen-go]: Go base image with govendor dependencies up to date\n* [Caliopen-python]: Python base image with base packages and the cassandra driver installed\n\n## Docker Registry\n\n#### Public / Private registry\n\nThe Docker registry is exposed both publicly through https://public-registry.caliopen.org, to allow everybody to pull images, and privately (requiring authentication), so only members can push images.\n\n#### Makefile and manual publishing\n\nAlthough not especially useful considering Drone usually keeps both stable and unstable images up to date in the registry, there is a generic [Makefile](https://github.com/CaliOpen/Caliopen/blob/develop/devtools/makefile) to build and publish Caliopen images and a [script](https://github.com/CaliOpen/Caliopen/blob/develop/devtools/publish-images.sh) to build and publish every service with a given tag. Note that to publish images with the script you need to have the registry credentials defined in a registry.conf file in the same directory. Usage:\n\n```\n./publish-images <tag>\n```\n\nCurrently the docker-compose file is used to build the images. Ideally, this should be changed to ```docker build``` commands.\n\n#### Getting a list of images and tags available\n\n* You can get a list of the available images with an HTTP GET request to https://public-registry.caliopen.org/v2/_catalog\n* To get a list of the tags available for an image make the request to https://public-registry.caliopen.org/v2/X/tags/list where X is the name of the image you want to get the list of tags for.\n\nA simpler and prettier way to do it is with a tool such as [registry-cli](https://github.com/andrey-pohilko/registry-cli).\n\n#### Cleaning image layers to reduce disk usage\n\nThe aforementioned tool also allows the deletion of images. Note that this does not actually delete the images, it only marks them for deletion, so the garbage collector can later delete them. For our registry the garbage collector runs once a week through a cronjob but it can also be manually ran. The environment variable REGISTRY_STORAGE_DELETE_ENABLED has to be set to \"true\" for the deletion to work, inside the docker-compose file of the registry for example.\n\n#### Integration with Drone\n\nAs previously mentioned, Drone automatically build and publishes stable and unstable versions of the images to the registry.\n\n[1]: https://github.com/CaliOpen/Caliopen/tree/develop/src/backend\n[2]: https://github.com/CaliOpen/Caliopen/tree/develop/src/frontend/web_application\n"
  },
  {
    "path": "doc/devops/kube_survival_guide.md",
    "content": "Kubernetes Caliopen survival guide\n==================================\n\n\nRestart a service\n-----------------\n\n```\nkubectl -n production get pod\n# find pods running the service\nkubectl -n production delete pod <pod_name>\n```\n\nView logs\n---------\n\n```\nkubectl -n production logs --tail 200 -f deployment/<service>\n```\n\nEnter into a container\n----------------------\n\ncurrently go container does not have any shell, it's not possible. For others\n\n```\nkubectl exec -it <pod_name> /bin/sh\n```\n\nRestart a node\n--------------\n\nSteps :\n~~~~~~~\n\n- disable a node (drain)\n- reboot the node\n- restart the service\n- reschedule the node (uncordon)\n\nCommands\n~~~~~~~~\n\n1 - drain\n\n```\n$ kubectl drain <node> --ignore-daemonsets\n$ kubectl get node\nNAME      STATUS                     ROLES     AGE       VERSION\nmaster    Ready                      master    190d      v1.13.2\nnode0     Ready                      <none>    190d      v1.13.2\nnode1     Ready,SchedulingDisabled   <none>    190d      v1.13.2\nnode2     Ready                      <none>    190d      v1.13.2\nnode3     Ready                      <none>    190d      v1.13.2\n\n```\n\n2 - reboot \n```\n$ gandi vm reboot <node>\n```\n\n3 - restart service\n\n```\n# ssh root@<node>\n$ service systemd-resolved start\n\n# disable swap\nswapoff -a\n# start kubelet service\n$ service kubelet restart\n```\n\n4 - reschedule\n\n```\nkubectl uncordon <node>\n$ kubectl get node\nNAME      STATUS    ROLES     AGE       VERSION\nmaster    Ready     master    190d      v1.13.2\nnode0     Ready     <none>    190d      v1.13.2\nnode1     Ready     <none>    190d      v1.13.2\nnode2     Ready     <none>    190d      v1.13.2\nnode3     Ready     <none>    190d      v1.13.2\n\n```\n"
  },
  {
    "path": "doc/index.md",
    "content": "# Caliopen Documentation\n\nWelcome.\n\nThis is the home of the documentation of Caliopen.\n\nThe code source is available at https://github.com/CaliOpen/Caliopen\n\nAn alpha version is available (registrations are open) at https://alpha.caliopen.org\n"
  },
  {
    "path": "doc/install/frontend-development.md",
    "content": "# Frontend development\n\nThis documentation describe how to configure then start caliopen using docker.\n\n## Initialize\n\n```bash\ncd devtools\n./init.sh\n```\n\n## Start and stop Caliopen\n\n**Start latest release of Caliopen**\n\n```bash\n./start.sh\n```\n\nThen open http://localhost:4000/\n\n**Start Caliopen and live build frontend**\n\n_Probably the most used command for frontend development_\n\n```bash\n./start.sh -d frontend\n\n# watch the status of the build:\ndocker-compose logs -f --tail=100 frontend_dev\n```\n\nThen start hack into `src/frontend/web_application/`.\n\nThen open or refresh http://localhost:4000/.\n\n**Stop Caliopen**\n\n```bash\n./stop.sh\n```\n\n## Cleanup Caliopen data and containers\n\n```bash\n./clean-dev-storage.sh\n```\n\n---\n\n**Archives**\n\nThe rest of this documentation describes how to start the backend in docker\ncontainer and the frontend in a native way.\n\n## Setup the docker_compose stack\n\nyou need to have [docker](https://docs.docker.com/engine/installation/) and\n[docker compose](https://docs.docker.com/compose/) installed on your machine.\n\nServices available in the docker compose stack are:\n\n- redis\n- elasticsearch\n- cassandra\n- object store (minio for now)\n- caliopen's api (api)\n- caliopen's frontend (frontend)\n- caliopen's lmtp service (broker)\n\nYou can start storage services using these commands:\n\n```\ncd devtools\ndocker-compose pull\ndocker-compose up -d redis cassandra elasticsearch\n```\n\n(wait few seconds for cassandra to warm-up)\n\nUsing caliopen cli tool:\n\n- You must setup storage and some basic configuration (TTL for notifications):\n\n```\ncd devtools\ndocker-compose run cli setup\n```\n\n- You should create an admin user with the same username as in\n  `configs/caliopen-go-api_dev.yaml`\n\n```\ndocker-compose run cli create_user -e admin -p 123456\n```\n\n- Then, you could create an user and import email\n\n```\ndocker-compose run cli create_user -e dev -p 123456\ndocker-compose run cli import -e dev@caliopen.local -f mbox -p devtools/fixtures/mbox/dev@caliopen.local\n```\n\n**NB** : data are persisted after containers are stopped, even after being\ndestroyed.\n\nFinally start the stack\n\n```\ndocker-compose up -d frontend lmtpd apiv2 apiv1\n```\n\n**NB** : for now, outgoing emails are caught by a local smtp server for testing\npurpose.\n\nYou will have a Caliopen instance filled with data, accessible from your browser\non localhost:4000. You could check outgoing emails by pointing your browser at\nlocalhost:8888.\n\n### Remote identities poller and workers\n\nTo automatically fetch emails from remote IMAP accounts you need to :\n\n1. launch _identityworker_\n2. create a _remote identity_ for user _dev_\n3. launch _identitypoller_\n\n##### 1. launch _imap_worker_\n\n`docker-compose up -d identity_worker`\n\n##### 2. create a _remote identity_ for user _dev_:\n\n- Create at least one remote identity for user _dev_. Enter email account (-l),\n  password (-p), server address (-s) and user*name for \\_dev* account (-u) :\n\n`docker-compose run --no-deps --entrypoint imapctl identity_worker addremote -l 'your_email@gmail.com' -p 'your_secret_password' -s 'imap.gmail.com:993' -u 'dev'`\n\n##### 3. launch _ids_poller_\n\n`docker-compose up -d identity_poller`\n\n_ids_poller_ will retrieve remote identities from cassandra and schedule\nfetching jobs accordingly on a regularly basis. You can add more remote\nidentities later, they will be retrieve by _ids_poller_ as well.\n\n## Start frontend natively\n\n**Requirements:**\n\n- You need [node](https://nodejs.org/en/) v8 or later\n- You need [yarn](https://yarnpkg.com/en/docs/install) to manage dependencies.\n- Enventually stop the frontend container `docker-compose stop frontend`\n\nThen develop locally using your normal practices:\n\n```\ncd src/frontend/web_application\nyarn install\nnpm start\n```\n\nIt's a bit long to compile. The first time you will see some errors about a\nmissing file, it will be fixed at the end of the compilation.\n\n## Rebuild a container\n\nIn case the image of a service (api or apiv1 ...) is not up-to-date, you mmight\nneed to rebuild a container.\n\n_Replace \"api\" by the service you want to rebuild_\n\n```\ndocker-compose build api\ndocker-compose stop api\ndocker-compose up -d api\n```\n\n## Fresh install\n\nTo reset containers and data:\n\n```\ndocker-compose stop\n\n# remove containers :\ndocker-compose rm\n\n# remove volumes created by containers :\ndocker volume rm devtools_db devtools_index devtools_store\n```\n\n## Troubleshoutings\n\n**elasticsearch won't start**:\n\nAccording to\n[this issue](https://github.com/docker-library/elasticsearch/issues/111), you\nmight solve it with this command on the host:\n\n```\nsysctl -w vm.max_map_count=262144\n\n# make it permanent:\necho \"vm.max_map_count = 262144\" > sudo tee /etc/sysctl.d/vm.caliopen.conf\n```\n\n**frontend does not build**\n\nThe following error may appears:\n\n```\nModule build failed: Error: ENOENT: no such file or directory, scandir '/.../CaliOpen/Caliopen/src/frontend/web_application/node_modules/node-sass/vendor\n```\n\nIt is related to\n[node-sass](https://github.com/sass/node-sass/issues/1579#issuecomment-227661284)\ninstallation. To fix the easiest way is to remove `node_modules` then reinstall\nwith yarn:\n\n```\n(cd src/frontend/web_application/ && rm -r node_modules && yarn)\n```\n\nor rebuild node-sass:\n\n```\nnpm rebuild node-sass\n```\n\n**docker container can't build or start**\n\nThe following error can appear when docker is not run as root:\n\n> Building api ERROR: Couldn't connect to Docker daemon at\n> http+docker://localunixsocket - is it running?\n\nIt is mostly due to minio server which updates its config file without asking.\n\nYou need to change the owner of the file (please don't commit) until we find a\nsolution.\n\n```bash\nsudo chown $USER: src/backend/configs/minio/config.json\n```\n\n_As described [here][1], the config is automatically migrated and we can't use\nenvironment variables yet._\n\n[1]: https://docs.minio.io/docs/minio-server-configuration-guide.html\n"
  },
  {
    "path": "doc/install/minikube-local-development.md",
    "content": "## Local development using minikube\n\n### Requirements\n\nyou need local resolution of service name used by munikube. Edit your /etc/hosts and add this line :\n\n```\n127.0.0.1\tcassandra elasticsearch redis nats minio vault apiv1 api broker idpoller imapworker\n```\n\n### Go development\n\nTo start a local apiv2 server, you have to be in the correct directory to find all related configuration files\n\n```sh\ncd src/backend/configs\ngo run ../interfaces/REST/go.server/cmd/caliopen_rest/main.go serve -c apiv2\n```\n\nTo start a local lmtp server, same location than for apiv2 and configuration files:\n\n```sh\ncd src/backend/configs\ngo run ../protocols/go.smtp/cmd/caliopen_lmtpd/main.go serve -c lmtp -p lmtp.pid\n```\n\nApply same principle for `idpoller` and `imapworker`\n\n### Python development\n\nTo start a local apiv1 server using pyramid pserve :\n\n```sh\ncd src/backend\npserve configs/apiv1.ini --reload\n```\n\nTo start a local worker processing NATS messages:\n\n```sh\ncd src/backend/\npython interfaces/NATS/py.client/caliopen_nats/listener.py -f configs/caliopen.yaml\n```\n\n### Javascript development\n\nYou need [yarn](https://yarnpkg.com) and [node 8.11.x](https://nodejs.org/) installed locally\nto be able to run a local caliopen client.\n\n```sh\nexport CALIOPEN_API_HOSTNAME=api\ncd src/frontend/web_application\nyarn run install\nyarn run start\n```\n"
  },
  {
    "path": "doc/install/native-installation.md",
    "content": "# Native installation of Caliopen\n\n## Go developments for CaliOpen\n\nAs Caliopen use a mono repository structure and use many languages,\nthe setup for a go development environment need some specific considerations.\n\n### A specific GOPATH is privilegied\n\nWe use [govendor](https://github.com/kardianos/govendor) for external dependencies\nit's better to have a specific GOPATH to develop on Caliopen.\n\n```\nmkdir $HOME/caliopen\ncd $HOME/caliopen\nfor i in bin pkg src/github.com/CaliOpen\ndo\n\tmkdir -p caliopen/$i\ndone\n\ncd src/github.com/CaliOpen\ngit clone https://github.com/CaliOpen/Caliopen\n\n# DO NOT FORGET TO SETUP AS a distinct GOPATH\nexport GOPATH=$HOME/caliopen\n\n```\n\nNOTE: you will need to setup frequently this GOPATH, add an alias into your shell, for example for bash or zsh:\n\n```\nalias tocaliopen=\"export GOPATH=$HOME/caliopen; cd $GOPATH/src/github.com/CaliOpen\"\n```\n\n### Setup external dependencies\n\nUsing govendor external dependencies will be fetch into your Caliopen repository\n\n```\ncd $GOPATH/src/github.com/CaliOpen/Caliopen\ngo get -u github.com/kardianos/govendor\ngovendor sync -v\n```\n\nDirectory `$GOPATH/src/github.com/CaliOpen/Caliopen/src/backend/vendor` contain\nall external golang dependencies for all CaliOpen services written in this\nlanguage.\n\n### Build\n\nRefer to devtools/package.yaml file for services in go language and their\nbuild command\n\nFor example:\n\n```\ngo build github.com/CaliOpen/Caliopen/src/backend/protocols/go.smtp/cmd/caliopen_lmtpd\n```\n\nWill produce a caliopen_lmtp binary from where this command is launched inside the $GOPATH\n\n## Python developments for CaliOpen\n\n### System packages\n\n#### Debian/Ubuntu\n\n```\napt-get install virtualenv libffi-dev gcc python-dev\n```\n\n### Python virtualenv\n\nYou will need a **python2** local [virtualenv](https://virtualenv.pypa.io/en/latest/) than can be setup using the\n[devtools/setup-virtualenv.sh](../../devtools/setup-virtualenv.sh) script.\n\n\n#### Apiv1\n\nTo start the apiv1 service and develop on it, you can use the pyramid pserve command to start a local\nhttp server that will be restarted each time you update part of the code.\n\n```\ncd src/backend\npserve configs/apiv1.ini --reload\n```\n\n#### Message queue worker\n\nThis service process nats message for :\n- incoming SMTP message\n- incomling Twitter message\n- cryptographic public key actions\n- contact actions (create, update)\n\n\n```\ncd src/backend/interfaces/NATS/py.client/caliopen_nats\npython listener.py -f ../../../../configs/caliopen.yaml.template\n```\n"
  },
  {
    "path": "doc/install.md",
    "content": "# Caliopen installation\n\nHere you will find tools for CaliOpen developments. There is many possibles\nscenarios for setup of an environment development, depending on what you want to\ndo.\n\n- [Native](./install/native-installation.md) for backend development purposes\n  (golang & python)\n- [Frontend development](./install/frontend-development.md) (js & react)\n\n## Tools\n\nThe [devtools directory](../../devtools) contains useful commands for\ndevelopment. _Most of this commands run natively and don't work in a dockerized\nenvironment._\n\n- api-mock: it's full js mock api, it used by travis for frontend functional\n  tests\n- storybook: its purpose is to show react components for designer & frontend\n  developers\n- clean-dev-storage.sh: destroy docker volumes used by persistent stores\n  (Cassandra and Elasticsearch)\n- gen-swagger-spec.sh: (native) re-generate swagger file and store it into\n  [`src/backend/configs/`](../../src/backend/configs/) directory and html doc\n  into [`src/backend/doc/api/`](../../src/backend/doc/api/)\n- make_release: python script to build a release\n- manage_package: python script to manage caliopen packages\n- run-tests.sh: start tests in travis, this launch frontend or backend tests\n  according to recent changes\n- setup-virtualenv.sh: script to setup a python virtualenv for python developers\n\n## Staging scenarios\n\nPlease refer to [ansible-poc][ansible-poc] project, this will prepare your host\nand start Caliopen in stable mode (latest release).\n\nUnder the hood it will run docker-compose with staging config:\n\n```bash\ndocker-compose -f docker-compose.staging.yml up -d frontend broker\n```\n\n[ansible-poc]: https://github.com/CaliOpen/ansible-poc\n"
  },
  {
    "path": "doc/monitoring-debug/analyse-frontend-bundle.md",
    "content": "# Analyse frontend bundle\n\n## bundle size\n\nThis is useful to see the real size of the js files loaded by the client.\n\n**Pre-requisites**\n\nYou need source-map-explorer: `npm i -g source-map-explorer`\n\n1. release the frontend:\n\n  ```bash\n  yarn release:web:browser\n  ```\n\n2. Launch the explorer for each generated file you want\n\n  ```bash\n  source-map-explorer dist/server/public/assets/app.{hash}.js\n  source-map-explorer dist/server/public/assets/vendor.{hash}.js\n  ...\n  ```\n\nThese command will generate an html page in temp dir with a dynimic tree of the bundle\n\n## webpack module tree\n\nThis tree shows all modules handled by webpack and see size usage\n\n**Pre-requisites**\n\nYou need webpack-bundle-analyzer: `npm i -g webpack-bundle-analyzer`\n\n1. generate stats\n\n  ```bash\n  NODE_ENV=production webpack --config webpack/webpack.config.browser.js -p --progress --profile --json > webpack-client-stats.json\n  ```\n\n2. launch analyser tool (it's little webserver)\n\n  ```bash\n  webpack-bundle-analyzer -p 4444 webpack-client-stats.json\n  ```\n\nAlso, you can upload the stats on http://webpack.github.io/analyse which can provide other useful informations\n"
  },
  {
    "path": "doc/release/Communication.md",
    "content": "# Public communications\n\nWhen releasing a new version a CaliOpen, some public communications have to be done. Follows up a non-exhaustive list:\n\n**The Changelog**\n\nThis is visible in release tab on github. It should list the functionalities that have been added, fixed or removed.\nIt is a technical content, in english.\n\n**Blog post**\n\nPost an article on https://www.caliopen.org/blog-fr/ in multiple languages. It defines what is the purpose of the release and informations understandable to regular people. It can content a link to github's release page.\n\n**Twitter**\n\nTwitter is a special case due to its constraint and its instantanity.\nPublish a simple short description of the release with a link to the blog post in main available languages.\nThe tweet can be reposted 2 or 3 times dependending on language and timezone.\n\n**Mailing list and social networks**\n\nFor now it is a simple description of the release with a link to the blog post in main available languages.\nPresently it's not necessary to publish on Facebook, Linked In, viadeo, tumblr, VKontakte, renren ...\n"
  },
  {
    "path": "doc/release/Guidelines.md",
    "content": "## GIT Taging\n\n- each package version have its own git tag in form of :  \n`<ressource>.<package_name>-<version>`  \n*\\<ressources\\>* values are names of the subdirectories under /src/ :  `backend/components/`, `frontends/web_app/` …\n\n- each global release have its git tag in form of :  \n    `release-<version>`\n\n## Versioning\nCaliopen follows the [semver](http://semver.org/)'s Semantic Versioning rules.\n\n#### Python versioning\n\n- pre-requisites:\n\neach python package **MUST** have in the `__init__.py` file on it's exposed namespace :\n    \n```\n__version__ = 'x.x.x'\n```\n\npackage's `setup.py` must expose package version using reading of this version number.\n\n- create a tag for a python package:  \n    - update `__version__` number with relevant version # in `__init__.py` file\n    - create the git tag\n"
  },
  {
    "path": "doc/release/README.md",
    "content": "# Release\n\nThings to do for a new release...\n\n## 1. Prepare\n\n- Merge all the things in `master` branch\n  ```\n  git checkout develop && git pull\n  git checkout master && git pull\n  git merge develop\n  ```\n- Make sure `CHANGELOG.md` is up to date according to http://keepachangelog.com\n- Stash or commit if you have changes\n- Change the version of caliopen packages (python and virtualenv are required):\n\n  ```bash\n  cd devtools\n  # or you can use `setup-virtualenv.sh`\n  virtualenv --python=python2.7 ./.venv\n  source .venv/bin/activate\n  pip install -r ./manage_package-requirements.txt\n  ./manage_package --help\n  # change in the following the package name you want (refer to packages.yaml e.g \"client\", etc. or all) and the version\n  ./manage_package --conf ./packages.yaml create_version all 0.18.1\n  deactivate\n  ```\n\n- Add a tag for the release, the tag will be signed and annoted:\n  ```\n  git tag -a -s release-0.18.0\n  ```\n- Publish the release on github:\n  ```\n  git push\n  git push origin release-0.18.0\n  ```\n- Create a new release https://github.com/CaliOpen/Caliopen/releases/new and\n  specify the tag name you've just published e.g. `release-0.18.0`\n\n## 2. Build images & Publish\n\n### Prepare\n\n```\n# checkout the tag you need to build\ngit checkout release-X.Y.Z\n\ncd devtools\n\n# Make sure having credential for registry access\ncp registry.conf.template registry.conf\nvi registry.conf\n\n# Note: It's excluded from git objects in .gitignore file.\n\n# Define environment variable for version to deploy\nexport CALIOPEN_VERSION=X.Y.Z\n```\n\n### Build and publish\n\nThis will build the container related to the given application and publish it on\nthe docker images registry using 2 tags:\n\n- `${APP_NAME}/latest`\n- `${APP_NAME}/${CALIOPEN_VERSION}`\n\n```\ncd devtools\nmake master APP_NAME=<name of app to deploy>\n```\n\n## 3. Deploy\n\ncf. [Deploy Kubernetes](./deploy_kubernetes.md)\n\n## 4. Communicate\n\ncf. [Communication](./Communication.md)\n"
  },
  {
    "path": "doc/release/deploy_kubernetes.md",
    "content": "Deploy application\n==================\n\nFor cluster basic operational commands, there is a [survival guide](../devops/kube_survival_guide.md)\n\nDeploy on cluster\n-----------------\n\n```\n# Connect to a node having a `kubectl` capability.\nssh root@bastion.cluster\n\n# Edit the deployment and change the image version to use\nkubectl get deployment\nkubectl edit deployment <deployment_name>\n\n# Then find section container:image in yaml file and replace version number.\n# Something like: public-registry.caliopen.org/${APP_NAME}:${CALIOPEN_VERSION}\n\n```\n\nDone.\n"
  },
  {
    "path": "doc/specifications/attachments/assets/management.puml",
    "content": "@startuml\ntitle Attachment handling\nbox \"User frontend\"\nactor \"authenticated user\" as user\nparticipant \"new message UI\" as compose\nendbox\nbox \"python application\"\ncontrol py.api\nparticipant py.main\nendbox\nbox \"go application\"\ncontrol go.api\nparticipant go.main\nendbox\ndatabase db\ndatabase index\ndatabase objects_store as os\nbox \"NATS\"\nentity \"outboundSMTP channel\" as out\nendbox\nbox \"Brokers\"\nparticipant \"Email broker\" as eb\nendbox\nuser o-> compose : hit 'add attachment'\nactivate compose\ncompose -[#00FF00]> go.api : [POST …messages/{message_id}/attachments]\ngo.api -> go.main : \"AddAttachment\"\nactivate go.main\ngo.main -> os : store temporary file\ngo.main -> go.main : update message draft\\nwith attachment attributes\\nand temporary file uri\ngo.main -> db : update draft in db\ngo.main -> index : update draft in index\ngo.main -> go.api : attachment's url\ndeactivate go.main\ngo.api -[#00FF00]-> compose: attachment's url\ndeactivate compose\nalt send draft\nuser o-> compose : hit 'send message'\nactivate compose\n\ncompose -[#00FF00]> go.api : send message\\n[POST /messages/{message_id}/actions]\ngo.api -> go.main : \"SendDraft\"\nactivate go.main\ngo.main -[#0000FF]> out : \"deliver\" order with message_id\neb x-[#0000FF]> out : subscribe \"deliver\"\nactivate eb\nnote right: email delivery process\neb x-> db : get message\neb x-> os : get temporary files\neb -> eb : marshal email, etc.\neb -> mta : send message\nmta -> eb : OK\neb -> db : store raw_message\neb -> os : delete temporary files\neb -> db : update message store (sent)\neb -> index : update message index (sent)\neb -[#0000FF]> out: \"deliver\" response\ndeactivate eb\ngo.main x-[#0000FF]> out : async \"deliver\" response\ngo.main x-> db : get_message\ngo.main -> go.api : send back updated message\ndeactivate go.main\ngo.api -[#00FF00]> compose : updated message\ndeactivate compose\nend\ndeactivate compose\nalt delete attachment\nuser o-> compose: hit 'delete attachment'\nactivate compose\ncompose -[#00FF00]> go.api : [DELETE …messages/{message_id}/{attachments/{attachment_id}]\ngo.api -> go.main: \"delete_attachment\"\nactivate go.main\ngo.main -> go.main: update message draft\\nwith attachment attributes\ngo.main -> os : delete temporary files\ngo.main -> db: update draft in db\ngo.main -> index: update draft in index\ngo.main -> go.api: ack or error\ndeactivate go.main\ngo.api -[#00FF00]-> compose : error or 204 OK\ndeactivate compose\nend\n@enduml"
  },
  {
    "path": "doc/specifications/attachments/index.md",
    "content": "# Attachments\n\n![management](./assets/management_2017-06-01.png)\n"
  },
  {
    "path": "doc/specifications/client/architecture.md",
    "content": "# Architecture\n_(Code organization for the client)_\n\n## Bootstrap\n\nThe main file is `src/App.jsx`, it is used both by SSR (expressjs http server) and the browser.\nThe file `src/index.js`is only used by the browser.\n\n## Routes\n\nThe routing is declared in `src/modules/routing` and indicates which «Scene» to render.\nAll the Scene are in `src/scenes/`.\n\ne.g `src/scenes/Timeline`\n\n\n## Layouts\n\nThe layouts represent the page according to the context, the login form has a different layout as the timeline…\n\ne.g. `src/layouts/Settings`\n\n## Modules\n\nThe business logic is in those modules: `src/modules/`.\nIt is organized by domain and are not directly coupled so a module can be completely mocked or replaced.\n\ne.g. `src/modules/contacts`\n\n### Inside a module\n\nA module has always the same architecture but all are optionnal except the index:\n\n- `index.js`: provide an access to the public API of the module\n- `actions/`: redux actions e.g. `requestContacts.js`\n- `assets`: images, fonts…\n- `components/`: react components (can be nested)\n- `contexts`: [react contexts](https://reactjs.org/docs/context.html)\n- `hoc`: [react higher order components](https://reactjs.org/docs/higher-order-components.html)\n- `hooks`: [react hooks](https://reactjs.org/docs/hooks-intro.html)\n- `models`: base class of entities e.g `src/modules/message/models/Message.js`\n- `selectors/`: redux selectors\n- `services/`: utility pure functions. Services are not coupled to redux nor react.\n\n#### Redux actions\n\nThe API of a redux action is a function that can return an object that redux can read, a function or a Promise (cf. thunk and Promise middlewares).\n\ne.g\n```\n// this a simple action cf. https://redux.js.org/introduction/getting-started#basic-example\nconst requestContactsBase = {\n  type: 'requestContacts',\n  payload: {},\n};\n// this is a «thunk action» that can chain multiple actions cf. https://github.com/reduxjs/redux-thunk\nexport const requestContacts = () => (dispatch) => dispatch(requestContactsBase);\n```\n\n## Store\n\nThis the implementation of redux using [«Ducks: Redux Reducer Bundles»](https://github.com/erikras/ducks-modular-redux/), a modular organization of [redux](https://redux.js.org/)\n\ne.g. `src/store/reducer.js`\n\n## Components\n\nEach reusable component without business logic is in `src/components/`.\n\ne.g. `src/components/Button`\n\n## Other «main» directories\n\nThose directories requires to be refactored because of the legacy code that can be in there. The best example is `src/services/` that should be splitted in multiple modules.\n"
  },
  {
    "path": "doc/specifications/client/frontend-data-flow.md",
    "content": "# Frontend data flow\n\nCaliopen must offers a simple API for a simple concept:\n\n1. The interface handles an user action\n2. An action is launched, it does one or more calls to backend\n3. The interface is able to display the state of this action\n4. At the end the interface show the result of the action (success or error)\n\n\n## Actions\n\nFollowing this assomption, an action MUST be a Promise.\nAn action MAY be specific to a component.\nAn action MAY be related to one or more concept.\nAn action MAY be a serie of redux actions.\nAn action MUST return an entity or a collection of entity or throw an erro except for `fetch` actions, see below.\nAn action MUST store the result of the action(s) in the state except for password related actions.\nAn action MUST be in the `actions` folder of a module. An exception for low level redux actions that are in `src/store/modules/<reduxModule>` (cf. client/architecure.md)\n\n### Naming\n\nThe name of an action tells' the developer the purpose of the action. There are 3 main prefixes: `fetch`, `get`, `request`\n\n* `get<Resource>` will try to retrieve data from the store and eventually request it then return the resource\n* `request<Resource>` will force the fetch and return the resource\n* `fetch<Resource>` is a raw fetch, the value return is the raw response\n\nExamples:\n_note: we use [axios middleware](https://github.com/svrcekmichal/redux-axios-middleware) to make the requests_\n\n```js\nimport { messageSelector } from 'a/message/module/';\n\nconst fetchMessage = (messageId) => {\n  type: 'fetchMessage', payload: { request: `/api/v2/messages/${messageId}` } ,\n};\n\nconst requestMessage = (messageId) => async (dispatch, getState) => {\n  await dispatch(fetchMessage(messageId));\n\n  return messageSelector(getState(), { messageId });\n}\n\nconst getMessage = (messageId) => (dispatch, getState) => {\n  const message =  messageSelector(getState(), { messageId });\n  if (message) {\n    return message;\n  }\n\n  return dispatch(fetchMessage(messageId));\n}\n```\n\n## State\n\nThe different parts of the state can be rendered anywhere at any time: a list of messages, a contact even if refreshing. It also can be the state of a request:\n\nLoading more message from a discussion will display a spinner in a tab. Sending a message will display a spinner in the button.\n\n**FIXME:** How to select loading action in a tab ?\n\nActions w/ business logic launch a redux action with the type of the action and an internal id (it can be a discussion_id or a simple string e.g `timeline`) And HoC or a render prop can select the state by passing the type and the id\n\n## Redux\n\nRedux actions are not meant to be used directly, and are ALWAYS a simple function retuning an object describing the action that MAY be parsed by the reducers.\n\nA middleware is NEVER blocking EXCEPT if it changes the action (xhr requests ...).\n\n## Cache\n\nIs there a need for cache ?\n\nCurrently, some requesets are made multiple times instead of using the state, because the interface or an action has no idea if the state is fullfiled when it requires it.\n\nFor example, suggest execute an xhr to retrieves a list of participants and contacts. it requires to fetch contacts one by one even if its are already loaded.\n\nA tab is created using messages from a discussion, the tab list is located in a other part of the application compared to the discussion.\n"
  },
  {
    "path": "doc/specifications/client/routing.md",
    "content": "# How to create a route\n\n## Scene\n\nCreate a scene component for example `src/scenes/Foobar`\n\n\n\n## Route Config\n\nEdit the file `src/modules/routing/components/RoutingProvider.jsx`, and add the config:\n\n```js\nimport Foobar from '../../../scenes/Foobar';\n// …\n\n{\n  path: '/foobar',\n  exact: true,\n  component: Foobar,\n  app: 'contact',\n  tab: {\n    type: 'default',\n    icon: 'foobar',\n    renderLabel: () => i18n._('route.foobar.label', null, { defaults: 'Foobar' }),\n    // use tabMatchPathname or tabMatchRoute when path uses params e.g. /foobar/:id\n    tabMatch: tabMatchPathname,\n  },\n}\n```\n\n## Tab implementation\n\n**Optionnally** use a specific tab for a custom label or icon to render.\n\nChange the tab's type previously set:\n\n```diff\ntab: {\n    -type: 'default',\n    +type: 'foobar',\n    icon: 'foobar',\n    renderLabel: () => i18n._('route.foobar.label', null, { defaults: 'Foobar' }),\n```\n\n_**FIXME:** finish refactor and rename Page2 into Page_\n\nCreate a tab component in `src/layouts/Page2/components/Navbar/components`\n\n\n```\nimport React from 'react';\nimport classnames from 'classnames';\n// …\nimport { Icon } from '../../../../../../components';\nimport { getTabUrl } from '../../../../../../modules/tab';\nimport Tab from '../Tab';\nimport NavbarItem from '../NavbarItem';\nimport ItemLink from '../ItemLink';\nimport ItemButton from '../ItemButton';\n\n// …\n\nclass FoobarTab extends Tab {\n  render() {\n    const {\n      className,\n      isActive,\n      tab,\n      routeConfig,\n    } = this.props;\n\n    const label = routeConfig.tab.renderLabel();\n\n    return (\n      <NavbarItem\n        className={classnames('m-tab', className)}\n        active={isActive}\n        contentChildren={(\n          <ItemLink\n            to={getTabUrl(tab.location)}\n            title={label}\n            className=\"m-tab__content\"\n          >\n            <Icon type=\"foobar\" className=\"m-tab__icon\" rightSpaced />\n            {label}\n          </ItemLink>\n        )}\n        actionChildren={<ItemButton onClick={this.handleRemove} icon=\"remove\" className=\"m-tab__action\" />}\n      />\n    );\n  }\n}\n```\n\nEdit the file `src/layouts/Page2/components/Navigation/presenter.jsx` and make a condition to render the specific tab:\n\n```\nswitch (routeConfig.tab.type) {\n  // …\n  case 'foobar':\n    return (\n      <FoobarTab\n        key={this.getTabIdentifier(tab.location)}\n        tab={tab}\n        routeConfig={routeConfig}\n        isActive={isActive}\n        onRemove={removeTab}\n      />\n    );\n```\n"
  },
  {
    "path": "doc/specifications/contact/assets/discover_contact_from_email.uml",
    "content": "@startuml\n\ntitle User add contact information that can be used to find additionnal informations, including a PGP key\n\nUser -> Frontend: add an email to a contact\nFrontend -> API: PATCH /contacts/<contact_id> {new_email}\nAPI -> Storage: patch_contact(contact_id, new_email)\nStorage -> nats: contact_add_email(contact_id, new_email)\n\nStorage -> API: True\nAPI -> Frontend: 200 OK\nFrontend -> User: updated contact\n\n\nalt asyncronous identity handler for contact information update\n\tnats -> identity_handler: contact_add_email(contact_id, email)\n\tidentity_handler -> DNS: find pgp key\n\tDNS -> identity_handler: nothing\n\tidentity_handler -> HKP: find pgp key by email\n\tHKP -> identity_handler: nothing found\n\tidentity_handler -> keybase: find contact on keybase by email\n\tkeybase -> identity_handler: 200 OK {contact with key and other social identities}\n\tidentity_handler -> nats: updated_contact(contact_id, new_informations)\n\nend\n\n@enduml"
  },
  {
    "path": "doc/specifications/contact/index.md",
    "content": "# Contacts\n\n## discover contact informations\n\n![discover](./assets/discover_contact_from_email.png)\n"
  },
  {
    "path": "doc/specifications/contact/vcard_doc.md",
    "content": "# Import vcard\n\nWe can import one or more vcards from a file (using the caliopen cli).\n\n## Import vcard\n\n```\ncd src/backend\ncaliopen import_vcard --help\ncaliopen -f configs/caliopen.yaml.template import_vcard -u <username> [-d <directory> | -f <file>]\n```\n\n```\ndocker-compose run cli import_vcard --help\ndocker-compose run cli import_vcard -u <username> [-d <directory> | -f <file>]\n```\n\nIt's possible import vcards with a directory as a parameter.\nElse, import vcards with a file as a parameter.\nAlso we can import vcards as a directory *and* a file. \n\n**NB**: Only file with extension *.vcf* or *.vcard* are imported.\n\n## Working\n\n* src/backend/main.py/main.caliopen_main/parsers\n\nIn vcard.py, we find functions `parse_vcard` and `parse_vcards` which read one or more vcards and return a NewContact or an array of NewContact. \n\n* src/backend/tools/py.CLI/caliopen_cli/commands\n\nIn import_vcard.py, function `import_vcard` create new contact, using parser, at a user (in parameter) with the file or the directory of vcards (in parameter). \n"
  },
  {
    "path": "doc/specifications/discussions/assets/delete_discussion.uml",
    "content": "@startuml\n\ntitle Delete a discussion\n\nactor Client\nparticipant API\nparticipant CoreDiscussion\nparticipant CoreMessage\ndatabase Cassandra\ndatabase Index\n\n\nClient -> API: DELETE /discussions/<discussion_id>\n\nAPI -> CoreDiscussion: instanciate Discussion core object\nactivate CoreDiscussion\n\nCoreDiscussion -> Index: find all messages related to <discussion_id>\n\nloop foreach messages\n\tCoreDiscussion -> CoreMessage: delete()\n\tCoreMessage -> Cassandra: delete()\n\tCoreMessage -> Index: delete()\n\tCoreDiscussion -> CoreMessage: save()\nend\n\nCoreDiscussion -> API: ok\ndeactivate CoreDiscussion\nAPI -> Client: 200 OK\n\n\n@enduml"
  },
  {
    "path": "doc/specifications/discussions/delete_discussion.md",
    "content": "Delete discussion technical specification\n=========================================\n\nSynopsis\n--------\n\n![delete discussion](./assets/delete_discussion.png)\n\nAPI receive a DELETE /discussions/<discussion_id> HTTP request.\n\nAPI is not responsible of the business logic, it only handle a message and\ncall an object that will execute the logic (a `core` object).\n\nThe core object will retrieve all messages that belong to the given `discussion_id` (core method filter(...) can do that). For each retrieved message, Message.date_delete is set to now() (UTC timezone) adn then message saved. The core method `Message.save()` will propagate changes on cassandra and elasticsearch.\n\nThe Discussion.date_delete parameter is set to same date as message.\n\nSo our objects (message and discussion) are not really deleted from our storages services, they are only flagged as it with a timestamp information. All interfaces to retrieve these objects filter automatically with a date_delete = NULL. \n\nThis is called a `soft delete` principle. Objects will be really deleted later (many days or months later) by a dedicated process.\n\nThe API return an HTTP status 200 if everything is fine.\n\nThings to do\n------------\n\n- Define the swagger specification for DELETE /discussions/<discussion_id> route\n- Create a `Discussion.delete()` method on class `caliopen_main.discussion.core`\n- Implement the API in `caliopen_api.discussion` python package\n\nWhat part of code to consider\n-----------------------------\n\n- src/backend/interfaces/REST/py.server for the apiv1 server\n- src/backend/main/py.main/caliopen_main for the core objects\n- src/backend/defs/rest-api for the swagger definition, \n- devtools/gen-swagger-spec.sh for generating the src/backend/configs/swagger.json global file loaded by API servers\n\nAn important design note\n------------------------\n\nFor an huge discussions the global response time for such DELETE action will take many seconds (at least). The natural evolution for this process will be to run asynchronous and emit a notification to client when it's done.\n\nThis is where the message queuing service (nats) is used, to do asynchronous processing.\n\n- API receive the delete action and do basic check (discussion exist and not yet deleted, ...)\n- API publish a message to nats and reply HTTP 202 immediatly\n\n[..]\n\n- nats worker receive a message to delete a discussion, the core Discussion object is instanciated and then delete() method call.\n- nats emit a notification when processing is done."
  },
  {
    "path": "doc/specifications/email-protocol/assets/email-delivery-inbound-rpc.puml",
    "content": "@startuml\nhide footbox\ntitle Inbound Email delivery\nbox \"Brokers\"\nparticipant \"Email broker\" as eb\nendbox\ndatabase db\ndatabase index\nbox \"gRPC\"\nentity \"RPC server\" as rpc\nendbox\nbox \"Caliopen main\"\ncontrol \"email delivery process\" as main\nendbox\nbox \"Caliopen components\"\nparticipant \"Message qualifier\" as mq\nparticipant \"PI calculator\" as pi\nparticipant \"ALICE builder\" as alice\nendbox\n\n[-> eb: smtp email\nactivate eb\neb -> eb: unmarshall email\neb -> eb: validation/conformation\neb -> eb: process recipients\neb --\\ db: users lookups\ndb --/ eb\n|||\neb -\\o db: store raw email once\ndb --// eb: email ID\neb -> eb: marshall \"message\" into db model\neb -> eb: marshall  \"message\" into protobuf\n== Repetition ==\neb -\\o db: store \"message\" model\\nfor each recipient (type email)\ndb --// eb: message IDs\n|||\neb -> rpc:call «process inbound message»\\nfor each recipient\\n(with message payload)\ndeactivate eb\nactivate main\nrpc -[#0000FF]> main: protobuf msg\\n\"process message\"\\n(with message payload)\nautonumber\nmain -> rpc: call \"qualify message\"\\n(with message payload)\nmain -> rpc: call \"calculate PI\"\\n(with message payload)\nautonumber stop\nrpc -[#0000FF]> pi: protobuf msg\\n\"calculate PI\"\\n(with message payload)\nactivate pi\nrpc -[#0000FF]> mq: protobuf msg\\n\"qualify message\"\\n(with message payload)\nactivate mq\nmq -> mq: unmarshall message\nmq -> mq: associate contact IDs with recipients\nmq -> mq: associate message to a discussion\nmq -> mq: calculate importance level\nmq -> mq: etc…\nmq -> mq: update lookup tables\nmq ->o db: store lookup info in lookup tables\nmq -> mq: marshall message to protobuf\nmq ->> rpc: call «qualification done»\\n(with message payload)\ndeactivate mq\nrpc -[#0000FF]> main: protobuf msg\\n\"qualification done\"\\n(with message payload)\n\npi -> pi: unmarshall message\npi -> pi: PI calculation\npi -> pi: marshall message to protobuf\npi ->> rpc: call «PI calculation done»\\n(with message payload)\ndeactivate pi\nrpc -[#0000FF]> main: protobuf msg\\n\"PI done\"\\n(with message payload)\nmain -> main: marshall \"message\" into db model\nautonumber resume\nmain ->o db: update \"message\" model\nautonumber stop\n\nmain -> main: build ALICE\nmain -> main: marshall ALICE into index model\nautonumber resume\nmain -> rpc: call \"build ALICE\"\\n(with message payload)\nautonumber stop\nrpc -[#0000FF]> alice: protobuf msg\\n\"build ALICE\"\\n(with message payload)\nactivate alice\nalice -> alice: unmarshall message\ncreate entity \"lucene engine\"\nalice -> \"lucene engine\": \"scan body\"\ncreate entity \"qwant engine\"\nalice -> \"qwant engine\": \"build semantics\"\n\"lucene engine\" -> alice: \"here is the lexicon\"\n\"qwant engine\" -> alice: \"here are the keywords\"\nalice -> alice: compute ALICE\nalice -> alice: marshall ALICE\nalice -> rpc: call \"index ALICE\"\\n(with ALICE payload)\ndeactivate alice\nrpc -[#0000FF]> main: protobuf msg\\n\"index ALICE\"\\n(with ALICE payload)\nautonumber resume\nmain ->o index: send ALICE to index\nautonumber stop\n\ndeactivate main\n@enduml\n"
  },
  {
    "path": "doc/specifications/email-protocol/assets/email-delivery-inbound.puml",
    "content": "@startuml\nhide footbox\ntitle Inbound Email delivery\nbox \"Brokers\"\nparticipant \"Email broker\" as eb\nendbox\ndatabase db\ndatabase index\nbox \"messages platform\"\nentity \"messages server\" as msg\nendbox\nbox \"Caliopen main\"\ncontrol \"email delivery process\" as main\nendbox\nbox \"Caliopen components\"\nparticipant \"Message qualifier\" as mq\nparticipant \"PI calculator\" as pi\nparticipant \"ALICE builder\" as alice\nendbox\n\n[-> eb: smtp email\nactivate eb\neb -> eb: unmarshall email\neb -> eb: validation/conformation\neb -> eb: process recipients\neb --\\ db: users lookups\ndb --/ eb\n|||\neb -\\o db: store raw email once\ndb --// eb: email ID\neb -> eb: marshall \"message\" into db model\neb -> eb: marshall  \"message\" into protobuf\n== Repetition ==\neb -\\o db: store \"message\" model\\nfor each recipient (type email)\ndb --// eb: message IDs\n|||\neb -> msg:send msg «process inbound message»\\nfor each recipient\ndeactivate eb\nactivate main\nmain x[#0000FF]-> msg: subscribe to \\n\"process inbound message\"\nautonumber\nmain -> msg: send msg \"qualify message\"\nmain -> msg: send msg \"calculate PI\"\nautonumber stop\npi x-[#0000FF]> msg: subscribe to msg\\n\"calculate PI\"\nactivate pi\nmq x-[#0000FF]> msg: subscribe to msg\\n\"qualify message\"\nactivate mq\nmq -> mq: unmarshall message\nmq -> mq: associate contact IDs with recipients\nmq -> mq: associate message to a discussion\nmq -> mq: calculate importance level\nmq -> mq: etc…\nmq -> mq: update lookup tables\nmq ->o db: store lookup info in lookup tables\nmq -> mq: marshall message to protobuf\nmq ->> msg: send msg «qualification done»\ndeactivate mq\nmain x-[#0000FF]> msg: subscribe to msg\\n\"qualification done\"\n\npi -> pi: unmarshall message\npi -> pi: PI calculation\npi -> pi: marshall message to protobuf\npi ->> msg: send msg «PI calculation done»\ndeactivate pi\nmain x-[#0000FF]> msg: subscribe to msg\\n\"PI done\"\nmain -> main: marshall \"message\" into db model\nautonumber resume\nmain ->o db: update \"message\" model\nautonumber stop\n\nmain -> main: build ALICE\nmain -> main: marshall ALICE into index model\nautonumber resume\nmain -> msg: send msg \"build ALICE\"\nautonumber stop\nalice x-[#0000FF]> msg: subscribe to msg\\n\"build ALICE\"\nactivate alice\nalice -> alice: unmarshall message\ncreate entity \"lucene engine\"\nalice -> \"lucene engine\": \"scan body\"\ncreate entity \"qwant engine\"\nalice -> \"qwant engine\": \"build semantics\"\n\"lucene engine\" -> alice: \"here is the lexicon\"\n\"qwant engine\" -> alice: \"here are the keywords\"\nalice -> alice: compute ALICE\nalice -> alice: marshall ALICE\nalice -> msg: send msg \"index ALICE\"\ndeactivate alice\nmain x-[#0000FF]> msg: subscribe to msg\\n\"index ALICE\"\nautonumber resume\nmain ->o index: send ALICE to index\nautonumber stop\n\ndeactivate main\n@enduml\n"
  },
  {
    "path": "doc/specifications/email-protocol/assets/email-delivery-outbound.puml",
    "content": ""
  },
  {
    "path": "doc/specifications/email-protocol/assets/email-inbound-delivery-poc.puml",
    "content": "@startuml\nskinparam activity {\n  BackgroundColor<< object >> Olive\n  BackgroundColor<< message >> Green\n  BackgroundColor<< msg_core >> #5555FF\n}\n(*) --> ===B1===\npartition lmtpd {\n===B1=== --> \"agent.process(mailfrom, rcpttos, data)\" as ap\n}\npartition agent {\nap -> self.resolve_users(rcpts)\nIf \"if user\" then\n-->[yes] ===repeat===\nnote left: repeat for each user\n\n\n===repeat=== --> \"RawMessage.create(user, buf)\"\n--> \"self.process_user_mail(user, raw_msg_id)\"\nnote left: logic here for user rules, etc\n\"self.process_user_mail(user, raw_msg_id)\" --> \"deliver.process(user, raw_msg.id)\"\n}\n\n\"RawMessage.create(user, buf)\" --> \"db store\"\n\npartition main.message.delivery.py {\n\"deliver.process(user, raw_msg.id)\" --> \"RawMessage.get(user, raw_msg_id)\"\n\"db store\" --> \"RawMessage.get(user, raw_msg_id)\"\n\"RawMessage.get(user, raw_msg_id)\" -right-> RawMessage <<object>>\nmessage -[#Green]-> msg.raw_msg_id\nRawMessage --> msg.raw_msg_id\nmsg.raw_msg_id -[#Green]-> message_step_2 <<message>>\nmail --> \"self._get_recipients(user, mail)\"\nmessage_step_2 -[#Green]-> \"self._get_recipients(user, mail)\"\n\"self._get_recipients(user, mail)\" -[#Green]-> message_step_3 <<message>>\nmessage_step_3 --> \"[x in message.recipients]\"\n\"[x in message.recipients]\" --> addresses\nmessage_step_3 -[#Green]-> \"self.get_tags(user, mail)\"\n\"self.get_tags(user, mail)\" -[#Green]-> message_step_4 <<message>>\nmail --> self.get_tags(user, mail)\nlookup_sequence -[#333333]-> \"self.lookup(user, lookup_sequence)\"\n\"self.lookup(user, lookup_sequence)\" -[#333333]-> lookup\nmsg_core -[#0000FF]-> \"ThreadMessageLookup.create(user, **params)\"\nmsg_core -[#0000FF]-> \"self.__init_lookup(user, lookup_sequence, msg)\"\nmessage_step_4 -[#Green]-> \"create()\"\nlookup -[#333333]-> \"create()\"\nthread_id --> \"create()\"\n}\n\"self.__init_lookup(user, lookup_sequence, msg)\" --> (*)\n\"ThreadMessageLookup.create(user, **params)\" --> (*)\n\npartition main.message.parameters {\nmail --> \"mail.to_parameter()\"\nnote bottom: output a schematics\n\"mail.to_parameter()\" --> message <<message>>\n}\n\npartition main.message.format.mail.py {\nRawMessage --> \"MailMessage(RawMessage.data)\"\nnote bottom: output a MailMessage object\n\"MailMessage(RawMessage.data)\" --> mail <<object>>\nmail --> \"mail.lookup_sequence()\"\n\"mail.lookup_sequence()\" -[#333333]-> lookup_sequence\n}\n\npartition main.message.core.thread.py {\nlookup -[#333333]->[yes] \"Thread.get(user, lookup.thread_id)\"\n\"Thread.get(user, lookup.thread_id)\" --> thread\nlookup-[#333333]->[yes] \"ThreadMessageLookup.create(user, **params)\"\nmessage --> \"Thread.create_from_message(user, message)\"\nlookup -[#333333]->[no] \"Thread.create_from_message(user, message)\"\n\"Thread.create_from_message(user, message)\" --> thread\nlookup -[#333333]->[no] \"self.__init_lookup(user, lookup_sequence, msg)\"\nthread --> thread_id\n}\n\npartition main.message.core.message.py {\n\n\n\"create()\" -[#0000FF]-> \"message.validate()\"\n\"message.validate()\" -[#0000FF]-> \"create_nested()\"\n\"create_nested()\" -[#0000FF]-> \"Message_model.create()\"\n\"Message_model.create()\" -[#0000FF]-> \"db store\"\n\"Message_model.create()\"  -[#0000FF]-> msg_core<<msg_core>>\n\"Message_model.create()\" --> \"create_index()\"\n}\n \"create_index()\" --> \"index store\"\n@enduml\n\n@startuml\nhide footbox\nbox \"caliopen_smtp\"\nparticipant agent.py\nendbox\ndatabase db\ndatabase index\nbox \"main.message\"\nparticipant deliver.py\nparticipant format.mail.py\nparticipant parameters.py\nparticipant core.thread.py\nparticipant core.message.py\nendbox\n\n\n[-> agent.py: smtp email\nactivate agent.py\nagent.py -> agent.py: resolve_users\n== Repeat for each user==\nagent.py ->o db: store raw message\ndb --/ agent.py: raw_msg_id\nagent.py ->> agent.py: process user rules\\n(empty for now)\nagent.py ->> deliver.py: process(user, raw_msg_id)\ndeactivate agent.py\nactivate deliver.py\ndeliver.py ->> db: get raw message\ndb ->o deliver.py: RawMessage object\ndeliver.py ->> format.mail.py: MailMessage(RawMessage.data)\nformat.mail.py ->o deliver.py: MailMessage object\ndeliver.py ->> parameters.py: to_parameter()\nparameters.py ->o deliver.py: ParamMessage object\ndeliver.py -> deliver.py: process message\nactivate deliver.py\ndeliver.py -> deliver.py: get raw_msg_id\ndeliver.py -> deliver.py: get recipients\ndeliver.py -> deliver.py: get tags\ndeactivate deliver.py\ndeliver.py -> format.mail.py: mail.lookup_sequence()\nactivate format.mail.py\nformat.mail.py -> deliver.py: lookup\ndeactivate format.mail.py\ndeliver.py -> core.thread.py: get/create thread()\nactivate core.thread.py\ncore.thread.py ->o deliver.py: thread_id\ndeactivate core.thread.py\ndeliver.py -> core.message.py: Message.create()\nactivate core.message.py\ncore.message.py -> core.message.py: validate()\ncore.message.py -> core.message.py: create_nested()\ncore.message.py ->o db: create()\ncore.message.py ->o index: create.index()\ncore.message.py -> deliver.py: core_message\ndeactivate core.message.py\ndeliver.py -> core.thread.py: thread lookup operations\n[<- deliver.py: return core_message obj\ndeactivate deliver.py\n@enduml"
  },
  {
    "path": "doc/specifications/email-protocol/assets/imap-outbound-process.puml",
    "content": "@startuml\nhide footbox\ntitle IMAP outbound : sending email through remote user identity account\nbox \"main API\"\nparticipant \"go.main\"\nendbox\nbox \"Imap Worker\"\nparticipant \"go.imap\"\nendbox\nbox \"lmtpd\"\nparticipant \"go.smtp\"\nparticipant \"go.emails\"\nendbox\nbox \"storage\"\ndatabase \"cassandra/vault\" as store\nendbox\nboundary \"remote IMAP\" as imap\nboundary \"remote MTA\" as mta\n[-> go.main : POST `send draft`\nactivate go.main\nautonumber\n\n@enduml"
  },
  {
    "path": "doc/specifications/email-protocol/assets/smtp-delivery-inbound-closeup.puml",
    "content": "@startuml\nhide footbox\ntitle Inbound SMTP closeup\nbox \"SMTPd\"\nparticipant \"go.smtpd\" as smtpd\nendbox\nentity S3\ndatabase cassandra\ndatabase elasticsearch\nbox \"message platform\"\nparticipant \"NATS server\" as nats\nendbox\nbox \"interface NATS\"\ncontrol \"py.nats\" as pynats\nendbox\nbox \"Caliopen Main\"\ncontrol \"py.main\" as pymain\nendbox\nbox\n[-> smtpd: smtp email\nactivate smtpd\nsmtpd --\\ cassandra: recipients lookup\ncassandra --/ smtpd\nsmtpd ->o cassandra: store raw email once\nsmtpd ->o S3: [(if needed) store large email]\ncassandra --// smtpd: raw email ID\n== Repetition ==\nsmtpd -[#0000FF]> nats: send order \"process_raw\"\\n to \"inboundSMTP\" topic\\nwith msg_id & rcp_id\npynats x[#0000FF]-> nats: subscribe to \"process_raw\"\\n on \"inboundSMTP\" topic\ngroup broker operations\npynats -> pymain: UserMessageDelivery()\nactivate pymain\ncassandra --> pymain: get raw email\nactivate pymain\nS3 --> pymain:[(if needed) get large raw email]\nnote over pymain: delivery.py\ncassandra --> pymain: get user\npymain -> pymain: unmarshall into a\\n\"MailMessage\" intermediary representation\npymain -> pymain: \"process_inbound()\ndeactivate pymain\ngroup qualifier operations\nnote over pymain: qualifier.py\npymain -> pymain: unmarshall into a\\n\"NewMessage\" intermediary representation\nactivate pymain\npymain -> pymain: basic validation/conformation\npymain -> pymain: message qualification/enrichment\nend\npymain -> pymain: build a Caliopen Message\npymain ->o cassandra: store message object\\n(type email)\npymain ->o elasticsearch: index message object\\n(type email)\ndeactivate pymain\npymain -> pynats: return Message or Exception\ndeactivate pymain\nend\npynats -[#0000FF]-> nats: reply \"OK\"\nnats -[#0000FF]-> smtpd: reply \"OK\"\n[<- smtpd: \"250 OK\"\ndeactivate smtpd\n@enduml"
  },
  {
    "path": "doc/specifications/email-protocol/imap-binaries.md",
    "content": "# Caliopen IMAP binaries\n\n2 programs to handle IMAP related operations :\n- **imapworker** : daemon that listen to NATS orders to fetch/sync remote accounts.  \nMust be launched with the rest of Caliopen's stack.\n- **imapctl** : cli for on-demand IMAP operations and/or remote identities operations.\n\n## imapworker\n\nA daemon to launch before requesting any IMAP operations.  \nIt needs a config file. A default one is at `src/backend/configs/imapworker.yaml`\n\n### dependencies\n\n_imapworker_'s dependencies will be installed with Caliopen's stack. If you checked-out `feature/worker/imap-fetcher` into your current stack, run `govendor sync` to ensure all required dependencies.\n\n### launching\n\n```shell\ncd src/backend/protocols/go.imap/cmd/imapworker\ngo run main.go start\n# if binary has been build :\nimapworker start\n```\n\n### building\n\n```shell\ngo build github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap/cmd/imapworker\n```\n\n### using\n\nStart _imapworker_ once with the rest of the stack and keep it running as a daemon.\nIf you need help : `go run main.go` or `imapworker` without any command will prompt available commands and flags.\n\n## imapctl\n\n_imapctl_ is a CLI to do on-demand operations.  \nFor now it has three available commands :\n- `fullfetch` : to fetch all mails from a remote IMAP mailbox into user account, without any synchronization and without keeping last state sync.\n- `addremote` : to create a new remote identity in db\n- `syncremote` : to sync emails for a remote identitiy.  \n\n**NB** : `fullfetch` and `syncremote` commands need `imapworker` to run in background to work properly.  \n_imapctl_ needs the same config file as _imapworker_'s one.\n\n### dependencies\n\n_imapctl_'s dependencies will be installed with Caliopen's stack. If you checked-out `feature/worker/imap-fetcher` into your current stack, run `govendor sync` to ensure all required dependencies.\n\n### launching\n\n```shell\ncd src/backend/protocols/go.imap/cmd/imapctl\ngo run main.go [command] [flags]\n# if binary has been build :\nimapctl [command] [flags]\n```\n\n### building\n\n```shell\ngo build github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap/cmd/imapctl\n```\n\n### using\n\nInvoke _imapctl_ without any command or flags to print help message.\n\n### examples :\n\n- To blindly fetch all mails from a remote mailbox into user account :\n\n  ```shell\n  imapctl fullfetch --login 'account@provider.tld' --pass 'your_imap_password' --server 'remote.server.address:port' --mailbox 'box_name' --userid xxxxx-local-user-id-xxxxx\n  ```\n\n  _--mailbox_ flag is optionnal, default is `INBOX`\n\n\n- To be able to sync a remote mailbox (ie fetch only new mails after the first synchronization), you need first to create and store a remote identity :\n\n  ```shell\n  imapctl addremote --login 'account@provider.tld' --pass 'your_imap_password' --server 'remote.server.address:port' --userid xxxxx-local-user-id-xxxxx\n  ```\n\n  if you omit _--password_, you'll need to give it to _imapctl syncremote_ at each invocation.\n\n  For now, default mailbox is `INBOX`\n\n  Add as many remote identities as needed.\n\n- To synchronize a remote identity account, ie to fetch all emails first time then only new ones :\n\n  ```shell\n  imapctl syncremote --userid xxxxx-local-user-id-xxxxx --identifier 'account@provider.tld'\n  ```\n\n  _impactl syncremote_ will make sync operations and quit.  \n\n  Relaunch it each time you want to sync or add the command to your cron.\n\n## idpoller\n\nA daemon that loads remote identities from cassandra and schedule jobs accordingly.\nIt needs a config file. A default one is at `src/backend/configs/idpoller.yaml`\n\n### how it works :\n\n_idpoller_ periodically finds 'active' remote identities in cassandra and schedule jobs accordingly in an internal cron table. \"Jobs\" consist of sending messages on nats queue to trigger _imapworker_ actions.\n\nHow often _idpoller_ scans remote identity table in cassandra is define within configuration file in 'scan_interval' setting. Default is 15 minutes.\n\n_idpoller_ schedules a recurring job for each 'active' remote identity according to the `infos.pollinterval` value in remote_identity table. Default to 15 minutes if the value is missing.\n\n**NB** : _idpoller_ schedules the sending of messages on nats queue. If no subscriber listen to the queue, no action will be triggered.\n\n### dependencies\n\n_idpoller_'s dependencies will be installed with Caliopen's stack. If you checked-out `feature/worker/imap-poller` into your current stack, run `govendor sync` to ensure all required dependencies.\n\n### launching\n\n```shell\ncd src/backend/workers/go.remoteIDs/cmd/idpoller\ngo run main.go start\n# if binary has been build :\nidpoller start\n```\n\n### building\n\n```shell\ngo build github.com/CaliOpen/Caliopen/src/backend/workers/go.remoteIDs/cmd/idpoller\n```\n\n### using\n\nStart _idpoller_ once with the rest of the stack and keep it running as a daemon.\nIf you need help : `go run main.go` or `idpoller` without any command will prompt available commands and flags."
  },
  {
    "path": "doc/specifications/email-protocol/index.md",
    "content": "# Email protocol\n\ncf. [assets directory](./assets/) for uml pdf etc.\n"
  },
  {
    "path": "doc/specifications/identities/Remote-identities-creation-diagram.puml",
    "content": "@startuml\nactor \"Browser window\" as user\nparticipant \"webapp\" as client\ndatabase \"Backend\" as backend\nuser --> client: click\\n« add remote account »\nclient -> backend: \"\"GET /providers\"\"\nbackend -> client: providers list with additional infos\\n(url, data to pre-fill form, etc.)\nhnote right user: UI : « choose a provider »\ngroup Gmail\nuser --> client: click « signin with Gmail »\nclient -> backend: \"\"GET /api/v2/providers/gmail\"\"\nbackend -> client: \"\"200 OK\"\" with Oauth params in payload\nclient -> user: [popup] redirect to gmail auth page\\nwith provided auth params\ncreate boundary Gmail\nactivate user\nuser -> Gmail: (gmail domain) login & authorize Caliopen\nGmail --> Gmail: authorize\nGmail -> user: Gmail redirect to our redirect_uri with authorization code\nuser -> backend: \"\"GET /api/v2/providers/gmail/callback\"\"\nactivate backend\nbackend -> Gmail: validate token info\nGmail -> backend: OK with authorization code\nbackend -> user: \"\"200 OK\"\" Thanks for authorizing Caliopen !\\nYour request is being processed.\\nYou can safely close this popup.\ndeactivate user\nbackend -> Gmail: \"\"GET\"\" with authorization secrets\nGmail -> backend: token + secret for user\ndeactivate backend\nbackend --> backend: create remote identity with token credentials\nclient -> backend: \"\"GET /identities/remotes\"\"\nbackend -> client: []remote identities with newly created included if ok\nhnote right user: UI : « Success ! »\nclient --> user: display identity created\nend\ngroup Twitter\nuser --> client: click « signin with Twitter »\nclient -> backend: \"\"GET /api/v2/providers/twitter\"\"\ncreate boundary Twitter\nbackend -> Twitter: request auth parameters\nTwitter -> backend: auth params\nbackend -> client: \"\"200 OK\"\" with Oauth params in payload\nclient -> user: [popup] redirect to Twitter auth page\\nwith provided auth params\nactivate user\nuser -> Twitter: (Twitter domain) login & authorize Caliopen\nTwitter --> Twitter: authorize\nTwitter -> user: Twitter redirect to our redirect_uri with authorization code\nuser -> backend: \"\"GET /api/v2/providers/twitter/callback\"\"\nactivate backend\nbackend -> Twitter: validate token info\nTwitter -> backend: OK with authorization code\nbackend -> user: \"\"200 OK\"\" Thanks for authorizing Caliopen !\\nYour request is being processed.\\nYou can safely close this popup.\ndeactivate user\nbackend -> Twitter: \"\"GET\"\" with authorization secrets\nTwitter -> backend: token + secret for user\ndeactivate backend\nbackend --> backend: create remote identity with token credentials\nclient -> backend: \"\"GET /identities/remotes\"\"\nbackend -> client: []remote identities with newly created included if ok\nhnote right user: UI : « Success ! »\nclient --> user: display identity created\nend\n\n== backend has credentials and account settings ==\nhnote left client: UI : « your remote account\\nis created.\\nTrying to connect… »\ncreate participant provider\n... async  ...\ngroup account connexion testing\nbackend -> provider: impersonate user to endpoint API\nprovider -> backend: \"\"OK\"\"\nbackend -> provider: \"\"bye\"\"\nbackend o-->o client: notify success\nend\nhnote left client: UI : « your remote account\\nis connected ! »\n@enduml"
  },
  {
    "path": "doc/specifications/identities/index.md",
    "content": "## Identities in Caliopen\n\n### Intro\n\nBefore summer 2018, Caliopen stack had 2 kinds of _identity_ objects to store data about user's accounts : _RemoteIdentity_ and _LocalIdentity_. While we made use of _LocalIdentity_ to store data about user's local Caliopen email account, we introduced _RemoteIdentity_ to store data about external accounts belonging to him. This design led us to redundant code and models. Decision was made to merge _LocalIdentity_ and _RemoteIdentity_ into a unique object, an **UserIdentity**.\n\n###Definitions and backend architecture\n\n####Definition\n\nAn _UserIdentity_ is an account belonging to an user at a communication service provider. It can be _local_ — for example an email address hosted on local Caliopen instance —, or _remote_ — for example an external mastodon account.\n\nAt creation, each Caliopen user has at least one _UserIdentity_, representing its Caliopen email box. This _UserIdentity_ is of `type=local` and has `protocol=smtp`.\n\n#### Model\n\n```yaml\ntype: object\nproperties:\n  credentials:    # set of key/value strings\n    type: object\n    additionalProperties:\n      type: string\n  display_name:\n    type: string\n  identifier:\n    type: string\n  identity_id:\n    type: string\n  infos:          # set of key/value strings\n    type: object\n    additionalProperties:\n      type: string\n  last_check:\n    type: string\n    format: date-time\n  protocol:\n    type: string\n    enum:\n    - imap\n    - smtp\n  status:\n    type: string\n    enum:\n    - active\n    - inactive\n    - deleted\n  type:\n    type: string\n    enum:\n    - local\n    - remote\n  user_id:\n    type: string\nrequired:\n- identifier\n- identity_id\n- infos\n- protocol\n- type\n- status\n- user_id\n```\n\nHas shown above, _UserIdentity_ has a set of mandatory properties. Among them, there are **2 mandatory immutable**  properties :\n\n- `identifier` : an email address, XMPP account, twitter account, etc. For example `me@caliopen.org`\n- `protocol` : smtp, sms, twitter, etc.\n\nThe pair `identifier + protocol` must be unique within an user account. It can't be changed, ie data must be copied into a new _UserIdentity_ if one wants to modify this pair.\n\n### ReST API\n\n#### Identities APIs\n\nIdentities APIs keep legacy routes to manage either _local_ or _remote_ identies on 2 different path : `…/identities/locals…` and `…/identities/remotes…`.\n\nRoutes :\n\n- `GET …/api/v2/identities/locals` to get an array of _UserIdentity_ of type `local`\n- `GET …/api/v2/identities/locals/{identity_id}` to get one _UserIdentity_ of type `local`\n- `GET …/api/v2/remotes` to get an array of _UserIdentity_ of type `remote`\n- `POST …/api/v2/remotes` to create a new _UserIdentity_ of type `remote`\n- `GET PATCH DELETE …/api/v2/remotes/{identity_id}` to operate on one _UserIdentity_ of type `remote`\n\nWhen creating a new remote identity (on route `…/api/v2/identities/remotes`) client MUST provide properties `protocol` and `identifier`. These properties are then immutable.\n\nNB : UserIdentity's `type` property will be enforced by backend on `POST` and `PATCH` verbs to match route (ie `type=remote` on routes `…/identities/remotes` and `type=local` on `…/identities/locals`), even if client provides a wrong type.\n\n#### Draft API\n\nRoutes :\n\n- `POST …/api/v1/messages`\n- `PATCH …/api/v1/messages/{message_id}`\n\nWhen creating or editing a draft message, client MUST provide one _UserIdentity_ on behalf of which the message will be sent. It can be a _local_ or _remote_ identity. (For now, only one identity per message is supported)\n\nClient links a draft to an _UserIdentity_ by embedding `identity_id` into draft's `user_identities` property : \n\n```json\nPOST …/api/vi/messages\n{\n\t\"subject\": \"My subject\",\n\t\"user_identities\": [\"ae8c45d2-c085-4fa9-bbd9-5ec83b4c8469\"],\n\t\"participants\": [\n\t\t{\n\t\t\t\"address\": \"dev@caliopen.local\",\n\t\t\t\"label\": \"Dev Idoire\",\n\t\t\t\"protocol\": \"email\",\n\t\t\t\"type\": \"To\"\n\t\t}\n\t],\n\t\"body\": \"my message\"\n}\n```\n\nAs shown above, model for _Message_ object (and consequently for draft) has slightly changed. Property `identities` has been renamed to `user_identities`, and is now an array of strings representing a set of `user_identity_id`. Here is an example of a simple message sent by an user : \n\n```json\n{\n\t\"body\": \"My message\",\n\t\"body_is_plain\": true,\n\t\"excerpt\": \"l…\",\n\t\"date\": \"2018-07-20T15:56:06.062Z\",\n\t\"date_insert\": \"2018-07-20T15:53:07.648Z\",\n\t\"date_sort\": \"2018-07-20T15:56:06.062Z\",\n\t\"discussion_id\": \"51d367b2-240a-4db0-8d4d-cedf592b1306\",\n\t\"external_references\": {\n\t\t\"message_id\": \"wkFJWsiC5vHTJcdxEIMvJD0zBL412b_LQ091DmNZdZw=@caliopen.org\"\n\t},\n\t\"user_identities\": [\n\t\t\"ae8c45d2-c085-4fa9-bbd9-5ec83b4c8469\"\n\t],\n\t\"importance_level\": 0,\n\t\"is_answered\": false,\n\t\"is_draft\": false,\n\t\"is_unread\": false,\n\t\"is_received\": false,\n\t\"message_id\": \"de7a5f69-905c-4158-8bdc-9a1e2ac1f197\",\n\t\"participants\": [\n\t\t{\n\t\t\t\"address\": \"dev@caliopen.local\",\n\t\t\t\"label\": \"Dev Idoire\",\n\t\t\t\"protocol\": \"email\",\n\t\t\t\"type\": \"To\"\n\t\t},\n\t\t{\n\t\t\t\"address\": \"dev@caliopen.local\",\n\t\t\t\"contact_ids\": [\n\t\t\t\t\"fb94efe5-1ff6-43cf-9823-5ec3887bc3fb\"\n\t\t\t],\n\t\t\t\"label\": \"Dev Idoire\",\n\t\t\t\"protocol\": \"email\",\n\t\t\t\"type\": \"From\"\n\t\t}\n\t],\n\t\"raw_msg_id\": \"cb61ca60-1931-4ae6-b036-67baf50aea76\",\n\t\"subject\": \"My subject\",\n\t\"type\": \"email\",\n\t\"user_id\": \"6cc36d88-6163-4465-805a-cacf58f455e4\"\n}\n```\n\n#### User model\n\n_User_ object has no longer a `local_identities` property embedded. Call to `…/identites/locals`  to fetch relevant data.\n\n"
  },
  {
    "path": "doc/specifications/identities/remote-identities.md",
    "content": "### Remote identities specifications\n\n#### Definition :\n\nA _remote identity_ is an object that store data about an account on an external server (server address, credentials, etc). A _remote identity_ could be use to receive/send messages through a distant server using any supported protocol (smtp/imap, xmpp, etc.)\n\n#### Model :\n\n```yaml\ntype: object\nproperties:\n  credentials:    # set of key/value strings\n    type: object\n    additionalProperties:\n      type: string\n  display_name:\n    type: string\n  infos:          # set of key/value strings\n    type: object\n    additionalProperties:\n      type: string\n  last_check:\n    type: string\n    format: date-time\n  remote_id:\n    type: string\n  status:\n    type: string\n    enum:\n    - active\n    - inactive\n    - deleted\n  type:\n    type: string\n    enum:\n    - imap\nrequired:\n- infos\n- type\n```\n\n`credentials` and `infos` are key/value map strings by design, to allow a wide range of data to be embedded into a _remote identity_, depending on type and protocol.\n\nRemote identities examples : \n\n- An **imap** remote identity stores credentials and server addresses for 2 remote services : an IMAP server to retrieve mail from and to synchronize messages' states, and a SMTP server to send emails through :\n\n```yaml\ncredentials:\n  inusername: \"my_username@server.tld\"            # username to login ingress server\n  inpassword: \"my_password\"                       # password for ingress server\n  outusername: \"my_username@server.tld\"           # username to login egress server\n  outpassword: \"my_password\"                      # password for egress server\ndisplay_name: My Imap account\ninfos:\n  lastseenuid: '3996'                             # property managed by imap worker, do not edit\n  lastsync: '2018-06-21T13:56:06+02:00'\t\t\t # property managed by imap worker, do not edit\n  pollinterval: '10'                              # poll interval in minutes\n  inserver: box.mailden.net:993                   # server address and port to fetch mail from\n  outserver: post.mailden.net:587                 # server address and port to send mail through\n  uidvalidity: '623613176'                        # property managed by imap worker, do not edit\nlast_check: '2018-06-21T11:56:06.719Z'            # property managed by imap worker, do not edit\nremote_id: 19421c2c-9d74-4d32-adcc-a3ede3ae7eea\nstatus: active\ntype: imap\n```\n\n"
  },
  {
    "path": "doc/specifications/message/assets/message-create-save-send.puml",
    "content": "@startuml\ntitle Message creation & sending\nbox \"User frontend\"\nactor \"authenticated user\" as user\nparticipant \"new message UI\" as compose\nendbox\nbox \"python application\"\ncontrol py.api\nparticipant py.main\nendbox\nbox \"go application\"\ncontrol go.api\nparticipant go.main\nendbox\ndatabase db\ndatabase index\nbox \"NATS\"\nentity \"outboundSMTP channel\" as out\nendbox\nbox \"Brokers\"\nparticipant \"Email broker\" as eb\nendbox\ngroup could be offline\nuser o-> compose : hit 'compose' or 'reply'\nactivate compose\nend\nhnote over compose : online\ncompose -[#00FF00]> py.api : [POST api/v1/messages]\npy.api -> py.main: \"create_draft\"\nactivate py.main\npy.main -> py.main : draft validation\npy.main -> db : store message (draft status)\npy.main -> index : index message (draft status)\npy.main -> py.api : response\ndeactivate py.main\npy.api -[#00FF00]-> compose : message url\nloop every 10 sec.\ncompose -[#00FF00]> py.api: auto save draft\\n[PATCH /messages/{message_id}]\npy.api -> py.main : \"patch_draft\"\nactivate py.main\npy.main x-> db : get_db\npy.main -> py.main : patch validation\npy.main -> db : store draft\npy.main -> index : index draft\npy.main -> py.api : response\ndeactivate py.main\npy.api -[#00FF00]-> compose : error or 204 OK\nend\ndeactivate compose\nuser o-> compose : hit 'save'\nactivate compose\ncompose -[#00FF00]> py.api: save draft\\n[PATCH /messages/{message_id}]\npy.api -> py.main : \"patch_draft\"\nactivate py.main\npy.main x-> db : get_db\npy.main -> py.main : patch validation\npy.main -> db : store draft\npy.main -> index : index draft\npy.main -> py.api : response\ndeactivate py.main\npy.api -[#00FF00]-> compose : error or 204 OK\ndeactivate compose\nalt edit draft\nuser o-> compose : hit 'edit draft'\nactivate compose\ncompose -[#00FF00]> py.api : edit draft\\n[GET /messages/{message_id}]\npy.api -> py.main : get_message\npy.main x-> db : get_db\npy.main -> py.api : message\npy.api -[#00FF00]> compose : message\nloop every 5 sec.\ncompose -[#00FF00]> py.api : auto save draft\\n[PATCH /messages/{message_id}]\npy.api -> py.main : \"patch_draft\"\nactivate py.main\npy.main x-> db : get_db\npy.main -> py.main : patch validation\npy.main -> db : store draft\npy.main -> index : index draft\npy.main -> py.api : response\ndeactivate py.main\npy.api -[#00FF00]-> compose : error or 204 OK\nend\ndeactivate compose\nuser o-> compose : hit 'save'\nactivate compose\ncompose -[#00FF00]> py.api : save draft\\n[PATCH /messages/{message_id}]\npy.api -> py.main : \"patch_draft\"\nactivate py.main\npy.main x-> db : get_db\npy.main -> py.main : patch validation\npy.main -> db : store draft\npy.main -> index : index draft\npy.main -> py.api : response\ndeactivate py.main\npy.api -[#00FF00]-> compose : error or 204 OK\ndeactivate compose\nend\nalt send draft\nuser o-> compose : hit 'send message'\nactivate compose\ncompose -[#00FF00]> py.api : save draft\\n[PATCH /messages/{message_id}]\npy.api -> py.main : \"patch_draft\"\nactivate py.main\npy.main x-> db : get_db\npy.main -> py.main : patch validation\npy.main -> db : store draft\npy.main -> index : index draft\npy.main -> py.api : response\ndeactivate py.main\npy.api -[#00FF00]-> compose : error or 204 OK\ncompose -[#00FF00]> go.api : send message\\n[POST /messages/{message_id}/actions]\ngo.api -> go.main : \"SendDraft\"\nactivate go.main\ngo.main -[#0000FF]> out : \"deliver\" order with message_id\neb x-[#0000FF]> out : subscribe \"deliver\"\nactivate eb\nnote right: email delivery process\neb x-> db : get message\neb -> eb : marshal email, etc.\neb -> mta : send message\nmta -> eb : OK\neb -> db : store raw_message\neb -> db : update message store (sent)\neb -> index : update message index (sent)\neb -[#0000FF]> out: \"deliver\" response\ndeactivate eb\ngo.main x-[#0000FF]> out : async \"deliver\" response\ngo.main x-> db : get_message\ngo.main -> go.api : send back updated message\ndeactivate go.main\ngo.api -[#00FF00]> compose : updated message\ndeactivate compose\nend\ndeactivate compose\nalt delete draft\nuser o-> compose: hit 'delete draft'\nactivate compose\ncompose -[#00FF00]> py.api : delete draft\\n[DELETE /messages/{message_id}]\npy.api -> py.main: \"delete_message\"\nactivate py.main\npy.main -> py.main: delete procedure\npy.main -> db: write delete state\npy.main -> index: write delete state\npy.main -> py.api: ack or error\ndeactivate py.main\npy.api -[#00FF00]-> compose : error or 204 OK\ndeactivate compose\nend\n@enduml"
  },
  {
    "path": "doc/specifications/message/assets/message-create-save-send_nats_async.puml",
    "content": "@startuml\nhide footbox\ntitle Message creation & sending\nbox \"User frontend\"\nactor \"authenticated user\" as user\nparticipant \"new message UI\" as compose\nendbox\nbox \"ReST interface\"\ncontrol api\nendbox\nbox \"NATS\"\nentity \"draft channel\" as draft\nentity \"message channel\" as message\nentity \"outboundSMTP channel\" as out\nendbox\nbox \"Caliopen main\"\ncontrol \"py/go.main\" as main\nendbox\ndatabase db\ndatabase index\nbox \"Brokers\"\nparticipant \"Email broker\" as eb\nendbox\ngroup could be offline\nuser o-> compose : hit 'compose message'\nactivate compose\nend\nhnote over compose : online\ncompose -> api : [POST api/v1/messages]\napi -> draft: request\\n\"create\"\nmain x-[#0000FF]> draft: subscribe \"create\"\nactivate main\nmain -> db: store message (draft status)\nmain -> draft: \"create\" response\ndeactivate main\napi x-> draft: response\napi --> compose : creation ack\nactivate compose\nloop every 10 sec.\ncompose ->> api: auto save draft\\n[PATCH /messages/{message_id}]\napi -> draft: send \"patch\"\nmain x-[#0000FF]> draft: subscribe \"patch\"\nactivate main\nmain -> db: update message (draft status)\ndeactivate main\nend\ndeactivate compose\nuser o-> compose : hit 'save' or 'close'\nactivate compose\ncompose -> api : save draft\\n[POST /messages/{message_id}/action]\napi -> draft: request \"save\"\nmain x-[#0000FF]> draft: subscribe \"save\"\nactivate main\nmain -> db: store message (draft status)\nmain -> draft: \"save\" response\ndeactivate main\napi x-> draft: response\napi --> compose : save ack\ndeactivate compose\nalt\nuser o-> compose: hit 'edit draft'\nactivate compose\ncompose -> api : edit draft\\n[GET /messages/{message_id}]\napi -> draft: request \"get\"\nmain x-[#0000FF]> draft: subscribe \"get\"\nactivate main\nmain -> db: get message (draft status)\ndb -> main: raw_message\nmain -> draft: \"get\" response\ndeactivate main\napi x-> draft: response\napi -> compose : raw_message\nend\nalt\nuser o-> compose: hit 'delete draft'\nactivate compose\ncompose -> api : delete draft\\n[DELETE /messages/{message_id}]\napi -> draft: request \"delete\"\nmain x-[#0000FF]> draft: subscribe \"delete\"\nactivate main\nmain -> db: delete message\nmain -> draft: \"delete\" response\ndeactivate main\napi x-> draft: response\napi --> compose : delete ack\nend\ndeactivate compose\nalt\nuser o-> compose : hit 'send message'\nactivate compose\ncompose -> api: save & send message\\n[POST /messages/{message_id}/action]\napi -> message: request \"send\"\nmain x-[#0000FF]> message: subscribe \"save\"\nactivate main\nmain -> db: store message (unsent status)\nmain -> index: index message (unsent status)\nmain -> out: request \"deliver\" (with msg_id only)\neb x-[#0000FF]> out: subscribe \"deliver\"\nactivate eb\nnote right: email delivery process\neb -> db : get raw_message\ndb -> eb : raw_message\neb -> eb : marshal email, etc.\neb ->] : send message\neb -> db : update status (sent)\neb -> index : update status (sent)\neb -> out: \"deliver\" response\ndeactivate eb\nmain x-> out: response\nmain -> message: \"save\" response\ndeactivate main\napi x-> message: response\napi --> compose : feedback\ndeactivate compose\nend\ndeactivate compose\n@enduml"
  },
  {
    "path": "doc/specifications/message/assets/message_discussion_association.puml",
    "content": "@startuml\ntitle Message discussion association\n\nbox \"User frontend\"\n  actor \"authenticated user\" as user\n  participant \"New message UI\" as compose\n  participant \"Discussion UI\" as reply\nendbox\nbox \"ReST interface\"\n  control api\nendbox\n\nnote over api\n  All declared properties used in requests are mandatory\n  except when \"[Opt]\" keyword is used\nend note\n\ngroup Compose\n  user o-> compose: write a new draft with \\nor without participants\n  activate compose\n  compose -> compose: throttle save\n  activate compose\n  compose -> api: [POST: /api/vx/messages]\\n{ message_id: String,\\n  participants: [Opt] Array … }\n  activate api\n  api -> compose: ack creation [status 204]\n  deactivate api\n  compose -> api: [GET: /api/vx/messages/{message_id}]\n  activate api\n  api -> compose: response [status 200]\\n{ message_id: String, …}\n  deactivate api\n  deactivate compose\n  deactivate compose\n\n  alt loop\n    user o-> compose: edit the new draft with \\nor without participants\n    activate compose\n    compose -> compose: throttle save\n    activate compose\n    compose -> api: [PATCH: /api/vx/messages/{message_id}]\\n{ current_state: object,\\n  participants: [Opt] Array … }\n    activate api\n    api -> compose: ack patch [status 204]\n    deactivate api\n    compose -> api: [GET: /api/vx/messages/{message_id}]\n    activate api\n    api -> compose: response [status 200]\\n{ message_id: String, …}\n    deactivate api\n    deactivate compose\n    deactivate compose\n  else\n    user o-> compose: hit «Send»\n    activate compose\n    compose -> api: [POST: /api/vx/messages/{message_id}/actions]\\n{ actions: [\"send\"] }\n    activate api\n    api -> api: validate participants\\ncalc discussion_id according participants\n    api -> compose: ack patch [status 204]\n    deactivate api\n    compose -> api: [GET: /api/vx/messages/{message_id}]\n    activate api\n    api -> compose: response [status 200]\\n{\\n  message_id: String,\\n  discussion_id: String,\\n  parent_id: [Opt] String,\\n  participants: Array, …\\n}\n    deactivate api\n    compose -> compose: render the discussion\n    compose -> user\n    deactivate compose\n  end\nend\n\ngroup Reply\n  user o-> reply: reply to a discussion\n  activate reply\n  reply -> reply: throttle save\n  activate reply\n  reply -> api: [POST: /api/vx/messages]\\n{\\n  message_id: String, \\n  parent_id: String, \\n  discussion_id: String, \\n  participants: Array, … \\n}\n  activate api\n  api -> api: validate discussion_id, participants, parent_id\n  api -> reply: ack creation [status 204]\n  deactivate api\n  reply -> api: [GET: /api/vx/messages/{message_id}]\n  activate api\n  api -> reply: response [status 200]\\n{\\n  message_id: String, \\n  parent_id: String, \\n  discussion_id: String, \\n  participants: Array, …\\n}\n  deactivate api\n  deactivate reply\n  deactivate reply\n\n  alt loop\n    user o-> reply: edit the reply\n    activate reply\n    reply -> reply: throttle save\n    activate reply\n    reply -> api: [PATCH: /api/vx/messages/{message_id}]\\n{ current_state: object, … }\n    activate api\n    api -> api: validate discussion_id, participants, parent_id\n    api -> reply: ack patch [status 204]\n    deactivate api\n    reply -> api: [GET: /api/vx/messages/{message_id}]\n    activate api\n    api -> reply: response [status 200]\\n{\\n  message_id: String, \\n  parent_id: String, \\n  discussion_id: String, \\n  participants: Array, …\\n}\n    deactivate api\n    deactivate reply\n    deactivate reply\n  else\n    user o-> reply: hit «Send»\n    activate reply\n    reply -> api: [POST: /api/vx/messages/{message_id}/actions]\\n{ actions: [\"send\"] }\n    activate api\n    api -> reply: ack patch [status 204]\n    deactivate api\n    reply -> api: [GET: /api/vx/messages/{message_id}]\n    activate api\n    api -> reply: response [status 200]\\n{\\n  message_id: String,\\n  discussion_id: String,\\n  parent_id: String,\\n  participants: Array, …\\n}\n    deactivate api\n    reply -> reply: render discussion\n    reply -> user\n    deactivate reply\n  end\nend\n\n@enduml\n"
  },
  {
    "path": "doc/specifications/message/index.md",
    "content": "# Message management\n\n## Create save send\n\n![uml](./assets/message-create-save-send-20170202.png)\n\n## Discussion association\n\nA discussion can be directly associated to a message in case of a reply so the participants will not be editable.\n\n![uml discussion association](./assets/message_discussion_association-20190327.png)\n\n## Reply or Compose a message\n\nFor a new message (compose button), we let the user choose the recipients and save it as is.\nThe user can change the recipients with no restrictions.\nThe `discussion_id` is calculated by the backend each time the draft is saved.\nThe `parent_id` is `undefined` if it is a new draft (compose).\n\nIn case of a reply, the `parent_id` is set by the client.\nAnd the backend accepts a `discussion_id` and `participants` but it will recalc the `discussion_id` and the `participants` each time the reply is saved.\n"
  },
  {
    "path": "doc/specifications/messages-drafts-discussions-routes/index-fr.md",
    "content": "# Messages and drafts\n\n## Routes pour les drafts :\n\n\n* `POST /messages` : création d'un draft\n    * ou `POST /drafts` ?\n* `PATCH /messages/{message_id}` : actualisation d'un draft\n* `DELETE /messages/{message_id}` : suppression d'un draft\n* `POST (ou PATCH?) /messages/{message_id}/send` : ordre d'envoi d'un draft (payload empty pour le moment, pourra ultérieurement contenir des instructions d'envoi par exemple : envoyer à une certaine date/heure)\n    * ou `POST (ou PATCH?) /messages/{message_id}/action`, avec un json en payload qui décrit l'action à accomplir : send pour le draft, read/unread, etc. pour les autres messages. Cette solution présente l'avantage d'avoin une seule route pour toutes les actions permises sur les messages. Exemple de payload possible : `{\"action\":\"send\"}`\n\n\n[Il faudra ultérieurement prévoir des routes pour la gestion des pièces jointes : upload, delete, attach, detach…]\n\n## Routes messages/discussions :\n\n* `GET /messages?` : une liste de messages selon les critères du filtre.  \nrègles de filtrage envisageables :\n```\n    discussion_id=\n    caliopen-pi=\n    limit=\n    offset=\n    et aussi : [unread, flag, date_before, date_after, from etc.]\n    (avec des opérateurs OR, AND, NOT, SORT ?), puis plus tard la notion de 'view', c'est-à-dire les infos qu'on veut récupérer (par exemple view=count pour ne recevoir qu'un décompte du nombre de messages qui répondent au filtre)\n```\n\n* `GET /messages/{message_id}` : récupération d'un message\n* `POST (ou PATCH?) /messages/{message_id}/action` (avec la description de l'action à accomplir dans le payload json). Ou bien une route différente pour chaque action `/send`, `/read`, `/unread`, etc. ?\n* `GET /discussions?` : retourne la liste des discussions de l'utilisateur selon les règles du filtre.  \nRègles de filtrage envisageables :\n\n```\n    caliopen-pi=\n    limit=\n    offset=\n    et aussi : [unread, date_before, date_after…]\n```\n\n## Route nécessaire pour la création d'un draft : `/identities/locals`\n\npour permettre à l'appli front de récupérer les identities du user.  \nPour l'instant, la route ne fera que retourner l'unique local identity créee par défaut.\n"
  },
  {
    "path": "doc/specifications/messaging-system/index.md",
    "content": "# Messaging system in Caliopen\nCaliopen relies on **nats** messaging system to communicate between services.  \nMost of the time, messages sent on **nats** are small json documents emitted by a service to trigger an action by another service. As a matter of fact, Caliopen needs a **nats** server up and running to work properly.  \nAll **nats** modes may be used by services : asynchronous/synchronous, publish/subscribe, request/reply, etc. depending on use case.\n\n## List of services making use of **nats** system\n- apiv2 : listen & emit\n- mq-worker : listen only\n- identities-worker : listen & emit\n- smtp-bridge : listen & emit\n- imap-bridge : listen & emit\n- twitter-bridge : listen & emit\n---\n#### apiv2\n###### function `Notifications.SendEmailAdminToUser`:\n- role: emitter\n- mode: `request/reply`\n- topic: `outboundSMTP`\n- orders emitted: `\"deliver\"`\n- payload: \n```json\n{\n  \t\"message_id\": \"xxxxxxx\",\n  \t\"order\":      \"deliver\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `REST.SendDraft`:\n- role: emitter\n- mode: `request/reply`\n- topics: `outboundSMTP`, `outboundIMAP`, `twitter_dm`\n- orders emitted: `\"deliver\"`\n- payload:\n```json\n{\n  \t\"message_id\": \"xxxxxxx\",\n  \t\"order\":      \"deliver\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `REST.UpdateContact`\n- role: emitter\n- mode: `publish/subscribe`\n- topics: `contactAction`\n- orders emitted: `\"contact_update\"`\n- payload: \n```json\n{\n  \t\"order\":      \"contact_update\",\n  \t\"contact_id\": \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `REST.launchKeyDiscovery`\n- role: emitter\n- mode: `publish/subscribe`\n- topics: `keyAction`\n- orders emitted: `\"discover_key\"`\n- payload: \n```json\n{\n  \t\"order\":      \"discover_key\",\n  \t\"contact_id\": \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `REST.CreateContact`\n- role: emitter\n- mode: `publish/subscribe`\n- topics: `contactAction`\n- orders emitted: `\"contact_update\"`\n- payload: \n```json\n{\n  \t\"order\":      \"contact_update\",\n  \t\"contact_id\": \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `REST.CreatePGPPubKey`\n- role: emitter\n- mode: `publish/subscribe`\n- topics: `keyAction`\n- orders emitted: `\"publish_key\"`\n- payload: \n```json\n{\n  \t\"key_id\":       \"xxxxxxx\",\n  \t\"order\":        \"publish_key\",\n  \t\"resource_id\":  \"xxxxxxx\",\n  \t\"user_id\":      \"xxxxxxx\"\n}\n```\n###### function `REST.DeletePubKey`\n- role: emitter\n- mode: `publish/subscribe`\n- topics: `keyAction`\n- orders emitted: `\"delete_key\"`\n- payload: \n```json\n{\n  \t\"key_id\":       \"xxxxxxx\",\n  \t\"order\":        \"delete_key\",\n  \t\"resource_id\":  \"xxxxxxx\",\n  \t\"user_id\":      \"xxxxxxx\"\n}\n```\n###### function `REST.CreateUserIdentity`\n- role: emitter\n- mode: `publish/subscribe`\n- topics: `identitiesWorker`\n- orders emitted: `\"add_identity\"`\n- payload:\n```json\n{\n    \"identity_id\":  \"xxxxxxx\",\n  \t\"order\":        \"add_identity\",\n  \t\"user_id\":      \"xxxxxxx\"\n}\n```\n###### function `REST.PatchUserIdentity`\n- role: emitter\n- mode: `publish/subscribe`\n- topics: `identitiesWorker`\n- orders emitted: `\"update_identity\"`\n- payload:\n```json\n{\n    \"identity_id\":  \"xxxxxxx\",\n  \t\"order\":        \"update_identity\",\n  \t\"user_id\":      \"xxxxxxx\"\n}\n```\n###### function `REST.DeleteUserIdentity`\n- role: emitter\n- mode: `publish/subscribe`\n- topics: `identitiesWorker`\n- orders emitted: `\"delete_identity\"`\n- payload:\n```json\n{\n    \"identity_id\":  \"xxxxxxx\",\n  \t\"order\":        \"delete_identity\",\n  \t\"user_id\":      \"xxxxxxx\"\n}\n```\n---\n#### twitter-bridge\n###### function `WorkerMsgHandler`:\n- role: subscriber\n- mode: `publish/subscribe`\n- topic: `twitter_worker `\n- queue: `twitterworkers`\n- orders handled : `\"add_worker\"`, `\"reload_worker\"`, `\"remove_worker\"`\n- payload: \n```json\n{\n  \t\"order\":      \"xxxxxxx\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `DMmsgHandler`:\n- role: subscriber/emitter\n- mode: `publish/subscribe`\n- topic: `twitter_dm`\n- queue: `twitterworkers`\n- orders handled: `\"sync\"`, `\"deliver\"`\n- payload:\n```json\n{\n  \t\"message_id\": \"xxxxxxx\",\n  \t\"order\":      \"xxxxxxx\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n- topic: `identitiesWorker`\n- orders emitted: `\"update_interval\"`\n- payload:\n```json\n{\n    \"identity_id\":    \"xxxxxxx\",\n  \t\"order\":          \"update_interval\",\n  \t\"poll_intervall\": \"xxxxxxx\",\n  \t\"protocol\":       \"twitter\",\n  \t\"user_id\":        \"xxxxxxx\"\n}\n```\n###### function `processInDM` :\n- role: emitter\n- mode : `request/reply`\n- topic : `inboundTwitter`\n- orders emitted: `\"process_inbound\"`\n- payload :\n```json\n{\n  \"message_id\": \"xxxxxxx\",\n  \"order\":      \"process_inbound\",\n  \"remote_id\":  \"xxxxxxx\",\n  \"user_id\":    \"xxxxxxx\"\n}\n```\n---\n#### imap-bridge\n###### function `natsMsgHandler`:\n- role: subscriber/emitter\n- mode: `publish/subscribe`\n- topic: `IMAPfetcher`\n- queue: `IMAPworkers`\n- orders handled: `\"sync\"`, `\"fullfetch\"`\n- payload:\n```json\n{\n  \t\"order\":      \"xxxxxxx\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n- topic: `identitiesWorker`\n- orders emitted: `\"update_interval\"`\n- payload:\n```json\n{\n    \"identity_id\":    \"xxxxxxx\",\n  \t\"order\":          \"update_interval\",\n  \t\"poll_intervall\": \"xxxxxxx\",\n  \t\"protocol\":       \"imap\",\n  \t\"user_id\":        \"xxxxxxx\"\n}\n```\n###### function `natsMsgHandler`:\n- role: subscriber\n- mode: `request/reply`\n- topic: `outboundIMAP`\n- queue: `IMAPworkers`\n- orders handled: `\"deliver\"`\n- payload:\n```json\n{\n    \"message_id\": \"xxxxxxx\",\n  \t\"order\":      \"deliver\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `WorkerMsgHandler`:\n- role: subscriber\n- mode: `publish/subscribe`\n- topic: `imap_worker `\n- queue: `IMAPworkers`\n- orders handled : `\"add_worker\"`, `\"reload_worker\"`, `\"remove_worker\"`\n- payload: \n```json\n{\n  \t\"order\":      \"xxxxxxx\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n---\n#### smtp-bridge\n###### function `natsMsgHandler`:\n- role: subscriber\n- mode: `request/reply`\n- topic: `outboundSMTP`\n- queue: `SMTPqueue`\n- orders handled: `\"deliver\"`\n- payload:\n```json\n{\n    \"message_id\": \"xxxxxxx\",\n  \t\"order\":      \"deliver\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `processInbound`:\n- role: emitter\n- mode: `request/reply`\n- topic: `inboundSMTP`\n- orders emitted: `\"process_inbound\"`\n- payload: \n```json\n{\n    \"message_id\": \"xxxxxxx\",\n  \t\"order\":      \"process_inbound\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n---\n#### identities-worker\n###### function `imapJob.Run`:\n- role: emitter\n- mode: `publish/subscribe`\n- topic: `IMAPfetcher`\n- orders emitted: `\"sync\"`\n- payload:\n```json\n{\n  \t\"order\":      \"sync\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `twitterJob.Run`:\n- role: emitter\n- mode: `publish/subscribe`\n- topic: `twitter_dm`\n- orders emitted: `\"sync\"`\n- payload:\n```json\n{\n  \t\"order\":      \"sync\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### function `natsOrdersHandler`:\n- role: subscriber\n- mode: `publish/subscribe`\n- topic: `identitiesWorker`\n- queue: `IDsworker`\n- orders handled: `\"update_interval\"`, `\"add_identity\"`, `\"update_identity\"`, `\"delete_identity\"`\n- payload: \n```json\n{\n    \"identity_id\":    \"xxxxxxx\",\n  \t\"order\":          \"xxxxxxx\",\n  \t\"poll_intervall\": \"xxxxxxx\",\n  \t\"protocol\":       \"xxxxxxx\",\n  \t\"user_id\":        \"xxxxxxx\"\n}\n```\n---\n#### mq-worker\n###### class `InboundEmail`:\n- role: subscriber\n- mode: `request/reply`\n- topic: `inboundSMTP`\n- queue: `SMTPqueue`\n- orders handled: `process_inbound`\n- payload:\n```json\n{\n    \"message_id\": \"xxxxxxx\",\n  \t\"order\":      \"process_inbound\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### class `InboundTwitter`:\n- role: subscriber\n- mode: `request/reply`\n- topic: `inboundTwitter`\n- queue: `Twitterqueue`\n- orders handled: `process_inbound`\n- payload:\n```json\n{\n    \"message_id\": \"xxxxxxx\",\n  \t\"order\":      \"process_inbound\",\n  \t\"remote_id\":  \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### class `ContactAction`:\n- role: subscriber\n- mode: `publish/subscribe`\n- topic: `contactAction`\n- queue: `contactQueue`\n- orders handled: `contact_update`\n- payload:\n```json\n{\n  \t\"order\":      \"contact_update\",\n  \t\"contact_id\": \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```\n###### class `KeyAction`:\n- role: subscriber\n- mode: `publish/subscribe`\n- topic: `keyAction`\n- queue: `keyQueue`\n- orders handled: `discover_key`\n- payload:\n```json\n{\n  \t\"order\":      \"discover_key\",\n  \t\"contact_id\": \"xxxxxxx\",\n  \t\"user_id\":    \"xxxxxxx\"\n}\n```"
  },
  {
    "path": "doc/specifications/notification/API-fr.md",
    "content": "# Caliopen #\n\n## Notifications\n\n### Objectif de ce document :\n\nDécrire le format des messages échangés entre le frontend et le backend\n\nDécrire le protocole utilisé pour l'échange de ces messages\n\nLister les fonctionnalités de notification exposées par le backend pour le frontend (API)\n\nDécrire le mode de gestion de la file d'attente des notifications côté backend\n\n### Format des messages de notification :\n###### format de la réponse retournée par le backend sur /api/v2/notifications :\n\n```yaml  \nnotifications:  \n  - emitter: string      // backend entity that's emitting the message. (contacts facility, email facility, etc.)  \n    notif_id: string           // universally unique id to unambiguously identify a notification message.  \n    type: string         // a single word to describe message's type and give indication of importance level (event, info, feedback, warning, teaser, error, alert, etc.)  \n    reference: string    // (optional) a reference number previously sent by frontend to link current notification to a previous action/event.  \n    timestamp: int       // unix timestamp at which backend created the notification  \n    body: string         // could be a simple word or a more complex structure like a json, depending of the notification. The structure can include other notifications, see new_message examples.\n```\n\n##### Les messages de notification comportent des 'headers' et un 'body'.\n\nles 'headers' (`emitter`, `notif_id`, `type`, `references`) permettent d'identifier la notification et de la classifier.\n\nle `body` comporte le message/payload de la notification\n\nle header `emitter` sert à identifier le composant du backend qui a émis la notification.\n\nle header `type` permet d'identifier immédiatement le type de notification : event, info, feedback, warning, teaser, error, alert, etc.\n\nle header `reference` pourrait servir à lier une notification à un évènement ou une action initiée par le front afin que celui identifie le feedback du backend sur le déroulement de cette action.\n\nle header `timestamp` est le timestamp unix du moment où la notification a été créée par le backend. Il pourrait permettre au frontend d'indiquer une fourchette horaire lors du `GET /v2/nofications?from=00000000&to=000000000`\n\n\n\n#### Exemples de notifications :\n\n##### notification générique de réception de messages :\n\n```yaml\ntotal: 1\nnotifications:  \n  - emitter: <notifier>(imap_worker|…)\n    notif_id: xxxxx-xxxxx-xxxxx  \n    type: new_message  \n    timestamp: 1518691674517  \n    body:\n      size: <nb>\n      elements:\n        - body:\n            message_id: xxxxxx-xxxxx-xxxxx\n            discussion_id: yyyyy-yyyy-yyyyy\n```\n\n##### notification de réception d'un grand nombre de message :\n\n```json\n{\n  \"total\": 1,\n  \"notifications\": [\n    {\n      \"body\": { \"size\": 646 },\n      \"emitter\": \"imap_worker\",\n      \"notif_id\": \"e23fdf95-9750-11e9-a402-0242ac13000a\",\n      \"type\": \"new_message\"\n    }\n  ]\n}\n```\n\n##### notification de fin d'import de vCards :\n\nle frontend a initié un import avec un POST  sur /v1/imports\n\nlors de son prochain call sur GET /v2/notifications, il pourrait recevoir la notification suivante :\n\n```yaml  \nnotifications:  \n  - emitter: contacts  \n    notif_id: xxxxxx-xxxxx-xxxxx  \n    type: import_result\n    reference: xxxxxxxxx     // could be a hash of the initial call to the API (POST /v1/imports + timestamp (+ headers ?))  \n    timestamp: 1518691674517  \n    body:\n     - body:\n        contact_id: xxx\n        status: imported\n     - body:\n        contact_id: yyy\n        status: error\n        error_msg: \"something went wrong\"\n     - body:\n        contact_id: zzz\n        status: ignored\n```\n\n##### notification d'échec d'envoi de email :\n\nl'utilisateur a envoyé un email (`POST /messages/{message_id}/actions`)\n\nlors de son prochain call sur GET /v2/notifications, le frontend pourrait recevoir la notification suivante :\n\n```yaml  \nnotifications:  \n  - emitter: email-broker  \n    notif_id: xxxxx-xxxxx-xxxxx  \n    type: warning  \n    reference: xxxxxx-xxxxx-xxxx // uuid of email sent  \n    timestamp: 1518691674517  \n    body:  \n      warning: sending email failed  \n      code: 23  \n      message: MTA timeout  \n```\n\n##### notification que l'index est à jour (suite à create/update de document)\n\nl'utilisateur a modifié un contact (`PATCH /contacts/{contact_id}`)\n\nlors de son prochain call sur GET /v2/notifications, le frontend pourrait recevoir la notification suivante :\n\n```yaml  \nnotifications:  \n  - emitter: contacts  \n    notif_id: xxxxx-xxxxx-xxxxx  \n    type: info  \n    reference: xxxxxx-xxxxx-xxxx // uuid of contact modified  \n    timestamp: 1518691674517  \n    body: indexed  \n```\n\n##### notification de ludification:\n\n```yaml  \nnotifications:  \n  - emitter: notification-center  \n    notif_id: xxxxx-xxxxx-xxxxx  \n    type: teaser  \n    timestamp: 1518691674517  \n    body: Pour améliorer votre PI vous pourriez faire ceci…  \n```\n\nEn l'absence de filtre lors du call sur `/api/v2/notifications`, toutes les notifications en instance sont envoyées dans la réponse :\n\n```yaml  \nnotifications:  \n  - emitter: lmtp  \n    notif_id: xxxxx-xxxxx-xxxxx  \n    type: event  \n    timestamp: 1518691674517  \n    body:  \n      - emailReceived: xxxxxx-xxxxx-xxxxx   \n  - emitter: contacts  \n    notif_id: xxxxxx-xxxxx-xxxxx  \n    type: feedback  \n    reference: xxxxxxxxx      \n    timestamp: 1518691674517  \n    body: success  \n  - emitter: lmtp\n    notif_id: xxxxx-xxxxx-xxxxx  \n    type: event  \n    timestamp: 1518691674517  \n    body:  \n      emailReceived: xxxxxx-xxxxx-xxxxx // uuid of new email  \n  - emitter: lmtp  \n    notif_id: xxxxx-xxxxx-xxxxx  \n    type: event  \n    timestamp: 1518691674517  \n    body:  \n      emailReceived: xxxxxx-xxxxx-xxxxx // uuid of new email  \n  - emitter: notification-center  \n    notif_id: xxxxx-xxxxx-xxxxx  \n    type: teaser  \n    timestamp: 1518691674517  \n    body: Pour améliorer votre PI vous pourriez faire ceci…  \n```\n\n### Protocole :\n\nLe frontend fait des requêtes HTTP (GET ou DELETE) sur /api/v2/notifications.\n\nLe serveur répond avec un payload en JSON et ferme la connexion.\n\n### Endpoint et API :\n\n#### endpoint : `…/api/v2/notifications`\n\n#### méthodes : \n\n`GET` pour obtenir des notifications en file d'attente :\n\n- sans params dans la query, toutes les notifications de la file d'attente sont retournées dans un seul document.\n- 2 params optionnels : `from` et `to` pour filtrer les notifications par timestamp ou par IDs de notifications (en effet les IDs des notifs sont des séquences alphanumériques qui grandissent avec le temps)\n\n`DELETE` pour supprimer des notifications de la file d'attente\n\n- 1 param obligatoire : `until` pour indiquer jusqu'à quel timestamp les notifications doivent être supprimées.\n\n#### spécifications (swagger) : voir [/src/backend/defs/rest-api/paths/notifications.yaml](/src/backend/defs/rest-api/paths/notifications.yaml)\n\n### Principe de fonctionnement :\n\n#### Backend :\n\nLe backend gère une pile de notifications pour chaque utilisateur.\n\n- De nouvelles notifications sont créées et ajoutées à la pile par le backend en fonction d'actions de l'utilisateur (création d'un contact par ex.), d'événements extérieurs (arrivée d'un nouvel email par ex.),de traitements batch, etc.\n\nChaque notification à une durée de vie (TTL) prédéfinie dans la pile, en fonction de son niveau d'importance.\n\nPar conséquent, la pile de notifications est automatiquement purgée.\n\n#### Frontend:\n\nLors d'un call `GET` du frontend, la pile de toutes les notifications en instance est envoyée.\n\nLe frontend peut refaire un `GET` xx secondes plus tard, il aura la même pile de notifications +/- les notifications arrivées entre-temps et celles qui ont expirées.\n\nLe frontend peut refaire un `GET` xx secondes plus tard en ajoutant le param from=xxxxxxx pour ne recevoir que les dernières notifications arrivées depuis son dernier call (ou depuis le timestamp de la notification la plus récente qu'il a reçu lors du dernier call)\n\nLe frontend peut explicitement purger la pile de notifications côté backend en faisant un call `DELETE` avec le param `until` ou un numéro de notification\n\n### Modèle de données :\n\nLa table des notifications est stockées dans cassandra.\n\n#### Chaque `INSERT` se fera avec la directive `USING TTL xx` pour gérer le nettoyage automatique de la table\n\n```sql  \nCREATE TABLE user_notification (  \nuser_id uuid,  \ntimestamp_ timestamp,  \nnotif_id uuid,  \ntype ascii,  \nemitter text,  \nreference text,  \nbody blob,  \nprimary key(user_id, timestamp, id));  \n```\n\nCes `primary keys` permettent de retrouver/supprimer toutes les notifications d'un utilisateur, ou celles d'un utilisateur dans un range de timestamp.\n"
  },
  {
    "path": "doc/specifications/notification/assets/client-message-sequence.puml",
    "content": "@startuml\nactor User\nparticipant DesktopNotifier\nparticipant AppNotifier\nparticipant MessageNotifications\nparticipant NotificationProvider\nparticipant Redux\nparticipant Worker\nparticipant API\n\nnote over User: Notications are up to date\n\nNotificationProvider -> Worker: subscribe\nactivate Worker\n\nloop every N sec. until unsubscribe\n  Worker -> API: GET\n  activate API\n  API -> Worker\n  deactivate API\n  Worker -> API: DELETE until N sec.\nend\n\nalt page: Timeline or a discussion (containing new messages)\n\n  Worker -> NotificationProvider: message\n  activate NotificationProvider\n  NotificationProvider -> Redux: dispatch add\n  deactivate NotificationProvider\n  activate Redux\n  Redux -> MessageNotifications: update\n  activate MessageNotifications\n  deactivate Redux\n\n  alt messages only in the discussion\n    MessageNotifications -> User: render\n    activate User\n  else has messages not in the current discussion\n    MessageNotifications -> User: render + with N messages in other discussions\n    deactivate MessageNotifications\n  end\n\n  User -> MessageNotifications: clic\n  deactivate User\n  activate MessageNotifications\n  MessageNotifications -> Redux: dispatch delete\n  deactivate MessageNotifications\n  activate Redux\n  Redux -> NotificationProvider: update\n  deactivate Redux\n\n\nelse page: other page\n\n  Worker -> NotificationProvider: message\n  activate NotificationProvider\n  NotificationProvider -> Redux: dispatch add\n  deactivate NotificationProvider\n  activate Redux\n  Redux -> MessageNotifications: update\n  activate MessageNotifications\n  deactivate Redux\n  MessageNotifications -> AppNotifier: fire\n  activate AppNotifier\n  MessageNotifications -> Redux: dispatch delete\n  deactivate MessageNotifications\n  deactivate MessageNotifications\n  AppNotifier -> User: notify\n  deactivate AppNotifier\n\nelse window not active\n\n  Worker -> NotificationProvider: message\n  activate NotificationProvider\n  NotificationProvider -> Redux: dispatch add\n  deactivate NotificationProvider\n  activate Redux\n  Redux -> MessageNotifications: update\n  activate MessageNotifications\n  deactivate Redux\n  MessageNotifications -> DesktopNotifier: fire\n  activate DesktopNotifier\n  MessageNotifications -> Redux: dispatch delete\n  deactivate MessageNotifications\n  DesktopNotifier -> User: notify\n  deactivate DesktopNotifier\n\nend\n\nNotificationProvider -> Worker: unsubscribe\ndeactivate Worker\n\n@enduml\n"
  },
  {
    "path": "doc/specifications/notification/frontend.md",
    "content": "# Notifications\n\n## Frontend\n\nSequences schema\n\n![client sequences](./assets/client-message-sequence.png)\n"
  },
  {
    "path": "doc/specifications/patch/index.md",
    "content": "# Caliopen « Patch » RFC\n\n## Abstract\nThis specification defines the format of the JSON body that should be sent to the REST API to update a resource with the PATCH verb.\n\n## Introduction\nThis specification extends [RFC 7396](https://tools.ietf.org/html/rfc7396) by adding a `current_state` field to the JSON patch object.\n\n## Json patch object specification\nThe JSON should indicate the fields to be changed, with the desired value for these fields, as describe in RFC 7396  \n**plus**  \na field called `current_state`.  \n`current_state` is an object that describes the values of the fields as seen by the client, before the patch application.\n\n## Examples :\nGiven the following document (client side) :\n```\n{\n  \"family_name\": \"Name\",\n  \"user_id\": \"6d401236-99a8-4088-9ceb-4b6391482c4c\",\n  \"title\": \"Old Title\",\n  \"deleted\": false,\n  \"privacy_index\": 15,\n  \"contact_id\": \"d080232b-8ce2-49e0-bd7e-262ccf82a9d4\",\n  \"date_insert\": \"2016-12-14T13:39:04.111000+00:00\",\n  \"given_name\": \"\",\n  \"emails\": [\n    {\n      \"email_id\": \"998ef52d-f3ba-4c8e-959e-0614ed80c255\",\n      \"is_primary\": false,\n      \"type\": \"work\",\n      \"address\": \"old@domain.tld\"\"\n    }\n  ],\n  \"avatar\": \"avatar.png\"\n}\n```\nTo change the email address and the title, send the JSON below with `PATCH` verb on route `/contacts/{contact_id}` :\n```\n{\n  \"title\": \"New Title\",\n  \"emails\": [\n    {\"address\": \"new@domain.tld\", \"type\": \"home\"}\n  ],\n  \"current_state\": {\n    \"title\": \"Old Title\",\n    \"emails\": [\n        {\n          \"email_id\": \"998ef52d-f3ba-4c8e-959e-0614ed80c255\",\n          \"is_primary\": false,\n          \"type\": \"work\",\n          \"address\": \"old@domain.tld\"\"\n        }\n    ]\n  }\n}\n```\nTo add an email address to a contact that does not have any email, they are two options : either put nothing within `current_state` or put an empty array :\n\n* Option 1 : put an empty array in `current_state`\n\n```\n{\n  \"emails\": [\n    {\"address\": \"local@domain.tld\"}\n  ],\n  \"current_state\": {\n    \"emails\": []\n  }\n}\n```\n* Option 2 : omit key in `current_state`\n```\n{\n  \"emails\": [\n    {\"address\": \"local@domain.tld\"}\n  ],\n  \"current_state\": {}\n}\n```\n"
  },
  {
    "path": "doc/specifications/protocol-implementation.md",
    "content": "Protocol implementation\n=======================\n\n**FIXME:** out of date\n\n**Important note:**\n\n> The following document has been imported as is from the previous client in angularJs; the file structure has changed and this part of the program is not implemeted.\n\n---\n\nThe purpose of this document is list tasks to do in order to support new protocol.\n\nThis procedure may evolve following API evolutions.\n\n## Client\n\n### Message creation: Available protocols\n\nAdd the protocol to the list in `module/recipient-list` component:\n\n```\nconst PROTOCOLS = {\n  // ...\n  sms: {\n    iconClass: 'fa-mobile',\n  },\n  email: {\n    iconClass: 'fa-envelope',\n    regexp: /^\\w+@\\w+(\\.\\w+)?$/,\n  },\n  // ...\n};\n```\n\nThe `iconClass` property is required and the stylesheet class must be available (cf. font-awesome or whatever). The `regexp` is optional but recommended in order to automatically detect the protocol.\n\n## Annexes\n\n### Actors\n\nA recipient is the target of a message and can be known or not.\n\nWhen a contact is attached to a recipient, the user can select a protocol known for the contact and available for the caliopen's instance. When the user write himself an address (or a number) an available protocol is detected. Eventually the user can change it afterward.\n"
  },
  {
    "path": "doc/specifications/pwa/assets/sequence.puml",
    "content": "@startuml\nactor User\nparticipant Browser\nparticipant ServiceWorker\nparticipant Server\n\nUser -> Browser : load / (or click refresh)\nactivate Browser\nBrowser -> ServiceWorker : get /\nactivate ServiceWorker\n\nalt first loading or has network\n  ServiceWorker -> Server : get /\n  activate Server\n  Server -> ServiceWorker : new version of /\n  deactivate Server\n\n  group Preloading\n    ServiceWorker -> Server : get /<hash>.js\n    ServiceWorker -> Server : get /<hash>.css\n  end\n\n  ServiceWorker -> ServiceWorker : cache \"/\" and *.js and *.css\n  activate ServiceWorker\n  deactivate ServiceWorker\n  deactivate Server\nelse no network\n  note over ServiceWorker : no operations\nend\nServiceWorker -> Browser : return / from cache\ndeactivate ServiceWorker\n\nloop on each asset in the html\n  Browser -> ServiceWorker : get /<hash>.js|css\n  activate ServiceWorker\n  alt has network\n    ServiceWorker -> Server : get /<hash>.js|css\n    ServiceWorker -> ServiceWorker : cache /<hash>.js|css\n    activate ServiceWorker\n    deactivate ServiceWorker\n  else no network\n    note over ServiceWorker : no operations\n  end\n  ServiceWorker -> Browser : return /<hash>.js|css from cache\n  deactivate ServiceWorker\nend\nBrowser -> User : ready\ndeactivate Browser\n@enduml\n"
  },
  {
    "path": "doc/specifications/pwa/pwa.md",
    "content": "# Progressive Web App\n\nCaliopen is a PWA, this means it can be «installed» on a device (smartphone or desktop) as a web application.\n\n## Features\n\nIt can be installed on android via a browser like brave, chrome, firefox … or on desktop (for now, only chrom·e·ium support PWA).\n\nhow to: [https://feedback.caliopen.org/t/installer-lapplication-mobile/709]() [fr]\n\nThis is enabled because it provides a manifest file and at least a service worker.\n\nThe manifest is generated by [webpack-pwa-manifest](https://github.com/arthurbergmz/webpack-pwa-manifest).\n\nAnd the service worker is generated by [offline-plugin](https://github.com/NekR/offline-plugin). This plugin uses webpack generated files list to allow the service worker to preload assets and make available offline.\n\n_**/!\\\\** For now only assets can be cached but Caliopen requires `/api/v1/me` and `/api/v1/settings` to be half available offline : the read only mode.\nAnd to be completely available offline, it requires a system that allows to queue user actions like saving or sending messages._\n\n## Client upgrade sequence\n\n![sequence diagram](./assets/sequence.png)\n"
  },
  {
    "path": "doc/specifications/remote-identities/assets/twitter-oauth.puml",
    "content": "@startuml\ntitle Add Twitter as remote identity\nactor \"Authenticated user\" as user\nparticipant \"Client\" as client\nparticipant \"Backend\" as backend\nparticipant \"Twitter API\" as twitter\n\nuser -> client: click add twitter\nclient -> backend: GET /api/vx/twitter/request_token\nbackend -> twitter: POST /oauth/request_token\nnote right: Authorization:\\nOAuth\\n\\t oauth_callback=\"http(s)://#{caliopen instance hostname}/api/vx/twitter/loggedin\"\n\ntwitter -> backend: oauth_token=xxx\\noauth_token_secret=xxx\\noauth_callback_confirmed=true\n\nbackend -> client: oauth_token=xxx\n\nclient -> twitter: (popup) GET /oauth/authenticate?oauth_token=xxx\n\ntwitter -> twitter: authorize\n\ntwitter -> user: 302 to <oauth_callback>\nuser -> backend: GET <oauth_callback>?oauth_token=xxx&oauth_verifier=xxx\nbackend -> twitter: POST /oauth/access_token\ntwitter -> backend: oauth_token=xxx\\noauth_token_secret=xxx\nbackend -> user: 200\nuser -> user: close popup\n\nbackend -> client: notify success\n@enduml\n"
  },
  {
    "path": "doc/specifications/remote-identities/gmail.md",
    "content": "# Gmail\n\nThis describes the  `SASL XOAUTH2 Mechanism` used by gmail to access to mailbox.\nAuthenticate with [Oauth 2.0](./oauth.md)\nThen use the Bearer to initialize SMTP/IMAP operations\n\n## The url for the request credentials popup\n\n```\nhttps://accounts.google.com/o/oauth2/v2/auth?\n  scope=https://mail.google.com/&\n  access_type=offline&\n  include_granted_scopes=true&\n  state=state_parameter_passthrough_value&\n  redirect_uri=<caliopen instance/api/v2/oauth/google/callback>\n  response_type=code&\n  client_id=<client_id>\n```\n\n## How to configure the gmail protocol\n\n* Go to https://console.developers.google.com/\n* Create a new project (choose a name for the project)\n* go to \"credentials\" page\n  * click on \"create credentials\"\n  * select \"OAuth client ID\"\n  * choose application type: \"Web application\"\n  * name it as you want (for example: \"oauth <my> caliopen instance\")\n  *  **FIXME** in the field \"Authorized redirect URIs\" enter the url for this api: `https://<caliopen instance/api/v2/oauth/google/callback>` then press \"Enter\"\n  * Click \"Create\"\n* on credentials page, copy the client_id of the credential you have created\n* **FIXME** and add it to _the config_\n\n\nMore information on  https://developers.google.com/identity/protocols/OAuth2UserAgent\n"
  },
  {
    "path": "doc/specifications/remote-identities/oauth.md",
    "content": "# OAuth 2.0 protocol\n\n\n**TODO:** _drop puml_\n\n```puml\n@startuml\ntitle Use OAuth for a remote identity\nactor \"Authenticated user\" as user\nparticipant \"Client\" as client\nparticipant \"Backend\" as backend\nparticipant \"OAuth server\" as oauth\n\nuser -> client: click add <protocol>\nclient -> backend: POST  /api/v2/identities/remotes { status: \"active\", … }\n\nbackend -> client: 201\n\nclient -> backend: GET /api/v2/identities/<identity_id>\nbackend -> client: 200\\n { info: { authorization_popup_url:  <xxx> } }\n\nclient -> user: open popup\nuser -> oauth: (popup) GET <authorization_popup_url>\n\noauth -> oauth: authorize\n\noauth -> user: 302 to <oauth_callback>\nuser -> backend: GET <oauth_callback>\n\nbackend -> oauth: exchange Token\noauth -> backend: token response\nbackend -> user: 200\nuser -> user: auto-close popup\n\nbackend -> client: notify success\nclient -> user: display \"connected\"\n@enduml\n```\n"
  },
  {
    "path": "doc/specifications/remote-identities/twitter.md",
    "content": "# DM Twitter\n\nBegin with authorization: [Oauth](../remote-identities/oauth.md)\n\nthen **TODO**\n\nThe following sequence as mostly been made using [this twitter signin guide](https://developer.twitter.com/en/docs/twitter-for-websites/log-in-with-twitter/guides/implementing-sign-in-with-twitter).\n\n## The url for the request credentials popup\n\n```\nhttps://api.twitter.com/oauth/authenticate?oauth_token=<token>\n```\n\n## How to configure the Twitter DM protocol\n\n**FIXME**\n\n1. Apply for a developper account https://developer.twitter.com/en/apply/user\n  it requires a valid account and a phone number\n2. create app\n  fill the form with: ...\n3. Navigate to the \"Keys and Tokens\" page.\n4. Select 'Create' under the \"Access token & access token secret\" section.\n\nMore information on https://developer.twitter.com/en/docs/basics/getting-started\n"
  },
  {
    "path": "doc/specifications/rest-api/index.md",
    "content": "# ReST API documentation\n\nReST API is documented using [OpenAPI specification v2.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md)\n\nyou will find the related swagger definition file [here](../../src/backend/doc/api/swagger.json) but it is never edited directly, it has to be generated using the JSON Schema definitions files [here](../../src/backend/defs/rest-api/).\n\nA python package [caliopen_api_doc](../../src/backend/tools/py.doc), acting as pyramid.includes service add a swagger-ui component for documentation and interaction with the API on your [local installation](http://localhost:6543/api-ui/#/)\n\n\nTo start testing it no part of Caliopen is yet installed, and you have docker and docker-compose installed, just run:\n\n```\ncd ../../devtools\ndocker-compose build\ndocker-compose up -d api\n```\n\nTo start play with the API (v1)\n"
  },
  {
    "path": "doc/specifications/scss-reference.md",
    "content": "# CaliOpen Scss reference\n\n**Important note:**\n\n> The following document has been imported as is from the previous client in angularJs; the file\n> structure has changed: each component import its stylsheets. Some rules may have changed and will\n> reported asap in this document\n\n---\n\n**Requirements:**\n\nA basic knowledge of ITCSS and BEM is highly recommanded.\n\n* [A  conference of Harry Roberts](https://www.youtube.com/watch?v=1OKZOV-iLj4) about managing css with ITCSS (1:13)\n* [BEM](http://getbem.com/introduction/) introduction\n\n---\n\nA CSS architecture is subject to two opposing interests. For one hand, we want a set of **generic\nand modular** components we can use freely and easily maintain. On the other hand, we want an\nergonomic interface and a semantic code, so **specific** to each task or showed information.\n\nAn unorganized CSS will tend to have across all components a high specificity. The presented\narchitecture aims to enable a complex interface to have **a modular, a generic, and therefore a\nmaintainable code**.\n\n1. [Organization](#organization)\n    - [Layout](#layout)\n    - [Section](#section)\n    - [Module](#module)\n2. [Development rules](#development-rules)\n    - [Components](#components)\n    - [Placeholders, variables, and mixins](#placeholders-variables-and-mixins)\n3. [Examples](#examples)\n    - [Define a module](#define-a-module)\n    - [Use this module in a section](#use-this-module-in-a-section)\n    - [Use this section in the layout](#use-this-section-in-the-layout)\n\n## Organization\n\nEach element used in the HTML is a component.\n\nA component must:\n- follow the **BEM method** (*block* having *elements* and *modifiers*) with *states*\n- be an semantic or interactive object (not an utility)\n- be simple (always split a component into several when possible)\n- only manage its own internal properties (no `position` or `float` on himself) or those of its\n  *elements*\n- if possible, modify its sub-components with only their own *modifiers*\n\nComponents are divided into several categories: `layout`, `section` and `module`, and respectively\nprefixed with `l-`, `s-` and `m-`. Each category have a specific approach.\n\n### Layout\n\nAn element composing the static part of the application.\n\nA layout component must:\n- does not have a parent component\n- define its own positioning, disposition\n- define its behaviour (standard and responsive) and its direct sub-components's one\n\n### Section\n\nA semantic set, which does not have meaning outside of the application, but is still functional.\n\nA section must:\n\n- be modular, reusable\n- define its behaviour (standard and responsive) and its direct sub-components's one\n\n### Module\n\nAn atomic and generic component, independent of the application meaning.\n\nA module must:\n- be atomic, re-used as much as possible\n- be generic, have a simple and no-responsive behaviour\n- provide modifiers (allowing among other things to make itself responsive)\n\n\n## Development rules\n\n### Components\n\nA component must apply the following naming rules:\n\n- Only uses `.classes`, not `#IDs`\n- BEM syntax: `component`, `component__element`, `component--modifier`\n- Component prefixed following its category: `l-`, `s-` or `m-`\n- *State* prefixed by `is-`: `component.is-state`\n\n**Note**:\nIf a component assume that one of its **elements** is a component (generally by applying on it a\nmodifier), this *element* must be named as this component (prefix included).\n\n\n### Placeholders, variables, and mixins\n\nA module must provide:\n- a placeholder, which only bring the standard behaviour and modifiers\n- a placeholder (if possible) and a mixin for each of its modifiers\n\n**Variables, placeholders and mixins must follow a BEM-like syntax:**\n\nIf they are globals (configuration or utilities):\n- variable: `$namespace__variable--variant`\n- placeholder: `%namespace--variant`\n- mixin: `namespace--variant()`\n\nIf they are related to a component:\n- variable: `$component__variable--variant`\n- placeholder: `%component--modifier`\n- mixin: `component--modifier()`\n\nAll variable, placeholder or mixin which shouldn't be used outside of a component declaration is\nconsidered as \"private\" and must be prefixed with an underscore (`_`).\n\n\n##Examples\n\n### Define a module\n\nDefine a single component, a module, with its variables, default properties, elements and modifiers.\nKeep the same setup and syntax for Layout and Section components.\n\n**`_m-module.scss`**\n```scss\n// Variables\n$m-module__size: 10rem !default; // public\n$_m-module__font-size: 1rem;     // private\n\n// Modifiers\n@mixin  m-module--white {\n  background: white;\n\n  &__text {\n    color: black;\n  }\n}\n\n@mixin  m-module--black {\n  background: black;\n\n  &__text {\n    color: white;\n  }\n}\n\n// Component\n.m-module {\n  // default properties\n  height: $m-module__size;\n  width: $m-module__size;\n\n  // Elements\n  &__text {\n    font-size: $_m-module__font-size;\n  }\n\n  // Modifiers\n  &, // (default: --white)\n  &--white { @include m-module--white; }\n  &--black { @include m-module--black; }\n}\n```\n\n**`example-1.html`**\n```html\n...\n<div class=\"m-module\">\n  <div class=\"m-module__text\">\n    I'm black on white\n  </div>\n</div>\n\n<div class=\"m-module m-module--black\">\n  <div class=\"m-module__text m-module--black__text\">\n    I'm white on black\n  </div>\n</div>\n...\n```\n\n### Use this module in a section\n\nUse the previous `m-module` component in a section. A section shouldn't define modifier. It only\ndefine a semantic and responsive behavior from other components modifiers.\n\n**`_s-section.scss`**\n```scss\n.s-section {\n  background: red;\n\n  // Elements\n  &__element {\n    ...\n  }\n\n  // Sub-components\n  &__m-module {\n    @include m-module--black;\n  }\n\n  // Responsive behavior (with Foundation mixins)\n  @include breakpoint(medium) {\n\n    &__m-module {\n      @include m-module--white;\n    }\n\n  }\n}\n```\n\n**`example-2.html`**\n```html\n...\n<div class=\"s-section\">\n  <div class=\"s-section__element\">\n    ...\n  </div>\n\n  <div class=\"s-section__m-module m-module\">\n    <div class=\"s-section__m-module__text m-module__text\">\n      I'm white on black on module,\n      and black on white on pc !\n    </div>\n  </div>\n</div>\n...\n```\n\n### Use this section in the layout\n\nUse the previous `s-section` component in a layout component. A layout component is like (with a\nsemantic and responsive behavior), but does not have to be modular, because it define its own\nposition.\n\n**`_l-layout.scss`**\n```scss\n.l-layout {\n  position: fixed;\n  top: 0;\n  right: 0;\n  left: 0;\n\n  padding: .5rem;\n}\n```\n\n**`example-3.html`**\n```html\n<div class=\"l-layout\">\n  <div class=\"s-section\">\n    ...\n  </div>\n</div>\n```\n"
  },
  {
    "path": "doc/specifications/search/index.md",
    "content": "# Search API documentation\n\nHow frontend should use `/api/v2/search` API to retrieve documents.\n\n## REQUEST:\n\nFor now, search API is triggered by a simple `GET` with few query params :\n\n- **Mandatory header:** `X-Caliopen-IL`\n    - the header is always required, *but* only taken into account if `doctype=message`.\n    - default values : -10;10\n- **Mandatory param:** `term`\n    - Example : `http://localhost:31415/api/v2/search?term=caliopdev`\n    - This is the simplier request. It will trigger a fulltext search across all document types on all fields for the word « **caliopdev** ».\n    - **NB**: API doesn't handle wildcards for now; i.e. a search with the term « caliop* » will not find documents with « caliopdev » or « caliopen ».\n- **Optional params:** `field`\n    - Example : `http://localhost:31415/api/v2/search?term=meeting&field=subject`\n    - `field` is name of a field on which to perform the search. If omitted defaults to « `_all` ».\n    - This request will trigger a search for the word « meeting » in a field « subject » in all kind of documents.\n    - **NB:** only one `field` param allowed for now.\n- **Special param:** `doctype`\n    - Example : `http://localhost:31415/api/v2/search?term=caliopdev&doctype=message`\n    - This request will narrow the search to documents of type « message ». Allowed `doctype` are « message » or « contact » for now.\n    - Within the context of a `doctype` search, and only in this context, two more params are allowed, and can be combined :\n        - `limit` : to limit the number of documents returned.\n            - ex. : `http://localhost:31415/api/v2/search?term=caliopen&doctype=message&limit=5`\n            - default to 10.\n        - `offset` : to skip documents from response.\n            - ex : `http://localhost:31415/api/v2/search?term=caliopen&doctype=message&offset=5`\n            - default to 0.\n\n## RESPONSES:\n\nWhatever the request is, the response has always the same schema :\n\n```\n{\n    \"total\": 0,\n    \"messages_hits\": {\n        \"total\": 0,\n        \"messages\": [\n                     {\n                        \"id\": \"xxxxx\",\n                        \"score\": 3.374578,\n                        \"highlights\": {\n                            \"a_field_name\": [\"a string\", \"another string\"],\n                            \"another_field_name\": [\"string\",\"string\"]\n                        },\n                        \"document\": { // full message doc here }\n                      },\n                      { // another message }\n        ]\n    },\n    \"contact_hits\": {\n        \"total\": 0,\n        \"contacts\": [\n                      {\n                        \"id\": \"xxxx,\n                        \"score\": 4.625036,\n                        \"highlights\": {\n                            \"field_name\": [\"string\"],\n                            \"another_field\": [\"string\"]\n                        },\n                        \"document\": { // full contact doc here },\n                      },\n                        { // another contact }\n        ]\n    }\n}\n```\n\n`total` fields always count how many docs have been found for the current search, whatever the actual number of docs are effectively returned.\n\n`messages` and `contacts` arrays hold the documents matching the request. How many documents are in these arrays depends of the request context:\n- if no `doctype` param has been provided in the request, arrays contain the top 5 relevant documents for each type.\n- if `doctype` param has been provided in the request, one array is empty (the one that do not match the `doctype` requested), other one holds has many documents as `limit` param, or 10 by default.\n- documents are sorted by relevance.\n"
  },
  {
    "path": "doc/specifications/user-and-device-identification/assets/authenticate_new_device.uml",
    "content": "@startuml\n\ntitle User signin with an unknown device\n\nUser -> Client: signin\nClient -> Client: generate ecdsa keypair and a device_id\n\nClient -> Api: POST /authentication {username, password, device}\nApi -> Backend: User.authenticate(username, password)\nBackend -> Api: user\n\nApi -> Backend: Device.get(user, device_id)\nBackend -> Api: Notfound\nApi -> Backend: Device.create(user, device_id, ecdsa_param)\nBackend -> Api: new_device\n\nApi -> Api: generate_token()\nApi -> Cache: set_token(user_id, device_id, token, device_state)\nApi -> Client: 200 {user_id, token, device_id, device_state}\n\n\n@enduml\n"
  },
  {
    "path": "doc/specifications/user-and-device-identification/assets/create_user_and_device.uml",
    "content": "@startuml\n\ntitle Create a new user and a default device\n\nUser -> Device: create a new account (choose username and a password)\nDevice -> API: GET /v2/username/isAvailable\nAPI -> Device: 200 OK\n\nDevice -> Device: generate an ECDSA keypair\nDevice -> Device: crypt_key = AES.crypt(private_key, password)\n\nDevice -> API: POST /v1/users {username, password, public_key}\nAPI -> Backend: create_user(username, password)\nBackend -> API: user_id\nAPI -> Backend: create_device('default', ecdsa public key)\nBackend-> API: device_id\nAPI -> Device: 200 {user_id, device_id}\n\nDevice -> Device: store device_id, crypted_key\nDevice -> User: first page\n\n@enduml\n"
  },
  {
    "path": "doc/specifications/user-and-device-identification/assets/user_and_device_authentication.uml",
    "content": "@startuml\n\ntitle A known device login on a Caliopen instance\n\n== Authentication ==\nUser -> Device: give username and password\nDevice -> Device: get device_id\n\nDevice -> API: POST /v1/authentications (username, password, device_id)\nAPI -> Backend: check_credentials\nBackend -> API: authentication and device informations\nAPI -> Device: {access_token, device.need_reset, device.reset_code}\n\ngroup device does not need reset\n\n\tDevice -> Device: private_key = AES.decrypt(crypted_key, password)\n\nelse device need reset\n\tDevice -> Device: generate an ECDSA keypair\n\tDevice -> Device: crypt_key = AES.crypt(private_key, password)\n\tDevice -> Device: store device_id, crypted_key\n\n\tDevice -> API: POST /v1/devices/{device_id}/publickeys {public_key, device.reset_code}\n\tAPI -> Device: 201 OK\nend\n\n\n== Call API ==\nDevice -> API: GET /v1/discussions with headers Authorization, X-Device-ID X-Device-Signature\nAPI -> Backend: check token and device signature (access_token, device_id, device_signature)\nBackend -> API: ok\nAPI -> Device: 200 [discussions]\n\n@enduml\n"
  },
  {
    "path": "doc/specifications/user-and-device-identification/assets/user_update_credential.uml",
    "content": "@startuml\n\ntitle User change his authentication credential\n\nUser -> DeviceX: GET /credentialupdate\nDeviceX -> User: form with old and new credential value to give\nUser -> DeviceX: old_credential and new_credential\nDeviceX -> API: POST /users/<user_id>/credentials {old_credential, new_credential} and headers\nAPI -> Backend: update_credential(user_id, old_credential, new_credential)\nBackend -> Backend: check_credential(user_id, old_credential)\nBackend -> Backend: check_device(device_id, device_sign)\nBackend -> Backend: update_credential(user_id, new_credential)\nBackend -> Backend: reset_other_devices(user_id, device_id)\nBackend -> API: ok\nAPI -> DeviceX: 200\nDeviceX -> DeviceX: AES.crypt(private_key, new_credential)\nDeviceX -> DeviceX: store new crypted private key\nDeviceX -> User: success\n\n@enduml\n"
  },
  {
    "path": "doc/specifications/user-and-device-identification/index.md",
    "content": "# Device Identification\n\n## Abstract\n\nUser devices connecting to Caliopen have to be identified strictly as possible.\nSo cryptopgraphic mechanism to sign device request are used. This document describe\nhow this signature is managed.\n\n## TL;DR;\n\nA private ECDSA key is crypted with the user password using AES algorith and stored in device.\nThis private key permit to sign all request made by a device to be validated by the backend.\n\n## Create a device on user registration\n\nWhen a user register to a Caliopen instance, we declare the used device as it's main one.\nThe device will generate an ECDSA key pair and send the public one to Caliopen upon registration.\n\nThe device crypt using AES the private key with the password choosen and keep it in it's local storage.\n\nA device_id is assigned and must also be stored on the device.\n\n![Sequence diagram for user registration and device declaration](./assets/create_user_and_device.png)\n\n## How a device is identified\n\nWhen connecting to caliopen, the device decrypt the private key and must sign every API request using\nthis private key. At this time it's the access_token value that must be used for signing.\n\nDevice must add some HTTP headers to implement this signature mechanism:\n\n```\nX-Caliopen-Device-ID: aaaa-bbbb-cccc-dddd-eeee\nX-Caliopen-Device-Signature: BASE64(privkey.sign(access_token))\nAuthentication: bearer;user_id:access_token\n```\n\n![Sequence diagram for user authentication and device signature during API call](./assets/user_and_device_authentication.png)\n\n### Backend validation\n\nAfter validating the access_token the API will validate the device signature using:\n\n```\nPu = current public key for device\nPu.verify_signature(access_token)\n```\n\n## Manage password change or reset on all devices.\n\nThe crypted private key can't be recover on a device when the user had change or reset his password on\nanother device. Upon authentication with such device, Caliopen reply that this device must be reset and\ngenerate a new ECDSA keypair.\n\n![Sequence diagram for user password update and device reset](./assets/user_update_credential.png)\n"
  },
  {
    "path": "doc/specifications/user-and-device-identification/revoke-devices.md",
    "content": "# Revoke devices\n\n## Client\n\n**Display revoke button**\n\n```mermaid\ngraph TD\n  start(Display a device)\n  is_current{is current device}\n  is_verified{is current device verified}\n  is_last_verified{is last verified device}\n  start-->is_current\n  is_current -- yes --> is_last_verified\n  is_current -- no --> is_verified\n  is_last_verified -- no --> display\n  is_last_verified -- yes --> hide\n  is_verified -- yes --> is_last_verified\n  is_verified -- no --> hide\n\n  display(Display revoke button)\n  hide(Hide revoke button)\n```\n\n**Handle revoke device**\n\n```mermaid\ngraph TD\n  start(Revoke the device)\n  is_current{is current device}\n  is_verified{is device verified}\n  start --> is_verified\n  is_verified -- yes --> confirm_PI\n  is_verified -- no --> is_current\n  confirm_PI -.- confirm_PI_text\n  confirm_PI --> is_current\n  is_current -- no --> redirect\n  is_current -- yes --> confirm_signout\n  confirm_signout --> signout\n\n  confirm_PI(Confirm PI)\n  confirm_PI_text>the device PI settings will be reset next time login]\n  confirm_signout(Confirm signout)\n  redirect(redirect to list)\n  signout(signout)\n```\n"
  },
  {
    "path": "doc/specifications/username/index.md",
    "content": "# _Username_ specifications for Caliopen accounts\n\nThis document describes valid **username** for the creation of an account within Caliopen instances.\n\n## Preambule\nCaliopen's users should be able to take (almost) any username they want to, as long as this username could make the local-part of an email address.  \nThe current specification is based on RFC 5322 for the Internet Message Format, but it does not allow \"comment\" and \"quoted-string\" lexical tokens. Consequently, some special characters are not allowed (see below).\n\n### Definition :\nAn **username** is the unique identifier an user makes use of to create an account within a Caliopen instance.\nUsername is an identifier for the user's account : it is not necessarily the user's real name, or email, or nickname… It will be used as a credential for the purpose of identifying the user when logging in Caliopen. The username is unique within a Caliopen instance.  \nThe **username** could form the _local-part_ of an email address within Caliopen's domain. (i.e. : _<username@caliopen.org>_)\n\nNB : once an user has chosen an username, she/he will be able to create or add some « identities », that are made of : first name, family name, email, etc.\n\n## Format :\n\n* Username are made of utf-8 \"characters\".  \nBy \"character\" we mean a single Unicode grapheme, encoded as a single Unicode code point.\n* Username **is not** case sensitive. (means that all case variations of the username will be considered as forming the same username)\n* Username **must** be at least 3 characters long, and up to 42 characters. (you should know why 42 ! ;-)\n* Username is made of :\n    * uppercase and lowercase Latin letters A to Z and a to z\n    * digits 0 to 9\n    * special characters `!#$%&'*+-/=?^_{|}~`\n    * dot `.`, provided that it is not the first or last char, and provided also that it does not appear consecutively (e.g. John..Doe is not allowed but John.Doe is allowed)\n    * In addition to the above ASCII characters, international characters above U+007F, encoded as UTF-8, are permitted\n* Username **cannot** have invisible control characters and unused Unicode code points (`\\p{C}` Unicode category)\n* Usernane **cannot** have character intended to be combined with another character (e.g. accents, umlauts, enclosing boxes, etc.) (`\\p{M}` Unicode category)\n* Username **cannot** have modifier character, neither have modifier symbol as a full character on its own (`\\p{Lm}` and `\\p{Sk}` Unicode category)\n* Username **cannot** contain those characters (unicode number):\n    * whitespace (U+0020)\n    * `\"` (U+0022)\n    * `,` (U+002C)\n    * `@` (U+0040)\n    *  `  (U+0060)\n    * `:` (U+003A)\n    * `;` (U+003B)\n    * `<` (U+003C)\n    * `>` (U+003E)\n    * `[` (U+005B)\n    * `\\ ` (U+005C)\n    * `]` (U+005D)\n\n## Technical overview\nOn a technical point of view, `username` is a string of utf-8 characters. In other words it is an array of _Unicode code point_, meaning that each character is encoded as a single Unicode code point. For example, the character `à` (grave accent) should be encoded as U+00E0, and **not** as the sequence of the two code points U+0061 (a) and U+0300 (\\`).  \nThe regex engines used to validate the username string must be unicode aware/compliant. This means that the regex engines **must** make use of _Single Unicode Grapheme_, and must be able to handle _Unicode property escapes_ patterns (i.e.: `\\p{category}`).\n\n## Regex :\nHere is the general utf-8 PCRE regex implementation of the username format rules described above :\n```\n^(([^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\x{0022},@\\x{0060}:;<>[\\\\\\]]|[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\x{0022},@\\x{0060}:;<>[\\\\\\]]\\.)){0,40}[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\x{0022},@\\x{0060}:;<>[\\\\\\]]$\n```\n**NB** : these regex will match username of 2 chars length, because they do not make use of lookhead pattern (lookhead is not implemented in GO). As a matter of fact, the minimum size of username must be enforced by an other mean.\n\n\n## Languages implementations :\n\n### Go\n\nThe PCRE regex is directly used, thanks to the `regexp` standard package.\n```\npackage main\n\nimport (\n    \"regexp\"\n    \"fmt\"\n)\n\nfunc main() {\n    var re = regexp.MustCompile(`^(([^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\x{0022},@\\x{0060}:;<>[\\\\\\]]|[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\x{0022},@\\x{0060}:;<>[\\\\\\]]\\.)){1,40}[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\x{0022},@\\x{0060}:;<>[\\\\\\]]$`)\n    var str = `John.Dœuf`\n\n    for i, match := range re.FindAllString(str, -1) {\n        fmt.Println(match, \"found at index\", i)\n        return\n    }\n    fmt.Println(\"not found\")\n}\n```\n### Python\nMust install the \"regex\" package : `pip install regex`  \nMust replace the `\\x{0000}` unicode reference pattern by `\\u0000`\n\n_Note: for Python 2.7 compatibility, use ur\"\" to prefix the regex and u\"\" to prefix the test string and substitution._\n\n```\n# coding=utf8\n\nimport regex\n\nr = ur\"^(([^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\u0022,@\\u0060:;<>[\\\\\\]]|[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\u0022,@\\u0060:;<>[\\\\\\]]\\.)){1,40}[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\u0022,@\\u0060:;<>[\\\\\\]]$\"\n\nusername = u\"John.Dœuf\"\n\nmatch = regex.search(r, username)\n\nif match is not None:\n    print(\"username is valid\")\nelse:\n    print(\"INVALID username\")\n```\n### Javascript\n\nBuilt-in ECMAScript 6 regex implementation does not support the Unicode property escapes syntax. There are currently two options :\n\n* Use a library such as [XRegExp](https://github.com/slevithan/xregexp) to create the regular expressions at run-time:  \n\n    ```\n    var XRegExp = require('xregexp');\n\n    const x = XRegExp(/^(([^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z} .\\u0022,@\\u0060:;<>[\\\\\\]]|[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z} .\\u0022,@\\u0060:;<>[\\\\\\]]\\.)){1,40}[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z} .\\u0022,@\\u0060:;<>[\\\\\\]]$/g);\n    console.log(x.test('John.Dœuf'));\n    // → true\n    ```\n    *NB : Need to double check this library implementation as it seems it is fautly (for example, whitespace characters are incorrectly accepted with the \\p{Z} selector)*\n* Use a library such as [Regenerate](https://github.com/mathiasbynens/regenerate) to generate the regular expression at build time.  \nThis approach results in optimal run-time performance, although the generated regular expressions tend to be fairly large in size (which could lead to load-time performance problems on the web). The biggest downside is that it requires a build script, which gets painful as the developer needs more Unicode-aware regular expressions. Whenever the Unicode Standard is updated, the build script must be updated and its results must be deployed in order to use the latest available data.\n"
  },
  {
    "path": "doc/tools.md",
    "content": "# Caliopen tools\n\n_The right tool for the right job_\n\nLike any project we follow this rules and get a growing list of tools. some are deprecated and some more used than others.\n\n**FIXME:**  Add links if any to the service\n\n## Bug tracking\n\n**discourse** (feedback)\n\n**github issues** (technical)\n\n## Task management\n\n**Trello**\n\n## Specifications\n\n**readthedoc** (right here)\n\n**etherpad**\n\n**invision**\n\n## Communication\n\n**stearleaf** (private)\n\n### public or fallback\n\n**framatalk** https://framatalk.org/caliopen\n**appearsin** https://appear.in/caliopen\n\n## File exchange\n\n**keybase** secure one-to-one\n**gists**\n**framabin** https://framabin.org/\n**framapic** https://framapic.org/\n\n## Deprecated\n\n* Taiga\n* [pivotaltracker](https://www.pivotaltracker.com/n/projects/901154)\n*\n"
  },
  {
    "path": "doc/welcome.md",
    "content": "# The Welcome Guide\n\nYou are involved in Caliopen developments, you are at the right place.\n\n## Git\n\nPlease configure your git to sign your commits with GPG.\n\n```\ngit config commit.gpgsign true\n```\n\nPublish your public key on [github](https://github.com/settings/keys)\n\n_Some info to help you with gpg and signed commits:  https://help.github.com/en/articles/managing-commit-signature-verification_\n"
  },
  {
    "path": "src/backend/Dockerfile.caliopen-go",
    "content": "# Base GO image to build Caliopen GO applications\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM golang:1.10.3\nMAINTAINER Caliopen\n\nRUN go get -u github.com/kardianos/govendor\nRUN go install github.com/kardianos/govendor\n\nCOPY ./vendor /go/src/github.com/CaliOpen/Caliopen/src/backend/vendor\nWORKDIR /go/src/github.com/CaliOpen/Caliopen/src/backend\n\n# Fetch dependencies needed for Caliopen GO apps\nRUN govendor sync -v\n"
  },
  {
    "path": "src/backend/Dockerfile.caliopen-python",
    "content": "# Base Python image to build Caliopen Python applications\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM python:2-alpine\nMAINTAINER Caliopen\n\n# Install gcc openssl and ffi lib\nRUN apk add --no-cache build-base openssl-dev libffi-dev\n\n# Install cassandra-driver regex\nRUN pip install --upgrade pip\nRUN pip install cassandra-driver==3.4.1\nRUN pip install regex\n\nCMD [\"python2\"]\n"
  },
  {
    "path": "src/backend/Dockerfile.cli",
    "content": "# This file creates a container that runs a Caliopen CLI tool\n# Important:\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM public-registry.caliopen.org/caliopen_python\nMAINTAINER Caliopen\n\n# Add local backend source directory in container filesystem\nCOPY . /srv/caliopen/src/backend\n\n# Install Caliopen base packages\nWORKDIR /srv/caliopen/src/backend/main/py.storage\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.pgp\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.pi\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/main/py.main\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/interfaces/NATS/py.client\nRUN pip install -e .\n\n## Container specific installation part\nWORKDIR /srv/caliopen/src/backend/tools/py.CLI\nRUN pip install -e .\nRUN pip install ipython\n\nWORKDIR /srv/caliopen/src/backend\nENTRYPOINT [\"caliopen\", \"-f\", \"/etc/caliopen/caliopen.yaml\"]\n"
  },
  {
    "path": "src/backend/Dockerfile.cli-ml",
    "content": "# This file creates a container that runs a Caliopen CLI tool for machine learning tasks\n# Important:\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM public-registry.caliopen.org/caliopen_python\nMAINTAINER Caliopen\n\nRUN apk add git\n\n# Add local backend source directory in container filesystem\nCOPY . /srv/caliopen/src/backend\n\n# Install external packages\nRUN pip install git+https://github.com/facebookresearch/fastText.git\n\n# Download nltk data\nRUN mkdir -p /usr/share/nltk_data/tokenizers\nWORKDIR /usr/share/nltk_data/tokenizers\nRUN wget -O punkt.zip https://raw.githubusercontent.com/nltk/nltk_data/gh-pages/packages/tokenizers/punkt.zip\nRUN unzip punkt.zip\n\n\n# Install Caliopen base packages\nWORKDIR /srv/caliopen/src/backend/main/py.storage\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.pgp\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.pi\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/main/py.main\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/interfaces/NATS/py.client\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.data\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.tag\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/tools/py.CLI\nRUN pip install -e .\n\n## Container specific installation part\nWORKDIR /srv/caliopen/src/backend/tools/py.ML\nRUN pip install -e .\nRUN pip install ipython\n\nWORKDIR /srv/caliopen/src/backend\nENTRYPOINT [\"caliopml\", \"--config\", \"/etc/caliopen/caliopen.yaml\"]\n"
  },
  {
    "path": "src/backend/Dockerfile.go-api",
    "content": "# This file creates a container that runs a Caliopen API (GO based)\n# Important:\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM public-registry.caliopen.org/caliopen_go as builder\n\nADD . /go/src/github.com/CaliOpen/Caliopen/src/backend\nWORKDIR /go/src/github.com/CaliOpen/Caliopen/src/backend\n\n# Fetch dependencies needed for Caliopen GO apps\nRUN govendor sync -v\n\nRUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags \"-static\"' github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/cmd/caliopen_rest\n\nFROM scratch\nMAINTAINER Caliopen\n\n# Add CA certificates\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\n# Add email templates\nCOPY --from=builder /go/src/github.com/CaliOpen/Caliopen/src/backend/defs/notifiers/templates /etc/defs/notifiers/templates\n\nCOPY --from=builder /go/src/github.com/CaliOpen/Caliopen/src/backend/caliopen_rest /usr/local/bin/caliopen_rest\n\nWORKDIR  \"/etc/caliopen\"\nENTRYPOINT [\"caliopen_rest\", \"serve\", \"-c\" ,\"apiv2\", \"--configpath\" ,\"/etc/caliopen\", \"-p\", \"/caliopen_rest.pid\"]\n\nEXPOSE 6544\n"
  },
  {
    "path": "src/backend/Dockerfile.go-lmtp",
    "content": "# This file creates a container that runs a Caliopen lmtp server\n# Important:\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM public-registry.caliopen.org/caliopen_go as builder\n\nADD . /go/src/github.com/CaliOpen/Caliopen/src/backend\nWORKDIR /go/src/github.com/CaliOpen/Caliopen/src/backend\n\n# Fetch dependencies needed for Caliopen GO apps\nRUN govendor sync -v\n\nRUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags \"-static\"' github.com/CaliOpen/Caliopen/src/backend/protocols/go.smtp/cmd/caliopen_lmtpd\n\nFROM scratch\nMAINTAINER Caliopen\n\n# Add CA certificates\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nCOPY --from=builder /go/src/github.com/CaliOpen/Caliopen/src/backend/caliopen_lmtpd /usr/local/bin/caliopen_lmtpd\n\nWORKDIR \"/etc/caliopen\"\nENTRYPOINT [\"caliopen_lmtpd\", \"serve\", \"-c\", \"lmtp\", \"--configpath\", \"/etc/caliopen\", \"-p\", \"/caliopen_lmtpd.pid\"]\n\nEXPOSE 2525\n"
  },
  {
    "path": "src/backend/Dockerfile.identity-poller",
    "content": "# This file creates a container that runs a Caliopen identity poller\n# Important:\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM public-registry.caliopen.org/caliopen_go as builder\n\nADD . /go/src/github.com/CaliOpen/Caliopen/src/backend\nWORKDIR /go/src/github.com/CaliOpen/Caliopen/src/backend\n\n# Fetch dependencies needed for Caliopen GO apps\nRUN govendor sync -v\n\nRUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags \"-static\"' github.com/CaliOpen/Caliopen/src/backend/workers/go.remoteIDs/cmd/idpoller\n\nFROM scratch\nMAINTAINER Caliopen\n\n# Add CA certificates\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nCOPY --from=builder /go/bin/idpoller /usr/local/bin/idpoller\n\nWORKDIR \"/etc/caliopen\"\nENTRYPOINT [ \"idpoller\", \"start\", \"-c\", \"idpoller\", \"--configpath\", \"/etc/caliopen\", \"-p\", \"/idpoller.pid\"]\n"
  },
  {
    "path": "src/backend/Dockerfile.imap-worker",
    "content": "# This file creates a container that runs a Caliopen identity worker\n# Important:\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM public-registry.caliopen.org/caliopen_go as builder\n\nADD . /go/src/github.com/CaliOpen/Caliopen/src/backend\nWORKDIR /go/src/github.com/CaliOpen/Caliopen/src/backend\n\n# Fetch dependencies needed for Caliopen GO apps\nRUN govendor sync -v\n\nRUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags \"-static\"' github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap/cmd/imapworker\nRUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags \"-static\"' github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap/cmd/imapctl\n\nFROM scratch\nMAINTAINER Caliopen\n\n# Add CA certificates\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nCOPY --from=builder /go/bin/imapworker /usr/local/bin/imapworker\nCOPY --from=builder /go/bin/imapctl /usr/local/bin/imapctl\n\nWORKDIR \"/etc/caliopen\"\nENTRYPOINT [ \"imapworker\", \"start\", \"-c\", \"imapworker\", \"--configpath\", \"/etc/caliopen\", \"-p\", \"/imapworker.pid\"]\n"
  },
  {
    "path": "src/backend/Dockerfile.mastodon-worker",
    "content": "# This file creates a container that runs a Caliopen mastodon worker\n# Important:\n# Author: Caliopen\n# Date: 2019-07-12\n\nFROM public-registry.caliopen.org/caliopen_go as builder\n\nADD . /go/src/github.com/CaliOpen/Caliopen/src/backend\nWORKDIR /go/src/github.com/CaliOpen/Caliopen/src/backend\n\n# Fetch dependencies needed for Caliopen GO apps\nRUN govendor sync -v\n\nRUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags \"-static\"' github.com/CaliOpen/Caliopen/src/backend/protocols/go.mastodon/cmd/mastodonworker\n\nFROM scratch\nMAINTAINER Caliopen\n\n# Add CA certificates\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nCOPY --from=builder /go/bin/mastodonworker /usr/local/bin/mastodonworker\n\nWORKDIR \"/etc/caliopen\"\nENTRYPOINT [ \"mastodonworker\", \"start\", \"--configpath\", \"/etc/caliopen\", \"-p\", \"/mastodonworker.pid\"]\n"
  },
  {
    "path": "src/backend/Dockerfile.mq-worker",
    "content": "# This file creates a container that runs a Caliopen message handler\n# Important:\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM public-registry.caliopen.org/caliopen_python\nMAINTAINER Caliopen\n\nRUN apk -U upgrade\n\n# Add local backend source directory in container filesystem\nCOPY . /srv/caliopen/src/backend\n\n# Install Caliopen base packages\nWORKDIR /srv/caliopen/src/backend/main/py.storage\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.pgp\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.pi\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/main/py.main\nRUN pip install -e .\n\n## Container specific installation part\nRUN pip install tornado==4.2\nRUN pip install nats-client>=0.8.4\n\n# Install python backend packages in develop mode\nWORKDIR /srv/caliopen/src/backend/interfaces/NATS/py.client\nRUN pip install -e .\n\nWORKDIR /srv/caliopen/src/backend/interfaces/NATS/py.client/caliopen_nats\nCMD [\"python\", \"listener.py\", \"-f\", \"/etc/caliopen/caliopen.yaml\"]\n"
  },
  {
    "path": "src/backend/Dockerfile.py-api",
    "content": "# This file creates a container that runs a Caliopen v1 API (python based)\n# Important:\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM public-registry.caliopen.org/caliopen_python\nMAINTAINER Caliopen\n\nRUN apk -U upgrade\n\nRUN apk add nginx linux-headers supervisor\nRUN pip install gunicorn paste\n\n# Copy configuration files to run apiv1 behind uwsi and nginx\nCOPY ./configs/apiv1-nginx.conf /etc/nginx/nginx.conf\nCOPY ./configs/apiv1-supervisord.conf /etc/supervisor.d/apiv1.ini\n\n# Add local backend source directory in container filesystem\nCOPY . /srv/caliopen/src/backend\n\n# Install Caliopen base packages\nWORKDIR /srv/caliopen/src/backend/main/py.storage\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.pgp\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/components/py.pi\nRUN pip install -e .\nWORKDIR /srv/caliopen/src/backend/main/py.main\nRUN pip install -e .\n\n## Container specific installation part\n\n# Install python API packages\nWORKDIR /srv/caliopen/src/backend/interfaces/REST/py.server\nRUN pip install -e .\n\n# Add documentation in container filesystem\nWORKDIR /srv/caliopen/src/backend/tools/py.doc\nRUN pip install -e .\n\nEXPOSE 6543\n\nENTRYPOINT [\"/usr/bin/supervisord\", \"-c\", \"/etc/supervisord.conf\"]\n"
  },
  {
    "path": "src/backend/Dockerfile.twitter-worker",
    "content": "# This file creates a container that runs a Caliopen twitter worker\n# Important:\n# Author: Caliopen\n# Date: 2018-07-20\n\nFROM public-registry.caliopen.org/caliopen_go as builder\n\nADD . /go/src/github.com/CaliOpen/Caliopen/src/backend\nWORKDIR /go/src/github.com/CaliOpen/Caliopen/src/backend\n\n# Fetch dependencies needed for Caliopen GO apps\nRUN govendor sync -v\n\nRUN CGO_ENABLED=0 GOOS=linux go install -a -ldflags '-extldflags \"-static\"' github.com/CaliOpen/Caliopen/src/backend/protocols/go.twitter/cmd/twitterworker\n\nFROM scratch\nMAINTAINER Caliopen\n\n# Add CA certificates\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nCOPY --from=builder /go/bin/twitterworker /usr/local/bin/twitterworker\n\nWORKDIR \"/etc/caliopen\"\nENTRYPOINT [ \"twitterworker\", \"start\", \"--configpath\", \"/etc/caliopen\", \"-p\", \"/twitterworker.pid\"]\n"
  },
  {
    "path": "src/backend/brokers/go.emails/README.md",
    "content": "#### Email Broker pakage\n\n##### Usage\n* import the package  \n`import broker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.emails\"`\n* initialize the broker with the relevant config struct  \n`brokerConnectors, err = broker.Initialize(config)`\n\n`config` is type `broker.LDAconfig` :  \n\n```\ntype (\n\tLDAConfig struct {\n\t\tBrokerType       string      `mapstructure:\"broker_type\"`\n\t\tNatsURL          string      `mapstructure:\"nats_url\"`\n\t\tStoreName        string      `mapstructure:\"store_name\"`\n\t\tStoreConfig      StoreConfig `mapstructure:\"store_settings\"`\n\t\tIndexName        string      `mapstructure:\"index_name\"`\n\t\tIndexConfig      IndexConfig `mapstructure:\"index_settings\"`\n\t\tInTopic          string      `mapstructure:\"in_topic\"`\n\t\tInWorkers        int         `mapstructure:\"lda_workers_size\"`\n\t\tLogReceivedMails bool        `mapstructure:\"log_received_mails\"`\n\t\tOutTopic         string      `mapstructure:\"out_topic\"`\n\t\tNatsListeners    int         `mapstructure:\"nats_listeners\"`\n\t\tAppVersion       string\n\t}\n\n\tStoreConfig struct {\n\t\tHosts       []string `mapstructure:\"hosts\"`\n\t\tKeyspace    string   `mapstructure:\"keyspace\"`\n\t\tConsistency uint16   `mapstructure:\"consistency_level\"`\n\t}\n\tIndexConfig struct {\n\t\tUrls []string `mapstructure:\"urls\"`\n\t}\n)\n```\n\n`broker.Initialize()` will start a broker that :\n\n* listen to Caliopen's NATS (outgoing messages)\n* listen to inbound channel from the smtp agent (ingoing emails)\n\nThe broker returns 2 channels to communicate with the smtp agent :\n\n* `IncomingSmtp` for the smtp agent to inject inbound emails into Caliopen.\n* `OutcomingSmtp` for the smtp agent to receive outbound emails from Caliopen and deliver them.\n \nThese channels deal with an `SmtpEmail` struct :  \n\n```\n\tSmtpEmail struct {\n\t\tEmailMessage *EmailMessage\n\t\tResponse     chan *DeliveryAck\n\t}\n```\nwhich is made of :\n```\n\t\n    // EmailMessage is a wrapper to handle the relationship\n    // between an email representation and its Caliopen message counterpart\n    EmailMessage struct {\n        Email   *Email\n        Message *obj.MessageModel\n    }\n\n    Email struct {\n        SmtpMailFrom string       // from or for the smtp agent\n        SmtpRcpTo    []string     // from or for the smtp agent\n        Raw          bytes.Buffer // raw email (without the Bcc header)\n    }\n```\n\n\n##### Example\n* Inject an email into Caliopen from the smtp agent : (errors handling and config retreiving skipped for brevity)\n\n```\nimport broker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.emails\"\n\nfunc main() {\n    var brokerConnectors broker.EmailBrokerConnectors\n    brokerConnectors, err = broker.Initialize(config)\n    emailMessage := broker.EmailMessage{\n    \t\tEmail: &broker.Email{\n    \t\t\tSmtpMailFrom: from,  // sender email as a string\n    \t\t\tSmtpRcpTo:    to,   // recipients list as a []string\n    \t\t\tRaw:          raw_email, // incoming email as a byte.buffer\n    \t\t},\n    \t\tMessage: nil,\n    \t}\n    \tincoming := &broker.SmtpEmail{\n    \t\tEmailMessage: &emailMessage,\n    \t\tResponse:     make(chan *broker.DeliveryAck),\n    \t}\n    \tdefer close(incoming.Response)\n    \n    \tbrokerConnectors.IncomingSmtp <- incoming\n    \n    \tselect {\n    \tcase response := <-incoming.Response:\n    \t\tif response.Err != nil {\n    \t\t\treturn NewSmtpResponse(fmt.Sprintf(\"554 Error : \" + response.Err.Error()))\n    \t\t} else {\n    \t\t\treturn NewSmtpResponse(\"250 OK: message(s) delivered.\")\n    \t\t}\n    \tcase <-time.After(30 * time.Second):\n    \t\treturn NewSmtpResponse(\"554 Error: LDA timeout\")\n    \t}\n}\n```\n\n"
  },
  {
    "path": "src/backend/brokers/go.emails/broker.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// package email_broker handles codec/decodec between external emails and Caliopen message format\npackage email_broker\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/index/elasticsearch\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"math/rand\"\n\t\"time\"\n)\n\ntype (\n\tEmailBroker struct {\n\t\tConfig            LDAConfig\n\t\tConnectors        EmailBrokerConnectors\n\t\tIndex             backends.LDAIndex\n\t\tNatsConn          *nats.Conn\n\t\tNotifier          Notifications.Notifiers\n\t\tStore             backends.LDAStore\n\t\tnatsSubscriptions []*nats.Subscription\n\t}\n\n\tEmailBrokerConnectors struct {\n\t\tEgress  chan *SmtpEmail\n\t\tIngress chan *SmtpEmail\n\t}\n\n\tSmtpEmail struct {\n\t\tEmailMessage *EmailMessage\n\t\tMTAparams    *MTAparams\n\t\tResponse     chan *EmailDeliveryAck\n\t\tBatch        *Notifications.BatchNotification\n\t}\n\n\tnatsOrder struct {\n\t\tMessageId string `json:\"message_id\"`\n\t\tOrder     string `json:\"order\"`\n\t\tUserId    string `json:\"user_id\"`\n\t}\n\n\t//MTAparams is for embedding credentials to deliver email via a remote SMTP server\n\tMTAparams struct {\n\t\tAuthType string\n\t\tHost     string\n\t\tPassword string\n\t\tUser     string\n\t}\n\n\t// DeliveryAck holds reply from nats when using request/reply system for email\n\tEmailDeliveryAck struct {\n\t\tEmailMessage *EmailMessage `json:\"-\"`\n\t\tErr          bool          `json:\"error\"`\n\t\tResponse     string        `json:\"message,omitempty\"`\n\t}\n)\n\nvar (\n\tbroker *EmailBroker\n)\n\nfunc Initialize(conf LDAConfig) (broker *EmailBroker, connectors EmailBrokerConnectors, err error) {\n\tvar e error\n\n\trand.Seed(time.Now().UnixNano())\n\n\tbroker = &EmailBroker{}\n\tbroker.Config = conf\n\tswitch conf.StoreName {\n\tcase \"cassandra\":\n\t\tc := store.CassandraConfig{\n\t\t\tHosts:       conf.StoreConfig.Hosts,\n\t\t\tKeyspace:    conf.StoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(conf.StoreConfig.Consistency),\n\t\t\tSizeLimit:   conf.StoreConfig.SizeLimit,\n\t\t\tUseVault:    conf.StoreConfig.UseVault,\n\t\t}\n\t\tif conf.StoreConfig.ObjectStore == \"s3\" {\n\t\t\tc.WithObjStore = true\n\t\t\tc.Endpoint = conf.StoreConfig.OSSConfig.Endpoint\n\t\t\tc.AccessKey = conf.StoreConfig.OSSConfig.AccessKey\n\t\t\tc.SecretKey = conf.StoreConfig.OSSConfig.SecretKey\n\t\t\tc.RawMsgBucket = conf.StoreConfig.OSSConfig.Buckets[\"raw_messages\"]\n\t\t\tc.AttachmentBucket = conf.StoreConfig.OSSConfig.Buckets[\"temporary_attachments\"]\n\t\t\tc.Location = conf.StoreConfig.OSSConfig.Location\n\t\t}\n\t\tif conf.StoreConfig.UseVault {\n\t\t\tc.HVaultConfig.Url = conf.StoreConfig.VaultConfig.Url\n\t\t\tc.HVaultConfig.Username = conf.StoreConfig.VaultConfig.Username\n\t\t\tc.HVaultConfig.Password = conf.StoreConfig.VaultConfig.Password\n\t\t}\n\t\tb, e := store.InitializeCassandraBackend(c)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlog.WithError(err).Warnf(\"[EmailBroker] initalization of %s backend failed\", conf.StoreName)\n\t\t\treturn\n\t\t}\n\n\t\tbroker.Store = backends.LDAStore(b) // type conversion to LDA interface\n\tdefault:\n\t\tlog.Warnf(\"[EmailBroker] unknown store backend: %s\", conf.StoreName)\n\t\terr = errors.New(\"[EmailBroker] unknown store backend\")\n\t\treturn\n\t}\n\n\tswitch conf.IndexName {\n\tcase \"elasticsearch\":\n\t\tc := index.ElasticSearchConfig{\n\t\t\tUrls: conf.IndexConfig.Urls,\n\t\t}\n\t\ti, e := index.InitializeElasticSearchIndex(c)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlog.WithError(err).Warnf(\"[EmailBroker] initalization of %s backend failed\", conf.IndexName)\n\t\t\treturn\n\t\t}\n\n\t\tbroker.Index = backends.LDAIndex(i) // type conversion to LDA interface\n\t}\n\n\tbroker.NatsConn, e = nats.Connect(conf.NatsURL)\n\tif e != nil {\n\t\terr = e\n\t\tlog.WithError(err).Warn(\"[EmailBroker] initalization of NATS connexion failed\")\n\t\treturn\n\t}\n\tswitch conf.BrokerType {\n\tcase \"smtp\":\n\t\tbroker.Connectors.Ingress = make(chan *SmtpEmail)\n\t\tbroker.Connectors.Egress = make(chan *SmtpEmail)\n\n\t\te = broker.startIncomingSmtpAgents()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlog.WithError(err).Warn(\"[EmailBroker] failed to start incoming smtp agent(s)\")\n\t\t\treturn\n\t\t}\n\t\tfor i := 0; i < conf.NatsListeners; i++ {\n\t\t\te = broker.startOutcomingSmtpAgents()\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\tlog.WithError(err).Warn(\"[EmailBroker] failed to start outcoming smtp agent(s)\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tconnectors = broker.Connectors\n\tcase \"imap\":\n\t\tbroker.Connectors.Ingress = make(chan *SmtpEmail)\n\t\te = broker.startImapAgents()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlog.WithError(err).Warn(\"[EmailBroker] failed to start imap agent(s)\")\n\t\t\treturn\n\t\t}\n\t\tconnectors = broker.Connectors\n\tdefault:\n\t\tlog.Errorf(\"[EmailBroker] initialize unknown broker type : <%s>\", conf.BrokerType)\n\t\treturn\n\t}\n\tcaliopenConfig := CaliopenConfig{\n\t\tNotifierConfig: conf.NotifierConfig,\n\t\tNatsConfig: NatsConfig{\n\t\t\tUrl:            conf.NatsURL,\n\t\t\tOutSMTP_topic:  conf.OutTopic,\n\t\t\tContacts_topic: conf.ContactsTopic,\n\t\t},\n\t\tRESTstoreConfig: RESTstoreConfig{\n\t\t\tBackendName:  conf.StoreName,\n\t\t\tConsistency:  conf.StoreConfig.Consistency,\n\t\t\tHosts:        conf.StoreConfig.Hosts,\n\t\t\tKeyspace:     conf.StoreConfig.Keyspace,\n\t\t\tOSSConfig:    conf.StoreConfig.OSSConfig,\n\t\t\tObjStoreType: conf.StoreConfig.ObjectStore,\n\t\t\tSizeLimit:    conf.StoreConfig.SizeLimit,\n\t\t},\n\t\tRESTindexConfig: RESTIndexConfig{\n\t\t\tHosts:     conf.IndexConfig.Urls,\n\t\t\tIndexName: conf.IndexName,\n\t\t},\n\t}\n\tbroker.Notifier = Notifications.NewNotificationsFacility(caliopenConfig, broker.NatsConn)\n\tlog.WithField(\"EmailBroker\", conf.BrokerType).Info(\"EmailBroker started.\")\n\treturn\n}\n\nfunc (broker *EmailBroker) ShutDown() {\n\tfor _, sub := range broker.natsSubscriptions {\n\t\tsub.Unsubscribe()\n\t}\n\tbroker.NatsConn.Close()\n\tlog.WithField(\"EmailBroker\", \"Nats subscriptions & connexion closed\").Info()\n\tbroker.Store.Close()\n\tlog.WithField(\"EmailBroker\", \"Store client closed\").Info()\n\tbroker.Index.Close()\n\tlog.WithField(\"EmailBroker\", \"Index client closed\").Info()\n}\n"
  },
  {
    "path": "src/backend/brokers/go.emails/email.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// package email_broker handles codec/decodec between external emails and Caliopen message format\npackage email_broker\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/messages\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/jhillyerd/go.enmime\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"gopkg.in/gomail.v2\"\n\t\"io\"\n\t\"mime\"\n\t\"net/mail\"\n\t\"net/textproto\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc newAddressesFields() (af map[string][]string) {\n\taf = map[string][]string{\n\t\t\"From\":     {},\n\t\t\"Sender\":   {},\n\t\t\"Reply-To\": {},\n\t\t\"To\":       {},\n\t\t\"Cc\":       {},\n\t\t\"Bcc\":      {},\n\t}\n\treturn\n}\n\n//  NewMessageId returns a valid Message-Id\nfunc (b *EmailBroker) NewMessageId(uuid []byte) string {\n\t// sha256 internal message id to form external message id\n\thasher := sha256.New()\n\thasher.Write(uuid)\n\tsha := base64.URLEncoding.EncodeToString(hasher.Sum(nil))\n\tmessageId := sha + \"@\" + b.Config.PrimaryMailHost // should be the default domain in case there are multiple 'from' addresses\n\treturn messageId\n}\n\n// build a 'ready to send' email from a Caliopen message model\n// conforms to\n// RFC822 / RFC2822 / RFC5322 (internet message format)\n// RFC2045 / RFC2046 / RFC2047 / RFC2048 / RFC2049 / RFC2183 (MIME) => TODO\nfunc (b *EmailBroker) MarshalEmail(msg *Message) (em *EmailMessage, err error) {\n\n\tem = &EmailMessage{\n\t\tEmail: &Email{\n\t\t\tSmtpMailFrom: []string{},\n\t\t\tSmtpRcpTo:    []string{},\n\t\t},\n\t\tMessage: msg,\n\t}\n\n\tmessageId := b.NewMessageId(msg.Message_id.Bytes())\n\tem.Message.Date = time.Now()\n\n\t// Assign computed values\n\tem.Message.Date_sort = em.Message.Date\n\tem.Message.External_references.Message_id = messageId\n\n\tm := gomail.NewMessage()\n\taddr_fields := newAddressesFields()\n\tfor _, participant := range msg.Participants {\n\t\taddress := m.FormatAddress(participant.Address, participant.Label)\n\t\tswitch participant.Type {\n\t\tcase ParticipantFrom:\n\t\t\taddr_fields[\"From\"] = append(addr_fields[\"From\"], address)\n\t\t\tem.Email.SmtpMailFrom = append(em.Email.SmtpMailFrom, participant.Address) //TODO: handle multisender to conform to RFC5322#3.6.2 (coupled with sender field)\n\t\tcase ParticipantReplyTo:\n\t\t\taddr_fields[\"Reply-To\"] = append(addr_fields[\"Reply-To\"], address)\n\t\tcase ParticipantTo:\n\t\t\taddr_fields[\"To\"] = append(addr_fields[\"To\"], address)\n\t\t\tem.Email.SmtpRcpTo = append(em.Email.SmtpRcpTo, participant.Address)\n\t\tcase ParticipantCC:\n\t\t\taddr_fields[\"Cc\"] = append(addr_fields[\"Cc\"], address)\n\t\t\tem.Email.SmtpRcpTo = append(em.Email.SmtpRcpTo, participant.Address)\n\t\tcase ParticipantBcc:\n\t\t\tem.Email.SmtpRcpTo = append(em.Email.SmtpRcpTo, participant.Address)\n\t\tcase ParticipantSender:\n\t\t\taddr_fields[\"Sender\"] = append(addr_fields[\"Sender\"], address)\n\t\t\tem.Email.SmtpMailFrom = append(em.Email.SmtpMailFrom, participant.Address) //TODO: handle multisender to conform to RFC5322#3.6.2 (coupled with sender field)\n\t\t}\n\t}\n\n\t// Handle if message is encrypted or not\n\tfeatures := *msg.Privacy_features\n\tcrypt_method, ok := features[\"message_encryption_method\"]\n\tif ok {\n\t\tif crypt_method == \"pgp\" {\n\t\t\terr := b.MarshalPGPEmail(msg, em, addr_fields)\n\t\t\tif err != nil {\n\t\t\t\treturn &EmailMessage{}, err\n\t\t\t}\n\t\t\treturn em, nil\n\t\t}\n\t}\n\n\tfor field, addrs := range addr_fields {\n\t\tif len(addrs) > 0 {\n\t\t\tm.SetHeader(field, addrs...)\n\t\t}\n\t}\n\n\tm.SetHeader(\"Date\", em.Message.Date.Format(time.RFC1123Z))\n\tm.SetHeader(\"Message-ID\", \"<\"+messageId+\">\")\n\tm.SetHeader(\"X-Mailer\", \"Caliopen-\"+b.Config.AppVersion)\n\n\tif msg.External_references.Parent_id != \"\" {\n\t\tm.SetHeader(\"In-Reply-To\", \"<\"+msg.External_references.Parent_id+\">\")\n\t\tref := []string{}\n\t\tfor _, ancestors := range msg.External_references.Ancestors_ids {\n\t\t\tref = append(ref, \"<\"+ancestors+\">\")\n\t\t}\n\t\tm.SetHeader(\"References\", strings.Join(ref, \" \"))\n\t}\n\n\t//TODO: In-Reply-To header\n\tm.SetHeader(\"Subject\", msg.Subject)\n\n\tmessages.SanitizeMessageBodies(msg)\n\tif msg.Body_html != \"\" {\n\t\tm.AddAlternative(\"text/html\", msg.Body_html)\n\t}\n\tif msg.Body_plain != \"\" {\n\t\tm.AddAlternative(\"text/plain\", msg.Body_plain)\n\t}\n\n\tfor _, attachment := range msg.Attachments {\n\t\t//check if file is available in object storage\n\t\tif b.Store.AttachmentExists(attachment.URL) {\n\t\t\t//give method to retrieve file from broker storage interface (instead of default filesystem)\n\t\t\tsize_str := fmt.Sprintf(\"%d\", attachment.Size)\n\t\t\tcontent_disposition := `attachment; filename=\"` + attachment.FileName + `\"; size=` + size_str\n\n\t\t\tm.Attach(attachment.FileName,\n\t\t\t\tgomail.SetCopyFunc(func(w io.Writer) error {\n\t\t\t\t\tfile, err := b.Store.GetAttachment(attachment.URL)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\t_, err = io.Copy(w, file)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}),\n\t\t\t\tgomail.SetHeader(map[string][]string{\n\t\t\t\t\t\"Content-Disposition\": {content_disposition},\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t}\n\n\t//TODO: errors handling\n\n\tm.WriteTo(&em.Email.Raw)\n\tjson_rep, _ := EmailToJsonRep(em.Email.Raw.String())\n\tem.Email_json = &json_rep\n\treturn\n}\n\n// executed by natsMsgHandler after an outgoing email has been transmitted to the MTA without error\n//  - flags the caliopen message to 'sent' in cassandra and elastic\n//  - cleans-up temporary attachment files if any\n//  - stores raw outbound email counterpart\n//  - creates discussion lookup entry\nfunc (b *EmailBroker) SaveIndexSentEmail(ack *EmailDeliveryAck) error {\n\n\t// save raw email in db\n\t/*\n\t\tjson_mail, err := EmailToJsonRep(ack.EmailMessage.Email.Raw.String())\n\t\tif err == nil {\n\t\t\tjson_mail.Envelope.From = ack.EmailMessage.Email.SmtpMailFrom\n\t\t\tjson_mail.Envelope.To = ack.EmailMessage.Email.SmtpRcpTo\n\t\t}\n\t*/\n\traw_uuid, err := gocql.RandomUUID()\n\tvar msg_id UUID\n\tmsg_id.UnmarshalBinary(raw_uuid.Bytes())\n\tm := RawMessage{\n\t\tRaw_msg_id: msg_id,\n\t\tRaw_Size:   uint64(len(ack.EmailMessage.Email.Raw.String())),\n\t\tRaw_data:   ack.EmailMessage.Email.Raw.String(),\n\t\tDelivered:  true,\n\t}\n\terr = b.Store.StoreRawMessage(m)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[Email Broker] outbound: storing raw email failed\")\n\t\treturn err\n\t}\n\n\t// clean-up attachments' temporary files\n\tfor _, attachment := range ack.EmailMessage.Message.Attachments {\n\t\tb.Store.DeleteAttachment(attachment.URL)\n\t}\n\t// get new references for embedded attachments\n\tack.EmailMessage.Message.Attachments = []Attachment{}\n\tfor part := range ack.EmailMessage.Email_json.MimeRoot.Parts.Walk() {\n\t\tif part.Is_attachment {\n\t\t\tis_inline := false\n\t\t\tfilename := \"\"\n\t\t\tsize := 0\n\t\t\theader, ok := part.Headers[\"Content-Disposition\"]\n\t\t\tif ok {\n\t\t\t\tdisposition, dparams, err := mime.ParseMediaType(header[0])\n\t\t\t\tif err == nil {\n\t\t\t\t\tfilename = dparams[\"filename\"]\n\t\t\t\t\tsize, _ = strconv.Atoi(dparams[\"size\"])\n\t\t\t\t\tif disposition == \"inline\" {\n\t\t\t\t\t\tis_inline = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tack.EmailMessage.Message.Attachments = append(ack.EmailMessage.Message.Attachments, Attachment{\n\t\t\t\tContentType:  part.ContentType,\n\t\t\t\tFileName:     filename,\n\t\t\t\tIsInline:     is_inline,\n\t\t\t\tSize:         size,\n\t\t\t\tMimeBoundary: part.Boundary,\n\t\t\t})\n\n\t\t}\n\t}\n\t// Retrieve user informations\n\tuser, err := b.Store.RetrieveUser(ack.EmailMessage.Message.User_id.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\tuser_info := &UserInfo{User_id: user.UserId.String(), Shard_id: user.ShardId}\n\n\t// update caliopen message status\n\tfields := make(map[string]interface{})\n\n\tfields[\"Raw_msg_id\"] = m.Raw_msg_id.String()\n\tfields[\"Is_draft\"] = false\n\tfields[\"Date\"] = ack.EmailMessage.Message.Date\n\tfields[\"Date_sort\"] = ack.EmailMessage.Message.Date_sort\n\tfields[\"Attachments\"] = ack.EmailMessage.Message.Attachments\n\tfields[\"External_references\"] = ack.EmailMessage.Message.External_references\n\terr = b.Store.UpdateMessage(ack.EmailMessage.Message, fields)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[Email Broker] Store.UpdateMessage operation failed\")\n\t}\n\terr = b.Index.UpdateMessage(user_info, ack.EmailMessage.Message, fields)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[Email Broker] Index.UpdateMessage operation failed\")\n\t}\n\n\t// if needed :\n\t// insert new entry into discussion_lookup table\n\t// with message's external reference\n\t/* TODO\n\tif ack.EmailMessage.Message.External_references.Parent_id == \"\" {\n\t\terr = b.Store.CreateThreadLookup(ack.EmailMessage.Message.User_id,\n\t\t\tack.EmailMessage.Message.Discussion_id,\n\t\t\tack.EmailMessage.Message.External_references.Message_id)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"[Email Broker] Store.CreateThreadLookup operation failed\")\n\t\t}\n\t}\n\t*/\n\n\treturn err\n}\n\n// gets a raw email and transforms into a Caliopen 'message' object\n// belonging to an user\nfunc (b *EmailBroker) UnmarshalEmail(em *EmailMessage, user_id UUID) (msg *Message, err error) {\n\n\tparsed_mail, err := mail.ReadMessage(&em.Email.Raw)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[Email Broker] unable to parse email with raw_id : %s\", em.Message.Raw_msg_id)\n\t\treturn nil, err\n\t}\n\n\tvar m_id UUID\n\tm_id.UnmarshalBinary(uuid.NewV4().Bytes())\n\tmail_date, err := parsed_mail.Header.Date()\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[Email Broker] unable to parse email's date\")\n\t}\n\n\t/*\n\t\tmail_body, err := ioutil.ReadAll(parsed_mail.Body)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"unable to parse email's body\")\n\t\t}\n\t*/\n\t//TODO: Body parts, Attachments, Externals_references, identities, parent_id…\n\tmsg = &Message{\n\t\tDate:             mail_date,\n\t\tDate_insert:      time.Now(),\n\t\tIs_unread:        true,\n\t\tMessage_id:       m_id,\n\t\tParticipants:     []Participant{},\n\t\tPrivacy_features: &PrivacyFeatures{},\n\t\tRaw_msg_id:       em.Message.Raw_msg_id,\n\t\tSubject:          parsed_mail.Header.Get(\"subject\"),\n\t\tProtocol:         EmailProtocol,\n\t\tUser_id:          user_id,\n\t}\n\n\tfor field := range newAddressesFields() {\n\t\tp, err := b.unmarshalParticipants(parsed_mail.Header, field, user_id)\n\t\tif err == nil {\n\t\t\tmsg.Participants = append(msg.Participants, p...)\n\t\t}\n\t}\n\n\treturn\n}\n\n// if an user_id is provided, the func will try to find a matching contact for each recipient within user's contactbook in db\n// otherwise, contact_id will be nil for recipient\nfunc (b *EmailBroker) unmarshalParticipants(h mail.Header, address_type string, user_id ...UUID) (participants []Participant, err error) {\n\tparticipants = []Participant{}\n\taddresses, err := h.AddressList(address_type)\n\tif err != nil {\n\t\treturn participants, err\n\t}\n\tfor _, a := range addresses {\n\t\trcpt := Participant{\n\t\t\tType:        address_type,\n\t\t\tProtocol:    EmailProtocol,\n\t\t\tAddress:     a.Address,\n\t\t\tLabel:       a.Name,\n\t\t\tContact_ids: []UUID{},\n\t\t}\n\t\tif len(user_id) == 1 {\n\t\t\tcontact_ids, err := b.Store.LookupContactsByIdentifier(user_id[0].String(), a.Address, \"email\")\n\t\t\tif err == nil {\n\t\t\t\tfor _, id := range contact_ids {\n\t\t\t\t\tvar contact_id UUID\n\t\t\t\t\tuuid, _ := uuid.FromString(id)\n\t\t\t\t\tcontact_id.UnmarshalBinary(uuid.Bytes())\n\t\t\t\t\trcpt.Contact_ids = append(rcpt.Contact_ids, contact_id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tparticipants = append(participants, rcpt)\n\t}\n\n\treturn\n}\n\n// returns an EmailJson object which is our json representation of the raw email\n// in particular, attachments are qualified following Caliopen's rules\n// (see addChildPart() func for attachment qualification algorithm)\nfunc EmailToJsonRep(email string) (json_email EmailJson, err error) {\n\treader := bytes.NewReader([]byte(email))\n\tmsg, err := mail.ReadMessage(reader) // Read email using Go's net/mail\n\tif err != nil {\n\t\treturn\n\t}\n\tmm, err := enmime.ParseMIMEBody(msg) // Parse message body with enmime\n\tif err != nil {\n\t\treturn\n\t}\n\n\tjson_email = EmailJson{\n\t\tAddresses: []EmailAddress{},\n\t\tHeaders:   map[string][]string{},\n\t}\n\n\tfor k, v := range msg.Header {\n\t\tswitch strings.ToLower(k) {\n\t\tcase \"from\", \"to\", \"cc\", \"bcc\", \"reply-to\", \"sender\":\n\t\t\taddr_ptrs, err := msg.Header.AddressList(k)\n\t\t\tif err == nil {\n\t\t\t\tvar addr []EmailAddress\n\t\t\t\tfor _, addr_ptr := range addr_ptrs {\n\t\t\t\t\taddr = append(addr, EmailAddress{\n\t\t\t\t\t\t*addr_ptr,\n\t\t\t\t\t\tk,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tjson_email.Addresses = append(json_email.Addresses, addr...)\n\t\t\t}\n\t\tcase \"date\":\n\t\t\tjson_email.Date = v[0] //TODO : manage multiple date fields\n\t\tcase \"subject\":\n\t\t\tjson_email.Subject = v[0] //TODO : manage multiple subject fields\n\t\t}\n\t\tjson_email.Headers[k] = v\n\t}\n\n\tjson_email.Html = mm.HTML\n\tjson_email.Plain = mm.Text\n\tjson_email.IsTextFromHTML = mm.IsTextFromHTML\n\n\tif mm.Root != nil {\n\t\t//message was MIME encoded, build the mime tree\n\t\troot_part_content_type := mm.GetHeader(\"Content-Type\")\n\t\troot_boundary, _ := GetBoundary(root_part_content_type)\n\t\tjson_email.MimeRoot = MimeRoot{\n\t\t\tAttachments_count: len(mm.Attachments),\n\t\t\tRoot_boundary:     root_boundary,\n\t\t\tInline_count:      len(mm.Inlines),\n\t\t\tParts:             []Part{},\n\t\t}\n\t\tmm.Root.SetHeader(textproto.MIMEHeader{\n\t\t\t\"Content-Type\": []string{root_part_content_type},\n\t\t})\n\t\tchild := mm.Root.FirstChild()\n\t\tif child != nil {\n\t\t\tjson_email.MimeRoot.Parts = addPart(json_email.MimeRoot.Parts, child, true)\n\t\t}\n\n\t\tfor sibling := mm.Root.NextSibling(); sibling != nil; sibling = mm.Root.NextSibling() {\n\t\t\tjson_email.MimeRoot.Parts = addPart(json_email.MimeRoot.Parts, sibling, false)\n\t\t}\n\n\t}\n\treturn json_email, nil\n}\n\n// Build part tree recursively\n// and compute properties for each part\nfunc addPart(parent []Part, part enmime.MIMEPart, isChild bool) []Part {\n\tchild := Part{\n\t\tParts: []Part{},\n\t}\n\tif content_type, ok := part.Parent().Header()[\"Content-Type\"]; ok {\n\t\tvar e error\n\t\tchild.Boundary, e = GetBoundary(content_type[0])\n\t\tif e != nil {\n\t\t\tchild.Boundary = \"\"\n\t\t}\n\t}\n\tchild.Charset = part.Charset()\n\tchild.Content = part.Content()\n\tchild.ContentType = part.ContentType()\n\tchild.Headers = part.Header()\n\n\tdisposition, _, _ := mime.ParseMediaType(part.Header().Get(\"Content-Disposition\"))\n\tdisposition = strings.ToLower(disposition)\n\tif disposition == \"attachment\" {\n\t\tchild.Is_attachment = true\n\t}\n\tif disposition == \"inline\" {\n\t\tchild.Is_attachment = true\n\t\tchild.Is_inline = true\n\t}\n\n\tattachment_types := map[string]bool{\n\t\t\"application\": true,\n\t\t\"image\":       true,\n\t\t\"video\":       true,\n\t\t\"audio\":       true,\n\t\t\"message\":     true,\n\t\t\"font\":        true,\n\t}\n\n\tmainType := strings.Split(strings.ToLower(child.ContentType), \"/\")[0]\n\tif _, ok := attachment_types[mainType]; ok {\n\t\tchild.Is_attachment = true\n\t}\n\n\tfor sibling := part.NextSibling(); sibling != nil; sibling = sibling.NextSibling() {\n\t\tchild.Parts = addPart(child.Parts, sibling, false)\n\t}\n\n\tsub_child := part.FirstChild()\n\tif sub_child != nil {\n\t\tchild.Parts = addPart(child.Parts, sub_child, false)\n\t}\n\n\treturn append(parent, child)\n}\n\n// Returns the boundary string from a part header\nfunc GetBoundary(s string) (boundary string, err error) {\n\tmediatype, params, err := mime.ParseMediaType(s)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif strings.HasPrefix(mediatype, \"multipart\") {\n\t\tboundary = params[\"boundary\"]\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn boundary, nil\n\t} else {\n\t\treturn \"\", errors.New(\"no boundary\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/brokers/go.emails/encrypt.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// package email_broker handles codec/decodec between external emails and Caliopen message format\npackage email_broker\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/emersion/go-message\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"time\"\n)\n\nvar boundaryChars = []rune(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\")\n\nfunc randomString(n int) string {\n\tb := make([]rune, n)\n\tfor i := range b {\n\t\tb[i] = boundaryChars[rand.Intn(len(boundaryChars))]\n\t}\n\treturn string(b)\n}\n\n// NewBoundary return a mail boundary according to RFC 1341 (7.2.1).\n//\n// boundary := 0*69<bchars> bcharsnospace\n// bchars := bcharsnospace / \" \"\n// bcharsnospace :=    DIGIT / ALPHA / \"'\" / \"(\" / \")\" / \"+\"  / \"_\"\n//                / \",\" / \"-\" / \".\" / \"/\" / \":\" / \"=\" / \"?\"\nfunc (b *EmailBroker) NewBoundary() string {\n\treturn \"_mime_b_\" + randomString(21)\n}\n\n// MarshalEncryptedEmail build an encrypted PGP/MIME email according to RFC 3156.\nfunc (b *EmailBroker) MarshalPGPEmail(msg *Message, em *EmailMessage, addresses map[string][]string) (err error) {\n\n\tmainHeader := make(message.Header)\n\tparams := map[string]string{\"boundary\": b.NewBoundary(), \"protocol\": \"application/pgp-encrypted\"}\n\tmainHeader.Set(\"Subject\", msg.Subject)\n\tmainHeader.Set(\"Date\", time.Now().Format(time.RFC1123Z))\n\tmainHeader.SetContentType(\"multipart/encrypted\", params)\n\tmainHeader.Set(\"X-Mailer\", \"Caliopen-\"+b.Config.AppVersion)\n\n\tfor field, addrs := range addresses {\n\t\tif len(addrs) > 0 {\n\t\t\tmainHeader.Set(field, strings.Join(addrs, \",\"))\n\t\t}\n\t}\n\n\t// References headers\n\tmainHeader.Set(\"Message-ID\", \"<\"+b.NewMessageId(em.Message.Message_id.Bytes())+\">\")\n\n\t// Formatted multipart body\n\tbody, err := formatBody(msg, mainHeader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tem.Email.Raw = body\n\tlog.Debug(\"Raw encrypted mail\", em.Email.Raw)\n\n\tjson_rep, _ := EmailToJsonRep(em.Email.Raw.String())\n\tem.Email_json = &json_rep\n\treturn\n}\n\nfunc formatBody(msg *Message, mainHeader message.Header) (bytes.Buffer, error) {\n\t// Create part to include into multipart/[encrypted/signed]\n\tvar body bytes.Buffer\n\tpart1 := make(message.Header)\n\tpart1.SetContentType(\"application/pgp-encrypted\", nil)\n\tpart1.SetContentDescription(\"PGP/MIME version identification\")\n\tpart1Body := bytes.NewBuffer([]byte(\"Version: 1\\n\"))\n\n\tpart2 := make(message.Header)\n\tpart2Params := map[string]string{\"name\": \"encrypted.asc\"}\n\tdispoParams := map[string]string{\"filename\": \"encrypted.asc\"}\n\n\tpart2.SetContentType(\"application/octet-stream\", part2Params)\n\tpart2.SetContentDescription(\"Caliopen PGP encrypted message\")\n\tpart2.SetContentDisposition(\"inline\", dispoParams)\n\tpart2Body := bytes.NewBuffer([]byte(msg.Body_plain))\n\n\tentityVersion := &message.Entity{Header: part1, Body: part1Body}\n\tentityBody := &message.Entity{Header: part2, Body: part2Body}\n\n\tentities := []*message.Entity{entityVersion, entityBody}\n\n\tmp, err := message.NewMultipart(mainHeader, entities)\n\tif err != nil {\n\t\treturn body, err\n\t}\n\n\terr = mp.WriteTo(&body)\n\tif err != nil {\n\t\treturn body, err\n\t}\n\treturn body, nil\n}\n"
  },
  {
    "path": "src/backend/brokers/go.emails/inbound.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// package email_broker handles codec/decodec between emails and Caliopen message format\npackage email_broker\n\n/* inbound is a Local Delivery Agent :\nstores raw incoming emails once in storage\nthen orders email processing via NATS topic « inboundSMTPEmail »\n*/\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/satori/go.uuid\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst (\n\tnatsMessageTmpl = \"{\\\"order\\\":\\\"%s\\\",\\\"user_id\\\":\\\"%s\\\",\\\"identity_id\\\":\\\"%s\\\",\\\"message_id\\\": \\\"%s\\\"}\"\n\tnatsOrderRaw    = \"process_raw\"\n)\n\nfunc (b *EmailBroker) startIncomingSmtpAgents() error {\n\tfor i := 0; i < b.Config.InWorkers; i++ {\n\t\tgo b.incomingSmtpWorker()\n\t}\n\t//TODO: error handling\n\treturn nil\n}\n\nfunc (b *EmailBroker) incomingSmtpWorker() {\n\t//  receives values from the channel repeatedly until channel is closed\n\tfor in := range b.Connectors.Ingress {\n\t\tif in.EmailMessage == nil {\n\t\t\tlog.Warn(\"[EmailBroker] incomingSmtpWorker received an empty payload\")\n\t\t\tack := &EmailDeliveryAck{\n\t\t\t\tEmailMessage: in.EmailMessage,\n\t\t\t\tErr:          true,\n\t\t\t\tResponse:     \"empty payload\",\n\t\t\t}\n\t\t\tin.Response <- ack\n\t\t}\n\t\tgo b.processInboundSMTP(in, true)\n\t}\n}\n\nfunc (b *EmailBroker) startImapAgents() error {\n\tfor i := 0; i < b.Config.InWorkers; i++ {\n\t\tgo b.imapWorker()\n\t}\n\t//TODO: error handling\n\treturn nil\n}\n\nfunc (b *EmailBroker) imapWorker() {\n\t//  receives values from the channel repeatedly until channel is closed\n\tfor in := range b.Connectors.Ingress {\n\t\tif in.EmailMessage == nil {\n\t\t\tlog.Warn(\"[EmailBroker] imapWorker received an empty payload\")\n\t\t\tack := &EmailDeliveryAck{\n\t\t\t\tEmailMessage: in.EmailMessage,\n\t\t\t\tErr:          true,\n\t\t\t\tResponse:     \"empty payload\",\n\t\t\t}\n\t\t\tin.Response <- ack\n\t\t}\n\t\tgo b.processInboundIMAP(in)\n\t}\n}\n\nfunc (b *EmailBroker) processInboundSMTP(in *SmtpEmail, raw_only bool) {\n\tresp := &EmailDeliveryAck{\n\t\tEmailMessage: in.EmailMessage,\n\t\tErr:          false,\n\t\tResponse:     \"\",\n\t}\n\n\t// recipients lookup\n\t// we need at least one valid local recipient before processing further\n\tif len(in.EmailMessage.Email.SmtpRcpTo) == 0 {\n\t\tresp.Response = \"no recipient\"\n\t\tresp.Err = true\n\t\tin.Response <- resp\n\t\treturn\n\t}\n\n\tif b.Config.LogReceivedMails {\n\t\tlog.Infof(\"inbound: processing envelope From: %s -> To: %v\", in.EmailMessage.Email.SmtpMailFrom, in.EmailMessage.Email.SmtpRcpTo)\n\t}\n\n\trcptsIds, err := b.Store.GetUsersForLocalMailRecipients(in.EmailMessage.Email.SmtpRcpTo)\n\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"inbound: recipients lookup failed\")\n\t\tresp.Response = \"recipients store lookup failed\"\n\t\tresp.Err = true\n\t\tin.Response <- resp\n\t\treturn\n\t}\n\n\tif len(rcptsIds) == 0 {\n\t\tresp.Response = \"no recipient found in Caliopen domain\"\n\t\tresp.Err = true\n\t\tin.Response <- resp\n\t\treturn\n\t}\n\n\tb.processInbound(rcptsIds, in, true, resp)\n\tin.Batch.Save(b.Notifier, \"\", LongLived)\n}\n\nfunc (b *EmailBroker) processInboundIMAP(in *SmtpEmail) {\n\t// emails coming from imap fetches are not addressed to a local recipient\n\t// local recipient MUST be embedded in SmtpEmail.Message before calling this method\n\tresp := &EmailDeliveryAck{\n\t\tEmailMessage: in.EmailMessage,\n\t\tErr:          false,\n\t\tResponse:     \"\",\n\t}\n\n\tif in != nil &&\n\t\tin.EmailMessage != nil &&\n\t\tin.EmailMessage.Message != nil &&\n\t\tin.EmailMessage.Message.User_id.String() != EmptyUUID.String() {\n\t\t//TODO : check if user exists\n\t\tb.processInbound([][]UUID{{in.EmailMessage.Message.User_id, in.EmailMessage.Message.UserIdentities[0]}}, in, true, resp)\n\t} else {\n\t\tresp.Response = \"missing user recipient for ingress IMAP message\"\n\t\tresp.Err = true\n\t\tin.Response <- resp\n\t}\n}\n\n// stores raw email + json + message and sends an order on NATS topic for next composant to process it\n// if raw_only is true, only stores the raw email with its json representation but do not unmarshal to our message model\nfunc (b *EmailBroker) processInbound(rcptsIds [][]UUID, in *SmtpEmail, raw_only bool, resp *EmailDeliveryAck) {\n\ttimer := time.Now()\n\t// do not forget to send back ack\n\tdefer func(r *EmailDeliveryAck) {\n\t\tif time.Now().Sub(timer) < time.Second*29 { // lda will not wait for more than 30 sec. : it'll close the chan if timeout reached\n\t\t\tin.Response <- r\n\t\t} else {\n\t\t\tlog.Errorf(\"[EmailBroker] processInbound last more than 29 sec., can't send back this EmailDeliveryAck : %v\", *resp)\n\t\t}\n\t}(resp)\n\n\tif len(rcptsIds) == 0 {\n\t\treturn\n\t}\n\t// store raw email and get its raw_id\n\traw_uuid, err := gocql.RandomUUID()\n\tvar msg_id UUID\n\tmsg_id.UnmarshalBinary(raw_uuid.Bytes())\n\tm := RawMessage{\n\t\tRaw_msg_id: msg_id,\n\t\tRaw_Size:   uint64(len(in.EmailMessage.Email.Raw.String())),\n\t\tRaw_data:   in.EmailMessage.Email.Raw.String(),\n\t\tDelivered:  false,\n\t}\n\terr = b.Store.StoreRawMessage(m)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"inbound: storing raw email failed\")\n\t\tresp.Response = \"storing raw email failed\"\n\t\tresp.Err = true\n\t\treturn\n\t}\n\n\t// send process order to nats for each rcpt\n\terrs := multierror.Error{\n\t\tErrors:      []error{},\n\t\tErrorFormat: ListFormatFunc,\n\t}\n\twg := new(sync.WaitGroup)\n\twg.Add(len(rcptsIds))\n\tfor _, rcptId := range rcptsIds { // rcptsId is a tuple [user_id, identity_id]\n\t\tgo func(rcptId []UUID, errs *multierror.Error) {\n\t\t\tdefer wg.Done()\n\t\t\tnatsMessage := fmt.Sprintf(natsMessageTmpl, natsOrderRaw, rcptId[0].String(), rcptId[1].String(), m.Raw_msg_id.String())\n\t\t\t// XXX manage timeout correctly\n\t\t\tresp, err := b.NatsConn.Request(b.Config.InTopic, []byte(natsMessage), 10*time.Second)\n\t\t\tif err != nil {\n\t\t\t\tif b.NatsConn.LastError() != nil {\n\t\t\t\t\tlog.WithError(b.NatsConn.LastError()).Warnf(\"[EmailBroker] failed to publish inbound request on NATS for user %s\", rcptId[0].String())\n\t\t\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\t\t\t\tmultierror.Append(errs, b.NatsConn.LastError())\n\t\t\t\t} else {\n\t\t\t\t\tlog.WithError(err).Warnf(\"[EmailBroker] failed to publish inbound request on NATS for user %s\", rcptId[0].String())\n\t\t\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\t\t\t\tmultierror.Append(errs, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnats_ack := new(map[string]interface{})\n\t\t\t\terr := json.Unmarshal(resp.Data, &nats_ack)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(\"[EmailBroker] failed to parse inbound ack on NATS for user %s\", rcptId[0].String())\n\t\t\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\t\t\t\tmultierror.Append(errs, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif err, ok := (*nats_ack)[\"error\"]; ok {\n\t\t\t\t\tif err == DuplicateMessage {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tlog.WithError(errors.New(err.(string))).Warnf(\"[EmailBroker] inbound delivery failed for user %s\", rcptId[0].String())\n\t\t\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %s\\n\", natsMessage, resp)\n\t\t\t\t\tmultierror.Append(errs, errors.New(err.(string)))\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t//nats delivery OK\n\t\t\t\tif b.Config.LogReceivedMails {\n\t\t\t\t\tlog.Infof(\"EmailBroker : NATS inbound request successfully handled for user %s : %s\", rcptId[0].String(), (*nats_ack)[\"message\"])\n\t\t\t\t}\n\n\t\t\t\tnotif := Notification{\n\t\t\t\t\tEmitter: \"smtp\",\n\t\t\t\t\tType:    NewMessageNotif,\n\t\t\t\t\tTTLcode: LongLived,\n\t\t\t\t\tUser: &User{\n\t\t\t\t\t\tUserId: rcptId[0],\n\t\t\t\t\t},\n\t\t\t\t\tNotifId: UUID(uuid.NewV1()),\n\t\t\t\t\tBody:    `{\"message_id\": \"` + (*nats_ack)[\"message_id\"].(string) + `\", \"discussion_id\":\"` + (*nats_ack)[\"discussion_id\"].(string) + `\"}`,\n\t\t\t\t}\n\t\t\t\tif in.Batch != nil {\n\t\t\t\t\tin.Batch.Add(notif)\n\t\t\t\t} else {\n\t\t\t\t\tgo b.Notifier.ByNotifQueue(&notif)\n\t\t\t\t}\n\t\t\t}\n\t\t}(rcptId, &errs)\n\t}\n\twg.Wait()\n\t// we assume the previous MTA did the rcpts lookup, so all rcpts should be OK\n\t// consequently, we discard the whole delivery if there is at least one error\n\t// TODO : only report recipients which failed to MTA\n\tif errs.ErrorOrNil() != nil {\n\t\tresp.Response = errs.Error()\n\t\tresp.Err = true\n\t\treturn\n\t} else {\n\t\t// update raw_message table to set raw_message.delivered=true\n\t\tb.Store.SetDeliveredStatus(m.Raw_msg_id.String(), true)\n\t}\n\n}\n\n// deliverMsgToUser marshal an incoming email to the Caliopen message format\n// TODO\nfunc (b *EmailBroker) deliverMsgToUser() {}\n\n// ListFormatFunc is a basic formatter that outputs the number of errors\n// that occurred along with a bullet point list of the errors but without newlines.\nfunc ListFormatFunc(es []error) string {\n\tif len(es) == 1 {\n\t\treturn fmt.Sprintf(\"1 error occurred:<BR>* %s\", es[0])\n\t}\n\n\tpoints := make([]string, len(es))\n\tfor i, err := range es {\n\t\tpoints[i] = fmt.Sprintf(\"* %s\", err)\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"%d errors occurred:<BR>%s\",\n\t\tlen(es), strings.Join(points, \"<BR>\"))\n}\n"
  },
  {
    "path": "src/backend/brokers/go.emails/outbound.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// package email_broker handles codec/decodec between emails and Caliopen message format\npackage email_broker\n\n/* outbound logic :\n- subscribe to 'deliver' topic on NATS channel 'outboundSMTP'\n- for each incoming NATS message\n\tretrieves message from db\n\tbuilds email\n\tforwards email to SMTP outboundDaemon(s) (go.smtp package)\n\tstores the raw_email that's been sent\n\tupdates message status in store and index\n*/\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/users\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/nats-io/go-nats\"\n\t\"time\"\n)\n\nfunc (b *EmailBroker) startOutcomingSmtpAgents() error {\n\n\tsub, err := b.NatsConn.QueueSubscribe(b.Config.OutTopic, b.Config.NatsQueue, func(msg *nats.Msg) {\n\t\t_, err := b.natsMsgHandler(msg)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"[broker outbound] : nats msg handler failed to process incoming msg\")\n\t\t}\n\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.natsSubscriptions = append(b.natsSubscriptions, sub)\n\tb.NatsConn.Flush()\n\n\t//TODO: error handling\n\treturn nil\n}\n\n// retrieves a caliopen message from db, build an email from it\n// sends the email to recipient(s) and stores the raw email sent in db\nfunc (b *EmailBroker) natsMsgHandler(msg *nats.Msg) (resp []byte, err error) {\n\tresp = []byte{}\n\tvar order natsOrder\n\terr = json.Unmarshal(msg.Data, &order)\n\tif err != nil {\n\t\treturn\n\t}\n\tif order.Order == \"deliver\" {\n\t\t//retrieve message from db\n\t\tm, err := b.Store.RetrieveMessage(order.UserId, order.MessageId)\n\t\tif err != nil {\n\t\t\tlog.Warn(err)\n\t\t\tb.natsReplyError(msg, err)\n\t\t\treturn resp, err\n\t\t}\n\t\tif m == nil {\n\t\t\tb.natsReplyError(msg, errors.New(\"message from db is empty\"))\n\t\t\treturn resp, err\n\t\t}\n\t\t//checks if message is draft\n\t\tif !m.Is_draft {\n\t\t\tb.natsReplyError(msg, errors.New(\"message is not a draft\"))\n\t\t\treturn resp, err\n\t\t}\n\n\t\tem, err := b.MarshalEmail(m)\n\t\tif err != nil {\n\t\t\tlog.Warn(err)\n\t\t\tb.natsReplyError(msg, err)\n\t\t\treturn resp, err\n\t\t}\n\n\t\t//checks that we have at least one sender and one recipient\n\t\tif len(em.Email.SmtpRcpTo) == 0 || len(em.Email.SmtpMailFrom) == 0 {\n\t\t\tb.natsReplyError(msg, errors.New(\"missing sender and/or recipient\"))\n\t\t\treturn resp, err\n\t\t}\n\n\t\tout := SmtpEmail{\n\t\t\tEmailMessage: em,\n\t\t\tMTAparams:    nil,\n\t\t\tResponse:     make(chan *EmailDeliveryAck),\n\t\t}\n\t\t//checks if identity is local or remote\n\t\t//fetch credentials and remote server info accordingly\n\t\tif m.UserIdentities == nil || len(m.UserIdentities) == 0 {\n\t\t\tb.natsReplyError(msg, errors.New(\"message \"+m.Message_id.String()+\" (user \"+m.User_id.String()+\") has no user identity embedded\"))\n\t\t\treturn resp, err\n\t\t}\n\t\tfirstIdentity, err := b.Store.RetrieveUserIdentity(m.User_id.String(), m.UserIdentities[0].String(), true) // handle one identity only for now\n\t\tif err != nil {\n\t\t\tb.natsReplyError(msg, fmt.Errorf(\"broker failed to retrieve sender's identity with error : %s\", err))\n\t\t\treturn resp, err\n\t\t}\n\t\tswitch firstIdentity.Type {\n\t\tcase RemoteIdentity:\n\t\t\tif firstIdentity.Credentials != nil {\n\t\t\t\tauthType, foundAuthType := firstIdentity.Infos[\"authtype\"]\n\t\t\t\tif foundAuthType {\n\t\t\t\t\tswitch authType {\n\t\t\t\t\tcase Oauth1:\n\t\t\t\t\t\terr = errors.New(\"oauth1 mechanism not implemented\")\n\t\t\t\t\t\tb.natsReplyError(msg, err)\n\t\t\t\t\t\treturn resp, err\n\t\t\t\t\tcase Oauth2:\n\t\t\t\t\t\tout.MTAparams = &MTAparams{\n\t\t\t\t\t\t\tAuthType: Oauth2,\n\t\t\t\t\t\t\tHost:     firstIdentity.Infos[\"outserver\"],\n\t\t\t\t\t\t\tPassword: (*firstIdentity.Credentials)[users.CRED_ACCESS_TOKEN],\n\t\t\t\t\t\t\tUser:     (*firstIdentity.Credentials)[users.CRED_USERNAME],\n\t\t\t\t\t\t}\n\t\t\t\t\tcase LoginPassword:\n\t\t\t\t\t\tout.MTAparams = &MTAparams{\n\t\t\t\t\t\t\tAuthType: LoginPassword,\n\t\t\t\t\t\t\tHost:     firstIdentity.Infos[\"outserver\"],\n\t\t\t\t\t\t\tPassword: (*firstIdentity.Credentials)[\"outpassword\"],\n\t\t\t\t\t\t\tUser:     (*firstIdentity.Credentials)[\"outusername\"],\n\t\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\terr = fmt.Errorf(\"unknown auth mechanism : <%s>\", authType)\n\t\t\t\t\t\tb.natsReplyError(msg, err)\n\t\t\t\t\t\treturn resp, err\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// fallback by trying default LoginPassword mechanism\n\t\t\t\t\tout.MTAparams = &MTAparams{\n\t\t\t\t\t\tAuthType: LoginPassword,\n\t\t\t\t\t\tHost:     firstIdentity.Infos[\"outserver\"],\n\t\t\t\t\t\tPassword: (*firstIdentity.Credentials)[\"outpassword\"],\n\t\t\t\t\t\tUser:     (*firstIdentity.Credentials)[\"outusername\"],\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t} else {\n\t\t\t\terr = fmt.Errorf(\"remote identity %s has no credentials\", firstIdentity.Id.String())\n\t\t\t\tb.natsReplyError(msg, err)\n\t\t\t\treturn resp, err\n\t\t\t}\n\t\tcase LocalIdentity:\n\t\t//nothing to do, MTAparams is already nil\n\t\tdefault:\n\t\t\tb.natsReplyError(msg, fmt.Errorf(\"broker can't handle sender's protocol %s\", firstIdentity.Protocol))\n\t\t\treturn resp, err\n\t\t}\n\n\t\tb.Connectors.Egress <- &out\n\t\t// non-blocking wait for delivery ack\n\t\tgo func(out *SmtpEmail, natsMsg *nats.Msg) {\n\t\t\tselect {\n\t\t\tcase resp, ok := <-out.Response:\n\t\t\t\tif resp.Err || !ok || resp == nil {\n\t\t\t\t\tlog.WithError(err).Warn(\"outbound: delivery error from MTA\")\n\t\t\t\t\tb.natsReplyError(msg, errors.New(\"outbound: delivery error from MTA : \"+resp.Response))\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\terr = b.SaveIndexSentEmail(resp)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Warn(\"outbound: error when saving back sent email\")\n\t\t\t\t\t\tresp.Response = err.Error()\n\t\t\t\t\t\tresp.Err = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresp.Err = false\n\t\t\t\t\t\tresp.Response = \"message \" + resp.EmailMessage.Message.Message_id.String() + \" has been sent.\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tjson_resp, _ := json.Marshal(resp)\n\t\t\t\tb.NatsConn.Publish(msg.Reply, json_resp)\n\t\t\tcase <-time.After(time.Second * 30):\n\t\t\t\tb.natsReplyError(msg, errors.New(\"SMTP server response timeout\"))\n\t\t\t}\n\t\t\treturn\n\t\t}(&out, msg)\n\t}\n\treturn resp, err\n}\n\nfunc (b *EmailBroker) natsReplyError(msg *nats.Msg, err error) {\n\tlog.WithError(err).Warnf(\"email broker [outbound] : error when processing incoming nats message : %s\", *msg)\n\n\tvar order natsOrder\n\tjson.Unmarshal(msg.Data, &order)\n\tack := DeliveryAck{\n\t\tErr:      true,\n\t\tResponse: fmt.Sprintf(\"failed to send message %s with error « %s » \", order.MessageId, err.Error()),\n\t}\n\n\tjson_resp, _ := json.Marshal(ack)\n\tb.NatsConn.Publish(msg.Reply, json_resp)\n}\n"
  },
  {
    "path": "src/backend/brokers/go.mastodon/broker.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage mastodonbroker\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/mattn/go-mastodon\"\n\t\"github.com/nats-io/go-nats\"\n)\n\ntype (\n\tMastodonBroker struct {\n\t\tConfig            BrokerConfig\n\t\tConnectors        MastodonBrokerConnectors\n\t\tIndex             backends.LDAIndex\n\t\tNatsConn          *nats.Conn\n\t\tNotifier          Notifications.Notifiers\n\t\tStore             backends.LDAStore\n\t\tnatsSubscriptions []*nats.Subscription\n\t}\n\n\tBrokerConfig struct {\n\t\tIndexConfig          IndexConfig `mapstructure:\"index_settings\"`\n\t\tIndexName            string      `mapstructure:\"index_name\"`\n\t\tNatsQueue            string      `mapstructure:\"nats_queue\"`\n\t\tNatsURL              string      `mapstructure:\"nats_url\"`\n\t\tNatsTopicPoller      string      `mapstructure:\"nats_topic_poller\"`\n\t\tNatsTopicPollerCache string      `mapstructure:\"nats_topic_poller_cache\"`\n\t\tNatsTopicDMs         string      `mapstructure:\"nats_topic_direct_message\"`\n\t\tStoreConfig          StoreConfig `mapstructure:\"store_settings\"`\n\t\tStoreName            string      `mapstructure:\"store_name\"`\n\t\tLDAConfig            LDAConfig   `mapstructure:\"LDAConfig\"`\n\t}\n\n\tMastodonBrokerConnectors struct {\n\t\tEgress chan NatsCom\n\t\tHalt   chan struct{}\n\t}\n\n\tDMpayload struct {\n\t\tToot     *mastodon.Toot\n\t\tErr      error\n\t\tResponse chan MastodonDeliveryAck\n\t}\n\n\t// MastodonAck embeds response from Mastodon API to pass back to broker.\n\tMastodonDeliveryAck struct {\n\t\tPayload  *mastodon.Status `json:\"-\"`\n\t\tErr      bool             `json:\"error\"`\n\t\tResponse string           `json:\"message,omitempty\"`\n\t}\n\n\t// natsCom is used to communicate between nats handler and broker\n\tNatsCom struct {\n\t\tOrder BrokerOrder\n\t\tAck   chan *DeliveryAck\n\t}\n)\n\nfunc Initialize(conf BrokerConfig, store backends.LDAStore, index backends.LDAIndex, natsConn *nats.Conn, notifier *Notifications.Notifier) (broker *MastodonBroker, err error) {\n\tbroker = new(MastodonBroker)\n\tbroker.Config = conf\n\tbroker.Store = store\n\tbroker.Index = index\n\tbroker.NatsConn = natsConn\n\tbroker.Notifier = notifier\n\tbroker.Connectors = MastodonBrokerConnectors{\n\t\tEgress: make(chan NatsCom, 5),\n\t\tHalt:   make(chan struct{}),\n\t}\n\treturn\n}\n\nfunc (broker *MastodonBroker) ShutDown() {\n\tbroker.NatsConn.Close()\n\tbroker.Store.Close()\n\tbroker.Index.Close()\n\tclose(broker.Connectors.Egress)\n\tclose(broker.Connectors.Halt)\n\tlog.WithField(\"MastodonBroker\", \"shutdown\").Info()\n}\n"
  },
  {
    "path": "src/backend/brokers/go.mastodon/direct_messages.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage mastodonbroker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/mattn/go-mastodon\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"strings\"\n\t\"time\"\n)\n\n// SaveRawDM marshal DM to json and save it as a raw message object in store\nfunc (broker *MastodonBroker) SaveRawDM(dm *mastodon.Status, userId UUID) (rawMessageId UUID, err error) {\n\n\tjsonDM, e := json.Marshal(dm)\n\tif e != nil {\n\t\terr = fmt.Errorf(\"[Mastodon Broker]SaveRawDM failed to marshal dm to json : %s\", e)\n\t\treturn\n\t}\n\n\trawMsg := RawMessage{\n\t\tRaw_msg_id: UUID(uuid.NewV4()),\n\t\tRaw_Size:   uint64(len(jsonDM)),\n\t\tRaw_data:   string(jsonDM),\n\t\tDelivered:  false,\n\t}\n\n\te = broker.Store.StoreRawMessage(rawMsg)\n\tif e != nil {\n\t\terr = fmt.Errorf(\"[Mastodon Broker]SaveRawDM failed to store raw message in store : %s\", e)\n\t\treturn\n\t}\n\n\treturn rawMsg.Raw_msg_id, nil\n}\n\nfunc (b *MastodonBroker) SaveIndexSentDM(initialOrder BrokerOrder, ack *MastodonDeliveryAck) error {\n\t// save raw status in db\n\trawMsgId, err := b.SaveRawDM(ack.Payload, UUID(uuid.FromStringOrNil(initialOrder.UserId)))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// TODO: handle attachments\n\n\t// Retrieve user informations\n\tuser, err := b.Store.RetrieveUser(initialOrder.UserId)\n\tif err != nil {\n\t\treturn err\n\t}\n\tuserInfo := &UserInfo{User_id: user.UserId.String(), Shard_id: user.ShardId}\n\n\t// update caliopen message status\n\tmessage, err := b.Store.RetrieveMessage(initialOrder.UserId, initialOrder.MessageId)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfields := make(map[string]interface{})\n\tvar date time.Time\n\tdate = ack.Payload.CreatedAt\n\tmessage.Raw_msg_id = rawMsgId\n\tfields[\"Raw_msg_id\"] = message.Raw_msg_id\n\tmessage.Is_draft = false\n\tfields[\"Is_draft\"] = message.Is_draft\n\tmessage.Date = date\n\tfields[\"Date\"] = message.Date\n\tmessage.Date_sort = date\n\tfields[\"Date_sort\"] = message.Date_sort\n\t//fields[\"Attachments\"] = TODO\n\tmessage.External_references = ExternalReferences{\n\t\tMessage_id: string(ack.Payload.ID),\n\t}\n\tfields[\"External_references\"] = message.External_references\n\n\terr = b.Store.UpdateMessage(message, fields)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[SaveIndexSentDM] Store.UpdateMessage operation failed\")\n\t\treturn err\n\t}\n\n\terr = b.Index.UpdateMessage(userInfo, message, fields)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[SaveIndexSentDM] Index.UpdateMessage operation failed\")\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// UnmarshalDM creates a new Caliopen Message entity from a mastodon status\nfunc UnmarshalDM(dm *mastodon.Status, userId UUID) (message *Message, err error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\n// MarshalDM builds a Mastodon toot with visibility=direct from a Caliopen message\nfunc MarshalDM(msg *Message) (toot *mastodon.Toot, err error) {\n\tvar body strings.Builder\n\tfor _, participant := range msg.Participants {\n\t\tif participant.Type != \"From\" {\n\t\t\tbody.WriteString(\"@\" + participant.Address + \" \")\n\t\t}\n\t}\n\tbody.WriteString(msg.Body_plain)\n\ttoot = &mastodon.Toot{\n\t\tStatus:      body.String(),\n\t\tInReplyToID: mastodon.ID(msg.External_references.Parent_id),\n\t\tVisibility:  \"direct\",\n\t}\n\treturn\n}\n\n// IDgreaterThan compares two mastodon IDs and returns true if first ID is greater or equal to second one\n// string IDs a representation of integers, but what is faster to do ? strconv to int , or len + lexically sorting ?\nfunc IDgreaterOrEqual(first, second string) bool {\n\tif len(first) > len(second) {\n\t\treturn true\n\t}\n\tif len(first) == len(second) {\n\t\treturn first >= second\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "src/backend/brokers/go.mastodon/doc.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// package mastodonbroker is a bridge between Mastodon status model and Caliopen message model\n// inbound : it unmarshal direct messages from Mastodon into Caliopen's Message struct, stores it and triggers relevant actions via NATS message queue\n// outbound : it listens to NATS message orders to retrieve a Caliopen Message and converts it to direct message ready to be sent through Mastodon API\n\npackage mastodonbroker\n"
  },
  {
    "path": "src/backend/brokers/go.mastodon/inbound.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage mastodonbroker\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/mattn/go-mastodon\"\n\t\"github.com/satori/go.uuid\"\n\t\"time\"\n)\n\nconst (\n\tnatsMessageTmpl  = \"{\\\"order\\\":\\\"%s\\\",\\\"user_id\\\":\\\"%s\\\",\\\"identity_id\\\":\\\"%s\\\",\\\"message_id\\\": \\\"%s\\\"}\"\n\tnatsOrderRaw     = \"process_raw\"\n\tNatsError        = \"nats error\"\n\tlastSeenInfosKey = \"lastseendm\"\n\tlastSyncInfosKey = \"lastsync\"\n)\n\n// ProcessInDM is in charge of saving raw DM before further processing (could be unmarshalled too)\n// it ends by\n//      sending natsOrderRaw for other stack components\n//      sending new message notification if everything went good\n//      updating remote identity state in db\nfunc (broker *MastodonBroker) ProcessInDM(userID, remoteID UUID, dm *mastodon.Status, rawOnly bool, batch *Notifications.BatchNotification) error {\n\n\trawID, err := broker.SaveRawDM(dm, userID)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// send process order to nats\n\tnatsMessage := fmt.Sprintf(natsMessageTmpl, natsOrderRaw, userID.String(), remoteID.String(), rawID.String())\n\tresp, err := broker.NatsConn.Request(broker.Config.LDAConfig.InTopic, []byte(natsMessage), 10*time.Second)\n\tif err != nil {\n\t\tif broker.NatsConn.LastError() != nil {\n\t\t\tlog.WithError(broker.NatsConn.LastError()).Warnf(\"[MastodonBroker] failed to publish inbound request on NATS for user %s. Raw message has been saved with id %s\", userID.String(), rawID.String())\n\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\t} else {\n\t\t\tlog.WithError(err).Warnf(\"[MastodonBroker] failed to publish inbound request on NATS for user %s. Raw message has been saved with id %s\", userID.String(), rawID.String())\n\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\t}\n\t\treturn errors.New(NatsError)\n\t}\n\t// handle nats response\n\tnats_ack := new(map[string]interface{})\n\terr = json.Unmarshal(resp.Data, &nats_ack)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\treturn errors.New(\"[ProcessInDM] failed to parse inbound ack on NATS\")\n\t}\n\tif err, ok := (*nats_ack)[\"error\"]; ok {\n\t\tif err == DuplicateMessage {\n\t\t\treturn nil\n\t\t}\n\t\tlog.WithError(errors.New(err.(string))).Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\treturn errors.New(\"[ProcessInDM] inbound delivery failed\")\n\t}\n\t// nats delivery OK, notify user\n\tnotif := Notification{\n\t\tEmitter: \"mastodonBroker\",\n\t\tType:    NewMessageNotif,\n\t\tTTLcode: LongLived,\n\t\tUser: &User{\n\t\t\tUserId: userID,\n\t\t},\n\t\tNotifId: UUID(uuid.NewV1()),\n\t\tBody:    `{\"message_id\": \"` + (*nats_ack)[\"message_id\"].(string) + `\", \"discussion_id\":\"` + (*nats_ack)[\"discussion_id\"].(string) + `\"}`,\n\t}\n\tif batch != nil {\n\t\tbatch.Add(notif)\n\t} else {\n\t\tgo broker.Notifier.ByNotifQueue(&notif)\n\t}\n\t// update raw_message table to set raw_message.delivered=true\n\tgo broker.Store.SetDeliveredStatus(rawID.String(), true)\n\treturn nil\n\n}\n"
  },
  {
    "path": "src/backend/brokers/go.mastodon/outbound.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage mastodonbroker\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"time\"\n)\n\n// retrieves a caliopen message from db, builds a DM from it\n// gives back DM to worker for it delivers it through mastodon API\n// waits for worker ack and finishes saving new state.\nfunc (b *MastodonBroker) ProcessOutDM(order BrokerOrder, worker chan *DMpayload) {\n\n\tdmPayload := &DMpayload{\n\t\tResponse: make(chan MastodonDeliveryAck),\n\t}\n\n\t// 1. build DM\n\tm, err := b.Store.RetrieveMessage(order.UserId, order.MessageId)\n\tif err != nil {\n\t\treplyError(err, worker)\n\t\treturn\n\t}\n\tif m == nil {\n\t\treplyError(errors.New(\"message from db is empty\"), worker)\n\t\treturn\n\t}\n\tif !m.Is_draft {\n\t\treplyError(errors.New(\"message is not a draft\"), worker)\n\t\treturn\n\t}\n\tdmPayload.Toot, err = MarshalDM(m)\n\tif err != nil {\n\t\treplyError(err, worker)\n\t\treturn\n\t}\n\n\t// 2. give it back to mastodon worker and wait for response\n\tselect {\n\tcase worker <- dmPayload:\n\t\tselect {\n\t\tcase ack := <-dmPayload.Response:\n\t\t\tif ack.Err {\n\t\t\t\tclose(worker)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// 3. DM has been sent, saving state before ending process\n\t\t\tif err := b.SaveIndexSentDM(order, &ack); err != nil {\n\t\t\t\treplyError(err, worker)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tworker <- &DMpayload{}\n\t\t\tclose(worker)\n\t\tcase <-time.After(30 * time.Second):\n\t\t\tlog.Warn(\"[ProcessOutDM] 30 second timeout when waiting worker ack\")\n\t\t\tclose(worker)\n\t\t\treturn\n\t\t}\n\n\tcase <-time.After(30 * time.Second):\n\t\tlog.Warn(\"[ProcessOutDM] 30 second timeout when giving DM to worker\")\n\t\tclose(worker)\n\t\treturn\n\t}\n}\n\nfunc replyError(err error, worker chan *DMpayload) {\n\tdefer close(worker)\n\tdmPayload := &DMpayload{\n\t\tErr: err,\n\t}\n\tselect {\n\tcase worker <- dmPayload:\n\t\treturn\n\tcase <-time.After(3 * time.Second):\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "src/backend/brokers/go.twitter/broker.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage twitter_broker\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/CaliOpen/go-twitter/twitter\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/nats-io/go-nats\"\n)\n\ntype (\n\tTwitterBroker struct {\n\t\tConfig            BrokerConfig\n\t\tConnectors        TwitterBrokerConnectors\n\t\tIndex             backends.LDAIndex\n\t\tNatsConn          *nats.Conn\n\t\tNotifier          Notifications.Notifiers\n\t\tStore             backends.LDAStore\n\t\tnatsSubscriptions []*nats.Subscription\n\t}\n\n\tBrokerConfig struct {\n\t\tIndexConfig          IndexConfig `mapstructure:\"index_settings\"`\n\t\tIndexName            string      `mapstructure:\"index_name\"`\n\t\tNatsQueue            string      `mapstructure:\"nats_queue\"`\n\t\tNatsURL              string      `mapstructure:\"nats_url\"`\n\t\tNatsTopicPoller      string      `mapstructure:\"nats_topic_poller\"`\n\t\tNatsTopicPollerCache string      `mapstructure:\"nats_topic_poller_cache\"`\n\t\tNatsTopicDMs         string      `mapstructure:\"nats_topic_direct_message\"`\n\t\tStoreConfig          StoreConfig `mapstructure:\"store_settings\"`\n\t\tStoreName            string      `mapstructure:\"store_name\"`\n\t\tLDAConfig            LDAConfig   `mapstructure:\"LDAConfig\"`\n\t}\n\n\tTwitterBrokerConnectors struct {\n\t\tEgress chan NatsCom\n\t\tHalt   chan struct{}\n\t}\n\n\tDMpayload struct {\n\t\tDM       *twitter.DirectMessageEvent\n\t\tErr      error\n\t\tResponse chan TwitterDeliveryAck\n\t}\n\n\t// TwitterAck embeds response from Twitter API to pass back to broker.\n\tTwitterDeliveryAck struct {\n\t\tPayload  *twitter.DirectMessageEventsCreateResponse `json:\"-\"`\n\t\tErr      bool                                       `json:\"error\"`\n\t\tResponse string                                     `json:\"message,omitempty\"`\n\t}\n\n\t// natsCom is used to communicate between nats handler and broker\n\tNatsCom struct {\n\t\tOrder BrokerOrder\n\t\tAck   chan *DeliveryAck\n\t}\n)\n\nfunc Initialize(conf BrokerConfig, store backends.LDAStore, index backends.LDAIndex, natsConn *nats.Conn, notifier *Notifications.Notifier) (broker *TwitterBroker, err error) {\n\tbroker = new(TwitterBroker)\n\tbroker.Config = conf\n\tbroker.Store = store\n\tbroker.Index = index\n\tbroker.NatsConn = natsConn\n\tbroker.Notifier = notifier\n\tbroker.Connectors = TwitterBrokerConnectors{\n\t\tEgress: make(chan NatsCom, 5),\n\t\tHalt:   make(chan struct{}),\n\t}\n\treturn\n}\n\nfunc (broker *TwitterBroker) ShutDown() {\n\tbroker.NatsConn.Close()\n\tbroker.Store.Close()\n\tbroker.Index.Close()\n\tclose(broker.Connectors.Egress)\n\tclose(broker.Connectors.Halt)\n\tlog.WithField(\"TwitterBroker\", \"shutdown\").Info()\n}\n"
  },
  {
    "path": "src/backend/brokers/go.twitter/direct_messages.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage twitter_broker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/go-twitter/twitter\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"time\"\n)\n\nconst (\n\tDirectMessageType = \"message_create\"\n)\n\n// SaveRawDM marshal DM to json and save it as a raw message object in store\nfunc (broker *TwitterBroker) SaveRawDM(dm *twitter.DirectMessageEvent, userId UUID) (rawMessageId UUID, err error) {\n\n\tjsonDM, e := json.Marshal(dm)\n\tif e != nil {\n\t\terr = fmt.Errorf(\"[Twitter Broker]SaveRawDM failed to marshal dm to json : %s\", e)\n\t\treturn\n\t}\n\n\trawMsg := RawMessage{\n\t\tRaw_msg_id: UUID(uuid.NewV4()),\n\t\tRaw_Size:   uint64(len(jsonDM)),\n\t\tRaw_data:   string(jsonDM),\n\t\tDelivered:  false,\n\t}\n\n\te = broker.Store.StoreRawMessage(rawMsg)\n\tif e != nil {\n\t\terr = fmt.Errorf(\"[Twitter Broker]SaveRawDM failed to store raw message in store : %s\", e)\n\t\treturn\n\t}\n\n\treturn rawMsg.Raw_msg_id, nil\n}\n\n\n\nfunc (b *TwitterBroker) SaveIndexSentDM(initialOrder BrokerOrder, ack *TwitterDeliveryAck) error {\n\t// save raw email in db\n\trawMsgId, err := b.SaveRawDM(&ack.Payload.Event, UUID(uuid.FromStringOrNil(initialOrder.UserId)))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// TODO: handle attachments\n\n\t// Retrieve user informations\n\tuser, err := b.Store.RetrieveUser(initialOrder.UserId)\n\tif err != nil {\n\t\treturn err\n\t}\n\tuserInfo := &UserInfo{User_id: user.UserId.String(), Shard_id: user.ShardId}\n\n\t// update caliopen message status\n\tmessage, err := b.Store.RetrieveMessage(initialOrder.UserId, initialOrder.MessageId)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfields := make(map[string]interface{})\n\tvar date time.Time\n\tdate, err = ack.Payload.Event.CreatedAtTime()\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[SaveIndexSentDM] failed to parse date, using time.Now()\")\n\t\tdate = time.Now()\n\t\terr = nil\n\t}\n\tmessage.Raw_msg_id = rawMsgId\n\tfields[\"Raw_msg_id\"] = message.Raw_msg_id\n\tmessage.Is_draft = false\n\tfields[\"Is_draft\"] = message.Is_draft\n\tmessage.Date = date\n\tfields[\"Date\"] = message.Date\n\tmessage.Date_sort = date\n\tfields[\"Date_sort\"] = message.Date_sort\n\t//fields[\"Attachments\"] = TODO\n\tmessage.External_references = ExternalReferences{\n\t\tMessage_id: ack.Payload.Event.ID,\n\t}\n\tfields[\"External_references\"] = message.External_references\n\n\terr = b.Store.UpdateMessage(message, fields)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[SaveIndexSentDM] Store.UpdateMessage operation failed\")\n\t\treturn err\n\t}\n\n\terr = b.Index.UpdateMessage(userInfo, message, fields)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[SaveIndexSentDM] Index.UpdateMessage operation failed\")\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// UnmarshalDM creates a new Caliopen Message entity from a twitter1.1's DM event\nfunc UnmarshalDM(dm *twitter.DirectMessageEvent, userId UUID) (message *Message, err error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\n// MarshalDM builds a Twitter direct message from a Caliopen message\nfunc MarshalDM(msg *Message) (dm *twitter.DirectMessageEvent, err error) {\n\n\tdm = &twitter.DirectMessageEvent{\n\t\tType: DirectMessageType,\n\t\tMessage: &twitter.DirectMessageEventMessage{\n\t\t\tTarget: twitter.DMEventMessageTarget{\n\t\t\t\tRecipientScreenName: msg.Participants[0].Address, //TODO : handle multiple participants\n\t\t\t},\n\t\t\tData: twitter.DMEventMessageData{\n\t\t\t\tText: msg.Body_plain, // TODO : check body length to conform to Twitter API\n\t\t\t},\n\t\t},\n\t}\n\treturn\n}\n"
  },
  {
    "path": "src/backend/brokers/go.twitter/doc.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// package twitter_broker is a bridge between Twitter message model and Caliopen message model\n// inbound : it unmarshal direct messages from Twitter into Caliopen's Message struct, stores it and triggers relevant actions via NATS message queue\n// outbound : it listens to NATS message orders to retrieve a Caliopen Message and converts it to direct message ready to be sent through Twitter API\n\npackage twitter_broker\n\n"
  },
  {
    "path": "src/backend/brokers/go.twitter/inbound.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage twitter_broker\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/CaliOpen/go-twitter/twitter\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/satori/go.uuid\"\n\t\"time\"\n)\n\nconst (\n\tnatsMessageTmpl  = \"{\\\"order\\\":\\\"%s\\\",\\\"user_id\\\":\\\"%s\\\",\\\"identity_id\\\":\\\"%s\\\",\\\"message_id\\\": \\\"%s\\\"}\"\n\tnatsOrderRaw     = \"process_raw\"\n\tNatsError        = \"nats error\"\n\tlastSeenInfosKey = \"lastseendm\"\n\tlastSyncInfosKey = \"lastsync\"\n)\n\n// ProcessInDM is in charge of saving raw DM before further processing (could be unmarshalled too)\n// it ends by\n//      sending natsOrderRaw for other stack components\n//      sending new message notification if everything went good\n//      updating remote identity state in db\nfunc (broker *TwitterBroker) ProcessInDM(userID, remoteID UUID, dm *twitter.DirectMessageEvent, rawOnly bool, batch *Notifications.BatchNotification) error {\n\n\trawID, err := broker.SaveRawDM(dm, userID)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// send process order to nats\n\tnatsMessage := fmt.Sprintf(natsMessageTmpl, natsOrderRaw, userID.String(), remoteID.String(), rawID.String())\n\tresp, err := broker.NatsConn.Request(broker.Config.LDAConfig.InTopic, []byte(natsMessage), 10*time.Second)\n\tif err != nil {\n\t\tif broker.NatsConn.LastError() != nil {\n\t\t\tlog.WithError(broker.NatsConn.LastError()).Warnf(\"[TwitterBroker] failed to publish inbound request on NATS for user %s. Raw message has been saved with id %s\", userID.String(), rawID.String())\n\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\t} else {\n\t\t\tlog.WithError(err).Warnf(\"[TwitterBroker] failed to publish inbound request on NATS for user %s. Raw message has been saved with id %s\", userID.String(), rawID.String())\n\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\t}\n\t\treturn errors.New(NatsError)\n\t}\n\t// handle nats response\n\tnats_ack := new(map[string]interface{})\n\terr = json.Unmarshal(resp.Data, &nats_ack)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\treturn errors.New(\"[ProcessInDM] failed to parse inbound ack on NATS\")\n\t}\n\tif err, ok := (*nats_ack)[\"error\"]; ok {\n\t\tif err == DuplicateMessage {\n\t\t\treturn nil\n\t\t}\n\t\tlog.WithError(errors.New(err.(string))).Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\treturn errors.New(\"[ProcessInDM] inbound delivery failed\")\n\t}\n\t// nats delivery OK, notify user\n\tnotif := Notification{\n\t\tEmitter: \"twitterBroker\",\n\t\tType:    NewMessageNotif,\n\t\tTTLcode: LongLived,\n\t\tUser: &User{\n\t\t\tUserId: userID,\n\t\t},\n\t\tNotifId: UUID(uuid.NewV1()),\n\t\tBody:    `{\"message_id\": \"` + (*nats_ack)[\"message_id\"].(string) + `\", \"discussion_id\":\"` + (*nats_ack)[\"discussion_id\"].(string) + `\"}`,\n\t}\n\tif batch != nil {\n\t\tbatch.Add(notif)\n\t} else {\n\t\tgo broker.Notifier.ByNotifQueue(&notif)\n\t}\n\t// update raw_message table to set raw_message.delivered=true\n\tgo broker.Store.SetDeliveredStatus(rawID.String(), true)\n\treturn nil\n\n}\n"
  },
  {
    "path": "src/backend/brokers/go.twitter/outbound.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage twitter_broker\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"time\"\n)\n\n// retrieves a caliopen message from db, builds a DM from it\n// gives back DM to worker for it delivers it through twitter API\n// waits for worker ack and finishes saving new state.\nfunc (b *TwitterBroker) ProcessOutDM(order BrokerOrder, worker chan *DMpayload) {\n\n\tdmPayload := &DMpayload{\n\t\tResponse: make(chan TwitterDeliveryAck),\n\t}\n\n\t// 1. build DM\n\tm, err := b.Store.RetrieveMessage(order.UserId, order.MessageId)\n\tif err != nil {\n\t\treplyError(err, worker)\n\t\treturn\n\t}\n\tif m == nil {\n\t\treplyError(errors.New(\"message from db is empty\"), worker)\n\t\treturn\n\t}\n\tif !m.Is_draft {\n\t\treplyError(errors.New(\"message is not a draft\"), worker)\n\t\treturn\n\t}\n\tdmPayload.DM, err = MarshalDM(m)\n\tif err != nil {\n\t\treplyError(err, worker)\n\t\treturn\n\t}\n\tif dmPayload.DM.Message.Target.RecipientScreenName == \"\" {\n\t\treplyError(errors.New(\"missing recipient\"), worker)\n\t\treturn\n\t}\n\n\t// 2. give it back to twitter worker and wait for response\n\tselect {\n\tcase worker <- dmPayload:\n\t\tselect {\n\t\tcase ack := <-dmPayload.Response:\n\t\t\tif ack.Err {\n\t\t\t\tclose(worker)\n\t\t\t\treturn\n\t\t\t}\n\t\t// 3. DM has been sent, saving state before ending process\n\t\t\tif err := b.SaveIndexSentDM(order, &ack); err != nil {\n\t\t\t\treplyError(err, worker)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tworker <- &DMpayload{}\n\t\t\tclose(worker)\n\t\tcase <-time.After(30 * time.Second):\n\t\t\tlog.Warn(\"[ProcessOutDM] 30 second timeout when waiting worker ack\")\n\t\t\tclose(worker)\n\t\t\treturn\n\t\t}\n\n\tcase <-time.After(30 * time.Second):\n\t\tlog.Warn(\"[ProcessOutDM] 30 second timeout when giving DM to worker\")\n\t\tclose(worker)\n\t\treturn\n\t}\n}\n\nfunc replyError(err error, worker chan *DMpayload) {\n\tdefer close(worker)\n\tdmPayload := &DMpayload{\n\t\tErr: err,\n\t}\n\tselect {\n\tcase worker <- dmPayload:\n\t\treturn\n\tcase <-time.After(3 * time.Second):\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "src/backend/components/py.data/CHANGES.rst",
    "content": "0.0.1\n-----\n\n-  Initial version\n\n"
  },
  {
    "path": "src/backend/components/py.data/README.rst",
    "content": "Caliopen data package\n=====================\n\nThis package contain base structure to build caliopen DataProvider object.\n\nThis kind of object must implement a given interface\n\nExamples\n--------\n\nFile provider\n~~~~~~~~~~~~~\n\n```\nfrom caliopen_data import FileDataProvider\n\nclass CaliopenFile(FileDataProvider):\n\n    def _format_item(self, item):\n        return item.upper()\n\nf = CaliopenFile({})\nf.prepare('README.md')\n\nfor item in f.next():\n    print(item)\n```\n\nElasticsearch provider\n~~~~~~~~~~~~~~~~~~~~~~\n\n```\nfrom caliopen_storage.config import Configuration\nfrom caliopen_data import ESProvider\nimport elasticsearch_dsl as dsl\n\n\nclass ESQuery(ESProvider):\n\n    def _format_item(self, item):\n        return [\",\".join(x['label'] for x in item.participants)]\n\n\nprovider = ESQuery(Configuration('global').configuration)\n\n\nquery = dsl.Search().filter('term', body_plain=\"think\")\n\nprovider.prepare(query, index=None, doc_type='indexed_message')\nfor item in provider.next():\n    print(item)\n```\n"
  },
  {
    "path": "src/backend/components/py.data/caliopen_data/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\nfrom .provider import DataProvider, FileDataProvider, ESProvider\nfrom .interface import IDataProvider\nfrom .store import save_file\n\n__version__ = '0.23.0'\n\n\n__all__ = ['IDataProvider', 'DataProvider',\n           'FileDataProvider', 'ESProvider',\n           'save_file']\n"
  },
  {
    "path": "src/backend/components/py.data/caliopen_data/interface.py",
    "content": "\"\"\"Data provider interfaces for dataset processing.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nimport zope.interface\n\n\nlog = logging.getLogger(__name__)\n\n\nclass IDataProvider(zope.interface.Interface):\n    \"\"\"Base class to be a data provider for ML processing.\"\"\"\n\n    def _connect_store(self):\n        raise NotImplementedError\n\n    def _format_item(self, item):\n        \"\"\"Format a result item from store into processing format.\"\"\"\n        raise NotImplementedError\n\n    def _prepare(self, query, **kwargs):\n        raise NotImplementedError\n\n    def _execute(self, **kwargs):\n        raise NotImplementedError\n\n    def next(self):\n        \"\"\"Iterator method to retrieve results from provider.\"\"\"\n        raise NotImplementedError\n"
  },
  {
    "path": "src/backend/components/py.data/caliopen_data/provider.py",
    "content": "\"\"\"Data provider class for dataset processing.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport os\nfrom io import open\n\nimport zope.interface\nfrom elasticsearch import Elasticsearch\n\nfrom .interface import IDataProvider\n\n\nclass DataProvider(object):\n    \"\"\"Class to interact with a data provider interface.\"\"\"\n\n    def __init__(self, config):\n        \"\"\"Initialize a data provider, connecting it to store.\"\"\"\n        self.config = config\n        self._store = self._connect_store()\n        self._search = None\n        self.iterator = None\n\n    def prepare(self, query, **kwargs):\n        \"\"\"Prepare the query to be iterated.\"\"\"\n        self._search = self._prepare(query, **kwargs)\n        self.iterator = self._execute(**kwargs)\n\n    def next(self):\n        \"\"\"Iterator method over search results.\"\"\"\n        if not self.iterator:\n            raise StopIteration\n        for item in self.iterator:\n            yield self._format_item(item)\n\n\nclass FileDataProvider(DataProvider):\n    \"\"\"Data provider reading from a file.\"\"\"\n\n    zope.interface.implements(IDataProvider)\n\n    def __init__(self, config):\n        \"\"\"Create a new FileDataProvider.\"\"\"\n        super(FileDataProvider, self).__init__(config)\n        self._filename = None\n\n    def _connect_store(self):\n        pass\n\n    def _prepare(self, filename, **kwargs):\n        if not os.path.isfile(filename):\n            raise ValueError('No such file: {0}'.format(filename))\n        self._filename = filename\n\n    def _execute(self):\n        if not self._filename:\n            raise ValueError('No prepared file')\n        with open(self._filename, 'r', encoding=\"utf-8\") as f:\n            return f.read().split('\\n')\n\n\nclass ESProvider(DataProvider):\n    \"\"\"Data provider reading from an elasticsearch query.\"\"\"\n\n    zope.interface.implements(IDataProvider)\n\n    def _connect_store(self):\n        config = self.config.get('elasticsearch')\n        return Elasticsearch(config['url'])\n\n    def _prepare(self, query, index=None, doc_type=None, **kwargs):\n        self.query = query\n        self.query = self.query.using(self._store).index(index). \\\n            doc_type(doc_type)\n\n    def _execute(self, **kwargs):\n        return self.query.scan()\n"
  },
  {
    "path": "src/backend/components/py.data/caliopen_data/store.py",
    "content": "\"\"\"Methods to interact with data store for file related to data analyse.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nfrom minio import Minio\nfrom minio.error import BucketAlreadyOwnedByYou, BucketAlreadyExists\n\nlog = logging.getLogger(__name__)\n\n\ndef save_file(config, input_file, dest):\n    \"\"\"Save an input file as dest on object store.\"\"\"\n    conf = config['object_store']\n    bucket = conf['buckets']['learn_models']\n    client = Minio(conf[\"endpoint\"],\n                   access_key=conf[\"access_key\"],\n                   secret_key=conf[\"secret_key\"],\n                   secure=False)\n    try:\n        client.make_bucket(bucket, conf['location'])\n    except BucketAlreadyOwnedByYou:\n        pass\n    except BucketAlreadyExists:\n        pass\n    except Exception as exc:\n        raise exc\n    try:\n        resp = client.fput_object(bucket, dest, input_file)\n        log.info('Put file {0}: {1}'.format(input_file, resp))\n    except Exception as exc:\n        log.info('Unable to save file in object store %r' % exc)\n        raise exc\n"
  },
  {
    "path": "src/backend/components/py.data/requirements.deps",
    "content": ""
  },
  {
    "path": "src/backend/components/py.data/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliopen\nwith-coverage = 1\ncover-erase = 1\n"
  },
  {
    "path": "src/backend/components/py.data/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_data\"\n\ninit_file = os.path.join(*([here] + name.split('.') + ['__init__.py']))\nwith open(init_file) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S). \\\n        match(v_file.read()).group(1)\n\nrequires = [\n    'zope.interface',\n    'elasticsearch>=5.0.0,<6.0.0'\n]\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\n\nextras_require = {\n    'dev': [],\n    'test': [\n        'coverage',\n        'freezegun',\n        'nose'\n    ],\n}\n\nsetup(name=name,\n      version=version,\n      description='Caliopen package to provide data for compute, learning, ..',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\"Programming Language :: Python\", ],\n      author='Caliopen contributors',\n      author_email='contact@caliopen.org',\n      url='https://caliopen.org',\n      license='AGPLv3',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      extras_require=extras_require,\n      install_requires=requires,\n      tests_require=requires,\n      )\n"
  },
  {
    "path": "src/backend/components/py.pgp/CHANGES.rst",
    "content": "0.0.1\n-----\n\n-  Initial version\n"
  },
  {
    "path": "src/backend/components/py.pgp/MANIFEST.in",
    "content": "include *.txt *.ini *.cfg *.rst\nrecursive-include caliop *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml\n"
  },
  {
    "path": "src/backend/components/py.pgp/README.rst",
    "content": "caliopen.pgp\n==================\n\nPGP utilities for Caliopen backend\n\n"
  },
  {
    "path": "src/backend/components/py.pgp/caliopen_pgp/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n__version__ = '0.23.0'\n\ntry:\n    import pkg_resources\n    pkg_resources.declare_namespace(__name__)\nexcept ImportError:\n    import pkgutil\n    __path__ = pkgutil.extend_path(__path__, __name__)\n"
  },
  {
    "path": "src/backend/components/py.pgp/caliopen_pgp/keys/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nPGP public keys management\n\"\"\"\n\nfrom __future__ import absolute_import, unicode_literals\n\nfrom .rfc7929 import DNSDiscovery\nfrom .hkp import HKPDiscovery\nfrom .keybase import KeybaseDiscovery\nfrom .base import PGPPublicKey, PGPUserId, DiscoveryResult\nfrom .discoverer import PublicKeyDiscoverer\nfrom .contact import ContactPublicKeyManager\n\n__all__ = ['DNSDiscovery', 'HKPDiscovery', 'KeybaseDiscovery',\n           'PGPPublicKey', 'PGPUserId', 'DiscoveryResult',\n           'PublicKeyDiscoverer', 'ContactPublicKeyManager']\n"
  },
  {
    "path": "src/backend/components/py.pgp/caliopen_pgp/keys/base.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nPGP public keys structures\n\"\"\"\nfrom __future__ import absolute_import, unicode_literals\nimport logging\n\nimport pgpy\n\nlog = logging.getLogger(__name__)\n\n\nclass PGPUserId(object):\n    \"\"\"PGP UserId related to a key.\"\"\"\n\n    def __init__(self, name, email, is_primary, comment=None, signers=None):\n        self.name = name\n        self.email = email\n        self.is_primary = is_primary\n        self.comment = comment\n        self.signers = signers if signers else set()\n\n\nclass PGPPublicKey(object):\n    \"\"\"PGP public key representation.\"\"\"\n\n    _map_algos = {\n        'DSA': 'dsa',\n        'ECDH': 'ecdh',\n        'ECDSA': 'ecdsa',\n        'ElGamal': 'elgamal',\n        'FormerlyElGamalEncryptOrSign': 'elgamal',\n        'Invalid': 'unknown',\n        'RSAEncrypt': 'rsa',\n        'RSAEncryptOrSign': 'rsa',\n        'RSASign': 'rsa',\n    }\n\n    def __init__(self, keyid, public_key):\n        assert public_key.is_public, 'Not a public PGP key'\n        self.id = keyid\n        self._pgpkey = public_key\n        self.version = public_key._key.header.version\n        self.created = public_key.created\n        self.expire_date = public_key.expires_at\n        self.is_expired = public_key.is_expired\n        self.keyid = public_key.fingerprint.keyid\n        self.fingerprint = public_key.fingerprint.replace(' ', '')\n        self.algorithm = self._map_algos.get(public_key.key_algorithm.name,\n                                             'unknown')\n\n        self.userids = self._get_userids(public_key.userids)\n        if public_key.subkeys:\n            self.subkeys = [PGPPublicKey(k, v)\n                            for k, v in public_key.subkeys.items()]\n        else:\n            self.subkeys = []\n        if self.algorithm == 'rsa':\n            self.size = public_key._key.keymaterial.n.bit_length()\n            self.e = public_key._key.keymaterial.e\n        else:\n            self.size = 0\n            self.e = 0\n\n    def _get_userids(self, ids):\n        \"\"\"Return list of ``PgpUserId``.\"\"\"\n        return [PGPUserId(u.name, u.email, u.is_primary, u.comment, u.signers)\n                for u in ids]\n\n    @property\n    def armored_key(self):\n        \"\"\"Return the armored key.\"\"\"\n        return str(self._pgpkey)\n\n\nclass DiscoveryResult(object):\n    \"\"\"Class to produce discovered public keys and extra informations.\"\"\"\n\n    def __init__(self, keys, extra_identities=None):\n        self.keys = keys\n        self.identities = extra_identities if extra_identities else []\n\n    @property\n    def emails(self):\n        \"\"\"Return distinct list of emails found in keys.\"\"\"\n        found_emails = []\n        for key in self.keys:\n            for userid in key.userids:\n                if userid.email not in found_emails and userid.is_uid:\n                    found_emails.append(userid.email)\n                    yield userid\n\n\nclass BaseDiscovery(object):\n    \"\"\"Base class for discovery and public key parsing logic.\"\"\"\n\n    @property\n    def empty_result(self):\n        \"\"\"No result found.\"\"\"\n        return DiscoveryResult([])\n\n    def lookup_identity(self, identity, type_):\n        \"\"\"Method to be implemented by sub class for discovery.\"\"\"\n        raise NotImplementedError\n\n    def _parse_key(self, armored):\n        pgpkey = pgpy.PGPKey()\n        try:\n            parse_result = pgpkey.parse(armored)\n            log.debug('Parse key result {}'.format(parse_result))\n            result_keys = []\n            for keyid, parsedkey in parse_result.items():\n                        result_keys.append(PGPPublicKey(keyid[0],\n                                                        parsedkey))\n            return result_keys\n        except Exception as exc:\n            log.error('Parse key failed {}'.format(exc))\n            return []\n"
  },
  {
    "path": "src/backend/components/py.pgp/caliopen_pgp/keys/contact.py",
    "content": "\"\"\"Caliopen contact public keys logic.\"\"\"\n\nimport logging\n\n\nfrom caliopen_pgp.keys import PublicKeyDiscoverer\nfrom caliopen_storage.config import Configuration\nfrom caliopen_main.common.core import PublicKey\n\nlog = logging.getLogger(__name__)\n\n\nclass ContactDiscoveryResult(object):\n    \"\"\"Store discovery results for a contact.\"\"\"\n\n    def __init__(self):\n        \"\"\"Instanciate a `ContactDiscoveryResult`.\"\"\"\n        self.keys = []\n        self.emails = []\n        self.identities = []\n\n\nclass ContactPublicKeyManager(object):\n    \"\"\"Manage contact public keys.\"\"\"\n\n    def __init__(self):\n        \"\"\"Instanciate a `ContactPublicKeyManager`.\"\"\"\n        conf = Configuration('global').configuration\n        self.discoverer = PublicKeyDiscoverer(conf)\n\n    def _find_keys(self, user, contact, new_identifier, type_):\n        \"\"\"Find keys related to a new identifier for a contact.\"\"\"\n        results = self.discoverer.lookup_identity(new_identifier, type_)\n        current_keys = PublicKey.find(user, contact.contact_id)\n        known_keys = [x.fingerprint for x in current_keys]\n        if results:\n            for result in results:\n                for key in result.keys:\n                    if key.fingerprint not in known_keys:\n                        log.info('Found new key {0}'.format(key.fingerprint))\n                        yield result\n                    else:\n                        log.info('Known key {0}'.format(key.fingerprint))\n\n    def _filter_new_emails(self, contact, key):\n        \"\"\"Find new emails related to a contact public key.\"\"\"\n        known_emails = [x.address for x in contact.emails]\n        for userid in key.userids:\n            if userid.email not in known_emails:\n                known_emails.append(userid.email)\n                yield userid\n\n    def _filter_new_identities(self, contact, identities):\n        \"\"\"Create new identities found for a contact.\"\"\"\n        known_ids = [(x.type, x.name) for x in contact.identities]\n        for identity in identities:\n            if identity not in known_ids:\n                yield identity\n\n    def process_identity(self, user, contact, new_identifier, type_):\n        \"\"\"Process a contact new identity.\"\"\"\n        result = ContactDiscoveryResult()\n        discovers = self._find_keys(user, contact, new_identifier, type_)\n        found_emails = []\n        found_ids = []\n        for disco in discovers:\n            for key in disco.keys:\n                result.keys.append(key)\n                found_emails.extend(self._filter_new_emails(contact, key))\n            if disco.identities:\n                ids = self._filter_new_identities(contact, disco.identities)\n                found_ids.extend(ids)\n        for email in found_emails:\n            if email.email not in [x.email for x in result.emails]:\n                result.emails.append(email)\n        for identity in found_ids:\n            if identity not in result.identities:\n                result.identities.append(identity)\n        return result\n"
  },
  {
    "path": "src/backend/components/py.pgp/caliopen_pgp/keys/discoverer.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Manager for discovery logic.\"\"\"\n\nfrom __future__ import absolute_import, unicode_literals\nimport logging\n\nfrom .keybase import KeybaseDiscovery\nfrom .rfc7929 import DNSDiscovery\nfrom .hkp import HKPDiscovery\n\nlog = logging.getLogger(__name__)\n\nlog.setLevel(logging.DEBUG)\n\n\nclass PublicKeyDiscoverer(object):\n    \"\"\"Discover of public keys for a contact information.\"\"\"\n\n    discoverers = {}\n\n    def __init__(self, conf):\n        params = conf.get('key_discovery', {}).get('dns', {})\n        if params.get('enable'):\n            disco = DNSDiscovery(params)\n            self.discoverers['dns'] = disco\n        params = conf.get('key_discovery', {}).get('keybase', {})\n        if params.get('enable'):\n            disco = KeybaseDiscovery(params)\n            self.discoverers['keybase'] = disco\n        params = conf.get('key_discovery', {}).get('hkp', {})\n        if params.get('enable'):\n            disco = HKPDiscovery(params)\n            self.discoverers['hkp'] = disco\n\n    def lookup_identity(self, identity, type_):\n        \"\"\"Search for public key for an identifier and a protocol type.\"\"\"\n        results = []\n        for disco in self.discoverers:\n            if type_ in self.discoverers[disco]._types:\n                discoverer = self.discoverers[disco]\n                try:\n                    result = discoverer.lookup_identity(identity, type_)\n                    if result.keys:\n                        results.append(result)\n                except Exception as exc:\n                    log.error('Exception during key lookup using {0} '\n                              'for identifier {1}: {2}'.format(disco,\n                                                               identity,\n                                                               exc))\n        return results\n"
  },
  {
    "path": "src/backend/components/py.pgp/caliopen_pgp/keys/hkp.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"HKP pgp key protocol dead simple implementation.\n\nhttps://tools.ietf.org/html/draft-shaw-openpgp-hkp-00\n\n\"\"\"\nfrom __future__ import absolute_import, unicode_literals\n\nfrom datetime import datetime\nimport urllib\nimport logging\n\nimport requests\n\nfrom .base import BaseDiscovery, DiscoveryResult\n\nlog = logging.getLogger(__name__)\n\n\ndef check_version(record):\n    # TODO complete\n    assert len(record) > 1\n\n\ndef parse_date(date):\n    return datetime.fromtimestamp(float(date)) if date else None\n\n\ndef parse_pubkey_record(record):\n    pub, keyid, algo, keylen, create, expire, flags = record.split(':', 7)\n    return keyid\n\n\ndef parse_uid_record(record):\n    uid, value, create, expire, flags = record.split(':')\n\n\ndef parse_search_result(lines):\n    assert len(lines) > 2, 'Not enough lines'\n    keyids = []\n    for i in range(0, len(lines)):\n        if i == 0:\n            check_version(lines[0])\n        elif i == 1:\n            if not lines[i].startswith('pub:'):\n                raise Exception('Not a public key record: {}'.format(lines[i]))\n        if lines[i].startswith('pub:'):\n            key = parse_pubkey_record(lines[i])\n            keyids.append(key)\n    return keyids\n\n\nclass HKPDiscovery(BaseDiscovery):\n\n    DEFAULT_HKP_URL = 'https://pgp.mit.edu/pks/lookup'\n    DEFAULT_TIMEOUT = 10\n\n    _types = ['email']\n\n    def __init__(self, conf):\n        self.url = conf.get('url', self.DEFAULT_HKP_URL)\n        self.timeout = conf.get('timeout', self.DEFAULT_TIMEOUT)\n\n    def lookup_identity(self, identity, type_):\n        \"\"\"Find pgp keys by user email.\"\"\"\n        pub_keys = self._search_keys(identity)\n        result_keys = []\n        for key in pub_keys:\n            asc_key = self._search_key(key)\n            if not asc_key:\n                log.warn('Key {} not found on HKP server'.format(key))\n            else:\n                txt_key = asc_key.encode('utf-8').rstrip()\n                result_keys.extend(self._parse_key(txt_key))\n        return DiscoveryResult(result_keys)\n\n    def _search_keys(self, email):\n        encoded = urllib.quote(email)\n        url = '{}?search={}&op=index&options=mr'.format(self.url, encoded)\n        res = requests.get(url, timeout=self.timeout)\n        if res.status_code != 200:\n            log.error('HKP discover status {} for {}'.\n                      format(res.status_code, email))\n            return []\n        try:\n            search_keys = parse_search_result(res.text.split('\\n'))\n            return search_keys\n        except AssertionError as exc:\n            log.error('Parse result failed during HKP search for {}: {}'.\n                      format(email, exc))\n        except Exception as exc:\n            log.error('Unhandled exception during HKP search for {}: {}'.\n                      format(email, exc))\n        return []\n\n    def _search_key(self, keyid):\n        url = '{}?search=0x{}&op=get&options=mr'.format(self.url, keyid)\n        res = requests.get(url, timeout=self.timeout)\n        if res.status_code != 200:\n            log.error('HKP discover status {} for {}'.\n                      format(res.status_code, keyid))\n            return None\n        return res.text\n"
  },
  {
    "path": "src/backend/components/py.pgp/caliopen_pgp/keys/keybase.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, unicode_literals\n\nimport logging\n\nimport requests\n\nfrom .base import BaseDiscovery, DiscoveryResult\n\nlog = logging.getLogger(__name__)\n\n\nclass KeybaseDiscovery(BaseDiscovery):\n    \"\"\"Discover pgp keys and others contact identities in keybase.\"\"\"\n\n    KEYBASE_DEFAULT_BASE_URL = 'https://keybase.io'\n    KEYBASE_DEFAULT_HEADERS = {'Content-Type': 'application/json'}\n    DEFAULT_TIMEOUT = 10\n\n    _types = ['twitter', 'github', 'facebook', 'coinbase', 'hackernews',\n              'reddit']\n\n    def __init__(self, conf):\n        self.base_url = conf.get('url', self.KEYBASE_DEFAULT_BASE_URL)\n        self.url = '{}/_/api/1.0'.format(self.base_url)\n        self.headers = conf.get('headers', self.KEYBASE_DEFAULT_HEADERS)\n        self.timeout = conf.get('timeout', self.DEFAULT_TIMEOUT)\n\n    def lookup_identity(self, identifier, type_):\n        \"\"\"Find by name and type.\"\"\"\n        if type_ not in self._types:\n            raise Exception('Invalid identity type {}'.format(type_))\n        users = []\n        find = self._fetch_identity(identifier, type_)\n        if find:\n            log.debug('Got keybase result : {}'.format(find))\n            users.extend(find)\n        keys = []\n        identities = []\n        for user in users:\n            user_key = self._get_public_key(user['username'])\n            if user_key:\n                keys.append(user_key)\n            for ident in self._types:\n                if user['remote_proofs'].get(ident) and type_ != ident:\n                    identities.append((ident, user['remote_proofs'][ident]))\n        public_keys = []\n        for key in keys:\n            public_keys.extend(self._parse_key(key))\n        return DiscoveryResult(public_keys, identities)\n\n    def _clean_name(self, name, type_):\n        \"\"\"Format a clean user name depending on type.\"\"\"\n        if type_ == 'twitter':\n            if name.startswith('@'):\n                return name.lstrip('@').lower()\n        return name.lower()\n\n    def _fetch_identity(self, name, type_):\n        \"\"\"Make a user discover API call on keybase for a name and type.\"\"\"\n        clean_name = self._clean_name(name, type_)\n        url = '{}/user/discover.json?{}={}'. \\\n              format(self.url, type_, clean_name)\n        log.debug('Will query keybase url {}'.format(url))\n        res = requests.get(url, headers=self.headers, timeout=self.timeout)\n        if res.status_code != 200:\n            log.error('Keybase discover status {} for {} on {}'.\n                      format(res.status_code, clean_name, type_))\n            return []\n        result = res.json()\n        if not result.get('matches'):\n            log.debug('No match for keybase discovery of {} {}'.\n                      format(clean_name, type_))\n            return []\n        matches = result['matches'].get(type_, [[]])\n        res = []\n        for match in matches:\n            res.extend(match)\n        return res\n\n    def _get_public_key(self, username):\n        \"\"\"Fetch a public key for an user.\"\"\"\n        url = '{}/{}/key.asc'.format(self.base_url, username)\n        log.debug('Will query keybase url {}'.format(url))\n        res = requests.get(url, headers=self.headers, timeout=self.timeout)\n        if res.status_code != 200:\n            log.error('Keybase user key fetch status {} for {}'.\n                      format(res.status_code, username))\n            return None\n        return res.text\n"
  },
  {
    "path": "src/backend/components/py.pgp/caliopen_pgp/keys/rfc7929.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nHelpers for key discovery in DNS.\n\nRelate to https://tools.ietf.org/html/rfc7929\n\"\"\"\n\nfrom __future__ import absolute_import, unicode_literals\n\nimport hashlib\nimport logging\n\nfrom dnsknife import resolver\n\nfrom .base import BaseDiscovery, DiscoveryResult\n\nfrom caliopen_main.common.helpers.normalize import clean_email_address\n\n\nlog = logging.getLogger(__name__)\n\n\ndef compute_qname(username, domain):\n    \"\"\"Compute qname value from an email (part 3 of RFC).\"\"\"\n    hash = hashlib.sha256()\n    hash.update(username.lower())\n    return '{}._openpgpkey.{}'.format(hash.hexdigest()[:56], domain.lower())\n\n\nclass DNSDiscovery(BaseDiscovery):\n    \"\"\"Class to discover OPENPGPKEY using Dns.\"\"\"\n\n    _types = ['email']\n\n    def __init__(self, conf):\n        self.default_name_server = conf.get('name_server')\n        self.resolve_timeout = conf.get('timeout', 5)\n\n    def lookup_identity(self, identity, type_):\n        \"\"\"Find for a given email an openpgp key in its DNS zone.\"\"\"\n        clean = clean_email_address(identity)\n        local_part, domain = clean[0].split('@', 2)\n        qname = compute_qname(local_part, domain)\n        ns = resolver.ns_for(domain)\n        if not ns:\n            log.warn('No nameservers found for domain {}'.format(domain))\n            return self.empty_result\n\n        # XXX use a random one\n        use_ns = ns[0]\n        resolv = resolver.Resolver(timeout=self.resolve_timeout)\n        # XXX use of dnssec validation\n        query = resolv.query_at(qname, 'OPENPGPKEY', use_ns)\n        response = query.get()\n        if not response:\n            log.warn('No response for OPENPGPKEY dns query')\n            return self.empty_result\n        rrsets = response.rrset\n        if not rrsets:\n            log.warn('No rrsets for OPENPGPKEY dns query')\n            return self.empty_result\n        # XXX many rrset, key and signature, must be considered\n        key = self._extract_key(rrsets[0])\n        keys = self._parse_key(key)\n        return DiscoveryResult(keys)\n\n    def _extract_key(self, record):\n        \"\"\"Extract an armored representation of key from dns record.\"\"\"\n        return record.data\n"
  },
  {
    "path": "src/backend/components/py.pgp/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliop\nwith-coverage = 1\ncover-erase = 1\n\n[compile_catalog]\ndirectory = caliop/locale\ndomain = caliop\nstatistics = true\n\n[extract_messages]\nadd_comments = TRANSLATORS:\noutput_file = caliop/locale/caliop.pot\nwidth = 80\n\n[init_catalog]\ndomain = caliop\ninput_file = caliop/locale/caliop.pot\noutput_dir = caliop/locale\n\n[update_catalog]\ndomain = caliop\ninput_file = caliop/locale/caliop.pot\noutput_dir = caliop/locale\nprevious = true\n"
  },
  {
    "path": "src/backend/components/py.pgp/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_pgp\"\n\nwith open(os.path.join(*([here] + name.split('.') + ['__init__.py']))) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S).match(v_file.read()).group(1)\n\nrequires = ['dnsknife==0.11', 'PGpy==0.4.3']\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\nsetup(name=name,\n      namespace_packages=[name],\n      version=version,\n      description='Caliopen PGP backend utilities',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\"Programming Language :: Python\"],\n      author='Caliopen Contributors',\n      author_email='',\n      url='https://github.com/CaliOpen/Caliopen',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      install_requires=requires,\n      tests_require=requires,\n      )\n"
  },
  {
    "path": "src/backend/components/py.pi/CHANGES.rst",
    "content": "0.0.1\n-----\n\n-  Initial version\n\n"
  },
  {
    "path": "src/backend/components/py.pi/README.rst",
    "content": "Caliopen PI package\n=============\n\nThis package contain all logic related to privacy index used in Caliopen platform\n\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n__version__ = '0.23.0'\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen privacy features namespace.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .mail import InboundMailFeature\nfrom .contact import ContactFeature\nfrom .device import DeviceFeature\nfrom .types import init_features, marshal_features, unmarshal_features\n\n\n__all__ = ['InboundMailFeature', 'ContactFeature', 'DeviceFeature',\n           'init_features', 'marshal_features', 'unmarshal_features']\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/contact.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact privacy features extraction methods.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom caliopen_main.pi.parameters import PIParameter\nfrom .helpers.histogram import ParticipantHistogram\nfrom .types import init_features, marshal_features\n\nlog = logging.getLogger(__name__)\n\nMESSAGE_TOTAL_THRESHOLD = 10\nNB_PROTOCOLS_THRESHOLD = 3\n\n\ndef pstdev(avg, data):\n    \"\"\"Population standard deviation.\"\"\"\n    ss = sum((x - avg) ** 2 for x in data)\n    return (ss / float(len(data))) ** 0.5\n\n\nclass ContactFeature(object):\n    \"\"\"Process a contact to extract its privacy features.\"\"\"\n\n    def __init__(self, user, conf=None):\n        self.user = user\n        self.conf = conf\n        self._features = init_features('contact')\n\n    def _compute_histogram(self, email):\n        \"\"\"Get an histogram for a contact email and compute basic infos.\"\"\"\n        histo = ParticipantHistogram(self.user, 'day')\n        data = histo.find_by_address(email)\n        log.info('Got email {0} histogram result {1}'.format(email, data))\n        if data:\n            values = [x[1] for x in data]\n            total = sum(values)\n            avg = total / float(len(values))\n            return (total, avg, pstdev(avg, values))\n        return (0.0, 0.0, 0.0)\n\n    def _get_histogram(self, contact):\n        \"\"\"Get privacy features related to message histograms.\"\"\"\n        histograms = [self._compute_histogram(x.address)\n                      for x in contact.emails]\n        if histograms:\n            best = max(histograms)\n            return {'message_day_total': best[0],\n                    'message_day_avg': best[1],\n                    'message_day_pstdev': best[2]}\n        return {}\n\n    def _get_technical(self, contact):\n        \"\"\"Get technical features for a contact.\"\"\"\n        features = {}\n        if hasattr(contact, 'public_keys') and contact.public_keys:\n            max_size = max([x.size for x in contact.public_keys])\n            if max_size:\n                features.update({'public_key_best_size': max_size})\n        return features\n\n    def _compute_pi(self, contact, features):\n        pi_t = 0\n        pi_cx = 0\n        pi_co = 0\n        if 'public_key_best_size' in features:\n            size = features['public_key_best_size']\n            if size < 2048:\n                pi_t += 15\n            elif size > 2048:\n                pi_t += 30\n            else:\n                pi_t += 20\n\n        if 'message_day_total' in features:\n            if features['message_day_total'] >= MESSAGE_TOTAL_THRESHOLD:\n                pi_cx += 10\n\n        if contact.addresses or contact.phones:\n            features.update({'address_or_phone': True})\n            pi_co += 10\n\n        nb_protocols = len(contact.emails) + len(contact.phones)\n        if nb_protocols >= NB_PROTOCOLS_THRESHOLD:\n            features.update({'nb_protocols': nb_protocols})\n            pi_co += 5\n\n        return PIParameter({'technic': pi_t,\n                            'comportment': pi_co,\n                            'context': pi_cx,\n                            'version': 0})\n\n    def process(self, contact):\n        \"\"\"Process a contact to extract all privacy features.\"\"\"\n        features = self._get_histogram(contact)\n        features.update(self._get_technical(contact))\n        log.info('Contact {0} have features {1}'.\n                 format(contact.contact_id, features))\n        pi = self._compute_pi(contact, features)\n        return pi, marshal_features(features)\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/device.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen device privacy features extraction methods.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport os\nimport logging\n\nfrom user_agents import parse as parse_ua\nimport geoip2.database as geoip\n\nfrom .types import init_features\n\nlog = logging.getLogger(__name__)\n\n\nclass Singleton(object):\n    \"\"\"Basic singleton class.\"\"\"\n\n    _instance = None\n\n    def __new__(cls, *args, **kwargs):\n        \"\"\"Create singleton instance of class.\"\"\"\n        if not isinstance(cls._instance, cls):\n            cls._instance = object.__new__(cls, *args, **kwargs)\n        return cls._instance\n\n\nclass GeoipReader(Singleton):\n    \"\"\"Singleton for a geoip reader.\"\"\"\n\n    def __init__(self, filename):\n        \"\"\"A singleton around geoip reader.\"\"\"\n        self.reader = geoip.Reader(filename)\n\n\nclass DeviceFeature(object):\n    \"\"\"Compute feature for a device.\"\"\"\n\n    def __init__(self, user, conf=None):\n        \"\"\"Instanciate a new qualifier for an user.\"\"\"\n        self.user = user\n        self.conf = conf\n        self._features = init_features('device')\n\n    def process(self, device):\n        \"\"\"Process a device to compute it's privacy features and PI.\"\"\"\n        features = {}\n        if device.user_agent:\n            ua = parse_ua(device.user_agent)\n            browser_version = ua.browser.version_string.lower()\n            features.update({'browser_family': ua.browser.family.lower(),\n                             'browser_version': browser_version,\n                             'os_family': ua.os.family.lower(),\n                             'os_version': ua.os.version_string.lower(),\n                             'device_family': ua.device.family.lower(),\n                             'device_type': self._get_device_type(ua)})\n        # XXX processing IP address to detect some informations\n        if device.ip_creation:\n            features.update(self._process_ip_address(device.ip_creation))\n        else:\n            log.warn('No ip address found for device')\n        return None, features\n\n    def _get_device_type(self, ua):\n        \"\"\"Return guessed type of user agent.\"\"\"\n        if ua.is_mobile:\n            return 'smartphone'\n        if ua.is_tablet:\n            return 'tablet'\n        if ua.is_pc:\n            return 'desktop'\n        return 'other'\n\n    def _process_ip_address(self, addr):\n        geoip_file = self.conf.get('qualifiers', {}).get('geoip', {}). \\\n            get('file')\n        if geoip_file and os.path.isfile(geoip_file):\n            try:\n                reader = GeoipReader(geoip_file).reader\n                resp = reader.country(addr)\n                return {'detected_country': resp.country.iso_code}\n            except Exception as exc:\n                log.exception('Error during geoip2 lookup %r' % exc)\n        else:\n            log.warn('Invalid geoip2 file configuration')\n        return {}\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/helpers/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/helpers/histogram.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen functions for histogram informations from index.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom dateutil.parser import parse as parse_date\nimport elasticsearch_dsl as dsl\n\nfrom caliopen_storage.helpers.connection import get_index_connection\n\n\nclass ParticipantHistogram(object):\n    \"\"\"Date histogram for a participant in user messages index.\"\"\"\n\n    def __init__(self, user, resolution='day'):\n        \"\"\"Instanciate a date histrogram for an user.\"\"\"\n        self.esclient = get_index_connection()\n        self.user = user\n        self.resolution = resolution\n\n    def _format_results(self, results):\n        \"\"\"Return a list of date, value tuples.\"\"\"\n        return [(parse_date(x['key_as_string']),\n                 x['doc_count']) for x in results]\n\n    def _do_query(self, value, term):\n        search = dsl.Search(using=self.esclient, index=self.user.shard_id,\n                            doc_type='indexed_message')\n        search.filter(user_id=self.user.user_id)\n        term_query = dsl.Q('term', **{term: value})\n        search = search.query('nested', path='participants',\n                              score_mode='avg',\n                              query=term_query)\n\n        search.aggs.bucket('messages_with_value', 'date_histogram',\n                           field='date', interval=self.resolution)\n\n        r = search.execute()\n        results = r.aggregations.messages_with_value['buckets']\n        return self._format_results(results)\n\n    def find_by_address(self, address):\n        \"\"\"Build a date histogram with a participant address.\"\"\"\n        return self._do_query(address, term='participants.address')\n\n    def find_by_contact_id(self, contact_id):\n        \"\"\"Build a date histogram with a know contact.\"\"\"\n        return self._do_query(contact_id, term='participants.contact_id')\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/helpers/importance_level.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Compute Caliopen message importance level.\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nlog = logging.getLogger(__name__)\n\n# Importance level is between MIN_VALUE and MAX_VALUE (inclusives)\nMIN_VALUE = -10\nMAX_VALUE = 10\nMAX_TAG_SCORE = 10  # Max importance level that can be set for a tag\n\nSPAM_RATIO = 0.5\n# Le PI contexte du message compte pour 1/8 des points positifs\nPI_CX_RATIO = 1.0 / 8.0\n# le PI comportement du message compte à lui seul pour 1/4 des points positifs\nPI_CO_RATIO = 1.0 / 4.0\n# Les éléments contextuels (tags associés, association du type de terminal\n# au protocole utilisé, horaire, langue...) comptent pour 1/8 des points\n# positifs.\nCONTEXT_RATIO = 1.0 / 8.0\n\nREPLY_VALUE = 2.0\n\n\ndef get_importance_tags(tags):\n    \"\"\"Get the max importance level from tags.\"\"\"\n    max_value = 0\n    for tag in tags:\n        if getattr(tag, 'importance_level'):\n            if tag.importance_level > max_value:\n                max_value = tag.importance_level\n    return max_value\n\n\ndef compute_importance(user, message, features, participants):\n    \"\"\"Compute importance level for an inbound message.\"\"\"\n    positive = 0\n    negative = 0\n    scores = []\n    # Spam level\n    if features.get('is_spam'):\n        spam_score = features.get('spam_score', 0) / 100.0\n        negative -= spam_score * abs(MIN_VALUE * SPAM_RATIO)\n        scores.append(('spam_score', spam_score))\n    # PI message\n    if message.pi.context:\n        context_score = message.pi.context / 100.0\n        positive += context_score * PI_CX_RATIO * MAX_VALUE\n        scores.append(('context_score', context_score))\n    if message.pi.comportment:\n        comportment_score = message.pi.comportment / 100.0\n        positive += comportment_score * PI_CO_RATIO * MAX_VALUE\n        scores.append(('comportment_score', comportment_score))\n    # Tags\n    if message.tags:\n        tag_score = get_importance_tags(message.tags)\n        positive += tag_score / MAX_TAG_SCORE * CONTEXT_RATIO\n        scores.append(('tag_score', tag_score))\n\n    # It's a reply ?\n    if message.external_references:\n        # XXX find if we know related messages to increment level\n        positive += REPLY_VALUE\n        scores.append(('reply_score', REPLY_VALUE))\n\n    log.info('Importance scores: {0}'.format(scores))\n    # Return the final value\n    if negative:\n        return round(max(MIN_VALUE, negative))\n    return round(min(MAX_VALUE, positive))\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/helpers/ingress_path.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nA kind of algorithm to parse SMTP Received headers.\n\nExtract some privacy features about an eventual detected ingress SMTP server\nand number of external hops from an SMTP relay path.\n\nIt's far from perfect as so many format and practices exists around Received\nheaders, and off course also a relative trust about them.\n\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport re\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\ndef normalize_ssocket_infos(socket_info, cipher):\n    \"\"\"Normalize tls information from a Received header.\"\"\"\n    if socket_info:\n        tls_version = socket_info.replace('_', '.').lower()\n    else:\n        tls_version = ''\n    return tls_version, cipher\n\n\ndef get_ingress_features(headers, internal_domains=None):\n    \"\"\"Try to find information about ingress server that send this mail.\"\"\"\n    internal_domains = internal_domains if internal_domains else []\n\n    def get_host(line):\n        if ' ' not in line:\n            return None\n        parts = line.split(' ')\n        return parts[0]\n\n    def parse_ingress(header):\n        \"\"\"Parse detected ingress header to find encryption infos.\"\"\"\n        header = header.replace('\\n', '')\n        # XXX to complete but MUST match with tls_version, cipher groups\n        searches = ['.*using (\\S+) with cipher ([\\S-]+)']\n        for regex in searches:\n            search = re.compile(regex, re.MULTILINE)\n            match = search.match(header)\n            if match:\n                return match.groups()\n        return None\n\n    found_features = {}\n\n    # First step Search for 'paths' (from / by) information\n    search = re.compile(r'^from (.*) by (.*)', re.MULTILINE + re.DOTALL)\n    paths = []\n    for r in headers:\n        r = r.replace('\\n', '')\n        match = search.match(r)\n        if match:\n            groups = match.groups()\n            from_ = get_host(groups[0])\n            by = get_host(groups[1])\n            if from_ and by:\n                paths.append((from_, by, groups))\n            else:\n                log.debug('Invalid from {} or by {} path in {}'.\n                          format(from_, by, r))\n        else:\n            if r.startswith('by'):\n                # XXX first hop, to consider ?\n                pass\n            else:\n                log.debug('Received header, does not match format {}'.\n                          format(r))\n\n    # Second step: qualify path if internal and try to find the ingress one\n    ingress = None\n    internal_hops = 0\n    for path in paths:\n        is_internal = False\n        for internal in internal_domains:\n            if internal in path[0]:\n                is_internal = True\n            else:\n                if internal in path[1]:\n                    ingress = path\n                    break\n        if is_internal:\n            internal_hops += 1\n\n    # Qualify ingress connection\n    if ingress:\n        cnx_info = parse_ingress(ingress[2][0])\n        if cnx_info and len(cnx_info) > 1:\n            tls_version, cipher = normalize_ssocket_infos(cnx_info)\n            found_features.update({'ingress_socket_version': tls_version,\n                                   'ingress_cipher': cipher})\n        found_features.update({'ingress_server': ingress[0]})\n\n    # Try to count external hops\n    external_hops = len(paths) - internal_hops\n    found_features.update({'nb_external_hops': external_hops})\n    return found_features\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/helpers/spam.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen spam score extraction methods.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport re\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\ndef extract_mail_spam_scores(mail):\n    \"\"\"Extract different spam scores from mail.\"\"\"\n    scores = {}\n    score = mail.get('X-Spam-Score', 0)\n    if score:\n        try:\n            score = float(score)\n            scores['score'] = score\n        except TypeError:\n            log.debug('Invalid type for X-Spam-Score value {}'.\n                      format(score))\n    level = mail.get('X-Spam-Level', '')\n    if '*' in level:\n        # SpamAssassin style, level is *** notation, up to 25 *\n        scores['level'] = level.count('*')\n    status = mail.get('X-Spam-Status', '')\n    if status:\n        match = re.match('^(.*), score=(\\d.\\d).*', status)\n        if match:\n            flag = match.groups()[0]\n            scores['status_score'] = float(match.groups()[1])\n            if flag.lower().startswith('y'):\n                scores['flag'] = True\n            else:\n                scores['flag'] = False\n\n    flag = mail.get('X-Spam-Flag', '')\n    if flag.lower().startswith('y'):\n        scores['flag'] = True\n\n    return scores\n\n\nclass SpamScorer(object):\n    \"\"\"Class to compute a spam score for a message.\"\"\"\n\n    is_spam = False\n    score = 0.0\n    method = ''\n    source_flag = ''\n\n    def __init__(self, mail):\n        \"\"\"Compute a global score using multiple methods and mail.\"\"\"\n        self.scores = extract_mail_spam_scores(mail)\n        log.debug('Found spam scores {s}'.format(s=self.scores))\n        if 'level' in self.scores:\n            self.method = 'level'\n            self.score = min(100.0, self.scores['level'] * 4.0)\n        if 'score' in self.scores:\n            self.method = 'score'\n            self.score = min(0.0, self.scores['score']) * 10.0\n        if 'status_score' in self.scores:\n            self.method = 'status'\n            if self.scores['status_score'] < 2.0:\n                self.score = 0.0\n            else:\n                tmp_score = min(100.0, self.scores['status_score'] * 10)\n                self.score = max(0.0, tmp_score)\n        if self.scores.get('flag'):\n            self.is_spam = True\n            self.source_flag = self.scores['flag']\n        if not self.is_spam and self.score >= 50:\n            self.is_spam = True\n        if self.score < 0:\n            self.score = 0.0\n        if self.score > 100:\n            self.score = 100.0\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/mail.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen mail message privacy features extraction methods.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nimport pgpy\n\nfrom caliopen_main.pi.parameters import PIParameter\nfrom .helpers.spam import SpamScorer\nfrom .helpers.ingress_path import get_ingress_features\nfrom .helpers.importance_level import compute_importance\nfrom .types import init_features\n\nlog = logging.getLogger(__name__)\n\nTLS_VERSION_PI = {\n    'tlsv1/sslv3': 2,\n    'tls1': 7,\n    'tlsv1': 7,\n    'tls12': 10,\n}\n\n\nPGP_MESSAGE_HEADER = '\\n-----BEGIN PGP MESSAGE-----'\n\n\nclass InboundMailFeature(object):\n    \"\"\"Process a parsed mail message and extract available privacy features.\"\"\"\n\n    def __init__(self, message, config):\n        \"\"\"Get a ``MailMessage`` instance and extract privacy features.\"\"\"\n        self.message = message\n        self.config = config\n        self._features = init_features('message')\n\n    def is_blacklist_mx(self, mx):\n        \"\"\"MX is blacklisted.\"\"\"\n        blacklisted = self.config.get('blacklistes.mx')\n        if not blacklisted:\n            return False\n        if mx in blacklisted:\n            return True\n        return False\n\n    def is_whitelist_mx(self, mx):\n        \"\"\"MX is whitelisted.\"\"\"\n        whitelistes = self.config.get('whitelistes.mx')\n        if not whitelistes:\n            return False\n        if mx in whitelistes:\n            return True\n        return False\n\n    @property\n    def internal_domains(self):\n        \"\"\"Get internal hosts from configuration.\"\"\"\n        domains = self.config.get('internal_domains')\n        return domains if domains else []\n\n    def emitter_reputation(self, mx):\n        \"\"\"Return features about emitter.\"\"\"\n        if self.is_blacklist_mx(mx):\n            return 'blacklisted'\n        if self.is_whitelist_mx(mx):\n            return 'whitelisted'\n        return 'unknown'\n\n    def emitter_certificate(self):\n        \"\"\"Get the certificate from emitter.\"\"\"\n        return None\n\n    @property\n    def mail_agent(self):\n        \"\"\"Get the mailer used for this message.\"\"\"\n        # XXX normalize better and more ?\n        return self.message.mail.get('X-Mailer', '').lower()\n\n    @property\n    def transport_signature(self):\n        \"\"\"Get the transport signature if any.\"\"\"\n        return self.message.mail.get('DKIM-Signature')\n\n    @property\n    def spam_informations(self):\n        \"\"\"Return a global spam_score and related features.\"\"\"\n        spam = SpamScorer(self.message.mail)\n        return {'spam_score': spam.score,\n                'spam_method': spam.method,\n                'is_spam': spam.is_spam}\n\n    @property\n    def is_internal(self):\n        \"\"\"Return true if it's an internal message.\"\"\"\n        from_ = self.message.mail.get('From')\n        for domain in self.internal_domains:\n            if domain in from_:\n                return True\n        return False\n\n    def get_signature_informations(self):\n        \"\"\"Get message signature features.\"\"\"\n        signed_parts = [x for x in self.message.attachments\n                        if 'pgp-sign' in x.content_type]\n        if not signed_parts:\n            return {}\n        sign = pgpy.PGPSignature()\n        features = {'message_signed': True,\n                    'message_signature_type': 'PGP'}\n        try:\n            sign.parse(signed_parts[0].data)\n            features.update({'message_signer': sign.signer})\n        except Exception as exc:\n            log.error('Unable to parse pgp signature {}'.format(exc))\n        return features\n\n    def get_encryption_informations(self):\n        \"\"\"Get message encryption features.\"\"\"\n        is_encrypted = False\n        if 'encrypted' in self.message.extra_parameters:\n            is_encrypted = True\n\n        # Maybe pgp/inline ?\n        if not is_encrypted:\n            try:\n                body = self.message.body_plain.decode('utf-8')\n                if body.startswith(PGP_MESSAGE_HEADER):\n                    is_encrypted = True\n            except UnicodeDecodeError:\n                log.warn('Invalid body_plain encoding for message')\n                pass\n\n        return {'message_encrypted': is_encrypted,\n                'message_encryption_method': 'pgp' if is_encrypted else ''}\n\n    def _get_features(self):\n        \"\"\"Extract privacy features.\"\"\"\n        features = self._features.copy()\n        received = self.message.headers.get('Received', [])\n        features.update(get_ingress_features(received, self.internal_domains))\n        mx = features.get('ingress_server')\n        reputation = None if not mx else self.emitter_reputation(mx)\n        features['mail_emitter_mx_reputation'] = reputation\n        features['mail_emitter_certificate'] = self.emitter_certificate()\n        features['mail_agent'] = self.mail_agent\n        features['is_internal'] = self.is_internal\n        features.update(self.get_signature_informations())\n        features.update(self.get_encryption_informations())\n        features.update(self.spam_informations)\n\n        if self.transport_signature:\n            features.update({'transport_signed': True})\n        return features\n\n    def _compute_pi(self, participants, features):\n        \"\"\"Compute Privacy Indexes for a message.\"\"\"\n        log.info('PI features {}'.format(features))\n        pi_cx = {}   # Contextual privacy index\n        pi_co = {}   # Comportemental privacy index\n        pi_t = {}   # Technical privacy index\n        reput = features.get('mail_emitter_mx_reputation')\n        if reput == 'whitelisted':\n            pi_cx['reputation_whitelist'] = 20\n        elif reput == 'unknown':\n            pi_cx['reputation_unknow'] = 10\n        known_contacts = []\n        known_public_key = 0\n        for part, contact in participants:\n            if contact:\n                known_contacts.append(contact)\n                if contact.public_key:\n                    known_public_key += 1\n        if len(participants) == len(known_contacts):\n            # - Si tous les contacts sont déjà connus le PIᶜˣ\n            # augmente de la valeur du PIᶜᵒ le plus bas des PIᶜᵒ des contacts.\n            contact_pi_cos = [x.pi['comportment'] for x in known_contacts\n                              if x.pi and 'comportment' in x.pi]\n            if contact_pi_cos:\n                pi_cx['known_contacts'] = min(contact_pi_cos)\n\n            if known_public_key == len(known_contacts):\n                pi_co['contact_pubkey'] = 20\n        ext_hops = features.get('nb_external_hops', 0)\n        if ext_hops <= 1:\n            tls = features.get('ingress_socket_version')\n            if tls:\n                if tls not in TLS_VERSION_PI:\n                    log.warn('Unknown TLS version {}'.format(tls))\n                else:\n                    pi_t += TLS_VERSION_PI[tls]\n        if features.get('mail_emitter_certificate'):\n            pi_t['emitter_certificate'] = 10\n        if features.get('transport_signed'):\n            pi_t['transport_signed'] = 10\n        if features.get('message_encrypted'):\n            pi_t['encrypted'] = 30\n        log.info('PI compute t:{} cx:{} co:{}'.format(pi_t, pi_cx, pi_co))\n        return PIParameter({'technic': sum(pi_t.values()),\n                            'context': sum(pi_cx.values()),\n                            'comportment': sum(pi_co.values()),\n                            'version': 0})\n\n    def process(self, user, message, participants):\n        \"\"\"\n        Process the message for privacy features and PI compute.\n\n        :param user: user the message belong to\n        :ptype user: caliopen_main.user.core.User\n        :param message: a message parameter that will be updated with PI\n        :ptype message: NewMessage\n        :param participants: an array of participant with related Contact\n        :ptype participants: list(Participant, Contact)\n        \"\"\"\n        features = self._get_features()\n        message.pi = self._compute_pi(participants, features)\n        il = compute_importance(user, message, features, participants)\n        message.privacy_features = features\n        message.importance_level = il\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/features/types.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen privacy features definition.\"\"\"\n\nfrom __future__ import unicode_literals, absolute_import\n\nimport logging\n\n# Privacy features definition\nMESSAGE = {'mail_emitter_mx_reputation': {'type': 'string'},\n           'mail_emitter_certificate': {'type': 'string'},\n           'mail_agent': {'type': 'string'},\n           'transport_signed': {'type': 'bool'},\n           'message_signed': {'type': 'bool'},\n           'message_signature_type': {'type': 'string'},\n           'message_encrypted': {'type': 'bool'},\n           'message_encryption_method': {'type': 'string'},\n           'message_encryption_infos': {'type': 'string'},\n           'is_spam': {'type': 'bool'},\n           'spam_score': {'max': 100, 'min': 0, 'type': 'int'},\n           'spam_method': {'type': 'string'},\n           'ingress_socket_version': {'type': 'string'},\n           'ingress_cipher': {'type': 'string'},\n           'nb_external_hops': {'type': 'int'}}\n\nDEVICE = {'browser_family': {'type': 'string'},\n          'browser_version': {'type': 'string'},\n          'os_family': {'type': 'string'},\n          'device_type': {'type': 'string'},\n          'detected_country': {'type': 'string'}}\n\nCONTACT = {'message_day_total': {'type': 'int'},\n           'message_day_avg': {'type': 'int'},\n           'message_day_pstdev': {'type': 'int'},\n           'address_or_phone': {'type': \"bool\"},\n           'public_key_best_size': {'type': 'int'},\n           'nb_protocols': {'type': 'int'}}\n\nFEATURES = {}\n\nfor feature in (MESSAGE, DEVICE, CONTACT):\n    FEATURES.update(feature)\n\nlog = logging.getLogger(__name__)\n\n\ndef check_feature_bounding(value, feature):\n    \"\"\"Check that a feature fit between min and max value if apply.\"\"\"\n    if 'min' in feature and value < feature['min']:\n        return False\n    if 'max' in feature and value > feature['max']:\n        return False\n    return True\n\n\ndef find_feature(name):\n    \"\"\"Find a feature by its name.\"\"\"\n    if name in FEATURES:\n        return FEATURES[name]\n\n\ndef check_feature(name, value):\n    \"\"\"Check that a feature have a correct value.\"\"\"\n\n    feature = find_feature(name)\n    if not feature:\n        return False\n\n    if feature['type'] == 'int':\n        try:\n            int(value)\n        except ValueError:\n            return False\n        return check_feature_bounding(value, feature)\n\n    elif feature['type'] == 'bool':\n        return True if value in (True, False) else False\n    elif feature['type'] == 'string':\n        return True\n    log.error('Invalid feature type %s, fail silently for the moment' %\n              feature['type'])\n\n\ndef unmarshal_feature(name, value):\n    \"\"\"Unmarshal a feature to it's type representation.\"\"\"\n    if not check_feature(name, value):\n        raise ValueError('Invalid value {} for feature {}'.format(value, name))\n    feature = find_feature(name)\n    if not feature:\n        log.warn('Unknow feature %s' % name)\n        return None\n    if feature['type'] == 'int':\n        return int(value)\n    elif feature['type'] == 'bool':\n        return True if value.lower().startswith('t') else False\n    elif feature['type'] == 'string':\n        return value\n\n\ndef unmarshal_features(features):\n    \"\"\"Unmarshal a dict of features for suitable output.\"\"\"\n    res = {}\n    for name, value in features.items():\n        try:\n            new_value = unmarshal_feature(name, value)\n            res[name] = new_value\n        except ValueError:\n            log.warn('Feature {} with {} do not marshall'.format(name, value))\n            pass\n    return res\n\n\ndef marshal_features(features):\n    \"\"\"Unmarshall a dict of features suitable for storage.\"\"\"\n    res = {}\n    for k, v in features.items():\n        if not (v == '' or v is None):\n            res[k] = str(v)\n    return res\n\n\ndef init_features(type_):\n    \"\"\"Initialize privacy features for a given object type.\"\"\"\n    if type_ == 'contact':\n        features = CONTACT\n    elif type_ == 'device':\n        features = DEVICE\n    elif type_ == 'message':\n        features = MESSAGE\n    else:\n        raise ValueError('Invalid feature type %s' % type_)\n\n    output = {}\n    for k, v in features.items():\n        if v['type'] == 'bool':\n            value = False\n        elif v['type'] == 'int':\n            value = 0\n        else:\n            value = ''\n        output.update({k: value})\n    return output\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/qualifiers/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user message qualification logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .mail import UserMessageQualifier\nfrom .contact import ContactEmailQualifier, ContactMessageQualifier\nfrom .device import NewDeviceQualifier\nfrom .twitter import UserTwitterQualifier\nfrom .mastodon import UserMastodonQualifier\n\n__all__ = ['UserMessageQualifier', 'ContactEmailQualifier',\n           'ContactMessageQualifier', 'NewDeviceQualifier',\n           'UserMastodonQualifier', 'UserTwitterQualifier']\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/qualifiers/base.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen message base qualifier.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_main.contact.core import Contact\nfrom caliopen_main.participant.parameters import Participant\n\n\nlog = logging.getLogger(__name__)\n\n\nclass BaseQualifier(object):\n    _lookups = {}\n\n    def __init__(self, user, identity):\n        \"\"\"Create a new instance of an user message qualifier.\"\"\"\n        self.user = user\n        self.identity = identity\n\n    def _get_tags(self, message):\n        \"\"\"Get tags for a message.\"\"\"\n        tags = []\n        if message.privacy_features.get('is_internal', False):\n            # XXX do not hardcode the wanted tag\n            internal_tag = [x for x in self.user.tags if x.name == 'internal']\n            if internal_tag:\n                tags.append(internal_tag[0].name)\n        message.tags = tags\n\n    def lookup(self, sequence):\n        \"\"\"Process lookup sequence to find discussion to associate.\"\"\"\n        log.debug('Lookup sequence %r' % sequence)\n        for prop in sequence:\n            try:\n                kls = self._lookups[prop[0]]\n                log.debug('Will lookup %s with value %s' %\n                          (prop[0], prop[1]))\n                return kls.get(self.user, prop[1])\n            except NotFound:\n                log.debug('Lookup type %s with value %s failed' %\n                          (prop[0], prop[1]))\n\n        return None\n\n    def create_lookups(self, sequence, message):\n        \"\"\"Initialize lookup classes for the related sequence.\"\"\"\n        for prop in sequence:\n            kls = self._lookups[prop[0]]\n            params = {\n                kls._pkey_name: prop[1],\n                'discussion_id': message.discussion_id\n            }\n            lookup = kls.create(self.user, **params)\n            log.info('Create lookup %r' % lookup)\n\n    def get_participant(self, message, participant):\n        \"\"\"Try to find a related contact and return a Participant instance.\"\"\"\n        p = Participant()\n        p.address = participant.address.lower()\n        p.type = participant.type\n        p.label = participant.label\n        p.protocol = message.message_protocol\n        log.debug('Will lookup contact {} for user {}'.\n                  format(participant.address, self.user.user_id))\n        try:\n            c = Contact.lookup(self.user, participant.address)\n        except Exception as exc:\n            log.error(\"Contact lookup failed in get_participant for participant {} : {}\".format(vars(participant), exc))\n            raise exc\n        if c:\n            p.contact_ids = [c.contact_id]\n        else:\n            if p.address == self.identity.identifier and self.user.contact_id:\n                p.contact_ids = [self.user.contact_id]\n        return p, c\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/qualifiers/contact.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user message qualification logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\n\nfrom caliopen_storage.config import Configuration\n\nfrom caliopen_pgp.keys import PublicKeyDiscoverer\nfrom ..features import ContactFeature\n\nlog = logging.getLogger(__name__)\n\n\nclass ContactMessageQualifier(object):\n    \"\"\"Explore messages between an user and a contact to qualify.\"\"\"\n\n    def __init__(self, user):\n        self.user = user\n\n    def process(self, contact):\n        \"\"\"Qualification for a contact.\"\"\"\n        extractor = ContactFeature(self.user)\n        pi, features = extractor.process(contact)\n        # XXX for the moment apply features and pi\n        if not contact.pi:\n            current_pi = None\n        else:\n            current_pi = contact.pi.marshall_dict()\n        new_pi = pi.serialize()\n        new_pi.pop('date_update')\n        current = {}\n        patch = {}\n        if pi.technic or pi.context or pi.comportment:\n            current.update({'pi': current_pi})\n            patch.update({'pi': new_pi})\n        if contact.privacy_features:\n            current_features = contact.privacy_features\n        else:\n            current_features = {}\n        if current_features != features:\n            current.update({'privacy_features': contact.privacy_features})\n            patch.update({'privacy_features': features})\n        if current and patch:\n            patch.update({'current_state': current})\n            log.info('Will patch with {0}'.format(patch))\n            contact.apply_patch(patch, db=True)\n            return True\n        log.debug('No confidentiality update for contact')\n        return False\n\n\nclass ContactEmailQualifier(object):\n    \"\"\"\n    Process new or delete of an email for a contact.\n\n    - will try to discover public keys\n    - process for privacy features and PI compute\n\n    \"\"\"\n\n    def __init__(self, user):\n        \"\"\"Create a new instance of an contact email qualifier.\"\"\"\n        self.user = user\n        conf = Configuration('global').configuration\n        self.key_disco = PublicKeyDiscoverer(conf)\n\n    def _process_new_keys(self, contact, keys):\n        known_fingerprints = [x.fingerprint for x in contact.public_keys]\n        new_keys = []\n        for new_key in keys:\n            if new_key.fingerprint not in known_fingerprints:\n                if not new_key.is_expired:\n                    new_keys.append(new_key)\n        if new_keys:\n            ids = [x.fingerprint for x in new_keys]\n            log.info('Found new keys {0} for contact'.format(ids))\n            # XXX for the moment\n            return new_keys\n\n        if 'valid_public_keys' not in contact.privacy_features.keys():\n            contact.privacy_features['valid_public_keys'] = True\n            #  - Si le contact a une clé publique connue, le PIᵗ\n            # gagne 30 points (20 points si la clé fait moins\n            # de 2048 bits, 15 points si elle fait 1024 bits ou moins).\n            max_size = max([x.size] for x in new_keys)\n            if max_size <= 1024:\n                contact.pi.technic = contact.pi.technic + 15\n            elif max_size <= 2048:\n                contact.pi.technic = contact.pi.technic + 20\n            elif max_size > 2048:\n                contact.pi.technic = contact.pi.technic + 30\n\n            co_boost = 5 * len(new_keys)\n            contact.pi.comportment = contact.pi.comportment + co_boost\n            contact.save()\n\n    def create_new_email(self, contact, email):\n        \"\"\"Add a new email for a contact.\"\"\"\n        found_keys = self.key_disco.search_email(email)\n        if found_keys:\n            log.info('Found keys for email {0}: {1}'.format(email, found_keys))\n            self._process_new_keys(contact, found_keys)\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/qualifiers/device.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user message qualification logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nfrom caliopen_storage.config import Configuration\n\nfrom ..features import DeviceFeature, marshal_features\n\n\nlog = logging.getLogger(__name__)\n\n\nclass NewDeviceQualifier(object):\n    \"\"\"Process an user device and enhance informations.\"\"\"\n\n    def __init__(self, user):\n        \"\"\"Create a new instance of an user device qualifier.\"\"\"\n        self.user = user\n        self.conf = Configuration('global').configuration\n\n    def process(self, device):\n        \"\"\"Process a device to qualify it.\"\"\"\n        extractor = DeviceFeature(self.user, self.conf)\n        pi, features = extractor.process(device)\n        device.privacy_features = marshal_features(features)\n        device.pi = pi\n        return True\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/qualifiers/mail.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user message qualification logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nfrom caliopen_main.message.parameters import (NewInboundMessage,\n                                              Attachment)\nfrom caliopen_main.participant.parameters.participant import Participant\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_main.participant.core import hash_participants_uri\nfrom caliopen_main.participant.store import HashLookup\n\n# XXX use a message formatter registry not directly mail format\nfrom caliopen_main.message.parsers.mail import MailMessage\nfrom caliopen_main.discussion.core import Discussion\n\nfrom ..features import InboundMailFeature, marshal_features\nfrom .base import BaseQualifier\n\nlog = logging.getLogger(__name__)\n\n\nclass UserMessageQualifier(BaseQualifier):\n    \"\"\"\n    Process a message to enhance it with.\n\n        - tags\n        - pi\n        - discussion reference\n        - etc.\n\n    \"\"\"\n\n    _lookups = {\n        'list': HashLookup,\n        'hash': HashLookup,\n    }\n\n    def lookup_discussion_sequence(self, mail, message):\n        \"\"\"\n        Return list of lookups (type, value) from a mail message\n        and the first lookup'hash from that list\n        \"\"\"\n        seq = []\n\n        # lists lookup first\n        lists = []\n        for list_id in mail.extra_parameters.get('lists', []):\n            lists.append(list_id)\n        if len(lists) > 0:\n            # list_ids are considered `participants`\n            # build uris' hash and upsert lookup tables\n            participants = []\n            for list_id in lists:\n                participant = Participant()\n                participant.address = list_id.lower()\n                participant.protocol = 'email'\n                participant.type = 'list-id'\n                participants.append(participant)\n                discuss = Discussion(self.user)\n                discuss.upsert_lookups_for_participants(participants)\n                # add list-id as a participant to the message\n                message.participants.append(participant)\n            hash = hash_participants_uri(participants)\n            seq.append(('list', hash['hash']))\n\n        # then participants\n        seq.append(('hash', message.hash_participants))\n\n        # try to link message to external thread's root message-id\n        #        if len(message.external_references[\"ancestors_ids\"]) > 0:\n        #            seq.append((\"thread\",\n        #                        message.external_references[\"ancestors_ids\"][0]))\n        #        elif message.external_references[\"parent_id\"]:\n        #            seq.append((\"thread\", message.external_references[\"parent_id\"]))\n        #        elif message.external_references[\"message_id\"]:\n        #            seq.append((\"thread\", message.external_references[\"message_id\"]))\n\n        return seq, seq[0][1]\n\n    def process_inbound(self, raw):\n        \"\"\"Process inbound message.\n\n        @param raw: a RawMessage object\n        @rtype: NewMessage\n        \"\"\"\n        email = MailMessage(raw.raw_data)\n        new_message = NewInboundMessage()\n        new_message.raw_msg_id = raw.raw_msg_id\n        new_message.subject = email.subject\n        new_message.body_html = email.body_html\n        new_message.body_plain = email.body_plain\n        new_message.date = email.date\n        new_message.size = email.size\n        new_message.protocol = email.message_protocol\n        new_message.is_unread = True\n        new_message.is_draft = False\n        new_message.is_answered = False\n        new_message.is_received = True\n        new_message.importance_level = 0  # XXX tofix on parser\n        new_message.external_references = email.external_references\n\n        participants = []\n        for p in email.participants:\n            p.address = p.address.lower()\n            try:\n                participant, contact = self.get_participant(email, p)\n                new_message.participants.append(participant)\n                participants.append((participant, contact))\n            except Exception as exc:\n                log.error(\"process_inbound failed to lookup participant for email {} : {}\".format(vars(email), exc))\n                raise exc\n\n\n        if not participants:\n            raise Exception(\"no participant found in raw email {}\".format(\n                raw.raw_msg_id))\n\n        for a in email.attachments:\n            attachment = Attachment()\n            attachment.content_type = a.content_type\n            attachment.file_name = a.filename\n            attachment.size = a.size\n            attachment.mime_boundary = a.mime_boundary\n            if hasattr(a, \"is_inline\"):\n                attachment.is_inline = a.is_inline\n            new_message.attachments.append(attachment)\n\n        # Compute PI !!\n        conf = Configuration('global').configuration\n        extractor = InboundMailFeature(email, conf)\n        extractor.process(self.user, new_message, participants)\n\n        # compute user tags\n        self._get_tags(new_message)\n        # embed external flags if any\n        new_message.ext_tags = email.external_flags\n        if new_message.tags:\n            log.debug('Resolved tags {}'.format(new_message.tags))\n\n        # build discussion_id from lookup_sequence\n        lookup_sequence, discussion_id = self.lookup_discussion_sequence(email,\n                                                                         new_message)\n        log.debug('Lookup with sequence {} gives {}'.format(lookup_sequence,\n                                                            discussion_id))\n        new_message.discussion_id = discussion_id\n\n        # upsert lookup tables\n        discuss = Discussion(self.user)\n        discuss.upsert_lookups_for_participants(new_message.participants)\n        # Format features\n        new_message.privacy_features = \\\n            marshal_features(new_message.privacy_features)\n        try:\n            new_message.validate()\n        except Exception as exc:\n            log.error(\n                \"validation failed with error : « {} » \\\n                for new_message {}[dump : {}]\".format(\n                    exc, new_message, vars(new_message)))\n            raise exc\n\n        return new_message\n        # TODO link raw message with current user\n        # XXX create lookup\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/qualifiers/mastodon.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user message qualification logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\n\nfrom caliopen_main.message.parameters import NewInboundMessage\nfrom caliopen_main.message.parsers.mastodon import MastodonStatus\nfrom caliopen_main.discussion.core import Discussion\nfrom caliopen_main.participant.store import HashLookup\nfrom caliopen_main.common.helpers.normalize import clean_mastodon_address\n\nfrom ..features import marshal_features\nfrom .base import BaseQualifier\n\nlog = logging.getLogger(__name__)\n\n\nclass UserMastodonQualifier(BaseQualifier):\n    \"\"\"Process a Mastodon direct message to unmarshal it in our stack.\"\"\"\n\n    _lookups = {\n        'hash': HashLookup,\n    }\n\n    def lookup_discussion_sequence(self, message, *args, **kwargs):\n        \"\"\"Return list of lookup type, value from a tweet.\"\"\"\n        seq = list()\n\n        participants = message.hash_participants\n        seq.append(('hash', participants))\n\n        return seq, seq[0][1]\n\n    def process_inbound(self, raw):\n        \"\"\"\n        Process inbound message.\n\n        @param raw: a RawMessage object\n            which should be a json conforming to\n            https://docs.joinmastodon.org/api/entities/#status\n        @rtype: NewMessage\n        \"\"\"\n        toot = MastodonStatus(raw.raw_data)\n        new_message = NewInboundMessage()\n        new_message.raw_msg_id = raw.raw_msg_id\n        new_message.body_html = toot.body_html\n        new_message.date = toot.date\n        new_message.protocol = toot.protocol\n        new_message.is_unread = True\n        new_message.is_draft = False\n        new_message.is_answered = False\n        new_message.is_received = True\n        new_message.importance_level = 0  # XXX tofix on parser\n        new_message.external_references = toot.external_references\n\n        participants = []\n        for p in toot.participants:\n            p.address = p.address\n            participant, contact = self.get_participant(toot, p)\n            new_message.participants.append(participant)\n            participants.append((participant, contact))\n\n        if not participants:\n            raise Exception(\"no participant found in raw tweet {}\".format(\n                raw.raw_msg_id))\n\n        # Compute PI !!\n        # TODO\n\n        # compute tags\n        self._get_tags(new_message)\n        if new_message.tags:\n            log.debug('Resolved tags {}'.format(new_message.tags))\n\n        # build discussion_id from lookup_sequence\n        lookup_sequence, discussion_id = self.lookup_discussion_sequence(\n            new_message)\n        log.debug('Lookup with sequence {} gives {}'.format(lookup_sequence,\n                                                            discussion_id))\n        new_message.discussion_id = discussion_id\n\n        # upsert lookup tables\n        discuss = Discussion(self.user)\n        discuss.upsert_lookups_for_participants(new_message.participants)\n        # Format features\n        new_message.privacy_features = \\\n            marshal_features(new_message.privacy_features)\n        try:\n            new_message.validate()\n        except Exception as exc:\n            log.error(\n                \"validation failed with error : « {} » \\\n                for new_message {}[dump : {}]\".format(\n                    exc, new_message, vars(new_message)))\n            raise exc\n\n        return new_message\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/qualifiers/twitter.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user message qualification logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\n\nfrom caliopen_main.message.parameters import NewInboundMessage\nfrom caliopen_main.message.parsers.twitter import TwitterDM\nfrom caliopen_main.discussion.core import Discussion\nfrom caliopen_main.participant.store import HashLookup\nfrom caliopen_main.common.helpers.normalize import clean_twitter_address\n\nfrom ..features import marshal_features\nfrom .base import BaseQualifier\n\nlog = logging.getLogger(__name__)\n\n\nclass UserTwitterQualifier(BaseQualifier):\n    \"\"\"Process a Twitter direct message to unmarshal it in our stack.\"\"\"\n\n    _lookups = {\n        'hash': HashLookup,\n    }\n\n    def lookup_discussion_sequence(self, message, *args, **kwargs):\n        \"\"\"Return list of lookup type, value from a tweet.\"\"\"\n        seq = list()\n\n        participants = message.hash_participants\n        seq.append(('hash', participants))\n\n        return seq, seq[0][1]\n\n    def process_inbound(self, raw):\n        \"\"\"\n        Process inbound message.\n\n        @param raw: a RawMessage object\n            which should be a json conforming to\n            https://developer.twitter.com/en/docs/direct-messages/\\\n            sending-and-receiving/guides/message-create-object\n        @rtype: NewMessage\n        \"\"\"\n        tweet = TwitterDM(raw.raw_data)\n        new_message = NewInboundMessage()\n        new_message.raw_msg_id = raw.raw_msg_id\n        new_message.body_plain = tweet.body_plain\n        new_message.date = tweet.date\n        new_message.protocol = tweet.protocol\n        new_message.is_unread = True\n        new_message.is_draft = False\n        new_message.is_answered = False\n        new_message.is_received = True\n        new_message.importance_level = 0  # XXX tofix on parser\n        new_message.external_references = tweet.external_references\n\n        participants = []\n        for p in tweet.participants:\n            p.address = clean_twitter_address(p.address)\n            participant, contact = self.get_participant(tweet, p)\n            new_message.participants.append(participant)\n            participants.append((participant, contact))\n\n        if not participants:\n            raise Exception(\"no participant found in raw tweet {}\".format(\n                raw.raw_msg_id))\n\n        # Compute PI !!\n        # TODO\n\n        # compute tags\n        self._get_tags(new_message)\n        if new_message.tags:\n            log.debug('Resolved tags {}'.format(new_message.tags))\n\n        # build discussion_id from lookup_sequence\n        lookup_sequence, discussion_id = self.lookup_discussion_sequence(\n            new_message)\n        log.debug('Lookup with sequence {} gives {}'.format(lookup_sequence,\n                                                            discussion_id))\n        new_message.discussion_id = discussion_id\n\n        # upsert lookup tables\n        discuss = Discussion(self.user)\n        discuss.upsert_lookups_for_participants(new_message.participants)\n        # Format features\n        new_message.privacy_features = \\\n            marshal_features(new_message.privacy_features)\n        try:\n            new_message.validate()\n        except Exception as exc:\n            log.error(\n                \"validation failed with error : « {} » \\\n                for new_message {}[dump : {}]\".format(\n                    exc, new_message, vars(new_message)))\n            raise exc\n\n        return new_message\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/tests/fixtures/dkim1.eml",
    "content": "Return-Path: <posts@futurfast.eu>\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 863D120BA118D\n\tfor <laurent@brasil.brainstorm.fr>; Mon, 11 Apr 2016 16:00:57 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 73A8C33181F7; Mon, 11 Apr 2016 16:00:57 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 6751433181FC; Mon, 11 Apr 2016 16:00:57 +0200 (CEST)\nReceived: from mail.futurfast.eu (mail.futurfast.eu [176.20.27.58])\n\tby mexico.brainstorm.fr (Postfix) with ESMTP id 3F24A33181F7\n\tfor <dev@caliopen.local>; Mon, 11 Apr 2016 16:00:49 +0200 (CEST)\nDKIM-Signature: a=rsa-sha1; t=1460383252; x=1460988052; s=futurfastkey; d=futurfast.eu; c=relaxed/relaxed; q=dns; h=From:Reply-To:Subject:Date:Message-ID:To:MIME-Version:Content-Type:Content-Transfer-Encoding;\n   b=nKhNJ84bK1V3FA5+Ovo0x3UJFo3wOuUv/B0l3zlt7BMVN5a4O314Qvr0wNNltgFx3UB3YHp54KSrnSsCVBPJDS8buCyvO+Vb0zy0VYlpxR57gPwxzMEzcn3YTLKwqw7+pJwuTR7ia6i2VfVmUWajjgk2L5y2TP3f3rL5pORmXOM=\nReceived: from www.supprabyte.co.uk\n        by mail.futurstats.eu (IceWarp 9.3.1) with SMTP id ZSL15752\n        for <dev@caliopen.local>; Mon, 11 Apr 2016 16:00:52 +0200\nTo: dev@caliopen.local\nSubject: Votre offre 4G entreprises\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/tests/fixtures/pgp_crypted_1.eml",
    "content": "Message-ID: <1492761665.964.1.camel@gandi.net>\nSubject: crypted content\nFrom: Chamal <chamal@caliopen.org>\nTo: Caliopen <contact@caliopen.org>\nDate: Fri, 21 Apr 2017 10:01:05 +0200\nContent-Type: multipart/encrypted; protocol=\"application/pgp-encrypted\";\n        boundary=\"=-hf6zgIn8mtv3IPX/X7Tq\"\nX-Mailer: Evolution 3.22.6 \nMime-Version: 1.0\nX-Evolution-Identity: 1485870052.1665.0@tao\nX-Evolution-Fcc: folder://1485870052.1665.2%40tao/Sent\nX-Evolution-Transport: 1485870052.1665.10@tao\nX-Evolution-Source: \n\n\n--=-hf6zgIn8mtv3IPX/X7Tq\nContent-Type: application/pgp-encrypted\nContent-Transfer-Encoding: 7bit\n\nVersion: 1\n\n--=-hf6zgIn8mtv3IPX/X7Tq\nContent-Type: application/octet-stream; name=\"encrypted.asc\"\nContent-Description: Ceci est une partie de message\n =?ISO-8859-1?Q?num=E9riquement?= =?ISO-8859-1?Q?_sign=E9e?=\nContent-Transfer-Encoding: 7bit\n\n-----BEGIN PGP MESSAGE-----\n\nhQIMAxEK1/MMDKzXAQ/+KpNeJBR9o5etazFr16HO/vWAdDlkq3iIu7BOm0sAXYIJ\nssjXN9cXY1LM1B70GeXvGeB4suqJBjrl6xbKCW0fMQsGovYQpyHJAOJCtModM6vM\n76OuuTcT0AdE40pgfscTdG3nCUV2T/w2IKg7TNLwEstRQ4BoA+RFPyar97Zq1Pyk\n74Wkwv1BT9jMejkpaQycqlV1eTF/JNbXa/hs5dcmirbH9PCoySQx+qFnp4w4ZHnx\nJDT/MpmK4M3w7FawyA2adUFKeBqXU9Oo2LBz9f9ZBtIPHeiV2iklEgQUqv/847dc\nOqB23fd+Jq8mXjmycLcwEsXjOL77dI+SCip4MheFvGjwSOUoIkIb6SbcnUq/hwsi\nxpLc3yeZDm4BCi/M0crmhMF8NEJYyu0Xg2Si+lWtWPHCtrXW+Lla226BcGNlV1Dx\ncpaeMlFF2MvADqTsxAD8F+km2mrf7RznobnX70TkH2wF80W+/KIKbc0R2ilyvyIm\nY/MgttT593nUJgIm77cIzUjSMJHVfhYASS960Rv4cvKMIYcJQ41zJ1O693eB8gXM\nebCU7Tg6gzZ1Fckgil0VQyRwY9cPERZQSrgcQOdZaw51jYzJNrRDXaaX8TuMnSTx\nxJkalCh8PEJ9mtLIvNPgw5cGNhttNkoGvPamEAXEHTNE7757qo5ku7w4X2u9t6uF\nAQwD08kNfkWMEY4BB/9N42dz6IwZ7GG/9Hj0Avcb5l8If1peNj/zOUXv88JBvMEL\nGukefhD2ez0R359ErrJk4Q+RhOOJYkrcPUShqm0SN7s50TVsms5/7T/OT0mxVsOR\n6nnm19DMGW32Kz819c8Gp+kVB7sFOldJpi/kyoXJsO/0HdnXaM4HyeF8csTFFvLf\nB2bZFNk5YgasiCbCys2M0Zv8MtmUXQc7y1xsHSvpgjM6Ftb2BQdfjNj+CUnJHpxc\nCfFtM8lmeVrjPFHqtyuofVEqQvHjKSKSfZH5LrYA6QJfL0Girfqm3x4fgCSAaHPQ\n87G7pcu5Ci2FDpQlHxPwnXHfbT/IKPNQrjB/nQ0E0ukBK5GRwm8AnqjK+cCcSaYo\nbcoNartPm8J3qPTC3Vdzw2OButC6wnXYrsbE1rADZ6PVByeXj7OtSX01fykHwgFj\nnEu4HEIMNTVtprZQ8UnMV95REM8yRteiNPfl+osK3Ae/PyZ/0go7bhYgfJh3qnIH\nNDzThiCy3/4YoBErejnLVQdpVTf2OFHEDpohwz76/4GhXouOiWkrSpjBWIwMnYC+\nF/ZEgXkUIja6l1K16ovNe03DiEb/dgZkF//BJsnIAzNqK8PzGawwxLkDrMKVVAjP\nTpdGSnwyDsjjFMYG4s7dWWo0miwPU5QLJIkqqyk2040YCV6rgebpfZ8RrIuZEERi\nuSFQtf+ghBWGJDuf49P+adf20HbNYFIi++TkG0fQ0RlcagRHYRahgytdQpUBz9GS\nafOnC9uPtIyBq6dNLsvAr1pIlBXsy14lSdAv67rtUtOzNoeww0BaiYpL6Ir3rrPg\npQ34U3RZ44kH0QWq/9btVWPTxLobRc41pf/LmEo4MAvwwZeyvJH5mJQotaWDRtJu\nNYLzCYcqT5UqRn5dYq8k8UFzLwaynoSxdUSV0uozGsLj5yXf5Dm1x/L+FAX5STgZ\n95MntgS5fPjYABNPD9oVqfFdJmm9s27b3LgFgQBlx4kU+rqN/aAQdIltlUsA+zl1\nqkoRdhilQfqn7g35HRm/ysD9pK7GKQ+R1D+fQAebs451E50q3VIaB0lJRluRXabK\nvWhMk29emRPKg8OMRR7r9oIss9gg+fTVmyttNX5MZ5t+DKjGdlApMhLPahyxa/i/\nWWhRmZTLkVRqssD3blfPa1AR/XJIONcfFROwLyHy3LwQyMBcH8YcFC5oT3oDn3PR\n07gXHSK6N7Xv6R08NF1Q6Ag4d6IUT//KvUf5WqyZaB5adLEE/Fip8doDed6zv/Hw\nqSeSqgWm9Y2t+6sekZXJkMHaY0Mdiuu6ONntG7cs87rHHKG/paCcAMLLoKkpFuJM\nvoKkjlafMq+HR59hY4fIgrOw2iydL1Ihb9YiMzl15zknpzQeQMNCElm3dZvWwsHU\nn3HYTc+JqTEXxpP7IiBpIbiy/EP5kIuKk9kapBgiDDzMiey3WbyyeqTH4luAh0Uv\nofJivNiEJeCzCR6Gml1bgvRbJrimsgYF1iBAsY3m14onkCCnWaim9kqLUmB8mVec\nxsYUJuNtGEqS8W0++uBzW+tXKlq6hjdGkfeCp/oD6gbBGxq+Iho8qVRSRPJ90EOR\nIZX5mCJJzSYdP9ELwWFAObl7WtRea7Ed376B6a7maQ==\n=EKUR\n-----END PGP MESSAGE-----\n\n--=-hf6zgIn8mtv3IPX/X7Tq--"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/tests/fixtures/pgpsigned1.elm",
    "content": "From: dev@caliopen.local\nTo: dev@caliopen.local\nSubject: Windows pretest builds\nDate: Mon, 11 Apr 2016 10:07:15 +0200\nMessage-ID: <87twj8k1qk.fsf@caliopen.local>\nMIME-Version: 1.0\nContent-Type: multipart/signed; boundary=\"=-=-=\";\n\tmicalg=pgp-sha256; protocol=\"application/pgp-signature\"\n\n--=-=-=\nContent-Type: text/plain\n\nHi guys,\n\nI just pushed Phillip Windows pretest builds at\nhttp://alpha.gnu.org/gnu/emacs/windows/\n\nCheers,\nNico\n\n--=-=-=\nContent-Type: application/pgp-signature; name=\"signature.asc\"\n\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2\nComment: GPGTools - http://gpgtools.org\n\niQEcBAEBCAAGBQJXC1szAAoJECM1h6R8IHkQ/T8H/0wMyJY3+E2VY94Mp4F0nDAY\nI6cguBfRrXo16vY+GJ1luuC3n6mz9D5XmFuW/Ms/s/1CVIkMnQqUlCFT4VeSe3t2\ndyqvAiXhd3qqNLxLARn6/ifrNM2l0N6crtFj7nwfAxnJajildQZJ+q+GDzhLPUZz\n7o2Cepu5RS8UtBJZqKoXifDSOmJW9dCYSJOlPPzSGQtrdS//Ma4/XVd8AwShaeYY\nTImhElsNnNDjnYV9aLywotzxZZLZpwSx0g5jW/EyF0f+Rc3QdF/50KILCnF/qHZO\n2Mi8rdd6il9yC4Q6ctw7GAOMly5EXRxOI4wxBxAeM1sVNjaM+rEjCtV/qmNoSC4=\n=Rf6V\n-----END PGP SIGNATURE-----\n--=-=-=--\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/tests/fixtures/spam1.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\nX-Original-To: dev@caliopen.local\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\n\tby mail4.gandi.net (Postfix) with ESMTP id 782B2120A1F;\n\tFri, 28 Nov 2014 16:00:57 +0100 (CET)\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\nX-Spam-Score: -99.049\nX-Spam-Level: \nX-Spam-Status: No, score=-99.049 tagged_above=-999 required=5\n\ttests=[BAYES_20=-0.74, RATWARE_GECKO_BUILD=1.691,\n\tUSER_IN_WHITELIST=-100]\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/tests/fixtures/spam2.eml",
    "content": "Return-Path: <caliopdev-bounces@caliop.net>\nX-Original-To: dev@caliopen.local\nReceived: from localhost (mfiltercorp1-d.gandi.net [217.70.183.155])\n\tby mail4.gandi.net (Postfix) with ESMTP id 782B2120A1F;\n\tFri, 28 Nov 2014 16:00:57 +0100 (CET)\nX-Virus-Scanned: Debian amavisd-new at mfiltercorp1-d.gandi.net\nX-Spam-Score: 5.111\nX-Spam-Level: \nX-Spam-Status: Yes, score=5.111 tagged_above=-999 required=5\n\ttests=[BAYES_20=-0.74, RATWARE_GECKO_BUILD=1.691]\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/tests/fixtures/spam3.eml",
    "content": "Return-Path: <bounce@astro-tarot-akmas.fr>\nX-Original-To: laurent@brasil.brainstorm.fr\nDelivered-To: laurent@brasil.brainstorm.fr\nReceived: from mexico.brainstorm.fr (mexico.brainstorm.fr [193.56.58.35])\n\tby brasil.brainstorm.fr (Postfix) with ESMTP id 6CD8C2086DDFC\n\tfor <laurent@brasil.brainstorm.fr>; Sun,  3 Apr 2016 21:30:19 +0200 (CEST)\nReceived: by mexico.brainstorm.fr (Postfix)\n\tid 59E2F344B4DB; Sun,  3 Apr 2016 21:30:19 +0200 (CEST)\nDelivered-To: dev@caliopen.local\nReceived: by mexico.brainstorm.fr (Postfix, from userid 1004)\n\tid 48A564E8AAC9; Sun,  3 Apr 2016 21:30:19 +0200 (CEST)\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mexico.octopuce.fr\nX-Spam-Flag: YES\nX-Spam-Level: *********\nX-Spam-Status: Yes, score=9.7 required=5.0 tests=AWL,BAYES_99,\n\tDATE_IN_PAST_06_12,HTML_IMAGE_RATIO_04,HTML_MESSAGE,RAZOR2_CF_RANGE_51_100,\n\tRAZOR2_CF_RANGE_E8_51_100,RAZOR2_CHECK,RCVD_IN_BRBL_LASTEXT,\n\tT_KHOP_FOREIGN_CLICK autolearn=spam version=3.3.2\nX-Spam-Report: \n\t*  3.5 BAYES_99 BODY: Bayes spam probability is 99 to 100%\n\t*      [score: 1.0000]\n\t*  1.5 DATE_IN_PAST_06_12 Date: is 6 to 12 hours before Received: date\n\t*  1.4 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT\n\t*      [91.121.179.117 listed in bb.barracudacentral.org]\n\t*  0.6 HTML_IMAGE_RATIO_04 BODY: HTML has a low ratio of text to image area\n\t*  0.0 HTML_MESSAGE BODY: HTML included in message\n\t*  0.0 T_KHOP_FOREIGN_CLICK T_KHOP_FOREIGN_CLICK\n\t*  0.9 RAZOR2_CHECK Listed in Razor2 (http://razor.sf.net/)\n\t*  1.9 RAZOR2_CF_RANGE_E8_51_100 Razor2 gives engine 8 confidence level\n\t*      above 50%\n\t*      [cf: 100]\n\t*  0.5 RAZOR2_CF_RANGE_51_100 Razor2 gives confidence level above 50%\n\t*      [cf: 100]\n\t* -0.7 AWL AWL: From: address is in the auto white-list\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/tests/test_features.py",
    "content": "\"\"\"Test spam privacy feature extraction.\"\"\"\n\nimport unittest\nimport os\nimport uuid\n\nfrom caliopen_storage.config import Configuration\n\nif 'CALIOPEN_BASEDIR' in os.environ:\n    conf_file = '{}/src/backend/configs/caliopen.yaml.template'. \\\n                format(os.environ['CALIOPEN_BASEDIR'])\nelse:\n    conf_file = '../../../../../configs/caliopen.yaml.template'\n\nConfiguration.load(conf_file, 'global')\n\nfrom mailbox import Message\nfrom caliopen_main.message.parsers.mail import MailMessage\nfrom caliopen_main.message.parameters import NewMessage\nfrom caliopen_pi.features.mail import InboundMailFeature\n\n\nclass FakeUser(object):\n\n    name = 'test'\n    user_id = uuid.uuid4()\n\n\ndef load_mail(filename):\n    \"\"\"Read email from fixtures of an user.\"\"\"\n    # XXX tofix: set fixtures in a more convenient way to not\n    # have dirty hacking on relative path\n    dir_path = os.path.dirname(os.path.realpath(__file__))\n    path = '{}/fixtures'.format(dir_path)\n    with open('{}/{}'.format(path, filename)) as f:\n        data = f.read()\n    return Message(data)\n\n\nclass TestEncyption(unittest.TestCase):\n    \"\"\"Test encryption features.\"\"\"\n\n    def test_no_encryption_signature(self):\n        mail = load_mail('spam1.eml')\n        message = MailMessage(mail)\n        param = NewMessage()\n        user = FakeUser()\n        featurer = InboundMailFeature(message, {})\n        featurer.process(user, param, ([]))\n        feats = param.privacy_features\n        self.assertEqual(feats.get('message_signed'), False)\n        self.assertEqual(feats.get('message_encrypted'), False)\n        self.assertEqual(feats.get('message_encryption_method'), '')\n        self.assertEqual(feats.get('message_signature_type'), '')\n        self.assertEqual(feats.get('transport_signed'), False)\n\n    def test_transport_signature(self):\n        mail = load_mail('dkim1.eml')\n        message = MailMessage(mail)\n        param = NewMessage()\n        user = FakeUser()\n        featurer = InboundMailFeature(message, {})\n        featurer.process(user, param, ([]))\n        feats = param.privacy_features\n        self.assertEqual(feats.get('transport_signed'), True)\n\n    def test_encrypted_message(self):\n        mail = load_mail('pgp_crypted_1.eml')\n        message = MailMessage(mail)\n        param = NewMessage()\n        user = FakeUser()\n        featurer = InboundMailFeature(message, {})\n        featurer.process(user, param, ([]))\n        feats = param.privacy_features\n        self.assertEqual(feats.get('message_encrypted'), True)\n        self.assertEqual(feats.get('message_encryption_method'), 'pgp')\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/tests/test_importance_level.py",
    "content": "\"\"\"Test importance level compute.\"\"\"\n\nimport unittest\nimport os\n\nfrom caliopen_storage.config import Configuration\n\nif 'CALIOPEN_BASEDIR' in os.environ:\n    conf_file = '{}/src/backend/configs/caliopen.yaml.template'. \\\n                format(os.environ['CALIOPEN_BASEDIR'])\nelse:\n    conf_file = '../../../../../configs/caliopen.yaml.template'\n\nConfiguration.load(conf_file, 'global')\n\nfrom caliopen_pi.features.helpers.importance_level import compute_importance\n\n\nclass MockPI(object):\n\n    def __init__(self, technic, context, comportment):\n        self.technic = technic\n        self.context = context\n        self.comportment = comportment\n\n\nclass MockMessage(object):\n\n    def __init__(self, pi, tags=None, refs=None):\n        self.pi = pi\n        self.tags = tags if tags else []\n        self.external_references = refs if refs else []\n\n\nclass TestInboundImportanceLevel(unittest.TestCase):\n\n    def test_max_spam(self):\n        pi = MockPI(0, 0, 0)\n        features = {'is_spam': True, 'spam_score': 100.0}\n        message = MockMessage(pi)\n        score = compute_importance(None, message, features, [])\n        self.assertEqual(score, -5)\n\n    def test_half_spam(self):\n        pi = MockPI(0, 0, 0)\n        features = {'is_spam': True, 'spam_score': 50.0}\n        message = MockMessage(pi)\n        score = compute_importance(None, message, features, [])\n        self.assertEqual(score, -3)\n\n    def test_max_pi_context(self):\n        pi = MockPI(0, 100, 0)\n        features = {}\n        message = MockMessage(pi)\n        score = compute_importance(None, message, features, [])\n        self.assertEqual(score, 1)\n\n    def test_max_pi_comportment(self):\n        pi = MockPI(0, 0, 100)\n        features = {}\n        message = MockMessage(pi)\n        score = compute_importance(None, message, features, [])\n        self.assertEqual(score, 3)\n\n    def test_max_pi_context_comportment(self):\n        pi = MockPI(0, 100, 100)\n        features = {}\n        message = MockMessage(pi)\n        score = compute_importance(None, message, features, [])\n        self.assertEqual(score, 4)\n"
  },
  {
    "path": "src/backend/components/py.pi/caliopen_pi/tests/test_spam.py",
    "content": "\"\"\"Test spam privacy feature extraction.\"\"\"\n\nimport unittest\nimport os\n\nfrom caliopen_storage.config import Configuration\n\nif 'CALIOPEN_BASEDIR' in os.environ:\n    conf_file = '{}/src/backend/configs/caliopen.yaml.template'. \\\n                format(os.environ['CALIOPEN_BASEDIR'])\nelse:\n    conf_file = '../../../../../configs/caliopen.yaml.template'\n\nConfiguration.load(conf_file, 'global')\n\nfrom mailbox import Message\nfrom caliopen_pi.features.helpers.spam import SpamScorer\n\n\ndef load_mail(filename):\n    \"\"\"Read email from fixtures of an user.\"\"\"\n    # XXX tofix: set fixtures in a more convenient way to not\n    # have dirty hacking on relative path\n    dir_path = os.path.dirname(os.path.realpath(__file__))\n    path = '{}/fixtures'.format(dir_path)\n    with open('{}/{}'.format(path, filename)) as f:\n        data = f.read()\n    return Message(data)\n\n\nclass TestSpamScorer(unittest.TestCase):\n    \"\"\"Test spam scorer.\"\"\"\n\n    def test_spam1(self):\n        mail = load_mail('spam1.eml')\n        scorer = SpamScorer(mail)\n        self.assertFalse(scorer.is_spam)\n        self.assertEqual(scorer.method, 'score')\n        self.assertEqual(scorer.score, 0.0)\n\n    def test_spam2(self):\n        mail = load_mail('spam2.eml')\n        scorer = SpamScorer(mail)\n        self.assertTrue(scorer.is_spam)\n        self.assertEqual(scorer.method, 'status')\n        self.assertEqual(scorer.score, 51.0)\n\n    def test_spam3(self):\n        mail = load_mail('spam3.eml')\n        scorer = SpamScorer(mail)\n        self.assertTrue(scorer.is_spam)\n        self.assertEqual(scorer.method, 'status')\n        self.assertEqual(scorer.score, 97.0)\n"
  },
  {
    "path": "src/backend/components/py.pi/requirements.deps",
    "content": "caliopen_pgp"
  },
  {
    "path": "src/backend/components/py.pi/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliopen\nwith-coverage = 1\ncover-erase = 1\n"
  },
  {
    "path": "src/backend/components/py.pi/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_pi\"\n\ninit_file = os.path.join(*([here] + name.split('.') + ['__init__.py']))\nwith open(init_file) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S). \\\n        match(v_file.read()).group(1)\n\nrequires = [\n    'schematics',\n    'pgpy',\n    'user-agents',\n    'maxminddb<2',\n    'geoip2==3.0.0',\n]\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\n\nextras_require = {\n    'dev': [],\n    'test': [\n        'coverage',\n        'docker-py',\n        'freezegun',\n        'nose'\n    ],\n}\n\nsetup(name=name,\n      version=version,\n      description='Caliopen pi package. All stuff related to privacy index',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\"Programming Language :: Python\", ],\n      author='Caliopen contributors',\n      author_email='contact@caliopen.org',\n      url='https://caliopen.org',\n      license='AGPLv3',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      extras_require=extras_require,\n      install_requires=requires,\n      tests_require=requires,\n      )\n"
  },
  {
    "path": "src/backend/components/py.tag/CHANGES.rst",
    "content": "0.0.1\n-----\n\n-  Initial version\n\n"
  },
  {
    "path": "src/backend/components/py.tag/README.rst",
    "content": "Caliopen Tagger package\n=======================\n\nThis package contain all logic related to the tagger in Caliopen platform\n\n"
  },
  {
    "path": "src/backend/components/py.tag/caliopen_tag/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n__version__ = '0.23.0'\n"
  },
  {
    "path": "src/backend/components/py.tag/caliopen_tag/models_manager/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen tagging model manager logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .manager import ModelManager\nfrom .data_manager import UsenetDataManager, ESDataManager, \\\n    MultipleSourceDataManager\n\n__all__ = ['ModelManager', 'UsenetDataManager', 'ESDataManager',\n           'MultipleSourceDataManager']\n"
  },
  {
    "path": "src/backend/components/py.tag/caliopen_tag/models_manager/data_manager.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen class for creating and preparing data for the tagger manager\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nfrom caliopen_data import FileDataProvider, ESProvider\nimport elasticsearch_dsl as dsl\nfrom ..utils import pre_process\n\n\nclass UsenetDataManager(FileDataProvider):\n    @classmethod\n    def _format_item(cls, item):\n        # The usenet data is already pre-processed\n        return item\n\n\nclass ESDataManager(ESProvider):\n    @classmethod\n    def _format_item(cls, item):\n        labels = \" \".join(\n            [\"__label__\" + t.replace(\" \", \"_\") for t in item.tags]\n        )\n        html_text = item.body_plain.encode(\"utf-8\")\n        text_pre = pre_process(html_text, html=True)\n        return \"{0} {1}\".format(labels, text_pre)\n\n    def get_query(self):\n        query = dsl.Search()\n        query = query.filter('exists', field='tags')\n        query = query.using(self._store).doc_type('indexed_message')\n        return query\n\n\nclass MultipleSourceDataManager(object):\n    def __init__(self, providers):\n        self.providers = providers\n\n    def next(self):\n        for provider in self.providers:\n            for item in provider.next():\n                yield item\n"
  },
  {
    "path": "src/backend/components/py.tag/caliopen_tag/models_manager/manager.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user message qualification logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\nimport os\nfrom tempfile import NamedTemporaryFile\nfrom ..utils import resources_path\n\nimport fastText\n\nlog = logging.getLogger(__name__)\n\n\nclass ModelManager(object):\n    \"\"\"\n    Manage tagging model:\n     - training a new model\n    \"\"\"\n\n    def __init__(\n            self,\n            provider,\n            use_pre=True,\n            epoch=15,\n            lr=0.1,\n            word_n_grams=2,\n            min_count=400,\n            dim=100,\n            loss=\"softmax\",\n            thread=12,\n            neg=5):\n        \"\"\"Initialize a model manager.\n\n        It requires a provider to iterate on for creating the data.\n        All hyperparameters for training the model are optional.\n        \"\"\"\n        self.provider = provider\n        self.use_pre = use_pre\n        self.epoch = epoch\n        self.lr = lr\n        self.wordNgrams = word_n_grams\n        self.minCount = min_count\n        self.dim = dim\n        self.loss = loss\n        self.thread = thread\n        self.neg = neg\n\n    def get_new_model(self, output):\n        \"\"\"Get a new model and save it in resources/models/output.\n\n        It first fetches the data from cassandra and write it to a temporary\n        file, following the fastText format.\n        Then it pre-processes the data (tokenization + lowercase).\n        Then it trains a model using fastText and saves it at output location.\n        Finally it removes the temporary file.\n        \"\"\"\n        self._write_training_data_to_file()\n        result_file = self._train_tagging_model(output)\n        self._remove_tempfile()\n        return result_file\n\n    def _write_training_data_to_file(self):\n        f = NamedTemporaryFile(delete=False, dir=resources_path)\n\n        count_lines = 0\n\n        for item in self.provider.next():\n            f.write(item.encode(\"utf-8\") + \"\\n\".encode(\"utf-8\"))\n            count_lines += 1\n\n        log.info('Wrote ' + str(count_lines) + \" lines from elastic data.\")\n\n        self.tempfilename = f.name\n        f.close()\n\n    def _train_tagging_model(self, output, quantization=False):\n        \"\"\"Train a tagging model using an annotated file and save it to output.\n\n        Pre-processing of the input file must have been done prior to training.\n        Training file should be utf-8.\n        The model can be quantized to reduce memory usage\n        (but quantization is quite expensive).\n        \"\"\"\n        log.info('Start training model.')\n        new_model = fastText.train_supervised(input=self.tempfilename,\n                                              epoch=self.epoch,\n                                              lr=self.lr,\n                                              wordNgrams=self.wordNgrams,\n                                              minCount=self.minCount,\n                                              dim=self.dim,\n                                              loss=self.loss,\n                                              thread=self.thread,\n                                              neg=self.neg)\n        log.info('Training model done.')\n        if quantization:\n            log.info('Start quantization.')\n            new_model.quantize(thread=self.thread, retrain=False)\n            log.info('Quantization done.')\n        model_file = '{}/{}.bin'.format(resources_path, output)\n        new_model.save_model(model_file)\n        log.info('Model saved at {}.'.format(model_file))\n        return model_file\n\n    def _remove_tempfile(self):\n        os.remove(self.tempfilename)\n"
  },
  {
    "path": "src/backend/components/py.tag/caliopen_tag/taggers/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen tagging model qualifier\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .tagger import MessageTagger\n\n__all__ = ['MessageTagger']\n"
  },
  {
    "path": "src/backend/components/py.tag/caliopen_tag/taggers/tagger.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user message qualification logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\n\nfrom ..utils import pre_process, resources_path\n\nimport fastText\n\nlog = logging.getLogger(__name__)\n\n\nclass MessageTagger(object):\n    \"\"\"Tag a message using a tagging model\"\"\"\n\n    def __init__(self, model_name=\"model_cat1\", k=5, threshold=0):\n        try:\n            self.model = fastText.load_model(\n                resources_path + \"models/{}.bin\".format(model_name)\n            )\n            log.info('Load tagging model {}'.format(model_name))\n        except ValueError as exc:\n            log.error(\n                'Error loading tagging model {}: {}'.format(model_name, exc)\n            )\n            raise exc\n        self.k = k\n        self.threshold = threshold\n\n    def process(self, msg):\n        \"\"\"Qualification for a message.\n\n        It will first remove any \\n because predict processes one line only.\n        Then it will tokenize the message with the same tokenizer as the one\n        used for the training data.\n        Afterwards, it will predict tag and return tag + prediction.\n        Finally, it will remove the __label__ prefix to predicted tags.\n        \"\"\"\n        text = pre_process(msg.body_plain, html=True)\n        # TODO: Add language support + check if msg.body_plain is html ?\n\n        predictions = self.model.predict(\n            text,\n            k=self.k,\n            threshold=self.threshold\n        )\n        nb_result = len(predictions[0])\n        result = [\n            (predictions[0][i][9:], predictions[1][i])\n            for i in range(nb_result)\n        ]\n        return result\n"
  },
  {
    "path": "src/backend/components/py.tag/caliopen_tag/tests/test_model_manager.py",
    "content": "\"\"\"Test model manager class\"\"\"\n\nimport unittest\nimport os\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_tag.models_manager import ESDataManager\n\nif 'CALIOPEN_BASEDIR' in os.environ:\n    conf_file = '{}/src/backend/configs/caliopen.yaml.template'. \\\n                format(os.environ['CALIOPEN_BASEDIR'])\nelse:\n    conf_file = '../../../../configs/caliopen.yaml.template'\n\nConfiguration.load(conf_file, 'global')\n\n\nclass MockMessage:\n    def __init__(self, content, tags):\n        self.body_plain = content\n        self.tags = tags\n\n\nclass TestModelManager(unittest.TestCase):\n    \"\"\"Test model manager.\"\"\"\n\n    def test_ESDataManager_format_item(self):\n        es_provider = ESDataManager({\"elasticsearch\": {\"url\": \"mock\"}})\n\n        message = MockMessage(\"\"\"<body><p>Bonjour M. Dupont,</p>\n                              Voici le contenu de mon message.\n                              <a href=\"\" alt=\"\">lien de la page.</a></body>\"\"\",\n                              [\"social\", \"important\"])\n\n        text = es_provider._format_item(message)\n        self.assertEqual(text, \"__label__social __label__important bonjour m. \"\n                               \"dupont , voici le contenu de mon message . \"\n                               \"lien de la page .\")\n"
  },
  {
    "path": "src/backend/components/py.tag/caliopen_tag/tests/test_tag.py",
    "content": "\"\"\"Test tagger class\"\"\"\n\nimport unittest\n\nfrom caliopen_tag.taggers.tagger import MessageTagger\nfrom caliopen_tag.utils import pre_process\n\n\nclass TestPredictTagger(unittest.TestCase):\n    \"\"\"Test tagger.\"\"\"\n\n    def test_tagger_load_fail_file_not_found(self):\n        with self.assertRaises(ValueError):\n            MessageTagger(\"random_cat\")\n\n    def test_prepare_msg(self):\n        text = pre_process(\"\"\"<html><body>\n                              <h1>Bonjour M. Dupont,</h1>\n                              <br>\n                              <p>Voici le contenu de mon message.</p>\n                              </body></html>\"\"\", html=True)\n        self.assertEqual(text, \"bonjour m. dupont , \"\n                               \"voici le contenu de mon message .\")\n"
  },
  {
    "path": "src/backend/components/py.tag/caliopen_tag/utils.py",
    "content": "from nltk.tokenize import word_tokenize\nfrom bs4 import BeautifulSoup\n\nresources_path = \"/var/tmp/\"\n\n\ndef pre_process(text, html=False):\n    if html:\n        text = BeautifulSoup(text, \"html.parser\").text\n    return \" \".join(word_tokenize(text)) \\\n        .replace(\"\\n\", \" \") \\\n        .lower()\n"
  },
  {
    "path": "src/backend/components/py.tag/requirements.deps",
    "content": "caliopen_data"
  },
  {
    "path": "src/backend/components/py.tag/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliopen\nwith-coverage = 1\ncover-erase = 1\n"
  },
  {
    "path": "src/backend/components/py.tag/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_tag\"\n\ninit_file = os.path.join(*([here] + name.split('.') + ['__init__.py']))\nwith open(init_file) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S). \\\n        match(v_file.read()).group(1)\n\nrequires = [\n    'schematics',\n    'pgpy',\n    'user-agents',\n    'geoip2',\n    'nltk',\n    'bs4',\n    'fastText==0.8.22',\n]\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\n\nextras_require = {\n    'dev': [],\n    'test': [\n        'coverage',\n        'docker-py',\n        'freezegun',\n        'nose'\n    ],\n}\n\nsetup(name=name,\n      version=version,\n      description='Caliopen tag package. All stuff related to message tagging',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\"Programming Language :: Python\", ],\n      author='Caliopen contributors',\n      author_email='contact@caliopen.org',\n      url='https://caliopen.org',\n      license='AGPLv3',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      extras_require=extras_require,\n      install_requires=requires,\n      tests_require=requires,\n      dependency_links=[\"https://github.com/facebookresearch/fastText/archive/\"\n                        \"master.zip#egg=fastText-0.8.22\"],\n      )\n"
  },
  {
    "path": "src/backend/configs/apiv1-nginx.conf",
    "content": "# /etc/nginx/nginx.conf\n\nuser nginx;\n\n# Set number of worker processes automatically based on number of CPU cores.\nworker_processes auto;\n\n# Enables the use of JIT for regular expressions to speed-up their processing.\npcre_jit on;\npid /run/nginx.pid;\ndaemon off;\n\n# Configures default error logger.\nerror_log /var/log/nginx/error.log warn;\n\n# Includes files with directives to load dynamic modules.\ninclude /etc/nginx/modules/*.conf;\n\n\nevents {\n        # The maximum number of simultaneous connections that can be opened by\n        # a worker process.\n        worker_connections 1024;\n}\n\nhttp {\n        # Includes mapping of file name extensions to MIME types of responses\n        # and defines the default type.\n        include /etc/nginx/mime.types;\n        default_type application/octet-stream;\n\n        # Name servers used to resolve names of upstream servers into addresses.\n        # It's also needed when using tcpsocket and udpsocket in Lua modules.\n        #resolver 208.67.222.222 208.67.220.220;\n\n        # Don't tell nginx version to clients.\n        server_tokens off;\n\n        # Specifies the maximum accepted body size of a client request, as\n        # indicated by the request header Content-Length. If the stated content\n        # length is greater than this size, then the client receives the HTTP\n        # error code 413. Set to 0 to disable.\n        client_max_body_size 1m;\n\n        # Timeout for keep-alive connections. Server will close connections after\n        # this time.\n        keepalive_timeout 65;\n\n        # Sendfile copies data between one FD and other from within the kernel,\n        # which is more efficient than read() + write().\n        sendfile on;\n\n        # Don't buffer data-sends (disable Nagle algorithm).\n        # Good for sending frequent small bursts of data in real time.\n        tcp_nodelay on;\n\n        # Causes nginx to attempt to send its HTTP response head in one packet,\n        # instead of using partial frames.\n        #tcp_nopush on;\n\n\n        # Path of the file with Diffie-Hellman parameters for EDH ciphers.\n        #ssl_dhparam /etc/ssl/nginx/dh2048.pem;\n\n        # Specifies that our cipher suits should be preferred over client ciphers.\n        ssl_prefer_server_ciphers on;\n\n        # Enables a shared SSL cache with size that can hold around 8000 sessions.\n        ssl_session_cache shared:SSL:2m;\n\n\n        # Enable gzipping of responses.\n        #gzip on;\n\n        # Set the Vary HTTP header as defined in the RFC 2616.\n        gzip_vary on;\n\n        # Enable checking the existence of precompressed files.\n        #gzip_static on;\n\n\n        # Specifies the main log format.\n        log_format main '$remote_addr - $remote_user [$time_local] \"$request\" '\n                        '$status $body_bytes_sent \"$http_referer\" '\n                        '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n        # Sets the path, format, and configuration for a buffered log write.\n        access_log /var/log/nginx/access.log main;\n\n        upstream gunicorn_apiv1 {\n            server localhost:3001 fail_timeout=0;\n        }\n\n        server {\n            listen    6543;\n            server_name apiv1;\n\n            client_max_body_size 5m;\n\n            location /api/v1 {\n                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                proxy_set_header X-Forwarded-Proto $scheme;\n                proxy_set_header Host $http_host;\n                proxy_redirect off;\n                proxy_pass http://gunicorn_apiv1;\n            }\n        }\n}\n\n\n    "
  },
  {
    "path": "src/backend/configs/apiv1-supervisord.conf",
    "content": "[supervisord]\nnodaemon=true\n\n[program:gunicorn]\ncommand=/usr/local/bin/gunicorn --paste /etc/caliopen/apiv1.ini\nexitcodes=0\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n\n[program:nginx]\ncommand=/usr/sbin/nginx\nexitcodes=0\npidfile=/run/nginx.pid\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n# Graceful stop, see http://nginx.org/en/docs/control.html\nstopsignal=QUIT"
  },
  {
    "path": "src/backend/configs/apiv1.ini",
    "content": "###\n# app configuration\n# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html\n###\n\n[app:main]\nuse = egg:caliopen_api\n\npyramid.reload_templates = true\npyramid.debug_authorization = false\npyramid.debug_notfound = false\npyramid.debug_routematch = false\npyramid.default_locale_name = en\npyramid.includes =\n    pyramid_kvs\n    cornice\n    caliopen_api.base\n    caliopen_api.message\n    caliopen_api.discussion\n    caliopen_api.user\n    caliopen_api_doc\n    pyramid_swagger\n\nkvs.cache = {\"kvs\": \"redis\",\n             \"ttl\": 86400,\n             \"kvs_kwargs\": {\"host\": \"redis\"},\n            \"key_prefix\": \"tokens::\",\n            \"codec\": \"json\"}\n\n# Caliopen related configuration\ncaliopen.config =\n    global:%(here)s/caliopen.yaml\n\ncaliopen_api.route_prefix = /api/v1\n\n# Services to load\ncaliopen_api.services = caliopen_api.base\n                        caliopen_api.user\n                        caliopen_api.message\n                        caliopen_api.discussion\n\n# Swagger configuration\npyramid_swagger.schema_directory = %(here)s/\n\n###\n# wsgi server configuration\n###\n\n[server:main]\nuse = egg:gunicorn#main\nhost = localhost\nport = 3001\nworkers = 4\npreload = true\nreload = true\nloglevel = debug\n\n###\n# logging configuration\n# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html\n###\n\n[loggers]\nkeys = root, caliopen, waitress\n\n[handlers]\nkeys = console\n\n[formatters]\nkeys = generic\n\n[logger_root]\nlevel = INFO\nhandlers = console\n\n[logger_caliopen]\nlevel = INFO\nhandlers = console\nqualname = caliopen\npropagate = 0\n\n[logger_waitress]\nlevel = INFO\nhandlers = console\nqualname = waitress\npropagate = 0\n\n[handler_console]\nclass = StreamHandler\nargs = (sys.stderr,)\nlevel = NOTSET\nformatter = generic\n\n[formatter_generic]\nformat = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s\n"
  },
  {
    "path": "src/backend/configs/apiv2.yaml",
    "content": "ApiConfig:\n  listen_interface: 0.0.0.0\n  listen_port: 6544\n  hostname: http://localhost\n  port: 6544\n  BackendConfig:\n    backend_name: cassandra\n    backend_settings:\n      hosts:\n        - cassandra\n      keyspace: caliopen\n      consistency_level: 1\n      raw_size_limit: 1048576                                # max size in bytes for objects in db. Use S3 interface if larger.\n      object_store: s3\n      object_store_settings:\n        endpoint: objectstore:9090\n        access_key: CALIOPEN_ACCESS_KEY_                     # Access key of 5 to 20 characters in length\n        secret_key: CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD # Secret key of 8 to 40 characters in length\n        location: eu-fr-localhost                            # S3 region.\n        buckets:\n          raw_messages: caliopen-raw-messages                # bucket name to put raw messages to\n          temporary_attachments: caliopen-tmp-attachments    # bucket name to store draft attachments\n      use_vault: false\n      vault_settings:\n        url: http://vault:8200\n        username: api2                                       # password authentication for now ; later we'll make use of more secure auth methods (TLScert, kubernetes…)\n        password: weak_password\n  IndexConfig:\n    index_name: elasticsearch\n    index_settings:\n      hosts:\n        - http://elasticsearch:9200\n  NatsConfig:\n    url: nats://nats:4222\n    nats_queue: NotifierQueue\n    outSMTP_topic: outboundSMTP       # topic's name for \"send\" draft order via SMTP\n    outIMAP_topic: outboundIMAP       # topic's name for \"send\" draft order via remote SMTP+IMAP\n    outTWITTER_topic: twitter_dm      # topics's name for \"send\" draft order via TWITTER\n    outMASTODON_topic: mastodon_dm    # topics's name for \"send\" draft order via MASTODON\n    contacts_topic: contactAction     # topic's name to post messages regarding contacts' events\n    keys_topic: keyAction             # topic's name to post messages regarding public key events\n    users_topic: userAction           # topic's name to post messages regarding users events\n    idpoller_topic: idCache           # topic's name to post messages to idpoller regarding identities management\n  swaggerSpec: ./swagger.json #absolute path or relative path to go.server bin\n  RedisConfig:\n    host: redis:6379\n    password: \"\"        #no password set\n    db: 0               #use default db\n  NotifierConfig:\n    base_url: http://localhost:4000                         # url upon which to build custom links sent to users. NO trailing slash please.\n    admin_username: admin                                   # username on whose behalf notifiers will act. This admin user must have been created before by other means.\n    templates_path: \"../defs/notifiers/templates/\"          # path to yaml/j2 templates directory, WITH trailing slash please.\n  Providers:                                                # temporary supported providers list for remote identities before moving this data into store facility\n    - name: gmail\n      protocol: email\n      infos:\n        client_id:\n        client_secret:\n        imapserver: imap.gmail.com:993\n        smtpserver: smtp.gmail.com:587\n    - name: twitter\n      protocol: twitter\n      infos:\n        consumer_key:\n        consumer_secret:\n    - name: mastodon\n      protocol: mastodon\nProxyConfig:\n  listen_interface: 0.0.0.0\n  port: 31415\n  routes:\n    - /api/v2/: localhost:6544\n    - /: apiv1:3001         # route \"/\" catch all other requests\n\n"
  },
  {
    "path": "src/backend/configs/caliopen-api.development.ini",
    "content": "###\n# app configuration\n# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html\n###\n\n[app:main]\nuse = egg:caliopen_api\n\npyramid.reload_templates = true\npyramid.reload_assets = true\npyramid.debug_authorization = false\npyramid.debug_notfound = false\npyramid.debug_routematch = false\npyramid.default_locale_name = en\npyramid.includes =\n    pyramid_kvs\n    cornice\n    caliopen_api.base\n    caliopen_api.message\n    caliopen_api.user\n    caliopen_api_doc\n    pyramid_swagger\n\nkvs.cache = {\"kvs\": \"redis\",\n             \"ttl\": 86400,\n            \"kvs_kwargs\": {\"host\": \"redis\"},\n             \"key_prefix\": \"tokens::\",\n             \"codec\": \"json\"}\n\n\n# Caliopen related configuration\ncaliopen.config =\n    global:%(here)s/caliopen.yaml.template\n\ncaliopen_api.route_prefix = /api/v1\n\n# Services to load\ncaliopen_api.services = caliopen_api.base\n                        caliopen_api.user\n                        caliopen_api.message\n\n# Swagger configuration\npyramid_swagger.schema_directory = %(here)s/../doc/api\n\n# XXX sound not working correctly, done in configuration\n# pyramid_swagger.enable_request_validation = True\n# pyramid_swagger.enable_response_validation = True\n# pyramid_swagger.enable_api_doc_views = False\n\n###\n# wsgi server configuration\n###\n\n[server:main]\nuse = egg:waitress#main\nhost = 0.0.0.0\nport = 6543\n\n###\n# logging configuration\n# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html\n###\n\n[loggers]\nkeys = root, caliopen, waitress\n\n[handlers]\nkeys = console\n\n[formatters]\nkeys = generic\n\n[logger_root]\nlevel = INFO\nhandlers = console\n\n[logger_caliopen]\nlevel = DEBUG\nhandlers = console\nqualname = caliopen\npropagate = 0\n\n[logger_waitress]\nlevel = DEBUG\nhandlers = console\nqualname = waitress\npropagate = 0\n\n[handler_console]\nclass = StreamHandler\nargs = (sys.stderr,)\nlevel = NOTSET\nformatter = generic\n\n[formatter_generic]\nformat = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s\n"
  },
  {
    "path": "src/backend/configs/caliopen.yaml",
    "content": "# Caliopen configuration file to run REST API server in a docker container\n#\n# Storage services must be accessible using FQDN:\n#  - cassandra.dev.caliopen.org\n#  - es.dev.caliopen.org\n#  - redis.dev.caliopen.org (see pyramid configuration file)\n#\n# Make your /etc/hosts lie or user docker containers from docker-compose.yml in /devtools\n\ndelivery_agent:\n    direct: True\n\nelasticsearch:\n    url: http://elasticsearch:9200\n    shards:\n        - caliopen-dev-0001\n        - caliopen-dev-0002\n        - caliopen-dev-0003\n        - caliopen-dev-0004\n\ncassandra:\n    keyspace: caliopen\n    hosts:\n        - 'cassandra'\n    consistency_level: 1\n    protocol_version: 3\n\nlmtp:\n    port: 4025\n    bind_address: 0.0.0.0\n\nbroker:\n    port: 2525\n    host: broker\n\nmessage_queue:\n    port: 4222\n    host: nats\n\nobject_store:\n    db_size_limit: 1048576\n    service: s3\n    endpoint: objectstore:9090\n    access_key: CALIOPEN_ACCESS_KEY_\n    secret_key: CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD\n    location: eu-fr-localhost\n\nsystem:\n    max_users: 100\n    default_tags:\n        -\n            name: inbox\n        -\n            name: spam\n        -\n            name: important\n            importance_level: 5\n        -\n            name: internal\n            label: Caliopen\n            importance_level: 2\n\ndefault_domain: caliopen.local\n\ninternal_domains:\n    - caliopen.local\n    - brainstorm.fr\n\nblacklistes:\n    mx:\n        - mx.bad.guy\n\nkey_discovery:\n    dns:\n        enable: True\n        name_server: 8.8.8.8\n    hkp:\n        enable: True\n"
  },
  {
    "path": "src/backend/configs/caliopen.yaml.template",
    "content": "delivery_agent:\n    direct: True\n\nelasticsearch:\n    mappings_version: v5\n    url:\n        - http://es.dev.caliopen.org:9200\n    shards:\n        - caliopen-dev-0001\n        - caliopen-dev-0002\n        - caliopen-dev-0003\n        - caliopen-dev-0004\n\ncassandra:\n    keyspace: caliopen\n    hosts:\n        - 'cassandra.dev.caliopen.org'\n    consistency_level: 1\n    protocol_version: 3\n\nlmtp:\n    port: 4025\n    bind_address: 0.0.0.0\n\nbroker:\n    port: 2525\n    host: localhost\n\nmessage_queue:\n    port: 4222\n    host: localhost\n\nobject_store:\n    db_size_limit: 1048576\n    service: s3\n    endpoint: objectstore:9090\n    access_key: CALIOPEN_ACCESS_KEY_\n    secret_key: CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD\n    location: eu-fr-localhost\n    buckets:\n        raw_messages: caliopen-raw-messages\n        temporary_attachments: caliopen-tmp-attachments\n        learn_models: learn-models\n\nsystem:\n    max_users: 2000\n    default_tags:\n        -\n            name: inbox\n        -\n            name: spam\n        -\n            name: important\n            importance_level: 5\n        -\n            name: internal\n            label: Caliopen\n            importance_level: 2\n\ndefault_domain: caliopen.local\n\ninternal_domains:\n    - caliopen.local\n\nblacklistes:\n    mx:\n        - mx.bad.guy\n\nwhitelistes:\n    # Restrict user registration to list of recovery emails in a file\n    user_emails: /etc/caliopen/whitelist_user_email.txt\n\nkey_discovery:\n    dns:\n        enable: True\n        name_server: ns.example.com\n    hkp:\n        enable: True\n        url: https://pgp.mit.edu/pks/lookup\n\nqualifiers:\n    geoip:\n        file: \"{{ geoip2_file }}\"\n\napiV1:\n    strict_patch: false ## if set to false, API will try to silently remove unexpected properties within patch."
  },
  {
    "path": "src/backend/configs/idpoller.yaml",
    "content": "#polling config\nscan_interval: 180                              # in minutes. How often storage is scanned to sync with remote identities data\nremote_protocols:                               # which kind of remote protocols poller must handle\n  - email\n  - imap\n  - twitter\n  - mastodon\n#storage facility\nstore_name: cassandra                           # backend for remote identities data\nstore_settings:\n  hosts: # many allowed\n  - cassandra\n  keyspace: caliopen\n  consistency_level: 1\n#messaging system\nnats_url: nats://nats:4222\nnats_queue : IDpoller\nnats_topics:                                 # NATS topics to work with\n  id_cache: idCache                          # receiving orders to update poller's cache\n  imap: imapJobs                             # receiving requests for IMAP jobs\n  twitter: twitterJobs                       # receiving requests for Twitter jobs\n  mastodon: mastodonJobs                     # receiving requests for Mastodon jobs"
  },
  {
    "path": "src/backend/configs/imapworker.yaml",
    "content": "workers: 10                                             # number of concurrent workers\nhostname: localhost\n#messaging system\nnats_url: nats://nats:4222\nnats_queue: IMAPworkers                                # NATS group queue for workers\nnats_topic_poller: imapJobs                            # NATS topic on which to request job from idpoller\nnats_topic_poller_cache: idCache                       # NATS topic to send orders to idpoller regarding identities management\nnats_topic_sender: outboundIMAP                        # NATS topic to listen to actions to execute\n#storage facility\nstore_name: cassandra                                  # backend to store raw emails and messages (inbound & outbound)\nstore_settings:\n  hosts: # many allowed\n  - cassandra\n  keyspace: caliopen\n  consistency_level: 1\n  raw_size_limit: 1048576                                 # max size in bytes for objects in db. Use S3 interface if larger.\n  object_store: s3\n  object_store_settings:\n    endpoint: objectstore:9090\n    access_key: CALIOPEN_ACCESS_KEY_                     # Access key of 5 to 20 characters in length\n    secret_key: CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD # Secret key of 8 to 40 characters in length\n    location: eu-fr-localhost                            # S3 region.\n    buckets:\n      raw_messages: caliopen-raw-messages                # bucket name to put raw messages to\n      temporary_attachments: caliopen-tmp-attachments    # bucket name to store draft attachments\n  use_vault: false\n  vault_settings:\n    url: http://vault:8200\n    username: imapworker                                 # password authentication for now ; later we'll make use of more secure auth methods (TLScert, kubernetes…)\n    password: a_weak_password\nLDAConfig:\n  broker_type: imap                                      # types are : smtp, imap, mailboxe, etc.\n  lda_workers_size: 2                                    # number of concurrent workers\n  log_received_mails: true\n  #index facility\n  index_name: elasticsearch                              # backend to index messages (inbound & outbound)\n  index_settings:\n    urls: # many allowed\n    - http://elasticsearch:9200\n  #messaging system\n  in_topic: inboundSMTP # NATS topic to listen to\n  out_topic: outboundSMTP\n  nats_queue: SMTPqueue\n  # notifications\n  NotifierConfig:\n    admin_username: admin                                # username on whose behalf notifiers will act. This admin user must have been created before by other means.\n  Providers:                                             # temporary supported providers list for remote identities before moving this data into store facility\n    - name: gmail\n      protocol: email\n      infos:\n        client_id:\n        client_secret:\n"
  },
  {
    "path": "src/backend/configs/lmtp.yaml",
    "content": "## SMTP config ##\nAppConfig:\n  allowed_hosts:\n  - localhost\n  - caliopen.local\n  - caliopen.org\n  - cluster.local\n  primary_mail_host: caliopen.org\n  inbound_servers: # only one allowed for now\n  - is_enabled: true\n    host_name: localhost\n    max_size: 20971520                                   # max authorized size for emails in bytes\n    timeout: 180\n    listen_interface: 0.0.0.0:2525\n    start_tls_on: false\n    tls_always_on: false\n    max_clients: 1000\n  #submit is the MTA to connect to for final delivery (postfix for example)\n  submit_address: smtp\n  submit_port: 2500\n  submit_user:\n  submit_password:\n  submit_workers: 2                                      # number of concurrent connexions to submit MTA\n\n## LDA (Email broker) config ##\nLDAConfig:\n  broker_type: smtp                                      # types are : smtp, imap, mailboxe, etc.\n  nats_url: nats://nats:4222\n  nats_queue: SMTPqueue                                  # NATS group queue for nats subscribers to share jobs\n  store_name: cassandra                                  # backend to store raw emails and messages (inbound & outbound)\n  store_settings:\n    hosts: # many allowed\n    - cassandra\n    keyspace: caliopen\n    consistency_level: 1\n    raw_size_limit: 1048576                              # max size in bytes for objects in db. Use S3 interface if larger.\n    object_store: s3\n    object_store_settings:\n      endpoint: objectstore:9090\n      access_key: CALIOPEN_ACCESS_KEY_                     # Access key of 5 to 20 characters in length\n      secret_key: CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD # Secret key of 8 to 40 characters in length\n      location: eu-fr-localhost                            # S3 region.\n      buckets:\n        raw_messages: caliopen-raw-messages                # bucket name to put raw messages to\n        temporary_attachments: caliopen-tmp-attachments    # bucket name to store draft attachments\n    use_vault: false\n    vault_settings:\n      url: http://vault:8200\n      username: lmtpd                                    # password authentication for now ; later we'll make use of more secure auth methods (TLScert, kubernetes…)\n      password: still_a_weak_password\n  index_name: elasticsearch                              # backend to index messages (inbound & outbound)\n  index_settings:\n    urls: # many allowed\n    - http://elasticsearch:9200\n\n  #inbound\n  in_topic: inboundSMTP                                  # NATS topic to listen to\n  lda_workers_size: 2                                    # number of concurrent workers\n  log_received_mails: true\n\n  # outbound\n  out_topic: outboundSMTP                                # NATS topic to listen to\n  nats_listeners: 2                                      # number of concurrent nats listeners\n\n  # notifications\n  contacts_topic: contactAction                             # topic's name to post messages regarding contacts' events\n  NotifierConfig:\n    base_url: http://localhost:4000                         # url upon which to build custom links sent to users. NO trailing slash please.\n    admin_username: admin                                   # username on whose behalf notifiers will act. This admin user must have been created before by other means.\n    templates_path: \"../defs/notifiers/templates/\"          # path to yaml/j2 templates directory, WITH trailing slash please."
  },
  {
    "path": "src/backend/configs/mastodonworker.yaml",
    "content": "workers: 10\nmastodon_app_id:\nmastodon_app_secret:\nBrokerConfig:\n  #messaging system\n  nats_url: nats://nats:4222\n  nats_queue: Mastodonworkers                              # NATS group queue for workers\n  nats_topic_poller: mastodonJobs                          # NATS topic on which to request job from idpoller\n  nats_topic_poller_cache: idCache                         # NATS topic to send orders to idpoller regarding identities management\n  nats_topic_direct_message: mastodon_dm                   # NATS topic to listen to orders for handling DMs (fetch, send)\n  #storage facility\n  store_name: cassandra                                    # backend to store raw emails and messages (inbound & outbound)\n  store_settings:\n    hosts: # many allowed\n      - cassandra\n    keyspace: caliopen\n    consistency_level: 1\n    raw_size_limit: 1048576                                # max size in bytes for objects in db. Use S3 interface if larger.\n    object_store: s3\n    object_store_settings:\n      endpoint: objectstore:9090\n      access_key: CALIOPEN_ACCESS_KEY_                     # Access key of 5 to 20 characters in length\n      secret_key: CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD # Secret key of 8 to 40 characters in length\n      location: eu-fr-localhost                            # S3 region.\n      buckets:\n        raw_messages: caliopen-raw-messages                # bucket name to put raw messages to\n        temporary_attachments: caliopen-tmp-attachments    # bucket name to store draft attachments\n    use_vault: false\n    vault_settings:\n      url: http://vault:8200\n      username: mastodonworker                             # password authentication for now ; later we'll make use of more secure auth methods (TLScert, kubernetes…)\n      password: a_weak_password_for_mastodon\n  LDAConfig:\n    broker_type: mastodon                                  # types are : smtp, imap, mailboxe, etc.\n    #index facility\n    index_name: elasticsearch                              # backend to index messages (inbound & outbound)\n    index_settings:\n      urls: # many allowed\n        - http://elasticsearch:9200\n    #messaging system\n    in_topic: inboundMastodon\n    # notifications\n    NotifierConfig:\n      admin_username: admin                                # username on whose behalf notifiers will act. This admin user must have been created before by other means."
  },
  {
    "path": "src/backend/configs/swagger.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"version\": \"0.2.0\",\n    \"title\": \"Caliopen HTTP/REST API\"\n  },\n  \"host\": \"localhost:31415\",\n  \"basePath\": \"/api\",\n  \"schemes\": [\n    \"http\"\n  ],\n  \"paths\": {\n    \"/v1/authentications\": {\n      \"post\": {\n        \"description\": \"Returns an auth token to build basicAuth for the provided credentials\",\n        \"tags\": [\n          \"users\"\n        ],\n        \"security\": [],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"authentication\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/Authentication\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful authentication\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"username\": {\n                  \"type\": \"string\"\n                },\n                \"user_id\": {\n                  \"type\": \"string\",\n                  \"description\": \"the user_id makes the 'username' for basicAuth\"\n                },\n                \"tokens\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"access_token\": {\n                      \"type\": \"string\",\n                      \"description\": \"the access_token makes the 'password' for basicAuth\"\n                    },\n                    \"expires_in\": {\n                      \"type\": \"integer\",\n                      \"format\": \"int32\"\n                    },\n                    \"expires_at\": {\n                      \"type\": \"string\"\n                    },\n                    \"refresh_token\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                },\n                \"device\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"device_id\": {\n                      \"type\": \"string\",\n                      \"description\": \"Identifier of the device used for authentication\"\n                    },\n                    \"status\": {\n                      \"type\": \"string\",\n                      \"description\": \"Device status\"\n                    }\n                  }\n                }\n              },\n              \"additionalProperties\": false\n            }\n          },\n          \"401\": {\n            \"description\": \"Authentication error\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v1/me\": {\n      \"get\": {\n        \"description\": \"Gets `user + contact` objects for current logged-in user\",\n        \"tags\": [\n          \"users\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"X-Caliopen-PI\",\n            \"in\": \"header\",\n            \"required\": true,\n            \"description\": \"The PI range requested in form of `1;100`\",\n            \"type\": \"string\",\n            \"default\": \"1;100\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful response with json object\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/User\"\n            }\n          }\n        }\n      }\n    },\n    \"/v1/users\": {\n      \"post\": {\n        \"description\": \"Create a new User with provided credentials\",\n        \"tags\": [\n          \"users\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"user\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/NewUser\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"User creation completed\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"location\": {\n                  \"type\": \"string\",\n                  \"description\": \"url to retrieve new user's infos at /users/{user_id}\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"406\": {\n            \"description\": \"NotAcceptable. Server refuses to fulfill the request\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/users/{user_id}\": {\n      \"patch\": {\n        \"description\": \"Partially implemented. Currently only for changing password.\",\n        \"tags\": [\n          \"users\",\n          \"password\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"user_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"attachments\": {\n                      \"type\": \"array\",\n                      \"items\": {\n                        \"$ref\": \"#/definitions/Attachment\"\n                      }\n                    },\n                    \"body\": {\n                      \"type\": \"string\"\n                    },\n                    \"body_is_plain\": {\n                      \"type\": \"boolean\"\n                    },\n                    \"date\": {\n                      \"type\": \"string\",\n                      \"format\": \"date-time\"\n                    },\n                    \"date_delete\": {\n                      \"type\": \"string\",\n                      \"format\": \"date-time\"\n                    },\n                    \"date_insert\": {\n                      \"type\": \"string\",\n                      \"format\": \"date-time\"\n                    },\n                    \"date_sort\": {\n                      \"type\": \"string\",\n                      \"format\": \"date-time\"\n                    },\n                    \"discussion_id\": {\n                      \"type\": \"string\"\n                    },\n                    \"external_references\": {\n                      \"type\": \"object\",\n                      \"$ref\": \"#/definitions/ExternalReferences\"\n                    },\n                    \"excerpt\": {\n                      \"type\": \"string\"\n                    },\n                    \"user_identities\": {\n                      \"type\": \"array\",\n                      \"items\": {\n                        \"type\": \"string\"\n                      }\n                    },\n                    \"importance_level\": {\n                      \"type\": \"integer\",\n                      \"format\": \"int32\"\n                    },\n                    \"is_answered\": {\n                      \"type\": \"boolean\"\n                    },\n                    \"is_draft\": {\n                      \"type\": \"boolean\"\n                    },\n                    \"is_unread\": {\n                      \"type\": \"boolean\"\n                    },\n                    \"is_received\": {\n                      \"type\": \"boolean\"\n                    },\n                    \"message_id\": {\n                      \"type\": \"string\"\n                    },\n                    \"parent_id\": {\n                      \"type\": \"string\"\n                    },\n                    \"participants\": {\n                      \"type\": \"array\",\n                      \"items\": {\n                        \"$ref\": \"#/definitions/Participant\"\n                      }\n                    },\n                    \"privacy_features\": {\n                      \"type\": \"object\",\n                      \"$ref\": \"#/definitions/PrivacyFeatures\"\n                    },\n                    \"pi\": {\n                      \"type\": \"object\",\n                      \"$ref\": \"#/definitions/PI\"\n                    },\n                    \"pi_message\": {\n                      \"type\": \"object\",\n                      \"$ref\": \"#/definitions/PIMessage\"\n                    },\n                    \"raw_msg_id\": {\n                      \"type\": \"string\"\n                    },\n                    \"subject\": {\n                      \"type\": \"string\"\n                    },\n                    \"tags\": {\n                      \"type\": \"array\",\n                      \"items\": {\n                        \"type\": \"string\"\n                      }\n                    },\n                    \"protocol\": {\n                      \"type\": \"string\"\n                    },\n                    \"user_id\": {\n                      \"type\": \"string\"\n                    }\n                  }\n                },\n                \"attachments\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Attachment\"\n                  }\n                },\n                \"body\": {\n                  \"type\": \"string\"\n                },\n                \"body_is_plain\": {\n                  \"type\": \"boolean\"\n                },\n                \"date\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"date_delete\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"date_insert\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"date_sort\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"discussion_id\": {\n                  \"type\": \"string\"\n                },\n                \"external_references\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/ExternalReferences\"\n                },\n                \"excerpt\": {\n                  \"type\": \"string\"\n                },\n                \"user_identities\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"importance_level\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                },\n                \"is_answered\": {\n                  \"type\": \"boolean\"\n                },\n                \"is_draft\": {\n                  \"type\": \"boolean\"\n                },\n                \"is_unread\": {\n                  \"type\": \"boolean\"\n                },\n                \"is_received\": {\n                  \"type\": \"boolean\"\n                },\n                \"message_id\": {\n                  \"type\": \"string\"\n                },\n                \"parent_id\": {\n                  \"type\": \"string\"\n                },\n                \"participants\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Participant\"\n                  }\n                },\n                \"privacy_features\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PrivacyFeatures\"\n                },\n                \"pi\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PI\"\n                },\n                \"pi_message\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PIMessage\"\n                },\n                \"raw_msg_id\": {\n                  \"type\": \"string\"\n                },\n                \"subject\": {\n                  \"type\": \"string\"\n                },\n                \"tags\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"protocol\": {\n                  \"type\": \"string\"\n                },\n                \"user_id\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"required\": [\n                \"current_state\"\n              ]\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Update successful. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"message not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but patch was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/users/{user_id}/actions\": {\n      \"post\": {\n        \"description\": \"send an order to execute one (or many) action(s) regarding the user : reset_password, etc. A successful execution of the action will probably modify one or more user's attribute(s) ## NOT YET IMPLEMENTED ##\",\n        \"tags\": [\n          \"users\",\n          \"password\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"user_id\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          },\n          {\n            \"name\": \"actions\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/Actions\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"action(s) successfully executed. Nothing returned.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/MessageV2\"\n            }\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden action. Server is refusing to execute the given action(s) to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Message not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but action was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"424\": {\n            \"description\": \"execution of action failed.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/username/isAvailable\": {\n      \"get\": {\n        \"description\": \"Check if an username is available for creation within Caliopen instance\",\n        \"tags\": [\n          \"users\",\n          \"username\"\n        ],\n        \"security\": [],\n        \"parameters\": [\n          {\n            \"name\": \"username\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"availability state for requested username\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"username\": {\n                  \"type\": \"string\"\n                },\n                \"available\": {\n                  \"type\": \"boolean\"\n                }\n              },\n              \"required\": [\n                \"username\",\n                \"available\"\n              ],\n              \"additionalProperties\": false\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request (probably missing 'username' query param)\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"username\": {\n                  \"type\": \"string\"\n                },\n                \"available\": {\n                  \"type\": \"boolean\"\n                }\n              },\n              \"required\": [\n                \"username\",\n                \"available\"\n              ]\n            }\n          }\n        }\n      }\n    },\n    \"/v1/settings\": {\n      \"get\": {\n        \"description\": \"Returns settings belonging to current user\",\n        \"tags\": [\n          \"settings\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Settings returned\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Settings\"\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"description\": \"Update settings with rfc5789 and rfc7396 specifications\",\n        \"tags\": [\n          \"settings\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"$ref\": \"../objects/Settings.yaml#/properties\"\n                  }\n                },\n                \"default_locale\": {\n                  \"type\": \"string\",\n                  \"default\": \"fr-FR\"\n                },\n                \"message_display_format\": {\n                  \"type\": \"string\",\n                  \"default\": \"rich_text\"\n                },\n                \"contact_display_order\": {\n                  \"type\": \"string\",\n                  \"default\": \"given_name\"\n                },\n                \"contact_display_format\": {\n                  \"type\": \"string\",\n                  \"default\": \"family_name, given_name\"\n                },\n                \"notification_enabled\": {\n                  \"type\": \"boolean\",\n                  \"default\": true\n                },\n                \"notification_message_preview\": {\n                  \"type\": \"string\",\n                  \"default\": \"always\"\n                },\n                \"notification_sound_enabled\": {\n                  \"type\": \"boolean\",\n                  \"default\": false\n                },\n                \"notification_delay_disappear\": {\n                  \"type\": \"integer\",\n                  \"default\": 10\n                }\n              }\n            }\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Update successful. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"patch was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/identities/locals\": {\n      \"get\": {\n        \"description\": \"returns the list of user's local identities\",\n        \"tags\": [\n          \"identities\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Local identities returned\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"number of local identities found for user\"\n                },\n                \"local_identities\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/UserIdentity\"\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/identities/remotes\": {\n      \"get\": {\n        \"description\": \"returns the list of user's remote identities, or filtered if query param\",\n        \"tags\": [\n          \"identities\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"pending\",\n            \"in\": \"query\",\n            \"type\": \"string\",\n            \"required\": false\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Remote identities returned\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"number of remote identities found for user\"\n                },\n                \"remote_identities\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/UserIdentity\"\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"post\": {\n        \"description\": \"create a new remote identity for user\",\n        \"tags\": [\n          \"identities\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"identity\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/NewUserIdentity\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Remote identity created\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"location\": {\n                  \"type\": \"string\",\n                  \"description\": \"url to retrieve new remote identity's at identities/remotes/{identifier}\"\n                },\n                \"identifier\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/v2/identities/remotes/{identifier}\": {\n      \"get\": {\n        \"description\": \"returns a remote identity belonging to user\",\n        \"tags\": [\n          \"identities\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"identifier\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Remote identity returned\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/UserIdentity\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Remote identity not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"Delete a remote identity belonging to user\",\n        \"tags\": [\n          \"identities\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"identifier\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Remote identity deleted\"\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Contact not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"description\": \"update a remote identity with rfc5789 and rfc7396 specifications\",\n        \"tags\": [\n          \"identities\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"identifier\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"$ref\": \"../objects/UserIdentity.yaml#/properties\"\n                  }\n                },\n                \"credentials\": {\n                  \"type\": \"object\",\n                  \"additionalProperties\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"display_name\": {\n                  \"type\": \"string\"\n                },\n                \"identity_id\": {\n                  \"type\": \"string\"\n                },\n                \"identifier\": {\n                  \"type\": \"string\"\n                },\n                \"infos\": {\n                  \"type\": \"object\",\n                  \"additionalProperties\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"last_check\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"protocol\": {\n                  \"type\": \"string\",\n                  \"enum\": [\n                    \"email\",\n                    \"twitter\"\n                  ]\n                },\n                \"status\": {\n                  \"type\": \"string\",\n                  \"enum\": [\n                    \"active\",\n                    \"inactive\",\n                    \"deleted\"\n                  ]\n                },\n                \"type\": {\n                  \"type\": \"string\",\n                  \"enum\": [\n                    \"local\",\n                    \"remote\"\n                  ]\n                },\n                \"user_id\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"required\": [\n                \"current_state\"\n              ]\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Update successful. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"identity not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but patch was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/passwords/reset\": {\n      \"post\": {\n        \"description\": \"Route to receive a \\\"reset password\\\" request from an anonymous user.\",\n        \"tags\": [\n          \"passwords\"\n        ],\n        \"security\": [],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"reset_request\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"data the user has typed into the reset form\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"recovery_email\": {\n                  \"type\": \"string\"\n                },\n                \"username\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"additionalProperties\": false\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Request successfully handled. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"user not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"424\": {\n            \"description\": \"Parameters were valid but the server was unable to handle the request.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/passwords/reset/{token}\": {\n      \"get\": {\n        \"description\": \"Returns an auth token to build basicAuth, if the token in path is valid\",\n        \"tags\": [\n          \"passwords\"\n        ],\n        \"security\": [],\n        \"parameters\": [\n          {\n            \"name\": \"token\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Route is valid. No body is returned.\"\n          },\n          \"404\": {\n            \"description\": \"Token not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"post\": {\n        \"description\": \"User posts a new password\",\n        \"tags\": [\n          \"passwords\"\n        ],\n        \"security\": [],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"token\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          },\n          {\n            \"name\": \"new_password\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"A simple json with the new password as a string\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"password\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"additionalProperties\": false,\n              \"required\": [\n                \"password\"\n              ]\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Password update successful. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"424\": {\n            \"description\": \"Parameters were valid but the server was unable to change the password.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/providers\": {\n      \"get\": {\n        \"description\": \"returns list of supported external providers for remote identities creation\",\n        \"tags\": [\n          \"identities\",\n          \"providers\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"List of supported providers\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                },\n                \"providers\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/providers/{provider_name}\": {\n      \"get\": {\n        \"description\": \"returns provider's properties, notably the url to call for initiating Oauth process\",\n        \"tags\": [\n          \"identities\",\n          \"providers\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"provider_name\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          },\n          {\n            \"name\": \"identifier\",\n            \"in\": \"query\",\n            \"type\": \"string\",\n            \"required\": false\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"providers infos\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"name\": {\n                  \"type\": \"string\"\n                },\n                \"oauth_request_url\": {\n                  \"type\": \"string\"\n                },\n                \"protocol\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/providers/{provider_name}/callback\": {\n      \"get\": {\n        \"description\": \"url registered at provider to which its API will redirect user after user authentication\",\n        \"tags\": [\n          \"identities\",\n          \"providers\"\n        ],\n        \"security\": [],\n        \"parameters\": [\n          {\n            \"name\": \"provider_name\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"a simple message for user to inform Oauth process completion\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"success\": {\n                  \"type\": \"boolean\"\n                },\n                \"message\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/v2/test/oauth\": {\n      \"get\": {\n        \"description\": \"serves an index to test Oauth processes\",\n        \"tags\": [\n          \"providers\"\n        ],\n        \"security\": [],\n        \"parameters\": [],\n        \"produces\": [\n          \"text/html\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"index to trigger oauth popup\"\n          }\n        }\n      }\n    },\n    \"/v2/contacts\": {\n      \"get\": {\n        \"description\": \"Returns contacts list for current user according to given params\",\n        \"tags\": [\n          \"contacts\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"X-Caliopen-PI\",\n            \"in\": \"header\",\n            \"required\": true,\n            \"description\": \"The PI range requested in form of `1;100`\",\n            \"type\": \"string\",\n            \"default\": \"1;100\"\n          },\n          {\n            \"name\": \"X-Caliopen-IL\",\n            \"in\": \"header\",\n            \"required\": true,\n            \"description\": \"The Importance Level range requested in form of `-10;10`\",\n            \"type\": \"string\",\n            \"default\": \"-10;10\"\n          },\n          {\n            \"name\": \"limit\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"description\": \"number of contacts to return per page\"\n          },\n          {\n            \"name\": \"offset\",\n            \"in\": \"query\",\n            \"type\": \"integer\",\n            \"required\": false,\n            \"description\": \"number of pages to skip from the response\"\n          },\n          {\n            \"name\": \"uri\",\n            \"in\": \"query\",\n            \"type\": \"string\",\n            \"required\": false,\n            \"description\": \"return contact that has this uri embedded, if any\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Contacts list\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"number of contacts found for current user for the given parameters\"\n                },\n                \"contacts\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Contact\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      },\n      \"post\": {\n        \"description\": \"Create a new contact for the logged-in user\",\n        \"tags\": [\n          \"contacts\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact\",\n            \"in\": \"body\",\n            \"description\": \"the contact to create\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/NewContact\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Contact created\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"location\": {\n                  \"type\": \"string\",\n                  \"description\": \"url to retrieve new contact's infos at /contacts/{contact_id}\"\n                },\n                \"contact_id\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/v2/contacts/{contact_id}\": {\n      \"get\": {\n        \"description\": \"Returns a contact\",\n        \"tags\": [\n          \"contacts\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Contact found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Contact\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Contact not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"Delete a contact\",\n        \"tags\": [\n          \"contacts\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Contact deleted\"\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Contact not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"description\": \"update a contact with rfc5789 and rfc7396 specifications\",\n        \"tags\": [\n          \"contacts\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"$ref\": \"../objects/Contact.yaml#/properties\"\n                  }\n                },\n                \"additional_name\": {\n                  \"type\": \"string\"\n                },\n                \"addresses\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/PostalAddress\"\n                  }\n                },\n                \"avatar\": {\n                  \"type\": \"string\"\n                },\n                \"contact_id\": {\n                  \"type\": \"string\"\n                },\n                \"date_insert\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"date_update\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"deleted\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"emails\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Email\"\n                  }\n                },\n                \"family_name\": {\n                  \"type\": \"string\"\n                },\n                \"given_name\": {\n                  \"type\": \"string\"\n                },\n                \"groups\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"identities\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/SocialIdentity\"\n                  }\n                },\n                \"ims\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/IM\"\n                  }\n                },\n                \"infos\": {\n                  \"type\": \"object\"\n                },\n                \"name_prefix\": {\n                  \"type\": \"string\"\n                },\n                \"name_suffix\": {\n                  \"type\": \"string\"\n                },\n                \"organizations\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Organization\"\n                  }\n                },\n                \"phones\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Phone\"\n                  }\n                },\n                \"pi\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PI\"\n                },\n                \"privacy_features\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PrivacyFeatures\"\n                },\n                \"public_keys\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/PublicKey\"\n                  }\n                },\n                \"tags\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"title\": {\n                  \"type\": \"string\"\n                },\n                \"user_id\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"required\": [\n                \"current_state\"\n              ]\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Update successful. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"contact not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but patch was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/contacts/{contact_id}/identities\": {\n      \"get\": {\n        \"description\": \"returns a list of contact's identities\",\n        \"tags\": [\n          \"contacts\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Contact's identities returned\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"number of identities found for the contact\"\n                },\n                \"contact_identities\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/ContactIdentity\"\n                  }\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Contact not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"description\": \"update tags list for contact\",\n        \"tags\": [\n          \"contacts\",\n          \"tags\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"$ref\": \"../objects/Contact.yaml#/properties\"\n                  }\n                },\n                \"additional_name\": {\n                  \"type\": \"string\"\n                },\n                \"addresses\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/PostalAddress\"\n                  }\n                },\n                \"avatar\": {\n                  \"type\": \"string\"\n                },\n                \"contact_id\": {\n                  \"type\": \"string\"\n                },\n                \"date_insert\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"date_update\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"deleted\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"emails\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Email\"\n                  }\n                },\n                \"family_name\": {\n                  \"type\": \"string\"\n                },\n                \"given_name\": {\n                  \"type\": \"string\"\n                },\n                \"groups\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"identities\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/SocialIdentity\"\n                  }\n                },\n                \"ims\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/IM\"\n                  }\n                },\n                \"infos\": {\n                  \"type\": \"object\"\n                },\n                \"name_prefix\": {\n                  \"type\": \"string\"\n                },\n                \"name_suffix\": {\n                  \"type\": \"string\"\n                },\n                \"organizations\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Organization\"\n                  }\n                },\n                \"phones\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Phone\"\n                  }\n                },\n                \"pi\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PI\"\n                },\n                \"privacy_features\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PrivacyFeatures\"\n                },\n                \"public_keys\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/PublicKey\"\n                  }\n                },\n                \"tags\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"title\": {\n                  \"type\": \"string\"\n                },\n                \"user_id\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"required\": [\n                \"current_state\"\n              ]\n            }\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Contact's tags updated. Empty body.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden, server is refusing to apply payload to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"contact not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but payload was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"424\": {\n            \"description\": \"server failed to execute the request\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/contacts/{contact_id}/publickeys\": {\n      \"post\": {\n        \"description\": \"Add a pgp public key to a contact\",\n        \"tags\": [\n          \"pgp\",\n          \"keys\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"key\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/NewPublicKey\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Public key created\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"location\": {\n                  \"type\": \"string\",\n                  \"description\": \"url to retrieve public key /contacts/{contact_id}/publickeys/{publickey_id}\"\n                },\n                \"publickey_id\": {\n                  \"type\": \"string\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"Unprocessable entity. Parameters were valid but the server was unable to create the key (probably because of a semantically erroneous document)\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"get\": {\n        \"description\": \"Returns all publickeys linked to contact\",\n        \"tags\": [\n          \"pgp\",\n          \"keys\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"List of public keys\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"number of keys found\"\n                },\n                \"pubkeys\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/PublicKey\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/v2/contacts/{contact_id}/publickeys/{pubkey_id}\": {\n      \"get\": {\n        \"description\": \"Retrieve publickey\",\n        \"tags\": [\n          \"keys\",\n          \"pgp\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pubkey_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful response with json object\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/PublicKey\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"description\": \"update a public key\",\n        \"tags\": [\n          \"keys\",\n          \"pgp\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pubkey_id\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. Property _label_ is the only one patchable.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"label\": {\n                  \"type\": \"string\"\n                },\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"label\": {\n                      \"type\": \"string\"\n                    }\n                  },\n                  \"additionalProperties\": false\n                }\n              },\n              \"additionalProperties\": false,\n              \"required\": [\n                \"current_state\",\n                \"label\"\n              ]\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Update successful. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"public key not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"409\": {\n            \"description\": \"json is valid but patch conflicts with db's current state\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but patch was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"Delete a public key\",\n        \"tags\": [\n          \"keys\",\n          \"pgp\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pubkey_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Successful deletion\"\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/contacts/{contact_id}/tags\": {\n      \"patch\": {\n        \"description\": \"update tags list for contact\",\n        \"tags\": [\n          \"contacts\",\n          \"tags\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"contact_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"tags\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"tags\": {\n                      \"type\": \"array\",\n                      \"items\": {\n                        \"type\": \"string\"\n                      }\n                    }\n                  }\n                }\n              },\n              \"required\": [\n                \"tags\",\n                \"current_state\"\n              ]\n            }\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Contact's tags updated. Empty body.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden, server is refusing to apply payload to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"contact not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but payload was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"424\": {\n            \"description\": \"server failed to execute the request\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/imports\": {\n      \"post\": {\n        \"consumes\": [\n          \"multipart/form-data\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"file\",\n            \"in\": \"formData\",\n            \"description\": \"the vcard file to upload\",\n            \"type\": \"file\",\n            \"required\": true\n          }\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Import successful\"\n          },\n          \"400\": {\n            \"description\": \"Syntax error\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"File valid but we can create the new contact\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/discussions\": {\n      \"get\": {\n        \"description\": \"Returns the list of discussions for current user according to given filter\",\n        \"tags\": [\n          \"discussions\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"X-Caliopen-PI\",\n            \"in\": \"header\",\n            \"required\": true,\n            \"description\": \"The PI range requested in form of `0;100`\",\n            \"type\": \"string\",\n            \"default\": \"0;100\"\n          },\n          {\n            \"name\": \"X-Caliopen-IL\",\n            \"in\": \"header\",\n            \"required\": true,\n            \"description\": \"The Importance Level range requested in form of `-10;10`\",\n            \"type\": \"string\",\n            \"default\": \"-10;10\"\n          },\n          {\n            \"name\": \"limit\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"description\": \"number of discussions to return per page\"\n          },\n          {\n            \"name\": \"offset\",\n            \"in\": \"query\",\n            \"type\": \"integer\",\n            \"required\": false,\n            \"description\": \"number of discussions to skip for pagination\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Discussions returned\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"number of discussions found for current user for the given parameters\"\n                },\n                \"discussions\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Discussion\"\n                  }\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/discussions/{discussion_id}\": {\n      \"get\": {\n        \"description\": \"Returns metadata of a discussion\",\n        \"tags\": [\n          \"discussions\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"discussion_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Discussion found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Discussion\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Discussion not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v1/messages\": {\n      \"post\": {\n        \"description\": \"Create a new Message (draft) for an user\",\n        \"tags\": [\n          \"messages\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"message\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/NewMessageV2\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Draft creation completed\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"location\": {\n                  \"type\": \"string\",\n                  \"description\": \"url to retrieve draft at /messages/{message_id}\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"Unprocessable entity. Parameters were valid but the server was unable to create the draft (probably because of a semantically erroneous document, like an empty draft)\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/messages\": {\n      \"get\": {\n        \"description\": \"Returns the list of messages for current user according to given parameters/filter\",\n        \"tags\": [\n          \"messages\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"X-Caliopen-PI\",\n            \"in\": \"header\",\n            \"required\": true,\n            \"description\": \"The PI range requested in form of `0;100`\",\n            \"type\": \"string\",\n            \"default\": \"0;100\"\n          },\n          {\n            \"name\": \"X-Caliopen-IL\",\n            \"in\": \"header\",\n            \"required\": true,\n            \"description\": \"The Importance Level range requested in form of `-10;10`\",\n            \"type\": \"string\",\n            \"default\": \"-10;10\"\n          },\n          {\n            \"name\": \"discussion_id\",\n            \"in\": \"query\",\n            \"description\": \"filter messages belonging to a specific discussion\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"limit\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"description\": \"number of messages to return per page\"\n          },\n          {\n            \"name\": \"offset\",\n            \"in\": \"query\",\n            \"type\": \"integer\",\n            \"required\": false,\n            \"description\": \"number of pages to skip from the response\"\n          },\n          {\n            \"name\": \"msg_id\",\n            \"in\": \"query\",\n            \"type\": \"string\",\n            \"required\": false,\n            \"description\": \"if provided with range[] param, specify a message_id around which messages will be fetched\"\n          },\n          {\n            \"name\": \"range[]\",\n            \"in\": \"query\",\n            \"type\": \"string\",\n            \"required\": false,\n            \"description\": \"boundaries param if message_id param is provided [before, after]\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Messages returned\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"number of messages found for user for the given parameters\"\n                },\n                \"messages\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/MessageV2\"\n                  }\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v1/messages/{message_id}\": {\n      \"patch\": {\n        \"description\": \"update a draft with rfc5789 and rfc7396 specifications\",\n        \"tags\": [\n          \"messages\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"message_id\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"$ref\": \"../objects/MessageV2.yaml#/properties\"\n                  }\n                },\n                \"attachments\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Attachment\"\n                  }\n                },\n                \"body\": {\n                  \"type\": \"string\"\n                },\n                \"body_is_plain\": {\n                  \"type\": \"boolean\"\n                },\n                \"date\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"date_delete\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"date_insert\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"date_sort\": {\n                  \"type\": \"string\",\n                  \"format\": \"date-time\"\n                },\n                \"discussion_id\": {\n                  \"type\": \"string\"\n                },\n                \"external_references\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/ExternalReferences\"\n                },\n                \"excerpt\": {\n                  \"type\": \"string\"\n                },\n                \"user_identities\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"importance_level\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                },\n                \"is_answered\": {\n                  \"type\": \"boolean\"\n                },\n                \"is_draft\": {\n                  \"type\": \"boolean\"\n                },\n                \"is_unread\": {\n                  \"type\": \"boolean\"\n                },\n                \"is_received\": {\n                  \"type\": \"boolean\"\n                },\n                \"message_id\": {\n                  \"type\": \"string\"\n                },\n                \"parent_id\": {\n                  \"type\": \"string\"\n                },\n                \"participants\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Participant\"\n                  }\n                },\n                \"privacy_features\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PrivacyFeatures\"\n                },\n                \"pi\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PI\"\n                },\n                \"pi_message\": {\n                  \"type\": \"object\",\n                  \"$ref\": \"#/definitions/PIMessage\"\n                },\n                \"raw_msg_id\": {\n                  \"type\": \"string\"\n                },\n                \"subject\": {\n                  \"type\": \"string\"\n                },\n                \"tags\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"protocol\": {\n                  \"type\": \"string\"\n                },\n                \"user_id\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"required\": [\n                \"current_state\"\n              ]\n            }\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Update successful. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"message not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but patch was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"Delete a message belonging to an user\",\n        \"tags\": [\n          \"messages\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"message_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Successful deletion. No body is returned\"\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden action. Server is refusing to delete this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"message not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/messages/{message_id}\": {\n      \"get\": {\n        \"description\": \"returns a message\",\n        \"tags\": [\n          \"messages\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"message_id\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Message found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/MessageV2\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Message not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/messages/{message_id}/tags\": {\n      \"patch\": {\n        \"description\": \"update tags list for message\",\n        \"tags\": [\n          \"messages\",\n          \"tags\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"message_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"tags\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                },\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"tags\": {\n                      \"type\": \"array\",\n                      \"items\": {\n                        \"type\": \"string\"\n                      }\n                    }\n                  }\n                }\n              },\n              \"required\": [\n                \"tags\",\n                \"current_state\"\n              ]\n            }\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"message's tags updated. Empty body.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden, server is refusing to apply payload to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"message not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but payload was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"424\": {\n            \"description\": \"server failed to execute the request\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/messages/{message_id}/actions\": {\n      \"post\": {\n        \"description\": \"send an order to execute one (or many) action(s) for the given message : send, etc. A successful execution of the action will probably modify one or more message's attribute(s)\",\n        \"tags\": [\n          \"messages\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"message_id\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          },\n          {\n            \"name\": \"actions\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/Actions\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"action(s) successfully executed. Message is sent back with its modified attributes (if any)\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/MessageV2\"\n            }\n          },\n          \"204\": {\n            \"description\": \"action(s) successfully executed. Nothing returned.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/MessageV2\"\n            }\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden action. Server is refusing to execute the given action(s) to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Message not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but action was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"424\": {\n            \"description\": \"execution of action failed.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/messages/{message_id}/attachments\": {\n      \"post\": {\n        \"description\": \"(for draft only) upload a file to server and add attachment reference to the draft.\",\n        \"tags\": [\n          \"attachments\",\n          \"messages\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"multipart/form-data\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"message_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"attachment\",\n            \"in\": \"formData\",\n            \"description\": \"the attachment file to upload\",\n            \"type\": \"file\",\n            \"required\": true\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"201\": {\n            \"description\": \"File uploaded sucessfully. Backend sends back attachment's temp_id.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"temp_id\": {\n                  \"type\": \"string\",\n                  \"description\": \"attachment's temporary id. temp_id exists as long as message has 'draft' status.\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"bad request\"\n          },\n          \"401\": {\n            \"description\": \"Unauthorized\"\n          },\n          \"404\": {\n            \"description\": \"Not Found\"\n          },\n          \"413\": {\n            \"description\": \"Request entity too large\"\n          },\n          \"415\": {\n            \"description\": \"Unsupported media type\"\n          }\n        }\n      }\n    },\n    \"/v2/messages/{message_id}/attachments/{attachment_id}\": {\n      \"get\": {\n        \"description\": \"Download file from server\",\n        \"tags\": [\n          \"messages\",\n          \"attachments\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"message_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"attachment_id\",\n            \"description\": \"attachment position within message\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"the attachment, in the 'content-transfer-encoding' format of the part.\",\n            \"schema\": {\n              \"type\": \"file\"\n            }\n          },\n          \"400\": {\n            \"description\": \"bad request\"\n          },\n          \"401\": {\n            \"description\": \"Unauthorized\"\n          },\n          \"404\": {\n            \"description\": \"message or attachment not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"(for drafts only) delete temporary file and remove attachment reference from the draft.\",\n        \"tags\": [\n          \"messages\",\n          \"attachments\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"message_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"attachment_id\",\n            \"description\": \"attachment's temporary id.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Successful deletion. No body is returned\"\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden action. Server is refusing to delete this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"message or attachment not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v1/raws/{raw_msg_id}\": {\n      \"get\": {\n        \"description\": \"Returns a raw message\",\n        \"tags\": [\n          \"messages\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"raw_msg_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"text/plain\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"the raw message\",\n            \"schema\": {\n              \"type\": \"string\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/tags\": {\n      \"get\": {\n        \"description\": \"Returns tags visible to current user according to given parameters\",\n        \"tags\": [\n          \"tags\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Tags returned\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"number of tags found for user for the given parameters\"\n                },\n                \"tags\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Tag\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      },\n      \"post\": {\n        \"description\": \"Create a new Tag for an user\",\n        \"tags\": [\n          \"tags\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"tag\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/NewTag\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"User tag creation completed\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"location\": {\n                  \"type\": \"string\",\n                  \"description\": \"url to retrieve new tag's infos at /tags/{name}\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"Unprocessable entity. Parameters were valid but the server was unable to create the tag (probably because of a semantically erroneous document, like an empty tag)\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/tags/{tag_id}\": {\n      \"get\": {\n        \"description\": \"Retrieve tag infos\",\n        \"tags\": [\n          \"tags\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"tag_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful response with json object\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Tag\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"description\": \"update a tag\",\n        \"tags\": [\n          \"tags\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"tag_id\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"label\": {\n                  \"type\": \"string\"\n                },\n                \"importance_level\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                },\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"label\": {\n                      \"type\": \"string\"\n                    },\n                    \"importance_level\": {\n                      \"type\": \"integer\",\n                      \"format\": \"int32\"\n                    }\n                  },\n                  \"additionalProperties\": false\n                }\n              },\n              \"additionalProperties\": false,\n              \"required\": [\n                \"current_state\"\n              ]\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Update successful. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"tag not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"409\": {\n            \"description\": \"json is valid but patch conflicts with db's current state\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but patch was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"Delete a tag belonging to an user\",\n        \"tags\": [\n          \"tags\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"tag_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Successful deletion\"\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/devices\": {\n      \"get\": {\n        \"description\": \"Returns devices belonging to current user according to given parameters\",\n        \"tags\": [\n          \"devices\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"X-Caliopen-PI\",\n            \"in\": \"header\",\n            \"required\": true,\n            \"description\": \"The PI range requested in form of `1;100`\",\n            \"type\": \"string\",\n            \"default\": \"1;100\"\n          },\n          {\n            \"name\": \"limit\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"description\": \"number of devices to return per page\"\n          },\n          {\n            \"name\": \"offset\",\n            \"in\": \"query\",\n            \"type\": \"integer\",\n            \"required\": false,\n            \"description\": \"number of pages to skip from the response\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Devices returned\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"number of devices found for current user for the given parameters\"\n                },\n                \"devices\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Device\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/v2/devices/{device_id}\": {\n      \"get\": {\n        \"description\": \"Returns a device\",\n        \"tags\": [\n          \"devices\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"device_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Device found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Device\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Device not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"Delete a device\",\n        \"tags\": [\n          \"devices\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"device_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Device deleted\"\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Device not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"description\": \"update a device with rfc5789 and rfc7396 specifications\",\n        \"tags\": [\n          \"devices\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"device_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"patch\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"description\": \"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"current_state\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"$ref\": \"../objects/Device.yaml#/properties\"\n                  }\n                },\n                \"device_id\": {\n                  \"type\": \"string\"\n                },\n                \"ip_creation\": {\n                  \"description\": \"ip address at creation\",\n                  \"type\": \"string\"\n                },\n                \"locations\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/DeviceLocation\"\n                  }\n                },\n                \"name\": {\n                  \"type\": \"string\"\n                },\n                \"public_keys\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/PublicKey\"\n                  }\n                },\n                \"status\": {\n                  \"type\": \"string\"\n                },\n                \"type\": {\n                  \"type\": \"string\",\n                  \"default\": \"unknow\",\n                  \"enum\": [\n                    \"other\",\n                    \"desktop\",\n                    \"laptop\",\n                    \"smartphone\",\n                    \"tablet\"\n                  ]\n                },\n                \"user_agent\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"required\": [\n                \"current_state\"\n              ]\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Update successful. No body is returned.\"\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"device not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"patch was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/devices/{device_id}/actions\": {\n      \"post\": {\n        \"description\": \"Route to receive orders to trigger actions on a device\",\n        \"tags\": [\n          \"devices\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"device_id\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          },\n          {\n            \"name\": \"actions\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/Actions\"\n            }\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"action(s) successfully executed. Nothing returned.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/MessageV2\"\n            }\n          },\n          \"400\": {\n            \"description\": \"json payload malformed\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"403\": {\n            \"description\": \"Forbidden action. Server is refusing to execute the given action(s) to this ressource\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Device not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"422\": {\n            \"description\": \"json is valid but action was semantically malformed or unprocessable\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"424\": {\n            \"description\": \"execution of action failed.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/validate-device/{token}\": {\n      \"get\": {\n        \"description\": \"complete device validation if token is valid\",\n        \"tags\": [\n          \"devices\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"token\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Route is valid. No body is returned.\"\n          },\n          \"404\": {\n            \"description\": \"Token not found or invalid\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/participants/suggest\": {\n      \"get\": {\n        \"description\": \"Returns a list of suggestions according to given parameters/filter. Search is performed within current user's indexes (messages & contacts).\",\n        \"tags\": [\n          \"participants\",\n          \"suggest\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"context\",\n            \"in\": \"query\",\n            \"description\": \"current user's context (to optimize suggest relevance)\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"enum\": [\n              \"msg_compose\"\n            ]\n          },\n          {\n            \"name\": \"q\",\n            \"in\": \"query\",\n            \"description\": \"a string (3 chars at least) from which to perform the suggestion search\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"minLength\": 3\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"a list of suggestions for user to pick-up an identity\",\n            \"schema\": {\n              \"type\": \"array\",\n              \"items\": {\n                \"$ref\": \"#/definitions/ParticipantSuggest\"\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/participants/discussion\": {\n      \"post\": {\n        \"description\": \"Returns discussion related to a list of participants\",\n        \"tags\": [\n          \"participants\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"consumes\": [\n          \"application/json\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"participants\",\n            \"in\": \"body\",\n            \"schema\": {\n              \"type\": \"array\",\n              \"items\": {\n                \"$ref\": \"#/definitions/Participant\"\n              }\n            },\n            \"required\": true\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Computed discussion information\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"hash\": {\n                  \"type\": \"string\",\n                  \"description\": \"participants informations hash for discussion lookup\"\n                },\n                \"discussion_id\": {\n                  \"type\": \"string\",\n                  \"description\": \"id of existing discussion for given participants\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/search\": {\n      \"get\": {\n        \"description\": \"Simple API to execute full-text searches within user's indexes. A more complexe API will be available with a POST verb.\",\n        \"tags\": [\n          \"messages\",\n          \"contacts\",\n          \"search\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"X-Caliopen-IL\",\n            \"in\": \"header\",\n            \"required\": true,\n            \"description\": \"The Importance Level range requested in form of `-10;10`\",\n            \"type\": \"string\",\n            \"default\": \"-10;10\"\n          },\n          {\n            \"name\": \"term\",\n            \"in\": \"query\",\n            \"description\": \"the search string\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"minLength\": 3\n          },\n          {\n            \"name\": \"field\",\n            \"in\": \"query\",\n            \"description\": \"name of a field on which to perform the search. If omitted defaults to « _all ».\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"doctype\",\n            \"in\": \"query\",\n            \"description\": \"type of documents to narrow the search to.\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"message\",\n              \"contact\",\n              \"\"\n            ]\n          },\n          {\n            \"name\": \"limit\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"description\": \"number of documents to return per page, but only if param «type» is present.\"\n          },\n          {\n            \"name\": \"offset\",\n            \"in\": \"query\",\n            \"type\": \"integer\",\n            \"required\": false,\n            \"description\": \"number of pages to skip from the response, but only if param «type» is present.\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"an object holding an array of documents found. Docs are assembled by type.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\",\n                  \"description\": \"total number of documents found\"\n                },\n                \"message_hits\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"total\": {\n                      \"type\": \"integer\",\n                      \"format\": \"int32\",\n                      \"description\": \"total number of messages found\"\n                    },\n                    \"messages\": {\n                      \"type\": \"array\",\n                      \"description\": \"at most 5 documents are returned if query param « type » is not specified.\",\n                      \"items\": {\n                        \"$ref\": \"#/definitions/SearchResponse\"\n                      }\n                    }\n                  }\n                },\n                \"contact_hits\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"total\": {\n                      \"type\": \"integer\",\n                      \"format\": \"int32\",\n                      \"description\": \"total number of contacts found\"\n                    },\n                    \"contacts\": {\n                      \"type\": \"array\",\n                      \"description\": \"at most 5 documents are returned if query param « type » is not specified.\",\n                      \"items\": {\n                        \"$ref\": \"#/definitions/SearchResponse\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"post\": {\n        \"description\": \"Not yet implemented. Future route for more complexe searches.\",\n        \"tags\": [\n          \"contacts\",\n          \"messages\",\n          \"search\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"responses\": {\n          \"501\": {\n            \"description\": \"route not implemented.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/notifications\": {\n      \"get\": {\n        \"description\": \"Returns pending notifications\",\n        \"tags\": [\n          \"notifications\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"from\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"description\": \"oldest timestamp or uuid to retrieve (older notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.\",\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"to\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"description\": \"earlest timestamp or uuid to retrieve (earler notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.\",\n            \"type\": \"string\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"json document with an array of pending notifications\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"total\": {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                },\n                \"notifications\": {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"$ref\": \"#/definitions/Notification\"\n                  }\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"Delete pending notifications by time range\",\n        \"tags\": [\n          \"notifications\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"until\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"description\": \"delete all notifications with a timestamp before `until` time (RFC3339 format)\",\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"deletions executed, no body returned.\"\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    },\n    \"/v2/notifications/{notification_id}\": {\n      \"get\": {\n        \"description\": \"Returns a notification\",\n        \"tags\": [\n          \"notifications\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"notification_id\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Notification found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Notification\"\n            }\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"404\": {\n            \"description\": \"Message not found\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"description\": \"Delete a notification\",\n        \"tags\": [\n          \"notifications\"\n        ],\n        \"security\": [\n          {\n            \"basicAuth\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"notification_id\",\n            \"in\": \"path\",\n            \"type\": \"string\",\n            \"required\": true\n          }\n        ],\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"deletion executed, no body returned.\"\n          },\n          \"400\": {\n            \"description\": \"malform request\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"$ref\": \"#/definitions/Error\"\n            }\n          },\n          \"401\": {\n            \"description\": \"Unauthorized access\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/Error\"\n            }\n          }\n        }\n      }\n    }\n  },\n  \"definitions\": {\n    \"ECKey\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"curve\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"P-256\",\n            \"P-384\",\n            \"P-512\"\n          ]\n        },\n        \"hash\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"SHA256\",\n            \"SHA384\",\n            \"SHA512\"\n          ]\n        },\n        \"x\": {\n          \"type\": \"string\"\n        },\n        \"y\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"curve\",\n        \"x\",\n        \"y\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"DefaultDevice\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"device_id\": {\n          \"type\": \"string\"\n        },\n        \"ecdsa_key\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/ECKey\"\n        }\n      },\n      \"required\": [\n        \"device_id\",\n        \"ecdsa_key\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"Authentication\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"username\": {\n          \"type\": \"string\"\n        },\n        \"password\": {\n          \"type\": \"string\"\n        },\n        \"context\": {\n          \"type\": \"string\"\n        },\n        \"device\": {\n          \"$ref\": \"#/definitions/DefaultDevice\"\n        }\n      },\n      \"required\": [\n        \"username\",\n        \"password\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"Error\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"error\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"message\": {\n              \"type\": \"string\"\n            },\n            \"code\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            \"name\": {\n              \"type\": \"string\"\n            }\n          }\n        }\n      }\n    },\n    \"PostalAddress\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"address_id\": {\n          \"type\": \"string\"\n        },\n        \"city\": {\n          \"type\": \"string\"\n        },\n        \"country\": {\n          \"type\": \"string\"\n        },\n        \"is_primary\": {\n          \"type\": \"boolean\"\n        },\n        \"label\": {\n          \"type\": \"string\"\n        },\n        \"postal_code\": {\n          \"type\": \"string\"\n        },\n        \"region\": {\n          \"type\": \"string\"\n        },\n        \"street\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"city\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"Email\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"email_id\": {\n          \"type\": \"string\"\n        },\n        \"address\": {\n          \"type\": \"string\"\n        },\n        \"is_primary\": {\n          \"type\": \"boolean\"\n        },\n        \"label\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"address\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"SocialIdentity\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"social_id\": {\n          \"type\": \"string\"\n        },\n        \"infos\": {\n          \"type\": \"object\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"name\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"IM\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"im_id\": {\n          \"type\": \"string\"\n        },\n        \"address\": {\n          \"type\": \"string\"\n        },\n        \"is_primary\": {\n          \"type\": \"boolean\"\n        },\n        \"label\": {\n          \"type\": \"string\"\n        },\n        \"protocol\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"address\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"Organization\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"deleted\": {\n          \"type\": \"boolean\"\n        },\n        \"organization_id\": {\n          \"type\": \"string\"\n        },\n        \"department\": {\n          \"type\": \"string\"\n        },\n        \"is_primary\": {\n          \"type\": \"boolean\"\n        },\n        \"job_description\": {\n          \"type\": \"string\"\n        },\n        \"label\": {\n          \"type\": \"string\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"title\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"name\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"Phone\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"phone_id\": {\n          \"type\": \"string\"\n        },\n        \"normalized_number\": {\n          \"type\": \"string\"\n        },\n        \"is_primary\": {\n          \"type\": \"boolean\"\n        },\n        \"number\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        },\n        \"uri\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"number\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"PI\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"technic\": {\n          \"type\": \"integer\"\n        },\n        \"context\": {\n          \"type\": \"integer\"\n        },\n        \"comportment\": {\n          \"type\": \"integer\"\n        },\n        \"version\": {\n          \"type\": \"integer\"\n        }\n      },\n      \"additionalProperties\": true\n    },\n    \"PrivacyFeatures\": {\n      \"type\": \"object\",\n      \"properties\": {}\n    },\n    \"PublicKey\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"alg\": {\n          \"type\": \"string\"\n        },\n        \"crv\": {\n          \"type\": \"string\"\n        },\n        \"date_insert\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"date_update\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"emails\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"expire_date\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"fingerprint\": {\n          \"type\": \"string\"\n        },\n        \"kty\": {\n          \"type\": \"string\"\n        },\n        \"key_id\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        },\n        \"resource_id\": {\n          \"type\": \"string\"\n        },\n        \"resource_type\": {\n          \"type\": \"string\"\n        },\n        \"size\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"use\": {\n          \"type\": \"string\"\n        },\n        \"user_id\": {\n          \"type\": \"string\"\n        },\n        \"x\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"y\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"key\": {\n          \"type\": \"string\",\n          \"description\": \"DER or PEM key, base64 encoded\"\n        },\n        \"label\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"key\",\n        \"label\",\n        \"key_id\",\n        \"resource_id\",\n        \"user_id\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"Contact\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"additional_name\": {\n          \"type\": \"string\"\n        },\n        \"addresses\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/PostalAddress\"\n          }\n        },\n        \"avatar\": {\n          \"type\": \"string\"\n        },\n        \"contact_id\": {\n          \"type\": \"string\"\n        },\n        \"date_insert\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"date_update\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"deleted\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"emails\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Email\"\n          }\n        },\n        \"family_name\": {\n          \"type\": \"string\"\n        },\n        \"given_name\": {\n          \"type\": \"string\"\n        },\n        \"groups\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"identities\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/SocialIdentity\"\n          }\n        },\n        \"ims\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/IM\"\n          }\n        },\n        \"infos\": {\n          \"type\": \"object\"\n        },\n        \"name_prefix\": {\n          \"type\": \"string\"\n        },\n        \"name_suffix\": {\n          \"type\": \"string\"\n        },\n        \"organizations\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Organization\"\n          }\n        },\n        \"phones\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Phone\"\n          }\n        },\n        \"pi\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PI\"\n        },\n        \"privacy_features\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PrivacyFeatures\"\n        },\n        \"public_keys\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/PublicKey\"\n          }\n        },\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"title\": {\n          \"type\": \"string\"\n        },\n        \"user_id\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"contact_id\",\n        \"user_id\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"User\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"contact\": {\n          \"$ref\": \"#/definitions/Contact\"\n        },\n        \"date_insert\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"family_name\": {\n          \"type\": \"string\"\n        },\n        \"given_name\": {\n          \"type\": \"string\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"password\": {\n          \"type\": \"string\"\n        },\n        \"params\": {\n          \"type\": \"object\"\n        },\n        \"privacy_features\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PrivacyFeatures\"\n        },\n        \"pi\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PI\"\n        },\n        \"user_id\": {\n          \"type\": \"string\"\n        },\n        \"recovery_email\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NewEmail\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"address\": {\n          \"type\": \"string\"\n        },\n        \"is_primary\": {\n          \"type\": \"boolean\"\n        },\n        \"label\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"address\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"NewContact\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"additional_name\": {\n          \"type\": \"string\"\n        },\n        \"addresses\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/PostalAddress\"\n          }\n        },\n        \"avatar\": {\n          \"type\": \"string\"\n        },\n        \"emails\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/NewEmail\"\n          }\n        },\n        \"family_name\": {\n          \"type\": \"string\"\n        },\n        \"given_name\": {\n          \"type\": \"string\"\n        },\n        \"title\": {\n          \"type\": \"string\"\n        },\n        \"groups\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"identities\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/SocialIdentity\"\n          }\n        },\n        \"ims\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\"\n          }\n        },\n        \"infos\": {\n          \"type\": \"object\"\n        },\n        \"name_prefix\": {\n          \"type\": \"string\"\n        },\n        \"name_suffix\": {\n          \"type\": \"string\"\n        },\n        \"organizations\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\"\n          }\n        },\n        \"phones\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Phone\"\n          }\n        },\n        \"public_keys\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/PublicKey\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Settings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"default_locale\": {\n          \"type\": \"string\",\n          \"default\": \"fr-FR\"\n        },\n        \"message_display_format\": {\n          \"type\": \"string\",\n          \"default\": \"rich_text\"\n        },\n        \"contact_display_order\": {\n          \"type\": \"string\",\n          \"default\": \"given_name\"\n        },\n        \"contact_display_format\": {\n          \"type\": \"string\",\n          \"default\": \"family_name, given_name\"\n        },\n        \"notification_enabled\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"notification_message_preview\": {\n          \"type\": \"string\",\n          \"default\": \"always\"\n        },\n        \"notification_sound_enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"notification_delay_disappear\": {\n          \"type\": \"integer\",\n          \"default\": 10\n        }\n      }\n    },\n    \"NewUser\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"contact\": {\n          \"$ref\": \"#/definitions/NewContact\"\n        },\n        \"username\": {\n          \"type\": \"string\"\n        },\n        \"password\": {\n          \"type\": \"string\"\n        },\n        \"recovery_email\": {\n          \"type\": \"string\"\n        },\n        \"tos\": {\n          \"type\": \"boolean\"\n        },\n        \"privacy\": {\n          \"type\": \"boolean\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/Settings\"\n        },\n        \"device\": {\n          \"$ref\": \"#/definitions/DefaultDevice\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"username\",\n        \"password\",\n        \"recovery_email\"\n      ]\n    },\n    \"Attachment\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"content_type\": {\n          \"type\": \"string\"\n        },\n        \"is_inline\": {\n          \"type\": \"boolean\"\n        },\n        \"file_name\": {\n          \"type\": \"string\"\n        },\n        \"size\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"temp_id\": {\n          \"type\": \"string\"\n        },\n        \"url\": {\n          \"type\": \"string\"\n        },\n        \"mime_boundary\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"ExternalReferences\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"ancestors_id\": {\n          \"type\": \"string\"\n        },\n        \"message_id\": {\n          \"type\": \"string\"\n        },\n        \"parent_id\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"Participant\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"address\": {\n          \"type\": \"string\"\n        },\n        \"contact_ids\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"label\": {\n          \"type\": \"string\"\n        },\n        \"protocol\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"To\",\n            \"Cc\",\n            \"Bcc\",\n            \"From\",\n            \"Reply-To\",\n            \"Sender\"\n          ]\n        }\n      },\n      \"required\": [\n        \"address\",\n        \"type\",\n        \"protocol\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"PIMessage\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"content\": {\n          \"type\": \"integer\"\n        },\n        \"transport\": {\n          \"type\": \"integer\"\n        },\n        \"social\": {\n          \"type\": \"integer\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Actions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"actions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"enum\": [\n              \"send\",\n              \"set_read\",\n              \"set_unread\",\n              \"reset_password\",\n              \"delete\",\n              \"device-validation\"\n            ]\n          }\n        },\n        \"params\": {\n          \"type\": \"object\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"actions\"\n      ]\n    },\n    \"MessageV2\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"attachments\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Attachment\"\n          }\n        },\n        \"body\": {\n          \"type\": \"string\"\n        },\n        \"body_is_plain\": {\n          \"type\": \"boolean\"\n        },\n        \"date\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"date_delete\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"date_insert\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"date_sort\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"discussion_id\": {\n          \"type\": \"string\"\n        },\n        \"external_references\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/ExternalReferences\"\n        },\n        \"excerpt\": {\n          \"type\": \"string\"\n        },\n        \"user_identities\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"importance_level\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"is_answered\": {\n          \"type\": \"boolean\"\n        },\n        \"is_draft\": {\n          \"type\": \"boolean\"\n        },\n        \"is_unread\": {\n          \"type\": \"boolean\"\n        },\n        \"is_received\": {\n          \"type\": \"boolean\"\n        },\n        \"message_id\": {\n          \"type\": \"string\"\n        },\n        \"parent_id\": {\n          \"type\": \"string\"\n        },\n        \"participants\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Participant\"\n          }\n        },\n        \"privacy_features\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PrivacyFeatures\"\n        },\n        \"pi\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PI\"\n        },\n        \"pi_message\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PIMessage\"\n        },\n        \"raw_msg_id\": {\n          \"type\": \"string\"\n        },\n        \"subject\": {\n          \"type\": \"string\"\n        },\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"protocol\": {\n          \"type\": \"string\"\n        },\n        \"user_id\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"UserIdentity\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"credentials\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"display_name\": {\n          \"type\": \"string\"\n        },\n        \"identity_id\": {\n          \"type\": \"string\"\n        },\n        \"identifier\": {\n          \"type\": \"string\"\n        },\n        \"infos\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"last_check\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"protocol\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"email\",\n            \"twitter\"\n          ]\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"active\",\n            \"inactive\",\n            \"deleted\"\n          ]\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"local\",\n            \"remote\"\n          ]\n        },\n        \"user_id\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NewUserIdentity\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"credentials\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"display_name\": {\n          \"type\": \"string\"\n        },\n        \"identifier\": {\n          \"type\": \"string\"\n        },\n        \"infos\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"protocol\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"email\",\n            \"twitter\"\n          ]\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"active\",\n            \"inactive\",\n            \"deleted\"\n          ]\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"local\",\n            \"remote\"\n          ]\n        },\n        \"user_id\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"identifier\",\n        \"protocol\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"ContactIdentity\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"identifier\": {\n          \"type\": \"string\"\n        },\n        \"label\": {\n          \"type\": \"string\"\n        },\n        \"privacy_index\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PI\"\n        },\n        \"protocol\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NewPublicKey\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"key\": {\n          \"type\": \"string\",\n          \"description\": \"DER or PEM key, base64 encoded\"\n        },\n        \"label\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"key\",\n        \"label\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"Discussion\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"attachment_count\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"date_insert\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"date_update\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"discussion_id\": {\n          \"type\": \"string\"\n        },\n        \"importance_level\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"participants\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Participant\"\n          }\n        },\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"excerpt\": {\n          \"type\": \"string\"\n        },\n        \"subject\": {\n          \"type\": \"string\"\n        },\n        \"protocol\": {\n          \"type\": \"string\"\n        },\n        \"total_count\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"unread_count\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"aliases\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"last_message_id\": {\n          \"type\": \"string\"\n        },\n        \"last_message_date\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"last_message_subject\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"NewMessageV2\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"attachments\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Attachment\"\n          }\n        },\n        \"body\": {\n          \"type\": \"string\"\n        },\n        \"discussion_id\": {\n          \"type\": \"string\"\n        },\n        \"user_identities\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"message_id\": {\n          \"type\": \"string\"\n        },\n        \"parent_id\": {\n          \"type\": \"string\"\n        },\n        \"participants\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Participant\"\n          }\n        },\n        \"subject\": {\n          \"type\": \"string\"\n        },\n        \"privacy_features\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PrivacyFeatures\"\n        }\n      },\n      \"required\": [\n        \"user_identities\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"Tag\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"date_insert\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"user\",\n            \"system\"\n          ]\n        },\n        \"label\": {\n          \"type\": \"string\"\n        },\n        \"importance_level\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NewTag\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"label\": {\n          \"type\": \"string\"\n        },\n        \"importance_level\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        }\n      },\n      \"required\": [\n        \"label\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"DeviceLocation\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"address\": {\n          \"type\": \"string\"\n        },\n        \"country\": {\n          \"type\": \"string\"\n        },\n        \"device_id\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"default\": \"other\",\n          \"enum\": [\n            \"other\",\n            \"home\",\n            \"work\",\n            \"public\"\n          ]\n        },\n        \"user_id\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"address\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"Device\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"device_id\": {\n          \"type\": \"string\"\n        },\n        \"date_insert\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"date_revoked\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"ip_creation\": {\n          \"type\": \"string\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"privacy_features\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PrivacyFeatures\"\n        },\n        \"pi\": {\n          \"type\": \"object\",\n          \"$ref\": \"#/definitions/PI\"\n        },\n        \"status\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        },\n        \"user_id\": {\n          \"type\": \"string\"\n        },\n        \"user_agent\": {\n          \"type\": \"string\"\n        },\n        \"locations\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/DeviceLocation\"\n          }\n        },\n        \"public_keys\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/PublicKey\"\n          }\n        }\n      },\n      \"required\": [\n        \"device_id\",\n        \"user_id\",\n        \"name\",\n        \"type\",\n        \"status\",\n        \"date_insert\"\n      ],\n      \"additionalProperties\": false\n    },\n    \"ParticipantSuggest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"address\": {\n          \"type\": \"string\",\n          \"description\": \"could be empty if suggestion is a contact (or should we automatically put preferred identity's address ?)\"\n        },\n        \"contact_id\": {\n          \"type\": \"string\",\n          \"description\": \"contact's ID if any\"\n        },\n        \"label\": {\n          \"type\": \"string\",\n          \"description\": \"name of contact or <display-name> in case of an address returned from participants lookup, if any\"\n        },\n        \"protocol\": {\n          \"type\": \"string\",\n          \"description\": \"email, IRC…\"\n        },\n        \"source\": {\n          \"type\": \"string\",\n          \"description\": \"'participant' or 'contact', ie from where this suggestion came from\"\n        }\n      }\n    },\n    \"SearchResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"description\": \"id of document (shortcut to fetch full doc from db if needed).\"\n        },\n        \"score\": {\n          \"type\": \"number\",\n          \"format\": \"float\",\n          \"description\": \"how confident is our index for this document to match the request. Higher is better. Documents are sorted on this field by default.\"\n        },\n        \"highlights\": {\n          \"type\": \"object\",\n          \"description\": \"Field names where terms of request where found. Each key maps to an array of excerpts.\"\n        },\n        \"document\": {\n          \"type\": \"object\",\n          \"description\": \"full document returned from index.\"\n        }\n      }\n    },\n    \"Notification\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"body\": {\n          \"type\": \"string\"\n        },\n        \"emitter\": {\n          \"type\": \"string\"\n        },\n        \"notif_id\": {\n          \"description\": \"notif_id is UUIDv1, including a timestamp\",\n          \"type\": \"string\"\n        },\n        \"reference\": {\n          \"type\": \"string\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        },\n        \"user_id\": {\n          \"type\": \"string\"\n        },\n        \"children\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"body\": {\n                \"type\": \"string\"\n              },\n              \"emitter\": {\n                \"type\": \"string\"\n              },\n              \"notif_id\": {\n                \"description\": \"notif_id is UUIDv1, including a timestamp\",\n                \"type\": \"string\"\n              },\n              \"reference\": {\n                \"type\": \"string\"\n              },\n              \"type\": {\n                \"type\": \"string\"\n              }\n            }\n          }\n        }\n      },\n      \"additionalProperties\": false\n    }\n  },\n  \"securityDefinitions\": {\n    \"basicAuth\": {\n      \"type\": \"basic\",\n      \"description\": \"HTTP Basic Authentication. `Password` is the `access_token` return by /authentications and `Username` is the `user_id` returned by /authentications\"\n    }\n  }\n}"
  },
  {
    "path": "src/backend/configs/twitterworker.yaml",
    "content": "workers: 10\ntwitter_app_key:\ntwitter_app_secret:\nBrokerConfig:\n  #messaging system\n  nats_url: nats://nats:4222\n  nats_queue: Twitterworkers                             # NATS group queue for workers\n  nats_topic_poller: twitterJobs                         # NATS topic on which to request job from idpoller\n  nats_topic_poller_cache: idCache                       # NATS topic to send orders to idpoller regarding identities management\n  nats_topic_direct_message: twitter_dm                  # NATS topic to listen to orders for handling DMs (fetch, send)\n  #storage facility\n  store_name: cassandra                                  # backend to store raw emails and messages (inbound & outbound)\n  store_settings:\n    hosts: # many allowed\n      - cassandra\n    keyspace: caliopen\n    consistency_level: 1\n    raw_size_limit: 1048576                                 # max size in bytes for objects in db. Use S3 interface if larger.\n    object_store: s3\n    object_store_settings:\n      endpoint: objectstore:9090\n      access_key: CALIOPEN_ACCESS_KEY_                     # Access key of 5 to 20 characters in length\n      secret_key: CALIOPEN_SECRET_KEY_BE_GOOD_AND_LIVE_OLD # Secret key of 8 to 40 characters in length\n      location: eu-fr-localhost                            # S3 region.\n      buckets:\n        raw_messages: caliopen-raw-messages                # bucket name to put raw messages to\n        temporary_attachments: caliopen-tmp-attachments    # bucket name to store draft attachments\n    use_vault: false\n    vault_settings:\n      url: http://vault:8200\n      username: twitterworker                                 # password authentication for now ; later we'll make use of more secure auth methods (TLScert, kubernetes…)\n      password: a_weak_password_for_twitter\n  LDAConfig:\n    broker_type: twitter                                   # types are : smtp, imap, mailboxe, etc.\n    #index facility\n    index_name: elasticsearch                              # backend to index messages (inbound & outbound)\n    index_settings:\n      urls: # many allowed\n        - http://elasticsearch:9200\n    #messaging system\n    in_topic: inboundTwitter\n    # notifications\n    NotifierConfig:\n      admin_username: admin                                # username on whose behalf notifiers will act. This admin user must have been created before by other means."
  },
  {
    "path": "src/backend/defs/go-objects/actions_payload.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n//\n// only struct and interfaces definitions in this pkg\n\npackage objects\n\ntype ActionsPayload struct {\n\tActions []string    `json:\"actions\"`\n\tParams  interface{} `json:\"params\"`\n\tUserId  string\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/attachment.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"github.com/satori/go.uuid\"\n)\n\ntype Attachment struct {\n\tContentType  string `cql:\"content_type\"     json:\"content_type,omitempty\"`\n\tFileName     string `cql:\"file_name\"        json:\"file_name,omitempty\"`\n\tIsInline     bool   `cql:\"is_inline\"        json:\"is_inline,omitempty\"`\n\tSize         int    `cql:\"size\"             json:\"size,omitempty\"`\n\tTempID       UUID   `cql:\"temp_id\"          json:\"temp_id,omitempty\"`\n\tURL          string `cql:\"url\"              json:\"url,omitempty\"`           // ObjectStore url for temporary file (draft)\n\tMimeBoundary string `cql:\"mime_boundary\"    json:\"mime_boundary,omitempty\"` // for attachments embedded in raw messages\n}\n\nfunc (a *Attachment) UnmarshalMap(input map[string]interface{}) error {\n\tif content_type, ok := input[\"content_type\"].(string); ok {\n\t\ta.ContentType = content_type\n\t}\n\tif file_name, ok := input[\"file_name\"].(string); ok {\n\t\ta.FileName = file_name\n\t}\n\tif is_inline, ok := input[\"is_inline\"].(bool); ok {\n\t\ta.IsInline = is_inline\n\t}\n\tif size, ok := input[\"size\"].(float64); ok {\n\t\ta.Size = int(size)\n\t}\n\tif tmpid, ok := input[\"temp_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(tmpid); err == nil {\n\t\t\ta.TempID.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif url, ok := input[\"url\"].(string); ok {\n\t\ta.URL = url\n\t}\n\tif mimeBoundary, ok := input[\"mime_boundary\"].(string); ok {\n\t\ta.MimeBoundary = mimeBoundary\n\t}\n\treturn nil //TODO: error handling\n}\n\nfunc (a *Attachment) JSONMarshaller() ([]byte, error) {\n\treturn JSONMarshaller(\"\", a)\n}\n\n// return a JSON representation of Device suitable for frontend client\nfunc (a *Attachment) MarshalFrontEnd() ([]byte, error) {\n\treturn JSONMarshaller(\"frontend\", a)\n}\n\n// part of CaliopenObject interface\nfunc (a *Attachment) MarshallNew(...interface{}) {\n\tif len(a.TempID) == 0 || (bytes.Equal(a.TempID.Bytes(), EmptyUUID.Bytes())) {\n\t\ta.TempID.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n}\n\nfunc (a *Attachment) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.UnmarshalMap(input)\n}\n\nfunc (a *Attachment) NewEmpty() interface{} {\n\treturn new(Attachment)\n}\n\nfunc (a *Attachment) JsonTags() map[string]string {\n\treturn jsonTags(a)\n}\n\nfunc (a *Attachment) SortSlices() {\n\t//nothing to sort\n}\n\n// Sort interface implementation\ntype ByFileName []Attachment\n\nfunc (a ByFileName) Len() int {\n\treturn len(a)\n}\n\nfunc (a ByFileName) Less(i, j int) bool {\n\treturn a[i].FileName < a[j].FileName\n}\n\nfunc (a ByFileName) Swap(i, j int) {\n\ta[i], a[j] = a[j], a[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/cache.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport \"time\"\n\n// struct to store temporary token sessions for users into cache\ntype TokenSession struct {\n\tExpiresAt  time.Time `json:\"expires_at\"`\n\tExpiresIn  int       `json:\"expires_in\"`\n\tResourceId string    `json:\"resource_id\"`\n\tToken      string    `json:\"token\"`\n\tUserId     string    `json:\"user_id\"`\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/common.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n)\n\n// this interface is mainly a collection of helpers to facilitate the job of reflection\ntype CaliopenObject interface {\n\t// NewEmpty returns the pointer returned by the new() builtin applied to the underlying object and its nested objects if needed\n\tNewEmpty() interface{}\n\t// UnmarshalJSON is json.Unmarshaller interface\n\tUnmarshalJSON(b []byte) error\n\t// UnmarshalMap hydrate struct with data from a map[string]interface{}\n\tUnmarshalMap(map[string]interface{}) error\n\t// NewMarshaller ensure mandatory properties are corretly set on struct\n\tNewMarshaller\n}\n\n// types below are helpers to manage embedded and related struct within storage\n// as well as lookup tables.\ntype (\n\t// objects with embedded struct that must be stored alongside their parent.\n\tHasNested interface {\n\t\tGetSetNested() <-chan interface{}\n\t}\n\n\t// objects with joined objects stored in separate table\n\tHasRelated interface {\n\t\tGetRelatedList() map[string]interface{} // returns a map[PropertyKey]Type of structs that are embedded into a struct from joined table(s)\n\t\tGetSetRelated() <-chan interface{}      // returns a chan to iterate over embedded structs\n\t}\n\n\t// objects with related tables that must be maintained up-to-date\n\tHasLookup interface {\n\t\tGetLookupsTables() map[string]StoreLookup // returns a map[tableName]interface{} with table(s) name(s) and model(s) of the lookup table(s)\n\t\tGetLookupKeys() <-chan StoreLookup        // returns a chan to iterate over fields and values that make up the lookup tables keys\n\t}\n\n\tNewMarshaller interface {\n\t\tMarshallNew(...interface{}) // ensure mandatory properties are correctly set on new structs.\n\t}\n\n\tStoreLookup interface {\n\t\tCleanupLookups(...interface{}) func(session *gocql.Session) error // returns a func for CassandraBackend that cleanups lookups table\n\t\tUpdateLookups(...interface{}) func(session *gocql.Session) error  // returns a func for CassandraBackend that creates/updates lookups table\n\t}\n\t// structs capable of returning JSON representation suitable for frontend client\n\tFrontEndMarshaller interface {\n\t\tMarshalFrontEnd() ([]byte, error)\n\t}\n\n\tObjectPatchable interface {\n\t\tCaliopenObject\n\t\t// JsonTags returns a map[string]string of the \"json\" tags found for each object property\n\t\t// map key is the tag value (json:\"xxxx\"), mapping to the corresponding struct property name\n\t\tJsonTags() map[string]string\n\t\t// SortSlices sorts all slices embedded in the struct in a determinist way\n\t\tSortSlices()\n\t\t// TODO: add a compare(a,b T) func to CaliopenObject interface\n\t}\n)\n\n// generic implementation of the jsonTags func (see above) for CaliopenObject interface\nfunc jsonTags(obj interface{}) (tags map[string]string) {\n\ttags = make(map[string]string)\n\tfields, err := reflections.Fields(obj)\n\tif err != nil {\n\t\treturn\n\t}\n\tfor _, field := range fields {\n\t\ttag, err := reflections.GetFieldTag(obj, field, \"json\")\n\t\tsplit := strings.Split(tag, \",\")\n\t\tif err == nil {\n\t\t\ttags[split[0]] = field\n\t\t}\n\t}\n\treturn\n}\n\n// MarshalNested calls MarshalNew() on nested structs\nfunc MarshalNested(obj HasNested) {\n\tfor nested := range obj.GetSetNested() {\n\t\tif nest, ok := nested.(NewMarshaller); ok {\n\t\t\tnest.MarshallNew()\n\t\t}\n\t}\n}\n\n// MarshalNested calls MarshalNew() on related structs\nfunc MarshalRelated(obj HasRelated) {\n\tfor related := range obj.GetSetRelated() {\n\t\tif rel, ok := related.(NewMarshaller); ok {\n\t\t\trel.MarshallNew(obj)\n\t\t}\n\t}\n}\n\n// bespoke implementation of the json.Marshaller interface\n// outputs a JSON representation of an object\n// this marshaller takes account of custom tags for given 'context'\nfunc JSONMarshaller(context string, obj interface{}) ([]byte, error) {\n\tvar jsonBuf bytes.Buffer\n\tenc := json.NewEncoder(&jsonBuf)\n\n\tfields, err := reflections.Fields(obj)\n\tif err != nil {\n\t\treturn jsonBuf.Bytes(), err\n\t}\n\tjsonBuf.WriteByte('{')\n\tfirst := new(bool)\n\t*first = true\nfieldsLoop:\n\tfor _, field := range fields {\n\t\tswitch context {\n\t\tcase \"elastic\":\n\t\t\tj_elastic, err := reflections.GetFieldTag(obj, field, \"elastic\")\n\t\t\tif err == nil {\n\t\t\t\tswitch j_elastic {\n\t\t\t\tcase \"omit\":\n\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"frontend\":\n\t\t\tfront, err := reflections.GetFieldTag(obj, field, \"frontend\")\n\t\t\tif err == nil {\n\t\t\t\tswitch front {\n\t\t\t\tcase \"omit\":\n\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tmarshallField(obj, field, context, &jsonBuf, first, enc)\n\t}\n\tjsonBuf.WriteByte('}')\n\treturn jsonBuf.Bytes(), nil\n}\n\nfunc marshallField(obj interface{}, field, context string, jsonBuf *bytes.Buffer, first *bool, enc *json.Encoder) {\n\tj_field, err := reflections.GetFieldTag(obj, field, \"json\")\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"reflection for field %s failed\", field)\n\t} else {\n\t\tif j_field != \"\" && j_field != \"-\" {\n\t\t\tsplit := strings.Split(j_field, \",\")\n\t\t\tif len(split) > 1 && split[1] == \"omitempty\" {\n\t\t\t\t// check if field is empty\n\t\t\t\tf, e := reflections.GetField(obj, field)\n\t\t\t\tif e != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif isEmptyValue(reflect.ValueOf(f)) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif *first {\n\t\t\t\t*first = false\n\t\t\t} else {\n\t\t\t\tjsonBuf.WriteByte(',')\n\t\t\t}\n\n\t\t\tjsonBuf.WriteString(\"\\\"\" + split[0] + \"\\\":\")\n\n\t\t\t// recursively apply JSONMarshaller to embedded structs\n\t\t\tfieldKind, _ := reflections.GetFieldKind(obj, field)\n\t\t\tfield_value, err := reflections.GetField(obj, field)\n\t\t\tj_formatter, err := reflections.GetFieldTag(obj, field, \"formatter\")\n\n\t\t\tif err == nil {\n\t\t\t\tif fieldKind == reflect.Slice {\n\t\t\t\t\tvalue := reflect.ValueOf(field_value)\n\t\t\t\t\tif value.Len() > 0 {\n\t\t\t\t\t\t// check if it's a slice of structs before iterating\n\t\t\t\t\t\tif value.Index(0).Kind() == reflect.Struct {\n\t\t\t\t\t\t\tsubFirst := true\n\t\t\t\t\t\t\tjsonBuf.WriteByte('[')\n\t\t\t\t\t\t\tfor i := 0; i < value.Len(); i++ {\n\t\t\t\t\t\t\t\tb, e := JSONMarshaller(context, value.Index(i).Interface())\n\t\t\t\t\t\t\t\tif e == nil {\n\t\t\t\t\t\t\t\t\tif subFirst {\n\t\t\t\t\t\t\t\t\t\tsubFirst = false\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tjsonBuf.WriteByte(',')\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tjsonBuf.Write(b)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tjsonBuf.WriteByte(']')\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tenc.Encode(field_value)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tswitch j_formatter {\n\t\t\t\tcase \"RFC3339Milli\":\n\t\t\t\t\tjsonBuf.WriteString(\"\\\"\" + field_value.(time.Time).Format(RFC3339Milli) + \"\\\"\")\n\t\t\t\tcase \"raw\":\n\t\t\t\t\tif fieldKind == reflect.String {\n\t\t\t\t\t\tjsonBuf.WriteString(field_value.(string))\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tvar b []byte\n\t\t\t\t\tvar e error\n\t\t\t\t\tif fieldKind == reflect.Struct {\n\t\t\t\t\t\tb, e = JSONMarshaller(context, field_value)\n\t\t\t\t\t} else if fieldKind == reflect.Ptr && field_value != nil {\n\t\t\t\t\t\tvalue_at := reflect.Indirect(reflect.ValueOf(field_value))\n\t\t\t\t\t\tif value_at.Type().Kind() == reflect.Struct {\n\t\t\t\t\t\t\tb, e = JSONMarshaller(context, value_at.Interface())\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tenc.Encode(field_value)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tenc.Encode(field_value)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif e == nil {\n\t\t\t\t\t\tjsonBuf.Write(b)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tjsonBuf.WriteString(\"null\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tjsonBuf.Write([]byte{'\"', '\"'})\n\t\t\t}\n\t\t}\n\t}\n}\n\n// borrowed from pkg/encoding/json/encode.go\n// add checks for :\n//  - time.Time\n//  - custom type UUID\nfunc isEmptyValue(v reflect.Value) bool {\n\tswitch v.Kind() {\n\tcase reflect.Array:\n\t\tif reflect.TypeOf(v.Interface()) == reflect.TypeOf(EmptyUUID) {\n\t\t\treturn v.Len() == 0 || (bytes.Equal(v.Interface().(UUID).Bytes(), EmptyUUID.Bytes()))\n\t\t} else {\n\t\t\treturn v.Len() == 0\n\t\t}\n\tcase reflect.Map, reflect.Slice, reflect.String:\n\t\treturn v.Len() == 0\n\tcase reflect.Bool:\n\t\treturn !v.Bool()\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\treturn v.Int() == 0\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:\n\t\treturn v.Uint() == 0\n\tcase reflect.Float32, reflect.Float64:\n\t\treturn v.Float() == 0\n\tcase reflect.Interface, reflect.Ptr:\n\t\treturn v.IsNil()\n\tcase reflect.Struct:\n\t\tif v.Type().String() == \"time.Time\" {\n\t\t\treturn v.Interface().(time.Time).IsZero()\n\t\t}\n\t\t//TODO: CaliopenObjects should implement IsEmpty() for checking empty struct\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/configs.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\ntype (\n\tCaliopenConfig struct {\n\t\tCacheConfig     CacheConfig\n\t\tHostname        string\n\t\tNatsConfig      NatsConfig\n\t\tNotifierConfig  NotifierConfig\n\t\tProviders       []Provider `mapstructure:\"Providers\"`\n\t\tRESTindexConfig RESTIndexConfig\n\t\tRESTstoreConfig RESTstoreConfig\n\t}\n\n\t// REST API\n\tRESTstoreConfig struct {\n\t\tBackendName  string   `mapstructure:\"backend_name\"`\n\t\tConsistency  uint16   `mapstructure:\"consistency_level\"`\n\t\tHosts        []string `mapstructure:\"hosts\"`\n\t\tKeyspace     string   `mapstructure:\"keyspace\"`\n\t\tOSSConfig    `mapstructure:\"object_store_settings\"`\n\t\tObjStoreType string `mapstructure:\"object_store\"`\n\t\tSizeLimit    uint64 `mapstructure:\"raw_size_limit\"` // max size for db (in bytes)\n\t\tUseVault     bool   `mapstructure:\"use_vault\"`\n\t\tVaultConfig  `mapstructure:\"vault_settings\"`\n\t}\n\n\tRESTIndexConfig struct {\n\t\tIndexName string   `mapstructure:\"index_name\"`\n\t\tHosts     []string `mapstructure:\"hosts\"`\n\t}\n\n\t// redis\n\tCacheConfig struct {\n\t\tHost     string `mapstructure:\"host\"`\n\t\tPassword string `mapstructure:\"password\"`\n\t\tDb       int    `mapstructure:\"db\"`\n\t}\n\n\t// NATS\n\tNatsConfig struct {\n\t\tUrl               string `mapstructure:\"url\"`\n\t\tNatsQueue         string `mapstructure:\"nats_queue\"`\n\t\tOutSMTP_topic     string `mapstructure:\"outSMTP_topic\"`\n\t\tOutIMAP_topic     string `mapstructure:\"outIMAP_topic\"`\n\t\tOutTWITTER_topic  string `mapstructure:\"outTWITTER_topic\"`\n\t\tOutMASTODON_topic string `mapstructure:\"outMASTODON_topic\"`\n\t\tContacts_topic    string `mapstructure:\"contacts_topic\"`\n\t\tKeys_topic        string `mapstructure:\"keys_topic\"`\n\t\tUsers_topic       string `mapstructure:\"users_topic\"`\n\t\tIdPoller_topic    string `mapstructure:\"idpoller_topic\"`\n\t}\n\t// Cassandra\n\tStoreConfig struct {\n\t\tHosts       []string    `mapstructure:\"hosts\"`\n\t\tKeyspace    string      `mapstructure:\"keyspace\"`\n\t\tConsistency uint16      `mapstructure:\"consistency_level\"`\n\t\tSizeLimit   uint64      `mapstructure:\"raw_size_limit\"` // max size to store (in bytes)\n\t\tObjectStore string      `mapstructure:\"object_store\"`\n\t\tOSSConfig   OSSConfig   `mapstructure:\"object_store_settings\"`\n\t\tUseVault    bool        `mapstructure:\"use_vault\"`\n\t\tVaultConfig VaultConfig `mapstructure:\"vault_settings\"`\n\t}\n\n\t// Elasticsearch\n\tIndexConfig struct {\n\t\tUrls []string `mapstructure:\"urls\"`\n\t}\n\n\t// Objects Store\n\tOSSConfig struct {\n\t\tEndpoint  string            `mapstructure:\"endpoint\"`\n\t\tAccessKey string            `mapstructure:\"access_key\"`\n\t\tSecretKey string            `mapstructure:\"secret_key\"`\n\t\tLocation  string            `mapstructure:\"location\"`\n\t\tBuckets   map[string]string `mapstructure:\"buckets\"`\n\t}\n\n\t// Notifications facility\n\tNotifierConfig struct {\n\t\tAdminUsername string `mapstructure:\"admin_username\"`\n\t\tBaseUrl       string `mapstructure:\"base_url\"`       // url upon which to build custom links sent to users. No trailing slash please.\n\t\tTemplatesPath string `mapstructure:\"templates_path\"` // path to templates Notifiers may need to access to\n\t}\n\n\t// Hashicorp Vault\n\tVaultConfig struct {\n\t\tUrl      string `mapstructure:\"url\"`\n\t\tUsername string `mapstructure:\"username\"`\n\t\tPassword string `mapstructure:\"password\"`\n\t}\n\n\t// providers\n\tProvidersConfig struct {\n\t\tProviders []Provider `mapstructure:\"Providers\"`\n\t}\n\n\tLDAConfig struct {\n\t\tAppVersion       string         `mapstructure:\"version\"`\n\t\tBrokerType       string         `mapstructure:\"broker_type\"`\n\t\tContactsTopic    string         `mapstructure:\"contacts_topic\"`\n\t\tInTopic          string         `mapstructure:\"in_topic\"`\n\t\tInWorkers        int            `mapstructure:\"lda_workers_size\"`\n\t\tIndexName        string         `mapstructure:\"index_name\"`\n\t\tLogReceivedMails bool           `mapstructure:\"log_received_mails\"`\n\t\tNatsListeners    int            `mapstructure:\"nats_listeners\"`\n\t\tNatsQueue        string         `mapstructure:\"nats_queue\"`\n\t\tNatsURL          string         `mapstructure:\"nats_url\"`\n\t\tOutTopic         string         `mapstructure:\"out_topic\"`\n\t\tPrimaryMailHost  string         `mapstructure:\"primary_mail_host\"`\n\t\tStoreName        string         `mapstructure:\"store_name\"`\n\t\tIndexConfig      IndexConfig    `mapstructure:\"index_settings\"`\n\t\tNotifierConfig   NotifierConfig `mapstructure:\"NotifierConfig\"`\n\t\tProviders        []Provider     `mapstructure:\"Providers\"`\n\t\tStoreConfig      StoreConfig    `mapstructure:\"store_settings\"`\n\t}\n)\n"
  },
  {
    "path": "src/backend/defs/go-objects/constants.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\n// constants declares global scope, general usage vars\n\nconst (\n\t// protocols' strings\n\tEmailProtocol     = \"email\"\n\tImapProtocol      = \"imap\"\n\tSmtpProtocol      = \"smtp\"\n\tIrcProtocol       = \"irc\"\n\tSmsProtocol       = \"sms\"\n\tXmppProtocol      = \"xmpp\"\n\tFacebookProtocl   = \"facebook\"\n\tTwitterProtocol   = \"twitter\"\n\tGnuSocialProtocol = \"GNUsocial\"\n\tMastodonProtocol  = \"mastodon\"\n\n\tTimeISO8601      = \"2006-01-02T15:04:05-07:00\"\n\tTimeUTCmicro     = \"2006-01-02T15:04:05.999999\"\n\tRFC3339Milli     = \"2006-01-02T15:04:05.000Z07:00\"\n\tMessageType      = \"message\"\n\tContactType      = \"contact\"\n\tMessageIndexType = \"indexed_message\"\n\tContactIndexType = \"indexed_contact\"\n\n\t// authentication methods\n\tLoginPassword = \"login-password\"\n\tOauth1        = \"Oauth1\"\n\tOauth2        = \"Oauth2\"\n\n\t//nats related constants\n\tNats_contact_tmpl         = \"{\\\"order\\\":\\\"%s\\\", \\\"contact_id\\\":\\\"%s\\\", \\\"user_id\\\":\\\"%s\\\"}\"\n\tNats_outSMTP_topicKey     = \"outSMTP_topic\"\n\tNats_inSMTP_topicKey      = \"inSMTP_topic\"\n\tNats_Contacts_topicKey    = \"contacts_topic\"\n\tNats_outIMAP_topicKey     = \"outIMAP_topic\"\n\tNats_outTwitter_topicKey  = \"outTWITTER_topic\"\n\tNats_outMastodon_topicKey = \"outMASTODON_topic\"\n\tNats_Keys_topicKey        = \"keys_topic\"\n\tNats_IdPoller_topicKey    = \"idpoller_topic\"\n\n\t//participant types\n\tParticipantBcc     = \"Bcc\"\n\tParticipantCC      = \"Cc\"\n\tParticipantFrom    = \"From\"\n\tParticipantReplyTo = \"Reply-To\"\n\tParticipantSender  = \"Sender\"\n\tParticipantTo      = \"To\"\n\n\t//notifications types\n\tNotifAdminMail        = \"adminMail\"\n\tNotifPasswordReset    = \"passwordReset\"\n\tNotifDeviceValidation = \"deviceValidation\"\n\tOnboardingMails       = \"onboardingMails\"\n\n\t//identity types\n\tLocalIdentity  = \"local\"\n\tRemoteIdentity = \"remote\"\n\n\t//praticipants lookup\n\tUrisKind         = \"uris\"\n\tParticipantsKind = \"participants\"\n\n\t//device status\n\tDeviceVerifiedStatus   = \"verified\"\n\tDeviceUnverifiedStatus = \"unverified\"\n\tDeviceDeletedStatus    = \"deleted\"\n\n\t//device types\n\tDeviceOtherType      = \"other\"\n\tDeviceDesktopType    = \"desktop\"\n\tDeviceLaptopType     = \"laptop\"\n\tDeviceSmartphoneType = \"smartphone\"\n\tDeviceTabletType     = \"tablet\"\n\n\t/** JWA strings from RFC7518 **/\n\t// \"alg\" param from RFC7581#3.1\n\tRSA256   = \"RS256\"\n\tRSA384   = \"RS384\"\n\tRSA512   = \"RS512\"\n\tECDSA256 = \"ES256\"\n\tECDSA384 = \"ES384\"\n\t// supplementary algorithms to handle GPG keys\n\tDSA256     = \"DSA256\"\n\tDSA384     = \"DSA384\"\n\tDSA512     = \"DSA512\"\n\tELGAMAL256 = \"ELGAMAL256\"\n\tELGAMAL384 = \"ELGAMAL384\"\n\tELGAMAL512 = \"ELGAMAL512\"\n\tECDH256    = \"ECDH256\"\n\tECDH384    = \"ECDH384\"\n\tECDH512    = \"ECDH512\"\n\t// \"crv\" param from RFC7518#7.6.2\n\tCURVE256 = \"P-256\"\n\tCURVE384 = \"P-384\"\n\tCURVE521 = \"P-521\"\n\t// \"kty\" param from RFC7518#6.1\n\tRSA_KEY_TYPE   = \"RSA\"\n\tEC_KEY_TYPE    = \"EC\"\n\tOCTET_KEY_TYPE = \"oct\" // (yes, lower case !)\n\t//supplementary types to handle GPG keys\n\tPGP_KEY_TYPE     = \"PGP\"\n\tDSA_KEY_TYPE     = \"DSA\"\n\tELGAMAL_KEY_TYPE = \"ELGAMAL\"\n\t// \"use\" param (from RFC ??)\n\tSIGNATURE_KEY  = \"sig\"\n\tENCRYPTION_KEY = \"enc\"\n\t/** end of JWA strings **/\n)\n\n// A Initiator specifies what kind of actor is triggering a PATCH method on any object\ntype Initiator int\n\nconst (\n\tUnknown Initiator = iota\n\tSystemActor\n\tUserActor\n)\n\n// map internal constants to json tags used within struct definition\nvar Initiators = map[string]Initiator{\n\t\"system\": SystemActor,\n\t\"user\":   UserActor,\n}\n\nvar DeviceTypes = [5]string{\"other\", \"laptop\", \"desktop\", \"smartphone\", \"tablet\"} // always put the default string at first, it will be found by device's func to fill default type.\n\nvar EmptyUUID = UUID{} // allocate an emptyUUID that should only be used for testing zero value.\n"
  },
  {
    "path": "src/backend/defs/go-objects/contact.go",
    "content": "package objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype (\n\tContact struct {\n\t\tLocker          *sync.Mutex       `cql:\"-\"                  json:\"-\"`\n\t\tAdditionalName  string            `cql:\"additional_name\"    json:\"additional_name,omitempty\"      patch:\"user\"`\n\t\tAddresses       []PostalAddress   `cql:\"addresses\"          json:\"addresses,omitempty\"            patch:\"user\"`\n\t\tAvatar          string            `cql:\"avatar\"             json:\"avatar,omitempty\"               patch:\"user\"`\n\t\tContactId       UUID              `cql:\"contact_id\"         json:\"contact_id,omitempty\"   elastic:\"omit\"`\n\t\tDateInsert      time.Time         `cql:\"date_insert\"        json:\"date_insert,omitempty\"          formatter:\"RFC3339Milli\"`\n\t\tDateUpdate      time.Time         `cql:\"date_update\"        json:\"date_update,omitempty\"          formatter:\"RFC3339Milli\"`\n\t\tDeleted         time.Time         `cql:\"deleted\"            json:\"deleted,omitempty\"              formatter:\"RFC3339Milli\"`\n\t\tEmails          []EmailContact    `cql:\"emails\"             json:\"emails,omitempty\"               patch:\"user\"`\n\t\tFamilyName      string            `cql:\"family_name\"        json:\"family_name,omitempty\"          patch:\"user\"`\n\t\tGivenName       string            `cql:\"given_name\"         json:\"given_name,omitempty\"           patch:\"user\"`\n\t\tGroups          []string          `cql:\"groups\"             json:\"groups,omitempty\"               patch:\"user\"`\n\t\tIdentities      []SocialIdentity  `cql:\"identities\"         json:\"identities,omitempty\"           patch:\"user\"`\n\t\tIms             []IM              `cql:\"ims\"                json:\"ims,omitempty\"                  patch:\"user\"`\n\t\tInfos           map[string]string `cql:\"infos\"              json:\"infos,omitempty\"                patch:\"user\"`\n\t\tNamePrefix      string            `cql:\"name_prefix\"        json:\"name_prefix,omitempty\"          patch:\"user\"`\n\t\tNameSuffix      string            `cql:\"name_suffix\"        json:\"name_suffix,omitempty\"          patch:\"user\"`\n\t\tOrganizations   []Organization    `cql:\"organizations\"      json:\"organizations,omitempty\"        patch:\"user\"`\n\t\tPhones          []Phone           `cql:\"phones\"             json:\"phones,omitempty\"               patch:\"user\"`\n\t\tPrivacyIndex    *PrivacyIndex     `cql:\"pi\"                 json:\"pi,omitempty\"`\n\t\tPublicKeys      []PublicKey       `cql:\"-\"                  json:\"public_keys,omitempty\"          patch:\"user\"`\n\t\tPrivacyFeatures *PrivacyFeatures  `cql:\"privacy_features\"   json:\"privacy_features,omitempty\"`\n\t\tTags            []string          `cql:\"tagnames\"           json:\"tags,omitempty\"                 patch:\"system\"`\n\t\tTitle           string            `cql:\"title\"              json:\"title,omitempty\"                patch:\"user\"`\n\t\tUserId          UUID              `cql:\"user_id\"            json:\"user_id,omitempty\"`\n\t}\n\n\t// ContactByContactPoints is the model of a Cassandra table to lookup contacts by address/email/phone/etc.\n\tContactByContactPoints struct {\n\t\tContactID string `cql:\"contact_id\"`\n\t\tType      string `cql:\"type\"`\n\t\tUserID    string `cql:\"user_id\"`\n\t\tValue     string `cql:\"value\"`\n\t}\n)\n\n// UnmarshalCQLMap hydrates a Contact with data from a map[string]interface{}\n// typical usage is for unmarshaling response from Cassandra backend\nfunc (contact *Contact) UnmarshalCQLMap(input map[string]interface{}) {\n\tif additionalName, ok := input[\"additional_name\"].(string); ok {\n\t\tcontact.AdditionalName = additionalName\n\t}\n\tif addresses, ok := input[\"addresses\"]; ok && addresses != nil {\n\t\tcontact.Addresses = []PostalAddress{}\n\t\tfor _, address := range addresses.([]map[string]interface{}) {\n\t\t\tpa := PostalAddress{}\n\t\t\taddressId, _ := address[\"address_id\"].(gocql.UUID)\n\t\t\tpa.AddressId.UnmarshalBinary(addressId.Bytes())\n\t\t\tpa.City, _ = address[\"city\"].(string)\n\t\t\tpa.Country, _ = address[\"country\"].(string)\n\t\t\tpa.IsPrimary, _ = address[\"is_primary\"].(bool)\n\t\t\tpa.Label, _ = address[\"label\"].(string)\n\t\t\tpa.PostalCode, _ = address[\"postal_code\"].(string)\n\t\t\tpa.Region, _ = address[\"region\"].(string)\n\t\t\tpa.Street, _ = address[\"street\"].(string)\n\t\t\tpa.Type, _ = address[\"type\"].(string)\n\t\t\tcontact.Addresses = append(contact.Addresses, pa)\n\t\t}\n\t}\n\n\tif avatar, ok := input[\"avatar\"].(string); ok {\n\t\tcontact.Avatar = avatar\n\t}\n\tif contactId, ok := input[\"contact_id\"].(gocql.UUID); ok {\n\t\tcontact.ContactId.UnmarshalBinary(contactId.Bytes())\n\t}\n\tif dateInsert, ok := input[\"date_insert\"].(time.Time); ok {\n\t\tcontact.DateInsert = dateInsert\n\t}\n\tif dateUpdate, ok := input[\"date_update\"].(time.Time); ok {\n\t\tcontact.DateUpdate = dateUpdate\n\t}\n\tif deleted, ok := input[\"deleted\"].(time.Time); ok {\n\t\tcontact.Deleted = deleted\n\t}\n\tif emails, ok := input[\"emails\"]; ok && emails != nil {\n\t\tcontact.Emails = []EmailContact{}\n\t\tfor _, email := range emails.([]map[string]interface{}) {\n\t\t\te := EmailContact{}\n\t\t\te.Address, _ = email[\"address\"].(string)\n\t\t\temailId, _ := email[\"email_id\"].(gocql.UUID)\n\t\t\te.EmailId.UnmarshalBinary(emailId.Bytes())\n\t\t\te.IsPrimary, _ = email[\"is_primary\"].(bool)\n\t\t\te.Label, _ = email[\"label\"].(string)\n\t\t\te.Type, _ = email[\"type\"].(string)\n\t\t\tcontact.Emails = append(contact.Emails, e)\n\t\t}\n\t}\n\n\tif familyName, ok := input[\"family_name\"].(string); ok {\n\t\tcontact.FamilyName = familyName\n\t}\n\tif givenName, ok := input[\"given_name\"].(string); ok {\n\t\tcontact.GivenName = givenName\n\t}\n\tif groups, ok := input[\"groups\"].([]string); ok {\n\t\tcontact.Groups = groups\n\t}\n\tif identities, ok := input[\"identities\"]; ok && identities != nil {\n\t\tcontact.Identities = []SocialIdentity{}\n\t\tfor _, identity := range identities.([]map[string]interface{}) {\n\t\t\ti := SocialIdentity{}\n\t\t\ti.Infos, _ = identity[\"infos\"].(map[string]string)\n\t\t\ti.Name, _ = identity[\"name\"].(string)\n\t\t\tsocialId, _ := identity[\"social_id\"].(gocql.UUID)\n\t\t\ti.SocialId.UnmarshalBinary(socialId.Bytes())\n\t\t\ti.Type, _ = identity[\"type\"].(string)\n\t\t\tcontact.Identities = append(contact.Identities, i)\n\t\t}\n\t}\n\tif ims, ok := input[\"ims\"]; ok && ims != nil {\n\t\tcontact.Ims = []IM{}\n\t\tfor _, im := range ims.([]map[string]interface{}) {\n\t\t\ti_m := IM{}\n\t\t\ti_m.Address, _ = im[\"address\"].(string)\n\t\t\timid, _ := im[\"im_id\"].(gocql.UUID)\n\t\t\ti_m.IMId.UnmarshalBinary(imid.Bytes())\n\t\t\ti_m.IsPrimary, _ = im[\"is_primary\"].(bool)\n\t\t\ti_m.Label, _ = im[\"label\"].(string)\n\t\t\ti_m.Protocol, _ = im[\"protocol\"].(string)\n\t\t\ti_m.Type, _ = im[\"type\"].(string)\n\t\t\tcontact.Ims = append(contact.Ims, i_m)\n\t\t}\n\t}\n\n\tif infos, ok := input[\"infos\"].(map[string]string); ok {\n\t\tcontact.Infos = infos\n\t}\n\tif namePrefix, ok := input[\"name_prefix\"].(string); ok {\n\t\tcontact.NamePrefix = namePrefix\n\t}\n\tif nameSuffix, ok := input[\"name_suffix\"].(string); ok {\n\t\tcontact.NameSuffix = nameSuffix\n\t}\n\tif organizations, ok := input[\"organizations\"]; ok && organizations != nil {\n\t\tcontact.Organizations = []Organization{}\n\t\tfor _, org := range organizations.([]map[string]interface{}) {\n\t\t\to := Organization{}\n\t\t\to.Deleted, _ = org[\"deleted\"].(bool)\n\t\t\to.Department, _ = org[\"department\"].(string)\n\t\t\to.IsPrimary, _ = org[\"is_primary\"].(bool)\n\t\t\to.JobDescription, _ = org[\"job_description\"].(string)\n\t\t\to.Label, _ = org[\"label\"].(string)\n\t\t\to.Name, _ = org[\"name\"].(string)\n\t\t\torgId, _ := org[\"organization_id\"].(gocql.UUID)\n\t\t\to.OrganizationId.UnmarshalBinary(orgId.Bytes())\n\t\t\to.Title, _ = org[\"title\"].(string)\n\t\t\to.Type, _ = org[\"type\"].(string)\n\t\t\tcontact.Organizations = append(contact.Organizations, o)\n\t\t}\n\t}\n\n\tif phones, ok := input[\"phones\"]; ok && phones != nil {\n\t\tcontact.Phones = []Phone{}\n\t\tfor _, phone := range phones.([]map[string]interface{}) {\n\t\t\tp := Phone{}\n\t\t\tp.IsPrimary, _ = phone[\"is_primary\"].(bool)\n\t\t\tp.Number, _ = phone[\"number\"].(string)\n\t\t\tphoneId, _ := phone[\"phone_id\"].(gocql.UUID)\n\t\t\tp.PhoneId.UnmarshalBinary(phoneId.Bytes())\n\t\t\tp.Type, _ = phone[\"type\"].(string)\n\t\t\tp.Uri, _ = phone[\"uri\"].(string)\n\t\t\tcontact.Phones = append(contact.Phones, p)\n\t\t}\n\t}\n\n\tif i_pi, ok := input[\"pi\"].(map[string]interface{}); ok && i_pi != nil {\n\t\tpi := PrivacyIndex{}\n\t\tpi.Comportment, _ = i_pi[\"comportment\"].(int)\n\t\tpi.Context, _ = i_pi[\"context\"].(int)\n\t\tpi.DateUpdate, _ = i_pi[\"date_update\"].(time.Time)\n\t\tpi.Technic, _ = i_pi[\"technic\"].(int)\n\t\tpi.Version, _ = i_pi[\"version\"].(int)\n\t\tcontact.PrivacyIndex = &pi\n\t} else {\n\t\tcontact.PrivacyIndex = nil\n\t}\n\tif i_pf, ok := input[\"privacy_features\"].(map[string]string); ok && i_pf != nil {\n\t\tpf := PrivacyFeatures{}\n\t\tfor k, v := range i_pf {\n\t\t\tpf[k] = v\n\t\t}\n\t\tcontact.PrivacyFeatures = &pf\n\n\t} else {\n\t\tcontact.PrivacyFeatures = nil\n\t}\n\n\tif tags, ok := input[\"tagnames\"].([]string); ok {\n\t\tcontact.Tags = tags\n\t}\n\tif title, ok := input[\"title\"].(string); ok {\n\t\tcontact.Title = title\n\t}\n\tif userid, ok := input[\"user_id\"].(gocql.UUID); ok {\n\t\tcontact.UserId.UnmarshalBinary(userid.Bytes())\n\t}\n}\n\nfunc (c *Contact) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn c.UnmarshalMap(input)\n}\n\n// UnmarshalMap hydrates a Contact with data from a map[string]interface{}\nfunc (c *Contact) UnmarshalMap(input map[string]interface{}) error {\n\n\tif additionalName, ok := input[\"additional_name\"].(string); ok {\n\t\tc.AdditionalName = additionalName\n\t}\n\t//addresses\n\tif pa, ok := input[\"addresses\"]; ok && pa != nil {\n\t\tc.Addresses = []PostalAddress{}\n\t\tfor _, address := range pa.([]interface{}) {\n\t\t\tPA := new(PostalAddress)\n\t\t\tif err := PA.UnmarshalMap(address.(map[string]interface{})); err == nil {\n\t\t\t\tc.Addresses = append(c.Addresses, *PA)\n\t\t\t}\n\t\t}\n\t}\n\tif avatar, ok := input[\"avatar\"].(string); ok {\n\t\tc.Avatar = avatar\n\t}\n\tif contact_id, ok := input[\"contact_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(contact_id); err == nil {\n\t\t\tc.ContactId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif date, ok := input[\"date_insert\"]; ok {\n\t\tc.DateInsert, _ = time.Parse(time.RFC3339Nano, date.(string))\n\t}\n\tif date, ok := input[\"date_update\"]; ok {\n\t\tc.DateUpdate, _ = time.Parse(time.RFC3339Nano, date.(string))\n\t}\n\tif deleted, ok := input[\"deleted\"]; ok {\n\t\tc.Deleted, _ = time.Parse(time.RFC3339Nano, deleted.(string))\n\t}\n\t//emails\n\tif emails, ok := input[\"emails\"]; ok && emails != nil {\n\t\tc.Emails = []EmailContact{}\n\t\tfor _, email := range emails.([]interface{}) {\n\t\t\tE := new(EmailContact)\n\t\t\tif err := E.UnmarshalMap(email.(map[string]interface{})); err == nil {\n\t\t\t\tc.Emails = append(c.Emails, *E)\n\t\t\t}\n\t\t}\n\t}\n\n\tif familyName, ok := input[\"family_name\"].(string); ok {\n\t\tc.FamilyName = familyName\n\t}\n\tif givenName, ok := input[\"given_name\"].(string); ok {\n\t\tc.GivenName = givenName\n\t}\n\tif groups, ok := input[\"groups\"]; ok {\n\t\tc.Groups = []string{}\n\t\tfor _, group := range groups.([]interface{}) {\n\t\t\tc.Groups = append(c.Groups, group.(string))\n\t\t}\n\t}\n\t//identities\n\tif identities, ok := input[\"identities\"]; ok && identities != nil {\n\t\tc.Identities = []SocialIdentity{}\n\t\tfor _, identity := range identities.([]interface{}) {\n\t\t\tI := new(SocialIdentity)\n\t\t\tI.Infos = map[string]string{}\n\t\t\tif err := I.UnmarshalMap(identity.(map[string]interface{})); err == nil {\n\t\t\t\tc.Identities = append(c.Identities, *I)\n\t\t\t}\n\t\t}\n\t}\n\t//Ims\n\tif ims, ok := input[\"ims\"]; ok && ims != nil {\n\t\tc.Ims = []IM{}\n\t\tfor _, im := range ims.([]interface{}) {\n\t\t\tI := new(IM)\n\t\t\tif err := I.UnmarshalMap(im.(map[string]interface{})); err == nil {\n\t\t\t\tc.Ims = append(c.Ims, *I)\n\t\t\t}\n\t\t}\n\t}\n\tif infos, ok := input[\"infos\"].(map[string]interface{}); ok && infos != nil {\n\t\tc.Infos = make(map[string]string)\n\t\tfor k, v := range infos {\n\t\t\tc.Infos[k] = v.(string)\n\t\t}\n\t}\n\tif namePrefix, ok := input[\"name_prefix\"].(string); ok {\n\t\tc.NamePrefix = namePrefix\n\t}\n\tif nameSuffix, ok := input[\"name_suffix\"].(string); ok {\n\t\tc.NameSuffix = nameSuffix\n\t}\n\t//organizations\n\tif orgas, ok := input[\"organizations\"]; ok && orgas != nil {\n\t\tc.Organizations = []Organization{}\n\t\tfor _, orga := range orgas.([]interface{}) {\n\t\t\tO := new(Organization)\n\t\t\tif err := O.UnmarshalMap(orga.(map[string]interface{})); err == nil {\n\t\t\t\tc.Organizations = append(c.Organizations, *O)\n\t\t\t}\n\t\t}\n\t}\n\t//phones\n\tif phones, ok := input[\"phones\"]; ok && phones != nil {\n\t\tc.Phones = []Phone{}\n\t\tfor _, phone := range phones.([]interface{}) {\n\t\t\tP := new(Phone)\n\t\t\tif err := P.UnmarshalMap(phone.(map[string]interface{})); err == nil {\n\t\t\t\tc.Phones = append(c.Phones, *P)\n\t\t\t}\n\t\t}\n\t}\n\t//PrivacyIndex\n\tif pi, ok := input[\"pi\"]; ok && pi != nil {\n\t\tPI := new(PrivacyIndex)\n\t\tif err := PI.UnmarshalMap(pi.(map[string]interface{})); err == nil {\n\t\t\tc.PrivacyIndex = PI\n\t\t}\n\t}\n\t//PublicKeys\n\tif pks, ok := input[\"public_keys\"]; ok && pks != nil {\n\t\tc.PublicKeys = []PublicKey{}\n\t\tfor _, pk := range pks.([]interface{}) {\n\t\t\tK := new(PublicKey)\n\t\t\tif err := K.UnmarshalMap(pk.(map[string]interface{})); err == nil {\n\t\t\t\tc.PublicKeys = append(c.PublicKeys, *K)\n\t\t\t}\n\t\t}\n\t}\n\t// Privacy features\n\tif pf, ok := input[\"privacy_features\"]; ok && pf != nil {\n\t\tPF := &PrivacyFeatures{}\n\t\tPF.UnmarshalMap(pf.(map[string]interface{}))\n\t\tc.PrivacyFeatures = PF\n\t}\n\tif tags, ok := input[\"tags\"].([]interface{}); ok && tags != nil {\n\t\tc.Tags = []string{}\n\t\tfor _, tag := range tags {\n\t\t\tc.Tags = append(c.Tags, tag.(string))\n\t\t}\n\t}\n\tif title, ok := input[\"title\"].(string); ok {\n\t\tc.Title = title\n\t}\n\tif user_id, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(user_id); err == nil {\n\t\t\tc.UserId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// return a JSON representation of Contact suitable for frontend client\nfunc (c *Contact) MarshalFrontEnd() ([]byte, error) {\n\treturn JSONMarshaller(\"frontend\", c)\n}\n\nfunc (c *Contact) MarshalES() ([]byte, error) {\n\treturn JSONMarshaller(\"elastic\", c)\n}\n\n// bespoke implementation of the json.Marshaller interface\n// outputs a JSON representation of an object\n// this marshaller takes account of custom tags for given 'context'\nfunc (c *Contact) JSONMarshaller() ([]byte, error) {\n\treturn JSONMarshaller(\"\", c)\n}\n\nfunc (c *Contact) JsonTags() map[string]string {\n\treturn jsonTags(c)\n}\n\nfunc (c *Contact) NewEmpty() interface{} {\n\tnc := new(Contact)\n\tnc.Addresses = []PostalAddress{}\n\tnc.Emails = []EmailContact{}\n\tnc.Groups = []string{}\n\tnc.Identities = []SocialIdentity{}\n\tnc.Ims = []IM{}\n\tnc.Infos = map[string]string{}\n\tnc.Organizations = []Organization{}\n\tnc.Phones = []Phone{}\n\tnc.PublicKeys = []PublicKey{}\n\tnc.Tags = []string{}\n\treturn nc\n}\n\n// GetSetNested returns a chan to iterate over pointers to embedded structs.\n// It allows the caller to get and/or set embedded structs, concurrent safely.\nfunc (c *Contact) GetSetNested() <-chan interface{} {\n\tgetSet := make(chan interface{})\n\tif c.Locker == nil {\n\t\tc.Locker = new(sync.Mutex)\n\t}\n\tgo func(*sync.Mutex, chan interface{}) {\n\t\tc.Locker.Lock()\n\t\t// Addresses\n\t\tfor i := range c.Addresses {\n\t\t\tgetSet <- &(c.Addresses[i])\n\t\t}\n\t\t// Emails\n\t\tfor i := range c.Emails {\n\t\t\tgetSet <- &(c.Emails[i])\n\t\t}\n\t\t// Identities\n\t\tfor i := range c.Identities {\n\t\t\tgetSet <- &(c.Identities[i])\n\t\t}\n\t\t// Ims\n\t\tfor i := range c.Ims {\n\t\t\tgetSet <- &(c.Ims[i])\n\t\t}\n\t\t// Organizations\n\t\tfor i := range c.Organizations {\n\t\t\tgetSet <- &(c.Organizations[i])\n\t\t}\n\t\t// Phones\n\t\tfor i := range c.Phones {\n\t\t\tgetSet <- &(c.Phones[i])\n\t\t}\n\t\tclose(getSet)\n\t\tc.Locker.Unlock()\n\t}(c.Locker, getSet)\n\n\treturn getSet\n}\n\n// GetRelatedList returns a map[PropertyKey]Type of structs that are embedded into a Contact from joined tables\nfunc (c *Contact) GetRelatedList() map[string]interface{} {\n\t/* TODO\n\treturn map[string]interface{}{\n\t\t\"PublicKeys\": &PublicKey{},\n\t}\n\t*/\n\treturn map[string]interface{}{}\n}\n\n// GetSetRelated returns a chan to iterate over pointers to embedded structs that are stored in separate tables.\n// It allows the caller to get and/or set these structs, concurrent safely.\nfunc (c *Contact) GetSetRelated() <-chan interface{} {\n\tgetSet := make(chan interface{})\n\tif c.Locker == nil {\n\t\tc.Locker = new(sync.Mutex)\n\t}\n\tgo func(*sync.Mutex, chan interface{}) {\n\t\tc.Locker.Lock()\n\t\t/* TODO\n\t\tfor i, _ := range c.PublicKeys {\n\t\t\tgetSet <- &(c.PublicKeys[i])\n\t\t}\n\t\t*/\n\t\tclose(getSet)\n\t\tc.Locker.Unlock()\n\t}(c.Locker, getSet)\n\n\treturn getSet\n}\n\n// GetLookups returns a map of table(s) and model(s) that must be up-to-date with Contact.\n// These structs must implement StoreLookup interface\nfunc (c *Contact) GetLookupsTables() map[string]StoreLookup {\n\treturn map[string]StoreLookup{\n\t\t\"contact_lookup\": &ContactByContactPoints{},\n\t\t//\"participant_lookup\": &ContactByContactPoints{}, TODO\n\t}\n}\n\n// SortSlices implements CaliopenObject interface.\n// It ensure that all embedded slices are sorted in a determinist way\nfunc (c *Contact) SortSlices() {\n\tsort.Sort(ByPostalAddressID(c.Addresses))\n\tsort.Sort(ByEmailContactID(c.Emails))\n\tsort.Strings(c.Groups)\n\tsort.Sort(BySocialIdentityID(c.Identities))\n\tsort.Sort(ByIMID(c.Ims))\n\tsort.Sort(ByOrganizationID(c.Organizations))\n\tsort.Sort(ByPhoneID(c.Phones))\n\tsort.Sort(ByKeyId(c.PublicKeys))\n\tsort.Strings(c.Tags)\n}\n\n// MarshallNew implements CaliopenObject interface\nfunc (c *Contact) MarshallNew(args ...interface{}) {\n\tif len(c.ContactId) == 0 || (bytes.Equal(c.ContactId.Bytes(), EmptyUUID.Bytes())) {\n\t\tc.ContactId.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n\tif len(c.UserId) == 0 || (bytes.Equal(c.UserId.Bytes(), EmptyUUID.Bytes())) {\n\t\tif len(args) == 1 {\n\t\t\tswitch args[0].(type) {\n\t\t\tcase UUID:\n\t\t\t\tc.UserId = args[0].(UUID)\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.DateInsert.IsZero() {\n\t\tc.DateInsert = time.Now()\n\t\tc.DateUpdate = time.Now()\n\t}\n\n\tc.Deleted = time.Time{}\n\n\tfor i := range c.Addresses {\n\t\tc.Addresses[i].MarshallNew()\n\t}\n\n\tfor i := range c.Emails {\n\t\tc.Emails[i].MarshallNew()\n\t}\n\n\tfor i := range c.Identities {\n\t\tc.Identities[i].MarshallNew()\n\t}\n\n\tfor i := range c.Organizations {\n\t\tc.Organizations[i].MarshallNew()\n\t}\n\n\tfor i := range c.Phones {\n\t\tc.Phones[i].MarshallNew()\n\t}\n\n\tfor i := range c.PublicKeys {\n\t\tc.PublicKeys[i].MarshallNew(c)\n\t}\n\n}\n\n// GetLookupKeys returns a chan to iterate over fields and values that make up the lookup tables keys\nfunc (c *Contact) GetLookupKeys() <-chan StoreLookup {\n\tgetter := make(chan StoreLookup)\n\tcontactId := c.ContactId.String()\n\tuserId := c.UserId.String()\n\tgo func(chan StoreLookup) {\n\t\t// emails\n\t\tfor _, email := range c.Emails {\n\t\t\tkey := ContactByContactPoints{\n\t\t\t\tContactID: contactId,\n\t\t\t\tType:      \"email\",\n\t\t\t\tUserID:    userId,\n\t\t\t\tValue:     email.Address,\n\t\t\t}\n\t\t\tgetter <- &key\n\t\t}\n\t\t// identities\n\t\tfor _, identity := range c.Identities {\n\t\t\tkey := ContactByContactPoints{\n\t\t\t\tContactID: contactId,\n\t\t\t\tType:      identity.Type,\n\t\t\t\tUserID:    userId,\n\t\t\t\tValue:     identity.Name,\n\t\t\t}\n\t\t\tgetter <- &key\n\t\t}\n\t\t// Ims\n\t\tfor _, im := range c.Ims {\n\t\t\tkey := ContactByContactPoints{\n\t\t\t\tContactID: contactId,\n\t\t\t\tType:      im.Protocol,\n\t\t\t\tUserID:    userId,\n\t\t\t\tValue:     im.Address,\n\t\t\t}\n\t\t\tgetter <- &key\n\t\t}\n\t\t// phones\n\t\tfor _, phone := range c.Phones {\n\t\t\tkey := ContactByContactPoints{\n\t\t\t\tContactID: contactId,\n\t\t\t\tType:      \"phone\",\n\t\t\t\tUserID:    userId,\n\t\t\t\tValue:     phone.Number,\n\t\t\t}\n\t\t\tgetter <- &key\n\n\t\t}\n\t\tclose(getter)\n\t}(getter)\n\n\treturn getter\n}\n\n// UpdateLookups iterates over contact's lookups to add or update them to the relevant table,\n// then it deletes lookups references that are no more linked to an embedded key which has been removed.\n// contacts should have one item in the context of a creation or two items [new, old] in the context of an update\nfunc (lookup *ContactByContactPoints) UpdateLookups(contacts ...interface{}) func(session *gocql.Session) error {\n\tcontactsLen := len(contacts)\n\tupdate := false\n\tif contactsLen > 0 {\n\t\tnewContact := contacts[0].(*Contact)\n\t\toldLookups := map[string]*ContactByContactPoints{}     // lookups found in old contact\n\t\tremovedLookups := map[string]*ContactByContactPoints{} // drained by loops below\n\t\treturn func(session *gocql.Session) error {\n\t\t\tif contactsLen == 2 && contacts[1] != nil {\n\t\t\t\t// it's an update, get a list of old lookupKeys to later find those that have been removed\n\t\t\t\tupdate = true\n\t\t\t\toldContact := contacts[1].(*Contact)\n\t\t\t\tfor lookup := range oldContact.GetLookupKeys() {\n\t\t\t\t\tlkp := lookup.(*ContactByContactPoints)\n\t\t\t\t\tlkp.Value = strings.ToLower(lkp.Value)\n\t\t\t\t\tlkpKey := lkp.UserID + lkp.Value + lkp.Type\n\t\t\t\t\toldLookups[lkpKey] = lkp\n\t\t\t\t\tremovedLookups[lkpKey] = lkp\n\t\t\t\t}\n\t\t\t}\n\t\t\t// iterate over contact's current state to add or update lookups\n\t\t\tfor lookup := range newContact.GetLookupKeys() {\n\t\t\t\tlkp := lookup.(*ContactByContactPoints)\n\t\t\t\tlkp.Value = strings.ToLower(lkp.Value)\n\t\t\t\tlkpKey := lkp.UserID + lkp.Value + lkp.Type\n\t\t\t\t// check if it's a new uri\n\t\t\t\terr := session.Query(`INSERT INTO contact_lookup (user_id, value, type, contact_id) VALUES (?,?,?,?)`,\n\t\t\t\t\tlkp.UserID,\n\t\t\t\t\tlkp.Value,\n\t\t\t\t\tlkp.Type,\n\t\t\t\t\tlkp.ContactID,\n\t\t\t\t).Exec()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(`[CassandraBackend] UpdateLookups INSERT failed for user: %s, value: %s, type: %s`,\n\t\t\t\t\t\tlkp.UserID,\n\t\t\t\t\t\tlkp.Value,\n\t\t\t\t\t\tlkp.Type)\n\t\t\t\t}\n\t\t\t\tif _, ok := oldLookups[lkpKey]; !ok || !update {\n\t\t\t\t\t// do the job of updating participants and discussion related tables\n\t\t\t\t\terr = UpdateURIWithContact(session, lkp.UserID, lkp.Type+\":\"+lkp.Value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t// TODO : handle multiple error returns to merge into one error or break immediately ?\n\t\t\t\t\t\tlog.WithError(err).Errorf(\"[Contact.UpdateLookups] associateURIWithContact failed with lookup %+v\", lkp)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif update {\n\t\t\t\t\t// remove keys in removedLookups,\n\t\t\t\t\t// thus at end it will only hold remaining entries that are not in the new state\n\t\t\t\t\tdelete(removedLookups, lkpKey)\n\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(removedLookups) > 0 {\n\t\t\t\t// it remains lookups in the map, meaning these lookups references have been removed from contact\n\t\t\t\t// need to cleanup lookup tables\n\t\t\t\tfor _, lookup := range removedLookups {\n\t\t\t\t\terr := session.Query(`DELETE FROM contact_lookup WHERE user_id = ? AND value = ? AND type = ?`,\n\t\t\t\t\t\tlookup.UserID,\n\t\t\t\t\t\tlookup.Value,\n\t\t\t\t\t\tlookup.Type,\n\t\t\t\t\t).Exec()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.WithError(err).Warnf(`[CassandraBackend] UpdateLookups DELETE failed for user: %s, value: %s, type: %s`,\n\t\t\t\t\t\t\tlookup.UserID,\n\t\t\t\t\t\t\tlookup.Value,\n\t\t\t\t\t\t\tlookup.Type)\n\t\t\t\t\t}\n\t\t\t\t\t// do the job of updating participants and discussion related tables\n\t\t\t\t\terr = UpdateURIWithContact(session, lookup.UserID, lookup.Type+\":\"+lookup.Value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t// TODO : handle multiple error returns to merge into one error or break immediately ?\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn nil\n}\n\n// CleanupLookups implements StoreLookup interface.\n// It returns a func which removes all contact points related to the contact given as param of the variadic func.\nfunc (lookup *ContactByContactPoints) CleanupLookups(contacts ...interface{}) func(session *gocql.Session) error {\n\tif len(contacts) == 1 {\n\t\tcontact := contacts[0].(*Contact)\n\t\treturn func(session *gocql.Session) error {\n\t\t\tfor lookup := range contact.GetLookupKeys() {\n\t\t\t\tlkp := lookup.(*ContactByContactPoints)\n\t\t\t\tlkp.Value = strings.ToLower(lkp.Value)\n\t\t\t\terr := session.Query(`DELETE FROM contact_lookup WHERE user_id = ? AND value = ? AND type = ?`,\n\t\t\t\t\tlkp.UserID,\n\t\t\t\t\tlkp.Value,\n\t\t\t\t\tlkp.Type,\n\t\t\t\t).Exec()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(`[CassandraBackend] UpdateLookups DELETE failed for user: %s, value: %s, type: %s`,\n\t\t\t\t\t\tlkp.UserID,\n\t\t\t\t\t\tlkp.Value,\n\t\t\t\t\t\tlkp.Type)\n\t\t\t\t}\n\t\t\t\t// do the job of updating participants and discussion related tables\n\t\t\t\terr = UpdateURIWithContact(session, contact.UserId.String(), lkp.Type+\":\"+lkp.Value)\n\t\t\t\tif err != nil {\n\t\t\t\t\t// TODO : handle multiple error returns to merge into one error or break immediately ?\n\t\t\t\t\tlog.WithError(err).Warnf(\"[CleanupLookups] for contacts failed to update uri with contact with lkp: %+v\", lkp)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn nil\n}\n\n// updateURIWithContact is algorithm to update hashes in ParticipantHash table\n// to be able to link URI with contact when building discussions\nfunc UpdateURIWithContact(session *gocql.Session, userId, uri string) error {\n\n\t// lookup uris_hashes where lkp's uri is embedded\n\turiLookups, err := session.Query(`SELECT hash, hash_components FROM hash_lookup WHERE user_id = ? AND uri = ?`, userId, uri).Iter().SliceMap()\n\tif err != nil && err.Error() != \"not found\" {\n\t\treturn err\n\t}\n\n\t// for each uris_hash found\n\tfor _, lookup := range uriLookups {\n\t\thashLookups, _ := session.Query(`SELECT * FROM participant_hash WHERE user_id = ? AND kind = ? AND key = ?`, userId, UrisKind, lookup[\"hash\"]).Iter().SliceMap()\n\t\tif len(hashLookups) == 0 {\n\t\t\t// it should not happen because every uris hash must resolve to a participant hash\n\t\t\t// creating one to be able to continue the update\n\t\t\tuid := uuid.FromStringOrNil(userId)\n\t\t\tif err == nil {\n\t\t\t\thashLookups = append(hashLookups, map[string]interface{}{\n\t\t\t\t\t\"user_id\":    gocql.UUID(uid),\n\t\t\t\t\t\"kind\":       UrisKind,\n\t\t\t\t\t\"key\":        lookup[\"hash\"],\n\t\t\t\t\t\"value\":      lookup[\"hash\"],\n\t\t\t\t\t\"components\": lookup[\"hash_components\"].([]string),\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"[UpdateURIWithContact] failed to unmarshal user id : %s\", userId)\n\t\t\t}\n\t\t}\n\t\tfor _, hashLookup := range hashLookups {\n\t\t\tvar participantHash ParticipantHash\n\t\t\tparticipantHash.UnmarshalCQLMap(hashLookup)\n\n\t\t\t// remove its obsolete participant_hash counterpart in hash_participant table\n\t\t\terr = RemoveURIsParticipantsBijection(session, participantHash)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"RemoveURIsParticipantsBijection failed for participantHash %+v\", participantHash)\n\t\t\t}\n\n\t\t\t// recompute a participant_hash\n\t\t\tparticipants := []Participant{}\n\t\t\tfor _, uri := range participantHash.Components {\n\t\t\t\turi = strings.ToLower(uri)\n\t\t\t\turiSplit := strings.SplitN(uri, \":\", 2)\n\t\t\t\tif len(uriSplit) != 2 {\n\t\t\t\t\terr := fmt.Errorf(\"[CreateLookupsFromUris] uri malformed : %s => %v\", uri, uriSplit)\n\t\t\t\t\tlog.WithError(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tvar contactId string\n\t\t\t\tsession.Query(`SELECT contact_id FROM contact_lookup WHERE user_id= ? AND value= ? AND type= ?`, userId, uriSplit[1], uriSplit[0]).Scan(&contactId)\n\t\t\t\tif contactId != \"\" {\n\t\t\t\t\tparticipants = append(participants, Participant{Address: contactId, Protocol: \"contact\"})\n\t\t\t\t} else {\n\t\t\t\t\tparticipants = append(participants, Participant{Address: uriSplit[1], Protocol: uriSplit[0]})\n\t\t\t\t}\n\t\t\t}\n\t\t\tparticipantsHash, participantsComponents, err := HashFromParticipantsUris(participants)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = StoreURIsParticipantsBijection(session, userId, participantHash.Key, participantsHash, participantHash.Components, participantsComponents)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"[updateURIWithContact] failed to store bijection for user %s, uriHash %s, participantHash %s, uriComponents %s, participantComponents %s\", userId, participantHash.Key, participantsHash, participantHash.Components, participantsComponents)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ContactsForUris lookups if a contact exists for each participant\n// and get related data from Contact table to fill-in Participant's Label accordingly\n// participants param is a map of unique participants : [uri]Participant\nfunc ContactsForParticipants(session *gocql.Session, userId string, participants map[string]Participant) error {\n\tfor uri, participant := range participants {\n\t\tvar contactId string\n\t\terr := session.Query(`SELECT contact_id FROM contact_lookup WHERE user_id = ? AND value = ? AND type = ?`, userId, participant.Address, participant.Protocol).Scan(&contactId)\n\t\tif err != nil && err.Error() != \"not found\" {\n\t\t\tlog.WithError(err).Warnf(\"contact_lookup failed for user %s, value %s, type %s\", userId, participant.Address, participant.Protocol)\n\t\t}\n\t\tif contactId != \"\" {\n\t\t\tparticipant.Contact_ids = []UUID{UUID(uuid.FromStringOrNil(contactId))}\n\t\t\tvar title string\n\t\t\terr = session.Query(`SELECT title FROM contact WHERE user_id = ? and contact_id = ?`, userId, contactId).Scan(&title)\n\t\t\tif title != \"\" {\n\t\t\t\tparticipant.Label = title\n\t\t\t}\n\t\t\tparticipants[uri] = participant\n\t\t} else {\n\t\t\tparticipant.Contact_ids = []UUID{}\n\t\t\tparticipants[uri] = participant\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/credentials.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage objects\n\ntype Credentials map[string]string\n\nfunc (cred *Credentials) UnmarshalMap(input map[string]interface{}) {\n\tfor k, v := range input {\n\t\t(*cred)[k] = v.(string)\n\t}\n}\n\nfunc (cred *Credentials) UnmarshalCQLMap(input map[string]string) {\n\tfor k, v := range input {\n\t\t(*cred)[k] = v\n\t}\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/crypto_keys.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdsa\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/keybase/go-crypto/openpgp\"\n\t\"github.com/keybase/go-crypto/openpgp/armor\"\n\t\"github.com/keybase/go-crypto/openpgp/packet\"\n\t\"github.com/satori/go.uuid\"\n\t\"math/big\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype PublicKey struct {\n\t// PRIMARY KEYS (UserId, ResourceId, KeyId)\n\tAlgorithm    string    `cql:\"alg\"              json:\"alg,omitempty\"                                             patch:\"system\"`\n\tCurve        string    `cql:\"crv\"              json:\"crv,omitempty\"                                             patch:\"system\"`\n\tDateInsert   time.Time `cql:\"date_insert\"      json:\"date_insert,omitempty\"         formatter:\"RFC3339Milli\"    patch:\"system\"`\n\tDateUpdate   time.Time `cql:\"date_update\"      json:\"date_update,omitempty\"         formatter:\"RFC3339Milli\"    patch:\"system\"`\n\tEmails       []string  `cql:\"emails\"           json:\"emails\"                                                    patch:\"system\"`\n\tExpireDate   time.Time `cql:\"expire_date\"      json:\"expire_date,omitempty\"         formatter:\"RFC3339Milli\"    patch:\"system\"`\n\tFingerprint  string    `cql:\"fingerprint\"      json:\"fingerprint,omitempty\"                                     patch:\"system\"`\n\tKey          string    `cql:\"key\"              json:\"key,omitempty\"                                             patch:\"system\"`\n\tKeyId        UUID      `cql:\"key_id\"           json:\"key_id\"                                                    patch:\"system\"`\n\tKeyType      string    `cql:\"kty\"              json:\"kty,omitempty\"                                             patch:\"system\"`\n\tLabel        string    `cql:\"label\"            json:\"label,omitempty\"                                           patch:\"user\"`\n\tResourceId   UUID      `cql:\"resource_id\"      json:\"resource_id\"                                               patch:\"system\"`\n\tResourceType string    `cql:\"resource_type\"    json:\"resource_type,omitempty\"                                   patch:\"system\"`\n\tSize         int       `cql:\"size\"             json:\"size\"                                                      patch:\"system\"`\n\tUse          string    `cql:\"\\\"use\\\"\"          json:\"use,omitempty\"                                             patch:\"system\"`\n\tUserId       UUID      `cql:\"user_id\"          json:\"user_id,omitempty\"                                         patch:\"system\"`\n\tX            big.Int   `cql:\"x\"                json:\"x,omitempty\"                                               patch:\"system\"`\n\tY            big.Int   `cql:\"y\"                json:\"y,omitempty\"                                               patch:\"system\"`\n}\n\n// model for nats message triggered after contact create/update\ntype DiscoverKeyMessage struct {\n\tOrder      string           `json:\"order\"`\n\tContactId  string           `json:\"contact_id\"`\n\tUserId     string           `json:\"user_id\"`\n\tEmails     []EmailContact   `json:\"emails,omitempty\"`\n\tIdentities []SocialIdentity `json:\"identities,omitempty\"`\n}\n\n// model for nats message triggered after a GPG key has been uploaded and linked to a contact\ntype PublishKeyMessage struct {\n\tOrder      string `json:\"order\"`\n\tUserId     string `json:\"user_id\"`\n\tResourceId string `json:\"resource_id\"`\n\tKeyId      string `json:\"key_id\"`\n}\n\n// unmarshal a map[string]interface{} that must owns all PublicKey's fields\n// typical usage is for unmarshaling response from Cassandra backend\nfunc (pk *PublicKey) UnmarshalCQLMap(input map[string]interface{}) {\n\tif alg, ok := input[\"alg\"].(string); ok {\n\t\tpk.Algorithm = alg\n\t}\n\tif crv, ok := input[\"crv\"].(string); ok {\n\t\tpk.Curve = crv\n\t}\n\tif dateInsert, ok := input[\"date_insert\"].(time.Time); ok {\n\t\tpk.DateInsert = dateInsert\n\t}\n\tif dateUpdate, ok := input[\"date_update\"].(time.Time); ok {\n\t\tpk.DateUpdate = dateUpdate\n\t}\n\tif emails, ok := input[\"emails\"]; ok && emails != nil {\n\t\tpk.Emails = []string{}\n\t\tfor _, email := range emails.([]string) {\n\t\t\tpk.Emails = append(pk.Emails, email)\n\t\t}\n\t}\n\tif expireDate, ok := input[\"expire_date\"].(time.Time); ok {\n\t\tpk.ExpireDate = expireDate\n\t}\n\tif fingerprint, ok := input[\"fingerprint\"].(string); ok {\n\t\tpk.Fingerprint = fingerprint\n\t}\n\tif key, ok := input[\"key\"].(string); ok {\n\t\tpk.Key = key\n\t}\n\tif keyid, ok := input[\"key_id\"].(gocql.UUID); ok {\n\t\tpk.KeyId.UnmarshalBinary(keyid.Bytes())\n\t}\n\tif kty, ok := input[\"kty\"].(string); ok {\n\t\tpk.KeyType = kty\n\t}\n\tif label, ok := input[\"label\"].(string); ok {\n\t\tpk.Label = label\n\t}\n\tif resourceId, ok := input[\"resource_id\"].(gocql.UUID); ok {\n\t\tpk.ResourceId.UnmarshalBinary(resourceId.Bytes())\n\t}\n\tif resourceType, ok := input[\"resource_type\"].(string); ok {\n\t\tpk.ResourceType = resourceType\n\t}\n\tif size, ok := input[\"size\"].(*big.Int); ok {\n\t\tpk.Size = int(size.Int64())\n\t}\n\tif use, ok := input[\"use\"].(string); ok {\n\t\tpk.Use = use\n\t}\n\tif userid, ok := input[\"user_id\"].(gocql.UUID); ok {\n\t\tpk.UserId.UnmarshalBinary(userid.Bytes())\n\t}\n\tif x, ok := input[\"x\"].(*big.Int); ok {\n\t\tpk.X = *x\n\t}\n\tif y, ok := input[\"y\"].(*big.Int); ok {\n\t\tpk.Y = *y\n\t}\n}\n\nfunc (pk *PublicKey) UnmarshalMap(input map[string]interface{}) error {\n\tif alg, ok := input[\"alg\"].(string); ok {\n\t\tpk.Algorithm = alg\n\t}\n\tif crv, ok := input[\"crv\"].(string); ok {\n\t\tpk.Curve = crv\n\t}\n\tif dateInsert, ok := input[\"date_insert\"]; ok {\n\t\tpk.DateInsert, _ = time.Parse(time.RFC3339Nano, dateInsert.(string))\n\t}\n\tif dateUpdate, ok := input[\"date_update\"]; ok {\n\t\tpk.DateUpdate, _ = time.Parse(time.RFC3339Nano, dateUpdate.(string))\n\t}\n\tif emails, ok := input[\"emails\"]; ok && emails != nil {\n\t\tpk.Emails = []string{}\n\t\tfor _, email := range emails.([]string) {\n\t\t\tpk.Emails = append(pk.Emails, email)\n\t\t}\n\t}\n\tif expireDate, ok := input[\"expire_date\"]; ok {\n\t\tpk.ExpireDate, _ = time.Parse(time.RFC3339Nano, expireDate.(string))\n\t}\n\tif fingerprint, ok := input[\"fingerprint\"].(string); ok {\n\t\tpk.Fingerprint = fingerprint\n\t}\n\tif key, ok := input[\"key\"].(string); ok {\n\t\tpk.Key = key\n\t}\n\tif keyid, ok := input[\"key_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(keyid); err == nil {\n\t\t\tpk.KeyId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif kty, ok := input[\"kty\"].(string); ok {\n\t\tpk.KeyType = kty\n\t}\n\tif label, ok := input[\"label\"].(string); ok {\n\t\tpk.Label = label\n\t}\n\tif resourceId, ok := input[\"resource_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(resourceId); err == nil {\n\t\t\tpk.ResourceId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif resourceType, ok := input[\"resource_type\"].(string); ok {\n\t\tpk.ResourceType = resourceType\n\t}\n\tif size, ok := input[\"size\"].(*big.Int); ok {\n\t\tpk.Size = int(size.Int64())\n\t}\n\tif use, ok := input[\"use\"].(string); ok {\n\t\tpk.Use = use\n\t}\n\tif userid, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(userid); err == nil {\n\t\t\tpk.UserId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif x, ok := input[\"x\"].(*big.Int); ok {\n\t\tpk.X = *x\n\t}\n\tif y, ok := input[\"y\"].(*big.Int); ok {\n\t\tpk.Y = *y\n\t}\n\treturn nil\n}\n\nfunc (pk *PublicKey) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn pk.UnmarshalMap(input)\n}\n\n// UnmarshalPGPEntity unmarshal a PGP entity into a PublicKey struct\n// ONLY if entity hold an identity matching with one contact's email address\nfunc (pk *PublicKey) UnmarshalPGPEntity(label string, entity *openpgp.Entity, contact *Contact) error {\n\n\tif entity.PrimaryKey == nil {\n\t\treturn fmt.Errorf(\"no primary found in entity\")\n\t}\n\tif contact == nil {\n\t\treturn fmt.Errorf(\"miss contact\")\n\t}\n\n\tif err := entity.PrimaryKey.ErrorIfDeprecated(); err != nil {\n\t\treturn err\n\t}\n\n\t//list contact's emails\n\temails := map[string]struct{}{}\n\tfor _, email := range contact.Emails {\n\t\tif email.Address != \"\" {\n\t\t\temails[email.Address] = struct{}{}\n\t\t}\n\t}\n\tif len(emails) == 0 {\n\t\treturn fmt.Errorf(\"no email address found in contact %s\", contact.ContactId.String())\n\t}\n\n\t/*  TODO : manage multiple identity and/or multiple subkeys */\n\tif len(entity.Identities) < 1 {\n\t\treturn fmt.Errorf(\"no identity packet found in entity\")\n\t}\n\tvar identityFound = false\n\tvar identityName string\n\temailsInKey := []string{}\n\tfor name, identity := range entity.Identities {\n\t\tif identity.Name != \"\" {\n\t\t\temailFound := ExtractEmailAddrFromString(identity.Name)\n\t\t\temailsInKey = append(emailsInKey, emailFound)\n\t\t\tif _, ok := emails[emailFound]; ok {\n\t\t\t\tidentityFound = true\n\t\t\t\tidentityName = name\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif !identityFound {\n\t\treturn fmt.Errorf(\"no matching identity found for contact %s\", contact.ContactId.String())\n\t}\n\n\tpk.MarshallNew(contact)\n\n\t// embed key in ASCII armor format\n\tb := new(bytes.Buffer)\n\tarmourBuffer, err := armor.Encode(b, openpgp.PublicKeyType, map[string]string{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to init ASCII armor : %s\", err.Error())\n\t}\n\tentity.Serialize(armourBuffer)\n\tarmourBuffer.Close()\n\tpk.Key = b.String()\n\n\t// encode fingerprint to upper hexastring\n\tpk.Fingerprint = strings.ToUpper(hex.EncodeToString(entity.PrimaryKey.Fingerprint[:]))\n\n\tpk.Label = label\n\tkeyLength, _ := entity.PrimaryKey.BitLength()\n\tpk.Size = int(keyLength)\n\tif entity.PrimaryKey.CanSign() {\n\t\tpk.Use = SIGNATURE_KEY\n\t}\n\n\t// try to complement data from identity & signature packets\n\tidentity := entity.Identities[identityName]\n\tif identity.SelfSignature == nil {\n\t\tlog.Warn(\"no self signature found to unmarshal PGP entity into public key \", pk.KeyId.String())\n\t\treturn nil\n\t}\n\n\tif identity.SelfSignature.KeyExpired(time.Now()) {\n\t\tlog.Warn(\"self signature has expired\")\n\t\treturn nil\n\t}\n\n\thashSize := identity.SelfSignature.Hash.Size() * 8 // size in in bytes, convert it to bits\n\tpk.ExpireDate = GetExpiryDate(identity.SelfSignature)\n\tpk.KeyType = PGP_KEY_TYPE\n\tswitch entity.PrimaryKey.PubKeyAlgo {\n\tcase packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoRSASignOnly:\n\t\tswitch hashSize {\n\t\tcase 256:\n\t\t\tpk.Algorithm = RSA256\n\t\tcase 384:\n\t\t\tpk.Algorithm = RSA384\n\t\tcase 512:\n\t\t\tpk.Algorithm = RSA512\n\t\tdefault:\n\t\t\tlog.Warn(\"unsupported hash size\")\n\t\t}\n\tcase packet.PubKeyAlgoECDSA:\n\t\tkey := entity.PrimaryKey.PublicKey.(*ecdsa.PublicKey)\n\t\tpk.Curve = key.Params().Name\n\t\tpk.X = *key.X\n\t\tpk.Y = *key.Y\n\t\tswitch hashSize {\n\t\tcase 256:\n\t\t\tpk.Algorithm = ECDSA256\n\t\tcase 384:\n\t\t\tpk.Algorithm = ECDSA384\n\t\tdefault:\n\t\t\tlog.Warn(\"unsupported hash size\")\n\t\t}\n\tcase packet.PubKeyAlgoDSA:\n\t\tswitch hashSize {\n\t\tcase 256:\n\t\t\tpk.Algorithm = DSA256\n\t\tcase 384:\n\t\t\tpk.Algorithm = DSA384\n\t\tcase 512:\n\t\t\tpk.Algorithm = DSA512\n\t\tdefault:\n\t\t\tlog.Warn(\"unsupported hash size\")\n\t\t}\n\tcase packet.PubKeyAlgoElGamal:\n\t\tswitch hashSize {\n\t\tcase 256:\n\t\t\tpk.Algorithm = ELGAMAL256\n\t\tcase 384:\n\t\t\tpk.Algorithm = ELGAMAL384\n\t\tcase 512:\n\t\t\tpk.Algorithm = ELGAMAL512\n\t\tdefault:\n\t\t\tlog.Warn(\"unsupported hash size\")\n\t\t}\n\tcase packet.PubKeyAlgoECDH:\n\t\tswitch hashSize {\n\t\tcase 256:\n\t\t\tpk.Algorithm = ECDH256\n\t\tcase 384:\n\t\t\tpk.Algorithm = ECDH384\n\t\tcase 512:\n\t\t\tpk.Algorithm = ECDH512\n\t\tdefault:\n\t\t\tlog.Warn(\"unsupported hash size\")\n\t\t}\n\tdefault:\n\t\tlog.Warn(\"unsupported public key\")\n\t}\n\n\t// embed emails found in key to ease later lookup\n\tpk.Emails = emailsInKey\n\treturn nil\n}\n\nfunc GetExpiryDate(s *packet.Signature) time.Time {\n\tif s.KeyLifetimeSecs != nil {\n\t\treturn s.CreationTime.Add(time.Duration(*s.KeyLifetimeSecs) * time.Second)\n\t}\n\treturn time.Time{}\n}\n\n// ExtractEmailAddrFromString returns the most left part email address pattern found in s\n// or an empty string if pattern not found\nfunc ExtractEmailAddrFromString(s string) string {\n\tr, _ := regexp.Compile(`[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\\.[a-zA-Z0-9_-]+`)\n\treturn r.FindString(s)\n}\n\n// GetTableInfos implements HasTable interface.\n// It returns params needed by CassandraBackend to CRUD on PublicKey table.\nfunc (pk *PublicKey) GetTableInfos() (table string, partitionKeys map[string]string, clusteringKeys map[string]string) {\n\treturn \"public_key\",\n\t\tmap[string]string{\n\t\t\t\"UserId\":     \"user_id\",\n\t\t\t\"ResourceId\": \"resource_id\",\n\t\t\t\"KeyId\":      \"key_id\",\n\t\t},\n\t\tmap[string]string{\n\t\t\t\"UserId\":     \"user_id\",\n\t\t\t\"ResourceId\": \"resource_id\",\n\t\t}\n}\n\nfunc (pk *PublicKey) MarshallNew(contacts ...interface{}) {\n\tif len(pk.KeyId) == 0 || (bytes.Equal(pk.KeyId.Bytes(), EmptyUUID.Bytes())) {\n\t\tpk.KeyId.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n\tif len(contacts) == 1 {\n\t\tc := contacts[0].(*Contact)\n\t\tpk.UserId.UnmarshalBinary(c.UserId.Bytes())\n\t\tpk.ResourceId.UnmarshalBinary(c.ContactId.Bytes())\n\t\tpk.ResourceType = ContactType\n\t\t// do not change date if mashallNew has been called multiple time on a publickey being created\n\t\tif pk.DateInsert.IsZero() {\n\t\t\tpk.DateInsert = time.Now()\n\t\t}\n\t\tif pk.DateUpdate.IsZero() {\n\t\t\tpk.DateUpdate = pk.DateInsert\n\t\t}\n\t}\n}\n\n// return a JSON representation of PublicKey suitable for frontend client\nfunc (pk *PublicKey) MarshalFrontEnd() ([]byte, error) {\n\treturn JSONMarshaller(\"frontend\", pk)\n}\n\nfunc (pk *PublicKey) JSONMarshaller() ([]byte, error) {\n\treturn JSONMarshaller(\"\", pk)\n}\n\nfunc (pk *PublicKey) NewEmpty() interface{} {\n\tp := new(PublicKey)\n\treturn p\n}\n\n/* ObjectPatchable interface */\nfunc (pk *PublicKey) JsonTags() map[string]string {\n\treturn jsonTags(pk)\n}\n\nfunc (pk *PublicKey) SortSlices() {\n\t// nothing to sort\n}\n\ntype PublicKeys []PublicKey\n\n// Sort interface implementation\ntype ByKeyId PublicKeys\n\nfunc (p ByKeyId) Len() int {\n\treturn len(p)\n}\n\nfunc (p ByKeyId) Less(i, j int) bool {\n\treturn p[i].KeyId.String() < p[j].KeyId.String()\n}\n\nfunc (p ByKeyId) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/device.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype Device struct {\n\t// PRIMARY KEYS (user_id, device_id)\n\tLocker          *sync.Mutex      `cql:\"-\"                json:\"-\"`\n\tDateInsert      time.Time        `cql:\"date_insert\"      json:\"date_insert,omitempty\"      patch:\"system\"        formatter:\"RFC3339Milli\"`\n\tDateRevoked     time.Time        `cql:\"date_revoked\"     json:\"date_revoked,omitempty\"     patch:\"system\"        formatter:\"RFC3339Milli\"`\n\tDeviceId        UUID             `cql:\"device_id\"        json:\"device_id\"                  patch:\"system\"`\n\tIpCreation      string           `cql:\"ip_creation\"      json:\"ip_creation\"                patch:\"user\"`\n\tLocations       DeviceLocations  `cql:\"-\"                json:\"locations,omitempty\"        patch:\"user\"`\n\tName            string           `cql:\"name\"             json:\"name\"                       patch:\"user\"`\n\tPrivacyFeatures *PrivacyFeatures `cql:\"privacy_features\" json:\"privacy_features,omitempty\" patch:\"user\"`\n\tPrivacyIndex    *PrivacyIndex    `cql:\"pi\"               json:\"pi,omitempty\"               patch:\"system\"`\n\tPublicKeys      PublicKeys       `cql:\"-\"                json:\"public_keys,omitempty\"      patch:\"user\"`\n\tStatus          string           `cql:\"status\"           json:\"status,omitempty\"           patch:\"system\"`\n\tType            string           `cql:\"type\"             json:\"type,omitempty\"             patch:\"user\"`\n\tUserAgent       string           `cql:\"user_agent\"       json:\"user_agent\"                 patch:\"system\"`\n\tUserId          UUID             `cql:\"user_id\"          json:\"user_id\"                    patch:\"system\"`\n}\n\n// payload for triggering a device validation process for an end-user\ntype DeviceValidationRequest struct {\n\tDeviceId UUID   `json:\"device_id\"`\n\tChannel  string `json:\"channel\"`\n}\n\n// UnmarshalCQLMap hydrates a Device with data from a map[string]interface{}\n// typical usage is for unmarshaling response from Cassandra backend\nfunc (d *Device) UnmarshalCQLMap(input map[string]interface{}) {\n\tif dateInsert, ok := input[\"date_insert\"].(time.Time); ok {\n\t\td.DateInsert = dateInsert\n\t}\n\tif deviceId, ok := input[\"device_id\"].(gocql.UUID); ok {\n\t\td.DeviceId.UnmarshalBinary(deviceId.Bytes())\n\t}\n\tif ipCreation, ok := input[\"ip_creation\"].(string); ok {\n\t\td.IpCreation = ipCreation\n\t}\n\t// locations are stored in another table\n\tif name, ok := input[\"name\"].(string); ok {\n\t\td.Name = name\n\t}\n\tif i_pf, ok := input[\"privacy_features\"].(map[string]string); ok && i_pf != nil {\n\t\tpf := PrivacyFeatures{}\n\t\tfor k, v := range i_pf {\n\t\t\tpf[k] = v\n\t\t}\n\t\td.PrivacyFeatures = &pf\n\n\t} else {\n\t\td.PrivacyFeatures = nil\n\t}\n\tif i_pi, ok := input[\"pi\"].(map[string]interface{}); ok && i_pi != nil {\n\t\tpi := PrivacyIndex{}\n\t\tpi.Comportment, _ = i_pi[\"comportment\"].(int)\n\t\tpi.Context, _ = i_pi[\"context\"].(int)\n\t\tpi.DateUpdate, _ = i_pi[\"date_update\"].(time.Time)\n\t\tpi.Technic, _ = i_pi[\"technic\"].(int)\n\t\tpi.Version, _ = i_pi[\"version\"].(int)\n\t\td.PrivacyIndex = &pi\n\t} else {\n\t\td.PrivacyIndex = nil\n\t}\n\n\t// publicKeys are stored in another table\n\n\tif revokedAt, ok := input[\"revoked_at\"].(time.Time); ok {\n\t\td.DateRevoked = revokedAt\n\t}\n\tif status, ok := input[\"status\"].(string); ok {\n\t\td.Status = status\n\t}\n\tif i_type, ok := input[\"type\"].(string); ok {\n\t\td.Type = i_type\n\t}\n\tif userAgent, ok := input[\"user_agent\"].(string); ok {\n\t\td.UserAgent = userAgent\n\t}\n\tif userId, ok := input[\"user_id\"].(gocql.UUID); ok {\n\t\td.UserId.UnmarshalBinary(userId.Bytes())\n\t}\n}\n\n// UnmarshalMap hydrates a Device with data from a map[string]interface{}\nfunc (d *Device) UnmarshalMap(input map[string]interface{}) error {\n\tif dateInsert, ok := input[\"date_insert\"]; ok {\n\t\td.DateInsert, _ = time.Parse(time.RFC3339Nano, dateInsert.(string))\n\t}\n\tif deviceId, ok := input[\"device_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(deviceId); err == nil {\n\t\t\td.DeviceId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif ipCreation, ok := input[\"ip_creation\"].(string); ok {\n\t\td.IpCreation = ipCreation\n\t}\n\n\tif locations, ok := input[\"locations\"]; ok && locations != nil {\n\t\td.Locations = DeviceLocations{}\n\t\tfor _, location := range locations.([]interface{}) {\n\t\t\tL := new(DeviceLocation)\n\t\t\tif err := L.UnmarshalMap(location.(map[string]interface{})); err == nil {\n\t\t\t\td.Locations = append(d.Locations, *L)\n\t\t\t}\n\t\t}\n\t}\n\tif name, ok := input[\"name\"].(string); ok {\n\t\td.Name = name\n\t}\n\tif i_pf, ok := input[\"privacy_features\"]; ok && i_pf != nil {\n\t\tpf := &PrivacyFeatures{}\n\t\tpf.UnmarshalMap(i_pf.(map[string]interface{}))\n\t\td.PrivacyFeatures = pf\n\n\t}\n\tif i_pi, ok := input[\"pi\"]; ok && i_pi != nil {\n\t\tpi := new(PrivacyIndex)\n\t\tif err := pi.UnmarshalMap(i_pi.(map[string]interface{})); err == nil {\n\t\t\td.PrivacyIndex = pi\n\t\t}\n\t}\n\tif pks, ok := input[\"public_keys\"]; ok && pks != nil {\n\t\td.PublicKeys = PublicKeys{}\n\t\tfor _, pk := range pks.([]interface{}) {\n\t\t\tK := new(PublicKey)\n\t\t\tif err := K.UnmarshalMap(pk.(map[string]interface{})); err == nil {\n\t\t\t\td.PublicKeys = append(d.PublicKeys, *K)\n\t\t\t}\n\t\t}\n\t}\n\tif revokedAt, ok := input[\"revoked_at\"]; ok {\n\t\td.DateRevoked, _ = time.Parse(time.RFC3339Nano, revokedAt.(string))\n\t}\n\tif status, ok := input[\"status\"].(string); ok {\n\t\td.Status = status\n\t}\n\tif i_type, ok := input[\"type\"].(string); ok {\n\t\td.Type = i_type\n\t}\n\tif userAgent, ok := input[\"user_agent\"].(string); ok {\n\t\td.UserAgent = userAgent\n\t}\n\tif userId, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(userId); err == nil {\n\t\t\td.UserId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *Device) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.UnmarshalMap(input)\n}\n\n// return a JSON representation of Device suitable for frontend client\nfunc (d *Device) MarshalFrontEnd() ([]byte, error) {\n\treturn JSONMarshaller(\"frontend\", d)\n}\n\n// bespoke implementation of the json.Marshaller interface\n// outputs a JSON representation of an object\n// this marshaller takes account of custom tags for given 'context'\nfunc (d *Device) JSONMarshaller() ([]byte, error) {\n\treturn JSONMarshaller(\"\", d)\n}\n\nfunc (d *Device) NewEmpty() interface{} {\n\tnd := new(Device)\n\tnd.Locations = DeviceLocations{}\n\treturn nd\n}\n\nfunc (d *Device) MarshallNew(args ...interface{}) {\n\tif len(d.DeviceId) == 0 || (bytes.Equal(d.DeviceId.Bytes(), EmptyUUID.Bytes())) {\n\t\td.DeviceId.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n\tif len(d.UserId) == 0 || (bytes.Equal(d.UserId.Bytes(), EmptyUUID.Bytes())) {\n\t\tif len(args) == 1 {\n\t\t\tswitch args[0].(type) {\n\t\t\tcase UUID:\n\t\t\t\td.UserId = args[0].(UUID)\n\t\t\t}\n\t\t}\n\t}\n\tif d.DateInsert.IsZero() {\n\t\td.DateInsert = time.Now()\n\t}\n\td.DateRevoked = time.Time{}\n\n\tfor i := range d.Locations {\n\t\td.Locations[i].MarshallNew()\n\t}\n\n\tif strings.TrimSpace(d.Type) == \"\" {\n\t\td.Type = DefaultDeviceType()\n\t}\n}\n\nfunc (d *Device) JsonTags() map[string]string {\n\treturn jsonTags(d)\n}\n\nfunc (d *Device) SortSlices() {\n\tsort.Sort(ByIpAddress(d.Locations))\n}\n\n// GetRelatedList returns a map[PropertyKey]Type of structs that are embedded into a Device from joined tables\nfunc (d *Device) GetRelatedList() map[string]interface{} {\n\treturn map[string]interface{}{\n\t\t\"Locations\": &DeviceLocation{},\n\t\t//\"PublicKey\": &PublicKey{},\n\t}\n}\n\nfunc (d *Device) GetSetRelated() <-chan interface{} {\n\tgetSet := make(chan interface{})\n\tif d.Locker == nil {\n\t\td.Locker = new(sync.Mutex)\n\t}\n\tgo func(*sync.Mutex, chan interface{}) {\n\t\td.Locker.Lock()\n\n\t\t// send locations\n\t\tfor i := range d.Locations {\n\t\t\tgetSet <- &(d.Locations[i])\n\t\t}\n\n\t\t// send publicKeys\n\t\tfor i := range d.PublicKeys {\n\t\t\tgetSet <- &(d.PublicKeys[i])\n\t\t}\n\n\t\t// all done\n\t\tclose(getSet)\n\t\td.Locker.Unlock()\n\t}(d.Locker, getSet)\n\n\treturn getSet\n}\n\n// IsValidDeviceType checks the `t` string against the constant `DeviceTypes`\n// returns true if `t` is a valid type for a device.\nfunc IsValidDeviceType(t string) bool {\n\tfor _, typ := range DeviceTypes {\n\t\tif typ == t {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// DefaultDeviceType returns the first type found within `DeviceTypes` constant\n// which should be the default one.\nfunc DefaultDeviceType() string {\n\treturn DeviceTypes[0]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/discussion.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"github.com/satori/go.uuid\"\n\t\"time\"\n)\n\ntype (\n\tDiscussion struct {\n\t\tAttachmentCount    int32         `json:\"attachment_count,omitempty\"`\n\t\tAliases            []string      `json:\"aliases,omitempty\"` // other discussion_id linked to this one, if any\n\t\tDateInsert         time.Time     `json:\"date_insert,omitempty\"              formatter:\"RFC3339Milli\"`\n\t\tDateUpdate         time.Time     `json:\"date_update,omitempty\"              formatter:\"RFC3339Milli\"`\n\t\tDiscussionId       string        `json:\"discussion_id\"                      formatter:\"rfc4122\"`\n\t\tExcerpt            string        `json:\"excerpt\"`\n\t\tImportanceLevel    int32         `json:\"importance_level\"`\n\t\tLastMessageDate    time.Time     `json:\"last_message_date,omitempty\"        formatter:\"RFC3339Milli\"`\n\t\tLastMessageId      UUID          `json:\"last_message_id\"`\n\t\tLastMessageSubject string        `json:\"last_message_subject\"`\n\t\tParticipants       []Participant `json:\"participants\"`\n\t\tProtocol           string        `json:\"protocol,omitempty\"`\n\t\tSubject            string        `json:\"subject\"`\n\t\tTags               []string      `json:\"tags,omitempty\"`\n\t\tTotalCount         int32         `json:\"total_count\"`\n\t\tUnreadCount        int32         `json:\"unread_count\"`\n\t\tUserId             UUID          `json:\"user_id\"`\n\t}\n)\n\nfunc (d *Discussion) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.UnmarshalMap(input)\n}\n\n// UnmarshalMap hydrates a Discussion with data from map[string]interface{}\nfunc (d *Discussion) UnmarshalMap(input map[string]interface{}) error {\n\tif aliases, ok := input[\"aliases\"].([]string); ok {\n\t\td.Aliases = aliases\n\t}\n\tif attachmentCount, ok := input[\"attachment_count\"].(float64); ok {\n\t\td.AttachmentCount = int32(attachmentCount)\n\t}\n\tif dateInsert, ok := input[\"date_insert\"].(string); ok {\n\t\td.DateInsert, _ = time.Parse(time.RFC3339Nano, dateInsert)\n\t}\n\tif dateUpdate, ok := input[\"date_update\"].(string); ok {\n\t\td.DateUpdate, _ = time.Parse(time.RFC3339Nano, dateUpdate)\n\t}\n\tif discId, ok := input[\"discussion_id\"].(string); ok {\n\t\td.DiscussionId = discId\n\t}\n\tif excerpt, ok := input[\"excerpt\"].(string); ok {\n\t\td.Excerpt = excerpt\n\t}\n\tif il, ok := input[\"importance_level\"].(float64); ok {\n\t\td.ImportanceLevel = int32(il)\n\t}\n\tif lastMsgDate, ok := input[\"last_message_date\"].(string); ok {\n\t\td.LastMessageDate, _ = time.Parse(time.RFC3339Nano, lastMsgDate)\n\t}\n\tif lastMsgId, ok := input[\"last_message_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(lastMsgId); err == nil {\n\t\t\td.LastMessageId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif participants, ok := input[\"participants\"]; ok && participants != nil {\n\t\td.Participants = []Participant{}\n\t\tfor _, participant := range participants.([]interface{}) {\n\t\t\tP := new(Participant)\n\t\t\tif err := P.UnmarshalMap(participant.(map[string]interface{})); err == nil {\n\t\t\t\td.Participants = append(d.Participants, *P)\n\t\t\t}\n\t\t}\n\t}\n\tif protocol, ok := input[\"protocol\"].(string); ok {\n\t\td.Protocol = protocol\n\t}\n\tif subject, ok := input[\"subject\"].(string); ok {\n\t\td.Subject = subject\n\t}\n\tif tags, ok := input[\"tags\"]; ok && tags != nil {\n\t\td.Tags = []string{}\n\t\tfor _, tag := range tags.([]interface{}) {\n\t\t\td.Tags = append(d.Tags, tag.(string))\n\t\t}\n\t}\n\tif totalCount, ok := input[\"total_count\"].(float64); ok {\n\t\td.TotalCount = int32(totalCount)\n\t}\n\tif unreadCount, ok := input[\"unread_count\"].(float64); ok {\n\t\td.UnreadCount = int32(unreadCount)\n\t}\n\tif userId, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(userId); err == nil {\n\t\t\td.UserId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\treturn nil\n}\n\n// bespoke implementation of the json.Marshaller interface\n// outputs a JSON representation of an object\n// this marshaler takes account of custom tags for given 'context'\nfunc (d *Discussion) JSONMarshaller() ([]byte, error) {\n\treturn JSONMarshaller(\"\", d)\n}\n\n// return a JSON representation of Message suitable for frontend client\nfunc (d *Discussion) MarshalFrontEnd() ([]byte, error) {\n\treturn JSONMarshaller(\"frontend\", d)\n}\n\n// MarshallNew implements CaliopenObject interface\nfunc (d *Discussion) MarshallNew(args ...interface{}) {\n\tif len(d.UserId) == 0 || (bytes.Equal(d.UserId.Bytes(), EmptyUUID.Bytes())) {\n\t\tif len(args) == 1 {\n\t\t\tswitch args[0].(type) {\n\t\t\tcase UUID:\n\t\t\t\td.UserId = args[0].(UUID)\n\t\t\t}\n\t\t}\n\t}\n\n\tif d.DateInsert.IsZero() {\n\t\td.DateInsert = time.Now()\n\t}\n}\n\ntype ByLastMessageDateDesc []Discussion\n\nfunc (blm ByLastMessageDateDesc) Len() int {\n\treturn len(blm)\n}\n\nfunc (blm ByLastMessageDateDesc) Less(i, j int) bool {\n\treturn blm[i].LastMessageDate.After(blm[j].LastMessageDate)\n}\n\nfunc (blm ByLastMessageDateDesc) Swap(i, j int) {\n\tblm[i], blm[j] = blm[j], blm[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/email.go",
    "content": "package objects\n\nimport (\n\t\"bytes\"\n\t\"github.com/satori/go.uuid\"\n\t\"net/mail\"\n)\n\ntype (\n\t// EmailMessage is a wrapper to handle the relationship\n\t// between a raw email, its json representation and its Caliopen counterpart\n\tEmailMessage struct {\n\t\tEmail      *Email\n\t\tEmail_json *EmailJson\n\t\tMessage    *Message\n\t}\n\n\t//email is a basic container to handle incoming & outcoming raw emails.\n\tEmail struct {\n\t\tSmtpMailFrom []string     // from or for the smtp agent\n\t\tSmtpRcpTo    []string     // from or for the smtp agent\n\t\tRaw          bytes.Buffer // raw email (without the Bcc header)\n\t\tImapUid      uint32       // optional uid fetched from remote imap account\n\t\t//TODO: add more infos from mta\n\t}\n\n\t//json representation of a parsed raw email.\n\tEmailJson struct {\n\t\tAddresses      []EmailAddress      // all email addresses extracted from address fields\n\t\tDate           string              // the exact string found. No treatment at all\n\t\tEnvelope       Envelope            // smtp envelope from MTA\n\t\tHeaders        map[string][]string // repeated headers are grouped into one\n\t\tHtml           string              // html body mime part, if any\n\t\tIsTextFromHTML bool                // plain text was empty; down-converted HTML\n\t\tMimeRoot       MimeRoot            // the top level mime part, if any\n\t\tPlain          string              // plain text body\n\t\tSubject        string\n\t}\n\n\tEmailAddress struct {\n\t\tAddr  mail.Address\n\t\tField string // To, From, etc. = field where email address was\n\t}\n\n\tEnvelope struct {\n\t\tTo          []string // the email addresses the server is sending to.\n\t\tRecipients  []string // the full list of recipients that the remote server is attempting to send to.\n\t\tFrom        []string // the email addresses that the server was sending from.\n\t\tHelo_domain string   // the domaine reported by the sending server\n\t\tRemote_ip   string   // the remote IP address of the sending server\n\t\tSpf         string   // the SPF result for the given IP address and domain\n\t}\n\n\t//struct to hold mime parts logic\n\tMimeRoot struct {\n\t\tAttachments_count int\n\t\tRoot_boundary     string\n\t\tInline_count      int\n\t\tParts             Parts\n\t}\n\n\tPart struct {\n\t\tBoundary      string\n\t\tCharset       string\n\t\tContent       []byte\n\t\tContentType   string\n\t\tHeaders       map[string][]string\n\t\tIs_attachment bool\n\t\tIs_inline     bool\n\t\tParts         Parts\n\t}\n\n\tParts []Part\n\n\t// email address embedded in contact\n\tEmailContact struct {\n\t\tAddress   string `cql:\"address\"     json:\"address,omitempty\"      patch:\"user\"`\n\t\tEmailId   UUID   `cql:\"email_id\"    json:\"email_id,omitempty\"     patch:\"system\"`\n\t\tIsPrimary bool   `cql:\"is_primary\"  json:\"is_primary\"   patch:\"user\"`\n\t\tLabel     string `cql:\"label\"       json:\"label,omitempty\"        patch:\"user\"`\n\t\tType      string `cql:\"type\"        json:\"type,omitempty\"         patch:\"user\"`\n\t}\n)\n\n// Returns a flattened array of attachments' bytes, ordered by precedence (in depth-first order, see Walk() func below)\n// function walks through email's parts tree to find parts that are labelled «is_attachment»\n// if an (optional) index is provided, the func returns bytes for the referenced attachment only\n// attachments are decoded according to \"Content-Transfer-Encoding\" header.\nfunc (email EmailJson) ExtractAttachments(index ...int) (attachments [][]byte, err error) {\n\ti := 0\n\twithIndex := false\n\tif len(index) == 1 {\n\t\twithIndex = true\n\t}\n\tfor part := range email.MimeRoot.Parts.Walk() {\n\t\tif part.Is_attachment {\n\t\t\tif withIndex {\n\t\t\t\tif i == index[0] {\n\t\t\t\t\tattachments = append(attachments, part.Content)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tattachments = append(attachments, part.Content)\n\t\t\t}\n\t\t\ti++\n\t\t}\n\t}\n\treturn\n}\n\n// mimic Python's email.walk() func :\n// Walk over the message tree, yielding each subpart.\n// The walk is performed in depth-first order.  This method is an iterator.\n// usage : for part := range parts.Walk() {…}\nfunc (parts Parts) Walk() (partChan chan Part) {\n\tpartChan = make(chan Part)\n\tif len(parts) < 1 {\n\t\tclose(partChan)\n\t\treturn\n\t}\n\tgo func() {\n\t\tfor _, part := range parts {\n\t\t\tpartChan <- part\n\t\t\tif len(part.Parts) > 0 {\n\t\t\t\tfor sub_part := range part.Parts.Walk() {\n\t\t\t\t\tpartChan <- sub_part\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tclose(partChan)\n\t}()\n\treturn\n}\n\nfunc (ec *EmailContact) UnmarshalMap(input map[string]interface{}) error {\n\tec.Address, _ = input[\"address\"].(string)\n\tif email_id, ok := input[\"email_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(email_id); err == nil {\n\t\t\t_ = ec.EmailId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tec.IsPrimary, _ = input[\"is_primary\"].(bool)\n\tec.Label, _ = input[\"label\"].(string)\n\tec.Type, _ = input[\"type\"].(string)\n\treturn nil //TODO: errors handling\n}\n\n// MarshallNew must be a variadic func to implement NewMarshaller interface,\n// but EmailContact does not need params to marshal a well-formed EmailContact: ...interface{} is ignored\nfunc (ec *EmailContact) MarshallNew(...interface{}) {\n\tif len(ec.EmailId.Bytes()) == 0 || (bytes.Equal(ec.EmailId.Bytes(), EmptyUUID.Bytes())) {\n\t\t_ = ec.EmailId.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n}\n\n// Sort interface implementation\ntype ByEmailContactID []EmailContact\n\nfunc (p ByEmailContactID) Len() int {\n\treturn len(p)\n}\n\nfunc (p ByEmailContactID) Less(i, j int) bool {\n\treturn p[i].EmailId.String() < p[j].EmailId.String()\n}\n\nfunc (p ByEmailContactID) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/errors.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage objects\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// implements standard Error interface as well as custom CaliopenError interface\ntype CaliopenErr struct {\n\tcause error\n\tcode  int\n\tmsg   string\n}\n\ntype CaliopenError interface {\n\tCause() error\n\tCode() int32\n\tError() string\n}\n\nfunc (ce CaliopenErr) Error() string {\n\treturn ce.msg\n}\n\nfunc (ce CaliopenErr) Code() int32 {\n\treturn int32(ce.code)\n}\n\nfunc (ce CaliopenErr) Cause() error {\n\treturn ce.cause\n}\nfunc NewCaliopenErr(code int, msg string) CaliopenErr {\n\treturn CaliopenErr{errors.New(\"nil\"), code, msg}\n}\n\nfunc NewCaliopenErrf(code int, format string, a ...interface{}) CaliopenErr {\n\treturn CaliopenErr{\n\t\tcause: errors.New(\"nil\"),\n\t\tcode:  code,\n\t\tmsg:   fmt.Sprintf(format, a...),\n\t}\n}\n\nfunc WrapCaliopenErrf(err error, code int, format string, a ...interface{}) CaliopenErr {\n\te := CaliopenErr{\n\t\tcode: code,\n\t\tmsg:  fmt.Sprintf(format, a...),\n\t}\n\tif err != nil {\n\t\te.cause = err\n\t} else {\n\t\te.cause = errors.New(\"nil\")\n\t}\n\treturn e\n}\n\nfunc WrapCaliopenErr(err error, code int, msg string) CaliopenErr {\n\te := CaliopenErr{\n\t\tcode: code,\n\t\tmsg:  msg,\n\t}\n\tif err != nil {\n\t\te.cause = err\n\t} else {\n\t\te.cause = errors.New(\"nil\")\n\t}\n\treturn e\n}\n\n// custom errors code\nconst (\n\tUnknownCaliopenErr = iota\n\tDbCaliopenErr\n\tIndexCaliopenErr\n\tNotFoundCaliopenErr\n\tFailDependencyCaliopenErr\n\tUnprocessableCaliopenErr\n\tForbiddenCaliopenErr\n\tNotImplementedCaliopenErr\n\tWrongCredentialsErr\n\n\tDuplicateMessage = \"message already imported for this user\" // error message sent by delivery.py via nats\n)\n"
  },
  {
    "path": "src/backend/defs/go-objects/external_references.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\ntype ExternalReferences struct {\n\tAncestors_ids []string `cql:\"ancestors_ids\"            json:\"ancestors_ids,omitempty\"`\n\tMessage_id    string   `cql:\"message_id\"               json:\"message_id,omitempty\"`\n\tParent_id     string   `cql:\"parent_id\"                json:\"parent_id,omitempty\"`\n}\n\nfunc (er *ExternalReferences) UnmarshalMap(input map[string]interface{}) error {\n\ter.Ancestors_ids = []string{}\n\tif _, ok := input[\"ancestors_ids\"]; ok && input[\"ancestors_ids\"] != nil {\n\t\tfor _, id := range input[\"ancestors_ids\"].([]interface{}) {\n\t\t\ter.Ancestors_ids = append(er.Ancestors_ids, id.(string))\n\t\t}\n\t}\n\ter.Message_id, _ = input[\"message_id\"].(string)\n\ter.Parent_id, _ = input[\"parent_id\"].(string)\n\treturn nil //TODO: error handling\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/im.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"github.com/satori/go.uuid\"\n)\n\n// contact's instant messaging address model\ntype IM struct {\n\tAddress   string `cql:\"address\"     json:\"address\"      cql_lookup:\"contact_lookup\"`\n\tIMId      UUID   `cql:\"im_id\"       json:\"im_id\"`\n\tIsPrimary bool   `cql:\"is_primary\"  json:\"is_primary\"`\n\tLabel     string `cql:\"label\"       json:\"label\"`\n\tProtocol  string `cql:\"protocol\"    json:\"protocol\"`\n\tType      string `cql:\"type\"        json:\"type\"`\n}\n\nfunc (i *IM) UnmarshalMap(input map[string]interface{}) error {\n\ti.Address, _ = input[\"address\"].(string)\n\tif im_id, ok := input[\"im_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(im_id); err == nil {\n\t\t\ti.IMId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\ti.IsPrimary, _ = input[\"is_primary\"].(bool)\n\ti.Label, _ = input[\"label\"].(string)\n\ti.Protocol, _ = input[\"protocol\"].(string)\n\ti.Type, _ = input[\"type\"].(string)\n\n\treturn nil //TODO: errors handling\n}\n\n// MarshallNew must be a variadic func to implement NewMarshaller interface,\n// but IM does not need params to marshal a well-formed IM: ...interface{} is ignored\nfunc (i *IM) MarshallNew(...interface{}) {\n\tif len(i.IMId) == 0 || (bytes.Equal(i.IMId.Bytes(), EmptyUUID.Bytes())) {\n\t\ti.IMId.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n}\n\n// Sort interface implementation\ntype ByIMID []IM\n\nfunc (p ByIMID) Len() int {\n\treturn len(p)\n}\n\nfunc (p ByIMID) Less(i, j int) bool {\n\treturn p[i].IMId.String() < p[j].IMId.String()\n}\n\nfunc (p ByIMID) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/location.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n)\n\ntype DeviceLocation struct {\n\t// PRIMARY KEYS (user_id, device_id, ip_address)\n\tCountry   string `cql:\"country\"          json:\"country,omitempty\"       patch:\"user\"`\n\tDeviceId  UUID   `cql:\"device_id\"        json:\"device_id\"               patch:\"user\"`\n\tIpAddress string `cql:\"address\"          json:\"address,omitempty\"       patch:\"user\"`\n\tType      string `cql:\"type\"             json:\"type,omitempty\"          patch:\"user\"`\n\tUserId    UUID   `cql:\"user_id\"          json:\"user_id\"                 patch:\"system\"`\n}\n\ntype DeviceLocations []DeviceLocation\n\nfunc (dl *DeviceLocation) UnmarshalMap(input map[string]interface{}) error {\n\tif country, ok := input[\"country\"].(string); ok {\n\t\tdl.Country = country\n\t}\n\tif deviceId, ok := input[\"device_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(deviceId); err == nil {\n\t\t\tdl.DeviceId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif ipAddress, ok := input[\"address\"].(string); ok {\n\t\tdl.IpAddress = ipAddress\n\t}\n\tif i_type, ok := input[\"type\"].(string); ok {\n\t\tdl.Type = i_type\n\t}\n\tif userId, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(userId); err == nil {\n\t\t\tdl.UserId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (dl *DeviceLocation) UnmarshalCQLMap(input map[string]interface{}) {\n\tif country, ok := input[\"country\"].(string); ok {\n\t\tdl.Country = country\n\t}\n\tif deviceId, ok := input[\"device_id\"].(gocql.UUID); ok {\n\t\tdl.DeviceId.UnmarshalBinary(deviceId.Bytes())\n\t}\n\tif ipAddress, ok := input[\"address\"].(string); ok {\n\t\tdl.IpAddress = ipAddress\n\t}\n\tif i_type, ok := input[\"type\"].(string); ok {\n\t\tdl.Type = i_type\n\t}\n\tif userId, ok := input[\"user_id\"].(gocql.UUID); ok {\n\t\tdl.UserId.UnmarshalBinary(userId.Bytes())\n\t}\n}\n\n// GetTableInfos implements HasTable interface.\n// It returns params needed by CassandraBackend to CRUD on location table.\nfunc (dl *DeviceLocation) GetTableInfos() (table string, partitionKeys map[string]string, clusteringKeys map[string]string) {\n\treturn \"device_location\",\n\t\tmap[string]string{\n\t\t\t\"UserId\":    \"user_id\",\n\t\t\t\"DeviceId\":  \"device_id\",\n\t\t\t\"IpAddress\": \"address\",\n\t\t},\n\t\tmap[string]string{\n\t\t\t\"UserId\":   \"user_id\",\n\t\t\t\"DeviceId\": \"device_id\",\n\t\t}\n}\n\n// MarshalNew could have a *Device has first argument\nfunc (dl *DeviceLocation) MarshallNew(args ...interface{}) {\n\tif len(dl.DeviceId) == 0 || (bytes.Equal(dl.DeviceId.Bytes(), EmptyUUID.Bytes())) {\n\t\tif len(args) == 1 {\n\t\t\tswitch args[0].(type) {\n\t\t\tcase *Device:\n\t\t\t\tdl.DeviceId = args[0].(*Device).DeviceId\n\t\t\t}\n\t\t}\n\t}\n\tif len(dl.UserId) == 0 || (bytes.Equal(dl.UserId.Bytes(), EmptyUUID.Bytes())) {\n\t\tif len(args) == 1 {\n\t\t\tswitch args[0].(type) {\n\t\t\tcase *Device:\n\t\t\t\tdl.UserId = args[0].(*Device).UserId\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (dl *DeviceLocation) NewEmpty() interface{} {\n\treturn new(DeviceLocation)\n}\n\nfunc (dl *DeviceLocation) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn dl.UnmarshalMap(input)\n}\n\nfunc (dl *DeviceLocation) MarshalFrontEnd() ([]byte, error) {\n\treturn JSONMarshaller(\"frontend\", dl)\n}\n\nfunc (dl *DeviceLocation) JSONMarshaller() ([]byte, error) {\n\treturn JSONMarshaller(\"\", dl)\n}\n\nfunc (dl *DeviceLocation) JsonTags() map[string]string {\n\treturn jsonTags(dl)\n}\n\nfunc (dl *DeviceLocation) SortSlices() {\n\t//nothing to sort\n}\n\n// Sort interface implementation\ntype ByIpAddress []DeviceLocation\n\nfunc (a ByIpAddress) Len() int {\n\treturn len(a)\n}\n\nfunc (a ByIpAddress) Less(i, j int) bool {\n\treturn a[i].IpAddress < a[j].IpAddress\n}\n\nfunc (a ByIpAddress) Swap(i, j int) {\n\ta[i], a[j] = a[j], a[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/message.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype PIMessage struct {\n\tTransport uint32 `json:\"transport\"`\n\tSocial    uint32 `json:\"social\"`\n\tContent   uint32 `json:\"content\"`\n}\n\ntype Message struct {\n\tAttachments         []Attachment       `cql:\"attachments\"              json:\"attachments,omitempty\"       `\n\tBody_html           string             `cql:\"body_html\"                json:\"body_html\"         `\n\tBody_plain          string             `cql:\"body_plain\"               json:\"body_plain\"        `\n\tBody_excerpt        string             `cql:\"-\"                        json:\"excerpt\"           `\n\tDate                time.Time          `cql:\"date\"                     json:\"date\"                                                      formatter:\"RFC3339Milli\"`\n\tDate_delete         time.Time          `cql:\"date_delete\"              json:\"date_delete,omitempty\"                                     formatter:\"RFC3339Milli\"`\n\tDate_insert         time.Time          `cql:\"date_insert\"              json:\"date_insert\"                                               formatter:\"RFC3339Milli\"`\n\tDate_sort           time.Time          `cql:\"date_sort\"                json:\"date_sort\"                                                 formatter:\"RFC3339Milli\"`\n\tDiscussion_id       string             `cql:\"discussion_id\"            json:\"discussion_id,omitempty\"                                   formatter:\"rfc4122\"`\n\tExternal_references ExternalReferences `cql:\"external_references\"      json:\"external_references,omitempty\"`\n\tUserIdentities      []UUID             `cql:\"user_identities\"          json:\"user_identities,omitempty\"       `\n\tImportance_level    int32              `cql:\"importance_level\"         json:\"importance_level\" `\n\tIs_answered         bool               `cql:\"is_answered\"              json:\"is_answered\"      `\n\tIs_draft            bool               `cql:\"is_draft\"                 json:\"is_draft\"         `\n\tIs_unread           bool               `cql:\"is_unread\"                json:\"is_unread\"        `\n\tIs_received         bool               `cql:\"is_received\"              json:\"is_received\"      `\n\tMessage_id          UUID               `cql:\"message_id\"               json:\"message_id,omitempty\"                                      formatter:\"rfc4122\"`\n\tParent_id           UUID               `cql:\"parent_id\"                json:\"parent_id,omitempty\"        `\n\tParticipants        []Participant      `cql:\"participants\"             json:\"participants\"     `\n\tPI                  *PIMessage         `cql:\"-\"                        json:\"pi_message,omitempty\"`\n\tPrivacy_features    *PrivacyFeatures   `cql:\"privacy_features\"         json:\"privacy_features,omitempty\" `\n\tPrivacyIndex        *PrivacyIndex      `cql:\"pi\"                       json:\"pi,omitempty\"`\n\tRaw_msg_id          UUID               `cql:\"raw_msg_id\"               json:\"raw_msg_id,omitempty\"                                      formatter:\"rfc4122\"`\n\tSubject             string             `cql:\"subject\"                  json:\"subject\"          `\n\tTags                []string           `cql:\"tagnames\"                 json:\"tags,omitempty\"                     patch:\"system\" `\n\tProtocol            string             `cql:\"protocol\"                 json:\"protocol,omitempty\"             `\n\tUser_id             UUID               `cql:\"user_id\"                  json:\"user_id,omitempty\"                  formatter:\"rfc4122\"`\n}\n\ntype Messages []*Message\n\n// bespoke implementation of the json.Marshaller interface\n// outputs a JSON representation of an object\n// this marshaler takes account of custom tags for given 'context'\nfunc (msg *Message) JSONMarshaller(context string, body_type ...string) ([]byte, error) {\n\tvar jsonBuf bytes.Buffer\n\tenc := json.NewEncoder(&jsonBuf)\n\n\tfields, err := reflections.Fields(*msg)\n\tif err != nil {\n\t\treturn jsonBuf.Bytes(), err\n\t}\n\tjsonBuf.WriteByte('{')\n\tfirst := new(bool)\n\t*first = true\n\tbody_not_merged := true\nfieldsLoop:\n\tfor _, field := range fields {\n\t\tswitch context {\n\t\tcase \"elastic\":\n\t\t\tj_elastic, err := reflections.GetFieldTag(msg, field, \"elastic\")\n\t\t\tif err == nil {\n\t\t\t\tswitch j_elastic {\n\t\t\t\tcase \"omit\":\n\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"frontend\":\n\t\t\tfront, err := reflections.GetFieldTag(msg, field, \"frontend\")\n\t\t\tif err == nil {\n\t\t\t\tswitch front {\n\t\t\t\tcase \"omit\":\n\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t}\n\t\t\t\t//output only one body for frontend clients\n\t\t\t\tif field == \"Body_html\" || field == \"Body_plain\" {\n\t\t\t\t\tif body_not_merged {\n\t\t\t\t\t\tif *first {\n\t\t\t\t\t\t\t*first = false\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tjsonBuf.WriteByte(',')\n\t\t\t\t\t\t}\n\t\t\t\t\t\tjsonBuf.WriteString(\"\\\"body\\\":\")\n\t\t\t\t\t\t// TODO : put html or plain in exported body regarding current user preferences\n\t\t\t\t\t\tvar body_is_plain bool\n\t\t\t\t\t\tif len(body_type) > 0 && len(msg.Body_html) > 0 && len(msg.Body_plain) > 0 {\n\t\t\t\t\t\t\tif body_type[0] == \"rich_text\" {\n\t\t\t\t\t\t\t\tenc.Encode(msg.Body_html)\n\t\t\t\t\t\t\t\tbody_is_plain = false\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tenc.Encode(msg.Body_plain)\n\t\t\t\t\t\t\t\tbody_is_plain = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif msg.Body_html != \"\" {\n\t\t\t\t\t\t\t\tenc.Encode(msg.Body_html)\n\t\t\t\t\t\t\t\tbody_is_plain = false\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tenc.Encode(msg.Body_plain)\n\t\t\t\t\t\t\t\tbody_is_plain = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif body_is_plain {\n\t\t\t\t\t\t\tjsonBuf.WriteString(\",\\\"body_is_plain\\\":true\")\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tjsonBuf.WriteString(\",\\\"body_is_plain\\\":false\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbody_not_merged = false\n\t\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tmarshallField(msg, field, context, &jsonBuf, first, enc)\n\t}\n\tjsonBuf.WriteByte('}')\n\treturn jsonBuf.Bytes(), nil\n}\n\nfunc (msg *Message) MarshalJSON() ([]byte, error) {\n\treturn msg.JSONMarshaller(\"json\")\n}\n\nfunc (msg *Message) MarshalES() ([]byte, error) {\n\treturn msg.JSONMarshaller(\"elastic\")\n}\n\n// return a JSON representation of Message suitable for frontend client\nfunc (msg *Message) MarshalFrontEnd(body_type string) ([]byte, error) {\n\treturn msg.JSONMarshaller(\"frontend\", body_type)\n}\n\nfunc (msg *Message) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\treturn msg.UnmarshalMap(input)\n}\n\nfunc (msg *Message) UnmarshalMap(input map[string]interface{}) error {\n\tif attachments, ok := input[\"attachments\"]; ok && attachments != nil {\n\t\tmsg.Attachments = []Attachment{}\n\t\tfor _, attachment := range attachments.([]interface{}) {\n\t\t\tA := new(Attachment)\n\t\t\tif err := A.UnmarshalMap(attachment.(map[string]interface{})); err == nil {\n\t\t\t\tmsg.Attachments = append(msg.Attachments, *A)\n\t\t\t}\n\t\t}\n\t}\n\tif body_html, ok := input[\"body_html\"].(string); ok {\n\t\tmsg.Body_html = body_html\n\t}\n\tif body_plain, ok := input[\"body_plain\"].(string); ok {\n\t\tmsg.Body_plain = body_plain\n\t}\n\tif date, ok := input[\"date\"]; ok && date != nil {\n\t\tmsg.Date, _ = time.Parse(time.RFC3339Nano, date.(string))\n\t}\n\tif date, ok := input[\"date_delete\"]; ok && date != nil {\n\t\tmsg.Date_delete, _ = time.Parse(time.RFC3339Nano, date.(string))\n\t}\n\tif date, ok := input[\"date_insert\"]; ok && date != nil {\n\t\tmsg.Date_insert, _ = time.Parse(time.RFC3339Nano, date.(string))\n\t}\n\tif date, ok := input[\"date_sort\"]; ok && date != nil {\n\t\tmsg.Date_sort, _ = time.Parse(time.RFC3339Nano, date.(string))\n\t}\n\tif discussion_id, ok := input[\"discussion_id\"].(string); ok {\n\t\tmsg.Discussion_id = discussion_id\n\t}\n\n\tif ex_ref, ok := input[\"external_references\"]; ok && ex_ref != nil {\n\t\tmsg.External_references = ExternalReferences{\n\t\t\tAncestors_ids: []string{},\n\t\t}\n\t\tmsg.External_references.UnmarshalMap(ex_ref.(map[string]interface{}))\n\t}\n\tif identities, ok := input[\"user_identities\"]; ok && identities != nil {\n\t\tmsg.UserIdentities = []UUID{}\n\t\tfor _, identity := range identities.([]interface{}) {\n\t\t\tuid := UUID{}\n\t\t\tif id, err := uuid.FromString(identity.(string)); err == nil {\n\t\t\t\tif e := uid.UnmarshalBinary(id.Bytes()); e == nil {\n\t\t\t\t\tmsg.UserIdentities = append(msg.UserIdentities, uid)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif importance_level, ok := input[\"importance_level\"].(float64); ok {\n\t\tmsg.Importance_level = int32(importance_level)\n\t}\n\tif is_answered, ok := input[\"is_answered\"].(bool); ok {\n\t\tmsg.Is_answered = is_answered\n\t}\n\tif is_draft, ok := input[\"is_draft\"].(bool); ok {\n\t\tmsg.Is_draft = is_draft\n\t}\n\tif is_unread, ok := input[\"is_unread\"].(bool); ok {\n\t\tmsg.Is_unread = is_unread\n\t}\n\tif is_received, ok := input[\"is_received\"].(bool); ok {\n\t\tmsg.Is_received = is_received\n\t}\n\tif message_id, ok := input[\"message_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(message_id); err == nil {\n\t\t\tmsg.Message_id.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\n\tif parent_id, ok := input[\"parent_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(parent_id); err == nil {\n\t\t\tmsg.Parent_id.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\n\tif participants, ok := input[\"participants\"]; ok && participants != nil {\n\t\tmsg.Participants = []Participant{}\n\t\tfor _, participant := range participants.([]interface{}) {\n\t\t\tP := new(Participant)\n\t\t\tif err := P.UnmarshalMap(participant.(map[string]interface{})); err == nil {\n\t\t\t\tmsg.Participants = append(msg.Participants, *P)\n\t\t\t}\n\t\t}\n\t}\n\tif i_pi, ok := input[\"pi\"]; ok && i_pi != nil {\n\t\tpi := new(PrivacyIndex)\n\t\tif err := pi.UnmarshalMap(i_pi.(map[string]interface{})); err == nil {\n\t\t\tmsg.PrivacyIndex = pi\n\t\t}\n\t}\n\tif pf, ok := input[\"privacy_features\"]; ok && pf != nil {\n\t\tPF := &PrivacyFeatures{}\n\t\tPF.UnmarshalMap(pf.(map[string]interface{}))\n\t\tmsg.Privacy_features = PF\n\t}\n\tif raw_msg_id, ok := input[\"raw_msg_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(raw_msg_id); err == nil {\n\t\t\tmsg.Raw_msg_id.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif subject, ok := input[\"subject\"].(string); ok {\n\t\tmsg.Subject = subject\n\t}\n\tif tags, ok := input[\"tags\"]; ok && tags != nil {\n\t\tmsg.Tags = []string{}\n\t\tfor _, tag := range tags.([]interface{}) {\n\t\t\tmsg.Tags = append(msg.Tags, tag.(string))\n\t\t}\n\t}\n\tif p, ok := input[\"protocol\"].(string); ok {\n\t\tmsg.Protocol = p\n\t}\n\tif user_id, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(user_id); err == nil {\n\t\t\tmsg.User_id.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// unmarshal a map[string]interface{} coming from gocql\nfunc (msg *Message) UnmarshalCQLMap(input map[string]interface{}) error {\n\tif _, ok := input[\"attachments\"]; ok {\n\t\tmsg.Attachments = []Attachment{}\n\t\tfor _, attachment := range input[\"attachments\"].([]map[string]interface{}) {\n\t\t\ta := Attachment{}\n\t\t\ta.ContentType, _ = attachment[\"content_type\"].(string)\n\t\t\ta.FileName, _ = attachment[\"file_name\"].(string)\n\t\t\ta.IsInline, _ = attachment[\"is_inline\"].(bool)\n\t\t\ta.Size, _ = attachment[\"size\"].(int)\n\t\t\ta.URL, _ = attachment[\"url\"].(string)\n\t\t\ta.MimeBoundary, _ = attachment[\"mime_boundary\"].(string)\n\t\t\tif temp_id, ok := attachment[\"temp_id\"].(gocql.UUID); ok {\n\t\t\t\ta.TempID.UnmarshalBinary(temp_id.Bytes())\n\t\t\t}\n\t\t\tmsg.Attachments = append(msg.Attachments, a)\n\t\t}\n\t}\n\tif body_html, ok := input[\"body_html\"].(string); ok {\n\t\tmsg.Body_html = body_html\n\t}\n\tif body_plain, ok := input[\"body_plain\"].(string); ok {\n\t\tmsg.Body_plain = body_plain\n\t}\n\tif date, ok := input[\"date\"].(time.Time); ok {\n\t\tmsg.Date = date\n\t}\n\tif date_delete, ok := input[\"date_delete\"].(time.Time); ok {\n\t\tmsg.Date_delete = date_delete\n\t}\n\tif date_insert, ok := input[\"date_insert\"].(time.Time); ok {\n\t\tmsg.Date_insert = date_insert\n\t}\n\tif date_sort, ok := input[\"date_sort\"].(time.Time); ok {\n\t\tmsg.Date_sort = date_sort\n\t}\n\tif discussion_id, ok := input[\"discussion_id\"].(string); ok {\n\t\tmsg.Discussion_id = discussion_id\n\t}\n\tmsg.External_references = ExternalReferences{\n\t\tAncestors_ids: []string{},\n\t}\n\tif ex_ref, ok := input[\"external_references\"].(map[string]interface{}); ok && ex_ref != nil {\n\t\tif ids, ok := ex_ref[\"ancestors_ids\"]; ok && len(ids.([]string)) > 0 {\n\t\t\tmsg.External_references.Ancestors_ids, _ = ids.([]string)\n\t\t} else {\n\t\t\tmsg.External_references.Ancestors_ids = []string{}\n\t\t}\n\t\tmsg.External_references.Message_id, _ = ex_ref[\"message_id\"].(string)\n\t\tmsg.External_references.Parent_id, _ = ex_ref[\"parent_id\"].(string)\n\t}\n\tif identities, ok := input[\"user_identities\"]; ok && identities != nil {\n\t\tmsg.UserIdentities = []UUID{}\n\t\tfor _, identity := range identities.([]gocql.UUID) {\n\t\t\tvar uid UUID\n\t\t\tif err := uid.UnmarshalBinary(identity.Bytes()); err == nil {\n\t\t\t\tmsg.UserIdentities = append(msg.UserIdentities, uid)\n\t\t\t}\n\t\t}\n\t}\n\tif importance_level, ok := input[\"importance_level\"].(int); ok {\n\t\tmsg.Importance_level = int32(importance_level)\n\t}\n\tif is_answered, ok := input[\"is_answered\"].(bool); ok {\n\t\tmsg.Is_answered = is_answered\n\t}\n\tif is_draft, ok := input[\"is_draft\"].(bool); ok {\n\t\tmsg.Is_draft = is_draft\n\t}\n\tif is_unread, ok := input[\"is_unread\"].(bool); ok {\n\t\tmsg.Is_unread = is_unread\n\t}\n\tif is_received, ok := input[\"is_received\"].(bool); ok {\n\t\tmsg.Is_received = is_received\n\t}\n\tif message_id, ok := input[\"message_id\"].(gocql.UUID); ok {\n\t\tmsg.Message_id.UnmarshalBinary(message_id.Bytes())\n\t}\n\n\tif parent_id, ok := input[\"parent_id\"].(gocql.UUID); ok {\n\t\tmsg.Parent_id.UnmarshalBinary(parent_id.Bytes())\n\t}\n\n\tif participants, ok := input[\"participants\"]; ok && participants != nil {\n\t\tmsg.Participants = []Participant{}\n\t\tfor _, participant := range participants.([]map[string]interface{}) {\n\t\t\tp := Participant{}\n\t\t\tp.Address, _ = participant[\"address\"].(string)\n\t\t\tp.Label, _ = participant[\"label\"].(string)\n\t\t\tp.Protocol, _ = participant[\"protocol\"].(string)\n\t\t\tp.Type, _ = participant[\"type\"].(string)\n\t\t\tif _, ok := participant[\"contact_ids\"]; ok {\n\t\t\t\tp.Contact_ids = []UUID{}\n\t\t\t\tfor _, id := range participant[\"contact_ids\"].([]gocql.UUID) {\n\t\t\t\t\tvar contact_uuid UUID\n\t\t\t\t\tcontact_uuid.UnmarshalBinary(id.Bytes())\n\t\t\t\t\tp.Contact_ids = append(p.Contact_ids, contact_uuid)\n\t\t\t\t}\n\t\t\t}\n\t\t\tmsg.Participants = append(msg.Participants, p)\n\t\t}\n\t}\n\tpi := PrivacyIndex{}\n\tif i_pi, ok := input[\"pi\"].(map[string]interface{}); ok && i_pi != nil {\n\t\tpi.Comportment, _ = i_pi[\"comportment\"].(int)\n\t\tpi.Context, _ = i_pi[\"context\"].(int)\n\t\tpi.DateUpdate, _ = i_pi[\"date_sort\"].(time.Time)\n\t\tpi.Technic, _ = i_pi[\"technic\"].(int)\n\t\tpi.Version, _ = i_pi[\"version\"].(int)\n\t}\n\tmsg.PrivacyIndex = &pi\n\n\tpf := PrivacyFeatures{}\n\tif i_pf, ok := input[\"privacy_features\"].(map[string]string); ok && i_pf != nil {\n\t\tfor k, v := range i_pf {\n\t\t\tpf[k] = v\n\t\t}\n\t}\n\tmsg.Privacy_features = &pf\n\n\tif raw_msg_id, ok := input[\"raw_msg_id\"].(gocql.UUID); ok {\n\t\tmsg.Raw_msg_id.UnmarshalBinary(raw_msg_id.Bytes())\n\t}\n\tif subject, ok := input[\"subject\"].(string); ok {\n\t\tmsg.Subject = subject\n\t}\n\tif tags, ok := input[\"tagnames\"]; ok && tags != nil {\n\t\tmsg.Tags = []string{}\n\t\tfor _, tag := range tags.([]string) {\n\t\t\tmsg.Tags = append(msg.Tags, tag)\n\t\t}\n\t}\n\tif p, ok := input[\"protocol\"].(string); ok {\n\t\tmsg.Protocol = p\n\t}\n\tif user_id, ok := input[\"user_id\"].(gocql.UUID); ok {\n\t\tmsg.User_id.UnmarshalBinary(user_id.Bytes())\n\t}\n\n\treturn nil //TODO: error handling\n}\n\n// part of the CaliopenObject interface\n// NewEmpty returns a new empty initialized sibling of itself\n// part of the CaliopenObject interface\nfunc (msg *Message) NewEmpty() interface{} {\n\tm := new(Message)\n\tm.Attachments = []Attachment{}\n\tm.External_references = ExternalReferences{}\n\tm.UserIdentities = []UUID{}\n\tm.Participants = []Participant{}\n\tm.Privacy_features = &PrivacyFeatures{}\n\tm.Tags = []string{}\n\treturn m\n}\n\nfunc (msg *Message) MarshallNew(args ...interface{}) {\n\tif len(msg.Message_id) == 0 || (bytes.Equal(msg.Message_id.Bytes(), EmptyUUID.Bytes())) {\n\t\tmsg.Message_id.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n\tif len(msg.User_id) == 0 || (bytes.Equal(msg.User_id.Bytes(), EmptyUUID.Bytes())) {\n\t\tif len(args) == 1 {\n\t\t\tswitch args[0].(type) {\n\t\t\tcase UUID:\n\t\t\t\tmsg.User_id = args[0].(UUID)\n\t\t\t}\n\t\t}\n\t}\n\n\tif msg.Date_insert.IsZero() {\n\t\tmsg.Date_insert = time.Now()\n\t}\n\n\tif msg.Date_sort.IsZero() {\n\t\tmsg.Date_sort = time.Now()\n\t}\n\n\tfor i := range msg.Attachments {\n\t\tmsg.Attachments[i].MarshallNew()\n\t}\n\n\tfor i := range msg.Participants {\n\t\tmsg.Participants[i].MarshallNew()\n\t}\n\n}\n\n// part of ObjectPatchable interface\nfunc (msg *Message) JsonTags() (tags map[string]string) {\n\treturn jsonTags(msg)\n}\n\nfunc (msg *Message) SortSlices() {\n\tsort.Sort(ByFileName(msg.Attachments))\n\tsort.Sort(ByUUID(msg.UserIdentities))\n\tsort.Sort(ByAddress(msg.Participants))\n\tsort.Strings(msg.Tags)\n}\n\n// sort interfaces\ntype ByDateSortAsc Messages\n\nfunc (ds ByDateSortAsc) Len() int {\n\treturn len(ds)\n}\n\nfunc (ds ByDateSortAsc) Less(i, j int) bool {\n\treturn ds[i].Date_sort.After(ds[j].Date_sort)\n}\n\nfunc (ds ByDateSortAsc) Swap(i, j int) {\n\tds[i], ds[j] = ds[j], ds[i]\n}\n\n// EmbedParticipantsDetails update messages slice in place\n// with data from Contact table related to participants\nfunc MessagesParticipantsDetails(session *gocql.Session, messages []Message) error {\n\tif len(messages) > 0 {\n\t\t// build a collection of unique participants from the messages list\n\t\tuniqueParticipants := map[string]Participant{}\n\t\tparticipants := []Participant{}\n\t\tfor _, msg := range messages {\n\t\t\tparticipants = append(participants, msg.Participants...)\n\t\t}\n\t\tfor _, p := range participants {\n\t\t\turi := strings.ToLower(p.Protocol + \":\" + p.Address)\n\t\t\tuniqueParticipants[uri] = p\n\t\t}\n\n\t\t// get related data from Contact if exist\n\t\tContactsForParticipants(session, messages[0].User_id.String(), uniqueParticipants)\n\n\t\t// update messages with participants' data\n\t\tfor i, msg := range messages {\n\t\t\tfor j, p := range msg.Participants {\n\t\t\t\turi := strings.ToLower(p.Protocol + \":\" + p.Address)\n\t\t\t\tmsg.Participants[j].Label = uniqueParticipants[uri].Label\n\t\t\t\tmsg.Participants[j].Contact_ids = uniqueParticipants[uri].Contact_ids\n\t\t\t}\n\t\t\tmessages[i] = msg\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc DiscussionsParticipantsDetails(session *gocql.Session, discussions []Discussion) error {\n\tif len(discussions) > 0 {\n\t\tparticipants := []Participant{}\n\t\tuniqueParticipants := map[string]Participant{}\n\t\tfor _, d := range discussions {\n\t\t\tparticipants = append(participants, d.Participants...)\n\t\t}\n\t\tfor _, p := range participants {\n\t\t\turi := strings.ToLower(p.Protocol + \":\" + p.Address)\n\t\t\tuniqueParticipants[uri] = p\n\t\t}\n\t\tContactsForParticipants(session, discussions[0].UserId.String(), uniqueParticipants)\n\t\tfor i, d := range discussions {\n\t\t\tfor j, p := range d.Participants {\n\t\t\t\turi := strings.ToLower(p.Protocol + \":\" + p.Address)\n\t\t\t\td.Participants[j] = uniqueParticipants[uri]\n\t\t\t}\n\t\t\tdiscussions[i] = d\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/nats.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage objects\n\n// DeliveryAck holds reply from nats when using request/reply system for messages\ntype DeliveryAck struct {\n\tErr      bool   `json:\"error\"`\n\tResponse string `json:\"message,omitempty\"`\n}\n\n// a lighter struct to reply to a request\ntype Ack struct {\n\tError    error  `json:\"error\"`\n\tOk       bool   `json:\"ok\"`\n\tResponse string `json:\"message,omitempty\"`\n}\n\n// model for job request sent by workers to idpoller\ntype WorkerRequest struct {\n\tWorker string      `json:\"worker\"`\n\tOrder  BrokerOrder `json:\"order\"`\n}\n\n// model to send messages to idpoller\ntype RemoteIDNatsMessage struct {\n\tIdentityId string `json:\"identity_id\"`\n\tOrder      string `json:\"order\"`\n\tOrderParam string `json:\"order_param\"`\n\tProtocol   string `json:\"protocol\"`\n\tUserId     string `json:\"user_id\"`\n}\n\n// model for orders sent to workers\ntype BrokerOrder struct {\n\tMessageId  string `json:\"message_id\"`\n\tOrder      string `json:\"order\"`\n\tIdentityId string `json:\"identity_id\"`\n\tUserId     string `json:\"user_id\"`\n}\n\n// IMAPorder is a BrokerOrder variant for imap\ntype IMAPorder struct {\n\tOrder      string `json:\"order\"`\n\tIdentityId string `json:\"identity_id\"`\n\tUserId     string `json:\"user_id\"`\n\t// optional fields sent by imapctl\n\tLogin    string `json:\"login\"`\n\tMailbox  string `json:\"mailbox\"`\n\tPassword string `json:\"password\"`\n\tServer   string `json:\"server\"`\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/notification.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage objects\n\nimport (\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"github.com/tidwall/gjson\"\n)\n\ntype Notification struct {\n\tBody            string      // could be a simple word or a more complex structure like a json, depending of the notification.\n\tEmitter         string      // backend entity that's emitting the message\n\tNotifId         UUID        // uuid v1, including a timestamp\n\tInternalPayload interface{} // placeholder to put objects needed to build/fulfil notification. Will not be emitted and/or saved.\n\tReference       string      // (optional) a reference number previously sent by frontend to link current notification to a previous action/event\n\tType            string      // a single word to describe message's type and give indication of importance level (event, info, feedback, warning, teaser, error, alert, etc.)\n\tTTLcode         string      // chars to pickup default duration into notification_ttl table.\n\tUser            *User       // only userId will be exported\n\tChildren        []Notification\n\tChildrenCount   int // in case they are too many children, children will be empty but not ChildrenCount\n}\n\n// model to queue a notification in cassandra or marshal one to json\ntype NotificationModel struct {\n\t// PRIMARY KEYS (user_id, notif_id)\n\tBody      string              `cql:\"body\"         json:\"body,omitempty\"    formatter:\"raw\"` // our bespoke jsonMarshaler will not escape this string, thus it could embed json object\n\tEmitter   string              `cql:\"emitter\"      json:\"emitter,omitempty\"`\n\tNotifId   string              `cql:\"notif_id\"     json:\"notif_id,omitempty\"`\n\tReference string              `cql:\"reference\"    json:\"reference,omitempty\"`\n\tType      string              `cql:\"type\"         json:\"type,omitempty\"`\n\tUserId    string              `cql:\"user_id\"      json:\"user_id\"          frontend:\"omit\"`\n\tChildren  []NotificationModel `cql:\"-\"            json:\"-\"`\n}\n\n// model to retrieve default durations for TTLcodes\ntype NotificationTTL struct {\n\t// PRIMARY KEY (ttl_code)\n\tTTLcode     string `cql:\"ttl_code\"      json:\"ttl_code\"`\n\tTTLduration int    `cql:\"ttl_duration\"  json:\"ttl_duration\"`\n\tDescription string `cql:\"description\"   json:\"description\"`\n}\n\nconst (\n\t// types list for Notification.Type property\n\tEventNotif      = \"event\"\n\tNewMessageNotif = \"new_message\"\n\tInfoNotif       = \"info\"\n\tFeedbackNotif   = \"feedback\"\n\tWarningNotif    = \"warning\"\n\tTeaserNotif     = \"teaser\"\n\tErrorNotif      = \"error\"\n\tAlertNotif      = \"alert\"\n\tBatchNotif      = \"batch\"\n\n\t// TTL codes stored in notification_ttl table\n\tShortLived = \"short-lived\"\n\tMidLived   = \"mid-lived\"\n\tLongLived  = \"long-lived\"\n\tShortTerm  = \"short-term\"\n\tMidTerm    = \"mid-term\"\n\tLongTerm   = \"long-term\"\n\tForever    = \"forever\"\n)\n\n// return a JSON representation of Notification suitable for frontend client\nfunc (n *Notification) MarshalFrontEnd() ([]byte, error) {\n\tsibling := NotificationModel{\n\t\tBody:      n.Body,\n\t\tEmitter:   n.Emitter,\n\t\tNotifId:   n.NotifId.String(),\n\t\tReference: n.Reference,\n\t\tType:      n.Type,\n\t\tUserId:    n.User.UserId.String(),\n\t}\n\tif len(n.Children) > 0 {\n\t\tsibling.Children = make([]NotificationModel, len(n.Children))\n\t\tfor i, child := range n.Children {\n\t\t\tc := NotificationModel{\n\t\t\t\tBody:      child.Body,\n\t\t\t\tEmitter:   child.Emitter,\n\t\t\t\tNotifId:   child.NotifId.String(),\n\t\t\t\tReference: child.Reference,\n\t\t\t\tType:      child.Type,\n\t\t\t\tUserId:    child.User.UserId.String(),\n\t\t\t}\n\t\t\tsibling.Children[i] = c\n\t\t}\n\t}\n\treturn JSONMarshaller(\"frontend\", &sibling)\n}\n\n// UnmarshalCQLMap hydrates a Notification with data from a map[string]interface{}\n// typical usage is for unmarshaling response from Cassandra backend\nfunc (n *Notification) UnmarshalCQLMap(input map[string]interface{}) {\n\n\tif userId, ok := input[\"user_id\"].(gocql.UUID); ok {\n\t\tn.User = &User{}\n\t\tn.User.UserId.UnmarshalBinary(userId.Bytes())\n\t}\n\n\tif id, ok := input[\"notif_id\"].(gocql.UUID); ok {\n\t\tn.NotifId.UnmarshalBinary(id.Bytes())\n\t}\n\n\tif body, ok := input[\"body\"].([]byte); ok {\n\t\tn.Body = string(body)\n\t}\n\t// body should be a json document\n\t// it may embed children notifications that need to be extracted to the children property\n\tif gjson.Valid(n.Body) {\n\t\tif children := gjson.Get(n.Body, \"elements\"); children.IsArray() {\n\t\t\tn.Children = []Notification{}\n\t\t\tchildren.ForEach(func(key, value gjson.Result) bool {\n\t\t\t\tif value.IsObject() {\n\t\t\t\t\tvar child NotificationModel\n\t\t\t\t\tif err := gjson.Unmarshal([]byte(value.Raw), &child); err == nil {\n\t\t\t\t\t\tn.Children = append(n.Children, Notification{\n\t\t\t\t\t\t\tBody:      child.Body,\n\t\t\t\t\t\t\tEmitter:   child.Emitter,\n\t\t\t\t\t\t\tNotifId:   UUID(uuid.FromStringOrNil(child.NotifId)),\n\t\t\t\t\t\t\tReference: child.Reference,\n\t\t\t\t\t\t\tType:      child.Type,\n\t\t\t\t\t\t\tUser:      &User{UserId: UUID(uuid.FromStringOrNil(child.UserId))},\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t\tn.ChildrenCount = int(gjson.Get(n.Body, \"size\").Int())\n\t}\n\n\tif emitter, ok := input[\"emitter\"].(string); ok {\n\t\tn.Emitter = emitter\n\t}\n\n\tif reference, ok := input[\"reference\"].(string); ok {\n\t\tn.Reference = reference\n\t}\n\n\tif typ, ok := input[\"type\"].(string); ok {\n\t\tn.Type = typ\n\t}\n}\n\n// MarshalJSON implements json.Marshaler interface\n// using a bespoke implementation allows to embed notification's body as a json object instead of a string\nfunc (n *NotificationModel) MarshalJSON() ([]byte, error) {\n\treturn JSONMarshaller(\"frontend\", n)\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/notification_test.go",
    "content": "package objects\n\nimport (\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"github.com/tidwall/gjson\"\n\t\"testing\"\n)\n\nfunc TestNotification_UnmarshalCQLMap(t *testing.T) {\n\tinput := map[string]interface{}{\n\t\t\"user_id\":  gocql.UUID(uuid.FromStringOrNil(\"63ab7904-c416-4f1a-9652-3de82e4fd1f1\")),\n\t\t\"notif_id\": gocql.UUID(uuid.FromStringOrNil(\"63ab7904-c416-1f1a-9652-3de82e4fd1f1\")),\n\t\t\"body\": []byte(`{\"elements\": [\n\t\t{\"body\": {\"contact_id\": \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\", \"status\": \"imported\"}},\n\t\t{\"body\": {\"contact_id\": \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\", \"status\": \"error\", \"error_msg\": \"something went wrong\"}},\n\t\t{\"body\": {\"contact_id\": \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\", \"status\": \"ignored\"}}\n\t\t],\n\t\t\"size\": 33}`),\n\t\t\"emitter\": \"contacts\",\n\t\t\"type\":    \"import_result\",\n\t}\n\tnotif := new(Notification)\n\tnotif.UnmarshalCQLMap(input)\n\tif len(notif.Children) != 3 {\n\t\tt.Errorf(\"expected Notification with 3 children, got %d\", len(notif.Children))\n\t}\n\tif !gjson.Valid(notif.Children[0].Body) {\n\t\tt.Error(\"expected a valid json in Notification.Children[0].Body, got gjson.Valid == false\")\n\t}\n\tif gjson.Get(notif.Children[0].Body, \"contact_id\").Str != \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\" {\n\t\tt.Errorf(\"expected Notification.Children[0].Body.contact_id == \\\"63ab7904-c416-4f1a-9652-3de82e4fd1f1\\\", got %s\", gjson.Get(notif.Children[0].Body, \"body.contact_id\").Str)\n\t}\n\tif notif.Emitter != \"contacts\" {\n\t\tt.Errorf(\"expected Notifiaction.Emitter == \\\"contacts\\\", got %s\", notif.Emitter)\n\t}\n\tif notif.ChildrenCount != 33 {\n\t\tt.Errorf(\"expected Notification.ChildrenCount == 33, got %d\", notif.ChildrenCount)\n\t}\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/organization.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"github.com/satori/go.uuid\"\n)\n\n// contacts' organization model\ntype Organization struct {\n\tDeleted        bool   `cql:\"deleted\"            json:\"deleted\"              patch:\"system\"`\n\tDepartment     string `cql:\"department\"         json:\"department,omitempty\"           patch:\"user\"`\n\tIsPrimary      bool   `cql:\"is_primary\"         json:\"is_primary\"           patch:\"user\"`\n\tJobDescription string `cql:\"job_description\"    json:\"job_description,omitempty\"      patch:\"user\"`\n\tLabel          string `cql:\"label\"              json:\"label,omitempty\"                patch:\"user\"`\n\tName           string `cql:\"name\"               json:\"name,omitempty\"                 patch:\"user\"`\n\tOrganizationId UUID   `cql:\"organization_id\"    json:\"organization_id,omitempty\"      patch:\"system\"`\n\tTitle          string `cql:\"title\"              json:\"title,omitempty\"                patch:\"user\"`\n\tType           string `cql:\"type\"               json:\"type,omitempty\"                 patch:\"user\"`\n}\n\nfunc (o *Organization) UnmarshalMap(input map[string]interface{}) error {\n\tif deleted, ok := input[\"deleted\"].(bool); ok {\n\t\to.Deleted = deleted\n\t}\n\tif department, ok := input[\"department\"].(string); ok {\n\t\to.Department = department\n\t}\n\tif isPrimary, ok := input[\"is_primary\"].(bool); ok {\n\t\to.IsPrimary = isPrimary\n\t}\n\tif jobDescription, ok := input[\"job_description\"].(string); ok {\n\t\to.JobDescription = jobDescription\n\t}\n\tif label, ok := input[\"label\"].(string); ok {\n\t\to.Label = label\n\t}\n\tif name, ok := input[\"name\"].(string); ok {\n\t\to.Name = name\n\t}\n\tif o_id, ok := input[\"organization_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(o_id); err == nil {\n\t\t\to.OrganizationId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif title, ok := input[\"title\"].(string); ok {\n\t\to.Title = title\n\t}\n\tif t, ok := input[\"type\"].(string); ok {\n\t\to.Title = t\n\t}\n\treturn nil //TODO: errors handling\n}\n\n// MarshallNew must be a variadic func to implement NewMarshaller interface,\n// but Organization does not need params to marshal a well-formed Organization: ...interface{} is ignored\nfunc (o *Organization) MarshallNew(...interface{}) {\n\tif len(o.OrganizationId) == 0 || (bytes.Equal(o.OrganizationId.Bytes(), EmptyUUID.Bytes())) {\n\t\to.OrganizationId.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n}\n\n// Sort interface implementation\ntype ByOrganizationID []Organization\n\nfunc (p ByOrganizationID) Len() int {\n\treturn len(p)\n}\n\nfunc (p ByOrganizationID) Less(i, j int) bool {\n\treturn p[i].OrganizationId.String() < p[j].OrganizationId.String()\n}\n\nfunc (p ByOrganizationID) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/participant.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype (\n\tParticipant struct {\n\t\tAddress     string `cql:\"address\"          json:\"address,omitempty\"`\n\t\tContact_ids []UUID `cql:\"contact_ids\"      json:\"contact_ids,omitempty\"             formatter:\"rfc4122\"`\n\t\tLabel       string `cql:\"label\"            json:\"label,omitempty\"`\n\t\tProtocol    string `cql:\"protocol\"         json:\"protocol,omitempty\"`\n\t\tType        string `cql:\"type\"             json:\"type,omitempty\"`\n\t}\n\n\tHashLookup struct {\n\t\tUserId         UUID      `cql:\"user_id\"` // primary key\n\t\tUri            string    `cql:\"uri\"`     // primary key\n\t\tHash           string    `cql:\"hash\"`    // primary key\n\t\tDateInsert     time.Time `cql:\"date_insert\"`\n\t\tHashComponents []string  `cql:\"hash_components\"`\n\t}\n\n\tParticipantHash struct {\n\t\tUserId     UUID      `cql:\"user_id\"` // primary key\n\t\tKind       string    `cql:\"kind\"`    // primary key\n\t\tKey        string    `cql:\"key\"`     // primary key\n\t\tValue      string    `cql:\"value\"`   // primary key\n\t\tComponents []string  `cql:\"components\"`\n\t\tDateInsert time.Time `cql:\"date_insert\"`\n\t}\n)\n\nfunc (p *Participant) UnmarshalMap(input map[string]interface{}) error {\n\tif address, ok := input[\"address\"].(string); ok {\n\t\tp.Address = address\n\t}\n\tif label, ok := input[\"label\"].(string); ok {\n\t\tp.Label = label\n\t}\n\tif protocol, ok := input[\"protocol\"].(string); ok {\n\t\tp.Protocol = protocol\n\t}\n\tif t, ok := input[\"type\"].(string); ok {\n\t\tp.Type = t\n\t}\n\tif contact_ids, ok := input[\"contact_ids\"]; ok && contact_ids != nil {\n\t\tp.Contact_ids = []UUID{}\n\t\tfor _, contact_id := range contact_ids.([]interface{}) {\n\t\t\tc_id := contact_id.(string)\n\t\t\tvar contact_uuid UUID\n\t\t\tif id, err := uuid.FromString(c_id); err == nil {\n\t\t\t\tcontact_uuid.UnmarshalBinary(id.Bytes())\n\t\t\t}\n\t\t\tp.Contact_ids = append(p.Contact_ids, contact_uuid)\n\t\t}\n\t}\n\treturn nil //TODO: errors handling\n}\n\nfunc (pl *HashLookup) UnmarshalCQLMap(input map[string]interface{}) error {\n\tif user_id, ok := input[\"user_id\"].(gocql.UUID); ok {\n\t\tpl.UserId.UnmarshalBinary(user_id.Bytes())\n\t}\n\tif uri, ok := input[\"uri\"].(string); ok {\n\t\tpl.Uri = uri\n\t}\n\tif hash, ok := input[\"hash\"].(string); ok {\n\t\tpl.Hash = hash\n\t}\n\tif dateInsert, ok := input[\"date_insert\"].(time.Time); ok {\n\t\tpl.DateInsert = dateInsert\n\t}\n\tif components, ok := input[\"hash_components\"].([]string); ok {\n\t\tpl.HashComponents = components\n\t}\n\treturn nil\n}\n\n// part of CaliopenObject interface\nfunc (p *Participant) MarshallNew(...interface{}) {\n\t// nothing to enforce\n}\n\nfunc (pl *HashLookup) MarshallNew(args ...interface{}) {\n\tif len(pl.UserId) == 0 || (bytes.Equal(pl.UserId.Bytes(), EmptyUUID.Bytes())) {\n\t\tif len(args) == 1 {\n\t\t\tswitch args[0].(type) {\n\t\t\tcase UUID:\n\t\t\t\tpl.UserId = args[0].(UUID)\n\t\t\t}\n\t\t}\n\t}\n\tpl.HashComponents = []string{}\n}\n\nfunc (hl *ParticipantHash) UnmarshalCQLMap(input map[string]interface{}) error {\n\tif user_id, ok := input[\"user_id\"].(gocql.UUID); ok {\n\t\thl.UserId.UnmarshalBinary(user_id.Bytes())\n\t}\n\tif kind, ok := input[\"kind\"].(string); ok {\n\t\thl.Kind = kind\n\t}\n\tif key, ok := input[\"key\"].(string); ok {\n\t\thl.Key = key\n\t}\n\tif value, ok := input[\"value\"].(string); ok {\n\t\thl.Value = value\n\t}\n\tif components, ok := input[\"components\"].([]string); ok {\n\t\thl.Components = components\n\t}\n\tif dateInsert, ok := input[\"date_insert\"].(time.Time); ok {\n\t\thl.DateInsert = dateInsert\n\t}\n\treturn nil\n}\n\n// Sort interface implementation\ntype ByAddress []Participant\n\nfunc (p ByAddress) Len() int {\n\treturn len(p)\n}\n\nfunc (p ByAddress) Less(i, j int) bool {\n\treturn p[i].Address < p[j].Address\n}\n\nfunc (p ByAddress) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n\n// HashFromParticipatnsUris creates a hash from a collection of Participant\nfunc HashFromParticipantsUris(participants []Participant) (hash string, components []string, err error) {\n\turisMap := map[string]struct{}{}\n\tfor _, participant := range participants {\n\t\turi := participant.Protocol + \":\" + strings.ToLower(participant.Address)\n\t\turisMap[uri] = struct{}{}\n\t}\n\tcomponents = []string{}\n\tfor k, _ := range urisMap {\n\t\tcomponents = append(components, k)\n\t}\n\thash = HashComponents(components)\n\treturn\n}\n\nfunc HashComponents(c []string) string {\n\tsort.Strings(c)\n\tsum := sha256.Sum256([]byte(strings.Join(c, \"\")))\n\treturn fmt.Sprintf(\"%x\", sum)\n}\n\n// StoreURIsParticipantsBijection stores uris_hash <-> participants_hash bijection\n// in participant_hash table\nfunc StoreURIsParticipantsBijection(session *gocql.Session, userId, uriHash, participantHash string, uriComponents, participantComponents []string) error {\n\tnow := time.Now()\n\t// store uris_hash -> participants_hash\n\te1 := session.Query(`INSERT INTO participant_hash (user_id, kind, key, value, components, date_insert) VALUES (?,?,?,?,?,?)`,\n\t\tuserId, UrisKind, uriHash, participantHash, uriComponents, now).Exec()\n\n\t// store participants_hash -> uris_hash\n\te2 := session.Query(`INSERT INTO participant_hash (user_id, kind, key, value, components, date_insert) VALUES (?,?,?,?,?,?)`,\n\t\tuserId, ParticipantsKind, participantHash, uriHash, participantComponents, now).Exec()\n\tswitch {\n\tcase e1 != nil:\n\t\treturn e1\n\tcase e2 != nil:\n\t\treturn e2\n\t}\n\treturn nil\n}\n\n// RemoveURIsParticipantsBijection delete uris_hash <-> participants_hash bijection\n// in participant_hash table\nfunc RemoveURIsParticipantsBijection(session *gocql.Session, former ParticipantHash) error {\n\tvar first, second string\n\tfirst = former.Kind\n\tif first == ParticipantsKind {\n\t\tsecond = UrisKind\n\t} else {\n\t\tsecond = ParticipantsKind\n\t}\n\te1 := session.Query(`DELETE FROM participant_hash WHERE user_id = ? AND kind = ? AND key = ? AND value = ?`,\n\t\tformer.UserId,\n\t\tfirst,\n\t\tformer.Key,\n\t\tformer.Value).Exec()\n\te2 := session.Query(`DELETE FROM participant_hash WHERE user_id = ? AND kind = ? AND key = ? AND value = ?`,\n\t\tformer.UserId,\n\t\tsecond,\n\t\tformer.Value,\n\t\tformer.Key).Exec()\n\tswitch {\n\tcase e1 != nil:\n\t\treturn e1\n\tcase e2 != nil:\n\t\treturn e2\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/participants_test.go",
    "content": "package objects\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\n/* deprecated\nfunc TestComputeNewParticipantHash(t *testing.T) {\n\tcurrent := ParticipantHash{\n\t\tUserId:     UUID(uuid.FromStringOrNil(\"ede04443-b60f-4869-9040-20bd6b1e33c1\")),\n\t\tKind:       \"participants\",\n\t\tKey:        \"f639c352610d899ed1d564fe9e133c4a4ab269f45b6e27507144bb1a196d90ec\",\n\t\tComponents: []string{\"email:dev@caliopen.local\", \"email:eliz@gnu.org\", \"email:kaushal.modi@gmail.com\"},\n\t}\n\turi := \"email:eliz@gnu.org\"\n\tcontactId := \"5f0baee8-1278-43eb-9931-01b7383b419b\"\n\tnewH, err := ComputeNewParticipantHash(uri, contactId, current, current.Components)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif newH.Key != \"a68a2042560695580765331e4fd528a611d27f5852b6d0dd499d8eccfc28f54b\" {\n\t\tt.Errorf(\"expected new.Key = a68a2042560695580765331e4fd528a611d27f5852b6d0dd499d8eccfc28f54b, got %s\", newH.Key)\n\t}\n}\n*/\nfunc TestHashFromParticipantsUris(t *testing.T) {\n\tparticipants := []Participant{\n\t\t{\n\t\t\tAddress:  \"kaushal.modi@gmail.com\",\n\t\t\tProtocol: \"email\",\n\t\t\tType:     \"From\",\n\t\t},\n\t\t{\n\t\t\tAddress:  \"dev@caliopen.local\",\n\t\t\tProtocol: \"email\",\n\t\t\tType:     \"To\",\n\t\t},\n\t\t{\n\t\t\tAddress:  \"eliz@gnu.org\",\n\t\t\tProtocol: \"email\",\n\t\t\tType:     \"Cc\",\n\t\t},\n\t\t{\n\t\t\tAddress:  \"dev@caliopen.local\",\n\t\t\tProtocol: \"email\",\n\t\t\tType:     \"Cc\",\n\t\t},\n\t}\n\thash, components, err := HashFromParticipantsUris(participants)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif hash != \"f639c352610d899ed1d564fe9e133c4a4ab269f45b6e27507144bb1a196d90ec\" {\n\t\tt.Errorf(\"expected hash f639c352610d899ed1d564fe9e133c4a4ab269f45b6e27507144bb1a196d90ec, got %s\", hash)\n\t}\n\tif !strings.EqualFold(\"email:dev@caliopen.localemail:eliz@gnu.orgemail:kaushal.modi@gmail.com\", strings.Join(components, \"\")) {\n\t\tt.Errorf(\"expected components = email:dev@caliopen.localemail:eliz@gnu.orgemail:kaushal.modi@gmail.com, got %s\", strings.Join(components, \"\"))\n\t}\n\t/*\n\n\n\t\thash = f639c352610d899ed1d564fe9e133c4a4ab269f45b6e27507144bb1a196d90ec\n\t*/\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/phone.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"github.com/satori/go.uuid\"\n)\n\n// contacts' phone model\ntype Phone struct {\n\tIsPrimary        bool   `cql:\"is_primary\"           json:\"is_primary,omitempty\"                                       patch:\"user\"`\n\tNormalizedNumber string `cql:\"normalized_number\"    json:\"normalized_number,omitempty\"                                patch:\"user\"`\n\tNumber           string `cql:\"number\"               json:\"number,omitempty\"           cql_lookup:\"contact_lookup\"     patch:\"user\"`\n\tPhoneId          UUID   `cql:\"phone_id\"             json:\"phone_id,omitempty\"                                         patch:\"system\"`\n\tType             string `cql:\"type\"                 json:\"type,omitempty\"                                             patch:\"user\"`\n\tUri              string `cql:\"uri\"                  json:\"uri,omitempty\"                                              patch:\"system\"` //RFC3966\n}\n\nfunc (p *Phone) UnmarshalMap(input map[string]interface{}) error {\n\tif isPrimary, ok := input[\"is_primary\"].(bool); ok {\n\t\tp.IsPrimary = isPrimary\n\t}\n\tif number, ok := input[\"number\"].(string); ok {\n\t\tp.Number = number\n\t}\n\tif number, ok := input[\"normalized_number\"].(string); ok {\n\t\tp.NormalizedNumber = number\n\t}\n\tif p_id, ok := input[\"phone_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(p_id); err == nil {\n\t\t\tp.PhoneId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif t, ok := input[\"type\"].(string); ok {\n\t\tp.Type = t\n\t}\n\tif uri, ok := input[\"uri\"].(string); ok {\n\t\tp.Uri = uri\n\t}\n\treturn nil //TODO : errors handling\n}\n\n// MarshallNew must be a variadic func to implement NewMarshaller interface,\n// but Phone does not need params to marshal a well-formed Phone: ...interface{} is ignored\nfunc (p *Phone) MarshallNew(...interface{}) {\n\tif len(p.PhoneId) == 0 || (bytes.Equal(p.PhoneId.Bytes(), EmptyUUID.Bytes())) {\n\t\tp.PhoneId.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n}\n\nfunc (p *Phone) JsonTags() map[string]string {\n\treturn jsonTags(p)\n}\n\nfunc (p *Phone) NewEmpty() interface{} {\n\treturn new(Phone)\n}\n\nfunc (p *Phone) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn p.UnmarshalMap(input)\n}\n\n// Sort interface implementation\ntype ByPhoneID []Phone\n\nfunc (p ByPhoneID) Len() int {\n\treturn len(p)\n}\n\nfunc (p ByPhoneID) Less(i, j int) bool {\n\treturn p[i].PhoneId.String() < p[j].PhoneId.String()\n}\n\nfunc (p ByPhoneID) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/postal_address.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"github.com/satori/go.uuid\"\n)\n\ntype PostalAddress struct {\n\tAddressId  UUID   `cql:\"address_id\"     json:\"address_id,omitempty\"       patch:\"system\"`\n\tCity       string `cql:\"city\"           json:\"city,omitempty\"             patch:\"user\"`\n\tCountry    string `cql:\"country\"        json:\"country,omitempty\"          patch:\"user\"`\n\tIsPrimary  bool   `cql:\"is_primary\"     json:\"is_primary\"       patch:\"user\"`\n\tLabel      string `cql:\"label\"          json:\"label,omitempty\"            patch:\"user\"`\n\tPostalCode string `cql:\"postal_code\"    json:\"postal_code,omitempty\"      patch:\"user\"`\n\tRegion     string `cql:\"region\"         json:\"region,omitempty\"           patch:\"user\"`\n\tStreet     string `cql:\"street\"         json:\"street,omitempty\"           patch:\"user\"`\n\tType       string `cql:\"type\"           json:\"type,omitempty\"             patch:\"user\"`\n}\n\nfunc (pa *PostalAddress) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn pa.UnmarshalMap(input)\n}\n\nfunc (pa *PostalAddress) UnmarshalMap(input map[string]interface{}) error {\n\tif add_id, ok := input[\"address_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(add_id); err == nil {\n\t\t\tpa.AddressId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif city, ok := input[\"city\"].(string); ok {\n\t\tpa.City = city\n\t}\n\tif country, ok := input[\"country\"].(string); ok {\n\t\tpa.Country = country\n\t}\n\tif isPrimary, ok := input[\"is_primary\"].(bool); ok {\n\t\tpa.IsPrimary = isPrimary\n\t}\n\tif label, ok := input[\"label\"].(string); ok {\n\t\tpa.Label = label\n\t}\n\tif postalCode, ok := input[\"postal_code\"].(string); ok {\n\t\tpa.PostalCode = postalCode\n\t}\n\tif region, ok := input[\"region\"].(string); ok {\n\t\tpa.Region = region\n\t}\n\tif street, ok := input[\"street\"].(string); ok {\n\t\tpa.Street = street\n\t}\n\tif t, ok := input[\"type\"].(string); ok {\n\t\tpa.Type = t\n\t}\n\n\treturn nil //TODO: errors handling\n}\n\n// MarshallNew must be a variadic func to implement NewMarshaller interface,\n// but PostalAddress does not need params to marshal a well-formed PostalAddress: ...interface{} is ignored\nfunc (pa *PostalAddress) MarshallNew(...interface{}) {\n\tif len(pa.AddressId) == 0 || (bytes.Equal(pa.AddressId.Bytes(), EmptyUUID.Bytes())) {\n\t\tpa.AddressId.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n}\n\n// Sort interface implementation\ntype ByPostalAddressID []PostalAddress\n\nfunc (p ByPostalAddressID) Len() int {\n\treturn len(p)\n}\n\nfunc (p ByPostalAddressID) Less(i, j int) bool {\n\treturn p[i].AddressId.String() < p[j].AddressId.String()\n}\n\nfunc (p ByPostalAddressID) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/privacy_features.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\ntype PrivacyFeatures map[string]string\n\nfunc (pf *PrivacyFeatures) UnmarshalMap(input map[string]interface{}) {\n\tfor k, v := range input {\n\t\t(*pf)[k] = v.(string)\n\t}\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/privacy_index.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"time\"\n)\n\ntype PrivacyIndex struct {\n\tComportment int       `cql:\"comportment\"    json:\"comportment\"`\n\tContext     int       `cql:\"context\"        json:\"context\"`\n\tDateUpdate  time.Time `cql:\"date_update\"    json:\"date_update,omitempty\"          formatter:\"RFC3339Milli\"`\n\tTechnic     int       `cql:\"technic\"        json:\"technic\"`\n\tVersion     int       `cql:\"version\"        json:\"version\"`\n}\n\nfunc (pi *PrivacyIndex) UnmarshalMap(input map[string]interface{}) error {\n\n\tif pi_comp, ok := input[\"comportment\"].(float64); ok {\n\t\tpi.Comportment = int(pi_comp)\n\t}\n\n\tif pi_ctx, ok := input[\"context\"].(float64); ok {\n\t\tpi.Context = int(pi_ctx)\n\t}\n\n\tif date, ok := input[\"date_update\"]; ok {\n\t\tpi.DateUpdate, _ = time.Parse(time.RFC3339Nano, date.(string))\n\t}\n\n\tif pi_tech, ok := input[\"technic\"].(float64); ok {\n\t\tpi.Technic = int(pi_tech)\n\t}\n\n\tif pi_ver, ok := input[\"version\"].(float64); ok {\n\t\tpi.Version = int(pi_ver)\n\t}\n\treturn nil //TODO: errors handling\n}\n\n// bespoke implementation of the json.Marshaller interface\n// outputs a JSON representation of an object\n// this marshaller takes account of custom tags for given 'context'\nfunc (pi *PrivacyIndex) JSONMarshaller() ([]byte, error) {\n\treturn JSONMarshaller(\"\", pi)\n}\n\nfunc (pi *PrivacyIndex) IsEmpty() bool {\n\treturn pi.DateUpdate.IsZero() &&\n\t\tpi.Comportment == 0 &&\n\t\tpi.Context == 0 &&\n\t\tpi.Technic == 0 &&\n\t\tpi.Version == 0\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/provider.go",
    "content": "package objects\n\nimport (\n\t\"github.com/emersion/go-imap\"\n\t\"time\"\n)\n\ntype Provider struct {\n\tDateInsert       time.Time         `json:\"date_insert\"                      cql:\"date_insert\"`\n\tInstance         string            `json:\"instance\"                         cql:\"instance\"`\n\tInfos            map[string]string `json:\"-\"                                cql:\"infos\"         mapstructure:\"infos\"                frontend:\"omit\"`\n\tName             string            `json:\"name\"                             cql:\"name\"          mapstructure:\"name\"`\n\tOauthCallbackUri string            `json:\"oauth_callback_uri,omitempty\"     cql:\"-\"             mapstructure:\"oauth_callback_uri\"`\n\tOauthRequestUrl  string            `json:\"oauth_request_url,omitempty\"      cql:\"-\"             mapstructure:\"oauth_request_url\"`\n\tProtocol         string            `json:\"protocol\"                         cql:\"-\"             mapstructure:\"protocol\"`\n\tCapabilities     map[string]bool   `json:\"omitempty\"                        cql:\"-\"` // capabilities sent back by provider at connection time\n\tFetchItems       []imap.FetchItem  `json:\"omitempty\"                        cql:\"-\"` // provider specific items that we want to fetch\n}\n\ntype OauthSession struct {\n\tRequestSecret string\n\tRequestToken  string\n\tUserId        string\n\tParams        map[string]string\n}\n\n// return a JSON representation of Provider suitable for frontend client\n// for now, Infos is not returned. If client need it in future, we shall cleanup Infos for sensitive data.\nfunc (p *Provider) MarshalFrontEnd() ([]byte, error) {\n\treturn JSONMarshaller(\"frontend\", p)\n}\n\nfunc (p *Provider) UnmarshalCQLmap(input map[string]interface{}) error {\n\tif name, ok := input[\"name\"]; ok {\n\t\tp.Name = name.(string)\n\t}\n\tif instance, ok := input[\"instance\"]; ok {\n\t\tp.Instance = instance.(string)\n\t}\n\tif infos, ok := input[\"infos\"].(map[string]string); ok {\n\t\tp.Infos = make(map[string]string)\n\t\tfor k, v := range infos {\n\t\t\tp.Infos[k] = v\n\t\t}\n\t}\n\tif dateInsert, ok := input[\"date_insert\"].(time.Time); ok {\n\t\tp.DateInsert = dateInsert\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/raw_message.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport \"github.com/gocql/gocql\"\n\ntype RawMessage struct {\n\t//Json_rep   string `cql:\"json_rep\"          json:\"json_rep\"` //json representation of the raw message with its envelope\n\tDelivered  bool   `cql:\"delivered\"         json:\"delivered\"`\n\tRaw_msg_id UUID   `cql:\"raw_msg_id\"        json:\"raw_msg_id\"`\n\tRaw_data   string `cql:\"raw_data\"          json:\"raw_data\"` //could be empty if raw message is too large to be stored in db\n\tRaw_Size   uint64 `cql:\"raw_size\"          json:\"raw_size\"`\n\tURI        string `cql:\"uri\"               json:\"uri\"` //object's location if message is too large to be stored in db\n}\n\n// unmarshal a map[string]interface{} that must owns all Message fields\nfunc (msg *RawMessage) UnmarshalCQLMap(input map[string]interface{}) {\n\tif raw_msg_id, ok := input[\"raw_msg_id\"].(gocql.UUID); ok {\n\t\tmsg.Raw_msg_id.UnmarshalBinary(raw_msg_id.Bytes())\n\t}\n\tif raw_data, ok := input[\"raw_data\"].([]byte); ok {\n\t\tmsg.Raw_data = string(raw_data)\n\t}\n\tif size, ok := input[\"raw_size\"].(int); ok {\n\t\tmsg.Raw_Size = uint64(size)\n\t}\n\tif uri, ok := input[\"uri\"].(string); ok {\n\t\tmsg.URI = uri\n\t}\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/search.go",
    "content": "package objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"gopkg.in/olivere/elastic.v5\"\n)\n\n// params to pass to API to trigger an elasticsearch filtering search\ntype IndexSearch struct {\n\tLimit    int                 `json:\"limit\"`\n\tOffset   int                 `json:\"offset\"`\n\tTerms    map[string][]string `json:\"terms\"`\n\tShard_id string              `json:\"shard_id\"`\n\tUser_id  UUID                `json:\"user_id\"`\n\tDocType  string              `json:\"doc_type\"`\n\tILrange  [2]int8             `json:\"il_range\"`\n\tPIrange  [2]int8             `json:\"pi_range\"`\n}\n\ntype IndexResult struct {\n\tTotal        int64       `json:\"total\"`\n\tMessagesHits MessageHits `json:\"messages_hits\"`\n\tContactsHits ContactHits `json:\"contact_hits\"`\n}\n\ntype MessageHits struct {\n\tTotal    int64       `json:\"total\"`\n\tMessages []*IndexHit `json:\"messages\"`\n}\n\ntype ContactHits struct {\n\tTotal    int64       `json:\"total\"`\n\tContacts []*IndexHit `json:\"contacts\"`\n}\n\ntype IndexHit struct {\n\tId         UUID                `json:\"id\"`\n\tScore      float64             `json:\"score\"`\n\tHighlights map[string][]string `json:\"highlights\"`\n\tDocument   interface{}         `json:\"document\"`\n}\n\nfunc (is *IndexSearch) FilterQuery(service *elastic.SearchService, withIL bool) *elastic.SearchService {\n\tq := elastic.NewBoolQuery()\n\t// Strictly filter on user_id\n\tq = q.Filter(elastic.NewTermQuery(\"user_id\", is.User_id))\n\n\t// if discussion_id in terms, add it to the query as a `terms` query (plural)\n\t// because it could have multiple values to lookup\n\tif discussion_id, ok := is.Terms[\"discussion_id\"]; ok {\n\t\tvalues := []interface{}{}\n\t\tfor _, value := range discussion_id {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t\tq = q.Filter(elastic.NewTermsQuery(\"discussion_id\", values...))\n\t\tdelete(is.Terms, \"discussion_id\")\n\t}\n\n\tfor name, values := range is.Terms {\n\t\tfor _, value := range values {\n\t\t\tq = q.Filter(elastic.NewTermQuery(name, value))\n\t\t}\n\t}\n\n\tif withIL {\n\t\trq := elastic.NewRangeQuery(\"importance_level\").Gte(is.ILrange[0]).Lte(is.ILrange[1])\n\t\tq = q.Filter(rq)\n\t}\n\n\tservice = service.Query(q)\n\treturn service\n}\n\nfunc (is *IndexSearch) MatchQuery(service *elastic.SearchService) *elastic.SearchService {\n\n\tif len(is.Terms) == 0 {\n\t\treturn service\n\t}\n\t//TODO\n\n\treturn service\n}\n\nfunc (ir *IndexResult) MarshalFrontEnd() ([]byte, error) {\n\treturn ir.JSONMarshaller(\"frontend\")\n}\n\n// bespoke implementation of the json.Marshaller interface\n// it makes use of our custom JSONMarshaller when available\nfunc (ir *IndexResult) JSONMarshaller(context string) ([]byte, error) {\n\tvar jsonBuf bytes.Buffer\n\tenc := json.NewEncoder(&jsonBuf)\n\n\tfields, err := reflections.Fields(*ir)\n\tif err != nil {\n\t\treturn jsonBuf.Bytes(), err\n\t}\n\tjsonBuf.WriteByte('{')\n\tfirst := true\nfieldsLoop:\n\tfor _, field := range fields {\n\t\tj_field, err := reflections.GetFieldTag(*ir, field, \"json\")\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"reflection for field %s failed\", field)\n\t\t} else {\n\t\t\tif j_field != \"\" && j_field != \"-\" {\n\t\t\t\tif first {\n\t\t\t\t\tfirst = false\n\t\t\t\t} else {\n\t\t\t\t\tjsonBuf.WriteByte(',')\n\t\t\t\t}\n\t\t\t\tjsonBuf.WriteString(\"\\\"\" + j_field + \"\\\":\")\n\t\t\t\t// marshal messages hits apart to use custom JSONMarshaller for Messages objects\n\t\t\t\tif field == \"MessagesHits\" {\n\t\t\t\t\tsub_fields, err := reflections.Fields((*ir).MessagesHits)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tjsonBuf.WriteByte('{')\n\t\t\t\t\t\tfirst := true\n\t\t\t\t\t\tfor _, sub_field := range sub_fields {\n\t\t\t\t\t\t\tj_field, err := reflections.GetFieldTag((*ir).MessagesHits, sub_field, \"json\")\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tlog.WithError(err).Warnf(\"reflection for field %s failed\", sub_field)\n\t\t\t\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif j_field != \"\" && j_field != \"-\" {\n\t\t\t\t\t\t\t\t\tif first {\n\t\t\t\t\t\t\t\t\t\tfirst = false\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tjsonBuf.WriteByte(',')\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tjsonBuf.WriteString(\"\\\"\" + j_field + \"\\\":\")\n\t\t\t\t\t\t\t\t\tif sub_field == \"Messages\" {\n\t\t\t\t\t\t\t\t\t\t// iterate over messages to use custom Message's JSONMarshaller\n\t\t\t\t\t\t\t\t\t\tjsonBuf.WriteByte('[')\n\t\t\t\t\t\t\t\t\t\tfirst := true\n\t\t\t\t\t\t\t\t\t\tfor _, msg_hit := range (*ir).MessagesHits.Messages {\n\t\t\t\t\t\t\t\t\t\t\tif first {\n\t\t\t\t\t\t\t\t\t\t\t\tfirst = false\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tjsonBuf.WriteByte(',')\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\thit_fields, err := reflections.Fields(*msg_hit)\n\t\t\t\t\t\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\t\t\t\t\t\tjsonBuf.WriteByte('{')\n\t\t\t\t\t\t\t\t\t\t\t\tfirst := true\n\t\t\t\t\t\t\t\t\t\t\t\tfor _, hit_field := range hit_fields {\n\t\t\t\t\t\t\t\t\t\t\t\t\tj_field, err := reflections.GetFieldTag(*msg_hit, hit_field, \"json\")\n\t\t\t\t\t\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlog.WithError(err).Warnf(\"reflection for field %s failed\", hit_field)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif j_field != \"\" && j_field != \"-\" {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif first {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfirst = false\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tjsonBuf.WriteByte(',')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tjsonBuf.WriteString(\"\\\"\" + j_field + \"\\\":\")\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif hit_field == \"Document\" {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tout, err := (*msg_hit).Document.(*Message).JSONMarshaller(context)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tjsonBuf.Write(out)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfield_value, err := reflections.GetField(*msg_hit, hit_field)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tenc.Encode(field_value)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tjsonBuf.Write([]byte{'\"', '\"'})\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tjsonBuf.WriteByte('}')\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tjsonBuf.WriteByte(']')\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tfield_value, err := reflections.GetField((*ir).MessagesHits, sub_field)\n\t\t\t\t\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\t\t\t\t\tenc.Encode(field_value)\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tjsonBuf.Write([]byte{'\"', '\"'})\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tjsonBuf.WriteByte('}')\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfield_value, err := reflections.GetField(*ir, field)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tenc.Encode(field_value)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tjsonBuf.Write([]byte{'\"', '\"'})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.Warnf(\"Invalid field %s value: %s\", field, j_field)\n\t\t\t}\n\t\t}\n\t}\n\tjsonBuf.WriteByte('}')\n\treturn jsonBuf.Bytes(), nil\n\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/settings.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n)\n\n// user settings\ntype Settings struct {\n\tContactDisplayFormat       string `cql:\"contact_display_format\"\tjson:\"contact_display_format\"`\n\tContactDisplayOrder        string `cql:\"contact_display_order\"\tjson:\"contact_display_order\"`\n\tDefaultLocale              string `cql:\"default_locale\"      json:\"default_locale\"`\n\tMessageDisplayFormat       string `cql:\"message_display_format\"\tjson:\"message_display_format\"`\n\tNotificationDelayDisappear int    `cql:\"notification_delay_disappear\"\tjson:\"notification_delay_disappear\"`\n\tNotificationEnabled        bool   `cql:\"notification_enabled\"\tjson:\"notification_enabled\"`\n\tNotificationMessagePreview string `cql:\"notification_message_preview\"\tjson:\"notification_message_preview\"`\n\tNotificationSoundEnabled   bool   `cql:\"notification_sound_enabled\"\tjson:\"notification_sound_enabled\"`\n\tUserId                     UUID   `cql:\"user_id\"            json:\"user_id\"`\n}\n\n// unmarshal a map[string]interface{} that must owns all Settings's fields\n// typical usage is for unmarshaling response from Cassandra backend\nfunc (s *Settings) UnmarshalCQLMap(input map[string]interface{}) {\n\ts.ContactDisplayOrder = input[\"contact_display_order\"].(string)\n\ts.ContactDisplayFormat = input[\"contact_display_format\"].(string)\n\ts.DefaultLocale = input[\"default_locale\"].(string)\n\ts.MessageDisplayFormat = input[\"message_display_format\"].(string)\n\ts.NotificationDelayDisappear = input[\"notification_delay_disappear\"].(int)\n\ts.NotificationEnabled = input[\"notification_enabled\"].(bool)\n\ts.NotificationSoundEnabled = input[\"notification_sound_enabled\"].(bool)\n\ts.NotificationMessagePreview = input[\"notification_message_preview\"].(string)\n\tuserid, _ := input[\"user_id\"].(gocql.UUID)\n\ts.UserId.UnmarshalBinary(userid.Bytes())\n}\n\nfunc (s *Settings) UnmarshalMap(input map[string]interface{}) error {\n\tif contactDisplayOrder, ok := input[\"contact_display_order\"].(string); ok {\n\t\ts.ContactDisplayOrder = contactDisplayOrder\n\t}\n\tif contactDisplayFormat, ok := input[\"contact_display_format\"].(string); ok {\n\t\ts.ContactDisplayFormat = contactDisplayFormat\n\t}\n\tif defaultLocale, ok := input[\"default_locale\"].(string); ok {\n\t\ts.DefaultLocale = defaultLocale\n\t}\n\tif messageDisplayFormat, ok := input[\"message_display_format\"].(string); ok {\n\t\ts.MessageDisplayFormat = messageDisplayFormat\n\t}\n\tif delay, ok := input[\"notification_delay_disappear\"].(float64); ok {\n\t\ts.NotificationDelayDisappear = int(delay)\n\t}\n\tif notificationEnabled, ok := input[\"notification_enabled\"].(bool); ok {\n\t\ts.NotificationEnabled = notificationEnabled\n\t}\n\tif notificationSoundEnabled, ok := input[\"notification_sound_enabled\"].(bool); ok {\n\t\ts.NotificationSoundEnabled = notificationSoundEnabled\n\t}\n\tif notificationMessagePreview, ok := input[\"notification_message_preview\"].(string); ok {\n\t\ts.NotificationMessagePreview = notificationMessagePreview\n\t}\n\tif userID, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(userID); err == nil {\n\t\t\ts.UserId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Settings) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn s.UnmarshalMap(input)\n}\n\nfunc (*Settings) NewEmpty() interface{} {\n\treturn new(Settings)\n}\n\nfunc (s *Settings) JsonTags() map[string]string {\n\treturn jsonTags(s)\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/social_identity.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"github.com/satori/go.uuid\"\n)\n\ntype (\n\t// embedded in a contact\n\tSocialIdentity struct {\n\t\tInfos    map[string]string `cql:\"infos\"     json:\"infos,omitempty\"        patch:\"user\"`\n\t\tName     string            `cql:\"name\"      json:\"name,omitempty\"         patch:\"user\"`\n\t\tSocialId UUID              `cql:\"social_id\" json:\"social_id,omitempty\"    patch:\"system\"`\n\t\tType     string            `cql:\"type\"      json:\"type,omitempty\"         patch:\"user\"`\n\t}\n\n\t// Mean of communication for a contact, with on-demand calculated PI.\n\tContactIdentity struct {\n\t\tIdentifier   string       `json:\"identifier\"` // the 'I' like in URI, ie : the email address for email ; the user's real name for IRC\n\t\tLabel        string       `json:\"label\"`      // the 'display-name' field in email address if available ; the 'nickname' for IRC\n\t\tPrivacyIndex PrivacyIndex `json:\"privacy_index\"`\n\t\tProtocol     string       `json:\"protocol\"` // email, irc, sms, etc.\n\t}\n\n\t//struct returned to user by suggest engine when performing a string query search\n\tRecipientSuggestion struct {\n\t\tAddress    string `json:\"address,omitempty\"`    // could be empty if suggestion is a contact (or should we automatically put preferred identity's address ?)\n\t\tContact_Id string `json:\"contact_id,omitempty\"` // contact's ID if any\n\t\tLabel      string `json:\"label,omitempty\"`      // name of contact or <display-name> in case of an address returned from participants lookup, if any\n\t\tProtocol   string `json:\"protocol,omitempty\"`   // email, IRC…\n\t\tSource     string `json:\"source,omitempty\"`     // \"participant\" or \"contact\", ie from where this suggestion came from\n\t}\n)\n\nfunc (si *SocialIdentity) UnmarshalMap(input map[string]interface{}) error {\n\tif infos, ok := input[\"infos\"].(map[string]interface{}); ok {\n\t\tfor k, v := range infos {\n\t\t\tsi.Infos[k] = v.(string)\n\t\t}\n\t}\n\tsi.Name, _ = input[\"name\"].(string)\n\tif soc_id, ok := input[\"social_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(soc_id); err == nil {\n\t\t\tsi.SocialId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tsi.Type, _ = input[\"type\"].(string)\n\treturn nil //TODO: errors handling\n}\n\n// MarshallNew must be a variadic func to implement NewMarshaller interface,\n// but SocialIdentity does not need params to marshal a well-formed SocialIdentity: ...interface{} is ignored\nfunc (si *SocialIdentity) MarshallNew(...interface{}) {\n\tif len(si.SocialId) == 0 || (bytes.Equal(si.SocialId.Bytes(), EmptyUUID.Bytes())) {\n\t\tsi.SocialId.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n}\n\nfunc (si *SocialIdentity) NewEmpty() interface{} {\n\tnewSi := new(SocialIdentity)\n\tnewSi.Infos = map[string]string{}\n\treturn newSi\n}\n\n// Sort interface implementations\ntype BySocialIdentityID []SocialIdentity\n\nfunc (p BySocialIdentityID) Len() int {\n\treturn len(p)\n}\n\nfunc (p BySocialIdentityID) Less(i, j int) bool {\n\treturn p[i].SocialId.String() < p[j].SocialId.String()\n}\n\nfunc (p BySocialIdentityID) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/tag.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"time\"\n)\n\n// \"patch\" tag indicates that the property could be modified by the specified actor.\n// for example :\n// If the \"patch\" tag is missing or not set to \"user\", property cannot be modified directly by an \"UserInitiator\" with a call to a PATCH method.\ntype Tag struct {\n\t// compound index\n\tUser_id UUID   `cql:\"user_id\" json:\"user_id\" formatter:\"rfc4122\" frontend:\"omit\"` // primary key\n\tName    string `cql:\"name\" json:\"name\"`                                           // primary key\n\t// values\n\tDate_insert      time.Time `cql:\"date_insert\" json:\"date_insert\" formatter:\"RFC3339Milli\"`\n\tImportance_level int32     `cql:\"importance_level\" json:\"importance_level\" patch:\"user\"`\n\tLabel            string    `cql:\"label\" json:\"label\" patch:\"user\"`\n\tType             TagType   `cql:\"type\" json:\"type\"`\n}\n\ntype TagType string\n\nconst (\n\tUserTag   TagType = \"user\"\n\tSystemTag TagType = \"system\"\n)\n\n// bespoke implementation of the json.Marshaller interface\n// outputs a JSON representation of an object\n// this marshaler takes account of custom tags for given 'context'\nfunc (tag *Tag) JSONMarshaller(context string) ([]byte, error) {\n\tvar jsonBuf bytes.Buffer\n\tenc := json.NewEncoder(&jsonBuf)\n\n\tfields, err := reflections.Fields(tag)\n\tif err != nil {\n\t\treturn jsonBuf.Bytes(), err\n\t}\n\tjsonBuf.WriteByte('{')\n\tfirst := true\nfieldsLoop:\n\tfor _, field := range fields {\n\t\tswitch context {\n\t\tcase \"frontend\":\n\t\t\tfront, err := reflections.GetFieldTag(*tag, field, \"frontend\")\n\t\t\tif err == nil {\n\t\t\t\tswitch front {\n\t\t\t\tcase \"omit\":\n\t\t\t\t\tcontinue fieldsLoop\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tj_field, err := reflections.GetFieldTag(tag, field, \"json\")\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"reflection for field %s failed\", field)\n\t\t} else {\n\t\t\tif j_field != \"\" && j_field != \"-\" {\n\t\t\t\tif first {\n\t\t\t\t\tfirst = false\n\t\t\t\t} else {\n\t\t\t\t\tjsonBuf.WriteByte(',')\n\t\t\t\t}\n\t\t\t\tjsonBuf.WriteString(\"\\\"\" + j_field + \"\\\":\")\n\t\t\t\tfield_value, err := reflections.GetField(tag, field)\n\t\t\t\tj_formatter, err := reflections.GetFieldTag(tag, field, \"formatter\")\n\t\t\t\tif err == nil {\n\t\t\t\t\tswitch j_formatter {\n\t\t\t\t\tcase \"RFC3339Milli\":\n\t\t\t\t\t\tjsonBuf.WriteString(\"\\\"\" + field_value.(time.Time).Format(RFC3339Milli) + \"\\\"\")\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tenc.Encode(field_value)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tjsonBuf.Write([]byte{'\"', '\"'})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.Warnf(\"Invalid field %s value: %s\", field, j_field)\n\t\t\t}\n\t\t}\n\t}\n\tjsonBuf.WriteByte('}')\n\treturn jsonBuf.Bytes(), nil\n}\n\n// return a JSON representation of Tag suitable for frontend client\nfunc (tag *Tag) MarshalFrontEnd() ([]byte, error) {\n\treturn tag.JSONMarshaller(\"frontend\")\n}\n\n// UnmarshalMap is used when unmarshalling a JSON to a Tag\n// it knows how to convert from json types to our custom types or nested struct.\n// it lazily ignores missing fields, or unknown fields found in input map.\nfunc (tag *Tag) UnmarshalMap(input map[string]interface{}) error {\n\tif date, ok := input[\"date_insert\"]; ok {\n\t\ttag.Date_insert, _ = time.Parse(time.RFC3339Nano, date.(string))\n\t}\n\tif il, ok := input[\"importance_level\"].(float64); ok {\n\t\ttag.Importance_level = int32(il)\n\t}\n\tif label, ok := input[\"label\"].(string); ok {\n\t\ttag.Label = label\n\t}\n\tif name, ok := input[\"name\"].(string); ok {\n\t\ttag.Name = name\n\t}\n\tif tt, ok := input[\"type\"].(string); ok {\n\t\ttag.Type = TagType(tt)\n\t}\n\tif id, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(id); err == nil {\n\t\t\ttag.User_id.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\treturn nil //TODO: errors handling\n}\n\n// unmarshal a map[string]interface{} that must owns all Tag's fields\n// typical usage is for unmarshaling response from Cassandra backend\nfunc (tag *Tag) UnmarshalCQLMap(input map[string]interface{}) error {\n\ttag.Date_insert = input[\"date_insert\"].(time.Time)\n\ttag.Importance_level = int32(input[\"importance_level\"].(int))\n\ttag.Label = input[\"label\"].(string)\n\ttag.Name = input[\"name\"].(string)\n\ttag.Type = TagType(input[\"type\"].(string))\n\tuserID, _ := input[\"user_id\"].(gocql.UUID)\n\ttag.User_id.UnmarshalBinary(userID.Bytes())\n\treturn nil\n}\n\nfunc (tag *Tag) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\treturn tag.UnmarshalMap(input)\n}\n\n// implementation of the CaliopenObject interface\nfunc (tag *Tag) JsonTags() (tags map[string]string) {\n\treturn jsonTags(tag)\n}\n\nfunc (tag *Tag) NewEmpty() interface{} {\n\treturn new(Tag)\n}\n\n// an UUID should be provided to fill User_id with\nfunc (tag *Tag) MarshallNew(args ...interface{}) {\n\tif len(tag.User_id) == 0 || (bytes.Equal(tag.User_id.Bytes(), EmptyUUID.Bytes())) {\n\t\tif len(args) == 1 {\n\t\t\tswitch args[0].(type) {\n\t\t\tcase UUID:\n\t\t\t\ttag.User_id = args[0].(UUID)\n\t\t\t}\n\t\t}\n\t}\n\n\tif tag.Date_insert.IsZero() {\n\t\ttag.Date_insert = time.Now()\n\t}\n}\n\n// part of CaliopenObject interface\nfunc (tag *Tag) SortSlices() {\n\t// nothing to sort\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/tls.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nvar TlsSuites map[uint16]string\nvar TlsVersions map[uint16]string\n\nfunc init() {\n\t// Taken from crypto/tls/cipher_suites which took it from http://www.iana.org/assignments/tls-parameters/tls-parameters.xml\n\t// TODO : add more suites\n\tTlsSuites = map[uint16]string{\n\t\t0x0005: \"TLS_RSA_WITH_RC4_128_SHA\",\n\t\t0x000a: \"TLS_RSA_WITH_3DES_EDE_CBC_SHA\",\n\t\t0x002f: \"TLS_RSA_WITH_AES_128_CBC_SHA\",\n\t\t0x0035: \"TLS_RSA_WITH_AES_256_CBC_SHA\",\n\t\t0x003c: \"TLS_RSA_WITH_AES_128_CBC_SHA256\",\n\t\t0x009c: \"TLS_RSA_WITH_AES_128_GCM_SHA256\",\n\t\t0x009d: \"TLS_RSA_WITH_AES_256_GCM_SHA384\",\n\t\t0xc007: \"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA\",\n\t\t0xc009: \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA\",\n\t\t0xc00a: \"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA\",\n\t\t0xc011: \"TLS_ECDHE_RSA_WITH_RC4_128_SHA\",\n\t\t0xc012: \"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA\",\n\t\t0xc013: \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA\",\n\t\t0xc014: \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA\",\n\t\t0xc023: \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256\",\n\t\t0xc027: \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256\",\n\t\t0xc02f: \"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\",\n\t\t0xc02b: \"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256\",\n\t\t0xc030: \"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384\",\n\t\t0xc02c: \"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384\",\n\t\t0xcca8: \"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305\",\n\t\t0xcca9: \"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305\",\n\t\t0x5600: \"TLS_FALLBACK_SCSV \",\n\t}\n\n\tTlsVersions = map[uint16]string{\n\t\t0x002: \"ssl2\",\n\t\t0x300: \"ssl3\",\n\t\t0x301: \"tls1.0\",\n\t\t0x302: \"tls1.1\",\n\t\t0x303: \"tls1.2\",\n\t\t0x304: \"tls1.3\",\n\t}\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/user.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n//\n// only struct and interfaces definitions in this pkg\n\npackage objects\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"math/big\"\n\t\"time\"\n)\n\n// Struct to operate on user objects\ntype UserInfo struct {\n\tUser_id  string\n\tShard_id string\n}\n\ntype User struct {\n\tContactId        UUID              `cql:\"contact_id\"               json:\"contact_id\"`\n\tDateInsert       time.Time         `cql:\"date_insert\"              json:\"date_insert\"                              formatter:\"RFC3339Milli\"`\n\tDateDelete       time.Time         `cql:\"date_delete\"              json:\"date_delete\"                              formatter:\"RCF3339Milli\"`\n\tFamilyName       string            `cql:\"family_name\"              json:\"family_name\"`\n\tGivenName        string            `cql:\"given_name\"               json:\"given_name\"`\n\tLocalIdentities  []string          `cql:\"local_identities\"         json:\"local_identities\"`\n\tMainUserId       UUID              `cql:\"main_user_id\"             json:\"main_user_id\"`\n\tName             string            `cql:\"name\"                     json:\"name\"`\n\tParams           map[string]string `cql:\"params\"                   json:\"params\"`\n\tPassword         []byte            `cql:\"password\"                 json:\"password\"`\n\tPrivacyIndex     *PrivacyIndex     `cql:\"pi\"                       json:\"pi\"`\n\t*PrivacyFeatures `cql:\"privacy_features\"         json:\"privacy_features\"`\n\tRecoveryEmail    string `cql:\"recovery_email\"            json:\"recovery_email\"`\n\tUserId           UUID   `cql:\"user_id\"                  json:\"user_id\"              elastic:\"omit\"      formatter:\"rfc4122\"`\n\tShardId          string `cql:\"shard_id\"          json:\"shard_id\"`\n}\n\n// payload for triggering a password reset procedure for an end-user.\ntype PasswordResetRequest struct {\n\tRecoveryMail string `json:\"recovery_email\"`\n\tUsername     string `json:\"username\"`\n}\n\n// payload for triggering delete user process inside an `actions` call\ntype DeleteUserParams struct {\n\tPassword    string `json:\"password\"`\n\tAccessToken string `json:\"access_token\"`\n}\n\n// data stored into cache for authenticated user\ntype Auth_cache struct {\n\tAccess_token  string    `json:\"access_token\"`\n\tExpires_in    int       `json:\"expires_in\"`\n\tExpires_at    time.Time `json:\"expires_at\"`\n\tRefresh_token string    `json:\"refresh_token\"`\n\tCurve         string    `json:\"curve\"`\n\tX             big.Int   `json:\"x\"`\n\tY             big.Int   `json:\"y\"`\n\tKey_id        string    `json:\"key_id\"`\n\tShard_id      string    `json:\"shard_id\"`\n\tUser_status   string    `json:\"user_status\"`\n}\n\n// unmarshal a map[string]interface{} that must owns all Contact's fields\n// typical usage is for unmarshaling response from Cassandra backend\nfunc (user *User) UnmarshalCQLMap(input map[string]interface{}) {\n\tcontactId, _ := input[\"contact_id\"].(gocql.UUID)\n\tuser.ContactId.UnmarshalBinary(contactId.Bytes())\n\tuser.DateInsert, _ = input[\"date_insert\"].(time.Time)\n\tuser.DateDelete, _ = input[\"date_delete\"].(time.Time)\n\tuser.FamilyName, _ = input[\"family_name\"].(string)\n\tuser.GivenName, _ = input[\"given_name\"].(string)\n\tuser.LocalIdentities, _ = input[\"local_identities\"].([]string)\n\tmainUserId, _ := input[\"main_user_id\"].(gocql.UUID)\n\tuser.MainUserId.UnmarshalBinary(mainUserId.Bytes())\n\tuser.Name, _ = input[\"name\"].(string)\n\tpassword, _ := input[\"password\"].(string)\n\tuser.Password = []byte(password)\n\tif input[\"pi\"] != nil {\n\t\ti_pi, _ := input[\"pi\"].(map[string]interface{})\n\t\tpi := PrivacyIndex{}\n\t\tpi.Comportment, _ = i_pi[\"comportment\"].(int)\n\t\tpi.Context, _ = i_pi[\"context\"].(int)\n\t\tpi.DateUpdate, _ = i_pi[\"date_update\"].(time.Time)\n\t\tpi.Technic, _ = i_pi[\"technic\"].(int)\n\t\tpi.Version, _ = i_pi[\"version\"].(int)\n\t\tuser.PrivacyIndex = &pi\n\t} else {\n\t\tuser.PrivacyIndex = nil\n\t}\n\tif input[\"privacy_features\"] != nil {\n\t\ti_pf, _ := input[\"privacy_features\"].(map[string]string)\n\t\tpf := PrivacyFeatures{}\n\t\tfor k, v := range i_pf {\n\t\t\tpf[k] = v\n\t\t}\n\t\tuser.PrivacyFeatures = &pf\n\n\t} else {\n\t\tuser.PrivacyFeatures = nil\n\t}\n\tuser.RecoveryEmail, _ = input[\"recovery_email\"].(string)\n\tuserid, _ := input[\"user_id\"].(gocql.UUID)\n\tuser.UserId.UnmarshalBinary(userid.Bytes())\n\tuser.ShardId, _ = input[\"shard_id\"].(string)\n}\n\nfunc (user *User) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn user.UnmarshalMap(input)\n}\n\nfunc (user *User) UnmarshalMap(input map[string]interface{}) error {\n\n\tif contact_id, ok := input[\"contact_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(contact_id); err == nil {\n\t\t\tuser.ContactId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\n\tif date, ok := input[\"date_insert\"]; ok {\n\t\tuser.DateInsert, _ = time.Parse(time.RFC3339Nano, date.(string))\n\t}\n\n\tuser.FamilyName, _ = input[\"family_name\"].(string)\n\tuser.GivenName, _ = input[\"given_name\"].(string)\n\tuser.LocalIdentities, _ = input[\"local_identities\"].([]string)\n\n\tif main_user_id, ok := input[\"main_user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(main_user_id); err == nil {\n\t\t\tuser.MainUserId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\n\tuser.Name, _ = input[\"name\"].(string)\n\tuser.Params, _ = input[\"params\"].(map[string]string)\n\tpassword, _ := input[\"password\"].(string)\n\tuser.Password = []byte(password)\n\n\tif pi, ok := input[\"pi\"]; ok && pi != nil {\n\t\tPI := new(PrivacyIndex)\n\t\tif err := PI.UnmarshalMap(pi.(map[string]interface{})); err == nil {\n\t\t\tuser.PrivacyIndex = PI\n\t\t}\n\t}\n\n\tif pf, ok := input[\"privacy_features\"]; ok && pf != nil {\n\t\tPF := &PrivacyFeatures{}\n\t\tPF.UnmarshalMap(pf.(map[string]interface{}))\n\t\tuser.PrivacyFeatures = PF\n\t}\n\n\tuser.RecoveryEmail, _ = input[\"recovery_email\"].(string)\n\n\tif user_id, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(user_id); err == nil {\n\t\t\tuser.UserId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// bespoke implementation of the json.Marshaller interface\n// outputs a JSON representation of an object\n// this marshaler takes account of custom tags for given 'context'\nfunc (user *User) JSONMarshaller(context string, body_type ...string) ([]byte, error) {\n\t//TODO\n\treturn []byte{}, nil\n}\n\nfunc (user *User) MarshalJSON() ([]byte, error) {\n\t//TODO\n\treturn []byte{}, nil\n}\n\n// return a JSON representation of Message suitable for frontend client\nfunc (user *User) MarshalFrontEnd(body_type string) ([]byte, error) {\n\t//TODO\n\treturn []byte{}, nil\n}\n\n// implementation of the CaliopenObject interface\nfunc (user *User) JsonTags() (tags map[string]string) {\n\treturn jsonTags(user)\n}\n\nfunc (user *User) NewEmpty() interface{} {\n\tu := new(User)\n\tu.Params = make(map[string]string)\n\treturn u\n}\n\n//bespoke unmarshaller to workaround the expires_at field that is not RFC in cache (tz is missing, thanks python)\nfunc (ac *Auth_cache) UnmarshalJSON(b []byte) error {\n\tvar temp struct {\n\t\tAccess_token  string  `json:\"access_token\"`\n\t\tExpires_in    int     `json:\"expires_in\"`\n\t\tExpires_at    string  `json:\"expires_at\"`\n\t\tRefresh_token string  `json:\"refresh_token\"`\n\t\tCurve         string  `json:\"curve\"`\n\t\tX             big.Int `json:\"x\"`\n\t\tY             big.Int `json:\"y\"`\n\t\tKey_id        string  `json:\"key_id\"`\n\t\tShard_id      string  `json:\"shard_id\"`\n\t\tUser_status   string  `json:\"user_status\"`\n\t}\n\tif err := json.Unmarshal(b, &temp); err != nil {\n\t\treturn err\n\t}\n\tac.Access_token = temp.Access_token\n\tac.Expires_in = temp.Expires_in\n\texpire, err := time.Parse(time.RFC3339Nano, temp.Expires_at+\"Z\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tac.Expires_at = expire\n\tac.Refresh_token = temp.Refresh_token\n\tac.Key_id = temp.Key_id\n\tac.X = temp.X\n\tac.Y = temp.Y\n\tac.Curve = temp.Curve\n\tac.Shard_id = temp.Shard_id\n\tac.User_status = temp.User_status\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/user_identity.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype (\n\t// object stored in db with primary keys on user_id and identity_id\n\tUserIdentity struct {\n\t\tCredentials *Credentials      `cql:\"credentials\"        json:\"credentials,omitempty\"                            patch:\"user\"`\n\t\tDisplayName string            `cql:\"display_name\"       json:\"display_name\"                                     patch:\"user\"`\n\t\tId          UUID              `cql:\"identity_id\"        json:\"identity_id\"`\n\t\tIdentifier  string            `cql:\"identifier\"         json:\"identifier\"                                                     ` // for example: me@caliopen.org, @mastodon_account\n\t\tInfos       map[string]string `cql:\"infos\"              json:\"infos\"                                            patch:\"user\"  `\n\t\tLastCheck   time.Time         `cql:\"last_check\"         json:\"last_check,omitempty\"                 formatter:\"RFC3339Milli\"`\n\t\tProtocol    string            `cql:\"protocol\"           json:\"protocol\"                                                       ` // for example: smtp, imap, mastodon\n\t\tStatus      string            `cql:\"status\"             json:\"status\"                                           patch:\"user\"`   // for example : active, inactive, deleted\n\t\tType        string            `cql:\"type\"               json:\"type\"                                                           ` // for example : local, remote\n\t\tUserId      UUID              `cql:\"user_id\"            json:\"user_id\"              frontend:\"omit\"`\n\t}\n\n\t// cassandra tables to lookup identities by identifier, protocol, user_id and/or type\n\t// lookup_tables :  identity_lookup(identifier + protocol + user_id)\n\t//                  identity_type_lookup(type + user_id)\n\tIdentityLookup struct {\n\t\tIdentifier string `cql:\"identifier\"`\n\t\tProtocol   string `cql:\"protocol\"`\n\t\tUserId     UUID   `cql:\"user_id\"`\n\t\tIdentityId UUID   `cql:\"identity_id\"`\n\t}\n\tIdentityTypeLookup struct {\n\t\tType       string `cql:\"type\"`\n\t\tUserId     UUID   `cql:\"user_id\"`\n\t\tIdentityId UUID   `cql:\"identity_id\"`\n\t}\n)\n\nfunc (ui *UserIdentity) NewEmpty() interface{} {\n\tnui := new(UserIdentity)\n\tnui.Infos = map[string]string{}\n\treturn nui\n}\n\nfunc (ui *UserIdentity) UnmarshalJSON(b []byte) error {\n\tinput := map[string]interface{}{}\n\tif err := json.Unmarshal(b, &input); err != nil {\n\t\treturn err\n\t}\n\n\treturn ui.UnmarshalMap(input)\n}\n\nfunc (ui *UserIdentity) UnmarshalMap(input map[string]interface{}) error {\n\tif credentials, ok := input[\"credentials\"]; ok && credentials != nil {\n\t\tcred := &Credentials{}\n\t\tcred.UnmarshalMap(credentials.(map[string]interface{}))\n\t\tui.Credentials = cred\n\t}\n\tif dn, ok := input[\"display_name\"].(string); ok {\n\t\tui.DisplayName = dn\n\t}\n\tif id, ok := input[\"identity_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(id); err == nil {\n\t\t\tui.Id.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\tif identifier, ok := input[\"identifier\"].(string); ok {\n\t\tui.Identifier = identifier\n\t}\n\tif infos, ok := input[\"infos\"].(map[string]interface{}); ok {\n\t\tui.Infos = make(map[string]string)\n\t\tfor k, v := range infos {\n\t\t\tui.Infos[k] = v.(string)\n\t\t}\n\t}\n\n\tif lc, ok := input[\"last_check\"]; ok {\n\t\tui.LastCheck, _ = time.Parse(time.RFC3339Nano, lc.(string))\n\t}\n\tif protocol, ok := input[\"protocol\"].(string); ok {\n\t\tui.Protocol = protocol\n\t}\n\tif status, ok := input[\"status\"].(string); ok {\n\t\tui.Status = status\n\t}\n\tif t, ok := input[\"type\"].(string); ok {\n\t\tui.Type = t\n\t}\n\tif userid, ok := input[\"user_id\"].(string); ok {\n\t\tif id, err := uuid.FromString(userid); err == nil {\n\t\t\tui.UserId.UnmarshalBinary(id.Bytes())\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (ui *UserIdentity) JsonTags() (tags map[string]string) {\n\treturn jsonTags(ui)\n}\n\nfunc (ui *UserIdentity) SortSlices() {\n\t//no slice to sort\n}\n\n// ensure mandatory properties are set, also default values.\nfunc (ui *UserIdentity) MarshallNew(args ...interface{}) {\n\tif len(ui.Id) == 0 || (bytes.Equal(ui.Id.Bytes(), EmptyUUID.Bytes())) {\n\t\tui.Id.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n\tif (len(ui.UserId) == 0 || bytes.Equal(ui.UserId.Bytes(), EmptyUUID.Bytes())) && len(args) == 1 {\n\t\tswitch args[0].(type) {\n\t\tcase UUID:\n\t\t\tui.UserId = args[0].(UUID)\n\t\t}\n\t}\n\n}\n\n// SetDefaults fills UserIdentity with default keys and values according to the type of the remote identity\nfunc (ui *UserIdentity) SetDefaults() {\n\tdefaults := map[string]string{}\n\n\tswitch ui.Protocol {\n\tcase EmailProtocol:\n\t\tdefaults = map[string]string{\n\t\t\t\"lastseenuid\":  \"\",\n\t\t\t\"lastsync\":     \"\",   // RFC3339 date string\n\t\t\t\"inserver\":     \"\",   // server hostname[|port]\n\t\t\t\"outserver\":    \"\",   // server hostname[|port]\n\t\t\t\"uidvalidity\":  \"\",   // uidvalidity to invalidate data if needed (see RFC4549#section-4.1)\n\t\t\t\"pollinterval\": \"15\", // how often remote account should be polled, in minutes.\n\t\t}\n\tcase TwitterProtocol, MastodonProtocol:\n\t\tdefaults = map[string]string{\n\t\t\t\"lastseendm\":   \"\",\n\t\t\t\"lastsync\":     \"\",  // RFC3339 date string\n\t\t\t\"pollinterval\": \"2\", // how often remote account should be polled, in minutes.\n\t\t}\n\t}\n\n\tif ui.Infos == nil {\n\t\t(*ui).Infos = defaults\n\t} else {\n\t\tfor default_key, default_value := range defaults {\n\t\t\tif v, ok := ui.Infos[default_key]; !ok || v == \"\" {\n\t\t\t\t(*ui).Infos[default_key] = default_value\n\t\t\t}\n\t\t}\n\t}\n\n\t// try to infer authentication mechanism\n\tif authType, ok := ui.Infos[\"authtype\"]; !ok || authType == \"\" {\n\t\tloginPassRegex, _ := regexp.Compile(`(?i)password|login`)\n\t\toauth1Regex, _ := regexp.Compile(`(?i)auth1`)\n\t\toauth2Regex, _ := regexp.Compile(`(?i)auth2`)\n\tcredentialsLoop:\n\t\tfor k, _ := range *ui.Credentials {\n\t\t\tswitch {\n\t\t\tcase loginPassRegex.MatchString(k):\n\t\t\t\t(*ui).Infos[\"authtype\"] = LoginPassword\n\t\t\t\tbreak credentialsLoop\n\t\t\tcase oauth1Regex.MatchString(k):\n\t\t\t\t(*ui).Infos[\"authtype\"] = Oauth1\n\t\t\t\tbreak credentialsLoop\n\t\t\tcase oauth2Regex.MatchString(k):\n\t\t\t\t(*ui).Infos[\"authtype\"] = Oauth2\n\t\t\t\tbreak credentialsLoop\n\t\t\t}\n\t\t}\n\t}\n\n\tif ui.Status == \"\" {\n\t\t(*ui).Status = \"active\"\n\t}\n\n\tif ui.Type == \"\" {\n\t\t(*ui).Type = \"remote\"\n\t}\n\t(*ui).LastCheck = time.Time{}\n\t// try to set DisplayName and Identifier if it is missing\n\tif ui.Identifier == \"\" {\n\t\tswitch ui.Protocol {\n\t\tcase ImapProtocol:\n\t\t\t(*ui).Identifier, _ = (*ui.Credentials)[\"username\"]\n\t\t}\n\t}\n\tif ui.DisplayName == \"\" {\n\t\tui.DisplayName = ui.Identifier\n\t}\n\tui.Identifier = strings.ToLower(ui.Identifier)\n}\n\nfunc (ui *UserIdentity) UnmarshalCQLMap(input map[string]interface{}) error {\n\tif credentials, ok := input[\"credentials\"]; ok && credentials != nil {\n\t\tcred := &Credentials{}\n\t\tcred.UnmarshalCQLMap(credentials.(map[string]string))\n\t\tui.Credentials = cred\n\t}\n\tif dn, ok := input[\"display_name\"].(string); ok {\n\t\tui.DisplayName = dn\n\t}\n\tif id, ok := input[\"identity_id\"].(gocql.UUID); ok {\n\t\tui.Id.UnmarshalBinary(id.Bytes())\n\t}\n\tif identifier, ok := input[\"identifier\"].(string); ok {\n\t\tui.Identifier = identifier\n\t}\n\tif infos, ok := input[\"infos\"].(map[string]string); ok {\n\t\tui.Infos = make(map[string]string)\n\t\tfor k, v := range infos {\n\t\t\tui.Infos[k] = v\n\t\t}\n\t}\n\tif lc, ok := input[\"last_check\"].(time.Time); ok {\n\t\tui.LastCheck = lc\n\t}\n\tif protocol, ok := input[\"protocol\"].(string); ok {\n\t\tui.Protocol = protocol\n\t}\n\tif status, ok := input[\"status\"].(string); ok {\n\t\tui.Status = status\n\t}\n\tif t, ok := input[\"type\"].(string); ok {\n\t\tui.Type = t\n\t}\n\tif userid, ok := input[\"user_id\"].(gocql.UUID); ok {\n\t\tui.UserId.UnmarshalBinary(userid.Bytes())\n\t}\n\treturn nil\n}\n\nfunc (ui *UserIdentity) MarshalFrontEnd() ([]byte, error) {\n\treturn JSONMarshaller(\"frontend\", ui)\n}\n\n/**`HasLookup` interface implementation for UserIdentity\n\tto ensure lookup tables consistency\n **/\nfunc (userIdentity *UserIdentity) GetLookupsTables() map[string]StoreLookup {\n\treturn map[string]StoreLookup{\n\t\t\"identity_lookup\":      &IdentityLookup{},\n\t\t\"identity_type_lookup\": &IdentityTypeLookup{},\n\t}\n}\n\n// GetLookupKeys returns a chan to iterate over fields and values that make up the lookup tables keys\nfunc (userIdentity *UserIdentity) GetLookupKeys() <-chan StoreLookup {\n\tgetter := make(chan StoreLookup)\n\n\tgo func(chan StoreLookup) {\n\t\tgetter <- &IdentityLookup{\n\t\t\tIdentifier: userIdentity.Identifier,\n\t\t\tProtocol:   userIdentity.Protocol,\n\t\t\tUserId:     userIdentity.UserId,\n\t\t}\n\t\tgetter <- &IdentityTypeLookup{\n\t\t\tType:   userIdentity.Type,\n\t\t\tUserId: userIdentity.UserId,\n\t\t}\n\t\tclose(getter)\n\t}(getter)\n\n\treturn getter\n}\n\n// CleanupLookups implements StoreLookup interface.\n// It returns a func which removes IdentityLookup related to the UserIdentity given as param of the variadic func.\nfunc (il *IdentityLookup) CleanupLookups(identities ...interface{}) func(session *gocql.Session) error {\n\tif len(identities) == 1 {\n\t\tidentity := identities[0].(*UserIdentity)\n\t\treturn func(session *gocql.Session) error {\n\t\t\terr := session.Query(`DELETE FROM identity_lookup WHERE identifier = ? AND protocol = ? AND user_id = ?`,\n\t\t\t\tidentity.Identifier, identity.Protocol, identity.UserId.String()).Exec()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn nil\n}\n\n// UpdateLookups iterates over remote identity's lookups to add/update them to the relevant table,\n// then it deletes lookups references that are no more linked to an embedded key which has been removed,\n// `identites` param should have one item in the context of a creation or 2 items [new, old] in the context of an update\nfunc (il *IdentityLookup) UpdateLookups(identities ...interface{}) func(session *gocql.Session) error {\n\tidentitiesLen := len(identities)\n\tupdate := false\n\tif identitiesLen > 0 {\n\t\tnewIdentity := identities[0].(*UserIdentity)\n\t\tvar oldLookup map[string]*IdentityLookup\n\t\treturn func(session *gocql.Session) error {\n\t\t\tif identitiesLen == 2 && identities[1] != nil {\n\t\t\t\t// it's an update\n\t\t\t\tupdate = true\n\t\t\t\toldIdentity := identities[1].(*UserIdentity)\n\t\t\t\toldLookup = map[string]*IdentityLookup{} // build strings with cassa's keys\n\t\t\t\tfor lookup := range oldIdentity.GetLookupKeys() {\n\t\t\t\t\tlkp := lookup.(*IdentityLookup)\n\t\t\t\t\toldLookup[lkp.Identifier+lkp.Protocol+lkp.UserId.String()] = lkp\n\t\t\t\t}\n\t\t\t}\n\t\t\tlkp := &IdentityLookup{\n\t\t\t\tIdentifier: newIdentity.Identifier,\n\t\t\t\tProtocol:   newIdentity.Protocol,\n\t\t\t\tUserId:     newIdentity.UserId,\n\t\t\t}\n\t\t\t// try to get identity_id\n\t\t\tvar identityId gocql.UUID\n\t\t\tsession.Query(`SELECT identity_id FROM identity_lookup WHERE identifier = ? AND protocol = ? AND user_id = ?`,\n\t\t\t\tlkp.Identifier,\n\t\t\t\tlkp.Protocol,\n\t\t\t\tlkp.UserId).Scan(&identityId)\n\t\t\tif identityId.String() == \"\" ||\n\t\t\t\tidentityId.String() != newIdentity.UserId.String() { // identity_id not found or changed => set one\n\t\t\t\terr := session.Query(`INSERT INTO identity_lookup (identifier, protocol, user_id, identity_id) VALUES (?,?,?,?)`,\n\t\t\t\t\tlkp.Identifier,\n\t\t\t\t\tlkp.Protocol,\n\t\t\t\t\tlkp.UserId,\n\t\t\t\t\tnewIdentity.Id.String()).Exec()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(`[CassandraBackend] UpdateLookups INSERT failed for user: %s, identifier: %s`,\n\t\t\t\t\t\tlkp.UserId,\n\t\t\t\t\t\tlkp.Identifier)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif update {\n\t\t\t\t// remove keys in current states,\n\t\t\t\t// thus oldLookup map will only holds remaining entries that are not in the new state\n\t\t\t\tdelete(oldLookup, lkp.Identifier+lkp.Protocol+lkp.UserId.String())\n\t\t\t}\n\t\t\tif len(oldLookup) > 0 {\n\t\t\t\t// it remains lookups in the map, meaning identifier has been changed\n\t\t\t\t// need to remove it from lookup table\n\t\t\t\tfor _, lookup := range oldLookup {\n\t\t\t\t\terr := session.Query(`DELETE FROM identity_lookup WHERE identifier = ? AND protocol = ? AND user_id = ?`,\n\t\t\t\t\t\tlookup.Identifier,\n\t\t\t\t\t\tlookup.Protocol,\n\t\t\t\t\t\tlookup.UserId).Exec()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn nil\n}\n\n// CleanupLookups implements StoreLookup interface.\n// It returns a func which removes IdentityTypeLookup related to the UserIdentity given as param of the variadic func.\nfunc (itl *IdentityTypeLookup) CleanupLookups(identities ...interface{}) func(session *gocql.Session) error {\n\tif len(identities) == 1 {\n\t\tidentity := identities[0].(*UserIdentity)\n\t\treturn func(session *gocql.Session) error {\n\t\t\terr := session.Query(`DELETE FROM identity_type_lookup WHERE type = ? AND user_id = ? AND identity_id = ?`,\n\t\t\t\tidentity.Type, identity.UserId.String(), identity.Id.String()).Exec()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (itl *IdentityTypeLookup) UpdateLookups(identities ...interface{}) func(session *gocql.Session) error {\n\tidentitiesLen := len(identities)\n\tupdate := false\n\tif identitiesLen > 0 {\n\t\tnewIdentity := identities[0].(*UserIdentity)\n\t\tvar oldLookup map[string]*IdentityTypeLookup\n\t\treturn func(session *gocql.Session) error {\n\t\t\tif identitiesLen == 2 && identities[1] != nil {\n\t\t\t\t// it's an update\n\t\t\t\tupdate = true\n\t\t\t\toldIdentity := identities[1].(*UserIdentity)\n\t\t\t\toldLookup = map[string]*IdentityTypeLookup{} // build strings with cassa's keys\n\t\t\t\tfor lookup := range oldIdentity.GetLookupKeys() {\n\t\t\t\t\tlkp := lookup.(*IdentityTypeLookup)\n\t\t\t\t\toldLookup[lkp.Type+lkp.UserId.String()+lkp.IdentityId.String()] = lkp\n\t\t\t\t}\n\t\t\t}\n\t\t\tlkp := &IdentityTypeLookup{\n\t\t\t\tType:   newIdentity.Type,\n\t\t\t\tUserId: newIdentity.UserId,\n\t\t\t}\n\t\t\terr := session.Query(`INSERT INTO identity_type_lookup (type, user_id, identity_id) VALUES (?,?,?)`,\n\t\t\t\tlkp.Type,\n\t\t\t\tlkp.UserId,\n\t\t\t\tnewIdentity.Id.String()).Exec()\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(`[CassandraBackend] UpdateLookups INSERT failed for user: %s, type: %s`,\n\t\t\t\t\tlkp.UserId,\n\t\t\t\t\tlkp.Type)\n\t\t\t}\n\t\t\tif update {\n\t\t\t\t// remove keys in current states,\n\t\t\t\t// thus oldLookup map will only holds remaining entries that are not in the new state\n\t\t\t\tdelete(oldLookup, lkp.Type+lkp.UserId.String()+lkp.IdentityId.String())\n\t\t\t}\n\t\t\tif len(oldLookup) > 0 {\n\t\t\t\t// it remains lookups in the map, meaning identifier has been changed\n\t\t\t\t// need to remove it from lookup table\n\t\t\t\tfor _, lookup := range oldLookup {\n\t\t\t\t\terr := session.Query(`DELETE FROM identity_type_lookup WHERE type = ? AND user_id = ? AND identity_id = ?`,\n\t\t\t\t\t\tlookup.Type,\n\t\t\t\t\t\tlookup.UserId,\n\t\t\t\t\t\tlookup.IdentityId).Exec()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn nil\n}\n\ntype ByUUID []UUID\n\nfunc (p ByUUID) Len() int {\n\treturn len(p)\n}\n\nfunc (p ByUUID) Less(i, j int) bool {\n\treturn p[i].String() < p[j].String()\n}\n\nfunc (p ByUUID) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n"
  },
  {
    "path": "src/backend/defs/go-objects/username.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage objects\n\ntype (\n\tUserName struct {\n\t\tUser_id []byte `json:\"user_id\"`\n\t\tName    string `json:\"name\"`\n\t}\n\n\tAvailability struct {\n\t\tAvailable bool   `json:\"available\" binding:\"required\"`\n\t\tUsername  string `json:\"username\" binding:\"required\"`\n\t}\n)\n"
  },
  {
    "path": "src/backend/defs/go-objects/uuid.go",
    "content": "package objects\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"github.com/gocql/gocql\"\n)\n\ntype UUID [16]byte\n\n// Used in string method conversion\nconst dash byte = '-'\n\nfunc (id UUID) MarshalCQL(info gocql.TypeInfo) ([]byte, error) {\n\treturn id[:], nil\n}\n\nfunc (id *UUID) UnmarshalCQL(info gocql.TypeInfo, data []byte) error {\n\tif len(data) != 16 {\n\t\treturn fmt.Errorf(\"uuid: UUID must be exactly 16 bytes long, got %d bytes\", len(data))\n\t}\n\treturn id.UnmarshalBinary(data)\n}\n\n// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.\n// It will return error if the slice isn't 16 bytes long.\nfunc (id *UUID) UnmarshalBinary(data []byte) (err error) {\n\tif len(data) != 16 {\n\t\terr = fmt.Errorf(\"uuid: UUID must be exactly 16 bytes long, got %d bytes\", len(data))\n\t\treturn\n\t}\n\tcopy(id[:], data)\n\n\treturn\n}\n\n// Returns canonical string representation of UUID:\n// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.\nfunc (id UUID) String() string {\n\tbuf := make([]byte, 36)\n\n\thex.Encode(buf[0:8], id[0:4])\n\tbuf[8] = dash\n\thex.Encode(buf[9:13], id[4:6])\n\tbuf[13] = dash\n\thex.Encode(buf[14:18], id[6:8])\n\tbuf[18] = dash\n\thex.Encode(buf[19:23], id[8:10])\n\tbuf[23] = dash\n\thex.Encode(buf[24:], id[10:])\n\n\treturn string(buf)\n}\n\nfunc (id UUID) MarshalJSON() ([]byte, error) {\n\treturn []byte(\"\\\"\" + id.String() + \"\\\"\"), nil\n}\n\n// Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits\n// (16 bytes) long.\nfunc (id UUID) Bytes() []byte {\n\treturn id[:]\n}\n"
  },
  {
    "path": "src/backend/defs/nats-messages/IMAPworkers_FetchOrder.yaml",
    "content": "# this is the model of the json payload that must be sent to 'IMAPfetcher' topic on NATS's queue 'IMAPworkers'\nOrder: fetch\nUserid: 1aae81ea-2f21-483e-8e68-b4fcf664df5a\nRemoteid: xxxx@xxxx.xxx\n# optional fields\nServer: imap.gmail.com:993\nMailbox: INBOX\nLogin: user@gmail.com\nPassword: xxxx"
  },
  {
    "path": "src/backend/defs/nats-messages/SMTPqueue_ack_msg.yaml",
    "content": "# this is the model of the json payload that must be sent to 'SMTPqueue' on NATS to acknowledge a request\nerror: \"error message\" # omit this field if there is no error\nmessage: \"response message\""
  },
  {
    "path": "src/backend/defs/nats-messages/UpdateContactPI_msg.yaml",
    "content": "# this is the model of the json payload that must be sent to 'PI' subject on NATS\n# to trigger a new PI computation for a contact that has been created or modified.\norder: contact_update\nuser_id: 1aae81ea-2f21-483e-8e68-b4fcf664df5a\ncontact_id: 1aae81ea-2f21-483e-8e68-b4fcf664df5a"
  },
  {
    "path": "src/backend/defs/nats-messages/inboundSMTP_deliver_msg.yaml",
    "content": "# this is the model of the json payload that must be sent to 'inboundSMTP' subject on NATS\n# to trigger incoming email processing for an user, after the raw email has been accepted, stored and indexed by email broker\norder: process_email_message\nuser_id: 1aae81ea-2f21-483e-8e68-b4fcf664df5a\nmessage_id: 1aae81ea-2f21-483e-8e68-b4fcf664df5a"
  },
  {
    "path": "src/backend/defs/nats-messages/outboundSMTP_deliver_msg.yaml",
    "content": "# this is the model of the payload that must be sent to 'outboundSMTP' subject on NATS\n# to trigger the operations of building a new email from a Caliopen draft message and sending it to the MTA\norder: deliver\nmessage_id: 1aae81ea-2f21-483e-8e68-b4fcf664df5a\nuser_id: 1aae81ea-2f21-483e-8e68-b4fcf664df5a"
  },
  {
    "path": "src/backend/defs/notifiers/templates/email-device-validation.yaml",
    "content": "---\n# django like formatting for string blocks\n# fields available within template blocks :\n#   - user's given_name => given_name\n#   - user's family_name => family_name\n#   - device name => device_name\n#   - url of the reset password link => url\n\nsubject: \"Demande de validation d'un appareil pour Caliopen\"\nbody_plain: \"\\n\nBonjour {{ given_name }} {{ family_name }},\\n\nvous avez demandé la validation de l'appareil « {{ device_name }} » pour vous connecter à Caliopen.\\n\nPour confirmer la validation, veuillez cliquer sur ce lien :\\n\n\\n\n{{ url }}\\n\n\\n\net suivre les indications à l'écran.\\n\n\\n\nSi vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer ce mail.\\n\n\\n\nCordialement,\\n\nL'équipe de Caliopen.\\n\n\"\n"
  },
  {
    "path": "src/backend/defs/notifiers/templates/email-onboarding.yaml",
    "content": "---\n# django like formatting for string blocks\n\nsubject: \"Bienvenue dans Caliopen !\"\nbody_plain: \"\\n\n(English version below)\\n\n\\n\nBienvenue dans Caliopen !\\n\nCaliopen est une application de messagerie qui respecte votre vie privée, vous permet d’envoyer des messages de manière confidentielle à vos interlocuteurs et de mieux comprendre la confidentialité des messages que vous recevez.\\n\n\\n\nSi vous le souhaitez, vous pouvez continuer d’utiliser votre adresse mail actuelle tout en profitant des fonctionnalités de Caliopen\\n\n\\n\nAvec Caliopen, vous pouvez accéder à vos email et messages privés Twitter (et plus à venir) au même endroit.\\n\n\\n\n## Philosophie\\n\nLa philosophie de Caliopen est de traiter tous les messages comme une discussion avec une ou plusieurs personnes, indépendamment de leur sujet. Vous retrouverez donc toujours vos messages rassemblés par interlocuteur.\\n\n\\n\nChaque message que vous recevrez et enverrez sera accompagné d’un indicateur, l’indice de vie privée, ou en anglais Privacy Index, abrégé PI, qui vous permettra de connaitre le degré de confidentialité de la discussion avec votre interlocuteur. Cet indice sera accompagné de conseils qui vous permettront de converser de manière plus sûre.\\n\n\\n\n## Maintenant, jetons un coup d’œil aux principales fonctionnalités de Caliopen !\\n\nPour le moment, le fil de discussion peut vous sembler vide (à part ce message bien sûr 😀) , nous allons y remédier. Vous pourrez retrouver et gérer tous vos emails précédemment reçus en ajoutant votre compte mail ou twitter dans Caliopen. Pas d’inquiétude, vous ne changez pas de fournisseur ni d’adresse.\\n\n\\n\nNous appelons chaque compte différent une “identité”.\\n\n\\n\nVous pouvez ajouter vos comptes mails et twitter depuis le menu utilisateur « 👤 > compte > compte externe »\\n\n\\n\n[assets/ext_account_fr.png Compte externe]\\n\n\\n\n## Envoyer un message\\n\nPour envoyer un message, appuyez sur le bouton “écrire” à tout moment. Si vous avez ajouté d’autres comptes à Caliopen, vous pouvez décider de la part de quel compte et par quel service vous souhaitez envoyer votre message en le choisissant en haut du message.\\n\n\\n\n[assets/compose_button_fr.png Bouton de nouveau message]\\n\n\\n\n[assets/compose_fr.png Formulaire de nouveau message]\\n\n\\n\nAjoutez ensuite des participants grâce à leur adresse email, nom d’utilisateur twitter ou directement depuis votre carnet d’adresse Caliopen.\n\\n\n[assets/participants.png Sélection des participants]\n\\n\n## Discussions\\n\nComme nous l’avons vu, Caliopen regroupe les messages en discussions, par interlocuteur. Lorsque vous parlez à une personne, vous pouvez lui écrire depuis n’importe laquelle de vos identités.\\n\n\\n\nVous pouvez contacter la personne depuis votre adresse Caliopen, il peut vous répondre avec un message privé Twitter, et vous pouvez lui répondre de nouveau avec une autre adresse email, la discussion se poursuivra dans le même fil.\\n\n\\n\nS’il y a plusieurs interlocuteurs dans une discussion, vous ne pouvez par contre répondre uniquement que par le premier moyen de communication utilisé par chacun.\\n\n\\n\nPour résumer techniquement, Caliopen permet du multi-protocole mono-destinataire, et du mono-protocole multi-destinataires.\\n\n\\n\n[assets/identity_fr.png Sélection de l'identité]\\n\n\\n\n## Carnet d’adresse\\n\nvous pouvez ajouter des contacts à votre carnet d’adresse, et ajouter toutes les identités de votre interlocuteur dans une seule fiche de contact. De cette manière, Caliopen saura que 2 adresses différentes sont une même personne et pourra rassembler les messages de ce contact dans une même discussion.\\n\n\\n\n[assets/new_contact_fr.png Ajout de contact]\\n\n\\n\n## Recherche intuitive\\n\nTrouvez tout ce que vous cherchez dans vos messages et vos contacts. Pour protéger votre vie privée, Caliopen ne peut pas chercher dans vos messages chiffrés, vous seul pouvez en lire le contenu.\\n\n\\n\n[assets/search_fr.png Écran de recherche]\\n\n\\n\n## Menu utilisateur\\n\nMettez à jour les informations de votre compte.\\n\nPersonnalisez votre application dans vos paramètres.\\n\n\\n\n## Tout est ok ? 👌\\n\nVous pouvez commencer à utiliser Caliopen dès maintenant !\\n\n\\n\nSi vous ne savez pas par où commencer, pourquoi ne pas commencer par répondre à ce message pour dire coucou à l’équipe ?\\n\ncontact@caliopen.org ou @caliopen_org\\n\n\\n\n\\n\n**English version**\\n\n\\n\nWelcome to Caliopen !\\n\nCaliopen is a privacy enabler messenger app, that allows you to send and receive messages confidentially by helping to understand how private are your messages.\\n\n\\n\nYou may, if you want, continue to use your previous email address and still enjoy Caliopen’s features.\\n\n\\n\nWith Caliopen you can already have your emails and your Twitter private messages (and more to come) all in one place.\\n\n\\n\n## Philosophy\\n\nCaliopen’s philosophy is to handle all messages as a discussion with one or more people, regardless of their subject. You will always find your messages grouped by contact.\\n\n\\n\nEach message you send or receive is flagged with a value, the Privacy Index (abbreviated PI), which allows you to know the level of confidentiality of your conversation. Some tips will be there to help you improve your privacy.\\n\n\\n\n## Let’s have a look on Caliopen’s main features !\\n\nAt first your main timeline may appear empty (aside this message of course 😀), let’s fix it. You will be able to find and handle here your old messages by adding your email or Twitter account in Caliopen. Don’t worry, your previous adress or provider will not change.\\n\n\\n\nWe call each different account an “identity”\\n\n\\n\nYou can add your Twitter and email accounts from the user menu « 👤 > account > external account ».\\n\n\\n\n[assets/ext_account_en.png External account]\\n\n\\n\n## Sending a message\\n\nTo send a messsage tou can use the “Compose” button at any time. If you added external accounts, you can then chose from which “identity” you wish to send your message, by selecting it at the top of the message window.\\n\n\\n\n[assets/compose_button_en.png New message button]\\n\n\\n\n[assets/compose_en.png New message form]\\n\n\\n\nYou then can specify your adressees by adding their email address, Twitter username or directly from your Caliopen contact book.\\n\n\\n\n[assets/participants.png Recipients selection]\\n\n\\n\n## Discussions\\n\nAs seen before, all messages are gathered into discussions grouped by contacts. When you talk to someone, you can write from any of your “identities”.\\n\n\\n\nContacted from your Caliopen address, your correspondent can answer with a Twitter private message, and you can answer again from another email address: it will appear as only one discussion from a Caliopen point of vue.\\n\n\\n\nHowever, in a discussion with more than on person, you can only use the first communication channel used by each contact to answer him.\\n\n\\n\nTechnically, one can see Caliopen as handling mono-recipient multi-protocols, or multi-recipients mono-protocol.\\n\n\\n\n[assets/identity_en.png Identity selection]\\n\n\\n\n## Address book\\n\nYou can add contacts to your address book, and specify all of your contact identities in his contact card. By doing this, Caliopen will know that 2 different addresses are the same person, and will be able to group all messages from this contact in the same discussion.\\n\n\\n\n[assets/new_contact_en.png New contact]\\n\n\\n\n## Intuitive search\\n\nFind everything you are looking for in your messages and contact. To preserve your privacy, Caliopen cannot search in your messages if they are encrypted: you only can read their content.\\n\n\\n\n[assets/search_en.png Search screen]\\n\n\\n\n## User menu\\n\nUpdate your account informations.\\n\nCutstomize your application settings.\\n\nIs everything ok ? 👌\\n\n\\n\nYou can start using Caliopen now!\\n\n\\n\nIf you’re not sure where to begin, why not trying to answer this message and say hello to the team ?\\n\n\\n\ncontact@caliopen.org ou @caliopen_org\\n\n\"\nbody_html: \"\\n\n<div>\n  <p><span style=\\\"font-style: italic;\\\">(English version below)</span></p>\n  <hr />\n  <h1>Bienvenue dans Caliopen !</h1>\n  <p><strong>Caliopen est une application de messagerie qui respecte votre vie privée, vous permet d’envoyer des messages de manière confidentielle à vos interlocuteurs et de mieux comprendre la confidentialité des messages que vous recevez.</strong></p>\n  <p><strong>Si vous le souhaitez, vous pouvez continuer d’utiliser votre adresse e-mail actuelle tout en profitant des fonctionnalités de Caliopen.</strong></p>\n  <p>Avec Caliopen, vous pouvez accéder à vos email et messages privés Twitter (et plus à venir) au même endroit.</p>\n  <h2>Philosophie</h2>\n  <p>La philosophie de Caliopen est de traiter tous les messages comme une discussion avec une ou plusieurs personnes, indépendamment de leur sujet. Vous retrouverez donc toujours vos messages rassemblés par interlocuteur.</p>\n  <p>Chaque message que vous recevrez et enverrez sera accompagné d’un indicateur, l’indice de vie privée, ou en anglais Privacy Index, abrégé PI, qui vous permettra de connaitre le degré de confidentialité de la discussion avec votre interlocuteur. Cet indice sera accompagné de conseils qui vous permettront de converser de manière plus sûre.</p>\n  <hr>\n  <h2>Maintenant, jetons un coup d’œil aux principales fonctionnalités de Caliopen !</h2>\n  <p>Pour le moment, le fil de discussion peut vous sembler vide (à part ce message bien sûr 😀) , nous allons y remédier. Vous pourrez retrouver et gérer tous vos emails précédemment reçus en ajoutant votre compte mail ou twitter dans Caliopen. Pas d’inquiétude, vous ne changez pas de fournisseur ni d’adresse.</p>\n  <p>Nous appelons chaque compte différent une « identité ».</p>\n  <p>Vous pouvez ajouter vos comptes mails et twitter depuis le menu utilisateur « 👤 &gt; compte &gt; compte externe »</p>\n  <div><img src=\\\"{{base64encodeImgSrc(\\\"assets/ext_account_fr.png\\\")}}\\\" alt=\\\"Compte externe\\\" /></div>\n  <h2>Envoyer un message</h2>\n  <p>Pour envoyer un message, appuyez sur le bouton “écrire” à tout moment. Si vous avez ajouté d’autres comptes à Caliopen, vous pouvez décider de la part de quel compte et par quel service vous souhaitez envoyer votre message en le choisissant en haut du message.</p>\n  <div><img src=\\\"{{base64encodeImgSrc(\\\"assets/compose_button_fr.png\\\")}}\\\" alt=\\\"Bouton de nouveau message\\\" /></div>\n  <br/>\n  <div><img src=\\\"{{base64encodeImgSrc(\\\"assets/compose_fr.png\\\")}}\\\" alt=\\\"Formulaire de nouveau message\\\" /></div>\n  <p>Ajoutez ensuite des participants grâce à leur adresse email, nom d’utilisateur twitter ou directement depuis votre carnet d’adresse Caliopen.</p>\n  <div><img src=\\\"{{base64encodeImgSrc(\\\"assets/participants.png\\\")}}\\\" alt=\\\"Sélection des participants\\\" /></div>\n  <h2>Discussions</h2>\n  <p>Comme nous l’avons vu, Caliopen regroupe les messages en discussions, par interlocuteur. Lorsque vous parlez à une personne, vous pouvez lui écrire depuis n’importe laquelle de vos identités.</p>\n  <p>Vous pouvez contacter la personne depuis votre adresse Caliopen, il peut vous répondre avec un message privé Twitter, et vous pouvez lui répondre de nouveau avec une autre adresse email, la discussion se poursuivra dans le même fil.</p>\n  <p>S’il y a plusieurs interlocuteurs dans une discussion, vous ne pouvez par contre répondre uniquement que par le premier moyen de communication utilisé par chacun.</p>\n  <p>Pour résumer techniquement, Caliopen permet du multi-protocole mono-destinataire, et du mono-protocole multi-destinataires.</p>\n  <div><img src=\\\"{{base64encodeImgSrc(\\\"assets/identity_fr.png\\\")}}\\\" alt=\\\"Sélection de l'identité\\\" /></div>\n  <h2>Carnet d’adresse</h2>\n  <p>vous pouvez ajouter des contacts à votre carnet d’adresse, et ajouter toutes les identités de votre interlocuteur dans une seule fiche de contact. De cette manière, Caliopen saura que 2 adresses différentes sont une même personne et pourra rassembler les messages de ce contact dans une même discussion.</p>\n  <div><img src=\\\"{{base64encodeImgSrc(\\\"assets/new_contact_fr.png\\\")}}\\\" alt=\\\"Ajout de contact\\\" /></div>\n  <h2>Recherche intuitive</h2>\n  <p>Trouvez tout ce que vous cherchez dans vos messages et vos contacts. Pour protéger votre vie privée, Caliopen ne peut pas chercher dans vos messages chiffrés, vous seul pouvez en lire le contenu.</p>\n  <div><img src=\\\"{{base64encodeImgSrc(\\\"assets/search_fr.png\\\")}}\\\" alt=\\\"Écran de recherche\\\" /></div>\n  <h2>Menu utilisateur</h2>\n  <p>Mettez à jour les informations de votre compte.<br>\n  Personnalisez votre application dans vos paramètres.</p>\n  <h2>Tout est ok ? 👌</h2>\n  <p>Vous pouvez commencer à utiliser Caliopen dès maintenant !</p>\n  <p>Si vous ne savez pas par où commencer, pourquoi ne pas commencer par répondre à ce message pour dire coucou à l’équipe ?</p>\n  <pre>contact@caliopen.org</pre> ou <pre>@caliopen_org</pre>\n  <hr/>\n  <p style=\\\"font-style: bold;\\\">**English version**</p>\n  <br/>\n  <p>Welcome to Caliopen !</p>\n  <p>Caliopen is a privacy enabler messenger app, that allows you to send and receive messages confidentially by helping to understand how private are your messages.</p>\n  <p>You may, if you want, continue to use your previous email address and still enjoy Caliopen’s features.</p>\n  <p>With Caliopen you can already have your emails and your Twitter private messages (and more to come) all in one place.</p>\n  <h2>Philosophy</h2>\n  <p>Caliopen’s philosophy is to handle all messages as a discussion with one or more people, regardless of their subject. You will always find your messages grouped by contact.</p>\n  <p>Each message you send or receive is flagged with a value, the Privacy Index (abbreviated PI), which allows you to know the level of confidentiality of your conversation. Some tips will be there to help you improve your privacy.</p>\n  <h2>Let’s have a look on Caliopen’s main features !</h2>\n  <p>At first your main timeline may appear empty (aside this message of course 😀), let’s fix it. You will be able to find and handle here your old messages by adding your email or Twitter account in Caliopen. Don’t worry, your previous adress or provider will not change.</p>\n  <p>We call each different account an “identity”</p>\n  <p>You can add your Twitter and email accounts from the user menu « 👤 > account > external account ».</p>\n  <p><img src=\\\"{{base64encodeImgSrc(\\\"assets/ext_account_en.png\\\")}}\\\" alt=\\\"External account\\\" /></p>\n  <h2>Sending a message</h2>\n  <p>To send a messsage tou can use the “Compose” button at any time. If you added external accounts, you can then chose from which “identity” you wish to send your message, by selecting it at the top of the message window.</p>\n  <p><img src=\\\"{{base64encodeImgSrc(\\\"assets/compose_button_en.png\\\")}}\\\" alt=\\\"New message button\\\" /></p>\n  <p><img src=\\\"{{base64encodeImgSrc(\\\"assets/compose_en.png\\\")}}\\\" alt=\\\"New message form\\\" /></p>\n  <p>You then can specify your adressees by adding their email address, Twitter username or directly from your Caliopen contact book.</p>\n  <p><img src=\\\"{{base64encodeImgSrc(\\\"assets/participants.png\\\")}}\\\" alt=\\\"Recipients selection\\\" /></p>\n  <h2>Discussions</h2>\n  <p>As seen before, all messages are gathered into discussions grouped by contacts. When you talk to someone, you can write from any of your “identities”.</p>\n  <p>Contacted from your Caliopen address, your correspondent can answer with a Twitter private message, and you can answer again from another email address: it will appear as only one discussion from a Caliopen point of vue.</p>\n  <p>However, in a discussion with more than on person, you can only use the first communication channel used by each contact to answer him.</p>\n  <p>Technically, one can see Caliopen as handling mono-recipient multi-protocols, or multi-recipients mono-protocol.</p>\n  <p><img src=\\\"{{base64encodeImgSrc(\\\"assets/identity_en.png\\\")}}\\\" alt=\\\"Identity selection\\\" /></p>\n  <h2>Address book</h2>\n  <p>You can add contacts to your address book, and specify all of your contact identities in his contact card. By doing this, Caliopen will know that 2 different addresses are the same person, and will be able to group all messages from this contact in the same discussion.</p>\n  <p><img src=\\\"{{base64encodeImgSrc(\\\"assets/new_contact_en.png\\\")}}\\\" alt=\\\"New contact\\\" /></p>\n  <h2>Intuitive search</h2>\n  <p>Find everything you are looking for in your messages and contact. To preserve your privacy, Caliopen cannot search in your messages if they are encrypted: you only can read their content.</p>\n  <p><img src=\\\"{{base64encodeImgSrc(\\\"assets/search_en.png\\\")}}\\\" alt=\\\"Search screen\\\" /></p>\n  <h2>User menu</h2>\n  <p>Update your account informations.</p>\n  <p>Cutstomize your application settings.</p>\n  <p>Is everything ok ? 👌</p>\n  <p>You can start using Caliopen now!</p>\n  <p>If you’re not sure where to begin, why not trying to answer this message and say hello to the team ?</p>\n  <p>contact@caliopen.org ou @caliopen_org</p>\n</div>\n\"\n"
  },
  {
    "path": "src/backend/defs/notifiers/templates/email-reset-password-link.yaml",
    "content": "---\n# django like formatting for string blocks\n# fields available within template blocks :\n#   - user's given_name => given_name\n#   - user's family_name => family_name\n#   - instance domain name => domain\n#   - url of the reset password link => url\n\nsubject: \"Demande de réinitialisation de votre mot de passe\"\nbody_plain: \"\\n\nBonjour {{ given_name }} {{ family_name }},\\n\nnous avons pris note d'une demande de réinitialisation de votre mot de passe sur {{ domain }}.\\n\nPour le réinitialiser, veuillez cliquer sur ce lien :\\n\n\\n\n{{ url }}\\n\n\\n\net suivre les indications à l'écran.\\n\n\\n\nSi vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer ce mail.\\n\n\\n\nCordialement,\\n\nL'équipe de Caliopen.\\n\n\"\n"
  },
  {
    "path": "src/backend/defs/notifiers/templates/email-welcome.yaml",
    "content": "---\n# django like formatting for string blocks\n\nsubject: \"Quelle aventure.\"\nbody_plain: \"\\n\nQuelle aventure.\\n\n\\n\nLancé comme une piste de reflexion après l’indignation soulevée par les alertes d’E. Snowden, avec un objectif qui s’est précisé - d’abord dans le débat public, puis les réunions en petits comité - en prenant le temps qu’il fallait pour définir son ambition et ses fonctionnalités, Caliopen est devenu, petit à petit, une réalité.\\n\n\\n\nLe chemin était long. Il l’est encore (si j’avais su à quel point, je ne sais pas si je l’aurais pris). Mais aujourd’hui - même s’il reste encore beaucoup à faire (toutes les contributions sont bienvenues) - il est possible d’utiliser Caliopen comme outil de messagerie quotidien.\\n\n\\n\nC’est la raison pour laquelle il passe en version “Beta”.\\n\n\\n\nUne version beta ce n’est pas rien mais ce n’est pas tout : les fonctionnalités sont encore limitées et il manque beaucoup d’options qui viendront en leur temps. Mais c’est un moment important, qui nous donne l’occasion de vous faire participer à l’aventure. N’hésitez surtout pas à nous faire part de vos commentaires sur https://feedback.caliopen.org.\\n\n\\n\nJe ne souhaite pas faire un message trop long ici, juste vous souhaiter la bienvenue, à vous qui nous rejoignez, et vous remercier de l’intérêt que vous portez à Caliopen. Je vous laisse découvrir notre travail.\\n\n\\n\n\\n\nLaurent Chemla\n\"\nbody_html: \"\n     <div>\n     Quelle aventure.<br/>\n     <br/>\n     Lancé comme une piste de reflexion après l’indignation soulevée par les alertes d’E. Snowden, avec un objectif qui s’est précisé - d’abord dans le débat public, puis les réunions en petits comité - en prenant le temps qu’il fallait pour définir son ambition et ses fonctionnalités, Caliopen est devenu, petit à petit, une réalité.<br/>\n     <br/>\n     Le chemin était long. Il l’est encore (si j’avais su à quel point, je ne sais pas si je l’aurais pris). Mais aujourd’hui - même s’il reste encore beaucoup à faire (toutes les contributions sont bienvenues) - il est possible d’utiliser Caliopen comme outil de messagerie quotidien.<br/>\n     <br/>\n     C’est la raison pour laquelle il passe en version “Beta”.<br/>\n     <br/>\n    Une version beta ce n’est pas rien mais ce n’est pas tout: les fonctionnalités sont encore limitées et il manque beaucoup d’options qui viendront en leur temps. Mais c’est un moment important, qui nous donne l’occasion de vous faire participer à l’aventure. N’hésitez surtout pas à nous faire part de vos commentaires sur https://feedback.caliopen.org.<br/>\n     <br/>\n     Je ne souhaite pas faire un message trop long ici, juste vous souhaiter la bienvenue, à vous qui nous rejoignez, et vous remercier de l’intérêt que vous portez à Caliopen. Je vous laisse découvrir notre travail.<br/>\n     <br/>\n     <br/>\n     Laurent Chemla<br/>\n     </div>\n  \"\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Actions.yaml",
    "content": "---\ntype: object\nproperties:\n  actions:\n    type: array\n    items:\n      type: string\n      enum:\n        - send\n        - set_read\n        - set_unread\n        - reset_password\n        - delete\n        - device-validation\n  params:\n    type: object\nadditionalProperties: false\nrequired:\n  - actions\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Attachment.yaml",
    "content": "---\ntype: object\nproperties:\n  content_type:\n    type: string\n  is_inline:\n    type: boolean\n  file_name:        # the name of the related original file, if any, given by the MIME field 'filename='\n    type: string\n  size:             # size calculated during parsing, for future reference\n    type: integer\n    format: int64\n  temp_id:\n    type: string    # temporary attachment id, for draft's attachments only.\n  url:              # ObjectStore url for temporary file (draft) or boundary reference for mime-part attachment\n    type: string\n  mime_boundary:    # for attachments embedded in raw messages\n    type: string\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Authentication.yaml",
    "content": "---\ntype: object\nproperties:\n  username:\n    type: string\n  password:\n    type: string\n  context:\n    type: string\n  device:\n    \"$ref\": DefaultDevice.yaml\nrequired:\n- username\n- password\n# - device  # XXX to set mandatory later\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Contact.yaml",
    "content": "---\ntype: object\nproperties:\n  additional_name:\n    type: string\n  addresses:\n    type: array\n    items:\n      \"$ref\": PostalAddress.yaml\n  avatar:\n    type: string\n  contact_id:\n    type: string\n  date_insert:\n    type: string\n    format: date-time\n  date_update:\n    type: string\n    format: date-time\n  deleted:\n    type: string\n    format: date-time\n  emails:\n    type: array\n    items:\n      \"$ref\": Email.yaml\n  family_name:\n    type: string\n  given_name:\n    type: string\n  groups:\n    type: array\n    items:\n      type: string\n  identities:\n    type: array\n    items:\n      \"$ref\": SocialIdentity.yaml\n  ims:\n    type: array\n    items:\n      \"$ref\": IM.yaml\n  infos:\n    type: object\n  name_prefix:\n    type: string\n  name_suffix:\n    type: string\n  organizations:\n    type: array\n    items:\n      \"$ref\": Organization.yaml\n  phones:\n    type: array\n    items:\n      \"$ref\": Phone.yaml\n  pi:\n    type: object\n    \"$ref\": PI.yaml\n  privacy_features:\n    type: object\n    \"$ref\": PrivacyFeatures.yaml\n  public_keys:\n    type: array\n    items:\n      \"$ref\": PublicKey.yaml\n  tags:\n    type: array\n    items:\n      type: string\n  title:\n    type: string\n  user_id:\n    type: string\nrequired:\n- contact_id\n- user_id\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/ContactIdentity.yaml",
    "content": "---\ntype: object\nproperties:\n  identifier:     # the 'I' like in URI, ie : the email address for email ; the user's real name for IRC\n    type: string\n  label:          # the 'display-name' field in email address if available ; the 'nickname' for IRC\n    type: string\n  privacy_index:\n    type: object\n    \"$ref\": PI.yaml\n  protocol:       # email, irc, sms, etc.\n    type: string\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/DefaultDevice.yaml",
    "content": "---\ntype: object\nproperties:\n  device_id:\n    type: string\n  ecdsa_key:\n    type: object\n    \"$ref\": ECKey.yaml\nrequired:\n- device_id\n- ecdsa_key\nadditionalProperties: false"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Device.yaml",
    "content": "---\ntype: object\nproperties:\n  '$ref': NewDevice.yaml#/properties\n  device_id:\n    type: string\n  date_insert:\n    type: string\n    format: date-time\n  date_revoked:\n    type: string\n    format: date-time\n  ip_creation:\n    type: string\n  name:\n    type: string\n  privacy_features:\n    type: object\n    '$ref': PrivacyFeatures.yaml\n  pi:\n    type: object\n    '$ref': PI.yaml\n  status:\n    type: string\n  type:\n    type: string\n  user_id:\n    type: string\n  user_agent:\n    type: string\nrequired:\n- device_id\n- user_id\n- name\n- type\n- status\n- date_insert\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/DeviceLocation.yaml",
    "content": "---\ntype: object\nproperties:\n  address:\n    type: string\n  country:\n    type: string\n  device_id:\n    type: string\n  type:\n    type: string\n    default: other\n    enum:\n    - other\n    - home\n    - work\n    - public\n  user_id:\n    type: string\nrequired:\n- address\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Discussion.yaml",
    "content": "---\ntype: object\nproperties:\n  attachment_count:\n    type: integer\n    format: int32\n  date_insert:\n    type: string\n    format: date-time\n  date_update:\n    type: string\n    format: date-time\n  discussion_id:\n    type: string\n  importance_level:\n    type: integer\n    format: int32\n  participants:\n    type: array\n    items:\n      \"$ref\": Participant.yaml\n  tags:\n    type: array\n    items:\n      type: string\n  excerpt:\n    type: string\n  subject:\n    type: string\n  protocol:\n    type: string\n  total_count:\n    type: integer\n    format: int32\n  unread_count:\n    type: integer\n    format: int32\n  aliases:       # others discussion_id linked to this one, if any\n    type: array\n    items:\n      type: string\n  last_message_id:\n    type: string\n  last_message_date:\n    type: string\n    format: date-time\n  last_message_subject:\n    type: string"
  },
  {
    "path": "src/backend/defs/rest-api/objects/ECKey.yaml",
    "content": "---\ntype: object\nproperties:\n  curve:\n    type: string\n    enum:\n      - P-256\n      - P-384\n      - P-512\n  hash:\n    type: string\n    enum:\n      - SHA256\n      - SHA384\n      - SHA512\n  x:\n    type: string\n  y:\n    type: string\nrequired:\n - curve\n - x\n - y\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Email.yaml",
    "content": "---\ntype: object\nproperties:\n  \"$ref\": NewEmail.yaml#/properties\n  email_id:\n    type: string\nrequired:\n- address\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Error.yaml",
    "content": "---\ntype: object\nproperties:\n  error:\n    type: object\n    properties:\n      message:\n        type: string\n      code:\n        type: integer\n        format: int32\n      name:\n        type: string\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/ExternalReferences.yaml",
    "content": "---\ntype: object\nproperties:\n  ancestors_id:\n    type: string\n  message_id:\n    type: string\n  parent_id:\n    type: string\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/IM.yaml",
    "content": "---\ntype: object\nproperties:\n  \"$ref\": NewIM.yaml#/properties\n  im_id:\n    type: string\nrequired:\n- address\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Identity.yaml",
    "content": "--- # Identity is a reference embedded in a message\ntype: object\nproperties:\n  identifier: # the 'I' like in URI, ie : the email address for email ; the user's real name for IRC\n    type: string\n  type:      # local, remote, etc.\n    type: string\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Message.yaml",
    "content": "---\ntype: object\nproperties:\n  date:\n    type: string\n    format: date-time\n  date_insert:\n    type: string\n    format: date-time\n  external_message_id:\n    type: string\n  external_parent_id:\n    type: string\n  external_thread_id:\n    type: string\n  from_:\n    type: string\n  headers:\n    type: object\n  importance_level:\n    type: integer\n    format: int32\n  message_id:\n    type: string\n  parts:\n    type: array\n    items:\n      type: string\n  recipients:\n    type: array\n    items:\n      \"$ref\": Recipient.yaml\n  size:\n    type: integer\n    format: int32\n  state:\n    type: string\n  subject:\n    type: string\n  tags:\n    type: array\n    items:\n      type: string\n  text:\n    type: string\n  type:\n    type: string\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/MessageV2.yaml",
    "content": "---\ntype: object\nproperties:\n  attachments:\n    type: array\n    items:\n      \"$ref\": Attachment.yaml\n  body:\n    type: string\n  body_is_plain:\n    type: boolean\n  date:\n    type: string\n    format: date-time\n  date_delete:\n    type: string\n    format: date-time\n  date_insert:\n    type: string\n    format: date-time\n  date_sort:\n    type: string\n    format: date-time\n  discussion_id:\n    type: string\n  external_references:\n    type: object\n    \"$ref\": ExternalReferences.yaml\n  excerpt:\n    type: string\n  user_identities: # to which user's identities the message is linked to\n    type: array\n    items:\n      type: string\n  importance_level:\n    type: integer\n    format: int32\n  is_answered:\n    type: boolean\n  is_draft:\n    type: boolean\n  is_unread:\n    type: boolean\n  is_received:\n    type: boolean\n  message_id:\n    type: string\n  parent_id: # The id of the Message this message is a reply to. This is primarily for drafts, but the server may support this for received messages as well by looking up the [@!RFC5322] Message-Id referenced in the In-Reply-To header and searching for this message in the user’s mail.\n    type: string\n  participants:\n    type: array\n    items:\n      \"$ref\": Participant.yaml\n  privacy_features:\n    type: object\n    \"$ref\": PrivacyFeatures.yaml\n  pi:\n    type: object\n    \"$ref\": PI.yaml\n  pi_message:\n    type: object\n    \"$ref\": PIMessage.yaml\n  raw_msg_id:\n    type: string\n  subject:\n    type: string\n  tags:\n    type: array\n    items:\n      type: string\n  protocol:\n    type: string\n  user_id:\n    type: string\n#required:\n  #- date\n  #- date_insert\n  #- discussion_id\n  #- is_answered\n  #- is_draft\n  #- is_unread\n  #- message_id\n  #- raw_msg_id\n  #- participants\n  #- protocol\n  #- user_id\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewContact.yaml",
    "content": "---\ntype: object\nproperties:\n  additional_name:\n    type: string\n  addresses:\n    type: array\n    items:\n      \"$ref\": PostalAddress.yaml\n  avatar:\n    type: string\n  emails:\n    type: array\n    items:\n      \"$ref\": NewEmail.yaml\n  family_name:\n    type: string\n  given_name:\n    type: string\n  title:\n    type: string\n  groups:\n    type: array\n    items:\n      type: string\n  identities:\n    type: array\n    items:\n      \"$ref\": SocialIdentity.yaml\n  ims:\n    type: array\n    items:\n      type: object\n  infos:\n    type: object\n  name_prefix:\n    type: string\n  name_suffix:\n    type: string\n  organizations:\n    type: array\n    items:\n      type: object\n  phones:\n    type: array\n    items:\n      \"$ref\": Phone.yaml\n  public_keys:\n    type: array\n    items:\n      \"$ref\": PublicKey.yaml\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewDevice.yaml",
    "content": "---\ntype: object\nproperties:\n  device_id:\n    type: string\n  ip_creation:\n    description: ip address at creation\n    type: string\n  locations:\n    type: array\n    items:\n      \"$ref\": DeviceLocation.yaml\n  name:\n    type: string\n  public_keys:\n    type: array\n    items:\n      \"$ref\": PublicKey.yaml\n  status:\n    type: string\n  type:\n    type: string\n    default: unknow\n    enum:\n    - other\n    - desktop\n    - laptop\n    - smartphone\n    - tablet\n  user_agent:\n    type: string\nrequired:\n- name\n- type\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewEmail.yaml",
    "content": "---\ntype: object\nproperties:\n  address:\n    type: string\n  is_primary:\n    type: boolean\n  label:\n    type: string\n  type:\n    type: string\nrequired:\n- address\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewIM.yaml",
    "content": "---\ntype: object\nproperties:\n  address:\n    type: string\n  is_primary:\n    type: boolean\n  label:\n    type: string\n  protocol:\n    type: string\n  type:\n    type: string\nrequired:\n- address\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewMessage.yaml",
    "content": "---\ntype: object\nproperties:\n  bcc_recipients:\n    type: array\n    items:\n      \"$ref\": Recipient.yaml\n  cc_recipients:\n    type: array\n    items:\n      \"$ref\": Recipient.yaml\n  reply_to:\n    type: string\n  subject:\n    type: string\n  text:\n    type: string\n  to_recipients:\n    type: array\n    items:\n      \"$ref\": Recipient.yaml\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewMessageV2.yaml",
    "content": "---\ntype: object\nproperties:\n  attachments:\n    type: array\n    items:\n      \"$ref\": Attachment.yaml\n  body:\n    type: string\n  discussion_id:\n    type: string\n  user_identities: # to which user's local identities the message is linked to\n    type: array\n    items:\n      type: string\n  message_id:\n    type: string\n  parent_id: # The id of the Message this message is a reply to. This is primarily for drafts, but the server may support this for received messages as well by looking up the [@!RFC5322] Message-Id referenced in the In-Reply-To header and searching for this message in the user’s mail.\n    type: string\n  participants:\n    type: array\n    items:\n      \"$ref\": Participant.yaml\n  subject:\n    type: string\n  privacy_features:\n    type: object\n    \"$ref\": PrivacyFeatures.yaml\nrequired:\n  - user_identities\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewOrganization.yaml",
    "content": "---\ntype: object\nproperties:\n  department:\n    type: string\n  is_primary:\n    type: boolean\n  job_description:\n    type: string\n  label:\n    type: string\n  name:\n    type: string\n  title:\n    type: string\n  type:\n    type: string\nrequired:\n- name\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewPhone.yaml",
    "content": "---\ntype: object\nproperties:\n  is_primary:\n    type: boolean\n  number:\n    type: string\n  type:\n    type: string\n  uri:\n    type: string\nrequired:\n- number\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewPostalAddress.yaml",
    "content": "---\ntype: object\nproperties:\n  address_id:\n    type: string\n  city:\n    type: string\n  country:\n    type: string\n  is_primary:\n    type: boolean\n  label:\n    type: string\n  postal_code:\n    type: string\n  region:\n    type: string\n  street:\n    type: string\n  type:\n    type: string\nrequired:\n- city\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewPublicKey.yaml",
    "content": "---\ntype: object\nproperties:\n  key:\n    type: string\n    description : \"DER or PEM key, base64 encoded\"\n  label:\n    type: string\nrequired:\n- key\n- label\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewSocialIdentity.yaml",
    "content": "---\ntype: object\nproperties:\n  infos:\n    type: object\n  name:\n    type: string\n  type:\n    type: string\nrequired:\n- name\n- type\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewTag.yaml",
    "content": "---\ntype: object\nproperties:\n  label:\n    type: string\n  importance_level:\n    type: integer\n    format: int32\nrequired:\n- label\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewUser.yaml",
    "content": "---\ntype: object\nproperties:\n  contact:\n    \"$ref\": NewContact.yaml\n  username:\n    type: string\n  password:\n    type: string\n  recovery_email:\n    type: string\n  tos:\n    type: boolean\n  privacy:\n    type: boolean\n  settings:\n    \"$ref\": Settings.yaml\n  device:\n    \"$ref\": DefaultDevice.yaml\nadditionalProperties: false\nrequired:\n- username\n- password\n- recovery_email\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/NewUserIdentity.yaml",
    "content": "---\ntype: object\nproperties:\n  credentials:    # set of key/value strings\n    type: object\n    additionalProperties:\n      type: string\n  display_name:\n    type: string\n  identifier:\n    type: string\n  infos:          # set of key/value strings\n    type: object\n    additionalProperties:\n      type: string\n  protocol:\n    type: string\n    enum:\n    - email\n    - twitter\n  status:\n    type: string\n    enum:\n    - active\n    - inactive\n    - deleted\n  type:\n    type: string\n    enum:\n    - local\n    - remote\n  user_id:\n    type: string\nrequired:\n- identifier\n- protocol\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Notification.yaml",
    "content": "---\ntype: object\nproperties:\n  body:\n    type: string\n  emitter:\n    type: string\n  notif_id:\n    description: notif_id is UUIDv1, including a timestamp\n    type: string\n  reference:\n    type: string\n  \"type\":\n    type: string\n  user_id:\n    type: string\n  children:\n    type: array\n    items:\n      type: object\n      properties:\n        body:\n          type: string\n        emitter:\n          type: string\n        notif_id:\n          description: notif_id is UUIDv1, including a timestamp\n          type: string\n        reference:\n          type: string\n        \"type\":\n          type: string\nadditionalProperties: false"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Organization.yaml",
    "content": "---\ntype: object\nproperties:\n  \"$ref\": NewOrganization.yaml#/properties\n  deleted:\n    type: boolean\n  organization_id:\n    type: string\nrequired:\n- name\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/PI.yaml",
    "content": "---\ntype: object\nproperties:\n  technic:\n    type: integer\n  context:\n    type: integer\n  comportment:\n    type: integer\n  version:\n    type: integer\nadditionalProperties: true\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/PIMessage.yaml",
    "content": "---\ntype: object\nproperties:\n  content:\n    type: integer\n  transport:\n    type: integer\n  social:\n    type: integer\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Participant.yaml",
    "content": "---\ntype: object\nproperties:\n  address:\n    type: string\n  contact_ids:         # may be empty if participant could not be linked to an user's contact\n    type: array\n    items:\n      type: string\n  label:              # 'display-name' for email ; 'real name' for IRC\n    type: string\n  protocol:           # email, twitter…\n    type: string\n  type:               # for now, enum is RFC5322's field types for emails.\n    type: string\n    enum:\n    - To\n    - Cc\n    - Bcc\n    - From\n    - Reply-To\n    - Sender\nrequired:\n- address\n- type\n- protocol\nadditionalProperties: false\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/ParticipantSuggest.yaml",
    "content": "---\ntype: object\nproperties:\n  address:\n    type: string\n    description: could be empty if suggestion is a contact (or should we automatically put preferred identity's address ?)\n  contact_id:\n    type: string\n    description: contact's ID if any\n  label:\n    type: string\n    description: name of contact or <display-name> in case of an address returned from participants lookup, if any\n  protocol:\n    type: string\n    description: email, IRC…\n  source:\n    type: string\n    description: \"'participant' or 'contact', ie from where this suggestion came from\"\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Phone.yaml",
    "content": "---\ntype: object\nproperties:\n  \"$ref\": NewPhone.yaml#/properties\n  phone_id:\n      type: string\n  normalized_number:\n    type: string\nrequired:\n- number\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/PostalAddress.yaml",
    "content": "---\ntype: object\nproperties:\n  \"$ref\": NewPostalAddress.yaml#/properties\n  address_id:\n    type: string\nrequired:\n- city\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/PrivacyFeatures.yaml",
    "content": "---\ntype: object\nproperties: {}\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/PublicKey.yaml",
    "content": "---\ntype: object\nproperties:\n  \"$ref\": NewPublicKey.yaml#/properties\n  alg:\n    type: string\n  crv:\n    type: string\n  date_insert:\n    type: string\n    format: date-time\n  date_update:\n    type: string\n    format: date-time\n  emails:\n    type: array\n    items:\n      type: string\n  expire_date:\n    type: string\n    format: date-time\n  fingerprint:\n    type: string\n  kty:\n    type: string\n  key_id:\n    type: string\n  type:\n    type: string\n  resource_id:\n    type: string\n  resource_type:\n    type: string\n  size:\n    type: integer\n    format: int32\n  use:\n    type: string\n  user_id:\n    type: string\n  x:\n    type: integer\n    format: int64\n  y:\n    type: integer\n    format: int64\nrequired:\n- key\n- label\n- key_id\n- resource_id\n- user_id\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/SearchResponse.yaml",
    "content": "---\ntype: object\nproperties:\n  id:\n    type: string\n    description: id of document (shortcut to fetch full doc from db if needed).\n  score:\n    type: number\n    format: float\n    description: how confident is our index for this document to match the request. Higher is better. Documents are sorted on this field by default.\n  highlights:\n    type: object\n    description: Field names where terms of request where found. Each key maps to an array of excerpts.\n  document:\n    type: object\n    description: full document returned from index.\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Settings.yaml",
    "content": "---\ntype: object\nproperties:\n  default_locale:\n    type: string\n    default: fr-FR\n  message_display_format:\n    type: string\n    default: rich_text\n  contact_display_order:\n    type: string\n    default: given_name\n  contact_display_format:\n    type: string\n    default: family_name, given_name\n  notification_enabled:\n    type: boolean\n    default: true\n  notification_message_preview:\n    type: string\n    default: always\n  notification_sound_enabled:\n    type: boolean\n    default: false\n  notification_delay_disappear:\n    type: integer\n    default: 10\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/ShortContact.yaml",
    "content": "---\ntype: object\nproperties:\n  contact_id:\n    type: string\n  family_name:\n    type: string\n  given_name:\n    type: string\n  tags:\n    type: array\n    items:\n      type: string\n  title:\n    type: string\nrequired:\n- contact_id\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/SocialIdentity.yaml",
    "content": "---\ntype: object\nproperties:\n  \"$ref\": NewSocialIdentity.yaml#/properties\n  social_id:\n      type: string\nrequired:\n- name\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/Tag.yaml",
    "content": "---\ntype: object\nproperties:\n  \"$ref\": NewTag.yaml#/properties\n  date_insert:\n    type: string\n    format: date-time\n  type:\n    type: string\n    enum:\n    - user\n    - system\nadditionalProperties: false\n\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/User.yaml",
    "content": "---\ntype: object\nproperties:\n  contact:\n    \"$ref\": Contact.yaml\n  date_insert:\n    type: string\n    format: date-time\n  family_name:\n    type: string\n  given_name:\n    type: string\n  name:\n    type: string\n  password:\n    type: string\n  params:\n    type: object\n  privacy_features:\n    type: object\n    \"$ref\": PrivacyFeatures.yaml\n  pi:\n    type: object\n    \"$ref\": PI.yaml\n  user_id:\n    type: string\n  recovery_email:\n    type: string\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/objects/UserIdentity.yaml",
    "content": "---\ntype: object\nproperties:\n  credentials:    # set of key/value strings\n    type: object\n    additionalProperties:\n      type: string\n  display_name:\n    type: string\n  identity_id:\n    type: string\n  identifier:\n    type: string\n  infos:          # set of key/value strings\n    type: object\n    additionalProperties:\n      type: string\n  last_check:\n    type: string\n    format: date-time\n  protocol:\n    type: string\n    enum:\n    - email\n    - twitter\n  status:\n    type: string\n    enum:\n    - active\n    - inactive\n    - deleted\n  type:\n    type: string\n    enum:\n    - local\n    - remote\n  user_id:\n    type: string\nadditionalProperties: false\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/authentications.yaml",
    "content": "---\nauthentications:\n  post:\n    description: Returns an auth token to build basicAuth for the provided credentials\n    tags:\n    - users\n    security: []\n    consumes:\n    - application/json\n    parameters:\n    - name: authentication\n      in: body\n      required: true\n      schema:\n        \"$ref\": \"../objects/Authentication.yaml\"\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Successful authentication\n        schema:\n          type: object\n          properties:\n            username:\n              type: string\n            user_id:\n              type: string\n              description: the user_id makes the 'username' for basicAuth\n            tokens:\n              type: object\n              properties:\n                access_token:\n                  type: string\n                  description: the access_token makes the 'password' for basicAuth\n                expires_in:\n                  type: integer\n                  format: int32\n                expires_at:\n                  type: string\n                refresh_token:\n                  type: string\n            device:\n              type: object\n              properties:\n                device_id:\n                  type: string\n                  description: Identifier of the device used for authentication\n                status:\n                  type: string\n                  description: Device status\n          additionalProperties: false\n      '401':\n        description: Authentication error\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/contacts.yaml",
    "content": "---\ncontacts:\n  get:\n    description: Returns contacts belonging to current user according to given parameters\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    parameters:\n    - name: X-Caliopen-PI\n      in: header\n      required: true\n      description: The PI range requested in form of `1;100`\n      type: string\n      default: 1;100\n    - name: X-Caliopen-IL\n      in: header\n      required: true\n      description: The Importance Level range requested in form of `-10;10`\n      type: string\n      default: -10;10\n    - name: limit\n      in: query\n      required: false\n      type: integer\n      description: number of contacts to return per page\n    - name: offset\n      in: query\n      type: integer\n      required: false\n      description: number of pages to skip from the response\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Contacts returned\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of contacts found for current user for the given\n                parameters\n            contacts:\n              type: array\n              items:\n                \"$ref\": \"../objects/Contact.yaml\"\n  post:\n    description: Create a new contact for the logged-in user\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: contact\n      in: body\n      description: the contact to create\n      schema:\n        \"$ref\": \"../objects/NewContact.yaml\"\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Contact created\n        schema:\n          type: object\n          properties:\n            location:\n              type: string\n              description: url to retrieve new contact's infos at /contacts/{contact_id}\ncontacts_{contact_id}:\n  get:\n    description: Returns a contact\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Contact found\n        schema:\n          \"$ref\": \"../objects/Contact.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: Delete a contact\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    responses:\n      '204':\n        description: Contact deleted\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  patch:\n    description: update a contact with rfc5789 and rfc7396 specifications\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n      schema:\n        type: object\n        properties:\n          \"$ref\": \"../objects/Contact.yaml#/properties\"\n          current_state:\n            type: object\n            properties:\n              \"$ref\": \"../objects/Contact.yaml#/properties\"\n        required:\n        - current_state\n    consumes:\n    - application/json\n    responses:\n      '204':\n        description: Update successful. No body is returned.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden patch. Server is refusing to apply the given patch's\n          properties to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but patch was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/contactsV2.yaml",
    "content": "---\ncontacts:\n  get:\n    description: Returns contacts list for current user according to given params\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    parameters:\n    - name: X-Caliopen-PI\n      in: header\n      required: true\n      description: The PI range requested in form of `1;100`\n      type: string\n      default: 1;100\n    - name: X-Caliopen-IL\n      in: header\n      required: true\n      description: The Importance Level range requested in form of `-10;10`\n      type: string\n      default: -10;10\n    - name: limit\n      in: query\n      required: false\n      type: integer\n      description: number of contacts to return per page\n    - name: offset\n      in: query\n      type: integer\n      required: false\n      description: number of pages to skip from the response\n    - name: uri\n      in: query\n      type: string\n      required: false\n      description: return contact that has this uri embedded, if any\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Contacts list\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of contacts found for current user for the given\n                parameters\n            contacts:\n              type: array\n              items:\n                \"$ref\": \"../objects/Contact.yaml\"\n  post:\n    description: Create a new contact for the logged-in user\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: contact\n      in: body\n      description: the contact to create\n      schema:\n        \"$ref\": \"../objects/NewContact.yaml\"\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Contact created\n        schema:\n          type: object\n          properties:\n            location:\n              type: string\n              description: url to retrieve new contact's infos at /contacts/{contact_id}\n            contact_id:\n              type: string\ncontacts_{contact_id}:\n  get:\n    description: Returns a contact\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Contact found\n        schema:\n          \"$ref\": \"../objects/Contact.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: Delete a contact\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    responses:\n      '204':\n        description: Contact deleted\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  patch:\n    description: update a contact with rfc5789 and rfc7396 specifications\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n      schema:\n        type: object\n        properties:\n          \"$ref\": \"../objects/Contact.yaml#/properties\"\n          current_state:\n            type: object\n            properties:\n              \"$ref\": \"../objects/Contact.yaml#/properties\"\n        required:\n        - current_state\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: Update successful. No body is returned.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden patch. Server is refusing to apply the given patch's\n          properties to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but patch was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\ncontacts_{contact_id}_identities:\n  get:\n    description: returns a list of contact's identities\n    tags:\n    - contacts\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    produces:\n    - application/json\n    responses:\n      200:\n        description: Contact's identities returned\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of identities found for the contact\n            contact_identities:\n              type: array\n              items:\n                \"$ref\": \"../objects/ContactIdentity.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      404:\n        description: Contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  patch:\n    description: update tags list for contact\n    tags:\n    - contacts\n    - tags\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n      schema:\n        type: object\n        properties:\n          \"$ref\": \"../objects/Contact.yaml#/properties\"\n          current_state:\n            type: object\n            properties:\n              \"$ref\": \"../objects/Contact.yaml#/properties\"\n        required:\n        - current_state\n    consumes:\n    - application/json\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: Contact's tags updated. Empty body.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden, server is refusing to apply payload to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but payload was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '424':\n        description: server failed to execute the request\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\ncontacts_{contact_id}_tags:\n  patch:\n    description: update tags list for contact\n    tags:\n    - contacts\n    - tags\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n      schema:\n        type: object\n        properties:\n          tags:\n            type: array\n            items:\n              type: string\n          current_state:\n            type: object\n            properties:\n              tags:\n                type: array\n                items:\n                  type: string\n        required:\n        - tags\n        - current_state\n    consumes:\n    - application/json\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: Contact's tags updated. Empty body.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden, server is refusing to apply payload to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but payload was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '424':\n        description: server failed to execute the request\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\ncontacts_{contact_id}_publickeys:\n  post:\n    description: Add a pgp public key to a contact\n    tags:\n    - pgp\n    - keys\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    - name: key\n      in: body\n      required: true\n      schema:\n        \"$ref\": \"../objects/NewPublicKey.yaml\"\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Public key created\n        schema:\n          type: object\n          properties:\n            location:\n              type: string\n              description: url to retrieve public key /contacts/{contact_id}/publickeys/{publickey_id}\n            publickey_id:\n              type: string\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: Unprocessable entity. Parameters were valid but the server was\n          unable to create the key (probably because of a semantically erroneous\n          document)\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n  get:\n    description: Returns all publickeys linked to contact\n    tags:\n    - pgp\n    - keys\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: List of public keys\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of keys found\n            pubkeys:\n              type: array\n              items:\n                \"$ref\": \"../objects/PublicKey.yaml\"\ncontacts_{contact_id}_publickeys_{pubkey_id}:\n  get:\n    description: Retrieve publickey\n    tags:\n    - keys\n    - pgp\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    - name: pubkey_id\n      in: path\n      required: true\n      type: string\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Successful response with json object\n        schema:\n          \"$ref\": \"../objects/PublicKey.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  patch:\n    description: update a public key\n    tags:\n    - keys\n    - pgp\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    - name: pubkey_id\n      in: path\n      type: string\n      required: true\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. Property _label_ is the only one patchable.\n      schema:\n        type: object\n        properties:\n          label:\n            type: string\n          current_state:\n            type: object\n            properties:\n              label:\n                type: string\n            additionalProperties: false\n        additionalProperties: false\n        required:\n        - current_state\n        - label\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: Update successful. No body is returned.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden patch. Server is refusing to apply the given patch's\n          properties to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: public key not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '409':\n        description: json is valid but patch conflicts with db's current state\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but patch was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: Delete a public key\n    tags:\n    - keys\n    - pgp\n    security:\n    - basicAuth: []\n    parameters:\n    - name: contact_id\n      in: path\n      required: true\n      type: string\n    - name: pubkey_id\n      in: path\n      required: true\n      type: string\n    responses:\n      '204':\n        description: Successful deletion\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\""
  },
  {
    "path": "src/backend/defs/rest-api/paths/devices.yaml",
    "content": "---\ndevices:\n  get:\n    description: Returns devices belonging to current user according to given parameters\n    tags:\n      - devices\n    security:\n      - basicAuth: []\n    parameters:\n      - name: X-Caliopen-PI\n        in: header\n        required: true\n        description: The PI range requested in form of `1;100`\n        type: string\n        default: 1;100\n      - name: limit\n        in: query\n        required: false\n        type: integer\n        description: number of devices to return per page\n      - name: offset\n        in: query\n        type: integer\n        required: false\n        description: number of pages to skip from the response\n    produces:\n      - application/json\n    responses:\n      '200':\n        description: Devices returned\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of devices found for current user for the given\n                parameters\n            devices:\n              type: array\n              items:\n                \"$ref\": \"../objects/Device.yaml\"\ndevices_{device_id}:\n  get:\n    description: Returns a device\n    tags:\n      - devices\n    security:\n      - basicAuth: []\n    parameters:\n      - name: device_id\n        in: path\n        required: true\n        type: string\n    produces:\n      - application/json\n    responses:\n      '200':\n        description: Device found\n        schema:\n          \"$ref\": \"../objects/Device.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Device not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: Delete a device\n    tags:\n      - devices\n    security:\n      - basicAuth: []\n    parameters:\n      - name: device_id\n        in: path\n        required: true\n        type: string\n    responses:\n      '204':\n        description: Device deleted\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Device not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  patch:\n    description: update a device with rfc5789 and rfc7396 specifications\n    tags:\n      - devices\n    security:\n      - basicAuth: []\n    consumes:\n      - application/json\n    parameters:\n      - name: device_id\n        in: path\n        required: true\n        type: string\n      - name: patch\n        in: body\n        required: true\n        description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n        schema:\n          type: object\n          properties:\n            \"$ref\": \"../objects/Device.yaml#/properties\"\n            current_state:\n              type: object\n              properties:\n                \"$ref\": \"../objects/Device.yaml#/properties\"\n          required:\n            - current_state\n    produces:\n      - application/json\n    responses:\n      '204':\n        description: Update successful. No body is returned.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden patch. Server is refusing to apply the given patch's\n          properties to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: device not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: patch was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\ndevices_{device_id}_actions:\n  post:\n    description: Route to receive orders to trigger actions on a device\n    tags:\n      - devices\n    security:\n      - basicAuth: []\n    consumes:\n      - application/json\n    parameters:\n      - name: device_id\n        in: path\n        type: string\n        required: true\n      - name: actions\n        in: body\n        required: true\n        schema:\n          \"$ref\": \"../objects/Actions.yaml\"\n    produces:\n      - application/json\n    responses:\n      '204':\n        description: action(s) successfully executed. Nothing returned.\n        schema:\n          \"$ref\": \"../objects/MessageV2.yaml\"\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden action. Server is refusing to execute the given action(s)\n          to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Device not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but action was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '424':\n        description: execution of action failed.\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nvalidate_device_{token}:\n  get:\n    description: complete device validation if token is valid\n    tags:\n      - devices\n    security:\n      - basicAuth: []\n    parameters:\n      - name: token\n        in: path\n        required: true\n        type: string\n    produces:\n      - application/json\n    responses:\n      '204':\n        description: Route is valid. No body is returned.\n      '404':\n        description: Token not found or invalid\n        schema:\n          \"$ref\": \"../objects/Error.yaml\""
  },
  {
    "path": "src/backend/defs/rest-api/paths/discussions.yaml",
    "content": "---\ndiscussions:\n  get:\n    description: Returns the list of discussions for current user according to given\n      filter\n    tags:\n    - discussions\n    security:\n    - basicAuth: []\n    parameters:\n    - name: X-Caliopen-PI\n      in: header\n      required: true\n      description: The PI range requested in form of `0;100`\n      type: string\n      default: 0;100\n    - name: X-Caliopen-IL\n      in: header\n      required: true\n      description: The Importance Level range requested in form of `-10;10`\n      type: string\n      default: -10;10\n    - name: limit\n      in: query\n      required: false\n      type: integer\n      description: number of discussions to return per page\n    - name: offset\n      in: query\n      type: integer\n      required: false\n      description: number of discussions to skip for pagination\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Discussions returned\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of discussions found for current user for the given\n                parameters\n            discussions:\n              type: array\n              items:\n                \"$ref\": \"../objects/Discussion.yaml\"\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\ndiscussions_{discussion_id}:\n  get:\n    description: Returns metadata of a discussion\n    tags:\n    - discussions\n    security:\n    - basicAuth: []\n    parameters:\n    - name: discussion_id\n      in: path\n      required: true\n      type: string\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Discussion found\n        schema:\n          \"$ref\": \"../objects/Discussion.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Discussion not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/files.yaml",
    "content": "---\nfiles:\n  post:\n    description: Upload a file to server\n    tags:\n    - files\n    security:\n    - basicAuth: []\n    consumes:\n    - multipart/form-data\n    parameters:\n    - name: file\n      in: form\n      required: true\nfiles_{file_id}:\n  get:\n    description: Get file's metadata\n    tags:\n    - files\n    security:\n    - basicAuth: []\n    parameters:\n    - name: file_id\n      in: path\n      required: true\n      type: string\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: the raw message\n        schema:\n          type: string\nfiles_{file_id}_download:\n  get:\n    description: Download file from server\n    tags:\n    - files\n    security:\n    - basicAuth: []\n    parameters:\n    - name: file_id\n      in: path\n      required: true\n      type: string\n    produces: [] # permissive because a large set of file types could be returned.\n    responses:\n      '200':\n        description: the raw message\n        schema:\n          type: string\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/hashdiscussion.yaml",
    "content": "---\nhashdiscussion:\n  post:\n    description: Returns the hash of a discussion for a list of participants\n    tags:\n    - compute\n    security:\n    - basicAuth: []\n    parameters:\n    - name: participants\n      in: body\n      type: array\n      items:\n        \"$ref\": \"../objects/Participant.yaml\"\n      required: true\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Computed discussion information\n        schema:\n          type: object\n          properties:\n            hash:\n              type: string\n              description: participants informations hash for discussion lookup\n            discussion_id:\n              type: string\n              description: id of existing discussion for given participants\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/identities.yaml",
    "content": "---\nidentities_locals:\n  get:\n    description: returns the list of user's local identities\n    tags:\n    - identities\n    security:\n    - basicAuth: []\n    parameters: []\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Local identities returned\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of local identities found for user\n            local_identities:\n              type: array\n              items:\n                \"$ref\": \"../objects/UserIdentity.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n\nidentities_remotes:\n  get:\n    description: returns the list of user's remote identities, or filtered if query param\n    tags:\n    - identities\n    security:\n    - basicAuth: []\n    parameters:\n    - name: pending\n      in: query\n      type: string\n      required: false\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Remote identities returned\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of remote identities found for user\n            remote_identities:\n              type: array\n              items:\n                \"$ref\": \"../objects/UserIdentity.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  post:\n    description: create a new remote identity for user\n    tags:\n    - identities\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: identity\n      in: body\n      required: true\n      schema:\n        \"$ref\": \"../objects/NewUserIdentity.yaml\"\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Remote identity created\n        schema:\n          type: object\n          properties:\n            location:\n              type: string\n              description: url to retrieve new remote identity's at identities/remotes/{identifier}\n            identifier:\n              type: string # url escaped identifier\n\nidentities_remotes_{identifier}:\n  get:\n    description: returns a remote identity belonging to user\n    tags:\n    - identities\n    security:\n    - basicAuth: []\n    parameters:\n    - name: identifier # url escaped identifier\n      in: path\n      type: string\n      required: true\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Remote identity returned\n        schema:\n          \"$ref\": \"../objects/UserIdentity.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Remote identity not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: Delete a remote identity belonging to user\n    tags:\n    - identities\n    security:\n    - basicAuth: []\n    parameters:\n    - name: identifier # url escaped identifier\n      in: path\n      type: string\n      required: true\n    responses:\n      '204':\n        description: Remote identity deleted\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Contact not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  patch:\n    description: update a remote identity with rfc5789 and rfc7396 specifications\n    tags:\n    - identities\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: identifier # url escaped identifier\n      in: path\n      type: string\n      required: true\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n      schema:\n        type: object\n        properties:\n          \"$ref\": \"../objects/UserIdentity.yaml#/properties\"\n          current_state:\n            type: object\n            properties:\n              \"$ref\": \"../objects/UserIdentity.yaml#/properties\"\n        required:\n        - current_state\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: Update successful. No body is returned.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden patch. Server is refusing to apply the given patch's\n          properties to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: identity not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but patch was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\""
  },
  {
    "path": "src/backend/defs/rest-api/paths/imports.yaml",
    "content": "imports:\n  post:\n    consumes:\n    - multipart/form-data\n    parameters:\n    - name: file\n      in: formData\n      description: the vcard file to upload\n      type: file\n      required: true\n    security:\n    - basicAuth: []\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Import successful\n      '400':\n        description: Syntax error\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: File valid but we can create the new contact\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/me.yaml",
    "content": "---\nme:\n  get:\n    description: \"Gets `user + contact` objects for current logged-in user\"\n    tags:\n    - users\n    security:\n    - basicAuth: []\n    parameters:\n    - name: X-Caliopen-PI\n      in: header\n      required: true\n      description: The PI range requested in form of `1;100`\n      type: string\n      default: 1;100\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Successful response with json object\n        schema:\n          \"$ref\": \"../objects/User.yaml\"\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/messages.yaml",
    "content": "---\nmessages:\n  post:\n    description: Create a new Message (draft) for an user\n    tags:\n    - messages\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: message\n      in: body\n      required: true\n      schema:\n        \"$ref\": \"../objects/NewMessageV2.yaml\"\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Draft creation completed\n        schema:\n          type: object\n          properties:\n            location:\n              type: string\n              description: url to retrieve draft at /messages/{message_id}\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: Unprocessable entity. Parameters were valid but the server was\n          unable to create the draft (probably because of a semantically erroneous\n          document, like an empty draft)\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\nmessages_{message_id}:\n  patch:\n    description: update a draft with rfc5789 and rfc7396 specifications\n    tags:\n    - messages\n    security:\n    - basicAuth: []\n    parameters:\n    - name: message_id\n      in: path\n      type: string\n      required: true\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n      schema:\n        type: object\n        properties:\n          \"$ref\": \"../objects/MessageV2.yaml#/properties\"\n          current_state:\n            type: object\n            properties:\n              \"$ref\": \"../objects/MessageV2.yaml#/properties\"\n        required:\n        - current_state\n    consumes:\n    - application/json\n    responses:\n      '204':\n        description: Update successful. No body is returned.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden patch. Server is refusing to apply the given patch's\n          properties to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: message not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but patch was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: Delete a message belonging to an user\n    tags:\n    - messages\n    security:\n    - basicAuth: []\n    parameters:\n    - name: message_id\n      in: path\n      required: true\n      type: string\n    responses:\n      '204':\n        description: Successful deletion. No body is returned\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden action. Server is refusing to delete this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: message not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/messagesV2.yaml",
    "content": "---\nmessages:\n  get:\n    description: Returns the list of messages for current user according to given\n      parameters/filter\n    tags:\n      - messages\n    security:\n      - basicAuth: []\n    parameters:\n      - name: X-Caliopen-PI\n        in: header\n        required: true\n        description: The PI range requested in form of `0;100`\n        type: string\n        default: 0;100\n      - name: X-Caliopen-IL\n        in: header\n        required: true\n        description: The Importance Level range requested in form of `-10;10`\n        type: string\n        default: -10;10\n      - name: discussion_id\n        in: query\n        description: filter messages belonging to a specific discussion\n        required: false\n        type: string\n      - name: limit\n        in: query\n        required: false\n        type: integer\n        description: number of messages to return per page\n      - name: offset\n        in: query\n        type: integer\n        required: false\n        description: number of pages to skip from the response\n      - name: msg_id\n        in: query\n        type: string\n        required: false\n        description: if provided with range[] param, specify a message_id around which messages will be fetched\n      - name: range[]\n        in: query\n        type: string\n        required: false\n        description: boundaries param if message_id param is provided [before, after]\n    produces:\n      - application/json\n    responses:\n      '200':\n        description: Messages returned\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of messages found for user for the given parameters\n            messages:\n              type: array\n              items:\n                \"$ref\": \"../objects/MessageV2.yaml\"\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nmessages_{message_id}:\n  get:\n    description: returns a message\n    tags:\n      - messages\n    security:\n      - basicAuth: []\n    parameters:\n      - name: message_id\n        in: path\n        type: string\n        required: true\n    produces:\n      - application/json\n    responses:\n      '200':\n        description: Message found\n        schema:\n          \"$ref\": \"../objects/MessageV2.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Message not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nmessages_{message_id}_actions:\n  post:\n    description: 'send an order to execute one (or many) action(s) for the given message\n      : send, etc. A successful execution of the action will probably modify one or\n      more message''s attribute(s)'\n    tags:\n      - messages\n    security:\n      - basicAuth: []\n    consumes:\n      - application/json\n    parameters:\n      - name: message_id\n        in: path\n        type: string\n        required: true\n      - name: actions\n        in: body\n        required: true\n        schema:\n          \"$ref\": \"../objects/Actions.yaml\"\n    produces:\n      - application/json\n    responses:\n      '200':\n        description: action(s) successfully executed. Message is sent back with its\n          modified attributes (if any)\n        schema:\n          \"$ref\": \"../objects/MessageV2.yaml\"\n      '204':\n        description: action(s) successfully executed. Nothing returned.\n        schema:\n          \"$ref\": \"../objects/MessageV2.yaml\"\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden action. Server is refusing to execute the given action(s)\n          to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Message not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but action was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '424':\n        description: execution of action failed.\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nmessages_{message_id}_attachments:\n  post:\n    description: (for draft only) upload a file to server and add attachment reference to the draft.\n    tags:\n      - attachments\n      - messages\n    security:\n      - basicAuth: []\n    consumes:\n      - multipart/form-data\n    parameters:\n      - name: message_id\n        in: path\n        required: true\n        type: string\n      - name: attachment\n        in: formData\n        description: the attachment file to upload\n        type: file\n        required: true\n    produces:\n      - application/json\n    responses:\n      '201':\n        description: File uploaded sucessfully. Backend sends back attachment's temp_id.\n        schema:\n          type: object\n          properties:\n            temp_id:\n              type: string\n              description: attachment's temporary id. temp_id exists as long as message has 'draft' status.\n      '400':\n        description: bad request\n      '401':\n        description: Unauthorized\n      '404':\n        description: Not Found\n      '413':\n        description: Request entity too large\n      '415':\n        description: Unsupported media type\nmessages_{message_id}_attachments_{attachment_id}:\n  get:\n    description: Download file from server\n    tags:\n      - messages\n      - attachments\n    security:\n      - basicAuth: []\n    parameters:\n      - name: message_id\n        in: path\n        required: true\n        type: string\n      - name: attachment_id\n        description: attachment position within message\n        in: path\n        required: true\n        type: string\n    produces: [] # permissive because a large set of file types could be returned.\n    responses:\n      '200':\n        description: the attachment, in the 'content-transfer-encoding' format of the part.\n        schema:\n          type: file\n      '400':\n        description: bad request\n      '401':\n        description: Unauthorized\n      '404':\n        description: message or attachment not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: (for drafts only) delete temporary file and remove attachment reference from the draft.\n    tags:\n      - messages\n      - attachments\n    security:\n      - basicAuth: []\n    parameters:\n      - name: message_id\n        in: path\n        required: true\n        type: string\n      - name: attachment_id\n        description: attachment's temporary id.\n        in: path\n        required: true\n        type: string\n    responses:\n      '204':\n        description: Successful deletion. No body is returned\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden action. Server is refusing to delete this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: message or attachment not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nmessages_{message_id}_tags:\n  patch:\n    description: update tags list for message\n    tags:\n      - messages\n      - tags\n    security:\n      - basicAuth: []\n    parameters:\n      - name: message_id\n        in: path\n        required: true\n        type: string\n      - name: patch\n        in: body\n        required: true\n        description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n        schema:\n          type: object\n          properties:\n            tags:\n              type: array\n              items:\n                type: string\n            current_state:\n              type: object\n              properties:\n                tags:\n                  type: array\n                  items:\n                    type: string\n          required:\n            - tags\n            - current_state\n    consumes:\n      - application/json\n    produces:\n      - application/json\n    responses:\n      '204':\n        description: message's tags updated. Empty body.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden, server is refusing to apply payload to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: message not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but payload was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '424':\n        description: server failed to execute the request\n        schema:\n          \"$ref\": \"../objects/Error.yaml\""
  },
  {
    "path": "src/backend/defs/rest-api/paths/notifications.yaml",
    "content": "---\nnotifications:\n  get:\n    description: Returns pending notifications\n    tags:\n    - notifications\n    security:\n    - basicAuth: []\n    parameters:\n    - name: from\n      in: query\n      required: false\n      description: oldest timestamp or uuid to retrieve (older notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.\n      type: string\n    - name: to\n      in: query\n      required: false\n      description: earlest timestamp or uuid to retrieve (earler notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.\n      type: string\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: json document with an array of pending notifications\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n            notifications:\n              type: array\n              items:\n                \"$ref\": \"../objects/Notification.yaml\"\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: Delete pending notifications by time range\n    tags:\n    - notifications\n    security:\n    - basicAuth: []\n    parameters:\n    - name: until\n      in: query\n      required: false\n      description: delete all notifications with a timestamp before `until` time (RFC3339 format)\n      type: string\n      format: date-time\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: deletions executed, no body returned.\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nnotifications_{notification_id}:\n  get:\n    description: Returns a notification\n    tags:\n    - notifications\n    security:\n    - basicAuth: []\n    parameters:\n    - name: notification_id\n      in: path\n      type: string\n      required: true\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Notification found\n        schema:\n          \"$ref\": \"../objects/Notification.yaml\"\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Message not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: Delete a notification\n    tags:\n    - notifications\n    security:\n    - basicAuth: []\n    parameters:\n    - name: notification_id\n      in: path\n      type: string\n      required: true\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: deletion executed, no body returned.\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/participants.yaml",
    "content": "---\nparticipants_suggest:\n  get:\n    description: Returns a list of suggestions according to given parameters/filter. Search is performed within current user's indexes (messages & contacts).\n    tags:\n    - participants\n    - suggest\n    security:\n    - basicAuth: []\n    parameters:\n    - name: context\n      in: query\n      description: current user's context (to optimize suggest relevance)\n      required: true\n      type: string\n      enum:\n      - msg_compose\n    - name: q\n      in: query\n      description: a string (3 chars at least) from which to perform the suggestion search\n      required: true\n      type: string\n      minLength: 3\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: a list of suggestions for user to pick-up an identity\n        schema:\n          type: array\n          items:\n            \"$ref\": \"../objects/ParticipantSuggest.yaml\"\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n\nparticipants_discussion:\n  post:\n    description: Returns discussion related to a list of participants\n    tags:\n    - participants\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: participants\n      in: body\n      schema:\n        type: array\n        items:\n          \"$ref\": \"../objects/Participant.yaml\"\n      required: true\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Computed discussion information\n        schema:\n          type: object\n          properties:\n            hash:\n              type: string\n              description: participants informations hash for discussion lookup\n            discussion_id:\n              type: string\n              description: id of existing discussion for given participants\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/passwords.yaml",
    "content": "---\npasswords_reset:\n  post:\n    description: Route to receive a \"reset password\" request from an anonymous user.\n    tags:\n      - passwords\n    security: []\n    consumes:\n      - application/json\n    parameters:\n      - name: reset_request\n        in: body\n        required: true\n        description: data the user has typed into the reset form\n        schema:\n          type: object\n          properties:\n            recovery_email:\n              type: string\n            username:\n              type: string\n          additionalProperties: false\n    produces:\n      - application/json\n    responses:\n      '204':\n        description: Request successfully handled. No body is returned.\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: user not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '424':\n        description: Parameters were valid but the server was unable to handle the request.\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\npasswords_reset_{token}:\n  get:\n    description: Returns an auth token to build basicAuth, if the token in path is valid\n    tags:\n      - passwords\n    security: []\n    parameters:\n      - name: token\n        in: path\n        required: true\n        type: string\n    produces:\n      - application/json\n    responses:\n      '204':\n        description: Route is valid. No body is returned.\n      '404':\n        description: Token not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  post:\n    description: User posts a new password\n    tags:\n      - passwords\n    security: []\n    consumes:\n      - application/json\n    parameters:\n      - name: token\n        in: path\n        type: string\n        required: true\n      - name: new_password\n        in: body\n        required: true\n        description: A simple json with the new password as a string\n        schema:\n          type: object\n          properties:\n            password:\n              type: string\n          additionalProperties: false\n          required:\n            - password\n    produces:\n      - application/json\n    responses:\n      '204':\n        description: Password update successful. No body is returned.\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '424':\n        description: Parameters were valid but the server was unable to change the password.\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\""
  },
  {
    "path": "src/backend/defs/rest-api/paths/providers.yaml",
    "content": "---\nproviders:\n  get:\n    description: returns list of supported external providers for remote identities creation\n    tags:\n    - identities\n    - providers\n    security:\n    - basicAuth: []\n    parameters: []\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: List of supported providers\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n            providers:\n              type: array\n              items:\n                type: string\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nproviders_{provider_name}:\n  get:\n    description: returns provider's properties, notably the url to call for initiating Oauth process\n    tags:\n    - identities\n    - providers\n    security:\n    - basicAuth: []\n    parameters:\n    - name: provider_name\n      in: path\n      type: string\n      required: true\n    - name: identifier\n      in: query\n      type: string\n      required: false\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: providers infos\n        schema:\n          type: object\n          properties:\n            name:\n              type: string\n            oauth_request_url:\n              type: string\n            protocol:\n              type: string\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nproviders_{provider_name}_callback:\n  get:\n    description: url registered at provider to which its API will redirect user after user authentication\n    tags:\n    - identities\n    - providers\n    security: []\n    parameters:\n    - name: provider_name\n      in: path\n      type: string\n      required: true\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: a simple message for user to inform Oauth process completion\n        schema:\n          type: object\n          properties:\n            success:\n              type: boolean\n            message:\n              type: string\noauth_test:\n  get:\n    description: serves an index to test Oauth processes\n    tags:\n    - providers\n    security: []\n    parameters: []\n    produces:\n    - text/html\n    responses:\n      '200':\n        description: index to trigger oauth popup\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/raws.yaml",
    "content": "---\nraws_{raw_msg_id}:\n  get:\n    description: Returns a raw message\n    tags:\n    - messages\n    security:\n    - basicAuth: []\n    parameters:\n    - name: raw_msg_id\n      in: path\n      required: true\n      type: string\n    produces:\n    - text/plain\n    responses:\n      '200':\n        description: the raw message\n        schema:\n          type: string\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/search.yaml",
    "content": "---\nsearch:\n  get:\n    description: Simple API to execute full-text searches within user's indexes. A more complexe API will be available with a POST verb.\n    tags:\n    - messages\n    - contacts\n    - search\n    security:\n    - basicAuth: []\n    parameters:\n    - name: X-Caliopen-IL\n      in: header\n      required: true\n      description: The Importance Level range requested in form of `-10;10`\n      type: string\n      default: -10;10\n    - name: term\n      in: query\n      description: the search string\n      required: true\n      type: string\n      minLength: 3\n    - name: field\n      in: query\n      description: name of a field on which to perform the search. If omitted defaults to « _all ».\n      required: false\n      type: string\n    - name: doctype\n      in: query\n      description: type of documents to narrow the search to.\n      required: false\n      type: string\n      enum:\n      - message\n      - contact\n      - \"\"\n    - name: limit\n      in: query\n      required: false\n      type: integer\n      description: number of documents to return per page, but only if param «type» is present.\n    - name: offset\n      in: query\n      type: integer\n      required: false\n      description: number of pages to skip from the response, but only if param «type» is present.\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: an object holding an array of documents found. Docs are assembled by type.\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: total number of documents found\n            message_hits:\n              type: object\n              properties:\n                total:\n                  type: integer\n                  format: int32\n                  description: total number of messages found\n                messages:\n                  type: array\n                  description: at most 5 documents are returned if query param « type » is not specified.\n                  items:\n                    \"$ref\": \"../objects/SearchResponse.yaml\"\n            contact_hits:\n              type: object\n              properties:\n                total:\n                  type: integer\n                  format: int32\n                  description: total number of contacts found\n                contacts:\n                  type: array\n                  description: at most 5 documents are returned if query param « type » is not specified.\n                  items:\n                    \"$ref\": \"../objects/SearchResponse.yaml\"\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  post:\n    description: Not yet implemented. Future route for more complexe searches.\n    tags:\n    - contacts\n    - messages\n    - search\n    security:\n    - basicAuth: []\n    responses:\n      '501':\n        description: route not implemented.\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/settings.yaml",
    "content": "---\nsettings:\n  get:\n    description: Returns settings belonging to current user\n    tags:\n    - settings\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Settings returned\n        schema:\n          \"$ref\": \"../objects/Settings.yaml\"\n\n  patch:\n    description: Update settings with rfc5789 and rfc7396 specifications\n    tags:\n    - settings\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n      schema:\n        type: object\n        properties:\n          \"$ref\": \"../objects/Settings.yaml#/properties\"\n          current_state:\n            type: object\n            properties:\n              \"$ref\": \"../objects/Settings.yaml#/properties\"\n    responses:\n      '204':\n        description: Update successful. No body is returned.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: patch was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/tags.yaml",
    "content": "---\ntags:\n  get:\n    description: Returns tags visible to current user according to given parameters\n    tags:\n    - tags\n    security:\n    - basicAuth: []\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Tags returned\n        schema:\n          type: object\n          properties:\n            total:\n              type: integer\n              format: int32\n              description: number of tags found for user for the given parameters\n            tags:\n              type: array\n              items:\n                \"$ref\": \"../objects/Tag.yaml\"\n  post:\n    description: Create a new Tag for an user\n    tags:\n    - tags\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: tag\n      in: body\n      required: true\n      schema:\n        \"$ref\": \"../objects/NewTag.yaml\"\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: User tag creation completed\n        schema:\n          type: object\n          properties:\n            location:\n              type: string\n              description: url to retrieve new tag's infos at /tags/{name}\n      '400':\n        description: malform request\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: Unprocessable entity. Parameters were valid but the server was\n          unable to create the tag (probably because of a semantically erroneous\n          document, like an empty tag)\n        schema:\n          type: object\n          \"$ref\": \"../objects/Error.yaml\"\ntags_{tag_id}:\n  get:\n    description: Retrieve tag infos\n    tags:\n    - tags\n    security:\n    - basicAuth: []\n    parameters:\n    - name: tag_id\n      in: path\n      required: true\n      type: string\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: Successful response with json object\n        schema:\n          \"$ref\": \"../objects/Tag.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  patch:\n    description: update a tag\n    tags:\n    - tags\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: tag_id\n      in: path\n      type: string\n      required: true\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n      schema:\n        type: object\n        properties:\n          label:\n            type: string\n          importance_level:\n            type: integer\n            format: int32\n          current_state:\n            type: object\n            properties:\n              label:\n                type: string\n              importance_level:\n                type: integer\n                format: int32\n            additionalProperties: false\n        additionalProperties: false\n        required :\n        - current_state\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: Update successful. No body is returned.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden patch. Server is refusing to apply the given patch's\n          properties to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: tag not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '409':\n        description: json is valid but patch conflicts with db's current state\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but patch was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n  delete:\n    description: Delete a tag belonging to an user\n    tags:\n    - tags\n    security:\n    - basicAuth: []\n    parameters:\n    - name: tag_id\n      in: path\n      required: true\n      type: string\n    responses:\n      '204':\n        description: Successful deletion\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/paths/users.yaml",
    "content": "---\nusers:\n  post:\n    description: Create a new User with provided credentials\n    tags:\n    - users\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: user\n      in: body\n      required: true\n      schema:\n        \"$ref\": \"../objects/NewUser.yaml\"\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: User creation completed\n        schema:\n          type: object\n          properties:\n            location:\n              type: string\n              description: url to retrieve new user's infos at /users/{user_id}\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '406':\n        description: NotAcceptable. Server refuses to fulfill the request\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nusers_{user_id}:\n  patch:\n    description: Partially implemented. Currently only for changing password.\n    tags:\n    - users\n    - password\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: user_id\n      in: path\n      required: true\n      type: string\n    - name: patch\n      in: body\n      required: true\n      description: the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\n      schema:\n        type: object\n        properties:\n          \"$ref\": \"../objects/MessageV2.yaml#/properties\"\n          current_state:\n            type: object\n            properties:\n              \"$ref\": \"../objects/MessageV2.yaml#/properties\"\n        required:\n        - current_state\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: Update successful. No body is returned.\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden patch. Server is refusing to apply the given patch's\n          properties to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: message not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but patch was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nusers_{user_id}_actions:\n  post:\n    description: 'send an order to execute one (or many) action(s) regarding the user\n                       : reset_password, etc. A successful execution of the action will probably modify one or\n                       more user''s attribute(s) ## NOT YET IMPLEMENTED ##'\n    tags:\n    - users\n    - password\n    security:\n    - basicAuth: []\n    consumes:\n    - application/json\n    parameters:\n    - name: user_id\n      in: path\n      type: string\n      required: true\n    - name: actions\n      in: body\n      required: true\n      schema:\n        \"$ref\": \"../objects/Actions.yaml\"\n    produces:\n    - application/json\n    responses:\n      '204':\n        description: action(s) successfully executed. Nothing returned.\n        schema:\n          \"$ref\": \"../objects/MessageV2.yaml\"\n      '400':\n        description: json payload malformed\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '401':\n        description: Unauthorized access\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '403':\n        description: Forbidden action. Server is refusing to execute the given action(s)\n          to this ressource\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '404':\n        description: Message not found\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '422':\n        description: json is valid but action was semantically malformed or unprocessable\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\n      '424':\n        description: execution of action failed.\n        schema:\n          \"$ref\": \"../objects/Error.yaml\"\nusers_isAvailable:\n  get:\n    description: Check if an username is available for creation within Caliopen instance\n    tags:\n    - users\n    - username\n    security: []\n    parameters:\n    - name: username\n      in: query\n      required: true\n      type: string\n    produces:\n    - application/json\n    responses:\n      '200':\n        description: availability state for requested username\n        schema:\n          type: object\n          properties:\n            username:\n              type: string\n            available:\n              type: boolean\n          required:\n          - username\n          - available\n          additionalProperties: false\n      '400':\n        description: malform request (probably missing 'username' query param)\n        schema:\n          type: object\n          properties:\n            username:\n              type: string\n            available:\n              type: boolean\n          required:\n          - username\n          - available\n\n"
  },
  {
    "path": "src/backend/defs/rest-api/swagger-root.yaml",
    "content": "---\nswagger: '2.0'\ninfo:\n  version: 0.2.0\n  title: Caliopen HTTP/REST API\nschemes:\n- http\nhost: localhost:31415\nbasePath: \"/api\"\npaths:\n## user ##\n  \"/v1/authentications\":\n    \"$ref\": paths/authentications.yaml#/authentications\n  \"/v1/me\":\n    \"$ref\": paths/me.yaml#/me\n  \"/v1/users\":\n    \"$ref\": paths/users.yaml#/users\n  \"/v2/users/{user_id}\":\n    \"$ref\": paths/users.yaml#/users_{user_id}\n  \"/v2/users/{user_id}/actions\":\n    \"$ref\": paths/users.yaml#/users_{user_id}_actions\n  \"/v2/username/isAvailable\":\n    \"$ref\": paths/users.yaml#/users_isAvailable\n  \"/v1/settings\":\n     \"$ref\": paths/settings.yaml#/settings\n  \"/v2/identities/locals\":\n    \"$ref\": paths/identities.yaml#/identities_locals\n  \"/v2/identities/remotes\":\n    \"$ref\": paths/identities.yaml#/identities_remotes\n  \"/v2/identities/remotes/{identifier}\":\n    \"$ref\": paths/identities.yaml#/identities_remotes_{identifier}\n  \"/v2/passwords/reset\":\n    \"$ref\": paths/passwords.yaml#/passwords_reset\n  \"/v2/passwords/reset/{token}\":\n    \"$ref\": paths/passwords.yaml#/passwords_reset_{token}\n  \"/v2/providers\":\n    \"$ref\": paths/providers.yaml#/providers\n  \"/v2/providers/{provider_name}\":\n    \"$ref\": paths/providers.yaml#/providers_{provider_name}\n  \"/v2/providers/{provider_name}/callback\":\n    \"$ref\": paths/providers.yaml#/providers_{provider_name}_callback\n  \"/v2/test/oauth\":\n    \"$ref\": paths/providers.yaml#/oauth_test\n## contacts ##\n  \"/v2/contacts\":\n    \"$ref\": paths/contactsV2.yaml#/contacts\n  \"/v2/contacts/{contact_id}\":\n    \"$ref\": paths/contactsV2.yaml#/contacts_{contact_id}\n  \"/v2/contacts/{contact_id}/identities\":\n    \"$ref\": paths/contactsV2.yaml#/contacts_{contact_id}_identities\n  \"/v2/contacts/{contact_id}/publickeys\":\n    \"$ref\": paths/contactsV2.yaml#/contacts_{contact_id}_publickeys\n  \"/v2/contacts/{contact_id}/publickeys/{pubkey_id}\":\n    \"$ref\": paths/contactsV2.yaml#/contacts_{contact_id}_publickeys_{pubkey_id}\n  \"/v2/contacts/{contact_id}/tags\":\n    \"$ref\": paths/contactsV2.yaml#/contacts_{contact_id}_tags\n  \"/v2/imports\":\n    \"$ref\": paths/imports.yaml#/imports\n## messages/discussions ##\n  \"/v2/discussions\":\n    \"$ref\": paths/discussions.yaml#/discussions\n  \"/v2/discussions/{discussion_id}\":\n    \"$ref\": paths/discussions.yaml#/discussions_{discussion_id}\n  \"/v1/messages\":\n    \"$ref\": paths/messages.yaml#/messages\n  \"/v2/messages\":\n    \"$ref\": paths/messagesV2.yaml#/messages\n  \"/v1/messages/{message_id}\":\n    \"$ref\": paths/messages.yaml#/messages_{message_id}\n  \"/v2/messages/{message_id}\":\n    \"$ref\": paths/messagesV2.yaml#/messages_{message_id}\n  \"/v2/messages/{message_id}/tags\":\n    \"$ref\": paths/messagesV2.yaml#/messages_{message_id}_tags\n  \"/v2/messages/{message_id}/actions\":\n    \"$ref\": paths/messagesV2.yaml#/messages_{message_id}_actions\n  \"/v2/messages/{message_id}/attachments\":\n    \"$ref\": paths/messagesV2.yaml#/messages_{message_id}_attachments\n  \"/v2/messages/{message_id}/attachments/{attachment_id}\":\n    \"$ref\": paths/messagesV2.yaml#/messages_{message_id}_attachments_{attachment_id}\n  \"/v1/raws/{raw_msg_id}\":\n    \"$ref\": paths/raws.yaml#/raws_{raw_msg_id}\n## tags ##\n  \"/v2/tags\":\n    \"$ref\": paths/tags.yaml#/tags\n  \"/v2/tags/{tag_id}\":\n    \"$ref\": paths/tags.yaml#/tags_{tag_id}\n## devices ##\n  \"/v2/devices\":\n    \"$ref\": paths/devices.yaml#/devices\n  \"/v2/devices/{device_id}\":\n    \"$ref\": paths/devices.yaml#/devices_{device_id}\n  \"/v2/devices/{device_id}/actions\":\n    \"$ref\": paths/devices.yaml#/devices_{device_id}_actions\n  \"/v2/validate-device/{token}\":\n    \"$ref\": paths/devices.yaml#/validate_device_{token}\n  ## search/suggest ##\n  \"/v2/participants/suggest\":\n    \"$ref\": paths/participants.yaml#/participants_suggest\n  \"/v2/participants/discussion\":\n    \"$ref\": paths/participants.yaml#/participants_discussion\n  \"/v2/search\":\n    \"$ref\": paths/search.yaml#/search\n## notifications\n  \"/v2/notifications\":\n    \"$ref\": paths/notifications.yaml#/notifications\n  \"/v2/notifications/{notification_id}\":\n    \"$ref\": paths/notifications.yaml#/notifications_{notification_id}\nsecurityDefinitions:\n  basicAuth:\n    type: basic\n    description: HTTP Basic Authentication. `Password` is the `access_token` return\n      by /authentications and `Username` is the `user_id` returned by /authentications\n"
  },
  {
    "path": "src/backend/doc/api/README.md",
    "content": "# Full swagger Caliopen ReST API definition\n\n## WARNING\n\nThis swagger.json file is not directly edited, but generated using JSON schema definitions.\n\n## Generate the swagger.json from definitions\n\nDocumentation definitions are splits in [objects definitions](https://github.com/CaliOpen/Caliopen/tree/master/src/backend/defs/rest-api/objects) and [route paths ones](https://github.com/CaliOpen/Caliopen/tree/master/src/backend/defs/rest-api/paths).\n\nTo generate a single swagger.json file from theses definition you can use the script /devtools/gen-swagger-spec.sh."
  },
  {
    "path": "src/backend/doc/api/swagger.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <meta charset=\"utf8\" />\n  <title>Caliopen HTTP/REST API</title>\n  <!-- needed for adaptive design -->\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <style>\n    body {\n      padding: 0;\n      margin: 0;\n    }\n  </style>\n  <script src=\"https://cdn.redoc.ly/redoc/v2.1.2/bundles/redoc.standalone.js\"></script><style data-styled=\"true\" data-styled-version=\"6.1.1\">.gvoNGN{width:calc(100% - 40%);padding:0 40px;}/*!sc*/\n@media print,screen and (max-width: 75rem){.gvoNGN{width:100%;padding:40px 40px;}}/*!sc*/\ndata-styled.g4[id=\"sc-hKgKIp\"]{content:\"gvoNGN,\"}/*!sc*/\n.kxmVZO{padding:40px 0;}/*!sc*/\n.kxmVZO:last-child{min-height:calc(100vh + 1px);}/*!sc*/\n.kxmVZO>.kxmVZO:last-child{min-height:initial;}/*!sc*/\n@media print,screen and (max-width: 75rem){.kxmVZO{padding:0;}}/*!sc*/\n.bCpIRB{padding:40px 0;position:relative;}/*!sc*/\n.bCpIRB:last-child{min-height:calc(100vh + 1px);}/*!sc*/\n.bCpIRB>.bCpIRB:last-child{min-height:initial;}/*!sc*/\n@media print,screen and (max-width: 75rem){.bCpIRB{padding:0;}}/*!sc*/\n.bCpIRB:not(:last-of-type):after{position:absolute;bottom:0;width:100%;display:block;content:'';border-bottom:1px solid rgba(0, 0, 0, 0.2);}/*!sc*/\ndata-styled.g5[id=\"sc-eCstZk\"]{content:\"kxmVZO,bCpIRB,\"}/*!sc*/\n.QymcT{width:40%;color:#ffffff;background-color:#263238;padding:0 40px;}/*!sc*/\n@media print,screen and (max-width: 75rem){.QymcT{width:100%;padding:40px 40px;}}/*!sc*/\ndata-styled.g6[id=\"sc-jSguLX\"]{content:\"QymcT,\"}/*!sc*/\n.bZqtR{background-color:#263238;}/*!sc*/\ndata-styled.g7[id=\"sc-gKsecS\"]{content:\"bZqtR,\"}/*!sc*/\n.keMybc{display:flex;width:100%;padding:0;}/*!sc*/\n@media print,screen and (max-width: 75rem){.keMybc{flex-direction:column;}}/*!sc*/\ndata-styled.g8[id=\"sc-iBPTVF\"]{content:\"keMybc,\"}/*!sc*/\n.djltfY{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#333333;}/*!sc*/\ndata-styled.g9[id=\"sc-fubDmA\"]{content:\"djltfY,\"}/*!sc*/\n.biKwui{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;margin:0 0 20px;}/*!sc*/\ndata-styled.g10[id=\"sc-pGaPU\"]{content:\"biKwui,\"}/*!sc*/\n.jmifsN{color:#ffffff;}/*!sc*/\ndata-styled.g12[id=\"sc-kEjbdu\"]{content:\"jmifsN,\"}/*!sc*/\n.bRzaSI{border-bottom:1px solid rgba(38, 50, 56, 0.3);margin:1em 0 1em 0;color:rgba(38, 50, 56, 0.5);font-weight:normal;text-transform:uppercase;font-size:0.929em;line-height:20px;}/*!sc*/\ndata-styled.g13[id=\"sc-iqHXzD\"]{content:\"bRzaSI,\"}/*!sc*/\n.sc-crrtmM{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;outline:0;}/*!sc*/\n.jGBpue:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;}/*!sc*/\nh1:hover>.jGBpue::before,h2:hover>.jGBpue::before,.jGBpue:hover::before{visibility:visible;}/*!sc*/\ndata-styled.g14[id=\"sc-crrtmM\"]{content:\"jGBpue,\"}/*!sc*/\n.hHHrnR{height:18px;width:18px;min-width:18px;vertical-align:middle;float:right;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/\n.cKHJqV{height:18px;width:18px;min-width:18px;vertical-align:middle;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/\n.iHKCKT{height:1.5em;width:1.5em;min-width:1.5em;vertical-align:middle;float:left;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/\n.iHKCKT polygon{fill:#1d8127;}/*!sc*/\n.bbipPn{height:1.5em;width:1.5em;min-width:1.5em;vertical-align:middle;float:left;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/\n.bbipPn polygon{fill:#d41f1c;}/*!sc*/\n.hHbBnT{height:20px;width:20px;min-width:20px;vertical-align:middle;float:right;transition:transform 0.2s ease-out;transform:rotateZ(0);}/*!sc*/\n.hHbBnT polygon{fill:white;}/*!sc*/\n.fgXSzg{height:1.3em;width:1.3em;min-width:1.3em;vertical-align:middle;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/\ndata-styled.g15[id=\"sc-dQpIV\"]{content:\"hHHrnR,cKHJqV,iHKCKT,bbipPn,hHbBnT,fgXSzg,\"}/*!sc*/\n.gPUvwc{border-left:1px solid #7c7cbb;box-sizing:border-box;position:relative;padding:10px 10px 10px 0;}/*!sc*/\n@media screen and (max-width: 50rem){.gPUvwc{display:block;overflow:hidden;}}/*!sc*/\ntr:first-of-type>.gPUvwc,tr.last>.gPUvwc{border-left-width:0;background-position:top left;background-repeat:no-repeat;background-size:1px 100%;}/*!sc*/\ntr:first-of-type>.gPUvwc{background-image:linear-gradient(\n      to bottom,\n      transparent 0%,\n      transparent 22px,\n      #7c7cbb 22px,\n      #7c7cbb 100%\n    );}/*!sc*/\ntr.last>.gPUvwc{background-image:linear-gradient(\n      to bottom,\n      #7c7cbb 0%,\n      #7c7cbb 22px,\n      transparent 22px,\n      transparent 100%\n    );}/*!sc*/\ntr.last+tr>.gPUvwc{border-left-color:transparent;}/*!sc*/\ntr.last:first-child>.gPUvwc{background:none;border-left-color:transparent;}/*!sc*/\ndata-styled.g18[id=\"sc-hBEZvw\"]{content:\"gPUvwc,\"}/*!sc*/\n.gXIfsF{vertical-align:top;line-height:20px;white-space:nowrap;font-size:13px;font-family:Courier,monospace;}/*!sc*/\n.gXIfsF.deprecated{text-decoration:line-through;color:#707070;}/*!sc*/\ndata-styled.g20[id=\"sc-fFubCH\"]{content:\"gXIfsF,\"}/*!sc*/\n.kMAhqG{border-bottom:1px solid #9fb4be;padding:10px 0;width:75%;box-sizing:border-box;}/*!sc*/\ntr.expanded .kMAhqG{border-bottom:none;}/*!sc*/\n@media screen and (max-width: 50rem){.kMAhqG{padding:0 20px;border-bottom:none;border-left:1px solid #7c7cbb;}tr.last>.kMAhqG{border-left:none;}}/*!sc*/\ndata-styled.g21[id=\"sc-bkzXAi\"]{content:\"kMAhqG,\"}/*!sc*/\n.jwsifT{color:#7c7cbb;font-family:Courier,monospace;margin-right:10px;}/*!sc*/\n.jwsifT::before{content:'';display:inline-block;vertical-align:middle;width:10px;height:1px;background:#7c7cbb;}/*!sc*/\n.jwsifT::after{content:'';display:inline-block;vertical-align:middle;width:1px;background:#7c7cbb;height:7px;}/*!sc*/\ndata-styled.g22[id=\"sc-idOjMB\"]{content:\"jwsifT,\"}/*!sc*/\n.buUaAP{border-collapse:separate;border-radius:3px;font-size:14px;border-spacing:0;width:100%;}/*!sc*/\n.buUaAP >tr{vertical-align:middle;}/*!sc*/\n@media screen and (max-width: 50rem){.buUaAP{display:block;}.buUaAP >tr,.buUaAP >tbody>tr{display:block;}}/*!sc*/\n@media screen and (max-width: 50rem) and (-ms-high-contrast:none){.buUaAP td{float:left;width:100%;}}/*!sc*/\n.buUaAP .sc-dIUfKc,.buUaAP .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc,.buUaAP .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc{margin:1em;margin-right:0;background:#fafafa;}/*!sc*/\n.buUaAP .sc-dIUfKc .sc-dIUfKc,.buUaAP .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc,.buUaAP .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc .sc-dIUfKc{background:#ffffff;}/*!sc*/\ndata-styled.g24[id=\"sc-hHftZz\"]{content:\"buUaAP,\"}/*!sc*/\n.LetjJ{font-size:0.9em;font-family:Courier,monospace;}/*!sc*/\n.LetjJ::after{content:' [';}/*!sc*/\ndata-styled.g28[id=\"sc-bBXqJn\"]{content:\"LetjJ,\"}/*!sc*/\n.jqOlfD{font-size:0.9em;font-family:Courier,monospace;}/*!sc*/\n.jqOlfD::after{content:']';}/*!sc*/\ndata-styled.g29[id=\"sc-iwyWfK\"]{content:\"jqOlfD,\"}/*!sc*/\n.jVRsAZ >ul{list-style:none;padding:0;margin:0;margin:0 -5px;}/*!sc*/\n.jVRsAZ >ul >li{padding:5px 10px;display:inline-block;background-color:#11171a;border-bottom:1px solid rgba(0, 0, 0, 0.5);cursor:pointer;text-align:center;outline:none;color:#ccc;margin:0 5px 5px 5px;border:1px solid #07090b;border-radius:5px;min-width:60px;font-size:0.9em;font-weight:bold;}/*!sc*/\n.jVRsAZ >ul >li.react-tabs__tab--selected{color:#333333;background:#ffffff;}/*!sc*/\n.jVRsAZ >ul >li.react-tabs__tab--selected:focus{outline:auto;}/*!sc*/\n.jVRsAZ >ul >li:only-child{flex:none;min-width:100px;}/*!sc*/\n.jVRsAZ >ul >li.tab-success{color:#1d8127;}/*!sc*/\n.jVRsAZ >ul >li.tab-redirect{color:#ffa500;}/*!sc*/\n.jVRsAZ >ul >li.tab-info{color:#87ceeb;}/*!sc*/\n.jVRsAZ >ul >li.tab-error{color:#d41f1c;}/*!sc*/\n.jVRsAZ >.react-tabs__tab-panel{background:#11171a;}/*!sc*/\n.jVRsAZ >.react-tabs__tab-panel>div,.jVRsAZ >.react-tabs__tab-panel>pre{padding:20px;margin:0;}/*!sc*/\n.jVRsAZ >.react-tabs__tab-panel>div>pre{padding:0;}/*!sc*/\ndata-styled.g30[id=\"sc-cxFKTC\"]{content:\"jVRsAZ,\"}/*!sc*/\n.gpkGbA code[class*='language-'],.gpkGbA pre[class*='language-']{text-shadow:0 -0.1em 0.2em black;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;}/*!sc*/\n@media print{.gpkGbA code[class*='language-'],.gpkGbA pre[class*='language-']{text-shadow:none;}}/*!sc*/\n.gpkGbA pre[class*='language-']{padding:1em;margin:0.5em 0;overflow:auto;}/*!sc*/\n.gpkGbA .token.comment,.gpkGbA .token.prolog,.gpkGbA .token.doctype,.gpkGbA .token.cdata{color:hsl(30, 20%, 50%);}/*!sc*/\n.gpkGbA .token.punctuation{opacity:0.7;}/*!sc*/\n.gpkGbA .namespace{opacity:0.7;}/*!sc*/\n.gpkGbA .token.property,.gpkGbA .token.tag,.gpkGbA .token.number,.gpkGbA .token.constant,.gpkGbA .token.symbol{color:#4a8bb3;}/*!sc*/\n.gpkGbA .token.boolean{color:#e64441;}/*!sc*/\n.gpkGbA .token.selector,.gpkGbA .token.attr-name,.gpkGbA .token.string,.gpkGbA .token.char,.gpkGbA .token.builtin,.gpkGbA .token.inserted{color:#a0fbaa;}/*!sc*/\n.gpkGbA .token.selector+a,.gpkGbA .token.attr-name+a,.gpkGbA .token.string+a,.gpkGbA .token.char+a,.gpkGbA .token.builtin+a,.gpkGbA .token.inserted+a,.gpkGbA .token.selector+a:visited,.gpkGbA .token.attr-name+a:visited,.gpkGbA .token.string+a:visited,.gpkGbA .token.char+a:visited,.gpkGbA .token.builtin+a:visited,.gpkGbA .token.inserted+a:visited{color:#4ed2ba;text-decoration:underline;}/*!sc*/\n.gpkGbA .token.property.string{color:white;}/*!sc*/\n.gpkGbA .token.operator,.gpkGbA .token.entity,.gpkGbA .token.url,.gpkGbA .token.variable{color:hsl(40, 90%, 60%);}/*!sc*/\n.gpkGbA .token.atrule,.gpkGbA .token.attr-value,.gpkGbA .token.keyword{color:hsl(350, 40%, 70%);}/*!sc*/\n.gpkGbA .token.regex,.gpkGbA .token.important{color:#e90;}/*!sc*/\n.gpkGbA .token.important,.gpkGbA .token.bold{font-weight:bold;}/*!sc*/\n.gpkGbA .token.italic{font-style:italic;}/*!sc*/\n.gpkGbA .token.entity{cursor:help;}/*!sc*/\n.gpkGbA .token.deleted{color:red;}/*!sc*/\ndata-styled.g32[id=\"sc-iJuWdM\"]{content:\"gpkGbA,\"}/*!sc*/\n.bIDavi{opacity:0.7;transition:opacity 0.3s ease;text-align:right;}/*!sc*/\n.bIDavi:focus-within{opacity:1;}/*!sc*/\n.bIDavi >button{background-color:transparent;border:0;color:inherit;padding:2px 10px;font-family:Roboto,sans-serif;font-size:14px;line-height:1.5em;cursor:pointer;outline:0;}/*!sc*/\n.bIDavi >button :hover,.bIDavi >button :focus{background:rgba(255, 255, 255, 0.1);}/*!sc*/\ndata-styled.g33[id=\"sc-giInvV\"]{content:\"bIDavi,\"}/*!sc*/\n.eJckYb{position:relative;}/*!sc*/\ndata-styled.g37[id=\"sc-kLgoAE\"]{content:\"eJckYb,\"}/*!sc*/\n.duVBEA{margin-left:10px;text-transform:none;font-size:0.929em;color:black;}/*!sc*/\ndata-styled.g41[id=\"sc-gWHiUp\"]{content:\"duVBEA,\"}/*!sc*/\n.casAkG{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;}/*!sc*/\n.casAkG p:last-child{margin-bottom:0;}/*!sc*/\n.casAkG h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;}/*!sc*/\n.casAkG h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;}/*!sc*/\n.casAkG code{color:#e53935;background-color:rgba(38, 50, 56, 0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38, 50, 56, 0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;}/*!sc*/\n.casAkG pre{font-family:Courier,monospace;white-space:pre;background-color:#11171a;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0;border:1px solid rgba(38, 50, 56, 0.1);}/*!sc*/\n.casAkG pre code{background-color:transparent;color:white;padding:0;}/*!sc*/\n.casAkG pre code:before,.casAkG pre code:after{content:none;}/*!sc*/\n.casAkG blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;}/*!sc*/\n.casAkG img{max-width:100%;box-sizing:content-box;}/*!sc*/\n.casAkG ul,.casAkG ol{padding-left:2em;margin:0;margin-bottom:1em;}/*!sc*/\n.casAkG ul ul,.casAkG ol ul,.casAkG ul ol,.casAkG ol ol{margin-bottom:0;margin-top:0;}/*!sc*/\n.casAkG table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;}/*!sc*/\n.casAkG table tr{background-color:#fff;border-top:1px solid #ccc;}/*!sc*/\n.casAkG table tr:nth-child(2n){background-color:#fafafa;}/*!sc*/\n.casAkG table th,.casAkG table td{padding:6px 13px;border:1px solid #ddd;}/*!sc*/\n.casAkG table th{text-align:left;font-weight:bold;}/*!sc*/\n.casAkG .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;outline:0;}/*!sc*/\n.casAkG .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;}/*!sc*/\n.casAkG h1:hover>.share-link::before,.casAkG h2:hover>.share-link::before,.casAkG .share-link:hover::before{visibility:visible;}/*!sc*/\n.casAkG a{text-decoration:auto;color:#32329f;}/*!sc*/\n.casAkG a:visited{color:#32329f;}/*!sc*/\n.casAkG a:hover{color:#6868cf;text-decoration:auto;}/*!sc*/\n.dujygE{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;}/*!sc*/\n.dujygE p:last-child{margin-bottom:0;}/*!sc*/\n.dujygE p:first-child{margin-top:0;}/*!sc*/\n.dujygE p:last-child{margin-bottom:0;}/*!sc*/\n.dujygE h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;}/*!sc*/\n.dujygE h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;}/*!sc*/\n.dujygE code{color:#e53935;background-color:rgba(38, 50, 56, 0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38, 50, 56, 0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;}/*!sc*/\n.dujygE pre{font-family:Courier,monospace;white-space:pre;background-color:#11171a;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0;border:1px solid rgba(38, 50, 56, 0.1);}/*!sc*/\n.dujygE pre code{background-color:transparent;color:white;padding:0;}/*!sc*/\n.dujygE pre code:before,.dujygE pre code:after{content:none;}/*!sc*/\n.dujygE blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;}/*!sc*/\n.dujygE img{max-width:100%;box-sizing:content-box;}/*!sc*/\n.dujygE ul,.dujygE ol{padding-left:2em;margin:0;margin-bottom:1em;}/*!sc*/\n.dujygE ul ul,.dujygE ol ul,.dujygE ul ol,.dujygE ol ol{margin-bottom:0;margin-top:0;}/*!sc*/\n.dujygE table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;}/*!sc*/\n.dujygE table tr{background-color:#fff;border-top:1px solid #ccc;}/*!sc*/\n.dujygE table tr:nth-child(2n){background-color:#fafafa;}/*!sc*/\n.dujygE table th,.dujygE table td{padding:6px 13px;border:1px solid #ddd;}/*!sc*/\n.dujygE table th{text-align:left;font-weight:bold;}/*!sc*/\n.dujygE .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;outline:0;}/*!sc*/\n.dujygE .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;}/*!sc*/\n.dujygE h1:hover>.share-link::before,.dujygE h2:hover>.share-link::before,.dujygE .share-link:hover::before{visibility:visible;}/*!sc*/\n.dujygE a{text-decoration:auto;color:#32329f;}/*!sc*/\n.dujygE a:visited{color:#32329f;}/*!sc*/\n.dujygE a:hover{color:#6868cf;text-decoration:auto;}/*!sc*/\ndata-styled.g42[id=\"sc-cBNeRQ\"]{content:\"casAkG,dujygE,\"}/*!sc*/\n.bzkUsW{display:inline;}/*!sc*/\ndata-styled.g43[id=\"sc-citwID\"]{content:\"bzkUsW,\"}/*!sc*/\n.iVrxDL{position:relative;}/*!sc*/\ndata-styled.g44[id=\"sc-jcVcfa\"]{content:\"iVrxDL,\"}/*!sc*/\n.gLzJQv:hover>.sc-giInvV{opacity:1;}/*!sc*/\ndata-styled.g49[id=\"sc-cTkyaV\"]{content:\"gLzJQv,\"}/*!sc*/\n.jXmkAn{font-family:Courier,monospace;font-size:13px;white-space:pre;contain:content;overflow-x:auto;}/*!sc*/\n.jXmkAn .redoc-json code>.collapser{display:none;pointer-events:none;}/*!sc*/\n.jXmkAn .callback-function{color:gray;}/*!sc*/\n.jXmkAn .collapser:after{content:'-';cursor:pointer;}/*!sc*/\n.jXmkAn .collapsed>.collapser:after{content:'+';cursor:pointer;}/*!sc*/\n.jXmkAn .ellipsis:after{content:' … ';}/*!sc*/\n.jXmkAn .collapsible{margin-left:2em;}/*!sc*/\n.jXmkAn .hoverable{padding-top:1px;padding-bottom:1px;padding-left:2px;padding-right:2px;border-radius:2px;}/*!sc*/\n.jXmkAn .hovered{background-color:rgba(235, 238, 249, 1);}/*!sc*/\n.jXmkAn .collapser{background-color:transparent;border:0;color:#fff;font-family:Courier,monospace;font-size:13px;padding-right:6px;padding-left:6px;padding-top:0;padding-bottom:0;display:flex;align-items:center;justify-content:center;width:15px;height:15px;position:absolute;top:4px;left:-1.5em;cursor:default;user-select:none;-webkit-user-select:none;padding:2px;}/*!sc*/\n.jXmkAn .collapser:focus{outline-color:#fff;outline-style:dotted;outline-width:1px;}/*!sc*/\n.jXmkAn ul{list-style-type:none;padding:0px;margin:0px 0px 0px 26px;}/*!sc*/\n.jXmkAn li{position:relative;display:block;}/*!sc*/\n.jXmkAn .hoverable{display:inline-block;}/*!sc*/\n.jXmkAn .selected{outline-style:solid;outline-width:1px;outline-style:dotted;}/*!sc*/\n.jXmkAn .collapsed>.collapsible{display:none;}/*!sc*/\n.jXmkAn .ellipsis{display:none;}/*!sc*/\n.jXmkAn .collapsed>.ellipsis{display:inherit;}/*!sc*/\ndata-styled.g50[id=\"sc-jNMdxs\"]{content:\"jXmkAn,\"}/*!sc*/\n.bAzVww{padding:0.9em;background-color:rgba(38,50,56,0.4);margin:0 0 10px 0;display:block;font-family:Montserrat,sans-serif;font-size:0.929em;line-height:1.5em;}/*!sc*/\ndata-styled.g51[id=\"sc-dOSSlk\"]{content:\"bAzVww,\"}/*!sc*/\n.etjuEr{font-family:Montserrat,sans-serif;font-size:12px;position:absolute;z-index:1;top:-11px;left:12px;font-weight:600;color:rgba(255,255,255,0.7);}/*!sc*/\ndata-styled.g52[id=\"sc-bBrOHt\"]{content:\"etjuEr,\"}/*!sc*/\n.eDzWuJ{position:relative;}/*!sc*/\ndata-styled.g53[id=\"sc-cOaiZO\"]{content:\"eDzWuJ,\"}/*!sc*/\n.kTaCsY{margin-top:15px;}/*!sc*/\ndata-styled.g56[id=\"sc-hTZhMB\"]{content:\"kTaCsY,\"}/*!sc*/\n.hEcUkb button{background-color:transparent;border:0;outline:0;font-size:13px;font-family:Courier,monospace;cursor:pointer;padding:0;color:#333333;}/*!sc*/\n.hEcUkb button:focus{font-weight:600;}/*!sc*/\n.hEcUkb .sc-dQpIV{height:1.1em;width:1.1em;}/*!sc*/\n.hEcUkb .sc-dQpIV polygon{fill:#666;}/*!sc*/\ndata-styled.g57[id=\"sc-jgHCeW\"]{content:\"hEcUkb,\"}/*!sc*/\n.ihUpGF{vertical-align:middle;font-size:13px;line-height:20px;}/*!sc*/\ndata-styled.g58[id=\"sc-gTgyBf\"]{content:\"ihUpGF,\"}/*!sc*/\n.cREGdc{color:rgba(102,102,102,0.9);}/*!sc*/\ndata-styled.g59[id=\"sc-laRQQM\"]{content:\"cREGdc,\"}/*!sc*/\n.iPuJRj{color:#666;}/*!sc*/\ndata-styled.g60[id=\"sc-iNqNmV\"]{content:\"iPuJRj,\"}/*!sc*/\n.KUVXc{color:#666;word-break:break-word;}/*!sc*/\ndata-styled.g61[id=\"sc-jeGSXX\"]{content:\"KUVXc,\"}/*!sc*/\n.giWqTo{color:#d41f1c;font-size:0.9em;font-weight:normal;margin-left:20px;line-height:1;}/*!sc*/\ndata-styled.g62[id=\"sc-eJMOVy\"]{content:\"giWqTo,\"}/*!sc*/\n.cEfEOv{border-radius:2px;word-break:break-word;background-color:rgba(51,51,51,0.05);color:rgba(51,51,51,0.9);padding:0 5px;border:1px solid rgba(51,51,51,0.1);font-family:Courier,monospace;}/*!sc*/\n+{margin-left:0;}/*!sc*/\ndata-styled.g66[id=\"sc-lcuiaG\"]{content:\"cEfEOv,\"}/*!sc*/\n.bThqtX{border-radius:2px;background-color:rgba(104,104,207,0.05);color:rgba(50,50,159,0.9);margin:0 5px;padding:0 5px;border:1px solid rgba(50,50,159,0.1);}/*!sc*/\n+{margin-left:0;}/*!sc*/\ndata-styled.g68[id=\"sc-clsGLE\"]{content:\"bThqtX,\"}/*!sc*/\n.eCVKtp{padding-left:10px;}/*!sc*/\ndata-styled.g75[id=\"sc-aelVe\"]{content:\"eCVKtp,\"}/*!sc*/\n.hVqHWQ:after{content:' and ';font-weight:normal;}/*!sc*/\n.hVqHWQ:last-child:after{content:none;}/*!sc*/\n.hVqHWQ a{text-decoration:auto;color:#32329f;}/*!sc*/\n.hVqHWQ a:visited{color:#32329f;}/*!sc*/\n.hVqHWQ a:hover{color:#6868cf;text-decoration:auto;}/*!sc*/\ndata-styled.g80[id=\"sc-kIeSmD\"]{content:\"hVqHWQ,\"}/*!sc*/\n.kdGkar{white-space:nowrap;}/*!sc*/\n.kdGkar:after{content:' or ';white-space:pre;}/*!sc*/\n.kdGkar:last-child:after,.kdGkar:only-child:after{content:none;}/*!sc*/\n.kdGkar a{text-decoration:auto;color:#32329f;}/*!sc*/\n.kdGkar a:visited{color:#32329f;}/*!sc*/\n.kdGkar a:hover{color:#6868cf;text-decoration:auto;}/*!sc*/\ndata-styled.g81[id=\"sc-hOqshF\"]{content:\"kdGkar,\"}/*!sc*/\n.bbCJhi{flex:1 1 auto;cursor:pointer;}/*!sc*/\ndata-styled.g82[id=\"sc-dtwofg\"]{content:\"bbCJhi,\"}/*!sc*/\n.dYSrpA{width:75%;text-overflow:ellipsis;border-radius:4px;overflow:hidden;}/*!sc*/\n@media screen and (max-width: 50rem){.dYSrpA{margin-top:10px;}}/*!sc*/\ndata-styled.g83[id=\"sc-dacFVT\"]{content:\"dYSrpA,\"}/*!sc*/\n.dHKKTL{display:inline-block;margin:0;}/*!sc*/\ndata-styled.g84[id=\"sc-jUElsq\"]{content:\"dHKKTL,\"}/*!sc*/\n.GKXXr{width:100%;display:flex;margin:1em 0;flex-direction:row;}/*!sc*/\n@media screen and (max-width: 50rem){.GKXXr{flex-direction:column;}}/*!sc*/\ndata-styled.g85[id=\"sc-jQbKEx\"]{content:\"GKXXr,\"}/*!sc*/\n.hWeMwQ{margin-top:0;margin-bottom:0.5em;}/*!sc*/\ndata-styled.g91[id=\"sc-eGCbyA\"]{content:\"hWeMwQ,\"}/*!sc*/\n.gUXnfH{border:1px solid #32329f;color:#32329f;font-weight:normal;margin-left:0.5em;padding:4px 8px 4px;display:inline-block;text-decoration:none;cursor:pointer;}/*!sc*/\ndata-styled.g92[id=\"sc-ctaXUJ\"]{content:\"gUXnfH,\"}/*!sc*/\n.bwwtyo{width:9ex;display:inline-block;height:13px;line-height:13px;background-color:#333;border-radius:3px;background-repeat:no-repeat;background-position:6px 4px;font-size:7px;font-family:Verdana,sans-serif;color:white;text-transform:uppercase;text-align:center;font-weight:bold;vertical-align:middle;margin-right:6px;margin-top:2px;}/*!sc*/\n.bwwtyo.get{background-color:#2F8132;}/*!sc*/\n.bwwtyo.post{background-color:#186FAF;}/*!sc*/\n.bwwtyo.put{background-color:#95507c;}/*!sc*/\n.bwwtyo.options{background-color:#947014;}/*!sc*/\n.bwwtyo.patch{background-color:#bf581d;}/*!sc*/\n.bwwtyo.delete{background-color:#cc3333;}/*!sc*/\n.bwwtyo.basic{background-color:#707070;}/*!sc*/\n.bwwtyo.link{background-color:#07818F;}/*!sc*/\n.bwwtyo.head{background-color:#A23DAD;}/*!sc*/\n.bwwtyo.hook{background-color:#32329f;}/*!sc*/\n.bwwtyo.schema{background-color:#707070;}/*!sc*/\ndata-styled.g99[id=\"sc-dkIYMQ\"]{content:\"bwwtyo,\"}/*!sc*/\n.YKcaq{margin:0;padding:0;}/*!sc*/\n.YKcaq:first-child{padding-bottom:32px;}/*!sc*/\n.sc-XhViZ .sc-XhViZ{font-size:0.929em;}/*!sc*/\n.VqZwz{margin:0;padding:0;display:none;}/*!sc*/\n.VqZwz:first-child{padding-bottom:32px;}/*!sc*/\n.sc-XhViZ .sc-XhViZ{font-size:0.929em;}/*!sc*/\ndata-styled.g100[id=\"sc-XhViZ\"]{content:\"YKcaq,VqZwz,\"}/*!sc*/\n.fhNqYr{list-style:none inside none;overflow:hidden;text-overflow:ellipsis;padding:0;}/*!sc*/\ndata-styled.g101[id=\"sc-ikPBrU\"]{content:\"fhNqYr,\"}/*!sc*/\n.cOUFXf{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:flex;justify-content:space-between;font-family:Montserrat,sans-serif;font-size:0.929em;text-transform:none;background-color:#fafafa;}/*!sc*/\n.cOUFXf:hover{color:#32329f;background-color:#e1e1e1;}/*!sc*/\n.cOUFXf .sc-dQpIV{height:1.5em;width:1.5em;}/*!sc*/\n.cOUFXf .sc-dQpIV polygon{fill:#333333;}/*!sc*/\n.CYQVt{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:flex;justify-content:space-between;font-family:Montserrat,sans-serif;background-color:#fafafa;}/*!sc*/\n.CYQVt:hover{color:#32329f;background-color:#ededed;}/*!sc*/\n.CYQVt .sc-dQpIV{height:1.5em;width:1.5em;}/*!sc*/\n.CYQVt .sc-dQpIV polygon{fill:#333333;}/*!sc*/\ndata-styled.g102[id=\"sc-tYqQR\"]{content:\"cOUFXf,CYQVt,\"}/*!sc*/\n.bkVowu{display:inline-block;vertical-align:middle;width:calc(100% - 38px);overflow:hidden;text-overflow:ellipsis;}/*!sc*/\ndata-styled.g103[id=\"sc-biBryG\"]{content:\"bkVowu,\"}/*!sc*/\n.jrtoRW{font-size:0.8em;margin-top:10px;text-align:center;position:fixed;width:260px;bottom:0;background:#fafafa;}/*!sc*/\n.jrtoRW a,.jrtoRW a:visited,.jrtoRW a:hover{color:#333333!important;padding:5px 0;border-top:1px solid #e1e1e1;text-decoration:none;display:flex;align-items:center;justify-content:center;}/*!sc*/\n.jrtoRW img{width:15px;margin-right:5px;}/*!sc*/\n@media screen and (max-width: 50rem){.jrtoRW{width:100%;}}/*!sc*/\ndata-styled.g104[id=\"sc-eHfQwz\"]{content:\"jrtoRW,\"}/*!sc*/\n.bhuodb{cursor:pointer;position:relative;margin-bottom:5px;}/*!sc*/\ndata-styled.g110[id=\"sc-bQdSiB\"]{content:\"bhuodb,\"}/*!sc*/\n.jzevEV{font-family:Courier,monospace;margin-left:10px;flex:1;overflow-x:hidden;text-overflow:ellipsis;}/*!sc*/\ndata-styled.g111[id=\"sc-fXoxOd\"]{content:\"jzevEV,\"}/*!sc*/\n.jlriCd{outline:0;color:inherit;width:100%;text-align:left;cursor:pointer;padding:10px 30px 10px 20px;border-radius:4px 4px 0 0;background-color:#11171a;display:flex;white-space:nowrap;align-items:center;border:1px solid transparent;border-bottom:0;transition:border-color 0.25s ease;}/*!sc*/\n.jlriCd ..sc-fXoxOd{color:#ffffff;}/*!sc*/\n.jlriCd:focus{box-shadow:inset 0 2px 2px rgba(0, 0, 0, 0.45),0 2px 0 rgba(128, 128, 128, 0.25);}/*!sc*/\ndata-styled.g112[id=\"sc-FyfbU\"]{content:\"jlriCd,\"}/*!sc*/\n.coiChI{font-size:0.929em;line-height:20px;background-color:#186FAF;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/\n.gDPfwK{font-size:0.929em;line-height:20px;background-color:#2F8132;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/\n.dgogVa{font-size:0.929em;line-height:20px;background-color:#bf581d;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/\n.cHWaxK{font-size:0.929em;line-height:20px;background-color:#cc3333;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/\ndata-styled.g113[id=\"sc-jXkspL\"]{content:\"coiChI,gDPfwK,dgogVa,cHWaxK,\"}/*!sc*/\n.ciRGmD{position:absolute;width:100%;z-index:100;background:#fafafa;color:#263238;box-sizing:border-box;box-shadow:0 0 6px rgba(0, 0, 0, 0.33);overflow:hidden;border-bottom-left-radius:4px;border-bottom-right-radius:4px;transition:all 0.25s ease;visibility:hidden;transform:translateY(-50%) scaleY(0);}/*!sc*/\ndata-styled.g114[id=\"sc-eFtZDC\"]{content:\"ciRGmD,\"}/*!sc*/\n.buTZbL{padding:10px;}/*!sc*/\ndata-styled.g115[id=\"sc-fmlKft\"]{content:\"buTZbL,\"}/*!sc*/\n.uePtT{padding:5px;border:1px solid #ccc;background:#fff;word-break:break-all;color:#32329f;}/*!sc*/\n.uePtT >span{color:#333333;}/*!sc*/\ndata-styled.g116[id=\"sc-ljRaSg\"]{content:\"uePtT,\"}/*!sc*/\n.gpLUhw{text-transform:lowercase;margin-left:0;line-height:1.5em;}/*!sc*/\ndata-styled.g117[id=\"sc-jmhEHb\"]{content:\"gpLUhw,\"}/*!sc*/\n.iePdRD{display:block;border:0;width:100%;text-align:left;padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;cursor:pointer;color:#1d8127;background-color:rgba(29,129,39,0.07);}/*!sc*/\n.iePdRD:focus{outline:auto #1d8127;}/*!sc*/\n.jEuzUJ{display:block;border:0;width:100%;text-align:left;padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;cursor:pointer;color:#d41f1c;background-color:rgba(212,31,28,0.07);}/*!sc*/\n.jEuzUJ:focus{outline:auto #d41f1c;}/*!sc*/\n.eEqsZl{display:block;border:0;width:100%;text-align:left;padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;cursor:pointer;color:#1d8127;background-color:rgba(29,129,39,0.07);cursor:default;}/*!sc*/\n.eEqsZl:focus{outline:auto #1d8127;}/*!sc*/\n.eEqsZl::before{content:\"—\";font-weight:bold;width:1.5em;text-align:center;display:inline-block;vertical-align:top;}/*!sc*/\n.eEqsZl:focus{outline:0;}/*!sc*/\n.jifJmL{display:block;border:0;width:100%;text-align:left;padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;cursor:pointer;color:#d41f1c;background-color:rgba(212,31,28,0.07);cursor:default;}/*!sc*/\n.jifJmL:focus{outline:auto #d41f1c;}/*!sc*/\n.jifJmL::before{content:\"—\";font-weight:bold;width:1.5em;text-align:center;display:inline-block;vertical-align:top;}/*!sc*/\n.jifJmL:focus{outline:0;}/*!sc*/\ndata-styled.g119[id=\"sc-httZfN\"]{content:\"iePdRD,jEuzUJ,eEqsZl,jifJmL,\"}/*!sc*/\n.goojcW{vertical-align:top;}/*!sc*/\ndata-styled.g122[id=\"sc-gVgoeb\"]{content:\"goojcW,\"}/*!sc*/\n.fCsSHf{font-size:1.3em;padding:0.2em 0;margin:3em 0 1.1em;color:#333333;font-weight:normal;}/*!sc*/\ndata-styled.g123[id=\"sc-fWPdKs\"]{content:\"fCsSHf,\"}/*!sc*/\n.fgfjUV{margin-bottom:30px;}/*!sc*/\ndata-styled.g128[id=\"sc-eWVLZl\"]{content:\"fgfjUV,\"}/*!sc*/\n.iEVneI{user-select:none;width:20px;height:20px;align-self:center;display:flex;flex-direction:column;color:#32329f;}/*!sc*/\ndata-styled.g129[id=\"sc-iGctyi\"]{content:\"iEVneI,\"}/*!sc*/\n.gXSQkB{width:260px;background-color:#fafafa;overflow:hidden;display:flex;flex-direction:column;backface-visibility:hidden;height:100vh;position:sticky;position:-webkit-sticky;top:0;}/*!sc*/\n@media screen and (max-width: 50rem){.gXSQkB{position:fixed;z-index:20;width:100%;background:#fafafa;display:none;}}/*!sc*/\n@media print{.gXSQkB{display:none;}}/*!sc*/\ndata-styled.g130[id=\"sc-irlPvL\"]{content:\"gXSQkB,\"}/*!sc*/\n.bEOmaQ{outline:none;user-select:none;background-color:#f2f2f2;color:#32329f;display:none;cursor:pointer;position:fixed;right:20px;z-index:100;border-radius:50%;box-shadow:0 0 20px rgba(0, 0, 0, 0.3);bottom:44px;width:60px;height:60px;padding:0 20px;}/*!sc*/\n@media screen and (max-width: 50rem){.bEOmaQ{display:flex;}}/*!sc*/\n.bEOmaQ svg{color:#0065FB;}/*!sc*/\n@media print{.bEOmaQ{display:none;}}/*!sc*/\ndata-styled.g131[id=\"sc-eWvOCH\"]{content:\"bEOmaQ,\"}/*!sc*/\n.bwTiCm{font-family:Roboto,sans-serif;font-size:14px;font-weight:400;line-height:1.5em;color:#333333;display:flex;position:relative;text-align:left;-webkit-font-smoothing:antialiased;font-smoothing:antialiased;text-rendering:optimizeSpeed!important;tap-highlight-color:rgba(0, 0, 0, 0);text-size-adjust:100%;}/*!sc*/\n.bwTiCm *{box-sizing:border-box;-webkit-tap-highlight-color:rgba(255, 255, 255, 0);}/*!sc*/\ndata-styled.g132[id=\"sc-kUbfpu\"]{content:\"bwTiCm,\"}/*!sc*/\n.dPIjnf{z-index:1;position:relative;overflow:hidden;width:calc(100% - 260px);contain:layout;}/*!sc*/\n@media print,screen and (max-width: 50rem){.dPIjnf{width:100%;}}/*!sc*/\ndata-styled.g133[id=\"sc-dwcvcB\"]{content:\"dPIjnf,\"}/*!sc*/\n.gfiMgB{background:#263238;position:absolute;top:0;bottom:0;right:0;width:calc((100% - 260px) * 0.4);}/*!sc*/\n@media print,screen and (max-width: 75rem){.gfiMgB{display:none;}}/*!sc*/\ndata-styled.g134[id=\"sc-jtHLPo\"]{content:\"gfiMgB,\"}/*!sc*/\n.hwhlAl{padding:5px 0;}/*!sc*/\ndata-styled.g135[id=\"sc-eltaTX\"]{content:\"hwhlAl,\"}/*!sc*/\n.eWIXbA{width:calc(100% - 40px);box-sizing:border-box;margin:0 20px;padding:5px 10px 5px 20px;border:0;border-bottom:1px solid #e1e1e1;font-family:Roboto,sans-serif;font-weight:bold;font-size:13px;color:#333333;background-color:transparent;outline:none;}/*!sc*/\ndata-styled.g136[id=\"sc-kiYrGK\"]{content:\"eWIXbA,\"}/*!sc*/\n.lcoFQc{position:absolute;left:20px;height:1.8em;width:0.9em;}/*!sc*/\n.lcoFQc path{fill:#333333;}/*!sc*/\ndata-styled.g137[id=\"sc-cKZHtR\"]{content:\"lcoFQc,\"}/*!sc*/\n</style>\n  \n</head>\n\n<body>\n  \n      <div id=\"redoc\"><div class=\"sc-kUbfpu bwTiCm redoc-wrap\"><div class=\"sc-irlPvL gXSQkB menu-content\" style=\"top:0px;height:calc(100vh - 0px)\"><div role=\"search\" class=\"sc-eltaTX hwhlAl\"><svg class=\"sc-cKZHtR lcoFQc search-icon\" version=\"1.1\" viewBox=\"0 0 1000 1000\" x=\"0px\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0px\"><path d=\"M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z\"></path></svg><input placeholder=\"Search...\" aria-label=\"Search\" type=\"text\" class=\"sc-kiYrGK eWIXbA search-input\" value=\"\"/></div><div class=\"sc-kLgoAE eJckYb scrollbar-container undefined\"><ul role=\"menu\" class=\"sc-XhViZ YKcaq\"><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/users\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"users\" class=\"sc-biBryG bkVowu\">users</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/users/paths/~1v1~1authentications/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns an auth token to build basicAuth for the p</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/users/paths/~1v1~1me/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Gets `user + contact` objects for current logged-i</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/users/paths/~1v1~1users/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Create a new User with provided credentials</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/users/paths/~1v2~1users~1{user_id}/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Partially implemented. Currently only for changing</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/users/paths/~1v2~1users~1{user_id}~1actions/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">send an order to execute one (or many) action(s) r</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/users/paths/~1v2~1username~1isAvailable/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Check if an username is available for creation wit</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/password\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"password\" class=\"sc-biBryG bkVowu\">password</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/password/paths/~1v2~1users~1{user_id}/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Partially implemented. Currently only for changing</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/password/paths/~1v2~1users~1{user_id}~1actions/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">send an order to execute one (or many) action(s) r</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/username\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"username\" class=\"sc-biBryG bkVowu\">username</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/username/paths/~1v2~1username~1isAvailable/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Check if an username is available for creation wit</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/settings\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"settings\" class=\"sc-biBryG bkVowu\">settings</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/settings/paths/~1v1~1settings/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns settings belonging to current user</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/settings/paths/~1v1~1settings/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Update settings with rfc5789 and rfc7396 specifica</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/identities\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"identities\" class=\"sc-biBryG bkVowu\">identities</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/identities/paths/~1v2~1identities~1locals/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">returns the list of user&#x27;s local identities</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/identities/paths/~1v2~1identities~1remotes/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">returns the list of user&#x27;s remote identities, or f</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/identities/paths/~1v2~1identities~1remotes/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">create a new remote identity for user</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">returns a remote identity belonging to user</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Delete a remote identity belonging to user</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update a remote identity with rfc5789 and rfc7396 </span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/identities/paths/~1v2~1providers/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">returns list of supported external providers for r</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/identities/paths/~1v2~1providers~1{provider_name}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">returns provider&#x27;s properties, notably the url to </span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/identities/paths/~1v2~1providers~1{provider_name}~1callback/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">url registered at provider to which its API will r</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/passwords\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"passwords\" class=\"sc-biBryG bkVowu\">passwords</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/passwords/paths/~1v2~1passwords~1reset/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Route to receive a &quot;reset password&quot; request from a</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns an auth token to build basicAuth, if the t</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">User posts a new password</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/providers\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"providers\" class=\"sc-biBryG bkVowu\">providers</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/providers/paths/~1v2~1providers/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">returns list of supported external providers for r</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/providers/paths/~1v2~1providers~1{provider_name}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">returns provider&#x27;s properties, notably the url to </span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/providers/paths/~1v2~1providers~1{provider_name}~1callback/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">url registered at provider to which its API will r</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/providers/paths/~1v2~1test~1oauth/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">serves an index to test Oauth processes</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/contacts\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"contacts\" class=\"sc-biBryG bkVowu\">contacts</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1contacts/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns contacts list for current user according t</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1contacts/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Create a new contact for the logged-in user</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns a contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Delete a contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update a contact with rfc5789 and rfc7396 specific</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">returns a list of contact&#x27;s identities</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update tags list for contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1tags/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update tags list for contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1search/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Simple API to execute full-text searches within us</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/contacts/paths/~1v2~1search/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Not yet implemented. Future route for more complex</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/tags\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"tags\" class=\"sc-biBryG bkVowu\">tags</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1identities/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update tags list for contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1tags/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update tags list for contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/tags/paths/~1v2~1messages~1{message_id}~1tags/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update tags list for message</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/tags/paths/~1v2~1tags/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns tags visible to current user according to </span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/tags/paths/~1v2~1tags/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Create a new Tag for an user</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/tags/paths/~1v2~1tags~1{tag_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Retrieve tag infos</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/tags/paths/~1v2~1tags~1{tag_id}/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update a tag</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/tags/paths/~1v2~1tags~1{tag_id}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Delete a tag belonging to an user</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/pgp\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"pgp\" class=\"sc-biBryG bkVowu\">pgp</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Add a pgp public key to a contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns all publickeys linked to contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Retrieve publickey</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update a public key</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Delete a public key</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/keys\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"keys\" class=\"sc-biBryG bkVowu\">keys</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Add a pgp public key to a contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns all publickeys linked to contact</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Retrieve publickey</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update a public key</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Delete a public key</span></label></li></ul></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"/paths/~1v2~1imports/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">/v2/imports</span></label></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/discussions\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"discussions\" class=\"sc-biBryG bkVowu\">discussions</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/discussions/paths/~1v2~1discussions/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns the list of discussions for current user a</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/discussions/paths/~1v2~1discussions~1{discussion_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns metadata of a discussion</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/messages\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"messages\" class=\"sc-biBryG bkVowu\">messages</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v1~1messages/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Create a new Message (draft) for an user</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v2~1messages/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns the list of messages for current user acco</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v1~1messages~1{message_id}/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update a draft with rfc5789 and rfc7396 specificat</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v1~1messages~1{message_id}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Delete a message belonging to an user</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v2~1messages~1{message_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">returns a message</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1tags/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update tags list for message</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1actions/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">send an order to execute one (or many) action(s) f</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">(for draft only) upload a file to server and add a</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Download file from server</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">(for drafts only) delete temporary file and remove</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v1~1raws~1{raw_msg_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns a raw message</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v2~1search/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Simple API to execute full-text searches within us</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/messages/paths/~1v2~1search/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Not yet implemented. Future route for more complex</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/attachments\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"attachments\" class=\"sc-biBryG bkVowu\">attachments</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">(for draft only) upload a file to server and add a</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Download file from server</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">(for drafts only) delete temporary file and remove</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/devices\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"devices\" class=\"sc-biBryG bkVowu\">devices</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/devices/paths/~1v2~1devices/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns devices belonging to current user accordin</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/devices/paths/~1v2~1devices~1{device_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns a device</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/devices/paths/~1v2~1devices~1{device_id}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Delete a device</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/devices/paths/~1v2~1devices~1{device_id}/patch\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"patch\" class=\"sc-dkIYMQ bwwtyo operation-type patch\">patch</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">update a device with rfc5789 and rfc7396 specifica</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/devices/paths/~1v2~1devices~1{device_id}~1actions/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Route to receive orders to trigger actions on a de</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/devices/paths/~1v2~1validate-device~1{token}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">complete device validation if token is valid</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/participants\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"participants\" class=\"sc-biBryG bkVowu\">participants</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/participants/paths/~1v2~1participants~1suggest/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns a list of suggestions according to given p</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/participants/paths/~1v2~1participants~1discussion/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns discussion related to a list of participan</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/suggest\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"suggest\" class=\"sc-biBryG bkVowu\">suggest</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/suggest/paths/~1v2~1participants~1suggest/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns a list of suggestions according to given p</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/search\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"search\" class=\"sc-biBryG bkVowu\">search</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/search/paths/~1v2~1search/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Simple API to execute full-text searches within us</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/search/paths/~1v2~1search/post\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"post\" class=\"sc-dkIYMQ bwwtyo operation-type post\">post</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Not yet implemented. Future route for more complex</span></label></li></ul></li><li tabindex=\"0\" depth=\"1\" data-item-id=\"tag/notifications\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR cOUFXf -depth1\"><span width=\"calc(100% - 38px)\" title=\"notifications\" class=\"sc-biBryG bkVowu\">notifications</span><svg class=\"sc-dQpIV hHHrnR\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></label><ul class=\"sc-XhViZ VqZwz\"><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/notifications/paths/~1v2~1notifications/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns pending notifications</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/notifications/paths/~1v2~1notifications/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Delete pending notifications by time range</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/get\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"get\" class=\"sc-dkIYMQ bwwtyo operation-type get\">get</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Returns a notification</span></label></li><li tabindex=\"0\" depth=\"2\" data-item-id=\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/delete\" role=\"menuitem\" class=\"sc-ikPBrU fhNqYr\"><label class=\"sc-tYqQR CYQVt -depth2\"><span type=\"delete\" class=\"sc-dkIYMQ bwwtyo operation-type delete\">del</span><span tabindex=\"0\" width=\"calc(100% - 38px)\" class=\"sc-biBryG bkVowu\">Delete a notification</span></label></li></ul></li></ul><div class=\"sc-eHfQwz jrtoRW\"><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://redocly.com/redoc/\">API docs by Redocly</a></div></div></div><div class=\"sc-eWvOCH bEOmaQ\"><div class=\"sc-iGctyi iEVneI\"><svg class=\"\" style=\"transform:translate(2px, -4px) rotate(180deg);transition:transform 0.2s ease\" viewBox=\"0 0 926.23699 573.74994\" version=\"1.1\" x=\"0px\" y=\"0px\" width=\"15\" height=\"15\"><g transform=\"translate(904.92214,-879.1482)\"><path d=\"\n          m -673.67664,1221.6502 -231.2455,-231.24803 55.6165,\n          -55.627 c 30.5891,-30.59485 56.1806,-55.627 56.8701,-55.627 0.6894,\n          0 79.8637,78.60862 175.9427,174.68583 l 174.6892,174.6858 174.6892,\n          -174.6858 c 96.079,-96.07721 175.253196,-174.68583 175.942696,\n          -174.68583 0.6895,0 26.281,25.03215 56.8701,\n          55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864\n          -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688,\n          -104.0616 -231.873,-231.248 z\n        \" fill=\"currentColor\"></path></g></svg><svg class=\"\" style=\"transform:translate(2px, 4px);transition:transform 0.2s ease\" viewBox=\"0 0 926.23699 573.74994\" version=\"1.1\" x=\"0px\" y=\"0px\" width=\"15\" height=\"15\"><g transform=\"translate(904.92214,-879.1482)\"><path d=\"\n          m -673.67664,1221.6502 -231.2455,-231.24803 55.6165,\n          -55.627 c 30.5891,-30.59485 56.1806,-55.627 56.8701,-55.627 0.6894,\n          0 79.8637,78.60862 175.9427,174.68583 l 174.6892,174.6858 174.6892,\n          -174.6858 c 96.079,-96.07721 175.253196,-174.68583 175.942696,\n          -174.68583 0.6895,0 26.281,25.03215 56.8701,\n          55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864\n          -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688,\n          -104.0616 -231.873,-231.248 z\n        \" fill=\"currentColor\"></path></g></svg></div></div><div class=\"sc-dwcvcB dPIjnf api-content\"><div class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN api-info\"><h1 class=\"sc-fubDmA sc-eGCbyA djltfY hWeMwQ\">Caliopen HTTP/REST API<!-- --> <span>(<!-- -->0.2.0<!-- -->)</span></h1><p>Download OpenAPI specification<!-- -->:<a download=\"openapi.json\" target=\"_blank\" class=\"sc-ctaXUJ gUXnfH\">Download</a></p><div class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><div data-role=\"redoc-summary\" html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><div data-role=\"redoc-description\" html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div></div></div></div><div id=\"tag/users\" data-section-id=\"tag/users\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/users\" aria-label=\"tag/users\"></a>users</h1></div></div></div><div id=\"tag/users/paths/~1v1~1authentications/post\" data-section-id=\"tag/users/paths/~1v1~1authentications/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/users/paths/~1v1~1authentications/post\" aria-label=\"tag/users/paths/~1v1~1authentications/post\"></a>Returns an auth token to build basicAuth for the p<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns an auth token to build basicAuth for the provided credentials&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns an auth token to build basicAuth for the provided credentials</p>\n</div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"username\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">username</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"password\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">password</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"context\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">context</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"device\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand device\"><span class=\"property-name\">device</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->DefaultDevice<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Successful authentication&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful authentication</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Authentication error&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Authentication error</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v1/authentications</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v1/authentications</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-0\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-1\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-1\" aria-labelledby=\"react-tabs-0\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"username\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"password\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"context\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"device\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"device_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ecdsa_key\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"curve\"</span>: <span class=\"token string\">&quot;P-256&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"hash\"</span>: <span class=\"token string\">&quot;SHA256&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-2\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-3\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-4\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-5\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-3\" aria-labelledby=\"react-tabs-2\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"username\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tokens\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"access_token\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expires_in\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expires_at\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"refresh_token\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"device\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"device_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"status\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-5\" aria-labelledby=\"react-tabs-4\"></div></div></div></div></div></div><div id=\"tag/users/paths/~1v1~1me/get\" data-section-id=\"tag/users/paths/~1v1~1me/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/users/paths/~1v1~1me/get\" aria-label=\"tag/users/paths/~1v1~1me/get\"></a>Gets `user + contact` objects for current logged-i<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Gets &lt;code&gt;user + contact&lt;/code&gt; objects for current logged-in user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Gets <code>user + contact</code> objects for current logged-in user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">header<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"X-Caliopen-PI\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-PI</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">1;100</span></div> <div><div html=\"&lt;p&gt;The PI range requested in form of &lt;code&gt;1;100&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The PI range requested in form of <code>1;100</code></p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Successful response with json object&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful response with json object</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v1/me</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v1/me</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-6\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-7\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-7\" aria-labelledby=\"react-tabs-6\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"contact\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"additional_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"addresses\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"city\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"postal_code\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"region\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"street\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"avatar\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"email_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"family_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"given_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"groups\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"identities\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ims\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"im_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name_prefix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name_suffix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"organizations\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"organization_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"department\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"job_description\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phones\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phone_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"normalized_number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"uri\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"family_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"given_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"password\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"params\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"recovery_email\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/users/paths/~1v1~1users/post\" data-section-id=\"tag/users/paths/~1v1~1users/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/users/paths/~1v1~1users/post\" aria-label=\"tag/users/paths/~1v1~1users/post\"></a>Create a new User with provided credentials<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Create a new User with provided credentials&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Create a new User with provided credentials</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"contact\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand contact\"><span class=\"property-name\">contact</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->NewContact<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"username\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">username</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"password\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">password</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"recovery_email\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">recovery_email</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"tos\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tos</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"privacy\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">privacy</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"settings\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand settings\"><span class=\"property-name\">settings</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Settings<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"device\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand device\"><span class=\"property-name\">device</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->DefaultDevice<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;User creation completed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>User creation completed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">406<!-- --> </strong><div html=\"&lt;p&gt;NotAcceptable. Server refuses to fulfill the request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>NotAcceptable. Server refuses to fulfill the request</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v1/users</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v1/users</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-8\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-9\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-9\" aria-labelledby=\"react-tabs-8\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"contact\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"additional_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"addresses\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"city\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"postal_code\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"region\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"street\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"avatar\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"family_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"given_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"groups\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"identities\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ims\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name_prefix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name_suffix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"organizations\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phones\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phone_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"normalized_number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"uri\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"username\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"password\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"recovery_email\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tos\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"settings\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"default_locale\"</span>: <span class=\"token string\">&quot;fr-FR&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_display_format\"</span>: <span class=\"token string\">&quot;rich_text&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_display_order\"</span>: <span class=\"token string\">&quot;given_name&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_display_format\"</span>: <span class=\"token string\">&quot;family_name, given_name&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"notification_enabled\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"notification_message_preview\"</span>: <span class=\"token string\">&quot;always&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"notification_sound_enabled\"</span>: <span class=\"token boolean\">false</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"notification_delay_disappear\"</span>: <span class=\"token number\">10</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"device\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"device_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ecdsa_key\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"curve\"</span>: <span class=\"token string\">&quot;P-256&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"hash\"</span>: <span class=\"token string\">&quot;SHA256&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-10\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-11\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-12\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-13\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-14\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-15\" data-rttab=\"true\">406</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-11\" aria-labelledby=\"react-tabs-10\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"location\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-13\" aria-labelledby=\"react-tabs-12\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-15\" aria-labelledby=\"react-tabs-14\"></div></div></div></div></div></div><div id=\"tag/users/paths/~1v2~1users~1{user_id}/patch\" data-section-id=\"tag/users/paths/~1v2~1users~1{user_id}/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/users/paths/~1v2~1users~1{user_id}/patch\" aria-label=\"tag/users/paths/~1v2~1users~1{user_id}/patch\"></a>Partially implemented. Currently only for changing<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Partially implemented. Currently only for changing password.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Partially implemented. Currently only for changing password.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"attachments\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand attachments\"><span class=\"property-name\">attachments</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Attachment<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"body\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">body</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"body_is_plain\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">body_is_plain</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_delete\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_delete</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_insert\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_insert</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_sort\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_sort</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"discussion_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">discussion_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"external_references\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand external_references\"><span class=\"property-name\">external_references</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->ExternalReferences<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"excerpt\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">excerpt</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"user_identities\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_identities</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"importance_level\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">importance_level</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->int32<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_answered\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_answered</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_draft\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_draft</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_unread\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_unread</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_received\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_received</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"parent_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">parent_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"participants\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand participants\"><span class=\"property-name\">participants</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Participant<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"privacy_features\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">privacy_features</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PrivacyFeatures<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"pi\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand pi\"><span class=\"property-name\">pi</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PI<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"pi_message\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand pi_message\"><span class=\"property-name\">pi_message</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PIMessage<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"raw_msg_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">raw_msg_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"subject\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">subject</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"protocol\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">protocol</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden patch. Server is refusing to apply the given patch&amp;#39;s properties to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden patch. Server is refusing to apply the given patch&#39;s properties to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/users/{user_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/users/{user_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-16\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-17\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-17\" aria-labelledby=\"react-tabs-16\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-18\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-19\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-20\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-21\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-22\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-23\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-24\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-25\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-26\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-27\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-19\" aria-labelledby=\"react-tabs-18\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-21\" aria-labelledby=\"react-tabs-20\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-23\" aria-labelledby=\"react-tabs-22\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-25\" aria-labelledby=\"react-tabs-24\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-27\" aria-labelledby=\"react-tabs-26\"></div></div></div></div></div></div><div id=\"tag/users/paths/~1v2~1users~1{user_id}~1actions/post\" data-section-id=\"tag/users/paths/~1v2~1users~1{user_id}~1actions/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/users/paths/~1v2~1users~1{user_id}~1actions/post\" aria-label=\"tag/users/paths/~1v2~1users~1{user_id}~1actions/post\"></a>send an order to execute one (or many) action(s) r<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;send an order to execute one (or many) action(s) regarding the user : reset_password, etc. A successful execution of the action will probably modify one or more user&amp;#39;s attribute(s) ## NOT YET IMPLEMENTED ##&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>send an order to execute one (or many) action(s) regarding the user : reset_password, etc. A successful execution of the action will probably modify one or more user&#39;s attribute(s) ## NOT YET IMPLEMENTED ##</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"actions\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">actions</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div><div><span class=\"sc-gTgyBf ihUpGF\">Items<!-- --> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;send&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;set_read&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;set_unread&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;reset_password&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;delete&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;device-validation&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"params\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">params</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;action(s) successfully executed. Nothing returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>action(s) successfully executed. Nothing returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden action. Server is refusing to execute the given action(s) to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden action. Server is refusing to execute the given action(s) to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Message not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but action was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but action was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;execution of action failed.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>execution of action failed.</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/users/{user_id}/actions</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/users/{user_id}/actions</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-28\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-29\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-29\" aria-labelledby=\"react-tabs-28\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"actions\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;send&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"params\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-30\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-31\" tabindex=\"0\" data-rttab=\"true\">204</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-32\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-33\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-34\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-35\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-36\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-37\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-38\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-39\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-40\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-41\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-42\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-43\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-31\" aria-labelledby=\"react-tabs-30\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-33\" aria-labelledby=\"react-tabs-32\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-35\" aria-labelledby=\"react-tabs-34\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-37\" aria-labelledby=\"react-tabs-36\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-39\" aria-labelledby=\"react-tabs-38\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-41\" aria-labelledby=\"react-tabs-40\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-43\" aria-labelledby=\"react-tabs-42\"></div></div></div></div></div></div><div id=\"tag/users/paths/~1v2~1username~1isAvailable/get\" data-section-id=\"tag/users/paths/~1v2~1username~1isAvailable/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/users/paths/~1v2~1username~1isAvailable/get\" aria-label=\"tag/users/paths/~1v2~1username~1isAvailable/get\"></a>Check if an username is available for creation wit<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Check if an username is available for creation within Caliopen instance&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Check if an username is available for creation within Caliopen instance</p>\n</div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"username\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">username</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;availability state for requested username&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>availability state for requested username</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request (probably missing &amp;#39;username&amp;#39; query param)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request (probably missing &#39;username&#39; query param)</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/username/isAvailable</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/username/isAvailable</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-44\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-45\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-46\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-47\" data-rttab=\"true\">400</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-45\" aria-labelledby=\"react-tabs-44\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"username\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"available\"</span>: <span class=\"token boolean\">true</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-47\" aria-labelledby=\"react-tabs-46\"></div></div></div></div></div></div><div id=\"tag/password\" data-section-id=\"tag/password\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/password\" aria-label=\"tag/password\"></a>password</h1></div></div></div><div id=\"tag/password/paths/~1v2~1users~1{user_id}/patch\" data-section-id=\"tag/password/paths/~1v2~1users~1{user_id}/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/password/paths/~1v2~1users~1{user_id}/patch\" aria-label=\"tag/password/paths/~1v2~1users~1{user_id}/patch\"></a>Partially implemented. Currently only for changing<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Partially implemented. Currently only for changing password.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Partially implemented. Currently only for changing password.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"attachments\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand attachments\"><span class=\"property-name\">attachments</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Attachment<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"body\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">body</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"body_is_plain\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">body_is_plain</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_delete\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_delete</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_insert\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_insert</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_sort\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_sort</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"discussion_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">discussion_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"external_references\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand external_references\"><span class=\"property-name\">external_references</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->ExternalReferences<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"excerpt\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">excerpt</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"user_identities\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_identities</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"importance_level\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">importance_level</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->int32<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_answered\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_answered</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_draft\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_draft</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_unread\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_unread</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_received\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_received</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"parent_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">parent_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"participants\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand participants\"><span class=\"property-name\">participants</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Participant<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"privacy_features\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">privacy_features</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PrivacyFeatures<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"pi\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand pi\"><span class=\"property-name\">pi</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PI<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"pi_message\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand pi_message\"><span class=\"property-name\">pi_message</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PIMessage<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"raw_msg_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">raw_msg_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"subject\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">subject</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"protocol\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">protocol</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden patch. Server is refusing to apply the given patch&amp;#39;s properties to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden patch. Server is refusing to apply the given patch&#39;s properties to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/users/{user_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/users/{user_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-48\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-49\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-49\" aria-labelledby=\"react-tabs-48\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-50\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-51\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-52\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-53\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-54\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-55\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-56\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-57\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-58\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-59\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-51\" aria-labelledby=\"react-tabs-50\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-53\" aria-labelledby=\"react-tabs-52\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-55\" aria-labelledby=\"react-tabs-54\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-57\" aria-labelledby=\"react-tabs-56\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-59\" aria-labelledby=\"react-tabs-58\"></div></div></div></div></div></div><div id=\"tag/password/paths/~1v2~1users~1{user_id}~1actions/post\" data-section-id=\"tag/password/paths/~1v2~1users~1{user_id}~1actions/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/password/paths/~1v2~1users~1{user_id}~1actions/post\" aria-label=\"tag/password/paths/~1v2~1users~1{user_id}~1actions/post\"></a>send an order to execute one (or many) action(s) r<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;send an order to execute one (or many) action(s) regarding the user : reset_password, etc. A successful execution of the action will probably modify one or more user&amp;#39;s attribute(s) ## NOT YET IMPLEMENTED ##&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>send an order to execute one (or many) action(s) regarding the user : reset_password, etc. A successful execution of the action will probably modify one or more user&#39;s attribute(s) ## NOT YET IMPLEMENTED ##</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"actions\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">actions</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div><div><span class=\"sc-gTgyBf ihUpGF\">Items<!-- --> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;send&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;set_read&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;set_unread&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;reset_password&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;delete&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;device-validation&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"params\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">params</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;action(s) successfully executed. Nothing returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>action(s) successfully executed. Nothing returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden action. Server is refusing to execute the given action(s) to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden action. Server is refusing to execute the given action(s) to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Message not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but action was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but action was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;execution of action failed.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>execution of action failed.</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/users/{user_id}/actions</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/users/{user_id}/actions</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-60\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-61\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-61\" aria-labelledby=\"react-tabs-60\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"actions\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;send&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"params\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-62\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-63\" tabindex=\"0\" data-rttab=\"true\">204</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-64\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-65\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-66\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-67\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-68\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-69\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-70\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-71\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-72\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-73\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-74\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-75\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-63\" aria-labelledby=\"react-tabs-62\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-65\" aria-labelledby=\"react-tabs-64\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-67\" aria-labelledby=\"react-tabs-66\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-69\" aria-labelledby=\"react-tabs-68\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-71\" aria-labelledby=\"react-tabs-70\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-73\" aria-labelledby=\"react-tabs-72\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-75\" aria-labelledby=\"react-tabs-74\"></div></div></div></div></div></div><div id=\"tag/username\" data-section-id=\"tag/username\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/username\" aria-label=\"tag/username\"></a>username</h1></div></div></div><div id=\"tag/username/paths/~1v2~1username~1isAvailable/get\" data-section-id=\"tag/username/paths/~1v2~1username~1isAvailable/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/username/paths/~1v2~1username~1isAvailable/get\" aria-label=\"tag/username/paths/~1v2~1username~1isAvailable/get\"></a>Check if an username is available for creation wit<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Check if an username is available for creation within Caliopen instance&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Check if an username is available for creation within Caliopen instance</p>\n</div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"username\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">username</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;availability state for requested username&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>availability state for requested username</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request (probably missing &amp;#39;username&amp;#39; query param)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request (probably missing &#39;username&#39; query param)</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/username/isAvailable</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/username/isAvailable</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-76\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-77\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-78\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-79\" data-rttab=\"true\">400</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-77\" aria-labelledby=\"react-tabs-76\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"username\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"available\"</span>: <span class=\"token boolean\">true</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-79\" aria-labelledby=\"react-tabs-78\"></div></div></div></div></div></div><div id=\"tag/settings\" data-section-id=\"tag/settings\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/settings\" aria-label=\"tag/settings\"></a>settings</h1></div></div></div><div id=\"tag/settings/paths/~1v1~1settings/get\" data-section-id=\"tag/settings/paths/~1v1~1settings/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/settings/paths/~1v1~1settings/get\" aria-label=\"tag/settings/paths/~1v1~1settings/get\"></a>Returns settings belonging to current user<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns settings belonging to current user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns settings belonging to current user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Settings returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Settings returned</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v1/settings</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v1/settings</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-80\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-81\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-81\" aria-labelledby=\"react-tabs-80\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"default_locale\"</span>: <span class=\"token string\">&quot;fr-FR&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_display_format\"</span>: <span class=\"token string\">&quot;rich_text&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_display_order\"</span>: <span class=\"token string\">&quot;given_name&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_display_format\"</span>: <span class=\"token string\">&quot;family_name, given_name&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notification_enabled\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notification_message_preview\"</span>: <span class=\"token string\">&quot;always&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notification_sound_enabled\"</span>: <span class=\"token boolean\">false</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notification_delay_disappear\"</span>: <span class=\"token number\">10</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/settings/paths/~1v1~1settings/patch\" data-section-id=\"tag/settings/paths/~1v1~1settings/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/settings/paths/~1v1~1settings/patch\" aria-label=\"tag/settings/paths/~1v1~1settings/patch\"></a>Update settings with rfc5789 and rfc7396 specifica<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Update settings with rfc5789 and rfc7396 specifications&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Update settings with rfc5789 and rfc7396 specifications</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"default_locale\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">default_locale</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;fr-FR&quot;</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"message_display_format\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_display_format</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;rich_text&quot;</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"contact_display_order\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_display_order</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;given_name&quot;</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"contact_display_format\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_display_format</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;family_name, given_name&quot;</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"notification_enabled\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">notification_enabled</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">true</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"notification_message_preview\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">notification_message_preview</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;always&quot;</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"notification_sound_enabled\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">notification_sound_enabled</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">false</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"notification_delay_disappear\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">notification_delay_disappear</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">10</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v1/settings</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v1/settings</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-82\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-83\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-83\" aria-labelledby=\"react-tabs-82\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"$ref\"</span>: <span class=\"token keyword\">null</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"default_locale\"</span>: <span class=\"token string\">&quot;fr-FR&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_display_format\"</span>: <span class=\"token string\">&quot;rich_text&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_display_order\"</span>: <span class=\"token string\">&quot;given_name&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_display_format\"</span>: <span class=\"token string\">&quot;family_name, given_name&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notification_enabled\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notification_message_preview\"</span>: <span class=\"token string\">&quot;always&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notification_sound_enabled\"</span>: <span class=\"token boolean\">false</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notification_delay_disappear\"</span>: <span class=\"token number\">10</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/identities\" data-section-id=\"tag/identities\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities\" aria-label=\"tag/identities\"></a>identities</h1></div></div></div><div id=\"tag/identities/paths/~1v2~1identities~1locals/get\" data-section-id=\"tag/identities/paths/~1v2~1identities~1locals/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities/paths/~1v2~1identities~1locals/get\" aria-label=\"tag/identities/paths/~1v2~1identities~1locals/get\"></a>returns the list of user&#x27;s local identities<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;returns the list of user&amp;#39;s local identities&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>returns the list of user&#39;s local identities</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Local identities returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Local identities returned</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/identities/locals</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/identities/locals</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-84\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-85\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-86\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-87\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-85\" aria-labelledby=\"react-tabs-84\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"local_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"credentials\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"display_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"identity_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"identifier\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"last_check\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;email&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"status\"</span>: <span class=\"token string\">&quot;active&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;local&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-87\" aria-labelledby=\"react-tabs-86\"></div></div></div></div></div></div><div id=\"tag/identities/paths/~1v2~1identities~1remotes/get\" data-section-id=\"tag/identities/paths/~1v2~1identities~1remotes/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities/paths/~1v2~1identities~1remotes/get\" aria-label=\"tag/identities/paths/~1v2~1identities~1remotes/get\"></a>returns the list of user&#x27;s remote identities, or f<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;returns the list of user&amp;#39;s remote identities, or filtered if query param&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>returns the list of user&#39;s remote identities, or filtered if query param</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"pending\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">pending</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Remote identities returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Remote identities returned</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/identities/remotes</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/identities/remotes</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-88\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-89\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-90\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-91\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-89\" aria-labelledby=\"react-tabs-88\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"remote_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"credentials\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"display_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"identity_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"identifier\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"last_check\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;email&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"status\"</span>: <span class=\"token string\">&quot;active&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;local&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-91\" aria-labelledby=\"react-tabs-90\"></div></div></div></div></div></div><div id=\"tag/identities/paths/~1v2~1identities~1remotes/post\" data-section-id=\"tag/identities/paths/~1v2~1identities~1remotes/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities/paths/~1v2~1identities~1remotes/post\" aria-label=\"tag/identities/paths/~1v2~1identities~1remotes/post\"></a>create a new remote identity for user<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;create a new remote identity for user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>create a new remote identity for user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"credentials\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand credentials\"><span class=\"property-name\">credentials</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"display_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">display_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"identifier\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">identifier</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"infos\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand infos\"><span class=\"property-name\">infos</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"protocol\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">protocol</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;email&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;twitter&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"status\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">status</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;active&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;inactive&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;deleted&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"type\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">type</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;local&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;remote&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Remote identity created&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Remote identity created</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/identities/remotes</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/identities/remotes</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-92\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-93\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-93\" aria-labelledby=\"react-tabs-92\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"credentials\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"display_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identifier\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"infos\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;email&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"status\"</span>: <span class=\"token string\">&quot;active&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;local&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-94\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-95\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-95\" aria-labelledby=\"react-tabs-94\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"location\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identifier\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/get\" data-section-id=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/get\" aria-label=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/get\"></a>returns a remote identity belonging to user<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;returns a remote identity belonging to user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>returns a remote identity belonging to user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"identifier\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">identifier</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Remote identity returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Remote identity returned</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Remote identity not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Remote identity not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/identities/remotes/{identifier}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/identities/remotes/{identifier}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-96\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-97\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-98\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-99\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-100\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-101\" data-rttab=\"true\">404</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-97\" aria-labelledby=\"react-tabs-96\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"credentials\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"display_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identity_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identifier\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"infos\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"last_check\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;email&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"status\"</span>: <span class=\"token string\">&quot;active&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;local&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-99\" aria-labelledby=\"react-tabs-98\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-101\" aria-labelledby=\"react-tabs-100\"></div></div></div></div></div></div><div id=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/delete\" data-section-id=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/delete\" aria-label=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/delete\"></a>Delete a remote identity belonging to user<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Delete a remote identity belonging to user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Delete a remote identity belonging to user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"identifier\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">identifier</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Remote identity deleted&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Remote identity deleted</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Contact not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/identities/remotes/{identifier}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/identities/remotes/{identifier}</div></div></div></div></div></div></div></div><div id=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/patch\" data-section-id=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/patch\" aria-label=\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/patch\"></a>update a remote identity with rfc5789 and rfc7396 <!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update a remote identity with rfc5789 and rfc7396 specifications&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update a remote identity with rfc5789 and rfc7396 specifications</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"identifier\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">identifier</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"credentials\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand credentials\"><span class=\"property-name\">credentials</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"display_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">display_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"identity_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">identity_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"identifier\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">identifier</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"infos\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand infos\"><span class=\"property-name\">infos</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"last_check\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">last_check</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"protocol\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">protocol</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;email&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;twitter&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"status\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">status</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;active&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;inactive&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;deleted&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"type\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">type</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;local&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;remote&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden patch. Server is refusing to apply the given patch&amp;#39;s properties to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden patch. Server is refusing to apply the given patch&#39;s properties to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;identity not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>identity not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/identities/remotes/{identifier}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/identities/remotes/{identifier}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-102\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-103\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-103\" aria-labelledby=\"react-tabs-102\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"$ref\"</span>: <span class=\"token keyword\">null</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"credentials\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"display_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identity_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identifier\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"infos\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property1\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"property2\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"last_check\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;email&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"status\"</span>: <span class=\"token string\">&quot;active&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;local&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-104\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-105\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-106\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-107\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-108\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-109\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-110\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-111\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-112\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-113\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-105\" aria-labelledby=\"react-tabs-104\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-107\" aria-labelledby=\"react-tabs-106\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-109\" aria-labelledby=\"react-tabs-108\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-111\" aria-labelledby=\"react-tabs-110\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-113\" aria-labelledby=\"react-tabs-112\"></div></div></div></div></div></div><div id=\"tag/identities/paths/~1v2~1providers/get\" data-section-id=\"tag/identities/paths/~1v2~1providers/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities/paths/~1v2~1providers/get\" aria-label=\"tag/identities/paths/~1v2~1providers/get\"></a>returns list of supported external providers for r<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;returns list of supported external providers for remote identities creation&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>returns list of supported external providers for remote identities creation</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;List of supported providers&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>List of supported providers</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/providers</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/providers</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-114\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-115\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-116\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-117\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-115\" aria-labelledby=\"react-tabs-114\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"providers\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-117\" aria-labelledby=\"react-tabs-116\"></div></div></div></div></div></div><div id=\"tag/identities/paths/~1v2~1providers~1{provider_name}/get\" data-section-id=\"tag/identities/paths/~1v2~1providers~1{provider_name}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities/paths/~1v2~1providers~1{provider_name}/get\" aria-label=\"tag/identities/paths/~1v2~1providers~1{provider_name}/get\"></a>returns provider&#x27;s properties, notably the url to <!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;returns provider&amp;#39;s properties, notably the url to call for initiating Oauth process&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>returns provider&#39;s properties, notably the url to call for initiating Oauth process</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"provider_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">provider_name</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"identifier\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">identifier</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;providers infos&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>providers infos</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/providers/{provider_name}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/providers/{provider_name}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-118\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-119\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-120\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-121\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-119\" aria-labelledby=\"react-tabs-118\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"oauth_request_url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-121\" aria-labelledby=\"react-tabs-120\"></div></div></div></div></div></div><div id=\"tag/identities/paths/~1v2~1providers~1{provider_name}~1callback/get\" data-section-id=\"tag/identities/paths/~1v2~1providers~1{provider_name}~1callback/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/identities/paths/~1v2~1providers~1{provider_name}~1callback/get\" aria-label=\"tag/identities/paths/~1v2~1providers~1{provider_name}~1callback/get\"></a>url registered at provider to which its API will r<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;url registered at provider to which its API will redirect user after user authentication&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>url registered at provider to which its API will redirect user after user authentication</p>\n</div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"provider_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">provider_name</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;a simple message for user to inform Oauth process completion&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>a simple message for user to inform Oauth process completion</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/providers/{provider_name}/callback</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/providers/{provider_name}/callback</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-122\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-123\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-123\" aria-labelledby=\"react-tabs-122\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"success\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/passwords\" data-section-id=\"tag/passwords\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/passwords\" aria-label=\"tag/passwords\"></a>passwords</h1></div></div></div><div id=\"tag/passwords/paths/~1v2~1passwords~1reset/post\" data-section-id=\"tag/passwords/paths/~1v2~1passwords~1reset/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/passwords/paths/~1v2~1passwords~1reset/post\" aria-label=\"tag/passwords/paths/~1v2~1passwords~1reset/post\"></a>Route to receive a &quot;reset password&quot; request from a<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Route to receive a &amp;quot;reset password&amp;quot; request from an anonymous user.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Route to receive a &quot;reset password&quot; request from an anonymous user.</p>\n</div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;data the user has typed into the reset form&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>data the user has typed into the reset form</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"recovery_email\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">recovery_email</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"username\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">username</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Request successfully handled. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Request successfully handled. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;user not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>user not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;Parameters were valid but the server was unable to handle the request.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Parameters were valid but the server was unable to handle the request.</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/passwords/reset</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/passwords/reset</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-124\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-125\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-125\" aria-labelledby=\"react-tabs-124\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"recovery_email\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"username\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-126\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-127\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-128\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-129\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-130\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-131\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-127\" aria-labelledby=\"react-tabs-126\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-129\" aria-labelledby=\"react-tabs-128\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-131\" aria-labelledby=\"react-tabs-130\"></div></div></div></div></div></div><div id=\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/get\" data-section-id=\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/passwords/paths/~1v2~1passwords~1reset~1{token}/get\" aria-label=\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/get\"></a>Returns an auth token to build basicAuth, if the t<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns an auth token to build basicAuth, if the token in path is valid&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns an auth token to build basicAuth, if the token in path is valid</p>\n</div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"token\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">token</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Route is valid. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Route is valid. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Token not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Token not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/passwords/reset/{token}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/passwords/reset/{token}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-132\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-133\" tabindex=\"0\" data-rttab=\"true\">404</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-133\" aria-labelledby=\"react-tabs-132\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/post\" data-section-id=\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/passwords/paths/~1v2~1passwords~1reset~1{token}/post\" aria-label=\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/post\"></a>User posts a new password<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;User posts a new password&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>User posts a new password</p>\n</div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"token\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">token</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;A simple json with the new password as a string&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>A simple json with the new password as a string</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"password\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">password</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Password update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Password update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;Parameters were valid but the server was unable to change the password.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Parameters were valid but the server was unable to change the password.</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/passwords/reset/{token}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/passwords/reset/{token}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-134\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-135\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-135\" aria-labelledby=\"react-tabs-134\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"password\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-136\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-137\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-138\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-139\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-140\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-141\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-137\" aria-labelledby=\"react-tabs-136\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-139\" aria-labelledby=\"react-tabs-138\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-141\" aria-labelledby=\"react-tabs-140\"></div></div></div></div></div></div><div id=\"tag/providers\" data-section-id=\"tag/providers\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/providers\" aria-label=\"tag/providers\"></a>providers</h1></div></div></div><div id=\"tag/providers/paths/~1v2~1providers/get\" data-section-id=\"tag/providers/paths/~1v2~1providers/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/providers/paths/~1v2~1providers/get\" aria-label=\"tag/providers/paths/~1v2~1providers/get\"></a>returns list of supported external providers for r<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;returns list of supported external providers for remote identities creation&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>returns list of supported external providers for remote identities creation</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;List of supported providers&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>List of supported providers</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/providers</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/providers</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-142\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-143\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-144\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-145\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-143\" aria-labelledby=\"react-tabs-142\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"providers\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-145\" aria-labelledby=\"react-tabs-144\"></div></div></div></div></div></div><div id=\"tag/providers/paths/~1v2~1providers~1{provider_name}/get\" data-section-id=\"tag/providers/paths/~1v2~1providers~1{provider_name}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/providers/paths/~1v2~1providers~1{provider_name}/get\" aria-label=\"tag/providers/paths/~1v2~1providers~1{provider_name}/get\"></a>returns provider&#x27;s properties, notably the url to <!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;returns provider&amp;#39;s properties, notably the url to call for initiating Oauth process&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>returns provider&#39;s properties, notably the url to call for initiating Oauth process</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"provider_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">provider_name</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"identifier\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">identifier</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;providers infos&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>providers infos</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/providers/{provider_name}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/providers/{provider_name}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-146\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-147\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-148\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-149\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-147\" aria-labelledby=\"react-tabs-146\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"oauth_request_url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-149\" aria-labelledby=\"react-tabs-148\"></div></div></div></div></div></div><div id=\"tag/providers/paths/~1v2~1providers~1{provider_name}~1callback/get\" data-section-id=\"tag/providers/paths/~1v2~1providers~1{provider_name}~1callback/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/providers/paths/~1v2~1providers~1{provider_name}~1callback/get\" aria-label=\"tag/providers/paths/~1v2~1providers~1{provider_name}~1callback/get\"></a>url registered at provider to which its API will r<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;url registered at provider to which its API will redirect user after user authentication&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>url registered at provider to which its API will redirect user after user authentication</p>\n</div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"provider_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">provider_name</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;a simple message for user to inform Oauth process completion&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>a simple message for user to inform Oauth process completion</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/providers/{provider_name}/callback</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/providers/{provider_name}/callback</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-150\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-151\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-151\" aria-labelledby=\"react-tabs-150\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"success\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/providers/paths/~1v2~1test~1oauth/get\" data-section-id=\"tag/providers/paths/~1v2~1test~1oauth/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/providers/paths/~1v2~1test~1oauth/get\" aria-label=\"tag/providers/paths/~1v2~1test~1oauth/get\"></a>serves an index to test Oauth processes<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;serves an index to test Oauth processes&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>serves an index to test Oauth processes</p>\n</div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;index to trigger oauth popup&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>index to trigger oauth popup</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/test/oauth</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/test/oauth</div></div></div></div></div></div></div></div><div id=\"tag/contacts\" data-section-id=\"tag/contacts\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts\" aria-label=\"tag/contacts\"></a>contacts</h1></div></div></div><div id=\"tag/contacts/paths/~1v2~1contacts/get\" data-section-id=\"tag/contacts/paths/~1v2~1contacts/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1contacts/get\" aria-label=\"tag/contacts/paths/~1v2~1contacts/get\"></a>Returns contacts list for current user according t<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns contacts list for current user according to given params&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns contacts list for current user according to given params</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"limit\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">limit</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of contacts to return per page&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of contacts to return per page</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"offset\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">offset</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of pages to skip from the response&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of pages to skip from the response</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"uri\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">uri</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;return contact that has this uri embedded, if any&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>return contact that has this uri embedded, if any</p>\n</div></div></div></td></tr></tbody></table></div><div><h5 class=\"sc-iqHXzD bRzaSI\">header<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"X-Caliopen-PI\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-PI</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">1;100</span></div> <div><div html=\"&lt;p&gt;The PI range requested in form of &lt;code&gt;1;100&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The PI range requested in form of <code>1;100</code></p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"X-Caliopen-IL\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-IL</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">-10;10</span></div> <div><div html=\"&lt;p&gt;The Importance Level range requested in form of &lt;code&gt;-10;10&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The Importance Level range requested in form of <code>-10;10</code></p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Contacts list&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contacts list</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-152\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-153\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-153\" aria-labelledby=\"react-tabs-152\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contacts\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"additional_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"addresses\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"city\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"postal_code\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"region\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"street\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"avatar\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"email_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"family_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"given_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"groups\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"identities\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ims\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"im_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name_prefix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name_suffix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"organizations\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"organization_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"department\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"job_description\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phones\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phone_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"normalized_number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"uri\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/contacts/paths/~1v2~1contacts/post\" data-section-id=\"tag/contacts/paths/~1v2~1contacts/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1contacts/post\" aria-label=\"tag/contacts/paths/~1v2~1contacts/post\"></a>Create a new contact for the logged-in user<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Create a new contact for the logged-in user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Create a new contact for the logged-in user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span></h5><div html=\"&lt;p&gt;the contact to create&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the contact to create</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"additional_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">additional_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"addresses\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand addresses\"><span class=\"property-name\">addresses</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PostalAddress<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"avatar\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">avatar</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"emails\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand emails\"><span class=\"property-name\">emails</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->NewEmail<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"family_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">family_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"given_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">given_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"title\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">title</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"groups\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">groups</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"identities\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand identities\"><span class=\"property-name\">identities</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->SocialIdentity<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"ims\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">ims</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"infos\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">infos</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"name_prefix\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">name_prefix</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"name_suffix\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">name_suffix</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"organizations\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">organizations</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"phones\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand phones\"><span class=\"property-name\">phones</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Phone<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"public_keys\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand public_keys\"><span class=\"property-name\">public_keys</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PublicKey<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Contact created&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact created</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-154\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-155\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-155\" aria-labelledby=\"react-tabs-154\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"additional_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"addresses\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"city\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"postal_code\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"region\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"street\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"avatar\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"family_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"given_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"groups\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"ims\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_prefix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_suffix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"organizations\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"phones\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phone_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"normalized_number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"uri\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-156\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-157\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-157\" aria-labelledby=\"react-tabs-156\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"location\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/get\" data-section-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1contacts~1{contact_id}/get\" aria-label=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/get\"></a>Returns a contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns a contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns a contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Contact found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Contact not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-158\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-159\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-160\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-161\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-162\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-163\" data-rttab=\"true\">404</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-159\" aria-labelledby=\"react-tabs-158\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"additional_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"addresses\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"city\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"postal_code\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"region\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"street\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"avatar\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"email_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"family_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"given_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"groups\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"ims\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"im_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_prefix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_suffix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"organizations\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"organization_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"department\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"job_description\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"phones\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phone_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"normalized_number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"uri\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-161\" aria-labelledby=\"react-tabs-160\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-163\" aria-labelledby=\"react-tabs-162\"></div></div></div></div></div></div><div id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/delete\" data-section-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1contacts~1{contact_id}/delete\" aria-label=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/delete\"></a>Delete a contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Delete a contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Delete a contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Contact deleted&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact deleted</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Contact not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}</div></div></div></div></div></div></div></div><div id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/patch\" data-section-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1contacts~1{contact_id}/patch\" aria-label=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/patch\"></a>update a contact with rfc5789 and rfc7396 specific<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update a contact with rfc5789 and rfc7396 specifications&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update a contact with rfc5789 and rfc7396 specifications</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"additional_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">additional_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"addresses\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand addresses\"><span class=\"property-name\">addresses</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PostalAddress<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"avatar\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">avatar</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_insert\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_insert</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_update\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_update</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"deleted\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">deleted</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"emails\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand emails\"><span class=\"property-name\">emails</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Email<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"family_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">family_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"given_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">given_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"groups\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">groups</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"identities\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand identities\"><span class=\"property-name\">identities</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->SocialIdentity<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"ims\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand ims\"><span class=\"property-name\">ims</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->IM<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"infos\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">infos</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"name_prefix\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">name_prefix</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"name_suffix\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">name_suffix</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"organizations\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand organizations\"><span class=\"property-name\">organizations</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Organization<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"phones\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand phones\"><span class=\"property-name\">phones</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Phone<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"pi\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand pi\"><span class=\"property-name\">pi</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PI<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"privacy_features\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">privacy_features</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PrivacyFeatures<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"public_keys\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand public_keys\"><span class=\"property-name\">public_keys</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PublicKey<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"title\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">title</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden patch. Server is refusing to apply the given patch&amp;#39;s properties to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden patch. Server is refusing to apply the given patch&#39;s properties to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;contact not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>contact not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-164\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-165\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-165\" aria-labelledby=\"react-tabs-164\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"$ref\"</span>: <span class=\"token keyword\">null</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"additional_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"addresses\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"city\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"postal_code\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"region\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"street\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"avatar\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"email_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"family_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"given_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"groups\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"ims\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"im_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_prefix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_suffix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"organizations\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"organization_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"department\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"job_description\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"phones\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phone_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"normalized_number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"uri\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-166\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-167\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-168\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-169\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-170\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-171\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-172\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-173\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-174\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-175\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-167\" aria-labelledby=\"react-tabs-166\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-169\" aria-labelledby=\"react-tabs-168\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-171\" aria-labelledby=\"react-tabs-170\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-173\" aria-labelledby=\"react-tabs-172\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-175\" aria-labelledby=\"react-tabs-174\"></div></div></div></div></div></div><div id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/get\" data-section-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/get\" aria-label=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/get\"></a>returns a list of contact&#x27;s identities<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;returns a list of contact&amp;#39;s identities&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>returns a list of contact&#39;s identities</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Contact&amp;#39;s identities returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact&#39;s identities returned</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Contact not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/identities</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/identities</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-176\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-177\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-178\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-179\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-180\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-181\" data-rttab=\"true\">404</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-177\" aria-labelledby=\"react-tabs-176\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"identifier\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"privacy_index\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-179\" aria-labelledby=\"react-tabs-178\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-181\" aria-labelledby=\"react-tabs-180\"></div></div></div></div></div></div><div id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/patch\" data-section-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/patch\" aria-label=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/patch\"></a>update tags list for contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update tags list for contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update tags list for contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"additional_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">additional_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"addresses\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand addresses\"><span class=\"property-name\">addresses</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PostalAddress<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"avatar\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">avatar</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_insert\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_insert</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_update\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_update</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"deleted\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">deleted</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"emails\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand emails\"><span class=\"property-name\">emails</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Email<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"family_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">family_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"given_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">given_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"groups\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">groups</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"identities\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand identities\"><span class=\"property-name\">identities</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->SocialIdentity<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"ims\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand ims\"><span class=\"property-name\">ims</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->IM<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"infos\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">infos</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"name_prefix\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">name_prefix</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"name_suffix\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">name_suffix</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"organizations\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand organizations\"><span class=\"property-name\">organizations</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Organization<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"phones\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand phones\"><span class=\"property-name\">phones</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Phone<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"pi\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand pi\"><span class=\"property-name\">pi</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PI<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"privacy_features\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">privacy_features</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PrivacyFeatures<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"public_keys\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand public_keys\"><span class=\"property-name\">public_keys</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PublicKey<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"title\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">title</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Contact&amp;#39;s tags updated. Empty body.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact&#39;s tags updated. Empty body.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden, server is refusing to apply payload to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden, server is refusing to apply payload to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;contact not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>contact not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but payload was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but payload was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;server failed to execute the request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>server failed to execute the request</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/identities</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/identities</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-182\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-183\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-183\" aria-labelledby=\"react-tabs-182\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"$ref\"</span>: <span class=\"token keyword\">null</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"additional_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"addresses\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"city\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"postal_code\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"region\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"street\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"avatar\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"email_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"family_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"given_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"groups\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"ims\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"im_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_prefix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_suffix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"organizations\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"organization_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"department\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"job_description\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"phones\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phone_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"normalized_number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"uri\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-184\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-185\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-186\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-187\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-188\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-189\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-190\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-191\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-192\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-193\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-194\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-195\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-185\" aria-labelledby=\"react-tabs-184\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-187\" aria-labelledby=\"react-tabs-186\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-189\" aria-labelledby=\"react-tabs-188\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-191\" aria-labelledby=\"react-tabs-190\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-193\" aria-labelledby=\"react-tabs-192\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-195\" aria-labelledby=\"react-tabs-194\"></div></div></div></div></div></div><div id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1tags/patch\" data-section-id=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1tags/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1contacts~1{contact_id}~1tags/patch\" aria-label=\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1tags/patch\"></a>update tags list for contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update tags list for contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update tags list for contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Contact&amp;#39;s tags updated. Empty body.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact&#39;s tags updated. Empty body.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden, server is refusing to apply payload to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden, server is refusing to apply payload to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;contact not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>contact not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but payload was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but payload was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;server failed to execute the request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>server failed to execute the request</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/tags</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/tags</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-196\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-197\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-197\" aria-labelledby=\"react-tabs-196\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-198\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-199\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-200\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-201\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-202\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-203\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-204\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-205\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-206\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-207\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-208\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-209\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-199\" aria-labelledby=\"react-tabs-198\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-201\" aria-labelledby=\"react-tabs-200\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-203\" aria-labelledby=\"react-tabs-202\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-205\" aria-labelledby=\"react-tabs-204\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-207\" aria-labelledby=\"react-tabs-206\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-209\" aria-labelledby=\"react-tabs-208\"></div></div></div></div></div></div><div id=\"tag/contacts/paths/~1v2~1search/get\" data-section-id=\"tag/contacts/paths/~1v2~1search/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1search/get\" aria-label=\"tag/contacts/paths/~1v2~1search/get\"></a>Simple API to execute full-text searches within us<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Simple API to execute full-text searches within user&amp;#39;s indexes. A more complexe API will be available with a POST verb.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Simple API to execute full-text searches within user&#39;s indexes. A more complexe API will be available with a POST verb.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"term\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">term</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span> <span class=\"sc-gTgyBf sc-clsGLE ihUpGF bThqtX\"> <!-- -->&gt;= 3 characters<!-- --> </span></span></div> <div><div html=\"&lt;p&gt;the search string&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>the search string</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"field\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">field</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;name of a field on which to perform the search. If omitted defaults to « _all ».&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>name of a field on which to perform the search. If omitted defaults to « _all ».</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"doctype\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">doctype</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;message&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;contact&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;&quot;</span> </div> <div><div html=\"&lt;p&gt;type of documents to narrow the search to.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>type of documents to narrow the search to.</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"limit\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">limit</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of documents to return per page, but only if param «type» is present.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of documents to return per page, but only if param «type» is present.</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"offset\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">offset</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of pages to skip from the response, but only if param «type» is present.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of pages to skip from the response, but only if param «type» is present.</p>\n</div></div></div></td></tr></tbody></table></div><div><h5 class=\"sc-iqHXzD bRzaSI\">header<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"X-Caliopen-IL\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-IL</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">-10;10</span></div> <div><div html=\"&lt;p&gt;The Importance Level range requested in form of &lt;code&gt;-10;10&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The Importance Level range requested in form of <code>-10;10</code></p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;an object holding an array of documents found. Docs are assembled by type.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>an object holding an array of documents found. Docs are assembled by type.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/search</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/search</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-210\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-211\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-212\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-213\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-214\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-215\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-211\" aria-labelledby=\"react-tabs-210\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_hits\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"messages\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"score\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"highlights\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"document\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_hits\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contacts\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"score\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"highlights\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"document\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-213\" aria-labelledby=\"react-tabs-212\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-215\" aria-labelledby=\"react-tabs-214\"></div></div></div></div></div></div><div id=\"tag/contacts/paths/~1v2~1search/post\" data-section-id=\"tag/contacts/paths/~1v2~1search/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/contacts/paths/~1v2~1search/post\" aria-label=\"tag/contacts/paths/~1v2~1search/post\"></a>Not yet implemented. Future route for more complex<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Not yet implemented. Future route for more complexe searches.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Not yet implemented. Future route for more complexe searches.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">501<!-- --> </strong><div html=\"&lt;p&gt;route not implemented.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>route not implemented.</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/search</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/search</div></div></div></div></div></div></div></div><div id=\"tag/tags\" data-section-id=\"tag/tags\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/tags\" aria-label=\"tag/tags\"></a>tags</h1></div></div></div><div id=\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1identities/patch\" data-section-id=\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1identities/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/tags/paths/~1v2~1contacts~1{contact_id}~1identities/patch\" aria-label=\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1identities/patch\"></a>update tags list for contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update tags list for contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update tags list for contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"additional_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">additional_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"addresses\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand addresses\"><span class=\"property-name\">addresses</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PostalAddress<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"avatar\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">avatar</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_insert\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_insert</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_update\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_update</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"deleted\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">deleted</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"emails\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand emails\"><span class=\"property-name\">emails</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Email<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"family_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">family_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"given_name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">given_name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"groups\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">groups</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"identities\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand identities\"><span class=\"property-name\">identities</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->SocialIdentity<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"ims\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand ims\"><span class=\"property-name\">ims</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->IM<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"infos\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">infos</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"name_prefix\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">name_prefix</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"name_suffix\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">name_suffix</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"organizations\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand organizations\"><span class=\"property-name\">organizations</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Organization<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"phones\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand phones\"><span class=\"property-name\">phones</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Phone<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"pi\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand pi\"><span class=\"property-name\">pi</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PI<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"privacy_features\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">privacy_features</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PrivacyFeatures<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"public_keys\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand public_keys\"><span class=\"property-name\">public_keys</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PublicKey<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"title\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">title</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Contact&amp;#39;s tags updated. Empty body.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact&#39;s tags updated. Empty body.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden, server is refusing to apply payload to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden, server is refusing to apply payload to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;contact not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>contact not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but payload was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but payload was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;server failed to execute the request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>server failed to execute the request</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/identities</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/identities</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-216\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-217\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-217\" aria-labelledby=\"react-tabs-216\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"$ref\"</span>: <span class=\"token keyword\">null</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"additional_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"addresses\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"city\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"postal_code\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"region\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"street\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"avatar\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"email_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"family_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"given_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"groups\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"ims\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"im_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"infos\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_prefix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name_suffix\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"organizations\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"deleted\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"organization_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"department\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"job_description\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"phones\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"phone_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"normalized_number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_primary\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"number\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"uri\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"title\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-218\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-219\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-220\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-221\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-222\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-223\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-224\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-225\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-226\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-227\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-228\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-229\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-219\" aria-labelledby=\"react-tabs-218\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-221\" aria-labelledby=\"react-tabs-220\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-223\" aria-labelledby=\"react-tabs-222\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-225\" aria-labelledby=\"react-tabs-224\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-227\" aria-labelledby=\"react-tabs-226\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-229\" aria-labelledby=\"react-tabs-228\"></div></div></div></div></div></div><div id=\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1tags/patch\" data-section-id=\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1tags/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/tags/paths/~1v2~1contacts~1{contact_id}~1tags/patch\" aria-label=\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1tags/patch\"></a>update tags list for contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update tags list for contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update tags list for contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Contact&amp;#39;s tags updated. Empty body.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Contact&#39;s tags updated. Empty body.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden, server is refusing to apply payload to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden, server is refusing to apply payload to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;contact not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>contact not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but payload was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but payload was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;server failed to execute the request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>server failed to execute the request</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/tags</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/tags</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-230\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-231\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-231\" aria-labelledby=\"react-tabs-230\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-232\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-233\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-234\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-235\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-236\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-237\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-238\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-239\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-240\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-241\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-242\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-243\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-233\" aria-labelledby=\"react-tabs-232\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-235\" aria-labelledby=\"react-tabs-234\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-237\" aria-labelledby=\"react-tabs-236\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-239\" aria-labelledby=\"react-tabs-238\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-241\" aria-labelledby=\"react-tabs-240\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-243\" aria-labelledby=\"react-tabs-242\"></div></div></div></div></div></div><div id=\"tag/tags/paths/~1v2~1messages~1{message_id}~1tags/patch\" data-section-id=\"tag/tags/paths/~1v2~1messages~1{message_id}~1tags/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/tags/paths/~1v2~1messages~1{message_id}~1tags/patch\" aria-label=\"tag/tags/paths/~1v2~1messages~1{message_id}~1tags/patch\"></a>update tags list for message<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update tags list for message&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update tags list for message</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;message&amp;#39;s tags updated. Empty body.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message&#39;s tags updated. Empty body.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden, server is refusing to apply payload to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden, server is refusing to apply payload to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but payload was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but payload was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;server failed to execute the request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>server failed to execute the request</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}/tags</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}/tags</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-244\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-245\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-245\" aria-labelledby=\"react-tabs-244\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-246\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-247\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-248\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-249\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-250\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-251\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-252\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-253\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-254\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-255\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-256\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-257\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-247\" aria-labelledby=\"react-tabs-246\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-249\" aria-labelledby=\"react-tabs-248\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-251\" aria-labelledby=\"react-tabs-250\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-253\" aria-labelledby=\"react-tabs-252\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-255\" aria-labelledby=\"react-tabs-254\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-257\" aria-labelledby=\"react-tabs-256\"></div></div></div></div></div></div><div id=\"tag/tags/paths/~1v2~1tags/get\" data-section-id=\"tag/tags/paths/~1v2~1tags/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/tags/paths/~1v2~1tags/get\" aria-label=\"tag/tags/paths/~1v2~1tags/get\"></a>Returns tags visible to current user according to <!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns tags visible to current user according to given parameters&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns tags visible to current user according to given parameters</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Tags returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Tags returned</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/tags</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/tags</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-258\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-259\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-259\" aria-labelledby=\"react-tabs-258\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;user&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/tags/paths/~1v2~1tags/post\" data-section-id=\"tag/tags/paths/~1v2~1tags/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/tags/paths/~1v2~1tags/post\" aria-label=\"tag/tags/paths/~1v2~1tags/post\"></a>Create a new Tag for an user<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Create a new Tag for an user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Create a new Tag for an user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"label\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">label</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"importance_level\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">importance_level</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->int32<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;User tag creation completed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>User tag creation completed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;Unprocessable entity. Parameters were valid but the server was unable to create the tag (probably because of a semantically erroneous document, like an empty tag)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unprocessable entity. Parameters were valid but the server was unable to create the tag (probably because of a semantically erroneous document, like an empty tag)</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/tags</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/tags</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-260\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-261\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-261\" aria-labelledby=\"react-tabs-260\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-262\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-263\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-264\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-265\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-266\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-267\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-268\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-269\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-263\" aria-labelledby=\"react-tabs-262\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"location\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-265\" aria-labelledby=\"react-tabs-264\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-267\" aria-labelledby=\"react-tabs-266\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-269\" aria-labelledby=\"react-tabs-268\"></div></div></div></div></div></div><div id=\"tag/tags/paths/~1v2~1tags~1{tag_id}/get\" data-section-id=\"tag/tags/paths/~1v2~1tags~1{tag_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/tags/paths/~1v2~1tags~1{tag_id}/get\" aria-label=\"tag/tags/paths/~1v2~1tags~1{tag_id}/get\"></a>Retrieve tag infos<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Retrieve tag infos&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Retrieve tag infos</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"tag_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tag_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Successful response with json object&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful response with json object</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/tags/{tag_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/tags/{tag_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-270\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-271\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-272\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-273\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-271\" aria-labelledby=\"react-tabs-270\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;user&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-273\" aria-labelledby=\"react-tabs-272\"></div></div></div></div></div></div><div id=\"tag/tags/paths/~1v2~1tags~1{tag_id}/patch\" data-section-id=\"tag/tags/paths/~1v2~1tags~1{tag_id}/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/tags/paths/~1v2~1tags~1{tag_id}/patch\" aria-label=\"tag/tags/paths/~1v2~1tags~1{tag_id}/patch\"></a>update a tag<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update a tag&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update a tag</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"tag_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tag_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"label\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">label</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"importance_level\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">importance_level</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->int32<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden patch. Server is refusing to apply the given patch&amp;#39;s properties to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden patch. Server is refusing to apply the given patch&#39;s properties to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;tag not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>tag not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">409<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch conflicts with db&amp;#39;s current state&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch conflicts with db&#39;s current state</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/tags/{tag_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/tags/{tag_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-274\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-275\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-275\" aria-labelledby=\"react-tabs-274\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-276\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-277\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-278\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-279\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-280\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-281\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-282\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-283\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-284\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-285\" data-rttab=\"true\">409</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-286\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-287\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-277\" aria-labelledby=\"react-tabs-276\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-279\" aria-labelledby=\"react-tabs-278\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-281\" aria-labelledby=\"react-tabs-280\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-283\" aria-labelledby=\"react-tabs-282\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-285\" aria-labelledby=\"react-tabs-284\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-287\" aria-labelledby=\"react-tabs-286\"></div></div></div></div></div></div><div id=\"tag/tags/paths/~1v2~1tags~1{tag_id}/delete\" data-section-id=\"tag/tags/paths/~1v2~1tags~1{tag_id}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/tags/paths/~1v2~1tags~1{tag_id}/delete\" aria-label=\"tag/tags/paths/~1v2~1tags~1{tag_id}/delete\"></a>Delete a tag belonging to an user<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Delete a tag belonging to an user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Delete a tag belonging to an user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"tag_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tag_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Successful deletion&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful deletion</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/tags/{tag_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/tags/{tag_id}</div></div></div></div></div></div></div></div><div id=\"tag/pgp\" data-section-id=\"tag/pgp\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/pgp\" aria-label=\"tag/pgp\"></a>pgp</h1></div></div></div><div id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\" data-section-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\" aria-label=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\"></a>Add a pgp public key to a contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Add a pgp public key to a contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Add a pgp public key to a contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"key\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">key</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;DER or PEM key, base64 encoded&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>DER or PEM key, base64 encoded</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"label\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">label</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Public key created&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Public key created</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;Unprocessable entity. Parameters were valid but the server was unable to create the key (probably because of a semantically erroneous document)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unprocessable entity. Parameters were valid but the server was unable to create the key (probably because of a semantically erroneous document)</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-288\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-289\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-289\" aria-labelledby=\"react-tabs-288\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-290\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-291\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-292\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-293\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-294\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-295\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-296\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-297\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-291\" aria-labelledby=\"react-tabs-290\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"location\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"publickey_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-293\" aria-labelledby=\"react-tabs-292\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-295\" aria-labelledby=\"react-tabs-294\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-297\" aria-labelledby=\"react-tabs-296\"></div></div></div></div></div></div><div id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\" data-section-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\" aria-label=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\"></a>Returns all publickeys linked to contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns all publickeys linked to contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns all publickeys linked to contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;List of public keys&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>List of public keys</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-298\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-299\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-299\" aria-labelledby=\"react-tabs-298\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pubkeys\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\" data-section-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\" aria-label=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\"></a>Retrieve publickey<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Retrieve publickey&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Retrieve publickey</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"pubkey_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">pubkey_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Successful response with json object&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful response with json object</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys/{pubkey_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys/{pubkey_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-300\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-301\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-302\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-303\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-301\" aria-labelledby=\"react-tabs-300\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-303\" aria-labelledby=\"react-tabs-302\"></div></div></div></div></div></div><div id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\" data-section-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\" aria-label=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\"></a>update a public key<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update a public key&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update a public key</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"pubkey_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">pubkey_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. Property &lt;em&gt;label&lt;/em&gt; is the only one patchable.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. Property <em>label</em> is the only one patchable.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"label\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">label</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden patch. Server is refusing to apply the given patch&amp;#39;s properties to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden patch. Server is refusing to apply the given patch&#39;s properties to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;public key not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>public key not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">409<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch conflicts with db&amp;#39;s current state&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch conflicts with db&#39;s current state</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys/{pubkey_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys/{pubkey_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-304\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-305\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-305\" aria-labelledby=\"react-tabs-304\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-306\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-307\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-308\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-309\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-310\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-311\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-312\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-313\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-314\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-315\" data-rttab=\"true\">409</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-316\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-317\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-307\" aria-labelledby=\"react-tabs-306\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-309\" aria-labelledby=\"react-tabs-308\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-311\" aria-labelledby=\"react-tabs-310\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-313\" aria-labelledby=\"react-tabs-312\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-315\" aria-labelledby=\"react-tabs-314\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-317\" aria-labelledby=\"react-tabs-316\"></div></div></div></div></div></div><div id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\" data-section-id=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\" aria-label=\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\"></a>Delete a public key<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Delete a public key&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Delete a public key</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"pubkey_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">pubkey_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Successful deletion&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful deletion</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys/{pubkey_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys/{pubkey_id}</div></div></div></div></div></div></div></div><div id=\"tag/keys\" data-section-id=\"tag/keys\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/keys\" aria-label=\"tag/keys\"></a>keys</h1></div></div></div><div id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\" data-section-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\" aria-label=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\"></a>Add a pgp public key to a contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Add a pgp public key to a contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Add a pgp public key to a contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"key\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">key</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;DER or PEM key, base64 encoded&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>DER or PEM key, base64 encoded</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"label\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">label</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Public key created&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Public key created</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;Unprocessable entity. Parameters were valid but the server was unable to create the key (probably because of a semantically erroneous document)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unprocessable entity. Parameters were valid but the server was unable to create the key (probably because of a semantically erroneous document)</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-318\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-319\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-319\" aria-labelledby=\"react-tabs-318\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-320\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-321\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-322\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-323\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-324\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-325\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-326\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-327\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-321\" aria-labelledby=\"react-tabs-320\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"location\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"publickey_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-323\" aria-labelledby=\"react-tabs-322\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-325\" aria-labelledby=\"react-tabs-324\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-327\" aria-labelledby=\"react-tabs-326\"></div></div></div></div></div></div><div id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\" data-section-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\" aria-label=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\"></a>Returns all publickeys linked to contact<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns all publickeys linked to contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns all publickeys linked to contact</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;List of public keys&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>List of public keys</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-328\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-329\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-329\" aria-labelledby=\"react-tabs-328\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pubkeys\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\" data-section-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\" aria-label=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\"></a>Retrieve publickey<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Retrieve publickey&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Retrieve publickey</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"pubkey_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">pubkey_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Successful response with json object&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful response with json object</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys/{pubkey_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys/{pubkey_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-330\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-331\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-332\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-333\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-331\" aria-labelledby=\"react-tabs-330\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-333\" aria-labelledby=\"react-tabs-332\"></div></div></div></div></div></div><div id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\" data-section-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\" aria-label=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\"></a>update a public key<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update a public key&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update a public key</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"pubkey_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">pubkey_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. Property &lt;em&gt;label&lt;/em&gt; is the only one patchable.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. Property <em>label</em> is the only one patchable.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"label\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">label</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden patch. Server is refusing to apply the given patch&amp;#39;s properties to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden patch. Server is refusing to apply the given patch&#39;s properties to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;public key not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>public key not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">409<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch conflicts with db&amp;#39;s current state&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch conflicts with db&#39;s current state</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys/{pubkey_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys/{pubkey_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-334\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-335\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-335\" aria-labelledby=\"react-tabs-334\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-336\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-337\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-338\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-339\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-340\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-341\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-342\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-343\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-344\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-345\" data-rttab=\"true\">409</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-346\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-347\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-337\" aria-labelledby=\"react-tabs-336\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-339\" aria-labelledby=\"react-tabs-338\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-341\" aria-labelledby=\"react-tabs-340\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-343\" aria-labelledby=\"react-tabs-342\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-345\" aria-labelledby=\"react-tabs-344\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-347\" aria-labelledby=\"react-tabs-346\"></div></div></div></div></div></div><div id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\" data-section-id=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\" aria-label=\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\"></a>Delete a public key<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Delete a public key&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Delete a public key</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"contact_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"pubkey_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">pubkey_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Successful deletion&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful deletion</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/contacts/{contact_id}/publickeys/{pubkey_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/contacts/{contact_id}/publickeys/{pubkey_id}</div></div></div></div></div></div></div></div><div id=\"/paths/~1v2~1imports/post\" data-section-id=\"/paths/~1v2~1imports/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#/paths/~1v2~1imports/post\" aria-label=\"/paths/~1v2~1imports/post\"></a>/v2/imports<!-- --> </h2><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">multipart/form-data</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"file\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">file</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->binary<!-- -->&gt;<!-- --> </span></div> <div><div html=\"&lt;p&gt;the vcard file to upload&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>the vcard file to upload</p>\n</div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Import successful&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Import successful</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;Syntax error&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Syntax error</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;File valid but we can create the new contact&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>File valid but we can create the new contact</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/imports</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/imports</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-348\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-349\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-350\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-351\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-349\" aria-labelledby=\"react-tabs-348\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-351\" aria-labelledby=\"react-tabs-350\"></div></div></div></div></div></div><div id=\"tag/discussions\" data-section-id=\"tag/discussions\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/discussions\" aria-label=\"tag/discussions\"></a>discussions</h1></div></div></div><div id=\"tag/discussions/paths/~1v2~1discussions/get\" data-section-id=\"tag/discussions/paths/~1v2~1discussions/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/discussions/paths/~1v2~1discussions/get\" aria-label=\"tag/discussions/paths/~1v2~1discussions/get\"></a>Returns the list of discussions for current user a<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns the list of discussions for current user according to given filter&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns the list of discussions for current user according to given filter</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"limit\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">limit</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of discussions to return per page&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of discussions to return per page</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"offset\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">offset</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of discussions to skip for pagination&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of discussions to skip for pagination</p>\n</div></div></div></td></tr></tbody></table></div><div><h5 class=\"sc-iqHXzD bRzaSI\">header<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"X-Caliopen-PI\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-PI</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">0;100</span></div> <div><div html=\"&lt;p&gt;The PI range requested in form of &lt;code&gt;0;100&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The PI range requested in form of <code>0;100</code></p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"X-Caliopen-IL\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-IL</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">-10;10</span></div> <div><div html=\"&lt;p&gt;The Importance Level range requested in form of &lt;code&gt;-10;10&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The Importance Level range requested in form of <code>-10;10</code></p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Discussions returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Discussions returned</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/discussions</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/discussions</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-352\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-353\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-354\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-355\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-356\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-357\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-353\" aria-labelledby=\"react-tabs-352\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussions\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"attachment_count\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"total_count\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"unread_count\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"aliases\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"last_message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"last_message_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"last_message_subject\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-355\" aria-labelledby=\"react-tabs-354\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-357\" aria-labelledby=\"react-tabs-356\"></div></div></div></div></div></div><div id=\"tag/discussions/paths/~1v2~1discussions~1{discussion_id}/get\" data-section-id=\"tag/discussions/paths/~1v2~1discussions~1{discussion_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/discussions/paths/~1v2~1discussions~1{discussion_id}/get\" aria-label=\"tag/discussions/paths/~1v2~1discussions~1{discussion_id}/get\"></a>Returns metadata of a discussion<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns metadata of a discussion&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns metadata of a discussion</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"discussion_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">discussion_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Discussion found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Discussion found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Discussion not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Discussion not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/discussions/{discussion_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/discussions/{discussion_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-358\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-359\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-360\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-361\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-362\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-363\" data-rttab=\"true\">404</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-359\" aria-labelledby=\"react-tabs-358\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"attachment_count\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"total_count\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"unread_count\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"aliases\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"last_message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"last_message_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"last_message_subject\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-361\" aria-labelledby=\"react-tabs-360\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-363\" aria-labelledby=\"react-tabs-362\"></div></div></div></div></div></div><div id=\"tag/messages\" data-section-id=\"tag/messages\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages\" aria-label=\"tag/messages\"></a>messages</h1></div></div></div><div id=\"tag/messages/paths/~1v1~1messages/post\" data-section-id=\"tag/messages/paths/~1v1~1messages/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v1~1messages/post\" aria-label=\"tag/messages/paths/~1v1~1messages/post\"></a>Create a new Message (draft) for an user<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Create a new Message (draft) for an user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Create a new Message (draft) for an user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"attachments\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand attachments\"><span class=\"property-name\">attachments</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Attachment<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"body\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">body</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"discussion_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">discussion_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"user_identities\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_identities</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"parent_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">parent_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"participants\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand participants\"><span class=\"property-name\">participants</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Participant<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"subject\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">subject</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"privacy_features\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">privacy_features</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PrivacyFeatures<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Draft creation completed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Draft creation completed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;Unprocessable entity. Parameters were valid but the server was unable to create the draft (probably because of a semantically erroneous document, like an empty draft)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unprocessable entity. Parameters were valid but the server was unable to create the draft (probably because of a semantically erroneous document, like an empty draft)</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v1/messages</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v1/messages</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-364\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-365\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-365\" aria-labelledby=\"react-tabs-364\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-366\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-367\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-368\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-369\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-370\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-371\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-372\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-373\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-367\" aria-labelledby=\"react-tabs-366\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"location\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-369\" aria-labelledby=\"react-tabs-368\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-371\" aria-labelledby=\"react-tabs-370\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-373\" aria-labelledby=\"react-tabs-372\"></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v2~1messages/get\" data-section-id=\"tag/messages/paths/~1v2~1messages/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v2~1messages/get\" aria-label=\"tag/messages/paths/~1v2~1messages/get\"></a>Returns the list of messages for current user acco<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns the list of messages for current user according to given parameters/filter&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns the list of messages for current user according to given parameters/filter</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"discussion_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">discussion_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;filter messages belonging to a specific discussion&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>filter messages belonging to a specific discussion</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"limit\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">limit</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of messages to return per page&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of messages to return per page</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"offset\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">offset</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of pages to skip from the response&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of pages to skip from the response</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"msg_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">msg_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;if provided with range[] param, specify a message_id around which messages will be fetched&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>if provided with range[] param, specify a message_id around which messages will be fetched</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"range[]\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">range[]</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;boundaries param if message_id param is provided [before, after]&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>boundaries param if message_id param is provided [before, after]</p>\n</div></div></div></td></tr></tbody></table></div><div><h5 class=\"sc-iqHXzD bRzaSI\">header<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"X-Caliopen-PI\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-PI</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">0;100</span></div> <div><div html=\"&lt;p&gt;The PI range requested in form of &lt;code&gt;0;100&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The PI range requested in form of <code>0;100</code></p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"X-Caliopen-IL\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-IL</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">-10;10</span></div> <div><div html=\"&lt;p&gt;The Importance Level range requested in form of &lt;code&gt;-10;10&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The Importance Level range requested in form of <code>-10;10</code></p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Messages returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Messages returned</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-374\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-375\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-376\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-377\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-378\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-379\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-375\" aria-labelledby=\"react-tabs-374\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"messages\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-377\" aria-labelledby=\"react-tabs-376\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-379\" aria-labelledby=\"react-tabs-378\"></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v1~1messages~1{message_id}/patch\" data-section-id=\"tag/messages/paths/~1v1~1messages~1{message_id}/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v1~1messages~1{message_id}/patch\" aria-label=\"tag/messages/paths/~1v1~1messages~1{message_id}/patch\"></a>update a draft with rfc5789 and rfc7396 specificat<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update a draft with rfc5789 and rfc7396 specifications&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update a draft with rfc5789 and rfc7396 specifications</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"attachments\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand attachments\"><span class=\"property-name\">attachments</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Attachment<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"body\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">body</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"body_is_plain\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">body_is_plain</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_delete\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_delete</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_insert\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_insert</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"date_sort\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">date_sort</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"discussion_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">discussion_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"external_references\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand external_references\"><span class=\"property-name\">external_references</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->ExternalReferences<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"excerpt\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">excerpt</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"user_identities\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_identities</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"importance_level\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">importance_level</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->int32<!-- -->&gt;<!-- --> </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_answered\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_answered</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_draft\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_draft</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_unread\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_unread</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"is_received\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">is_received</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">boolean</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"parent_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">parent_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"participants\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand participants\"><span class=\"property-name\">participants</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->Participant<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"privacy_features\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">privacy_features</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PrivacyFeatures<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"pi\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand pi\"><span class=\"property-name\">pi</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PI<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"pi_message\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand pi_message\"><span class=\"property-name\">pi_message</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PIMessage<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"raw_msg_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">raw_msg_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"subject\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">subject</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"protocol\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">protocol</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"user_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden patch. Server is refusing to apply the given patch&amp;#39;s properties to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden patch. Server is refusing to apply the given patch&#39;s properties to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v1/messages/{message_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v1/messages/{message_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-380\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-381\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-381\" aria-labelledby=\"react-tabs-380\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"$ref\"</span>: <span class=\"token keyword\">null</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v1~1messages~1{message_id}/delete\" data-section-id=\"tag/messages/paths/~1v1~1messages~1{message_id}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v1~1messages~1{message_id}/delete\" aria-label=\"tag/messages/paths/~1v1~1messages~1{message_id}/delete\"></a>Delete a message belonging to an user<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Delete a message belonging to an user&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Delete a message belonging to an user</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Successful deletion. No body is returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful deletion. No body is returned</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden action. Server is refusing to delete this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden action. Server is refusing to delete this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v1/messages/{message_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v1/messages/{message_id}</div></div></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v2~1messages~1{message_id}/get\" data-section-id=\"tag/messages/paths/~1v2~1messages~1{message_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v2~1messages~1{message_id}/get\" aria-label=\"tag/messages/paths/~1v2~1messages~1{message_id}/get\"></a>returns a message<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;returns a message&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>returns a message</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Message found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Message found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Message not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-382\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-383\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-384\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-385\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-386\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-387\" data-rttab=\"true\">404</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-383\" aria-labelledby=\"react-tabs-382\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-385\" aria-labelledby=\"react-tabs-384\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-387\" aria-labelledby=\"react-tabs-386\"></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1tags/patch\" data-section-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1tags/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v2~1messages~1{message_id}~1tags/patch\" aria-label=\"tag/messages/paths/~1v2~1messages~1{message_id}~1tags/patch\"></a>update tags list for message<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update tags list for message&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update tags list for message</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"tags\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">tags</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;message&amp;#39;s tags updated. Empty body.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message&#39;s tags updated. Empty body.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden, server is refusing to apply payload to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden, server is refusing to apply payload to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but payload was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but payload was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;server failed to execute the request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>server failed to execute the request</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}/tags</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}/tags</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-388\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-389\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-389\" aria-labelledby=\"react-tabs-388\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-390\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-391\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-392\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-393\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-394\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-395\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-396\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-397\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-398\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-399\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-400\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-401\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-391\" aria-labelledby=\"react-tabs-390\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-393\" aria-labelledby=\"react-tabs-392\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-395\" aria-labelledby=\"react-tabs-394\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-397\" aria-labelledby=\"react-tabs-396\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-399\" aria-labelledby=\"react-tabs-398\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-401\" aria-labelledby=\"react-tabs-400\"></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1actions/post\" data-section-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1actions/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v2~1messages~1{message_id}~1actions/post\" aria-label=\"tag/messages/paths/~1v2~1messages~1{message_id}~1actions/post\"></a>send an order to execute one (or many) action(s) f<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;send an order to execute one (or many) action(s) for the given message : send, etc. A successful execution of the action will probably modify one or more message&amp;#39;s attribute(s)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>send an order to execute one (or many) action(s) for the given message : send, etc. A successful execution of the action will probably modify one or more message&#39;s attribute(s)</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"actions\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">actions</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div><div><span class=\"sc-gTgyBf ihUpGF\">Items<!-- --> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;send&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;set_read&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;set_unread&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;reset_password&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;delete&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;device-validation&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"params\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">params</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;action(s) successfully executed. Message is sent back with its modified attributes (if any)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>action(s) successfully executed. Message is sent back with its modified attributes (if any)</p>\n</div></button></div><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;action(s) successfully executed. Nothing returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>action(s) successfully executed. Nothing returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden action. Server is refusing to execute the given action(s) to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden action. Server is refusing to execute the given action(s) to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Message not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but action was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but action was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;execution of action failed.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>execution of action failed.</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}/actions</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}/actions</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-402\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-403\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-403\" aria-labelledby=\"react-tabs-402\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"actions\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;send&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"params\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-404\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-405\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-success\" role=\"tab\" id=\"react-tabs-406\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-407\" data-rttab=\"true\">204</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-408\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-409\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-410\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-411\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-412\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-413\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-414\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-415\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-416\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-417\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-418\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-419\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-405\" aria-labelledby=\"react-tabs-404\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-407\" aria-labelledby=\"react-tabs-406\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-409\" aria-labelledby=\"react-tabs-408\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-411\" aria-labelledby=\"react-tabs-410\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-413\" aria-labelledby=\"react-tabs-412\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-415\" aria-labelledby=\"react-tabs-414\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-417\" aria-labelledby=\"react-tabs-416\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-419\" aria-labelledby=\"react-tabs-418\"></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments/post\" data-section-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v2~1messages~1{message_id}~1attachments/post\" aria-label=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments/post\"></a>(for draft only) upload a file to server and add a<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;(for draft only) upload a file to server and add attachment reference to the draft.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>(for draft only) upload a file to server and add attachment reference to the draft.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">multipart/form-data</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"attachment\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">attachment</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->binary<!-- -->&gt;<!-- --> </span></div> <div><div html=\"&lt;p&gt;the attachment file to upload&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>the attachment file to upload</p>\n</div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">201<!-- --> </strong><div html=\"&lt;p&gt;File uploaded sucessfully. Backend sends back attachment&amp;#39;s temp_id.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>File uploaded sucessfully. Backend sends back attachment&#39;s temp_id.</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;bad request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>bad request</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Not Found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Not Found</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">413<!-- --> </strong><div html=\"&lt;p&gt;Request entity too large&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Request entity too large</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">415<!-- --> </strong><div html=\"&lt;p&gt;Unsupported media type&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unsupported media type</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}/attachments</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}/attachments</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-420\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-421\" tabindex=\"0\" data-rttab=\"true\">201</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-421\" aria-labelledby=\"react-tabs-420\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\" data-section-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\" aria-label=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\"></a>Download file from server<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Download file from server&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Download file from server</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"attachment_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">attachment_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;attachment position within message&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>attachment position within message</p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;the attachment, in the &amp;#39;content-transfer-encoding&amp;#39; format of the part.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>the attachment, in the &#39;content-transfer-encoding&#39; format of the part.</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;bad request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>bad request</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message or attachment not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message or attachment not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}/attachments/{attachment_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}/attachments/{attachment_id}</div></div></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\" data-section-id=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\" aria-label=\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\"></a>(for drafts only) delete temporary file and remove<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;(for drafts only) delete temporary file and remove attachment reference from the draft.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>(for drafts only) delete temporary file and remove attachment reference from the draft.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"attachment_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">attachment_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;attachment&amp;#39;s temporary id.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>attachment&#39;s temporary id.</p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Successful deletion. No body is returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful deletion. No body is returned</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden action. Server is refusing to delete this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden action. Server is refusing to delete this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message or attachment not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message or attachment not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}/attachments/{attachment_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}/attachments/{attachment_id}</div></div></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v1~1raws~1{raw_msg_id}/get\" data-section-id=\"tag/messages/paths/~1v1~1raws~1{raw_msg_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v1~1raws~1{raw_msg_id}/get\" aria-label=\"tag/messages/paths/~1v1~1raws~1{raw_msg_id}/get\"></a>Returns a raw message<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns a raw message&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns a raw message</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"raw_msg_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">raw_msg_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;the raw message&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>the raw message</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v1/raws/{raw_msg_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v1/raws/{raw_msg_id}</div></div></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v2~1search/get\" data-section-id=\"tag/messages/paths/~1v2~1search/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v2~1search/get\" aria-label=\"tag/messages/paths/~1v2~1search/get\"></a>Simple API to execute full-text searches within us<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Simple API to execute full-text searches within user&amp;#39;s indexes. A more complexe API will be available with a POST verb.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Simple API to execute full-text searches within user&#39;s indexes. A more complexe API will be available with a POST verb.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"term\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">term</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span> <span class=\"sc-gTgyBf sc-clsGLE ihUpGF bThqtX\"> <!-- -->&gt;= 3 characters<!-- --> </span></span></div> <div><div html=\"&lt;p&gt;the search string&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>the search string</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"field\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">field</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;name of a field on which to perform the search. If omitted defaults to « _all ».&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>name of a field on which to perform the search. If omitted defaults to « _all ».</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"doctype\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">doctype</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;message&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;contact&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;&quot;</span> </div> <div><div html=\"&lt;p&gt;type of documents to narrow the search to.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>type of documents to narrow the search to.</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"limit\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">limit</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of documents to return per page, but only if param «type» is present.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of documents to return per page, but only if param «type» is present.</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"offset\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">offset</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of pages to skip from the response, but only if param «type» is present.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of pages to skip from the response, but only if param «type» is present.</p>\n</div></div></div></td></tr></tbody></table></div><div><h5 class=\"sc-iqHXzD bRzaSI\">header<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"X-Caliopen-IL\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-IL</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">-10;10</span></div> <div><div html=\"&lt;p&gt;The Importance Level range requested in form of &lt;code&gt;-10;10&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The Importance Level range requested in form of <code>-10;10</code></p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;an object holding an array of documents found. Docs are assembled by type.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>an object holding an array of documents found. Docs are assembled by type.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/search</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/search</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-422\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-423\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-424\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-425\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-426\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-427\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-423\" aria-labelledby=\"react-tabs-422\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_hits\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"messages\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"score\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"highlights\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"document\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_hits\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contacts\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"score\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"highlights\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"document\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-425\" aria-labelledby=\"react-tabs-424\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-427\" aria-labelledby=\"react-tabs-426\"></div></div></div></div></div></div><div id=\"tag/messages/paths/~1v2~1search/post\" data-section-id=\"tag/messages/paths/~1v2~1search/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/messages/paths/~1v2~1search/post\" aria-label=\"tag/messages/paths/~1v2~1search/post\"></a>Not yet implemented. Future route for more complex<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Not yet implemented. Future route for more complexe searches.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Not yet implemented. Future route for more complexe searches.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">501<!-- --> </strong><div html=\"&lt;p&gt;route not implemented.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>route not implemented.</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/search</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/search</div></div></div></div></div></div></div></div><div id=\"tag/attachments\" data-section-id=\"tag/attachments\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/attachments\" aria-label=\"tag/attachments\"></a>attachments</h1></div></div></div><div id=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments/post\" data-section-id=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments/post\" aria-label=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments/post\"></a>(for draft only) upload a file to server and add a<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;(for draft only) upload a file to server and add attachment reference to the draft.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>(for draft only) upload a file to server and add attachment reference to the draft.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">multipart/form-data</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"attachment\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">attachment</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->binary<!-- -->&gt;<!-- --> </span></div> <div><div html=\"&lt;p&gt;the attachment file to upload&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>the attachment file to upload</p>\n</div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">201<!-- --> </strong><div html=\"&lt;p&gt;File uploaded sucessfully. Backend sends back attachment&amp;#39;s temp_id.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>File uploaded sucessfully. Backend sends back attachment&#39;s temp_id.</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;bad request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>bad request</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Not Found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Not Found</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">413<!-- --> </strong><div html=\"&lt;p&gt;Request entity too large&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Request entity too large</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">415<!-- --> </strong><div html=\"&lt;p&gt;Unsupported media type&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unsupported media type</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}/attachments</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}/attachments</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-428\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-429\" tabindex=\"0\" data-rttab=\"true\">201</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-429\" aria-labelledby=\"react-tabs-428\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\" data-section-id=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\" aria-label=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\"></a>Download file from server<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Download file from server&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Download file from server</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"attachment_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">attachment_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;attachment position within message&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>attachment position within message</p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;the attachment, in the &amp;#39;content-transfer-encoding&amp;#39; format of the part.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>the attachment, in the &#39;content-transfer-encoding&#39; format of the part.</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;bad request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>bad request</p>\n</div></button></div><div><button class=\"sc-httZfN jifJmL\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message or attachment not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message or attachment not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}/attachments/{attachment_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}/attachments/{attachment_id}</div></div></div></div></div></div></div></div><div id=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\" data-section-id=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\" aria-label=\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\"></a>(for drafts only) delete temporary file and remove<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;(for drafts only) delete temporary file and remove attachment reference from the draft.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>(for drafts only) delete temporary file and remove attachment reference from the draft.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"message_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">message_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"attachment_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">attachment_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;attachment&amp;#39;s temporary id.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>attachment&#39;s temporary id.</p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Successful deletion. No body is returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Successful deletion. No body is returned</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden action. Server is refusing to delete this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden action. Server is refusing to delete this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;message or attachment not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>message or attachment not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/messages/{message_id}/attachments/{attachment_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/messages/{message_id}/attachments/{attachment_id}</div></div></div></div></div></div></div></div><div id=\"tag/devices\" data-section-id=\"tag/devices\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/devices\" aria-label=\"tag/devices\"></a>devices</h1></div></div></div><div id=\"tag/devices/paths/~1v2~1devices/get\" data-section-id=\"tag/devices/paths/~1v2~1devices/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/devices/paths/~1v2~1devices/get\" aria-label=\"tag/devices/paths/~1v2~1devices/get\"></a>Returns devices belonging to current user accordin<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns devices belonging to current user according to given parameters&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns devices belonging to current user according to given parameters</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"limit\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">limit</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of devices to return per page&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of devices to return per page</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"offset\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">offset</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of pages to skip from the response&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of pages to skip from the response</p>\n</div></div></div></td></tr></tbody></table></div><div><h5 class=\"sc-iqHXzD bRzaSI\">header<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"X-Caliopen-PI\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-PI</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">1;100</span></div> <div><div html=\"&lt;p&gt;The PI range requested in form of &lt;code&gt;1;100&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The PI range requested in form of <code>1;100</code></p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Devices returned&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Devices returned</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/devices</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/devices</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-430\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-431\" tabindex=\"0\" data-rttab=\"true\">200</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-431\" aria-labelledby=\"react-tabs-430\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"devices\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"device_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_revoked\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ip_creation\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"status\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_agent\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"locations\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"device_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;other&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/devices/paths/~1v2~1devices~1{device_id}/get\" data-section-id=\"tag/devices/paths/~1v2~1devices~1{device_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/devices/paths/~1v2~1devices~1{device_id}/get\" aria-label=\"tag/devices/paths/~1v2~1devices~1{device_id}/get\"></a>Returns a device<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns a device&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns a device</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"device_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">device_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Device found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Device found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Device not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Device not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/devices/{device_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/devices/{device_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-432\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-433\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-434\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-435\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-436\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-437\" data-rttab=\"true\">404</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-433\" aria-labelledby=\"react-tabs-432\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"device_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_revoked\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"ip_creation\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"status\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_agent\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"locations\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"device_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;other&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-435\" aria-labelledby=\"react-tabs-434\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-437\" aria-labelledby=\"react-tabs-436\"></div></div></div></div></div></div><div id=\"tag/devices/paths/~1v2~1devices~1{device_id}/delete\" data-section-id=\"tag/devices/paths/~1v2~1devices~1{device_id}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/devices/paths/~1v2~1devices~1{device_id}/delete\" aria-label=\"tag/devices/paths/~1v2~1devices~1{device_id}/delete\"></a>Delete a device<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Delete a device&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Delete a device</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"device_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">device_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Device deleted&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Device deleted</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Device not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Device not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/devices/{device_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/devices/{device_id}</div></div></div></div></div></div></div></div><div id=\"tag/devices/paths/~1v2~1devices~1{device_id}/patch\" data-section-id=\"tag/devices/paths/~1v2~1devices~1{device_id}/patch\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/devices/paths/~1v2~1devices~1{device_id}/patch\" aria-label=\"tag/devices/paths/~1v2~1devices~1{device_id}/patch\"></a>update a device with rfc5789 and rfc7396 specifica<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;update a device with rfc5789 and rfc7396 specifications&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>update a device with rfc5789 and rfc7396 specifications</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"device_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">device_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"&lt;p&gt;the patch to apply. See &amp;#39;Caliopen Patch RFC&amp;#39; within /doc directory.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>the patch to apply. See &#39;Caliopen Patch RFC&#39; within /doc directory.</p>\n</div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"current_state\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand current_state\"><span class=\"property-name\">current_state</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"device_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">device_id</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"ip_creation\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">ip_creation</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;ip address at creation&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>ip address at creation</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"locations\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand locations\"><span class=\"property-name\">locations</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->DeviceLocation<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"name\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">name</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"public_keys\" class=\"sc-hBEZvw sc-fFubCH sc-jgHCeW gPUvwc gXIfsF hEcUkb\"><span class=\"sc-idOjMB jwsifT\"></span><button aria-label=\"expand public_keys\"><span class=\"property-name\">public_keys</span><svg class=\"sc-dQpIV cKHJqV\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">objects</span><span class=\"sc-gTgyBf sc-jeGSXX ihUpGF KUVXc\"> (<!-- -->PublicKey<!-- -->) </span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"status\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">status</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"type\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">type</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;unknow&quot;</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;other&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;desktop&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;laptop&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;smartphone&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;tablet&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"user_agent\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">user_agent</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Update successful. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Update successful. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden patch. Server is refusing to apply the given patch&amp;#39;s properties to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden patch. Server is refusing to apply the given patch&#39;s properties to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;device not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>device not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;patch was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>patch was semantically malformed or unprocessable</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"patch\" class=\"sc-jXkspL dgogVa http-verb patch\">patch</span><span class=\"sc-fXoxOd jzevEV\">/v2/devices/{device_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/devices/{device_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-438\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-439\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-439\" aria-labelledby=\"react-tabs-438\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"current_state\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"$ref\"</span>: <span class=\"token keyword\">null</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"device_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"ip_creation\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"locations\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"country\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"device_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;other&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"public_keys\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"alg\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"crv\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"date_update\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emails\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"expire_date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"fingerprint\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"kty\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"resource_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"use\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"x\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"y\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"key\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"status\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;other&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_agent\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-440\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-441\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-442\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-443\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-444\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-445\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-446\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-447\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-448\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-449\" data-rttab=\"true\">422</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-441\" aria-labelledby=\"react-tabs-440\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-443\" aria-labelledby=\"react-tabs-442\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-445\" aria-labelledby=\"react-tabs-444\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-447\" aria-labelledby=\"react-tabs-446\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-449\" aria-labelledby=\"react-tabs-448\"></div></div></div></div></div></div><div id=\"tag/devices/paths/~1v2~1devices~1{device_id}~1actions/post\" data-section-id=\"tag/devices/paths/~1v2~1devices~1{device_id}~1actions/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/devices/paths/~1v2~1devices~1{device_id}~1actions/post\" aria-label=\"tag/devices/paths/~1v2~1devices~1{device_id}~1actions/post\"></a>Route to receive orders to trigger actions on a de<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Route to receive orders to trigger actions on a device&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Route to receive orders to trigger actions on a device</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"device_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">device_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"actions\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">actions</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div><div><span class=\"sc-gTgyBf ihUpGF\">Items<!-- --> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;send&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;set_read&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;set_unread&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;reset_password&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;delete&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;device-validation&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"params\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">params</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">object</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;action(s) successfully executed. Nothing returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>action(s) successfully executed. Nothing returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;json payload malformed&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json payload malformed</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">403<!-- --> </strong><div html=\"&lt;p&gt;Forbidden action. Server is refusing to execute the given action(s) to this ressource&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Forbidden action. Server is refusing to execute the given action(s) to this ressource</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Device not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Device not found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">422<!-- --> </strong><div html=\"&lt;p&gt;json is valid but action was semantically malformed or unprocessable&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json is valid but action was semantically malformed or unprocessable</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">424<!-- --> </strong><div html=\"&lt;p&gt;execution of action failed.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>execution of action failed.</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/devices/{device_id}/actions</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/devices/{device_id}/actions</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-450\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-451\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-451\" aria-labelledby=\"react-tabs-450\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"actions\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;send&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"params\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-452\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-453\" tabindex=\"0\" data-rttab=\"true\">204</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-454\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-455\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-456\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-457\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-458\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-459\" data-rttab=\"true\">403</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-460\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-461\" data-rttab=\"true\">404</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-462\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-463\" data-rttab=\"true\">422</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-464\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-465\" data-rttab=\"true\">424</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-453\" aria-labelledby=\"react-tabs-452\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"attachments\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content_type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"is_inline\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"file_name\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"size\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"temp_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"url\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"mime_boundary\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"body_is_plain\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_delete\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_insert\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"date_sort\"</span>: <span class=\"token string\">&quot;2019-08-24T14:15:22Z&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"external_references\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"ancestors_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"excerpt\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_identities\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"importance_level\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_answered\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_draft\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_unread\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"is_received\"</span>: <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"parent_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"participants\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"privacy_features\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"technic\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"context\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"comportment\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"version\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"pi_message\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"content\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"transport\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"social\"</span>: <span class=\"token number\">0</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"raw_msg_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"subject\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"tags\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-455\" aria-labelledby=\"react-tabs-454\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-457\" aria-labelledby=\"react-tabs-456\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-459\" aria-labelledby=\"react-tabs-458\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-461\" aria-labelledby=\"react-tabs-460\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-463\" aria-labelledby=\"react-tabs-462\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-465\" aria-labelledby=\"react-tabs-464\"></div></div></div></div></div></div><div id=\"tag/devices/paths/~1v2~1validate-device~1{token}/get\" data-section-id=\"tag/devices/paths/~1v2~1validate-device~1{token}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/devices/paths/~1v2~1validate-device~1{token}/get\" aria-label=\"tag/devices/paths/~1v2~1validate-device~1{token}/get\"></a>complete device validation if token is valid<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;complete device validation if token is valid&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>complete device validation if token is valid</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"token\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">token</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;Route is valid. No body is returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Route is valid. No body is returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Token not found or invalid&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Token not found or invalid</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/validate-device/{token}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/validate-device/{token}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-466\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-467\" tabindex=\"0\" data-rttab=\"true\">404</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-467\" aria-labelledby=\"react-tabs-466\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div id=\"tag/participants\" data-section-id=\"tag/participants\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/participants\" aria-label=\"tag/participants\"></a>participants</h1></div></div></div><div id=\"tag/participants/paths/~1v2~1participants~1suggest/get\" data-section-id=\"tag/participants/paths/~1v2~1participants~1suggest/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/participants/paths/~1v2~1participants~1suggest/get\" aria-label=\"tag/participants/paths/~1v2~1participants~1suggest/get\"></a>Returns a list of suggestions according to given p<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns a list of suggestions according to given parameters/filter. Search is performed within current user&amp;#39;s indexes (messages &amp;amp; contacts).&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns a list of suggestions according to given parameters/filter. Search is performed within current user&#39;s indexes (messages &amp; contacts).</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"context\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">context</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Value<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;msg_compose&quot;</span> </div> <div><div html=\"&lt;p&gt;current user&amp;#39;s context (to optimize suggest relevance)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>current user&#39;s context (to optimize suggest relevance)</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"q\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">q</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span> <span class=\"sc-gTgyBf sc-clsGLE ihUpGF bThqtX\"> <!-- -->&gt;= 3 characters<!-- --> </span></span></div> <div><div html=\"&lt;p&gt;a string (3 chars at least) from which to perform the suggestion search&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>a string (3 chars at least) from which to perform the suggestion search</p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;a list of suggestions for user to pick-up an identity&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>a list of suggestions for user to pick-up an identity</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/participants/suggest</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/participants/suggest</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-468\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-469\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-470\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-471\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-472\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-473\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-469\" aria-labelledby=\"react-tabs-468\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable \"><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"source\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-471\" aria-labelledby=\"react-tabs-470\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-473\" aria-labelledby=\"react-tabs-472\"></div></div></div></div></div></div><div id=\"tag/participants/paths/~1v2~1participants~1discussion/post\" data-section-id=\"tag/participants/paths/~1v2~1participants~1discussion/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/participants/paths/~1v2~1participants~1discussion/post\" aria-label=\"tag/participants/paths/~1v2~1participants~1discussion/post\"></a>Returns discussion related to a list of participan<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns discussion related to a list of participants&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns discussion related to a list of participants</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><h5 class=\"sc-iqHXzD bRzaSI\">Request Body schema: <span class=\"sc-gWHiUp duVBEA\">application/json</span><div class=\"sc-gTgyBf sc-eJMOVy sc-jmhEHb ihUpGF giWqTo gpLUhw\">required</div></h5><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"></div><div><div class=\"sc-bBXqJn LetjJ\"> Array </div><div class=\"sc-aelVe eCVKtp\"><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"address\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">address</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"contact_ids\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">contact_ids</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\">Array of </span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">strings</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"label\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">label</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"protocol\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">protocol</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"type\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">type</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;To&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;Cc&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;Bcc&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;From&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;Reply-To&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;Sender&quot;</span> </div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div class=\"sc-iwyWfK jqOlfD\"></div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Computed discussion information&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Computed discussion information</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/participants/discussion</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/participants/discussion</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Request samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"react-tabs__tab react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-474\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-475\" tabindex=\"0\" data-rttab=\"true\">Payload</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-475\" aria-labelledby=\"react-tabs-474\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable \"><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_ids\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;To&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></code></div></div></div></div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-476\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-477\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-478\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-479\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-480\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-481\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-477\" aria-labelledby=\"react-tabs-476\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"hash\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"discussion_id\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-479\" aria-labelledby=\"react-tabs-478\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-481\" aria-labelledby=\"react-tabs-480\"></div></div></div></div></div></div><div id=\"tag/suggest\" data-section-id=\"tag/suggest\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/suggest\" aria-label=\"tag/suggest\"></a>suggest</h1></div></div></div><div id=\"tag/suggest/paths/~1v2~1participants~1suggest/get\" data-section-id=\"tag/suggest/paths/~1v2~1participants~1suggest/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/suggest/paths/~1v2~1participants~1suggest/get\" aria-label=\"tag/suggest/paths/~1v2~1participants~1suggest/get\"></a>Returns a list of suggestions according to given p<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns a list of suggestions according to given parameters/filter. Search is performed within current user&amp;#39;s indexes (messages &amp;amp; contacts).&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns a list of suggestions according to given parameters/filter. Search is performed within current user&#39;s indexes (messages &amp; contacts).</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"context\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">context</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Value<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;msg_compose&quot;</span> </div> <div><div html=\"&lt;p&gt;current user&amp;#39;s context (to optimize suggest relevance)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>current user&#39;s context (to optimize suggest relevance)</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"q\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">q</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span> <span class=\"sc-gTgyBf sc-clsGLE ihUpGF bThqtX\"> <!-- -->&gt;= 3 characters<!-- --> </span></span></div> <div><div html=\"&lt;p&gt;a string (3 chars at least) from which to perform the suggestion search&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>a string (3 chars at least) from which to perform the suggestion search</p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;a list of suggestions for user to pick-up an identity&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>a list of suggestions for user to pick-up an identity</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/participants/suggest</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/participants/suggest</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-482\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-483\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-484\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-485\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-486\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-487\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-483\" aria-labelledby=\"react-tabs-482\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable \"><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"address\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contact_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"label\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"protocol\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"source\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-485\" aria-labelledby=\"react-tabs-484\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-487\" aria-labelledby=\"react-tabs-486\"></div></div></div></div></div></div><div id=\"tag/search\" data-section-id=\"tag/search\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/search\" aria-label=\"tag/search\"></a>search</h1></div></div></div><div id=\"tag/search/paths/~1v2~1search/get\" data-section-id=\"tag/search/paths/~1v2~1search/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/search/paths/~1v2~1search/get\" aria-label=\"tag/search/paths/~1v2~1search/get\"></a>Simple API to execute full-text searches within us<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Simple API to execute full-text searches within user&amp;#39;s indexes. A more complexe API will be available with a POST verb.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Simple API to execute full-text searches within user&#39;s indexes. A more complexe API will be available with a POST verb.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"term\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">term</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span> <span class=\"sc-gTgyBf sc-clsGLE ihUpGF bThqtX\"> <!-- -->&gt;= 3 characters<!-- --> </span></span></div> <div><div html=\"&lt;p&gt;the search string&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>the search string</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"field\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">field</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;name of a field on which to perform the search. If omitted defaults to « _all ».&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>name of a field on which to perform the search. If omitted defaults to « _all ».</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"doctype\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">doctype</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Enum<!-- -->:</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;message&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;contact&quot;</span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">&quot;&quot;</span> </div> <div><div html=\"&lt;p&gt;type of documents to narrow the search to.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>type of documents to narrow the search to.</p>\n</div></div></div></td></tr><tr class=\"\"><td kind=\"field\" title=\"limit\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">limit</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of documents to return per page, but only if param «type» is present.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of documents to return per page, but only if param «type» is present.</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"offset\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">offset</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">integer</span></div> <div><div html=\"&lt;p&gt;number of pages to skip from the response, but only if param «type» is present.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>number of pages to skip from the response, but only if param «type» is present.</p>\n</div></div></div></td></tr></tbody></table></div><div><h5 class=\"sc-iqHXzD bRzaSI\">header<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"X-Caliopen-IL\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">X-Caliopen-IL</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div><div><span class=\"sc-gTgyBf ihUpGF\"> <!-- -->Default:<!-- --> </span> <span class=\"sc-gTgyBf sc-lcuiaG ihUpGF cEfEOv\">-10;10</span></div> <div><div html=\"&lt;p&gt;The Importance Level range requested in form of &lt;code&gt;-10;10&lt;/code&gt;&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>The Importance Level range requested in form of <code>-10;10</code></p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;an object holding an array of documents found. Docs are assembled by type.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>an object holding an array of documents found. Docs are assembled by type.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/search</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/search</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-488\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-489\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-490\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-491\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-492\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-493\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-489\" aria-labelledby=\"react-tabs-488\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"message_hits\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"messages\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"score\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"highlights\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"document\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"contact_hits\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"contacts\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"score\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"highlights\"</span>: <span class=\"token punctuation\">{ }</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"document\"</span>: <span class=\"token punctuation\">{ }</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-491\" aria-labelledby=\"react-tabs-490\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-493\" aria-labelledby=\"react-tabs-492\"></div></div></div></div></div></div><div id=\"tag/search/paths/~1v2~1search/post\" data-section-id=\"tag/search/paths/~1v2~1search/post\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/search/paths/~1v2~1search/post\" aria-label=\"tag/search/paths/~1v2~1search/post\"></a>Not yet implemented. Future route for more complex<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Not yet implemented. Future route for more complexe searches.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Not yet implemented. Future route for more complexe searches.</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">501<!-- --> </strong><div html=\"&lt;p&gt;route not implemented.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>route not implemented.</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"post\" class=\"sc-jXkspL coiChI http-verb post\">post</span><span class=\"sc-fXoxOd jzevEV\">/v2/search</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/search</div></div></div></div></div></div></div></div><div id=\"tag/notifications\" data-section-id=\"tag/notifications\" class=\"sc-eCstZk kxmVZO\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h1 class=\"sc-fubDmA djltfY\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/notifications\" aria-label=\"tag/notifications\"></a>notifications</h1></div></div></div><div id=\"tag/notifications/paths/~1v2~1notifications/get\" data-section-id=\"tag/notifications/paths/~1v2~1notifications/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/notifications/paths/~1v2~1notifications/get\" aria-label=\"tag/notifications/paths/~1v2~1notifications/get\"></a>Returns pending notifications<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns pending notifications&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns pending notifications</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"\"><td kind=\"field\" title=\"from\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">from</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;oldest timestamp or uuid to retrieve (older notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>oldest timestamp or uuid to retrieve (older notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.</p>\n</div></div></div></td></tr><tr class=\"last \"><td kind=\"field\" title=\"to\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">to</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"&lt;p&gt;earlest timestamp or uuid to retrieve (earler notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>earlest timestamp or uuid to retrieve (earler notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.</p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;json document with an array of pending notifications&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>json document with an array of pending notifications</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/notifications</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/notifications</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-494\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-495\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-496\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-497\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-498\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-499\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-495\" aria-labelledby=\"react-tabs-494\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"total\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notifications\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emitter\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"notif_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"reference\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"children\"</span>: <button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emitter\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"notif_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"reference\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-497\" aria-labelledby=\"react-tabs-496\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-499\" aria-labelledby=\"react-tabs-498\"></div></div></div></div></div></div><div id=\"tag/notifications/paths/~1v2~1notifications/delete\" data-section-id=\"tag/notifications/paths/~1v2~1notifications/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/notifications/paths/~1v2~1notifications/delete\" aria-label=\"tag/notifications/paths/~1v2~1notifications/delete\"></a>Delete pending notifications by time range<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Delete pending notifications by time range&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Delete pending notifications by time range</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">query<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"until\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">until</span></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\"> <!-- -->&lt;<!-- -->date-time<!-- -->&gt;<!-- --> </span></div> <div><div html=\"&lt;p&gt;delete all notifications with a timestamp before &lt;code&gt;until&lt;/code&gt; time (RFC3339 format)&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"><p>delete all notifications with a timestamp before <code>until</code> time (RFC3339 format)</p>\n</div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;deletions executed, no body returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>deletions executed, no body returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/notifications</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/notifications</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-500\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-501\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-502\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-503\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-501\" aria-labelledby=\"react-tabs-500\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-503\" aria-labelledby=\"react-tabs-502\"></div></div></div></div></div></div><div id=\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/get\" data-section-id=\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/get\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/notifications/paths/~1v2~1notifications~1{notification_id}/get\" aria-label=\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/get\"></a>Returns a notification<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Returns a notification&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Returns a notification</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"notification_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">notification_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN iePdRD\"><svg class=\"sc-dQpIV iHKCKT\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">200<!-- --> </strong><div html=\"&lt;p&gt;Notification found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Notification found</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">404<!-- --> </strong><div html=\"&lt;p&gt;Message not found&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Message not found</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"get\" class=\"sc-jXkspL gDPfwK http-verb get\">get</span><span class=\"sc-fXoxOd jzevEV\">/v2/notifications/{notification_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/notifications/{notification_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-success react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-504\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-505\" tabindex=\"0\" data-rttab=\"true\">200</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-506\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-507\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-508\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-509\" data-rttab=\"true\">401</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-510\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-511\" data-rttab=\"true\">404</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-505\" aria-labelledby=\"react-tabs-504\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"emitter\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"notif_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"reference\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"user_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable \"><span class=\"property token string\">\"children\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">[</span><span class=\"ellipsis\"></span><ul class=\"array collapsible\"><li><div class=\"hoverable collapsed\"><button class=\"collapser\" aria-label=\"expand\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"body\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"emitter\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"notif_id\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"reference\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"type\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">]</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-507\" aria-labelledby=\"react-tabs-506\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-509\" aria-labelledby=\"react-tabs-508\"></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-511\" aria-labelledby=\"react-tabs-510\"></div></div></div></div></div></div><div id=\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/delete\" data-section-id=\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/delete\" class=\"sc-eCstZk bCpIRB\"><div class=\"sc-iBPTVF keMybc\"><div class=\"sc-hKgKIp gvoNGN\"><h2 class=\"sc-pGaPU biKwui\"><a class=\"sc-crrtmM jGBpue\" href=\"#tag/notifications/paths/~1v2~1notifications~1{notification_id}/delete\" aria-label=\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/delete\"></a>Delete a notification<!-- --> </h2><div class=\"sc-eWVLZl fgfjUV\"><div html=\"&lt;p&gt;Delete a notification&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG\"><p>Delete a notification</p>\n</div></div><div class=\"sc-jQbKEx GKXXr\"><div class=\"sc-dtwofg bbCJhi\"><h5 class=\"sc-iqHXzD sc-jUElsq bRzaSI dHKKTL\">Authorizations:</h5><svg class=\"sc-dQpIV fgXSzg\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></div><div class=\"sc-dacFVT dYSrpA\"><span class=\"sc-hOqshF kdGkar\"><span class=\"sc-kIeSmD hVqHWQ\"><i>basicAuth</i></span></span></div></div><div><h5 class=\"sc-iqHXzD bRzaSI\">path<!-- --> Parameters</h5><table class=\"sc-hHftZz buUaAP\"><tbody><tr class=\"last \"><td kind=\"field\" title=\"notification_id\" class=\"sc-hBEZvw sc-fFubCH gPUvwc gXIfsF\"><span class=\"sc-idOjMB jwsifT\"></span><span class=\"property-name\">notification_id</span><div class=\"sc-gTgyBf sc-eJMOVy ihUpGF giWqTo\">required</div></td><td class=\"sc-bkzXAi kMAhqG\"><div><div><span class=\"sc-gTgyBf sc-laRQQM ihUpGF cREGdc\"></span><span class=\"sc-gTgyBf sc-iNqNmV ihUpGF iPuJRj\">string</span></div> <div><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div></div></div></td></tr></tbody></table></div><div><h3 class=\"sc-fWPdKs fCsSHf\">Responses</h3><div><button class=\"sc-httZfN eEqsZl\" disabled=\"\"><strong class=\"sc-gVgoeb goojcW\">204<!-- --> </strong><div html=\"&lt;p&gt;deletion executed, no body returned.&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>deletion executed, no body returned.</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">400<!-- --> </strong><div html=\"&lt;p&gt;malform request&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>malform request</p>\n</div></button></div><div><button class=\"sc-httZfN jEuzUJ\"><svg class=\"sc-dQpIV bbipPn\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg><strong class=\"sc-gVgoeb goojcW\">401<!-- --> </strong><div html=\"&lt;p&gt;Unauthorized access&lt;/p&gt;\n\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA casAkG sc-citwID bzkUsW\"><p>Unauthorized access</p>\n</div></button></div></div></div><div class=\"sc-jSguLX sc-gKsecS QymcT bZqtR\"><div class=\"sc-bQdSiB bhuodb\"><button class=\"sc-FyfbU jlriCd\"><span type=\"delete\" class=\"sc-jXkspL cHWaxK http-verb delete\">delete</span><span class=\"sc-fXoxOd jzevEV\">/v2/notifications/{notification_id}</span><svg class=\"sc-dQpIV hHbBnT\" style=\"margin-right:-25px\" version=\"1.1\" viewBox=\"0 0 24 24\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\" aria-hidden=\"true\"><polygon points=\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \"></polygon></svg></button><div aria-hidden=\"true\" class=\"sc-eFtZDC ciRGmD\"><div class=\"sc-fmlKft buTZbL\"><div html=\"\" class=\"sc-iJuWdM sc-cBNeRQ gpkGbA dujygE\"></div><div tabindex=\"0\" role=\"button\"><div class=\"sc-ljRaSg uePtT\"><span>http://localhost:31415/api</span>/v2/notifications/{notification_id}</div></div></div></div></div><div><h3 class=\"sc-kEjbdu jmifsN\"> <!-- -->Response samples<!-- --> </h3><div class=\"sc-cxFKTC jVRsAZ\" data-rttabs=\"true\"><ul class=\"react-tabs__tab-list\" role=\"tablist\"><li class=\"tab-error react-tabs__tab--selected\" role=\"tab\" id=\"react-tabs-512\" aria-selected=\"true\" aria-disabled=\"false\" aria-controls=\"react-tabs-513\" tabindex=\"0\" data-rttab=\"true\">400</li><li class=\"tab-error\" role=\"tab\" id=\"react-tabs-514\" aria-selected=\"false\" aria-disabled=\"false\" aria-controls=\"react-tabs-515\" data-rttab=\"true\">401</li></ul><div class=\"react-tabs__tab-panel react-tabs__tab-panel--selected\" role=\"tabpanel\" id=\"react-tabs-513\" aria-labelledby=\"react-tabs-512\"><div><div class=\"sc-cOaiZO eDzWuJ\"><span class=\"sc-bBrOHt etjuEr\">Content type</span><div class=\"sc-dOSSlk bAzVww\">application/json</div></div><div class=\"sc-hTZhMB kTaCsY\"><div class=\"sc-cTkyaV gLzJQv\"><div class=\"sc-giInvV bIDavi\"><button><div class=\"sc-jcVcfa iVrxDL\">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class=\"sc-iJuWdM gpkGbA sc-jNMdxs jXmkAn\"><div class=\"redoc-json\"><code><button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable \"><span class=\"property token string\">\"error\"</span>: <button class=\"collapser\" aria-label=\"collapse\"></button><span class=\"token punctuation\">{</span><span class=\"ellipsis\"></span><ul class=\"obj collapsible\"><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"message\"</span>: <span class=\"token string\">&quot;string&quot;</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"code\"</span>: <span class=\"token number\">0</span><span class=\"token punctuation\">,</span></div></li><li><div class=\"hoverable collapsed\"><span class=\"property token string\">\"name\"</span>: <span class=\"token string\">&quot;string&quot;</span></div></li></ul><span class=\"token punctuation\">}</span></div></li></ul><span class=\"token punctuation\">}</span></code></div></div></div></div></div></div><div class=\"react-tabs__tab-panel\" role=\"tabpanel\" id=\"react-tabs-515\" aria-labelledby=\"react-tabs-514\"></div></div></div></div></div></div></div><div class=\"sc-jtHLPo gfiMgB\"></div></div></div>\n      <script>\n      const __redoc_state = {\"menu\":{\"activeItemIdx\":-1},\"spec\":{\"data\":{\"openapi\":\"3.0.0\",\"info\":{\"version\":\"0.2.0\",\"title\":\"Caliopen HTTP/REST API\"},\"paths\":{\"/v1/authentications\":{\"post\":{\"description\":\"Returns an auth token to build basicAuth for the provided credentials\",\"tags\":[\"users\"],\"security\":[],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Authentication\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Successful authentication\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"username\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\",\"description\":\"the user_id makes the 'username' for basicAuth\"},\"tokens\":{\"type\":\"object\",\"properties\":{\"access_token\":{\"type\":\"string\",\"description\":\"the access_token makes the 'password' for basicAuth\"},\"expires_in\":{\"type\":\"integer\",\"format\":\"int32\"},\"expires_at\":{\"type\":\"string\"},\"refresh_token\":{\"type\":\"string\"}}},\"device\":{\"type\":\"object\",\"properties\":{\"device_id\":{\"type\":\"string\",\"description\":\"Identifier of the device used for authentication\"},\"status\":{\"type\":\"string\",\"description\":\"Device status\"}}}},\"additionalProperties\":false}}}},\"401\":{\"description\":\"Authentication error\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v1/me\":{\"get\":{\"description\":\"Gets `user + contact` objects for current logged-in user\",\"tags\":[\"users\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"X-Caliopen-PI\",\"in\":\"header\",\"required\":true,\"description\":\"The PI range requested in form of `1;100`\",\"schema\":{\"type\":\"string\",\"default\":\"1;100\"}}],\"responses\":{\"200\":{\"description\":\"Successful response with json object\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/User\"}}}}}}},\"/v1/users\":{\"post\":{\"description\":\"Create a new User with provided credentials\",\"tags\":[\"users\"],\"security\":[{\"basicAuth\":[]}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/NewUser\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"User creation completed\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"url to retrieve new user's infos at /users/{user_id}\"}}}}}},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"406\":{\"description\":\"NotAcceptable. Server refuses to fulfill the request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/users/{user_id}\":{\"patch\":{\"description\":\"Partially implemented. Currently only for changing password.\",\"tags\":[\"users\",\"password\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"user_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"current_state\":{\"type\":\"object\",\"properties\":{\"attachments\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Attachment\"}},\"body\":{\"type\":\"string\"},\"body_is_plain\":{\"type\":\"boolean\"},\"date\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_delete\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_sort\":{\"type\":\"string\",\"format\":\"date-time\"},\"discussion_id\":{\"type\":\"string\"},\"external_references\":{\"$ref\":\"#/components/schemas/ExternalReferences\"},\"excerpt\":{\"type\":\"string\"},\"user_identities\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"importance_level\":{\"type\":\"integer\",\"format\":\"int32\"},\"is_answered\":{\"type\":\"boolean\"},\"is_draft\":{\"type\":\"boolean\"},\"is_unread\":{\"type\":\"boolean\"},\"is_received\":{\"type\":\"boolean\"},\"message_id\":{\"type\":\"string\"},\"parent_id\":{\"type\":\"string\"},\"participants\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Participant\"}},\"privacy_features\":{\"$ref\":\"#/components/schemas/PrivacyFeatures\"},\"pi\":{\"$ref\":\"#/components/schemas/PI\"},\"pi_message\":{\"$ref\":\"#/components/schemas/PIMessage\"},\"raw_msg_id\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"},\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"protocol\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\"}}},\"attachments\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Attachment\"}},\"body\":{\"type\":\"string\"},\"body_is_plain\":{\"type\":\"boolean\"},\"date\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_delete\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_sort\":{\"type\":\"string\",\"format\":\"date-time\"},\"discussion_id\":{\"type\":\"string\"},\"external_references\":{\"$ref\":\"#/components/schemas/ExternalReferences\"},\"excerpt\":{\"type\":\"string\"},\"user_identities\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"importance_level\":{\"type\":\"integer\",\"format\":\"int32\"},\"is_answered\":{\"type\":\"boolean\"},\"is_draft\":{\"type\":\"boolean\"},\"is_unread\":{\"type\":\"boolean\"},\"is_received\":{\"type\":\"boolean\"},\"message_id\":{\"type\":\"string\"},\"parent_id\":{\"type\":\"string\"},\"participants\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Participant\"}},\"privacy_features\":{\"$ref\":\"#/components/schemas/PrivacyFeatures\"},\"pi\":{\"$ref\":\"#/components/schemas/PI\"},\"pi_message\":{\"$ref\":\"#/components/schemas/PIMessage\"},\"raw_msg_id\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"},\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"protocol\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\"}},\"required\":[\"current_state\"]}}},\"description\":\"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\"required\":true},\"responses\":{\"204\":{\"description\":\"Update successful. No body is returned.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"message not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but patch was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/users/{user_id}/actions\":{\"post\":{\"description\":\"send an order to execute one (or many) action(s) regarding the user : reset_password, etc. A successful execution of the action will probably modify one or more user's attribute(s) ## NOT YET IMPLEMENTED ##\",\"tags\":[\"users\",\"password\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"user_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"$ref\":\"#/components/requestBodies/Actions\"},\"responses\":{\"204\":{\"description\":\"action(s) successfully executed. Nothing returned.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MessageV2\"}}}},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden action. Server is refusing to execute the given action(s) to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Message not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but action was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"424\":{\"description\":\"execution of action failed.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/username/isAvailable\":{\"get\":{\"description\":\"Check if an username is available for creation within Caliopen instance\",\"tags\":[\"users\",\"username\"],\"security\":[],\"parameters\":[{\"name\":\"username\",\"in\":\"query\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"availability state for requested username\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"username\":{\"type\":\"string\"},\"available\":{\"type\":\"boolean\"}},\"required\":[\"username\",\"available\"],\"additionalProperties\":false}}}},\"400\":{\"description\":\"malform request (probably missing 'username' query param)\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"username\":{\"type\":\"string\"},\"available\":{\"type\":\"boolean\"}},\"required\":[\"username\",\"available\"]}}}}}}},\"/v1/settings\":{\"get\":{\"description\":\"Returns settings belonging to current user\",\"tags\":[\"settings\"],\"security\":[{\"basicAuth\":[]}],\"responses\":{\"200\":{\"description\":\"Settings returned\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Settings\"}}}}}},\"patch\":{\"description\":\"Update settings with rfc5789 and rfc7396 specifications\",\"tags\":[\"settings\"],\"security\":[{\"basicAuth\":[]}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"current_state\":{\"type\":\"object\",\"properties\":{\"$ref\":\"../objects/Settings.yaml#/properties\"}},\"default_locale\":{\"type\":\"string\",\"default\":\"fr-FR\"},\"message_display_format\":{\"type\":\"string\",\"default\":\"rich_text\"},\"contact_display_order\":{\"type\":\"string\",\"default\":\"given_name\"},\"contact_display_format\":{\"type\":\"string\",\"default\":\"family_name, given_name\"},\"notification_enabled\":{\"type\":\"boolean\",\"default\":true},\"notification_message_preview\":{\"type\":\"string\",\"default\":\"always\"},\"notification_sound_enabled\":{\"type\":\"boolean\",\"default\":false},\"notification_delay_disappear\":{\"type\":\"integer\",\"default\":10}}}}},\"description\":\"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\"required\":true},\"responses\":{\"204\":{\"description\":\"Update successful. No body is returned.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"patch was semantically malformed or unprocessable\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/identities/locals\":{\"get\":{\"description\":\"returns the list of user's local identities\",\"tags\":[\"identities\"],\"security\":[{\"basicAuth\":[]}],\"responses\":{\"200\":{\"description\":\"Local identities returned\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"number of local identities found for user\"},\"local_identities\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/UserIdentity\"}}}}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/identities/remotes\":{\"get\":{\"description\":\"returns the list of user's remote identities, or filtered if query param\",\"tags\":[\"identities\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"pending\",\"in\":\"query\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Remote identities returned\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"number of remote identities found for user\"},\"remote_identities\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/UserIdentity\"}}}}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"post\":{\"description\":\"create a new remote identity for user\",\"tags\":[\"identities\"],\"security\":[{\"basicAuth\":[]}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/NewUserIdentity\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Remote identity created\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"url to retrieve new remote identity's at identities/remotes/{identifier}\"},\"identifier\":{\"type\":\"string\"}}}}}}}}},\"/v2/identities/remotes/{identifier}\":{\"get\":{\"description\":\"returns a remote identity belonging to user\",\"tags\":[\"identities\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"identifier\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Remote identity returned\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UserIdentity\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Remote identity not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"delete\":{\"description\":\"Delete a remote identity belonging to user\",\"tags\":[\"identities\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"identifier\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"Remote identity deleted\"},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Contact not found\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"patch\":{\"description\":\"update a remote identity with rfc5789 and rfc7396 specifications\",\"tags\":[\"identities\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"identifier\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"current_state\":{\"type\":\"object\",\"properties\":{\"$ref\":\"../objects/UserIdentity.yaml#/properties\"}},\"credentials\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\"}},\"display_name\":{\"type\":\"string\"},\"identity_id\":{\"type\":\"string\"},\"identifier\":{\"type\":\"string\"},\"infos\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\"}},\"last_check\":{\"type\":\"string\",\"format\":\"date-time\"},\"protocol\":{\"type\":\"string\",\"enum\":[\"email\",\"twitter\"]},\"status\":{\"type\":\"string\",\"enum\":[\"active\",\"inactive\",\"deleted\"]},\"type\":{\"type\":\"string\",\"enum\":[\"local\",\"remote\"]},\"user_id\":{\"type\":\"string\"}},\"required\":[\"current_state\"]}}},\"description\":\"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\"required\":true},\"responses\":{\"204\":{\"description\":\"Update successful. No body is returned.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"identity not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but patch was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/passwords/reset\":{\"post\":{\"description\":\"Route to receive a \\\"reset password\\\" request from an anonymous user.\",\"tags\":[\"passwords\"],\"security\":[],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"recovery_email\":{\"type\":\"string\"},\"username\":{\"type\":\"string\"}},\"additionalProperties\":false}}},\"description\":\"data the user has typed into the reset form\",\"required\":true},\"responses\":{\"204\":{\"description\":\"Request successfully handled. No body is returned.\"},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"user not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"424\":{\"description\":\"Parameters were valid but the server was unable to handle the request.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/passwords/reset/{token}\":{\"get\":{\"description\":\"Returns an auth token to build basicAuth, if the token in path is valid\",\"tags\":[\"passwords\"],\"security\":[],\"parameters\":[{\"name\":\"token\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"Route is valid. No body is returned.\"},\"404\":{\"description\":\"Token not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"post\":{\"description\":\"User posts a new password\",\"tags\":[\"passwords\"],\"security\":[],\"parameters\":[{\"name\":\"token\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"password\":{\"type\":\"string\"}},\"additionalProperties\":false,\"required\":[\"password\"]}}},\"description\":\"A simple json with the new password as a string\",\"required\":true},\"responses\":{\"204\":{\"description\":\"Password update successful. No body is returned.\"},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"424\":{\"description\":\"Parameters were valid but the server was unable to change the password.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/providers\":{\"get\":{\"description\":\"returns list of supported external providers for remote identities creation\",\"tags\":[\"identities\",\"providers\"],\"security\":[{\"basicAuth\":[]}],\"responses\":{\"200\":{\"description\":\"List of supported providers\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\"},\"providers\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/providers/{provider_name}\":{\"get\":{\"description\":\"returns provider's properties, notably the url to call for initiating Oauth process\",\"tags\":[\"identities\",\"providers\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"provider_name\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"name\":\"identifier\",\"in\":\"query\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"providers infos\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\"oauth_request_url\":{\"type\":\"string\"},\"protocol\":{\"type\":\"string\"}}}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/providers/{provider_name}/callback\":{\"get\":{\"description\":\"url registered at provider to which its API will redirect user after user authentication\",\"tags\":[\"identities\",\"providers\"],\"security\":[],\"parameters\":[{\"name\":\"provider_name\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"a simple message for user to inform Oauth process completion\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"success\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string\"}}}}}}}}},\"/v2/test/oauth\":{\"get\":{\"description\":\"serves an index to test Oauth processes\",\"tags\":[\"providers\"],\"security\":[],\"responses\":{\"200\":{\"description\":\"index to trigger oauth popup\"}}}},\"/v2/contacts\":{\"get\":{\"description\":\"Returns contacts list for current user according to given params\",\"tags\":[\"contacts\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"X-Caliopen-PI\",\"in\":\"header\",\"required\":true,\"description\":\"The PI range requested in form of `1;100`\",\"schema\":{\"type\":\"string\",\"default\":\"1;100\"}},{\"name\":\"X-Caliopen-IL\",\"in\":\"header\",\"required\":true,\"description\":\"The Importance Level range requested in form of `-10;10`\",\"schema\":{\"type\":\"string\",\"default\":\"-10;10\"}},{\"name\":\"limit\",\"in\":\"query\",\"required\":false,\"description\":\"number of contacts to return per page\",\"schema\":{\"type\":\"integer\"}},{\"name\":\"offset\",\"in\":\"query\",\"required\":false,\"description\":\"number of pages to skip from the response\",\"schema\":{\"type\":\"integer\"}},{\"name\":\"uri\",\"in\":\"query\",\"required\":false,\"description\":\"return contact that has this uri embedded, if any\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Contacts list\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"number of contacts found for current user for the given parameters\"},\"contacts\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Contact\"}}}}}}}}},\"post\":{\"description\":\"Create a new contact for the logged-in user\",\"tags\":[\"contacts\"],\"security\":[{\"basicAuth\":[]}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/NewContact\"}}},\"description\":\"the contact to create\"},\"responses\":{\"200\":{\"description\":\"Contact created\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"url to retrieve new contact's infos at /contacts/{contact_id}\"},\"contact_id\":{\"type\":\"string\"}}}}}}}}},\"/v2/contacts/{contact_id}\":{\"get\":{\"description\":\"Returns a contact\",\"tags\":[\"contacts\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Contact found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Contact\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Contact not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"delete\":{\"description\":\"Delete a contact\",\"tags\":[\"contacts\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"Contact deleted\"},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Contact not found\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"patch\":{\"description\":\"update a contact with rfc5789 and rfc7396 specifications\",\"tags\":[\"contacts\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"$ref\":\"#/components/requestBodies/Patch2\"},\"responses\":{\"204\":{\"description\":\"Update successful. No body is returned.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"contact not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but patch was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/contacts/{contact_id}/identities\":{\"get\":{\"description\":\"returns a list of contact's identities\",\"tags\":[\"contacts\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Contact's identities returned\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"number of identities found for the contact\"},\"contact_identities\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/ContactIdentity\"}}}}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Contact not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"patch\":{\"description\":\"update tags list for contact\",\"tags\":[\"contacts\",\"tags\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"$ref\":\"#/components/requestBodies/Patch2\"},\"responses\":{\"204\":{\"description\":\"Contact's tags updated. Empty body.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden, server is refusing to apply payload to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"contact not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but payload was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"424\":{\"description\":\"server failed to execute the request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/contacts/{contact_id}/publickeys\":{\"post\":{\"description\":\"Add a pgp public key to a contact\",\"tags\":[\"pgp\",\"keys\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/NewPublicKey\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Public key created\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"url to retrieve public key /contacts/{contact_id}/publickeys/{publickey_id}\"},\"publickey_id\":{\"type\":\"string\"}}}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"Unprocessable entity. Parameters were valid but the server was unable to create the key (probably because of a semantically erroneous document)\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"get\":{\"description\":\"Returns all publickeys linked to contact\",\"tags\":[\"pgp\",\"keys\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"List of public keys\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"number of keys found\"},\"pubkeys\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/PublicKey\"}}}}}}}}}},\"/v2/contacts/{contact_id}/publickeys/{pubkey_id}\":{\"get\":{\"description\":\"Retrieve publickey\",\"tags\":[\"keys\",\"pgp\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"name\":\"pubkey_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Successful response with json object\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PublicKey\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"patch\":{\"description\":\"update a public key\",\"tags\":[\"keys\",\"pgp\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"name\":\"pubkey_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"label\":{\"type\":\"string\"},\"current_state\":{\"type\":\"object\",\"properties\":{\"label\":{\"type\":\"string\"}},\"additionalProperties\":false}},\"additionalProperties\":false,\"required\":[\"current_state\",\"label\"]}}},\"description\":\"the patch to apply. Property _label_ is the only one patchable.\",\"required\":true},\"responses\":{\"204\":{\"description\":\"Update successful. No body is returned.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"public key not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"409\":{\"description\":\"json is valid but patch conflicts with db's current state\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but patch was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"delete\":{\"description\":\"Delete a public key\",\"tags\":[\"keys\",\"pgp\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"name\":\"pubkey_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"Successful deletion\"},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/contacts/{contact_id}/tags\":{\"patch\":{\"description\":\"update tags list for contact\",\"tags\":[\"contacts\",\"tags\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"contact_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"$ref\":\"#/components/requestBodies/Patch\"},\"responses\":{\"204\":{\"description\":\"Contact's tags updated. Empty body.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden, server is refusing to apply payload to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"contact not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but payload was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"424\":{\"description\":\"server failed to execute the request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/imports\":{\"post\":{\"requestBody\":{\"content\":{\"multipart/form-data\":{\"schema\":{\"type\":\"object\",\"properties\":{\"file\":{\"description\":\"the vcard file to upload\",\"type\":\"string\",\"format\":\"binary\"}},\"required\":[\"file\"]}}},\"required\":true},\"security\":[{\"basicAuth\":[]}],\"responses\":{\"200\":{\"description\":\"Import successful\"},\"400\":{\"description\":\"Syntax error\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"File valid but we can create the new contact\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/discussions\":{\"get\":{\"description\":\"Returns the list of discussions for current user according to given filter\",\"tags\":[\"discussions\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"X-Caliopen-PI\",\"in\":\"header\",\"required\":true,\"description\":\"The PI range requested in form of `0;100`\",\"schema\":{\"type\":\"string\",\"default\":\"0;100\"}},{\"name\":\"X-Caliopen-IL\",\"in\":\"header\",\"required\":true,\"description\":\"The Importance Level range requested in form of `-10;10`\",\"schema\":{\"type\":\"string\",\"default\":\"-10;10\"}},{\"name\":\"limit\",\"in\":\"query\",\"required\":false,\"description\":\"number of discussions to return per page\",\"schema\":{\"type\":\"integer\"}},{\"name\":\"offset\",\"in\":\"query\",\"required\":false,\"description\":\"number of discussions to skip for pagination\",\"schema\":{\"type\":\"integer\"}}],\"responses\":{\"200\":{\"description\":\"Discussions returned\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"number of discussions found for current user for the given parameters\"},\"discussions\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Discussion\"}}}}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/discussions/{discussion_id}\":{\"get\":{\"description\":\"Returns metadata of a discussion\",\"tags\":[\"discussions\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"discussion_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Discussion found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Discussion\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Discussion not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v1/messages\":{\"post\":{\"description\":\"Create a new Message (draft) for an user\",\"tags\":[\"messages\"],\"security\":[{\"basicAuth\":[]}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/NewMessageV2\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Draft creation completed\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"url to retrieve draft at /messages/{message_id}\"}}}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"Unprocessable entity. Parameters were valid but the server was unable to create the draft (probably because of a semantically erroneous document, like an empty draft)\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/messages\":{\"get\":{\"description\":\"Returns the list of messages for current user according to given parameters/filter\",\"tags\":[\"messages\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"X-Caliopen-PI\",\"in\":\"header\",\"required\":true,\"description\":\"The PI range requested in form of `0;100`\",\"schema\":{\"type\":\"string\",\"default\":\"0;100\"}},{\"name\":\"X-Caliopen-IL\",\"in\":\"header\",\"required\":true,\"description\":\"The Importance Level range requested in form of `-10;10`\",\"schema\":{\"type\":\"string\",\"default\":\"-10;10\"}},{\"name\":\"discussion_id\",\"in\":\"query\",\"description\":\"filter messages belonging to a specific discussion\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"name\":\"limit\",\"in\":\"query\",\"required\":false,\"description\":\"number of messages to return per page\",\"schema\":{\"type\":\"integer\"}},{\"name\":\"offset\",\"in\":\"query\",\"required\":false,\"description\":\"number of pages to skip from the response\",\"schema\":{\"type\":\"integer\"}},{\"name\":\"msg_id\",\"in\":\"query\",\"required\":false,\"description\":\"if provided with range[] param, specify a message_id around which messages will be fetched\",\"schema\":{\"type\":\"string\"}},{\"name\":\"range[]\",\"in\":\"query\",\"required\":false,\"description\":\"boundaries param if message_id param is provided [before, after]\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Messages returned\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"number of messages found for user for the given parameters\"},\"messages\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/MessageV2\"}}}}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v1/messages/{message_id}\":{\"patch\":{\"description\":\"update a draft with rfc5789 and rfc7396 specifications\",\"tags\":[\"messages\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"message_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"current_state\":{\"type\":\"object\",\"properties\":{\"$ref\":\"../objects/MessageV2.yaml#/properties\"}},\"attachments\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Attachment\"}},\"body\":{\"type\":\"string\"},\"body_is_plain\":{\"type\":\"boolean\"},\"date\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_delete\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_sort\":{\"type\":\"string\",\"format\":\"date-time\"},\"discussion_id\":{\"type\":\"string\"},\"external_references\":{\"$ref\":\"#/components/schemas/ExternalReferences\"},\"excerpt\":{\"type\":\"string\"},\"user_identities\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"importance_level\":{\"type\":\"integer\",\"format\":\"int32\"},\"is_answered\":{\"type\":\"boolean\"},\"is_draft\":{\"type\":\"boolean\"},\"is_unread\":{\"type\":\"boolean\"},\"is_received\":{\"type\":\"boolean\"},\"message_id\":{\"type\":\"string\"},\"parent_id\":{\"type\":\"string\"},\"participants\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Participant\"}},\"privacy_features\":{\"$ref\":\"#/components/schemas/PrivacyFeatures\"},\"pi\":{\"$ref\":\"#/components/schemas/PI\"},\"pi_message\":{\"$ref\":\"#/components/schemas/PIMessage\"},\"raw_msg_id\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"},\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"protocol\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\"}},\"required\":[\"current_state\"]}}},\"description\":\"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\"required\":true},\"responses\":{\"204\":{\"description\":\"Update successful. No body is returned.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"message not found\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but patch was semantically malformed or unprocessable\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"delete\":{\"description\":\"Delete a message belonging to an user\",\"tags\":[\"messages\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"message_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"Successful deletion. No body is returned\"},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden action. Server is refusing to delete this ressource\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"message not found\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/messages/{message_id}\":{\"get\":{\"description\":\"returns a message\",\"tags\":[\"messages\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"message_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Message found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MessageV2\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Message not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/messages/{message_id}/tags\":{\"patch\":{\"description\":\"update tags list for message\",\"tags\":[\"messages\",\"tags\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"message_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"$ref\":\"#/components/requestBodies/Patch\"},\"responses\":{\"204\":{\"description\":\"message's tags updated. Empty body.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden, server is refusing to apply payload to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"message not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but payload was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"424\":{\"description\":\"server failed to execute the request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/messages/{message_id}/actions\":{\"post\":{\"description\":\"send an order to execute one (or many) action(s) for the given message : send, etc. A successful execution of the action will probably modify one or more message's attribute(s)\",\"tags\":[\"messages\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"message_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"$ref\":\"#/components/requestBodies/Actions\"},\"responses\":{\"200\":{\"description\":\"action(s) successfully executed. Message is sent back with its modified attributes (if any)\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MessageV2\"}}}},\"204\":{\"description\":\"action(s) successfully executed. Nothing returned.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MessageV2\"}}}},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden action. Server is refusing to execute the given action(s) to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Message not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but action was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"424\":{\"description\":\"execution of action failed.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/messages/{message_id}/attachments\":{\"post\":{\"description\":\"(for draft only) upload a file to server and add attachment reference to the draft.\",\"tags\":[\"attachments\",\"messages\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"message_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"multipart/form-data\":{\"schema\":{\"type\":\"object\",\"properties\":{\"attachment\":{\"description\":\"the attachment file to upload\",\"type\":\"string\",\"format\":\"binary\"}},\"required\":[\"attachment\"]}}},\"required\":true},\"responses\":{\"201\":{\"description\":\"File uploaded sucessfully. Backend sends back attachment's temp_id.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"temp_id\":{\"type\":\"string\",\"description\":\"attachment's temporary id. temp_id exists as long as message has 'draft' status.\"}}}}}},\"400\":{\"description\":\"bad request\"},\"401\":{\"description\":\"Unauthorized\"},\"404\":{\"description\":\"Not Found\"},\"413\":{\"description\":\"Request entity too large\"},\"415\":{\"description\":\"Unsupported media type\"}}}},\"/v2/messages/{message_id}/attachments/{attachment_id}\":{\"get\":{\"description\":\"Download file from server\",\"tags\":[\"messages\",\"attachments\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"message_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"name\":\"attachment_id\",\"description\":\"attachment position within message\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"the attachment, in the 'content-transfer-encoding' format of the part.\",\"content\":{\"*/*\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}},\"400\":{\"description\":\"bad request\"},\"401\":{\"description\":\"Unauthorized\"},\"404\":{\"description\":\"message or attachment not found\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"delete\":{\"description\":\"(for drafts only) delete temporary file and remove attachment reference from the draft.\",\"tags\":[\"messages\",\"attachments\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"message_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"name\":\"attachment_id\",\"description\":\"attachment's temporary id.\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"Successful deletion. No body is returned\"},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden action. Server is refusing to delete this ressource\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"message or attachment not found\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v1/raws/{raw_msg_id}\":{\"get\":{\"description\":\"Returns a raw message\",\"tags\":[\"messages\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"raw_msg_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"the raw message\",\"content\":{\"text/plain\":{\"schema\":{\"type\":\"string\"}}}}}}},\"/v2/tags\":{\"get\":{\"description\":\"Returns tags visible to current user according to given parameters\",\"tags\":[\"tags\"],\"security\":[{\"basicAuth\":[]}],\"responses\":{\"200\":{\"description\":\"Tags returned\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"number of tags found for user for the given parameters\"},\"tags\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Tag\"}}}}}}}}},\"post\":{\"description\":\"Create a new Tag for an user\",\"tags\":[\"tags\"],\"security\":[{\"basicAuth\":[]}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/NewTag\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"User tag creation completed\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"url to retrieve new tag's infos at /tags/{name}\"}}}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"Unprocessable entity. Parameters were valid but the server was unable to create the tag (probably because of a semantically erroneous document, like an empty tag)\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/tags/{tag_id}\":{\"get\":{\"description\":\"Retrieve tag infos\",\"tags\":[\"tags\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"tag_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Successful response with json object\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Tag\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"patch\":{\"description\":\"update a tag\",\"tags\":[\"tags\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"tag_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"label\":{\"type\":\"string\"},\"importance_level\":{\"type\":\"integer\",\"format\":\"int32\"},\"current_state\":{\"type\":\"object\",\"properties\":{\"label\":{\"type\":\"string\"},\"importance_level\":{\"type\":\"integer\",\"format\":\"int32\"}},\"additionalProperties\":false}},\"additionalProperties\":false,\"required\":[\"current_state\"]}}},\"description\":\"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\"required\":true},\"responses\":{\"204\":{\"description\":\"Update successful. No body is returned.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"tag not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"409\":{\"description\":\"json is valid but patch conflicts with db's current state\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but patch was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"delete\":{\"description\":\"Delete a tag belonging to an user\",\"tags\":[\"tags\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"tag_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"Successful deletion\"},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/devices\":{\"get\":{\"description\":\"Returns devices belonging to current user according to given parameters\",\"tags\":[\"devices\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"X-Caliopen-PI\",\"in\":\"header\",\"required\":true,\"description\":\"The PI range requested in form of `1;100`\",\"schema\":{\"type\":\"string\",\"default\":\"1;100\"}},{\"name\":\"limit\",\"in\":\"query\",\"required\":false,\"description\":\"number of devices to return per page\",\"schema\":{\"type\":\"integer\"}},{\"name\":\"offset\",\"in\":\"query\",\"required\":false,\"description\":\"number of pages to skip from the response\",\"schema\":{\"type\":\"integer\"}}],\"responses\":{\"200\":{\"description\":\"Devices returned\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"number of devices found for current user for the given parameters\"},\"devices\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Device\"}}}}}}}}}},\"/v2/devices/{device_id}\":{\"get\":{\"description\":\"Returns a device\",\"tags\":[\"devices\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"device_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Device found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Device\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Device not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"delete\":{\"description\":\"Delete a device\",\"tags\":[\"devices\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"device_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"Device deleted\"},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Device not found\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"patch\":{\"description\":\"update a device with rfc5789 and rfc7396 specifications\",\"tags\":[\"devices\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"device_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"current_state\":{\"type\":\"object\",\"properties\":{\"$ref\":\"../objects/Device.yaml#/properties\"}},\"device_id\":{\"type\":\"string\"},\"ip_creation\":{\"description\":\"ip address at creation\",\"type\":\"string\"},\"locations\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/DeviceLocation\"}},\"name\":{\"type\":\"string\"},\"public_keys\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/PublicKey\"}},\"status\":{\"type\":\"string\"},\"type\":{\"type\":\"string\",\"default\":\"unknow\",\"enum\":[\"other\",\"desktop\",\"laptop\",\"smartphone\",\"tablet\"]},\"user_agent\":{\"type\":\"string\"}},\"required\":[\"current_state\"]}}},\"description\":\"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\"required\":true},\"responses\":{\"204\":{\"description\":\"Update successful. No body is returned.\"},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden patch. Server is refusing to apply the given patch's properties to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"device not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"patch was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/devices/{device_id}/actions\":{\"post\":{\"description\":\"Route to receive orders to trigger actions on a device\",\"tags\":[\"devices\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"device_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"requestBody\":{\"$ref\":\"#/components/requestBodies/Actions\"},\"responses\":{\"204\":{\"description\":\"action(s) successfully executed. Nothing returned.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MessageV2\"}}}},\"400\":{\"description\":\"json payload malformed\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"403\":{\"description\":\"Forbidden action. Server is refusing to execute the given action(s) to this ressource\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Device not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"422\":{\"description\":\"json is valid but action was semantically malformed or unprocessable\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"424\":{\"description\":\"execution of action failed.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/validate-device/{token}\":{\"get\":{\"description\":\"complete device validation if token is valid\",\"tags\":[\"devices\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"token\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"Route is valid. No body is returned.\"},\"404\":{\"description\":\"Token not found or invalid\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/participants/suggest\":{\"get\":{\"description\":\"Returns a list of suggestions according to given parameters/filter. Search is performed within current user's indexes (messages & contacts).\",\"tags\":[\"participants\",\"suggest\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"context\",\"in\":\"query\",\"description\":\"current user's context (to optimize suggest relevance)\",\"required\":true,\"schema\":{\"type\":\"string\",\"enum\":[\"msg_compose\"]}},{\"name\":\"q\",\"in\":\"query\",\"description\":\"a string (3 chars at least) from which to perform the suggestion search\",\"required\":true,\"schema\":{\"type\":\"string\",\"minLength\":3}}],\"responses\":{\"200\":{\"description\":\"a list of suggestions for user to pick-up an identity\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/ParticipantSuggest\"}}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/participants/discussion\":{\"post\":{\"description\":\"Returns discussion related to a list of participants\",\"tags\":[\"participants\"],\"security\":[{\"basicAuth\":[]}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Participant\"}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Computed discussion information\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"hash\":{\"type\":\"string\",\"description\":\"participants informations hash for discussion lookup\"},\"discussion_id\":{\"type\":\"string\",\"description\":\"id of existing discussion for given participants\"}}}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/search\":{\"get\":{\"description\":\"Simple API to execute full-text searches within user's indexes. A more complexe API will be available with a POST verb.\",\"tags\":[\"messages\",\"contacts\",\"search\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"X-Caliopen-IL\",\"in\":\"header\",\"required\":true,\"description\":\"The Importance Level range requested in form of `-10;10`\",\"schema\":{\"type\":\"string\",\"default\":\"-10;10\"}},{\"name\":\"term\",\"in\":\"query\",\"description\":\"the search string\",\"required\":true,\"schema\":{\"type\":\"string\",\"minLength\":3}},{\"name\":\"field\",\"in\":\"query\",\"description\":\"name of a field on which to perform the search. If omitted defaults to « _all ».\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"name\":\"doctype\",\"in\":\"query\",\"description\":\"type of documents to narrow the search to.\",\"required\":false,\"schema\":{\"type\":\"string\",\"enum\":[\"message\",\"contact\",\"\"]}},{\"name\":\"limit\",\"in\":\"query\",\"required\":false,\"description\":\"number of documents to return per page, but only if param «type» is present.\",\"schema\":{\"type\":\"integer\"}},{\"name\":\"offset\",\"in\":\"query\",\"required\":false,\"description\":\"number of pages to skip from the response, but only if param «type» is present.\",\"schema\":{\"type\":\"integer\"}}],\"responses\":{\"200\":{\"description\":\"an object holding an array of documents found. Docs are assembled by type.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"total number of documents found\"},\"message_hits\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"total number of messages found\"},\"messages\":{\"type\":\"array\",\"description\":\"at most 5 documents are returned if query param « type » is not specified.\",\"items\":{\"$ref\":\"#/components/schemas/SearchResponse\"}}}},\"contact_hits\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\",\"description\":\"total number of contacts found\"},\"contacts\":{\"type\":\"array\",\"description\":\"at most 5 documents are returned if query param « type » is not specified.\",\"items\":{\"$ref\":\"#/components/schemas/SearchResponse\"}}}}}}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"post\":{\"description\":\"Not yet implemented. Future route for more complexe searches.\",\"tags\":[\"contacts\",\"messages\",\"search\"],\"security\":[{\"basicAuth\":[]}],\"responses\":{\"501\":{\"description\":\"route not implemented.\",\"content\":{\"*/*\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/notifications\":{\"get\":{\"description\":\"Returns pending notifications\",\"tags\":[\"notifications\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"from\",\"in\":\"query\",\"required\":false,\"description\":\"oldest timestamp or uuid to retrieve (older notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.\",\"schema\":{\"type\":\"string\"}},{\"name\":\"to\",\"in\":\"query\",\"required\":false,\"description\":\"earlest timestamp or uuid to retrieve (earler notifications will not be fetched). RFC3339 format if time, UUIDv1 if id.\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"json document with an array of pending notifications\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"integer\",\"format\":\"int32\"},\"notifications\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Notification\"}}}}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"delete\":{\"description\":\"Delete pending notifications by time range\",\"tags\":[\"notifications\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"until\",\"in\":\"query\",\"required\":false,\"description\":\"delete all notifications with a timestamp before `until` time (RFC3339 format)\",\"schema\":{\"type\":\"string\",\"format\":\"date-time\"}}],\"responses\":{\"204\":{\"description\":\"deletions executed, no body returned.\"},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}},\"/v2/notifications/{notification_id}\":{\"get\":{\"description\":\"Returns a notification\",\"tags\":[\"notifications\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"notification_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"description\":\"Notification found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Notification\"}}}},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"404\":{\"description\":\"Message not found\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}},\"delete\":{\"description\":\"Delete a notification\",\"tags\":[\"notifications\"],\"security\":[{\"basicAuth\":[]}],\"parameters\":[{\"name\":\"notification_id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"204\":{\"description\":\"deletion executed, no body returned.\"},\"400\":{\"description\":\"malform request\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}},\"401\":{\"description\":\"Unauthorized access\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Error\"}}}}}}}},\"servers\":[{\"url\":\"http://localhost:31415/api\"}],\"components\":{\"requestBodies\":{\"Patch\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"current_state\":{\"type\":\"object\",\"properties\":{\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}}},\"required\":[\"tags\",\"current_state\"]}}},\"description\":\"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\"required\":true},\"Patch2\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"properties\":{\"current_state\":{\"type\":\"object\",\"properties\":{\"$ref\":\"../objects/Contact.yaml#/properties\"}},\"additional_name\":{\"type\":\"string\"},\"addresses\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/PostalAddress\"}},\"avatar\":{\"type\":\"string\"},\"contact_id\":{\"type\":\"string\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_update\":{\"type\":\"string\",\"format\":\"date-time\"},\"deleted\":{\"type\":\"string\",\"format\":\"date-time\"},\"emails\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Email\"}},\"family_name\":{\"type\":\"string\"},\"given_name\":{\"type\":\"string\"},\"groups\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"identities\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/SocialIdentity\"}},\"ims\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/IM\"}},\"infos\":{\"type\":\"object\"},\"name_prefix\":{\"type\":\"string\"},\"name_suffix\":{\"type\":\"string\"},\"organizations\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Organization\"}},\"phones\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Phone\"}},\"pi\":{\"$ref\":\"#/components/schemas/PI\"},\"privacy_features\":{\"$ref\":\"#/components/schemas/PrivacyFeatures\"},\"public_keys\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/PublicKey\"}},\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"title\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\"}},\"required\":[\"current_state\"]}}},\"description\":\"the patch to apply. See 'Caliopen Patch RFC' within /doc directory.\",\"required\":true},\"Actions\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Actions\"}}},\"required\":true}},\"securitySchemes\":{\"basicAuth\":{\"type\":\"http\",\"description\":\"HTTP Basic Authentication. `Password` is the `access_token` return by /authentications and `Username` is the `user_id` returned by /authentications\",\"scheme\":\"basic\"}},\"schemas\":{\"ECKey\":{\"type\":\"object\",\"properties\":{\"curve\":{\"type\":\"string\",\"enum\":[\"P-256\",\"P-384\",\"P-512\"]},\"hash\":{\"type\":\"string\",\"enum\":[\"SHA256\",\"SHA384\",\"SHA512\"]},\"x\":{\"type\":\"string\"},\"y\":{\"type\":\"string\"}},\"required\":[\"curve\",\"x\",\"y\"],\"additionalProperties\":false},\"DefaultDevice\":{\"type\":\"object\",\"properties\":{\"device_id\":{\"type\":\"string\"},\"ecdsa_key\":{\"$ref\":\"#/components/schemas/ECKey\"}},\"required\":[\"device_id\",\"ecdsa_key\"],\"additionalProperties\":false},\"Authentication\":{\"type\":\"object\",\"properties\":{\"username\":{\"type\":\"string\"},\"password\":{\"type\":\"string\"},\"context\":{\"type\":\"string\"},\"device\":{\"$ref\":\"#/components/schemas/DefaultDevice\"}},\"required\":[\"username\",\"password\"],\"additionalProperties\":false},\"Error\":{\"type\":\"object\",\"properties\":{\"error\":{\"type\":\"object\",\"properties\":{\"message\":{\"type\":\"string\"},\"code\":{\"type\":\"integer\",\"format\":\"int32\"},\"name\":{\"type\":\"string\"}}}}},\"PostalAddress\":{\"type\":\"object\",\"properties\":{\"address_id\":{\"type\":\"string\"},\"city\":{\"type\":\"string\"},\"country\":{\"type\":\"string\"},\"is_primary\":{\"type\":\"boolean\"},\"label\":{\"type\":\"string\"},\"postal_code\":{\"type\":\"string\"},\"region\":{\"type\":\"string\"},\"street\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"Email\":{\"type\":\"object\",\"properties\":{\"email_id\":{\"type\":\"string\"},\"address\":{\"type\":\"string\"},\"is_primary\":{\"type\":\"boolean\"},\"label\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"address\"],\"additionalProperties\":false},\"SocialIdentity\":{\"type\":\"object\",\"properties\":{\"social_id\":{\"type\":\"string\"},\"infos\":{\"type\":\"object\"},\"name\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"name\"],\"additionalProperties\":false},\"IM\":{\"type\":\"object\",\"properties\":{\"im_id\":{\"type\":\"string\"},\"address\":{\"type\":\"string\"},\"is_primary\":{\"type\":\"boolean\"},\"label\":{\"type\":\"string\"},\"protocol\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"address\"],\"additionalProperties\":false},\"Organization\":{\"type\":\"object\",\"properties\":{\"deleted\":{\"type\":\"boolean\"},\"organization_id\":{\"type\":\"string\"},\"department\":{\"type\":\"string\"},\"is_primary\":{\"type\":\"boolean\"},\"job_description\":{\"type\":\"string\"},\"label\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"name\"],\"additionalProperties\":false},\"Phone\":{\"type\":\"object\",\"properties\":{\"phone_id\":{\"type\":\"string\"},\"normalized_number\":{\"type\":\"string\"},\"is_primary\":{\"type\":\"boolean\"},\"number\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"uri\":{\"type\":\"string\"}},\"required\":[\"number\"],\"additionalProperties\":false},\"PI\":{\"type\":\"object\",\"properties\":{\"technic\":{\"type\":\"integer\"},\"context\":{\"type\":\"integer\"},\"comportment\":{\"type\":\"integer\"},\"version\":{\"type\":\"integer\"}},\"additionalProperties\":true},\"PrivacyFeatures\":{\"type\":\"object\",\"properties\":{}},\"PublicKey\":{\"type\":\"object\",\"properties\":{\"alg\":{\"type\":\"string\"},\"crv\":{\"type\":\"string\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_update\":{\"type\":\"string\",\"format\":\"date-time\"},\"emails\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"expire_date\":{\"type\":\"string\",\"format\":\"date-time\"},\"fingerprint\":{\"type\":\"string\"},\"kty\":{\"type\":\"string\"},\"key_id\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"resource_id\":{\"type\":\"string\"},\"resource_type\":{\"type\":\"string\"},\"size\":{\"type\":\"integer\",\"format\":\"int32\"},\"use\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\"},\"x\":{\"type\":\"integer\",\"format\":\"int64\"},\"y\":{\"type\":\"integer\",\"format\":\"int64\"},\"key\":{\"type\":\"string\",\"description\":\"DER or PEM key, base64 encoded\"},\"label\":{\"type\":\"string\"}},\"required\":[\"key\",\"label\",\"key_id\",\"resource_id\",\"user_id\"],\"additionalProperties\":false},\"Contact\":{\"type\":\"object\",\"properties\":{\"additional_name\":{\"type\":\"string\"},\"addresses\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/PostalAddress\"}},\"avatar\":{\"type\":\"string\"},\"contact_id\":{\"type\":\"string\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_update\":{\"type\":\"string\",\"format\":\"date-time\"},\"deleted\":{\"type\":\"string\",\"format\":\"date-time\"},\"emails\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Email\"}},\"family_name\":{\"type\":\"string\"},\"given_name\":{\"type\":\"string\"},\"groups\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"identities\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/SocialIdentity\"}},\"ims\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/IM\"}},\"infos\":{\"type\":\"object\"},\"name_prefix\":{\"type\":\"string\"},\"name_suffix\":{\"type\":\"string\"},\"organizations\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Organization\"}},\"phones\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Phone\"}},\"pi\":{\"$ref\":\"#/components/schemas/PI\"},\"privacy_features\":{\"$ref\":\"#/components/schemas/PrivacyFeatures\"},\"public_keys\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/PublicKey\"}},\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"title\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\"}},\"required\":[\"contact_id\",\"user_id\"],\"additionalProperties\":false},\"User\":{\"type\":\"object\",\"properties\":{\"contact\":{\"$ref\":\"#/components/schemas/Contact\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"family_name\":{\"type\":\"string\"},\"given_name\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"password\":{\"type\":\"string\"},\"params\":{\"type\":\"object\"},\"privacy_features\":{\"$ref\":\"#/components/schemas/PrivacyFeatures\"},\"pi\":{\"$ref\":\"#/components/schemas/PI\"},\"user_id\":{\"type\":\"string\"},\"recovery_email\":{\"type\":\"string\"}},\"additionalProperties\":false},\"NewEmail\":{\"type\":\"object\",\"properties\":{\"address\":{\"type\":\"string\"},\"is_primary\":{\"type\":\"boolean\"},\"label\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"address\"],\"additionalProperties\":false},\"NewContact\":{\"type\":\"object\",\"properties\":{\"additional_name\":{\"type\":\"string\"},\"addresses\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/PostalAddress\"}},\"avatar\":{\"type\":\"string\"},\"emails\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/NewEmail\"}},\"family_name\":{\"type\":\"string\"},\"given_name\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"},\"groups\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"identities\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/SocialIdentity\"}},\"ims\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"infos\":{\"type\":\"object\"},\"name_prefix\":{\"type\":\"string\"},\"name_suffix\":{\"type\":\"string\"},\"organizations\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"phones\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Phone\"}},\"public_keys\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/PublicKey\"}}},\"additionalProperties\":false},\"Settings\":{\"type\":\"object\",\"properties\":{\"default_locale\":{\"type\":\"string\",\"default\":\"fr-FR\"},\"message_display_format\":{\"type\":\"string\",\"default\":\"rich_text\"},\"contact_display_order\":{\"type\":\"string\",\"default\":\"given_name\"},\"contact_display_format\":{\"type\":\"string\",\"default\":\"family_name, given_name\"},\"notification_enabled\":{\"type\":\"boolean\",\"default\":true},\"notification_message_preview\":{\"type\":\"string\",\"default\":\"always\"},\"notification_sound_enabled\":{\"type\":\"boolean\",\"default\":false},\"notification_delay_disappear\":{\"type\":\"integer\",\"default\":10}}},\"NewUser\":{\"type\":\"object\",\"properties\":{\"contact\":{\"$ref\":\"#/components/schemas/NewContact\"},\"username\":{\"type\":\"string\"},\"password\":{\"type\":\"string\"},\"recovery_email\":{\"type\":\"string\"},\"tos\":{\"type\":\"boolean\"},\"privacy\":{\"type\":\"boolean\"},\"settings\":{\"$ref\":\"#/components/schemas/Settings\"},\"device\":{\"$ref\":\"#/components/schemas/DefaultDevice\"}},\"additionalProperties\":false,\"required\":[\"username\",\"password\",\"recovery_email\"]},\"Attachment\":{\"type\":\"object\",\"properties\":{\"content_type\":{\"type\":\"string\"},\"is_inline\":{\"type\":\"boolean\"},\"file_name\":{\"type\":\"string\"},\"size\":{\"type\":\"integer\",\"format\":\"int64\"},\"temp_id\":{\"type\":\"string\"},\"url\":{\"type\":\"string\"},\"mime_boundary\":{\"type\":\"string\"}}},\"ExternalReferences\":{\"type\":\"object\",\"properties\":{\"ancestors_id\":{\"type\":\"string\"},\"message_id\":{\"type\":\"string\"},\"parent_id\":{\"type\":\"string\"}}},\"Participant\":{\"type\":\"object\",\"properties\":{\"address\":{\"type\":\"string\"},\"contact_ids\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"label\":{\"type\":\"string\"},\"protocol\":{\"type\":\"string\"},\"type\":{\"type\":\"string\",\"enum\":[\"To\",\"Cc\",\"Bcc\",\"From\",\"Reply-To\",\"Sender\"]}},\"required\":[\"address\",\"type\",\"protocol\"],\"additionalProperties\":false},\"PIMessage\":{\"type\":\"object\",\"properties\":{\"content\":{\"type\":\"integer\"},\"transport\":{\"type\":\"integer\"},\"social\":{\"type\":\"integer\"}},\"additionalProperties\":false},\"Actions\":{\"type\":\"object\",\"properties\":{\"actions\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"send\",\"set_read\",\"set_unread\",\"reset_password\",\"delete\",\"device-validation\"]}},\"params\":{\"type\":\"object\"}},\"additionalProperties\":false,\"required\":[\"actions\"]},\"MessageV2\":{\"type\":\"object\",\"properties\":{\"attachments\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Attachment\"}},\"body\":{\"type\":\"string\"},\"body_is_plain\":{\"type\":\"boolean\"},\"date\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_delete\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_sort\":{\"type\":\"string\",\"format\":\"date-time\"},\"discussion_id\":{\"type\":\"string\"},\"external_references\":{\"$ref\":\"#/components/schemas/ExternalReferences\"},\"excerpt\":{\"type\":\"string\"},\"user_identities\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"importance_level\":{\"type\":\"integer\",\"format\":\"int32\"},\"is_answered\":{\"type\":\"boolean\"},\"is_draft\":{\"type\":\"boolean\"},\"is_unread\":{\"type\":\"boolean\"},\"is_received\":{\"type\":\"boolean\"},\"message_id\":{\"type\":\"string\"},\"parent_id\":{\"type\":\"string\"},\"participants\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Participant\"}},\"privacy_features\":{\"$ref\":\"#/components/schemas/PrivacyFeatures\"},\"pi\":{\"$ref\":\"#/components/schemas/PI\"},\"pi_message\":{\"$ref\":\"#/components/schemas/PIMessage\"},\"raw_msg_id\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"},\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"protocol\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\"}},\"additionalProperties\":false},\"UserIdentity\":{\"type\":\"object\",\"properties\":{\"credentials\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\"}},\"display_name\":{\"type\":\"string\"},\"identity_id\":{\"type\":\"string\"},\"identifier\":{\"type\":\"string\"},\"infos\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\"}},\"last_check\":{\"type\":\"string\",\"format\":\"date-time\"},\"protocol\":{\"type\":\"string\",\"enum\":[\"email\",\"twitter\"]},\"status\":{\"type\":\"string\",\"enum\":[\"active\",\"inactive\",\"deleted\"]},\"type\":{\"type\":\"string\",\"enum\":[\"local\",\"remote\"]},\"user_id\":{\"type\":\"string\"}},\"additionalProperties\":false},\"NewUserIdentity\":{\"type\":\"object\",\"properties\":{\"credentials\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\"}},\"display_name\":{\"type\":\"string\"},\"identifier\":{\"type\":\"string\"},\"infos\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\"}},\"protocol\":{\"type\":\"string\",\"enum\":[\"email\",\"twitter\"]},\"status\":{\"type\":\"string\",\"enum\":[\"active\",\"inactive\",\"deleted\"]},\"type\":{\"type\":\"string\",\"enum\":[\"local\",\"remote\"]},\"user_id\":{\"type\":\"string\"}},\"required\":[\"identifier\",\"protocol\"],\"additionalProperties\":false},\"ContactIdentity\":{\"type\":\"object\",\"properties\":{\"identifier\":{\"type\":\"string\"},\"label\":{\"type\":\"string\"},\"privacy_index\":{\"$ref\":\"#/components/schemas/PI\"},\"protocol\":{\"type\":\"string\"}},\"additionalProperties\":false},\"NewPublicKey\":{\"type\":\"object\",\"properties\":{\"key\":{\"type\":\"string\",\"description\":\"DER or PEM key, base64 encoded\"},\"label\":{\"type\":\"string\"}},\"required\":[\"key\",\"label\"],\"additionalProperties\":false},\"Discussion\":{\"type\":\"object\",\"properties\":{\"attachment_count\":{\"type\":\"integer\",\"format\":\"int32\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_update\":{\"type\":\"string\",\"format\":\"date-time\"},\"discussion_id\":{\"type\":\"string\"},\"importance_level\":{\"type\":\"integer\",\"format\":\"int32\"},\"participants\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Participant\"}},\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"excerpt\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"},\"protocol\":{\"type\":\"string\"},\"total_count\":{\"type\":\"integer\",\"format\":\"int32\"},\"unread_count\":{\"type\":\"integer\",\"format\":\"int32\"},\"aliases\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"last_message_id\":{\"type\":\"string\"},\"last_message_date\":{\"type\":\"string\",\"format\":\"date-time\"},\"last_message_subject\":{\"type\":\"string\"}}},\"NewMessageV2\":{\"type\":\"object\",\"properties\":{\"attachments\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Attachment\"}},\"body\":{\"type\":\"string\"},\"discussion_id\":{\"type\":\"string\"},\"user_identities\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"message_id\":{\"type\":\"string\"},\"parent_id\":{\"type\":\"string\"},\"participants\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Participant\"}},\"subject\":{\"type\":\"string\"},\"privacy_features\":{\"$ref\":\"#/components/schemas/PrivacyFeatures\"}},\"required\":[\"user_identities\"],\"additionalProperties\":false},\"Tag\":{\"type\":\"object\",\"properties\":{\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"type\":{\"type\":\"string\",\"enum\":[\"user\",\"system\"]},\"label\":{\"type\":\"string\"},\"importance_level\":{\"type\":\"integer\",\"format\":\"int32\"}},\"additionalProperties\":false},\"NewTag\":{\"type\":\"object\",\"properties\":{\"label\":{\"type\":\"string\"},\"importance_level\":{\"type\":\"integer\",\"format\":\"int32\"}},\"required\":[\"label\"],\"additionalProperties\":false},\"DeviceLocation\":{\"type\":\"object\",\"properties\":{\"address\":{\"type\":\"string\"},\"country\":{\"type\":\"string\"},\"device_id\":{\"type\":\"string\"},\"type\":{\"type\":\"string\",\"default\":\"other\",\"enum\":[\"other\",\"home\",\"work\",\"public\"]},\"user_id\":{\"type\":\"string\"}},\"required\":[\"address\"],\"additionalProperties\":false},\"Device\":{\"type\":\"object\",\"properties\":{\"device_id\":{\"type\":\"string\"},\"date_insert\":{\"type\":\"string\",\"format\":\"date-time\"},\"date_revoked\":{\"type\":\"string\",\"format\":\"date-time\"},\"ip_creation\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"privacy_features\":{\"$ref\":\"#/components/schemas/PrivacyFeatures\"},\"pi\":{\"$ref\":\"#/components/schemas/PI\"},\"status\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\"},\"user_agent\":{\"type\":\"string\"},\"locations\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/DeviceLocation\"}},\"public_keys\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/PublicKey\"}}},\"required\":[\"device_id\",\"user_id\",\"name\",\"type\",\"status\",\"date_insert\"],\"additionalProperties\":false},\"ParticipantSuggest\":{\"type\":\"object\",\"properties\":{\"address\":{\"type\":\"string\",\"description\":\"could be empty if suggestion is a contact (or should we automatically put preferred identity's address ?)\"},\"contact_id\":{\"type\":\"string\",\"description\":\"contact's ID if any\"},\"label\":{\"type\":\"string\",\"description\":\"name of contact or <display-name> in case of an address returned from participants lookup, if any\"},\"protocol\":{\"type\":\"string\",\"description\":\"email, IRC…\"},\"source\":{\"type\":\"string\",\"description\":\"'participant' or 'contact', ie from where this suggestion came from\"}}},\"SearchResponse\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"id of document (shortcut to fetch full doc from db if needed).\"},\"score\":{\"type\":\"number\",\"format\":\"float\",\"description\":\"how confident is our index for this document to match the request. Higher is better. Documents are sorted on this field by default.\"},\"highlights\":{\"type\":\"object\",\"description\":\"Field names where terms of request where found. Each key maps to an array of excerpts.\"},\"document\":{\"type\":\"object\",\"description\":\"full document returned from index.\"}}},\"Notification\":{\"type\":\"object\",\"properties\":{\"body\":{\"type\":\"string\"},\"emitter\":{\"type\":\"string\"},\"notif_id\":{\"description\":\"notif_id is UUIDv1, including a timestamp\",\"type\":\"string\"},\"reference\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"user_id\":{\"type\":\"string\"},\"children\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"body\":{\"type\":\"string\"},\"emitter\":{\"type\":\"string\"},\"notif_id\":{\"description\":\"notif_id is UUIDv1, including a timestamp\",\"type\":\"string\"},\"reference\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}}}}},\"additionalProperties\":false}}}}},\"searchIndex\":{\"store\":[\"tag/users\",\"tag/users/paths/~1v1~1authentications/post\",\"tag/users/paths/~1v1~1me/get\",\"tag/users/paths/~1v1~1users/post\",\"tag/users/paths/~1v2~1users~1{user_id}/patch\",\"tag/users/paths/~1v2~1users~1{user_id}~1actions/post\",\"tag/users/paths/~1v2~1username~1isAvailable/get\",\"tag/password\",\"tag/password/paths/~1v2~1users~1{user_id}/patch\",\"tag/password/paths/~1v2~1users~1{user_id}~1actions/post\",\"tag/username\",\"tag/username/paths/~1v2~1username~1isAvailable/get\",\"tag/settings\",\"tag/settings/paths/~1v1~1settings/get\",\"tag/settings/paths/~1v1~1settings/patch\",\"tag/identities\",\"tag/identities/paths/~1v2~1identities~1locals/get\",\"tag/identities/paths/~1v2~1identities~1remotes/get\",\"tag/identities/paths/~1v2~1identities~1remotes/post\",\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/get\",\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/delete\",\"tag/identities/paths/~1v2~1identities~1remotes~1{identifier}/patch\",\"tag/identities/paths/~1v2~1providers/get\",\"tag/identities/paths/~1v2~1providers~1{provider_name}/get\",\"tag/identities/paths/~1v2~1providers~1{provider_name}~1callback/get\",\"tag/passwords\",\"tag/passwords/paths/~1v2~1passwords~1reset/post\",\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/get\",\"tag/passwords/paths/~1v2~1passwords~1reset~1{token}/post\",\"tag/providers\",\"tag/providers/paths/~1v2~1providers/get\",\"tag/providers/paths/~1v2~1providers~1{provider_name}/get\",\"tag/providers/paths/~1v2~1providers~1{provider_name}~1callback/get\",\"tag/providers/paths/~1v2~1test~1oauth/get\",\"tag/contacts\",\"tag/contacts/paths/~1v2~1contacts/get\",\"tag/contacts/paths/~1v2~1contacts/post\",\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/get\",\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/delete\",\"tag/contacts/paths/~1v2~1contacts~1{contact_id}/patch\",\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/get\",\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1identities/patch\",\"tag/contacts/paths/~1v2~1contacts~1{contact_id}~1tags/patch\",\"tag/contacts/paths/~1v2~1search/get\",\"tag/contacts/paths/~1v2~1search/post\",\"tag/tags\",\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1identities/patch\",\"tag/tags/paths/~1v2~1contacts~1{contact_id}~1tags/patch\",\"tag/tags/paths/~1v2~1messages~1{message_id}~1tags/patch\",\"tag/tags/paths/~1v2~1tags/get\",\"tag/tags/paths/~1v2~1tags/post\",\"tag/tags/paths/~1v2~1tags~1{tag_id}/get\",\"tag/tags/paths/~1v2~1tags~1{tag_id}/patch\",\"tag/tags/paths/~1v2~1tags~1{tag_id}/delete\",\"tag/pgp\",\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\",\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\",\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\",\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\",\"tag/pgp/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\",\"tag/keys\",\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/post\",\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys/get\",\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/get\",\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/patch\",\"tag/keys/paths/~1v2~1contacts~1{contact_id}~1publickeys~1{pubkey_id}/delete\",\"/paths/~1v2~1imports/post\",\"tag/discussions\",\"tag/discussions/paths/~1v2~1discussions/get\",\"tag/discussions/paths/~1v2~1discussions~1{discussion_id}/get\",\"tag/messages\",\"tag/messages/paths/~1v1~1messages/post\",\"tag/messages/paths/~1v2~1messages/get\",\"tag/messages/paths/~1v1~1messages~1{message_id}/patch\",\"tag/messages/paths/~1v1~1messages~1{message_id}/delete\",\"tag/messages/paths/~1v2~1messages~1{message_id}/get\",\"tag/messages/paths/~1v2~1messages~1{message_id}~1tags/patch\",\"tag/messages/paths/~1v2~1messages~1{message_id}~1actions/post\",\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments/post\",\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\",\"tag/messages/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\",\"tag/messages/paths/~1v1~1raws~1{raw_msg_id}/get\",\"tag/messages/paths/~1v2~1search/get\",\"tag/messages/paths/~1v2~1search/post\",\"tag/attachments\",\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments/post\",\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/get\",\"tag/attachments/paths/~1v2~1messages~1{message_id}~1attachments~1{attachment_id}/delete\",\"tag/devices\",\"tag/devices/paths/~1v2~1devices/get\",\"tag/devices/paths/~1v2~1devices~1{device_id}/get\",\"tag/devices/paths/~1v2~1devices~1{device_id}/delete\",\"tag/devices/paths/~1v2~1devices~1{device_id}/patch\",\"tag/devices/paths/~1v2~1devices~1{device_id}~1actions/post\",\"tag/devices/paths/~1v2~1validate-device~1{token}/get\",\"tag/participants\",\"tag/participants/paths/~1v2~1participants~1suggest/get\",\"tag/participants/paths/~1v2~1participants~1discussion/post\",\"tag/suggest\",\"tag/suggest/paths/~1v2~1participants~1suggest/get\",\"tag/search\",\"tag/search/paths/~1v2~1search/get\",\"tag/search/paths/~1v2~1search/post\",\"tag/notifications\",\"tag/notifications/paths/~1v2~1notifications/get\",\"tag/notifications/paths/~1v2~1notifications/delete\",\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/get\",\"tag/notifications/paths/~1v2~1notifications~1{notification_id}/delete\"],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"title\",\"description\"],\"fieldVectors\":[[\"title/0\",[0,1.422]],[\"description/0\",[]],[\"title/1\",[1,0.537,2,2.605,3,2.304,4,2.605,5,2.605,6,2.81]],[\"description/1\",[1,0.572,2,2.773,3,2.453,4,2.773,5,2.773,7,1.884,8,2.992,9,3.729]],[\"title/2\",[0,0.745,10,2.84,11,2.014,12,0.934,13,2.84,14,1.257,15,3.225]],[\"description/2\",[0,1.183,10,3.097,11,2.195,12,1.018,13,3.097,14,1.371,16,2.821,17,3.516]],[\"title/3\",[0,0.885,7,1.937,8,3.075,18,2.093,19,1.937]],[\"description/3\",[0,0.981,7,2.145,8,3.406,18,2.318,19,2.145,20,4.245]],[\"title/4\",[14,1.65,21,3.147,22,2.138,23,3.147]],[\"description/4\",[14,1.655,21,3.157,22,2.145,23,3.157,24,2.527,25,3.739]],[\"title/5\",[26,2.122,27,1.92,28,1.629,29,2.122,30,2.122,31,2.122,32,2.122]],[\"description/5\",[0,0.437,11,2.529,22,0.956,26,1.244,27,1.126,28,1.592,29,2.073,30,1.244,31,1.244,33,1.666,34,1.666,35,1.517,36,1.517,37,1.318,38,1.517,39,1.517,40,0.956,41,0.992,42,1.517,43,1.666]],[\"title/6\",[44,2.851,45,2.671,46,2.394,47,2.522,48,3.376]],[\"description/6\",[44,2.773,45,2.599,46,2.329,47,2.453,49,2.036,50,3.285,51,3.285,52,3.285]],[\"title/7\",[24,3.664]],[\"description/7\",[]],[\"title/8\",[14,1.65,21,3.147,22,2.138,23,3.147]],[\"description/8\",[14,1.655,21,3.157,22,2.145,23,3.157,24,2.527,25,3.739]],[\"title/9\",[26,2.122,27,1.92,28,1.629,29,2.122,30,2.122,31,2.122,32,2.122]],[\"description/9\",[0,0.437,11,2.529,22,0.956,26,1.244,27,1.126,28,1.592,29,2.073,30,1.244,31,1.244,33,1.666,34,1.666,35,1.517,36,1.517,37,1.318,38,1.517,39,1.517,40,0.956,41,0.992,42,1.517,43,1.666]],[\"title/10\",[45,4.289]],[\"description/10\",[]],[\"title/11\",[44,2.851,45,2.671,46,2.394,47,2.522,48,3.376]],[\"description/11\",[44,2.773,45,2.599,46,2.329,47,2.453,49,2.036,50,3.285,51,3.285,52,3.285]],[\"title/12\",[53,4.289]],[\"description/12\",[]],[\"title/13\",[0,0.885,1,0.588,14,1.494,53,2.671,54,1.937]],[\"description/13\",[0,0.981,1,0.651,14,1.655,53,2.958,54,2.145,55,3.739]],[\"title/14\",[53,2.671,56,1.2,57,2.093,58,2.093,59,3.376]],[\"description/14\",[53,2.958,55,3.739,56,1.329,57,2.318,58,2.318,60,2.792]],[\"title/15\",[61,2.627]],[\"description/15\",[]],[\"title/16\",[1,0.588,41,2.011,61,1.636,62,1.029,63,3.376]],[\"description/16\",[1,0.651,41,2.227,61,1.812,62,1.139,63,3.739,64,4.245]],[\"title/17\",[1,0.537,41,1.838,61,1.495,62,0.94,65,1.77,66,3.085]],[\"description/17\",[1,0.539,41,1.845,61,1.5,62,0.944,65,1.776,67,3.097,68,3.516,69,3.097,70,3.097]],[\"title/18\",[0,0.885,18,2.093,19,1.937,61,1.636,65,1.937]],[\"description/18\",[0,0.981,18,2.318,19,2.145,61,1.812,65,2.145,70,3.739]],[\"title/19\",[0,0.885,1,0.588,54,1.937,61,1.636,65,1.937]],[\"description/19\",[0,0.981,1,0.651,54,2.145,61,1.812,65,2.145,71,3.406]],[\"title/20\",[0,0.885,54,1.937,61,1.636,65,1.937,72,1.411]],[\"description/20\",[0,0.981,54,2.145,61,1.812,65,2.145,71,3.406,72,1.563]],[\"title/21\",[56,1.2,57,2.093,58,2.093,61,1.636,65,1.937]],[\"description/21\",[56,1.243,57,2.168,58,2.168,60,2.612,61,1.694,65,2.006,71,3.185]],[\"title/22\",[1,0.537,7,1.77,32,2.304,62,0.94,73,2.605,74,2.605]],[\"description/22\",[1,0.539,7,1.776,47,2.313,61,1.5,62,0.944,65,1.776,73,2.614,74,2.614,75,3.097]],[\"title/23\",[1,0.588,76,2.851,77,2.851,78,2.851,79,2.282]],[\"description/23\",[1,0.51,76,2.473,77,2.473,78,2.473,79,1.979,80,2.929,81,2.929,82,2.473,83,2.473,84,2.929]],[\"title/24\",[7,1.937,32,2.522,79,2.282,85,2.851,86,2.093]],[\"description/24\",[0,1.183,7,1.776,79,2.093,85,2.614,86,1.919,87,3.097,88,3.097,89,3.097]],[\"title/25\",[24,3.664]],[\"description/25\",[]],[\"title/26\",[24,2.282,90,2.093,91,2.851,92,3.376,93,3.376]],[\"description/26\",[0,0.861,24,2.22,90,2.036,91,2.773,92,3.285,93,3.285,94,3.729,95,3.729]],[\"title/27\",[1,0.537,2,2.605,3,2.304,4,2.605,5,2.605,96,3.085]],[\"description/27\",[1,0.539,2,2.614,3,3.369,4,2.614,5,2.614,97,3.516,98,2.821,99,3.097]],[\"title/28\",[0,0.978,19,2.138,24,2.519,100,2.949]],[\"description/28\",[0,1.053,19,2.304,24,2.714,99,4.016,100,3.178]],[\"title/29\",[7,3.11]],[\"description/29\",[]],[\"title/30\",[1,0.537,7,1.77,32,2.304,62,0.94,73,2.605,74,2.605]],[\"description/30\",[1,0.539,7,1.776,47,2.313,61,1.5,62,0.944,65,1.776,73,2.614,74,2.614,75,3.097]],[\"title/31\",[1,0.588,76,2.851,77,2.851,78,2.851,79,2.282]],[\"description/31\",[1,0.51,76,2.473,77,2.473,78,2.473,79,1.979,80,2.929,81,2.929,82,2.473,83,2.473,84,2.929]],[\"title/32\",[7,1.937,32,2.522,79,2.282,85,2.851,86,2.093]],[\"description/32\",[0,1.183,7,1.776,79,2.093,85,2.614,86,1.919,87,3.097,88,3.097,89,3.097]],[\"title/33\",[82,2.851,83,2.851,101,3.376,102,2.394,103,3.376]],[\"description/33\",[82,3.157,83,3.157,101,3.739,102,2.651,103,3.739,104,4.245]],[\"title/34\",[12,1.783]],[\"description/34\",[]],[\"title/35\",[0,0.745,1,0.494,12,0.934,14,1.257,62,0.866,96,2.84,105,1.692]],[\"description/35\",[0,0.812,1,0.539,12,1.018,14,1.371,62,0.944,69,3.097,105,1.845,106,1.919,107,3.097]],[\"title/36\",[0,0.885,12,1.11,16,3.075,18,2.093,19,1.937]],[\"description/36\",[0,0.981,12,1.229,16,3.406,18,2.318,19,2.145,107,3.739]],[\"title/37\",[1,0.82,12,1.548]],[\"description/37\",[1,0.821,12,1.551,108,4.297]],[\"title/38\",[12,1.548,72,1.968]],[\"description/38\",[12,1.551,72,1.972,108,4.297]],[\"title/39\",[12,1.11,56,1.2,57,2.093,58,2.093,60,2.522]],[\"description/39\",[12,1.229,56,1.329,57,2.318,58,2.318,60,2.792,108,3.406]],[\"title/40\",[1,0.649,61,1.806,62,1.136,109,3.728]],[\"description/40\",[1,0.699,61,1.946,62,1.224,109,4.016,110,3.658]],[\"title/41\",[12,1.226,56,1.325,62,1.136,111,1.515]],[\"description/41\",[12,1.321,56,1.427,62,1.224,110,3.658,111,1.632]],[\"title/42\",[12,1.226,56,1.325,62,1.136,111,1.515]],[\"description/42\",[12,1.321,56,1.427,62,1.224,111,1.632,112,4.016]],[\"title/43\",[28,1.77,49,1.912,86,1.912,113,2.304,114,2.304,115,1.77]],[\"description/43\",[28,1.322,40,1.322,41,1.373,46,1.634,49,1.429,86,2.237,100,1.823,102,1.634,113,1.721,114,1.721,115,1.322,116,1.49,117,2.099,118,1.721]],[\"title/44\",[22,1.937,40,1.937,90,2.093,116,2.182,119,2.522]],[\"description/44\",[22,2.006,40,2.006,90,2.168,115,2.006,116,2.26,118,2.612,119,2.612]],[\"title/45\",[111,2.203]],[\"description/45\",[]],[\"title/46\",[12,1.226,56,1.325,62,1.136,111,1.515]],[\"description/46\",[12,1.321,56,1.427,62,1.224,110,3.658,111,1.632]],[\"title/47\",[12,1.226,56,1.325,62,1.136,111,1.515]],[\"description/47\",[12,1.321,56,1.427,62,1.224,111,1.632,112,4.016]],[\"title/48\",[56,1.325,62,1.136,111,1.515,120,1.751]],[\"description/48\",[56,1.427,62,1.224,111,1.632,120,1.887,121,4.016]],[\"title/49\",[0,0.809,1,0.537,14,1.366,105,1.838,111,1.254,122,3.085]],[\"description/49\",[0,0.812,1,0.539,14,1.371,105,1.845,106,1.919,111,1.259,122,3.097,123,3.097,124,3.097]],[\"title/50\",[0,0.978,18,2.311,19,2.138,111,1.515]],[\"description/50\",[0,1.053,18,2.49,19,2.304,111,1.632,124,4.016]],[\"title/51\",[111,1.691,125,3.108,126,4.161]],[\"description/51\",[111,1.763,125,3.24,126,4.339,127,3.952]],[\"title/52\",[56,1.673,111,1.914]],[\"description/52\",[56,1.676,111,1.917,127,4.297]],[\"title/53\",[0,0.978,54,2.138,72,1.558,111,1.515]],[\"description/53\",[0,1.053,54,2.304,72,1.679,111,1.632,127,3.658]],[\"title/54\",[128,4.289]],[\"description/54\",[]],[\"title/55\",[12,1.11,128,2.671,129,2.282,130,1.937,131,1.868]],[\"description/55\",[12,1.229,128,2.958,129,2.527,130,2.145,131,2.069,132,3.157]],[\"title/56\",[1,0.649,12,1.226,133,2.519,134,3.147]],[\"description/56\",[1,0.699,12,1.321,132,3.391,133,2.714,134,3.391]],[\"title/57\",[125,3.517,133,3.182]],[\"description/57\",[125,3.523,133,3.188,135,3.523]],[\"title/58\",[56,1.479,130,2.387,131,2.302]],[\"description/58\",[56,1.542,130,2.489,131,2.4,135,3.24]],[\"title/59\",[72,1.739,130,2.387,131,2.302]],[\"description/59\",[72,1.813,130,2.489,131,2.4,135,3.24]],[\"title/60\",[131,2.999]],[\"description/60\",[]],[\"title/61\",[12,1.11,128,2.671,129,2.282,130,1.937,131,1.868]],[\"description/61\",[12,1.229,128,2.958,129,2.527,130,2.145,131,2.069,132,3.157]],[\"title/62\",[1,0.649,12,1.226,133,2.519,134,3.147]],[\"description/62\",[1,0.699,12,1.321,132,3.391,133,2.714,134,3.391]],[\"title/63\",[125,3.517,133,3.182]],[\"description/63\",[125,3.523,133,3.188,135,3.523]],[\"title/64\",[56,1.479,130,2.387,131,2.302]],[\"description/64\",[56,1.542,130,2.489,131,2.4,135,3.24]],[\"title/65\",[72,1.739,130,2.387,131,2.302]],[\"description/65\",[72,1.813,130,2.489,131,2.4,135,3.24]],[\"title/66\",[136,5.421]],[\"description/66\",[136,5.714]],[\"title/67\",[137,3.844]],[\"description/67\",[]],[\"title/68\",[0,0.885,1,0.588,14,1.494,62,1.029,137,2.394]],[\"description/68\",[0,0.812,1,0.539,14,1.371,62,0.944,67,3.097,105,1.845,106,1.919,137,2.195,138,3.516]],[\"title/69\",[1,0.724,137,2.95,139,4.161]],[\"description/69\",[1,0.755,137,3.076,139,4.339,140,4.926]],[\"title/70\",[120,2.547]],[\"description/70\",[]],[\"title/71\",[0,0.885,18,2.093,19,1.937,120,1.586,141,1.937]],[\"description/71\",[0,0.981,18,2.318,19,2.145,120,1.757,141,2.145,142,4.245]],[\"title/72\",[0,0.809,1,0.537,14,1.366,62,0.94,120,1.449,143,3.503]],[\"description/72\",[0,0.812,1,0.539,14,1.371,62,0.944,105,1.845,106,1.919,120,1.455,144,2.821,145,3.516]],[\"title/73\",[56,1.2,57,2.093,58,2.093,141,1.937,146,3.833]],[\"description/73\",[56,1.329,57,2.318,58,2.318,60,2.792,141,2.145,147,3.739]],[\"title/74\",[0,0.978,54,2.138,72,1.558,120,1.751]],[\"description/74\",[0,1.053,54,2.304,72,1.679,120,1.887,147,4.016]],[\"title/75\",[1,0.82,120,2.212]],[\"description/75\",[1,0.821,120,2.216,148,5.355]],[\"title/76\",[56,1.325,62,1.136,111,1.515,120,1.751]],[\"description/76\",[56,1.427,62,1.224,111,1.632,120,1.887,121,4.016]],[\"title/77\",[26,2.122,27,1.92,28,1.629,29,2.122,30,2.122,31,2.122,66,2.84]],[\"description/77\",[11,1.301,26,2.246,27,1.24,28,1.725,29,2.246,30,1.371,31,1.371,35,1.672,36,1.672,37,1.452,38,1.672,39,1.672,40,1.053,42,1.672,106,1.138,120,0.862,149,2.084,150,2.084]],[\"title/78\",[129,2.282,141,1.937,151,2.851,152,1.937,153,2.282]],[\"description/78\",[129,2.093,141,2.588,151,2.614,152,1.776,153,2.093,154,2.45,155,2.614,156,3.097]],[\"title/79\",[152,2.387,153,2.812,157,3.513]],[\"description/79\",[152,2.489,153,2.932,157,3.663,158,3.663]],[\"title/80\",[72,1.411,141,1.937,152,1.937,159,2.851,160,2.851]],[\"description/80\",[72,1.294,141,2.588,152,1.776,154,2.45,155,2.614,158,2.614,159,2.614,160,2.614]],[\"title/81\",[1,0.724,120,1.955,161,4.161]],[\"description/81\",[1,0.755,120,2.038,161,4.339,162,4.926]],[\"title/82\",[28,1.77,49,1.912,86,1.912,113,2.304,114,2.304,115,1.77]],[\"description/82\",[28,1.322,40,1.322,41,1.373,46,1.634,49,1.429,86,2.237,100,1.823,102,1.634,113,1.721,114,1.721,115,1.322,116,1.49,117,2.099,118,1.721]],[\"title/83\",[22,1.937,40,1.937,90,2.093,116,2.182,119,2.522]],[\"description/83\",[22,2.006,40,2.006,90,2.168,115,2.006,116,2.26,118,2.612,119,2.612]],[\"title/84\",[154,4.289]],[\"description/84\",[]],[\"title/85\",[129,2.282,141,1.937,151,2.851,152,1.937,153,2.282]],[\"description/85\",[129,2.093,141,2.588,151,2.614,152,1.776,153,2.093,154,2.45,155,2.614,156,3.097]],[\"title/86\",[152,2.387,153,2.812,157,3.513]],[\"description/86\",[152,2.489,153,2.932,157,3.663,158,3.663]],[\"title/87\",[72,1.411,141,1.937,152,1.937,159,2.851,160,2.851]],[\"description/87\",[72,1.294,141,2.588,152,1.776,154,2.45,155,2.614,158,2.614,159,2.614,160,2.614]],[\"title/88\",[163,3.11]],[\"description/88\",[]],[\"title/89\",[0,0.809,1,0.537,14,1.366,54,1.77,163,1.77,164,3.503]],[\"description/89\",[0,0.812,1,0.539,14,1.371,54,1.776,105,1.845,106,1.919,123,3.097,163,1.776,165,3.516]],[\"title/90\",[1,0.82,163,2.701]],[\"description/90\",[1,0.821,163,2.706,166,4.297]],[\"title/91\",[72,1.968,163,2.701]],[\"description/91\",[72,1.972,163,2.706,166,4.297]],[\"title/92\",[56,1.2,57,2.093,58,2.093,59,3.376,163,1.937]],[\"description/92\",[56,1.329,57,2.318,58,2.318,60,2.792,163,2.145,166,3.406]],[\"title/93\",[27,2.085,37,2.441,90,1.912,91,2.605,167,3.085,168,3.503]],[\"description/93\",[27,2.363,37,2.767,90,2.168,91,2.953,163,2.006,167,3.497,169,3.97]],[\"title/94\",[3,2.522,98,4.373,163,1.937,170,3.376]],[\"description/94\",[3,2.792,98,4.697,163,2.145,170,3.739,171,4.245]],[\"title/95\",[172,5.421]],[\"description/95\",[]],[\"title/96\",[1,0.537,6,2.81,62,0.94,105,1.838,106,1.912,173,2.441]],[\"description/96\",[1,0.385,11,1.567,12,0.727,14,0.978,41,1.317,49,1.37,62,0.674,102,1.567,105,1.317,106,1.37,115,1.268,120,1.039,144,2.014,173,1.749,174,2.211,175,2.211]],[\"title/97\",[1,0.588,62,1.029,137,2.394,176,3.376,177,3.833]],[\"description/97\",[1,0.651,62,1.139,137,2.651,172,3.739,176,3.739,178,4.245]],[\"title/98\",[173,4.289]],[\"description/98\",[]],[\"title/99\",[1,0.537,6,2.81,62,0.94,105,1.838,106,1.912,173,2.441]],[\"description/99\",[1,0.385,11,1.567,12,0.727,14,0.978,41,1.317,49,1.37,62,0.674,102,1.567,105,1.317,106,1.37,115,1.268,120,1.039,144,2.014,173,1.749,174,2.211,175,2.211]],[\"title/100\",[115,3.11]],[\"description/100\",[]],[\"title/101\",[28,1.77,49,1.912,86,1.912,113,2.304,114,2.304,115,1.77]],[\"description/101\",[28,1.322,40,1.322,41,1.373,46,1.634,49,1.429,86,2.237,100,1.823,102,1.634,113,1.721,114,1.721,115,1.322,116,1.49,117,2.099,118,1.721]],[\"title/102\",[22,1.937,40,1.937,90,2.093,116,2.182,119,2.522]],[\"description/102\",[22,2.006,40,2.006,90,2.168,115,2.006,116,2.26,118,2.612,119,2.612]],[\"title/103\",[179,3.504]],[\"description/103\",[]],[\"title/104\",[1,0.724,179,2.69,180,3.513]],[\"description/104\",[1,0.755,179,2.804,180,3.663,181,4.339]],[\"title/105\",[72,1.411,179,2.182,180,2.851,182,3.376,183,3.376]],[\"description/105\",[72,1.563,179,2.417,180,3.157,181,3.739,182,3.739,183,3.739]],[\"title/106\",[1,0.82,179,3.043]],[\"description/106\",[1,0.821,179,3.049,184,4.717]],[\"title/107\",[72,1.968,179,3.043]],[\"description/107\",[72,1.972,179,3.049,184,4.717]]],\"invertedIndex\":[[\"\",{\"_index\":11,\"title\":{\"2\":{}},\"description\":{\"2\":{},\"5\":{},\"9\":{},\"77\":{},\"96\":{},\"99\":{}}}],[\"acco\",{\"_index\":143,\"title\":{\"72\":{}},\"description\":{}}],[\"accord\",{\"_index\":105,\"title\":{\"35\":{},\"49\":{},\"96\":{},\"99\":{}},\"description\":{\"35\":{},\"49\":{},\"68\":{},\"72\":{},\"89\":{},\"96\":{},\"99\":{}}}],[\"accordin\",{\"_index\":164,\"title\":{\"89\":{}},\"description\":{}}],[\"action\",{\"_index\":37,\"title\":{\"93\":{}},\"description\":{\"5\":{},\"9\":{},\"77\":{},\"93\":{}}}],[\"action(\",{\"_index\":31,\"title\":{\"5\":{},\"9\":{},\"77\":{}},\"description\":{\"5\":{},\"9\":{},\"77\":{}}}],[\"add\",{\"_index\":129,\"title\":{\"55\":{},\"61\":{},\"78\":{},\"85\":{}},\"description\":{\"55\":{},\"61\":{},\"78\":{},\"85\":{}}}],[\"anonym\",{\"_index\":94,\"title\":{},\"description\":{\"26\":{}}}],[\"api\",{\"_index\":86,\"title\":{\"24\":{},\"32\":{},\"43\":{},\"82\":{},\"101\":{}},\"description\":{\"24\":{},\"32\":{},\"43\":{},\"82\":{},\"101\":{}}}],[\"attach\",{\"_index\":154,\"title\":{\"84\":{}},\"description\":{\"78\":{},\"80\":{},\"85\":{},\"87\":{}}}],[\"attribute(\",{\"_index\":42,\"title\":{},\"description\":{\"5\":{},\"9\":{},\"77\":{}}}],[\"auth\",{\"_index\":2,\"title\":{\"1\":{},\"27\":{}},\"description\":{\"1\":{},\"27\":{}}}],[\"authent\",{\"_index\":88,\"title\":{},\"description\":{\"24\":{},\"32\":{}}}],[\"avail\",{\"_index\":46,\"title\":{\"6\":{},\"11\":{}},\"description\":{\"6\":{},\"11\":{},\"43\":{},\"82\":{},\"101\":{}}}],[\"basicauth\",{\"_index\":5,\"title\":{\"1\":{},\"27\":{}},\"description\":{\"1\":{},\"27\":{}}}],[\"belong\",{\"_index\":54,\"title\":{\"13\":{},\"19\":{},\"20\":{},\"53\":{},\"74\":{},\"89\":{}},\"description\":{\"13\":{},\"19\":{},\"20\":{},\"53\":{},\"74\":{},\"89\":{}}}],[\"build\",{\"_index\":4,\"title\":{\"1\":{},\"27\":{}},\"description\":{\"1\":{},\"27\":{}}}],[\"caliopen\",{\"_index\":50,\"title\":{},\"description\":{\"6\":{},\"11\":{}}}],[\"call\",{\"_index\":80,\"title\":{},\"description\":{\"23\":{},\"31\":{}}}],[\"chang\",{\"_index\":23,\"title\":{\"4\":{},\"8\":{}},\"description\":{\"4\":{},\"8\":{}}}],[\"check\",{\"_index\":44,\"title\":{\"6\":{},\"11\":{}},\"description\":{\"6\":{},\"11\":{}}}],[\"complet\",{\"_index\":170,\"title\":{\"94\":{}},\"description\":{\"94\":{}}}],[\"complex\",{\"_index\":116,\"title\":{\"44\":{},\"83\":{},\"102\":{}},\"description\":{\"43\":{},\"44\":{},\"82\":{},\"83\":{},\"101\":{},\"102\":{}}}],[\"contact\",{\"_index\":12,\"title\":{\"2\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"39\":{},\"41\":{},\"42\":{},\"46\":{},\"47\":{},\"55\":{},\"56\":{},\"61\":{},\"62\":{}},\"description\":{\"2\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"39\":{},\"41\":{},\"42\":{},\"46\":{},\"47\":{},\"55\":{},\"56\":{},\"61\":{},\"62\":{},\"96\":{},\"99\":{}}}],[\"contact'\",{\"_index\":109,\"title\":{\"40\":{}},\"description\":{\"40\":{}}}],[\"creat\",{\"_index\":18,\"title\":{\"3\":{},\"18\":{},\"36\":{},\"50\":{},\"71\":{}},\"description\":{\"3\":{},\"18\":{},\"36\":{},\"50\":{},\"71\":{}}}],[\"creation\",{\"_index\":47,\"title\":{\"6\":{},\"11\":{}},\"description\":{\"6\":{},\"11\":{},\"22\":{},\"30\":{}}}],[\"credenti\",{\"_index\":8,\"title\":{\"3\":{}},\"description\":{\"1\":{},\"3\":{}}}],[\"current\",{\"_index\":14,\"title\":{\"2\":{},\"4\":{},\"8\":{},\"13\":{},\"35\":{},\"49\":{},\"68\":{},\"72\":{},\"89\":{}},\"description\":{\"2\":{},\"4\":{},\"8\":{},\"13\":{},\"35\":{},\"49\":{},\"68\":{},\"72\":{},\"89\":{},\"96\":{},\"99\":{}}}],[\"de\",{\"_index\":168,\"title\":{\"93\":{}},\"description\":{}}],[\"delet\",{\"_index\":72,\"title\":{\"20\":{},\"38\":{},\"53\":{},\"59\":{},\"65\":{},\"74\":{},\"80\":{},\"87\":{},\"91\":{},\"105\":{},\"107\":{}},\"description\":{\"20\":{},\"38\":{},\"53\":{},\"59\":{},\"65\":{},\"74\":{},\"80\":{},\"87\":{},\"91\":{},\"105\":{},\"107\":{}}}],[\"devic\",{\"_index\":163,\"title\":{\"88\":{},\"89\":{},\"90\":{},\"91\":{},\"92\":{},\"94\":{}},\"description\":{\"89\":{},\"90\":{},\"91\":{},\"92\":{},\"93\":{},\"94\":{}}}],[\"discuss\",{\"_index\":137,\"title\":{\"67\":{},\"68\":{},\"69\":{},\"97\":{}},\"description\":{\"68\":{},\"69\":{},\"97\":{}}}],[\"download\",{\"_index\":157,\"title\":{\"79\":{},\"86\":{}},\"description\":{\"79\":{},\"86\":{}}}],[\"draft\",{\"_index\":141,\"title\":{\"71\":{},\"73\":{},\"78\":{},\"80\":{},\"85\":{},\"87\":{}},\"description\":{\"71\":{},\"73\":{},\"78\":{},\"80\":{},\"85\":{},\"87\":{}}}],[\"etc\",{\"_index\":35,\"title\":{},\"description\":{\"5\":{},\"9\":{},\"77\":{}}}],[\"execut\",{\"_index\":28,\"title\":{\"5\":{},\"9\":{},\"43\":{},\"77\":{},\"82\":{},\"101\":{}},\"description\":{\"5\":{},\"9\":{},\"43\":{},\"77\":{},\"82\":{},\"101\":{}}}],[\"extern\",{\"_index\":74,\"title\":{\"22\":{},\"30\":{}},\"description\":{\"22\":{},\"30\":{}}}],[\"f\",{\"_index\":66,\"title\":{\"17\":{},\"77\":{}},\"description\":{}}],[\"file\",{\"_index\":152,\"title\":{\"78\":{},\"79\":{},\"80\":{},\"85\":{},\"86\":{},\"87\":{}},\"description\":{\"78\":{},\"79\":{},\"80\":{},\"85\":{},\"86\":{},\"87\":{}}}],[\"filter\",{\"_index\":67,\"title\":{},\"description\":{\"17\":{},\"68\":{}}}],[\"full-text\",{\"_index\":114,\"title\":{\"43\":{},\"82\":{},\"101\":{}},\"description\":{\"43\":{},\"82\":{},\"101\":{}}}],[\"futur\",{\"_index\":119,\"title\":{\"44\":{},\"83\":{},\"102\":{}},\"description\":{\"44\":{},\"83\":{},\"102\":{}}}],[\"get\",{\"_index\":10,\"title\":{\"2\":{}},\"description\":{\"2\":{}}}],[\"given\",{\"_index\":106,\"title\":{\"96\":{},\"99\":{}},\"description\":{\"35\":{},\"49\":{},\"68\":{},\"72\":{},\"77\":{},\"89\":{},\"96\":{},\"99\":{}}}],[\"ident\",{\"_index\":61,\"title\":{\"15\":{},\"16\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"40\":{}},\"description\":{\"16\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"30\":{},\"40\":{}}}],[\"implement\",{\"_index\":22,\"title\":{\"4\":{},\"8\":{},\"44\":{},\"83\":{},\"102\":{}},\"description\":{\"4\":{},\"5\":{},\"8\":{},\"9\":{},\"44\":{},\"83\":{},\"102\":{}}}],[\"index\",{\"_index\":102,\"title\":{\"33\":{}},\"description\":{\"33\":{},\"43\":{},\"82\":{},\"96\":{},\"99\":{},\"101\":{}}}],[\"info\",{\"_index\":126,\"title\":{\"51\":{}},\"description\":{\"51\":{}}}],[\"initi\",{\"_index\":81,\"title\":{},\"description\":{\"23\":{},\"31\":{}}}],[\"instanc\",{\"_index\":51,\"title\":{},\"description\":{\"6\":{},\"11\":{}}}],[\"key\",{\"_index\":131,\"title\":{\"55\":{},\"58\":{},\"59\":{},\"60\":{},\"61\":{},\"64\":{},\"65\":{}},\"description\":{\"55\":{},\"58\":{},\"59\":{},\"61\":{},\"64\":{},\"65\":{}}}],[\"link\",{\"_index\":134,\"title\":{\"56\":{},\"62\":{}},\"description\":{\"56\":{},\"62\":{}}}],[\"list\",{\"_index\":62,\"title\":{\"16\":{},\"17\":{},\"22\":{},\"30\":{},\"35\":{},\"40\":{},\"41\":{},\"42\":{},\"46\":{},\"47\":{},\"48\":{},\"68\":{},\"72\":{},\"76\":{},\"96\":{},\"97\":{},\"99\":{}},\"description\":{\"16\":{},\"17\":{},\"22\":{},\"30\":{},\"35\":{},\"40\":{},\"41\":{},\"42\":{},\"46\":{},\"47\":{},\"48\":{},\"68\":{},\"72\":{},\"76\":{},\"96\":{},\"97\":{},\"99\":{}}}],[\"local\",{\"_index\":63,\"title\":{\"16\":{}},\"description\":{\"16\":{}}}],[\"logged-i\",{\"_index\":15,\"title\":{\"2\":{}},\"description\":{}}],[\"logged-in\",{\"_index\":16,\"title\":{\"36\":{}},\"description\":{\"2\":{},\"36\":{}}}],[\"mani\",{\"_index\":30,\"title\":{\"5\":{},\"9\":{},\"77\":{}},\"description\":{\"5\":{},\"9\":{},\"77\":{}}}],[\"messag\",{\"_index\":120,\"title\":{\"48\":{},\"70\":{},\"71\":{},\"72\":{},\"74\":{},\"75\":{},\"76\":{},\"81\":{}},\"description\":{\"48\":{},\"71\":{},\"72\":{},\"74\":{},\"75\":{},\"76\":{},\"77\":{},\"81\":{},\"96\":{},\"99\":{}}}],[\"message'\",{\"_index\":149,\"title\":{},\"description\":{\"77\":{}}}],[\"metadata\",{\"_index\":139,\"title\":{\"69\":{}},\"description\":{\"69\":{}}}],[\"modifi\",{\"_index\":39,\"title\":{},\"description\":{\"5\":{},\"9\":{},\"77\":{}}}],[\"more\",{\"_index\":40,\"title\":{\"44\":{},\"83\":{},\"102\":{}},\"description\":{\"5\":{},\"9\":{},\"43\":{},\"44\":{},\"77\":{},\"82\":{},\"83\":{},\"101\":{},\"102\":{}}}],[\"new\",{\"_index\":19,\"title\":{\"3\":{},\"18\":{},\"28\":{},\"36\":{},\"50\":{},\"71\":{}},\"description\":{\"3\":{},\"18\":{},\"28\":{},\"36\":{},\"50\":{},\"71\":{}}}],[\"notabl\",{\"_index\":78,\"title\":{\"23\":{},\"31\":{}},\"description\":{\"23\":{},\"31\":{}}}],[\"notif\",{\"_index\":179,\"title\":{\"103\":{},\"104\":{},\"105\":{},\"106\":{},\"107\":{}},\"description\":{\"104\":{},\"105\":{},\"106\":{},\"107\":{}}}],[\"oauth\",{\"_index\":82,\"title\":{\"33\":{}},\"description\":{\"23\":{},\"31\":{},\"33\":{}}}],[\"object\",{\"_index\":13,\"title\":{\"2\":{}},\"description\":{\"2\":{}}}],[\"on\",{\"_index\":29,\"title\":{\"5\":{},\"9\":{},\"77\":{}},\"description\":{\"5\":{},\"9\":{},\"77\":{}}}],[\"order\",{\"_index\":27,\"title\":{\"5\":{},\"9\":{},\"77\":{},\"93\":{}},\"description\":{\"5\":{},\"9\":{},\"77\":{},\"93\":{}}}],[\"p\",{\"_index\":6,\"title\":{\"1\":{},\"96\":{},\"99\":{}},\"description\":{}}],[\"param\",{\"_index\":69,\"title\":{},\"description\":{\"17\":{},\"35\":{}}}],[\"paramet\",{\"_index\":123,\"title\":{},\"description\":{\"49\":{},\"89\":{}}}],[\"parameters/filt\",{\"_index\":144,\"title\":{},\"description\":{\"72\":{},\"96\":{},\"99\":{}}}],[\"partial\",{\"_index\":21,\"title\":{\"4\":{},\"8\":{}},\"description\":{\"4\":{},\"8\":{}}}],[\"particip\",{\"_index\":172,\"title\":{\"95\":{}},\"description\":{\"97\":{}}}],[\"participan\",{\"_index\":177,\"title\":{\"97\":{}},\"description\":{}}],[\"password\",{\"_index\":24,\"title\":{\"7\":{},\"25\":{},\"26\":{},\"28\":{}},\"description\":{\"4\":{},\"8\":{},\"26\":{},\"28\":{}}}],[\"path\",{\"_index\":97,\"title\":{},\"description\":{\"27\":{}}}],[\"pend\",{\"_index\":180,\"title\":{\"104\":{},\"105\":{}},\"description\":{\"104\":{},\"105\":{}}}],[\"perform\",{\"_index\":174,\"title\":{},\"description\":{\"96\":{},\"99\":{}}}],[\"pgp\",{\"_index\":128,\"title\":{\"54\":{},\"55\":{},\"61\":{}},\"description\":{\"55\":{},\"61\":{}}}],[\"post\",{\"_index\":100,\"title\":{\"28\":{}},\"description\":{\"28\":{},\"43\":{},\"82\":{},\"101\":{}}}],[\"probabl\",{\"_index\":38,\"title\":{},\"description\":{\"5\":{},\"9\":{},\"77\":{}}}],[\"process\",{\"_index\":83,\"title\":{\"33\":{}},\"description\":{\"23\":{},\"31\":{},\"33\":{}}}],[\"properti\",{\"_index\":77,\"title\":{\"23\":{},\"31\":{}},\"description\":{\"23\":{},\"31\":{}}}],[\"provid\",{\"_index\":7,\"title\":{\"3\":{},\"22\":{},\"24\":{},\"29\":{},\"30\":{},\"32\":{}},\"description\":{\"1\":{},\"3\":{},\"22\":{},\"24\":{},\"30\":{},\"32\":{}}}],[\"provider'\",{\"_index\":76,\"title\":{\"23\":{},\"31\":{}},\"description\":{\"23\":{},\"31\":{}}}],[\"public\",{\"_index\":130,\"title\":{\"55\":{},\"58\":{},\"59\":{},\"61\":{},\"64\":{},\"65\":{}},\"description\":{\"55\":{},\"58\":{},\"59\":{},\"61\":{},\"64\":{},\"65\":{}}}],[\"publickey\",{\"_index\":133,\"title\":{\"56\":{},\"57\":{},\"62\":{},\"63\":{}},\"description\":{\"56\":{},\"57\":{},\"62\":{},\"63\":{}}}],[\"queri\",{\"_index\":68,\"title\":{},\"description\":{\"17\":{}}}],[\"r\",{\"_index\":32,\"title\":{\"5\":{},\"9\":{},\"22\":{},\"24\":{},\"30\":{},\"32\":{}},\"description\":{}}],[\"rang\",{\"_index\":183,\"title\":{\"105\":{}},\"description\":{\"105\":{}}}],[\"raw\",{\"_index\":161,\"title\":{\"81\":{}},\"description\":{\"81\":{}}}],[\"receiv\",{\"_index\":91,\"title\":{\"26\":{},\"93\":{}},\"description\":{\"26\":{},\"93\":{}}}],[\"redirect\",{\"_index\":87,\"title\":{},\"description\":{\"24\":{},\"32\":{}}}],[\"refer\",{\"_index\":155,\"title\":{},\"description\":{\"78\":{},\"80\":{},\"85\":{},\"87\":{}}}],[\"regard\",{\"_index\":33,\"title\":{},\"description\":{\"5\":{},\"9\":{}}}],[\"regist\",{\"_index\":85,\"title\":{\"24\":{},\"32\":{}},\"description\":{\"24\":{},\"32\":{}}}],[\"relat\",{\"_index\":176,\"title\":{\"97\":{}},\"description\":{\"97\":{}}}],[\"remot\",{\"_index\":65,\"title\":{\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{}},\"description\":{\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"30\":{}}}],[\"remov\",{\"_index\":160,\"title\":{\"80\":{},\"87\":{}},\"description\":{\"80\":{},\"87\":{}}}],[\"request\",{\"_index\":93,\"title\":{\"26\":{}},\"description\":{\"26\":{}}}],[\"reset\",{\"_index\":92,\"title\":{\"26\":{}},\"description\":{\"26\":{}}}],[\"reset_password\",{\"_index\":34,\"title\":{},\"description\":{\"5\":{},\"9\":{}}}],[\"retriev\",{\"_index\":125,\"title\":{\"51\":{},\"57\":{},\"63\":{}},\"description\":{\"51\":{},\"57\":{},\"63\":{}}}],[\"return\",{\"_index\":1,\"title\":{\"1\":{},\"13\":{},\"16\":{},\"17\":{},\"19\":{},\"22\":{},\"23\":{},\"27\":{},\"30\":{},\"31\":{},\"35\":{},\"37\":{},\"40\":{},\"49\":{},\"56\":{},\"62\":{},\"68\":{},\"69\":{},\"72\":{},\"75\":{},\"81\":{},\"89\":{},\"90\":{},\"96\":{},\"97\":{},\"99\":{},\"104\":{},\"106\":{}},\"description\":{\"1\":{},\"13\":{},\"16\":{},\"17\":{},\"19\":{},\"22\":{},\"23\":{},\"27\":{},\"30\":{},\"31\":{},\"35\":{},\"37\":{},\"40\":{},\"49\":{},\"56\":{},\"62\":{},\"68\":{},\"69\":{},\"72\":{},\"75\":{},\"81\":{},\"89\":{},\"90\":{},\"96\":{},\"97\":{},\"99\":{},\"104\":{},\"106\":{}}}],[\"rfc5789\",{\"_index\":57,\"title\":{\"14\":{},\"21\":{},\"39\":{},\"73\":{},\"92\":{}},\"description\":{\"14\":{},\"21\":{},\"39\":{},\"73\":{},\"92\":{}}}],[\"rfc7396\",{\"_index\":58,\"title\":{\"14\":{},\"21\":{},\"39\":{},\"73\":{},\"92\":{}},\"description\":{\"14\":{},\"21\":{},\"39\":{},\"73\":{},\"92\":{}}}],[\"rout\",{\"_index\":90,\"title\":{\"26\":{},\"44\":{},\"83\":{},\"93\":{},\"102\":{}},\"description\":{\"26\":{},\"44\":{},\"83\":{},\"93\":{},\"102\":{}}}],[\"search\",{\"_index\":115,\"title\":{\"43\":{},\"82\":{},\"100\":{},\"101\":{}},\"description\":{\"43\":{},\"44\":{},\"82\":{},\"83\":{},\"96\":{},\"99\":{},\"101\":{},\"102\":{}}}],[\"send\",{\"_index\":26,\"title\":{\"5\":{},\"9\":{},\"77\":{}},\"description\":{\"5\":{},\"9\":{},\"77\":{}}}],[\"serv\",{\"_index\":101,\"title\":{\"33\":{}},\"description\":{\"33\":{}}}],[\"server\",{\"_index\":153,\"title\":{\"78\":{},\"79\":{},\"85\":{},\"86\":{}},\"description\":{\"78\":{},\"79\":{},\"85\":{},\"86\":{}}}],[\"set\",{\"_index\":53,\"title\":{\"12\":{},\"13\":{},\"14\":{}},\"description\":{\"13\":{},\"14\":{}}}],[\"simpl\",{\"_index\":113,\"title\":{\"43\":{},\"82\":{},\"101\":{}},\"description\":{\"43\":{},\"82\":{},\"101\":{}}}],[\"specif\",{\"_index\":60,\"title\":{\"39\":{}},\"description\":{\"14\":{},\"21\":{},\"39\":{},\"73\":{},\"92\":{}}}],[\"specifica\",{\"_index\":59,\"title\":{\"14\":{},\"92\":{}},\"description\":{}}],[\"specificat\",{\"_index\":146,\"title\":{\"73\":{}},\"description\":{}}],[\"success\",{\"_index\":36,\"title\":{},\"description\":{\"5\":{},\"9\":{},\"77\":{}}}],[\"suggest\",{\"_index\":173,\"title\":{\"96\":{},\"98\":{},\"99\":{}},\"description\":{\"96\":{},\"99\":{}}}],[\"support\",{\"_index\":73,\"title\":{\"22\":{},\"30\":{}},\"description\":{\"22\":{},\"30\":{}}}],[\"t\",{\"_index\":96,\"title\":{\"27\":{},\"35\":{}},\"description\":{}}],[\"tag\",{\"_index\":111,\"title\":{\"41\":{},\"42\":{},\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{},\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"76\":{}},\"description\":{\"41\":{},\"42\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{},\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"76\":{}}}],[\"temporari\",{\"_index\":159,\"title\":{\"80\":{},\"87\":{}},\"description\":{\"80\":{},\"87\":{}}}],[\"test\",{\"_index\":103,\"title\":{\"33\":{}},\"description\":{\"33\":{}}}],[\"time\",{\"_index\":182,\"title\":{\"105\":{}},\"description\":{\"105\":{}}}],[\"token\",{\"_index\":3,\"title\":{\"1\":{},\"27\":{},\"94\":{}},\"description\":{\"1\":{},\"27\":{},\"94\":{}}}],[\"trigger\",{\"_index\":167,\"title\":{\"93\":{}},\"description\":{\"93\":{}}}],[\"updat\",{\"_index\":56,\"title\":{\"14\":{},\"21\":{},\"39\":{},\"41\":{},\"42\":{},\"46\":{},\"47\":{},\"48\":{},\"52\":{},\"58\":{},\"64\":{},\"73\":{},\"76\":{},\"92\":{}},\"description\":{\"14\":{},\"21\":{},\"39\":{},\"41\":{},\"42\":{},\"46\":{},\"47\":{},\"48\":{},\"52\":{},\"58\":{},\"64\":{},\"73\":{},\"76\":{},\"92\":{}}}],[\"upload\",{\"_index\":151,\"title\":{\"78\":{},\"85\":{}},\"description\":{\"78\":{},\"85\":{}}}],[\"url\",{\"_index\":79,\"title\":{\"23\":{},\"24\":{},\"31\":{},\"32\":{}},\"description\":{\"23\":{},\"24\":{},\"31\":{},\"32\":{}}}],[\"user\",{\"_index\":0,\"title\":{\"0\":{},\"2\":{},\"3\":{},\"13\":{},\"18\":{},\"19\":{},\"20\":{},\"28\":{},\"35\":{},\"36\":{},\"49\":{},\"50\":{},\"53\":{},\"68\":{},\"71\":{},\"72\":{},\"74\":{},\"89\":{}},\"description\":{\"2\":{},\"3\":{},\"5\":{},\"9\":{},\"13\":{},\"18\":{},\"19\":{},\"20\":{},\"24\":{},\"26\":{},\"28\":{},\"32\":{},\"35\":{},\"36\":{},\"49\":{},\"50\":{},\"53\":{},\"68\":{},\"71\":{},\"72\":{},\"74\":{},\"89\":{}}}],[\"user'\",{\"_index\":41,\"title\":{\"16\":{},\"17\":{}},\"description\":{\"5\":{},\"9\":{},\"16\":{},\"17\":{},\"43\":{},\"82\":{},\"96\":{},\"99\":{},\"101\":{}}}],[\"usernam\",{\"_index\":45,\"title\":{\"6\":{},\"10\":{},\"11\":{}},\"description\":{\"6\":{},\"11\":{}}}],[\"v1/authent\",{\"_index\":9,\"title\":{},\"description\":{\"1\":{}}}],[\"v1/me\",{\"_index\":17,\"title\":{},\"description\":{\"2\":{}}}],[\"v1/messag\",{\"_index\":142,\"title\":{},\"description\":{\"71\":{}}}],[\"v1/messages/{message_id\",{\"_index\":147,\"title\":{},\"description\":{\"73\":{},\"74\":{}}}],[\"v1/raws/{raw_msg_id\",{\"_index\":162,\"title\":{},\"description\":{\"81\":{}}}],[\"v1/set\",{\"_index\":55,\"title\":{},\"description\":{\"13\":{},\"14\":{}}}],[\"v1/user\",{\"_index\":20,\"title\":{},\"description\":{\"3\":{}}}],[\"v2/contact\",{\"_index\":107,\"title\":{},\"description\":{\"35\":{},\"36\":{}}}],[\"v2/contacts/{contact_id\",{\"_index\":108,\"title\":{},\"description\":{\"37\":{},\"38\":{},\"39\":{}}}],[\"v2/contacts/{contact_id}/ident\",{\"_index\":110,\"title\":{},\"description\":{\"40\":{},\"41\":{},\"46\":{}}}],[\"v2/contacts/{contact_id}/publickey\",{\"_index\":132,\"title\":{},\"description\":{\"55\":{},\"56\":{},\"61\":{},\"62\":{}}}],[\"v2/contacts/{contact_id}/publickeys/{pubkey_id\",{\"_index\":135,\"title\":{},\"description\":{\"57\":{},\"58\":{},\"59\":{},\"63\":{},\"64\":{},\"65\":{}}}],[\"v2/contacts/{contact_id}/tag\",{\"_index\":112,\"title\":{},\"description\":{\"42\":{},\"47\":{}}}],[\"v2/devic\",{\"_index\":165,\"title\":{},\"description\":{\"89\":{}}}],[\"v2/devices/{device_id\",{\"_index\":166,\"title\":{},\"description\":{\"90\":{},\"91\":{},\"92\":{}}}],[\"v2/devices/{device_id}/act\",{\"_index\":169,\"title\":{},\"description\":{\"93\":{}}}],[\"v2/discuss\",{\"_index\":138,\"title\":{},\"description\":{\"68\":{}}}],[\"v2/discussions/{discussion_id\",{\"_index\":140,\"title\":{},\"description\":{\"69\":{}}}],[\"v2/identities/loc\",{\"_index\":64,\"title\":{},\"description\":{\"16\":{}}}],[\"v2/identities/remot\",{\"_index\":70,\"title\":{},\"description\":{\"17\":{},\"18\":{}}}],[\"v2/identities/remotes/{identifi\",{\"_index\":71,\"title\":{},\"description\":{\"19\":{},\"20\":{},\"21\":{}}}],[\"v2/import\",{\"_index\":136,\"title\":{\"66\":{}},\"description\":{\"66\":{}}}],[\"v2/messag\",{\"_index\":145,\"title\":{},\"description\":{\"72\":{}}}],[\"v2/messages/{message_id\",{\"_index\":148,\"title\":{},\"description\":{\"75\":{}}}],[\"v2/messages/{message_id}/act\",{\"_index\":150,\"title\":{},\"description\":{\"77\":{}}}],[\"v2/messages/{message_id}/attach\",{\"_index\":156,\"title\":{},\"description\":{\"78\":{},\"85\":{}}}],[\"v2/messages/{message_id}/attachments/{attachment_id\",{\"_index\":158,\"title\":{},\"description\":{\"79\":{},\"80\":{},\"86\":{},\"87\":{}}}],[\"v2/messages/{message_id}/tag\",{\"_index\":121,\"title\":{},\"description\":{\"48\":{},\"76\":{}}}],[\"v2/notif\",{\"_index\":181,\"title\":{},\"description\":{\"104\":{},\"105\":{}}}],[\"v2/notifications/{notification_id\",{\"_index\":184,\"title\":{},\"description\":{\"106\":{},\"107\":{}}}],[\"v2/participants/discuss\",{\"_index\":178,\"title\":{},\"description\":{\"97\":{}}}],[\"v2/participants/suggest\",{\"_index\":175,\"title\":{},\"description\":{\"96\":{},\"99\":{}}}],[\"v2/passwords/reset\",{\"_index\":95,\"title\":{},\"description\":{\"26\":{}}}],[\"v2/passwords/reset/{token\",{\"_index\":99,\"title\":{},\"description\":{\"27\":{},\"28\":{}}}],[\"v2/provid\",{\"_index\":75,\"title\":{},\"description\":{\"22\":{},\"30\":{}}}],[\"v2/providers/{provider_nam\",{\"_index\":84,\"title\":{},\"description\":{\"23\":{},\"31\":{}}}],[\"v2/providers/{provider_name}/callback\",{\"_index\":89,\"title\":{},\"description\":{\"24\":{},\"32\":{}}}],[\"v2/search\",{\"_index\":118,\"title\":{},\"description\":{\"43\":{},\"44\":{},\"82\":{},\"83\":{},\"101\":{},\"102\":{}}}],[\"v2/tag\",{\"_index\":124,\"title\":{},\"description\":{\"49\":{},\"50\":{}}}],[\"v2/tags/{tag_id\",{\"_index\":127,\"title\":{},\"description\":{\"51\":{},\"52\":{},\"53\":{}}}],[\"v2/test/oauth\",{\"_index\":104,\"title\":{},\"description\":{\"33\":{}}}],[\"v2/username/isavail\",{\"_index\":52,\"title\":{},\"description\":{\"6\":{},\"11\":{}}}],[\"v2/users/{user_id\",{\"_index\":25,\"title\":{},\"description\":{\"4\":{},\"8\":{}}}],[\"v2/users/{user_id}/act\",{\"_index\":43,\"title\":{},\"description\":{\"5\":{},\"9\":{}}}],[\"v2/validate-device/{token\",{\"_index\":171,\"title\":{},\"description\":{\"94\":{}}}],[\"valid\",{\"_index\":98,\"title\":{\"94\":{}},\"description\":{\"27\":{},\"94\":{}}}],[\"verb\",{\"_index\":117,\"title\":{},\"description\":{\"43\":{},\"82\":{},\"101\":{}}}],[\"visibl\",{\"_index\":122,\"title\":{\"49\":{}},\"description\":{\"49\":{}}}],[\"wit\",{\"_index\":48,\"title\":{\"6\":{},\"11\":{}},\"description\":{}}],[\"within\",{\"_index\":49,\"title\":{\"43\":{},\"82\":{},\"101\":{}},\"description\":{\"6\":{},\"11\":{},\"43\":{},\"82\":{},\"96\":{},\"99\":{},\"101\":{}}}]],\"pipeline\":[]}},\"options\":{}};\n\n      var container = document.getElementById('redoc');\n      Redoc.hydrate(__redoc_state, container);\n\n      </script>\n</body>\n\n</html>\n"
  },
  {
    "path": "src/backend/interfaces/NATS/go.mockednats/nats.go",
    "content": "package mockednats\n\nimport (\n\t\"errors\"\n\t\"github.com/nats-io/gnatsd/server\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/phayes/freeport\"\n\t\"strconv\"\n\t\"time\"\n)\n\nconst (\n\tnatsUrl = \"0.0.0.0\"\n)\n\n// GetNats starts an embedded nats server on localhost,\n// picking a free available port.\nfunc GetNats() (*server.Server, *nats.Conn, error) {\n\t// starting an embedded nats server\n\tport, err := freeport.GetFreePort()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tnatsServer, err := server.NewServer(&server.Options{\n\t\tHost:     natsUrl,\n\t\tPort:     port,\n\t\tHTTPPort: -1,\n\t\tCluster:  server.ClusterOpts{Port: -1},\n\t\tNoLog:    true,\n\t\tNoSigs:   true,\n\t\tDebug:    false,\n\t\tTrace:    false,\n\t})\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif natsServer == nil {\n\t\treturn nil, nil, errors.New(\"natsServer is nil\")\n\t}\n\n\tgo natsServer.Start()\n\t// Wait for accept loop(s) to be started\n\tif !natsServer.ReadyForConnections(10 * time.Second) {\n\t\treturn nil, nil, errors.New(\"timeout waiting nats server ready\")\n\t}\n\n\tconn, err := nats.Connect(\"nats://\" + natsUrl + \":\" + strconv.Itoa(port))\n\tif err != nil {\n\t\tnatsServer.Shutdown()\n\t\treturn nil, nil, err\n\t}\n\treturn natsServer, conn, nil\n}\n"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/CHANGES.rst",
    "content": "0.0.1\n-----\n- initial version\n"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/MANIFEST.in",
    "content": "include *.txt *.ini *.cfg *.rst"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/README.rst",
    "content": "Entry point\n===========\n\nThis repository is part of CaliOpen platform. For documentation, installation and\ncontribution instructions, please refer to https://caliopen.github.io\n\nnats client\n============\n\ncaliopen_nats package is an interface for py.main to subscribe to \"topics\" published on NATS server.\nFor now, it listens for messages with 'inboundSMTP' subject. It triggers the inbound emails processing.\n\nto launch the daemon :\n\n```\n$ cd caliopen_nats/\n$ python listener.py -f ../../../../configs/caliopen.yaml.template\n```"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/caliopen_nats/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n__version__ = '0.23.0'\n"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/caliopen_nats/delivery.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user message delivery logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\nimport uuid\n\nimport datetime\nimport pytz\nfrom caliopen_storage.exception import NotFound, DuplicateObject\nfrom caliopen_main.message.core import RawMessage\nfrom caliopen_main.message.core import MessageExternalRefLookup as Merl\nfrom caliopen_main.message.objects.message import Message\nfrom caliopen_main.user.store.tag import UserTag as Tag\nfrom caliopen_pi.qualifiers import UserMessageQualifier, UserTwitterQualifier, UserMastodonQualifier\n\nlog = logging.getLogger(__name__)\n\nDUPLICATE_MESSAGE_EXC = \"message already imported for this user\"\n\n\nclass UserMessageDelivery(object):\n\n    def __init__(self, user, identity):\n        \"\"\"Create a new UserMessageDelivery belong to an user.\"\"\"\n        self.user = user\n        self.identity = identity\n        self.qualifier = None\n\n    def process_raw(self, raw_msg_id):\n        \"\"\"Process a raw message for an user, ie makes it a rich 'message'.\"\"\"\n        raw = RawMessage.get(raw_msg_id)\n        if not raw:\n            log.error('Raw message <{}> not found'.format(raw_msg_id))\n            raise NotFound\n        log.debug('Retrieved raw message {}'.format(raw_msg_id))\n\n        message = self.qualifier.process_inbound(raw)\n        # fill user_tag table with imported tags and embed in message\n        for tag in message.ext_tags:\n            try:\n                Tag.get(user_id=self.user.user_id, name=tag.name)\n            except NotFound:\n                try:\n                    Tag.create(user_id=self.user.user_id, name=tag.name,\n                               label=tag.label, type=tag.type,\n                               date_insert=datetime.datetime.now(tz=pytz.utc))\n                except Exception as exc:\n                    log.exception(\n                        \"UserMessageDelivery failed to create tag : {}\".format(\n                            exc))\n            message.tags.append(tag.name)\n\n        if message.external_msg_id:\n            external_refs = Merl._model_class.filter(\n                user_id=self.user.user_id,\n                external_msg_id=message.external_msg_id)\n            if external_refs:\n                msg = external_refs[0]\n                # message already imported, should update it with identity_id ?\n                obj = Message(user=self.user,\n                              message_id=msg.message_id)\n                if str(msg.identity_id) != self.identity.identity_id:\n                    obj.get_db()\n                    obj.unmarshall_db()\n                    obj.user_identities.append(self.identity.identity_id)\n                    obj.marshall_db()\n                    obj.save_db()\n                    obj.marshall_index()\n                    obj.save_index()\n                    Merl.create(self.user,\n                                external_msg_id=msg.external_msg_id,\n                                identity_id=self.identity.identity_id,\n                                message_id=msg.message_id)\n                    # TODO: update flags ?\n                raise DuplicateObject(DUPLICATE_MESSAGE_EXC)\n        else:\n            log.warn('Message without external message_id for raw {}'.\n                     format(raw.raw_msg_id))\n        # store and index Message\n        obj = Message(user=self.user)\n        obj.unmarshall_dict(message.to_native())\n        obj.user_id = uuid.UUID(self.user.user_id)\n        obj.user_identities = [uuid.UUID(self.identity.identity_id)]\n        obj.message_id = uuid.uuid4()\n        obj.date_insert = datetime.datetime.now(tz=pytz.utc)\n        obj.date_sort = obj.date_insert\n        obj.marshall_db()\n        obj.save_db()\n        obj.marshall_index()\n        obj.save_index()\n\n        if message.external_msg_id:\n            # store external_msg_id in lookup table\n            # but do not abort if it failed\n            try:\n                Merl.create(self.user,\n                            external_msg_id=obj.external_msg_id,\n                            identity_id=obj.user_identity,\n                            message_id=obj.message_id)\n            except Exception as exc:\n                log.exception(\"UserMessageDelivery failed \"\n                              \"to store message_external_ref : {}\".format(exc))\n\n        return obj\n\n\nclass UserMailDelivery(UserMessageDelivery):\n    \"\"\"User email delivery processing.\"\"\"\n\n    def __init__(self, user, identity):\n        super(UserMailDelivery, self).__init__(user, identity)\n        self.qualifier = UserMessageQualifier(self.user, self.identity)\n\n\nclass UserTwitterDelivery(UserMessageDelivery):\n    \"\"\"Twitter Direct Message delivery processing\"\"\"\n\n    def __init__(self, user, identity):\n        super(UserTwitterDelivery, self).__init__(user, identity)\n        self.qualifier = UserTwitterQualifier(self.user, self.identity)\n\n\nclass UserMastodonDelivery(UserMessageDelivery):\n    \"\"\"Mastodon Direct Message delivery processing\"\"\"\n\n    def __init__(self, user, identity):\n        super(UserMastodonDelivery, self).__init__(user, identity)\n        self.qualifier = UserMastodonQualifier(self.user, self.identity)"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/caliopen_nats/listener.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen NATS listener for message processing.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport argparse\nimport sys\nimport logging\n\nimport tornado.ioloop\nimport tornado.gen\nfrom nats.io import Client as Nats\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_storage.helpers.connection import connect_storage\n\nlog = logging.getLogger(__name__)\nlogging.basicConfig(level=logging.INFO)\n\n\n@tornado.gen.coroutine\ndef inbound_smtp_handler(config):\n    \"\"\"Inbound message NATS handler.\"\"\"\n    client = Nats()\n    server = 'nats://{}:{}'.format(config['host'], config['port'])\n    servers = [server]\n\n    opts = {\"servers\": servers}\n    yield client.connect(**opts)\n\n    # create and register subscriber(s)\n    inbound_email_sub = subscribers.InboundEmail(client)\n    future = client.subscribe(\"inboundSMTP\", \"SMTPqueue\",\n                              inbound_email_sub.handler)\n    log.info(\"nats subscription started for inboundSMTP\")\n    future.result()\n\n@tornado.gen.coroutine\ndef inbound_twitter_handler(config):\n    \"\"\"Inbound twitterDM NATS handler\"\"\"\n    client = Nats()\n    server = 'nats://{}:{}'.format(config['host'], config['port'])\n    servers = [server]\n\n    opts = {\"servers\": servers}\n    yield client.connect(**opts)\n\n    # create and register subscriber(s)\n    inbound_twitter_sub = subscribers.InboundTwitter(client)\n    future = client.subscribe(\"inboundTwitter\", \"Twitterqueue\",\n                              inbound_twitter_sub.handler)\n    log.info(\"nats subscription started for inboundTwitter\")\n    future.result()\n\n@tornado.gen.coroutine\ndef inbound_mastodon_handler(config):\n    \"\"\"Inbound mastodonDM NATS handler\"\"\"\n    client = Nats()\n    server = 'nats://{}:{}'.format(config['host'], config['port'])\n    servers = [server]\n\n    opts = {\"servers\": servers}\n    yield client.connect(**opts)\n\n    # create and register subscriber(s)\n    inbound_mastodon_sub = subscribers.InboundMastodon(client)\n    future = client.subscribe(\"inboundMastodon\", \"Mastodonqueue\",\n                              inbound_mastodon_sub.handler)\n    log.info(\"nats subscription started for inboundMastodon\")\n    future.result()\n\n@tornado.gen.coroutine\ndef contact_handler(config):\n    \"\"\"NATS handler for contact update events.\"\"\"\n    client = Nats()\n    server = 'nats://{}:{}'.format(config['host'], config['port'])\n    servers = [server]\n\n    opts = {\"servers\": servers}\n    yield client.connect(**opts)\n\n    # create and register subscriber(s)\n    contact_subscriber = subscribers.ContactAction(client)\n    future = client.subscribe(\"contactAction\", \"contactQueue\",\n                     contact_subscriber.handler)\n    log.info(\"nats subscription started for contactAction\")\n    future.result()\n\n\n@tornado.gen.coroutine\ndef key_handler(config):\n    \"\"\"NATS handler for discover_key events.\"\"\"\n    client = Nats()\n    server = 'nats://{}:{}'.format(config['host'], config['port'])\n    servers = [server]\n\n    opts = {\"servers\": servers}\n    yield client.connect(**opts)\n\n    # create and register subscriber(s)\n    key_subscriber = subscribers.KeyAction(client)\n    future = client.subscribe(\"keyAction\",\n                              \"keyQueue\",\n                              key_subscriber.handler)\n    log.info(\"nats subscription started for keyAction\")\n    future.result()\n\n\nif __name__ == '__main__':\n    # load Caliopen config\n    args = sys.argv\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-f', dest='conffile')\n    kwargs = parser.parse_args(args[1:])\n    kwargs = vars(kwargs)\n    Configuration.load(kwargs['conffile'], 'global')\n    # need to load config before importing subscribers\n    import subscribers\n\n    connect_storage()\n    inbound_smtp_handler(Configuration('global').get('message_queue'))\n    inbound_twitter_handler(Configuration('global').get('message_queue'))\n    inbound_mastodon_handler(Configuration('global').get('message_queue'))\n    contact_handler(Configuration('global').get('message_queue'))\n    key_handler(Configuration('global').get('message_queue'))\n    loop_instance = tornado.ioloop.IOLoop.instance()\n    loop_instance.start()\n"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/caliopen_nats/subscribers.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen inbound nats message handler.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport json\nimport sys\nimport traceback\n\nfrom caliopen_storage.exception import DuplicateObject\nfrom caliopen_main.user.core import User, UserIdentity\nfrom caliopen_main.contact.objects import Contact\n\nfrom caliopen_nats.delivery import UserMailDelivery, UserTwitterDelivery, \\\n    UserMastodonDelivery\nfrom caliopen_pi.qualifiers import ContactMessageQualifier\nfrom caliopen_pgp.keys import ContactPublicKeyManager\nfrom caliopen_main.common.core import PublicKey\n\nlog = logging.getLogger(__name__)\n\n\nclass BaseHandler(object):\n    \"\"\"Base class for NATS message handlers.\"\"\"\n\n    def __init__(self, nats_cnx):\n        \"\"\"Create a new inbound messsage handler from a nats connection.\"\"\"\n        self.natsConn = nats_cnx\n\n\nclass InboundEmail(BaseHandler):\n    \"\"\"Inbound message class handler.\"\"\"\n\n    def process_raw(self, msg, payload):\n        \"\"\"Process an inbound raw message.\"\"\"\n        nats_error = {\n            'error': '',\n            'message': 'inbound email message process failed'\n        }\n        nats_success = {\n            'message': 'OK : inbound email message proceeded'\n        }\n        try:\n            user = User.get(payload['user_id'])\n            identity = UserIdentity.get(user, payload['identity_id'])\n            deliver = UserMailDelivery(user, identity)\n            new_message = deliver.process_raw(payload['message_id'])\n            nats_success['message_id'] = str(new_message.message_id)\n            nats_success['discussion_id'] = str(new_message.discussion_id)\n            self.natsConn.publish(msg.reply, json.dumps(nats_success))\n        except DuplicateObject:\n            log.info(\"Message already imported : {}\".format(payload))\n            nats_success['message_id'] = str(payload['message_id'])\n            nats_success['discussion_id'] = \"\"  # message has not been parsed\n            nats_success['message'] = 'raw message already imported'\n            self.natsConn.publish(msg.reply, json.dumps(nats_success))\n        except Exception as exc:\n            # TODO: handle abort exception and report it as special case\n            exc_info = sys.exc_info()\n            log.error(\"deliver process failed for raw {}: {}\".\n                      format(payload, traceback.print_exception(*exc_info)))\n            nats_error['error'] = str(exc)\n            self.natsConn.publish(msg.reply, json.dumps(nats_error))\n            return exc\n\n    def handler(self, msg):\n        \"\"\"Handle an process_raw nats messages.\"\"\"\n        payload = json.loads(msg.data)\n        log.info('Get payload order {}'.format(payload))\n        if payload['order'] == \"process_raw\":\n            self.process_raw(msg, payload)\n        else:\n            log.warn(\n                'Unhandled payload order \"{}\" \\\n                (queue: SMTPqueue, subject : inboundSMTP)'.format(\n                    payload['order']))\n            raise NotImplementedError\n\n\nclass InboundTwitter(BaseHandler):\n    \"\"\"Inbound TwitterDM class handler.\"\"\"\n\n    def process_raw(self, msg, payload):\n        \"\"\"Process an inbound raw message.\"\"\"\n        nats_error = {\n            'error': '',\n            'message': 'inbound twitter message process failed'\n        }\n        nats_success = {\n            'message': 'OK : inbound twitter message proceeded'\n        }\n        try:\n            user = User.get(payload['user_id'])\n            identity = UserIdentity.get(user, payload['identity_id'])\n            deliver = UserTwitterDelivery(user, identity)\n            new_message = deliver.process_raw(payload['message_id'])\n            nats_success['message_id'] = str(new_message.message_id)\n            nats_success['discussion_id'] = str(new_message.discussion_id)\n            self.natsConn.publish(msg.reply, json.dumps(nats_success))\n        except DuplicateObject:\n            log.info(\"Message already imported : {}\".format(payload))\n            nats_success['message_id'] = str(payload['message_id'])\n            nats_success['discussion_id'] = \"\"  # message has not been parsed\n            nats_success['message'] = 'raw message already imported'\n            self.natsConn.publish(msg.reply, json.dumps(nats_success))\n        except Exception as exc:\n            # TODO: handle abort exception and report it as special case\n            exc_info = sys.exc_info()\n            log.error(\"deliver process failed for raw {}: {}\".\n                      format(payload, traceback.print_exception(*exc_info)))\n            nats_error['error'] = str(exc.message)\n            self.natsConn.publish(msg.reply, json.dumps(nats_error))\n            return exc\n\n    def handler(self, msg):\n        \"\"\"Handle an process_raw nats messages.\"\"\"\n        payload = json.loads(msg.data)\n        log.info('Get payload order {}'.format(payload['order']))\n        if payload['order'] == \"process_raw\":\n            self.process_raw(msg, payload)\n        else:\n            log.warn('Unhandled payload type {}'.format(payload['order']))\n\n\nclass InboundMastodon(BaseHandler):\n    \"\"\"Inbound MastodonDM class handler.\"\"\"\n\n    def process_raw(self, msg, payload):\n        \"\"\"Process an inbound raw message.\"\"\"\n        nats_error = {\n            'error': '',\n            'message': 'inbound mastodon message process failed'\n        }\n        nats_success = {\n            'message': 'OK : inbound mastodon message proceeded'\n        }\n        try:\n            user = User.get(payload['user_id'])\n            identity = UserIdentity.get(user, payload['identity_id'])\n            deliver = UserMastodonDelivery(user, identity)\n            new_message = deliver.process_raw(payload['message_id'])\n            nats_success['message_id'] = str(new_message.message_id)\n            nats_success['discussion_id'] = str(new_message.discussion_id)\n            self.natsConn.publish(msg.reply, json.dumps(nats_success))\n        except DuplicateObject:\n            log.info(\"Message already imported : {}\".format(payload))\n            nats_success['message_id'] = str(payload['message_id'])\n            nats_success['discussion_id'] = \"\"  # message has not been parsed\n            nats_success['message'] = 'raw message already imported'\n            self.natsConn.publish(msg.reply, json.dumps(nats_success))\n        except Exception as exc:\n            # TODO: handle abort exception and report it as special case\n            exc_info = sys.exc_info()\n            log.error(\"deliver process failed for raw {}: {}\".\n                      format(payload, traceback.print_exception(*exc_info)))\n            nats_error['error'] = str(exc.message)\n            self.natsConn.publish(msg.reply, json.dumps(nats_error))\n            return exc\n\n    def handler(self, msg):\n        \"\"\"Handle an process_raw nats messages.\"\"\"\n        payload = json.loads(msg.data)\n        log.info('Get payload order {}'.format(payload['order']))\n        if payload['order'] == \"process_raw\":\n            self.process_raw(msg, payload)\n        else:\n            log.warn('Unhandled payload type {}'.format(payload['order']))\n\n\nclass ContactAction(BaseHandler):\n    \"\"\"Handler for contact action message.\"\"\"\n\n    def process_update(self, msg, payload):\n        \"\"\"Process a contact update message.\"\"\"\n        # XXX validate payload structure\n        if 'user_id' not in payload or 'contact_id' not in payload:\n            raise Exception('Invalid contact_update structure')\n        user = User.get(payload['user_id'])\n        contact = Contact(user, contact_id=payload['contact_id'])\n        contact.get_db()\n        contact.unmarshall_db()\n        qualifier = ContactMessageQualifier(user)\n        log.info('Will process update for contact {0} of user {1}'.\n                 format(contact.contact_id, user.user_id))\n        # TODO: (re)discover GPG keys\n        qualifier.process(contact)\n\n    def handler(self, msg):\n        \"\"\"Handle an process_raw nats messages.\"\"\"\n        payload = json.loads(msg.data)\n        # log.info('Get payload order {}'.format(payload['order']))\n        if payload['order'] == \"contact_update\":\n            self.process_update(msg, payload)\n        else:\n            log.warn(\n                'Unhandled payload order \"{}\" \\\n                (queue: contactQueue, subject : contactAction)'.format(\n                    payload['order']))\n            raise NotImplementedError\n\n\nclass KeyAction(BaseHandler):\n    \"\"\"Handler for public key discovery message.\"\"\"\n\n    def _process_key(self, user, contact, key):\n        if not key.is_expired:\n            if key.userids:\n                label = key.userids[0].name\n            else:\n                label = '{0} {1}'.format(key.algorithm, key.size)\n            pub = PublicKey.create(user, contact.contact_id, 'contact',\n                                   fingerprint=key.fingerprint,\n                                   key=key.armored_key,\n                                   expire_date=key.expire_date,\n                                   label=label)\n            log.info('Created public key {0}'.format(pub.key_id))\n\n    def _process_results(self, user, contact, results):\n        fingerprints = []\n        for result in results:\n            for key in result.keys:\n                log.debug('Processing key %r' % key)\n                if key.fingerprint not in fingerprints:\n                    self._process_key(user, contact, key)\n                    fingerprints.append(key.fingerprint)\n\n    def process_key_discovery(self, msg, payload):\n        \"\"\"Discover public keys related to a new contact identifier.\"\"\"\n        if 'user_id' not in payload or 'contact_id' not in payload:\n            raise Exception('Invalid contact_update structure')\n        user = User.get(payload['user_id'])\n        contact = Contact(user.user_id, contact_id=payload['contact_id'])\n        contact.get_db()\n        contact.unmarshall_db()\n        manager = ContactPublicKeyManager()\n        founds = []\n        for ident in payload.get('emails', []):\n            log.info('Process email identity {0}'.format(ident['address']))\n            discovery = manager.process_identity(user, contact,\n                                                 ident['address'], 'email')\n            if discovery:\n                founds.append(discovery)\n        for ident in payload.get('identities', []):\n            log.info('Process identity {0}:{1}'.\n                     format(ident['type'], ident['name']))\n            discovery = manager.process_identity(user, contact,\n                                                 ident['name'], ident['type'])\n            if discovery:\n                founds.append(discovery)\n        if founds:\n            log.info('Found %d results' % len(founds))\n            self._process_results(user, contact, founds)\n\n    def handler(self, msg):\n        \"\"\"Handle a discover_key nats messages.\"\"\"\n        payload = json.loads(msg.data)\n        if payload['order'] == \"discover_key\":\n            self.process_key_discovery(msg, payload)\n        else:\n            log.warn(\n                'Unhandled payload order \"{}\" \\\n                (queue : keyQueue, subject : keyAction)'.format(\n                    payload['order']))\n            raise NotImplementedError\n"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/requirements.deps",
    "content": "caliopen_main"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliop\nwith-coverage = 1\ncover-erase = 1\n\n[compile_catalog]\ndirectory = caliop/locale\ndomain = caliop\nstatistics = true\n\n[extract_messages]\nadd_comments = TRANSLATORS:\noutput_file = caliop/locale/caliop.pot\nwidth = 80\n\n[init_catalog]\ndomain = caliop\ninput_file = caliop/locale/caliop.pot\noutput_dir = caliop/locale\n\n[update_catalog]\ndomain = caliop\ninput_file = caliop/locale/caliop.pot\noutput_dir = caliop/locale\nprevious = true\n"
  },
  {
    "path": "src/backend/interfaces/NATS/py.client/setup.py",
    "content": "import os\nimport sys\nimport re\n\nfrom setuptools import setup, find_packages\n\nPY3 = sys.version_info[0] == 3\n\nname = \"caliopen_nats\"\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nwith open(os.path.join(*([here] + name.split('.') + ['__init__.py']))) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S).match(v_file.read()).group(1)\n\nrequires = [\n    'nats-client>=0.8.4',\n    'tornado==4.2',]\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\n\ntests_require = []\nif sys.version_info < (3, 3):\n    tests_require.append('mock')\n\n\nextras_require = {\n    'dev': [],\n    'doc': [],\n    'test': tests_require\n}\n\nsetup(name=name,\n      version=version,\n      description='subscription service to NATS topics',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\n          \"Programming Language :: Python\",\n          \"Topic :: Internet :: WWW/HTTP :: NATS\",\n          \"Topic :: Internet :: WWW/HTTP :: WSGI :: Application\",\n      ],\n      author='Caliopen Contributors',\n      author_email='',\n      url='https://github.com/CaliOpen/Caliopen/src/backend/interface/NATS/py.client',\n      license='AGPLv3',\n      keywords='nats',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      install_requires=requires,\n      tests_require=tests_require,\n      extras_require=extras_require,\n      test_suite=\"\",\n      entry_points={\n          'paste.app_factory': ['main = nats_client:listener'],\n      })\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/README.md",
    "content": "#### HTTP REST front-end interface for Caliopen application\nThis package launches a HTTP server to interact with Caliopen services.\nBy default, a proxy server runs in front of the http server to route requests to py.server or go.server as needed. For now, routes /api/v1/ are handled by py.server and routes /api/v2 by go server. \n\n##### Installation\nA list of required dependencies is in `vendor/vendor.json`.  \nTo install go dependencies, run `govendor sync` from within `src/backend/interfaces/REST/go.server` directory.  \nTo compile the binary run `go build github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/cmd/caliopen_rest`\n\n##### Usage\nConfiguration files are in `src/backend/configs`.  \nTo launch the server from source files :   \n\n```\n$ cd cmd/caliopen_rest/\n$ go run main.go serve  \n```\n\nTo disable the proxy server : \n`go run main.go serve --proxy=false`\n\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/api_server.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage rest_api\n\nimport (\n\tobj \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/contacts\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/devices\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/discussions\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/identities\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/imports\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/messages\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/notifications\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/participants\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/providers\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/tags\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations/users\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/go-openapi/loads\"\n\t\"os\"\n)\n\nvar (\n\tserver *REST_API\n)\n\ntype (\n\tREST_API struct {\n\t\tconfig   APIConfig\n\t\tswagSpec *loads.Document\n\t}\n\n\tAPIConfig struct {\n\t\tInterface      string `mapstructure:\"listen_interface\"`\n\t\tListenPort     string `mapstructure:\"listen_port\"`\n\t\tPort           string `mapstructure:\"port\"`\n\t\tHostname       string `mapstructure:\"hostname\"`\n\t\tSwaggerFile    string `mapstructure:\"swaggerSpec\"`\n\t\tBackendConfig  `mapstructure:\"BackendConfig\"`\n\t\tIndexConfig    `mapstructure:\"IndexConfig\"`\n\t\tCacheSettings  `mapstructure:\"RedisConfig\"`\n\t\tNatsConfig     `mapstructure:\"NatsConfig\"`\n\t\tNotifierConfig `mapstructure:\"NotifierConfig\"`\n\t\tProviders      []obj.Provider `mapstructure:\"Providers\"`\n\t}\n\n\tBackendConfig struct {\n\t\tBackendName string          `mapstructure:\"backend_name\"`\n\t\tSettings    BackendSettings `mapstructure:\"backend_settings\"`\n\t}\n\n\tBackendSettings struct {\n\t\tHosts            []string        `mapstructure:\"hosts\"`\n\t\tKeyspace         string          `mapstructure:\"keyspace\"`\n\t\tConsistency      uint16          `mapstructure:\"consistency_level\"`\n\t\tSizeLimit        uint64          `mapstructure:\"raw_size_limit\"` // max size for db (in bytes)\n\t\tObjStoreType     string          `mapstructure:\"object_store\"`\n\t\tObjStoreSettings obj.OSSConfig   `mapstructure:\"object_store_settings\"`\n\t\tUseVault         bool            `mapstructure:\"use_vault\"`\n\t\tVaultSettings    obj.VaultConfig `mapstructure:\"vault_settings\"`\n\t}\n\n\tIndexConfig struct {\n\t\tIndexName string        `mapstructure:\"index_name\"`\n\t\tSettings  IndexSettings `mapstructure:\"index_settings\"`\n\t}\n\n\tIndexSettings struct {\n\t\tHosts []string `mapstructure:\"hosts\"`\n\t}\n\n\tCacheSettings struct {\n\t\tHost     string `mapstructure:\"host\"`\n\t\tPassword string `mapstructure:\"password\"`\n\t\tDb       int    `mapstructure:\"db\"`\n\t}\n\n\tNatsConfig struct {\n\t\tUrl               string `mapstructure:\"url\"`\n\t\tNatsQueue         string `mapstructure:\"nats_queue\"`\n\t\tOutSMTP_topic     string `mapstructure:\"outSMTP_topic\"`\n\t\tOutIMAP_topic     string `mapstructure:\"outIMAP_topic\"`\n\t\tOutTWITTER_topic  string `mapstructure:\"outTWITTER_topic\"`\n\t\tOutMASTODON_topic string `mapstructure:\"outMASTODON_topic\"`\n\t\tContacts_topic    string `mapstructure:\"contacts_topic\"`\n\t\tKeys_topic        string `mapstructure:\"keys_topic\"`\n\t\tUsers_topic       string `mapstructure:\"users_topic\"`\n\t\tIdPoller_topic    string `mapstructure:\"idpoller_topic\"`\n\t}\n\n\tNotifierConfig struct {\n\t\tAdminUsername string `mapstructure:\"admin_username\"`\n\t\tBaseUrl       string `mapstructure:\"base_url\"`\n\t\tTemplatesPath string `mapstructure:\"templates_path\"`\n\t}\n)\n\nfunc InitializeServer(config APIConfig) error {\n\tserver = new(REST_API)\n\treturn server.initialize(config)\n}\n\nfunc (server *REST_API) initialize(config APIConfig) error {\n\tserver.config = config\n\n\t//init Caliopen facility\n\tcaliopenConfig := obj.CaliopenConfig{\n\t\tRESTstoreConfig: obj.RESTstoreConfig{\n\t\t\tBackendName:  config.BackendName,\n\t\t\tHosts:        config.BackendConfig.Settings.Hosts,\n\t\t\tKeyspace:     config.BackendConfig.Settings.Keyspace,\n\t\t\tConsistency:  config.BackendConfig.Settings.Consistency,\n\t\t\tSizeLimit:    config.BackendConfig.Settings.SizeLimit,\n\t\t\tObjStoreType: config.BackendConfig.Settings.ObjStoreType,\n\t\t\tOSSConfig:    config.BackendConfig.Settings.ObjStoreSettings,\n\t\t\tUseVault:     config.BackendConfig.Settings.UseVault,\n\t\t\tVaultConfig:  config.BackendConfig.Settings.VaultSettings,\n\t\t},\n\t\tRESTindexConfig: obj.RESTIndexConfig{\n\t\t\tIndexName: config.IndexConfig.IndexName,\n\t\t\tHosts:     config.IndexConfig.Settings.Hosts,\n\t\t},\n\t\tCacheConfig: obj.CacheConfig{\n\t\t\tHost:     config.CacheSettings.Host,\n\t\t\tPassword: config.CacheSettings.Password,\n\t\t\tDb:       config.CacheSettings.Db,\n\t\t},\n\t\tNatsConfig: obj.NatsConfig{\n\t\t\tUrl:               config.NatsConfig.Url,\n\t\t\tNatsQueue:         config.NatsConfig.NatsQueue,\n\t\t\tOutSMTP_topic:     config.NatsConfig.OutSMTP_topic,\n\t\t\tOutIMAP_topic:     config.NatsConfig.OutIMAP_topic,\n\t\t\tOutTWITTER_topic:  config.NatsConfig.OutTWITTER_topic,\n\t\t\tOutMASTODON_topic: config.NatsConfig.OutMASTODON_topic,\n\t\t\tContacts_topic:    config.NatsConfig.Contacts_topic,\n\t\t\tKeys_topic:        config.NatsConfig.Keys_topic,\n\t\t\tUsers_topic:       config.NatsConfig.Users_topic,\n\t\t\tIdPoller_topic:    config.NatsConfig.IdPoller_topic,\n\t\t},\n\t\tNotifierConfig: obj.NotifierConfig{\n\t\t\tAdminUsername: config.NotifierConfig.AdminUsername,\n\t\t\tBaseUrl:       config.NotifierConfig.BaseUrl,\n\t\t\tTemplatesPath: config.NotifierConfig.TemplatesPath,\n\t\t},\n\t\tProviders: config.Providers,\n\t\tHostname:  config.Hostname + \":\" + config.Port,\n\t}\n\n\terr := caliopen.Initialize(caliopenConfig)\n\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"Caliopen facilities initialization failed\")\n\t}\n\n\t//checks that with could open the swagger specs file\n\t_, err = os.Stat(server.config.SwaggerFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc StartServer() error {\n\treturn server.start()\n}\n\nfunc (server *REST_API) start() error {\n\t// Creates a gin router with default middleware:\n\t// logger and recovery (crash-free) middleware\n\trouter := gin.Default()\n\n\t//router.Use(Dumper())\n\n\t// adds our middlewares\n\terr := http_middleware.InitSwaggerMiddleware(server.config.SwaggerFile)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"init swagger middleware failed\")\n\t} else {\n\t\trouter.Use(http_middleware.SwaggerValidator())\n\t}\n\t// adds our routes and handlers\n\tapi := router.Group(http_middleware.RoutePrefix)\n\tserver.AddHandlers(api)\n\n\t// listens\n\taddr := server.config.Interface + \":\" + server.config.ListenPort\n\terr = router.Run(addr)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"unable to start gin server\")\n\t}\n\treturn err\n}\n\nfunc (server *REST_API) AddHandlers(api *gin.RouterGroup) {\n\n\t/** users API **/\n\tusrs := api.Group(\"/users\", http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\tusrs.PATCH(\"/:user_id\", users.PatchUser)\n\tusrs.POST(\"/:user_id/actions\", users.Delete)\n\n\t/** identities **/\n\tids := api.Group(http_middleware.IdentitiesRoute, http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\tids.GET(\"/locals\", identities.GetLocalsIdentities)\n\tids.GET(\"/locals/:identity_id\", identities.GetLocalIdentity)\n\tids.GET(\"/remotes\", identities.GetRemoteIdentities)\n\tids.POST(\"/remotes\", identities.NewRemoteIdentity)\n\tids.GET(\"/remotes/:remote_id\", identities.GetRemoteIdentity)\n\tids.PATCH(\"/remotes/:remote_id\", identities.PatchRemoteIdentity)\n\tids.DELETE(\"/remotes/:remote_id\", identities.DeleteRemoteIdentity)\n\n\t/** passwords API **/\n\tpasswords := api.Group(\"/passwords\")\n\tpasswords.GET(\"/reset\", notImplemented)\n\tpasswords.POST(\"/reset\", users.RequestPasswordReset)\n\tpasswords.GET(\"/reset/:reset_token\", users.ValidatePassResetToken)\n\tpasswords.POST(\"/reset/:reset_token\", users.ResetPassword)\n\n\t/** username API **/\n\tapi.GET(\"/username/isAvailable\", users.IsAvailable)\n\n\t/** messages API **/\n\tmsg := api.Group(\"/messages\", http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\tmsg.GET(\"\", messages.GetMessagesList)\n\tmsg.GET(\"/:message_id\", messages.GetMessage)\n\tmsg.POST(\"/:message_id/actions\", messages.Actions)\n\t//attachments\n\tmsg.POST(\"/:message_id/attachments\", messages.UploadAttachment)\n\tmsg.DELETE(\"/:message_id/attachments/:attachment_id\", messages.DeleteAttachment)\n\tmsg.GET(\"/:message_id/attachments/:attachment_id\", messages.DownloadAttachment)\n\t//tags\n\tmsg.PATCH(\"/:message_id/tags\", tags.PatchResourceWithTags)\n\n\t/** discussions API **/\n\tdisc := api.Group(\"/discussions\", http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\tdisc.GET(\"\", discussions.GetDiscussionsList)\n\tdisc.GET(\"/:discussionId\", discussions.GetDiscussion)\n\n\t/** participants API **/\n\tparts := api.Group(\"/participants\", http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\tparts.GET(\"/suggest\", participants.Suggest)\n\tparts.POST(\"/discussion\", participants.HashUris)\n\n\t/** contacts API **/\n\tcts := api.Group(http_middleware.ContactsRoute, http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\tcts.GET(\"\", contacts.GetContactsList)\n\tcts.POST(\"\", contacts.NewContact)\n\tcts.GET(\"/:contactID\", contacts.GetContact)\n\tcts.PATCH(\"/:contactID\", contacts.PatchContact)\n\tcts.DELETE(\"/:contactID\", contacts.DeleteContact)\n\tcts.GET(\"/:contactID/identities\", contacts.GetIdentities)\n\t//publickeys\n\tcts.POST(\"/:contactID/publickeys\", contacts.NewPublicKey)\n\tcts.GET(\"/:contactID/publickeys\", contacts.GetPubKeys)\n\tcts.GET(\"/:contactID/publickeys/:pubkeyID\", contacts.GetPubKey)\n\tcts.PATCH(\"/:contactID/publickeys/:pubkeyID\", contacts.PatchPubKey)\n\tcts.DELETE(\"/:contactID/publickeys/:pubkeyID\", contacts.DeletePubKey)\n\t//tags\n\tcts.PATCH(\"/:contactID/tags\", tags.PatchResourceWithTags)\n\n\t/** devices API **/\n\tapi.GET(\"/validate-device/:token\", http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"), devices.ValidateDevice)\n\tdev := api.Group(http_middleware.DevicesRoute, http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\tdev.GET(\"\", devices.GetDevicesList)\n\t//dev.POST(\"\", devices.NewDevice)\n\tdev.GET(\"/:deviceID\", devices.GetDevice)\n\tdev.PATCH(\"/:deviceID\", devices.PatchDevice)\n\tdev.DELETE(\"/:deviceID\", devices.DeleteDevice)\n\tdev.POST(\"/:deviceID/actions\", devices.Actions)\n\n\t// imports\n\timp := api.Group(http_middleware.ImportsRoute, http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\timp.POST(\"\", imports.ImportFile)\n\n\t/** tags API **/\n\ttag := api.Group(http_middleware.TagsRoute, http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\ttag.GET(\"\", tags.RetrieveUserTags)\n\ttag.POST(\"\", tags.CreateTag)\n\ttag.GET(\"/:tag_name\", tags.RetrieveTag)\n\ttag.PATCH(\"/:tag_name\", tags.PatchTag)\n\ttag.DELETE(\"/:tag_name\", tags.DeleteTag)\n\n\t/** search API **/\n\tsearch := api.Group(\"/search\", http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\tsearch.GET(\"\", operations.SimpleSearch)\n\tsearch.POST(\"\", operations.AdvancedSearch)\n\n\t/** notifications API **/\n\tnotif := api.Group(\"/notifications\", http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"))\n\tnotif.GET(\"\", notifications.GetPendingNotif)\n\tnotif.DELETE(\"\", notifications.DeleteNotifications)\n\tnotif.GET(\"/:notification_id\", notifications.GetNotification)\n\tnotif.DELETE(\"/:notification_id\", notifications.DeleteNotification)\n\n\t/** providers **/\n\tprov := api.Group(\"/providers\")\n\tprov.GET(\"\", http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"), providers.GetProvidersList)\n\tprov.GET(\"/:provider_name\", http_middleware.BasicAuthFromCache(caliopen.Facilities.Cache, \"caliopen\"), providers.GetProvider)\n\tprov.GET(\"/:provider_name/callback\", providers.CallbackHandler)\n\tapi.StaticFile(\"/test/oauth\", \"../interfaces/REST/go.server/operations/providers/oauth-test.html\")\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/cmd/caliopen_rest/cli_cmds/root.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tverbose bool\n\tversion bool\n\tRootCmd = &cobra.Command{\n\t\tUse:   \"caliopen_rest\",\n\t\tShort: \"Caliopen REST HTTP API\",\n\t\tLong:  `HTTP server (& proxy) for the frontend applications`,\n\t\tRun:   nil,\n\t}\n)\n\nconst __version__ = \"0.26.1\"\n\nfunc init() {\n\tcobra.OnInitialize()\n\tRootCmd.PersistentFlags().BoolVarP(&verbose, \"verbose\", \"v\", false,\n\t\t\"print out more debug information\")\n\tRootCmd.PersistentFlags().BoolVarP(&version, \"version\", \"V\", false,\n\t\t\"print out the version of this program\")\n\tRootCmd.Run = func(cmd *cobra.Command, args []string) {\n\t\tif version {\n\t\t\tlog.Infof(\"Caliopen APIv2 version %s\", __version__)\n\t\t}\n\t}\n\tRootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {\n\t\tif verbose {\n\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t} else {\n\t\t\tlog.SetLevel(log.InfoLevel)\n\t\t}\n\t}\n\tRootCmd.AddCommand(versionCmd)\n}\n\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print the version number of Caliopen REST api\",\n\tLong:  `All software has versions. This is Caliopen API's`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tlog.Infof(\"Caliopen REST API version %s\", __version__)\n\t},\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/cmd/caliopen_rest/cli_cmds/serve.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// APIs() launches the Caliopen application's interfaces processes\n\npackage cmd\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\nvar (\n\twithProxy      bool\n\tconfigFileName string\n\tconfigPath     string\n\tpidFile        string\n\tcmdConfig      CmdConfig\n\tserveCmd       = &cobra.Command{\n\t\tUse:   \"serve\",\n\t\tShort: \"start the caliopen REST HTTP API server (& proxy)\",\n\t\tRun:   API,\n\t}\n\n\tsignalChannel = make(chan os.Signal, 1) // for trapping SIG_HUP\n)\n\nfunc init() {\n\tcobra.OnInitialize()\n\tserveCmd.PersistentFlags().StringVarP(&configFileName, \"configfile\", \"c\",\n\t\t\"caliopen-go-api_dev\", \"Name of the configuration file, without extension. (YAML, TOML, JSON… allowed)\")\n\tserveCmd.PersistentFlags().StringVarP(&configPath, \"configpath\", \"\",\n\t\t\"../../../../../configs/\", \"API config file path.\")\n\tserveCmd.PersistentFlags().StringVarP(&pidFile, \"pid-file\", \"p\",\n\t\t\"/var/run/caliopen_rest.pid\", \"Path to the pid file\")\n\tserveCmd.PersistentFlags().BoolVarP(&withProxy, \"proxy\", \"\", false, \"Start HTTP proxy for routing to both GO & Python services\")\n\tserveCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {\n\t\tif verbose {\n\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t} else {\n\t\t\tlog.SetLevel(log.InfoLevel)\n\t\t}\n\t}\n\tRootCmd.AddCommand(serveCmd)\n\n\tsignalChannel = make(chan os.Signal, 1)\n}\n\nfunc sigHandler() {\n\t// handle SIGHUP for reloading the configuration while running\n\tsignal.Notify(signalChannel, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGKILL)\n\n\tfor sig := range signalChannel {\n\n\t\tif sig == syscall.SIGHUP {\n\t\t\terr := readConfig(false)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Error(\"Error while ReadConfig (reload)\")\n\t\t\t} else {\n\t\t\t\tlog.Infof(\"Configuration is reloaded\")\n\t\t\t}\n\t\t\t// TODO: reinitialize\n\t\t} else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT {\n\t\t\tlog.Info(\"Shutdown signal caught\")\n\n\t\t\tif withProxy {\n\n\t\t\t}\n\n\t\t\t//app.Shutdown()\n\t\t\tlog.Infof(\"Shutdown completed, exiting.\")\n\t\t\tos.Exit(0)\n\t\t} else {\n\t\t\tos.Exit(0)\n\t\t}\n\t}\n}\n\nfunc API(cmd *cobra.Command, args []string) {\n\terr := readConfig(false)\n\n\tif withProxy {\n\t\t// start HTTP reverse proxy\n\t\tgo rest_api.StartProxy(cmdConfig.ProxyConfig)\n\t}\n\n\terr = rest_api.InitializeServer(cmdConfig.APIConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tgo rest_api.StartServer()\n\n\tsigHandler()\n}\n\n// Read and parse api configuration file\nfunc readConfig(readAll bool) error {\n\t// load in the main config. Reading from YAML, TOML, JSON, HCL and Java properties config files\n\n\tapiViper := viper.New()\n\tapiViper.SetConfigName(configFileName)\n\tapiViper.AddConfigPath(configPath)\n\tapiViper.AddConfigPath(\"$CALIOPENROOT/src/backend/configs/\")\n\tapiViper.AddConfigPath(\".\")\n\n\t// load APIs config\n\terr := apiViper.ReadInConfig()\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not read api config file <%s>.\", configFileName)\n\t\treturn err\n\t}\n\n\terr = apiViper.Unmarshal(&cmdConfig)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not parse api config file: <%s>\", configFileName)\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype CmdConfig struct {\n\trest_api.APIConfig\n\trest_api.IndexConfig\n\trest_api.ProxyConfig\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/cmd/caliopen_rest/main.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/cmd/caliopen_rest/cli_cmds\"\n\t\"os\"\n)\n\nfunc main() {\n\t// launch 'root cmd' that will register other commands that could be executed\n\tif err := cmd.RootCmd.Execute(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(-1)\n\t}\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/dump_request.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage rest_api\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc Dumper() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// before request\n\n\t\tlog.Infof(\"Client IP : %s\", c.ClientIP())\n\t\tlog.Infof(\"User Agent: %s\", c.GetHeader(\"User-Agent\"))\n\n\t\tc.Next()\n\t\t// after request\n\n\t}\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/errors.go",
    "content": "package rest_api\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"net/http\"\n)\n\ntype (\n\tErrors []Error\n\n\tError struct {\n\t\t// An error supports zero or more field names, because an\n\t\t// error can morph three ways: (1) it can indicate something\n\t\t// wrong with the request as a whole, (2) it can point to a\n\t\t// specific problem with a particular input field, or (3) it\n\t\t// can span multiple related input fields.\n\t\tFieldNames []string `json:\"fieldNames,omitempty\"`\n\n\t\t// The classification is like an error code, convenient to\n\t\t// use when processing or categorizing an error programmatically.\n\t\t// It may also be called the \"kind\" of error.\n\t\tClassification string `json:\"classification,omitempty\"`\n\n\t\t// Message should be human-readable and detailed enough to\n\t\t// pinpoint and resolve the problem, but it should be brief. For\n\t\t// example, a payload of 100 objects in a JSON array might have\n\t\t// an error in the 41st object. The message should help the\n\t\t// end user find and fix the error with their request.\n\t\tMessage string `json:\"message,omitempty\"`\n\t}\n)\n\nfunc notImplemented(ctx *gin.Context) {\n\te := swgErr.New(http.StatusNotImplemented, \"not implemented\")\n\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\tctx.Abort()\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/middlewares/authentication.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage http_middleware\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/sha256\"\n\t\"encoding/asn1\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gin-gonic/gin\"\n\t\"math/big\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype ecdsaSignature struct {\n\tR, S *big.Int\n}\n\n// getSignedQuery, build the HTTP query that has been signed\nfunc getSignedQuery(c *gin.Context) string {\n\tquery := c.Request.Method + c.Request.URL.String()\n\treturn query\n}\n\n// verifySignature, check for validity of device ecdsa signature\nfunc verifySignature(signature, query, curve string, x, y big.Int) (bool, error) {\n\tsign := &ecdsaSignature{}\n\tdecoded, err := base64.StdEncoding.DecodeString(signature)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t_, err = asn1.Unmarshal([]byte(decoded), sign)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tvar hashed []byte\n\tvar key ecdsa.PublicKey\n\tswitch curve {\n\tcase \"P-256\":\n\t\t// Create hash of content\n\t\thash := sha256.New()\n\t\thash.Write([]byte(query))\n\t\thashed = hash.Sum(nil)\n\t\tcrv := elliptic.P256()\n\t\tkey = ecdsa.PublicKey{Curve: crv, X: &x, Y: &y}\n\tdefault:\n\t\treturn false, errors.New(\"Invalid device curve\")\n\t}\n\n\tvalid := ecdsa.Verify(&key, hashed, sign.R, sign.S)\n\treturn valid, nil\n}\n\nfunc BasicAuthFromCache(cache backends.APICache, realm string) gin.HandlerFunc {\n\tif realm == \"\" {\n\t\trealm = \"Authorization Required\"\n\t}\n\trealm = \"Basic realm=\" + strconv.Quote(realm)\n\n\treturn func(c *gin.Context) {\n\t\t// Get provided auth headers\n\t\tvar user_id, access_token string\n\t\tvar ok bool\n\t\tvar cache_key string\n\n\t\t//Try auth-scheme 'Bearer' then 'Basic'\n\t\tif user_id, access_token, ok = BearerAuth(c.Request); !ok {\n\t\t\tif user_id, access_token, ok = c.Request.BasicAuth(); !ok {\n\t\t\t\tkickUnauthorizedRequest(c, realm)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif device_id := c.Request.Header.Get(\"X-Caliopen-Device-ID\"); device_id != \"\" {\n\t\t\tcache_key = user_id + \"-\" + device_id\n\t\t}\n\n\t\t// Search user in cache of allowed credentials\n\t\tauth, err := cache.GetAuthToken(\"tokens::\" + cache_key)\n\t\tif err != nil || auth == nil || auth.Access_token != access_token || time.Since(auth.Expires_at) > 0 {\n\t\t\tkickUnauthorizedRequest(c, realm)\n\t\t\treturn\n\t\t}\n\t\tif auth.User_status == \"maintenance\" || auth.User_status == \"locked\" {\n\t\t\tc.AbortWithError(401, errors.New(\"User status does not permit operations\"))\n\t\t}\n\n\t\tif device_sign := c.Request.Header.Get(\"X-Caliopen-Device-Signature\"); device_sign != \"\" {\n\t\t\tquery := getSignedQuery(c)\n\t\t\tvalid, err := verifySignature(device_sign, query, auth.Curve, auth.X, auth.Y)\n\t\t\tif err != nil {\n\t\t\t\tlog.Println(\"Error during signature verification: \", err)\n\t\t\t\t// kickUnauthorizedRequest(c, \"Authorization error\")\n\t\t\t}\n\t\t\tif valid == false {\n\t\t\t\tlog.Println(\"Verification of signature failed\")\n\t\t\t\t// kickUnauthorizedRequest(c, \"Authorization failed\")\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Println(\"No signature found for device \")\n\t\t}\n\n\t\t//save user_id in context for future retreival\n\t\tc.Set(\"user_id\", user_id)\n\t\tc.Set(\"access_token\", \"tokens::\"+cache_key)\n\t\tc.Set(\"shard_id\", auth.Shard_id)\n\t}\n}\n\nfunc kickUnauthorizedRequest(c *gin.Context, realm string) {\n\tc.AbortWithStatus(401)\n}\n\nfunc BearerAuth(r *http.Request) (username, password string, ok bool) {\n\tauth := r.Header.Get(\"Authorization\")\n\tif auth == \"\" {\n\t\treturn\n\t}\n\treturn parseBearerAuth(auth)\n}\n\nfunc parseBearerAuth(auth string) (username, password string, ok bool) {\n\tconst prefix = \"Bearer \"\n\tif !strings.HasPrefix(auth, prefix) {\n\t\treturn\n\t}\n\tc, err := base64.StdEncoding.DecodeString(auth[len(prefix):])\n\tif err != nil {\n\t\treturn\n\t}\n\tcs := string(c)\n\ts := strings.IndexByte(cs, ':')\n\tif s < 0 {\n\t\treturn\n\t}\n\treturn cs[:s], cs[s+1:], true\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/middlewares/config.go",
    "content": "package http_middleware\n\nconst (\n\tRoutePrefix     = \"/api/v2\"\n\tIdentitiesRoute = \"/identities\"\n\tTagsRoute       = \"/tags\"\n\tContactsRoute   = \"/contacts\"\n\tDevicesRoute    = \"/devices\"\n\tImportsRoute    = \"/imports\"\n)\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/middlewares/swagger.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage http_middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/go-openapi/analysis\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"github.com/go-openapi/loads\"\n\t\"github.com/go-openapi/runtime\"\n\t\"github.com/go-openapi/runtime/middleware\"\n\t\"github.com/go-openapi/runtime/middleware/untyped\"\n\t\"github.com/go-openapi/spec\"\n\t\"github.com/go-openapi/strfmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n)\n\nvar (\n\tswaggerSpec    *loads.Document\n\tswaggerAPI     *routableUntypedAPI\n\tswaggerContext *middleware.Context\n)\n\nconst (\n\tnoWritten     = -1\n\tdefaultStatus = 200\n)\n\ntype routableUntypedAPI struct {\n\tapi             *untyped.API\n\thlock           *sync.Mutex\n\thandlers        map[string]map[string]http.Handler\n\tdefaultConsumes string\n\tdefaultProduces string\n}\n\ntype jsonError struct {\n\tCode    int32  `json:\"code\"`\n\tMessage string `json:\"message\"`\n\tName    string `json:\"name\"`\n}\n\ntype jsonErrors []jsonError\n\nfunc InitSwaggerMiddleware(swaggerFile string) (err error) {\n\tlog.Infoln(\"Loading swagger specifications…\")\n\tswaggerSpec, err = loads.JSONSpec(swaggerFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\tswagAPI := untyped.NewAPI(swaggerSpec)\n\tswagAPI.WithJSONDefaults()\n\tswagAPI.RegisterConsumer(\"multipart/form-data\", runtime.TextConsumer())\n\t//TODO: write our consumers to be less tighted to open-api\n\t//swagAPI.ServeError = ServeError\n\n\tswagCtx := middleware.NewContext(swaggerSpec, swagAPI, nil)\n\tif swagCtx == nil {\n\t\tlog.Warn(\"no swagContext\")\n\t}\n\n\tswaggerAPI = newRoutableUntypedAPI(swaggerSpec, swagAPI, swagCtx)\n\n\tswagRouter := middleware.DefaultRouter(swaggerSpec, swaggerAPI)\n\tif swagRouter == nil {\n\t\tlog.Warn(\"no swagRouter\")\n\t}\n\tswaggerContext = middleware.NewRoutableContext(swaggerSpec, swaggerAPI, swagRouter)\n\tif swaggerContext == nil {\n\t\tlog.Warn(\"no swagContext\")\n\t}\n\n\tmiddleware.NewRouter(swaggerContext, nil) //workaround to set the router within swaggerContext\n\n\treturn\n}\n\n// checks that inputs and/or outputs conform to the swagger specs for the route\n// this middleware should be registred as the first middleware to ensure that it checks\n// requests before next handlers\nfunc SwaggerValidator() gin.HandlerFunc {\n\treturn func(ctx *gin.Context) {\n\t\tSwaggerInboundValidation(ctx)\n\t\tctx.Next()\n\t\t//SwaggerOutboundValidation(ctx)\n\t}\n}\n\nfunc SwaggerInboundValidation(ctx *gin.Context) {\n\t// make a copy of request to be able to drain body twice :\n\t// one for swagger validation, other to ctx.next handlers\n\tbody1, body2, err := drainBody(ctx.Request.Body)\n\treq_copy := new(http.Request)\n\t*req_copy = *ctx.Request\n\tctx.Request.Body = body1\n\treq_copy.Body = body2\n\tif err != nil {\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\troute, ok := swaggerContext.RouteInfo(ctx.Request)\n\tif route != nil && ok {\n\t\t_, err := swaggerContext.BindAndValidate(req_copy, route)\n\t\tif err != nil {\n\t\t\tServeError(ctx.Writer, ctx.Request, err)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tServeError(ctx.Writer, ctx.Request, errors.New(\"Route <\"+ctx.Request.Method+\" \"+ctx.Request.RequestURI+\"> not found in swagger specs.\"))\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tctx.Set(\"swgCtx\", swaggerContext)\n}\n\nfunc newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *middleware.Context) *routableUntypedAPI {\n\tvar handlers map[string]map[string]http.Handler\n\tif spec == nil || api == nil {\n\t\treturn nil\n\t}\n\tanalyzer := analysis.New(spec.Spec())\n\tfor method, hls := range analyzer.Operations() {\n\t\tum := strings.ToUpper(method)\n\t\tfor path, op := range hls {\n\t\t\tschemes := analyzer.SecurityDefinitionsFor(op)\n\n\t\t\tif handlers == nil {\n\t\t\t\thandlers = make(map[string]map[string]http.Handler)\n\t\t\t}\n\t\t\tif b, ok := handlers[um]; !ok || b == nil {\n\t\t\t\thandlers[um] = make(map[string]http.Handler)\n\t\t\t}\n\t\t\tvar handler http.Handler //fake handler as we won't use it\n\n\t\t\tif len(schemes) > 0 {\n\t\t\t\thandler = newSecureAPI(context, nil)\n\t\t\t}\n\t\t\thandlers[um][path] = handler\n\t\t}\n\t}\n\n\treturn &routableUntypedAPI{\n\t\tapi:             api,\n\t\thlock:           new(sync.Mutex),\n\t\thandlers:        handlers,\n\t\tdefaultProduces: api.DefaultProduces,\n\t\tdefaultConsumes: api.DefaultConsumes,\n\t}\n}\n\n// ServeError the error handler interface implementation\n// returns an error json as defined within swagger.json, if any\nfunc ServeError(rw gin.ResponseWriter, r *http.Request, err error) {\n\trw.Header().Set(\"Content-Type\", \"application/json\")\n\tswitch e := err.(type) {\n\tcase *swgErr.CompositeError:\n\t\ter := flattenComposite(e)\n\t\tvar lastCode int\n\t\t//get the last error code to return it to client\n\t\tif lastErr, ok := er.Errors[0].(swgErr.Error); ok {\n\t\t\tlastCode = int(lastErr.Code())\n\t\t} else {\n\t\t\tlastCode = int(e.Code())\n\t\t}\n\t\trw.WriteHeader(asHTTPCode(lastCode))\n\t\tif r == nil || r.Method != \"HEAD\" {\n\t\t\trw.Write(errorAsJSON(er))\n\t\t}\n\tcase *swgErr.MethodNotAllowedError:\n\t\trw.Header().Add(\"Allow\", strings.Join(err.(*swgErr.MethodNotAllowedError).Allowed, \",\"))\n\t\trw.WriteHeader(asHTTPCode(int(e.Code())))\n\t\tif r == nil || r.Method != \"HEAD\" {\n\t\t\trw.Write(errorAsJSON(e))\n\t\t}\n\tcase swgErr.Error:\n\t\tif e == nil {\n\t\t\trw.WriteHeader(http.StatusInternalServerError)\n\t\t\trw.Write(errorAsJSON(swgErr.New(http.StatusInternalServerError, \"Unknown error\")))\n\t\t\treturn\n\t\t}\n\t\trw.WriteHeader(asHTTPCode(int(e.Code())))\n\t\tif r == nil || r.Method != \"HEAD\" {\n\t\t\trw.Write(errorAsJSON(e))\n\t\t}\n\tdefault:\n\t\trw.WriteHeader(http.StatusInternalServerError)\n\t\tif r == nil || r.Method != \"HEAD\" {\n\t\t\trw.Write(errorAsJSON(swgErr.New(http.StatusInternalServerError, err.Error())))\n\t\t}\n\t}\n\tlog.WithError(err).Error(\"Processing error\")\n\trw.Flush()\n}\n\nfunc errorAsJSON(err swgErr.Error) []byte {\n\terrs := struct {\n\t\tErrors jsonErrors `json:\"errors\"`\n\t}{}\n\tswitch er := err.(type) {\n\tcase *swgErr.CompositeError:\n\t\tfor _, e := range er.Errors {\n\t\t\tif swgerr, ok := e.(swgErr.Error); ok {\n\t\t\t\terrs.Errors = append(errs.Errors, jsonError{swgerr.Code(), e.Error(), \"\"})\n\t\t\t} else {\n\t\t\t\terrs.Errors = append(errs.Errors, jsonError{err.Code(), e.Error(), \"\"})\n\t\t\t}\n\t\t}\n\t\tb, _ := json.Marshal(errs)\n\t\treturn b\n\tdefault:\n\t\terrs.Errors = append(errs.Errors, jsonError{err.Code(), err.Error(), \"\"})\n\t\tb, _ := json.Marshal(errs)\n\t\treturn b\n\t}\n}\n\nfunc flattenComposite(errs *swgErr.CompositeError) *swgErr.CompositeError {\n\tvar res []error\n\tfor _, er := range errs.Errors {\n\t\tswitch e := er.(type) {\n\t\tcase *swgErr.CompositeError:\n\t\t\tif len(e.Errors) > 0 {\n\t\t\t\tflat := flattenComposite(e)\n\t\t\t\tif len(flat.Errors) > 0 {\n\t\t\t\t\tres = append(res, flat.Errors...)\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tif e != nil {\n\t\t\t\tres = append(res, e)\n\t\t\t}\n\t\t}\n\t}\n\treturn swgErr.CompositeValidationError(res...)\n}\n\nfunc asHTTPCode(input int) int {\n\tif input < 400 || input >= 600 {\n\t\treturn 422\n\t}\n\treturn input\n}\n\n// drainBody reads all of b to memory and then returns two equivalent\n// ReadClosers yielding the same bytes.\n//\n// It returns an error if the initial slurp of all bytes fails. It does not attempt\n// to make the returned ReadClosers have identical error-matching behavior.\nfunc drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {\n\tif b == http.NoBody {\n\t\t// No copying needed. Preserve the magic sentinel meaning of NoBody.\n\t\treturn http.NoBody, http.NoBody, nil\n\t}\n\tvar buf bytes.Buffer\n\tif _, err = buf.ReadFrom(b); err != nil {\n\t\treturn nil, b, err\n\t}\n\tif err = b.Close(); err != nil {\n\t\treturn nil, b, err\n\t}\n\treturn ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil\n}\n\n// Func copied from go-openapi\nfunc newSecureAPI(ctx *middleware.Context, next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {\n\t\troute, _ := ctx.RouteInfo(r)\n\t\tif route != nil && len(route.Authenticators) == 0 {\n\t\t\tnext.ServeHTTP(rw, r)\n\t\t\treturn\n\t\t}\n\n\t\tif _, err := ctx.Authorize(r, route); err != nil {\n\t\t\tctx.Respond(rw, r, route.Produces, route, err)\n\t\t\treturn\n\t\t}\n\n\t\tnext.ServeHTTP(rw, r)\n\t})\n}\n\n// Funcs below are copied from openapi sources to facilitate the swagger validation.\n// They allow us to satisfy the openapi \"RoutableAPI\" interface.\n// Our current implementation do not call them directly at anytime.\n// They should be removed in future.\nfunc (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {\n\tr.hlock.Lock()\n\tpaths, ok := r.handlers[strings.ToUpper(method)]\n\tif !ok {\n\t\tr.hlock.Unlock()\n\t\treturn nil, false\n\t}\n\thandler, ok := paths[path]\n\tr.hlock.Unlock()\n\treturn handler, ok\n}\nfunc (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {\n\treturn r.api.ServeError\n}\nfunc (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {\n\treturn r.api.ConsumersFor(mediaTypes)\n}\nfunc (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {\n\treturn r.api.ProducersFor(mediaTypes)\n}\nfunc (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {\n\treturn r.api.AuthenticatorsFor(schemes)\n}\nfunc (r *routableUntypedAPI) Formats() strfmt.Registry {\n\treturn r.api.Formats()\n}\nfunc (r *routableUntypedAPI) DefaultProduces() string {\n\treturn r.defaultProduces\n}\nfunc (r *routableUntypedAPI) DefaultConsumes() string {\n\treturn r.defaultConsumes\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/contacts/Identities.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage contacts\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"net/http\"\n)\n\n//GET …/contacts/{contactID}/identities\nfunc GetIdentities(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\tcontact_id, err := operations.NormalizeUUIDstring(ctx.Param(\"contactID\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tidentities, err := caliopen.Facilities.RESTfacility.RetrieveContactIdentities(user_id, contact_id)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusInternalServerError, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tret := struct {\n\t\tTotal             int                       `json:\"total\"`\n\t\tContactIdentities []objects.ContactIdentity `json:\"contact_identities\"`\n\t}{len(identities), identities}\n\tctx.JSON(http.StatusOK, ret)\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/contacts/contacts.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage contacts\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// GetContactList handles GET /contacts\nfunc GetContactsList(ctx *gin.Context) {\n\tvar limit, offset int\n\tvar user_UUID UUID\n\tvar list []*Contact\n\tvar totalFound int64\n\tvar err error\n\n\tuser_uuid_str := ctx.MustGet(\"user_id\").(string)\n\tuser_uuid, _ := uuid.FromString(user_uuid_str)\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser_UUID.UnmarshalBinary(user_uuid.Bytes())\n\n\tquery_values := ctx.Request.URL.Query()\n\tif uriFilter, ok := query_values[\"uri\"]; ok {\n\t\t// lookup contact by uri is made into store\n\t\tlist, totalFound, err = caliopen.Facilities.RESTfacility.LookupContactByUri(user_uuid_str, uriFilter[0])\n\t\tif err != nil && err.Error() != \"not found\" {\n\t\t\te := swgErr.New(http.StatusInternalServerError, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tif l, ok := query_values[\"limit\"]; ok {\n\t\t\tlimit, _ = strconv.Atoi(l[0])\n\t\t\tquery_values.Del(\"limit\")\n\t\t}\n\t\tif o, ok := query_values[\"offset\"]; ok {\n\t\t\toffset, _ = strconv.Atoi(o[0])\n\t\t\tquery_values.Del(\"offset\")\n\t\t}\n\n\t\tfilter := IndexSearch{\n\t\t\tUser_id:  user_UUID,\n\t\t\tShard_id: shard_id,\n\t\t\tTerms:    map[string][]string(query_values),\n\t\t\tLimit:    limit,\n\t\t\tOffset:   offset,\n\t\t}\n\n\t\tlist, totalFound, err = caliopen.Facilities.RESTfacility.RetrieveContacts(filter)\n\t\tif err != nil {\n\t\t\te := swgErr.New(http.StatusInternalServerError, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t}\n\n\t// render response\n\tvar respBuf bytes.Buffer\n\trespBuf.WriteString(\"{\\\"total\\\": \" + strconv.FormatInt(totalFound, 10) + \",\")\n\trespBuf.WriteString(\"\\\"contacts\\\":[\")\n\tfirst := true\n\tfor _, contact := range list {\n\t\tjson_contact, err := contact.MarshalFrontEnd()\n\t\tif err == nil {\n\t\t\tif first {\n\t\t\t\tfirst = false\n\t\t\t} else {\n\t\t\t\trespBuf.WriteByte(',')\n\t\t\t}\n\t\t\trespBuf.Write(json_contact)\n\t\t}\n\t}\n\trespBuf.WriteString(\"]}\")\n\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", respBuf.Bytes())\n}\n\n// NewContact handles POST /contacts\nfunc NewContact(ctx *gin.Context) {\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser_info := &UserInfo{User_id: userId, Shard_id: shard_id}\n\n\tcontact := new(Contact)\n\tcontact.MarshallNew()\n\terr = ctx.ShouldBindJSON(contact)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tcontact.UserId.UnmarshalBinary(uuid.FromStringOrNil(userId).Bytes())\n\terr = caliopen.Facilities.RESTfacility.CreateContact(user_info, contact)\n\tif err != nil {\n\t\tvar e error\n\t\tif strings.HasPrefix(err.Error(), \"uri <\") {\n\t\t\te = swgErr.New(http.StatusForbidden, err.Error())\n\t\t} else {\n\t\t\te = swgErr.New(http.StatusInternalServerError, err.Error())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.JSON(http.StatusOK, struct {\n\t\t\tLocation  string `json:\"location\"`\n\t\t\tContactId string `json:\"contact_id\"`\n\t\t}{\n\t\t\thttp_middleware.RoutePrefix + http_middleware.ContactsRoute + \"/\" + contact.ContactId.String(),\n\t\t\tcontact.ContactId.String(),\n\t\t})\n\t}\n\treturn\n}\n\n// GetContact handles GET /contacts/:contactID\nfunc GetContact(ctx *gin.Context) {\n\tuserID := ctx.MustGet(\"user_id\").(string)\n\tcontactID, err := operations.NormalizeUUIDstring(ctx.Param(\"contactID\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tcontact, err := caliopen.Facilities.RESTfacility.RetrieveContact(userID, contactID)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusInternalServerError, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tcontact_json, err := contact.MarshalFrontEnd()\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", contact_json)\n\t}\n}\n\n// PatchContact handles PATCH /contacts/:contactID\nfunc PatchContact(ctx *gin.Context) {\n\tvar err error\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser_info := &UserInfo{User_id: userId, Shard_id: shard_id}\n\n\tcontactId, err := operations.NormalizeUUIDstring(ctx.Param(\"contactID\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tvar patch []byte\n\tpatch, err = ioutil.ReadAll(ctx.Request.Body)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// call REST facility with payload\n\terr = caliopen.Facilities.RESTfacility.PatchContact(user_info, patch, contactId)\n\tif err != nil {\n\t\tif Cerr, ok := err.(CaliopenError); ok {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\tif Cerr.Code() == FailDependencyCaliopenErr {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"[RESTfacility] PatchContact failed\"), Cerr, Cerr.Cause())\n\t\t\t} else if Cerr.Code() == ForbiddenCaliopenErr {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusForbidden, \"[RESTfacility] PatchContact forbidden\"), Cerr, Cerr.Cause())\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(Cerr, Cerr.Cause())\n\t\t\t}\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n\n}\n\n// DeleteContact handles DELETE /contacts/:contactID\nfunc DeleteContact(ctx *gin.Context) {\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tcontactID, err := operations.NormalizeUUIDstring(ctx.Param(\"contactID\"))\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser_info := &UserInfo{User_id: userId, Shard_id: shard_id}\n\n\terr = caliopen.Facilities.RESTfacility.DeleteContact(user_info, contactID)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusInternalServerError, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tctx.Status(http.StatusNoContent)\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/contacts/keys.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage contacts\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\n// NewPublicKey handles POST …/contacts/:contactID/publickeys\nfunc NewPublicKey(ctx *gin.Context) {\n\t// check payload\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\tuserId, err := operations.NormalizeUUIDstring(userId)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tcontactId := ctx.Param(\"contactID\")\n\tif contactId == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"empty contactID\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tcontactId, err = operations.NormalizeUUIDstring(contactId)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tcontact, err := caliopen.Facilities.RESTfacility.RetrieveContact(userId, contactId)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusNotFound, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tpayload := struct {\n\t\tLabel string\n\t\tKey   string\n\t}{}\n\terr = ctx.ShouldBindJSON(&payload)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\trawKey, err := base64.StdEncoding.DecodeString(payload.Key)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// call API\n\tpubkey, apiErr := caliopen.Facilities.RESTfacility.CreatePGPPubKey(payload.Label, rawKey, contact)\n\tif apiErr != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tswitch apiErr.Code() {\n\t\tcase UnprocessableCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnprocessableEntity, \"api returned unprocessable error\"), apiErr, apiErr.Cause())\n\t\tcase DbCaliopenErr:\n\t\t\tif prevErr, ok := apiErr.Cause().(CaliopenError); ok {\n\t\t\t\tswitch prevErr.Code() {\n\t\t\t\tcase ForbiddenCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusForbidden, \"api returned forbidden error\"), apiErr, apiErr.Cause())\n\t\t\t\tcase NotFoundCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"api failed to retrieve in store\"), apiErr, apiErr.Cause())\n\t\t\t\tdefault:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t}\n\t\tdefault:\n\t\t\treturnedErr = swgErr.CompositeValidationError(apiErr, apiErr.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.JSON(http.StatusOK, struct {\n\t\t\tLocation    string `json:\"location\"`\n\t\t\tPublicKeyID string `json:\"publickey_id\"`\n\t\t}{\n\t\t\thttp_middleware.RoutePrefix + http_middleware.ContactsRoute + \"/\" + contactId + \"/publickeys/\" + pubkey.KeyId.String(),\n\t\t\tpubkey.KeyId.String(),\n\t\t})\n\t}\n\treturn\n\n}\n\n// GetPubKeys handles GET …/contacts/:contactID/publickeys\nfunc GetPubKeys(ctx *gin.Context) {\n\t// check payload\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\tuserId, err := operations.NormalizeUUIDstring(userId)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tcontactId := ctx.Param(\"contactID\")\n\tif contactId == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"empty contactID\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tcontactId, err = operations.NormalizeUUIDstring(contactId)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// check if contact exist to return relevant error\n\tif !caliopen.Facilities.RESTfacility.ContactExists(userId, contactId) {\n\t\te := swgErr.New(http.StatusNotFound, \"contact not found\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// call API\n\tkeys, apiErr := caliopen.Facilities.RESTfacility.RetrieveContactPubKeys(userId, contactId)\n\tif apiErr != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tswitch apiErr.Code() {\n\t\tcase UnprocessableCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnprocessableEntity, \"api returned unprocessable error\"), apiErr, apiErr.Cause())\n\t\tcase DbCaliopenErr:\n\t\t\tif prevErr, ok := apiErr.Cause().(CaliopenError); ok {\n\t\t\t\tswitch prevErr.Code() {\n\t\t\t\tcase ForbiddenCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusForbidden, \"api returned forbidden error\"), apiErr, apiErr.Cause())\n\t\t\t\tcase NotFoundCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"api failed to retrieve in store\"), apiErr, apiErr.Cause())\n\t\t\t\tdefault:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t}\n\t\tdefault:\n\t\t\treturnedErr = swgErr.CompositeValidationError(apiErr, apiErr.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t} else {\n\t\tvar respBuf bytes.Buffer\n\t\trespBuf.WriteString(\"{\\\"total\\\": \" + strconv.Itoa(len(keys)) + \", \")\n\t\trespBuf.WriteString(\"\\\"pubkeys\\\":[\")\n\t\tfirst := true\n\t\tfor _, pubkey := range keys {\n\t\t\tjsonKey, err := pubkey.MarshalFrontEnd()\n\t\t\tif err == nil {\n\t\t\t\tif first {\n\t\t\t\t\tfirst = false\n\t\t\t\t} else {\n\t\t\t\t\trespBuf.WriteByte(',')\n\t\t\t\t}\n\t\t\t\trespBuf.Write(jsonKey)\n\t\t\t}\n\t\t}\n\t\trespBuf.WriteString(\"]}\")\n\n\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", respBuf.Bytes())\n\t}\n}\n\n// GetPubKey handles GET …/contacts/:contactID/publickeys/:pubkeyID\nfunc GetPubKey(ctx *gin.Context) {\n\t// check payload\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\tuserId, err1 := operations.NormalizeUUIDstring(userId)\n\tcontactId := ctx.Param(\"contactID\")\n\tcontactId, err2 := operations.NormalizeUUIDstring(contactId)\n\tpubkeyId := ctx.Param(\"pubkeyID\")\n\tpubkeyId, err3 := operations.NormalizeUUIDstring(pubkeyId)\n\tif err1 != nil || err2 != nil || err3 != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"invalid uuid\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// check if contact exist to return relevant error\n\tif !caliopen.Facilities.RESTfacility.ContactExists(userId, contactId) {\n\t\te := swgErr.New(http.StatusNotFound, \"contact not found\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// call API\n\tpubkey, err := caliopen.Facilities.RESTfacility.RetrievePubKey(userId, contactId, pubkeyId)\n\tif err != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tswitch err.Code() {\n\t\tcase UnprocessableCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnprocessableEntity, \"api returned unprocessable error\"), err, err.Cause())\n\t\tcase DbCaliopenErr:\n\t\t\tif prevErr, ok := err.Cause().(CaliopenError); ok {\n\t\t\t\tswitch prevErr.Code() {\n\t\t\t\tcase ForbiddenCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusForbidden, \"api returned forbidden error\"), err, err.Cause())\n\t\t\t\tcase NotFoundCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"api failed to retrieve in store\"), err, err.Cause())\n\t\t\t\tdefault:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), err, err.Cause())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), err, err.Cause())\n\t\t\t}\n\t\tdefault:\n\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tkey_json, e := pubkey.MarshalFrontEnd()\n\tif e != nil {\n\t\tse := swgErr.New(http.StatusFailedDependency, e.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, se)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", key_json)\n\t}\n}\n\n// PatchPubKey handles PATCH …/contacts/:contactID/publickeys/:pubkeyID\nfunc PatchPubKey(ctx *gin.Context) {\n\t// check payload\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\tuserId, err1 := operations.NormalizeUUIDstring(userId)\n\tcontactId := ctx.Param(\"contactID\")\n\tcontactId, err2 := operations.NormalizeUUIDstring(contactId)\n\tpubkeyId := ctx.Param(\"pubkeyID\")\n\tpubkeyId, err3 := operations.NormalizeUUIDstring(pubkeyId)\n\tif err1 != nil || err2 != nil || err3 != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"invalid uuid\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tvar patch []byte\n\tpatch, err := ioutil.ReadAll(ctx.Request.Body)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// check if contact exist to return relevant error\n\tif !caliopen.Facilities.RESTfacility.ContactExists(userId, contactId) {\n\t\te := swgErr.New(http.StatusNotFound, \"contact not found\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tcaliopenErr := caliopen.Facilities.RESTfacility.PatchPubKey(patch, userId, contactId, pubkeyId)\n\tif caliopenErr != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tswitch caliopenErr.Code() {\n\t\tcase UnprocessableCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnprocessableEntity, \"api returned unprocessable error\"), caliopenErr, caliopenErr.Cause())\n\t\tcase DbCaliopenErr:\n\t\t\tif prevErr, ok := caliopenErr.Cause().(CaliopenError); ok {\n\t\t\t\tswitch prevErr.Code() {\n\t\t\t\tcase ForbiddenCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusForbidden, \"api returned forbidden error\"), caliopenErr, caliopenErr.Cause())\n\t\t\t\tcase NotFoundCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"api failed to retrieve in store\"), caliopenErr, caliopenErr.Cause())\n\t\t\t\tdefault:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), caliopenErr, caliopenErr.Cause())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), caliopenErr, caliopenErr.Cause())\n\t\t\t}\n\t\tdefault:\n\t\t\treturnedErr = swgErr.CompositeValidationError(caliopenErr, caliopenErr.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n}\n\n// DeletePubKey handles DELETE …/contacts/:contactID/publickeys/:pubkeyID\nfunc DeletePubKey(ctx *gin.Context) {\n\t// check payload\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\tuserId, err1 := operations.NormalizeUUIDstring(userId)\n\tcontactId := ctx.Param(\"contactID\")\n\tcontactId, err2 := operations.NormalizeUUIDstring(contactId)\n\tpubkeyId := ctx.Param(\"pubkeyID\")\n\tpubkeyId, err3 := operations.NormalizeUUIDstring(pubkeyId)\n\tif err1 != nil || err2 != nil || err3 != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"invalid uuid\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// check if contact exist to return relevant error\n\tif !caliopen.Facilities.RESTfacility.ContactExists(userId, contactId) {\n\t\te := swgErr.New(http.StatusNotFound, \"contact not found\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// call API\n\tpubkey := &PublicKey{\n\t\tKeyId:      UUID(uuid.FromStringOrNil(pubkeyId)),\n\t\tResourceId: UUID(uuid.FromStringOrNil(contactId)),\n\t\tUserId:     UUID(uuid.FromStringOrNil(userId)),\n\t}\n\terr := caliopen.Facilities.RESTfacility.DeletePubKey(pubkey)\n\tif err != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tswitch err.Code() {\n\t\tcase UnprocessableCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnprocessableEntity, \"api returned unprocessable error\"), err, err.Cause())\n\t\tcase DbCaliopenErr:\n\t\t\tif prevErr, ok := err.Cause().(CaliopenError); ok {\n\t\t\t\tswitch prevErr.Code() {\n\t\t\t\tcase ForbiddenCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusForbidden, \"api returned forbidden error\"), err, err.Cause())\n\t\t\t\tcase NotFoundCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"api failed to retrieve in store\"), err, err.Cause())\n\t\t\t\tdefault:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), err, err.Cause())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), err, err.Cause())\n\t\t\t}\n\t\tdefault:\n\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/devices/devices.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage devices\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\n// NewDevice handles POST /devices\nfunc NewDevice(ctx *gin.Context) {\n\te := swgErr.New(http.StatusForbidden, \"do not create new device this way\")\n\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\tctx.Abort()\n\treturn\n\n\t/* LEGACY code\n\tvar device Device\n\tb := binding.JSON\n\tif err := b.Bind(ctx.Request, &device); err == nil {\n\t\tuser_uuid, _ := uuid.FromString(ctx.MustGet(\"user_id\").(string))\n\t\tdevice.UserId.UnmarshalBinary(user_uuid.Bytes())\n\t\tif device.Name == \"\" || strings.Replace(device.Name, \" \", \"\", -1) == \"\" {\n\t\t\terr := errors.New(\"device's name is empty\")\n\t\t\te := swgErr.New(http.StatusBadRequest, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t\tif device.Type == \"\" || strings.Replace(device.Type, \" \", \"\", -1) == \"\" {\n\t\t\terr := errors.New(\"device's type is empty\")\n\t\t\te := swgErr.New(http.StatusBadRequest, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t\tdevice.Name = strings.TrimSpace(device.Name)\n\t\tdevice.Type = strings.TrimSpace(device.Type)\n\n\t\tdevice.IpCreation = ctx.ClientIP()\n\t\tdevice.UserAgent = ctx.GetHeader(\"User-Agent\")\n\n\t\terr := caliopen.Facilities.RESTfacility.CreateDevice(&device)\n\t\tif err != nil {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\tif err.Code() == DbCaliopenErr && err.Cause().Error() == \"not found\" {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), err, err.Cause())\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t\t}\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t} else {\n\t\t\tctx.JSON(http.StatusOK, struct {\n\t\t\t\tLocation string `json:\"location\"`\n\t\t\t\tDeviceId string `json:\"device_id\"`\n\t\t\t}{\n\t\t\t\thttp_middleware.RoutePrefix + http_middleware.DevicesRoute + \"/\" + device.DeviceId.String(),\n\t\t\t\tdevice.DeviceId.String(),\n\t\t\t})\n\t\t}\n\t} else {\n\t\te := swgErr.New(http.StatusBadRequest, fmt.Sprintf(\"Unable to json marshal the provided payload : %s\", err))\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n\t*/\n}\n\n// GetDevicesList handles GET /devices\nfunc GetDevicesList(ctx *gin.Context) {\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\tdevices, err := caliopen.Facilities.RESTfacility.RetrieveDevices(userId)\n\tif err != nil && err.Cause().Error() != \"devices not found\" {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tvar respBuf bytes.Buffer\n\trespBuf.WriteString(\"{\\\"total\\\": \" + strconv.Itoa(len(devices)) + \",\")\n\trespBuf.WriteString((\"\\\"devices\\\":[\"))\n\tfirst := true\n\tfor _, device := range devices {\n\t\tjson_device, err := device.MarshalFrontEnd()\n\t\tif err == nil {\n\t\t\tif first {\n\t\t\t\tfirst = false\n\t\t\t} else {\n\t\t\t\trespBuf.WriteByte(',')\n\t\t\t}\n\t\t\trespBuf.Write(json_device)\n\t\t}\n\t}\n\trespBuf.WriteString(\"]}\")\n\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", respBuf.Bytes())\n}\n\n// GetDevice handles GET /devices/:deviceID\nfunc GetDevice(ctx *gin.Context) {\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\tdeviceId, err := operations.NormalizeUUIDstring(ctx.Param(\"deviceID\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tdevice, CalErr := caliopen.Facilities.RESTfacility.RetrieveDevice(userId, deviceId)\n\tif CalErr != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif CalErr.Code() == DbCaliopenErr && CalErr.Cause().Error() == \"not found\" {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), CalErr, CalErr.Cause())\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(CalErr, CalErr.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tdevice_json, err := device.MarshalFrontEnd()\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", device_json)\n\t}\n}\n\n// PatchDevice handles PATCH /devices/:deviceID\nfunc PatchDevice(ctx *gin.Context) {\n\tvar err error\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tdeviceId, err := operations.NormalizeUUIDstring(ctx.Param(\"deviceID\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tvar patch []byte\n\tpatch, err = ioutil.ReadAll(ctx.Request.Body)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// call REST facility with payload\n\terr = caliopen.Facilities.RESTfacility.PatchDevice(patch, userId, deviceId)\n\tif err != nil {\n\t\tif Cerr, ok := err.(CaliopenError); ok {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\tif Cerr.Code() == DbCaliopenErr && Cerr.Cause().Error() == \"not found\" {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), Cerr, Cerr.Cause())\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(Cerr, Cerr.Cause())\n\t\t\t}\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n}\n\n// DeleteDevice handles DELETE /devices/:deviceID\nfunc DeleteDevice(ctx *gin.Context) {\n\tvar err error\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tdeviceId, err := operations.NormalizeUUIDstring(ctx.Param(\"deviceID\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\terr = caliopen.Facilities.RESTfacility.DeleteDevice(userId, deviceId)\n\tif err != nil {\n\t\tif Cerr, ok := err.(CaliopenError); ok {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\tif Cerr.Code() == DbCaliopenErr && Cerr.Cause().Error() == \"not found\" {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), Cerr, Cerr.Cause())\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(Cerr, Cerr.Cause())\n\t\t\t}\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n}\n\n// Actions handles POST /devices/:deviceID/actions\nfunc Actions(ctx *gin.Context) {\n\tvar err error\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tdeviceId, err := operations.NormalizeUUIDstring(ctx.Param(\"deviceID\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tvar actions ActionsPayload\n\tif err := ctx.BindJSON(&actions); err == nil {\n\t\tswitch actions.Actions[0] {\n\t\tcase \"device-validation\":\n\t\t\t// validate params in payload before calling API\n\t\t\tvalid := true\n\t\t\tvar channel string\n\t\t\tif actions.Params == nil {\n\t\t\t\tvalid = false\n\t\t\t} else if params, ok := actions.Params.(map[string]interface{}); ok {\n\t\t\t\tif channel, ok = params[\"channel\"].(string); channel == \"\" || !ok {\n\t\t\t\t\tvalid = false\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvalid = false\n\t\t\t}\n\t\t\tif !valid {\n\t\t\t\te := swgErr.New(http.StatusUnprocessableEntity, \"params is missing or malformed\")\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactions.UserId = userId\n\t\t\tactions.Params.(map[string]interface{})[\"device_id\"] = deviceId\n\t\t\terr := caliopen.Facilities.RESTfacility.RequestDeviceValidation(userId, deviceId, channel, caliopen.Facilities.Notifiers)\n\t\t\tif err != nil {\n\t\t\t\tif Cerr, ok := err.(CaliopenError); ok {\n\t\t\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\t\t\tif (Cerr.Code() == DbCaliopenErr && Cerr.Cause().Error() == \"not found\") || Cerr.Code() == NotFoundCaliopenErr {\n\t\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), Cerr, Cerr.Cause())\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(Cerr, Cerr.Cause())\n\t\t\t\t\t}\n\t\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\t\t\tctx.Abort()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t} else {\n\t\t\t\tctx.Status(http.StatusNoContent)\n\t\t\t}\n\t\tdefault:\n\t\t\te := swgErr.New(http.StatusNotImplemented, \"unknown action \"+actions.Actions[0])\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t}\n\t} else {\n\t\tlog.WithError(err).Errorf(\"failed to bind json payload to ActionsPayload struct\")\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n}\n\n// ValidateDevice handles GET /validate-device/:token\nfunc ValidateDevice(ctx *gin.Context) {\n\n\tvar err error\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\ttoken := ctx.Param(\"token\")\n\n\tif token == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"validation token is empty\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\terr = caliopen.Facilities.RESTfacility.ConfirmDeviceValidation(userId, token)\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusNotFound, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/discussions/discussions.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage discussions\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\n// GET …/discussions\nfunc GetDiscussionsList(ctx *gin.Context) {\n\t// temporary hack to check if X-Caliopen-IL header is in request, because go-openapi pkg fails to do it.\n\t// (NB : CanonicalHeaderKey func normalize http headers with uppercase at beginning of words)\n\tif _, ok := ctx.Request.Header[\"X-Caliopen-Il\"]; !ok {\n\t\te := swgErr.New(http.StatusFailedDependency, \"Missing mandatory header 'X-Caliopen-Il'.\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tILrange := operations.GetImportanceLevel(ctx)\n\t// temporary hack to check if X-Caliopen-IL header is in request, because go-openapi pkg fails to do it.\n\t// (NB : CanonicalHeaderKey func normalize http headers with uppercase at beginning of words)\n\tif _, ok := ctx.Request.Header[\"X-Caliopen-Pi\"]; !ok {\n\t\te := swgErr.New(http.StatusFailedDependency, \"Missing mandatory header 'X-Caliopen-PI'.\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tPIrange := operations.GetPrivacyIndex(ctx)\n\tuserId, err := operations.NormalizeUUIDstring(ctx.GetString(\"user_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n\tshardId := ctx.MustGet(\"shard_id\").(string)\n\tuserInfo := &UserInfo{User_id: userId, Shard_id: shardId}\n\tvar limit, offset int\n\tquery_values := ctx.Request.URL.Query()\n\tif l, ok := query_values[\"limit\"]; ok {\n\t\tlimit, _ = strconv.Atoi(l[0])\n\t\tquery_values.Del(\"limit\")\n\t}\n\tif o, ok := query_values[\"offset\"]; ok {\n\t\toffset, _ = strconv.Atoi(o[0])\n\t\tquery_values.Del(\"offset\")\n\t}\n\n\tlist, totalFound, err := caliopen.Facilities.RESTfacility.GetDiscussionsList(userInfo, ILrange, PIrange, limit, offset)\n\tif err != nil && err.Error() != \"not found\" {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tvar respBuf bytes.Buffer\n\trespBuf.WriteString(\"{\\\"total\\\": \" + strconv.FormatInt(int64(totalFound), 10) + \",\")\n\trespBuf.WriteString(\"\\\"discussions\\\":[\")\n\tfirst := true\n\tfor _, disc := range list {\n\t\tjson_disc, err := disc.MarshalFrontEnd()\n\t\tif err == nil {\n\t\t\tif first {\n\t\t\t\tfirst = false\n\t\t\t} else {\n\t\t\t\trespBuf.WriteByte(',')\n\t\t\t}\n\t\t\trespBuf.Write(json_disc)\n\t\t}\n\t}\n\trespBuf.WriteString(\"]}\")\n\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", respBuf.Bytes())\n}\n\n// GET …/discussions/:discussionId\nfunc GetDiscussion(ctx *gin.Context) {\n\tuserId, err := operations.NormalizeUUIDstring(ctx.GetString(\"user_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n\tshardId := ctx.MustGet(\"shard_id\").(string)\n\tuserInfo := &UserInfo{User_id: userId, Shard_id: shardId}\n\tdiscussion, err := caliopen.Facilities.RESTfacility.DiscussionMetadata(userInfo, ctx.Param(\"discussionId\"))\n\tif err != nil {\n\t\tvar e error\n\t\tif err.Error() == \"not found\" {\n\t\t\te = swgErr.New(http.StatusNotFound, err.Error())\n\t\t} else {\n\t\t\te = swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tdisc_json, err := discussion.MarshalFrontEnd()\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", disc_json)\n\t}\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/helpers.go",
    "content": "package operations\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// fall back to default values if can't extract valid numbers.\nfunc GetImportanceLevel(ctx *gin.Context) (il [2]int8) {\n\til = [2]int8{-10, 10} // default values\n\tvar from, to int\n\tvar err error\n\tif il_header, ok := ctx.Request.Header[\"X-Caliopen-Il\"]; !ok {\n\t\treturn il\n\t} else {\n\t\til_range_str := strings.Split(il_header[0], \";\") // get only first value found\n\t\tif len(il_range_str) != 2 {\n\t\t\treturn il\n\t\t}\n\t\tfrom, err = strconv.Atoi(il_range_str[0])\n\t\tif err != nil {\n\t\t\tfrom = -10\n\t\t}\n\t\tto, err = strconv.Atoi(il_range_str[1])\n\t\tif err != nil {\n\t\t\tto = 10\n\t\t}\n\t\tif from < -10 || from > 10 {\n\t\t\tfrom = -10\n\t\t}\n\t\tif to < -10 || to > 10 {\n\t\t\tto = 10\n\t\t}\n\t\tif from > to {\n\t\t\tfrom = to\n\t\t}\n\t\til[0] = int8(from)\n\t\til[1] = int8(to)\n\t\treturn\n\t}\n}\n\n// fall back to default values if can't extract valid numbers.\nfunc GetPrivacyIndex(ctx *gin.Context) (pi [2]int8) {\n\tpi = [2]int8{0, 100} // default values\n\tvar from, to int\n\tvar err error\n\tif pi_header, ok := ctx.Request.Header[\"X-Caliopen-Pi\"]; !ok {\n\t\treturn pi\n\t} else {\n\t\tpi_range_str := strings.Split(pi_header[0], \";\") // get only first value found\n\t\tif len(pi_range_str) != 2 {\n\t\t\treturn pi\n\t\t}\n\t\tfrom, err = strconv.Atoi(pi_range_str[0])\n\t\tif err != nil {\n\t\t\tfrom = 0\n\t\t}\n\t\tto, err = strconv.Atoi(pi_range_str[1])\n\t\tif err != nil {\n\t\t\tto = 100\n\t\t}\n\t\tif from < 0 || from > 100 {\n\t\t\tfrom = 0\n\t\t}\n\t\tif to < 0 || to > 100 {\n\t\t\tto = 100\n\t\t}\n\t\tif from > to {\n\t\t\tfrom = to\n\t\t}\n\t\tpi[0] = int8(from)\n\t\tpi[1] = int8(to)\n\t\treturn\n\t}\n}\n\n// NormalizeUUIDstring returns a valid uuidv4 string from input\n// or an error if input string is invalid.\n// Following input formats are supported:\n// \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\",\n// \"{6ba7b810-9dad-11d1-80b4-00c04fd430c8}\",\n// \"urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8\"\n// Output string is always in \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\" format.\nfunc NormalizeUUIDstring(uuid_str string) (string, error) {\n\tid, err := uuid.FromString(uuid_str)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn id.String(), nil\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/identities/identities.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage identities\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n//GET …/identities/locals/{identity_id}\nfunc GetLocalIdentity(ctx *gin.Context) {\n\te := swgErr.New(http.StatusNotImplemented, \"not implemented\")\n\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\tctx.Abort()\n}\n\n//GET …/identities/locals\nfunc GetLocalsIdentities(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\tidentities, err := caliopen.Facilities.RESTfacility.RetrieveLocalIdentities(user_id)\n\tif err != nil && err.Error() != \"not found\" {\n\t\te := swgErr.New(http.StatusInternalServerError, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tret := struct {\n\t\tTotal            int            `json:\"total\"`\n\t\tLocalsIdentities []UserIdentity `json:\"local_identities\"`\n\t}{len(identities), identities}\n\tctx.JSON(http.StatusOK, ret)\n}\n\n// GetRemoteIdentities handles GET …/identities/remotes\nfunc GetRemoteIdentities(ctx *gin.Context) {\n\n\tuserID, err := operations.NormalizeUUIDstring(ctx.GetString(\"user_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n\tnoCredentials := false // by default do not return Credentials\n\tlist, e := caliopen.Facilities.RESTfacility.RetrieveRemoteIdentities(userID, noCredentials)\n\tif e != nil && e.Code() != NotFoundCaliopenErr {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"RESTfacility returned error\"), e, e.Cause())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tvar respBuf bytes.Buffer\n\trespBuf.WriteString(\"{\\\"total\\\": \" + strconv.Itoa(len(list)) + \",\")\n\trespBuf.WriteString(\"\\\"remote_identities\\\":[\")\n\tfirst := true\n\tfor _, id := range list {\n\t\tjson_id, err := id.MarshalFrontEnd()\n\t\tif err == nil {\n\t\t\tif first {\n\t\t\t\tfirst = false\n\t\t\t} else {\n\t\t\t\trespBuf.WriteByte(',')\n\t\t\t}\n\t\t\trespBuf.Write(json_id)\n\t\t}\n\t}\n\trespBuf.WriteString(\"]}\")\n\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", respBuf.Bytes())\n\n}\n\n// GetRemoteIdentity handles GET …/identities/remotes/:remote_id\nfunc GetRemoteIdentity(ctx *gin.Context) {\n\tuserID, err := operations.NormalizeUUIDstring(ctx.GetString(\"user_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n\tremote_id := ctx.Param(\"remote_id\")\n\tif remote_id == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"empty remote_id\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\twithCredentials := false // by default do not return Credentials\n\tidentity, e := caliopen.Facilities.RESTfacility.RetrieveUserIdentity(userID, remote_id, withCredentials)\n\tif e != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif e.Code() == NotFoundCaliopenErr {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), e, e.Cause())\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(e, e.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t} else {\n\t\tif identity.Type != RemoteIdentity {\n\t\t\te := swgErr.New(http.StatusNotFound, \"resource not available on this route\")\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t\tid_json, err := identity.MarshalFrontEnd()\n\t\tif err != nil {\n\t\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t} else {\n\t\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", id_json)\n\t\t}\n\t}\n}\n\n// NewRemoteIdentity handles POST …/identities/remotes\nfunc NewRemoteIdentity(ctx *gin.Context) {\n\t// check payload\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\tuserID, err := operations.NormalizeUUIDstring(user_id)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tidentity := new(UserIdentity)\n\tidentity.MarshallNew()\n\terr = ctx.ShouldBindJSON(identity)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tif identity.Protocol == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"mandatory property `protocol` is missing\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tif identity.Identifier == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"mandatory property `identifier` is missing\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tidentity.Identifier = strings.ToLower(identity.Identifier)\n\t// add UserId and type\n\tidentity.UserId.UnmarshalBinary(uuid.FromStringOrNil(userID).Bytes())\n\tidentity.Type = RemoteIdentity\n\n\t// call api\n\tapiErr := caliopen.Facilities.RESTfacility.CreateUserIdentity(identity)\n\tif apiErr != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tswitch apiErr.Code() {\n\t\tcase UnprocessableCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnprocessableEntity, \"api returned unprocessable error\"), apiErr, apiErr.Cause())\n\t\tcase DbCaliopenErr:\n\t\t\tif prevErr, ok := apiErr.Cause().(CaliopenError); ok {\n\t\t\t\tswitch prevErr.Code() {\n\t\t\t\tcase ForbiddenCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusForbidden, \"api returned forbidden error\"), apiErr, apiErr.Cause())\n\t\t\t\tcase NotFoundCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"api failed to retrieve in store\"), apiErr, apiErr.Cause())\n\t\t\t\tdefault:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t}\n\t\tdefault:\n\t\t\treturnedErr = swgErr.CompositeValidationError(apiErr, apiErr.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.JSON(http.StatusOK, struct {\n\t\t\tLocation string `json:\"location\"`\n\t\t\tRemoteId string `json:\"remote_id\"`\n\t\t}{\n\t\t\thttp_middleware.RoutePrefix + http_middleware.IdentitiesRoute + \"/remotes/\" + identity.Id.String(),\n\t\t\tidentity.Id.String(),\n\t\t})\n\t}\n\treturn\n}\n\n// PatchRemoteIdentity handles PATCH …/identities/remotes/:remote_id\nfunc PatchRemoteIdentity(ctx *gin.Context) {\n\tvar err error\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tremoteId := ctx.Param(\"remote_id\")\n\tif remoteId == \"\" {\n\t\te := swgErr.New(http.StatusBadRequest, \"empty remote_id\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tvar patch []byte\n\tpatch, err = ioutil.ReadAll(ctx.Request.Body)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tif !caliopen.Facilities.RESTfacility.IsRemoteIdentity(userId, remoteId) {\n\t\te := swgErr.New(http.StatusNotFound, \"resource not found on this route\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// call REST facility with payload\n\tapiErr := caliopen.Facilities.RESTfacility.PatchUserIdentity(patch, userId, remoteId)\n\tif apiErr != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tswitch apiErr.Code() {\n\t\tcase UnprocessableCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnprocessableEntity, \"api returned unprocessable error\"), apiErr, apiErr.Cause())\n\t\tcase DbCaliopenErr:\n\t\t\tif prevErr, ok := apiErr.Cause().(CaliopenError); ok {\n\t\t\t\tswitch prevErr.Code() {\n\t\t\t\tcase ForbiddenCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusForbidden, \"api returned forbidden error\"), apiErr, apiErr.Cause())\n\t\t\t\tcase NotFoundCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"api failed to retrieve in store\"), apiErr, apiErr.Cause())\n\t\t\t\tdefault:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t}\n\t\tdefault:\n\t\t\treturnedErr = swgErr.CompositeValidationError(apiErr, apiErr.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n}\n\n// DeleteRemoteIdentity handles DELETE …/identities/remotes/:remote_id\nfunc DeleteRemoteIdentity(ctx *gin.Context) {\n\tvar err error\n\t// check request\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tremoteId := ctx.Param(\"remote_id\")\n\tif remoteId == \"\" {\n\t\te := swgErr.New(http.StatusBadRequest, \"empty remote_id\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tif !caliopen.Facilities.RESTfacility.IsRemoteIdentity(userId, remoteId) {\n\t\te := swgErr.New(http.StatusNotFound, \"resource not found on this route\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// call api\n\tapiErr := caliopen.Facilities.RESTfacility.DeleteUserIdentity(userId, remoteId)\n\tif apiErr != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tswitch apiErr.Code() {\n\t\tcase UnprocessableCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnprocessableEntity, \"api returned unprocessable error\"), apiErr, apiErr.Cause())\n\t\tcase DbCaliopenErr:\n\t\t\tif prevErr, ok := apiErr.Cause().(CaliopenError); ok {\n\t\t\t\tswitch prevErr.Code() {\n\t\t\t\tcase ForbiddenCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusForbidden, \"api returned forbidden error\"), apiErr, apiErr.Cause())\n\t\t\t\tcase NotFoundCaliopenErr:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"api failed to retrieve in store\"), apiErr, apiErr.Cause())\n\t\t\t\tdefault:\n\t\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"api failed to call store\"), apiErr, apiErr.Cause())\n\t\t\t}\n\t\tdefault:\n\t\t\treturnedErr = swgErr.CompositeValidationError(apiErr, apiErr.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/imports/import.go",
    "content": "/*\n * // Copyleft (ɔ) 2019 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage imports\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"net/http\"\n)\n\n// ImportFile handles POST /imports and do logic depending on file mime type\nfunc ImportFile(ctx *gin.Context) {\n\tuserId, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser_info := &UserInfo{User_id: userId, Shard_id: shard_id}\n\n\tfile, _, err := ctx.Request.FormFile(\"file\")\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// TODO manage import switch file content-type / name\n\terr = caliopen.Facilities.RESTfacility.ImportVcardFile(user_info, file)\n\tif err != nil {\n\t\tvar e error\n\t\te = swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusOK)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/messages/actions.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage messages\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"net/http\"\n)\n\n// POST …/:message_id/actions\nfunc Actions(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser_info := &UserInfo{User_id: user_id, Shard_id: shard_id}\n\tmsg_id, err := operations.NormalizeUUIDstring(ctx.Param(\"message_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tvar actions ActionsPayload\n\tif err := ctx.BindJSON(&actions); err == nil {\n\t\tswitch actions.Actions[0] {\n\t\tcase \"send\":\n\t\t\tupdated_msg, err := caliopen.Facilities.RESTfacility.SendDraft(user_info, msg_id)\n\t\t\tif err != nil {\n\t\t\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t} else {\n\t\t\t\t// TODO : find the correct body_type to use\n\t\t\t\tmsg_json, err := updated_msg.MarshalFrontEnd(\"plain_text\")\n\t\t\t\tif err != nil {\n\t\t\t\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\t\tctx.Abort()\n\t\t\t\t} else {\n\t\t\t\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", msg_json)\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"set_read\":\n\t\t\terr := caliopen.Facilities.RESTfacility.SetMessageUnread(user_info, msg_id, false)\n\t\t\tif err != nil {\n\t\t\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t} else {\n\t\t\t\tctx.Status(http.StatusNoContent)\n\t\t\t}\n\t\tcase \"set_unread\":\n\t\t\terr := caliopen.Facilities.RESTfacility.SetMessageUnread(user_info, msg_id, true)\n\t\t\tif err != nil {\n\t\t\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t} else {\n\t\t\t\tctx.Status(http.StatusNoContent)\n\t\t\t}\n\t\tdefault:\n\t\t\te := swgErr.New(http.StatusNotImplemented, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t}\n\t} else {\n\t\tlog.WithError(err).Errorf(\"failed to bind json payload to ActionsPayload struct\")\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/messages/attachments.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage messages\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"mime\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// POST …/:message_id/attachments\nfunc UploadAttachment(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser := &UserInfo{User_id: user_id, Shard_id: shard_id}\n\tmsg_id, err := operations.NormalizeUUIDstring(ctx.Param(\"message_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tfile, header, err := ctx.Request.FormFile(\"attachment\")\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tfilename := header.Filename\n\tcontent_type := header.Header[\"Content-Type\"][0]\n\tattchmtUrl, err := caliopen.Facilities.RESTfacility.AddAttachment(user, msg_id, filename, content_type, file)\n\tif err != nil {\n\t\tvar e error\n\t\tif err.Error() == \"not found\" {\n\t\t\te = swgErr.New(http.StatusNotFound, err.Error())\n\t\t} else {\n\t\t\te = swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tresp := struct {\n\t\tTempId string `json:\"temp_id\"`\n\t}{attchmtUrl}\n\tctx.JSON(http.StatusOK, resp)\n}\n\n// DELETE …/:message_id/attachments/:attachment_id\nfunc DeleteAttachment(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser := &UserInfo{User_id: user_id, Shard_id: shard_id}\n\tmsg_id, err := operations.NormalizeUUIDstring(ctx.Param(\"message_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tattch_id, err := operations.NormalizeUUIDstring(ctx.Param(\"attachment_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tcaliopenErr := caliopen.Facilities.RESTfacility.DeleteAttachment(user, msg_id, attch_id)\n\tif caliopenErr != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif caliopenErr.Error() == \"message not found\" ||\n\t\t\tcaliopenErr.Error() == \"attachment not found\" ||\n\t\t\tcaliopenErr.Code() == NotFoundCaliopenErr {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), caliopenErr, caliopenErr.Cause())\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(caliopenErr, caliopenErr.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t}\n\tctx.Status(http.StatusOK)\n}\n\n// GET …/:message_id/attachments/:attachment_id\n// sends attachment as a file to client\nfunc DownloadAttachment(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\tmsg_id, err := operations.NormalizeUUIDstring(ctx.Param(\"message_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tmeta, content, err := caliopen.Facilities.RESTfacility.OpenAttachment(user_id, msg_id, ctx.Param(\"attachment_id\"))\n\tif err != nil {\n\t\tvar e error\n\t\tif err.Error() == \"attachment not found\" {\n\t\t\te = swgErr.New(http.StatusNotFound, err.Error())\n\t\t} else {\n\t\t\te = swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\t// create a ReaderSeeker from the io.Reader returned by OpenAttachment\n\tsize, err := strconv.ParseInt(meta[\"Message-Size\"], 10, 64)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tattch_bytes := make([]byte, size)\n\tcontent.Read(attch_bytes)\n\trs := bytes.NewReader(attch_bytes)\n\n\tctx.Header(\"Content-Type\", meta[\"Content-Type\"])\n\tctx.Header(\"Content-Disposition\", `attachment; filename=\"`+mime.BEncoding.Encode(\"UTF-8\", meta[\"Filename\"])+`\"`)\n\thttp.ServeContent(ctx.Writer, ctx.Request, \"\", time.Time{}, rs)\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/messages/messages.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage messages\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/pi\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\n// GET …/messages\nfunc GetMessagesList(ctx *gin.Context) {\n\t// temporary hack to check if X-Caliopen-IL header is in request, because go-openapi pkg fails to do it.\n\t// (NB : CanonicalHeaderKey func normalize http headers with uppercase at beginning of words)\n\tif _, ok := ctx.Request.Header[\"X-Caliopen-Il\"]; !ok {\n\t\te := swgErr.New(http.StatusFailedDependency, \"Missing mandatory header 'X-Caliopen-Il'.\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tvar limit, offset int\n\tvar user_UUID UUID\n\n\tuser_uuid_str := ctx.MustGet(\"user_id\").(string)\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser_uuid, _ := uuid.FromString(user_uuid_str)\n\tuser_UUID.UnmarshalBinary(user_uuid.Bytes())\n\n\tquery_values := ctx.Request.URL.Query()\n\tif l, ok := query_values[\"limit\"]; ok {\n\t\tlimit, _ = strconv.Atoi(l[0])\n\t\tquery_values.Del(\"limit\")\n\t}\n\tif o, ok := query_values[\"offset\"]; ok {\n\t\toffset, _ = strconv.Atoi(o[0])\n\t\tquery_values.Del(\"offset\")\n\t}\n\n\t// check for params to retrieve a range of messages 'around' a specific one\n\t// params `msg_id` and `range[]` must be both present\n\tvar msgID string\n\tvar msgRange []string\n\tif id, ok := query_values[\"msg_id\"]; ok {\n\t\tmsgID = id[0]\n\t}\n\tmsgRange = query_values[\"range[]\"]\n\tif msgID != \"\" && len(msgRange) == 0 {\n\t\te := swgErr.New(http.StatusBadRequest, \"range[] param must be provided with msg_id param\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tif msgID == \"\" && len(msgRange) != 0 {\n\t\te := swgErr.New(http.StatusBadRequest, \"msg_id param must be provided with range[] param\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tfilter := IndexSearch{\n\t\tShard_id: shard_id,\n\t\tUser_id:  user_UUID,\n\t\tTerms:    map[string][]string(query_values),\n\t\tLimit:    limit,\n\t\tOffset:   offset,\n\t\tILrange:  operations.GetImportanceLevel(ctx),\n\t}\n\n\tvar list []*Message\n\tvar totalFound int64\n\tvar err error\n\tif msgID != \"\" && len(msgRange) != 0 {\n\t\tlist, totalFound, err = caliopen.Facilities.RESTfacility.GetMessagesRange(filter)\n\t} else {\n\t\tlist, totalFound, err = caliopen.Facilities.RESTfacility.GetMessagesList(filter)\n\t}\n\tif err != nil && err.Error() != \"not found\" {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tsettings, err := caliopen.Facilities.RESTfacility.GetSettings(user_uuid_str)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tvar respBuf bytes.Buffer\n\trespBuf.WriteString(\"{\\\"total\\\": \" + strconv.FormatInt(totalFound, 10) + \",\")\n\trespBuf.WriteString(\"\\\"messages\\\":[\")\n\tfirst := true\n\tfor _, msg := range list {\n\t\tmsg.PI = pi.ComputePIMessage(msg)\n\t\tjson_msg, err := msg.MarshalFrontEnd(settings.MessageDisplayFormat)\n\t\tif err == nil {\n\t\t\tif first {\n\t\t\t\tfirst = false\n\t\t\t} else {\n\t\t\t\trespBuf.WriteByte(',')\n\t\t\t}\n\t\t\trespBuf.Write(json_msg)\n\t\t}\n\t}\n\trespBuf.WriteString(\"]}\")\n\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", respBuf.Bytes())\n}\n\n// GET …/messages/:message_id\nfunc GetMessage(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser_info := &UserInfo{User_id: user_id, Shard_id: shard_id}\n\tmsg_id, err := operations.NormalizeUUIDstring(ctx.Param(\"message_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tmsg, err := caliopen.Facilities.RESTfacility.GetMessage(user_info, msg_id)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tsettings, err := caliopen.Facilities.RESTfacility.GetSettings(user_id)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tmsg.PI = pi.ComputePIMessage(msg)\n\tmsg_json, err := msg.MarshalFrontEnd(settings.MessageDisplayFormat)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", msg_json)\n\t}\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/notifications/notifications.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage notifications\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// GetPendingNotif handles GET /notifications\n// two optional params may be in query : `to` and `from` to narrow the notifications list to either :\n// - a time range\n// - a uuid range\nfunc GetPendingNotif(ctx *gin.Context) {\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\tto_param := ctx.Query(\"to\")\n\tfrom_param := ctx.Query(\"from\")\n\n\tvar to_kind, from_kind string\n\tuuidv1Regex := regexp.MustCompile(`[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-1[0-9a-fA-F]{3}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}`)\n\trfc3339Regex := regexp.MustCompile(`(?i)^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))$`)\n\n\t// validate from param\n\tif from_param != \"\" {\n\t\tif uuidv1Regex.MatchString(from_param) {\n\t\t\tfrom_kind = \"id\"\n\t\t} else if rfc3339Regex.MatchString(from_param) {\n\t\t\tfrom_kind = \"time\"\n\t\t}\n\t\tif from_kind == \"\" {\n\t\t\te := swgErr.New(http.StatusUnprocessableEntity, \"invalid from param\")\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t}\n\n\t// validate to param\n\tif to_param != \"\" {\n\t\tif uuidv1Regex.MatchString(to_param) {\n\t\t\tto_kind = \"id\"\n\t\t} else if rfc3339Regex.MatchString(to_param) {\n\t\t\tto_kind = \"time\"\n\t\t}\n\t\tif to_kind == \"\" {\n\t\t\te := swgErr.New(http.StatusUnprocessableEntity, \"invalid to param\")\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t}\n\n\t// validate params consistency\n\tif (from_param != \"\" && to_param != \"\") && (from_kind != to_kind) {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"params are mix of time and uuid\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tvar notifs []Notification\n\tvar err CaliopenError\n\t// call relevant API\n\tif from_kind == \"time\" || to_kind == \"time\" || (from_param == \"\" && to_param == \"\") {\n\t\tfrom, to := time.Time{}, time.Time{}\n\t\tif to_param != \"\" {\n\t\t\tt, err := time.Parse(time.RFC3339, to_param)\n\t\t\tif err != nil {\n\t\t\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tto = t\n\t\t}\n\t\tif from_param != \"\" {\n\t\t\tf, err := time.Parse(time.RFC3339, from_param)\n\t\t\tif err != nil {\n\t\t\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfrom = f\n\t\t}\n\n\t\tnotifs, err = caliopen.Facilities.Notifiers.NotificationsByTime(userId, from, to)\n\t\tif err != nil && err.Cause().Error() != \"notifications not found\" {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t}\n\t}\n\tif from_kind == \"id\" || to_kind == \"id\" {\n\t\tvar from, to string\n\t\tif to_param != \"\" {\n\t\t\tt, err := uuid.FromString(to_param)\n\t\t\tif err != nil {\n\t\t\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tto = t.String()\n\t\t}\n\t\tif from_param != \"\" {\n\t\t\tf, err := uuid.FromString(from_param)\n\t\t\tif err != nil {\n\t\t\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfrom = f.String()\n\t\t}\n\n\t\tnotifs, err = caliopen.Facilities.Notifiers.NotificationsByID(userId, from, to)\n\t\tif err != nil && err.Cause().Error() != \"notifications not found\" {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t}\n\t}\n\n\tvar respBuf bytes.Buffer\n\trespBuf.WriteString(\"{\\\"total\\\": \" + strconv.Itoa(len(notifs)) + \",\")\n\trespBuf.WriteString((\"\\\"notifications\\\":[\"))\n\tfirst := true\n\tfor _, notif := range notifs {\n\t\tjson_notif, err := notif.MarshalFrontEnd()\n\t\tif err == nil {\n\t\t\tif first {\n\t\t\t\tfirst = false\n\t\t\t} else {\n\t\t\t\trespBuf.WriteByte(',')\n\t\t\t}\n\t\t\trespBuf.Write(json_notif)\n\t\t}\n\t}\n\trespBuf.WriteString(\"]}\")\n\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", respBuf.Bytes())\n}\n\n// DeleteNotifications handles DELETE /notifications\nfunc DeleteNotifications(ctx *gin.Context) {\n\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\n\tuntil := time.Time{}\n\n\tuntil_param := ctx.Query(\"until\")\n\tif until_param != \"\" {\n\t\tu, err := time.Parse(time.RFC3339, until_param)\n\t\tif err != nil {\n\t\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t\tuntil = u\n\t}\n\n\terr := caliopen.Facilities.Notifiers.DeleteNotifications(userId, until)\n\tif err != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif err.Code() == DbCaliopenErr && err.Cause().Error() == \"not found\" {\n\t\t\tctx.Status(http.StatusNoContent)\n\t\t\treturn\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tctx.Status(http.StatusNoContent)\n}\n\n// GetNotification handles GET /notifications/:notification_id\nfunc GetNotification(ctx *gin.Context) {\n\tuserID, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tnotificationID, err := operations.NormalizeUUIDstring(ctx.Param(\"notification_id\"))\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tnotif, e := caliopen.Facilities.Notifiers.RetrieveNotification(userID, notificationID)\n\tif e != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif e.Code() == DbCaliopenErr && e.Cause().Error() == \"not found\" {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), e, e.Cause())\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(e, e.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t} else {\n\t\tnotif_json, err := notif.MarshalFrontEnd()\n\t\tif err != nil {\n\t\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t} else {\n\t\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", notif_json)\n\t\t}\n\t}\n}\n\n// DeleteNotification handles DELETE /notifications/:notification_id\nfunc DeleteNotification(ctx *gin.Context) {\n\tuserID, err := operations.NormalizeUUIDstring(ctx.MustGet(\"user_id\").(string))\n\tnotificationID, err := operations.NormalizeUUIDstring(ctx.Param(\"notification_id\"))\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\te := caliopen.Facilities.Notifiers.DeleteNotification(userID, notificationID)\n\tif err != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif e.Code() == DbCaliopenErr && e.Cause().Error() == \"not found\" {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), e, e.Cause())\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(e, e.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tctx.Status(http.StatusNoContent)\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/participants/discussion.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage participants\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"net/http\"\n)\n\n// POST …/participants/discussion\n// returns canonical hash of participant_uris and if the corresponding discussion_id exists\nfunc HashUris(ctx *gin.Context) {\n\tuserId := ctx.MustGet(\"user_id\").(string)\n\tshardId := ctx.MustGet(\"shard_id\").(string)\n\tuserInfo := &UserInfo{User_id: userId, Shard_id: shardId}\n\tvar err error\n\tvar participants []Participant\n\tctx.ShouldBindJSON(&participants)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\thash, _, err := HashFromParticipantsUris(participants)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tdiscussion, err := caliopen.Facilities.RESTfacility.DiscussionMetadata(userInfo, hash)\n\tif err != nil && err.Error() != \"not found\" {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tvar respBuf bytes.Buffer\n\trespBuf.WriteString(\"{\\\"discussion_id\\\":\\\"\" + discussion.DiscussionId + \"\\\",\\\"hash\\\":\\\"\" + hash + \"\\\"}\")\n\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", respBuf.Bytes())\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/participants/suggest.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage participants\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// GET …/participants/suggest?context=xxxx&q=xxx\nfunc Suggest(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tuser_info := &UserInfo{User_id: user_id, Shard_id: shard_id}\n\tquery_context := ctx.Request.URL.Query().Get(\"context\")\n\tif query_context == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"Missing 'context' param in query\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tquery_string := ctx.Request.URL.Query().Get(\"q\")\n\tif query_string == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"Missing 'q' param in query\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tif len(query_string) < 3 {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"Query string must be at least 3 chars long.\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tswitch query_context {\n\tcase \"msg_compose\":\n\t\t// convert string query to lower to benefit the case insensitive search from ES\n\t\t// as suggest is in the context of a msg_compose, ie : we are looking for an address\n\t\tquery_string = strings.ToLower(query_string)\n\t\tsuggests, err := caliopen.Facilities.RESTfacility.SuggestRecipients(user_info, query_string)\n\n\t\tif err != nil {\n\t\t\te := swgErr.New(http.StatusInternalServerError, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tctx.JSON(http.StatusOK, suggests)\n\tdefault:\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"Unknown \")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/providers/oauth-test.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Oauth providers test</title>\n    <style>\n        a.button {\n            text-decoration: none;\n        }\n    </style>\n    <script type=\"text/javascript\">\n        let pop;\n        let intervalId;\n        let headers = {\n            \"x-caliopen-device-id\": \"6894dd9f-e40b-42f3-affd-134a2e86d6b1\",\n            \"x-caliopen-device-signatue\": \"xxxxxx\",\n            \"authorization\": \"Basic ODExZTllYTMtMmJjNy00MjFkLWE2ZTgtODlkM2EyZGM5Y2Y3OjQxZTU4ZjNlYzczMDMwNjZmYmJkNzE4YWY4YTI3YTRhZDU5MGQwNWI=\"\n        }\n\n        function handleClickTwitter() {\n            console.log('start');\n            const callbackUrl = 'http://localhost:6544/api/v2/providers/twitter/callback';\n            let apiTwitterUrl;\n            // Il est NÉCESSAIRE d'ouvrir la popup dans un context synchrone sinon elle sera bloquée par le navigateur\n            // on ne peut pas non plus mettre de data:,whatever (genre <h1>Loading..</h1> ça aurait été sympa)\n            if (!pop || pop.closed) {\n                pop = window.open('', 'foo', 'resizable,scrollbars,status');\n            }\n            fetch('http://localhost:6544/api/v2/providers/twitter', {headers}).then((response) => {\n                // mock response:\n                // response.json = () => Promise.resolve({ twitter_url: 'http://twitter.com' });\n\n                return response.json().then((body) => {\n                    apiTwitterUrl = body.oauth_request_url;\n                    // on redirige la popup vers la bonne url maintenant qu'on a tout ce qu'il faut\n                    pop.location.href = apiTwitterUrl;\n\n                    if (intervalId) {\n                        clearInterval(intervalId);\n                    }\n\n                    let inc = 0;\n                    intervalId = setInterval(() => {\n                        inc++;\n\n                        if (pop.closed) {\n                            clearInterval(intervalId);\n                            console.log('popup has been closed');\n                            // refresh remote identity list & clear form & **eventually** notify the user notify Caliopen has not been authorized to fetch the remote account\n                            return;\n                        }\n\n                        try {\n                            if (pop.location.href.startsWith(callbackUrl)) {\n                                clearInterval(intervalId);\n                                pop.close();\n                                console.log('authorization process ended');\n                                return;\n                            }\n                        } catch (err) {\n                            console.log('popup still on the remote');\n                        }\n\n\n                        if (inc > 3600) {\n                            clearInterval(intervalId);\n                            console.log('nothing happend since 1H');\n                            // refresh remote identity list & clear form & notify the user Caliopen has not been authorized to fetch the remote account\n                        }\n                    }, 500);\n                });\n            }).catch((err) => {\n                console.error({err});\n            });\n        }\n\n        function handleClickGmail() {\n            console.log('start');\n            const callbackUrl = 'http://localhost:6544/api/v2/providers/gmail/callback';\n            let apiGmailUrl;\n            // Il est NÉCESSAIRE d'ouvrir la popup dans un context synchrone sinon elle sera bloquée par le navigateur\n            // cf. (je retrouve plus le lien)\n            // on ne peut pas non plus mettre de data:,whatever (genre <h1>Loading..</h1> ça aurait été sympa)\n            if (!pop || pop.closed) {\n                pop = window.open('', 'foo', 'resizable,scrollbars,status');\n            }\n            fetch('http://localhost:6544/api/v2/providers/gmail', {headers}).then((response) => {\n                // mock response:\n                // response.json = () => Promise.resolve({ twitter_url: 'http://twitter.com' });\n\n                return response.json().then((body) => {\n                    console.log(body)\n                    apiGmailUrl = body.oauth_request_url;\n                    // on redirige la popup vers la bonne url maintenant qu'on a tout ce qu'il faut\n                    pop.location.href = apiGmailUrl;\n\n                    if (intervalId) {\n                        clearInterval(intervalId);\n                    }\n\n                    let inc = 0;\n                    intervalId = setInterval(() => {\n                        inc++;\n\n                        if (pop.closed) {\n                            clearInterval(intervalId);\n                            console.log('popup has been closed');\n                            // refresh remote identity list & clear form & **eventually** notify the user notify Caliopen has not been authorized to fetch the remote account\n                            return;\n                        }\n\n                        try {\n                            if (pop.location.href.startsWith(callbackUrl)) {\n                                clearInterval(intervalId);\n                                pop.close();\n                                console.log('authorization process ended');\n                                return;\n                            }\n                        } catch (err) {\n                            console.log('popup still on the remote');\n                        }\n\n\n                        if (inc > 3600) {\n                            clearInterval(intervalId);\n                            console.log('nothing happend since 1H');\n                            // refresh remote identity list & clear form & notify the user Caliopen has not been authorized to fetch the remote account\n                        }\n                    }, 500);\n                });\n            }).catch((err) => {\n                console.error({err});\n            });\n        }\n\n        function handleClickMastodon() {\n            console.log('start');\n            const identifier = encodeURI(document.getElementById(\"inMasto\").value);\n            const callbackUrl = 'http://localhost:6544/api/v2/providers/mastodon/callback?identifier=' + identifier;\n\n            let apiMastodonUrl;\n            // Il est NÉCESSAIRE d'ouvrir la popup dans un context synchrone sinon elle sera bloquée par le navigateur\n            // cf. (je retrouve plus le lien)\n            // on ne peut pas non plus mettre de data:,whatever (genre <h1>Loading..</h1> ça aurait été sympa)\n            if (!pop || pop.closed) {\n                pop = window.open('', 'foo', 'resizable,scrollbars,status');\n            }\n            fetch('http://localhost:6544/api/v2/providers/mastodon?identifier=' + identifier, {headers}).then((response) => {\n                // mock response:\n                // response.json = () => Promise.resolve({ twitter_url: 'http://twitter.com' });\n\n                return response.json().then((body) => {\n                    apiMastodonUrl = body.oauth_request_url;\n                    console.log(body)\n                    // on redirige la popup vers la bonne url maintenant qu'on a tout ce qu'il faut\n                    pop.location.href = apiMastodonUrl;\n\n                    if (intervalId) {\n                        clearInterval(intervalId);\n                    }\n\n                    let inc = 0;\n                    intervalId = setInterval(() => {\n                        inc++;\n\n                        if (pop.closed) {\n                            clearInterval(intervalId);\n                            console.log('popup has been closed');\n                            // refresh remote identity list & clear form & **eventually** notify the user notify Caliopen has not been authorized to fetch the remote account\n                            return;\n                        }\n\n                        try {\n                            if (pop.location.href.startsWith(callbackUrl)) {\n                                clearInterval(intervalId);\n                                pop.close();\n                                console.log('authorization process ended');\n                                return;\n                            }\n                        } catch (err) {\n                            console.log('popup still on the remote');\n                        }\n\n\n                        if (inc > 3600) {\n                            clearInterval(intervalId);\n                            console.log('nothing happend since 1H');\n                            // refresh remote identity list & clear form & notify the user Caliopen has not been authorized to fetch the remote account\n                        }\n                    }, 500);\n                });\n            }).catch((err) => {\n                console.error({err});\n            });\n        }\n    </script>\n</head>\n​\n<body>\n<button class=\"button\" onclick=\"handleClickTwitter()\">\n    Login with Twitter\n</button>\n<button class=\"button\" onclick=\"handleClickGmail()\">\n    Login with Gmail\n</button>\n<button class=\"button\" onclick=\"handleClickMastodon()\">\n    Login with Mastodon\n</button>\n<input id=\"inMasto\">\n<div class=\"results\"></div>\n</body>\n</html>\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/providers/providers.go",
    "content": "package providers\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"net/http\"\n)\n\n// GetProvidersList handles get …/providers\nfunc GetProvidersList(ctx *gin.Context) {\n\tproviders, err := caliopen.Facilities.RESTfacility.RetrieveProvidersList()\n\tif err != nil && err.Error() != \"not found\" {\n\t\te := swgErr.New(http.StatusInternalServerError, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tret := struct {\n\t\tTotal     int        `json:\"total\"`\n\t\tProviders []Provider `json:\"providers,omitempty\"`\n\t}{len(providers), providers}\n\tctx.JSON(http.StatusOK, ret)\n}\n\n// GetProvider handles get …/providers/:provider_name\nfunc GetProvider(ctx *gin.Context) {\n\tuserID, err := operations.NormalizeUUIDstring(ctx.GetString(\"user_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tproviderName := ctx.Param(\"provider_name\")\n\tif providerName == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"provider name is empty\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tvar identifier string\n\tif providerName == \"mastodon\" {\n\t\tidentifier = ctx.Query(\"identifier\")\n\t\tif identifier == \"\" {\n\t\t\t// TODO : return registered instances\n\t\t\te := swgErr.New(http.StatusUnprocessableEntity, \"missing mastodon identifier\")\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t}\n\tprovider, errC := caliopen.Facilities.RESTfacility.GetProviderOauthFor(userID, providerName, identifier)\n\tif errC != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tswitch errC.Code() {\n\t\tcase NotFoundCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"RESTfacility returned error\"), errC, errC.Cause())\n\t\tcase UnprocessableCaliopenErr:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnprocessableEntity, \"RESTfacility returned error\"), errC, errC.Cause())\n\t\tdefault:\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"RESTfacility returned error\"), errC, errC.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tctx.JSON(http.StatusOK, provider)\n}\n\n// CallbackHandler handles get …/providers/:provider_name/callback\nfunc CallbackHandler(ctx *gin.Context) {\n\tprovider := ctx.Param(\"provider_name\")\n\tswitch provider {\n\tcase \"twitter\":\n\t\ttoken := ctx.Query(\"oauth_token\")\n\t\tverifier := ctx.Query(\"oauth_verifier\")\n\t\t_, errC := caliopen.Facilities.RESTfacility.CreateTwitterIdentity(token, verifier)\n\t\tif errC != nil {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"RESTfacility returned error\"), errC, errC.Cause())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t\tctx.Status(http.StatusNoContent)\n\tcase \"gmail\", \"mastodon\":\n\t\tstate := ctx.Query(\"state\")\n\t\tcode := ctx.Query(\"code\")\n\t\tvar errC CaliopenError\n\t\tif provider == \"gmail\" {\n\t\t\t_, errC = caliopen.Facilities.RESTfacility.CreateGmailIdentity(state, code)\n\t\t} else {\n\t\t\tif state == \"\" {\n\t\t\t\tctx.Status(http.StatusNoContent)\n\t\t\t}\n\t\t\t_, errC = caliopen.Facilities.RESTfacility.CreateMastodonIdentity(state, code)\n\t\t}\n\t\tif errC != nil {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusFailedDependency, \"RESTfacility returned error\"), errC, errC.Cause())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t\tctx.Status(http.StatusNoContent)\n\tdefault:\n\t\te := swgErr.New(http.StatusNotImplemented, \"not implemented\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/search.go",
    "content": "package operations\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc SimpleSearch(ctx *gin.Context) {\n\t// temporary hack to check if X-Caliopen-IL header is in request, because go-openapi pkg fails to do it.\n\t// (NB : CanonicalHeaderKey func normalize http headers with uppercase at beginning of words)\n\tif _, ok := ctx.Request.Header[\"X-Caliopen-Il\"]; !ok {\n\t\te := swgErr.New(http.StatusFailedDependency, \"Missing mandatory header 'X-Caliopen-Il'.\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tuser_uuid, _ := uuid.FromString(ctx.MustGet(\"user_id\").(string))\n\tshard_id := ctx.MustGet(\"shard_id\").(string)\n\tvar user_UUID UUID\n\tvar limit, offset int\n\tuser_UUID.UnmarshalBinary(user_uuid.Bytes())\n\tquery := ctx.Request.URL.Query()\n\n\t// check request consistency. (see search API readme in doc folder)\n\tinvalid := false\n\treasons := []error{}\n\tif len(query) < 1 || len(query) > 4242 { // why 4242 ? why not ?\n\t\tinvalid = true\n\t\treasons = append(reasons, errors.New(\"invalid query length\"))\n\t}\n\tterm, term_ok := query[\"term\"]\n\tif !term_ok {\n\t\tinvalid = true\n\t\treasons = append(reasons, errors.New(\"'term' param is missing\"))\n\t}\n\tfor _, t := range term {\n\t\tif len(t) < 3 {\n\t\t\tinvalid = true\n\t\t\treasons = append(reasons, errors.New(\"'term' param must length 3 chars at least\"))\n\t\t}\n\t}\n\tdoc_type, has_doc_type := query[\"doctype\"]\n\n\tif l, ok := query[\"limit\"]; ok {\n\t\tif !has_doc_type {\n\t\t\tinvalid = true\n\t\t\treasons = append(reasons, errors.New(\"'limit' param only allowed if 'doctype' param also provided\"))\n\t\t} else {\n\t\t\tlimit, _ = strconv.Atoi(l[0])\n\t\t}\n\t}\n\tif o, ok := query[\"offset\"]; ok {\n\t\tif !has_doc_type {\n\t\t\tinvalid = true\n\t\t\treasons = append(reasons, errors.New(\"'offset' param only allowed if 'doctype' param also provided\"))\n\t\t} else {\n\t\t\toffset, _ = strconv.Atoi(o[0])\n\t\t}\n\t}\n\n\t// build the search object\n\tsearch := IndexSearch{\n\t\tUser_id:  user_UUID,\n\t\tShard_id: shard_id,\n\t\tLimit:    limit,\n\t\tOffset:   offset,\n\t\tILrange:  GetImportanceLevel(ctx),\n\t}\n\n\tif field, ok := query[\"field\"]; ok {\n\t\tif len(field) > 1 {\n\t\t\tinvalid = true\n\t\t\treasons = append(reasons, errors.New(\"at most one 'field' param allowed\"))\n\t\t} else {\n\t\t\tsearch.Terms = map[string][]string{field[0]: query[\"term\"]} // take only first field provided for now\n\t\t}\n\t} else {\n\t\tsearch.Terms = map[string][]string{\"_all\": query[\"term\"]}\n\t}\n\n\tif has_doc_type {\n\t\tif len(doc_type) > 1 {\n\t\t\tinvalid = true\n\t\t\treasons = append(reasons, errors.New(\"at most one 'doctype' param allowed\"))\n\t\t} else {\n\t\t\tswitch doc_type[0] { // take only first doctype provided for now\n\t\t\tcase \"message\":\n\t\t\t\tsearch.DocType = MessageIndexType\n\t\t\tcase \"contact\":\n\t\t\t\tsearch.DocType = ContactIndexType\n\t\t\tdefault:\n\t\t\t\tinvalid = true\n\t\t\t\treasons = append(reasons, errors.New(\"'doctype' unknown\"))\n\t\t\t}\n\t\t}\n\t}\n\n\tif invalid {\n\t\te := swgErr.CompositeValidationError(reasons...)\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// trigger the search\n\tresult, err := caliopen.Facilities.RESTfacility.Search(search)\n\n\t// handle response\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tresponse, err := result.MarshalFrontEnd()\n\t\tif err != nil {\n\t\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t}\n\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", response)\n\n\t}\n}\n\nfunc AdvancedSearch(ctx *gin.Context) {\n\tctx.AbortWithStatus(http.StatusNotImplemented)\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/tags/tags.go",
    "content": "package tags\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gin-gonic/gin/binding\"\n\tswgErr \"github.com/go-openapi/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"github.com/tidwall/gjson\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// RetrieveUserTags fetches all tags tied to an user, system tags as well as custom tags.\nfunc RetrieveUserTags(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\ttags, err := caliopen.Facilities.RESTfacility.RetrieveUserTags(user_id)\n\tif err != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif err.Code() == DbCaliopenErr && err.Cause().Error() == \"tags not found\" {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), err, err.Cause())\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t} else {\n\t\tvar respBuf bytes.Buffer\n\t\trespBuf.WriteString(\"{\\\"total\\\": \" + strconv.Itoa(len(tags)) + \",\")\n\t\trespBuf.WriteString((\"\\\"tags\\\":[\"))\n\t\tfirst := true\n\t\tfor _, tag := range tags {\n\t\t\tjson_tag, err := tag.MarshalFrontEnd()\n\t\t\tif err == nil {\n\t\t\t\tif first {\n\t\t\t\t\tfirst = false\n\t\t\t\t} else {\n\t\t\t\t\trespBuf.WriteByte(',')\n\t\t\t\t}\n\t\t\t\trespBuf.Write(json_tag)\n\t\t\t}\n\t\t}\n\t\trespBuf.WriteString(\"]}\")\n\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", respBuf.Bytes())\n\t}\n}\n\nfunc CreateTag(ctx *gin.Context) {\n\tvar tag Tag\n\tb := binding.JSON\n\tif err := b.Bind(ctx.Request, &tag); err == nil {\n\t\tuser_uuid, _ := uuid.FromString(ctx.MustGet(\"user_id\").(string))\n\t\ttag.User_id.UnmarshalBinary(user_uuid.Bytes())\n\t\tif tag.Label == \"\" || strings.Replace(tag.Label, \" \", \"\", -1) == \"\" {\n\t\t\terr := errors.New(\"tag's name is empty\")\n\t\t\te := swgErr.New(http.StatusBadRequest, err.Error())\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t\ttag.Label = strings.TrimSpace(tag.Label)\n\t\terr := caliopen.Facilities.RESTfacility.CreateTag(&tag)\n\t\tif err != nil {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\tif err.Code() == DbCaliopenErr && err.Cause().Error() == \"not found\" {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), err, err.Cause())\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t\t}\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t} else {\n\t\t\tctx.JSON(http.StatusOK, struct{ Location string }{\n\t\t\t\thttp_middleware.RoutePrefix + http_middleware.TagsRoute + \"/\" + tag.Name,\n\t\t\t})\n\t\t}\n\t} else {\n\t\te := swgErr.New(http.StatusBadRequest, fmt.Sprintf(\"Unable to json marshal the provided payload : %s\", err))\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n\n}\n\n// RetrieveTag fetches a tag with tag_name & user_id\nfunc RetrieveTag(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\ttag_name := ctx.Param(\"tag_name\")\n\tif tag_name == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"missing tag name\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tif user_id != \"\" {\n\t\ttag, err := caliopen.Facilities.RESTfacility.RetrieveTag(user_id, tag_name)\n\t\tif err != nil {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\tif err.Code() == DbCaliopenErr && err.Cause().Error() == \"tag not found\" {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), err, err.Cause())\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t\t}\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t} else {\n\t\t\ttag_json, err := tag.MarshalFrontEnd()\n\t\t\tif err != nil {\n\t\t\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\t\tctx.Abort()\n\t\t\t} else {\n\t\t\t\tctx.Data(http.StatusOK, \"application/json; charset=utf-8\", tag_json)\n\t\t\t}\n\t\t}\n\t} else {\n\t\terr := errors.New(\"invalid params\")\n\t\te := swgErr.New(http.StatusBadRequest, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n}\n\nfunc PatchTag(ctx *gin.Context) {\n\tvar err error\n\tvar user_id string\n\tvar tag_name string\n\n\tif id, ok := ctx.Get(\"user_id\"); !ok {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"user_id is missing\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t} else {\n\t\tif user_id, err = operations.NormalizeUUIDstring(id.(string)); err != nil {\n\t\t\te := swgErr.New(http.StatusUnprocessableEntity, \"user_id is invalid\")\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t}\n\n\ttag_name = ctx.Param(\"tag_name\")\n\tif tag_name == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"tag_name is missing\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tvar patch []byte\n\tpatch, err = ioutil.ReadAll(ctx.Request.Body)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\te := caliopen.Facilities.RESTfacility.PatchTag(patch, user_id, tag_name)\n\tif e != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif e.Code() == DbCaliopenErr && e.Cause().Error() == \"tag not found\" {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), e, e.Cause())\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(e, e.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n}\n\nfunc DeleteTag(ctx *gin.Context) {\n\tuser_id := ctx.MustGet(\"user_id\").(string)\n\ttag_name := ctx.Param(\"tag_name\")\n\tif tag_name == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"tag_name is missing\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tif user_id != \"\" {\n\t\terr := caliopen.Facilities.RESTfacility.DeleteTag(user_id, tag_name)\n\t\tif err != nil {\n\t\t\treturnedErr := new(swgErr.CompositeError)\n\t\t\tif err.Code() == DbCaliopenErr && err.Cause().Error() == \"tag not found\" {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), err, err.Cause())\n\t\t\t} else {\n\t\t\t\treturnedErr = swgErr.CompositeValidationError(err, err.Cause())\n\t\t\t}\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\t\tctx.Abort()\n\t\t} else {\n\t\t\tctx.Status(http.StatusNoContent)\n\t\t}\n\t} else {\n\t\terr := errors.New(\"invalid params\")\n\t\te := swgErr.New(http.StatusBadRequest, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t}\n\t// TODO : remove tag refs. nested in resources\n}\n\n// PatchResourceWithTag apply the payload (a PATCH tag json) to a resource to update its tags\nfunc PatchResourceWithTags(ctx *gin.Context) {\n\tvar err error\n\tvar userID string\n\tvar resourceID string\n\tvar patch []byte\n\tvar resourceType string\n\n\tif id, ok := ctx.Get(\"user_id\"); !ok {\n\t\te := swgErr.New(http.StatusBadRequest, \"user_id is missing\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t} else {\n\t\tif userID, err = operations.NormalizeUUIDstring(id.(string)); err != nil {\n\t\t\te := swgErr.New(http.StatusUnprocessableEntity, \"user_id is invalid\")\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t}\n\tshard_id, ok := ctx.Get(\"shard_id\")\n\tif !ok {\n\t\te := swgErr.New(http.StatusBadRequest, \"shard_id is missing in context\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// parse payload and ensure it is a patch for tags property only\n\tpatch, err = ioutil.ReadAll(ctx.Request.Body)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tif !gjson.Valid(string(patch)) {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"invalid json\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tp := gjson.ParseBytes(patch)\n\tp.ForEach(func(key, value gjson.Result) bool {\n\t\tif key.Str != \"tags\" && key.Str != \"current_state\" {\n\t\t\terr = swgErr.New(http.StatusBadRequest, fmt.Sprintf(\"invalid property <%s> within json\", key.Str))\n\t\t\treturn false\n\t\t} else if key.Str == \"current_state\" {\n\t\t\tvalue.ForEach(func(k, v gjson.Result) bool {\n\t\t\t\tif k.Str != \"tags\" {\n\t\t\t\t\terr = swgErr.New(http.StatusBadRequest, fmt.Sprintf(\"invalid property <%s> within json\", k.Str))\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t\treturn true\n\t})\n\tif err != nil {\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, err)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t// call UpdateResourceWithPatch API with correct resourceType depending on provided param\n\tparam := ctx.Params[0]\n\tswitch param.Key {\n\tcase \"contactID\":\n\t\tresourceType = ContactType\n\tcase \"message_id\":\n\t\tresourceType = MessageType\n\tdefault:\n\t\terr = swgErr.New(http.StatusBadRequest, \"missing resource param\")\n\t\tif err != nil {\n\t\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, err)\n\t\t\tctx.Abort()\n\t\t\treturn\n\t\t}\n\t}\n\tif resourceID, err = operations.NormalizeUUIDstring(param.Value); err != nil {\n\t\terr = swgErr.New(http.StatusBadRequest, \"resource_id is invalid\")\n\t}\n\tif err != nil {\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, err)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tuser_info := &UserInfo{User_id: userID, Shard_id: shard_id.(string)}\n\te := caliopen.Facilities.RESTfacility.UpdateResourceTags(user_info, resourceID, resourceType, patch)\n\tif e != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif e.Code() == DbCaliopenErr && e.Cause().Error() == \"not found\" {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), e, e.Cause())\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(e, e.Cause())\n\t\t}\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tctx.Status(http.StatusNoContent)\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/users/user.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage users\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/middlewares\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server/operations\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\t\"github.com/gin-gonic/gin\"\n\tswgErr \"github.com/go-openapi/errors\"\n)\n\n// PATCH …/users/{user_id}\n// as of october 2017, PatchUser is partially implemented :\n// it should be only use to change user's password.\nfunc PatchUser(ctx *gin.Context) {\n\tvar err error\n\tauth_user := ctx.MustGet(\"user_id\").(string)\n\tuser_id, err := operations.NormalizeUUIDstring(ctx.Param(\"user_id\"))\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\t// for now, an user can only modify himself\n\tif auth_user != user_id {\n\t\te := swgErr.New(http.StatusUnauthorized, \"user can only modify himself\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tbody, err := ioutil.ReadAll(ctx.Request.Body)\n\tpatch, err := helpers.ParsePatch(body)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\terr = caliopen.Facilities.RESTfacility.PatchUser(auth_user, patch, caliopen.Facilities.Notifiers)\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n\n}\n\n// RequestPasswordReset handles an anonymous POST request on /passwords/reset/ with json payload\n// it will try to trigger a password reset procedure\nfunc RequestPasswordReset(ctx *gin.Context) {\n\tvar payload PasswordResetRequest\n\terr := ctx.BindJSON(&payload)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tif payload.RecoveryMail == \"\" && payload.Username == \"\" {\n\t\te := swgErr.New(http.StatusBadRequest, \"neither username nor recovery email provided, at least one required\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\terr = caliopen.Facilities.RESTfacility.RequestPasswordReset(payload, caliopen.Facilities.Notifiers)\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n\treturn\n}\n\n// ValidatePassResetToken handles a GET on /passwords/reset/:reset_token\n// this route does nothing more than responding with a 204 if reset_token is still valid\nfunc ValidatePassResetToken(ctx *gin.Context) {\n\n\ttoken := ctx.Param(\"reset_token\")\n\n\tif token == \"\" {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"reset token is empty\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\t_, err := caliopen.Facilities.RESTfacility.ValidatePasswordResetToken(token)\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusNotFound, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n\treturn\n}\n\n// ResetPassword handles POST on /passwords/reset/:reset_token\n// payload should be a json with new password\nfunc ResetPassword(ctx *gin.Context) {\n\n\ttoken := ctx.Param(\"reset_token\")\n\n\tif token == \"\" {\n\t\te := swgErr.New(http.StatusBadRequest, \"reset token is empty\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tvar payload struct {\n\t\tPassword string `json:\"password\"`\n\t}\n\terr := ctx.BindJSON(&payload)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"unable to unmarshal payload : \"+err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\terr = caliopen.Facilities.RESTfacility.ResetUserPassword(token, payload.Password, caliopen.Facilities.Notifiers)\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusFailedDependency, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n}\n\n// POST …/users/\nfunc Create(ctx *gin.Context) {\n\te := swgErr.New(http.StatusNotImplemented, \"not implemented\")\n\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\tctx.Abort()\n\treturn\n}\n\n// POST …/users/{user_id}/actions\nfunc Delete(ctx *gin.Context) {\n\tvar err error\n\tvar caliopenErr CaliopenError\n\tauth_user := ctx.MustGet(\"user_id\").(string)\n\taccess_token := ctx.MustGet(\"access_token\").(string)\n\tuser_id, err := operations.NormalizeUUIDstring(ctx.Param(\"user_id\"))\n\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\t//for now, an user can only modify himself\n\tif auth_user != user_id {\n\t\te := swgErr.New(http.StatusUnauthorized, \"user can only modify himself\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tvar payload ActionsPayload\n\n\terr = ctx.BindJSON(&payload)\n\tif err != nil {\n\t\te := swgErr.New(http.StatusUnprocessableEntity, \"unable to unmarshal payload : \"+err.Error())\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tpassword := payload.Params.(map[string]interface{})[\"password\"].(string)\n\n\tif payload.Params == nil || password == \"\" {\n\t\te := swgErr.New(http.StatusBadRequest, \"Password missing\")\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\t\tctx.Abort()\n\t\treturn\n\t}\n\n\tpayload.UserId = user_id\n\n\tcaliopenErr = caliopen.Facilities.RESTfacility.DeleteUser(ActionsPayload{\n\t\tActions: payload.Actions,\n\t\tParams: DeleteUserParams{\n\t\t\tPassword:    password,\n\t\t\tAccessToken: access_token,\n\t\t},\n\t\tUserId: payload.UserId,\n\t})\n\tif caliopenErr != nil {\n\t\treturnedErr := new(swgErr.CompositeError)\n\t\tif caliopenErr.Code() == DbCaliopenErr && caliopenErr.Cause().Error() == \"[CassandraBackend] user not found\" {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusNotFound, \"db returned not found\"), caliopenErr, caliopenErr.Cause())\n\t\t} else if caliopenErr.Code() == WrongCredentialsErr && caliopenErr.Cause().Error() == \"[RESTfacility] DeleteUser Wrong password\" {\n\t\t\treturnedErr = swgErr.CompositeValidationError(swgErr.New(http.StatusUnauthorized, \"wrong password\"), caliopenErr, caliopenErr.Cause())\n\t\t} else {\n\t\t\treturnedErr = swgErr.CompositeValidationError(caliopenErr, caliopenErr.Cause())\n\t\t}\n\n\t\thttp_middleware.ServeError(ctx.Writer, ctx.Request, returnedErr)\n\t\tctx.Abort()\n\t} else {\n\t\tctx.Status(http.StatusNoContent)\n\t}\n}\n\nfunc Get(ctx *gin.Context) {\n\te := swgErr.New(http.StatusNotImplemented, \"not implemented\")\n\thttp_middleware.ServeError(ctx.Writer, ctx.Request, e)\n\tctx.Abort()\n\treturn\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/operations/users/username.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage users\n\nimport (\n\tobj \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main\"\n\t\"github.com/gin-gonic/gin\"\n\t\"net/http\"\n)\n\n// GET …/users/isAvailable\nfunc IsAvailable(ctx *gin.Context) {\n\tusername := ctx.Query(\"username\")\n\tif username == \"\" {\n\t\tctx.JSON(http.StatusBadRequest, obj.Availability{false, username})\n\t\treturn\n\t}\n\n\tavailable, err := caliopen.Facilities.RESTfacility.UsernameIsAvailable(username)\n\n\tif available && err == nil {\n\t\tctx.JSON(http.StatusOK, obj.Availability{true, username})\n\t\treturn\n\t}\n\tctx.JSON(http.StatusOK, obj.Availability{false, username})\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/go.server/proxy.go",
    "content": "package rest_api\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n)\n\nfunc StartProxy(config ProxyConfig) {\n\n\tmux := http.NewServeMux()\n\n\tfor path, target := range config.Routes {\n\t\tmux.Handle(path, httputil.NewSingleHostReverseProxy(&url.URL{\n\t\t\tScheme: \"http\",\n\t\t\tHost:   target,\n\t\t}))\n\t}\n\n\taddr := config.Interface + \":\" + config.Port\n\tlog.Printf(\"HTTP proxy listening on %s\", addr)\n\tlog.Fatal(http.ListenAndServe(addr, mux))\n\n\treturn\n}\n\ntype ProxyConfig struct {\n\tInterface  string            `mapstructure:\"listen_interface\"`\n\tListenPort string            `mapstructure:\"listen_port\"`\n\tPort       string            `mapstructure:\"port\"`\n\tRoutes     map[string]string `mapstructure:\"routes\"`\n}\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/CHANGES.rst",
    "content": "0.0.1\n-----\n\n-  Initial version\n\n0.0.2\n-----\n- routes « threads » renamed to « discussions »\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/MANIFEST.in",
    "content": "include *.txt *.ini *.cfg *.rst"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/README.rst",
    "content": "Entry point\n===========\n\nThis repository is part of CaliOpen platform. For documentation, installation and\ncontribution instructions, please refer to https://caliopen.github.io\n\ncaliopen.api\n============\n\ncaliopen_api package is a simple Pyramid container to include CaliOpen Rest API services.\n\n\nDocumentation\n-------------\n\nThe complete documentation of the API is available at `/doc/api/swagger.json` (it follows swagger/openAPI specs).\nDevelopers can browse and test the API at `http://localhost:6543/api-ui/`\n\nLocal Installation\n------------------\n\nTo install local dependencies, use `pip <https://pip.pypa.io/en/latest/>`_:\n\n::\n\n    pip install -e \".[dev,test]\"\n\nThen install supported components and include them under caliopen.api.services\nin pyramid configuration file.\n\n::\n\n    caliopen.api.services = caliopen.api.user\n                            caliopen.api.message\n\nYou will need storage services running locally, cassandra, elasticsearch\nand a redis instance.\n\n`caliopen.cli <https://github.com/caliopen/caliopen.cli>`_, should be used\nto create user and load mail data.\n\n\nRunning API\n-----------\n\n::\n\n    cd src/backend/main\n    ./startup\n\n\nComponents\n----------\n\nCurrent components are :\n\n* `caliopen_api.user`\n* `caliopen_api.message`\n* `caliopen_api.base`\n\nTests\n-----\n\nTests are launched using `nose <https://nose.readthedocs.org/en/latest/>`_.\n\n::\n\n    nosetests -sxv caliopen/api/tests/*.py\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n__version__ = '0.23.0'\n\n\nimport logging\nfrom pyramid.config import Configurator\nfrom caliopen_storage.config import Configuration\n\nlog = logging.getLogger(__name__)\n\nlogging.basicConfig(level=logging.DEBUG)\n\n\ndef main(global_config, **settings):\n    \"\"\"Caliopen entry point for WSGI application.\n\n    Load Caliopen configuration and setup a WSGI application\n    with loaded API services.\n    \"\"\"\n    # XXX ugly way to init caliopen configuration before pyramid\n    caliopen_config = settings['caliopen.config'].split(':')[1]\n    Configuration.load(caliopen_config, 'global')\n\n    settings['pyramid_swagger.exclude_paths'] = [r'^/api-ui/?', r'^/doc/api/?', r'^/defs/?']\n    settings['pyramid_swagger.enable_response_validation'] = True\n    config = Configurator(settings=settings)\n    services = config.registry.settings. \\\n        get('caliopen_api.services', []). \\\n        split('\\n')\n    route_prefix = settings.get('caliopen_api.route_prefix')\n    for service in services:\n        log.info('Loading %s service' % service)\n        config.include(service, route_prefix=route_prefix)\n    config.end()\n    return config.make_wsgi_app()\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/base/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .config import includeme\n\nDEFAULT_LIMIT = 20\n\n\nclass Api(object):\n\n    \"\"\"Base class for all Api.\"\"\"\n\n    def __init__(self, context, request):\n        self.request = request\n        self.context = context\n\n    def get_limit(self):\n        \"\"\"Return pagination limit from request else default.\"\"\"\n        return int(self.request.params.get('limit', DEFAULT_LIMIT))\n\n    def get_offset(self):\n        \"\"\"Return pagination offset from request else 0.\"\"\"\n        return int(self.request.params.get('offset', 0))\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/base/config.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom caliopen_storage.helpers.connection import connect_storage\n\nfrom .renderer import TextPlainRenderer, JsonRenderer, PartRenderer\nfrom .deserializer import json_deserializer\nfrom .exception import ValidationError\n\nlog = logging.getLogger(__name__)\n\n\"\"\"\nSolution from :\n\nhttps://github.com/striglia/pyramid_swagger/issues/177#issuecomment-373220674\n\n\"\"\"\n\n\ndef swagger_error_view(exc, request):\n    \"\"\"Format swagger validation error.\"\"\"\n    raise ValidationError(exc.child.message)\n\n\ndef includeme(config):\n    \"\"\"Configure REST API.\"\"\"\n    connect_storage()\n    config.commit()\n\n    # configure renderers\n    config.add_renderer('text_plain', TextPlainRenderer)\n    config.add_renderer('json', JsonRenderer)\n    config.add_renderer('simplejson', JsonRenderer)\n    config.add_renderer('part', PartRenderer)\n\n    # configure specific views for API errors\n    config.scan('caliopen_api.base.errors')\n    config.add_cornice_deserializer('application/json',\n                                    json_deserializer)\n    swagger_context = 'pyramid_swagger.exceptions.RequestValidationError'\n    config.add_exception_view(context=swagger_context,\n                              view=swagger_error_view,\n                              renderer='json')\n    config.commit()\n    log.info('Base API configured')\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/base/context.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nThe Root Context\n\nused when a view did not declare it's own context.\n\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom pyramid.security import (Everyone, Authenticated, Allow,\n                              NO_PERMISSION_REQUIRED, ALL_PERMISSIONS,\n                              )\nfrom pyramid.decorator import reify\n\n\nclass DefaultContext(object):\n\n    \"\"\"A default request context.\"\"\"\n\n    default_acl = [(Allow, Everyone, NO_PERMISSION_REQUIRED),\n                   (Allow, Authenticated, 'authenticated'),\n                   ]\n\n    def __init__(self, request):\n        self.request = request\n        self.return_schema = None\n        self._acl = DefaultContext.default_acl[:]\n\n    @reify\n    def authenticated_user(self):\n        \"\"\"\n        Return the authenticated user.\n\n        :return: authenticated user\n        :rtype: dict\n        \"\"\"\n        user = self.request.authenticated_userid\n        return user\n\n    @reify\n    def __acl__(self):\n        return self._acl\n\n    def append_acl(self, role, rights):\n        self._acl.append((Allow, role, rights))\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/base/deserializer.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen api deserializers.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\n\ndef json_deserializer(request):\n    \"\"\"Manage json content type.\"\"\"\n    if request.json_body:\n        return request.json_body\n    return request.body\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/base/errors.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nCaliopen API error formatting.\n\nSee https://caliopen.github.io/rfc/2016/04/18/errors-schema for reference.\n\nOutput structure for any error from API servier is:\n\n{\n  \"errors\": [\n    {\n      \"description\": \"string\",\n      \"type\": \"string\",\n      \"values\": [\"string\"],\n      \"property\": \"string\",\n      \"component\": \"string\",\n      \"code\": \"string\"\n    }\n  ]\n}\n\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport sys\nimport traceback\nimport json\n\nfrom pyramid.response import Response\nfrom pyramid.view import view_config\nfrom pyramid.exceptions import Forbidden\nfrom pyramid.httpexceptions import (\n    HTTPRequestTimeout,\n    HTTPUnauthorized,\n    HTTPInternalServerError,\n    HTTPNotFound,\n    HTTPConflict,\n    HTTPMethodNotAllowed,\n    HTTPServiceUnavailable,\n    HTTPBadRequest,\n    HTTPClientError,\n    HTTPNotImplemented,\n    HTTPUnprocessableEntity,\n    HTTPExpectationFailed,\n    HTTPException,\n)\nfrom pyramid_swagger.exceptions import RequestValidationError, ResponseValidationError\n\n\nlog = logging.getLogger(__name__)\n\n\ndef format_response_detail(exc, request):\n    \"\"\"Format error details for a server error.\"\"\"\n    route = (request.matched_route.name\n             if request.matched_route else ('Unknown'))\n    return {'component': route,\n            'values': exc.code,\n            'property': exc.__class__.__name__,\n            'message': exc.message}\n\n\ndef format_response(exc, request, details=None):\n    \"\"\"Format response error, details contains application errors.\"\"\"\n    # XXX better design for details as keys are not validated\n    if not details:\n        details = {'values': [exc.code],\n                   'property': None,\n                   'component': 'server'}\n    error = {\"errors\": [{\"description\": exc.explanation,\n                         \"type\": exc.title,\n                         \"values\": details['values'],\n                         \"property\": details['property'],\n                         \"component\": details['component'],\n                         \"message\": \"{}\".format(details['message']),\n                         \"code\": exc.code}]}\n    response = Response(json.dumps(error))\n    response.content_type = str('application/json; charset=UTF-8')\n    response.status_int = exc.code\n    return response\n\n\n@view_config(context=Forbidden)\ndef http_forbidden(exc, request):\n    \"\"\"Raise HTTPUnauthorized exception.\"\"\"\n    if request.authenticated_userid is None:\n        exc = HTTPUnauthorized(explanation=\"Invalid credentials\")\n    return http_exception(exc, request)\n\n\n@view_config(context=HTTPConflict)\n@view_config(context=HTTPRequestTimeout)\n@view_config(context=HTTPInternalServerError)\n@view_config(context=HTTPNotFound)\n@view_config(context=HTTPMethodNotAllowed)\n@view_config(context=HTTPServiceUnavailable)\n@view_config(context=HTTPClientError)\n@view_config(context=HTTPNotImplemented)\n@view_config(context=HTTPExpectationFailed)\ndef http_exception(exc, request):\n    if isinstance(request.exc_info[1], ResponseValidationError):\n        # Formatter swagger validation errors\n        exc = request.exc_info[1]\n    details = format_response_detail(exc, request)\n    return format_response(exc, request, details)\n\n\n@view_config(context=Exception)\ndef internal_server_error(exc, request):\n    if isinstance(exc, HTTPException):\n        if isinstance(exc, Response):\n            return exc\n    else:\n        exc = HTTPInternalServerError()\n\n    details = format_response_detail(exc, request)\n    response = format_response(exc, request, details)\n    exc_type, exc_value, exc_tb = sys.exc_info()\n    formatted_tb = traceback.format_exception(exc_type, exc_value, exc_tb)\n    log.error('Unexpected error ''{}'': {}'.format(' '.join(formatted_tb), response))\n    return response\n\n\n@view_config(context=HTTPUnprocessableEntity)\n@view_config(context=HTTPBadRequest)\ndef http_unprocessable_entity(exc, request):\n    if isinstance(request.exc_info[1], RequestValidationError):\n        # Formatter swagger validation errors\n        exc = request.exc_info[1]\n    details = format_response_detail(exc, request)\n    return format_response(exc, request, details)\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/base/exception.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Specific API exception to be process with Specific view.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nfrom pyramid.httpexceptions import HTTPClientError\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_main.common.errors import (PatchUnprocessable, PatchError,\n                                         PatchConflict)\n\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\nclass ValidationError(HTTPClientError):\n    \"\"\"\n    subclass of :class:`~HTTPClientError`.\n\n    This indicates that the body or headers failed validity checks,\n    preventing the server from being able to continue processing.\n\n    code: 400, title: Bad Request\n    \"\"\"\n\n    code = 400\n    title = 'Bad Request'\n    explanation = ('The server could not comply with the request since '\n                   'it is either malformed or otherwise incorrect.')\n\n\nclass AuthenticationError(HTTPClientError):\n    \"\"\"\n    subclass of :class:`~HTTPClientError`.\n\n    This indicates that the user authentication failed.\n\n    code: 401, title: Unauthorized\n    \"\"\"\n\n    code = 401\n    title = 'Authentication error'\n    explanation = 'Wrong credentials (e.g., bad password or username)'\n\n\nclass NotAcceptable(HTTPClientError):\n    \"\"\"\n    subclass of :class:`~HTTPClientError`.\n\n    This indicates that the body or headers failed validity checks,\n    preventing the server from being able to continue processing.\n\n    code: 406, title: Not acceptable\n    \"\"\"\n\n    code = 406\n    title = 'Not acceptable'\n    explanation = 'Server cannot fulfill the request with given payload'\n\n\nclass ResourceNotFound(HTTPClientError):\n    \"\"\"\n    subclass of :class:`~HTTPClientError`.\n\n    This indicates that the body or headers failed validity checks,\n    preventing the server from being able to continue processing.\n\n    code: 404, title: Not found\n    \"\"\"\n\n    code = 404\n    title = 'Not Found'\n    explanation = 'The resource could not be found.'\n\n\nclass MethodNotAllowed(HTTPClientError):\n    \"\"\"\n    subclass of :class:`~HTTPClientError`.\n\n    This indicates that the body or headers failed validity checks,\n    preventing the server from being able to continue processing.\n\n    code: 405, title: Method not allowed\n    \"\"\"\n\n    code = 405\n    title = 'Method not allowed'\n    explanation = 'The method is not allowed or not yet implemented'\n\n\nclass MergePatchError(HTTPClientError):\n    \"\"\"Merge error during patch method.\"\"\"\n\n    def __init__(self, error=None):\n        self.message = error.message\n        if isinstance(error, NotFound):\n            self.code = 404\n            self.title = \"Not Found\"\n            self.explanation = \"The resource could not be found to apply PATCH\"\n        elif isinstance(error, PatchUnprocessable):\n            self.code = 422\n            self.title = \"Patch Unprocessable\"\n            self.explanation = \"PATCH payload was malformed or unprocessable\"\n        elif isinstance(error, PatchError):\n            self.code = 422\n            self.title = \"Patch Error\"\n            self.explanation = \"Application encountered an error when \" \\\n                               \"applying patch\"\n        elif isinstance(error, PatchConflict):\n            self.code = 409\n            self.title = \"Patch Conflict\"\n            self.explanation = \"The request cannot be applied given \" \\\n                               \"the state of the resource\"\n\n\nclass Unprocessable(HTTPClientError):\n    \"\"\"\n    subclass of :class:`~HTTPClientError`.\n\n    This indicates that the body or headers failed validity checks,\n    preventing the server from being able to continue processing.\n\n    code: 422, title: Bad Request\n    \"\"\"\n\n    code = 422\n    title = 'Unprocessable entity'\n    explanation = 'The method is not allowed or not yet implemented'\n\n\nclass MethodFailure(HTTPClientError):\n    \"\"\"\n    subclass of :class:`~HTTPClientError`.\n\n    This indicates that the body or headers failed validity checks,\n    preventing the server from being able to continue processing.\n\n    code: 424, title: Method failure\n    \"\"\"\n\n    code = 424\n    title = 'Method failure'\n    explanation = 'The method failed for a dependency reason'\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/base/renderer.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen pyramid renderer.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport datetime\nimport pytz\nfrom uuid import UUID\nfrom decimal import Decimal\n\nimport simplejson as json\nfrom zope.interface import implementer\nfrom pyramid.interfaces import ITemplateRenderer\n\n\nlog = logging.getLogger(__name__)\n\n\n@implementer(ITemplateRenderer)\nclass TextPlainRenderer(object):\n\n    def __init__(self, request):\n        self.request = request\n\n    def __call__(self, value, system):\n        request = system['request']\n        request.response.content_type = b'text/plain'\n        return value\n\n\nclass JSONEncoder(json.JSONEncoder):\n\n    def default(self, obj):\n        '''Convert object to JSON encodable type.'''\n        if isinstance(obj, Decimal):\n            return float(obj)\n        if isinstance(obj, datetime.datetime):\n            if obj.tzinfo is None:\n                return str(obj.replace(tzinfo=pytz.utc))\n        if isinstance(obj, datetime.date):\n            return obj.isoformat()\n        if isinstance(obj, UUID):\n            return str(obj)\n        return super(JSONEncoder, self).default(obj)\n\n\n@implementer(ITemplateRenderer)\nclass JsonRenderer(object):\n    \"\"\"\n    Template Factory for render json that accept datetime and decimal.\n    \"\"\"\n\n    def __init__(self, _renderer_helper):\n        pass\n\n    def __call__(self, data, context):\n        acceptable = ('application/json', 'text/json', 'text/plain')\n        response = context['request'].response\n        content_type = (context['request'].accept.best_match(acceptable)\n                        or acceptable[0])\n        response.content_type = str(content_type)\n        return json.dumps(data, cls=JSONEncoder)\n\n\n@implementer(ITemplateRenderer)\nclass PartRenderer(object):\n    \"\"\"\n    Renderer for a message part, content type is defined in the part\n    \"\"\"\n\n    def __init__(self, request):\n        self.request = request\n\n    def __call__(self, part, context):\n        response = context['request'].response\n        response.content_type = part['part'].content_type.encode('utf-8')\n        return part['part'].payload.encode('utf-8')\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/discussion/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .config import includeme\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/discussion/config.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\ndef includeme(config):\n    \"\"\"\n    Serve discussion related REST API.\n    \"\"\"\n\n    config.commit()\n\n    log.debug('Loading participants discussion API')\n    config.scan('caliopen_api.discussion.participants')\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/discussion/participants.py",
    "content": "import logging\n\nfrom cornice.resource import resource, view\n\nfrom ..base import Api\nfrom caliopen_main.participant.core import hash_participants_uri\nfrom caliopen_main.participant.objects import Participant\n\nlog = logging.getLogger(__name__)\n\n\n@resource(collection_path='/participants/discussion',\n          path='/participants/discussion')\nclass ParticipantDiscussion(Api):\n    \"\"\"\n    returns canonical hash of participant_uris\n    which is the corresponding discussion_id\n    \"\"\"\n\n    def __init__(self, request):\n        self.request = request\n        self.user = request.authenticated_userid\n\n    @view(renderer='json', permission='authenticated')\n    def collection_post(self):\n        parts = self.request.swagger_data['participants']\n        participants = []\n        for part in parts:\n            participant = Participant()\n            participant.address = part['address']\n            participant.label = part['label']\n            participant.protocol = part['protocol']\n            participant.contact_id = part.get('contact_ids', [])\n            participants.append(participant)\n        uris = hash_participants_uri(participants)\n        return {'hash': uris['hash'],\n                'discussion_id': uris['hash']}\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/message/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .config import includeme\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/message/config.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\ndef includeme(config):\n    \"\"\"\n    Serve message and discussion REST API.\n    \"\"\"\n\n    config.commit()\n\n    # Activate cornice in any case and scan\n    log.debug('Loading message API')\n    config.scan('caliopen_api.message.message')\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/message/message.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom cornice.resource import resource, view\nfrom pyramid.response import Response\n\nfrom caliopen_main.message.objects.message import Message as ObjectMessage\nfrom caliopen_main.message.core import RawMessage\nfrom caliopen_storage.exception import NotFound\n\nfrom ..base import Api\n\nfrom ..base.exception import (ResourceNotFound,\n                              MergePatchError)\nfrom pyramid.httpexceptions import HTTPServerError, HTTPMovedPermanently\nfrom caliopen_pi.features import marshal_features\n\nlog = logging.getLogger(__name__)\n\n\n@resource(collection_path='/messages',\n          path='/messages/{message_id}')\nclass Message(Api):\n    def __init__(self, request):\n        self.request = request\n        self.user = request.authenticated_userid\n\n    @view(renderer='json', permission='authenticated')\n    def collection_post(self):\n        data = self.request.json\n        if 'privacy_features' in data:\n            features = marshal_features(data['privacy_features'])\n            data['privacy_features'] = features\n        # ^ json payload should have been validated by swagger module\n        try:\n            message = ObjectMessage.create_draft(user=self.user, **data)\n        except Exception as exc:\n            log.exception(exc)\n            raise MergePatchError(error=exc)\n\n        message_url = self.request.route_path('message',\n                                              message_id=str(\n                                                  message.message_id))\n        message_url = message_url.replace(\"/v1/\", \"/v2/\")\n\n        self.request.response.location = message_url.encode('utf-8')\n        return {'location': message_url}\n\n    @view(renderer='json', permission='authenticated')\n    def patch(self):\n        \"\"\"Update a message with payload.\n\n        method follows the rfc5789 PATCH and rfc7396 Merge patch specifications,\n        + 'current_state' caliopen's specs.\n        stored messages are modified according to the fields within the payload,\n        ie payload fields squash existing db fields, no other modification done.\n        If message doesn't existing, response is 404.\n        If payload fields are not conform to the message db schema, response is\n        422 (Unprocessable Entity).\n        Successful response is 204, without a body.\n        \"\"\"\n        message_id = self.request.swagger_data[\"message_id\"]\n        patch = self.request.json\n        if 'privacy_features' in patch:\n            features = marshal_features(patch['privacy_features'])\n            patch['privacy_features'] = features\n        if 'privacy_features' in patch.get('current_state', {}):\n            current = patch['current_state']['privacy_features']\n            features = marshal_features(current)\n            patch['current_state']['privacy_features'] = features\n\n        message = ObjectMessage(user=self.user, message_id=message_id)\n        try:\n            message.patch_draft(self.user, patch, db=True, index=True,\n                                with_validation=True)\n        except Exception as exc:\n            raise MergePatchError(exc)\n\n        return Response(None, 204)\n\n    @view(renderer='json', permission='authenticated')\n    def delete(self):\n        message_id = self.request.swagger_data[\"message_id\"]\n        message = ObjectMessage(user=self.user, message_id=message_id)\n\n        try:\n            message.get_db()\n            message.get_index()\n        except NotFound:\n            raise ResourceNotFound\n\n        try:\n            message.delete_db()\n            message.delete_index()\n        except Exception as exc:\n            raise HTTPServerError(exc)\n\n        return Response(None, 204)\n\n\n@resource(path='/raws/{raw_msg_id}')\nclass Raw(Api):\n    \"\"\"returns a raw message\"\"\"\n\n    def __init__(self, request):\n        self.request = request\n        self.user = request.authenticated_userid\n\n    @view(renderer='text_plain', permission='authenticated')\n    def get(self):\n        # XXX how to check privacy_index ?\n        raw_msg_id = self.request.matchdict.get('raw_msg_id')\n        raw = RawMessage.get_for_user(self.user.user_id, raw_msg_id)\n        if raw:\n            return raw.raw_data\n        raise ResourceNotFound('No such message')\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/user/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .config import includeme\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/user/authentication.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport base64\nimport ecdsa\nimport hashlib\n\nfrom asn1crypto.core import Sequence, Integer\n\nfrom zope.interface import implements, implementer\nfrom pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy\nfrom pyramid.security import Everyone, NO_PERMISSION_REQUIRED\n\nfrom caliopen_main.user.core import User\nfrom ..base.exception import AuthenticationError\n\nlog = logging.getLogger(__name__)\n\n\nclass EcdsaSignature(Sequence):\n    \"\"\"Asn.1 structure for an ECDSA signature.\"\"\"\n    _fields = [\n        ('r', Integer),\n        ('s', Integer)]\n\n\nclass AuthenticatedUser(object):\n    \"\"\"Represent an authenticated user.\"\"\"\n\n    def __init__(self, request):\n        self.request = request\n        self.user_id = None\n        self.device_id = None\n        self.shard_id = None\n        self._check_user()\n        # self._load_user()\n\n    def _check_user(self):\n        if 'Authorization' not in self.request.headers:\n            raise AuthenticationError\n\n        authorization = self.request.headers['Authorization'].split()\n        if authorization[0] != 'Bearer' and len(authorization) != 2:\n            raise AuthenticationError\n\n        log.debug('Authentication via Access Token')\n        auth = base64.decodestring(authorization[1])\n        # authentication values is user_id:token\n        if ':' not in auth:\n            raise AuthenticationError\n\n        user_id, token = auth.split(':')\n\n        device_id = None\n        device_header = self.request.headers.get('X-Caliopen-Device-ID', None)\n        if device_header:\n            device_id = device_header\n            cache_key = '{}-{}'.format(user_id, device_id)\n        else:\n            raise AuthenticationError\n\n        infos = self.request.cache.get(cache_key)\n\n        if not infos:\n            raise AuthenticationError\n        if infos.get('access_token') != token:\n            raise AuthenticationError\n        if infos.get('user_status', 'unknown') in ['locked', 'maintenance']:\n            raise AuthenticationError('Status {} does not permit operations'.\n                                      format(infos.get('user_status')))\n\n        if self.request.headers.get('X-Caliopen-Device-Signature', None):\n            valid = self._validate_signature(self.request, device_id, infos)\n            log.info('Signature verification for device %s: %r' %\n                     (device_id, valid))\n\n        self.user_id = user_id\n        self.device_id = device_id\n        self.shard_id = infos['shard_id']\n        self.access_token = token\n        self._user = None\n\n    def _get_il_range(self):\n        il_range = self.request.headers.get('X-Caliopen-IL', None)\n        if not il_range:\n            log.warn('No X-Caliopen-IL header')\n            raise ValueError\n        min_il, max_il = il_range.split(';', 1)\n        try:\n            return (int(min_il), int(max_il))\n        except ValueError:\n            log.error('Invalid value for IL {}'.format(il_range))\n            raise ValueError\n        except Exception as exc:\n            log.error('Invalid range for IL {}: {}'.format(il_range, exc))\n            raise exc\n\n    def _validate_signature(self, request, device_id, infos):\n        \"\"\"Validate device signature.\"\"\"\n        if infos['curve'] == 'P-256':\n            curve = ecdsa.ecdsa.curve_256\n            crv = ecdsa.curves.NIST256p\n            hashfunc = hashlib.sha256\n        else:\n            log.warn('Unsupported curve %r' % infos['curve'])\n            return False\n        try:\n            point = ecdsa.ellipticcurve.Point(curve, infos['x'], infos['y'])\n        except AssertionError:\n            log.warn('Invalid curve points')\n            return False\n\n        sign_header = request.headers.get('X-Caliopen-Device-Signature', None)\n        if sign_header:\n            data = '{}{}{}'.format(request.method, request.path_qs, '')\n            try:\n                b64_header = base64.decodestring(sign_header)\n                ecdsasign = EcdsaSignature.load(b64_header)\n                signature = ecdsasign['r'].contents + ecdsasign['s'].contents\n                vk = ecdsa.VerifyingKey.from_public_point(point, crv,\n                                                          hashfunc=hashfunc)\n                return vk.verify(signature, data)\n            except ecdsa.BadSignatureError:\n                pass\n            except Exception as exc:\n                log.error('Exception during signature verification %r' % exc)\n            return False\n        else:\n            log.warn('no device signature')\n            return False\n\n    def _load_user(self):\n        if self._user:\n            return\n        self._user = User.get(self.user_id)\n\n    @property\n    def id(self):\n        self._load_user()\n        return self._user.user_id\n\n    @property\n    def username(self):\n        self._load_user()\n        return self._user.name\n\n    @property\n    def contact(self):\n        self._load_user()\n        return self._user.contact\n\n\nclass AuthenticationPolicy(object):\n    \"\"\"Global authentication policy.\"\"\"\n\n    implements(IAuthenticationPolicy)\n\n    def authenticated_userid(self, request):\n        if hasattr(request, '_CaliopenUser'):\n            return request._CaliopenUser\n\n        try:\n            request._CaliopenUser = AuthenticatedUser(request)\n        except AuthenticationError:\n            return None\n\n        return request._CaliopenUser\n\n    def effective_principals(self, request):\n        account = self.authenticated_userid(request)\n        if not account:\n            return [Everyone]\n        return [\"%s:%s\" % (account.user_id, account.access_token)]\n\n    def unauthenticated_userid(self, request):\n        try:\n            return AuthenticatedUser(request)\n        except AuthenticationError:\n            return None\n\n    def remember(self, request, principal, **kw):\n        \"\"\"Token Key mechanism can't remember anyone.\"\"\"\n        return []\n\n    def forget(self, request):\n        return [('WWW-Authenticate', 'Bearer realm=\"Caliopen\"')]\n\n\n@implementer(IAuthorizationPolicy)\nclass AuthorizationPolicy(object):\n    \"\"\"Basic authorization policy.\"\"\"\n\n    def permits(self, context, principals, permission):\n        \"\"\" Return an instance of\n        :class:`pyramid.security.ACLAllowed` instance if the policy\n        permits access, return an instance of\n        :class:`pyramid.security.ACLDenied` if not.\"\"\"\n        if permission == NO_PERMISSION_REQUIRED:\n            return True\n\n        if not principals:\n            False\n\n        token = principals[0]\n        if ':' in token and permission == 'authenticated':\n            # All managed objects belong to authenticated user\n            # no other policy to apply\n            return True\n        return False\n\n    def principals_allowed_by_permission(self, context, permission):\n        raise NotImplementedError\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/user/config.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom .authentication import AuthenticationPolicy, AuthorizationPolicy\n\nlog = logging.getLogger(__name__)\n\n\ndef includeme(config):\n    \"\"\"Configure REST API for user and contact.\"\"\"\n    config.set_authentication_policy(AuthenticationPolicy())\n    config.set_authorization_policy(AuthorizationPolicy())\n    log.debug('Loading user API')\n    config.scan('caliopen_api.user.user')\n\n    log.debug('Loading contact API')\n    config.scan('caliopen_api.user.contact')\n\n    log.debug('Loading imports API')\n    config.scan('caliopen_api.user.imports')\n\n    log.debug('Loading settings API')\n    config.scan('caliopen_api.user.settings')\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/user/contact.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen Contact REST API.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport uuid\nfrom cornice.resource import resource, view\nfrom pyramid.response import Response\nfrom pyramid.httpexceptions import HTTPServerError, HTTPForbidden\nfrom caliopen_main.common.errors import ForbiddenAction\n\nfrom caliopen_main.contact.core import Contact as CoreContact\n\nfrom caliopen_main.contact.objects.contact import Contact as ContactObject\n\nfrom caliopen_main.contact.returns import ReturnContact\n\nfrom caliopen_main.contact.parameters import NewContact as NewContactParam\n\nfrom ..base import Api\nfrom ..base.exception import (ResourceNotFound,\n                              ValidationError,\n                              MergePatchError)\n\nlog = logging.getLogger(__name__)\n\n\n@resource(collection_path='/contacts',\n          path='/contacts/{contact_id}')\nclass Contact(Api):\n    \"\"\"Contact API.\"\"\"\n\n    def __init__(self, request):\n        self.request = request\n        self.user = request.authenticated_userid\n\n    @view(renderer='json', permission='authenticated')\n    def collection_get(self):\n        filter_params = {'limit': self.get_limit(),\n                         'offset': self.get_offset()}\n        log.debug('Filter parameters {}'.format(filter_params))\n        results = CoreContact._model_class.search(self.user, **filter_params)\n        data = []\n        for item in results:\n            try:\n                c = ReturnContact.build(\n                    CoreContact.get(self.user, item.contact_id)). \\\n                    serialize()\n                data.append(c)\n            except Exception as exc:\n                log.error(\"unable to serialize contact : {}\".format(exc))\n\n        return {'contacts': data, 'total': results.hits.total}\n\n    @view(renderer='json', permission='authenticated')\n    def get(self):\n        contact_id = self.request.swagger_data[\"contact_id\"]\n        try:\n            uuid.UUID(contact_id)\n        except Exception as exc:\n            log.error(\"unable to extract contact_id: {}\".format(exc))\n            raise ValidationError(exc)\n\n        contact = ContactObject(user=self.user, contact_id=contact_id)\n        try:\n            contact.get_db()\n            contact.unmarshall_db()\n        except Exception as exc:\n            log.warn(exc)\n            raise ResourceNotFound(detail=exc.message)\n        return contact.marshall_json_dict()\n\n    @view(renderer='json', permission='authenticated')\n    def collection_post(self):\n        \"\"\"Create a new contact from json post data structure.\"\"\"\n        data = self.request.json\n        contact_param = NewContactParam(data)\n        try:\n            contact_param.validate()\n            if hasattr(contact_param, \"tags\") and contact_param.tags:\n                raise ValidationError(\n                    \"adding tags through parent object is forbidden\")\n        except Exception as exc:\n            raise ValidationError(exc)\n        contact = CoreContact.create(self.user, contact_param)\n        contact_url = self.request.route_path('contact',\n                                              contact_id=contact.contact_id)\n        self.request.response.location = contact_url.encode('utf-8')\n        # XXX return a Location to get contact not send it direct\n        return {'location': contact_url}\n\n    @view(renderer='json', permission='authenticated')\n    def patch(self):\n        \"\"\"Update a contact with payload.\n\n        method follows the rfc5789 PATCH and rfc7396 Merge\n        patch specifications, + 'current_state' caliopen's specs.\n        stored messages are modified according to the fields within\n        the payload, ie payload fields squash existing db fields,\n        no other modification done.\n        If message doesn't existing, response is 404.\n        If payload fields are not conform to the message db schema, response is\n        422 (Unprocessable Entity).\n        Successful response is 204, without a body.\n        \"\"\"\n        contact_id = self.request.swagger_data[\"contact_id\"]\n        patch = self.request.json\n\n        contact = ContactObject(user=self.user, contact_id=contact_id)\n        try:\n            contact.apply_patch(patch, db=True, index=True,\n                                with_validation=True)\n        except Exception as exc:\n            raise MergePatchError(error=exc)\n\n        return Response(None, 204)\n\n    @view(renderer='json', permission='authenticated')\n    def delete(self):\n        contact_id = self.request.swagger_data[\"contact_id\"]\n        contact = ContactObject(user=self.user, contact_id=contact_id)\n\n        try:\n            contact.delete()\n        except Exception as exc:\n            if isinstance(exc, ForbiddenAction):\n                raise HTTPForbidden(exc)\n            else:\n                raise HTTPServerError(exc)\n\n        return Response(None, 204)\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/user/imports.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen file import REST API.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom caliopen_main.contact.core import Contact as CoreContact\n\nfrom cornice.resource import resource, view\nfrom pyramid.response import Response\n\nfrom caliopen_main.contact.parsers import VcardParser\n\nfrom ..base.exception import (ValidationError,\n                              Unprocessable)\n\nfrom ..base import Api\n\nlog = logging.getLogger(__name__)\n\n\n@resource(collection_path='/imports', path='')\nclass ContactImport(Api):\n    def __init__(self, request):\n        self.request = request\n        self.user = request.authenticated_userid\n\n    @view(permission='authenticated')\n    def collection_post(self):\n        \"\"\"API to import an user file (vcard at this time).\"\"\"\n        # need to check by ourself if <file> param is present\n        # because swagger lib failed to do it correctly :(\n        try:\n            self.request.POST.getone(\"file\")\n        except Exception as exc:\n            raise ValidationError(exc)\n\n        data = self.request.POST['file'].file\n        try:\n            parser = VcardParser(data)\n        except Exception as exc:\n            log.exception('Exception during vcard file parsing %r' % exc)\n            raise ValidationError(exc)\n        try:\n            new_contacts = parser.parse()\n        except Exception as exc:\n            log.error('Syntax error: {}'.format(exc))\n            raise ValidationError(exc)\n        try:\n            for contact in new_contacts:\n                CoreContact.create(self.user, contact.contact)\n        except Exception as exc:\n            log.error(\n                'File valid but we can create the new contact: {}'.format(exc))\n            raise Unprocessable(detail=exc.message)\n\n        return Response(status=200)\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/user/settings.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen ReST API for user settings management.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom pyramid.response import Response\nfrom pyramid.httpexceptions import HTTPNoContent\nfrom caliopen_main.user.objects.settings import Settings as ObjectSettings\nfrom cornice.resource import resource, view\n\nfrom ..base import Api\nfrom ..base.context import DefaultContext\n\nfrom ..base.exception import MergePatchError\n\nlog = logging.getLogger(__name__)\n\n\n@resource(path='/settings',\n          name='Settings',\n          factory=DefaultContext\n          )\nclass SettingsAPI(Api):\n    \"\"\"Settings management API.\"\"\"\n\n    def __init__(self, request, context):\n        \"\"\"Create an instance of Device API.\"\"\"\n        self.request = request\n        self.user = request.authenticated_userid\n\n    @view(renderer='json', permission='authenticated')\n    def get(self):\n        \"\"\"Return user settings.\"\"\"\n        objects = ObjectSettings.list_db(self.user)\n        settings = [x.marshall_dict() for x in objects]\n        if settings:\n            return settings[0]\n        raise HTTPNoContent()\n\n    @view(renderer='json', permission='authenticated')\n    def patch(self):\n        \"\"\"Update settings with payload.\n        method follows the rfc5789 PATCH and rfc7396 Merge patch\n        specifications, + 'current_state' caliopen's specs.\n        stored messages are modified according to the fields within the\n        payload, ie payload fields squash existing db fields, no other\n        modification done. If message doesn't existing, response is 404.\n        If payload fields are not conform to the message db schema, response is\n        422 (Unprocessable Entity).\n        Successful response is 204, without a body.\n        \"\"\"\n        patch = self.request.json\n\n        settings = ObjectSettings(self.user)\n        error = settings.apply_patch(patch, db=True)\n        if error is not None:\n            raise MergePatchError(error)\n\n        return Response(None, 204)\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/user/user.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user API.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport datetime\n\nfrom pyramid.security import NO_PERMISSION_REQUIRED\nfrom cornice.resource import resource, view\n\nimport tornado.ioloop\nimport tornado.gen\nfrom nats.io import Client as Nats\nimport json\n\nfrom ..base.context import DefaultContext\nfrom .util import create_token\n\nfrom ..base import Api\nfrom ..base.exception import AuthenticationError, NotAcceptable\nfrom ..base.exception import Unprocessable, ValidationError\n\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_storage.config import Configuration\nfrom caliopen_main.common.core import PublicKey\nfrom caliopen_main.user.core import User\nfrom caliopen_main.user.parameters import NewUser, Settings\nfrom caliopen_main.user.returns.user import ReturnUser\nfrom caliopen_main.contact.parameters import NewContact, NewEmail\nfrom caliopen_main.device.core import Device\n\nlog = logging.getLogger(__name__)\n\n\ndef get_device_sig_key(user, device):\n    \"\"\"Get device signature key.\"\"\"\n    keys = PublicKey._model_class.filter(user_id=user.user_id,\n                                         resource_id=device.device_id)\n    keys = [x for x in keys if x.resource_type == 'device' and\n            x.use == 'sig']\n    if keys:\n        return keys[0]\n    return None\n\n\ndef patch_device_key(key, param):\n    \"\"\"Patch a device signature public key as X and Y points are not valid.\"\"\"\n    if not key.x and not key.y:\n        key.x = int(param['ecdsa_key']['x'], 16)\n        key.y = int(param['ecdsa_key']['y'], 16)\n        key.save()\n        return True\n    return False\n\n\ndef make_user_device_tokens(request, user, device, key, ttl=86400):\n    \"\"\"Return (key, tokens) informations for cache entry management.\"\"\"\n    cache_key = '{}-{}'.format(user.user_id, device.device_id)\n\n    previous = request.cache.get(cache_key)\n    if previous:\n        status = previous.get('user_status', 'unknown')\n        log.info('Found current user device entry {} : {}'.\n                 format(cache_key, status))\n        if status in ['locked', 'maintenance']:\n            raise AuthenticationError('Status {} does not permit operations'.\n                                      format(status))\n\n    access_token = create_token()\n    refresh_token = create_token(80)\n\n    expires_at = (datetime.datetime.utcnow() +\n                  datetime.timedelta(seconds=ttl))\n\n    tokens = {'access_token': access_token,\n              'refresh_token': refresh_token,\n              'expires_in': ttl,  # TODO : remove this value\n              'shard_id': user.shard_id,\n              'expires_at': expires_at.isoformat(),\n              'user_status': 'active',\n              'key_id': str(key.key_id),\n              'x': key.x,\n              'y': key.y,\n              'curve': key.crv}\n\n    request.cache.set(cache_key, tokens)\n    result = tokens.copy()\n    result.pop('shard_id')\n    return result\n\n\n@resource(path='',\n          collection_path='/authentications',\n          name='Authentication',\n          factory=DefaultContext\n          )\nclass AuthenticationAPI(Api):\n    \"\"\"User authentication API.\"\"\"\n\n    @view(renderer='json', permission=NO_PERMISSION_REQUIRED)\n    def collection_post(self):\n        \"\"\"\n        Api for user authentication.\n\n        Store generated tokens in a cache entry related to user_id\n        and return a structure with this tokens for client usage.\n        \"\"\"\n        params = self.request.json\n        try:\n            user = User.authenticate(params['username'], params['password'])\n            log.info('Authenticate user {username}'.format(username=user.name))\n        except Exception as exc:\n            log.info('Authentication error for {name} : {error}'.\n                     format(name=params['username'], error=exc))\n            raise AuthenticationError(detail=exc.message)\n        # Device management\n        in_device = self.request.swagger_data['authentication']['device']\n        key = None\n        if in_device:\n            try:\n                device = Device.get(user, in_device['device_id'])\n                log.info(\"Found device %s\" % device.device_id)\n                # Found a device, check if signature public key have X and Y\n                key = get_device_sig_key(user, device)\n                if not key:\n                    log.error('No signature key found for device %r'\n                              % device.device_id)\n                else:\n                    if patch_device_key(key, in_device):\n                        log.info('Patch device key OK')\n                    else:\n                        log.warn('Patch device key does not work')\n            except NotFound:\n                devices = Device.find(user)\n                if devices.get('objects', []):\n                    in_device['status'] = 'unverified'\n                else:\n                    in_device['name'] = 'default'\n                # we must declare a new device\n                device = Device.create_from_parameter(user, in_device,\n                                                      self.request.headers)\n                log.info(\"Created device %s\" % device.device_id)\n                key = get_device_sig_key(user, device)\n                if not key:\n                    log.error('No signature key found for device %r'\n                              % device.device_id)\n\n        else:\n            raise ValidationError(detail='No device informations')\n        try:\n            device.login(self.request.headers.get('X-Forwarded-For'))\n        except Exception as exc:\n            log.exception('Device login failed: {0}'.format(exc))\n\n        tokens = make_user_device_tokens(self.request, user, device, key)\n        return {'user_id': user.user_id,\n                'username': user.name,\n                'tokens': tokens,\n                'device': {'device_id': device.device_id,\n                           'status': device.status}}\n\n\ndef no_such_user(request):\n    \"\"\"Validator that an user does not exist.\"\"\"\n    username = request.swagger_data['user']['username']\n    if not User.is_username_available(username):\n        raise NotAcceptable(detail='User already exist')\n\n\n@resource(path='/users/{user_id}',\n          collection_path='/users',\n          name='User',\n          factory=DefaultContext)\nclass UserAPI(Api):\n    \"\"\"User API.\"\"\"\n\n    @view(renderer='json',\n          permission=NO_PERMISSION_REQUIRED,\n          validators=no_such_user)\n    def collection_post(self):\n        \"\"\"Create a new user.\"\"\"\n        settings = Settings()\n        settings.import_data(self.request.swagger_data['user']['settings'])\n        try:\n            settings.validate()\n        except Exception as exc:\n            raise Unprocessable(detail=exc.message)\n\n        param = NewUser({'name': self.request.swagger_data['user']['username'],\n                         'password': self.request.swagger_data['user'][\n                             'password'],\n                         'recovery_email': self.request.swagger_data['user'][\n                             'recovery_email'],\n                         'settings': settings,\n                         })\n\n        if self.request.swagger_data['user']['contact'] is not None:\n            param.contact = self.request.swagger_data['user']['contact']\n        else:\n            c = NewContact()\n            c.given_name = param.name\n            c.family_name = \"\"  # can't guess it !\n            email = NewEmail()\n            email.address = param.recovery_email\n            c.emails = [email]\n            param.contact = c\n\n        try:\n            user = User.create(param)\n        except Exception as exc:\n            log.exception('Error during user creation {0}'.format(exc))\n            raise NotAcceptable(detail=exc.message)\n\n        log.info('Created user {} with name {}'.\n                 format(user.user_id, user.name))\n        # default device management\n        in_device = self.request.swagger_data['user']['device']\n        if in_device:\n            try:\n                in_device['name'] = 'default'\n                device = Device.create_from_parameter(user, in_device,\n                                                      self.request.headers)\n                log.info('Device %r created' % device.device_id)\n            except Exception as exc:\n                log.exception('Error during default device creation %r' % exc)\n        else:\n            log.warn('Missing default device parameter')\n        user_url = self.request.route_path('User', user_id=user.user_id)\n        self.request.response.location = user_url.encode('utf-8')\n\n        # send notification to apiv2 to trigger post-registration actions\n        config = Configuration('global').get(\"message_queue\")\n        try:\n            tornado.ioloop.IOLoop.current().run_sync(\n                lambda: notify_new_user(user, config), timeout=5)\n        except Exception as exc:\n            log.exception(\n                'Error when sending new_user notification on NATS : {0}'.\n                format(exc))\n\n        return {'location': user_url}\n\n\n@resource(path='/me',\n          name='MeUser',\n          factory=DefaultContext)\nclass MeUserAPI(Api):\n    \"\"\"Me API.\"\"\"\n\n    @view(renderer='json',\n          permission='authenticated')\n    def get(self):\n        \"\"\"Get information about logged user.\"\"\"\n        user_id = self.request.authenticated_userid.user_id\n        user = User.get(user_id)\n        return ReturnUser.build(user).serialize()\n\n\n@tornado.gen.coroutine\ndef notify_new_user(user, config):\n    client = Nats()\n    server = 'nats://{}:{}'.format(config['host'], config['port'])\n    opts = {\"servers\": [server]}\n    yield client.connect(**opts)\n    notif = {\n        'message': 'created',\n        'user_name': '{0}'.format(user.name),\n        'user_id': '{0}'.format(user.user_id)\n    }\n    yield client.publish('userAction', json.dumps(notif))\n    yield client.flush()\n    log.info(\"New user notification sent on NATS for {0}\".format(user.user_id))\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/caliopen_api/user/util.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport os\nimport binascii\n\n\ndef create_token(size=40):\n    return binascii.hexlify(os.urandom(int(size/2))).decode('ascii')\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/requirements.deps",
    "content": "caliopen_storage\ncaliopen_main"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliop\nwith-coverage = 1\ncover-erase = 1\n\n[compile_catalog]\ndirectory = caliop/locale\ndomain = caliop\nstatistics = true\n\n[extract_messages]\nadd_comments = TRANSLATORS:\noutput_file = caliop/locale/caliop.pot\nwidth = 80\n\n[init_catalog]\ndomain = caliop\ninput_file = caliop/locale/caliop.pot\noutput_dir = caliop/locale\n\n[update_catalog]\ndomain = caliop\ninput_file = caliop/locale/caliop.pot\noutput_dir = caliop/locale\nprevious = true\n"
  },
  {
    "path": "src/backend/interfaces/REST/py.server/setup.py",
    "content": "import os\nimport sys\nimport re\n\nfrom setuptools import setup, find_packages\n\nPY3 = sys.version_info[0] == 3\n\nname = \"caliopen_api\"\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nwith open(\n        os.path.join(*([here] + name.split('.') + ['__init__.py']))) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S).match(\n        v_file.read()).group(1)\n\nrequires = [\n    'pyramid',\n    'pyramid_jinja2',\n    'redis==2.10.6',  # Enforce this version, version >= 3.0.0 break setex\n    'pyramid_kvs==0.3.0',\n    'waitress',\n    'cornice==1.2.1',\n    'colander',\n    'pyramid-swagger',\n    'rfc3987',\n    'webcolors',\n    'strict-rfc3339',\n    'nats-client',\n    'tornado==4.2',\n    'gunicorn',\n    'ecdsa']\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\ntests_require = ['nose', 'coverage']\nif sys.version_info < (3, 3):\n    tests_require.append('mock')\n\nextras_require = {\n    'dev': [\n        'pyramid_debugtoolbar',\n        'caliopen_api_doc',\n    ],\n    'doc': [\n        'sphinx',\n    ],\n    'test': tests_require\n}\n\nsetup(name=name,\n      version=version,\n      description='Caliopen REST API Server',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\n          \"Programming Language :: Python\",\n          \"Framework :: Pyramid\",\n          \"Topic :: Internet :: WWW/HTTP\",\n          \"Topic :: Internet :: WWW/HTTP :: WSGI :: Application\",\n      ],\n      author='Caliopen Contributors',\n      author_email='',\n      url='https://github.com/Caliopen/caliopen.api',\n      license='AGPLv3',\n      keywords='web pyramid api rest',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      install_requires=requires,\n      tests_require=tests_require,\n      extras_require=extras_require,\n      test_suite=\"caliopen_api.tests\",\n      entry_points={\n          'paste.app_factory': ['main = caliopen_api:main'],\n      })\n"
  },
  {
    "path": "src/backend/main/go.backends/AttachmentsInterfaces.go",
    "content": "package backends\n\nimport (\n\t\"io\"\n)\n\ntype AttachmentStorage interface {\n\tStoreAttachment(attachment_id string, file io.Reader) (uri string, size int, err error)\n\tGetAttachment(uri string) (file io.Reader, err error)\n\tDeleteAttachment(uri string) error\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/CacheInterfaces.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"time\"\n)\n\ntype APICache interface {\n\t// authentication\n\tGetAuthToken(token string) (value *Auth_cache, err error)\n\tLogoutUser(key string) error\n\t// password reset process\n\tGetResetPasswordToken(token string) (*TokenSession, error)\n\tGetResetPasswordSession(user_id string) (*TokenSession, error)\n\tSetResetPasswordSession(user_id, reset_token string) (*TokenSession, error)\n\tDeleteResetPasswordSession(user_id string) error\n\t// Oauth session handling\n\tSetOauthSession(key string, session *OauthSession) error\n\tGetOauthSession(key string) (*OauthSession, error)\n\tDeleteOauthSession(user_id string) error\n\t// Device validation\n\tGetDeviceValidationSession(userId, deviceId string) (*TokenSession, error)\n\tGetTokenValidationSession(userId, token string) (*TokenSession, error)\n\tSetDeviceValidationSession(userId, deviceId, token string) (*TokenSession, error)\n\tDeleteDeviceValidationSession(userId, deviceId string) error\n}\n\ntype CacheBackend interface {\n\t// CRD interface to the underlying backend\n\tSet(key string, value []byte, ttl time.Duration) error\n\tGet(key string) (value []byte, err error)\n\tDel(key string) error\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/ContactsInterfaces.go",
    "content": "/*\n * // Copyleft (ɔ) 2017 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype ContactStorage interface {\n\tCreateContact(contact *Contact) error\n\tRetrieveContact(userID, contactID string) (contact *Contact, err error)\n\tRetrieveUserContactId(userID string) string\n\tUpdateContact(contact, oldContact *Contact, fields map[string]interface{}) error\n\tDeleteContact(contact *Contact) error\n\tContactExists(userId, contactId string) bool\n\tLookupContactsByIdentifier(user_id, address, kind string) (contact_ids []string, err error)\n\tContactsForParticipants(userID string, participants map[string]Participant) error\n}\n\ntype ContactIndex interface {\n\tCreateContact(user *UserInfo, contact *Contact) error\n\tDeleteContact(user *UserInfo, contact *Contact) error\n\tUpdateContact(user *UserInfo, contact *Contact, fields map[string]interface{}) error\n\tFilterContacts(search IndexSearch) (Contacts []*Contact, totalFound int64, err error)\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/CredentialsInterfaces.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype CredentialsStorage interface {\n\tCreateCredentials(userIdentity *UserIdentity, cred Credentials) error\n\tRetrieveCredentials(userId, remoteId string) (Credentials, error)\n\tUpdateCredentials(userId, remoteId string, cred Credentials) error\n\tDeleteCredentials(userId, remoteId string) error\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/DevicesInterfaces.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype DevicesStorage interface {\n\tCreateDevice(device *Device) error\n\tRetrieveDevices(user_id string) (devices []Device, err error)\n\tRetrieveDevice(userId, deviceId string) (device *Device, err error)\n\tUpdateDevice(device, oldDevice *Device, modifiedFields map[string]interface{}) error\n\tDeleteDevice(device *Device) error\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/DiscussionsInterface.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype DiscussionStorage interface {\n\tGetUserLookupHashes(userId UUID, kind, key string) (hashes []ParticipantHash, err error)\n\tUpsertDiscussionLookups(userId UUID, participants []Participant) error\n}\n\ntype DiscussionIndex interface {\n\tGetDiscussionsList(filter IndexSearch, withIL bool) ([]Discussion, error)\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/IdentitiesInterface.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype (\n\tIdentityStorage interface {\n\t\tRetrieveLocalsIdentities(user_id string) ([]UserIdentity, error)\n\t\tCreateUserIdentity(userIdentity *UserIdentity) CaliopenError\n\t\tRetrieveUserIdentity(userId, RemoteId string, withCredentials bool) (*UserIdentity, error)\n\t\tLookupIdentityByIdentifier(string, ...string) ([][2]string, error)\n\t\tLookupIdentityByType(string, ...string) ([][2]string, error)\n\t\tIdentityStorageUpdater\n\t\tDeleteUserIdentity(userIdentity *UserIdentity) error\n\t\tRetrieveRemoteIdentities(userId string, withCredentials bool) ([]*UserIdentity, error)\n\t\tRetrieveAllRemotes(withCredentials bool) (<-chan *UserIdentity, error)\n\t\tUpdateRemoteInfosMap(userId, remoteId string, infos map[string]string) error\n\t\tRetrieveRemoteInfosMap(userId, remoteId string) (infos map[string]string, err error)\n\t\tIsLocalIdentity(userId, identityId string) bool\n\t\tIsRemoteIdentity(userId, identityId string) bool\n\t\tClose()\n\t}\n)\n\ntype IdentityStorageUpdater interface {\n\tUpdateUserIdentity(userIdentity *UserIdentity, fields map[string]interface{}) error\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/KeysInterfaces.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype KeysStorage interface {\n\tCreatePGPPubKey(pubkey *PublicKey) CaliopenError\n\tRetrieveContactPubKeys(userId, contactId string) (PublicKeys, CaliopenError)\n\tRetrievePubKey(userId, resourceId, keyId string) (*PublicKey, CaliopenError)\n\tDeletePubKey(pubkey *PublicKey) CaliopenError\n\tUpdatePubKey(newPubKey, oldPubKey *PublicKey, modifiedFields map[string]interface{}) CaliopenError\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/LDAInterfaces.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"io\"\n\t\"time\"\n)\n\n//Local Delivery Agent storage interface\ntype LDAStore interface {\n\tClose()\n\tRetrieveMessage(user_id, msg_id string) (msg *Message, err error)\n\tGetUsersForLocalMailRecipients([]string) ([][]UUID, error) // returns a list of tuples ([user_id, identity_id]) of **local** users found for given recipients list. No deduplicate.\n\tGetSettings(user_id string) (settings *Settings, err error)\n\tCreateMessage(msg *Message) error\n\n\tStoreRawMessage(msg RawMessage) (err error)\n\tGetRawMessage(raw_message_id string) (raw_message RawMessage, err error)\n\tSetDeliveredStatus(raw_msg_id string, delivered bool) error\n\tUpdateMessage(msg *Message, fields map[string]interface{}) error // 'fields' are the struct fields names that have been modified\n\tSeekMessageByExternalRef(userID, externalMessageID, identityID string) (UUID, error)\n\n\tLookupContactsByIdentifier(user_id, address, kind string) (contact_ids []string, err error)\n\n\tGetAttachment(uri string) (file io.Reader, err error)\n\tDeleteAttachment(uri string) error\n\tAttachmentExists(uri string) bool\n\n\tRetrieveUserIdentity(userId, identityId string, withCredentials bool) (*UserIdentity, error)\n\tUpdateUserIdentity(userIdentity *UserIdentity, fields map[string]interface{}) error\n\tRetrieveUser(user_id string) (user *User, err error)\n\tUpdateRemoteInfosMap(userId, remoteId string, infos map[string]string) error\n\tRetrieveRemoteInfosMap(userId, remoteId string) (infos map[string]string, err error)\n\tTimestampRemoteLastCheck(userId, remoteId string, time ...time.Time) error\n\n\tRetrieveProvider(name, instance string) (*Provider, CaliopenError)\n}\n\ntype LDAIndex interface {\n\tClose()\n\tCreateMessage(user *UserInfo, msg *Message) error\n\tUpdateMessage(user *UserInfo, msg *Message, fields map[string]interface{}) error\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/MessagesInterfaces.go",
    "content": "package backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype MessageStorage interface {\n\tCreateMessage(msg *Message) error\n\tRetrieveMessage(user_id, msg_id string) (msg *Message, err error)\n\tUpdateMessage(msg *Message, fields map[string]interface{}) error // 'fields' are the struct fields names that have been modified\n\tDeleteMessage(msg *Message) error\n\tSetMessageUnread(user_id, message_id string, status bool) error\n\tGetRawMessage(raw_message_id string) (raw_message RawMessage, err error)\n}\n\ntype MessageIndex interface {\n\tSetMessageUnread(user *UserInfo, message_id string, status bool) error\n\tCreateMessage(user *UserInfo, msg *Message) error\n\tUpdateMessage(user *UserInfo, msg *Message, fields map[string]interface{}) error\n\tFilterMessages(search IndexSearch) (messages []*Message, totalFound int64, err error)\n\tGetMessagesRange(search IndexSearch) (messages []*Message, totalFound int64, err error)\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/NotificationsInterfaces.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"time\"\n)\n\ntype NotificationsStore interface {\n\tCreateMessage(msg *Message) error\n\tUserByUsername(username string) (user *User, err error) // to retrieve admin user\n\tRetrieveLocalsIdentities(user_id string) (identities []UserIdentity, err error)\n\tPutNotificationInQueue(*Notification) error\n\tNotificationsByTime(userId string, from, to time.Time) ([]Notification, error)\n\tNotificationsByID(userId, from, to string) ([]Notification, error)\n\tRetrieveNotification(userId, notificationId string) (Notification, error)\n\tDeleteNotifications(userId string, until time.Time) error\n\tDeleteNotification(userId, notificationId string) error\n}\n\ntype NotificationsIndex interface {\n\tCreateMessage(user *UserInfo, msg *Message) error\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/ProvidersInterfaces.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype (\n\tProviderStorage interface {\n\t\tCreateProvider(*Provider) CaliopenError\n\t\tRetrieveProvider(name, instance string) (*Provider, CaliopenError)\n\t\tUpdateProvider(*Provider, map[string]interface{}) CaliopenError\n\t\tDeleteProvider(*Provider) CaliopenError\n\t}\n)\n"
  },
  {
    "path": "src/backend/main/go.backends/RESTInterfaces.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/gocql/gocql\"\n)\n\ntype APIStorage interface {\n\tAttachmentStorage\n\tCredentialsStorage\n\tContactStorage\n\tDevicesStorage\n\tDiscussionStorage\n\tIdentityStorage\n\tKeysStorage\n\tMessageStorage\n\tUrisStorage\n\tTagsStorage\n\tUserNameStorage\n\tUserStorage\n\tProviderStorage\n\tGetSession() *gocql.Session\n}\n\ntype APIIndex interface {\n\tMessageIndex\n\tContactIndex\n\tDiscussionIndex\n\tRecipientsSuggest(user *UserInfo, query_string string) (suggests []RecipientSuggestion, err error)\n\tSearch(search IndexSearch) (result *IndexResult, err error)\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/TagsInterfaces.go",
    "content": "package backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype TagsStorage interface {\n\tRetrieveUserTags(user_id string) (tags []Tag, err error)\n\tCreateTag(tag *Tag) error\n\tRetrieveTag(user_id, tag_id string) (tag Tag, err error)\n\tUpdateTag(tag *Tag) error\n\tDeleteTag(user_id, tag_id string) error\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/UrisInterface.go",
    "content": "package backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype UrisStorage interface {\n\tLookupHash(user_id UUID, uri string) ([]HashLookup, error)\n\tCreateHashLookup(lookup HashLookup) error\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/UsersInterfaces.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backends\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype (\n\tUserStorage interface {\n\t\tGetSettings(userId string) (settings *Settings, err error)\n\t\tRetrieveUser(userId string) (user *User, err error)\n\t\tUpdateUserPasswordHash(user *User) error\n\t\tUpdateUser(user *User, fields map[string]interface{}) error // 'fields' are the struct fields names that have been modified\n\t\tUserByRecoveryEmail(email string) (user *User, err error)\n\t\tDeleteUser(userId string) error\n\t\tGetShardForUser(userID string) string\n\t}\n\tUserNameStorage interface {\n\t\tUsernameIsAvailable(username string) (bool, error)\n\t\tUserByUsername(username string) (user *User, err error)\n\t}\n)\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/APIstore.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"github.com/gocql/gocql\"\n)\n\ntype APIStore struct {\n\tAttachmentStore\n\tCredentialStore\n\tContactsBackend\n\tDevicesStore\n\tDiscussionsStore\n\tIdentitiesBackend\n\tKeysStore\n\tMessagesBackend\n\tParticipantStore\n\tTagsStore\n\tUserNamesStore\n\tUsersBackend\n\tProvidersStore\n}\n\nfunc (s *APIStore) GetSession() *gocql.Session {\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Attachments.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t\"io\"\n)\n\ntype AttachmentStore struct{}\n\nfunc (as AttachmentStore) StoreAttachment(attachment_id string, file io.Reader) (uri string, size int, err error) {\n\treturn \"\", 0, errors.New(\"test interface not implemented\")\n}\nfunc (as AttachmentStore) GetAttachment(uri string) (file io.Reader, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (as AttachmentStore) DeleteAttachment(uri string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Cache.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"gopkg.in/redis.v5\"\n\t\"time\"\n)\n\ntype MockRedis struct {\n\tStore map[string][]byte\n\tTtl   map[string]time.Duration\n}\n\nfunc (mr *MockRedis) GetAuthToken(token string) (value *Auth_cache, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) LogoutUser(key string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) GetResetPasswordToken(token string) (*TokenSession, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) GetResetPasswordSession(user_id string) (*TokenSession, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) SetResetPasswordSession(user_id, reset_token string) (*TokenSession, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) DeleteResetPasswordSession(user_id string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) SetOauthSession(key string, session *OauthSession) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) GetOauthSession(key string) (*OauthSession, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) DeleteOauthSession(user_id string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) GetDeviceValidationSession(userId, deviceId string) (*TokenSession, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) GetTokenValidationSession(userId, token string) (*TokenSession, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) SetDeviceValidationSession(userId, deviceId, token string) (*TokenSession, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (mr *MockRedis) DeleteDeviceValidationSession(userId, deviceId string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\n// Set mocks Set func from gopkg.in/redis.v5/internal\n// expiration is not handled\nfunc (mr *MockRedis) Set(key string, value []byte, expiration time.Duration) error {\n\tmr.Store[key] = value\n\tmr.Ttl[key] = expiration\n\treturn nil\n}\n\n// Get mocks Get func from gopkg.in/redis.v5/internal\nfunc (mr *MockRedis) Get(key string) (value []byte, err error) {\n\tif v, ok := mr.Store[key]; ok {\n\t\treturn v, err\n\t} else {\n\t\treturn nil, redis.Nil\n\t}\n}\n\n// Del mocks Del func from gopkg.in/redis.v5/internal\nfunc (mr *MockRedis) Del(key string) error {\n\tdelete(mr.Store, key)\n\treturn nil\n}\n\n// GetTTL returns the Ttl that has been set along with a key when Set has been previously called\n// for testing purpose\nfunc (mr *MockRedis) GetTTL(key string) (Ttl time.Duration, err error) {\n\tif v, ok := mr.Ttl[key]; ok {\n\t\treturn v, err\n\t} else {\n\t\treturn 0, errors.New(\"not found\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Contacts.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype ContactsBackend struct {\n\tcontacts map[string]*Contact\n}\n\nfunc GetContactBackend() ContactsBackend {\n\treturn ContactsBackend{\n\t\tcontacts: Contacts,\n\t}\n}\n\nfunc (cb ContactsBackend) CreateContact(contact *Contact) error {\n\treturn errors.New(\"CreateContact test interface not implemented\")\n}\nfunc (cb ContactsBackend) RetrieveContact(userID, contactID string) (contact *Contact, err error) {\n\treturn nil, errors.New(\"RetrieveContact test interface not implemented\")\n}\nfunc (cb ContactsBackend) RetrieveUserContactId(userID string) string {\n\treturn \"\"\n}\nfunc (cb ContactsBackend) UpdateContact(contact, oldContact *Contact, fields map[string]interface{}) error {\n\t//return errors.New(\"UpdateContact test interface not implemented\")\n\treturn nil\n}\nfunc (cb ContactsBackend) DeleteContact(contact *Contact) error {\n\treturn errors.New(\"DeleteContact test interface not implemented\")\n}\nfunc (cb ContactsBackend) ContactExists(userId, contactId string) bool {\n\treturn false\n}\n\nfunc (cb ContactsBackend) LookupContactsByIdentifier(user_id, address, kind string) ([]string, error) {\n\tif contact_id, ok := ContactLookup[kind+\":\"+address]; ok {\n\t\treturn []string{contact_id}, nil\n\t} else {\n\t\treturn nil, errors.New(\"not found\")\n\t}\n}\n\nfunc (cb ContactsBackend) ContactsForParticipants(userID string, participants map[string]Participant) error {\n\treturn errors.New(\"ContactForParticipants test interface not implemented\")\n}\n\n// ContactIndex interface\ntype ContactsIndex struct {\n}\n\nfunc (ci ContactsIndex) CreateContact(user *UserInfo, contact *Contact) error {\n\treturn errors.New(\"CreateContact test interface not implemented\")\n}\n\nfunc (ci ContactsIndex) UpdateContact(user *UserInfo, contact *Contact, fields map[string]interface{}) error {\n\t//return errors.New(\"UpdateContact test interface not implemented\")\n\treturn nil\n}\n\nfunc (ci ContactsIndex) FilterContacts(search IndexSearch) (contacts []*Contact, totalFound int64, err error) {\n\treturn nil, 0, errors.New(\"FilterContact test interface not implemented\")\n}\n\nfunc (ci ContactsIndex) DeleteContact(user *UserInfo, contact *Contact) error {\n\treturn errors.New(\"DeleteContact test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Credentials.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype CredentialStore struct{}\n\nfunc (cs CredentialStore) CreateCredentials(userIdentity *UserIdentity, cred Credentials) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (cs CredentialStore) RetrieveCredentials(userId, remoteId string) (Credentials, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (cs CredentialStore) UpdateCredentials(userId, remoteId string, cred Credentials) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (cs CredentialStore) DeleteCredentials(userId, remoteId string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Devices.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype DevicesStore struct {\n\tdevices map[string]*Device\n}\n\nfunc (ds DevicesStore) CreateDevice(device *Device) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ds DevicesStore) RetrieveDevices(user_id string) (devices []Device, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (ds DevicesStore) RetrieveDevice(userId, deviceId string) (device *Device, err error) {\n\tif device, ok := Devices[userId+deviceId]; ok {\n\t\treturn device, nil\n\t}\n\treturn nil, errors.New(\"not found\")\n}\nfunc (ds DevicesStore) UpdateDevice(device, oldDevice *Device, modifiedFields map[string]interface{}) error {\n\tDevices[device.UserId.String()+device.DeviceId.String()] = device\n\treturn nil\n}\nfunc (ds DevicesStore) DeleteDevice(device *Device) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Discussions.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype DiscussionsStore struct{}\n\nfunc (ds *DiscussionsStore) GetUserLookupHashes(userId UUID, kind, key string) (hashes []ParticipantHash, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (ds *DiscussionsStore) UpsertDiscussionLookups(userId UUID, participants []Participant) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Identities.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// Package backendstest provides utilities and interfaces for mocking backends interfaces\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\n// IdentitiesStorage and IdentityStorageUpdater implementation\ntype IdentitiesBackend struct {\n\tlocalIdentities  map[string]*UserIdentity\n\tremoteIdentities map[string]*UserIdentity\n}\n\n// GetIdentitiesBackend returns an IdentitiesBackend implementing all IdentitiesStorage interfaces\n// serving default testdata unless some data are provided in params arrays\nfunc GetIdentitiesBackend(locals, remotes []*UserIdentity) *IdentitiesBackend {\n\ti := IdentitiesBackend{}\n\tif len(locals) > 0 {\n\t\tfor _, local := range locals {\n\t\t\ti.localIdentities[local.UserId.String()+local.Id.String()] = local\n\t\t}\n\t} else {\n\t\ti.localIdentities = LocalIdentities\n\t}\n\tif len(remotes) > 0 {\n\t\tfor _, remote := range remotes {\n\t\t\ti.remoteIdentities[remote.UserId.String()+remote.Id.String()] = remote\n\t\t}\n\t} else {\n\t\ti.remoteIdentities = RemoteIdentities\n\t}\n\n\treturn &i\n}\n\nfunc (ib IdentitiesBackend) RetrieveLocalsIdentities(user_id string) ([]UserIdentity, error) {\n\treturn RetrieveLocalsIdentities(user_id)\n}\nfunc (ib IdentitiesBackend) CreateUserIdentity(userIdentity *UserIdentity) CaliopenError {\n\treturn NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\nfunc (ib IdentitiesBackend) RetrieveUserIdentity(userId, identityId string, withCredentials bool) (*UserIdentity, error) {\n\treturn RetrieveUserIdentity(userId, identityId, withCredentials)\n}\nfunc (ib IdentitiesBackend) LookupIdentityByIdentifier(string, ...string) ([][2]string, error) {\n\treturn [][2]string{}, errors.New(\"test interface not implemented\")\n}\nfunc (ib IdentitiesBackend) LookupIdentityByType(string, ...string) ([][2]string, error) {\n\treturn [][2]string{}, errors.New(\"test interface not implemented\")\n}\nfunc (ib IdentitiesBackend) UpdateUserIdentity(userIdentity *UserIdentity, fields map[string]interface{}) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ib IdentitiesBackend) DeleteUserIdentity(userIdentity *UserIdentity) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ib IdentitiesBackend) RetrieveRemoteIdentities(userId string, withCredentials bool) ([]*UserIdentity, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (ib IdentitiesBackend) RetrieveAllRemotes(withCredentials bool) (<-chan *UserIdentity, error) {\n\treturn RetrieveAllRemotes(withCredentials)\n}\nfunc (ib IdentitiesBackend) UpdateRemoteInfosMap(userId, remoteId string, infos map[string]string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ib IdentitiesBackend) RetrieveRemoteInfosMap(userId, remoteId string) (infos map[string]string, err error) {\n\treturn map[string]string{}, errors.New(\"test interface not implemented\")\n}\nfunc (ib IdentitiesBackend) IsLocalIdentity(userId, identityId string) bool {\n\treturn false\n}\nfunc (ib IdentitiesBackend) IsRemoteIdentity(userId, identityId string) bool {\n\treturn false\n}\nfunc (ib IdentitiesBackend) Close() {\n\n}\n\nfunc LocalsCount() int {\n\treturn len(LocalIdentities)\n}\n\nfunc RemotesCount() int {\n\treturn len(RemoteIdentities)\n}\n\nfunc ActiveRemotesCount() int {\n\tvar c int\n\tfor _, remote := range RemoteIdentities {\n\t\tif remote.Status == \"active\" {\n\t\t\tc += 1\n\t\t}\n\t}\n\treturn c\n}\n\nfunc RetrieveLocalsIdentities(user_id string) ([]UserIdentity, error) {\n\tlocals := []UserIdentity{}\n\tfor _, local := range LocalIdentities {\n\t\tlocals = append(locals, *local)\n\t}\n\treturn locals, nil\n}\n\nfunc RetrieveUserIdentity(userId, identityId string, withCredentials bool) (*UserIdentity, error) {\n\tif userId == \"\" && identityId == \"\" {\n\t\t// return one remote identity by default\n\t\tfor _, remote := range RemoteIdentities {\n\t\t\tif !withCredentials {\n\t\t\t\tremote.Credentials = nil\n\t\t\t}\n\t\t\treturn remote, nil\n\t\t}\n\t}\n\tif remote, ok := RemoteIdentities[userId+identityId]; ok {\n\t\tif !withCredentials {\n\t\t\tremote.Credentials = nil\n\t\t}\n\t\treturn remote, nil\n\t}\n\tif local, ok := LocalIdentities[userId+identityId]; ok {\n\t\tif !withCredentials {\n\t\t\tlocal.Credentials = nil\n\t\t}\n\t\treturn local, nil\n\t}\n\treturn nil, errors.New(\"not found\")\n}\n\nfunc RetrieveAllRemotes(withCredentials bool) (<-chan *UserIdentity, error) {\n\tch := make(chan *UserIdentity)\n\tgo func() {\n\t\tfor _, remote := range RemoteIdentities {\n\t\t\tch <- remote\n\t\t}\n\t\tclose(ch)\n\t}()\n\treturn ch, nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Keys.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype KeysStore struct{}\n\nfunc (ks KeysStore) CreatePGPPubKey(pubkey *PublicKey) CaliopenError {\n\treturn NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\nfunc (ks KeysStore) RetrieveContactPubKeys(userId, contactId string) (PublicKeys, CaliopenError) {\n\treturn nil, NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\nfunc (ks KeysStore) RetrievePubKey(userId, resourceId, keyId string) (*PublicKey, CaliopenError) {\n\treturn nil, NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\nfunc (ks KeysStore) DeletePubKey(pubkey *PublicKey) CaliopenError {\n\treturn NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\nfunc (ks KeysStore) UpdatePubKey(newPubKey, oldPubKey *PublicKey, modifiedFields map[string]interface{}) CaliopenError {\n\treturn NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/LDA.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"io\"\n\t\"time\"\n)\n\ntype LDAStoreBackend struct {\n}\n\ntype LDAIndexBackend struct {\n}\n\n// GetLDAStoreBackend returns an LDAStoreBackend implementing all LDAStore interfaces\n// serving default testdata unless some data are provided in params arrays\nfunc GetLDAStoreBackend() *LDAStoreBackend {\n\ts := LDAStoreBackend{}\n\treturn &s\n}\n\n// GetLDAIndexBackend returns an LDAIndexBackend implementing all LDAIndex interfaces\n// serving default testdata unless some data are provided in params arrays\nfunc GetLDAIndexBackend() *LDAIndexBackend {\n\ti := LDAIndexBackend{}\n\treturn &i\n}\n\nfunc (ldaStore *LDAStoreBackend) Close() {\n\n}\n\nfunc (ldaStore *LDAStoreBackend) RetrieveMessage(userId, msgId string) (msg *Message, err error) {\n\tmb := GetMessagesBackend()\n\treturn mb.RetrieveMessage(userId, msgId)\n}\n\nfunc (ldaStore *LDAStoreBackend) GetUsersForLocalMailRecipients([]string) ([][]UUID, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\n\nfunc (ldaStore *LDAStoreBackend) GetSettings(userId string) (settings *Settings, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\n\nfunc (ldaStore *LDAStoreBackend) CreateMessage(msg *Message) (err error) {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (ldaStore *LDAStoreBackend) StoreRawMessage(msg RawMessage) (err error) {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (ldaStore *LDAStoreBackend) GetRawMessage(rawMsgId string) (rawMsg RawMessage, err error) {\n\treturn RawMessage{}, errors.New(\"test interface not implemented\")\n}\n\nfunc (ldaStore *LDAStoreBackend) SetDeliveredStatus(raw_msg_id string, delivered bool) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) UpdateMessage(msg *Message, fields map[string]interface{}) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) CreateThreadLookup(user_id, discussion_id UUID, external_msg_id string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) SeekMessageByExternalRef(userID, externalMessageID, identityID string) (UUID, error) {\n\treturn EmptyUUID, errors.New(\"test interface not implemented\")\n}\n\nfunc (ldaStore *LDAStoreBackend) LookupContactsByIdentifier(user_id, address, kind string) (contact_ids []string, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\n\nfunc (ldaStore *LDAStoreBackend) GetAttachment(uri string) (file io.Reader, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) DeleteAttachment(uri string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) AttachmentExists(uri string) bool {\n\treturn false\n}\n\nfunc (ldaStore *LDAStoreBackend) RetrieveUserIdentity(userId, identityId string, withCredentials bool) (*UserIdentity, error) {\n\tib := GetIdentitiesBackend([]*UserIdentity{}, []*UserIdentity{})\n\treturn ib.RetrieveUserIdentity(userId, identityId, withCredentials)\n}\nfunc (ldaStore *LDAStoreBackend) UpdateUserIdentity(userIdentity *UserIdentity, fields map[string]interface{}) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) RetrieveUser(user_id string) (user *User, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) UpdateRemoteInfosMap(userId, remoteId string, infos map[string]string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) RetrieveRemoteInfosMap(userId, remoteId string) (infos map[string]string, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) TimestampRemoteLastCheck(userId, remoteId string, time ...time.Time) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ldaStore *LDAStoreBackend) RetrieveProvider(name, instance string) (*Provider, CaliopenError) {\n\treturn nil, NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\n\nfunc (ldIndex *LDAIndexBackend) Close() {}\nfunc (ldIndex *LDAIndexBackend) CreateMessage(user *UserInfo, msg *Message) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ldIndex *LDAIndexBackend) UpdateMessage(user *UserInfo, msg *Message, fields map[string]interface{}) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Messages.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype MessagesBackend map[string]*Message\n\nfunc GetMessagesBackend() MessagesBackend {\n\treturn MessagesBackend(Msgs)\n}\n\nfunc (mb MessagesBackend) RetrieveMessage(userId, msgId string) (msg *Message, err error) {\n\tif userId == \"\" && msgId == \"\" {\n\t\t// return one message by default\n\t\tfor _, msg = range mb {\n\t\t\treturn\n\t\t}\n\t}\n\tvar ok bool\n\tif msg, ok = mb[userId+msgId]; ok {\n\t\treturn\n\t}\n\treturn nil, errors.New(\"not found\")\n}\n\nfunc (mb MessagesBackend) CreateMessage(msg *Message) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (mb MessagesBackend) UpdateMessage(msg *Message, fields map[string]interface{}) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (mb MessagesBackend) DeleteMessage(msg *Message) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (mb MessagesBackend) SetMessageUnread(user_id, message_id string, status bool) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (mb MessagesBackend) GetRawMessage(raw_message_id string) (raw_message RawMessage, err error) {\n\treturn RawMessage{}, errors.New(\"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Notifications.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"time\"\n)\n\ntype NotificationsStore struct{}\ntype NotificationsIndex struct{}\n\nfunc GetNotificationsBackends() (store NotificationsStore, index NotificationsIndex) {\n\treturn NotificationsStore{}, NotificationsIndex{}\n}\n\nfunc (ns NotificationsStore) CreateMessage(msg *Message) error {\n\treturn errors.New(\"test interface not implemented\")\n}\nfunc (ns NotificationsStore) UserByUsername(username string) (user *User, err error) {\n\treturn UserByUsername(username)\n}\nfunc (ns NotificationsStore) RetrieveLocalsIdentities(userId string) (identities []UserIdentity, err error) {\n\treturn RetrieveLocalsIdentities(userId)\n}\nfunc (ns NotificationsStore) PutNotificationInQueue(*Notification) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (ns NotificationsStore) DeleteNotifications(userId string, until time.Time) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (ns NotificationsStore) NotificationsByTime(userId string, from, to time.Time) ([]Notification, error) {\n\n\treturn []Notification{}, errors.New(\"test interface not implemented\")\n}\nfunc (ns NotificationsStore) NotificationsByID(userId, from, to string) ([]Notification, error) {\n\n\treturn []Notification{}, errors.New(\"test interface not implemented\")\n}\nfunc (ns NotificationsStore) RetrieveNotification(userId, notificationId string) (Notification, error) {\n\n\treturn Notification{}, errors.New(\"test interface not implemented\")\n}\nfunc (ns NotificationsStore) DeleteNotification(userId, notificationId string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (ni NotificationsIndex) CreateMessage(user *UserInfo, msg *Message) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Providers.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype ProvidersStore struct{}\n\nfunc (ps *ProvidersStore) CreateProvider(*Provider) CaliopenError {\n\treturn NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\nfunc (ps *ProvidersStore) RetrieveProvider(name, instance string) (*Provider, CaliopenError) {\n\treturn nil, NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\nfunc (ps *ProvidersStore) UpdateProvider(*Provider, map[string]interface{}) CaliopenError {\n\treturn NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\nfunc (ps *ProvidersStore) DeleteProvider(*Provider) CaliopenError {\n\treturn NewCaliopenErr(NotImplementedCaliopenErr, \"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Tags.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype TagsStore struct{}\n\nfunc (ts TagsStore) RetrieveUserTags(user_id string) (tags []Tag, err error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\n\nfunc (ts TagsStore) CreateTag(tag *Tag) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (ts TagsStore) RetrieveTag(user_id, tag_id string) (tag Tag, err error) {\n\treturn Tag{}, errors.New(\"test interface not implemented\")\n}\n\nfunc (ts TagsStore) UpdateTag(tag *Tag) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n\nfunc (ts TagsStore) DeleteTag(user_id, tag_id string) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/UrisInterface.go",
    "content": "package backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype ParticipantStore struct {\n}\n\nfunc (ps *ParticipantStore) LookupHash(user_id UUID, uri string) ([]HashLookup, error) {\n\treturn nil, errors.New(\"test interface not implemented\")\n}\n\nfunc (ps *ParticipantStore) CreateHashLookup(lookup HashLookup) error {\n\treturn errors.New(\"test interface not implemented\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/UserNames.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype UserNamesStore struct{}\n\nfunc (uns UserNamesStore) UsernameIsAvailable(username string) (bool, error) {\n\treturn false, errors.New(\"test interface not implemented\")\n}\nfunc (uns UserNamesStore) UserByUsername(username string) (user *User, err error) {\n\treturn UserByUsername(username)\n}\n\nfunc UserByUsername(username string) (user *User, err error) {\n\tfor _, user := range Users {\n\t\tif user.Name == username {\n\t\t\treturn user, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"not found\")\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/Users.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage backendstest\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype UsersBackend struct {\n\tusers map[string]*User\n}\n\nfunc (ub UsersBackend) GetSettings(userID string) (settings *Settings, err error) {\n\treturn nil, errors.New(\"GetSettings test interface not implemented\")\n}\nfunc (ub UsersBackend) RetrieveUser(userID string) (user *User, err error) {\n\tif user, ok := Users[userID]; ok {\n\t\treturn user, nil\n\t}\n\treturn nil, errors.New(\"not found\")\n}\nfunc (ub UsersBackend) UpdateUserPasswordHash(user *User) error {\n\treturn errors.New(\"UpdateUserPasswordHash test interface not implemented\")\n}\nfunc (ub UsersBackend) UpdateUser(user *User, fields map[string]interface{}) error {\n\treturn errors.New(\"UpdateUser test interface not implemented\")\n}\nfunc (ub UsersBackend) UserByRecoveryEmail(email string) (user *User, err error) {\n\treturn nil, errors.New(\"UserByRecoveryEmail test interface not implemented\")\n}\nfunc (ub UsersBackend) DeleteUser(userID string) error {\n\treturn errors.New(\"DeleteUser test interface not implemented\")\n}\nfunc (ub UsersBackend) GetShardForUser(userID string) string {\n\tif user, ok := Users[userID]; ok {\n\t\treturn user.ShardId\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/backendstest/testdata.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// Package backendstest provides utilities and interfaces for mocking backends interfaces\npackage backendstest\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/satori/go.uuid\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst (\n\tDevIdoireUserId = \"ede04443-b60f-4869-9040-20bd6b1e33c1\"\n\tEmmaTommeUserId = \"7f8329c4-e220-45fc-89b2-d8535df69e83\"\n\tJeanThubUserId  = \"b26908c5-c32a-4301-8f79-80abf0d8f8fe\"\n)\n\nvar (\n\tUsers = map[string]*User{\n\t\tEmmaTommeUserId: {\n\t\t\tContactId:     UUID(uuid.FromStringOrNil(\"63ab7904-c416-4f1a-9652-3de82e4fd1f1\")),\n\t\t\tFamilyName:    \"Tomme\",\n\t\t\tGivenName:     \"Emma\",\n\t\t\tName:          \"emma\",\n\t\t\tRecoveryEmail: \"emma@recovery-caliopen.local\",\n\t\t\tShardId:       \"4faae137-5938-42d3-bf1a-8e1a4e1868e1\",\n\t\t\tUserId:        UUID(uuid.FromStringOrNil(EmmaTommeUserId)),\n\t\t},\n\t\tDevIdoireUserId: {\n\t\t\tContactId:     UUID(uuid.FromStringOrNil(\"5f0baee8-1278-43eb-9931-01b7383b419b\")),\n\t\t\tFamilyName:    \"Idoire\",\n\t\t\tGivenName:     \"Dev\",\n\t\t\tName:          \"dev\",\n\t\t\tRecoveryEmail: \"dev@recovery-caliopen.local\",\n\t\t\tShardId:       \"4faae137-5938-42d3-bf1a-8e1a4e1868e1\",\n\t\t\tUserId:        UUID(uuid.FromStringOrNil(DevIdoireUserId)),\n\t\t},\n\t}\n\n\tLocalIdentities = map[string]*UserIdentity{\n\t\tDevIdoireUserId + \"3fc38dde-1f11-42c0-a489-361d13caebac\": {\n\t\t\tDisplayName: \"Dev Idoire\",\n\t\t\tId:          UUID(uuid.FromStringOrNil(\"3fc38dde-1f11-42c0-a489-361d13caebac\")),\n\t\t\tIdentifier:  \"idoire@caliopen.local\",\n\t\t\tInfos: map[string]string{\n\t\t\t\t\"lastseenuid\":  \"\",\n\t\t\t\t\"lastsync\":     \"\",\n\t\t\t\t\"inserver\":     \"\",\n\t\t\t\t\"outserver\":    \"\",\n\t\t\t\t\"uidvalidity\":  \"\",\n\t\t\t\t\"pollinterval\": \"15\",\n\t\t\t},\n\t\t\tLastCheck: time.Now(),\n\t\t\tProtocol:  \"email\",\n\t\t\tStatus:    \"active\",\n\t\t\tType:      \"local\",\n\t\t\tUserId:    UUID(uuid.FromStringOrNil(DevIdoireUserId)),\n\t\t},\n\t\tEmmaTommeUserId + \"cd1e6f68-163b-4fe6-8107-f10d140d3c35\": {\n\t\t\tDisplayName: \"Emma Tomme\",\n\t\t\tId:          UUID(uuid.FromStringOrNil(\"cd1e6f68-163b-4fe6-8107-f10d140d3c35\")),\n\t\t\tIdentifier:  \"emmatomme@caliopen.local\",\n\t\t\tInfos: map[string]string{\n\t\t\t\t\"lastseenuid\":  \"\",\n\t\t\t\t\"lastsync\":     \"\",\n\t\t\t\t\"inserver\":     \"\",\n\t\t\t\t\"outserver\":    \"\",\n\t\t\t\t\"uidvalidity\":  \"\",\n\t\t\t\t\"pollinterval\": \"15\",\n\t\t\t},\n\t\t\tLastCheck: time.Now(),\n\t\t\tProtocol:  \"email\",\n\t\t\tStatus:    \"active\",\n\t\t\tType:      \"local\",\n\t\t\tUserId:    UUID(uuid.FromStringOrNil(EmmaTommeUserId)),\n\t\t},\n\t\tJeanThubUserId + \"6817de1c-0cc5-4964-8c47-58699cf783f7\": {\n\t\t\tDisplayName: \"Jean Thube\",\n\t\t\tId:          UUID(uuid.FromStringOrNil(\"6817de1c-0cc5-4964-8c47-58699cf783f7\")),\n\t\t\tIdentifier:  \"jeanthube@caliopen.local\",\n\t\t\tInfos: map[string]string{\n\t\t\t\t\"lastseenuid\":  \"\",\n\t\t\t\t\"lastsync\":     \"\",\n\t\t\t\t\"inserver\":     \"\",\n\t\t\t\t\"outserver\":    \"\",\n\t\t\t\t\"uidvalidity\":  \"\",\n\t\t\t\t\"pollinterval\": \"15\",\n\t\t\t},\n\t\t\tLastCheck: time.Now(),\n\t\t\tProtocol:  \"email\",\n\t\t\tStatus:    \"active\",\n\t\t\tType:      \"local\",\n\t\t\tUserId:    UUID(uuid.FromStringOrNil(JeanThubUserId)),\n\t\t},\n\t}\n\n\tRemoteIdentities = map[string]*UserIdentity{\n\t\tDevIdoireUserId + \"7e356efb-d24c-493a-b558-e58c7ad20ac3\": {\n\t\t\tDisplayName: \"Dev Idoire email remote\",\n\t\t\tId:          UUID(uuid.FromStringOrNil(\"7e356efb-d24c-493a-b558-e58c7ad20ac3\")),\n\t\t\tIdentifier:  \"idoire@remote.server\",\n\t\t\tInfos: map[string]string{\n\t\t\t\t\"lastseenuid\":  \"\",\n\t\t\t\t\"lastsync\":     \"\",\n\t\t\t\t\"inserver\":     \"\",\n\t\t\t\t\"outserver\":    \"\",\n\t\t\t\t\"uidvalidity\":  \"\",\n\t\t\t\t\"pollinterval\": \"\",\n\t\t\t},\n\t\t\tLastCheck: time.Now(),\n\t\t\tProtocol:  \"email\",\n\t\t\tStatus:    \"active\",\n\t\t\tType:      \"remote\",\n\t\t\tUserId:    UUID(uuid.FromStringOrNil(DevIdoireUserId)),\n\t\t},\n\t\tEmmaTommeUserId + \"7e4eb26d-1b70-4bb3-b556-6c54f046e88e\": {\n\t\t\tCredentials: &Credentials{\n\t\t\t\t\"oauh2refreshtoken\": \"1/MiibIppEIP0LtCxLpbdseVGNYrIqp0JtMwppeRMgbM5\",                                                                                     // fake token\n\t\t\t\t\"oauth2accesstoken\": \"bu32.sLujBrJwWQAoXJ4QMqdAgEOjiBfXu104dgI3fRDBY0bd-KuKeI1f4orAtMoMeBFFf1aJD6F9SEYo2p0hFWSOieyo-ASEqrJ38T4booBAIuWdV2sSMFw8n2bjNasa\", // fake token\n\t\t\t\t\"tokenexpiry\":       \"2019-02-01T16:42:29+01:00\",\n\t\t\t\t\"tokentype\":         \"Bearer\",\n\t\t\t\t\"username\":          \"emmatomme@gmail.com\"},\n\t\t\tDisplayName: \"Emma Tomme gmail\",\n\t\t\tId:          UUID(uuid.FromStringOrNil(\"7e4eb26d-1b70-4bb3-b556-6c54f046e88e\")),\n\t\t\tIdentifier:  \"emmatomme@gmail.com\",\n\t\t\tInfos: map[string]string{\n\t\t\t\t\"lastseenuid\":  \"\",\n\t\t\t\t\"lastsync\":     \"\",\n\t\t\t\t\"inserver\":     \"\",\n\t\t\t\t\"outserver\":    \"\",\n\t\t\t\t\"uidvalidity\":  \"\",\n\t\t\t\t\"pollinterval\": \"5\",\n\t\t\t},\n\t\t\tLastCheck: time.Now(),\n\t\t\tProtocol:  \"email\",\n\t\t\tStatus:    \"active\",\n\t\t\tType:      \"remote\",\n\t\t\tUserId:    UUID(uuid.FromStringOrNil(EmmaTommeUserId)),\n\t\t},\n\t\tEmmaTommeUserId + \"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\": {\n\t\t\tCredentials: &Credentials{\n\t\t\t\t\"secret\": \"b65ebjh9ACNFlhYwCByl0PEEAyU3wtNqOapplEwWuUEBl\",      // fake secret\n\t\t\t\t\"token\":  \"8977654370-cooB2ALLcP4OaejKk7g4lstpommuapp3Kki3dIU\", // fake token\n\t\t\t},\n\t\t\tDisplayName: \"Emma Tomme twitter remote\",\n\t\t\tId:          UUID(uuid.FromStringOrNil(\"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\")),\n\t\t\tIdentifier:  \"emmatomme\",\n\t\t\tInfos: map[string]string{\n\t\t\t\t\"lastseenuid\":  \"\",\n\t\t\t\t\"lastsync\":     \"\",\n\t\t\t\t\"inserver\":     \"\",\n\t\t\t\t\"outserver\":    \"\",\n\t\t\t\t\"uidvalidity\":  \"\",\n\t\t\t\t\"pollinterval\": \"5\",\n\t\t\t\t\"twitterid\":    \"000000\",\n\t\t\t},\n\t\t\tLastCheck: time.Now(),\n\t\t\tProtocol:  \"twitter\",\n\t\t\tStatus:    \"active\",\n\t\t\tType:      \"remote\",\n\t\t\tUserId:    UUID(uuid.FromStringOrNil(EmmaTommeUserId)),\n\t\t},\n\t\tJeanThubUserId + \"a87b1a18-23e7-6744-8a5a-3ee71ee5c001\": {\n\t\t\tDisplayName: \"Jean Thube inactive remote\",\n\t\t\tId:          UUID(uuid.FromStringOrNil(\"a87b1a18-23e7-6744-8a5a-3ee71ee5c001\")),\n\t\t\tIdentifier:  \"jeanthube\",\n\t\t\tInfos: map[string]string{\n\t\t\t\t\"lastseenuid\":  \"\",\n\t\t\t\t\"lastsync\":     \"\",\n\t\t\t\t\"inserver\":     \"\",\n\t\t\t\t\"outserver\":    \"\",\n\t\t\t\t\"uidvalidity\":  \"\",\n\t\t\t\t\"pollinterval\": \"5\",\n\t\t\t},\n\t\t\tLastCheck: time.Now(),\n\t\t\tProtocol:  \"twitter\",\n\t\t\tStatus:    \"inactive\",\n\t\t\tType:      \"remote\",\n\t\t\tUserId:    UUID(uuid.FromStringOrNil(JeanThubUserId)),\n\t\t},\n\t}\n\n\tContacts = map[string]*Contact{\n\t\tDevIdoireUserId + \"5f0baee8-1278-43eb-9931-01b7383b419b\": {\n\t\t\tContactId: UUID(uuid.FromStringOrNil(\"5f0baee8-1278-43eb-9931-01b7383b419b\")),\n\t\t\tEmails: []EmailContact{\n\t\t\t\t{\n\t\t\t\t\tAddress:   \"dev@recovery-caliopen.local\",\n\t\t\t\t\tEmailId:   UUID(uuid.FromStringOrNil(\"64f782ef-721a-487a-a68a-d0a37707d463\")),\n\t\t\t\t\tIsPrimary: false,\n\t\t\t\t\tLabel:     \"dev@recovery-caliopen.local\",\n\t\t\t\t\tType:      \"other\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tFamilyName: \"Idoire\",\n\t\t\tGivenName:  \"Dev\",\n\t\t\tTitle:      \"Dev Idoire\",\n\t\t\tUserId:     UUID(uuid.FromStringOrNil(DevIdoireUserId)),\n\t\t},\n\t\tEmmaTommeUserId + \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\": {\n\t\t\tContactId: UUID(uuid.FromStringOrNil(\"63ab7904-c416-4f1a-9652-3de82e4fd1f1\")),\n\t\t\tEmails: []EmailContact{\n\t\t\t\t{\n\t\t\t\t\tAddress:   \"emma@recovery-caliopen.local\",\n\t\t\t\t\tEmailId:   UUID(uuid.FromStringOrNil(\"444d71f6-324c-4733-88a2-77ca28ea6d2d\")),\n\t\t\t\t\tIsPrimary: false,\n\t\t\t\t\tLabel:     \"emma@recovery-caliopen.local\",\n\t\t\t\t\tType:      \"other\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tIdentities: []SocialIdentity{\n\t\t\t\t{\n\t\t\t\t\tName: \"emmatoclite\",\n\t\t\t\t\tType: \"twitter\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tFamilyName: \"Tomme\",\n\t\t\tGivenName:  \"Emma\",\n\t\t\tTitle:      \"Emma Tomme\",\n\t\t\tUserId:     UUID(uuid.FromStringOrNil(EmmaTommeUserId)),\n\t\t},\n\t}\n\n\tMsgs = map[string]*Message{\n\t\tEmmaTommeUserId + \"b26e5ba4-34cc-42bb-9b70-5279648134f8\": {\n\t\t\tBody_plain:     \"email's body plain\",\n\t\t\tIs_draft:       false,\n\t\t\tMessage_id:     UUID(uuid.FromStringOrNil(\"b26e5ba4-34cc-42bb-9b70-5279648134f8\")),\n\t\t\tRaw_msg_id:     UUID(uuid.FromStringOrNil(\"70beae6e-d96e-456e-9d78-7c13f00f0edd\")),\n\t\t\tSubject:        \"Sent email message with external identity\",\n\t\t\tUser_id:        UUID(uuid.FromStringOrNil(EmmaTommeUserId)),\n\t\t\tUserIdentities: []UUID{UUID(uuid.FromStringOrNil(\"7e4eb26d-1b70-4bb3-b556-6c54f046e88e\"))},\n\t\t},\n\t}\n\n\tDevices = map[string]*Device{\n\t\tEmmaTommeUserId + \"b8c11acd-a90d-467f-90f7-21b6b615149d\": {\n\t\t\tLocker:   new(sync.Mutex),\n\t\t\tDeviceId: UUID(uuid.FromStringOrNil(\"b8c11acd-a90d-467f-90f7-21b6b615149d\")),\n\t\t\tName:     \"fake device for tests\",\n\t\t\tStatus:   DeviceUnverifiedStatus,\n\t\t\tType:     DeviceLaptopType,\n\t\t\tUserId:   UUID(uuid.FromStringOrNil(EmmaTommeUserId)),\n\t\t},\n\t}\n\n\t// used to return contact by uris (kind + \":\" + address)\n\tContactLookup = map[string]string{\n\t\t\"email:dev@recovery-caliopen.local\":  \"5f0baee8-1278-43eb-9931-01b7383b419b\",\n\t\t\"email:emma@recovery-caliopen.local\": \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\",\n\t\t\"twitter:emmatomme\":                  \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\",\n\t}\n)\n"
  },
  {
    "path": "src/backend/main/go.backends/cache/authentication.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cache\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"strings\"\n)\n\n// GetAuthToken retrieves auth values stored for the given key\n// values are casted into an Auth_cache struct\n// key is in the form of \"tokens::user_id\"\nfunc (c *Cache) GetAuthToken(key string) (value *Auth_cache, err error) {\n\tvalue = &Auth_cache{}\n\tcache_str, err := c.Backend.Get(key)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[GetAuthToken] failed to get cache key %s\", key)\n\t\treturn nil, err\n\t}\n\n\terr = json.Unmarshal(cache_str, value)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[GetAuthToken] failed to unmarshal cache %s for key %s\", cache_str, key)\n\t\treturn nil, err\n\t}\n\treturn\n}\n\n// LogoutUser will delete the entry of the user corresponding to the key\nfunc (c *Cache) LogoutUser(key string) error {\n\tif !strings.HasPrefix(key, \"tokens::\") {\n\t\treturn errors.New(\"Unvalid key\")\n\t}\n\terr := c.Backend.Del(key)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[LogoutUser] failed to delete key %s\", key)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/cache/cache.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cache\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n)\n\ntype Cache struct {\n\t// il faut passer ça partout et juste set le backend en un redis backend\n\t// comme ça je pourrai créé un cachebackend avec un autre underlying backend\n\tCacheConfig\n\tBackend backends.CacheBackend\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/cache/devicevalidation.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cache\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"gopkg.in/redis.v5\"\n\t\"time\"\n)\n\nconst (\n\tvalidationPrefix    = \"validationsession::\"\n\tdeviceValidationTTL = 24 // ttl in hours\n)\n\nfunc (c *Cache) GetDeviceValidationSession(userId, deviceId string) (session *TokenSession, err error) {\n\treturn c.getValidationSession(validationPrefix + userId + \"::\" + deviceId)\n}\n\nfunc (c *Cache) GetTokenValidationSession(userId, token string) (session *TokenSession, err error) {\n\treturn c.getValidationSession(validationPrefix + userId + \"::\" + token)\n}\n\nfunc (c *Cache) getValidationSession(key string) (session *TokenSession, err error) {\n\tsession_str, err := c.Backend.Get(key)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[getValidationSession] failed to get key %s\", key)\n\t\treturn nil, err\n\t}\n\n\tsession = &TokenSession{}\n\terr = json.Unmarshal(session_str, session)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[getValidationSession] failed to unmarshal value %s for key %s\", session_str, key)\n\t\treturn nil, err\n\t}\n\treturn\n}\n\n// SetDeviceValidationSession sets two keys in cache facility\n// - one to retrieve session by device id\n// - one to retrieve session by token\nfunc (c *Cache) SetDeviceValidationSession(userId, deviceId, token string) (session *TokenSession, err error) {\n\tttl := deviceValidationTTL * time.Hour\n\texpiration := time.Now().Add(ttl)\n\tsession = &TokenSession{\n\t\tExpiresAt:  expiration,\n\t\tExpiresIn:  int(ttl / time.Second),\n\t\tToken:      token,\n\t\tUserId:     userId,\n\t\tResourceId: deviceId,\n\t}\n\tsession_str, err := json.Marshal(session)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[SetDeviceValidationSession] failed to marshal session %+v\", *session)\n\t\treturn nil, err\n\t}\n\tprefix := validationPrefix + userId + \"::\"\n\tdeviceKey := prefix + deviceId\n\ttokenKey := prefix + token\n\n\terr = c.Backend.Set(deviceKey, session_str, ttl)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[SetDeviceValidationSession] failed to set session key in cache for user %s, deviceId %s\", userId, deviceId)\n\t\treturn nil, err\n\t}\n\n\terr = c.Backend.Set(tokenKey, session_str, ttl)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[SetDeviceValidationSession] failed to set session key in cache for user %s, token %s\", userId, token)\n\t\t_ = c.Backend.Del(deviceKey)\n\t\treturn nil, err\n\t}\n\n\treturn session, nil\n}\n\n// DeleteDeviceValidationSession deletes the two keys associated with a device validation session\nfunc (c *Cache) DeleteDeviceValidationSession(userId, deviceId string) error {\n\n\tsession, _ := c.GetDeviceValidationSession(userId, deviceId)\n\tif session == nil {\n\t\tlog.Errorf(\"[DeleteDeviceValidationSession] failed to retrieve session for user %s, device %s\", userId, deviceId)\n\t\treturn errors.New(\"not found\")\n\t}\n\n\tprefix := validationPrefix + userId + \"::\"\n\tdeviceKey := prefix + deviceId\n\ttokenKey := prefix + session.Token\n\n\terr := c.Backend.Del(deviceKey)\n\tif err != nil && err != redis.Nil {\n\t\tlog.WithError(err).Errorf(\"[DeleteDeviceValidationSession] failed to delete device validation session for user %s, device %s\", userId, deviceId)\n\t}\n\terr = c.Backend.Del(tokenKey)\n\tif err != nil && err != redis.Nil {\n\t\tlog.WithError(err).Errorf(\"[DeleteDeviceValidationSession] failed to delete device validation session for user %s, token %s\", userId, session.Token)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/cache/devicevalidation_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cache\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestRedisBackend_SetDeviceValidationSession(t *testing.T) {\n\tmockCache, mockRedis, err := InitializeTestCache()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tsession, err := mockCache.SetDeviceValidationSession(\"user_id\", \"device_id\", \"token\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\t// check that TokenSession is well-formed\n\tttl := int((deviceValidationTTL * time.Hour) / time.Second)\n\tif session.ExpiresIn != ttl {\n\t\tt.Errorf(\"expected TokenSession.ExpiresIn = %d, got %d\", ttl, session.ExpiresIn)\n\t}\n\tif session.Token != \"token\" {\n\t\tt.Errorf(\"expected TokenSession.Token = token, got %s\", session.Token)\n\t}\n\tif session.UserId != \"user_id\" {\n\t\tt.Errorf(\"expected TokenSession.UserId = user_id, got %s\", session.UserId)\n\t}\n\tif session.ResourceId != \"device_id\" {\n\t\tt.Errorf(\"expected TokenSession.ResourceId = device_id, got ResourceId = %s\", session.ResourceId)\n\t}\n\n\t// check that two keys with correct TTL have been effectively put in cache\n\tdeviceValue, err := mockCache.Backend.Get(validationPrefix + \"user_id::device_id\")\n\tif err != nil {\n\t\tt.Errorf(\"failed to retrieve deviceKey : %s\", err)\n\t}\n\ttokenValue, err := mockCache.Backend.Get(validationPrefix + \"user_id::token\")\n\tif err != nil {\n\t\tt.Errorf(\"failed to retrieve tokenKey : %s\", err)\n\t}\n\n\tif !bytes.Equal(deviceValue, tokenValue) {\n\t\tt.Errorf(\"expected deviceValue and tokenValue to be the same, got %s and %s\", string(deviceValue), string(tokenValue))\n\t}\n\tdeviceTTL, err := mockRedis.GetTTL(validationPrefix + \"user_id::device_id\")\n\tif err != nil {\n\t\tt.Errorf(\"failed to retrieve TTL for deviceKey : %s\", err)\n\t}\n\tif int(deviceTTL/time.Second) != ttl {\n\t\tt.Errorf(\"expected deviceTTl set to %d, got %d\", ttl, deviceTTL)\n\t}\n\ttokenTTL, err := mockRedis.GetTTL(validationPrefix + \"user_id::token\")\n\tif err != nil {\n\t\tt.Errorf(\"failed to retrieve TTL for tokenKey : %s\", err)\n\t}\n\tif int(tokenTTL/time.Second) != ttl {\n\t\tt.Errorf(\"expected tokenTTl set to %d, got %d\", ttl, tokenTTL)\n\t}\n}\n\nfunc TestRedisBackend_GetDeviceValidationSession(t *testing.T) {\n\tmockCache, _, err := InitializeTestCache()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tsessionSet, err := mockCache.SetDeviceValidationSession(\"user_id\", \"device_id\", \"token\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tsessionGot, err := mockCache.GetDeviceValidationSession(\"user_id\", \"device_id\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif sessionSet.Token != sessionGot.Token {\n\t\tt.Errorf(\"expected to retrieve a TokenSession with same Token = token, got %s\", sessionGot.Token)\n\t}\n}\n\nfunc TestRedisBackend_GetTokenValidationSession(t *testing.T) {\n\tmockCache, _, err := InitializeTestCache()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tsessionSet, err := mockCache.SetDeviceValidationSession(\"user_id\", \"device_id\", \"token\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tsessionGot, err := mockCache.GetTokenValidationSession(\"user_id\", \"device_id\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif sessionSet.Token != sessionGot.Token {\n\t\tt.Errorf(\"expected to retrieve a TokenSession with same Token = token, got %s\", sessionGot.Token)\n\t}\n}\n\nfunc TestRedisBackend_DeleteDeviceValidationSession(t *testing.T) {\n\tmockCache, mockRedis, err := InitializeTestCache()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\t_, err = mockCache.SetDeviceValidationSession(\"user_id\", \"device_id\", \"token\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\terr = mockCache.DeleteDeviceValidationSession(\"user_id\", \"device_id\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// check that both keys have been deleted\n\tvar value []byte\n\tvalue, err = mockRedis.Get(validationPrefix + \"user_id::device_id\")\n\tif err == nil || value != nil {\n\t\tt.Errorf(\"delete deviceKey failed : expected to have not found error and nil value, got err = %s and value = %s\", err, string(value))\n\t}\n\tvalue, err = mockRedis.Get(validationPrefix + \"user_id::token\")\n\tif err == nil || value != nil {\n\t\tt.Errorf(\"delete tokenKey failed : expected to have not found error and nil value, got err = %s and value = %s\", err, string(value))\n\t}\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/cache/oauthsessions.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cache\n\nimport (\n\t\"encoding/json\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"time\"\n)\n\nconst (\n\toauthSessionPrefix = \"oauthsession::\"\n\toauthSessionTTL    = 10 // ttl in minutes\n)\n\n// GetOauthSession unmarshal json found at `key`, if any, into an OauthSession struct\nfunc (c *Cache) GetOauthSession(key string) (session *OauthSession, err error) {\n\tsession_str, err := c.Backend.Get(oauthSessionPrefix + key)\n\tif err != nil || len(session_str) == 0 {\n\t\tlog.WithError(err).Errorf(\"[GetOauthSession] failed to get session with key %s\", key)\n\t\treturn nil, err\n\t}\n\n\tsession = &OauthSession{}\n\terr = json.Unmarshal(session_str, session)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[GetOauthSession] failed to unmarshal session %s for key %s\", session_str, key)\n\t\treturn nil, err\n\t}\n\treturn\n}\n\n// SetOauthSession put `OauthSession` as a json string at `key` prefixed with oauthSessionPrefix\nfunc (c *Cache) SetOauthSession(key string, session *OauthSession) (err error) {\n\tttl := oauthSessionTTL * time.Minute\n\tsession_str, err := json.Marshal(session)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[SetOauthSession] failed to marshal session.for key %s\", key)\n\t\treturn err\n\t}\n\n\terr = c.Backend.Set(oauthSessionPrefix+key, session_str, ttl)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[SetOauthSession] failed to set session for key %s\", key)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// DeleteOauthSession deletes value found at `key` prefixed with oauthSessionPrefix\nfunc (c *Cache) DeleteOauthSession(key string) error {\n\terr := c.Backend.Del(oauthSessionPrefix + key)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[DeleteOauthSession] failed to delete session for key %s\", key)\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/cache/passwordreset.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cache\n\nimport (\n\t\"encoding/json\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"time\"\n)\n\nconst (\n\tsessionPrefix    = \"resetsession::\"\n\tresetTokenPrefix = \"resettoken::\"\n\tresetPasswordTTL = 8 // ttl in hours\n)\n\n// GetResetPasswordSession returns reset password session values stored for the userId, if any\n// Returns a nil 'session' if key is not found\nfunc (c *Cache) GetResetPasswordSession(userId string) (session *TokenSession, err error) {\n\tkey := sessionPrefix + userId\n\tsession_str, err := c.Backend.Get(key)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[GetResetPasswordSession] failed to get key %s\", key)\n\t\treturn nil, err\n\t}\n\n\tsession = &TokenSession{}\n\terr = json.Unmarshal(session_str, session)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[GetResetPasswordSession] failed to unmarshal value %s for key %s\", session_str, key)\n\t\treturn nil, err\n\t}\n\treturn\n}\n\n// SetResetPasswordSession stores key,value for given userId and resetToken.\n// The key will be in the form of \"resetsession::userId\".\n// Func will also call setResetPasswordToken() to add a secondary key in the form \"resettoken::resetToken\" pointing to the same value\n// Func returns a pointer to the Pass_reset_session object that represents values stored in the cache.\n// userId and resetToken strings must be well-formatted, they will not be checked.\nfunc (c *Cache) SetResetPasswordSession(userId, resetToken string) (session *TokenSession, err error) {\n\tttl := resetPasswordTTL * time.Hour\n\texpiration := time.Now().Add(ttl)\n\tsession = &TokenSession{\n\t\tExpiresAt: expiration,\n\t\tExpiresIn: int(ttl / time.Second),\n\t\tToken:     resetToken,\n\t\tUserId:    userId,\n\t}\n\tsession_str, err := json.Marshal(session)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[SetResetPasswordSession] failed to marshal session %+v\", *session)\n\t\treturn nil, err\n\t}\n\n\terr = c.Backend.Set(sessionPrefix+userId, session_str, ttl)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[SetResetPasswordSession] failed to set session key in cache for user %s\", userId)\n\t\treturn nil, err\n\t}\n\n\terr = c.setResetPasswordToken(resetToken, session_str, ttl)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[SetResetPasswordSession] failed to setResetPasswordToken in cache for user %s\", userId)\n\t\treturn nil, err\n\t}\n\treturn session, nil\n}\n\n// SetResetPasswordToken stores key,value for given resetToken.\n// It is called by SetResetPasswordSession to add a secondary key pointing to the same underlying value.\n// The key is in the form \"resettoken::resetToken\"\nfunc (c *Cache) setResetPasswordToken(token string, session []byte, ttl time.Duration) error {\n\treturn c.Backend.Set(resetTokenPrefix+token, session, ttl)\n}\n\n// GetResetPasswordToken returns values found for the given resetToken key\nfunc (c *Cache) GetResetPasswordToken(token string) (session *TokenSession, err error) {\n\tkey := resetTokenPrefix + token\n\tsession_str, err := c.Backend.Get(key)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[GetResetPasswordToken] failed to get key for token %s\", token)\n\t\treturn nil, err\n\t}\n\n\tsession = &TokenSession{}\n\terr = json.Unmarshal(session_str, session)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[GetResetPasswordToken] failed to unmarshal session %s for token %s\", session_str, token)\n\t\treturn nil, err\n\t}\n\treturn\n}\n\n// DeleteResetPasswordSession will delete two keys in a row :\n// the resetsession key and the resettoken key\nfunc (c *Cache) DeleteResetPasswordSession(userId string) error {\n\n\tsession, err := c.GetResetPasswordSession(userId)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[DeleteResetPasswordSession] failed to get session for user %s\", userId)\n\t\treturn err\n\t}\n\n\tkey := sessionPrefix + userId\n\terr = c.Backend.Del(key)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[DeleteResetPasswordSession] failed to delete session for user %s\", userId)\n\t\treturn err\n\t}\n\n\tkey = resetTokenPrefix + session.Token\n\terr = c.Backend.Del(key)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[DeleteResetPasswordSession] failed to delete session token for user %s\", userId)\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/cache/redis.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cache\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"gopkg.in/redis.v5\"\n\t\"time\"\n)\n\ntype redisBackend struct {\n\tclient *redis.Client\n}\n\nfunc InitializeRedisBackend(config CacheConfig) (c *Cache, err error) {\n\tc = new(Cache)\n\tc.CacheConfig = config\n\n\tredisClient := redis.NewClient(&redis.Options{\n\t\tAddr:     config.Host,\n\t\tPassword: config.Password,\n\t\tDB:       config.Db,\n\t})\n\t_, err = redisClient.Ping().Result()\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[RedisBackend] initialize failed\")\n\t\treturn nil, err\n\t}\n\n\tc.Backend = &redisBackend{\n\t\tclient: redisClient,\n\t}\n\n\treturn\n}\n\nfunc (rb *redisBackend) Set(key string, value []byte, ttl time.Duration) error {\n\treturn rb.client.Set(key, value, ttl).Err()\n}\n\nfunc (rb *redisBackend) Get(key string) (value []byte, err error) {\n\treturn rb.client.Get(key).Bytes()\n}\n\nfunc (rb *redisBackend) Del(key string) error {\n\treturn rb.client.Del(key).Err()\n}\n\nfunc InitializeTestCache() (c *Cache, mock *backendstest.MockRedis, err error) {\n\tc = new(Cache)\n\tmock = &backendstest.MockRedis{\n\t\tStore: map[string][]byte{},\n\t\tTtl:   map[string]time.Duration{},\n\t}\n\tc.Backend = mock\n\treturn\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/index/elasticsearch/broad_search.go",
    "content": "package index\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/satori/go.uuid\"\n\t\"gopkg.in/olivere/elastic.v5\"\n)\n\n// Composes a full text ES query from IndexSearch object,\n// making use of \"common terms query\" (see https://www.elastic.co/guide/en/elasticsearch/reference/5.4/query-dsl-common-terms-query.html).\n// The func returns a compound response from ES to return 5 relevant docs filed by type if no doctype is provided,\n// otherwise, all docs found within type are returned.\n// See search API readme file into doc folder to see how the search func could be used by frontend.\nfunc (es *ElasticSearchBackend) Search(search IndexSearch) (result *IndexResult, err error) {\n\tconst (\n\t\tsub_agg_key = \"top_score_hits\"\n\t\tagg_key     = \"by_type\"\n\t)\n\tq := elastic.NewBoolQuery()\n\n\t// Strictly filter on user_id\n\tq = q.Filter(elastic.NewTermQuery(\"user_id\", search.User_id))\n\n\tcutoffFrequency := 0.05 //words that have a document frequency greater than xx% will be treated as common terms.\n\tfor field, value := range search.Terms {\n\t\tq = q.Should(elastic.NewCommonTermsQuery(field, value).CutoffFrequency(cutoffFrequency))\n\t\t// always add the common fields below to improve results\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"body_plain\", value).CutoffFrequency(cutoffFrequency))\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"body_plain.normalized\", value).CutoffFrequency(cutoffFrequency))\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"body_html\", value).CutoffFrequency(cutoffFrequency))\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"body_html.normalized\", value).CutoffFrequency(cutoffFrequency))\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"subject\", value).CutoffFrequency(cutoffFrequency)).Boost(2)\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"subject.normalized\", value).CutoffFrequency(cutoffFrequency)).Boost(2)\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"given_name\", value).CutoffFrequency(cutoffFrequency)).Boost(3)\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"given_name.normalized\", value).CutoffFrequency(cutoffFrequency)).Boost(3)\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"family_name\", value).CutoffFrequency(cutoffFrequency)).Boost(3)\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"family_name.normalized\", value).CutoffFrequency(cutoffFrequency)).Boost(3)\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"title.raw\", value).CutoffFrequency(cutoffFrequency)).Boost(5)\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"participants.address.raw\", value).CutoffFrequency(cutoffFrequency)).Boost(2)\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"participants.label\", value).CutoffFrequency(cutoffFrequency)).Boost(2)\n\t\tq = q.Should(elastic.NewCommonTermsQuery(\"emails.address.raw\", value).CutoffFrequency(cutoffFrequency)).Boost(2)\n\t}\n\n\t// make aggregation to file docs by type:\n\t// get only the 5 most relevant doc for each type if search.DocType is empty\n\t// otherwise get docs according to limit & offset params.\n\tvar s *elastic.SearchService\n\tswitch search.DocType {\n\tcase \"\":\n\t\t// no doctype provided. Trigger search on all document types within index and build an aggregation\n\t\t/*highlight disabled*/                                                //h := elastic.NewHighlight().Fields(elastic.NewHighlighterField(\"*\").RequireFieldMatch(false))\n\t\ttop_hits := elastic.NewTopHitsAggregation().Size(5).FetchSource(true) //.Highlight(h)\n\t\tby_type := elastic.NewTermsAggregation().Field(\"_type\").SubAggregation(sub_agg_key, top_hits)\n\t\t//TODO/WIP\n\t\t/*iq := elastic.NewIndicesQuery(elastic.NewRangeQuery(\"importance_level\").Gte(search.ILrange[0]).Lte(search.ILrange[1]), MessageIndexType)\n\t\tmsg_hits := elastic.NewFilterAggregation().Filter(iq)\n\t\t*/\n\t\ts = es.Client.Search().Index(search.Shard_id).Query(q).FetchSource(false).Aggregation(agg_key, by_type) //.Highlight(h)\n\tcase MessageIndexType:\n\t\t// The search focuses on message document type, no aggregation needed, but importance level apply\n\t\t/*highlight disabled*/ //h := elastic.NewHighlight().Fields(elastic.NewHighlighterField(\"*\").RequireFieldMatch(false))\n\t\trq := elastic.NewRangeQuery(\"importance_level\").Gte(search.ILrange[0]).Lte(search.ILrange[1])\n\t\ts = es.Client.Search().Index(search.Shard_id).Query(q).FetchSource(true). /*.Highlight(h)*/ PostFilter(rq)\n\tcase ContactIndexType:\n\t\t// The search focuses on contact document type, no aggregation needed and importance level not taken into account\n\t\t/*highlight disabled*/                                                   //h := elastic.NewHighlight().Fields(elastic.NewHighlighterField(\"*\").RequireFieldMatch(false))\n\t\ts = es.Client.Search().Index(search.Shard_id).Query(q).FetchSource(true) //.Highlight(h)\n\t}\n\n\t//prepare search\n\t// add type, from & size params only if type is not empty\n\tif search.DocType != \"\" {\n\t\ts = s.Type(search.DocType)\n\t\tif search.Offset > 0 {\n\t\t\ts = s.From(search.Offset)\n\t\t}\n\t\tif search.Limit > 0 {\n\t\t\ts = s.Size(search.Limit)\n\t\t}\n\t}\n\t/** log the full json query to help development\n\tsource, _ := q.Source()\n\tjson_query, _ := json.Marshal(source)\n\tlog.Infof(\"\\nES query source: %s\\n\", json_query)\n\n\t/** end of log **/\n\t// execute the search\n\ts.MinScore(0.1)\n\tresponse, err := s.Do(context.TODO())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// build IndexResult from ES response\n\tresult = &IndexResult{\n\t\tTotal:        response.TotalHits(),\n\t\tMessagesHits: MessageHits{0, []*IndexHit{}},\n\t\tContactsHits: ContactHits{0, []*IndexHit{}},\n\t}\n\tif search.DocType != \"\" {\n\t\t// no aggregation, thus elastic returns a parsed json\n\t\tswitch search.DocType {\n\t\tcase MessageIndexType:\n\t\t\tresult.MessagesHits.Total = response.TotalHits()\n\t\t\tfor _, hit := range response.Hits.Hits {\n\t\t\t\tmsg := new(Message)\n\t\t\t\tif err := json.Unmarshal(*hit.Source, msg); err != nil {\n\t\t\t\t\tlog.Info(err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tmsg_id, _ := uuid.FromString(hit.Id)\n\t\t\t\tmsg.Message_id.UnmarshalBinary(msg_id.Bytes())\n\n\t\t\t\tmsg_hit := new(IndexHit)\n\t\t\t\tmsg_hit.Id = msg.Message_id\n\t\t\t\tmsg_hit.Score = *hit.Score\n\t\t\t\tmsg_hit.Highlights = hit.Highlight\n\t\t\t\tmsg_hit.Document = msg\n\t\t\t\t(*result).MessagesHits.Messages = append((*result).MessagesHits.Messages, msg_hit)\n\t\t\t}\n\t\tcase ContactIndexType:\n\t\t\tresult.ContactsHits.Total = response.TotalHits()\n\t\t\tfor _, hit := range response.Hits.Hits {\n\t\t\t\tcontact := new(Contact)\n\t\t\t\tif err := json.Unmarshal(*hit.Source, contact); err != nil {\n\t\t\t\t\tlog.Info(err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcontact_id, _ := uuid.FromString(hit.Id)\n\t\t\t\tcontact.ContactId.UnmarshalBinary(contact_id.Bytes())\n\n\t\t\t\tcontact_hit := new(IndexHit)\n\t\t\t\tcontact_hit.Id = contact.ContactId\n\t\t\t\tcontact_hit.Score = *hit.Score\n\t\t\t\tcontact_hit.Highlights = hit.Highlight\n\t\t\t\tcontact_hit.Document = contact\n\t\t\t\t(*result).ContactsHits.Contacts = append((*result).ContactsHits.Contacts, contact_hit)\n\t\t\t}\n\t\t}\n\n\t} else {\n\t\t// elastic returns buckets aggregation as a raw []byte, need to unmarshal\n\t\tby_types, _ := response.Aggregations[agg_key] // by_types is a *json.RawMessage\n\t\tvar agg map[string]interface{}\n\t\terr = json.Unmarshal(*by_types, &agg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbuckets := agg[\"buckets\"].([]interface{})\n\t\tfor _, b := range buckets {\n\t\t\tbucket := b.(map[string]interface{})\n\t\t\ttotal, _ := bucket[\"doc_count\"].(float64)\n\t\t\tswitch bucket[\"key\"].(string) {\n\t\t\tcase MessageIndexType:\n\t\t\t\t(*result).MessagesHits.Total = int64(total)\n\t\t\tcase ContactIndexType:\n\t\t\t\t(*result).ContactsHits.Total = int64(total)\n\t\t\t}\n\n\t\t\t//go deeper within map to get documents and unmarshal them to our objects\n\t\t\ttop_score_hits, _ := bucket[sub_agg_key].(map[string]interface{})\n\t\t\thits, _ := top_score_hits[\"hits\"].(map[string]interface{})\n\t\t\thits_hits, _ := hits[\"hits\"].([]interface{})\n\n\t\t\tfor _, hh := range hits_hits {\n\t\t\t\thit := hh.(map[string]interface{})\n\t\t\t\th := new(IndexHit)\n\t\t\t\tid, _ := hit[\"_id\"].(string)\n\t\t\t\tuid, _ := uuid.FromString(id)\n\t\t\t\th.Id.UnmarshalBinary(uid.Bytes())\n\t\t\t\th.Score, _ = hit[\"_score\"].(float64)\n\t\t\t\thighlights, _ := hit[\"highlight\"].(map[string]interface{})\n\t\t\t\th.Highlights = map[string][]string{}\n\t\t\t\tfor key, value := range highlights {\n\t\t\t\t\tfor _, highlight := range value.([]interface{}) {\n\t\t\t\t\t\th.Highlights[key] = append(h.Highlights[key], highlight.(string))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tswitch bucket[\"key\"].(string) {\n\t\t\t\tcase MessageIndexType:\n\t\t\t\t\tmsg_map, _ := hit[\"_source\"].(map[string]interface{})\n\t\t\t\t\tmsg := new(Message)\n\t\t\t\t\tif err := msg.UnmarshalMap(msg_map); err == nil {\n\t\t\t\t\t\tmsg.User_id = search.User_id\n\t\t\t\t\t\tmsg.Message_id = h.Id\n\t\t\t\t\t\th.Document = msg\n\t\t\t\t\t\t(*result).MessagesHits.Messages = append(result.MessagesHits.Messages, h)\n\t\t\t\t\t}\n\t\t\t\tcase ContactIndexType:\n\t\t\t\t\tcontact_map, _ := hit[\"_source\"].(map[string]interface{})\n\t\t\t\t\tcontact := new(Contact)\n\t\t\t\t\tif err := contact.UnmarshalMap(contact_map); err == nil {\n\t\t\t\t\t\tcontact.UserId = search.User_id\n\t\t\t\t\t\tcontact.ContactId = h.Id\n\t\t\t\t\t\th.Document = contact\n\t\t\t\t\t\t(*result).ContactsHits.Contacts = append(result.ContactsHits.Contacts, h)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/index/elasticsearch/contacts.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage index\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/satori/go.uuid\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"strings\"\n)\n\nfunc (es *ElasticSearchBackend) CreateContact(user *UserInfo, contact *Contact) error {\n\tes_contact, err := contact.MarshalES()\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[ElasticSearchBackend] failed to parse contact to json : %s\", string(es_contact))\n\t\treturn err\n\t}\n\tresp, err := es.Client.Index().Index(user.Shard_id).Type(ContactIndexType).Id(contact.ContactId.String()).\n\t\tBodyString(string(es_contact)).\n\t\tRefresh(\"wait_for\").\n\t\tDo(context.TODO())\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[ElasticSearchBackend] CreateContact failed for user %s and contact %s\", contact.UserId.String(), contact.ContactId.String())\n\t\treturn err\n\t}\n\tlog.Infof(\"New contact indexed with id %s\", resp.Id)\n\treturn nil\n}\n\nfunc (es *ElasticSearchBackend) UpdateContact(user *UserInfo, contact *Contact, fields map[string]interface{}) error {\n\n\t//get json field name for each field to modify\n\tjsonFields := map[string]interface{}{}\n\tfor field, value := range fields {\n\t\tjsonField, err := reflections.GetFieldTag(contact, field, \"json\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"[ElasticSearchBackend] UpdateContact failed to find a json field for object field %s\", field)\n\t\t}\n\t\tsplit := strings.Split(jsonField, \",\")\n\t\tjsonFields[split[0]] = value\n\t}\n\tupdate, err := es.Client.Update().Index(user.Shard_id).Type(ContactIndexType).Id(contact.ContactId.String()).\n\t\tDoc(jsonFields).\n\t\tRefresh(\"wait_for\").\n\t\tDo(context.TODO())\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[ElasticSearchBackend] updateContact operation failed\")\n\t\treturn err\n\t}\n\tlog.Infof(\"New version of indexed contact %s is now %d\", update.Id, update.Version)\n\treturn nil\n}\n\nfunc (es *ElasticSearchBackend) DeleteContact(user *UserInfo, contact *Contact) error {\n\t_, err := es.Client.Delete().Index(user.Shard_id).Type(ContactIndexType).Id(contact.ContactId.String()).Refresh(\"wait_for\").Do(context.TODO())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (es *ElasticSearchBackend) SetContactUnread(user_id, Contact_id string, status bool) (err error) {\n\treturn errors.New(\"[ElasticSearchBackend] not implemented\")\n}\n\nfunc (es *ElasticSearchBackend) FilterContacts(filter IndexSearch) (contacts []*Contact, totalFound int64, err error) {\n\tsearch := es.Client.Search().Index(filter.Shard_id).Type(ContactIndexType)\n\tsearch = filter.FilterQuery(search, false).Sort(\"title.raw\", true)\n\n\tif filter.Offset > 0 {\n\t\tsearch = search.From(filter.Offset)\n\t}\n\tif filter.Limit > 0 {\n\t\tsearch = search.Size(filter.Limit)\n\t}\n\n\tresult, err := search.Do(context.TODO())\n\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tfor _, hit := range result.Hits.Hits {\n\t\tcontact := new(Contact).NewEmpty().(*Contact)\n\t\tif err := json.Unmarshal(*hit.Source, contact); err != nil {\n\t\t\tlog.Info(err)\n\t\t\tcontinue\n\t\t}\n\t\tcontact_id, _ := uuid.FromString(hit.Id)\n\t\tcontact.ContactId.UnmarshalBinary(contact_id.Bytes())\n\t\tcontact.UserId = filter.User_id\n\t\tcontacts = append(contacts, contact)\n\t}\n\ttotalFound = result.TotalHits()\n\treturn\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/index/elasticsearch/discussions.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage index\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/messages\"\n\tuuid \"github.com/satori/go.uuid\"\n\t\"gopkg.in/olivere/elastic.v5\"\n)\n\n// GetDiscussionList returns all the discussion_id found in index for an user\n// aggregated by discussion_id, with metadata.\nfunc (es *ElasticSearchBackend) GetDiscussionsList(filter IndexSearch, withIL bool) (discussions []Discussion, err error) {\n\tdiscussions = []Discussion{}\n\n\tsearch := es.Client.Search().Index(filter.Shard_id).Type(MessageIndexType)\n\tsearch = filter.FilterQuery(search, withIL)\n\tmsgSource := elastic.NewFetchSourceContext(true)\n\tmsgSource.Include(\"date_sort\", \"subject\", \"participants\", \"body_plain\", \"body_html\", \"importance_level\")\n\tsearch.Aggregation(\"by_uris\", elastic.NewTermsAggregation().Field(\"discussion_id\").Size(10e3). // need to fetch all buckets to have an accurate buckets count\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tSubAggregation(\"sub_bucket\", elastic.NewTopHitsAggregation().Sort(\"date_sort\", false).Size(1).FetchSourceContext(msgSource)).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tSubAggregation(\"unread_count\", elastic.NewFilterAggregation().Filter(elastic.NewTermQuery(\"is_unread\", true))).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tSubAggregation(\"importance_level\", elastic.NewTermsAggregation().Field(\"importance_level\").Size(100).OrderByTermDesc()))\n\t// TODO : attachment count aggr\n\n\tvar result *elastic.SearchResult\n\tresult, err = search.Do(context.TODO())\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tagg := result.Aggregations[\"by_uris\"]\n\tvar byUris Aggregation\n\tjson.Unmarshal([]byte(*agg), &byUris)\n\n\tif len(byUris.Buckets) == 0 {\n\t\treturn\n\t}\n\n\t/*\n\t\tbyUris' Bucket.SubBucket :\n\t\t\tmap[hits:\n\t\t\t\tmap[hits:\n\t\t\t\t [map[\n\t\t\t\t\t_id:xxxx\n\t\t\t\t\t_index:xxxx\n\t\t\t\t\t_score:<nil>\n\t\t\t\t\t_source:map[date_sort:xx participants:[xxxx] subject:xxx]\n\t*/\n\n\t// build discussions' array from raw bytes result\n\tfor _, hit := range byUris.Buckets {\n\t\tmsg := &Message{}\n\t\tvar subBucket map[string]interface{}\n\t\tjson.Unmarshal(hit.SubBucket, &subBucket)\n\t\tvar subHit = subBucket[\"hits\"].(map[string]interface{})[\"hits\"].([]interface{})[0].(map[string]interface{})\n\n\t\tmsg.UnmarshalMap(subHit[\"_source\"].(map[string]interface{}))\n\t\t// flatten importance levels distribution to an array of float64 for later computation\n\t\tvar importanceAgg Aggregation\n\t\tjson.Unmarshal(hit.ImportanceLevel, &importanceAgg)\n\t\tmessagesIL := []float64{}\n\t\tfor _, bucket := range importanceAgg.Buckets {\n\t\t\tfor i := 0; i < bucket.DocCount; i++ {\n\t\t\t\tmessagesIL = append(messagesIL, float64(bucket.Key.(float64)))\n\t\t\t}\n\t\t}\n\n\t\tdiscussions = append(discussions, Discussion{\n\t\t\tDateUpdate:         msg.Date_sort,\n\t\t\tDiscussionId:       hit.Key.(string),\n\t\t\tExcerpt:            messages.ExcerptMessage(*msg, 200, true, true),\n\t\t\tImportanceLevel:    int32(helpers.ComputeDiscussionIL(messagesIL)),\n\t\t\tLastMessageDate:    msg.Date_sort,\n\t\t\tLastMessageId:      UUID(uuid.FromStringOrNil(subHit[\"_id\"].(string))),\n\t\t\tLastMessageSubject: msg.Subject,\n\t\t\tParticipants:       msg.Participants,\n\t\t\tSubject:            msg.Subject,\n\t\t\tTotalCount:         int32(hit.DocCount),\n\t\t\tUnreadCount:        int32(hit.UnreadCount.DocCount),\n\t\t\tUserId:             filter.User_id,\n\t\t})\n\t}\n\n\treturn discussions, err\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/index/elasticsearch/elasticsearch.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage index\n\nimport (\n\t\"encoding/json\"\n\tlog \"github.com/Sirupsen/logrus\"\n\telastic \"gopkg.in/olivere/elastic.v5\"\n)\n\ntype (\n\tElasticSearchBackend struct {\n\t\tElasticSearchConfig\n\t\tClient *elastic.Client\n\t}\n\tElasticSearchConfig struct {\n\t\tUrls []string `mapstructure:\"elastic_urls\"`\n\t}\n\n\tBucket struct {\n\t\tDocCount        int             `json:\"doc_count\"`\n\t\tKey             interface{}     `json:\"key\"`\n\t\tSubBucket       json.RawMessage `json:\"sub_bucket\"`\n\t\tUnreadCount     DocCounter      `json:\"unread_count\"`\n\t\tImportanceLevel json.RawMessage `json:\"importance_level\"`\n\t}\n\n\tAggregation struct {\n\t\tBuckets                 []Bucket `json:\"buckets\"`\n\t\tDocCountErrorUpperBound int      `json:\"doc_count_error_upper_bound\"`\n\t\tSumOtherDocCount        int      `json:\"sum_other_doc_count\"`\n\t}\n\n\tDocCounter struct {\n\t\tDocCount int `json:\"doc_count\"`\n\t}\n)\n\nfunc InitializeElasticSearchIndex(config ElasticSearchConfig) (es *ElasticSearchBackend, err error) {\n\tes = new(ElasticSearchBackend)\n\terr = es.initialize(config)\n\treturn\n}\n\nfunc (es *ElasticSearchBackend) initialize(config ElasticSearchConfig) (err error) {\n\t// Create elastic client\n\tes.Client, err = elastic.NewClient(\n\t\telastic.SetURL(config.Urls...),\n\t\t//elastic.SetTraceLog(log.StandardLogger()), // comment out to stop tracing requests & responses\n\t)\n\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"package index : failed to create ES client\")\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (es *ElasticSearchBackend) Close() {\n\tes.Client.Stop()\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/index/elasticsearch/messages.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage index\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\tobjects \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/satori/go.uuid\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"gopkg.in/olivere/elastic.v5\"\n\t\"sort\"\n\t\"strings\"\n)\n\nfunc (es *ElasticSearchBackend) CreateMessage(user *objects.UserInfo, msg *objects.Message) error {\n\n\tes_msg, err := msg.MarshalES()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresp, err := es.Client.Index().Index(user.Shard_id).Type(objects.MessageIndexType).Id(msg.Message_id.String()).\n\t\tBodyString(string(es_msg)).\n\t\tRefresh(\"wait_for\").\n\t\tDo(context.TODO())\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"backend Index: IndexMessage operation failed\")\n\t\treturn err\n\t}\n\n\tlog.Infof(\"New msg indexed with id %s\", resp.Id)\n\treturn nil\n\n}\n\nfunc (es *ElasticSearchBackend) UpdateMessage(user *objects.UserInfo, msg *objects.Message, fields map[string]interface{}) error {\n\t//get json field name for each field to modify\n\tjsonFields := map[string]interface{}{}\n\n\tfor field, value := range fields {\n\t\tjsonField, err := reflections.GetFieldTag(msg, field, \"json\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"[ElasticSearchBackend] UpdateMessage failed to find a json field for object field %s\", field)\n\t\t}\n\t\tsplit := strings.Split(jsonField, \",\")\n\t\tjsonFields[split[0]] = value\n\t}\n\n\tupdate, err := es.Client.Update().Index(user.Shard_id).Type(objects.MessageIndexType).Id(msg.Message_id.String()).\n\t\tDoc(jsonFields).\n\t\tRefresh(\"wait_for\").\n\t\tDo(context.TODO())\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"backend Index: updateMessage operation failed\")\n\t\treturn err\n\t}\n\tlog.Infof(\"New version of indexed msg %s is now %d\", update.Id, update.Version)\n\treturn nil\n}\n\nfunc (es *ElasticSearchBackend) SetMessageUnread(user *objects.UserInfo, message_id string, status bool) (err error) {\n\tpayload := struct {\n\t\tIs_unread bool `json:\"is_unread\"`\n\t}{status}\n\n\tupdate := es.Client.Update().Index(user.Shard_id).Type(objects.MessageIndexType).Id(message_id)\n\t_, err = update.Doc(payload).Refresh(\"true\").Do(context.TODO())\n\n\treturn\n}\n\nfunc (es *ElasticSearchBackend) FilterMessages(filter objects.IndexSearch) (messages []*objects.Message, totalFound int64, err error) {\n\n\tsearch := es.Client.Search().Index(filter.Shard_id).Type(objects.MessageIndexType)\n\tsearch = filter.FilterQuery(search, true).Sort(\"date_sort\", false)\n\n\tif filter.Offset > 0 {\n\t\tsearch = search.From(filter.Offset)\n\t}\n\tif filter.Limit > 0 {\n\t\tsearch = search.Size(filter.Limit)\n\t}\n\n\treturn executeMessagesQuery(search)\n\n}\n\n// GetMessagesRange build a `search_after` query to retrieve messages before and/or after a specific message within a discussion\nfunc (es *ElasticSearchBackend) GetMessagesRange(filter objects.IndexSearch) (messages []*objects.Message, totalFound int64, err error) {\n\n\tmessages = []*objects.Message{}\n\tvar msg *objects.Message\n\n\t// remove range[] and msg_id from terms\n\tmsgId := filter.Terms[\"msg_id\"][0]\n\tdelete(filter.Terms, \"msg_id\")\n\tvar wantBefore bool\n\tvar wantAfter bool\n\tfor _, param := range filter.Terms[\"range[]\"] {\n\t\tif param == \"before\" {\n\t\t\twantBefore = true\n\t\t} else if param == \"after\" {\n\t\t\twantAfter = true\n\t\t}\n\t}\n\tdelete(filter.Terms, \"range[]\")\n\n\t// retrieve message with msg_id because search_after will not return it\n\t// XXX chamal: need Userinfo filtering\n\tesMsg, esErr := es.Client.Get().Index(filter.Shard_id).Type(objects.MessageIndexType).Id(msgId).Do(context.TODO())\n\tif esErr != nil {\n\t\treturn nil, 0, esErr\n\t}\n\tif !esMsg.Found {\n\t\treturn nil, 0, errors.New(\"not found\")\n\t}\n\tmsg = new(objects.Message).NewEmpty().(*objects.Message)\n\tif err := json.Unmarshal(*esMsg.Source, msg); err != nil {\n\t\treturn nil, 0, err\n\t}\n\tif e := msg.Message_id.UnmarshalBinary(uuid.FromStringOrNil(msgId).Bytes()); e != nil {\n\t\tlog.WithError(e).Warnf(\"failed to unmarshal %s\", msgId)\n\t}\n\tmessages = append(messages, msg)\n\n\t// prepare search\n\t// make search_after query for `after` param\n\tif wantAfter {\n\t\tsearchAfter := es.Client.Search().Index(filter.Shard_id).Type(objects.MessageIndexType)\n\t\tif filter.Offset > 0 {\n\t\t\tsearchAfter = searchAfter.From(filter.Offset)\n\t\t}\n\t\tif filter.Limit > 0 {\n\t\t\tsearchAfter = searchAfter.Size(filter.Limit)\n\t\t}\n\t\tsearchAfter = filter.FilterQuery(searchAfter, true).Sort(\"date_sort\", false).Sort(\"_uid\", false)\n\t\tsearchAfter = searchAfter.SearchAfter(msg.Date_sort.UnixNano()/10e5, objects.MessageIndexType+\"#\"+msgId)\n\t\tafter, afterTotal, afterErr := executeMessagesQuery(searchAfter)\n\t\tif afterErr != nil {\n\t\t\treturn nil, 0, afterErr\n\t\t}\n\t\tmessages = append(messages, after...)\n\t\ttotalFound = afterTotal\n\t}\n\n\t// make search_after query for `before` param\n\tif wantBefore {\n\t\tsearchBefore := es.Client.Search().Index(filter.Shard_id).Type(objects.MessageIndexType)\n\t\tif filter.Offset > 0 {\n\t\t\tsearchBefore = searchBefore.From(filter.Offset)\n\t\t}\n\t\tif filter.Limit > 0 {\n\t\t\tsearchBefore = searchBefore.Size(filter.Limit)\n\t\t}\n\t\tsearchBefore = filter.FilterQuery(searchBefore, true).Sort(\"date_sort\", true).Sort(\"_uid\", true)\n\t\tsearchBefore = searchBefore.SearchAfter(msg.Date_sort.UnixNano()/10e5, objects.MessageIndexType+\"#\"+msgId)\n\n\t\tbefore, beforeTotal, beforeErr := executeMessagesQuery(searchBefore)\n\t\tif beforeErr != nil {\n\t\t\treturn nil, 0, beforeErr\n\t\t}\n\t\tmessages = append(messages, before...)\n\t\ttotalFound = beforeTotal\n\t}\n\tsort.Sort(objects.ByDateSortAsc(messages))\n\treturn messages, totalFound, nil\n}\n\nfunc executeMessagesQuery(search *elastic.SearchService) (messages []*objects.Message, totalFound int64, err error) {\n\tresult, err := search.Do(context.TODO())\n\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tfor _, hit := range result.Hits.Hits {\n\t\tmsg := new(objects.Message).NewEmpty().(*objects.Message)\n\t\tif err := json.Unmarshal(*hit.Source, msg); err != nil {\n\t\t\tlog.Info(err)\n\t\t\tcontinue\n\t\t}\n\t\tmsg_id, _ := uuid.FromString(hit.Id)\n\t\tmsg.Message_id.UnmarshalBinary(msg_id.Bytes())\n\t\tmessages = append(messages, msg)\n\t}\n\ttotalFound = result.TotalHits()\n\treturn\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/index/elasticsearch/user_recipients_lookup.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage index\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"gopkg.in/olivere/elastic.v5\"\n)\n\ntype (\n\treturnedContact struct {\n\t\tFname string `json:\"family_name\"`\n\t\tGname string `json:\"given_name\"`\n\t\tTitle string `json:\"title\"`\n\t}\n\n\treturnedParticipant struct {\n\t\tAddress     string   `json:\"address\"`\n\t\tContact_ids []string `json:\"contact_ids\"`\n\t\tLabel       string   `json:\"label\"`\n\t\tProtocol    string   `json:\"protocol\"`\n\t\tType        string   `json:\"type\"`\n\t}\n\treturnedMessage struct {\n\t\tDate string `json:\"date\"`\n\t\tType string `json:\"type\"`\n\t}\n)\n\n// RecipientsSuggest builds ES queries and responses for finding relevant recipients when an user compose a message\nfunc (es *ElasticSearchBackend) RecipientsSuggest(user *UserInfo, query_string string) (suggests []RecipientSuggestion, err error) {\n\tsuggests = []RecipientSuggestion{}\n\tq_string := query_string\n\t// build nested queries for participants lookup\n\tqueries := []elastic.Query{\n\t\telastic.NewPrefixQuery(\"participants.label\", q_string),\n\t\telastic.NewPrefixQuery(\"participants.address.raw\", q_string),\n\t\telastic.NewTermQuery(\"participants.address.parts\", q_string),\n\t}\n\tparticipants_fields_q := elastic.NewBoolQuery().Should(queries...)\n\tparticipants_q := elastic.NewNestedQuery(\"participants\", participants_fields_q)\n\tparticipants_q.InnerHit(elastic.NewInnerHit().Size(1))\n\n\t// build queries for contact lookup\n\tqueries = []elastic.Query{\n\t\telastic.NewPrefixQuery(\"given_name\", q_string).Boost(3),\n\t\telastic.NewPrefixQuery(\"given_name.normalized\", q_string).Boost(3),\n\t\telastic.NewPrefixQuery(\"family_name\", q_string).Boost(3),\n\t\telastic.NewPrefixQuery(\"family_name.normalized\", q_string).Boost(3),\n\t}\n\tcontact_name_q := elastic.NewBoolQuery().Should(queries...)\n\n\tqueries = []elastic.Query{\n\t\telastic.NewPrefixQuery(\"emails.label\", q_string).Boost(2),\n\t\telastic.NewPrefixQuery(\"emails.address.raw\", q_string).Boost(2),\n\t\telastic.NewTermQuery(\"emails.address.parts\", q_string).Boost(2),\n\t}\n\tnested_emails_q := elastic.NewBoolQuery().Should(queries...)\n\temails_q := elastic.NewNestedQuery(\"emails\", nested_emails_q)\n\temails_q.InnerHit(elastic.NewInnerHit())\n\n\tqueries = []elastic.Query{\n\t\telastic.NewPrefixQuery(\"ims.address\", q_string).Boost(2),\n\t\telastic.NewPrefixQuery(\"ims.label\", q_string).Boost(2),\n\t}\n\tnested_ims_q := elastic.NewBoolQuery().Should(queries...)\n\tims_q := elastic.NewNestedQuery(\"ims\", nested_ims_q)\n\tims_q.InnerHit(elastic.NewInnerHit())\n\n\tqueries = []elastic.Query{\n\t\telastic.NewPrefixQuery(\"identities.name\", q_string).Boost(2),\n\t\telastic.NewPrefixQuery(\"identities.infos\", q_string).Boost(2), // TODO: check if this query could find string within infos map\n\t}\n\tnested_socials_q := elastic.NewBoolQuery().Should(queries...)\n\tsocials_q := elastic.NewNestedQuery(\"identities\", nested_socials_q)\n\tsocials_q.InnerHit(elastic.NewInnerHit())\n\n\t// doc source pruning\n\tfsc := elastic.NewFetchSourceContext(true)\n\tfsc.Include(\"title\")\n\n\t// run the query\n\tmain_query := elastic.NewBoolQuery().Filter(elastic.NewTermQuery(\"user_id\", user.User_id)).\n\t\tShould(participants_q, contact_name_q, emails_q, ims_q, socials_q)\n\tsearch := es.Client.Search().\n\t\tIndex(user.Shard_id).\n\t\tFetchSourceContext(fsc).\n\t\tSize(30).\n\t\tMinScore(0.1)\n\t/** log the full json query to help development\n\tsource, _ := main_query.Source()\n\tjson_query, _ := json.Marshal(source)\n\tlog.Infof(\"\\nES query source: %s\\n\", json_query)\n\tagg_source, _ := max_date_agg.Source()\n\tjson_agg, _ := json.Marshal(agg_source)\n\tlog.Infof(\"\\nES aggregation source: %s\\n\", json_agg)\n\t/** end of log **/\n\tresult, err := search.Query(main_query).Do(context.TODO())\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[Elasticsearch] failed to suggest participant.\")\n\t\treturn\n\t}\n\tparticipants_suggests := make(map[string]RecipientSuggestion)\n\tfor _, hit := range result.Hits.Hits {\n\t\tswitch hit.Type {\n\t\tcase MessageIndexType:\n\t\t\tsuggest, e := extractParticipantInfos(hit)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[Elasticsearch] failed to extract message participants\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t//deduplicate\n\t\t\tif _, ok := participants_suggests[suggest.Address]; !ok {\n\t\t\t\tparticipants_suggests[suggest.Address] = suggest\n\t\t\t\tsuggests = append(suggests, suggest)\n\t\t\t}\n\t\tcase ContactIndexType:\n\t\t\tsuggest, e := extractContactInfos(hit)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[Elasticsearch] failed to extract contact info\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsuggests = append(suggests, suggest)\n\t\tdefault:\n\t\t\tsuggest := RecipientSuggestion{\n\t\t\t\tSource: \"<\" + hit.Type + \">\",\n\t\t\t}\n\t\t\tsuggests = append(suggests, suggest)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc extractContactInfos(contact_hit *elastic.SearchHit) (suggest RecipientSuggestion, err error) {\n\tvar contact returnedContact\n\tif e := json.Unmarshal(*contact_hit.Source, &contact); e != nil {\n\t\terr = errors.New(\"[ES RecipientSuggest] failed unmarshaling hit's source : \" + e.Error())\n\t\treturn\n\t}\n\tsuggest.Source = \"contact\"\n\tsuggest.Label = contact.Title\n\tsuggest.Contact_Id = contact_hit.Id\n\treturn\n}\n\nfunc extractParticipantInfos(message_hit *elastic.SearchHit) (suggest RecipientSuggestion, err error) {\n\tif participants, ok := message_hit.InnerHits[\"participants\"]; ok && len(participants.Hits.Hits) > 0 {\n\t\tinner_hit := message_hit.InnerHits[\"participants\"].Hits.Hits[0]\n\t\tvar participant returnedParticipant\n\t\tif e := json.Unmarshal(*inner_hit.Source, &participant); e != nil {\n\t\t\terr = errors.New(\"[ES RecipientSuggest] failed unmarshaling hit's source : \" + e.Error())\n\t\t\treturn\n\t\t}\n\t\tsuggest.Source = \"participant\"\n\t\tsuggest.Label = participant.Label\n\t\tsuggest.Address = participant.Address\n\t\tsuggest.Protocol = participant.Protocol\n\t\treturn\n\t}\n\treturn\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/attachments.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport \"io\"\n\nfunc (cb *CassandraBackend) StoreAttachment(attachment_id string, file io.Reader) (uri string, size int, err error) {\n\turi, s, err := cb.ObjectsStore.PutAttachment(attachment_id, file)\n\treturn uri, int(s), err\n}\n\nfunc (cb *CassandraBackend) DeleteAttachment(uri string) error {\n\treturn cb.ObjectsStore.RemoveObject(uri)\n}\n\nfunc (cb *CassandraBackend) GetAttachment(uri string) (file io.Reader, err error) {\n\treturn cb.ObjectsStore.GetObject(uri)\n}\n\nfunc (cb *CassandraBackend) AttachmentExists(uri string) bool {\n\tinfo, err := cb.ObjectsStore.StatObject(uri)\n\tif err == nil && info.Err == nil {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/cassandra.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/object_store\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/vault\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocassa/gocassa\"\n\t\"github.com/gocql/gocql\"\n\t\"time\"\n)\n\ntype (\n\tCassandraBackend struct {\n\t\tCassandraConfig\n\t\tSession      *gocql.Session\n\t\tIKeyspace    gocassa.KeySpace //gocassa keyspace interface\n\t\tObjectsStore object_store.ObjectsStore\n\t\tTimeout      time.Duration\n\t\tVault        vault.HVault\n\t}\n\n\tCassandraConfig struct {\n\t\tHosts        []string          `mapstructure:\"hosts\"`\n\t\tKeyspace     string            `mapstructure:\"keyspace\"`\n\t\tConsistency  gocql.Consistency `mapstructure:\"consistency_level\"`\n\t\tSizeLimit    uint64            `mapstructure:\"raw_size_limit\"` // max size to store (in bytes)\n\t\tWithObjStore bool              // whether to use an objects store service for objects above SizeLimit\n\t\tobject_store.OSSConfig\n\t\tUseVault bool `mapstructure:\"use_vault\"`\n\t\tvault.HVaultConfig\n\t}\n\n\tHasTable interface {\n\t\t// GetTableInfos returns the table name and maps with couple [PropertyName]CassandryKeys\n\t\tGetTableInfos() (table string, partitionKeys map[string]string, collectionKeys map[string]string)\n\t\tUnmarshalCQLMap(input map[string]interface{})\n\t}\n)\n\nconst DefaultTimeout = time.Second * 2\n\nfunc InitializeCassandraBackend(config CassandraConfig) (cb *CassandraBackend, err error) {\n\tcb = new(CassandraBackend)\n\terr = cb.initialize(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// objects store\n\tif config.WithObjStore {\n\t\tcb.ObjectsStore, err = object_store.InitializeObjectsStore(config.OSSConfig)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"[InitializeCassandraBackend] object store initialization failed\")\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// credentials store\n\tcb.UseVault = config.UseVault\n\tif cb.UseVault {\n\t\tcb.Vault, err = vault.InitializeVaultBackend(config.HVaultConfig)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"[InitializeCassandraBackend] vault initialization failed\")\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn\n}\n\nfunc (cb *CassandraBackend) initialize(config CassandraConfig) (err error) {\n\tcb.CassandraConfig = config\n\tcb.Timeout = DefaultTimeout\n\t// connect to the cluster\n\tcluster := gocql.NewCluster(cb.CassandraConfig.Hosts...)\n\tcluster.Keyspace = cb.Keyspace\n\tcluster.Consistency = cb.Consistency\n\n\t//try to get a Session\n\tconst maxAttempts = 10\n\tfor i := 0; i < maxAttempts; i++ {\n\t\tcb.Session, err = cluster.CreateSession()\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"package store : unable to create a session to cassandra. Retrying in 3 sec…\")\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\tconnection := gocassa.NewConnection(gocassa.GoCQLSessionToQueryExecutor(cb.Session))\n\tcb.IKeyspace = connection.KeySpace(cb.Keyspace)\n\n\treturn\n}\n\nfunc (cb *CassandraBackend) Close() {\n\tcb.Session.Close()\n}\n\nfunc (cb *CassandraBackend) GetSession() *gocql.Session {\n\treturn cb.Session\n}\n\n// SessionQuery is a wrapper to cb.Session.Query(…………) that re-init a Session if it has been closed\nfunc (cb *CassandraBackend) SessionQuery(stmt string, values ...interface{}) *gocql.Query {\n\tif cb.Session.Closed() {\n\t\terr := (*cb).initialize(cb.CassandraConfig)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"[CassandraBackend] SessionQuery failed to re-init a gocql Session\")\n\t\t}\n\t}\n\treturn cb.Session.Query(stmt, values...)\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/contacts.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocassa/gocassa\"\n\t\"github.com/gocql/gocql\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n)\n\n// CreateContact saves Contact to Cassandra\n// AND fills/updates joined and lookup tables\nfunc (cb *CassandraBackend) CreateContact(contact *Contact) error {\n\t// validate embedded uris in newContact\n\tcheckList := ControlURIsUniqueness(cb, contact)\n\tfor uri, check := range checkList {\n\t\tif !check.Ok {\n\t\t\treturn fmt.Errorf(\"uri <%s> belongs to contact %s\", uri, check.OtherContact)\n\t\t}\n\t}\n\n\tcontactT := cb.IKeyspace.Table(\"contact\", &Contact{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"contact_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"contact\"}) // need to overwrite default gocassa table naming convention\n\n\t// save contact\n\terr := contactT.Set(contact).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"[CassandraBackend] CreateContact: %s\", err)\n\t}\n\tisNew := true\n\n\t// create related rows in joined tables\n\tgo func(*CassandraBackend, *Contact, bool) {\n\t\terr = cb.UpdateRelated(contact, nil, isNew)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] CreateContact : failed to UpdateRelated\")\n\t\t}\n\t}(cb, contact, isNew)\n\n\t// create related rows in relevant lookup tables\n\tgo func(*CassandraBackend, *Contact, bool) {\n\t\terr = cb.UpdateLookups(contact, nil, isNew)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] CreateContact : failed to UpdateLookups\")\n\t\t}\n\t}(cb, contact, isNew)\n\n\treturn nil\n}\n\nfunc (cb *CassandraBackend) RetrieveContact(user_id, contact_id string) (contact *Contact, err error) {\n\n\t// retrieve contact\n\tcontact = new(Contact).NewEmpty().(*Contact)\n\tm := map[string]interface{}{}\n\tq := cb.SessionQuery(`SELECT * FROM contact WHERE user_id = ? AND contact_id = ?`, user_id, contact_id)\n\terr = q.MapScan(m)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(m) == 0 {\n\t\treturn nil, gocql.ErrNotFound\n\t}\n\tcontact.UnmarshalCQLMap(m)\n\n\t// embed objects from joined tables\n\terr = cb.RetrieveRelated(contact)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[CassandraBackend] RetrieveContact: failed to retrieve related.\")\n\t}\n\n\treturn contact, err\n}\n\n// RetrieveUserContactId returns contactID embedded in user entry\n// or empty string if error or not found\nfunc (cb *CassandraBackend) RetrieveUserContactId(userID string) string {\n\tvar contactID string\n\terr := cb.SessionQuery(`SELECT contact_id FROM user WHERE user_id = ?`, userID).Scan(&contactID)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn contactID\n}\n\n// UpdateContact updates fields into Cassandra\n// AND updates related lookup tables if needed\nfunc (cb *CassandraBackend) UpdateContact(contact, oldContact *Contact, fields map[string]interface{}) error {\n\n\t// validate embedded uris in newContact\n\tcheckList := ControlURIsUniqueness(cb, contact)\n\tfor uri, check := range checkList {\n\t\tif !check.Ok {\n\t\t\treturn fmt.Errorf(\"uri <%s> belongs to contact %s\", uri, check.OtherContact)\n\t\t}\n\t}\n\n\t//get cassandra's field name for each field to modify\n\tcassaFields := map[string]interface{}{}\n\tfor field, value := range fields {\n\t\tcassaField, err := reflections.GetFieldTag(contact, field, \"cql\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"[CassandraBackend] UpdateContact failed to find a cql field for object field %s\", field)\n\t\t}\n\t\tif cassaField != \"-\" {\n\t\t\tcassaFields[cassaField] = value\n\t\t}\n\t}\n\n\tcontactT := cb.IKeyspace.Table(\"contact\", &Contact{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"contact_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"contact\"}) // need to overwrite default gocassa table naming convention\n\n\terr := contactT.\n\t\tWhere(gocassa.Eq(\"user_id\", contact.UserId.String()), gocassa.Eq(\"contact_id\", contact.ContactId.String())).\n\t\tUpdate(cassaFields).\n\t\tRun()\n\tisNew := false\n\n\t// update related rows in joined tables\n\tgo func(cb *CassandraBackend, new, old *Contact, isNew bool) {\n\t\terr = cb.UpdateRelated(contact, oldContact, isNew)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] UpdateContact : failed to UpdateRelated\")\n\t\t}\n\t}(cb, contact, oldContact, isNew)\n\n\t// update related rows in relevant lookup tables\n\tgo func(cb *CassandraBackend, new, old *Contact, isNew bool) {\n\t\terr = cb.UpdateLookups(contact, oldContact, isNew)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] UpdateContact : failed to UpdateLookups\")\n\t\t}\n\t}(cb, contact, oldContact, isNew)\n\n\treturn nil\n}\n\n// DeleteContact removes Contact from Cassandra\n// AND removes contactID from related lookup_tables\nfunc (cb *CassandraBackend) DeleteContact(contact *Contact) error {\n\n\t// (hard) delete contact. TODO: soft delete\n\terr := cb.SessionQuery(`DELETE FROM contact WHERE user_id = ? AND contact_id = ?`, contact.UserId.String(), contact.ContactId.String()).Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// delete related rows in joined tables\n\tgo func(*CassandraBackend, *Contact) {\n\t\terr = cb.DeleteRelated(contact)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] DeleteContact: failed to delete related\")\n\t\t}\n\t}(cb, contact)\n\n\t// delete related rows in relevant lookup tables\n\tgo func(*CassandraBackend, *Contact) {\n\t\terr = cb.DeleteLookups(contact)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] DeleteContact: failed to delete lookups\")\n\t\t}\n\t}(cb, contact)\n\n\treturn nil\n}\n\ntype UrisCheck struct {\n\tOk           bool\n\tOtherContact string\n}\n\ntype UrisChecklist map[string]UrisCheck\n\n// ControlURIsUniqueness checks if all uris embedded in contact belong to this contact only\n// for earch uri, it returns other contact's id if uri is not available, Ok==true otherwise\nfunc ControlURIsUniqueness(cb backends.ContactStorage, contact *Contact) (checkList UrisChecklist) {\n\tcheckList = UrisChecklist{}\n\tfor lookup := range contact.GetLookupKeys() {\n\t\tlkp := lookup.(*ContactByContactPoints)\n\t\tcontacts, err := cb.LookupContactsByIdentifier(contact.UserId.String(), lkp.Value, lkp.Type)\n\t\tif err != nil {\n\t\t\tif err.Error() == \"not found\" {\n\t\t\t\tcheckList[lkp.Type+\":\"+lkp.Value] = UrisCheck{Ok: true, OtherContact: \"\"}\n\t\t\t} else {\n\t\t\t\tcheckList[lkp.Type+\":\"+lkp.Value] = UrisCheck{Ok: false, OtherContact: \"error: \" + err.Error()}\n\t\t\t}\n\t\t}\n\t\tfor _, c := range contacts {\n\t\t\tif c != contact.ContactId.String() {\n\t\t\t\tcheckList[lkp.Type+\":\"+lkp.Value] = UrisCheck{Ok: false, OtherContact: c}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif _, ok := checkList[lkp.Type+\":\"+lkp.Value]; !ok {\n\t\t\tcheckList[lkp.Type+\":\"+lkp.Value] = UrisCheck{Ok: true, OtherContact: \"\"}\n\t\t}\n\t}\n\treturn checkList\n}\n\nfunc (cb *CassandraBackend) LookupContactsByIdentifier(user_id, address, kind string) (contact_ids []string, err error) {\n\tvar contact_id string\n\terr = cb.SessionQuery(`SELECT contact_id FROM contact_lookup WHERE user_id= ? AND value= ? AND type= ?`, user_id, address, kind).Scan(&contact_id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcontact_ids = []string{contact_id}\n\treturn\n}\n\n// ContactExist exposes a simple API to check if a contact with these uuids exits in db\nfunc (cb *CassandraBackend) ContactExists(userId, contactId string) bool {\n\tvar count int\n\terr := cb.SessionQuery(`SELECT count(*) FROM contact WHERE user_id = ? AND contact_id = ?`, userId, contactId).Scan(&count)\n\tif err != nil || count == 0 {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (cb *CassandraBackend) ContactsForParticipants(userID string, participants map[string]Participant) error {\n\treturn ContactsForParticipants(cb.Session, userID, participants)\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/contacts_test.go",
    "content": "package store\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"github.com/satori/go.uuid\"\n\t\"testing\"\n)\n\nfunc TestCassandraBackend_ControlURIsUniqueness(t *testing.T) {\n\tcb := backendstest.GetContactBackend()\n\t// test uris not available\n\tcheckList := ControlURIsUniqueness(cb, &Contact{\n\t\tEmails: []EmailContact{\n\t\t\t{\n\t\t\t\tAddress:   \"emma@recovery-caliopen.local\",\n\t\t\t\tEmailId:   UUID(uuid.FromStringOrNil(\"444d71f6-324c-4733-88a2-77ca28ea6d2d\")),\n\t\t\t\tIsPrimary: false,\n\t\t\t\tLabel:     \"emma@recovery-caliopen.local\",\n\t\t\t\tType:      \"other\",\n\t\t\t},\n\t\t},\n\t})\n\tif len(checkList) == 0 {\n\t\tt.Error(\"expected an non empty checklit, got empty\")\n\t} else {\n\t\tif check, ok := checkList[\"email:emma@recovery-caliopen.local\"]; ok {\n\t\t\tif check.Ok {\n\t\t\t\tt.Error(\"expected <email:emma@recovery-caliopen.local> to be unavailable, got OK == true\")\n\t\t\t} else if check.OtherContact != \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\" {\n\t\t\t\tt.Errorf(\"expected to find another contact with id = 63ab7904-c416-4f1a-9652-3de82e4fd1f1, got %s\", check.OtherContact)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"expected to find key <email:emma@recovery-caliopen.local>\")\n\t\t}\n\t}\n\n\tcheckList = ControlURIsUniqueness(cb, &Contact{\n\t\tEmails: []EmailContact{\n\t\t\t{\n\t\t\t\tAddress:   \"emma@recovery-caliopen.local\",\n\t\t\t\tEmailId:   UUID(uuid.FromStringOrNil(\"444d71f6-324c-4733-88a2-77ca28ea6d2d\")),\n\t\t\t\tIsPrimary: false,\n\t\t\t\tLabel:     \"emma@recovery-caliopen.local\",\n\t\t\t\tType:      \"other\",\n\t\t\t},\n\t\t},\n\t\tIdentities: []SocialIdentity{\n\t\t\t{\n\t\t\t\tName: \"emmatomme\",\n\t\t\t\tType: \"twitter\",\n\t\t\t},\n\t\t},\n\t})\n\t// TODO\n\tt.Log(checkList)\n\tcheckList = ControlURIsUniqueness(cb, &Contact{\n\t\tEmails: []EmailContact{\n\t\t\t{\n\t\t\t\tAddress: \"elvis@able\",\n\t\t\t\tType:    \"other\",\n\t\t\t},\n\t\t},\n\t\tIdentities: []SocialIdentity{\n\t\t\t{\n\t\t\t\tName: \"toto\",\n\t\t\t\tType: \"twitter\",\n\t\t\t},\n\t\t},\n\t})\n\t// TODO\n\tt.Log(checkList)\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/credentials.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage store\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/gocassa/gocassa\"\n)\n\nfunc (cb *CassandraBackend) CreateCredentials(userIdentity *UserIdentity, cred Credentials) error {\n\n\tif cb.UseVault {\n\t\treturn cb.Vault.CreateCredentials(userIdentity, cred)\n\t}\n\n\t//(re)embed credentials into UserIdentity that has already been created\n\t(*userIdentity).Credentials = &cred\n\treturn cb.UpdateUserIdentity(userIdentity, map[string]interface{}{\n\t\t\"Credentials\": cred,\n\t})\n}\n\nfunc (cb *CassandraBackend) RetrieveCredentials(userId, identityId string) (cred Credentials, err error) {\n\n\tif cb.UseVault {\n\t\treturn cb.Vault.RetrieveCredentials(userId, identityId)\n\t}\n\n\terr = cb.SessionQuery(`SELECT credentials FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, identityId).Scan(&cred)\n\n\treturn\n}\n\nfunc (cb *CassandraBackend) UpdateCredentials(userId, identityId string, cred Credentials) error {\n\n\tif cb.UseVault {\n\t\treturn cb.Vault.UpdateCredentials(userId, identityId, cred, false)\n\t}\n\n\t// check if identity exists before executing UPDATE because `IF EXISTS` statement not supported by scylladb as of february 2019\n\tif cb.SessionQuery(`SELECT user_id FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, identityId).Iter().NumRows() == 0 {\n\t\treturn errors.New(\"not found\")\n\t}\n\n\tuserIdentityTable := cb.IKeyspace.Table(\"user_identity\", &UserIdentity{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"identity_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"user_identity\"})\n\n\treturn userIdentityTable.Where(gocassa.Eq(\"user_id\", userId), gocassa.Eq(\"identity_id\", identityId)).\n\t\tUpdate(map[string]interface{}{\n\t\t\t\"credentials\": cred,\n\t\t}).Run()\n\n}\n\nfunc (cb *CassandraBackend) DeleteCredentials(userId, identityId string) error {\n\n\tif cb.UseVault {\n\t\treturn cb.Vault.DeleteCredentials(userId, identityId)\n\t}\n\n\treturn cb.UpdateCredentials(userId, identityId, Credentials{})\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/devices.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocassa/gocassa\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n)\n\nfunc (cb *CassandraBackend) CreateDevice(device *Device) error {\n\n\tdeviceT := cb.IKeyspace.Table(\"device\", &Device{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"device_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"device\"})\n\n\t//save device\n\terr := deviceT.Set(device).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"[CassandraBackend] CreateContact: %s\", err)\n\t}\n\n\tisNew := true\n\n\t// create related rows in joinde tables (if any)\n\tgo func(*CassandraBackend, *Device, bool) {\n\t\terr = cb.UpdateRelated(device, nil, isNew)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] CreateDevice : failed to UpdateRelated\")\n\t\t}\n\t}(cb, device, isNew)\n\n\t/*** NO LOOKUPS for now, code below will be uncommented if needed ***/\n\t// create related rows in relevant lookup tables (if any)\n\t/*\n\t\tgo func(*CassandraBackend, *Device, bool) {\n\t\t\terr = cb.UpdateLookups(device, nil, isNew)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Error(\"[CassandraBackend] CreateDevice : failed to UpdateLookups\")\n\t\t\t}\n\t\t}(cb, device, isNew)\n\t*/\n\treturn nil\n}\n\n// retrieve devices belonging to user_id\nfunc (cb *CassandraBackend) RetrieveDevices(userId string) (devices []Device, err error) {\n\tall_devices, err := cb.SessionQuery(`SELECT * FROM device WHERE user_id = ?`, userId).Iter().SliceMap()\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(all_devices) == 0 {\n\t\terr = errors.New(\"devices not found\")\n\t\treturn\n\t}\n\tfor _, device := range all_devices {\n\t\td := new(Device).NewEmpty().(*Device)\n\t\td.UnmarshalCQLMap(device)\n\t\t// embed objects from joined tables\n\t\terr = cb.RetrieveRelated(d)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] RetrieveDevice: failed to retrieve related.\")\n\t\t} else {\n\t\t\tdevices = append(devices, *d)\n\t\t}\n\t}\n\treturn\n}\n\nfunc (cb *CassandraBackend) RetrieveDevice(userId, deviceId string) (device *Device, err error) {\n\n\tdevice = new(Device).NewEmpty().(*Device)\n\td := map[string]interface{}{}\n\tq := cb.SessionQuery(`SELECT * FROM device WHERE user_id = ? AND device_id = ?`, userId, deviceId)\n\terr = q.MapScan(d)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(d) == 0 {\n\t\terr = errors.New(\"not found\")\n\t\treturn nil, err\n\t}\n\tdevice.UnmarshalCQLMap(d)\n\n\t// embed objects from joined tables\n\terr = cb.RetrieveRelated(device)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[CassandraBackend] RetrieveDevice: failed to retrieve related.\")\n\t}\n\n\treturn device, nil\n}\n\nfunc (cb *CassandraBackend) UpdateDevice(device, oldDevice *Device, fields map[string]interface{}) error {\n\n\t//get cassandra's field name for each field to modify\n\tcassaFields := map[string]interface{}{}\n\tfor field, value := range fields {\n\t\tcassaField, err := reflections.GetFieldTag(device, field, \"cql\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"[CassandraBackend] UpdateDevice failed to find a cql field for object field %s\", field)\n\t\t}\n\t\tif cassaField != \"-\" {\n\t\t\tcassaFields[cassaField] = value\n\t\t}\n\t}\n\n\tdeviceT := cb.IKeyspace.Table(\"device\", &Device{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"device_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"device\"}) // need to overwrite default gocassa table naming convention\n\n\terr := deviceT.\n\t\tWhere(gocassa.Eq(\"user_id\", device.UserId.String()), gocassa.Eq(\"device_id\", device.DeviceId.String())).\n\t\tUpdate(cassaFields).\n\t\tRun()\n\tisNew := false\n\n\t// update related rows in joined tables\n\tgo func(cb *CassandraBackend, new, old *Device, isNew bool) {\n\t\terr = cb.UpdateRelated(device, oldDevice, isNew)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] Updatedevice : failed to UpdateRelated\")\n\t\t}\n\t}(cb, device, oldDevice, isNew)\n\n\t// update related rows in relevant lookup tables\n\t/*** no lookups for now\n\t\tgo func(cb *CassandraBackend, new, old *Device, isNew bool) {\n\t\t\terr = cb.UpdateLookups(device, oldDevice, isNew)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Error(\"[CassandraBackend] Updatedevice : failed to UpdateLookups\")\n\t\t\t}\n\t\t}(cb, device, oldDevice, isNew)\n\t  ***/\n\treturn nil\n}\n\nfunc (cb *CassandraBackend) DeleteDevice(device *Device) error {\n\t// (hard) delete device. TODO: soft delete\n\terr := cb.SessionQuery(`DELETE FROM device WHERE user_id = ? AND device_id = ?`, device.UserId.String(), device.DeviceId.String()).Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// delete related rows in joined tables\n\tgo func(*CassandraBackend, *Device) {\n\t\terr = cb.DeleteRelated(device)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] DeleteDevice: failed to delete related\")\n\t\t}\n\t}(cb, device)\n\n\t// delete related rows in relevant lookup tables\n\t/*** no lookups for now\n\tgo func(*CassandraBackend, *Device) {\n\t\terr = cb.DeleteLookups(device)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[CassandraBackend] Deletedevice: failed to delete lookups\")\n\t\t}\n\t}(cb, device)\n\t***/\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/discussions.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/gocassa/gocassa\"\n\t\"time\"\n)\n\nfunc (cb *CassandraBackend) GetUserLookupHashes(userId UUID, kind, key string) (hashes []ParticipantHash, err error) {\n\tvar rawHashes []map[string]interface{}\n\tif key != \"\" {\n\t\trawHashes, err = cb.SessionQuery(`SELECT * from participant_hash WHERE user_id = ? AND kind = ? AND key = ?`, userId, kind, key).Iter().SliceMap()\n\t} else {\n\t\trawHashes, err = cb.SessionQuery(`SELECT * from participant_hash WHERE user_id = ? AND kind = ?`, userId, kind).Iter().SliceMap()\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(rawHashes) == 0 {\n\t\terr = errors.New(\"not found\")\n\t\treturn\n\t}\n\tfor _, hash := range rawHashes {\n\t\th := new(ParticipantHash)\n\t\th.UnmarshalCQLMap(hash)\n\t\thashes = append(hashes, *h)\n\t}\n\treturn\n}\n\nfunc (cb *CassandraBackend) RetrieveParticipantHash(userId UUID, kind, hash string) (lookup ParticipantHash, err error) {\n\tm := map[string]interface{}{}\n\tq := cb.SessionQuery(`SELECT * from participant_hash WHERE user_id = ? AND kind = ? AND key = ?`, userId, kind, hash)\n\terr = q.MapScan(m)\n\tif err != nil {\n\t\treturn\n\t}\n\tlookup.UnmarshalCQLMap(m)\n\treturn\n}\n\nfunc (cb *CassandraBackend) CreateParticipantHash(lookup *ParticipantHash) error {\n\tlookupT := cb.IKeyspace.Table(\"participant_hash\", &ParticipantHash{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"kind\", \"key\", \"value\"},\n\t}).WithOptions(gocassa.Options{TableName: \"participant_hash\"}) // need to overwrite default gocassa table naming convention\n\n\t// save lookup\n\terr := lookupT.Set(lookup).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"[CassandraBackend] CreateParticipantHash: %s\", err)\n\t}\n\treturn nil\n}\n\n// UpsertDiscussionLookups ensures that relevant entries are present in both HashLookup and ParticipantHash tables\n// for the provided participants, taking into account user's contacts addresses\nfunc (cb *CassandraBackend) UpsertDiscussionLookups(userId UUID, participants []Participant) error {\n\thash, components, err := HashFromParticipantsUris(participants)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cb.CreateLookupsFromUris(userId, hash, components)\n}\n\n// CreateLookupsFromUris resolves uris to contact to build participants' set\n// then computes participants_hash\n// then creates bijection in HashLookup and ParticipantLookup tables:\n//    uris<->uris_hash\n//    uris_hash<->participants_hash\nfunc (cb *CassandraBackend) CreateLookupsFromUris(userId UUID, uriHash string, uris []string) error {\n\tnow := time.Now()\n\tfor _, uri := range uris {\n\t\t// store uri->uris_hash\n\t\te := cb.CreateHashLookup(HashLookup{\n\t\t\tUserId:         userId,\n\t\t\tUri:            uri,\n\t\t\tHash:           uriHash,\n\t\t\tHashComponents: uris,\n\t\t\tDateInsert:     now,\n\t\t})\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\t}\n\t// pick one uri to trigger participant_hash table update\n\tif len(uris) > 0 {\n\t\treturn UpdateURIWithContact(cb.Session, userId.String(), uris[0])\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/discussions_test.go",
    "content": "package store\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/emails.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/satori/go.uuid\"\n)\n\n// GetUsersForLocalMailRecipients is part of LDABackend interface implementation\n// return a list of tuples ([user_id, identity_id]) of **local** users found for the given email addresses\nfunc (cb *CassandraBackend) GetUsersForLocalMailRecipients(rcpts []string) (userIds [][]UUID, err error) {\n\tuserIds = [][]UUID{}\n\tfor _, rcpt := range rcpts {\n\t\tidentities, err := cb.LookupIdentityByIdentifier(rcpt)\n\t\tif err == nil {\n\t\t\tfor _, identity := range identities {\n\t\t\t\tif cb.IsLocalIdentity(identity[0], identity[1]) {\n\t\t\t\t\tuserIds = append(userIds, []UUID{UUID(uuid.FromStringOrNil(identity[0])), UUID(uuid.FromStringOrNil(identity[1]))})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/identities.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocassa/gocassa\"\n\t\"github.com/gocql/gocql\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc (cb *CassandraBackend) RetrieveLocalsIdentities(userId string) (identities []UserIdentity, err error) {\n\tvar count int\n\titer := cb.SessionQuery(`SELECT identity_id FROM identity_type_lookup WHERE type = ? AND user_id = ?`, LocalIdentity, userId).Iter()\n\tfor {\n\t\tvar identityID gocql.UUID\n\t\tif !iter.Scan(&identityID) {\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t\ti := make(map[string]interface{})\n\t\tcb.SessionQuery(`SELECT * FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, identityID).MapScan(i)\n\t\tidentity := new(UserIdentity)\n\t\tidentity.UnmarshalCQLMap(i)\n\t\tidentities = append(identities, *identity)\n\t}\n\tif count == 0 {\n\t\terr = errors.New(\"not found\")\n\t\treturn\n\t}\n\treturn\n}\n\nfunc (cb *CassandraBackend) CreateUserIdentity(userIdentity *UserIdentity) CaliopenError {\n\n\tuserIdentity.Identifier = strings.ToLower(userIdentity.Identifier)\n\tuserIdentityTable := cb.IKeyspace.Table(\"user_identity\", &UserIdentity{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"identity_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"user_identity\"})\n\n\t// check if user identity already exist\n\tvar count int\n\terr := cb.SessionQuery(`SELECT count(*) from user_identity WHERE user_id = ? AND identity_id = ?`, userIdentity.UserId, userIdentity.Id).Scan(&count)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[CassandraBackend] SELECT fails for user_id=%s, identity_id=%s\", userIdentity.UserId.String(), userIdentity.Id.String())\n\t\treturn WrapCaliopenErrf(err, DbCaliopenErr, \"[CassandraBackend] CreateUserIdentity fails : <%s>\", err.Error())\n\t}\n\tif count != 0 {\n\t\treturn NewCaliopenErrf(ForbiddenCaliopenErr, \"[CassandraBackend] CreateUserIdentity error : user identity <%s> already exist for user <%s>\", userIdentity.Id, userIdentity.UserId.String())\n\t}\n\n\t// remove credentials from struct\n\tcred := userIdentity.Credentials\n\t(*userIdentity).Credentials = nil\n\n\t// create user identity\n\terr = userIdentityTable.Set(userIdentity).Run()\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[CassandraBackend] set fails for %+v\", userIdentity)\n\t\treturn WrapCaliopenErrf(err, DbCaliopenErr, \"[CassandraBackend] CreateUserIdentity fails : %s\", err.Error())\n\t}\n\n\t// update lookup tables\n\terr = cb.UpdateLookups(userIdentity, nil, true)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[CassandraBackend] UpdateLookups error for UserIdentity %s (user %s)\", userIdentity.Id.String(), userIdentity.UserId.String())\n\t}\n\n\t// create credentials apart\n\tif cred != nil {\n\t\terr = cb.CreateCredentials(userIdentity, *cred)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Errorf(\"[CassandraBackend] create credentials failed for %+v\", *cred)\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[CassandraBackend) CreateUserIdentity failed to createCredentials\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (cb *CassandraBackend) RetrieveUserIdentity(userId, identityId string, withCredentials bool) (userIdentity *UserIdentity, err error) {\n\n\tuserIdentity = new(UserIdentity)\n\tm := map[string]interface{}{}\n\tq := cb.SessionQuery(`SELECT * FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, identityId)\n\terr = q.MapScan(m)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tuserIdentity.UnmarshalCQLMap(m)\n\tif withCredentials && userIdentity.Type != LocalIdentity {\n\t\tcred, err := cb.RetrieveCredentials(userId, identityId)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tuserIdentity.Credentials = &cred\n\t} else {\n\t\t// discard credentials\n\t\tuserIdentity.Credentials = nil\n\t}\n\n\treturn\n}\n\nfunc (cb *CassandraBackend) UpdateUserIdentity(userIdentity *UserIdentity, fields map[string]interface{}) (err error) {\n\tuserIdentity.Identifier = strings.ToLower(userIdentity.Identifier)\n\t// check if identity exists before executing UPDATE because `IF EXISTS` statement not supported by scylladb as of february 2019\n\tif cb.SessionQuery(`SELECT user_id FROM user_identity WHERE user_id = ? AND identity_id = ?`, userIdentity.UserId.String(), userIdentity.Id.String()).Iter().NumRows() == 0 {\n\t\treturn errors.New(\"not found\")\n\t}\n\t//remove Credentials from userIdentity and process this special property apart\n\tif cred, ok := fields[\"Credentials\"].(*Credentials); ok && cred != nil {\n\t\t(*userIdentity).Credentials = nil\n\t\tdelete(fields, \"Credentials\")\n\t\terr = cb.UpdateCredentials(userIdentity.UserId.String(), userIdentity.Id.String(), *cred)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"[CassandraBackend] UpdateUserIdentity failed to update credentials\")\n\t\t}\n\t}\n\n\tif identifier, ok := fields[\"identifier\"]; ok {\n\t\tfields[\"identifier\"] = strings.ToLower(identifier.(string))\n\t}\n\n\tif len(fields) > 0 {\n\t\t//get cassandra's field name for each field to modify\n\t\tcassaFields := map[string]interface{}{}\n\t\tfor field, value := range fields {\n\t\t\tcassaField, err := reflections.GetFieldTag(userIdentity, field, \"cql\")\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"[CassandraBackend] UpdateUserIdentity failed to find a cql field for object field %s\", field)\n\t\t\t}\n\t\t\tif cassaField != \"-\" {\n\t\t\t\tcassaFields[cassaField] = value\n\t\t\t}\n\t\t}\n\n\t\tuserIdentityTable := cb.IKeyspace.Table(\"user_identity\", &UserIdentity{}, gocassa.Keys{\n\t\t\tPartitionKeys: []string{\"user_id\", \"identity_id\"},\n\t\t}).WithOptions(gocassa.Options{TableName: \"user_identity\"})\n\n\t\terr = userIdentityTable.Where(gocassa.Eq(\"user_id\", userIdentity.UserId.String()), gocassa.Eq(\"identity_id\", userIdentity.Id.String())).\n\t\t\tUpdate(cassaFields).Run()\n\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Errorf(\"[CassandraBackend]UpdateUserIdentity failed to UPDATE with fields %+v\", cassaFields)\n\t\t\treturn err\n\t\t}\n\n\t\t// update lookup tables\n\t\treturn cb.UpdateLookups(userIdentity, nil, false)\n\n\t}\n\n\treturn err\n}\n\n// UpdateRemoteInfos is a convenient way to quickly update infos map without the need of an already created UserIdentity object\nfunc (cb *CassandraBackend) UpdateRemoteInfosMap(userId, remoteId string, infos map[string]string) error {\n\t// check if identity exists before executing UPDATE because `IF EXISTS` statement not supported by scylladb as of february 2019\n\tif cb.SessionQuery(`SELECT user_id FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, remoteId).Iter().NumRows() == 0 {\n\t\treturn errors.New(\"not found\")\n\t}\n\tuserIdentityTable := cb.IKeyspace.Table(\"user_identity\", &UserIdentity{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"identity_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"user_identity\"})\n\n\treturn userIdentityTable.Where(gocassa.Eq(\"user_id\", userId), gocassa.Eq(\"identity_id\", remoteId)).\n\t\tUpdate(map[string]interface{}{\n\t\t\t\"infos\": infos,\n\t\t}).Run()\n}\n\n// RetrieveRemoteInfos is a convenient way to quickly retrieve infos map without the need of an already created UserIdentity object\nfunc (cb *CassandraBackend) RetrieveRemoteInfosMap(userId, remoteId string) (infos map[string]string, err error) {\n\tm := map[string]interface{}{}\n\tinfos = map[string]string{}\n\tq := cb.SessionQuery(`SELECT infos FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, remoteId)\n\terr = q.MapScan(m)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k, v := range m[\"infos\"].(map[string]string) {\n\t\tinfos[k] = v\n\t}\n\treturn\n}\n\nfunc (cb *CassandraBackend) RetrieveRemoteIdentities(userId string, withCredentials bool) (userIdentities []*UserIdentity, err error) {\n\tvar count int\n\titer := cb.SessionQuery(`SELECT identity_id FROM identity_type_lookup WHERE type = ? AND user_id = ?`, RemoteIdentity, userId).Iter()\n\tfor {\n\t\tvar identityID gocql.UUID\n\t\tif !iter.Scan(&identityID) {\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t\ti := make(map[string]interface{})\n\t\te := cb.SessionQuery(`SELECT * FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, identityID).MapScan(i)\n\t\tif e != nil {\n\t\t\tlog.Warnf(\"[CassandraBackend] RetrieveRemoteIdentities failed on inconsistency between identity_type_lookup and user_identity for user %s and identity %s\", userId, identityID)\n\t\t\tcontinue\n\t\t}\n\t\tidentity := new(UserIdentity)\n\t\tidentity.UnmarshalCQLMap(i)\n\t\tif withCredentials {\n\t\t\tcred, err := cb.RetrieveCredentials(userId, identity.Id.String())\n\t\t\tif err != nil {\n\t\t\t\t// return user identity even if credentials retrieval failed\n\t\t\t\tcred = Credentials{}\n\t\t\t}\n\t\t\tidentity.Credentials = &cred\n\t\t} else {\n\t\t\t// discard credentials\n\t\t\tidentity.Credentials = nil\n\t\t}\n\t\tuserIdentities = append(userIdentities, identity)\n\t}\n\tif count == 0 {\n\t\terr = errors.New(\"not found\")\n\t\treturn\n\t}\n\treturn\n}\n\n// RetrieveAllRemotes returns a chan to range over all remote identities found in db\nfunc (cb *CassandraBackend) RetrieveAllRemotes(withCredentials bool) (<-chan *UserIdentity, error) {\n\n\tch := make(chan *UserIdentity)\n\tgo func(cb *CassandraBackend, ch chan *UserIdentity) {\n\t\titer := cb.SessionQuery(`SELECT user_id, identity_id from identity_type_lookup WHERE type = ?`, RemoteIdentity).Iter()\n\n\t\tfor {\n\t\t\tvar userId, identityId gocql.UUID\n\t\t\tif !iter.Scan(&userId, &identityId) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tuserIdentity, err := cb.RetrieveUserIdentity(userId.String(), identityId.String(), withCredentials)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"[CassandraBackend]RetrieveAllRemotes fails to retrieve identity for user %s and identity_id %s\", userId.String(), identityId.String())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif withCredentials {\n\t\t\t\tcred, err := cb.RetrieveCredentials(userIdentity.UserId.String(), userIdentity.Id.String())\n\t\t\t\tif err != nil {\n\t\t\t\t\tuserIdentity.Credentials = &cred\n\t\t\t\t} else {\n\t\t\t\t\tuserIdentity.Credentials = nil\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tuserIdentity.Credentials = nil\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase ch <- userIdentity:\n\t\t\tcase <-time.After(cb.Timeout):\n\t\t\t\tlog.Warn(\"[RetrieveAllRemote] write timeout on chan\")\n\t\t\t}\n\t\t}\n\n\t\titer.Close()\n\t\tclose(ch)\n\t}(cb, ch)\n\n\treturn ch, nil\n}\n\nfunc (cb *CassandraBackend) DeleteUserIdentity(userIdentity *UserIdentity) error {\n\tif cb.UseVault {\n\t\terr := cb.Vault.DeleteCredentials(userIdentity.UserId.String(), userIdentity.Id.String())\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"[CassandraBackend] DeleteUserIdentity failed to delete credentials in vault\")\n\t\t}\n\t}\n\t// delete related rows in relevant lookup tables\n\terr := cb.DeleteLookups(userIdentity)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[CassandraBackend] DeleteUserIdentity: failed to delete lookups\")\n\t}\n\treturn cb.SessionQuery(`DELETE FROM user_identity WHERE user_id = ? AND identity_id = ?`, userIdentity.UserId, userIdentity.Id).Exec()\n}\n\n// LookupIdentityByIdentifier retrieve one or more identity_id depending on given parameters :\n// an identifier (mandatory)\n// other params could be protocol string, user_id string\n// returns an array of [user_id, identity_id]\nfunc (cb *CassandraBackend) LookupIdentityByIdentifier(identifier string, params ...string) (identities [][2]string, err error) {\n\tif identifier == \"\" {\n\t\terr = errors.New(\"identifier is mandatory\")\n\t\treturn\n\t}\n\tidentifier = strings.ToLower(identifier)\n\tvar rows []map[string]interface{}\n\tswitch len(params) {\n\tcase 0:\n\t\trows, err = cb.SessionQuery(`SELECT * from identity_lookup WHERE identifier = ?`, identifier).Iter().SliceMap()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\tcase 1:\n\t\trows, err = cb.SessionQuery(`SELECT * from identity_lookup WHERE identifier = ? AND protocol = ?`,\n\t\t\tidentifier, params[0]).Iter().SliceMap()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\tcase 2:\n\t\trows, err = cb.SessionQuery(`SELECT * from identity_lookup WHERE identifier = ? AND protocol = ? AND user_id = ?`,\n\t\t\tidentifier, params[0], params[1]).Iter().SliceMap()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\tdefault:\n\t\terr = errors.New(\"too many params provided\")\n\t\treturn\n\t}\n\tfor _, row := range rows {\n\t\tidentities = append(identities, [2]string{\n\t\t\trow[\"user_id\"].(gocql.UUID).String(),\n\t\t\trow[\"identity_id\"].(gocql.UUID).String(),\n\t\t})\n\t}\n\treturn\n}\n\n// LookupIdentityByType retrieve one or more identity_id depending on given parameters :\n// a type (mandatory)\n// a user_id (optional)\n// returns an array of [user_id, identity_id]\nfunc (cb *CassandraBackend) LookupIdentityByType(identityType string, user_id ...string) (identities [][2]string, err error) {\n\tif identityType == \"\" {\n\t\terr = errors.New(\"identity type is mandatory\")\n\t\treturn\n\t}\n\tvar rows []map[string]interface{}\n\tswitch len(user_id) {\n\tcase 0:\n\t\trows, err = cb.SessionQuery(`SELECT * from identity_type_lookup WHERE type = ?`, identityType).Iter().SliceMap()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\tcase 1:\n\t\trows, err = cb.SessionQuery(`SELECT * from identity_type_lookup WHERE type = ? AND user_id = ?`, identityType, user_id[0]).Iter().SliceMap()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\tdefault:\n\t\terr = errors.New(\"too many user_id provided\")\n\t\treturn\n\t}\n\tfor _, row := range rows {\n\t\tidentities = append(identities, [2]string{\n\t\t\trow[\"user_id\"].(gocql.UUID).String(),\n\t\t\trow[\"identity_id\"].(gocql.UUID).String(),\n\t\t})\n\t}\n\treturn\n}\n\n// IsLocalIdentity is helper to make a lookup query in cassandra and check if a UserIdentity is \"local\"\n// return true only if identity has been found and is local\nfunc (cb *CassandraBackend) IsLocalIdentity(userId, identityId string) bool {\n\tvar identityType string\n\terr := cb.SessionQuery(`SELECT type FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, identityId).Scan(&identityType)\n\tif err != nil || identityType != LocalIdentity {\n\t\treturn false\n\t}\n\treturn true\n}\n\n// IsRemoteIdentity is helper to make a lookup query in cassandra and check if a UserIdentity is \"local\"\n// return true only if identity has been found and is local\nfunc (cb *CassandraBackend) IsRemoteIdentity(userId, identityId string) bool {\n\tvar identityType string\n\terr := cb.SessionQuery(`SELECT type FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, identityId).Scan(&identityType)\n\tif err != nil || identityType != RemoteIdentity {\n\t\treturn false\n\t}\n\treturn true\n}\n\n// TimestampRemoteLastCheck writes timestamp to user_identity.last_check property.\n// If no time is provided defaults to time.Now()\nfunc (cb *CassandraBackend) TimestampRemoteLastCheck(userId, remoteId string, t ...time.Time) error {\n\t// check if identity exists before executing UPDATE because `IF EXISTS` statement not supported by scylladb as of february 2019\n\tif cb.SessionQuery(`SELECT user_id FROM user_identity WHERE user_id = ? AND identity_id = ?`, userId, remoteId).Iter().NumRows() == 0 {\n\t\treturn errors.New(\"not found\")\n\t}\n\tvar timestamp time.Time\n\tif len(t) < 1 {\n\t\ttimestamp = time.Now()\n\t} else {\n\t\ttimestamp = t[0]\n\t}\n\treturn cb.SessionQuery(`UPDATE user_identity SET last_check = ? WHERE user_id = ? AND identity_id = ?`, timestamp, userId, remoteId).Exec()\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/keys.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage store\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/gocassa/gocassa\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n)\n\nfunc (cb *CassandraBackend) CreatePGPPubKey(pubkey *PublicKey) CaliopenError {\n\t// write complete statement because gocassa failed to retrieve tag \"use\" and write quotes to cassandra\n\terr := cb.SessionQuery(`UPDATE public_key SET \n\talg = ?,\n\tcrv = ?,\n\tdate_insert = ?,\n\tdate_update = ?,\n\temails = ?,\n\texpire_date = ?,\n\tfingerprint = ?,\n\tkey = ?,\n\tkty = ?,\n\tlabel = ?,\n\tresource_type = ?,\n\tsize = ?,\n\t\"use\" = ?,\n\tx = ?,\n\ty = ?\n\tWHERE user_id = ? AND resource_id = ? AND key_id = ?`,\n\t\tpubkey.Algorithm,\n\t\tpubkey.Curve,\n\t\tpubkey.DateInsert,\n\t\tpubkey.DateUpdate,\n\t\tpubkey.Emails,\n\t\tpubkey.ExpireDate,\n\t\tpubkey.Fingerprint,\n\t\tpubkey.Key,\n\t\tpubkey.KeyType,\n\t\tpubkey.Label,\n\t\tpubkey.ResourceType,\n\t\tpubkey.Size,\n\t\tpubkey.Use,\n\t\tpubkey.X,\n\t\tpubkey.Y,\n\t\tpubkey.UserId,\n\t\tpubkey.ResourceId,\n\t\tpubkey.KeyId).Exec()\n\n\tif err != nil {\n\t\treturn NewCaliopenErrf(DbCaliopenErr, \"[CassandraBackend]CreatePGPPubKey db error : %s\", err.Error())\n\t}\n\treturn nil\n}\n\nfunc (cb *CassandraBackend) RetrieveContactPubKeys(userId, contactId string) (keys PublicKeys, err CaliopenError) {\n\tks, e := cb.SessionQuery(`SELECT * FROM public_key WHERE user_id = ? AND resource_id = ?`, userId, contactId).Iter().SliceMap()\n\tif e != nil {\n\t\treturn nil, WrapCaliopenErrf(e, DbCaliopenErr, \"[CassandraBackend]RetrieveContactPubKeys failed\")\n\t}\n\tfor _, k := range ks {\n\t\tpubkey := new(PublicKey)\n\t\tpubkey.UnmarshalCQLMap(k)\n\t\tkeys = append(keys, *pubkey)\n\t}\n\treturn\n}\n\nfunc (cb *CassandraBackend) RetrievePubKey(userId, resourceId, keyId string) (pubkey *PublicKey, err CaliopenError) {\n\tresult := map[string]interface{}{}\n\te := cb.SessionQuery(`SELECT * FROM public_key WHERE user_id = ? AND resource_id = ? AND key_id = ?`, userId, resourceId, keyId).MapScan(result)\n\tif e != nil {\n\t\tif e.Error() == \"not found\" {\n\t\t\terr = WrapCaliopenErr(NewCaliopenErr(NotFoundCaliopenErr, \"not found\"), DbCaliopenErr, \"[CassandraBackend]RetrievePubKey not found in db\")\n\t\t} else {\n\t\t\terr = NewCaliopenErrf(DbCaliopenErr, \"[CassandraBackend]RetrievePubKey returned error from cassandra : %s\", e.Error())\n\t\t}\n\t\treturn\n\t}\n\tpubkey = new(PublicKey)\n\tpubkey.UnmarshalCQLMap(result)\n\treturn\n}\n\nfunc (cb *CassandraBackend) UpdatePubKey(newPubKey, oldPubKey *PublicKey, fields map[string]interface{}) CaliopenError {\n\n\t//get cassandra's field name for each field to modify\n\tcassaFields := map[string]interface{}{}\n\tfor field, value := range fields {\n\t\tcassaField, err := reflections.GetFieldTag(newPubKey, field, \"cql\")\n\t\tif err != nil {\n\t\t\treturn NewCaliopenErrf(FailDependencyCaliopenErr, \"[CassandraBackend]UpdatePubKey failed to find a cql field for object field %s\", field)\n\t\t}\n\t\tif cassaField != \"-\" {\n\t\t\tcassaFields[cassaField] = value\n\t\t}\n\t}\n\n\tkeyT := cb.IKeyspace.Table(\"public_key\", &PublicKey{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"resource_id\", \"key_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"public_key\"}) // need to overwrite default gocassa table naming convention\n\n\terr := keyT.\n\t\tWhere(gocassa.Eq(\"user_id\", newPubKey.UserId.String()),\n\t\t\tgocassa.Eq(\"resource_id\", newPubKey.ResourceId.String()),\n\t\t\tgocassa.Eq(\"key_id\", newPubKey.KeyId.String())).\n\t\tUpdate(cassaFields).\n\t\tRun()\n\tif err != nil {\n\t\treturn NewCaliopenErrf(DbCaliopenErr, \"[CassandraBackend]UpdatePubKey failed to call store with cassandra error : %s\", err.Error())\n\t}\n\treturn nil\n}\n\nfunc (cb *CassandraBackend) DeletePubKey(pubkey *PublicKey) CaliopenError {\n\te := cb.SessionQuery(`DELETE FROM public_key WHERE user_id = ? AND resource_id = ? AND key_id = ?`, pubkey.UserId, pubkey.ResourceId, pubkey.KeyId).Exec()\n\tif e != nil {\n\t\treturn NewCaliopenErrf(DbCaliopenErr, \"[CassandraBackend]DeletePubKey returned err from cassandra : %s\", e.Error())\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/lookups.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage store\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n)\n\n// UpdateLookups ensure that tables lookup related to are up to date in db,\n// it updates values with `new` state and delete lookups that have been removed\nfunc (cb *CassandraBackend) UpdateLookups(new, old HasLookup, isNew bool) error {\n\tvar err error\n\tif cb.Session.Closed() {\n\t\terr = (*cb).initialize(cb.CassandraConfig)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"[CassandraBackend] UpdateLookups failed to re-init a gocql Session\")\n\t\t}\n\t}\n\t// update db with current values\n\tfor _, lookup := range new.GetLookupsTables() {\n\t\tif updateFunc := lookup.UpdateLookups(new, old); updateFunc != nil {\n\t\t\terr = updateFunc(cb.Session)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteLookups ensure that tables related (joined) to obj are up to date in db,\n// ie related objects are deleted accordingly.\nfunc (cb *CassandraBackend) DeleteLookups(obj HasLookup) error {\n\tvar err error\n\tif cb.Session.Closed() {\n\t\terr = (*cb).initialize(cb.CassandraConfig)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"[CassandraBackend] DeleteLookups failed to re-init a gocql Session\")\n\t\t}\n\t}\n\tfor _, lookup := range obj.GetLookupsTables() {\n\t\tif cleanup := lookup.CleanupLookups(obj); cleanup != nil {\n\t\t\terr = cleanup(cb.Session)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/messages.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/gocassa/gocassa\"\n\t\"github.com/gocql/gocql\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n)\n\nfunc (cb *CassandraBackend) CreateMessage(msg *Message) error {\n\n\tmessageT := cb.IKeyspace.Table(\"message\", &Message{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"message_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"message\"}) // need to overwrite default gocassa table naming convention\n\n\treturn messageT.Set(msg).Run()\n}\n\nfunc (cb *CassandraBackend) RetrieveMessage(user_id, msg_id string) (msg *Message, err error) {\n\tmsg = new(Message).NewEmpty().(*Message) // correctly initialize nested values\n\tm := map[string]interface{}{}\n\terr = cb.SessionQuery(`SELECT * FROM message WHERE user_id = ? and message_id = ?`, user_id, msg_id).MapScan(m)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmsg.UnmarshalCQLMap(m)\n\n\t// embed up-to-date participants' details\n\tMessagesParticipantsDetails(cb.GetSession(), []Message{*msg})\n\n\treturn msg, err\n}\n\n// update given fields for a message in db\nfunc (cb *CassandraBackend) UpdateMessage(msg *Message, fields map[string]interface{}) error {\n\n\t//get cassandra's field name for each field to modify\n\tcassaFields := map[string]interface{}{}\n\tfor field, value := range fields {\n\t\tcassaField, err := reflections.GetFieldTag(msg, field, \"cql\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"[CassandraBackend] UpdateMessage failed to find a cql field for object field %s\", field)\n\t\t}\n\t\tcassaFields[cassaField] = value\n\t}\n\n\tmessageT := cb.IKeyspace.Table(\"message\", &Message{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"message_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"message\"}) // need to overwrite default gocassa table naming convention\n\n\terr := messageT.\n\t\tWhere(gocassa.Eq(\"user_id\", msg.User_id.String()), gocassa.Eq(\"message_id\", msg.Message_id.String())).\n\t\tUpdate(cassaFields).\n\t\tRun()\n\n\tif externalRefs, ok := fields[\"External_references\"].(ExternalReferences); ok {\n\t\t// need to update lookup table\n\t\terr := cb.SessionQuery(`INSERT INTO message_external_ref_lookup (user_id,external_msg_id,identity_id,message_id) VALUES (?,?,?,?)`, msg.User_id, externalRefs.Message_id, msg.UserIdentities[0], msg.Message_id).Exec()\n\t\tif err != nil {\n\t\t\tlogrus.WithError(err).Errorf(\"UpdateMessage failed to update external ref lookup for user %s, message id %s, user identity %s, external ref %s\", msg.User_id, msg.Message_id, msg.UserIdentities[0], externalRefs.Message_id)\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc (cb *CassandraBackend) DeleteMessage(msg *Message) error {\n\treturn errors.New(\"[CassandraBackend] DeleteMessage not yet implemented\")\n}\n\nfunc (cb *CassandraBackend) SetMessageUnread(user_id, message_id string, status bool) (err error) {\n\tq := cb.SessionQuery(`UPDATE message SET is_unread= ? WHERE message_id = ? AND user_id = ?`, status, message_id, user_id)\n\treturn q.Exec()\n}\n\n// SeekMessageByExternalRef return first message found in cassandra's message_external_ref_lookup table, if any.\n// if identityID param is an empty string, `identity_id` key will be ignored in cql request\nfunc (cb *CassandraBackend) SeekMessageByExternalRef(userID, externalMessageID, identityID string) (messageID UUID, err error) {\n\tresult := map[string]interface{}{}\n\tif identityID == \"\" {\n\t\terr = cb.SessionQuery(`SELECT message_id FROM message_external_ref_lookup WHERE user_id = ? AND external_msg_id = ? LIMIT 1\"`, userID, externalMessageID).MapScan(result)\n\t} else {\n\t\terr = cb.SessionQuery(`SELECT message_id FROM message_external_ref_lookup WHERE user_id = ? AND external_msg_id = ? AND identity_id = ?`, userID, externalMessageID, identityID).MapScan(result)\n\t}\n\tif err != nil || result[\"message_id\"] == nil {\n\t\treturn EmptyUUID, err\n\t}\n\treturn UUID(result[\"message_id\"].(gocql.UUID)), err\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/notifications.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage store\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocassa/gocassa\"\n\t\"github.com/gocql/gocql\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc (cb *CassandraBackend) PutNotificationInQueue(notif *Notification) error {\n\n\tvar ttl NotificationTTL\n\n\t// TODO: find a way to avoid retrieving duration from cassandra for each Put\n\terr := cb.SessionQuery(`SELECT * FROM notification_ttl WHERE ttl_code = ?`, notif.TTLcode).Scan(&ttl.TTLcode, &ttl.Description, &ttl.TTLduration)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[CassandraBackend]PutNotificationInQueue failed to retrieve ttl\")\n\t\treturn err\n\t}\n\n\tnotifT := cb.IKeyspace.Table(\"notification\", &NotificationModel{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"notif_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"notification\"})\n\n\tn := NotificationModel{\n\t\tBody:      notif.Body,\n\t\tEmitter:   notif.Emitter,\n\t\tNotifId:   notif.NotifId.String(),\n\t\tReference: notif.Reference,\n\t\tType:      notif.Type,\n\t\tUserId:    notif.User.UserId.String(),\n\t}\n\n\treturn notifT.Set(&n).WithOptions(gocassa.Options{TTL: time.Duration(ttl.TTLduration) * time.Second}).Run()\n}\n\nfunc (cb *CassandraBackend) NotificationsByTime(userId string, from, to time.Time) ([]Notification, error) {\n\n\tvar query_builder strings.Builder\n\tvalues := []interface{}{userId}\n\tnotifs := []Notification{}\n\n\tquery_builder.WriteString(`SELECT * FROM notification WHERE user_id = ?`)\n\n\tif !from.IsZero() {\n\t\tquery_builder.WriteString(` AND notif_id > minTimeuuid(?)`)\n\t\tvalues = append(values, from)\n\t}\n\n\tif !to.IsZero() {\n\t\tquery_builder.WriteString(` AND notif_id < maxTimeuuid(?)`)\n\t\tvalues = append(values, to)\n\t}\n\n\tnotifs_found, err := cb.SessionQuery(query_builder.String(), values...).Iter().SliceMap()\n\tif err != nil {\n\t\treturn notifs, err\n\t}\n\tif len(notifs_found) == 0 {\n\t\treturn []Notification{}, errors.New(\"notifications not found\")\n\t}\n\n\tfor _, notif := range notifs_found {\n\t\tn := new(Notification)\n\t\tn.UnmarshalCQLMap(notif)\n\t\tnotifs = append(notifs, *n)\n\t}\n\n\treturn notifs, nil\n}\n\nfunc (cb *CassandraBackend) NotificationsByID(userID, from, to string) ([]Notification, error) {\n\tvar query_builder strings.Builder\n\tvalues := []interface{}{userID}\n\tnotifs := []Notification{}\n\n\tquery_builder.WriteString(`SELECT * FROM notification WHERE user_id = ?`)\n\n\tif from != \"\" {\n\t\tquery_builder.WriteString(` AND notif_id > ?`)\n\t\tvalues = append(values, from)\n\t}\n\n\tif to != \"\" {\n\t\tquery_builder.WriteString(` AND notif_id < ?`)\n\t\tvalues = append(values, to)\n\t}\n\n\tnotifs_found, err := cb.SessionQuery(query_builder.String(), values...).Iter().SliceMap()\n\tif err != nil {\n\t\treturn notifs, err\n\t}\n\tif len(notifs_found) == 0 {\n\t\treturn []Notification{}, errors.New(\"notifications not found\")\n\t}\n\n\tfor _, notif := range notifs_found {\n\t\tn := new(Notification)\n\t\tn.UnmarshalCQLMap(notif)\n\t\tnotifs = append(notifs, *n)\n\t}\n\n\treturn notifs, nil\n}\n\nfunc (cb *CassandraBackend) DeleteNotifications(userId string, until time.Time) error {\n\n\tvar query_builder strings.Builder\n\tvalues := []interface{}{userId}\n\n\t/***\n\t\t**** as of 2018 may 16th, production server is running cassandra 2.4.1,\n\t    **** which is not able to do a range DELETE.\n\n\tquery_builder.WriteString(`DELETE FROM notification WHERE user_id = ?`)\n\n\tif !until.IsZero() {\n\t\tquery_builder.WriteString(` AND notif_id > minTimeuuid(0) AND notif_id < maxTimeuuid(?)`)\n\t\tvalues = append(values, until)\n\t}\n\n\treturn cb.Session.Query(query_builder.String(), values...).Exec()\n\t*/\n\n\tquery_builder.WriteString(`SELECT notif_id FROM notification WHERE user_id = ?`)\n\n\tif !until.IsZero() {\n\t\tquery_builder.WriteString(` AND notif_id > minTimeuuid(0) AND notif_id < maxTimeuuid(?)`)\n\t\tvalues = append(values, until)\n\t}\n\n\tvar notifId gocql.UUID\n\titer := cb.SessionQuery(query_builder.String(), values...).Iter()\n\n\tfor iter.Scan(&notifId) {\n\t\te := cb.DeleteNotification(userId, notifId.String())\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Warnf(\"[DeleteNotifications] failed to delete notif %s for user %s\", notifId.String(), userId)\n\t\t}\n\t}\n\n\tif err := iter.Close(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (cb *CassandraBackend) RetrieveNotification(userID, notifID string) (notif Notification, err error) {\n\tnotifs, err := cb.SessionQuery(`SELECT * FROM notification WHERE user_id = ? AND notif_id = ?`, userID, notifID).Iter().SliceMap()\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(notifs) == 0 {\n\t\terr = errors.New(\"not found\")\n\t\treturn\n\t}\n\tnotif = Notification{}\n\tnotif.UnmarshalCQLMap(notifs[0])\n\treturn notif, nil\n}\n\nfunc (cb *CassandraBackend) DeleteNotification(userID, notifID string) error {\n\treturn cb.SessionQuery(`DELETE FROM notification WHERE user_id = ? AND notif_id = ?`, userID, notifID).Exec()\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/participant_lookup.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/gocassa/gocassa\"\n\t\"strings\"\n)\n\nfunc (cb *CassandraBackend) LookupHash(user_id UUID, uri string) (hashes []HashLookup, err error) {\n\trawHashes := []map[string]interface{}{}\n\tclean_uri := strings.ToLower(uri)\n\trawHashes, err = cb.SessionQuery(`SELECT * FROM hash_lookup WHERE user_id = ? AND uri = ?`, user_id, clean_uri).Iter().SliceMap()\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(rawHashes) == 0 {\n\t\terr = errors.New(\"not found\")\n\t\treturn\n\t}\n\tfor _, hash := range rawHashes {\n\t\th := new(HashLookup)\n\t\th.UnmarshalCQLMap(hash)\n\t\thashes = append(hashes, *h)\n\t}\n\treturn\n}\n\nfunc (cb *CassandraBackend) CreateHashLookup(participant HashLookup) error {\n\tlookupT := cb.IKeyspace.Table(\"hash_lookup\", &HashLookup{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\", \"uri\", \"hash\"},\n\t}).WithOptions(gocassa.Options{TableName: \"hash_lookup\"}) // need to overwrite default gocassa table naming convention\n\n\t// save lookup\n\terr := lookupT.Set(participant).Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"[CassandraBackend] CreateHashLookup: %s\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/providers.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocassa/gocassa\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"time\"\n)\n\nfunc (cb *CassandraBackend) CreateProvider(provider *Provider) CaliopenError {\n\tif provider.DateInsert.IsZero() {\n\t\tprovider.DateInsert = time.Now()\n\t}\n\ttable := cb.IKeyspace.Table(\"provider\", &Provider{},\n\t\tgocassa.Keys{\n\t\t\tPartitionKeys: []string{\"name\", \"instance\"},\n\t\t}).WithOptions(gocassa.Options{TableName: \"provider\"})\n\n\terr := table.Set(provider).Run()\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[CassandraBackend] set fails for %+v\", provider)\n\t\treturn WrapCaliopenErrf(err, DbCaliopenErr, \"[CassandraBackend] CreateProvider fails : %s\", err.Error())\n\t}\n\treturn nil\n}\n\nfunc (cb *CassandraBackend) RetrieveProvider(name, instance string) (provider *Provider, err CaliopenError) {\n\tprovider = new(Provider)\n\tm := map[string]interface{}{}\n\tq := cb.SessionQuery(`SELECT * FROM provider WHERE name = ? AND instance = ?`, name, instance)\n\te := q.MapScan(m)\n\tif e != nil {\n\t\treturn nil, WrapCaliopenErr(e, DbCaliopenErr, \"\")\n\t}\n\tprovider.UnmarshalCQLmap(m)\n\treturn\n}\n\nfunc (cb *CassandraBackend) UpdateProvider(provider *Provider, fields map[string]interface{}) (err CaliopenError) {\n\t// check if provider exists before executing UPDATE because `IF EXISTST` statement not supported by scylladb as of june 2019\n\tif cb.SessionQuery(`SELECT name FROM providers WHERE name = ? AND instance = ?`, provider.Name, provider.Instance).Iter().NumRows() == 0 {\n\t\treturn NewCaliopenErr(NotFoundCaliopenErr, \"not found\")\n\t}\n\tif len(fields) > 0 {\n\t\t//get cassandra's field name for each field to modify\n\t\tcassaFields := map[string]interface{}{}\n\t\tfor field, value := range fields {\n\t\t\tcassaField, err := reflections.GetFieldTag(provider, field, \"cql\")\n\t\t\tif err != nil {\n\t\t\t\treturn NewCaliopenErrf(UnprocessableCaliopenErr, \"[CassandraBackend] UpdateProvider failed to find a cql field for object field %s\", field)\n\t\t\t}\n\t\t\tif cassaField != \"-\" {\n\t\t\t\tcassaFields[cassaField] = value\n\t\t\t}\n\t\t}\n\t\ttable := cb.IKeyspace.Table(\"provider\", &Provider{},\n\t\t\tgocassa.Keys{\n\t\t\t\tPartitionKeys: []string{\"name\", \"instance\"},\n\t\t\t}).WithOptions(gocassa.Options{TableName: \"provider\"})\n\t\te := table.Where(gocassa.Eq(\"name\", provider.Name), gocassa.Eq(\"instance\", provider.Instance)).Update(cassaFields).Run()\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Errorf(\"[CassandraBackend] UpdateProvider failed to UPDATE with fields %+v\", cassaFields)\n\t\t\treturn WrapCaliopenErr(e, DbCaliopenErr, \"\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (cb *CassandraBackend) DeleteProvider(provider *Provider) CaliopenError {\n\terr := cb.SessionQuery(`DELETE FROM provider WHERE name = ? AND instance = ?`, provider.Name, provider.Instance).Exec()\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[CassandraBackend] DeleteProvider failed for %+v\", provider)\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"\")\n\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/raw_messages.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocassa/gocassa\"\n\t\"github.com/gocql/gocql\"\n\t\"io\"\n)\n\nfunc (cb *CassandraBackend) StoreRawMessage(msg RawMessage) (err error) {\n\trawMsgTable := cb.IKeyspace.MapTable(\"raw_message\", \"raw_msg_id\", &RawMessage{})\n\tconsistency := gocql.Consistency(cb.CassandraConfig.Consistency)\n\n\t// need to overwrite default gocassa naming convention that add `_map_name` to the mapTable name\n\trawMsgTable = rawMsgTable.WithOptions(gocassa.Options{\n\t\tTableName:   \"raw_message\",\n\t\tConsistency: &consistency,\n\t})\n\n\t// handle emails too large to fit into cassandra\n\tif msg.Raw_Size > cb.CassandraConfig.SizeLimit {\n\t\tif cb.CassandraConfig.WithObjStore {\n\t\t\turi, err := cb.ObjectsStore.PutRawMessage(msg.Raw_msg_id, msg.Raw_data)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tmsg.URI = uri\n\t\t\tmsg.Raw_data = \"\"\n\t\t} else {\n\t\t\treturn errors.New(\"Object too large to fit into cassandra\")\n\t\t}\n\t}\n\n\tif err = rawMsgTable.Set(msg).Run(); err != nil {\n\t\treturn err\n\t}\n\treturn\n}\n\n// returns a RawMessage object, with 'raw_data' property always filled\n// (even if raw_data was stored outside of cassandra)\nfunc (cb *CassandraBackend) GetRawMessage(raw_message_id string) (message RawMessage, err error) {\n\n\tm := map[string]interface{}{}\n\tq := cb.SessionQuery(`SELECT * FROM raw_message WHERE raw_msg_id = ?`, raw_message_id)\n\terr = q.MapScan(m)\n\tif err != nil {\n\t\treturn RawMessage{}, err\n\t}\n\tmessage.UnmarshalCQLMap(m)\n\n\t// check if raw_data is filled or if we need to get it from object store\n\tif message.URI != \"\" && len(message.Raw_data) == 0 {\n\t\treader, e := cb.ObjectsStore.GetObject(message.URI)\n\t\tif e != nil {\n\t\t\treturn RawMessage{}, e\n\t\t}\n\t\traw_data := make([]byte, message.Raw_Size)\n\t\ts, e := reader.Read(raw_data)\n\t\tif s == 0 || e != io.EOF {\n\t\t\treturn RawMessage{}, e\n\t\t}\n\t\tif uint64(s) != message.Raw_Size {\n\t\t\tlog.Warnf(\"[cassandra.GetRawMessage] : Read %d bytes from Object Store, expected %d.\", s, message.Raw_Size)\n\t\t}\n\t\tmessage.Raw_data = string(raw_data)\n\t}\n\n\treturn\n}\n\nfunc (cb *CassandraBackend) SetDeliveredStatus(raw_msg_id string, delivered bool) error {\n\treturn cb.SessionQuery(`UPDATE raw_message SET delivered = ? WHERE raw_msg_id = ?`, delivered, raw_msg_id).Exec()\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/related.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage store\n\nimport (\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/gocassa/gocassa\"\n\t\"github.com/mitchellh/hashstructure\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"reflect\"\n)\n\ntype relatedReference struct {\n\tKeys      map[string]string\n\tObject    HasTable\n\tRelations []gocassa.Relation\n\tTable     string\n}\n\n// UpdateRelated ensure that tables related (joined) to obj are up to date in db,\n// ie related objects are updated, created or deleted accordingly.\nfunc (cb *CassandraBackend) UpdateRelated(new, old HasRelated, isNew bool) error {\n\toldRelateds := map[uint64]relatedReference{}\n\tif isNew {\n\t\tMarshalRelated(new)\n\t} else {\n\t\t// build a map with old's relateds list to find if some relateds have been deleted in new\n\t\tfor oldRelated := range old.GetSetRelated() {\n\t\t\tref := relatedReference{\n\t\t\t\tKeys:      map[string]string{},\n\t\t\t\tObject:    oldRelated.(HasTable),\n\t\t\t\tRelations: []gocassa.Relation{},\n\t\t\t}\n\t\t\tref.Table, ref.Keys, _ = oldRelated.(HasTable).GetTableInfos()\n\n\t\t\t// build relations to select the right row\n\t\t\trelations := map[string]interface{}{}\n\t\t\tfor property, key := range ref.Keys {\n\t\t\t\tvalue, err := reflections.GetField(oldRelated, property)\n\t\t\t\tif err == nil {\n\t\t\t\t\tref.Relations = append(ref.Relations, gocassa.Eq(key, value))\n\t\t\t\t\trelations[key] = value\n\t\t\t\t}\n\t\t\t}\n\t\t\thash, err := hashstructure.Hash(relations, nil)\n\t\t\tif err == nil {\n\t\t\t\toldRelateds[hash] = ref\n\t\t\t}\n\t\t}\n\t}\n\n\tfor newRelated := range new.GetSetRelated() {\n\t\tif rel, ok := newRelated.(HasTable); ok {\n\t\t\ttable, mapKeys, _ := rel.GetTableInfos()\n\n\t\t\t// build gocassa Table object\n\t\t\tkeys := []string{}\n\t\t\tfor _, key := range mapKeys {\n\t\t\t\tkeys = append(keys, key)\n\t\t\t}\n\t\t\tT := cb.IKeyspace.Table(table, rel, gocassa.Keys{\n\t\t\t\tPartitionKeys: keys,\n\t\t\t}).WithOptions(gocassa.Options{TableName: table}) // need to overwrite default gocassa table naming convention\n\n\t\t\terr := T.Set(rel).Run()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"[CassandraBackend] UpdateRelated: %s\", err)\n\t\t\t}\n\n\t\t\tif !isNew {\n\t\t\t\t// remove keys from oldRelated\n\t\t\t\t// thus oldRelateds map will only holds remaining entries that are not in the new state\n\t\t\t\trelations := map[string]interface{}{}\n\t\t\t\tfor property, key := range mapKeys {\n\t\t\t\t\tvalue, err := reflections.GetField(newRelated, property)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\trelations[key] = value\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\thash, err := hashstructure.Hash(relations, nil)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdelete(oldRelateds, hash)\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(oldRelateds) > 0 {\n\t\t// it remains relateds in the map, meaning these relateds have been removed from contact\n\t\t// need to delete related in joined table\n\t\tfor _, related := range oldRelateds {\n\t\t\t// build gocassa Table object\n\t\t\tkeys := []string{}\n\t\t\tfor _, key := range related.Keys {\n\t\t\t\tkeys = append(keys, key)\n\t\t\t}\n\t\t\tT := cb.IKeyspace.Table(related.Table, related.Object, gocassa.Keys{\n\t\t\t\tPartitionKeys: keys,\n\t\t\t}).WithOptions(gocassa.Options{TableName: related.Table}) // need to overwrite default gocassa table naming convention\n\n\t\t\t// delete row\n\t\t\terr := T.Where(related.Relations...).Delete().Run()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"[CassandraBackend] UpdateRelated: %s\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// RetrieveRelated fetches rows from related (joined) table(s) and embeds rows into obj.\nfunc (cb *CassandraBackend) RetrieveRelated(obj HasRelated) error {\n\n\tfor field, related := range obj.GetRelatedList() {\n\t\tif rel, ok := related.(HasTable); ok {\n\t\t\ttable, partitionKeys, collectionKeys := rel.GetTableInfos()\n\t\t\t// build gocassa Table object\n\t\t\tkeys := []string{}\n\t\t\tfor _, key := range partitionKeys {\n\t\t\t\tkeys = append(keys, key)\n\t\t\t}\n\t\t\tT := cb.IKeyspace.Table(table, rel, gocassa.Keys{\n\t\t\t\tPartitionKeys: keys,\n\t\t\t}).WithOptions(gocassa.Options{TableName: table}) // need to overwrite default gocassa table naming convention\n\n\t\t\t// build relations to select right rows\n\t\t\trelations := []gocassa.Relation{}\n\t\t\tfor property, key := range collectionKeys {\n\t\t\t\tvalue, err := reflections.GetField(obj, property)\n\t\t\t\tif err == nil && value != nil {\n\t\t\t\t\trelations = append(relations, gocassa.Eq(key, value))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// retrieve rows\n\t\t\trows := new([]map[string]interface{})\n\t\t\terr := T.Where(relations...).Read(rows).Run()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"[CassandraBackend] RetrieveRelated: %s\", err)\n\t\t\t}\n\n\t\t\t// put rows (of unknown type at compilation) into obj\n\n\t\t\tembeddedSlice, err := reflections.GetField(obj, field)\n\t\t\t// Create a slice to begin with\n\t\t\tembeddedSliceType := reflect.TypeOf(embeddedSlice)\n\t\t\ttoEmbed := reflect.MakeSlice(embeddedSliceType, 0, 1) // set capacity as needed\n\t\t\t// Create a pointer to the slice value\n\t\t\tp := reflect.New(toEmbed.Type())\n\t\t\t// Set the pointer to the slice (otherwise, my_slice would be 'un-addressable')\n\t\t\tp.Elem().Set(toEmbed)\n\t\t\tif err == nil {\n\t\t\t\tfor _, row := range *rows {\n\t\t\t\t\trel.UnmarshalCQLMap(row)\n\t\t\t\t\ttoEmbed = reflect.Append(toEmbed, reflect.Indirect(reflect.ValueOf(rel)))\n\t\t\t\t}\n\t\t\t}\n\t\t\treflections.SetField(obj, field, toEmbed.Interface())\n\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteRelated ensure that tables related (joined) to obj are up to date in db,\n// ie related objects are deleted accordingly.\nfunc (cb *CassandraBackend) DeleteRelated(obj HasRelated) error {\n\n\tfor related := range obj.GetSetRelated() {\n\t\tif rel, ok := related.(HasTable); ok {\n\n\t\t\ttable, mapKeys, _ := rel.GetTableInfos()\n\n\t\t\t// build gocassa Table object\n\t\t\tkeys := []string{}\n\t\t\tfor _, key := range mapKeys {\n\t\t\t\tkeys = append(keys, key)\n\t\t\t}\n\t\t\tT := cb.IKeyspace.Table(table, rel, gocassa.Keys{\n\t\t\t\tPartitionKeys: keys,\n\t\t\t}).WithOptions(gocassa.Options{TableName: table}) // need to overwrite default gocassa table naming convention\n\n\t\t\t// build relations to select the right row\n\t\t\trelations := []gocassa.Relation{}\n\t\t\tfor property, key := range mapKeys {\n\t\t\t\tvalue, err := reflections.GetField(obj, property)\n\t\t\t\tif err == nil {\n\t\t\t\t\trelations = append(relations, gocassa.Eq(key, value))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// delete row\n\t\t\terr := T.Where(relations...).Delete().Run()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"[CassandraBackend] UpdateRelated: %s\", err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/settings.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\nfunc (cb *CassandraBackend) GetSettings(user_id string) (settings *Settings, err error) {\n\n\tsettings = new(Settings).NewEmpty().(*Settings)\n\tm := map[string]interface{}{}\n\tq := cb.SessionQuery(`SELECT * FROM settings WHERE user_id = ?`, user_id)\n\terr = q.MapScan(m)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsettings.UnmarshalCQLMap(m)\n\treturn settings, err\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/tags.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage store\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/gocql/gocql\"\n\t\"time\"\n)\n\n// retrieve tags of type 'user' belonging to user_id\nfunc (cb *CassandraBackend) RetrieveUserTags(user_id string) (tags []Tag, err error) {\n\tall_tags, err := cb.SessionQuery(`SELECT * FROM user_tag WHERE user_id = ?`, user_id).Iter().SliceMap()\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(all_tags) == 0 {\n\t\terr = errors.New(\"tags not found\")\n\t\treturn\n\t}\n\tfor _, tag := range all_tags {\n\t\tt := new(Tag)\n\t\tt.UnmarshalCQLMap(tag)\n\t\ttags = append(tags, *t)\n\t}\n\treturn\n}\n\n// CreateTag inserts Tag into db\nfunc (cb *CassandraBackend) CreateTag(tag *Tag) error {\n\n\tuser_id, _ := gocql.UUIDFromBytes((*tag).User_id.Bytes())\n\t(*tag).Date_insert = time.Now()\n\t(*tag).Type = TagType(UserTag)\n\treturn cb.SessionQuery(`INSERT INTO user_tag (user_id, name, date_insert, importance_level, label, type) VALUES (?,?,?,?,?,?)`,\n\t\tuser_id,\n\t\t(*tag).Name,\n\t\t(*tag).Date_insert,\n\t\t(*tag).Importance_level,\n\t\t(*tag).Label,\n\t\t(*tag).Type).Exec()\n}\n\nfunc (cb *CassandraBackend) RetrieveTag(user_id, name string) (tag Tag, err error) {\n\ttags, err := cb.SessionQuery(`SELECT * FROM user_tag WHERE user_id = ? AND name = ?`, user_id, name).Iter().SliceMap()\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(tags) == 0 {\n\t\terr = errors.New(\"tag not found\")\n\t\treturn\n\t}\n\ttag = Tag{}\n\terr = tag.UnmarshalCQLMap(tags[0])\n\tif err != nil {\n\t\treturn\n\t}\n\treturn tag, nil\n}\n\nfunc (cb *CassandraBackend) UpdateTag(tag *Tag) error {\n\treturn cb.SessionQuery(`UPDATE user_tag SET importance_level = ?, label = ?, type = ? WHERE user_id = ? AND name = ?`,\n\t\ttag.Importance_level,\n\t\ttag.Label,\n\t\ttag.Type,\n\t\ttag.User_id,\n\t\ttag.Name,\n\t).Exec()\n\n}\n\nfunc (cb *CassandraBackend) DeleteTag(user_id, name string) error {\n\treturn cb.SessionQuery(`DELETE FROM user_tag WHERE user_id = ? AND name = ?`, user_id, name).Exec()\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/usernames.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n//\n\npackage store\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"strings\"\n)\n\n// UserNameStorage interface implementation for cassandra\nfunc (cb *CassandraBackend) UsernameIsAvailable(username string) (resp bool, err error) {\n\tresp = false\n\terr = nil\n\tlookup := helpers.EscapeUsername(username)\n\tfound := map[string]interface{}{}\n\terr = cb.SessionQuery(`SELECT COUNT(*) FROM user_name WHERE name = ?`, strings.ToLower(lookup)).MapScan(found)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"username lookup error : %v\", err)\n\t\treturn\n\t}\n\tif found[\"count\"].(int64) != 0 {\n\t\treturn\n\t}\n\tresp = true\n\treturn\n}\n\n// UserByUsername lookups table user_name to get the user_id for the given username\n// if a user_id is found, the user is fetched from user table.\nfunc (cb *CassandraBackend) UserByUsername(username string) (user *User, err error) {\n\tuser_id := new(gocql.UUID)\n\terr = cb.SessionQuery(`SELECT user_id FROM user_name WHERE name = ?`, strings.ToLower(username)).Scan(user_id)\n\tif err != nil || len(user_id.Bytes()) == 0 {\n\t\treturn nil, err\n\t}\n\treturn cb.RetrieveUser(user_id.String())\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/cassandra/users.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n//\n// UserStorage interface implementation for cassandra backend\n\npackage store\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/gocassa/gocassa\"\n\t\"github.com/gocql/gocql\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n)\n\nfunc (cb *CassandraBackend) RetrieveUser(user_id string) (user *User, err error) {\n\tu, err := cb.SessionQuery(`SELECT * FROM user WHERE user_id = ?`, user_id).Iter().SliceMap()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(u) != 1 {\n\t\terr = errors.New(\"[CassandraBackend] user not found\")\n\t\treturn nil, err\n\t}\n\tuser = new(User)\n\tuser.UnmarshalCQLMap(u[0])\n\treturn user, nil\n}\n\nfunc (cb *CassandraBackend) UpdateUser(user *User, fields map[string]interface{}) error {\n\n\t//get cassandra's field name for each field to modify\n\tcassaFields := map[string]interface{}{}\n\tfor field, value := range fields {\n\t\tcassaField, err := reflections.GetFieldTag(user, field, \"cql\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"[CassandraBackend] UpdateMessage failed to find a cql field for object field %s\", field)\n\t\t}\n\t\tcassaFields[cassaField] = value\n\t}\n\n\tuserT := cb.IKeyspace.Table(\"user\", &User{}, gocassa.Keys{\n\t\tPartitionKeys: []string{\"user_id\"},\n\t}).WithOptions(gocassa.Options{TableName: \"user\"})\n\n\treturn userT.Where(gocassa.Eq(\"user_id\", user.UserId.String())).Update(cassaFields).Run()\n}\n\nfunc (cb *CassandraBackend) UpdateUserPasswordHash(user *User) error {\n\treturn cb.SessionQuery(`UPDATE user SET password = ?, privacy_features = ? WHERE user_id = ?`,\n\t\tuser.Password,\n\t\tuser.PrivacyFeatures,\n\t\tuser.UserId,\n\t).Exec()\n}\n\n// UserByRecoveryEmail lookups table user_recovery_email to get the user_id for the given email\n// if a user_id is found, the user is fetched from user table.\nfunc (cb *CassandraBackend) UserByRecoveryEmail(email string) (user *User, err error) {\n\tuser_id := new(gocql.UUID)\n\terr = cb.SessionQuery(`SELECT user_id FROM user_recovery_email WHERE recovery_email = ?`, email).Scan(user_id)\n\tif err != nil || len(user_id.Bytes()) == 0 {\n\t\treturn nil, err\n\t}\n\treturn cb.RetrieveUser(user_id.String())\n}\n\n// DeleteUser sets the date_delete in the database\nfunc (cb *CassandraBackend) DeleteUser(user_id string) error {\n\treturn cb.SessionQuery(`UPDATE user SET date_delete = ? WHERE user_id = ?`, time.Now(), user_id).Exec()\n}\n\n// GetShardForUser returns user's shard_id or empty string if error\nfunc (cb *CassandraBackend) GetShardForUser(userID string) string {\n\tvar shardID string\n\terr := cb.SessionQuery(`SELECT shard_id FROM user WHERE user_id = ?`, userID).Scan(&shardID)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn shardID\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/object_store/attachments.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage object_store\n\nimport (\n\t\"io\"\n)\n\nfunc (mb *MinioBackend) PutAttachment(attchId string, attch io.Reader) (uri string, size int64, err error) {\n\treturn mb.PutObject(attchId, mb.AttachmentBucket, attch)\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/object_store/minio.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage object_store\n\nimport (\n\tobj \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/minio/minio-go\"\n\t\"io\"\n)\n\ntype (\n\tMinioBackend struct {\n\t\tOSSConfig\n\t\tClient *minio.Client\n\t}\n\n\tOSSConfig struct {\n\t\tEndpoint         string\n\t\tAccessKey        string\n\t\tSecretKey        string\n\t\tLocation         string\n\t\tRawMsgBucket     string\n\t\tAttachmentBucket string\n\t}\n\n\tObjectsStore interface {\n\t\tPutRawMessage(message_uuid obj.UUID, raw_message string) (uri string, err error)\n\t\tPutAttachment(attchId string, attch io.Reader) (uri string, size int64, err error)\n\t\tRemoveObject(uri string) error\n\t\tGetObject(uri string) (file io.Reader, err error)\n\t\tStatObject(uri string) (info minio.ObjectInfo, err error)\n\t}\n)\n\nfunc InitializeObjectsStore(config OSSConfig) (oss ObjectsStore, err error) {\n\tmb := new(MinioBackend)\n\tmb.OSSConfig = config\n\n\t// Initialize minio client object.\n\tmb.Client, err = minio.NewWithRegion(config.Endpoint, config.AccessKey, config.SecretKey, false, config.Location)\n\t// or NewWithCredentials to avoid putting credentials directly into conf. file ?\n\tif err != nil {\n\t\tmb.Client = nil\n\t\treturn\n\t}\n\n\t// Check to see if we already own RawMsgBucket (which happens if bucket already created by this client)\n\texists, err := mb.Client.BucketExists(config.RawMsgBucket)\n\tif err != nil || !exists {\n\t\t// Create a new bucket for raw messages\n\t\terr = mb.Client.MakeBucket(config.RawMsgBucket, config.Location)\n\t\tif err != nil {\n\t\t\tlogrus.WithError(err).Warnf(\"[ObjectStore] failed to create new bucket for large raw messages\")\n\t\t\tmb.Client = nil\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Check to see if we already own AttachmentBucket (which happens if bucket already created by this client)\n\texists, err = mb.Client.BucketExists(config.AttachmentBucket)\n\tif err != nil || !exists {\n\t\t// Create a new bucket for raw messages\n\t\terr = mb.Client.MakeBucket(config.AttachmentBucket, config.Location)\n\t\tif err != nil {\n\t\t\tlogrus.WithError(err).Warnf(\"[ObjectStore] failed to create new bucket for draft attachments\")\n\t\t\tmb.Client = nil\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn mb, err\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/object_store/objects.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage object_store\n\nimport (\n\t\"fmt\"\n\t\"github.com/minio/minio-go\"\n\t\"io\"\n\t\"net/url\"\n)\n\nfunc (mb *MinioBackend) PutObject(name, bucket string, object io.Reader) (uri string, size int64, err error) {\n\tconst uriTemplate = \"s3://%s/%s\"\n\n\tsize, err = mb.Client.PutObject(bucket, name, object, -1, minio.PutObjectOptions{ContentType: \"application/octet-stream\"})\n\tif err != nil {\n\t\treturn \"\", 0, err\n\t}\n\n\treturn fmt.Sprintf(uriTemplate, bucket, name), size, nil\n}\n\nfunc (mb *MinioBackend) RemoveObject(objURI string) error {\n\turi, err := url.Parse(objURI)\n\tif err != nil || len(uri.Host) < 1 || len(uri.Path) < 2 {\n\t\treturn err\n\t}\n\treturn mb.Client.RemoveObject(uri.Host, uri.Path[1:])\n}\n\nfunc (mb *MinioBackend) GetObject(objURI string) (file io.Reader, err error) {\n\turi, err := url.Parse(objURI)\n\tif err != nil || len(uri.Host) < 1 || len(uri.Path) < 2 {\n\t\treturn nil, err\n\t}\n\treturn mb.Client.GetObject(uri.Host, uri.Path[1:], minio.GetObjectOptions{})\n}\n\nfunc (mb *MinioBackend) StatObject(objURI string) (info minio.ObjectInfo, err error) {\n\turi, err := url.Parse(objURI)\n\tif err != nil || len(uri.Host) < 1 || len(uri.Path) < 2 {\n\t\treturn\n\t}\n\treturn mb.Client.StatObject(uri.Host, uri.Path[1:], minio.StatObjectOptions{})\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/object_store/raw_messages.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage object_store\n\nimport (\n\tobj \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"strings\"\n)\n\nfunc (mb *MinioBackend) PutRawMessage(message_uuid obj.UUID, raw_email string) (uri string, err error) {\n\temail_reader := strings.NewReader(raw_email)\n\turi, _, err = mb.PutObject(message_uuid.String(), mb.RawMsgBucket, email_reader)\n\treturn\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/vault/credentials.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage vault\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype VaultCredentials interface {\n\tCreateCredentials(userIdentity *UserIdentity, cred Credentials) error\n\tRetrieveCredentials(userId, remoteId string) (Credentials, error)\n\tUpdateCredentials(userId, remoteId string, cred Credentials, upsertMode bool) error\n\tDeleteCredentials(userId, remoteId string) error\n}\n\nfunc (vault *HVaultClient) CreateCredentials(userIdentity *UserIdentity, cred Credentials) error {\n\treturn vault.UpdateCredentials(userIdentity.UserId.String(), userIdentity.Id.String(), cred, true)\n}\n\n// RetrieveCrendentials gets credentials from vault, if application has read rights on vault\nfunc (vault *HVaultClient) RetrieveCredentials(userId, remoteId string) (cred Credentials, err error) {\n\tcred = Credentials{}\n\tpath := fmt.Sprintf(credentialsPath, userId, remoteId)\n\tsecret, err := vault.hclient.Logical().Read(path)\n\tif err != nil {\n\t\treturn\n\t}\n\tif secret == nil || secret.Data == nil {\n\t\t// secret not found\n\t\terr = errors.New(\"secret not found\")\n\t\treturn\n\t}\n\n\tdata, ok := secret.Data[\"data\"]\n\tif !ok {\n\t\terr = errors.New(\"secret not found\")\n\t\treturn\n\t}\n\t// Credentials is a map[string]string\n\t// only copy values from secret.Data that are strings\n\tfor k, v := range data.(map[string]interface{}) {\n\t\tswitch v.(type) {\n\t\tcase string:\n\t\t\tcred[k] = v.(string)\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn\n}\n\nfunc (vault *HVaultClient) UpdateCredentials(userId, remoteId string, cred Credentials, upsertMode bool) error {\n\tpayload := make(map[string]interface{})\n\tpayload[\"data\"] = cred\n\tpath := fmt.Sprintf(credentialsPath, userId, remoteId)\n\tif !upsertMode {\n\t\t// ensure credentials exist before updating\n\t\t// or silently returns without doing anything (mimicking cassandra)\n\t\tsecret, err := vault.hclient.Logical().Read(path)\n\t\tif err != nil || secret == nil {\n\t\t\treturn nil\n\t\t}\n\t}\n\t_, err := vault.hclient.Logical().Write(path, payload)\n\treturn err\n}\n\nfunc (vault *HVaultClient) DeleteCredentials(userId, remoteId string) error {\n\tpath := fmt.Sprintf(credentialsPath, userId, remoteId)\n\t_, err := vault.hclient.Logical().Delete(path)\n\treturn err\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/vault/hvault_interface.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n// interfaces definition to Hashicorp Vault server\npackage vault\n\n// As of june 2018, only one interface for CRUD operation on credentials. Later on, we may add Cubbyhole secrets engine, databases secret engine and so on…\ntype HVault interface {\n\tVaultCredentials\n}\n"
  },
  {
    "path": "src/backend/main/go.backends/store/vault/vault_client.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n// vault_client provides an interface to interact with HashiCorp Vault server\n\npackage vault\n\nimport (\n\t\"fmt\"\n\thvault \"github.com/hashicorp/vault/api\"\n)\n\ntype HVaultClient struct {\n\thclient *hvault.Client\n\tHVaultConfig\n}\n\ntype HVaultConfig struct {\n\tUrl      string `mapstructure:\"url\"`\n\tUsername string `mapstructure:\"username\"`\n\tPassword string `mapstructure:\"password\"`\n}\n\nconst credentialsPath = \"secret/data/remoteid/credentials/%s/%s\" // path to store credentials => secret/data/remoteid/credentials/user_id/remote_id\nconst loginPath = \"auth/userpass/login/%s\"\n\n// InitializeVaultBackend checks if a Vault server is available and returns an authenticated VaultClient\nfunc InitializeVaultBackend(hvConf HVaultConfig) (hv HVault, err error) {\n\tconfig := hvault.DefaultConfig()\n\tconfig.Address = hvConf.Url\n\n\tvar hc HVaultClient\n\n\thc.Url = hvConf.Url\n\thc.Username = hvConf.Username\n\thc.Password = hvConf.Password\n\n\thc.hclient, err = hvault.NewClient(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t//authentication with user/password method\n\toptions := map[string]interface{}{\n\t\t\"password\": hvConf.Password,\n\t}\n\tpath := fmt.Sprintf(loginPath, hvConf.Username)\n\tsecret, err := hc.hclient.Logical().Write(path, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thc.hclient.SetToken(secret.Auth.ClientToken)\n\n\t//TODO: manage token expiration (default TTL is 32 days)\n\n\t_, err = hc.hclient.Sys().Health()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &hc, nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/caliopen.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage caliopen\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Messaging\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/REST\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/nats-io/go-nats\"\n)\n\nvar (\n\tFacilities *CaliopenFacilities\n)\n\ntype (\n\tCaliopenFacilities struct {\n\t\tconfig       CaliopenConfig\n\t\tRESTfacility REST.RESTservices\n\n\t\tCache backends.APICache\n\n\t\t// NATS facility\n\t\tnats              *nats.Conn\n\t\tMessagingFacility Messaging.Facility\n\n\t\t// LDA facility\n\t\tLDAstore backends.LDAStore\n\t\t// Notifications facility\n\t\tNotifiers Notifications.Notifiers\n\t\t// Admin user on whose behalf actions could be done\n\t\tAdmin *User\n\t}\n)\n\nfunc Initialize(config CaliopenConfig) error {\n\tFacilities = new(CaliopenFacilities)\n\treturn Facilities.initialize(config)\n}\n\nfunc (facilities *CaliopenFacilities) initialize(config CaliopenConfig) (err error) {\n\n\tfacilities.config = config\n\n\t// NATS facility initialization\n\tfacilities.nats, err = nats.Connect(config.NatsConfig.Url)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"CaliopenFacilities : initalization of NATS connexion failed\")\n\t\treturn\n\t}\n\n\t// REST facility initialization\n\trest := REST.NewRESTfacility(config, facilities.nats)\n\tfacilities.RESTfacility = rest\n\n\t// copy cache facility from REST facility\n\tfacilities.Cache = rest.Cache\n\n\t// Notifications facility initialization\n\tnotifier := Notifications.NewNotificationsFacility(config, facilities.nats)\n\tfacilities.Notifiers = notifier\n\n\t// Messaging facility initialization\n\tfacilities.MessagingFacility, err = Messaging.NewCaliopenMessaging(config, notifier)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"CaliopenFacilities : initalization of Messaging facility failed\")\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "src/backend/main/go.main/contact/contact.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage contact\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/emersion/go-vcard\"\n\t\"github.com/keybase/go-crypto/openpgp\"\n\t\"github.com/satori/go.uuid\"\n\t\"strings\"\n)\n\n// Parse EMAIL vcard field\nfunc parseEmail(field *vcard.Field) *objects.EmailContact {\n\temail := new(objects.EmailContact)\n\tuid := new(objects.UUID)\n\t_ = uid.UnmarshalBinary(uuid.NewV4().Bytes())\n\temail.EmailId = *uid\n\temail.Address = strings.ToLower(field.Value)\n\tif field.Params[\"TYPE\"] != nil {\n\t\temail.Type = strings.ToLower(field.Params[\"TYPE\"][0])\n\t}\n\t// TODO complete struct filling\n\temail.Label = field.Value\n\treturn email\n}\n\n// Parse TEL vcard field\nfunc parsePhone(field *vcard.Field) *objects.Phone {\n\tphone := new(objects.Phone)\n\tuid := new(objects.UUID)\n\t_ = uid.UnmarshalBinary(uuid.NewV4().Bytes())\n\tphone.PhoneId = *uid\n\tphone.Number = field.Value\n\tif field.Params[\"TYPE\"] != nil {\n\t\tphone.Type = strings.ToLower(field.Params[\"TYPE\"][0])\n\t}\n\treturn phone\n}\n\n// Parse IMPP vcard field\nfunc parseIm(field *vcard.Field) *objects.IM {\n\tim := new(objects.IM)\n\tuid := new(objects.UUID)\n\t_ = uid.UnmarshalBinary(uuid.NewV4().Bytes())\n\tim.IMId = *uid\n\tim.Address = strings.ToLower(field.Value)\n\tim.Label = field.Value\n\tif field.Params[\"TYPE\"] != nil {\n\t\tim.Type = strings.ToLower(field.Params[\"TYPE\"][0])\n\t}\n\treturn im\n}\n\n// Read an armored PGP public key and return an openpgp.Entity structure\nfunc readPgpKey(pubkey []byte) (*openpgp.Entity, error) {\n\treader := bytes.NewReader(pubkey)\n\tvar entitiesList openpgp.EntityList\n\tvar err error\n\n\tentitiesList, err = openpgp.ReadArmoredKeyRing(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t//handle only first key found for now\n\tif len(entitiesList) > 1 {\n\t\treturn nil, errors.New(\"More than one key found in payload\")\n\t}\n\treturn entitiesList[0], nil\n}\n\n// Parse KEY vcard field and transform to a PublicKey that belong to a contact\nfunc parseKey(field *vcard.Field, contact *objects.Contact) (*objects.PublicKey, error) {\n\tkey := new(objects.PublicKey)\n\tvar err error\n\n\tif field.Params[\"TYPE\"] != nil && field.Params[\"TYPE\"][0] == \"PGP\" {\n\t\tkey.KeyType = \"pgp\"\n\t}\n\tif field.Params[\"ENCODING\"] != nil && field.Params[\"ENCODING\"][0] == \"b\" {\n\t\tpubkey, err := base64.StdEncoding.DecodeString(field.Value)\n\t\tif err != nil {\n\t\t\treturn &objects.PublicKey{}, err\n\t\t}\n\t\tentity, err := readPgpKey(pubkey)\n\t\tif err != nil {\n\t\t\treturn &objects.PublicKey{}, err\n\t\t}\n\t\terr = key.UnmarshalPGPEntity(\"PGP key\", entity, contact)\n\t} else {\n\t\treturn &objects.PublicKey{}, errors.New(\"Unknow key encoding\")\n\t}\n\tlog.Info(\"Have parsed PGP key \", key.Fingerprint, \" with algorithm \", key.Algorithm)\n\treturn key, err\n}\n\n// Parse ADR vcard field\nfunc parseAddress(addr *vcard.Address) *objects.PostalAddress {\n\taddress := new(objects.PostalAddress)\n\tuid := new(objects.UUID)\n\t_ = uid.UnmarshalBinary(uuid.NewV4().Bytes())\n\taddress.AddressId = *uid\n\taddress.City = addr.Locality\n\taddress.Country = addr.Country\n\taddress.Region = addr.Region\n\taddress.PostalCode = addr.PostalCode\n\taddress.Street = addr.StreetAddress\n\treturn address\n}\n\n// Transform a vcard into an objects.Contact structure\nfunc FromVcard(user *objects.UserInfo, card vcard.Card) (*objects.Contact, error) {\n\tcontact := new(objects.Contact).NewEmpty().(*objects.Contact)\n\tcontact.UserId = objects.UUID(uuid.FromStringOrNil(user.User_id))\n\tcontact.Title = card.PreferredValue(vcard.FieldFormattedName)\n\tif card.Name() != nil {\n\t\tcontact.FamilyName = card.Name().FamilyName\n\t\tcontact.GivenName = card.Name().GivenName\n\t}\n\n\t// check version\n\t// TODO implement version 4.0 (rfc 6350)\n\tversion := card[vcard.FieldVersion]\n\tif version != nil {\n\t\tif !(version[0].Value == \"3.0\" || version[0].Value == \"4.0\") {\n\t\t\treturn contact, errors.New(\"Only vcard format 3.0 and 4.0 are supported\")\n\t\t}\n\t}\n\n\t// emails\n\temails := card[vcard.FieldEmail]\n\tif emails != nil {\n\t\tcontact.Emails = []objects.EmailContact{}\n\t\tfor _, email := range emails {\n\t\t\te := parseEmail(email)\n\t\t\tif e.Address != \"\" {\n\t\t\t\tcontact.Emails = append(contact.Emails, *e)\n\t\t\t}\n\t\t}\n\t}\n\n\t// phones\n\tphones := card[vcard.FieldTelephone]\n\tif phones != nil {\n\t\tcontact.Phones = []objects.Phone{}\n\t\tfor _, phone := range phones {\n\t\t\tp := parsePhone(phone)\n\t\t\tif p.Number != \"\" {\n\t\t\t\tcontact.Phones = append(contact.Phones, *p)\n\t\t\t}\n\t\t}\n\t}\n\thelpers.NormalizePhoneNumbers(contact)\n\n\tims := card[vcard.FieldIMPP]\n\tif ims != nil {\n\t\tcontact.Ims = []objects.IM{}\n\t\tfor _, im := range ims {\n\t\t\ti := parseIm(im)\n\t\t\tif i.Address != \"\" {\n\t\t\t\tcontact.Ims = append(contact.Ims, *i)\n\t\t\t}\n\t\t}\n\t}\n\n\t// addresses\n\taddrs := card.Addresses()\n\tif addrs != nil {\n\t\tcontact.Addresses = []objects.PostalAddress{}\n\t\tfor _, addr := range addrs {\n\t\t\ta := parseAddress(addr)\n\t\t\tcontact.Addresses = append(contact.Addresses, *a)\n\t\t}\n\t}\n\n\t// public keys\n\tkeys := card[vcard.FieldKey]\n\tif keys != nil {\n\t\tcontact.PublicKeys = make([]objects.PublicKey, 0, len(keys))\n\t\tfor _, key := range keys {\n\t\t\tk, err := parseKey(key, contact)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(\"Error during vcard KEY parsing \", err)\n\t\t\t} else {\n\t\t\t\tcontact.PublicKeys = append(contact.PublicKeys, *k)\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t\tTODO: Need to change index mappings of contact.infos\n\t\tinfos := make(map[string]string)\n\t\tif uid := card[vcard.FieldUID]; uid != nil {\n\t\t\tinfos[\"uid\"] = uid[0].Value\n\t\t}\n\t\tif rev := card[vcard.FieldRevision]; rev != nil {\n\t\t\tinfos[\"revision\"] = rev[0].Value\n\t\t}\n\t\tcontact.Infos = make(map[string]string)\n\t\tfor k, v := range infos {\n\t\t\tcontact.Infos[k] = v\n\t\t}\n\t*/\n\n\treturn contact, nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/contact/contact_test.go",
    "content": "package contact\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// https://en.wikipedia.org/wiki/VCard#vCard_2.1\nvar testWikipediav2_1 = `BEGIN:VCARD\nVERSION:2.1\nN:Gump;Forrest;;Mr.\nFN:Forrest Gump\nORG:Bubba Gump Shrimp Co.\nTITLE:Shrimp Man\nPHOTO;GIF:http://www.example.com/dir_photos/my_photo.gif\nTEL;WORK;VOICE:(111) 555-1212\nTEL;HOME;VOICE:(404) 555-1212\nADR;WORK;PREF:;;100 Waters Edge;Baytown;LA;30314;United States of America\nLABEL;WORK;PREF;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:100 Waters Edge=0D=\n =0ABaytown\\, LA 30314=0D=0AUnited States of America\nADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America\nLABEL;HOME;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:42 Plantation St.=0D=0A=\n Baytown, LA 30314=0D=0AUnited States of America\nEMAIL:forrestgump@example.com\nREV:20080424T195243Z\nEND:VCARD`\n\n// https://en.wikipedia.org/wiki/VCard#vCard_3.0\nvar testWikipediav3 = `BEGIN:VCARD\nVERSION:3.0\nN:Gump;Forrest;;Mr.;\nFN:Forrest Gump\nORG:Bubba Gump Shrimp Co.\nTITLE:Shrimp Man\nPHOTO;VALUE=URI;TYPE=GIF:;http://www.example.com/dir_photos/my_photo.gif\nTEL;TYPE=WORK,VOICE:(111) 555-1212\nTEL;TYPE=HOME,VOICE:(404) 555-1212\nADR;TYPE=WORK,PREF:;;100 Waters Edge;Baytown;LA;30314;United States of America\nLABEL;TYPE=WORK,PREF:100 Waters Edge\\nBaytown\\, LA 30314\\nUnited States of America\nADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America\nLABEL;TYPE=HOME:42 Plantation St.\\nBaytown\\, LA 30314\\nUnited States of America\nEMAIL:forrestgump@example.com\nREV:2008-04-24T19:52:43Z\nEND:VCARD`\n\n// https://en.wikipedia.org/wiki/VCard#vCard_4.0\nvar testWikipediav4 = `BEGIN:VCARD\nVERSION:4.0\nN:Gump;Forrest;;Mr.;\nFN:Forrest Gump\nORG:Bubba Gump Shrimp Co.\nTITLE:Shrimp Man\nPHOTO;MEDIATYPE=image/gif:http://www.example.com/dir_photos/my_photo.gif\nTEL;TYPE=work,voice;VALUE=uri:tel:+1-111-555-1212\nTEL;TYPE=home,voice;VALUE=uri:tel:+1-404-555-1212\nADR;TYPE=WORK;PREF=1;LABEL=\"100 Waters Edge\\nBaytown\\, LA 30314\\nUnited States of America\":;;100 Waters Edge;Baytown;LA;30314;United St\nates of America\nADR;TYPE=HOME;LABEL=\"42 Plantation St.\\nBaytown\\, LA 30314\\nUnited States of America\":;;42 Plantation St.;Baytown;LA;30314;United State\ns of America\nEMAIL:forrestgump@example.com\nREV:20080424T195243Z\nx-qq:21588891\nEND:VCARD`\n\n// Format 2.1 used for v3\nvar testInvalidv3 = `begin:vcard\nfn:Emma\nemail;internet:Emma@tomme.de.savoie\nversion:3.0\nend:vcard\n`\n\nvar validTests = []struct {\n\ts string\n}{\n\t{testWikipediav3},\n\t{testWikipediav4},\n}\n\nvar invalidFormat = []struct {\n\ts string\n}{\n\t{testWikipediav2_1},\n}\n\nfunc TestFromVcardValid(t *testing.T) {\n\tinfo := objects.UserInfo{User_id: \"ede04443-b60f-4869-9040-20bd6b1e33c1\"}\n\tfor _, test := range validTests {\n\t\tr := strings.NewReader(test.s)\n\t\tcards, err := ParseVcardFile(r)\n\t\tif len(cards) != 1 {\n\t\t\tt.Error(\"Expected only one vcard\")\n\t\t}\n\t\tcontact, err := FromVcard(&info, cards[0])\n\t\tif err != nil {\n\t\t\tt.Error(\"Expecting null error \", err)\n\t\t}\n\t\tif len(contact.Emails) == 0 {\n\t\t\tt.Error(\"No email found in test vcard \")\n\t\t}\n\t\tif len(contact.Phones) == 0 {\n\t\t\tt.Error(\"No phone found in test vcard \")\n\t\t}\n\t}\n}\n\nfunc TestFromVcardInvalid(t *testing.T) {\n\tinfo := objects.UserInfo{User_id: \"ede04443-b60f-4869-9040-20bd6b1e33c1\"}\n\tfor _, test := range invalidFormat {\n\t\tr := strings.NewReader(test.s)\n\t\tcards, err := ParseVcardFile(r)\n\t\tif len(cards) != 1 {\n\t\t\tt.Error(\"Expected only one vcard\")\n\t\t}\n\t\t_, err = FromVcard(&info, cards[0])\n\t\tif err == nil {\n\t\t\tt.Error(\"Expecting not null error \")\n\t\t}\n\t}\n}\n\n// Test testInvalidv3 card\nfunc TestFromVcardInvalidEmailFormat(t *testing.T) {\n\tinfo := objects.UserInfo{User_id: \"ede04443-b60f-4869-9040-20bd6b1e33c1\"}\n\tr := strings.NewReader(testInvalidv3)\n\tcards, err := ParseVcardFile(r)\n\tif len(cards) != 1 {\n\t\tt.Error(\"Expected only one vcard\")\n\t}\n\tcontact, err := FromVcard(&info, cards[0])\n\tif err != nil {\n\t\tt.Error(\"Expecting no error \")\n\t}\n\tif len(contact.Emails) > 0 {\n\t\tt.Error(\"Expecting no email in invalid vcard\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/main/go.main/contact/vcard.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage contact\n\nimport (\n\t\"errors\"\n\t\"github.com/emersion/go-vcard\"\n\t\"io\"\n)\n\n// Parse .vcf .vcard file, returting list of Card objects\nfunc ParseVcardFile(file io.Reader) ([]vcard.Card, error) {\n\tcards := make([]vcard.Card, 0, 5)\n\n\tdec := vcard.NewDecoder(file)\n\n\tfor {\n\t\tcard, err := dec.Decode()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn []vcard.Card{}, err\n\t\t}\n\t\tcards = append(cards, card)\n\t}\n\tif len(cards) == 0 {\n\t\treturn cards, errors.New(\"No vcard found in file\")\n\t}\n\treturn cards, nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/Messaging/facility.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage Messaging\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/satori/go.uuid\"\n)\n\ntype Facility interface {\n\tHandleUserAction(msg *nats.Msg)\n}\n\ntype CaliopenMessaging struct {\n\tcaliopenNotifier *Notifications.Notifier\n\tnatsConn         *nats.Conn\n\tSubscriptions    map[string]*nats.Subscription\n}\n\n// unexported vars to help override funcs in tests\nvar (\n\tnotifyByEmail = func(notifier *Notifications.Notifier, notif *Notification) CaliopenError {\n\t\treturn notifier.ByEmail(notif)\n\t}\n)\n\nfunc NewCaliopenMessaging(config CaliopenConfig, notifier *Notifications.Notifier) (Facility, error) {\n\tif notifier == nil {\n\t\treturn nil, errors.New(\"[NewCaliopenMessaging] needs a non-nil notifier\")\n\t}\n\tif notifier.NatsQueue == nil {\n\t\treturn nil, errors.New(\"[NewCaliopenMessaging] needs a non-nil natsConn\")\n\t}\n\tcm := new(CaliopenMessaging)\n\tcm.natsConn = notifier.NatsQueue\n\tcm.Subscriptions = map[string]*nats.Subscription{}\n\tif config.NatsConfig.Users_topic == \"\" {\n\t\treturn nil, errors.New(\"[NewCaliopenMessaging] wont subscribe to empty topic\")\n\t}\n\tuserSub, err := cm.natsConn.QueueSubscribe(config.NatsConfig.Users_topic, config.NatsConfig.NatsQueue, cm.HandleUserAction)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[NewCaliopenMessaging] failed to subscribe to %s topic on NATS\", config.NatsConfig.Users_topic)\n\t\treturn nil, err\n\t}\n\tcm.Subscriptions[config.NatsConfig.Users_topic] = userSub\n\tcm.caliopenNotifier = notifier\n\treturn cm, nil\n}\n\nfunc (cm *CaliopenMessaging) HandleUserAction(msg *nats.Msg) {\n\tpayload := new(struct {\n\t\tMessage  string `json:\"message\"`\n\t\tUserName string `json:\"user_name\"`\n\t\tUserId   string `json:\"user_id\"`\n\t})\n\terr := json.Unmarshal(msg.Data, payload)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[HandleUserAction] failed to unmarshal this nats' payload : %+v\", msg.Data)\n\t\treturn\n\t}\n\tswitch payload.Message {\n\tcase \"created\":\n\t\tuser, err := cm.caliopenNotifier.Store.UserByUsername(payload.UserName)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Errorf(\"[HandleUserAction] failed to retrieve user %s\", payload.UserName)\n\t\t\treturn\n\t\t}\n\t\tnotif := &Notification{\n\t\t\tNotifId: UUID(uuid.NewV1()),\n\t\t\tType:    OnboardingMails,\n\t\t\tUser:    user,\n\t\t}\n\t\tcErr := notifyByEmail(cm.caliopenNotifier, notif)\n\t\tif cErr != nil {\n\t\t\tlog.WithError(cErr).Warnf(\"[HandleUserAction] ByEmail notification failed (code : %d, cause : %s)\", cErr.Code(), cErr.Cause())\n\t\t}\n\tdefault:\n\t\tlog.Errorf(\"[HandleUserAction] unhandled message : %s\", payload.Message)\n\t}\n\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/Messaging/facility_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage Messaging\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/NATS/go.mockednats\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/nats-io/go-nats\"\n\t\"testing\"\n)\n\nfunc TestNewCaliopenMessaging(t *testing.T) {\n\t// test errors returns\n\tfacility, err := NewCaliopenMessaging(CaliopenConfig{}, nil)\n\tif facility != nil {\n\t\tt.Errorf(\"expected a nil facility, got %+v\", facility)\n\t}\n\tif err == nil {\n\t\tt.Error(\"NewCaliopenMessaging should return an error, got nil\")\n\t}\n\tfacility, err = NewCaliopenMessaging(CaliopenConfig{}, &Notifications.Notifier{})\n\tif facility != nil {\n\t\tt.Errorf(\"expected a nil facility, got %+v\", facility)\n\t}\n\tif err == nil {\n\t\tt.Error(\"initializing caliopen messaging with empty notifier should return an error, got nil\")\n\t}\n\n\tnatsServer, natsConn, err := mockednats.GetNats()\n\tdefer natsServer.Shutdown()\n\n\tfacility, err = NewCaliopenMessaging(CaliopenConfig{}, &Notifications.Notifier{\n\t\tNatsQueue: natsConn,\n\t})\n\tif err == nil {\n\t\tt.Error(\"initializing caliopen messaging with empty configuration should return error, got nil\")\n\t}\n\n\t// test correct initialization\n\tnotifier := &Notifications.Notifier{NatsQueue: natsConn}\n\tfacility, err = NewCaliopenMessaging(CaliopenConfig{NatsConfig: NatsConfig{Users_topic: \"userAction\"}}, notifier)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif f, ok := facility.(*CaliopenMessaging); ok {\n\t\tif len(f.Subscriptions) != 1 {\n\t\t\tt.Errorf(\"expected a facility with one nats subscription, got %d\", len(f.Subscriptions))\n\t\t} else if s, ok := f.Subscriptions[\"userAction\"]; ok {\n\t\t\tif !s.IsValid() {\n\t\t\t\tt.Error(\"expected a valid subscription at Subscriptions[\\\"UserAction\\\"], got invalid\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.Error(\"expected to have a entry in CaliopenMessaging's Subscriptions at key `userAction`, got nothing\")\n\t\t}\n\t\tif f.caliopenNotifier != notifier {\n\t\t\tt.Errorf(\"expected to have a CaliopenMessaging with the notifier passed in constructor (%p), got %p\", &f.caliopenNotifier, &notifier)\n\t\t}\n\t} else {\n\t\tt.Errorf(\"expected NewCaliopenMessagin to return a *CaliopenMessaging struct, got %T\", f)\n\t}\n\n\t// last error checking\n\tnatsConn.Close()\n\tfacility, err = NewCaliopenMessaging(CaliopenConfig{}, &Notifications.Notifier{\n\t\tNatsQueue: natsConn, // passing closed connexion on purpose\n\t})\n\tif err == nil {\n\t\tt.Error(\"initializing caliopen messaging with empty nats connexion should return an error, got nil\")\n\t}\n\n}\n\nfunc TestCaliopenMessaging_HandleUserAction(t *testing.T) {\n\tnatsServer, natsConn, err := mockednats.GetNats()\n\tdefer natsServer.Shutdown()\n\tstore, _ := backendstest.GetNotificationsBackends()\n\tnotifier := &Notifications.Notifier{\n\t\tNatsQueue: natsConn,\n\t\tStore:     store,\n\t}\n\tfacility, err := NewCaliopenMessaging(CaliopenConfig{NatsConfig: NatsConfig{Users_topic: \"userAction\"}}, notifier)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// overring notifyByEmail func\n\t// because this test checks if the func is correctly called within HandleUserAction, nothing more\n\tvar notifCalled bool\n\tnotifyByEmail = func(notifier *Notifications.Notifier, notif *Notification) CaliopenError {\n\t\tnotifCalled = true\n\t\treturn nil\n\t}\n\n\t// test errors handling\n\t// invalid nats msg\n\tfacility.HandleUserAction(&nats.Msg{})\n\tif notifCalled {\n\t\tt.Error(\"expected that calling HandleUserAction with an empty nats message will not trigger notifyByEmail, but func was called\")\n\t\tnotifCalled = false\n\t}\n\t// unknown username\n\tfacility.HandleUserAction(&nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"test_reply\",\n\t\tData:    []byte(`{\"message\":\"created\", \"user_name\": \"unknown\", \"user_id\": \"7f8329c4-e220-45fc-89b2-d8535df69e83\"}`), // invalid user\n\t})\n\tif notifCalled {\n\t\tt.Error(\"expected that calling HandleUserAction with unknown username will not trigger notifyByEmail, but func was called\")\n\t\tnotifCalled = false\n\t}\n\t// unknown message\n\tfacility.HandleUserAction(&nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"test_reply\",\n\t\tData:    []byte(`{\"message\":\"unknown\", \"user_name\": \"emma\", \"user_id\": \"7f8329c4-e220-45fc-89b2-d8535df69e83\"}`), // invalid user\n\t})\n\tif notifCalled {\n\t\tt.Error(\"expected that calling HandleUserAction with unknown username will not trigger notifyByEmail, but func was called\")\n\t\tnotifCalled = false\n\t}\n\t// test valid payload\n\tfacility.HandleUserAction(&nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"test_reply\",\n\t\tData:    []byte(`{\"message\":\"created\", \"user_name\": \"emma\", \"user_id\": \"7f8329c4-e220-45fc-89b2-d8535df69e83\"}`),\n\t})\n\tif !notifCalled {\n\t\tt.Error(\"expected HandleUserAction to call notifyByEmail, but func was not called\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/Notifications/batch.go",
    "content": "package Notifications\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/satori/go.uuid\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n)\n\ntype BatchNotification struct {\n\temitter                string\n\tlocker                 *sync.Mutex\n\tnotifications          []Notification\n\tnotificationsCount     int\n\tnotificationsThreshold int\n}\n\nconst notificationsThreshold = 20\n\ntype BatchNotifier interface {\n\tAdd(Notification)\n\tSave(notifier Notifiers, reference, ttl string)\n}\n\nfunc NewBatch(emitter string) *BatchNotification {\n\treturn &BatchNotification{\n\t\temitter:                emitter,\n\t\tlocker:                 new(sync.Mutex),\n\t\tnotificationsThreshold: notificationsThreshold,\n\t}\n}\n\nfunc (bn *BatchNotification) Add(n Notification) {\n\tbn.locker.Lock()\n\tbn.notificationsCount++\n\tif bn.notificationsCount <= bn.notificationsThreshold {\n\t\tbn.notifications = append(bn.notifications, n)\n\t}\n\tbn.locker.Unlock()\n}\n\n// Save aggregates notifications into a single one with sub-notifications into its Body as a json array\n// then saves this notification in user's cassandra queue\nfunc (bn *BatchNotification) Save(notifier Notifiers, reference, ttl string) {\n\tnotif, err := bn.aggregate(reference, ttl)\n\tif err == nil {\n\t\tnotifier.ByNotifQueue(&notif)\n\t}\n}\n\n// aggregate flatten notifications into a single Notification\n// children are embedded in Notification.Body as a json array if they are less than batchThreshold\n// otherwise, only children_count is written\nfunc (bn *BatchNotification) aggregate(reference, ttl string) (Notification, error) {\n\tif len(bn.notifications) == 0 {\n\t\treturn Notification{}, errors.New(\"[BatchNotifier] elements is empty\")\n\t}\n\tnotif := Notification{\n\t\tEmitter:       bn.emitter,\n\t\tNotifId:       UUID(uuid.NewV1()),\n\t\tReference:     reference,\n\t\tTTLcode:       ttl,\n\t\tType:          bn.notifications[0].Type,\n\t\tUser:          bn.notifications[0].User,\n\t\tChildrenCount: bn.notificationsCount,\n\t}\n\tchildren := make([]NotificationModel, 0, len(bn.notifications))\n\tif bn.notificationsCount <= bn.notificationsThreshold {\n\t\tfor _, n := range bn.notifications {\n\t\t\tif notif.User == nil || n.User == nil || (notif.User.UserId.String() != n.User.UserId.String()) {\n\t\t\t\treturn Notification{}, errors.New(\"[BatchNotifier] can't aggregate notifications : inconsistent user ids within notifications slice\")\n\t\t\t}\n\t\t\tchildren = append(children, NotificationModel{\n\t\t\t\tBody: n.Body,\n\t\t\t})\n\t\t}\n\t}\n\n\tbody := strings.Builder{}\n\tbody.WriteString(`{\"size\":`)\n\tbody.WriteString(strconv.Itoa(bn.notificationsCount))\n\n\tif len(children) > 0 {\n\t\tjChildren, err := json.Marshal(children)\n\t\tif err != nil {\n\t\t\treturn Notification{}, err\n\t\t}\n\t\tbody.WriteString(`,\"elements\":`)\n\t\tbody.WriteString(string(jChildren))\n\t}\n\n\tbody.WriteString(`}`)\n\tnotif.Body = body.String()\n\treturn notif, nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/Notifications/batch_test.go",
    "content": "package Notifications\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/satori/go.uuid\"\n\t\"github.com/tidwall/gjson\"\n\t\"sync\"\n\t\"testing\"\n)\n\nfunc TestBatchNotification_aggregate(t *testing.T) {\n\tbn := BatchNotification{\n\t\temitter:                \"test\",\n\t\tlocker:                 new(sync.Mutex),\n\t\tnotificationsCount:     3,\n\t\tnotificationsThreshold: notificationsThreshold,\n\t\tnotifications: []Notification{\n\t\t\t{Body: `{\"contact_id\": \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\", \"status\": \"imported\"}`,\n\t\t\t\tUser: &User{UserId: UUID(uuid.FromStringOrNil(\"63ab7904-c416-4f1a-9652-3de82e4fd1f1\"))}},\n\t\t\t{Body: `{\"contact_id\": \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\", \"status\": \"error\", \"error_msg\": \"something went wrong\"}`,\n\t\t\t\tUser: &User{UserId: UUID(uuid.FromStringOrNil(\"63ab7904-c416-4f1a-9652-3de82e4fd1f1\"))}},\n\t\t\t{Body: `{\"contact_id\": \"63ab7904-c416-4f1a-9652-3de82e4fd1f1\", \"status\": \"ignored\"}`,\n\t\t\t\tUser: &User{UserId: UUID(uuid.FromStringOrNil(\"63ab7904-c416-4f1a-9652-3de82e4fd1f1\"))}},\n\t\t},\n\t}\n\n\tn, err := bn.aggregate(\"ref\", LongLived)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else {\n\t\tif !gjson.Valid(n.Body) {\n\t\t\tt.Error(\"expected a valid json in body, gjson reported invalid\")\n\t\t} else {\n\t\t\tresult := gjson.Get(n.Body, \"size\")\n\t\t\tif !result.Exists() {\n\t\t\t\tt.Error(\"expected property 'size' in notification body but gjson can't find it\")\n\t\t\t} else {\n\t\t\t\tif result.Int() != 3 {\n\t\t\t\t\tt.Errorf(\"expected body.size = 3, got %d\", result.Int())\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult = gjson.Get(n.Body, \"elements\")\n\t\t\tif !result.Exists() || !result.IsArray() {\n\t\t\t\tt.Error(\"expected body.elements to be an array, gjson reported it doesn't exist or not an array\")\n\t\t\t} else {\n\t\t\t\tchildrenLength := gjson.Get(n.Body, \"elements.#\")\n\t\t\t\tif childrenLength.Num != 3.0 {\n\t\t\t\t\tt.Errorf(\"expected an array with 3 obj in body.elements, got %f\", childrenLength.Num)\n\t\t\t\t}\n\t\t\t\tlastChildStatus := gjson.Get(n.Body, \"elements.2.body.status\")\n\t\t\t\tif lastChildStatus.Str != \"ignored\" {\n\t\t\t\t\tt.Errorf(\"expected last children.2.body.status == 'ignored', got %s\", lastChildStatus.Raw)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tbn.notificationsThreshold = 2\n\tn, err = bn.aggregate(\"\", LongLived)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else {\n\t\tif !gjson.Valid(n.Body) {\n\t\t\tt.Error(\"expected a valid json in body, gjson reported invalid\")\n\t\t} else {\n\t\t\tresult := gjson.Get(n.Body, \"size\")\n\t\t\tif !result.Exists() {\n\t\t\t\tt.Error(\"expected property 'size' in notification body but gjson can't find it\")\n\t\t\t} else {\n\t\t\t\tif result.Int() != 3 {\n\t\t\t\t\tt.Errorf(\"expected body.size = 3, got %d\", result.Int())\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult = gjson.Get(n.Body, \"elements\")\n\t\t\tif result.Exists() {\n\t\t\t\tt.Error(\"expected body.elements doesn't exist, but gjson found it\")\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/Notifications/email.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage Notifications\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/satori/go.uuid\"\n\t\"time\"\n)\n\ntype EmailNotifiers interface {\n\tSendEmailAdminToUser(user *User, participants []Participant, email *Message) error\n}\n\nconst (\n\tresetPasswordTemplate    = \"email-reset-password-link.yaml\"\n\tonboardingEmailTemplate  = \"email-onboarding.yaml\"\n\twelcomeEmailTemplate     = \"email-welcome.yaml\"\n\tresetLinkFmt             = \"%s/auth/passwords/reset/%s\"\n\tdeviceValidationLinkFmt  = \"%s/validate-device/%s\"\n\tdeviceValidationTemplate = \"email-device-validation.yaml\"\n)\n\n// ByEmail notifies an user by the mean of an email.\nfunc (N *Notifier) ByEmail(notif *Notification) CaliopenError {\n\tif N.admin == nil {\n\t\terr := NewCaliopenErr(FailDependencyCaliopenErr, \"[NotificationsFacility] can't SendEmailAdminToUser, no admin user has been set\")\n\t\tlog.Error(err)\n\t\treturn err\n\t}\n\tN.LogNotification(\"ByEmail\", notif)\n\tswitch notif.Type {\n\tcase NotifAdminMail:\n\t\tparticipants := []Participant{\n\t\t\t{ // sender\n\t\t\t\tAddress:  N.adminLocalID.Identifier,\n\t\t\t\tLabel:    N.adminLocalID.DisplayName,\n\t\t\t\tProtocol: EmailProtocol,\n\t\t\t\tType:     ParticipantFrom,\n\t\t\t},\n\t\t\t{ // recipient\n\t\t\t\tAddress:     notif.User.RecoveryEmail,\n\t\t\t\tContact_ids: []UUID{notif.User.ContactId},\n\t\t\t\tLabel:       notif.User.Name,\n\t\t\t\tProtocol:    EmailProtocol,\n\t\t\t\tType:        ParticipantTo,\n\t\t\t},\n\t\t}\n\t\tif notif.Body == \"ccLocalMailbox\" {\n\t\t\tlocals, err := N.Store.RetrieveLocalsIdentities(notif.User.UserId.String())\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"[SendEmailAdminToUser] failed to retrieve local identities for user %s\", notif.User.UserId)\n\t\t\t} else {\n\t\t\t\tfor _, localIdentity := range locals {\n\t\t\t\t\tif localIdentity.Identifier != \"\" {\n\t\t\t\t\t\tparticipants = append(participants, Participant{\n\t\t\t\t\t\t\tAddress:  localIdentity.Identifier,\n\t\t\t\t\t\t\tLabel:    localIdentity.DisplayName,\n\t\t\t\t\t\t\tProtocol: EmailProtocol,\n\t\t\t\t\t\t\tType:     ParticipantCC,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\terr := N.SendEmailAdminToUser(notif.User, participants, notif.InternalPayload.(*Message))\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Errorf(\"[ByEmail] SendEmailAdminToUser failed for notification %+v\", *notif)\n\t\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[ByEmail] SendEmailAdminToUser failed\")\n\t\t}\n\tcase NotifPasswordReset:\n\t\terr := N.SendPasswordResetEmail(notif.User, notif.InternalPayload.(*TokenSession))\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Errorf(\"[ByEmail] SendPasswordResetEmail failed for notification %+v\", *notif)\n\t\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[ByEmail] SendPasswordResetEmail failed\")\n\t\t}\n\tcase OnboardingMails:\n\t\terr := N.SendOnboardingMails(notif.User)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Errorf(\"[ByEmail] SendOnboardingMails failed for notification %+v\", *notif)\n\t\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[ByEmail] SendOnboardingMails failed\")\n\t\t}\n\tcase NotifDeviceValidation:\n\t\terr := N.SendDeviceValidationEmail(notif.User, notif.Body, notif.InternalPayload.(*TokenSession))\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Errorf(\"[ByEmail] SendDeviceValidationEmail failed for notification %+v\", *notif)\n\t\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[ByEmail] SendDeviceValidationEmail failed\")\n\t\t}\n\tdefault:\n\t\tlog.Errorf(\"[Notifier]ByEmail : unknown notification type <%s>\", notif.Type)\n\t\treturn NewCaliopenErrf(UnprocessableCaliopenErr, \"[Notifier]ByEmail : unknown notification type <%s>\", notif.Type)\n\t}\n\treturn nil\n}\n\n// SendEmailAdminToUser sends an administrative email to recipients found in participants slice.\n// Participants must include at least one `From` and one `To`\n// this is an email composed by the backend to inform user that something happened related to its account\n// func is in charge of saving & indexing draft before sending the \"deliver\" order to the SMTP broker.\nfunc (notif *Notifier) SendEmailAdminToUser(user *User, participants []Participant, email *Message) error {\n\tif email == nil {\n\t\treturn errors.New(\"[SendEmailAdminToUser] can't send a nil email\")\n\t}\n\tnow := time.Now()\n\t(*email).Date = now\n\t(*email).Date_insert = now\n\t(*email).Message_id.UnmarshalBinary(uuid.NewV4().Bytes())\n\t//TODO : (*email).Discussion_id.UnmarshalBinary(uuid.NewV4().Bytes())\n\t(*email).Is_draft = true\n\t(*email).Participants = participants\n\t(*email).Protocol = EmailProtocol\n\t(*email).User_id = notif.admin.UserId\n\t(*email).UserIdentities = []UUID{notif.adminLocalID.Id}\n\n\t// save & index message\n\terr := notif.Store.CreateMessage(email)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[EmailNotifiers]: SendEmailAdminToUser failed to store draft\")\n\t\treturn err\n\t}\n\tuser_info := &UserInfo{User_id: notif.admin.UserId.String(), Shard_id: notif.admin.ShardId}\n\terr = notif.index.CreateMessage(user_info, email)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[EmailNotifiers]: SendEmailAdminToUser failed to index draft\")\n\t\treturn err\n\t}\n\n\tlog.Infof(\"[NotificationsFacility] sending email admin for user <%s> [%s]\", user.Name, user.UserId.String())\n\tconst nats_order = \"deliver\"\n\n\torder := BrokerOrder{\n\t\tOrder:      nats_order,\n\t\tMessageId:  email.Message_id.String(),\n\t\tUserId:     notif.admin.UserId.String(),\n\t\tIdentityId: notif.adminLocalID.Id.String(),\n\t}\n\tnatsMessage, e := json.Marshal(order)\n\tif e != nil {\n\t\treturn fmt.Errorf(\"[EmailNotifiers] failed to build nats message : %s\", e.Error())\n\t}\n\trep, err := notif.NatsQueue.Request(notif.natsTopics[Nats_outSMTP_topicKey], natsMessage, 30*time.Second)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[EmailNotifiers]: SendEmailAdminToUser error\")\n\t\tif notif.NatsQueue.LastError() != nil {\n\t\t\tlog.WithError(notif.NatsQueue.LastError()).Warn(\"[EmailNotifiers]: SendEmailAdminToUser error\")\n\t\t\treturn err\n\t\t}\n\t\treturn err\n\t}\n\tvar reply DeliveryAck\n\terr = json.Unmarshal(rep.Data, &reply)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[EmailNotifiers]: SendEmailAdminToUser error\")\n\t\treturn err\n\t}\n\tif reply.Err {\n\t\terr := errors.New(reply.Response)\n\t\tlog.WithError(err).Warn(\"[EmailNotifiers]: SendEmailAdminToUser error\")\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (notif *Notifier) SendPasswordResetEmail(user *User, session *TokenSession) error {\n\tif user == nil || session == nil {\n\t\treturn errors.New(\"[NotificationsFacility] SendPasswordResetEmail invalid params\")\n\t}\n\n\treset_link := fmt.Sprintf(resetLinkFmt, notif.config.BaseUrl, session.Token)\n\tcontext := map[string]interface{}{\n\t\t\"given_name\":  user.GivenName,\n\t\t\"family_name\": user.FamilyName,\n\t\t\"domain\":      notif.config.BaseUrl,\n\t\t\"url\":         reset_link,\n\t}\n\temail, err := RenderEmail(notif.config.TemplatesPath+resetPasswordTemplate, context)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[RESTfacility] failed to build reset email from template for user %s\", user.UserId.String())\n\t\treturn errors.New(\"[RESTfacility] failed to build reset email\")\n\t}\n\tparticipants := []Participant{\n\t\t{ // sender\n\t\t\tAddress:  (*notif.adminLocalID).Identifier,\n\t\t\tLabel:    (*notif.adminLocalID).DisplayName,\n\t\t\tProtocol: EmailProtocol,\n\t\t\tType:     ParticipantFrom,\n\t\t},\n\t\t{ // recipient\n\t\t\tAddress:     user.RecoveryEmail,\n\t\t\tContact_ids: []UUID{user.ContactId},\n\t\t\tLabel:       user.Name,\n\t\t\tProtocol:    EmailProtocol,\n\t\t\tType:        ParticipantTo,\n\t\t},\n\t}\n\n\terr = notif.SendEmailAdminToUser(user, participants, email)\n\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[RESTfacility] sending password reset email failed for user %s\", user.UserId.String())\n\t\treturn errors.New(\"[RESTfacility] failed to send password reset email\")\n\t}\n\n\treturn nil\n}\n\n// SendOnboardingMails builds and sends one-time emails to user that signed-up\nfunc (notif *Notifier) SendOnboardingMails(user *User) error {\n\tif user == nil {\n\t\treturn errors.New(\"[SendOnboardingMails] must be called with an user, got nil\")\n\t}\n\n\trecipients := []Participant{}\n\t// retrieve user's local email\n\tlocals, err := notif.Store.RetrieveLocalsIdentities(user.UserId.String())\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[SendEmailAdminToUser] failed to retrieve local identities for user %s\", user.UserId)\n\t\treturn errors.New(\"failed to retrieve user's local email address, can't send email\")\n\t}\n\tfor _, localIdentity := range locals {\n\t\tif localIdentity.Identifier != \"\" {\n\t\t\trecipients = append(recipients, Participant{\n\t\t\t\tAddress:  localIdentity.Identifier,\n\t\t\t\tLabel:    localIdentity.DisplayName,\n\t\t\t\tProtocol: EmailProtocol,\n\t\t\t\tType:     ParticipantTo,\n\t\t\t})\n\t\t}\n\t}\n\n\t// build and send first email\n\tonboardingMail, err := RenderEmail(notif.config.TemplatesPath+onboardingEmailTemplate, map[string]interface{}{})\n\tif err == nil {\n\t\tparticipants := recipients\n\t\tparticipants = append(participants, Participant{ // sender\n\t\t\tAddress:  \"contact@caliopen.org\", // TODO: use config\n\t\t\tLabel:    \"Caliopen\",\n\t\t\tProtocol: EmailProtocol,\n\t\t\tType:     ParticipantFrom,\n\t\t})\n\t\terr = notif.SendEmailAdminToUser(user, participants, onboardingMail)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[SendOnboardingMails] failed to send onboarding mail for user %s\", user.UserId)\n\t\t}\n\t} else {\n\t\tlog.WithError(err).Warnf(\"[SendOnboardingMails] failed to render onboardingMail. This email won't be send.\")\n\t}\n\n\t// build and send first email\n\twelcomeMail, err := RenderEmail(notif.config.TemplatesPath+welcomeEmailTemplate, map[string]interface{}{})\n\tif err == nil {\n\t\tparticipants := recipients\n\t\tparticipants = append(participants, Participant{ // sender\n\t\t\tAddress:  \"contact@caliopen.org\", // TODO: use config\n\t\t\tLabel:    \"Laurent Chemla\",\n\t\t\tProtocol: EmailProtocol,\n\t\t\tType:     ParticipantFrom,\n\t\t})\n\t\terr = notif.SendEmailAdminToUser(user, participants, welcomeMail)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[SendOnboardingMails] failed to send welcome mail for user %s\", user.UserId)\n\t\t}\n\t} else {\n\t\tlog.WithError(err).Warnf(\"[SendOnboardingMails] failed to render welcomeMail. This email won't be send.\")\n\t}\n\n\treturn nil\n}\n\nfunc (notif *Notifier) SendDeviceValidationEmail(user *User, deviceName string, session *TokenSession) error {\n\tif user == nil || session == nil {\n\t\treturn errors.New(\"[NotificationsFacility] SendDeviceValidationEmail invalid params\")\n\t}\n\n\treset_link := fmt.Sprintf(deviceValidationLinkFmt, notif.config.BaseUrl, session.Token)\n\tcontext := map[string]interface{}{\n\t\t\"given_name\":  user.GivenName,\n\t\t\"family_name\": user.FamilyName,\n\t\t\"device_name\": deviceName,\n\t\t\"url\":         reset_link,\n\t}\n\temail, err := RenderEmail(notif.config.TemplatesPath+deviceValidationTemplate, context)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[SendDeviceValidationEmail] failed to build device validation email from template for user %s\", user.UserId.String())\n\t\treturn errors.New(\"[SendDeviceValidationEmail] failed to build validation email\")\n\t}\n\tparticipants := []Participant{\n\t\t{ // sender\n\t\t\tAddress:  (*notif.adminLocalID).Identifier,\n\t\t\tLabel:    (*notif.adminLocalID).DisplayName,\n\t\t\tProtocol: EmailProtocol,\n\t\t\tType:     ParticipantFrom,\n\t\t},\n\t\t{ // recipient\n\t\t\tAddress:     user.RecoveryEmail,\n\t\t\tContact_ids: []UUID{user.ContactId},\n\t\t\tLabel:       user.Name,\n\t\t\tProtocol:    EmailProtocol,\n\t\t\tType:        ParticipantTo,\n\t\t},\n\t}\n\n\terr = notif.SendEmailAdminToUser(user, participants, email)\n\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[SendDeviceValidationEmail] sending device validation email failed for user %s\", user.UserId.String())\n\t\treturn errors.New(\"[SendDeviceValidationEmail] failed to send device validation email\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/Notifications/facility.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage Notifications\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/index/elasticsearch\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"os\"\n\t\"time\"\n)\n\ntype (\n\tNotifiers interface {\n\t\tByEmail(*Notification) CaliopenError\n\t\tByNotifQueue(*Notification) CaliopenError\n\t\tNotificationsByTime(userId string, from, to time.Time) ([]Notification, CaliopenError)\n\t\tNotificationsByID(userId, from, to string) ([]Notification, CaliopenError)\n\t\tRetrieveNotification(userId, notificationId string) (Notification, CaliopenError)\n\t\tDeleteNotifications(userId string, until time.Time) CaliopenError\n\t\tDeleteNotification(userId, notificationId string) CaliopenError\n\t}\n\n\tNotifier struct {\n\t\tadmin        *User         // Admin user on whose behalf actions could be done\n\t\tadminLocalID *UserIdentity // Admin's local identity used to send emails\n\t\tconfig       *NotifierConfig\n\t\tindex        backends.NotificationsIndex\n\t\tNatsQueue    *nats.Conn\n\t\tnatsTopics   map[string]string\n\t\tStore        backends.NotificationsStore\n\t\tlog          *log.Logger\n\t}\n)\n\n// NewNotificationsFacility initialises the notifiers\n// it takes the same store & index configurations than the REST API for now\nfunc NewNotificationsFacility(config CaliopenConfig, queue *nats.Conn) (notifier *Notifier) {\n\tnotifier = new(Notifier)\n\tnotifier.log = log.New()\n\tnotifier.log.Out = os.Stdout\n\t// We could set this to any `io.Writer` such as a file\n\t// file, err := os.OpenFile(\"notifications.log\", os.O_CREATE|os.O_WRONLY, 0666)\n\t// if err == nil {\n\t//  notifier.log.Out = file\n\t// } else {\n\t//  log.Info(\"Failed to log to file, using default stdout\")\n\t//  notifier.log.Out = os.Stdout\n\t// }\n\tnotifier.config = &config.NotifierConfig\n\tnotifier.natsTopics = make(map[string]string)\n\tnotifier.natsTopics[Nats_outSMTP_topicKey] = config.NatsConfig.OutSMTP_topic\n\tnotifier.natsTopics[Nats_Contacts_topicKey] = config.NatsConfig.Contacts_topic\n\tnotifier.NatsQueue = queue\n\tswitch config.RESTstoreConfig.BackendName {\n\tcase \"cassandra\":\n\t\tcassaConfig := store.CassandraConfig{\n\t\t\tHosts:       config.RESTstoreConfig.Hosts,\n\t\t\tKeyspace:    config.RESTstoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(config.RESTstoreConfig.Consistency),\n\t\t}\n\t\tif config.RESTstoreConfig.ObjStoreType == \"s3\" {\n\t\t\tcassaConfig.WithObjStore = true\n\t\t\tcassaConfig.OSSConfig.Endpoint = config.RESTstoreConfig.OSSConfig.Endpoint\n\t\t\tcassaConfig.OSSConfig.AccessKey = config.RESTstoreConfig.OSSConfig.AccessKey\n\t\t\tcassaConfig.OSSConfig.SecretKey = config.RESTstoreConfig.OSSConfig.SecretKey\n\t\t\tcassaConfig.OSSConfig.Location = config.RESTstoreConfig.OSSConfig.Location\n\t\t\tcassaConfig.OSSConfig.RawMsgBucket = config.RESTstoreConfig.OSSConfig.Buckets[\"raw_messages\"]\n\t\t\tcassaConfig.OSSConfig.AttachmentBucket = config.RESTstoreConfig.OSSConfig.Buckets[\"temporary_attachments\"]\n\t\t}\n\t\tbackend, err := store.InitializeCassandraBackend(cassaConfig)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatalf(\"Initalization of %s backend failed\", config.RESTstoreConfig.BackendName)\n\t\t}\n\t\tnotifier.Store = backends.NotificationsStore(backend) // type conversion\n\tdefault:\n\t\tlog.Fatalf(\"Unknown backend: %s\", config.RESTstoreConfig.BackendName)\n\t}\n\n\tswitch config.RESTindexConfig.IndexName {\n\tcase \"elasticsearch\":\n\t\tesConfig := index.ElasticSearchConfig{\n\t\t\tUrls: config.RESTindexConfig.Hosts,\n\t\t}\n\t\tindex, err := index.InitializeElasticSearchIndex(esConfig)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatalf(\"Initalization of %s index failed\", config.RESTindexConfig.IndexName)\n\t\t}\n\t\tnotifier.index = backends.NotificationsIndex(index) // type conversion\n\tdefault:\n\t\tlog.Fatalf(\"Unknown index: %s\", config.RESTindexConfig.IndexName)\n\t}\n\n\tuser, err := notifier.Store.UserByUsername(config.NotifierConfig.AdminUsername)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"Failed to retrieve admin user <%s>\", config.NotifierConfig.AdminUsername)\n\t} else if user != nil {\n\t\tnotifier.admin = user\n\t\tids, err := notifier.Store.RetrieveLocalsIdentities(user.UserId.String())\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"Failed to retrieve local identities for admin user <%s>\", config.NotifierConfig.AdminUsername)\n\t\t} else {\n\t\t\t// get first local identity found for now\n\t\t\tnotifier.adminLocalID = &(ids[0])\n\t\t}\n\t}\n\n\treturn notifier\n}\n\nfunc (N *Notifier) LogNotification(method string, notif *Notification) {\n\tif notif != nil {\n\t\tvar userId string\n\t\tif notif.User != nil {\n\t\t\tuserId = notif.User.UserId.String()\n\t\t} else {\n\t\t\tuserId = \"<unknown user id>\"\n\t\t}\n\t\tN.log.WithFields(log.Fields{\n\t\t\t\"method\":   method,\n\t\t\t\"notif_id\": notif.NotifId.String(),\n\t\t}).Infof(\"[Notifier] a notification has been issued for user %s\", userId)\n\t}\n}\n\nfunc (N *Notifier) NotificationsByTime(userId string, from, to time.Time) ([]Notification, CaliopenError) {\n\n\tnotifs, err := N.Store.NotificationsByTime(userId, from, to)\n\tif err != nil {\n\t\treturn []Notification{}, WrapCaliopenErr(err, DbCaliopenErr, \"[RetrieveNotifications] failed\")\n\t}\n\n\treturn notifs, nil\n}\n\nfunc (N *Notifier) NotificationsByID(userId, from, to string) ([]Notification, CaliopenError) {\n\n\tnotifs, err := N.Store.NotificationsByID(userId, from, to)\n\tif err != nil {\n\t\treturn []Notification{}, WrapCaliopenErr(err, DbCaliopenErr, \"[RetrieveNotifications] failed\")\n\t}\n\n\treturn notifs, nil\n}\n\nfunc (N *Notifier) RetrieveNotification(userID, notifID string) (Notification, CaliopenError) {\n\tnotif, err := N.Store.RetrieveNotification(userID, notifID)\n\tif err != nil {\n\t\treturn Notification{}, WrapCaliopenErr(err, DbCaliopenErr, \"[RetrieveNotification] failed\")\n\t}\n\treturn notif, nil\n}\n\nfunc (N *Notifier) DeleteNotifications(userId string, until time.Time) CaliopenError {\n\n\terr := N.Store.DeleteNotifications(userId, until)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[Notifier]DeleteNotifications failed\")\n\t}\n\n\treturn nil\n}\n\nfunc (N *Notifier) DeleteNotification(userID, notifID string) CaliopenError {\n\terr := N.Store.DeleteNotification(userID, notifID)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[Notifier]DeleteNotifications failed\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/Notifications/queue.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage Notifications\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/Sirupsen/logrus\"\n)\n\n// ByNotifQueue notifies an user by the mean of the notification queue\nfunc (N *Notifier) ByNotifQueue(notif *Notification) CaliopenError {\n\tN.LogNotification(\"ByNotificationQueue\", notif)\n\n\terr := N.Store.PutNotificationInQueue(notif)\n\n\tif err != nil {\n\t\tlogrus.WithError(err).Errorf(\"[Notifier]ByNotifQueue failed to put notification in queue\")\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[Notifier]ByNotifQueue failed to put notification in queue\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/Notifications/templating.go",
    "content": "package Notifications\n\nimport (\n\t\"encoding/base64\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"gopkg.in/flosch/pongo2.v3\"\n\t\"gopkg.in/yaml.v2\"\n\t\"io/ioutil\"\n\t\"path\"\n)\n\nvar templateDir string\n\n// RenderResetEmail will load the yaml/j2 template from template_path\n// and scaffold a Message with data provided in context map\nfunc RenderEmail(template_path string, context map[string]interface{}) (*Message, error) {\n\ttemplateDir = path.Dir(template_path)\n\t// adds our base64encodeImgSrc encoder in context\n\tcontext[\"base64encodeImgSrc\"] = base64encodeImgSrc\n\n\t// load email attributes from template file\n\tvar email map[string]interface{}\n\n\tfile, err := ioutil.ReadFile(template_path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = yaml.Unmarshal(file, &email)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar bodyHtml, bodyPlain, subject string\n\n\tif s, found := email[\"subject\"]; found {\n\t\tif s_, ok := s.(string); ok && s_ != \"\" {\n\t\t\ttpl, err := pongo2.FromString(s_)\n\t\t\tif err == nil {\n\t\t\t\tsubject, _ = tpl.Execute(pongo2.Context(context))\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"[RenderEmail] failed to execute template %s\", template_path)\n\t\t\t}\n\t\t}\n\t}\n\n\tif plain, found := email[\"body_plain\"]; found {\n\t\tif p, ok := plain.(string); ok && p != \"\" {\n\t\t\ttpl, err := pongo2.FromString(p)\n\t\t\tif err == nil {\n\t\t\t\tbodyPlain, err = tpl.Execute(pongo2.Context(context))\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"[RenderEmail] failed to execute template %s\", template_path)\n\t\t\t}\n\t\t}\n\t}\n\n\tif html, found := email[\"body_html\"]; found {\n\t\tif h, ok := html.(string); ok && h != \"\" {\n\t\t\ttpl, err := pongo2.FromString(h)\n\t\t\tif err == nil {\n\t\t\t\tbodyHtml, err = tpl.Execute(pongo2.Context(context))\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"[RenderEmail] failed to execute template %s\", template_path)\n\t\t\t}\n\t\t}\n\t}\n\n\t// create Message with strings\n\treturn &Message{\n\t\tSubject:    subject,\n\t\tBody_html:  bodyHtml,\n\t\tBody_plain: bodyPlain,\n\t}, nil\n}\n\n// base64encodeImgSrc takes a path to a file and returns src string to inline image in base64\n// it implements pongo2 filter interface\nfunc base64encodeImgSrc(in *pongo2.Value) *pongo2.Value {\n\timgPath := templateDir + \"/\" + in.String()\n\tif len(imgPath) < 6 {\n\t\treturn pongo2.AsValue(\"\")\n\t}\n\timgExt := path.Ext(imgPath)[1:]\n\timg, err := ioutil.ReadFile(imgPath)\n\tif err != nil {\n\t\treturn pongo2.AsValue(err.Error())\n\t}\n\tbase64Img := base64.StdEncoding.EncodeToString(img)\n\treturn pongo2.AsValue(`data:image/` + imgExt + `;base64,` + base64Img)\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/RESTfacility.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/cache\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/index/elasticsearch\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/tidwall/gjson\"\n\t\"io\"\n)\n\ntype (\n\tRESTservices interface {\n\t\tUsernameIsAvailable(string) (bool, error)\n\t\tSuggestRecipients(user *UserInfo, query_string string) (suggests []RecipientSuggestion, err error)\n\t\tGetSettings(user_id string) (settings *Settings, err error)\n\t\t//contacts\n\t\tCreateContact(user *UserInfo, contact *Contact) error\n\t\tRetrieveContacts(filter IndexSearch) (contacts []*Contact, totalFound int64, err error)\n\t\tRetrieveContact(userID, contactID string) (*Contact, error)\n\t\tRetrieveUserContact(userID string) (*Contact, error)\n\t\tUpdateContact(user *UserInfo, contact, oldContact *Contact, update map[string]interface{}) error\n\t\tPatchContact(user *UserInfo, patch []byte, contactID string) error\n\t\tDeleteContact(user *UserInfo, contactID string) error\n\t\tContactExists(userID, contactID string) bool\n\t\tLookupContactByUri(userID, uri string) ([]*Contact, int64, error)\n\t\tImportVcardFile(info *UserInfo, file io.Reader) error\n\t\t//identities\n\t\tRetrieveContactIdentities(user_id, contact_id string) (identities []ContactIdentity, err error)\n\t\tRetrieveLocalIdentities(user_id string) (identities []UserIdentity, err error)\n\t\tCreateUserIdentity(identity *UserIdentity) CaliopenError\n\t\tRetrieveRemoteIdentities(userId string, withCredentials bool) (ids []*UserIdentity, err CaliopenError)\n\t\tRetrieveUserIdentity(userId, RemoteId string, withCredentials bool) (id *UserIdentity, err CaliopenError)\n\t\tUpdateUserIdentity(identity, oldIdentity *UserIdentity, update map[string]interface{}) CaliopenError\n\t\tPatchUserIdentity(patch []byte, userId, RemoteId string) CaliopenError\n\t\tDeleteUserIdentity(userId, remoteId string) CaliopenError\n\t\tIsRemoteIdentity(userId, remoteId string) bool\n\t\t//providers\n\t\tRetrieveProvidersList() (providers []Provider, err error)\n\t\tGetProviderOauthFor(userID, provider, instance string) (Provider, CaliopenError)\n\t\tCreateTwitterIdentity(requestToken, verifier string) (remoteId string, err CaliopenError)\n\t\tCreateGmailIdentity(state, code string) (remoteId string, err CaliopenError)\n\t\tCreateMastodonIdentity(state, code string) (remoteId string, err CaliopenError)\n\t\t//discussions\n\t\tGetDiscussionsList(user *UserInfo, ILrange, PIrange [2]int8, limit, offset int) ([]Discussion, int, error)\n\t\tDiscussionMetadata(user *UserInfo, discussionId string) (Discussion, error)\n\t\t//messages\n\t\tGetMessagesList(filter IndexSearch) (messages []*Message, totalFound int64, err error)\n\t\tGetMessagesRange(filter IndexSearch) (messages []*Message, totalFound int64, err error)\n\t\tGetMessage(user *UserInfo, message_id string) (message *Message, err error)\n\t\tSendDraft(user *UserInfo, msg_id string) (msg *Message, err error)\n\t\tSetMessageUnread(user *UserInfo, message_id string, status bool) error\n\t\tGetRawMessage(raw_message_id string) (message []byte, err error)\n\t\t//attachments\n\t\tAddAttachment(user *UserInfo, message_id, filename, content_type string, file io.Reader) (attachmentURL string, err error)\n\t\tDeleteAttachment(user *UserInfo, message_id string, attchmt_id string) CaliopenError\n\t\tOpenAttachment(user_id, message_id string, attchmtIndex string) (meta map[string]string, content io.Reader, err error)\n\t\t//tags\n\t\tRetrieveUserTags(user_id string) (tags []Tag, err CaliopenError)\n\t\tCreateTag(tag *Tag) CaliopenError\n\t\tRetrieveTag(user_id, tag_id string) (tag Tag, err CaliopenError)\n\t\tUpdateTag(tag *Tag) CaliopenError\n\t\tPatchTag(patch []byte, user_id, tag_name string) CaliopenError\n\t\tDeleteTag(user_id, tag_name string) CaliopenError\n\t\tUpdateResourceTags(user *UserInfo, resourceID, resourceType string, patch []byte) CaliopenError\n\t\t//search\n\t\tSearch(IndexSearch) (result *IndexResult, err error)\n\t\t//users\n\t\tPatchUser(user_id string, patch *gjson.Result, notifier Notifications.Notifiers) error\n\t\tRequestPasswordReset(payload PasswordResetRequest, notifier Notifications.Notifiers) error\n\t\tValidatePasswordResetToken(token string) (session *TokenSession, err error)\n\t\tResetUserPassword(token, new_password string, notifier Notifications.Notifiers) error\n\t\tDeleteUser(payload ActionsPayload) CaliopenError\n\t\t//devices\n\t\tCreateDevice(device *Device) CaliopenError\n\t\tRetrieveDevices(userId string) ([]Device, CaliopenError)\n\t\tRetrieveDevice(userId, deviceId string) (*Device, CaliopenError)\n\t\tUpdateDevice(device, oldDevice *Device, update map[string]interface{}) CaliopenError\n\t\tPatchDevice(patch []byte, userId, deviceId string) CaliopenError\n\t\tDeleteDevice(userId, deviceId string) CaliopenError\n\t\tRequestDeviceValidation(userId, deviceId, channel string, notifier Notifications.Notifiers) CaliopenError\n\t\tConfirmDeviceValidation(userId, token string) CaliopenError\n\t\t//keys\n\t\tCreatePGPPubKey(label string, pubkey []byte, contact *Contact) (*PublicKey, CaliopenError)\n\t\tRetrieveContactPubKeys(userId, contactId string) (pubkeys PublicKeys, err CaliopenError)\n\t\tRetrievePubKey(userId, resourceId, keyId string) (pubkey *PublicKey, err CaliopenError)\n\t\tDeletePubKey(pubkey *PublicKey) CaliopenError\n\t\tPatchPubKey(patch []byte, userId, resourceId, keyId string) CaliopenError\n\t}\n\tRESTfacility struct {\n\t\tCache      backends.APICache\n\t\tindex      backends.APIIndex\n\t\tnatsTopics map[string]string\n\t\tnats_conn  *nats.Conn\n\t\tproviders  map[string]Provider\n\t\tstore      backends.APIStorage\n\t\tHostname   string\n\t}\n)\n\nfunc NewRESTfacility(config CaliopenConfig, nats_conn *nats.Conn) (rest_facility *RESTfacility) {\n\trest_facility = new(RESTfacility)\n\trest_facility.nats_conn = nats_conn\n\trest_facility.natsTopics = map[string]string{\n\t\tNats_outSMTP_topicKey:     config.NatsConfig.OutSMTP_topic,\n\t\tNats_outIMAP_topicKey:     config.NatsConfig.OutIMAP_topic,\n\t\tNats_Contacts_topicKey:    config.NatsConfig.Contacts_topic,\n\t\tNats_outTwitter_topicKey:  config.NatsConfig.OutTWITTER_topic,\n\t\tNats_outMastodon_topicKey: config.NatsConfig.OutMASTODON_topic,\n\t\tNats_Keys_topicKey:        config.NatsConfig.Keys_topic,\n\t\tNats_IdPoller_topicKey:    config.NatsConfig.IdPoller_topic,\n\t}\n\tswitch config.RESTstoreConfig.BackendName {\n\tcase \"cassandra\":\n\t\tcassaConfig := store.CassandraConfig{\n\t\t\tHosts:       config.RESTstoreConfig.Hosts,\n\t\t\tKeyspace:    config.RESTstoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(config.RESTstoreConfig.Consistency),\n\t\t\tUseVault:    config.RESTstoreConfig.UseVault,\n\t\t}\n\t\tif config.RESTstoreConfig.ObjStoreType == \"s3\" {\n\t\t\tcassaConfig.WithObjStore = true\n\t\t\tcassaConfig.OSSConfig.Endpoint = config.RESTstoreConfig.OSSConfig.Endpoint\n\t\t\tcassaConfig.OSSConfig.AccessKey = config.RESTstoreConfig.OSSConfig.AccessKey\n\t\t\tcassaConfig.OSSConfig.SecretKey = config.RESTstoreConfig.OSSConfig.SecretKey\n\t\t\tcassaConfig.OSSConfig.Location = config.RESTstoreConfig.OSSConfig.Location\n\t\t\tcassaConfig.OSSConfig.RawMsgBucket = config.RESTstoreConfig.OSSConfig.Buckets[\"raw_messages\"]\n\t\t\tcassaConfig.OSSConfig.AttachmentBucket = config.RESTstoreConfig.OSSConfig.Buckets[\"temporary_attachments\"]\n\t\t}\n\t\tif config.RESTstoreConfig.UseVault {\n\t\t\tcassaConfig.HVaultConfig.Url = config.RESTstoreConfig.VaultConfig.Url\n\t\t\tcassaConfig.HVaultConfig.Username = config.RESTstoreConfig.VaultConfig.Username\n\t\t\tcassaConfig.HVaultConfig.Password = config.RESTstoreConfig.VaultConfig.Password\n\t\t}\n\t\tbackend, err := store.InitializeCassandraBackend(cassaConfig)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatalf(\"initalization of %s backend failed\", config.RESTstoreConfig.BackendName)\n\t\t}\n\n\t\trest_facility.store = backends.APIStorage(backend) // type conversion\n\n\tdefault:\n\t\tlog.Fatalf(\"unknown backend: %s\", config.RESTstoreConfig.BackendName)\n\t}\n\n\tswitch config.RESTindexConfig.IndexName {\n\tcase \"elasticsearch\":\n\t\tesConfig := index.ElasticSearchConfig{\n\t\t\tUrls: config.RESTindexConfig.Hosts,\n\t\t}\n\t\tindx, err := index.InitializeElasticSearchIndex(esConfig)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatalf(\"initalization of %s index failed\", config.RESTindexConfig.IndexName)\n\t\t}\n\t\trest_facility.index = backends.APIIndex(indx) // type conversion\n\tdefault:\n\t\tlog.Fatalf(\"unknown index: %s\", config.RESTindexConfig.IndexName)\n\t}\n\tvar err error\n\trest_facility.Cache, err = cache.InitializeRedisBackend(config.CacheConfig)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"initialization of Redis cache failed\")\n\t}\n\n\trest_facility.providers = map[string]Provider{}\n\tfor _, provider := range config.Providers {\n\t\tif provider.Name != \"\" {\n\t\t\trest_facility.providers[provider.Name] = provider\n\t\t}\n\t}\n\n\trest_facility.Hostname = config.Hostname\n\treturn rest_facility\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/attachment.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/brokers/go.emails\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/satori/go.uuid\"\n\t\"io\"\n\t\"strconv\"\n)\n\nfunc (rest *RESTfacility) AddAttachment(user *UserInfo, message_id, filename, content_type string, file io.Reader) (tempId string, err error) {\n\t//check if message_id belongs to user and is a draft\n\tmsg, err := rest.store.RetrieveMessage(user.User_id, message_id)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !msg.Is_draft {\n\t\treturn \"\", errors.New(\"message \" + message_id + \" is not a draft.\")\n\t}\n\n\t//store temporary file in objectStore facility\n\ttmpId := uuid.NewV4()\n\ttempId = tmpId.String()\n\turl, size, err := rest.store.StoreAttachment(tempId, file)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t//update draft with new attachment references\n\tdraftAttchmnt := Attachment{\n\t\tContentType: content_type,\n\t\tFileName:    filename,\n\t\tIsInline:    false,\n\t\tSize:        size,\n\t\tTempID:      UUID(tmpId),\n\t\tURL:         url,\n\t}\n\tdraftAttchmnt.TempID.UnmarshalBinary(tmpId.Bytes())\n\tmsg.Attachments = append(msg.Attachments, draftAttchmnt)\n\n\t//update store\n\tfields := make(map[string]interface{})\n\tfields[\"Attachments\"] = msg.Attachments\n\terr = rest.store.UpdateMessage(msg, fields)\n\tif err != nil {\n\t\t//roll-back attachment storage before returning the error\n\t\trest.store.DeleteAttachment(url)\n\t\treturn \"\", err\n\t}\n\t//update index\n\terr = rest.index.UpdateMessage(user, msg, fields)\n\tif err != nil {\n\t\t//roll-back attachment storage before returning the error\n\t\tfields[\"Attachments\"] = msg.Attachments[:len(msg.Attachments)-1]\n\t\trest.store.UpdateMessage(msg, fields)\n\t\trest.store.DeleteAttachment(url)\n\t\treturn \"\", err\n\t}\n\n\treturn\n}\n\nfunc (rest *RESTfacility) DeleteAttachment(user *UserInfo, message_id string, attchmt_id string) CaliopenError {\n\t//check if message_id belongs to user and is a draft and index is consistent\n\tmsg, err := rest.store.RetrieveMessage(user.User_id, message_id)\n\tif err != nil {\n\t\tvar msg string\n\t\tif err.Error() == \"not found\" {\n\t\t\tmsg = \"message not found\"\n\t\t}\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, msg)\n\t}\n\n\tif !msg.Is_draft {\n\t\treturn NewCaliopenErrf(ForbiddenCaliopenErr, \"message %s is not a draft\", message_id)\n\t}\n\n\t//find and remove attachment's from draft\n\tfor i, attachment := range msg.Attachments {\n\t\tif attachment.TempID.String() == attchmt_id {\n\t\t\tmsg.Attachments = append(msg.Attachments[:i], msg.Attachments[i+1:]...)\n\n\t\t\t//update store\n\t\t\tfields := make(map[string]interface{})\n\t\t\tfields[\"Attachments\"] = msg.Attachments\n\t\t\trest.store.UpdateMessage(msg, fields)\n\t\t\tif err != nil {\n\t\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"\")\n\t\t\t}\n\t\t\t//update index\n\t\t\terr = rest.index.UpdateMessage(user, msg, fields)\n\n\t\t\t//remove temporary file from object store\n\t\t\terr = rest.store.DeleteAttachment(attachment.URL)\n\t\t\tif err != nil {\n\t\t\t\treturn WrapCaliopenErrf(err, DbCaliopenErr, \"failed to remove temp attachment at uri '%s' with error <%s>\", attachment.URL, err.Error())\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn NewCaliopenErr(NotFoundCaliopenErr, \"attachment not found\")\n\n}\n\n// returns an io.Reader and metadata to conveniently read the attachment\nfunc (rest *RESTfacility) OpenAttachment(user_id, message_id, attchmtIndex string) (meta map[string]string, content io.Reader, err error) {\n\tif attchmtIndex == \"\" {\n\t\treturn meta, nil, errors.New(fmt.Sprint(\"empty attachment id\"))\n\t}\n\tmeta = make(map[string]string)\n\t//check if message_id belongs to user and index is consistent\n\tmsg, err := rest.store.RetrieveMessage(user_id, message_id)\n\tif err != nil {\n\t\treturn meta, nil, err\n\t}\n\tvar index int\n\tif msg.Is_draft {\n\t\t// retrieve attachment by temp_id\n\t\tnotfound := true\n\t\tfor _, att := range msg.Attachments {\n\t\t\tif att.TempID.String() == attchmtIndex {\n\t\t\t\tmeta[\"Content-Type\"] = att.ContentType\n\t\t\t\tmeta[\"Message-Size\"] = strconv.Itoa(att.Size)\n\t\t\t\tmeta[\"Filename\"] = att.FileName\n\t\t\t\tmeta[\"Url\"] = att.URL\n\t\t\t\tnotfound = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif notfound {\n\t\t\treturn meta, nil, NewCaliopenErr(NotFoundCaliopenErr, \"attachment not found\")\n\t\t}\n\n\t} else {\n\t\t// retrieve attachment by index\n\t\tindex, err = strconv.Atoi(attchmtIndex)\n\t\tif err != nil || index < 0 || index > (len(msg.Attachments)-1) {\n\t\t\treturn meta, nil, NewCaliopenErr(NotFoundCaliopenErr, \"attachment not found\")\n\t\t}\n\t\tmeta[\"Content-Type\"] = msg.Attachments[index].ContentType\n\t\tmeta[\"Message-Size\"] = strconv.Itoa(msg.Attachments[index].Size)\n\t\tmeta[\"Filename\"] = msg.Attachments[index].FileName\n\t}\n\n\t// create a Reader\n\t// either from object store (draft context)\n\t// or from raw message's mime part (non-draft context)\n\tif msg.Is_draft {\n\t\tattachment, e := rest.store.GetAttachment(meta[\"Url\"])\n\t\tif e != nil {\n\t\t\treturn map[string]string{}, nil, e\n\t\t}\n\t\tcontent = attachment\n\t\treturn\n\n\t} else {\n\t\trawMsg, e := rest.store.GetRawMessage(msg.Raw_msg_id.String())\n\t\tif e != nil {\n\t\t\treturn map[string]string{}, nil, e\n\t\t}\n\t\tjson_email, e := email_broker.EmailToJsonRep(rawMsg.Raw_data)\n\t\tif e != nil {\n\t\t\treturn map[string]string{}, nil, e\n\t\t}\n\t\tattachments, e := json_email.ExtractAttachments(index)\n\t\tif e != nil {\n\t\t\treturn map[string]string{}, nil, e\n\t\t}\n\t\tcontent = bytes.NewReader(attachments[0])\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/contacts.go",
    "content": "/*\n * // Copyleft (ɔ) 2017 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage REST\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/contact\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/bitly/go-simplejson\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/satori/go.uuid\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n)\n\n// CreateContact validates Contact before saving it to cassandra and ES\nfunc (rest *RESTfacility) CreateContact(user *UserInfo, contact *Contact) (err error) {\n\t// add missing properties\n\tcontact.ContactId.UnmarshalBinary(uuid.NewV4().Bytes())\n\tcontact.DateInsert = time.Now()\n\tcontact.DateUpdate = contact.DateInsert\n\n\t// normalization\n\thelpers.ComputeNewTitle(contact)\n\thelpers.NormalizePhoneNumbers(contact)\n\tMarshalNested(contact)\n\tMarshalRelated(contact)\n\n\terr = rest.store.CreateContact(contact)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = rest.index.CreateContact(user, contact)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// notify external components\n\tgo func(contact *Contact) {\n\t\tconst updatePI_order = \"contact_update\"\n\t\tnatsMessage := fmt.Sprintf(Nats_contact_tmpl, updatePI_order, contact.ContactId.String(), contact.UserId.String())\n\t\trest.PublishOnNats(natsMessage, rest.natsTopics[Nats_Contacts_topicKey])\n\t}(contact)\n\n\treturn nil\n}\n\n// RetrieveContacts returns contacts collection from index given filter params\nfunc (rest *RESTfacility) RetrieveContacts(filter IndexSearch) (contacts []*Contact, totalFound int64, err error) {\n\n\tcontacts, totalFound, err = rest.index.FilterContacts(filter)\n\tif err != nil {\n\t\treturn []*Contact{}, 0, err\n\t}\n\n\treturn\n}\n\n// RetrieveContact returns one contact\nfunc (rest *RESTfacility) RetrieveContact(userID, contactID string) (contact *Contact, err error) {\n\treturn rest.store.RetrieveContact(userID, contactID)\n}\n\nfunc (rest *RESTfacility) LookupContactByUri(userID, uri string) (contacts []*Contact, totalFound int64, err error) {\n\tcontacts = []*Contact{}\n\turi = strings.ToLower(uri)\n\turiSplit := strings.SplitN(uri, \":\", 2)\n\tif len(uriSplit) != 2 {\n\t\terr = fmt.Errorf(\"[LookupContactByUri] uri malformed : %s => %v\", uri, uriSplit)\n\t\tlog.WithError(err)\n\t\treturn\n\t}\n\tids, err := rest.store.LookupContactsByIdentifier(userID, uriSplit[1], uriSplit[0])\n\tif err != nil {\n\t\treturn\n\t}\n\tfor _, contactId := range ids {\n\t\tc := Contact{}\n\t\tc.ContactId = UUID(uuid.FromStringOrNil(contactId))\n\t\tcontacts = append(contacts, &c)\n\t\ttotalFound++\n\t}\n\treturn\n}\n\n// RetrieveUserContact returns the contact entry belonging to user.\n// This is the contact that is auto-created for user and can't be deleted.\nfunc (rest *RESTfacility) RetrieveUserContact(userID string) (contact *Contact, err error) {\n\tcontactID := rest.store.RetrieveUserContactId(userID)\n\tif contactID == \"\" {\n\t\treturn nil, NewCaliopenErrf(NotFoundCaliopenErr, \"[RetrieveUserContact] didn't find contact id for user %s\", userID)\n\t}\n\tcontact, err = rest.store.RetrieveContact(userID, contactID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn\n}\n\n// PatchContact is a shortcut for REST api to :\n// - retrieve the contact from db\n// - UpdateWithPatch() with UserActor role\n// - then UpdateContact() to save updated contact to stores & index if everything went good.\nfunc (rest *RESTfacility) PatchContact(user *UserInfo, patch []byte, contactID string) error {\n\t// TODO : fix removing identities which fails silently on user's contact\n\tcurrent_contact, err := rest.RetrieveContact(user.User_id, contactID)\n\tif err != nil {\n\t\tif err.Error() == \"not found\" {\n\t\t\treturn NewCaliopenErr(NotFoundCaliopenErr, \"[RESTfacility] contact not found\")\n\t\t} else {\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] PatchContact failed to retrieve contact\")\n\t\t}\n\t}\n\n\t// read into the patch to make basic controls before processing it with generic helper\n\tpatchReader, err := simplejson.NewJson(patch)\n\tif err != nil {\n\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[RESTfacility] PatchContact failed with simplejson error : %s\", err)\n\t}\n\t// forbid tags modification immediately\n\tif _, hasTagsProp := patchReader.CheckGet(\"tags\"); hasTagsProp {\n\t\treturn NewCaliopenErr(ForbiddenCaliopenErr, \"[RESTfacility] PatchContact : patching tags through parent object is forbidden\")\n\t}\n\t// checks \"current_state\" property is present\n\tif _, hasCurrentState := patchReader.CheckGet(\"current_state\"); !hasCurrentState {\n\t\treturn NewCaliopenErr(ForbiddenCaliopenErr, \"[RESTfacility] PatchContact : current_state property must be in patch\")\n\t}\n\n\t// patch seams OK, apply it to the resource\n\tvar modifiedFields map[string]interface{}\n\tnewContact, modifiedFields, err := helpers.UpdateWithPatch(patch, current_contact, UserActor)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[RESTfacility] PatchContact failed\")\n\t\treturn WrapCaliopenErr(err, FailDependencyCaliopenErr, \"[RESTfacility] PatchContact failed\")\n\t}\n\n\tneedNewTitle := false\n\tdiscoverKey := false\n\n\tfor key := range modifiedFields {\n\t\tswitch key {\n\t\t// check if title has to be re-computed\n\t\tcase \"AdditionalName\", \"FamilyName\", \"GivenName\", \"NamePrefix\", \"NameSuffix\":\n\t\t\tneedNewTitle = true\n\t\t\t// Check if we can try to discover a public key\n\t\tcase \"Emails\", \"Identities\":\n\t\t\tdiscoverKey = true\n\t\t}\n\t}\n\tif needNewTitle {\n\t\thelpers.ComputeTitle(newContact.(*Contact))\n\t\tmodifiedFields[\"Title\"] = newContact.(*Contact).Title\n\t}\n\n\t// save updated resource\n\terr = rest.UpdateContact(user, newContact.(*Contact), current_contact, modifiedFields)\n\tif err != nil {\n\t\tif strings.HasPrefix(err.Error(), \"uri <\") {\n\t\t\treturn WrapCaliopenErrf(err, ForbiddenCaliopenErr, \"[RESTfacility] PatchContact forbidden : %s\", err)\n\t\t} else {\n\t\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[RESTfacility] PatchContact failed with UpdateContact error : %s\", err)\n\t\t}\n\t}\n\tif discoverKey {\n\t\terr = rest.launchKeyDiscovery(current_contact, modifiedFields)\n\t}\n\n\treturn nil\n\n}\n\nfunc (rest *RESTfacility) launchKeyDiscovery(current_contact *Contact, updatedFields map[string]interface{}) error {\n\tgo func(contact *Contact) {\n\t\tconst discover_order = \"discover_key\"\n\t\tmessage := DiscoverKeyMessage{Order: discover_order,\n\t\t\tContactId: current_contact.ContactId.String(),\n\t\t\tUserId:    current_contact.UserId.String()}\n\t\tif value, ok := updatedFields[\"Emails\"]; ok {\n\t\t\tmessage.Emails = value.([]EmailContact)\n\t\t}\n\t\tif value, ok := updatedFields[\"Identities\"]; ok {\n\t\t\tmessage.Identities = value.([]SocialIdentity)\n\t\t}\n\t\tnatsMessage, err := json.Marshal(message)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tlog.Infof(\"Will publish nats topic %s for contact %s\", rest.natsTopics[Nats_Keys_topicKey], current_contact.ContactId.String())\n\t\trest.PublishOnNats(string(natsMessage), rest.natsTopics[Nats_Keys_topicKey])\n\t}(current_contact)\n\treturn nil\n}\n\n// UpdateContact updates a contact in store & index with payload\nfunc (rest *RESTfacility) UpdateContact(user *UserInfo, contact, oldContact *Contact, modifiedFields map[string]interface{}) error {\n\n\terr := rest.store.UpdateContact(contact, oldContact, modifiedFields)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = rest.index.UpdateContact(user, contact, modifiedFields)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// notify external components\n\tgo func(contact *Contact) {\n\t\tconst update_order = \"contact_update\"\n\t\tnatsMessage := fmt.Sprintf(Nats_contact_tmpl, update_order, contact.ContactId.String(), contact.UserId.String())\n\t\terr := rest.PublishOnNats(natsMessage, rest.natsTopics[Nats_Contacts_topicKey])\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[UpdateContact] failed to publish contact_update on nats\")\n\t\t}\n\t}(contact)\n\n\treturn nil\n}\n\n// DeleteContact deletes a contact in store & index, only if :\n// - contact belongs to user ;-)\n// - contact is not the user's contact card\nfunc (rest *RESTfacility) DeleteContact(info *UserInfo, contactID string) error {\n\tuser, err := rest.store.RetrieveUser(info.User_id)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := rest.store.RetrieveContact(info.User_id, contactID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif user.ContactId == c.ContactId {\n\t\treturn errors.New(\"can't delete contact card related to user\")\n\t}\n\n\terrGroup := new([]string)\n\terr = rest.store.DeleteContact(c)\n\tif err != nil {\n\t\t*errGroup = append(*errGroup, err.Error())\n\t}\n\n\terr = rest.index.DeleteContact(info, c)\n\tif err != nil {\n\t\t*errGroup = append(*errGroup, err.Error())\n\t}\n\tif len(*errGroup) > 0 {\n\t\treturn fmt.Errorf(\"%s\", strings.Join(*errGroup, \" / \"))\n\t}\n\treturn nil\n}\n\nfunc (rest *RESTfacility) ContactExists(userID, contactID string) bool {\n\treturn rest.store.ContactExists(userID, contactID)\n}\n\n// Process a vcard file and create related contacts\nfunc (rest *RESTfacility) ImportVcardFile(info *UserInfo, file io.Reader) error {\n\tvcards, err := contact.ParseVcardFile(file)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlog.Debug(\"[ImportVcardFile] Have parse \", len(vcards), \" vcards\")\n\n\timportErrors := make([]error, 0, len(vcards))\n\tfor _, card := range vcards {\n\t\tc, err := contact.FromVcard(info, card)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"[ImportVcardFile] Error during vcard transformation \", err)\n\t\t\timportErrors = append(importErrors, err)\n\t\t} else {\n\t\t\terr = rest.CreateContact(info, c)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(\"[ImportVcardFile] Create contact failed with error \", err)\n\t\t\t\timportErrors = append(importErrors, err)\n\t\t\t} else {\n\t\t\t\tif c.PublicKeys != nil {\n\t\t\t\t\tfor _, key := range c.PublicKeys {\n\t\t\t\t\t\terr = rest.store.CreatePGPPubKey(&key)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Warn(\"Create pgp public key failed \", err)\n\t\t\t\t\t\t\timportErrors = append(importErrors, err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfor _, err := range importErrors {\n\t\tlog.Warn(\"Import vcard error: \", err)\n\t}\n\tif len(importErrors) == len(vcards) {\n\t\treturn errors.New(\"No vcard imported\")\n\t}\n\treturn nil\n}\n\n// addIdentityToContact updates Contact card in db and index with data from UserIdentity\n// it embeds a new Email or a new SocialIdentity or a new IM depending of UserIdentity's type.\n// returns new version of Contact saved in stores.\nfunc addIdentityToContact(storeContact backends.ContactStorage, indexContact backends.ContactIndex, storeUser backends.UserStorage, identity UserIdentity, contact *Contact) (*Contact, CaliopenError) {\n\t// TODO : prevent duplicate\n\tupdatedFields := map[string]interface{}{}\n\tnewContact := *contact\n\tswitch identity.Protocol {\n\tcase EmailProtocol, ImapProtocol, SmtpProtocol:\n\t\t// prevent duplicate\n\t\tfor _, email := range contact.Emails {\n\t\t\tif email.Address == identity.Identifier {\n\t\t\t\tlog.Infof(\"[addIdentityToContact] email %s already exists for user %s, aborting\", identity.Identifier, identity.UserId)\n\t\t\t\treturn contact, nil\n\t\t\t}\n\t\t}\n\t\tec := new(EmailContact)\n\t\tec.MarshallNew()\n\t\tec.Address = identity.Identifier\n\t\tec.Type = \"other\"\n\t\tif identity.DisplayName != \"\" {\n\t\t\tec.Label = identity.DisplayName\n\t\t} else {\n\t\t\tec.Label = identity.Identifier\n\t\t}\n\t\tec.IsPrimary = false\n\t\tif contact.Emails == nil {\n\t\t\tnewContact.Emails = []EmailContact{*ec}\n\t\t} else {\n\t\t\tnewContact.Emails = append(contact.Emails, *ec)\n\t\t}\n\t\tupdatedFields[\"Emails\"] = newContact.Emails\n\tcase TwitterProtocol:\n\t\t// prevent duplicate\n\t\tfor _, socialId := range contact.Identities {\n\t\t\tif socialId.Type == TwitterProtocol && socialId.Name == identity.Identifier {\n\t\t\t\tlog.Infof(\"[addIdentityToContact] social identity %s already exists for user %s, aborting\", identity.Identifier, identity.UserId)\n\t\t\t\treturn contact, nil\n\t\t\t}\n\t\t}\n\t\tsi := new(SocialIdentity)\n\t\tsi.MarshallNew()\n\t\tsi.Type = TwitterProtocol\n\t\tsi.Name = identity.Identifier\n\t\tsi.Infos = map[string]string{\n\t\t\t\"twitterid\":   identity.Infos[\"twitterid\"],\n\t\t\t\"screen_name\": identity.Identifier,\n\t\t}\n\t\tif contact.Identities == nil {\n\t\t\tnewContact.Identities = []SocialIdentity{*si}\n\t\t} else {\n\t\t\tnewContact.Identities = append(contact.Identities, *si)\n\t\t}\n\t\tupdatedFields[\"Identities\"] = newContact.Identities\n\tcase MastodonProtocol:\n\t\t// prevent duplicate\n\t\tfor _, socialId := range contact.Identities {\n\t\t\tif socialId.Type == MastodonProtocol && socialId.Name == identity.Identifier {\n\t\t\t\tlog.Infof(\"[addIdentityToContact] social identity %s already exists for user %s, aborting\", identity.Identifier, identity.UserId)\n\t\t\t\treturn contact, nil\n\t\t\t}\n\t\t}\n\t\tsi := new(SocialIdentity)\n\t\tsi.MarshallNew()\n\t\tsi.Type = MastodonProtocol\n\t\tsi.Name = identity.Identifier\n\t\tsi.Infos = map[string]string{\n\t\t\t\"mastodon_id\":  identity.Infos[\"mastodon_id\"],\n\t\t\t\"display_name\": identity.DisplayName,\n\t\t}\n\t\tif contact.Identities == nil {\n\t\t\tnewContact.Identities = []SocialIdentity{*si}\n\t\t} else {\n\t\t\tnewContact.Identities = append(contact.Identities, *si)\n\t\t}\n\t\tupdatedFields[\"Identities\"] = newContact.Identities\n\tdefault:\n\t\treturn nil, NewCaliopenErrf(UnprocessableCaliopenErr, \"[addIdentityToContact] unknown protocol %s for identity %s. Can't add identity to contact card.\", identity.Protocol, identity.Id)\n\t}\n\n\terr := storeContact.UpdateContact(&newContact, contact, updatedFields)\n\tif err != nil {\n\t\treturn nil, WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[addIdentityToContact] failed to update contact %s in store\", contact.ContactId)\n\t}\n\tuserShard := storeUser.GetShardForUser(identity.UserId.String())\n\terr = indexContact.UpdateContact(&UserInfo{identity.UserId.String(), userShard}, &newContact, updatedFields)\n\tif err != nil {\n\t\treturn nil, WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[addIdentityToContact] failed to update contact %s in index\", contact.ContactId)\n\t}\n\n\treturn &newContact, nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/contacts_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"testing\"\n)\n\nfunc TestRESTfacility_addIdentityToContact(t *testing.T) {\n\t// test adding an email account\n\tremoteEmail := backendstest.RemoteIdentities[backendstest.DevIdoireUserId+\"7e356efb-d24c-493a-b558-e58c7ad20ac3\"]\n\tcontact := backendstest.Contacts[backendstest.DevIdoireUserId+\"5f0baee8-1278-43eb-9931-01b7383b419b\"]\n\tnewContact, err := addIdentityToContact(&backendstest.ContactsBackend{}, &backendstest.ContactsIndex{}, &backendstest.UsersBackend{}, *remoteEmail, contact)\n\tif err != nil {\n\t\tt.Errorf(\"add email identity returns error : %s. Cause : %s. Code : %d\", err, err.Cause(), err.Code())\n\t}\n\tif newContact == nil {\n\t\tt.Error(\"expected add email identity returned a reference to a new Contact, got nil\")\n\t}\n\tif len(newContact.Emails) != 2 {\n\t\tt.Errorf(\"expected newContact.Emails with 2 elements, got %d\", len(newContact.Emails))\n\t} else {\n\t\tcontactEmail := EmailContact{\n\t\t\tAddress:   remoteEmail.Identifier,\n\t\t\tIsPrimary: false,\n\t\t\tLabel:     remoteEmail.DisplayName,\n\t\t\tType:      \"other\",\n\t\t}\n\t\tnewContactEmail := newContact.Emails[1]\n\t\tif newContactEmail.IsPrimary {\n\t\t\tt.Errorf(\"expected newContactEmail.IsPrimary set to false, got true\")\n\t\t}\n\t\tif newContactEmail.Address != contactEmail.Address {\n\t\t\tt.Errorf(\"expected newContactEmail.Address set to %s, got %s\", contactEmail.Address, newContactEmail.Address)\n\t\t}\n\t\tif newContactEmail.Label != contactEmail.Label {\n\t\t\tt.Errorf(\"expected newContactEmail.Label set to %s, got %s\", contactEmail.Label, newContactEmail.Label)\n\t\t}\n\t\tif newContactEmail.Type != contactEmail.Type {\n\t\t\tt.Errorf(\"expected newContactEmail.Type set to %s, got %s\", contactEmail.Type, newContactEmail.Type)\n\t\t}\n\t}\n\n\t// test adding a Twitter account\n\tremoteTw := backendstest.RemoteIdentities[backendstest.EmmaTommeUserId+\"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\"]\n\tcontact = backendstest.Contacts[backendstest.EmmaTommeUserId+\"63ab7904-c416-4f1a-9652-3de82e4fd1f1\"]\n\tnewContact, err = addIdentityToContact(&backendstest.ContactsBackend{}, &backendstest.ContactsIndex{}, &backendstest.UsersBackend{}, *remoteTw, contact)\n\tif err != nil {\n\t\tt.Errorf(\"add twitter identity returns error : %s. Cause : %s. Code : %d\", err, err.Cause(), err.Code())\n\t}\n\tif newContact == nil {\n\t\tt.Error(\"expected add twitter identity returned a reference to a new Contact, got nil\")\n\t}\n\tif len(newContact.Identities) != 2 {\n\t\tt.Errorf(\"expected newContact.Identities with 2 element, got %d\", len(newContact.Identities))\n\t} else {\n\t\tcontactTwitter := SocialIdentity{\n\t\t\tType: TwitterProtocol,\n\t\t\tName: remoteTw.Identifier,\n\t\t\tInfos: map[string]string{\n\t\t\t\t\"twitterid\":   remoteTw.Infos[\"twitterid\"],\n\t\t\t\t\"screen_name\": remoteTw.Identifier,\n\t\t\t},\n\t\t}\n\t\tnewContactTw := newContact.Identities[1]\n\t\tif newContactTw.Type != contactTwitter.Type {\n\t\t\tt.Errorf(\"expected newContact.Identity.Type set to %s, got %s\", contactTwitter.Type, newContactTw.Type)\n\t\t}\n\t\tif newContactTw.Name != contactTwitter.Name {\n\t\t\tt.Errorf(\"expected newContact.Identity.Name set to %s, got %s\", contactTwitter.Name, newContactTw.Name)\n\t\t}\n\t\tif len(newContactTw.Infos) != 2 {\n\t\t\tt.Errorf(\"expected infos map with 2 elements, got %d\", len(newContactTw.Infos))\n\t\t} else {\n\t\t\tif v, ok := newContactTw.Infos[\"twitterid\"]; !ok || v != contactTwitter.Infos[\"twitterid\"] {\n\t\t\t\tt.Errorf(\"expected newContact.Infos['twitterid'] set to %s, got %s\", contactTwitter.Infos[\"twitterid\"], newContactTw.Infos[\"twitterid\"])\n\t\t\t}\n\t\t\tif v, ok := newContactTw.Infos[\"screen_name\"]; !ok || v != contactTwitter.Infos[\"screen_name\"] {\n\t\t\t\tt.Errorf(\"expected newContact.Infos['screen_name'] set to %s, got %s\", contactTwitter.Infos[\"screen_name\"], newContactTw.Infos[\"screen_name\"])\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/devices.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage REST\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/bitly/go-simplejson\"\n\t\"github.com/renstrom/shortuuid\"\n\t\"github.com/satori/go.uuid\"\n\t\"gopkg.in/redis.v5\"\n\t\"strings\"\n\t\"time\"\n)\n\n// unexported vars to help override funcs in tests\nvar (\n\tnotifyByEmail = func(notifier Notifications.Notifiers, notif *Notification) CaliopenError {\n\t\treturn notifier.ByEmail(notif)\n\t}\n)\n\nfunc (rest *RESTfacility) RetrieveDevices(userId string) (devices []Device, err CaliopenError) {\n\tdevices, e := rest.store.RetrieveDevices(userId)\n\tif e != nil {\n\t\treturn devices, WrapCaliopenErr(e, DbCaliopenErr, \"[RESTfacility] RetrieveDevices failed\")\n\t}\n\treturn devices, nil\n}\n\nfunc (rest *RESTfacility) CreateDevice(device *Device) CaliopenError {\n\t// add missing properties\n\tdevice.DeviceId.UnmarshalBinary(uuid.NewV4().Bytes())\n\tdevice.DateInsert = time.Now()\n\tif strings.TrimSpace(device.Type) == \"\" {\n\t\tdevice.Type = DefaultDeviceType()\n\t}\n\t/** MarshalNested(device) /** no nested for now. **/\n\tMarshalRelated(device)\n\n\tif !IsValidDeviceType(device.Type) {\n\t\treturn NewCaliopenErrf(UnprocessableCaliopenErr, \"[RESTfacility] CreateDevice : unknown type <%s> for new device\", device.Type)\n\t}\n\n\terr := rest.store.CreateDevice(device)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] CreateDevice failed to CreateDevice in store\")\n\t}\n\n\treturn nil\n}\n\nfunc (rest *RESTfacility) RetrieveDevice(userId, deviceId string) (device *Device, err CaliopenError) {\n\n\tdevice, e := rest.store.RetrieveDevice(userId, deviceId)\n\n\tif e != nil {\n\t\treturn nil, WrapCaliopenErr(e, DbCaliopenErr, \"[RESTfacility] RetrieveDevice failed\")\n\t}\n\n\treturn device, nil\n}\n\n// PatchDevice is a shortcut for REST api to :\n// - retrieve the device from db\n// - UpdateWithPatch()\n// - then UpdateDevice() to save updated device to store if everything went good.\nfunc (rest *RESTfacility) PatchDevice(patch []byte, userId, deviceId string) CaliopenError {\n\tcurrent_device, e := rest.RetrieveDevice(userId, deviceId)\n\tif e != nil {\n\t\treturn e\n\t}\n\n\t// read into the patch to make basic controls before processing it with generic helper\n\tpatchReader, err := simplejson.NewJson(patch)\n\tif err != nil {\n\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[RESTfacility] PatchTag failed with simplejson error : %s\", err)\n\t}\n\t// check \"current_state\" property is present\n\tif _, hasCurrentState := patchReader.CheckGet(\"current_state\"); !hasCurrentState {\n\t\treturn NewCaliopenErr(ForbiddenCaliopenErr, \"[RESTfacility] PatchTag : current_state property must be in patch\")\n\t}\n\n\t// check device type consistency\n\tif deviceType, hasType := patchReader.CheckGet(\"type\"); hasType {\n\t\tif !IsValidDeviceType(deviceType.MustString()) {\n\t\t\treturn NewCaliopenErrf(UnprocessableCaliopenErr, \"[RESTfacility] PatchDevice : unknown type <%s> for device\", deviceType.MustString())\n\t\t}\n\t}\n\n\t// patch seams OK, apply it to the resource\n\tvar modifiedFields map[string]interface{}\n\tnewDevice, modifiedFields, err := helpers.UpdateWithPatch(patch, current_device, UserActor)\n\tif err != nil {\n\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[RESTfacility] PatchContact failed with UpdateContact error : %s\", err)\n\t}\n\n\t// save updated resource\n\te = rest.UpdateDevice(newDevice.(*Device), current_device, modifiedFields)\n\tif e != nil {\n\t\treturn e\n\t}\n\n\treturn nil\n}\n\nfunc (rest *RESTfacility) UpdateDevice(device, oldDevice *Device, modifiedFields map[string]interface{}) CaliopenError {\n\terr := rest.store.UpdateDevice(device, oldDevice, modifiedFields)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] UpdateDevice failed to update device\")\n\t}\n\treturn nil\n}\n\nfunc (rest *RESTfacility) DeleteDevice(userId, deviceId string) CaliopenError {\n\tdevice, e := rest.store.RetrieveDevice(userId, deviceId)\n\tif e != nil {\n\t\treturn WrapCaliopenErr(e, DbCaliopenErr, \"[RESTfacility] DeleteDevice failed to retrieve device\")\n\t}\n\n\te = rest.store.DeleteDevice(device)\n\tif e != nil {\n\t\treturn WrapCaliopenErr(e, DbCaliopenErr, \"[RESTfacility] DeleteDevice failed to delete device\")\n\t}\n\n\treturn nil\n}\n\n// RequestDeviceValidation sets a temporary validation token in cache\n// and sends it to user via Notifier facility on given channel\nfunc (rest *RESTfacility) RequestDeviceValidation(userId, deviceId, channel string, notifier Notifications.Notifiers) CaliopenError {\n\t// 1. check if resources exist\n\tuser, err := rest.store.RetrieveUser(userId)\n\tif err != nil || user == nil || !user.DateDelete.IsZero() {\n\t\treturn NewCaliopenErr(NotFoundCaliopenErr, \"user not found\")\n\t}\n\tdevice, err := rest.store.RetrieveDevice(userId, deviceId)\n\tif err != nil || device == nil {\n\t\treturn WrapCaliopenErr(err, NotFoundCaliopenErr, \"device not found\")\n\t}\n\n\t// 2. check if a validation request has already been ignited for these resources\n\tvalidationSession, err := rest.Cache.GetDeviceValidationSession(userId, deviceId)\n\tif err != nil && err != redis.Nil {\n\t\tlog.WithError(err).Errorf(\"[RequestDeviceValidation] failed to GetDeviceValidationSession for user %s, device %s\", userId, deviceId)\n\t\treturn WrapCaliopenErrf(err, DbCaliopenErr, \"failed to check  validation session in cache for user %s, device %s. Aborting\", userId, deviceId)\n\t}\n\tif validationSession != nil {\n\t\terr = rest.Cache.DeleteDeviceValidationSession(userId, deviceId)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Errorf(\"[RequestDeviceValidation] failed to delete previous validation session for user %s, device %s\", userId, deviceId)\n\t\t\treturn WrapCaliopenErrf(err, DbCaliopenErr, \"failed to delete previous validation session in cache for user %s, device %s. Aborting\", userId, deviceId)\n\t\t}\n\t\tlog.Infof(\"[RequestDeviceValidation] device validation session delete for user <%s> and device <%s>\", userId, deviceId)\n\t}\n\n\t// 3. generate a validation token and cache it\n\ttoken := shortuuid.New()\n\tvalidationSession, err = rest.Cache.SetDeviceValidationSession(userId, deviceId, token)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[RequestDeviceValidation] failed to store validation session in cache for user %s, device %s\", userId, deviceId)\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"failed to store validation session in cache\")\n\t}\n\n\t// 4. sends valilation token to user\n\tswitch channel {\n\tcase \"email\":\n\t\tnotif := &Notification{\n\t\t\tUser:            user,\n\t\t\tInternalPayload: validationSession,\n\t\t\tNotifId:         UUID(uuid.NewV1()),\n\t\t\tBody:            device.Name,\n\t\t\tType:            NotifDeviceValidation,\n\t\t}\n\t\tgo notifyByEmail(notifier, notif)\n\tdefault:\n\t\tlog.Warnf(\"[RequestDeviceValidation] unknown channel notification : %s\", channel)\n\t\t_ = rest.Cache.DeleteDeviceValidationSession(userId, deviceId)\n\t\treturn NewCaliopenErrf(FailDependencyCaliopenErr, \"[RequestDeviceValidation] unknown channel notification : aborting process\")\n\t}\n\n\treturn nil\n}\n\nfunc (rest *RESTfacility) ConfirmDeviceValidation(userId, token string) CaliopenError {\n\n\tsession, err := rest.Cache.GetTokenValidationSession(userId, token)\n\tif err != nil && err != redis.Nil {\n\t\treturn WrapCaliopenErrf(err, DbCaliopenErr, \"[ConfirmDeviceValidation] failed to get session for user %s, token %s\", userId, token)\n\t}\n\tif session == nil || err == redis.Nil {\n\t\treturn NewCaliopenErr(NotFoundCaliopenErr, \"not found\")\n\t}\n\n\t// update device's state\n\tcurrentDevice, err := rest.RetrieveDevice(userId, session.ResourceId)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[ConfirmDeviceValidation] failed to retrieve device %s\", session.ResourceId)\n\t\treturn WrapCaliopenErrf(err, NotFoundCaliopenErr, \"failed to retrieve device %s\", session.ResourceId)\n\t}\n\tvar newDevice Device\n\tnewDevice = *currentDevice\n\tnewDevice.Status = DeviceVerifiedStatus\n\terr = rest.UpdateDevice(&newDevice, currentDevice, map[string]interface{}{\"Status\": DeviceVerifiedStatus})\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[ConfirmDeviceValidation] failed to update device %s\", session.ResourceId)\n\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"failed to update device %s\", session.ResourceId)\n\t}\n\n\t// invalidate validation token\n\terr = rest.Cache.DeleteDeviceValidationSession(userId, session.ResourceId)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[ConfirmDeviceValidation] failed to delete session for user %s, device %s\", userId, session.ResourceId)\n\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"failed to delete session for user %s, device %s\", userId, session.ResourceId)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/devices_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/cache\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/renstrom/shortuuid\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc initRest() *RESTfacility {\n\trest := new(RESTfacility)\n\trest.Cache, _, _ = cache.InitializeTestCache()\n\trest.store = new(backendstest.APIStore)\n\treturn rest\n}\n\nfunc TestRESTfacility_RequestDeviceValidation(t *testing.T) {\n\trest := initRest()\n\tnotifier := &Notifications.Notifier{}\n\n\t// overring notifyByEmail func\n\t// because this test checks if the func is effectively called within RequestDeviceValidation, but nothing more\n\tnotifCalled := make(chan struct{})\n\tnotifyByEmail = func(notifier Notifications.Notifiers, notif *Notification) CaliopenError {\n\t\tclose(notifCalled)\n\t\treturn nil\n\t}\n\n\terr := rest.RequestDeviceValidation(backendstest.EmmaTommeUserId, \"b8c11acd-a90d-467f-90f7-21b6b615149d\", \"\", notifier)\n\tif err == nil {\n\t\tt.Errorf(\"expected requesting validation with empty notification channel to return an error, got nil\")\n\t} else if err.Error() != \"[RequestDeviceValidation] unknown channel notification : aborting process\" {\n\t\tt.Error(err)\n\t}\n\n\terr = rest.RequestDeviceValidation(backendstest.EmmaTommeUserId, \"b8c11acd-a90d-467f-90f7-21b6b615149d\", \"email\", notifier)\n\tselect {\n\tcase <-notifCalled:\n\tcase <-time.After(1 * time.Second):\n\t\tt.Error(\"timeout waiting for notifyByEmail to be called\")\n\t}\n\n\t// check that a validation session has been inserted into cache\n\tsession, Cerr := rest.Cache.GetDeviceValidationSession(backendstest.EmmaTommeUserId, \"b8c11acd-a90d-467f-90f7-21b6b615149d\")\n\tif Cerr != nil {\n\t\tt.Error(Cerr)\n\t}\n\tif session == nil {\n\t\tt.Error(\"expected to retrieve a validation session from cache, got nil\")\n\t}\n}\n\nfunc TestRESTfacility_ConfirmDeviceValidation(t *testing.T) {\n\t// create a validation session before testing confirmation process\n\trest, session := boostrapValidationSession(backendstest.EmmaTommeUserId, \"b8c11acd-a90d-467f-90f7-21b6b615149d\")\n\n\t// test calling with invalid token\n\terr := rest.ConfirmDeviceValidation(backendstest.EmmaTommeUserId, \"invalid_token\")\n\tif err == nil {\n\t\tt.Error(\"expected calling deviceValidation with invalid token to return DbCaliopenErr, got nil\")\n\t} else if err.Code() == DbCaliopenErr {\n\t\tt.Errorf(\"expected calling deviceValidation with invalid token to return DbCaliopenErr, got %d\", err.Code())\n\t}\n\n\t// test if device's status has been updated\n\terr = rest.ConfirmDeviceValidation(backendstest.EmmaTommeUserId, session.Token)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tupdatedDevice, err := rest.RetrieveDevice(backendstest.EmmaTommeUserId, \"b8c11acd-a90d-467f-90f7-21b6b615149d\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif updatedDevice.Status != DeviceVerifiedStatus {\n\t\tt.Errorf(\"expected a device with status = verified, got %s\", updatedDevice.Status)\n\t}\n\n}\n\nfunc boostrapValidationSession(userId, deviceId string) (*RESTfacility, *TokenSession) {\n\trest := initRest()\n\ttoken := shortuuid.New()\n\tvalidationSession, _ := rest.Cache.SetDeviceValidationSession(userId, deviceId, token)\n\treturn rest, validationSession\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/discussions.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/satori/go.uuid\"\n\t\"sort\"\n)\n\nfunc (rest *RESTfacility) GetDiscussionsList(user *UserInfo, ILrange, PIrange [2]int8, limit, offset int) ([]Discussion, int, error) {\n\t// Get bucket aggregation of messages by discussion_id, ie by uris_hash\n\tfilter := IndexSearch{\n\t\tOffset:   offset,\n\t\tShard_id: user.Shard_id,\n\t\tUser_id:  UUID(uuid.FromStringOrNil(user.User_id)),\n\t\tILrange:  ILrange,\n\t\tPIrange:  PIrange,\n\t}\n\tif limit < 1 {\n\t\tlimit = 20\n\t}\n\tfilter.Limit = limit\n\n\tURIsDiscussions, err := rest.index.GetDiscussionsList(filter, true)\n\tif err != nil {\n\t\treturn []Discussion{}, 0, err\n\t}\n\n\t// Get all current participants hashes for user\n\tlookups, err := rest.store.GetUserLookupHashes(UUID(uuid.FromStringOrNil(user.User_id)), \"uris\", \"\")\n\tif err != nil {\n\t\treturn []Discussion{}, 0, err\n\t}\n\t// build a map to resolve uris_hash to participants_hash\n\tlookupMap := map[string]string{} // => [uris_hash]participants_hash\n\tfor _, lookup := range lookups {\n\t\tlookupMap[lookup.Key] = lookup.Value\n\t}\n\t// aggregate discussions by participants\n\tparticipantsMap := map[string][]Discussion{} // => [participants_hash][]Discussion\n\tfor _, discussion := range URIsDiscussions {\n\t\tparticipants_hash := lookupMap[discussion.DiscussionId]\n\t\tif _, ok := participantsMap[participants_hash]; !ok {\n\t\t\tparticipantsMap[participants_hash] = []Discussion{}\n\t\t}\n\t\tparticipantsMap[participants_hash] = append(participantsMap[participants_hash], discussion)\n\t}\n\n\t// get last message for each discussion\n\tdiscussionsList := []Discussion{}\n\tvar discussionsCount int\n\tfor _, discussions := range participantsMap {\n\t\tdiscussionsList = append(discussionsList, mergeDiscussionAliases(discussions))\n\t}\n\tdiscussionsCount = len(discussionsList)\n\n\t// sort discussions by date_sort\n\tsort.Sort(ByLastMessageDateDesc(discussionsList))\n\n\tif len(discussionsList) == 0 {\n\t\treturn discussionsList, discussionsCount, err\n\t}\n\n\t// apply offset and limit\n\tif offset > len(discussionsList) {\n\t\treturn nil, 0, errors.New(\"offset is greater than result set\")\n\t}\n\tif offset+limit > len(discussionsList) {\n\t\tdiscussionsList = discussionsList[offset:]\n\t} else {\n\t\tdiscussionsList = discussionsList[offset : offset+limit]\n\t}\n\n\t// update participants' data\n\tDiscussionsParticipantsDetails(rest.store.GetSession(), discussionsList)\n\n\treturn discussionsList, discussionsCount, nil\n}\n\n// DiscussionMetadata returns one Discussion hydrated with metadata from its messages\n// and from messages linked to this discussion_id\nfunc (rest *RESTfacility) DiscussionMetadata(user *UserInfo, discussionId string) (discussion Discussion, err error) {\n\tuserId := UUID(uuid.FromStringOrNil(user.User_id))\n\t// create a slice of related discussionId\n\tdiscussionsIds, err := rest.ExpandDiscussionSet(userId, discussionId)\n\tif err != nil {\n\t\treturn\n\t}\n\t// retrieve discussions' metadata from index\n\tdiscussions, err := rest.index.GetDiscussionsList(IndexSearch{\n\t\tShard_id: user.Shard_id,\n\t\tUser_id:  userId,\n\t\tTerms:    map[string][]string{\"discussion_id\": discussionsIds},\n\t}, false)\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(discussions) == 0 {\n\t\terr = errors.New(\"not found\")\n\t\treturn\n\t}\n\tdiscussion = mergeDiscussionAliases(discussions)\n\n\t// update participants' data\n\tDiscussionsParticipantsDetails(rest.store.GetSession(), []Discussion{discussion})\n\n\treturn\n}\n\n// ExpandDiscussionSet returns a slice of discussions ids currently connected to the discussionId in params\nfunc (rest *RESTfacility) ExpandDiscussionSet(userId UUID, discussionId string) (discussionsIds []string, err error) {\n\t// Get current participants hash for this discussionId\n\tparticipants_hash, err := rest.store.GetUserLookupHashes(userId, \"uris\", discussionId)\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(participants_hash) > 1 {\n\t\tlog.Warnf(\"[DiscussionMetadata] found more than one participants_hash for user %s discussion %s. possible inconsistency\", userId.String(), discussionId)\n\t}\n\tif len(participants_hash) == 0 {\n\t\terr = errors.New(\"not found\")\n\t\treturn\n\t}\n\t// Get all discussion_id related to this participants hash\n\trelated_hashes, err := rest.store.GetUserLookupHashes(userId, \"participants\", participants_hash[0].Value)\n\tif err != nil {\n\t\treturn\n\t}\n\tdiscussionsIds = []string{}\n\tfor _, related := range related_hashes {\n\t\tdiscussionsIds = append(discussionsIds, related.Value)\n\t}\n\treturn\n}\n\n// mergeDiscussionAliases return one Discussion with aggregated metadata from a discussions set\nfunc mergeDiscussionAliases(discussions []Discussion) Discussion {\n\tearlier := Discussion{}\n\tvar msgCount int32\n\tvar unreadCount int32\n\taliases := []string{}\n\tfor _, disc := range discussions {\n\t\taliases = append(aliases, disc.DiscussionId)\n\t\tif disc.LastMessageDate.After(earlier.LastMessageDate) {\n\t\t\tearlier = disc\n\t\t}\n\t\tmsgCount += disc.TotalCount\n\t\tunreadCount += disc.UnreadCount\n\t}\n\tearlier.TotalCount = msgCount\n\tearlier.UnreadCount = unreadCount\n\tif len(aliases) > 1 {\n\t\tfor i, alias := range aliases {\n\t\t\tif alias == earlier.DiscussionId {\n\t\t\t\taliases = append(aliases[:i], aliases[i+1:]...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tearlier.Aliases = aliases\n\t}\n\n\treturn earlier\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/draft.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/messages\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"time\"\n)\n\nfunc (rest *RESTfacility) SendDraft(user_info *UserInfo, msg_id string) (msg *Message, err error) {\n\tconst nats_order = \"deliver\"\n\tvar order BrokerOrder\n\tdraft, draftErr := rest.store.RetrieveMessage(user_info.User_id, msg_id)\n\tif draftErr != nil {\n\t\tlog.WithError(draftErr).Info(\"[SendDraft] failed to retrieve draft from store\")\n\t\treturn nil, errors.New(\"draft not found\")\n\t}\n\t// resolve sender's address protocol for selecting natsTopics accordingly\n\tprotocol, resolvErr := rest.ResolveSenderProtocol(draft)\n\tif resolvErr != nil {\n\t\tlog.WithError(resolvErr).Info(\"[SendDraft] failed to resolve sender's protocol\")\n\t\treturn nil, errors.New(\"unknown protocol for sender\")\n\t}\n\t// Ensure participants and hash lookups are filled accordingly (they should)\n\terr = rest.store.UpsertDiscussionLookups(draft.User_id, draft.Participants)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[SendDraft] failed to create discussion's lookups\")\n\t\treturn nil, err\n\t}\n\n\tvar natsTopic string\n\tswitch protocol {\n\tcase EmailProtocol, ImapProtocol:\n\t\tnatsTopic = Nats_outIMAP_topicKey\n\t\torder = BrokerOrder{\n\t\t\tOrder:      nats_order,\n\t\t\tMessageId:  msg_id,\n\t\t\tUserId:     user_info.User_id,\n\t\t\tIdentityId: draft.UserIdentities[0].String(), // handle one identity only for now\n\t\t}\n\tcase SmtpProtocol:\n\t\tnatsTopic = Nats_outSMTP_topicKey\n\t\torder = BrokerOrder{\n\t\t\tOrder:      nats_order,\n\t\t\tMessageId:  msg_id,\n\t\t\tUserId:     user_info.User_id,\n\t\t\tIdentityId: draft.UserIdentities[0].String(), // handle one identity only for now\n\t\t}\n\tcase TwitterProtocol:\n\t\tnatsTopic = Nats_outTwitter_topicKey\n\t\torder = BrokerOrder{\n\t\t\tOrder:      nats_order,\n\t\t\tMessageId:  msg_id,\n\t\t\tUserId:     user_info.User_id,\n\t\t\tIdentityId: draft.UserIdentities[0].String(), // handle one identity for now\n\t\t}\n\tcase MastodonProtocol:\n\t\tnatsTopic = Nats_outMastodon_topicKey\n\t\torder = BrokerOrder{\n\t\t\tOrder:      nats_order,\n\t\t\tMessageId:  msg_id,\n\t\t\tUserId:     user_info.User_id,\n\t\t\tIdentityId: draft.UserIdentities[0].String(), // handle one identity for now\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"[SendDraft] no handler for <%s> protocol\", protocol)\n\t}\n\tnatsMessage, e := json.Marshal(order)\n\tif e != nil {\n\t\tlog.WithError(e).Info(\"[SendDraft] failed to build nats message\")\n\t\treturn nil, errors.New(\"[SendDraft] failed to build nats message\")\n\t}\n\trep, err := rest.nats_conn.Request(rest.natsTopics[natsTopic], natsMessage, 30*time.Second)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[RESTfacility]: SendDraft error (1)\")\n\t\tif rest.nats_conn.LastError() != nil {\n\t\t\tlog.WithError(rest.nats_conn.LastError()).Warn(\"[RESTfacility]: SendDraft error\")\n\t\t\treturn nil, err\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tvar reply DeliveryAck\n\terr = json.Unmarshal(rep.Data, &reply)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[RESTfacility]: SendDraft error (2)\")\n\t\treturn nil, err\n\t}\n\tif reply.Err {\n\t\tlog.Warn(\"[RESTfacility]: SendDraft error (3)\")\n\t\treturn nil, errors.New(reply.Response)\n\t}\n\tmsg, err = rest.store.RetrieveMessage(user_info.User_id, msg_id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmessages.SanitizeMessageBodies(msg)\n\t(*msg).Body_excerpt = messages.ExcerptMessage(*msg, 200, true, true)\n\treturn msg, err\n}\n\n// ResolveSenderProtocol returns outbound protocol to use for sending draft by resolving draft's sender identity\nfunc (rest *RESTfacility) ResolveSenderProtocol(draft *Message) (string, error) {\n\tfirstIdentity, err := rest.RetrieveUserIdentity(draft.User_id.String(), draft.UserIdentities[0].String(), false) // handle one identity only for now\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn firstIdentity.Protocol, nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/identities.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/pi\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/bitly/go-simplejson\"\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc (rest *RESTfacility) RetrieveLocalIdentities(user_id string) ([]UserIdentity, error) {\n\treturn rest.store.RetrieveLocalsIdentities(user_id)\n}\n\n// get contact from db\n// aggregate contact's identities\n// then update PI for each identity\nfunc (rest *RESTfacility) RetrieveContactIdentities(user_id, contact_id string) (identities []ContactIdentity, err error) {\n\t_, e := uuid.FromString(contact_id)\n\tif user_id != \"\" && contact_id != \"\" && e == nil {\n\t\tcontact, err := rest.store.RetrieveContact(user_id, contact_id)\n\t\tif err != nil || contact == nil {\n\t\t\terr = errors.New(\"[RESTfacility.ContactIdentities] error when retrieving contact : \" + err.Error())\n\t\t\treturn []ContactIdentity{}, err\n\t\t}\n\n\t\tfor _, email := range contact.Emails {\n\t\t\tidentities = append(identities, ContactIdentity{\n\t\t\t\tIdentifier:   email.Address,\n\t\t\t\tLabel:        email.Label,\n\t\t\t\tPrivacyIndex: PrivacyIndex{},\n\t\t\t\tProtocol:     EmailProtocol,\n\t\t\t})\n\t\t}\n\n\t\tfor _, identity := range contact.Identities {\n\t\t\tidentities = append(identities, ContactIdentity{\n\t\t\t\tIdentifier:   identity.Name,\n\t\t\t\tLabel:        identity.Name,\n\t\t\t\tPrivacyIndex: PrivacyIndex{},\n\t\t\t\tProtocol:     identity.Type,\n\t\t\t})\n\n\t\t}\n\n\t\tfor _, im := range contact.Ims {\n\t\t\tidentities = append(identities, ContactIdentity{\n\t\t\t\tIdentifier:   im.Address,\n\t\t\t\tLabel:        im.Label,\n\t\t\t\tPrivacyIndex: PrivacyIndex{},\n\t\t\t\tProtocol:     im.Protocol,\n\t\t\t})\n\t\t}\n\n\t\tfor _, phone := range contact.Phones {\n\t\t\tidentities = append(identities, ContactIdentity{\n\t\t\t\tIdentifier:   phone.Number,\n\t\t\t\tLabel:        phone.Number,\n\t\t\t\tPrivacyIndex: PrivacyIndex{},\n\t\t\t\tProtocol:     SmsProtocol,\n\t\t\t})\n\t\t}\n\n\t\tfor i := range identities {\n\t\t\t// (for now, this func is a monkey)\n\t\t\tpi.UpdatePIContactIdentity(context.TODO(), contact, &identities[i]) // TODO: do not use context pkg, it is not for this king of context\n\t\t}\n\t} else {\n\t\terr = errors.New(\"[RESTfacility.ContactIdentities] unprocessable parameters\")\n\t\treturn\n\t}\n\treturn\n}\n\nfunc (rest *RESTfacility) RetrieveRemoteIdentities(userId string, withCredentials bool) (ids []*UserIdentity, err CaliopenError) {\n\tvar e error\n\tids, e = rest.store.RetrieveRemoteIdentities(userId, withCredentials)\n\tif e != nil {\n\t\tif e.Error() == \"not found\" {\n\t\t\terr = WrapCaliopenErr(e, NotFoundCaliopenErr, \"store did not found remote ids\")\n\t\t} else {\n\t\t\terr = WrapCaliopenErr(e, DbCaliopenErr, \"store failed to retrieve remote ids\")\n\t\t}\n\t}\n\treturn\n}\n\n// CreateUserIdentity wraps the following actions :\n// - checks UserIdentity correctness\n// - adds UserIdentity in cassandra's tables\n// - adds identity to user's contact entry\n// - emits nats message toward identity poller\nfunc (rest *RESTfacility) CreateUserIdentity(identity *UserIdentity) CaliopenError {\n\t// check if mandatory properties are ok\n\tif len(identity.UserId.Bytes()) == 0 || (bytes.Equal(identity.UserId.Bytes(), EmptyUUID.Bytes())) {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[CreateUserIdentity] empty user id\")\n\t}\n\tif identity.Type == \"\" || identity.Protocol == \"\" || identity.Identifier == \"\" {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[CreateUserIdentity] miss mandatory property\")\n\t}\n\n\tif len((*identity).Id.Bytes()) == 0 || (bytes.Equal((*identity).Id.Bytes(), EmptyUUID.Bytes())) {\n\t\t_ = (*identity).Id.UnmarshalBinary(uuid.NewV4().Bytes())\n\t}\n\t// set defaults\n\tidentity.SetDefaults()\n\n\t// ensure identifier+protocol+user_id uniqueness\n\trows, e := rest.store.LookupIdentityByIdentifier(identity.Identifier, identity.Protocol, identity.UserId.String())\n\tif e != nil || len(rows) > 0 {\n\t\treturn NewCaliopenErrf(ForbiddenCaliopenErr, \"[CreateUserIdentity] tuple(%s, %s, %s) breaks uniqueness constraint\", identity.Identifier, identity.Protocol, identity.UserId.String())\n\t}\n\n\terr := rest.store.CreateUserIdentity(identity)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[CreateUserIdentity] CreateUserIdentity failed to create identity in store\")\n\t}\n\n\t// emit nats message to idpoller to start polling asap\n\tif identity.Type == RemoteIdentity {\n\t\torder := RemoteIDNatsMessage{\n\t\t\tIdentityId: identity.Id.String(),\n\t\t\tOrder:      \"add\",\n\t\t\tOrderParam: identity.Infos[\"pollinterval\"],\n\t\t\tProtocol:   identity.Protocol,\n\t\t\tUserId:     identity.UserId.String(),\n\t\t}\n\t\tjorder, jerr := json.Marshal(order)\n\t\tif jerr == nil {\n\t\t\te := rest.nats_conn.Publish(rest.natsTopics[Nats_IdPoller_topicKey], jorder)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[CreateUserIdentity] failed to publish 'add' order to idpoller\")\n\t\t\t}\n\t\t}\n\t}\n\n\t// adds identity to user's contact entry\n\tcontact, e := rest.RetrieveUserContact(identity.UserId.String())\n\tif e != nil {\n\t\tlog.WithError(e).Warnf(\"[CreateUserIdentity] failed to retrieve user's contact. Can't add identity to contact.\")\n\t\treturn NewCaliopenErr(NotFoundCaliopenErr, \"[CreateUserIdentity] failed to retrieve user's contact. Can't add identity to contact.\")\n\t} else {\n\t\t_, err = addIdentityToContact(rest.store, rest.index, rest.store, *identity, contact)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[CreateUserIdentity] failed to add identity <%s> to user <%s>'s contact <%s>\", identity.Id, identity.UserId, contact.ContactId)\n\t\t\treturn NewCaliopenErrf(FailDependencyCaliopenErr, \"[CreateUserIdentity] failed to add identity <%s> to user <%s>'s contact <%s>\", identity.Id, identity.UserId, contact.ContactId)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (rest *RESTfacility) RetrieveUserIdentity(userId, identityId string, withCredentials bool) (id *UserIdentity, err CaliopenError) {\n\tvar e error\n\tid, e = rest.store.RetrieveUserIdentity(userId, identityId, withCredentials)\n\tif e != nil {\n\t\tif e.Error() == \"not found\" {\n\t\t\terr = WrapCaliopenErr(e, NotFoundCaliopenErr, \"remote identity not found\")\n\t\t} else {\n\t\t\terr = WrapCaliopenErr(e, DbCaliopenErr, \"store failed to retrieve remote identity\")\n\t\t}\n\t}\n\treturn\n}\n\nfunc (rest *RESTfacility) UpdateUserIdentity(identity, oldIdentity *UserIdentity, update map[string]interface{}) CaliopenError {\n\terr := rest.store.UpdateUserIdentity(identity, update)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] UpdateUserIdentity fails to call store\")\n\t}\n\treturn nil\n}\n\nfunc (rest *RESTfacility) PatchUserIdentity(patch []byte, userId, identityId string) CaliopenError {\n\tcurrentRemoteID, err1 := rest.RetrieveUserIdentity(userId, identityId, false)\n\tif err1 != nil {\n\t\tif err1.Error() == \"not found\" {\n\t\t\treturn WrapCaliopenErr(err1, NotFoundCaliopenErr, \"remote identity not found\")\n\t\t} else {\n\t\t\treturn WrapCaliopenErr(err1, DbCaliopenErr, \"store failed to retrieve remote identity\")\n\t\t}\n\t}\n\t// read into the patch to make basic controls before processing it with generic helper\n\tpatchReader, err2 := simplejson.NewJson(patch)\n\tif err2 != nil {\n\t\treturn WrapCaliopenErrf(err2, FailDependencyCaliopenErr, \"[RESTfacility] PatchUserIdentity failed with simplejson error : %s\", err2)\n\t}\n\t// checks \"current_state\" property is present\n\tif _, hasCurrentState := patchReader.CheckGet(\"current_state\"); !hasCurrentState {\n\t\treturn NewCaliopenErr(ForbiddenCaliopenErr, \"[RESTfacility] PatchUserIdentity : current_state property must be in patch\")\n\t}\n\n\t// special case : updating credentials. Credentials' current state should not be provided by caller.\n\t// we need to get current credentials from db and put them in \"current_state\" before applying generic UpdateWithPatch()\n\tif _, hasCredentials := patchReader.CheckGet(\"credentials\"); hasCredentials {\n\t\tpatchReader.SetPath([]string{\"current_state\", \"credentials\"}, currentRemoteID.Credentials)\n\t\tpatch, _ = patchReader.MarshalJSON()\n\t}\n\n\t// patch seams OK, apply it to the resource\n\tnewRemoteID, modifiedFields, err3 := helpers.UpdateWithPatch(patch, currentRemoteID, UserActor)\n\tif err3 != nil {\n\t\treturn WrapCaliopenErrf(err3, FailDependencyCaliopenErr, \"[RESTfacility] PatchUserIdentity : call to generic UpdateWithPatch failed : %s\", err3)\n\t}\n\n\t// save updated resource\n\terr4 := rest.UpdateUserIdentity(newRemoteID.(*UserIdentity), currentRemoteID, modifiedFields)\n\tif err4 != nil {\n\t\treturn WrapCaliopenErrf(err4, FailDependencyCaliopenErr, \"[RESTfacility] PatchUserIdentity failed with UpdateUserIdentity error : %s\", err4)\n\t}\n\n\t//TODO: emit nats message to IDpoller if it's a remote identity\n\n\treturn nil\n}\n\nfunc (rest *RESTfacility) DeleteUserIdentity(userId, identityId string) CaliopenError {\n\tuserIdentity, err1 := rest.RetrieveUserIdentity(userId, identityId, false)\n\tif err1 != nil {\n\t\tif err1.Error() == \"not found\" {\n\t\t\treturn WrapCaliopenErr(err1, NotFoundCaliopenErr, \"remote identity not found\")\n\t\t} else {\n\t\t\treturn WrapCaliopenErr(err1, DbCaliopenErr, \"store failed to retrieve remote identity\")\n\t\t}\n\t}\n\n\terr2 := rest.store.DeleteUserIdentity(userIdentity)\n\tif err2 != nil {\n\t\treturn WrapCaliopenErrf(err2, DbCaliopenErr, \"[RESTfacility DeleteUserIdentity failed to delete in store\")\n\t}\n\n\t// send nats message to idpoller to stop polling\n\tif userIdentity.Type == RemoteIdentity {\n\t\torder := RemoteIDNatsMessage{\n\t\t\tIdentityId: userIdentity.Id.String(),\n\t\t\tOrder:      \"delete\",\n\t\t\tProtocol:   userIdentity.Protocol,\n\t\t\tUserId:     userIdentity.UserId.String(),\n\t\t}\n\t\tjorder, jerr := json.Marshal(order)\n\t\tif jerr == nil {\n\t\t\te := rest.nats_conn.Publish(rest.natsTopics[Nats_IdPoller_topicKey], jorder)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warn(\"[saveErrorState] failed to publish delete order to idpoller\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (rest *RESTfacility) IsRemoteIdentity(userId, identityId string) bool {\n\treturn rest.store.IsRemoteIdentity(userId, identityId)\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/keys.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage REST\n\nimport (\n\t\"bytes\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/bitly/go-simplejson\"\n\t\"github.com/gin-gonic/gin/json\"\n\t\"github.com/keybase/go-crypto/openpgp\"\n)\n\n// CreatePGPPubKey create and store a PublicKey object for given contact\n// it takes either PEM or DER encoded GPG public key, extracting as much possible data into struct's properties\nfunc (rest *RESTfacility) CreatePGPPubKey(label string, pubkey []byte, contact *Contact) (*PublicKey, CaliopenError) {\n\treader := bytes.NewReader(pubkey)\n\tvar entitiesList openpgp.EntityList\n\tvar err error\n\n\tentitiesList, err = openpgp.ReadArmoredKeyRing(reader)\n\tif err != nil {\n\t\t// pubkey should be DER encoded\n\t\treader.Reset(pubkey)\n\t\tentitiesList, err = openpgp.ReadKeyRing(reader)\n\t}\n\n\tif err != nil {\n\t\treturn nil, NewCaliopenErr(FailDependencyCaliopenErr, err.Error())\n\t}\n\n\t//handle only first key found for now\n\tif len(entitiesList) > 1 {\n\t\treturn nil, NewCaliopenErr(FailDependencyCaliopenErr, \"more than one key found in payload\")\n\t}\n\n\tpubKey := new(PublicKey)\n\terr = pubKey.UnmarshalPGPEntity(label, entitiesList[0], contact)\n\tif err != nil {\n\t\treturn nil, NewCaliopenErr(FailDependencyCaliopenErr, err.Error())\n\t}\n\n\terr = rest.store.CreatePGPPubKey(pubKey)\n\tif err != nil {\n\t\treturn nil, WrapCaliopenErr(err, DbCaliopenErr, \"[CreatePGPPubKey] store failed to create PGP key\")\n\t}\n\n\tnatsMsg := PublishKeyMessage{\n\t\tOrder:      \"publish_key\",\n\t\tUserId:     pubKey.UserId.String(),\n\t\tResourceId: pubKey.ResourceId.String(),\n\t\tKeyId:      pubKey.KeyId.String(),\n\t}\n\tjsonMsg, err := json.Marshal(natsMsg)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[RESTfacility]CreatePGPPubKey failed to marshal nats message\")\n\t} else {\n\t\te := rest.nats_conn.Publish(rest.natsTopics[Nats_Keys_topicKey], jsonMsg)\n\t\tif e != nil {\n\t\t\tlog.WithError(err).Warn(\"[RESTfacility]CreatePGPPubKey failed to publish nats message\")\n\t\t}\n\t}\n\n\treturn pubKey, nil\n}\n\nfunc (rest *RESTfacility) RetrieveContactPubKeys(userId, contactId string) (pubkeys PublicKeys, err CaliopenError) {\n\treturn rest.store.RetrieveContactPubKeys(userId, contactId)\n}\n\nfunc (rest *RESTfacility) RetrievePubKey(userId, resourceId, keyId string) (pubkey *PublicKey, err CaliopenError) {\n\treturn rest.store.RetrievePubKey(userId, resourceId, keyId)\n}\n\n// PatchPubKey is a shortcut for REST api to :\n// - retrieve pubkey from db\n// - UpdateWithPatch() with UserActor role\n// - then UpdatePubKey() to save updated key in store\nfunc (rest *RESTfacility) PatchPubKey(patch []byte, userId, resourceId, keyId string) CaliopenError {\n\n\tcurrent_pubkey, err := rest.RetrievePubKey(userId, resourceId, keyId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// read into the patch to make basic controls before processing it with generic helper\n\tpatchReader, e := simplejson.NewJson(patch)\n\tif e != nil {\n\t\treturn NewCaliopenErrf(FailDependencyCaliopenErr, \"[RESTfacility]PatchPubKey failed with simplejson error : %s\", e)\n\t}\n\t// checks \"current_state\" property is present\n\tif _, hasCurrentState := patchReader.CheckGet(\"current_state\"); !hasCurrentState {\n\t\treturn NewCaliopenErr(ForbiddenCaliopenErr, \"[RESTfacility]PatchPubKey : current_state property must be in patch\")\n\t}\n\t// patch seams OK, apply it to the resource\n\tvar modifiedFields map[string]interface{}\n\tnewPubKey, modifiedFields, e := helpers.UpdateWithPatch(patch, current_pubkey, UserActor)\n\tif e != nil {\n\t\treturn NewCaliopenErrf(FailDependencyCaliopenErr, \"[RESTfacility]PatchPubKey failed to call UpdateWithPatch : %s\", e)\n\t}\n\n\t// save updated resource\n\treturn rest.UpdatePubKey(newPubKey.(*PublicKey), current_pubkey, modifiedFields)\n}\n\nfunc (rest *RESTfacility) UpdatePubKey(newPubKey, oldPubKey *PublicKey, modifiedFields map[string]interface{}) CaliopenError {\n\treturn rest.store.UpdatePubKey(newPubKey, oldPubKey, modifiedFields)\n}\n\nfunc (rest *RESTfacility) DeletePubKey(pubKey *PublicKey) CaliopenError {\n\terr := rest.store.DeletePubKey(pubKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\tnatsMsg := PublishKeyMessage{\n\t\tOrder:      \"delete_key\",\n\t\tUserId:     pubKey.UserId.String(),\n\t\tResourceId: pubKey.ResourceId.String(),\n\t\tKeyId:      pubKey.KeyId.String(),\n\t}\n\tjsonMsg, e := json.Marshal(natsMsg)\n\tif e != nil {\n\t\tlog.WithError(e).Warn(\"[RESTfacility]CreatePGPPubKey failed to marshal nats message\")\n\t} else {\n\t\te := rest.nats_conn.Publish(rest.natsTopics[Nats_Keys_topicKey], jsonMsg)\n\t\tif e != nil {\n\t\t\tlog.WithError(err).Warn(\"[RESTfacility]CreatePGPPubKey failed to publish nats message\")\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/message.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tm \"github.com/CaliOpen/Caliopen/src/backend/main/go.main/messages\"\n)\n\nfunc (rest *RESTfacility) SetMessageUnread(user *UserInfo, message_id string, status bool) (err error) {\n\n\terr = rest.store.SetMessageUnread(user.User_id, message_id, status)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = rest.index.SetMessageUnread(user, message_id, status)\n\treturn err\n}\n\nfunc (rest *RESTfacility) GetRawMessage(raw_message_id string) (raw_message []byte, err error) {\n\traw_msg, err := rest.store.GetRawMessage(raw_message_id)\n\tif err != nil {\n\t\treturn\n\t}\n\treturn []byte(raw_msg.Raw_data), nil\n}\n\n//return a list of messages given filter parameters\n//messages are sanitized, ie : ready for display in front interface, and an excerpt of body is generated\nfunc (rest *RESTfacility) GetMessagesList(filter IndexSearch) (messages []*Message, totalFound int64, err error) {\n\t// if discussion_id in filter, expand to all related discussion_ids before querying index\n\tif discussionId, ok := filter.Terms[\"discussion_id\"]; ok {\n\t\tdiscussionsIds, e := rest.ExpandDiscussionSet(filter.User_id, discussionId[0])\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tfilter.Terms[\"discussion_id\"] = discussionsIds\n\t}\n\tmessages, totalFound, err = rest.index.FilterMessages(filter)\n\tif err != nil {\n\t\treturn []*Message{}, 0, err\n\t}\n\tfor _, msg := range messages {\n\t\tm.SanitizeMessageBodies(msg)\n\t\t(*msg).Body_excerpt = m.ExcerptMessage(*msg, 200, true, true)\n\t}\n\n\t// embed up-to-date participants's details\n\tmsgs := []Message{}\n\tfor _, msg := range messages {\n\t\tmsgs = append(msgs, *msg)\n\t}\n\tMessagesParticipantsDetails(rest.store.GetSession(), msgs)\n\n\treturn\n}\n\n//return a list of messages 'around' a message within a discussion\n//messages are sanitized, ie : ready for display in front interface, and an excerpt of body is generated\nfunc (rest *RESTfacility) GetMessagesRange(filter IndexSearch) (messages []*Message, totalFound int64, err error) {\n\t// if discussion_id in filter, expand to all related discussion_ids before querying index\n\tif discussionId, ok := filter.Terms[\"discussion_id\"]; ok {\n\t\tdiscussionsIds, e := rest.ExpandDiscussionSet(filter.User_id, discussionId[0])\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\t\tfilter.Terms[\"discussion_id\"] = discussionsIds\n\t}\n\tmessages, totalFound, err = rest.index.GetMessagesRange(filter)\n\tif err != nil {\n\t\treturn []*Message{}, 0, err\n\t}\n\tfor _, msg := range messages {\n\t\tm.SanitizeMessageBodies(msg)\n\t\t(*msg).Body_excerpt = m.ExcerptMessage(*msg, 200, true, true)\n\t}\n\n\t// embed up-to-date participants's details\n\tmsgs := []Message{}\n\tfor _, msg := range messages {\n\t\tmsgs = append(msgs, *msg)\n\t}\n\tMessagesParticipantsDetails(rest.store.GetSession(), msgs)\n\n\treturn\n}\n\n//return a sanitized message, ready for display in front interface\nfunc (rest *RESTfacility) GetMessage(user *UserInfo, msg_id string) (msg *Message, err error) {\n\tmsg, err = rest.store.RetrieveMessage(user.User_id, msg_id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm.SanitizeMessageBodies(msg)\n\t(*msg).Body_excerpt = m.ExcerptMessage(*msg, 200, true, true)\n\treturn msg, err\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/nats.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage REST\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n)\n\n// PublishOnNats publishes a simple message on a topic\nfunc (rest *RESTfacility) PublishOnNats(message, topic string) error {\n\terr := rest.nats_conn.Publish(topic, []byte(message))\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[RESTfacility]: PublishOnNats failed\")\n\t\tif rest.nats_conn.LastError() != nil {\n\t\t\tlog.WithError(rest.nats_conn.LastError()).Warn(\"[RESTfacility]: PublishOnNats failed\")\n\t\t\treturn err\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/providers.go",
    "content": "package REST\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/users\"\n\t\"github.com/CaliOpen/go-twitter/twitter\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/dghubble/oauth1\"\n\ttwitterOAuth1 \"github.com/dghubble/oauth1/twitter\"\n\t\"github.com/mattn/go-mastodon\"\n\t\"github.com/satori/go.uuid\"\n\t\"golang.org/x/oauth2\"\n\tgoogleApi \"google.golang.org/api/oauth2/v2\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc (rest *RESTfacility) RetrieveProvidersList() (providers []Provider, err error) {\n\tif rest.providers != nil {\n\t\tproviders = []Provider{}\n\t\tfor _, provider := range rest.providers {\n\t\t\tproviders = append(providers, provider)\n\t\t}\n\t\treturn providers, nil\n\t}\n\treturn providers, errors.New(\"providers slice is nil\")\n}\n\nfunc (rest *RESTfacility) RetrieveRegisteredMastodon() (instances []Provider, err error) {\n\n\treturn nil, errors.New(\"not implemented\")\n}\n\n// GetProviderOauthFor returns provider's params required for authenticated user to initiate an Oauth request\n// In case of Twitter, auth request url is fetched from twitter API endpoint on the fly.\n// For all requests, an Oauth session cache is initialized for requesting user, making use of cache facility.\nfunc (rest *RESTfacility) GetProviderOauthFor(userId, name, identifier string) (provider Provider, err CaliopenError) {\n\tprovider, found := rest.providers[name]\n\tif found {\n\t\tswitch provider.Name {\n\t\tcase \"twitter\":\n\t\t\trequestToken, requestSecret, e := setTwitterAuthRequestUrl(&provider, rest.Hostname)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Errorf(\"[GetProviderOauthFor] failed to set twitter auth request for user %s, provider %s\", userId, name)\n\t\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[GetProviderOauthFor] failed to set twitter auth request\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcacheErr := rest.Cache.SetOauthSession(requestToken, &OauthSession{\n\t\t\t\tRequestSecret: requestSecret,\n\t\t\t\tUserId:        userId,\n\t\t\t})\n\t\t\tif cacheErr != nil {\n\t\t\t\tlog.WithError(cacheErr).Errorf(\"[GetProviderOauthFor] failed to set Oauth session in cache for user %s, provider %s\", userId, name)\n\t\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[GetProviderOauthFor] failed to set twitter Oauth session in cache\")\n\t\t\t\treturn\n\t\t\t}\n\t\tcase \"gmail\":\n\t\t\tstate, e := users.SetGoogleAuthRequestUrl(&provider, rest.Hostname)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Errorf(\"[GetProviderOauthFor] failed to set gmail auth request for user %s, provider %s\", userId, name)\n\t\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[GetProviderOauthFor] failed to set gmail auth request\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcacheErr := rest.Cache.SetOauthSession(state, &OauthSession{\n\t\t\t\tUserId: userId,\n\t\t\t})\n\t\t\tif cacheErr != nil {\n\t\t\t\tlog.WithError(cacheErr).Errorf(\"[GetProviderOauthFor] failed to set Oauth session in cache for user %s, provider %s\", userId, name)\n\t\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[GetProviderOauthFor] failed to set gmail Oauth session in cache\")\n\t\t\t\treturn\n\t\t\t}\n\t\tcase \"mastodon\":\n\t\t\t// identifier should be in the format `@username@server.tld` or `username@server.tld`\n\t\t\tidentifier = strings.TrimPrefix(identifier, \"@\")\n\t\t\taccount := strings.Split(identifier, \"@\")\n\t\t\tif len(account) != 2 || account[0] == \"\" || account[1] == \"\" {\n\t\t\t\te := fmt.Errorf(\"[GetProviderOauthFor] missing or malformed mastodon identifier : <%s>\", identifier)\n\t\t\t\tlog.WithError(e)\n\t\t\t\terr = WrapCaliopenErrf(e, UnprocessableCaliopenErr, \"[GetProviderOauthFor] failed to set mastodon auth request\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tu, e := url.Parse(account[1])\n\t\t\tif e != nil || (u.Host == \"\" && u.Path == \"\") {\n\t\t\t\tlog.WithError(e)\n\t\t\t\terr = WrapCaliopenErrf(e, UnprocessableCaliopenErr, \"[GetProviderOauthFor] malformed mastodon instance : <%s>\", account[1])\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvar instance string\n\t\t\tif u.Host != \"\" {\n\t\t\t\tinstance = u.Host\n\t\t\t} else {\n\t\t\t\tinstance = u.Path\n\t\t\t}\n\n\t\t\t// check if caliopen is already registered on instance\n\t\t\tmastoInstance, e := rest.store.RetrieveProvider(\"mastodon\", instance)\n\t\t\tif e != nil || mastoInstance == nil {\n\t\t\t\t// else, register this caliopen app to mastodon instance\n\t\t\t\tapp, e := mastodon.RegisterApp(context.Background(), &mastodon.AppConfig{\n\t\t\t\t\tServer:       \"https://\" + instance,\n\t\t\t\t\tClientName:   \"Caliopen@\" + rest.Hostname,\n\t\t\t\t\tScopes:       \"read write follow\",\n\t\t\t\t\tWebsite:      \"https://\" + rest.Hostname,\n\t\t\t\t\tRedirectURIs: rest.Hostname + fmt.Sprintf(users.CALLBACK_BASE_URI, \"mastodon\"),\n\t\t\t\t})\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[GetProviderOauthFor] failed to register on mastodon instance <%s>\", name)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// => save instance's params into db for next time\n\t\t\t\tmastoInstance = new(Provider)\n\t\t\t\tmastoInstance.Name = name\n\t\t\t\tmastoInstance.Instance = instance\n\t\t\t\tmastoInstance.Protocol = \"mastodon\"\n\t\t\t\tmastoInstance.OauthRequestUrl = app.AuthURI\n\t\t\t\tmastoInstance.Infos = map[string]string{}\n\t\t\t\tmastoInstance.Infos[\"client_id\"] = app.ClientID\n\t\t\t\tmastoInstance.Infos[\"client_secret\"] = app.ClientSecret\n\t\t\t\tmastoInstance.Infos[\"address\"] = \"https://\" + instance\n\t\t\t\tmastoInstance.Infos[\"auth_uri\"] = app.AuthURI\n\t\t\t\te = rest.store.CreateProvider(mastoInstance)\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warnf(\"[GetProviderOauthFor] failed to save instance param in db for mastoInstance %+v\", mastoInstance)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmastoInstance.Protocol = \"mastodon\"\n\t\t\t\tmastoInstance.OauthRequestUrl = mastoInstance.Infos[\"auth_uri\"]\n\t\t\t}\n\t\t\tstate, e := users.SetMastodonAuthRequestUrl(mastoInstance, rest.Hostname)\n\t\t\tprovider = *mastoInstance\n\t\t\tif e != nil {\n\t\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[GetProviderOauthFor] failed to set mastodon auth request for user %s, on mastodon instance <%s>\", userId, instance)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcacheErr := rest.Cache.SetOauthSession(state, &OauthSession{\n\t\t\t\tUserId: userId,\n\t\t\t\tParams: map[string]string{\n\t\t\t\t\t\"instance\": instance,\n\t\t\t\t\t\"account\":  account[0],\n\t\t\t\t},\n\t\t\t})\n\t\t\tif cacheErr != nil {\n\t\t\t\tlog.WithError(cacheErr)\n\t\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[GetProviderOauthFor] failed to set Oauth session in cache for user %s, provider %s\", userId, name)\n\t\t\t\treturn\n\t\t\t}\n\n\t\tdefault:\n\t\t\terr = NewCaliopenErr(NotImplementedCaliopenErr, \"not implemented\")\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\tlog.Errorf(\"[GetProviderOauthFor] failed to found provider %s\", name)\n\terr = NewCaliopenErr(NotFoundCaliopenErr, \"not found\")\n\treturn\n}\n\nfunc (rest *RESTfacility) CreateTwitterIdentity(requestToken, verifier string) (remoteId string, err CaliopenError) {\n\tif requestToken == \"\" || verifier == \"\" {\n\t\treturn \"\", NewCaliopenErr(UnprocessableCaliopenErr, \"[CreateTwitterIdentity] missing oauth_token or oauth_verifier\")\n\t}\n\tprovider := rest.providers[TwitterProtocol]\n\tconf := &oauth1.Config{\n\t\tConsumerKey:    provider.Infos[\"consumer_key\"],\n\t\tConsumerSecret: provider.Infos[\"consumer_secret\"],\n\t\tEndpoint:       twitterOAuth1.AuthorizeEndpoint,\n\t}\n\toauthCache, e := rest.Cache.GetOauthSession(requestToken)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateTwitterIdentity] failed to retrieve Oauth session in cache for request token %s and verifier %s\", requestToken, verifier)\n\t\terr = WrapCaliopenErrf(e, NotFoundCaliopenErr, \"[CreateTwitterIdentity] failed to retrieve Oauth session in cache for request token %s\", requestToken)\n\t\treturn\n\t}\n\tif len(oauthCache.RequestSecret) < 5 {\n\t\tlog.WithError(e).Errorf(\"[CreateTwitterIdentity] oauthCache.RequestSecret too short to be a valid one: <%s> (token %s, verifier %s), user_id %s\", oauthCache.RequestSecret, requestToken, verifier, oauthCache.UserId)\n\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[CreateTwitterIdentity] invalid oauthCache.RequestSecret\")\n\t\treturn\n\t}\n\taccessToken, accessSecret, e := conf.AccessToken(requestToken, oauthCache.RequestSecret, verifier)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateTwitterIdentity] failed to request an access token : token %s, verifier %s, secret : (5 first chars only) <%s>, user_id %s\", requestToken, verifier, oauthCache.RequestSecret[0:5], oauthCache.UserId)\n\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[CreateTwitterIdentity] AccessToken request failed\")\n\t\treturn\n\t}\n\n\t// retrieve twitter profile from Twitter api\n\ttoken := oauth1.NewToken(accessToken, accessSecret)\n\thttpClient := conf.Client(oauth1.NoContext, token)\n\ttwitterClient := twitter.NewClient(httpClient)\n\taccountVerifyParams := &twitter.AccountVerifyParams{\n\t\tIncludeEntities: twitter.Bool(false),\n\t\tSkipStatus:      twitter.Bool(true),\n\t\tIncludeEmail:    twitter.Bool(false),\n\t}\n\ttwitterUser, resp, e := twitterClient.Accounts.VerifyCredentials(accountVerifyParams)\n\tif e != nil || resp.StatusCode != http.StatusOK ||\n\t\ttwitterUser == nil || twitterUser.ID == 0 || twitterUser.IDStr == \"\" {\n\t\tlog.Errorf(\"[CreateTwitterIdentity] failed to get twitter user : error=%s, twitterUser=%+v, resp.StatusCode=%d\", e, twitterUser, resp.StatusCode)\n\t\terr = NewCaliopenErr(FailDependencyCaliopenErr, \"[CreateTwitterIdentity] twitter client failed to get Twitter User\")\n\t\treturn\n\t}\n\n\t// build user identity\n\t//1.check if this user_identity already exists\n\tfoundIdentities, e := rest.store.LookupIdentityByIdentifier(twitterUser.ScreenName, TwitterProtocol)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateTwitterIdentity] failed to lookup in store if identity already exists : screenName %s, protocol %s\", twitterUser.ScreenName, TwitterProtocol)\n\t\terr = WrapCaliopenErrf(e, DbCaliopenErr, \"[CreateTwitterIdentity] failed to lookup in store if identity already exists. Aborting\")\n\t\treturn\n\t}\n\tfoundCount := len(foundIdentities)\n\tswitch foundCount {\n\tcase 0:\n\t\tuserIdentity := new(UserIdentity)\n\t\tuserID := UUID(uuid.FromStringOrNil(oauthCache.UserId))\n\t\tuserIdentity.MarshallNew(userID)\n\t\tuserIdentity.Protocol = TwitterProtocol\n\t\tuserIdentity.Type = RemoteIdentity\n\t\tuserIdentity.DisplayName = twitterUser.Name\n\t\tuserIdentity.Identifier = twitterUser.ScreenName\n\t\tuserIdentity.Credentials = &Credentials{\n\t\t\t\"token\":  accessToken,\n\t\t\t\"secret\": accessSecret,\n\t\t}\n\t\tuserIdentity.Infos = map[string]string{\n\t\t\t\"provider\":  \"twitter\",\n\t\t\t\"authtype\":  Oauth1,\n\t\t\t\"twitterid\": twitterUser.IDStr,\n\t\t}\n\t\t// save identity\n\t\te := rest.CreateUserIdentity(userIdentity)\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Errorf(\"[CreateTwitterIdentity] failed to create user identity : %+v\", *userIdentity)\n\t\t\terr = WrapCaliopenErr(e, FailDependencyCaliopenErr, \"[CreateTwitterIdentity] failed to create user identity\")\n\t\t\treturn\n\t\t}\n\t\tremoteId = userIdentity.Id.String()\n\t\treturn\n\tcase 1:\n\t\t// this twitter identity already exists, checking if it belongs to this user and, if ok, just updating Twitter's name\n\t\tstoredIdentity, e := rest.RetrieveUserIdentity(foundIdentities[0][0], foundIdentities[0][1], false)\n\t\tif e != nil || storedIdentity == nil {\n\t\t\tlog.Errorf(\"[CreateTwitterIdentity] failed to retrieve user identity found for twitter account %s. Error=%s, identity=%+v\", twitterUser.ScreenName, e, *storedIdentity)\n\t\t\terr = WrapCaliopenErrf(e, DbCaliopenErr, \"[CreateTwitterIdentity] failed to retrieve user identity found for twitter account %s\", twitterUser.ScreenName)\n\t\t\treturn\n\t\t}\n\t\tmodifiedFields := map[string]interface{}{\n\t\t\t\"DisplayName\": twitterUser.Name,\n\t\t}\n\t\tstoredIdentity.DisplayName = twitterUser.Name\n\t\tif e := rest.store.UpdateUserIdentity(storedIdentity, modifiedFields); e != nil {\n\t\t\tlog.WithError(e).Errorf(\"[CreateTwitterIdentity] failed to update user identity in db : identity=%+v, fields=%+v\", *storedIdentity, modifiedFields)\n\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[CreateTwitterIdentity] failed to update user identity in db\")\n\t\t\treturn\n\t\t}\n\t\tremoteId = storedIdentity.Id.String()\n\t\treturn\n\tdefault:\n\t\tlog.Errorf(\"[CreateTwitterIdentity] inconsistency in store : more than one identity found with twitter screen name <%s>\", twitterUser.ScreenName)\n\t\terr = NewCaliopenErrf(FailDependencyCaliopenErr, \"[CreateTwitterIdentity] inconsistency in store : more than one identity found with twitter screen name <%s>\", twitterUser.ScreenName)\n\t\treturn\n\t}\n}\n\nfunc (rest *RESTfacility) CreateGmailIdentity(state, code string) (remoteId string, err CaliopenError) {\n\n\t// start by checking if we have the state in cache\n\toauthCache, e := rest.Cache.GetOauthSession(state)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateGmailIdentity] failed to retrieve Oauth session in cache for state %s\", state)\n\t\terr = WrapCaliopenErrf(e, NotFoundCaliopenErr, \"[CreateGmailIdentity] failed to retrieve Oauth session in cache for state %s\", state)\n\t\treturn\n\t}\n\n\tgmailProvider := rest.providers[\"gmail\"]\n\tgmailProvider.OauthCallbackUri = fmt.Sprintf(users.CALLBACK_BASE_URI, \"gmail\")\n\n\toauthConfig := users.SetGoogleOauthConfig(gmailProvider, rest.Hostname)\n\n\tctx := context.Background()\n\n\t// get access & refresh tokens that will be added to UserIdentity.Credentials\n\ttoken, e := oauthConfig.Exchange(ctx, code, oauth2.AccessTypeOffline)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateGmailIdentity] failed to exchange access token for state %s and code %s. oauthConfig=%+v\", state, code, *oauthConfig)\n\t\terr = WrapCaliopenErrf(e, NotFoundCaliopenErr, \"[CreateGmailIdentity] failed to retrieve access token for state %s\", state)\n\t\treturn\n\t}\n\t// retrieve gmail profile from Google api\n\thttpClient := oauthConfig.Client(ctx, token)\n\tgoogleService, e := googleApi.New(httpClient)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateGmailIdentity] failed to create google service with oauthconfig=%+v, httpclient=%+v\", *oauthConfig, *httpClient)\n\t\terr = WrapCaliopenErr(e, NotFoundCaliopenErr, \"[CreateGmailIdentity] failed to create google service\")\n\t\treturn\n\t}\n\tgoogleUser, e := googleService.Userinfo.Get().Do()\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateGmailIdentity] failed to retrieve user from google api, googleService=%+v\", *googleService)\n\t\terr = WrapCaliopenErr(e, NotFoundCaliopenErr, \"[CreateGmailIdentity] failed to retrieve user from google api\")\n\t\treturn\n\t}\n\t// build user identity\n\t// 1. check if this user_identity already exists\n\tfoundIdentities, e := rest.store.LookupIdentityByIdentifier(googleUser.Email, EmailProtocol)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateGmailIdentity] failed to lookup identity in store : googleUser.Email=%s, protocol=%s\", googleUser.Email, EmailProtocol)\n\t\terr = WrapCaliopenErrf(e, DbCaliopenErr, \"[CreateGmailIdentity] failed to lookup in store if identity already exists. Aborting\")\n\t\treturn\n\t}\n\tfoundCount := len(foundIdentities)\n\tswitch foundCount {\n\tcase 0:\n\t\t// it appears that google API does not return a refresh token if Caliopen has already been authorize for this google account and user is logged in his google account\n\t\tif token.RefreshToken == \"\" {\n\t\t\tlog.WithError(e).Errorf(\"[CreateGmailIdentity] exchange access token for state %s and code %s got an empty refreshToken. oauthConfig=%+v, token=%+v\", state, code, *oauthConfig, *token)\n\t\t\terr = WrapCaliopenErrf(e, NotFoundCaliopenErr, \"[CreateGmailIdentity] failed to get a refresh token for state %s. User should check application permissions.\", state)\n\t\t\treturn\n\t\t}\n\t\tuserIdentity := new(UserIdentity)\n\t\tuserID := UUID(uuid.FromStringOrNil(oauthCache.UserId))\n\t\tuserIdentity.MarshallNew(userID)\n\t\tuserIdentity.Protocol = EmailProtocol\n\t\tuserIdentity.Type = RemoteIdentity\n\t\tuserIdentity.DisplayName = googleUser.Name\n\t\tuserIdentity.Identifier = googleUser.Email\n\t\tuserIdentity.Credentials = &Credentials{\n\t\t\tusers.CRED_ACCESS_TOKEN:  token.AccessToken,\n\t\t\tusers.CRED_REFRESH_TOKEN: token.RefreshToken,\n\t\t\tusers.CRED_TOKEN_EXPIRY:  token.Expiry.Format(time.RFC3339),\n\t\t\tusers.CRED_TOKEN_TYPE:    token.TokenType,\n\t\t\tusers.CRED_USERNAME:      googleUser.Email,\n\t\t}\n\t\tuserIdentity.Infos = map[string]string{\n\t\t\t\"inserver\":  gmailProvider.Infos[\"imapserver\"],\n\t\t\t\"outserver\": gmailProvider.Infos[\"smtpserver\"],\n\t\t\t\"provider\":  \"gmail\",\n\t\t\t\"authtype\":  Oauth2,\n\t\t}\n\n\t\t// save identity\n\t\te := rest.CreateUserIdentity(userIdentity)\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Errorf(\"[CreateGmailIdentity] failed to create user identity : userIdentity=%+v\", *userIdentity)\n\t\t\terr = WrapCaliopenErr(e, FailDependencyCaliopenErr, \"[CreateGmailIdentity] failed to create user identity\")\n\t\t\treturn\n\t\t}\n\t\tremoteId = userIdentity.Id.String()\n\tcase 1:\n\t\t// this identity already exists,\n\t\t// WHAT TO DO ?? -> stop here or continue ?\n\t\t// checking if it belongs to this user and, if ok, just updating display name\n\t\t// but not tokens\n\t\tstoredIdentity, e := rest.RetrieveUserIdentity(foundIdentities[0][0], foundIdentities[0][1], false)\n\t\tif e != nil || storedIdentity == nil {\n\t\t\tlog.WithError(e).Errorf(\"[CreateGmailIdentity] failed to retrieve user identity found for google account %s: foundIdentities=%+v\", googleUser.Name, foundIdentities)\n\t\t\terr = WrapCaliopenErrf(e, DbCaliopenErr, \"[CreateGmailIdentity] failed to retrieve user identity found for google account %s\", googleUser.Name)\n\t\t\treturn\n\t\t}\n\t\tstoredIdentity.DisplayName = googleUser.Name\n\t\t/*\n\t\t\tstoredIdentity.Credentials = &Credentials{\n\t\t\t\tusers.CRED_ACCESS_TOKEN:  token.AccessToken,\n\t\t\t\tusers.CRED_REFRESH_TOKEN: token.RefreshToken,\n\t\t\t\tusers.CRED_TOKEN_EXPIRY:  token.Expiry.Format(time.RFC3339),\n\t\t\t\tusers.CRED_TOKEN_TYPE:    token.TokenType,\n\t\t\t\tusers.CRED_USERNAME:      googleUser.Email,\n\t\t\t}\n\t\t*/\n\t\tmodifiedFields := map[string]interface{}{\n\t\t\t\"DisplayName\": googleUser.Name,\n\t\t\t//\"Credentials\": storedIdentity.Credentials,\n\t\t}\n\t\tif e := rest.store.UpdateUserIdentity(storedIdentity, modifiedFields); e != nil {\n\t\t\tlog.WithError(e).Errorf(\"[CreateGmailIdentity] failed to update user identity in db : identity=%+v, fields=%+v\", *storedIdentity, modifiedFields)\n\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[CreateGmailIdentity] failed to update user identity in db\")\n\t\t\treturn\n\t\t}\n\t\tremoteId = storedIdentity.Id.String()\n\t\treturn\n\tdefault:\n\t\tlog.Errorf(\"[CreateGmailIdentity] inconsistency in store : more than one identity found with email <%s>. foundIdentities=%+v\", googleUser.Email, foundIdentities)\n\t\terr = NewCaliopenErrf(FailDependencyCaliopenErr, \"[CreateGmailIdentity] inconsistency in store : more than one identity found with email <%s>\", googleUser.Email)\n\t\treturn\n\t}\n\treturn\n}\n\nfunc (rest *RESTfacility) CreateMastodonIdentity(state, code string) (remoteId string, err CaliopenError) {\n\n\t// start by checking if we have the state in cache\n\toauthCache, e := rest.Cache.GetOauthSession(state)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateMastodonIdentity] failed to retrieve Oauth session in cache for state %s\", state)\n\t\terr = WrapCaliopenErrf(e, NotFoundCaliopenErr, \"[CreateMastodonIdentity] failed to retrieve Oauth session in cache for state %s\", state)\n\t\treturn\n\t}\n\n\tmastodonProvider, e := rest.store.RetrieveProvider(\"mastodon\", oauthCache.Params[\"instance\"])\n\tif e != nil || mastodonProvider == nil {\n\t\tlog.WithError(e)\n\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[CreateMastodonIdentity] failed to retrieve mastodon instance %s from db\", oauthCache.Params[\"instance\"])\n\t\treturn\n\t}\n\tmastodonProvider.OauthCallbackUri = fmt.Sprintf(users.CALLBACK_BASE_URI, \"mastodon\")\n\n\tctx := context.Background()\n\n\t// get access token that will be added to UserIdentity.Credentials\n\toauthConfig := users.SetMastodonOauthConfig(*mastodonProvider, rest.Hostname)\n\ttoken, e := oauthConfig.Exchange(ctx, code, oauth2.AccessTypeOffline)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateMastodonIdentity] failed to exchange access token for state %s and code %s. oauthConfig=%+v\", state, code, *oauthConfig)\n\t\terr = WrapCaliopenErrf(e, NotFoundCaliopenErr, \"[CreateMastodonIdentity] failed to retrieve access token for state %s\", state)\n\t\treturn\n\t}\n\n\t// retrieve user profile from mastodon API\n\tmastodonClient := mastodon.NewClient(&mastodon.Config{\n\t\tServer:       mastodonProvider.Infos[\"address\"],\n\t\tClientID:     mastodonProvider.Infos[\"client_id\"],\n\t\tClientSecret: mastodonProvider.Infos[\"client_secret\"],\n\t\tAccessToken:  token.AccessToken,\n\t})\n\n\tuserAcct, acctErr := mastodonClient.GetAccountCurrentUser(ctx)\n\tif acctErr != nil {\n\t\tlog.WithError(acctErr)\n\t\terr = WrapCaliopenErrf(acctErr, FailDependencyCaliopenErr, \"[CreateMastodonIdentity] failed to retrieve mastodon user account\")\n\t\treturn\n\t}\n\n\t// build user identity\n\t// 1. check if this user_identity already exists\n\tmastodonIdentifier := userAcct.Username + \"@\" + mastodonProvider.Instance\n\tfoundIdentities, e := rest.store.LookupIdentityByIdentifier(mastodonIdentifier, MastodonProtocol)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[CreateMastodonIdentity] failed to lookup mastodon identity in store : %s\", userAcct.Acct)\n\t\terr = WrapCaliopenErrf(e, DbCaliopenErr, \"[CreateMastodonIdentity] failed to lookup in store if identity already exists. Aborting\")\n\t\treturn\n\t}\n\tfoundCount := len(foundIdentities)\n\tswitch foundCount {\n\tcase 0:\n\t\tuserIdentity := new(UserIdentity)\n\t\tuserID := UUID(uuid.FromStringOrNil(oauthCache.UserId))\n\t\tuserIdentity.MarshallNew(userID)\n\t\tuserIdentity.Protocol = MastodonProtocol\n\t\tuserIdentity.Type = RemoteIdentity\n\t\tuserIdentity.DisplayName = userAcct.DisplayName\n\t\tuserIdentity.Identifier = mastodonIdentifier\n\t\tuserIdentity.Credentials = &Credentials{\n\t\t\tusers.CRED_ACCESS_TOKEN: token.AccessToken,\n\t\t\tusers.CRED_TOKEN_EXPIRY: token.Expiry.Format(time.RFC3339),\n\t\t\tusers.CRED_TOKEN_TYPE:   token.TokenType,\n\t\t}\n\t\tuserIdentity.Infos = map[string]string{\n\t\t\t\"provider\":    \"mastodon\",\n\t\t\t\"authtype\":    Oauth2,\n\t\t\t\"mastodon_id\": string(userAcct.ID),\n\t\t\t\"url\":         userAcct.URL,\n\t\t\t\"avatar\":      userAcct.Avatar,\n\t\t}\n\n\t\t// save identity\n\t\te := rest.CreateUserIdentity(userIdentity)\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Errorf(\"[CreateMastodonIdentity] failed to create user identity : userIdentity=%+v\", *userIdentity)\n\t\t\terr = WrapCaliopenErr(e, FailDependencyCaliopenErr, \"[CreateMastodonIdentity] failed to create user identity\")\n\t\t\treturn\n\t\t}\n\t\tremoteId = userIdentity.Id.String()\n\tcase 1:\n\t\t// this identity already exists,\n\t\t// WHAT TO DO ?? -> stop here or continue ?\n\t\t// checking if it belongs to this user and, if ok, just updating display name\n\t\t// but not tokens\n\t\tstoredIdentity, e := rest.RetrieveUserIdentity(foundIdentities[0][0], foundIdentities[0][1], false)\n\t\tif e != nil || storedIdentity == nil {\n\t\t\tlog.WithError(e).Errorf(\"[CreateMastodonIdentity] failed to retrieve user identity found for account %s: foundIdentities=%+v\", mastodonIdentifier, foundIdentities)\n\t\t\terr = WrapCaliopenErrf(e, DbCaliopenErr, \"[CreateMastodonIdentity] failed to retrieve user identity found for account %s\", mastodonIdentifier)\n\t\t\treturn\n\t\t}\n\t\tstoredIdentity.DisplayName = userAcct.DisplayName\n\t\tmodifiedFields := map[string]interface{}{\n\t\t\t\"DisplayName\": userAcct.DisplayName,\n\t\t}\n\t\tif e := rest.store.UpdateUserIdentity(storedIdentity, modifiedFields); e != nil {\n\t\t\tlog.WithError(e).Errorf(\"[CreateMastodonIdentity] failed to update user identity in db : identity=%+v, fields=%+v\", *storedIdentity, modifiedFields)\n\t\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[CreateMastodonIdentity] failed to update user identity in db\")\n\t\t\treturn\n\t\t}\n\t\tremoteId = storedIdentity.Id.String()\n\t\treturn\n\tdefault:\n\t\tlog.Errorf(\"[CreateMastodonIdentity] inconsistency in store : more than one identity found with name <%s>. foundIdentities=%+v\", mastodonIdentifier, foundIdentities)\n\t\terr = NewCaliopenErrf(FailDependencyCaliopenErr, \"[CreateMastodonIdentity] inconsistency in store : more than one identity found with name <%s>\", mastodonIdentifier)\n\t\treturn\n\t}\n\treturn\n}\n\nfunc setTwitterAuthRequestUrl(provider *Provider, hostname string) (requestToken, requestSecret string, err CaliopenError) {\n\n\tprovider.OauthCallbackUri = fmt.Sprintf(users.CALLBACK_BASE_URI, \"twitter\")\n\n\t//IMPORTANT TODO: make use of vault to store consumer key and secret\n\tconf := &oauth1.Config{\n\t\tConsumerKey:    provider.Infos[\"consumer_key\"],\n\t\tConsumerSecret: provider.Infos[\"consumer_secret\"],\n\t\tCallbackURL:    hostname + provider.OauthCallbackUri,\n\t\tEndpoint:       twitterOAuth1.AuthorizeEndpoint,\n\t}\n\trequestToken, requestSecret, e := conf.RequestToken()\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[setTwitterAuthRequestUrl] failed to request token with config : %+v\", *conf)\n\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[setTwitterAuthRequestUrl] failed with RequestToken()\")\n\t\treturn\n\t}\n\tauthUrl, e := conf.AuthorizationURL(requestToken)\n\tif e != nil {\n\t\tlog.WithError(e).Errorf(\"[setTwitterAuthRequestUrl] failed to request auth url with config : %+v, token %s\", *conf, requestToken)\n\t\terr = WrapCaliopenErrf(e, FailDependencyCaliopenErr, \"[setTwitterAuthRequestUrl] failed with AuthorizationURL()\")\n\t\treturn\n\t}\n\tprovider.OauthRequestUrl = authUrl.String()\n\treturn\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/search.go",
    "content": "package REST\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tm \"github.com/CaliOpen/Caliopen/src/backend/main/go.main/messages\"\n)\n\n// API to execute broad-based searches within index\n// Searches are executed in all docs types of user\nfunc (rest *RESTfacility) Search(search IndexSearch) (response *IndexResult, err error) {\n\n\t// double check search object consistency before triggering the search\n\tif search.DocType != \"\" {\n\t\tif search.DocType != MessageIndexType && search.DocType != ContactIndexType {\n\t\t\treturn nil, errors.New(\"[RESTfacility] Invalid doc_type in search request\")\n\t\t}\n\t} else {\n\t\tif search.Offset > 0 || search.Limit > 0 {\n\t\t\treturn nil, errors.New(\"[RESTfacility] invalid search request: params 'offset', 'limit' are only accepted if param 'doc_type' is also provided\")\n\t\t}\n\t}\n\n\t// trigger the search\n\tresult, err := rest.index.Search(search)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// prepare messages objects for frontend rendering\n\tfor _, doc := range result.MessagesHits.Messages {\n\t\tmsg := doc.Document.(*Message)\n\t\tm.SanitizeMessageBodies(msg)\n\t\t(*msg).Body_excerpt = m.ExcerptMessage(*msg, 200, true, true)\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/settings.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\nfunc (rest *RESTfacility) GetSettings(user_id string) (settings *Settings, err error) {\n\tsettings, err = rest.store.GetSettings(user_id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn settings, err\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/suggest_participants.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t\"errors\"\n\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\n// SuggestRecipients makes use of index facility to return to user a list of suggested recipients\n// within the context of composing a new message\n// list is ordered by relevance : first suggestion should be the best\nfunc (rest *RESTfacility) SuggestRecipients(user *UserInfo, query_string string) (suggests []RecipientSuggestion, err error) {\n\tif user != nil && query_string != \"\" && len(query_string) > 2 {\n\t\t// TODO : more consistency checking against user_id & query_string\n\t\treturn rest.index.RecipientsSuggest(user, query_string)\n\t} else {\n\t\terr = errors.New(\"[RESTfacility.SuggestRecipients] unprocessable parameters\")\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/tags.go",
    "content": "package REST\n\nimport (\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\t\"github.com/bitly/go-simplejson\"\n\t\"github.com/mozillazg/go-unidecode\"\n\t\"strings\"\n)\n\nfunc (rest *RESTfacility) RetrieveUserTags(user_id string) (tags []Tag, err CaliopenError) {\n\ttags, e := rest.store.RetrieveUserTags(user_id)\n\tif e != nil {\n\t\treturn tags, WrapCaliopenErr(e, DbCaliopenErr, \"[RESTfacility] RetrieveUserTags failed\")\n\t}\n\treturn tags, nil\n}\n\n// CreateTag :\n// - ensures tag's label is unique\n// - copies tag's name to tag's label\n// - converts tag's name to lower & ASCII and replace spaces by \"_\"\n// - adds the tag in db for user if it doesn't exist yet\n// - updates tag in-place with its new properties\nfunc (rest *RESTfacility) CreateTag(tag *Tag) CaliopenError {\n\tvar isUnique bool\n\tvar err error\n\tisUnique, tag.Name, err = rest.IsTagLabelNameUnique(tag.Label, tag.User_id.String())\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, UnprocessableCaliopenErr, \"[RESTfacility] CreateTag failed to check tag's uniqueness\")\n\t}\n\tif !isUnique {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] tag's name/label conflict with existing one\")\n\t}\n\n\terr = rest.store.CreateTag(tag)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] CreateTag failed to CreateTag in store\")\n\t}\n\treturn nil\n}\n\nfunc (rest *RESTfacility) RetrieveTag(user_id, tag_name string) (tag Tag, err CaliopenError) {\n\ttag, e := rest.store.RetrieveTag(user_id, tag_name)\n\tif e != nil {\n\t\treturn tag, WrapCaliopenErr(e, DbCaliopenErr, \"[RESTfacility] RetrieveTag failed\")\n\t}\n\treturn tag, nil\n}\n\n// PatchTag is a shortcut for REST api to call two methods :\n// - UpdateWithPatch () to retrieve the tag from db\n// - checks that new label is not conflicting with an existing one\n// - then UpdateTag() to save updated tag to stores if everything went good.\nfunc (rest *RESTfacility) PatchTag(patch []byte, user_id, tag_name string) CaliopenError {\n\n\tcurrent_tag, Cerr := rest.RetrieveTag(user_id, tag_name)\n\tif Cerr != nil {\n\t\treturn Cerr\n\t}\n\n\t// read into the patch to make basic controls before processing it with generic helper\n\tpatchReader, err := simplejson.NewJson(patch)\n\tif err != nil {\n\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[RESTfacility] PatchTag failed with simplejson error : %s\", err)\n\t}\n\tlabel := patchReader.Get(\"label\").MustString()\n\tif label == \"\" || strings.Replace(label, \" \", \"\", -1) == \"\" {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] new tag's label is empty\")\n\t}\n\tisUnique, name, err := rest.IsTagLabelNameUnique(label, user_id)\n\tif err != nil {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] tag's name/label conflict with existing one\")\n\t}\n\tif !isUnique && name != tag_name {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] tag's name/label conflict with existing one\")\n\t}\n\n\t// patch seams OK, apply it to the resource\n\tnewTag, _, err := helpers.UpdateWithPatch(patch, &current_tag, UserActor)\n\tif err != nil {\n\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[RESTfacility] PatchTag failed with UpdateWithPatch error : %s\", err)\n\t}\n\n\t// save updated resource\n\terr = rest.UpdateTag(newTag.(*Tag))\n\tif err != nil {\n\t\treturn WrapCaliopenErrf(err, FailDependencyCaliopenErr, \"[RESTfacility] PatchTag failed with UpdateTag error : %s\", err)\n\t}\n\n\treturn nil\n}\n\n// UpdateTag updates a tag in store with payload,\nfunc (rest *RESTfacility) UpdateTag(tag *Tag) CaliopenError {\n\tuser_id := tag.User_id.String()\n\tif user_id != \"\" && tag.Name != \"\" {\n\t\tdb_tag, err := rest.store.RetrieveTag(user_id, tag.Name)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] UpdateTag failed to RetrieveTag from store\")\n\t\t}\n\t\t// RESTfacility allows user to only modify label and importance_level properties\n\t\t// thus squash other properties with those from db to ignore any modifications\n\t\ttag.Date_insert = db_tag.Date_insert\n\t\ttag.Name = db_tag.Name\n\t\ttag.Type = db_tag.Type\n\t\ttag.User_id = db_tag.User_id\n\t\terr = rest.store.UpdateTag(tag)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] UpdateTag failed to UpdateTag in store\")\n\t\t}\n\n\t} else {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] invalid tag's name and/or user_id\")\n\t}\n\treturn nil\n}\n\n// DeleteTag deletes a tag in store,\n// only if tag is a user tag.\nfunc (rest *RESTfacility) DeleteTag(user_id, tag_name string) CaliopenError {\n\n\ttag, err := rest.store.RetrieveTag(user_id, tag_name)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] DeleteTag failed to retrieve tag\")\n\t}\n\tif tag.Type == SystemTag {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] system tags can't be deleted by user\")\n\t}\n\n\terr = rest.store.DeleteTag(user_id, tag_name)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] DeleteTag failed to DeleteTag in store\")\n\t}\n\tgo rest.deleteEmbeddedTagReferences(user_id, tag_name)\n\treturn nil\n}\n\n// UpdateResourceTags :\n// - checks that tag_names within patch belong to user and are unique,\n// - calls generic UpdateWithPatch func to patch the resource,\n// - saves and indexes updated resource.\n// It is caller responsibility to call this func with a well-formed patch that has only \"tags\" properties\nfunc (rest *RESTfacility) UpdateResourceTags(user *UserInfo, resourceID, resourceType string, patch []byte) CaliopenError {\n\tvar err error\n\t// 1. check that tag_names within patch belong to user and are unique\n\ttags, err := rest.RetrieveUserTags(user.User_id)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] UpdateResourceTags error\")\n\t}\n\tuserTagsMap := make(map[string]bool)\n\tfor _, tag := range tags {\n\t\tuserTagsMap[tag.Name] = true\n\t}\n\n\tp, err := simplejson.NewJson(patch)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, UnknownCaliopenErr, \"[RESTfacility] UpdateResourceTags simplejson error\")\n\t}\n\tdeduplicatedTagsList := []string{}\n\tpatchTagsMap := make(map[string]bool)\n\tfor _, tag := range p.Get(\"tags\").MustStringArray() {\n\t\t// is tag belonging to user ?\n\t\tif _, ok := userTagsMap[tag]; !ok {\n\t\t\terr = fmt.Errorf(\"[RESTfacility] UpdateResourceTags : tag with name <%s> does not belong to user <%s>\", tag, user.User_id)\n\t\t\tbreak\n\t\t}\n\t\t// is tag unique ?\n\t\tif _, ok := patchTagsMap[tag]; !ok {\n\n\t\t\tpatchTagsMap[tag] = true\n\t\t\tdeduplicatedTagsList = append(deduplicatedTagsList, tag)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, UnknownCaliopenErr, \"[RESTfacility] UpdateResourceTags\")\n\t}\n\n\tp.Set(\"tags\", deduplicatedTagsList)\n\n\tpatch, _ = p.MarshalJSON()\n\t// 2. call generic UpdateWitchPatch func\n\tvar obj ObjectPatchable\n\tvar newObj ObjectPatchable\n\n\tswitch resourceType {\n\tcase MessageType:\n\t\tm, err := rest.store.RetrieveMessage(user.User_id, resourceID)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] UpdateResourceTags\")\n\t\t}\n\t\tobj = ObjectPatchable(m)\n\tcase ContactType:\n\t\tc, err := rest.store.RetrieveContact(user.User_id, resourceID)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] UpdateResourceTags\")\n\t\t}\n\t\tobj = ObjectPatchable(c)\n\tdefault:\n\t\treturn NewCaliopenErr(UnknownCaliopenErr, \"[RESTfacility] UpdateResourceWithPatch : invalid resourceType\")\n\t}\n\n\tnewObj, _, err = helpers.UpdateWithPatch(patch, obj, SystemActor)\n\tif err != nil {\n\t\treturn WrapCaliopenErr(err, UnknownCaliopenErr, \"[RESTfacility] UpdateResourceTags : helpers.UpdateWithPatch failed\")\n\t}\n\n\t// 3. store and index updated resource\n\tswitch resourceType {\n\tcase MessageType:\n\t\tupdate := map[string]interface{}{\n\t\t\t\"Tags\": newObj.(*Message).Tags,\n\t\t}\n\t\terr := rest.store.UpdateMessage(newObj.(*Message), update)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] UpdateResourceTags\")\n\t\t}\n\n\t\terr = rest.index.UpdateMessage(user, obj.(*Message), update)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, IndexCaliopenErr, \"[RESTfacility] UpdateResourceTags\")\n\t\t}\n\tcase ContactType:\n\t\tupdate := map[string]interface{}{\n\t\t\t\"Tags\": newObj.(*Contact).Tags,\n\t\t}\n\t\terr := rest.store.UpdateContact(newObj.(*Contact), obj.(*Contact), update)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] UpdateResourceTags\")\n\t\t}\n\n\t\terr = rest.index.UpdateContact(user, newObj.(*Contact), update)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, IndexCaliopenErr, \"[RESTfacility] UpdateResourceTags\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// IsTagLabelNameUnique ensures that a name and/or label is not conflicting with a name/label of another tag.\n// returns :\n//  - true if name/label is unique, along with the labelname normalized to a string that could be used as new tag's slug.\n//  - false if name/label already exists, along with the slug that conflict with\nfunc (rest *RESTfacility) IsTagLabelNameUnique(labelname, userID string) (bool, string, error) {\n\tslug := utf8ToASCIILowerNoSpace(labelname)\n\ttags, err := rest.store.RetrieveUserTags(userID)\n\tif err != nil {\n\t\treturn false, \"\", err\n\t}\n\tfor _, t := range tags {\n\t\tif t.Name == slug || t.Label == slug || t.Label == labelname {\n\t\t\treturn false, slug, nil\n\t\t}\n\t}\n\treturn true, slug, nil\n}\n\nfunc (rest *RESTfacility) deleteEmbeddedTagReferences(userID, tagName string) {\n\t//TODO : async deletion of tag references embedded into resources.\n}\n\nfunc utf8ToASCIILowerNoSpace(s string) string {\n\tb := make([]byte, len(s))\n\tb = []byte(unidecode.Unidecode(s))\n\tb = []byte(strings.ToLower(string(b)))\n\tb = []byte(strings.Replace(string(b), \" \", \"_\", -1))\n\treturn string(b)\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/username.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nfunc (rest *RESTfacility) UsernameIsAvailable(username string) (bool, error) {\n\treturn rest.store.UsernameIsAvailable(username)\n}\n"
  },
  {
    "path": "src/backend/main/go.main/facilities/REST/users.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage REST\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/users\"\n\t\"github.com/Sirupsen/logrus\"\n\t\"github.com/renstrom/shortuuid\"\n\t\"github.com/satori/go.uuid\"\n\t\"github.com/tidwall/gjson\"\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\nconst (\n\tchangePasswordSubject   = \"Information Caliopen : votre mot de passe a été changé\"\n\tchangePasswordBodyPlain = `\n\tCaliopen vous informe que le mot de passe de votre compte a été changé.\n\t`\n\tchangePasswordBodyRich = changePasswordBodyPlain\n)\n\n// as of oct. 2017, PatchUser only implemented for changing user's password\n// any attempt to patch something else should trigger an error\nfunc (rest *RESTfacility) PatchUser(userId string, patch *gjson.Result, notify Notifications.Notifiers) error {\n\n\tuser, err := rest.store.RetrieveUser(userId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif patch.Get(\"password\").Exists() {\n\t\t// if found a `password` property in patch, then special case :\n\t\t// there should be no other properties to patch\n\t\terr = validatePasswordPatch(patch)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"[REST PatchUser] invalid password patch : \" + err.Error())\n\t\t}\n\t\t// call the service that change user password\n\t\terr = users.ChangeUserPassword(user, patch, rest.store)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// compose and send a notification via email\n\t\tnotif := &Notification{\n\t\t\tBody: fmt.Sprintf(\"password changed for user %s\", user.UserId.String()),\n\t\t\tInternalPayload: &Message{\n\t\t\t\tBody_plain: changePasswordBodyPlain,\n\t\t\t\tBody_html:  changePasswordBodyRich,\n\t\t\t\tSubject:    changePasswordSubject,\n\t\t\t},\n\t\t\tNotifId: UUID(uuid.NewV1()),\n\t\t\tType:    NotifAdminMail,\n\t\t\tUser:    user,\n\t\t}\n\t\tgo notify.ByEmail(notif)\n\t\treturn nil\n\t} else {\n\t\t// hack to ensure that patch is for password only\n\t\t// should be replaced by :\n\t\t// helpers.ValidatePatchSemantic(user, patch)\n\t\treturn errors.New(\"[REST] PatchUser only implemented for changing password (for now)\")\n\t}\n\treturn nil\n}\n\nfunc (rest *RESTfacility) GetUser(userId string) (user *User, err error) {\n\t//TODO\n\treturn\n}\n\n// validatePasswordPatch checks if patch has only `password` property\nfunc validatePasswordPatch(patch *gjson.Result) error {\n\tvar err error\n\tcurrent_state := patch.Get(\"current_state\")\n\tif !current_state.Exists() {\n\t\treturn errors.New(\"[Patch] missing 'current_state' property in patch json\")\n\t}\n\tif !current_state.IsObject() {\n\t\treturn errors.New(\"[Patch] 'current_state' property in patch json is not an object\")\n\t}\n\n\tkeyValidator := func(key, value gjson.Result) bool {\n\t\tif key.String() != \"current_state\" && key.String() != \"password\" {\n\t\t\terr = errors.New(fmt.Sprintf(\"[Patch] found invalid key <%s> in the password patch\", key.String()))\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\tpatch.ForEach(keyValidator)\n\tif err == nil {\n\t\tcurrent_state.ForEach(keyValidator)\n\t}\n\n\treturn err\n}\n\n// RequestPasswordReset checks if an user could be found with provided payload request,\n// if found, it will trigger the password reset procedure that ends by notifying the user via the provided notifiers interface\nfunc (rest *RESTfacility) RequestPasswordReset(payload PasswordResetRequest, notify Notifications.Notifiers) error {\n\tvar user *User\n\tvar err error\n\t// 1. check if user exist\n\tif payload.Username != \"\" {\n\t\tuser, err = rest.store.UserByUsername(payload.Username)\n\t\tif err != nil || user == nil {\n\t\t\tlogrus.Info(err)\n\t\t\treturn errors.New(\"[RESTfacility] user not found\")\n\t\t}\n\t\tif payload.RecoveryMail != \"\" {\n\t\t\t// check if provided email is consistent for this user\n\t\t\tif payload.RecoveryMail != user.RecoveryEmail {\n\t\t\t\treturn errors.New(\"[RESTfacility] username and recovery email mismatch\")\n\t\t\t}\n\t\t}\n\t} else if payload.RecoveryMail != \"\" {\n\t\tuser, err = rest.store.UserByRecoveryEmail(payload.RecoveryMail)\n\t\tif err != nil || user == nil {\n\t\t\tlogrus.Info(err)\n\t\t\treturn errors.New(\"[RESTfacility] user not found\")\n\t\t}\n\t\tif payload.Username != \"\" {\n\t\t\t// check if provided username is consistent for this user\n\t\t\tif payload.Username != user.Name {\n\t\t\t\treturn errors.New(\"[RESTfacility] username and recovery email mismatch\")\n\t\t\t}\n\t\t}\n\t} else {\n\t\treturn errors.New(\"[RESTfacility] neither username, nor recovery email provided, at least one required\")\n\t}\n\n\t// 2. check if a password reset has already been ignited for that user\n\treset_session, err := rest.Cache.GetResetPasswordSession(user.UserId.String())\n\tif reset_session != nil {\n\t\trest.Cache.DeleteResetPasswordSession(user.UserId.String())\n\t\tlogrus.Infof(\"[RESTFacility] reset password session deleted for user <%s> [%s]\", user.Name, user.UserId.String())\n\t}\n\n\t// 3. generate a reset token and cache it\n\ttoken := shortuuid.New()\n\treset_session, err = rest.Cache.SetResetPasswordSession(user.UserId.String(), token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 4. send reset link to user's recovery email address.\n\tnotif := &Notification{\n\t\tUser:            user,\n\t\tInternalPayload: reset_session,\n\t\tNotifId:         UUID(uuid.NewV1()),\n\t\tBody:            fmt.Sprintf(\"reset link for user %s\", user.UserId.String()),\n\t\tType:            NotifPasswordReset,\n\t}\n\tgo notify.ByEmail(notif)\n\n\tlogrus.Infof(\"[RESTFacility] reset password session ignited for user <%s> [%s]\", user.Name, user.UserId.String())\n\n\treturn nil\n}\n\nfunc (rest *RESTfacility) ValidatePasswordResetToken(token string) (session *TokenSession, err error) {\n\tsession, err = rest.Cache.GetResetPasswordToken(token)\n\tif err != nil || session == nil {\n\t\treturn nil, errors.New(\"[RESTfacility] token not found\")\n\t}\n\tif time.Now().After(session.ExpiresAt) { // unlikely to happen because ttl is also set in cache facility\n\t\treturn nil, errors.New(\"[RESTfacility] token expired\")\n\t}\n\treturn session, nil\n}\n\nfunc (rest *RESTfacility) ResetUserPassword(token, new_password string, notify Notifications.Notifiers) error {\n\tsession, err := rest.ValidatePasswordResetToken(token)\n\tif err != nil {\n\t\treturn err\n\t}\n\tuser, err := rest.store.RetrieveUser(session.UserId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// reset password\n\terr = users.ResetUserPassword(user, new_password, rest.store)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlogrus.Infof(\"[RESTFacility] password reset for user <%s> [%s]\", user.Name, user.UserId.String())\n\n\t// delete reset session cache\n\terr = rest.Cache.DeleteResetPasswordSession(user.UserId.String())\n\tif err != nil {\n\t\tlogrus.WithError(err).Warnf(\"[RESTfacility] failed to delete reset session cache for user %s\", user.UserId.String())\n\t} else {\n\t\tlogrus.Infof(\"[RESTFacility] reset password session deleted for user <%s> [%s]\", user.Name, user.UserId.String())\n\t}\n\n\t// send email notification to user's recovery email address\n\tnotif := Notification{\n\t\tUser: user,\n\t\tInternalPayload: &Message{\n\t\t\tBody_plain: changePasswordBodyPlain,\n\t\t\tBody_html:  changePasswordBodyRich,\n\t\t\tSubject:    changePasswordSubject,\n\t\t},\n\t\tNotifId: UUID(uuid.NewV1()),\n\t\tBody:    fmt.Sprintf(\"password changed for user %s\", user.UserId.String()),\n\t\tType:    NotifAdminMail,\n\t}\n\tgo notify.ByEmail(&notif)\n\n\treturn nil\n}\n\n// DeleteUser deletes a user in store,\n// Check the password as a validation before\nfunc (rest *RESTfacility) DeleteUser(payload ActionsPayload) CaliopenError {\n\n\tif payload.Params == nil {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] delete user params is missing\")\n\t}\n\n\tif params, ok := payload.Params.(DeleteUserParams); ok {\n\t\tuser, err := rest.store.RetrieveUser(payload.UserId)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] DeleteUser failed to retrieve user\")\n\t\t}\n\n\t\tif !user.DateDelete.IsZero() {\n\t\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] User already deleted.\")\n\t\t}\n\n\t\terr = bcrypt.CompareHashAndPassword(user.Password, []byte(params.Password))\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, WrongCredentialsErr, \"[RESTfacility] DeleteUser Wrong password\")\n\t\t}\n\t\terr = rest.store.DeleteUser(payload.UserId)\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, DbCaliopenErr, \"[RESTfacility] DeleteUser failed to delete user in store\")\n\t\t}\n\n\t\t// Logout\n\t\terr = rest.Cache.LogoutUser(params.AccessToken)\n\n\t\tif err != nil {\n\t\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] Unable to logout.\")\n\t\t}\n\t} else {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"[RESTfacility] payload.Params is not of type DeleteUserParams\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/helpers/contact.go",
    "content": "/*\n * // Copyleft (ɔ) 2017 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage helpers\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/ttacon/libphonenumber\"\n\t\"strings\"\n)\n\n// ComputeNewTitle applies ComputeTitle logic to a new Contact\n// If names are empty but Title is not, names are filled from Title.\nfunc ComputeNewTitle(c *Contact) {\n\t// TODO: improve it\n\tif c.Title == \"\" {\n\t\tComputeTitle(c)\n\t} else {\n\t\t// Title has been set by user,\n\t\t// if given_name & family_name are empty we try to fill them from title\n\t\tif c.GivenName == \"\" && c.FamilyName == \"\" {\n\t\t\tif space_sep := strings.Split(c.Title, \" \"); len(space_sep) > 1 {\n\t\t\t\t//fill it the Google way : last word to family_name\n\t\t\t\tc.FamilyName = space_sep[len(space_sep)-1]\n\t\t\t\tc.GivenName = strings.Join(space_sep[:len(space_sep)-1], \" \")\n\t\t\t} else if comma_sep := strings.Split(c.Title, \",\"); len(comma_sep) > 1 {\n\t\t\t\t//same algo as above, but with comma\n\t\t\t\tc.FamilyName = space_sep[len(space_sep)-1]\n\t\t\t\tc.GivenName = strings.Join(space_sep[:len(space_sep)-1], \" \")\n\t\t\t\tc.Title = strings.Replace(c.Title, \",\", \" \", -1)\n\t\t\t} else {\n\t\t\t\tc.FamilyName = c.Title\n\t\t\t}\n\t\t}\n\t}\n\n}\n\n// ComputeTitle modifies Title property in-place or do nothing if it fails\n// it tries to fill Title with names, then email/phones/etc.\nfunc ComputeTitle(c *Contact) {\n\ttitle := []string{}\n\tif c.NamePrefix != \"\" {\n\t\ttitle = append(title, c.NamePrefix)\n\t}\n\tif c.GivenName != \"\" {\n\t\ttitle = append(title, c.GivenName)\n\t}\n\tif c.AdditionalName != \"\" {\n\t\ttitle = append(title, c.AdditionalName)\n\t}\n\tif c.FamilyName != \"\" {\n\t\ttitle = append(title, c.FamilyName)\n\t}\n\tif c.NameSuffix != \"\" {\n\t\ttitle = append(title, c.NameSuffix)\n\t}\n\tswitch len(title) {\n\tcase 0:\n\t\t//Title is still empty. try more properties\n\t\tswitch {\n\t\tcase len(c.Emails) > 0:\n\t\t\tc.Title = c.Emails[0].Address\n\t\tcase len(c.Phones) > 0:\n\t\t\tc.Title = c.Phones[0].Number\n\t\tcase len(c.Ims) > 0:\n\t\t\tc.Title = c.Ims[0].Address\n\t\t}\n\tcase 1:\n\t\tc.Title = title[0]\n\tdefault:\n\t\tc.Title = strings.Join(title, \" \")\n\t}\n}\n\n// NormalizePhoneNumbers tries to normalize phone numbers found within contact.\n// It fills Phone.NormalizedNumber property if it could\nfunc NormalizePhoneNumbers(c *Contact) {\n\tvar num libphonenumber.PhoneNumber\n\tfor i, phone := range c.Phones {\n\t\t// try to parse phone number by seeking a country code\n\t\t// fallback to french number only if no country code could be found\n\t\t// TODO: lookup into user's contactbook to find the most relevant country code to fallback to\n\t\tif err := libphonenumber.ParseToNumber(phone.Number, \"FR\", &num); err == nil {\n\t\t\tc.Phones[i].NormalizedNumber = libphonenumber.Format(&num, libphonenumber.INTERNATIONAL)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/backend/main/go.main/helpers/discussion.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage helpers\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/montanaflynn/stats\"\n)\n\n// ComputeDiscussionIL is the algorithm to return an Importance Level for a discussion\n// based on importance levels found within messages of the discussion.\n// It returns the average Importance Level of the top 10% most important messages.\n// If calculation fails, returned 0.\nfunc ComputeDiscussionIL(messagesIL []float64) float64 {\n\trank, err := stats.PercentileNearestRank(messagesIL, 90)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[ComputeDiscussionIL] failed to get percentile from set\")\n\t\treturn 0\n\t}\n\tvar percentile stats.Float64Data\n\tfor _, n := range messagesIL {\n\t\tif n >= rank {\n\t\t\tpercentile = append(percentile, n)\n\t\t}\n\t}\n\tmean, err := stats.Mean(percentile)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[ComputeDiscussionIL] failed to compute mean from set\")\n\t\treturn 0\n\t}\n\treturn mean\n}\n"
  },
  {
    "path": "src/backend/main/go.main/helpers/discussion_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage helpers\n\nimport (\n\t\"testing\"\n)\n\nfunc TestComputeDiscussionIL(t *testing.T) {\n\tsets := [][]float64{\n\t\t{1, 2, 2, 2, 5, -1, 10, 5, 5, 6},\n\t\t{-1, -2, 3, 2, 5, -1, 10, 5, 5, 6},\n\t\t{1, 2},\n\t\t{5},\n\t\t{-10},\n\t\t{9, -10},\n\t\t{-1, -1},\n\t}\n\tfor i, set := range sets {\n\t\tIL := ComputeDiscussionIL(set)\n\t\tif IL == 0 {\n\t\t\tt.Errorf(\"got 0 result for set %d\", i)\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/backend/main/go.main/helpers/filesystem.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage helpers\n\n/*\nimport (\n\t\"github.com/spf13/afero\"\n\t\"os\"\n)\n\nfunc init() {\n\tvar AppFs afero.Fs = afero.NewOsFs()\n}\n\nfunc Exists(path string) (bool, error) {\n\t_, err := afero.Fs.Stat(path)\n\tif err == nil {\n\t\treturn true, nil\n\t}\n\tif os.IsNotExist(err) {\n\t\treturn false, nil\n\t}\n\treturn false, err\n}\n*/\n"
  },
  {
    "path": "src/backend/main/go.main/helpers/misc.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage helpers\n\nfunc EscapeUsername(username string) string {\n\t// TODO : implement an algorithm against injections\n\treturn username\n}\n"
  },
  {
    "path": "src/backend/main/go.main/helpers/netTest.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage helpers\n\nimport (\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"encoding/hex\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"math/big\"\n\t\"net\"\n\t\"time\"\n)\n\n// netTest provides mocked network structs and interfaces to run tests which need networking\n\nvar (\n\ttlsConfig          *tls.Config\n\ttestRSACertificate = fromHex(\"3082024b308201b4a003020102020900e8f09d3fe25beaa6300d06092a864886f70d01010b0500301f310b3009060355040a1302476f3110300e06035504031307476f20526f6f74301e170d3136303130313030303030305a170d3235303130313030303030305a301a310b3009060355040a1302476f310b300906035504031302476f30819f300d06092a864886f70d010101050003818d0030818902818100db467d932e12270648bc062821ab7ec4b6a25dfe1e5245887a3647a5080d92425bc281c0be97799840fb4f6d14fd2b138bc2a52e67d8d4099ed62238b74a0b74732bc234f1d193e596d9747bf3589f6c613cc0b041d4d92b2b2423775b1c3bbd755dce2054cfa163871d1e24c4f31d1a508baab61443ed97a77562f414c852d70203010001a38193308190300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b06010505070302300c0603551d130101ff0402300030190603551d0e041204109f91161f43433e49a6de6db680d79f60301b0603551d230414301280104813494d137e1631bba301d5acab6e7b30190603551d1104123010820e6578616d706c652e676f6c616e67300d06092a864886f70d01010b0500038181009d30cc402b5b50a061cbbae55358e1ed8328a9581aa938a495a1ac315a1a84663d43d32dd90bf297dfd320643892243a00bccf9c7db74020015faad3166109a276fd13c3cce10c5ceeb18782f16c04ed73bbb343778d0c1cf10fa1d8408361c94c722b9daedb4606064df4c1b33ec0d1bd42d4dbfe3d1360845c21d33be9fae7\")\n\ttestRSAPrivateKey  = &rsa.PrivateKey{\n\t\tPublicKey: rsa.PublicKey{\n\t\t\tN: bigFromString(\"153980389784927331788354528594524332344709972855165340650588877572729725338415474372475094155672066328274535240275856844648695200875763869073572078279316458648124537905600131008790701752441155668003033945258023841165089852359980273279085783159654751552359397986180318708491098942831252291841441726305535546071\"),\n\t\t\tE: 65537,\n\t\t},\n\t\tD: bigFromString(\"7746362285745539358014631136245887418412633787074173796862711588221766398229333338511838891484974940633857861775630560092874987828057333663969469797013996401149696897591265769095952887917296740109742927689053276850469671231961384712725169432413343763989564437170644270643461665184965150423819594083121075825\"),\n\t\tPrimes: []*big.Int{\n\t\t\tbigFromString(\"13299275414352936908236095374926261633419699590839189494995965049151460173257838079863316944311313904000258169883815802963543635820059341150014695560313417\"),\n\t\t\tbigFromString(\"11578103692682951732111718237224894755352163854919244905974423810539077224889290605729035287537520656160688625383765857517518932447378594964220731750802463\"),\n\t\t},\n\t}\n)\n\nfunc init() {\n\ttlsConfig = &tls.Config{\n\t\tTime:               func() time.Time { return time.Unix(0, 0) },\n\t\tRand:               zeroSource{},\n\t\tCertificates:       make([]tls.Certificate, 1),\n\t\tInsecureSkipVerify: true,\n\t\tMinVersion:         tls.VersionSSL30,\n\t\tMaxVersion:         tls.VersionTLS12,\n\t\tCipherSuites:       allCipherSuites(),\n\t}\n\ttlsConfig.Certificates[0].Certificate = [][]byte{testRSACertificate}\n\ttlsConfig.Certificates[0].PrivateKey = testRSAPrivateKey\n\ttlsConfig.BuildNameToCertificate()\n}\n\n// GetTlsConn returns a tls Conn suitable fo tests\nfunc GetTestTlsConn() *tls.Conn {\n\t_, serverConn := net.Pipe()\n\treturn tls.Server(serverConn, tlsConfig.Clone())\n}\n\n// zeroSource is an io.Reader that returns an unlimited number of zero bytes.\ntype zeroSource struct{}\n\nfunc (zeroSource) Read(b []byte) (n int, err error) {\n\tfor i := range b {\n\t\tb[i] = 0\n\t}\n\n\treturn len(b), nil\n}\n\nfunc fromHex(s string) []byte {\n\tb, _ := hex.DecodeString(s)\n\treturn b\n}\n\nfunc bigFromString(s string) *big.Int {\n\tret := new(big.Int)\n\tret.SetString(s, 10)\n\treturn ret\n}\n\nfunc allCipherSuites() []uint16 {\n\tids := make([]uint16, len(TlsSuites))\n\tvar i int\n\tfor id := range TlsSuites {\n\t\tids[i] = id\n\t\ti++\n\t}\n\n\treturn ids\n}\n"
  },
  {
    "path": "src/backend/main/go.main/helpers/patch.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage helpers\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/tidwall/gjson\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n\t\"reflect\"\n)\n\ntype errHandler struct {\n\terr error\n}\n\ntype patch struct {\n\tactor                Initiator\n\tcurrentState         ObjectPatchable\n\tdbState              ObjectPatchable\n\tfieldsInCurrentState map[string]interface{}\n\tfieldsInPatch        map[string]interface{}\n\tjsonMap              map[string]string // keys are json properties, values are struct Field name counterpart\n\tnewState             ObjectPatchable\n\traw                  []byte\n}\n\n// UpdateWithPatch replace obj with updated version according to patch directives if everything went well.\n// It returns fields that have been effectively modified.\nfunc UpdateWithPatch(patch []byte, obj ObjectPatchable, actor Initiator) (newObj ObjectPatchable, modifiedFields map[string]interface{}, err error) {\n\n\tp, err := buildPatch(patch, obj, actor)\n\tif err != nil {\n\t\treturn obj, nil, err\n\t}\n\n\terr = validateCurrentState(p)\n\tif err != nil {\n\t\treturn obj, nil, err\n\t}\n\n\t// squash newState values with dbState values for those that it's not allowed to modify\n\tvalidatedFields, err := validateActorRights(p.dbState, p.newState, p.actor)\n\tif err != nil {\n\t\treturn obj, nil, err\n\t}\n\n\t// ensure mandatory properties are present\n\tp.newState.MarshallNew()\n\n\t// copy fields that have been validated and modified into the map that will be returned.\n\tmodifiedFields = map[string]interface{}{}\n\tfor key := range p.fieldsInPatch {\n\t\tfieldName := p.jsonMap[key]\n\t\tif value, ok := validatedFields[fieldName]; ok {\n\t\t\tmodifiedFields[fieldName] = value\n\t\t}\n\t}\n\n\treturn p.newState, modifiedFields, nil\n}\n\n//\nfunc buildPatch(rawPatch []byte, dbState ObjectPatchable, actor Initiator) (*patch, error) {\n\n\tpatch := patch{\n\t\tactor:                actor,\n\t\tdbState:              dbState,\n\t\tfieldsInCurrentState: map[string]interface{}{},\n\t\tfieldsInPatch:        map[string]interface{}{},\n\t\tjsonMap:              dbState.JsonTags(),\n\t\traw:                  rawPatch,\n\t}\n\n\t// unmarshal raw json to a map[string]interface{}\n\terr := json.Unmarshal(rawPatch, &patch.fieldsInPatch)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// get raw 'current_state' from patch and apply it to currentState object\n\tpatch.currentState = dbState.NewEmpty().(ObjectPatchable)\n\treflect.ValueOf(patch.currentState).Elem().Set(reflect.ValueOf(dbState).Elem()) // now patch.currentState has same kind than object counterpart\n\n\tpatch.fieldsInCurrentState = patch.fieldsInPatch[\"current_state\"].(map[string]interface{})\n\terr = patch.currentState.UnmarshalMap(patch.fieldsInCurrentState)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// remove 'current_state' before creating the newState from remaining properties\n\tdelete(patch.fieldsInPatch, \"current_state\")\n\tpatch.newState = dbState.NewEmpty().(ObjectPatchable)\n\treflect.ValueOf(patch.newState).Elem().Set(reflect.ValueOf(dbState).Elem())\n\terr = patch.newState.UnmarshalMap(patch.fieldsInPatch)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &patch, nil\n}\n\n//\nfunc validateCurrentState(patch *patch) (err error) {\n\n\t// before comparing dbState and currentState we must ensure that embedded slices are sorted the same way\n\tpatch.dbState.SortSlices()\n\tpatch.currentState.SortSlices()\n\tpatch.newState.SortSlices()\n\n\t// check global equality\n\t// TODO: add a compare(a,b T) func to CaliopenObject interface\n\tif !reflect.DeepEqual(patch.dbState, patch.currentState) {\n\t\treturn NewCaliopenErr(UnprocessableCaliopenErr, \"current state not consistent with db state\")\n\t}\n\n\t// seek for keys within patch that are not in current_state meaning patch wants to add the key,\n\t// consequently the value in db should equals to default zero\n\temptyState := patch.dbState.NewEmpty()\n\tfor key := range patch.fieldsInPatch {\n\t\tfield := patch.jsonMap[key]\n\t\tif ok, err := reflections.HasField(patch.dbState, field); err != nil || !ok {\n\t\t\treturn NewCaliopenErrf(UnprocessableCaliopenErr, \"struct %s has no key %s\", reflect.TypeOf(patch.dbState).String(), key)\n\t\t}\n\t\tif _, present := patch.fieldsInCurrentState[key]; !present {\n\t\t\tempty, err1 := reflections.GetField(emptyState, field)\n\t\t\tstore, err2 := reflections.GetField(patch.dbState, field)\n\t\t\tif err1 != nil || err2 != nil {\n\t\t\t\treturn NewCaliopenErrf(UnprocessableCaliopenErr, \"[Patch] failed to retrieve field <%s> from object\", field)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(store, empty) {\n\t\t\t\treturn NewCaliopenErrf(UnprocessableCaliopenErr, \"[Patch] field <%s> not consistent with db state\", field)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// validateActorRights iterate recursively over dbState fields to prevent actor to modify those that he hasn't right on.\n// If a field is protected against current actor, its value is replaced by its value from db,\n// otherwise field name and value are copied to validatedFields for later use\n// dbState and newState must be pointers to structs, but dbState could be a nil pointer\nfunc validateActorRights(dbState, newState interface{}, actor Initiator) (validatedFields map[string]interface{}, err error) {\n\n\tvalidatedFields = map[string]interface{}{}\n\n\tfieldsCount := reflect.TypeOf(newState).Elem().NumField()\n\tfor i := 0; i < fieldsCount; i++ {\n\t\tfieldName := reflect.TypeOf(newState).Elem().Field(i).Name\n\t\tif canModifyProperty(newState, fieldName, actor) {\n\t\t\tfieldKind, _ := reflections.GetFieldKind(newState, fieldName)\n\t\t\tswitch fieldKind {\n\t\t\tcase reflect.Slice:\n\t\t\t\tsliceNew := reflect.ValueOf(newState).Elem().FieldByName(fieldName)\n\n\t\t\t\t// check if it's a slice of structs\n\t\t\t\tif sliceNew.Len() > 0 {\n\t\t\t\t\tswitch sliceNew.Index(0).Kind() {\n\t\t\t\t\tcase reflect.Struct:\n\t\t\t\t\t\tif _, ok := sliceNew.Index(0).Addr().Interface().(ObjectPatchable); ok {\n\t\t\t\t\t\t\t// iterate over items to check rights on sub-fields.\n\t\t\t\t\t\t\tsliceDB := reflect.ValueOf(dbState).Elem().FieldByName(fieldName)\n\t\t\t\t\t\t\tfor i := 0; i < sliceNew.Len(); i++ {\n\t\t\t\t\t\t\t\t// check that we have a db counterpart before calling validateActorRights\n\t\t\t\t\t\t\t\tif i < sliceDB.Len() {\n\t\t\t\t\t\t\t\t\t_, err := validateActorRights(sliceDB.Index(i).Addr().Interface(), sliceNew.Index(i).Addr().Interface(), actor)\n\t\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t_, err := validateActorRights(nil, sliceNew.Index(i).Addr().Interface(), actor)\n\t\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tvalidatedFields[fieldName], _ = reflections.GetField(newState, fieldName)\n\t\t\tcase reflect.Struct:\n\t\t\t\tsubNew, _ := reflections.GetField(newState, fieldName)\n\t\t\t\tif _, ok := subNew.(ObjectPatchable); ok {\n\t\t\t\t\t// we must check rights on sub-fields.\n\t\t\t\t\tvar subDB *interface{}\n\t\t\t\t\tif dbState != nil {\n\t\t\t\t\t\tf, err := reflections.GetField(dbState, fieldName)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, WrapCaliopenErr(err, UnprocessableCaliopenErr, \"validateActorRights failed\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsubDB = &f\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsubDB = nil\n\t\t\t\t\t}\n\t\t\t\t\t_, err = validateActorRights(subDB, &subNew, actor)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tvalidatedFields[fieldName], _ = reflections.GetField(newState, fieldName)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// no sub-level, field could be updated directly\n\t\t\t\tvalidatedFields[fieldName], _ = reflections.GetField(newState, fieldName)\n\t\t\t}\n\t\t} else {\n\t\t\t// actor can't patch this field\n\t\t\tif dbState != nil {\n\t\t\t\t// try to silently discard the field by replacing it with value from db\n\t\t\t\tdbValue, err := reflections.GetField(dbState, fieldName)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, WrapCaliopenErrf(err, UnknownCaliopenErr, \"validateActorRights failed to fetch field %s\", fieldName)\n\t\t\t\t}\n\t\t\t\treflections.SetField(newState, fieldName, dbValue)\n\t\t\t} else {\n\t\t\t\t// no counterpart available in db, set field to zero\n\t\t\t\tfieldType := reflect.TypeOf(newState).Elem().Field(i).Type\n\t\t\t\treflections.SetField(newState, fieldName, reflect.New(fieldType).Elem().Interface())\n\t\t\t}\n\t\t}\n\t}\n\n\treturn validatedFields, nil\n}\n\n// canModifyProperty returns true if and only if Initiator has rights to modify the property of the object.\n// directive comes from the \"patch\" tag found within struct declaration\nfunc canModifyProperty(obj interface{}, field string, actor Initiator) bool {\n\trightLevel := Unknown\n\tdirective, err := reflections.GetFieldTag(obj, field, \"patch\")\n\tif directive != \"\" && err == nil {\n\t\tif level, ok := Initiators[directive]; ok {\n\t\t\trightLevel = level\n\t\t}\n\t}\n\treturn rightLevel >= actor // if rightLevel is below actor level, then actor has not the right to modify\n}\n\n// func to do the parsing once and get a pointer to the result.\nfunc ParsePatch(json []byte) (*gjson.Result, error) {\n\tif !gjson.Valid(string(json)) {\n\t\treturn nil, errors.New(\"invalid json\")\n\t}\n\tr := gjson.ParseBytes(json)\n\treturn &r, nil\n}\n"
  },
  {
    "path": "src/backend/main/go.main/helpers/uuid.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n *\n * // const, GetTime & ToUnixTime borrowed from github.com/google/uuid\n */\npackage helpers\n\nimport (\n\t\"encoding/binary\"\n\t\"github.com/satori/go.uuid\"\n\t\"time\"\n)\n\nconst (\n\tlillian    = 2299160          // Julian day of 15 Oct 1582\n\tunix       = 2440587          // Julian day of 1 Jan 1970\n\tepoch      = unix - lillian   // Days between epochs\n\tg1582      = epoch * 86400    // seconds between epochs\n\tg1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs\n)\n\n// GetTime returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in\n// uuid.  The time is only defined for version 1 and 2 UUIDs.\nfunc GetTime(uuid uuid.UUID) time.Time {\n\tt := int64(binary.BigEndian.Uint32(uuid[0:4]))\n\tt |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32\n\tt |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48\n\treturn time.Unix(toUnixTime(t))\n}\n\n// ToUnixTime converts t the number of seconds and nanoseconds using the Unix\n// epoch of 1 Jan 1970.\nfunc toUnixTime(t int64) (sec, nsec int64) {\n\tsec = int64(t - g1582ns100)\n\tnsec = (sec % 10000000) * 100\n\tsec /= 10000000\n\treturn sec, nsec\n}\n"
  },
  {
    "path": "src/backend/main/go.main/messages/messages.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage messages\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/microcosm-cc/bluemonday\"\n\t\"golang.org/x/net/html\"\n\t\"regexp\"\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\n// scrub message's bodies to make message displayable in frontend interfaces.\n// message is modified in-place.\n// if sanitation failed, message's string bodies are emptied\nfunc SanitizeMessageBodies(msg *Message) {\n\tp := CaliopenPolicy()\n\t(*msg).Body_html = p.Sanitize(msg.Body_html)\n\t(*msg).Body_html = replaceBodyTag(msg.Body_html)\n}\n\n// UGCPolicy returns a policy aimed at user generated content that is a result\n// of HTML WYSIWYG tools and Markdown conversions.\n//\n// This is expected to be a fairly rich document where as much markup as\n// possible should be retained. Markdown permits raw HTML so we are basically\n// providing a policy to sanitise HTML5 documents safely but with the\n// least intrusion on the formatting expectations of the user.\n//\n//See https://github.com/microcosm-cc/bluemonday to build a bespoke policy.\nfunc CaliopenPolicy() *bluemonday.Policy {\n\tbasePolicy := bluemonday.UGCPolicy()\n\tbasePolicy.AllowAttrs(\"title\").Matching(regexp.MustCompile(`[\\p{L}\\p{N}\\s\\-_',:\\[\\]!\\./\\\\\\(\\)&]*`)).Globally()\n\tbasePolicy.RequireNoFollowOnFullyQualifiedLinks(true)\n\tbasePolicy.AddTargetBlankToFullyQualifiedLinks(true)\n\t// allow body with few attributes\n\tbasePolicy.AllowElements(\"body\")\n\tbasePolicy.AllowAttrs(\"leftmargin\").Matching(bluemonday.Integer).OnElements(\"body\")\n\tbasePolicy.AllowAttrs(\"rightmargin\").Matching(bluemonday.Integer).OnElements(\"body\")\n\tbasePolicy.AllowAttrs(\"topmargin\").Matching(bluemonday.Integer).OnElements(\"body\")\n\tbasePolicy.AllowAttrs(\"bottommargin\").Matching(bluemonday.Integer).OnElements(\"body\")\n\tbasePolicy.AllowAttrs(\"marginwidth\").Matching(bluemonday.Integer).OnElements(\"body\")\n\tbasePolicy.AllowAttrs(\"marginheight\").Matching(bluemonday.Integer).OnElements(\"body\")\n\tbasePolicy.AllowAttrs(\"offset\").Matching(bluemonday.Integer).OnElements(\"body\")\n\t// allow img src=\"data:…\n\tbasePolicy.AllowDataURIImages()\n\n\treturn basePolicy\n}\n\n// Returns an excerpt of Message from either body_plain, body_html or subject.\n// Excerpt is always a plain text without any markup, of a length of 'length' runes max.\n// Best effort is made to retrieve relevant excerpt from html body (see excerptFromHMTL func)\n// A string is always returned, even if excerpt extraction failed.\n// If option \"wordWrap\" is true, string is trimmed at the end of a word, thus it may be shorter than length.\n// If option \"addEllipsis\" is true, … (unicode 2026) is added at the end of the string if the string has been shortened.\nfunc ExcerptMessage(msg Message, length int, wordWrap, addEllipsis bool) (excerpt string) {\n\t// 1. try to extract excerpt from HTML\n\tif msg.Body_html != \"\" {\n\t\tvar err error\n\t\texcerpt, err = excerptFromHMTL(msg.Body_html)\n\t\tif err == nil && excerpt != \"\" {\n\t\t\treturn trimExcerpt(excerpt, length, wordWrap, addEllipsis)\n\t\t}\n\t}\n\n\t// 2. fall-back to plain body if any\n\tif msg.Body_plain != \"\" {\n\t\treturn trimExcerpt(msg.Body_plain, length, wordWrap, addEllipsis)\n\t}\n\n\t// 3. then subject\n\tif msg.Subject != \"\" {\n\t\treturn trimExcerpt(msg.Subject, length, wordWrap, addEllipsis)\n\t}\n\n\t// 4. nothing found, return empty string\n\treturn \"\"\n}\n\n// algorithm to retrieve the more relevant excerpt from an HMTL doc.\n// still WIP in september 2017\nfunc excerptFromHMTL(source string) (excerpt string, err error) {\n\tp := CaliopenPolicy()\n\tsanitized := p.Sanitize(source)\n\tsanitized = replaceBodyTag(sanitized)\n\tdoc, err := html.Parse(strings.NewReader(sanitized))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\texcerpt_strings := []string{}\n\tvar f func(*html.Node)\n\tf = func(n *html.Node) {\n\t\t//take textNode only, which are not within anchor node\n\t\tif n.Type == html.TextNode && n.Data != \"\" && n.Parent.Data != \"a\" {\n\t\t\t//remove lines filled with only spaces and/or control chars\n\t\t\ttrim_str := strings.TrimFunc(n.Data, func(r rune) bool {\n\t\t\t\treturn unicode.IsControl(r) || unicode.IsSpace(r)\n\t\t\t})\n\t\t\tif len(trim_str) > 0 {\n\t\t\t\texcerpt_strings = append(excerpt_strings, trim_str)\n\t\t\t}\n\t\t}\n\t\tfor c := n.FirstChild; c != nil; c = c.NextSibling {\n\t\t\tf(c)\n\t\t}\n\t}\n\n\tf(doc)\n\texcerpt = strings.Join(excerpt_strings, \" \")\n\texcerpt = html.UnescapeString(excerpt)\n\treturn\n}\n\nfunc trimExcerpt(s string, l int, wordWrap, addEllipsis bool) string {\n\n\ttrimmed := truncate_unicode(s, l)\n\n\tif wordWrap {\n\t\tlast_valid, width := lastIndexPunctuation(trimmed)\n\t\tif last_valid != len(trimmed) && last_valid != -1 { //punctuation found, but not at end of string\n\t\t\ttrimmed = trimmed[:last_valid-width]\n\t\t}\n\t}\n\n\tif addEllipsis {\n\t\tif len(trimmed) < len(s) {\n\t\t\treturn trimmed + \"…\"\n\t\t}\n\t}\n\treturn trimmed\n\n}\n\nfunc truncate_unicode(s string, l int) string {\n\tif l == 0 {\n\t\treturn \"\"\n\t}\n\trunesCount := utf8.RuneCountInString(s)\n\tif runesCount <= l {\n\t\treturn s\n\t}\n\trunesString := []rune(s)\n\n\treturn string(runesString[:l])\n\n}\n\nfunc lastIndexPunctuation(s string) (index, width int) {\n\n\tfor i, w := len(s), 0; i > 0; i -= w {\n\t\truneValue, width := utf8.DecodeLastRuneInString(s[:i])\n\t\tif unicode.IsPunct(runeValue) || unicode.IsSpace(runeValue) {\n\t\t\treturn i, width\n\t\t}\n\t\tw = width\n\t}\n\treturn -1, 0\n}\n\nfunc replaceBodyTag(in string) (out string) {\n\n\tbodyStart := strings.NewReplacer(`<body`, `<div class=\"caliopen-email\"`)\n\tout = bodyStart.Replace(in)\n\tbodyEnd := strings.NewReplacer(`/body>`, `/div>`)\n\tout = bodyEnd.Replace(out)\n\n\treturn out\n}\n"
  },
  {
    "path": "src/backend/main/go.main/pi/identities.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage pi\n\nimport (\n\t\"context\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"time\"\n)\n\nfunc UpdatePIContactIdentity(ctx context.Context, contact *Contact, identity *ContactIdentity) {\n\t// TODO: real update. But how to do it ???\n\t// ctx should be use to pass context information to this calculator\n\t// few computations below are just for demonstration\n\n\tif contact.PrivacyIndex != nil {\n\t\tidentity.PrivacyIndex.Comportment = contact.PrivacyIndex.Comportment\n\t\tidentity.PrivacyIndex.Context = contact.PrivacyIndex.Context\n\t\tidentity.PrivacyIndex.Technic = contact.PrivacyIndex.Technic\n\t} else {\n\t\tidentity.PrivacyIndex = PrivacyIndex{}\n\t}\n\tswitch identity.Protocol {\n\tcase \"email\":\n\t\tidentity.PrivacyIndex.Technic--\n\tcase \"telephone\":\n\t\tidentity.PrivacyIndex.Technic++\n\tcase \"im\":\n\t\tidentity.PrivacyIndex.Technic -= 2\n\t}\n\n\tidentity.PrivacyIndex.DateUpdate = time.Now()\n}\n"
  },
  {
    "path": "src/backend/main/go.main/pi/message.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage pi\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// ComputePIMessage returns estimated PIMessage values\nfunc ComputePIMessage(message *Message) *PIMessage {\n\tpiMessage := &PIMessage{Transport: 0, Social: 0, Content: 0}\n\tfeatures := *message.Privacy_features\n\t// pi.Content\n\tif encrypted, ok := features[\"messsage_encryption_method\"]; ok && encrypted != \"\" {\n\t\tpiMessage.Content += 50\n\t}\n\tsigned, ok := features[\"message_signed\"]\n\t// TODO : have a correct unmarshalling of typed features\n\tif ok && len(signed) > 0 && strings.ToLower(string(signed[0])) == \"t\" {\n\t\tpiMessage.Content += 20\n\t}\n\n\t// pi.Social\n\tknown_participants := 0.0\n\tfor _, participant := range message.Participants {\n\t\tif len(participant.Contact_ids) > 0 {\n\t\t\tknown_participants += 1\n\t\t}\n\t}\n\tif known_participants > 1 {\n\t\t// user contact is included\n\t\tknown_participants -= 1\n\t}\n\tratio := known_participants / float64(len(message.Participants)) * 100\n\tpiMessage.Social += uint32(math.Min(80, ratio))\n\n\t// pi.Transport\n\ttrSigned, ok := features[\"transport_signed\"]\n\t// TODO : have a correct unmarshalling of typed features\n\tif ok && len(trSigned) > 0 && strings.ToLower(string(trSigned[0])) == \"t\" {\n\t\tpiMessage.Transport += 20\n\t}\n\tnbHops, ok := features[\"nb_external_hops\"]\n\tif ok {\n\t\tvalue, err := strconv.Atoi(nbHops)\n\t\tif err == nil && value <= 5 {\n\t\t\tbump := float64((-5 * value) + 25.0)\n\t\t\tpiMessage.Transport += uint32(math.Max(0, bump))\n\t\t}\n\t}\n\tinCipher, ok := features[\"ingress_cipher\"]\n\tif ok && inCipher != \"\" {\n\t\tpiMessage.Transport += 20\n\t}\n\t// TODO : normalize\n\treturn piMessage\n}\n"
  },
  {
    "path": "src/backend/main/go.main/users/oauth2.go",
    "content": "package users\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/Sirupsen/logrus\"\n\t\"golang.org/x/oauth2\"\n\tgoogleOAuth2 \"golang.org/x/oauth2/google\"\n\t\"time\"\n)\n\nconst (\n\tCALLBACK_BASE_URI  = \"/api/v2/providers/%s/callback\"\n\tCRED_ACCESS_TOKEN  = \"oauth2accesstoken\"\n\tCRED_REFRESH_TOKEN = \"oauh2refreshtoken\"\n\tCRED_TOKEN_TYPE    = \"tokentype\"\n\tCRED_TOKEN_EXPIRY  = \"tokenexpiry\"\n\tCRED_USERNAME      = \"username\"\n)\n\ntype Oauth2Interfacer interface {\n\tGetProviders() map[string]Provider\n\tGetHostname() string\n\tGetIdentityStore() backends.IdentityStorageUpdater\n}\n\n// ValidateOauth2Credentials wraps methods to check Oauth2 access token validity for different providers.\n// If oauth2 access token has expired it's renewed using oauth2 refresh token.\n// New access token is embedded in userIdentity, and stored in db if updateStore is set to true.\nfunc ValidateOauth2Credentials(userIdentity *UserIdentity, interfacer Oauth2Interfacer, updateStore bool) CaliopenError {\n\tswitch userIdentity.Infos[\"provider\"] {\n\tcase \"gmail\":\n\t\tgmail, gotProvider := interfacer.GetProviders()[\"gmail\"]\n\t\tif !gotProvider {\n\t\t\treturn NewCaliopenErr(FailDependencyCaliopenErr, \"failed to find gmail provider params in providers map\")\n\t\t}\n\t\tcredentialsUpdated, err := getValidGmailAccessToken(userIdentity, gmail, interfacer.GetHostname())\n\t\tif err != nil {\n\t\t\treturn WrapCaliopenErr(err, WrongCredentialsErr, \"failed to get valid gmail access token\")\n\t\t}\n\t\tif credentialsUpdated && updateStore {\n\t\t\tcredentials := userIdentity.Credentials\n\t\t\tstore := interfacer.GetIdentityStore()\n\t\t\terr := store.UpdateUserIdentity(userIdentity, map[string]interface{}{\n\t\t\t\t\"Credentials\": userIdentity.Credentials,\n\t\t\t})\n\t\t\t// re-embed credentials in userIdentity because store.UpdateUserIdentity has removed it from UserIdentity struct\n\t\t\tuserIdentity.Credentials = credentials\n\t\t\tif err != nil {\n\t\t\t\treturn WrapCaliopenErr(err, WrongCredentialsErr, \"imapLogin failed to update access token in store\")\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn NewCaliopenErrf(FailDependencyCaliopenErr, \"unhandled oauth2 provider <%v>\", interfacer.GetProviders()[userIdentity.Infos[\"provider\"]])\n\t}\n\treturn nil\n}\n\n/* Google services */\n\nfunc SetGoogleAuthRequestUrl(provider *Provider, hostname string) (state string, err error) {\n\n\tprovider.OauthCallbackUri = fmt.Sprintf(CALLBACK_BASE_URI, \"gmail\")\n\n\tconfig := SetGoogleOauthConfig(*provider, hostname)\n\n\tstate = randomState()\n\n\tprovider.OauthRequestUrl = config.AuthCodeURL(state, oauth2.AccessTypeOffline)\n\treturn\n}\n\nfunc SetGoogleOauthConfig(provider Provider, hostname string) *oauth2.Config {\n\treturn &oauth2.Config{\n\t\tClientID:     provider.Infos[\"client_id\"],\n\t\tClientSecret: provider.Infos[\"client_secret\"],\n\t\tEndpoint:     googleOAuth2.Endpoint,\n\t\tRedirectURL:  hostname + provider.OauthCallbackUri,\n\t\tScopes:       []string{\"profile\", \"email\", \"https://mail.google.com/\"},\n\t}\n}\n\nfunc SetMastodonOauthConfig(provider Provider, hostname string) *oauth2.Config {\n\treturn &oauth2.Config{\n\t\tClientID:     provider.Infos[\"client_id\"],\n\t\tClientSecret: provider.Infos[\"client_secret\"],\n\t\tEndpoint: oauth2.Endpoint{\n\t\t\tAuthURL:   provider.Infos[\"address\"] + \"/oauth/authorize\",\n\t\t\tTokenURL:  provider.Infos[\"address\"] + \"/oauth/token\",\n\t\t\tAuthStyle: oauth2.AuthStyleInParams,\n\t\t},\n\t\tRedirectURL: hostname + provider.OauthCallbackUri,\n\t\tScopes:      []string{\"read\", \"write\", \"follow\"},\n\t}\n}\n\n// GetValidGmailAccessToken checks identity's access token validity.\n// If token has expired a new one is retrieved by the mean of refresh token,\n// new credentials are embedded in user identity and credentialsUpdated is set to true.\n// UserIdentity MUST carry identity's credentials. It's caller responsibility to store new credentials in db.\nfunc getValidGmailAccessToken(uId *UserIdentity, provider Provider, hostname string) (credentialsUpdated bool, err error) {\n\tif uId.Credentials == nil {\n\t\terr = errors.New(\"[GetValidGmailAccessToken] missing credentials in user identity\")\n\t\treturn\n\t}\n\n\texpiry, err := time.Parse(time.RFC3339, (*uId.Credentials)[CRED_TOKEN_EXPIRY])\n\tif err != nil {\n\t\tlogrus.Error(err)\n\t}\n\trestoredToken := &oauth2.Token{\n\t\tAccessToken:  (*uId.Credentials)[CRED_ACCESS_TOKEN],\n\t\tTokenType:    (*uId.Credentials)[CRED_TOKEN_TYPE],\n\t\tRefreshToken: (*uId.Credentials)[CRED_REFRESH_TOKEN],\n\t\tExpiry:       expiry,\n\t}\n\t//logrus.Infof(\"restoredToken : %+v\\n\\n\", restoredToken)\n\tif restoredToken.Expiry.IsZero() || !restoredToken.Valid() {\n\t\t// need a new token\n\t\toauthConfig := SetGoogleOauthConfig(provider, hostname)\n\t\tctx := context.TODO()\n\t\t//logrus.Infof(\"oauthConfig : %+v\\n\\n\", oauthConfig)\n\t\ttokenSource := oauthConfig.TokenSource(ctx, restoredToken)\n\t\tupdatedToken, tokenErr := tokenSource.Token()\n\t\tif tokenErr != nil {\n\t\t\tlogrus.Errorf(\"[getValidGmailAccessToken]TokenSource error : %+v\", tokenErr)\n\t\t\terr = tokenErr\n\t\t\treturn\n\t\t}\n\t\t(*uId.Credentials)[CRED_ACCESS_TOKEN] = updatedToken.AccessToken\n\t\t(*uId.Credentials)[CRED_REFRESH_TOKEN] = updatedToken.RefreshToken\n\t\t(*uId.Credentials)[CRED_TOKEN_EXPIRY] = updatedToken.Expiry.Format(time.RFC3339)\n\t\t(*uId.Credentials)[CRED_TOKEN_TYPE] = updatedToken.TokenType\n\t\tcredentialsUpdated = true\n\t\treturn\n\t}\n\treturn\n}\n\n/* Mastodon API */\n\nfunc SetMastodonAuthRequestUrl(provider *Provider, hostname string) (state string, err error) {\n\n\tstate = randomState()\n\tprovider.OauthCallbackUri = fmt.Sprintf(CALLBACK_BASE_URI, \"mastodon\")\n\n\tconfig := SetMastodonOauthConfig(*provider, hostname)\n\n\tprovider.OauthRequestUrl = config.AuthCodeURL(state, oauth2.AccessTypeOffline)\n\treturn\n}\n\n// Returns a base64 encoded random 32 byte string.\nfunc randomState() string {\n\tb := make([]byte, 32)\n\trand.Read(b)\n\treturn base64.RawURLEncoding.EncodeToString(b)\n}\n\n/* end of Google services*/\n"
  },
  {
    "path": "src/backend/main/go.main/users/password.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage users\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/nbutton23/zxcvbn-go\"\n\t\"github.com/tidwall/gjson\"\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\nconst (\n\tdefaultBcryptCost   = 12                  // 12 is the default cost of python's bcrypt lib\n\tpasswordStrengthkey = \"password_strength\" // key in privacy_features map\n)\n\nfunc ChangeUserPassword(user *User, patch *gjson.Result, store backends.UserStorage) error {\n\t// verify that current_password in patch is the good one\n\tcurrent_pwd := patch.Get(\"current_state.password\").Str\n\terr := bcrypt.CompareHashAndPassword(user.Password, []byte(current_pwd))\n\tif err != nil {\n\t\treturn errors.New(\"old password is incorrect\")\n\t}\n\n\tnew_password := patch.Get(\"password\").Str\n\n\treturn ResetUserPassword(user, new_password, store)\n}\n\nfunc ResetUserPassword(user *User, new_password string, store backends.UserStorage) error {\n\t//compute new password strength\n\tuser_infos := []string{user.Name, user.GivenName, user.FamilyName, user.RecoveryEmail}\n\tuser_infos = append(user_infos, user.LocalIdentities...)\n\tscoring := zxcvbn.PasswordStrength(new_password, user_infos)\n\t(*user.PrivacyFeatures)[passwordStrengthkey] = strconv.FormatInt(int64(scoring.Score), 10)\n\n\t// hash new password and store it\n\thashpass, err := bcrypt.GenerateFromPassword([]byte(new_password), defaultBcryptCost)\n\t(*user).Password = hashpass\n\terr = store.UpdateUserPasswordHash(user)\n\tif err != nil {\n\t\treturn errors.New(\"[ChangeUserPassword] failed to store updated user : \" + err.Error())\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/main/py.main/CHANGES.rst",
    "content": "0.0.1\n-----\n\n-  Initial version\n\n0.0.2\n-----\n\n- Code refactoring into a single repository. Caliopen platform is made of 3 python packages : caliopen.main, caliopen_storage. and caliopen.server (ie the REST HTTP API)\n"
  },
  {
    "path": "src/backend/main/py.main/MANIFEST.in",
    "content": "include *.cfg *.rst *.template\n"
  },
  {
    "path": "src/backend/main/py.main/README.rst",
    "content": "Entry point\n===========\n\nThis repository is part of CaliOpen platform. For documentation, installation and\ncontribution instructions, please refer to https://caliopen.github.io\n\n\nCaliopen Main package\n=============\n\nThis is the main entry point for whole application.\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n__version__ = '0.23.0'\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/core/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen common core classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .base import BaseUserCore\nfrom .pubkey import PublicKey\nfrom .related import BaseUserRelatedCore\n\n__all__ = ['BaseUserCore', 'PublicKey', 'BaseUserRelatedCore']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/core/base.py",
    "content": "\"\"\"Caliopen core base class for objects belong to an user.\"\"\"\n\nfrom caliopen_storage.core import BaseCore\nfrom caliopen_storage.exception import NotFound\n\n\nclass BaseUserCore(BaseCore):\n    \"\"\"Used by objects related to only one user (most core).\"\"\"\n\n    _user = None\n\n    @property\n    def user(self):\n        \"\"\"Return user related to this object.\"\"\"\n        from caliopen_main.user.core import User\n\n        if not self._user:\n            self._user = User.get(self.user_id)\n        return self._user\n\n    @classmethod\n    def get(cls, user, obj_id):\n        \"\"\"Get a core object belong to user, with model related id.\"\"\"\n        param = {cls._pkey_name: obj_id}\n        obj = cls._model_class.get(user_id=user.user_id, **param)\n        if obj:\n            return cls(obj)\n        raise NotFound('%s #%s not found for user %s' %\n                       (cls.__class__.name, obj_id, user.user_id))\n\n    @classmethod\n    def get_by_user_id(cls, user_id, obj_id):\n        \"\"\"Get a core object belong to user, with model related id.\"\"\"\n        param = {cls._pkey_name: obj_id}\n        obj = cls._model_class.get(user_id=user_id, **param)\n        if obj:\n            return cls(obj)\n        raise NotFound('%s #%s not found for user %s' %\n                       (cls.__class__.name, obj_id, user_id))\n\n    @classmethod\n    def find(cls, user, filters=None, limit=None, offset=0, count=False):\n        \"\"\"\n        Find core objects that belong to an user.\n\n        can only use columns part of primary key\n        \"\"\"\n        q = cls._model_class.filter(user_id=user.user_id)\n        if not filters:\n            objs = q\n        else:\n            objs = q.filter(**filters)\n        if count:\n            return objs.count()\n        if limit or offset:\n            objs = objs[offset:(limit + offset)]\n\n        return {'objects': [cls(x) for x in objs], 'total': len(q)}\n\n    @classmethod\n    def count(cls, user, filters=None):\n        \"\"\"Count core objects that belong to an user.\"\"\"\n        return cls.find(user, filters, count=True)\n\n    @classmethod\n    def create(cls, user, **attrs):\n        \"\"\"Create a core object belong to an user.\"\"\"\n        obj = cls._model_class.create(user_id=user.user_id, **attrs)\n        return cls(obj)\n\n    @classmethod\n    def belongs_to_user(cls, user_id, object_id):\n        \"\"\"Test if an object belong to an user.\"\"\"\n        param = {cls._pkey_name: object_id}\n        obj = cls._model_class.get(user_id=user_id, **param)\n        if obj:\n            return True\n        return False\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/core/pubkey.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen public key core classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport uuid\n\nfrom caliopen_main.common.store import PublicKey as ModelPublicKey\nfrom caliopen_main.common.core.related import BaseUserRelatedCore\n\n\nclass PublicKey(BaseUserRelatedCore):\n    \"\"\"Public key core class.\"\"\"\n\n    _model_class = ModelPublicKey\n    _pkey_name = 'key_id'\n\n    @classmethod\n    def find(cls, user, resource_id):\n        \"\"\"Get public keys for an user and a resource.\"\"\"\n        models = cls._model_class.filter(user_id=user.user_id,\n                                         resource_id=resource_id)\n        for m in models:\n            yield cls(m)\n\n    @classmethod\n    def create(cls, user, resource_id, resource_type, **kwargs):\n        \"\"\"Create a new public key related to an user and a resource.\"\"\"\n        if 'key_id' not in kwargs:\n            kwargs['key_id'] = uuid.uuid4()\n        obj = cls._model_class.create(user_id=user.user_id,\n                                      resource_id=resource_id,\n                                      resource_type=resource_type,\n                                      **kwargs)\n        return cls(obj)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/core/related.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen core related classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\n\nfrom caliopen_storage.core import BaseCore\n\nlog = logging.getLogger(__name__)\n\n\nclass BaseUserRelatedCore(BaseCore):\n    \"\"\"Core class for related objects to an user and another entity.\"\"\"\n\n    _pkey_name = None   # to be defined in real class\n\n    @classmethod\n    def create(cls, user, resource_id, **kwargs):\n        \"\"\"Create a related user and resource entity.\"\"\"\n        obj = cls._model_class.create(user_id=user.user_id,\n                                      resource_id=resource_id,\n                                      **kwargs)\n        return cls(obj)\n\n    @classmethod\n    def get(cls, user, resource_id, value):\n        \"\"\"Get a related entity.\"\"\"\n        kwargs = {'user_id': user.user_id,\n                  'resource_id': resource_id,\n                  cls._pkey_name: value}\n        try:\n            obj = cls._model_class.get(**kwargs)\n            return cls(obj)\n        except Exception as exc:\n            log.exception('Unexpected error during retrieve of resource %s'\n                          % exc)\n            return None\n\n    @classmethod\n    def find(cls, user, resource_id, filters=None):\n        \"\"\"Find related object for an user and an given resource.\"\"\"\n        filters = filters if filters else {}\n        filters.update({'user_id': user.user_id,\n                        'resource_id': resource_id})\n        q = cls._model_class.filter(**filters)\n        if not filters:\n            objs = q\n        else:\n            objs = q.filter(**filters)\n        return {'total': len(objs), 'data': [cls(x) for x in objs]}\n\n    def to_dict(self):\n        \"\"\"Return a dict representation.\"\"\"\n        return {col: getattr(self, col)\n                for col in self._model_class._columns.keys()}\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/errors.py",
    "content": "class PatchUnprocessable(Exception):\n    \"\"\"Exception when patch dict is malformed or unprocessable.\"\"\"\n\n    def __init__(self, message=None, **kw):\n        Exception.__init__(self, message, **kw)\n\n\nclass PatchError(Exception):\n    \"\"\"Exception when processing patch was unsuccessfull\"\"\"\n\n    def __init__(self, message=None, **kw):\n        Exception.__init__(self, message, **kw)\n\n\nclass PatchConflict(Exception):\n    \"\"\"Exception when processing patch was unsuccessfull\"\"\"\n\n    def __init__(self, message=None, **kw):\n        Exception.__init__(self, message, **kw)\n\n\nclass ObjectInitFailed(Exception):\n    \"\"\"Exception when __init__ func failed to process object initialization\"\"\"\n\n    def __init__(self, message=None, **kw):\n        Exception.__init__(self, message, **kw)\n\n\nclass ForbiddenAction(Exception):\n    \"\"\" Exception when an user tries to do something forbidden\n    \n        because of insufficient rights\n        or because action is not allowed on a specific object or attribute\n    \"\"\"\n\n    def __init__(self, message=None, **kw):\n        Exception.__init__(self, message, **kw)\n\n\nclass DuplicateMessage(Exception):\n    \"\"\"Exception when processing ingress messages already imported for user\"\"\"\n\n    def __init__(self, message=None, **kw):\n        Exception.__init__(self, message, **kw)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/helpers/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/helpers/normalize.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Normalization functions for different values.\"\"\"\nfrom __future__ import absolute_import, unicode_literals\nimport re\nimport logging\nfrom email.utils import parseaddr\n\nlog = logging.getLogger(__name__)\n\nmastodon_url_regex = '^https:\\/\\/(.*)\\/@(.*)'\nmastodon_url_legacy_regex = '^https:\\/\\/(.*)\\/users\\/(.*)'\n\n\ndef clean_email_address(addr):\n    \"\"\"Clean an email address for user resolve.\"\"\"\n    try:\n        real_name, email = parseaddr(addr.replace('\\r', ''))\n    except UnicodeError:\n        addr = addr.decode('utf-8', errors='ignore')\n        real_name, email = parseaddr(addr.replace('\\r', ''))\n    err_msg = 'Invalid email address {}'.format(addr)\n    if not email or '@' not in email:\n        # Try something else\n        log.info('Last chance email parsing for {}'.format(addr))\n        matches = re.match('(.*)<(.*@.*)>', addr)\n        if matches and matches.groups():\n            real_name, email = matches.groups()\n        else:\n            log.warn(err_msg)\n            return (\"\", \"\")\n    name, domain = email.lower().split('@', 1)\n    if '@' in domain:\n        log.error(err_msg)\n        return (\"\", \"\")\n    if '+' in name:\n        try:\n            name, ext = name.split('+', 1)\n        except Exception as exc:\n            log.info(exc)\n    # unicode everywhere\n    return (u'%s@%s' % (name, domain), email)\n\n\ndef clean_twitter_address(addr):\n    return addr.strip('@').lower()\n\n\ndef clean_mastodon_address(addr):\n    return addr.strip('@').lower().split('@')\n\n\ndef parse_mastodon_url(url):\n    \"\"\"extract username and domain from a mastodon account url\n    in the format https://instance.tld/@username\n\n    :return: tuple (server, username)\n    \"\"\"\n\n    matches = re.findall(mastodon_url_regex, url)\n    if len(matches) != 1 or len(matches[0]) != 2:\n        # try legacy fallback\n        matches = re.findall(mastodon_url_legacy_regex, url)\n        if len(matches) != 1 or len(matches[0]) != 2:\n            raise SyntaxError\n\n    return matches[0]\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/helpers/strings.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"helpers to work with strings\"\"\"\nfrom __future__ import absolute_import, unicode_literals\nimport re\n\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\ndef unicode_truncate(s, length):\n    \"\"\"\"Truncate string after `length` bytes less trailing unicode char.\"\"\"\n    if isinstance(s, str):\n        s = s.decode(\"utf8\", 'ignore')\n\n    partial = s[:length]\n    return re.sub(\"([\\xf6-\\xf7][\\x80-\\xbf]{0,2}|[\\xe0-\\xef][\\x80-\\xbf]{0,1}\"\n                  \"|[\\xc0-\\xdf])$\", \"\", partial)\n\n\ndef to_utf8(input, charset):\n    \"\"\"Convert input string to utf-8 return input string if it fails.\n\n    :param input: string\n    :param charset: string\n    :return: utf-8 string\n    \"\"\"\n    if charset:\n        matches = re.match('^charset.*\"(.*)\"', charset)\n        if matches and matches.groups():\n            charset = matches.groups()[0]\n        try:\n            return input.decode(charset, \"replace\"). \\\n                encode(\"utf-8\", \"replace\")\n        except UnicodeError as exc:\n            log.warn(\"decoding <{}> string to utf-8 failed \"\n                     \"with error : {}\".format(input, exc))\n            return input\n    else:\n        try:\n            return input.decode(\"us-ascii\", \"replace\"). \\\n                encode(\"utf-8\", \"replace\")\n        except Exception as exc:\n            log.warn(\"decoding <{}> string to utf-8 failed \"\n                     \"with error : {}\".format(bytes(input), exc))\n            return input\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/interfaces/IO.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen IO interfaces definitions.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport zope.interface\n\n\"\"\"\nmarshall functions transform a core object to something expected by counterpart\nunMarshall func transform something coming from counterpart to a core object\n\"\"\"\n\n\nclass JsonDictIO(zope.interface.Interface):\n    \"\"\"json dict is a dict ready to be serialized into a json\n\n    ie : attr values are only str, int, bool or None\n    \"\"\"\n\n    def marshall_json_dict(**options):\n        raise NotImplementedError\n\n    def unmarshall_json_dict(**options):\n        raise NotImplementedError\n\n\nclass ProtobufIO(zope.interface.Interface):\n    \"\"\"IO between caliopen's objects and protobuf objects\"\"\"\n\n    def marshall_protobuf(**options):\n        raise NotImplementedError\n\n    def unmarshall_protobuf(message, **options):\n        raise NotImplementedError\n\n\nclass DictIO(zope.interface.Interface):\n    def marshall_dict(**options):\n        raise NotImplementedError\n\n    def unmarshall_dict(document, **options):\n        raise NotImplementedError\n\n\nclass JsonIO(zope.interface.Interface):\n    \"\"\"json is an array of bytes\"\"\"\n\n    def marshall_json(**options):\n        raise NotImplementedError\n\n    def unmarshall_json(document, **options):\n        raise NotImplementedError\n\n\n\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/interfaces/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen main interfaces definitions.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .IO import JsonDictIO, ProtobufIO, DictIO, JsonIO\nfrom .storage import DbIO, IndexIO\n\nfrom .parser import IAttachmentParser, IMessageParser, IParticipantParser\n\n__all__ = ['JsonDictIO', 'ProtobufIO', 'DictIO', 'JsonIO',\n           'DbIO', 'IndexIO', 'IAttachmentParser', 'IMessageParser',\n           'IParticipantParser']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/interfaces/parser.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen base classes for parsing logic.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport zope.interface\n\n\nclass IAttachmentParser(zope.interface.Interface):\n    \"\"\"Interface for a message attachment parsing.\"\"\"\n\n    content_type = zope.interface.Attribute('Attachment MIME content type')\n    filename = zope.interface.Attribute('Filename if any')\n    data = zope.interface.Attribute('Attachment data')\n    size = zope.interface.Attribute('Attachment size')\n    charset = zope.interface.Attribute('Attachment charset')\n    is_inline = zope.interface.Attribute('Is inline')\n    mime_boundary = zope.interface.Attribute('MIME boundary value')\n\n\nclass IParticipantParser(zope.interface.Interface):\n    \"\"\"Interface for a message participant parsing.\"\"\"\n\n    type = zope.interface.Attribute('Participant role type')\n    address = zope.interface.Attribute('Participant address')\n    label = zope.interface.Attribute('Participant label')\n\n\nclass IMessageParser(zope.interface.Interface):\n    \"\"\"Interface for all message parsers.\"\"\"\n\n    message_protocol = zope.interface.Attribute('Type of message')\n\n    raw = zope.interface.Attribute('Raw message')\n\n    subject = zope.interface.Attribute('Message subject if any')\n    date = zope.interface.Attribute('Message date')\n    size = zope.interface.Attribute('Message size in bytes')\n    body_html = zope.interface.Attribute('Message html body')\n    body_plain = zope.interface.Attribute('Message plain txt body')\n\n    participants = zope.interface.Attribute('List of participants')\n    attachments = zope.interface.Attribute('List of attachments')\n\n    external_references = zope.interface.Attribute('External references')\n    extra_parameters = zope.interface.Attribute('Extra parameters')\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/interfaces/storage.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen storage interfaces definitions.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport zope.interface\n\n\nclass DbIO(zope.interface.Interface):\n    \"\"\"Interface for objects persisted in cassandra\"\"\"\n\n    def get_db(**options):\n        \"\"\"Retreive object from db and place it in a _model_class instance\"\"\"\n        raise NotImplementedError\n\n    def save_db(**options):\n        \"\"\"\"\"\"\n        raise NotImplementedError\n\n    def create_db(**options):\n        \"\"\"\"\"\"\n        raise NotImplementedError\n\n    def delete_db(**options):\n        \"\"\"\"\"\"\n        raise NotImplementedError\n\n    def update_db(**options):\n        \"\"\"Update values within _model_class from object values and save them\"\"\"\n        raise NotImplementedError\n\n    def marshall_db(**options):\n        \"\"\"Create a _model_class instance with current object attributes\n\n        For now, we rely on cassandra's Encoder.\n        We could customize this marshaller in future if needed\n        \"\"\"\n        raise NotImplementedError\n\n    def unmarshall_db(**options):\n        \"\"\"Fill object attributes with values from _db\"\"\"\n        raise NotImplementedError\n\n\nclass IndexIO(zope.interface.Interface):\n    \"\"\"Interface for objects indexed in Elasticsearch\"\"\"\n\n    def marshall_index(**options):\n        \"\"\"Create a _index_class instance with current object attributes\"\"\"\n        raise NotImplementedError\n\n    def unmarshall_index(**options):\n        \"\"\"Fill object's attributes with values from _index\"\"\"\n        raise NotImplementedError\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/objects/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/objects/base.py",
    "content": "import zope.interface\n\nimport types\nimport uuid\nimport datetime\nimport pytz\nfrom six import add_metaclass\n\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_main.common.errors import PatchConflict, PatchUnprocessable, \\\n    PatchError\nfrom caliopen_main.common.errors import ForbiddenAction\nfrom caliopen_main.common.interfaces import (IO, storage)\nfrom elasticsearch import exceptions as ESexceptions\n\nfrom caliopen_storage.core.base import CoreMetaClass\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\nclass CaliopenObject(object):\n    \"\"\"\n    Empty class to identify Caliopen objects/types.\n\n    all custom classes should inherit from that\n    \"\"\"\n\n    _attrs = {}\n\n    def __init__(self, **kwargs):\n        # TODO: type check and kwargs consistency check\n        for k, v in kwargs.items():\n            if k in self._attrs:\n                if isinstance(self._attrs[k], list):\n                    setattr(self, k, [])\n                    att_list = getattr(self, k)\n                    if isinstance(v, list):\n                        for item in v:\n                            if issubclass(self._attrs[k][0], CaliopenObject):\n                                att_list.append(self._attrs[k][0](**item))\n                            else:\n                                att_list.append(item)\n                    else:\n                        if issubclass(self._attrs[k][0], CaliopenObject):\n                            att_list.append(self._attrs[k][0](**v))\n                        else:\n                            att_list.append(v)\n                elif issubclass(self._attrs[k], CaliopenObject):\n                    setattr(self, k, self._attrs[k][0](v))\n                else:\n                    setattr(self, k, v)\n\n        for attr, attrtype in self._attrs.items():\n            if not hasattr(self, attr):\n                if isinstance(attrtype, list):\n                    setattr(self, attr, [])\n                elif isinstance(attrtype, dict):\n                    setattr(self, attr, {})\n                else:\n                    setattr(self, attr, None)\n\n    def keys(self):\n        \"\"\"returns a list of current attributes\"\"\"\n\n        return [k for k in self._attrs if hasattr(self, k)]\n\n    def update_with(self, sibling):\n        \"\"\"update self attributes with those from sibling\n\n\n        returns a list of first level attributes that have been modified\n        \"\"\"\n        pass\n\n\nclass ObjectDictifiable(CaliopenObject):\n    \"\"\"Object that can marshall/unmarshall to/from python dict\"\"\"\n    zope.interface.implements(IO.DictIO)\n\n    def marshall_dict(self, **options):\n        \"\"\"output a dict representation of self 'public' attributes\"\"\"\n\n        self_dict = {}\n        for att, val in vars(self).items():\n            if not att.startswith(\"_\") and val is not None:\n                if isinstance(self._attrs[att], types.ListType):\n                    lst = []\n                    if len(att) > 0:\n                        if issubclass(self._attrs[att][0], ObjectDictifiable):\n                            for item in val:\n                                lst.append(item.marshall_dict())\n                        else:\n                            lst = val\n                        self_dict[att] = lst\n                    else:\n                        self_dict[att] = lst\n                elif issubclass(self._attrs[att], ObjectDictifiable):\n                    self_dict[att] = val.marshall_dict()\n                else:\n                    self_dict[att] = val\n\n        return self_dict\n\n    def unmarshall_dict(self, document, **options):\n        \"\"\"squash self.attrs with dict input document\n\n        all self.attrs are reset if not in document\n        \"\"\"\n        for attr, attrtype in self._attrs.items():\n            if attr in document and document[attr] is not None:\n                unmarshall_item(document, attr, self, attrtype,\n                                is_creation=False)\n            else:\n                if isinstance(attrtype, types.ListType):\n                    setattr(self, attr, [])\n                elif issubclass(attrtype, types.DictType):\n                    setattr(self, attr, {})\n                elif issubclass(attrtype, types.BooleanType):\n                    setattr(self, attr, False)\n                elif issubclass(attrtype, types.StringType):\n                    setattr(self, attr, \"\")\n                elif issubclass(attrtype, types.IntType):\n                    setattr(self, attr, 0)\n                else:\n                    setattr(self, attr, None)\n\n\nclass ObjectJsonDictifiable(ObjectDictifiable):\n    \"\"\"Object can marshall/unmarshall to/from python json compatible dict\n\n    A json compatible dict is a dict with only 4 value types :\n                                                                str\n                                                                num\n                                                                bool\n                                                                None\n    We rely on schematics to do the job from our object's 'public' attributes\n    \"\"\"\n\n    zope.interface.implements(IO.JsonDictIO)\n\n    _json_model = None\n\n    def marshall_json_dict(self, **options):\n        d = self.marshall_dict()\n        return self._json_model(d).serialize()\n\n    def unmarshall_json_dict(self, document, **options):\n        \"\"\" TODO: handle conversion of basic json type into obj. types\"\"\"\n\n        # validate document against json_model before trying to unmarshal\n        valid_doc = self._json_model(document)\n        try:\n            valid_doc.validate()\n        except Exception as exc:\n            log.warn(\"document validation failed with error {}\".format(exc))\n            raise exc\n\n        self.unmarshall_dict(document, **options)\n\n\n@add_metaclass(CoreMetaClass)\nclass ObjectStorable(ObjectJsonDictifiable):\n    zope.interface.implements(storage.DbIO)\n\n    _model_class = None  # cql model for object\n    _db = None  # cql model instance\n    _pkey_name = None  # name of primary key in cassandra\n    _relations = None  # related tables into cassandra\n    _lookup_class = None  #\n    _lookup_values = None  # tables keys, values for lookups\n\n    def get_db(self, **options):\n        \"\"\"Get a core object from database and put it in self._db attribute\"\"\"\n        if self._pkey_name:\n            param = {\n                self._pkey_name: getattr(self, self._pkey_name)\n            }\n        else:\n            param = {}\n        self._db = self._model_class.get(**param)\n        if self._db is None:\n            raise NotFound('%s #%s not found.' %\n                           (self.__class__.__name__,\n                            getattr(self, self._pkey_name)))\n\n    def save_db(self, **options):\n        try:\n            self._db.save()\n        except Exception as exc:\n            log.exception(exc)\n            return exc\n\n        return None\n\n    def delete_db(self, **options):\n        try:\n            self._db.delete()\n        except Exception as exc:\n            log.exception(exc)\n            return exc\n\n        return None\n\n    def update_db(self, **options):\n        \"\"\"push updated model into db\"\"\"\n\n        try:\n            self._db.update()\n        except Exception as exc:\n            log.exception(exc)\n            return exc\n\n        return None\n\n    def marshall_db(self, **options):\n        \"\"\"squash self._db with self 'public' attributes\n\n        self._db being a cqlengine Model, we can (re)set attributes one\n        by one, cqlengine will do the job of changes logging to later make a\n        smart update into the db\n        \"\"\"\n\n        if not isinstance(self._db, self._model_class):\n            self._db = self._model_class()\n\n        self_keys = self._attrs.keys()\n        for att in self._db.keys():\n            if not att.startswith(\"_\") and att in self_keys:\n                # TODO : manage protected attrs\n                # (ie attributes that user should not be able to change)\n                if isinstance(self._attrs[att], list):\n                    # TODO : manage change within list to only elem changed\n                    # (use builtin set() collection ?)\n                    if issubclass(self._attrs[att][0], CaliopenObject):\n                        setattr(self._db, att,\n                                [self._attrs[att][0]._model_class(\n                                    **x.marshall_dict())\n                                    for x in getattr(self, att)])\n                    else:\n                        setattr(self._db, att, getattr(self, att))\n                else:\n                    if issubclass(self._attrs[att], datetime.datetime) and \\\n                            getattr(self, att) is not None:\n                        # datetime in cqlengine are 'naive', ours are 'aware'\n                        setattr(self._db, att,\n                                getattr(self, att).replace(tzinfo=None))\n                    else:\n                        self_att = self._attrs[att]\n                        get_att = getattr(self, att)\n                        if issubclass(self_att, CaliopenObject):\n                            if get_att is not None:\n                                setattr(self._db, att, self_att._model_class(\n                                    **get_att.marshall_dict()))\n                        else:\n                            setattr(self._db, att, get_att)\n\n    def unmarshall_db(self, **options):\n        \"\"\"squash self.attrs with db representation\"\"\"\n\n        if isinstance(self._db, self._model_class):\n            self.unmarshall_dict(dict(self._db))\n        else:\n            log.warn('Invalid model class, expect {}, have {}'.\n                     format(self._db.__class__, self._model_class.__class__))\n\n    def set_uuid(self):\n        setattr(self, self._model_class._pkey, uuid.uuid4())\n\n\nclass ObjectUser(ObjectStorable):\n    \"\"\"Objects that MUST belong to a user to survive in Caliopen's world...\"\"\"\n\n    def __init__(self, user=None, **params):\n        self.user = user\n        if user:\n            self.user_id = user.user_id\n        else:\n            self.user_id = None\n        super(ObjectUser, self).__init__(**params)\n\n    def marshall_dict(self, **options):\n        \"\"\"output a dict representation of self 'public' attributes\"\"\"\n\n        self_dict = {}\n        for att, val in vars(self).items():\n            if not att.startswith(\"_\") and val is not None and att != 'user':\n                if isinstance(self._attrs[att], types.ListType):\n                    lst = []\n                    if len(att) > 0:\n                        if issubclass(self._attrs[att][0], ObjectDictifiable):\n                            for item in val:\n                                lst.append(item.marshall_dict())\n                        else:\n                            lst = val\n                        self_dict[att] = lst\n                    else:\n                        self_dict[att] = lst\n                elif issubclass(self._attrs[att], ObjectDictifiable):\n                    self_dict[att] = val.marshall_dict()\n                else:\n                    self_dict[att] = val\n\n        return self_dict\n\n    @classmethod\n    def list_db(cls, user):\n        \"\"\"List all objects that belong to an user.\"\"\"\n        models = cls._model_class.filter(user_id=user.user_id)\n        objects = []\n        for model in models:\n            obj = cls(user)\n            obj._db = model\n            obj.unmarshall_db()\n            objects.append(obj)\n        return objects\n\n    def get_db(self, **options):\n        \"\"\"Get an object belonging to an user and put it in self._db attrs\"\"\"\n        if self._pkey_name:\n            param = {\n                self._pkey_name: getattr(self, self._pkey_name)\n            }\n        else:\n            param = {}\n\n        try:\n            self._db = self._model_class.get(user_id=self.user_id, **param)\n        except NotFound:\n            raise NotFound('%s %s not found for user %s' %\n                           (self.__class__.__name__,\n                            param[self._pkey_name], self.user_id))\n\n    def apply_patch(self, patch, **options):\n        \"\"\"\n        Update self attributes with patch rfc7396 and Caliopen's specifications\n        if, and only if, patch is consistent with current obj db instance\n\n        :param patch: json-dict object describing the patch to apply\n                with a \"current_state\" key. see caliopen rfc for explanation\n        :param options: whether patch should be propagated to db and/or index\n        :return: Exception or None\n        \"\"\"\n        if patch is None or \"current_state\" not in patch:\n            raise PatchUnprocessable(message='Invalid patch')\n\n        patch_current = patch.pop(\"current_state\")\n\n        # build 3 siblings : 2 from patch and last one from db\n        obj_patch_new = self.__class__(user_id=self.user_id)\n        obj_patch_old = self.__class__(user_id=self.user_id)\n        try:\n            obj_patch_new.unmarshall_json_dict(patch)\n        except Exception as exc:\n            log.exception(exc)\n            raise PatchUnprocessable(message=\"unmarshall patch \"\n                                             \"error: %r\" % exc)\n        try:\n            obj_patch_old.unmarshall_json_dict(patch_current)\n        except Exception as exc:\n            log.exception(exc)\n            raise PatchUnprocessable(message=\"unmarshall current \"\n                                             \"patch error: %r\" % exc)\n        self.get_db()\n\n        # TODO : manage protected attributes, to prevent patch on them\n        if \"tags\" in patch.keys():\n            raise ForbiddenAction(\n                message=\"patching tags through parent object is forbidden\")\n\n        # check if patch is consistent with db current state\n        # if it is, squash self attributes\n        self.unmarshall_db()\n\n        for key in patch.keys():\n            current_attr = self._attrs[key]\n            try:\n                self._check_key_consistency(current_attr, key,\n                                            obj_patch_old,\n                                            obj_patch_new)\n            except Exception as exc:\n                log.exception(\"key consistency checking failed: {}\".\n                              format(exc))\n                raise exc\n\n            # all controls passed, we can actually set the new attribute\n            create_sub_object = False\n            if key not in patch_current.keys():\n                create_sub_object = True\n            else:\n                if patch_current[key] in (None, [], {}):\n                    create_sub_object = True\n                if isinstance(patch_current[key], list) and len(\n                        patch[key]) > len(patch_current[key]):\n                    create_sub_object = True\n\n            if patch[key] is not None:\n                unmarshall_item(patch, key, self, self._attrs[key],\n                                create_sub_object)\n\n        if \"db\" in options and options[\"db\"] is True:\n            # apply changes to db model and update db\n            if \"with_validation\" in options and options[\n                \"with_validation\"] is True:\n                d = self.marshall_dict()\n                try:\n                    self._json_model(d).validate()\n                except Exception as exc:\n                    log.exception(\"document is not valid: {}\".format(exc))\n                    raise PatchUnprocessable(\n                        message=\"document is not valid,\"\n                                \" can't insert it into db: <{}>\".format(exc))\n\n            self.marshall_db()\n            try:\n                self.update_db()\n            except Exception as exc:\n                log.exception(exc)\n                raise PatchError(message=\"Error when updating db\")\n\n    def _check_key_consistency(self, current_attr, key, obj_patch_old,\n                               patch_current):\n        \"\"\"\n        check if a key provided in patch is consistent with current state\n\n        \"\"\"\n\n        if key not in self._attrs.keys():\n            raise PatchUnprocessable(\n                message=\"unknown key in patch\")\n        old_val = getattr(obj_patch_old, key)\n        cur_val = getattr(self, key)\n        msg = \"Patch current_state not consistent with db, step {} key {}\"\n\n        if isinstance(current_attr, types.ListType):\n            if not isinstance(cur_val, types.ListType):\n                raise PatchConflict(\n                    messag=msg.format(0, key))\n\n        if key not in patch_current.keys():\n            # means patch wants to add the key.\n            # Value in db should be null or empty\n            if cur_val not in (None, [], {}):\n                raise PatchConflict(\n                    message=msg.format(0.5, key))\n        else:\n            if isinstance(current_attr, types.ListType):\n                if old_val == [] and cur_val != []:\n                    raise PatchConflict(\n                        message=msg.format(1, key))\n                if cur_val == [] and old_val != []:\n                    raise PatchConflict(\n                        message=msg.format(2, key))\n                for old in old_val:\n                    for elem in cur_val:\n                        if issubclass(current_attr[0], CaliopenObject):\n                            if elem.__dict__ == old.__dict__:\n                                break\n                        else:\n                            if elem == old:\n                                break\n                    else:\n                        raise PatchConflict(\n                            message=msg.format(3, key))\n            elif issubclass(self._attrs[key], types.DictType):\n                if cmp(old_val, cur_val) != 0:\n                    raise PatchConflict(\n                        message=msg.format(4, key))\n            else:\n                # XXX ugly patch but else compare 2 distinct objects\n                # and not their representation\n                if hasattr(old_val, 'marshall_dict') and \\\n                        hasattr(cur_val, 'marshall_dict'):\n                    old_val = old_val.marshall_dict()\n                    cur_val = cur_val.marshall_dict()\n                if old_val != cur_val:\n                    raise PatchConflict(\n                        message=msg.format(5, key))\n\n\nclass ObjectIndexable(ObjectUser):\n    zope.interface.implements(storage.IndexIO)\n\n    _index_class = None  # dsl model for object\n    _index = None  # dsl model instance\n\n    def get_index(self, **options):\n        \"\"\"Get a doc from ES within user's index and put it at self._index\"\"\"\n\n        obj_id = getattr(self, self._pkey_name)\n        try:\n            self._index = self._index_class.get(index=self.user.shard_id,\n                                                id=obj_id,\n                                                using=self._index_class.client())\n        except Exception as exc:\n            if isinstance(exc, ESexceptions.NotFoundError):\n                log.exception(\"indexed doc not found\")\n                self._index = None\n                raise NotFound('%s #%s not found for user %s' %\n                               (self.__class__.__name__, obj_id, self.user_id))\n            else:\n                raise exc\n\n    def save_index(self, wait_for=False, **options):\n        if wait_for:\n            self._index.save(using=self._index_class.client(),\n                             refresh=\"wait_for\")\n        else:\n            self._index.save(using=self._index_class.client())\n\n    def create_index(self, **options):\n        \"\"\"Create indexed document from current self._index state\"\"\"\n\n        self.marshall_index()\n        self.save_index()\n\n    def delete_index(self, **options):\n        try:\n            self._index.delete(using=self._index_class.client(),\n                               refresh=\"wait_for\")\n        except Exception as exc:\n            log.exception(exc)\n            return exc\n\n        return None\n\n    def update_index(self, wait_for=False, **options):\n        \"\"\"get indexed doc from elastic and update it with self attrs\n\n        if indexed doc doesn't exist, create it\n        else update changed fields only\n        \"\"\"\n        self.get_index()\n        if self._index is not None:\n            try:\n                update_dict = self.marshall_index(update=True)\n                if wait_for:\n                    self._index.update(using=self._index_class.client(),\n                                       refresh=\"wait_for\",\n                                       **update_dict)\n                else:\n                    self._index.update(using=self._index_class.client(),\n                                       **update_dict)\n            except Exception as exc:\n                log.exception(\"update index failed: {}\".format(exc))\n\n        else:\n            # for some reasons, index doc not found... create one from scratch\n            self.create_index()\n\n    def marshall_index(self, **options):\n        \"\"\"squash self._index with self 'public' attributes\n\n        options:\n            update=True : only changed values will be replace in self._index\n                          and a dict with changed applied will be returned\n        \"\"\"\n\n        # TODO : manage protected attrs (ie attributes that user should not be able to change directly)\n\n        update = False\n\n        if \"update\" in options and options[\"update\"] is True:\n            update = True\n        # index_sibling is instanciated with self._index values to perform\n        # object comparaison\n        index_sibling = self.__class__(user=self.user)\n        index_sibling._index = self._index\n\n        index_sibling.unmarshall_index()\n        if not isinstance(self._index, self._index_class):\n            self._index = self._index_class()\n            self._index.meta.index = self.user.shard_id\n            self._index.meta.using = self._index.client()\n            self._index.meta.id = getattr(self, self._pkey_name)\n\n        # update_sibling is an empty sibling that will be filled\n        # with attributes from self\n        update_sibling = self.__class__(user=self.user)\n        m = self._index._doc_type.mapping.to_dict()\n        for att in m[self._index._doc_type.name][\"properties\"]:\n            if not att.startswith(\"_\") and att in index_sibling.keys():\n                if update:\n                    if getattr(self, att) != getattr(index_sibling, att):\n                        setattr(update_sibling, att, getattr(self, att))\n                    else:\n                        delattr(update_sibling, att)\n                else:\n                    setattr(update_sibling, att, getattr(self, att))\n\n        update_dict = update_sibling.marshall_dict()\n        for k, v in update_dict.iteritems():\n            if k in self._index_class.__dict__:\n                # do not try to set a property directly\n                if not isinstance(getattr(self._index_class, k), property):\n                    setattr(self._index, k, v)\n            else:\n                setattr(self._index, k, v)\n\n        if update:\n            return update_sibling.marshall_dict()\n\n    def unmarshall_index(self, **options):\n        \"\"\"squash self.attrs with index representation\"\"\"\n        if isinstance(self._index, self._index_class):\n            self.unmarshall_dict(self._index.to_dict())\n\n    def apply_patch(self, patch, **options):\n        try:\n            super(ObjectIndexable, self).apply_patch(patch, **options)\n        except Exception as exc:\n            log.exception(\"ObjectIndexable apply_patch() returned error: {}\".\n                          format(exc))\n            raise exc\n\n        if \"index\" in options and options[\"index\"] is True:\n            # silently update index. Should we raise an error if it fails ?\n            try:\n                self.update_index(wait_for=True)\n            except Exception as exc:\n                log.exception(\"apply_patch update_index() exception: {}\".\n                              format(exc))\n                raise exc\n\n\ndef unmarshall_item(document, key, target_object, target_attr_type,\n                    is_creation):\n    \"\"\"\n    general function to cast a dict item (ie: document[key])\n    into the corresponding target_object's attr (ie: target_object.key)\n\n    :param document: source dict\n    :param key: source dict key to unmarshall\n    :param target_object: object to unmarshall document[key] into\n    :param target_attr_type: the types.type of corresponding attr in target obj.\n    :param is_creation: if true, we are in the context of the creation of an obj\n    :return: nothing, target object is modified in-place\n    \"\"\"\n\n    if isinstance(target_attr_type, list):\n        lst = []\n        if issubclass(target_attr_type[0], ObjectDictifiable):\n            for item in document[key]:\n                sub_obj = target_attr_type[0]()\n                sub_obj.unmarshall_dict(item)\n                if is_creation and isinstance(sub_obj, ObjectStorable):\n                    sub_obj.set_uuid()\n                lst.append(sub_obj)\n        elif issubclass(target_attr_type[0], uuid.UUID):\n            for item in document[key]:\n                sub_obj = uuid.UUID(str(item))\n                lst.append(sub_obj)\n        else:\n            lst = document[key]\n        setattr(target_object, key, lst)\n\n    elif issubclass(target_attr_type, ObjectDictifiable):\n        if hasattr(target_object, 'user'):\n            opts = {'user': target_object.user}\n        else:\n            opts = {}\n        sub_obj = target_attr_type(**opts)\n        sub_obj.unmarshall_dict(document[key])\n        setattr(target_object, key, sub_obj)\n\n    elif issubclass(target_attr_type, uuid.UUID):\n        setattr(target_object, key, uuid.UUID(str(document[key])))\n\n    elif issubclass(target_attr_type, datetime.datetime):\n        if document[key] is not None \\\n                and document[key].tzinfo is None:\n            setattr(target_object, key, document[key].replace(tzinfo=\n                                                              pytz.utc))\n        else:\n            setattr(target_object, key, document[key])\n    else:\n        new_attr = document[key]\n        if hasattr(target_attr_type, \"validate\"):\n            new_attr = target_attr_type().validate(document[key])\n        setattr(target_object, key, new_attr)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/objects/tag.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen User tag parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport types\nimport datetime\n\nfrom .base import ObjectJsonDictifiable\nfrom ..store.tag import ResourceTag as ModelResourceTag\nfrom ..store.tag import IndexedResourceTag\n\nimport logging\nlog = logging.getLogger(__name__)\n\n\n### legacy code.\n# Tags are not anymore nested into other objects as objects, but as []string.\nclass ResourceTag(ObjectJsonDictifiable):\n    \"\"\"Tag nested in resources.\"\"\"\n\n    _attrs = {\n        'date_insert': datetime.datetime,\n        'importance_level': types.IntType,\n        'name': types.StringType,\n        'label': types.StringType,\n        'type': types.StringType,\n    }\n\n    _model_class = ModelResourceTag\n    _pkey_name = 'tag_id'\n    _index_class = IndexedResourceTag\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/parameters/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen common parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .pubkey import NewPublicKey, PublicKey\n\n__all__ = ['NewPublicKey', 'PublicKey']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/parameters/pubkey.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom schematics.models import Model\nfrom schematics.types import StringType, UUIDType, DateTimeType, LongType\nfrom schematics.transforms import blacklist\n\nimport caliopen_storage.helpers.json as helpers\n\nKEY_CHOICES = ['rsa', 'gpg', 'ssh']\n\n\nclass NewPublicKey(Model):\n    \"\"\"Input structure for a new public key.\"\"\"\n\n    resource_id = UUIDType(required=True)\n    resource_type = StringType(required=True)\n    key_id = UUIDType()\n\n    expire_date = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    label = StringType(required=True)\n    fingerprint = StringType()\n    key = StringType()\n    type = StringType()\n\n    # JWT parameters\n    kty = StringType()    # rsa / ec\n    use = StringType()    # sig / enc\n    alg = StringType()    # algorithm\n    # Elliptic curve public key parameters (rfc7518 6.2.1)\n    crv = StringType()\n    x = LongType()\n    y = LongType()\n\n    class Options:\n        serialize_when_none = False\n\n\nclass PublicKey(NewPublicKey):\n    \"\"\"Existing public key.\"\"\"\n\n    key_id = UUIDType(required=True)\n    date_insert = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    date_update = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    user_id = UUIDType()\n\n    class Options:\n        roles = {'default': blacklist('user_id', 'device_id')}\n        serialize_when_none = False\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/parameters/tag.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen tags parameters.\"\"\"\n\nfrom schematics.models import Model\nfrom schematics.types import StringType, UUIDType, DateTimeType, IntType\n\n\nclass ResourceTag(Model):\n    \"\"\"Tag related to a resource.\"\"\"\n\n    date_insert = DateTimeType()\n    importance_level = IntType()\n    name = StringType()\n    tag_id = UUIDType()\n    type = StringType()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/parameters/types.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameter validators.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom schematics.types import StringType\nfrom schematics.exceptions import ValidationError\nimport phonenumbers\n\nfrom ..helpers.normalize import clean_email_address\n\n\nclass InternetAddressType(StringType):\n\n    \"\"\"Validate an email or instant messaging address, return normalized.\"\"\"\n\n    def validate_email(self, value, context=None):\n        try:\n            clean, email = clean_email_address(value)\n        except Exception as exc:\n            raise ValidationError(exc.message)\n        return clean\n\n\nclass PhoneNumberType(StringType):\n\n    \"\"\"Validate a phone number and normalize in international format.\"\"\"\n\n    def validate_phone(self, value, context=None):\n        try:\n            number = phonenumbers.parse(value, None)\n            phone_format = phonenumbers.PhoneNumberFormat.INTERNATIONAL\n            return phonenumbers.format_number(number, phone_format)\n        except Exception as exc:\n            raise ValidationError(exc.message)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/store/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen common store classes classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\n\nfrom .pubkey import PublicKey\n\n__all__ = ['PublicKey']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/store/pubkey.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen objects related to contact definition.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nfrom datetime import datetime\n\nfrom cassandra.cqlengine import columns\n\nfrom caliopen_storage.store import BaseModel\n\n\nclass PublicKey(BaseModel):\n    \"\"\"Public cryptographic keys model.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    resource_id = columns.UUID(primary_key=True)    # clustering key\n    key_id = columns.UUID(primary_key=True)         # clustering key\n\n    resource_type = columns.Text()\n    label = columns.Text()\n\n    date_insert = columns.DateTime(default=datetime.utcnow)\n    date_update = columns.DateTime()\n    expire_date = columns.DateTime()\n\n    emails = columns.List(columns.Text())\n\n    key = columns.Text()\n    fingerprint = columns.Text()\n    size = columns.VarInt()\n\n    # JWT parameters\n    kty = columns.Text()    # rsa / ec\n    use = columns.Text()    # sig / enc\n    alg = columns.Text()    # algorithm\n    # Elliptic curve public key parameters (rfc7518 6.2.1)\n    crv = columns.Text()\n    x = columns.VarInt()\n    y = columns.VarInt()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/common/store/tag.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen tag objects.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom cassandra.cqlengine import columns\nfrom elasticsearch_dsl import InnerObjectWrapper, Date, Integer\nfrom elasticsearch_dsl import Boolean, Keyword\n\nfrom caliopen_storage.store import BaseUserType\n\n\nclass ResourceTag(BaseUserType):\n    \"\"\"Tag nested in resource model.\"\"\"\n\n    _pkey = 'tag_id'\n\n    date_insert = columns.DateTime()\n    importance_level = columns.Integer()\n    name = columns.Text()\n    tag_id = columns.UUID()\n    type = columns.Text()\n\n\nclass IndexedResourceTag(InnerObjectWrapper):\n    \"\"\"Nested tag into indexed resource model.\"\"\"\n\n    date_insert = Date()\n    importance_level = Integer()\n    name = Keyword()\n    tag_id = Keyword()\n    type = Boolean()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/core.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact core classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport uuid\nimport datetime\nimport pytz\nimport phonenumbers\n\nfrom .store import (Contact as ModelContact,\n                    ContactLookup as ModelContactLookup,\n                    Organization, Email, IM, PostalAddress,\n                    Phone, SocialIdentity)\nfrom .store.contact_index import IndexedContact\nfrom caliopen_storage.core import BaseCore\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_storage.core.mixin import MixinCoreRelation, MixinCoreNested\nfrom caliopen_main.pi.objects import PIModel\nfrom caliopen_main.common.core import BaseUserCore\nfrom caliopen_main.participant.objects import Participant\n\nlog = logging.getLogger(__name__)\n\n\nclass ContactLookup(BaseUserCore):\n    \"\"\"Contact lookup core class.\"\"\"\n\n    _model_class = ModelContactLookup\n    _pkey_name = 'value'\n\n\nclass BaseContactSubCore(BaseCore):\n    \"\"\"\n    Base core object for contact related objects\n    \"\"\"\n\n    @classmethod\n    def create(cls, user, contact, **kwargs):\n        obj = cls._model_class.create(user_id=user.user_id,\n                                      contact_id=contact.contact_id,\n                                      **kwargs)\n        return cls(obj)\n\n    @classmethod\n    def get(cls, user, contact, value):\n        kwargs = {cls._pkey_name: value}\n        try:\n            obj = cls._model_class.get(user_id=user.user_id,\n                                       contact_id=contact.contact_id,\n                                       **kwargs)\n            return cls(obj)\n        except Exception:\n            return None\n\n    @classmethod\n    def find(cls, user, contact, filters=None):\n        q = cls._model_class.filter(user_id=user.user_id). \\\n            filter(contact_id=contact.contact_id)\n        if not filters:\n            objs = q\n        else:\n            objs = q.filter(**filters)\n        return {'total': len(objs), 'data': [cls(x) for x in objs]}\n\n    def to_dict(self):\n        return {col: getattr(self, col)\n                for col in self._model_class._columns.keys()}\n\n\nclass Contact(BaseUserCore, MixinCoreRelation, MixinCoreNested):\n    _model_class = ModelContact\n    _pkey_name = 'contact_id'\n    _index_class = IndexedContact\n\n    _nested = {\n        'emails': Email,\n        'phones': Phone,\n        'ims': IM,\n        'social_identities': SocialIdentity,\n        'addresses': PostalAddress,\n        'organizations': Organization,\n    }\n\n    # Any of these nested objects,can be a lookup value\n    _lookup_class = ContactLookup\n    _lookup_values = {\n        'emails': {'value': 'address', 'type': 'email'},\n        'ims': {'value': 'address', 'type': 'email'},\n        'phones': {'value': 'number', 'type': 'phone'},\n        'social_identities': {'value': 'name', 'type': 'social'},\n    }\n\n    @classmethod\n    def _compute_title(cls, contact):\n        elmts = []\n        elmts.append(contact.name_prefix) if contact.name_prefix else None\n        elmts.append(contact.name_suffix) if contact.name_suffix else None\n        elmts.append(contact.given_name) if contact.given_name else None\n        elmts.append(contact.additional_name) if \\\n            contact.additional_name else None\n        elmts.append(contact.family_name) if contact.family_name else None\n        # XXX may be empty, got info from related infos\n        return \" \".join(elmts)\n\n    def _create_lookup(self, type, value):\n        \"\"\"Create one contact lookup.\"\"\"\n        log.debug('Will create lookup for type {} and value {}'.\n                  format(type, value))\n        lookup = ContactLookup.create(self.user, value=value, type=type,\n                                      contact_id=self.contact_id)\n        participant = Participant(address=value, protocol=type)\n\n        return lookup\n\n    def _create_lookups(self):\n        \"\"\"Create lookups for a contact using its nested attributes.\"\"\"\n        for attr_name, obj in self._lookup_values.items():\n            nested = getattr(self, attr_name)\n            if nested:\n                for attr in nested:\n                    lookup_value = attr[obj['value']]\n                    if lookup_value:\n                        self._create_lookup(obj['type'], lookup_value)\n\n    @classmethod\n    def normalize_phones(cls, phones):\n        for phone in phones:\n            try:\n                normalized = phonenumbers.parse(phone.number, None)\n                phone_format = phonenumbers.PhoneNumberFormat.INTERNATIONAL\n                new = phonenumbers.format_number(normalized, phone_format)\n                phone.normalized_number = new\n            except:\n                pass\n\n    @classmethod\n    def create(cls, user, contact, **related):\n        # XXX do sanity check about only one primary for related objects\n        # XXX check no extra arguments in related than relations\n\n        contact.validate()\n        for k, v in related.iteritems():\n            if k in cls._relations:\n                [x.validate() for x in v]\n            else:\n                raise Exception('Invalid argument to contact.create : %s' % k)\n\n        contact_id = uuid.uuid4()\n        if not contact.title:\n            title = cls._compute_title(contact)\n        else:\n            title = contact.title\n            if not contact.given_name and not contact.family_name:\n                # XXX more complex logic and not arbitrary order and character\n                if ',' in contact.title:\n                    gn, fn = contact.title.split(',', 2)\n                    contact.given_name = gn.rstrip().lstrip()\n                    contact.family_name = fn.rstrip().lstrip()\n\n        # XXX PI compute\n        pi = PIModel()\n        pi.technic = 0\n        pi.comportment = 0\n        pi.context = 0\n        pi.version = 0\n        phones = cls.create_nested(contact.phones, Phone)\n        # Normalize phones if possible\n        cls.normalize_phones(phones)\n\n        attrs = {'contact_id': contact_id,\n                 'info': contact.infos,\n                 'groups': contact.groups,\n                 'date_insert': datetime.datetime.now(tz=pytz.utc),\n                 'given_name': contact.given_name,\n                 'additional_name': contact.additional_name,\n                 'family_name': contact.family_name,\n                 'prefix_name': contact.name_prefix,\n                 'suffix_name': contact.name_suffix,\n                 'title': title,\n                 'emails': cls.create_nested(contact.emails, Email),\n                 'ims': cls.create_nested(contact.ims, IM),\n                 'phones': phones,\n                 'addresses': cls.create_nested(contact.addresses,\n                                                PostalAddress),\n                 'social_identities': cls.create_nested(contact.identities,\n                                                        SocialIdentity),\n                 'organizations': cls.create_nested(contact.organizations,\n                                                    Organization),\n                 'tags': contact.tags,\n                 'pi': pi}\n\n        core = super(Contact, cls).create(user, **attrs)\n        log.debug('Created contact %s' % core.contact_id)\n        core._create_lookups()\n        # Create relations\n        related_cores = {}\n        for k, v in related.iteritems():\n            if k in cls._relations:\n                for obj in v:\n                    log.debug('Processing object %r' % obj)\n                    # XXX check only one is_primary per relation using it\n                    new_core = cls._relations[k].create(user, core, **obj)\n                    related_cores.setdefault(k, []).append(new_core.to_dict())\n                    log.debug('Created related core %r' % new_core)\n        return core\n\n    @classmethod\n    def lookup(cls, user, value):\n        try:\n            lookup = ContactLookup._model_class.get(user_id=user.user_id,\n                                                    value=value)\n        except NotFound:\n            return None\n        if lookup and lookup.contact_id:\n            # as of 2019, april it is forbidden in Caliopen\n            # to add an external address to more than one contact\n            # as a mater of fact, lookup should always return one contact only\n            try:\n                return cls.get(user, lookup.contact_id)\n            except NotFound:\n                log.warn('Inconsistent contact lookup with non existing '\n                         ' contact %r' % lookup.contact_id)\n                return None\n        # XXX something else to do ?\n        return None\n\n    def delete(self):\n        if self.user.contact_id == self.contact_id:\n            raise Exception(\"Can't delete contact related to user\")\n        return super(Contact, self).delete()\n\n    @property\n    def public_keys(self):\n        \"\"\"Return detailed public keys.\"\"\"\n        return self._expand_relation('public_keys')\n\n    # MixinCoreRelation methods\n    def add_organization(self, organization):\n        return self._add_nested('organizations', organization)\n\n    def delete_organization(self, organization_id):\n        return self._delete_nested('organizations', organization_id)\n\n    def add_address(self, address):\n        return self._add_nested('addresses', address)\n\n    def delete_address(self, address_id):\n        return self._delete_nested('addresses', address_id)\n\n    def add_email(self, email):\n        return self._add_nested('emails', email)\n\n    def delete_email(self, email_addr):\n        return self._delete_nested('emails', email_addr)\n\n    def add_im(self, im):\n        return self._add_nested('ims', im)\n\n    def delete_im(self, im_addr):\n        return self._delete_nested('ims', im_addr)\n\n    def add_phone(self, phone):\n        return self._add_nested('phones', phone)\n\n    def delete_phone(self, phone_num):\n        return self._delete_nested('phones', phone_num)\n\n    def add_social_identity(self, identity):\n        return self._add_nested('social_identities', identity)\n\n    def delete_social_identity(self, identity_name):\n        return self._delete_nested('social_identities', identity_name)\n\n    def add_public_key(self, key):\n        # XXX Compute fingerprint and check key validity\n        return self._add_relation('public_keys', key)\n\n    def delete_public_key(self, key_id):\n        return self._delete_relation('public_keys', key_id)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/objects/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .contact import Contact\n\n__all__ = ['Contact']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/objects/contact.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom uuid import UUID\nimport datetime\n\nfrom caliopen_main.common.objects.base import ObjectStorable, ObjectIndexable\n\nfrom ..store.contact import (Contact as ModelContact,\n                             ContactLookup as ModelContactLookup)\nfrom ..store.contact_index import IndexedContact\nfrom ..parameters import Contact as ParamContact\n\nfrom .email import Email\nfrom .identity import SocialIdentity\nfrom .im import IM\nfrom .organization import Organization\nfrom .phone import Phone\nfrom .postal_address import PostalAddress\nfrom caliopen_main.pi.objects import PIObject\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_main.common.errors import ForbiddenAction\n\nimport logging\nlog = logging.getLogger(__name__)\n\n\nclass ContactLookup(ObjectStorable):\n    \"\"\"Contact lookup core class.\"\"\"\n\n    def __init__(self):\n        self._model_class = ModelContactLookup\n        self._pkey_name = 'value'\n\n\nclass Contact(ObjectIndexable):\n\n    # TODO : manage attrs that should not be modifiable directly by users\n    _attrs = {\n        'additional_name': types.StringType,\n        'addresses': [PostalAddress],\n        'avatar': types.StringType,\n        'contact_id': UUID,\n        'date_insert': datetime.datetime,\n        'date_update': datetime.datetime,\n        'deleted': datetime.datetime,\n        'emails': [Email],\n        'family_name': types.StringType,\n        'given_name': types.StringType,\n        'groups': [types.StringType],\n        'identities': [SocialIdentity],\n        'ims': [IM],\n        'infos': types.DictType,\n        'name_prefix': types.StringType,\n        'name_suffix': types.StringType,\n        'organizations': [Organization],\n        'phones': [Phone],\n        'pi': PIObject,\n        'privacy_features': types.DictType,\n        'tags': [types.StringType],\n        'title': types.StringType,\n        'user_id': UUID\n    }\n\n    _json_model = ParamContact\n\n    # operations related to cassandra\n    _model_class = ModelContact\n    _db = None  # model instance with datas from db\n    _pkey_name = \"contact_id\"\n    _lookup_class = ContactLookup\n    _lookup_values = {\n        'emails': {'value': 'address', 'type': 'email'},\n        'ims': {'value': 'address', 'type': 'email'},\n        'phones': {'value': 'number', 'type': 'phone'},\n        'social_identities': {'value': 'name', 'type': 'social'},\n    }\n\n    #  operations related to elasticsearch\n    _index_class = IndexedContact\n    _index = None\n\n    def delete(self):\n        # XXX prevent circular dependency import\n        from caliopen_main.user.core.user import User\n        user = User.get(self.user_id)\n        if user.contact_id == self.contact_id:\n            raise ForbiddenAction(\"Can't delete contact related to user\")\n        try:\n            self.get_db()\n            self.get_index()\n        except Exception as exc:\n            raise NotFound\n\n        try:\n            self.delete_db()\n            self.delete_index()\n        except Exception as exc:\n            raise exc\n\n    @classmethod\n    def _compute_title(cls, contact):\n        elmts = []\n        elmts.append(contact.name_prefix) if contact.name_prefix else None\n        elmts.append(contact.name_suffix) if contact.name_suffix else None\n        elmts.append(contact.given_name) if contact.given_name else None\n        elmts.append(contact.additional_name) if \\\n            contact.additional_name else None\n        elmts.append(contact.family_name) if contact.family_name else None\n        return \" \".join(elmts) if len(elmts) > 0 else \" (N/A) \"\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/objects/email.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom caliopen_main.common.objects.base import ObjectStorable\nfrom uuid import UUID\nfrom caliopen_main.common.parameters.types import InternetAddressType\nfrom ..parameters import Email as EmailParam\nfrom ..store.contact import Email as ModelEmail\n\n\nclass Email(ObjectStorable):\n\n    _attrs = {\n        \"address\":              InternetAddressType,\n        \"email_id\":             UUID,\n        \"is_primary\":           types.BooleanType,\n        \"label\":                types.StringType,\n        \"type\":                 types.StringType,\n    }\n\n    _json_model = EmailParam\n    _model_class = ModelEmail\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/objects/identity.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen message object classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom uuid import UUID\n\nfrom caliopen_main.common.objects.base import ObjectIndexable, \\\n    ObjectJsonDictifiable\nfrom caliopen_main.pi.objects import PIObject\n\nfrom ..store.contact import SocialIdentity as ModelSocialIdentity\nfrom ..parameters import SocialIdentity as SocialIdentityParam\nfrom ..store.contact_index import IndexedSocialIdentity\n\n\nclass SocialIdentity(ObjectIndexable):\n    \"\"\"Social identity related to a contact.\"\"\"\n\n    _attrs = {\n        \"contact_id\": UUID,\n        \"social_id\": UUID,\n        \"infos\": types.DictType,\n        \"name\": types.StringType,\n        \"type\": types.StringType,\n        \"user_id\": UUID\n    }\n\n    _json_model = SocialIdentityParam\n    _model_class = ModelSocialIdentity\n    _index_class = IndexedSocialIdentity\n\n\nclass ContactIdentity(ObjectJsonDictifiable):\n    \"\"\"\n    Mean of communication for a contact, with on-demand calculated PI.\n\n    [for ex., a list of ContactIdentity is built for REST API\n     …/contact/{contact_id}/identities]\n\n    \"\"\"\n\n    _attrs = {\n        \"identifier\": types.StringType,\n        \"label\": types.StringType,\n        \"privacy_index\": PIObject,\n        \"protocol\": types.StringType,\n    }\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/objects/im.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom caliopen_main.common.objects.base import ObjectIndexable\nfrom uuid import UUID\nfrom caliopen_main.common.parameters.types import InternetAddressType\nfrom ..store.contact import IM as ModelIM\nfrom ..returns import IMParam\nfrom ..store.contact_index import IndexedInternetAddress\n\n\nclass IM(ObjectIndexable):\n\n    _attrs = {\n        \"address\":              InternetAddressType,\n        \"is_primary\":           types.BooleanType,\n        \"label\":                types.StringType,\n        \"protocol\":             types.StringType,\n        \"type\":                 types.StringType,\n        \"contact_id\":           UUID,\n        \"im_id\":                UUID,\n        \"user_id\":              UUID\n    }\n\n    _model_class = ModelIM\n    _json_model = IMParam\n    _index_class = IndexedInternetAddress\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/objects/organization.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom caliopen_main.common.objects.base import ObjectIndexable\nfrom uuid import UUID\nfrom ..store.contact import Organization as ModelOrganization\nfrom ..store.contact_index import IndexedOrganization\nfrom ..returns import OrganizationParam\n\n\nclass Organization(ObjectIndexable):\n\n    _attrs = {\n        \"department\":               types.StringType,\n        \"is_primary\":               types.BooleanType,\n        \"job_description\":          types.StringType,\n        \"label\":                    types.StringType,\n        \"name\":                     types.StringType,\n        \"title\":                    types.StringType,\n        \"type\":                     types.StringType,\n        \"contact_id\":               UUID,\n        \"deleted\":                  types.BooleanType,\n        \"organization_id\":          UUID,\n        \"user_id\":                  UUID\n    }\n\n    _model_class = ModelOrganization\n    _json_model = OrganizationParam\n    _index_class = IndexedOrganization\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/objects/phone.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport types\nfrom uuid import UUID\nimport phonenumbers\n\nfrom caliopen_main.common.objects.base import ObjectIndexable\nfrom caliopen_main.common.parameters.types import PhoneNumberType\nfrom ..store.contact import Phone as ModelPhone\nfrom ..store.contact_index import IndexedPhone\nfrom ..returns import PhoneParam\n\n\nlog = logging.getLogger(__name__)\n\n\nclass Phone(ObjectIndexable):\n\n    _attrs = {\n        \"contact_id\": UUID,\n        \"is_primary\": types.BooleanType,\n        \"number\": types.StringType,\n        \"normalized_number\": types.StringType,\n        \"phone_id\": UUID,\n        \"type\": types.StringType,\n        \"uri\": types.StringType,\n        \"user_id\": UUID\n    }\n\n    _model_class = ModelPhone\n    _json_model = PhoneParam\n    _index_class = IndexedPhone\n\n    def normalize_number(self, number):\n        try:\n            normalized = phonenumbers.parse(number, None)\n            phone_format = phonenumbers.PhoneNumberFormat.INTERNATIONAL\n            return phonenumbers.format_number(normalized, phone_format)\n        except Exception as exc:\n            log.warn('Unable to normalize phone number {0} : {1}'.\n                     format(number, exc))\n\n    def unmarshall_dict(self, document, **options):\n        \"\"\"try to extract a normalize phone number from document\"\"\"\n        super(Phone, self).unmarshall_dict(document, **options)\n        normalized = self.normalize_number(self.number)\n        if normalized:\n            setattr(self, 'normalized_number', normalized)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/objects/postal_address.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom caliopen_main.common.objects.base import ObjectIndexable\nfrom uuid import UUID\nfrom ..store.contact import PostalAddress as ModelPostalAddress\nfrom ..returns import PostalAddressParam\nfrom ..store.contact_index import IndexedPostalAddress\n\n\nclass PostalAddress(ObjectIndexable):\n\n    _attrs = {\n        \"address_id\":               UUID,\n        \"city\":                     types.StringType,\n        \"contact_id\":               UUID,\n        \"country\":                  types.StringType,\n        \"is_primary\":               types.BooleanType,\n        \"label\":                    types.StringType,\n        \"postal_code\":              types.StringType,\n        \"region\":                   types.StringType,\n        \"street\":                   types.StringType,\n        \"type\":                     types.StringType,\n        \"user_id\":                  UUID\n    }\n\n    _model_class = ModelPostalAddress\n    _json_model = PostalAddressParam\n    _index_class = IndexedPostalAddress\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/parameters.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom schematics.models import Model\nfrom schematics.types import StringType, UUIDType, DateTimeType, BooleanType\nfrom schematics.types.compound import ListType, ModelType, DictType\nfrom schematics.transforms import blacklist\n\nfrom caliopen_main.common.parameters.types import InternetAddressType\n\nfrom caliopen_main.pi.parameters import PIParameter\nimport caliopen_storage.helpers.json as helpers\n\nORG_TYPES = ['work', 'home', '']\nADDRESS_TYPES = ['work', 'home', 'other', '']\nEMAIL_TYPES = ['work', 'home', 'other', '']\nIM_TYPES = ['work', 'home', 'other', 'netmeeting', '']\n\nPHONE_TYPES = ['assistant', 'callback', 'car', 'company_main',\n               'fax', 'home', 'home_fax', 'isdn', 'main', 'mobile',\n               'other', 'other_fax', 'pager', 'radio', 'telex',\n               'tty_tdd', 'work', 'work_fax', 'work_mobile', 'work_pager']\n# XXX : use configuration instead ?\nSOCIAL_TYPES = ['facebook', 'twitter', 'google', 'github', 'bitbucket',\n                'linkedin', 'ello', 'instagram', 'tumblr', 'skype', 'mastodon']\n\nRECIPIENT_TYPES = ['to', 'from', 'cc', 'bcc']\n\n\nclass Recipient(Model):\n    \"\"\"Store a contact reference and one of it's address used in a message.\"\"\"\n\n    address = StringType(required=True)\n    contact_id = UUIDType()\n    type = StringType(required=True, choices=RECIPIENT_TYPES)\n\n    class Options:\n        serialize_when_none = False\n\n\nclass NewOrganization(Model):\n    \"\"\"Input structure for a new organization.\"\"\"\n\n    department = StringType()\n    is_primary = BooleanType(default=False)\n    job_description = StringType()\n    label = StringType()\n    name = StringType(required=True)\n    title = StringType()\n    # XXX Add enumerated list\n    type = StringType()\n\n    class Options:\n        serialize_when_none = False\n\n\nclass Organization(NewOrganization):\n    \"\"\"Existing organization.\"\"\"\n\n    contact_id = UUIDType()\n    deleted = BooleanType(default=False)\n    organization_id = UUIDType()\n    user_id = UUIDType()\n\n    class Options:\n        roles = {'default': blacklist('user_id', 'contact_id')}\n        serialize_when_none = False\n\n\nclass NewPostalAddress(Model):\n    \"\"\"Input structure for a new postal address.\"\"\"\n\n    address_id = StringType()\n    city = StringType()\n    country = StringType()\n    is_primary = BooleanType(default=False)\n    label = StringType()\n    postal_code = StringType()\n    region = StringType()\n    street = StringType()\n    type = StringType(choices=ADDRESS_TYPES)\n\n    class Options:\n        serialize_when_none = False\n\n\nclass PostalAddress(NewPostalAddress):\n    \"\"\"Existing postal address.\"\"\"\n\n    address_id = UUIDType()\n    contact_id = UUIDType()\n    user_id = UUIDType()\n\n    class Options:\n        roles = {'default': blacklist('user_id', 'contact_id')}\n        serialize_when_none = False\n\n\nclass NewEmail(Model):\n    \"\"\"Input structure for a new email.\"\"\"\n\n    address = InternetAddressType(required=True)\n    is_primary = BooleanType(default=False)\n    label = StringType()\n    type = StringType(choices=EMAIL_TYPES, default='other')\n\n    class Options:\n        serialize_when_none = False\n\n\nclass Email(NewEmail):\n    \"\"\"Existing email.\"\"\"\n\n    email_id = UUIDType()\n\n    class Options:\n        roles = {'default': blacklist('user_id', 'contact_id')}\n        serialize_when_none = False\n\n\nclass NewIM(Model):\n    \"\"\"Input structure for a new IM.\"\"\"\n\n    address = StringType(required=True)\n    is_primary = BooleanType(default=False)\n    label = StringType()\n    protocol = StringType()\n    type = StringType(choices=IM_TYPES, default='other')\n\n    class Options:\n        serialize_when_none = False\n\n\nclass IM(NewIM):\n    \"\"\"Existing IM.\"\"\"\n\n    contact_id = UUIDType()\n    im_id = UUIDType()\n    user_id = UUIDType()\n\n    class Options:\n        roles = {'default': blacklist('user_id', 'contact_id')}\n        serialize_when_none = False\n\n\nclass NewPhone(Model):\n    \"\"\"Input structure for a new phone.\"\"\"\n\n    is_primary = BooleanType(default=False)\n    number = StringType(required=True)\n    normalized_number = StringType()\n    type = StringType(choices=PHONE_TYPES, default='other')\n    uri = StringType()\n\n    class Options:\n        serialize_when_none = False\n\n\nclass Phone(NewPhone):\n    \"\"\"Existing phone.\"\"\"\n\n    contact_id = UUIDType()\n    phone_id = UUIDType()\n    user_id = UUIDType()\n\n    class Options:\n        roles = {'default': blacklist('user_id', 'contact_id')}\n        serialize_when_none = False\n\n\nclass NewSocialIdentity(Model):\n    \"\"\"Input structure for a new social identity.\"\"\"\n\n    infos = DictType(StringType, default=lambda: {})\n    name = StringType(required=True)\n    type = StringType(choices=SOCIAL_TYPES, required=True)\n\n    class Options:\n        serialize_when_none = False\n\n\nclass SocialIdentity(NewSocialIdentity):\n    \"\"\"Existing social identity.\"\"\"\n\n    contact_id = UUIDType()\n    social_id = UUIDType()\n    user_id = UUIDType()\n\n    class Options:\n        roles = {'default': blacklist('user_id', 'contact_id')}\n        serialize_when_none = False\n\n\nclass NewContact(Model):\n    \"\"\"Input structure for a new contact.\"\"\"\n\n    additional_name = StringType()\n    addresses = ListType(ModelType(NewPostalAddress), default=lambda: [])\n    emails = ListType(ModelType(NewEmail), default=lambda: [])\n    family_name = StringType()\n    given_name = StringType()\n    title = StringType()\n    groups = ListType(StringType())\n    identities = ListType(ModelType(NewSocialIdentity), default=lambda: [])\n    ims = ListType(ModelType(NewIM), default=lambda: [], )\n    infos = DictType(StringType())\n    name_prefix = StringType()\n    name_suffix = StringType()\n    organizations = ListType(ModelType(NewOrganization), default=lambda: [])\n    phones = ListType(ModelType(NewPhone), default=lambda: [])\n    privacy_features = DictType(StringType(), default=lambda: {})\n    tags = ListType(StringType(), default=lambda: [])\n\n    class Options:\n        serialize_when_none = False\n\n\nclass Contact(NewContact):\n    \"\"\"Existing contact.\"\"\"\n\n    addresses = ListType(ModelType(PostalAddress), default=lambda: [])\n    avatar = StringType(default='avatar.png')\n    contact_id = UUIDType()\n    date_insert = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    date_update = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    deleted = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                           tzd=u'utc')\n    emails = ListType(ModelType(Email), default=lambda: [])\n    identities = ListType(ModelType(SocialIdentity), default=lambda: [])\n    ims = ListType(ModelType(IM), default=lambda: [])\n    organizations = ListType(ModelType(Organization), default=lambda: [])\n    phones = ListType(ModelType(Phone), default=lambda: [])\n    pi = ModelType(PIParameter)\n    user_id = UUIDType()\n\n    class Options:\n        serialize_when_none = False\n\n\nclass ShortContact(Model):\n    \"\"\"Input structure for contact in short form.\"\"\"\n\n    contact_id = UUIDType()\n    family_name = StringType()\n    given_name = StringType()\n    tags = ListType(StringType(), default=lambda: [])\n    title = StringType()\n    pi = ModelType(PIParameter)\n\n    class Options:\n        serialize_when_none = False\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/parsers/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact format parsers.\"\"\"\n\nfrom .vcard import VcardContact, VcardParser\n\n__all__ = ['VcardParser', 'VcardContact']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/parsers/vcard.py",
    "content": "\"\"\"Caliopen vcard format parser.\"\"\"\n\nimport logging\nimport vobject\nimport phonenumbers\n\nfrom caliopen_main.contact.parameters import NewContact, NewEmail, EMAIL_TYPES\nfrom caliopen_main.contact.parameters import NewIM, IM_TYPES, NewPhone\nfrom caliopen_main.contact.parameters import NewSocialIdentity\nfrom caliopen_main.contact.parameters import NewPostalAddress, NewOrganization\n\nlog = logging.getLogger(__name__)\n\n\nclass VcardContact(object):\n    \"\"\"Contact from a vcard entry.\"\"\"\n\n    _meta = {}\n\n    def __init__(self, vcard):\n        \"\"\"Parse a vcard contact.\"\"\"\n        self._vcard = vcard\n        self._parse()\n\n    def _get_not_empty(self, prop):\n        \"\"\"Get non empty value (and only value) from a vcard property.\"\"\"\n        if prop in self._vcard.contents:\n            attr = self._vcard.contents[prop]\n            if isinstance(attr, (list, tuple)):\n                return [x.value for x in attr if x.value]\n            return attr.value if attr.value else None\n        return None\n\n    def __build_email(self, param):\n        email = NewEmail()\n        email.address = param.value\n        if 'TYPE' in param.params:\n            email_type = param.params['TYPE'][0].lower()\n            if email_type in EMAIL_TYPES:\n                email.type = email_type\n        if 'PREF' in param.params:\n            email.is_primary = True\n        return email\n\n    def __parse_emails(self):\n        \"\"\"Read vcard email property and build NewEmail instances.\"\"\"\n        for param in self._vcard.contents.get('email', []):\n            yield self.__build_email(param)\n\n    def __build_phone(self, param):\n        # XXX TOFIX\n        _vcard_types = {\n            'text': 'other',\n            'voice': 'other',\n            'fax': 'fax',\n            'cell': 'mobile',\n            'video': 'other',\n            'pager': 'pager',\n            'textphone': 'other',\n        }\n\n        phone = NewPhone()\n        phone.number = param.value\n        if 'TYPE' in param.params and param.params['TYPE']:\n            phone_type = param.params['TYPE'][0].lower()\n            if phone_type in _vcard_types:\n                phone.type = _vcard_types[phone_type]\n            else:\n                phone.type = 'other'\n        if 'PREF' in param.params:\n            phone.is_primary = True\n        try:\n            number = phonenumbers.parse(phone.number, None)\n            phone_format = phonenumbers.PhoneNumberFormat.INTERNATIONAL\n            normalized = phonenumbers.format_number(number, phone_format)\n            if normalized:\n                phone.normalized_number = normalized\n        except:\n            pass\n        return phone\n\n    def __parse_phones(self):\n        \"\"\"Read vcard tel property and build NewPhone instances.\"\"\"\n        for param in self._vcard.contents.get('tel', []):\n            yield self.__build_phone(param)\n\n    def __build_address(self, param):\n        adr = NewPostalAddress()\n        adr.city = param.value.city\n        adr.zip = param.value.code\n        adr.street = param.value.street\n        adr.region = param.value.region\n        adr.country = param.value.country\n        return adr\n\n    def __parse_addresses(self):\n        \"\"\"Read vcard adr property and build NewPostalAddress instances.\"\"\"\n        for param in self._vcard.contents.get('adr', []):\n            yield self.__build_address(param)\n\n    def __build_organization(self, param):\n        org = NewOrganization()\n        org.name = param.value[0]\n        return org\n\n    def __parse_organizations(self):\n        for param in self._vcard.contents.get('org', []):\n            yield self.__build_organization(param)\n\n    def __build_im(self, param):\n        im = NewIM()\n        im.address = param.value\n        if 'TYPE' in param.params and param.params['TYPE']:\n            im_type = param.params['TYPE'][0].lower()\n            if im_type in IM_TYPES:\n                im.type = im_type\n        if 'PREF' in param.params:\n            im.is_primary = True\n        return im\n\n    def __parse_impps(self):\n        for param in self._vcard.contents.get('impp', []):\n            yield self.__build_im(param)\n\n    def __parse_social_identities(self):\n        idents = []\n        if 'x-twitter' in self._vcard.contents:\n            for ident in self._vcard.contents.get('x-twitter', []):\n                social = NewSocialIdentity()\n                social.type = 'twitter'\n                social.name = ident.value\n                idents.append(social)\n        return idents\n\n    def _parse(self):\n        contact = NewContact()\n        if 'n' in self._vcard.contents:\n            contact.given_name = self._vcard.n.value.given\n            contact.family_name = self._vcard.n.value.family\n            contact.title = '{0} {1}'.format(contact.given_name,\n                                             contact.family_name)\n        elif 'fn' in self._vcard.contents:\n            contact.title = self._vcard.fn.value\n        elif 'cn' in self._vcard.contents:\n            contact.title = self._vcard.contents['cn'][0].value\n        elif self._vcard.contents.get('email'):\n            contact.title = self._vcard.contents['email'][0].value\n\n        if not contact.infos:\n            contact.infos = {}\n        for prop in ['nickname']:\n            value = self._get_not_empty(prop)\n            if value and len(value):\n                contact.infos[prop] = value[0]\n        for prop in ['uid', 'rev']:\n            value = self._get_not_empty(prop)\n            if value:\n                self._meta[prop] = value\n\n        contact.phones = self.__parse_phones()\n        contact.emails = self.__parse_emails()\n        contact.addresses = self.__parse_addresses()\n        contact.organizations = self.__parse_organizations()\n        contact.ims = self.__parse_impps()\n        contact.identities = self.__parse_social_identities()\n        self.contact = contact\n\n    def serialize(self):\n        \"\"\"Serialize contact.\"\"\"\n        data = self.contact.serialize()\n        data.update({'_meta': self._meta})\n        return data\n\n    def validate(self):\n        \"\"\"Validate contact parsed informations.\"\"\"\n        return self.contact.validate()\n\n\nclass VcardParser(object):\n    \"\"\"Vcard format parser class.\"\"\"\n\n    def __init__(self, f):\n        \"\"\"Read a vcard file and create a generator on vcard objects.\"\"\"\n        self._vcards = vobject.readComponents(f)\n\n    def parse(self):\n        \"\"\"Generator on vcards objects read from read file.\"\"\"\n        for vcard in self._vcards:\n            yield VcardContact(vcard)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/returns.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom caliopen_storage.parameters import ReturnCoreObject\n\nfrom .core import Contact\nfrom .parameters import (Contact as ContactParam,\n                         ShortContact as ContactShortParam,\n                         Email as EmailParam,\n                         Phone as PhoneParam,\n                         IM as IMParam,\n                         Organization as OrganizationParam,\n                         PostalAddress as PostalAddressParam,\n                         SocialIdentity as SocialIdentityParam)\n\n\nclass ReturnContact(ReturnCoreObject):\n\n    _core_class = Contact\n    _return_class = ContactParam\n\n\nclass ReturnShortContact(ReturnCoreObject):\n\n    _core_class = Contact\n    _return_class = ContactShortParam\n\n\nclass ReturnEmail(ReturnCoreObject):\n\n    _return_class = EmailParam\n\n\nclass ReturnIM(ReturnCoreObject):\n\n    _return_class = IMParam\n\n\nclass ReturnPhone(ReturnCoreObject):\n\n    _return_class = PhoneParam\n\n\nclass ReturnAddress(ReturnCoreObject):\n\n    _return_class = PostalAddressParam\n\n\nclass ReturnSocialIdentity(ReturnCoreObject):\n\n    _return_class = SocialIdentityParam\n\n\nclass ReturnOrganization(ReturnCoreObject):\n\n    _return_class = OrganizationParam\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/store/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .contact import Contact, IndexedContact, ContactLookup\nfrom .contact import Organization, PostalAddress\nfrom .contact import Email, IM, Phone, SocialIdentity\n\n\n__all__ = ['Contact', 'ContactLookup', 'IndexedContact',\n           'Organization', 'PostalAddress',\n           'Email', 'IM', 'Phone', 'SocialIdentity']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/store/contact.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen objects related to contact definition.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport uuid\n\nfrom cassandra.cqlengine import columns\n\nfrom caliopen_storage.store.mixin import IndexedModelMixin\nfrom caliopen_storage.store import BaseModel, BaseUserType\nfrom caliopen_main.pi.objects import PIModel\n\nfrom .contact_index import IndexedContact\n\n\nclass Organization(BaseUserType):\n    \"\"\"Contact organizations model.\"\"\"\n\n    _pkey = 'organization_id'\n\n    deleted = columns.Boolean(default=False)\n    department = columns.Text()\n    is_primary = columns.Boolean(default=False)\n    job_description = columns.Text()\n    label = columns.Text()\n    name = columns.Text()\n    organization_id = columns.UUID(default=uuid.uuid4)\n    title = columns.Text()\n    type = columns.Text()  # work, other\n\n\nclass PostalAddress(BaseUserType):\n    \"\"\"Contact postal addresses model.\"\"\"\n\n    _pkey = 'address_id'\n\n    address_id = columns.UUID(default=uuid.uuid4)\n    city = columns.Text()\n    country = columns.Text()\n    is_primary = columns.Boolean(default=False)\n    label = columns.Text()\n    postal_code = columns.Text()\n    region = columns.Text()\n    street = columns.Text()\n    type = columns.Text()\n\n\nclass Email(BaseUserType):\n    \"\"\"Contact emails model.\"\"\"\n\n    _pkey = 'email_id'\n    uniq_name = 'address'\n\n    address = columns.Text()\n    email_id = columns.UUID(default=uuid.uuid4)\n    is_primary = columns.Boolean(default=False)\n    label = columns.Text()\n    type = columns.Text()  # home, work, other\n\n\nclass IM(BaseUserType):\n    \"\"\"Contact instant messaging adresses model.\"\"\"\n\n    _pkey = 'im_id'\n    uniq_name = 'address'\n\n    address = columns.Text()\n    im_id = columns.UUID(default=uuid.uuid4)\n    is_primary = columns.Boolean(default=False)\n    label = columns.Text()\n    protocol = columns.Text()\n    type = columns.Text()\n\n\nclass Phone(BaseUserType):\n    \"\"\"Contact phones model.\"\"\"\n\n    _pkey = 'phone_id'\n    uniq_name = 'number'\n\n    is_primary = columns.Boolean(default=False)\n    number = columns.Text()\n    normalized_number = columns.Text()\n    phone_id = columns.UUID(default=uuid.uuid4)\n    type = columns.Text()\n    uri = columns.Text()  # RFC3966\n\n\nclass SocialIdentity(BaseUserType):\n    \"\"\"Any contact social identity (facebook, twitter, linkedin, etc).\"\"\"\n\n    _pkey = 'social_id'\n\n    social_id = columns.UUID(default=uuid.uuid4)\n    name = columns.Text()\n    type = columns.Text()\n    # Abstract everything else in a map\n    infos = columns.Map(columns.Text, columns.Text)\n\n\nclass Contact(BaseModel, IndexedModelMixin):\n    \"\"\"Contact model.\"\"\"\n\n    _index_class = IndexedContact\n\n    user_id = columns.UUID(primary_key=True)\n    contact_id = columns.UUID(primary_key=True)  # clustering key\n\n    additional_name = columns.Text()\n    addresses = columns.List(columns.UserDefinedType(PostalAddress))\n    avatar = columns.Text()\n    date_insert = columns.DateTime()\n    date_update = columns.DateTime()\n    deleted = columns.DateTime()\n    emails = columns.List(columns.UserDefinedType(Email))\n    family_name = columns.Text()\n    given_name = columns.Text()\n    groups = columns.List(columns.Text())\n    identities = columns.List(columns.UserDefinedType(SocialIdentity))\n    ims = columns.List(columns.UserDefinedType(IM))\n    infos = columns.Map(columns.Text, columns.Text)\n    name_prefix = columns.Text()\n    name_suffix = columns.Text()\n    organizations = columns.List(columns.UserDefinedType(Organization))\n    phones = columns.List(columns.UserDefinedType(Phone))\n    pi = columns.UserDefinedType(PIModel)\n    privacy_features = columns.Map(columns.Text(), columns.Text())\n    tags = columns.List(columns.Text(), db_field=\"tagnames\")\n    title = columns.Text()  # computed value, read only\n\n\nclass ContactLookup(BaseModel):\n    \"\"\"Lookup any information needed to recognize a user contact.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    value = columns.Text(\n        primary_key=True)  # address or 'identifier' in identity\n    type = columns.Text(primary_key=True)  # email, IM, etc.\n    contact_id = columns.UUID()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/contact/store/contact_index.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact index classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom elasticsearch_dsl import Mapping, Nested, Text, Keyword, Date, Boolean, \\\n    InnerObjectWrapper, Object\nfrom caliopen_storage.store.model import BaseIndexDocument\nfrom caliopen_main.pi.objects import PIIndexModel\n\nlog = logging.getLogger(__name__)\n\n\nclass IndexedOrganization(InnerObjectWrapper):\n    \"\"\"Contact indexed organization model.\"\"\"\n\n    deleted = Boolean()\n    department = Text()\n    is_primary = Boolean()\n    job_description = Text()\n    label = Text()\n    name = Keyword()\n    organization_id = Keyword()\n    title = Keyword()\n    type = Keyword()\n\n\nclass IndexedPostalAddress(InnerObjectWrapper):\n    \"\"\"Contact indexed postal addresse model.\"\"\"\n\n    address_id = Keyword()\n    label = Text()\n    type = Keyword()\n    is_primary = Boolean()\n    street = Text()\n    city = Text()\n    postal_code = Text()\n    country = Text()\n    region = Text()\n\n\nclass IndexedInternetAddress(InnerObjectWrapper):\n    \"\"\"Contact indexed address on internet (email, im) model.\"\"\"\n\n    address = Keyword()\n    email_id = Keyword()\n    is_primary = Boolean()\n    label = Text()\n    type = Keyword()\n\n\nclass IndexedPhone(InnerObjectWrapper):\n    \"\"\"Contact indexed phone model.\"\"\"\n\n    is_primary = Boolean()\n    number = Text()\n    normalized_number = Text()\n    phone_id = Keyword()\n    type = Keyword()\n    uri = Keyword()\n\n\nclass IndexedSocialIdentity(InnerObjectWrapper):\n    \"\"\"Contact indexed social identity model.\"\"\"\n\n    name = Text()\n    type = Keyword()\n    # Abstract everything else in a map\n    infos = Nested()\n\n\nclass IndexedContact(BaseIndexDocument):\n    \"\"\"Indexed contact model.\"\"\"\n\n    doc_type = 'indexed_contact'\n\n    user_id = Keyword()\n    contact_id = Keyword()\n\n    additional_name = Keyword()\n    addresses = Nested(doc_class=IndexedPostalAddress)\n    avatar = Keyword()\n    date_insert = Date()\n    date_update = Date()\n    deleted = Date()\n    emails = Nested(doc_class=IndexedInternetAddress)\n    family_name = Keyword()\n    given_name = Keyword()\n    groups = Keyword(multi=True)\n    identities = Nested(doc_class=IndexedSocialIdentity)\n    ims = Nested(doc_class=IndexedInternetAddress)\n    infos = Nested()\n    name_prefix = Keyword()\n    name_suffix = Keyword()\n    organizations = Nested(doc_class=IndexedOrganization)\n    phones = Nested(doc_class=IndexedPhone)\n    pi = Object(doc_class=PIIndexModel)\n    privacy_features = Object()\n    public_key = Nested()\n    social_identities = Nested(doc_class=IndexedSocialIdentity)\n    tags = Keyword(multi=True)\n    title = Text()\n\n    @property\n    def contact_id(self):\n        \"\"\"The compound primary key for a contact is contact_id.\"\"\"\n        return self.meta.id\n\n    @classmethod\n    def build_mapping(cls):\n        \"\"\"Create elasticsearch indexed_contacts mapping object for an user.\"\"\"\n        m = Mapping(cls.doc_type)\n        m.meta('_all', enabled=True)\n\n        m.field('user_id', 'keyword')\n        m.field('contact_id', 'keyword')\n\n        m.field('additional_name', 'text', fields={\n            \"normalized\": {\"type\": \"text\", \"analyzer\": \"text_analyzer\"}\n        })\n        # addresses\n        addresses = Nested(doc_class=IndexedPostalAddress, include_in_all=True,\n                           properties={\n                               \"address_id\": \"keyword\",\n                               \"label\": \"text\",\n                               \"type\": \"keyword\",\n                               \"is_primary\": \"boolean\",\n                               \"street\": \"text\",\n                               \"city\": \"text\",\n                               \"postal_code\": \"keyword\",\n                               \"country\": \"text\",\n                               \"region\": \"text\"\n                           })\n        m.field(\"addresses\", addresses)\n        m.field(\"avatar\", \"keyword\")\n        m.field('date_insert', 'date')\n        m.field('date_update', 'date')\n        m.field('deleted', 'date')\n        # emails\n        internet_addr = Nested(doc_class=IndexedInternetAddress,\n                               include_in_all=True,\n                               )\n        internet_addr.field(\"address\", \"text\", analyzer=\"text_analyzer\",\n                            fields={\n                                \"raw\": {\"type\": \"keyword\"},\n                                \"parts\": {\"type\": \"text\",\n                                          \"analyzer\": \"email_analyzer\"}\n                            })\n        internet_addr.field(\"email_id\", Keyword())\n        internet_addr.field(\"is_primary\", Boolean())\n        internet_addr.field(\"label\", \"text\", analyzer=\"text_analyzer\")\n        internet_addr.field(\"type\", Keyword())\n        m.field(\"emails\", internet_addr)\n\n        m.field('family_name', \"text\", fields={\n            \"normalized\": {\"type\": \"text\", \"analyzer\": \"text_analyzer\"}\n        })\n        m.field('given_name', 'text', fields={\n            \"normalized\": {\"type\": \"text\", \"analyzer\": \"text_analyzer\"}\n        })\n        m.field(\"groups\", Keyword(multi=True))\n        # social ids\n        social_ids = Nested(doc_class=IndexedSocialIdentity,\n                            include_in_all=True,\n                            properties={\n                                \"name\": \"text\",\n                                \"type\": \"keyword\",\n                                \"infos\": Nested()\n                            })\n        m.field(\"identities\", social_ids)\n        m.field(\"ims\", internet_addr)\n        m.field(\"infos\", Nested())\n        m.field('name_prefix', 'keyword')\n        m.field('name_suffix', 'keyword')\n        # organizations\n        organizations = Nested(doc_class=IndexedOrganization,\n                               include_in_all=True)\n        organizations.field(\"deleted\", Boolean())\n        organizations.field(\"department\", \"text\", analyzer=\"text_analyzer\")\n        organizations.field(\"is_primary\", Boolean())\n        organizations.field(\"job_description\", \"text\")\n        organizations.field(\"label\", \"text\", analyzer=\"text_analyzer\")\n        organizations.field(\"name\", 'text', fields={\n            \"normalized\": {\"type\": \"text\", \"analyzer\": \"text_analyzer\"}\n        })\n        organizations.field(\"organization_id\", Keyword())\n        organizations.field(\"title\", Keyword())\n        organizations.field(\"type\", Keyword())\n        m.field(\"organizations\", organizations)\n        # phones\n        phones = Nested(doc_class=IndexedPhone, include_in_all=True,\n                        properties={\n                            \"is_primary\": \"boolean\",\n                            \"number\": \"text\",\n                            \"normalized_number\": \"text\",\n                            \"phone_id\": \"keyword\",\n                            \"type\": \"keyword\",\n                            \"uri\": \"keyword\"\n                        })\n\n        m.field(\"phones\", phones)\n        # pi\n        pi = Object(doc_class=PIIndexModel, include_in_all=True,\n                    properties={\n                        \"comportment\": \"integer\",\n                        \"context\": \"integer\",\n                        \"date_update\": \"date\",\n                        \"technic\": \"integer\",\n                        \"version\": \"integer\"\n                    })\n        m.field(\"pi\", pi)\n        m.field(\"privacy_features\", Object(include_in_all=True))\n        m.field(\"public_key\", Nested())\n        m.field(\"social_identities\", social_ids)\n        m.field(\"tags\", Keyword(multi=True))\n        m.field('title', 'text', analyzer=\"text_analyzer\",\n                fields={\n                    \"raw\": {\"type\": \"keyword\"}\n                })\n\n        return m\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/device/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/device/core.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen device core classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport uuid\n\nfrom .store import Device as ModelDevice\nfrom .store import DeviceLocation as ModelDeviceLocation\nfrom .store import DeviceConnectionLog as ModelDeviceLog\n\nfrom caliopen_storage.core.mixin import MixinCoreRelation, MixinCoreNested\nfrom caliopen_main.common.core import BaseUserCore\nfrom caliopen_main.common.core import PublicKey, BaseUserRelatedCore\nfrom caliopen_main.common.parameters import NewPublicKey\nfrom caliopen_main.device.parameters import NewDevice\n\nfrom caliopen_pi.qualifiers import NewDeviceQualifier\n\n\nlog = logging.getLogger(__name__)\n\n\nclass DeviceLocation(BaseUserRelatedCore):\n    \"\"\"Locations defined for a device to restrict access.\"\"\"\n\n    _model_class = ModelDeviceLocation\n    _pkey_name = 'address'\n\n\nclass DeviceLog(BaseUserRelatedCore):\n    \"\"\"Locations defined for a device to restrict access.\"\"\"\n\n    _model_class = ModelDeviceLog\n    _pkey_name = 'timestamp'\n\n\nclass Device(BaseUserCore, MixinCoreRelation, MixinCoreNested):\n    \"\"\"User device core class.\"\"\"\n\n    _model_class = ModelDevice\n    _pkey_name = 'device_id'\n\n    _relations = {\n        'public_keys': PublicKey,\n        'locations': DeviceLocation,\n    }\n\n    @classmethod\n    def create_from_parameter(cls, user, param, headers):\n        \"\"\"Create a device from API parameters.\"\"\"\n        dev = NewDevice()\n        dev.device_id = param['device_id']\n        dev.user_agent = headers.get('User-Agent')\n        dev.ip_creation = headers.get('X-Forwarded-For')\n        dev.status = 'verified' if 'status' not in param else param['status']\n        qualifier = NewDeviceQualifier(user)\n        qualifier.process(dev)\n        if 'name' in param:\n            # Used to set the default device\n            dev.name = param['name']\n        else:\n            dev_name = 'new device'\n            if 'device_type' in dev.privacy_features:\n                dev_ext = dev.privacy_features['device_type']\n                dev_name = '{} {}'.format(dev_name, dev_ext)\n            dev.name = dev_name\n\n        dev.type = dev.privacy_features.get('device_type', 'other')\n        # Device ecdsa key\n        dev_key = NewPublicKey()\n        dev_key.key_id = uuid.uuid4()\n        dev_key.resource_id = dev.device_id\n        dev_key.resource_type = 'device'\n        dev_key.label = 'ecdsa key'\n        dev_key.kty = 'ec'\n        dev_key.use = 'sig'\n        dev_key.x = int(param['ecdsa_key']['x'], 16)\n        dev_key.y = int(param['ecdsa_key']['y'], 16)\n        dev_key.crv = param['ecdsa_key']['curve']\n        # XXX Should be better design\n        alg_map = {\n            'P-256': 'ES256',\n            'P-384': 'ES384',\n            'P-521': 'ES512'\n        }\n        dev_key.alg = alg_map[dev_key.crv]\n        return Device.create(user, dev, public_keys=[dev_key])\n\n    @classmethod\n    def create(cls, user, device, **related):\n        \"\"\"Create a new device for an user.\"\"\"\n        device.validate()\n        attrs = {'device_id': device.device_id,\n                 'type': device.type,\n                 'name': device.name,\n                 'user_agent': device.user_agent,\n                 'ip_creation': device.ip_creation,\n                 'privacy_features': device.privacy_features,\n                 'status': device.status}\n\n        core = super(Device, cls).create(user, **attrs)\n        log.debug('Created device %s' % core.device_id)\n        related_cores = {}\n        for k, v in related.iteritems():\n            if k in cls._relations:\n                for obj in v:\n                    log.debug('Processing object %r' % obj.to_native())\n                    # XXX check only one is_primary per relation using it\n                    new_core = core._relations[k].create(user, **obj)\n                    related_cores.setdefault(k, []).append(new_core.to_dict())\n                    log.debug('Created related core %r' % new_core)\n        return core\n\n    def check_locations(self, ipaddr):\n        \"\"\"Check if the location of device is in related authorized ips.\"\"\"\n        if self.locations:\n            for loc in self.locations:\n                log.info(\"Testing cidr {0} with ip {1}\".\n                         format(loc.address, ipaddr))\n        return True\n\n    def _log_action(self, ipaddr, action):\n        DeviceLog.create(self.user, self.device_id,\n                         ipaddr=ipaddr,\n                         type=action)\n\n    def login(self, ipaddr):\n        \"\"\"Login action for a device.\"\"\"\n        log.info(\"Logging device {0} for user {1}\".format(self.device_id,\n                                                          self.user_id))\n        if not self.check_locations(ipaddr):\n            raise Exception(\"Device IP Address not in allowed locations\")\n        self._log_action(ipaddr, 'login')\n\n    def logout(self, ipaddr):\n        \"\"\"Logout action for a device.\"\"\"\n        self._log_action(ipaddr, \"logout\")\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/device/parameters.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen device parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom schematics.models import Model\nfrom schematics.transforms import blacklist\nfrom schematics.types import DateTimeType, StringType, UUIDType\nfrom schematics.types.compound import ListType, ModelType, DictType\n\nfrom caliopen_main.pi.parameters import PIParameter\nimport caliopen_storage.helpers.json as helpers\n\nDEVICE_TYPES = ['other', 'desktop', 'laptop', 'smartphone', 'tablet']\n\n\nclass DeviceLocation(Model):\n    \"\"\"Location structure for a device.\"\"\"\n\n    user_id = UUIDType()\n    device_id = UUIDType()\n    address = StringType(required=True)  # With CIDR notation\n    type = StringType()\n    country = StringType()\n\n    class Options:\n        serialize_when_none = False\n        roles = {'default': blacklist('user_id', 'device_id')}\n\n\nclass NewDevice(Model):\n    \"\"\"Structure to create a new user device.\"\"\"\n\n    name = StringType(required=True)\n    type = StringType(required=True, choices=DEVICE_TYPES, default='other')\n\n    locations = ListType(ModelType(DeviceLocation), default=lambda: [])\n    user_agent = StringType()\n    ip_creation = StringType()\n\n\nclass Device(NewDevice):\n    \"\"\"Parameter for an existing device.\"\"\"\n\n    device_id = UUIDType()\n    user_id = UUIDType()\n    date_insert = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    date_revoked = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                                tzd=u'utc')\n    status = StringType()\n\n    privacy_features = DictType(StringType, default=lambda: {})\n    pi = ModelType(PIParameter)\n\n    class Options:\n        serialize_when_none = False\n        roles = {'default': blacklist('user_id')}\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/device/store.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen cassandra models related to device.\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport uuid\n\nfrom datetime import datetime\n\nfrom cassandra.cqlengine import columns\n\nfrom caliopen_storage.store.model import BaseModel\nfrom caliopen_main.pi.objects import PIModel\n\n\nlog = logging.getLogger(__name__)\n\n\nclass DeviceLocation(BaseModel):\n    \"\"\"Device defined location, based on IP address.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    device_id = columns.UUID(primary_key=True)\n    address = columns.Text(primary_key=True)    # IP address with CIDR\n    type = columns.Text()                       # home/work/etc\n    country = columns.Text()\n\n\nclass Device(BaseModel):\n    \"\"\"User device.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    device_id = columns.UUID(primary_key=True, default=uuid.uuid4)\n\n    name = columns.Text()\n    date_insert = columns.DateTime(required=True, default=datetime.utcnow)\n    date_revoked = columns.DateTime()\n    type = columns.Text(required=True)      # laptop, desktop, smartphone, etc\n    status = columns.Text(default='unverified')\n    user_agent = columns.Text()\n    ip_creation = columns.Text()\n    privacy_features = columns.Map(columns.Text, columns.Text)\n    pi = columns.UserDefinedType(PIModel)\n\n\nclass DeviceConnectionLog(BaseModel):\n    \"\"\"Log a device connection.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    resource_id = columns.UUID(primary_key=True)  # device_id\n    date_insert = columns.DateTime(primary_key=True, default=datetime.utcnow)\n    ip_address = columns.Text()\n    type = columns.Text()       # Connection type (login/logout)\n    country = columns.Text()    # Geoip detected country\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/core/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .discussion import Discussion\n\n__all__ = ['Discussion']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/core/discussion.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen core discussion related classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom caliopen_main.common.objects.base import ObjectUser\n\nfrom caliopen_main.participant.core import participants_from_uris, \\\n    hash_participants_uri\nfrom caliopen_main.participant.core import ParticipantHash\n\nlog = logging.getLogger(__name__)\n\n\nclass Discussion(ObjectUser):\n    \"\"\"Discussion core object.\"\"\"\n\n    def upsert_lookups_for_participants(self, participants):\n        \"\"\"\n        Ensure that participants lookup and hash lookup tables are filled\n        for these participants\n\n        :param user:\n        :param participants: a collection of parameters/Participant\n        :type uris: set\n        :return: Discussion\n        \"\"\"\n        if not participants:\n            raise Exception(\"missing mandatory property to create lookup entry\")\n\n        uris = hash_participants_uri(participants)\n        hash_lookup = ParticipantHash.find(user_id=self.user.user_id,\n                                           kind=\"uris\",\n                                           key=uris['hash'])\n        if len(hash_lookup) > 0:\n            self.participants = hash_lookup[0].components\n            self.participants_hash = hash_lookup[0].value\n        else:\n            parts = participants_from_uris(self.user, uris['uris'],\n                                           uris['hash'])\n            self.participants = parts['components']\n            self.participants_hash = parts['hash']\n\n        return self\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/objects/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .discussion import Discussion\n\n__all__ = {'Discussion'}\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/objects/discussion.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen message object classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom caliopen_main.common.objects.base import ObjectUser\nfrom caliopen_main.participant.core import hash_participants_uri, \\\n    participants_from_uris\nfrom caliopen_main.participant.store import ParticipantHash\n\n\nclass Discussion(ObjectUser):\n    _attrs = {\n        'uris_hash': types.StringType,\n        'uris': [types.StringType],\n        'participants_hash': types.StringType,\n        'participants': [types.StringType],\n    }\n\n    def upsert_lookups_for_participants(self, user, participants):\n        \"\"\"\n        Ensure that participants lookup and hash lookup tables are filled\n        for these participants\n\n        :param user:\n        :param participants: a collection of parameters/Participant\n        :type uris: set\n        :return: Discussion\n        \"\"\"\n        if not participants:\n            raise Exception(\"missing mandatory property to create lookup entry\")\n\n        uris = hash_participants_uri(participants)\n        self.uris = uris['uris']\n        self.uris_hash = uris['hash']\n        hash_lookup = ParticipantHash.find(user_id=user.user_id, kind=\"uris\",\n                                           key=uris['hash'])\n        if len(hash_lookup) > 0:\n            self.participants = hash_lookup[0].components\n            self.participants_hash = hash_lookup[0].value\n        else:\n            parts = participants_from_uris(user, uris['uris'],\n                                           uris['hash'])\n            self.participants = parts['components']\n            self.participants_hash = parts['hash']\n\n        return self\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/parameters/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .discussion import Discussion\n\n__all__ = [\n    'Discussion'\n]\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/parameters/discussion.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom schematics.models import Model\nfrom schematics.types import (StringType, DateTimeType,\n                              IntType, UUIDType, BooleanType)\nfrom schematics.types.compound import ListType, ModelType\nfrom schematics.transforms import blacklist\n\nfrom caliopen_main.participant.parameters import Participant\nimport caliopen_storage.helpers.json as helpers\n\n\nclass Discussion(Model):\n    \"\"\"Existing discussion.\"\"\"\n\n    user_id = UUIDType()\n    discussion_id = StringType(required=True) # = participants_hash\n    date_insert = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    date_update = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    last_message_subject = StringType()\n    excerpt = StringType(required=True)\n    importance_level = IntType(required=True, default=0)\n    participants = ListType(ModelType(Participant), default=lambda: [])\n    total_count = IntType(required=True, default=0)\n    unread_count = IntType(required=True, default=0)\n    attachment_count = IntType(default=0)\n    subject = StringType()\n    protocol = StringType()\n    last_message_id = UUIDType(required=True)\n\n    class Options:\n        roles = {'default': blacklist('user_id')}\n        serialize_when_none = False\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/store/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .discussion_index import DiscussionIndexManager\n\n__all__ = ['DiscussionIndexManager']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/store/discussion_index.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen disccions index classes.\n\nDiscussions are not really indexed, they are result of messages aggregations.\n\nSo there is not direct document mapping, only helpers to find discussions\nand build a suitable representation for displaying.\n\n\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\n\nfrom elasticsearch_dsl import A\nfrom caliopen_storage.store.model import BaseIndexDocument\nfrom caliopen_main.message.store.message_index import IndexedMessage\n\nlog = logging.getLogger(__name__)\n\n\nclass DiscussionIndex(object):\n    \"\"\"Informations from index about a discussion.\"\"\"\n\n    total_count = 0\n    unread_count = 0\n    attachment_count = 0\n    last_message = None\n\n    def __init__(self, id):\n        self.discussion_id = id\n\n\nclass DiscussionIndexManager(object):\n    \"\"\"Manager for building discussions from index storage layer.\"\"\"\n\n    def __init__(self, user):\n        self.index = user.shard_id\n        self.user_id = user.user_id\n        self.proxy = BaseIndexDocument.client()\n\n    def _prepare_search(self):\n        \"\"\"Prepare a dsl.Search object on current index.\"\"\"\n        search = IndexedMessage.search(using=self.proxy,\n                                       index=self.index)\n        search = search.filter('term', user_id=self.user_id)\n        return search\n\n    def __search_ids(self, limit, offset, min_pi, max_pi, min_il, max_il):\n        \"\"\"Search discussions ids as a bucket aggregation.\"\"\"\n        # TODO : search on participants_hash instead\n        search = self._prepare_search(). \\\n            filter(\"range\", importance_level={'gte': min_il, 'lte': max_il})\n        # Do bucket term aggregation, sorted by last_message date\n        size = offset + (limit * 2)\n        agg = A('terms', field='discussion_id',\n                order={'last_message': 'desc'}, size=size, shard_size=size)\n        search.aggs.bucket('discussions', agg) \\\n            .metric('last_message', 'max', field='date_sort') \\\n            .bucket(\"unread\", \"filter\", term={\"is_unread\": True})\n\n        result = search.source(exclude=[\"*\"]).execute()\n        if hasattr(result, 'aggregations'):\n            # Something found\n            buckets = result.aggregations.discussions.buckets\n            # XXX Ugly but don't find a way to paginate on bucket aggregation\n            buckets = buckets[offset:offset + limit]\n            total = result.aggregations.discussions.sum_other_doc_count\n            # remove last_message for now as it doesn't have relevant attrs\n            for discussion in buckets:\n                del discussion[\"last_message\"]\n            return buckets, total\n        log.debug('No result found on index {}'.format(self.index))\n        return {}, 0\n\n    def get_last_message(self, discussion_id, min_il, max_il, include_draft):\n        \"\"\"Get last message of a given discussion.\"\"\"\n        search = self._prepare_search() \\\n            .filter(\"match\", discussion_id=discussion_id) \\\n            .filter(\"range\", importance_level={'gte': min_il, 'lte': max_il})\n\n        if not include_draft:\n            search = search.filter(\"match\", is_draft=False)\n\n        result = search.sort('-date_sort')[0:1].execute()\n        if not result.hits:\n            # XXX what to do better if not found ?\n            return {}\n        return result.hits[0]\n\n    def list_discussions(self, limit=10, offset=0, min_pi=0, max_pi=0,\n                         min_il=-10, max_il=10):\n        \"\"\"Build a list of limited number of discussions.\"\"\"\n        buckets, total = self.__search_ids(limit, offset, min_pi, max_pi,\n                                           min_il,\n                                           max_il)\n        discussions = []\n        for bucket in buckets:\n            # TODO : les buckets seront des hash_participants, donc il faut créer la liste des discussion_id avant et itérer là-dessus\n            message = self.get_last_message(bucket['key'],\n                                            min_il, max_il,\n                                            True)\n            discussion = DiscussionIndex(bucket['key'])\n            discussion.total_count = bucket['doc_count']\n            discussion.unread_count = bucket['unread']['doc_count']\n            discussion.last_message = message\n            # XXX build others values from index\n            discussions.append(discussion)\n        # XXX total do not work completly, hack a bit\n        return discussions, total + len(discussions)\n\n    def message_belongs_to(self, discussion_id, message_id):\n        \"\"\"Search if a message belongs to a discussion\"\"\"\n\n        msg = IndexedMessage.get(message_id, using=self.proxy, index=self.index)\n        return str(msg.discussion_id) == str(discussion_id)\n\n    def get_by_id(self, discussion_id, min_il=0, max_il=100):\n        \"\"\"Return a single discussion by discussion_id\"\"\"\n\n        # TODO : search by multiple discussion_id because they are hashes now\n        search = self._prepare_search() \\\n            .filter(\"match\", discussion_id=discussion_id)\n        search.aggs.bucket('discussions', A('terms', field='discussion_id')) \\\n            .bucket(\"unread\", \"filter\", term={\"is_unread\": True})\n        result = search.execute()\n        if not result.hits or len(result.hits) < 1:\n            return None\n\n        message = self.get_last_message(discussion_id, min_il, max_il, True)\n        discussion = DiscussionIndex(discussion_id)\n        discussion.total_count = result.hits.total\n        discussion.last_message = message\n        discussion.unread_count = result.aggregations.discussions.buckets[\n            0].unread.doc_count\n        return discussion\n\n    def get_by_uris(self, uris_hashes, min_il=0, max_il=100):\n        \"\"\"\n\n        :param uris_hashes: an array of uris hashes\n        :param min_il:\n        :param max_il:\n        :return:\n        \"\"\"\n        search = self._prepare_search(). \\\n            filter(\"terms\", discussion_id=uris_hashes). \\\n            filter(\"range\", importance_level={'gte': min_il, 'lte': max_il})\n\n        agg = A('terms', field='discussion_id',\n                order={'last_message': 'desc'})\n        search.aggs.bucket('discussions', agg). \\\n            metric('last_message', 'max', field='date_sort'). \\\n            bucket(\"unread\", \"filter\", term={\"is_unread\": True})\n\n        result = search.execute()\n        if not result.hits or len(result.hits) < 1:\n            return None\n\n        return result\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/discussion/store/discussion_lookup.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen storage model for messages.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom cassandra.cqlengine import columns\nfrom caliopen_storage.store.model import BaseModel\n\n\n# TODO : make user of ParticipantLookup table\nclass DiscussionListLookup(BaseModel):\n    \"\"\"Lookup discussion by external list-id.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    list_id = columns.Text(primary_key=True)\n    discussion_id = columns.UUID()  # TODO : primary ?\n\n\nclass DiscussionThreadLookup(BaseModel):\n    \"\"\"Lookup discussion by external thread's root message_id.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    external_root_msg_id = columns.Text(primary_key=True)\n    discussion_id = columns.UUID()\n\n\nclass DiscussionLookup(BaseModel):\n    user_id = columns.UUID(primary_key=True)\n    key = columns.Text(primary_key=True)\n    value = columns.Text(primary_key=True)\n    date_insert = columns.DateTime()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/core/__init__.py",
    "content": "from .raw import RawMessage, UserRawLookup\nfrom .external_references import MessageExternalRefLookup\n\n__all__ = ['RawMessage', 'UserRawLookup', 'MessageExternalRefLookup']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/core/external_references.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen core message external references lookup class.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom caliopen_main.common.core import BaseUserCore\nfrom ..store import MessageExternalRefLookup as ModelMessageExternalRefLookup\n\n\nclass MessageExternalRefLookup(BaseUserCore):\n    \"\"\"Lookup message by external message-id\"\"\"\n\n    _model_class = ModelMessageExternalRefLookup\n    _pkey_name = 'external_msg_id'"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/core/raw.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen core raw message class.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport uuid\nimport logging\n\nfrom minio import Minio\nfrom minio.error import ResponseError\nimport urlparse\n\nfrom minio import Minio\nfrom minio.error import ResponseError\n\nfrom caliopen_storage.core import BaseCore\nfrom caliopen_main.common.core import BaseUserCore\n\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_storage.config import Configuration\n\nfrom ..store import (RawMessage as ModelRaw,\n                     UserRawLookup as ModelUserRawLookup)\nfrom caliopen_main.message.parsers.mail import MailMessage\n\nlog = logging.getLogger(__name__)\n\n\nclass RawMessage(BaseCore):\n    \"\"\"\n    Raw message core.\n\n    Store in binary form any message before processing\n    \"\"\"\n\n    _model_class = ModelRaw\n    _pkey_name = 'raw_msg_id'\n\n    @classmethod\n    def create(cls, raw):\n        \"\"\"Create raw message.\"\"\"\n        key = uuid.uuid4()\n        size = len(raw)\n        return super(RawMessage, cls).create(raw_msg_id=key,\n                                             raw_data=raw,\n                                             raw_size=size)\n\n    @classmethod\n    def get(cls, raw_msg_id):\n        \"\"\"\n        Get raw message from db or ObjectStorage service\n\n        :param raw_msg_id:\n        :return: a RawMessage or NotFound exception\n        \"\"\"\n        try:\n            raw_msg = super(RawMessage, cls).get(raw_msg_id)\n        except Exception as exc:\n            log.warn(exc)\n            raise NotFound\n\n        if len(raw_msg.raw_data) == 0 and raw_msg.uri != '':\n            # means raw message data have been stored in object store\n            # need to retrieve raw_data from it\n            url = urlparse.urlsplit(raw_msg.uri)\n            path = url.path.strip(\"/\")\n            if url.scheme == 's3':\n                minioConf = Configuration(\"global\").get(\"object_store\")\n                minioClient = Minio(minioConf[\"endpoint\"],\n                                    access_key=minioConf[\"access_key\"],\n                                    secret_key=minioConf[\"secret_key\"],\n                                    secure=False,\n                                    region=minioConf[\"location\"])\n                try:\n                    resp = minioClient.get_object(url.netloc, path)\n                except Exception as exc:\n                    log.warn(exc)\n                    raise NotFound\n                # resp is a urllib3.response.HTTPResponse class\n                try:\n                    raw_msg.raw_data = resp.data\n                except Exception as exc:\n                    log.warn(exc)\n                    raise NotFound\n            else:\n                log.warn(\"raw message uri scheme not implemented\")\n                raise NotFound\n\n        return raw_msg\n\n    @classmethod\n    def get_for_user(cls, user_id, raw_msg_id):\n        \"\"\"\n        Get raw message by raw_msg_id, if message belongs to user.\n\n        :param: user_id is a string\n        :param: raw_msg_id is a string\n        :return: a RawMessage or None\n        \"\"\"\n        if not UserRawLookup.belongs_to_user(user_id, raw_msg_id):\n            return None\n        try:\n            return cls.get(raw_msg_id)\n        except NotFound:\n            return None\n\n    def parse(self):\n        \"\"\"Parse raw message to get a formatted object.\"\"\"\n        return MailMessage(self)\n\n\nclass UserRawLookup(BaseUserCore):\n    \"\"\"User raw message affectation.\"\"\"\n\n    _model_class = ModelUserRawLookup\n    _pkey_name = 'raw_msg_id'\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/objects/__init__.py",
    "content": "from .message import Message\n\n__all__ = ['Message']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/objects/attachment.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen message object classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom uuid import UUID\nfrom caliopen_main.common.objects.base import ObjectJsonDictifiable\nfrom ..store.attachment import MessageAttachment as ModelMessageAttachment\nfrom ..store.attachment_index import IndexedMessageAttachment\n\n\nclass MessageAttachment(ObjectJsonDictifiable):\n\n    \"\"\"attachment's attributes, nested within message object\"\"\"\n\n    _attrs = {\n        'content_type': types.StringType,\n        'file_name': types.SliceType,\n        'is_inline': types.BooleanType,\n        'size': types.IntType,\n        'temp_id': UUID,\n        'url': types.StringType,\n        'mime_boundary': types.StringType\n    }\n\n    _model_class = ModelMessageAttachment\n    _index_class = IndexedMessageAttachment\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/objects/external_references.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen message object classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom caliopen_main.common.objects.base import ObjectJsonDictifiable\nfrom ..store.external_references import ExternalReferences as ModelExtRef\nfrom ..store.external_references_index import IndexedExternalReferences\n\n\nclass ExternalReferences(ObjectJsonDictifiable):\n    \"\"\"external references, nested within message object\"\"\"\n\n    _attrs = {\n        'ancestors_ids': [types.StringType],\n        'message_id': types.StringType,\n        'parent_id': types.StringType\n    }\n\n    _model_class = ModelExtRef\n    _index_class = IndexedExternalReferences\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/objects/message.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen message object classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom caliopen_main.common.objects.base import ObjectIndexable\n\nimport uuid\nfrom uuid import UUID\nimport datetime\nimport pytz\nimport json\nimport copy\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_main.pi.objects import PIObject\n\nfrom ..store import Message as ModelMessage\nfrom ..store import IndexedMessage\nfrom ..parameters.message import Message as ParamMessage\nfrom ..parameters.draft import Draft\nfrom ..core import RawMessage\nfrom .attachment import MessageAttachment\nfrom .external_references import ExternalReferences\nfrom caliopen_main.participant.objects.participant import Participant\nfrom schematics.types import UUIDType\nfrom caliopen_main.participant.parameters import \\\n    Participant as IndexedParticipant\nfrom caliopen_main.common import errors as err\n\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\nclass Message(ObjectIndexable):\n    \"\"\"Message object class.\"\"\"\n\n    # TODO : manage attrs that should not be editable directly by users\n    _attrs = {\n        'attachments': [MessageAttachment],\n        'body_html': types.StringType,\n        'body_plain': types.StringType,\n        'date': datetime.datetime,\n        'date_delete': datetime.datetime,\n        'date_insert': datetime.datetime,\n        'date_sort': datetime.datetime,\n        'discussion_id': types.StringType,\n        'external_references': ExternalReferences,\n        'importance_level': types.IntType,\n        'is_answered': types.BooleanType,\n        'is_draft': types.BooleanType,\n        'is_unread': types.BooleanType,\n        'is_received': types.BooleanType,\n        'message_id': UUID,\n        'parent_id': UUID,\n        'participants': [Participant],\n        'privacy_features': types.DictType,\n        'pi': PIObject,\n        'raw_msg_id': UUID,\n        'subject': types.StringType,\n        'tags': [types.StringType],\n        'protocol': types.StringType,\n        'user_id': UUID,\n        'user_identities': [UUID],\n    }\n\n    _json_model = ParamMessage\n\n    # operations related to cassandra\n    _model_class = ModelMessage\n    _db = None  # model instance with datas from db\n    _pkey_name = \"message_id\"\n\n    #  operations related to elasticsearch\n    _index_class = IndexedMessage\n    _index = None\n\n    @property\n    def raw(self):\n        \"\"\"Return raw text from pristine raw message.\"\"\"\n        msg = RawMessage.get_for_user(self.user_id, self.raw_msg_id)\n        return msg.raw_data\n\n    @property\n    def raw_json(self):\n        \"\"\"Return json representation of pristine raw message.\"\"\"\n        msg = RawMessage.get_for_user(self.user_id, self.raw_msg_id)\n        return json.loads(msg.json_rep)\n\n    @property\n    def external_msg_id(self):\n        if self.external_references:\n            return self.external_references.message_id\n        return None\n\n    @property\n    def user_identity(self):\n        \"\"\"\n        return first user_identity\n        \"\"\"\n        return self.user_identities[0] if self.user_identities else None\n\n    @classmethod\n    def create_draft(cls, user, **params):\n        \"\"\"\n        Create and save a new message (draft) for an user.\n\n        :params: a NewMessage dict\n        \"\"\"\n        # silently remove unexpected props within patch if not in strict mode\n        strict_patch = Configuration('global').get('apiV1.strict_patch', False)\n        if not strict_patch:\n            allowed_properties = [\n                \"body\",\n                \"message_id\",\n                \"parent_id\",\n                \"participants\",\n                \"subject\",\n                \"user_identities\",\n                \"privacy_features\",\n            ]\n            for key, value in params.items():\n                if key not in allowed_properties:\n                    del (params[key])\n        try:\n            draft_param = Draft(params, strict=strict_patch)\n            if draft_param.message_id:\n                draft_param.validate_uuid(user.user_id)\n            else:\n                draft_param.message_id = uuid.uuid4()\n            discussion_id = draft_param.validate_consistency(user, True)\n        except Exception as exc:\n            log.warn(\"create_draft error %r\" % exc)\n            raise exc\n\n        message = Message(user)\n        message.unmarshall_json_dict(draft_param.to_primitive())\n        message.user_id = UUID(user.user_id)\n        message.is_draft = True\n        message.is_received = False\n        message.discussion_id = discussion_id\n\n        if not message.protocol:\n            log.warn(\"failed to pick a protocol\")\n            raise Exception(\"`message protocol is missing\")\n\n        # forbid multiple protocol\n        for participant in message.participants:\n            if participant.protocol != message.protocol:\n                log.warning(\"Different protocols detected {0} and {1}\".\n                            format(participant.protocol, message.protocol))\n\n        message.date = message.date_sort = message.date_insert = \\\n            datetime.datetime.now(tz=pytz.utc)\n\n        try:\n            message.marshall_db()\n            message.save_db()\n        except Exception as exc:\n            log.warn(exc)\n            raise exc\n        try:\n            message.marshall_index()\n            message.save_index(wait_for=True)\n        except Exception as exc:\n            log.warn(exc)\n            raise exc\n        return message\n\n    def patch_draft(self, user, patch, **options):\n        \"\"\"Operation specific to draft, before applying generic patch.\"\"\"\n        try:\n            params = dict(patch)\n        except Exception as exc:\n            log.info(exc)\n            raise err.PatchError(message=exc.message)\n        # silently remove unexpected props within patch if not in strict mode\n        strict_patch = Configuration('global').get('apiV1.strict_patch', False)\n        if not strict_patch:\n            allowed_properties = [\n                \"body\",\n                \"current_state\",\n                \"user_identities\",\n                \"message_id\",\n                \"parent_id\",\n                \"participants\",\n                \"subject\",\n                \"privacy_features\",\n            ]\n            for key, value in params.items():\n                if key not in allowed_properties:\n                    del (params[key])\n\n            for key, value in params[\"current_state\"].items():\n                if key not in allowed_properties:\n                    del (params[\"current_state\"][key])\n\n        try:\n            self.get_db()\n            self.unmarshall_db()\n        except Exception as exc:\n            log.info(\"patch_draft() failed to get msg from db: {}\".format(\n                exc))\n            raise exc\n\n        if not self.is_draft:\n            raise err.PatchUnprocessable(message=\"this message is not a draft\")\n        try:\n            current_state = params.pop(\"current_state\")\n            draft_param = Draft(params, strict=strict_patch)\n        except Exception as exc:\n            log.info(exc)\n            raise err.PatchError(message=exc.message)\n\n        # add missing params to be able to check consistency\n        self_dict = self.marshall_dict()\n        if \"message_id\" not in params and self.message_id:\n            draft_param.message_id = UUIDType().to_native(self.message_id)\n\n        if \"parent_id\" not in params and self.parent_id:\n            draft_param.parent_id = UUIDType().to_native(self.parent_id)\n\n        if \"subject\" not in params:\n            draft_param.subject = self.subject\n\n        if \"participants\" not in params and self.participants:\n            for participant in self_dict['participants']:\n                indexed = IndexedParticipant(participant)\n                draft_param.participants.append(indexed)\n\n\n        if \"user_identities\" not in params and self.user_identities:\n            draft_param.user_identities = self_dict[\"user_identities\"]\n\n        # make sure the <from> participant is present\n        # and is consistent with selected user's identity\n        try:\n            new_discussion_id = draft_param.validate_consistency(user, False)\n        except Exception as exc:\n            log.info(\"consistency validation failed with err : {}\".format(exc))\n            raise err.PatchError(message=exc.message)\n\n        validated_draft = draft_param.serialize()\n        validated_params = copy.deepcopy(params)\n\n        if \"participants\" in params:\n            validated_params[\"participants\"] = validated_draft[\"participants\"]\n        if new_discussion_id != self.discussion_id:\n            # discussion_id has changed, update draft's discussion_id\n            current_state[\"discussion_id\"] = self.discussion_id\n            validated_params[\"discussion_id\"] = new_discussion_id\n\n        # remove empty ids from current state if any\n        if \"parent_id\" in current_state and current_state[\"parent_id\"] == \"\":\n            del (current_state[\"parent_id\"])\n\n        # handle body key mapping to body_plain or body_html\n        # TODO: handle plain/html flag to map to right field\n        if \"body\" in validated_params:\n            validated_params[\"body_plain\"] = validated_params[\"body\"]\n            del (validated_params[\"body\"])\n        if \"body\" in current_state:\n            current_state[\"body_plain\"] = current_state[\"body\"]\n            del (current_state[\"body\"])\n\n        # date should reflect last edit time\n        current_state[\"date\"] = self.date\n        current_state[\"date_sort\"] = self.date_sort\n        validated_params[\"date\"] = validated_params[\"date_sort\"] = \\\n            datetime.datetime.now(tz=pytz.utc)\n\n        validated_params[\"current_state\"] = current_state\n\n        if \"participants\" in current_state and self.participants:\n            # replace participants' label and contact_ids\n            # because frontend has up-to-date data for these properties\n            # which are probably not the one stored in db\n            db_parts = {}\n            for p in self_dict['participants']:\n                db_parts[p['protocol'] + p['type'] + p['address']] = p\n            for i, p in enumerate(current_state['participants']):\n                current_state['participants'][i] =  db_parts[p['protocol'] + p['type'] + p['address']]\n\n        try:\n            self.apply_patch(validated_params, **options)\n        except Exception as exc:\n            log.info(\"apply_patch() failed with error : {}\".format(exc))\n            raise exc\n\n    def unmarshall_json_dict(self, document, **options):\n        super(Message, self).unmarshall_json_dict(document, **options)\n        # TODO: handle html/plain flag to copy \"body\" key into right place\n        if \"body\" in document and document[\"body\"] is not None:\n            self.body_plain = document[\"body\"]\n\n    def marshall_json_dict(self, **options):\n        d = self.marshall_dict()\n        # TODO: handle html/plain regarding user's preferences\n        d[\"body\"] = self.body_plain\n        if \"body_plain\" in d:\n            del (d[\"body_plain\"])\n        if \"body_html\" in d:\n            del (d[\"body_html\"])\n        return self._json_model(d).serialize()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/parameters/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\nfrom .attachment import Attachment\nfrom .draft import Draft\nfrom .external_references import ExternalReferences\nfrom .message import NewMessage, NewInboundMessage, Message\n\n\n__all__ = ['Attachment', 'Draft', 'ExternalReferences',\n           'NewMessage', 'Message']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/parameters/attachment.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom schematics.models import Model\nfrom schematics.types import (StringType, UUIDType,\n                              IntType, BooleanType)\n\n\nclass Attachment(Model):\n    content_type = StringType()\n    file_name = StringType()\n    is_inline = BooleanType()\n    size = IntType()\n    temp_id = UUIDType()\n    url = StringType()  # objectsStore uri for temporary file (draft) or boundary reference for mime-part attachment\n    mime_boundary = StringType()  # for attachments embedded in raw messages\n\n    class Options:\n        serialize_when_none = False\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/parameters/draft.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport re\nimport uuid\nfrom schematics.types import StringType\nfrom .message import NewInboundMessage\nfrom caliopen_main.user.objects.identity import UserIdentity\nfrom caliopen_main.participant.parameters import Participant\nfrom caliopen_main.message.parameters.external_references import \\\n    ExternalReferences\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_main.common.errors import PatchUnprocessable, PatchConflict, \\\n    PatchError\nfrom caliopen_main.message.store import Message as ModelMessage\n\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\nclass Draft(NewInboundMessage):\n    body = StringType()\n\n    def validate_uuid(self, user_id):\n        if not self.message_id or not isinstance(self.message_id, uuid.UUID):\n            raise PatchUnprocessable(\n                message=\"missing or invalid message_id\")\n        try:\n            ModelMessage.get(user_id=user_id,\n                             message_id=self.message_id)\n            raise PatchUnprocessable(message=\"message_id not unique\")\n        except NotFound:\n            pass\n\n    def validate_consistency(self, user, is_new):\n        \"\"\"\n        Function used by create_draft and patch_draft\n        to unsure provided params are consistent with draft's context\n\n        :param user : the user object\n        :param is_new : if true indicates that we want to validate a new draft,\n                                    otherwise it is an update of existing one\n\n        If needed, draft is modified to conform.\n        \"\"\"\n        try:\n            self.validate()\n\n        except Exception as exc:\n            log.exception(\"draft validation failed with error {}\".format(exc))\n            raise exc\n        # copy body to body_plain TODO : manage plain or html switch user pref\n        if hasattr(self, \"body\") and self.body is not None:\n            self.body_plain = self.body\n        else:\n            self.body = self.body_plain = \"\"\n\n        # fill <from> field consistently\n        # based on current user's selected identity\n        from_participant = self._add_from_participant(user)\n\n        if hasattr(self, 'parent_id') \\\n                and self.parent_id is not None \\\n                and self.parent_id != \"\" \\\n                and is_new:\n            # it is a reply, enforce participants\n            # and other mandatory properties\n            try:\n                parent_msg = ModelMessage.get(user_id=user.user_id,\n                                              message_id=self.parent_id)\n            except NotFound:\n                raise PatchError(message=\"parent message not found\")\n            self._build_participants_for_reply(parent_msg, from_participant)\n            self.discussion_id = self.hash_participants\n            self._update_external_references(user)\n            self._build_subject_for_reply(parent_msg)\n        elif self.discussion_id != self.hash_participants:\n            # participants_hash has changed, update lookups\n            self.discussion_id = self.hash_participants\n\n        return self.discussion_id\n\n    def _add_from_participant(self, user):\n\n        if 'user_identities' not in self:\n            raise PatchUnprocessable('Missing user identities')\n\n        if len(self['user_identities']) != 1:\n            raise PatchUnprocessable('Invalid user identities')\n\n        user_identity = UserIdentity(user,\n                                     identity_id=str(\n                                         self['user_identities'][0]))\n        try:\n            user_identity.get_db()\n            user_identity.unmarshall_db()\n        except NotFound:\n            raise PatchUnprocessable(message=\"identity not found\")\n\n        # add 'from' participant with local identity's identifier\n        if not hasattr(self, 'participants'):\n            self.participants = []\n        else:\n            if len(self.participants) > 0:\n                parts = list(self.participants)\n                for i, participant in enumerate(parts):\n                    if re.match(\"from\", participant['type'], re.IGNORECASE):\n                        self.participants.pop(i)\n        from_participant = Participant()\n        from_participant.address = user_identity.identifier.lower()\n        from_participant.label = user_identity.display_name\n        from_participant.protocol = user_identity.protocol\n        from_participant.type = \"From\"\n        from_participant.contact_ids = [user.contact.contact_id]\n        self.participants.append(from_participant)\n\n        # set message's protocol to sender's one\n        if from_participant.protocol in ['email', 'smtp', 'imap']:\n            self.protocol = 'email'\n        else:\n            self.protocol = from_participant.protocol\n\n        return from_participant\n\n    def _build_participants_for_reply(self, parent_msg, sender):\n        \"\"\"\n        Build participants list from message in-reply to.\n\n        - former 'From' recipients are replaced by 'To' recipients\n        - provided identity is used to fill the new 'From' participant\n        - new sender is removed from former recipients\n\n        :param sender: participant previously computed by _add_from_participant\n        \"\"\"\n        if not self.parent_id:\n            return\n        # TODO : manage reply to discussion-list\n        # TODO : and to messages that have a `reply-to` header\n        self.participants = []\n        sender['address'] = sender['address'].lower()\n        for i, participant in enumerate(parent_msg.participants):\n            participant['address'] = participant['address'].lower()\n            if not re.match(sender['address'], participant['address'],\n                            re.IGNORECASE):\n                if re.match(\"from\", participant['type'], re.IGNORECASE):\n                    participant[\"type\"] = \"To\"\n                    self.participants.append(participant)\n                elif not re.match(\"list-id\", participant['type'],\n                                  re.IGNORECASE):\n                    self.participants.append(participant)\n            elif not re.match(\"from\", participant['type'], re.IGNORECASE):\n                self.participants.append(participant)\n\n        # add sender\n        self.participants.append(sender)\n\n    def _build_subject_for_reply(self, parent_msg):\n        \"\"\"\n\n        :param user:\n        :return:\n        \"\"\"\n        # check subject consistency\n        # (https://www.wikiwand.com/en/List_of_email_subject_abbreviations)\n        # for now, we use standard prefix «Re: » (RFC5322#section-3.6.5)\n        p = re.compile(\n            '([\\[\\(] *)?(RE?S?|FYI|RIF|I|FS|VB|RV|ENC|ODP|PD|YNT|ILT|SV|VS|VL|AW|WG|ΑΠ|ΣΧΕΤ|ΠΡΘ|תגובה|הועבר|主题|转发|FWD?) *([-:;)\\]][ :;\\])-]*|$)|\\]+ *$',\n            re.IGNORECASE)\n        # if hasattr(self, 'subject') and self.subject is not None:\n        #    if p.sub('', self.subject).strip() != p.sub('',\n        #                                                parent_msg.subject).strip():\n        #        raise PatchConflict(message=\"subject has been changed\")\n        # else:\n        #    # no subject property provided :\n        #    # add subject from context with only one \"Re: \" prefix\n        self.subject = \"Re: \" + p.sub('', parent_msg.subject, -1)\n\n    def _update_external_references(self, user):\n        \"\"\"\n        copy externals references from current draft's ancestor\n        and change parent_id to reflect new message's hierarchy\n        :return:\n        \"\"\"\n        from caliopen_main.message.objects.message import Message\n        parent_msg = Message(user, message_id=self.parent_id)\n        parent_msg.get_db()\n        parent_msg.unmarshall_db()\n        if parent_msg:\n            self.external_references = ExternalReferences(\n                vars(parent_msg.external_references))\n            self.external_references.ancestors_ids.append(\n                parent_msg.external_references.message_id)\n            self.external_references.parent_id = parent_msg.external_references.message_id\n            self.external_references.message_id = \"\"  # will be set by broker when sending\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/parameters/external_references.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom schematics.models import Model\nfrom schematics.types import StringType\nfrom schematics.types.compound import ListType\n\n\nclass ExternalReferences(Model):\n    ancestors_ids = ListType(StringType())\n    message_id = StringType()\n    parent_id = StringType()\n\n    class Options:\n        serialize_when_none = False\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/parameters/message.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen parameters for message related classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom schematics.models import Model\nfrom schematics.types import (StringType, DateTimeType,\n                              IntType, UUIDType, BooleanType)\nfrom schematics.types.compound import ListType, ModelType, DictType\nfrom schematics.transforms import blacklist\n\nfrom .attachment import Attachment\nfrom .external_references import ExternalReferences\nfrom caliopen_main.pi.parameters import PIParameter\nfrom caliopen_main.participant.parameters import Participant\nfrom caliopen_main.user.parameters.tag import ImportedTag\nfrom caliopen_main.participant.core import hash_participants_uri\nimport caliopen_storage.helpers.json as helpers\n\nRECIPIENT_TYPES = ['To', 'From', 'Cc', 'Bcc', 'Reply-To', 'Sender']\nMESSAGE_PROTOCOLS = ['email', 'twitter', 'mastodon', None]\nMESSAGE_STATES = ['draft', 'sending', 'sent', 'cancel',\n                  'unread', 'read', 'deleted']\n\nlog = logging.getLogger(__name__)\n\n\nclass NewMessage(Model):\n    \"\"\"New message parameter.\"\"\"\n\n    attachments = ListType(ModelType(Attachment), default=lambda: [])\n    date = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                        tzd=u'utc')\n    discussion_id = StringType()  # = participants uris' hash\n    external_references = ModelType(ExternalReferences)\n    importance_level = IntType()\n    is_answered = BooleanType()\n    is_draft = BooleanType()\n    is_unread = BooleanType()\n    is_received = BooleanType()\n    message_id = UUIDType()\n    parent_id = UUIDType()\n    participants = ListType(ModelType(Participant), default=lambda: [])\n    privacy_features = DictType(StringType(), default=lambda: {})\n    pi = ModelType(PIParameter)\n    raw_msg_id = UUIDType()\n    subject = StringType()\n    tags = ListType(StringType(), default=lambda: [])\n    ext_tags = ListType(ModelType(ImportedTag), default=lambda: [])\n    protocol = StringType(choices=MESSAGE_PROTOCOLS, required=False)\n    user_identities = ListType(UUIDType(), default=lambda: [])\n\n    @property\n    def external_msg_id(self):\n        return self.external_references.message_id if self.external_references \\\n            else None\n\n    class Options:\n        serialize_when_none = False\n\n    @property\n    def hash_participants(self):\n        ids_hash = hash_participants_uri(self.participants)\n        return ids_hash['hash']\n\n\nclass NewInboundMessage(NewMessage):\n    body_html = StringType()\n    body_plain = StringType()\n\n\nclass Message(NewInboundMessage):\n    \"\"\"Existing message parameter.\"\"\"\n\n    body = StringType()\n    user_id = UUIDType()\n    date_insert = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    date_delete = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    date_sort = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                             tzd=u'utc')\n\n    class Options:\n        roles = {'default': blacklist('user_id', 'date_delete')}\n        serialize_when_none = False\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/parsers/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/parsers/mail.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nCaliopen mail message format management.\n\nmail parsing is included in python, so this is not\ngetting external dependencies.\n\nFor formats with needs of external packages, they\nmust be defined outside of this one.\n\"\"\"\n\nimport logging\nimport base64\nimport string\nfrom itertools import groupby\nfrom mailbox import Message\nfrom email.header import decode_header\nimport datetime\nimport pytz\n\nfrom email.utils import parsedate_tz, mktime_tz, getaddresses\n\nimport zope.interface\n\nfrom caliopen_main.common.helpers.normalize import clean_email_address\nfrom caliopen_main.common.helpers.strings import to_utf8\nfrom caliopen_main.common.interfaces import (IAttachmentParser, IMessageParser,\n                                             IParticipantParser)\n# from caliopen_main.common.objects.tag import ResourceTag as Tag\nfrom caliopen_main.user.parameters.tag import ImportedTag as Tag\n\nlog = logging.getLogger(__name__)\n\nTEXT_CONTENT_TYPE = ['text', 'xml', 'vnd', 'xhtml', 'json', 'msword']\nEXCLUDED_EXT_FLAGS = ['\\Seen', 'nonjunk', '$notjunk', 'notjunk', '$mdnsent',\n                      '$forwarded', '$sent', '\\Recent', '\\All', '\\Archive',\n                      '\\Drafts', '\\Junk', '\\Sent', '\\Trash']\n\n\nclass MailAttachment(object):\n    \"\"\"Mail part structure.\"\"\"\n\n    zope.interface.implements(IAttachmentParser)\n\n    def __init__(self, part):\n        \"\"\"Extract attachment attributes from a mail part.\"\"\"\n        self.content_type = part.get_content_type()\n        self.filename = part.get_filename()\n        if self.filename:\n            try:\n                self.filename = self.filename.decode('utf-8')\n            except UnicodeError:\n                log.warn('Invalid filename encoding')\n        content_disposition = part.get(\"Content-Disposition\")\n        if content_disposition:\n            dispositions = content_disposition.strip().split(\";\")\n            self.is_inline = bool(dispositions[0].lower() == \"inline\")\n        else:\n            self.is_inline = True\n        data = part.get_payload()\n        self.can_index = False\n        if any(x in part.get_content_type() for x in TEXT_CONTENT_TYPE):\n            self.can_index = True\n            charsets = part.get_charsets()\n            if len(charsets) > 1:\n                raise Exception('Too many charset %r for %s' %\n                                (charsets, part.get_payload()))\n            self.charset = charsets[0]\n            if 'Content-Transfer-Encoding' in part.keys():\n                if part.get('Content-Transfer-Encoding') == 'base64':\n                    data = base64.b64decode(data)\n            if self.charset:\n                data = data.decode(self.charset, 'replace'). \\\n                    encode('utf-8')\n        boundary = part.get(\"Mime-Boundary\", failobj=\"\")\n        if boundary is not \"\":\n            self.mime_boundary = boundary\n        else:\n            self.mime_boundary = \"\"\n        self.data = data\n        self.size = len(data) if data else 0\n\n    @classmethod\n    def is_attachment(cls, part):\n        \"\"\"Check if a part conform to Caliopen's attachment definition.\n\n        A part is an \"attachment\" if it verifies ANY of this conditions :\n        - it has a Content-Disposition header with param \"attachment\"\n        - the main part of the Content-Type header\n                                        is within \"attachment_types\" list below\n        - the part is not a PGP/Mime encryption envelope\n\n        see https://www.iana.org/assignments/media-types/media-types.xhtml\n\n        :param part: an email/message's part as return by the walk() func.\n        :return: true or false\n        \"\"\"\n        content_disposition = part.get(\"Content-Disposition\")\n        if content_disposition:\n            dispositions = content_disposition.strip().split(\";\")\n            if bool(dispositions[0].lower() == \"attachment\") or \\\n                    bool(dispositions[0].lower() == \"inline\"):\n                return True\n\n        attachment_types = (\n            \"application\", \"image\", \"video\", \"audio\", \"message\", \"font\")\n        if part.get_content_maintype() in attachment_types:\n            return True\n        return False\n\n\nclass MailParticipant(object):\n    \"\"\"Mail participant parser.\"\"\"\n\n    zope.interface.implements(IParticipantParser)\n\n    def __init__(self, type, addr):\n        \"\"\"Parse an email address and create a participant.\"\"\"\n        self.type = type\n        parts = clean_email_address(addr)\n        self.address = parts[0]\n        self.label = parts[1]\n\n\nclass MailMessage(object):\n    \"\"\"\n    Mail message structure.\n\n    Got a mail in raw rfc2822 format, parse it to\n    resolve all recipients emails, parts and group headers\n    \"\"\"\n\n    zope.interface.implements(IMessageParser)\n\n    recipient_headers = ['From', 'To', 'Cc', 'Bcc']\n    message_protocol = 'email'\n    warnings = []\n    body_html = \"\"\n    body_plain = \"\"\n\n    def __init__(self, raw_data):\n        \"\"\"Parse an RFC2822,5322 mail message.\"\"\"\n        self.raw = raw_data\n        self._extra_parameters = {}\n        try:\n            self.mail = Message(raw_data)\n        except Exception as exc:\n            log.error('Parse message failed %s' % exc)\n            raise exc\n        if self.mail.defects:\n            # XXX what to do ?\n            log.warn('Defects on parsed mail %r' % self.mail.defects)\n            self.warning = self.mail.defects\n        self.get_bodies()\n\n    def get_bodies(self):\n        \"\"\"Extract body alternatives, if any.\"\"\"\n        body_html = \"\"\n        body_plain = \"\"\n\n        if self.mail.get(\"Content-Type\", None):\n            if self.mail.is_multipart():\n                if self.mail.get_content_subtype() == 'encrypted':\n                    parts = self.mail.get_payload()\n                    if len(parts) == 2:\n                        self.body_plain = parts[1].get_payload()\n                        return\n                    else:\n                        log.warn('Encrypted message with invalid parts count')\n                for top_level_part in self.mail.get_payload():\n                    if top_level_part.get_content_maintype() == \"multipart\":\n                        for alternative in top_level_part.get_payload():\n                            charset = alternative.get_param(\"charset\")\n                            if isinstance(charset, tuple):\n                                charset = unicode(charset[2],\n                                                  charset[0] or \"us-ascii\")\n                            if alternative.get_content_type() == \"text/plain\":\n                                body_plain = alternative.get_payload(\n                                    decode=True)\n                                self.body_plain = to_utf8(body_plain, charset)\n                            elif alternative.get_content_type() == \"text/html\":\n                                body_html = alternative. \\\n                                    get_payload(decode=True)\n                                self.body_html = to_utf8(body_html, charset)\n                        break\n                    else:\n                        charset = top_level_part.get_param(\"charset\")\n                        if isinstance(charset, tuple):\n                            charset = unicode(charset[2],\n                                              charset[0] or \"us-ascii\")\n                        if top_level_part.get_content_type() == \"text/plain\":\n                            body_plain = top_level_part. \\\n                                get_payload(decode=True)\n                            self.body_plain = to_utf8(body_plain, charset)\n                        elif top_level_part.get_content_type() == \"text/html\":\n                            body_html = top_level_part.get_payload(decode=True)\n                            self.body_html = to_utf8(body_html, charset)\n            else:\n                charset = self.mail.get_param(\"charset\")\n                if isinstance(charset, tuple):\n                    charset = unicode(charset[2], charset[0] or \"us-ascii\")\n                if self.mail.get_content_type() == \"text/html\":\n                    body_html = self.mail.get_payload(decode=True)\n                    self.body_html = to_utf8(body_html, charset)\n                else:\n                    body_plain = self.mail.get_payload(decode=True)\n                    self.body_plain = to_utf8(body_plain, charset)\n        else:\n            self.body_plain = self.mail.get_payload(decode=True)\n\n    @property\n    def subject(self):\n        \"\"\"Mail subject.\"\"\"\n        s = decode_header(self.mail.get('Subject'))\n        charset = s[0][1]\n        if charset is not None:\n            return s[0][0].decode(charset, \"replace\"). \\\n                encode(\"utf-8\", \"replace\")\n        else:\n            try:\n                return s[0][0].decode('utf-8', errors='ignore')\n            except UnicodeError:\n                log.warn('Invalid subject encoding')\n                return s[0][0]\n\n    @property\n    def size(self):\n        \"\"\"Get mail size in bytes.\"\"\"\n        return len(self.mail.as_string())\n\n    @property\n    def external_references(self):\n        \"\"\"Return mail references to be used as external references.\n\n         making use of RFC5322 headers :\n            message-id\n            in-reply-to\n            references\n        headers' strings are pruned to extract email addresses only.\n        \"\"\"\n        ext_id = self.mail.get('Message-Id')\n        parent_id = self.mail.get('In-Reply-To')\n        ref = self.mail.get_all(\"References\")\n        ref_addr = getaddresses(ref) if ref else None\n        ref_ids = [address[1] for address in ref_addr] if ref_addr else []\n        mid = clean_email_address(ext_id)[1] if ext_id else None\n        if not mid:\n            log.error('Unable to find correct message_id {}'.format(ext_id))\n            mid = ext_id\n        pid = clean_email_address(parent_id)[1] if parent_id else None\n        if not pid:\n            pid = parent_id\n        return {\n            'message_id': mid,\n            'parent_id': pid,\n            'ancestors_ids': ref_ids}\n\n    @property\n    def date(self):\n        \"\"\"Get UTC date from a mail message.\"\"\"\n        mail_date = self.mail.get('Date')\n        if mail_date:\n            try:\n                tmp_date = parsedate_tz(mail_date)\n                return datetime.datetime.fromtimestamp(mktime_tz(tmp_date))\n            except TypeError:\n                log.error('Invalid date in mail {}'.format(mail_date))\n        log.debug('No date on mail using now (UTC)')\n        return datetime.datetime.now(tz=pytz.utc)\n\n    @property\n    def participants(self):\n        \"\"\"Mail participants.\"\"\"\n        participants = []\n        for header in self.recipient_headers:\n            addrs = []\n            participant_type = header.capitalize()\n            if self.mail.get(header):\n                parts = self.mail.get(header).split('>,')\n                if not parts:\n                    pass\n                if parts and parts[0] == 'undisclosed-recipients:;':\n                    pass\n                filtered = [x for x in parts if '@' in x]\n                addrs.extend(filtered)\n            for addr in addrs:\n                participant = MailParticipant(participant_type, addr.lower())\n                if participant.address == '' and participant.label == '':\n                    log.warn('Invalid email address {}'.format(addr))\n                else:\n                    participants.append(participant)\n        return participants\n\n    @property\n    def attachments(self):\n        \"\"\"Extract parts which we consider as attachments.\"\"\"\n        if not self.mail.is_multipart():\n            return []\n        attchs = []\n        for p in walk_with_boundary(self.mail, \"\"):\n            if not p.is_multipart():\n                if p.get_content_subtype() == 'pgp-encrypted':\n                    # Special consideration. Do not present it as an attachment\n                    # but set _extra_parameters accordingly\n                    self._extra_parameters.update({'encrypted': 'pgp'})\n                    continue\n                if MailAttachment.is_attachment(p):\n                    attchs.append(MailAttachment(p))\n        return attchs\n\n    @property\n    def extra_parameters(self):\n        \"\"\"Mail message extra parameters.\"\"\"\n        lists = self.mail.get_all(\"List-ID\")\n        lists_addr = getaddresses(lists) if lists else None\n        lists_ids = [address[1] for address in lists_addr] \\\n            if lists_addr else []\n        self._extra_parameters.update({'lists': lists_ids})\n        return self._extra_parameters\n\n    # Others parameters specific for mail message\n\n    @property\n    def headers(self):\n        \"\"\"Extract all headers into list.\n\n        Duplicate on headers exists, group them by name\n        with a related list of values\n        \"\"\"\n\n        def keyfunc(item):\n            return item[0]\n\n        # Group multiple value for same headers into a dict of list\n        headers = {}\n        data = sorted(self.mail.items(), key=keyfunc)\n        for k, g in groupby(data, key=keyfunc):\n            headers[k] = [x[1] for x in g]\n        return headers\n\n    @property\n    def external_flags(self):\n        \"\"\"\n        Get headers added by our fetcher that represent flags or labels\n        set by external provider,\n        returned as list of tags\n        \"\"\"\n        tags = []\n        for h in ['X-Fetched-Imap-Flags', 'X-Fetched-X-GM-LABELS']:\n            enc_flags = self.mail.get(h)\n            if enc_flags:\n                flags_str = base64.decodestring(enc_flags)\n                for flag in string.split(flags_str, '\\r\\n'):\n                    if flag not in EXCLUDED_EXT_FLAGS:\n                        tag = Tag()\n                        tag.name = flag\n                        tag.label = flag\n                        tag.type = 'imported'\n                        tags.append(tag)\n        return tags\n\n\ndef walk_with_boundary(message, boundary):\n    \"\"\"Recurse in boundaries.\"\"\"\n    message.add_header(\"Mime-Boundary\", boundary)\n    yield message\n    if message.is_multipart():\n        subboundary = message.get_boundary(\"\")\n        for subpart in message.get_payload():\n            for subsubpart in walk_with_boundary(subpart, subboundary):\n                yield subsubpart\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/parsers/mastodon.py",
    "content": "# -*- coding: utf-8 -*-\n\nimport logging\nimport json\nimport dateutil.parser\n\nimport zope.interface\n\nfrom caliopen_main.common.interfaces import (IMessageParser, IParticipantParser)\nfrom caliopen_main.common.helpers.normalize import parse_mastodon_url\n\nlog = logging.getLogger(__name__)\n\n\nclass MastodonStatus(object):\n    \"\"\"\n    Mastodon status structure\n    \"\"\"\n\n    zope.interface.implements(IMessageParser)\n\n    message_protocol = 'mastodon'\n    warnings = []\n    body_html = \"\"\n    body_plain = \"\"\n\n    def __init__(self, raw_data):\n        self.raw = raw_data\n        self.dm = json.loads(self.raw)\n        self.recipients = []\n        for m in self.dm[\"mentions\"]:\n            self.recipients.append(MastodonParticipant(\"To\", m['url']))\n\n        self.sender = MastodonParticipant('From', self.dm['account']['url'])\n        self.protocol = self.message_protocol\n        self.is_unread = True  # TODO: handle DM sent by user\n        #                                     if broker keeps them when fetching\n        self.is_draft = False\n        self.is_answered = False\n        self.is_received = True  # TODO: handle DM sent by user\n        #                                     if broker keeps them when fetching\n        self.importance_level = 0\n        self.get_bodies()\n\n    def get_bodies(self):\n        if self.dm['spoiler_text'] != '':\n            self.body_html = \"<span class='spoiler_text'>\" + self.dm[\n                'spoiler_text'] + \"</span>\"\n        self.body_html += self.dm['content']\n\n    @property\n    def subject(self):\n        \"\"\"\n        toots don't have subject\n        should we return an excerpt ?\n        \"\"\"\n        return ''\n\n    @property\n    def size(self):\n        \"\"\"Get json toot object size in bytes.\"\"\"\n        return len(self.dm.as_string())\n\n    @property\n    def date(self):\n        return dateutil.parser.isoparse(self.dm['created_at'].rstrip('UTC'))\n\n    @property\n    def participants(self):\n        \"one sender only for now\"\n        p = [self.sender]\n        p.extend(self.recipients)\n        return p\n\n    @property\n    def external_references(self):\n        return {'message_id': self.dm[\"id\"],\n                'parent_id': self.dm[\"in_reply_to_id\"]}\n\n    @property\n    def attachments(self):\n        \"\"\"TODO\"\"\"\n        return []\n\n    @property\n    def extra_parameters(self):\n        \"\"\"TODO\"\"\"\n        return {}\n\n\nclass MastodonParticipant(object):\n    \"\"\"\n    Mastodon sender and recipient parser\n    \"\"\"\n\n    zope.interface.implements(IParticipantParser)\n\n    def __init__(self, type, url):\n        \"\"\"Parse a mastodon address and create a participant.\"\"\"\n        domain, username = parse_mastodon_url(url)\n        self.address = username + '@' + domain\n        self.label = username\n        self.type = type\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/parsers/twitter.py",
    "content": "# -*- coding: utf-8 -*-\n\nimport logging\nimport json\nfrom datetime import datetime\nimport pytz\n\nimport zope.interface\n\nfrom caliopen_main.common.interfaces import (IMessageParser, IParticipantParser)\nfrom caliopen_main.common.helpers.normalize import clean_twitter_address\n\nlog = logging.getLogger(__name__)\n\n\nclass TwitterDM(object):\n    \"\"\"\n    Twitter direct message structure\n    \"\"\"\n\n    zope.interface.implements(IMessageParser)\n\n    recipient_headers = ['From', 'To']\n    message_protocol = 'twitter'\n    warnings = []\n    body_html = \"\"\n    body_plain = \"\"\n\n    def __init__(self, raw_data):\n        self.raw = raw_data\n        self.dm = json.loads(self.raw)\n        self.recipient_name = self.dm[\"message_create\"][\"target\"][\n            \"recipient_screen_name\"]\n        self.sender_name = self.dm[\"message_create\"][\"sender_screen_name\"]\n        self.protocol = self.message_protocol\n        self.is_unread = True  # TODO: handle DM sent by user\n        #                                     if broker keeps them when fetching\n        self.is_draft = False\n        self.is_answered = False\n        self.is_received = True  # TODO: handle DM sent by user\n        #                                     if broker keeps them when fetching\n        self.importance_level = 0\n        self.get_bodies()\n\n    def get_bodies(self):\n        self.body_plain = self.dm[\"message_create\"][\"message_data\"][\"text\"]\n\n    @property\n    def subject(self):\n        \"\"\"\n        tweets don't have subject\n        should we return an excerpt ?\n        \"\"\"\n        return ''\n\n    @property\n    def size(self):\n        \"\"\"Get json tweet object size in bytes.\"\"\"\n        return len(self.dm.as_string())\n\n    @property\n    def date(self):\n        return datetime.fromtimestamp(float(self.dm[\"created_timestamp\"])/1000,\n                                      tz=pytz.utc)\n\n    @property\n    def participants(self):\n        \"one sender only for now\"\n        return [TwitterParticipant(\"To\", clean_twitter_address(self.recipient_name)),\n                TwitterParticipant(\"From\", clean_twitter_address(self.sender_name))]\n\n    @property\n    def external_references(self):\n        return {'message_id': self.dm[\"id\"]}\n\n    @property\n    def attachments(self):\n        \"\"\"TODO\"\"\"\n        return []\n\n    @property\n    def extra_parameters(self):\n        \"\"\"TODO\"\"\"\n        return {}\n\n\nclass TwitterParticipant(object):\n    \"\"\"\n    Twitter sender and recipient parser\n    \"\"\"\n\n    zope.interface.implements(IParticipantParser)\n\n    def __init__(self, type, screen_name):\n        \"\"\"Parse a twitter address and create a participant.\"\"\"\n        self.type = type\n        self.address = clean_twitter_address(screen_name)\n        self.label = screen_name\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/store/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .attachment import MessageAttachment\nfrom .attachment_index import IndexedMessageAttachment\nfrom .external_references import ExternalReferences, MessageExternalRefLookup\nfrom .external_references_index import IndexedExternalReferences\nfrom .message import Message\nfrom .message_index import IndexedMessage\nfrom caliopen_main.participant.store.participant import Participant\nfrom .raw import RawMessage, UserRawLookup\n\n__all__ = ['MessageAttachment', 'IndexedMessageAttachment',\n           'RawMessage', 'UserRawLookup',\n           'Message', 'IndexedMessage',\n           'ExternalReferences', 'IndexedExternalReferences',\n           'Participant', 'IndexedParticipant', 'MessageExternalRefLookup'\n           ]\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/store/attachment.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom cassandra.cqlengine import columns\n\nfrom caliopen_storage.store import BaseUserType\n\n\nclass MessageAttachment(BaseUserType):\n\n    \"\"\"Attachment nested in message.\"\"\"\n\n    content_type = columns.Text()\n    file_name = columns.Text()\n    is_inline = columns.Boolean()\n    size = columns.Integer()\n    temp_id = columns.UUID()\n    url = columns.Text()  # objectsStore uri for temporary file (draft)\n    mime_boundary = columns.Text()  # for attachments embedded in raw messages\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/store/attachment_index.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom elasticsearch_dsl import InnerObjectWrapper, Boolean, Integer, Keyword\n\nlog = logging.getLogger(__name__)\n\n\nclass IndexedMessageAttachment(InnerObjectWrapper):\n\n    \"\"\"Nest attachment indexed model.\"\"\"\n\n    content_type = Keyword()\n    file_name = Keyword()\n    is_inline = Boolean()\n    size = Integer()\n    temp_id = Keyword()\n    url = Keyword()  # objectsStore uri for temporary file (draft)\n    mime_boundary = Keyword()  # for attachments embedded in raw messages\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/store/external_references.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom cassandra.cqlengine import columns\n\nfrom caliopen_storage.store import BaseUserType\nfrom caliopen_storage.store.model import BaseModel\n\n\nclass ExternalReferences(BaseUserType):\n    \"\"\"External references nested in message.\"\"\"\n\n    ancestors_ids = columns.List(columns.Text())\n    message_id = columns.Text()\n    parent_id = columns.Text()\n\n\nclass MessageExternalRefLookup(BaseModel):\n    \"\"\"Table to lookup message by external message-id\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    external_msg_id = columns.Text(primary_key=True)\n    identity_id = columns.UUID(primary_key=True)\n    message_id = columns.UUID()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/store/external_references_index.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom elasticsearch_dsl import InnerObjectWrapper, Keyword, Nested\n\nlog = logging.getLogger(__name__)\n\n\nclass IndexedExternalReferences(InnerObjectWrapper):\n\n    \"\"\"Nest attachment indexed model.\"\"\"\n\n    ancestors_ids = Keyword(multi=True)\n    message_id = Keyword()\n    parent_id = Keyword()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/store/message.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen storage model for messages.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom cassandra.cqlengine import columns\n\nfrom caliopen_storage.store.model import BaseModel\nfrom caliopen_storage.store.mixin import IndexedModelMixin\nfrom caliopen_main.pi.objects import PIModel\n\nfrom .attachment import MessageAttachment\nfrom .external_references import ExternalReferences\nfrom caliopen_main.participant.store.participant import Participant\nfrom .message_index import IndexedMessage\n\nimport uuid\n\n\nclass Message(BaseModel, IndexedModelMixin):\n    \"\"\"Message model.\"\"\"\n\n    _index_class = IndexedMessage\n\n    user_id = columns.UUID(primary_key=True)\n    message_id = columns.UUID(primary_key=True, default=uuid.uuid4)\n\n    attachments = columns.List(columns.UserDefinedType(MessageAttachment))\n    body_html = columns.Text()\n    body_plain = columns.Text()\n    date = columns.DateTime()\n    date_delete = columns.DateTime()\n    date_insert = columns.DateTime()\n    date_sort = columns.DateTime()\n    discussion_id = columns.Text()\n    external_references = columns.UserDefinedType(ExternalReferences)\n    importance_level = columns.Integer()\n    is_answered = columns.Boolean()\n    is_draft = columns.Boolean()\n    is_unread = columns.Boolean()\n    is_received = columns.Boolean(default=False)\n    parent_id = columns.UUID()\n    participants = columns.List(columns.UserDefinedType(Participant))\n    privacy_features = columns.Map(columns.Text(), columns.Text())\n    pi = columns.UserDefinedType(PIModel)\n    raw_msg_id = columns.UUID()\n    subject = columns.Text()  # Subject of email, the message for short\n    tags = columns.List(columns.Text(), db_field=\"tagnames\")\n    protocol = columns.Text()\n    user_identities = columns.List(columns.UUID)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/store/message_index.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen message index classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom elasticsearch_dsl import Mapping, Nested, Text, Keyword, Date, Boolean, \\\n    Integer, Object\nfrom caliopen_storage.store.model import BaseIndexDocument\n\nfrom .attachment_index import IndexedMessageAttachment\nfrom .external_references_index import IndexedExternalReferences\nfrom caliopen_main.pi.objects import PIIndexModel\nfrom caliopen_main.participant.store.participant_index import IndexedParticipant\n\n\nclass IndexedMessage(BaseIndexDocument):\n    \"\"\"Contact indexed message model.\"\"\"\n\n    doc_type = 'indexed_message'\n\n    user_id = Keyword()\n    message_id = Keyword()\n\n    attachments = Nested(doc_class=IndexedMessageAttachment)\n    body_html = Text()\n    body_plain = Text()\n    date = Date()\n    date_delete = Date()\n    date_insert = Date()\n    date_sort = Date()\n    discussion_id = Keyword()\n    external_references = Nested(doc_class=IndexedExternalReferences)\n    importance_level = Integer()\n    is_answered = Boolean()\n    is_draft = Boolean()\n    is_unread = Boolean()\n    is_received = Boolean()\n    parent_id = Keyword()\n    participants = Nested(doc_class=IndexedParticipant)\n    privacy_features = Object()\n    pi = Object(doc_class=PIIndexModel)\n    raw_msg_id = Keyword()\n    subject = Text()\n    tags = Keyword(multi=True)\n    protocol = Keyword()\n    user_identities = Keyword(multi=True)\n\n    @property\n    def message_id(self):\n        \"\"\"The compound primary key for a message is message_id.\"\"\"\n        return self.meta.id\n\n    @classmethod\n    def build_mapping(cls):\n        \"\"\"Generate the mapping definition for indexed messages\"\"\"\n        m = Mapping(cls.doc_type)\n        m.meta('_all', enabled=True)\n        m.field('user_id', 'keyword')\n        # attachments\n        m.field('attachments', Nested(doc_class=IndexedMessageAttachment,\n                                      include_in_all=True,\n                                      properties={\n                                          \"content_type\": Keyword(),\n                                          \"file_name\": Keyword(),\n                                          \"is_inline\": Boolean(),\n                                          \"size\": Integer(),\n                                          \"temp_id\": Keyword(),\n                                          \"url\": Keyword(),\n                                          \"mime_boundary\": Keyword()\n                                      })\n                )\n        m.field('body_html', 'text', fields={\n            \"normalized\": {\"type\": \"text\", \"analyzer\": \"text_analyzer\"}\n        })\n        m.field('body_plain', 'text', fields={\n            \"normalized\": {\"type\": \"text\", \"analyzer\": \"text_analyzer\"}\n        })\n        m.field('date', 'date')\n        m.field('date_delete', 'date')\n        m.field('date_insert', 'date')\n        m.field('date_sort', 'date')\n        m.field('discussion_id', 'keyword')\n        # external references\n        m.field('external_references',\n                Nested(doc_class=IndexedExternalReferences,\n                       include_in_all=True,\n                       properties={\n                           \"ancestors_ids\": Keyword(),\n                           \"message_id\": Keyword(),\n                           \"parent_id\": Keyword()\n                       })\n                )\n        m.field('importance_level', 'short')\n        m.field('is_answered', 'boolean')\n        m.field('is_draft', 'boolean')\n        m.field('is_unread', 'boolean')\n        m.field('is_received', 'boolean')\n        m.field('message_id', 'keyword')\n        m.field('parent_id', 'keyword')\n        # participants\n        participants = Nested(doc_class=IndexedParticipant,\n                              include_in_all=True)\n        participants.field(\"address\", \"text\", analyzer=\"text_analyzer\",\n                           fields={\n                               \"raw\": {\"type\": \"keyword\"},\n                               \"parts\": {\"type\": \"text\",\n                                         \"analyzer\": \"email_analyzer\"}\n                           })\n        participants.field(\"contact_ids\", Keyword(multi=True))\n        participants.field(\"label\", \"text\", analyzer=\"text_analyzer\")\n        participants.field(\"protocol\", Keyword())\n        participants.field(\"type\", Keyword())\n        m.field('participants', participants)\n        # PI\n        pi = Object(doc_class=PIIndexModel, include_in_all=True,\n                    properties={\n                        \"technic\": \"integer\",\n                        \"comportment\": \"integer\",\n                        \"context\": \"integer\",\n                        \"version\": \"integer\",\n                        \"date_update\": \"date\"\n                    })\n        m.field(\"pi\", pi)\n        m.field('privacy_features', Object(include_in_all=True))\n        m.field('raw_msg_id', \"keyword\")\n        m.field('subject', 'text', fields={\n            \"normalized\": {\"type\": \"text\", \"analyzer\": \"text_analyzer\"}\n        })\n        m.field('tags', Keyword(multi=True))\n\n        m.field('subject', 'text')\n        m.field('tags', Keyword(multi=True))\n        m.field('protocol', 'keyword')\n        m.field('user_identities', Keyword(multi=True))\n\n        return m\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/message/store/raw.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen storage model for messages.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport uuid\n\nfrom cassandra.cqlengine import columns\n\nfrom caliopen_storage.store.model import BaseModel\n\n\nclass RawMessage(BaseModel):\n    \"\"\"Raw message model.\"\"\"\n\n    raw_msg_id = columns.UUID(primary_key=True, default=uuid.uuid4)\n    raw_data = columns.Bytes()  # may be empty if data is too large to fit into cassandra\n    raw_size = columns.Integer()  # number of bytes in 'data' column\n    uri = columns.Text()  # where object is stored if it was too large to fit into raw_data column\n    delivered = columns.Boolean()  # true only if complete delivery succeeded\n\n\nclass UserRawLookup(BaseModel):\n    \"\"\"User's raw message pointer.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    raw_msg_id = columns.UUID(primary_key=True)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/notification/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/notification/core.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen device core classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom caliopen_storage.core import BaseCore\nfrom caliopen_main.common.core import BaseUserCore\n\nfrom .store import Notification as ModelNotification, \\\n    NotificationTtl as ModelTTLs\n\n\nclass Notification(BaseUserCore):\n    \"\"\"User Notification core class\"\"\"\n\n    _model_class = ModelNotification\n    _pkey_name = \"user_id\"\n\n\nclass NotificationTtl(BaseCore):\n    \"\"\"core class to store default TTLs for each notification kind\"\"\"\n\n    _model_class = ModelTTLs\n    _pkey_name = \"notif_code\"\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/notification/store.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen cassandra models related to device.\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom cassandra.cqlengine import columns\n\nfrom caliopen_storage.store.model import BaseModel\n\n\nclass Notification(BaseModel):\n    \"\"\"\n    Table to store notifications queues in cassandra\n\n    user_id: user's id to which notification belongs to.\n    notif_id: time UUID V1 (en.wikipedia.org/wiki/Universally_unique_identifier)\n    emitter : backend entity that's emitting the message.\n    type: a single word to describe notification's type: event, info, feedback..\n    reference: (optional) a reference number previously sent by frontend.\n    body: could be a simple word or a more complex structure like a json.\n\n    \"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    notif_id = columns.TimeUUID(primary_key=True)\n    emitter = columns.Text()\n    type = columns.Ascii()\n    reference = columns.Text()\n    body = columns.Blob()\n\n\nclass NotificationTtl(BaseModel):\n    \"\"\"\n    Table to store ttl configuration for each kind of notification\n\n    ttl_code: chars to identify a ttl.\n    ttl_duration: default duration for this kind of ttl\n    description: free text description\n    \"\"\"\n\n    ttl_code = columns.Ascii(primary_key=True)\n    ttl_duration = columns.Integer()\n    description = columns.Text()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/core/__init__.py",
    "content": "from .participant import hash_participants_uri, participants_from_uris, \\\n    HashLookup, ParticipantHash\n\n__all__ = ['hash_participants_uri', 'participants_from_uris', 'HashLookup',\n           'ParticipantHash']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/core/participant.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport hashlib\nimport logging\n\nfrom datetime import datetime\n\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_storage.core import BaseCore\nfrom caliopen_main.participant.store import HashLookup as ModelHashLookup, \\\n    ParticipantHash as ModelParticipantHash\n\nlog = logging.getLogger(__name__)\n\n\nclass HashLookup(BaseCore):\n    _model_class = ModelHashLookup\n    _pkey_name = 'hash'\n\n\nclass ParticipantHash(BaseCore):\n    _model_class = ModelParticipantHash\n    _pkey_name = 'value'\n\n\ndef hash_participants_uri(participants):\n    \"\"\"\n    Create hash from a collection of Participant\n\n    :param participants: a collection of Participant\n    :return: sorted collection of participants' URI + hash of this collection\n\n    hash is computed from a set of URIs which are strings modeled as\n    'participant.protocol:participant.address'\n    \"\"\"\n    URIs = set()\n    for participant in participants:\n        if not participant.address or not participant.protocol:\n            raise Exception(\"missing mandatory property in participant\")\n        uri = participant.protocol + \":\" + participant.address.lower()\n        URIs.add(uri)\n\n    URIs = list(URIs)\n    URIs.sort()\n    hash = hashlib.sha256(''.join(URIs)).hexdigest()\n    return {'uris': URIs, 'hash': hash}\n\n\ndef participants_from_uris(user, uris, uris_hash):\n    \"\"\"\n    - resolve uris to contact to build participants' set\n    - compute participants_hash\n    - create two ways links :\n                            uris<->uris_hash\n                            uris<->participants_hash\n\n    :param user:\n    :param uris: a set() of uris formatted like 'scheme:path'\n    :param uris_hash: the hash or uris' components\n    :type uris: set\n    :type uris_hash: string\n    :return: participant\n    \"\"\"\n    from caliopen_main.contact.core import ContactLookup\n\n    participants = set()\n    for uri in uris:\n        try:\n            contact = ContactLookup.get(user, uri.split(\":\", 1)[1])\n            if contact:\n                participants.add(\"contact:\" + contact.contact_id)\n            else:\n                participants.add(uri)\n        except NotFound:\n            participants.add(uri)\n\n    participants = list(participants)\n    participants.sort()\n    participants_hash = hashlib.sha256(''.join(participants)).hexdigest()\n\n    date = datetime.utcnow()\n\n    # store uris_hash -> participants_hash\n    ParticipantHash.create(user_id=user.user_id,\n                           kind=\"uris\",\n                           key=uris_hash,\n                           value=participants_hash,\n                           components=uris,\n                           date_insert=date)\n    # store participants_hash -> uris_hash\n    ParticipantHash.create(user_id=user.user_id,\n                           kind=\"participants\",\n                           key=participants_hash,\n                           value=uris_hash,\n                           components=participants,\n                           date_insert=date)\n    for uri in uris:\n        # uri->uris_hash\n        HashLookup.create(user_id=user.user_id,\n                          uri=uri,\n                          hash=uris_hash,\n                          hash_components=uris,\n                          date_insert=date)\n\n    return {'components': participants, 'hash': participants_hash}\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/objects/__init__.py",
    "content": "from .participant import Participant\n\n__all__ = ['Participant']"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/objects/participant.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen message object classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\n\nfrom uuid import UUID\nfrom caliopen_main.common.objects.base import ObjectJsonDictifiable\nfrom caliopen_main.participant.store.participant import \\\n    Participant as ModelParticipant\nfrom caliopen_main.participant.store.participant_index import IndexedParticipant\n\n\nclass Participant(ObjectJsonDictifiable):\n    \"\"\"participant's attributes, nested within message object\"\"\"\n\n    _attrs = {\n        'address': types.StringType,\n        'contact_ids': [UUID],\n        'label': types.StringType,\n        'participant_id': UUID,\n        'protocol': types.StringType,\n        'type': types.StringType\n    }\n\n    _model_class = ModelParticipant\n    _index_class = IndexedParticipant\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/parameters/__init__.py",
    "content": "from .participant import Participant\n\n__all__ = ['Participant']"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/parameters/participant.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom schematics.models import Model\nfrom schematics.types import StringType, UUIDType\nfrom schematics.types.compound import ListType\n\nlog = logging.getLogger(__name__)\n\n\nclass Participant(Model):\n    address = StringType()\n    contact_ids = ListType(UUIDType(), default=lambda: [])\n    label = StringType()\n    protocol = StringType()\n    type = StringType()\n    participant_id = UUIDType()\n\n    class Options:\n        serialize_when_none = False\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/store/__init__.py",
    "content": "from .participant import Participant, ParticipantHash, HashLookup\nfrom .participant_index import IndexedParticipant\n\n__all__ = ['Participant', 'ParticipantHash', 'IndexedParticipant', 'HashLookup']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/store/participant.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom cassandra.cqlengine import columns\nfrom caliopen_storage.store import BaseUserType, BaseModel\n\n\nclass Participant(BaseUserType):\n    \"\"\"participant nested in message.\"\"\"\n\n    address = columns.Text()\n    contact_ids = columns.List(columns.UUID())\n    participant_id = columns.UUID()\n    label = columns.Text()\n    protocol = columns.Text()\n    type = columns.Text()\n\n\nclass HashLookup(BaseModel):\n    \"\"\"\n    Table to lookup in which hash(es) an uri is embedded\n\n    URIs in the form of \"scheme:path\"\n     - for example : \"email:john@example.com\", \"twitter:caliopen_org\"\n\n    It is updated each time a message gets in or out (including draft edition)\n    \"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    uri = columns.Text(primary_key=True)\n    hash = columns.Text(primary_key=True)\n    hash_components = columns.List(columns.Text())\n    date_insert = columns.DateTime()\n\n\nclass ParticipantHash(BaseModel):\n    \"\"\"\n    Table to store two ways links between uris'hash (immutable message's prop.)\n    and corresponding current participants'hash\n\n    It is updated each time :\n        - a lookup is made on one uris'hash,\n                            but its participant hash counterpart does not exist\n        - a participant is added/removed from a contact\n    \"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    kind = columns.Text(primary_key=True)  # 'uris' or 'participants'\n    key = columns.Text(primary_key=True)  # uris or partcipants' hash\n    value = columns.Text(primary_key=True)  # the hash of opposite kind\n    components = columns.List(columns.Text())  # what hash is made of\n    date_insert = columns.DateTime()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/participant/store/participant_index.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom elasticsearch_dsl import InnerObjectWrapper, Keyword, Text\n\nlog = logging.getLogger(__name__)\n\n\nclass IndexedParticipant(InnerObjectWrapper):\n\n    \"\"\"Nest participant indexed model.\"\"\"\n\n    address = Keyword()\n    contact_ids = Keyword(multi=True)\n    label = Text()\n    participant_id = Keyword()\n    protocol = Keyword()\n    type = Keyword()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/pi/__init__.py",
    "content": "\nfrom .parameters import PIParameter\nfrom .objects import PIModel, PIIndexModel, PIObject\n\n__all__ = ['PIParameter', 'PIModel', 'PIIndexModel', 'PIObject']\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/pi/objects.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nCaliopen PI (privacy indexes) definition.\n\nThis structure is common to many entities (user, contact, message)\n\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nfrom uuid import UUID\n\nfrom cassandra.cqlengine import columns\nfrom elasticsearch_dsl import InnerObjectWrapper, Integer, Date\n\nfrom caliopen_storage.store import BaseUserType\nfrom caliopen_main.common.objects.base import ObjectIndexable\n\n\nclass PIModel(BaseUserType):\n    \"\"\"The privacy indexes model definition.\"\"\"\n\n    technic = columns.Integer(default=0)\n    comportment = columns.Integer(default=0)\n    context = columns.Integer(default=0)\n    version = columns.Integer(default=0)\n    date_update = columns.DateTime()\n\n\nclass PIIndexModel(InnerObjectWrapper):\n    \"\"\"The privacy indexes model definition for index part.\"\"\"\n\n    comportment = Integer()\n    context = Integer()\n    date_update = Date()\n    technic = Integer()\n    version = Integer()\n\n\nclass PIObject(ObjectIndexable):\n    \"\"\"The caliopen object definition of privacy indexes.\"\"\"\n\n    _attrs = {\n        \"technic\": types.IntType,\n        \"comportment\": types.IntType,\n        \"context\": types.IntType,\n        \"version\": types.IntType,\n        \"user_id\": UUID\n    }\n\n    _model_class = PIModel\n    _index_class = PIIndexModel\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/pi/parameters.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nCaliopen PI (privacy indexes) definition.\n\nThis structure is common to many entities (user, contact, message)\n\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom schematics.models import Model\nfrom schematics.types import IntType, DateTimeType\n\n\nclass PIParameter(Model):\n    \"\"\"The privacy indexes schematics parameter definition.\"\"\"\n\n    technic = IntType()\n    comportment = IntType()\n    context = IntType()\n    version = IntType()\n    date_update = DateTimeType()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/protocol/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/protocol/core/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .provider import Provider\n\n__all__ = [ 'Provider' ]\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/protocol/core/provider.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen device core classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom caliopen_storage.core import BaseCore\n\nfrom ..store.provider import Provider as ModelProvider\n\n\nclass Provider(BaseCore):\n    \"\"\"Provider core class\"\"\"\n\n    _model_class = ModelProvider\n    _pkey_name = \"name\"\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/protocol/store/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .provider import Provider\n\n__all__ = [ 'Provider' ]\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/protocol/store/provider.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen tag objects.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom cassandra.cqlengine import columns\nfrom caliopen_storage.store.model import BaseModel\n\n\nclass Provider(BaseModel):\n    \"\"\"model to store data related to external protocol endpoints\"\"\"\n\n    name = columns.Text(primary_key=True)\n    instance = columns.Text(primary_key=True)\n    infos = columns.Map(columns.Text, columns.Text)\n    date_insert = columns.DateTime()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/mail/pgp_crypted_1.eml",
    "content": "Message-ID: <1492761665.964.1.camel@gandi.net>\nSubject: crypted content\nFrom: Chamal <chamal@caliopen.org>\nTo: Caliopen <contact@caliopen.org>\nDate: Fri, 21 Apr 2017 10:01:05 +0200\nContent-Type: multipart/encrypted; protocol=\"application/pgp-encrypted\";\n        boundary=\"=-hf6zgIn8mtv3IPX/X7Tq\"\nX-Mailer: Evolution 3.22.6 \nMime-Version: 1.0\nX-Evolution-Identity: 1485870052.1665.0@tao\nX-Evolution-Fcc: folder://1485870052.1665.2%40tao/Sent\nX-Evolution-Transport: 1485870052.1665.10@tao\nX-Evolution-Source: \n\n\n--=-hf6zgIn8mtv3IPX/X7Tq\nContent-Type: application/pgp-encrypted\nContent-Transfer-Encoding: 7bit\n\nVersion: 1\n\n--=-hf6zgIn8mtv3IPX/X7Tq\nContent-Type: application/octet-stream; name=\"encrypted.asc\"\nContent-Description: Ceci est une partie de message\n =?ISO-8859-1?Q?num=E9riquement?= =?ISO-8859-1?Q?_sign=E9e?=\nContent-Transfer-Encoding: 7bit\n\n-----BEGIN PGP MESSAGE-----\n\nhQIMAxEK1/MMDKzXAQ/+KpNeJBR9o5etazFr16HO/vWAdDlkq3iIu7BOm0sAXYIJ\nssjXN9cXY1LM1B70GeXvGeB4suqJBjrl6xbKCW0fMQsGovYQpyHJAOJCtModM6vM\n76OuuTcT0AdE40pgfscTdG3nCUV2T/w2IKg7TNLwEstRQ4BoA+RFPyar97Zq1Pyk\n74Wkwv1BT9jMejkpaQycqlV1eTF/JNbXa/hs5dcmirbH9PCoySQx+qFnp4w4ZHnx\nJDT/MpmK4M3w7FawyA2adUFKeBqXU9Oo2LBz9f9ZBtIPHeiV2iklEgQUqv/847dc\nOqB23fd+Jq8mXjmycLcwEsXjOL77dI+SCip4MheFvGjwSOUoIkIb6SbcnUq/hwsi\nxpLc3yeZDm4BCi/M0crmhMF8NEJYyu0Xg2Si+lWtWPHCtrXW+Lla226BcGNlV1Dx\ncpaeMlFF2MvADqTsxAD8F+km2mrf7RznobnX70TkH2wF80W+/KIKbc0R2ilyvyIm\nY/MgttT593nUJgIm77cIzUjSMJHVfhYASS960Rv4cvKMIYcJQ41zJ1O693eB8gXM\nebCU7Tg6gzZ1Fckgil0VQyRwY9cPERZQSrgcQOdZaw51jYzJNrRDXaaX8TuMnSTx\nxJkalCh8PEJ9mtLIvNPgw5cGNhttNkoGvPamEAXEHTNE7757qo5ku7w4X2u9t6uF\nAQwD08kNfkWMEY4BB/9N42dz6IwZ7GG/9Hj0Avcb5l8If1peNj/zOUXv88JBvMEL\nGukefhD2ez0R359ErrJk4Q+RhOOJYkrcPUShqm0SN7s50TVsms5/7T/OT0mxVsOR\n6nnm19DMGW32Kz819c8Gp+kVB7sFOldJpi/kyoXJsO/0HdnXaM4HyeF8csTFFvLf\nB2bZFNk5YgasiCbCys2M0Zv8MtmUXQc7y1xsHSvpgjM6Ftb2BQdfjNj+CUnJHpxc\nCfFtM8lmeVrjPFHqtyuofVEqQvHjKSKSfZH5LrYA6QJfL0Girfqm3x4fgCSAaHPQ\n87G7pcu5Ci2FDpQlHxPwnXHfbT/IKPNQrjB/nQ0E0ukBK5GRwm8AnqjK+cCcSaYo\nbcoNartPm8J3qPTC3Vdzw2OButC6wnXYrsbE1rADZ6PVByeXj7OtSX01fykHwgFj\nnEu4HEIMNTVtprZQ8UnMV95REM8yRteiNPfl+osK3Ae/PyZ/0go7bhYgfJh3qnIH\nNDzThiCy3/4YoBErejnLVQdpVTf2OFHEDpohwz76/4GhXouOiWkrSpjBWIwMnYC+\nF/ZEgXkUIja6l1K16ovNe03DiEb/dgZkF//BJsnIAzNqK8PzGawwxLkDrMKVVAjP\nTpdGSnwyDsjjFMYG4s7dWWo0miwPU5QLJIkqqyk2040YCV6rgebpfZ8RrIuZEERi\nuSFQtf+ghBWGJDuf49P+adf20HbNYFIi++TkG0fQ0RlcagRHYRahgytdQpUBz9GS\nafOnC9uPtIyBq6dNLsvAr1pIlBXsy14lSdAv67rtUtOzNoeww0BaiYpL6Ir3rrPg\npQ34U3RZ44kH0QWq/9btVWPTxLobRc41pf/LmEo4MAvwwZeyvJH5mJQotaWDRtJu\nNYLzCYcqT5UqRn5dYq8k8UFzLwaynoSxdUSV0uozGsLj5yXf5Dm1x/L+FAX5STgZ\n95MntgS5fPjYABNPD9oVqfFdJmm9s27b3LgFgQBlx4kU+rqN/aAQdIltlUsA+zl1\nqkoRdhilQfqn7g35HRm/ysD9pK7GKQ+R1D+fQAebs451E50q3VIaB0lJRluRXabK\nvWhMk29emRPKg8OMRR7r9oIss9gg+fTVmyttNX5MZ5t+DKjGdlApMhLPahyxa/i/\nWWhRmZTLkVRqssD3blfPa1AR/XJIONcfFROwLyHy3LwQyMBcH8YcFC5oT3oDn3PR\n07gXHSK6N7Xv6R08NF1Q6Ag4d6IUT//KvUf5WqyZaB5adLEE/Fip8doDed6zv/Hw\nqSeSqgWm9Y2t+6sekZXJkMHaY0Mdiuu6ONntG7cs87rHHKG/paCcAMLLoKkpFuJM\nvoKkjlafMq+HR59hY4fIgrOw2iydL1Ihb9YiMzl15zknpzQeQMNCElm3dZvWwsHU\nn3HYTc+JqTEXxpP7IiBpIbiy/EP5kIuKk9kapBgiDDzMiey3WbyyeqTH4luAh0Uv\nofJivNiEJeCzCR6Gml1bgvRbJrimsgYF1iBAsY3m14onkCCnWaim9kqLUmB8mVec\nxsYUJuNtGEqS8W0++uBzW+tXKlq6hjdGkfeCp/oD6gbBGxq+Iho8qVRSRPJ90EOR\nIZX5mCJJzSYdP9ELwWFAObl7WtRea7Ed376B6a7maQ==\n=EKUR\n-----END PGP MESSAGE-----\n\n--=-hf6zgIn8mtv3IPX/X7Tq--"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/mail/pgp_signed_1.eml",
    "content": "Message-ID: <1492765618.4984.1.camel@gandi.net>\nSubject: signed content\nFrom: Chamal <chamal@caliopen.org>\nTo: Caliopen <contact@caliopen.org>\nDate: Fri, 21 Apr 2017 11:06:58 +0200\nIn-Reply-To: <5B952AE1-6C33-42C5-B25C-AF4F7AD903D4@toto.net>\nContent-Type: multipart/signed; micalg=\"pgp-sha256\";\n        protocol=\"application/pgp-signature\"; boundary=\"=-pqHfzN3tdfFfcK4qJTZs\"\nX-Mailer: Evolution 3.22.6 \nMime-Version: 1.0\nX-Evolution-Identity: 1485870052.1665.0@tao\nX-Evolution-Fcc: folder://1485870052.1665.2%40tao/Sent\nX-Evolution-Transport: 1485870052.1665.10@tao\nX-Evolution-Source-Folder: folder://1485870052.1665.2%40tao/INBOX\nX-Evolution-Source-Message: 2188502\nX-Evolution-Source-Flags: ANSWERED ANSWERED_ALL SEEN \nX-Evolution-Source: \n\n\n--=-pqHfzN3tdfFfcK4qJTZs\nContent-Type: text/plain; charset=\"UTF-8\"\nContent-Transfer-Encoding: quoted-printable\n\nSome clear text\n--=-pqHfzN3tdfFfcK4qJTZs\nContent-Type: application/pgp-signature; name=\"signature.asc\"\nContent-Description: This is a digitally signed message part\nContent-Transfer-Encoding: 7bit\n\n-----BEGIN PGP SIGNATURE-----\n\niQEzBAABCAAdFiEEOhFyGH73QG/Lzs+mDmjE+F0E2PwFAlj5y7IACgkQDmjE+F0E\n2PycuggAjVQBUkJ1LzZyhKTyRilv/Wa14hYTg0eFlCRbeuo7WRWPB3/1EIo9CcF5\nvL2r78Uwg4YUiXimjW02lfEqzPqBYWMm91dOwlN1PeDD6wuY0aWawJwXl8ow8z7S\nGP4N2GltfL+SXqPK6zOIvC2HDhyVVh+aIMuRpqt8S8senTWLTonsef0vUmzgjbVY\nz+cxKrcLCX1/rrygnj9J8j5gFkKeiB13/wAvqtsvN+HTkr2Uo0zsAQEjI+33VMCn\nV6HvalZobX1IuMJX+c6UVI0ZoC9Xy9isD6aMOg6ZGQbCkc+0KkFapkSnBxBtVHpq\nlBfSJH5YveSpj8ZmmeBirfGcnHhBaA==\n=/Kr9\n-----END PGP SIGNATURE-----\n\n--=-pqHfzN3tdfFfcK4qJTZs--"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/multi.vcf",
    "content": "BEGIN:VCARD\nVERSION:3.0\nFN:Patrice Tran\nN:Tran;Patrice;;;\nX-EVOLUTION-FILE-AS:Tran\\, Patrice\nEMAIL;TYPE=OTHER:patrice@caliopen.org\nUID:pas-id-58AF0A5A00000000\nREV:2017-02-23T16:14:18Z(1)\nEND:VCARD\n\nBEGIN:VCARD\nVERSION:3.0\nFN:Lison Ferez\nN:Ferez;Lison;;;\nX-EVOLUTION-FILE-AS:Ferez\\, Lison\nEMAIL;TYPE=OTHER:lison@caliopen.org\nUID:pas-id-58AF0A5A00000001\nREV:2017-02-23T16:14:18Z(1)\nEND:VCARD\n\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/rfc2425-1.vcard",
    "content": "cn:Babs Jensen\ncn:Barbara J Jensen\nsn:Jensen\nemail:babs@umich.edu\nphone:+1 313 747-4454\nx-id:1234567890\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/rfc2425-2.vcard",
    "content": "begin:VCARD\nsource:ldap://cn=bjorn%20Jensen, o=university%20of%20Michigan, c=US\nname:Bjorn Jensen\nfn:Bj=F8rn Jensen\nn:Jensen;Bj=F8rn\nemail;type=internet:bjorn@umich.edu\ntel;type=work,voice,msg:+1 313 747-4454\nkey;type=x509;encoding=B:dGhpcyBjb3VsZCBiZSAKbXkgY2VydGlmaWNhdGUK\nend:VCARD\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/rfc2425-3.vcard",
    "content": "begin:vcard\nsource:ldap://cn=Meister%20Berger,o=Universitaet%20Goerlitz,c=DE\nname:Meister Berger\nfn:Meister Berger\nn:Berger;Meister\nbday;value=date:1963-09-21\no:Universit=E6t G=F6rlitz\ntitle:Mayor\ntitle;language=de;value=text:Burgermeister\nnote:The Mayor of the great city of\n  Goerlitz in the great country of Germany.\nemail;internet:mb@goerlitz.de\nhome.tel;type=fax,voice,msg:+49 3581 123456\nhome.label:Hufenshlagel 1234\\n\n 02828 Goerlitz\\n\n Deutschland\nkey;type=X509;encoding=b:MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhvcNAQEEBQ\n AwdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENvbW11bmljYXRpb25zI\n ENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZvcm1hdGlvbiBTeXN0ZW1zMRwwGgYDVQQD\n ExNyb290Y2EubmV0c2NhcGUuY29tMB4XDTk3MDYwNjE5NDc1OVoXDTk3MTIwMzE5NDc\n 1OVowgYkxCzAJBgNVBAYTAlVTMSYwJAYDVQQKEx1OZXRzY2FwZSBDb21tdW5pY2F0aW\n 9ucyBDb3JwLjEYMBYGA1UEAxMPVGltb3RoeSBBIEhvd2VzMSEwHwYJKoZIhvcNAQkBF\n hJob3dlc0BuZXRzY2FwZS5jb20xFTATBgoJkiaJk/IsZAEBEwVob3dlczBcMA0GCSqG\n SIb3DQEBAQUAA0sAMEgCQQC0JZf6wkg8pLMXHHCUvMfL5H6zjSk4vTTXZpYyrdN2dXc\n oX49LKiOmgeJSzoiFKHtLOIboyludF90CgqcxtwKnAgMBAAGjNjA0MBEGCWCGSAGG+E\n IBAQQEAwIAoDAfBgNVHSMEGDAWgBT84FToB/GV3jr3mcau+hUMbsQukjANBgkqhkiG9\n w0BAQQFAAOBgQBexv7o7mi3PLXadkmNP9LcIPmx93HGp0Kgyx1jIVMyNgsemeAwBM+M\n SlhMfcpbTrONwNjZYW8vJDSoi//yrZlVt9bJbs7MNYZVsyF1unsqaln4/vy6Uawfg8V\n UMk1U7jt8LYpo4YULU7UZHPYVUaSgVttImOHZIKi4hlPXBOhcUQ==\nend:vcard\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/rfc2426-1.vcard",
    "content": "BEGIN:VCARD\nFN:Rene van der Harten\nN:van der Harten;Rene;J.;Sir;R.D.O.N.\nSORT-STRING:Harten\nEND:VCARD\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/rfc2426-2.vcard",
    "content": "BEGIN:VCARD\nFN:Robert Pau Shou Chang\nN:Pau;Shou Chang;Robert\nSORT-STRING:Pau\nEND:VCARD\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/rfc2426-3.vcard",
    "content": "BEGIN:VCARD\nFN:Osamu Koura\nN:Koura;Osamu\nSORT-STRING:Koura\nEND:VCARD\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/rfc2426-4.vcard",
    "content": "BEGIN:VCARD\nFN:Oscar del Pozo\nN:del Pozo Triscon;Oscar\nSORT-STRING:Pozo\nEND:VCARD\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/rfc2426-5.vcard",
    "content": "BEGIN:VCARD\nFN:Chistine d'Aboville\nN:d'Aboville;Christine\nSORT-STRING:Aboville\nEND:VCARD"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/vcard1.vcf",
    "content": "BEGIN:VCARD\nVERSION:2.1\nADR;TYPE=WORK:;;63-65 Boulevard Massena;Paris;;75314;France\nADR;TYPE=WORK:;;63-65 Boulevard Massena;Paris;;75314;France\nEMAIL;TYPE=WORK:jeffrey1@osafoundation.org\nEMAIL:jeffrey2@osafoundation.org\nEMAIL:jeffrey3@osafoundation.org\nN:Harris;Jeffrey;;;\nIMPP:aim:johndoe@aol.com\nNOTE;ENCODING=QUOTED-PRINTABLE: This is a note associated with this\nTEL:+33 1 02 03 04 05\nTEL:+33 (0)1 02 03 04 05\nFN:Jeffrey Harris\nKEY;TYPE=PGP:http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x9F0FE587374BBE81\nKEY;PGP:http://example.com/key.pgp\nCATEGORIES:swimmer,biker\nEND:VCARD\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/vcard2.vcf",
    "content": "BEGIN:VCARD\nBDAY;VALUE=DATE:1963-09-21\nVERSION:3.0\nADR;TYPE=WORK,POSTAL,PARCEL:;;\nEMAIL;TYPE=INTERNET:deriks@Microsoft.com\nN:Stenerson;Derik\nIMPP:aim:johndoe@aol.com\nNOTE:I am proficient in Tiger-Crane Style,\\nand I am more than proficient in the exquisite art of the Samurai sword.\nTEL:+343 1 02 03 04 05\nTEL:+343 (0)1 02 03 04 05\nTEL:+355 1832 3849\nTEL:+61 777 888 999\nTEL:+43 1 765 437\nTEL:+32 33 12 34 56\nTEL:+229 90 89 77 66\nFN:Derik Stenerson\nKEY;TYPE=PGP:http://example.com/key.pgp\nORG:Microsoft Corporation\nCATEGORIES:swimmer,biker\nEND:VCARD\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/fixtures/vcard/vcard3.vcf",
    "content": "BEGIN:VCARD\nVERSION:4.0\nADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America\nADR;TYPE=home:;;123 Main St.;Springfield;IL;12345;USA\nEMAIL;TYPE=PREF,INTERNET:forrestgump@example.com\nTEL:+93 77 888 9999\nTEL:+852.23.21.33.34\nTEL:+389 4 345 4456\nTEL:+52 (13) 33 22 44 55\nTEL:+235 [22/66/99/77]902876\nTEL:+58 234 454 55 67\nN:Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P.\nIMPP:aim:johndoe@aol.com\nIMPP;TYPE=personal,text,store,pref:im:john@example.com\nNOTE:I am proficient in Tiger-Crane Style,\\nand I am more than proficient in the exquisite art of the Samurai sword.\nFN:Forrest Gump\nFN:Dr. John Doe\nORG:Bubba Gump Shrimp Co.\nORG:ABC\\, Inc.;North American Division;Marketing\nTITLE:Shrimp Man\nLABEL;TYPE=HOME:42 Plantation St.\\nBaytown, LA 30314\\nUnited States of America\nREV:20080424T195243Z\nKEY:data:application/x-pgp-fingerprint,5E61C8780F86295CE17D86779F0FE587374BBE81\nKEY:http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x9F0FE587374BBE81\nKEY:MEDIATYPE=application/pgp-keys:http://example.com/key.pgp\nCATEGORIES:swimmer,biker\nPHOTO;VALUE=URL;TYPE=GIF:http://www.example.com/dir_photos/my_photo.gif\nEND:VCARD\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/parsers/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/parsers/test_email.py",
    "content": "import unittest\nimport os\n\nfrom datetime import datetime\nfrom zope.interface.verify import verifyObject\n\nfrom caliopen_storage.config import Configuration\n\nimport vobject\n\nif 'CALIOPEN_BASEDIR' in os.environ:\n    conf_file = '{}/src/backend/configs/caliopen.yaml.template'. \\\n                format(os.environ['CALIOPEN_BASEDIR'])\nelse:\n    conf_file = '../../../../../configs/caliopen.yaml.template'\n\nConfiguration.load(conf_file, 'global')\n\nfrom caliopen_main.common.helpers.normalize import clean_email_address\n\n\nclass TestEmailParser(unittest.TestCase):\n\n    def test_simple_1(self):\n        email = 'toto@toto.fr'\n        res = clean_email_address(email)\n        self.assertEqual(email, res[0])\n        self.assertEqual(email, res[1])\n\n    def test_with_name_1(self):\n        email = '\"Ceci est, une virgule\" <test@toto.com>'\n        res = clean_email_address(email)\n        self.assertEqual('test@toto.com', res[0])\n        self.assertEqual('test@toto.com', res[1])\n\n    def test_multiple(self):\n        emails = '\"Ceci est, une virgule\" <test@toto.com>, ' \\\n                 '\"Est une, autre virgule\" <test2@toto.tld>'\n        parts = emails.split('>,')\n        self.assertEqual(len(parts), 2)\n        for part in parts:\n            res = clean_email_address(part)\n            self.assertTrue('@' in res[0])\n\n    def test_invalid_but_valid(self):\n        email = 'Ceci [lamentable.ment] <email@truc.tld>'\n        res = clean_email_address(email)\n        self.assertEqual('email@truc.tld', res[0])\n\n    def test_strange_1(self):\n        email = 'ideascube/ideascube <ideascube@noreply.github.com>'\n        res = clean_email_address(email)\n        self.assertEqual('ideascube@noreply.github.com', res[0])\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/parsers/test_mail.py",
    "content": "\"\"\"Test mail message format processing.\"\"\"\n\nimport unittest\nimport os\n\nfrom datetime import datetime\nfrom zope.interface.verify import verifyObject\n\nfrom caliopen_storage.config import Configuration\n\nif 'CALIOPEN_BASEDIR' in os.environ:\n    conf_file = '{}/src/backend/configs/caliopen.yaml.template'. \\\n                format(os.environ['CALIOPEN_BASEDIR'])\nelse:\n    conf_file = '../../../../../configs/caliopen.yaml.template'\n\nConfiguration.load(conf_file, 'global')\n\nfrom caliopen_main.common.interfaces import IMessageParser\nfrom caliopen_main.message.parsers.mail import MailMessage\n\n\ndef load_mail(filename):\n    \"\"\"Read email from fixtures of an user.\"\"\"\n    # XXX tofix: set fixtures in a more convenient way to not\n    # have dirty hacking on relative path\n    dir_path = os.path.dirname(os.path.realpath(__file__))\n    path = '{}/../fixtures/mail'.format(dir_path)\n    with open('{}/{}'.format(path, filename)) as f:\n        data = f.read()\n    return data\n\n\nclass TestMailFormat(unittest.TestCase):\n    \"\"\"Test formatting of a mail objet (rfc2822).\"\"\"\n\n    def test_signed_mail(self):\n        \"\"\"Test parsing of a pgp signed mail.\"\"\"\n        data = load_mail('pgp_signed_1.eml')\n        mail = MailMessage(data)\n        self.assertTrue(verifyObject(IMessageParser, mail))\n        self.assertTrue(len(mail.participants) > 1)\n        self.assertEqual(len(mail.attachments), 1)\n        self.assertEqual(mail.subject, 'signed content')\n        self.assertTrue(isinstance(mail.date, datetime))\n\n    def test_encrypted_mail(self):\n        \"\"\"Test parsing of a pgp encrypted mail.\"\"\"\n        data = load_mail('pgp_crypted_1.eml')\n        mail = MailMessage(data)\n        self.assertTrue(verifyObject(IMessageParser, mail))\n        self.assertTrue(len(mail.participants) > 1)\n        self.assertEqual(len(mail.attachments), 1)\n        self.assertEqual(mail.subject, 'crypted content')\n        self.assertTrue(isinstance(mail.date, datetime))\n        self.assertTrue(mail.extra_parameters.get('encrypted', None), 'pgp')\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/tests/parsers/test_vcard.py",
    "content": "import unittest\nimport os\n\nfrom datetime import datetime\nfrom zope.interface.verify import verifyObject\n\nfrom caliopen_storage.config import Configuration\n\nimport vobject\n\nif 'CALIOPEN_BASEDIR' in os.environ:\n    conf_file = '{}/src/backend/configs/caliopen.yaml.template'. \\\n                format(os.environ['CALIOPEN_BASEDIR'])\nelse:\n    conf_file = '../../../../../configs/caliopen.yaml.template'\n\nConfiguration.load(conf_file, 'global')\n\n#from caliopen_main.interfaces import IMessageParser\nfrom caliopen_main.contact.parameters import NewContact\nfrom caliopen_main.contact.parsers import VcardContact\n\n\ndef load_vcard(filename):\n\n    dir_path = os.path.dirname(os.path.realpath(__file__))\n    path = '{}/../fixtures/vcard'.format(dir_path)\n    with open('{}/{}'.format(path, filename)) as f:\n        data = f.read()\n    return data\n\n\ndef parse_vcard(vcard):\n    contact = VcardContact(vcard)\n    return contact.contact\n\n\nclass TestVcardFormat(unittest.TestCase):\n\n    def test_name_vcard(self):\n        data = load_vcard('vcard2.vcf')\n        vcard = vobject.readOne(data)\n        contact = parse_vcard(vcard)\n        self.assertIsNotNone(contact.family_name)\n        self.assertIsNotNone(contact.given_name)\n    \n    def test_address_vcard(self):\n        data = load_vcard('vcard1.vcf')\n        vcard = vobject.readOne(data)\n        contact = parse_vcard(vcard)\n        for i in contact.addresses:\n            self.assertIsNotNone(i.city)\n\n    def test_email_vcard(self):\n        data = load_vcard('vcard1.vcf')\n        vcard = vobject.readOne(data)\n        contact = parse_vcard(vcard)\n        for i in contact.emails:\n            self.assertIsNotNone(i.address)\n\n    def test_ims_vcard(self):\n        data = load_vcard('vcard1.vcf')\n        vcard = vobject.readOne(data)\n        contact = parse_vcard(vcard)\n        for i in contact.ims:\n            self.assertIsNotNone(i.type)\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/core/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .user import User, Tag, FilterRule, UserIdentity, ReservedName\nfrom .user import allocate_user_shard\nfrom .identity import IdentityLookup, IdentityTypeLookup\n\n__all__ = [\n    'User', 'Tag', 'FilterRule', 'UserIdentity', 'ReservedName',\n    'allocate_user_shard', 'IdentityLookup', 'IdentityTypeLookup'\n]\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/core/identity.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user related core classes.\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom caliopen_storage.core import BaseCore\nfrom caliopen_main.common.core import BaseUserCore\n\nfrom ..store import (UserIdentity as ModelUserIdentity,\n                     IdentityLookup as ModelIdentityLookup,\n                     IdentityTypeLookup as ModelIdentityTypeLookup)\n\n\nclass UserIdentity(BaseUserCore):\n    \"\"\"User's identity core class.\"\"\"\n\n    _model_class = ModelUserIdentity\n    _pkey_name = 'identity_id'\n\n    @classmethod\n    def get_by_identifier(cls, identifier, protocol, user_id):\n        \"\"\"return an array of one or more user_identities\"\"\"\n        if not protocol:\n            ids = IdentityLookup.find(identifier=identifier)\n        elif not user_id:\n            ids = IdentityLookup.find(identifier=identifier,\n                                      protocol=protocol)\n        else:\n            ids = IdentityLookup.find(identifier=identifier,\n                                      protocol=protocol,\n                                      user_id=user_id)\n        identities = []\n        for id in ids:\n            identities.append(\n                UserIdentity.get_by_user_id(id.user_id, id.identity_id))\n        return identities\n\n\nclass IdentityLookup(BaseCore):\n    \"\"\"Lookup table core class.\"\"\"\n\n    _model_class = ModelIdentityLookup\n    _pkey_name = 'identifier'\n\n\nclass IdentityTypeLookup(BaseCore):\n    \"\"\"Lookup table core class\"\"\"\n\n    _model_class = ModelIdentityTypeLookup\n    _pkey_name = 'type'\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/core/setups.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"new user related logic\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport datetime\nimport pytz\n\nfrom caliopen_storage.config import Configuration\nfrom elasticsearch import Elasticsearch\nfrom caliopen_storage.core import core_registry\nfrom caliopen_main.user.objects.settings import Settings as ObjectSettings\n\nlog = logging.getLogger(__name__)\n\n\ndef setup_index(user):\n    \"\"\"Creates user index and setups mappings.\"\"\"\n    url = Configuration('global').get('elasticsearch.url')\n    client = Elasticsearch(url)\n\n    shard_id = user.shard_id\n    # does shard exist ?\n    if not client.indices.exists(shard_id):\n        setup_shard_index(shard_id)\n\n    index_name = shard_id\n    alias_name = user.user_id\n\n    # Points an alias to the underlying user's index\n    try:\n        client.indices.put_alias(index=index_name, name=alias_name)\n    except Exception as exc:\n        log.exception(\"failed to create alias : {0}\".format(exc))\n        raise exc\n\n\ndef setup_shard_index(shard):\n    \"\"\"Setup a shard index.\"\"\"\n    url = Configuration('global').get('elasticsearch.url')\n    client = Elasticsearch(url)\n\n    try:\n        log.info('Creating index {0}'.format(shard))\n        client.indices.create(\n            index=shard,\n            body={\n                \"settings\": {\n                    \"analysis\": {\n                        \"analyzer\": {\n                            \"text_analyzer\": {\n                                \"type\": \"custom\",\n                                \"tokenizer\": \"lowercase\",\n                                \"filter\": [\n                                    \"ascii_folding\"\n                                ]\n                            },\n                            \"email_analyzer\": {\n                                \"type\": \"custom\",\n                                \"tokenizer\": \"email_tokenizer\",\n                                \"filter\": [\n                                    \"ascii_folding\"\n                                ]\n                            }\n                        },\n                        \"filter\": {\n                            \"ascii_folding\": {\n                                \"type\": \"asciifolding\",\n                                \"preserve_original\": True\n                            }\n                        },\n                        \"tokenizer\": {\n                            \"email_tokenizer\": {\n                                \"type\": \"ngram\",\n                                \"min_gram\": 3,\n                                \"max_gram\": 25\n                            }\n                        }\n                    }\n                }\n            })\n    except Exception as exc:\n        log.warn(\"failed to create index {} : {}\".format(shard, exc))\n        return\n\n    # TOFIX\n    # core Message is no more in core_registry, use hard coded build mapping\n    from caliopen_main.message.store.message_index import IndexedMessage\n    from caliopen_main.contact.store.contact_index import IndexedContact\n    log.info('Creating index mapping for message and contact in shard {}'.\n             format(shard))\n    message_mapping = IndexedMessage.build_mapping()\n    message_mapping.save(shard, using=client)\n    contact_mapping = IndexedContact.build_mapping()\n    contact_mapping.save(shard, using=client)\n\n\ndef setup_system_tags(user):\n    \"\"\"Create system tags.\"\"\"\n    # TODO: translate tags'name to user's preferred language\n    default_tags = Configuration('global').get('system.default_tags')\n    for tag in default_tags:\n        tag['type'] = 'system'\n        tag['date_insert'] = datetime.datetime.now(tz=pytz.utc)\n        tag['label'] = tag.get('label', tag['name'])\n        from .user import Tag\n        Tag.create(user, **tag)\n\n\ndef setup_settings(user, settings):\n    \"\"\"Create settings related to user.\"\"\"\n    # XXX set correct values\n\n    settings = {\n        'user_id': user.user_id,\n        'default_locale': settings.default_locale,\n        'message_display_format': settings.message_display_format,\n        'contact_display_order': settings.contact_display_order,\n        'contact_display_format': settings.contact_display_format,\n        'notification_enabled': settings.notification_enabled,\n        'notification_message_preview':\n            settings.notification_message_preview,\n        'notification_sound_enabled':\n            settings.notification_sound_enabled,\n        'notification_delay_disappear':\n            settings.notification_delay_disappear,\n    }\n\n    obj = ObjectSettings(user)\n    obj.unmarshall_dict(settings)\n    obj.marshall_db()\n    obj.save_db()\n    return True\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/core/user.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user related core classes.\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport os\nimport datetime\nimport pytz\nimport bcrypt\nimport logging\nimport uuid\n\nfrom zxcvbn import zxcvbn\nfrom validate_email import validate_email\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_storage.exception import NotFound, CredentialException\nfrom ..store import (User as ModelUser,\n                     UserName as ModelUserName,\n                     UserRecoveryEmail as ModelUserRecoveryEmail,\n                     IndexUser,\n                     UserTag as ModelUserTag,\n                     Settings as ModelSettings,\n                     FilterRule as ModelFilterRule,\n                     ReservedName as ModelReservedName)\nfrom ..core.identity import UserIdentity, IdentityLookup, IdentityTypeLookup\n\nfrom caliopen_storage.core import BaseCore\nfrom caliopen_main.common.core import BaseUserCore\n\nfrom caliopen_main.contact.core import Contact as CoreContact\nfrom caliopen_main.contact.parameters import NewEmail\nfrom caliopen_main.pi.objects import PIModel\nfrom caliopen_main.user.helpers import validators\nfrom .setups import (setup_index, setup_system_tags,\n                     setup_settings)\n\nlog = logging.getLogger(__name__)\n\n\ndef allocate_user_shard(user_id):\n    \"\"\"Find allocation to a shard for an user.\"\"\"\n    shards = Configuration('global').get('elasticsearch.shards')\n    if not shards:\n        raise Exception('No shards configured for index')\n    shard_idx = int(user_id.hex, 16) % len(shards)\n    return shards[shard_idx]\n\n\nclass Tag(BaseUserCore):\n    \"\"\"Tag core object.\"\"\"\n\n    _model_class = ModelUserTag\n    _pkey_name = 'tag_id'\n\n\nclass FilterRule(BaseUserCore):\n    \"\"\"Filter rule core class.\"\"\"\n\n    _model_class = ModelFilterRule\n    _pkey_name = 'rule_id'\n\n    @classmethod\n    def create(cls, user, rule):\n        \"\"\"Create a new filtering rule.\"\"\"\n        rule.validate()\n        # XXX : expr value is evil\n        o = super(FilterRule, cls).create(user_id=user.user_id,\n                                          rule_id=user.new_rule_id(),\n                                          date_insert=datetime.datetime.now(\n                                              tz=pytz.utc),\n                                          name=rule.name,\n                                          filter_expr=rule.expr,\n                                          position=rule.position,\n                                          stop_condition=rule.stop_condition,\n                                          tags=rule.tags)\n        return o\n\n    def eval(self, message):\n        \"\"\"\n        Evaluate if this rule apply to the given message.\n\n        evaluation return a list of TAGS to add or nothing\n        stop condition if set and if result is empty or not\n        and match this stop condition, processing of rules\n        on this message stop.\n\n        \"\"\"\n        # XXXX WARN WARN WARN WARN WARN WARN WARN WARN\n        #\n        # This is a REALLY BASIC filtering concept with no\n        # security consideration abount what is evaluated !!!!\n        #\n        # XXXX WARN WARN WARN WARN WARN WARN WARN WARN\n\n        res = eval(self.filter_expr)\n        if self.stop_condition is not None:\n            if self.stop_condition and res:\n                return self.tags, True\n            if not self.stop_condition and not res:\n                return self.tags, True\n        if res:\n            return self.tags, False\n        return [], False\n\n\nclass ReservedName(BaseCore):\n    \"\"\"Reserved name core object.\"\"\"\n\n    _model_class = ModelReservedName\n    _pkey_name = 'name'\n\n\nclass UserName(BaseCore):\n    \"\"\"User name core object.\"\"\"\n\n    _model_class = ModelUserName\n    _pkey_name = 'name'\n\n\nclass UserRecoveryEmail(BaseCore):\n    \"\"\"User Recovery Email object to retrieve user by recovery_email\"\"\"\n\n    _model_class = ModelUserRecoveryEmail\n    _pkey_name = 'recovery_email'\n\n\nclass Settings(BaseUserCore):\n    \"\"\"User settings core object.\"\"\"\n\n    # XXX this core object is here to fill core_registry\n    # it's not used, objects representation have to be used.\n\n    _model_class = ModelSettings\n    _pkey_name = None\n\n\nclass User(BaseCore):\n    \"\"\"User core object.\"\"\"\n\n    _model_class = ModelUser\n    _pkey_name = 'user_id'\n    _index_class = IndexUser\n\n    @classmethod\n    def _check_whitelistes(cls, user):\n        \"\"\"Check if user is in a white list if apply.\"\"\"\n        whitelistes = Configuration('global').get('whitelistes', {})\n        emails_file = whitelistes.get('user_emails')\n        if emails_file and os.path.isfile(emails_file):\n            with open(emails_file) as f:\n                emails = [x for x in f.read().split('\\n') if x]\n                if user.recovery_email in emails:\n                    return True\n                else:\n                    raise ValueError('user email not in whitelist')\n\n    @classmethod\n    def _check_max_users(cls):\n        \"\"\"Check if maximum number of users reached.\"\"\"\n        conf = Configuration('global').get('system', {})\n        max_users = conf.get('max_users', 0)\n        if max_users:\n            nb_users = User._model_class.objects.count()\n            if nb_users >= max_users:\n                raise ValueError('Max number of users reached')\n\n    @classmethod\n    def create(cls, new_user):\n        \"\"\"Create a new user.\n\n        @param: new_user is a parameters/user.py.NewUser object\n        # 1.check username regex\n        # 2.check username is not in reserved_name table\n        # 3.check recovery email validity (TODO : check if email is not within\n        #   the current Caliopen's instance)\n        # 4.check username availability\n        # 5.add username to user cassa user_name table (to block the\n        #   availability)\n        # 6.check password strength (and regex?)\n        # then\n        #      create user and linked contact\n        \"\"\"\n\n        def rollback_username_storage(username):\n            UserName.get(username).delete()\n\n        # 0. check for user email white list and max number of users\n        cls._check_whitelistes(new_user)\n        cls._check_max_users()\n\n        # 1.\n        try:\n            validators.is_valid_username(new_user.name)\n        except SyntaxError:\n            raise ValueError(\"Malformed username\")\n\n        # 2.\n        try:\n            ReservedName.get(new_user.name)\n            raise ValueError('Reserved user name')\n        except NotFound:\n            pass\n\n        user_id = uuid.uuid4()\n        # 3.\n        if not new_user.recovery_email:\n            raise ValueError(\"Missing recovery email\")\n\n        try:\n            cls.validate_recovery_email(new_user.recovery_email)\n        except Exception as exc:\n            log.info(\"recovery email failed validation : {}\".format(exc))\n            raise ValueError(exc)\n\n        # 4. & 5.\n        if User.is_username_available(new_user.name.lower()):\n            # save username immediately to prevent concurrent creation\n            UserName.create(name=new_user.name.lower(), user_id=user_id)\n            # NB : need to rollback this username creation if the below\n            #      User creation failed for any reason\n        else:\n            raise ValueError(\"Username already exist\")\n\n        # 6.\n        try:\n            user_inputs = [new_user.name.encode(\"utf-8\"),\n                           new_user.recovery_email.encode(\"utf-8\")]\n            # TODO: add contact inputs if any\n            password_strength = zxcvbn(new_user.password,\n                                       user_inputs=user_inputs)\n            privacy_features = {\"password_strength\":\n                                    str(password_strength[\"score\"])}\n            passwd = new_user.password.encode('utf-8')\n            new_user.password = bcrypt.hashpw(passwd, bcrypt.gensalt())\n        except Exception as exc:\n            log.exception(exc)\n            rollback_username_storage(new_user.name)\n            raise exc\n\n        try:\n            new_user.validate()  # schematic model validation\n        except Exception as exc:\n            rollback_username_storage(new_user.name)\n            log.info(\"schematics validation error: {}\".format(exc))\n            raise ValueError(\"new user malformed\")\n\n        try:\n            recovery = new_user.recovery_email\n            if hasattr(new_user, \"contact\"):\n                family_name = new_user.contact.family_name\n                given_name = new_user.contact.given_name\n            else:\n                family_name = \"\"\n                given_name = \"\"\n\n            # XXX PI compute\n            pi = PIModel()\n            pi.technic = 0\n            pi.comportment = 0\n            pi.context = 0\n            pi.version = 0\n            shard_id = allocate_user_shard(user_id)\n\n            core = super(User, cls).create(user_id=user_id,\n                                           name=new_user.name,\n                                           password=new_user.password,\n                                           recovery_email=recovery,\n                                           params=new_user.params,\n                                           date_insert=datetime.datetime.now(\n                                               tz=pytz.utc),\n                                           privacy_features=privacy_features,\n                                           pi=pi,\n                                           family_name=family_name,\n                                           given_name=given_name,\n                                           shard_id=shard_id)\n        except Exception as exc:\n            log.info(exc)\n            rollback_username_storage(new_user.name)\n            raise exc\n\n        # **** operations below do not raise fatal error and rollback **** #\n        # Setup index\n        setup_index(core)\n        # Setup others entities related to user\n        setup_system_tags(core)\n        setup_settings(core, new_user.settings)\n\n        UserRecoveryEmail.create(recovery_email=recovery, user_id=user_id)\n        # Add a default local identity on a default configured domain\n        default_domain = Configuration('global').get('default_domain')\n        default_local_id = '{}@{}'.format(core.name, default_domain)\n        if not core.add_local_identity(default_local_id):\n            log.warn('Impossible to create default local identity {}'.\n                     format(default_local_id))\n\n        # save and index linked contact\n        if hasattr(new_user, \"contact\"):\n            #  add local email to contact\n            local_mail = NewEmail()\n            local_mail.address = default_local_id\n            new_user.contact.emails.append(local_mail)\n\n            #  create default contact for user\n            contact = CoreContact.create(core, new_user.contact)\n\n            core.model.contact_id = contact.contact_id\n\n            log.info(\"contact id {} for new user {}\".format(contact.contact_id,\n                                                            core.user_id))\n        else:\n            log.error(\n                \"missing contact in new_user params for user {}. \"\n                \"Can't create related tables\".format(core.user_id))\n\n        core.save()\n        return core\n\n    @classmethod\n    def by_name(cls, name):\n        \"\"\"Get user by name.\"\"\"\n        uname = UserName.get(name.lower())\n        return cls.get(uname.user_id)\n\n    @classmethod\n    def is_username_available(cls, username):\n        \"\"\"Return True if username is available.\"\"\"\n        try:\n            UserName.get(username.lower())\n            return False\n        except NotFound:\n            return True\n\n    @classmethod\n    def by_local_identifier(cls, address, protocol):\n        \"\"\"Get a user by one of a local identifier.\"\"\"\n        identities = UserIdentity.get_by_identifier(address.lower(), protocol,\n                                                    None)\n        for identity in identities:\n            if identity.type == 'local':\n                return cls.get(identity.user_id)\n        raise NotFound\n\n    @classmethod\n    def authenticate(cls, user_name, password):\n        \"\"\"Authenticate an user.\"\"\"\n        try:\n            user = cls.by_name(user_name)\n        except NotFound:\n            raise CredentialException('Invalid user')\n\n        if user.date_delete:\n            raise CredentialException('Invalid credentials')\n\n        # XXX : decode unicode not this way\n        if bcrypt.hashpw(str(password.encode('utf-8')),\n                         str(user.password)) == user.password:\n            return user\n        raise CredentialException('Invalid credentials')\n\n    @property\n    def contact(self):\n        \"\"\"User is a contact.\"\"\"\n        if self.contact_id is None:\n            return None\n        try:\n            return CoreContact.get(self, self.contact_id)\n        except NotFound:\n            log.warn(\"contact {} not found for user {}\".\n                     format(self.contact_id, self.user_id))\n            return None\n\n    @property\n    def tags(self):\n        \"\"\"Tag objects related to an user.\"\"\"\n        objs = Tag._model_class.filter(user_id=self.user_id)\n        return [Tag(x) for x in objs]\n\n    @property\n    def rules(self):\n        \"\"\"Filtering rules associated to an user, sorted by position.\"\"\"\n        objs = FilterRule._model_class.filter(user_id=self.user_id)\n        cores = [FilterRule(x) for x in objs]\n        return sorted(cores, key=lambda x: x.position)\n\n    def add_local_identity(self, address):\n        \"\"\"\n        Add a local smtp identity to an user and fill related lookup tables\n        \"\"\"\n        formatted = address.lower()\n        try:\n            local_identity = User.by_local_identifier(address, 'smtp')\n            if local_identity:\n                log.warn(\"local identifier {} already exist\".format(address))\n                return None\n        except NotFound:\n            try:\n                identities = IdentityLookup.find(identifier=formatted)\n                if len(identities) == 0: raise NotFound\n                for id in identities:\n                    identity = UserIdentity.get(self, id.identity_id)\n                    if identity and identity.protocol == 'smtp' and \\\n                            identity.user_id == self.user_id:\n                        if identity.identity_id in self.local_identities:\n                            # Local identity already created. Should raise error ?\n                            return identity\n                    raise Exception(\n                        'Inconsistent local identity {}'.format(address))\n            except NotFound:\n                display_name = \"{} {}\".format(self.given_name, self.family_name)\n                identity = UserIdentity.create(self, identifier=formatted,\n                                               identity_id=uuid.uuid4(),\n                                               type='local',\n                                               status='active',\n                                               protocol='email',\n                                               display_name=display_name)\n                #  insert entries in relevant lookup tables\n                IdentityLookup.create(identifier=identity.identifier,\n                                      protocol=identity.protocol,\n                                      user_id=identity.user_id,\n                                      identity_id=identity.identity_id)\n                IdentityTypeLookup.create(type=identity.type,\n                                          user_id=identity.user_id,\n                                          identity_id=identity.identity_id)\n                return identity\n            except Exception as exc:\n                log.error('Unexpected exception {}'.format(exc))\n            return None\n\n    @property\n    def local_identities(self):\n        return IdentityTypeLookup.find(type='local', user_id=self.user_id)\n\n    @classmethod\n    def validate_recovery_email(cls, email):\n        \"\"\"\n        provided email has to pass the validations below,\n        otherwise this func will raise an error\n        @:arg email: string\n        \"\"\"\n\n        #  1. is email well-formed ?\n        if not validate_email(email):\n            raise ValueError(\"recovery email malformed\")\n\n        # 2. is email already in our db ? (recovery_email must be unique)\n        try:\n            UserRecoveryEmail.get(email)\n        except NotFound:\n            pass\n        else:\n            raise ValueError(\"recovery email already used in this instance\")\n\n        # 3. is email belonging to one of our domains ?\n        #  (recovery_email must be external)\n        domain = email.split(\"@\")[1]\n        if domain in Configuration(\"global\").get(\"default_domain\"):\n            raise ValueError(\n                \"recovery email must be outside of this domain instance\")\n\n            # 4. TODO: check that provided recovery email can really receive email\n            # send a confirmation email ?\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/helpers/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/helpers/mergePatch.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nFunctions to handle patch dict arriving from clients\nto update client's resources\n\"\"\"\n\nimport logging\nlog = logging.getLogger(__name__)\n\n\ndef merge_patch(target, patch):\n    \"\"\"rfc 7396 merge patch implementation\"\"\"\n    if type(patch) is dict:\n        if type(target) is not dict:\n            target = {}  # Ignore the contents and set it to an empty Object\n        for key, value in patch.iteritems():\n            if value is None:\n                if key in target:\n                    del target[key]\n            else:\n                target[key] = merge_patch(target[key], value)\n        return target\n    else:\n        return patch\n\n\nclass MergeBatch(object):\n\n    \"\"\"Describe a batch of operations for processing a merge.\"\"\"\n\n    def __init__(self, core):\n        self.obj = core\n        self.operations = []\n\n    def add_operation(self, type, column, value):\n        self.operations.append((type, column, value))\n\n    def process(self):\n        \"\"\"Process the merge on core object.\"\"\"\n        log.info(\"process operations : {}\".format(self.operations))\n        for typ, column, value in self.operations:\n            core_attr = getattr(self.obj, column)\n            if typ == 'add':\n                core_attr = core_attr + value\n            elif typ == 'del':\n                core_attr = core_attr - value\n            elif typ == 'replace':\n                core_attr = value\n            else:\n                raise NotImplementedError('Merge operation {} not supported',\n                                          format(typ))\n        self.obj.save()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/helpers/validators.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport regex\n\n\ndef is_valid_username(username):\n    \"\"\" conforms to doc/RFCs/username_specifications\"\"\"\n\n    if len(username) < 3:\n        raise SyntaxError\n\n    rgx = ur\"^(([^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\u0022,@\\u0060:;<>\\[\\\\\\]]|\" \\\n          ur\"[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\u0022,@\\u0060:;<>\\[\\\\\\]]\\.)){1,40}\" \\\n          ur\"[^\\p{C}\\p{M}\\p{Lm}\\p{Sk}\\p{Z}.\\u0022,@\\u0060:;<>\\[\\\\\\]]$\"\n\n    r = regex.compile(rgx, flags=regex.V1+regex.UNICODE)\n\n    if r.match(username) is None:\n        raise SyntaxError\n\n\ndef main():\n    try:\n        is_valid_username(\"0123❤xxx___the______johnΔœuf\")\n    except:\n        print(\"username NOT valid\")\n        return\n\n    print(\"username OK\")\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/objects/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/objects/device.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/objects/identity.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen message object classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport types\nimport datetime\nfrom uuid import UUID\n\nfrom caliopen_main.common.objects.base import ObjectUser\n\nfrom ..store.identity import UserIdentity as ModelUserIdentity\n\n\nclass Credentials():\n    _attrs = {}\n\n\nclass UserIdentity(ObjectUser):\n    \"\"\"Local or remote identity related to an user.\"\"\"\n\n    _attrs = {\n        'credentials': Credentials,\n        'display_name': types.StringType,\n        'identifier': types.StringType,  # for example: me@caliopen.org\n        'identity_id': UUID,\n        'infos': types.DictionaryType,\n        'last_check': datetime.datetime,\n        'protocol': types.StringType,  # for example: smtp, imap, mastodon\n        'status': types.StringType,  # for example : active, inactive, deleted\n        'type': types.StringType,  # for example : local, remote\n        'user_id': UUID\n    }\n\n    _model_class = ModelUserIdentity\n    _pkey_name = 'identity_id'\n    _db = None  # model instance with data from db\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/objects/settings.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen User tag parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport types\nimport uuid\n\nfrom caliopen_main.user.parameters.settings import Settings as SettingsParam\n\nfrom caliopen_main.user.store import Settings as ModelSettings\nfrom caliopen_main.common.objects.base import ObjectUser\n\nlog = logging.getLogger(__name__)\n\n\nclass Settings(ObjectUser):\n    \"\"\"Settings related to an user.\"\"\"\n\n    _attrs = {\n        'user_id': uuid.UUID,\n        'default_locale': types.StringType,\n        'message_display_format': types.StringType,\n        'contact_display_order': types.StringType,\n        'contact_display_format': types.StringType,\n        'notification_enabled': types.BooleanType,\n        'notification_message_preview': types.StringType,\n        'notification_sound_enabled': types.BooleanType,\n        'notification_delay_disappear': types.IntType,\n    }\n\n    _model_class = ModelSettings\n    _pkey_name = None\n    _json_model = SettingsParam\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/objects/tag.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen User tag parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport types\nimport uuid\nimport datetime\n\nfrom caliopen_main.common.objects.base import ObjectUser\nfrom caliopen_main.user.store import UserTag as ModelUserTag\n\nimport logging\nlog = logging.getLogger(__name__)\n\n\nclass UserTag(ObjectUser):\n    \"\"\"Tag related to an user.\"\"\"\n\n    _attrs = {\n        'date_insert': datetime.datetime,\n        'importance_level': types.IntType,\n        'name': types.StringType,\n        'label': types.StringType,\n        'type': types.StringType,\n        'user_id': uuid.UUID\n    }\n\n    _model_class = ModelUserTag\n    _pkey_name = 'tag_id'\n\n    def delete_db(self):\n        \"\"\"Delete a tag in store.\"\"\"\n        self._db.delete()\n        return True\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/parameters/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .user import NewUser, User, NewRule\n\nfrom caliopen_main.common.parameters.types import InternetAddressType, PhoneNumberType\n\nfrom .tag import NewUserTag, UserTag\nfrom .identity import NewUserIdentity, UserIdentity\nfrom .settings import Settings\n\n__all__ = [\n    'InternetAddressType', 'PhoneNumberType',\n    'NewUser', 'User', 'NewRule',\n    'NewUserTag', 'UserTag',\n    'NewUserIdentity', 'UserIdentity',\n    'Settings',\n]\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/parameters/identity.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user parameters.\"\"\"\n\nfrom schematics.models import Model\nfrom schematics.types import StringType, UUIDType, DateTimeType\nfrom schematics.types.compound import DictType\n\nimport caliopen_storage.helpers.json as helpers\n\nUSER_IDENTITY_PROTOCOLS = ['smtp', 'imap']\nUSER_IDENTITY_STATUS = ['active', 'inactive', 'deleted']\nUSER_IDENTITY_TYPES = ['local', 'remote']\n\n\nclass NewUserIdentity(Model):\n    credentials = DictType(StringType, default=lambda: {})\n    display_name = StringType()\n    identifier = StringType(required=True)\n    infos = DictType(StringType, default=lambda: {})\n    protocol = StringType(choices=USER_IDENTITY_PROTOCOLS)\n    status = StringType(default='active', choices=USER_IDENTITY_STATUS)\n    type = StringType(choices=USER_IDENTITY_TYPES)\n\n\nclass UserIdentity(NewUserIdentity):\n    user_id = UUIDType()\n    identity_id = UUIDType()\n    last_check = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                              tzd=u'utc')\n\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/parameters/settings.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen contact parameters classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom schematics.models import Model\nfrom schematics.types import StringType, IntType, BooleanType\n\nMESSAGE_FORMAT_CHOICES = ['rich_text', 'plain_text']\nCONTACT_FORMAT_CHOICES = ['given_name, family_name',\n                          'family_name, given_name']\nCONTACT_ORDER_CHOICES = ['family_name', 'given_name']\nPREVIEW_CHOICES = ['off', 'always']\nDELAY_CHOICES = [0, 5, 10, 30]\n\n\nclass Settings(Model):\n    \"\"\"Location structure for a device.\"\"\"\n\n    default_locale = StringType(default='fr-FR')\n    message_display_format = StringType(default='rich_text',\n                                        choices=MESSAGE_FORMAT_CHOICES)\n    contact_display_format = StringType(default='family_name, given_name',\n                                        choices=CONTACT_FORMAT_CHOICES)\n    contact_display_order = StringType(default='given_name',\n                                       choices=CONTACT_ORDER_CHOICES)\n    notification_enabled = BooleanType(default=True)\n    notification_message_preview = StringType(default='always',\n                                              choices=PREVIEW_CHOICES)\n    notification_sound_enabled = BooleanType(default=False)\n    notification_delay_disappear = IntType(default=10,\n                                           choices=DELAY_CHOICES)\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/parameters/tag.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen tags parameters.\"\"\"\n\nfrom schematics.models import Model\nfrom schematics.types import StringType, UUIDType, DateTimeType\nfrom schematics.transforms import blacklist\n\nimport caliopen_storage.helpers.json as helpers\n\nclass NewUserTag(Model):\n    \"\"\"Create a new user tag.\"\"\"\n\n    user_id = UUIDType()\n    name = StringType()\n\n    class Option:\n        roles = {'default': blacklist('user_id')}\n        serialize_when_none = False\n\n\nclass UserTag(NewUserTag):\n    \"\"\"Existing user tag.\"\"\"\n\n    type = StringType()\n    date_insert = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n\nclass ImportedTag(Model):\n    \"\"\"Create a tag from external label or flag\"\"\"\n\n    user_id = UUIDType()\n    label = StringType()\n    name = StringType()\n    type = StringType()\n\n    class Option:\n        roles = {'default': blacklist('user_id')}\n        serialize_when_none = False"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/parameters/user.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen user parameters.\"\"\"\n\nfrom schematics.models import Model\nfrom schematics.types import (StringType, UUIDType, IntType,\n                              DateTimeType, BooleanType, EmailType)\nfrom schematics.types.compound import ModelType, DictType, ListType\nfrom schematics.transforms import blacklist\n\nfrom caliopen_main.contact.parameters import NewContact, Contact\nfrom .settings import Settings\n\nfrom caliopen_main.pi.parameters import PIParameter\nimport caliopen_storage.helpers.json as helpers\n\n\nclass NewUser(Model):\n    \"\"\"\n    Parameter to create a new user.\n\n    name, recovery_email and password are required\n    a ``NewContact`` can be attached when creating user\n    \"\"\"\n\n    contact = ModelType(NewContact)\n    main_user_id = UUIDType()\n    name = StringType(required=True)\n    params = DictType(StringType())\n    password = StringType(required=True)\n    recovery_email = EmailType(required=True)\n    settings = ModelType(Settings, default=lambda: {})\n\n    class Options:\n        serialize_when_none = False\n\n\nclass User(NewUser):\n    \"\"\"Existing user.\"\"\"\n\n    contact = ModelType(Contact)\n    date_insert = DateTimeType(serialized_format=helpers.RFC3339Milli,\n                               tzd=u'utc')\n    family_name = StringType()\n    given_name = StringType()\n    password = StringType()     # not outpout by default, not required\n    privacy_features = DictType(StringType, default=lambda: {}, )\n    pi = ModelType(PIParameter)\n    user_id = UUIDType()\n\n    class Options:\n        roles = {'default': blacklist('password', 'settings')}\n        serialize_when_none = False\n\n\nclass NewRule(Model):\n    \"\"\"New filter rule.\"\"\"\n\n    name = StringType(required=True)\n    expr = StringType(required=True)\n    position = IntType()\n    stop_condition = BooleanType(default=False)\n    tags = ListType(StringType)\n\n    class Options:\n        serialize_when_none = False\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/returns/__init__.py",
    "content": "# from .user import ReturnUser\n#\n# from .contact import ReturnContact, ReturnShortContact\n# from .contact import ReturnEmail, ReturnIM, ReturnPhone\n# from .contact import ReturnAddress, ReturnOrganization\n# from .contact import ReturnPublicKey, ReturnSocialIdentity\n\n__all__ = [\n    'ReturnUser',\n    'ReturnContact', 'ReturnShortContact',\n    'ReturnEmail', 'ReturnIM', 'ReturnPhone',\n    'ReturnAddress', 'ReturnOrganization',\n    'ReturnPublicKey', 'ReturnSocialIdentity'\n]\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/returns/user.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"User return object structure.\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom caliopen_storage.parameters import ReturnCoreObject\nfrom ..core import User, UserIdentity\nfrom ..parameters import User as UserParam\nfrom ..parameters import UserIdentity as UserIdentityParam\n\n\nclass ReturnUser(ReturnCoreObject):\n    \"\"\"Return object for ``User`` core.\"\"\"\n\n    _core_class = User\n    _return_class = UserParam\n\n\nclass ReturnUserIdentity(ReturnCoreObject):\n    \"\"\"Return object for ``UserIdentity`` core.\"\"\"\n\n    _core_class = UserIdentity\n    _return_class = UserIdentityParam\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/store/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom .user import User, UserName, ReservedName, FilterRule, UserRecoveryEmail\nfrom .user import IndexUser, Settings\nfrom .identity import UserIdentity, IdentityLookup, IdentityTypeLookup\nfrom .tag import UserTag\n\n\n__all__ = [\n    'User', 'UserName', 'UserRecoveryEmail', 'UserTag', 'FilterRule',\n    'ReservedName', 'UserIdentity', 'IdentityLookup', 'IdentityTypeLookup',\n    'IndexUser', 'UserTag', 'Settings',\n]\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/store/identity.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen cassandra models related to device.\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom caliopen_storage.store.model import BaseModel\n\nfrom cassandra.cqlengine import columns\n\nlog = logging.getLogger(__name__)\n\n\nclass UserIdentity(BaseModel):\n    \"\"\"User's identities model.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    identity_id = columns.UUID(primary_key=True)\n    credentials = columns.Map(columns.Text, columns.Text)\n    display_name = columns.Text()\n    identifier = columns.Text()\n    infos = columns.Map(columns.Text, columns.Text)\n    last_check = columns.DateTime()\n    protocol = columns.Text()\n    status = columns.Text()\n    type = columns.Text()\n\n\nclass IdentityLookup(BaseModel):\n    \"\"\"Model for identity_lookup table to retrieve identity by identifier\"\"\"\n\n    identifier = columns.Text(primary_key=True)\n    protocol = columns.Text(primary_key=True)\n    user_id = columns.UUID(primary_key=True)\n    identity_id = columns.UUID()\n\n\nclass IdentityTypeLookup(BaseModel):\n    \"\"\"Model for identity_type_lookup table to retrieve identity by type\"\"\"\n\n    type = columns.Text(primary_key=True)\n    user_id = columns.UUID(primary_key=True)\n    identity_id = columns.UUID(primary_key=True)\n\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/store/tag.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen tag objects.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nfrom cassandra.cqlengine import columns\n\nfrom caliopen_storage.store import BaseModel\n\n\nclass UserTag(BaseModel):\n    \"\"\"User tags model.\"\"\"\n    user_id = columns.UUID(primary_key=True)\n    name = columns.Text(primary_key=True)\n    date_insert = columns.DateTime()\n    importance_level = columns.Integer()\n    label = columns.Text()\n    type = columns.Text()\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/store/user.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen cassandra objects related to user.\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport uuid\n\nfrom cassandra.cqlengine import columns\nfrom caliopen_storage.store.model import BaseModel\nfrom caliopen_main.pi.objects import PIModel\n\nlog = logging.getLogger(__name__)\n\n\nclass UserName(BaseModel):\n    \"\"\"Maintain unicity of user name and permit lookup to user_id.\"\"\"\n\n    name = columns.Text(primary_key=True)\n    user_id = columns.UUID(required=True)\n\n\nclass UserRecoveryEmail(BaseModel):\n    \"\"\"Permit user lookup by recovery_email.\"\"\"\n\n    recovery_email = columns.Text(primary_key=True)\n    user_id = columns.UUID(required=True)\n\n\nclass ReservedName(BaseModel):\n    \"\"\"List of reserved user names.\"\"\"\n\n    name = columns.Text(primary_key=True)\n\n\nclass User(BaseModel):\n    \"\"\"User main model.\"\"\"\n\n    user_id = columns.UUID(primary_key=True, default=uuid.uuid4)\n    name = columns.Text(required=True)\n    password = columns.Text(required=True)\n    date_insert = columns.DateTime()\n    date_delete = columns.DateTime()\n    given_name = columns.Text()\n    family_name = columns.Text()\n    params = columns.Map(columns.Text, columns.Text)\n    contact_id = columns.UUID()\n    main_user_id = columns.UUID()\n    recovery_email = columns.Text(required=True)\n    shard_id = columns.Text()\n\n    privacy_features = columns.Map(columns.Text(), columns.Text())\n    pi = columns.UserDefinedType(PIModel)\n\n\nclass FilterRule(BaseModel):\n    \"\"\"User filter rules model.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    rule_id = columns.UUID(primary_key=True)\n    date_insert = columns.DateTime()\n    name = columns.Text()\n    filter_expr = columns.Text()\n    position = columns.Integer()\n    stop_condition = columns.Boolean()\n\n\nclass Settings(BaseModel):\n    \"\"\"All settings related to an user.\"\"\"\n\n    user_id = columns.UUID(primary_key=True)\n    default_locale = columns.Text()\n    message_display_format = columns.Text()\n    contact_display_format = columns.Text()\n    contact_display_order = columns.Text()\n    notification_enabled = columns.Boolean()\n    notification_message_preview = columns.Text()\n    notification_sound_enabled = columns.Boolean()\n    notification_delay_disappear = columns.Integer()\n\n\nclass IndexUser(object):\n    \"\"\"User index management class.\"\"\"\n"
  },
  {
    "path": "src/backend/main/py.main/caliopen_main/user/store/user_index.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen index classes for nested tag.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom elasticsearch_dsl import InnerObjectWrapper, Keyword, Text\nfrom caliopen_storage.store.model import BaseIndexDocument\n\nlog = logging.getLogger(__name__)\n\n\nclass IndexedUser(BaseIndexDocument):\n    doc_type = 'indexed_local_identity'\n\n    display_name = Text()\n    identifier = Text()\n    status = Keyword()\n    type = Keyword()\n\n\nclass IndexedIdentity(InnerObjectWrapper):\n    \"\"\"nested identity within a message\"\"\"\n\n    identifier = Text()\n    type = Keyword()\n"
  },
  {
    "path": "src/backend/main/py.main/requirements.deps",
    "content": "caliopen_storage\ncaliopen_pi"
  },
  {
    "path": "src/backend/main/py.main/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliopen\nwith-coverage = 1\ncover-erase = 1\n"
  },
  {
    "path": "src/backend/main/py.main/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_main\"\n\nwith open(os.path.join(*([here] + name.split('.') + ['__init__.py']))) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S).match(v_file.read()).group(1)\n\nrequires = [\n    'phonenumbers',\n    'pytz',\n    'zxcvbn_python',\n    'validate_email',\n    'uuid',\n    'regex',\n    'zope.interface',\n    'vobject',\n    'minio<5',\n]\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\nextras_require = {\n    'dev': [],\n    'test': [\n        'coverage',\n        'docker-py',\n        'freezegun',\n        'nose'\n    ],\n}\n\nsetup(name=name,\n      version=version,\n      description='Caliopen main package. Entry point for whole application',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\"Programming Language :: Python\", ],\n      author='Caliopen contributors',\n      author_email='contact@caliopen.org',\n      url='https://caliopen.org',\n      license='AGPLv3',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      extras_require=extras_require,\n      install_requires=requires,\n      tests_require=requires,\n      )\n"
  },
  {
    "path": "src/backend/main/py.storage/CHANGES.rst",
    "content": "0.0.1\n-----\n\n-  Initial version\n"
  },
  {
    "path": "src/backend/main/py.storage/MANIFEST.in",
    "content": "include *.cfg *.rst *.template\n"
  },
  {
    "path": "src/backend/main/py.storage/README.rst",
    "content": "Entry point\n===========\n\nThis repository is part of CaliOpen platform. For documentation, installation and\ncontribution instructions, please refer to https://caliopen.github.io\n\nCaliopen Storage\n=============\n\nThis is the base storage package for caliopen platform.\n\nIt contains following sub packages:\n\n    store \n        All classes related to datastore.\n        Base model User and Contact are included.\n\n    core\n        Classes where business logic must be define.\n        Datastores objects are not directly managed,\n        they must have a related core class to act as\n        an interface with others caliopen components.\n\n    helpers\n        Some common helpers for all caliopen parts.\n\n\nNotes\n-----\n\nwaitress and cassandra driver conflict\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nCassandra python driver use async_core by default and can\nconflict with waitress event loop (1). It is advocated to install\nlibev on your system to avoid this problem (2).\n\n(1) https://github.com/Pylons/waitress/issues/63\n(2) http://datastax.github.io/python-driver/installation.html#c-extensions\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n__version__ = '0.23.0'\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/config.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen configuration class.\"\"\"\n\nfrom __future__ import unicode_literals, absolute_import\n\nimport yaml\n\ntry:\n    from yaml import CSafeLoader as YAMLLoader\nexcept ImportError:\n    from yaml import SafeLoader as YAMLLoader\n\n\nclass Configuration(object):\n\n    \"\"\" Configuration store.\"\"\"\n\n    _conffiles = {}\n    _filename = None\n\n    def __init__(self, name):\n        self._name = name\n\n    @classmethod\n    def load(cls, filename, name=None):\n        \"\"\"\n        Load configuration from `filename`.\n\n        An optional `name` is recommended to use many environment.\n        \"\"\"\n        name = name or filename\n\n        if name not in cls._conffiles:\n            with open(filename) as fdesc:\n                cls._conffiles[name] = yaml.load(fdesc, YAMLLoader)\n        return cls(name)\n\n    @property\n    def configuration(self):\n        \"\"\" Get the configuration for current object.\n\n        .. deprecated:: use the :meth:`get` instead\n        \"\"\"\n        return self._conffiles[self._name]\n\n    def get(self, key, default=None, separator='.'):\n        \"\"\" Retrieve a configuration setting.\n\n        :param key: a dot separated string\n        :type key: str\n        \"\"\"\n        key = key.split(separator)\n        value = self.configuration\n        try:\n            for k in key:\n                value = value[k]\n            return value\n        except KeyError:\n            return default\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/core/__init__.py",
    "content": "from .registry import core_registry\nfrom .base import BaseCore\nfrom .mixin import MixinCoreRelation\n\n__all__ = [\n    'core_registry', 'BaseCore', 'MixinCoreRelation']\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/core/base.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\nCaliop core base class.\n\nCore are glue code to the storage layer.\n\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\nfrom six import add_metaclass\nfrom uuid import UUID\nimport logging\n\nfrom ..exception import NotFound\nfrom ..core.registry import core_registry\n\nlog = logging.getLogger(__name__)\n\n\nclass CoreMetaClass(type):\n\n    \"\"\"\n    Metaclass for all core.\n\n    For all core classes related to a model, add it to core_registry.\n    \"\"\"\n\n    def __init__(cls, name, bases, namespace):\n        super(CoreMetaClass, cls).__init__(name, bases, namespace)\n        if cls._model_class:\n            table_name = cls._model_class.__name__\n            if not core_registry.get(table_name):\n                core_registry.update({table_name: cls})\n\n\n@add_metaclass(CoreMetaClass)\nclass BaseCore(object):\n\n    \"\"\"Base class for all core objects.\"\"\"\n\n    _model_class = None\n    _lookup_class = None\n    _pkey_name = 'id'\n\n    def __init__(self, model):\n        \"\"\"Initialize a core object with a model.\"\"\"\n        self.model = model\n\n    @classmethod\n    def create(cls, **attrs):\n        \"\"\"Create a core object.\"\"\"\n        obj = cls._model_class.create(**attrs)\n        return cls(obj)\n\n    @classmethod\n    def get(cls, key):\n        \"\"\"Get a core object by key.\"\"\"\n        params = {cls._pkey_name: key}\n        obj = cls._model_class.get(**params)\n        if obj:\n            return cls(obj)\n        raise NotFound('%s #%s not found' % (cls._model_class.__name__, key))\n\n    def save(self):\n        \"\"\"Save a core object.\"\"\"\n        return self.model.save()\n\n    def delete(self):\n        \"\"\"Delete a core object.\"\"\"\n        # XXX delete related object (relation, lookup)\n        return self.model.delete()\n\n    def __getattr__(self, attr):\n        \"\"\"\n        used to proxy model attribute.\n\n        Does not proxy attributed retrieve via a \"lookup\".\n        \"\"\"\n        if attr in self.model._columns.keys():\n            value = getattr(self.model, attr)\n            if isinstance(value, UUID):\n                return str(value)\n            return value\n\n    def get_id(self):\n        \"\"\"Return object id defined as its primary key.\"\"\"\n        return getattr(self, self._pkey_name)\n\n    @classmethod\n    def find(cls, **kwargs):\n        \"\"\"Find core objects, can only use columns part of primary key.\"\"\"\n        if 'count' in kwargs:\n            count = kwargs.pop('count')\n        else:\n            count = False\n        if not kwargs:\n            objs = cls._model_class.all()\n        else:\n            objs = cls._model_class.filter(**kwargs)\n        if count:\n            return objs.count()\n        return [cls(x) for x in objs]\n\n    @classmethod\n    def count(cls, **kwargs):\n        \"\"\"Count core objects matching filters.\"\"\"\n        kwargs['count'] = True\n        return cls.find(**kwargs)\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/core/mixin.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliop core mixin classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\nimport uuid\n\nfrom ..exception import NotFound\n\nlog = logging.getLogger(__name__)\n\n\nclass MixinCoreRelation(object):\n\n    \"\"\"Mixin to manage relations on core object.\"\"\"\n\n    def _expand_relation(self, reltype):\n        \"\"\"Return collection for given relation.\"\"\"\n        res = self._relations[reltype].find(self.user, self)\n        return res['data'] if res else []\n\n    def _get_relation(self, reltype, id):\n        \"\"\"Get a specific core by in in relation.\"\"\"\n        rel_pkey = self._relations[reltype]._pkey_name\n        result = self._relations[reltype].find(self.user,\n                                               self,\n                                               {rel_pkey: id})\n        return result['data'][0] if result and result['data'] else None\n\n    def _add_relation(self, reltype, param):\n        \"\"\"Add a new core to given relation.\"\"\"\n        param.validate()\n        if hasattr(param, 'is_primary') and param.is_primary:\n            existing = self._expand_relation(reltype)\n            for obj in existing:\n                if obj.is_primary:\n                    obj.is_primary = False\n                    obj.save()\n                    # XXX don't forget to update index\n\n        # Transform param into core object\n        # XXX find a better method ?\n        attrs = {k: v for k, v in param.to_primitive().iteritems()\n                 if v is not None}\n        new_obj = self._relations[reltype].create(self.user,\n                                                  self,\n                                                  **attrs)\n        rel_list = getattr(self, reltype)\n        rel_list.append(new_obj.get_id())\n        self.save()\n        if self._index_class:\n            self._add_relation_index(reltype, attrs)\n        if hasattr(self, '_lookup_objects') and \\\n           reltype in self._lookup_objects:\n            lookupkls = self._lookup_class\n            look = lookupkls.create(user_id=self.user.user_id,\n                                    value=new_obj.get_id(),\n                                    contact_id=self.contact_id,\n                                    type=reltype,\n                                    lookup_id=new_obj.get_id())\n            log.debug('Created lookup object for relation %s:%r' %\n                      (reltype, look))\n\n        return new_obj\n\n    def _delete_relation(self, reltype, id):\n        \"\"\"Delete core from relation.\"\"\"\n        rel_list = getattr(self, reltype)\n        if id in rel_list:\n            rel_list.remove(id)\n        self.save()\n        related = self._get_relation(reltype, id)\n        if self._index_class and related:\n            pkey = related._pkey_name\n            self._delete_relation_index(reltype, pkey, id)\n        if related:\n            related.model.delete()\n        else:\n            raise NotFound\n        if hasattr(self, '_lookup_objects') and \\\n           reltype in self._lookup_objects:\n            lookupkls = self._lookup_class\n            lookup = lookupkls.get(self.user, id)\n            if lookup:\n                lookup.delete()\n            else:\n                log.warn('Lookup object not found when deleting relation')\n        return True\n\n    def _add_relation_index(self, reltype, attrs):\n        \"\"\"Add a relation to indexed object.\"\"\"\n        idx = self._index_class.get(self.user_id, self.get_id())\n        nested = getattr(idx, reltype)\n        if not nested:\n            log.warn('Nested index {} not found for {}'.format(reltype, self))\n            return False\n        nested.append(attrs)\n        return True\n\n    def _delete_relation_index(self, reltype, key, id):\n        \"\"\"Delete a relation from an indexed object.\"\"\"\n        idx = self._index_class.get(self.user_id, self.get_id())\n        # Look for existing entry\n        found = None\n        nested = getattr(idx, reltype)\n        if not nested:\n            log.warn('Nested index {} not found for {}'.format(reltype, self))\n            return False\n        for child in nested:\n            if getattr(child, key) == id:\n                found = child\n        if not found:\n            log.warn('Relation %s %s with id %s not found in index' %\n                     (reltype, key, id))\n        nested.remove(found)\n        return True\n\n\nclass MixinCoreNested(object):\n\n    \"\"\"Mixin class for core nested objects management.\"\"\"\n\n    def _add_nested(self, column, nested):\n        \"\"\"Add a nested object to a list.\"\"\"\n        nested.validate()\n        kls = self._nested.get(column)\n        if not kls:\n            raise Exception('No nested class for {}'.format(column))\n        column = getattr(self.model, column)\n        # Ensure unicity\n        if hasattr(kls, 'uniq_name'):\n            for value in column:\n                uniq = getattr(value, kls.uniq_name)\n                if uniq == getattr(nested, kls.uniq_name):\n                    raise Exception('Unicity conflict for {}'.format(uniq))\n        if hasattr(nested, 'is_primary') and nested.is_primary:\n            for old_primary in column:\n                column.is_primary = False\n        value = nested.to_primitive()\n        pkey = getattr(kls, '_pkey')\n        value[pkey] = uuid.uuid4()\n        log.debug('Will insert nested {} : {}'.format(column, value))\n        column.append(kls(**value))\n        return value\n\n    def _delete_nested(self, column, nested_id):\n        \"\"\"Delete a nested object with its id from a list.\"\"\"\n        attr = getattr(self, column)\n        log.debug('Will delete {} with id {}'.format(column, nested_id))\n        found = -1\n        for pos in xrange(0, len(attr)):\n            nested = attr[pos]\n            current_id = str(getattr(nested, nested._pkey))\n            if current_id == nested_id:\n                found = pos\n        if found == -1:\n            log.warn('Nested object {}#{} not found for deletion'.\n                     format(column, nested_id))\n            return None\n        return attr.pop(found)\n\n    @classmethod\n    def create_nested(cls, values, kls):\n        \"\"\"Create nested objects in store format.\"\"\"\n        nested = []\n        for param in values:\n            param.validate()\n            attrs = param.to_primitive()\n            if hasattr(kls, '_pkey'):\n                # XXX default value not correctly handled\n                attrs[getattr(kls, '_pkey')] = uuid.uuid4()\n            nested.append(kls(**attrs))\n        return nested\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/core/registry.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliop core registry.\"\"\"\n\n\ncore_registry = {}  # registry for all core classes\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/exception.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen cassandra models related to user.\"\"\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\n\nclass NotFound(Exception):\n    \"\"\"Exception when object is not found in store.\"\"\"\n\n    pass\n\n\nclass CredentialException(Exception):\n    \"\"\"Generic exception during user authentication process.\"\"\"\n\n    pass\n\n\nclass DuplicateObject(Exception):\n    \"\"\"Exception when an existing object already exists.\"\"\"\n\n    pass\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/helpers/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/helpers/connection.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen storage session helpers.\"\"\"\n\nfrom cassandra.cqlengine.connection import setup as setup_cassandra\nfrom elasticsearch import Elasticsearch\nfrom ..config import Configuration\n\n\ndef connect_storage():\n    \"\"\"Connect to storage engines.\"\"\"\n    try:\n        from cassandra.io.libevreactor import LibevConnection\n        kwargs = {'connection_class': LibevConnection}\n    except ImportError:\n        kwargs = {}\n    hosts = Configuration('global').get('cassandra.hosts')\n    keyspace = Configuration('global').get('cassandra.keyspace')\n    consistency = Configuration('global').get('cassandra.consistency_level')\n    protocol = Configuration('global').get('cassandra.protocol_version')\n    setup_cassandra(hosts, keyspace, consistency,\n                    lazy_connect=True,\n                    protocol_version=protocol,\n                    **kwargs)\n\n\ndef get_index_connection():\n    \"\"\"Return a connection to index store.\"\"\"\n    url = Configuration('global').get('elasticsearch.url')\n    return Elasticsearch(url)\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/helpers/json.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen helpers related to json data.\"\"\"\n\n# Strange situation under python 2.x where json do not have JSONENcoder\ntry:\n    import simplejson as json\nexcept ImportError:\n    import json\n\nimport datetime\nfrom uuid import UUID\nfrom decimal import Decimal\n\n\nclass JSONEncoder(json.JSONEncoder):\n    \"\"\"Specific json encoder to deal with some specific types.\"\"\"\n\n    _datetypes = (datetime.date, datetime.datetime)\n\n    def default(self, obj):\n        \"\"\"Convert object to JSON encodable type.\"\"\"\n        if isinstance(obj, Decimal):\n            return float(obj)\n        if isinstance(obj, self._datetypes):\n            return RFC3339Milli(obj)\n        if isinstance(obj, UUID):\n            return str(obj)\n        return super(JSONEncoder, self).default(obj)\n\n\ndef to_json(data):\n    \"\"\"Helper to dump using correct encoder.\"\"\"\n    return json.dumps(data, cls=JSONEncoder)\n\n\ndef RFC3339Milli(value):\n    return u\"%s.%sZ\" % (\n        value.strftime(\"%Y-%m-%dT%H:%M:%S\"), value.microsecond / 1000)\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/parameters.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen parameters classes.\n\nAll input and ouput parameters of core object methods must\nuse a class that inherit from of of these.\n\"\"\"\nimport logging\n\nfrom .core import BaseCore\n\nlog = logging.getLogger(__name__)\n\n\nclass BaseReturnObject(object):\n\n    \"\"\"Base return object.\"\"\"\n\n    _return_class = None\n    _aliases = {}\n\n\nclass ReturnCoreObject(BaseReturnObject):\n\n    \"\"\"Return object for core instance.\n\n    to define a return object for a core suitable for protocol\n    output, inherit from this class defining _core_class,\n    _return_class attribute.\n\n    parameter = ReturnObject.build(core)\n    parameter.serialize()\n    \"\"\"\n\n    _core_class = None\n\n    @classmethod\n    def _build_sub_core(cls, core):\n        \"\"\"Build return for a core object attached to another.\"\"\"\n        attr = {}\n        for col in core._model_class._columns.keys():\n            value = getattr(core, col)\n            if isinstance(value, (list, tuple)):\n                new_value = []\n                for val in value:\n                    if hasattr(val, 'to_dict'):\n                        new_value.append(val.to_dict())\n                    else:\n                        new_value.append(val)\n                value = new_value\n            elif hasattr(value, 'to_dict'):\n                value = value.to_dict()\n            attr.update({col: value})\n        return attr\n\n    @classmethod\n    def build(cls, core):\n        \"\"\"Main method to build a return object from a core.\"\"\"\n        kls = cls._return_class\n        obj = kls()\n        for k, v in obj._data.iteritems():\n            if cls._aliases.get(k):\n                core_key = cls._aliases[k]\n            else:\n                core_key = k\n            attr = getattr(core, core_key)\n            if hasattr(cls._core_class, '_relations') \\\n               and k in cls._core_class._relations:\n                # XXX bad design using data key\n                if 'data' in attr:\n                    attr = attr['data']\n                attr = [x.to_dict() for x in attr]\n            if isinstance(attr, BaseCore):\n                attr = cls._build_sub_core(attr)\n            elif isinstance(attr, (list, tuple)):\n                new_attr = []\n                for val in attr:\n                    if hasattr(val, 'to_dict'):\n                        value = val.to_dict()\n                    else:\n                        value = val\n                    new_attr.append(value)\n                attr = new_attr\n            if attr is None and v is not None:\n                setattr(obj, k, v)\n            else:\n                if hasattr(attr, 'to_dict'):\n                    setattr(obj, k, attr.to_dict())\n                else:\n                    setattr(obj, k, attr)\n        obj.validate()\n        return obj\n\n\nclass ReturnIndexObject(BaseReturnObject):\n\n    \"\"\"Return object from index entry.\n\n    Inherit from this class for an index class\n    and define which parameter return class to use for build\n\n    parameter = ReturnIndexObject(index_entry)\n    parameter.serialize()\n    \"\"\"\n\n    _index_class = None\n    _default = {}\n\n    @classmethod\n    def build(cls, entry):\n        \"\"\"Main method to build a return object from an index entry.\"\"\"\n        kls = cls._return_class\n        obj = kls()\n        for k, v in obj._data.iteritems():\n            if cls._aliases.get(k):\n                idx_key = cls._aliases[k]\n            else:\n                idx_key = k\n            attr = entry.get(idx_key, cls._default.get(idx_key))\n            setattr(obj, k, attr)\n        obj.validate()\n        return obj\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/returns.py",
    "content": ""
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/store/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\nfrom .model import BaseModel, BaseIndexDocument, BaseUserType\n\n__all__ = [\n    'BaseModel', 'BaseIndexDocument', 'BaseUserType',\n]\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/store/mixin.py",
    "content": "\"\"\"Caliopen mixins related to store.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\nimport uuid\n\nfrom cassandra.cqlengine import columns\n\nlog = logging.getLogger(__name__)\n\n\ndef get_user_index(user_id):\n    from caliopen_main.user.store import User\n    log.warning('We should not be there to get user.shard_id')\n    user = User.get(user_id=user_id)\n    return user.shard_id\n\n\nclass IndexedModelMixin(object):\n    \"\"\"Mixin to transform model into indexed documents.\"\"\"\n\n    def __process_udt(self, column, idx):\n        \"\"\"Process a cassandra UDT column to translate into nested index.\"\"\"\n\n        def map_udt_attributes(item):\n            ret = {}\n            for col_name, col_value in item.items():\n                if col_value is not None:\n                    if isinstance(col_value, (columns.UUID, uuid.UUID)):\n                        value = str(col_value)\n                    else:\n                        value = col_value\n                    ret[col_name] = value\n            return ret\n\n        attr_udt = getattr(self, column.column_name)\n        if isinstance(attr_udt, list):\n            udts = []\n            for item in attr_udt:\n                udts.append(map_udt_attributes(item))\n            setattr(idx, column.column_name, udts)\n        else:\n            if attr_udt:\n                setattr(idx, column.column_name, map_udt_attributes(attr_udt))\n\n    def _process_column(self, column, idx):\n        \"\"\"Process a core column and translate into index document.\"\"\"\n        col_name = column.column_name\n        col_value = getattr(self, col_name)\n        try:\n            getattr(idx, col_name)\n        except AttributeError:\n            log.debug('No such column in index mapping {}'.\n                      format(column.column_name))\n            return\n        if isinstance(column, columns.List):\n            is_udt = isinstance(column.sub_types[0], columns.UserDefinedType)\n            if is_udt:\n                self.__process_udt(column, idx)\n            else:\n                setattr(idx, col_name, col_value)\n        elif isinstance(column, columns.UserDefinedType):\n            self.__process_udt(column, idx)\n        else:\n            setattr(idx, col_name, col_value)\n\n    def create_index(self, **extras):\n        \"\"\"Translate a model object into an indexed document.\"\"\"\n        if not self._index_class:\n            return False\n        idx = self._index_class()\n        # XXX TODO TEMPORARY FIX\n        # Design on core object did not follow correctly user.shard_id logic\n\n        idx.meta.index = get_user_index(self.user_id)\n\n        for name, desc in self._columns.items():\n            if desc.is_primary_key:\n                if name != 'user_id':\n                    idx.meta.id = getattr(self, name)\n                else:\n                    self._process_column(desc, idx)\n            else:\n                self._process_column(desc, idx)\n        for k, v in extras.items():\n            setattr(idx, k, v)\n        idx.save(using=idx.client())\n        return True\n\n    def update_index(self, object_id, changed_columns):\n        \"\"\"Update an existing index with a list of new values\"\"\"\n\n        idx = self._index_class()\n        idx.meta.index = get_user_index(self.user_id)\n        idx.meta.id = object_id\n\n        update_doc = {}\n        for name in changed_columns:\n            if name == 'user_id':\n                raise Exception('Can not change user_id column')\n            column = self._columns[name]\n            self._process_column(column, idx)\n            update_doc[name] = getattr(idx, name)\n\n        # serialize index doc keeping empty or None value\n        out = {}\n        for k, v in idx._d_.iteritems():\n            try:\n                f = idx._doc_type.mapping[k]\n                if f._coerce:\n                    v = f.serialize(v)\n            except KeyError:\n                pass\n            out[k] = v\n        # XXX This method is no more used, deprecate it smoothly\n        log.warning('Deprecation warning on IndexedModelMixin.update_index')\n        idx.update(using=idx.client(), **out)\n\n    @classmethod\n    def search(cls, user, limit=None, offset=0,\n               min_pi=0, max_pi=0, sort=None, **params):\n        \"\"\"Search in index using a dict parameter.\"\"\"\n        search = cls._index_class.search(using=cls._index_class.client(),\n                                         index=user.shard_id)\n        search.filter('term', user_id=user.user_id)\n        for k, v in params.items():\n            term = {k: v}\n            search = search.filter('match', **term)\n        if limit:\n            search = search[offset:offset + limit]\n        else:\n            log.warn('Pagination not set for search query,'\n                     ' using default storage one')\n        if sort:\n            search = search.sort(sort)\n        log.debug('Search is {}'.format(search.to_dict()))\n        resp = search.execute()\n        log.debug('Search result {}'.format(resp))\n        # XXX This method is no more used, deprecate it smoothly\n        log.warning('Deprecation warning on IndexedModelMixin.update_index')\n        return resp\n"
  },
  {
    "path": "src/backend/main/py.storage/caliopen_storage/store/model.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"Caliopen cassandra base model classes.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\nimport logging\n\nfrom cassandra.cqlengine.models import Model\nfrom cassandra.cqlengine.query import DoesNotExist\nfrom cassandra.cqlengine.usertype import UserType\n\nfrom elasticsearch import Elasticsearch\nfrom elasticsearch_dsl import DocType\n\nfrom ..config import Configuration\nfrom ..exception import NotFound\n\nlog = logging.getLogger(__name__)\n\n\nclass BaseModel(Model):\n    \"\"\"Cassandra base model.\"\"\"\n\n    __abstract__ = True\n    __keyspace__ = Configuration('global').get('cassandra.keyspace')\n    _index_class = None\n\n    @classmethod\n    def create(cls, **kwargs):\n        \"\"\"Create a new model record.\"\"\"\n        attrs = {key: val for key, val in kwargs.items()\n                 if key in cls._columns}\n        obj = super(BaseModel, cls).create(**attrs)\n        if obj._index_class:\n            extras = kwargs.get('_indexed_extra', {})\n            obj.create_index(**extras)\n        return obj\n\n    @classmethod\n    def get(cls, **kwargs):\n        \"\"\"Raise our exception when model not found.\"\"\"\n        try:\n            return super(BaseModel, cls).get(**kwargs)\n        except DoesNotExist as exc:\n            raise NotFound(exc)\n        except:\n            raise\n\n    @classmethod\n    def filter(cls, **kwargs):\n        \"\"\"Filter storable objects.\"\"\"\n        return cls.objects.filter(**kwargs)\n\n    @classmethod\n    def all(cls):\n        \"\"\"Return all storable objects.\"\"\"\n        return cls.objects.all()\n\n\nclass BaseIndexDocument(DocType):\n    \"\"\"Base class for indexed objects.\"\"\"\n\n    doc_type = None\n    __url__ = Configuration('global').get('elasticsearch.url')\n\n    @classmethod\n    def client(cls):\n        \"\"\"Return an elasticsearch client.\"\"\"\n        return Elasticsearch(cls.__url__)\n\n    @classmethod\n    def create_mapping(cls, index_name):\n        \"\"\"Create and save elasticsearch mapping for the cls.doc_type.\"\"\"\n\n        if hasattr(cls, 'build_mapping'):\n            log.info('Create index {} mapping for doc_type {}'.\n                     format(index_name, cls.doc_type))\n            try:\n                cls.build_mapping().save(using=cls.client(), index=index_name)\n            except Exception as exc:\n                log.error(\"failed to put mapping for {} : {}\".\n                          format(index_name, exc))\n                raise exc\n\n\nclass BaseUserType(UserType):\n    \"\"\"Base class for UserDefined Type in store layer.\"\"\"\n\n    def to_dict(self):\n        return {k: v for k, v in self.items()}\n"
  },
  {
    "path": "src/backend/main/py.storage/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliopen\nwith-coverage = 1\ncover-erase = 1\n"
  },
  {
    "path": "src/backend/main/py.storage/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_storage\"\n\nwith open(os.path.join(*([here] + name.split('.') + ['__init__.py']))) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S).match(v_file.read()).group(1)\n\nrequires = [\n    'setuptools',\n    'cryptography==2.4',\n    'six == 1.10.0',  # https://github.com/SecurityInnovation/PGPy/issues/217\n    'bcrypt',\n    'PyYAML',\n    'elasticsearch-dsl>=5.0.0,<6.0.0',\n    'cassandra-driver==3.4.1',\n    'schematics',\n    'simplejson',\n    'jsonschema == 2.6.0',\n    ]\n\nextras_require = {\n    'dev': [],\n    'test': ['nose', 'coverage', 'freezegun', 'docker-py'],\n}\n\n\nsetup(name=name,\n      version=version,\n      description='Caliopen base package for storage routines.',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\"Programming Language :: Python\", ],\n      author='Caliopen contributors',\n      author_email='contact@caliopen.org',\n      url='https://caliopen.org',\n      license='AGPLv3',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      extras_require=extras_require,\n      install_requires=requires,\n      tests_require=requires,\n      )\n"
  },
  {
    "path": "src/backend/protocols/go.imap/cmd/imapctl/cli_cmds/addremote.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tid           remoteId\n\taddRemoteCmd = &cobra.Command{\n\t\tUse:   \"addremote\",\n\t\tShort: \"add an IMAP remote identity for specified user\",\n\t\tRun:   addRemote,\n\t}\n)\n\ntype remoteId struct {\n\tDisplayName  string\n\tLogin        string\n\tMailbox      string\n\tPassword     string\n\tPollInterval string\n\tRemoteId     string\n\tServer       string\n\tUserId       UUID\n\tUserName     string\n}\n\nfunc init() {\n\t//mandatory\n\taddRemoteCmd.Flags().StringVarP(&id.UserName, \"username\", \"u\", \"\", \"user name account in which mails will be imported (required)\")\n\taddRemoteCmd.Flags().StringVarP(&id.Server, \"server\", \"s\", \"\", \"remote hostname[:port] IMAP server address (required)\")\n\taddRemoteCmd.Flags().StringVarP(&id.Login, \"login\", \"l\", \"\", \"IMAP login credential (required)\")\n\t//optional\n\taddRemoteCmd.Flags().StringVarP(&id.Password, \"pass\", \"p\", \"\", \"IMAP password credential\")\n\taddRemoteCmd.Flags().StringVarP(&id.Mailbox, \"mailbox\", \"m\", \"INBOX\", \"IMAP mailbox name to fetch mail from (case sensitive, default to 'INBOX'\")\n\taddRemoteCmd.Flags().StringVarP(&id.DisplayName, \"display\", \"d\", \"\", \"display name for remote identity (default to login)\")\n\taddRemoteCmd.MarkFlagRequired(\"username\")\n\taddRemoteCmd.MarkFlagRequired(\"server\")\n\taddRemoteCmd.MarkFlagRequired(\"login\")\n\tRootCmd.AddCommand(addRemoteCmd)\n}\n\nfunc addRemote(cmd *cobra.Command, args []string) {\n\tvar is backends.IdentityStorage\n\tvar us backends.UserNameStorage\n\tvar cb *store.CassandraBackend\n\tvar rId UserIdentity\n\tvar err error\n\tswitch cmdConfig.StoreName {\n\tcase \"cassandra\":\n\t\tc := store.CassandraConfig{\n\t\t\tHosts:       cmdConfig.StoreConfig.Hosts,\n\t\t\tKeyspace:    cmdConfig.StoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(cmdConfig.StoreConfig.Consistency),\n\t\t\tSizeLimit:   cmdConfig.StoreConfig.SizeLimit,\n\t\t}\n\n\t\tcb, err = store.InitializeCassandraBackend(c)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatalf(\"[addRemote] initalization of %s backend failed\", cmdConfig.StoreName)\n\t\t}\n\n\t}\n\tis = backends.IdentityStorage(cb)\n\tus = backends.UserNameStorage(cb)\n\n\tuser, e := us.UserByUsername(id.UserName)\n\tif e != nil {\n\t\tlog.WithError(e).Fatalf(\"[addRemote] failed to retrieve user name <%s>\", id.UserName)\n\t}\n\tid.UserId = user.UserId\n\tif id.DisplayName == \"\" {\n\t\tid.DisplayName = id.Login\n\t}\n\trId = UserIdentity{\n\t\tDisplayName: id.DisplayName,\n\t\tIdentifier:  id.Login,\n\t\tStatus:      \"active\",\n\t\tType:        RemoteIdentity,\n\t\tProtocol:    EmailProtocol,\n\t\tUserId:      id.UserId,\n\t}\n\trId.Id.UnmarshalBinary(uuid.NewV4().Bytes())\n\trId.SetDefaults()\n\trId.Infos[\"inserver\"] = id.Server\n\trId.Credentials = &Credentials{\n\t\t\"inpassword\": id.Password,\n\t\t\"inusername\": id.Login,\n\t}\n\n\terr = is.CreateUserIdentity(&rId)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[addRemote] storage failed to store remote identity\")\n\t} else {\n\t\tlog.Infof(\"OK, new remote identity added with id %s ! Bye.\", rId.Id.String())\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/cmd/imapctl/cli_cmds/fullfetch.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\t\"encoding/json\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\t\"github.com/Sirupsen/logrus\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tfullFetchCmd = &cobra.Command{\n\t\tUse:   \"fullfetch\",\n\t\tShort: \"blindly imports all mails from a remote IMAP mailbox into user's account.\",\n\t\tRun:   fullFetch,\n\t}\n)\n\nfunc init() {\n\tfullFetchCmd.Flags().StringVarP(&id.UserName, \"username\", \"u\", \"\", \"user_name account in which mails will be imported (required)\")\n\tfullFetchCmd.Flags().StringVarP(&id.Server, \"server\", \"s\", \"\", \"remote hostname[:port] IMAP server address (required)\")\n\tfullFetchCmd.Flags().StringVarP(&id.Mailbox, \"mailbox\", \"m\", \"INBOX\", \"IMAP mailbox name to fetch mail from, case sensitive\")\n\tfullFetchCmd.Flags().StringVarP(&id.Login, \"login\", \"l\", \"\", \"IMAP login credential (required)\")\n\tfullFetchCmd.Flags().StringVarP(&id.Password, \"pass\", \"p\", \"\", \"IMAP password credential (required)\")\n\tfullFetchCmd.MarkFlagRequired(\"username\")\n\tfullFetchCmd.MarkFlagRequired(\"server\")\n\tfullFetchCmd.MarkFlagRequired(\"login\")\n\tfullFetchCmd.MarkFlagRequired(\"pass\")\n\tRootCmd.AddCommand(fullFetchCmd)\n}\n\n// fullFetch\nfunc fullFetch(cmd *cobra.Command, args []string) {\n\tvar us backends.UserNameStorage\n\tvar cb *store.CassandraBackend\n\tvar err error\n\tswitch cmdConfig.StoreName {\n\tcase \"cassandra\":\n\t\tc := store.CassandraConfig{\n\t\t\tHosts:       cmdConfig.StoreConfig.Hosts,\n\t\t\tKeyspace:    cmdConfig.StoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(cmdConfig.StoreConfig.Consistency),\n\t\t\tSizeLimit:   cmdConfig.StoreConfig.SizeLimit,\n\t\t}\n\n\t\tcb, err = store.InitializeCassandraBackend(c)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatalf(\"[addRemote] initalization of %s backend failed\", cmdConfig.StoreName)\n\t\t}\n\n\t}\n\tus = backends.UserNameStorage(cb)\n\n\tuser, e := us.UserByUsername(id.UserName)\n\tif e != nil {\n\t\tlog.WithError(e).Fatalf(\"[addRemote] failed to retrieve user name <%s>\", id.UserName)\n\t}\n\tid.UserId = user.UserId\n\tnc, err := nats.Connect(cmdConfig.NatsUrl)\n\tif err != nil {\n\t\tlogrus.WithError(err).Fatal(\"nats connect failed\")\n\t}\n\tdefer nc.Close()\n\n\tmsg, err := json.Marshal(IMAPorder{\n\t\tOrder:    \"fullfetch\",\n\t\tUserId:   id.UserId.String(),\n\t\tServer:   id.Server,\n\t\tMailbox:  id.Mailbox,\n\t\tLogin:    id.Login,\n\t\tPassword: id.Password,\n\t})\n\tif err != nil {\n\t\tlogrus.WithError(err).Fatal(\"unable to marshal natsOrder\")\n\t}\n\n\tnc.Publish(cmdConfig.NatsTopicSender, msg)\n\tnc.Flush()\n\n\tif err := nc.LastError(); err != nil {\n\t\tlogrus.WithError(err).Fatal(\"nats publish failed\")\n\t}\n\n\tlogrus.Infof(\"ordering to fetch all mails from %s for user %s\", id.Login, id.UserId)\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/cmd/imapctl/cli_cmds/root.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\timapWorker \"github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcmdConfig  CmdConfig\n\tconfigFile string\n\tconfigPath string\n\tverbose    bool\n\tversion    bool\n\tRootCmd    = &cobra.Command{\n\t\tUse:   \"imapctl\",\n\t\tShort: \"cli for IMAP operations\",\n\t\tLong:  \"IMAPctl is a cli to control IMAP related operations.\",\n\t\tRun:   nil,\n\t}\n)\n\nconst __version__ = \"0.1.0\"\n\ntype CmdConfig imapWorker.WorkerConfig\n\nfunc init() {\n\tcobra.OnInitialize(initConfig)\n\tRootCmd.PersistentFlags().BoolVarP(&verbose, \"verbose\", \"v\", false,\n\t\t\"print out more debug information\")\n\tRootCmd.PersistentFlags().BoolVarP(&version, \"version\", \"V\", false,\n\t\t\"print out the version of this program\")\n\tRootCmd.PersistentFlags().StringVarP(&configFile, \"config\", \"c\",\n\t\t\"imapworker\", \"Name of the configuration file, without extension. (YAML, TOML, JSON… allowed)\")\n\tRootCmd.PersistentFlags().StringVarP(&configPath, \"configpath\", \"\",\n\t\t\"../../../../configs/\", \"Main config file path.\")\n\tRootCmd.Run = func(cmd *cobra.Command, args []string) {\n\t\tif version {\n\t\t\tlog.Infof(\"IMAPctl version %s\", __version__)\n\t\t}\n\t\tif len(args) == 0 {\n\t\t\tcmd.Help()\n\t\t}\n\t}\n\tRootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {\n\t\tif verbose {\n\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t} else {\n\t\t\tlog.SetLevel(log.InfoLevel)\n\t\t}\n\t}\n\tRootCmd.AddCommand(versionCmd)\n\n}\n\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print the version number of IMAPctl\",\n\tLong:  `All software has versions. This is IMAPctl's`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tlog.Infof(\"IMAPctl version %s\", __version__)\n\t},\n}\n\nfunc initConfig() {\n\t// load in the main config. Reading from YAML, TOML, JSON, HCL and Java properties config files\n\tv := viper.New()\n\tv.SetConfigName(configFile)                           // name of config file (without extension)\n\tv.AddConfigPath(configPath)                           // path to look for the config file in\n\tv.AddConfigPath(\"$CALIOPENROOT/src/backend/configs/\") // call multiple times to add many search paths\n\tv.AddConfigPath(\".\")                                  // optionally look for config in the working directory\n\n\terr := v.ReadInConfig() // Find and read the config file*/\n\tif err != nil {\n\t\tlog.WithError(err).Fatalf(\"Could not read main config file <%s>.\", configFile)\n\t}\n\terr = v.Unmarshal(&cmdConfig)\n\tif err != nil {\n\t\tlog.WithError(err).Fatalf(\"Could not parse config file: <%s>\", configFile)\n\t}\n\n\tcmdConfig.LDAConfig.AppVersion = __version__\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/cmd/imapctl/cli_cmds/syncremote.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\t\"encoding/json\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\t\"github.com/Sirupsen/logrus\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tsyncRemoteCmd = &cobra.Command{\n\t\tUse:   \"syncremote\",\n\t\tShort: \"sync remote mailbox for provided remote identity\",\n\t\tRun:   syncRemote,\n\t}\n)\n\nfunc init() {\n\tsyncRemoteCmd.Flags().StringVarP(&id.UserName, \"username\", \"u\", \"\", \"Caliopen username account to which mails will be delivered (required)\")\n\tsyncRemoteCmd.Flags().StringVarP(&id.RemoteId, \"remoteid\", \"r\", \"\", \"remote identity's uuid (required)\")\n\tsyncRemoteCmd.Flags().StringVarP(&id.Password, \"pass\", \"p\", \"\", \"IMAP password (if not stored in db)\")\n\tsyncRemoteCmd.MarkFlagRequired(\"userid\")\n\tsyncRemoteCmd.MarkFlagRequired(\"remoteid\")\n\tRootCmd.AddCommand(syncRemoteCmd)\n}\n\nfunc syncRemote(cmd *cobra.Command, args []string) {\n\n\tvar us backends.UserNameStorage\n\tvar cb *store.CassandraBackend\n\tvar err error\n\tswitch cmdConfig.StoreName {\n\tcase \"cassandra\":\n\t\tc := store.CassandraConfig{\n\t\t\tHosts:       cmdConfig.StoreConfig.Hosts,\n\t\t\tKeyspace:    cmdConfig.StoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(cmdConfig.StoreConfig.Consistency),\n\t\t\tSizeLimit:   cmdConfig.StoreConfig.SizeLimit,\n\t\t}\n\n\t\tcb, err = store.InitializeCassandraBackend(c)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatalf(\"[addRemote] initalization of %s backend failed\", cmdConfig.StoreName)\n\t\t}\n\n\t}\n\tus = backends.UserNameStorage(cb)\n\n\tuser, e := us.UserByUsername(id.UserName)\n\tif e != nil {\n\t\tlog.WithError(e).Fatalf(\"[addRemote] failed to retrieve user name <%s>\", id.UserName)\n\t}\n\n\tnc, err := nats.Connect(cmdConfig.NatsUrl)\n\tif err != nil {\n\t\tlogrus.WithError(err).Fatal(\"nats connect failed\")\n\t}\n\tdefer nc.Close()\n\n\tmsg, err := json.Marshal(IMAPorder{\n\t\tLogin:      id.Login,\n\t\tMailbox:    id.Mailbox,\n\t\tOrder:      \"sync\",\n\t\tPassword:   id.Password,\n\t\tIdentityId: id.RemoteId,\n\t\tServer:     id.Server,\n\t\tUserId:     user.UserId.String(),\n\t})\n\tif err != nil {\n\t\tlogrus.WithError(err).Fatal(\"unable to marshal natsOrder\")\n\t}\n\n\tnc.Publish(cmdConfig.NatsTopicSender, msg)\n\tnc.Flush()\n\n\tif err := nc.LastError(); err != nil {\n\t\tlogrus.WithError(err).Fatal(\"nats publish failed\")\n\t}\n\n\tlogrus.Infof(\"ordering to sync mailbox from %s for user %s\", id.DisplayName, user.UserId.String())\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/cmd/imapctl/main.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap/cmd/imapctl/cli_cmds\"\n\t\"os\"\n)\n\nfunc main() {\n\tif err := cmd.RootCmd.Execute(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(-1)\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/cmd/imapworker/cli_cmds/root.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tverbose bool\n\tversion bool\n\tRootCmd = &cobra.Command{\n\t\tUse:   \"imapworker\",\n\t\tShort: \"IMAP worker daemon\",\n\t\tLong:  `IMAP worker is a daemon that subscribes to relevant messaging system queues and executes operations on remote IMAP servers`,\n\t\tRun:   nil,\n\t}\n)\n\nconst __version__ = \"0.23.0\"\n\nfunc init() {\n\tcobra.OnInitialize()\n\tRootCmd.PersistentFlags().BoolVarP(&verbose, \"verbose\", \"v\", false,\n\t\t\"print out more debug information\")\n\tRootCmd.PersistentFlags().BoolVarP(&version, \"version\", \"V\", false,\n\t\t\"print out the version of this program\")\n\tRootCmd.Run = func(cmd *cobra.Command, args []string) {\n\t\tif version {\n\t\t\tlog.Infof(\"IMAP worker version %s\", __version__)\n\t\t}\n\t\tif len(args) == 0 {\n\t\t\tcmd.Help()\n\t\t}\n\t}\n\tRootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {\n\t\tif verbose {\n\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t} else {\n\t\t\tlog.SetLevel(log.InfoLevel)\n\t\t}\n\t}\n\tRootCmd.AddCommand(versionCmd)\n}\n\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print the version number of IMAP worker\",\n\tLong:  `All software has versions. This is IMAP worker's`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tlog.Infof(\"IMAP worker version %s\", __version__)\n\t},\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/cmd/imapworker/cli_cmds/start.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\timapWorker \"github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n)\n\nvar (\n\tconfigPath    string\n\tconfigFile    string\n\tpidFile       string\n\tsignalChannel chan os.Signal // for trapping SIG_HUP\n\tcmdConfig     CmdConfig\n\timapWorkers   []*imapWorker.Worker\n\n\tstartCmd = &cobra.Command{\n\t\tUse:   \"start\",\n\t\tShort: \"Starts IMAP worker(s)\",\n\t\tRun:   start,\n\t}\n)\n\nconst (\n\tshutdownTimeout = 3 // minutes to wait before forcing shutdown\n)\n\nfunc init() {\n\tstartCmd.PersistentFlags().StringVarP(&configFile, \"config\", \"c\",\n\t\t\"imapworker\", \"Name of the configuration file, without extension. (YAML, TOML, JSON… allowed)\")\n\tstartCmd.PersistentFlags().StringVarP(&configPath, \"configpath\", \"\",\n\t\t\"../../../../configs/\", \"Main config file path.\")\n\tstartCmd.PersistentFlags().StringVarP(&pidFile, \"pid-file\", \"p\",\n\t\t\"/var/run/caliopen_imap_worker.pid\", \"Path to the pid file\")\n\n\tRootCmd.AddCommand(startCmd)\n\tsignalChannel = make(chan os.Signal, 1)\n\tcmdConfig = CmdConfig{}\n}\n\nfunc sigHandler(workers []*imapWorker.Worker) {\n\t// handle SIGHUP for reloading the configuration while running\n\tsignal.Notify(signalChannel, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGKILL)\n\n\tfor sig := range signalChannel {\n\n\t\tif sig == syscall.SIGHUP {\n\t\t\terr := readConfig(&cmdConfig)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Error(\"Error while ReadConfig (reload)\")\n\t\t\t} else {\n\t\t\t\tlog.Info(\"Configuration is reloaded\")\n\t\t\t}\n\t\t\t// TODO: reinitialize\n\t\t} else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT || sig == syscall.SIGKILL {\n\t\t\tlog.Infof(\"Shutdown signal caught. Gracefully halting %d workers within 3 minutes timeframe…\", len(workers))\n\t\t\twg := new(sync.WaitGroup)\n\t\t\twg.Add(len(workers))\n\t\t\tfor i := range workers {\n\t\t\t\tworkers[i].HaltGroup = wg\n\t\t\t}\n\t\t\t// timeout mechanism to avoid infinite wait\n\t\t\tc := make(chan struct{})\n\t\t\tgo func() {\n\t\t\t\tdefer close(c)\n\t\t\t\twg.Wait()\n\t\t\t}()\n\t\t\tselect {\n\t\t\tcase <-c:\n\t\t\t\tlog.Info(\"Shutdown completed, exiting\")\n\t\t\t\tos.Exit(0)\n\t\t\tcase <-time.After(shutdownTimeout * time.Minute):\n\t\t\t\tlog.Warn(\"Shutdown timeout, force exiting\")\n\t\t\t\tos.Exit(0)\n\t\t\t}\n\t\t} else {\n\t\t\tos.Exit(0)\n\t\t}\n\t}\n}\n\nfunc start(cmd *cobra.Command, args []string) {\n\n\terr := readConfig(&cmdConfig)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"Error while reading config\")\n\t}\n\t// Write out our PID\n\tif len(pidFile) > 0 {\n\t\tif f, err := os.Create(pidFile); err == nil {\n\t\t\tdefer f.Close()\n\t\t\tif _, err := f.WriteString(fmt.Sprintf(\"%d\", os.Getpid())); err == nil {\n\t\t\t\tf.Sync()\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"Error while writing pidFile (%s)\", pidFile)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.WithError(err).Warnf(\"Error while creating pidFile (%s)\", pidFile)\n\t\t}\n\t}\n\n\t// init and start worker(s)\n\tvar i uint8\n\timapWorkers = make([]*imapWorker.Worker, cmdConfig.Workers)\n\tfor i = 0; i < cmdConfig.Workers; i++ {\n\t\tlog.Infof(\"initializing IMAP worker %d\", i)\n\t\timapWorkers[i], err = imapWorker.NewWorker(imapWorker.WorkerConfig(cmdConfig), randomIdentifier())\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatal(\"Failed to init IMAP Worker\")\n\t\t}\n\t\tgo imapWorkers[i].Start()\n\t}\n\tsigHandler(imapWorkers)\n}\n\ntype CmdConfig imapWorker.WorkerConfig\n\n// ReadConfig which should be called at startup, or when a SIG_HUP is caught\nfunc readConfig(config *CmdConfig) error {\n\t// load in the main config. Reading from YAML, TOML, JSON, HCL and Java properties config files\n\tv := viper.New()\n\tv.SetConfigName(configFile)                           // name of config file (without extension)\n\tv.AddConfigPath(configPath)                           // path to look for the config file in\n\tv.AddConfigPath(\"$CALIOPENROOT/src/backend/configs/\") // call multiple times to add many search paths\n\tv.AddConfigPath(\".\")                                  // optionally look for config in the working directory\n\n\terr := v.ReadInConfig() // Find and read the config file*/\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not read main config file <%s>.\", configFile)\n\t\treturn err\n\t}\n\terr = v.Unmarshal(config)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not parse config file: <%s>\", configFile)\n\t\treturn err\n\t}\n\n\tconfig.LDAConfig.AppVersion = __version__\n\treturn nil\n}\n\nfunc randomIdentifier() string {\n\tvar buf [4]byte\n\t_, err := io.ReadFull(rand.Reader, buf[:])\n\tif err != nil {\n\t\treturn \"00000000\"\n\t}\n\treturn fmt.Sprintf(\"%x\", buf[:])\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/cmd/imapworker/main.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap/cmd/imapworker/cli_cmds\"\n\t\"os\"\n)\n\nfunc main() {\n\tif err := cmd.RootCmd.Execute(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(-1)\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/config.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage imap_worker\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"time\"\n)\n\ntype (\n\tWorkerConfig struct {\n\t\tHostname             string      `mapstructure:\"hostname\"`\n\t\tNatsQueue            string      `mapstructure:\"nats_queue\"`\n\t\tNatsTopicPoller      string      `mapstructure:\"nats_topic_poller\"`\n\t\tNatsTopicPollerCache string      `mapstructure:\"nats_topic_poller_cache\"`\n\t\tNatsTopicSender      string      `mapstructure:\"nats_topic_sender\"`\n\t\tNatsUrl              string      `mapstructure:\"nats_url\"`\n\t\tStoreName            string      `mapstructure:\"store_name\"`\n\t\tWorkers              uint8       `mapstructure:\"workers\"`\n\t\tStoreConfig          StoreConfig `mapstructure:\"store_settings\"`\n\t\tLDAConfig            LDAConfig   `mapstructure:\"LDAConfig\"`\n\t}\n)\n\nconst (\n\tsyncingTimeout    = 24               // how many hours to wait before restarting sync op\n\tfailuresThreshold = 72               // how many hours to wait before disabling a faulty remote\n\tpollThrottling    = 10 * time.Second // how long to pause before requesting jobs again to idpoller\n)\n"
  },
  {
    "path": "src/backend/protocols/go.imap/fetcher.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage imap_worker\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/users\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/emersion/go-imap\"\n\t\"github.com/satori/go.uuid\"\n\t\"strconv\"\n\t\"time\"\n)\n\ntype Fetcher struct {\n\tHostname string\n\tStore    backends.LDAStore\n\tLda      *Lda\n}\n\ntype imapBox struct {\n\tlastSeenUid uint32\n\tlastSync    time.Time\n\tname        string\n\tuidValidity uint32\n}\n\nconst (\n\tlastErrorKey      = \"lastFetchError\"\n\tdateFirstErrorKey = \"firstErrorDate\"\n\tdateLastErrorKey  = \"lastErrorDate\"\n\terrorsCountKey    = \"errorsCount\"\n)\n\n// unexported vars to help override funcs in tests\nvar syncRemoteWithLocal = func(f *Fetcher, order IMAPorder) error {\n\treturn f.SyncRemoteWithLocal(order)\n}\nvar fetchRemoteToLocal = func(f *Fetcher, order IMAPorder) error {\n\treturn f.FetchRemoteToLocal(order)\n}\n\n// FetchSyncRemote retrieves remote identity credentials and last sync data,\n// connects to remote IMAP server to fetch new mails,\n// adds X-Fetched-Imap headers before forwarding mails to lda,\n// updates last sync data for identity in db.\nfunc (f *Fetcher) SyncRemoteWithLocal(order IMAPorder) error {\n\n\tlog.Infof(\"[Fetcher] will fetch mails for remote %s\", order.IdentityId)\n\n\t// 1. retrieve infos from db\n\tuserIdentity, err := f.Store.RetrieveUserIdentity(order.UserId, order.IdentityId, true)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"[SyncRemoteWithLocal] failed to retrieve remote identity <%s> : <%s>\", order.UserId, order.IdentityId)\n\t\treturn err\n\t}\n\tif order.Password != \"\" {\n\t\t(*userIdentity.Credentials)[\"inpassword\"] = order.Password\n\t}\n\n\t// 1.2 check if a sync process is running\n\tif syncing, ok := userIdentity.Infos[\"syncing\"]; ok && syncing != \"\" {\n\t\tstartDate, e := time.Parse(time.RFC3339, syncing)\n\t\tif e == nil && time.Since(startDate)/time.Hour < syncingTimeout {\n\t\t\tlog.Infof(\"[SyncRemoteWithLocal] avoiding concurrent sync for <%s>. Syncing in progress since %s\", order.IdentityId, userIdentity.Infos[\"syncing\"])\n\t\t\treturn nil\n\t\t}\n\t}\n\t// save syncing state in db to prevent concurrent sync\n\t(*userIdentity).Infos[\"syncing\"] = time.Now().Format(time.RFC3339)\n\terr = f.Store.UpdateUserIdentity(userIdentity, map[string]interface{}{\n\t\t\"Infos\": userIdentity.Infos,\n\t})\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"[SyncRemoteWithLocal] failed to update remote identity <%s> : <%s>\", order.UserId, order.IdentityId)\n\t\treturn err\n\t}\n\n\t// 2. sync/fetch with remote IMAP\n\tmails := make(chan *Email)\n\tlastsync := time.Time{}\n\tif ls, ok := userIdentity.Infos[\"lastsync\"]; ok && ls != \"\" {\n\t\tlastsync, err = time.Parse(time.RFC3339, userIdentity.Infos[\"lastsync\"])\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[syncMails] failed to parse lastsync string <%s>\", userIdentity.Infos[\"lastsync\"])\n\t\t\tlastsync = time.Time{}\n\t\t}\n\t} else {\n\t\tlastsync = time.Time{}\n\t}\n\t// Sync INBOX (only INBOX for now)\n\t// TODO : sync other mailbox(es) from userIdentity.Infos params or from order\n\tlastseenuid, _ := strconv.Atoi(userIdentity.Infos[\"lastseenuid\"])\n\tuidvalidity, _ := strconv.Atoi(userIdentity.Infos[\"uidvalidity\"])\n\tbox := imapBox{\n\t\tlastSeenUid: uint32(lastseenuid),\n\t\tlastSync:    lastsync,\n\t\tname:        \"INBOX\",\n\t\tuidValidity: uint32(uidvalidity),\n\t}\n\tgo f.syncMails(userIdentity, &box, mails)\n\n\t// 3. forward mails to lda as they come on mails chan\n\terrs := []error{}\n\tsyncTimeout := time.Now()\n\tbatch := Notifications.NewBatch(\"imap_worker\")\n\tfor mail := range mails {\n\t\tif mail.ImapUid <= box.lastSeenUid {\n\t\t\t// do not forward seen message, we already have it\n\t\t\tcontinue\n\t\t}\n\t\terr := f.Lda.deliverMail(mail, order.UserId, userIdentity.Id.String(), batch)\n\t\terrs = append(errs, err)\n\t\tif err == nil {\n\t\t\tbox.lastSeenUid = mail.ImapUid\n\t\t}\n\t\tif time.Since(syncTimeout)/time.Hour > syncingTimeout {\n\t\t\terrs = append(errs, errors.New(\"[Fetcher] sync timeout, aborting for \"+order.IdentityId))\n\t\t\tclose(mails)\n\t\t\tbreak\n\t\t}\n\n\t}\n\n\tfor i, err := range errs {\n\t\t// TODO: improve error handling protocol\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[Fetcher] SyncRemoteWithLocal error delivering mail #%d\", i)\n\t\t}\n\t}\n\n\t// 4. backup sync state in db\n\tvar fields map[string]interface{}\n\tdelete((*userIdentity).Infos, \"syncing\")\n\tuserIdentity.LastCheck = time.Now()\n\tif _, ok := userIdentity.Infos[errorsCountKey]; !ok {\n\t\t// if errorsCountKey IS NOT in Infos then sync succeeded\n\t\t// update infos accordingly\n\t\tuserIdentity.Infos[\"uidvalidity\"] = strconv.Itoa(int(box.uidValidity))\n\t\tuserIdentity.Infos[\"lastsync\"] = userIdentity.LastCheck.Format(time.RFC3339)\n\t\tuserIdentity.Infos[\"lastseenuid\"] = strconv.Itoa(int(box.lastSeenUid))\n\t}\n\tfields = map[string]interface{}{\n\t\t\"LastCheck\": userIdentity.LastCheck,\n\t\t\"Infos\":     userIdentity.Infos,\n\t}\n\terr = f.Store.UpdateUserIdentity(userIdentity, fields)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[syncMails] failed to backup sync state\")\n\t\treturn err\n\t}\n\n\tlog.Infof(\"[Fetcher] all done for %s : %d new mail(s) fetched\", order.IdentityId, len(errs))\n\tbatch.Save(f.Lda.broker.Notifier, \"\", LongLived)\n\treturn nil\n}\n\n// FetchRemoteToLocal blindly fetches all mails from remote without retrieving/saving any state in UserIdentity\nfunc (f *Fetcher) FetchRemoteToLocal(order IMAPorder) error {\n\tuserIdentity := UserIdentity{\n\t\tUserId: UUID(uuid.FromStringOrNil(order.UserId)),\n\t\tInfos: map[string]string{\n\t\t\t\"inserver\": order.Server,\n\t\t},\n\t\tCredentials: &Credentials{\n\t\t\t\"inusername\": order.Login,\n\t\t\t\"inpassword\": order.Password,\n\t\t},\n\t}\n\n\tbox := imapBox{\n\t\tlastSync: time.Time{},\n\t\tname:     order.Mailbox,\n\t}\n\n\t// 2. fetch remote messages\n\tmails := make(chan *Email, 10)\n\tgo f.fetchMails(&userIdentity, &box, mails)\n\t//TODO errors handling\n\n\t// 3. forward mails to lda\n\terrs := make([]error, len(mails))\n\tbatch := Notifications.NewBatch(\"imap_worker\")\n\tfor mail := range mails {\n\t\terr := f.Lda.deliverMail(mail, order.UserId, order.IdentityId, batch)\n\t\terrs = append(errs, err)\n\t}\n\n\treturn nil\n}\n\n// fetchMails fetches all messages from remote mailbox and returns well-formed Emails for lda.\nfunc (f *Fetcher) fetchMails(userIdentity *UserIdentity, box *imapBox, ch chan *Email) (err error) {\n\tif userIdentity.Infos[\"authtype\"] == Oauth2 {\n\t\terr = users.ValidateOauth2Credentials(userIdentity, f, true)\n\t\tif err != nil {\n\t\t\treturn f.handleFetchFailure(userIdentity, WrapCaliopenErr(err, WrongCredentialsErr, \"Oauth2 validation failure\"))\n\t\t}\n\t}\n\ttlsConn, imapClient, provider, err := imapLogin(userIdentity)\n\t// Don't forget to logout and close chan\n\tdefer func() {\n\t\timapClient.Logout()\n\t\tlog.Println(\"Logged out\")\n\t\tclose(ch)\n\t}()\n\tif err != nil {\n\t\treturn f.handleFetchFailure(userIdentity, WrapCaliopenErr(err, WrongCredentialsErr, \"imapLogin failure\"))\n\t} else {\n\t\tdelete((*userIdentity).Infos, lastErrorKey)\n\t\tdelete((*userIdentity).Infos, errorsCountKey)\n\t\tdelete((*userIdentity).Infos, dateFirstErrorKey)\n\t\tdelete((*userIdentity).Infos, dateLastErrorKey)\n\t}\n\n\tnewMessages := make(chan *imap.Message, 10)\n\tgo fetchMailbox(box, imapClient, provider, newMessages) //TODO : errors handling\n\tfor msg := range newMessages {\n\t\tmail, err := MarshalImap(msg, buildXheaders(tlsConn, userIdentity, box, msg, provider))\n\t\tif err != nil {\n\t\t\t//todo\n\t\t\tcontinue\n\t\t}\n\t\tch <- mail\n\t}\n\n\treturn\n}\n\n// fetchSyncMails reads last sync state for remote identity,\n// fetches new messages accordingly, and returns well-formed Emails for lda.\nfunc (f *Fetcher) syncMails(userIdentity *UserIdentity, box *imapBox, ch chan *Email) (err error) {\n\t// Don't forget to close chan before leaving\n\tdefer close(ch)\n\tif userIdentity.Infos[\"authtype\"] == Oauth2 {\n\t\terr = users.ValidateOauth2Credentials(userIdentity, f, true)\n\t\tif err != nil {\n\t\t\treturn f.handleFetchFailure(userIdentity, WrapCaliopenErr(err, WrongCredentialsErr, \"Oauth2 validation failure\"))\n\t\t}\n\t}\n\ttlsConn, imapClient, provider, err := imapLogin(userIdentity)\n\tif err != nil {\n\t\treturn f.handleFetchFailure(userIdentity, WrapCaliopenErr(err, WrongCredentialsErr, \"imapLogin failure\"))\n\t} else {\n\t\tdelete((*userIdentity).Infos, lastErrorKey)\n\t\tdelete((*userIdentity).Infos, errorsCountKey)\n\t\tdelete((*userIdentity).Infos, dateFirstErrorKey)\n\t\tdelete((*userIdentity).Infos, dateLastErrorKey)\n\t}\n\t// Don't forget to logout\n\tdefer func() {\n\t\timapClient.Logout()\n\t\tlog.Println(\"Logged out\")\n\t}()\n\n\tnewMessages := make(chan *imap.Message, 10)\n\t//TODO : manage closing\n\tgo syncMailbox(box, imapClient, provider, newMessages) //TODO : errors handling\n\n\t// read new messages coming from imap chan and write to lda chan with added custom headers\n\tfor msg := range newMessages {\n\t\txHeaders := buildXheaders(tlsConn, userIdentity, box, msg, provider)\n\t\tmail, err := MarshalImap(msg, xHeaders)\n\t\tif err != nil {\n\t\t\t//todo\n\t\t\tcontinue\n\t\t} else {\n\t\t\tch <- mail\n\t\t}\n\t}\n\treturn\n}\n\n// handleFetchFailure logs a warn and save failure log in db.\n// If failures reach failuresThreshold, remote id is disabled and a new notification is emitted.\nfunc (f *Fetcher) handleFetchFailure(userIdentity *UserIdentity, err CaliopenError) error {\n\n\t// ensure errors data fields are present\n\tif _, ok := userIdentity.Infos[lastErrorKey]; !ok {\n\t\t(*userIdentity).Infos[lastErrorKey] = \"\"\n\t}\n\tif _, ok := userIdentity.Infos[dateFirstErrorKey]; !ok {\n\t\t(*userIdentity).Infos[dateFirstErrorKey] = \"\"\n\t}\n\tif _, ok := userIdentity.Infos[dateLastErrorKey]; !ok {\n\t\t(*userIdentity).Infos[dateLastErrorKey] = \"\"\n\t}\n\tif _, ok := userIdentity.Infos[errorsCountKey]; !ok {\n\t\t(*userIdentity).Infos[errorsCountKey] = \"0\"\n\t}\n\n\t// log last error\n\t(*userIdentity).Infos[lastErrorKey] = \"imap connection failed : \" + err.Cause().Error()\n\tlog.WithError(err.Cause()).Warnf(\"imap connection failed for remote identity %s\", userIdentity.Id)\n\t// increment counter\n\tcount, _ := strconv.Atoi(userIdentity.Infos[errorsCountKey])\n\tcount++\n\t(*userIdentity).Infos[errorsCountKey] = strconv.Itoa(count)\n\n\t// update dates\n\tlastDate := time.Now()\n\tvar firstDate time.Time\n\tfirstDate, _ = time.Parse(time.RFC3339, userIdentity.Infos[dateFirstErrorKey])\n\tif firstDate.IsZero() {\n\t\tfirstDate = lastDate\n\t}\n\t(*userIdentity).Infos[dateFirstErrorKey] = firstDate.Format(time.RFC3339)\n\t(*userIdentity).Infos[dateLastErrorKey] = lastDate.Format(time.RFC3339)\n\n\t// check failuresThreshold\n\tif lastDate.Sub(firstDate)/time.Hour > failuresThreshold {\n\t\tf.disableRemoteIdentity(userIdentity)\n\t}\n\n\t// unlock sync state\n\tdelete((*userIdentity).Infos, \"syncing\")\n\n\t// udpate UserIdentity in db\n\treturn f.Store.UpdateUserIdentity(userIdentity, map[string]interface{}{\n\t\t\"Infos\":     userIdentity.Infos,\n\t\t\"LastCheck\": lastDate,\n\t})\n\n}\n\nfunc (f *Fetcher) disableRemoteIdentity(userIdentity *UserIdentity) {\n\t(*userIdentity).Status = \"inactive\"\n\terr := f.Store.UpdateUserIdentity(userIdentity, map[string]interface{}{\n\t\t\"Status\": \"inactive\",\n\t})\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[disableRemoteIdentity] failed to deactivate remote identity %s for user %s\", userIdentity.Id, userIdentity.Id)\n\t}\n\t// send nats message to idpoller to stop polling\n\torder := RemoteIDNatsMessage{\n\t\tIdentityId: userIdentity.Id.String(),\n\t\tOrder:      \"delete\",\n\t\tProtocol:   \"email\",\n\t\tUserId:     userIdentity.UserId.String(),\n\t}\n\tjorder, jerr := json.Marshal(order)\n\tif jerr == nil {\n\t\te := f.Lda.broker.NatsConn.Publish(f.Lda.Config.NatsTopicPollerCache, jorder)\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Warnf(\"[saveErrorState] failed to publish delete order to idpoller\")\n\t\t}\n\t}\n}\n\nfunc (f Fetcher) emitNotification() {\n\t//TODO\n}\n\n/* Oauth2Interfacer implementation */\n\nfunc (f *Fetcher) GetProviders() map[string]Provider {\n\treturn f.Lda.Providers\n}\n\nfunc (f *Fetcher) GetHostname() string {\n\treturn f.Lda.Config.Hostname\n\n}\n\nfunc (f *Fetcher) GetIdentityStore() backends.IdentityStorageUpdater {\n\treturn f.Store\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/imap.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage imap_worker\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/users\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/emersion/go-imap\"\n\t\"github.com/emersion/go-imap/client\"\n\t\"github.com/emersion/go-sasl\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// ImapFetcherHeaders are headers added to emails fetched from remote IMAP\ntype ImapFetcherHeaders map[string]string\n\nconst (\n\t//gmail related\n\tgmail_msgid  = \"X-GM-MSGID\"\n\tgmail_labels = \"X-GM-LABELS\"\n)\n\nvar providers map[string]Provider\n\nfunc init() {\n\t// extensions to seek in IMAP server capabilities to identify remote provider\n\tproviders = map[string]Provider{\n\t\t// as of april, 2018 (see https://developers.google.com/gmail/imap/imap-extensions)\n\t\t\"X-GM-EXT-1\": {\n\t\t\tName:       \"gmail\",\n\t\t\tFetchItems: []imap.FetchItem{gmail_msgid, gmail_labels},\n\t\t},\n\t}\n}\n\nfunc imapLogin(rId *UserIdentity) (tlsConn *tls.Conn, imapClient *client.Client, provider Provider, err error) {\n\tlog.Println(\"Connecting to server...\")\n\t// Dial TLS directly to be able to dump tls connection state\n\t// First try in secure mode, then retry in insecure mode if it fails\n\ttlsConn, err = tls.Dial(\"tcp\", rId.Infos[\"inserver\"], nil)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[fetchMail] imapLogin failed to dial tls in secure mode for identity %s (user %s) server : %s\", rId.Id, rId.UserId, rId.Infos[\"inserver\"])\n\t\tlog.Infof(\"[fetchMail] trying INSECURE mode for identity %s (user %s) server : %s\", rId.Id, rId.UserId, rId.Infos[\"inserver\"])\n\t\ttlsConfig := tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t}\n\t\ttlsConn, err = tls.Dial(\"tcp\", rId.Infos[\"inserver\"], &tlsConfig)\n\t\t// TODO: save as many data as possible into user_identity table if remote cert is faulty\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Errorf(\"[fetchMail] imapLogin failed to dial INSECURE tls for identity %s (user %s) server : %s\", rId.Id, rId.UserId, rId.Infos[\"inserver\"])\n\t\t\treturn\n\t\t}\n\t}\n\n\timapClient, err = client.New(tlsConn)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[fetchMail] imapLogin failed to create IMAP client\")\n\t\treturn\n\t}\n\tlog.Println(\"Connected\")\n\n\t// identify provider' capabilities\n\tcapabilities, _ := imapClient.Capability()\n\tprovider = Provider{Capabilities: capabilities}\n\tfor capability := range capabilities {\n\t\tif p, ok := providers[capability]; ok {\n\t\t\tprovider.Name = p.Name\n\t\t\tprovider.FetchItems = p.FetchItems\n\t\t}\n\t}\n\n\t// choose auth mechanism according to provider capabilities and identity's authtype\n\tauthType, foundAuthType := rId.Infos[\"authtype\"]\n\tif foundAuthType {\n\t\tswitch authType {\n\t\tcase Oauth1:\n\t\t\terr = errors.New(\"oauth1 mechanism not implemented\")\n\t\t\treturn\n\t\tcase Oauth2:\n\t\t\tsaslClient := sasl.NewXoauth2Client((*rId.Credentials)[users.CRED_USERNAME], (*rId.Credentials)[users.CRED_ACCESS_TOKEN])\n\t\t\terr = imapClient.Authenticate(saslClient)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Errorf(\"[fetchMail] imapLogin failed to authenticate identity %s with proto Xoauth2\", rId.Id)\n\t\t\t\treturn\n\t\t\t}\n\t\tcase LoginPassword:\n\t\t\terr = imapClient.Login((*rId.Credentials)[\"inusername\"], (*rId.Credentials)[\"inpassword\"])\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Errorf(\"[fetchMail] imapLogin failed to login IMAP for user %s\", rId.UserId)\n\t\t\t\treturn\n\t\t\t}\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"unknown auth mechanism : <%s>\", authType)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\t// fallback by trying default LoginPassword mechanism\n\t\terr = imapClient.Login((*rId.Credentials)[\"inusername\"], (*rId.Credentials)[\"inpassword\"])\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"[fetchMail] imapLogin failed to login IMAP\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tlog.Println(\"Logged in\")\n\treturn\n}\n\n// syncMailbox will check uidvalidity and fetch only new messages since last sync state saved in RemoteIdentity.\n// If no previous state found in RemoteIdentity or uidvalidity has changed, syncMailbox will do a full fetch instead.\nfunc syncMailbox(ibox *imapBox, imapClient *client.Client, provider Provider, ch chan *imap.Message) (err error) {\n\n\tmbox, err := imapClient.Select(ibox.name, false)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[syncMailbox] failed to select mailbox <%s>\", ibox.name)\n\t\tclose(ch)\n\t\treturn\n\t}\n\tvar from, to uint32\n\tif ibox.lastSync.IsZero() {\n\t\t// first sync, blindly fetch all messages\n\t\tfrom, to = 1, 0\n\t\t(*ibox).uidValidity = mbox.UidValidity\n\t} else {\n\t\t// check mailbox UIDVALIDITY\n\t\tif ibox.uidValidity != mbox.UidValidity {\n\t\t\t// TODO\n\t\t\t// MUST empty the local cache of that mailbox and resync mailbox\n\t\t\tlog.Warnf(\"[syncMailbox] uidValidity has changed from %d to %d. Local mailbox should resync.\", ibox.uidValidity, mbox.UidValidity)\n\t\t\t// for now, we blindly (re)fetch all messages\n\t\t\tfrom, to = 1, 0\n\t\t\t(*ibox).uidValidity = mbox.UidValidity\n\t\t} else {\n\t\t\tif ibox.lastSeenUid == 0 {\n\t\t\t\tfrom, to = 1, 0\n\t\t\t} else {\n\t\t\t\tfrom = ibox.lastSeenUid + 1\n\t\t\t\tto = 0\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fetch(imapClient, provider, from, to, ch)\n}\n\n// fetchMailbox retrieves all messages found within remote mailbox\n// unaware of synchronization\nfunc fetchMailbox(ibox *imapBox, imapClient *client.Client, provider Provider, ch chan *imap.Message) (err error) {\n\n\tmbox, err := imapClient.Select(ibox.name, true)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[fetchMailbox] failed to select INBOX\")\n\t\treturn\n\t}\n\n\tfrom := uint32(1)\n\tto := mbox.UidNext\n\n\terr = fetch(imapClient, provider, from, to, ch)\n\tif err != nil {\n\t\tlog.WithError(err).Error(\"[fetchMailbox] failed\")\n\t}\n\treturn err\n}\n\n// MashalImap build RFC5322 mail from imap.Message,\n// adds custom `X-Fetched` headers,\n// returns an Email suitable to send to our email lda.\nfunc MarshalImap(message *imap.Message, xHeaders ImapFetcherHeaders) (mail *Email, err error) {\n\n\tvar mailBuff bytes.Buffer\n\n\tfor k, v := range xHeaders {\n\t\tmailBuff.WriteString(k + \": \" + v + \"\\r\\n\")\n\t}\n\n\tif len(message.Body) == 1 { // should have only one body\n\t\tfor _, body := range message.Body {\n\t\t\t_, err := mailBuff.ReadFrom(body)\n\t\t\tif err != nil {\n\t\t\t\t//TODO\n\t\t\t}\n\t\t\t// stop at first iteration because only one body\n\t\t\tbreak\n\t\t}\n\t}\n\tmail = &Email{\n\t\tRaw:     mailBuff,\n\t\tImapUid: message.Uid,\n\t}\n\treturn\n}\n\n// buildXheaders builds custom X-Fetched headers\n// with provider specific information\nfunc buildXheaders(tlsConn *tls.Conn, rId *UserIdentity, box *imapBox, message *imap.Message, provider Provider) (xHeaders ImapFetcherHeaders) {\n\tconnState := tlsConn.ConnectionState()\n\n\tvar proto string\n\tif provider.Capabilities[\"IMAP4rev1\"] == true {\n\t\tproto = \"with IMAP4rev1 protocol\"\n\t}\n\txHeaders = make(ImapFetcherHeaders)\n\txHeaders[\"X-Fetched-Imap\"] = fmt.Sprintf(`from %s ([%s])\n        (using %s with cipher %s)\n        by imap-fetcher (Caliopen) %s;\n        %s`,\n\t\trId.Infos[\"inserver\"],\n\t\ttlsConn.RemoteAddr().String(),\n\t\tTlsVersions[connState.Version],\n\t\tTlsSuites[connState.CipherSuite],\n\t\tproto,\n\t\ttime.Now().Format(time.RFC1123Z))\n\n\txHeaders[\"X-Fetched-Imap-Account\"] = rId.DisplayName\n\txHeaders[\"X-Fetched-Imap-Box\"] = base64.StdEncoding.EncodeToString([]byte(box.name))\n\txHeaders[\"X-Fetched-Imap-For\"] = rId.UserId.String()\n\txHeaders[\"X-Fetched-Imap-Uid\"] = strconv.Itoa(int(message.Uid))\n\tif len(message.Flags) > 0 {\n\t\txHeaders[\"X-Fetched-Imap-Flags\"] = base64.StdEncoding.EncodeToString([]byte(strings.Join(message.Flags, \"\\r\\n\")))\n\t}\n\tswitch provider.Name {\n\tcase \"gmail\":\n\t\tif msgid, ok := message.Items[gmail_msgid].(string); ok {\n\t\t\txHeaders[\"X-Fetched-\"+gmail_msgid] = msgid\n\t\t}\n\t\tgLabels := strings.Builder{}\n\t\tif labels, ok := message.Items[gmail_labels]; ok && labels != nil {\n\t\t\tfor i, label := range labels.([]interface{}) {\n\t\t\t\tif label != nil {\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\tgLabels.WriteString(label.(string))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tgLabels.WriteString(\"\\r\\n\" + label.(string))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif gLabels.Len() > 0 {\n\t\t\txHeaders[\"X-Fetched-\"+gmail_labels] = base64.StdEncoding.EncodeToString([]byte(gLabels.String()))\n\t\t}\n\n\t}\n\treturn\n}\n\n// from and to must be uid\n// zero values will be replaced by * wildcard\nfunc fetch(imapClient *client.Client, provider Provider, from, to uint32, ch chan *imap.Message) error {\n\n\tif from != 0 && to != 0 && from > to {\n\t\tclose(ch)\n\t\treturn fmt.Errorf(\"[fetch] 'to' param is lower than 'from'\")\n\t}\n\n\tif from != 0 && from == to {\n\t\tlog.Info(\"nothing to fetch\")\n\t\tclose(ch)\n\t\treturn nil\n\t}\n\tseqset := new(imap.SeqSet)\n\tseqset.AddRange(from, to)\n\n\tlog.Info(\"beginning to fetch messages…\")\n\titems := []imap.FetchItem{imap.FetchFlags, imap.FetchUid, \"BODY.PEEK[]\"}\n\tif len(provider.FetchItems) > 0 {\n\t\titems = append(items, provider.FetchItems...)\n\t}\n\n\treturn imapClient.UidFetch(seqset, items, ch)\n}\n\n// uploadSentMessage uploads a RFC 5322 mail to relevent `sent` mailbox and flags it has seen\nfunc uploadSentMessage(imapClient *client.Client, mail string, date time.Time) error {\n\n\t//1. list mailboxes to find which one is for `sent` messages\n\tvar sentMbx string\n\tboxes := make(chan *imap.MailboxInfo)\n\tgo func() {\n\t\terr := imapClient.List(\"\", \"*\", boxes)\n\n\t\tif err != nil {\n\t\t\t// ensure channel is closed\n\t\t\tif _, ok := <-boxes; ok {\n\t\t\t\tclose(boxes)\n\t\t\t}\n\t\t}\n\t}()\n\tvar found bool\n\tfor box := range boxes {\n\t\tif !found {\n\t\t\tfor _, attr := range box.Attributes {\n\t\t\t\tif strings.Contains(attr, \"Sent\") {\n\t\t\t\t\tsentMbx = box.Name\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif sentMbx == \"\" {\n\t\t\t\tif strings.Contains(box.Name, \"Sent\") {\n\t\t\t\t\tsentMbx = box.Name\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t//name still missing, use standard rfc6154#2\n\tif sentMbx == \"\" {\n\t\tsentMbx = `\\Sent`\n\t}\n\n\t//2. append mail to mailbox\n\treturn imapClient.Append(sentMbx, []string{imap.SeenFlag}, date, bytes.NewBufferString(mail))\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/imap_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage imap_worker\n\nimport (\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/helpers\"\n\t\"github.com/emersion/go-imap\"\n\t\"github.com/satori/go.uuid\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\n// NB : as of february'19 IMAP commands are not tested yet\n\nfunc Test_buildXheaders(t *testing.T) {\n\ttlsConn := helpers.GetTestTlsConn()\n\tuserIdentity := &UserIdentity{}\n\tbox := &imapBox{}\n\tmessage := &imap.Message{}\n\tprovider := Provider{}\n\ttype params struct {\n\t\tc *tls.Conn\n\t\tu *UserIdentity\n\t\ti *imapBox\n\t\tm *imap.Message\n\t\tp Provider\n\t}\n\tconnState := tlsConn.ConnectionState()\n\n\tdata := []struct {\n\t\tin  params\n\t\tout ImapFetcherHeaders\n\t}{\n\t\t{\n\t\t\tin: params{tlsConn, userIdentity, box, message, provider},\n\t\t\tout: ImapFetcherHeaders{\n\t\t\t\t\"X-Fetched-Imap-Account\": \"\",\n\t\t\t\t\"X-Fetched-Imap-Box\":     \"\",\n\t\t\t\t\"X-Fetched-Imap-For\":     \"00000000-0000-0000-0000-000000000000\",\n\t\t\t\t\"X-Fetched-Imap-Uid\":     \"0\",\n\t\t\t\t\"X-Fetched-Imap\": fmt.Sprintf(`from %s ([%s])\n        (using %s with cipher %s)\n        by imap-fetcher (Caliopen) %s;\n        %s`, \"\", tlsConn.RemoteAddr().String(), TlsVersions[connState.Version], TlsSuites[connState.CipherSuite], \"\", time.Now().Format(time.RFC1123Z)),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tin: params{tlsConn, userIdentity, box, message, Provider{Capabilities: map[string]bool{\"IMAP4rev1\": true}}},\n\t\t\tout: ImapFetcherHeaders{\n\t\t\t\t\"X-Fetched-Imap-Account\": \"\",\n\t\t\t\t\"X-Fetched-Imap-Box\":     \"\",\n\t\t\t\t\"X-Fetched-Imap-For\":     \"00000000-0000-0000-0000-000000000000\",\n\t\t\t\t\"X-Fetched-Imap-Uid\":     \"0\",\n\t\t\t\t\"X-Fetched-Imap\": fmt.Sprintf(`from %s ([%s])\n        (using %s with cipher %s)\n        by imap-fetcher (Caliopen) %s;\n        %s`, \"\", tlsConn.RemoteAddr().String(), TlsVersions[connState.Version], TlsSuites[connState.CipherSuite], \"with IMAP4rev1 protocol\", time.Now().Format(time.RFC1123Z)),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tin: params{tlsConn, &UserIdentity{\n\t\t\t\tDisplayName: \"account name\",\n\t\t\t\tUserId:      UUID(uuid.FromStringOrNil(backendstest.EmmaTommeUserId)),\n\t\t\t}, box, message, provider},\n\t\t\tout: ImapFetcherHeaders{\n\t\t\t\t\"X-Fetched-Imap-Account\": \"account name\",\n\t\t\t\t\"X-Fetched-Imap-Box\":     \"\",\n\t\t\t\t\"X-Fetched-Imap-For\":     uuid.FromStringOrNil(backendstest.EmmaTommeUserId).String(),\n\t\t\t\t\"X-Fetched-Imap-Uid\":     \"0\",\n\t\t\t\t\"X-Fetched-Imap\": fmt.Sprintf(`from %s ([%s])\n        (using %s with cipher %s)\n        by imap-fetcher (Caliopen) %s;\n        %s`, \"\", tlsConn.RemoteAddr().String(), TlsVersions[connState.Version], TlsSuites[connState.CipherSuite], \"\", time.Now().Format(time.RFC1123Z)),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tin: params{tlsConn, userIdentity, &imapBox{name: \"box name\"}, message, provider},\n\t\t\tout: ImapFetcherHeaders{\n\t\t\t\t\"X-Fetched-Imap-Account\": \"\",\n\t\t\t\t\"X-Fetched-Imap-Box\":     base64.StdEncoding.EncodeToString([]byte(\"box name\")),\n\t\t\t\t\"X-Fetched-Imap-For\":     \"00000000-0000-0000-0000-000000000000\",\n\t\t\t\t\"X-Fetched-Imap-Uid\":     \"0\",\n\t\t\t\t\"X-Fetched-Imap\": fmt.Sprintf(`from %s ([%s])\n        (using %s with cipher %s)\n        by imap-fetcher (Caliopen) %s;\n        %s`, \"\", tlsConn.RemoteAddr().String(), TlsVersions[connState.Version], TlsSuites[connState.CipherSuite], \"\", time.Now().Format(time.RFC1123Z)),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tin: params{tlsConn, userIdentity, box, &imap.Message{Uid: 999}, provider},\n\t\t\tout: ImapFetcherHeaders{\n\t\t\t\t\"X-Fetched-Imap-Account\": \"\",\n\t\t\t\t\"X-Fetched-Imap-Box\":     \"\",\n\t\t\t\t\"X-Fetched-Imap-For\":     \"00000000-0000-0000-0000-000000000000\",\n\t\t\t\t\"X-Fetched-Imap-Uid\":     \"999\",\n\t\t\t\t\"X-Fetched-Imap\": fmt.Sprintf(`from %s ([%s])\n        (using %s with cipher %s)\n        by imap-fetcher (Caliopen) %s;\n        %s`, \"\", tlsConn.RemoteAddr().String(), TlsVersions[connState.Version], TlsSuites[connState.CipherSuite], \"\", time.Now().Format(time.RFC1123Z)),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tin: params{tlsConn, userIdentity, box, &imap.Message{Flags: []string{\"flag1\", \"flag2\"}}, provider},\n\t\t\tout: ImapFetcherHeaders{\n\t\t\t\t\"X-Fetched-Imap-Account\": \"\",\n\t\t\t\t\"X-Fetched-Imap-Box\":     \"\",\n\t\t\t\t\"X-Fetched-Imap-For\":     \"00000000-0000-0000-0000-000000000000\",\n\t\t\t\t\"X-Fetched-Imap-Uid\":     \"0\",\n\t\t\t\t\"X-Fetched-Imap-Flags\":   base64.StdEncoding.EncodeToString([]byte(\"flag1\\r\\nflag2\")),\n\t\t\t\t\"X-Fetched-Imap\": fmt.Sprintf(`from %s ([%s])\n        (using %s with cipher %s)\n        by imap-fetcher (Caliopen) %s;\n        %s`, \"\", tlsConn.RemoteAddr().String(), TlsVersions[connState.Version], TlsSuites[connState.CipherSuite], \"\", time.Now().Format(time.RFC1123Z)),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tin: params{tlsConn, userIdentity, box, message, Provider{Name: \"gmail\"}},\n\t\t\tout: ImapFetcherHeaders{\n\t\t\t\t\"X-Fetched-Imap-Account\": \"\",\n\t\t\t\t\"X-Fetched-Imap-Box\":     \"\",\n\t\t\t\t\"X-Fetched-Imap-For\":     \"00000000-0000-0000-0000-000000000000\",\n\t\t\t\t\"X-Fetched-Imap-Uid\":     \"0\",\n\t\t\t\t\"X-Fetched-Imap\": fmt.Sprintf(`from %s ([%s])\n        (using %s with cipher %s)\n        by imap-fetcher (Caliopen) %s;\n        %s`, \"\", tlsConn.RemoteAddr().String(), TlsVersions[connState.Version], TlsSuites[connState.CipherSuite], \"\", time.Now().Format(time.RFC1123Z)),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tin: params{tlsConn, userIdentity, box, &imap.Message{Items: map[imap.FetchItem]interface{}{\n\t\t\t\timap.FetchItem(gmail_msgid):  \"gmail message-id\",\n\t\t\t\timap.FetchItem(gmail_labels): []interface{}{`\\Inbox`, `Important`, `Très Important`},\n\t\t\t}}, Provider{Name: \"gmail\"}},\n\t\t\tout: ImapFetcherHeaders{\n\t\t\t\t\"X-Fetched-Imap-Account\":    \"\",\n\t\t\t\t\"X-Fetched-Imap-Box\":        \"\",\n\t\t\t\t\"X-Fetched-Imap-For\":        \"00000000-0000-0000-0000-000000000000\",\n\t\t\t\t\"X-Fetched-Imap-Uid\":        \"0\",\n\t\t\t\t\"X-Fetched-\" + gmail_msgid:  \"gmail message-id\",\n\t\t\t\t\"X-Fetched-\" + gmail_labels: \"XEluYm94DQpJbXBvcnRhbnQNClRyw6hzIEltcG9ydGFudA==\",\n\t\t\t\t\"X-Fetched-Imap\": fmt.Sprintf(`from %s ([%s])\n        (using %s with cipher %s)\n        by imap-fetcher (Caliopen) %s;\n        %s`, \"\", tlsConn.RemoteAddr().String(), TlsVersions[connState.Version], TlsSuites[connState.CipherSuite], \"\", time.Now().Format(time.RFC1123Z)),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, set := range data {\n\t\tresult := buildXheaders(set.in.c, set.in.u, set.in.i, set.in.m, set.in.p)\n\t\tif !reflect.DeepEqual(result, set.out) {\n\t\t\tt.Errorf(\"invalid headers for set %d.\\nExpected : %+v\\nGot : %+v\", i, set.out, result)\n\t\t}\n\t}\n}\n\nfunc TestMarshalImap(t *testing.T) {\n\tmessage := imap.NewMessage(42, []imap.FetchItem{imap.FetchBody, imap.FetchFlags})\n\tconst mailBody = \"mail body\"\n\tconst headerKey = \"x-test-header\"\n\tconst headerValue = \"test-header-value\"\n\tmessage.Body = map[*imap.BodySectionName]imap.Literal{&imap.BodySectionName{}: strings.NewReader(mailBody)}\n\txHeaders := map[string]string{headerKey: headerValue}\n\temail, err := MarshalImap(message, xHeaders)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\t// expect xHeaders added at head of email's bytes.Buffer\n\ts := headerKey + \": \" + headerValue + \"\\r\\n\" + mailBody\n\tif s != email.Raw.String() {\n\t\tt.Errorf(\"MarshalImap failed to build expected email, expected :\\n%s\\ngot :\\n%s\\n\", s, email.Raw.String())\n\t}\n\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/lda.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage imap_worker\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tbroker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.emails\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/satori/go.uuid\"\n\t\"time\"\n)\n\n//Local Delivery Agent, in charge of IO between fetcher and our email broker\ntype Lda struct {\n\tConfig           WorkerConfig\n\tbroker           *broker.EmailBroker\n\tbrokerConnectors broker.EmailBrokerConnectors\n\tProviders        map[string]Provider\n}\n\nfunc NewLda(config WorkerConfig) (*Lda, error) {\n\tvar err error\n\tlda := Lda{}\n\tlda.Config = config\n\tlda.broker, lda.brokerConnectors, err = broker.Initialize(config.LDAConfig)\n\tlda.Providers = make(map[string]Provider)\n\tfor _, provider := range config.LDAConfig.Providers {\n\t\tlda.Providers[provider.Name] = provider\n\t}\n\treturn &lda, err\n}\n\nfunc (lda *Lda) shutdown() error {\n\tlda.broker.ShutDown()\n\treturn nil\n}\n\nfunc (lda *Lda) deliverMail(mail *Email, userId, identityID string, batch *Notifications.BatchNotification) (err error) {\n\temailMsg := &EmailMessage{\n\t\tEmail: mail,\n\t\tMessage: &Message{\n\t\t\tUser_id:        UUID(uuid.FromStringOrNil(userId)),\n\t\t\tUserIdentities: []UUID{UUID(uuid.FromStringOrNil(identityID))},\n\t\t},\n\t}\n\tincoming := &broker.SmtpEmail{\n\t\tEmailMessage: emailMsg,\n\t\tResponse:     make(chan *broker.EmailDeliveryAck),\n\t\tBatch:        batch,\n\t}\n\tdefer close(incoming.Response)\n\n\tlda.brokerConnectors.Ingress <- incoming\n\n\tselect {\n\tcase response := <-incoming.Response:\n\t\tif response.Err {\n\t\t\treturn errors.New(fmt.Sprintf(\"[deliverMail] Error : \" + response.Response))\n\t\t}\n\t\treturn nil\n\tcase <-time.After(30 * time.Second):\n\t\treturn errors.New(\"[deliverMail] LDA timeout\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/sender.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage imap_worker\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/users\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/nats-io/go-nats\"\n\t\"time\"\n)\n\ntype Sender struct {\n\tHostname      string\n\tImapProviders map[string]Provider\n\tNatsConn      *nats.Conn\n\tNatsMessage   *nats.Msg\n\tOutSMTPtopic  string\n\tStore         backends.LDAStore\n}\n\n// unexported vars to help override funcs in tests\nvar (\n\tsendDraft = func(s *Sender, msg *nats.Msg) {\n\t\ts.SendDraft(msg)\n\t}\n\n\tuploadSentMessageToRemote = func(s *Sender, userIdentity *UserIdentity, msg *Message) error {\n\t\treturn s.UploadSentMessageToRemote(userIdentity, msg)\n\t}\n)\n\nfunc (s *Sender) SendDraft(msg *nats.Msg) {\n\tvar order BrokerOrder\n\terr := json.Unmarshal(msg.Data, &order)\n\tif err != nil {\n\t\ts.natsReplyError(msg, fmt.Errorf(\"Unable to unmarshal message from NATS. Payload was <%s>\", string(msg.Data)))\n\t\treturn\n\t}\n\t// get userIdentity and check auth params validity\n\tif err != nil {\n\t\ts.natsReplyError(msg, err)\n\t\treturn\n\t}\n\tuserIdentity, err := s.Store.RetrieveUserIdentity(order.UserId, order.IdentityId, true)\n\tif err != nil {\n\t\ts.natsReplyError(msg, err)\n\t\treturn\n\t}\n\tif userIdentity.Infos[\"authtype\"] == Oauth2 {\n\t\terr = users.ValidateOauth2Credentials(userIdentity, s, true)\n\t\tif err != nil {\n\t\t\ts.natsReplyError(msg, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\t//1. make use of our lmtpd to send email\n\tnatsMessage, e := json.Marshal(order)\n\tif e != nil {\n\t\ts.natsReplyError(msg, errors.New(\"[SendDraft] failed to build nats message\"))\n\t\treturn\n\t}\n\tsmtpReply, err := s.NatsConn.Request(s.OutSMTPtopic, []byte(natsMessage), 30*time.Second)\n\n\t//2. handle LMTP response\n\tif err != nil {\n\t\tif smtpReply != nil {\n\t\t\ts.natsReplyError(msg, errors.New(smtpReply.Reply))\n\t\t\treturn\n\t\t} else {\n\t\t\ts.natsReplyError(msg, err)\n\t\t\treturn\n\t\t}\n\t}\n\tvar reply DeliveryAck\n\terr = json.Unmarshal(smtpReply.Data, &reply)\n\tif err != nil {\n\t\ts.natsReplyError(msg, fmt.Errorf(\"[IMAPworker]SendDraft failed to unmarshal smtpReply : %s\", err))\n\t\treturn\n\t}\n\tif reply.Err {\n\t\ts.natsReplyError(msg, errors.New(reply.Response))\n\t\treturn\n\t}\n\n\t//3. no error when sending email,\n\t// if applicable upload a copy to remote IMAP account\n\tif userIdentity.Type == RemoteIdentity {\n\t\tsentMsg, err := s.Store.RetrieveMessage(order.UserId, order.MessageId)\n\t\tif err != nil {\n\t\t\ts.natsReplyError(msg, fmt.Errorf(\"[IMAPworker]SendDraft failed to retrieve sent message : %s\", err))\n\t\t\treturn\n\t\t}\n\t\terr = uploadSentMessageToRemote(s, userIdentity, sentMsg)\n\t\tif err != nil {\n\t\t\ts.natsReplyError(msg, fmt.Errorf(\"[IMAPworker]SendDraft failed to upload sent email to remote IMAP account : %s\", err))\n\t\t\treturn\n\t\t}\n\t}\n\t//4. respond to caller\n\t_ = s.NatsConn.Publish(msg.Reply, smtpReply.Data)\n}\n\nfunc (s *Sender) natsReplyError(msg *nats.Msg, err error) {\n\tlog.WithError(err).Warnf(\"IMAPworker [outbound] : error when processing incoming nats message : %+v\", *msg)\n\n\tack := DeliveryAck{\n\t\tErr:      true,\n\t\tResponse: fmt.Sprintf(\"failed to handle order with error « %s » \", err.Error()),\n\t}\n\n\tjson_resp, _ := json.Marshal(ack)\n\ts.NatsConn.Publish(msg.Reply, json_resp)\n}\n\nfunc (s *Sender) UploadSentMessageToRemote(userIdentity *UserIdentity, msg *Message) error {\n\n\t//get raw message\n\trawMail, err := s.Store.GetRawMessage(msg.Raw_msg_id.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif userIdentity.Infos[\"authtype\"] == Oauth2 {\n\t\terr = users.ValidateOauth2Credentials(userIdentity, s, true)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t_, imapClient, _, err := imapLogin(userIdentity)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer imapClient.Logout()\n\n\treturn uploadSentMessage(imapClient, rawMail.Raw_data, msg.Date)\n}\n\n/* Oauth2Interfacer implementation */\n\nfunc (s *Sender) GetProviders() map[string]Provider {\n\treturn s.ImapProviders\n}\n\nfunc (s *Sender) GetHostname() string {\n\treturn s.Hostname\n}\n\nfunc (s *Sender) GetIdentityStore() backends.IdentityStorageUpdater {\n\treturn s.Store\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/sender_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage imap_worker\n\nimport (\n\t\"encoding/json\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"github.com/nats-io/gnatsd/server\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/satori/go.uuid\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst (\n\treplyErrorTopic = \"testReplyError\"\n)\n\nfunc initTestSender() (sender *Sender, natsServer *server.Server, err error) {\n\tworker, natsServer, err := newWorkerTest()\n\tsender = &Sender{\n\t\tHostname:      worker.Config.Hostname,\n\t\tImapProviders: worker.Lda.Providers,\n\t\tNatsConn:      worker.NatsConn,\n\t\tOutSMTPtopic:  worker.Config.LDAConfig.OutTopic,\n\t\tStore:         worker.Store,\n\t}\n\treturn\n}\n\n// the whole process of sending an email is not tested here,\n// but only APIs calls and responses/errors handling\nfunc TestSender_SendDraft(t *testing.T) {\n\tsender, natsServer, err := initTestSender()\n\tdefer natsServer.Shutdown()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tc := make(chan struct{})\n\t// add a global subscriber to test errors replies\n\tglobalErrSub, err := sender.NatsConn.Subscribe(replyErrorTopic, func(msg *nats.Msg) {\n\t\tdefer close(c)\n\t\tvar resp DeliveryAck\n\t\terr := json.Unmarshal(msg.Data, &resp)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tif !resp.Err {\n\t\t\tt.Error(\"expected DeliveryAck.Err == true, got false\")\n\t\t\treturn\n\t\t}\n\t\tif resp.Response == \"\" {\n\t\t\tt.Error(\"expected DeliveryAck.Response to be non empty string, got empty string\")\n\t\t}\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\t// test SendDraft with non existent identity\n\tfakeUUID := uuid.NewV4().String()\n\n\tsendOrder := BrokerOrder{\n\t\tIdentityId: fakeUUID,\n\t\tMessageId:  fakeUUID,\n\t\tOrder:      \"deliver\",\n\t\tUserId:     fakeUUID,\n\t}\n\tjsonOrder, _ := json.Marshal(sendOrder)\n\tnatsPayload := nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   replyErrorTopic,\n\t\tData:    jsonOrder,\n\t}\n\tsender.SendDraft(&natsPayload)\n\tselect {\n\tcase <-c:\n\tcase <-time.After(time.Second):\n\t\tt.Errorf(\"timeout waiting for sendDraft response for order : %+v\", sendOrder)\n\t}\n\n\t// test SendDraft with LMTP responding an error\n\tc = make(chan struct{})\n\tlmtpErrorSub, err := sender.NatsConn.Subscribe(sender.OutSMTPtopic, func(msg *nats.Msg) {\n\t\terr := sender.NatsConn.Publish(msg.Reply, []byte(`{\"error\":true,\"message\":\"fake smtp error\"}`))\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t} else {\n\t\tsendOrder := BrokerOrder{\n\t\t\tIdentityId: \"7e4eb26d-1b70-4bb3-b556-6c54f046e88e\",\n\t\t\tMessageId:  fakeUUID,\n\t\t\tOrder:      \"deliver\",\n\t\t\tUserId:     backendstest.EmmaTommeUserId,\n\t\t}\n\t\tjsonOrder, _ := json.Marshal(sendOrder)\n\t\tnatsPayload := nats.Msg{\n\t\t\tSubject: \"test\",\n\t\t\tReply:   replyErrorTopic,\n\t\t\tData:    jsonOrder,\n\t\t}\n\t\tsender.SendDraft(&natsPayload)\n\t\tselect {\n\t\tcase <-c:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Errorf(\"timeout waiting for sendDraft response for order : %+v\", sendOrder)\n\t\t}\n\t}\n\t_ = lmtpErrorSub.Unsubscribe()\n\n\t// test SendDraft with valid payload and OK from lmtp\n\t// but invalid message ID\n\tc = make(chan struct{})\n\tlmtpBadMsgSub, err := sender.NatsConn.Subscribe(sender.OutSMTPtopic, func(msg *nats.Msg) {\n\t\terr := sender.NatsConn.Publish(msg.Reply, []byte(`{\"error\":false,\"message\":\"\"}`))\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t} else {\n\t\tsendOrder := BrokerOrder{\n\t\t\tIdentityId: \"7e4eb26d-1b70-4bb3-b556-6c54f046e88e\",\n\t\t\tMessageId:  fakeUUID,\n\t\t\tOrder:      \"deliver\",\n\t\t\tUserId:     backendstest.EmmaTommeUserId,\n\t\t}\n\t\tjsonOrder, _ := json.Marshal(sendOrder)\n\t\tnatsPayload := nats.Msg{\n\t\t\tSubject: \"test\",\n\t\t\tReply:   replyErrorTopic,\n\t\t\tData:    jsonOrder,\n\t\t}\n\t\tsender.SendDraft(&natsPayload)\n\t\tselect {\n\t\tcase <-c:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Errorf(\"timeout waiting for sendDraft response for order : %+v\", sendOrder)\n\t\t}\n\t}\n\t_ = lmtpBadMsgSub.Unsubscribe()\n\n\t// test SendDraft with valid payload and OK from lmtp\n\t// should call uploadSentMessageToRemote because identity is a remote one\n\t// and should re-publish lmtp reply\n\t_ = globalErrSub.Unsubscribe()\n\tc = make(chan struct{})\n\tlmtpOKMsgSub, err := sender.NatsConn.Subscribe(sender.OutSMTPtopic, func(msg *nats.Msg) {\n\t\terr := sender.NatsConn.Publish(msg.Reply, []byte(`{\"error\":false,\"message\":\"\"}`))\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t})\n\t_, err = sender.NatsConn.Subscribe(\"ok reply\", func(msg *nats.Msg) {\n\t\tdefer close(c)\n\t\tvar resp DeliveryAck\n\t\terr := json.Unmarshal(msg.Data, &resp)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tif resp.Err {\n\t\t\tt.Error(\"expected DeliveryAck.Err == false, got true\")\n\t\t\treturn\n\t\t}\n\t\tif resp.Response != \"\" {\n\t\t\tt.Error(\"expected DeliveryAck.Response to be empty string, got non empty string\")\n\t\t}\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t} else {\n\t\tsendOrder := BrokerOrder{\n\t\t\tIdentityId: \"7e4eb26d-1b70-4bb3-b556-6c54f046e88e\",\n\t\t\tMessageId:  \"b26e5ba4-34cc-42bb-9b70-5279648134f8\",\n\t\t\tOrder:      \"deliver\",\n\t\t\tUserId:     backendstest.EmmaTommeUserId,\n\t\t}\n\t\tjsonOrder, _ := json.Marshal(sendOrder)\n\t\tnatsPayload := nats.Msg{\n\t\t\tSubject: \"test\",\n\t\t\tReply:   \"ok reply\",\n\t\t\tData:    jsonOrder,\n\t\t}\n\t\t// override func to prevent cascading calls\n\t\tuploadSentMessageToRemote = func(s *Sender, userIdentity *UserIdentity, msg *Message) error {\n\t\t\treturn nil\n\t\t}\n\t\tsender.SendDraft(&natsPayload)\n\t\tselect {\n\t\tcase <-c:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Errorf(\"timeout waiting for sendDraft response for order : %+v\", sendOrder)\n\t\t}\n\t}\n\t_ = lmtpOKMsgSub.Unsubscribe()\n\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/worker.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage imap_worker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype Worker struct {\n\tConfig    WorkerConfig\n\tId        string\n\tLda       *Lda\n\tNatsConn  *nats.Conn\n\tNatsSubs  []*nats.Subscription\n\tStore     backends.LDAStore\n\tHaltGroup *sync.WaitGroup\n}\n\nconst (\n\tnoPendingJobErr = \"no pending job\"\n\tneedJobOrderStr = `{\"worker\":\"%s\",\"order\":{\"order\":\"need_job\"}}`\n)\n\n// NewWorker loads config, checks for errors then returns a worker ready to start.\nfunc NewWorker(config WorkerConfig, id string) (worker *Worker, err error) {\n\n\tw := Worker{\n\t\tConfig:   config,\n\t\tId:       id,\n\t\tNatsSubs: make([]*nats.Subscription, 1),\n\t}\n\t//copy relevant config to LDAConfig\n\tw.Config.LDAConfig.StoreName = w.Config.StoreName\n\tw.Config.LDAConfig.NatsURL = w.Config.NatsUrl\n\tw.Config.LDAConfig.StoreConfig = w.Config.StoreConfig\n\n\t// init all connexions, they will be pass to fetchers\n\t// Lda\n\tw.Lda, err = NewLda(w.Config)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[NewWorker] : initalization of LDA failed\")\n\t\treturn nil, err\n\t}\n\n\t// Nats\n\tw.NatsConn, err = nats.Connect(config.NatsUrl)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[NewWorker] : initalization of NATS connexion failed\")\n\t\treturn nil, err\n\t}\n\n\t// Store\n\tswitch config.StoreName {\n\tcase \"cassandra\":\n\t\tc := store.CassandraConfig{\n\t\t\tHosts:       config.StoreConfig.Hosts,\n\t\t\tKeyspace:    config.StoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(config.StoreConfig.Consistency),\n\t\t\tSizeLimit:   config.StoreConfig.SizeLimit,\n\t\t\tUseVault:    config.StoreConfig.UseVault,\n\t\t}\n\t\tif config.StoreConfig.ObjectStore == \"s3\" {\n\t\t\tc.WithObjStore = true\n\t\t\tc.Endpoint = config.StoreConfig.OSSConfig.Endpoint\n\t\t\tc.AccessKey = config.StoreConfig.OSSConfig.AccessKey\n\t\t\tc.SecretKey = config.StoreConfig.OSSConfig.SecretKey\n\t\t\tc.RawMsgBucket = config.StoreConfig.OSSConfig.Buckets[\"raw_messages\"]\n\t\t\tc.AttachmentBucket = config.StoreConfig.OSSConfig.Buckets[\"temporary_attachments\"]\n\t\t\tc.Location = config.StoreConfig.OSSConfig.Location\n\t\t}\n\t\tif c.UseVault {\n\t\t\tc.Url = config.StoreConfig.VaultConfig.Url\n\t\t\tc.Username = config.StoreConfig.VaultConfig.Username\n\t\t\tc.Password = config.StoreConfig.VaultConfig.Password\n\t\t}\n\t\tw.Store, err = store.InitializeCassandraBackend(c)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[NewWorker] initalization of %s backend failed\", config.StoreName)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn &w, nil\n}\n\nfunc (worker *Worker) Start(throttling ...time.Duration) error {\n\tvar throttle time.Duration\n\tif len(throttling) == 1 && throttling[0] != 0 {\n\t\tthrottle = throttling[0]\n\t} else {\n\t\tthrottle = pollThrottling\n\t}\n\tvar err error\n\t(*worker).NatsSubs[0], err = worker.NatsConn.QueueSubscribe(worker.Config.NatsTopicSender, worker.Config.NatsQueue, worker.natsMsgHandler)\n\tif err != nil {\n\t\treturn err\n\t}\n\tworker.NatsConn.Flush()\n\tlog.Infof(\"IMAP worker %s starting with %d sec throttling\", worker.Id, throttle/time.Second)\n\n\t// start throttled jobs polling\n\tfor {\n\t\tstart := time.Now()\n\t\trequestOrder := []byte(fmt.Sprintf(needJobOrderStr, worker.Id))\n\t\tlog.Infof(\"IMAP worker %s is requesting jobs to idpoller\", worker.Id)\n\t\tresp, err := worker.NatsConn.Request(worker.Config.NatsTopicPoller, requestOrder, time.Minute)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[worker %s] failed to request pending jobs on nats\", worker.Id)\n\t\t} else {\n\t\t\tworker.natsMsgHandler(resp)\n\t\t}\n\t\t// check for interrupt after job is finished\n\t\tif worker.HaltGroup != nil {\n\t\t\tworker.Stop()\n\t\t\tbreak\n\t\t}\n\t\telapsed := time.Now().Sub(start)\n\t\tif elapsed < throttle {\n\t\t\ttime.Sleep(throttle - elapsed)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (worker *Worker) Stop() {\n\tfor _, sub := range worker.NatsSubs {\n\t\tsub.Unsubscribe()\n\t}\n\tworker.NatsConn.Close()\n\tworker.Store.Close()\n\tworker.Lda.broker.Store.Close()\n\tworker.Lda.broker.NatsConn.Close()\n\tworker.HaltGroup.Done()\n\tlog.Infof(\"worker %s stopped\", worker.Id)\n}\n\n// MsgHandler parses message and launches appropriate goroutine to handle requested operations\nfunc (worker *Worker) natsMsgHandler(msg *nats.Msg) {\n\tmessage := IMAPorder{}\n\terr := json.Unmarshal(msg.Data, &message)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"Unable to unmarshal message from NATS. Payload was <%s>\", string(msg.Data))\n\t\treturn\n\t}\n\tswitch message.Order {\n\tcase noPendingJobErr:\n\t\treturn\n\tcase \"sync\": // simplest order to initiate a sync op for a stored remote identity\n\t\tfetcher := Fetcher{\n\t\t\tHostname: worker.Config.Hostname,\n\t\t\tLda:      worker.Lda,\n\t\t\tStore:    worker.Store,\n\t\t}\n\t\tsyncRemoteWithLocal(&fetcher, message)\n\tcase \"fullfetch\": // order sent by imapctl to initiate a fetch op for an user\n\t\tfetcher := Fetcher{\n\t\t\tHostname: worker.Config.Hostname,\n\t\t\tLda:      worker.Lda,\n\t\t\tStore:    worker.Store,\n\t\t}\n\t\tfetchRemoteToLocal(&fetcher, message)\n\tcase \"deliver\": // order sent by api2 to send a draft via remote SMTP/IMAP\n\t\tsender := Sender{\n\t\t\tHostname:      worker.Config.Hostname,\n\t\t\tImapProviders: worker.Lda.Providers,\n\t\t\tNatsConn:      worker.NatsConn,\n\t\t\tNatsMessage:   msg,\n\t\t\tOutSMTPtopic:  worker.Config.LDAConfig.OutTopic,\n\t\t\tStore:         worker.Store,\n\t\t}\n\t\tsendDraft(&sender, msg)\n\tcase \"test\":\n\t\tlog.Info(\"Order « test » received\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.imap/worker_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage imap_worker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/brokers/go.emails\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/NATS/go.mockednats\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"github.com/nats-io/gnatsd/server\"\n\t\"github.com/nats-io/go-nats\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst (\n\tnatsUrl = \"0.0.0.0\"\n)\n\nfunc newWorkerTest() (worker *Worker, natsServer *server.Server, err error) {\n\tworker = &Worker{\n\t\tConfig: WorkerConfig{\n\t\t\tLDAConfig: LDAConfig{\n\t\t\t\tOutTopic: \"outboundIMAP\",\n\t\t\t},\n\t\t\tNatsQueue:            \"IMAPworkers\",\n\t\t\tNatsTopicPoller:      \"imapJobs\",\n\t\t\tNatsTopicPollerCache: \"idCache\",\n\t\t\tNatsTopicSender:      \"outboundIMAP\",\n\t\t},\n\t\tId:       \"testWorker\",\n\t\tNatsSubs: make([]*nats.Subscription, 1),\n\t}\n\n\tnatsServer, natsConn, err := mockednats.GetNats()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tworker.NatsConn = natsConn\n\n\tconnectors := email_broker.EmailBrokerConnectors{\n\t\tIngress: make(chan *email_broker.SmtpEmail),\n\t\tEgress:  make(chan *email_broker.SmtpEmail),\n\t}\n\tworker.Lda = &Lda{\n\t\tbroker: &email_broker.EmailBroker{\n\t\t\tStore:      backendstest.GetLDAStoreBackend(),\n\t\t\tIndex:      backendstest.GetLDAIndexBackend(),\n\t\t\tNatsConn:   worker.NatsConn,\n\t\t\tConnectors: connectors,\n\t\t},\n\t\tbrokerConnectors: connectors,\n\t\tProviders:        make(map[string]Provider),\n\t}\n\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"[initMqHandler] failed to init NATS connection : %s\", err)\n\t}\n\n\tworker.Store = backendstest.GetLDAStoreBackend()\n\n\treturn\n}\n\nfunc TestWorker_StartAndStop(t *testing.T) {\n\tw, s, err := newWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tdefer s.Shutdown()\n\n\t// test if worker requests on Nats every second with the right payload\n\tc := make(chan struct{})\n\twg := new(sync.WaitGroup)\n\twg.Add(1)\n\tgo w.Start(time.Second)\n\tcount := 0\n\t_, err = w.NatsConn.Subscribe(\"imapJobs\", func(msg *nats.Msg) {\n\t\tvar req WorkerRequest\n\t\terr := json.Unmarshal(msg.Data, &req)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unable to unmarshal worker's request : %s\", err)\n\t\t\treturn\n\t\t}\n\t\tif req.Order.Order != \"need_job\" {\n\t\t\tt.Errorf(\"expected to receive order 'need_job', got %s\", req.Order.Order)\n\t\t}\n\t\tw.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"no pending job\"}`))\n\t\tcount++\n\t\tif count == 3 {\n\t\t\twg.Done()\n\t\t\treturn\n\t\t}\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(c)\n\t}()\n\tselect {\n\tcase <-c:\n\t\t// worker confirmed to send request every second ; now test halting\n\t\tw.HaltGroup = new(sync.WaitGroup)\n\t\tw.HaltGroup.Add(1)\n\t\ttime.Sleep(500 * time.Millisecond)\n\t\tif !w.NatsConn.IsClosed() {\n\t\t\tt.Error(\"expected worker's nats connexion to be closed\")\n\t\t}\n\t\tfor _, sub := range w.NatsSubs {\n\t\t\tif sub.IsValid() {\n\t\t\t\tt.Errorf(\"expected all worker's subscription closed, got <%s> still valid\", sub.Subject)\n\t\t\t}\n\t\t}\n\t\treturn\n\tcase <-time.After(5 * time.Second):\n\t\tt.Error(\"timeout waiting for worker to send requests on nats\")\n\t}\n}\n\nfunc TestWorker_natsMsgHandler(t *testing.T) {\n\tw, s, err := newWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tdefer s.Shutdown()\n\n\tc := make(chan struct{})\n\t// overriding funcs that should be called within natsMsgHandler but are out of this test scope\n\tsyncRemoteWithLocal = func(f *Fetcher, order IMAPorder) error {\n\t\tdefer close(c)\n\t\tif f == nil {\n\t\t\tt.Error(\"expected a Fetcher within syncRemoteWithLocal call, got nil\")\n\t\t\treturn nil\n\t\t}\n\t\tif f.Store != w.Store {\n\t\t\tt.Errorf(\"expected a fetcher set with worker's store, got %+v\", f.Store)\n\t\t}\n\t\tif f.Lda != w.Lda {\n\t\t\tt.Errorf(\"expected a fetcher set with worker's store, got %+v\", f.Store)\n\t\t}\n\t\treturn nil\n\t}\n\tfetchRemoteToLocal = func(f *Fetcher, order IMAPorder) error {\n\t\tdefer close(c)\n\t\tif f == nil {\n\t\t\tt.Error(\"expected a Fetcher within fetchRemoteToLocal call, got nil\")\n\t\t\treturn nil\n\t\t}\n\t\tif f.Store != w.Store {\n\t\t\tt.Errorf(\"expected a fetcher set with worker's store, got %+v\", f.Store)\n\t\t}\n\t\tif f.Lda != w.Lda {\n\t\t\tt.Errorf(\"expected a fetcher set with worker's store, got %+v\", f.Store)\n\t\t}\n\t\treturn nil\n\t}\n\tsendDraft = func(s *Sender, msg *nats.Msg) {\n\t\tdefer close(c)\n\t\tif s == nil {\n\t\t\tt.Error(\"expected a Sender within sendDraft call, got nil\")\n\t\t\treturn\n\t\t}\n\t\tif s.Store != w.Store {\n\t\t\tt.Errorf(\"expected a sender set with worker's store, got %+v\", s.Store)\n\t\t}\n\t\tif s.NatsConn != w.NatsConn {\n\t\t\tt.Errorf(\"expected a sender set with worker's natsConn, got %+v\", s.NatsConn)\n\t\t}\n\t\tif s.NatsMessage != msg {\n\t\t\tt.Errorf(\"expected a sender with nats message embedded, got %+v\", s.NatsMessage)\n\t\t}\n\t}\n\t// test orders handling\n\t// 'sync'\n\torder := IMAPorder{\n\t\tOrder: \"sync\",\n\t}\n\tdata, _ := json.Marshal(order)\n\tnatsPayload := nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    data,\n\t}\n\tw.natsMsgHandler(&natsPayload)\n\tselect {\n\tcase <-c:\n\tcase <-time.After(10 * time.Millisecond):\n\t\tt.Error(\"expected 'sync' order to trigger a call to syncRemoteWithLocal func, but func was not called\")\n\t}\n\t// 'fullfetch'\n\tc = make(chan struct{})\n\torder.Order = \"fullfetch\"\n\tdata, _ = json.Marshal(order)\n\tnatsPayload = nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    data,\n\t}\n\tw.natsMsgHandler(&natsPayload)\n\tselect {\n\tcase <-c:\n\tcase <-time.After(10 * time.Millisecond):\n\t\tt.Error(\"expected 'fullfetch' order to trigger a call to fetchRemoteToLocal func, but func was not called\")\n\t}\n\t// 'deliver'\n\tc = make(chan struct{})\n\torder.Order = \"deliver\"\n\tdata, _ = json.Marshal(order)\n\tnatsPayload = nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    data,\n\t}\n\tw.natsMsgHandler(&natsPayload)\n\tselect {\n\tcase <-c:\n\tcase <-time.After(10 * time.Millisecond):\n\t\tt.Error(\"expected 'deliver' order to trigger a call to sendDraft func, but func was not called\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.mastodon/account.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage mastodonworker\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"context\"\n\tbroker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.mastodon\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/mattn/go-mastodon\"\n\t\"strings\"\n)\n\ntype (\n\tAccountHandler struct {\n\t\tAccountDesk    chan uint\n\t\tbroker         *broker.MastodonBroker\n\t\tclosed         bool\n\t\tlastDMseen     string\n\t\tMasterDesk     chan DeskMessage\n\t\tmastodonClient *mastodon.Client\n\t\tuserAccount    *MastodonAccount\n\t}\n\n\tMastodonAccount struct {\n\t\tdisplayName string\n\t\tmastodonID  string\n\t\tremoteID    UUID\n\t\tuserID      UUID\n\t\tusername    string\n\t}\n)\n\nconst (\n\t//AccountDesk commands\n\tPollDM = uint(iota)\n\tPollTimeLine\n\tStop\n\n\tlastSeenInfosKey = \"lastseendm\"\n\tlastSyncInfosKey = \"lastsync\"\n\n\tlastErrorKey      = \"lastFetchError\"\n\tdateFirstErrorKey = \"firstErrorDate\"\n\tdateLastErrorKey  = \"lastErrorDate\"\n\terrorsCountKey    = \"errorsCount\"\n\tsyncingKey        = \"syncing\"\n\n\tdefaultPollInterval = 10\n\tsyncingTimeout      = 1 // how many hours to wait before restarting sync op\n)\n\n// NewAccountHandler creates a handler dedicated to a specific mastodon account.\n// It caches remote identity credentials and data, as well as user context connection to mastodon API.\nfunc NewAccountHandler(userID, remoteID string, worker Worker) (accountHandler *AccountHandler, err error) {\n\taccountHandler = new(AccountHandler)\n\taccountHandler.AccountDesk = make(chan uint)\n\taccountHandler.MasterDesk = worker.Desk\n\tb, e := broker.Initialize(worker.Conf.BrokerConfig, worker.Store, worker.Index, worker.NatsConn, worker.Notifier)\n\tif e != nil {\n\t\terr = fmt.Errorf(\"[MastodonAccount]NewAccountHandler failed to initialize a mastodon broker : %s\", e)\n\t\treturn nil, err\n\t}\n\taccountHandler.broker = b\n\tvar remote *UserIdentity\n\t// retrieve data from db\n\tremote, err = accountHandler.broker.Store.RetrieveUserIdentity(userID, remoteID, true)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[MastodonAccount]NewAccountHandler failed to retrieve remote identity <%s> (user <%s>)\", remoteID, userID)\n\t\treturn\n\t}\n\tif remote.Credentials == nil {\n\t\terr = fmt.Errorf(\"[MastodonAccount]NewAccountHandler failed to retrieve credentials for remote identity <%s> (user <%s>)\", remoteID, userID)\n\t\treturn\n\t}\n\tuserAcct := strings.Split(remote.Identifier, \"@\")\n\tif len(userAcct) != 2 {\n\t\terr = fmt.Errorf(\"[MastodonAccount]NewAccountHandler failed to split user account identifier : <%s>\", remote.Identifier)\n\t\treturn\n\t}\n\tprovider, e := accountHandler.broker.Store.RetrieveProvider(\"mastodon\", userAcct[1])\n\tif e != nil {\n\t\tlog.WithError(e)\n\t\terr = fmt.Errorf(\"[MastodonAccount]NewAccountHandler failed to retrieve provider %s from db : %s\", userAcct[1], e)\n\t\treturn\n\t}\n\n\taccountHandler.userAccount = &MastodonAccount{\n\t\tdisplayName: remote.DisplayName,\n\t\tmastodonID:  remote.Infos[\"mastodon_id\"],\n\t\tremoteID:    remote.Id,\n\t\tuserID:      remote.UserId,\n\t\tusername:    userAcct[0],\n\t}\n\n\tif lastseen, ok := remote.Infos[lastSeenInfosKey]; ok {\n\t\taccountHandler.lastDMseen = lastseen\n\t} else {\n\t\taccountHandler.lastDMseen = \"0\"\n\t}\n\n\taccountHandler.mastodonClient = mastodon.NewClient(&mastodon.Config{\n\t\tAccessToken:  (*remote.Credentials)[\"oauth2accesstoken\"],\n\t\tClientID:     provider.Infos[\"client_id\"],\n\t\tClientSecret: provider.Infos[\"client_secret\"],\n\t\tServer:       provider.Infos[\"address\"],\n\t})\n\n\treturn\n}\n\n// Start begins infinite loops, until receiving stop order. This func must be call within goroutine.\nfunc (worker *AccountHandler) Start() {\n\tgo func(w *AccountHandler) {\n\t\tfor worker.broker != nil {\n\t\t\tselect {\n\t\t\tcase egress, ok := <-w.broker.Connectors.Egress:\n\t\t\t\tif !ok {\n\t\t\t\t\tif !worker.closed {\n\t\t\t\t\t\tclose(worker.broker.Connectors.Halt)\n\t\t\t\t\t\tclose(worker.AccountDesk)\n\t\t\t\t\t\tworker.closed = true\n\t\t\t\t\t}\n\t\t\t\t\tworker.MasterDesk <- DeskMessage{closeAccountOrder, worker}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\terr := w.SendDM(egress.Order)\n\t\t\t\tif err != nil {\n\t\t\t\t\tegress.Ack <- &DeliveryAck{\n\t\t\t\t\t\tErr:      true,\n\t\t\t\t\t\tResponse: err.Error(),\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tegress.Ack <- &DeliveryAck{\n\t\t\t\t\t\tErr:      false,\n\t\t\t\t\t\tResponse: \"OK\",\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase <-w.broker.Connectors.Halt:\n\t\t\t\tworker.MasterDesk <- DeskMessage{closeAccountOrder, worker}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}(worker)\n\n\tfor command := range worker.AccountDesk {\n\t\tswitch command {\n\t\tcase PollDM:\n\t\t\tworker.PollDM()\n\t\tcase Stop:\n\t\t\tworker.Stop()\n\t\t\treturn\n\t\tdefault:\n\t\t\tlog.Warnf(\"worker received unknown command number %d\", command)\n\t\t}\n\t}\n}\n\nfunc (worker *AccountHandler) Stop() {\n\tif !worker.closed {\n\t\tclose(worker.broker.Connectors.Egress)\n\t\tclose(worker.broker.Connectors.Halt)\n\t\tclose(worker.AccountDesk)\n\t\tworker.closed = true\n\t}\n}\n\n// PollDM calls Mastodon API endpoint to fetch DMs\n// it passes unseen DM to its embedded broker\nfunc (worker *AccountHandler) PollDM() {\n\t// retrieve user_identity.infos\n\taccountInfos, retrieveErr := worker.broker.Store.RetrieveRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String())\n\tif retrieveErr != nil {\n\t\tlog.WithError(retrieveErr).Warnf(\"[AccountHandler %s] PollDM failed to retrieve infos map\", worker.userAccount.remoteID.String())\n\t\treturn\n\t}\n\t// check if a sync process is running\n\tif syncing, ok := accountInfos[syncingKey]; ok && syncing != \"\" {\n\t\tstartDate, e := time.Parse(time.RFC3339, syncing)\n\t\tif e == nil && time.Since(startDate)/time.Hour < syncingTimeout {\n\t\t\tlog.Infof(\"[PollDM] avoiding concurrent sync for <%s>. Syncing in progress since %s\", worker.userAccount.remoteID, accountInfos[\"syncing\"])\n\t\t\treturn\n\t\t}\n\t}\n\t// save syncing state in db to prevent concurrent sync\n\taccountInfos[syncingKey] = time.Now().Format(time.RFC3339)\n\terr := worker.broker.Store.UpdateRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String(), accountInfos)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"[PollDM] failed to update syncing state user <%s>, identity <%s>\", worker.userAccount.userID, worker.userAccount.remoteID)\n\t\treturn\n\t}\n\n\t// do not forget to always write down last_check timestamp\n\t// and to remove syncing state before leaving\n\tdefer func() {\n\t\tif worker.broker != nil {\n\t\t\tdelete(accountInfos, syncingKey)\n\t\t\te := worker.broker.Store.UpdateRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String(), accountInfos)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM failed to update sync state in db\", worker.userAccount.remoteID.String())\n\t\t\t}\n\t\t\tlog.Infof(\"[AccountHandler %s] PollDM finished\", worker.userAccount.remoteID.String())\n\t\t\te = worker.broker.Store.TimestampRemoteLastCheck(worker.userAccount.userID.String(), worker.userAccount.remoteID.String())\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM failed to update last_check state in db\", worker.userAccount.remoteID.String())\n\t\t\t}\n\t\t}\n\t}()\n\n\t// retrieve DM list from mastodon API\n\t// 40 statuses by 40 statuses in reverse order\n\t// until lastSeenDM or end of feed\n\tpg := &mastodon.Pagination{}\n\tstatuses := make([]*mastodon.Status, 0, 40)\n\tvar getErr error\n\tfor {\n\t\tpg.Limit = 40\n\t\tpg.SinceID = \"\"\n\t\tpg.MinID = \"\"\n\t\tpage, e := worker.mastodonClient.GetTimelineDirect(context.Background(), pg) // GetTimelineDirect will update pg.maxID\n\t\tif e != nil {\n\t\t\tgetErr = e\n\t\t\tbreak\n\t\t}\n\t\tif len(page) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tstatuses = append(statuses, page...)\n\n\t\tif accountInfos[lastSeenInfosKey] != \"\" && broker.IDgreaterOrEqual(accountInfos[lastSeenInfosKey], string(pg.MaxID)) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif getErr != nil {\n\t\tlog.WithError(getErr)\n\t\te := worker.saveErrorState(accountInfos, getErr.Error())\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM failed to update sync state in db\", worker.userAccount.remoteID.String())\n\t\t}\n\t\treturn\n\t}\n\n\t// inject DM in Caliopen, reverse order\n\tbatch := Notifications.NewBatch(\"mastodon_worker\")\n\tfor i := len(statuses) - 1; i >= 0; i-- {\n\t\tif broker.IDgreaterOrEqual(string(statuses[i].ID), accountInfos[lastSeenInfosKey]) &&\n\t\t\tstring(statuses[i].ID) != accountInfos[lastSeenInfosKey] {\n\t\t\tprocessErr := worker.broker.ProcessInDM(worker.userAccount.userID, worker.userAccount.remoteID, statuses[i], true, batch)\n\t\t\tif processErr != nil {\n\t\t\t\tlog.WithError(processErr).Warnf(\"[AccountHandler %s] ProcessInDM failed for status: %+v\", worker.userAccount.remoteID.String(), statuses[i])\n\t\t\t} else {\n\t\t\t\taccountInfos[lastSeenInfosKey] = string(statuses[i].ID)\n\t\t\t}\n\t\t}\n\t}\n\n\taccountInfos[lastSyncInfosKey] = time.Now().Format(time.RFC3339)\n\n\t// sync terminated without error, cleanup userIdentity infos map\n\tdelete(accountInfos, lastErrorKey)\n\tdelete(accountInfos, errorsCountKey)\n\tdelete(accountInfos, dateFirstErrorKey)\n\tdelete(accountInfos, dateLastErrorKey)\n\terr = worker.broker.Store.UpdateRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String(), accountInfos)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[AccountHandler %s] ProcessInDM failed to update InfosMap at end of process\", worker.userAccount.remoteID.String())\n\t}\n\n\t// notify new messages\n\tbatch.Save(worker.broker.Notifier, \"\", LongLived)\n}\n\nfunc (worker *AccountHandler) dmNotSeen(status mastodon.Status) bool {\n\treturn worker.lastDMseen < string(status.ID)\n}\n\n// SendDM delivers DM to Mastodon endpoint and give back Mastodon's response to broker.\nfunc (worker *AccountHandler) SendDM(order BrokerOrder) error {\n\t// make use of broker to marshal a direct message\n\tbrokerPort := make(chan *broker.DMpayload)\n\tvar brokerMessage *broker.DMpayload\n\n\tgo worker.broker.ProcessOutDM(order, brokerPort)\n\n\tselect {\n\tcase brokerMessage = <-brokerPort:\n\t\tif brokerMessage.Err != nil {\n\t\t\treturn brokerMessage.Err\n\t\t}\n\tcase <-time.After(10 * time.Second):\n\t\treturn errors.New(\"[SendDM] broker timeout\")\n\t}\n\n\t// deliver DM through Mastodon API\n\n\tstatus, errResponse := worker.mastodonClient.PostStatus(context.Background(), brokerMessage.Toot)\n\tif errResponse != nil {\n\t\tbrokerMessage.Response <- broker.MastodonDeliveryAck{\n\t\t\tPayload:  status,\n\t\t\tErr:      true,\n\t\t\tResponse: errResponse.Error(),\n\t\t}\n\t\treturn errResponse\n\t}\n\n\t// give back Mastodon's reply to broker for it finishes its job\n\tbrokerMessage.Response <- broker.MastodonDeliveryAck{\n\t\tPayload: status,\n\t\tErr:     false,\n\t}\n\n\tselect {\n\tcase brokerMessage = <-brokerPort:\n\t\tif brokerMessage.Err != nil {\n\t\t\treturn brokerMessage.Err\n\t\t}\n\t\treturn nil\n\tcase <-time.After(10 * time.Second):\n\t\treturn errors.New(\"[SendDM] broker timeout\")\n\t}\n}\n\n// getAccountName returns Mastodon account screen name given a Mastodon account ID\n// screen name is retrieve either from worker's cache or Mastodon API\n// returns empty string if it fails.\nfunc (worker *AccountHandler) getAccountName(accountID string) (accountName string) {\n\t// useless ?\n\treturn \"\"\n}\n\n// isDMUnique returns true if Mastodon Direct Message id is not found within user's messages index\n// if seeking fails for any reason, true is returned anyway to allow duplication\nfunc (worker *AccountHandler) isDMUnique(dmID string) bool {\n\tmessageID, err := worker.broker.Store.SeekMessageByExternalRef(worker.userAccount.userID.String(), dmID, \"\")\n\tif err != nil || bytes.Equal(messageID.Bytes(), EmptyUUID.Bytes()) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (worker *AccountHandler) saveErrorState(infos map[string]string, err string) error {\n\n\t// ensure errors data fields are present\n\tif _, ok := infos[lastErrorKey]; !ok {\n\t\tinfos[lastErrorKey] = \"\"\n\t}\n\tif _, ok := infos[dateFirstErrorKey]; !ok {\n\t\tinfos[dateFirstErrorKey] = \"\"\n\t}\n\tif _, ok := infos[dateLastErrorKey]; !ok {\n\t\tinfos[dateLastErrorKey] = \"\"\n\t}\n\tif _, ok := infos[errorsCountKey]; !ok {\n\t\tinfos[errorsCountKey] = \"0\"\n\t}\n\n\t// log last error\n\tinfos[lastErrorKey] = \"Mastodon connection failed : \" + err\n\tlog.Warnf(\"Mastodon connection failed for remote identity %s : %s\", worker.userAccount.remoteID, err)\n\t// increment counter\n\tcount, _ := strconv.Atoi(infos[errorsCountKey])\n\tcount++\n\tinfos[errorsCountKey] = strconv.Itoa(count)\n\n\t// update dates\n\tlastDate := time.Now()\n\tvar firstDate time.Time\n\tfirstDate, _ = time.Parse(time.RFC3339, infos[dateFirstErrorKey])\n\tif firstDate.IsZero() {\n\t\tfirstDate = lastDate\n\t}\n\tinfos[dateFirstErrorKey] = firstDate.Format(time.RFC3339)\n\tinfos[dateLastErrorKey] = lastDate.Format(time.RFC3339)\n\n\t// check failuresThreshold\n\tif lastDate.Sub(firstDate)/time.Hour > failuresThreshold {\n\t\t// disable remote identity\n\t\terr := worker.broker.Store.UpdateUserIdentity(&UserIdentity{\n\t\t\tUserId: worker.userAccount.userID,\n\t\t\tId:     worker.userAccount.remoteID,\n\t\t}, map[string]interface{}{\n\t\t\t\"Status\": \"inactive\",\n\t\t})\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[saveErrorState] failed to deactivate remote identity %s for user %s\", worker.userAccount.remoteID, worker.userAccount.userID)\n\t\t}\n\t\t// send nats message to idpoller to stop polling\n\t\torder := RemoteIDNatsMessage{\n\t\t\tIdentityId: worker.userAccount.remoteID.String(),\n\t\t\tOrder:      \"delete\",\n\t\t\tProtocol:   \"mastodon\",\n\t\t\tUserId:     worker.userAccount.userID.String(),\n\t\t}\n\t\tjorder, jerr := json.Marshal(order)\n\t\tif jerr == nil {\n\t\t\te := worker.broker.NatsConn.Publish(worker.broker.Config.NatsTopicPollerCache, jorder)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[saveErrorState] failed to publish delete order to idpoller\")\n\t\t\t}\n\t\t}\n\t}\n\n\t// update UserIdentity in db\n\treturn worker.broker.Store.UpdateRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String(), infos)\n\n}\n\n// ByAscID implements sort interface\ntype ByAscID []mastodon.Status\n\nfunc (bri ByAscID) Len() int {\n\treturn len(bri)\n}\n\nfunc (bri ByAscID) Less(i, j int) bool {\n\treturn bri[i].ID < bri[j].ID\n}\n\nfunc (bri ByAscID) Swap(i, j int) {\n\tbri[i], bri[j] = bri[j], bri[i]\n}\n"
  },
  {
    "path": "src/backend/protocols/go.mastodon/cmd/mastodonworker/cli_cmds/root.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tverbose bool\n\tversion bool\n\tRootCmd = &cobra.Command{\n\t\tUse:   \"mastodond\",\n\t\tShort: \"Mastodon API daemon\",\n\t\tLong:  `mastodond is a daemon that connect to Mastodon accounts on one side and to our NATS queues on other side to executes IO operations with Mastodon API`,\n\t\tRun:   nil,\n\t}\n)\n\nconst __version__ = \"0.23.0\"\n\nfunc init() {\n\tcobra.OnInitialize()\n\tRootCmd.PersistentFlags().BoolVarP(&verbose, \"verbose\", \"v\", false,\n\t\t\"print out more debug information\")\n\tRootCmd.PersistentFlags().BoolVarP(&version, \"version\", \"V\", false,\n\t\t\"print out the version of this program\")\n\tRootCmd.Run = func(cmd *cobra.Command, args []string) {\n\t\tif version {\n\t\t\tlog.Infof(\"mastodond version %s\", __version__)\n\t\t}\n\t\tif len(args) == 0 {\n\t\t\tcmd.Help()\n\t\t}\n\t}\n\tRootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {\n\t\tif verbose {\n\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t} else {\n\t\t\tlog.SetLevel(log.InfoLevel)\n\t\t}\n\t}\n\tRootCmd.AddCommand(versionCmd)\n}\n\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print the version number of mastodond\",\n\tLong:  `All software has versions. This is mastodond's`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tlog.Infof(\"mastodond version %s\", __version__)\n\t},\n}\n"
  },
  {
    "path": "src/backend/protocols/go.mastodon/cmd/mastodonworker/cli_cmds/start.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\tmast \"github.com/CaliOpen/Caliopen/src/backend/protocols/go.mastodon\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n)\n\nconst (\n\tshutdownTimeout = 3 // minutes to wait before forcing shutdown\n)\n\nvar (\n\tconfigPath      string\n\tconfigFile      string\n\tpidFile         string\n\tsignalChannel   chan os.Signal\n\tmastodonWorkers []*mast.Worker\n\n\tstartCmd = &cobra.Command{\n\t\tUse:   \"start\",\n\t\tShort: \"Starts a pool of mastodon API worker(s)\",\n\t\tRun:   start,\n\t}\n)\n\nfunc init() {\n\tstartCmd.PersistentFlags().StringVarP(&configFile, \"config\", \"c\",\n\t\t\"mastodonworker\", \"Name of the configuration file, without extension. (YAML, TOML, JSON… allowed)\")\n\tstartCmd.PersistentFlags().StringVarP(&configPath, \"configpath\", \"\",\n\t\t\"../../../../configs/\", \"Main config file path.\")\n\tstartCmd.PersistentFlags().StringVarP(&pidFile, \"pid-file\", \"p\",\n\t\t\"/var/run/caliopen_mastodond.pid\", \"Path to the pid file\")\n\n\tRootCmd.AddCommand(startCmd)\n\tsignalChannel = make(chan os.Signal, 1)\n}\n\nfunc start(cmd *cobra.Command, args []string) {\n\n\tvar conf mast.WorkerConfig\n\terr := readConfig(&conf)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"Error while reading config\")\n\t}\n\t// Write out our PID\n\tif len(pidFile) > 0 {\n\t\tif f, err := os.Create(pidFile); err == nil {\n\t\t\tdefer f.Close()\n\t\t\tif _, err := f.WriteString(fmt.Sprintf(\"%d\", os.Getpid())); err == nil {\n\t\t\t\tf.Sync()\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"Error while writing pidFile (%s)\", pidFile)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.WithError(err).Warnf(\"Error while creating pidFile (%s)\", pidFile)\n\t\t}\n\t}\n\n\t// init and start worker(s)\n\tvar i uint8\n\tmastodonWorkers = make([]*mast.Worker, conf.Workers)\n\tfor i = 0; i < conf.Workers; i++ {\n\t\tlog.Infof(\"Initializing mastodon worker %d\", i)\n\t\tmastodonWorkers[i], err = mast.InitWorker(conf, verbose, randomIdentifier())\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatal(\"failed to init worker\")\n\t\t}\n\t\tgo mastodonWorkers[i].Start()\n\t}\n\t// listening mode, waiting for nats orders to add/update workers or os sig to shutdown\n\tsigHandler(mastodonWorkers)\n\n}\n\n// ReadConfig which should be called at startup, or when a SIG_HUP is caught\nfunc readConfig(config *mast.WorkerConfig) error {\n\t// load in the main config. Reading from YAML, TOML, JSON, HCL and Java properties config files\n\tv := viper.New()\n\tv.SetConfigName(configFile)                           // name of config file (without extension)\n\tv.AddConfigPath(configPath)                           // path to look for the config file in\n\tv.AddConfigPath(\"$CALIOPENROOT/src/backend/configs/\") // call multiple times to add many search paths\n\tv.AddConfigPath(\".\")                                  // optionally look for config in the working directory\n\n\terr := v.ReadInConfig() // Find and read the config file*/\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not read main config file <%s>.\", configFile)\n\t\treturn err\n\t}\n\terr = v.Unmarshal(config)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not parse config file: <%s>\", configFile)\n\t\treturn err\n\t}\n\n\treturn nil\n}\nfunc sigHandler(workers []*mast.Worker) {\n\tsignal.Notify(signalChannel, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGKILL)\n\n\tfor sig := range signalChannel {\n\n\t\tif sig == syscall.SIGHUP {\n\t\t\t// TODO: handle SIGHUP\n\t\t} else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT || sig == syscall.SIGKILL {\n\t\t\tlog.Infof(\"Shutdown signal caught. Gracefully halting %d workers within 3 minutes timeframe…\", len(workers))\n\t\t\twg := new(sync.WaitGroup)\n\t\t\twg.Add(len(workers))\n\t\t\tfor i := range workers {\n\t\t\t\tworkers[i].HaltGroup = wg\n\t\t\t}\n\t\t\t// timeout mechanism to avoid infinite wait\n\t\t\tc := make(chan struct{})\n\t\t\tgo func() {\n\t\t\t\tdefer close(c)\n\t\t\t\twg.Wait()\n\t\t\t}()\n\t\t\tselect {\n\t\t\tcase <-c:\n\t\t\t\tlog.Info(\"Shutdown completed, exiting\")\n\t\t\t\tos.Exit(0)\n\t\t\tcase <-time.After(shutdownTimeout * time.Minute):\n\t\t\t\tlog.Warn(\"Shutdown timeout, force exiting\")\n\t\t\t\tos.Exit(0)\n\t\t\t}\n\t\t} else {\n\t\t\tos.Exit(0)\n\t\t}\n\t}\n}\n\nfunc randomIdentifier() string {\n\tvar buf [4]byte\n\t_, err := io.ReadFull(rand.Reader, buf[:])\n\tif err != nil {\n\t\treturn \"00000000\"\n\t}\n\treturn fmt.Sprintf(\"%x\", buf[:])\n}\n"
  },
  {
    "path": "src/backend/protocols/go.mastodon/cmd/mastodonworker/main.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/protocols/go.mastodon/cmd/mastodonworker/cli_cmds\"\n\t\"os\"\n)\n\nfunc main() {\n\tif err := cmd.RootCmd.Execute(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(-1)\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.mastodon/messaging.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage mastodonworker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/brokers/go.mastodon\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/pkg/errors\"\n\t\"time\"\n)\n\n// WorkerMsgHandler handles message coming from idpoller\nfunc (w *Worker) WorkerMsgHandler(msg *nats.Msg) {\n\tmessage := BrokerOrder{}\n\terr := json.Unmarshal(msg.Data, &message)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"Unable to unmarshal message from NATS. Payload was <%s>\", string(msg.Data))\n\t\treturn\n\t}\n\tswitch message.Order {\n\tcase noPendingJobErr:\n\t\treturn\n\tcase \"sync\":\n\t\tlog.Infof(\"received sync order for remote mastodon ID %s\", message.IdentityId)\n\t\tif accountWorker := w.getOrCreateHandler(message.UserId, message.IdentityId); accountWorker != nil {\n\t\t\tselect {\n\t\t\tcase accountWorker.AccountDesk <- PollDM:\n\t\t\t\tlog.Infof(\"[DMmsgHandler] ordering to pollDM for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\tcase <-time.After(30 * time.Second):\n\t\t\t\tlog.Warnf(\"[DMmsgHandler] worker's desk is full for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Warnf(\"[DMmsgHandler] failed to get a worker for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] failed to get a worker\"))\n\t\t}\n\tcase \"reload_worker\":\n\t\tlog.Infof(\"received reload_worker order for remote mastodon ID %s\", message.IdentityId)\n\t\t//TODO: order to force refreshing cache data for an account\n\tcase \"add_worker\":\n\t\tlog.Infof(\"received add_worker order for remote mastodon ID %s\", message.IdentityId)\n\t\taccountWorker := w.getOrCreateHandler(message.UserId, message.IdentityId)\n\t\tif accountWorker == nil {\n\t\t\tlog.WithError(err).Warnf(\"[WorkerMsgHandler] failed to create new worker for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] failed to get a worker\"))\n\t\t}\n\tcase \"remove_worker\":\n\t\tlog.Infof(\"received remove_worker order for remote mastodon ID %s\", message.IdentityId)\n\t\t// TODO\n\t}\n}\n\n// DMmsgHandler handles messages coming on topic dedicated to DM management\nfunc (w *Worker) DMmsgHandler(msg *nats.Msg) {\n\tmessage := BrokerOrder{}\n\terr := json.Unmarshal(msg.Data, &message)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"Unable to unmarshal message from NATS. Payload was <%s>\", string(msg.Data))\n\t\treturn\n\t}\n\tswitch message.Order {\n\tcase \"deliver\":\n\t\tif accountWorker := w.getOrCreateHandler(message.UserId, message.IdentityId); accountWorker != nil {\n\t\t\tcom := mastodonbroker.NatsCom{\n\t\t\t\tOrder: message,\n\t\t\t\tAck:   make(chan *DeliveryAck),\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase accountWorker.broker.Connectors.Egress <- com:\n\t\t\t\tlog.Infof(\"[DMmsgHandler] sending DM for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\t\t// non-blocking wait for delivery ack\n\t\t\t\tgo func(com mastodonbroker.NatsCom) {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase resp := <-com.Ack:\n\t\t\t\t\t\tif resp.Err {\n\t\t\t\t\t\t\tw.natsReplyError(msg, errors.New(resp.Response))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tack := DeliveryAck{\n\t\t\t\t\t\t\t\tErr:      false,\n\t\t\t\t\t\t\t\tResponse: \"OK\",\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tjson_resp, _ := json.Marshal(ack)\n\t\t\t\t\t\t\tw.NatsConn.Publish(msg.Reply, json_resp)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase <-time.After(30 * time.Second):\n\t\t\t\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] timeout waiting broker delivery ack\"))\n\t\t\t\t\t}\n\t\t\t\t}(com)\n\t\t\tcase <-time.After(30 * time.Second):\n\t\t\t\tlog.Warnf(\"[DMmsgHandler] worker's Egress connectors is full for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] failed to get a worker\"))\n\t\t\t}\n\t\t} else {\n\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] failed to get a worker\"))\n\t\t}\n\tdefault:\n\t\tw.natsReplyError(msg, errors.New(\"not implemented\"))\n\t}\n}\n\nfunc (w *Worker) natsReplyError(msg *nats.Msg, err error) {\n\tlog.WithError(err).Warnf(\"mastodon broker [outbound] : error when processing incoming nats message : %v\", *msg)\n\n\tack := DeliveryAck{\n\t\tErr:      true,\n\t\tResponse: fmt.Sprintf(\"failed to send message with error « %s » \", err), //TODO\n\t}\n\n\tjson_resp, _ := json.Marshal(ack)\n\tw.NatsConn.Publish(msg.Reply, json_resp)\n}\n"
  },
  {
    "path": "src/backend/protocols/go.mastodon/worker.go",
    "content": "package mastodonworker\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tbroker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.mastodon\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/index/elasticsearch\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype (\n\tWorker struct {\n\t\tAccountHandlers map[string]*AccountHandler // one worker per active Mastodon account\n\t\tHaltGroup       *sync.WaitGroup\n\t\tIndex           backends.LDAIndex\n\t\tId              string\n\t\tNatsConn        *nats.Conn\n\t\tNatsSubs        []*nats.Subscription\n\t\tNotifier        *Notifications.Notifier\n\t\tStore           backends.LDAStore\n\t\tWorkersGuard    *sync.RWMutex\n\t\tConf            WorkerConfig\n\t\tDesk            chan DeskMessage // chan to allow accountworkers to communicate with their master\n\t}\n\n\tWorkerConfig struct {\n\t\tWorkers           uint8               `mapstructure:\"workers\"`\n\t\tMastodonAppKey    string              `mapstructure:\"mastodon_app_key\"`\n\t\tMastodonAppSecret string              `mapstructure:\"mastodon_app_secret\"`\n\t\tBrokerConfig      broker.BrokerConfig `mapstructure:\"BrokerConfig\"`\n\t}\n\n\tDeskMessage struct {\n\t\torder   string\n\t\taccount *AccountHandler\n\t}\n)\n\nconst (\n\tfailuresThreshold = 72 // how many hours to wait before disabling a faulty remote.\n\tnoPendingJobErr   = \"no pending job\"\n\tpollThrottling    = 10 * time.Second\n\tneedJobOrderStr   = `{\"worker\":\"%s\",\"order\":{\"order\":\"need_job\"}}`\n\tcloseAccountOrder = \"close_account\"\n)\n\nfunc InitWorker(conf WorkerConfig, verboseLog bool, id string) (worker *Worker, err error) {\n\n\tif verboseLog {\n\t\tlog.SetLevel(log.DebugLevel)\n\t}\n\n\tworker = &Worker{\n\t\tAccountHandlers: map[string]*AccountHandler{},\n\t\tConf:            conf,\n\t\tId:              id,\n\t\tWorkersGuard:    new(sync.RWMutex),\n\t}\n\n\t// init Store\n\tswitch conf.BrokerConfig.StoreName {\n\tcase \"cassandra\":\n\t\tc := store.CassandraConfig{\n\t\t\tHosts:       conf.BrokerConfig.StoreConfig.Hosts,\n\t\t\tKeyspace:    conf.BrokerConfig.StoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(conf.BrokerConfig.StoreConfig.Consistency),\n\t\t\tSizeLimit:   conf.BrokerConfig.StoreConfig.SizeLimit,\n\t\t\tUseVault:    conf.BrokerConfig.StoreConfig.UseVault,\n\t\t}\n\t\tif conf.BrokerConfig.StoreConfig.ObjectStore == \"s3\" {\n\t\t\tc.WithObjStore = true\n\t\t\tc.Endpoint = conf.BrokerConfig.StoreConfig.OSSConfig.Endpoint\n\t\t\tc.AccessKey = conf.BrokerConfig.StoreConfig.OSSConfig.AccessKey\n\t\t\tc.SecretKey = conf.BrokerConfig.StoreConfig.OSSConfig.SecretKey\n\t\t\tc.RawMsgBucket = conf.BrokerConfig.StoreConfig.OSSConfig.Buckets[\"raw_messages\"]\n\t\t\tc.AttachmentBucket = conf.BrokerConfig.StoreConfig.OSSConfig.Buckets[\"temporary_attachments\"]\n\t\t\tc.Location = conf.BrokerConfig.StoreConfig.OSSConfig.Location\n\t\t}\n\t\tif conf.BrokerConfig.StoreConfig.UseVault {\n\t\t\tc.HVaultConfig.Url = conf.BrokerConfig.StoreConfig.VaultConfig.Url\n\t\t\tc.HVaultConfig.Username = conf.BrokerConfig.StoreConfig.VaultConfig.Username\n\t\t\tc.HVaultConfig.Password = conf.BrokerConfig.StoreConfig.VaultConfig.Password\n\t\t}\n\t\tb, e := store.InitializeCassandraBackend(c)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlog.WithError(err).Warnf(\"[MastodonWorker] initialization of %s backend failed\", conf.BrokerConfig.StoreName)\n\t\t\treturn\n\t\t}\n\n\t\tworker.Store = backends.LDAStore(b) // type conversion to LDA interface\n\tdefault:\n\t\tlog.Warnf(\"[MastodonWorker] unknown store backend: %s\", conf.BrokerConfig.StoreName)\n\t\terr = errors.New(\"[MastodonWorker] unknown store backend\")\n\t\treturn\n\n\t}\n\n\t// init Index\n\tswitch conf.BrokerConfig.LDAConfig.IndexName {\n\tcase \"elasticsearch\":\n\t\tc := index.ElasticSearchConfig{\n\t\t\tUrls: conf.BrokerConfig.LDAConfig.IndexConfig.Urls,\n\t\t}\n\t\ti, e := index.InitializeElasticSearchIndex(c)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlog.WithError(err).Warnf(\"[MastodonBroker] initialization of %s backend failed\", conf.BrokerConfig.IndexName)\n\t\t\treturn\n\t\t}\n\n\t\tworker.Index = backends.LDAIndex(i) // type conversion to LDA interface\n\tdefault:\n\t\tlog.Warnf(\"[MastodonBroker] unknown index backend: %s\", conf.BrokerConfig.LDAConfig.IndexName)\n\t\terr = errors.New(\"[MastodonBroker] unknown index backend\")\n\t\treturn\n\t}\n\n\tworker.NatsConn, err = nats.Connect(conf.BrokerConfig.NatsURL)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[MastodonBroker] initalization of NATS connexion failed\")\n\t\treturn\n\t}\n\tcaliopenConfig := CaliopenConfig{\n\t\tNotifierConfig: conf.BrokerConfig.LDAConfig.NotifierConfig,\n\t\tNatsConfig: NatsConfig{\n\t\t\tUrl: conf.BrokerConfig.NatsURL,\n\t\t},\n\t\tRESTstoreConfig: RESTstoreConfig{\n\t\t\tBackendName:  conf.BrokerConfig.StoreName,\n\t\t\tConsistency:  conf.BrokerConfig.StoreConfig.Consistency,\n\t\t\tHosts:        conf.BrokerConfig.StoreConfig.Hosts,\n\t\t\tKeyspace:     conf.BrokerConfig.StoreConfig.Keyspace,\n\t\t\tOSSConfig:    conf.BrokerConfig.StoreConfig.OSSConfig,\n\t\t\tObjStoreType: conf.BrokerConfig.StoreConfig.ObjectStore,\n\t\t\tSizeLimit:    conf.BrokerConfig.StoreConfig.SizeLimit,\n\t\t},\n\t\tRESTindexConfig: RESTIndexConfig{\n\t\t\tHosts:     conf.BrokerConfig.LDAConfig.IndexConfig.Urls,\n\t\t\tIndexName: conf.BrokerConfig.LDAConfig.IndexName,\n\t\t},\n\t}\n\tworker.Notifier = Notifications.NewNotificationsFacility(caliopenConfig, worker.NatsConn)\n\n\t// init Nats connector\n\tworker.NatsConn, err = nats.Connect(conf.BrokerConfig.NatsURL)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"[MastodonWorker] initialization of NATS connexion failed\")\n\t}\n\tworker.NatsSubs = make([]*nats.Subscription, 1)\n\tworker.NatsSubs[0], err = worker.NatsConn.QueueSubscribe(conf.BrokerConfig.NatsTopicDMs, conf.BrokerConfig.NatsQueue, worker.DMmsgHandler)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"[MastodonWorker] initialization of NATS fetcher subscription failed\")\n\t}\n\terr = worker.NatsConn.Flush()\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"[MastodonWorker] initialization of NATS fetcher subscription failed\")\n\t}\n\n\tworker.Desk = make(chan DeskMessage, 2)\n\n\treturn worker, nil\n}\n\nfunc (worker *Worker) Start(throttling ...time.Duration) {\n\tvar throttle time.Duration\n\tif len(throttling) == 1 && throttling[0] != 0 {\n\t\tthrottle = throttling[0]\n\t} else {\n\t\tthrottle = pollThrottling\n\t}\n\n\tgo func() {\n\t\tfor msg := range worker.Desk {\n\t\t\tswitch msg.order {\n\t\t\tcase closeAccountOrder:\n\t\t\t\tif msg.account != nil {\n\t\t\t\t\tworker.RemoveAccountHandler(msg.account)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tlog.Debugf(\"[MastodonWorker] received unknown order « %s » from account %s\", msg.order, msg.account.userAccount.userID.String()+msg.account.userAccount.remoteID.String())\n\t\t\t}\n\t\t}\n\t}()\n\n\t// start throttled jobs polling\n\tlog.Infof(\"Mastodon worker %s starting with %d sec throttling\", worker.Id, throttle/time.Second)\n\tfor {\n\t\tstart := time.Now()\n\t\trequestOrder := []byte(fmt.Sprintf(needJobOrderStr, worker.Id))\n\t\tlog.Infof(\"Mastodon worker %s is requesting jobs to idpoller\", worker.Id)\n\t\tresp, err := worker.NatsConn.Request(worker.Conf.BrokerConfig.NatsTopicPoller, requestOrder, time.Minute)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[worker %s] failed to request pending jobs on nats\", worker.Id)\n\t\t} else {\n\t\t\tworker.WorkerMsgHandler(resp)\n\t\t}\n\t\t// check for interrupt after job is finished\n\t\tif worker.HaltGroup != nil {\n\t\t\tworker.stop()\n\t\t\tbreak\n\t\t}\n\t\telapsed := time.Now().Sub(start)\n\t\tif elapsed < throttle {\n\t\t\ttime.Sleep(throttle - elapsed)\n\t\t}\n\t}\n}\n\nfunc (worker *Worker) stop() {\n\tfor _, w := range worker.AccountHandlers {\n\t\tw.AccountDesk <- Stop\n\t}\n\tfor _, sub := range worker.NatsSubs {\n\t\tsub.Unsubscribe()\n\t}\n\tworker.NatsConn.Close()\n\tworker.Store.Close()\n\tworker.Index.Close()\n\tworker.HaltGroup.Done()\n\tclose(worker.Desk)\n\tlog.Infof(\"worker %s stopped\", worker.Id)\n}\n\n// getOrCreateHandler returns a pointer to a worker already in cache\n// or tries to create a new worker for the remote identity if not.\n// returns nil if get or create failed.\nfunc (w *Worker) getOrCreateHandler(userId, remoteId string) *AccountHandler {\n\tw.WorkersGuard.RLock()\n\tif accountHandler, ok := w.AccountHandlers[userId+remoteId]; ok {\n\t\tw.WorkersGuard.RUnlock()\n\t\treturn accountHandler\n\t} else {\n\t\tw.WorkersGuard.RUnlock()\n\t\tlog.Infof(\"[getOrCreateHandler] failed to retrieve registered worker for remote %s (user %s). Trying to add one.\", remoteId, userId)\n\t\tif userId == \"\" || remoteId == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\taccountHandler, err := NewAccountHandler(userId, remoteId, *w)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[getOrCreateHandler] failed to create new worker for remote %s (user %s)\", remoteId, userId)\n\t\t\treturn nil\n\t\t}\n\t\tw.RegisterAccountHandler(accountHandler)\n\t\tgo accountHandler.Start()\n\t\treturn accountHandler\n\n\t}\n}\n\nfunc (w *Worker) RegisterAccountHandler(accountHandler *AccountHandler) {\n\tworkerKey := accountHandler.userAccount.userID.String() + accountHandler.userAccount.remoteID.String()\n\t// stop & remove handler first if it's already registered\n\tw.WorkersGuard.RLock()\n\tregisteredHandler, ok := w.AccountHandlers[workerKey]\n\tw.WorkersGuard.RUnlock()\n\tif ok {\n\t\tw.RemoveAccountHandler(registeredHandler)\n\t}\n\tw.WorkersGuard.Lock()\n\tw.AccountHandlers[workerKey] = accountHandler\n\tw.WorkersGuard.Unlock()\n}\n\nfunc (w *Worker) RemoveAccountHandler(accountHandler *AccountHandler) {\n\tworkerKey := accountHandler.userAccount.userID.String() + accountHandler.userAccount.remoteID.String()\n\tw.WorkersGuard.Lock()\n\taccountHandler.Stop()\n\tdelete(w.AccountHandlers, workerKey)\n\tw.WorkersGuard.Unlock()\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/cmd/caliopen_lmtpd/cli_cmds/root.go",
    "content": "package cmd\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tverbose bool\n\tversion bool\n\tRootCmd = &cobra.Command{\n\t\tUse:   \"caliopen_lmtpd\",\n\t\tShort: \"LMTP daemon\",\n\t\tLong:  `LMTP daemon for the purpose of bridging MTAs to our local delivery agent.`,\n\t\tRun:   nil,\n\t}\n)\n\nconst __version__ = \"0.23.0\"\n\nfunc init() {\n\tcobra.OnInitialize()\n\tRootCmd.PersistentFlags().BoolVarP(&verbose, \"verbose\", \"v\", false,\n\t\t\"print out more debug information\")\n\tRootCmd.PersistentFlags().BoolVarP(&version, \"version\", \"V\", false,\n\t\t\"print out the version of this program\")\n\tRootCmd.Run = func(cmd *cobra.Command, args []string) {\n\t\tif version {\n\t\t\tlog.Infof(\"Caliopen SMTPd version %s\", __version__)\n\t\t}\n\t}\n\tRootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {\n\t\tif verbose {\n\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t} else {\n\t\t\tlog.SetLevel(log.InfoLevel)\n\t\t}\n\t}\n\tRootCmd.AddCommand(versionCmd)\n}\n\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print the version number of Caliopen SMTPd\",\n\tLong:  `All software has versions. This is Caliopen SMTPd's`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tlog.Infof(\"Caliopen SMTPd version %s\", __version__)\n\t},\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/cmd/caliopen_lmtpd/cli_cmds/serve.go",
    "content": "package cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tcsmtp \"github.com/CaliOpen/Caliopen/src/backend/protocols/go.smtp\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\nvar (\n\tconfigPath    string\n\tconfigFile    string\n\tpidFile       string\n\tsignalChannel chan os.Signal // for trapping SIG_HUP\n\tcmdConfig     CmdConfig\n\n\tserveCmd = &cobra.Command{\n\t\tUse:   \"serve\",\n\t\tShort: \"Start the caliopen LMTP server\",\n\t\tRun:   serve,\n\t}\n)\n\nfunc init() {\n\tserveCmd.PersistentFlags().StringVarP(&configFile, \"config\", \"c\",\n\t\t\"lmtp\", \"Name of the configuration file, without extension. (YAML, TOML, JSON… allowed)\")\n\tserveCmd.PersistentFlags().StringVarP(&configPath, \"configpath\", \"\",\n\t\t\"../../../../configs/\", \"Main config file path.\")\n\tserveCmd.PersistentFlags().StringVarP(&pidFile, \"pid-file\", \"p\",\n\t\t\"/var/run/caliopen_lmtpd.pid\", \"Path to the pid file\")\n\n\tRootCmd.AddCommand(serveCmd)\n\tsignalChannel = make(chan os.Signal, 1)\n\tcmdConfig = CmdConfig{}\n}\n\nfunc sigHandler() {\n\t// handle SIGHUP for reloading the configuration while running\n\tsignal.Notify(signalChannel, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGKILL)\n\n\tfor sig := range signalChannel {\n\n\t\tif sig == syscall.SIGHUP {\n\t\t\terr := readConfig(&cmdConfig)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Error(\"Error while ReadConfig (reload)\")\n\t\t\t} else {\n\t\t\t\tlog.Info(\"Configuration is reloaded\")\n\t\t\t}\n\t\t\t// TODO: reinitialize\n\t\t} else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT {\n\t\t\tlog.Infof(\"Shutdown signal caught\")\n\t\t\tcsmtp.ShutdownServer()\n\t\t\tlog.Infof(\"Shutdown completed, exiting.\")\n\t\t\tos.Exit(0)\n\t\t} else {\n\t\t\tos.Exit(0)\n\t\t}\n\t}\n}\n\nfunc serve(cmd *cobra.Command, args []string) {\n\n\terr := readConfig(&cmdConfig)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"Error while reading config\")\n\t}\n\t// Write out our PID\n\tif len(pidFile) > 0 {\n\t\tif f, err := os.Create(pidFile); err == nil {\n\t\t\tdefer f.Close()\n\t\t\tif _, err := f.WriteString(fmt.Sprintf(\"%d\", os.Getpid())); err == nil {\n\t\t\t\tf.Sync()\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"Error while writing pidFile (%s)\", pidFile)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.WithError(err).Warnf(\"Error while creating pidFile (%s)\", pidFile)\n\t\t}\n\t}\n\n\terr = csmtp.InitializeServer(csmtp.SMTPConfig(cmdConfig))\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"Failed to init LMTP server\")\n\t}\n\n\tgo csmtp.StartServer()\n\n\tsigHandler()\n}\n\ntype CmdConfig csmtp.SMTPConfig\n\n// ReadConfig which should be called at startup, or when a SIG_HUP is caught\nfunc readConfig(config *CmdConfig) error {\n\t// load in the main config. Reading from YAML, TOML, JSON, HCL and Java properties config files\n\tv := viper.New()\n\tv.SetConfigName(configFile)                           // name of config file (without extension)\n\tv.AddConfigPath(configPath)                           // path to look for the config file in\n\tv.AddConfigPath(\"$CALIOPENROOT/src/backend/configs/\") // call multiple times to add many search paths\n\tv.AddConfigPath(\".\")                                  // optionally look for config in the working directory\n\n\terr := v.ReadInConfig() // Find and read the config file*/\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not read main config file <%s>.\", configFile)\n\t\treturn err\n\t}\n\terr = v.Unmarshal(config)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not parse config file: <%s>\", configFile)\n\t\treturn err\n\t}\n\n\tif len(config.AppConfig.AllowedHosts) == 0 {\n\t\treturn errors.New(\"Empty `allowed_hosts` is not allowed\")\n\t}\n\tconfig.AppConfig.AppVersion = __version__\n\tconfig.LDAConfig.AppVersion = config.AppConfig.AppVersion\n\tconfig.LDAConfig.PrimaryMailHost = config.AppConfig.PrimaryMailHost\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/cmd/caliopen_lmtpd/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/protocols/go.smtp/cmd/caliopen_lmtpd/cli_cmds\"\n\t\"os\"\n)\n\nfunc main() {\n\tif err := cmd.RootCmd.Execute(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(-1)\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/config.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage caliopen_smtp\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n)\n\ntype (\n\tSMTPConfig struct {\n\t\tAppConfig AppConfig\n\t\tLDAConfig LDAConfig\n\t}\n\n\tAppConfig struct {\n\t\tAppVersion      string         `mapstructure:\"version\"`\n\t\tServers         []ServerConfig `mapstructure:\"inbound_servers\"`\n\t\tAllowedHosts    []string       `mapstructure:\"allowed_hosts\"`\n\t\tPrimaryMailHost string         `mapstructure:\"primary_mail_host\"`\n\t\tSubmitAddress   string         `mapstructure:\"submit_address\"`\n\t\tSubmitPort      int            `mapstructure:\"submit_port\"`\n\t\tSubmitUser      string         `mapstructure:\"submit_user\"`\n\t\tSubmitPassword  string         `mapstructure:\"submit_password\"`\n\t\tOutWorkers      int            `mapstructure:\"submit_workers\"`\n\t}\n\n\t// ServerConfig specifies config options for a single smtp server\n\tServerConfig struct {\n\t\tIsEnabled       bool   `mapstructure:\"is_enabled\"`\n\t\tHostname        string `mapstructure:\"host_name\"`\n\t\tAllowedHosts    []string\n\t\tMaxSize         uint64 `mapstructure:\"max_size\"` //max size for emails\n\t\tPrivateKeyFile  string `mapstructure:\"private_key_file\"`\n\t\tPublicKeyFile   string `mapstructure:\"public_key_file\"`\n\t\tTimeout         int    `mapstructure:\"timeout\"`\n\t\tListenInterface string `mapstructure:\"listen_interface\"`\n\t\tStartTLSOn      bool   `mapstructure:\"start_tls_on,omitempty\"`\n\t\tTLSAlwaysOn     bool   `mapstructure:\"tls_always_on,omitempty\"`\n\t\tMaxClients      int    `mapstructure:\"max_clients\"`\n\t}\n)\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/envelope.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage caliopen_smtp\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Envelope holds a message\ntype SmtpEnvelope struct {\n\tSender     string\n\tRecipients []string\n\tData       []byte\n}\n\n// AddReceivedLine prepends a Received header to the Data\nfunc (env *SmtpEnvelope) AddReceivedLine(peer Peer) {\n\n\ttlsDetails := \"\"\n\n\ttlsVersions := map[uint16]string{\n\t\ttls.VersionSSL30: \"SSL3.0\",\n\t\ttls.VersionTLS10: \"TLS1.0\",\n\t\ttls.VersionTLS11: \"TLS1.1\",\n\t\ttls.VersionTLS12: \"TLS1.2\",\n\t}\n\n\tif peer.TLS != nil {\n\t\ttlsDetails = fmt.Sprintf(\n\t\t\t\"\\r\\n\\t(version=%s cipher=0x%x);\",\n\t\t\ttlsVersions[peer.TLS.Version],\n\t\t\tpeer.TLS.CipherSuite,\n\t\t)\n\t}\n\n\tline := wrap([]byte(fmt.Sprintf(\n\t\t\"Received: from %s [%s] by %s with %s;%s\\r\\n\\t%s\\r\\n\",\n\t\tpeer.HeloName,\n\t\tstrings.Split(peer.Addr.String(), \":\")[0],\n\t\tpeer.ServerName,\n\t\tpeer.Protocol,\n\t\ttlsDetails,\n\t\ttime.Now().Format(\"Mon Jan 2 15:04:05 -0700 2006\"),\n\t)))\n\n\tenv.Data = append(env.Data, line...)\n\n\t// Move the new Received line up front\n\n\tcopy(env.Data[len(line):], env.Data[0:len(env.Data)-len(line)])\n\tcopy(env.Data, line)\n\n}\n\n// Wrap a byte slice paragraph for use in SMTP header\nfunc wrap(sl []byte) []byte {\n\tlength := 0\n\tfor i := 0; i < len(sl); i++ {\n\t\tif length > 76 && sl[i] == ' ' {\n\t\t\tsl = append(sl, 0, 0)\n\t\t\tcopy(sl[i+2:], sl[i:])\n\t\t\tsl[i] = '\\r'\n\t\t\tsl[i+1] = '\\n'\n\t\t\tsl[i+2] = '\\t'\n\t\t\ti += 2\n\t\t\tlength = 0\n\t\t}\n\t\tif sl[i] == '\\n' {\n\t\t\tlength = 0\n\t\t}\n\t\tlength++\n\t}\n\treturn sl\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/lda.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n//\n// SMTP server to handle in/out emails from/to MTAs\n\npackage caliopen_smtp\n\nimport (\n\tbroker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.emails\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n//Local Delivery Agent, in charge of IO between SMTP server and our email broker\ntype Lda struct {\n\tConfig           SMTPConfig\n\tbroker           *broker.EmailBroker\n\tbrokerConnectors broker.EmailBrokerConnectors\n\tinboundListener  *Server\n\toutboundListener *submitter\n}\n\nfunc (lda *Lda) initialize(config SMTPConfig) (err error) {\n\tlda.Config = config\n\tlda.broker, lda.brokerConnectors, err = broker.Initialize(config.LDAConfig)\n\treturn err\n}\n\nfunc (lda *Lda) start() (err error) {\n\n\t// Check that max clients is not greater than system open file limit.\n\tfileLimit := getFileLimit()\n\tif fileLimit > 0 {\n\t\tmaxClients := 0\n\t\tfor _, s := range lda.Config.AppConfig.Servers {\n\t\t\tmaxClients += s.MaxClients\n\t\t}\n\t\tif maxClients > fileLimit {\n\t\t\tlog.Fatalf(\"Combined max clients for all servers (%d) is greater than open file limit (%d). \"+\n\t\t\t\t\"Please increase your open file limit or decrease max clients.\", maxClients, fileLimit)\n\t\t}\n\t}\n\n\t// launch outbound chan listener\n\tlda.outboundListener, err = lda.newSubmitter()\n\tif err != nil {\n\t\tlog.Warn(\"LDA submitter initialization failed\")\n\t}\n\tgo func() {\n\t\tlda.runSubmitterAgent()\n\t}()\n\n\treturn\n}\n\nfunc (lda *Lda) shutdown() error {\n\tlda.broker.ShutDown()\n\treturn nil\n}\n\nfunc getFileLimit() int {\n\tcmd := exec.Command(\"ulimit\", \"-n\")\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn -1\n\t}\n\n\tlimit, err := strconv.Atoi(strings.TrimSpace(string(out)))\n\tif err != nil {\n\t\treturn -1\n\t}\n\n\treturn limit\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/lmtpd.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage caliopen_smtp\n\nimport (\n\t\"bufio\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"time\"\n)\n\n// Server defines the parameters for running the SMTP server\ntype Server struct {\n\tListenAddr string //(default: \"localhost:2525\")\n\n\tHostname       string // Server hostname. (default: \"localhost.localdomain\")\n\tWelcomeMessage string // Initial server banner. (default: \"<hostname> ESMTP ready.\")\n\n\tReadTimeout  time.Duration // Socket timeout for read operations. (default: 60s)\n\tWriteTimeout time.Duration // Socket timeout for write operations. (default: 60s)\n\tDataTimeout  time.Duration // Socket timeout for DATA command (default: 5m)\n\n\tMaxConnections int // Max concurrent connections, use -1 to disable. (default: 100)\n\tMaxMessageSize int // Max message size in bytes. (default: 10240000)\n\tMaxRecipients  int // Max RCPT TO calls for each envelope. (default: 100)\n\n\t// New e-mails are handed off to this function.\n\t// Can be left empty for a NOOP server.\n\t// If an error is returned, it will be reported in the SMTP session.\n\tHandler func(peer Peer, env SmtpEnvelope) error\n\n\t// Enable various checks during the SMTP session.\n\t// Can be left empty for no restrictions.\n\t// If an error is returned, it will be reported in the SMTP session.\n\t// Use the Error struct for access to error codes.\n\tConnectionChecker func(peer Peer) error              // Called upon new connection.\n\tHeloChecker       func(peer Peer, name string) error // Called after HELO/EHLO.\n\tSenderChecker     func(peer Peer, addr string) error // Called after MAIL FROM.\n\tRecipientChecker  func(peer Peer, addr string) error // Called after each RCPT TO.\n\n\t// Enable PLAIN/LOGIN authentication, only available after STARTTLS.\n\t// Can be left empty for no authentication support.\n\tAuthenticator func(peer Peer, username, password string) error\n\n\tEnableXCLIENT bool // Enable XCLIENT support (default: false)\n\n\tTLSConfig *tls.Config // Enable STARTTLS support.\n\tForceTLS  bool        // Force STARTTLS usage.\n\n\tProtocolLogger *log.Logger\n}\n\n// Protocol represents the protocol used in the SMTP session\ntype Protocol string\n\nconst (\n\tSMTP  Protocol = \"SMTP\"\n\tESMTP          = \"ESMTP\"\n)\n\n// Peer represents the client connecting to the server\ntype Peer struct {\n\tHeloName   string               // Server name used in HELO/EHLO command\n\tUsername   string               // Username from authentication, if authenticated\n\tPassword   string               // Password from authentication, if authenticated\n\tProtocol   Protocol             // Protocol used, SMTP or ESMTP\n\tServerName string               // A copy of Server.Hostname\n\tAddr       net.Addr             // Network address\n\tTLS        *tls.ConnectionState // TLS Connection details, if on TLS\n}\n\n// Error represents an Error reported in the SMTP session.\ntype Error struct {\n\tCode    int    // The integer error code\n\tMessage string // The error message\n}\n\n// Error returns a string representation of the SMTP error\nfunc (e Error) Error() string { return fmt.Sprintf(\"%d %s\", e.Code, e.Message) }\n\ntype session struct {\n\tserver *Server\n\n\tpeer     Peer\n\tenvelope *SmtpEnvelope\n\n\tconn net.Conn\n\n\treader  *bufio.Reader\n\twriter  *bufio.Writer\n\tscanner *bufio.Scanner\n\n\ttls bool\n}\n\nfunc (srv *Server) newSession(c net.Conn) (s *session) {\n\n\ts = &session{\n\t\tserver: srv,\n\t\tconn:   c,\n\t\treader: bufio.NewReader(c),\n\t\twriter: bufio.NewWriter(c),\n\t\tpeer: Peer{\n\t\t\tAddr:       c.RemoteAddr(),\n\t\t\tServerName: srv.Hostname,\n\t\t},\n\t}\n\n\ts.scanner = bufio.NewScanner(s.reader)\n\n\treturn\n\n}\n\n// Feed Server struct with config\nfunc (srv *Server) initialize(conf SMTPConfig) error {\n\tif lda == nil {\n\t\treturn errors.New(\"unable to init smtpd : LDA is nil\")\n\t}\n\t//TODO: handle multiple servers\n\tsrv.ListenAddr = conf.AppConfig.Servers[0].ListenInterface\n\tsrv.Hostname = conf.AppConfig.Servers[0].Hostname\n\tsrv.ForceTLS = conf.AppConfig.Servers[0].TLSAlwaysOn\n\tsrv.MaxConnections = conf.AppConfig.Servers[0].MaxClients\n\tsrv.MaxMessageSize = int(conf.AppConfig.Servers[0].MaxSize)\n\tsrv.Handler = lda.handler\n\n\treturn nil\n}\n\nfunc (srv *Server) start() (err error) {\n\tgo srv.ListenAndServe()\n\treturn\n}\n\nfunc (srv *Server) shutdown() (err error) {\n\treturn nil\n}\n\n// ListenAndServe starts the SMTP server and listens on the address provided\nfunc (srv *Server) ListenAndServe() error {\n\n\tsrv.configureDefaults()\n\n\tl, err := net.Listen(\"tcp\", srv.ListenAddr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn srv.Serve(l)\n}\n\n// Serve starts the SMTP server and listens on the Listener provided\nfunc (srv *Server) Serve(l net.Listener) error {\n\n\tsrv.configureDefaults()\n\n\tdefer l.Close()\n\n\tvar limiter chan struct{}\n\n\tif srv.MaxConnections > 0 {\n\t\tlimiter = make(chan struct{}, srv.MaxConnections)\n\t} else {\n\t\tlimiter = nil\n\t}\n\n\tfor {\n\n\t\tconn, e := l.Accept()\n\t\tif e != nil {\n\t\t\tif ne, ok := e.(net.Error); ok && ne.Temporary() {\n\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn e\n\t\t}\n\n\t\tsession := srv.newSession(conn)\n\n\t\tif limiter != nil {\n\t\t\tgo func() {\n\t\t\t\tselect {\n\t\t\t\tcase limiter <- struct{}{}:\n\t\t\t\t\tsession.serve()\n\t\t\t\t\t<-limiter\n\t\t\t\tdefault:\n\t\t\t\t\tsession.reject()\n\t\t\t\t}\n\t\t\t}()\n\t\t} else {\n\t\t\tgo session.serve()\n\t\t}\n\n\t}\n\n}\n\nfunc (srv *Server) configureDefaults() {\n\n\tif srv.MaxMessageSize == 0 {\n\t\tsrv.MaxMessageSize = 10240000\n\t}\n\n\tif srv.MaxConnections == 0 {\n\t\tsrv.MaxConnections = 100\n\t}\n\n\tif srv.MaxRecipients == 0 {\n\t\tsrv.MaxRecipients = 100\n\t}\n\n\tif srv.ReadTimeout == 0 {\n\t\tsrv.ReadTimeout = time.Second * 60\n\t}\n\n\tif srv.WriteTimeout == 0 {\n\t\tsrv.WriteTimeout = time.Second * 60\n\t}\n\n\tif srv.DataTimeout == 0 {\n\t\tsrv.DataTimeout = time.Minute * 5\n\t}\n\n\tif srv.ForceTLS && srv.TLSConfig == nil {\n\t\tlog.Fatal(\"Cannot use ForceTLS with no TLSConfig\")\n\t}\n\n\tif srv.Hostname == \"\" {\n\t\tsrv.Hostname = \"localhost.localdomain\"\n\t}\n\n\tif srv.WelcomeMessage == \"\" {\n\t\tsrv.WelcomeMessage = fmt.Sprintf(\"%s ESMTP ready.\", srv.Hostname)\n\t}\n\n}\n\nfunc (session *session) serve() {\n\n\tdefer session.close()\n\n\tsession.welcome()\n\n\tfor {\n\n\t\tfor session.scanner.Scan() {\n\t\t\tsession.handle(session.scanner.Text())\n\t\t}\n\n\t\terr := session.scanner.Err()\n\n\t\tif err == bufio.ErrTooLong {\n\n\t\t\tsession.reply(500, \"Line too long\")\n\n\t\t\t// Advance reader to the next newline\n\n\t\t\tsession.reader.ReadString('\\n')\n\t\t\tsession.scanner = bufio.NewScanner(session.reader)\n\n\t\t\t// Reset and have the client start over.\n\n\t\t\tsession.reset()\n\n\t\t\tcontinue\n\t\t}\n\n\t\tbreak\n\t}\n\n}\n\nfunc (session *session) reject() {\n\tsession.reply(421, \"Too busy. Try again later.\")\n\tsession.close()\n}\n\nfunc (session *session) reset() {\n\tsession.envelope = nil\n}\n\nfunc (session *session) welcome() {\n\n\tif session.server.ConnectionChecker != nil {\n\t\terr := session.server.ConnectionChecker(session.peer)\n\t\tif err != nil {\n\t\t\tsession.error(err)\n\t\t\tsession.close()\n\t\t\treturn\n\t\t}\n\t}\n\n\tsession.reply(220, session.server.WelcomeMessage)\n\n}\n\nfunc (session *session) reply(code int, message string) {\n\tif session.server.ProtocolLogger != nil {\n\t\tsession.server.ProtocolLogger.Printf(\"%s > %d %s\", session.conn.RemoteAddr(), code, message)\n\t}\n\tfmt.Fprintf(session.writer, \"%d %s\\r\\n\", code, message)\n\tsession.flush()\n}\n\nfunc (session *session) flush() {\n\tsession.conn.SetWriteDeadline(time.Now().Add(session.server.WriteTimeout))\n\tsession.writer.Flush()\n\tsession.conn.SetReadDeadline(time.Now().Add(session.server.ReadTimeout))\n}\n\nfunc (session *session) error(err error) {\n\tif smtpdError, ok := err.(Error); ok {\n\t\tsession.reply(smtpdError.Code, smtpdError.Message)\n\t} else {\n\t\tsession.reply(502, fmt.Sprintf(\"%s\", err))\n\t}\n}\n\nfunc (session *session) extensions() []string {\n\n\textensions := []string{\n\t\tfmt.Sprintf(\"SIZE %d\", session.server.MaxMessageSize),\n\t\t\"8BITMIME\",\n\t\t\"PIPELINING\",\n\t}\n\n\tif session.server.EnableXCLIENT {\n\t\textensions = append(extensions, \"XCLIENT\")\n\t}\n\n\tif session.server.TLSConfig != nil && !session.tls {\n\t\textensions = append(extensions, \"STARTTLS\")\n\t}\n\n\tif session.server.Authenticator != nil && session.tls {\n\t\textensions = append(extensions, \"AUTH PLAIN LOGIN\")\n\t}\n\n\treturn extensions\n\n}\n\nfunc (session *session) deliver() error {\n\tif session.server.Handler != nil {\n\t\treturn session.server.Handler(session.peer, *session.envelope)\n\t}\n\treturn nil\n}\n\nfunc (session *session) close() {\n\tsession.writer.Flush()\n\ttime.Sleep(200 * time.Millisecond)\n\tsession.conn.Close()\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/oauth.go",
    "content": "package caliopen_smtp\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/smtp\"\n)\n\n// The XOAUTH2 mechanism name.\nconst Xoauth2 = \"XOAUTH2\"\n\n// An XOAUTH2 error.\ntype Xoauth2Response struct {\n\tStatus  string `json:\"status\"`\n\tSchemes string `json:\"schemes\"`\n\tScope   string `json:\"scope\"`\n}\n\n// Implements error.\nfunc (err *Xoauth2Response) Error() string {\n\treturn fmt.Sprintf(\"XOAUTH2 authentication error (%v)\", err.Status)\n}\n\n// xoauth2Client is the smtp.Auth interface that implements the XOauth2 authentication mechanism.\ntype Xoauth2Client struct {\n\tUsername string\n\tToken    string\n}\n\nfunc (a *Xoauth2Client) Start(server *smtp.ServerInfo) (string, []byte, error) {\n\treturn Xoauth2, []byte(\"user=\" + a.Username + \"\\x01auth=Bearer \" + a.Token + \"\\x01\\x01\"), nil\n}\n\nfunc (a *Xoauth2Client) Next(fromServer []byte, more bool) ([]byte, error) {\n\tif more {\n\t\txoauth2Err := &Xoauth2Response{}\n\t\tif err := json.Unmarshal(fromServer, xoauth2Err); err != nil {\n\t\t\treturn nil, err\n\t\t} else {\n\t\t\treturn nil, xoauth2Err\n\t\t}\n\t}\n\treturn nil, nil\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/protocol.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage caliopen_smtp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/textproto\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype command struct {\n\tline   string\n\taction string\n\tfields []string\n\tparams []string\n}\n\nfunc parseLine(line string) (cmd command) {\n\n\tcmd.line = line\n\tcmd.fields = strings.Fields(line)\n\n\tif len(cmd.fields) > 0 {\n\t\tcmd.action = strings.ToUpper(cmd.fields[0])\n\t\tif len(cmd.fields) > 1 {\n\t\t\tcmd.params = strings.Split(cmd.fields[1], \":\")\n\t\t}\n\t}\n\n\treturn\n\n}\n\nfunc (session *session) handle(line string) {\n\tif session.server.ProtocolLogger != nil {\n\t\tsession.server.ProtocolLogger.Printf(\"%s < %s\", session.conn.RemoteAddr(), line)\n\t}\n\tcmd := parseLine(line)\n\n\t// Commands are dispatched to the appropriate handler functions.\n\t// If a network error occurs during handling, the handler should\n\t// just return and let the error be handled on the next read.\n\n\tswitch cmd.action {\n\n\tcase \"HELO\":\n\t\tsession.handleHELO(cmd)\n\t\treturn\n\n\tcase \"EHLO\":\n\t\tsession.handleEHLO(cmd)\n\t\treturn\n\n\tcase \"MAIL\":\n\t\tsession.handleMAIL(cmd)\n\t\treturn\n\n\tcase \"RCPT\":\n\t\tsession.handleRCPT(cmd)\n\t\treturn\n\n\tcase \"STARTTLS\":\n\t\tsession.handleSTARTTLS(cmd)\n\t\treturn\n\n\tcase \"DATA\":\n\t\tsession.handleDATA(cmd)\n\t\treturn\n\n\tcase \"RSET\":\n\t\tsession.handleRSET(cmd)\n\t\treturn\n\n\tcase \"NOOP\":\n\t\tsession.handleNOOP(cmd)\n\t\treturn\n\n\tcase \"QUIT\":\n\t\tsession.handleQUIT(cmd)\n\t\treturn\n\n\tcase \"AUTH\":\n\t\tsession.handleAUTH(cmd)\n\t\treturn\n\n\tcase \"XCLIENT\":\n\t\tsession.handleXCLIENT(cmd)\n\t\treturn\n\n\t}\n\n\tsession.reply(502, \"Unsupported command.\")\n\n}\n\nfunc (session *session) handleHELO(cmd command) {\n\n\tif len(cmd.fields) < 2 {\n\t\tsession.reply(502, \"Missing parameter\")\n\t\treturn\n\t}\n\n\tif session.peer.HeloName != \"\" {\n\t\t// Reset envelope in case of duplicate HELO\n\t\tsession.reset()\n\t}\n\n\tif session.server.HeloChecker != nil {\n\t\terr := session.server.HeloChecker(session.peer, cmd.fields[1])\n\t\tif err != nil {\n\t\t\tsession.error(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tsession.peer.HeloName = cmd.fields[1]\n\tsession.peer.Protocol = SMTP\n\tsession.reply(250, \"Go ahead\")\n\n\treturn\n\n}\n\nfunc (session *session) handleEHLO(cmd command) {\n\n\tif len(cmd.fields) < 2 {\n\t\tsession.reply(502, \"Missing parameter\")\n\t\treturn\n\t}\n\n\tif session.peer.HeloName != \"\" {\n\t\t// Reset envelope in case of duplicate EHLO\n\t\tsession.reset()\n\t}\n\n\tif session.server.HeloChecker != nil {\n\t\terr := session.server.HeloChecker(session.peer, cmd.fields[1])\n\t\tif err != nil {\n\t\t\tsession.error(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tsession.peer.HeloName = cmd.fields[1]\n\tsession.peer.Protocol = ESMTP\n\n\tfmt.Fprintf(session.writer, \"250-%s\\r\\n\", session.server.Hostname)\n\n\textensions := session.extensions()\n\n\tif len(extensions) > 1 {\n\t\tfor _, ext := range extensions[:len(extensions)-1] {\n\t\t\tfmt.Fprintf(session.writer, \"250-%s\\r\\n\", ext)\n\t\t}\n\t}\n\n\tsession.reply(250, extensions[len(extensions)-1])\n\n\treturn\n\n}\n\nfunc (session *session) handleMAIL(cmd command) {\n\tif len(cmd.params) != 2 || strings.ToUpper(cmd.params[0]) != \"FROM\" {\n\t\tsession.reply(502, \"Invalid syntax.\")\n\t\treturn\n\t}\n\n\tif session.peer.HeloName == \"\" {\n\t\tsession.reply(502, \"Please introduce yourself first.\")\n\t\treturn\n\t}\n\n\tif !session.tls && session.server.ForceTLS {\n\t\tsession.reply(502, \"Please turn on TLS by issuing a STARTTLS command.\")\n\t\treturn\n\t}\n\n\tif session.envelope != nil {\n\t\tsession.reply(502, \"Duplicate MAIL\")\n\t\treturn\n\t}\n\n\taddr, err := parseAddress(cmd.params[1])\n\n\tif err != nil {\n\t\tsession.reply(502, \"Ill-formatted e-mail address\")\n\t\treturn\n\t}\n\n\tif session.server.SenderChecker != nil {\n\t\terr = session.server.SenderChecker(session.peer, addr)\n\t\tif err != nil {\n\t\t\tsession.error(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tsession.envelope = &SmtpEnvelope{\n\t\tSender: addr,\n\t}\n\n\tsession.reply(250, \"Go ahead\")\n\n\treturn\n\n}\n\nfunc (session *session) handleRCPT(cmd command) {\n\tif len(cmd.params) != 2 || strings.ToUpper(cmd.params[0]) != \"TO\" {\n\t\tsession.reply(502, \"Invalid syntax.\")\n\t\treturn\n\t}\n\n\tif session.envelope == nil {\n\t\tsession.reply(502, \"Missing MAIL FROM command.\")\n\t\treturn\n\t}\n\n\tif len(session.envelope.Recipients) >= session.server.MaxRecipients {\n\t\tsession.reply(452, \"Too many recipients\")\n\t\treturn\n\t}\n\n\taddr, err := parseAddress(cmd.params[1])\n\n\tif err != nil {\n\t\tsession.reply(502, \"Ill-formatted e-mail address\")\n\t\treturn\n\t}\n\n\tif session.server.RecipientChecker != nil {\n\t\terr = session.server.RecipientChecker(session.peer, addr)\n\t\tif err != nil {\n\t\t\tsession.error(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tsession.envelope.Recipients = append(session.envelope.Recipients, addr)\n\n\tsession.reply(250, \"Go ahead\")\n\n\treturn\n\n}\n\nfunc (session *session) handleSTARTTLS(cmd command) {\n\n\tif session.tls {\n\t\tsession.reply(502, \"Already running in TLS\")\n\t\treturn\n\t}\n\n\tif session.server.TLSConfig == nil {\n\t\tsession.reply(502, \"TLS not supported\")\n\t\treturn\n\t}\n\n\ttlsConn := tls.Server(session.conn, session.server.TLSConfig)\n\tsession.reply(220, \"Go ahead\")\n\n\tif err := tlsConn.Handshake(); err != nil {\n\t\tsession.reply(550, \"Handshake error\")\n\t\treturn\n\t}\n\n\t// Reset envelope as a new EHLO/HELO is required after STARTTLS\n\tsession.reset()\n\n\t// Reset deadlines on the underlying connection before I replace it\n\t// with a TLS connection\n\tsession.conn.SetDeadline(time.Time{})\n\n\t// Replace connection with a TLS connection\n\tsession.conn = tlsConn\n\tsession.reader = bufio.NewReader(tlsConn)\n\tsession.writer = bufio.NewWriter(tlsConn)\n\tsession.scanner = bufio.NewScanner(session.reader)\n\tsession.tls = true\n\n\t// Save connection state on peer\n\tstate := tlsConn.ConnectionState()\n\tsession.peer.TLS = &state\n\n\t// Flush the connection to set new timeout deadlines\n\tsession.flush()\n\n\treturn\n\n}\n\nfunc (session *session) handleDATA(cmd command) {\n\n\tif session.envelope == nil || len(session.envelope.Recipients) == 0 {\n\t\tsession.reply(502, \"Missing RCPT TO command.\")\n\t\treturn\n\t}\n\n\tsession.reply(354, \"Go ahead. End your data with <CR><LF>.<CR><LF>\")\n\tsession.conn.SetDeadline(time.Now().Add(session.server.DataTimeout))\n\n\tdata := &bytes.Buffer{}\n\treader := textproto.NewReader(session.reader).DotReader()\n\n\t_, err := io.CopyN(data, reader, int64(session.server.MaxMessageSize))\n\n\tif err == io.EOF {\n\n\t\t// EOF was reached before MaxMessageSize\n\t\t// Accept and deliver message\n\n\t\tsession.envelope.Data = data.Bytes()\n\n\t\tif err := session.deliver(); err != nil {\n\t\t\tsession.error(err)\n\t\t} else {\n\t\t\tsession.reply(250, \"Thank you.\")\n\t\t}\n\n\t\tsession.reset()\n\n\t}\n\n\tif err != nil {\n\t\t// Network error, ignore\n\t\treturn\n\t}\n\n\t// Discard the rest and report an error.\n\t_, err = io.Copy(ioutil.Discard, reader)\n\n\tif err != nil {\n\t\t// Network error, ignore\n\t\treturn\n\t}\n\n\tsession.reply(552, fmt.Sprintf(\n\t\t\"Message exceeded max message size of %d bytes\",\n\t\tsession.server.MaxMessageSize,\n\t))\n\n\tsession.reset()\n\n\treturn\n\n}\n\nfunc (session *session) handleRSET(cmd command) {\n\tsession.reset()\n\tsession.reply(250, \"Go ahead\")\n\treturn\n}\n\nfunc (session *session) handleNOOP(cmd command) {\n\tsession.reply(250, \"Go ahead\")\n\treturn\n}\n\nfunc (session *session) handleQUIT(cmd command) {\n\tsession.reply(221, \"OK, bye\")\n\tsession.close()\n\treturn\n}\n\nfunc (session *session) handleAUTH(cmd command) {\n\tif len(cmd.fields) < 2 {\n\t\tsession.reply(502, \"Invalid syntax.\")\n\t\treturn\n\t}\n\n\tif session.server.Authenticator == nil {\n\t\tsession.reply(502, \"AUTH not supported.\")\n\t\treturn\n\t}\n\n\tif session.peer.HeloName == \"\" {\n\t\tsession.reply(502, \"Please introduce yourself first.\")\n\t\treturn\n\t}\n\n\tif !session.tls {\n\t\tsession.reply(502, \"Cannot AUTH in plain text mode. Use STARTTLS.\")\n\t\treturn\n\t}\n\n\tmechanism := strings.ToUpper(cmd.fields[1])\n\n\tusername := \"\"\n\tpassword := \"\"\n\n\tswitch mechanism {\n\n\tcase \"PLAIN\":\n\n\t\tauth := \"\"\n\n\t\tif len(cmd.fields) < 3 {\n\t\t\tsession.reply(334, \"Give me your credentials\")\n\t\t\tif !session.scanner.Scan() {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tauth = session.scanner.Text()\n\t\t} else {\n\t\t\tauth = cmd.fields[2]\n\t\t}\n\n\t\tdata, err := base64.StdEncoding.DecodeString(auth)\n\n\t\tif err != nil {\n\t\t\tsession.reply(502, \"Couldn't decode your credentials\")\n\t\t\treturn\n\t\t}\n\n\t\tparts := bytes.Split(data, []byte{0})\n\n\t\tif len(parts) != 3 {\n\t\t\tsession.reply(502, \"Couldn't decode your credentials\")\n\t\t\treturn\n\t\t}\n\n\t\tusername = string(parts[1])\n\t\tpassword = string(parts[2])\n\n\tcase \"LOGIN\":\n\n\t\tsession.reply(334, \"VXNlcm5hbWU6\")\n\n\t\tif !session.scanner.Scan() {\n\t\t\treturn\n\t\t}\n\n\t\tbyteUsername, err := base64.StdEncoding.DecodeString(session.scanner.Text())\n\n\t\tif err != nil {\n\t\t\tsession.reply(502, \"Couldn't decode your credentials\")\n\t\t\treturn\n\t\t}\n\n\t\tsession.reply(334, \"UGFzc3dvcmQ6\")\n\n\t\tif !session.scanner.Scan() {\n\t\t\treturn\n\t\t}\n\n\t\tbytePassword, err := base64.StdEncoding.DecodeString(session.scanner.Text())\n\n\t\tif err != nil {\n\t\t\tsession.reply(502, \"Couldn't decode your credentials\")\n\t\t\treturn\n\t\t}\n\n\t\tusername = string(byteUsername)\n\t\tpassword = string(bytePassword)\n\n\tdefault:\n\n\t\tsession.reply(502, \"Unknown authentication mechanism\")\n\t\treturn\n\n\t}\n\n\terr := session.server.Authenticator(session.peer, username, password)\n\tif err != nil {\n\t\tsession.error(err)\n\t\treturn\n\t}\n\n\tsession.peer.Username = username\n\tsession.peer.Password = password\n\n\tsession.reply(235, \"OK, you are now authenticated\")\n\n}\n\nfunc (session *session) handleXCLIENT(cmd command) {\n\tif len(cmd.fields) < 2 {\n\t\tsession.reply(502, \"Invalid syntax.\")\n\t\treturn\n\t}\n\n\tif !session.server.EnableXCLIENT {\n\t\tsession.reply(550, \"XCLIENT not enabled\")\n\t\treturn\n\t}\n\n\tvar (\n\t\tnewHeloName          = \"\"\n\t\tnewAddr     net.IP   = nil\n\t\tnewTCPPort  uint64   = 0\n\t\tnewUsername          = \"\"\n\t\tnewProto    Protocol = \"\"\n\t)\n\n\tfor _, item := range cmd.fields[1:] {\n\n\t\tparts := strings.Split(item, \"=\")\n\n\t\tif len(parts) != 2 {\n\t\t\tsession.reply(502, \"Couldn't decode the command.\")\n\t\t\treturn\n\t\t}\n\n\t\tname := parts[0]\n\t\tvalue := parts[1]\n\n\t\tswitch name {\n\n\t\tcase \"NAME\":\n\t\t\t// Unused in smtpd package\n\t\t\tcontinue\n\n\t\tcase \"HELO\":\n\t\t\tnewHeloName = value\n\t\t\tcontinue\n\n\t\tcase \"ADDR\":\n\t\t\tnewAddr = net.ParseIP(value)\n\t\t\tcontinue\n\n\t\tcase \"PORT\":\n\t\t\tvar err error\n\t\t\tnewTCPPort, err = strconv.ParseUint(value, 10, 16)\n\t\t\tif err != nil {\n\t\t\t\tsession.reply(502, \"Couldn't decode the command.\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcontinue\n\n\t\tcase \"LOGIN\":\n\t\t\tnewUsername = value\n\t\t\tcontinue\n\n\t\tcase \"PROTO\":\n\t\t\tif value == \"SMTP\" {\n\t\t\t\tnewProto = SMTP\n\t\t\t} else if value == \"ESMTP\" {\n\t\t\t\tnewProto = ESMTP\n\t\t\t}\n\t\t\tcontinue\n\n\t\tdefault:\n\t\t\tsession.reply(502, \"Couldn't decode the command.\")\n\t\t\treturn\n\t\t}\n\n\t}\n\n\ttcpAddr, ok := session.peer.Addr.(*net.TCPAddr)\n\tif !ok {\n\t\tsession.reply(502, \"Unsupported network connection\")\n\t\treturn\n\t}\n\n\tif newHeloName != \"\" {\n\t\tsession.peer.HeloName = newHeloName\n\t}\n\n\tif newAddr != nil {\n\t\ttcpAddr.IP = newAddr\n\t}\n\n\tif newTCPPort != 0 {\n\t\ttcpAddr.Port = int(newTCPPort)\n\t}\n\n\tif newUsername != \"\" {\n\t\tsession.peer.Username = newUsername\n\t}\n\n\tif newProto != \"\" {\n\t\tsession.peer.Protocol = newProto\n\t}\n\n\tsession.welcome()\n\n}\n\nfunc parseAddress(src string) (string, error) {\n\n\tif len(src) == 0 || src[0] != '<' || src[len(src)-1] != '>' {\n\t\treturn \"\", fmt.Errorf(\"Ill-formatted e-mail address: %s\", src)\n\t}\n\n\tif strings.Count(src, \"@\") > 1 {\n\t\treturn \"\", fmt.Errorf(\"Ill-formatted e-mail address: %s\", src)\n\t}\n\n\treturn src[1 : len(src)-1], nil\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/receiver.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage caliopen_smtp\n\nimport (\n\t\"bytes\"\n\tbroker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.emails\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"time\"\n)\n\n// handler is called by smtpd for each incoming email\nfunc (lda *Lda) handler(peer Peer, ev SmtpEnvelope) error {\n\tvar raw_email bytes.Buffer\n\traw_email.WriteString(string(ev.Data))\n\n\temailMessage := EmailMessage{\n\t\tEmail: &Email{\n\t\t\tSmtpMailFrom: []string{ev.Sender}, //TODO: handle multiple senders\n\t\t\tSmtpRcpTo:    ev.Recipients,\n\t\t\tRaw:          raw_email,\n\t\t},\n\t\tMessage: &Message{},\n\t}\n\tbatch := Notifications.NewBatch(\"smtp\")\n\tincoming := &broker.SmtpEmail{\n\t\tEmailMessage: &emailMessage,\n\t\tResponse:     make(chan *broker.EmailDeliveryAck),\n\t\tBatch:        batch,\n\t}\n\tdefer close(incoming.Response)\n\n\tlda.brokerConnectors.Ingress <- incoming\n\n\tselect {\n\tcase response := <-incoming.Response:\n\t\tif response.Err {\n\t\t\treturn Error{\n\t\t\t\tCode:    554,\n\t\t\t\tMessage: response.Response,\n\t\t\t}\n\t\t}\n\t\treturn nil\n\tcase <-time.After(30 * time.Second):\n\t\treturn Error{\n\t\t\tCode:    554,\n\t\t\tMessage: \"LDA timeout\",\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/server.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage caliopen_smtp\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n)\n\nvar (\n\tlda    *Lda\n\tdaemon *Server\n)\n\n// load configuration into package's vars above\nfunc InitializeServer(config SMTPConfig) (err error) {\n\tlda = new(Lda)\n\tdaemon = new(Server)\n\terr = lda.initialize(config)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = daemon.initialize(config)\n\treturn\n}\n\n// listen & serve\nfunc StartServer() {\n\terr := lda.start()\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"LDA failed to start\")\n\t} else {\n\t\tlog.Infof(\"Caliopen lda started\")\n\t}\n\terr = daemon.start()\n\tif err != nil {\n\t\tlda.shutdown()\n\t\tlog.WithError(err).Fatal(\"smtpd failed to start\")\n\t}\n\tlog.Infof(\"Caliopen smtpd started\")\n\n}\n\nfunc ShutdownServer() (err error) {\n\terr = lda.shutdown()\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"Error when shutting down LDA\")\n\t}\n\terr = daemon.shutdown()\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"Error when shutting down smtpd\")\n\t}\n\treturn\n}\n"
  },
  {
    "path": "src/backend/protocols/go.smtp/submitter.go",
    "content": "// Copyleft (ɔ) 2017 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage caliopen_smtp\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\tbroker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.emails\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"gopkg.in/gomail.v2\"\n\t\"io\"\n\t\"net/smtp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype submitter struct {\n\tconfig          LDAConfig\n\tworkersCountMux sync.Mutex\n\trunningWorkers  int\n\tsubmitChan      chan *broker.SmtpEmail\n}\n\ntype smtpSender struct {\n\tsmtpClient\n\td *gomail.Dialer\n}\n\ntype smtpClient interface {\n\tHello(string) error\n\tExtension(string) (bool, string)\n\tStartTLS(*tls.Config) error\n\tAuth(smtp.Auth) error\n\tMail(string) error\n\tRcpt(string) error\n\tData() (io.WriteCloser, error)\n\tQuit() error\n\tClose() error\n}\n\nfunc (lda *Lda) newSubmitter() (submit *submitter, err error) {\n\n\tsubmit = &submitter{}\n\tsubmit.config = lda.Config.LDAConfig\n\tsubmit.submitChan = make(chan *broker.SmtpEmail)\n\n\treturn\n}\n\nfunc (lda *Lda) runSubmitterAgent() {\n\n\tfor email := range lda.brokerConnectors.Egress {\n\t\tgo func(email *broker.SmtpEmail) {\n\t\t\tlda.outboundListener.workersCountMux.Lock()\n\t\t\tif lda.outboundListener.runningWorkers < lda.Config.AppConfig.OutWorkers {\n\t\t\t\tgo lda.OutboundWorker()\n\t\t\t\tlda.outboundListener.runningWorkers++\n\t\t\t}\n\t\t\tlda.outboundListener.workersCountMux.Unlock()\n\n\t\t\t//submit email\n\t\t\tlda.outboundListener.submitChan <- email\n\t\t}(email)\n\t}\n\n}\n\n/*  OutboundWorker dials to static MTA and maintains connection open to handle outbound deliveries,\nthen close the connection if no email comes in for 30 sec.\nshould be launched in a goroutine\n*/\nfunc (lda *Lda) OutboundWorker() {\n\tc := lda.Config.AppConfig\n\td := gomail.NewDialer(c.SubmitAddress, c.SubmitPort, c.SubmitUser, c.SubmitPassword)\n\tdefer func() {\n\t\tlda.outboundListener.workersCountMux.Lock()\n\t\tlda.outboundListener.runningWorkers--\n\t\tlda.outboundListener.workersCountMux.Unlock()\n\t}()\n\n\tvar smtp_sender gomail.SendCloser\n\tvar smtp_remote_sender gomail.SendCloser\n\tvar err error\n\topen := false\n\tfor {\n\t\tselect {\n\t\tcase outcoming, ok := <-lda.outboundListener.submitChan:\n\t\t\tif !ok {\n\t\t\t\t//TODO\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfrom := outcoming.EmailMessage.Email.SmtpMailFrom[0] //TODO: manage multiple senders\n\t\t\tto := outcoming.EmailMessage.Email.SmtpRcpTo\n\t\t\tvar raw bytes.Buffer\n\t\t\traw.WriteString((&outcoming.EmailMessage.Email.Raw).String())\n\n\t\t\t// send via local or remote MTA, accordingly\n\t\t\tif outcoming.MTAparams != nil {\n\t\t\t\tserver := strings.Split(outcoming.MTAparams.Host, \":\")\n\t\t\t\thost := server[0]\n\t\t\t\tport, _ := strconv.Atoi(server[1])\n\t\t\t\tvar dialErr error\n\t\t\t\tvar remoteDialer *gomail.Dialer\n\t\t\t\tswitch outcoming.MTAparams.AuthType {\n\t\t\t\tcase Oauth1:\n\t\t\t\t\tdialErr = errors.New(\"oauth1 mechanism not implemented\")\n\t\t\t\tcase Oauth2:\n\t\t\t\t\tremoteDialer = &gomail.Dialer{\n\t\t\t\t\t\tHost: host,\n\t\t\t\t\t\tPort: port,\n\t\t\t\t\t\tSSL:  false,\n\t\t\t\t\t\tAuth: &Xoauth2Client{\n\t\t\t\t\t\t\tUsername: outcoming.MTAparams.User,\n\t\t\t\t\t\t\tToken:    outcoming.MTAparams.Password,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\tcase LoginPassword:\n\t\t\t\t\tremoteDialer = &gomail.Dialer{\n\t\t\t\t\t\tHost:     host,\n\t\t\t\t\t\tPort:     port,\n\t\t\t\t\t\tUsername: outcoming.MTAparams.User,\n\t\t\t\t\t\tPassword: outcoming.MTAparams.Password,\n\t\t\t\t\t\tSSL:      port == 465,\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tdialErr = fmt.Errorf(\"unknown auth mechanism <%s>\", outcoming.MTAparams.AuthType)\n\t\t\t\t}\n\n\t\t\t\tif dialErr == nil {\n\t\t\t\t\tsmtp_remote_sender, dialErr = remoteDialer.Dial()\n\t\t\t\t}\n\t\t\t\tif dialErr != nil {\n\t\t\t\t\terr = fmt.Errorf(\"outbound: unable to connect to remote MTA with error : %s\", dialErr)\n\t\t\t\t} else {\n\t\t\t\t\terr = smtp_remote_sender.Send(from, to, &raw)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// no MTA params means submitter has to go through the configured local MTA\n\t\t\t\tif !open {\n\t\t\t\t\tvar dialErr error\n\t\t\t\t\tif smtp_sender, dialErr = d.Dial(); dialErr != nil {\n\t\t\t\t\t\terr = fmt.Errorf(\"outbound: unable to connect to MTA with error : %s\", dialErr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\topen = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err == nil {\n\t\t\t\t\terr = smtp_sender.Send(from, to, &raw)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar ack broker.EmailDeliveryAck\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warn(\"outbound: unable to send to MTA\")\n\t\t\t\tack.Err = true\n\t\t\t\tack.Response = err.Error()\n\t\t\t} else {\n\t\t\t\tack.Err = false\n\t\t\t\tack.Response = \"\"\n\t\t\t}\n\t\t\tack.EmailMessage = outcoming.EmailMessage\n\t\t\toutcoming.Response <- &ack\n\t\t// Close the connection to the SMTP server and this worker\n\t\t// if no email was sent in the last 30 seconds.\n\t\tcase <-time.After(30 * time.Second):\n\t\t\tif open {\n\t\t\t\tif err := smtp_sender.Close(); err != nil {\n\t\t\t\t\tlog.WithError(err).Warn(\"outbound: unable to close connection to MTA\")\n\t\t\t\t}\n\t\t\t\topen = false\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "src/backend/protocols/go.twitter/account.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage twitterworker\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tbroker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.twitter\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\t\"github.com/CaliOpen/go-twitter/twitter\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/dghubble/oauth1\"\n)\n\ntype (\n\tAccountHandler struct {\n\t\tAccountDesk      chan uint\n\t\tbroker           *broker.TwitterBroker\n\t\tclosed           bool\n\t\tlastDMseen       string\n\t\tMasterDesk       chan DeskMessage\n\t\ttwitterClient    *twitter.Client\n\t\tuserAccount      *TwitterAccount\n\t\tusersScreenNames map[int64]string // a cache facility to avoid calling too often twitter API for screen_name lookup\n\t}\n\n\tTwitterAccount struct {\n\t\taccessToken       string\n\t\taccessTokenSecret string\n\t\tuserID            UUID\n\t\tremoteID          UUID\n\t\tscreenName        string\n\t\ttwitterID         string\n\t}\n)\n\nconst (\n\t//AccountDesk commands\n\tPollDM = uint(iota)\n\tPollTimeLine\n\tStop\n\n\tlastSeenInfosKey = \"lastseendm\"\n\tlastSyncInfosKey = \"lastsync\"\n\n\tlastErrorKey      = \"lastFetchError\"\n\tdateFirstErrorKey = \"firstErrorDate\"\n\tdateLastErrorKey  = \"lastErrorDate\"\n\terrorsCountKey    = \"errorsCount\"\n\tsyncingKey        = \"syncing\"\n\n\tdefaultPollInterval = 10\n\tsyncingTimeout      = 1 // how many hours to wait before restarting sync op\n)\n\n// NewAccountHandler creates a handler dedicated to a specific twitter account.\n// It caches remote identity credentials and data, as well as user context connection to twitter API.\nfunc NewAccountHandler(userID, remoteID string, worker Worker) (accountHandler *AccountHandler, err error) {\n\taccountHandler = new(AccountHandler)\n\taccountHandler.AccountDesk = make(chan uint)\n\taccountHandler.MasterDesk = worker.Desk\n\tb, e := broker.Initialize(worker.Conf.BrokerConfig, worker.Store, worker.Index, worker.NatsConn, worker.Notifier)\n\tif e != nil {\n\t\terr = fmt.Errorf(\"[TwitterAccount]NewAccountHandler failed to initialize a twitter broker : %s\", e)\n\t\treturn nil, err\n\t}\n\taccountHandler.broker = b\n\tvar remote *UserIdentity\n\t// retrieve data from db\n\tremote, err = accountHandler.broker.Store.RetrieveUserIdentity(userID, remoteID, true)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"[TwitterAccount]NewAccountHandler failed to retrieve remote identity <%s> (user <%s>)\", remoteID, userID)\n\t\treturn\n\t}\n\tif remote.Credentials == nil {\n\t\terr = fmt.Errorf(\"[TwitterAccount]NewAccountHandler failed to retrieve credentials for remote identity <%s> (user <%s>)\", remoteID, userID)\n\t\treturn\n\t}\n\taccountHandler.userAccount = &TwitterAccount{\n\t\taccessToken:       (*remote.Credentials)[\"token\"],\n\t\taccessTokenSecret: (*remote.Credentials)[\"secret\"],\n\t\tuserID:            remote.UserId,\n\t\tremoteID:          remote.Id,\n\t\tscreenName:        remote.Identifier,\n\t}\n\n\tif lastseen, ok := remote.Infos[lastSeenInfosKey]; ok {\n\t\taccountHandler.lastDMseen = lastseen\n\t} else {\n\t\taccountHandler.lastDMseen = \"0\"\n\t}\n\n\tauthConf := oauth1.NewConfig(worker.Conf.TwitterAppKey, worker.Conf.TwitterAppSecret)\n\ttoken := oauth1.NewToken(accountHandler.userAccount.accessToken, accountHandler.userAccount.accessTokenSecret)\n\thttpClient := authConf.Client(oauth1.NoContext, token)\n\tif accountHandler.twitterClient = twitter.NewClient(httpClient); accountHandler.twitterClient == nil {\n\t\treturn nil, errors.New(\"[NewWorker] twitter api failed to create http client\")\n\t}\n\tvar twitterID int64\n\tvar screenName string\n\taccountHandler.usersScreenNames = map[int64]string{}\n\n\t// try to cache account's ID and screenName\n\tif twitterid, ok := remote.Infos[\"twitterid\"]; ok && twitterid != \"\" {\n\t\taccountHandler.userAccount.twitterID = twitterid\n\t\ttwitterID, _ = strconv.ParseInt(twitterid, 10, 64)\n\t\tif twittername, ok := remote.Infos[\"screen_name\"]; ok && twittername != \"\" {\n\t\t\tscreenName = twittername\n\t\t} else {\n\t\t\tscreenName = accountHandler.getAccountName(twitterid)\n\t\t}\n\t} else {\n\t\ttwitterUser, _, e := accountHandler.twitterClient.Users.Show(&twitter.UserShowParams{ScreenName: accountHandler.userAccount.screenName})\n\t\tif e == nil {\n\t\t\taccountHandler.userAccount.twitterID = twitterUser.IDStr\n\t\t\ttwitterID = twitterUser.ID\n\t\t\tscreenName = twitterUser.ScreenName\n\t\t}\n\t}\n\tif twitterID != 0 && screenName != \"\" {\n\t\taccountHandler.usersScreenNames[twitterID] = screenName\n\t}\n\treturn\n}\n\n// Start begins infinite loops, until receiving stop order. This func must be call within goroutine.\nfunc (worker *AccountHandler) Start() {\n\tgo func(w *AccountHandler) {\n\t\tfor worker.broker != nil {\n\t\t\tselect {\n\t\t\tcase egress, ok := <-w.broker.Connectors.Egress:\n\t\t\t\tif !ok {\n\t\t\t\t\tif !worker.closed {\n\t\t\t\t\t\tclose(worker.broker.Connectors.Halt)\n\t\t\t\t\t\tclose(worker.AccountDesk)\n\t\t\t\t\t\tworker.closed = true\n\t\t\t\t\t}\n\t\t\t\t\tworker.MasterDesk <- DeskMessage{closeAccountOrder, worker}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\terr := w.SendDM(egress.Order)\n\t\t\t\tif err != nil {\n\t\t\t\t\tegress.Ack <- &DeliveryAck{\n\t\t\t\t\t\tErr:      true,\n\t\t\t\t\t\tResponse: err.Error(),\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tegress.Ack <- &DeliveryAck{\n\t\t\t\t\t\tErr:      false,\n\t\t\t\t\t\tResponse: \"OK\",\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase <-w.broker.Connectors.Halt:\n\t\t\t\tworker.MasterDesk <- DeskMessage{closeAccountOrder, worker}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}(worker)\n\n\tfor command := range worker.AccountDesk {\n\t\tswitch command {\n\t\tcase PollDM:\n\t\t\tworker.PollDM()\n\t\tcase Stop:\n\t\t\tworker.Stop()\n\t\t\treturn\n\t\tdefault:\n\t\t\tlog.Warnf(\"worker received unknown command number %d\", command)\n\t\t}\n\t}\n}\n\nfunc (worker *AccountHandler) Stop() {\n\tif !worker.closed {\n\t\tclose(worker.broker.Connectors.Egress)\n\t\tclose(worker.broker.Connectors.Halt)\n\t\tclose(worker.AccountDesk)\n\t\tworker.closed = true\n\t}\n}\n\n// PollDM calls Twitter API endpoint to fetch DMs\n// it passes unseen DM to its embedded broker\nfunc (worker *AccountHandler) PollDM() {\n\t// retrieve user_identity.infos\n\taccountInfos, retrieveErr := worker.broker.Store.RetrieveRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String())\n\tif retrieveErr != nil {\n\t\tlog.WithError(retrieveErr).Warnf(\"[AccountHandler %s] PollDM failed to retrieve infos map\", worker.userAccount.remoteID.String())\n\t\treturn\n\t}\n\t// check if a sync process is running\n\tif syncing, ok := accountInfos[syncingKey]; ok && syncing != \"\" {\n\t\tstartDate, e := time.Parse(time.RFC3339, syncing)\n\t\tif e == nil && time.Since(startDate)/time.Hour < syncingTimeout {\n\t\t\tlog.Infof(\"[PollDM] avoiding concurrent sync for <%s>. Syncing in progress since %s\", worker.userAccount.remoteID, accountInfos[\"syncing\"])\n\t\t\treturn\n\t\t}\n\t}\n\t// save syncing state in db to prevent concurrent sync\n\taccountInfos[syncingKey] = time.Now().Format(time.RFC3339)\n\terr := worker.broker.Store.UpdateRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String(), accountInfos)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"[PollDM] failed to update syncing state user <%s>, identity <%s>\", worker.userAccount.userID, worker.userAccount.remoteID)\n\t\treturn\n\t}\n\n\t// do not forget to always write down last_check timestamp\n\t// and to remove syncing state before leaving\n\tdefer func() {\n\t\tif worker.broker != nil {\n\t\t\t// TODO : remove deletion of errors key from defer func ? (see mastodon broker)\n\t\t\tdelete(accountInfos, lastErrorKey)\n\t\t\tdelete(accountInfos, errorsCountKey)\n\t\t\tdelete(accountInfos, dateFirstErrorKey)\n\t\t\tdelete(accountInfos, dateLastErrorKey)\n\t\t\tdelete(accountInfos, syncingKey)\n\t\t\te := worker.broker.Store.UpdateRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String(), accountInfos)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM failed to update sync state in db\", worker.userAccount.remoteID.String())\n\t\t\t}\n\t\t\tlog.Infof(\"[AccountHandler %s] PollDM finished\", worker.userAccount.remoteID.String())\n\t\t\te = worker.broker.Store.TimestampRemoteLastCheck(worker.userAccount.userID.String(), worker.userAccount.remoteID.String())\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM failed to update last_check state in db\", worker.userAccount.remoteID.String())\n\t\t\t}\n\t\t}\n\t}()\n\n\t// retrieve DM list from twitter API\n\tDMs, _, err := worker.twitterClient.DirectMessages.EventsList(&twitter.DirectMessageEventsListParams{Count: 50})\n\tif err != nil {\n\t\tif e, ok := err.(twitter.APIError); ok {\n\t\t\terrorsMessages := new(strings.Builder)\n\t\t\tfor _, err := range e.Errors {\n\t\t\t\tswitch err.Code {\n\t\t\t\tcase 88: // rate limit error => send throttling order to idpoller\n\t\t\t\t\tvar interval int\n\t\t\t\t\tlog.Infof(\"[AccountHandler %s] PollDM : twitter returned rate limit error, slowing down worker for account\", worker.userAccount.remoteID)\n\t\t\t\t\tif pollInterval, ok := accountInfos[\"pollinterval\"]; ok {\n\t\t\t\t\t\tinterval, e := strconv.Atoi(pollInterval)\n\t\t\t\t\t\tif e == nil {\n\t\t\t\t\t\t\tinterval *= 2\n\t\t\t\t\t\t\t// prevent boundaries overflow : min = 1 min, max = 3 days\n\t\t\t\t\t\t\tif interval < 1 || interval > 3*24*60 {\n\t\t\t\t\t\t\t\tinterval = defaultPollInterval\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tinterval = defaultPollInterval\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tinterval = defaultPollInterval\n\t\t\t\t\t}\n\t\t\t\t\tnewInterval := strconv.Itoa(interval)\n\t\t\t\t\taccountInfos[\"pollinterval\"] = newInterval\n\t\t\t\t\te := worker.broker.Store.UpdateRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String(), accountInfos)\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM : failed to updateRemoteInfosMap with new poll interval\", worker.userAccount.userID.String()+\"/\"+worker.userAccount.remoteID.String())\n\t\t\t\t\t}\n\t\t\t\t\torder := RemoteIDNatsMessage{\n\t\t\t\t\t\tIdentityId: worker.userAccount.remoteID.String(),\n\t\t\t\t\t\tOrder:      \"update_interval\",\n\t\t\t\t\t\tOrderParam: newInterval,\n\t\t\t\t\t\tProtocol:   \"twitter\",\n\t\t\t\t\t\tUserId:     worker.userAccount.userID.String(),\n\t\t\t\t\t}\n\t\t\t\t\tjorder, jerr := json.Marshal(order)\n\t\t\t\t\tif jerr == nil {\n\t\t\t\t\t\te := worker.broker.NatsConn.Publish(worker.broker.Config.NatsTopicPollerCache, jorder)\n\t\t\t\t\t\tif e != nil {\n\t\t\t\t\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM : failed to publish new poll interval to idpoller\", worker.userAccount.userID.String()+\"/\"+worker.userAccount.remoteID.String())\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tcase 89: // invalid token or token expired. Suicide this accountworker thus it will be re-created with new oauth next time idpoller will order it\n\t\t\t\t\tdelete(accountInfos, syncingKey)\n\t\t\t\t\te := worker.saveErrorState(accountInfos, errorsMessages.String())\n\t\t\t\t\tif e != nil {\n\t\t\t\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM failed to update sync state in db\", worker.userAccount.remoteID.String())\n\t\t\t\t\t}\n\t\t\t\t\tworker.MasterDesk <- DeskMessage{closeAccountOrder, worker}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\terrorsMessages.WriteString(err.Message + \" \")\n\t\t\t}\n\t\t\te := worker.saveErrorState(accountInfos, errorsMessages.String())\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM failed to update sync state in db\", worker.userAccount.remoteID.String())\n\t\t\t}\n\t\t\treturn\n\n\t\t} else {\n\t\t\te := worker.saveErrorState(accountInfos, err.Error())\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[AccountHandler %s] PollDM failed to update sync state in db\", worker.userAccount.remoteID.String())\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tsort.Sort(ByAscID(DMs.Events)) // reverse events order to get older DMs first\n\n\tif len(DMs.Events) > 0 && worker.dmNotSeen(DMs.Events[0]) {\n\t\t//TODO: handle pagination with `cursor` param\n\t}\n\n\tlog.Infof(\"[AccountHandler %s] PollDM %d events retrieved\", worker.userAccount.remoteID.String(), len(DMs.Events))\n\tbatch := Notifications.NewBatch(\"twitter_worker\")\n\tfor _, event := range DMs.Events {\n\t\tif worker.dmNotSeen(event) {\n\t\t\t//lookup sender & recipient's screen_names because there are not embedded in event object\n\t\t\taccountName := worker.getAccountName(event.Message.SenderID)\n\t\t\tif accountName == \"\" {\n\t\t\t\tcontinue // we don't want to inject a message with an incomplete participant\n\t\t\t}\n\t\t\t(*event.Message).SenderScreenName = accountName\n\t\t\taccountName = worker.getAccountName(event.Message.Target.RecipientID)\n\t\t\tif accountName == \"\" {\n\t\t\t\tcontinue // we don't want to inject a message with an incomplete participant\n\t\t\t}\n\t\t\t(*event.Message).Target.RecipientScreenName = accountName\n\t\t\terr = worker.broker.ProcessInDM(worker.userAccount.userID, worker.userAccount.remoteID, &event, true, batch)\n\t\t\tif err != nil {\n\t\t\t\t// something went wrong, forget this DM\n\t\t\t\tlog.WithError(err).Warnf(\"[AccountHandler %s] ProcessInDM failed for event : %+v\", worker.userAccount.remoteID.String(), event)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tworker.lastDMseen = event.ID\n\t\t\t// update sync status in db\n\t\t\t// TODO: algorithm to shorten pollinterval after new DM has been received\n\t\t\taccountInfos[lastSeenInfosKey] = event.ID\n\t\t\taccountInfos[lastSyncInfosKey] = time.Now().Format(time.RFC3339)\n\t\t\terr = worker.broker.Store.UpdateRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String(), accountInfos)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"[AccountHandler %s] ProcessInDM failed to update InfosMap for event : %+v\", worker.userAccount.remoteID.String(), event)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\tbatch.Save(worker.broker.Notifier, \"\", LongLived)\n}\n\nfunc (worker *AccountHandler) dmNotSeen(event twitter.DirectMessageEvent) bool {\n\treturn worker.lastDMseen < event.ID\n}\n\n// SendDM delivers DM to Twitter endpoint and give back Twitter's response to broker.\nfunc (worker *AccountHandler) SendDM(order BrokerOrder) error {\n\t// make use of broker to marshal a direct message\n\tbrokerPort := make(chan *broker.DMpayload)\n\tvar brokerMessage *broker.DMpayload\n\n\tgo worker.broker.ProcessOutDM(order, brokerPort)\n\n\tselect {\n\tcase brokerMessage = <-brokerPort:\n\t\tif brokerMessage.Err != nil {\n\t\t\treturn brokerMessage.Err\n\t\t}\n\tcase <-time.After(10 * time.Second):\n\t\treturn errors.New(\"[SendDM] broker timeout\")\n\t}\n\n\t// retrieve recipient's twitter ID from DM's screenName\n\tuser, _, userErr := worker.twitterClient.Users.Show(&twitter.UserShowParams{\n\t\tScreenName: brokerMessage.DM.Message.Target.RecipientScreenName,\n\t})\n\tif userErr != nil {\n\t\tbrokerMessage.Response <- broker.TwitterDeliveryAck{\n\t\t\tErr:      true,\n\t\t\tResponse: userErr.Error(),\n\t\t}\n\t\treturn userErr\n\t}\n\tbrokerMessage.DM.Message.Target.RecipientID = user.IDStr\n\n\t// deliver DM through Twitter API\n\tcreateResponse, _, errResponse := worker.twitterClient.DirectMessages.EventsCreate(brokerMessage.DM.Message)\n\tif errResponse != nil {\n\t\tbrokerMessage.Response <- broker.TwitterDeliveryAck{\n\t\t\tPayload:  createResponse,\n\t\t\tErr:      true,\n\t\t\tResponse: errResponse.Error(),\n\t\t}\n\t\treturn errResponse\n\t}\n\n\t// give back Twitter's reply to broker for it finishes its job\n\tbrokerMessage.Response <- broker.TwitterDeliveryAck{\n\t\tPayload: createResponse,\n\t}\n\n\tselect {\n\tcase brokerMessage = <-brokerPort:\n\t\tif brokerMessage.Err != nil {\n\t\t\treturn brokerMessage.Err\n\t\t}\n\t\treturn nil\n\tcase <-time.After(10 * time.Second):\n\t\treturn errors.New(\"[SendDM] broker timeout\")\n\t}\n}\n\n// getAccountName returns Twitter account screen name given a Twitter account ID\n// screen name is retrieve either from worker's cache or Twitter API\n// returns empty string if it fails.\nfunc (worker *AccountHandler) getAccountName(accountID string) (accountName string) {\n\tID, err := strconv.ParseInt(accountID, 10, 64)\n\tif err == nil {\n\t\tvar inCache bool\n\t\tif accountName, inCache = worker.usersScreenNames[ID]; !inCache {\n\t\t\tuser, resp, err := worker.twitterClient.Users.Show(&twitter.UserShowParams{UserID: ID})\n\t\t\tif err == nil && user != nil {\n\t\t\t\t(*worker).usersScreenNames[ID] = user.ScreenName\n\t\t\t\treturn user.ScreenName\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"[AccountHandler] failed to getAccountName for twitter ID %s. Got user {%+v} and http response {%+v}\", accountID, user, resp)\n\t\t\t}\n\t\t}\n\t\treturn accountName\n\t}\n\treturn\n}\n\n// isDMUnique returns true if Twitter Direct Message id is not found within user's messages index\n// if seeking fails for any reason, true is returned anyway to allow duplication\nfunc (worker *AccountHandler) isDMUnique(dmID string) bool {\n\tmessageID, err := worker.broker.Store.SeekMessageByExternalRef(worker.userAccount.userID.String(), dmID, \"\")\n\tif err != nil || bytes.Equal(messageID.Bytes(), EmptyUUID.Bytes()) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (worker *AccountHandler) saveErrorState(infos map[string]string, err string) error {\n\n\t// ensure errors data fields are present\n\tif _, ok := infos[lastErrorKey]; !ok {\n\t\tinfos[lastErrorKey] = \"\"\n\t}\n\tif _, ok := infos[dateFirstErrorKey]; !ok {\n\t\tinfos[dateFirstErrorKey] = \"\"\n\t}\n\tif _, ok := infos[dateLastErrorKey]; !ok {\n\t\tinfos[dateLastErrorKey] = \"\"\n\t}\n\tif _, ok := infos[errorsCountKey]; !ok {\n\t\tinfos[errorsCountKey] = \"0\"\n\t}\n\n\t// log last error\n\tinfos[lastErrorKey] = \"Twitter connection failed : \" + err\n\tlog.Warnf(\"Twitter connection failed for remote identity %s : %s\", worker.userAccount.remoteID, err)\n\t// increment counter\n\tcount, _ := strconv.Atoi(infos[errorsCountKey])\n\tcount++\n\tinfos[errorsCountKey] = strconv.Itoa(count)\n\n\t// update dates\n\tlastDate := time.Now()\n\tvar firstDate time.Time\n\tfirstDate, _ = time.Parse(time.RFC3339, infos[dateFirstErrorKey])\n\tif firstDate.IsZero() {\n\t\tfirstDate = lastDate\n\t}\n\tinfos[dateFirstErrorKey] = firstDate.Format(time.RFC3339)\n\tinfos[dateLastErrorKey] = lastDate.Format(time.RFC3339)\n\n\t// check failuresThreshold\n\tif lastDate.Sub(firstDate)/time.Hour > failuresThreshold {\n\t\t// disable remote identity\n\t\terr := worker.broker.Store.UpdateUserIdentity(&UserIdentity{\n\t\t\tUserId: worker.userAccount.userID,\n\t\t\tId:     worker.userAccount.remoteID,\n\t\t}, map[string]interface{}{\n\t\t\t\"Status\": \"inactive\",\n\t\t})\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[saveErrorState] failed to deactivate remote identity %s for user %s\", worker.userAccount.remoteID, worker.userAccount.userID)\n\t\t}\n\t\t// send nats message to idpoller to stop polling\n\t\torder := RemoteIDNatsMessage{\n\t\t\tIdentityId: worker.userAccount.remoteID.String(),\n\t\t\tOrder:      \"delete\",\n\t\t\tProtocol:   \"twitter\",\n\t\t\tUserId:     worker.userAccount.userID.String(),\n\t\t}\n\t\tjorder, jerr := json.Marshal(order)\n\t\tif jerr == nil {\n\t\t\te := worker.broker.NatsConn.Publish(worker.broker.Config.NatsTopicPollerCache, jorder)\n\t\t\tif e != nil {\n\t\t\t\tlog.WithError(e).Warnf(\"[saveErrorState] failed to publish delete order to idpoller\")\n\t\t\t}\n\t\t}\n\t}\n\n\t// udpate UserIdentity in db\n\treturn worker.broker.Store.UpdateRemoteInfosMap(worker.userAccount.userID.String(), worker.userAccount.remoteID.String(), infos)\n\n}\n\n// ByAscID implements sort interface\ntype ByAscID []twitter.DirectMessageEvent\n\nfunc (bri ByAscID) Len() int {\n\treturn len(bri)\n}\n\nfunc (bri ByAscID) Less(i, j int) bool {\n\treturn bri[i].ID < bri[j].ID\n}\n\nfunc (bri ByAscID) Swap(i, j int) {\n\tbri[i], bri[j] = bri[j], bri[i]\n}\n"
  },
  {
    "path": "src/backend/protocols/go.twitter/account_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage twitterworker\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestNewAccountHandler(t *testing.T) {\n\tw, s, err := initWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer s.Shutdown()\n\tah, err := NewAccountHandler(backendstest.EmmaTommeUserId, \"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\", *w)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tif ah.broker == nil {\n\t\tt.Error(\"expected account handler with broker initialized, got broker==nil\")\n\t}\n\tif ah.twitterClient == nil {\n\t\tt.Error(\"expected account handler with twitterClient initialized, got client==nil\")\n\t}\n\tif ah.userAccount.twitterID != \"000000\" {\n\t\tt.Errorf(\"expected userAccount with twitterID == 000000, got %s\", ah.userAccount.twitterID)\n\t}\n\n\t// test that closing broker's connectors will kill accountHandler\n\tgo ah.Start()\n\ttime.Sleep(100 * time.Millisecond)\n\tclose(ah.broker.Connectors.Egress)\n\ttime.Sleep(100 * time.Millisecond)\n\tif _, ok := <-ah.AccountDesk; ok {\n\t\tt.Error(\"expected handler's accountDesk to be closed, still open\")\n\t}\n\tif _, ok := <-ah.broker.Connectors.Halt; ok {\n\t\tt.Error(\"expected handler.broker's connectors.halt to be closed, still open\")\n\t}\n\tif _, ok := <-ah.broker.Connectors.Egress; ok {\n\t\tt.Error(\"expected handler.broker's connectors.Egress to be closed, still open\")\n\t}\n\tif len(w.AccountHandlers) > 0 {\n\t\tt.Errorf(\"expected empty AccountHandlers map, got len=%d\", len(w.AccountHandlers))\n\t}\n}\n\nfunc TestAccountHandler_Start(t *testing.T) {\n\tw, s, err := initWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer s.Shutdown()\n\tah, err := NewAccountHandler(backendstest.EmmaTommeUserId, \"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\", *w)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tgo ah.Start()\n}\n\nfunc TestAccountHandler_Stop(t *testing.T) {\n\tw, s, err := initWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer s.Shutdown()\n\tah, err := NewAccountHandler(backendstest.EmmaTommeUserId, \"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\", *w)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tgo ah.Start()\n\ttime.Sleep(100 * time.Millisecond)\n\tah.Stop()\n\ttime.Sleep(100 * time.Millisecond)\n\tif _, ok := <-ah.AccountDesk; ok {\n\t\tt.Error(\"expected handler's accountDesk to be closed, still open\")\n\t}\n\tif _, ok := <-ah.broker.Connectors.Halt; ok {\n\t\tt.Error(\"expected handler.broker's connectors.halt to be closed, still open\")\n\t}\n\tif _, ok := <-ah.broker.Connectors.Egress; ok {\n\t\tt.Error(\"expected handler.broker's connectors.Egress to be closed, still open\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.twitter/cmd/twitterworker/cli_cmds/root.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tverbose bool\n\tversion bool\n\tRootCmd = &cobra.Command{\n\t\tUse:   \"twitterd\",\n\t\tShort: \"Twitter API daemon\",\n\t\tLong:  `twitterd is a daemon that subscribes to Twitter accounts on one side and to our NATS queues on other side to executes IO operations with Twitter API`,\n\t\tRun:   nil,\n\t}\n)\n\nconst __version__ = \"0.23.0\"\n\nfunc init() {\n\tcobra.OnInitialize()\n\tRootCmd.PersistentFlags().BoolVarP(&verbose, \"verbose\", \"v\", false,\n\t\t\"print out more debug information\")\n\tRootCmd.PersistentFlags().BoolVarP(&version, \"version\", \"V\", false,\n\t\t\"print out the version of this program\")\n\tRootCmd.Run = func(cmd *cobra.Command, args []string) {\n\t\tif version {\n\t\t\tlog.Infof(\"twitterd version %s\", __version__)\n\t\t}\n\t\tif len(args) == 0 {\n\t\t\tcmd.Help()\n\t\t}\n\t}\n\tRootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {\n\t\tif verbose {\n\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t} else {\n\t\t\tlog.SetLevel(log.InfoLevel)\n\t\t}\n\t}\n\tRootCmd.AddCommand(versionCmd)\n}\n\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print the version number of twitterd\",\n\tLong:  `All software has versions. This is twitterd's`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tlog.Infof(\"twitterd version %s\", __version__)\n\t},\n}\n"
  },
  {
    "path": "src/backend/protocols/go.twitter/cmd/twitterworker/cli_cmds/start.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\ttwd \"github.com/CaliOpen/Caliopen/src/backend/protocols/go.twitter\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n)\n\nconst (\n\tshutdownTimeout = 3 // minutes to wait before forcing shutdown\n)\n\nvar (\n\tconfigPath     string\n\tconfigFile     string\n\tpidFile        string\n\tsignalChannel  chan os.Signal\n\ttwitterWorkers []*twd.Worker\n\n\tstartCmd = &cobra.Command{\n\t\tUse:   \"start\",\n\t\tShort: \"Starts a pool of twitter API worker(s)\",\n\t\tRun:   start,\n\t}\n)\n\nfunc init() {\n\tstartCmd.PersistentFlags().StringVarP(&configFile, \"config\", \"c\",\n\t\t\"twitterworker\", \"Name of the configuration file, without extension. (YAML, TOML, JSON… allowed)\")\n\tstartCmd.PersistentFlags().StringVarP(&configPath, \"configpath\", \"\",\n\t\t\"../../../../configs/\", \"Main config file path.\")\n\tstartCmd.PersistentFlags().StringVarP(&pidFile, \"pid-file\", \"p\",\n\t\t\"/var/run/caliopen_twitterd.pid\", \"Path to the pid file\")\n\n\tRootCmd.AddCommand(startCmd)\n\tsignalChannel = make(chan os.Signal, 1)\n}\n\nfunc start(cmd *cobra.Command, args []string) {\n\n\tvar conf twd.WorkerConfig\n\terr := readConfig(&conf)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"Error while reading config\")\n\t}\n\t// Write out our PID\n\tif len(pidFile) > 0 {\n\t\tif f, err := os.Create(pidFile); err == nil {\n\t\t\tdefer f.Close()\n\t\t\tif _, err := f.WriteString(fmt.Sprintf(\"%d\", os.Getpid())); err == nil {\n\t\t\t\tf.Sync()\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"Error while writing pidFile (%s)\", pidFile)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.WithError(err).Warnf(\"Error while creating pidFile (%s)\", pidFile)\n\t\t}\n\t}\n\n\t// init and start worker(s)\n\tvar i uint8\n\ttwitterWorkers = make([]*twd.Worker, conf.Workers)\n\tfor i = 0; i < conf.Workers; i++ {\n\t\tlog.Infof(\"Initializing Twitter worker %d\", i)\n\t\ttwitterWorkers[i], err = twd.InitWorker(conf, verbose, randomIdentifier())\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Fatal(\"failed to init worker\")\n\t\t}\n\t\tgo twitterWorkers[i].Start()\n\t}\n\t// listening mode, waiting for nats orders to add/update workers or os sig to shutdown\n\tsigHandler(twitterWorkers)\n\n}\n\n// ReadConfig which should be called at startup, or when a SIG_HUP is caught\nfunc readConfig(config *twd.WorkerConfig) error {\n\t// load in the main config. Reading from YAML, TOML, JSON, HCL and Java properties config files\n\tv := viper.New()\n\tv.SetConfigName(configFile)                           // name of config file (without extension)\n\tv.AddConfigPath(configPath)                           // path to look for the config file in\n\tv.AddConfigPath(\"$CALIOPENROOT/src/backend/configs/\") // call multiple times to add many search paths\n\tv.AddConfigPath(\".\")                                  // optionally look for config in the working directory\n\n\terr := v.ReadInConfig() // Find and read the config file*/\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not read main config file <%s>.\", configFile)\n\t\treturn err\n\t}\n\terr = v.Unmarshal(config)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not parse config file: <%s>\", configFile)\n\t\treturn err\n\t}\n\n\treturn nil\n}\nfunc sigHandler(workers []*twd.Worker) {\n\tsignal.Notify(signalChannel, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGKILL)\n\n\tfor sig := range signalChannel {\n\n\t\tif sig == syscall.SIGHUP {\n\t\t\t// TODO: handle SIGHUP\n\t\t} else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT || sig == syscall.SIGKILL {\n\t\t\tlog.Infof(\"Shutdown signal caught. Gracefully halting %d workers within 3 minutes timeframe…\", len(workers))\n\t\t\twg := new(sync.WaitGroup)\n\t\t\twg.Add(len(workers))\n\t\t\tfor i := range workers {\n\t\t\t\tworkers[i].HaltGroup = wg\n\t\t\t}\n\t\t\t// timeout mechanism to avoid infinite wait\n\t\t\tc := make(chan struct{})\n\t\t\tgo func() {\n\t\t\t\tdefer close(c)\n\t\t\t\twg.Wait()\n\t\t\t}()\n\t\t\tselect {\n\t\t\tcase <-c:\n\t\t\t\tlog.Info(\"Shutdown completed, exiting\")\n\t\t\t\tos.Exit(0)\n\t\t\tcase <-time.After(shutdownTimeout * time.Minute):\n\t\t\t\tlog.Warn(\"Shutdown timeout, force exiting\")\n\t\t\t\tos.Exit(0)\n\t\t\t}\n\t\t} else {\n\t\t\tos.Exit(0)\n\t\t}\n\t}\n}\n\nfunc randomIdentifier() string {\n\tvar buf [4]byte\n\t_, err := io.ReadFull(rand.Reader, buf[:])\n\tif err != nil {\n\t\treturn \"00000000\"\n\t}\n\treturn fmt.Sprintf(\"%x\", buf[:])\n}\n"
  },
  {
    "path": "src/backend/protocols/go.twitter/cmd/twitterworker/main.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/protocols/go.twitter/cmd/twitterworker/cli_cmds\"\n\t\"os\"\n)\n\nfunc main() {\n\tif err := cmd.RootCmd.Execute(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(-1)\n\t}\n}"
  },
  {
    "path": "src/backend/protocols/go.twitter/messaging.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage twitterworker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/brokers/go.twitter\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/pkg/errors\"\n\t\"time\"\n)\n\n// WorkerMsgHandler handles message coming from idpoller\nfunc (w *Worker) WorkerMsgHandler(msg *nats.Msg) {\n\tmessage := BrokerOrder{}\n\terr := json.Unmarshal(msg.Data, &message)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"Unable to unmarshal message from NATS. Payload was <%s>\", string(msg.Data))\n\t\treturn\n\t}\n\tswitch message.Order {\n\tcase noPendingJobErr:\n\t\treturn\n\tcase \"sync\":\n\t\tlog.Infof(\"received sync order for remote twitter ID %s\", message.IdentityId)\n\t\tif accountWorker := w.getOrCreateHandler(message.UserId, message.IdentityId); accountWorker != nil {\n\t\t\tselect {\n\t\t\tcase accountWorker.AccountDesk <- PollDM:\n\t\t\t\tlog.Infof(\"[DMmsgHandler] ordering to pollDM for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\tcase <-time.After(30 * time.Second):\n\t\t\t\tlog.Warnf(\"[DMmsgHandler] worker's desk is full for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Warnf(\"[DMmsgHandler] failed to get a worker for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] failed to get a worker\"))\n\t\t}\n\tcase \"reload_worker\":\n\t\tlog.Infof(\"received reload_worker order for remote twitter ID %s\", message.IdentityId)\n\t\t//TODO: order to force refreshing cache data for an account\n\tcase \"add_worker\":\n\t\tlog.Infof(\"received add_worker order for remote twitter ID %s\", message.IdentityId)\n\t\taccountWorker := w.getOrCreateHandler(message.UserId, message.IdentityId)\n\t\tif accountWorker == nil {\n\t\t\tlog.WithError(err).Warnf(\"[WorkerMsgHandler] failed to create new worker for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] failed to get a worker\"))\n\t\t}\n\tcase \"remove_worker\":\n\t\tlog.Infof(\"received remove_worker order for remote twitter ID %s\", message.IdentityId)\n\t\t// TODO\n\t}\n}\n\n// DMmsgHandler handles messages coming on topic dedicated to DM management\nfunc (w *Worker) DMmsgHandler(msg *nats.Msg) {\n\tmessage := BrokerOrder{}\n\terr := json.Unmarshal(msg.Data, &message)\n\tif err != nil {\n\t\tlog.WithError(err).Errorf(\"Unable to unmarshal message from NATS. Payload was <%s>\", string(msg.Data))\n\t\treturn\n\t}\n\tswitch message.Order {\n\tcase \"deliver\":\n\t\tif accountWorker := w.getOrCreateHandler(message.UserId, message.IdentityId); accountWorker != nil {\n\t\t\tcom := twitter_broker.NatsCom{\n\t\t\t\tOrder: message,\n\t\t\t\tAck:   make(chan *DeliveryAck),\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase accountWorker.broker.Connectors.Egress <- com:\n\t\t\t\tlog.Infof(\"[DMmsgHandler] sending DM for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\t\t// non-blocking wait for delivery ack\n\t\t\t\tgo func(com twitter_broker.NatsCom) {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase resp := <-com.Ack:\n\t\t\t\t\t\tif resp.Err {\n\t\t\t\t\t\t\tw.natsReplyError(msg, errors.New(resp.Response))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tack := DeliveryAck{\n\t\t\t\t\t\t\t\tErr:      false,\n\t\t\t\t\t\t\t\tResponse: \"OK\",\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tjson_resp, _ := json.Marshal(ack)\n\t\t\t\t\t\t\tw.NatsConn.Publish(msg.Reply, json_resp)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase <-time.After(30 * time.Second):\n\t\t\t\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] timeout waiting broker delivery ack\"))\n\t\t\t\t\t}\n\t\t\t\t}(com)\n\t\t\tcase <-time.After(30 * time.Second):\n\t\t\t\tlog.Warnf(\"[DMmsgHandler] worker's Egress connectors is full for remote %s (user %s)\", message.IdentityId, message.UserId)\n\t\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] failed to get a worker\"))\n\t\t\t}\n\t\t} else {\n\t\t\tw.natsReplyError(msg, errors.New(\"[DMmsgHandler] failed to get a worker\"))\n\t\t}\n\tdefault:\n\t\tw.natsReplyError(msg, errors.New(\"not implemented\"))\n\t}\n}\n\nfunc (w *Worker) natsReplyError(msg *nats.Msg, err error) {\n\tlog.WithError(err).Warnf(\"twitter broker [outbound] : error when processing incoming nats message : %v\", *msg)\n\n\tack := DeliveryAck{\n\t\tErr:      true,\n\t\tResponse: fmt.Sprintf(\"failed to send message with error « %s » \", err), //TODO\n\t}\n\n\tjson_resp, _ := json.Marshal(ack)\n\tw.NatsConn.Publish(msg.Reply, json_resp)\n}\n"
  },
  {
    "path": "src/backend/protocols/go.twitter/messaging_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage twitterworker\n\nimport (\n\t\"encoding/json\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\tidpoller \"github.com/CaliOpen/Caliopen/src/backend/workers/go.remoteIDs\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/satori/go.uuid\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestWorker_WorkerMsgHandler(t *testing.T) {\n\tw, s, err := initWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer s.Shutdown()\n\n\tnoJobMsg := nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    []byte(`{\"order\":\"no pending job\"}`),\n\t}\n\t// 'no job' message should trigger an immediate return\n\tc := make(chan struct{})\n\tgo func() {\n\t\tw.WorkerMsgHandler(&noJobMsg)\n\t\tclose(c)\n\t}()\n\tselect {\n\tcase <-c:\n\tcase <-time.After(100 * time.Millisecond):\n\t\tt.Error(\"timeout waiting for no job return\")\n\t}\n\n\tgotReply := false\n\tw.NatsConn.Subscribe(\"testMsgReply\", func(msg *nats.Msg) {\n\t\tgotReply = true\n\t})\n\t// test 'add_worker' that should fail\n\tfakeID := uuid.NewV4().String()\n\tjob := idpoller.Job{\n\t\tWorker: \"twitter\",\n\t\tOrder: BrokerOrder{\n\t\t\tMessageId:  fakeID,\n\t\t\tOrder:      \"add_worker\",\n\t\t\tIdentityId: fakeID,\n\t\t\tUserId:     fakeID,\n\t\t},\n\t}\n\tdata, _ := json.Marshal(job.Order)\n\tsyncMsg := nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    data,\n\t}\n\tw.WorkerMsgHandler(&syncMsg)\n\ttime.Sleep(time.Second)\n\tif !gotReply {\n\t\tt.Error(\"expected worker replied an error for bad 'add_worker', got nothing on topic\")\n\t}\n\n\t// test 'sync' order that should fail\n\tgotReply = false\n\tjob = idpoller.Job{\n\t\tWorker: \"twitter\",\n\t\tOrder: BrokerOrder{\n\t\t\tMessageId:  fakeID,\n\t\t\tOrder:      \"sync\",\n\t\t\tIdentityId: fakeID,\n\t\t\tUserId:     fakeID,\n\t\t},\n\t}\n\tdata, _ = json.Marshal(job.Order)\n\tsyncMsg = nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    data,\n\t}\n\tw.WorkerMsgHandler(&syncMsg)\n\ttime.Sleep(time.Second)\n\tif !gotReply {\n\t\tt.Error(\"expected worker replied an error for bad 'sync', got nothing on topic\")\n\t}\n\n\t// test 'add_worker' with a valid remote\n\tgotReply = false\n\tjob = idpoller.Job{\n\t\tWorker: \"twitter\",\n\t\tOrder: BrokerOrder{\n\t\t\tMessageId:  fakeID,\n\t\t\tOrder:      \"add_worker\",\n\t\t\tIdentityId: \"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\",\n\t\t\tUserId:     backendstest.EmmaTommeUserId,\n\t\t},\n\t}\n\tdata, _ = json.Marshal(job.Order)\n\tsyncMsg = nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    data,\n\t}\n\tw.NatsConn.Subscribe(\"testMsgReply\", func(msg *nats.Msg) {\n\t\tt.Errorf(\"expected no reply for valid 'add_worker' order , got %s\", msg.Data)\n\t})\n\tw.WorkerMsgHandler(&syncMsg)\n\n\t// test 'sync' order with a valid remote\n\tjob = idpoller.Job{\n\t\tWorker: \"twitter\",\n\t\tOrder: BrokerOrder{\n\t\t\tMessageId:  fakeID,\n\t\t\tOrder:      \"sync\",\n\t\t\tIdentityId: \"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\",\n\t\t\tUserId:     backendstest.EmmaTommeUserId,\n\t\t},\n\t}\n\tdata, _ = json.Marshal(job.Order)\n\tsyncMsg = nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    data,\n\t}\n\tw.NatsConn.Subscribe(\"testMsgReply\", func(msg *nats.Msg) {\n\t\tt.Errorf(\"expected no reply for valid 'sync' order , got %s\", msg.Data)\n\t})\n\tw.WorkerMsgHandler(&syncMsg)\n}\n\nfunc TestWorker_DMmsgHandler(t *testing.T) {\n\tw, s, err := initWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer s.Shutdown()\n\n\tgotReply := false\n\tw.NatsConn.Subscribe(\"testMsgReply\", func(msg *nats.Msg) {\n\t\tgotReply = true\n\t})\n\n\t// test 'deliver' that should fail\n\tfakeID := uuid.NewV4().String()\n\tjob := idpoller.Job{\n\t\tWorker: \"twitter\",\n\t\tOrder: BrokerOrder{\n\t\t\tMessageId:  fakeID,\n\t\t\tOrder:      \"deliver\",\n\t\t\tIdentityId: fakeID,\n\t\t\tUserId:     fakeID,\n\t\t},\n\t}\n\tdata, _ := json.Marshal(job.Order)\n\tdeliverMsg := nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    data,\n\t}\n\tw.DMmsgHandler(&deliverMsg)\n\ttime.Sleep(time.Second)\n\tif !gotReply {\n\t\tt.Error(\"expected worker replied an error for bad 'deliver', got nothing on topic\")\n\t}\n\n\t// test 'deliver' with a valid remote\n\tjob = idpoller.Job{\n\t\tWorker: \"twitter\",\n\t\tOrder: BrokerOrder{\n\t\t\tMessageId:  fakeID,\n\t\t\tOrder:      \"deliver\",\n\t\t\tIdentityId: \"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\",\n\t\t\tUserId:     backendstest.EmmaTommeUserId,\n\t\t},\n\t}\n\tdata, _ = json.Marshal(job.Order)\n\tdeliverMsg = nats.Msg{\n\t\tSubject: \"test\",\n\t\tReply:   \"testMsgReply\",\n\t\tData:    data,\n\t}\n\tw.NatsConn.Subscribe(\"testMsgReply\", func(msg *nats.Msg) {\n\t\t// should return an error because deliver process is not mocked for now\n\t\tgotReply = true\n\t\tvar reply DeliveryAck\n\t\terr = json.Unmarshal(msg.Data, &reply)\n\t\tif !reply.Err {\n\t\t\tt.Error(\"expected 'deliver' order to trigger an error, got reply.Err == false\")\n\t\t}\n\t})\n\tw.DMmsgHandler(&deliverMsg)\n\ttime.Sleep(time.Second)\n\tif !gotReply {\n\t\tt.Error(\"expected worker replied an error for valid 'deliver', got nothing on topic\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/protocols/go.twitter/worker.go",
    "content": "package twitterworker\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tbroker \"github.com/CaliOpen/Caliopen/src/backend/brokers/go.twitter\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/index/elasticsearch\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/Notifications\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype (\n\tWorker struct {\n\t\tAccountHandlers map[string]*AccountHandler // one worker per active Twitter account\n\t\tHaltGroup       *sync.WaitGroup\n\t\tIndex           backends.LDAIndex\n\t\tId              string\n\t\tNatsConn        *nats.Conn\n\t\tNatsSubs        []*nats.Subscription\n\t\tNotifier        *Notifications.Notifier\n\t\tStore           backends.LDAStore\n\t\tWorkersGuard    *sync.RWMutex\n\t\tConf            WorkerConfig\n\t\tDesk            chan DeskMessage // chan to allow accountworkers to communicate with their master\n\t}\n\n\tWorkerConfig struct {\n\t\tWorkers          uint8               `mapstructure:\"workers\"`\n\t\tTwitterAppKey    string              `mapstructure:\"twitter_app_key\"`\n\t\tTwitterAppSecret string              `mapstructure:\"twitter_app_secret\"`\n\t\tBrokerConfig     broker.BrokerConfig `mapstructure:\"BrokerConfig\"`\n\t}\n\n\tDeskMessage struct {\n\t\torder   string\n\t\taccount *AccountHandler\n\t}\n)\n\nconst (\n\tfailuresThreshold = 72 // how many hours to wait before disabling a faulty remote.\n\tnoPendingJobErr   = \"no pending job\"\n\tpollThrottling    = 10 * time.Second\n\tneedJobOrderStr   = `{\"worker\":\"%s\",\"order\":{\"order\":\"need_job\"}}`\n\tcloseAccountOrder = \"close_account\"\n)\n\nfunc InitWorker(conf WorkerConfig, verboseLog bool, id string) (worker *Worker, err error) {\n\n\tif verboseLog {\n\t\tlog.SetLevel(log.DebugLevel)\n\t}\n\n\tworker = &Worker{\n\t\tAccountHandlers: map[string]*AccountHandler{},\n\t\tConf:            conf,\n\t\tId:              id,\n\t\tWorkersGuard:    new(sync.RWMutex),\n\t}\n\n\t// init Store\n\tswitch conf.BrokerConfig.StoreName {\n\tcase \"cassandra\":\n\t\tc := store.CassandraConfig{\n\t\t\tHosts:       conf.BrokerConfig.StoreConfig.Hosts,\n\t\t\tKeyspace:    conf.BrokerConfig.StoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(conf.BrokerConfig.StoreConfig.Consistency),\n\t\t\tSizeLimit:   conf.BrokerConfig.StoreConfig.SizeLimit,\n\t\t\tUseVault:    conf.BrokerConfig.StoreConfig.UseVault,\n\t\t}\n\t\tif conf.BrokerConfig.StoreConfig.ObjectStore == \"s3\" {\n\t\t\tc.WithObjStore = true\n\t\t\tc.Endpoint = conf.BrokerConfig.StoreConfig.OSSConfig.Endpoint\n\t\t\tc.AccessKey = conf.BrokerConfig.StoreConfig.OSSConfig.AccessKey\n\t\t\tc.SecretKey = conf.BrokerConfig.StoreConfig.OSSConfig.SecretKey\n\t\t\tc.RawMsgBucket = conf.BrokerConfig.StoreConfig.OSSConfig.Buckets[\"raw_messages\"]\n\t\t\tc.AttachmentBucket = conf.BrokerConfig.StoreConfig.OSSConfig.Buckets[\"temporary_attachments\"]\n\t\t\tc.Location = conf.BrokerConfig.StoreConfig.OSSConfig.Location\n\t\t}\n\t\tif conf.BrokerConfig.StoreConfig.UseVault {\n\t\t\tc.HVaultConfig.Url = conf.BrokerConfig.StoreConfig.VaultConfig.Url\n\t\t\tc.HVaultConfig.Username = conf.BrokerConfig.StoreConfig.VaultConfig.Username\n\t\t\tc.HVaultConfig.Password = conf.BrokerConfig.StoreConfig.VaultConfig.Password\n\t\t}\n\t\tb, e := store.InitializeCassandraBackend(c)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlog.WithError(err).Warnf(\"[TwitterWorker] initialization of %s backend failed\", conf.BrokerConfig.StoreName)\n\t\t\treturn\n\t\t}\n\n\t\tworker.Store = backends.LDAStore(b) // type conversion to LDA interface\n\tdefault:\n\t\tlog.Warnf(\"[TwitterWorker] unknown store backend: %s\", conf.BrokerConfig.StoreName)\n\t\terr = errors.New(\"[TwitterWorker] unknown store backend\")\n\t\treturn\n\n\t}\n\n\t// init Index\n\tswitch conf.BrokerConfig.LDAConfig.IndexName {\n\tcase \"elasticsearch\":\n\t\tc := index.ElasticSearchConfig{\n\t\t\tUrls: conf.BrokerConfig.LDAConfig.IndexConfig.Urls,\n\t\t}\n\t\ti, e := index.InitializeElasticSearchIndex(c)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tlog.WithError(err).Warnf(\"[TwitterBroker] initialization of %s backend failed\", conf.BrokerConfig.IndexName)\n\t\t\treturn\n\t\t}\n\n\t\tworker.Index = backends.LDAIndex(i) // type conversion to LDA interface\n\tdefault:\n\t\tlog.Warnf(\"[TwitterBroker] unknown index backend: %s\", conf.BrokerConfig.LDAConfig.IndexName)\n\t\terr = errors.New(\"[TwitterBroker] unknown index backend\")\n\t\treturn\n\t}\n\n\tworker.NatsConn, err = nats.Connect(conf.BrokerConfig.NatsURL)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[TwitterBroker] initalization of NATS connexion failed\")\n\t\treturn\n\t}\n\tcaliopenConfig := CaliopenConfig{\n\t\tNotifierConfig: conf.BrokerConfig.LDAConfig.NotifierConfig,\n\t\tNatsConfig: NatsConfig{\n\t\t\tUrl: conf.BrokerConfig.NatsURL,\n\t\t},\n\t\tRESTstoreConfig: RESTstoreConfig{\n\t\t\tBackendName:  conf.BrokerConfig.StoreName,\n\t\t\tConsistency:  conf.BrokerConfig.StoreConfig.Consistency,\n\t\t\tHosts:        conf.BrokerConfig.StoreConfig.Hosts,\n\t\t\tKeyspace:     conf.BrokerConfig.StoreConfig.Keyspace,\n\t\t\tOSSConfig:    conf.BrokerConfig.StoreConfig.OSSConfig,\n\t\t\tObjStoreType: conf.BrokerConfig.StoreConfig.ObjectStore,\n\t\t\tSizeLimit:    conf.BrokerConfig.StoreConfig.SizeLimit,\n\t\t},\n\t\tRESTindexConfig: RESTIndexConfig{\n\t\t\tHosts:     conf.BrokerConfig.LDAConfig.IndexConfig.Urls,\n\t\t\tIndexName: conf.BrokerConfig.LDAConfig.IndexName,\n\t\t},\n\t}\n\tworker.Notifier = Notifications.NewNotificationsFacility(caliopenConfig, worker.NatsConn)\n\n\t// init Nats connector\n\tworker.NatsConn, err = nats.Connect(conf.BrokerConfig.NatsURL)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"[TwitterWorker] initialization of NATS connexion failed\")\n\t}\n\tworker.NatsSubs = make([]*nats.Subscription, 1)\n\tworker.NatsSubs[0], err = worker.NatsConn.QueueSubscribe(conf.BrokerConfig.NatsTopicDMs, conf.BrokerConfig.NatsQueue, worker.DMmsgHandler)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"[TwitterWorker] initialization of NATS fetcher subscription failed\")\n\t}\n\terr = worker.NatsConn.Flush()\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"[TwitterWorker] initialization of NATS fetcher subscription failed\")\n\t}\n\n\tworker.Desk = make(chan DeskMessage, 2)\n\n\treturn worker, nil\n}\n\nfunc (worker *Worker) Start(throttling ...time.Duration) {\n\tvar throttle time.Duration\n\tif len(throttling) == 1 && throttling[0] != 0 {\n\t\tthrottle = throttling[0]\n\t} else {\n\t\tthrottle = pollThrottling\n\t}\n\n\tgo func() {\n\t\tfor msg := range worker.Desk {\n\t\t\tswitch msg.order {\n\t\t\tcase closeAccountOrder:\n\t\t\t\tif msg.account != nil {\n\t\t\t\t\tworker.RemoveAccountHandler(msg.account)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tlog.Debugf(\"[TwitterWorker] received unknown order « %s » from account %s\", msg.order, msg.account.userAccount.userID.String()+msg.account.userAccount.remoteID.String())\n\t\t\t}\n\t\t}\n\t}()\n\n\t// start throttled jobs polling\n\tlog.Infof(\"Twitter worker %s starting with %d sec throttling\", worker.Id, throttle/time.Second)\n\tfor {\n\t\tstart := time.Now()\n\t\trequestOrder := []byte(fmt.Sprintf(needJobOrderStr, worker.Id))\n\t\tlog.Infof(\"Twitter worker %s is requesting jobs to idpoller\", worker.Id)\n\t\tresp, err := worker.NatsConn.Request(worker.Conf.BrokerConfig.NatsTopicPoller, requestOrder, time.Minute)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[worker %s] failed to request pending jobs on nats\", worker.Id)\n\t\t} else {\n\t\t\tworker.WorkerMsgHandler(resp)\n\t\t}\n\t\t// check for interrupt after job is finished\n\t\tif worker.HaltGroup != nil {\n\t\t\tworker.stop()\n\t\t\tbreak\n\t\t}\n\t\telapsed := time.Now().Sub(start)\n\t\tif elapsed < throttle {\n\t\t\ttime.Sleep(throttle - elapsed)\n\t\t}\n\t}\n}\n\nfunc (worker *Worker) stop() {\n\tfor _, w := range worker.AccountHandlers {\n\t\tw.AccountDesk <- Stop\n\t}\n\tfor _, sub := range worker.NatsSubs {\n\t\tsub.Unsubscribe()\n\t}\n\tworker.NatsConn.Close()\n\tworker.Store.Close()\n\tworker.Index.Close()\n\tworker.HaltGroup.Done()\n\tclose(worker.Desk)\n\tlog.Infof(\"worker %s stopped\", worker.Id)\n}\n\n// getOrCreateHandler returns a pointer to a worker already in cache\n// or tries to create a new worker for the remote identity if not.\n// returns nil if get or create failed.\nfunc (w *Worker) getOrCreateHandler(userId, remoteId string) *AccountHandler {\n\tw.WorkersGuard.RLock()\n\tif accountHandler, ok := w.AccountHandlers[userId+remoteId]; ok {\n\t\tw.WorkersGuard.RUnlock()\n\t\treturn accountHandler\n\t} else {\n\t\tw.WorkersGuard.RUnlock()\n\t\tlog.Infof(\"[getOrCreateHandler] failed to retrieve registered worker for remote %s (user %s). Trying to add one.\", remoteId, userId)\n\t\tif userId == \"\" || remoteId == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\taccountHandler, err := NewAccountHandler(userId, remoteId, *w)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[getOrCreateHandler] failed to create new worker for remote %s (user %s)\", remoteId, userId)\n\t\t\treturn nil\n\t\t}\n\t\tw.RegisterAccountHandler(accountHandler)\n\t\tgo accountHandler.Start()\n\t\treturn accountHandler\n\n\t}\n}\n\nfunc (w *Worker) RegisterAccountHandler(accountHandler *AccountHandler) {\n\tworkerKey := accountHandler.userAccount.userID.String() + accountHandler.userAccount.remoteID.String()\n\t// stop & remove handler first if it's already registered\n\tw.WorkersGuard.RLock()\n\tregisteredHandler, ok := w.AccountHandlers[workerKey]\n\tw.WorkersGuard.RUnlock()\n\tif ok {\n\t\tw.RemoveAccountHandler(registeredHandler)\n\t}\n\tw.WorkersGuard.Lock()\n\tw.AccountHandlers[workerKey] = accountHandler\n\tw.WorkersGuard.Unlock()\n}\n\nfunc (w *Worker) RemoveAccountHandler(accountHandler *AccountHandler) {\n\tworkerKey := accountHandler.userAccount.userID.String() + accountHandler.userAccount.remoteID.String()\n\tw.WorkersGuard.Lock()\n\taccountHandler.Stop()\n\tdelete(w.AccountHandlers, workerKey)\n\tw.WorkersGuard.Unlock()\n}\n"
  },
  {
    "path": "src/backend/protocols/go.twitter/worker_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage twitterworker\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/brokers/go.twitter\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"github.com/nats-io/gnatsd/server\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/phayes/freeport\"\n\t\"github.com/satori/go.uuid\"\n\t\"math/rand\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst (\n\tnatsUrl = \"0.0.0.0\"\n)\n\nfunc initWorkerTest() (worker *Worker, natsServer *server.Server, err error) {\n\t// starting an embedded nats server\n\tport, err := freeport.GetFreePort()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tnatsServer, err = server.NewServer(&server.Options{\n\t\tHost:     natsUrl,\n\t\tPort:     port,\n\t\tHTTPPort: -1,\n\t\tCluster:  server.ClusterOpts{Port: -1},\n\t\tNoLog:    true,\n\t\tNoSigs:   true,\n\t\tDebug:    false,\n\t\tTrace:    false,\n\t})\n\tif err != nil || natsServer == nil {\n\t\tpanic(fmt.Sprintf(\"No NATS Server object returned: %v\", err))\n\t}\n\tgo natsServer.Start()\n\t// Wait for accept loop(s) to be started\n\tif !natsServer.ReadyForConnections(10 * time.Second) {\n\t\tpanic(\"Unable to start NATS Server in Go Routine\")\n\t}\n\n\tworker = &Worker{\n\t\tAccountHandlers: map[string]*AccountHandler{},\n\t\tConf: WorkerConfig{\n\t\t\tBrokerConfig: twitter_broker.BrokerConfig{\n\t\t\t\tNatsQueue:            \"Twitterworkers\",\n\t\t\t\tNatsTopicPoller:      \"twitterJobs\",\n\t\t\t\tNatsTopicPollerCache: \"idCache\",\n\t\t\t\tNatsTopicDMs:         \"twitter_dm\",\n\t\t\t},\n\t\t},\n\t\tId:           \"worker_id\",\n\t\tIndex:        backendstest.GetLDAIndexBackend(),\n\t\tStore:        backendstest.GetLDAStoreBackend(),\n\t\tWorkersGuard: new(sync.RWMutex),\n\t}\n\tworker.NatsConn, err = nats.Connect(\"nats://\" + natsUrl + \":\" + strconv.Itoa(port))\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"[initMqHandler] failed to init NATS connection : %s\", err)\n\t}\n\tworker.NatsSubs = make([]*nats.Subscription, 1)\n\tworker.NatsSubs[0], err = worker.NatsConn.QueueSubscribe(worker.Conf.BrokerConfig.NatsTopicDMs, worker.Conf.BrokerConfig.NatsQueue, worker.DMmsgHandler)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tworker.Desk = make(chan DeskMessage)\n\tgo func() {\n\t\tfor msg := range worker.Desk {\n\t\t\tswitch msg.order {\n\t\t\tcase closeAccountOrder:\n\t\t\t\tif msg.account != nil {\n\t\t\t\t\tworker.RemoveAccountHandler(msg.account)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\treturn\n}\n\nfunc TestWorker_StartAndStop(t *testing.T) {\n\tw, s, err := initWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer s.Shutdown()\n\n\t// test if worker requests on Nats every second with the right payload\n\tc := make(chan struct{})\n\twg := new(sync.WaitGroup)\n\twg.Add(1)\n\tgo w.Start(time.Second)\n\tcount := 0\n\t_, err = w.NatsConn.Subscribe(\"twitterJobs\", func(msg *nats.Msg) {\n\t\tvar req WorkerRequest\n\t\terr := json.Unmarshal(msg.Data, &req)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unable to unmarshal worker's request : %s\", err)\n\t\t\treturn\n\t\t}\n\t\tif req.Order.Order != \"need_job\" {\n\t\t\tt.Errorf(\"expected to receive order 'need_job', got %s\", req.Order.Order)\n\t\t}\n\t\tcount++\n\t\tw.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"no pending job\"}`))\n\t\tif count == 3 {\n\t\t\twg.Done()\n\t\t\treturn\n\t\t}\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(c)\n\t}()\n\tselect {\n\tcase <-c:\n\t\t// worker confirmed to send request every second ; now test halting\n\t\tw.HaltGroup = new(sync.WaitGroup)\n\t\tw.HaltGroup.Add(1)\n\t\ttime.Sleep(500 * time.Millisecond)\n\t\tif !w.NatsConn.IsClosed() {\n\t\t\tt.Error(\"expected worker's nats connexion to be closed\")\n\t\t}\n\t\tfor _, sub := range w.NatsSubs {\n\t\t\tif sub.IsValid() {\n\t\t\t\tt.Errorf(\"expected all worker's subscription closed, got <%s> still valid\", sub.Subject)\n\t\t\t}\n\t\t}\n\t\treturn\n\tcase <-time.After(5 * time.Second):\n\t\tt.Error(\"timeout waiting for twitter worker to send requests on nats\")\n\t}\n}\n\nfunc TestWorker_RegisterAccountHandler(t *testing.T) {\n\tw, s, err := initWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer s.Shutdown()\n\n\t// test concurrent account handler registration\n\tconst count = 1000 // must be an even number\n\tworkers := [count + 1]string{}\n\twg := new(sync.WaitGroup)\n\twg.Add(count)\n\tc := make(chan struct{})\n\tfor i := 0; i < count; i++ {\n\t\tgo func(indice int) {\n\t\t\thandler := new(AccountHandler)\n\t\t\tuserId := UUID(uuid.NewV4())\n\t\t\tremoteId := UUID(uuid.NewV4())\n\t\t\tworkers[indice] = userId.String() + remoteId.String()\n\t\t\thandler.userAccount = &TwitterAccount{\n\t\t\t\tremoteID:  remoteId,\n\t\t\t\ttwitterID: strconv.Itoa(indice),\n\t\t\t\tuserID:    userId,\n\t\t\t}\n\t\t\thandler.broker = &twitter_broker.TwitterBroker{\n\t\t\t\tNatsConn: w.NatsConn,\n\t\t\t\tStore:    backendstest.GetLDAStoreBackend(),\n\t\t\t\tIndex:    backendstest.GetLDAIndexBackend(),\n\t\t\t\tConnectors: twitter_broker.TwitterBrokerConnectors{\n\t\t\t\t\tEgress: make(chan twitter_broker.NatsCom),\n\t\t\t\t\tHalt:   make(chan struct{}),\n\t\t\t\t},\n\t\t\t}\n\t\t\thandler.AccountDesk = make(chan uint)\n\t\t\tw.RegisterAccountHandler(handler)\n\t\t\twg.Done()\n\t\t}(i)\n\t}\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(c)\n\t}()\n\n\tselect {\n\tcase <-c:\n\t\tif len(w.AccountHandlers) != count {\n\t\t\tt.Errorf(\"expected %d accountHandlers, got %d\", count, len(w.AccountHandlers))\n\t\t}\n\t\t// pick few handlers randomly to test AccountHandlers consistency\n\t\trand.Seed(time.Now().Unix())\n\t\tfor i := 0; i < count/2; i++ {\n\t\t\tpick := rand.Intn(count)\n\t\t\tif handler, ok := w.AccountHandlers[workers[pick]]; ok {\n\t\t\t\thandlerKey := handler.userAccount.userID.String() + handler.userAccount.remoteID.String()\n\t\t\t\tif handlerKey != workers[pick] {\n\t\t\t\t\tt.Errorf(\"expected handler's key to be %s, got %s\", workers[pick], handlerKey)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tt.Errorf(\"expected to find handler with key %s, got nothing\", workers[pick])\n\t\t\t}\n\t\t}\n\t\t// re-register an existing handler to test stop&remove operations\n\t\tpick := rand.Intn(count)\n\t\thandler := w.AccountHandlers[workers[pick]]\n\t\tconst s = \"register twice\"\n\t\t(*handler).userAccount.screenName = s\n\t\tw.RegisterAccountHandler(handler)\n\t\tif w.AccountHandlers[workers[pick]].userAccount.screenName != s {\n\t\t\tt.Errorf(\"expected userAccount.screenName of re-registered worker to be %s, got %s\", s, w.AccountHandlers[workers[pick]].userAccount.screenName)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Error(\"timeout waiting for concurrent RegisterAccountHandler\")\n\t}\n\n}\n\nfunc TestWorker_getOrCreateHandler(t *testing.T) {\n\tw, s, err := initWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer s.Shutdown()\n\n\t// test automatic creation of AccountHandler\n\tuserId := backendstest.EmmaTommeUserId\n\tremoteId := \"b91f0fa8-17a2-4729-8a5a-5ff58ee5c121\"\n\thandler := w.getOrCreateHandler(userId, remoteId)\n\tif handler == nil {\n\t\tt.Error(\"expected a new handler, got nil\")\n\t}\n}\n\nfunc TestWorker_RemoveAccountHandler(t *testing.T) {\n\tw, s, err := initWorkerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tdefer s.Shutdown()\n\t// add a bunch of workers\n\tconst count = 1000 // must be an even number\n\tworkers := [count + 1][2]string{}\n\tfor i := 0; i < count; i++ {\n\t\thandler := new(AccountHandler)\n\t\tuserId := UUID(uuid.NewV4())\n\t\tremoteId := UUID(uuid.NewV4())\n\t\tworkers[i] = [2]string{userId.String(), remoteId.String()}\n\t\thandler.userAccount = &TwitterAccount{\n\t\t\tremoteID:  remoteId,\n\t\t\ttwitterID: strconv.Itoa(i),\n\t\t\tuserID:    userId,\n\t\t}\n\t\thandler.broker = &twitter_broker.TwitterBroker{\n\t\t\tNatsConn: w.NatsConn,\n\t\t\tStore:    backendstest.GetLDAStoreBackend(),\n\t\t\tIndex:    backendstest.GetLDAIndexBackend(),\n\t\t\tConnectors: twitter_broker.TwitterBrokerConnectors{\n\t\t\t\tEgress: make(chan twitter_broker.NatsCom),\n\t\t\t\tHalt:   make(chan struct{}),\n\t\t\t},\n\t\t}\n\t\thandler.AccountDesk = make(chan uint)\n\t\tw.RegisterAccountHandler(handler)\n\t}\n\n\t// concurrently get & remove half of workers\n\twg := new(sync.WaitGroup)\n\twg.Add(count / 2)\n\tc := make(chan struct{})\n\tfor i := 0; i < count/2; i++ {\n\t\tgo func(indice int) {\n\t\t\tah := w.getOrCreateHandler(workers[indice][0], workers[indice][1])\n\t\t\tif ah != nil {\n\t\t\t\tw.RemoveAccountHandler(ah)\n\t\t\t} else {\n\t\t\t\tt.Error(\"expcted to get a worker, got nil\")\n\t\t\t}\n\t\t\twg.Done()\n\t\t}(i)\n\t}\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(c)\n\t}()\n\tselect {\n\tcase <-c:\n\t\tif len(w.AccountHandlers) != count/2 {\n\t\t\tt.Errorf(\"expected %d account handlers left, got %d\", count/2, len(w.AccountHandlers))\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Error(\"timeout waiting for concurrent RemoveAccountHandler\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/tools/go.CLI/cmd/gocaliopen/cli_cmds/changeIdentitiyEmailProtocol.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar emailProtocolCmd = &cobra.Command{\n\tUse:   \"changeIdentityEmailProtocol\",\n\tShort: \"rename protocol from `imap`|`smtp` to `email` in UserIdentity\",\n\tLong:  `command updates protocol name for email to conform to new specs`,\n\tRun:   emailProtocolMigration,\n}\n\nfunc init() {\n\tRootCmd.AddCommand(emailProtocolCmd)\n}\n\nfunc emailProtocolMigration(cmd *cobra.Command, args []string) {\n\tvar err error\n\t//check/get connexions to facilities\n\t//store\n\tvar Store *store.CassandraBackend\n\tStore, err = getStoreFacility()\n\tif err != nil {\n\t\tlog.WithError(err).Fatalf(\"initialization of %s backend failed\", apiConf.BackendName)\n\t}\n\tdefer Store.Close()\n\n\t/* get all identities */\n\terroneousUsers := []string{}\n\tif lookups, err := Store.Session.Query(`SELECT user_id, identity_id, protocol FROM user_identity`).Iter().SliceMap(); err != nil {\n\t\tlog.WithError(err).Fatal(\"failed to retrieve remote identities\")\n\t} else {\n\t\tlog.Infof(\"\\nFound %d identities to work with\\n\", len(lookups))\n\t\tfor _, lookup := range lookups {\n\t\t\tif lookup[\"protocol\"].(string) == objects.SmtpProtocol || lookup[\"protocol\"].(string) == objects.ImapProtocol {\n\t\t\t\tvar userId string\n\t\t\t\tvar remoteId string\n\t\t\t\tuserId = lookup[\"user_id\"].(gocql.UUID).String()\n\t\t\t\tremoteId = lookup[\"identity_id\"].(gocql.UUID).String()\n\n\t\t\t\tuserIdentity, err := Store.RetrieveUserIdentity(userId, remoteId, true)\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(\"failed to retrieve identity <%s> for user <%s>\", userId, remoteId)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tupdateFields := map[string]interface{}{}\n\t\t\t\tuserIdentity.Protocol = objects.EmailProtocol\n\t\t\t\tupdateFields[\"Protocol\"] = objects.EmailProtocol\n\n\t\t\t\terr = Store.UpdateUserIdentity(userIdentity, updateFields)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Warnf(\"failed to update identity <%s> for user <%s>\", userIdentity.Id.String(), userIdentity.UserId.String())\n\t\t\t\t} else {\n\t\t\t\t\tStore.Session.Query(`DELETE FROM identity_lookup WHERE identifier = ? AND protocol = ? AND user_id = ?`, userIdentity.Identifier, lookup[\"protocol\"].(string), userIdentity)\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t\tlog.Info(\"\\nAll done\\n\")\n\t\tif len(erroneousUsers) > 0 {\n\t\t\tlog.Warnf(\"Users with errors : %v\", erroneousUsers)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/backend/tools/go.CLI/cmd/gocaliopen/cli_cmds/changeUserIdentitiesCredentialsKeys.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar credentialsKeysMigrationCmd = &cobra.Command{\n\tUse:   \"credentialsKeysMigration\",\n\tShort: \"rename keys in UserIdentity.Infos and .Credentials to conform to new specs\",\n\tLong: `command updates keys name to conform to new specs after branch \"imap-outbound\" has been merged in prod\"`,\n\tRun: credentialsKeysMigration,\n}\n\nfunc init() {\n\tRootCmd.AddCommand(credentialsKeysMigrationCmd)\n}\n\nfunc credentialsKeysMigration(cmd *cobra.Command, args []string) {\n\tvar err error\n\t//check/get connexions to facilities\n\t//store\n\tvar Store *store.CassandraBackend\n\tStore, err = getStoreFacility()\n\tif err != nil {\n\t\tlog.WithError(err).Fatalf(\"initialization of %s backend failed\", apiConf.BackendName)\n\t}\n\tdefer Store.Close()\n\n\t/* get all remote_identities */\n\terroneousUsers := []string{}\n\tif lookups, err := Store.Session.Query(`SELECT * FROM identity_type_lookup WHERE type='remote'`).Iter().SliceMap(); err != nil {\n\t\tlog.WithError(err).Fatal(\"failed to retrieve remote identities\")\n\t} else {\n\t\tlog.Infof(\"\\nFound %d identities to work with\\n\", len(lookups))\n\t\tfor _, lookup := range lookups {\n\t\t\tvar userId string\n\t\t\tvar remoteId string\n\t\t\tuserId = lookup[\"user_id\"].(gocql.UUID).String()\n\t\t\tremoteId = lookup[\"identity_id\"].(gocql.UUID).String()\n\n\t\t\tuserIdentity, err := Store.RetrieveUserIdentity(userId, remoteId, true)\n\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"failed to retrieve identity <%s> for user <%s>\", userId, remoteId)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tupdateFields := map[string]interface{}{}\n\t\t\tupdateInfos := map[string]string{}\n\t\t\tupdateCredentials := objects.Credentials{}\n\n\t\t\t//copy values from infos' map but `server` key\n\t\t\tfor k,v := range userIdentity.Infos {\n\t\t\t\tif k == \"server\" {\n\t\t\t\t\tupdateInfos[\"inserver\"] = v\n\t\t\t\t} else if k != \"inserver\"{\n\t\t\t\t\tupdateInfos[k] = v\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//copy values from credentials' map but `password` and `username`\n\t\t\tif userIdentity.Credentials != nil {\n\t\t\t\tfor k, v := range *userIdentity.Credentials {\n\t\t\t\t\tif k == \"password\" {\n\t\t\t\t\t\tupdateCredentials[\"inpassword\"] = v\n\t\t\t\t\t} else if k == \"username\" {\n\t\t\t\t\t\tupdateCredentials[\"inusername\"] = v\n\t\t\t\t\t} else {\n\t\t\t\t\t\tupdateCredentials[k] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tupdateFields[\"Infos\"] = updateInfos\n\t\t\tupdateFields[\"Credentials\"] = &updateCredentials\n\n\t\t\terr = Store.UpdateUserIdentity(userIdentity, updateFields)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warnf(\"failed to update identity <%s> for user <%s>\", userIdentity.Id.String(), userIdentity.UserId.String())\n\t\t\t}\n\t\t}\n\t\tlog.Info(\"\\nAll done\\n\")\n\t\tif len(erroneousUsers) > 0 {\n\t\t\tlog.Warnf(\"Users with errors : %v\", erroneousUsers)\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "src/backend/tools/go.CLI/cmd/gocaliopen/cli_cmds/fixMissingParticipants.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/index/elasticsearch\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/spf13/cobra\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n)\n\n// fixMissingParticipantsCmd represents the fixMissingParticipants command\nvar fixMissingParticipantsCmd = &cobra.Command{\n\tUse:   \"fixMissingParticipants\",\n\tShort: \"Fill missing message.participants in db and index\",\n\tLong: `This command iterates over all messages in cassandra to find messages that miss participants.\n\tIf message.participants exists in index, db version is filled with it.\n\tIf not, command will reinject raw_message in stack to trigger again message inbound processing.`,\n\tRun: fixMissingParticipants,\n}\n\nfunc init() {\n\tRootCmd.AddCommand(fixMissingParticipantsCmd)\n\n\t// Here you will define your flags and configuration settings.\n\n\t// Cobra supports Persistent Flags which will work for this command\n\t// and all subcommands, e.g.:\n\t// fixMissingParticipantsCmd.PersistentFlags().String(\"foo\", \"\", \"A help for foo\")\n\n\t// Cobra supports local flags which will only run when this command\n\t// is called directly, e.g.:\n\t// fixMissingParticipantsCmd.Flags().BoolP(\"toggle\", \"t\", false, \"Help message for toggle\")\n}\n\nfunc fixMissingParticipants(cmd *cobra.Command, args []string) {\n\tvar err error\n\t//check/get connexions to facilities\n\t//store\n\tvar Store *store.CassandraBackend\n\n\tStore, err = getStoreFacility()\n\tif err != nil {\n\t\tlog.WithError(err).Fatalf(\"initialization of %s backend failed\", apiConf.BackendName)\n\t}\n\tdefer Store.Close()\n\t//index\n\tvar Index *index.ElasticSearchBackend\n\tIndex, err = getIndexFacility()\n\tif err != nil {\n\t\tlog.WithError(err).Fatalf(\"initialization of %s index failed\", apiConf.IndexConfig.IndexName)\n\t}\n\tdefer Index.Close()\n\t//nats\n\tvar MsgQueue *nats.Conn\n\tMsgQueue, err = getMsgSystemFacility()\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"initiazation of message queue failed\")\n\t}\n\n\tvar total int\n\tcount := 0\n\tinvalidCount := 0\n\tfixedCount := 0\n\tfailedCount := 0\n\t//\n\thandleInvalidMessage := func(msgId, usrId, rawMsgId gocql.UUID) {\n\t\tinvalidCount++\n\t\t//try to fetch message from index and check if it has participants\n\t\tresult, err := Index.Client.Get().Index(usrId.String()).Type(objects.MessageIndexType).Id(msgId.String()).Do(context.TODO())\n\t\tif err != nil && !strings.Contains(err.Error(), \"Not Found\") {\n\t\t\tlog.Warn(err)\n\t\t\tfailedCount++\n\t\t\treturn\n\t\t}\n\t\tif result != nil && result.Found {\n\t\t\tmsg := new(objects.Message).NewEmpty().(*objects.Message)\n\t\t\tif err := json.Unmarshal(*result.Source, msg); err != nil {\n\t\t\t\tlog.Warn(err)\n\t\t\t\tfailedCount++\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = msg.Message_id.UnmarshalBinary(msgId.Bytes())\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warn(\"failed to unmarshal messageId\")\n\t\t\t\tfailedCount++\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif msg.Participants != nil && len(msg.Participants) > 0 {\n\t\t\t\terr = Store.UpdateMessage(msg, map[string]interface{}{\n\t\t\t\t\t\"Participants\": msg.Participants,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(\"failed to update message in db\")\n\t\t\t\t\tfailedCount++\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfixedCount++\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tlog.Infoln(\"failed to retrieve participant from index, trying to reinject raw message\")\n\t\tvar newMsgId string\n\t\tnewMsgId, err = reInjectRaw(usrId.String(), rawMsgId.String(), MsgQueue)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warn(\"failed to re-inject raw message in stack\")\n\t\t\tfailedCount++\n\t\t\treturn\n\t\t}\n\t\t//reinjection OK, get previous status and delete former invalid message\n\t\tformerMsg, err := Store.RetrieveMessage(usrId.String(), msgId.String())\n\t\tif err == nil {\n\t\t\tnewMsg, err := Store.RetrieveMessage(usrId.String(), newMsgId)\n\t\t\tif err == nil {\n\t\t\t\terr = Store.UpdateMessage(newMsg, map[string]interface{}{\n\t\t\t\t\t\"Date_sort\":   formerMsg.Date_sort,\n\t\t\t\t\t\"Is_answered\": formerMsg.Is_answered,\n\t\t\t\t\t\"Is_unread\":   formerMsg.Is_unread,\n\t\t\t\t\t\"Tags\":        formerMsg.Tags,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(\"failed to update new message with former status %s\", msgId)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\terr = Store.Session.Query(`DELETE FROM message WHERE message_id = ? AND user_id = ?`, msgId, usrId).Exec()\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"failed to delete former invalid message %s\", msgId)\n\t\t}\n\t\tfixedCount++\n\n\t}\n\n\t//get an iterator on table message and iterate over all messages\n\tStore.Session.SetTrace(gocql.NewTraceWriter(Store.Session, os.Stdout))\n\tStore.Session.Query(`SELECT count(*) FROM message`).Scan(&total)\n\tmsgIterator := Store.Session.Query(`SELECT message_id, user_id, raw_msg_id, participants FROM message`).PageSize(500).NoSkipMetadata().Iter()\n\tif msgIterator == nil {\n\t\tlog.Fatal(\"fail to get iterator on table message\")\n\t}\n\tvar msgId gocql.UUID\n\tvar usrId gocql.UUID\n\tvar rawMsgId gocql.UUID\n\tvar participants []map[string]interface{}\n\tfor msgIterator.Scan(&msgId, &usrId, &rawMsgId, &participants) {\n\t\tcount++\n\t\tif len(msgIterator.Warnings()) > 0 {\n\t\t\tlog.Info(msgIterator.Warnings())\n\t\t}\n\t\tif len(participants) == 0 {\n\t\t\thandleInvalidMessage(msgId, usrId, rawMsgId)\n\t\t}\n\t}\n\tmsgIterator.Close()\n\tif count < total {\n\t\tlog.Warnf(\"for some reason, only %d messages have been scan, out of %d\", count, total)\n\t} else {\n\t\tlog.Infof(\"%d messages scanned\", count)\n\t}\n\tlog.Infof(\"%d invalid messages handled => %d fixed, %d failed\", invalidCount, fixedCount, failedCount)\n\n}\n\nfunc reInjectRaw(userId, msgId string, msgQueue *nats.Conn) (newMsgId string, err error) {\n\tconst nats_message_tmpl = \"{\\\"order\\\":\\\"process_raw\\\",\\\"user_id\\\": \\\"%s\\\", \\\"message_id\\\": \\\"%s\\\"}\"\n\tnatsMessage := fmt.Sprintf(nats_message_tmpl, userId, msgId)\n\n\tresp, err := msgQueue.Request(lmtpConf.LDAConfig.InTopic, []byte(natsMessage), 10*time.Second)\n\tif err != nil {\n\t\tif msgQueue.LastError() != nil {\n\t\t\tlog.WithError(msgQueue.LastError()).Warnf(\"[EmailBroker] failed to publish inbound request on NATS for user %s\", userId)\n\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\t\treturn\n\t\t} else {\n\t\t\tlog.WithError(err).Warnf(\"[EmailBroker] failed to publish inbound request on NATS for user %s\", userId)\n\t\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\t\treturn\n\t\t}\n\t}\n\n\tnats_ack := new(map[string]interface{})\n\terr = json.Unmarshal(resp.Data, &nats_ack)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[EmailBroker] failed to parse inbound ack on NATS for user %s\", userId)\n\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\treturn\n\t}\n\tif e, ok := (*nats_ack)[\"error\"]; ok {\n\t\tlog.WithError(errors.New(e.(string))).Warnf(\"[EmailBroker] inbound delivery failed for user %s\", userId)\n\t\tlog.Infof(\"natsMessage: %s\\nnatsResponse: %+v\\n\", natsMessage, resp)\n\t\terr = errors.New(e.(string))\n\t\treturn\n\t}\n\n\tnewMsgId = (*nats_ack)[\"message_id\"].(string)\n\treturn\n}\n"
  },
  {
    "path": "src/backend/tools/go.CLI/cmd/gocaliopen/cli_cmds/identitiesMigration.go",
    "content": "// Copyleft (ɔ) 2018 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/REST\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/satori/go.uuid\"\n\t\"github.com/spf13/cobra\"\n\t\"time\"\n)\n\nvar identitiesMigrationCmd = &cobra.Command{\n\tUse:   \"identitiesMigration\",\n\tShort: \"manage db and index migration for new model UserIdentity\",\n\tLong: `command merges LocalIdentities and RemoteIdentities into new UserIdentities,\n\tbuilds lookup tables, then iterates over all messages in db and index to replace identities by user_identities`,\n\tRun: identitiesMigration,\n}\n\nfunc init() {\n\tRootCmd.AddCommand(identitiesMigrationCmd)\n}\n\nfunc identitiesMigration(cmd *cobra.Command, args []string) {\n\t// this script needs to read usernames from vault\n\t// replace apiv2's credentials with imapworker's credentials\n\tapiConf.BackendConfig.Settings.VaultSettings.Username = imapWorkerConf.StoreConfig.VaultConfig.Username\n\tapiConf.BackendConfig.Settings.VaultSettings.Password = imapWorkerConf.StoreConfig.VaultConfig.Password\n\tapiConf.BackendConfig.Settings.VaultSettings.Url = imapWorkerConf.StoreConfig.VaultConfig.Url\n\n\tvar err error\n\t//check/get connexions to facilities\n\t//store\n\tvar Store *store.CassandraBackend\n\tStore, err = getStoreFacility()\n\tif err != nil {\n\t\tlog.WithError(err).Fatalf(\"initialization of %s backend failed\", apiConf.BackendName)\n\t}\n\tdefer Store.Close()\n\n\tvar Rest *REST.RESTfacility\n\tRest, err = getRESTFacility()\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"initialization of ReST facility failed\")\n\t}\n\n\t/* get all users id */\n\terroneousUsers := []string{}\n\terroneousMessages := []string{}\n\tif users, err := Store.Session.Query(`SELECT user_id, shard_id, local_identities FROM user`).Iter().SliceMap(); err != nil {\n\t\tlog.WithError(err).Fatal(\"failed to retrieve user ids\")\n\t} else {\n\t\tusers_total := len(users)\n\t\tusers_count := 0\n\t\tlog.Infof(\"\\nFound %d users to work with\\n\", users_total)\n\t\tfor _, user := range users {\n\t\t\tusers_count++\n\t\t\tlog.Infof(\"starting migration for user %d/%d\", users_count, users_total)\n\t\t\tvar userId UUID\n\t\t\tuserId.UnmarshalBinary(user[\"user_id\"].(gocql.UUID).Bytes())\n\t\t\tshardId := user[\"shard_id\"].(string)\n\t\t\tvar localId *UUID\n\t\t\tvar err error\n\n\t\t\t// check if a local user_identity has already been created\n\t\t\tlocalIds, err := Rest.RetrieveLocalIdentities(userId.String())\n\t\t\tif err != nil || len(localIds) == 0 {\n\t\t\t\tlocalId, err = createLocal(user, Store, erroneousUsers, userId, Rest)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(\"createLocal returned error for user %s\", userId.String())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlocalId = &(localIds[0].Id)\n\t\t\t}\n\n\t\t\terr = createRemotes(Store, erroneousUsers, userId, Rest)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"createRemote returned error for user %s\", userId.String())\n\t\t\t}\n\n\t\t\tif localId != nil {\n\t\t\t\terr = updateSentMessages(Store, userId, shardId, Rest, erroneousMessages, *localId)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(\"updateSentMessages returned error for user %s\", userId.String())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlog.Info(\"\\nAll done\\n\")\n\t\tif len(erroneousUsers) > 0 {\n\t\t\tlog.Warnf(\"Users with errors : %v\", erroneousUsers)\n\t\t}\n\t\tif len(erroneousMessages) > 0 {\n\t\t\tlog.Warnf(\"Messages with errors : %v\", erroneousMessages)\n\t\t}\n\t}\n}\n\nfunc createLocal(user map[string]interface{}, Store *store.CassandraBackend, erroneousUsers []string, userId UUID, Rest *REST.RESTfacility) (localId *UUID, err error) {\n\tlog.Info(\"creating UserIdentity for local\")\n\tlocal := map[string]interface{}{}\n\tidentifier := user[\"local_identities\"].([]string)\n\tif len(identifier) > 0 {\n\t\terr = Store.Session.Query(`SELECT * FROM local_identity WHERE identifier = ?`, identifier[0]).MapScan(local)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"failed to retrieve local identity '%s' for user %s\", identifier[0], userId.String())\n\t\t\terroneousUsers = append(erroneousUsers, userId.String())\n\t\t\treturn\n\t\t}\n\t\tuid := new(UUID)\n\t\terr = uid.UnmarshalBinary(uuid.NewV4().Bytes())\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"failed to create uuid for local identity for user %s\", userId.String())\n\t\t\terroneousUsers = append(erroneousUsers, userId.String())\n\t\t\treturn\n\t\t}\n\t\tlocalUser := UserIdentity{\n\t\t\tDisplayName: local[\"display_name\"].(string),\n\t\t\tId:          *uid,\n\t\t\tIdentifier:  local[\"identifier\"].(string),\n\t\t\tProtocol:    \"smtp\",\n\t\t\tStatus:      \"active\",\n\t\t\tType:        \"local\",\n\t\t\tUserId:      userId,\n\t\t}\n\t\tcaliopenErr := Rest.CreateUserIdentity(&localUser)\n\t\tif caliopenErr != nil {\n\t\t\tlog.WithError(caliopenErr).Warnf(\"failed to save local identity %v for user %s\", localUser, userId.String())\n\t\t\terroneousUsers = append(erroneousUsers, userId.String())\n\t\t\treturn nil, caliopenErr.Cause()\n\t\t}\n\t\treturn uid, nil\n\t}\n\treturn nil, errors.New(\"local_identities empty\")\n}\n\nfunc createRemotes(Store *store.CassandraBackend, erroneousUsers []string, userId UUID, Rest *REST.RESTfacility) (err error) {\n\tlog.Info(\"creating UserIdentity for remotes\")\n\tremotes, err := Store.Session.Query(`SELECT * from remote_identity WHERE user_id = ?`, userId.String()).Iter().SliceMap()\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"failed to retrieve remote identities for user %s\", userId.String())\n\t\terroneousUsers = append(erroneousUsers, userId.String())\n\t\treturn\n\t}\n\n\tfor _, remote := range remotes {\n\t\tremoteID := new(UserIdentity)\n\t\tremoteID.NewEmpty()\n\n\t\tif dn, ok := remote[\"display_name\"].(string); ok {\n\t\t\tremoteID.DisplayName = dn\n\t\t}\n\n\t\tif infos, ok := remote[\"infos\"].(map[string]string); ok {\n\t\t\tremoteID.Infos = make(map[string]string)\n\t\t\tfor k, v := range infos {\n\t\t\t\tremoteID.Infos[k] = v\n\t\t\t}\n\t\t}\n\t\tif lc, ok := remote[\"last_check\"].(time.Time); ok {\n\t\t\tremoteID.LastCheck = lc\n\t\t}\n\t\tif remote_id, ok := remote[\"remote_id\"].(gocql.UUID); ok {\n\t\t\tremoteID.Id.UnmarshalBinary(remote_id.Bytes())\n\t\t}\n\t\tif status, ok := remote[\"status\"].(string); ok {\n\t\t\tremoteID.Status = status\n\t\t}\n\t\tif t, ok := remote[\"type\"].(string); ok {\n\t\t\tremoteID.Protocol = t\n\t\t}\n\t\tif userid, ok := remote[\"user_id\"].(gocql.UUID); ok {\n\t\t\tremoteID.UserId.UnmarshalBinary(userid.Bytes())\n\t\t}\n\n\t\t//try to fill-in identifier from username or display name\n\t\tif apiConf.BackendConfig.Settings.UseVault {\n\t\t\tcred, err := Store.RetrieveCredentials(userId.String(), remoteID.Id.String())\n\t\t\tif err != nil {\n\t\t\t\tremoteID.Identifier = remoteID.DisplayName\n\t\t\t} else {\n\t\t\t\tif username, ok := cred[\"username\"]; ok {\n\t\t\t\t\tremoteID.Identifier = username\n\t\t\t\t} else {\n\t\t\t\t\tremoteID.Identifier = remoteID.DisplayName\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif credentials, ok := remote[\"credentials\"]; ok && credentials != nil {\n\t\t\t\tcred := &Credentials{}\n\t\t\t\tcred.UnmarshalCQLMap(credentials.(map[string]string))\n\t\t\t\tremoteID.Credentials = cred\n\t\t\t\tif username, ok := credentials.(map[string]string)[\"username\"]; ok {\n\t\t\t\t\tremoteID.Identifier = username\n\t\t\t\t} else {\n\t\t\t\t\tremoteID.Identifier = remoteID.DisplayName\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tremoteID.Identifier = remoteID.DisplayName\n\t\t\t}\n\t\t}\n\n\t\tremoteID.Type = RemoteIdentity\n\t\tcaliopenErr := Rest.CreateUserIdentity(remoteID)\n\t\tif caliopenErr != nil {\n\t\t\tlog.WithError(caliopenErr).Warnf(\"failed to save remote identity %v for user %s\", *remoteID, userId.String())\n\t\t\terroneousUsers = append(erroneousUsers, userId.String())\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc updateSentMessages(Store *store.CassandraBackend, userId UUID, shardId string, Rest *REST.RESTfacility, erroneousMessages []string, localId UUID) error {\n\t/* get all user's messages id from store */\n\tif messages, err := Store.Session.Query(`SELECT message_id, is_received FROM message WHERE user_id = ? ALLOW FILTERING`, userId.String()).Iter().SliceMap(); err != nil {\n\t\tlog.WithError(err).Errorf(\"failed to retrieve messages for user %s\", userId.String())\n\t\treturn err\n\t} else {\n\t\tlog.Infof(\"iterating over %d messages for user %s\", len(messages), userId)\n\t\tfor _, msg := range messages {\n\t\t\tif !msg[\"is_received\"].(bool) {\n\t\t\t\tmessage, err := Rest.GetMessage(&UserInfo{userId.String(), shardId}, msg[\"message_id\"].(gocql.UUID).String())\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tuserIdentities := []UUID{localId}\n\t\t\t\tupdatedFields := map[string]interface{}{\"UserIdentities\": userIdentities}\n\t\t\t\tmessage.UserIdentities = userIdentities\n\t\t\t\terr = Store.UpdateMessage(message, updatedFields)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.WithError(err).Warnf(\"failed to update message %s in db\", message.Message_id.String())\n\t\t\t\t\terroneousMessages = append(erroneousMessages, userId.String())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/tools/go.CLI/cmd/gocaliopen/cli_cmds/root.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/interfaces/REST/go.server\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/index/elasticsearch\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/vault\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.main/facilities/REST\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/protocols/go.imap\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/protocols/go.smtp\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"net/http\"\n\t\"os\"\n)\n\ntype CmdConfig struct {\n\trest_api.APIConfig\n\trest_api.IndexConfig\n\trest_api.ProxyConfig\n}\n\nvar (\n\tcfgPath           string\n\tapiCfgFile        string\n\tlmtpCfgFile       string\n\timapWorkerCfgFile string\n\tapiConf           CmdConfig\n\tlmtpConf          caliopen_smtp.SMTPConfig\n\timapWorkerConf    imap_worker.WorkerConfig\n\n\t// RootCmd represents the base command when called without any subcommands\n\tRootCmd = &cobra.Command{\n\t\tUse:   \"gocaliopen\",\n\t\tShort: \"Caliopen CLI to interact with stack\",\n\t\tLong: `gocaliopen needs two of Caliopen's config files : caliopen-go-api_dev.yaml and caliopen-go-lmtp_dev.yaml.\nIt loads them from within directory specified with flag --confPath, or if path/filenames are specified with the --apiConf and --lmtpConf.\ngocaliopen subcommands could interact with\n\t- store (Cassandra)\n\t- index (Elasticsearch)\n\t- message queue (NATS)\n\t- cache (Redis)\n\t- caliopen REST facility\t\n\t- apiV1\n\t- apiV2\n\t- lmtpd\n\t- caliopen notification facility\n\t- email broker\t`,\n\t}\n)\n\nconst __version__ = \"0.23.0\"\n\nfunc init() {\n\tcobra.OnInitialize(initConfig)\n\n\tRootCmd.PersistentFlags().StringVar(&cfgPath, \"confPath\", \"\", \"Path to seek the two mandatory config files: apiv2.yaml and lmtp.yaml\")\n\tRootCmd.PersistentFlags().StringVar(&apiCfgFile, \"apiConf\", \"\", \"Caliopen's API config file\")\n\tRootCmd.PersistentFlags().StringVar(&lmtpCfgFile, \"lmtpConf\", \"\", \"Caliopen's lmtpd config file\")\n\tRootCmd.PersistentFlags().StringVar(&imapWorkerCfgFile, \"imapWorkerConf\", \"\", \"Imap worker config file\")\n}\n\n// Execute adds all child commands to the root command and sets flags appropriately.\n// This is called by main.main(). It only needs to happen once to the rootCmd.\nfunc Execute() {\n\tif err := RootCmd.Execute(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n}\n\n// initConfig reads in config files and ENV variables if set.\nfunc initConfig() {\n\tapiCfg := viper.New()\n\tlmtpCfg := viper.New()\n\timapWorkerCfg := viper.New()\n\n\tif cfgPath != \"\" {\n\t\t// Use path from the flag\n\t\tapiCfg.AddConfigPath(cfgPath)\n\t\tlmtpCfg.AddConfigPath(cfgPath)\n\t\timapWorkerCfg.AddConfigPath(cfgPath)\n\t} else {\n\t\t// set defaults to current dir\n\t\tapiCfg.AddConfigPath(\".\")\n\t\tlmtpCfg.AddConfigPath(\".\")\n\t\timapWorkerCfg.AddConfigPath(\".\")\n\t}\n\n\tif apiCfgFile != \"\" {\n\t\t// Use config file name and path from the flag.\n\t\tapiCfg.SetConfigFile(apiCfgFile)\n\t} else {\n\t\tapiCfg.SetConfigName(\"apiv2\")\n\t}\n\n\tif lmtpCfgFile != \"\" {\n\t\t// Use config file name and path from the flag.\n\t\tlmtpCfg.SetConfigFile(lmtpCfgFile)\n\t} else {\n\t\tlmtpCfg.SetConfigName(\"lmtp\")\n\t}\n\n\tif imapWorkerCfgFile != \"\" {\n\t\t// Use config file name and path from the flag.\n\t\timapWorkerCfg.SetConfigFile(imapWorkerCfgFile)\n\t} else {\n\t\timapWorkerCfg.SetConfigName(\"imapworker\")\n\t}\n\n\t// read in environment variables that match\n\tapiCfg.AutomaticEnv()\n\tlmtpCfg.AutomaticEnv()\n\timapWorkerCfg.AutomaticEnv()\n\n\tif err := apiCfg.ReadInConfig(); err != nil {\n\t\tlog.WithError(err).Fatalf(\"can't load api config file %s\", apiCfgFile)\n\t}\n\tif err := apiCfg.Unmarshal(&apiConf); err != nil {\n\t\tlog.WithError(err).Fatalf(\"can't parse api config file %s\", apiCfgFile)\n\t}\n\n\tif err := lmtpCfg.ReadInConfig(); err != nil {\n\t\tlog.WithError(err).Fatalf(\"can't load lmtp config file %s\", lmtpCfgFile)\n\t}\n\tif err := lmtpCfg.Unmarshal(&lmtpConf); err != nil {\n\t\tlog.WithError(err).Fatalf(\"can't parse lmtp config file %s\", lmtpCfgFile)\n\t}\n\n\tif err := imapWorkerCfg.ReadInConfig(); err != nil {\n\t\tlog.WithError(err).Warn(\"can't load imapworker config file %s\", imapWorkerCfgFile)\n\t}\n\tif err := imapWorkerCfg.Unmarshal(&imapWorkerConf); err != nil {\n\t\tlog.WithError(err).Warn(\"can't parse imapworker config file %s\", imapWorkerCfgFile)\n\t}\n}\n\n// getStoreFacility reads configuration and tries to connect to a store\n// It returns a handler to make use of store facility\n// For now, only returns a CassandraBackend\nfunc getStoreFacility() (Store *store.CassandraBackend, err error) {\n\tswitch apiConf.BackendName {\n\tcase \"cassandra\":\n\t\tc := store.CassandraConfig{\n\t\t\tHosts:        apiConf.BackendConfig.Settings.Hosts,\n\t\t\tKeyspace:     apiConf.BackendConfig.Settings.Keyspace,\n\t\t\tConsistency:  gocql.Consistency(apiConf.BackendConfig.Settings.Consistency),\n\t\t\tSizeLimit:    apiConf.BackendConfig.Settings.SizeLimit,\n\t\t\tWithObjStore: true,\n\t\t\tUseVault:     apiConf.BackendConfig.Settings.UseVault,\n\t\t\tHVaultConfig: vault.HVaultConfig{\n\t\t\t\tapiConf.BackendConfig.Settings.VaultSettings.Url,\n\t\t\t\tapiConf.BackendConfig.Settings.VaultSettings.Username,\n\t\t\t\tapiConf.BackendConfig.Settings.VaultSettings.Password,\n\t\t\t},\n\t\t}\n\t\tc.Endpoint = apiConf.BackendConfig.Settings.ObjStoreSettings.Endpoint\n\t\tc.AccessKey = apiConf.BackendConfig.Settings.ObjStoreSettings.AccessKey\n\t\tc.SecretKey = apiConf.BackendConfig.Settings.ObjStoreSettings.SecretKey\n\t\tc.RawMsgBucket = apiConf.BackendConfig.Settings.ObjStoreSettings.Buckets[\"raw_messages\"]\n\t\tc.AttachmentBucket = apiConf.BackendConfig.Settings.ObjStoreSettings.Buckets[\"temporary_attachments\"]\n\t\tc.Location = apiConf.BackendConfig.Settings.ObjStoreSettings.Location\n\n\t\t//TODO: add a conf file for gocli.\n\t\tif c.UseVault {\n\t\t\tc.Url = apiConf.BackendConfig.Settings.VaultSettings.Url\n\t\t\tc.Username = \"gocli\"\n\t\t\tc.Password = \"gocli_weak_password\"\n\t\t}\n\n\t\tStore, err = store.InitializeCassandraBackend(c)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn\n}\n\n// getIndexFacility reads configuration and tries to connect to an index\n// It returns a handler to make use of index facility\n// For now, only returns an ElasticSearchBackend\nfunc getIndexFacility() (Index *index.ElasticSearchBackend, err error) {\n\tswitch apiConf.APIConfig.IndexConfig.IndexName {\n\tcase \"elasticsearch\":\n\t\tc := index.ElasticSearchConfig{\n\t\t\tUrls: apiConf.APIConfig.IndexConfig.Settings.Hosts,\n\t\t}\n\t\tIndex, err = index.InitializeElasticSearchIndex(c)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, errors.New(\"unknown index\")\n\t}\n\treturn\n}\n\n// getMsgSystemFacility reads configuration and tries to connect to a messages broker\n// It returns a handler to make use of facility\n// For now, only returns an Nats conn\nfunc getMsgSystemFacility() (MsgSys *nats.Conn, err error) {\n\tMsgSys, err = nats.Connect(apiConf.APIConfig.NatsConfig.Url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn\n}\n\n// getCacheFacility reads configuration and tries to connect to a memory cache\n// It returns a handler to make use of facility\n// For now, only returns a RedisBackend\n/*\nfunc getCacheFacility() (Cache *cache.RedisBackend, err error) {\n\tCache, err = cache.InitializeRedisBackend(CacheConfig(apiConf.APIConfig.CacheSettings))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn\n}\n*/\n\n// getAPIConnection pings API1 and API2 connections and returns URLs from configuration file\n// OK is false if at least one API is not responding\nfunc getAPIConnection() (API1, API2 string, OK1, OK2 bool) {\n\tAPI1 = apiConf.ProxyConfig.Routes[\"/\"]\n\tAPI2 = apiConf.ProxyConfig.Routes[\"/api/v2/\"]\n\tif _, err := http.Head(\"http://\" + API1); err == nil {\n\t\tOK1 = true\n\t}\n\tif _, err := http.Head(\"http://\" + API2); err == nil {\n\t\tOK2 = true\n\t}\n\treturn\n}\n\n// getAPI2Facility reads configuration and initializes RESTfacility interface\n// to expose all its functions\nfunc getRESTFacility() (API2 *REST.RESTfacility, err error) {\n\tvar MsgSys *nats.Conn\n\tMsgSys, err = getMsgSystemFacility()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcaliopenConf := CaliopenConfig{\n\t\tRESTstoreConfig: RESTstoreConfig{\n\t\t\tBackendName:  apiConf.APIConfig.BackendConfig.BackendName,\n\t\t\tHosts:        apiConf.APIConfig.BackendConfig.Settings.Hosts,\n\t\t\tKeyspace:     apiConf.APIConfig.BackendConfig.Settings.Keyspace,\n\t\t\tConsistency:  apiConf.APIConfig.BackendConfig.Settings.Consistency,\n\t\t\tSizeLimit:    apiConf.APIConfig.BackendConfig.Settings.SizeLimit,\n\t\t\tObjStoreType: apiConf.APIConfig.BackendConfig.Settings.ObjStoreType,\n\t\t},\n\t\tRESTindexConfig: RESTIndexConfig{\n\t\t\tIndexName: apiConf.APIConfig.IndexConfig.IndexName,\n\t\t\tHosts:     apiConf.APIConfig.IndexConfig.Settings.Hosts,\n\t\t},\n\t\tNatsConfig: NatsConfig{\n\t\t\tUrl:            apiConf.APIConfig.NatsConfig.Url,\n\t\t\tOutSMTP_topic:  apiConf.APIConfig.OutSMTP_topic,\n\t\t\tContacts_topic: apiConf.APIConfig.Contacts_topic,\n\t\t},\n\t\tCacheConfig: CacheConfig{\n\t\t\tHost:     apiConf.APIConfig.CacheSettings.Host,\n\t\t\tPassword: apiConf.APIConfig.CacheSettings.Password,\n\t\t\tDb:       apiConf.APIConfig.CacheSettings.Db,\n\t\t},\n\t\tNotifierConfig: NotifierConfig{\n\t\t\tAdminUsername: apiConf.APIConfig.NotifierConfig.AdminUsername,\n\t\t\tBaseUrl:       apiConf.APIConfig.NotifierConfig.BaseUrl,\n\t\t\tTemplatesPath: apiConf.APIConfig.NotifierConfig.TemplatesPath,\n\t\t},\n\t}\n\tcaliopenConf.RESTstoreConfig.Endpoint = apiConf.APIConfig.BackendConfig.Settings.ObjStoreSettings.Endpoint\n\tcaliopenConf.RESTstoreConfig.AccessKey = apiConf.APIConfig.BackendConfig.Settings.ObjStoreSettings.AccessKey\n\tcaliopenConf.RESTstoreConfig.SecretKey = apiConf.APIConfig.BackendConfig.Settings.ObjStoreSettings.SecretKey\n\tcaliopenConf.RESTstoreConfig.Location = apiConf.APIConfig.BackendConfig.Settings.ObjStoreSettings.Location\n\tcaliopenConf.RESTstoreConfig.Buckets = apiConf.APIConfig.BackendConfig.Settings.ObjStoreSettings.Buckets\n\n\tAPI2 = REST.NewRESTfacility(caliopenConf, MsgSys)\n\n\treturn API2, nil\n}\n\nfunc getNotificationsFacility() { /*TODO*/ }\n\nfunc getLMTPFacility() { /*TODO*/ }\n"
  },
  {
    "path": "src/backend/tools/go.CLI/cmd/gocaliopen/main.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage main\n\nimport \"github.com/CaliOpen/Caliopen/src/backend/tools/go.CLI/cmd/gocaliopen/cli_cmds\"\n\nfunc main() {\n\tcmd.Execute()\n}\n"
  },
  {
    "path": "src/backend/tools/py.CLI/CHANGES.rst",
    "content": "0.0.1\n-----\n\n-  Initial version\n"
  },
  {
    "path": "src/backend/tools/py.CLI/README.rst",
    "content": "caliopen.cli\n============\n\nCaliopen Command Line Interface\n\nSimple tool to provide some administration commands for caliopen project.\n\nDefine a ``caliopen`` command in your shell path.\n\n# Usage\n\n## Copy the sample caliopen.yaml.template file to roll your own configuration parameters::\n\n    cp caliopen.yaml.template caliopen.yaml\n\n\n## Setup the storage database::\n\n    caliopen -f caliopen.yaml setup\n\n\n## Create an admin ::\n\n    caliopen -f caliopen.yaml create_user -e admin -p password\n\n## Create a user::\n\n    caliopen create_user --help\n    caliopen -f caliopen.yaml create_user -e imported@email -p password -g given_name -f family_name\n\n## Import a mailbox ::\n\n    caliopen import --help\n    caliopen -f caliopen.yaml import -p ~/path_to_maildir -e  imported@email -f maildir\n\n## Dump a model ::\n\n    caliopen -f caliopen.yaml dump_model -m model_name -o output_path\n\n## Import vcard ::\n\n    Refer to [import vcard](../doc/for-developers/vcard_doc.md)\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n__version__ = '0.27.0'\n\ntry:\n    import pkg_resources\n    pkg_resources.declare_namespace(__name__)\nexcept ImportError:\n    import pkgutil\n    __path__ = pkgutil.extend_path(__path__, __name__)\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/cli.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\nCommand Line Interface (CLI) for caliopen project\n\n\n\"\"\"\nimport sys\nimport argparse\nimport logging\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_storage.helpers.connection import connect_storage\nfrom caliopen_cli.commands import (shell, import_email, setup, create_user,\n                                   import_vcard, dump_model, dump_indexes,\n                                   inject_email, basic_compute, migrate_index,\n                                   import_reserved_names, resync_index,\n                                   resync_shard_index, copy_model)\n\nlogging.basicConfig(level=logging.INFO)\n\n\ndef main(args=sys.argv):\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-f', dest='conffile', default='development.ini')\n    subparsers = parser.add_subparsers(title=\"action\")\n\n    sp_import = subparsers.add_parser('import', help='import existing mailbox')\n    sp_import.set_defaults(func=import_email)\n    sp_import.add_argument('-f', dest='format', choices=['mbox', 'maildir'],\n                           default='mbox')\n    sp_import.add_argument('-p', dest='import_path')\n    sp_import.add_argument('-e', dest='email')\n    sp_import.add_argument('--contact-probability', dest='contact_probability',\n                           default=0.8)\n    sp_import.add_argument('-t', dest='to')\n\n    sp_import_vcard = subparsers.add_parser('import_vcard',\n                                            help='import vcard')\n    sp_import_vcard.set_defaults(func=import_vcard)\n    sp_import_vcard.add_argument('-u', dest='username', help='username')\n    sp_import_vcard.add_argument('-d', dest='directory', help='directory')\n    sp_import_vcard.add_argument('-f', dest='file_vcard', help='file')\n\n    sp_setup = subparsers.add_parser('setup',\n                                     help='initialize the storage engine')\n    sp_setup.set_defaults(func=setup)\n\n    sp_create_user = subparsers.add_parser('create_user',\n                                           help='Create a new user')\n    sp_create_user.set_defaults(func=create_user)\n    sp_create_user.add_argument('-e', dest='email', help='user email')\n    sp_create_user.add_argument('-p', dest='password', help='password')\n    sp_create_user.add_argument('-g', dest='given_name',\n                                help='user given name')\n    sp_create_user.add_argument('-f', dest='family_name',\n                                help='user family name')\n\n    sp_shell = subparsers.add_parser('shell')\n    sp_shell.set_defaults(func=shell)\n\n    sp_dump = subparsers.add_parser('dump')\n    sp_dump.set_defaults(func=dump_model)\n    sp_dump.add_argument('-m', dest='model', help='model to dump')\n    sp_dump.add_argument('-o', dest='output_path', help='output path')\n\n    sp_copy = subparsers.add_parser('copy')\n    sp_copy.set_defaults(func=copy_model)\n    sp_copy.add_argument('-m', dest='model', help='model to dump')\n    sp_copy.add_argument('--where', dest='where', help='where condition')\n    sp_copy.add_argument('--fetch-size', dest='fetch_size', default=100)\n\n    sp_dump_index = subparsers.add_parser('dump_index')\n    sp_dump_index.set_defaults(func=dump_indexes)\n    sp_dump_index.add_argument('-o', dest='output_path', help='output path')\n\n    sp_migrate_index = subparsers.add_parser('migrate_index')\n    sp_migrate_index.set_defaults(func=migrate_index)\n    sp_migrate_index.add_argument('-s', dest='input_script',\n                                  help='python script to execute on index')\n\n    sp_inject = subparsers.add_parser('inject')\n    sp_inject.set_defaults(func=inject_email)\n    sp_inject.add_argument('-e', dest='email')\n    sp_inject.add_argument('-r', dest='recipient')\n\n    sp_compute = subparsers.add_parser('compute', help='Launch basic compute')\n    sp_compute.set_defaults(func=basic_compute)\n    sp_compute.add_argument('-u', dest='username', help='username')\n    sp_compute.add_argument('-j', dest='job', help='job name')\n\n    sp_reserved = subparsers.add_parser('reserved_names',\n                                        help='Import reserved names list')\n    sp_reserved.set_defaults(func=import_reserved_names)\n    sp_reserved.add_argument('-i', dest='input_file', help='csv file')\n\n    sp_resync = subparsers.add_parser('resync_index',\n                                      help='Resync index for an user')\n    sp_resync.set_defaults(func=resync_index)\n    sp_resync.add_argument('-u', dest='user_name', help='User name')\n    sp_resync.add_argument('-i', dest='user_id', help='User uuid')\n    sp_resync.add_argument('--delete', dest='delete', action='store_true')\n\n    sp_resync = subparsers.add_parser('resync_shard',\n                                      help='Resync shard index')\n    sp_resync.set_defaults(func=resync_shard_index)\n    sp_resync.add_argument('-s', dest='shard_id', help='Shard id')\n    sp_resync.add_argument('-o', dest='old_shard_id', help='Old shard id')\n    kwargs = parser.parse_args(args[1:])\n    kwargs = vars(kwargs)\n\n    config_uri = kwargs.pop('conffile')\n    func = kwargs.pop('func')\n\n    Configuration.load(config_uri, 'global')\n    connect_storage()\n\n    func(**kwargs)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/__init__.py",
    "content": "from .shell import shell\nfrom .setup import setup\nfrom .create_user import create_user\nfrom .import_email import import_email\nfrom .inject_email import inject_email\nfrom .import_vcard import import_vcard\nfrom .dump_model import dump_model\nfrom .dump_indexes_mappings import dump_indexes\nfrom .migrate_index import migrate_index\nfrom .compute import basic_compute\nfrom .reserved_names import import_reserved_names\nfrom .resync_index import resync_index\nfrom .resync_shard_index import resync_shard_index\nfrom .copy_model import copy_model"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/compute.py",
    "content": "\"\"\"Launch of nasic compute on caliopen platform.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\n\nlog = logging.getLogger(__name__)\n\n\ndef basic_compute(username, job, ** kwargs):\n    \"\"\"Import emails for an user.\"\"\"\n    from caliopen_main.user.core import User\n    from caliopen_main.contact.objects import Contact\n    from caliopen_pi.qualifiers import ContactMessageQualifier\n\n    user = User.by_name(username)\n    qualifier = ContactMessageQualifier(user)\n    contacts = Contact.list_db(user.user_id)\n\n    if job == 'contact_privacy':\n        for contact in contacts:\n            log.info('Processing contact {0}'.format(contact.contact_id))\n            qualifier.process(contact)\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/copy_model.py",
    "content": "\nimport sys\n\nfrom cassandra.cluster import Cluster\nfrom cassandra.query import SimpleStatement\nfrom cassandra.query import dict_factory\n\nfrom caliopen_storage.config import Configuration\n\n\ndef copy_model(**kwargs):\n    conf = Configuration('global').configuration\n    cluster_source = Cluster(conf['old_cassandra']['hosts'])\n    source = cluster_source.connect(conf['old_cassandra']['keyspace'])\n    source.row_factory = dict_factory\n    cluster_dest = Cluster(conf['cassandra']['hosts'])\n    dest = cluster_dest.connect(conf['cassandra']['keyspace'])\n\n    table = kwargs['model'].lower()\n    fetch_size = kwargs.get('fetch_size', 100)\n    query = \"SELECT * FROM {0}\".format(table)\n    if 'where' in kwargs and kwargs['where']:\n        query = \"{0} WHERE {1} ALLOW FILTERING\".format(query, kwargs['where'])\n    statement = SimpleStatement(query, fetch_size=fetch_size)\n    insert_query = \"INSERT INTO {0} ({1}) VALUES ({2})\"\n    cpt = 0\n    insert = None\n    for row in source.execute(statement):\n        if cpt == 0:\n            columns = ['\"{}\"'.format(x) for x in row.keys()]\n            binds = ['?' for x in range(0, len(columns))]\n            insert_str = insert_query.format(table,\n                                             ','.join(columns),\n                                             ','.join(binds))\n            insert = dest.prepare(insert_str)\n        bound = insert.bind(row.values())\n        dest.execute(bound)\n        cpt += 1\n    print('Copy of {} records from {}'.format(cpt, table))\n    return cpt\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/create_user.py",
    "content": "\"\"\"Create a user with a password in a Calipen instance.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nfrom caliopen_storage.config import Configuration\n\nlog = logging.getLogger(__name__)\n\n\ndef create_user(**kwargs):\n    \"\"\"Create user in Caliopen instance.\"\"\"\n    from caliopen_main.user.core import User\n    from caliopen_main.user.parameters import NewUser\n    from caliopen_main.contact.parameters import NewContact\n    from caliopen_main.contact.parameters import NewEmail\n\n    # Fill core registry\n    from caliopen_main.message.objects.message import Message\n\n    param = NewUser()\n    param.name = kwargs['email']\n    if '@' in param.name:\n        username, domain = param.name.split('@')\n        param.name = username\n        # Monkey patch configuration local_domain with provided value\n        conf = Configuration('global').configuration\n        conf['default_domain'] = domain\n    param.password = kwargs['password']\n    param.recovery_email = u'{}@recovery-caliopen.local'.format(param.name)\n\n    contact = NewContact()\n    contact.given_name = kwargs.get('given_name')\n    contact.family_name = kwargs.get('family_name')\n    email = NewEmail()\n    email.address = param.recovery_email\n    contact.emails = [email]\n    param.contact = contact\n    user = User.create(param)\n    log.info('User %s (%s) created' % (user.user_id, user.name))\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/dump_indexes_mappings.py",
    "content": "import json\nfrom caliopen_storage.helpers.json import JSONEncoder\n\n\ndef dump_indexes(**kwargs):\n    # Discover base core classes\n    from caliopen_main.user.core import User\n    from caliopen_main.contact.objects.contact import Contact\n    from caliopen_main.message.objects.message import Message\n    from caliopen_main.common.objects.tag import ResourceTag\n    from caliopen_storage.core import core_registry\n    _exports = {\n        'contact': ['Contact'],\n        'message': ['Message'],\n    }\n    for keys in _exports:\n        for obj in _exports[keys]:\n            kls = core_registry.get(obj)\n            if not kls:\n                raise Exception('core class %s not found in registry' % obj)\n            output_file = '%s/%s.json' % (kwargs[\"output_path\"], obj.lower())\n            dump_index_mapping(kls._index_class, output_file)\n\n\ndef dump_index_mapping(kls, output_file):\n    \"\"\"Output the json definition  class.\"\"\"\n\n    m = kls.build_mapping().to_dict()\n    with open(output_file, 'w') as f:\n        f.write(json.dumps(m, cls=JSONEncoder,\n                           indent=4, sort_keys=True))\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/dump_model.py",
    "content": "\nfrom caliopen_storage.helpers.json import json, JSONEncoder\n\n\ndef dump_model(model, output_path, **kwargs):\n    # Discover base core classes\n    from caliopen_main.user.core import User\n    from caliopen_storage.core import core_registry\n    _exports = {\n        'contact': ['Contact', 'Organization', 'PostalAddress', 'Email', 'IM',\n                    'Ohone', 'PublicKey', 'SocialIdentity'],\n        'message': ['Message', 'Thread'],\n        'user':    ['User', 'UserTag', 'FilterRule'],\n    }\n    for obj in _exports[model]:\n        kls = core_registry.get(obj)\n        if not kls:\n            raise Exception('core class %s not found in registry' % obj)\n        output_file = '%s/%s.json' % (output_path, obj.lower())\n        dump_model_class(kls, output_file)\n\n\ndef dump_model_class(kls, output_file):\n    \"\"\"Dump a model class into output file.\"\"\"\n    records = kls._model_class.objects.all()\n    data = []\n    for rec in records:\n        d = {}\n        for k in rec._columns.keys():\n            attr = getattr(rec, k)\n            if hasattr(attr, 'to_python'):\n                d[k] = attr.to_python()\n            else:\n                d[k] = attr\n        data.append(d)\n    with open(output_file, 'w') as f:\n        f.write(json.dumps(data, cls=JSONEncoder,\n                           indent=4, sort_keys=True))\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/import_email.py",
    "content": "\"\"\"\nThis script parse mail from a mbox or maildir format and import them.\n\nUser must be created before import\n\n\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport os\nimport re\nfrom random import random\nimport logging\n\nfrom email import message_from_string, message_from_file\nfrom mailbox import mbox, Maildir\n\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_main.common.errors import DuplicateMessage\n\nlog = logging.getLogger(__name__)\n\n\ndef import_email(email, import_path, format, contact_probability,\n                 **kwargs):\n    \"\"\"Import emails for an user.\"\"\"\n    from caliopen_main.user.core import User\n    from caliopen_main.contact.core import Contact, ContactLookup\n    from caliopen_main.message.parsers.mail import MailMessage\n    from caliopen_main.contact.parameters import NewContact, NewEmail\n    from caliopen_nats.delivery import UserMailDelivery\n    from caliopen_main.message.core import RawMessage\n    from caliopen_storage.config import Configuration\n\n    max_size = int(Configuration(\"global\").get(\"object_store.db_size_limit\"))\n\n    if 'to' in kwargs and kwargs['to']:\n        dest_email = kwargs['to']\n    else:\n        dest_email = email\n\n    if format == 'maildir':\n        if dest_email != email:\n            raise Exception('Cannot change To email using maildir format')\n        emails = Maildir(import_path, factory=message_from_file)\n        mode = 'maildir'\n    else:\n        if os.path.isdir(import_path):\n            mode = 'mbox_directory'\n            emails = {}\n            files = [f for f in os.listdir(import_path) if\n                     os.path.isfile(os.path.join(import_path, f))]\n            for f in files:\n                try:\n                    log.debug('Importing mail from file {}'.format(f))\n                    with open('%s/%s' % (import_path, f)) as fh:\n                        data = fh.read()\n                        data = re.sub('^To: (.*)', 'To: %s' % dest_email,\n                                      data, flags=re.MULTILINE)\n                        emails[f] = message_from_string(data)\n                except Exception as exc:\n                    log.error('Error importing email {}'.format(exc))\n        else:\n            mode = 'mbox'\n            emails = mbox(import_path)\n\n    user = User.by_local_identifier(dest_email, 'email')\n\n    log.info(\"Processing mode %s\" % mode)\n\n    for key, data in emails.iteritems():\n        # Prevent creating message too large to fit in db.\n        # (should use inject cmd for large messages)\n        size = len(data.as_string())\n        if size > max_size:\n            log.warn(\"Message too large to fit into db. \\\n            Please, use 'inject' cmd for importing large emails.\")\n            continue\n\n        raw = RawMessage.create(data.as_string())\n        log.debug('Created raw message {}'.format(raw.raw_msg_id))\n        message = MailMessage(data.as_string())\n        dice = random()\n        if dice <= contact_probability:\n            for participant in message.participants:\n                try:\n                    ContactLookup.get(user, participant.address)\n                except NotFound:\n                    log.info('Creating contact %s' % participant.address)\n                    name, domain = participant.address.split('@')\n                    contact_param = NewContact()\n                    contact_param.family_name = name\n                    if participant.address:\n                        e_mail = NewEmail()\n                        e_mail.address = participant.address\n                        contact_param.emails = [e_mail]\n                    Contact.create(user, contact_param)\n        else:\n            log.info('No contact associated to raw {} '.format(raw.raw_msg_id))\n\n        processor = UserMailDelivery(user,\n                                     user.local_identities[\n                                         0])  # assume one local identity\n        try:\n            obj_message = processor.process_raw(raw.raw_msg_id)\n        except Exception as exc:\n            if isinstance(exc, DuplicateMessage):\n                log.info('duplicate message {}, not imported'.format(\n                    raw.raw_msg_id))\n            else:\n                log.exception(exc)\n        else:\n            log.info('Created message {}'.format(obj_message.message_id))\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/import_vcard.py",
    "content": "import logging\nimport os\n\nlog = logging.getLogger(__name__)\n\n\ndef import_vcard(username, directory, file_vcard, **kwargs):\n\n    from caliopen_main.contact.core import Contact as CoreContact\n    from caliopen_main.user.core.user import User as CoreUser\n\n    from caliopen_main.contact.parsers import VcardParser\n\n    new_contacts = []\n    if directory:\n        files = [f for f in os.listdir(directory) if\n                 os.path.isfile(os.path.join(directory, f))]\n        for f in files:\n            ext = f.split('.')[-1]\n            if ext == 'vcard' or ext == 'vcf':\n                file = '{directory}/{file}'.format(directory=directory, file=f)\n                parser = VcardParser(file)\n                new_contacts.extend(parser.parse())\n            else:\n                log.warn(\"Not valid file extension for vcard %s\" % f)\n\n    if file_vcard:\n        parser = VcardParser(file_vcard)\n        new_contacts = parser.parse()\n\n    user = CoreUser.by_name(username)\n\n    for contact in new_contacts:\n        CoreContact.create(user, contact.contact)\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/inject_email.py",
    "content": "\"\"\"\nThis script parse for a local user email (1st arg) and a text file (2nd arg).\nText file SHOULD be an RFC5322 email, with or without embedded attachment.\nIt will be streamed to lmtpd (aka 'broker' in docker stack).\n\nUser must be created before import\n\n\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport smtplib\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\ndef inject_email(recipient, email, **kwargs):\n    \"\"\"Inject an email for an user.\"\"\"\n\n    with open(email) as f:\n        conn = smtplib.SMTP('localhost', 2525)\n        try:\n            conn.sendmail(\"inject_email@py.cli.caliopen\", [recipient],\n                          str(f.read()))\n        except Exception as exc:\n            log.exception(exc)\n        conn.quit()\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/migrate_index.py",
    "content": "# -*- coding: utf-8 -*-\n\n# migrate_index will load the script at the given path\n# this script must implement a class named \"IndexMigrator\"\n# with a method \"run(elasticsearch_client)\"\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport imp\nimport os\n\nfrom elasticsearch import Elasticsearch\nfrom caliopen_storage.config import Configuration\n\nlog = logging.getLogger(__name__)\n\n\ndef migrate_index(**kwargs):\n    raise Exception(\"This script cannot be execute in this current state; it's based on versionned indices which is not used anymore, the version was in the name. So it breaks the migration mechinism. You can use sync_indices command instead to sync settings & mapping, it's almost like a migration except it uses latest version of the mapping.\")\n\n    # TODO eventually reintroduce version but in `_meta` in mapping configuration.\n    # Also remove url in params since it's already set in client and\n    # any way to have an interface for Migrators?\n    # Migrator = load_from_file(kwargs[\"input_script\"])\n    # if Migrator:\n    #     url = Configuration('global').get('elasticsearch.url')\n    #     mappings_version = Configuration('global').get(\n    #         'elasticsearch.mappings_version')\n    #     if url and mappings_version:\n    #         client = Elasticsearch(url)\n    #         migration = Migrator(client=client,\n    #                              mappings_version=mappings_version,\n    #                              url=url)\n    #         migration.run()\n\n\ndef load_from_file(filepath):\n    c = None\n    expected_class = 'IndexMigrator'\n\n    mod_name, file_ext = os.path.splitext(os.path.split(filepath)[-1])\n\n    if file_ext.lower() == '.py':\n        py_mod = imp.load_source(mod_name, filepath)\n\n    if hasattr(py_mod, expected_class):\n        c = getattr(py_mod, expected_class)\n\n    return c\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/reserved_names.py",
    "content": "\"\"\"Launch of nasic compute on caliopen platform.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\n\nlog = logging.getLogger(__name__)\n\n\ndef import_reserved_names(input_file, ** kwargs):\n    \"\"\"Import emails for an user.\"\"\"\n    from caliopen_main.user.core import ReservedName\n\n    cpt = 0\n    with open(input_file) as f:\n        for name in f.read().decode('utf8').split('\\n'):\n            if not name.startswith('#'):\n                ReservedName.create(name=name)\n                cpt += 1\n    log.info('Created {} reserved names'.format(cpt))\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/resync_index.py",
    "content": "# -*- coding: utf-8 -*-\n\n# Resync data index for an user\n#\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport sys\nimport uuid\nfrom elasticsearch import Elasticsearch\nfrom caliopen_storage.config import Configuration\n\nlog = logging.getLogger(__name__)\nlog.setLevel(logging.INFO)\n\n\ndef clean_index_user(client, user):\n    \"\"\"Delete old data belong to an user from index.\"\"\"\n    query = {'query': {'term': {'user_id': user.user_id}}}\n    r1 = client.delete_by_query(index=user.shard_id,\n                                doc_type='indexed_message',\n                                body=query)\n    r2 = client.delete_by_query(index=user.shard_id,\n                                doc_type='indexed_contact',\n                                body=query)\n    return r1['deleted'], r2['deleted']\n\n\ndef resync_index(**kwargs):\n    \"\"\"Resync an index for an user.\"\"\"\n    from caliopen_main.user.core import User, allocate_user_shard\n    from caliopen_main.user.core.setups import setup_index\n    from caliopen_main.contact.store import Contact\n    from caliopen_main.contact.objects import Contact as ContactObject\n    from caliopen_main.message.store import Message\n    from caliopen_main.message.objects import Message as MessageObject\n\n    if 'user_name' in kwargs and kwargs['user_name']:\n        user = User.by_name(kwargs['user_name'])\n    elif 'user_id' in kwargs and kwargs['user_id']:\n        user = User.get(kwargs['user_id'])\n    else:\n        print('Need user_name or user_id parameter')\n        sys.exit(1)\n\n    es_url = Configuration('global').get('elasticsearch.url')\n    es_client = Elasticsearch(es_url)\n\n    if 'delete' in kwargs and kwargs['delete']:\n        del_msg, del_con = clean_index_user(es_client, user)\n        log.info('Delete of {0} old contacts and {1} old messages'.\n                 format(del_con, del_msg))\n\n    user_id = uuid.UUID(user.user_id)\n    shard_id = allocate_user_shard(user_id)\n    if user.shard_id != shard_id:\n        log.warn('Reallocate user index shard from {} to {}'.\n                 format(user.shard_id, shard_id))\n        # XXX fixme. attribute should be set without using model\n        user.model.shard_id = shard_id\n        user.save()\n\n    setup_index(user)\n\n    cpt_contact = 0\n    contacts = Contact.filter(user_id=user.user_id)\n    for contact in contacts:\n        log.debug('Reindex contact %r' % contact.contact_id)\n        obj = ContactObject(user, contact_id=contact.contact_id)\n        obj.get_db()\n        obj.unmarshall_db()\n        obj.create_index()\n        cpt_contact += 1\n\n    cpt_message = 0\n    skip_message = 0\n    messages = Message.filter(user_id=user.user_id).timeout(None). \\\n        allow_filtering()\n    for message in messages:\n        log.debug('Reindex message %r' % message.message_id)\n        try:\n            obj = MessageObject(user, message_id=message.message_id)\n            obj.get_db()\n            obj.unmarshall_db()\n            obj.create_index()\n            cpt_message += 1\n        except Exception as exc:\n            skip_message += 1\n            log.exception(exc)\n    log.info('Sync of {0} contacts, {1} messages. Skipped {2} messages.'.\n             format(cpt_contact, cpt_message, skip_message))\n    log.info('Create index alias %r' % user.user_id)\n    try:\n        es_client.indices.put_alias(index=shard_id, name=user.user_id)\n    except Exception as exc:\n        log.exception('Error during alias creation : {}'.format(exc))\n        raise exc\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/resync_shard_index.py",
    "content": "# -*- coding: utf-8 -*-\n\n# resync index. This command will delete the current index for an user\n# And rebuild it entirely using cassandra data\n#\n\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\nimport sys\n\nfrom caliopen_storage.config import Configuration\nfrom elasticsearch import Elasticsearch\n\nlog = logging.getLogger(__name__)\nlog.setLevel(logging.DEBUG)\n\n\ndef resync_user(user):\n    \"\"\"Resync data for an user into its index shard.\"\"\"\n    from caliopen_main.contact.store import Contact\n    from caliopen_main.contact.objects import Contact as ContactObject\n    from caliopen_main.message.store import Message\n    from caliopen_main.message.objects import Message as MessageObject\n\n    log.info('Sync user {0} into shard {1}'.format(user.user_id,\n             user.shard_id))\n\n    client = Elasticsearch(Configuration('global').get('elasticsearch.url'))\n    body = {'filter': {'term': {'user_id': user.user_id}}}\n    # if not client.indices.exists_alias(user.user_id):\n    #     log.info('Creating alias {} for index {}'.format(user.user_id, user.shard_id))\n    client.indices.put_alias(user.shard_id, user.user_id, body=body)\n\n    contacts = Contact.filter(user_id=user.user_id).timeout(None)\n    for contact in contacts:\n        log.debug('Reindex contact %r' % contact.contact_id)\n        obj = ContactObject(user, contact_id=contact.contact_id)\n        obj.get_db()\n        obj.unmarshall_db()\n        obj.create_index()\n\n    messages = Message.filter(user_id=user.user_id). \\\n        allow_filtering().timeout(None)\n    for message in messages:\n        try:\n            log.debug('Reindex message %r' % message.message_id)\n            obj = MessageObject(user, message_id=message.message_id)\n            obj.get_db()\n            obj.unmarshall_db()\n            obj.create_index()\n        except Exception as exc:\n            log.error('{}: reindexing message {} for user {}. Continue'.format(type(exc).__name__, message.message_id, user.user_id))\n            raise\n\ndef resync_shard_index(**kwargs):\n    \"\"\"Resync all index of a shard.\"\"\"\n    from caliopen_main.user.core import User\n    from caliopen_main.user.core.setups import setup_shard_index\n\n    shard_id = kwargs['shard_id']\n    old_shard_id = kwargs.get('old_shard_id')\n    if not old_shard_id:\n        old_shard_id = shard_id\n    shards = Configuration('global').get('elasticsearch.shards')\n    if shard_id not in shards:\n        log.error('Invalid shard {0}'.format(shard_id))\n        sys.exit(1)\n\n    # Recreate index and mappings\n    setup_shard_index(shard_id)\n\n    users = User._model_class.all()\n    cpt = 0\n    skip = 0\n    for user in users:\n        if user.shard_id not in (old_shard_id, shard_id):\n            continue\n\n        if user.shard_id != shard_id:\n            user.shard_id = shard_id\n            user.save()\n        try:\n            resync_user(user)\n            cpt += 1\n        except Exception as exc:\n            skip += 1\n            log.exception(exc)\n    log.info('Sync {0} users into shards. Skipped {1} users'.format(cpt, skip))\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/setup.py",
    "content": "\"\"\"Setup backend.\"\"\"\n\nimport logging\n\nfrom .setup_storage import setup_storage\nfrom .setup_notifications_ttls import setup_notifications_ttls\n\nlog = logging.getLogger(__name__)\n\n\ndef setup():\n    \"\"\"Setup backend, storage and configuration.\"\"\"\n    log.info('Setup storage')\n    setup_storage()\n    log.info('Setup notification ttls')\n    setup_notifications_ttls()\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/setup_notifications_ttls.py",
    "content": "\"\"\"Create a user with a password in a Calipen instance.\"\"\"\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\ndef setup_notifications_ttls():\n    \"\"\"Fill up table `notification_ttl` with default ttls in seconds\"\"\"\n\n    from caliopen_main.notification.core import NotificationTtl\n\n    default_ttls = {\n        \"short-lived\": 60,  # a minute\n        \"mid-lived\": 3600,  # an hour\n        \"long-lived\": 43200,  # 12 hours\n        \"short-term\": 86400,  # a day\n        \"mid-term\": 172800,  # 2 days\n        \"long-term\": 1728000,  # 10 days\n        \"forever\": 0\n    }\n\n    for k, v in default_ttls.items():\n        NotificationTtl.create(ttl_code=k, ttl_duration=v)\n\n    log.info('{} default ttls have been set'.format(len(default_ttls)))\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/setup_storage.py",
    "content": "\"\"\"Setup storage backend.\"\"\"\n\nimport logging\nfrom caliopen_storage.config import Configuration\n\nlog = logging.getLogger(__name__)\n\n\ndef setup_storage(settings=None):\n    \"\"\"Create cassandra models.\"\"\"\n    from caliopen_storage.core import core_registry\n    # Make discovery happen\n    from caliopen_main.user.core import User\n    from caliopen_main.user.core import (UserIdentity,\n                                         IdentityLookup,\n                                         IdentityTypeLookup)\n    from caliopen_main.contact.objects.contact import Contact\n    from caliopen_main.message.objects.message import Message\n    from caliopen_main.common.objects.tag import ResourceTag\n    from caliopen_main.device.core import Device\n    from caliopen_main.notification.core import Notification, NotificationTtl\n    from caliopen_main.protocol.core import Provider\n\n    from cassandra.cqlengine.management import sync_table, \\\n        create_keyspace_simple\n    keyspace = Configuration('global').get('cassandra.keyspace')\n    if not keyspace:\n        raise Exception('Configuration missing for cassandra keyspace')\n    # XXX tofix : define strategy and replication_factor in configuration\n    create_keyspace_simple(keyspace, 1)\n    for name, kls in core_registry.items():\n        log.info('Creating cassandra model %s' % name)\n        if hasattr(kls._model_class, 'pk'):\n            # XXX find a better way to detect model from udt\n            sync_table(kls._model_class)\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/commands/shell.py",
    "content": "\"\"\"\nCaliopen Shell using Ipython if available.\n\"\"\"\n\n\ndef shell(**kwargs):\n    try:\n        from IPython import embed\n        from traitlets.config.loader import Config\n        cfg = Config()\n        cfg.InteractiveShellEmbed.confirm_exit = False\n        embed(config=cfg, banner1=\"Caliopen Shell\")\n    except ImportError:\n        # try ~IPython-0.10 API\n        try:\n            from IPython.Shell import IPShellEmbed as embed\n            ipshell = embed(banner=\"Caliopen Shell\")\n            ipshell()\n        except ImportError:\n            import code\n            code.interact(\"Caliopen Shell\", local=locals())\n"
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/utils/__init__.py",
    "content": ""
  },
  {
    "path": "src/backend/tools/py.CLI/caliopen_cli/utils/user_token.py",
    "content": "import logging\nimport redis\nimport json\n\nlog = logging.getLogger(__name__)\n\n\nclass UserToken(object):\n\n    \"\"\"Utility class to deal with user token session in cache.\"\"\"\n\n    _token_prefix = 'tokens::'\n\n    def __init__(self, redis_host, redis_port=6379):\n        self.client = redis.Redis(redis_host, redis_port)\n\n    def parse_session(self, session):\n        if '-' not in session:\n            return session, None\n        user_id, device_id = session.split('-')\n        return user_id, device_id\n\n    def list_user_sessions(self, user_id):\n        return self.client.keys('{}{}-*'.format(self._token_prefix, user_id))\n\n    def get_token(self, token):\n        infos = self.client.get(token)\n        if infos:\n            return json.loads(infos)\n        return {}\n\n    def delete_token(self, token):\n        return self.client.delete(token)\n\n    def get_user_status(self, user_id):\n        sessions = self.list_user_sessions(user_id)\n        for session in sessions:\n            token = self.get_token(session)\n            log.info('User session {} status is {}'.\n                     format(session, token.get('user_status')))\n\n    def _set_user_status(self, user_id, status):\n        sessions = self.list_user_sessions(user_id)\n        for session in sessions:\n            token = self.get_token(session)\n            if token.get('user_status') != status:\n                log.info('Updating user session {} status '.format(session))\n                token['user_status'] = status\n                self.client.set(session, json.dumps(token))\n        return True\n\n    def user_set_maintenance(self, user_id):\n        return self._set_user_status(user_id, 'maintenance')\n\n    def user_unset_maintenance(self, user_id):\n        return self._set_user_status(user_id, 'active')\n"
  },
  {
    "path": "src/backend/tools/py.CLI/requirements.deps",
    "content": "caliopen_pi\ncaliopen_nats"
  },
  {
    "path": "src/backend/tools/py.CLI/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliop\nwith-coverage = 1\ncover-erase = 1\n"
  },
  {
    "path": "src/backend/tools/py.CLI/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_cli\"\n\nwith open(os.path.join(*([here] + name.split('.') + ['__init__.py']))) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S).match(v_file.read()).group(1)\n\nrequires = [\n]\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\nsetup(name=name,\n      namespace_packages=[name],\n      version=version,\n      description='Caliopen Command Line Interface`',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\n        \"Programming Language :: Python\",\n        \"Topic :: Shell\",\n        ],\n      author='Caliopen Contributors',\n      author_email='',\n      url='https://github.com/Caliopen/caliopen.cli',\n      keywords='caliopen cli',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      install_requires=requires,\n      tests_require=requires,\n      test_suite=\"caliopen.cli.tests\",\n      entry_points={\n          'console_scripts': 'caliopen = caliopen_cli.cli:main',\n      })\n"
  },
  {
    "path": "src/backend/tools/py.ML/CHANGES.rst",
    "content": ""
  },
  {
    "path": "src/backend/tools/py.ML/README.rst",
    "content": ""
  },
  {
    "path": "src/backend/tools/py.ML/caliopen_climl/__init__.py",
    "content": "__version__ = '0.23.0'\n\ntry:\n    import pkg_resources\n    pkg_resources.declare_namespace(__name__)\nexcept ImportError:\n    import pkgutil\n    __path__ = pkgutil.extend_path(__path__, __name__)\n"
  },
  {
    "path": "src/backend/tools/py.ML/caliopen_climl/cli.py",
    "content": "\"\"\"Command Line Interface (CLI) for caliopen machine learning tasks.\"\"\"\nimport click\n\nfrom caliopen_tag.models_manager import ModelManager, ESDataManager\nfrom caliopen_data import save_file\n\n\nclass Config(object):\n    \"\"\"Delay configuration load and storage connection.\"\"\"\n\n    def __init__(self, filename):\n        from caliopen_storage.config import Configuration\n        from caliopen_storage.helpers.connection import connect_storage\n        self.conf = Configuration.load(filename, 'global').configuration\n        connect_storage()\n\n\n@click.group()\n@click.option('--config', 'config')\n@click.pass_context\ndef cli(ctx, config):\n    \"\"\"Entry point.\"\"\"\n    ctx.obj = Config(config).conf\n\n\n@cli.command()\n@click.argument('model')\n@click.option('--index', default='all')\n@click.option('--output')\n@click.pass_obj\ndef train(config, model, index, output):\n    \"\"\"Train a model command.\"\"\"\n    click.echo('Will train model {0} with index {1}'.\n               format(model, index))\n    if model == 'tagger':\n        provider = ESDataManager(config)\n        provider.prepare(provider.get_query(),\n                         index=None,\n                         doc_type='indexed_message')\n        manager = ModelManager(provider)\n        result_file = manager.get_new_model(output)\n        save_file(config, result_file, model)\n    else:\n        click.echo('Unknow model {0}'.format(model))\n"
  },
  {
    "path": "src/backend/tools/py.ML/requirements.deps",
    "content": "caliopen_tag"
  },
  {
    "path": "src/backend/tools/py.ML/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliopen\nwith-coverage = 1\ncover-erase = 1\n"
  },
  {
    "path": "src/backend/tools/py.ML/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_climl\"\n\nversion_file = os.path.join(*([here] + name.split('.') + ['__init__.py']))\nwith open(version_file) as v_file:\n    comp = re.compile(r\".*__version__ = '(.*?)'\", re.S)\n    version = comp.match(v_file.read()).group(1)\n\nrequires = [\n    'click'\n]\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\nsetup(name=name,\n      namespace_packages=[name],\n      version=version,\n      description='Caliopen CLI interface for Machine Learning tasks`',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\"Programming Language :: Python\",\n                   \"Topic :: Shell\"],\n      author='Caliopen Contributors',\n      author_email='',\n      url='https://github.com/Caliopen',\n      keywords='caliopen machine learning CLI',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      install_requires=requires,\n      tests_require=requires,\n      test_suite=\"caliopen_climl.cli.tests\",\n      entry_points={\n          'console_scripts': 'caliopml = caliopen_climl.cli:cli',\n      })\n"
  },
  {
    "path": "src/backend/tools/py.doc/CHANGES.rst",
    "content": "0.0.2\n-----\n\n-  Initial version\n"
  },
  {
    "path": "src/backend/tools/py.doc/README.rst",
    "content": "Caliopen swagger automatic API plugin package\n=============================================\n\nThis package permit to include in a caliopen development platform, the swagger-ui tool for documentation and also interaction with the ReST API.\n\nWhen installed and configured into your pyramid.includes section as `caliopen_api_doc`, if you are using a different configuration file for pyramid than the provided one for development or docker environment, you then can use it under localhost:6543/api-ui/#\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n__version__ = '0.0.2'\n\nfrom .config import includeme\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/config.py",
    "content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, print_function, unicode_literals\n\nimport logging\n\nlog = logging.getLogger(__name__)\n\n\ndef includeme(config):\n    \"\"\"Configure API to serve static documentation files.\"\"\"\n    log.info('Loading api doc module')\n    settings = config.registry.settings\n    swagger_dir = settings.get('pyramid_swagger.schema_directory')\n    if not swagger_dir:\n        log.warn('No configured swagger schema directory found')\n    else:\n        log.info('Will load swagger.json from {}'.format(swagger_dir))\n        config.add_static_view('doc/api', swagger_dir,\n                               cache_max_age=3600)\n        # the api swagger-ui is within folder /devtools/swagger-ui\n        config.add_static_view('api-ui', 'caliopen_api_doc:swagger-ui/',\n                               cache_max_age=3600)\n        # the Single Source of Truth\n        config.add_static_view('defs', 'caliopen_api_doc:../../defs/',\n                               cache_max_age=3600)\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/css/print.css",
    "content": "/* Original style from softwaremaniacs.org (c) Ivan Sagalaev <Maniac@SoftwareManiacs.Org> */\n.swagger-section pre code {\n  display: block;\n  padding: 0.5em;\n  background: #F0F0F0;\n}\n.swagger-section pre code,\n.swagger-section pre .subst,\n.swagger-section pre .tag .title,\n.swagger-section pre .lisp .title,\n.swagger-section pre .clojure .built_in,\n.swagger-section pre .nginx .title {\n  color: black;\n}\n.swagger-section pre .string,\n.swagger-section pre .title,\n.swagger-section pre .constant,\n.swagger-section pre .parent,\n.swagger-section pre .tag .value,\n.swagger-section pre .rules .value,\n.swagger-section pre .rules .value .number,\n.swagger-section pre .preprocessor,\n.swagger-section pre .ruby .symbol,\n.swagger-section pre .ruby .symbol .string,\n.swagger-section pre .aggregate,\n.swagger-section pre .template_tag,\n.swagger-section pre .django .variable,\n.swagger-section pre .smalltalk .class,\n.swagger-section pre .addition,\n.swagger-section pre .flow,\n.swagger-section pre .stream,\n.swagger-section pre .bash .variable,\n.swagger-section pre .apache .tag,\n.swagger-section pre .apache .cbracket,\n.swagger-section pre .tex .command,\n.swagger-section pre .tex .special,\n.swagger-section pre .erlang_repl .function_or_atom,\n.swagger-section pre .markdown .header {\n  color: #800;\n}\n.swagger-section pre .comment,\n.swagger-section pre .annotation,\n.swagger-section pre .template_comment,\n.swagger-section pre .diff .header,\n.swagger-section pre .chunk,\n.swagger-section pre .markdown .blockquote {\n  color: #888;\n}\n.swagger-section pre .number,\n.swagger-section pre .date,\n.swagger-section pre .regexp,\n.swagger-section pre .literal,\n.swagger-section pre .smalltalk .symbol,\n.swagger-section pre .smalltalk .char,\n.swagger-section pre .go .constant,\n.swagger-section pre .change,\n.swagger-section pre .markdown .bullet,\n.swagger-section pre .markdown .link_url {\n  color: #080;\n}\n.swagger-section pre .label,\n.swagger-section pre .javadoc,\n.swagger-section pre .ruby .string,\n.swagger-section pre .decorator,\n.swagger-section pre .filter .argument,\n.swagger-section pre .localvars,\n.swagger-section pre .array,\n.swagger-section pre .attr_selector,\n.swagger-section pre .important,\n.swagger-section pre .pseudo,\n.swagger-section pre .pi,\n.swagger-section pre .doctype,\n.swagger-section pre .deletion,\n.swagger-section pre .envvar,\n.swagger-section pre .shebang,\n.swagger-section pre .apache .sqbracket,\n.swagger-section pre .nginx .built_in,\n.swagger-section pre .tex .formula,\n.swagger-section pre .erlang_repl .reserved,\n.swagger-section pre .prompt,\n.swagger-section pre .markdown .link_label,\n.swagger-section pre .vhdl .attribute,\n.swagger-section pre .clojure .attribute,\n.swagger-section pre .coffeescript .property {\n  color: #88F;\n}\n.swagger-section pre .keyword,\n.swagger-section pre .id,\n.swagger-section pre .phpdoc,\n.swagger-section pre .title,\n.swagger-section pre .built_in,\n.swagger-section pre .aggregate,\n.swagger-section pre .css .tag,\n.swagger-section pre .javadoctag,\n.swagger-section pre .phpdoc,\n.swagger-section pre .yardoctag,\n.swagger-section pre .smalltalk .class,\n.swagger-section pre .winutils,\n.swagger-section pre .bash .variable,\n.swagger-section pre .apache .tag,\n.swagger-section pre .go .typename,\n.swagger-section pre .tex .command,\n.swagger-section pre .markdown .strong,\n.swagger-section pre .request,\n.swagger-section pre .status {\n  font-weight: bold;\n}\n.swagger-section pre .markdown .emphasis {\n  font-style: italic;\n}\n.swagger-section pre .nginx .built_in {\n  font-weight: normal;\n}\n.swagger-section pre .coffeescript .javascript,\n.swagger-section pre .javascript .xml,\n.swagger-section pre .tex .formula,\n.swagger-section pre .xml .javascript,\n.swagger-section pre .xml .vbscript,\n.swagger-section pre .xml .css,\n.swagger-section pre .xml .cdata {\n  opacity: 0.5;\n}\n.swagger-section .hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  background: #F0F0F0;\n}\n.swagger-section .hljs,\n.swagger-section .hljs-subst {\n  color: #444;\n}\n.swagger-section .hljs-keyword,\n.swagger-section .hljs-attribute,\n.swagger-section .hljs-selector-tag,\n.swagger-section .hljs-meta-keyword,\n.swagger-section .hljs-doctag,\n.swagger-section .hljs-name {\n  font-weight: bold;\n}\n.swagger-section .hljs-built_in,\n.swagger-section .hljs-literal,\n.swagger-section .hljs-bullet,\n.swagger-section .hljs-code,\n.swagger-section .hljs-addition {\n  color: #1F811F;\n}\n.swagger-section .hljs-regexp,\n.swagger-section .hljs-symbol,\n.swagger-section .hljs-variable,\n.swagger-section .hljs-template-variable,\n.swagger-section .hljs-link,\n.swagger-section .hljs-selector-attr,\n.swagger-section .hljs-selector-pseudo {\n  color: #BC6060;\n}\n.swagger-section .hljs-type,\n.swagger-section .hljs-string,\n.swagger-section .hljs-number,\n.swagger-section .hljs-selector-id,\n.swagger-section .hljs-selector-class,\n.swagger-section .hljs-quote,\n.swagger-section .hljs-template-tag,\n.swagger-section .hljs-deletion {\n  color: #880000;\n}\n.swagger-section .hljs-title,\n.swagger-section .hljs-section {\n  color: #880000;\n  font-weight: bold;\n}\n.swagger-section .hljs-comment {\n  color: #888888;\n}\n.swagger-section .hljs-meta {\n  color: #2B6EA1;\n}\n.swagger-section .hljs-emphasis {\n  font-style: italic;\n}\n.swagger-section .hljs-strong {\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap {\n  line-height: 1;\n  font-family: \"Droid Sans\", sans-serif;\n  min-width: 760px;\n  max-width: 960px;\n  margin-left: auto;\n  margin-right: auto;\n  /* JSONEditor specific styling */\n}\n.swagger-section .swagger-ui-wrap b,\n.swagger-section .swagger-ui-wrap strong {\n  font-family: \"Droid Sans\", sans-serif;\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap q,\n.swagger-section .swagger-ui-wrap blockquote {\n  quotes: none;\n}\n.swagger-section .swagger-ui-wrap p {\n  line-height: 1.4em;\n  padding: 0 0 10px;\n  color: #333333;\n}\n.swagger-section .swagger-ui-wrap q:before,\n.swagger-section .swagger-ui-wrap q:after,\n.swagger-section .swagger-ui-wrap blockquote:before,\n.swagger-section .swagger-ui-wrap blockquote:after {\n  content: none;\n}\n.swagger-section .swagger-ui-wrap .heading_with_menu h1,\n.swagger-section .swagger-ui-wrap .heading_with_menu h2,\n.swagger-section .swagger-ui-wrap .heading_with_menu h3,\n.swagger-section .swagger-ui-wrap .heading_with_menu h4,\n.swagger-section .swagger-ui-wrap .heading_with_menu h5,\n.swagger-section .swagger-ui-wrap .heading_with_menu h6 {\n  display: block;\n  clear: none;\n  float: left;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  -ms-box-sizing: border-box;\n  box-sizing: border-box;\n  width: 60%;\n}\n.swagger-section .swagger-ui-wrap table {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n.swagger-section .swagger-ui-wrap table thead tr th {\n  padding: 5px;\n  font-size: 0.9em;\n  color: #666666;\n  border-bottom: 1px solid #999999;\n}\n.swagger-section .swagger-ui-wrap table tbody tr:last-child td {\n  border-bottom: none;\n}\n.swagger-section .swagger-ui-wrap table tbody tr.offset {\n  background-color: #f0f0f0;\n}\n.swagger-section .swagger-ui-wrap table tbody tr td {\n  padding: 6px;\n  font-size: 0.9em;\n  border-bottom: 1px solid #cccccc;\n  vertical-align: top;\n  line-height: 1.3em;\n}\n.swagger-section .swagger-ui-wrap ol {\n  margin: 0px 0 10px;\n  padding: 0 0 0 18px;\n  list-style-type: decimal;\n}\n.swagger-section .swagger-ui-wrap ol li {\n  padding: 5px 0px;\n  font-size: 0.9em;\n  color: #333333;\n}\n.swagger-section .swagger-ui-wrap ol,\n.swagger-section .swagger-ui-wrap ul {\n  list-style: none;\n}\n.swagger-section .swagger-ui-wrap h1 a,\n.swagger-section .swagger-ui-wrap h2 a,\n.swagger-section .swagger-ui-wrap h3 a,\n.swagger-section .swagger-ui-wrap h4 a,\n.swagger-section .swagger-ui-wrap h5 a,\n.swagger-section .swagger-ui-wrap h6 a {\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap h1 a:hover,\n.swagger-section .swagger-ui-wrap h2 a:hover,\n.swagger-section .swagger-ui-wrap h3 a:hover,\n.swagger-section .swagger-ui-wrap h4 a:hover,\n.swagger-section .swagger-ui-wrap h5 a:hover,\n.swagger-section .swagger-ui-wrap h6 a:hover {\n  text-decoration: underline;\n}\n.swagger-section .swagger-ui-wrap h1 span.divider,\n.swagger-section .swagger-ui-wrap h2 span.divider,\n.swagger-section .swagger-ui-wrap h3 span.divider,\n.swagger-section .swagger-ui-wrap h4 span.divider,\n.swagger-section .swagger-ui-wrap h5 span.divider,\n.swagger-section .swagger-ui-wrap h6 span.divider {\n  color: #aaaaaa;\n}\n.swagger-section .swagger-ui-wrap a {\n  color: #547f00;\n}\n.swagger-section .swagger-ui-wrap a img {\n  border: none;\n}\n.swagger-section .swagger-ui-wrap article,\n.swagger-section .swagger-ui-wrap aside,\n.swagger-section .swagger-ui-wrap details,\n.swagger-section .swagger-ui-wrap figcaption,\n.swagger-section .swagger-ui-wrap figure,\n.swagger-section .swagger-ui-wrap footer,\n.swagger-section .swagger-ui-wrap header,\n.swagger-section .swagger-ui-wrap hgroup,\n.swagger-section .swagger-ui-wrap menu,\n.swagger-section .swagger-ui-wrap nav,\n.swagger-section .swagger-ui-wrap section,\n.swagger-section .swagger-ui-wrap summary {\n  display: block;\n}\n.swagger-section .swagger-ui-wrap pre {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  background-color: #fcf6db;\n  border: 1px solid #e5e0c6;\n  padding: 10px;\n}\n.swagger-section .swagger-ui-wrap pre code {\n  line-height: 1.6em;\n  background: none;\n}\n.swagger-section .swagger-ui-wrap .content > .content-type > div > label {\n  clear: both;\n  display: block;\n  color: #0F6AB4;\n  font-size: 1.1em;\n  margin: 0;\n  padding: 15px 0 5px;\n}\n.swagger-section .swagger-ui-wrap .content pre {\n  font-size: 12px;\n  margin-top: 5px;\n  padding: 5px;\n}\n.swagger-section .swagger-ui-wrap .icon-btn {\n  cursor: pointer;\n}\n.swagger-section .swagger-ui-wrap .info_title {\n  padding-bottom: 10px;\n  font-weight: bold;\n  font-size: 25px;\n}\n.swagger-section .swagger-ui-wrap .footer {\n  margin-top: 20px;\n}\n.swagger-section .swagger-ui-wrap p.big,\n.swagger-section .swagger-ui-wrap div.big p {\n  font-size: 1em;\n  margin-bottom: 10px;\n}\n.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input,\n.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input,\n.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea,\n.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input {\n  width: 500px !important;\n}\n.swagger-section .swagger-ui-wrap .info_license {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .info_tos {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .message-fail {\n  color: #cc0000;\n}\n.swagger-section .swagger-ui-wrap .info_url {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .info_email {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .info_name {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .info_description {\n  padding-bottom: 10px;\n  font-size: 15px;\n}\n.swagger-section .swagger-ui-wrap .markdown ol li,\n.swagger-section .swagger-ui-wrap .markdown ul li {\n  padding: 3px 0px;\n  line-height: 1.4em;\n  color: #333333;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input,\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input,\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input {\n  display: block;\n  padding: 4px;\n  width: auto;\n  clear: both;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title,\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title,\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title {\n  font-size: 1.3em;\n}\n.swagger-section .swagger-ui-wrap table.fullwidth {\n  width: 100%;\n}\n.swagger-section .swagger-ui-wrap .model-signature {\n  font-family: \"Droid Sans\", sans-serif;\n  font-size: 1em;\n  line-height: 1.5em;\n}\n.swagger-section .swagger-ui-wrap .model-signature .signature-nav a {\n  text-decoration: none;\n  color: #AAA;\n}\n.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover {\n  text-decoration: underline;\n  color: black;\n}\n.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected {\n  color: black;\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap .model-signature .propType {\n  color: #5555aa;\n}\n.swagger-section .swagger-ui-wrap .model-signature pre:hover {\n  background-color: #ffffdd;\n}\n.swagger-section .swagger-ui-wrap .model-signature pre {\n  font-size: .85em;\n  line-height: 1.2em;\n  overflow: auto;\n  max-height: 200px;\n  cursor: pointer;\n}\n.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav {\n  display: block;\n  min-width: 230px;\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child {\n  padding-right: 0;\n  border-right: none;\n}\n.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li {\n  float: left;\n  margin: 0 5px 5px 0;\n  padding: 2px 5px 2px 0;\n  border-right: 1px solid #ddd;\n}\n.swagger-section .swagger-ui-wrap .model-signature .propOpt {\n  color: #555;\n}\n.swagger-section .swagger-ui-wrap .model-signature .snippet small {\n  font-size: 0.75em;\n}\n.swagger-section .swagger-ui-wrap .model-signature .propOptKey {\n  font-style: italic;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .strong {\n  font-weight: bold;\n  color: #000;\n  font-size: .9em;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description div {\n  font-size: 0.9em;\n  line-height: 1.5em;\n  margin-left: 1em;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .stronger {\n  font-weight: bold;\n  color: #000;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper {\n  border-spacing: 0;\n  position: absolute;\n  background-color: #ffffff;\n  border: 1px solid #bbbbbb;\n  display: none;\n  font-size: 11px;\n  max-width: 400px;\n  line-height: 30px;\n  color: black;\n  padding: 5px;\n  margin-left: 10px;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th {\n  text-align: center;\n  background-color: #eeeeee;\n  border: 1px solid #bbbbbb;\n  font-size: 11px;\n  color: #666666;\n  font-weight: bold;\n  padding: 5px;\n  line-height: 15px;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName {\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child,\n.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child {\n  display: inline;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before {\n  display: block;\n  content: '';\n}\n.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child {\n  margin-right: -3px;\n}\n.swagger-section .swagger-ui-wrap .model-signature .propName {\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap .model-signature .signature-container {\n  clear: both;\n}\n.swagger-section .swagger-ui-wrap .body-textarea {\n  width: 300px;\n  height: 100px;\n  border: 1px solid #aaa;\n}\n.swagger-section .swagger-ui-wrap .markdown p code,\n.swagger-section .swagger-ui-wrap .markdown li code {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  background-color: #f0f0f0;\n  color: black;\n  padding: 1px 3px;\n}\n.swagger-section .swagger-ui-wrap .required {\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap .editor_holder {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap .editor_holder label {\n  font-weight: normal!important;\n  /* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */\n}\n.swagger-section .swagger-ui-wrap .editor_holder label.required {\n  font-weight: bold!important;\n}\n.swagger-section .swagger-ui-wrap input.parameter {\n  width: 300px;\n  border: 1px solid #aaa;\n}\n.swagger-section .swagger-ui-wrap h1 {\n  color: black;\n  font-size: 1.5em;\n  line-height: 1.3em;\n  padding: 10px 0 10px 0;\n  font-family: \"Droid Sans\", sans-serif;\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap .heading_with_menu {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n}\n.swagger-section .swagger-ui-wrap .heading_with_menu ul {\n  display: block;\n  clear: none;\n  float: right;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  -ms-box-sizing: border-box;\n  box-sizing: border-box;\n  margin-top: 10px;\n}\n.swagger-section .swagger-ui-wrap h2 {\n  color: black;\n  font-size: 1.3em;\n  padding: 10px 0 10px 0;\n}\n.swagger-section .swagger-ui-wrap h2 a {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap h2 span.sub {\n  font-size: 0.7em;\n  color: #999999;\n  font-style: italic;\n}\n.swagger-section .swagger-ui-wrap h2 span.sub a {\n  color: #777777;\n}\n.swagger-section .swagger-ui-wrap span.weak {\n  color: #666666;\n}\n.swagger-section .swagger-ui-wrap .message-success {\n  color: #89BF04;\n}\n.swagger-section .swagger-ui-wrap caption,\n.swagger-section .swagger-ui-wrap th,\n.swagger-section .swagger-ui-wrap td {\n  text-align: left;\n  font-weight: normal;\n  vertical-align: middle;\n}\n.swagger-section .swagger-ui-wrap .code {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea {\n  font-family: \"Droid Sans\", sans-serif;\n  height: 250px;\n  padding: 4px;\n  display: block;\n  clear: both;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select {\n  display: block;\n  clear: both;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label {\n  display: block;\n  float: left;\n  clear: none;\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input {\n  display: block;\n  float: left;\n  clear: none;\n  margin: 0 5px 0 0;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label {\n  display: block;\n  clear: both;\n  width: auto;\n  padding: 0 0 3px;\n  color: #666666;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr {\n  padding-left: 3px;\n  color: #888888;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints {\n  margin-left: 0;\n  font-style: italic;\n  font-size: 0.9em;\n  margin: 0;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons {\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap span.blank,\n.swagger-section .swagger-ui-wrap span.empty {\n  color: #888888;\n  font-style: italic;\n}\n.swagger-section .swagger-ui-wrap .markdown h3 {\n  color: #547f00;\n}\n.swagger-section .swagger-ui-wrap .markdown h4 {\n  color: #666666;\n}\n.swagger-section .swagger-ui-wrap .markdown pre {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  background-color: #fcf6db;\n  border: 1px solid #e5e0c6;\n  padding: 10px;\n  margin: 0 0 10px 0;\n}\n.swagger-section .swagger-ui-wrap .markdown pre code {\n  line-height: 1.6em;\n  overflow: auto;\n}\n.swagger-section .swagger-ui-wrap div.gist {\n  margin: 20px 0 25px 0 !important;\n}\n.swagger-section .swagger-ui-wrap ul#resources {\n  font-family: \"Droid Sans\", sans-serif;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource {\n  border-bottom: 1px solid #dddddd;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a,\n.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a,\n.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a {\n  color: #555555;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child {\n  border-bottom: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading {\n  border: 1px solid transparent;\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options {\n  overflow: hidden;\n  padding: 0;\n  display: block;\n  clear: none;\n  float: right;\n  margin: 14px 10px 0 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li {\n  float: left;\n  clear: none;\n  margin: 0;\n  padding: 2px 10px;\n  border-right: 1px solid #dddddd;\n  color: #666666;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a {\n  color: #aaaaaa;\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover {\n  text-decoration: underline;\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active {\n  text-decoration: underline;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first {\n  padding-left: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last {\n  padding-right: 0;\n  border-right: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first {\n  padding-left: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 {\n  color: #999999;\n  padding-left: 0;\n  display: block;\n  clear: none;\n  float: left;\n  font-family: \"Droid Sans\", sans-serif;\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a {\n  color: #999999;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n  margin: 0 0 10px;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 {\n  display: block;\n  clear: none;\n  float: left;\n  width: auto;\n  margin: 0;\n  padding: 0;\n  line-height: 1.1em;\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path {\n  padding-left: 10px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a {\n  color: black;\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a.toggleOperation.deprecated {\n  text-decoration: line-through;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover {\n  text-decoration: underline;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a {\n  text-transform: uppercase;\n  text-decoration: none;\n  color: white;\n  display: inline-block;\n  width: 50px;\n  font-size: 0.7em;\n  text-align: center;\n  padding: 7px 0 4px;\n  -moz-border-radius: 2px;\n  -webkit-border-radius: 2px;\n  -o-border-radius: 2px;\n  -ms-border-radius: 2px;\n  -khtml-border-radius: 2px;\n  border-radius: 2px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span {\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options {\n  overflow: hidden;\n  padding: 0;\n  display: block;\n  clear: none;\n  float: right;\n  margin: 6px 10px 0 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li {\n  float: left;\n  clear: none;\n  margin: 0;\n  padding: 2px 10px;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a {\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a .markdown p {\n  color: inherit;\n  padding: 0;\n  line-height: inherit;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content {\n  border-top: none;\n  padding: 10px;\n  -moz-border-radius-bottomleft: 6px;\n  -webkit-border-bottom-left-radius: 6px;\n  -o-border-bottom-left-radius: 6px;\n  -ms-border-bottom-left-radius: 6px;\n  -khtml-border-bottom-left-radius: 6px;\n  border-bottom-left-radius: 6px;\n  -moz-border-radius-bottomright: 6px;\n  -webkit-border-bottom-right-radius: 6px;\n  -o-border-bottom-right-radius: 6px;\n  -ms-border-bottom-right-radius: 6px;\n  -khtml-border-bottom-right-radius: 6px;\n  border-bottom-right-radius: 6px;\n  margin: 0 0 20px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 {\n  font-size: 1.1em;\n  margin: 0;\n  padding: 15px 0 5px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a {\n  padding: 4px 0 0 10px;\n  display: inline-block;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit {\n  display: block;\n  clear: none;\n  float: left;\n  padding: 6px 8px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber {\n  background-image: url('../images/throbber.gif');\n  width: 128px;\n  height: 16px;\n  display: block;\n  clear: none;\n  float: right;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error {\n  outline: 2px solid black;\n  outline-color: #cc0000;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] {\n  max-width: 300px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  padding: 10px;\n  font-size: 0.9em;\n  max-height: 400px;\n  overflow-y: auto;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading {\n  background-color: #f9f2e9;\n  border: 1px solid #f0e0ca;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a {\n  background-color: #c5862b;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #f0e0ca;\n  color: #c5862b;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a {\n  color: #c5862b;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content {\n  background-color: #faf5ee;\n  border: 1px solid #f0e0ca;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 {\n  color: #c5862b;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a {\n  color: #dcb67f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading {\n  background-color: #fcffcd;\n  border: 1px solid black;\n  border-color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a {\n  text-transform: uppercase;\n  background-color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #ffd20f;\n  color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a {\n  color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content {\n  background-color: #fcffcd;\n  border: 1px solid black;\n  border-color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 {\n  color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a {\n  color: #6fc992;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading {\n  background-color: #f5e8e8;\n  border: 1px solid #e8c6c7;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a {\n  text-transform: uppercase;\n  background-color: #a41e22;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #e8c6c7;\n  color: #a41e22;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a {\n  color: #a41e22;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content {\n  background-color: #f7eded;\n  border: 1px solid #e8c6c7;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 {\n  color: #a41e22;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a {\n  color: #c8787a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading {\n  background-color: #e7f6ec;\n  border: 1px solid #c3e8d1;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a {\n  background-color: #10a54a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #c3e8d1;\n  color: #10a54a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a {\n  color: #10a54a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content {\n  background-color: #ebf7f0;\n  border: 1px solid #c3e8d1;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 {\n  color: #10a54a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a {\n  color: #6fc992;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading {\n  background-color: #FCE9E3;\n  border: 1px solid #F5D5C3;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a {\n  background-color: #D38042;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #f0cecb;\n  color: #D38042;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a {\n  color: #D38042;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content {\n  background-color: #faf0ef;\n  border: 1px solid #f0cecb;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 {\n  color: #D38042;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a {\n  color: #dcb67f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading {\n  background-color: #e7f0f7;\n  border: 1px solid #c3d9ec;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a {\n  background-color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #c3d9ec;\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a {\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content {\n  background-color: #ebf3f9;\n  border: 1px solid #c3d9ec;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 {\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a {\n  color: #6fa5d2;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading {\n  background-color: #e7f0f7;\n  border: 1px solid #c3d9ec;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a {\n  background-color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #c3d9ec;\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a {\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content {\n  background-color: #ebf3f9;\n  border: 1px solid #c3d9ec;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 {\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a {\n  color: #6fa5d2;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content {\n  border-top: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last {\n  padding-right: 0;\n  border-right: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active {\n  text-decoration: underline;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first {\n  padding-left: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first {\n  padding-left: 0;\n}\n.swagger-section .swagger-ui-wrap p#colophon {\n  margin: 0 15px 40px 15px;\n  padding: 10px 0;\n  font-size: 0.8em;\n  border-top: 1px solid #dddddd;\n  font-family: \"Droid Sans\", sans-serif;\n  color: #999999;\n  font-style: italic;\n}\n.swagger-section .swagger-ui-wrap p#colophon a {\n  text-decoration: none;\n  color: #547f00;\n}\n.swagger-section .swagger-ui-wrap h3 {\n  color: black;\n  font-size: 1.1em;\n  padding: 10px 0 10px 0;\n}\n.swagger-section .swagger-ui-wrap .markdown ol,\n.swagger-section .swagger-ui-wrap .markdown ul {\n  font-family: \"Droid Sans\", sans-serif;\n  margin: 5px 0 10px;\n  padding: 0 0 0 18px;\n  list-style-type: disc;\n}\n.swagger-section .swagger-ui-wrap form.form_box {\n  background-color: #ebf3f9;\n  border: 1px solid #c3d9ec;\n  padding: 10px;\n}\n.swagger-section .swagger-ui-wrap form.form_box label {\n  color: #0f6ab4 !important;\n}\n.swagger-section .swagger-ui-wrap form.form_box input[type=submit] {\n  display: block;\n  padding: 10px;\n}\n.swagger-section .swagger-ui-wrap form.form_box p.weak {\n  font-size: 0.8em;\n}\n.swagger-section .swagger-ui-wrap form.form_box p {\n  font-size: 0.9em;\n  padding: 0 0 15px;\n  color: #7e7b6d;\n}\n.swagger-section .swagger-ui-wrap form.form_box p a {\n  color: #646257;\n}\n.swagger-section .swagger-ui-wrap form.form_box p strong {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child {\n  padding-bottom: 0;\n}\n.swagger-section .title {\n  font-style: bold;\n}\n.swagger-section .secondary_form {\n  display: none;\n}\n.swagger-section .main_image {\n  display: block;\n  margin-left: auto;\n  margin-right: auto;\n}\n.swagger-section .oauth_body {\n  margin-left: 100px;\n  margin-right: 100px;\n}\n.swagger-section .oauth_submit {\n  text-align: center;\n  display: inline-block;\n}\n.swagger-section .authorize-wrapper {\n  margin: 15px 0 10px;\n}\n.swagger-section .authorize-wrapper_operation {\n  float: right;\n}\n.swagger-section .authorize__btn:hover {\n  text-decoration: underline;\n  cursor: pointer;\n}\n.swagger-section .authorize__btn_operation:hover .authorize-scopes {\n  display: block;\n}\n.swagger-section .authorize-scopes {\n  position: absolute;\n  margin-top: 20px;\n  background: #FFF;\n  border: 1px solid #ccc;\n  border-radius: 5px;\n  display: none;\n  font-size: 13px;\n  max-width: 300px;\n  line-height: 30px;\n  color: black;\n  padding: 5px;\n}\n.swagger-section .authorize-scopes .authorize__scope {\n  text-decoration: none;\n}\n.swagger-section .authorize__btn_operation {\n  height: 18px;\n  vertical-align: middle;\n  display: inline-block;\n  background: url(../images/explorer_icons.png) no-repeat;\n}\n.swagger-section .authorize__btn_operation_login {\n  background-position: 0 0;\n  width: 18px;\n  margin-top: -6px;\n  margin-left: 4px;\n}\n.swagger-section .authorize__btn_operation_logout {\n  background-position: -30px 0;\n  width: 18px;\n  margin-top: -6px;\n  margin-left: 4px;\n}\n.swagger-section #auth_container {\n  color: #fff;\n  display: inline-block;\n  border: none;\n  padding: 5px;\n  width: 87px;\n  height: 13px;\n}\n.swagger-section #auth_container .authorize__btn {\n  color: #fff;\n}\n.swagger-section .auth_container {\n  padding: 0 0 10px;\n  margin-bottom: 5px;\n  border-bottom: solid 1px #CCC;\n  font-size: 0.9em;\n}\n.swagger-section .auth_container .auth__title {\n  color: #547f00;\n  font-size: 1.2em;\n}\n.swagger-section .auth_container .basic_auth__label {\n  display: inline-block;\n  width: 60px;\n}\n.swagger-section .auth_container .auth__description {\n  color: #999999;\n  margin-bottom: 5px;\n}\n.swagger-section .auth_container .auth__button {\n  margin-top: 10px;\n  height: 30px;\n}\n.swagger-section .auth_container .key_auth__field {\n  margin: 5px 0;\n}\n.swagger-section .auth_container .key_auth__label {\n  display: inline-block;\n  width: 60px;\n}\n.swagger-section .api-popup-dialog {\n  position: absolute;\n  display: none;\n}\n.swagger-section .api-popup-dialog-wrapper {\n  z-index: 1000;\n  width: 500px;\n  background: #FFF;\n  padding: 20px;\n  border: 1px solid #ccc;\n  border-radius: 5px;\n  font-size: 13px;\n  color: #777;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}\n.swagger-section .api-popup-dialog-shadow {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  opacity: 0.2;\n  background-color: gray;\n  z-index: 900;\n}\n.swagger-section .api-popup-dialog .api-popup-title {\n  font-size: 24px;\n  padding: 10px 0;\n}\n.swagger-section .api-popup-dialog .api-popup-title {\n  font-size: 24px;\n  padding: 10px 0;\n}\n.swagger-section .api-popup-dialog .error-msg {\n  padding-left: 5px;\n  padding-bottom: 5px;\n}\n.swagger-section .api-popup-dialog .api-popup-content {\n  max-height: 500px;\n  overflow-y: auto;\n}\n.swagger-section .api-popup-dialog .api-popup-authbtn {\n  height: 30px;\n}\n.swagger-section .api-popup-dialog .api-popup-cancel {\n  height: 30px;\n}\n.swagger-section .api-popup-scopes {\n  padding: 10px 20px;\n}\n.swagger-section .api-popup-scopes li {\n  padding: 5px 0;\n  line-height: 20px;\n}\n.swagger-section .api-popup-scopes li input {\n  position: relative;\n  top: 2px;\n}\n.swagger-section .api-popup-scopes .api-scope-desc {\n  padding-left: 20px;\n  font-style: italic;\n}\n.swagger-section .api-popup-actions {\n  padding-top: 10px;\n}\n#header {\n  display: none;\n}\n.swagger-section .swagger-ui-wrap .model-signature pre {\n  max-height: none;\n}\n.swagger-section .swagger-ui-wrap .body-textarea {\n  width: 100px;\n}\n.swagger-section .swagger-ui-wrap input.parameter {\n  width: 100px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options {\n  display: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints {\n  display: block !important;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content {\n  display: block !important;\n}\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/css/reset.css",
    "content": "/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */\nhtml,\nbody,\ndiv,\nspan,\napplet,\nobject,\niframe,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote,\npre,\na,\nabbr,\nacronym,\naddress,\nbig,\ncite,\ncode,\ndel,\ndfn,\nem,\nimg,\nins,\nkbd,\nq,\ns,\nsamp,\nsmall,\nstrike,\nstrong,\nsub,\nsup,\ntt,\nvar,\nb,\nu,\ni,\ncenter,\ndl,\ndt,\ndd,\nol,\nul,\nli,\nfieldset,\nform,\nlabel,\nlegend,\ntable,\ncaption,\ntbody,\ntfoot,\nthead,\ntr,\nth,\ntd,\narticle,\naside,\ncanvas,\ndetails,\nembed,\nfigure,\nfigcaption,\nfooter,\nheader,\nhgroup,\nmenu,\nnav,\noutput,\nruby,\nsection,\nsummary,\ntime,\nmark,\naudio,\nvideo {\n  margin: 0;\n  padding: 0;\n  border: 0;\n  font-size: 100%;\n  font: inherit;\n  vertical-align: baseline;\n}\n/* HTML5 display-role reset for older browsers */\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmenu,\nnav,\nsection {\n  display: block;\n}\nbody {\n  line-height: 1;\n}\nol,\nul {\n  list-style: none;\n}\nblockquote,\nq {\n  quotes: none;\n}\nblockquote:before,\nblockquote:after,\nq:before,\nq:after {\n  content: '';\n  content: none;\n}\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/css/screen.css",
    "content": "/* Original style from softwaremaniacs.org (c) Ivan Sagalaev <Maniac@SoftwareManiacs.Org> */\n.swagger-section pre code {\n  display: block;\n  padding: 0.5em;\n  background: #F0F0F0;\n}\n.swagger-section pre code,\n.swagger-section pre .subst,\n.swagger-section pre .tag .title,\n.swagger-section pre .lisp .title,\n.swagger-section pre .clojure .built_in,\n.swagger-section pre .nginx .title {\n  color: black;\n}\n.swagger-section pre .string,\n.swagger-section pre .title,\n.swagger-section pre .constant,\n.swagger-section pre .parent,\n.swagger-section pre .tag .value,\n.swagger-section pre .rules .value,\n.swagger-section pre .rules .value .number,\n.swagger-section pre .preprocessor,\n.swagger-section pre .ruby .symbol,\n.swagger-section pre .ruby .symbol .string,\n.swagger-section pre .aggregate,\n.swagger-section pre .template_tag,\n.swagger-section pre .django .variable,\n.swagger-section pre .smalltalk .class,\n.swagger-section pre .addition,\n.swagger-section pre .flow,\n.swagger-section pre .stream,\n.swagger-section pre .bash .variable,\n.swagger-section pre .apache .tag,\n.swagger-section pre .apache .cbracket,\n.swagger-section pre .tex .command,\n.swagger-section pre .tex .special,\n.swagger-section pre .erlang_repl .function_or_atom,\n.swagger-section pre .markdown .header {\n  color: #800;\n}\n.swagger-section pre .comment,\n.swagger-section pre .annotation,\n.swagger-section pre .template_comment,\n.swagger-section pre .diff .header,\n.swagger-section pre .chunk,\n.swagger-section pre .markdown .blockquote {\n  color: #888;\n}\n.swagger-section pre .number,\n.swagger-section pre .date,\n.swagger-section pre .regexp,\n.swagger-section pre .literal,\n.swagger-section pre .smalltalk .symbol,\n.swagger-section pre .smalltalk .char,\n.swagger-section pre .go .constant,\n.swagger-section pre .change,\n.swagger-section pre .markdown .bullet,\n.swagger-section pre .markdown .link_url {\n  color: #080;\n}\n.swagger-section pre .label,\n.swagger-section pre .javadoc,\n.swagger-section pre .ruby .string,\n.swagger-section pre .decorator,\n.swagger-section pre .filter .argument,\n.swagger-section pre .localvars,\n.swagger-section pre .array,\n.swagger-section pre .attr_selector,\n.swagger-section pre .important,\n.swagger-section pre .pseudo,\n.swagger-section pre .pi,\n.swagger-section pre .doctype,\n.swagger-section pre .deletion,\n.swagger-section pre .envvar,\n.swagger-section pre .shebang,\n.swagger-section pre .apache .sqbracket,\n.swagger-section pre .nginx .built_in,\n.swagger-section pre .tex .formula,\n.swagger-section pre .erlang_repl .reserved,\n.swagger-section pre .prompt,\n.swagger-section pre .markdown .link_label,\n.swagger-section pre .vhdl .attribute,\n.swagger-section pre .clojure .attribute,\n.swagger-section pre .coffeescript .property {\n  color: #88F;\n}\n.swagger-section pre .keyword,\n.swagger-section pre .id,\n.swagger-section pre .phpdoc,\n.swagger-section pre .title,\n.swagger-section pre .built_in,\n.swagger-section pre .aggregate,\n.swagger-section pre .css .tag,\n.swagger-section pre .javadoctag,\n.swagger-section pre .phpdoc,\n.swagger-section pre .yardoctag,\n.swagger-section pre .smalltalk .class,\n.swagger-section pre .winutils,\n.swagger-section pre .bash .variable,\n.swagger-section pre .apache .tag,\n.swagger-section pre .go .typename,\n.swagger-section pre .tex .command,\n.swagger-section pre .markdown .strong,\n.swagger-section pre .request,\n.swagger-section pre .status {\n  font-weight: bold;\n}\n.swagger-section pre .markdown .emphasis {\n  font-style: italic;\n}\n.swagger-section pre .nginx .built_in {\n  font-weight: normal;\n}\n.swagger-section pre .coffeescript .javascript,\n.swagger-section pre .javascript .xml,\n.swagger-section pre .tex .formula,\n.swagger-section pre .xml .javascript,\n.swagger-section pre .xml .vbscript,\n.swagger-section pre .xml .css,\n.swagger-section pre .xml .cdata {\n  opacity: 0.5;\n}\n.swagger-section .hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  background: #F0F0F0;\n}\n.swagger-section .hljs,\n.swagger-section .hljs-subst {\n  color: #444;\n}\n.swagger-section .hljs-keyword,\n.swagger-section .hljs-attribute,\n.swagger-section .hljs-selector-tag,\n.swagger-section .hljs-meta-keyword,\n.swagger-section .hljs-doctag,\n.swagger-section .hljs-name {\n  font-weight: bold;\n}\n.swagger-section .hljs-built_in,\n.swagger-section .hljs-literal,\n.swagger-section .hljs-bullet,\n.swagger-section .hljs-code,\n.swagger-section .hljs-addition {\n  color: #1F811F;\n}\n.swagger-section .hljs-regexp,\n.swagger-section .hljs-symbol,\n.swagger-section .hljs-variable,\n.swagger-section .hljs-template-variable,\n.swagger-section .hljs-link,\n.swagger-section .hljs-selector-attr,\n.swagger-section .hljs-selector-pseudo {\n  color: #BC6060;\n}\n.swagger-section .hljs-type,\n.swagger-section .hljs-string,\n.swagger-section .hljs-number,\n.swagger-section .hljs-selector-id,\n.swagger-section .hljs-selector-class,\n.swagger-section .hljs-quote,\n.swagger-section .hljs-template-tag,\n.swagger-section .hljs-deletion {\n  color: #880000;\n}\n.swagger-section .hljs-title,\n.swagger-section .hljs-section {\n  color: #880000;\n  font-weight: bold;\n}\n.swagger-section .hljs-comment {\n  color: #888888;\n}\n.swagger-section .hljs-meta {\n  color: #2B6EA1;\n}\n.swagger-section .hljs-emphasis {\n  font-style: italic;\n}\n.swagger-section .hljs-strong {\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap {\n  line-height: 1;\n  font-family: \"Droid Sans\", sans-serif;\n  min-width: 760px;\n  max-width: 960px;\n  margin-left: auto;\n  margin-right: auto;\n  /* JSONEditor specific styling */\n}\n.swagger-section .swagger-ui-wrap b,\n.swagger-section .swagger-ui-wrap strong {\n  font-family: \"Droid Sans\", sans-serif;\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap q,\n.swagger-section .swagger-ui-wrap blockquote {\n  quotes: none;\n}\n.swagger-section .swagger-ui-wrap p {\n  line-height: 1.4em;\n  padding: 0 0 10px;\n  color: #333333;\n}\n.swagger-section .swagger-ui-wrap q:before,\n.swagger-section .swagger-ui-wrap q:after,\n.swagger-section .swagger-ui-wrap blockquote:before,\n.swagger-section .swagger-ui-wrap blockquote:after {\n  content: none;\n}\n.swagger-section .swagger-ui-wrap .heading_with_menu h1,\n.swagger-section .swagger-ui-wrap .heading_with_menu h2,\n.swagger-section .swagger-ui-wrap .heading_with_menu h3,\n.swagger-section .swagger-ui-wrap .heading_with_menu h4,\n.swagger-section .swagger-ui-wrap .heading_with_menu h5,\n.swagger-section .swagger-ui-wrap .heading_with_menu h6 {\n  display: block;\n  clear: none;\n  float: left;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  -ms-box-sizing: border-box;\n  box-sizing: border-box;\n  width: 60%;\n}\n.swagger-section .swagger-ui-wrap table {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n.swagger-section .swagger-ui-wrap table thead tr th {\n  padding: 5px;\n  font-size: 0.9em;\n  color: #666666;\n  border-bottom: 1px solid #999999;\n}\n.swagger-section .swagger-ui-wrap table tbody tr:last-child td {\n  border-bottom: none;\n}\n.swagger-section .swagger-ui-wrap table tbody tr.offset {\n  background-color: #f0f0f0;\n}\n.swagger-section .swagger-ui-wrap table tbody tr td {\n  padding: 6px;\n  font-size: 0.9em;\n  border-bottom: 1px solid #cccccc;\n  vertical-align: top;\n  line-height: 1.3em;\n}\n.swagger-section .swagger-ui-wrap ol {\n  margin: 0px 0 10px;\n  padding: 0 0 0 18px;\n  list-style-type: decimal;\n}\n.swagger-section .swagger-ui-wrap ol li {\n  padding: 5px 0px;\n  font-size: 0.9em;\n  color: #333333;\n}\n.swagger-section .swagger-ui-wrap ol,\n.swagger-section .swagger-ui-wrap ul {\n  list-style: none;\n}\n.swagger-section .swagger-ui-wrap h1 a,\n.swagger-section .swagger-ui-wrap h2 a,\n.swagger-section .swagger-ui-wrap h3 a,\n.swagger-section .swagger-ui-wrap h4 a,\n.swagger-section .swagger-ui-wrap h5 a,\n.swagger-section .swagger-ui-wrap h6 a {\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap h1 a:hover,\n.swagger-section .swagger-ui-wrap h2 a:hover,\n.swagger-section .swagger-ui-wrap h3 a:hover,\n.swagger-section .swagger-ui-wrap h4 a:hover,\n.swagger-section .swagger-ui-wrap h5 a:hover,\n.swagger-section .swagger-ui-wrap h6 a:hover {\n  text-decoration: underline;\n}\n.swagger-section .swagger-ui-wrap h1 span.divider,\n.swagger-section .swagger-ui-wrap h2 span.divider,\n.swagger-section .swagger-ui-wrap h3 span.divider,\n.swagger-section .swagger-ui-wrap h4 span.divider,\n.swagger-section .swagger-ui-wrap h5 span.divider,\n.swagger-section .swagger-ui-wrap h6 span.divider {\n  color: #aaaaaa;\n}\n.swagger-section .swagger-ui-wrap a {\n  color: #547f00;\n}\n.swagger-section .swagger-ui-wrap a img {\n  border: none;\n}\n.swagger-section .swagger-ui-wrap article,\n.swagger-section .swagger-ui-wrap aside,\n.swagger-section .swagger-ui-wrap details,\n.swagger-section .swagger-ui-wrap figcaption,\n.swagger-section .swagger-ui-wrap figure,\n.swagger-section .swagger-ui-wrap footer,\n.swagger-section .swagger-ui-wrap header,\n.swagger-section .swagger-ui-wrap hgroup,\n.swagger-section .swagger-ui-wrap menu,\n.swagger-section .swagger-ui-wrap nav,\n.swagger-section .swagger-ui-wrap section,\n.swagger-section .swagger-ui-wrap summary {\n  display: block;\n}\n.swagger-section .swagger-ui-wrap pre {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  background-color: #fcf6db;\n  border: 1px solid #e5e0c6;\n  padding: 10px;\n}\n.swagger-section .swagger-ui-wrap pre code {\n  line-height: 1.6em;\n  background: none;\n}\n.swagger-section .swagger-ui-wrap .content > .content-type > div > label {\n  clear: both;\n  display: block;\n  color: #0F6AB4;\n  font-size: 1.1em;\n  margin: 0;\n  padding: 15px 0 5px;\n}\n.swagger-section .swagger-ui-wrap .content pre {\n  font-size: 12px;\n  margin-top: 5px;\n  padding: 5px;\n}\n.swagger-section .swagger-ui-wrap .icon-btn {\n  cursor: pointer;\n}\n.swagger-section .swagger-ui-wrap .info_title {\n  padding-bottom: 10px;\n  font-weight: bold;\n  font-size: 25px;\n}\n.swagger-section .swagger-ui-wrap .footer {\n  margin-top: 20px;\n}\n.swagger-section .swagger-ui-wrap p.big,\n.swagger-section .swagger-ui-wrap div.big p {\n  font-size: 1em;\n  margin-bottom: 10px;\n}\n.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input,\n.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input,\n.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea,\n.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input {\n  width: 500px !important;\n}\n.swagger-section .swagger-ui-wrap .info_license {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .info_tos {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .message-fail {\n  color: #cc0000;\n}\n.swagger-section .swagger-ui-wrap .info_url {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .info_email {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .info_name {\n  padding-bottom: 5px;\n}\n.swagger-section .swagger-ui-wrap .info_description {\n  padding-bottom: 10px;\n  font-size: 15px;\n}\n.swagger-section .swagger-ui-wrap .markdown ol li,\n.swagger-section .swagger-ui-wrap .markdown ul li {\n  padding: 3px 0px;\n  line-height: 1.4em;\n  color: #333333;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input,\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input,\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input {\n  display: block;\n  padding: 4px;\n  width: auto;\n  clear: both;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title,\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title,\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title {\n  font-size: 1.3em;\n}\n.swagger-section .swagger-ui-wrap table.fullwidth {\n  width: 100%;\n}\n.swagger-section .swagger-ui-wrap .model-signature {\n  font-family: \"Droid Sans\", sans-serif;\n  font-size: 1em;\n  line-height: 1.5em;\n}\n.swagger-section .swagger-ui-wrap .model-signature .signature-nav a {\n  text-decoration: none;\n  color: #AAA;\n}\n.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover {\n  text-decoration: underline;\n  color: black;\n}\n.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected {\n  color: black;\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap .model-signature .propType {\n  color: #5555aa;\n}\n.swagger-section .swagger-ui-wrap .model-signature pre:hover {\n  background-color: #ffffdd;\n}\n.swagger-section .swagger-ui-wrap .model-signature pre {\n  font-size: .85em;\n  line-height: 1.2em;\n  overflow: auto;\n  max-height: 200px;\n  cursor: pointer;\n}\n.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav {\n  display: block;\n  min-width: 230px;\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child {\n  padding-right: 0;\n  border-right: none;\n}\n.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li {\n  float: left;\n  margin: 0 5px 5px 0;\n  padding: 2px 5px 2px 0;\n  border-right: 1px solid #ddd;\n}\n.swagger-section .swagger-ui-wrap .model-signature .propOpt {\n  color: #555;\n}\n.swagger-section .swagger-ui-wrap .model-signature .snippet small {\n  font-size: 0.75em;\n}\n.swagger-section .swagger-ui-wrap .model-signature .propOptKey {\n  font-style: italic;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .strong {\n  font-weight: bold;\n  color: #000;\n  font-size: .9em;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description div {\n  font-size: 0.9em;\n  line-height: 1.5em;\n  margin-left: 1em;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .stronger {\n  font-weight: bold;\n  color: #000;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper {\n  border-spacing: 0;\n  position: absolute;\n  background-color: #ffffff;\n  border: 1px solid #bbbbbb;\n  display: none;\n  font-size: 11px;\n  max-width: 400px;\n  line-height: 30px;\n  color: black;\n  padding: 5px;\n  margin-left: 10px;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th {\n  text-align: center;\n  background-color: #eeeeee;\n  border: 1px solid #bbbbbb;\n  font-size: 11px;\n  color: #666666;\n  font-weight: bold;\n  padding: 5px;\n  line-height: 15px;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName {\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child,\n.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child {\n  display: inline;\n}\n.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before {\n  display: block;\n  content: '';\n}\n.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child {\n  margin-right: -3px;\n}\n.swagger-section .swagger-ui-wrap .model-signature .propName {\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap .model-signature .signature-container {\n  clear: both;\n}\n.swagger-section .swagger-ui-wrap .body-textarea {\n  width: 300px;\n  height: 100px;\n  border: 1px solid #aaa;\n}\n.swagger-section .swagger-ui-wrap .markdown p code,\n.swagger-section .swagger-ui-wrap .markdown li code {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  background-color: #f0f0f0;\n  color: black;\n  padding: 1px 3px;\n}\n.swagger-section .swagger-ui-wrap .required {\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap .editor_holder {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap .editor_holder label {\n  font-weight: normal!important;\n  /* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */\n}\n.swagger-section .swagger-ui-wrap .editor_holder label.required {\n  font-weight: bold!important;\n}\n.swagger-section .swagger-ui-wrap input.parameter {\n  width: 300px;\n  border: 1px solid #aaa;\n}\n.swagger-section .swagger-ui-wrap h1 {\n  color: black;\n  font-size: 1.5em;\n  line-height: 1.3em;\n  padding: 10px 0 10px 0;\n  font-family: \"Droid Sans\", sans-serif;\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap .heading_with_menu {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n}\n.swagger-section .swagger-ui-wrap .heading_with_menu ul {\n  display: block;\n  clear: none;\n  float: right;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  -ms-box-sizing: border-box;\n  box-sizing: border-box;\n  margin-top: 10px;\n}\n.swagger-section .swagger-ui-wrap h2 {\n  color: black;\n  font-size: 1.3em;\n  padding: 10px 0 10px 0;\n}\n.swagger-section .swagger-ui-wrap h2 a {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap h2 span.sub {\n  font-size: 0.7em;\n  color: #999999;\n  font-style: italic;\n}\n.swagger-section .swagger-ui-wrap h2 span.sub a {\n  color: #777777;\n}\n.swagger-section .swagger-ui-wrap span.weak {\n  color: #666666;\n}\n.swagger-section .swagger-ui-wrap .message-success {\n  color: #89BF04;\n}\n.swagger-section .swagger-ui-wrap caption,\n.swagger-section .swagger-ui-wrap th,\n.swagger-section .swagger-ui-wrap td {\n  text-align: left;\n  font-weight: normal;\n  vertical-align: middle;\n}\n.swagger-section .swagger-ui-wrap .code {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea {\n  font-family: \"Droid Sans\", sans-serif;\n  height: 250px;\n  padding: 4px;\n  display: block;\n  clear: both;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select {\n  display: block;\n  clear: both;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label {\n  display: block;\n  float: left;\n  clear: none;\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input {\n  display: block;\n  float: left;\n  clear: none;\n  margin: 0 5px 0 0;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label {\n  display: block;\n  clear: both;\n  width: auto;\n  padding: 0 0 3px;\n  color: #666666;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr {\n  padding-left: 3px;\n  color: #888888;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints {\n  margin-left: 0;\n  font-style: italic;\n  font-size: 0.9em;\n  margin: 0;\n}\n.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons {\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap span.blank,\n.swagger-section .swagger-ui-wrap span.empty {\n  color: #888888;\n  font-style: italic;\n}\n.swagger-section .swagger-ui-wrap .markdown h3 {\n  color: #547f00;\n}\n.swagger-section .swagger-ui-wrap .markdown h4 {\n  color: #666666;\n}\n.swagger-section .swagger-ui-wrap .markdown pre {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  background-color: #fcf6db;\n  border: 1px solid #e5e0c6;\n  padding: 10px;\n  margin: 0 0 10px 0;\n}\n.swagger-section .swagger-ui-wrap .markdown pre code {\n  line-height: 1.6em;\n  overflow: auto;\n}\n.swagger-section .swagger-ui-wrap div.gist {\n  margin: 20px 0 25px 0 !important;\n}\n.swagger-section .swagger-ui-wrap ul#resources {\n  font-family: \"Droid Sans\", sans-serif;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource {\n  border-bottom: 1px solid #dddddd;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a,\n.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a,\n.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a {\n  color: #555555;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child {\n  border-bottom: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading {\n  border: 1px solid transparent;\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options {\n  overflow: hidden;\n  padding: 0;\n  display: block;\n  clear: none;\n  float: right;\n  margin: 14px 10px 0 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li {\n  float: left;\n  clear: none;\n  margin: 0;\n  padding: 2px 10px;\n  border-right: 1px solid #dddddd;\n  color: #666666;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a {\n  color: #aaaaaa;\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover {\n  text-decoration: underline;\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active {\n  text-decoration: underline;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first {\n  padding-left: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last {\n  padding-right: 0;\n  border-right: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first {\n  padding-left: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 {\n  color: #999999;\n  padding-left: 0;\n  display: block;\n  clear: none;\n  float: left;\n  font-family: \"Droid Sans\", sans-serif;\n  font-weight: bold;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a {\n  color: #999999;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n  margin: 0 0 10px;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 {\n  display: block;\n  clear: none;\n  float: left;\n  width: auto;\n  margin: 0;\n  padding: 0;\n  line-height: 1.1em;\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path {\n  padding-left: 10px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a {\n  color: black;\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a.toggleOperation.deprecated {\n  text-decoration: line-through;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover {\n  text-decoration: underline;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a {\n  text-transform: uppercase;\n  text-decoration: none;\n  color: white;\n  display: inline-block;\n  width: 50px;\n  font-size: 0.7em;\n  text-align: center;\n  padding: 7px 0 4px;\n  -moz-border-radius: 2px;\n  -webkit-border-radius: 2px;\n  -o-border-radius: 2px;\n  -ms-border-radius: 2px;\n  -khtml-border-radius: 2px;\n  border-radius: 2px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span {\n  margin: 0;\n  padding: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options {\n  overflow: hidden;\n  padding: 0;\n  display: block;\n  clear: none;\n  float: right;\n  margin: 6px 10px 0 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li {\n  float: left;\n  clear: none;\n  margin: 0;\n  padding: 2px 10px;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a {\n  text-decoration: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a .markdown p {\n  color: inherit;\n  padding: 0;\n  line-height: inherit;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content {\n  border-top: none;\n  padding: 10px;\n  -moz-border-radius-bottomleft: 6px;\n  -webkit-border-bottom-left-radius: 6px;\n  -o-border-bottom-left-radius: 6px;\n  -ms-border-bottom-left-radius: 6px;\n  -khtml-border-bottom-left-radius: 6px;\n  border-bottom-left-radius: 6px;\n  -moz-border-radius-bottomright: 6px;\n  -webkit-border-bottom-right-radius: 6px;\n  -o-border-bottom-right-radius: 6px;\n  -ms-border-bottom-right-radius: 6px;\n  -khtml-border-bottom-right-radius: 6px;\n  border-bottom-right-radius: 6px;\n  margin: 0 0 20px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 {\n  font-size: 1.1em;\n  margin: 0;\n  padding: 15px 0 5px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header {\n  float: none;\n  clear: both;\n  overflow: hidden;\n  display: block;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a {\n  padding: 4px 0 0 10px;\n  display: inline-block;\n  font-size: 0.9em;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit {\n  display: block;\n  clear: none;\n  float: left;\n  padding: 6px 8px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber {\n  background-image: url('../images/throbber.gif');\n  width: 128px;\n  height: 16px;\n  display: block;\n  clear: none;\n  float: right;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error {\n  outline: 2px solid black;\n  outline-color: #cc0000;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] {\n  max-width: 300px;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre {\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  padding: 10px;\n  font-size: 0.9em;\n  max-height: 400px;\n  overflow-y: auto;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading {\n  background-color: #f9f2e9;\n  border: 1px solid #f0e0ca;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a {\n  background-color: #c5862b;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #f0e0ca;\n  color: #c5862b;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a {\n  color: #c5862b;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content {\n  background-color: #faf5ee;\n  border: 1px solid #f0e0ca;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 {\n  color: #c5862b;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a {\n  color: #dcb67f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading {\n  background-color: #fcffcd;\n  border: 1px solid black;\n  border-color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a {\n  text-transform: uppercase;\n  background-color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #ffd20f;\n  color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a {\n  color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content {\n  background-color: #fcffcd;\n  border: 1px solid black;\n  border-color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 {\n  color: #ffd20f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a {\n  color: #6fc992;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading {\n  background-color: #f5e8e8;\n  border: 1px solid #e8c6c7;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a {\n  text-transform: uppercase;\n  background-color: #a41e22;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #e8c6c7;\n  color: #a41e22;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a {\n  color: #a41e22;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content {\n  background-color: #f7eded;\n  border: 1px solid #e8c6c7;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 {\n  color: #a41e22;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a {\n  color: #c8787a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading {\n  background-color: #e7f6ec;\n  border: 1px solid #c3e8d1;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a {\n  background-color: #10a54a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #c3e8d1;\n  color: #10a54a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a {\n  color: #10a54a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content {\n  background-color: #ebf7f0;\n  border: 1px solid #c3e8d1;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 {\n  color: #10a54a;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a {\n  color: #6fc992;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading {\n  background-color: #FCE9E3;\n  border: 1px solid #F5D5C3;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a {\n  background-color: #D38042;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #f0cecb;\n  color: #D38042;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a {\n  color: #D38042;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content {\n  background-color: #faf0ef;\n  border: 1px solid #f0cecb;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 {\n  color: #D38042;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a {\n  color: #dcb67f;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading {\n  background-color: #e7f0f7;\n  border: 1px solid #c3d9ec;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a {\n  background-color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #c3d9ec;\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a {\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content {\n  background-color: #ebf3f9;\n  border: 1px solid #c3d9ec;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 {\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a {\n  color: #6fa5d2;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading {\n  background-color: #e7f0f7;\n  border: 1px solid #c3d9ec;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a {\n  background-color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li {\n  border-right: 1px solid #dddddd;\n  border-right-color: #c3d9ec;\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a {\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content {\n  background-color: #ebf3f9;\n  border: 1px solid #c3d9ec;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 {\n  color: #0f6ab4;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a {\n  color: #6fa5d2;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content {\n  border-top: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last {\n  padding-right: 0;\n  border-right: none;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active {\n  text-decoration: underline;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first {\n  padding-left: 0;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child,\n.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first {\n  padding-left: 0;\n}\n.swagger-section .swagger-ui-wrap p#colophon {\n  margin: 0 15px 40px 15px;\n  padding: 10px 0;\n  font-size: 0.8em;\n  border-top: 1px solid #dddddd;\n  font-family: \"Droid Sans\", sans-serif;\n  color: #999999;\n  font-style: italic;\n}\n.swagger-section .swagger-ui-wrap p#colophon a {\n  text-decoration: none;\n  color: #547f00;\n}\n.swagger-section .swagger-ui-wrap h3 {\n  color: black;\n  font-size: 1.1em;\n  padding: 10px 0 10px 0;\n}\n.swagger-section .swagger-ui-wrap .markdown ol,\n.swagger-section .swagger-ui-wrap .markdown ul {\n  font-family: \"Droid Sans\", sans-serif;\n  margin: 5px 0 10px;\n  padding: 0 0 0 18px;\n  list-style-type: disc;\n}\n.swagger-section .swagger-ui-wrap form.form_box {\n  background-color: #ebf3f9;\n  border: 1px solid #c3d9ec;\n  padding: 10px;\n}\n.swagger-section .swagger-ui-wrap form.form_box label {\n  color: #0f6ab4 !important;\n}\n.swagger-section .swagger-ui-wrap form.form_box input[type=submit] {\n  display: block;\n  padding: 10px;\n}\n.swagger-section .swagger-ui-wrap form.form_box p.weak {\n  font-size: 0.8em;\n}\n.swagger-section .swagger-ui-wrap form.form_box p {\n  font-size: 0.9em;\n  padding: 0 0 15px;\n  color: #7e7b6d;\n}\n.swagger-section .swagger-ui-wrap form.form_box p a {\n  color: #646257;\n}\n.swagger-section .swagger-ui-wrap form.form_box p strong {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child {\n  padding-bottom: 0;\n}\n.swagger-section .title {\n  font-style: bold;\n}\n.swagger-section .secondary_form {\n  display: none;\n}\n.swagger-section .main_image {\n  display: block;\n  margin-left: auto;\n  margin-right: auto;\n}\n.swagger-section .oauth_body {\n  margin-left: 100px;\n  margin-right: 100px;\n}\n.swagger-section .oauth_submit {\n  text-align: center;\n  display: inline-block;\n}\n.swagger-section .authorize-wrapper {\n  margin: 15px 0 10px;\n}\n.swagger-section .authorize-wrapper_operation {\n  float: right;\n}\n.swagger-section .authorize__btn:hover {\n  text-decoration: underline;\n  cursor: pointer;\n}\n.swagger-section .authorize__btn_operation:hover .authorize-scopes {\n  display: block;\n}\n.swagger-section .authorize-scopes {\n  position: absolute;\n  margin-top: 20px;\n  background: #FFF;\n  border: 1px solid #ccc;\n  border-radius: 5px;\n  display: none;\n  font-size: 13px;\n  max-width: 300px;\n  line-height: 30px;\n  color: black;\n  padding: 5px;\n}\n.swagger-section .authorize-scopes .authorize__scope {\n  text-decoration: none;\n}\n.swagger-section .authorize__btn_operation {\n  height: 18px;\n  vertical-align: middle;\n  display: inline-block;\n  background: url(../images/explorer_icons.png) no-repeat;\n}\n.swagger-section .authorize__btn_operation_login {\n  background-position: 0 0;\n  width: 18px;\n  margin-top: -6px;\n  margin-left: 4px;\n}\n.swagger-section .authorize__btn_operation_logout {\n  background-position: -30px 0;\n  width: 18px;\n  margin-top: -6px;\n  margin-left: 4px;\n}\n.swagger-section #auth_container {\n  color: #fff;\n  display: inline-block;\n  border: none;\n  padding: 5px;\n  width: 87px;\n  height: 13px;\n}\n.swagger-section #auth_container .authorize__btn {\n  color: #fff;\n}\n.swagger-section .auth_container {\n  padding: 0 0 10px;\n  margin-bottom: 5px;\n  border-bottom: solid 1px #CCC;\n  font-size: 0.9em;\n}\n.swagger-section .auth_container .auth__title {\n  color: #547f00;\n  font-size: 1.2em;\n}\n.swagger-section .auth_container .basic_auth__label {\n  display: inline-block;\n  width: 60px;\n}\n.swagger-section .auth_container .auth__description {\n  color: #999999;\n  margin-bottom: 5px;\n}\n.swagger-section .auth_container .auth__button {\n  margin-top: 10px;\n  height: 30px;\n}\n.swagger-section .auth_container .key_auth__field {\n  margin: 5px 0;\n}\n.swagger-section .auth_container .key_auth__label {\n  display: inline-block;\n  width: 60px;\n}\n.swagger-section .api-popup-dialog {\n  position: absolute;\n  display: none;\n}\n.swagger-section .api-popup-dialog-wrapper {\n  z-index: 1000;\n  width: 500px;\n  background: #FFF;\n  padding: 20px;\n  border: 1px solid #ccc;\n  border-radius: 5px;\n  font-size: 13px;\n  color: #777;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}\n.swagger-section .api-popup-dialog-shadow {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  opacity: 0.2;\n  background-color: gray;\n  z-index: 900;\n}\n.swagger-section .api-popup-dialog .api-popup-title {\n  font-size: 24px;\n  padding: 10px 0;\n}\n.swagger-section .api-popup-dialog .api-popup-title {\n  font-size: 24px;\n  padding: 10px 0;\n}\n.swagger-section .api-popup-dialog .error-msg {\n  padding-left: 5px;\n  padding-bottom: 5px;\n}\n.swagger-section .api-popup-dialog .api-popup-content {\n  max-height: 500px;\n  overflow-y: auto;\n}\n.swagger-section .api-popup-dialog .api-popup-authbtn {\n  height: 30px;\n}\n.swagger-section .api-popup-dialog .api-popup-cancel {\n  height: 30px;\n}\n.swagger-section .api-popup-scopes {\n  padding: 10px 20px;\n}\n.swagger-section .api-popup-scopes li {\n  padding: 5px 0;\n  line-height: 20px;\n}\n.swagger-section .api-popup-scopes li input {\n  position: relative;\n  top: 2px;\n}\n.swagger-section .api-popup-scopes .api-scope-desc {\n  padding-left: 20px;\n  font-style: italic;\n}\n.swagger-section .api-popup-actions {\n  padding-top: 10px;\n}\n.swagger-section .access {\n  float: right;\n}\n.swagger-section .auth {\n  float: right;\n}\n.swagger-section .api-ic {\n  height: 18px;\n  vertical-align: middle;\n  display: inline-block;\n  background: url(../images/explorer_icons.png) no-repeat;\n}\n.swagger-section .api-ic .api_information_panel {\n  position: relative;\n  margin-top: 20px;\n  margin-left: -5px;\n  background: #FFF;\n  border: 1px solid #ccc;\n  border-radius: 5px;\n  display: none;\n  font-size: 13px;\n  max-width: 300px;\n  line-height: 30px;\n  color: black;\n  padding: 5px;\n}\n.swagger-section .api-ic .api_information_panel p .api-msg-enabled {\n  color: green;\n}\n.swagger-section .api-ic .api_information_panel p .api-msg-disabled {\n  color: red;\n}\n.swagger-section .api-ic:hover .api_information_panel {\n  position: absolute;\n  display: block;\n}\n.swagger-section .ic-info {\n  background-position: 0 0;\n  width: 18px;\n  margin-top: -6px;\n  margin-left: 4px;\n}\n.swagger-section .ic-warning {\n  background-position: -60px 0;\n  width: 18px;\n  margin-top: -6px;\n  margin-left: 4px;\n}\n.swagger-section .ic-error {\n  background-position: -30px 0;\n  width: 18px;\n  margin-top: -6px;\n  margin-left: 4px;\n}\n.swagger-section .ic-off {\n  background-position: -90px 0;\n  width: 58px;\n  margin-top: -4px;\n  cursor: pointer;\n}\n.swagger-section .ic-on {\n  background-position: -160px 0;\n  width: 58px;\n  margin-top: -4px;\n  cursor: pointer;\n}\n.swagger-section #header {\n  background-color: #89bf04;\n  padding: 9px 14px 19px 14px;\n  height: 23px;\n  min-width: 775px;\n}\n.swagger-section #input_baseUrl {\n  width: 400px;\n}\n.swagger-section #api_selector {\n  display: block;\n  clear: none;\n  float: right;\n}\n.swagger-section #api_selector .input {\n  display: inline-block;\n  clear: none;\n  margin: 0 10px 0 0;\n}\n.swagger-section #api_selector input {\n  font-size: 0.9em;\n  padding: 3px;\n  margin: 0;\n}\n.swagger-section #input_apiKey {\n  width: 200px;\n}\n.swagger-section #explore,\n.swagger-section #auth_container .authorize__btn {\n  display: block;\n  text-decoration: none;\n  font-weight: bold;\n  padding: 6px 8px;\n  font-size: 0.9em;\n  color: white;\n  background-color: #547f00;\n  -moz-border-radius: 4px;\n  -webkit-border-radius: 4px;\n  -o-border-radius: 4px;\n  -ms-border-radius: 4px;\n  -khtml-border-radius: 4px;\n  border-radius: 4px;\n}\n.swagger-section #explore:hover,\n.swagger-section #auth_container .authorize__btn:hover {\n  background-color: #547f00;\n}\n.swagger-section #header #logo {\n  font-size: 1.5em;\n  font-weight: bold;\n  text-decoration: none;\n  color: white;\n}\n.swagger-section #header #logo .logo__img {\n  display: block;\n  float: left;\n  margin-top: 2px;\n}\n.swagger-section #header #logo .logo__title {\n  display: inline-block;\n  padding: 5px 0 0 10px;\n}\n.swagger-section #content_message {\n  margin: 10px 15px;\n  font-style: italic;\n  color: #999999;\n}\n.swagger-section #message-bar {\n  min-height: 30px;\n  text-align: center;\n  padding-top: 10px;\n}\n.swagger-section .swagger-collapse:before {\n  content: \"-\";\n}\n.swagger-section .swagger-expand:before {\n  content: \"+\";\n}\n.swagger-section .error {\n  outline-color: #cc0000;\n  background-color: #f2dede;\n}\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/css/style.css",
    "content": ".swagger-section #header a#logo {\n  font-size: 1.5em;\n  font-weight: bold;\n  text-decoration: none;\n  background: transparent url(../images/logo.png) no-repeat left center;\n  padding: 20px 0 20px 40px;\n}\n#text-head {\n  font-size: 80px;\n  font-family: 'Roboto', sans-serif;\n  color: #ffffff;\n  float: right;\n  margin-right: 20%;\n}\n.navbar-fixed-top .navbar-nav {\n  height: auto;\n}\n.navbar-fixed-top .navbar-brand {\n  height: auto;\n}\n.navbar-header {\n  height: auto;\n}\n.navbar-inverse {\n  background-color: #000;\n  border-color: #000;\n}\n#navbar-brand {\n  margin-left: 20%;\n}\n.navtext {\n  font-size: 10px;\n}\n.h1,\nh1 {\n  font-size: 60px;\n}\n.navbar-default .navbar-header .navbar-brand {\n  color: #a2dfee;\n}\n/* tag titles */\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a {\n  color: #393939;\n  font-family: 'Arvo', serif;\n  font-size: 1.5em;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover {\n  color: black;\n}\n.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 {\n  color: #525252;\n  padding-left: 0px;\n  display: block;\n  clear: none;\n  float: left;\n  font-family: 'Arvo', serif;\n  font-weight: bold;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n  border-color: #0A0A0A;\n}\n.container1 {\n  width: 1500px;\n  margin: auto;\n  margin-top: 0;\n  background-image: url('../images/shield.png');\n  background-repeat: no-repeat;\n  background-position: -40px -20px;\n  margin-bottom: 210px;\n}\n.container-inner {\n  width: 1200px;\n  margin: auto;\n  background-color: rgba(223, 227, 228, 0.75);\n  padding-bottom: 40px;\n  padding-top: 40px;\n  border-radius: 15px;\n}\n.header-content {\n  padding: 0;\n  width: 1000px;\n}\n.title1 {\n  font-size: 80px;\n  font-family: 'Vollkorn', serif;\n  color: #404040;\n  text-align: center;\n  padding-top: 40px;\n  padding-bottom: 100px;\n}\n#icon {\n  margin-top: -18px;\n}\n.subtext {\n  font-size: 25px;\n  font-style: italic;\n  color: #08b;\n  text-align: right;\n  padding-right: 250px;\n}\n.bg-primary {\n  background-color: #00468b;\n}\n.navbar-default .nav > li > a,\n.navbar-default .nav > li > a:focus {\n  color: #08b;\n}\n.navbar-default .nav > li > a,\n.navbar-default .nav > li > a:hover {\n  color: #08b;\n}\n.navbar-default .nav > li > a,\n.navbar-default .nav > li > a:focus:hover {\n  color: #08b;\n}\n.text-faded {\n  font-size: 25px;\n  font-family: 'Vollkorn', serif;\n}\n.section-heading {\n  font-family: 'Vollkorn', serif;\n  font-size: 45px;\n  padding-bottom: 10px;\n}\nhr {\n  border-color: #00468b;\n  padding-bottom: 10px;\n}\n.description {\n  margin-top: 20px;\n  padding-bottom: 200px;\n}\n.description li {\n  font-family: 'Vollkorn', serif;\n  font-size: 25px;\n  color: #525252;\n  margin-left: 28%;\n  padding-top: 5px;\n}\n.gap {\n  margin-top: 200px;\n}\n.troubleshootingtext {\n  color: rgba(255, 255, 255, 0.7);\n  padding-left: 30%;\n}\n.troubleshootingtext li {\n  list-style-type: circle;\n  font-size: 25px;\n  padding-bottom: 5px;\n}\n.overlay {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  z-index: 1000;\n}\n.block.response_body.json:hover {\n  cursor: pointer;\n}\n.backdrop {\n  color: blue;\n}\n#myModal {\n  height: 100%;\n}\n.modal-backdrop {\n  bottom: 0;\n  position: fixed;\n}\n.curl {\n  padding: 10px;\n  font-family: \"Anonymous Pro\", \"Menlo\", \"Consolas\", \"Bitstream Vera Sans Mono\", \"Courier New\", monospace;\n  font-size: 0.9em;\n  max-height: 400px;\n  margin-top: 5px;\n  overflow-y: auto;\n  background-color: #fcf6db;\n  border: 1px solid #e5e0c6;\n  border-radius: 4px;\n}\n.curl_title {\n  font-size: 1.1em;\n  margin: 0;\n  padding: 15px 0 5px;\n  font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;\n  font-weight: 500;\n  line-height: 1.1;\n}\n.footer {\n  display: none;\n}\n.swagger-section .swagger-ui-wrap h2 {\n  padding: 0;\n}\nh2 {\n  margin: 0;\n  margin-bottom: 5px;\n}\n.markdown p {\n  font-size: 15px;\n  font-family: 'Arvo', serif;\n}\n.swagger-section .swagger-ui-wrap .code {\n  font-size: 15px;\n  font-family: 'Arvo', serif;\n}\n.swagger-section .swagger-ui-wrap b {\n  font-family: 'Arvo', serif;\n}\n#signin:hover {\n  cursor: pointer;\n}\n.dropdown-menu {\n  padding: 15px;\n}\n.navbar-right .dropdown-menu {\n  left: 0;\n  right: auto;\n}\n#signinbutton {\n  width: 100%;\n  height: 32px;\n  font-size: 13px;\n  font-weight: bold;\n  color: #08b;\n}\n.navbar-default .nav > li .details {\n  color: #000000;\n  text-transform: none;\n  font-size: 15px;\n  font-weight: normal;\n  font-family: 'Open Sans', sans-serif;\n  font-style: italic;\n  line-height: 20px;\n  top: -2px;\n}\n.navbar-default .nav > li .details:hover {\n  color: black;\n}\n#signout {\n  width: 100%;\n  height: 32px;\n  font-size: 13px;\n  font-weight: bold;\n  color: #08b;\n}\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/css/typography.css",
    "content": "/* Google Font's Droid Sans */\n@font-face {\n  font-family: 'Droid Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf') format('truetype');\n}\n/* Google Font's Droid Sans Bold */\n@font-face {\n  font-family: 'Droid Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf') format('truetype');\n}\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Swagger UI</title>\n  <link rel=\"icon\" type=\"image/png\" href=\"images/favicon-32x32.png\" sizes=\"32x32\" />\n  <link rel=\"icon\" type=\"image/png\" href=\"images/favicon-16x16.png\" sizes=\"16x16\" />\n  <link href='css/typography.css' media='screen' rel='stylesheet' type='text/css'/>\n  <link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>\n  <link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>\n  <link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>\n  <link href='css/print.css' media='print' rel='stylesheet' type='text/css'/>\n\n  <script src='lib/object-assign-pollyfill.js' type='text/javascript'></script>\n  <script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>\n  <script src='lib/jquery.slideto.min.js' type='text/javascript'></script>\n  <script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>\n  <script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>\n  <script src='lib/handlebars-4.0.5.js' type='text/javascript'></script>\n  <script src='lib/lodash.min.js' type='text/javascript'></script>\n  <script src='lib/backbone-min.js' type='text/javascript'></script>\n  <script src='swagger-ui.js' type='text/javascript'></script>\n  <script src='lib/highlight.9.1.0.pack.js' type='text/javascript'></script>\n  <script src='lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script>\n  <script src='lib/jsoneditor.min.js' type='text/javascript'></script>\n  <script src='lib/marked.js' type='text/javascript'></script>\n  <script src='lib/swagger-oauth.js' type='text/javascript'></script>\n  <!-- Some basic translations -->\n  <!-- <script src='lang/translator.js' type='text/javascript'></script> -->\n  <!-- <script src='lang/ru.js' type='text/javascript'></script> -->\n  <!-- <script src='lang/en.js' type='text/javascript'></script> -->\n\n  <script type=\"text/javascript\">\n    $.getJSON(\"/doc/api/swagger.json\", function(caliopen_api) {\n      $(function () {\n        var url = window.location.search.match(/url=([^&]+)/);\n        if (url && url.length > 1) {\n          url = decodeURIComponent(url[1]);\n        } else {\n          url = \"http://localhost:6543\";\n        }\n        hljs.configure({\n          highlightSizeThreshold: 5000\n        });\n\n        // Pre load translate...\n        if(window.SwaggerTranslator) {\n          window.SwaggerTranslator.translate();\n        }\n        window.swaggerUi = new SwaggerUi({\n          url: url,\n          spec: caliopen_api,\n          dom_id: \"swagger-ui-container\",\n          supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],\n          onComplete: function(swaggerApi, swaggerUi){\n            if(typeof initOAuth == \"function\") {\n              initOAuth({\n                clientId: \"your-client-id\",\n                clientSecret: \"your-client-secret-if-required\",\n                realm: \"your-realms\",\n                appName: \"your-app-name\",\n                scopeSeparator: \" \",\n                additionalQueryStringParams: {}\n              });\n            }\n\n            if(window.SwaggerTranslator) {\n              window.SwaggerTranslator.translate();\n            }\n          },\n          onFailure: function(data) {\n            log(\"Unable to Load SwaggerUI\");\n          },\n          docExpansion: \"none\",\n          jsonEditor: false,\n          defaultModelRendering: 'schema',\n          showRequestHeaders: false\n        });\n\n        window.swaggerUi.load();\n\n        function log() {\n          if ('console' in window) {\n            console.log.apply(console, arguments);\n          }\n        }\n      });\n    });\n\n  </script>\n</head>\n\n<body class=\"swagger-section\">\n<div id='header'>\n  <div class=\"swagger-ui-wrap\">\n    <a id=\"logo\" href=\"http://caliopen.org\"><img class=\"logo__img\" alt=\"swagger\" height=\"30\" width=\"30\" src=\"images/caliopen.png\" /><span class=\"logo__title\">Caliopen API</span></a>\n    <form id='api_selector'>\n      <div class='input'><input placeholder=\"http://api.caliopen.org/\" id=\"input_baseUrl\" name=\"baseUrl\" type=\"text\"/></div>\n      <div id='auth_container'></div>\n      <div class='input'><a id=\"explore\" class=\"header__btn\" href=\"#\" data-sw-translate>Explore</a></div>\n    </form>\n  </div>\n</div>\n\n<div id=\"message-bar\" class=\"swagger-ui-wrap\" data-sw-translate>&nbsp;</div>\n<div id=\"swagger-ui-container\" class=\"swagger-ui-wrap\"></div>\n</body>\n</html>\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/ca.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Advertència: Obsolet\",\n    \"Implementation Notes\":\"Notes d'implementació\",\n    \"Response Class\":\"Classe de la Resposta\",\n    \"Status\":\"Estatus\",\n    \"Parameters\":\"Paràmetres\",\n    \"Parameter\":\"Paràmetre\",\n    \"Value\":\"Valor\",\n    \"Description\":\"Descripció\",\n    \"Parameter Type\":\"Tipus del Paràmetre\",\n    \"Data Type\":\"Tipus de la Dada\",\n    \"Response Messages\":\"Missatges de la Resposta\",\n    \"HTTP Status Code\":\"Codi d'Estatus HTTP\",\n    \"Reason\":\"Raó\",\n    \"Response Model\":\"Model de la Resposta\",\n    \"Request URL\":\"URL de la Sol·licitud\",\n    \"Response Body\":\"Cos de la Resposta\",\n    \"Response Code\":\"Codi de la Resposta\",\n    \"Response Headers\":\"Capçaleres de la Resposta\",\n    \"Hide Response\":\"Amagar Resposta\",\n    \"Try it out!\":\"Prova-ho!\",\n    \"Show/Hide\":\"Mostrar/Amagar\",\n    \"List Operations\":\"Llista Operacions\",\n    \"Expand Operations\":\"Expandir Operacions\",\n    \"Raw\":\"Cru\",\n    \"can't parse JSON.  Raw result\":\"no puc analitzar el JSON.  Resultat cru\",\n    \"Example Value\":\"Valor d'Exemple\",\n    \"Model Schema\":\"Esquema del Model\",\n    \"Model\":\"Model\",\n    \"apply\":\"aplicar\",\n    \"Username\":\"Nom d'usuari\",\n    \"Password\":\"Contrasenya\",\n    \"Terms of service\":\"Termes del servei\",\n    \"Created by\":\"Creat per\",\n    \"See more at\":\"Veure més en\",\n    \"Contact the developer\":\"Contactar amb el desenvolupador\",\n    \"api version\":\"versió de la api\",\n    \"Response Content Type\":\"Tipus de Contingut de la Resposta\",\n    \"fetching resource\":\"recollint recurs\",\n    \"fetching resource list\":\"recollins llista de recursos\",\n    \"Explore\":\"Explorant\",\n    \"Show Swagger Petstore Example Apis\":\"Mostrar API d'Exemple Swagger Petstore\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"No es pot llegir del servidor. Potser no teniu la configuració de control d'accés apropiada.\",\n    \"Please specify the protocol for\":\"Si us plau, especifiqueu el protocol per a\",\n    \"Can't read swagger JSON from\":\"No es pot llegir el JSON de swagger des de\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Finalitzada la càrrega del recurs informatiu. Renderitzant Swagger UI\",\n    \"Unable to read api\":\"No es pot llegir l'api\",\n    \"from path\":\"des de la ruta\",\n    \"server returned\":\"el servidor ha retornat\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/el.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Προειδοποίηση: Έχει αποσυρθεί\",\n    \"Implementation Notes\":\"Σημειώσεις Υλοποίησης\",\n    \"Response Class\":\"Απόκριση\",\n    \"Status\":\"Κατάσταση\",\n    \"Parameters\":\"Παράμετροι\",\n    \"Parameter\":\"Παράμετρος\",\n    \"Value\":\"Τιμή\",\n    \"Description\":\"Περιγραφή\",\n    \"Parameter Type\":\"Τύπος Παραμέτρου\",\n    \"Data Type\":\"Τύπος Δεδομένων\",\n    \"Response Messages\":\"Μηνύματα Απόκρισης\",\n    \"HTTP Status Code\":\"Κωδικός Κατάστασης HTTP\",\n    \"Reason\":\"Αιτιολογία\",\n    \"Response Model\":\"Μοντέλο Απόκρισης\",\n    \"Request URL\":\"URL Αιτήματος\",\n    \"Response Body\":\"Σώμα Απόκρισης\",\n    \"Response Code\":\"Κωδικός Απόκρισης\",\n    \"Response Headers\":\"Επικεφαλίδες Απόκρισης\",\n    \"Hide Response\":\"Απόκρυψη Απόκρισης\",\n    \"Headers\":\"Επικεφαλίδες\",\n    \"Try it out!\":\"Δοκιμάστε το!\",\n    \"Show/Hide\":\"Εμφάνιση/Απόκρυψη\",\n    \"List Operations\":\"Λίστα Λειτουργιών\",\n    \"Expand Operations\":\"Ανάπτυξη Λειτουργιών\",\n    \"Raw\":\"Ακατέργαστο\",\n    \"can't parse JSON.  Raw result\":\"αδυναμία ανάλυσης JSON.  Ακατέργαστο αποτέλεσμα\",\n    \"Example Value\":\"Παράδειγμα Τιμής\",\n    \"Model Schema\":\"Σχήμα Μοντέλου\",\n    \"Model\":\"Μοντέλο\",\n    \"Click to set as parameter value\":\"Πατήστε για να θέσετε τιμή παραμέτρου\",\n    \"apply\":\"εφαρμογή\",\n    \"Username\":\"Όνομα χρήση\",\n    \"Password\":\"Κωδικός πρόσβασης\",\n    \"Terms of service\":\"Όροι χρήσης\",\n    \"Created by\":\"Δημιουργήθηκε από\",\n    \"See more at\":\"Δείτε περισσότερα στο\",\n    \"Contact the developer\":\"Επικοινωνήστε με τον προγραμματιστή\",\n    \"api version\":\"έκδοση api\",\n    \"Response Content Type\":\"Τύπος Περιεχομένου Απόκρισης\",\n    \"Parameter content type:\":\"Τύπος περιεχομένου παραμέτρου:\",\n    \"fetching resource\":\"παραλαβή πόρου\",\n    \"fetching resource list\":\"παραλαβή λίστας πόρων\",\n    \"Explore\":\"Εξερεύνηση\",\n    \"Show Swagger Petstore Example Apis\":\"Εμφάνιση Api Δειγμάτων Petstore του Swagger\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"Αδυναμία ανάγνωσης από τον εξυπηρετητή.  Μπορεί να μην έχει κατάλληλες ρυθμίσεις για access-control-origin.\",\n    \"Please specify the protocol for\":\"Παρακαλώ προσδιορίστε το πρωτόκολλο για\",\n    \"Can't read swagger JSON from\":\"Αδυναμία ανάγνωσης swagger JSON από\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Ολοκλήρωση Φόρτωσης Πληροφορικών Πόρου. Παρουσίαση Swagger UI\",\n    \"Unable to read api\":\"Αδυναμία ανάγνωσης api\",\n    \"from path\":\"από το μονοπάτι\",\n    \"server returned\":\"ο εξυπηρετηρής επέστρεψε\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/en.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Warning: Deprecated\",\n    \"Implementation Notes\":\"Implementation Notes\",\n    \"Response Class\":\"Response Class\",\n    \"Status\":\"Status\",\n    \"Parameters\":\"Parameters\",\n    \"Parameter\":\"Parameter\",\n    \"Value\":\"Value\",\n    \"Description\":\"Description\",\n    \"Parameter Type\":\"Parameter Type\",\n    \"Data Type\":\"Data Type\",\n    \"Response Messages\":\"Response Messages\",\n    \"HTTP Status Code\":\"HTTP Status Code\",\n    \"Reason\":\"Reason\",\n    \"Response Model\":\"Response Model\",\n    \"Request URL\":\"Request URL\",\n    \"Response Body\":\"Response Body\",\n    \"Response Code\":\"Response Code\",\n    \"Response Headers\":\"Response Headers\",\n    \"Hide Response\":\"Hide Response\",\n    \"Headers\":\"Headers\",\n    \"Try it out!\":\"Try it out!\",\n    \"Show/Hide\":\"Show/Hide\",\n    \"List Operations\":\"List Operations\",\n    \"Expand Operations\":\"Expand Operations\",\n    \"Raw\":\"Raw\",\n    \"can't parse JSON.  Raw result\":\"can't parse JSON.  Raw result\",\n    \"Example Value\":\"Example Value\",\n    \"Model Schema\":\"Model Schema\",\n    \"Model\":\"Model\",\n    \"Click to set as parameter value\":\"Click to set as parameter value\",\n    \"apply\":\"apply\",\n    \"Username\":\"Username\",\n    \"Password\":\"Password\",\n    \"Terms of service\":\"Terms of service\",\n    \"Created by\":\"Created by\",\n    \"See more at\":\"See more at\",\n    \"Contact the developer\":\"Contact the developer\",\n    \"api version\":\"api version\",\n    \"Response Content Type\":\"Response Content Type\",\n    \"Parameter content type:\":\"Parameter content type:\",\n    \"fetching resource\":\"fetching resource\",\n    \"fetching resource list\":\"fetching resource list\",\n    \"Explore\":\"Explore\",\n    \"Show Swagger Petstore Example Apis\":\"Show Swagger Petstore Example Apis\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"Can't read from server.  It may not have the appropriate access-control-origin settings.\",\n    \"Please specify the protocol for\":\"Please specify the protocol for\",\n    \"Can't read swagger JSON from\":\"Can't read swagger JSON from\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Finished Loading Resource Information. Rendering Swagger UI\",\n    \"Unable to read api\":\"Unable to read api\",\n    \"from path\":\"from path\",\n    \"server returned\":\"server returned\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/es.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Advertencia: Obsoleto\",\n    \"Implementation Notes\":\"Notas de implementación\",\n    \"Response Class\":\"Clase de la Respuesta\",\n    \"Status\":\"Status\",\n    \"Parameters\":\"Parámetros\",\n    \"Parameter\":\"Parámetro\",\n    \"Value\":\"Valor\",\n    \"Description\":\"Descripción\",\n    \"Parameter Type\":\"Tipo del Parámetro\",\n    \"Data Type\":\"Tipo del Dato\",\n    \"Response Messages\":\"Mensajes de la Respuesta\",\n    \"HTTP Status Code\":\"Código de Status HTTP\",\n    \"Reason\":\"Razón\",\n    \"Response Model\":\"Modelo de la Respuesta\",\n    \"Request URL\":\"URL de la Solicitud\",\n    \"Response Body\":\"Cuerpo de la Respuesta\",\n    \"Response Code\":\"Código de la Respuesta\",\n    \"Response Headers\":\"Encabezados de la Respuesta\",\n    \"Hide Response\":\"Ocultar Respuesta\",\n    \"Try it out!\":\"Pruébalo!\",\n    \"Show/Hide\":\"Mostrar/Ocultar\",\n    \"List Operations\":\"Listar Operaciones\",\n    \"Expand Operations\":\"Expandir Operaciones\",\n    \"Raw\":\"Crudo\",\n    \"can't parse JSON.  Raw result\":\"no puede parsear el JSON.  Resultado crudo\",\n    \"Example Value\":\"Valor de Ejemplo\",\n    \"Model Schema\":\"Esquema del Modelo\",\n    \"Model\":\"Modelo\",\n    \"apply\":\"aplicar\",\n    \"Username\":\"Nombre de usuario\",\n    \"Password\":\"Contraseña\",\n    \"Terms of service\":\"Términos de Servicio\",\n    \"Created by\":\"Creado por\",\n    \"See more at\":\"Ver más en\",\n    \"Contact the developer\":\"Contactar al desarrollador\",\n    \"api version\":\"versión de la api\",\n    \"Response Content Type\":\"Tipo de Contenido (Content Type) de la Respuesta\",\n    \"fetching resource\":\"buscando recurso\",\n    \"fetching resource list\":\"buscando lista del recurso\",\n    \"Explore\":\"Explorar\",\n    \"Show Swagger Petstore Example Apis\":\"Mostrar Api Ejemplo de Swagger Petstore\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"No se puede leer del servidor. Tal vez no tiene la configuración de control de acceso de origen (access-control-origin) apropiado.\",\n    \"Please specify the protocol for\":\"Por favor, especificar el protocola para\",\n    \"Can't read swagger JSON from\":\"No se puede leer el JSON de swagger desde\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Finalizada la carga del recurso de Información. Mostrando Swagger UI\",\n    \"Unable to read api\":\"No se puede leer la api\",\n    \"from path\":\"desde ruta\",\n    \"server returned\":\"el servidor retornó\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/fr.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Avertissement : Obsolète\",\n    \"Implementation Notes\":\"Notes d'implémentation\",\n    \"Response Class\":\"Classe de la réponse\",\n    \"Status\":\"Statut\",\n    \"Parameters\":\"Paramètres\",\n    \"Parameter\":\"Paramètre\",\n    \"Value\":\"Valeur\",\n    \"Description\":\"Description\",\n    \"Parameter Type\":\"Type du paramètre\",\n    \"Data Type\":\"Type de données\",\n    \"Response Messages\":\"Messages de la réponse\",\n    \"HTTP Status Code\":\"Code de statut HTTP\",\n    \"Reason\":\"Raison\",\n    \"Response Model\":\"Modèle de réponse\",\n    \"Request URL\":\"URL appelée\",\n    \"Response Body\":\"Corps de la réponse\",\n    \"Response Code\":\"Code de la réponse\",\n    \"Response Headers\":\"En-têtes de la réponse\",\n    \"Hide Response\":\"Cacher la réponse\",\n    \"Headers\":\"En-têtes\",\n    \"Try it out!\":\"Testez !\",\n    \"Show/Hide\":\"Afficher/Masquer\",\n    \"List Operations\":\"Liste des opérations\",\n    \"Expand Operations\":\"Développer les opérations\",\n    \"Raw\":\"Brut\",\n    \"can't parse JSON.  Raw result\":\"impossible de décoder le JSON.  Résultat brut\",\n    \"Example Value\":\"Exemple la valeur\",\n    \"Model Schema\":\"Définition du modèle\",\n    \"Model\":\"Modèle\",\n    \"apply\":\"appliquer\",\n    \"Username\":\"Nom d'utilisateur\",\n    \"Password\":\"Mot de passe\",\n    \"Terms of service\":\"Conditions de service\",\n    \"Created by\":\"Créé par\",\n    \"See more at\":\"Voir plus sur\",\n    \"Contact the developer\":\"Contacter le développeur\",\n    \"api version\":\"version de l'api\",\n    \"Response Content Type\":\"Content Type de la réponse\",\n    \"fetching resource\":\"récupération de la ressource\",\n    \"fetching resource list\":\"récupération de la liste de ressources\",\n    \"Explore\":\"Explorer\",\n    \"Show Swagger Petstore Example Apis\":\"Montrer les Apis de l'exemple Petstore de Swagger\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"Impossible de lire à partir du serveur. Il se peut que les réglages access-control-origin ne soient pas appropriés.\",\n    \"Please specify the protocol for\":\"Veuillez spécifier un protocole pour\",\n    \"Can't read swagger JSON from\":\"Impossible de lire le JSON swagger à partir de\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Chargement des informations terminé. Affichage de Swagger UI\",\n    \"Unable to read api\":\"Impossible de lire l'api\",\n    \"from path\":\"à partir du chemin\",\n    \"server returned\":\"réponse du serveur\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/geo.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"ყურადღება: აღარ გამოიყენება\",\n    \"Implementation Notes\":\"იმპლემენტაციის აღწერა\",\n    \"Response Class\":\"რესპონს კლასი\",\n    \"Status\":\"სტატუსი\",\n    \"Parameters\":\"პარამეტრები\",\n    \"Parameter\":\"პარამეტრი\",\n    \"Value\":\"მნიშვნელობა\",\n    \"Description\":\"აღწერა\",\n    \"Parameter Type\":\"პარამეტრის ტიპი\",\n    \"Data Type\":\"მონაცემის ტიპი\",\n    \"Response Messages\":\"პასუხი\",\n    \"HTTP Status Code\":\"HTTP სტატუსი\",\n    \"Reason\":\"მიზეზი\",\n    \"Response Model\":\"რესპონს მოდელი\",\n    \"Request URL\":\"მოთხოვნის URL\",\n    \"Response Body\":\"პასუხის სხეული\",\n    \"Response Code\":\"პასუხის კოდი\",\n    \"Response Headers\":\"პასუხის ჰედერები\",\n    \"Hide Response\":\"დამალე პასუხი\",\n    \"Headers\":\"ჰედერები\",\n    \"Try it out!\":\"ცადე !\",\n    \"Show/Hide\":\"გამოჩენა/დამალვა\",\n    \"List Operations\":\"ოპერაციების სია\",\n    \"Expand Operations\":\"ოპერაციები ვრცლად\",\n    \"Raw\":\"ნედლი\",\n    \"can't parse JSON.  Raw result\":\"JSON-ის დამუშავება ვერ მოხერხდა.  ნედლი პასუხი\",\n    \"Example Value\":\"მაგალითი\",\n    \"Model Schema\":\"მოდელის სტრუქტურა\",\n    \"Model\":\"მოდელი\",\n    \"Click to set as parameter value\":\"პარამეტრისთვის მნიშვნელობის მისანიჭებლად, დააკლიკე\",\n    \"apply\":\"გამოყენება\",\n    \"Username\":\"მოხმარებელი\",\n    \"Password\":\"პაროლი\",\n    \"Terms of service\":\"მომსახურების პირობები\",\n    \"Created by\":\"შექმნა\",\n    \"See more at\":\"ნახე ვრცლად\",\n    \"Contact the developer\":\"დაუკავშირდი დეველოპერს\",\n    \"api version\":\"api ვერსია\",\n    \"Response Content Type\":\"პასუხის კონტენტის ტიპი\",\n    \"Parameter content type:\":\"პარამეტრის კონტენტის ტიპი:\",\n    \"fetching resource\":\"რესურსების მიღება\",\n    \"fetching resource list\":\"რესურსების სიის მიღება\",\n    \"Explore\":\"ნახვა\",\n    \"Show Swagger Petstore Example Apis\":\"ნახე Swagger Petstore სამაგალითო Api\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"სერვერთან დაკავშირება ვერ ხერხდება.  შეამოწმეთ access-control-origin.\",\n    \"Please specify the protocol for\":\"მიუთითეთ პროტოკოლი\",\n    \"Can't read swagger JSON from\":\"swagger JSON წაკითხვა ვერ მოხერხდა\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"რესურსების ჩატვირთვა სრულდება. Swagger UI რენდერდება\",\n    \"Unable to read api\":\"api წაკითხვა ვერ მოხერხდა\",\n    \"from path\":\"მისამართიდან\",\n    \"server returned\":\"სერვერმა დააბრუნა\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/it.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Attenzione: Deprecato\",\n    \"Implementation Notes\":\"Note di implementazione\",\n    \"Response Class\":\"Classe della risposta\",\n    \"Status\":\"Stato\",\n    \"Parameters\":\"Parametri\",\n    \"Parameter\":\"Parametro\",\n    \"Value\":\"Valore\",\n    \"Description\":\"Descrizione\",\n    \"Parameter Type\":\"Tipo di parametro\",\n    \"Data Type\":\"Tipo di dato\",\n    \"Response Messages\":\"Messaggi della risposta\",\n    \"HTTP Status Code\":\"Codice stato HTTP\",\n    \"Reason\":\"Motivo\",\n    \"Response Model\":\"Modello di risposta\",\n    \"Request URL\":\"URL della richiesta\",\n    \"Response Body\":\"Corpo della risposta\",\n    \"Response Code\":\"Oggetto della risposta\",\n    \"Response Headers\":\"Intestazioni della risposta\",\n    \"Hide Response\":\"Nascondi risposta\",\n    \"Try it out!\":\"Provalo!\",\n    \"Show/Hide\":\"Mostra/Nascondi\",\n    \"List Operations\":\"Mostra operazioni\",\n    \"Expand Operations\":\"Espandi operazioni\",\n    \"Raw\":\"Grezzo (raw)\",\n    \"can't parse JSON.  Raw result\":\"non è possibile parsare il JSON. Risultato grezzo (raw).\",\n    \"Model Schema\":\"Schema del modello\",\n    \"Model\":\"Modello\",\n    \"apply\":\"applica\",\n    \"Username\":\"Nome utente\",\n    \"Password\":\"Password\",\n    \"Terms of service\":\"Condizioni del servizio\",\n    \"Created by\":\"Creato da\",\n    \"See more at\":\"Informazioni aggiuntive:\",\n    \"Contact the developer\":\"Contatta lo sviluppatore\",\n    \"api version\":\"versione api\",\n    \"Response Content Type\":\"Tipo di contenuto (content type) della risposta\",\n    \"fetching resource\":\"recuperando la risorsa\",\n    \"fetching resource list\":\"recuperando lista risorse\",\n    \"Explore\":\"Esplora\",\n    \"Show Swagger Petstore Example Apis\":\"Mostra le api di esempio di Swagger Petstore\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"Non è possibile leggere dal server. Potrebbe non avere le impostazioni di controllo accesso origine (access-control-origin) appropriate.\",\n    \"Please specify the protocol for\":\"Si prega di specificare il protocollo per\",\n    \"Can't read swagger JSON from\":\"Impossibile leggere JSON swagger da:\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Lettura informazioni risorse termianta. Swagger UI viene mostrata\",\n    \"Unable to read api\":\"Impossibile leggere la api\",\n    \"from path\":\"da cartella\",\n    \"server returned\":\"il server ha restituito\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/ja.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"警告: 廃止予定\",\n    \"Implementation Notes\":\"実装メモ\",\n    \"Response Class\":\"レスポンスクラス\",\n    \"Status\":\"ステータス\",\n    \"Parameters\":\"パラメータ群\",\n    \"Parameter\":\"パラメータ\",\n    \"Value\":\"値\",\n    \"Description\":\"説明\",\n    \"Parameter Type\":\"パラメータタイプ\",\n    \"Data Type\":\"データタイプ\",\n    \"Response Messages\":\"レスポンスメッセージ\",\n    \"HTTP Status Code\":\"HTTPステータスコード\",\n    \"Reason\":\"理由\",\n    \"Response Model\":\"レスポンスモデル\",\n    \"Request URL\":\"リクエストURL\",\n    \"Response Body\":\"レスポンスボディ\",\n    \"Response Code\":\"レスポンスコード\",\n    \"Response Headers\":\"レスポンスヘッダ\",\n    \"Hide Response\":\"レスポンスを隠す\",\n    \"Headers\":\"ヘッダ\",\n    \"Try it out!\":\"実際に実行!\",\n    \"Show/Hide\":\"表示/非表示\",\n    \"List Operations\":\"操作一覧\",\n    \"Expand Operations\":\"操作の展開\",\n    \"Raw\":\"未加工\",\n    \"can't parse JSON.  Raw result\":\"JSONへ解釈できません.  未加工の結果\",\n    \"Example Value\":\"値の例\",\n    \"Model Schema\":\"モデルスキーマ\",\n    \"Model\":\"モデル\",\n    \"Click to set as parameter value\":\"パラメータ値と設定するにはクリック\",\n    \"apply\":\"実行\",\n    \"Username\":\"ユーザ名\",\n    \"Password\":\"パスワード\",\n    \"Terms of service\":\"サービス利用規約\",\n    \"Created by\":\"Created by\",\n    \"See more at\":\"詳細を見る\",\n    \"Contact the developer\":\"開発者に連絡\",\n    \"api version\":\"APIバージョン\",\n    \"Response Content Type\":\"レスポンス コンテンツタイプ\",\n    \"Parameter content type:\":\"パラメータコンテンツタイプ:\",\n    \"fetching resource\":\"リソースの取得\",\n    \"fetching resource list\":\"リソース一覧の取得\",\n    \"Explore\":\"調査\",\n    \"Show Swagger Petstore Example Apis\":\"SwaggerペットストアAPIの表示\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"サーバから読み込めません.  適切なaccess-control-origin設定を持っていない可能性があります.\",\n    \"Please specify the protocol for\":\"プロトコルを指定してください\",\n    \"Can't read swagger JSON from\":\"次からswagger JSONを読み込めません\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"リソース情報の読み込みが完了しました. Swagger UIを描画しています\",\n    \"Unable to read api\":\"APIを読み込めません\",\n    \"from path\":\"次のパスから\",\n    \"server returned\":\"サーバからの返答\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/ko-kr.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"경고：폐기예정됨\",\n    \"Implementation Notes\":\"구현 노트\",\n    \"Response Class\":\"응답 클래스\",\n    \"Status\":\"상태\",\n    \"Parameters\":\"매개변수들\",\n    \"Parameter\":\"매개변수\",\n    \"Value\":\"값\",\n    \"Description\":\"설명\",\n    \"Parameter Type\":\"매개변수 타입\",\n    \"Data Type\":\"데이터 타입\",\n    \"Response Messages\":\"응답 메세지\",\n    \"HTTP Status Code\":\"HTTP 상태 코드\",\n    \"Reason\":\"원인\",\n    \"Response Model\":\"응답 모델\",\n    \"Request URL\":\"요청 URL\",\n    \"Response Body\":\"응답 본문\",\n    \"Response Code\":\"응답 코드\",\n    \"Response Headers\":\"응답 헤더\",\n    \"Hide Response\":\"응답 숨기기\",\n    \"Headers\":\"헤더\",\n    \"Try it out!\":\"써보기！\",\n    \"Show/Hide\":\"보이기/숨기기\",\n    \"List Operations\":\"목록 작업\",\n    \"Expand Operations\":\"전개 작업\",\n    \"Raw\":\"원본\",\n    \"can't parse JSON.  Raw result\":\"JSON을 파싱할수 없음. 원본결과:\",\n    \"Model Schema\":\"모델 스키마\",\n    \"Model\":\"모델\",\n    \"apply\":\"적용\",\n    \"Username\":\"사용자 이름\",\n    \"Password\":\"암호\",\n    \"Terms of service\":\"이용약관\",\n    \"Created by\":\"작성자\",\n    \"See more at\":\"추가정보：\",\n    \"Contact the developer\":\"개발자에게 문의\",\n    \"api version\":\"api버전\",\n    \"Response Content Type\":\"응답Content Type\",\n    \"fetching resource\":\"리소스 가져오기\",\n    \"fetching resource list\":\"리소스 목록 가져오기\",\n    \"Explore\":\"탐색\",\n    \"Show Swagger Petstore Example Apis\":\"Swagger Petstore 예제 보기\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"서버로부터 읽어들일수 없습니다. access-control-origin 설정이 올바르지 않을수 있습니다.\",\n    \"Please specify the protocol for\":\"다음을 위한 프로토콜을 정하세요\",\n    \"Can't read swagger JSON from\":\"swagger JSON 을 다음으로 부터 읽을수 없습니다\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"리소스 정보 불러오기 완료. Swagger UI 랜더링\",\n    \"Unable to read api\":\"api를 읽을 수 없습니다.\",\n    \"from path\":\"다음 경로로 부터\",\n    \"server returned\":\"서버 응답함.\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/pl.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Uwaga: Wycofane\",\n    \"Implementation Notes\":\"Uwagi Implementacji\",\n    \"Response Class\":\"Klasa Odpowiedzi\",\n    \"Status\":\"Status\",\n    \"Parameters\":\"Parametry\",\n    \"Parameter\":\"Parametr\",\n    \"Value\":\"Wartość\",\n    \"Description\":\"Opis\",\n    \"Parameter Type\":\"Typ Parametru\",\n    \"Data Type\":\"Typ Danych\",\n    \"Response Messages\":\"Wiadomości Odpowiedzi\",\n    \"HTTP Status Code\":\"Kod Statusu HTTP\",\n    \"Reason\":\"Przyczyna\",\n    \"Response Model\":\"Model Odpowiedzi\",\n    \"Request URL\":\"URL Wywołania\",\n    \"Response Body\":\"Treść Odpowiedzi\",\n    \"Response Code\":\"Kod Odpowiedzi\",\n    \"Response Headers\":\"Nagłówki Odpowiedzi\",\n    \"Hide Response\":\"Ukryj Odpowiedź\",\n    \"Headers\":\"Nagłówki\",\n    \"Try it out!\":\"Wypróbuj!\",\n    \"Show/Hide\":\"Pokaż/Ukryj\",\n    \"List Operations\":\"Lista Operacji\",\n    \"Expand Operations\":\"Rozwiń Operacje\",\n    \"Raw\":\"Nieprzetworzone\",\n    \"can't parse JSON.  Raw result\":\"nie można przetworzyć pliku JSON.  Nieprzetworzone dane\",\n    \"Model Schema\":\"Schemat Modelu\",\n    \"Model\":\"Model\",\n    \"apply\":\"użyj\",\n    \"Username\":\"Nazwa użytkownika\",\n    \"Password\":\"Hasło\",\n    \"Terms of service\":\"Warunki używania\",\n    \"Created by\":\"Utworzone przez\",\n    \"See more at\":\"Zobacz więcej na\",\n    \"Contact the developer\":\"Kontakt z deweloperem\",\n    \"api version\":\"wersja api\",\n    \"Response Content Type\":\"Typ Zasobu Odpowiedzi\",\n    \"fetching resource\":\"ładowanie zasobu\",\n    \"fetching resource list\":\"ładowanie listy zasobów\",\n    \"Explore\":\"Eksploruj\",\n    \"Show Swagger Petstore Example Apis\":\"Pokaż Przykładowe Api Swagger Petstore\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"Brak połączenia z serwerem. Może on nie mieć odpowiednich ustawień access-control-origin.\",\n    \"Please specify the protocol for\":\"Proszę podać protokół dla\",\n    \"Can't read swagger JSON from\":\"Nie można odczytać swagger JSON z\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Ukończono Ładowanie Informacji o Zasobie. Renderowanie Swagger UI\",\n    \"Unable to read api\":\"Nie można odczytać api\",\n    \"from path\":\"ze ścieżki\",\n    \"server returned\":\"serwer zwrócił\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/pt.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Aviso: Depreciado\",\n    \"Implementation Notes\":\"Notas de Implementação\",\n    \"Response Class\":\"Classe de resposta\",\n    \"Status\":\"Status\",\n    \"Parameters\":\"Parâmetros\",\n    \"Parameter\":\"Parâmetro\",\n    \"Value\":\"Valor\",\n    \"Description\":\"Descrição\",\n    \"Parameter Type\":\"Tipo de parâmetro\",\n    \"Data Type\":\"Tipo de dados\",\n    \"Response Messages\":\"Mensagens de resposta\",\n    \"HTTP Status Code\":\"Código de status HTTP\",\n    \"Reason\":\"Razão\",\n    \"Response Model\":\"Modelo resposta\",\n    \"Request URL\":\"URL requisição\",\n    \"Response Body\":\"Corpo da resposta\",\n    \"Response Code\":\"Código da resposta\",\n    \"Response Headers\":\"Cabeçalho da resposta\",\n    \"Headers\":\"Cabeçalhos\",\n    \"Hide Response\":\"Esconder resposta\",\n    \"Try it out!\":\"Tente agora!\",\n    \"Show/Hide\":\"Mostrar/Esconder\",\n    \"List Operations\":\"Listar operações\",\n    \"Expand Operations\":\"Expandir operações\",\n    \"Raw\":\"Cru\",\n    \"can't parse JSON.  Raw result\":\"Falha ao analisar JSON.  Resulto cru\",\n    \"Model Schema\":\"Modelo esquema\",\n    \"Model\":\"Modelo\",\n    \"apply\":\"Aplicar\",\n    \"Username\":\"Usuário\",\n    \"Password\":\"Senha\",\n    \"Terms of service\":\"Termos do serviço\",\n    \"Created by\":\"Criado por\",\n    \"See more at\":\"Veja mais em\",\n    \"Contact the developer\":\"Contate o desenvolvedor\",\n    \"api version\":\"Versão api\",\n    \"Response Content Type\":\"Tipo de conteúdo da resposta\",\n    \"fetching resource\":\"busca recurso\",\n    \"fetching resource list\":\"buscando lista de recursos\",\n    \"Explore\":\"Explorar\",\n    \"Show Swagger Petstore Example Apis\":\"Show Swagger Petstore Example Apis\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"Não é possível ler do servidor. Pode não ter as apropriadas configurações access-control-origin\",\n    \"Please specify the protocol for\":\"Por favor especifique o protocolo\",\n    \"Can't read swagger JSON from\":\"Não é possível ler o JSON Swagger de\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Carregar informação de recurso finalizada. Renderizando Swagger UI\",\n    \"Unable to read api\":\"Não foi possível ler api\",\n    \"from path\":\"do caminho\",\n    \"server returned\":\"servidor retornou\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/ru.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Предупреждение: Устарело\",\n    \"Implementation Notes\":\"Заметки\",\n    \"Response Class\":\"Пример ответа\",\n    \"Status\":\"Статус\",\n    \"Parameters\":\"Параметры\",\n    \"Parameter\":\"Параметр\",\n    \"Value\":\"Значение\",\n    \"Description\":\"Описание\",\n    \"Parameter Type\":\"Тип параметра\",\n    \"Data Type\":\"Тип данных\",\n    \"HTTP Status Code\":\"HTTP код\",\n    \"Reason\":\"Причина\",\n    \"Response Model\":\"Структура ответа\",\n    \"Request URL\":\"URL запроса\",\n    \"Response Body\":\"Тело ответа\",\n    \"Response Code\":\"HTTP код ответа\",\n    \"Response Headers\":\"Заголовки ответа\",\n    \"Hide Response\":\"Спрятать ответ\",\n    \"Headers\":\"Заголовки\",\n    \"Response Messages\":\"Что может прийти в ответ\",\n    \"Try it out!\":\"Попробовать!\",\n    \"Show/Hide\":\"Показать/Скрыть\",\n    \"List Operations\":\"Операции кратко\",\n    \"Expand Operations\":\"Операции подробно\",\n    \"Raw\":\"В сыром виде\",\n    \"can't parse JSON.  Raw result\":\"Не удается распарсить ответ:\",\n    \"Example Value\":\"Пример\",\n    \"Model Schema\":\"Структура\",\n    \"Model\":\"Описание\",\n    \"Click to set as parameter value\":\"Нажмите, чтобы испльзовать в качестве значения параметра\",\n    \"apply\":\"применить\",\n    \"Username\":\"Имя пользователя\",\n    \"Password\":\"Пароль\",\n    \"Terms of service\":\"Условия использования\",\n    \"Created by\":\"Разработано\",\n    \"See more at\":\"Еще тут\",\n    \"Contact the developer\":\"Связаться с разработчиком\",\n    \"api version\":\"Версия API\",\n    \"Response Content Type\":\"Content Type ответа\",\n    \"Parameter content type:\":\"Content Type параметра:\",\n    \"fetching resource\":\"Получение ресурса\",\n    \"fetching resource list\":\"Получение ресурсов\",\n    \"Explore\":\"Показать\",\n    \"Show Swagger Petstore Example Apis\":\"Показать примеры АПИ\",\n    \"Can't read from server. It may not have the appropriate access-control-origin settings.\":\"Не удается получить ответ от сервера. Возможно, проблема с настройками доступа\",\n    \"Please specify the protocol for\":\"Пожалуйста, укажите протокол для\",\n    \"Can't read swagger JSON from\":\"Не получается прочитать swagger json из\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Загрузка информации о ресурсах завершена. Рендерим\",\n    \"Unable to read api\":\"Не удалось прочитать api\",\n    \"from path\":\"по адресу\",\n    \"server returned\":\"сервер сказал\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/tr.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"Uyarı: Deprecated\",\n    \"Implementation Notes\":\"Gerçekleştirim Notları\",\n    \"Response Class\":\"Dönen Sınıf\",\n    \"Status\":\"Statü\",\n    \"Parameters\":\"Parametreler\",\n    \"Parameter\":\"Parametre\",\n    \"Value\":\"Değer\",\n    \"Description\":\"Açıklama\",\n    \"Parameter Type\":\"Parametre Tipi\",\n    \"Data Type\":\"Veri Tipi\",\n    \"Response Messages\":\"Dönüş Mesajı\",\n    \"HTTP Status Code\":\"HTTP Statü Kodu\",\n    \"Reason\":\"Gerekçe\",\n    \"Response Model\":\"Dönüş Modeli\",\n    \"Request URL\":\"İstek URL\",\n    \"Response Body\":\"Dönüş İçeriği\",\n    \"Response Code\":\"Dönüş Kodu\",\n    \"Response Headers\":\"Dönüş Üst Bilgileri\",\n    \"Hide Response\":\"Dönüşü Gizle\",\n    \"Headers\":\"Üst Bilgiler\",\n    \"Try it out!\":\"Dene!\",\n    \"Show/Hide\":\"Göster/Gizle\",\n    \"List Operations\":\"Operasyonları Listele\",\n    \"Expand Operations\":\"Operasyonları Aç\",\n    \"Raw\":\"Ham\",\n    \"can't parse JSON.  Raw result\":\"JSON çözümlenemiyor.  Ham sonuç\",\n    \"Model Schema\":\"Model Şema\",\n    \"Model\":\"Model\",\n    \"apply\":\"uygula\",\n    \"Username\":\"Kullanıcı Adı\",\n    \"Password\":\"Parola\",\n    \"Terms of service\":\"Servis şartları\",\n    \"Created by\":\"Oluşturan\",\n    \"See more at\":\"Daha fazlası için\",\n    \"Contact the developer\":\"Geliştirici ile İletişime Geçin\",\n    \"api version\":\"api versiyon\",\n    \"Response Content Type\":\"Dönüş İçerik Tipi\",\n    \"fetching resource\":\"kaynak getiriliyor\",\n    \"fetching resource list\":\"kaynak listesi getiriliyor\",\n    \"Explore\":\"Keşfet\",\n    \"Show Swagger Petstore Example Apis\":\"Swagger Petstore Örnek Api'yi Gör\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"Sunucudan okuma yapılamıyor. Sunucu access-control-origin ayarlarınızı kontrol edin.\",\n    \"Please specify the protocol for\":\"Lütfen istenen adres için protokol belirtiniz\",\n    \"Can't read swagger JSON from\":\"Swagger JSON bu kaynaktan okunamıyor\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"Kaynak baglantısı tamamlandı. Swagger UI gösterime hazırlanıyor\",\n    \"Unable to read api\":\"api okunamadı\",\n    \"from path\":\"yoldan\",\n    \"server returned\":\"sunucuya dönüldü\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/translator.js",
    "content": "'use strict';\n\n/**\n * Translator for documentation pages.\n *\n * To enable translation you should include one of language-files in your index.html\n * after <script src='lang/translator.js' type='text/javascript'></script>.\n * For example - <script src='lang/ru.js' type='text/javascript'></script>\n *\n * If you wish to translate some new texts you should do two things:\n * 1. Add a new phrase pair (\"New Phrase\": \"New Translation\") into your language file (for example lang/ru.js). It will be great if you add it in other language files too.\n * 2. Mark that text it templates this way <anyHtmlTag data-sw-translate>New Phrase</anyHtmlTag> or <anyHtmlTag data-sw-translate value='New Phrase'/>.\n * The main thing here is attribute data-sw-translate. Only inner html, title-attribute and value-attribute are going to translate.\n *\n */\nwindow.SwaggerTranslator = {\n\n    _words:[],\n\n    translate: function(sel) {\n      var $this = this;\n      sel = sel || '[data-sw-translate]';\n\n      $(sel).each(function() {\n        $(this).html($this._tryTranslate($(this).html()));\n\n        $(this).val($this._tryTranslate($(this).val()));\n        $(this).attr('title', $this._tryTranslate($(this).attr('title')));\n      });\n    },\n\n    _tryTranslate: function(word) {\n      return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word;\n    },\n\n    learn: function(wordsMap) {\n      this._words = wordsMap;\n    }\n};\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lang/zh-cn.js",
    "content": "'use strict';\n\n/* jshint quotmark: double */\nwindow.SwaggerTranslator.learn({\n    \"Warning: Deprecated\":\"警告：已过时\",\n    \"Implementation Notes\":\"实现备注\",\n    \"Response Class\":\"响应类\",\n    \"Status\":\"状态\",\n    \"Parameters\":\"参数\",\n    \"Parameter\":\"参数\",\n    \"Value\":\"值\",\n    \"Description\":\"描述\",\n    \"Parameter Type\":\"参数类型\",\n    \"Data Type\":\"数据类型\",\n    \"Response Messages\":\"响应消息\",\n    \"HTTP Status Code\":\"HTTP状态码\",\n    \"Reason\":\"原因\",\n    \"Response Model\":\"响应模型\",\n    \"Request URL\":\"请求URL\",\n    \"Response Body\":\"响应体\",\n    \"Response Code\":\"响应码\",\n    \"Response Headers\":\"响应头\",\n    \"Hide Response\":\"隐藏响应\",\n    \"Headers\":\"头\",\n    \"Try it out!\":\"试一下！\",\n    \"Show/Hide\":\"显示/隐藏\",\n    \"List Operations\":\"显示操作\",\n    \"Expand Operations\":\"展开操作\",\n    \"Raw\":\"原始\",\n    \"can't parse JSON.  Raw result\":\"无法解析JSON. 原始结果\",\n    \"Example Value\":\"示例\",\n    \"Click to set as parameter value\":\"点击设置参数\",\n    \"Model Schema\":\"模型架构\",\n    \"Model\":\"模型\",\n    \"apply\":\"应用\",\n    \"Username\":\"用户名\",\n    \"Password\":\"密码\",\n    \"Terms of service\":\"服务条款\",\n    \"Created by\":\"创建者\",\n    \"See more at\":\"查看更多：\",\n    \"Contact the developer\":\"联系开发者\",\n    \"api version\":\"api版本\",\n    \"Response Content Type\":\"响应Content Type\",\n    \"Parameter content type:\":\"参数类型:\",\n    \"fetching resource\":\"正在获取资源\",\n    \"fetching resource list\":\"正在获取资源列表\",\n    \"Explore\":\"浏览\",\n    \"Show Swagger Petstore Example Apis\":\"显示 Swagger Petstore 示例 Apis\",\n    \"Can't read from server.  It may not have the appropriate access-control-origin settings.\":\"无法从服务器读取。可能没有正确设置access-control-origin。\",\n    \"Please specify the protocol for\":\"请指定协议：\",\n    \"Can't read swagger JSON from\":\"无法读取swagger JSON于\",\n    \"Finished Loading Resource Information. Rendering Swagger UI\":\"已加载资源信息。正在渲染Swagger UI\",\n    \"Unable to read api\":\"无法读取api\",\n    \"from path\":\"从路径\",\n    \"server returned\":\"服务器返回\"\n});\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lib/backbone-min.js",
    "content": "// Backbone.js 1.1.2\n\n(function(t,e){if(typeof define===\"function\"&&define.amd){define([\"underscore\",\"jquery\",\"exports\"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!==\"undefined\"){var i=require(\"underscore\");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION=\"1.1.2\";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,\"on\",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,\"once\",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,\"off\",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h<u;h++){t=o[h];if(a=this._events[t]){this._events[t]=s=[];if(e||r){for(l=0,f=a.length;l<f;l++){n=a[l];if(e&&e!==n.callback&&e!==n.callback._callback||r&&r!==n.context){s.push(n)}}}if(!s.length)delete this._events[t]}}return this},trigger:function(t){if(!this._events)return this;var e=o.call(arguments,1);if(!c(this,\"trigger\",t,e))return this;var i=this._events[t];var r=this._events.all;if(i)f(i,e);if(r)f(r,arguments);return this},stopListening:function(t,e,r){var s=this._listeningTo;if(!s)return this;var n=!e&&!r;if(!r&&typeof e===\"object\")r=this;if(t)(s={})[t._listenId]=t;for(var a in s){t=s[a];t.off(e,r,this);if(n||i.isEmpty(t._events))delete this._listeningTo[a]}return this}};var l=/\\s+/;var c=function(t,e,i,r){if(!i)return true;if(typeof i===\"object\"){for(var s in i){t[e].apply(t,[s,i[s]].concat(r))}return false}if(l.test(i)){var n=i.split(l);for(var a=0,o=n.length;a<o;a++){t[e].apply(t,[n[a]].concat(r))}return false}return true};var f=function(t,e){var i,r=-1,s=t.length,n=e[0],a=e[1],o=e[2];switch(e.length){case 0:while(++r<s)(i=t[r]).callback.call(i.ctx);return;case 1:while(++r<s)(i=t[r]).callback.call(i.ctx,n);return;case 2:while(++r<s)(i=t[r]).callback.call(i.ctx,n,a);return;case 3:while(++r<s)(i=t[r]).callback.call(i.ctx,n,a,o);return;default:while(++r<s)(i=t[r]).callback.apply(i.ctx,e);return}};var d={listenTo:\"on\",listenToOnce:\"once\"};i.each(d,function(t,e){u[e]=function(e,r,s){var n=this._listeningTo||(this._listeningTo={});var a=e._listenId||(e._listenId=i.uniqueId(\"l\"));n[a]=e;if(!s&&typeof r===\"object\")s=this;e[t](r,s,this);return this}});u.bind=u.on;u.unbind=u.off;i.extend(e,u);var p=e.Model=function(t,e){var r=t||{};e||(e={});this.cid=i.uniqueId(\"c\");this.attributes={};if(e.collection)this.collection=e.collection;if(e.parse)r=this.parse(r,e)||{};r=i.defaults({},r,i.result(this,\"defaults\"));this.set(r,e);this.changed={};this.initialize.apply(this,arguments)};i.extend(p.prototype,u,{changed:null,validationError:null,idAttribute:\"id\",initialize:function(){},toJSON:function(t){return i.clone(this.attributes)},sync:function(){return e.sync.apply(this,arguments)},get:function(t){return this.attributes[t]},escape:function(t){return i.escape(this.get(t))},has:function(t){return this.get(t)!=null},set:function(t,e,r){var s,n,a,o,h,u,l,c;if(t==null)return this;if(typeof t===\"object\"){n=t;r=e}else{(n={})[t]=e}r||(r={});if(!this._validate(n,r))return false;a=r.unset;h=r.silent;o=[];u=this._changing;this._changing=true;if(!u){this._previousAttributes=i.clone(this.attributes);this.changed={}}c=this.attributes,l=this._previousAttributes;if(this.idAttribute in n)this.id=n[this.idAttribute];for(s in n){e=n[s];if(!i.isEqual(c[s],e))o.push(s);if(!i.isEqual(l[s],e)){this.changed[s]=e}else{delete this.changed[s]}a?delete c[s]:c[s]=e}if(!h){if(o.length)this._pending=r;for(var f=0,d=o.length;f<d;f++){this.trigger(\"change:\"+o[f],this,c[o[f]],r)}}if(u)return this;if(!h){while(this._pending){r=this._pending;this._pending=false;this.trigger(\"change\",this,r)}}this._pending=false;this._changing=false;return this},unset:function(t,e){return this.set(t,void 0,i.extend({},e,{unset:true}))},clear:function(t){var e={};for(var r in this.attributes)e[r]=void 0;return this.set(e,i.extend({},t,{unset:true}))},hasChanged:function(t){if(t==null)return!i.isEmpty(this.changed);return i.has(this.changed,t)},changedAttributes:function(t){if(!t)return this.hasChanged()?i.clone(this.changed):false;var e,r=false;var s=this._changing?this._previousAttributes:this.attributes;for(var n in t){if(i.isEqual(s[n],e=t[n]))continue;(r||(r={}))[n]=e}return r},previous:function(t){if(t==null||!this._previousAttributes)return null;return this._previousAttributes[t]},previousAttributes:function(){return i.clone(this._previousAttributes)},fetch:function(t){t=t?i.clone(t):{};if(t.parse===void 0)t.parse=true;var e=this;var r=t.success;t.success=function(i){if(!e.set(e.parse(i,t),t))return false;if(r)r(e,i,t);e.trigger(\"sync\",e,i,t)};q(this,t);return this.sync(\"read\",this,t)},save:function(t,e,r){var s,n,a,o=this.attributes;if(t==null||typeof t===\"object\"){s=t;r=e}else{(s={})[t]=e}r=i.extend({validate:true},r);if(s&&!r.wait){if(!this.set(s,r))return false}else{if(!this._validate(s,r))return false}if(s&&r.wait){this.attributes=i.extend({},o,s)}if(r.parse===void 0)r.parse=true;var h=this;var u=r.success;r.success=function(t){h.attributes=o;var e=h.parse(t,r);if(r.wait)e=i.extend(s||{},e);if(i.isObject(e)&&!h.set(e,r)){return false}if(u)u(h,t,r);h.trigger(\"sync\",h,t,r)};q(this,r);n=this.isNew()?\"create\":r.patch?\"patch\":\"update\";if(n===\"patch\")r.attrs=s;a=this.sync(n,this,r);if(s&&r.wait)this.attributes=o;return a},destroy:function(t){t=t?i.clone(t):{};var e=this;var r=t.success;var s=function(){e.trigger(\"destroy\",e,e.collection,t)};t.success=function(i){if(t.wait||e.isNew())s();if(r)r(e,i,t);if(!e.isNew())e.trigger(\"sync\",e,i,t)};if(this.isNew()){t.success();return false}q(this,t);var n=this.sync(\"delete\",this,t);if(!t.wait)s();return n},url:function(){var t=i.result(this,\"urlRoot\")||i.result(this.collection,\"url\")||M();if(this.isNew())return t;return t.replace(/([^\\/])$/,\"$1/\")+encodeURIComponent(this.id)},parse:function(t,e){return t},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return!this.has(this.idAttribute)},isValid:function(t){return this._validate({},i.extend(t||{},{validate:true}))},_validate:function(t,e){if(!e.validate||!this.validate)return true;t=i.extend({},this.attributes,t);var r=this.validationError=this.validate(t,e)||null;if(!r)return true;this.trigger(\"invalid\",this,r,i.extend(e,{validationError:r}));return false}});var v=[\"keys\",\"values\",\"pairs\",\"invert\",\"pick\",\"omit\"];i.each(v,function(t){p.prototype[t]=function(){var e=o.call(arguments);e.unshift(this.attributes);return i[t].apply(i,e)}});var g=e.Collection=function(t,e){e||(e={});if(e.model)this.model=e.model;if(e.comparator!==void 0)this.comparator=e.comparator;this._reset();this.initialize.apply(this,arguments);if(t)this.reset(t,i.extend({silent:true},e))};var m={add:true,remove:true,merge:true};var y={add:true,remove:false};i.extend(g.prototype,u,{model:p,initialize:function(){},toJSON:function(t){return this.map(function(e){return e.toJSON(t)})},sync:function(){return e.sync.apply(this,arguments)},add:function(t,e){return this.set(t,i.extend({merge:false},e,y))},remove:function(t,e){var r=!i.isArray(t);t=r?[t]:i.clone(t);e||(e={});var s,n,a,o;for(s=0,n=t.length;s<n;s++){o=t[s]=this.get(t[s]);if(!o)continue;delete this._byId[o.id];delete this._byId[o.cid];a=this.indexOf(o);this.models.splice(a,1);this.length--;if(!e.silent){e.index=a;o.trigger(\"remove\",o,this,e)}this._removeReference(o,e)}return r?t[0]:t},set:function(t,e){e=i.defaults({},e,m);if(e.parse)t=this.parse(t,e);var r=!i.isArray(t);t=r?t?[t]:[]:i.clone(t);var s,n,a,o,h,u,l;var c=e.at;var f=this.model;var d=this.comparator&&c==null&&e.sort!==false;var v=i.isString(this.comparator)?this.comparator:null;var g=[],y=[],_={};var b=e.add,w=e.merge,x=e.remove;var E=!d&&b&&x?[]:false;for(s=0,n=t.length;s<n;s++){h=t[s]||{};if(h instanceof p){a=o=h}else{a=h[f.prototype.idAttribute||\"id\"]}if(u=this.get(a)){if(x)_[u.cid]=true;if(w){h=h===o?o.attributes:h;if(e.parse)h=u.parse(h,e);u.set(h,e);if(d&&!l&&u.hasChanged(v))l=true}t[s]=u}else if(b){o=t[s]=this._prepareModel(h,e);if(!o)continue;g.push(o);this._addReference(o,e)}o=u||o;if(E&&(o.isNew()||!_[o.id]))E.push(o);_[o.id]=true}if(x){for(s=0,n=this.length;s<n;++s){if(!_[(o=this.models[s]).cid])y.push(o)}if(y.length)this.remove(y,e)}if(g.length||E&&E.length){if(d)l=true;this.length+=g.length;if(c!=null){for(s=0,n=g.length;s<n;s++){this.models.splice(c+s,0,g[s])}}else{if(E)this.models.length=0;var k=E||g;for(s=0,n=k.length;s<n;s++){this.models.push(k[s])}}}if(l)this.sort({silent:true});if(!e.silent){for(s=0,n=g.length;s<n;s++){(o=g[s]).trigger(\"add\",o,this,e)}if(l||E&&E.length)this.trigger(\"sort\",this,e)}return r?t[0]:t},reset:function(t,e){e||(e={});for(var r=0,s=this.models.length;r<s;r++){this._removeReference(this.models[r],e)}e.previousModels=this.models;this._reset();t=this.add(t,i.extend({silent:true},e));if(!e.silent)this.trigger(\"reset\",this,e);return t},push:function(t,e){return this.add(t,i.extend({at:this.length},e))},pop:function(t){var e=this.at(this.length-1);this.remove(e,t);return e},unshift:function(t,e){return this.add(t,i.extend({at:0},e))},shift:function(t){var e=this.at(0);this.remove(e,t);return e},slice:function(){return o.apply(this.models,arguments)},get:function(t){if(t==null)return void 0;return this._byId[t]||this._byId[t.id]||this._byId[t.cid]},at:function(t){return this.models[t]},where:function(t,e){if(i.isEmpty(t))return e?void 0:[];return this[e?\"find\":\"filter\"](function(e){for(var i in t){if(t[i]!==e.get(i))return false}return true})},findWhere:function(t){return this.where(t,true)},sort:function(t){if(!this.comparator)throw new Error(\"Cannot sort a set without a comparator\");t||(t={});if(i.isString(this.comparator)||this.comparator.length===1){this.models=this.sortBy(this.comparator,this)}else{this.models.sort(i.bind(this.comparator,this))}if(!t.silent)this.trigger(\"sort\",this,t);return this},pluck:function(t){return i.invoke(this.models,\"get\",t)},fetch:function(t){t=t?i.clone(t):{};if(t.parse===void 0)t.parse=true;var e=t.success;var r=this;t.success=function(i){var s=t.reset?\"reset\":\"set\";r[s](i,t);if(e)e(r,i,t);r.trigger(\"sync\",r,i,t)};q(this,t);return this.sync(\"read\",this,t)},create:function(t,e){e=e?i.clone(e):{};if(!(t=this._prepareModel(t,e)))return false;if(!e.wait)this.add(t,e);var r=this;var s=e.success;e.success=function(t,i){if(e.wait)r.add(t,e);if(s)s(t,i,e)};t.save(null,e);return t},parse:function(t,e){return t},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0;this.models=[];this._byId={}},_prepareModel:function(t,e){if(t instanceof p)return t;e=e?i.clone(e):{};e.collection=this;var r=new this.model(t,e);if(!r.validationError)return r;this.trigger(\"invalid\",this,r.validationError,e);return false},_addReference:function(t,e){this._byId[t.cid]=t;if(t.id!=null)this._byId[t.id]=t;if(!t.collection)t.collection=this;t.on(\"all\",this._onModelEvent,this)},_removeReference:function(t,e){if(this===t.collection)delete t.collection;t.off(\"all\",this._onModelEvent,this)},_onModelEvent:function(t,e,i,r){if((t===\"add\"||t===\"remove\")&&i!==this)return;if(t===\"destroy\")this.remove(e,r);if(e&&t===\"change:\"+e.idAttribute){delete this._byId[e.previous(e.idAttribute)];if(e.id!=null)this._byId[e.id]=e}this.trigger.apply(this,arguments)}});var _=[\"forEach\",\"each\",\"map\",\"collect\",\"reduce\",\"foldl\",\"inject\",\"reduceRight\",\"foldr\",\"find\",\"detect\",\"filter\",\"select\",\"reject\",\"every\",\"all\",\"some\",\"any\",\"include\",\"contains\",\"invoke\",\"max\",\"min\",\"toArray\",\"size\",\"first\",\"head\",\"take\",\"initial\",\"rest\",\"tail\",\"drop\",\"last\",\"without\",\"difference\",\"indexOf\",\"shuffle\",\"lastIndexOf\",\"isEmpty\",\"chain\",\"sample\"];i.each(_,function(t){g.prototype[t]=function(){var e=o.call(arguments);e.unshift(this.models);return i[t].apply(i,e)}});var b=[\"groupBy\",\"countBy\",\"sortBy\",\"indexBy\"];i.each(b,function(t){g.prototype[t]=function(e,r){var s=i.isFunction(e)?e:function(t){return t.get(e)};return i[t](this.models,s,r)}});var w=e.View=function(t){this.cid=i.uniqueId(\"view\");t||(t={});i.extend(this,i.pick(t,E));this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()};var x=/^(\\S+)\\s*(.*)$/;var E=[\"model\",\"collection\",\"el\",\"id\",\"attributes\",\"className\",\"tagName\",\"events\"];i.extend(w.prototype,u,{tagName:\"div\",$:function(t){return this.$el.find(t)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(t,i){if(this.$el)this.undelegateEvents();this.$el=t instanceof e.$?t:e.$(t);this.el=this.$el[0];if(i!==false)this.delegateEvents();return this},delegateEvents:function(t){if(!(t||(t=i.result(this,\"events\"))))return this;this.undelegateEvents();for(var e in t){var r=t[e];if(!i.isFunction(r))r=this[t[e]];if(!r)continue;var s=e.match(x);var n=s[1],a=s[2];r=i.bind(r,this);n+=\".delegateEvents\"+this.cid;if(a===\"\"){this.$el.on(n,r)}else{this.$el.on(n,a,r)}}return this},undelegateEvents:function(){this.$el.off(\".delegateEvents\"+this.cid);return this},_ensureElement:function(){if(!this.el){var t=i.extend({},i.result(this,\"attributes\"));if(this.id)t.id=i.result(this,\"id\");if(this.className)t[\"class\"]=i.result(this,\"className\");var r=e.$(\"<\"+i.result(this,\"tagName\")+\">\").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,\"el\"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:\"json\"};if(!s.url){a.url=i.result(r,\"url\")||M()}if(s.data==null&&r&&(t===\"create\"||t===\"update\"||t===\"patch\")){a.contentType=\"application/json\";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType=\"application/x-www-form-urlencoded\";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n===\"PUT\"||n===\"DELETE\"||n===\"PATCH\")){a.type=\"POST\";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader(\"X-HTTP-Method-Override\",n);if(o)return o.apply(this,arguments)}}if(a.type!==\"GET\"&&!s.emulateJSON){a.processData=false}if(a.type===\"PATCH\"&&k){a.xhr=function(){return new ActiveXObject(\"Microsoft.XMLHTTP\")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger(\"request\",r,h,s);return h};var k=typeof window!==\"undefined\"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:\"POST\",update:\"PUT\",patch:\"PATCH\",\"delete\":\"DELETE\",read:\"GET\"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\\((.*?)\\)/g;var H=/(\\(\\?)?:\\w+/g;var A=/\\*\\w+/g;var I=/[\\-{}\\[\\]+?.,\\\\\\^$|#\\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=\"\"}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,[\"route:\"+r].concat(a));n.trigger(\"route\",r,a);e.history.trigger(\"route\",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,\"routes\");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,\"\\\\$&\").replace(S,\"(?:$1)?\").replace(H,function(t,e){return e?t:\"([^/?]+)\"}).replace(A,\"([^?]*?)\");return new RegExp(\"^\"+t+\"(?:\\\\?([\\\\s\\\\S]*))?$\")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,\"checkUrl\");if(typeof window!==\"undefined\"){this.location=window.location;this.history=window.history}};var R=/^[#\\/]|\\s+$/g;var O=/^\\/+|\\/+$/g;var P=/msie [\\w.]+/;var C=/\\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\\/]$/,\"$&/\")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:\"\"},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,\"\");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,\"\")},start:function(t){if(N.started)throw new Error(\"Backbone.history has already been started\");N.started=true;this.options=i.extend({root:\"/\"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=(\"/\"+this.root+\"/\").replace(O,\"/\");if(n&&this._wantsHashChange){var a=e.$('<iframe src=\"javascript:0\" tabindex=\"-1\">');this.iframe=a.hide().appendTo(\"body\")[0].contentWindow;this.navigate(r)}if(this._hasPushState){e.$(window).on(\"popstate\",this.checkUrl)}else if(this._wantsHashChange&&\"onhashchange\"in window&&!n){e.$(window).on(\"hashchange\",this.checkUrl)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}this.fragment=r;var o=this.location;if(this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){this.fragment=this.getFragment(null,true);this.location.replace(this.root+\"#\"+this.fragment);return true}else if(this._hasPushState&&this.atRoot()&&o.hash){this.fragment=this.getHash().replace(R,\"\");this.history.replaceState({},document.title,this.root+this.fragment)}}if(!this.options.silent)return this.loadUrl()},stop:function(){e.$(window).off(\"popstate\",this.checkUrl).off(\"hashchange\",this.checkUrl);if(this._checkUrlInterval)clearInterval(this._checkUrlInterval);N.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getFragment(this.getHash(this.iframe))}if(e===this.fragment)return false;if(this.iframe)this.navigate(e);this.loadUrl()},loadUrl:function(t){t=this.fragment=this.getFragment(t);return i.any(this.handlers,function(e){if(e.route.test(t)){e.callback(t);return true}})},navigate:function(t,e){if(!N.started)return false;if(!e||e===true)e={trigger:!!e};var i=this.root+(t=this.getFragment(t||\"\"));t=t.replace(j,\"\");if(this.fragment===t)return;this.fragment=t;if(t===\"\"&&i!==\"/\")i=i.slice(0,-1);if(this._hasPushState){this.history[e.replace?\"replaceState\":\"pushState\"]({},document.title,i)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getFragment(this.getHash(this.iframe))){if(!e.replace)this.iframe.document.open().close();this._updateHash(this.iframe.location,t,e.replace)}}else{return this.location.assign(i)}if(e.trigger)return this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var r=t.href.replace(/(javascript:|#).*$/,\"\");t.replace(r+\"#\"+e)}else{t.hash=\"#\"+e}}});e.history=new N;var U=function(t,e){var r=this;var s;if(t&&i.has(t,\"constructor\")){s=t.constructor}else{s=function(){return r.apply(this,arguments)}}i.extend(s,r,e);var n=function(){this.constructor=s};n.prototype=r.prototype;s.prototype=new n;if(t)i.extend(s.prototype,t);s.__super__=r.prototype;return s};p.extend=g.extend=$.extend=w.extend=N.extend=U;var M=function(){throw new Error('A \"url\" property or function must be specified')};var q=function(t,e){var i=e.error;e.error=function(r){if(i)i(t,r,e);t.trigger(\"error\",t,r,e)}};return e});\n\n// From http://stackoverflow.com/a/19431552\n// Compatibility override - Backbone 1.1 got rid of the 'options' binding\n// automatically to views in the constructor - we need to keep that.\nBackbone.View = (function(View) {\n   return View.extend({\n        constructor: function(options) {\n            this.options = options || {};\n            View.apply(this, arguments);\n        }\n    });\n})(Backbone.View);"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lib/es5-shim.js",
    "content": "/*!\n * https://github.com/es-shims/es5-shim\n * @license es5-shim Copyright 2009-2015 by contributors, MIT License\n * see https://github.com/es-shims/es5-shim/blob/master/LICENSE\n */\n\n// vim: ts=4 sts=4 sw=4 expandtab\n\n// Add semicolon to prevent IIFE from being passed as argument to concatenated code.\n;\n\n// UMD (Universal Module Definition)\n// see https://github.com/umdjs/umd/blob/master/templates/returnExports.js\n(function (root, factory) {\n    'use strict';\n\n    /* global define, exports, module */\n    if (typeof define === 'function' && define.amd) {\n        // AMD. Register as an anonymous module.\n        define(factory);\n    } else if (typeof exports === 'object') {\n        // Node. Does not work with strict CommonJS, but\n        // only CommonJS-like enviroments that support module.exports,\n        // like Node.\n        module.exports = factory();\n    } else {\n        // Browser globals (root is window)\n        root.returnExports = factory();\n    }\n}(this, function () {\n    /**\n     * Brings an environment as close to ECMAScript 5 compliance\n     * as is possible with the facilities of erstwhile engines.\n     *\n     * Annotated ES5: http://es5.github.com/ (specific links below)\n     * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf\n     * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/\n     */\n\n    // Shortcut to an often accessed properties, in order to avoid multiple\n    // dereference that costs universally. This also holds a reference to known-good\n    // functions.\n    var $Array = Array;\n    var ArrayPrototype = $Array.prototype;\n    var $Object = Object;\n    var ObjectPrototype = $Object.prototype;\n    var $Function = Function;\n    var FunctionPrototype = $Function.prototype;\n    var $String = String;\n    var StringPrototype = $String.prototype;\n    var $Number = Number;\n    var NumberPrototype = $Number.prototype;\n    var array_slice = ArrayPrototype.slice;\n    var array_splice = ArrayPrototype.splice;\n    var array_push = ArrayPrototype.push;\n    var array_unshift = ArrayPrototype.unshift;\n    var array_concat = ArrayPrototype.concat;\n    var array_join = ArrayPrototype.join;\n    var call = FunctionPrototype.call;\n    var apply = FunctionPrototype.apply;\n    var max = Math.max;\n    var min = Math.min;\n\n    // Having a toString local variable name breaks in Opera so use to_string.\n    var to_string = ObjectPrototype.toString;\n\n    /* global Symbol */\n    /* eslint-disable one-var-declaration-per-line, no-redeclare, max-statements-per-line */\n    var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';\n    var isCallable; /* inlined from https://npmjs.com/is-callable */ var fnToStr = Function.prototype.toString, constructorRegex = /^\\s*class /, isES6ClassFn = function isES6ClassFn(value) { try { var fnStr = fnToStr.call(value); var singleStripped = fnStr.replace(/\\/\\/.*\\n/g, ''); var multiStripped = singleStripped.replace(/\\/\\*[.\\s\\S]*\\*\\//g, ''); var spaceStripped = multiStripped.replace(/\\n/mg, ' ').replace(/ {2}/g, ' '); return constructorRegex.test(spaceStripped); } catch (e) { return false; /* not a function */ } }, tryFunctionObject = function tryFunctionObject(value) { try { if (isES6ClassFn(value)) { return false; } fnToStr.call(value); return true; } catch (e) { return false; } }, fnClass = '[object Function]', genClass = '[object GeneratorFunction]', isCallable = function isCallable(value) { if (!value) { return false; } if (typeof value !== 'function' && typeof value !== 'object') { return false; } if (hasToStringTag) { return tryFunctionObject(value); } if (isES6ClassFn(value)) { return false; } var strClass = to_string.call(value); return strClass === fnClass || strClass === genClass; };\n\n    var isRegex; /* inlined from https://npmjs.com/is-regex */ var regexExec = RegExp.prototype.exec, tryRegexExec = function tryRegexExec(value) { try { regexExec.call(value); return true; } catch (e) { return false; } }, regexClass = '[object RegExp]'; isRegex = function isRegex(value) { if (typeof value !== 'object') { return false; } return hasToStringTag ? tryRegexExec(value) : to_string.call(value) === regexClass; };\n    var isString; /* inlined from https://npmjs.com/is-string */ var strValue = String.prototype.valueOf, tryStringObject = function tryStringObject(value) { try { strValue.call(value); return true; } catch (e) { return false; } }, stringClass = '[object String]'; isString = function isString(value) { if (typeof value === 'string') { return true; } if (typeof value !== 'object') { return false; } return hasToStringTag ? tryStringObject(value) : to_string.call(value) === stringClass; };\n    /* eslint-enable one-var-declaration-per-line, no-redeclare, max-statements-per-line */\n\n    /* inlined from http://npmjs.com/define-properties */\n    var supportsDescriptors = $Object.defineProperty && (function () {\n        try {\n            var obj = {};\n            $Object.defineProperty(obj, 'x', { enumerable: false, value: obj });\n            for (var _ in obj) { // jscs:ignore disallowUnusedVariables\n                return false;\n            }\n            return obj.x === obj;\n        } catch (e) { /* this is ES3 */\n            return false;\n        }\n    }());\n    var defineProperties = (function (has) {\n        // Define configurable, writable, and non-enumerable props\n        // if they don't exist.\n        var defineProperty;\n        if (supportsDescriptors) {\n            defineProperty = function (object, name, method, forceAssign) {\n                if (!forceAssign && (name in object)) {\n                    return;\n                }\n                $Object.defineProperty(object, name, {\n                    configurable: true,\n                    enumerable: false,\n                    writable: true,\n                    value: method\n                });\n            };\n        } else {\n            defineProperty = function (object, name, method, forceAssign) {\n                if (!forceAssign && (name in object)) {\n                    return;\n                }\n                object[name] = method;\n            };\n        }\n        return function defineProperties(object, map, forceAssign) {\n            for (var name in map) {\n                if (has.call(map, name)) {\n                    defineProperty(object, name, map[name], forceAssign);\n                }\n            }\n        };\n    }(ObjectPrototype.hasOwnProperty));\n\n    //\n    // Util\n    // ======\n    //\n\n    /* replaceable with https://npmjs.com/package/es-abstract /helpers/isPrimitive */\n    var isPrimitive = function isPrimitive(input) {\n        var type = typeof input;\n        return input === null || (type !== 'object' && type !== 'function');\n    };\n\n    var isActualNaN = $Number.isNaN || function isActualNaN(x) {\n        return x !== x;\n    };\n\n    var ES = {\n        // ES5 9.4\n        // http://es5.github.com/#x9.4\n        // http://jsperf.com/to-integer\n        /* replaceable with https://npmjs.com/package/es-abstract ES5.ToInteger */\n        ToInteger: function ToInteger(num) {\n            var n = +num;\n            if (isActualNaN(n)) {\n                n = 0;\n            } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {\n                n = (n > 0 || -1) * Math.floor(Math.abs(n));\n            }\n            return n;\n        },\n\n        /* replaceable with https://npmjs.com/package/es-abstract ES5.ToPrimitive */\n        ToPrimitive: function ToPrimitive(input) {\n            var val, valueOf, toStr;\n            if (isPrimitive(input)) {\n                return input;\n            }\n            valueOf = input.valueOf;\n            if (isCallable(valueOf)) {\n                val = valueOf.call(input);\n                if (isPrimitive(val)) {\n                    return val;\n                }\n            }\n            toStr = input.toString;\n            if (isCallable(toStr)) {\n                val = toStr.call(input);\n                if (isPrimitive(val)) {\n                    return val;\n                }\n            }\n            throw new TypeError();\n        },\n\n        // ES5 9.9\n        // http://es5.github.com/#x9.9\n        /* replaceable with https://npmjs.com/package/es-abstract ES5.ToObject */\n        ToObject: function (o) {\n            if (o == null) { // this matches both null and undefined\n                throw new TypeError(\"can't convert \" + o + ' to object');\n            }\n            return $Object(o);\n        },\n\n        /* replaceable with https://npmjs.com/package/es-abstract ES5.ToUint32 */\n        ToUint32: function ToUint32(x) {\n            return x >>> 0;\n        }\n    };\n\n    //\n    // Function\n    // ========\n    //\n\n    // ES-5 15.3.4.5\n    // http://es5.github.com/#x15.3.4.5\n\n    var Empty = function Empty() {};\n\n    defineProperties(FunctionPrototype, {\n        bind: function bind(that) { // .length is 1\n            // 1. Let Target be the this value.\n            var target = this;\n            // 2. If IsCallable(Target) is false, throw a TypeError exception.\n            if (!isCallable(target)) {\n                throw new TypeError('Function.prototype.bind called on incompatible ' + target);\n            }\n            // 3. Let A be a new (possibly empty) internal list of all of the\n            //   argument values provided after thisArg (arg1, arg2 etc), in order.\n            // XXX slicedArgs will stand in for \"A\" if used\n            var args = array_slice.call(arguments, 1); // for normal call\n            // 4. Let F be a new native ECMAScript object.\n            // 11. Set the [[Prototype]] internal property of F to the standard\n            //   built-in Function prototype object as specified in 15.3.3.1.\n            // 12. Set the [[Call]] internal property of F as described in\n            //   15.3.4.5.1.\n            // 13. Set the [[Construct]] internal property of F as described in\n            //   15.3.4.5.2.\n            // 14. Set the [[HasInstance]] internal property of F as described in\n            //   15.3.4.5.3.\n            var bound;\n            var binder = function () {\n\n                if (this instanceof bound) {\n                    // 15.3.4.5.2 [[Construct]]\n                    // When the [[Construct]] internal method of a function object,\n                    // F that was created using the bind function is called with a\n                    // list of arguments ExtraArgs, the following steps are taken:\n                    // 1. Let target be the value of F's [[TargetFunction]]\n                    //   internal property.\n                    // 2. If target has no [[Construct]] internal method, a\n                    //   TypeError exception is thrown.\n                    // 3. Let boundArgs be the value of F's [[BoundArgs]] internal\n                    //   property.\n                    // 4. Let args be a new list containing the same values as the\n                    //   list boundArgs in the same order followed by the same\n                    //   values as the list ExtraArgs in the same order.\n                    // 5. Return the result of calling the [[Construct]] internal\n                    //   method of target providing args as the arguments.\n\n                    var result = apply.call(\n                        target,\n                        this,\n                        array_concat.call(args, array_slice.call(arguments))\n                    );\n                    if ($Object(result) === result) {\n                        return result;\n                    }\n                    return this;\n\n                } else {\n                    // 15.3.4.5.1 [[Call]]\n                    // When the [[Call]] internal method of a function object, F,\n                    // which was created using the bind function is called with a\n                    // this value and a list of arguments ExtraArgs, the following\n                    // steps are taken:\n                    // 1. Let boundArgs be the value of F's [[BoundArgs]] internal\n                    //   property.\n                    // 2. Let boundThis be the value of F's [[BoundThis]] internal\n                    //   property.\n                    // 3. Let target be the value of F's [[TargetFunction]] internal\n                    //   property.\n                    // 4. Let args be a new list containing the same values as the\n                    //   list boundArgs in the same order followed by the same\n                    //   values as the list ExtraArgs in the same order.\n                    // 5. Return the result of calling the [[Call]] internal method\n                    //   of target providing boundThis as the this value and\n                    //   providing args as the arguments.\n\n                    // equiv: target.call(this, ...boundArgs, ...args)\n                    return apply.call(\n                        target,\n                        that,\n                        array_concat.call(args, array_slice.call(arguments))\n                    );\n\n                }\n\n            };\n\n            // 15. If the [[Class]] internal property of Target is \"Function\", then\n            //     a. Let L be the length property of Target minus the length of A.\n            //     b. Set the length own property of F to either 0 or L, whichever is\n            //       larger.\n            // 16. Else set the length own property of F to 0.\n\n            var boundLength = max(0, target.length - args.length);\n\n            // 17. Set the attributes of the length own property of F to the values\n            //   specified in 15.3.5.1.\n            var boundArgs = [];\n            for (var i = 0; i < boundLength; i++) {\n                array_push.call(boundArgs, '$' + i);\n            }\n\n            // XXX Build a dynamic function with desired amount of arguments is the only\n            // way to set the length property of a function.\n            // In environments where Content Security Policies enabled (Chrome extensions,\n            // for ex.) all use of eval or Function costructor throws an exception.\n            // However in all of these environments Function.prototype.bind exists\n            // and so this code will never be executed.\n            bound = $Function('binder', 'return function (' + array_join.call(boundArgs, ',') + '){ return binder.apply(this, arguments); }')(binder);\n\n            if (target.prototype) {\n                Empty.prototype = target.prototype;\n                bound.prototype = new Empty();\n                // Clean up dangling references.\n                Empty.prototype = null;\n            }\n\n            // TODO\n            // 18. Set the [[Extensible]] internal property of F to true.\n\n            // TODO\n            // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).\n            // 20. Call the [[DefineOwnProperty]] internal method of F with\n            //   arguments \"caller\", PropertyDescriptor {[[Get]]: thrower, [[Set]]:\n            //   thrower, [[Enumerable]]: false, [[Configurable]]: false}, and\n            //   false.\n            // 21. Call the [[DefineOwnProperty]] internal method of F with\n            //   arguments \"arguments\", PropertyDescriptor {[[Get]]: thrower,\n            //   [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},\n            //   and false.\n\n            // TODO\n            // NOTE Function objects created using Function.prototype.bind do not\n            // have a prototype property or the [[Code]], [[FormalParameters]], and\n            // [[Scope]] internal properties.\n            // XXX can't delete prototype in pure-js.\n\n            // 22. Return F.\n            return bound;\n        }\n    });\n\n    // _Please note: Shortcuts are defined after `Function.prototype.bind` as we\n    // use it in defining shortcuts.\n    var owns = call.bind(ObjectPrototype.hasOwnProperty);\n    var toStr = call.bind(ObjectPrototype.toString);\n    var arraySlice = call.bind(array_slice);\n    var arraySliceApply = apply.bind(array_slice);\n    var strSlice = call.bind(StringPrototype.slice);\n    var strSplit = call.bind(StringPrototype.split);\n    var strIndexOf = call.bind(StringPrototype.indexOf);\n    var pushCall = call.bind(array_push);\n    var isEnum = call.bind(ObjectPrototype.propertyIsEnumerable);\n    var arraySort = call.bind(ArrayPrototype.sort);\n\n    //\n    // Array\n    // =====\n    //\n\n    var isArray = $Array.isArray || function isArray(obj) {\n        return toStr(obj) === '[object Array]';\n    };\n\n    // ES5 15.4.4.12\n    // http://es5.github.com/#x15.4.4.13\n    // Return len+argCount.\n    // [bugfix, ielt8]\n    // IE < 8 bug: [].unshift(0) === undefined but should be \"1\"\n    var hasUnshiftReturnValueBug = [].unshift(0) !== 1;\n    defineProperties(ArrayPrototype, {\n        unshift: function () {\n            array_unshift.apply(this, arguments);\n            return this.length;\n        }\n    }, hasUnshiftReturnValueBug);\n\n    // ES5 15.4.3.2\n    // http://es5.github.com/#x15.4.3.2\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray\n    defineProperties($Array, { isArray: isArray });\n\n    // The IsCallable() check in the Array functions\n    // has been replaced with a strict check on the\n    // internal class of the object to trap cases where\n    // the provided function was actually a regular\n    // expression literal, which in V8 and\n    // JavaScriptCore is a typeof \"function\".  Only in\n    // V8 are regular expression literals permitted as\n    // reduce parameters, so it is desirable in the\n    // general case for the shim to match the more\n    // strict and common behavior of rejecting regular\n    // expressions.\n\n    // ES5 15.4.4.18\n    // http://es5.github.com/#x15.4.4.18\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach\n\n    // Check failure of by-index access of string characters (IE < 9)\n    // and failure of `0 in boxedString` (Rhino)\n    var boxedString = $Object('a');\n    var splitString = boxedString[0] !== 'a' || !(0 in boxedString);\n\n    var properlyBoxesContext = function properlyBoxed(method) {\n        // Check node 0.6.21 bug where third parameter is not boxed\n        var properlyBoxesNonStrict = true;\n        var properlyBoxesStrict = true;\n        var threwException = false;\n        if (method) {\n            try {\n                method.call('foo', function (_, __, context) {\n                    if (typeof context !== 'object') {\n                        properlyBoxesNonStrict = false;\n                    }\n                });\n\n                method.call([1], function () {\n                    'use strict';\n\n                    properlyBoxesStrict = typeof this === 'string';\n                }, 'x');\n            } catch (e) {\n                threwException = true;\n            }\n        }\n        return !!method && !threwException && properlyBoxesNonStrict && properlyBoxesStrict;\n    };\n\n    defineProperties(ArrayPrototype, {\n        forEach: function forEach(callbackfn/*, thisArg*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var i = -1;\n            var length = ES.ToUint32(self.length);\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.forEach callback must be a function');\n            }\n\n            while (++i < length) {\n                if (i in self) {\n                    // Invoke the callback function with call, passing arguments:\n                    // context, property value, property key, thisArg object\n                    if (typeof T === 'undefined') {\n                        callbackfn(self[i], i, object);\n                    } else {\n                        callbackfn.call(T, self[i], i, object);\n                    }\n                }\n            }\n        }\n    }, !properlyBoxesContext(ArrayPrototype.forEach));\n\n    // ES5 15.4.4.19\n    // http://es5.github.com/#x15.4.4.19\n    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map\n    defineProperties(ArrayPrototype, {\n        map: function map(callbackfn/*, thisArg*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n            var result = $Array(length);\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.map callback must be a function');\n            }\n\n            for (var i = 0; i < length; i++) {\n                if (i in self) {\n                    if (typeof T === 'undefined') {\n                        result[i] = callbackfn(self[i], i, object);\n                    } else {\n                        result[i] = callbackfn.call(T, self[i], i, object);\n                    }\n                }\n            }\n            return result;\n        }\n    }, !properlyBoxesContext(ArrayPrototype.map));\n\n    // ES5 15.4.4.20\n    // http://es5.github.com/#x15.4.4.20\n    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter\n    defineProperties(ArrayPrototype, {\n        filter: function filter(callbackfn/*, thisArg*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n            var result = [];\n            var value;\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.filter callback must be a function');\n            }\n\n            for (var i = 0; i < length; i++) {\n                if (i in self) {\n                    value = self[i];\n                    if (typeof T === 'undefined' ? callbackfn(value, i, object) : callbackfn.call(T, value, i, object)) {\n                        pushCall(result, value);\n                    }\n                }\n            }\n            return result;\n        }\n    }, !properlyBoxesContext(ArrayPrototype.filter));\n\n    // ES5 15.4.4.16\n    // http://es5.github.com/#x15.4.4.16\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every\n    defineProperties(ArrayPrototype, {\n        every: function every(callbackfn/*, thisArg*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.every callback must be a function');\n            }\n\n            for (var i = 0; i < length; i++) {\n                if (i in self && !(typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) {\n                    return false;\n                }\n            }\n            return true;\n        }\n    }, !properlyBoxesContext(ArrayPrototype.every));\n\n    // ES5 15.4.4.17\n    // http://es5.github.com/#x15.4.4.17\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some\n    defineProperties(ArrayPrototype, {\n        some: function some(callbackfn/*, thisArg */) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.some callback must be a function');\n            }\n\n            for (var i = 0; i < length; i++) {\n                if (i in self && (typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) {\n                    return true;\n                }\n            }\n            return false;\n        }\n    }, !properlyBoxesContext(ArrayPrototype.some));\n\n    // ES5 15.4.4.21\n    // http://es5.github.com/#x15.4.4.21\n    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce\n    var reduceCoercesToObject = false;\n    if (ArrayPrototype.reduce) {\n        reduceCoercesToObject = typeof ArrayPrototype.reduce.call('es5', function (_, __, ___, list) {\n            return list;\n        }) === 'object';\n    }\n    defineProperties(ArrayPrototype, {\n        reduce: function reduce(callbackfn/*, initialValue*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.reduce callback must be a function');\n            }\n\n            // no value to return if no initial value and an empty array\n            if (length === 0 && arguments.length === 1) {\n                throw new TypeError('reduce of empty array with no initial value');\n            }\n\n            var i = 0;\n            var result;\n            if (arguments.length >= 2) {\n                result = arguments[1];\n            } else {\n                do {\n                    if (i in self) {\n                        result = self[i++];\n                        break;\n                    }\n\n                    // if array contains no values, no initial value to return\n                    if (++i >= length) {\n                        throw new TypeError('reduce of empty array with no initial value');\n                    }\n                } while (true);\n            }\n\n            for (; i < length; i++) {\n                if (i in self) {\n                    result = callbackfn(result, self[i], i, object);\n                }\n            }\n\n            return result;\n        }\n    }, !reduceCoercesToObject);\n\n    // ES5 15.4.4.22\n    // http://es5.github.com/#x15.4.4.22\n    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight\n    var reduceRightCoercesToObject = false;\n    if (ArrayPrototype.reduceRight) {\n        reduceRightCoercesToObject = typeof ArrayPrototype.reduceRight.call('es5', function (_, __, ___, list) {\n            return list;\n        }) === 'object';\n    }\n    defineProperties(ArrayPrototype, {\n        reduceRight: function reduceRight(callbackfn/*, initial*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.reduceRight callback must be a function');\n            }\n\n            // no value to return if no initial value, empty array\n            if (length === 0 && arguments.length === 1) {\n                throw new TypeError('reduceRight of empty array with no initial value');\n            }\n\n            var result;\n            var i = length - 1;\n            if (arguments.length >= 2) {\n                result = arguments[1];\n            } else {\n                do {\n                    if (i in self) {\n                        result = self[i--];\n                        break;\n                    }\n\n                    // if array contains no values, no initial value to return\n                    if (--i < 0) {\n                        throw new TypeError('reduceRight of empty array with no initial value');\n                    }\n                } while (true);\n            }\n\n            if (i < 0) {\n                return result;\n            }\n\n            do {\n                if (i in self) {\n                    result = callbackfn(result, self[i], i, object);\n                }\n            } while (i--);\n\n            return result;\n        }\n    }, !reduceRightCoercesToObject);\n\n    // ES5 15.4.4.14\n    // http://es5.github.com/#x15.4.4.14\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf\n    var hasFirefox2IndexOfBug = ArrayPrototype.indexOf && [0, 1].indexOf(1, 2) !== -1;\n    defineProperties(ArrayPrototype, {\n        indexOf: function indexOf(searchElement/*, fromIndex */) {\n            var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this);\n            var length = ES.ToUint32(self.length);\n\n            if (length === 0) {\n                return -1;\n            }\n\n            var i = 0;\n            if (arguments.length > 1) {\n                i = ES.ToInteger(arguments[1]);\n            }\n\n            // handle negative indices\n            i = i >= 0 ? i : max(0, length + i);\n            for (; i < length; i++) {\n                if (i in self && self[i] === searchElement) {\n                    return i;\n                }\n            }\n            return -1;\n        }\n    }, hasFirefox2IndexOfBug);\n\n    // ES5 15.4.4.15\n    // http://es5.github.com/#x15.4.4.15\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf\n    var hasFirefox2LastIndexOfBug = ArrayPrototype.lastIndexOf && [0, 1].lastIndexOf(0, -3) !== -1;\n    defineProperties(ArrayPrototype, {\n        lastIndexOf: function lastIndexOf(searchElement/*, fromIndex */) {\n            var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this);\n            var length = ES.ToUint32(self.length);\n\n            if (length === 0) {\n                return -1;\n            }\n            var i = length - 1;\n            if (arguments.length > 1) {\n                i = min(i, ES.ToInteger(arguments[1]));\n            }\n            // handle negative indices\n            i = i >= 0 ? i : length - Math.abs(i);\n            for (; i >= 0; i--) {\n                if (i in self && searchElement === self[i]) {\n                    return i;\n                }\n            }\n            return -1;\n        }\n    }, hasFirefox2LastIndexOfBug);\n\n    // ES5 15.4.4.12\n    // http://es5.github.com/#x15.4.4.12\n    var spliceNoopReturnsEmptyArray = (function () {\n        var a = [1, 2];\n        var result = a.splice();\n        return a.length === 2 && isArray(result) && result.length === 0;\n    }());\n    defineProperties(ArrayPrototype, {\n        // Safari 5.0 bug where .splice() returns undefined\n        splice: function splice(start, deleteCount) {\n            if (arguments.length === 0) {\n                return [];\n            } else {\n                return array_splice.apply(this, arguments);\n            }\n        }\n    }, !spliceNoopReturnsEmptyArray);\n\n    var spliceWorksWithEmptyObject = (function () {\n        var obj = {};\n        ArrayPrototype.splice.call(obj, 0, 0, 1);\n        return obj.length === 1;\n    }());\n    defineProperties(ArrayPrototype, {\n        splice: function splice(start, deleteCount) {\n            if (arguments.length === 0) {\n                return [];\n            }\n            var args = arguments;\n            this.length = max(ES.ToInteger(this.length), 0);\n            if (arguments.length > 0 && typeof deleteCount !== 'number') {\n                args = arraySlice(arguments);\n                if (args.length < 2) {\n                    pushCall(args, this.length - start);\n                } else {\n                    args[1] = ES.ToInteger(deleteCount);\n                }\n            }\n            return array_splice.apply(this, args);\n        }\n    }, !spliceWorksWithEmptyObject);\n    var spliceWorksWithLargeSparseArrays = (function () {\n        // Per https://github.com/es-shims/es5-shim/issues/295\n        // Safari 7/8 breaks with sparse arrays of size 1e5 or greater\n        var arr = new $Array(1e5);\n        // note: the index MUST be 8 or larger or the test will false pass\n        arr[8] = 'x';\n        arr.splice(1, 1);\n        // note: this test must be defined *after* the indexOf shim\n        // per https://github.com/es-shims/es5-shim/issues/313\n        return arr.indexOf('x') === 7;\n    }());\n    var spliceWorksWithSmallSparseArrays = (function () {\n        // Per https://github.com/es-shims/es5-shim/issues/295\n        // Opera 12.15 breaks on this, no idea why.\n        var n = 256;\n        var arr = [];\n        arr[n] = 'a';\n        arr.splice(n + 1, 0, 'b');\n        return arr[n] === 'a';\n    }());\n    defineProperties(ArrayPrototype, {\n        splice: function splice(start, deleteCount) {\n            var O = ES.ToObject(this);\n            var A = [];\n            var len = ES.ToUint32(O.length);\n            var relativeStart = ES.ToInteger(start);\n            var actualStart = relativeStart < 0 ? max((len + relativeStart), 0) : min(relativeStart, len);\n            var actualDeleteCount = min(max(ES.ToInteger(deleteCount), 0), len - actualStart);\n\n            var k = 0;\n            var from;\n            while (k < actualDeleteCount) {\n                from = $String(actualStart + k);\n                if (owns(O, from)) {\n                    A[k] = O[from];\n                }\n                k += 1;\n            }\n\n            var items = arraySlice(arguments, 2);\n            var itemCount = items.length;\n            var to;\n            if (itemCount < actualDeleteCount) {\n                k = actualStart;\n                var maxK = len - actualDeleteCount;\n                while (k < maxK) {\n                    from = $String(k + actualDeleteCount);\n                    to = $String(k + itemCount);\n                    if (owns(O, from)) {\n                        O[to] = O[from];\n                    } else {\n                        delete O[to];\n                    }\n                    k += 1;\n                }\n                k = len;\n                var minK = len - actualDeleteCount + itemCount;\n                while (k > minK) {\n                    delete O[k - 1];\n                    k -= 1;\n                }\n            } else if (itemCount > actualDeleteCount) {\n                k = len - actualDeleteCount;\n                while (k > actualStart) {\n                    from = $String(k + actualDeleteCount - 1);\n                    to = $String(k + itemCount - 1);\n                    if (owns(O, from)) {\n                        O[to] = O[from];\n                    } else {\n                        delete O[to];\n                    }\n                    k -= 1;\n                }\n            }\n            k = actualStart;\n            for (var i = 0; i < items.length; ++i) {\n                O[k] = items[i];\n                k += 1;\n            }\n            O.length = len - actualDeleteCount + itemCount;\n\n            return A;\n        }\n    }, !spliceWorksWithLargeSparseArrays || !spliceWorksWithSmallSparseArrays);\n\n    var originalJoin = ArrayPrototype.join;\n    var hasStringJoinBug;\n    try {\n        hasStringJoinBug = Array.prototype.join.call('123', ',') !== '1,2,3';\n    } catch (e) {\n        hasStringJoinBug = true;\n    }\n    if (hasStringJoinBug) {\n        defineProperties(ArrayPrototype, {\n            join: function join(separator) {\n                var sep = typeof separator === 'undefined' ? ',' : separator;\n                return originalJoin.call(isString(this) ? strSplit(this, '') : this, sep);\n            }\n        }, hasStringJoinBug);\n    }\n\n    var hasJoinUndefinedBug = [1, 2].join(undefined) !== '1,2';\n    if (hasJoinUndefinedBug) {\n        defineProperties(ArrayPrototype, {\n            join: function join(separator) {\n                var sep = typeof separator === 'undefined' ? ',' : separator;\n                return originalJoin.call(this, sep);\n            }\n        }, hasJoinUndefinedBug);\n    }\n\n    var pushShim = function push(item) {\n        var O = ES.ToObject(this);\n        var n = ES.ToUint32(O.length);\n        var i = 0;\n        while (i < arguments.length) {\n            O[n + i] = arguments[i];\n            i += 1;\n        }\n        O.length = n + i;\n        return n + i;\n    };\n\n    var pushIsNotGeneric = (function () {\n        var obj = {};\n        var result = Array.prototype.push.call(obj, undefined);\n        return result !== 1 || obj.length !== 1 || typeof obj[0] !== 'undefined' || !owns(obj, 0);\n    }());\n    defineProperties(ArrayPrototype, {\n        push: function push(item) {\n            if (isArray(this)) {\n                return array_push.apply(this, arguments);\n            }\n            return pushShim.apply(this, arguments);\n        }\n    }, pushIsNotGeneric);\n\n    // This fixes a very weird bug in Opera 10.6 when pushing `undefined\n    var pushUndefinedIsWeird = (function () {\n        var arr = [];\n        var result = arr.push(undefined);\n        return result !== 1 || arr.length !== 1 || typeof arr[0] !== 'undefined' || !owns(arr, 0);\n    }());\n    defineProperties(ArrayPrototype, { push: pushShim }, pushUndefinedIsWeird);\n\n    // ES5 15.2.3.14\n    // http://es5.github.io/#x15.4.4.10\n    // Fix boxed string bug\n    defineProperties(ArrayPrototype, {\n        slice: function (start, end) {\n            var arr = isString(this) ? strSplit(this, '') : this;\n            return arraySliceApply(arr, arguments);\n        }\n    }, splitString);\n\n    var sortIgnoresNonFunctions = (function () {\n        try {\n            [1, 2].sort(null);\n            [1, 2].sort({});\n            return true;\n        } catch (e) {}\n        return false;\n    }());\n    var sortThrowsOnRegex = (function () {\n        // this is a problem in Firefox 4, in which `typeof /a/ === 'function'`\n        try {\n            [1, 2].sort(/a/);\n            return false;\n        } catch (e) {}\n        return true;\n    }());\n    var sortIgnoresUndefined = (function () {\n        // applies in IE 8, for one.\n        try {\n            [1, 2].sort(undefined);\n            return true;\n        } catch (e) {}\n        return false;\n    }());\n    defineProperties(ArrayPrototype, {\n        sort: function sort(compareFn) {\n            if (typeof compareFn === 'undefined') {\n                return arraySort(this);\n            }\n            if (!isCallable(compareFn)) {\n                throw new TypeError('Array.prototype.sort callback must be a function');\n            }\n            return arraySort(this, compareFn);\n        }\n    }, sortIgnoresNonFunctions || !sortIgnoresUndefined || !sortThrowsOnRegex);\n\n    //\n    // Object\n    // ======\n    //\n\n    // ES5 15.2.3.14\n    // http://es5.github.com/#x15.2.3.14\n\n    // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation\n    var hasDontEnumBug = !isEnum({ 'toString': null }, 'toString');\n    var hasProtoEnumBug = isEnum(function () {}, 'prototype');\n    var hasStringEnumBug = !owns('x', '0');\n    var equalsConstructorPrototype = function (o) {\n        var ctor = o.constructor;\n        return ctor && ctor.prototype === o;\n    };\n    var blacklistedKeys = {\n        $window: true,\n        $console: true,\n        $parent: true,\n        $self: true,\n        $frame: true,\n        $frames: true,\n        $frameElement: true,\n        $webkitIndexedDB: true,\n        $webkitStorageInfo: true,\n        $external: true\n    };\n    var hasAutomationEqualityBug = (function () {\n        /* globals window */\n        if (typeof window === 'undefined') {\n            return false;\n        }\n        for (var k in window) {\n            try {\n                if (!blacklistedKeys['$' + k] && owns(window, k) && window[k] !== null && typeof window[k] === 'object') {\n                    equalsConstructorPrototype(window[k]);\n                }\n            } catch (e) {\n                return true;\n            }\n        }\n        return false;\n    }());\n    var equalsConstructorPrototypeIfNotBuggy = function (object) {\n        if (typeof window === 'undefined' || !hasAutomationEqualityBug) {\n            return equalsConstructorPrototype(object);\n        }\n        try {\n            return equalsConstructorPrototype(object);\n        } catch (e) {\n            return false;\n        }\n    };\n    var dontEnums = [\n        'toString',\n        'toLocaleString',\n        'valueOf',\n        'hasOwnProperty',\n        'isPrototypeOf',\n        'propertyIsEnumerable',\n        'constructor'\n    ];\n    var dontEnumsLength = dontEnums.length;\n\n    // taken directly from https://github.com/ljharb/is-arguments/blob/master/index.js\n    // can be replaced with require('is-arguments') if we ever use a build process instead\n    var isStandardArguments = function isArguments(value) {\n        return toStr(value) === '[object Arguments]';\n    };\n    var isLegacyArguments = function isArguments(value) {\n        return value !== null &&\n            typeof value === 'object' &&\n            typeof value.length === 'number' &&\n            value.length >= 0 &&\n            !isArray(value) &&\n            isCallable(value.callee);\n    };\n    var isArguments = isStandardArguments(arguments) ? isStandardArguments : isLegacyArguments;\n\n    defineProperties($Object, {\n        keys: function keys(object) {\n            var isFn = isCallable(object);\n            var isArgs = isArguments(object);\n            var isObject = object !== null && typeof object === 'object';\n            var isStr = isObject && isString(object);\n\n            if (!isObject && !isFn && !isArgs) {\n                throw new TypeError('Object.keys called on a non-object');\n            }\n\n            var theKeys = [];\n            var skipProto = hasProtoEnumBug && isFn;\n            if ((isStr && hasStringEnumBug) || isArgs) {\n                for (var i = 0; i < object.length; ++i) {\n                    pushCall(theKeys, $String(i));\n                }\n            }\n\n            if (!isArgs) {\n                for (var name in object) {\n                    if (!(skipProto && name === 'prototype') && owns(object, name)) {\n                        pushCall(theKeys, $String(name));\n                    }\n                }\n            }\n\n            if (hasDontEnumBug) {\n                var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object);\n                for (var j = 0; j < dontEnumsLength; j++) {\n                    var dontEnum = dontEnums[j];\n                    if (!(skipConstructor && dontEnum === 'constructor') && owns(object, dontEnum)) {\n                        pushCall(theKeys, dontEnum);\n                    }\n                }\n            }\n            return theKeys;\n        }\n    });\n\n    var keysWorksWithArguments = $Object.keys && (function () {\n        // Safari 5.0 bug\n        return $Object.keys(arguments).length === 2;\n    }(1, 2));\n    var keysHasArgumentsLengthBug = $Object.keys && (function () {\n        var argKeys = $Object.keys(arguments);\n        return arguments.length !== 1 || argKeys.length !== 1 || argKeys[0] !== 1;\n    }(1));\n    var originalKeys = $Object.keys;\n    defineProperties($Object, {\n        keys: function keys(object) {\n            if (isArguments(object)) {\n                return originalKeys(arraySlice(object));\n            } else {\n                return originalKeys(object);\n            }\n        }\n    }, !keysWorksWithArguments || keysHasArgumentsLengthBug);\n\n    //\n    // Date\n    // ====\n    //\n\n    var hasNegativeMonthYearBug = new Date(-3509827329600292).getUTCMonth() !== 0;\n    var aNegativeTestDate = new Date(-1509842289600292);\n    var aPositiveTestDate = new Date(1449662400000);\n    var hasToUTCStringFormatBug = aNegativeTestDate.toUTCString() !== 'Mon, 01 Jan -45875 11:59:59 GMT';\n    var hasToDateStringFormatBug;\n    var hasToStringFormatBug;\n    var timeZoneOffset = aNegativeTestDate.getTimezoneOffset();\n    if (timeZoneOffset < -720) {\n        hasToDateStringFormatBug = aNegativeTestDate.toDateString() !== 'Tue Jan 02 -45875';\n        hasToStringFormatBug = !(/^Thu Dec 10 2015 \\d\\d:\\d\\d:\\d\\d GMT[-\\+]\\d\\d\\d\\d(?: |$)/).test(aPositiveTestDate.toString());\n    } else {\n        hasToDateStringFormatBug = aNegativeTestDate.toDateString() !== 'Mon Jan 01 -45875';\n        hasToStringFormatBug = !(/^Wed Dec 09 2015 \\d\\d:\\d\\d:\\d\\d GMT[-\\+]\\d\\d\\d\\d(?: |$)/).test(aPositiveTestDate.toString());\n    }\n\n    var originalGetFullYear = call.bind(Date.prototype.getFullYear);\n    var originalGetMonth = call.bind(Date.prototype.getMonth);\n    var originalGetDate = call.bind(Date.prototype.getDate);\n    var originalGetUTCFullYear = call.bind(Date.prototype.getUTCFullYear);\n    var originalGetUTCMonth = call.bind(Date.prototype.getUTCMonth);\n    var originalGetUTCDate = call.bind(Date.prototype.getUTCDate);\n    var originalGetUTCDay = call.bind(Date.prototype.getUTCDay);\n    var originalGetUTCHours = call.bind(Date.prototype.getUTCHours);\n    var originalGetUTCMinutes = call.bind(Date.prototype.getUTCMinutes);\n    var originalGetUTCSeconds = call.bind(Date.prototype.getUTCSeconds);\n    var originalGetUTCMilliseconds = call.bind(Date.prototype.getUTCMilliseconds);\n    var dayName = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n    var monthName = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n    var daysInMonth = function daysInMonth(month, year) {\n        return originalGetDate(new Date(year, month, 0));\n    };\n\n    defineProperties(Date.prototype, {\n        getFullYear: function getFullYear() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetFullYear(this);\n            if (year < 0 && originalGetMonth(this) > 11) {\n                return year + 1;\n            }\n            return year;\n        },\n        getMonth: function getMonth() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetFullYear(this);\n            var month = originalGetMonth(this);\n            if (year < 0 && month > 11) {\n                return 0;\n            }\n            return month;\n        },\n        getDate: function getDate() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetFullYear(this);\n            var month = originalGetMonth(this);\n            var date = originalGetDate(this);\n            if (year < 0 && month > 11) {\n                if (month === 12) {\n                    return date;\n                }\n                var days = daysInMonth(0, year + 1);\n                return (days - date) + 1;\n            }\n            return date;\n        },\n        getUTCFullYear: function getUTCFullYear() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetUTCFullYear(this);\n            if (year < 0 && originalGetUTCMonth(this) > 11) {\n                return year + 1;\n            }\n            return year;\n        },\n        getUTCMonth: function getUTCMonth() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetUTCFullYear(this);\n            var month = originalGetUTCMonth(this);\n            if (year < 0 && month > 11) {\n                return 0;\n            }\n            return month;\n        },\n        getUTCDate: function getUTCDate() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetUTCFullYear(this);\n            var month = originalGetUTCMonth(this);\n            var date = originalGetUTCDate(this);\n            if (year < 0 && month > 11) {\n                if (month === 12) {\n                    return date;\n                }\n                var days = daysInMonth(0, year + 1);\n                return (days - date) + 1;\n            }\n            return date;\n        }\n    }, hasNegativeMonthYearBug);\n\n    defineProperties(Date.prototype, {\n        toUTCString: function toUTCString() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var day = originalGetUTCDay(this);\n            var date = originalGetUTCDate(this);\n            var month = originalGetUTCMonth(this);\n            var year = originalGetUTCFullYear(this);\n            var hour = originalGetUTCHours(this);\n            var minute = originalGetUTCMinutes(this);\n            var second = originalGetUTCSeconds(this);\n            return dayName[day] + ', ' +\n                (date < 10 ? '0' + date : date) + ' ' +\n                monthName[month] + ' ' +\n                year + ' ' +\n                (hour < 10 ? '0' + hour : hour) + ':' +\n                (minute < 10 ? '0' + minute : minute) + ':' +\n                (second < 10 ? '0' + second : second) + ' GMT';\n        }\n    }, hasNegativeMonthYearBug || hasToUTCStringFormatBug);\n\n    // Opera 12 has `,`\n    defineProperties(Date.prototype, {\n        toDateString: function toDateString() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var day = this.getDay();\n            var date = this.getDate();\n            var month = this.getMonth();\n            var year = this.getFullYear();\n            return dayName[day] + ' ' +\n                monthName[month] + ' ' +\n                (date < 10 ? '0' + date : date) + ' ' +\n                year;\n        }\n    }, hasNegativeMonthYearBug || hasToDateStringFormatBug);\n\n    // can't use defineProperties here because of toString enumeration issue in IE <= 8\n    if (hasNegativeMonthYearBug || hasToStringFormatBug) {\n        Date.prototype.toString = function toString() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var day = this.getDay();\n            var date = this.getDate();\n            var month = this.getMonth();\n            var year = this.getFullYear();\n            var hour = this.getHours();\n            var minute = this.getMinutes();\n            var second = this.getSeconds();\n            var timezoneOffset = this.getTimezoneOffset();\n            var hoursOffset = Math.floor(Math.abs(timezoneOffset) / 60);\n            var minutesOffset = Math.floor(Math.abs(timezoneOffset) % 60);\n            return dayName[day] + ' ' +\n                monthName[month] + ' ' +\n                (date < 10 ? '0' + date : date) + ' ' +\n                year + ' ' +\n                (hour < 10 ? '0' + hour : hour) + ':' +\n                (minute < 10 ? '0' + minute : minute) + ':' +\n                (second < 10 ? '0' + second : second) + ' GMT' +\n                (timezoneOffset > 0 ? '-' : '+') +\n                (hoursOffset < 10 ? '0' + hoursOffset : hoursOffset) +\n                (minutesOffset < 10 ? '0' + minutesOffset : minutesOffset);\n        };\n        if (supportsDescriptors) {\n            $Object.defineProperty(Date.prototype, 'toString', {\n                configurable: true,\n                enumerable: false,\n                writable: true\n            });\n        }\n    }\n\n    // ES5 15.9.5.43\n    // http://es5.github.com/#x15.9.5.43\n    // This function returns a String value represent the instance in time\n    // represented by this Date object. The format of the String is the Date Time\n    // string format defined in 15.9.1.15. All fields are present in the String.\n    // The time zone is always UTC, denoted by the suffix Z. If the time value of\n    // this object is not a finite Number a RangeError exception is thrown.\n    var negativeDate = -62198755200000;\n    var negativeYearString = '-000001';\n    var hasNegativeDateBug = Date.prototype.toISOString && new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1;\n    var hasSafari51DateBug = Date.prototype.toISOString && new Date(-1).toISOString() !== '1969-12-31T23:59:59.999Z';\n\n    var getTime = call.bind(Date.prototype.getTime);\n\n    defineProperties(Date.prototype, {\n        toISOString: function toISOString() {\n            if (!isFinite(this) || !isFinite(getTime(this))) {\n                // Adope Photoshop requires the second check.\n                throw new RangeError('Date.prototype.toISOString called on non-finite value.');\n            }\n\n            var year = originalGetUTCFullYear(this);\n\n            var month = originalGetUTCMonth(this);\n            // see https://github.com/es-shims/es5-shim/issues/111\n            year += Math.floor(month / 12);\n            month = (month % 12 + 12) % 12;\n\n            // the date time string format is specified in 15.9.1.15.\n            var result = [month + 1, originalGetUTCDate(this), originalGetUTCHours(this), originalGetUTCMinutes(this), originalGetUTCSeconds(this)];\n            year = (\n                (year < 0 ? '-' : (year > 9999 ? '+' : '')) +\n                strSlice('00000' + Math.abs(year), (0 <= year && year <= 9999) ? -4 : -6)\n            );\n\n            for (var i = 0; i < result.length; ++i) {\n                // pad months, days, hours, minutes, and seconds to have two digits.\n                result[i] = strSlice('00' + result[i], -2);\n            }\n            // pad milliseconds to have three digits.\n            return (\n                year + '-' + arraySlice(result, 0, 2).join('-') +\n                'T' + arraySlice(result, 2).join(':') + '.' +\n                strSlice('000' + originalGetUTCMilliseconds(this), -3) + 'Z'\n            );\n        }\n    }, hasNegativeDateBug || hasSafari51DateBug);\n\n    // ES5 15.9.5.44\n    // http://es5.github.com/#x15.9.5.44\n    // This function provides a String representation of a Date object for use by\n    // JSON.stringify (15.12.3).\n    var dateToJSONIsSupported = (function () {\n        try {\n            return Date.prototype.toJSON &&\n                new Date(NaN).toJSON() === null &&\n                new Date(negativeDate).toJSON().indexOf(negativeYearString) !== -1 &&\n                Date.prototype.toJSON.call({ // generic\n                    toISOString: function () { return true; }\n                });\n        } catch (e) {\n            return false;\n        }\n    }());\n    if (!dateToJSONIsSupported) {\n        Date.prototype.toJSON = function toJSON(key) {\n            // When the toJSON method is called with argument key, the following\n            // steps are taken:\n\n            // 1.  Let O be the result of calling ToObject, giving it the this\n            // value as its argument.\n            // 2. Let tv be ES.ToPrimitive(O, hint Number).\n            var O = $Object(this);\n            var tv = ES.ToPrimitive(O);\n            // 3. If tv is a Number and is not finite, return null.\n            if (typeof tv === 'number' && !isFinite(tv)) {\n                return null;\n            }\n            // 4. Let toISO be the result of calling the [[Get]] internal method of\n            // O with argument \"toISOString\".\n            var toISO = O.toISOString;\n            // 5. If IsCallable(toISO) is false, throw a TypeError exception.\n            if (!isCallable(toISO)) {\n                throw new TypeError('toISOString property is not callable');\n            }\n            // 6. Return the result of calling the [[Call]] internal method of\n            //  toISO with O as the this value and an empty argument list.\n            return toISO.call(O);\n\n            // NOTE 1 The argument is ignored.\n\n            // NOTE 2 The toJSON function is intentionally generic; it does not\n            // require that its this value be a Date object. Therefore, it can be\n            // transferred to other kinds of objects for use as a method. However,\n            // it does require that any such object have a toISOString method. An\n            // object is free to use the argument key to filter its\n            // stringification.\n        };\n    }\n\n    // ES5 15.9.4.2\n    // http://es5.github.com/#x15.9.4.2\n    // based on work shared by Daniel Friesen (dantman)\n    // http://gist.github.com/303249\n    var supportsExtendedYears = Date.parse('+033658-09-27T01:46:40.000Z') === 1e15;\n    var acceptsInvalidDates = !isNaN(Date.parse('2012-04-04T24:00:00.500Z')) || !isNaN(Date.parse('2012-11-31T23:59:59.000Z')) || !isNaN(Date.parse('2012-12-31T23:59:60.000Z'));\n    var doesNotParseY2KNewYear = isNaN(Date.parse('2000-01-01T00:00:00.000Z'));\n    if (doesNotParseY2KNewYear || acceptsInvalidDates || !supportsExtendedYears) {\n        // XXX global assignment won't work in embeddings that use\n        // an alternate object for the context.\n        /* global Date: true */\n        /* eslint-disable no-undef */\n        var maxSafeUnsigned32Bit = Math.pow(2, 31) - 1;\n        var hasSafariSignedIntBug = isActualNaN(new Date(1970, 0, 1, 0, 0, 0, maxSafeUnsigned32Bit + 1).getTime());\n        /* eslint-disable no-implicit-globals */\n        Date = (function (NativeDate) {\n        /* eslint-enable no-implicit-globals */\n        /* eslint-enable no-undef */\n            // Date.length === 7\n            var DateShim = function Date(Y, M, D, h, m, s, ms) {\n                var length = arguments.length;\n                var date;\n                if (this instanceof NativeDate) {\n                    var seconds = s;\n                    var millis = ms;\n                    if (hasSafariSignedIntBug && length >= 7 && ms > maxSafeUnsigned32Bit) {\n                        // work around a Safari 8/9 bug where it treats the seconds as signed\n                        var msToShift = Math.floor(ms / maxSafeUnsigned32Bit) * maxSafeUnsigned32Bit;\n                        var sToShift = Math.floor(msToShift / 1e3);\n                        seconds += sToShift;\n                        millis -= sToShift * 1e3;\n                    }\n                    date = length === 1 && $String(Y) === Y ? // isString(Y)\n                        // We explicitly pass it through parse:\n                        new NativeDate(DateShim.parse(Y)) :\n                        // We have to manually make calls depending on argument\n                        // length here\n                        length >= 7 ? new NativeDate(Y, M, D, h, m, seconds, millis) :\n                        length >= 6 ? new NativeDate(Y, M, D, h, m, seconds) :\n                        length >= 5 ? new NativeDate(Y, M, D, h, m) :\n                        length >= 4 ? new NativeDate(Y, M, D, h) :\n                        length >= 3 ? new NativeDate(Y, M, D) :\n                        length >= 2 ? new NativeDate(Y, M) :\n                        length >= 1 ? new NativeDate(Y instanceof NativeDate ? +Y : Y) :\n                                      new NativeDate();\n                } else {\n                    date = NativeDate.apply(this, arguments);\n                }\n                if (!isPrimitive(date)) {\n                    // Prevent mixups with unfixed Date object\n                    defineProperties(date, { constructor: DateShim }, true);\n                }\n                return date;\n            };\n\n            // 15.9.1.15 Date Time String Format.\n            var isoDateExpression = new RegExp('^' +\n                '(\\\\d{4}|[+-]\\\\d{6})' + // four-digit year capture or sign +\n                                          // 6-digit extended year\n                '(?:-(\\\\d{2})' + // optional month capture\n                '(?:-(\\\\d{2})' + // optional day capture\n                '(?:' + // capture hours:minutes:seconds.milliseconds\n                    'T(\\\\d{2})' + // hours capture\n                    ':(\\\\d{2})' + // minutes capture\n                    '(?:' + // optional :seconds.milliseconds\n                        ':(\\\\d{2})' + // seconds capture\n                        '(?:(\\\\.\\\\d{1,}))?' + // milliseconds capture\n                    ')?' +\n                '(' + // capture UTC offset component\n                    'Z|' + // UTC capture\n                    '(?:' + // offset specifier +/-hours:minutes\n                        '([-+])' + // sign capture\n                        '(\\\\d{2})' + // hours offset capture\n                        ':(\\\\d{2})' + // minutes offset capture\n                    ')' +\n                ')?)?)?)?' +\n            '$');\n\n            var months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];\n\n            var dayFromMonth = function dayFromMonth(year, month) {\n                var t = month > 1 ? 1 : 0;\n                return (\n                    months[month] +\n                    Math.floor((year - 1969 + t) / 4) -\n                    Math.floor((year - 1901 + t) / 100) +\n                    Math.floor((year - 1601 + t) / 400) +\n                    365 * (year - 1970)\n                );\n            };\n\n            var toUTC = function toUTC(t) {\n                var s = 0;\n                var ms = t;\n                if (hasSafariSignedIntBug && ms > maxSafeUnsigned32Bit) {\n                    // work around a Safari 8/9 bug where it treats the seconds as signed\n                    var msToShift = Math.floor(ms / maxSafeUnsigned32Bit) * maxSafeUnsigned32Bit;\n                    var sToShift = Math.floor(msToShift / 1e3);\n                    s += sToShift;\n                    ms -= sToShift * 1e3;\n                }\n                return $Number(new NativeDate(1970, 0, 1, 0, 0, s, ms));\n            };\n\n            // Copy any custom methods a 3rd party library may have added\n            for (var key in NativeDate) {\n                if (owns(NativeDate, key)) {\n                    DateShim[key] = NativeDate[key];\n                }\n            }\n\n            // Copy \"native\" methods explicitly; they may be non-enumerable\n            defineProperties(DateShim, {\n                now: NativeDate.now,\n                UTC: NativeDate.UTC\n            }, true);\n            DateShim.prototype = NativeDate.prototype;\n            defineProperties(DateShim.prototype, {\n                constructor: DateShim\n            }, true);\n\n            // Upgrade Date.parse to handle simplified ISO 8601 strings\n            var parseShim = function parse(string) {\n                var match = isoDateExpression.exec(string);\n                if (match) {\n                    // parse months, days, hours, minutes, seconds, and milliseconds\n                    // provide default values if necessary\n                    // parse the UTC offset component\n                    var year = $Number(match[1]),\n                        month = $Number(match[2] || 1) - 1,\n                        day = $Number(match[3] || 1) - 1,\n                        hour = $Number(match[4] || 0),\n                        minute = $Number(match[5] || 0),\n                        second = $Number(match[6] || 0),\n                        millisecond = Math.floor($Number(match[7] || 0) * 1000),\n                        // When time zone is missed, local offset should be used\n                        // (ES 5.1 bug)\n                        // see https://bugs.ecmascript.org/show_bug.cgi?id=112\n                        isLocalTime = Boolean(match[4] && !match[8]),\n                        signOffset = match[9] === '-' ? 1 : -1,\n                        hourOffset = $Number(match[10] || 0),\n                        minuteOffset = $Number(match[11] || 0),\n                        result;\n                    var hasMinutesOrSecondsOrMilliseconds = minute > 0 || second > 0 || millisecond > 0;\n                    if (\n                        hour < (hasMinutesOrSecondsOrMilliseconds ? 24 : 25) &&\n                        minute < 60 && second < 60 && millisecond < 1000 &&\n                        month > -1 && month < 12 && hourOffset < 24 &&\n                        minuteOffset < 60 && // detect invalid offsets\n                        day > -1 &&\n                        day < (dayFromMonth(year, month + 1) - dayFromMonth(year, month))\n                    ) {\n                        result = (\n                            (dayFromMonth(year, month) + day) * 24 +\n                            hour +\n                            hourOffset * signOffset\n                        ) * 60;\n                        result = (\n                            (result + minute + minuteOffset * signOffset) * 60 +\n                            second\n                        ) * 1000 + millisecond;\n                        if (isLocalTime) {\n                            result = toUTC(result);\n                        }\n                        if (-8.64e15 <= result && result <= 8.64e15) {\n                            return result;\n                        }\n                    }\n                    return NaN;\n                }\n                return NativeDate.parse.apply(this, arguments);\n            };\n            defineProperties(DateShim, { parse: parseShim });\n\n            return DateShim;\n        }(Date));\n        /* global Date: false */\n    }\n\n    // ES5 15.9.4.4\n    // http://es5.github.com/#x15.9.4.4\n    if (!Date.now) {\n        Date.now = function now() {\n            return new Date().getTime();\n        };\n    }\n\n    //\n    // Number\n    // ======\n    //\n\n    // ES5.1 15.7.4.5\n    // http://es5.github.com/#x15.7.4.5\n    var hasToFixedBugs = NumberPrototype.toFixed && (\n      (0.00008).toFixed(3) !== '0.000' ||\n      (0.9).toFixed(0) !== '1' ||\n      (1.255).toFixed(2) !== '1.25' ||\n      (1000000000000000128).toFixed(0) !== '1000000000000000128'\n    );\n\n    var toFixedHelpers = {\n        base: 1e7,\n        size: 6,\n        data: [0, 0, 0, 0, 0, 0],\n        multiply: function multiply(n, c) {\n            var i = -1;\n            var c2 = c;\n            while (++i < toFixedHelpers.size) {\n                c2 += n * toFixedHelpers.data[i];\n                toFixedHelpers.data[i] = c2 % toFixedHelpers.base;\n                c2 = Math.floor(c2 / toFixedHelpers.base);\n            }\n        },\n        divide: function divide(n) {\n            var i = toFixedHelpers.size;\n            var c = 0;\n            while (--i >= 0) {\n                c += toFixedHelpers.data[i];\n                toFixedHelpers.data[i] = Math.floor(c / n);\n                c = (c % n) * toFixedHelpers.base;\n            }\n        },\n        numToString: function numToString() {\n            var i = toFixedHelpers.size;\n            var s = '';\n            while (--i >= 0) {\n                if (s !== '' || i === 0 || toFixedHelpers.data[i] !== 0) {\n                    var t = $String(toFixedHelpers.data[i]);\n                    if (s === '') {\n                        s = t;\n                    } else {\n                        s += strSlice('0000000', 0, 7 - t.length) + t;\n                    }\n                }\n            }\n            return s;\n        },\n        pow: function pow(x, n, acc) {\n            return (n === 0 ? acc : (n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc)));\n        },\n        log: function log(x) {\n            var n = 0;\n            var x2 = x;\n            while (x2 >= 4096) {\n                n += 12;\n                x2 /= 4096;\n            }\n            while (x2 >= 2) {\n                n += 1;\n                x2 /= 2;\n            }\n            return n;\n        }\n    };\n\n    var toFixedShim = function toFixed(fractionDigits) {\n        var f, x, s, m, e, z, j, k;\n\n        // Test for NaN and round fractionDigits down\n        f = $Number(fractionDigits);\n        f = isActualNaN(f) ? 0 : Math.floor(f);\n\n        if (f < 0 || f > 20) {\n            throw new RangeError('Number.toFixed called with invalid number of decimals');\n        }\n\n        x = $Number(this);\n\n        if (isActualNaN(x)) {\n            return 'NaN';\n        }\n\n        // If it is too big or small, return the string value of the number\n        if (x <= -1e21 || x >= 1e21) {\n            return $String(x);\n        }\n\n        s = '';\n\n        if (x < 0) {\n            s = '-';\n            x = -x;\n        }\n\n        m = '0';\n\n        if (x > 1e-21) {\n            // 1e-21 < x < 1e21\n            // -70 < log2(x) < 70\n            e = toFixedHelpers.log(x * toFixedHelpers.pow(2, 69, 1)) - 69;\n            z = (e < 0 ? x * toFixedHelpers.pow(2, -e, 1) : x / toFixedHelpers.pow(2, e, 1));\n            z *= 0x10000000000000; // Math.pow(2, 52);\n            e = 52 - e;\n\n            // -18 < e < 122\n            // x = z / 2 ^ e\n            if (e > 0) {\n                toFixedHelpers.multiply(0, z);\n                j = f;\n\n                while (j >= 7) {\n                    toFixedHelpers.multiply(1e7, 0);\n                    j -= 7;\n                }\n\n                toFixedHelpers.multiply(toFixedHelpers.pow(10, j, 1), 0);\n                j = e - 1;\n\n                while (j >= 23) {\n                    toFixedHelpers.divide(1 << 23);\n                    j -= 23;\n                }\n\n                toFixedHelpers.divide(1 << j);\n                toFixedHelpers.multiply(1, 1);\n                toFixedHelpers.divide(2);\n                m = toFixedHelpers.numToString();\n            } else {\n                toFixedHelpers.multiply(0, z);\n                toFixedHelpers.multiply(1 << (-e), 0);\n                m = toFixedHelpers.numToString() + strSlice('0.00000000000000000000', 2, 2 + f);\n            }\n        }\n\n        if (f > 0) {\n            k = m.length;\n\n            if (k <= f) {\n                m = s + strSlice('0.0000000000000000000', 0, f - k + 2) + m;\n            } else {\n                m = s + strSlice(m, 0, k - f) + '.' + strSlice(m, k - f);\n            }\n        } else {\n            m = s + m;\n        }\n\n        return m;\n    };\n    defineProperties(NumberPrototype, { toFixed: toFixedShim }, hasToFixedBugs);\n\n    var hasToPrecisionUndefinedBug = (function () {\n        try {\n            return 1.0.toPrecision(undefined) === '1';\n        } catch (e) {\n            return true;\n        }\n    }());\n    var originalToPrecision = NumberPrototype.toPrecision;\n    defineProperties(NumberPrototype, {\n        toPrecision: function toPrecision(precision) {\n            return typeof precision === 'undefined' ? originalToPrecision.call(this) : originalToPrecision.call(this, precision);\n        }\n    }, hasToPrecisionUndefinedBug);\n\n    //\n    // String\n    // ======\n    //\n\n    // ES5 15.5.4.14\n    // http://es5.github.com/#x15.5.4.14\n\n    // [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers]\n    // Many browsers do not split properly with regular expressions or they\n    // do not perform the split correctly under obscure conditions.\n    // See http://blog.stevenlevithan.com/archives/cross-browser-split\n    // I've tested in many browsers and this seems to cover the deviant ones:\n    //    'ab'.split(/(?:ab)*/) should be [\"\", \"\"], not [\"\"]\n    //    '.'.split(/(.?)(.?)/) should be [\"\", \".\", \"\", \"\"], not [\"\", \"\"]\n    //    'tesst'.split(/(s)*/) should be [\"t\", undefined, \"e\", \"s\", \"t\"], not\n    //       [undefined, \"t\", undefined, \"e\", ...]\n    //    ''.split(/.?/) should be [], not [\"\"]\n    //    '.'.split(/()()/) should be [\".\"], not [\"\", \"\", \".\"]\n\n    if (\n        'ab'.split(/(?:ab)*/).length !== 2 ||\n        '.'.split(/(.?)(.?)/).length !== 4 ||\n        'tesst'.split(/(s)*/)[1] === 't' ||\n        'test'.split(/(?:)/, -1).length !== 4 ||\n        ''.split(/.?/).length ||\n        '.'.split(/()()/).length > 1\n    ) {\n        (function () {\n            var compliantExecNpcg = typeof (/()??/).exec('')[1] === 'undefined'; // NPCG: nonparticipating capturing group\n            var maxSafe32BitInt = Math.pow(2, 32) - 1;\n\n            StringPrototype.split = function (separator, limit) {\n                var string = String(this);\n                if (typeof separator === 'undefined' && limit === 0) {\n                    return [];\n                }\n\n                // If `separator` is not a regex, use native split\n                if (!isRegex(separator)) {\n                    return strSplit(this, separator, limit);\n                }\n\n                var output = [];\n                var flags = (separator.ignoreCase ? 'i' : '') +\n                            (separator.multiline ? 'm' : '') +\n                            (separator.unicode ? 'u' : '') + // in ES6\n                            (separator.sticky ? 'y' : ''), // Firefox 3+ and ES6\n                    lastLastIndex = 0,\n                    // Make `global` and avoid `lastIndex` issues by working with a copy\n                    separator2, match, lastIndex, lastLength;\n                var separatorCopy = new RegExp(separator.source, flags + 'g');\n                if (!compliantExecNpcg) {\n                    // Doesn't need flags gy, but they don't hurt\n                    separator2 = new RegExp('^' + separatorCopy.source + '$(?!\\\\s)', flags);\n                }\n                /* Values for `limit`, per the spec:\n                 * If undefined: 4294967295 // maxSafe32BitInt\n                 * If 0, Infinity, or NaN: 0\n                 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;\n                 * If negative number: 4294967296 - Math.floor(Math.abs(limit))\n                 * If other: Type-convert, then use the above rules\n                 */\n                var splitLimit = typeof limit === 'undefined' ? maxSafe32BitInt : ES.ToUint32(limit);\n                match = separatorCopy.exec(string);\n                while (match) {\n                    // `separatorCopy.lastIndex` is not reliable cross-browser\n                    lastIndex = match.index + match[0].length;\n                    if (lastIndex > lastLastIndex) {\n                        pushCall(output, strSlice(string, lastLastIndex, match.index));\n                        // Fix browsers whose `exec` methods don't consistently return `undefined` for\n                        // nonparticipating capturing groups\n                        if (!compliantExecNpcg && match.length > 1) {\n                            /* eslint-disable no-loop-func */\n                            match[0].replace(separator2, function () {\n                                for (var i = 1; i < arguments.length - 2; i++) {\n                                    if (typeof arguments[i] === 'undefined') {\n                                        match[i] = void 0;\n                                    }\n                                }\n                            });\n                            /* eslint-enable no-loop-func */\n                        }\n                        if (match.length > 1 && match.index < string.length) {\n                            array_push.apply(output, arraySlice(match, 1));\n                        }\n                        lastLength = match[0].length;\n                        lastLastIndex = lastIndex;\n                        if (output.length >= splitLimit) {\n                            break;\n                        }\n                    }\n                    if (separatorCopy.lastIndex === match.index) {\n                        separatorCopy.lastIndex++; // Avoid an infinite loop\n                    }\n                    match = separatorCopy.exec(string);\n                }\n                if (lastLastIndex === string.length) {\n                    if (lastLength || !separatorCopy.test('')) {\n                        pushCall(output, '');\n                    }\n                } else {\n                    pushCall(output, strSlice(string, lastLastIndex));\n                }\n                return output.length > splitLimit ? arraySlice(output, 0, splitLimit) : output;\n            };\n        }());\n\n    // [bugfix, chrome]\n    // If separator is undefined, then the result array contains just one String,\n    // which is the this value (converted to a String). If limit is not undefined,\n    // then the output array is truncated so that it contains no more than limit\n    // elements.\n    // \"0\".split(undefined, 0) -> []\n    } else if ('0'.split(void 0, 0).length) {\n        StringPrototype.split = function split(separator, limit) {\n            if (typeof separator === 'undefined' && limit === 0) {\n                return [];\n            }\n            return strSplit(this, separator, limit);\n        };\n    }\n\n    var str_replace = StringPrototype.replace;\n    var replaceReportsGroupsCorrectly = (function () {\n        var groups = [];\n        'x'.replace(/x(.)?/g, function (match, group) {\n            pushCall(groups, group);\n        });\n        return groups.length === 1 && typeof groups[0] === 'undefined';\n    }());\n\n    if (!replaceReportsGroupsCorrectly) {\n        StringPrototype.replace = function replace(searchValue, replaceValue) {\n            var isFn = isCallable(replaceValue);\n            var hasCapturingGroups = isRegex(searchValue) && (/\\)[*?]/).test(searchValue.source);\n            if (!isFn || !hasCapturingGroups) {\n                return str_replace.call(this, searchValue, replaceValue);\n            } else {\n                var wrappedReplaceValue = function (match) {\n                    var length = arguments.length;\n                    var originalLastIndex = searchValue.lastIndex;\n                    searchValue.lastIndex = 0;\n                    var args = searchValue.exec(match) || [];\n                    searchValue.lastIndex = originalLastIndex;\n                    pushCall(args, arguments[length - 2], arguments[length - 1]);\n                    return replaceValue.apply(this, args);\n                };\n                return str_replace.call(this, searchValue, wrappedReplaceValue);\n            }\n        };\n    }\n\n    // ECMA-262, 3rd B.2.3\n    // Not an ECMAScript standard, although ECMAScript 3rd Edition has a\n    // non-normative section suggesting uniform semantics and it should be\n    // normalized across all browsers\n    // [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE\n    var string_substr = StringPrototype.substr;\n    var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b';\n    defineProperties(StringPrototype, {\n        substr: function substr(start, length) {\n            var normalizedStart = start;\n            if (start < 0) {\n                normalizedStart = max(this.length + start, 0);\n            }\n            return string_substr.call(this, normalizedStart, length);\n        }\n    }, hasNegativeSubstrBug);\n\n    // ES5 15.5.4.20\n    // whitespace from: http://es5.github.io/#x15.5.4.20\n    var ws = '\\x09\\x0A\\x0B\\x0C\\x0D\\x20\\xA0\\u1680\\u180E\\u2000\\u2001\\u2002\\u2003' +\n        '\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000\\u2028' +\n        '\\u2029\\uFEFF';\n    var zeroWidth = '\\u200b';\n    var wsRegexChars = '[' + ws + ']';\n    var trimBeginRegexp = new RegExp('^' + wsRegexChars + wsRegexChars + '*');\n    var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + '*$');\n    var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim());\n    defineProperties(StringPrototype, {\n        // http://blog.stevenlevithan.com/archives/faster-trim-javascript\n        // http://perfectionkills.com/whitespace-deviations/\n        trim: function trim() {\n            if (typeof this === 'undefined' || this === null) {\n                throw new TypeError(\"can't convert \" + this + ' to object');\n            }\n            return $String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, '');\n        }\n    }, hasTrimWhitespaceBug);\n    var trim = call.bind(String.prototype.trim);\n\n    var hasLastIndexBug = StringPrototype.lastIndexOf && 'abcあい'.lastIndexOf('あい', 2) !== -1;\n    defineProperties(StringPrototype, {\n        lastIndexOf: function lastIndexOf(searchString) {\n            if (typeof this === 'undefined' || this === null) {\n                throw new TypeError(\"can't convert \" + this + ' to object');\n            }\n            var S = $String(this);\n            var searchStr = $String(searchString);\n            var numPos = arguments.length > 1 ? $Number(arguments[1]) : NaN;\n            var pos = isActualNaN(numPos) ? Infinity : ES.ToInteger(numPos);\n            var start = min(max(pos, 0), S.length);\n            var searchLen = searchStr.length;\n            var k = start + searchLen;\n            while (k > 0) {\n                k = max(0, k - searchLen);\n                var index = strIndexOf(strSlice(S, k, start + searchLen), searchStr);\n                if (index !== -1) {\n                    return k + index;\n                }\n            }\n            return -1;\n        }\n    }, hasLastIndexBug);\n\n    var originalLastIndexOf = StringPrototype.lastIndexOf;\n    defineProperties(StringPrototype, {\n        lastIndexOf: function lastIndexOf(searchString) {\n            return originalLastIndexOf.apply(this, arguments);\n        }\n    }, StringPrototype.lastIndexOf.length !== 1);\n\n    // ES-5 15.1.2.2\n    /* eslint-disable radix */\n    if (parseInt(ws + '08') !== 8 || parseInt(ws + '0x16') !== 22) {\n    /* eslint-enable radix */\n        /* global parseInt: true */\n        parseInt = (function (origParseInt) {\n            var hexRegex = /^[\\-+]?0[xX]/;\n            return function parseInt(str, radix) {\n                var string = trim(String(str));\n                var defaultedRadix = $Number(radix) || (hexRegex.test(string) ? 16 : 10);\n                return origParseInt(string, defaultedRadix);\n            };\n        }(parseInt));\n    }\n\n    // https://es5.github.io/#x15.1.2.3\n    if (1 / parseFloat('-0') !== -Infinity) {\n        /* global parseFloat: true */\n        parseFloat = (function (origParseFloat) {\n            return function parseFloat(string) {\n                var inputString = trim(String(string));\n                var result = origParseFloat(inputString);\n                return result === 0 && strSlice(inputString, 0, 1) === '-' ? -0 : result;\n            };\n        }(parseFloat));\n    }\n\n    if (String(new RangeError('test')) !== 'RangeError: test') {\n        var errorToStringShim = function toString() {\n            if (typeof this === 'undefined' || this === null) {\n                throw new TypeError(\"can't convert \" + this + ' to object');\n            }\n            var name = this.name;\n            if (typeof name === 'undefined') {\n                name = 'Error';\n            } else if (typeof name !== 'string') {\n                name = $String(name);\n            }\n            var msg = this.message;\n            if (typeof msg === 'undefined') {\n                msg = '';\n            } else if (typeof msg !== 'string') {\n                msg = $String(msg);\n            }\n            if (!name) {\n                return msg;\n            }\n            if (!msg) {\n                return name;\n            }\n            return name + ': ' + msg;\n        };\n        // can't use defineProperties here because of toString enumeration issue in IE <= 8\n        Error.prototype.toString = errorToStringShim;\n    }\n\n    if (supportsDescriptors) {\n        var ensureNonEnumerable = function (obj, prop) {\n            if (isEnum(obj, prop)) {\n                var desc = Object.getOwnPropertyDescriptor(obj, prop);\n                if (desc.configurable) {\n                    desc.enumerable = false;\n                    Object.defineProperty(obj, prop, desc);\n                }\n            }\n        };\n        ensureNonEnumerable(Error.prototype, 'message');\n        if (Error.prototype.message !== '') {\n            Error.prototype.message = '';\n        }\n        ensureNonEnumerable(Error.prototype, 'name');\n    }\n\n    if (String(/a/mig) !== '/a/gim') {\n        var regexToString = function toString() {\n            var str = '/' + this.source + '/';\n            if (this.global) {\n                str += 'g';\n            }\n            if (this.ignoreCase) {\n                str += 'i';\n            }\n            if (this.multiline) {\n                str += 'm';\n            }\n            return str;\n        };\n        // can't use defineProperties here because of toString enumeration issue in IE <= 8\n        RegExp.prototype.toString = regexToString;\n    }\n}));\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lib/handlebars-4.0.5.js",
    "content": "/*!\n\n handlebars v4.0.5\n\nCopyright (C) 2011-2015 by Yehuda Katz\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n@license\n*/\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"Handlebars\"] = factory();\n\telse\n\t\troot[\"Handlebars\"] = factory();\n})(this, function() {\nreturn /******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId])\n/******/ \t\t\treturn installedModules[moduleId].exports;\n\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\n/******/ \t\t};\n\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.loaded = true;\n\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n\n\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(0);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\n\tvar _handlebarsRuntime = __webpack_require__(2);\n\n\tvar _handlebarsRuntime2 = _interopRequireDefault(_handlebarsRuntime);\n\n\t// Compiler imports\n\n\tvar _handlebarsCompilerAst = __webpack_require__(21);\n\n\tvar _handlebarsCompilerAst2 = _interopRequireDefault(_handlebarsCompilerAst);\n\n\tvar _handlebarsCompilerBase = __webpack_require__(22);\n\n\tvar _handlebarsCompilerCompiler = __webpack_require__(27);\n\n\tvar _handlebarsCompilerJavascriptCompiler = __webpack_require__(28);\n\n\tvar _handlebarsCompilerJavascriptCompiler2 = _interopRequireDefault(_handlebarsCompilerJavascriptCompiler);\n\n\tvar _handlebarsCompilerVisitor = __webpack_require__(25);\n\n\tvar _handlebarsCompilerVisitor2 = _interopRequireDefault(_handlebarsCompilerVisitor);\n\n\tvar _handlebarsNoConflict = __webpack_require__(20);\n\n\tvar _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict);\n\n\tvar _create = _handlebarsRuntime2['default'].create;\n\tfunction create() {\n\t  var hb = _create();\n\n\t  hb.compile = function (input, options) {\n\t    return _handlebarsCompilerCompiler.compile(input, options, hb);\n\t  };\n\t  hb.precompile = function (input, options) {\n\t    return _handlebarsCompilerCompiler.precompile(input, options, hb);\n\t  };\n\n\t  hb.AST = _handlebarsCompilerAst2['default'];\n\t  hb.Compiler = _handlebarsCompilerCompiler.Compiler;\n\t  hb.JavaScriptCompiler = _handlebarsCompilerJavascriptCompiler2['default'];\n\t  hb.Parser = _handlebarsCompilerBase.parser;\n\t  hb.parse = _handlebarsCompilerBase.parse;\n\n\t  return hb;\n\t}\n\n\tvar inst = create();\n\tinst.create = create;\n\n\t_handlebarsNoConflict2['default'](inst);\n\n\tinst.Visitor = _handlebarsCompilerVisitor2['default'];\n\n\tinst['default'] = inst;\n\n\texports['default'] = inst;\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 1 */\n/***/ function(module, exports) {\n\n\t\"use strict\";\n\n\texports[\"default\"] = function (obj) {\n\t  return obj && obj.__esModule ? obj : {\n\t    \"default\": obj\n\t  };\n\t};\n\n\texports.__esModule = true;\n\n/***/ },\n/* 2 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireWildcard = __webpack_require__(3)['default'];\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\n\tvar _handlebarsBase = __webpack_require__(4);\n\n\tvar base = _interopRequireWildcard(_handlebarsBase);\n\n\t// Each of these augment the Handlebars object. No need to setup here.\n\t// (This is done to easily share code between commonjs and browse envs)\n\n\tvar _handlebarsSafeString = __webpack_require__(18);\n\n\tvar _handlebarsSafeString2 = _interopRequireDefault(_handlebarsSafeString);\n\n\tvar _handlebarsException = __webpack_require__(6);\n\n\tvar _handlebarsException2 = _interopRequireDefault(_handlebarsException);\n\n\tvar _handlebarsUtils = __webpack_require__(5);\n\n\tvar Utils = _interopRequireWildcard(_handlebarsUtils);\n\n\tvar _handlebarsRuntime = __webpack_require__(19);\n\n\tvar runtime = _interopRequireWildcard(_handlebarsRuntime);\n\n\tvar _handlebarsNoConflict = __webpack_require__(20);\n\n\tvar _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict);\n\n\t// For compatibility and usage outside of module systems, make the Handlebars object a namespace\n\tfunction create() {\n\t  var hb = new base.HandlebarsEnvironment();\n\n\t  Utils.extend(hb, base);\n\t  hb.SafeString = _handlebarsSafeString2['default'];\n\t  hb.Exception = _handlebarsException2['default'];\n\t  hb.Utils = Utils;\n\t  hb.escapeExpression = Utils.escapeExpression;\n\n\t  hb.VM = runtime;\n\t  hb.template = function (spec) {\n\t    return runtime.template(spec, hb);\n\t  };\n\n\t  return hb;\n\t}\n\n\tvar inst = create();\n\tinst.create = create;\n\n\t_handlebarsNoConflict2['default'](inst);\n\n\tinst['default'] = inst;\n\n\texports['default'] = inst;\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 3 */\n/***/ function(module, exports) {\n\n\t\"use strict\";\n\n\texports[\"default\"] = function (obj) {\n\t  if (obj && obj.__esModule) {\n\t    return obj;\n\t  } else {\n\t    var newObj = {};\n\n\t    if (obj != null) {\n\t      for (var key in obj) {\n\t        if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];\n\t      }\n\t    }\n\n\t    newObj[\"default\"] = obj;\n\t    return newObj;\n\t  }\n\t};\n\n\texports.__esModule = true;\n\n/***/ },\n/* 4 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\texports.HandlebarsEnvironment = HandlebarsEnvironment;\n\n\tvar _utils = __webpack_require__(5);\n\n\tvar _exception = __webpack_require__(6);\n\n\tvar _exception2 = _interopRequireDefault(_exception);\n\n\tvar _helpers = __webpack_require__(7);\n\n\tvar _decorators = __webpack_require__(15);\n\n\tvar _logger = __webpack_require__(17);\n\n\tvar _logger2 = _interopRequireDefault(_logger);\n\n\tvar VERSION = '4.0.5';\n\texports.VERSION = VERSION;\n\tvar COMPILER_REVISION = 7;\n\n\texports.COMPILER_REVISION = COMPILER_REVISION;\n\tvar REVISION_CHANGES = {\n\t  1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it\n\t  2: '== 1.0.0-rc.3',\n\t  3: '== 1.0.0-rc.4',\n\t  4: '== 1.x.x',\n\t  5: '== 2.0.0-alpha.x',\n\t  6: '>= 2.0.0-beta.1',\n\t  7: '>= 4.0.0'\n\t};\n\n\texports.REVISION_CHANGES = REVISION_CHANGES;\n\tvar objectType = '[object Object]';\n\n\tfunction HandlebarsEnvironment(helpers, partials, decorators) {\n\t  this.helpers = helpers || {};\n\t  this.partials = partials || {};\n\t  this.decorators = decorators || {};\n\n\t  _helpers.registerDefaultHelpers(this);\n\t  _decorators.registerDefaultDecorators(this);\n\t}\n\n\tHandlebarsEnvironment.prototype = {\n\t  constructor: HandlebarsEnvironment,\n\n\t  logger: _logger2['default'],\n\t  log: _logger2['default'].log,\n\n\t  registerHelper: function registerHelper(name, fn) {\n\t    if (_utils.toString.call(name) === objectType) {\n\t      if (fn) {\n\t        throw new _exception2['default']('Arg not supported with multiple helpers');\n\t      }\n\t      _utils.extend(this.helpers, name);\n\t    } else {\n\t      this.helpers[name] = fn;\n\t    }\n\t  },\n\t  unregisterHelper: function unregisterHelper(name) {\n\t    delete this.helpers[name];\n\t  },\n\n\t  registerPartial: function registerPartial(name, partial) {\n\t    if (_utils.toString.call(name) === objectType) {\n\t      _utils.extend(this.partials, name);\n\t    } else {\n\t      if (typeof partial === 'undefined') {\n\t        throw new _exception2['default']('Attempting to register a partial called \"' + name + '\" as undefined');\n\t      }\n\t      this.partials[name] = partial;\n\t    }\n\t  },\n\t  unregisterPartial: function unregisterPartial(name) {\n\t    delete this.partials[name];\n\t  },\n\n\t  registerDecorator: function registerDecorator(name, fn) {\n\t    if (_utils.toString.call(name) === objectType) {\n\t      if (fn) {\n\t        throw new _exception2['default']('Arg not supported with multiple decorators');\n\t      }\n\t      _utils.extend(this.decorators, name);\n\t    } else {\n\t      this.decorators[name] = fn;\n\t    }\n\t  },\n\t  unregisterDecorator: function unregisterDecorator(name) {\n\t    delete this.decorators[name];\n\t  }\n\t};\n\n\tvar log = _logger2['default'].log;\n\n\texports.log = log;\n\texports.createFrame = _utils.createFrame;\n\texports.logger = _logger2['default'];\n\n/***/ },\n/* 5 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\texports.extend = extend;\n\texports.indexOf = indexOf;\n\texports.escapeExpression = escapeExpression;\n\texports.isEmpty = isEmpty;\n\texports.createFrame = createFrame;\n\texports.blockParams = blockParams;\n\texports.appendContextPath = appendContextPath;\n\tvar escape = {\n\t  '&': '&amp;',\n\t  '<': '&lt;',\n\t  '>': '&gt;',\n\t  '\"': '&quot;',\n\t  \"'\": '&#x27;',\n\t  '`': '&#x60;',\n\t  '=': '&#x3D;'\n\t};\n\n\tvar badChars = /[&<>\"'`=]/g,\n\t    possible = /[&<>\"'`=]/;\n\n\tfunction escapeChar(chr) {\n\t  return escape[chr];\n\t}\n\n\tfunction extend(obj /* , ...source */) {\n\t  for (var i = 1; i < arguments.length; i++) {\n\t    for (var key in arguments[i]) {\n\t      if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {\n\t        obj[key] = arguments[i][key];\n\t      }\n\t    }\n\t  }\n\n\t  return obj;\n\t}\n\n\tvar toString = Object.prototype.toString;\n\n\texports.toString = toString;\n\t// Sourced from lodash\n\t// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt\n\t/* eslint-disable func-style */\n\tvar isFunction = function isFunction(value) {\n\t  return typeof value === 'function';\n\t};\n\t// fallback for older versions of Chrome and Safari\n\t/* istanbul ignore next */\n\tif (isFunction(/x/)) {\n\t  exports.isFunction = isFunction = function (value) {\n\t    return typeof value === 'function' && toString.call(value) === '[object Function]';\n\t  };\n\t}\n\texports.isFunction = isFunction;\n\n\t/* eslint-enable func-style */\n\n\t/* istanbul ignore next */\n\tvar isArray = Array.isArray || function (value) {\n\t  return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false;\n\t};\n\n\texports.isArray = isArray;\n\t// Older IE versions do not directly support indexOf so we must implement our own, sadly.\n\n\tfunction indexOf(array, value) {\n\t  for (var i = 0, len = array.length; i < len; i++) {\n\t    if (array[i] === value) {\n\t      return i;\n\t    }\n\t  }\n\t  return -1;\n\t}\n\n\tfunction escapeExpression(string) {\n\t  if (typeof string !== 'string') {\n\t    // don't escape SafeStrings, since they're already safe\n\t    if (string && string.toHTML) {\n\t      return string.toHTML();\n\t    } else if (string == null) {\n\t      return '';\n\t    } else if (!string) {\n\t      return string + '';\n\t    }\n\n\t    // Force a string conversion as this will be done by the append regardless and\n\t    // the regex test will do this transparently behind the scenes, causing issues if\n\t    // an object's to string has escaped characters in it.\n\t    string = '' + string;\n\t  }\n\n\t  if (!possible.test(string)) {\n\t    return string;\n\t  }\n\t  return string.replace(badChars, escapeChar);\n\t}\n\n\tfunction isEmpty(value) {\n\t  if (!value && value !== 0) {\n\t    return true;\n\t  } else if (isArray(value) && value.length === 0) {\n\t    return true;\n\t  } else {\n\t    return false;\n\t  }\n\t}\n\n\tfunction createFrame(object) {\n\t  var frame = extend({}, object);\n\t  frame._parent = object;\n\t  return frame;\n\t}\n\n\tfunction blockParams(params, ids) {\n\t  params.path = ids;\n\t  return params;\n\t}\n\n\tfunction appendContextPath(contextPath, id) {\n\t  return (contextPath ? contextPath + '.' : '') + id;\n\t}\n\n/***/ },\n/* 6 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\n\tvar errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];\n\n\tfunction Exception(message, node) {\n\t  var loc = node && node.loc,\n\t      line = undefined,\n\t      column = undefined;\n\t  if (loc) {\n\t    line = loc.start.line;\n\t    column = loc.start.column;\n\n\t    message += ' - ' + line + ':' + column;\n\t  }\n\n\t  var tmp = Error.prototype.constructor.call(this, message);\n\n\t  // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.\n\t  for (var idx = 0; idx < errorProps.length; idx++) {\n\t    this[errorProps[idx]] = tmp[errorProps[idx]];\n\t  }\n\n\t  /* istanbul ignore else */\n\t  if (Error.captureStackTrace) {\n\t    Error.captureStackTrace(this, Exception);\n\t  }\n\n\t  if (loc) {\n\t    this.lineNumber = line;\n\t    this.column = column;\n\t  }\n\t}\n\n\tException.prototype = new Error();\n\n\texports['default'] = Exception;\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 7 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\texports.registerDefaultHelpers = registerDefaultHelpers;\n\n\tvar _helpersBlockHelperMissing = __webpack_require__(8);\n\n\tvar _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing);\n\n\tvar _helpersEach = __webpack_require__(9);\n\n\tvar _helpersEach2 = _interopRequireDefault(_helpersEach);\n\n\tvar _helpersHelperMissing = __webpack_require__(10);\n\n\tvar _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing);\n\n\tvar _helpersIf = __webpack_require__(11);\n\n\tvar _helpersIf2 = _interopRequireDefault(_helpersIf);\n\n\tvar _helpersLog = __webpack_require__(12);\n\n\tvar _helpersLog2 = _interopRequireDefault(_helpersLog);\n\n\tvar _helpersLookup = __webpack_require__(13);\n\n\tvar _helpersLookup2 = _interopRequireDefault(_helpersLookup);\n\n\tvar _helpersWith = __webpack_require__(14);\n\n\tvar _helpersWith2 = _interopRequireDefault(_helpersWith);\n\n\tfunction registerDefaultHelpers(instance) {\n\t  _helpersBlockHelperMissing2['default'](instance);\n\t  _helpersEach2['default'](instance);\n\t  _helpersHelperMissing2['default'](instance);\n\t  _helpersIf2['default'](instance);\n\t  _helpersLog2['default'](instance);\n\t  _helpersLookup2['default'](instance);\n\t  _helpersWith2['default'](instance);\n\t}\n\n/***/ },\n/* 8 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\n\tvar _utils = __webpack_require__(5);\n\n\texports['default'] = function (instance) {\n\t  instance.registerHelper('blockHelperMissing', function (context, options) {\n\t    var inverse = options.inverse,\n\t        fn = options.fn;\n\n\t    if (context === true) {\n\t      return fn(this);\n\t    } else if (context === false || context == null) {\n\t      return inverse(this);\n\t    } else if (_utils.isArray(context)) {\n\t      if (context.length > 0) {\n\t        if (options.ids) {\n\t          options.ids = [options.name];\n\t        }\n\n\t        return instance.helpers.each(context, options);\n\t      } else {\n\t        return inverse(this);\n\t      }\n\t    } else {\n\t      if (options.data && options.ids) {\n\t        var data = _utils.createFrame(options.data);\n\t        data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name);\n\t        options = { data: data };\n\t      }\n\n\t      return fn(context, options);\n\t    }\n\t  });\n\t};\n\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 9 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\n\tvar _utils = __webpack_require__(5);\n\n\tvar _exception = __webpack_require__(6);\n\n\tvar _exception2 = _interopRequireDefault(_exception);\n\n\texports['default'] = function (instance) {\n\t  instance.registerHelper('each', function (context, options) {\n\t    if (!options) {\n\t      throw new _exception2['default']('Must pass iterator to #each');\n\t    }\n\n\t    var fn = options.fn,\n\t        inverse = options.inverse,\n\t        i = 0,\n\t        ret = '',\n\t        data = undefined,\n\t        contextPath = undefined;\n\n\t    if (options.data && options.ids) {\n\t      contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';\n\t    }\n\n\t    if (_utils.isFunction(context)) {\n\t      context = context.call(this);\n\t    }\n\n\t    if (options.data) {\n\t      data = _utils.createFrame(options.data);\n\t    }\n\n\t    function execIteration(field, index, last) {\n\t      if (data) {\n\t        data.key = field;\n\t        data.index = index;\n\t        data.first = index === 0;\n\t        data.last = !!last;\n\n\t        if (contextPath) {\n\t          data.contextPath = contextPath + field;\n\t        }\n\t      }\n\n\t      ret = ret + fn(context[field], {\n\t        data: data,\n\t        blockParams: _utils.blockParams([context[field], field], [contextPath + field, null])\n\t      });\n\t    }\n\n\t    if (context && typeof context === 'object') {\n\t      if (_utils.isArray(context)) {\n\t        for (var j = context.length; i < j; i++) {\n\t          if (i in context) {\n\t            execIteration(i, i, i === context.length - 1);\n\t          }\n\t        }\n\t      } else {\n\t        var priorKey = undefined;\n\n\t        for (var key in context) {\n\t          if (context.hasOwnProperty(key)) {\n\t            // We're running the iterations one step out of sync so we can detect\n\t            // the last iteration without have to scan the object twice and create\n\t            // an itermediate keys array.\n\t            if (priorKey !== undefined) {\n\t              execIteration(priorKey, i - 1);\n\t            }\n\t            priorKey = key;\n\t            i++;\n\t          }\n\t        }\n\t        if (priorKey !== undefined) {\n\t          execIteration(priorKey, i - 1, true);\n\t        }\n\t      }\n\t    }\n\n\t    if (i === 0) {\n\t      ret = inverse(this);\n\t    }\n\n\t    return ret;\n\t  });\n\t};\n\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 10 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\n\tvar _exception = __webpack_require__(6);\n\n\tvar _exception2 = _interopRequireDefault(_exception);\n\n\texports['default'] = function (instance) {\n\t  instance.registerHelper('helperMissing', function () /* [args, ]options */{\n\t    if (arguments.length === 1) {\n\t      // A missing field in a {{foo}} construct.\n\t      return undefined;\n\t    } else {\n\t      // Someone is actually trying to call something, blow up.\n\t      throw new _exception2['default']('Missing helper: \"' + arguments[arguments.length - 1].name + '\"');\n\t    }\n\t  });\n\t};\n\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 11 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\n\tvar _utils = __webpack_require__(5);\n\n\texports['default'] = function (instance) {\n\t  instance.registerHelper('if', function (conditional, options) {\n\t    if (_utils.isFunction(conditional)) {\n\t      conditional = conditional.call(this);\n\t    }\n\n\t    // Default behavior is to render the positive path if the value is truthy and not empty.\n\t    // The `includeZero` option may be set to treat the condtional as purely not empty based on the\n\t    // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.\n\t    if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) {\n\t      return options.inverse(this);\n\t    } else {\n\t      return options.fn(this);\n\t    }\n\t  });\n\n\t  instance.registerHelper('unless', function (conditional, options) {\n\t    return instance.helpers['if'].call(this, conditional, { fn: options.inverse, inverse: options.fn, hash: options.hash });\n\t  });\n\t};\n\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 12 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\n\texports['default'] = function (instance) {\n\t  instance.registerHelper('log', function () /* message, options */{\n\t    var args = [undefined],\n\t        options = arguments[arguments.length - 1];\n\t    for (var i = 0; i < arguments.length - 1; i++) {\n\t      args.push(arguments[i]);\n\t    }\n\n\t    var level = 1;\n\t    if (options.hash.level != null) {\n\t      level = options.hash.level;\n\t    } else if (options.data && options.data.level != null) {\n\t      level = options.data.level;\n\t    }\n\t    args[0] = level;\n\n\t    instance.log.apply(instance, args);\n\t  });\n\t};\n\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 13 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\n\texports['default'] = function (instance) {\n\t  instance.registerHelper('lookup', function (obj, field) {\n\t    return obj && obj[field];\n\t  });\n\t};\n\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 14 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\n\tvar _utils = __webpack_require__(5);\n\n\texports['default'] = function (instance) {\n\t  instance.registerHelper('with', function (context, options) {\n\t    if (_utils.isFunction(context)) {\n\t      context = context.call(this);\n\t    }\n\n\t    var fn = options.fn;\n\n\t    if (!_utils.isEmpty(context)) {\n\t      var data = options.data;\n\t      if (options.data && options.ids) {\n\t        data = _utils.createFrame(options.data);\n\t        data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]);\n\t      }\n\n\t      return fn(context, {\n\t        data: data,\n\t        blockParams: _utils.blockParams([context], [data && data.contextPath])\n\t      });\n\t    } else {\n\t      return options.inverse(this);\n\t    }\n\t  });\n\t};\n\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 15 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\texports.registerDefaultDecorators = registerDefaultDecorators;\n\n\tvar _decoratorsInline = __webpack_require__(16);\n\n\tvar _decoratorsInline2 = _interopRequireDefault(_decoratorsInline);\n\n\tfunction registerDefaultDecorators(instance) {\n\t  _decoratorsInline2['default'](instance);\n\t}\n\n/***/ },\n/* 16 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\n\tvar _utils = __webpack_require__(5);\n\n\texports['default'] = function (instance) {\n\t  instance.registerDecorator('inline', function (fn, props, container, options) {\n\t    var ret = fn;\n\t    if (!props.partials) {\n\t      props.partials = {};\n\t      ret = function (context, options) {\n\t        // Create a new partials stack frame prior to exec.\n\t        var original = container.partials;\n\t        container.partials = _utils.extend({}, original, props.partials);\n\t        var ret = fn(context, options);\n\t        container.partials = original;\n\t        return ret;\n\t      };\n\t    }\n\n\t    props.partials[options.args[0]] = options.fn;\n\n\t    return ret;\n\t  });\n\t};\n\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 17 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\n\tvar _utils = __webpack_require__(5);\n\n\tvar logger = {\n\t  methodMap: ['debug', 'info', 'warn', 'error'],\n\t  level: 'info',\n\n\t  // Maps a given level value to the `methodMap` indexes above.\n\t  lookupLevel: function lookupLevel(level) {\n\t    if (typeof level === 'string') {\n\t      var levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase());\n\t      if (levelMap >= 0) {\n\t        level = levelMap;\n\t      } else {\n\t        level = parseInt(level, 10);\n\t      }\n\t    }\n\n\t    return level;\n\t  },\n\n\t  // Can be overridden in the host environment\n\t  log: function log(level) {\n\t    level = logger.lookupLevel(level);\n\n\t    if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {\n\t      var method = logger.methodMap[level];\n\t      if (!console[method]) {\n\t        // eslint-disable-line no-console\n\t        method = 'log';\n\t      }\n\n\t      for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n\t        message[_key - 1] = arguments[_key];\n\t      }\n\n\t      console[method].apply(console, message); // eslint-disable-line no-console\n\t    }\n\t  }\n\t};\n\n\texports['default'] = logger;\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 18 */\n/***/ function(module, exports) {\n\n\t// Build out our basic SafeString type\n\t'use strict';\n\n\texports.__esModule = true;\n\tfunction SafeString(string) {\n\t  this.string = string;\n\t}\n\n\tSafeString.prototype.toString = SafeString.prototype.toHTML = function () {\n\t  return '' + this.string;\n\t};\n\n\texports['default'] = SafeString;\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 19 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireWildcard = __webpack_require__(3)['default'];\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\texports.checkRevision = checkRevision;\n\texports.template = template;\n\texports.wrapProgram = wrapProgram;\n\texports.resolvePartial = resolvePartial;\n\texports.invokePartial = invokePartial;\n\texports.noop = noop;\n\n\tvar _utils = __webpack_require__(5);\n\n\tvar Utils = _interopRequireWildcard(_utils);\n\n\tvar _exception = __webpack_require__(6);\n\n\tvar _exception2 = _interopRequireDefault(_exception);\n\n\tvar _base = __webpack_require__(4);\n\n\tfunction checkRevision(compilerInfo) {\n\t  var compilerRevision = compilerInfo && compilerInfo[0] || 1,\n\t      currentRevision = _base.COMPILER_REVISION;\n\n\t  if (compilerRevision !== currentRevision) {\n\t    if (compilerRevision < currentRevision) {\n\t      var runtimeVersions = _base.REVISION_CHANGES[currentRevision],\n\t          compilerVersions = _base.REVISION_CHANGES[compilerRevision];\n\t      throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');\n\t    } else {\n\t      // Use the embedded version info since the runtime doesn't know about this revision yet\n\t      throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').');\n\t    }\n\t  }\n\t}\n\n\tfunction template(templateSpec, env) {\n\t  /* istanbul ignore next */\n\t  if (!env) {\n\t    throw new _exception2['default']('No environment passed to template');\n\t  }\n\t  if (!templateSpec || !templateSpec.main) {\n\t    throw new _exception2['default']('Unknown template object: ' + typeof templateSpec);\n\t  }\n\n\t  templateSpec.main.decorator = templateSpec.main_d;\n\n\t  // Note: Using env.VM references rather than local var references throughout this section to allow\n\t  // for external users to override these as psuedo-supported APIs.\n\t  env.VM.checkRevision(templateSpec.compiler);\n\n\t  function invokePartialWrapper(partial, context, options) {\n\t    if (options.hash) {\n\t      context = Utils.extend({}, context, options.hash);\n\t      if (options.ids) {\n\t        options.ids[0] = true;\n\t      }\n\t    }\n\n\t    partial = env.VM.resolvePartial.call(this, partial, context, options);\n\t    var result = env.VM.invokePartial.call(this, partial, context, options);\n\n\t    if (result == null && env.compile) {\n\t      options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);\n\t      result = options.partials[options.name](context, options);\n\t    }\n\t    if (result != null) {\n\t      if (options.indent) {\n\t        var lines = result.split('\\n');\n\t        for (var i = 0, l = lines.length; i < l; i++) {\n\t          if (!lines[i] && i + 1 === l) {\n\t            break;\n\t          }\n\n\t          lines[i] = options.indent + lines[i];\n\t        }\n\t        result = lines.join('\\n');\n\t      }\n\t      return result;\n\t    } else {\n\t      throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');\n\t    }\n\t  }\n\n\t  // Just add water\n\t  var container = {\n\t    strict: function strict(obj, name) {\n\t      if (!(name in obj)) {\n\t        throw new _exception2['default']('\"' + name + '\" not defined in ' + obj);\n\t      }\n\t      return obj[name];\n\t    },\n\t    lookup: function lookup(depths, name) {\n\t      var len = depths.length;\n\t      for (var i = 0; i < len; i++) {\n\t        if (depths[i] && depths[i][name] != null) {\n\t          return depths[i][name];\n\t        }\n\t      }\n\t    },\n\t    lambda: function lambda(current, context) {\n\t      return typeof current === 'function' ? current.call(context) : current;\n\t    },\n\n\t    escapeExpression: Utils.escapeExpression,\n\t    invokePartial: invokePartialWrapper,\n\n\t    fn: function fn(i) {\n\t      var ret = templateSpec[i];\n\t      ret.decorator = templateSpec[i + '_d'];\n\t      return ret;\n\t    },\n\n\t    programs: [],\n\t    program: function program(i, data, declaredBlockParams, blockParams, depths) {\n\t      var programWrapper = this.programs[i],\n\t          fn = this.fn(i);\n\t      if (data || depths || blockParams || declaredBlockParams) {\n\t        programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);\n\t      } else if (!programWrapper) {\n\t        programWrapper = this.programs[i] = wrapProgram(this, i, fn);\n\t      }\n\t      return programWrapper;\n\t    },\n\n\t    data: function data(value, depth) {\n\t      while (value && depth--) {\n\t        value = value._parent;\n\t      }\n\t      return value;\n\t    },\n\t    merge: function merge(param, common) {\n\t      var obj = param || common;\n\n\t      if (param && common && param !== common) {\n\t        obj = Utils.extend({}, common, param);\n\t      }\n\n\t      return obj;\n\t    },\n\n\t    noop: env.VM.noop,\n\t    compilerInfo: templateSpec.compiler\n\t  };\n\n\t  function ret(context) {\n\t    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];\n\n\t    var data = options.data;\n\n\t    ret._setup(options);\n\t    if (!options.partial && templateSpec.useData) {\n\t      data = initData(context, data);\n\t    }\n\t    var depths = undefined,\n\t        blockParams = templateSpec.useBlockParams ? [] : undefined;\n\t    if (templateSpec.useDepths) {\n\t      if (options.depths) {\n\t        depths = context !== options.depths[0] ? [context].concat(options.depths) : options.depths;\n\t      } else {\n\t        depths = [context];\n\t      }\n\t    }\n\n\t    function main(context /*, options*/) {\n\t      return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);\n\t    }\n\t    main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);\n\t    return main(context, options);\n\t  }\n\t  ret.isTop = true;\n\n\t  ret._setup = function (options) {\n\t    if (!options.partial) {\n\t      container.helpers = container.merge(options.helpers, env.helpers);\n\n\t      if (templateSpec.usePartial) {\n\t        container.partials = container.merge(options.partials, env.partials);\n\t      }\n\t      if (templateSpec.usePartial || templateSpec.useDecorators) {\n\t        container.decorators = container.merge(options.decorators, env.decorators);\n\t      }\n\t    } else {\n\t      container.helpers = options.helpers;\n\t      container.partials = options.partials;\n\t      container.decorators = options.decorators;\n\t    }\n\t  };\n\n\t  ret._child = function (i, data, blockParams, depths) {\n\t    if (templateSpec.useBlockParams && !blockParams) {\n\t      throw new _exception2['default']('must pass block params');\n\t    }\n\t    if (templateSpec.useDepths && !depths) {\n\t      throw new _exception2['default']('must pass parent depths');\n\t    }\n\n\t    return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths);\n\t  };\n\t  return ret;\n\t}\n\n\tfunction wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {\n\t  function prog(context) {\n\t    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];\n\n\t    var currentDepths = depths;\n\t    if (depths && context !== depths[0]) {\n\t      currentDepths = [context].concat(depths);\n\t    }\n\n\t    return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths);\n\t  }\n\n\t  prog = executeDecorators(fn, prog, container, depths, data, blockParams);\n\n\t  prog.program = i;\n\t  prog.depth = depths ? depths.length : 0;\n\t  prog.blockParams = declaredBlockParams || 0;\n\t  return prog;\n\t}\n\n\tfunction resolvePartial(partial, context, options) {\n\t  if (!partial) {\n\t    if (options.name === '@partial-block') {\n\t      partial = options.data['partial-block'];\n\t    } else {\n\t      partial = options.partials[options.name];\n\t    }\n\t  } else if (!partial.call && !options.name) {\n\t    // This is a dynamic partial that returned a string\n\t    options.name = partial;\n\t    partial = options.partials[partial];\n\t  }\n\t  return partial;\n\t}\n\n\tfunction invokePartial(partial, context, options) {\n\t  options.partial = true;\n\t  if (options.ids) {\n\t    options.data.contextPath = options.ids[0] || options.data.contextPath;\n\t  }\n\n\t  var partialBlock = undefined;\n\t  if (options.fn && options.fn !== noop) {\n\t    options.data = _base.createFrame(options.data);\n\t    partialBlock = options.data['partial-block'] = options.fn;\n\n\t    if (partialBlock.partials) {\n\t      options.partials = Utils.extend({}, options.partials, partialBlock.partials);\n\t    }\n\t  }\n\n\t  if (partial === undefined && partialBlock) {\n\t    partial = partialBlock;\n\t  }\n\n\t  if (partial === undefined) {\n\t    throw new _exception2['default']('The partial ' + options.name + ' could not be found');\n\t  } else if (partial instanceof Function) {\n\t    return partial(context, options);\n\t  }\n\t}\n\n\tfunction noop() {\n\t  return '';\n\t}\n\n\tfunction initData(context, data) {\n\t  if (!data || !('root' in data)) {\n\t    data = data ? _base.createFrame(data) : {};\n\t    data.root = context;\n\t  }\n\t  return data;\n\t}\n\n\tfunction executeDecorators(fn, prog, container, depths, data, blockParams) {\n\t  if (fn.decorator) {\n\t    var props = {};\n\t    prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);\n\t    Utils.extend(prog, props);\n\t  }\n\t  return prog;\n\t}\n\n/***/ },\n/* 20 */\n/***/ function(module, exports) {\n\n\t/* WEBPACK VAR INJECTION */(function(global) {/* global window */\n\t'use strict';\n\n\texports.__esModule = true;\n\n\texports['default'] = function (Handlebars) {\n\t  /* istanbul ignore next */\n\t  var root = typeof global !== 'undefined' ? global : window,\n\t      $Handlebars = root.Handlebars;\n\t  /* istanbul ignore next */\n\t  Handlebars.noConflict = function () {\n\t    if (root.Handlebars === Handlebars) {\n\t      root.Handlebars = $Handlebars;\n\t    }\n\t    return Handlebars;\n\t  };\n\t};\n\n\tmodule.exports = exports['default'];\n\t/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))\n\n/***/ },\n/* 21 */\n/***/ function(module, exports) {\n\n\t'use strict';\n\n\texports.__esModule = true;\n\tvar AST = {\n\t  // Public API used to evaluate derived attributes regarding AST nodes\n\t  helpers: {\n\t    // a mustache is definitely a helper if:\n\t    // * it is an eligible helper, and\n\t    // * it has at least one parameter or hash segment\n\t    helperExpression: function helperExpression(node) {\n\t      return node.type === 'SubExpression' || (node.type === 'MustacheStatement' || node.type === 'BlockStatement') && !!(node.params && node.params.length || node.hash);\n\t    },\n\n\t    scopedId: function scopedId(path) {\n\t      return (/^\\.|this\\b/.test(path.original)\n\t      );\n\t    },\n\n\t    // an ID is simple if it only has one part, and that part is not\n\t    // `..` or `this`.\n\t    simpleId: function simpleId(path) {\n\t      return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;\n\t    }\n\t  }\n\t};\n\n\t// Must be exported as an object rather than the root of the module as the jison lexer\n\t// must modify the object to operate properly.\n\texports['default'] = AST;\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 22 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\tvar _interopRequireWildcard = __webpack_require__(3)['default'];\n\n\texports.__esModule = true;\n\texports.parse = parse;\n\n\tvar _parser = __webpack_require__(23);\n\n\tvar _parser2 = _interopRequireDefault(_parser);\n\n\tvar _whitespaceControl = __webpack_require__(24);\n\n\tvar _whitespaceControl2 = _interopRequireDefault(_whitespaceControl);\n\n\tvar _helpers = __webpack_require__(26);\n\n\tvar Helpers = _interopRequireWildcard(_helpers);\n\n\tvar _utils = __webpack_require__(5);\n\n\texports.parser = _parser2['default'];\n\n\tvar yy = {};\n\t_utils.extend(yy, Helpers);\n\n\tfunction parse(input, options) {\n\t  // Just return if an already-compiled AST was passed in.\n\t  if (input.type === 'Program') {\n\t    return input;\n\t  }\n\n\t  _parser2['default'].yy = yy;\n\n\t  // Altering the shared object here, but this is ok as parser is a sync operation\n\t  yy.locInfo = function (locInfo) {\n\t    return new yy.SourceLocation(options && options.srcName, locInfo);\n\t  };\n\n\t  var strip = new _whitespaceControl2['default'](options);\n\t  return strip.accept(_parser2['default'].parse(input));\n\t}\n\n/***/ },\n/* 23 */\n/***/ function(module, exports) {\n\n\t/* istanbul ignore next */\n\t/* Jison generated parser */\n\t\"use strict\";\n\n\tvar handlebars = (function () {\n\t    var parser = { trace: function trace() {},\n\t        yy: {},\n\t        symbols_: { \"error\": 2, \"root\": 3, \"program\": 4, \"EOF\": 5, \"program_repetition0\": 6, \"statement\": 7, \"mustache\": 8, \"block\": 9, \"rawBlock\": 10, \"partial\": 11, \"partialBlock\": 12, \"content\": 13, \"COMMENT\": 14, \"CONTENT\": 15, \"openRawBlock\": 16, \"rawBlock_repetition_plus0\": 17, \"END_RAW_BLOCK\": 18, \"OPEN_RAW_BLOCK\": 19, \"helperName\": 20, \"openRawBlock_repetition0\": 21, \"openRawBlock_option0\": 22, \"CLOSE_RAW_BLOCK\": 23, \"openBlock\": 24, \"block_option0\": 25, \"closeBlock\": 26, \"openInverse\": 27, \"block_option1\": 28, \"OPEN_BLOCK\": 29, \"openBlock_repetition0\": 30, \"openBlock_option0\": 31, \"openBlock_option1\": 32, \"CLOSE\": 33, \"OPEN_INVERSE\": 34, \"openInverse_repetition0\": 35, \"openInverse_option0\": 36, \"openInverse_option1\": 37, \"openInverseChain\": 38, \"OPEN_INVERSE_CHAIN\": 39, \"openInverseChain_repetition0\": 40, \"openInverseChain_option0\": 41, \"openInverseChain_option1\": 42, \"inverseAndProgram\": 43, \"INVERSE\": 44, \"inverseChain\": 45, \"inverseChain_option0\": 46, \"OPEN_ENDBLOCK\": 47, \"OPEN\": 48, \"mustache_repetition0\": 49, \"mustache_option0\": 50, \"OPEN_UNESCAPED\": 51, \"mustache_repetition1\": 52, \"mustache_option1\": 53, \"CLOSE_UNESCAPED\": 54, \"OPEN_PARTIAL\": 55, \"partialName\": 56, \"partial_repetition0\": 57, \"partial_option0\": 58, \"openPartialBlock\": 59, \"OPEN_PARTIAL_BLOCK\": 60, \"openPartialBlock_repetition0\": 61, \"openPartialBlock_option0\": 62, \"param\": 63, \"sexpr\": 64, \"OPEN_SEXPR\": 65, \"sexpr_repetition0\": 66, \"sexpr_option0\": 67, \"CLOSE_SEXPR\": 68, \"hash\": 69, \"hash_repetition_plus0\": 70, \"hashSegment\": 71, \"ID\": 72, \"EQUALS\": 73, \"blockParams\": 74, \"OPEN_BLOCK_PARAMS\": 75, \"blockParams_repetition_plus0\": 76, \"CLOSE_BLOCK_PARAMS\": 77, \"path\": 78, \"dataName\": 79, \"STRING\": 80, \"NUMBER\": 81, \"BOOLEAN\": 82, \"UNDEFINED\": 83, \"NULL\": 84, \"DATA\": 85, \"pathSegments\": 86, \"SEP\": 87, \"$accept\": 0, \"$end\": 1 },\n\t        terminals_: { 2: \"error\", 5: \"EOF\", 14: \"COMMENT\", 15: \"CONTENT\", 18: \"END_RAW_BLOCK\", 19: \"OPEN_RAW_BLOCK\", 23: \"CLOSE_RAW_BLOCK\", 29: \"OPEN_BLOCK\", 33: \"CLOSE\", 34: \"OPEN_INVERSE\", 39: \"OPEN_INVERSE_CHAIN\", 44: \"INVERSE\", 47: \"OPEN_ENDBLOCK\", 48: \"OPEN\", 51: \"OPEN_UNESCAPED\", 54: \"CLOSE_UNESCAPED\", 55: \"OPEN_PARTIAL\", 60: \"OPEN_PARTIAL_BLOCK\", 65: \"OPEN_SEXPR\", 68: \"CLOSE_SEXPR\", 72: \"ID\", 73: \"EQUALS\", 75: \"OPEN_BLOCK_PARAMS\", 77: \"CLOSE_BLOCK_PARAMS\", 80: \"STRING\", 81: \"NUMBER\", 82: \"BOOLEAN\", 83: \"UNDEFINED\", 84: \"NULL\", 85: \"DATA\", 87: \"SEP\" },\n\t        productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 5], [8, 5], [11, 5], [12, 3], [59, 5], [63, 1], [63, 1], [64, 5], [69, 1], [71, 3], [74, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [56, 1], [56, 1], [79, 2], [78, 1], [86, 3], [86, 1], [6, 0], [6, 2], [17, 1], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [49, 0], [49, 2], [50, 0], [50, 1], [52, 0], [52, 2], [53, 0], [53, 1], [57, 0], [57, 2], [58, 0], [58, 1], [61, 0], [61, 2], [62, 0], [62, 1], [66, 0], [66, 2], [67, 0], [67, 1], [70, 1], [70, 2], [76, 1], [76, 2]],\n\t        performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$\n\t        /**/) {\n\n\t            var $0 = $$.length - 1;\n\t            switch (yystate) {\n\t                case 1:\n\t                    return $$[$0 - 1];\n\t                    break;\n\t                case 2:\n\t                    this.$ = yy.prepareProgram($$[$0]);\n\t                    break;\n\t                case 3:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 4:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 5:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 6:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 7:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 8:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 9:\n\t                    this.$ = {\n\t                        type: 'CommentStatement',\n\t                        value: yy.stripComment($$[$0]),\n\t                        strip: yy.stripFlags($$[$0], $$[$0]),\n\t                        loc: yy.locInfo(this._$)\n\t                    };\n\n\t                    break;\n\t                case 10:\n\t                    this.$ = {\n\t                        type: 'ContentStatement',\n\t                        original: $$[$0],\n\t                        value: $$[$0],\n\t                        loc: yy.locInfo(this._$)\n\t                    };\n\n\t                    break;\n\t                case 11:\n\t                    this.$ = yy.prepareRawBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$);\n\t                    break;\n\t                case 12:\n\t                    this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1] };\n\t                    break;\n\t                case 13:\n\t                    this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], false, this._$);\n\t                    break;\n\t                case 14:\n\t                    this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], true, this._$);\n\t                    break;\n\t                case 15:\n\t                    this.$ = { open: $$[$0 - 5], path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) };\n\t                    break;\n\t                case 16:\n\t                    this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) };\n\t                    break;\n\t                case 17:\n\t                    this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) };\n\t                    break;\n\t                case 18:\n\t                    this.$ = { strip: yy.stripFlags($$[$0 - 1], $$[$0 - 1]), program: $$[$0] };\n\t                    break;\n\t                case 19:\n\t                    var inverse = yy.prepareBlock($$[$0 - 2], $$[$0 - 1], $$[$0], $$[$0], false, this._$),\n\t                        program = yy.prepareProgram([inverse], $$[$0 - 1].loc);\n\t                    program.chained = true;\n\n\t                    this.$ = { strip: $$[$0 - 2].strip, program: program, chain: true };\n\n\t                    break;\n\t                case 20:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 21:\n\t                    this.$ = { path: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 2], $$[$0]) };\n\t                    break;\n\t                case 22:\n\t                    this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$);\n\t                    break;\n\t                case 23:\n\t                    this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$);\n\t                    break;\n\t                case 24:\n\t                    this.$ = {\n\t                        type: 'PartialStatement',\n\t                        name: $$[$0 - 3],\n\t                        params: $$[$0 - 2],\n\t                        hash: $$[$0 - 1],\n\t                        indent: '',\n\t                        strip: yy.stripFlags($$[$0 - 4], $$[$0]),\n\t                        loc: yy.locInfo(this._$)\n\t                    };\n\n\t                    break;\n\t                case 25:\n\t                    this.$ = yy.preparePartialBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$);\n\t                    break;\n\t                case 26:\n\t                    this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 4], $$[$0]) };\n\t                    break;\n\t                case 27:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 28:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 29:\n\t                    this.$ = {\n\t                        type: 'SubExpression',\n\t                        path: $$[$0 - 3],\n\t                        params: $$[$0 - 2],\n\t                        hash: $$[$0 - 1],\n\t                        loc: yy.locInfo(this._$)\n\t                    };\n\n\t                    break;\n\t                case 30:\n\t                    this.$ = { type: 'Hash', pairs: $$[$0], loc: yy.locInfo(this._$) };\n\t                    break;\n\t                case 31:\n\t                    this.$ = { type: 'HashPair', key: yy.id($$[$0 - 2]), value: $$[$0], loc: yy.locInfo(this._$) };\n\t                    break;\n\t                case 32:\n\t                    this.$ = yy.id($$[$0 - 1]);\n\t                    break;\n\t                case 33:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 34:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 35:\n\t                    this.$ = { type: 'StringLiteral', value: $$[$0], original: $$[$0], loc: yy.locInfo(this._$) };\n\t                    break;\n\t                case 36:\n\t                    this.$ = { type: 'NumberLiteral', value: Number($$[$0]), original: Number($$[$0]), loc: yy.locInfo(this._$) };\n\t                    break;\n\t                case 37:\n\t                    this.$ = { type: 'BooleanLiteral', value: $$[$0] === 'true', original: $$[$0] === 'true', loc: yy.locInfo(this._$) };\n\t                    break;\n\t                case 38:\n\t                    this.$ = { type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(this._$) };\n\t                    break;\n\t                case 39:\n\t                    this.$ = { type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(this._$) };\n\t                    break;\n\t                case 40:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 41:\n\t                    this.$ = $$[$0];\n\t                    break;\n\t                case 42:\n\t                    this.$ = yy.preparePath(true, $$[$0], this._$);\n\t                    break;\n\t                case 43:\n\t                    this.$ = yy.preparePath(false, $$[$0], this._$);\n\t                    break;\n\t                case 44:\n\t                    $$[$0 - 2].push({ part: yy.id($$[$0]), original: $$[$0], separator: $$[$0 - 1] });this.$ = $$[$0 - 2];\n\t                    break;\n\t                case 45:\n\t                    this.$ = [{ part: yy.id($$[$0]), original: $$[$0] }];\n\t                    break;\n\t                case 46:\n\t                    this.$ = [];\n\t                    break;\n\t                case 47:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 48:\n\t                    this.$ = [$$[$0]];\n\t                    break;\n\t                case 49:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 50:\n\t                    this.$ = [];\n\t                    break;\n\t                case 51:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 58:\n\t                    this.$ = [];\n\t                    break;\n\t                case 59:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 64:\n\t                    this.$ = [];\n\t                    break;\n\t                case 65:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 70:\n\t                    this.$ = [];\n\t                    break;\n\t                case 71:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 78:\n\t                    this.$ = [];\n\t                    break;\n\t                case 79:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 82:\n\t                    this.$ = [];\n\t                    break;\n\t                case 83:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 86:\n\t                    this.$ = [];\n\t                    break;\n\t                case 87:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 90:\n\t                    this.$ = [];\n\t                    break;\n\t                case 91:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 94:\n\t                    this.$ = [];\n\t                    break;\n\t                case 95:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 98:\n\t                    this.$ = [$$[$0]];\n\t                    break;\n\t                case 99:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t                case 100:\n\t                    this.$ = [$$[$0]];\n\t                    break;\n\t                case 101:\n\t                    $$[$0 - 1].push($$[$0]);\n\t                    break;\n\t            }\n\t        },\n\t        table: [{ 3: 1, 4: 2, 5: [2, 46], 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 1: [3] }, { 5: [1, 4] }, { 5: [2, 2], 7: 5, 8: 6, 9: 7, 10: 8, 11: 9, 12: 10, 13: 11, 14: [1, 12], 15: [1, 20], 16: 17, 19: [1, 23], 24: 15, 27: 16, 29: [1, 21], 34: [1, 22], 39: [2, 2], 44: [2, 2], 47: [2, 2], 48: [1, 13], 51: [1, 14], 55: [1, 18], 59: 19, 60: [1, 24] }, { 1: [2, 1] }, { 5: [2, 47], 14: [2, 47], 15: [2, 47], 19: [2, 47], 29: [2, 47], 34: [2, 47], 39: [2, 47], 44: [2, 47], 47: [2, 47], 48: [2, 47], 51: [2, 47], 55: [2, 47], 60: [2, 47] }, { 5: [2, 3], 14: [2, 3], 15: [2, 3], 19: [2, 3], 29: [2, 3], 34: [2, 3], 39: [2, 3], 44: [2, 3], 47: [2, 3], 48: [2, 3], 51: [2, 3], 55: [2, 3], 60: [2, 3] }, { 5: [2, 4], 14: [2, 4], 15: [2, 4], 19: [2, 4], 29: [2, 4], 34: [2, 4], 39: [2, 4], 44: [2, 4], 47: [2, 4], 48: [2, 4], 51: [2, 4], 55: [2, 4], 60: [2, 4] }, { 5: [2, 5], 14: [2, 5], 15: [2, 5], 19: [2, 5], 29: [2, 5], 34: [2, 5], 39: [2, 5], 44: [2, 5], 47: [2, 5], 48: [2, 5], 51: [2, 5], 55: [2, 5], 60: [2, 5] }, { 5: [2, 6], 14: [2, 6], 15: [2, 6], 19: [2, 6], 29: [2, 6], 34: [2, 6], 39: [2, 6], 44: [2, 6], 47: [2, 6], 48: [2, 6], 51: [2, 6], 55: [2, 6], 60: [2, 6] }, { 5: [2, 7], 14: [2, 7], 15: [2, 7], 19: [2, 7], 29: [2, 7], 34: [2, 7], 39: [2, 7], 44: [2, 7], 47: [2, 7], 48: [2, 7], 51: [2, 7], 55: [2, 7], 60: [2, 7] }, { 5: [2, 8], 14: [2, 8], 15: [2, 8], 19: [2, 8], 29: [2, 8], 34: [2, 8], 39: [2, 8], 44: [2, 8], 47: [2, 8], 48: [2, 8], 51: [2, 8], 55: [2, 8], 60: [2, 8] }, { 5: [2, 9], 14: [2, 9], 15: [2, 9], 19: [2, 9], 29: [2, 9], 34: [2, 9], 39: [2, 9], 44: [2, 9], 47: [2, 9], 48: [2, 9], 51: [2, 9], 55: [2, 9], 60: [2, 9] }, { 20: 25, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 36, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 37, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 4: 38, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 13: 40, 15: [1, 20], 17: 39 }, { 20: 42, 56: 41, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 45, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 5: [2, 10], 14: [2, 10], 15: [2, 10], 18: [2, 10], 19: [2, 10], 29: [2, 10], 34: [2, 10], 39: [2, 10], 44: [2, 10], 47: [2, 10], 48: [2, 10], 51: [2, 10], 55: [2, 10], 60: [2, 10] }, { 20: 46, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 47, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 48, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 42, 56: 49, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [2, 78], 49: 50, 65: [2, 78], 72: [2, 78], 80: [2, 78], 81: [2, 78], 82: [2, 78], 83: [2, 78], 84: [2, 78], 85: [2, 78] }, { 23: [2, 33], 33: [2, 33], 54: [2, 33], 65: [2, 33], 68: [2, 33], 72: [2, 33], 75: [2, 33], 80: [2, 33], 81: [2, 33], 82: [2, 33], 83: [2, 33], 84: [2, 33], 85: [2, 33] }, { 23: [2, 34], 33: [2, 34], 54: [2, 34], 65: [2, 34], 68: [2, 34], 72: [2, 34], 75: [2, 34], 80: [2, 34], 81: [2, 34], 82: [2, 34], 83: [2, 34], 84: [2, 34], 85: [2, 34] }, { 23: [2, 35], 33: [2, 35], 54: [2, 35], 65: [2, 35], 68: [2, 35], 72: [2, 35], 75: [2, 35], 80: [2, 35], 81: [2, 35], 82: [2, 35], 83: [2, 35], 84: [2, 35], 85: [2, 35] }, { 23: [2, 36], 33: [2, 36], 54: [2, 36], 65: [2, 36], 68: [2, 36], 72: [2, 36], 75: [2, 36], 80: [2, 36], 81: [2, 36], 82: [2, 36], 83: [2, 36], 84: [2, 36], 85: [2, 36] }, { 23: [2, 37], 33: [2, 37], 54: [2, 37], 65: [2, 37], 68: [2, 37], 72: [2, 37], 75: [2, 37], 80: [2, 37], 81: [2, 37], 82: [2, 37], 83: [2, 37], 84: [2, 37], 85: [2, 37] }, { 23: [2, 38], 33: [2, 38], 54: [2, 38], 65: [2, 38], 68: [2, 38], 72: [2, 38], 75: [2, 38], 80: [2, 38], 81: [2, 38], 82: [2, 38], 83: [2, 38], 84: [2, 38], 85: [2, 38] }, { 23: [2, 39], 33: [2, 39], 54: [2, 39], 65: [2, 39], 68: [2, 39], 72: [2, 39], 75: [2, 39], 80: [2, 39], 81: [2, 39], 82: [2, 39], 83: [2, 39], 84: [2, 39], 85: [2, 39] }, { 23: [2, 43], 33: [2, 43], 54: [2, 43], 65: [2, 43], 68: [2, 43], 72: [2, 43], 75: [2, 43], 80: [2, 43], 81: [2, 43], 82: [2, 43], 83: [2, 43], 84: [2, 43], 85: [2, 43], 87: [1, 51] }, { 72: [1, 35], 86: 52 }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 52: 53, 54: [2, 82], 65: [2, 82], 72: [2, 82], 80: [2, 82], 81: [2, 82], 82: [2, 82], 83: [2, 82], 84: [2, 82], 85: [2, 82] }, { 25: 54, 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 55, 47: [2, 54] }, { 28: 60, 43: 61, 44: [1, 59], 47: [2, 56] }, { 13: 63, 15: [1, 20], 18: [1, 62] }, { 15: [2, 48], 18: [2, 48] }, { 33: [2, 86], 57: 64, 65: [2, 86], 72: [2, 86], 80: [2, 86], 81: [2, 86], 82: [2, 86], 83: [2, 86], 84: [2, 86], 85: [2, 86] }, { 33: [2, 40], 65: [2, 40], 72: [2, 40], 80: [2, 40], 81: [2, 40], 82: [2, 40], 83: [2, 40], 84: [2, 40], 85: [2, 40] }, { 33: [2, 41], 65: [2, 41], 72: [2, 41], 80: [2, 41], 81: [2, 41], 82: [2, 41], 83: [2, 41], 84: [2, 41], 85: [2, 41] }, { 20: 65, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 66, 47: [1, 67] }, { 30: 68, 33: [2, 58], 65: [2, 58], 72: [2, 58], 75: [2, 58], 80: [2, 58], 81: [2, 58], 82: [2, 58], 83: [2, 58], 84: [2, 58], 85: [2, 58] }, { 33: [2, 64], 35: 69, 65: [2, 64], 72: [2, 64], 75: [2, 64], 80: [2, 64], 81: [2, 64], 82: [2, 64], 83: [2, 64], 84: [2, 64], 85: [2, 64] }, { 21: 70, 23: [2, 50], 65: [2, 50], 72: [2, 50], 80: [2, 50], 81: [2, 50], 82: [2, 50], 83: [2, 50], 84: [2, 50], 85: [2, 50] }, { 33: [2, 90], 61: 71, 65: [2, 90], 72: [2, 90], 80: [2, 90], 81: [2, 90], 82: [2, 90], 83: [2, 90], 84: [2, 90], 85: [2, 90] }, { 20: 75, 33: [2, 80], 50: 72, 63: 73, 64: 76, 65: [1, 44], 69: 74, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 72: [1, 80] }, { 23: [2, 42], 33: [2, 42], 54: [2, 42], 65: [2, 42], 68: [2, 42], 72: [2, 42], 75: [2, 42], 80: [2, 42], 81: [2, 42], 82: [2, 42], 83: [2, 42], 84: [2, 42], 85: [2, 42], 87: [1, 51] }, { 20: 75, 53: 81, 54: [2, 84], 63: 82, 64: 76, 65: [1, 44], 69: 83, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 84, 47: [1, 67] }, { 47: [2, 55] }, { 4: 85, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 47: [2, 20] }, { 20: 86, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 87, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 26: 88, 47: [1, 67] }, { 47: [2, 57] }, { 5: [2, 11], 14: [2, 11], 15: [2, 11], 19: [2, 11], 29: [2, 11], 34: [2, 11], 39: [2, 11], 44: [2, 11], 47: [2, 11], 48: [2, 11], 51: [2, 11], 55: [2, 11], 60: [2, 11] }, { 15: [2, 49], 18: [2, 49] }, { 20: 75, 33: [2, 88], 58: 89, 63: 90, 64: 76, 65: [1, 44], 69: 91, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 65: [2, 94], 66: 92, 68: [2, 94], 72: [2, 94], 80: [2, 94], 81: [2, 94], 82: [2, 94], 83: [2, 94], 84: [2, 94], 85: [2, 94] }, { 5: [2, 25], 14: [2, 25], 15: [2, 25], 19: [2, 25], 29: [2, 25], 34: [2, 25], 39: [2, 25], 44: [2, 25], 47: [2, 25], 48: [2, 25], 51: [2, 25], 55: [2, 25], 60: [2, 25] }, { 20: 93, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 31: 94, 33: [2, 60], 63: 95, 64: 76, 65: [1, 44], 69: 96, 70: 77, 71: 78, 72: [1, 79], 75: [2, 60], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 66], 36: 97, 63: 98, 64: 76, 65: [1, 44], 69: 99, 70: 77, 71: 78, 72: [1, 79], 75: [2, 66], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 22: 100, 23: [2, 52], 63: 101, 64: 76, 65: [1, 44], 69: 102, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 92], 62: 103, 63: 104, 64: 76, 65: [1, 44], 69: 105, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 106] }, { 33: [2, 79], 65: [2, 79], 72: [2, 79], 80: [2, 79], 81: [2, 79], 82: [2, 79], 83: [2, 79], 84: [2, 79], 85: [2, 79] }, { 33: [2, 81] }, { 23: [2, 27], 33: [2, 27], 54: [2, 27], 65: [2, 27], 68: [2, 27], 72: [2, 27], 75: [2, 27], 80: [2, 27], 81: [2, 27], 82: [2, 27], 83: [2, 27], 84: [2, 27], 85: [2, 27] }, { 23: [2, 28], 33: [2, 28], 54: [2, 28], 65: [2, 28], 68: [2, 28], 72: [2, 28], 75: [2, 28], 80: [2, 28], 81: [2, 28], 82: [2, 28], 83: [2, 28], 84: [2, 28], 85: [2, 28] }, { 23: [2, 30], 33: [2, 30], 54: [2, 30], 68: [2, 30], 71: 107, 72: [1, 108], 75: [2, 30] }, { 23: [2, 98], 33: [2, 98], 54: [2, 98], 68: [2, 98], 72: [2, 98], 75: [2, 98] }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 73: [1, 109], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 23: [2, 44], 33: [2, 44], 54: [2, 44], 65: [2, 44], 68: [2, 44], 72: [2, 44], 75: [2, 44], 80: [2, 44], 81: [2, 44], 82: [2, 44], 83: [2, 44], 84: [2, 44], 85: [2, 44], 87: [2, 44] }, { 54: [1, 110] }, { 54: [2, 83], 65: [2, 83], 72: [2, 83], 80: [2, 83], 81: [2, 83], 82: [2, 83], 83: [2, 83], 84: [2, 83], 85: [2, 83] }, { 54: [2, 85] }, { 5: [2, 13], 14: [2, 13], 15: [2, 13], 19: [2, 13], 29: [2, 13], 34: [2, 13], 39: [2, 13], 44: [2, 13], 47: [2, 13], 48: [2, 13], 51: [2, 13], 55: [2, 13], 60: [2, 13] }, { 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 112, 46: 111, 47: [2, 76] }, { 33: [2, 70], 40: 113, 65: [2, 70], 72: [2, 70], 75: [2, 70], 80: [2, 70], 81: [2, 70], 82: [2, 70], 83: [2, 70], 84: [2, 70], 85: [2, 70] }, { 47: [2, 18] }, { 5: [2, 14], 14: [2, 14], 15: [2, 14], 19: [2, 14], 29: [2, 14], 34: [2, 14], 39: [2, 14], 44: [2, 14], 47: [2, 14], 48: [2, 14], 51: [2, 14], 55: [2, 14], 60: [2, 14] }, { 33: [1, 114] }, { 33: [2, 87], 65: [2, 87], 72: [2, 87], 80: [2, 87], 81: [2, 87], 82: [2, 87], 83: [2, 87], 84: [2, 87], 85: [2, 87] }, { 33: [2, 89] }, { 20: 75, 63: 116, 64: 76, 65: [1, 44], 67: 115, 68: [2, 96], 69: 117, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 118] }, { 32: 119, 33: [2, 62], 74: 120, 75: [1, 121] }, { 33: [2, 59], 65: [2, 59], 72: [2, 59], 75: [2, 59], 80: [2, 59], 81: [2, 59], 82: [2, 59], 83: [2, 59], 84: [2, 59], 85: [2, 59] }, { 33: [2, 61], 75: [2, 61] }, { 33: [2, 68], 37: 122, 74: 123, 75: [1, 121] }, { 33: [2, 65], 65: [2, 65], 72: [2, 65], 75: [2, 65], 80: [2, 65], 81: [2, 65], 82: [2, 65], 83: [2, 65], 84: [2, 65], 85: [2, 65] }, { 33: [2, 67], 75: [2, 67] }, { 23: [1, 124] }, { 23: [2, 51], 65: [2, 51], 72: [2, 51], 80: [2, 51], 81: [2, 51], 82: [2, 51], 83: [2, 51], 84: [2, 51], 85: [2, 51] }, { 23: [2, 53] }, { 33: [1, 125] }, { 33: [2, 91], 65: [2, 91], 72: [2, 91], 80: [2, 91], 81: [2, 91], 82: [2, 91], 83: [2, 91], 84: [2, 91], 85: [2, 91] }, { 33: [2, 93] }, { 5: [2, 22], 14: [2, 22], 15: [2, 22], 19: [2, 22], 29: [2, 22], 34: [2, 22], 39: [2, 22], 44: [2, 22], 47: [2, 22], 48: [2, 22], 51: [2, 22], 55: [2, 22], 60: [2, 22] }, { 23: [2, 99], 33: [2, 99], 54: [2, 99], 68: [2, 99], 72: [2, 99], 75: [2, 99] }, { 73: [1, 109] }, { 20: 75, 63: 126, 64: 76, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 23], 14: [2, 23], 15: [2, 23], 19: [2, 23], 29: [2, 23], 34: [2, 23], 39: [2, 23], 44: [2, 23], 47: [2, 23], 48: [2, 23], 51: [2, 23], 55: [2, 23], 60: [2, 23] }, { 47: [2, 19] }, { 47: [2, 77] }, { 20: 75, 33: [2, 72], 41: 127, 63: 128, 64: 76, 65: [1, 44], 69: 129, 70: 77, 71: 78, 72: [1, 79], 75: [2, 72], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 24], 14: [2, 24], 15: [2, 24], 19: [2, 24], 29: [2, 24], 34: [2, 24], 39: [2, 24], 44: [2, 24], 47: [2, 24], 48: [2, 24], 51: [2, 24], 55: [2, 24], 60: [2, 24] }, { 68: [1, 130] }, { 65: [2, 95], 68: [2, 95], 72: [2, 95], 80: [2, 95], 81: [2, 95], 82: [2, 95], 83: [2, 95], 84: [2, 95], 85: [2, 95] }, { 68: [2, 97] }, { 5: [2, 21], 14: [2, 21], 15: [2, 21], 19: [2, 21], 29: [2, 21], 34: [2, 21], 39: [2, 21], 44: [2, 21], 47: [2, 21], 48: [2, 21], 51: [2, 21], 55: [2, 21], 60: [2, 21] }, { 33: [1, 131] }, { 33: [2, 63] }, { 72: [1, 133], 76: 132 }, { 33: [1, 134] }, { 33: [2, 69] }, { 15: [2, 12] }, { 14: [2, 26], 15: [2, 26], 19: [2, 26], 29: [2, 26], 34: [2, 26], 47: [2, 26], 48: [2, 26], 51: [2, 26], 55: [2, 26], 60: [2, 26] }, { 23: [2, 31], 33: [2, 31], 54: [2, 31], 68: [2, 31], 72: [2, 31], 75: [2, 31] }, { 33: [2, 74], 42: 135, 74: 136, 75: [1, 121] }, { 33: [2, 71], 65: [2, 71], 72: [2, 71], 75: [2, 71], 80: [2, 71], 81: [2, 71], 82: [2, 71], 83: [2, 71], 84: [2, 71], 85: [2, 71] }, { 33: [2, 73], 75: [2, 73] }, { 23: [2, 29], 33: [2, 29], 54: [2, 29], 65: [2, 29], 68: [2, 29], 72: [2, 29], 75: [2, 29], 80: [2, 29], 81: [2, 29], 82: [2, 29], 83: [2, 29], 84: [2, 29], 85: [2, 29] }, { 14: [2, 15], 15: [2, 15], 19: [2, 15], 29: [2, 15], 34: [2, 15], 39: [2, 15], 44: [2, 15], 47: [2, 15], 48: [2, 15], 51: [2, 15], 55: [2, 15], 60: [2, 15] }, { 72: [1, 138], 77: [1, 137] }, { 72: [2, 100], 77: [2, 100] }, { 14: [2, 16], 15: [2, 16], 19: [2, 16], 29: [2, 16], 34: [2, 16], 44: [2, 16], 47: [2, 16], 48: [2, 16], 51: [2, 16], 55: [2, 16], 60: [2, 16] }, { 33: [1, 139] }, { 33: [2, 75] }, { 33: [2, 32] }, { 72: [2, 101], 77: [2, 101] }, { 14: [2, 17], 15: [2, 17], 19: [2, 17], 29: [2, 17], 34: [2, 17], 39: [2, 17], 44: [2, 17], 47: [2, 17], 48: [2, 17], 51: [2, 17], 55: [2, 17], 60: [2, 17] }],\n\t        defaultActions: { 4: [2, 1], 55: [2, 55], 57: [2, 20], 61: [2, 57], 74: [2, 81], 83: [2, 85], 87: [2, 18], 91: [2, 89], 102: [2, 53], 105: [2, 93], 111: [2, 19], 112: [2, 77], 117: [2, 97], 120: [2, 63], 123: [2, 69], 124: [2, 12], 136: [2, 75], 137: [2, 32] },\n\t        parseError: function parseError(str, hash) {\n\t            throw new Error(str);\n\t        },\n\t        parse: function parse(input) {\n\t            var self = this,\n\t                stack = [0],\n\t                vstack = [null],\n\t                lstack = [],\n\t                table = this.table,\n\t                yytext = \"\",\n\t                yylineno = 0,\n\t                yyleng = 0,\n\t                recovering = 0,\n\t                TERROR = 2,\n\t                EOF = 1;\n\t            this.lexer.setInput(input);\n\t            this.lexer.yy = this.yy;\n\t            this.yy.lexer = this.lexer;\n\t            this.yy.parser = this;\n\t            if (typeof this.lexer.yylloc == \"undefined\") this.lexer.yylloc = {};\n\t            var yyloc = this.lexer.yylloc;\n\t            lstack.push(yyloc);\n\t            var ranges = this.lexer.options && this.lexer.options.ranges;\n\t            if (typeof this.yy.parseError === \"function\") this.parseError = this.yy.parseError;\n\t            function popStack(n) {\n\t                stack.length = stack.length - 2 * n;\n\t                vstack.length = vstack.length - n;\n\t                lstack.length = lstack.length - n;\n\t            }\n\t            function lex() {\n\t                var token;\n\t                token = self.lexer.lex() || 1;\n\t                if (typeof token !== \"number\") {\n\t                    token = self.symbols_[token] || token;\n\t                }\n\t                return token;\n\t            }\n\t            var symbol,\n\t                preErrorSymbol,\n\t                state,\n\t                action,\n\t                a,\n\t                r,\n\t                yyval = {},\n\t                p,\n\t                len,\n\t                newState,\n\t                expected;\n\t            while (true) {\n\t                state = stack[stack.length - 1];\n\t                if (this.defaultActions[state]) {\n\t                    action = this.defaultActions[state];\n\t                } else {\n\t                    if (symbol === null || typeof symbol == \"undefined\") {\n\t                        symbol = lex();\n\t                    }\n\t                    action = table[state] && table[state][symbol];\n\t                }\n\t                if (typeof action === \"undefined\" || !action.length || !action[0]) {\n\t                    var errStr = \"\";\n\t                    if (!recovering) {\n\t                        expected = [];\n\t                        for (p in table[state]) if (this.terminals_[p] && p > 2) {\n\t                            expected.push(\"'\" + this.terminals_[p] + \"'\");\n\t                        }\n\t                        if (this.lexer.showPosition) {\n\t                            errStr = \"Parse error on line \" + (yylineno + 1) + \":\\n\" + this.lexer.showPosition() + \"\\nExpecting \" + expected.join(\", \") + \", got '\" + (this.terminals_[symbol] || symbol) + \"'\";\n\t                        } else {\n\t                            errStr = \"Parse error on line \" + (yylineno + 1) + \": Unexpected \" + (symbol == 1 ? \"end of input\" : \"'\" + (this.terminals_[symbol] || symbol) + \"'\");\n\t                        }\n\t                        this.parseError(errStr, { text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected });\n\t                    }\n\t                }\n\t                if (action[0] instanceof Array && action.length > 1) {\n\t                    throw new Error(\"Parse Error: multiple actions possible at state: \" + state + \", token: \" + symbol);\n\t                }\n\t                switch (action[0]) {\n\t                    case 1:\n\t                        stack.push(symbol);\n\t                        vstack.push(this.lexer.yytext);\n\t                        lstack.push(this.lexer.yylloc);\n\t                        stack.push(action[1]);\n\t                        symbol = null;\n\t                        if (!preErrorSymbol) {\n\t                            yyleng = this.lexer.yyleng;\n\t                            yytext = this.lexer.yytext;\n\t                            yylineno = this.lexer.yylineno;\n\t                            yyloc = this.lexer.yylloc;\n\t                            if (recovering > 0) recovering--;\n\t                        } else {\n\t                            symbol = preErrorSymbol;\n\t                            preErrorSymbol = null;\n\t                        }\n\t                        break;\n\t                    case 2:\n\t                        len = this.productions_[action[1]][1];\n\t                        yyval.$ = vstack[vstack.length - len];\n\t                        yyval._$ = { first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column };\n\t                        if (ranges) {\n\t                            yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];\n\t                        }\n\t                        r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);\n\t                        if (typeof r !== \"undefined\") {\n\t                            return r;\n\t                        }\n\t                        if (len) {\n\t                            stack = stack.slice(0, -1 * len * 2);\n\t                            vstack = vstack.slice(0, -1 * len);\n\t                            lstack = lstack.slice(0, -1 * len);\n\t                        }\n\t                        stack.push(this.productions_[action[1]][0]);\n\t                        vstack.push(yyval.$);\n\t                        lstack.push(yyval._$);\n\t                        newState = table[stack[stack.length - 2]][stack[stack.length - 1]];\n\t                        stack.push(newState);\n\t                        break;\n\t                    case 3:\n\t                        return true;\n\t                }\n\t            }\n\t            return true;\n\t        }\n\t    };\n\t    /* Jison generated lexer */\n\t    var lexer = (function () {\n\t        var lexer = { EOF: 1,\n\t            parseError: function parseError(str, hash) {\n\t                if (this.yy.parser) {\n\t                    this.yy.parser.parseError(str, hash);\n\t                } else {\n\t                    throw new Error(str);\n\t                }\n\t            },\n\t            setInput: function setInput(input) {\n\t                this._input = input;\n\t                this._more = this._less = this.done = false;\n\t                this.yylineno = this.yyleng = 0;\n\t                this.yytext = this.matched = this.match = '';\n\t                this.conditionStack = ['INITIAL'];\n\t                this.yylloc = { first_line: 1, first_column: 0, last_line: 1, last_column: 0 };\n\t                if (this.options.ranges) this.yylloc.range = [0, 0];\n\t                this.offset = 0;\n\t                return this;\n\t            },\n\t            input: function input() {\n\t                var ch = this._input[0];\n\t                this.yytext += ch;\n\t                this.yyleng++;\n\t                this.offset++;\n\t                this.match += ch;\n\t                this.matched += ch;\n\t                var lines = ch.match(/(?:\\r\\n?|\\n).*/g);\n\t                if (lines) {\n\t                    this.yylineno++;\n\t                    this.yylloc.last_line++;\n\t                } else {\n\t                    this.yylloc.last_column++;\n\t                }\n\t                if (this.options.ranges) this.yylloc.range[1]++;\n\n\t                this._input = this._input.slice(1);\n\t                return ch;\n\t            },\n\t            unput: function unput(ch) {\n\t                var len = ch.length;\n\t                var lines = ch.split(/(?:\\r\\n?|\\n)/g);\n\n\t                this._input = ch + this._input;\n\t                this.yytext = this.yytext.substr(0, this.yytext.length - len - 1);\n\t                //this.yyleng -= len;\n\t                this.offset -= len;\n\t                var oldLines = this.match.split(/(?:\\r\\n?|\\n)/g);\n\t                this.match = this.match.substr(0, this.match.length - 1);\n\t                this.matched = this.matched.substr(0, this.matched.length - 1);\n\n\t                if (lines.length - 1) this.yylineno -= lines.length - 1;\n\t                var r = this.yylloc.range;\n\n\t                this.yylloc = { first_line: this.yylloc.first_line,\n\t                    last_line: this.yylineno + 1,\n\t                    first_column: this.yylloc.first_column,\n\t                    last_column: lines ? (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length : this.yylloc.first_column - len\n\t                };\n\n\t                if (this.options.ranges) {\n\t                    this.yylloc.range = [r[0], r[0] + this.yyleng - len];\n\t                }\n\t                return this;\n\t            },\n\t            more: function more() {\n\t                this._more = true;\n\t                return this;\n\t            },\n\t            less: function less(n) {\n\t                this.unput(this.match.slice(n));\n\t            },\n\t            pastInput: function pastInput() {\n\t                var past = this.matched.substr(0, this.matched.length - this.match.length);\n\t                return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\\n/g, \"\");\n\t            },\n\t            upcomingInput: function upcomingInput() {\n\t                var next = this.match;\n\t                if (next.length < 20) {\n\t                    next += this._input.substr(0, 20 - next.length);\n\t                }\n\t                return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\\n/g, \"\");\n\t            },\n\t            showPosition: function showPosition() {\n\t                var pre = this.pastInput();\n\t                var c = new Array(pre.length + 1).join(\"-\");\n\t                return pre + this.upcomingInput() + \"\\n\" + c + \"^\";\n\t            },\n\t            next: function next() {\n\t                if (this.done) {\n\t                    return this.EOF;\n\t                }\n\t                if (!this._input) this.done = true;\n\n\t                var token, match, tempMatch, index, col, lines;\n\t                if (!this._more) {\n\t                    this.yytext = '';\n\t                    this.match = '';\n\t                }\n\t                var rules = this._currentRules();\n\t                for (var i = 0; i < rules.length; i++) {\n\t                    tempMatch = this._input.match(this.rules[rules[i]]);\n\t                    if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {\n\t                        match = tempMatch;\n\t                        index = i;\n\t                        if (!this.options.flex) break;\n\t                    }\n\t                }\n\t                if (match) {\n\t                    lines = match[0].match(/(?:\\r\\n?|\\n).*/g);\n\t                    if (lines) this.yylineno += lines.length;\n\t                    this.yylloc = { first_line: this.yylloc.last_line,\n\t                        last_line: this.yylineno + 1,\n\t                        first_column: this.yylloc.last_column,\n\t                        last_column: lines ? lines[lines.length - 1].length - lines[lines.length - 1].match(/\\r?\\n?/)[0].length : this.yylloc.last_column + match[0].length };\n\t                    this.yytext += match[0];\n\t                    this.match += match[0];\n\t                    this.matches = match;\n\t                    this.yyleng = this.yytext.length;\n\t                    if (this.options.ranges) {\n\t                        this.yylloc.range = [this.offset, this.offset += this.yyleng];\n\t                    }\n\t                    this._more = false;\n\t                    this._input = this._input.slice(match[0].length);\n\t                    this.matched += match[0];\n\t                    token = this.performAction.call(this, this.yy, this, rules[index], this.conditionStack[this.conditionStack.length - 1]);\n\t                    if (this.done && this._input) this.done = false;\n\t                    if (token) return token;else return;\n\t                }\n\t                if (this._input === \"\") {\n\t                    return this.EOF;\n\t                } else {\n\t                    return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\\n' + this.showPosition(), { text: \"\", token: null, line: this.yylineno });\n\t                }\n\t            },\n\t            lex: function lex() {\n\t                var r = this.next();\n\t                if (typeof r !== 'undefined') {\n\t                    return r;\n\t                } else {\n\t                    return this.lex();\n\t                }\n\t            },\n\t            begin: function begin(condition) {\n\t                this.conditionStack.push(condition);\n\t            },\n\t            popState: function popState() {\n\t                return this.conditionStack.pop();\n\t            },\n\t            _currentRules: function _currentRules() {\n\t                return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;\n\t            },\n\t            topState: function topState() {\n\t                return this.conditionStack[this.conditionStack.length - 2];\n\t            },\n\t            pushState: function begin(condition) {\n\t                this.begin(condition);\n\t            } };\n\t        lexer.options = {};\n\t        lexer.performAction = function anonymous(yy, yy_, $avoiding_name_collisions, YY_START\n\t        /**/) {\n\n\t            function strip(start, end) {\n\t                return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng - end);\n\t            }\n\n\t            var YYSTATE = YY_START;\n\t            switch ($avoiding_name_collisions) {\n\t                case 0:\n\t                    if (yy_.yytext.slice(-2) === \"\\\\\\\\\") {\n\t                        strip(0, 1);\n\t                        this.begin(\"mu\");\n\t                    } else if (yy_.yytext.slice(-1) === \"\\\\\") {\n\t                        strip(0, 1);\n\t                        this.begin(\"emu\");\n\t                    } else {\n\t                        this.begin(\"mu\");\n\t                    }\n\t                    if (yy_.yytext) return 15;\n\n\t                    break;\n\t                case 1:\n\t                    return 15;\n\t                    break;\n\t                case 2:\n\t                    this.popState();\n\t                    return 15;\n\n\t                    break;\n\t                case 3:\n\t                    this.begin('raw');return 15;\n\t                    break;\n\t                case 4:\n\t                    this.popState();\n\t                    // Should be using `this.topState()` below, but it currently\n\t                    // returns the second top instead of the first top. Opened an\n\t                    // issue about it at https://github.com/zaach/jison/issues/291\n\t                    if (this.conditionStack[this.conditionStack.length - 1] === 'raw') {\n\t                        return 15;\n\t                    } else {\n\t                        yy_.yytext = yy_.yytext.substr(5, yy_.yyleng - 9);\n\t                        return 'END_RAW_BLOCK';\n\t                    }\n\n\t                    break;\n\t                case 5:\n\t                    return 15;\n\t                    break;\n\t                case 6:\n\t                    this.popState();\n\t                    return 14;\n\n\t                    break;\n\t                case 7:\n\t                    return 65;\n\t                    break;\n\t                case 8:\n\t                    return 68;\n\t                    break;\n\t                case 9:\n\t                    return 19;\n\t                    break;\n\t                case 10:\n\t                    this.popState();\n\t                    this.begin('raw');\n\t                    return 23;\n\n\t                    break;\n\t                case 11:\n\t                    return 55;\n\t                    break;\n\t                case 12:\n\t                    return 60;\n\t                    break;\n\t                case 13:\n\t                    return 29;\n\t                    break;\n\t                case 14:\n\t                    return 47;\n\t                    break;\n\t                case 15:\n\t                    this.popState();return 44;\n\t                    break;\n\t                case 16:\n\t                    this.popState();return 44;\n\t                    break;\n\t                case 17:\n\t                    return 34;\n\t                    break;\n\t                case 18:\n\t                    return 39;\n\t                    break;\n\t                case 19:\n\t                    return 51;\n\t                    break;\n\t                case 20:\n\t                    return 48;\n\t                    break;\n\t                case 21:\n\t                    this.unput(yy_.yytext);\n\t                    this.popState();\n\t                    this.begin('com');\n\n\t                    break;\n\t                case 22:\n\t                    this.popState();\n\t                    return 14;\n\n\t                    break;\n\t                case 23:\n\t                    return 48;\n\t                    break;\n\t                case 24:\n\t                    return 73;\n\t                    break;\n\t                case 25:\n\t                    return 72;\n\t                    break;\n\t                case 26:\n\t                    return 72;\n\t                    break;\n\t                case 27:\n\t                    return 87;\n\t                    break;\n\t                case 28:\n\t                    // ignore whitespace\n\t                    break;\n\t                case 29:\n\t                    this.popState();return 54;\n\t                    break;\n\t                case 30:\n\t                    this.popState();return 33;\n\t                    break;\n\t                case 31:\n\t                    yy_.yytext = strip(1, 2).replace(/\\\\\"/g, '\"');return 80;\n\t                    break;\n\t                case 32:\n\t                    yy_.yytext = strip(1, 2).replace(/\\\\'/g, \"'\");return 80;\n\t                    break;\n\t                case 33:\n\t                    return 85;\n\t                    break;\n\t                case 34:\n\t                    return 82;\n\t                    break;\n\t                case 35:\n\t                    return 82;\n\t                    break;\n\t                case 36:\n\t                    return 83;\n\t                    break;\n\t                case 37:\n\t                    return 84;\n\t                    break;\n\t                case 38:\n\t                    return 81;\n\t                    break;\n\t                case 39:\n\t                    return 75;\n\t                    break;\n\t                case 40:\n\t                    return 77;\n\t                    break;\n\t                case 41:\n\t                    return 72;\n\t                    break;\n\t                case 42:\n\t                    yy_.yytext = yy_.yytext.replace(/\\\\([\\\\\\]])/g, '$1');return 72;\n\t                    break;\n\t                case 43:\n\t                    return 'INVALID';\n\t                    break;\n\t                case 44:\n\t                    return 5;\n\t                    break;\n\t            }\n\t        };\n\t        lexer.rules = [/^(?:[^\\x00]*?(?=(\\{\\{)))/, /^(?:[^\\x00]+)/, /^(?:[^\\x00]{2,}?(?=(\\{\\{|\\\\\\{\\{|\\\\\\\\\\{\\{|$)))/, /^(?:\\{\\{\\{\\{(?=[^\\/]))/, /^(?:\\{\\{\\{\\{\\/[^\\s!\"#%-,\\.\\/;->@\\[-\\^`\\{-~]+(?=[=}\\s\\/.])\\}\\}\\}\\})/, /^(?:[^\\x00]*?(?=(\\{\\{\\{\\{)))/, /^(?:[\\s\\S]*?--(~)?\\}\\})/, /^(?:\\()/, /^(?:\\))/, /^(?:\\{\\{\\{\\{)/, /^(?:\\}\\}\\}\\})/, /^(?:\\{\\{(~)?>)/, /^(?:\\{\\{(~)?#>)/, /^(?:\\{\\{(~)?#\\*?)/, /^(?:\\{\\{(~)?\\/)/, /^(?:\\{\\{(~)?\\^\\s*(~)?\\}\\})/, /^(?:\\{\\{(~)?\\s*else\\s*(~)?\\}\\})/, /^(?:\\{\\{(~)?\\^)/, /^(?:\\{\\{(~)?\\s*else\\b)/, /^(?:\\{\\{(~)?\\{)/, /^(?:\\{\\{(~)?&)/, /^(?:\\{\\{(~)?!--)/, /^(?:\\{\\{(~)?![\\s\\S]*?\\}\\})/, /^(?:\\{\\{(~)?\\*?)/, /^(?:=)/, /^(?:\\.\\.)/, /^(?:\\.(?=([=~}\\s\\/.)|])))/, /^(?:[\\/.])/, /^(?:\\s+)/, /^(?:\\}(~)?\\}\\})/, /^(?:(~)?\\}\\})/, /^(?:\"(\\\\[\"]|[^\"])*\")/, /^(?:'(\\\\[']|[^'])*')/, /^(?:@)/, /^(?:true(?=([~}\\s)])))/, /^(?:false(?=([~}\\s)])))/, /^(?:undefined(?=([~}\\s)])))/, /^(?:null(?=([~}\\s)])))/, /^(?:-?[0-9]+(?:\\.[0-9]+)?(?=([~}\\s)])))/, /^(?:as\\s+\\|)/, /^(?:\\|)/, /^(?:([^\\s!\"#%-,\\.\\/;->@\\[-\\^`\\{-~]+(?=([=~}\\s\\/.)|]))))/, /^(?:\\[(\\\\\\]|[^\\]])*\\])/, /^(?:.)/, /^(?:$)/];\n\t        lexer.conditions = { \"mu\": { \"rules\": [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], \"inclusive\": false }, \"emu\": { \"rules\": [2], \"inclusive\": false }, \"com\": { \"rules\": [6], \"inclusive\": false }, \"raw\": { \"rules\": [3, 4, 5], \"inclusive\": false }, \"INITIAL\": { \"rules\": [0, 1, 44], \"inclusive\": true } };\n\t        return lexer;\n\t    })();\n\t    parser.lexer = lexer;\n\t    function Parser() {\n\t        this.yy = {};\n\t    }Parser.prototype = parser;parser.Parser = Parser;\n\t    return new Parser();\n\t})();exports.__esModule = true;\n\texports['default'] = handlebars;\n\n/***/ },\n/* 24 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\n\tvar _visitor = __webpack_require__(25);\n\n\tvar _visitor2 = _interopRequireDefault(_visitor);\n\n\tfunction WhitespaceControl() {\n\t  var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];\n\n\t  this.options = options;\n\t}\n\tWhitespaceControl.prototype = new _visitor2['default']();\n\n\tWhitespaceControl.prototype.Program = function (program) {\n\t  var doStandalone = !this.options.ignoreStandalone;\n\n\t  var isRoot = !this.isRootSeen;\n\t  this.isRootSeen = true;\n\n\t  var body = program.body;\n\t  for (var i = 0, l = body.length; i < l; i++) {\n\t    var current = body[i],\n\t        strip = this.accept(current);\n\n\t    if (!strip) {\n\t      continue;\n\t    }\n\n\t    var _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),\n\t        _isNextWhitespace = isNextWhitespace(body, i, isRoot),\n\t        openStandalone = strip.openStandalone && _isPrevWhitespace,\n\t        closeStandalone = strip.closeStandalone && _isNextWhitespace,\n\t        inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;\n\n\t    if (strip.close) {\n\t      omitRight(body, i, true);\n\t    }\n\t    if (strip.open) {\n\t      omitLeft(body, i, true);\n\t    }\n\n\t    if (doStandalone && inlineStandalone) {\n\t      omitRight(body, i);\n\n\t      if (omitLeft(body, i)) {\n\t        // If we are on a standalone node, save the indent info for partials\n\t        if (current.type === 'PartialStatement') {\n\t          // Pull out the whitespace from the final line\n\t          current.indent = /([ \\t]+$)/.exec(body[i - 1].original)[1];\n\t        }\n\t      }\n\t    }\n\t    if (doStandalone && openStandalone) {\n\t      omitRight((current.program || current.inverse).body);\n\n\t      // Strip out the previous content node if it's whitespace only\n\t      omitLeft(body, i);\n\t    }\n\t    if (doStandalone && closeStandalone) {\n\t      // Always strip the next node\n\t      omitRight(body, i);\n\n\t      omitLeft((current.inverse || current.program).body);\n\t    }\n\t  }\n\n\t  return program;\n\t};\n\n\tWhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function (block) {\n\t  this.accept(block.program);\n\t  this.accept(block.inverse);\n\n\t  // Find the inverse program that is involed with whitespace stripping.\n\t  var program = block.program || block.inverse,\n\t      inverse = block.program && block.inverse,\n\t      firstInverse = inverse,\n\t      lastInverse = inverse;\n\n\t  if (inverse && inverse.chained) {\n\t    firstInverse = inverse.body[0].program;\n\n\t    // Walk the inverse chain to find the last inverse that is actually in the chain.\n\t    while (lastInverse.chained) {\n\t      lastInverse = lastInverse.body[lastInverse.body.length - 1].program;\n\t    }\n\t  }\n\n\t  var strip = {\n\t    open: block.openStrip.open,\n\t    close: block.closeStrip.close,\n\n\t    // Determine the standalone candiacy. Basically flag our content as being possibly standalone\n\t    // so our parent can determine if we actually are standalone\n\t    openStandalone: isNextWhitespace(program.body),\n\t    closeStandalone: isPrevWhitespace((firstInverse || program).body)\n\t  };\n\n\t  if (block.openStrip.close) {\n\t    omitRight(program.body, null, true);\n\t  }\n\n\t  if (inverse) {\n\t    var inverseStrip = block.inverseStrip;\n\n\t    if (inverseStrip.open) {\n\t      omitLeft(program.body, null, true);\n\t    }\n\n\t    if (inverseStrip.close) {\n\t      omitRight(firstInverse.body, null, true);\n\t    }\n\t    if (block.closeStrip.open) {\n\t      omitLeft(lastInverse.body, null, true);\n\t    }\n\n\t    // Find standalone else statments\n\t    if (!this.options.ignoreStandalone && isPrevWhitespace(program.body) && isNextWhitespace(firstInverse.body)) {\n\t      omitLeft(program.body);\n\t      omitRight(firstInverse.body);\n\t    }\n\t  } else if (block.closeStrip.open) {\n\t    omitLeft(program.body, null, true);\n\t  }\n\n\t  return strip;\n\t};\n\n\tWhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function (mustache) {\n\t  return mustache.strip;\n\t};\n\n\tWhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function (node) {\n\t  /* istanbul ignore next */\n\t  var strip = node.strip || {};\n\t  return {\n\t    inlineStandalone: true,\n\t    open: strip.open,\n\t    close: strip.close\n\t  };\n\t};\n\n\tfunction isPrevWhitespace(body, i, isRoot) {\n\t  if (i === undefined) {\n\t    i = body.length;\n\t  }\n\n\t  // Nodes that end with newlines are considered whitespace (but are special\n\t  // cased for strip operations)\n\t  var prev = body[i - 1],\n\t      sibling = body[i - 2];\n\t  if (!prev) {\n\t    return isRoot;\n\t  }\n\n\t  if (prev.type === 'ContentStatement') {\n\t    return (sibling || !isRoot ? /\\r?\\n\\s*?$/ : /(^|\\r?\\n)\\s*?$/).test(prev.original);\n\t  }\n\t}\n\tfunction isNextWhitespace(body, i, isRoot) {\n\t  if (i === undefined) {\n\t    i = -1;\n\t  }\n\n\t  var next = body[i + 1],\n\t      sibling = body[i + 2];\n\t  if (!next) {\n\t    return isRoot;\n\t  }\n\n\t  if (next.type === 'ContentStatement') {\n\t    return (sibling || !isRoot ? /^\\s*?\\r?\\n/ : /^\\s*?(\\r?\\n|$)/).test(next.original);\n\t  }\n\t}\n\n\t// Marks the node to the right of the position as omitted.\n\t// I.e. {{foo}}' ' will mark the ' ' node as omitted.\n\t//\n\t// If i is undefined, then the first child will be marked as such.\n\t//\n\t// If mulitple is truthy then all whitespace will be stripped out until non-whitespace\n\t// content is met.\n\tfunction omitRight(body, i, multiple) {\n\t  var current = body[i == null ? 0 : i + 1];\n\t  if (!current || current.type !== 'ContentStatement' || !multiple && current.rightStripped) {\n\t    return;\n\t  }\n\n\t  var original = current.value;\n\t  current.value = current.value.replace(multiple ? /^\\s+/ : /^[ \\t]*\\r?\\n?/, '');\n\t  current.rightStripped = current.value !== original;\n\t}\n\n\t// Marks the node to the left of the position as omitted.\n\t// I.e. ' '{{foo}} will mark the ' ' node as omitted.\n\t//\n\t// If i is undefined then the last child will be marked as such.\n\t//\n\t// If mulitple is truthy then all whitespace will be stripped out until non-whitespace\n\t// content is met.\n\tfunction omitLeft(body, i, multiple) {\n\t  var current = body[i == null ? body.length - 1 : i - 1];\n\t  if (!current || current.type !== 'ContentStatement' || !multiple && current.leftStripped) {\n\t    return;\n\t  }\n\n\t  // We omit the last node if it's whitespace only and not preceeded by a non-content node.\n\t  var original = current.value;\n\t  current.value = current.value.replace(multiple ? /\\s+$/ : /[ \\t]+$/, '');\n\t  current.leftStripped = current.value !== original;\n\t  return current.leftStripped;\n\t}\n\n\texports['default'] = WhitespaceControl;\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 25 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\n\tvar _exception = __webpack_require__(6);\n\n\tvar _exception2 = _interopRequireDefault(_exception);\n\n\tfunction Visitor() {\n\t  this.parents = [];\n\t}\n\n\tVisitor.prototype = {\n\t  constructor: Visitor,\n\t  mutating: false,\n\n\t  // Visits a given value. If mutating, will replace the value if necessary.\n\t  acceptKey: function acceptKey(node, name) {\n\t    var value = this.accept(node[name]);\n\t    if (this.mutating) {\n\t      // Hacky sanity check: This may have a few false positives for type for the helper\n\t      // methods but will generally do the right thing without a lot of overhead.\n\t      if (value && !Visitor.prototype[value.type]) {\n\t        throw new _exception2['default']('Unexpected node type \"' + value.type + '\" found when accepting ' + name + ' on ' + node.type);\n\t      }\n\t      node[name] = value;\n\t    }\n\t  },\n\n\t  // Performs an accept operation with added sanity check to ensure\n\t  // required keys are not removed.\n\t  acceptRequired: function acceptRequired(node, name) {\n\t    this.acceptKey(node, name);\n\n\t    if (!node[name]) {\n\t      throw new _exception2['default'](node.type + ' requires ' + name);\n\t    }\n\t  },\n\n\t  // Traverses a given array. If mutating, empty respnses will be removed\n\t  // for child elements.\n\t  acceptArray: function acceptArray(array) {\n\t    for (var i = 0, l = array.length; i < l; i++) {\n\t      this.acceptKey(array, i);\n\n\t      if (!array[i]) {\n\t        array.splice(i, 1);\n\t        i--;\n\t        l--;\n\t      }\n\t    }\n\t  },\n\n\t  accept: function accept(object) {\n\t    if (!object) {\n\t      return;\n\t    }\n\n\t    /* istanbul ignore next: Sanity code */\n\t    if (!this[object.type]) {\n\t      throw new _exception2['default']('Unknown type: ' + object.type, object);\n\t    }\n\n\t    if (this.current) {\n\t      this.parents.unshift(this.current);\n\t    }\n\t    this.current = object;\n\n\t    var ret = this[object.type](object);\n\n\t    this.current = this.parents.shift();\n\n\t    if (!this.mutating || ret) {\n\t      return ret;\n\t    } else if (ret !== false) {\n\t      return object;\n\t    }\n\t  },\n\n\t  Program: function Program(program) {\n\t    this.acceptArray(program.body);\n\t  },\n\n\t  MustacheStatement: visitSubExpression,\n\t  Decorator: visitSubExpression,\n\n\t  BlockStatement: visitBlock,\n\t  DecoratorBlock: visitBlock,\n\n\t  PartialStatement: visitPartial,\n\t  PartialBlockStatement: function PartialBlockStatement(partial) {\n\t    visitPartial.call(this, partial);\n\n\t    this.acceptKey(partial, 'program');\n\t  },\n\n\t  ContentStatement: function ContentStatement() /* content */{},\n\t  CommentStatement: function CommentStatement() /* comment */{},\n\n\t  SubExpression: visitSubExpression,\n\n\t  PathExpression: function PathExpression() /* path */{},\n\n\t  StringLiteral: function StringLiteral() /* string */{},\n\t  NumberLiteral: function NumberLiteral() /* number */{},\n\t  BooleanLiteral: function BooleanLiteral() /* bool */{},\n\t  UndefinedLiteral: function UndefinedLiteral() /* literal */{},\n\t  NullLiteral: function NullLiteral() /* literal */{},\n\n\t  Hash: function Hash(hash) {\n\t    this.acceptArray(hash.pairs);\n\t  },\n\t  HashPair: function HashPair(pair) {\n\t    this.acceptRequired(pair, 'value');\n\t  }\n\t};\n\n\tfunction visitSubExpression(mustache) {\n\t  this.acceptRequired(mustache, 'path');\n\t  this.acceptArray(mustache.params);\n\t  this.acceptKey(mustache, 'hash');\n\t}\n\tfunction visitBlock(block) {\n\t  visitSubExpression.call(this, block);\n\n\t  this.acceptKey(block, 'program');\n\t  this.acceptKey(block, 'inverse');\n\t}\n\tfunction visitPartial(partial) {\n\t  this.acceptRequired(partial, 'name');\n\t  this.acceptArray(partial.params);\n\t  this.acceptKey(partial, 'hash');\n\t}\n\n\texports['default'] = Visitor;\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 26 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\texports.SourceLocation = SourceLocation;\n\texports.id = id;\n\texports.stripFlags = stripFlags;\n\texports.stripComment = stripComment;\n\texports.preparePath = preparePath;\n\texports.prepareMustache = prepareMustache;\n\texports.prepareRawBlock = prepareRawBlock;\n\texports.prepareBlock = prepareBlock;\n\texports.prepareProgram = prepareProgram;\n\texports.preparePartialBlock = preparePartialBlock;\n\n\tvar _exception = __webpack_require__(6);\n\n\tvar _exception2 = _interopRequireDefault(_exception);\n\n\tfunction validateClose(open, close) {\n\t  close = close.path ? close.path.original : close;\n\n\t  if (open.path.original !== close) {\n\t    var errorNode = { loc: open.path.loc };\n\n\t    throw new _exception2['default'](open.path.original + \" doesn't match \" + close, errorNode);\n\t  }\n\t}\n\n\tfunction SourceLocation(source, locInfo) {\n\t  this.source = source;\n\t  this.start = {\n\t    line: locInfo.first_line,\n\t    column: locInfo.first_column\n\t  };\n\t  this.end = {\n\t    line: locInfo.last_line,\n\t    column: locInfo.last_column\n\t  };\n\t}\n\n\tfunction id(token) {\n\t  if (/^\\[.*\\]$/.test(token)) {\n\t    return token.substr(1, token.length - 2);\n\t  } else {\n\t    return token;\n\t  }\n\t}\n\n\tfunction stripFlags(open, close) {\n\t  return {\n\t    open: open.charAt(2) === '~',\n\t    close: close.charAt(close.length - 3) === '~'\n\t  };\n\t}\n\n\tfunction stripComment(comment) {\n\t  return comment.replace(/^\\{\\{~?\\!-?-?/, '').replace(/-?-?~?\\}\\}$/, '');\n\t}\n\n\tfunction preparePath(data, parts, loc) {\n\t  loc = this.locInfo(loc);\n\n\t  var original = data ? '@' : '',\n\t      dig = [],\n\t      depth = 0,\n\t      depthString = '';\n\n\t  for (var i = 0, l = parts.length; i < l; i++) {\n\t    var part = parts[i].part,\n\n\t    // If we have [] syntax then we do not treat path references as operators,\n\t    // i.e. foo.[this] resolves to approximately context.foo['this']\n\t    isLiteral = parts[i].original !== part;\n\t    original += (parts[i].separator || '') + part;\n\n\t    if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {\n\t      if (dig.length > 0) {\n\t        throw new _exception2['default']('Invalid path: ' + original, { loc: loc });\n\t      } else if (part === '..') {\n\t        depth++;\n\t        depthString += '../';\n\t      }\n\t    } else {\n\t      dig.push(part);\n\t    }\n\t  }\n\n\t  return {\n\t    type: 'PathExpression',\n\t    data: data,\n\t    depth: depth,\n\t    parts: dig,\n\t    original: original,\n\t    loc: loc\n\t  };\n\t}\n\n\tfunction prepareMustache(path, params, hash, open, strip, locInfo) {\n\t  // Must use charAt to support IE pre-10\n\t  var escapeFlag = open.charAt(3) || open.charAt(2),\n\t      escaped = escapeFlag !== '{' && escapeFlag !== '&';\n\n\t  var decorator = /\\*/.test(open);\n\t  return {\n\t    type: decorator ? 'Decorator' : 'MustacheStatement',\n\t    path: path,\n\t    params: params,\n\t    hash: hash,\n\t    escaped: escaped,\n\t    strip: strip,\n\t    loc: this.locInfo(locInfo)\n\t  };\n\t}\n\n\tfunction prepareRawBlock(openRawBlock, contents, close, locInfo) {\n\t  validateClose(openRawBlock, close);\n\n\t  locInfo = this.locInfo(locInfo);\n\t  var program = {\n\t    type: 'Program',\n\t    body: contents,\n\t    strip: {},\n\t    loc: locInfo\n\t  };\n\n\t  return {\n\t    type: 'BlockStatement',\n\t    path: openRawBlock.path,\n\t    params: openRawBlock.params,\n\t    hash: openRawBlock.hash,\n\t    program: program,\n\t    openStrip: {},\n\t    inverseStrip: {},\n\t    closeStrip: {},\n\t    loc: locInfo\n\t  };\n\t}\n\n\tfunction prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {\n\t  if (close && close.path) {\n\t    validateClose(openBlock, close);\n\t  }\n\n\t  var decorator = /\\*/.test(openBlock.open);\n\n\t  program.blockParams = openBlock.blockParams;\n\n\t  var inverse = undefined,\n\t      inverseStrip = undefined;\n\n\t  if (inverseAndProgram) {\n\t    if (decorator) {\n\t      throw new _exception2['default']('Unexpected inverse block on decorator', inverseAndProgram);\n\t    }\n\n\t    if (inverseAndProgram.chain) {\n\t      inverseAndProgram.program.body[0].closeStrip = close.strip;\n\t    }\n\n\t    inverseStrip = inverseAndProgram.strip;\n\t    inverse = inverseAndProgram.program;\n\t  }\n\n\t  if (inverted) {\n\t    inverted = inverse;\n\t    inverse = program;\n\t    program = inverted;\n\t  }\n\n\t  return {\n\t    type: decorator ? 'DecoratorBlock' : 'BlockStatement',\n\t    path: openBlock.path,\n\t    params: openBlock.params,\n\t    hash: openBlock.hash,\n\t    program: program,\n\t    inverse: inverse,\n\t    openStrip: openBlock.strip,\n\t    inverseStrip: inverseStrip,\n\t    closeStrip: close && close.strip,\n\t    loc: this.locInfo(locInfo)\n\t  };\n\t}\n\n\tfunction prepareProgram(statements, loc) {\n\t  if (!loc && statements.length) {\n\t    var firstLoc = statements[0].loc,\n\t        lastLoc = statements[statements.length - 1].loc;\n\n\t    /* istanbul ignore else */\n\t    if (firstLoc && lastLoc) {\n\t      loc = {\n\t        source: firstLoc.source,\n\t        start: {\n\t          line: firstLoc.start.line,\n\t          column: firstLoc.start.column\n\t        },\n\t        end: {\n\t          line: lastLoc.end.line,\n\t          column: lastLoc.end.column\n\t        }\n\t      };\n\t    }\n\t  }\n\n\t  return {\n\t    type: 'Program',\n\t    body: statements,\n\t    strip: {},\n\t    loc: loc\n\t  };\n\t}\n\n\tfunction preparePartialBlock(open, program, close, locInfo) {\n\t  validateClose(open, close);\n\n\t  return {\n\t    type: 'PartialBlockStatement',\n\t    name: open.path,\n\t    params: open.params,\n\t    hash: open.hash,\n\t    program: program,\n\t    openStrip: open.strip,\n\t    closeStrip: close && close.strip,\n\t    loc: this.locInfo(locInfo)\n\t  };\n\t}\n\n/***/ },\n/* 27 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* eslint-disable new-cap */\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\texports.Compiler = Compiler;\n\texports.precompile = precompile;\n\texports.compile = compile;\n\n\tvar _exception = __webpack_require__(6);\n\n\tvar _exception2 = _interopRequireDefault(_exception);\n\n\tvar _utils = __webpack_require__(5);\n\n\tvar _ast = __webpack_require__(21);\n\n\tvar _ast2 = _interopRequireDefault(_ast);\n\n\tvar slice = [].slice;\n\n\tfunction Compiler() {}\n\n\t// the foundHelper register will disambiguate helper lookup from finding a\n\t// function in a context. This is necessary for mustache compatibility, which\n\t// requires that context functions in blocks are evaluated by blockHelperMissing,\n\t// and then proceed as if the resulting value was provided to blockHelperMissing.\n\n\tCompiler.prototype = {\n\t  compiler: Compiler,\n\n\t  equals: function equals(other) {\n\t    var len = this.opcodes.length;\n\t    if (other.opcodes.length !== len) {\n\t      return false;\n\t    }\n\n\t    for (var i = 0; i < len; i++) {\n\t      var opcode = this.opcodes[i],\n\t          otherOpcode = other.opcodes[i];\n\t      if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {\n\t        return false;\n\t      }\n\t    }\n\n\t    // We know that length is the same between the two arrays because they are directly tied\n\t    // to the opcode behavior above.\n\t    len = this.children.length;\n\t    for (var i = 0; i < len; i++) {\n\t      if (!this.children[i].equals(other.children[i])) {\n\t        return false;\n\t      }\n\t    }\n\n\t    return true;\n\t  },\n\n\t  guid: 0,\n\n\t  compile: function compile(program, options) {\n\t    this.sourceNode = [];\n\t    this.opcodes = [];\n\t    this.children = [];\n\t    this.options = options;\n\t    this.stringParams = options.stringParams;\n\t    this.trackIds = options.trackIds;\n\n\t    options.blockParams = options.blockParams || [];\n\n\t    // These changes will propagate to the other compiler components\n\t    var knownHelpers = options.knownHelpers;\n\t    options.knownHelpers = {\n\t      'helperMissing': true,\n\t      'blockHelperMissing': true,\n\t      'each': true,\n\t      'if': true,\n\t      'unless': true,\n\t      'with': true,\n\t      'log': true,\n\t      'lookup': true\n\t    };\n\t    if (knownHelpers) {\n\t      for (var _name in knownHelpers) {\n\t        /* istanbul ignore else */\n\t        if (_name in knownHelpers) {\n\t          options.knownHelpers[_name] = knownHelpers[_name];\n\t        }\n\t      }\n\t    }\n\n\t    return this.accept(program);\n\t  },\n\n\t  compileProgram: function compileProgram(program) {\n\t    var childCompiler = new this.compiler(),\n\t        // eslint-disable-line new-cap\n\t    result = childCompiler.compile(program, this.options),\n\t        guid = this.guid++;\n\n\t    this.usePartial = this.usePartial || result.usePartial;\n\n\t    this.children[guid] = result;\n\t    this.useDepths = this.useDepths || result.useDepths;\n\n\t    return guid;\n\t  },\n\n\t  accept: function accept(node) {\n\t    /* istanbul ignore next: Sanity code */\n\t    if (!this[node.type]) {\n\t      throw new _exception2['default']('Unknown type: ' + node.type, node);\n\t    }\n\n\t    this.sourceNode.unshift(node);\n\t    var ret = this[node.type](node);\n\t    this.sourceNode.shift();\n\t    return ret;\n\t  },\n\n\t  Program: function Program(program) {\n\t    this.options.blockParams.unshift(program.blockParams);\n\n\t    var body = program.body,\n\t        bodyLength = body.length;\n\t    for (var i = 0; i < bodyLength; i++) {\n\t      this.accept(body[i]);\n\t    }\n\n\t    this.options.blockParams.shift();\n\n\t    this.isSimple = bodyLength === 1;\n\t    this.blockParams = program.blockParams ? program.blockParams.length : 0;\n\n\t    return this;\n\t  },\n\n\t  BlockStatement: function BlockStatement(block) {\n\t    transformLiteralToPath(block);\n\n\t    var program = block.program,\n\t        inverse = block.inverse;\n\n\t    program = program && this.compileProgram(program);\n\t    inverse = inverse && this.compileProgram(inverse);\n\n\t    var type = this.classifySexpr(block);\n\n\t    if (type === 'helper') {\n\t      this.helperSexpr(block, program, inverse);\n\t    } else if (type === 'simple') {\n\t      this.simpleSexpr(block);\n\n\t      // now that the simple mustache is resolved, we need to\n\t      // evaluate it by executing `blockHelperMissing`\n\t      this.opcode('pushProgram', program);\n\t      this.opcode('pushProgram', inverse);\n\t      this.opcode('emptyHash');\n\t      this.opcode('blockValue', block.path.original);\n\t    } else {\n\t      this.ambiguousSexpr(block, program, inverse);\n\n\t      // now that the simple mustache is resolved, we need to\n\t      // evaluate it by executing `blockHelperMissing`\n\t      this.opcode('pushProgram', program);\n\t      this.opcode('pushProgram', inverse);\n\t      this.opcode('emptyHash');\n\t      this.opcode('ambiguousBlockValue');\n\t    }\n\n\t    this.opcode('append');\n\t  },\n\n\t  DecoratorBlock: function DecoratorBlock(decorator) {\n\t    var program = decorator.program && this.compileProgram(decorator.program);\n\t    var params = this.setupFullMustacheParams(decorator, program, undefined),\n\t        path = decorator.path;\n\n\t    this.useDecorators = true;\n\t    this.opcode('registerDecorator', params.length, path.original);\n\t  },\n\n\t  PartialStatement: function PartialStatement(partial) {\n\t    this.usePartial = true;\n\n\t    var program = partial.program;\n\t    if (program) {\n\t      program = this.compileProgram(partial.program);\n\t    }\n\n\t    var params = partial.params;\n\t    if (params.length > 1) {\n\t      throw new _exception2['default']('Unsupported number of partial arguments: ' + params.length, partial);\n\t    } else if (!params.length) {\n\t      if (this.options.explicitPartialContext) {\n\t        this.opcode('pushLiteral', 'undefined');\n\t      } else {\n\t        params.push({ type: 'PathExpression', parts: [], depth: 0 });\n\t      }\n\t    }\n\n\t    var partialName = partial.name.original,\n\t        isDynamic = partial.name.type === 'SubExpression';\n\t    if (isDynamic) {\n\t      this.accept(partial.name);\n\t    }\n\n\t    this.setupFullMustacheParams(partial, program, undefined, true);\n\n\t    var indent = partial.indent || '';\n\t    if (this.options.preventIndent && indent) {\n\t      this.opcode('appendContent', indent);\n\t      indent = '';\n\t    }\n\n\t    this.opcode('invokePartial', isDynamic, partialName, indent);\n\t    this.opcode('append');\n\t  },\n\t  PartialBlockStatement: function PartialBlockStatement(partialBlock) {\n\t    this.PartialStatement(partialBlock);\n\t  },\n\n\t  MustacheStatement: function MustacheStatement(mustache) {\n\t    this.SubExpression(mustache);\n\n\t    if (mustache.escaped && !this.options.noEscape) {\n\t      this.opcode('appendEscaped');\n\t    } else {\n\t      this.opcode('append');\n\t    }\n\t  },\n\t  Decorator: function Decorator(decorator) {\n\t    this.DecoratorBlock(decorator);\n\t  },\n\n\t  ContentStatement: function ContentStatement(content) {\n\t    if (content.value) {\n\t      this.opcode('appendContent', content.value);\n\t    }\n\t  },\n\n\t  CommentStatement: function CommentStatement() {},\n\n\t  SubExpression: function SubExpression(sexpr) {\n\t    transformLiteralToPath(sexpr);\n\t    var type = this.classifySexpr(sexpr);\n\n\t    if (type === 'simple') {\n\t      this.simpleSexpr(sexpr);\n\t    } else if (type === 'helper') {\n\t      this.helperSexpr(sexpr);\n\t    } else {\n\t      this.ambiguousSexpr(sexpr);\n\t    }\n\t  },\n\t  ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {\n\t    var path = sexpr.path,\n\t        name = path.parts[0],\n\t        isBlock = program != null || inverse != null;\n\n\t    this.opcode('getContext', path.depth);\n\n\t    this.opcode('pushProgram', program);\n\t    this.opcode('pushProgram', inverse);\n\n\t    path.strict = true;\n\t    this.accept(path);\n\n\t    this.opcode('invokeAmbiguous', name, isBlock);\n\t  },\n\n\t  simpleSexpr: function simpleSexpr(sexpr) {\n\t    var path = sexpr.path;\n\t    path.strict = true;\n\t    this.accept(path);\n\t    this.opcode('resolvePossibleLambda');\n\t  },\n\n\t  helperSexpr: function helperSexpr(sexpr, program, inverse) {\n\t    var params = this.setupFullMustacheParams(sexpr, program, inverse),\n\t        path = sexpr.path,\n\t        name = path.parts[0];\n\n\t    if (this.options.knownHelpers[name]) {\n\t      this.opcode('invokeKnownHelper', params.length, name);\n\t    } else if (this.options.knownHelpersOnly) {\n\t      throw new _exception2['default']('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);\n\t    } else {\n\t      path.strict = true;\n\t      path.falsy = true;\n\n\t      this.accept(path);\n\t      this.opcode('invokeHelper', params.length, path.original, _ast2['default'].helpers.simpleId(path));\n\t    }\n\t  },\n\n\t  PathExpression: function PathExpression(path) {\n\t    this.addDepth(path.depth);\n\t    this.opcode('getContext', path.depth);\n\n\t    var name = path.parts[0],\n\t        scoped = _ast2['default'].helpers.scopedId(path),\n\t        blockParamId = !path.depth && !scoped && this.blockParamIndex(name);\n\n\t    if (blockParamId) {\n\t      this.opcode('lookupBlockParam', blockParamId, path.parts);\n\t    } else if (!name) {\n\t      // Context reference, i.e. `{{foo .}}` or `{{foo ..}}`\n\t      this.opcode('pushContext');\n\t    } else if (path.data) {\n\t      this.options.data = true;\n\t      this.opcode('lookupData', path.depth, path.parts, path.strict);\n\t    } else {\n\t      this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);\n\t    }\n\t  },\n\n\t  StringLiteral: function StringLiteral(string) {\n\t    this.opcode('pushString', string.value);\n\t  },\n\n\t  NumberLiteral: function NumberLiteral(number) {\n\t    this.opcode('pushLiteral', number.value);\n\t  },\n\n\t  BooleanLiteral: function BooleanLiteral(bool) {\n\t    this.opcode('pushLiteral', bool.value);\n\t  },\n\n\t  UndefinedLiteral: function UndefinedLiteral() {\n\t    this.opcode('pushLiteral', 'undefined');\n\t  },\n\n\t  NullLiteral: function NullLiteral() {\n\t    this.opcode('pushLiteral', 'null');\n\t  },\n\n\t  Hash: function Hash(hash) {\n\t    var pairs = hash.pairs,\n\t        i = 0,\n\t        l = pairs.length;\n\n\t    this.opcode('pushHash');\n\n\t    for (; i < l; i++) {\n\t      this.pushParam(pairs[i].value);\n\t    }\n\t    while (i--) {\n\t      this.opcode('assignToHash', pairs[i].key);\n\t    }\n\t    this.opcode('popHash');\n\t  },\n\n\t  // HELPERS\n\t  opcode: function opcode(name) {\n\t    this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });\n\t  },\n\n\t  addDepth: function addDepth(depth) {\n\t    if (!depth) {\n\t      return;\n\t    }\n\n\t    this.useDepths = true;\n\t  },\n\n\t  classifySexpr: function classifySexpr(sexpr) {\n\t    var isSimple = _ast2['default'].helpers.simpleId(sexpr.path);\n\n\t    var isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);\n\n\t    // a mustache is an eligible helper if:\n\t    // * its id is simple (a single part, not `this` or `..`)\n\t    var isHelper = !isBlockParam && _ast2['default'].helpers.helperExpression(sexpr);\n\n\t    // if a mustache is an eligible helper but not a definite\n\t    // helper, it is ambiguous, and will be resolved in a later\n\t    // pass or at runtime.\n\t    var isEligible = !isBlockParam && (isHelper || isSimple);\n\n\t    // if ambiguous, we can possibly resolve the ambiguity now\n\t    // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.\n\t    if (isEligible && !isHelper) {\n\t      var _name2 = sexpr.path.parts[0],\n\t          options = this.options;\n\n\t      if (options.knownHelpers[_name2]) {\n\t        isHelper = true;\n\t      } else if (options.knownHelpersOnly) {\n\t        isEligible = false;\n\t      }\n\t    }\n\n\t    if (isHelper) {\n\t      return 'helper';\n\t    } else if (isEligible) {\n\t      return 'ambiguous';\n\t    } else {\n\t      return 'simple';\n\t    }\n\t  },\n\n\t  pushParams: function pushParams(params) {\n\t    for (var i = 0, l = params.length; i < l; i++) {\n\t      this.pushParam(params[i]);\n\t    }\n\t  },\n\n\t  pushParam: function pushParam(val) {\n\t    var value = val.value != null ? val.value : val.original || '';\n\n\t    if (this.stringParams) {\n\t      if (value.replace) {\n\t        value = value.replace(/^(\\.?\\.\\/)*/g, '').replace(/\\//g, '.');\n\t      }\n\n\t      if (val.depth) {\n\t        this.addDepth(val.depth);\n\t      }\n\t      this.opcode('getContext', val.depth || 0);\n\t      this.opcode('pushStringParam', value, val.type);\n\n\t      if (val.type === 'SubExpression') {\n\t        // SubExpressions get evaluated and passed in\n\t        // in string params mode.\n\t        this.accept(val);\n\t      }\n\t    } else {\n\t      if (this.trackIds) {\n\t        var blockParamIndex = undefined;\n\t        if (val.parts && !_ast2['default'].helpers.scopedId(val) && !val.depth) {\n\t          blockParamIndex = this.blockParamIndex(val.parts[0]);\n\t        }\n\t        if (blockParamIndex) {\n\t          var blockParamChild = val.parts.slice(1).join('.');\n\t          this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);\n\t        } else {\n\t          value = val.original || value;\n\t          if (value.replace) {\n\t            value = value.replace(/^this(?:\\.|$)/, '').replace(/^\\.\\//, '').replace(/^\\.$/, '');\n\t          }\n\n\t          this.opcode('pushId', val.type, value);\n\t        }\n\t      }\n\t      this.accept(val);\n\t    }\n\t  },\n\n\t  setupFullMustacheParams: function setupFullMustacheParams(sexpr, program, inverse, omitEmpty) {\n\t    var params = sexpr.params;\n\t    this.pushParams(params);\n\n\t    this.opcode('pushProgram', program);\n\t    this.opcode('pushProgram', inverse);\n\n\t    if (sexpr.hash) {\n\t      this.accept(sexpr.hash);\n\t    } else {\n\t      this.opcode('emptyHash', omitEmpty);\n\t    }\n\n\t    return params;\n\t  },\n\n\t  blockParamIndex: function blockParamIndex(name) {\n\t    for (var depth = 0, len = this.options.blockParams.length; depth < len; depth++) {\n\t      var blockParams = this.options.blockParams[depth],\n\t          param = blockParams && _utils.indexOf(blockParams, name);\n\t      if (blockParams && param >= 0) {\n\t        return [depth, param];\n\t      }\n\t    }\n\t  }\n\t};\n\n\tfunction precompile(input, options, env) {\n\t  if (input == null || typeof input !== 'string' && input.type !== 'Program') {\n\t    throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input);\n\t  }\n\n\t  options = options || {};\n\t  if (!('data' in options)) {\n\t    options.data = true;\n\t  }\n\t  if (options.compat) {\n\t    options.useDepths = true;\n\t  }\n\n\t  var ast = env.parse(input, options),\n\t      environment = new env.Compiler().compile(ast, options);\n\t  return new env.JavaScriptCompiler().compile(environment, options);\n\t}\n\n\tfunction compile(input, options, env) {\n\t  if (options === undefined) options = {};\n\n\t  if (input == null || typeof input !== 'string' && input.type !== 'Program') {\n\t    throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);\n\t  }\n\n\t  if (!('data' in options)) {\n\t    options.data = true;\n\t  }\n\t  if (options.compat) {\n\t    options.useDepths = true;\n\t  }\n\n\t  var compiled = undefined;\n\n\t  function compileInput() {\n\t    var ast = env.parse(input, options),\n\t        environment = new env.Compiler().compile(ast, options),\n\t        templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);\n\t    return env.template(templateSpec);\n\t  }\n\n\t  // Template is only compiled on first use and cached after that point.\n\t  function ret(context, execOptions) {\n\t    if (!compiled) {\n\t      compiled = compileInput();\n\t    }\n\t    return compiled.call(this, context, execOptions);\n\t  }\n\t  ret._setup = function (setupOptions) {\n\t    if (!compiled) {\n\t      compiled = compileInput();\n\t    }\n\t    return compiled._setup(setupOptions);\n\t  };\n\t  ret._child = function (i, data, blockParams, depths) {\n\t    if (!compiled) {\n\t      compiled = compileInput();\n\t    }\n\t    return compiled._child(i, data, blockParams, depths);\n\t  };\n\t  return ret;\n\t}\n\n\tfunction argEquals(a, b) {\n\t  if (a === b) {\n\t    return true;\n\t  }\n\n\t  if (_utils.isArray(a) && _utils.isArray(b) && a.length === b.length) {\n\t    for (var i = 0; i < a.length; i++) {\n\t      if (!argEquals(a[i], b[i])) {\n\t        return false;\n\t      }\n\t    }\n\t    return true;\n\t  }\n\t}\n\n\tfunction transformLiteralToPath(sexpr) {\n\t  if (!sexpr.path.parts) {\n\t    var literal = sexpr.path;\n\t    // Casting to string here to make false and 0 literal values play nicely with the rest\n\t    // of the system.\n\t    sexpr.path = {\n\t      type: 'PathExpression',\n\t      data: false,\n\t      depth: 0,\n\t      parts: [literal.original + ''],\n\t      original: literal.original + '',\n\t      loc: literal.loc\n\t    };\n\t  }\n\t}\n\n/***/ },\n/* 28 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t'use strict';\n\n\tvar _interopRequireDefault = __webpack_require__(1)['default'];\n\n\texports.__esModule = true;\n\n\tvar _base = __webpack_require__(4);\n\n\tvar _exception = __webpack_require__(6);\n\n\tvar _exception2 = _interopRequireDefault(_exception);\n\n\tvar _utils = __webpack_require__(5);\n\n\tvar _codeGen = __webpack_require__(29);\n\n\tvar _codeGen2 = _interopRequireDefault(_codeGen);\n\n\tfunction Literal(value) {\n\t  this.value = value;\n\t}\n\n\tfunction JavaScriptCompiler() {}\n\n\tJavaScriptCompiler.prototype = {\n\t  // PUBLIC API: You can override these methods in a subclass to provide\n\t  // alternative compiled forms for name lookup and buffering semantics\n\t  nameLookup: function nameLookup(parent, name /* , type*/) {\n\t    if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {\n\t      return [parent, '.', name];\n\t    } else {\n\t      return [parent, '[', JSON.stringify(name), ']'];\n\t    }\n\t  },\n\t  depthedLookup: function depthedLookup(name) {\n\t    return [this.aliasable('container.lookup'), '(depths, \"', name, '\")'];\n\t  },\n\n\t  compilerInfo: function compilerInfo() {\n\t    var revision = _base.COMPILER_REVISION,\n\t        versions = _base.REVISION_CHANGES[revision];\n\t    return [revision, versions];\n\t  },\n\n\t  appendToBuffer: function appendToBuffer(source, location, explicit) {\n\t    // Force a source as this simplifies the merge logic.\n\t    if (!_utils.isArray(source)) {\n\t      source = [source];\n\t    }\n\t    source = this.source.wrap(source, location);\n\n\t    if (this.environment.isSimple) {\n\t      return ['return ', source, ';'];\n\t    } else if (explicit) {\n\t      // This is a case where the buffer operation occurs as a child of another\n\t      // construct, generally braces. We have to explicitly output these buffer\n\t      // operations to ensure that the emitted code goes in the correct location.\n\t      return ['buffer += ', source, ';'];\n\t    } else {\n\t      source.appendToBuffer = true;\n\t      return source;\n\t    }\n\t  },\n\n\t  initializeBuffer: function initializeBuffer() {\n\t    return this.quotedString('');\n\t  },\n\t  // END PUBLIC API\n\n\t  compile: function compile(environment, options, context, asObject) {\n\t    this.environment = environment;\n\t    this.options = options;\n\t    this.stringParams = this.options.stringParams;\n\t    this.trackIds = this.options.trackIds;\n\t    this.precompile = !asObject;\n\n\t    this.name = this.environment.name;\n\t    this.isChild = !!context;\n\t    this.context = context || {\n\t      decorators: [],\n\t      programs: [],\n\t      environments: []\n\t    };\n\n\t    this.preamble();\n\n\t    this.stackSlot = 0;\n\t    this.stackVars = [];\n\t    this.aliases = {};\n\t    this.registers = { list: [] };\n\t    this.hashes = [];\n\t    this.compileStack = [];\n\t    this.inlineStack = [];\n\t    this.blockParams = [];\n\n\t    this.compileChildren(environment, options);\n\n\t    this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;\n\t    this.useBlockParams = this.useBlockParams || environment.useBlockParams;\n\n\t    var opcodes = environment.opcodes,\n\t        opcode = undefined,\n\t        firstLoc = undefined,\n\t        i = undefined,\n\t        l = undefined;\n\n\t    for (i = 0, l = opcodes.length; i < l; i++) {\n\t      opcode = opcodes[i];\n\n\t      this.source.currentLocation = opcode.loc;\n\t      firstLoc = firstLoc || opcode.loc;\n\t      this[opcode.opcode].apply(this, opcode.args);\n\t    }\n\n\t    // Flush any trailing content that might be pending.\n\t    this.source.currentLocation = firstLoc;\n\t    this.pushSource('');\n\n\t    /* istanbul ignore next */\n\t    if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {\n\t      throw new _exception2['default']('Compile completed with content left on stack');\n\t    }\n\n\t    if (!this.decorators.isEmpty()) {\n\t      this.useDecorators = true;\n\n\t      this.decorators.prepend('var decorators = container.decorators;\\n');\n\t      this.decorators.push('return fn;');\n\n\t      if (asObject) {\n\t        this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);\n\t      } else {\n\t        this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\\n');\n\t        this.decorators.push('}\\n');\n\t        this.decorators = this.decorators.merge();\n\t      }\n\t    } else {\n\t      this.decorators = undefined;\n\t    }\n\n\t    var fn = this.createFunctionContext(asObject);\n\t    if (!this.isChild) {\n\t      var ret = {\n\t        compiler: this.compilerInfo(),\n\t        main: fn\n\t      };\n\n\t      if (this.decorators) {\n\t        ret.main_d = this.decorators; // eslint-disable-line camelcase\n\t        ret.useDecorators = true;\n\t      }\n\n\t      var _context = this.context;\n\t      var programs = _context.programs;\n\t      var decorators = _context.decorators;\n\n\t      for (i = 0, l = programs.length; i < l; i++) {\n\t        if (programs[i]) {\n\t          ret[i] = programs[i];\n\t          if (decorators[i]) {\n\t            ret[i + '_d'] = decorators[i];\n\t            ret.useDecorators = true;\n\t          }\n\t        }\n\t      }\n\n\t      if (this.environment.usePartial) {\n\t        ret.usePartial = true;\n\t      }\n\t      if (this.options.data) {\n\t        ret.useData = true;\n\t      }\n\t      if (this.useDepths) {\n\t        ret.useDepths = true;\n\t      }\n\t      if (this.useBlockParams) {\n\t        ret.useBlockParams = true;\n\t      }\n\t      if (this.options.compat) {\n\t        ret.compat = true;\n\t      }\n\n\t      if (!asObject) {\n\t        ret.compiler = JSON.stringify(ret.compiler);\n\n\t        this.source.currentLocation = { start: { line: 1, column: 0 } };\n\t        ret = this.objectLiteral(ret);\n\n\t        if (options.srcName) {\n\t          ret = ret.toStringWithSourceMap({ file: options.destName });\n\t          ret.map = ret.map && ret.map.toString();\n\t        } else {\n\t          ret = ret.toString();\n\t        }\n\t      } else {\n\t        ret.compilerOptions = this.options;\n\t      }\n\n\t      return ret;\n\t    } else {\n\t      return fn;\n\t    }\n\t  },\n\n\t  preamble: function preamble() {\n\t    // track the last context pushed into place to allow skipping the\n\t    // getContext opcode when it would be a noop\n\t    this.lastContext = 0;\n\t    this.source = new _codeGen2['default'](this.options.srcName);\n\t    this.decorators = new _codeGen2['default'](this.options.srcName);\n\t  },\n\n\t  createFunctionContext: function createFunctionContext(asObject) {\n\t    var varDeclarations = '';\n\n\t    var locals = this.stackVars.concat(this.registers.list);\n\t    if (locals.length > 0) {\n\t      varDeclarations += ', ' + locals.join(', ');\n\t    }\n\n\t    // Generate minimizer alias mappings\n\t    //\n\t    // When using true SourceNodes, this will update all references to the given alias\n\t    // as the source nodes are reused in situ. For the non-source node compilation mode,\n\t    // aliases will not be used, but this case is already being run on the client and\n\t    // we aren't concern about minimizing the template size.\n\t    var aliasCount = 0;\n\t    for (var alias in this.aliases) {\n\t      // eslint-disable-line guard-for-in\n\t      var node = this.aliases[alias];\n\n\t      if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {\n\t        varDeclarations += ', alias' + ++aliasCount + '=' + alias;\n\t        node.children[0] = 'alias' + aliasCount;\n\t      }\n\t    }\n\n\t    var params = ['container', 'depth0', 'helpers', 'partials', 'data'];\n\n\t    if (this.useBlockParams || this.useDepths) {\n\t      params.push('blockParams');\n\t    }\n\t    if (this.useDepths) {\n\t      params.push('depths');\n\t    }\n\n\t    // Perform a second pass over the output to merge content when possible\n\t    var source = this.mergeSource(varDeclarations);\n\n\t    if (asObject) {\n\t      params.push(source);\n\n\t      return Function.apply(this, params);\n\t    } else {\n\t      return this.source.wrap(['function(', params.join(','), ') {\\n  ', source, '}']);\n\t    }\n\t  },\n\t  mergeSource: function mergeSource(varDeclarations) {\n\t    var isSimple = this.environment.isSimple,\n\t        appendOnly = !this.forceBuffer,\n\t        appendFirst = undefined,\n\t        sourceSeen = undefined,\n\t        bufferStart = undefined,\n\t        bufferEnd = undefined;\n\t    this.source.each(function (line) {\n\t      if (line.appendToBuffer) {\n\t        if (bufferStart) {\n\t          line.prepend('  + ');\n\t        } else {\n\t          bufferStart = line;\n\t        }\n\t        bufferEnd = line;\n\t      } else {\n\t        if (bufferStart) {\n\t          if (!sourceSeen) {\n\t            appendFirst = true;\n\t          } else {\n\t            bufferStart.prepend('buffer += ');\n\t          }\n\t          bufferEnd.add(';');\n\t          bufferStart = bufferEnd = undefined;\n\t        }\n\n\t        sourceSeen = true;\n\t        if (!isSimple) {\n\t          appendOnly = false;\n\t        }\n\t      }\n\t    });\n\n\t    if (appendOnly) {\n\t      if (bufferStart) {\n\t        bufferStart.prepend('return ');\n\t        bufferEnd.add(';');\n\t      } else if (!sourceSeen) {\n\t        this.source.push('return \"\";');\n\t      }\n\t    } else {\n\t      varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer());\n\n\t      if (bufferStart) {\n\t        bufferStart.prepend('return buffer + ');\n\t        bufferEnd.add(';');\n\t      } else {\n\t        this.source.push('return buffer;');\n\t      }\n\t    }\n\n\t    if (varDeclarations) {\n\t      this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\\n'));\n\t    }\n\n\t    return this.source.merge();\n\t  },\n\n\t  // [blockValue]\n\t  //\n\t  // On stack, before: hash, inverse, program, value\n\t  // On stack, after: return value of blockHelperMissing\n\t  //\n\t  // The purpose of this opcode is to take a block of the form\n\t  // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and\n\t  // replace it on the stack with the result of properly\n\t  // invoking blockHelperMissing.\n\t  blockValue: function blockValue(name) {\n\t    var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),\n\t        params = [this.contextName(0)];\n\t    this.setupHelperArgs(name, 0, params);\n\n\t    var blockName = this.popStack();\n\t    params.splice(1, 0, blockName);\n\n\t    this.push(this.source.functionCall(blockHelperMissing, 'call', params));\n\t  },\n\n\t  // [ambiguousBlockValue]\n\t  //\n\t  // On stack, before: hash, inverse, program, value\n\t  // Compiler value, before: lastHelper=value of last found helper, if any\n\t  // On stack, after, if no lastHelper: same as [blockValue]\n\t  // On stack, after, if lastHelper: value\n\t  ambiguousBlockValue: function ambiguousBlockValue() {\n\t    // We're being a bit cheeky and reusing the options value from the prior exec\n\t    var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),\n\t        params = [this.contextName(0)];\n\t    this.setupHelperArgs('', 0, params, true);\n\n\t    this.flushInline();\n\n\t    var current = this.topStack();\n\t    params.splice(1, 0, current);\n\n\t    this.pushSource(['if (!', this.lastHelper, ') { ', current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params), '}']);\n\t  },\n\n\t  // [appendContent]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: ...\n\t  //\n\t  // Appends the string value of `content` to the current buffer\n\t  appendContent: function appendContent(content) {\n\t    if (this.pendingContent) {\n\t      content = this.pendingContent + content;\n\t    } else {\n\t      this.pendingLocation = this.source.currentLocation;\n\t    }\n\n\t    this.pendingContent = content;\n\t  },\n\n\t  // [append]\n\t  //\n\t  // On stack, before: value, ...\n\t  // On stack, after: ...\n\t  //\n\t  // Coerces `value` to a String and appends it to the current buffer.\n\t  //\n\t  // If `value` is truthy, or 0, it is coerced into a string and appended\n\t  // Otherwise, the empty string is appended\n\t  append: function append() {\n\t    if (this.isInline()) {\n\t      this.replaceStack(function (current) {\n\t        return [' != null ? ', current, ' : \"\"'];\n\t      });\n\n\t      this.pushSource(this.appendToBuffer(this.popStack()));\n\t    } else {\n\t      var local = this.popStack();\n\t      this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);\n\t      if (this.environment.isSimple) {\n\t        this.pushSource(['else { ', this.appendToBuffer(\"''\", undefined, true), ' }']);\n\t      }\n\t    }\n\t  },\n\n\t  // [appendEscaped]\n\t  //\n\t  // On stack, before: value, ...\n\t  // On stack, after: ...\n\t  //\n\t  // Escape `value` and append it to the buffer\n\t  appendEscaped: function appendEscaped() {\n\t    this.pushSource(this.appendToBuffer([this.aliasable('container.escapeExpression'), '(', this.popStack(), ')']));\n\t  },\n\n\t  // [getContext]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: ...\n\t  // Compiler value, after: lastContext=depth\n\t  //\n\t  // Set the value of the `lastContext` compiler value to the depth\n\t  getContext: function getContext(depth) {\n\t    this.lastContext = depth;\n\t  },\n\n\t  // [pushContext]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: currentContext, ...\n\t  //\n\t  // Pushes the value of the current context onto the stack.\n\t  pushContext: function pushContext() {\n\t    this.pushStackLiteral(this.contextName(this.lastContext));\n\t  },\n\n\t  // [lookupOnContext]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: currentContext[name], ...\n\t  //\n\t  // Looks up the value of `name` on the current context and pushes\n\t  // it onto the stack.\n\t  lookupOnContext: function lookupOnContext(parts, falsy, strict, scoped) {\n\t    var i = 0;\n\n\t    if (!scoped && this.options.compat && !this.lastContext) {\n\t      // The depthed query is expected to handle the undefined logic for the root level that\n\t      // is implemented below, so we evaluate that directly in compat mode\n\t      this.push(this.depthedLookup(parts[i++]));\n\t    } else {\n\t      this.pushContext();\n\t    }\n\n\t    this.resolvePath('context', parts, i, falsy, strict);\n\t  },\n\n\t  // [lookupBlockParam]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: blockParam[name], ...\n\t  //\n\t  // Looks up the value of `parts` on the given block param and pushes\n\t  // it onto the stack.\n\t  lookupBlockParam: function lookupBlockParam(blockParamId, parts) {\n\t    this.useBlockParams = true;\n\n\t    this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);\n\t    this.resolvePath('context', parts, 1);\n\t  },\n\n\t  // [lookupData]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: data, ...\n\t  //\n\t  // Push the data lookup operator\n\t  lookupData: function lookupData(depth, parts, strict) {\n\t    if (!depth) {\n\t      this.pushStackLiteral('data');\n\t    } else {\n\t      this.pushStackLiteral('container.data(data, ' + depth + ')');\n\t    }\n\n\t    this.resolvePath('data', parts, 0, true, strict);\n\t  },\n\n\t  resolvePath: function resolvePath(type, parts, i, falsy, strict) {\n\t    // istanbul ignore next\n\n\t    var _this = this;\n\n\t    if (this.options.strict || this.options.assumeObjects) {\n\t      this.push(strictLookup(this.options.strict && strict, this, parts, type));\n\t      return;\n\t    }\n\n\t    var len = parts.length;\n\t    for (; i < len; i++) {\n\t      /* eslint-disable no-loop-func */\n\t      this.replaceStack(function (current) {\n\t        var lookup = _this.nameLookup(current, parts[i], type);\n\t        // We want to ensure that zero and false are handled properly if the context (falsy flag)\n\t        // needs to have the special handling for these values.\n\t        if (!falsy) {\n\t          return [' != null ? ', lookup, ' : ', current];\n\t        } else {\n\t          // Otherwise we can use generic falsy handling\n\t          return [' && ', lookup];\n\t        }\n\t      });\n\t      /* eslint-enable no-loop-func */\n\t    }\n\t  },\n\n\t  // [resolvePossibleLambda]\n\t  //\n\t  // On stack, before: value, ...\n\t  // On stack, after: resolved value, ...\n\t  //\n\t  // If the `value` is a lambda, replace it on the stack by\n\t  // the return value of the lambda\n\t  resolvePossibleLambda: function resolvePossibleLambda() {\n\t    this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);\n\t  },\n\n\t  // [pushStringParam]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: string, currentContext, ...\n\t  //\n\t  // This opcode is designed for use in string mode, which\n\t  // provides the string value of a parameter along with its\n\t  // depth rather than resolving it immediately.\n\t  pushStringParam: function pushStringParam(string, type) {\n\t    this.pushContext();\n\t    this.pushString(type);\n\n\t    // If it's a subexpression, the string result\n\t    // will be pushed after this opcode.\n\t    if (type !== 'SubExpression') {\n\t      if (typeof string === 'string') {\n\t        this.pushString(string);\n\t      } else {\n\t        this.pushStackLiteral(string);\n\t      }\n\t    }\n\t  },\n\n\t  emptyHash: function emptyHash(omitEmpty) {\n\t    if (this.trackIds) {\n\t      this.push('{}'); // hashIds\n\t    }\n\t    if (this.stringParams) {\n\t      this.push('{}'); // hashContexts\n\t      this.push('{}'); // hashTypes\n\t    }\n\t    this.pushStackLiteral(omitEmpty ? 'undefined' : '{}');\n\t  },\n\t  pushHash: function pushHash() {\n\t    if (this.hash) {\n\t      this.hashes.push(this.hash);\n\t    }\n\t    this.hash = { values: [], types: [], contexts: [], ids: [] };\n\t  },\n\t  popHash: function popHash() {\n\t    var hash = this.hash;\n\t    this.hash = this.hashes.pop();\n\n\t    if (this.trackIds) {\n\t      this.push(this.objectLiteral(hash.ids));\n\t    }\n\t    if (this.stringParams) {\n\t      this.push(this.objectLiteral(hash.contexts));\n\t      this.push(this.objectLiteral(hash.types));\n\t    }\n\n\t    this.push(this.objectLiteral(hash.values));\n\t  },\n\n\t  // [pushString]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: quotedString(string), ...\n\t  //\n\t  // Push a quoted version of `string` onto the stack\n\t  pushString: function pushString(string) {\n\t    this.pushStackLiteral(this.quotedString(string));\n\t  },\n\n\t  // [pushLiteral]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: value, ...\n\t  //\n\t  // Pushes a value onto the stack. This operation prevents\n\t  // the compiler from creating a temporary variable to hold\n\t  // it.\n\t  pushLiteral: function pushLiteral(value) {\n\t    this.pushStackLiteral(value);\n\t  },\n\n\t  // [pushProgram]\n\t  //\n\t  // On stack, before: ...\n\t  // On stack, after: program(guid), ...\n\t  //\n\t  // Push a program expression onto the stack. This takes\n\t  // a compile-time guid and converts it into a runtime-accessible\n\t  // expression.\n\t  pushProgram: function pushProgram(guid) {\n\t    if (guid != null) {\n\t      this.pushStackLiteral(this.programExpression(guid));\n\t    } else {\n\t      this.pushStackLiteral(null);\n\t    }\n\t  },\n\n\t  // [registerDecorator]\n\t  //\n\t  // On stack, before: hash, program, params..., ...\n\t  // On stack, after: ...\n\t  //\n\t  // Pops off the decorator's parameters, invokes the decorator,\n\t  // and inserts the decorator into the decorators list.\n\t  registerDecorator: function registerDecorator(paramSize, name) {\n\t    var foundDecorator = this.nameLookup('decorators', name, 'decorator'),\n\t        options = this.setupHelperArgs(name, paramSize);\n\n\t    this.decorators.push(['fn = ', this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]), ' || fn;']);\n\t  },\n\n\t  // [invokeHelper]\n\t  //\n\t  // On stack, before: hash, inverse, program, params..., ...\n\t  // On stack, after: result of helper invocation\n\t  //\n\t  // Pops off the helper's parameters, invokes the helper,\n\t  // and pushes the helper's return value onto the stack.\n\t  //\n\t  // If the helper is not found, `helperMissing` is called.\n\t  invokeHelper: function invokeHelper(paramSize, name, isSimple) {\n\t    var nonHelper = this.popStack(),\n\t        helper = this.setupHelper(paramSize, name),\n\t        simple = isSimple ? [helper.name, ' || '] : '';\n\n\t    var lookup = ['('].concat(simple, nonHelper);\n\t    if (!this.options.strict) {\n\t      lookup.push(' || ', this.aliasable('helpers.helperMissing'));\n\t    }\n\t    lookup.push(')');\n\n\t    this.push(this.source.functionCall(lookup, 'call', helper.callParams));\n\t  },\n\n\t  // [invokeKnownHelper]\n\t  //\n\t  // On stack, before: hash, inverse, program, params..., ...\n\t  // On stack, after: result of helper invocation\n\t  //\n\t  // This operation is used when the helper is known to exist,\n\t  // so a `helperMissing` fallback is not required.\n\t  invokeKnownHelper: function invokeKnownHelper(paramSize, name) {\n\t    var helper = this.setupHelper(paramSize, name);\n\t    this.push(this.source.functionCall(helper.name, 'call', helper.callParams));\n\t  },\n\n\t  // [invokeAmbiguous]\n\t  //\n\t  // On stack, before: hash, inverse, program, params..., ...\n\t  // On stack, after: result of disambiguation\n\t  //\n\t  // This operation is used when an expression like `{{foo}}`\n\t  // is provided, but we don't know at compile-time whether it\n\t  // is a helper or a path.\n\t  //\n\t  // This operation emits more code than the other options,\n\t  // and can be avoided by passing the `knownHelpers` and\n\t  // `knownHelpersOnly` flags at compile-time.\n\t  invokeAmbiguous: function invokeAmbiguous(name, helperCall) {\n\t    this.useRegister('helper');\n\n\t    var nonHelper = this.popStack();\n\n\t    this.emptyHash();\n\t    var helper = this.setupHelper(0, name, helperCall);\n\n\t    var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');\n\n\t    var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];\n\t    if (!this.options.strict) {\n\t      lookup[0] = '(helper = ';\n\t      lookup.push(' != null ? helper : ', this.aliasable('helpers.helperMissing'));\n\t    }\n\n\t    this.push(['(', lookup, helper.paramsInit ? ['),(', helper.paramsInit] : [], '),', '(typeof helper === ', this.aliasable('\"function\"'), ' ? ', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))']);\n\t  },\n\n\t  // [invokePartial]\n\t  //\n\t  // On stack, before: context, ...\n\t  // On stack after: result of partial invocation\n\t  //\n\t  // This operation pops off a context, invokes a partial with that context,\n\t  // and pushes the result of the invocation back.\n\t  invokePartial: function invokePartial(isDynamic, name, indent) {\n\t    var params = [],\n\t        options = this.setupParams(name, 1, params);\n\n\t    if (isDynamic) {\n\t      name = this.popStack();\n\t      delete options.name;\n\t    }\n\n\t    if (indent) {\n\t      options.indent = JSON.stringify(indent);\n\t    }\n\t    options.helpers = 'helpers';\n\t    options.partials = 'partials';\n\t    options.decorators = 'container.decorators';\n\n\t    if (!isDynamic) {\n\t      params.unshift(this.nameLookup('partials', name, 'partial'));\n\t    } else {\n\t      params.unshift(name);\n\t    }\n\n\t    if (this.options.compat) {\n\t      options.depths = 'depths';\n\t    }\n\t    options = this.objectLiteral(options);\n\t    params.push(options);\n\n\t    this.push(this.source.functionCall('container.invokePartial', '', params));\n\t  },\n\n\t  // [assignToHash]\n\t  //\n\t  // On stack, before: value, ..., hash, ...\n\t  // On stack, after: ..., hash, ...\n\t  //\n\t  // Pops a value off the stack and assigns it to the current hash\n\t  assignToHash: function assignToHash(key) {\n\t    var value = this.popStack(),\n\t        context = undefined,\n\t        type = undefined,\n\t        id = undefined;\n\n\t    if (this.trackIds) {\n\t      id = this.popStack();\n\t    }\n\t    if (this.stringParams) {\n\t      type = this.popStack();\n\t      context = this.popStack();\n\t    }\n\n\t    var hash = this.hash;\n\t    if (context) {\n\t      hash.contexts[key] = context;\n\t    }\n\t    if (type) {\n\t      hash.types[key] = type;\n\t    }\n\t    if (id) {\n\t      hash.ids[key] = id;\n\t    }\n\t    hash.values[key] = value;\n\t  },\n\n\t  pushId: function pushId(type, name, child) {\n\t    if (type === 'BlockParam') {\n\t      this.pushStackLiteral('blockParams[' + name[0] + '].path[' + name[1] + ']' + (child ? ' + ' + JSON.stringify('.' + child) : ''));\n\t    } else if (type === 'PathExpression') {\n\t      this.pushString(name);\n\t    } else if (type === 'SubExpression') {\n\t      this.pushStackLiteral('true');\n\t    } else {\n\t      this.pushStackLiteral('null');\n\t    }\n\t  },\n\n\t  // HELPERS\n\n\t  compiler: JavaScriptCompiler,\n\n\t  compileChildren: function compileChildren(environment, options) {\n\t    var children = environment.children,\n\t        child = undefined,\n\t        compiler = undefined;\n\n\t    for (var i = 0, l = children.length; i < l; i++) {\n\t      child = children[i];\n\t      compiler = new this.compiler(); // eslint-disable-line new-cap\n\n\t      var index = this.matchExistingProgram(child);\n\n\t      if (index == null) {\n\t        this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children\n\t        index = this.context.programs.length;\n\t        child.index = index;\n\t        child.name = 'program' + index;\n\t        this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);\n\t        this.context.decorators[index] = compiler.decorators;\n\t        this.context.environments[index] = child;\n\n\t        this.useDepths = this.useDepths || compiler.useDepths;\n\t        this.useBlockParams = this.useBlockParams || compiler.useBlockParams;\n\t      } else {\n\t        child.index = index;\n\t        child.name = 'program' + index;\n\n\t        this.useDepths = this.useDepths || child.useDepths;\n\t        this.useBlockParams = this.useBlockParams || child.useBlockParams;\n\t      }\n\t    }\n\t  },\n\t  matchExistingProgram: function matchExistingProgram(child) {\n\t    for (var i = 0, len = this.context.environments.length; i < len; i++) {\n\t      var environment = this.context.environments[i];\n\t      if (environment && environment.equals(child)) {\n\t        return i;\n\t      }\n\t    }\n\t  },\n\n\t  programExpression: function programExpression(guid) {\n\t    var child = this.environment.children[guid],\n\t        programParams = [child.index, 'data', child.blockParams];\n\n\t    if (this.useBlockParams || this.useDepths) {\n\t      programParams.push('blockParams');\n\t    }\n\t    if (this.useDepths) {\n\t      programParams.push('depths');\n\t    }\n\n\t    return 'container.program(' + programParams.join(', ') + ')';\n\t  },\n\n\t  useRegister: function useRegister(name) {\n\t    if (!this.registers[name]) {\n\t      this.registers[name] = true;\n\t      this.registers.list.push(name);\n\t    }\n\t  },\n\n\t  push: function push(expr) {\n\t    if (!(expr instanceof Literal)) {\n\t      expr = this.source.wrap(expr);\n\t    }\n\n\t    this.inlineStack.push(expr);\n\t    return expr;\n\t  },\n\n\t  pushStackLiteral: function pushStackLiteral(item) {\n\t    this.push(new Literal(item));\n\t  },\n\n\t  pushSource: function pushSource(source) {\n\t    if (this.pendingContent) {\n\t      this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));\n\t      this.pendingContent = undefined;\n\t    }\n\n\t    if (source) {\n\t      this.source.push(source);\n\t    }\n\t  },\n\n\t  replaceStack: function replaceStack(callback) {\n\t    var prefix = ['('],\n\t        stack = undefined,\n\t        createdStack = undefined,\n\t        usedLiteral = undefined;\n\n\t    /* istanbul ignore next */\n\t    if (!this.isInline()) {\n\t      throw new _exception2['default']('replaceStack on non-inline');\n\t    }\n\n\t    // We want to merge the inline statement into the replacement statement via ','\n\t    var top = this.popStack(true);\n\n\t    if (top instanceof Literal) {\n\t      // Literals do not need to be inlined\n\t      stack = [top.value];\n\t      prefix = ['(', stack];\n\t      usedLiteral = true;\n\t    } else {\n\t      // Get or create the current stack name for use by the inline\n\t      createdStack = true;\n\t      var _name = this.incrStack();\n\n\t      prefix = ['((', this.push(_name), ' = ', top, ')'];\n\t      stack = this.topStack();\n\t    }\n\n\t    var item = callback.call(this, stack);\n\n\t    if (!usedLiteral) {\n\t      this.popStack();\n\t    }\n\t    if (createdStack) {\n\t      this.stackSlot--;\n\t    }\n\t    this.push(prefix.concat(item, ')'));\n\t  },\n\n\t  incrStack: function incrStack() {\n\t    this.stackSlot++;\n\t    if (this.stackSlot > this.stackVars.length) {\n\t      this.stackVars.push('stack' + this.stackSlot);\n\t    }\n\t    return this.topStackName();\n\t  },\n\t  topStackName: function topStackName() {\n\t    return 'stack' + this.stackSlot;\n\t  },\n\t  flushInline: function flushInline() {\n\t    var inlineStack = this.inlineStack;\n\t    this.inlineStack = [];\n\t    for (var i = 0, len = inlineStack.length; i < len; i++) {\n\t      var entry = inlineStack[i];\n\t      /* istanbul ignore if */\n\t      if (entry instanceof Literal) {\n\t        this.compileStack.push(entry);\n\t      } else {\n\t        var stack = this.incrStack();\n\t        this.pushSource([stack, ' = ', entry, ';']);\n\t        this.compileStack.push(stack);\n\t      }\n\t    }\n\t  },\n\t  isInline: function isInline() {\n\t    return this.inlineStack.length;\n\t  },\n\n\t  popStack: function popStack(wrapped) {\n\t    var inline = this.isInline(),\n\t        item = (inline ? this.inlineStack : this.compileStack).pop();\n\n\t    if (!wrapped && item instanceof Literal) {\n\t      return item.value;\n\t    } else {\n\t      if (!inline) {\n\t        /* istanbul ignore next */\n\t        if (!this.stackSlot) {\n\t          throw new _exception2['default']('Invalid stack pop');\n\t        }\n\t        this.stackSlot--;\n\t      }\n\t      return item;\n\t    }\n\t  },\n\n\t  topStack: function topStack() {\n\t    var stack = this.isInline() ? this.inlineStack : this.compileStack,\n\t        item = stack[stack.length - 1];\n\n\t    /* istanbul ignore if */\n\t    if (item instanceof Literal) {\n\t      return item.value;\n\t    } else {\n\t      return item;\n\t    }\n\t  },\n\n\t  contextName: function contextName(context) {\n\t    if (this.useDepths && context) {\n\t      return 'depths[' + context + ']';\n\t    } else {\n\t      return 'depth' + context;\n\t    }\n\t  },\n\n\t  quotedString: function quotedString(str) {\n\t    return this.source.quotedString(str);\n\t  },\n\n\t  objectLiteral: function objectLiteral(obj) {\n\t    return this.source.objectLiteral(obj);\n\t  },\n\n\t  aliasable: function aliasable(name) {\n\t    var ret = this.aliases[name];\n\t    if (ret) {\n\t      ret.referenceCount++;\n\t      return ret;\n\t    }\n\n\t    ret = this.aliases[name] = this.source.wrap(name);\n\t    ret.aliasable = true;\n\t    ret.referenceCount = 1;\n\n\t    return ret;\n\t  },\n\n\t  setupHelper: function setupHelper(paramSize, name, blockHelper) {\n\t    var params = [],\n\t        paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);\n\t    var foundHelper = this.nameLookup('helpers', name, 'helper'),\n\t        callContext = this.aliasable(this.contextName(0) + ' != null ? ' + this.contextName(0) + ' : {}');\n\n\t    return {\n\t      params: params,\n\t      paramsInit: paramsInit,\n\t      name: foundHelper,\n\t      callParams: [callContext].concat(params)\n\t    };\n\t  },\n\n\t  setupParams: function setupParams(helper, paramSize, params) {\n\t    var options = {},\n\t        contexts = [],\n\t        types = [],\n\t        ids = [],\n\t        objectArgs = !params,\n\t        param = undefined;\n\n\t    if (objectArgs) {\n\t      params = [];\n\t    }\n\n\t    options.name = this.quotedString(helper);\n\t    options.hash = this.popStack();\n\n\t    if (this.trackIds) {\n\t      options.hashIds = this.popStack();\n\t    }\n\t    if (this.stringParams) {\n\t      options.hashTypes = this.popStack();\n\t      options.hashContexts = this.popStack();\n\t    }\n\n\t    var inverse = this.popStack(),\n\t        program = this.popStack();\n\n\t    // Avoid setting fn and inverse if neither are set. This allows\n\t    // helpers to do a check for `if (options.fn)`\n\t    if (program || inverse) {\n\t      options.fn = program || 'container.noop';\n\t      options.inverse = inverse || 'container.noop';\n\t    }\n\n\t    // The parameters go on to the stack in order (making sure that they are evaluated in order)\n\t    // so we need to pop them off the stack in reverse order\n\t    var i = paramSize;\n\t    while (i--) {\n\t      param = this.popStack();\n\t      params[i] = param;\n\n\t      if (this.trackIds) {\n\t        ids[i] = this.popStack();\n\t      }\n\t      if (this.stringParams) {\n\t        types[i] = this.popStack();\n\t        contexts[i] = this.popStack();\n\t      }\n\t    }\n\n\t    if (objectArgs) {\n\t      options.args = this.source.generateArray(params);\n\t    }\n\n\t    if (this.trackIds) {\n\t      options.ids = this.source.generateArray(ids);\n\t    }\n\t    if (this.stringParams) {\n\t      options.types = this.source.generateArray(types);\n\t      options.contexts = this.source.generateArray(contexts);\n\t    }\n\n\t    if (this.options.data) {\n\t      options.data = 'data';\n\t    }\n\t    if (this.useBlockParams) {\n\t      options.blockParams = 'blockParams';\n\t    }\n\t    return options;\n\t  },\n\n\t  setupHelperArgs: function setupHelperArgs(helper, paramSize, params, useRegister) {\n\t    var options = this.setupParams(helper, paramSize, params);\n\t    options = this.objectLiteral(options);\n\t    if (useRegister) {\n\t      this.useRegister('options');\n\t      params.push('options');\n\t      return ['options=', options];\n\t    } else if (params) {\n\t      params.push(options);\n\t      return '';\n\t    } else {\n\t      return options;\n\t    }\n\t  }\n\t};\n\n\t(function () {\n\t  var reservedWords = ('break else new var' + ' case finally return void' + ' catch for switch while' + ' continue function this with' + ' default if throw' + ' delete in try' + ' do instanceof typeof' + ' abstract enum int short' + ' boolean export interface static' + ' byte extends long super' + ' char final native synchronized' + ' class float package throws' + ' const goto private transient' + ' debugger implements protected volatile' + ' double import public let yield await' + ' null true false').split(' ');\n\n\t  var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};\n\n\t  for (var i = 0, l = reservedWords.length; i < l; i++) {\n\t    compilerWords[reservedWords[i]] = true;\n\t  }\n\t})();\n\n\tJavaScriptCompiler.isValidJavaScriptVariableName = function (name) {\n\t  return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name);\n\t};\n\n\tfunction strictLookup(requireTerminal, compiler, parts, type) {\n\t  var stack = compiler.popStack(),\n\t      i = 0,\n\t      len = parts.length;\n\t  if (requireTerminal) {\n\t    len--;\n\t  }\n\n\t  for (; i < len; i++) {\n\t    stack = compiler.nameLookup(stack, parts[i], type);\n\t  }\n\n\t  if (requireTerminal) {\n\t    return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];\n\t  } else {\n\t    return stack;\n\t  }\n\t}\n\n\texports['default'] = JavaScriptCompiler;\n\tmodule.exports = exports['default'];\n\n/***/ },\n/* 29 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* global define */\n\t'use strict';\n\n\texports.__esModule = true;\n\n\tvar _utils = __webpack_require__(5);\n\n\tvar SourceNode = undefined;\n\n\ttry {\n\t  /* istanbul ignore next */\n\t  if (false) {\n\t    // We don't support this in AMD environments. For these environments, we asusme that\n\t    // they are running on the browser and thus have no need for the source-map library.\n\t    var SourceMap = require('source-map');\n\t    SourceNode = SourceMap.SourceNode;\n\t  }\n\t} catch (err) {}\n\t/* NOP */\n\n\t/* istanbul ignore if: tested but not covered in istanbul due to dist build  */\n\tif (!SourceNode) {\n\t  SourceNode = function (line, column, srcFile, chunks) {\n\t    this.src = '';\n\t    if (chunks) {\n\t      this.add(chunks);\n\t    }\n\t  };\n\t  /* istanbul ignore next */\n\t  SourceNode.prototype = {\n\t    add: function add(chunks) {\n\t      if (_utils.isArray(chunks)) {\n\t        chunks = chunks.join('');\n\t      }\n\t      this.src += chunks;\n\t    },\n\t    prepend: function prepend(chunks) {\n\t      if (_utils.isArray(chunks)) {\n\t        chunks = chunks.join('');\n\t      }\n\t      this.src = chunks + this.src;\n\t    },\n\t    toStringWithSourceMap: function toStringWithSourceMap() {\n\t      return { code: this.toString() };\n\t    },\n\t    toString: function toString() {\n\t      return this.src;\n\t    }\n\t  };\n\t}\n\n\tfunction castChunk(chunk, codeGen, loc) {\n\t  if (_utils.isArray(chunk)) {\n\t    var ret = [];\n\n\t    for (var i = 0, len = chunk.length; i < len; i++) {\n\t      ret.push(codeGen.wrap(chunk[i], loc));\n\t    }\n\t    return ret;\n\t  } else if (typeof chunk === 'boolean' || typeof chunk === 'number') {\n\t    // Handle primitives that the SourceNode will throw up on\n\t    return chunk + '';\n\t  }\n\t  return chunk;\n\t}\n\n\tfunction CodeGen(srcFile) {\n\t  this.srcFile = srcFile;\n\t  this.source = [];\n\t}\n\n\tCodeGen.prototype = {\n\t  isEmpty: function isEmpty() {\n\t    return !this.source.length;\n\t  },\n\t  prepend: function prepend(source, loc) {\n\t    this.source.unshift(this.wrap(source, loc));\n\t  },\n\t  push: function push(source, loc) {\n\t    this.source.push(this.wrap(source, loc));\n\t  },\n\n\t  merge: function merge() {\n\t    var source = this.empty();\n\t    this.each(function (line) {\n\t      source.add(['  ', line, '\\n']);\n\t    });\n\t    return source;\n\t  },\n\n\t  each: function each(iter) {\n\t    for (var i = 0, len = this.source.length; i < len; i++) {\n\t      iter(this.source[i]);\n\t    }\n\t  },\n\n\t  empty: function empty() {\n\t    var loc = this.currentLocation || { start: {} };\n\t    return new SourceNode(loc.start.line, loc.start.column, this.srcFile);\n\t  },\n\t  wrap: function wrap(chunk) {\n\t    var loc = arguments.length <= 1 || arguments[1] === undefined ? this.currentLocation || { start: {} } : arguments[1];\n\n\t    if (chunk instanceof SourceNode) {\n\t      return chunk;\n\t    }\n\n\t    chunk = castChunk(chunk, this, loc);\n\n\t    return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);\n\t  },\n\n\t  functionCall: function functionCall(fn, type, params) {\n\t    params = this.generateList(params);\n\t    return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);\n\t  },\n\n\t  quotedString: function quotedString(str) {\n\t    return '\"' + (str + '').replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n').replace(/\\r/g, '\\\\r').replace(/\\u2028/g, '\\\\u2028') // Per Ecma-262 7.3 + 7.8.4\n\t    .replace(/\\u2029/g, '\\\\u2029') + '\"';\n\t  },\n\n\t  objectLiteral: function objectLiteral(obj) {\n\t    var pairs = [];\n\n\t    for (var key in obj) {\n\t      if (obj.hasOwnProperty(key)) {\n\t        var value = castChunk(obj[key], this);\n\t        if (value !== 'undefined') {\n\t          pairs.push([this.quotedString(key), ':', value]);\n\t        }\n\t      }\n\t    }\n\n\t    var ret = this.generateList(pairs);\n\t    ret.prepend('{');\n\t    ret.add('}');\n\t    return ret;\n\t  },\n\n\t  generateList: function generateList(entries) {\n\t    var ret = this.empty();\n\n\t    for (var i = 0, len = entries.length; i < len; i++) {\n\t      if (i) {\n\t        ret.add(',');\n\t      }\n\n\t      ret.add(castChunk(entries[i], this));\n\t    }\n\n\t    return ret;\n\t  },\n\n\t  generateArray: function generateArray(entries) {\n\t    var ret = this.generateList(entries);\n\t    ret.prepend('[');\n\t    ret.add(']');\n\n\t    return ret;\n\t  }\n\t};\n\n\texports['default'] = CodeGen;\n\tmodule.exports = exports['default'];\n\n/***/ }\n/******/ ])\n});\n;"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lib/highlight.9.1.0.pack.js",
    "content": "/*! highlight.js v9.1.0 | BSD3 License | git.io/hljslicense */\n!function(e){\"undefined\"!=typeof exports?e(exports):(self.hljs=e({}),\"function\"==typeof define&&define.amd&&define(\"hljs\",[],function(){return self.hljs}))}(function(e){function n(e){return e.replace(/&/gm,\"&amp;\").replace(/</gm,\"&lt;\").replace(/>/gm,\"&gt;\")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var n,t,r,i=e.className+\" \";if(i+=e.parentNode?e.parentNode.className:\"\",t=/\\blang(?:uage)?-([\\w-]+)\\b/i.exec(i))return E(t[1])?t[1]:\"no-highlight\";for(i=i.split(/\\s+/),n=0,r=i.length;r>n;n++)if(E(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:\"start\",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:\"stop\",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset<r[0].offset?e:r:\"start\"==r[0].event?e:r:e.length?e:r}function o(e){function r(e){return\" \"+e.nodeName+'=\"'+n(e.value)+'\"'}l+=\"<\"+t(e)+Array.prototype.map.call(e.attributes,r).join(\"\")+\">\"}function u(e){l+=\"</\"+t(e)+\">\"}function c(e){(\"start\"==e.event?o:u)(e.node)}for(var s=0,l=\"\",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(o)}else\"start\"==g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),\"m\"+(e.cI?\"i\":\"\")+(r?\"g\":\"\"))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(\" \").forEach(function(e){var t=e.split(\"|\");u[t[0]]=[n,t[1]?Number(t[1]):1]})};\"string\"==typeof a.k?c(\"keyword\",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\\b\\w+\\b/,!0),i&&(a.bK&&(a.b=\"\\\\b(\"+a.bK.split(\" \").join(\"|\")+\")\\\\b\"),a.b||(a.b=/\\B|\\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\\B|\\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||\"\",a.eW&&i.tE&&(a.tE+=(a.e?\"|\":\"\")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push(\"self\"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?\"\\\\.?(\"+e.b+\")\\\\.?\":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join(\"|\"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){for(var t=0;t<n.c.length;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?\"\":x.classPrefix,i='<span class=\"'+a,o=t?\"\":\"</span>\";return i+=e+'\">',i+n+o}function p(){if(!L.k)return n(M);var e=\"\",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(M);r;){e+=n(M.substr(t,r.index-t));var a=g(L,r);a?(B+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(M)}return e+n(M.substr(t))}function d(){var e=\"string\"==typeof L.sL;if(e&&!R[L.sL])return n(M);var t=e?l(L.sL,M,!0,y[L.sL]):f(M,L.sL.length?L.sL:void 0);return L.r>0&&(B+=t.r),e&&(y[L.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,\"\",!0):\"\";e.rB?(k+=r,M=\"\"):e.eB?(k+=n(t)+r,M=\"\"):(k+=r,M=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(M+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(M+=t),k+=b();do L.cN&&(k+=\"</span>\"),B+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),M=\"\",a.starts&&v(a.starts,\"\"),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme \"'+t+'\" for mode \"'+(L.cN||\"<unnamed>\")+'\"');return M+=t,t.length||1}var N=E(e);if(!N)throw new Error('Unknown language: \"'+e+'\"');s(N);var w,L=i||N,y={},k=\"\";for(w=L;w!=N;w=w.parent)w.cN&&(k=h(w.cN,\"\",!0)+k);var M=\"\",B=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),w=L;w.parent;w=w.parent)w.cN&&(k+=\"</span>\");return{r:B,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf(\"Illegal\"))return{r:0,value:n(t)};throw O}}function f(e,t){t=t||x.languages||Object.keys(R);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(E(n)){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return x.tabReplace&&(e=e.replace(/^((<[^>]+>|\\t)+)/gm,function(e,n){return n.replace(/\\t/g,x.tabReplace)})),x.useBR&&(e=e.replace(/\\n/g,\"<br>\")),e}function h(e,n,t){var r=n?w[n]:t,a=[e.trim()];return e.match(/\\bhljs\\b/)||a.push(\"hljs\"),-1===e.indexOf(r)&&a.push(r),a.join(\" \").trim()}function p(e){var n=i(e);if(!a(n)){var t;x.useBR?(t=document.createElementNS(\"http://www.w3.org/1999/xhtml\",\"div\"),t.innerHTML=e.innerHTML.replace(/\\n/g,\"\").replace(/<br[ \\/]*>/g,\"\\n\")):t=e;var r=t.textContent,o=n?l(n,r,!0):f(r),s=u(t);if(s.length){var p=document.createElementNS(\"http://www.w3.org/1999/xhtml\",\"div\");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){x=o(x,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll(\"pre code\");Array.prototype.forEach.call(e,p)}}function v(){addEventListener(\"DOMContentLoaded\",b,!1),addEventListener(\"load\",b,!1)}function m(n,t){var r=R[n]=t(e);r.aliases&&r.aliases.forEach(function(e){w[e]=n})}function N(){return Object.keys(R)}function E(e){return e=(e||\"\").toLowerCase(),R[e]||R[w[e]]}var x={classPrefix:\"hljs-\",tabReplace:null,useBR:!1,languages:void 0},R={},w={};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=E,e.inherit=o,e.IR=\"[a-zA-Z]\\\\w*\",e.UIR=\"[a-zA-Z_]\\\\w*\",e.NR=\"\\\\b\\\\d+(\\\\.\\\\d+)?\",e.CNR=\"(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)([eE][-+]?\\\\d+)?)\",e.BNR=\"\\\\b(0b[01]+)\",e.RSR=\"!|!=|!==|%|%=|&|&&|&=|\\\\*|\\\\*=|\\\\+|\\\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\\\?|\\\\[|\\\\{|\\\\(|\\\\^|\\\\^=|\\\\||\\\\|=|\\\\|\\\\||~\",e.BE={b:\"\\\\\\\\[\\\\s\\\\S]\",r:0},e.ASM={cN:\"string\",b:\"'\",e:\"'\",i:\"\\\\n\",c:[e.BE]},e.QSM={cN:\"string\",b:'\"',e:'\"',i:\"\\\\n\",c:[e.BE]},e.PWM={b:/\\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\\b/},e.C=function(n,t,r){var a=e.inherit({cN:\"comment\",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:\"doctag\",b:\"(?:TODO|FIXME|NOTE|BUG|XXX):\",r:0}),a},e.CLCM=e.C(\"//\",\"$\"),e.CBCM=e.C(\"/\\\\*\",\"\\\\*/\"),e.HCM=e.C(\"#\",\"$\"),e.NM={cN:\"number\",b:e.NR,r:0},e.CNM={cN:\"number\",b:e.CNR,r:0},e.BNM={cN:\"number\",b:e.BNR,r:0},e.CSSNM={cN:\"number\",b:e.NR+\"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?\",r:0},e.RM={cN:\"regexp\",b:/\\//,e:/\\/[gimuy]*/,i:/\\n/,c:[e.BE,{b:/\\[/,e:/\\]/,r:0,c:[e.BE]}]},e.TM={cN:\"title\",b:e.IR,r:0},e.UTM={cN:\"title\",b:e.UIR,r:0},e});hljs.registerLanguage(\"json\",function(e){var t={literal:\"true false null\"},i=[e.QSM,e.CNM],r={e:\",\",eW:!0,eE:!0,c:i,k:t},s={b:\"{\",e:\"}\",c:[{cN:\"attr\",b:'\\\\s*\"',e:'\"\\\\s*:\\\\s*',eB:!0,eE:!0,c:[e.BE],i:\"\\\\n\",starts:r}],i:\"\\\\S\"},n={b:\"\\\\[\",e:\"\\\\]\",c:[e.inherit(r)],i:\"\\\\S\"};return i.splice(i.length,0,s,n),{c:i,k:t,i:\"\\\\S\"}});hljs.registerLanguage(\"xml\",function(s){var t=\"[A-Za-z0-9\\\\._:-]+\",e={b:/<\\?(php)?(?!\\w)/,e:/\\?>/,sL:\"php\"},r={eW:!0,i:/</,r:0,c:[e,{cN:\"attr\",b:t,r:0},{b:\"=\",r:0,c:[{cN:\"string\",c:[e],v:[{b:/\"/,e:/\"/},{b:/'/,e:/'/},{b:/[^\\s\\/>]+/}]}]}]};return{aliases:[\"html\",\"xhtml\",\"rss\",\"atom\",\"xsl\",\"plist\"],cI:!0,c:[{cN:\"meta\",b:\"<!DOCTYPE\",e:\">\",r:10,c:[{b:\"\\\\[\",e:\"\\\\]\"}]},s.C(\"<!--\",\"-->\",{r:10}),{b:\"<\\\\!\\\\[CDATA\\\\[\",e:\"\\\\]\\\\]>\",r:10},{cN:\"tag\",b:\"<style(?=\\\\s|>|$)\",e:\">\",k:{name:\"style\"},c:[r],starts:{e:\"</style>\",rE:!0,sL:[\"css\",\"xml\"]}},{cN:\"tag\",b:\"<script(?=\\\\s|>|$)\",e:\">\",k:{name:\"script\"},c:[r],starts:{e:\"</script>\",rE:!0,sL:[\"actionscript\",\"javascript\",\"handlebars\",\"xml\"]}},e,{cN:\"meta\",b:/<\\?\\w+/,e:/\\?>/,r:10},{cN:\"tag\",b:\"</?\",e:\"/?>\",c:[{cN:\"name\",b:/[^\\/><\\s]+/,r:0},r]}]}});hljs.registerLanguage(\"javascript\",function(e){return{aliases:[\"js\"],k:{keyword:\"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await import from as\",literal:\"true false null undefined NaN Infinity\",built_in:\"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise\"},c:[{cN:\"meta\",r:10,b:/^\\s*['\"]use (strict|asm)['\"]/},{cN:\"meta\",b:/^#!/,e:/$/},e.ASM,e.QSM,{cN:\"string\",b:\"`\",e:\"`\",c:[e.BE,{cN:\"subst\",b:\"\\\\$\\\\{\",e:\"\\\\}\"}]},e.CLCM,e.CBCM,{cN:\"number\",v:[{b:\"\\\\b(0[bB][01]+)\"},{b:\"\\\\b(0[oO][0-7]+)\"},{b:e.CNR}],r:0},{b:\"(\"+e.RSR+\"|\\\\b(case|return|throw)\\\\b)\\\\s*\",k:\"return throw case\",c:[e.CLCM,e.CBCM,e.RM,{b:/</,e:/>\\s*[);\\]]/,r:0,sL:\"xml\"}],r:0},{cN:\"function\",bK:\"function\",e:/\\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:\"params\",b:/\\(/,e:/\\)/,eB:!0,eE:!0,c:[e.CLCM,e.CBCM]}],i:/\\[|%/},{b:/\\$[(.]/},{b:\"\\\\.\"+e.IR,r:0},{cN:\"class\",bK:\"class\",e:/[{;=]/,eE:!0,i:/[:\"\\[\\]]/,c:[{bK:\"extends\"},e.UTM]},{bK:\"constructor\",e:/\\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage(\"css\",function(e){var c=\"[a-zA-Z-][a-zA-Z0-9_-]*\",t={b:/[A-Z\\_\\.\\-]+\\s*:/,rB:!0,e:\";\",eW:!0,c:[{cN:\"attribute\",b:/\\S/,e:\":\",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\\w-]+\\s*\\(/,rB:!0,c:[{cN:\"built_in\",b:/[\\w-]+/}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:\"number\",b:\"#[0-9A-Fa-f]+\"},{cN:\"meta\",b:\"!important\"}]}}]};return{cI:!0,i:/[=\\/|'\\$]/,c:[e.CBCM,{cN:\"selector-id\",b:/#[A-Za-z0-9_-]+/},{cN:\"selector-class\",b:/\\.[A-Za-z0-9_-]+/},{cN:\"selector-attr\",b:/\\[/,e:/\\]/,i:\"$\"},{cN:\"selector-pseudo\",b:/:(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\"'.]+/},{b:\"@(font-face|page)\",l:\"[a-z-]+\",k:\"font-face page\"},{b:\"@\",e:\"[{;]\",c:[{cN:\"keyword\",b:/\\S+/},{b:/\\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:\"selector-tag\",b:c,r:0},{b:\"{\",e:\"}\",i:/\\S/,c:[e.CBCM,t]}]}});"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lib/highlight.9.1.0.pack_extended.js",
    "content": "'use strict';\n\n(function () {\n    var configure, highlightBlock;\n\n    configure = hljs.configure;\n    // \"extending\" hljs.configure method\n    hljs.configure = function _configure (options) {\n        var size = options.highlightSizeThreshold;\n\n        // added highlightSizeThreshold option to set maximum size\n        // of processed string. Set to null if not a number\n        hljs.highlightSizeThreshold = size === +size ? size : null;\n\n        configure.call(this, options);\n    };\n\n    highlightBlock = hljs.highlightBlock;\n\n    // \"extending\" hljs.highlightBlock method\n    hljs.highlightBlock = function _highlightBlock (el) {\n        var innerHTML = el.innerHTML;\n        var size = hljs.highlightSizeThreshold;\n\n        // check if highlightSizeThreshold is not set or element innerHTML\n        // is less than set option highlightSizeThreshold\n        if (size == null || size > innerHTML.length) {\n            // proceed with hljs.highlightBlock\n            highlightBlock.call(hljs, el);\n        }\n    };\n\n})();\n\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lib/marked.js",
    "content": "/**\n * marked - a markdown parser\n * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)\n * https://github.com/chjj/marked\n */\n\n;(function() {\n\n/**\n * Block-Level Grammar\n */\n\nvar block = {\n  newline: /^\\n+/,\n  code: /^( {4}[^\\n]+\\n*)+/,\n  fences: noop,\n  hr: /^( *[-*_]){3,} *(?:\\n+|$)/,\n  heading: /^ *(#{1,6}) *([^\\n]+?) *#* *(?:\\n+|$)/,\n  nptable: noop,\n  lheading: /^([^\\n]+)\\n *(=|-){2,} *(?:\\n+|$)/,\n  blockquote: /^( *>[^\\n]+(\\n(?!def)[^\\n]+)*\\n*)+/,\n  list: /^( *)(bull) [\\s\\S]+?(?:hr|def|\\n{2,}(?! )(?!\\1bull )\\n*|\\s*$)/,\n  html: /^ *(?:comment *(?:\\n|\\s*$)|closed *(?:\\n{2,}|\\s*$)|closing *(?:\\n{2,}|\\s*$))/,\n  def: /^ *\\[([^\\]]+)\\]: *<?([^\\s>]+)>?(?: +[\"(]([^\\n]+)[\")])? *(?:\\n+|$)/,\n  table: noop,\n  paragraph: /^((?:[^\\n]+\\n?(?!hr|heading|lheading|blockquote|tag|def))+)\\n*/,\n  text: /^[^\\n]+/\n};\n\nblock.bullet = /(?:[*+-]|\\d+\\.)/;\nblock.item = /^( *)(bull) [^\\n]*(?:\\n(?!\\1bull )[^\\n]*)*/;\nblock.item = replace(block.item, 'gm')\n  (/bull/g, block.bullet)\n  ();\n\nblock.list = replace(block.list)\n  (/bull/g, block.bullet)\n  ('hr', '\\\\n+(?=\\\\1?(?:[-*_] *){3,}(?:\\\\n+|$))')\n  ('def', '\\\\n+(?=' + block.def.source + ')')\n  ();\n\nblock.blockquote = replace(block.blockquote)\n  ('def', block.def)\n  ();\n\nblock._tag = '(?!(?:'\n  + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'\n  + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'\n  + '|span|br|wbr|ins|del|img)\\\\b)\\\\w+(?!:/|[^\\\\w\\\\s@]*@)\\\\b';\n\nblock.html = replace(block.html)\n  ('comment', /<!--[\\s\\S]*?-->/)\n  ('closed', /<(tag)[\\s\\S]+?<\\/\\1>/)\n  ('closing', /<tag(?:\"[^\"]*\"|'[^']*'|[^'\">])*?>/)\n  (/tag/g, block._tag)\n  ();\n\nblock.paragraph = replace(block.paragraph)\n  ('hr', block.hr)\n  ('heading', block.heading)\n  ('lheading', block.lheading)\n  ('blockquote', block.blockquote)\n  ('tag', '<' + block._tag)\n  ('def', block.def)\n  ();\n\n/**\n * Normal Block Grammar\n */\n\nblock.normal = merge({}, block);\n\n/**\n * GFM Block Grammar\n */\n\nblock.gfm = merge({}, block.normal, {\n  fences: /^ *(`{3,}|~{3,}) *(\\S+)? *\\n([\\s\\S]+?)\\s*\\1 *(?:\\n+|$)/,\n  paragraph: /^/\n});\n\nblock.gfm.paragraph = replace(block.paragraph)\n  ('(?!', '(?!'\n    + block.gfm.fences.source.replace('\\\\1', '\\\\2') + '|'\n    + block.list.source.replace('\\\\1', '\\\\3') + '|')\n  ();\n\n/**\n * GFM + Tables Block Grammar\n */\n\nblock.tables = merge({}, block.gfm, {\n  nptable: /^ *(\\S.*\\|.*)\\n *([-:]+ *\\|[-| :]*)\\n((?:.*\\|.*(?:\\n|$))*)\\n*/,\n  table: /^ *\\|(.+)\\n *\\|( *[-:]+[-| :]*)\\n((?: *\\|.*(?:\\n|$))*)\\n*/\n});\n\n/**\n * Block Lexer\n */\n\nfunction Lexer(options) {\n  this.tokens = [];\n  this.tokens.links = {};\n  this.options = options || marked.defaults;\n  this.rules = block.normal;\n\n  if (this.options.gfm) {\n    if (this.options.tables) {\n      this.rules = block.tables;\n    } else {\n      this.rules = block.gfm;\n    }\n  }\n}\n\n/**\n * Expose Block Rules\n */\n\nLexer.rules = block;\n\n/**\n * Static Lex Method\n */\n\nLexer.lex = function(src, options) {\n  var lexer = new Lexer(options);\n  return lexer.lex(src);\n};\n\n/**\n * Preprocessing\n */\n\nLexer.prototype.lex = function(src) {\n  src = src\n    .replace(/\\r\\n|\\r/g, '\\n')\n    .replace(/\\t/g, '    ')\n    .replace(/\\u00a0/g, ' ')\n    .replace(/\\u2424/g, '\\n');\n\n  return this.token(src, true);\n};\n\n/**\n * Lexing\n */\n\nLexer.prototype.token = function(src, top, bq) {\n  var src = src.replace(/^ +$/gm, '')\n    , next\n    , loose\n    , cap\n    , bull\n    , b\n    , item\n    , space\n    , i\n    , l;\n\n  while (src) {\n    // newline\n    if (cap = this.rules.newline.exec(src)) {\n      src = src.substring(cap[0].length);\n      if (cap[0].length > 1) {\n        this.tokens.push({\n          type: 'space'\n        });\n      }\n    }\n\n    // code\n    if (cap = this.rules.code.exec(src)) {\n      src = src.substring(cap[0].length);\n      cap = cap[0].replace(/^ {4}/gm, '');\n      this.tokens.push({\n        type: 'code',\n        text: !this.options.pedantic\n          ? cap.replace(/\\n+$/, '')\n          : cap\n      });\n      continue;\n    }\n\n    // fences (gfm)\n    if (cap = this.rules.fences.exec(src)) {\n      src = src.substring(cap[0].length);\n      this.tokens.push({\n        type: 'code',\n        lang: cap[2],\n        text: cap[3]\n      });\n      continue;\n    }\n\n    // heading\n    if (cap = this.rules.heading.exec(src)) {\n      src = src.substring(cap[0].length);\n      this.tokens.push({\n        type: 'heading',\n        depth: cap[1].length,\n        text: cap[2]\n      });\n      continue;\n    }\n\n    // table no leading pipe (gfm)\n    if (top && (cap = this.rules.nptable.exec(src))) {\n      src = src.substring(cap[0].length);\n\n      item = {\n        type: 'table',\n        header: cap[1].replace(/^ *| *\\| *$/g, '').split(/ *\\| */),\n        align: cap[2].replace(/^ *|\\| *$/g, '').split(/ *\\| */),\n        cells: cap[3].replace(/\\n$/, '').split('\\n')\n      };\n\n      for (i = 0; i < item.align.length; i++) {\n        if (/^ *-+: *$/.test(item.align[i])) {\n          item.align[i] = 'right';\n        } else if (/^ *:-+: *$/.test(item.align[i])) {\n          item.align[i] = 'center';\n        } else if (/^ *:-+ *$/.test(item.align[i])) {\n          item.align[i] = 'left';\n        } else {\n          item.align[i] = null;\n        }\n      }\n\n      for (i = 0; i < item.cells.length; i++) {\n        item.cells[i] = item.cells[i].split(/ *\\| */);\n      }\n\n      this.tokens.push(item);\n\n      continue;\n    }\n\n    // lheading\n    if (cap = this.rules.lheading.exec(src)) {\n      src = src.substring(cap[0].length);\n      this.tokens.push({\n        type: 'heading',\n        depth: cap[2] === '=' ? 1 : 2,\n        text: cap[1]\n      });\n      continue;\n    }\n\n    // hr\n    if (cap = this.rules.hr.exec(src)) {\n      src = src.substring(cap[0].length);\n      this.tokens.push({\n        type: 'hr'\n      });\n      continue;\n    }\n\n    // blockquote\n    if (cap = this.rules.blockquote.exec(src)) {\n      src = src.substring(cap[0].length);\n\n      this.tokens.push({\n        type: 'blockquote_start'\n      });\n\n      cap = cap[0].replace(/^ *> ?/gm, '');\n\n      // Pass `top` to keep the current\n      // \"toplevel\" state. This is exactly\n      // how markdown.pl works.\n      this.token(cap, top, true);\n\n      this.tokens.push({\n        type: 'blockquote_end'\n      });\n\n      continue;\n    }\n\n    // list\n    if (cap = this.rules.list.exec(src)) {\n      src = src.substring(cap[0].length);\n      bull = cap[2];\n\n      this.tokens.push({\n        type: 'list_start',\n        ordered: bull.length > 1\n      });\n\n      // Get each top-level item.\n      cap = cap[0].match(this.rules.item);\n\n      next = false;\n      l = cap.length;\n      i = 0;\n\n      for (; i < l; i++) {\n        item = cap[i];\n\n        // Remove the list item's bullet\n        // so it is seen as the next token.\n        space = item.length;\n        item = item.replace(/^ *([*+-]|\\d+\\.) +/, '');\n\n        // Outdent whatever the\n        // list item contains. Hacky.\n        if (~item.indexOf('\\n ')) {\n          space -= item.length;\n          item = !this.options.pedantic\n            ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')\n            : item.replace(/^ {1,4}/gm, '');\n        }\n\n        // Determine whether the next list item belongs here.\n        // Backpedal if it does not belong in this list.\n        if (this.options.smartLists && i !== l - 1) {\n          b = block.bullet.exec(cap[i + 1])[0];\n          if (bull !== b && !(bull.length > 1 && b.length > 1)) {\n            src = cap.slice(i + 1).join('\\n') + src;\n            i = l - 1;\n          }\n        }\n\n        // Determine whether item is loose or not.\n        // Use: /(^|\\n)(?! )[^\\n]+\\n\\n(?!\\s*$)/\n        // for discount behavior.\n        loose = next || /\\n\\n(?!\\s*$)/.test(item);\n        if (i !== l - 1) {\n          next = item.charAt(item.length - 1) === '\\n';\n          if (!loose) loose = next;\n        }\n\n        this.tokens.push({\n          type: loose\n            ? 'loose_item_start'\n            : 'list_item_start'\n        });\n\n        // Recurse.\n        this.token(item, false, bq);\n\n        this.tokens.push({\n          type: 'list_item_end'\n        });\n      }\n\n      this.tokens.push({\n        type: 'list_end'\n      });\n\n      continue;\n    }\n\n    // html\n    if (cap = this.rules.html.exec(src)) {\n      src = src.substring(cap[0].length);\n      this.tokens.push({\n        type: this.options.sanitize\n          ? 'paragraph'\n          : 'html',\n        pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',\n        text: cap[0]\n      });\n      continue;\n    }\n\n    // def\n    if ((!bq && top) && (cap = this.rules.def.exec(src))) {\n      src = src.substring(cap[0].length);\n      this.tokens.links[cap[1].toLowerCase()] = {\n        href: cap[2],\n        title: cap[3]\n      };\n      continue;\n    }\n\n    // table (gfm)\n    if (top && (cap = this.rules.table.exec(src))) {\n      src = src.substring(cap[0].length);\n\n      item = {\n        type: 'table',\n        header: cap[1].replace(/^ *| *\\| *$/g, '').split(/ *\\| */),\n        align: cap[2].replace(/^ *|\\| *$/g, '').split(/ *\\| */),\n        cells: cap[3].replace(/(?: *\\| *)?\\n$/, '').split('\\n')\n      };\n\n      for (i = 0; i < item.align.length; i++) {\n        if (/^ *-+: *$/.test(item.align[i])) {\n          item.align[i] = 'right';\n        } else if (/^ *:-+: *$/.test(item.align[i])) {\n          item.align[i] = 'center';\n        } else if (/^ *:-+ *$/.test(item.align[i])) {\n          item.align[i] = 'left';\n        } else {\n          item.align[i] = null;\n        }\n      }\n\n      for (i = 0; i < item.cells.length; i++) {\n        item.cells[i] = item.cells[i]\n          .replace(/^ *\\| *| *\\| *$/g, '')\n          .split(/ *\\| */);\n      }\n\n      this.tokens.push(item);\n\n      continue;\n    }\n\n    // top-level paragraph\n    if (top && (cap = this.rules.paragraph.exec(src))) {\n      src = src.substring(cap[0].length);\n      this.tokens.push({\n        type: 'paragraph',\n        text: cap[1].charAt(cap[1].length - 1) === '\\n'\n          ? cap[1].slice(0, -1)\n          : cap[1]\n      });\n      continue;\n    }\n\n    // text\n    if (cap = this.rules.text.exec(src)) {\n      // Top-level should never reach here.\n      src = src.substring(cap[0].length);\n      this.tokens.push({\n        type: 'text',\n        text: cap[0]\n      });\n      continue;\n    }\n\n    if (src) {\n      throw new\n        Error('Infinite loop on byte: ' + src.charCodeAt(0));\n    }\n  }\n\n  return this.tokens;\n};\n\n/**\n * Inline-Level Grammar\n */\n\nvar inline = {\n  escape: /^\\\\([\\\\`*{}\\[\\]()#+\\-.!_>])/,\n  autolink: /^<([^ >]+(@|:\\/)[^ >]+)>/,\n  url: noop,\n  tag: /^<!--[\\s\\S]*?-->|^<\\/?\\w+(?:\"[^\"]*\"|'[^']*'|[^'\">])*?>/,\n  link: /^!?\\[(inside)\\]\\(href\\)/,\n  reflink: /^!?\\[(inside)\\]\\s*\\[([^\\]]*)\\]/,\n  nolink: /^!?\\[((?:\\[[^\\]]*\\]|[^\\[\\]])*)\\]/,\n  strong: /^__([\\s\\S]+?)__(?!_)|^\\*\\*([\\s\\S]+?)\\*\\*(?!\\*)/,\n  em: /^\\b_((?:__|[\\s\\S])+?)_\\b|^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/,\n  code: /^(`+)\\s*([\\s\\S]*?[^`])\\s*\\1(?!`)/,\n  br: /^ {2,}\\n(?!\\s*$)/,\n  del: noop,\n  text: /^[\\s\\S]+?(?=[\\\\<!\\[_*`]| {2,}\\n|$)/\n};\n\ninline._inside = /(?:\\[[^\\]]*\\]|[^\\[\\]]|\\](?=[^\\[]*\\]))*/;\ninline._href = /\\s*<?([\\s\\S]*?)>?(?:\\s+['\"]([\\s\\S]*?)['\"])?\\s*/;\n\ninline.link = replace(inline.link)\n  ('inside', inline._inside)\n  ('href', inline._href)\n  ();\n\ninline.reflink = replace(inline.reflink)\n  ('inside', inline._inside)\n  ();\n\n/**\n * Normal Inline Grammar\n */\n\ninline.normal = merge({}, inline);\n\n/**\n * Pedantic Inline Grammar\n */\n\ninline.pedantic = merge({}, inline.normal, {\n  strong: /^__(?=\\S)([\\s\\S]*?\\S)__(?!_)|^\\*\\*(?=\\S)([\\s\\S]*?\\S)\\*\\*(?!\\*)/,\n  em: /^_(?=\\S)([\\s\\S]*?\\S)_(?!_)|^\\*(?=\\S)([\\s\\S]*?\\S)\\*(?!\\*)/\n});\n\n/**\n * GFM Inline Grammar\n */\n\ninline.gfm = merge({}, inline.normal, {\n  escape: replace(inline.escape)('])', '~|])')(),\n  url: /^(https?:\\/\\/[^\\s<]+[^<.,:;\"')\\]\\s])/,\n  del: /^~~(?=\\S)([\\s\\S]*?\\S)~~/,\n  text: replace(inline.text)\n    (']|', '~]|')\n    ('|', '|https?://|')\n    ()\n});\n\n/**\n * GFM + Line Breaks Inline Grammar\n */\n\ninline.breaks = merge({}, inline.gfm, {\n  br: replace(inline.br)('{2,}', '*')(),\n  text: replace(inline.gfm.text)('{2,}', '*')()\n});\n\n/**\n * Inline Lexer & Compiler\n */\n\nfunction InlineLexer(links, options) {\n  this.options = options || marked.defaults;\n  this.links = links;\n  this.rules = inline.normal;\n  this.renderer = this.options.renderer || new Renderer;\n  this.renderer.options = this.options;\n\n  if (!this.links) {\n    throw new\n      Error('Tokens array requires a `links` property.');\n  }\n\n  if (this.options.gfm) {\n    if (this.options.breaks) {\n      this.rules = inline.breaks;\n    } else {\n      this.rules = inline.gfm;\n    }\n  } else if (this.options.pedantic) {\n    this.rules = inline.pedantic;\n  }\n}\n\n/**\n * Expose Inline Rules\n */\n\nInlineLexer.rules = inline;\n\n/**\n * Static Lexing/Compiling Method\n */\n\nInlineLexer.output = function(src, links, options) {\n  var inline = new InlineLexer(links, options);\n  return inline.output(src);\n};\n\n/**\n * Lexing/Compiling\n */\n\nInlineLexer.prototype.output = function(src) {\n  var out = ''\n    , link\n    , text\n    , href\n    , cap;\n\n  while (src) {\n    // escape\n    if (cap = this.rules.escape.exec(src)) {\n      src = src.substring(cap[0].length);\n      out += cap[1];\n      continue;\n    }\n\n    // autolink\n    if (cap = this.rules.autolink.exec(src)) {\n      src = src.substring(cap[0].length);\n      if (cap[2] === '@') {\n        text = cap[1].charAt(6) === ':'\n          ? this.mangle(cap[1].substring(7))\n          : this.mangle(cap[1]);\n        href = this.mangle('mailto:') + text;\n      } else {\n        text = escape(cap[1]);\n        href = text;\n      }\n      out += this.renderer.link(href, null, text);\n      continue;\n    }\n\n    // url (gfm)\n    if (!this.inLink && (cap = this.rules.url.exec(src))) {\n      src = src.substring(cap[0].length);\n      text = escape(cap[1]);\n      href = text;\n      out += this.renderer.link(href, null, text);\n      continue;\n    }\n\n    // tag\n    if (cap = this.rules.tag.exec(src)) {\n      if (!this.inLink && /^<a /i.test(cap[0])) {\n        this.inLink = true;\n      } else if (this.inLink && /^<\\/a>/i.test(cap[0])) {\n        this.inLink = false;\n      }\n      src = src.substring(cap[0].length);\n      out += this.options.sanitize\n        ? escape(cap[0])\n        : cap[0];\n      continue;\n    }\n\n    // link\n    if (cap = this.rules.link.exec(src)) {\n      src = src.substring(cap[0].length);\n      this.inLink = true;\n      out += this.outputLink(cap, {\n        href: cap[2],\n        title: cap[3]\n      });\n      this.inLink = false;\n      continue;\n    }\n\n    // reflink, nolink\n    if ((cap = this.rules.reflink.exec(src))\n        || (cap = this.rules.nolink.exec(src))) {\n      src = src.substring(cap[0].length);\n      link = (cap[2] || cap[1]).replace(/\\s+/g, ' ');\n      link = this.links[link.toLowerCase()];\n      if (!link || !link.href) {\n        out += cap[0].charAt(0);\n        src = cap[0].substring(1) + src;\n        continue;\n      }\n      this.inLink = true;\n      out += this.outputLink(cap, link);\n      this.inLink = false;\n      continue;\n    }\n\n    // strong\n    if (cap = this.rules.strong.exec(src)) {\n      src = src.substring(cap[0].length);\n      out += this.renderer.strong(this.output(cap[2] || cap[1]));\n      continue;\n    }\n\n    // em\n    if (cap = this.rules.em.exec(src)) {\n      src = src.substring(cap[0].length);\n      out += this.renderer.em(this.output(cap[2] || cap[1]));\n      continue;\n    }\n\n    // code\n    if (cap = this.rules.code.exec(src)) {\n      src = src.substring(cap[0].length);\n      out += this.renderer.codespan(escape(cap[2], true));\n      continue;\n    }\n\n    // br\n    if (cap = this.rules.br.exec(src)) {\n      src = src.substring(cap[0].length);\n      out += this.renderer.br();\n      continue;\n    }\n\n    // del (gfm)\n    if (cap = this.rules.del.exec(src)) {\n      src = src.substring(cap[0].length);\n      out += this.renderer.del(this.output(cap[1]));\n      continue;\n    }\n\n    // text\n    if (cap = this.rules.text.exec(src)) {\n      src = src.substring(cap[0].length);\n      out += escape(this.smartypants(cap[0]));\n      continue;\n    }\n\n    if (src) {\n      throw new\n        Error('Infinite loop on byte: ' + src.charCodeAt(0));\n    }\n  }\n\n  return out;\n};\n\n/**\n * Compile Link\n */\n\nInlineLexer.prototype.outputLink = function(cap, link) {\n  var href = escape(link.href)\n    , title = link.title ? escape(link.title) : null;\n\n  return cap[0].charAt(0) !== '!'\n    ? this.renderer.link(href, title, this.output(cap[1]))\n    : this.renderer.image(href, title, escape(cap[1]));\n};\n\n/**\n * Smartypants Transformations\n */\n\nInlineLexer.prototype.smartypants = function(text) {\n  if (!this.options.smartypants) return text;\n  return text\n    // em-dashes\n    .replace(/--/g, '\\u2014')\n    // opening singles\n    .replace(/(^|[-\\u2014/(\\[{\"\\s])'/g, '$1\\u2018')\n    // closing singles & apostrophes\n    .replace(/'/g, '\\u2019')\n    // opening doubles\n    .replace(/(^|[-\\u2014/(\\[{\\u2018\\s])\"/g, '$1\\u201c')\n    // closing doubles\n    .replace(/\"/g, '\\u201d')\n    // ellipses\n    .replace(/\\.{3}/g, '\\u2026');\n};\n\n/**\n * Mangle Links\n */\n\nInlineLexer.prototype.mangle = function(text) {\n  var out = ''\n    , l = text.length\n    , i = 0\n    , ch;\n\n  for (; i < l; i++) {\n    ch = text.charCodeAt(i);\n    if (Math.random() > 0.5) {\n      ch = 'x' + ch.toString(16);\n    }\n    out += '&#' + ch + ';';\n  }\n\n  return out;\n};\n\n/**\n * Renderer\n */\n\nfunction Renderer(options) {\n  this.options = options || {};\n}\n\nRenderer.prototype.code = function(code, lang, escaped) {\n  if (this.options.highlight) {\n    var out = this.options.highlight(code, lang);\n    if (out != null && out !== code) {\n      escaped = true;\n      code = out;\n    }\n  }\n\n  if (!lang) {\n    return '<pre><code>'\n      + (escaped ? code : escape(code, true))\n      + '\\n</code></pre>';\n  }\n\n  return '<pre><code class=\"'\n    + this.options.langPrefix\n    + escape(lang, true)\n    + '\">'\n    + (escaped ? code : escape(code, true))\n    + '\\n</code></pre>\\n';\n};\n\nRenderer.prototype.blockquote = function(quote) {\n  return '<blockquote>\\n' + quote + '</blockquote>\\n';\n};\n\nRenderer.prototype.html = function(html) {\n  return html;\n};\n\nRenderer.prototype.heading = function(text, level, raw) {\n  return '<h'\n    + level\n    + ' id=\"'\n    + this.options.headerPrefix\n    + raw.toLowerCase().replace(/[^\\w]+/g, '-')\n    + '\">'\n    + text\n    + '</h'\n    + level\n    + '>\\n';\n};\n\nRenderer.prototype.hr = function() {\n  return this.options.xhtml ? '<hr/>\\n' : '<hr>\\n';\n};\n\nRenderer.prototype.list = function(body, ordered) {\n  var type = ordered ? 'ol' : 'ul';\n  return '<' + type + '>\\n' + body + '</' + type + '>\\n';\n};\n\nRenderer.prototype.listitem = function(text) {\n  return '<li>' + text + '</li>\\n';\n};\n\nRenderer.prototype.paragraph = function(text) {\n  return '<p>' + text + '</p>\\n';\n};\n\nRenderer.prototype.table = function(header, body) {\n  return '<table>\\n'\n    + '<thead>\\n'\n    + header\n    + '</thead>\\n'\n    + '<tbody>\\n'\n    + body\n    + '</tbody>\\n'\n    + '</table>\\n';\n};\n\nRenderer.prototype.tablerow = function(content) {\n  return '<tr>\\n' + content + '</tr>\\n';\n};\n\nRenderer.prototype.tablecell = function(content, flags) {\n  var type = flags.header ? 'th' : 'td';\n  var tag = flags.align\n    ? '<' + type + ' style=\"text-align:' + flags.align + '\">'\n    : '<' + type + '>';\n  return tag + content + '</' + type + '>\\n';\n};\n\n// span level renderer\nRenderer.prototype.strong = function(text) {\n  return '<strong>' + text + '</strong>';\n};\n\nRenderer.prototype.em = function(text) {\n  return '<em>' + text + '</em>';\n};\n\nRenderer.prototype.codespan = function(text) {\n  return '<code>' + text + '</code>';\n};\n\nRenderer.prototype.br = function() {\n  return this.options.xhtml ? '<br/>' : '<br>';\n};\n\nRenderer.prototype.del = function(text) {\n  return '<del>' + text + '</del>';\n};\n\nRenderer.prototype.link = function(href, title, text) {\n  if (this.options.sanitize) {\n    try {\n      var prot = decodeURIComponent(unescape(href))\n        .replace(/[^\\w:]/g, '')\n        .toLowerCase();\n    } catch (e) {\n      return '';\n    }\n    if (prot.indexOf('javascript:') === 0) {\n      return '';\n    }\n  }\n  var out = '<a href=\"' + href + '\"';\n  if (title) {\n    out += ' title=\"' + title + '\"';\n  }\n  out += '>' + text + '</a>';\n  return out;\n};\n\nRenderer.prototype.image = function(href, title, text) {\n  var out = '<img src=\"' + href + '\" alt=\"' + text + '\"';\n  if (title) {\n    out += ' title=\"' + title + '\"';\n  }\n  out += this.options.xhtml ? '/>' : '>';\n  return out;\n};\n\n/**\n * Parsing & Compiling\n */\n\nfunction Parser(options) {\n  this.tokens = [];\n  this.token = null;\n  this.options = options || marked.defaults;\n  this.options.renderer = this.options.renderer || new Renderer;\n  this.renderer = this.options.renderer;\n  this.renderer.options = this.options;\n}\n\n/**\n * Static Parse Method\n */\n\nParser.parse = function(src, options, renderer) {\n  var parser = new Parser(options, renderer);\n  return parser.parse(src);\n};\n\n/**\n * Parse Loop\n */\n\nParser.prototype.parse = function(src) {\n  this.inline = new InlineLexer(src.links, this.options, this.renderer);\n  this.tokens = src.reverse();\n\n  var out = '';\n  while (this.next()) {\n    out += this.tok();\n  }\n\n  return out;\n};\n\n/**\n * Next Token\n */\n\nParser.prototype.next = function() {\n  return this.token = this.tokens.pop();\n};\n\n/**\n * Preview Next Token\n */\n\nParser.prototype.peek = function() {\n  return this.tokens[this.tokens.length - 1] || 0;\n};\n\n/**\n * Parse Text Tokens\n */\n\nParser.prototype.parseText = function() {\n  var body = this.token.text;\n\n  while (this.peek().type === 'text') {\n    body += '\\n' + this.next().text;\n  }\n\n  return this.inline.output(body);\n};\n\n/**\n * Parse Current Token\n */\n\nParser.prototype.tok = function() {\n  switch (this.token.type) {\n    case 'space': {\n      return '';\n    }\n    case 'hr': {\n      return this.renderer.hr();\n    }\n    case 'heading': {\n      return this.renderer.heading(\n        this.inline.output(this.token.text),\n        this.token.depth,\n        this.token.text);\n    }\n    case 'code': {\n      return this.renderer.code(this.token.text,\n        this.token.lang,\n        this.token.escaped);\n    }\n    case 'table': {\n      var header = ''\n        , body = ''\n        , i\n        , row\n        , cell\n        , flags\n        , j;\n\n      // header\n      cell = '';\n      for (i = 0; i < this.token.header.length; i++) {\n        flags = { header: true, align: this.token.align[i] };\n        cell += this.renderer.tablecell(\n          this.inline.output(this.token.header[i]),\n          { header: true, align: this.token.align[i] }\n        );\n      }\n      header += this.renderer.tablerow(cell);\n\n      for (i = 0; i < this.token.cells.length; i++) {\n        row = this.token.cells[i];\n\n        cell = '';\n        for (j = 0; j < row.length; j++) {\n          cell += this.renderer.tablecell(\n            this.inline.output(row[j]),\n            { header: false, align: this.token.align[j] }\n          );\n        }\n\n        body += this.renderer.tablerow(cell);\n      }\n      return this.renderer.table(header, body);\n    }\n    case 'blockquote_start': {\n      var body = '';\n\n      while (this.next().type !== 'blockquote_end') {\n        body += this.tok();\n      }\n\n      return this.renderer.blockquote(body);\n    }\n    case 'list_start': {\n      var body = ''\n        , ordered = this.token.ordered;\n\n      while (this.next().type !== 'list_end') {\n        body += this.tok();\n      }\n\n      return this.renderer.list(body, ordered);\n    }\n    case 'list_item_start': {\n      var body = '';\n\n      while (this.next().type !== 'list_item_end') {\n        body += this.token.type === 'text'\n          ? this.parseText()\n          : this.tok();\n      }\n\n      return this.renderer.listitem(body);\n    }\n    case 'loose_item_start': {\n      var body = '';\n\n      while (this.next().type !== 'list_item_end') {\n        body += this.tok();\n      }\n\n      return this.renderer.listitem(body);\n    }\n    case 'html': {\n      var html = !this.token.pre && !this.options.pedantic\n        ? this.inline.output(this.token.text)\n        : this.token.text;\n      return this.renderer.html(html);\n    }\n    case 'paragraph': {\n      return this.renderer.paragraph(this.inline.output(this.token.text));\n    }\n    case 'text': {\n      return this.renderer.paragraph(this.parseText());\n    }\n  }\n};\n\n/**\n * Helpers\n */\n\nfunction escape(html, encode) {\n  return html\n    .replace(!encode ? /&(?!#?\\w+;)/g : /&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#39;');\n}\n\nfunction unescape(html) {\n  return html.replace(/&([#\\w]+);/g, function(_, n) {\n    n = n.toLowerCase();\n    if (n === 'colon') return ':';\n    if (n.charAt(0) === '#') {\n      return n.charAt(1) === 'x'\n        ? String.fromCharCode(parseInt(n.substring(2), 16))\n        : String.fromCharCode(+n.substring(1));\n    }\n    return '';\n  });\n}\n\nfunction replace(regex, opt) {\n  regex = regex.source;\n  opt = opt || '';\n  return function self(name, val) {\n    if (!name) return new RegExp(regex, opt);\n    val = val.source || val;\n    val = val.replace(/(^|[^\\[])\\^/g, '$1');\n    regex = regex.replace(name, val);\n    return self;\n  };\n}\n\nfunction noop() {}\nnoop.exec = noop;\n\nfunction merge(obj) {\n  var i = 1\n    , target\n    , key;\n\n  for (; i < arguments.length; i++) {\n    target = arguments[i];\n    for (key in target) {\n      if (Object.prototype.hasOwnProperty.call(target, key)) {\n        obj[key] = target[key];\n      }\n    }\n  }\n\n  return obj;\n}\n\n\n/**\n * Marked\n */\n\nfunction marked(src, opt, callback) {\n  if (callback || typeof opt === 'function') {\n    if (!callback) {\n      callback = opt;\n      opt = null;\n    }\n\n    opt = merge({}, marked.defaults, opt || {});\n\n    var highlight = opt.highlight\n      , tokens\n      , pending\n      , i = 0;\n\n    try {\n      tokens = Lexer.lex(src, opt)\n    } catch (e) {\n      return callback(e);\n    }\n\n    pending = tokens.length;\n\n    var done = function(err) {\n      if (err) {\n        opt.highlight = highlight;\n        return callback(err);\n      }\n\n      var out;\n\n      try {\n        out = Parser.parse(tokens, opt);\n      } catch (e) {\n        err = e;\n      }\n\n      opt.highlight = highlight;\n\n      return err\n        ? callback(err)\n        : callback(null, out);\n    };\n\n    if (!highlight || highlight.length < 3) {\n      return done();\n    }\n\n    delete opt.highlight;\n\n    if (!pending) return done();\n\n    for (; i < tokens.length; i++) {\n      (function(token) {\n        if (token.type !== 'code') {\n          return --pending || done();\n        }\n        return highlight(token.text, token.lang, function(err, code) {\n          if (err) return done(err);\n          if (code == null || code === token.text) {\n            return --pending || done();\n          }\n          token.text = code;\n          token.escaped = true;\n          --pending || done();\n        });\n      })(tokens[i]);\n    }\n\n    return;\n  }\n  try {\n    if (opt) opt = merge({}, marked.defaults, opt);\n    return Parser.parse(Lexer.lex(src, opt), opt);\n  } catch (e) {\n    e.message += '\\nPlease report this to https://github.com/chjj/marked.';\n    if ((opt || marked.defaults).silent) {\n      return '<p>An error occured:</p><pre>'\n        + escape(e.message + '', true)\n        + '</pre>';\n    }\n    throw e;\n  }\n}\n\n/**\n * Options\n */\n\nmarked.options =\nmarked.setOptions = function(opt) {\n  merge(marked.defaults, opt);\n  return marked;\n};\n\nmarked.defaults = {\n  gfm: true,\n  tables: true,\n  breaks: false,\n  pedantic: false,\n  sanitize: false,\n  smartLists: false,\n  silent: false,\n  highlight: null,\n  langPrefix: 'lang-',\n  smartypants: false,\n  headerPrefix: '',\n  renderer: new Renderer,\n  xhtml: false\n};\n\n/**\n * Expose\n */\n\nmarked.Parser = Parser;\nmarked.parser = Parser.parse;\n\nmarked.Renderer = Renderer;\n\nmarked.Lexer = Lexer;\nmarked.lexer = Lexer.lex;\n\nmarked.InlineLexer = InlineLexer;\nmarked.inlineLexer = InlineLexer.output;\n\nmarked.parse = marked;\n\nif (typeof module !== 'undefined' && typeof exports === 'object') {\n  module.exports = marked;\n} else if (typeof define === 'function' && define.amd) {\n  define(function() { return marked; });\n} else {\n  this.marked = marked;\n}\n\n}).call(function() {\n  return this || (typeof window !== 'undefined' ? window : global);\n}());"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lib/object-assign-pollyfill.js",
    "content": "if (typeof Object.assign != 'function') {\n  (function () {\n    Object.assign = function (target) {\n      'use strict';\n      if (target === undefined || target === null) {\n        throw new TypeError('Cannot convert undefined or null to object');\n      }\n\n      var output = Object(target);\n      for (var index = 1; index < arguments.length; index++) {\n        var source = arguments[index];\n        if (source !== undefined && source !== null) {\n          for (var nextKey in source) {\n            if (Object.prototype.hasOwnProperty.call(source, nextKey)) {\n              output[nextKey] = source[nextKey];\n            }\n          }\n        }\n      }\n      return output;\n    };\n  })();\n}\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/lib/swagger-oauth.js",
    "content": "var appName;\nvar popupMask;\nvar popupDialog;\nvar clientId;\nvar realm;\nvar redirect_uri;\nvar clientSecret;\nvar scopeSeparator;\nvar additionalQueryStringParams;\n\nfunction handleLogin() {\n  var scopes = [];\n\n  var auths = window.swaggerUi.api.authSchemes || window.swaggerUi.api.securityDefinitions;\n  if(auths) {\n    var key;\n    var defs = auths;\n    for(key in defs) {\n      var auth = defs[key];\n      if(auth.type === 'oauth2' && auth.scopes) {\n        var scope;\n        if(Array.isArray(auth.scopes)) {\n          // 1.2 support\n          var i;\n          for(i = 0; i < auth.scopes.length; i++) {\n            scopes.push(auth.scopes[i]);\n          }\n        }\n        else {\n          // 2.0 support\n          for(scope in auth.scopes) {\n            scopes.push({scope: scope, description: auth.scopes[scope], OAuthSchemeKey: key});\n          }\n        }\n      }\n    }\n  }\n\n  if(window.swaggerUi.api\n    && window.swaggerUi.api.info) {\n    appName = window.swaggerUi.api.info.title;\n  }\n\n  $('.api-popup-dialog').remove(); \n  popupDialog = $(\n    [\n      '<div class=\"api-popup-dialog\">',\n      '<div class=\"api-popup-title\">Select OAuth2.0 Scopes</div>',\n      '<div class=\"api-popup-content\">',\n        '<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.',\n          '<a href=\"#\">Learn how to use</a>',\n        '</p>',\n        '<p><strong>' + appName + '</strong> API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>',\n        '<ul class=\"api-popup-scopes\">',\n        '</ul>',\n        '<p class=\"error-msg\"></p>',\n        '<div class=\"api-popup-actions\"><button class=\"api-popup-authbtn api-button green\" type=\"button\">Authorize</button><button class=\"api-popup-cancel api-button gray\" type=\"button\">Cancel</button></div>',\n      '</div>',\n      '</div>'].join(''));\n  $(document.body).append(popupDialog);\n\n  //TODO: only display applicable scopes (will need to pass them into handleLogin)\n  popup = popupDialog.find('ul.api-popup-scopes').empty();\n  for (i = 0; i < scopes.length; i ++) {\n    scope = scopes[i];\n    str = '<li><input type=\"checkbox\" id=\"scope_' + i + '\" scope=\"' + scope.scope + '\"' +'\" oauthtype=\"' + scope.OAuthSchemeKey +'\"/>' + '<label for=\"scope_' + i + '\">' + scope.scope ;\n    if (scope.description) {\n      if ($.map(auths, function(n, i) { return i; }).length > 1) //if we have more than one scheme, display schemes\n\t    str += '<br/><span class=\"api-scope-desc\">' + scope.description + ' ('+ scope.OAuthSchemeKey+')' +'</span>';\n\t  else\n\t    str += '<br/><span class=\"api-scope-desc\">' + scope.description + '</span>';\n    }\n    str += '</label></li>';\n    popup.append(str);\n  }\n\n  var $win = $(window),\n    dw = $win.width(),\n    dh = $win.height(),\n    st = $win.scrollTop(),\n    dlgWd = popupDialog.outerWidth(),\n    dlgHt = popupDialog.outerHeight(),\n    top = (dh -dlgHt)/2 + st,\n    left = (dw - dlgWd)/2;\n\n  popupDialog.css({\n    top: (top < 0? 0 : top) + 'px',\n    left: (left < 0? 0 : left) + 'px'\n  });\n\n  popupDialog.find('button.api-popup-cancel').click(function() {\n    popupMask.hide();\n    popupDialog.hide();\n    popupDialog.empty();\n    popupDialog = [];\n  });\n\n  $('button.api-popup-authbtn').unbind();\n  popupDialog.find('button.api-popup-authbtn').click(function() {\n    popupMask.hide();\n    popupDialog.hide();\n\n    var authSchemes = window.swaggerUi.api.authSchemes;\n    var host = window.location;\n    var pathname = location.pathname.substring(0, location.pathname.lastIndexOf(\"/\"));\n    var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html';\n    var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl;\n    var url = null;\n    var scopes = []\n    var o = popup.find('input:checked'); \n    var OAuthSchemeKeys = [];\n    var state;\n    for(k =0; k < o.length; k++) {\n      var scope = $(o[k]).attr('scope');\n      if (scopes.indexOf(scope) === -1)\n        scopes.push(scope);\n      var OAuthSchemeKey = $(o[k]).attr('oauthtype');      \n      if (OAuthSchemeKeys.indexOf(OAuthSchemeKey) === -1)\n          OAuthSchemeKeys.push(OAuthSchemeKey);\n    }\n    \n    //TODO: merge not replace if scheme is different from any existing \n    //(needs to be aware of schemes to do so correctly)\n    window.enabledScopes=scopes;    \n    \n    for (var key in authSchemes) { \n      if (authSchemes.hasOwnProperty(key) && OAuthSchemeKeys.indexOf(key) != -1) { //only look at keys that match this scope.\n        var flow = authSchemes[key].flow;\n\n        if(authSchemes[key].type === 'oauth2' && flow && (flow === 'implicit' || flow === 'accessCode')) {\n          var dets = authSchemes[key];\n          url = dets.authorizationUrl + '?response_type=' + (flow === 'implicit' ? 'token' : 'code');\n          window.swaggerUi.tokenName = dets.tokenName || 'access_token';\n          window.swaggerUi.tokenUrl = (flow === 'accessCode' ? dets.tokenUrl : null);\n          state = key;\n        }\n        else if(authSchemes[key].type === 'oauth2' && flow && (flow === 'application')) {\n            var dets = authSchemes[key];\n            window.swaggerUi.tokenName = dets.tokenName || 'access_token';\n            clientCredentialsFlow(scopes, dets.tokenUrl, key);\n            return;\n        }        \n        else if(authSchemes[key].grantTypes) {\n          // 1.2 support\n          var o = authSchemes[key].grantTypes;\n          for(var t in o) {\n            if(o.hasOwnProperty(t) && t === 'implicit') {\n              var dets = o[t];\n              var ep = dets.loginEndpoint.url;\n              url = dets.loginEndpoint.url + '?response_type=token';\n              window.swaggerUi.tokenName = dets.tokenName;\n            }\n            else if (o.hasOwnProperty(t) && t === 'accessCode') {\n              var dets = o[t];\n              var ep = dets.tokenRequestEndpoint.url;\n              url = dets.tokenRequestEndpoint.url + '?response_type=code';\n              window.swaggerUi.tokenName = dets.tokenName;\n            }\n          }\n        }\n      }\n    }\n\n    redirect_uri = redirectUrl;\n\n    url += '&redirect_uri=' + encodeURIComponent(redirectUrl);\n    url += '&realm=' + encodeURIComponent(realm);\n    url += '&client_id=' + encodeURIComponent(clientId);\n    url += '&scope=' + encodeURIComponent(scopes.join(scopeSeparator));\n    url += '&state=' + encodeURIComponent(state);\n    for (var key in additionalQueryStringParams) {\n        url += '&' + key + '=' + encodeURIComponent(additionalQueryStringParams[key]);\n    }\n\n    window.open(url);\n  });\n\n  popupMask.show();\n  popupDialog.show();\n  return;\n}\n\n\nfunction handleLogout() {\n  for(key in window.swaggerUi.api.clientAuthorizations.authz){\n    window.swaggerUi.api.clientAuthorizations.remove(key)\n  }\n  window.enabledScopes = null;\n  $('.api-ic.ic-on').addClass('ic-off');\n  $('.api-ic.ic-on').removeClass('ic-on');\n\n  // set the info box\n  $('.api-ic.ic-warning').addClass('ic-error');\n  $('.api-ic.ic-warning').removeClass('ic-warning');\n}\n\nfunction initOAuth(opts) {\n  var o = (opts||{});\n  var errors = [];\n\n  appName = (o.appName||errors.push('missing appName'));\n  popupMask = (o.popupMask||$('#api-common-mask'));\n  popupDialog = (o.popupDialog||$('.api-popup-dialog'));\n  clientId = (o.clientId||errors.push('missing client id'));\n  clientSecret = (o.clientSecret||null);\n  realm = (o.realm||errors.push('missing realm'));\n  scopeSeparator = (o.scopeSeparator||' ');\n  additionalQueryStringParams = (o.additionalQueryStringParams||{});\n\n  if(errors.length > 0){\n    log('auth unable initialize oauth: ' + errors);\n    return;\n  }\n\n  $('pre code').each(function(i, e) {hljs.highlightBlock(e)});\n  $('.api-ic').unbind();\n  $('.api-ic').click(function(s) {\n    if($(s.target).hasClass('ic-off'))\n      handleLogin();\n    else {\n      handleLogout();\n    }\n    false;\n  });\n}\n\nfunction clientCredentialsFlow(scopes, tokenUrl, OAuthSchemeKey) {\n    var params = {\n      'client_id': clientId,\n      'client_secret': clientSecret,\n      'scope': scopes.join(' '),\n      'grant_type': 'client_credentials'\n    }\n    $.ajax(\n    {\n      url : tokenUrl,\n      type: \"POST\",\n      data: params,\n      success:function(data, textStatus, jqXHR)\n      {\n        onOAuthComplete(data,OAuthSchemeKey);\n      },\n      error: function(jqXHR, textStatus, errorThrown)\n      {\n        onOAuthComplete(\"\");\n      }\n    });\n    \n  }\n\nwindow.processOAuthCode = function processOAuthCode(data) {\n  var OAuthSchemeKey = data.state;\n\n  // redirect_uri is required in auth code flow \n  // see https://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1.3\n  var host = window.location;\n  var pathname = location.pathname.substring(0, location.pathname.lastIndexOf(\"/\"));\n  var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html';\n  var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl;\n\n  var params = {\n    'client_id': clientId,\n    'code': data.code,\n    'grant_type': 'authorization_code',\n    'redirect_uri': redirectUrl\n  };\n\n  if (clientSecret) {\n    params.client_secret = clientSecret;\n  }\n\n  $.ajax(\n  {\n    url : window.swaggerUi.tokenUrl,\n    type: \"POST\",\n    data: params,\n    success:function(data, textStatus, jqXHR)\n    {\n      onOAuthComplete(data, OAuthSchemeKey);\n    },\n    error: function(jqXHR, textStatus, errorThrown)\n    {\n      onOAuthComplete(\"\");\n    }\n  });\n};\n\nwindow.onOAuthComplete = function onOAuthComplete(token,OAuthSchemeKey) {\n  if(token) {\n    if(token.error) {\n      var checkbox = $('input[type=checkbox],.secured')\n      checkbox.each(function(pos){\n        checkbox[pos].checked = false;\n      });\n      alert(token.error);\n    }\n    else {\n      var b = token[window.swaggerUi.tokenName];      \n      if (!OAuthSchemeKey){\n          OAuthSchemeKey = token.state;\n      }\n      if(b){\n        // if all roles are satisfied\n        var o = null;\n        $.each($('.auth .api-ic .api_information_panel'), function(k, v) { \n          var children = v;\n          if(children && children.childNodes) {\n            var requiredScopes = [];\n            $.each((children.childNodes), function (k1, v1){\n              var inner = v1.innerHTML;\n              if(inner)\n                requiredScopes.push(inner);\n            });\n            var diff = [];\n            for(var i=0; i < requiredScopes.length; i++) {\n              var s = requiredScopes[i];\n              if(window.enabledScopes && window.enabledScopes.indexOf(s) == -1) {\n                diff.push(s);\n              }\n            }\n            if(diff.length > 0){\n              o = v.parentNode.parentNode;\n              $(o.parentNode).find('.api-ic.ic-on').addClass('ic-off');\n              $(o.parentNode).find('.api-ic.ic-on').removeClass('ic-on');\n\n              // sorry, not all scopes are satisfied\n              $(o).find('.api-ic').addClass('ic-warning');\n              $(o).find('.api-ic').removeClass('ic-error');\n            }\n            else {\n              o = v.parentNode.parentNode;\n              $(o.parentNode).find('.api-ic.ic-off').addClass('ic-on');\n              $(o.parentNode).find('.api-ic.ic-off').removeClass('ic-off');\n\n              // all scopes are satisfied\n              $(o).find('.api-ic').addClass('ic-info');\n              $(o).find('.api-ic').removeClass('ic-warning');\n              $(o).find('.api-ic').removeClass('ic-error');\n            }\n          }\n        });\n        window.swaggerUi.api.clientAuthorizations.add(window.OAuthSchemeKey, new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + b, 'header'));\n        window.swaggerUi.load();\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/o2c.html",
    "content": "<script>\nvar qp = null;\nif(window.location.hash) {\n  qp = location.hash.substring(1);\n}\nelse {\n  qp = location.search.substring(1);\n}\nqp = qp ? JSON.parse('{\"' + qp.replace(/&/g, '\",\"').replace(/=/g,'\":\"') + '\"}',\n  function(key, value) {\n    return key===\"\"?value:decodeURIComponent(value) }\n  ):{}\n\nif (window.opener.swaggerUi.tokenUrl)\n    window.opener.processOAuthCode(qp);\nelse\n    window.opener.onOAuthComplete(qp);\n\nwindow.close();\n</script>"
  },
  {
    "path": "src/backend/tools/py.doc/caliopen_api_doc/swagger-ui/swagger-ui.js",
    "content": "/**\n * swagger-ui - Swagger UI is a dependency-free collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API\n * @version v2.2.6\n * @link http://swagger.io\n * @license Apache-2.0\n */\n(function(){/* jshint ignore:start */ \n {(function() {\n  var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};\ntemplates['apikey_auth'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"                <span class=\\\"key_auth__value\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.value : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</span>\\n\";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    return \"                <input placeholder=\\\"api_key\\\" class=\\\"auth_input input_apiKey_entry\\\" name=\\\"apiKey\\\" type=\\\"text\\\"/>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<div class=\\\"key_input_container\\\">\\n    <h3 class=\\\"auth__title\\\">Api key authorization</h3>\\n    <div class=\\\"auth__description\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</div>\\n    <div>\\n        <div class=\\\"key_auth__field\\\">\\n            <span class=\\\"key_auth__label\\\">name:</span>\\n            <span class=\\\"key_auth__value\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</span>\\n        </div>\\n        <div class=\\\"key_auth__field\\\">\\n            <span class=\\\"key_auth__label\\\">in:</span>\\n            <span class=\\\"key_auth__value\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0[\"in\"] : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</span>\\n        </div>\\n        <div class=\\\"key_auth__field\\\">\\n            <span class=\\\"key_auth__label\\\">value:</span>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isLogout : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(3, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"        </div>\\n    </div>\\n</div>\\n\";\n},\"useData\":true});\ntemplates['auth_button'] = template({\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    return \"<a class='authorize__btn' href=\\\"#\\\">Authorize</a>\\n\";\n},\"useData\":true});\ntemplates['auth_button_operation'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    return \"        authorize__btn_operation_login\\n\";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    return \"        authorize__btn_operation_logout\\n\";\n},\"5\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"        <ul class=\\\"authorize-scopes\\\">\\n\"\n    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.scopes : depth0),{\"name\":\"each\",\"hash\":{},\"fn\":container.program(6, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"        </ul>\\n\";\n},\"6\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"                <li class=\\\"authorize__scope\\\" title=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.scope : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</li>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {};\n\n  return \"<div class=\\\"authorize__btn authorize__btn_operation\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isLogout : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(3, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.scopes : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(5, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"</div>\\n\";\n},\"useData\":true});\ntemplates['auth_view'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    return \"            <button type=\\\"button\\\" class=\\\"auth__button auth_submit__button\\\" data-sw-translate>Authorize</button>\\n\";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    return \"            <button type=\\\"button\\\" class=\\\"auth__button auth_logout__button\\\" data-sw-translate>Logout</button>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {};\n\n  return \"<div class=\\\"auth_container\\\">\\n\\n    <div class=\\\"auth_inner\\\"></div>\\n    <div class=\\\"auth_submit\\\">\\n\"\n    + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.isLogout : depth0),{\"name\":\"unless\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isAuthorized : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(3, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"    </div>\\n\\n</div>\\n\";\n},\"useData\":true});\ntemplates['basic_auth'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    return \" - authorized\";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"                <span class=\\\"basic_auth__value\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.username : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</span>\\n\";\n},\"5\":function(container,depth0,helpers,partials,data) {\n    return \"                <input required placeholder=\\\"username\\\" class=\\\"basic_auth__username auth_input\\\" name=\\\"username\\\" type=\\\"text\\\"/>\\n\";\n},\"7\":function(container,depth0,helpers,partials,data) {\n    return \"            <div class=\\\"auth_label\\\">\\n                <span class=\\\"basic_auth__label\\\" data-sw-translate>password:</span>\\n                <input required placeholder=\\\"password\\\" class=\\\"basic_auth__password auth_input\\\" name=\\\"password\\\" type=\\\"password\\\"/></label>\\n            </div>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {};\n\n  return \"<div class='basic_auth_container'>\\n    <h3 class=\\\"auth__title\\\">Basic authentication\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isLogout : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"</h3>\\n    <form class=\\\"basic_input_container\\\">\\n        <div class=\\\"auth__description\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</div>\\n        <div class=\\\"auth_label\\\">\\n            <span class=\\\"basic_auth__label\\\" data-sw-translate>username:</span>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isLogout : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(3, data, 0),\"inverse\":container.program(5, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"        </div>\\n\"\n    + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.isLogout : depth0),{\"name\":\"unless\",\"hash\":{},\"fn\":container.program(7, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"    </form>\\n</div>\\n\";\n},\"useData\":true});\ntemplates['content_type'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.produces : depth0),{\"name\":\"each\",\"hash\":{},\"fn\":container.program(2, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\");\n},\"2\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\t<option value=\\\"\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,depth0,{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,depth0,{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</option>\\n\";\n},\"4\":function(container,depth0,helpers,partials,data) {\n    return \"  <option value=\\\"application/json\\\">application/json</option>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<label data-sw-translate for=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.contentTypeId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">Response Content Type</label>\\n<select name=\\\"contentType\\\" id=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.contentTypeId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.produces : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(4, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"</select>\\n\";\n},\"useData\":true});\ntemplates['main'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"  <div class=\\\"info_title\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.title : stack1),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</div>\\n  <div class=\\\"info_description markdown\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.description : stack1),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</div>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.externalDocs : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(2, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"  \"\n    + ((stack1 = helpers[\"if\"].call(alias1,((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.termsOfServiceUrl : stack1),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(4, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n  \"\n    + ((stack1 = helpers[\"if\"].call(alias1,((stack1 = ((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.contact : stack1)) != null ? stack1.name : stack1),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(6, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n  \"\n    + ((stack1 = helpers[\"if\"].call(alias1,((stack1 = ((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.contact : stack1)) != null ? stack1.url : stack1),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(8, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n  \"\n    + ((stack1 = helpers[\"if\"].call(alias1,((stack1 = ((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.contact : stack1)) != null ? stack1.email : stack1),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(10, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n  \"\n    + ((stack1 = helpers[\"if\"].call(alias1,((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.license : stack1),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(12, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n\";\n},\"2\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"  <p>\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,((stack1 = (depth0 != null ? depth0.externalDocs : depth0)) != null ? stack1.description : stack1),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</p>\\n  <a href=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,((stack1 = (depth0 != null ? depth0.externalDocs : depth0)) != null ? stack1.url : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\" target=\\\"_blank\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,((stack1 = (depth0 != null ? depth0.externalDocs : depth0)) != null ? stack1.url : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</a>\\n\";\n},\"4\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"<div class=\\\"info_tos\\\"><a target=\\\"_blank\\\" href=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.termsOfServiceUrl : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\" data-sw-translate>Terms of service</a></div>\";\n},\"6\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"<div><div class='info_name' style=\\\"display: inline\\\" data-sw-translate>Created by </div> \"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(depth0 != null ? depth0 : {},((stack1 = ((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.contact : stack1)) != null ? stack1.name : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</div>\";\n},\"8\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<div class='info_url' data-sw-translate>See more at <a href=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,((stack1 = ((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.contact : stack1)) != null ? stack1.url : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,((stack1 = ((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.contact : stack1)) != null ? stack1.url : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</a></div>\";\n},\"10\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<div class='info_email'><a target=\\\"_parent\\\" href=\\\"mailto:\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,((stack1 = ((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.contact : stack1)) != null ? stack1.email : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"?subject=\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.title : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\" data-sw-translate>Contact the developer</a></div>\";\n},\"12\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<div class='info_license'><a target=\\\"_blank\\\" href='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,((stack1 = ((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.license : stack1)) != null ? stack1.url : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,((stack1 = ((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.license : stack1)) != null ? stack1.name : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</a></div>\";\n},\"14\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"  , <span style=\\\"font-variant: small-caps\\\" data-sw-translate>api version</span>: \"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.version : stack1),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n    \";\n},\"16\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {};\n\n  return \"<div class='info' id='api_info'>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.info : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"</div>\\n<div class='container' id='resources_container'>\\n  <div class='authorize-wrapper'></div>\\n\\n  <ul id='resources'></ul>\\n\\n  <div class=\\\"footer\\\">\\n    <h4 style=\\\"color: #999\\\">[ <span style=\\\"font-variant: small-caps\\\">base url</span>: \"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(alias1,(depth0 != null ? depth0.basePath : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,((stack1 = (depth0 != null ? depth0.info : depth0)) != null ? stack1.version : stack1),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(14, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"]\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.validatorUrl : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(16, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"    </h4>\\n    </div>\\n</div>\\n\";\n},\"useData\":true});\ntemplates['oauth2'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"            <li>\\n                <input class=\\\"oauth-scope\\\" type=\\\"checkbox\\\" data-scope=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.scope : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\" oauthtype=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.OAuthSchemeKey : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\"/>\\n                <label>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.scope : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</label><br/>\\n                <span class=\\\"api-scope-desc\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.OAuthSchemeKey : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(2, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"                </span>\\n            </li>\\n\";\n},\"2\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"                        (\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.OAuthSchemeKey : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \")\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<div>\\n    <h3 class=\\\"auth__title\\\">Select OAuth2.0 Scopes</h3>\\n    <p>\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</p>\\n    <p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.\\n        <a href=\\\"#\\\">Learn how to use</a>\\n    </p>\\n    <p><strong> \"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.appName : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \" </strong> API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>\\n    <p>Authorization URL: \"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.authorizationUrl : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</p>\\n    <p>flow: \"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.flow : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</p>\\n    <ul class=\\\"api-popup-scopes\\\">\\n\"\n    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.scopes : depth0),{\"name\":\"each\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"    </ul>\\n</div>\";\n},\"useData\":true});\ntemplates['operation'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    return \"deprecated\";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    return \"            <h4><span data-sw-translate>Warning: Deprecated</span></h4>\\n\";\n},\"5\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"        <h4><span data-sw-translate>Implementation Notes</span></h4>\\n        <div class=\\\"markdown\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</div>\\n\";\n},\"7\":function(container,depth0,helpers,partials,data) {\n    return \"            <div class='authorize-wrapper authorize-wrapper_operation'></div>\\n\";\n},\"9\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {};\n\n  return \"          <div class=\\\"response-class\\\">\\n            <h4><span data-sw-translate>Response Class</span> (<span data-sw-translate>Status</span> \"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(alias1,(depth0 != null ? depth0.successCode : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \")</h4>\\n              \"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.successDescription : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(10, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n            <p><span class=\\\"model-signature\\\" /></p>\\n            <br/>\\n            <div class=\\\"response-content-type\\\" />\\n            </div>\\n\";\n},\"10\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"<div class=\\\"markdown\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.successDescription : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</div>\";\n},\"12\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"          <h4 data-sw-translate>Headers</h4>\\n          <table class=\\\"headers\\\">\\n            <thead>\\n              <tr>\\n                <th style=\\\"width: 100px; max-width: 100px\\\" data-sw-translate>Header</th>\\n                <th style=\\\"width: 310px; max-width: 310px\\\" data-sw-translate>Description</th>\\n                <th style=\\\"width: 200px; max-width: 200px\\\" data-sw-translate>Type</th>\\n                <th style=\\\"width: 320px; max-width: 320px\\\" data-sw-translate>Other</th>\\n              </tr>\\n            </thead>\\n            <tbody>\\n\"\n    + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.headers : depth0),{\"name\":\"each\",\"hash\":{},\"fn\":container.program(13, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"            </tbody>\\n          </table>\\n\";\n},\"13\":function(container,depth0,helpers,partials,data) {\n    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"              <tr>\\n                <td>\"\n    + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2),(typeof helper === \"function\" ? helper.call(alias1,{\"name\":\"key\",\"hash\":{},\"data\":data}) : helper)))\n    + \"</td>\\n                <td>\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n                <td>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.type : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n                <td>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.other : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n              </tr>\\n\";\n},\"15\":function(container,depth0,helpers,partials,data) {\n    return \"          <h4 data-sw-translate>Parameters</h4>\\n          <table class='fullwidth parameters'>\\n          <thead>\\n            <tr>\\n            <th style=\\\"width: 100px; max-width: 100px\\\" data-sw-translate>Parameter</th>\\n            <th style=\\\"width: 310px; max-width: 310px\\\" data-sw-translate>Value</th>\\n            <th style=\\\"width: 200px; max-width: 200px\\\" data-sw-translate>Description</th>\\n            <th style=\\\"width: 100px; max-width: 100px\\\" data-sw-translate>Parameter Type</th>\\n            <th style=\\\"width: 220px; max-width: 230px\\\" data-sw-translate>Data Type</th>\\n            </tr>\\n          </thead>\\n          <tbody class=\\\"operation-params\\\">\\n\\n          </tbody>\\n          </table>\\n\";\n},\"17\":function(container,depth0,helpers,partials,data) {\n    return \"          <div style='margin:0;padding:0;display:inline'></div>\\n          <h4 data-sw-translate>Response Messages</h4>\\n          <table class='fullwidth response-messages'>\\n            <thead>\\n            <tr>\\n              <th data-sw-translate>HTTP Status Code</th>\\n              <th data-sw-translate>Reason</th>\\n              <th data-sw-translate>Response Model</th>\\n              <th data-sw-translate>Headers</th>\\n            </tr>\\n            </thead>\\n            <tbody class=\\\"operation-status\\\">\\n            </tbody>\\n          </table>\\n\";\n},\"19\":function(container,depth0,helpers,partials,data) {\n    return \"\";\n},\"21\":function(container,depth0,helpers,partials,data) {\n    return \"          <div class='sandbox_header'>\\n            <input class='submit' type='submit' value='Try it out!' data-sw-translate/>\\n            <a href='#' class='response_hider' style='display:none' data-sw-translate>Hide Response</a>\\n            <span class='response_throbber' style='display:none'></span>\\n          </div>\\n\";\n},\"23\":function(container,depth0,helpers,partials,data) {\n    return \"          <h4 data-sw-translate>Request Headers</h4>\\n          <div class='block request_headers'></div>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3=container.escapeExpression;\n\n  return \"  <ul class='operations' >\\n    <li class='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.method : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \" operation' id='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.parentId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"_\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.nickname : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\\n      <div class='heading'>\\n        <h3>\\n          <span class='http_method'>\\n          <a href='#!/\"\n    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.encodedParentId : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data}))\n    + \"/\"\n    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.nickname : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data}))\n    + \"' class=\\\"toggleOperation\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.method : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</a>\\n          </span>\\n          <span class='path'>\\n          <a href='#!/\"\n    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.encodedParentId : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data}))\n    + \"/\"\n    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.nickname : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data}))\n    + \"' class=\\\"toggleOperation \"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.deprecated : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.path : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</a>\\n          </span>\\n        </h3>\\n        <ul class='options'>\\n          <li>\\n          <a href='#!/\"\n    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.encodedParentId : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data}))\n    + \"/\"\n    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.nickname : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data}))\n    + \"' class=\\\"toggleOperation\\\"><span class=\\\"markdown\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.summary : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</span></a>\\n          </li>\\n        </ul>\\n      </div>\\n      <div class='content' id='\"\n    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.encodedParentId : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data}))\n    + \"_\"\n    + alias3((helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.nickname : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data}))\n    + \"_content' style='display:none'>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.deprecated : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(3, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(5, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.security : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(7, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.type : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(9, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.headers : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(12, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n        <form accept-charset='UTF-8' class='sandbox'>\\n          <div style='margin:0;padding:0;display:inline'></div>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.parameters : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(15, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.responseMessages : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(17, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isReadOnly : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(19, data, 0),\"inverse\":container.program(21, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"        </form>\\n        <div class='response' style='display:none'>\\n          <h4 class='curl'>Curl</h4>\\n          <div class='block curl'></div>\\n          <h4 data-sw-translate>Request URL</h4>\\n          <div class='block request_url'></div>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.showRequestHeaders : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(23, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"          <h4 data-sw-translate>Response Body</h4>\\n          <div class='block response_body'></div>\\n          <h4 data-sw-translate>Response Code</h4>\\n          <div class='block response_code'></div>\\n          <h4 data-sw-translate>Response Headers</h4>\\n          <div class='block response_headers'></div>\\n        </div>\\n      </div>\\n    </li>\\n  </ul>\\n\";\n},\"useData\":true});\ntemplates['param'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers[\"if\"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.isFile : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(2, data, 0),\"inverse\":container.program(4, data, 0),\"data\":data})) != null ? stack1 : \"\");\n},\"2\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\t\t\t<input type=\\\"file\\\" name='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'/>\\n\t\t\t<div class=\\\"parameter-content-type\\\" />\\n\";\n},\"4\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers[\"if\"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(5, data, 0),\"inverse\":container.program(7, data, 0),\"data\":data})) != null ? stack1 : \"\");\n},\"5\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\t\t\t\t<div class=\\\"editor_holder\\\"></div>\\n\t\t\t\t<textarea class='body-textarea' name='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</textarea>\\n        <br />\\n        <div class=\\\"parameter-content-type\\\" />\\n\";\n},\"7\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\t\t\t\t<textarea class='body-textarea' name='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'></textarea>\\n\t\t\t\t<div class=\\\"editor_holder\\\"></div>\\n\t\t\t\t<br />\\n\t\t\t\t<div class=\\\"parameter-content-type\\\" />\\n\";\n},\"9\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers[\"if\"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.isFile : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(2, data, 0),\"inverse\":container.program(10, data, 0),\"data\":data})) != null ? stack1 : \"\");\n},\"10\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = (helpers.renderTextParam || (depth0 && depth0.renderTextParam) || helpers.helperMissing).call(depth0 != null ? depth0 : {},depth0,{\"name\":\"renderTextParam\",\"hash\":{},\"fn\":container.program(11, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\");\n},\"11\":function(container,depth0,helpers,partials,data) {\n    return \"\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<td class='code'><label for='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</label></td>\\n<td>\\n\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isBody : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(9, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n</td>\\n<td class=\\\"markdown\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.paramType : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td>\\n\t<span class=\\\"model-signature\\\"></span>\\n</td>\\n\";\n},\"useData\":true});\ntemplates['param_list'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    return \" required\";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    return \" multiple=\\\"multiple\\\"\";\n},\"5\":function(container,depth0,helpers,partials,data) {\n    return \" required \";\n},\"7\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"      <option \"\n    + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.hasDefault : depth0),{\"name\":\"unless\",\"hash\":{},\"fn\":container.program(8, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \" value=''></option>\\n\";\n},\"8\":function(container,depth0,helpers,partials,data) {\n    return \"  selected=\\\"\\\" \";\n},\"10\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\\n      <option \"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isDefault : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(11, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"  value='\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.value : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'> \"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.value : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \" \"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isDefault : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(13, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \" </option>\\n\\n\";\n},\"11\":function(container,depth0,helpers,partials,data) {\n    return \" selected=\\\"\\\"  \";\n},\"13\":function(container,depth0,helpers,partials,data) {\n    return \" (default) \";\n},\"15\":function(container,depth0,helpers,partials,data) {\n    return \"<strong>\";\n},\"17\":function(container,depth0,helpers,partials,data) {\n    return \"</strong>\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<td class='code\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.required : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"'><label for='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</label></td>\\n<td>\\n  <select \"\n    + ((stack1 = (helpers.isArray || (depth0 && depth0.isArray) || alias2).call(alias1,depth0,{\"name\":\"isArray\",\"hash\":{},\"fn\":container.program(3, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \" class=\\\"parameter \"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.required : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(5, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\" name=\\\"\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\" id=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\\n\\n\"\n    + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.required : depth0),{\"name\":\"unless\",\"hash\":{},\"fn\":container.program(7, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n\"\n    + ((stack1 = helpers.each.call(alias1,((stack1 = (depth0 != null ? depth0.allowableValues : depth0)) != null ? stack1.descriptiveValues : stack1),{\"name\":\"each\",\"hash\":{},\"fn\":container.program(10, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n  </select>\\n</td>\\n<td class=\\\"markdown\\\">\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.required : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(15, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + ((stack1 = ((helper = (helper = helpers.description || (depth0 != null ? depth0.description : depth0)) != null ? helper : alias2),(typeof helper === \"function\" ? helper.call(alias1,{\"name\":\"description\",\"hash\":{},\"data\":data}) : helper))) != null ? stack1 : \"\")\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.required : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(17, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.paramType : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td><span class=\\\"model-signature\\\"></span></td>\\n\";\n},\"useData\":true});\ntemplates['param_readonly'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"        <textarea class='body-textarea' readonly='readonly' name='\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</textarea>\\n        <div class=\\\"parameter-content-type\\\" />\\n\";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers[\"if\"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(4, data, 0),\"inverse\":container.program(6, data, 0),\"data\":data})) != null ? stack1 : \"\");\n},\"4\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"            \"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n\";\n},\"6\":function(container,depth0,helpers,partials,data) {\n    return \"            (empty)\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<td class='code'><label for='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</label></td>\\n<td>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isBody : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(3, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td class=\\\"markdown\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.paramType : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td><span class=\\\"model-signature\\\"></span></td>\\n\";\n},\"useData\":true});\ntemplates['param_readonly_required'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"        <textarea class='body-textarea' readonly='readonly' placeholder='(required)' name='\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</textarea>\\n\";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers[\"if\"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(4, data, 0),\"inverse\":container.program(6, data, 0),\"data\":data})) != null ? stack1 : \"\");\n},\"4\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"            \"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n\";\n},\"6\":function(container,depth0,helpers,partials,data) {\n    return \"            (empty)\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<td class='code required'><label for='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</label></td>\\n<td>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isBody : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(3, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td class=\\\"markdown\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.paramType : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td><span class=\\\"model-signature\\\"></span></td>\\n\";\n},\"useData\":true});\ntemplates['param_required'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers[\"if\"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.isFile : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(2, data, 0),\"inverse\":container.program(4, data, 0),\"data\":data})) != null ? stack1 : \"\");\n},\"2\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\t\t\t<input type=\\\"file\\\" name='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'/>\\n\";\n},\"4\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers[\"if\"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(5, data, 0),\"inverse\":container.program(7, data, 0),\"data\":data})) != null ? stack1 : \"\");\n},\"5\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\t\t\t\t<div class=\\\"editor_holder\\\"></div>\\n\t\t\t\t<textarea class='body-textarea required' placeholder='(required)' name='\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id=\\\"\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0[\"default\"] : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</textarea>\\n        <br />\\n        <div class=\\\"parameter-content-type\\\" />\\n\";\n},\"7\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\t\t\t\t<textarea class='body-textarea required' placeholder='(required)' name='\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'></textarea>\\n\t\t\t\t<div class=\\\"editor_holder\\\"></div>\\n\t\t\t\t<br />\\n\t\t\t\t<div class=\\\"parameter-content-type\\\" />\\n\";\n},\"9\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers[\"if\"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.isFile : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(10, data, 0),\"inverse\":container.program(12, data, 0),\"data\":data})) != null ? stack1 : \"\");\n},\"10\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"\t\t\t<input class='parameter' class='required' type='file' name='\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'/>\\n\";\n},\"12\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = (helpers.renderTextParam || (depth0 && depth0.renderTextParam) || helpers.helperMissing).call(depth0 != null ? depth0 : {},depth0,{\"name\":\"renderTextParam\",\"hash\":{},\"fn\":container.program(13, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\");\n},\"13\":function(container,depth0,helpers,partials,data) {\n    return \"\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<td class='code required'><label for='\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.valueId : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"'>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</label></td>\\n<td>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isBody : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(9, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td>\\n\t<strong><span class=\\\"markdown\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</span></strong>\\n</td>\\n<td>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.paramType : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td><span class=\\\"model-signature\\\"></span></td>\\n\";\n},\"useData\":true});\ntemplates['parameter_content_type'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.consumes : depth0),{\"name\":\"each\",\"hash\":{},\"fn\":container.program(2, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\");\n},\"2\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"  <option value=\\\"\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,depth0,{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,depth0,{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</option>\\n\";\n},\"4\":function(container,depth0,helpers,partials,data) {\n    return \"  <option value=\\\"application/json\\\">application/json</option>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<label for=\\\"\"\n    + container.escapeExpression(((helper = (helper = helpers.parameterContentTypeId || (depth0 != null ? depth0.parameterContentTypeId : depth0)) != null ? helper : alias2),(typeof helper === \"function\" ? helper.call(alias1,{\"name\":\"parameterContentTypeId\",\"hash\":{},\"data\":data}) : helper)))\n    + \"\\\" data-sw-translate>Parameter content type:</label>\\n<select name=\\\"parameterContentType\\\" id=\\\"\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.parameterContentTypeId : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.consumes : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(4, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"</select>\\n\";\n},\"useData\":true});\ntemplates['popup'] = template({\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var helper;\n\n  return \"<div class=\\\"api-popup-dialog-wrapper\\\">\\n    <div class=\\\"api-popup-title\\\">\"\n    + container.escapeExpression(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === \"function\" ? helper.call(depth0 != null ? depth0 : {},{\"name\":\"title\",\"hash\":{},\"data\":data}) : helper)))\n    + \"</div>\\n    <div class=\\\"api-popup-content\\\"></div>\\n    <p class=\\\"error-msg\\\"></p>\\n    <div class=\\\"api-popup-actions\\\">\\n        <button class=\\\"api-popup-cancel api-button gray\\\" type=\\\"button\\\">Cancel</button>\\n    </div>\\n</div>\\n<div class=\\\"api-popup-dialog-shadow\\\"></div>\";\n},\"useData\":true});\ntemplates['resource'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    return \" : \";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"    <li>\\n      <a href='\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.url : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' data-sw-translate>Raw</a>\\n    </li>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, helper, options, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, buffer = \n  \"<div class='heading'>\\n  <h2>\\n    <a href='#!/\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.id : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' class=\\\"toggleEndpointList\\\" data-id=\\\"\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.id : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.name : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</a> \";\n  stack1 = ((helper = (helper = helpers.summary || (depth0 != null ? depth0.summary : depth0)) != null ? helper : alias2),(options={\"name\":\"summary\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.noop,\"data\":data}),(typeof helper === \"function\" ? helper.call(alias1,options) : helper));\n  if (!helpers.summary) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}\n  if (stack1 != null) { buffer += stack1; }\n  return buffer + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.summary : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n  </h2>\\n  <ul class='options'>\\n    <li>\\n      <a href='#!/\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.id : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' id='endpointListTogger_\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.id : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"' class=\\\"toggleEndpointList\\\" data-id=\\\"\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.id : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\" data-sw-translate>Show/Hide</a>\\n    </li>\\n    <li>\\n      <a href='#' class=\\\"collapseResource\\\" data-id=\\\"\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.id : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\" data-sw-translate>\\n        List Operations\\n      </a>\\n    </li>\\n    <li>\\n      <a href='#' class=\\\"expandResource\\\" data-id=\\\"\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.id : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\" data-sw-translate>\\n        Expand Operations\\n      </a>\\n    </li>\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.url : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(3, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"  </ul>\\n</div>\\n<ul class='endpoints' id='\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.id : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"_endpoint_list' style='display:none'>\\n\\n</ul>\\n\";\n},\"useData\":true});\ntemplates['response_content_type'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.produces : depth0),{\"name\":\"each\",\"hash\":{},\"fn\":container.program(2, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\");\n},\"2\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"  <option value=\\\"\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,depth0,{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\\">\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,depth0,{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</option>\\n\";\n},\"4\":function(container,depth0,helpers,partials,data) {\n    return \"  <option value=\\\"application/json\\\">application/json</option>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3=\"function\", alias4=container.escapeExpression;\n\n  return \"<label data-sw-translate for=\\\"\"\n    + alias4(((helper = (helper = helpers.responseContentTypeId || (depth0 != null ? depth0.responseContentTypeId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{\"name\":\"responseContentTypeId\",\"hash\":{},\"data\":data}) : helper)))\n    + \"\\\">Response Content Type</label>\\n<select name=\\\"responseContentType\\\" id=\\\"\"\n    + alias4(((helper = (helper = helpers.responseContentTypeId || (depth0 != null ? depth0.responseContentTypeId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{\"name\":\"responseContentTypeId\",\"hash\":{},\"data\":data}) : helper)))\n    + \"\\\">\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.produces : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(4, data, 0),\"data\":data})) != null ? stack1 : \"\")\n    + \"</select>\\n\";\n},\"useData\":true});\ntemplates['signature'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {};\n\n  return \"\\n<div>\\n<ul class=\\\"signature-nav\\\">\\n  <li><a class=\\\"description-link\\\" href=\\\"#\\\" data-sw-translate>Model</a></li>\\n  <li><a class=\\\"snippet-link\\\" href=\\\"#\\\" data-sw-translate>Example Value</a></li>\\n</ul>\\n<div>\\n\\n<div class=\\\"signature-container\\\">\\n  <div class=\\\"description\\\">\\n      \"\n    + container.escapeExpression((helpers.sanitize || (depth0 && depth0.sanitize) || helpers.helperMissing).call(alias1,(depth0 != null ? depth0.signature : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data}))\n    + \"\\n  </div>\\n\\n  <div class=\\\"snippet\\\">\\n\"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.sampleJSON : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(2, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.sampleXML : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(5, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"  </div>\\n</div>\\n\";\n},\"2\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {};\n\n  return \"      <div class=\\\"snippet_json\\\">\\n        <pre><code>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(alias1,(depth0 != null ? depth0.sampleJSON : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</code></pre>\\n        \"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isParam : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(3, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n      </div>\\n\";\n},\"3\":function(container,depth0,helpers,partials,data) {\n    return \"<small class=\\\"notice\\\" data-sw-translate></small>\";\n},\"5\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {};\n\n  return \"    <div class=\\\"snippet_xml\\\">\\n      <pre><code>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(alias1,(depth0 != null ? depth0.sampleXML : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</code></pre>\\n      \"\n    + ((stack1 = helpers[\"if\"].call(alias1,(depth0 != null ? depth0.isParam : depth0),{\"name\":\"if\",\"hash\":{},\"fn\":container.program(3, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n    </div>\\n\";\n},\"7\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return \"    \"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.signature : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1;\n\n  return ((stack1 = (helpers.ifCond || (depth0 && depth0.ifCond) || helpers.helperMissing).call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.sampleJSON : depth0),\"||\",(depth0 != null ? depth0.sampleXML : depth0),{\"name\":\"ifCond\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.program(7, data, 0),\"data\":data})) != null ? stack1 : \"\");\n},\"useData\":true});\ntemplates['status_code'] = template({\"1\":function(container,depth0,helpers,partials,data) {\n    var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"      <tr>\\n        <td>\"\n    + container.escapeExpression(((helper = (helper = helpers.key || (data && data.key)) != null ? helper : alias2),(typeof helper === \"function\" ? helper.call(alias1,{\"name\":\"key\",\"hash\":{},\"data\":data}) : helper)))\n    + \"</td>\\n        <td>\"\n    + ((stack1 = (helpers.sanitize || (depth0 && depth0.sanitize) || alias2).call(alias1,(depth0 != null ? depth0.description : depth0),{\"name\":\"sanitize\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n        <td>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.type : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n      </tr>\\n\";\n},\"compiler\":[7,\">= 4.0.0\"],\"main\":function(container,depth0,helpers,partials,data) {\n    var stack1, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing;\n\n  return \"<td width='15%' class='code'>\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.code : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td class=\\\"markdown\\\">\"\n    + ((stack1 = (helpers.escape || (depth0 && depth0.escape) || alias2).call(alias1,(depth0 != null ? depth0.message : depth0),{\"name\":\"escape\",\"hash\":{},\"data\":data})) != null ? stack1 : \"\")\n    + \"</td>\\n<td width='50%'><span class=\\\"model-signature\\\" /></td>\\n<td class=\\\"headers\\\">\\n  <table>\\n    <tbody>\\n\"\n    + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.headers : depth0),{\"name\":\"each\",\"hash\":{},\"fn\":container.program(1, data, 0),\"inverse\":container.noop,\"data\":data})) != null ? stack1 : \"\")\n    + \"    </tbody>\\n  </table>\\n</td>\";\n},\"useData\":true});\n})();} \n /* jshint ignore:end */\n'use strict';\n\n\n$(function() {\n\n\t// Helper function for vertically aligning DOM elements\n\t// http://www.seodenver.com/simple-vertical-align-plugin-for-jquery/\n\t$.fn.vAlign = function() {\n\t\treturn this.each(function(){\n\t\t\tvar ah = $(this).height();\n\t\t\tvar ph = $(this).parent().height();\n\t\t\tvar mh = (ph - ah) / 2;\n\t\t\t$(this).css('margin-top', mh);\n\t\t});\n\t};\n\n\t$.fn.stretchFormtasticInputWidthToParent = function() {\n\t\treturn this.each(function(){\n\t\t\tvar p_width = $(this).closest(\"form\").innerWidth();\n\t\t\tvar p_padding = parseInt($(this).closest(\"form\").css('padding-left') ,10) + parseInt($(this).closest('form').css('padding-right'), 10);\n\t\t\tvar this_padding = parseInt($(this).css('padding-left'), 10) + parseInt($(this).css('padding-right'), 10);\n\t\t\t$(this).css('width', p_width - p_padding - this_padding);\n\t\t});\n\t};\n\n\t$('form.formtastic li.string input, form.formtastic textarea').stretchFormtasticInputWidthToParent();\n\n\t// Vertically center these paragraphs\n\t// Parent may need a min-height for this to work..\n\t$('ul.downplayed li div.content p').vAlign();\n\n\t// When a sandbox form is submitted..\n\t$(\"form.sandbox\").submit(function(){\n\n\t\tvar error_free = true;\n\n\t\t// Cycle through the forms required inputs\n \t\t$(this).find(\"input.required\").each(function() {\n\n\t\t\t// Remove any existing error styles from the input\n\t\t\t$(this).removeClass('error');\n\n\t\t\t// Tack the error style on if the input is empty..\n\t\t\tif ($(this).val() === '') {\n\t\t\t\t$(this).addClass('error');\n\t\t\t\t$(this).wiggle();\n\t\t\t\terror_free = false;\n\t\t\t}\n\n\t\t});\n\n\t\treturn error_free;\n\t});\n\n});\n\nfunction clippyCopiedCallback() {\n  $('#api_key_copied').fadeIn().delay(1000).fadeOut();\n\n  // var b = $(\"#clippy_tooltip_\" + a);\n  // b.length != 0 && (b.attr(\"title\", \"copied!\").trigger(\"tipsy.reload\"), setTimeout(function() {\n  //   b.attr(\"title\", \"copy to clipboard\")\n  // },\n  // 500))\n}\n\n// Logging function that accounts for browsers that don't have window.console\nfunction log(){\n  log.history = log.history || [];\n  log.history.push(arguments);\n  if(this.console){\n    console.log( Array.prototype.slice.call(arguments)[0] );\n  }\n}\n\n// Handle browsers that do console incorrectly (IE9 and below, see http://stackoverflow.com/a/5539378/7913)\nif (Function.prototype.bind && console && typeof console.log === \"object\") {\n    [\n      \"log\",\"info\",\"warn\",\"error\",\"assert\",\"dir\",\"clear\",\"profile\",\"profileEnd\"\n    ].forEach(function (method) {\n        console[method] = this.bind(console[method], console);\n    }, Function.prototype.call);\n}\n\nwindow.Docs = {\n\n\tshebang: function() {\n\n\t\t// If shebang has an operation nickname in it..\n\t\t// e.g. /docs/#!/words/get_search\n\t\tvar fragments = $.param.fragment().split('/');\n\t\tfragments.shift(); // get rid of the bang\n\n\t\tswitch (fragments.length) {\n\t\t\tcase 1:\n        if (fragments[0].length > 0) { // prevent matching \"#/\"\n          // Expand all operations for the resource and scroll to it\n          var dom_id = 'resource_' + fragments[0];\n\n          Docs.expandEndpointListForResource(fragments[0]);\n          $(\"#\"+dom_id).slideto({highlight: false});\n        }\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\t// Refer to the endpoint DOM element, e.g. #words_get_search\n\n        // Expand Resource\n        Docs.expandEndpointListForResource(fragments[0]);\n        $(\"#\"+dom_id).slideto({highlight: false});\n\n            // Expand operation\n            var li_dom_id = fragments.join('_');\n            var li_content_dom_id = li_dom_id + \"_content\";\n\n\n            Docs.expandOperation($('#'+li_content_dom_id));\n            $('#'+li_dom_id).slideto({highlight: false});\n            break;\n\t\t}\n\t},\n\n\ttoggleEndpointListForResource: function(resource) {\n\t\tvar elem = $('li#resource_' + Docs.escapeResourceName(resource) + ' ul.endpoints');\n\t\tif (elem.is(':visible')) {\n\t\t\t$.bbq.pushState('#/', 2);\n\t\t\tDocs.collapseEndpointListForResource(resource);\n\t\t} else {\n            $.bbq.pushState('#/' + resource, 2);\n\t\t\tDocs.expandEndpointListForResource(resource);\n\t\t}\n\t},\n\n\t// Expand resource\n\texpandEndpointListForResource: function(resource) {\n\t\tvar resource = Docs.escapeResourceName(resource);\n\t\tif (resource == '') {\n\t\t\t$('.resource ul.endpoints').slideDown();\n\t\t\treturn;\n\t\t}\n\n\t\t$('li#resource_' + resource).addClass('active');\n\n\t\tvar elem = $('li#resource_' + resource + ' ul.endpoints');\n\t\telem.slideDown();\n\t},\n\n\t// Collapse resource and mark as explicitly closed\n\tcollapseEndpointListForResource: function(resource) {\n\t\tvar resource = Docs.escapeResourceName(resource);\n\t\tif (resource == '') {\n\t\t\t$('.resource ul.endpoints').slideUp();\n\t\t\treturn;\n\t\t}\n\n\t\t$('li#resource_' + resource).removeClass('active');\n\n\t\tvar elem = $('li#resource_' + resource + ' ul.endpoints');\n\t\telem.slideUp();\n\t},\n\n\texpandOperationsForResource: function(resource) {\n\t\t// Make sure the resource container is open..\n\t\tDocs.expandEndpointListForResource(resource);\n\n\t\tif (resource == '') {\n\t\t\t$('.resource ul.endpoints li.operation div.content').slideDown();\n\t\t\treturn;\n\t\t}\n\n\t\t$('li#resource_' + Docs.escapeResourceName(resource) + ' li.operation div.content').each(function() {\n\t\t\tDocs.expandOperation($(this));\n\t\t});\n\t},\n\n\tcollapseOperationsForResource: function(resource) {\n\t\t// Make sure the resource container is open..\n\t\tDocs.expandEndpointListForResource(resource);\n\n\t\tif (resource == '') {\n\t\t\t$('.resource ul.endpoints li.operation div.content').slideUp();\n\t\t\treturn;\n\t\t}\n\n\t\t$('li#resource_' + Docs.escapeResourceName(resource) + ' li.operation div.content').each(function() {\n\t\t\tDocs.collapseOperation($(this));\n\t\t});\n\t},\n\n\tescapeResourceName: function(resource) {\n\t\treturn resource.replace(/[!\"#$%&'()*+,.\\/:;<=>?@\\[\\\\\\]\\^`{|}~]/g, \"\\\\$&\");\n\t},\n\n\texpandOperation: function(elem) {\n\t\telem.slideDown();\n\t},\n\n\tcollapseOperation: function(elem) {\n\t\telem.slideUp();\n\t}\n};\n\n/*!\n * https://github.com/es-shims/es5-shim\n * @license es5-shim Copyright 2009-2015 by contributors, MIT License\n * see https://github.com/es-shims/es5-shim/blob/master/LICENSE\n */\n\n// vim: ts=4 sts=4 sw=4 expandtab\n\n// Add semicolon to prevent IIFE from being passed as argument to concatenated code.\n;\n\n// UMD (Universal Module Definition)\n// see https://github.com/umdjs/umd/blob/master/templates/returnExports.js\n(function (root, factory) {\n    'use strict';\n\n    /* global define, exports, module */\n    if (typeof define === 'function' && define.amd) {\n        // AMD. Register as an anonymous module.\n        define(factory);\n    } else if (typeof exports === 'object') {\n        // Node. Does not work with strict CommonJS, but\n        // only CommonJS-like enviroments that support module.exports,\n        // like Node.\n        module.exports = factory();\n    } else {\n        // Browser globals (root is window)\n        root.returnExports = factory();\n    }\n}(this, function () {\n    /**\n     * Brings an environment as close to ECMAScript 5 compliance\n     * as is possible with the facilities of erstwhile engines.\n     *\n     * Annotated ES5: http://es5.github.com/ (specific links below)\n     * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf\n     * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/\n     */\n\n    // Shortcut to an often accessed properties, in order to avoid multiple\n    // dereference that costs universally. This also holds a reference to known-good\n    // functions.\n    var $Array = Array;\n    var ArrayPrototype = $Array.prototype;\n    var $Object = Object;\n    var ObjectPrototype = $Object.prototype;\n    var $Function = Function;\n    var FunctionPrototype = $Function.prototype;\n    var $String = String;\n    var StringPrototype = $String.prototype;\n    var $Number = Number;\n    var NumberPrototype = $Number.prototype;\n    var array_slice = ArrayPrototype.slice;\n    var array_splice = ArrayPrototype.splice;\n    var array_push = ArrayPrototype.push;\n    var array_unshift = ArrayPrototype.unshift;\n    var array_concat = ArrayPrototype.concat;\n    var array_join = ArrayPrototype.join;\n    var call = FunctionPrototype.call;\n    var apply = FunctionPrototype.apply;\n    var max = Math.max;\n    var min = Math.min;\n\n    // Having a toString local variable name breaks in Opera so use to_string.\n    var to_string = ObjectPrototype.toString;\n\n    /* global Symbol */\n    /* eslint-disable one-var-declaration-per-line, no-redeclare, max-statements-per-line */\n    var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';\n    var isCallable; /* inlined from https://npmjs.com/is-callable */ var fnToStr = Function.prototype.toString, constructorRegex = /^\\s*class /, isES6ClassFn = function isES6ClassFn(value) { try { var fnStr = fnToStr.call(value); var singleStripped = fnStr.replace(/\\/\\/.*\\n/g, ''); var multiStripped = singleStripped.replace(/\\/\\*[.\\s\\S]*\\*\\//g, ''); var spaceStripped = multiStripped.replace(/\\n/mg, ' ').replace(/ {2}/g, ' '); return constructorRegex.test(spaceStripped); } catch (e) { return false; /* not a function */ } }, tryFunctionObject = function tryFunctionObject(value) { try { if (isES6ClassFn(value)) { return false; } fnToStr.call(value); return true; } catch (e) { return false; } }, fnClass = '[object Function]', genClass = '[object GeneratorFunction]', isCallable = function isCallable(value) { if (!value) { return false; } if (typeof value !== 'function' && typeof value !== 'object') { return false; } if (hasToStringTag) { return tryFunctionObject(value); } if (isES6ClassFn(value)) { return false; } var strClass = to_string.call(value); return strClass === fnClass || strClass === genClass; };\n\n    var isRegex; /* inlined from https://npmjs.com/is-regex */ var regexExec = RegExp.prototype.exec, tryRegexExec = function tryRegexExec(value) { try { regexExec.call(value); return true; } catch (e) { return false; } }, regexClass = '[object RegExp]'; isRegex = function isRegex(value) { if (typeof value !== 'object') { return false; } return hasToStringTag ? tryRegexExec(value) : to_string.call(value) === regexClass; };\n    var isString; /* inlined from https://npmjs.com/is-string */ var strValue = String.prototype.valueOf, tryStringObject = function tryStringObject(value) { try { strValue.call(value); return true; } catch (e) { return false; } }, stringClass = '[object String]'; isString = function isString(value) { if (typeof value === 'string') { return true; } if (typeof value !== 'object') { return false; } return hasToStringTag ? tryStringObject(value) : to_string.call(value) === stringClass; };\n    /* eslint-enable one-var-declaration-per-line, no-redeclare, max-statements-per-line */\n\n    /* inlined from http://npmjs.com/define-properties */\n    var supportsDescriptors = $Object.defineProperty && (function () {\n        try {\n            var obj = {};\n            $Object.defineProperty(obj, 'x', { enumerable: false, value: obj });\n            for (var _ in obj) { // jscs:ignore disallowUnusedVariables\n                return false;\n            }\n            return obj.x === obj;\n        } catch (e) { /* this is ES3 */\n            return false;\n        }\n    }());\n    var defineProperties = (function (has) {\n        // Define configurable, writable, and non-enumerable props\n        // if they don't exist.\n        var defineProperty;\n        if (supportsDescriptors) {\n            defineProperty = function (object, name, method, forceAssign) {\n                if (!forceAssign && (name in object)) {\n                    return;\n                }\n                $Object.defineProperty(object, name, {\n                    configurable: true,\n                    enumerable: false,\n                    writable: true,\n                    value: method\n                });\n            };\n        } else {\n            defineProperty = function (object, name, method, forceAssign) {\n                if (!forceAssign && (name in object)) {\n                    return;\n                }\n                object[name] = method;\n            };\n        }\n        return function defineProperties(object, map, forceAssign) {\n            for (var name in map) {\n                if (has.call(map, name)) {\n                    defineProperty(object, name, map[name], forceAssign);\n                }\n            }\n        };\n    }(ObjectPrototype.hasOwnProperty));\n\n    //\n    // Util\n    // ======\n    //\n\n    /* replaceable with https://npmjs.com/package/es-abstract /helpers/isPrimitive */\n    var isPrimitive = function isPrimitive(input) {\n        var type = typeof input;\n        return input === null || (type !== 'object' && type !== 'function');\n    };\n\n    var isActualNaN = $Number.isNaN || function isActualNaN(x) {\n        return x !== x;\n    };\n\n    var ES = {\n        // ES5 9.4\n        // http://es5.github.com/#x9.4\n        // http://jsperf.com/to-integer\n        /* replaceable with https://npmjs.com/package/es-abstract ES5.ToInteger */\n        ToInteger: function ToInteger(num) {\n            var n = +num;\n            if (isActualNaN(n)) {\n                n = 0;\n            } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {\n                n = (n > 0 || -1) * Math.floor(Math.abs(n));\n            }\n            return n;\n        },\n\n        /* replaceable with https://npmjs.com/package/es-abstract ES5.ToPrimitive */\n        ToPrimitive: function ToPrimitive(input) {\n            var val, valueOf, toStr;\n            if (isPrimitive(input)) {\n                return input;\n            }\n            valueOf = input.valueOf;\n            if (isCallable(valueOf)) {\n                val = valueOf.call(input);\n                if (isPrimitive(val)) {\n                    return val;\n                }\n            }\n            toStr = input.toString;\n            if (isCallable(toStr)) {\n                val = toStr.call(input);\n                if (isPrimitive(val)) {\n                    return val;\n                }\n            }\n            throw new TypeError();\n        },\n\n        // ES5 9.9\n        // http://es5.github.com/#x9.9\n        /* replaceable with https://npmjs.com/package/es-abstract ES5.ToObject */\n        ToObject: function (o) {\n            if (o == null) { // this matches both null and undefined\n                throw new TypeError(\"can't convert \" + o + ' to object');\n            }\n            return $Object(o);\n        },\n\n        /* replaceable with https://npmjs.com/package/es-abstract ES5.ToUint32 */\n        ToUint32: function ToUint32(x) {\n            return x >>> 0;\n        }\n    };\n\n    //\n    // Function\n    // ========\n    //\n\n    // ES-5 15.3.4.5\n    // http://es5.github.com/#x15.3.4.5\n\n    var Empty = function Empty() {};\n\n    defineProperties(FunctionPrototype, {\n        bind: function bind(that) { // .length is 1\n            // 1. Let Target be the this value.\n            var target = this;\n            // 2. If IsCallable(Target) is false, throw a TypeError exception.\n            if (!isCallable(target)) {\n                throw new TypeError('Function.prototype.bind called on incompatible ' + target);\n            }\n            // 3. Let A be a new (possibly empty) internal list of all of the\n            //   argument values provided after thisArg (arg1, arg2 etc), in order.\n            // XXX slicedArgs will stand in for \"A\" if used\n            var args = array_slice.call(arguments, 1); // for normal call\n            // 4. Let F be a new native ECMAScript object.\n            // 11. Set the [[Prototype]] internal property of F to the standard\n            //   built-in Function prototype object as specified in 15.3.3.1.\n            // 12. Set the [[Call]] internal property of F as described in\n            //   15.3.4.5.1.\n            // 13. Set the [[Construct]] internal property of F as described in\n            //   15.3.4.5.2.\n            // 14. Set the [[HasInstance]] internal property of F as described in\n            //   15.3.4.5.3.\n            var bound;\n            var binder = function () {\n\n                if (this instanceof bound) {\n                    // 15.3.4.5.2 [[Construct]]\n                    // When the [[Construct]] internal method of a function object,\n                    // F that was created using the bind function is called with a\n                    // list of arguments ExtraArgs, the following steps are taken:\n                    // 1. Let target be the value of F's [[TargetFunction]]\n                    //   internal property.\n                    // 2. If target has no [[Construct]] internal method, a\n                    //   TypeError exception is thrown.\n                    // 3. Let boundArgs be the value of F's [[BoundArgs]] internal\n                    //   property.\n                    // 4. Let args be a new list containing the same values as the\n                    //   list boundArgs in the same order followed by the same\n                    //   values as the list ExtraArgs in the same order.\n                    // 5. Return the result of calling the [[Construct]] internal\n                    //   method of target providing args as the arguments.\n\n                    var result = apply.call(\n                        target,\n                        this,\n                        array_concat.call(args, array_slice.call(arguments))\n                    );\n                    if ($Object(result) === result) {\n                        return result;\n                    }\n                    return this;\n\n                } else {\n                    // 15.3.4.5.1 [[Call]]\n                    // When the [[Call]] internal method of a function object, F,\n                    // which was created using the bind function is called with a\n                    // this value and a list of arguments ExtraArgs, the following\n                    // steps are taken:\n                    // 1. Let boundArgs be the value of F's [[BoundArgs]] internal\n                    //   property.\n                    // 2. Let boundThis be the value of F's [[BoundThis]] internal\n                    //   property.\n                    // 3. Let target be the value of F's [[TargetFunction]] internal\n                    //   property.\n                    // 4. Let args be a new list containing the same values as the\n                    //   list boundArgs in the same order followed by the same\n                    //   values as the list ExtraArgs in the same order.\n                    // 5. Return the result of calling the [[Call]] internal method\n                    //   of target providing boundThis as the this value and\n                    //   providing args as the arguments.\n\n                    // equiv: target.call(this, ...boundArgs, ...args)\n                    return apply.call(\n                        target,\n                        that,\n                        array_concat.call(args, array_slice.call(arguments))\n                    );\n\n                }\n\n            };\n\n            // 15. If the [[Class]] internal property of Target is \"Function\", then\n            //     a. Let L be the length property of Target minus the length of A.\n            //     b. Set the length own property of F to either 0 or L, whichever is\n            //       larger.\n            // 16. Else set the length own property of F to 0.\n\n            var boundLength = max(0, target.length - args.length);\n\n            // 17. Set the attributes of the length own property of F to the values\n            //   specified in 15.3.5.1.\n            var boundArgs = [];\n            for (var i = 0; i < boundLength; i++) {\n                array_push.call(boundArgs, '$' + i);\n            }\n\n            // XXX Build a dynamic function with desired amount of arguments is the only\n            // way to set the length property of a function.\n            // In environments where Content Security Policies enabled (Chrome extensions,\n            // for ex.) all use of eval or Function costructor throws an exception.\n            // However in all of these environments Function.prototype.bind exists\n            // and so this code will never be executed.\n            bound = $Function('binder', 'return function (' + array_join.call(boundArgs, ',') + '){ return binder.apply(this, arguments); }')(binder);\n\n            if (target.prototype) {\n                Empty.prototype = target.prototype;\n                bound.prototype = new Empty();\n                // Clean up dangling references.\n                Empty.prototype = null;\n            }\n\n            // TODO\n            // 18. Set the [[Extensible]] internal property of F to true.\n\n            // TODO\n            // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).\n            // 20. Call the [[DefineOwnProperty]] internal method of F with\n            //   arguments \"caller\", PropertyDescriptor {[[Get]]: thrower, [[Set]]:\n            //   thrower, [[Enumerable]]: false, [[Configurable]]: false}, and\n            //   false.\n            // 21. Call the [[DefineOwnProperty]] internal method of F with\n            //   arguments \"arguments\", PropertyDescriptor {[[Get]]: thrower,\n            //   [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},\n            //   and false.\n\n            // TODO\n            // NOTE Function objects created using Function.prototype.bind do not\n            // have a prototype property or the [[Code]], [[FormalParameters]], and\n            // [[Scope]] internal properties.\n            // XXX can't delete prototype in pure-js.\n\n            // 22. Return F.\n            return bound;\n        }\n    });\n\n    // _Please note: Shortcuts are defined after `Function.prototype.bind` as we\n    // use it in defining shortcuts.\n    var owns = call.bind(ObjectPrototype.hasOwnProperty);\n    var toStr = call.bind(ObjectPrototype.toString);\n    var arraySlice = call.bind(array_slice);\n    var arraySliceApply = apply.bind(array_slice);\n    var strSlice = call.bind(StringPrototype.slice);\n    var strSplit = call.bind(StringPrototype.split);\n    var strIndexOf = call.bind(StringPrototype.indexOf);\n    var pushCall = call.bind(array_push);\n    var isEnum = call.bind(ObjectPrototype.propertyIsEnumerable);\n    var arraySort = call.bind(ArrayPrototype.sort);\n\n    //\n    // Array\n    // =====\n    //\n\n    var isArray = $Array.isArray || function isArray(obj) {\n        return toStr(obj) === '[object Array]';\n    };\n\n    // ES5 15.4.4.12\n    // http://es5.github.com/#x15.4.4.13\n    // Return len+argCount.\n    // [bugfix, ielt8]\n    // IE < 8 bug: [].unshift(0) === undefined but should be \"1\"\n    var hasUnshiftReturnValueBug = [].unshift(0) !== 1;\n    defineProperties(ArrayPrototype, {\n        unshift: function () {\n            array_unshift.apply(this, arguments);\n            return this.length;\n        }\n    }, hasUnshiftReturnValueBug);\n\n    // ES5 15.4.3.2\n    // http://es5.github.com/#x15.4.3.2\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray\n    defineProperties($Array, { isArray: isArray });\n\n    // The IsCallable() check in the Array functions\n    // has been replaced with a strict check on the\n    // internal class of the object to trap cases where\n    // the provided function was actually a regular\n    // expression literal, which in V8 and\n    // JavaScriptCore is a typeof \"function\".  Only in\n    // V8 are regular expression literals permitted as\n    // reduce parameters, so it is desirable in the\n    // general case for the shim to match the more\n    // strict and common behavior of rejecting regular\n    // expressions.\n\n    // ES5 15.4.4.18\n    // http://es5.github.com/#x15.4.4.18\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach\n\n    // Check failure of by-index access of string characters (IE < 9)\n    // and failure of `0 in boxedString` (Rhino)\n    var boxedString = $Object('a');\n    var splitString = boxedString[0] !== 'a' || !(0 in boxedString);\n\n    var properlyBoxesContext = function properlyBoxed(method) {\n        // Check node 0.6.21 bug where third parameter is not boxed\n        var properlyBoxesNonStrict = true;\n        var properlyBoxesStrict = true;\n        var threwException = false;\n        if (method) {\n            try {\n                method.call('foo', function (_, __, context) {\n                    if (typeof context !== 'object') {\n                        properlyBoxesNonStrict = false;\n                    }\n                });\n\n                method.call([1], function () {\n                    'use strict';\n\n                    properlyBoxesStrict = typeof this === 'string';\n                }, 'x');\n            } catch (e) {\n                threwException = true;\n            }\n        }\n        return !!method && !threwException && properlyBoxesNonStrict && properlyBoxesStrict;\n    };\n\n    defineProperties(ArrayPrototype, {\n        forEach: function forEach(callbackfn/*, thisArg*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var i = -1;\n            var length = ES.ToUint32(self.length);\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.forEach callback must be a function');\n            }\n\n            while (++i < length) {\n                if (i in self) {\n                    // Invoke the callback function with call, passing arguments:\n                    // context, property value, property key, thisArg object\n                    if (typeof T === 'undefined') {\n                        callbackfn(self[i], i, object);\n                    } else {\n                        callbackfn.call(T, self[i], i, object);\n                    }\n                }\n            }\n        }\n    }, !properlyBoxesContext(ArrayPrototype.forEach));\n\n    // ES5 15.4.4.19\n    // http://es5.github.com/#x15.4.4.19\n    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map\n    defineProperties(ArrayPrototype, {\n        map: function map(callbackfn/*, thisArg*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n            var result = $Array(length);\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.map callback must be a function');\n            }\n\n            for (var i = 0; i < length; i++) {\n                if (i in self) {\n                    if (typeof T === 'undefined') {\n                        result[i] = callbackfn(self[i], i, object);\n                    } else {\n                        result[i] = callbackfn.call(T, self[i], i, object);\n                    }\n                }\n            }\n            return result;\n        }\n    }, !properlyBoxesContext(ArrayPrototype.map));\n\n    // ES5 15.4.4.20\n    // http://es5.github.com/#x15.4.4.20\n    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter\n    defineProperties(ArrayPrototype, {\n        filter: function filter(callbackfn/*, thisArg*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n            var result = [];\n            var value;\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.filter callback must be a function');\n            }\n\n            for (var i = 0; i < length; i++) {\n                if (i in self) {\n                    value = self[i];\n                    if (typeof T === 'undefined' ? callbackfn(value, i, object) : callbackfn.call(T, value, i, object)) {\n                        pushCall(result, value);\n                    }\n                }\n            }\n            return result;\n        }\n    }, !properlyBoxesContext(ArrayPrototype.filter));\n\n    // ES5 15.4.4.16\n    // http://es5.github.com/#x15.4.4.16\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every\n    defineProperties(ArrayPrototype, {\n        every: function every(callbackfn/*, thisArg*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.every callback must be a function');\n            }\n\n            for (var i = 0; i < length; i++) {\n                if (i in self && !(typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) {\n                    return false;\n                }\n            }\n            return true;\n        }\n    }, !properlyBoxesContext(ArrayPrototype.every));\n\n    // ES5 15.4.4.17\n    // http://es5.github.com/#x15.4.4.17\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some\n    defineProperties(ArrayPrototype, {\n        some: function some(callbackfn/*, thisArg */) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n            var T;\n            if (arguments.length > 1) {\n                T = arguments[1];\n            }\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.some callback must be a function');\n            }\n\n            for (var i = 0; i < length; i++) {\n                if (i in self && (typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) {\n                    return true;\n                }\n            }\n            return false;\n        }\n    }, !properlyBoxesContext(ArrayPrototype.some));\n\n    // ES5 15.4.4.21\n    // http://es5.github.com/#x15.4.4.21\n    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce\n    var reduceCoercesToObject = false;\n    if (ArrayPrototype.reduce) {\n        reduceCoercesToObject = typeof ArrayPrototype.reduce.call('es5', function (_, __, ___, list) {\n            return list;\n        }) === 'object';\n    }\n    defineProperties(ArrayPrototype, {\n        reduce: function reduce(callbackfn/*, initialValue*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.reduce callback must be a function');\n            }\n\n            // no value to return if no initial value and an empty array\n            if (length === 0 && arguments.length === 1) {\n                throw new TypeError('reduce of empty array with no initial value');\n            }\n\n            var i = 0;\n            var result;\n            if (arguments.length >= 2) {\n                result = arguments[1];\n            } else {\n                do {\n                    if (i in self) {\n                        result = self[i++];\n                        break;\n                    }\n\n                    // if array contains no values, no initial value to return\n                    if (++i >= length) {\n                        throw new TypeError('reduce of empty array with no initial value');\n                    }\n                } while (true);\n            }\n\n            for (; i < length; i++) {\n                if (i in self) {\n                    result = callbackfn(result, self[i], i, object);\n                }\n            }\n\n            return result;\n        }\n    }, !reduceCoercesToObject);\n\n    // ES5 15.4.4.22\n    // http://es5.github.com/#x15.4.4.22\n    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight\n    var reduceRightCoercesToObject = false;\n    if (ArrayPrototype.reduceRight) {\n        reduceRightCoercesToObject = typeof ArrayPrototype.reduceRight.call('es5', function (_, __, ___, list) {\n            return list;\n        }) === 'object';\n    }\n    defineProperties(ArrayPrototype, {\n        reduceRight: function reduceRight(callbackfn/*, initial*/) {\n            var object = ES.ToObject(this);\n            var self = splitString && isString(this) ? strSplit(this, '') : object;\n            var length = ES.ToUint32(self.length);\n\n            // If no callback function or if callback is not a callable function\n            if (!isCallable(callbackfn)) {\n                throw new TypeError('Array.prototype.reduceRight callback must be a function');\n            }\n\n            // no value to return if no initial value, empty array\n            if (length === 0 && arguments.length === 1) {\n                throw new TypeError('reduceRight of empty array with no initial value');\n            }\n\n            var result;\n            var i = length - 1;\n            if (arguments.length >= 2) {\n                result = arguments[1];\n            } else {\n                do {\n                    if (i in self) {\n                        result = self[i--];\n                        break;\n                    }\n\n                    // if array contains no values, no initial value to return\n                    if (--i < 0) {\n                        throw new TypeError('reduceRight of empty array with no initial value');\n                    }\n                } while (true);\n            }\n\n            if (i < 0) {\n                return result;\n            }\n\n            do {\n                if (i in self) {\n                    result = callbackfn(result, self[i], i, object);\n                }\n            } while (i--);\n\n            return result;\n        }\n    }, !reduceRightCoercesToObject);\n\n    // ES5 15.4.4.14\n    // http://es5.github.com/#x15.4.4.14\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf\n    var hasFirefox2IndexOfBug = ArrayPrototype.indexOf && [0, 1].indexOf(1, 2) !== -1;\n    defineProperties(ArrayPrototype, {\n        indexOf: function indexOf(searchElement/*, fromIndex */) {\n            var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this);\n            var length = ES.ToUint32(self.length);\n\n            if (length === 0) {\n                return -1;\n            }\n\n            var i = 0;\n            if (arguments.length > 1) {\n                i = ES.ToInteger(arguments[1]);\n            }\n\n            // handle negative indices\n            i = i >= 0 ? i : max(0, length + i);\n            for (; i < length; i++) {\n                if (i in self && self[i] === searchElement) {\n                    return i;\n                }\n            }\n            return -1;\n        }\n    }, hasFirefox2IndexOfBug);\n\n    // ES5 15.4.4.15\n    // http://es5.github.com/#x15.4.4.15\n    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf\n    var hasFirefox2LastIndexOfBug = ArrayPrototype.lastIndexOf && [0, 1].lastIndexOf(0, -3) !== -1;\n    defineProperties(ArrayPrototype, {\n        lastIndexOf: function lastIndexOf(searchElement/*, fromIndex */) {\n            var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this);\n            var length = ES.ToUint32(self.length);\n\n            if (length === 0) {\n                return -1;\n            }\n            var i = length - 1;\n            if (arguments.length > 1) {\n                i = min(i, ES.ToInteger(arguments[1]));\n            }\n            // handle negative indices\n            i = i >= 0 ? i : length - Math.abs(i);\n            for (; i >= 0; i--) {\n                if (i in self && searchElement === self[i]) {\n                    return i;\n                }\n            }\n            return -1;\n        }\n    }, hasFirefox2LastIndexOfBug);\n\n    // ES5 15.4.4.12\n    // http://es5.github.com/#x15.4.4.12\n    var spliceNoopReturnsEmptyArray = (function () {\n        var a = [1, 2];\n        var result = a.splice();\n        return a.length === 2 && isArray(result) && result.length === 0;\n    }());\n    defineProperties(ArrayPrototype, {\n        // Safari 5.0 bug where .splice() returns undefined\n        splice: function splice(start, deleteCount) {\n            if (arguments.length === 0) {\n                return [];\n            } else {\n                return array_splice.apply(this, arguments);\n            }\n        }\n    }, !spliceNoopReturnsEmptyArray);\n\n    var spliceWorksWithEmptyObject = (function () {\n        var obj = {};\n        ArrayPrototype.splice.call(obj, 0, 0, 1);\n        return obj.length === 1;\n    }());\n    defineProperties(ArrayPrototype, {\n        splice: function splice(start, deleteCount) {\n            if (arguments.length === 0) {\n                return [];\n            }\n            var args = arguments;\n            this.length = max(ES.ToInteger(this.length), 0);\n            if (arguments.length > 0 && typeof deleteCount !== 'number') {\n                args = arraySlice(arguments);\n                if (args.length < 2) {\n                    pushCall(args, this.length - start);\n                } else {\n                    args[1] = ES.ToInteger(deleteCount);\n                }\n            }\n            return array_splice.apply(this, args);\n        }\n    }, !spliceWorksWithEmptyObject);\n    var spliceWorksWithLargeSparseArrays = (function () {\n        // Per https://github.com/es-shims/es5-shim/issues/295\n        // Safari 7/8 breaks with sparse arrays of size 1e5 or greater\n        var arr = new $Array(1e5);\n        // note: the index MUST be 8 or larger or the test will false pass\n        arr[8] = 'x';\n        arr.splice(1, 1);\n        // note: this test must be defined *after* the indexOf shim\n        // per https://github.com/es-shims/es5-shim/issues/313\n        return arr.indexOf('x') === 7;\n    }());\n    var spliceWorksWithSmallSparseArrays = (function () {\n        // Per https://github.com/es-shims/es5-shim/issues/295\n        // Opera 12.15 breaks on this, no idea why.\n        var n = 256;\n        var arr = [];\n        arr[n] = 'a';\n        arr.splice(n + 1, 0, 'b');\n        return arr[n] === 'a';\n    }());\n    defineProperties(ArrayPrototype, {\n        splice: function splice(start, deleteCount) {\n            var O = ES.ToObject(this);\n            var A = [];\n            var len = ES.ToUint32(O.length);\n            var relativeStart = ES.ToInteger(start);\n            var actualStart = relativeStart < 0 ? max((len + relativeStart), 0) : min(relativeStart, len);\n            var actualDeleteCount = min(max(ES.ToInteger(deleteCount), 0), len - actualStart);\n\n            var k = 0;\n            var from;\n            while (k < actualDeleteCount) {\n                from = $String(actualStart + k);\n                if (owns(O, from)) {\n                    A[k] = O[from];\n                }\n                k += 1;\n            }\n\n            var items = arraySlice(arguments, 2);\n            var itemCount = items.length;\n            var to;\n            if (itemCount < actualDeleteCount) {\n                k = actualStart;\n                var maxK = len - actualDeleteCount;\n                while (k < maxK) {\n                    from = $String(k + actualDeleteCount);\n                    to = $String(k + itemCount);\n                    if (owns(O, from)) {\n                        O[to] = O[from];\n                    } else {\n                        delete O[to];\n                    }\n                    k += 1;\n                }\n                k = len;\n                var minK = len - actualDeleteCount + itemCount;\n                while (k > minK) {\n                    delete O[k - 1];\n                    k -= 1;\n                }\n            } else if (itemCount > actualDeleteCount) {\n                k = len - actualDeleteCount;\n                while (k > actualStart) {\n                    from = $String(k + actualDeleteCount - 1);\n                    to = $String(k + itemCount - 1);\n                    if (owns(O, from)) {\n                        O[to] = O[from];\n                    } else {\n                        delete O[to];\n                    }\n                    k -= 1;\n                }\n            }\n            k = actualStart;\n            for (var i = 0; i < items.length; ++i) {\n                O[k] = items[i];\n                k += 1;\n            }\n            O.length = len - actualDeleteCount + itemCount;\n\n            return A;\n        }\n    }, !spliceWorksWithLargeSparseArrays || !spliceWorksWithSmallSparseArrays);\n\n    var originalJoin = ArrayPrototype.join;\n    var hasStringJoinBug;\n    try {\n        hasStringJoinBug = Array.prototype.join.call('123', ',') !== '1,2,3';\n    } catch (e) {\n        hasStringJoinBug = true;\n    }\n    if (hasStringJoinBug) {\n        defineProperties(ArrayPrototype, {\n            join: function join(separator) {\n                var sep = typeof separator === 'undefined' ? ',' : separator;\n                return originalJoin.call(isString(this) ? strSplit(this, '') : this, sep);\n            }\n        }, hasStringJoinBug);\n    }\n\n    var hasJoinUndefinedBug = [1, 2].join(undefined) !== '1,2';\n    if (hasJoinUndefinedBug) {\n        defineProperties(ArrayPrototype, {\n            join: function join(separator) {\n                var sep = typeof separator === 'undefined' ? ',' : separator;\n                return originalJoin.call(this, sep);\n            }\n        }, hasJoinUndefinedBug);\n    }\n\n    var pushShim = function push(item) {\n        var O = ES.ToObject(this);\n        var n = ES.ToUint32(O.length);\n        var i = 0;\n        while (i < arguments.length) {\n            O[n + i] = arguments[i];\n            i += 1;\n        }\n        O.length = n + i;\n        return n + i;\n    };\n\n    var pushIsNotGeneric = (function () {\n        var obj = {};\n        var result = Array.prototype.push.call(obj, undefined);\n        return result !== 1 || obj.length !== 1 || typeof obj[0] !== 'undefined' || !owns(obj, 0);\n    }());\n    defineProperties(ArrayPrototype, {\n        push: function push(item) {\n            if (isArray(this)) {\n                return array_push.apply(this, arguments);\n            }\n            return pushShim.apply(this, arguments);\n        }\n    }, pushIsNotGeneric);\n\n    // This fixes a very weird bug in Opera 10.6 when pushing `undefined\n    var pushUndefinedIsWeird = (function () {\n        var arr = [];\n        var result = arr.push(undefined);\n        return result !== 1 || arr.length !== 1 || typeof arr[0] !== 'undefined' || !owns(arr, 0);\n    }());\n    defineProperties(ArrayPrototype, { push: pushShim }, pushUndefinedIsWeird);\n\n    // ES5 15.2.3.14\n    // http://es5.github.io/#x15.4.4.10\n    // Fix boxed string bug\n    defineProperties(ArrayPrototype, {\n        slice: function (start, end) {\n            var arr = isString(this) ? strSplit(this, '') : this;\n            return arraySliceApply(arr, arguments);\n        }\n    }, splitString);\n\n    var sortIgnoresNonFunctions = (function () {\n        try {\n            [1, 2].sort(null);\n            [1, 2].sort({});\n            return true;\n        } catch (e) {}\n        return false;\n    }());\n    var sortThrowsOnRegex = (function () {\n        // this is a problem in Firefox 4, in which `typeof /a/ === 'function'`\n        try {\n            [1, 2].sort(/a/);\n            return false;\n        } catch (e) {}\n        return true;\n    }());\n    var sortIgnoresUndefined = (function () {\n        // applies in IE 8, for one.\n        try {\n            [1, 2].sort(undefined);\n            return true;\n        } catch (e) {}\n        return false;\n    }());\n    defineProperties(ArrayPrototype, {\n        sort: function sort(compareFn) {\n            if (typeof compareFn === 'undefined') {\n                return arraySort(this);\n            }\n            if (!isCallable(compareFn)) {\n                throw new TypeError('Array.prototype.sort callback must be a function');\n            }\n            return arraySort(this, compareFn);\n        }\n    }, sortIgnoresNonFunctions || !sortIgnoresUndefined || !sortThrowsOnRegex);\n\n    //\n    // Object\n    // ======\n    //\n\n    // ES5 15.2.3.14\n    // http://es5.github.com/#x15.2.3.14\n\n    // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation\n    var hasDontEnumBug = !isEnum({ 'toString': null }, 'toString');\n    var hasProtoEnumBug = isEnum(function () {}, 'prototype');\n    var hasStringEnumBug = !owns('x', '0');\n    var equalsConstructorPrototype = function (o) {\n        var ctor = o.constructor;\n        return ctor && ctor.prototype === o;\n    };\n    var blacklistedKeys = {\n        $window: true,\n        $console: true,\n        $parent: true,\n        $self: true,\n        $frame: true,\n        $frames: true,\n        $frameElement: true,\n        $webkitIndexedDB: true,\n        $webkitStorageInfo: true,\n        $external: true\n    };\n    var hasAutomationEqualityBug = (function () {\n        /* globals window */\n        if (typeof window === 'undefined') {\n            return false;\n        }\n        for (var k in window) {\n            try {\n                if (!blacklistedKeys['$' + k] && owns(window, k) && window[k] !== null && typeof window[k] === 'object') {\n                    equalsConstructorPrototype(window[k]);\n                }\n            } catch (e) {\n                return true;\n            }\n        }\n        return false;\n    }());\n    var equalsConstructorPrototypeIfNotBuggy = function (object) {\n        if (typeof window === 'undefined' || !hasAutomationEqualityBug) {\n            return equalsConstructorPrototype(object);\n        }\n        try {\n            return equalsConstructorPrototype(object);\n        } catch (e) {\n            return false;\n        }\n    };\n    var dontEnums = [\n        'toString',\n        'toLocaleString',\n        'valueOf',\n        'hasOwnProperty',\n        'isPrototypeOf',\n        'propertyIsEnumerable',\n        'constructor'\n    ];\n    var dontEnumsLength = dontEnums.length;\n\n    // taken directly from https://github.com/ljharb/is-arguments/blob/master/index.js\n    // can be replaced with require('is-arguments') if we ever use a build process instead\n    var isStandardArguments = function isArguments(value) {\n        return toStr(value) === '[object Arguments]';\n    };\n    var isLegacyArguments = function isArguments(value) {\n        return value !== null &&\n            typeof value === 'object' &&\n            typeof value.length === 'number' &&\n            value.length >= 0 &&\n            !isArray(value) &&\n            isCallable(value.callee);\n    };\n    var isArguments = isStandardArguments(arguments) ? isStandardArguments : isLegacyArguments;\n\n    defineProperties($Object, {\n        keys: function keys(object) {\n            var isFn = isCallable(object);\n            var isArgs = isArguments(object);\n            var isObject = object !== null && typeof object === 'object';\n            var isStr = isObject && isString(object);\n\n            if (!isObject && !isFn && !isArgs) {\n                throw new TypeError('Object.keys called on a non-object');\n            }\n\n            var theKeys = [];\n            var skipProto = hasProtoEnumBug && isFn;\n            if ((isStr && hasStringEnumBug) || isArgs) {\n                for (var i = 0; i < object.length; ++i) {\n                    pushCall(theKeys, $String(i));\n                }\n            }\n\n            if (!isArgs) {\n                for (var name in object) {\n                    if (!(skipProto && name === 'prototype') && owns(object, name)) {\n                        pushCall(theKeys, $String(name));\n                    }\n                }\n            }\n\n            if (hasDontEnumBug) {\n                var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object);\n                for (var j = 0; j < dontEnumsLength; j++) {\n                    var dontEnum = dontEnums[j];\n                    if (!(skipConstructor && dontEnum === 'constructor') && owns(object, dontEnum)) {\n                        pushCall(theKeys, dontEnum);\n                    }\n                }\n            }\n            return theKeys;\n        }\n    });\n\n    var keysWorksWithArguments = $Object.keys && (function () {\n        // Safari 5.0 bug\n        return $Object.keys(arguments).length === 2;\n    }(1, 2));\n    var keysHasArgumentsLengthBug = $Object.keys && (function () {\n        var argKeys = $Object.keys(arguments);\n        return arguments.length !== 1 || argKeys.length !== 1 || argKeys[0] !== 1;\n    }(1));\n    var originalKeys = $Object.keys;\n    defineProperties($Object, {\n        keys: function keys(object) {\n            if (isArguments(object)) {\n                return originalKeys(arraySlice(object));\n            } else {\n                return originalKeys(object);\n            }\n        }\n    }, !keysWorksWithArguments || keysHasArgumentsLengthBug);\n\n    //\n    // Date\n    // ====\n    //\n\n    var hasNegativeMonthYearBug = new Date(-3509827329600292).getUTCMonth() !== 0;\n    var aNegativeTestDate = new Date(-1509842289600292);\n    var aPositiveTestDate = new Date(1449662400000);\n    var hasToUTCStringFormatBug = aNegativeTestDate.toUTCString() !== 'Mon, 01 Jan -45875 11:59:59 GMT';\n    var hasToDateStringFormatBug;\n    var hasToStringFormatBug;\n    var timeZoneOffset = aNegativeTestDate.getTimezoneOffset();\n    if (timeZoneOffset < -720) {\n        hasToDateStringFormatBug = aNegativeTestDate.toDateString() !== 'Tue Jan 02 -45875';\n        hasToStringFormatBug = !(/^Thu Dec 10 2015 \\d\\d:\\d\\d:\\d\\d GMT[-\\+]\\d\\d\\d\\d(?: |$)/).test(aPositiveTestDate.toString());\n    } else {\n        hasToDateStringFormatBug = aNegativeTestDate.toDateString() !== 'Mon Jan 01 -45875';\n        hasToStringFormatBug = !(/^Wed Dec 09 2015 \\d\\d:\\d\\d:\\d\\d GMT[-\\+]\\d\\d\\d\\d(?: |$)/).test(aPositiveTestDate.toString());\n    }\n\n    var originalGetFullYear = call.bind(Date.prototype.getFullYear);\n    var originalGetMonth = call.bind(Date.prototype.getMonth);\n    var originalGetDate = call.bind(Date.prototype.getDate);\n    var originalGetUTCFullYear = call.bind(Date.prototype.getUTCFullYear);\n    var originalGetUTCMonth = call.bind(Date.prototype.getUTCMonth);\n    var originalGetUTCDate = call.bind(Date.prototype.getUTCDate);\n    var originalGetUTCDay = call.bind(Date.prototype.getUTCDay);\n    var originalGetUTCHours = call.bind(Date.prototype.getUTCHours);\n    var originalGetUTCMinutes = call.bind(Date.prototype.getUTCMinutes);\n    var originalGetUTCSeconds = call.bind(Date.prototype.getUTCSeconds);\n    var originalGetUTCMilliseconds = call.bind(Date.prototype.getUTCMilliseconds);\n    var dayName = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n    var monthName = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n    var daysInMonth = function daysInMonth(month, year) {\n        return originalGetDate(new Date(year, month, 0));\n    };\n\n    defineProperties(Date.prototype, {\n        getFullYear: function getFullYear() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetFullYear(this);\n            if (year < 0 && originalGetMonth(this) > 11) {\n                return year + 1;\n            }\n            return year;\n        },\n        getMonth: function getMonth() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetFullYear(this);\n            var month = originalGetMonth(this);\n            if (year < 0 && month > 11) {\n                return 0;\n            }\n            return month;\n        },\n        getDate: function getDate() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetFullYear(this);\n            var month = originalGetMonth(this);\n            var date = originalGetDate(this);\n            if (year < 0 && month > 11) {\n                if (month === 12) {\n                    return date;\n                }\n                var days = daysInMonth(0, year + 1);\n                return (days - date) + 1;\n            }\n            return date;\n        },\n        getUTCFullYear: function getUTCFullYear() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetUTCFullYear(this);\n            if (year < 0 && originalGetUTCMonth(this) > 11) {\n                return year + 1;\n            }\n            return year;\n        },\n        getUTCMonth: function getUTCMonth() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetUTCFullYear(this);\n            var month = originalGetUTCMonth(this);\n            if (year < 0 && month > 11) {\n                return 0;\n            }\n            return month;\n        },\n        getUTCDate: function getUTCDate() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var year = originalGetUTCFullYear(this);\n            var month = originalGetUTCMonth(this);\n            var date = originalGetUTCDate(this);\n            if (year < 0 && month > 11) {\n                if (month === 12) {\n                    return date;\n                }\n                var days = daysInMonth(0, year + 1);\n                return (days - date) + 1;\n            }\n            return date;\n        }\n    }, hasNegativeMonthYearBug);\n\n    defineProperties(Date.prototype, {\n        toUTCString: function toUTCString() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var day = originalGetUTCDay(this);\n            var date = originalGetUTCDate(this);\n            var month = originalGetUTCMonth(this);\n            var year = originalGetUTCFullYear(this);\n            var hour = originalGetUTCHours(this);\n            var minute = originalGetUTCMinutes(this);\n            var second = originalGetUTCSeconds(this);\n            return dayName[day] + ', ' +\n                (date < 10 ? '0' + date : date) + ' ' +\n                monthName[month] + ' ' +\n                year + ' ' +\n                (hour < 10 ? '0' + hour : hour) + ':' +\n                (minute < 10 ? '0' + minute : minute) + ':' +\n                (second < 10 ? '0' + second : second) + ' GMT';\n        }\n    }, hasNegativeMonthYearBug || hasToUTCStringFormatBug);\n\n    // Opera 12 has `,`\n    defineProperties(Date.prototype, {\n        toDateString: function toDateString() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var day = this.getDay();\n            var date = this.getDate();\n            var month = this.getMonth();\n            var year = this.getFullYear();\n            return dayName[day] + ' ' +\n                monthName[month] + ' ' +\n                (date < 10 ? '0' + date : date) + ' ' +\n                year;\n        }\n    }, hasNegativeMonthYearBug || hasToDateStringFormatBug);\n\n    // can't use defineProperties here because of toString enumeration issue in IE <= 8\n    if (hasNegativeMonthYearBug || hasToStringFormatBug) {\n        Date.prototype.toString = function toString() {\n            if (!this || !(this instanceof Date)) {\n                throw new TypeError('this is not a Date object.');\n            }\n            var day = this.getDay();\n            var date = this.getDate();\n            var month = this.getMonth();\n            var year = this.getFullYear();\n            var hour = this.getHours();\n            var minute = this.getMinutes();\n            var second = this.getSeconds();\n            var timezoneOffset = this.getTimezoneOffset();\n            var hoursOffset = Math.floor(Math.abs(timezoneOffset) / 60);\n            var minutesOffset = Math.floor(Math.abs(timezoneOffset) % 60);\n            return dayName[day] + ' ' +\n                monthName[month] + ' ' +\n                (date < 10 ? '0' + date : date) + ' ' +\n                year + ' ' +\n                (hour < 10 ? '0' + hour : hour) + ':' +\n                (minute < 10 ? '0' + minute : minute) + ':' +\n                (second < 10 ? '0' + second : second) + ' GMT' +\n                (timezoneOffset > 0 ? '-' : '+') +\n                (hoursOffset < 10 ? '0' + hoursOffset : hoursOffset) +\n                (minutesOffset < 10 ? '0' + minutesOffset : minutesOffset);\n        };\n        if (supportsDescriptors) {\n            $Object.defineProperty(Date.prototype, 'toString', {\n                configurable: true,\n                enumerable: false,\n                writable: true\n            });\n        }\n    }\n\n    // ES5 15.9.5.43\n    // http://es5.github.com/#x15.9.5.43\n    // This function returns a String value represent the instance in time\n    // represented by this Date object. The format of the String is the Date Time\n    // string format defined in 15.9.1.15. All fields are present in the String.\n    // The time zone is always UTC, denoted by the suffix Z. If the time value of\n    // this object is not a finite Number a RangeError exception is thrown.\n    var negativeDate = -62198755200000;\n    var negativeYearString = '-000001';\n    var hasNegativeDateBug = Date.prototype.toISOString && new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1;\n    var hasSafari51DateBug = Date.prototype.toISOString && new Date(-1).toISOString() !== '1969-12-31T23:59:59.999Z';\n\n    var getTime = call.bind(Date.prototype.getTime);\n\n    defineProperties(Date.prototype, {\n        toISOString: function toISOString() {\n            if (!isFinite(this) || !isFinite(getTime(this))) {\n                // Adope Photoshop requires the second check.\n                throw new RangeError('Date.prototype.toISOString called on non-finite value.');\n            }\n\n            var year = originalGetUTCFullYear(this);\n\n            var month = originalGetUTCMonth(this);\n            // see https://github.com/es-shims/es5-shim/issues/111\n            year += Math.floor(month / 12);\n            month = (month % 12 + 12) % 12;\n\n            // the date time string format is specified in 15.9.1.15.\n            var result = [month + 1, originalGetUTCDate(this), originalGetUTCHours(this), originalGetUTCMinutes(this), originalGetUTCSeconds(this)];\n            year = (\n                (year < 0 ? '-' : (year > 9999 ? '+' : '')) +\n                strSlice('00000' + Math.abs(year), (0 <= year && year <= 9999) ? -4 : -6)\n            );\n\n            for (var i = 0; i < result.length; ++i) {\n                // pad months, days, hours, minutes, and seconds to have two digits.\n                result[i] = strSlice('00' + result[i], -2);\n            }\n            // pad milliseconds to have three digits.\n            return (\n                year + '-' + arraySlice(result, 0, 2).join('-') +\n                'T' + arraySlice(result, 2).join(':') + '.' +\n                strSlice('000' + originalGetUTCMilliseconds(this), -3) + 'Z'\n            );\n        }\n    }, hasNegativeDateBug || hasSafari51DateBug);\n\n    // ES5 15.9.5.44\n    // http://es5.github.com/#x15.9.5.44\n    // This function provides a String representation of a Date object for use by\n    // JSON.stringify (15.12.3).\n    var dateToJSONIsSupported = (function () {\n        try {\n            return Date.prototype.toJSON &&\n                new Date(NaN).toJSON() === null &&\n                new Date(negativeDate).toJSON().indexOf(negativeYearString) !== -1 &&\n                Date.prototype.toJSON.call({ // generic\n                    toISOString: function () { return true; }\n                });\n        } catch (e) {\n            return false;\n        }\n    }());\n    if (!dateToJSONIsSupported) {\n        Date.prototype.toJSON = function toJSON(key) {\n            // When the toJSON method is called with argument key, the following\n            // steps are taken:\n\n            // 1.  Let O be the result of calling ToObject, giving it the this\n            // value as its argument.\n            // 2. Let tv be ES.ToPrimitive(O, hint Number).\n            var O = $Object(this);\n            var tv = ES.ToPrimitive(O);\n            // 3. If tv is a Number and is not finite, return null.\n            if (typeof tv === 'number' && !isFinite(tv)) {\n                return null;\n            }\n            // 4. Let toISO be the result of calling the [[Get]] internal method of\n            // O with argument \"toISOString\".\n            var toISO = O.toISOString;\n            // 5. If IsCallable(toISO) is false, throw a TypeError exception.\n            if (!isCallable(toISO)) {\n                throw new TypeError('toISOString property is not callable');\n            }\n            // 6. Return the result of calling the [[Call]] internal method of\n            //  toISO with O as the this value and an empty argument list.\n            return toISO.call(O);\n\n            // NOTE 1 The argument is ignored.\n\n            // NOTE 2 The toJSON function is intentionally generic; it does not\n            // require that its this value be a Date object. Therefore, it can be\n            // transferred to other kinds of objects for use as a method. However,\n            // it does require that any such object have a toISOString method. An\n            // object is free to use the argument key to filter its\n            // stringification.\n        };\n    }\n\n    // ES5 15.9.4.2\n    // http://es5.github.com/#x15.9.4.2\n    // based on work shared by Daniel Friesen (dantman)\n    // http://gist.github.com/303249\n    var supportsExtendedYears = Date.parse('+033658-09-27T01:46:40.000Z') === 1e15;\n    var acceptsInvalidDates = !isNaN(Date.parse('2012-04-04T24:00:00.500Z')) || !isNaN(Date.parse('2012-11-31T23:59:59.000Z')) || !isNaN(Date.parse('2012-12-31T23:59:60.000Z'));\n    var doesNotParseY2KNewYear = isNaN(Date.parse('2000-01-01T00:00:00.000Z'));\n    if (doesNotParseY2KNewYear || acceptsInvalidDates || !supportsExtendedYears) {\n        // XXX global assignment won't work in embeddings that use\n        // an alternate object for the context.\n        /* global Date: true */\n        /* eslint-disable no-undef */\n        var maxSafeUnsigned32Bit = Math.pow(2, 31) - 1;\n        var hasSafariSignedIntBug = isActualNaN(new Date(1970, 0, 1, 0, 0, 0, maxSafeUnsigned32Bit + 1).getTime());\n        /* eslint-disable no-implicit-globals */\n        Date = (function (NativeDate) {\n        /* eslint-enable no-implicit-globals */\n        /* eslint-enable no-undef */\n            // Date.length === 7\n            var DateShim = function Date(Y, M, D, h, m, s, ms) {\n                var length = arguments.length;\n                var date;\n                if (this instanceof NativeDate) {\n                    var seconds = s;\n                    var millis = ms;\n                    if (hasSafariSignedIntBug && length >= 7 && ms > maxSafeUnsigned32Bit) {\n                        // work around a Safari 8/9 bug where it treats the seconds as signed\n                        var msToShift = Math.floor(ms / maxSafeUnsigned32Bit) * maxSafeUnsigned32Bit;\n                        var sToShift = Math.floor(msToShift / 1e3);\n                        seconds += sToShift;\n                        millis -= sToShift * 1e3;\n                    }\n                    date = length === 1 && $String(Y) === Y ? // isString(Y)\n                        // We explicitly pass it through parse:\n                        new NativeDate(DateShim.parse(Y)) :\n                        // We have to manually make calls depending on argument\n                        // length here\n                        length >= 7 ? new NativeDate(Y, M, D, h, m, seconds, millis) :\n                        length >= 6 ? new NativeDate(Y, M, D, h, m, seconds) :\n                        length >= 5 ? new NativeDate(Y, M, D, h, m) :\n                        length >= 4 ? new NativeDate(Y, M, D, h) :\n                        length >= 3 ? new NativeDate(Y, M, D) :\n                        length >= 2 ? new NativeDate(Y, M) :\n                        length >= 1 ? new NativeDate(Y instanceof NativeDate ? +Y : Y) :\n                                      new NativeDate();\n                } else {\n                    date = NativeDate.apply(this, arguments);\n                }\n                if (!isPrimitive(date)) {\n                    // Prevent mixups with unfixed Date object\n                    defineProperties(date, { constructor: DateShim }, true);\n                }\n                return date;\n            };\n\n            // 15.9.1.15 Date Time String Format.\n            var isoDateExpression = new RegExp('^' +\n                '(\\\\d{4}|[+-]\\\\d{6})' + // four-digit year capture or sign +\n                                          // 6-digit extended year\n                '(?:-(\\\\d{2})' + // optional month capture\n                '(?:-(\\\\d{2})' + // optional day capture\n                '(?:' + // capture hours:minutes:seconds.milliseconds\n                    'T(\\\\d{2})' + // hours capture\n                    ':(\\\\d{2})' + // minutes capture\n                    '(?:' + // optional :seconds.milliseconds\n                        ':(\\\\d{2})' + // seconds capture\n                        '(?:(\\\\.\\\\d{1,}))?' + // milliseconds capture\n                    ')?' +\n                '(' + // capture UTC offset component\n                    'Z|' + // UTC capture\n                    '(?:' + // offset specifier +/-hours:minutes\n                        '([-+])' + // sign capture\n                        '(\\\\d{2})' + // hours offset capture\n                        ':(\\\\d{2})' + // minutes offset capture\n                    ')' +\n                ')?)?)?)?' +\n            '$');\n\n            var months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];\n\n            var dayFromMonth = function dayFromMonth(year, month) {\n                var t = month > 1 ? 1 : 0;\n                return (\n                    months[month] +\n                    Math.floor((year - 1969 + t) / 4) -\n                    Math.floor((year - 1901 + t) / 100) +\n                    Math.floor((year - 1601 + t) / 400) +\n                    365 * (year - 1970)\n                );\n            };\n\n            var toUTC = function toUTC(t) {\n                var s = 0;\n                var ms = t;\n                if (hasSafariSignedIntBug && ms > maxSafeUnsigned32Bit) {\n                    // work around a Safari 8/9 bug where it treats the seconds as signed\n                    var msToShift = Math.floor(ms / maxSafeUnsigned32Bit) * maxSafeUnsigned32Bit;\n                    var sToShift = Math.floor(msToShift / 1e3);\n                    s += sToShift;\n                    ms -= sToShift * 1e3;\n                }\n                return $Number(new NativeDate(1970, 0, 1, 0, 0, s, ms));\n            };\n\n            // Copy any custom methods a 3rd party library may have added\n            for (var key in NativeDate) {\n                if (owns(NativeDate, key)) {\n                    DateShim[key] = NativeDate[key];\n                }\n            }\n\n            // Copy \"native\" methods explicitly; they may be non-enumerable\n            defineProperties(DateShim, {\n                now: NativeDate.now,\n                UTC: NativeDate.UTC\n            }, true);\n            DateShim.prototype = NativeDate.prototype;\n            defineProperties(DateShim.prototype, {\n                constructor: DateShim\n            }, true);\n\n            // Upgrade Date.parse to handle simplified ISO 8601 strings\n            var parseShim = function parse(string) {\n                var match = isoDateExpression.exec(string);\n                if (match) {\n                    // parse months, days, hours, minutes, seconds, and milliseconds\n                    // provide default values if necessary\n                    // parse the UTC offset component\n                    var year = $Number(match[1]),\n                        month = $Number(match[2] || 1) - 1,\n                        day = $Number(match[3] || 1) - 1,\n                        hour = $Number(match[4] || 0),\n                        minute = $Number(match[5] || 0),\n                        second = $Number(match[6] || 0),\n                        millisecond = Math.floor($Number(match[7] || 0) * 1000),\n                        // When time zone is missed, local offset should be used\n                        // (ES 5.1 bug)\n                        // see https://bugs.ecmascript.org/show_bug.cgi?id=112\n                        isLocalTime = Boolean(match[4] && !match[8]),\n                        signOffset = match[9] === '-' ? 1 : -1,\n                        hourOffset = $Number(match[10] || 0),\n                        minuteOffset = $Number(match[11] || 0),\n                        result;\n                    var hasMinutesOrSecondsOrMilliseconds = minute > 0 || second > 0 || millisecond > 0;\n                    if (\n                        hour < (hasMinutesOrSecondsOrMilliseconds ? 24 : 25) &&\n                        minute < 60 && second < 60 && millisecond < 1000 &&\n                        month > -1 && month < 12 && hourOffset < 24 &&\n                        minuteOffset < 60 && // detect invalid offsets\n                        day > -1 &&\n                        day < (dayFromMonth(year, month + 1) - dayFromMonth(year, month))\n                    ) {\n                        result = (\n                            (dayFromMonth(year, month) + day) * 24 +\n                            hour +\n                            hourOffset * signOffset\n                        ) * 60;\n                        result = (\n                            (result + minute + minuteOffset * signOffset) * 60 +\n                            second\n                        ) * 1000 + millisecond;\n                        if (isLocalTime) {\n                            result = toUTC(result);\n                        }\n                        if (-8.64e15 <= result && result <= 8.64e15) {\n                            return result;\n                        }\n                    }\n                    return NaN;\n                }\n                return NativeDate.parse.apply(this, arguments);\n            };\n            defineProperties(DateShim, { parse: parseShim });\n\n            return DateShim;\n        }(Date));\n        /* global Date: false */\n    }\n\n    // ES5 15.9.4.4\n    // http://es5.github.com/#x15.9.4.4\n    if (!Date.now) {\n        Date.now = function now() {\n            return new Date().getTime();\n        };\n    }\n\n    //\n    // Number\n    // ======\n    //\n\n    // ES5.1 15.7.4.5\n    // http://es5.github.com/#x15.7.4.5\n    var hasToFixedBugs = NumberPrototype.toFixed && (\n      (0.00008).toFixed(3) !== '0.000' ||\n      (0.9).toFixed(0) !== '1' ||\n      (1.255).toFixed(2) !== '1.25' ||\n      (1000000000000000128).toFixed(0) !== '1000000000000000128'\n    );\n\n    var toFixedHelpers = {\n        base: 1e7,\n        size: 6,\n        data: [0, 0, 0, 0, 0, 0],\n        multiply: function multiply(n, c) {\n            var i = -1;\n            var c2 = c;\n            while (++i < toFixedHelpers.size) {\n                c2 += n * toFixedHelpers.data[i];\n                toFixedHelpers.data[i] = c2 % toFixedHelpers.base;\n                c2 = Math.floor(c2 / toFixedHelpers.base);\n            }\n        },\n        divide: function divide(n) {\n            var i = toFixedHelpers.size;\n            var c = 0;\n            while (--i >= 0) {\n                c += toFixedHelpers.data[i];\n                toFixedHelpers.data[i] = Math.floor(c / n);\n                c = (c % n) * toFixedHelpers.base;\n            }\n        },\n        numToString: function numToString() {\n            var i = toFixedHelpers.size;\n            var s = '';\n            while (--i >= 0) {\n                if (s !== '' || i === 0 || toFixedHelpers.data[i] !== 0) {\n                    var t = $String(toFixedHelpers.data[i]);\n                    if (s === '') {\n                        s = t;\n                    } else {\n                        s += strSlice('0000000', 0, 7 - t.length) + t;\n                    }\n                }\n            }\n            return s;\n        },\n        pow: function pow(x, n, acc) {\n            return (n === 0 ? acc : (n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc)));\n        },\n        log: function log(x) {\n            var n = 0;\n            var x2 = x;\n            while (x2 >= 4096) {\n                n += 12;\n                x2 /= 4096;\n            }\n            while (x2 >= 2) {\n                n += 1;\n                x2 /= 2;\n            }\n            return n;\n        }\n    };\n\n    var toFixedShim = function toFixed(fractionDigits) {\n        var f, x, s, m, e, z, j, k;\n\n        // Test for NaN and round fractionDigits down\n        f = $Number(fractionDigits);\n        f = isActualNaN(f) ? 0 : Math.floor(f);\n\n        if (f < 0 || f > 20) {\n            throw new RangeError('Number.toFixed called with invalid number of decimals');\n        }\n\n        x = $Number(this);\n\n        if (isActualNaN(x)) {\n            return 'NaN';\n        }\n\n        // If it is too big or small, return the string value of the number\n        if (x <= -1e21 || x >= 1e21) {\n            return $String(x);\n        }\n\n        s = '';\n\n        if (x < 0) {\n            s = '-';\n            x = -x;\n        }\n\n        m = '0';\n\n        if (x > 1e-21) {\n            // 1e-21 < x < 1e21\n            // -70 < log2(x) < 70\n            e = toFixedHelpers.log(x * toFixedHelpers.pow(2, 69, 1)) - 69;\n            z = (e < 0 ? x * toFixedHelpers.pow(2, -e, 1) : x / toFixedHelpers.pow(2, e, 1));\n            z *= 0x10000000000000; // Math.pow(2, 52);\n            e = 52 - e;\n\n            // -18 < e < 122\n            // x = z / 2 ^ e\n            if (e > 0) {\n                toFixedHelpers.multiply(0, z);\n                j = f;\n\n                while (j >= 7) {\n                    toFixedHelpers.multiply(1e7, 0);\n                    j -= 7;\n                }\n\n                toFixedHelpers.multiply(toFixedHelpers.pow(10, j, 1), 0);\n                j = e - 1;\n\n                while (j >= 23) {\n                    toFixedHelpers.divide(1 << 23);\n                    j -= 23;\n                }\n\n                toFixedHelpers.divide(1 << j);\n                toFixedHelpers.multiply(1, 1);\n                toFixedHelpers.divide(2);\n                m = toFixedHelpers.numToString();\n            } else {\n                toFixedHelpers.multiply(0, z);\n                toFixedHelpers.multiply(1 << (-e), 0);\n                m = toFixedHelpers.numToString() + strSlice('0.00000000000000000000', 2, 2 + f);\n            }\n        }\n\n        if (f > 0) {\n            k = m.length;\n\n            if (k <= f) {\n                m = s + strSlice('0.0000000000000000000', 0, f - k + 2) + m;\n            } else {\n                m = s + strSlice(m, 0, k - f) + '.' + strSlice(m, k - f);\n            }\n        } else {\n            m = s + m;\n        }\n\n        return m;\n    };\n    defineProperties(NumberPrototype, { toFixed: toFixedShim }, hasToFixedBugs);\n\n    var hasToPrecisionUndefinedBug = (function () {\n        try {\n            return 1.0.toPrecision(undefined) === '1';\n        } catch (e) {\n            return true;\n        }\n    }());\n    var originalToPrecision = NumberPrototype.toPrecision;\n    defineProperties(NumberPrototype, {\n        toPrecision: function toPrecision(precision) {\n            return typeof precision === 'undefined' ? originalToPrecision.call(this) : originalToPrecision.call(this, precision);\n        }\n    }, hasToPrecisionUndefinedBug);\n\n    //\n    // String\n    // ======\n    //\n\n    // ES5 15.5.4.14\n    // http://es5.github.com/#x15.5.4.14\n\n    // [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers]\n    // Many browsers do not split properly with regular expressions or they\n    // do not perform the split correctly under obscure conditions.\n    // See http://blog.stevenlevithan.com/archives/cross-browser-split\n    // I've tested in many browsers and this seems to cover the deviant ones:\n    //    'ab'.split(/(?:ab)*/) should be [\"\", \"\"], not [\"\"]\n    //    '.'.split(/(.?)(.?)/) should be [\"\", \".\", \"\", \"\"], not [\"\", \"\"]\n    //    'tesst'.split(/(s)*/) should be [\"t\", undefined, \"e\", \"s\", \"t\"], not\n    //       [undefined, \"t\", undefined, \"e\", ...]\n    //    ''.split(/.?/) should be [], not [\"\"]\n    //    '.'.split(/()()/) should be [\".\"], not [\"\", \"\", \".\"]\n\n    if (\n        'ab'.split(/(?:ab)*/).length !== 2 ||\n        '.'.split(/(.?)(.?)/).length !== 4 ||\n        'tesst'.split(/(s)*/)[1] === 't' ||\n        'test'.split(/(?:)/, -1).length !== 4 ||\n        ''.split(/.?/).length ||\n        '.'.split(/()()/).length > 1\n    ) {\n        (function () {\n            var compliantExecNpcg = typeof (/()??/).exec('')[1] === 'undefined'; // NPCG: nonparticipating capturing group\n            var maxSafe32BitInt = Math.pow(2, 32) - 1;\n\n            StringPrototype.split = function (separator, limit) {\n                var string = String(this);\n                if (typeof separator === 'undefined' && limit === 0) {\n                    return [];\n                }\n\n                // If `separator` is not a regex, use native split\n                if (!isRegex(separator)) {\n                    return strSplit(this, separator, limit);\n                }\n\n                var output = [];\n                var flags = (separator.ignoreCase ? 'i' : '') +\n                            (separator.multiline ? 'm' : '') +\n                            (separator.unicode ? 'u' : '') + // in ES6\n                            (separator.sticky ? 'y' : ''), // Firefox 3+ and ES6\n                    lastLastIndex = 0,\n                    // Make `global` and avoid `lastIndex` issues by working with a copy\n                    separator2, match, lastIndex, lastLength;\n                var separatorCopy = new RegExp(separator.source, flags + 'g');\n                if (!compliantExecNpcg) {\n                    // Doesn't need flags gy, but they don't hurt\n                    separator2 = new RegExp('^' + separatorCopy.source + '$(?!\\\\s)', flags);\n                }\n                /* Values for `limit`, per the spec:\n                 * If undefined: 4294967295 // maxSafe32BitInt\n                 * If 0, Infinity, or NaN: 0\n                 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;\n                 * If negative number: 4294967296 - Math.floor(Math.abs(limit))\n                 * If other: Type-convert, then use the above rules\n                 */\n                var splitLimit = typeof limit === 'undefined' ? maxSafe32BitInt : ES.ToUint32(limit);\n                match = separatorCopy.exec(string);\n                while (match) {\n                    // `separatorCopy.lastIndex` is not reliable cross-browser\n                    lastIndex = match.index + match[0].length;\n                    if (lastIndex > lastLastIndex) {\n                        pushCall(output, strSlice(string, lastLastIndex, match.index));\n                        // Fix browsers whose `exec` methods don't consistently return `undefined` for\n                        // nonparticipating capturing groups\n                        if (!compliantExecNpcg && match.length > 1) {\n                            /* eslint-disable no-loop-func */\n                            match[0].replace(separator2, function () {\n                                for (var i = 1; i < arguments.length - 2; i++) {\n                                    if (typeof arguments[i] === 'undefined') {\n                                        match[i] = void 0;\n                                    }\n                                }\n                            });\n                            /* eslint-enable no-loop-func */\n                        }\n                        if (match.length > 1 && match.index < string.length) {\n                            array_push.apply(output, arraySlice(match, 1));\n                        }\n                        lastLength = match[0].length;\n                        lastLastIndex = lastIndex;\n                        if (output.length >= splitLimit) {\n                            break;\n                        }\n                    }\n                    if (separatorCopy.lastIndex === match.index) {\n                        separatorCopy.lastIndex++; // Avoid an infinite loop\n                    }\n                    match = separatorCopy.exec(string);\n                }\n                if (lastLastIndex === string.length) {\n                    if (lastLength || !separatorCopy.test('')) {\n                        pushCall(output, '');\n                    }\n                } else {\n                    pushCall(output, strSlice(string, lastLastIndex));\n                }\n                return output.length > splitLimit ? arraySlice(output, 0, splitLimit) : output;\n            };\n        }());\n\n    // [bugfix, chrome]\n    // If separator is undefined, then the result array contains just one String,\n    // which is the this value (converted to a String). If limit is not undefined,\n    // then the output array is truncated so that it contains no more than limit\n    // elements.\n    // \"0\".split(undefined, 0) -> []\n    } else if ('0'.split(void 0, 0).length) {\n        StringPrototype.split = function split(separator, limit) {\n            if (typeof separator === 'undefined' && limit === 0) {\n                return [];\n            }\n            return strSplit(this, separator, limit);\n        };\n    }\n\n    var str_replace = StringPrototype.replace;\n    var replaceReportsGroupsCorrectly = (function () {\n        var groups = [];\n        'x'.replace(/x(.)?/g, function (match, group) {\n            pushCall(groups, group);\n        });\n        return groups.length === 1 && typeof groups[0] === 'undefined';\n    }());\n\n    if (!replaceReportsGroupsCorrectly) {\n        StringPrototype.replace = function replace(searchValue, replaceValue) {\n            var isFn = isCallable(replaceValue);\n            var hasCapturingGroups = isRegex(searchValue) && (/\\)[*?]/).test(searchValue.source);\n            if (!isFn || !hasCapturingGroups) {\n                return str_replace.call(this, searchValue, replaceValue);\n            } else {\n                var wrappedReplaceValue = function (match) {\n                    var length = arguments.length;\n                    var originalLastIndex = searchValue.lastIndex;\n                    searchValue.lastIndex = 0;\n                    var args = searchValue.exec(match) || [];\n                    searchValue.lastIndex = originalLastIndex;\n                    pushCall(args, arguments[length - 2], arguments[length - 1]);\n                    return replaceValue.apply(this, args);\n                };\n                return str_replace.call(this, searchValue, wrappedReplaceValue);\n            }\n        };\n    }\n\n    // ECMA-262, 3rd B.2.3\n    // Not an ECMAScript standard, although ECMAScript 3rd Edition has a\n    // non-normative section suggesting uniform semantics and it should be\n    // normalized across all browsers\n    // [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE\n    var string_substr = StringPrototype.substr;\n    var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b';\n    defineProperties(StringPrototype, {\n        substr: function substr(start, length) {\n            var normalizedStart = start;\n            if (start < 0) {\n                normalizedStart = max(this.length + start, 0);\n            }\n            return string_substr.call(this, normalizedStart, length);\n        }\n    }, hasNegativeSubstrBug);\n\n    // ES5 15.5.4.20\n    // whitespace from: http://es5.github.io/#x15.5.4.20\n    var ws = '\\x09\\x0A\\x0B\\x0C\\x0D\\x20\\xA0\\u1680\\u180E\\u2000\\u2001\\u2002\\u2003' +\n        '\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000\\u2028' +\n        '\\u2029\\uFEFF';\n    var zeroWidth = '\\u200b';\n    var wsRegexChars = '[' + ws + ']';\n    var trimBeginRegexp = new RegExp('^' + wsRegexChars + wsRegexChars + '*');\n    var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + '*$');\n    var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim());\n    defineProperties(StringPrototype, {\n        // http://blog.stevenlevithan.com/archives/faster-trim-javascript\n        // http://perfectionkills.com/whitespace-deviations/\n        trim: function trim() {\n            if (typeof this === 'undefined' || this === null) {\n                throw new TypeError(\"can't convert \" + this + ' to object');\n            }\n            return $String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, '');\n        }\n    }, hasTrimWhitespaceBug);\n    var trim = call.bind(String.prototype.trim);\n\n    var hasLastIndexBug = StringPrototype.lastIndexOf && 'abcあい'.lastIndexOf('あい', 2) !== -1;\n    defineProperties(StringPrototype, {\n        lastIndexOf: function lastIndexOf(searchString) {\n            if (typeof this === 'undefined' || this === null) {\n                throw new TypeError(\"can't convert \" + this + ' to object');\n            }\n            var S = $String(this);\n            var searchStr = $String(searchString);\n            var numPos = arguments.length > 1 ? $Number(arguments[1]) : NaN;\n            var pos = isActualNaN(numPos) ? Infinity : ES.ToInteger(numPos);\n            var start = min(max(pos, 0), S.length);\n            var searchLen = searchStr.length;\n            var k = start + searchLen;\n            while (k > 0) {\n                k = max(0, k - searchLen);\n                var index = strIndexOf(strSlice(S, k, start + searchLen), searchStr);\n                if (index !== -1) {\n                    return k + index;\n                }\n            }\n            return -1;\n        }\n    }, hasLastIndexBug);\n\n    var originalLastIndexOf = StringPrototype.lastIndexOf;\n    defineProperties(StringPrototype, {\n        lastIndexOf: function lastIndexOf(searchString) {\n            return originalLastIndexOf.apply(this, arguments);\n        }\n    }, StringPrototype.lastIndexOf.length !== 1);\n\n    // ES-5 15.1.2.2\n    /* eslint-disable radix */\n    if (parseInt(ws + '08') !== 8 || parseInt(ws + '0x16') !== 22) {\n    /* eslint-enable radix */\n        /* global parseInt: true */\n        parseInt = (function (origParseInt) {\n            var hexRegex = /^[\\-+]?0[xX]/;\n            return function parseInt(str, radix) {\n                var string = trim(String(str));\n                var defaultedRadix = $Number(radix) || (hexRegex.test(string) ? 16 : 10);\n                return origParseInt(string, defaultedRadix);\n            };\n        }(parseInt));\n    }\n\n    // https://es5.github.io/#x15.1.2.3\n    if (1 / parseFloat('-0') !== -Infinity) {\n        /* global parseFloat: true */\n        parseFloat = (function (origParseFloat) {\n            return function parseFloat(string) {\n                var inputString = trim(String(string));\n                var result = origParseFloat(inputString);\n                return result === 0 && strSlice(inputString, 0, 1) === '-' ? -0 : result;\n            };\n        }(parseFloat));\n    }\n\n    if (String(new RangeError('test')) !== 'RangeError: test') {\n        var errorToStringShim = function toString() {\n            if (typeof this === 'undefined' || this === null) {\n                throw new TypeError(\"can't convert \" + this + ' to object');\n            }\n            var name = this.name;\n            if (typeof name === 'undefined') {\n                name = 'Error';\n            } else if (typeof name !== 'string') {\n                name = $String(name);\n            }\n            var msg = this.message;\n            if (typeof msg === 'undefined') {\n                msg = '';\n            } else if (typeof msg !== 'string') {\n                msg = $String(msg);\n            }\n            if (!name) {\n                return msg;\n            }\n            if (!msg) {\n                return name;\n            }\n            return name + ': ' + msg;\n        };\n        // can't use defineProperties here because of toString enumeration issue in IE <= 8\n        Error.prototype.toString = errorToStringShim;\n    }\n\n    if (supportsDescriptors) {\n        var ensureNonEnumerable = function (obj, prop) {\n            if (isEnum(obj, prop)) {\n                var desc = Object.getOwnPropertyDescriptor(obj, prop);\n                if (desc.configurable) {\n                    desc.enumerable = false;\n                    Object.defineProperty(obj, prop, desc);\n                }\n            }\n        };\n        ensureNonEnumerable(Error.prototype, 'message');\n        if (Error.prototype.message !== '') {\n            Error.prototype.message = '';\n        }\n        ensureNonEnumerable(Error.prototype, 'name');\n    }\n\n    if (String(/a/mig) !== '/a/gim') {\n        var regexToString = function toString() {\n            var str = '/' + this.source + '/';\n            if (this.global) {\n                str += 'g';\n            }\n            if (this.ignoreCase) {\n                str += 'i';\n            }\n            if (this.multiline) {\n                str += 'm';\n            }\n            return str;\n        };\n        // can't use defineProperties here because of toString enumeration issue in IE <= 8\n        RegExp.prototype.toString = regexToString;\n    }\n}));\n\n'use strict';\n/*jslint eqeq: true*/\n\nHandlebars.registerHelper('sanitize', function (text) {\n    var result;\n\n    if (text === undefined) { return ''; }\n\n    result = sanitizeHtml(text, {\n        allowedTags: [ 'div', 'span', 'b', 'i', 'em', 'strong', 'a' ],\n        allowedAttributes: {\n            'div': [ 'class' ],\n            'span': [ 'class' ],\n            'a': [ 'href' ]\n        }\n    });\n\n    return new Handlebars.SafeString(result);\n});\n\nHandlebars.registerHelper('renderTextParam', function(param) {\n    var result, type = 'text', idAtt = '';\n    var paramType = param.type || param.schema && param.schema.type || '';\n    var isArray = paramType.toLowerCase() === 'array' || param.allowMultiple;\n    var defaultValue = isArray && Array.isArray(param.default) ? param.default.join('\\n') : param.default;\n    var name = Handlebars.Utils.escapeExpression(param.name);\n    var valueId = Handlebars.Utils.escapeExpression(param.valueId);\n    paramType = Handlebars.Utils.escapeExpression(paramType);\n\n    var dataVendorExtensions = Object.keys(param).filter(function(property) {\n        // filter X-data- properties\n        return property.match(/^X-data-/i) !== null;\n    }).reduce(function(result, property) {\n        // remove X- from property name, so it results in html attributes like data-foo='bar'\n        return result += ' ' + property.substring(2, property.length) + '=\\'' + param[property] + '\\'';\n    }, '');\n\n    if(param.format && param.format === 'password') {\n        type = 'password';\n    }\n\n    if(valueId) {\n        idAtt = ' id=\\'' + valueId + '\\'';\n    }\n\n    if (defaultValue) {\n      defaultValue = sanitizeHtml(defaultValue);\n    } else {\n      defaultValue = '';\n    }\n\n    if(isArray) {\n        result = '<textarea class=\\'body-textarea' + (param.required ? ' required' : '') + '\\' name=\\'' + name + '\\'' + idAtt + dataVendorExtensions;\n        result += ' placeholder=\\'Provide multiple values in new lines' + (param.required ? ' (at least one required).' : '.') + '\\'>';\n        result += defaultValue + '</textarea>';\n    } else {\n        var parameterClass = 'parameter';\n        if(param.required) {\n          parameterClass += ' required';\n        }\n        result = '<input class=\\'' + parameterClass + '\\' minlength=\\'' + (param.required ? 1 : 0) + '\\'';\n        result += ' name=\\'' + name +'\\' placeholder=\\'' + (param.required ? '(required)' : '') + '\\'' + idAtt + dataVendorExtensions;\n        result += ' type=\\'' + type + '\\' value=\\'' + defaultValue + '\\'/>';\n    }\n    return new Handlebars.SafeString(result);\n});\n\nHandlebars.registerHelper('ifCond', function (v1, operator, v2, options) {\n\n    switch (operator) {\n        case '==':\n            return (v1 == v2) ? options.fn(this) : options.inverse(this);\n        case '===':\n            return (v1 === v2) ? options.fn(this) : options.inverse(this);\n        case '<':\n            return (v1 < v2) ? options.fn(this) : options.inverse(this);\n        case '<=':\n            return (v1 <= v2) ? options.fn(this) : options.inverse(this);\n        case '>':\n            return (v1 > v2) ? options.fn(this) : options.inverse(this);\n        case '>=':\n            return (v1 >= v2) ? options.fn(this) : options.inverse(this);\n        case '&&':\n            return (v1 && v2) ? options.fn(this) : options.inverse(this);\n        case '||':\n            return (v1 || v2) ? options.fn(this) : options.inverse(this);\n        default:\n            return options.inverse(this);\n    }\n});\n\nHandlebars.registerHelper('escape', function (value) {\n    var text = Handlebars.Utils.escapeExpression(value);\n\n    return new Handlebars.SafeString(text);\n});\n\n(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.sanitizeHtml=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){var htmlparser=require(\"htmlparser2\");var extend=require(\"xtend\");var quoteRegexp=require(\"regexp-quote\");function each(obj,cb){if(obj)Object.keys(obj).forEach(function(key){cb(obj[key],key)})}function has(obj,key){return{}.hasOwnProperty.call(obj,key)}module.exports=sanitizeHtml;function sanitizeHtml(html,options,_recursing){var result=\"\";function Frame(tag,attribs){var that=this;this.tag=tag;this.attribs=attribs||{};this.tagPosition=result.length;this.text=\"\";this.updateParentNodeText=function(){if(stack.length){var parentFrame=stack[stack.length-1];parentFrame.text+=that.text}}}if(!options){options=sanitizeHtml.defaults;options.parser=htmlParserDefaults}else{options=extend(sanitizeHtml.defaults,options);if(options.parser){options.parser=extend(htmlParserDefaults,options.parser)}else{options.parser=htmlParserDefaults}}var nonTextTagsArray=options.nonTextTags||[\"script\",\"style\",\"textarea\"];var allowedAttributesMap;var allowedAttributesGlobMap;if(options.allowedAttributes){allowedAttributesMap={};allowedAttributesGlobMap={};each(options.allowedAttributes,function(attributes,tag){allowedAttributesMap[tag]=[];var globRegex=[];attributes.forEach(function(name){if(name.indexOf(\"*\")>=0){globRegex.push(quoteRegexp(name).replace(/\\\\\\*/g,\".*\"))}else{allowedAttributesMap[tag].push(name)}});allowedAttributesGlobMap[tag]=new RegExp(\"^(\"+globRegex.join(\"|\")+\")$\")})}var allowedClassesMap={};each(options.allowedClasses,function(classes,tag){if(allowedAttributesMap){if(!has(allowedAttributesMap,tag)){allowedAttributesMap[tag]=[]}allowedAttributesMap[tag].push(\"class\")}allowedClassesMap[tag]=classes});var transformTagsMap={};var transformTagsAll;each(options.transformTags,function(transform,tag){var transFun;if(typeof transform===\"function\"){transFun=transform}else if(typeof transform===\"string\"){transFun=sanitizeHtml.simpleTransform(transform)}if(tag===\"*\"){transformTagsAll=transFun}else{transformTagsMap[tag]=transFun}});var depth=0;var stack=[];var skipMap={};var transformMap={};var skipText=false;var skipTextDepth=0;var parser=new htmlparser.Parser({onopentag:function(name,attribs){if(skipText){skipTextDepth++;return}var frame=new Frame(name,attribs);stack.push(frame);var skip=false;var hasText=frame.text?true:false;var transformedTag;if(has(transformTagsMap,name)){transformedTag=transformTagsMap[name](name,attribs);frame.attribs=attribs=transformedTag.attribs;if(transformedTag.text!==undefined){frame.innerText=transformedTag.text}if(name!==transformedTag.tagName){frame.name=name=transformedTag.tagName;transformMap[depth]=transformedTag.tagName}}if(transformTagsAll){transformedTag=transformTagsAll(name,attribs);frame.attribs=attribs=transformedTag.attribs;if(name!==transformedTag.tagName){frame.name=name=transformedTag.tagName;transformMap[depth]=transformedTag.tagName}}if(options.allowedTags&&options.allowedTags.indexOf(name)===-1){skip=true;if(nonTextTagsArray.indexOf(name)!==-1){skipText=true;skipTextDepth=1}skipMap[depth]=true}depth++;if(skip){return}result+=\"<\"+name;if(!allowedAttributesMap||has(allowedAttributesMap,name)||allowedAttributesMap[\"*\"]){each(attribs,function(value,a){if(!allowedAttributesMap||has(allowedAttributesMap,name)&&allowedAttributesMap[name].indexOf(a)!==-1||allowedAttributesMap[\"*\"]&&allowedAttributesMap[\"*\"].indexOf(a)!==-1||has(allowedAttributesGlobMap,name)&&allowedAttributesGlobMap[name].test(a)||allowedAttributesGlobMap[\"*\"]&&allowedAttributesGlobMap[\"*\"].test(a)){if(a===\"href\"||a===\"src\"){if(naughtyHref(name,value)){delete frame.attribs[a];return}}if(a===\"class\"){value=filterClasses(value,allowedClassesMap[name]);if(!value.length){delete frame.attribs[a];return}}result+=\" \"+a;if(value.length){result+='=\"'+escapeHtml(value)+'\"'}}else{delete frame.attribs[a]}})}if(options.selfClosing.indexOf(name)!==-1){result+=\" />\"}else{result+=\">\";if(frame.innerText&&!hasText&&!options.textFilter){result+=frame.innerText}}},ontext:function(text){if(skipText){return}var lastFrame=stack[stack.length-1];var tag;if(lastFrame){tag=lastFrame.tag;text=lastFrame.innerText!==undefined?lastFrame.innerText:text}if(tag===\"script\"||tag===\"style\"){result+=text}else{var escaped=escapeHtml(text);if(options.textFilter){result+=options.textFilter(escaped)}else{result+=escaped}}if(stack.length){var frame=stack[stack.length-1];frame.text+=text}},onclosetag:function(name){if(skipText){skipTextDepth--;if(!skipTextDepth){skipText=false}else{return}}var frame=stack.pop();if(!frame){return}skipText=false;depth--;if(skipMap[depth]){delete skipMap[depth];frame.updateParentNodeText();return}if(transformMap[depth]){name=transformMap[depth];delete transformMap[depth]}if(options.exclusiveFilter&&options.exclusiveFilter(frame)){result=result.substr(0,frame.tagPosition);return}frame.updateParentNodeText();if(options.selfClosing.indexOf(name)!==-1){return}result+=\"</\"+name+\">\"}},options.parser);parser.write(html);parser.end();return result;function escapeHtml(s){if(typeof s!==\"string\"){s=s+\"\"}return s.replace(/\\&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/\\>/g,\"&gt;\").replace(/\\\"/g,\"&quot;\")}function naughtyHref(name,href){href=href.replace(/[\\x00-\\x20]+/g,\"\");href=href.replace(/<\\!\\-\\-.*?\\-\\-\\>/g,\"\");var matches=href.match(/^([a-zA-Z]+)\\:/);if(!matches){return false}var scheme=matches[1].toLowerCase();if(has(options.allowedSchemesByTag,name)){return options.allowedSchemesByTag[name].indexOf(scheme)===-1}return!options.allowedSchemes||options.allowedSchemes.indexOf(scheme)===-1}function filterClasses(classes,allowed){if(!allowed){return classes}classes=classes.split(/\\s+/);return classes.filter(function(clss){return allowed.indexOf(clss)!==-1}).join(\" \")}}var htmlParserDefaults={decodeEntities:true};sanitizeHtml.defaults={allowedTags:[\"h3\",\"h4\",\"h5\",\"h6\",\"blockquote\",\"p\",\"a\",\"ul\",\"ol\",\"nl\",\"li\",\"b\",\"i\",\"strong\",\"em\",\"strike\",\"code\",\"hr\",\"br\",\"div\",\"table\",\"thead\",\"caption\",\"tbody\",\"tr\",\"th\",\"td\",\"pre\"],allowedAttributes:{a:[\"href\",\"name\",\"target\"],img:[\"src\"]},selfClosing:[\"img\",\"br\",\"hr\",\"area\",\"base\",\"basefont\",\"input\",\"link\",\"meta\"],allowedSchemes:[\"http\",\"https\",\"ftp\",\"mailto\"],allowedSchemesByTag:{}};sanitizeHtml.simpleTransform=function(newTagName,newAttribs,merge){merge=merge===undefined?true:merge;newAttribs=newAttribs||{};return function(tagName,attribs){var attrib;if(merge){for(attrib in newAttribs){attribs[attrib]=newAttribs[attrib]}}else{attribs=newAttribs}return{tagName:newTagName,attribs:attribs}}}},{htmlparser2:36,\"regexp-quote\":54,xtend:58}],2:[function(require,module,exports){\"use strict\";exports.toByteArray=toByteArray;exports.fromByteArray=fromByteArray;var lookup=[];var revLookup=[];var Arr=typeof Uint8Array!==\"undefined\"?Uint8Array:Array;function init(){var code=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";for(var i=0,len=code.length;i<len;++i){lookup[i]=code[i];revLookup[code.charCodeAt(i)]=i}revLookup[\"-\".charCodeAt(0)]=62;revLookup[\"_\".charCodeAt(0)]=63}init();function toByteArray(b64){var i,j,l,tmp,placeHolders,arr;var len=b64.length;if(len%4>0){throw new Error(\"Invalid string. Length must be a multiple of 4\")}placeHolders=b64[len-2]===\"=\"?2:b64[len-1]===\"=\"?1:0;arr=new Arr(len*3/4-placeHolders);l=placeHolders>0?len-4:len;var L=0;for(i=0,j=0;i<l;i+=4,j+=3){tmp=revLookup[b64.charCodeAt(i)]<<18|revLookup[b64.charCodeAt(i+1)]<<12|revLookup[b64.charCodeAt(i+2)]<<6|revLookup[b64.charCodeAt(i+3)];arr[L++]=tmp>>16&255;arr[L++]=tmp>>8&255;arr[L++]=tmp&255}if(placeHolders===2){tmp=revLookup[b64.charCodeAt(i)]<<2|revLookup[b64.charCodeAt(i+1)]>>4;arr[L++]=tmp&255}else if(placeHolders===1){tmp=revLookup[b64.charCodeAt(i)]<<10|revLookup[b64.charCodeAt(i+1)]<<4|revLookup[b64.charCodeAt(i+2)]>>2;arr[L++]=tmp>>8&255;arr[L++]=tmp&255}return arr}function tripletToBase64(num){return lookup[num>>18&63]+lookup[num>>12&63]+lookup[num>>6&63]+lookup[num&63]}function encodeChunk(uint8,start,end){var tmp;var output=[];for(var i=start;i<end;i+=3){tmp=(uint8[i]<<16)+(uint8[i+1]<<8)+uint8[i+2];output.push(tripletToBase64(tmp))}return output.join(\"\")}function fromByteArray(uint8){var tmp;var len=uint8.length;var extraBytes=len%3;var output=\"\";var parts=[];var maxChunkLength=16383;for(var i=0,len2=len-extraBytes;i<len2;i+=maxChunkLength){parts.push(encodeChunk(uint8,i,i+maxChunkLength>len2?len2:i+maxChunkLength))}if(extraBytes===1){tmp=uint8[len-1];output+=lookup[tmp>>2];output+=lookup[tmp<<4&63];output+=\"==\"}else if(extraBytes===2){tmp=(uint8[len-2]<<8)+uint8[len-1];output+=lookup[tmp>>10];output+=lookup[tmp>>4&63];output+=lookup[tmp<<2&63];output+=\"=\"}parts.push(output);return parts.join(\"\")}},{}],3:[function(require,module,exports){},{}],4:[function(require,module,exports){(function(global){\"use strict\";var buffer=require(\"buffer\");var Buffer=buffer.Buffer;var SlowBuffer=buffer.SlowBuffer;var MAX_LEN=buffer.kMaxLength||2147483647;exports.alloc=function alloc(size,fill,encoding){if(typeof Buffer.alloc===\"function\"){return Buffer.alloc(size,fill,encoding)}if(typeof encoding===\"number\"){throw new TypeError(\"encoding must not be number\")}if(typeof size!==\"number\"){throw new TypeError(\"size must be a number\")}if(size>MAX_LEN){throw new RangeError(\"size is too large\")}var enc=encoding;var _fill=fill;if(_fill===undefined){enc=undefined;_fill=0}var buf=new Buffer(size);if(typeof _fill===\"string\"){var fillBuf=new Buffer(_fill,enc);var flen=fillBuf.length;var i=-1;while(++i<size){buf[i]=fillBuf[i%flen]}}else{buf.fill(_fill)}return buf};exports.allocUnsafe=function allocUnsafe(size){if(typeof Buffer.allocUnsafe===\"function\"){return Buffer.allocUnsafe(size)}if(typeof size!==\"number\"){throw new TypeError(\"size must be a number\")}if(size>MAX_LEN){throw new RangeError(\"size is too large\")}return new Buffer(size)};exports.from=function from(value,encodingOrOffset,length){if(typeof Buffer.from===\"function\"&&(!global.Uint8Array||Uint8Array.from!==Buffer.from)){return Buffer.from(value,encodingOrOffset,length)}if(typeof value===\"number\"){throw new TypeError('\"value\" argument must not be a number')}if(typeof value===\"string\"){return new Buffer(value,encodingOrOffset)}if(typeof ArrayBuffer!==\"undefined\"&&value instanceof ArrayBuffer){var offset=encodingOrOffset;if(arguments.length===1){return new Buffer(value)}if(typeof offset===\"undefined\"){offset=0}var len=length;if(typeof len===\"undefined\"){len=value.byteLength-offset}if(offset>=value.byteLength){throw new RangeError(\"'offset' is out of bounds\")}if(len>value.byteLength-offset){throw new RangeError(\"'length' is out of bounds\")}return new Buffer(value.slice(offset,offset+len))}if(Buffer.isBuffer(value)){var out=new Buffer(value.length);value.copy(out,0,0,value.length);return out}if(value){if(Array.isArray(value)||typeof ArrayBuffer!==\"undefined\"&&value.buffer instanceof ArrayBuffer||\"length\"in value){return new Buffer(value)}if(value.type===\"Buffer\"&&Array.isArray(value.data)){return new Buffer(value.data)}}throw new TypeError(\"First argument must be a string, Buffer, \"+\"ArrayBuffer, Array, or array-like object.\")};exports.allocUnsafeSlow=function allocUnsafeSlow(size){if(typeof Buffer.allocUnsafeSlow===\"function\"){return Buffer.allocUnsafeSlow(size)}if(typeof size!==\"number\"){throw new TypeError(\"size must be a number\")}if(size>=MAX_LEN){throw new RangeError(\"size is too large\")}return new SlowBuffer(size)}}).call(this,typeof global!==\"undefined\"?global:typeof self!==\"undefined\"?self:typeof window!==\"undefined\"?window:{})},{buffer:5}],5:[function(require,module,exports){(function(global){\"use strict\";var base64=require(\"base64-js\");var ieee754=require(\"ieee754\");var isArray=require(\"isarray\");exports.Buffer=Buffer;exports.SlowBuffer=SlowBuffer;exports.INSPECT_MAX_BYTES=50;Buffer.TYPED_ARRAY_SUPPORT=global.TYPED_ARRAY_SUPPORT!==undefined?global.TYPED_ARRAY_SUPPORT:typedArraySupport();exports.kMaxLength=kMaxLength();function typedArraySupport(){try{var arr=new Uint8Array(1);arr.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42}};return arr.foo()===42&&typeof arr.subarray===\"function\"&&arr.subarray(1,1).byteLength===0}catch(e){return false}}function kMaxLength(){return Buffer.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function createBuffer(that,length){if(kMaxLength()<length){throw new RangeError(\"Invalid typed array length\")}if(Buffer.TYPED_ARRAY_SUPPORT){that=new Uint8Array(length);that.__proto__=Buffer.prototype}else{if(that===null){that=new Buffer(length)}that.length=length}return that}function Buffer(arg,encodingOrOffset,length){if(!Buffer.TYPED_ARRAY_SUPPORT&&!(this instanceof Buffer)){return new Buffer(arg,encodingOrOffset,length)}if(typeof arg===\"number\"){if(typeof encodingOrOffset===\"string\"){throw new Error(\"If encoding is specified then the first argument must be a string\")}return allocUnsafe(this,arg)}return from(this,arg,encodingOrOffset,length)}Buffer.poolSize=8192;Buffer._augment=function(arr){arr.__proto__=Buffer.prototype;return arr};function from(that,value,encodingOrOffset,length){if(typeof value===\"number\"){throw new TypeError('\"value\" argument must not be a number')}if(typeof ArrayBuffer!==\"undefined\"&&value instanceof ArrayBuffer){return fromArrayBuffer(that,value,encodingOrOffset,length)}if(typeof value===\"string\"){return fromString(that,value,encodingOrOffset)}return fromObject(that,value)}Buffer.from=function(value,encodingOrOffset,length){return from(null,value,encodingOrOffset,length)};if(Buffer.TYPED_ARRAY_SUPPORT){Buffer.prototype.__proto__=Uint8Array.prototype;Buffer.__proto__=Uint8Array;if(typeof Symbol!==\"undefined\"&&Symbol.species&&Buffer[Symbol.species]===Buffer){Object.defineProperty(Buffer,Symbol.species,{value:null,configurable:true})}}function assertSize(size){if(typeof size!==\"number\"){throw new TypeError('\"size\" argument must be a number')}else if(size<0){throw new RangeError('\"size\" argument must not be negative')}}function alloc(that,size,fill,encoding){assertSize(size);if(size<=0){return createBuffer(that,size)}if(fill!==undefined){return typeof encoding===\"string\"?createBuffer(that,size).fill(fill,encoding):createBuffer(that,size).fill(fill)}return createBuffer(that,size)}Buffer.alloc=function(size,fill,encoding){return alloc(null,size,fill,encoding)};function allocUnsafe(that,size){assertSize(size);that=createBuffer(that,size<0?0:checked(size)|0);if(!Buffer.TYPED_ARRAY_SUPPORT){for(var i=0;i<size;++i){that[i]=0}}return that}Buffer.allocUnsafe=function(size){return allocUnsafe(null,size)};Buffer.allocUnsafeSlow=function(size){return allocUnsafe(null,size)};function fromString(that,string,encoding){if(typeof encoding!==\"string\"||encoding===\"\"){encoding=\"utf8\"}if(!Buffer.isEncoding(encoding)){throw new TypeError('\"encoding\" must be a valid string encoding')}var length=byteLength(string,encoding)|0;that=createBuffer(that,length);var actual=that.write(string,encoding);if(actual!==length){that=that.slice(0,actual)}return that}function fromArrayLike(that,array){var length=array.length<0?0:checked(array.length)|0;that=createBuffer(that,length);for(var i=0;i<length;i+=1){that[i]=array[i]&255}return that}function fromArrayBuffer(that,array,byteOffset,length){array.byteLength;if(byteOffset<0||array.byteLength<byteOffset){throw new RangeError(\"'offset' is out of bounds\")}if(array.byteLength<byteOffset+(length||0)){throw new RangeError(\"'length' is out of bounds\")}if(byteOffset===undefined&&length===undefined){array=new Uint8Array(array)}else if(length===undefined){array=new Uint8Array(array,byteOffset)}else{array=new Uint8Array(array,byteOffset,length)}if(Buffer.TYPED_ARRAY_SUPPORT){that=array;that.__proto__=Buffer.prototype}else{that=fromArrayLike(that,array)}return that}function fromObject(that,obj){if(Buffer.isBuffer(obj)){var len=checked(obj.length)|0;that=createBuffer(that,len);if(that.length===0){return that}obj.copy(that,0,0,len);return that}if(obj){if(typeof ArrayBuffer!==\"undefined\"&&obj.buffer instanceof ArrayBuffer||\"length\"in obj){if(typeof obj.length!==\"number\"||isnan(obj.length)){return createBuffer(that,0)}return fromArrayLike(that,obj)}if(obj.type===\"Buffer\"&&isArray(obj.data)){return fromArrayLike(that,obj.data)}}throw new TypeError(\"First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.\")}function checked(length){if(length>=kMaxLength()){throw new RangeError(\"Attempt to allocate Buffer larger than maximum \"+\"size: 0x\"+kMaxLength().toString(16)+\" bytes\")}return length|0}function SlowBuffer(length){if(+length!=length){length=0}return Buffer.alloc(+length)}Buffer.isBuffer=function isBuffer(b){return!!(b!=null&&b._isBuffer)};Buffer.compare=function compare(a,b){if(!Buffer.isBuffer(a)||!Buffer.isBuffer(b)){throw new TypeError(\"Arguments must be Buffers\")}if(a===b)return 0;var x=a.length;var y=b.length;for(var i=0,len=Math.min(x,y);i<len;++i){if(a[i]!==b[i]){x=a[i];y=b[i];break}}if(x<y)return-1;if(y<x)return 1;return 0};Buffer.isEncoding=function isEncoding(encoding){switch(String(encoding).toLowerCase()){case\"hex\":case\"utf8\":case\"utf-8\":case\"ascii\":case\"latin1\":case\"binary\":case\"base64\":case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":return true;default:return false}};Buffer.concat=function concat(list,length){if(!isArray(list)){throw new TypeError('\"list\" argument must be an Array of Buffers')}if(list.length===0){return Buffer.alloc(0)}var i;if(length===undefined){length=0;for(i=0;i<list.length;++i){length+=list[i].length}}var buffer=Buffer.allocUnsafe(length);var pos=0;for(i=0;i<list.length;++i){var buf=list[i];if(!Buffer.isBuffer(buf)){throw new TypeError('\"list\" argument must be an Array of Buffers')}buf.copy(buffer,pos);pos+=buf.length}return buffer};function byteLength(string,encoding){if(Buffer.isBuffer(string)){return string.length}if(typeof ArrayBuffer!==\"undefined\"&&typeof ArrayBuffer.isView===\"function\"&&(ArrayBuffer.isView(string)||string instanceof ArrayBuffer)){return string.byteLength}if(typeof string!==\"string\"){string=\"\"+string}var len=string.length;if(len===0)return 0;var loweredCase=false;for(;;){switch(encoding){case\"ascii\":case\"latin1\":case\"binary\":return len;case\"utf8\":case\"utf-8\":case undefined:return utf8ToBytes(string).length;case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":return len*2;case\"hex\":return len>>>1;case\"base64\":return base64ToBytes(string).length;default:if(loweredCase)return utf8ToBytes(string).length;encoding=(\"\"+encoding).toLowerCase();loweredCase=true}}}Buffer.byteLength=byteLength;function slowToString(encoding,start,end){var loweredCase=false;if(start===undefined||start<0){start=0}if(start>this.length){return\"\"}if(end===undefined||end>this.length){end=this.length}if(end<=0){return\"\"}end>>>=0;start>>>=0;if(end<=start){return\"\"}if(!encoding)encoding=\"utf8\";while(true){switch(encoding){case\"hex\":return hexSlice(this,start,end);case\"utf8\":case\"utf-8\":return utf8Slice(this,start,end);case\"ascii\":return asciiSlice(this,start,end);case\"latin1\":case\"binary\":return latin1Slice(this,start,end);case\"base64\":return base64Slice(this,start,end);case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":return utf16leSlice(this,start,end);default:if(loweredCase)throw new TypeError(\"Unknown encoding: \"+encoding);encoding=(encoding+\"\").toLowerCase();loweredCase=true}}}Buffer.prototype._isBuffer=true;function swap(b,n,m){var i=b[n];b[n]=b[m];b[m]=i}Buffer.prototype.swap16=function swap16(){var len=this.length;if(len%2!==0){throw new RangeError(\"Buffer size must be a multiple of 16-bits\")}for(var i=0;i<len;i+=2){swap(this,i,i+1)}return this};Buffer.prototype.swap32=function swap32(){var len=this.length;if(len%4!==0){throw new RangeError(\"Buffer size must be a multiple of 32-bits\")}for(var i=0;i<len;i+=4){swap(this,i,i+3);swap(this,i+1,i+2)}return this};Buffer.prototype.swap64=function swap64(){var len=this.length;if(len%8!==0){throw new RangeError(\"Buffer size must be a multiple of 64-bits\")}for(var i=0;i<len;i+=8){swap(this,i,i+7);swap(this,i+1,i+6);swap(this,i+2,i+5);swap(this,i+3,i+4)}return this};Buffer.prototype.toString=function toString(){var length=this.length|0;if(length===0)return\"\";if(arguments.length===0)return utf8Slice(this,0,length);return slowToString.apply(this,arguments)};Buffer.prototype.equals=function equals(b){if(!Buffer.isBuffer(b))throw new TypeError(\"Argument must be a Buffer\");if(this===b)return true;return Buffer.compare(this,b)===0};Buffer.prototype.inspect=function inspect(){var str=\"\";var max=exports.INSPECT_MAX_BYTES;if(this.length>0){str=this.toString(\"hex\",0,max).match(/.{2}/g).join(\" \");if(this.length>max)str+=\" ... \"}return\"<Buffer \"+str+\">\"};Buffer.prototype.compare=function compare(target,start,end,thisStart,thisEnd){if(!Buffer.isBuffer(target)){throw new TypeError(\"Argument must be a Buffer\")}if(start===undefined){start=0}if(end===undefined){end=target?target.length:0}if(thisStart===undefined){thisStart=0}if(thisEnd===undefined){thisEnd=this.length}if(start<0||end>target.length||thisStart<0||thisEnd>this.length){throw new RangeError(\"out of range index\")}if(thisStart>=thisEnd&&start>=end){return 0}if(thisStart>=thisEnd){return-1}if(start>=end){return 1}start>>>=0;end>>>=0;thisStart>>>=0;thisEnd>>>=0;if(this===target)return 0;var x=thisEnd-thisStart;var y=end-start;var len=Math.min(x,y);var thisCopy=this.slice(thisStart,thisEnd);var targetCopy=target.slice(start,end);for(var i=0;i<len;++i){if(thisCopy[i]!==targetCopy[i]){x=thisCopy[i];y=targetCopy[i];break}}if(x<y)return-1;if(y<x)return 1;return 0};function bidirectionalIndexOf(buffer,val,byteOffset,encoding,dir){if(buffer.length===0)return-1;if(typeof byteOffset===\"string\"){encoding=byteOffset;byteOffset=0}else if(byteOffset>2147483647){byteOffset=2147483647}else if(byteOffset<-2147483648){byteOffset=-2147483648}byteOffset=+byteOffset;if(isNaN(byteOffset)){byteOffset=dir?0:buffer.length-1}if(byteOffset<0)byteOffset=buffer.length+byteOffset;if(byteOffset>=buffer.length){if(dir)return-1;else byteOffset=buffer.length-1}else if(byteOffset<0){if(dir)byteOffset=0;else return-1}if(typeof val===\"string\"){val=Buffer.from(val,encoding)}if(Buffer.isBuffer(val)){if(val.length===0){return-1}return arrayIndexOf(buffer,val,byteOffset,encoding,dir)}else if(typeof val===\"number\"){val=val&255;if(Buffer.TYPED_ARRAY_SUPPORT&&typeof Uint8Array.prototype.indexOf===\"function\"){if(dir){return Uint8Array.prototype.indexOf.call(buffer,val,byteOffset)}else{return Uint8Array.prototype.lastIndexOf.call(buffer,val,byteOffset)}}return arrayIndexOf(buffer,[val],byteOffset,encoding,dir)}throw new TypeError(\"val must be string, number or Buffer\")}function arrayIndexOf(arr,val,byteOffset,encoding,dir){var indexSize=1;var arrLength=arr.length;var valLength=val.length;if(encoding!==undefined){encoding=String(encoding).toLowerCase();if(encoding===\"ucs2\"||encoding===\"ucs-2\"||encoding===\"utf16le\"||encoding===\"utf-16le\"){if(arr.length<2||val.length<2){return-1}indexSize=2;arrLength/=2;valLength/=2;byteOffset/=2}}function read(buf,i){if(indexSize===1){return buf[i]}else{return buf.readUInt16BE(i*indexSize)}}var i;if(dir){var foundIndex=-1;for(i=byteOffset;i<arrLength;i++){if(read(arr,i)===read(val,foundIndex===-1?0:i-foundIndex)){if(foundIndex===-1)foundIndex=i;if(i-foundIndex+1===valLength)return foundIndex*indexSize}else{if(foundIndex!==-1)i-=i-foundIndex;foundIndex=-1}}}else{if(byteOffset+valLength>arrLength)byteOffset=arrLength-valLength;for(i=byteOffset;i>=0;i--){var found=true;for(var j=0;j<valLength;j++){if(read(arr,i+j)!==read(val,j)){found=false;break}}if(found)return i}}return-1}Buffer.prototype.includes=function includes(val,byteOffset,encoding){return this.indexOf(val,byteOffset,encoding)!==-1};Buffer.prototype.indexOf=function indexOf(val,byteOffset,encoding){return bidirectionalIndexOf(this,val,byteOffset,encoding,true)};Buffer.prototype.lastIndexOf=function lastIndexOf(val,byteOffset,encoding){return bidirectionalIndexOf(this,val,byteOffset,encoding,false)};function hexWrite(buf,string,offset,length){offset=Number(offset)||0;var remaining=buf.length-offset;if(!length){length=remaining}else{length=Number(length);if(length>remaining){length=remaining}}var strLen=string.length;if(strLen%2!==0)throw new TypeError(\"Invalid hex string\");if(length>strLen/2){length=strLen/2}for(var i=0;i<length;++i){var parsed=parseInt(string.substr(i*2,2),16);if(isNaN(parsed))return i;buf[offset+i]=parsed}return i}function utf8Write(buf,string,offset,length){return blitBuffer(utf8ToBytes(string,buf.length-offset),buf,offset,length)}function asciiWrite(buf,string,offset,length){return blitBuffer(asciiToBytes(string),buf,offset,length)}function latin1Write(buf,string,offset,length){return asciiWrite(buf,string,offset,length)}function base64Write(buf,string,offset,length){return blitBuffer(base64ToBytes(string),buf,offset,length)}function ucs2Write(buf,string,offset,length){return blitBuffer(utf16leToBytes(string,buf.length-offset),buf,offset,length)}Buffer.prototype.write=function write(string,offset,length,encoding){if(offset===undefined){encoding=\"utf8\";length=this.length;offset=0}else if(length===undefined&&typeof offset===\"string\"){encoding=offset;length=this.length;offset=0}else if(isFinite(offset)){offset=offset|0;if(isFinite(length)){length=length|0;if(encoding===undefined)encoding=\"utf8\"}else{encoding=length;length=undefined}}else{throw new Error(\"Buffer.write(string, encoding, offset[, length]) is no longer supported\")}var remaining=this.length-offset;if(length===undefined||length>remaining)length=remaining;if(string.length>0&&(length<0||offset<0)||offset>this.length){throw new RangeError(\"Attempt to write outside buffer bounds\")}if(!encoding)encoding=\"utf8\";var loweredCase=false;for(;;){switch(encoding){case\"hex\":return hexWrite(this,string,offset,length);case\"utf8\":case\"utf-8\":return utf8Write(this,string,offset,length);case\"ascii\":return asciiWrite(this,string,offset,length);case\"latin1\":case\"binary\":return latin1Write(this,string,offset,length);case\"base64\":return base64Write(this,string,offset,length);case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":return ucs2Write(this,string,offset,length);default:if(loweredCase)throw new TypeError(\"Unknown encoding: \"+encoding);encoding=(\"\"+encoding).toLowerCase();loweredCase=true}}};Buffer.prototype.toJSON=function toJSON(){return{type:\"Buffer\",data:Array.prototype.slice.call(this._arr||this,0)}};function base64Slice(buf,start,end){if(start===0&&end===buf.length){return base64.fromByteArray(buf)}else{return base64.fromByteArray(buf.slice(start,end))}}function utf8Slice(buf,start,end){end=Math.min(buf.length,end);var res=[];var i=start;while(i<end){var firstByte=buf[i];var codePoint=null;var bytesPerSequence=firstByte>239?4:firstByte>223?3:firstByte>191?2:1;if(i+bytesPerSequence<=end){var secondByte,thirdByte,fourthByte,tempCodePoint;switch(bytesPerSequence){case 1:if(firstByte<128){codePoint=firstByte}break;case 2:secondByte=buf[i+1];if((secondByte&192)===128){tempCodePoint=(firstByte&31)<<6|secondByte&63;if(tempCodePoint>127){codePoint=tempCodePoint}}break;case 3:secondByte=buf[i+1];thirdByte=buf[i+2];if((secondByte&192)===128&&(thirdByte&192)===128){tempCodePoint=(firstByte&15)<<12|(secondByte&63)<<6|thirdByte&63;if(tempCodePoint>2047&&(tempCodePoint<55296||tempCodePoint>57343)){codePoint=tempCodePoint}}break;case 4:secondByte=buf[i+1];thirdByte=buf[i+2];fourthByte=buf[i+3];if((secondByte&192)===128&&(thirdByte&192)===128&&(fourthByte&192)===128){tempCodePoint=(firstByte&15)<<18|(secondByte&63)<<12|(thirdByte&63)<<6|fourthByte&63;if(tempCodePoint>65535&&tempCodePoint<1114112){codePoint=tempCodePoint}}}}if(codePoint===null){codePoint=65533;bytesPerSequence=1}else if(codePoint>65535){codePoint-=65536;res.push(codePoint>>>10&1023|55296);codePoint=56320|codePoint&1023}res.push(codePoint);i+=bytesPerSequence}return decodeCodePointsArray(res)}var MAX_ARGUMENTS_LENGTH=4096;function decodeCodePointsArray(codePoints){var len=codePoints.length;if(len<=MAX_ARGUMENTS_LENGTH){return String.fromCharCode.apply(String,codePoints)}var res=\"\";var i=0;while(i<len){res+=String.fromCharCode.apply(String,codePoints.slice(i,i+=MAX_ARGUMENTS_LENGTH))}return res}function asciiSlice(buf,start,end){var ret=\"\";end=Math.min(buf.length,end);for(var i=start;i<end;++i){ret+=String.fromCharCode(buf[i]&127)}return ret}function latin1Slice(buf,start,end){var ret=\"\";end=Math.min(buf.length,end);for(var i=start;i<end;++i){ret+=String.fromCharCode(buf[i])}return ret}function hexSlice(buf,start,end){var len=buf.length;if(!start||start<0)start=0;if(!end||end<0||end>len)end=len;var out=\"\";for(var i=start;i<end;++i){out+=toHex(buf[i])}return out}function utf16leSlice(buf,start,end){var bytes=buf.slice(start,end);var res=\"\";for(var i=0;i<bytes.length;i+=2){res+=String.fromCharCode(bytes[i]+bytes[i+1]*256)}return res}Buffer.prototype.slice=function slice(start,end){var len=this.length;start=~~start;end=end===undefined?len:~~end;if(start<0){start+=len;if(start<0)start=0}else if(start>len){start=len}if(end<0){end+=len;if(end<0)end=0}else if(end>len){end=len}if(end<start)end=start;var newBuf;if(Buffer.TYPED_ARRAY_SUPPORT){newBuf=this.subarray(start,end);newBuf.__proto__=Buffer.prototype}else{var sliceLen=end-start;newBuf=new Buffer(sliceLen,undefined);for(var i=0;i<sliceLen;++i){newBuf[i]=this[i+start]}}return newBuf};function checkOffset(offset,ext,length){if(offset%1!==0||offset<0)throw new RangeError(\"offset is not uint\");if(offset+ext>length)throw new RangeError(\"Trying to access beyond buffer length\")}Buffer.prototype.readUIntLE=function readUIntLE(offset,byteLength,noAssert){offset=offset|0;byteLength=byteLength|0;if(!noAssert)checkOffset(offset,byteLength,this.length);var val=this[offset];var mul=1;var i=0;while(++i<byteLength&&(mul*=256)){val+=this[offset+i]*mul}return val};Buffer.prototype.readUIntBE=function readUIntBE(offset,byteLength,noAssert){offset=offset|0;byteLength=byteLength|0;if(!noAssert){checkOffset(offset,byteLength,this.length)}var val=this[offset+--byteLength];var mul=1;while(byteLength>0&&(mul*=256)){val+=this[offset+--byteLength]*mul}return val};Buffer.prototype.readUInt8=function readUInt8(offset,noAssert){if(!noAssert)checkOffset(offset,1,this.length);return this[offset]};Buffer.prototype.readUInt16LE=function readUInt16LE(offset,noAssert){if(!noAssert)checkOffset(offset,2,this.length);return this[offset]|this[offset+1]<<8};Buffer.prototype.readUInt16BE=function readUInt16BE(offset,noAssert){if(!noAssert)checkOffset(offset,2,this.length);return this[offset]<<8|this[offset+1]};Buffer.prototype.readUInt32LE=function readUInt32LE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length);return(this[offset]|this[offset+1]<<8|this[offset+2]<<16)+this[offset+3]*16777216};Buffer.prototype.readUInt32BE=function readUInt32BE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length);return this[offset]*16777216+(this[offset+1]<<16|this[offset+2]<<8|this[offset+3])};Buffer.prototype.readIntLE=function readIntLE(offset,byteLength,noAssert){offset=offset|0;byteLength=byteLength|0;if(!noAssert)checkOffset(offset,byteLength,this.length);var val=this[offset];var mul=1;var i=0;while(++i<byteLength&&(mul*=256)){val+=this[offset+i]*mul}mul*=128;if(val>=mul)val-=Math.pow(2,8*byteLength);return val};Buffer.prototype.readIntBE=function readIntBE(offset,byteLength,noAssert){offset=offset|0;byteLength=byteLength|0;if(!noAssert)checkOffset(offset,byteLength,this.length);\nvar i=byteLength;var mul=1;var val=this[offset+--i];while(i>0&&(mul*=256)){val+=this[offset+--i]*mul}mul*=128;if(val>=mul)val-=Math.pow(2,8*byteLength);return val};Buffer.prototype.readInt8=function readInt8(offset,noAssert){if(!noAssert)checkOffset(offset,1,this.length);if(!(this[offset]&128))return this[offset];return(255-this[offset]+1)*-1};Buffer.prototype.readInt16LE=function readInt16LE(offset,noAssert){if(!noAssert)checkOffset(offset,2,this.length);var val=this[offset]|this[offset+1]<<8;return val&32768?val|4294901760:val};Buffer.prototype.readInt16BE=function readInt16BE(offset,noAssert){if(!noAssert)checkOffset(offset,2,this.length);var val=this[offset+1]|this[offset]<<8;return val&32768?val|4294901760:val};Buffer.prototype.readInt32LE=function readInt32LE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length);return this[offset]|this[offset+1]<<8|this[offset+2]<<16|this[offset+3]<<24};Buffer.prototype.readInt32BE=function readInt32BE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length);return this[offset]<<24|this[offset+1]<<16|this[offset+2]<<8|this[offset+3]};Buffer.prototype.readFloatLE=function readFloatLE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length);return ieee754.read(this,offset,true,23,4)};Buffer.prototype.readFloatBE=function readFloatBE(offset,noAssert){if(!noAssert)checkOffset(offset,4,this.length);return ieee754.read(this,offset,false,23,4)};Buffer.prototype.readDoubleLE=function readDoubleLE(offset,noAssert){if(!noAssert)checkOffset(offset,8,this.length);return ieee754.read(this,offset,true,52,8)};Buffer.prototype.readDoubleBE=function readDoubleBE(offset,noAssert){if(!noAssert)checkOffset(offset,8,this.length);return ieee754.read(this,offset,false,52,8)};function checkInt(buf,value,offset,ext,max,min){if(!Buffer.isBuffer(buf))throw new TypeError('\"buffer\" argument must be a Buffer instance');if(value>max||value<min)throw new RangeError('\"value\" argument is out of bounds');if(offset+ext>buf.length)throw new RangeError(\"Index out of range\")}Buffer.prototype.writeUIntLE=function writeUIntLE(value,offset,byteLength,noAssert){value=+value;offset=offset|0;byteLength=byteLength|0;if(!noAssert){var maxBytes=Math.pow(2,8*byteLength)-1;checkInt(this,value,offset,byteLength,maxBytes,0)}var mul=1;var i=0;this[offset]=value&255;while(++i<byteLength&&(mul*=256)){this[offset+i]=value/mul&255}return offset+byteLength};Buffer.prototype.writeUIntBE=function writeUIntBE(value,offset,byteLength,noAssert){value=+value;offset=offset|0;byteLength=byteLength|0;if(!noAssert){var maxBytes=Math.pow(2,8*byteLength)-1;checkInt(this,value,offset,byteLength,maxBytes,0)}var i=byteLength-1;var mul=1;this[offset+i]=value&255;while(--i>=0&&(mul*=256)){this[offset+i]=value/mul&255}return offset+byteLength};Buffer.prototype.writeUInt8=function writeUInt8(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,1,255,0);if(!Buffer.TYPED_ARRAY_SUPPORT)value=Math.floor(value);this[offset]=value&255;return offset+1};function objectWriteUInt16(buf,value,offset,littleEndian){if(value<0)value=65535+value+1;for(var i=0,j=Math.min(buf.length-offset,2);i<j;++i){buf[offset+i]=(value&255<<8*(littleEndian?i:1-i))>>>(littleEndian?i:1-i)*8}}Buffer.prototype.writeUInt16LE=function writeUInt16LE(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,2,65535,0);if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=value&255;this[offset+1]=value>>>8}else{objectWriteUInt16(this,value,offset,true)}return offset+2};Buffer.prototype.writeUInt16BE=function writeUInt16BE(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,2,65535,0);if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=value>>>8;this[offset+1]=value&255}else{objectWriteUInt16(this,value,offset,false)}return offset+2};function objectWriteUInt32(buf,value,offset,littleEndian){if(value<0)value=4294967295+value+1;for(var i=0,j=Math.min(buf.length-offset,4);i<j;++i){buf[offset+i]=value>>>(littleEndian?i:3-i)*8&255}}Buffer.prototype.writeUInt32LE=function writeUInt32LE(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,4,4294967295,0);if(Buffer.TYPED_ARRAY_SUPPORT){this[offset+3]=value>>>24;this[offset+2]=value>>>16;this[offset+1]=value>>>8;this[offset]=value&255}else{objectWriteUInt32(this,value,offset,true)}return offset+4};Buffer.prototype.writeUInt32BE=function writeUInt32BE(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,4,4294967295,0);if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=value>>>24;this[offset+1]=value>>>16;this[offset+2]=value>>>8;this[offset+3]=value&255}else{objectWriteUInt32(this,value,offset,false)}return offset+4};Buffer.prototype.writeIntLE=function writeIntLE(value,offset,byteLength,noAssert){value=+value;offset=offset|0;if(!noAssert){var limit=Math.pow(2,8*byteLength-1);checkInt(this,value,offset,byteLength,limit-1,-limit)}var i=0;var mul=1;var sub=0;this[offset]=value&255;while(++i<byteLength&&(mul*=256)){if(value<0&&sub===0&&this[offset+i-1]!==0){sub=1}this[offset+i]=(value/mul>>0)-sub&255}return offset+byteLength};Buffer.prototype.writeIntBE=function writeIntBE(value,offset,byteLength,noAssert){value=+value;offset=offset|0;if(!noAssert){var limit=Math.pow(2,8*byteLength-1);checkInt(this,value,offset,byteLength,limit-1,-limit)}var i=byteLength-1;var mul=1;var sub=0;this[offset+i]=value&255;while(--i>=0&&(mul*=256)){if(value<0&&sub===0&&this[offset+i+1]!==0){sub=1}this[offset+i]=(value/mul>>0)-sub&255}return offset+byteLength};Buffer.prototype.writeInt8=function writeInt8(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,1,127,-128);if(!Buffer.TYPED_ARRAY_SUPPORT)value=Math.floor(value);if(value<0)value=255+value+1;this[offset]=value&255;return offset+1};Buffer.prototype.writeInt16LE=function writeInt16LE(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,2,32767,-32768);if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=value&255;this[offset+1]=value>>>8}else{objectWriteUInt16(this,value,offset,true)}return offset+2};Buffer.prototype.writeInt16BE=function writeInt16BE(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,2,32767,-32768);if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=value>>>8;this[offset+1]=value&255}else{objectWriteUInt16(this,value,offset,false)}return offset+2};Buffer.prototype.writeInt32LE=function writeInt32LE(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,4,2147483647,-2147483648);if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=value&255;this[offset+1]=value>>>8;this[offset+2]=value>>>16;this[offset+3]=value>>>24}else{objectWriteUInt32(this,value,offset,true)}return offset+4};Buffer.prototype.writeInt32BE=function writeInt32BE(value,offset,noAssert){value=+value;offset=offset|0;if(!noAssert)checkInt(this,value,offset,4,2147483647,-2147483648);if(value<0)value=4294967295+value+1;if(Buffer.TYPED_ARRAY_SUPPORT){this[offset]=value>>>24;this[offset+1]=value>>>16;this[offset+2]=value>>>8;this[offset+3]=value&255}else{objectWriteUInt32(this,value,offset,false)}return offset+4};function checkIEEE754(buf,value,offset,ext,max,min){if(offset+ext>buf.length)throw new RangeError(\"Index out of range\");if(offset<0)throw new RangeError(\"Index out of range\")}function writeFloat(buf,value,offset,littleEndian,noAssert){if(!noAssert){checkIEEE754(buf,value,offset,4,3.4028234663852886e38,-3.4028234663852886e38)}ieee754.write(buf,value,offset,littleEndian,23,4);return offset+4}Buffer.prototype.writeFloatLE=function writeFloatLE(value,offset,noAssert){return writeFloat(this,value,offset,true,noAssert)};Buffer.prototype.writeFloatBE=function writeFloatBE(value,offset,noAssert){return writeFloat(this,value,offset,false,noAssert)};function writeDouble(buf,value,offset,littleEndian,noAssert){if(!noAssert){checkIEEE754(buf,value,offset,8,1.7976931348623157e308,-1.7976931348623157e308)}ieee754.write(buf,value,offset,littleEndian,52,8);return offset+8}Buffer.prototype.writeDoubleLE=function writeDoubleLE(value,offset,noAssert){return writeDouble(this,value,offset,true,noAssert)};Buffer.prototype.writeDoubleBE=function writeDoubleBE(value,offset,noAssert){return writeDouble(this,value,offset,false,noAssert)};Buffer.prototype.copy=function copy(target,targetStart,start,end){if(!start)start=0;if(!end&&end!==0)end=this.length;if(targetStart>=target.length)targetStart=target.length;if(!targetStart)targetStart=0;if(end>0&&end<start)end=start;if(end===start)return 0;if(target.length===0||this.length===0)return 0;if(targetStart<0){throw new RangeError(\"targetStart out of bounds\")}if(start<0||start>=this.length)throw new RangeError(\"sourceStart out of bounds\");if(end<0)throw new RangeError(\"sourceEnd out of bounds\");if(end>this.length)end=this.length;if(target.length-targetStart<end-start){end=target.length-targetStart+start}var len=end-start;var i;if(this===target&&start<targetStart&&targetStart<end){for(i=len-1;i>=0;--i){target[i+targetStart]=this[i+start]}}else if(len<1e3||!Buffer.TYPED_ARRAY_SUPPORT){for(i=0;i<len;++i){target[i+targetStart]=this[i+start]}}else{Uint8Array.prototype.set.call(target,this.subarray(start,start+len),targetStart)}return len};Buffer.prototype.fill=function fill(val,start,end,encoding){if(typeof val===\"string\"){if(typeof start===\"string\"){encoding=start;start=0;end=this.length}else if(typeof end===\"string\"){encoding=end;end=this.length}if(val.length===1){var code=val.charCodeAt(0);if(code<256){val=code}}if(encoding!==undefined&&typeof encoding!==\"string\"){throw new TypeError(\"encoding must be a string\")}if(typeof encoding===\"string\"&&!Buffer.isEncoding(encoding)){throw new TypeError(\"Unknown encoding: \"+encoding)}}else if(typeof val===\"number\"){val=val&255}if(start<0||this.length<start||this.length<end){throw new RangeError(\"Out of range index\")}if(end<=start){return this}start=start>>>0;end=end===undefined?this.length:end>>>0;if(!val)val=0;var i;if(typeof val===\"number\"){for(i=start;i<end;++i){this[i]=val}}else{var bytes=Buffer.isBuffer(val)?val:utf8ToBytes(new Buffer(val,encoding).toString());var len=bytes.length;for(i=0;i<end-start;++i){this[i+start]=bytes[i%len]}}return this};var INVALID_BASE64_RE=/[^+\\/0-9A-Za-z-_]/g;function base64clean(str){str=stringtrim(str).replace(INVALID_BASE64_RE,\"\");if(str.length<2)return\"\";while(str.length%4!==0){str=str+\"=\"}return str}function stringtrim(str){if(str.trim)return str.trim();return str.replace(/^\\s+|\\s+$/g,\"\")}function toHex(n){if(n<16)return\"0\"+n.toString(16);return n.toString(16)}function utf8ToBytes(string,units){units=units||Infinity;var codePoint;var length=string.length;var leadSurrogate=null;var bytes=[];for(var i=0;i<length;++i){codePoint=string.charCodeAt(i);if(codePoint>55295&&codePoint<57344){if(!leadSurrogate){if(codePoint>56319){if((units-=3)>-1)bytes.push(239,191,189);continue}else if(i+1===length){if((units-=3)>-1)bytes.push(239,191,189);continue}leadSurrogate=codePoint;continue}if(codePoint<56320){if((units-=3)>-1)bytes.push(239,191,189);leadSurrogate=codePoint;continue}codePoint=(leadSurrogate-55296<<10|codePoint-56320)+65536}else if(leadSurrogate){if((units-=3)>-1)bytes.push(239,191,189)}leadSurrogate=null;if(codePoint<128){if((units-=1)<0)break;bytes.push(codePoint)}else if(codePoint<2048){if((units-=2)<0)break;bytes.push(codePoint>>6|192,codePoint&63|128)}else if(codePoint<65536){if((units-=3)<0)break;bytes.push(codePoint>>12|224,codePoint>>6&63|128,codePoint&63|128)}else if(codePoint<1114112){if((units-=4)<0)break;bytes.push(codePoint>>18|240,codePoint>>12&63|128,codePoint>>6&63|128,codePoint&63|128)}else{throw new Error(\"Invalid code point\")}}return bytes}function asciiToBytes(str){var byteArray=[];for(var i=0;i<str.length;++i){byteArray.push(str.charCodeAt(i)&255)}return byteArray}function utf16leToBytes(str,units){var c,hi,lo;var byteArray=[];for(var i=0;i<str.length;++i){if((units-=2)<0)break;c=str.charCodeAt(i);hi=c>>8;lo=c%256;byteArray.push(lo);byteArray.push(hi)}return byteArray}function base64ToBytes(str){return base64.toByteArray(base64clean(str))}function blitBuffer(src,dst,offset,length){for(var i=0;i<length;++i){if(i+offset>=dst.length||i>=src.length)break;dst[i+offset]=src[i]}return i}function isnan(val){return val!==val}}).call(this,typeof global!==\"undefined\"?global:typeof self!==\"undefined\"?self:typeof window!==\"undefined\"?window:{})},{\"base64-js\":2,ieee754:37,isarray:40}],6:[function(require,module,exports){(function(Buffer){function isArray(arg){if(Array.isArray){return Array.isArray(arg)}return objectToString(arg)===\"[object Array]\"}exports.isArray=isArray;function isBoolean(arg){return typeof arg===\"boolean\"}exports.isBoolean=isBoolean;function isNull(arg){return arg===null}exports.isNull=isNull;function isNullOrUndefined(arg){return arg==null}exports.isNullOrUndefined=isNullOrUndefined;function isNumber(arg){return typeof arg===\"number\"}exports.isNumber=isNumber;function isString(arg){return typeof arg===\"string\"}exports.isString=isString;function isSymbol(arg){return typeof arg===\"symbol\"}exports.isSymbol=isSymbol;function isUndefined(arg){return arg===void 0}exports.isUndefined=isUndefined;function isRegExp(re){return objectToString(re)===\"[object RegExp]\"}exports.isRegExp=isRegExp;function isObject(arg){return typeof arg===\"object\"&&arg!==null}exports.isObject=isObject;function isDate(d){return objectToString(d)===\"[object Date]\"}exports.isDate=isDate;function isError(e){return objectToString(e)===\"[object Error]\"||e instanceof Error}exports.isError=isError;function isFunction(arg){return typeof arg===\"function\"}exports.isFunction=isFunction;function isPrimitive(arg){return arg===null||typeof arg===\"boolean\"||typeof arg===\"number\"||typeof arg===\"string\"||typeof arg===\"symbol\"||typeof arg===\"undefined\"}exports.isPrimitive=isPrimitive;exports.isBuffer=Buffer.isBuffer;function objectToString(o){return Object.prototype.toString.call(o)}}).call(this,{isBuffer:require(\"../../is-buffer/index.js\")})},{\"../../is-buffer/index.js\":39}],7:[function(require,module,exports){var ElementType=require(\"domelementtype\");var entities=require(\"entities\");var booleanAttributes={__proto__:null,allowfullscreen:true,async:true,autofocus:true,autoplay:true,checked:true,controls:true,default:true,defer:true,disabled:true,hidden:true,ismap:true,loop:true,multiple:true,muted:true,open:true,readonly:true,required:true,reversed:true,scoped:true,seamless:true,selected:true,typemustmatch:true};var unencodedElements={__proto__:null,style:true,script:true,xmp:true,iframe:true,noembed:true,noframes:true,plaintext:true,noscript:true};function formatAttrs(attributes,opts){if(!attributes)return;var output=\"\",value;for(var key in attributes){value=attributes[key];if(output){output+=\" \"}if(!value&&booleanAttributes[key]){output+=key}else{output+=key+'=\"'+(opts.decodeEntities?entities.encodeXML(value):value)+'\"'}}return output}var singleTag={__proto__:null,area:true,base:true,basefont:true,br:true,col:true,command:true,embed:true,frame:true,hr:true,img:true,input:true,isindex:true,keygen:true,link:true,meta:true,param:true,source:true,track:true,wbr:true};var render=module.exports=function(dom,opts){if(!Array.isArray(dom)&&!dom.cheerio)dom=[dom];opts=opts||{};var output=\"\";for(var i=0;i<dom.length;i++){var elem=dom[i];if(elem.type===\"root\")output+=render(elem.children,opts);else if(ElementType.isTag(elem))output+=renderTag(elem,opts);else if(elem.type===ElementType.Directive)output+=renderDirective(elem);else if(elem.type===ElementType.Comment)output+=renderComment(elem);else if(elem.type===ElementType.CDATA)output+=renderCdata(elem);else output+=renderText(elem,opts)}return output};function renderTag(elem,opts){if(elem.name===\"svg\")opts={decodeEntities:opts.decodeEntities,xmlMode:true};var tag=\"<\"+elem.name,attribs=formatAttrs(elem.attribs,opts);if(attribs){tag+=\" \"+attribs}if(opts.xmlMode&&(!elem.children||elem.children.length===0)){tag+=\"/>\"}else{tag+=\">\";if(elem.children){tag+=render(elem.children,opts)}if(!singleTag[elem.name]||opts.xmlMode){tag+=\"</\"+elem.name+\">\"}}return tag}function renderDirective(elem){return\"<\"+elem.data+\">\"}function renderText(elem,opts){var data=elem.data||\"\";if(opts.decodeEntities&&!(elem.parent&&elem.parent.name in unencodedElements)){data=entities.encodeXML(data)}return data}function renderCdata(elem){return\"<![CDATA[\"+elem.children[0].data+\"]]>\"}function renderComment(elem){return\"<!--\"+elem.data+\"-->\"}},{domelementtype:8,entities:20}],8:[function(require,module,exports){module.exports={Text:\"text\",Directive:\"directive\",Comment:\"comment\",Script:\"script\",Style:\"style\",Tag:\"tag\",CDATA:\"cdata\",isTag:function(elem){return elem.type===\"tag\"||elem.type===\"script\"||elem.type===\"style\"}}},{}],9:[function(require,module,exports){module.exports={Text:\"text\",Directive:\"directive\",Comment:\"comment\",Script:\"script\",Style:\"style\",Tag:\"tag\",CDATA:\"cdata\",Doctype:\"doctype\",isTag:function(elem){return elem.type===\"tag\"||elem.type===\"script\"||elem.type===\"style\"}}},{}],10:[function(require,module,exports){var ElementType=require(\"domelementtype\");var re_whitespace=/\\s+/g;var NodePrototype=require(\"./lib/node\");var ElementPrototype=require(\"./lib/element\");function DomHandler(callback,options,elementCB){if(typeof callback===\"object\"){elementCB=options;options=callback;callback=null}else if(typeof options===\"function\"){elementCB=options;options=defaultOpts}this._callback=callback;this._options=options||defaultOpts;this._elementCB=elementCB;this.dom=[];this._done=false;this._tagStack=[];this._parser=this._parser||null}var defaultOpts={normalizeWhitespace:false,withStartIndices:false};DomHandler.prototype.onparserinit=function(parser){this._parser=parser};DomHandler.prototype.onreset=function(){DomHandler.call(this,this._callback,this._options,this._elementCB)};DomHandler.prototype.onend=function(){if(this._done)return;this._done=true;this._parser=null;this._handleCallback(null)};DomHandler.prototype._handleCallback=DomHandler.prototype.onerror=function(error){if(typeof this._callback===\"function\"){this._callback(error,this.dom)}else{if(error)throw error}};DomHandler.prototype.onclosetag=function(){var elem=this._tagStack.pop();if(this._elementCB)this._elementCB(elem)};DomHandler.prototype._addDomElement=function(element){var parent=this._tagStack[this._tagStack.length-1];var siblings=parent?parent.children:this.dom;var previousSibling=siblings[siblings.length-1];element.next=null;if(this._options.withStartIndices){element.startIndex=this._parser.startIndex}if(this._options.withDomLvl1){element.__proto__=element.type===\"tag\"?ElementPrototype:NodePrototype}if(previousSibling){element.prev=previousSibling;previousSibling.next=element}else{element.prev=null}siblings.push(element);element.parent=parent||null};DomHandler.prototype.onopentag=function(name,attribs){var element={type:name===\"script\"?ElementType.Script:name===\"style\"?ElementType.Style:ElementType.Tag,name:name,attribs:attribs,children:[]};this._addDomElement(element);this._tagStack.push(element)};DomHandler.prototype.ontext=function(data){var normalize=this._options.normalizeWhitespace||this._options.ignoreWhitespace;var lastTag;if(!this._tagStack.length&&this.dom.length&&(lastTag=this.dom[this.dom.length-1]).type===ElementType.Text){if(normalize){lastTag.data=(lastTag.data+data).replace(re_whitespace,\" \")}else{lastTag.data+=data}}else{if(this._tagStack.length&&(lastTag=this._tagStack[this._tagStack.length-1])&&(lastTag=lastTag.children[lastTag.children.length-1])&&lastTag.type===ElementType.Text){if(normalize){lastTag.data=(lastTag.data+data).replace(re_whitespace,\" \")}else{lastTag.data+=data}}else{if(normalize){data=data.replace(re_whitespace,\" \")}this._addDomElement({data:data,type:ElementType.Text})}}};DomHandler.prototype.oncomment=function(data){var lastTag=this._tagStack[this._tagStack.length-1];if(lastTag&&lastTag.type===ElementType.Comment){lastTag.data+=data;return}var element={data:data,type:ElementType.Comment};this._addDomElement(element);this._tagStack.push(element)};DomHandler.prototype.oncdatastart=function(){var element={children:[{data:\"\",type:ElementType.Text}],type:ElementType.CDATA};this._addDomElement(element);this._tagStack.push(element)};DomHandler.prototype.oncommentend=DomHandler.prototype.oncdataend=function(){this._tagStack.pop()};DomHandler.prototype.onprocessinginstruction=function(name,data){this._addDomElement({name:name,data:data,type:ElementType.Directive})};module.exports=DomHandler},{\"./lib/element\":11,\"./lib/node\":12,domelementtype:9}],11:[function(require,module,exports){var NodePrototype=require(\"./node\");var ElementPrototype=module.exports=Object.create(NodePrototype);var domLvl1={tagName:\"name\"};Object.keys(domLvl1).forEach(function(key){var shorthand=domLvl1[key];Object.defineProperty(ElementPrototype,key,{get:function(){return this[shorthand]||null},set:function(val){this[shorthand]=val;return val}})})},{\"./node\":12}],12:[function(require,module,exports){var NodePrototype=module.exports={get firstChild(){var children=this.children;return children&&children[0]||null},get lastChild(){var children=this.children;return children&&children[children.length-1]||null},get nodeType(){return nodeTypes[this.type]||nodeTypes.element}};var domLvl1={tagName:\"name\",childNodes:\"children\",parentNode:\"parent\",previousSibling:\"prev\",nextSibling:\"next\",nodeValue:\"data\"};var nodeTypes={element:1,text:3,cdata:4,comment:8};Object.keys(domLvl1).forEach(function(key){var shorthand=domLvl1[key];Object.defineProperty(NodePrototype,key,{get:function(){return this[shorthand]||null},set:function(val){this[shorthand]=val;return val}})})},{}],13:[function(require,module,exports){var DomUtils=module.exports;[require(\"./lib/stringify\"),require(\"./lib/traversal\"),require(\"./lib/manipulation\"),require(\"./lib/querying\"),require(\"./lib/legacy\"),require(\"./lib/helpers\")].forEach(function(ext){Object.keys(ext).forEach(function(key){DomUtils[key]=ext[key].bind(DomUtils)})})},{\"./lib/helpers\":14,\"./lib/legacy\":15,\"./lib/manipulation\":16,\"./lib/querying\":17,\"./lib/stringify\":18,\"./lib/traversal\":19}],14:[function(require,module,exports){exports.removeSubsets=function(nodes){var idx=nodes.length,node,ancestor,replace;while(--idx>-1){node=ancestor=nodes[idx];nodes[idx]=null;replace=true;while(ancestor){if(nodes.indexOf(ancestor)>-1){replace=false;nodes.splice(idx,1);break}ancestor=ancestor.parent}if(replace){nodes[idx]=node}}return nodes};var POSITION={DISCONNECTED:1,PRECEDING:2,FOLLOWING:4,CONTAINS:8,CONTAINED_BY:16};var comparePos=exports.compareDocumentPosition=function(nodeA,nodeB){var aParents=[];var bParents=[];var current,sharedParent,siblings,aSibling,bSibling,idx;if(nodeA===nodeB){return 0}current=nodeA;while(current){aParents.unshift(current);current=current.parent}current=nodeB;while(current){bParents.unshift(current);current=current.parent}idx=0;while(aParents[idx]===bParents[idx]){idx++}if(idx===0){return POSITION.DISCONNECTED}sharedParent=aParents[idx-1];siblings=sharedParent.children;aSibling=aParents[idx];bSibling=bParents[idx];if(siblings.indexOf(aSibling)>siblings.indexOf(bSibling)){if(sharedParent===nodeB){return POSITION.FOLLOWING|POSITION.CONTAINED_BY}return POSITION.FOLLOWING}else{if(sharedParent===nodeA){return POSITION.PRECEDING|POSITION.CONTAINS}return POSITION.PRECEDING}};exports.uniqueSort=function(nodes){var idx=nodes.length,node,position;nodes=nodes.slice();while(--idx>-1){node=nodes[idx];position=nodes.indexOf(node);if(position>-1&&position<idx){nodes.splice(idx,1)}}nodes.sort(function(a,b){var relative=comparePos(a,b);if(relative&POSITION.PRECEDING){return-1}else if(relative&POSITION.FOLLOWING){return 1}return 0});return nodes}},{}],15:[function(require,module,exports){var ElementType=require(\"domelementtype\");var isTag=exports.isTag=ElementType.isTag;exports.testElement=function(options,element){for(var key in options){if(!options.hasOwnProperty(key));else if(key===\"tag_name\"){if(!isTag(element)||!options.tag_name(element.name)){return false}}else if(key===\"tag_type\"){if(!options.tag_type(element.type))return false}else if(key===\"tag_contains\"){if(isTag(element)||!options.tag_contains(element.data)){return false}}else if(!element.attribs||!options[key](element.attribs[key])){return false}}return true};var Checks={tag_name:function(name){if(typeof name===\"function\"){return function(elem){return isTag(elem)&&name(elem.name)}}else if(name===\"*\"){return isTag}else{return function(elem){return isTag(elem)&&elem.name===name}}},tag_type:function(type){if(typeof type===\"function\"){return function(elem){return type(elem.type)}}else{return function(elem){return elem.type===type}}},tag_contains:function(data){if(typeof data===\"function\"){return function(elem){return!isTag(elem)&&data(elem.data)}}else{return function(elem){return!isTag(elem)&&elem.data===data}}}};function getAttribCheck(attrib,value){if(typeof value===\"function\"){return function(elem){return elem.attribs&&value(elem.attribs[attrib])}}else{return function(elem){return elem.attribs&&elem.attribs[attrib]===value}}}function combineFuncs(a,b){return function(elem){return a(elem)||b(elem)}}exports.getElements=function(options,element,recurse,limit){var funcs=Object.keys(options).map(function(key){var value=options[key];return key in Checks?Checks[key](value):getAttribCheck(key,value)});return funcs.length===0?[]:this.filter(funcs.reduce(combineFuncs),element,recurse,limit)};exports.getElementById=function(id,element,recurse){if(!Array.isArray(element))element=[element];return this.findOne(getAttribCheck(\"id\",id),element,recurse!==false)};exports.getElementsByTagName=function(name,element,recurse,limit){return this.filter(Checks.tag_name(name),element,recurse,limit)};exports.getElementsByTagType=function(type,element,recurse,limit){return this.filter(Checks.tag_type(type),element,recurse,limit)}},{domelementtype:9}],16:[function(require,module,exports){exports.removeElement=function(elem){if(elem.prev)elem.prev.next=elem.next;if(elem.next)elem.next.prev=elem.prev;if(elem.parent){var childs=elem.parent.children;childs.splice(childs.lastIndexOf(elem),1)}};exports.replaceElement=function(elem,replacement){var prev=replacement.prev=elem.prev;if(prev){prev.next=replacement}var next=replacement.next=elem.next;if(next){next.prev=replacement}var parent=replacement.parent=elem.parent;if(parent){var childs=parent.children;childs[childs.lastIndexOf(elem)]=replacement}};exports.appendChild=function(elem,child){child.parent=elem;if(elem.children.push(child)!==1){var sibling=elem.children[elem.children.length-2];sibling.next=child;child.prev=sibling;child.next=null}};exports.append=function(elem,next){var parent=elem.parent,currNext=elem.next;next.next=currNext;next.prev=elem;elem.next=next;next.parent=parent;if(currNext){currNext.prev=next;if(parent){var childs=parent.children;childs.splice(childs.lastIndexOf(currNext),0,next)}}else if(parent){parent.children.push(next)}};exports.prepend=function(elem,prev){var parent=elem.parent;if(parent){var childs=parent.children;childs.splice(childs.lastIndexOf(elem),0,prev)}if(elem.prev){elem.prev.next=prev}prev.parent=parent;prev.prev=elem.prev;prev.next=elem;elem.prev=prev}},{}],17:[function(require,module,exports){var isTag=require(\"domelementtype\").isTag;module.exports={filter:filter,find:find,findOneChild:findOneChild,findOne:findOne,existsOne:existsOne,findAll:findAll};function filter(test,element,recurse,limit){if(!Array.isArray(element))element=[element];if(typeof limit!==\"number\"||!isFinite(limit)){limit=Infinity}return find(test,element,recurse!==false,limit)}function find(test,elems,recurse,limit){var result=[],childs;for(var i=0,j=elems.length;i<j;i++){if(test(elems[i])){result.push(elems[i]);if(--limit<=0)break}childs=elems[i].children;if(recurse&&childs&&childs.length>0){childs=find(test,childs,recurse,limit);result=result.concat(childs);limit-=childs.length;if(limit<=0)break}}return result}function findOneChild(test,elems){for(var i=0,l=elems.length;i<l;i++){if(test(elems[i]))return elems[i]}return null}function findOne(test,elems){var elem=null;for(var i=0,l=elems.length;i<l&&!elem;i++){if(!isTag(elems[i])){continue}else if(test(elems[i])){elem=elems[i]}else if(elems[i].children.length>0){elem=findOne(test,elems[i].children)}}return elem}function existsOne(test,elems){for(var i=0,l=elems.length;i<l;i++){if(isTag(elems[i])&&(test(elems[i])||elems[i].children.length>0&&existsOne(test,elems[i].children))){return true}}return false}function findAll(test,elems){var result=[];for(var i=0,j=elems.length;i<j;i++){if(!isTag(elems[i]))continue;if(test(elems[i]))result.push(elems[i]);if(elems[i].children.length>0){result=result.concat(findAll(test,elems[i].children))}}return result}},{domelementtype:9}],18:[function(require,module,exports){var ElementType=require(\"domelementtype\"),getOuterHTML=require(\"dom-serializer\"),isTag=ElementType.isTag;module.exports={getInnerHTML:getInnerHTML,getOuterHTML:getOuterHTML,getText:getText};function getInnerHTML(elem,opts){return elem.children?elem.children.map(function(elem){return getOuterHTML(elem,opts)}).join(\"\"):\"\"}function getText(elem){if(Array.isArray(elem))return elem.map(getText).join(\"\");if(isTag(elem)||elem.type===ElementType.CDATA)return getText(elem.children);if(elem.type===ElementType.Text)return elem.data;return\"\"}},{\"dom-serializer\":7,domelementtype:9}],19:[function(require,module,exports){var getChildren=exports.getChildren=function(elem){return elem.children};var getParent=exports.getParent=function(elem){return elem.parent};exports.getSiblings=function(elem){var parent=getParent(elem);return parent?getChildren(parent):[elem]};exports.getAttributeValue=function(elem,name){return elem.attribs&&elem.attribs[name]};exports.hasAttrib=function(elem,name){return!!elem.attribs&&hasOwnProperty.call(elem.attribs,name)};exports.getName=function(elem){return elem.name}},{}],20:[function(require,module,exports){var encode=require(\"./lib/encode.js\"),decode=require(\"./lib/decode.js\");exports.decode=function(data,level){return(!level||level<=0?decode.XML:decode.HTML)(data)};exports.decodeStrict=function(data,level){return(!level||level<=0?decode.XML:decode.HTMLStrict)(data)};exports.encode=function(data,level){return(!level||level<=0?encode.XML:encode.HTML)(data)};exports.encodeXML=encode.XML;exports.encodeHTML4=exports.encodeHTML5=exports.encodeHTML=encode.HTML;exports.decodeXML=exports.decodeXMLStrict=decode.XML;exports.decodeHTML4=exports.decodeHTML5=exports.decodeHTML=decode.HTML;exports.decodeHTML4Strict=exports.decodeHTML5Strict=exports.decodeHTMLStrict=decode.HTMLStrict;exports.escape=encode.escape},{\"./lib/decode.js\":21,\"./lib/encode.js\":23}],21:[function(require,module,exports){var entityMap=require(\"../maps/entities.json\"),legacyMap=require(\"../maps/legacy.json\"),xmlMap=require(\"../maps/xml.json\"),decodeCodePoint=require(\"./decode_codepoint.js\");var decodeXMLStrict=getStrictDecoder(xmlMap),decodeHTMLStrict=getStrictDecoder(entityMap);function getStrictDecoder(map){var keys=Object.keys(map).join(\"|\"),replace=getReplacer(map);keys+=\"|#[xX][\\\\da-fA-F]+|#\\\\d+\";var re=new RegExp(\"&(?:\"+keys+\");\",\"g\");return function(str){return String(str).replace(re,replace)}}var decodeHTML=function(){var legacy=Object.keys(legacyMap).sort(sorter);var keys=Object.keys(entityMap).sort(sorter);for(var i=0,j=0;i<keys.length;i++){if(legacy[j]===keys[i]){keys[i]+=\";?\";j++}else{keys[i]+=\";\"}}var re=new RegExp(\"&(?:\"+keys.join(\"|\")+\"|#[xX][\\\\da-fA-F]+;?|#\\\\d+;?)\",\"g\"),replace=getReplacer(entityMap);function replacer(str){if(str.substr(-1)!==\";\")str+=\";\";return replace(str)}return function(str){return String(str).replace(re,replacer)}}();function sorter(a,b){return a<b?1:-1}function getReplacer(map){return function replace(str){if(str.charAt(1)===\"#\"){if(str.charAt(2)===\"X\"||str.charAt(2)===\"x\"){return decodeCodePoint(parseInt(str.substr(3),16))}return decodeCodePoint(parseInt(str.substr(2),10))}return map[str.slice(1,-1)];\n}}module.exports={XML:decodeXMLStrict,HTML:decodeHTML,HTMLStrict:decodeHTMLStrict}},{\"../maps/entities.json\":25,\"../maps/legacy.json\":26,\"../maps/xml.json\":27,\"./decode_codepoint.js\":22}],22:[function(require,module,exports){var decodeMap=require(\"../maps/decode.json\");module.exports=decodeCodePoint;function decodeCodePoint(codePoint){if(codePoint>=55296&&codePoint<=57343||codePoint>1114111){return\"�\"}if(codePoint in decodeMap){codePoint=decodeMap[codePoint]}var output=\"\";if(codePoint>65535){codePoint-=65536;output+=String.fromCharCode(codePoint>>>10&1023|55296);codePoint=56320|codePoint&1023}output+=String.fromCharCode(codePoint);return output}},{\"../maps/decode.json\":24}],23:[function(require,module,exports){var inverseXML=getInverseObj(require(\"../maps/xml.json\")),xmlReplacer=getInverseReplacer(inverseXML);exports.XML=getInverse(inverseXML,xmlReplacer);var inverseHTML=getInverseObj(require(\"../maps/entities.json\")),htmlReplacer=getInverseReplacer(inverseHTML);exports.HTML=getInverse(inverseHTML,htmlReplacer);function getInverseObj(obj){return Object.keys(obj).sort().reduce(function(inverse,name){inverse[obj[name]]=\"&\"+name+\";\";return inverse},{})}function getInverseReplacer(inverse){var single=[],multiple=[];Object.keys(inverse).forEach(function(k){if(k.length===1){single.push(\"\\\\\"+k)}else{multiple.push(k)}});multiple.unshift(\"[\"+single.join(\"\")+\"]\");return new RegExp(multiple.join(\"|\"),\"g\")}var re_nonASCII=/[^\\0-\\x7F]/g,re_astralSymbols=/[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g;function singleCharReplacer(c){return\"&#x\"+c.charCodeAt(0).toString(16).toUpperCase()+\";\"}function astralReplacer(c){var high=c.charCodeAt(0);var low=c.charCodeAt(1);var codePoint=(high-55296)*1024+low-56320+65536;return\"&#x\"+codePoint.toString(16).toUpperCase()+\";\"}function getInverse(inverse,re){function func(name){return inverse[name]}return function(data){return data.replace(re,func).replace(re_astralSymbols,astralReplacer).replace(re_nonASCII,singleCharReplacer)}}var re_xmlChars=getInverseReplacer(inverseXML);function escapeXML(data){return data.replace(re_xmlChars,singleCharReplacer).replace(re_astralSymbols,astralReplacer).replace(re_nonASCII,singleCharReplacer)}exports.escape=escapeXML},{\"../maps/entities.json\":25,\"../maps/xml.json\":27}],24:[function(require,module,exports){module.exports={0:65533,128:8364,130:8218,131:402,132:8222,133:8230,134:8224,135:8225,136:710,137:8240,138:352,139:8249,140:338,142:381,145:8216,146:8217,147:8220,148:8221,149:8226,150:8211,151:8212,152:732,153:8482,154:353,155:8250,156:339,158:382,159:376}},{}],25:[function(require,module,exports){module.exports={Aacute:\"Á\",aacute:\"á\",Abreve:\"Ă\",abreve:\"ă\",ac:\"∾\",acd:\"∿\",acE:\"∾̳\",Acirc:\"Â\",acirc:\"â\",acute:\"´\",Acy:\"А\",acy:\"а\",AElig:\"Æ\",aelig:\"æ\",af:\"⁡\",Afr:\"𝔄\",afr:\"𝔞\",Agrave:\"À\",agrave:\"à\",alefsym:\"ℵ\",aleph:\"ℵ\",Alpha:\"Α\",alpha:\"α\",Amacr:\"Ā\",amacr:\"ā\",amalg:\"⨿\",amp:\"&\",AMP:\"&\",andand:\"⩕\",And:\"⩓\",and:\"∧\",andd:\"⩜\",andslope:\"⩘\",andv:\"⩚\",ang:\"∠\",ange:\"⦤\",angle:\"∠\",angmsdaa:\"⦨\",angmsdab:\"⦩\",angmsdac:\"⦪\",angmsdad:\"⦫\",angmsdae:\"⦬\",angmsdaf:\"⦭\",angmsdag:\"⦮\",angmsdah:\"⦯\",angmsd:\"∡\",angrt:\"∟\",angrtvb:\"⊾\",angrtvbd:\"⦝\",angsph:\"∢\",angst:\"Å\",angzarr:\"⍼\",Aogon:\"Ą\",aogon:\"ą\",Aopf:\"𝔸\",aopf:\"𝕒\",apacir:\"⩯\",ap:\"≈\",apE:\"⩰\",ape:\"≊\",apid:\"≋\",apos:\"'\",ApplyFunction:\"⁡\",approx:\"≈\",approxeq:\"≊\",Aring:\"Å\",aring:\"å\",Ascr:\"𝒜\",ascr:\"𝒶\",Assign:\"≔\",ast:\"*\",asymp:\"≈\",asympeq:\"≍\",Atilde:\"Ã\",atilde:\"ã\",Auml:\"Ä\",auml:\"ä\",awconint:\"∳\",awint:\"⨑\",backcong:\"≌\",backepsilon:\"϶\",backprime:\"‵\",backsim:\"∽\",backsimeq:\"⋍\",Backslash:\"∖\",Barv:\"⫧\",barvee:\"⊽\",barwed:\"⌅\",Barwed:\"⌆\",barwedge:\"⌅\",bbrk:\"⎵\",bbrktbrk:\"⎶\",bcong:\"≌\",Bcy:\"Б\",bcy:\"б\",bdquo:\"„\",becaus:\"∵\",because:\"∵\",Because:\"∵\",bemptyv:\"⦰\",bepsi:\"϶\",bernou:\"ℬ\",Bernoullis:\"ℬ\",Beta:\"Β\",beta:\"β\",beth:\"ℶ\",between:\"≬\",Bfr:\"𝔅\",bfr:\"𝔟\",bigcap:\"⋂\",bigcirc:\"◯\",bigcup:\"⋃\",bigodot:\"⨀\",bigoplus:\"⨁\",bigotimes:\"⨂\",bigsqcup:\"⨆\",bigstar:\"★\",bigtriangledown:\"▽\",bigtriangleup:\"△\",biguplus:\"⨄\",bigvee:\"⋁\",bigwedge:\"⋀\",bkarow:\"⤍\",blacklozenge:\"⧫\",blacksquare:\"▪\",blacktriangle:\"▴\",blacktriangledown:\"▾\",blacktriangleleft:\"◂\",blacktriangleright:\"▸\",blank:\"␣\",blk12:\"▒\",blk14:\"░\",blk34:\"▓\",block:\"█\",bne:\"=⃥\",bnequiv:\"≡⃥\",bNot:\"⫭\",bnot:\"⌐\",Bopf:\"𝔹\",bopf:\"𝕓\",bot:\"⊥\",bottom:\"⊥\",bowtie:\"⋈\",boxbox:\"⧉\",boxdl:\"┐\",boxdL:\"╕\",boxDl:\"╖\",boxDL:\"╗\",boxdr:\"┌\",boxdR:\"╒\",boxDr:\"╓\",boxDR:\"╔\",boxh:\"─\",boxH:\"═\",boxhd:\"┬\",boxHd:\"╤\",boxhD:\"╥\",boxHD:\"╦\",boxhu:\"┴\",boxHu:\"╧\",boxhU:\"╨\",boxHU:\"╩\",boxminus:\"⊟\",boxplus:\"⊞\",boxtimes:\"⊠\",boxul:\"┘\",boxuL:\"╛\",boxUl:\"╜\",boxUL:\"╝\",boxur:\"└\",boxuR:\"╘\",boxUr:\"╙\",boxUR:\"╚\",boxv:\"│\",boxV:\"║\",boxvh:\"┼\",boxvH:\"╪\",boxVh:\"╫\",boxVH:\"╬\",boxvl:\"┤\",boxvL:\"╡\",boxVl:\"╢\",boxVL:\"╣\",boxvr:\"├\",boxvR:\"╞\",boxVr:\"╟\",boxVR:\"╠\",bprime:\"‵\",breve:\"˘\",Breve:\"˘\",brvbar:\"¦\",bscr:\"𝒷\",Bscr:\"ℬ\",bsemi:\"⁏\",bsim:\"∽\",bsime:\"⋍\",bsolb:\"⧅\",bsol:\"\\\\\",bsolhsub:\"⟈\",bull:\"•\",bullet:\"•\",bump:\"≎\",bumpE:\"⪮\",bumpe:\"≏\",Bumpeq:\"≎\",bumpeq:\"≏\",Cacute:\"Ć\",cacute:\"ć\",capand:\"⩄\",capbrcup:\"⩉\",capcap:\"⩋\",cap:\"∩\",Cap:\"⋒\",capcup:\"⩇\",capdot:\"⩀\",CapitalDifferentialD:\"ⅅ\",caps:\"∩︀\",caret:\"⁁\",caron:\"ˇ\",Cayleys:\"ℭ\",ccaps:\"⩍\",Ccaron:\"Č\",ccaron:\"č\",Ccedil:\"Ç\",ccedil:\"ç\",Ccirc:\"Ĉ\",ccirc:\"ĉ\",Cconint:\"∰\",ccups:\"⩌\",ccupssm:\"⩐\",Cdot:\"Ċ\",cdot:\"ċ\",cedil:\"¸\",Cedilla:\"¸\",cemptyv:\"⦲\",cent:\"¢\",centerdot:\"·\",CenterDot:\"·\",cfr:\"𝔠\",Cfr:\"ℭ\",CHcy:\"Ч\",chcy:\"ч\",check:\"✓\",checkmark:\"✓\",Chi:\"Χ\",chi:\"χ\",circ:\"ˆ\",circeq:\"≗\",circlearrowleft:\"↺\",circlearrowright:\"↻\",circledast:\"⊛\",circledcirc:\"⊚\",circleddash:\"⊝\",CircleDot:\"⊙\",circledR:\"®\",circledS:\"Ⓢ\",CircleMinus:\"⊖\",CirclePlus:\"⊕\",CircleTimes:\"⊗\",cir:\"○\",cirE:\"⧃\",cire:\"≗\",cirfnint:\"⨐\",cirmid:\"⫯\",cirscir:\"⧂\",ClockwiseContourIntegral:\"∲\",CloseCurlyDoubleQuote:\"”\",CloseCurlyQuote:\"’\",clubs:\"♣\",clubsuit:\"♣\",colon:\":\",Colon:\"∷\",Colone:\"⩴\",colone:\"≔\",coloneq:\"≔\",comma:\",\",commat:\"@\",comp:\"∁\",compfn:\"∘\",complement:\"∁\",complexes:\"ℂ\",cong:\"≅\",congdot:\"⩭\",Congruent:\"≡\",conint:\"∮\",Conint:\"∯\",ContourIntegral:\"∮\",copf:\"𝕔\",Copf:\"ℂ\",coprod:\"∐\",Coproduct:\"∐\",copy:\"©\",COPY:\"©\",copysr:\"℗\",CounterClockwiseContourIntegral:\"∳\",crarr:\"↵\",cross:\"✗\",Cross:\"⨯\",Cscr:\"𝒞\",cscr:\"𝒸\",csub:\"⫏\",csube:\"⫑\",csup:\"⫐\",csupe:\"⫒\",ctdot:\"⋯\",cudarrl:\"⤸\",cudarrr:\"⤵\",cuepr:\"⋞\",cuesc:\"⋟\",cularr:\"↶\",cularrp:\"⤽\",cupbrcap:\"⩈\",cupcap:\"⩆\",CupCap:\"≍\",cup:\"∪\",Cup:\"⋓\",cupcup:\"⩊\",cupdot:\"⊍\",cupor:\"⩅\",cups:\"∪︀\",curarr:\"↷\",curarrm:\"⤼\",curlyeqprec:\"⋞\",curlyeqsucc:\"⋟\",curlyvee:\"⋎\",curlywedge:\"⋏\",curren:\"¤\",curvearrowleft:\"↶\",curvearrowright:\"↷\",cuvee:\"⋎\",cuwed:\"⋏\",cwconint:\"∲\",cwint:\"∱\",cylcty:\"⌭\",dagger:\"†\",Dagger:\"‡\",daleth:\"ℸ\",darr:\"↓\",Darr:\"↡\",dArr:\"⇓\",dash:\"‐\",Dashv:\"⫤\",dashv:\"⊣\",dbkarow:\"⤏\",dblac:\"˝\",Dcaron:\"Ď\",dcaron:\"ď\",Dcy:\"Д\",dcy:\"д\",ddagger:\"‡\",ddarr:\"⇊\",DD:\"ⅅ\",dd:\"ⅆ\",DDotrahd:\"⤑\",ddotseq:\"⩷\",deg:\"°\",Del:\"∇\",Delta:\"Δ\",delta:\"δ\",demptyv:\"⦱\",dfisht:\"⥿\",Dfr:\"𝔇\",dfr:\"𝔡\",dHar:\"⥥\",dharl:\"⇃\",dharr:\"⇂\",DiacriticalAcute:\"´\",DiacriticalDot:\"˙\",DiacriticalDoubleAcute:\"˝\",DiacriticalGrave:\"`\",DiacriticalTilde:\"˜\",diam:\"⋄\",diamond:\"⋄\",Diamond:\"⋄\",diamondsuit:\"♦\",diams:\"♦\",die:\"¨\",DifferentialD:\"ⅆ\",digamma:\"ϝ\",disin:\"⋲\",div:\"÷\",divide:\"÷\",divideontimes:\"⋇\",divonx:\"⋇\",DJcy:\"Ђ\",djcy:\"ђ\",dlcorn:\"⌞\",dlcrop:\"⌍\",dollar:\"$\",Dopf:\"𝔻\",dopf:\"𝕕\",Dot:\"¨\",dot:\"˙\",DotDot:\"⃜\",doteq:\"≐\",doteqdot:\"≑\",DotEqual:\"≐\",dotminus:\"∸\",dotplus:\"∔\",dotsquare:\"⊡\",doublebarwedge:\"⌆\",DoubleContourIntegral:\"∯\",DoubleDot:\"¨\",DoubleDownArrow:\"⇓\",DoubleLeftArrow:\"⇐\",DoubleLeftRightArrow:\"⇔\",DoubleLeftTee:\"⫤\",DoubleLongLeftArrow:\"⟸\",DoubleLongLeftRightArrow:\"⟺\",DoubleLongRightArrow:\"⟹\",DoubleRightArrow:\"⇒\",DoubleRightTee:\"⊨\",DoubleUpArrow:\"⇑\",DoubleUpDownArrow:\"⇕\",DoubleVerticalBar:\"∥\",DownArrowBar:\"⤓\",downarrow:\"↓\",DownArrow:\"↓\",Downarrow:\"⇓\",DownArrowUpArrow:\"⇵\",DownBreve:\"̑\",downdownarrows:\"⇊\",downharpoonleft:\"⇃\",downharpoonright:\"⇂\",DownLeftRightVector:\"⥐\",DownLeftTeeVector:\"⥞\",DownLeftVectorBar:\"⥖\",DownLeftVector:\"↽\",DownRightTeeVector:\"⥟\",DownRightVectorBar:\"⥗\",DownRightVector:\"⇁\",DownTeeArrow:\"↧\",DownTee:\"⊤\",drbkarow:\"⤐\",drcorn:\"⌟\",drcrop:\"⌌\",Dscr:\"𝒟\",dscr:\"𝒹\",DScy:\"Ѕ\",dscy:\"ѕ\",dsol:\"⧶\",Dstrok:\"Đ\",dstrok:\"đ\",dtdot:\"⋱\",dtri:\"▿\",dtrif:\"▾\",duarr:\"⇵\",duhar:\"⥯\",dwangle:\"⦦\",DZcy:\"Џ\",dzcy:\"џ\",dzigrarr:\"⟿\",Eacute:\"É\",eacute:\"é\",easter:\"⩮\",Ecaron:\"Ě\",ecaron:\"ě\",Ecirc:\"Ê\",ecirc:\"ê\",ecir:\"≖\",ecolon:\"≕\",Ecy:\"Э\",ecy:\"э\",eDDot:\"⩷\",Edot:\"Ė\",edot:\"ė\",eDot:\"≑\",ee:\"ⅇ\",efDot:\"≒\",Efr:\"𝔈\",efr:\"𝔢\",eg:\"⪚\",Egrave:\"È\",egrave:\"è\",egs:\"⪖\",egsdot:\"⪘\",el:\"⪙\",Element:\"∈\",elinters:\"⏧\",ell:\"ℓ\",els:\"⪕\",elsdot:\"⪗\",Emacr:\"Ē\",emacr:\"ē\",empty:\"∅\",emptyset:\"∅\",EmptySmallSquare:\"◻\",emptyv:\"∅\",EmptyVerySmallSquare:\"▫\",emsp13:\" \",emsp14:\" \",emsp:\" \",ENG:\"Ŋ\",eng:\"ŋ\",ensp:\" \",Eogon:\"Ę\",eogon:\"ę\",Eopf:\"𝔼\",eopf:\"𝕖\",epar:\"⋕\",eparsl:\"⧣\",eplus:\"⩱\",epsi:\"ε\",Epsilon:\"Ε\",epsilon:\"ε\",epsiv:\"ϵ\",eqcirc:\"≖\",eqcolon:\"≕\",eqsim:\"≂\",eqslantgtr:\"⪖\",eqslantless:\"⪕\",Equal:\"⩵\",equals:\"=\",EqualTilde:\"≂\",equest:\"≟\",Equilibrium:\"⇌\",equiv:\"≡\",equivDD:\"⩸\",eqvparsl:\"⧥\",erarr:\"⥱\",erDot:\"≓\",escr:\"ℯ\",Escr:\"ℰ\",esdot:\"≐\",Esim:\"⩳\",esim:\"≂\",Eta:\"Η\",eta:\"η\",ETH:\"Ð\",eth:\"ð\",Euml:\"Ë\",euml:\"ë\",euro:\"€\",excl:\"!\",exist:\"∃\",Exists:\"∃\",expectation:\"ℰ\",exponentiale:\"ⅇ\",ExponentialE:\"ⅇ\",fallingdotseq:\"≒\",Fcy:\"Ф\",fcy:\"ф\",female:\"♀\",ffilig:\"ﬃ\",fflig:\"ﬀ\",ffllig:\"ﬄ\",Ffr:\"𝔉\",ffr:\"𝔣\",filig:\"ﬁ\",FilledSmallSquare:\"◼\",FilledVerySmallSquare:\"▪\",fjlig:\"fj\",flat:\"♭\",fllig:\"ﬂ\",fltns:\"▱\",fnof:\"ƒ\",Fopf:\"𝔽\",fopf:\"𝕗\",forall:\"∀\",ForAll:\"∀\",fork:\"⋔\",forkv:\"⫙\",Fouriertrf:\"ℱ\",fpartint:\"⨍\",frac12:\"½\",frac13:\"⅓\",frac14:\"¼\",frac15:\"⅕\",frac16:\"⅙\",frac18:\"⅛\",frac23:\"⅔\",frac25:\"⅖\",frac34:\"¾\",frac35:\"⅗\",frac38:\"⅜\",frac45:\"⅘\",frac56:\"⅚\",frac58:\"⅝\",frac78:\"⅞\",frasl:\"⁄\",frown:\"⌢\",fscr:\"𝒻\",Fscr:\"ℱ\",gacute:\"ǵ\",Gamma:\"Γ\",gamma:\"γ\",Gammad:\"Ϝ\",gammad:\"ϝ\",gap:\"⪆\",Gbreve:\"Ğ\",gbreve:\"ğ\",Gcedil:\"Ģ\",Gcirc:\"Ĝ\",gcirc:\"ĝ\",Gcy:\"Г\",gcy:\"г\",Gdot:\"Ġ\",gdot:\"ġ\",ge:\"≥\",gE:\"≧\",gEl:\"⪌\",gel:\"⋛\",geq:\"≥\",geqq:\"≧\",geqslant:\"⩾\",gescc:\"⪩\",ges:\"⩾\",gesdot:\"⪀\",gesdoto:\"⪂\",gesdotol:\"⪄\",gesl:\"⋛︀\",gesles:\"⪔\",Gfr:\"𝔊\",gfr:\"𝔤\",gg:\"≫\",Gg:\"⋙\",ggg:\"⋙\",gimel:\"ℷ\",GJcy:\"Ѓ\",gjcy:\"ѓ\",gla:\"⪥\",gl:\"≷\",glE:\"⪒\",glj:\"⪤\",gnap:\"⪊\",gnapprox:\"⪊\",gne:\"⪈\",gnE:\"≩\",gneq:\"⪈\",gneqq:\"≩\",gnsim:\"⋧\",Gopf:\"𝔾\",gopf:\"𝕘\",grave:\"`\",GreaterEqual:\"≥\",GreaterEqualLess:\"⋛\",GreaterFullEqual:\"≧\",GreaterGreater:\"⪢\",GreaterLess:\"≷\",GreaterSlantEqual:\"⩾\",GreaterTilde:\"≳\",Gscr:\"𝒢\",gscr:\"ℊ\",gsim:\"≳\",gsime:\"⪎\",gsiml:\"⪐\",gtcc:\"⪧\",gtcir:\"⩺\",gt:\">\",GT:\">\",Gt:\"≫\",gtdot:\"⋗\",gtlPar:\"⦕\",gtquest:\"⩼\",gtrapprox:\"⪆\",gtrarr:\"⥸\",gtrdot:\"⋗\",gtreqless:\"⋛\",gtreqqless:\"⪌\",gtrless:\"≷\",gtrsim:\"≳\",gvertneqq:\"≩︀\",gvnE:\"≩︀\",Hacek:\"ˇ\",hairsp:\" \",half:\"½\",hamilt:\"ℋ\",HARDcy:\"Ъ\",hardcy:\"ъ\",harrcir:\"⥈\",harr:\"↔\",hArr:\"⇔\",harrw:\"↭\",Hat:\"^\",hbar:\"ℏ\",Hcirc:\"Ĥ\",hcirc:\"ĥ\",hearts:\"♥\",heartsuit:\"♥\",hellip:\"…\",hercon:\"⊹\",hfr:\"𝔥\",Hfr:\"ℌ\",HilbertSpace:\"ℋ\",hksearow:\"⤥\",hkswarow:\"⤦\",hoarr:\"⇿\",homtht:\"∻\",hookleftarrow:\"↩\",hookrightarrow:\"↪\",hopf:\"𝕙\",Hopf:\"ℍ\",horbar:\"―\",HorizontalLine:\"─\",hscr:\"𝒽\",Hscr:\"ℋ\",hslash:\"ℏ\",Hstrok:\"Ħ\",hstrok:\"ħ\",HumpDownHump:\"≎\",HumpEqual:\"≏\",hybull:\"⁃\",hyphen:\"‐\",Iacute:\"Í\",iacute:\"í\",ic:\"⁣\",Icirc:\"Î\",icirc:\"î\",Icy:\"И\",icy:\"и\",Idot:\"İ\",IEcy:\"Е\",iecy:\"е\",iexcl:\"¡\",iff:\"⇔\",ifr:\"𝔦\",Ifr:\"ℑ\",Igrave:\"Ì\",igrave:\"ì\",ii:\"ⅈ\",iiiint:\"⨌\",iiint:\"∭\",iinfin:\"⧜\",iiota:\"℩\",IJlig:\"Ĳ\",ijlig:\"ĳ\",Imacr:\"Ī\",imacr:\"ī\",image:\"ℑ\",ImaginaryI:\"ⅈ\",imagline:\"ℐ\",imagpart:\"ℑ\",imath:\"ı\",Im:\"ℑ\",imof:\"⊷\",imped:\"Ƶ\",Implies:\"⇒\",incare:\"℅\",in:\"∈\",infin:\"∞\",infintie:\"⧝\",inodot:\"ı\",intcal:\"⊺\",int:\"∫\",Int:\"∬\",integers:\"ℤ\",Integral:\"∫\",intercal:\"⊺\",Intersection:\"⋂\",intlarhk:\"⨗\",intprod:\"⨼\",InvisibleComma:\"⁣\",InvisibleTimes:\"⁢\",IOcy:\"Ё\",iocy:\"ё\",Iogon:\"Į\",iogon:\"į\",Iopf:\"𝕀\",iopf:\"𝕚\",Iota:\"Ι\",iota:\"ι\",iprod:\"⨼\",iquest:\"¿\",iscr:\"𝒾\",Iscr:\"ℐ\",isin:\"∈\",isindot:\"⋵\",isinE:\"⋹\",isins:\"⋴\",isinsv:\"⋳\",isinv:\"∈\",it:\"⁢\",Itilde:\"Ĩ\",itilde:\"ĩ\",Iukcy:\"І\",iukcy:\"і\",Iuml:\"Ï\",iuml:\"ï\",Jcirc:\"Ĵ\",jcirc:\"ĵ\",Jcy:\"Й\",jcy:\"й\",Jfr:\"𝔍\",jfr:\"𝔧\",jmath:\"ȷ\",Jopf:\"𝕁\",jopf:\"𝕛\",Jscr:\"𝒥\",jscr:\"𝒿\",Jsercy:\"Ј\",jsercy:\"ј\",Jukcy:\"Є\",jukcy:\"є\",Kappa:\"Κ\",kappa:\"κ\",kappav:\"ϰ\",Kcedil:\"Ķ\",kcedil:\"ķ\",Kcy:\"К\",kcy:\"к\",Kfr:\"𝔎\",kfr:\"𝔨\",kgreen:\"ĸ\",KHcy:\"Х\",khcy:\"х\",KJcy:\"Ќ\",kjcy:\"ќ\",Kopf:\"𝕂\",kopf:\"𝕜\",Kscr:\"𝒦\",kscr:\"𝓀\",lAarr:\"⇚\",Lacute:\"Ĺ\",lacute:\"ĺ\",laemptyv:\"⦴\",lagran:\"ℒ\",Lambda:\"Λ\",lambda:\"λ\",lang:\"⟨\",Lang:\"⟪\",langd:\"⦑\",langle:\"⟨\",lap:\"⪅\",Laplacetrf:\"ℒ\",laquo:\"«\",larrb:\"⇤\",larrbfs:\"⤟\",larr:\"←\",Larr:\"↞\",lArr:\"⇐\",larrfs:\"⤝\",larrhk:\"↩\",larrlp:\"↫\",larrpl:\"⤹\",larrsim:\"⥳\",larrtl:\"↢\",latail:\"⤙\",lAtail:\"⤛\",lat:\"⪫\",late:\"⪭\",lates:\"⪭︀\",lbarr:\"⤌\",lBarr:\"⤎\",lbbrk:\"❲\",lbrace:\"{\",lbrack:\"[\",lbrke:\"⦋\",lbrksld:\"⦏\",lbrkslu:\"⦍\",Lcaron:\"Ľ\",lcaron:\"ľ\",Lcedil:\"Ļ\",lcedil:\"ļ\",lceil:\"⌈\",lcub:\"{\",Lcy:\"Л\",lcy:\"л\",ldca:\"⤶\",ldquo:\"“\",ldquor:\"„\",ldrdhar:\"⥧\",ldrushar:\"⥋\",ldsh:\"↲\",le:\"≤\",lE:\"≦\",LeftAngleBracket:\"⟨\",LeftArrowBar:\"⇤\",leftarrow:\"←\",LeftArrow:\"←\",Leftarrow:\"⇐\",LeftArrowRightArrow:\"⇆\",leftarrowtail:\"↢\",LeftCeiling:\"⌈\",LeftDoubleBracket:\"⟦\",LeftDownTeeVector:\"⥡\",LeftDownVectorBar:\"⥙\",LeftDownVector:\"⇃\",LeftFloor:\"⌊\",leftharpoondown:\"↽\",leftharpoonup:\"↼\",leftleftarrows:\"⇇\",leftrightarrow:\"↔\",LeftRightArrow:\"↔\",Leftrightarrow:\"⇔\",leftrightarrows:\"⇆\",leftrightharpoons:\"⇋\",leftrightsquigarrow:\"↭\",LeftRightVector:\"⥎\",LeftTeeArrow:\"↤\",LeftTee:\"⊣\",LeftTeeVector:\"⥚\",leftthreetimes:\"⋋\",LeftTriangleBar:\"⧏\",LeftTriangle:\"⊲\",LeftTriangleEqual:\"⊴\",LeftUpDownVector:\"⥑\",LeftUpTeeVector:\"⥠\",LeftUpVectorBar:\"⥘\",LeftUpVector:\"↿\",LeftVectorBar:\"⥒\",LeftVector:\"↼\",lEg:\"⪋\",leg:\"⋚\",leq:\"≤\",leqq:\"≦\",leqslant:\"⩽\",lescc:\"⪨\",les:\"⩽\",lesdot:\"⩿\",lesdoto:\"⪁\",lesdotor:\"⪃\",lesg:\"⋚︀\",lesges:\"⪓\",lessapprox:\"⪅\",lessdot:\"⋖\",lesseqgtr:\"⋚\",lesseqqgtr:\"⪋\",LessEqualGreater:\"⋚\",LessFullEqual:\"≦\",LessGreater:\"≶\",lessgtr:\"≶\",LessLess:\"⪡\",lesssim:\"≲\",LessSlantEqual:\"⩽\",LessTilde:\"≲\",lfisht:\"⥼\",lfloor:\"⌊\",Lfr:\"𝔏\",lfr:\"𝔩\",lg:\"≶\",lgE:\"⪑\",lHar:\"⥢\",lhard:\"↽\",lharu:\"↼\",lharul:\"⥪\",lhblk:\"▄\",LJcy:\"Љ\",ljcy:\"љ\",llarr:\"⇇\",ll:\"≪\",Ll:\"⋘\",llcorner:\"⌞\",Lleftarrow:\"⇚\",llhard:\"⥫\",lltri:\"◺\",Lmidot:\"Ŀ\",lmidot:\"ŀ\",lmoustache:\"⎰\",lmoust:\"⎰\",lnap:\"⪉\",lnapprox:\"⪉\",lne:\"⪇\",lnE:\"≨\",lneq:\"⪇\",lneqq:\"≨\",lnsim:\"⋦\",loang:\"⟬\",loarr:\"⇽\",lobrk:\"⟦\",longleftarrow:\"⟵\",LongLeftArrow:\"⟵\",Longleftarrow:\"⟸\",longleftrightarrow:\"⟷\",LongLeftRightArrow:\"⟷\",Longleftrightarrow:\"⟺\",longmapsto:\"⟼\",longrightarrow:\"⟶\",LongRightArrow:\"⟶\",Longrightarrow:\"⟹\",looparrowleft:\"↫\",looparrowright:\"↬\",lopar:\"⦅\",Lopf:\"𝕃\",lopf:\"𝕝\",loplus:\"⨭\",lotimes:\"⨴\",lowast:\"∗\",lowbar:\"_\",LowerLeftArrow:\"↙\",LowerRightArrow:\"↘\",loz:\"◊\",lozenge:\"◊\",lozf:\"⧫\",lpar:\"(\",lparlt:\"⦓\",lrarr:\"⇆\",lrcorner:\"⌟\",lrhar:\"⇋\",lrhard:\"⥭\",lrm:\"‎\",lrtri:\"⊿\",lsaquo:\"‹\",lscr:\"𝓁\",Lscr:\"ℒ\",lsh:\"↰\",Lsh:\"↰\",lsim:\"≲\",lsime:\"⪍\",lsimg:\"⪏\",lsqb:\"[\",lsquo:\"‘\",lsquor:\"‚\",Lstrok:\"Ł\",lstrok:\"ł\",ltcc:\"⪦\",ltcir:\"⩹\",lt:\"<\",LT:\"<\",Lt:\"≪\",ltdot:\"⋖\",lthree:\"⋋\",ltimes:\"⋉\",ltlarr:\"⥶\",ltquest:\"⩻\",ltri:\"◃\",ltrie:\"⊴\",ltrif:\"◂\",ltrPar:\"⦖\",lurdshar:\"⥊\",luruhar:\"⥦\",lvertneqq:\"≨︀\",lvnE:\"≨︀\",macr:\"¯\",male:\"♂\",malt:\"✠\",maltese:\"✠\",Map:\"⤅\",map:\"↦\",mapsto:\"↦\",mapstodown:\"↧\",mapstoleft:\"↤\",mapstoup:\"↥\",marker:\"▮\",mcomma:\"⨩\",Mcy:\"М\",mcy:\"м\",mdash:\"—\",mDDot:\"∺\",measuredangle:\"∡\",MediumSpace:\" \",Mellintrf:\"ℳ\",Mfr:\"𝔐\",mfr:\"𝔪\",mho:\"℧\",micro:\"µ\",midast:\"*\",midcir:\"⫰\",mid:\"∣\",middot:\"·\",minusb:\"⊟\",minus:\"−\",minusd:\"∸\",minusdu:\"⨪\",MinusPlus:\"∓\",mlcp:\"⫛\",mldr:\"…\",mnplus:\"∓\",models:\"⊧\",Mopf:\"𝕄\",mopf:\"𝕞\",mp:\"∓\",mscr:\"𝓂\",Mscr:\"ℳ\",mstpos:\"∾\",Mu:\"Μ\",mu:\"μ\",multimap:\"⊸\",mumap:\"⊸\",nabla:\"∇\",Nacute:\"Ń\",nacute:\"ń\",nang:\"∠⃒\",nap:\"≉\",napE:\"⩰̸\",napid:\"≋̸\",napos:\"ŉ\",napprox:\"≉\",natural:\"♮\",naturals:\"ℕ\",natur:\"♮\",nbsp:\" \",nbump:\"≎̸\",nbumpe:\"≏̸\",ncap:\"⩃\",Ncaron:\"Ň\",ncaron:\"ň\",Ncedil:\"Ņ\",ncedil:\"ņ\",ncong:\"≇\",ncongdot:\"⩭̸\",ncup:\"⩂\",Ncy:\"Н\",ncy:\"н\",ndash:\"–\",nearhk:\"⤤\",nearr:\"↗\",neArr:\"⇗\",nearrow:\"↗\",ne:\"≠\",nedot:\"≐̸\",NegativeMediumSpace:\"​\",NegativeThickSpace:\"​\",NegativeThinSpace:\"​\",NegativeVeryThinSpace:\"​\",nequiv:\"≢\",nesear:\"⤨\",nesim:\"≂̸\",NestedGreaterGreater:\"≫\",NestedLessLess:\"≪\",NewLine:\"\\n\",nexist:\"∄\",nexists:\"∄\",Nfr:\"𝔑\",nfr:\"𝔫\",ngE:\"≧̸\",nge:\"≱\",ngeq:\"≱\",ngeqq:\"≧̸\",ngeqslant:\"⩾̸\",nges:\"⩾̸\",nGg:\"⋙̸\",ngsim:\"≵\",nGt:\"≫⃒\",ngt:\"≯\",ngtr:\"≯\",nGtv:\"≫̸\",nharr:\"↮\",nhArr:\"⇎\",nhpar:\"⫲\",ni:\"∋\",nis:\"⋼\",nisd:\"⋺\",niv:\"∋\",NJcy:\"Њ\",njcy:\"њ\",nlarr:\"↚\",nlArr:\"⇍\",nldr:\"‥\",nlE:\"≦̸\",nle:\"≰\",nleftarrow:\"↚\",nLeftarrow:\"⇍\",nleftrightarrow:\"↮\",nLeftrightarrow:\"⇎\",nleq:\"≰\",nleqq:\"≦̸\",nleqslant:\"⩽̸\",nles:\"⩽̸\",nless:\"≮\",nLl:\"⋘̸\",nlsim:\"≴\",nLt:\"≪⃒\",nlt:\"≮\",nltri:\"⋪\",nltrie:\"⋬\",nLtv:\"≪̸\",nmid:\"∤\",NoBreak:\"⁠\",NonBreakingSpace:\" \",nopf:\"𝕟\",Nopf:\"ℕ\",Not:\"⫬\",not:\"¬\",NotCongruent:\"≢\",NotCupCap:\"≭\",NotDoubleVerticalBar:\"∦\",NotElement:\"∉\",NotEqual:\"≠\",NotEqualTilde:\"≂̸\",NotExists:\"∄\",NotGreater:\"≯\",NotGreaterEqual:\"≱\",NotGreaterFullEqual:\"≧̸\",NotGreaterGreater:\"≫̸\",NotGreaterLess:\"≹\",NotGreaterSlantEqual:\"⩾̸\",NotGreaterTilde:\"≵\",NotHumpDownHump:\"≎̸\",NotHumpEqual:\"≏̸\",notin:\"∉\",notindot:\"⋵̸\",notinE:\"⋹̸\",notinva:\"∉\",notinvb:\"⋷\",notinvc:\"⋶\",NotLeftTriangleBar:\"⧏̸\",NotLeftTriangle:\"⋪\",NotLeftTriangleEqual:\"⋬\",NotLess:\"≮\",NotLessEqual:\"≰\",NotLessGreater:\"≸\",NotLessLess:\"≪̸\",NotLessSlantEqual:\"⩽̸\",NotLessTilde:\"≴\",NotNestedGreaterGreater:\"⪢̸\",NotNestedLessLess:\"⪡̸\",notni:\"∌\",notniva:\"∌\",notnivb:\"⋾\",notnivc:\"⋽\",NotPrecedes:\"⊀\",NotPrecedesEqual:\"⪯̸\",NotPrecedesSlantEqual:\"⋠\",NotReverseElement:\"∌\",NotRightTriangleBar:\"⧐̸\",NotRightTriangle:\"⋫\",NotRightTriangleEqual:\"⋭\",NotSquareSubset:\"⊏̸\",NotSquareSubsetEqual:\"⋢\",NotSquareSuperset:\"⊐̸\",NotSquareSupersetEqual:\"⋣\",NotSubset:\"⊂⃒\",NotSubsetEqual:\"⊈\",NotSucceeds:\"⊁\",NotSucceedsEqual:\"⪰̸\",NotSucceedsSlantEqual:\"⋡\",NotSucceedsTilde:\"≿̸\",NotSuperset:\"⊃⃒\",NotSupersetEqual:\"⊉\",NotTilde:\"≁\",NotTildeEqual:\"≄\",NotTildeFullEqual:\"≇\",NotTildeTilde:\"≉\",NotVerticalBar:\"∤\",nparallel:\"∦\",npar:\"∦\",nparsl:\"⫽⃥\",npart:\"∂̸\",npolint:\"⨔\",npr:\"⊀\",nprcue:\"⋠\",nprec:\"⊀\",npreceq:\"⪯̸\",npre:\"⪯̸\",nrarrc:\"⤳̸\",nrarr:\"↛\",nrArr:\"⇏\",nrarrw:\"↝̸\",nrightarrow:\"↛\",nRightarrow:\"⇏\",nrtri:\"⋫\",nrtrie:\"⋭\",nsc:\"⊁\",nsccue:\"⋡\",nsce:\"⪰̸\",Nscr:\"𝒩\",nscr:\"𝓃\",nshortmid:\"∤\",nshortparallel:\"∦\",nsim:\"≁\",nsime:\"≄\",nsimeq:\"≄\",nsmid:\"∤\",nspar:\"∦\",nsqsube:\"⋢\",nsqsupe:\"⋣\",nsub:\"⊄\",nsubE:\"⫅̸\",nsube:\"⊈\",nsubset:\"⊂⃒\",nsubseteq:\"⊈\",nsubseteqq:\"⫅̸\",nsucc:\"⊁\",nsucceq:\"⪰̸\",nsup:\"⊅\",nsupE:\"⫆̸\",nsupe:\"⊉\",nsupset:\"⊃⃒\",nsupseteq:\"⊉\",nsupseteqq:\"⫆̸\",ntgl:\"≹\",Ntilde:\"Ñ\",ntilde:\"ñ\",ntlg:\"≸\",ntriangleleft:\"⋪\",ntrianglelefteq:\"⋬\",ntriangleright:\"⋫\",ntrianglerighteq:\"⋭\",Nu:\"Ν\",nu:\"ν\",num:\"#\",numero:\"№\",numsp:\" \",nvap:\"≍⃒\",nvdash:\"⊬\",nvDash:\"⊭\",nVdash:\"⊮\",nVDash:\"⊯\",nvge:\"≥⃒\",nvgt:\">⃒\",nvHarr:\"⤄\",nvinfin:\"⧞\",nvlArr:\"⤂\",nvle:\"≤⃒\",nvlt:\"<⃒\",nvltrie:\"⊴⃒\",nvrArr:\"⤃\",nvrtrie:\"⊵⃒\",nvsim:\"∼⃒\",nwarhk:\"⤣\",nwarr:\"↖\",nwArr:\"⇖\",nwarrow:\"↖\",nwnear:\"⤧\",Oacute:\"Ó\",oacute:\"ó\",oast:\"⊛\",Ocirc:\"Ô\",ocirc:\"ô\",ocir:\"⊚\",Ocy:\"О\",ocy:\"о\",odash:\"⊝\",Odblac:\"Ő\",odblac:\"ő\",odiv:\"⨸\",odot:\"⊙\",odsold:\"⦼\",OElig:\"Œ\",oelig:\"œ\",ofcir:\"⦿\",Ofr:\"𝔒\",ofr:\"𝔬\",ogon:\"˛\",Ograve:\"Ò\",ograve:\"ò\",ogt:\"⧁\",ohbar:\"⦵\",ohm:\"Ω\",oint:\"∮\",olarr:\"↺\",olcir:\"⦾\",olcross:\"⦻\",oline:\"‾\",olt:\"⧀\",Omacr:\"Ō\",omacr:\"ō\",Omega:\"Ω\",omega:\"ω\",Omicron:\"Ο\",omicron:\"ο\",omid:\"⦶\",ominus:\"⊖\",Oopf:\"𝕆\",oopf:\"𝕠\",opar:\"⦷\",OpenCurlyDoubleQuote:\"“\",OpenCurlyQuote:\"‘\",operp:\"⦹\",oplus:\"⊕\",orarr:\"↻\",Or:\"⩔\",or:\"∨\",ord:\"⩝\",order:\"ℴ\",orderof:\"ℴ\",ordf:\"ª\",ordm:\"º\",origof:\"⊶\",oror:\"⩖\",orslope:\"⩗\",orv:\"⩛\",oS:\"Ⓢ\",Oscr:\"𝒪\",oscr:\"ℴ\",Oslash:\"Ø\",oslash:\"ø\",osol:\"⊘\",Otilde:\"Õ\",otilde:\"õ\",otimesas:\"⨶\",Otimes:\"⨷\",otimes:\"⊗\",Ouml:\"Ö\",ouml:\"ö\",ovbar:\"⌽\",OverBar:\"‾\",OverBrace:\"⏞\",OverBracket:\"⎴\",OverParenthesis:\"⏜\",para:\"¶\",parallel:\"∥\",par:\"∥\",parsim:\"⫳\",parsl:\"⫽\",part:\"∂\",PartialD:\"∂\",Pcy:\"П\",pcy:\"п\",percnt:\"%\",period:\".\",permil:\"‰\",perp:\"⊥\",pertenk:\"‱\",Pfr:\"𝔓\",pfr:\"𝔭\",Phi:\"Φ\",phi:\"φ\",phiv:\"ϕ\",phmmat:\"ℳ\",phone:\"☎\",Pi:\"Π\",pi:\"π\",pitchfork:\"⋔\",piv:\"ϖ\",planck:\"ℏ\",planckh:\"ℎ\",plankv:\"ℏ\",plusacir:\"⨣\",plusb:\"⊞\",pluscir:\"⨢\",plus:\"+\",plusdo:\"∔\",plusdu:\"⨥\",pluse:\"⩲\",PlusMinus:\"±\",plusmn:\"±\",plussim:\"⨦\",plustwo:\"⨧\",pm:\"±\",Poincareplane:\"ℌ\",pointint:\"⨕\",popf:\"𝕡\",Popf:\"ℙ\",pound:\"£\",prap:\"⪷\",Pr:\"⪻\",pr:\"≺\",prcue:\"≼\",precapprox:\"⪷\",prec:\"≺\",preccurlyeq:\"≼\",Precedes:\"≺\",PrecedesEqual:\"⪯\",PrecedesSlantEqual:\"≼\",PrecedesTilde:\"≾\",preceq:\"⪯\",precnapprox:\"⪹\",precneqq:\"⪵\",precnsim:\"⋨\",pre:\"⪯\",prE:\"⪳\",precsim:\"≾\",prime:\"′\",Prime:\"″\",primes:\"ℙ\",prnap:\"⪹\",prnE:\"⪵\",prnsim:\"⋨\",prod:\"∏\",Product:\"∏\",profalar:\"⌮\",profline:\"⌒\",profsurf:\"⌓\",prop:\"∝\",Proportional:\"∝\",Proportion:\"∷\",propto:\"∝\",prsim:\"≾\",prurel:\"⊰\",Pscr:\"𝒫\",pscr:\"𝓅\",Psi:\"Ψ\",psi:\"ψ\",puncsp:\" \",Qfr:\"𝔔\",qfr:\"𝔮\",qint:\"⨌\",qopf:\"𝕢\",Qopf:\"ℚ\",qprime:\"⁗\",Qscr:\"𝒬\",qscr:\"𝓆\",quaternions:\"ℍ\",quatint:\"⨖\",quest:\"?\",questeq:\"≟\",quot:'\"',QUOT:'\"',rAarr:\"⇛\",race:\"∽̱\",Racute:\"Ŕ\",racute:\"ŕ\",radic:\"√\",raemptyv:\"⦳\",rang:\"⟩\",Rang:\"⟫\",rangd:\"⦒\",range:\"⦥\",rangle:\"⟩\",raquo:\"»\",rarrap:\"⥵\",rarrb:\"⇥\",rarrbfs:\"⤠\",rarrc:\"⤳\",rarr:\"→\",Rarr:\"↠\",rArr:\"⇒\",rarrfs:\"⤞\",rarrhk:\"↪\",rarrlp:\"↬\",rarrpl:\"⥅\",rarrsim:\"⥴\",Rarrtl:\"⤖\",rarrtl:\"↣\",rarrw:\"↝\",ratail:\"⤚\",rAtail:\"⤜\",ratio:\"∶\",rationals:\"ℚ\",rbarr:\"⤍\",rBarr:\"⤏\",RBarr:\"⤐\",rbbrk:\"❳\",rbrace:\"}\",rbrack:\"]\",rbrke:\"⦌\",rbrksld:\"⦎\",rbrkslu:\"⦐\",Rcaron:\"Ř\",rcaron:\"ř\",Rcedil:\"Ŗ\",rcedil:\"ŗ\",rceil:\"⌉\",rcub:\"}\",Rcy:\"Р\",rcy:\"р\",rdca:\"⤷\",rdldhar:\"⥩\",rdquo:\"”\",rdquor:\"”\",rdsh:\"↳\",real:\"ℜ\",realine:\"ℛ\",realpart:\"ℜ\",reals:\"ℝ\",Re:\"ℜ\",rect:\"▭\",reg:\"®\",REG:\"®\",ReverseElement:\"∋\",ReverseEquilibrium:\"⇋\",ReverseUpEquilibrium:\"⥯\",rfisht:\"⥽\",rfloor:\"⌋\",rfr:\"𝔯\",Rfr:\"ℜ\",rHar:\"⥤\",rhard:\"⇁\",rharu:\"⇀\",rharul:\"⥬\",Rho:\"Ρ\",rho:\"ρ\",rhov:\"ϱ\",RightAngleBracket:\"⟩\",RightArrowBar:\"⇥\",rightarrow:\"→\",RightArrow:\"→\",Rightarrow:\"⇒\",RightArrowLeftArrow:\"⇄\",rightarrowtail:\"↣\",RightCeiling:\"⌉\",RightDoubleBracket:\"⟧\",RightDownTeeVector:\"⥝\",RightDownVectorBar:\"⥕\",RightDownVector:\"⇂\",RightFloor:\"⌋\",rightharpoondown:\"⇁\",rightharpoonup:\"⇀\",rightleftarrows:\"⇄\",rightleftharpoons:\"⇌\",rightrightarrows:\"⇉\",rightsquigarrow:\"↝\",RightTeeArrow:\"↦\",RightTee:\"⊢\",RightTeeVector:\"⥛\",rightthreetimes:\"⋌\",RightTriangleBar:\"⧐\",RightTriangle:\"⊳\",RightTriangleEqual:\"⊵\",RightUpDownVector:\"⥏\",RightUpTeeVector:\"⥜\",RightUpVectorBar:\"⥔\",RightUpVector:\"↾\",RightVectorBar:\"⥓\",RightVector:\"⇀\",ring:\"˚\",risingdotseq:\"≓\",rlarr:\"⇄\",rlhar:\"⇌\",rlm:\"‏\",rmoustache:\"⎱\",rmoust:\"⎱\",rnmid:\"⫮\",roang:\"⟭\",roarr:\"⇾\",robrk:\"⟧\",ropar:\"⦆\",ropf:\"𝕣\",Ropf:\"ℝ\",roplus:\"⨮\",rotimes:\"⨵\",RoundImplies:\"⥰\",rpar:\")\",rpargt:\"⦔\",rppolint:\"⨒\",rrarr:\"⇉\",Rrightarrow:\"⇛\",rsaquo:\"›\",rscr:\"𝓇\",Rscr:\"ℛ\",rsh:\"↱\",Rsh:\"↱\",rsqb:\"]\",rsquo:\"’\",rsquor:\"’\",rthree:\"⋌\",rtimes:\"⋊\",rtri:\"▹\",rtrie:\"⊵\",rtrif:\"▸\",rtriltri:\"⧎\",RuleDelayed:\"⧴\",ruluhar:\"⥨\",rx:\"℞\",Sacute:\"Ś\",sacute:\"ś\",sbquo:\"‚\",scap:\"⪸\",Scaron:\"Š\",scaron:\"š\",Sc:\"⪼\",sc:\"≻\",sccue:\"≽\",sce:\"⪰\",scE:\"⪴\",Scedil:\"Ş\",scedil:\"ş\",Scirc:\"Ŝ\",scirc:\"ŝ\",scnap:\"⪺\",scnE:\"⪶\",scnsim:\"⋩\",scpolint:\"⨓\",scsim:\"≿\",Scy:\"С\",scy:\"с\",sdotb:\"⊡\",sdot:\"⋅\",sdote:\"⩦\",searhk:\"⤥\",searr:\"↘\",seArr:\"⇘\",searrow:\"↘\",sect:\"§\",semi:\";\",seswar:\"⤩\",setminus:\"∖\",setmn:\"∖\",sext:\"✶\",Sfr:\"𝔖\",sfr:\"𝔰\",sfrown:\"⌢\",sharp:\"♯\",SHCHcy:\"Щ\",shchcy:\"щ\",SHcy:\"Ш\",shcy:\"ш\",ShortDownArrow:\"↓\",ShortLeftArrow:\"←\",shortmid:\"∣\",shortparallel:\"∥\",ShortRightArrow:\"→\",ShortUpArrow:\"↑\",shy:\"­\",Sigma:\"Σ\",sigma:\"σ\",sigmaf:\"ς\",sigmav:\"ς\",sim:\"∼\",simdot:\"⩪\",sime:\"≃\",simeq:\"≃\",simg:\"⪞\",simgE:\"⪠\",siml:\"⪝\",simlE:\"⪟\",simne:\"≆\",simplus:\"⨤\",simrarr:\"⥲\",slarr:\"←\",SmallCircle:\"∘\",smallsetminus:\"∖\",smashp:\"⨳\",smeparsl:\"⧤\",smid:\"∣\",smile:\"⌣\",smt:\"⪪\",smte:\"⪬\",smtes:\"⪬︀\",SOFTcy:\"Ь\",softcy:\"ь\",solbar:\"⌿\",solb:\"⧄\",sol:\"/\",Sopf:\"𝕊\",sopf:\"𝕤\",spades:\"♠\",spadesuit:\"♠\",spar:\"∥\",sqcap:\"⊓\",sqcaps:\"⊓︀\",sqcup:\"⊔\",sqcups:\"⊔︀\",Sqrt:\"√\",sqsub:\"⊏\",sqsube:\"⊑\",sqsubset:\"⊏\",sqsubseteq:\"⊑\",sqsup:\"⊐\",sqsupe:\"⊒\",sqsupset:\"⊐\",sqsupseteq:\"⊒\",square:\"□\",Square:\"□\",SquareIntersection:\"⊓\",SquareSubset:\"⊏\",SquareSubsetEqual:\"⊑\",SquareSuperset:\"⊐\",SquareSupersetEqual:\"⊒\",SquareUnion:\"⊔\",squarf:\"▪\",squ:\"□\",squf:\"▪\",srarr:\"→\",Sscr:\"𝒮\",sscr:\"𝓈\",ssetmn:\"∖\",ssmile:\"⌣\",sstarf:\"⋆\",Star:\"⋆\",star:\"☆\",starf:\"★\",straightepsilon:\"ϵ\",straightphi:\"ϕ\",strns:\"¯\",sub:\"⊂\",Sub:\"⋐\",subdot:\"⪽\",subE:\"⫅\",sube:\"⊆\",subedot:\"⫃\",submult:\"⫁\",subnE:\"⫋\",subne:\"⊊\",subplus:\"⪿\",subrarr:\"⥹\",subset:\"⊂\",Subset:\"⋐\",subseteq:\"⊆\",subseteqq:\"⫅\",SubsetEqual:\"⊆\",subsetneq:\"⊊\",subsetneqq:\"⫋\",subsim:\"⫇\",subsub:\"⫕\",subsup:\"⫓\",succapprox:\"⪸\",succ:\"≻\",succcurlyeq:\"≽\",Succeeds:\"≻\",SucceedsEqual:\"⪰\",SucceedsSlantEqual:\"≽\",SucceedsTilde:\"≿\",succeq:\"⪰\",succnapprox:\"⪺\",succneqq:\"⪶\",succnsim:\"⋩\",succsim:\"≿\",SuchThat:\"∋\",sum:\"∑\",Sum:\"∑\",sung:\"♪\",sup1:\"¹\",sup2:\"²\",sup3:\"³\",sup:\"⊃\",Sup:\"⋑\",supdot:\"⪾\",supdsub:\"⫘\",supE:\"⫆\",supe:\"⊇\",supedot:\"⫄\",Superset:\"⊃\",SupersetEqual:\"⊇\",suphsol:\"⟉\",suphsub:\"⫗\",suplarr:\"⥻\",supmult:\"⫂\",supnE:\"⫌\",supne:\"⊋\",supplus:\"⫀\",supset:\"⊃\",Supset:\"⋑\",supseteq:\"⊇\",supseteqq:\"⫆\",supsetneq:\"⊋\",supsetneqq:\"⫌\",supsim:\"⫈\",supsub:\"⫔\",supsup:\"⫖\",swarhk:\"⤦\",swarr:\"↙\",swArr:\"⇙\",swarrow:\"↙\",swnwar:\"⤪\",szlig:\"ß\",Tab:\"\\t\",target:\"⌖\",Tau:\"Τ\",tau:\"τ\",tbrk:\"⎴\",Tcaron:\"Ť\",tcaron:\"ť\",Tcedil:\"Ţ\",tcedil:\"ţ\",Tcy:\"Т\",tcy:\"т\",tdot:\"⃛\",telrec:\"⌕\",Tfr:\"𝔗\",tfr:\"𝔱\",there4:\"∴\",therefore:\"∴\",Therefore:\"∴\",Theta:\"Θ\",theta:\"θ\",thetasym:\"ϑ\",thetav:\"ϑ\",thickapprox:\"≈\",thicksim:\"∼\",ThickSpace:\"  \",ThinSpace:\" \",thinsp:\" \",thkap:\"≈\",thksim:\"∼\",THORN:\"Þ\",thorn:\"þ\",tilde:\"˜\",Tilde:\"∼\",TildeEqual:\"≃\",TildeFullEqual:\"≅\",TildeTilde:\"≈\",timesbar:\"⨱\",timesb:\"⊠\",times:\"×\",timesd:\"⨰\",tint:\"∭\",toea:\"⤨\",topbot:\"⌶\",topcir:\"⫱\",top:\"⊤\",Topf:\"𝕋\",topf:\"𝕥\",topfork:\"⫚\",tosa:\"⤩\",tprime:\"‴\",trade:\"™\",TRADE:\"™\",triangle:\"▵\",triangledown:\"▿\",triangleleft:\"◃\",trianglelefteq:\"⊴\",triangleq:\"≜\",triangleright:\"▹\",trianglerighteq:\"⊵\",tridot:\"◬\",trie:\"≜\",triminus:\"⨺\",TripleDot:\"⃛\",triplus:\"⨹\",trisb:\"⧍\",tritime:\"⨻\",trpezium:\"⏢\",Tscr:\"𝒯\",tscr:\"𝓉\",TScy:\"Ц\",tscy:\"ц\",TSHcy:\"Ћ\",tshcy:\"ћ\",Tstrok:\"Ŧ\",tstrok:\"ŧ\",twixt:\"≬\",twoheadleftarrow:\"↞\",twoheadrightarrow:\"↠\",Uacute:\"Ú\",uacute:\"ú\",uarr:\"↑\",Uarr:\"↟\",uArr:\"⇑\",Uarrocir:\"⥉\",Ubrcy:\"Ў\",ubrcy:\"ў\",Ubreve:\"Ŭ\",ubreve:\"ŭ\",Ucirc:\"Û\",ucirc:\"û\",Ucy:\"У\",ucy:\"у\",udarr:\"⇅\",Udblac:\"Ű\",udblac:\"ű\",udhar:\"⥮\",ufisht:\"⥾\",Ufr:\"𝔘\",ufr:\"𝔲\",Ugrave:\"Ù\",ugrave:\"ù\",uHar:\"⥣\",uharl:\"↿\",uharr:\"↾\",uhblk:\"▀\",ulcorn:\"⌜\",ulcorner:\"⌜\",ulcrop:\"⌏\",ultri:\"◸\",Umacr:\"Ū\",umacr:\"ū\",uml:\"¨\",UnderBar:\"_\",UnderBrace:\"⏟\",UnderBracket:\"⎵\",UnderParenthesis:\"⏝\",Union:\"⋃\",UnionPlus:\"⊎\",Uogon:\"Ų\",uogon:\"ų\",Uopf:\"𝕌\",uopf:\"𝕦\",UpArrowBar:\"⤒\",uparrow:\"↑\",UpArrow:\"↑\",Uparrow:\"⇑\",UpArrowDownArrow:\"⇅\",updownarrow:\"↕\",UpDownArrow:\"↕\",Updownarrow:\"⇕\",UpEquilibrium:\"⥮\",upharpoonleft:\"↿\",upharpoonright:\"↾\",uplus:\"⊎\",UpperLeftArrow:\"↖\",UpperRightArrow:\"↗\",upsi:\"υ\",Upsi:\"ϒ\",upsih:\"ϒ\",Upsilon:\"Υ\",upsilon:\"υ\",UpTeeArrow:\"↥\",UpTee:\"⊥\",upuparrows:\"⇈\",urcorn:\"⌝\",urcorner:\"⌝\",urcrop:\"⌎\",Uring:\"Ů\",uring:\"ů\",urtri:\"◹\",Uscr:\"𝒰\",uscr:\"𝓊\",utdot:\"⋰\",Utilde:\"Ũ\",utilde:\"ũ\",utri:\"▵\",utrif:\"▴\",uuarr:\"⇈\",Uuml:\"Ü\",uuml:\"ü\",uwangle:\"⦧\",vangrt:\"⦜\",varepsilon:\"ϵ\",varkappa:\"ϰ\",varnothing:\"∅\",varphi:\"ϕ\",varpi:\"ϖ\",varpropto:\"∝\",varr:\"↕\",vArr:\"⇕\",varrho:\"ϱ\",varsigma:\"ς\",varsubsetneq:\"⊊︀\",varsubsetneqq:\"⫋︀\",varsupsetneq:\"⊋︀\",varsupsetneqq:\"⫌︀\",vartheta:\"ϑ\",vartriangleleft:\"⊲\",vartriangleright:\"⊳\",vBar:\"⫨\",Vbar:\"⫫\",vBarv:\"⫩\",Vcy:\"В\",vcy:\"в\",vdash:\"⊢\",vDash:\"⊨\",Vdash:\"⊩\",VDash:\"⊫\",Vdashl:\"⫦\",veebar:\"⊻\",vee:\"∨\",Vee:\"⋁\",veeeq:\"≚\",vellip:\"⋮\",verbar:\"|\",Verbar:\"‖\",vert:\"|\",Vert:\"‖\",VerticalBar:\"∣\",VerticalLine:\"|\",VerticalSeparator:\"❘\",VerticalTilde:\"≀\",VeryThinSpace:\" \",Vfr:\"𝔙\",vfr:\"𝔳\",vltri:\"⊲\",vnsub:\"⊂⃒\",vnsup:\"⊃⃒\",Vopf:\"𝕍\",vopf:\"𝕧\",vprop:\"∝\",vrtri:\"⊳\",Vscr:\"𝒱\",vscr:\"𝓋\",vsubnE:\"⫋︀\",vsubne:\"⊊︀\",vsupnE:\"⫌︀\",vsupne:\"⊋︀\",Vvdash:\"⊪\",vzigzag:\"⦚\",Wcirc:\"Ŵ\",wcirc:\"ŵ\",wedbar:\"⩟\",wedge:\"∧\",Wedge:\"⋀\",wedgeq:\"≙\",weierp:\"℘\",Wfr:\"𝔚\",wfr:\"𝔴\",Wopf:\"𝕎\",wopf:\"𝕨\",wp:\"℘\",wr:\"≀\",wreath:\"≀\",Wscr:\"𝒲\",wscr:\"𝓌\",xcap:\"⋂\",xcirc:\"◯\",xcup:\"⋃\",xdtri:\"▽\",Xfr:\"𝔛\",xfr:\"𝔵\",xharr:\"⟷\",xhArr:\"⟺\",Xi:\"Ξ\",xi:\"ξ\",xlarr:\"⟵\",xlArr:\"⟸\",xmap:\"⟼\",xnis:\"⋻\",xodot:\"⨀\",Xopf:\"𝕏\",xopf:\"𝕩\",xoplus:\"⨁\",xotime:\"⨂\",xrarr:\"⟶\",xrArr:\"⟹\",Xscr:\"𝒳\",xscr:\"𝓍\",xsqcup:\"⨆\",xuplus:\"⨄\",xutri:\"△\",xvee:\"⋁\",xwedge:\"⋀\",Yacute:\"Ý\",yacute:\"ý\",YAcy:\"Я\",yacy:\"я\",Ycirc:\"Ŷ\",ycirc:\"ŷ\",Ycy:\"Ы\",ycy:\"ы\",yen:\"¥\",Yfr:\"𝔜\",yfr:\"𝔶\",YIcy:\"Ї\",yicy:\"ї\",Yopf:\"𝕐\",yopf:\"𝕪\",Yscr:\"𝒴\",yscr:\"𝓎\",YUcy:\"Ю\",yucy:\"ю\",yuml:\"ÿ\",Yuml:\"Ÿ\",Zacute:\"Ź\",zacute:\"ź\",Zcaron:\"Ž\",zcaron:\"ž\",Zcy:\"З\",zcy:\"з\",Zdot:\"Ż\",zdot:\"ż\",zeetrf:\"ℨ\",ZeroWidthSpace:\"​\",Zeta:\"Ζ\",zeta:\"ζ\",zfr:\"𝔷\",Zfr:\"ℨ\",ZHcy:\"Ж\",zhcy:\"ж\",zigrarr:\"⇝\",zopf:\"𝕫\",Zopf:\"ℤ\",Zscr:\"𝒵\",zscr:\"𝓏\",zwj:\"‍\",zwnj:\"‌\"}},{}],26:[function(require,module,exports){module.exports={Aacute:\"Á\",aacute:\"á\",Acirc:\"Â\",acirc:\"â\",acute:\"´\",AElig:\"Æ\",aelig:\"æ\",Agrave:\"À\",agrave:\"à\",amp:\"&\",AMP:\"&\",Aring:\"Å\",aring:\"å\",Atilde:\"Ã\",atilde:\"ã\",Auml:\"Ä\",auml:\"ä\",brvbar:\"¦\",Ccedil:\"Ç\",ccedil:\"ç\",cedil:\"¸\",cent:\"¢\",copy:\"©\",COPY:\"©\",curren:\"¤\",deg:\"°\",divide:\"÷\",Eacute:\"É\",eacute:\"é\",Ecirc:\"Ê\",ecirc:\"ê\",Egrave:\"È\",egrave:\"è\",ETH:\"Ð\",eth:\"ð\",Euml:\"Ë\",euml:\"ë\",frac12:\"½\",frac14:\"¼\",frac34:\"¾\",gt:\">\",GT:\">\",Iacute:\"Í\",iacute:\"í\",Icirc:\"Î\",icirc:\"î\",iexcl:\"¡\",Igrave:\"Ì\",igrave:\"ì\",iquest:\"¿\",Iuml:\"Ï\",iuml:\"ï\",laquo:\"«\",lt:\"<\",LT:\"<\",macr:\"¯\",micro:\"µ\",middot:\"·\",nbsp:\" \",not:\"¬\",Ntilde:\"Ñ\",ntilde:\"ñ\",Oacute:\"Ó\",oacute:\"ó\",Ocirc:\"Ô\",ocirc:\"ô\",Ograve:\"Ò\",ograve:\"ò\",ordf:\"ª\",ordm:\"º\",Oslash:\"Ø\",oslash:\"ø\",Otilde:\"Õ\",otilde:\"õ\",Ouml:\"Ö\",ouml:\"ö\",para:\"¶\",plusmn:\"±\",pound:\"£\",quot:'\"',QUOT:'\"',raquo:\"»\",reg:\"®\",REG:\"®\",sect:\"§\",shy:\"­\",sup1:\"¹\",sup2:\"²\",sup3:\"³\",szlig:\"ß\",THORN:\"Þ\",thorn:\"þ\",times:\"×\",Uacute:\"Ú\",uacute:\"ú\",Ucirc:\"Û\",ucirc:\"û\",Ugrave:\"Ù\",ugrave:\"ù\",uml:\"¨\",Uuml:\"Ü\",uuml:\"ü\",Yacute:\"Ý\",yacute:\"ý\",yen:\"¥\",yuml:\"ÿ\"}},{}],27:[function(require,module,exports){module.exports={amp:\"&\",apos:\"'\",gt:\">\",lt:\"<\",quot:'\"'}},{}],28:[function(require,module,exports){function EventEmitter(){this._events=this._events||{};this._maxListeners=this._maxListeners||undefined}module.exports=EventEmitter;EventEmitter.EventEmitter=EventEmitter;EventEmitter.prototype._events=undefined;EventEmitter.prototype._maxListeners=undefined;EventEmitter.defaultMaxListeners=10;EventEmitter.prototype.setMaxListeners=function(n){if(!isNumber(n)||n<0||isNaN(n))throw TypeError(\"n must be a positive number\");this._maxListeners=n;return this};EventEmitter.prototype.emit=function(type){var er,handler,len,args,i,listeners;if(!this._events)this._events={};if(type===\"error\"){if(!this._events.error||isObject(this._events.error)&&!this._events.error.length){er=arguments[1];if(er instanceof Error){throw er}else{var err=new Error('Uncaught, unspecified \"error\" event. ('+er+\")\");err.context=er;throw err}}}handler=this._events[type];if(isUndefined(handler))return false;if(isFunction(handler)){switch(arguments.length){case 1:handler.call(this);break;case 2:handler.call(this,arguments[1]);break;case 3:handler.call(this,arguments[1],arguments[2]);break;default:args=Array.prototype.slice.call(arguments,1);handler.apply(this,args)}}else if(isObject(handler)){args=Array.prototype.slice.call(arguments,1);listeners=handler.slice();len=listeners.length;for(i=0;i<len;i++)listeners[i].apply(this,args)}return true};EventEmitter.prototype.addListener=function(type,listener){var m;if(!isFunction(listener))throw TypeError(\"listener must be a function\");if(!this._events)this._events={};if(this._events.newListener)this.emit(\"newListener\",type,isFunction(listener.listener)?listener.listener:listener);if(!this._events[type])this._events[type]=listener;else if(isObject(this._events[type]))this._events[type].push(listener);else this._events[type]=[this._events[type],listener];if(isObject(this._events[type])&&!this._events[type].warned){if(!isUndefined(this._maxListeners)){m=this._maxListeners}else{m=EventEmitter.defaultMaxListeners}if(m&&m>0&&this._events[type].length>m){this._events[type].warned=true;console.error(\"(node) warning: possible EventEmitter memory \"+\"leak detected. %d listeners added. \"+\"Use emitter.setMaxListeners() to increase limit.\",this._events[type].length);if(typeof console.trace===\"function\"){console.trace()}}}return this};EventEmitter.prototype.on=EventEmitter.prototype.addListener;EventEmitter.prototype.once=function(type,listener){if(!isFunction(listener))throw TypeError(\"listener must be a function\");var fired=false;function g(){this.removeListener(type,g);if(!fired){fired=true;listener.apply(this,arguments)}}g.listener=listener;this.on(type,g);return this};EventEmitter.prototype.removeListener=function(type,listener){var list,position,length,i;if(!isFunction(listener))throw TypeError(\"listener must be a function\");if(!this._events||!this._events[type])return this;list=this._events[type];length=list.length;position=-1;if(list===listener||isFunction(list.listener)&&list.listener===listener){delete this._events[type];if(this._events.removeListener)this.emit(\"removeListener\",type,listener)}else if(isObject(list)){for(i=length;i-- >0;){if(list[i]===listener||list[i].listener&&list[i].listener===listener){position=i;break}}if(position<0)return this;if(list.length===1){list.length=0;delete this._events[type]}else{list.splice(position,1);\n}if(this._events.removeListener)this.emit(\"removeListener\",type,listener)}return this};EventEmitter.prototype.removeAllListeners=function(type){var key,listeners;if(!this._events)return this;if(!this._events.removeListener){if(arguments.length===0)this._events={};else if(this._events[type])delete this._events[type];return this}if(arguments.length===0){for(key in this._events){if(key===\"removeListener\")continue;this.removeAllListeners(key)}this.removeAllListeners(\"removeListener\");this._events={};return this}listeners=this._events[type];if(isFunction(listeners)){this.removeListener(type,listeners)}else if(listeners){while(listeners.length)this.removeListener(type,listeners[listeners.length-1])}delete this._events[type];return this};EventEmitter.prototype.listeners=function(type){var ret;if(!this._events||!this._events[type])ret=[];else if(isFunction(this._events[type]))ret=[this._events[type]];else ret=this._events[type].slice();return ret};EventEmitter.prototype.listenerCount=function(type){if(this._events){var evlistener=this._events[type];if(isFunction(evlistener))return 1;else if(evlistener)return evlistener.length}return 0};EventEmitter.listenerCount=function(emitter,type){return emitter.listenerCount(type)};function isFunction(arg){return typeof arg===\"function\"}function isNumber(arg){return typeof arg===\"number\"}function isObject(arg){return typeof arg===\"object\"&&arg!==null}function isUndefined(arg){return arg===void 0}},{}],29:[function(require,module,exports){module.exports=CollectingHandler;function CollectingHandler(cbs){this._cbs=cbs||{};this.events=[]}var EVENTS=require(\"./\").EVENTS;Object.keys(EVENTS).forEach(function(name){if(EVENTS[name]===0){name=\"on\"+name;CollectingHandler.prototype[name]=function(){this.events.push([name]);if(this._cbs[name])this._cbs[name]()}}else if(EVENTS[name]===1){name=\"on\"+name;CollectingHandler.prototype[name]=function(a){this.events.push([name,a]);if(this._cbs[name])this._cbs[name](a)}}else if(EVENTS[name]===2){name=\"on\"+name;CollectingHandler.prototype[name]=function(a,b){this.events.push([name,a,b]);if(this._cbs[name])this._cbs[name](a,b)}}else{throw Error(\"wrong number of arguments\")}});CollectingHandler.prototype.onreset=function(){this.events=[];if(this._cbs.onreset)this._cbs.onreset()};CollectingHandler.prototype.restart=function(){if(this._cbs.onreset)this._cbs.onreset();for(var i=0,len=this.events.length;i<len;i++){if(this._cbs[this.events[i][0]]){var num=this.events[i].length;if(num===1){this._cbs[this.events[i][0]]()}else if(num===2){this._cbs[this.events[i][0]](this.events[i][1])}else{this._cbs[this.events[i][0]](this.events[i][1],this.events[i][2])}}}}},{\"./\":36}],30:[function(require,module,exports){var index=require(\"./index.js\"),DomHandler=index.DomHandler,DomUtils=index.DomUtils;function FeedHandler(callback,options){this.init(callback,options)}require(\"inherits\")(FeedHandler,DomHandler);FeedHandler.prototype.init=DomHandler;function getElements(what,where){return DomUtils.getElementsByTagName(what,where,true)}function getOneElement(what,where){return DomUtils.getElementsByTagName(what,where,true,1)[0]}function fetch(what,where,recurse){return DomUtils.getText(DomUtils.getElementsByTagName(what,where,recurse,1)).trim()}function addConditionally(obj,prop,what,where,recurse){var tmp=fetch(what,where,recurse);if(tmp)obj[prop]=tmp}var isValidFeed=function(value){return value===\"rss\"||value===\"feed\"||value===\"rdf:RDF\"};FeedHandler.prototype.onend=function(){var feed={},feedRoot=getOneElement(isValidFeed,this.dom),tmp,childs;if(feedRoot){if(feedRoot.name===\"feed\"){childs=feedRoot.children;feed.type=\"atom\";addConditionally(feed,\"id\",\"id\",childs);addConditionally(feed,\"title\",\"title\",childs);if((tmp=getOneElement(\"link\",childs))&&(tmp=tmp.attribs)&&(tmp=tmp.href))feed.link=tmp;addConditionally(feed,\"description\",\"subtitle\",childs);if(tmp=fetch(\"updated\",childs))feed.updated=new Date(tmp);addConditionally(feed,\"author\",\"email\",childs,true);feed.items=getElements(\"entry\",childs).map(function(item){var entry={},tmp;item=item.children;addConditionally(entry,\"id\",\"id\",item);addConditionally(entry,\"title\",\"title\",item);if((tmp=getOneElement(\"link\",item))&&(tmp=tmp.attribs)&&(tmp=tmp.href))entry.link=tmp;if(tmp=fetch(\"summary\",item)||fetch(\"content\",item))entry.description=tmp;if(tmp=fetch(\"updated\",item))entry.pubDate=new Date(tmp);return entry})}else{childs=getOneElement(\"channel\",feedRoot.children).children;feed.type=feedRoot.name.substr(0,3);feed.id=\"\";addConditionally(feed,\"title\",\"title\",childs);addConditionally(feed,\"link\",\"link\",childs);addConditionally(feed,\"description\",\"description\",childs);if(tmp=fetch(\"lastBuildDate\",childs))feed.updated=new Date(tmp);addConditionally(feed,\"author\",\"managingEditor\",childs,true);feed.items=getElements(\"item\",feedRoot.children).map(function(item){var entry={},tmp;item=item.children;addConditionally(entry,\"id\",\"guid\",item);addConditionally(entry,\"title\",\"title\",item);addConditionally(entry,\"link\",\"link\",item);addConditionally(entry,\"description\",\"description\",item);if(tmp=fetch(\"pubDate\",item))entry.pubDate=new Date(tmp);return entry})}}this.dom=feed;DomHandler.prototype._handleCallback.call(this,feedRoot?null:Error(\"couldn't find root of feed\"))};module.exports=FeedHandler},{\"./index.js\":36,inherits:38}],31:[function(require,module,exports){var Tokenizer=require(\"./Tokenizer.js\");var formTags={input:true,option:true,optgroup:true,select:true,button:true,datalist:true,textarea:true};var openImpliesClose={tr:{tr:true,th:true,td:true},th:{th:true},td:{thead:true,th:true,td:true},body:{head:true,link:true,script:true},li:{li:true},p:{p:true},h1:{p:true},h2:{p:true},h3:{p:true},h4:{p:true},h5:{p:true},h6:{p:true},select:formTags,input:formTags,output:formTags,button:formTags,datalist:formTags,textarea:formTags,option:{option:true},optgroup:{optgroup:true}};var voidElements={__proto__:null,area:true,base:true,basefont:true,br:true,col:true,command:true,embed:true,frame:true,hr:true,img:true,input:true,isindex:true,keygen:true,link:true,meta:true,param:true,source:true,track:true,wbr:true,path:true,circle:true,ellipse:true,line:true,rect:true,use:true,stop:true,polyline:true,polygon:true};var re_nameEnd=/\\s|\\//;function Parser(cbs,options){this._options=options||{};this._cbs=cbs||{};this._tagname=\"\";this._attribname=\"\";this._attribvalue=\"\";this._attribs=null;this._stack=[];this.startIndex=0;this.endIndex=null;this._lowerCaseTagNames=\"lowerCaseTags\"in this._options?!!this._options.lowerCaseTags:!this._options.xmlMode;this._lowerCaseAttributeNames=\"lowerCaseAttributeNames\"in this._options?!!this._options.lowerCaseAttributeNames:!this._options.xmlMode;if(this._options.Tokenizer){Tokenizer=this._options.Tokenizer}this._tokenizer=new Tokenizer(this._options,this);if(this._cbs.onparserinit)this._cbs.onparserinit(this)}require(\"inherits\")(Parser,require(\"events\").EventEmitter);Parser.prototype._updatePosition=function(initialOffset){if(this.endIndex===null){if(this._tokenizer._sectionStart<=initialOffset){this.startIndex=0}else{this.startIndex=this._tokenizer._sectionStart-initialOffset}}else this.startIndex=this.endIndex+1;this.endIndex=this._tokenizer.getAbsoluteIndex()};Parser.prototype.ontext=function(data){this._updatePosition(1);this.endIndex--;if(this._cbs.ontext)this._cbs.ontext(data)};Parser.prototype.onopentagname=function(name){if(this._lowerCaseTagNames){name=name.toLowerCase()}this._tagname=name;if(!this._options.xmlMode&&name in openImpliesClose){for(var el;(el=this._stack[this._stack.length-1])in openImpliesClose[name];this.onclosetag(el));}if(this._options.xmlMode||!(name in voidElements)){this._stack.push(name)}if(this._cbs.onopentagname)this._cbs.onopentagname(name);if(this._cbs.onopentag)this._attribs={}};Parser.prototype.onopentagend=function(){this._updatePosition(1);if(this._attribs){if(this._cbs.onopentag)this._cbs.onopentag(this._tagname,this._attribs);this._attribs=null}if(!this._options.xmlMode&&this._cbs.onclosetag&&this._tagname in voidElements){this._cbs.onclosetag(this._tagname)}this._tagname=\"\"};Parser.prototype.onclosetag=function(name){this._updatePosition(1);if(this._lowerCaseTagNames){name=name.toLowerCase()}if(this._stack.length&&(!(name in voidElements)||this._options.xmlMode)){var pos=this._stack.lastIndexOf(name);if(pos!==-1){if(this._cbs.onclosetag){pos=this._stack.length-pos;while(pos--)this._cbs.onclosetag(this._stack.pop())}else this._stack.length=pos}else if(name===\"p\"&&!this._options.xmlMode){this.onopentagname(name);this._closeCurrentTag()}}else if(!this._options.xmlMode&&(name===\"br\"||name===\"p\")){this.onopentagname(name);this._closeCurrentTag()}};Parser.prototype.onselfclosingtag=function(){if(this._options.xmlMode||this._options.recognizeSelfClosing){this._closeCurrentTag()}else{this.onopentagend()}};Parser.prototype._closeCurrentTag=function(){var name=this._tagname;this.onopentagend();if(this._stack[this._stack.length-1]===name){if(this._cbs.onclosetag){this._cbs.onclosetag(name)}this._stack.pop()}};Parser.prototype.onattribname=function(name){if(this._lowerCaseAttributeNames){name=name.toLowerCase()}this._attribname=name};Parser.prototype.onattribdata=function(value){this._attribvalue+=value};Parser.prototype.onattribend=function(){if(this._cbs.onattribute)this._cbs.onattribute(this._attribname,this._attribvalue);if(this._attribs&&!Object.prototype.hasOwnProperty.call(this._attribs,this._attribname)){this._attribs[this._attribname]=this._attribvalue}this._attribname=\"\";this._attribvalue=\"\"};Parser.prototype._getInstructionName=function(value){var idx=value.search(re_nameEnd),name=idx<0?value:value.substr(0,idx);if(this._lowerCaseTagNames){name=name.toLowerCase()}return name};Parser.prototype.ondeclaration=function(value){if(this._cbs.onprocessinginstruction){var name=this._getInstructionName(value);this._cbs.onprocessinginstruction(\"!\"+name,\"!\"+value)}};Parser.prototype.onprocessinginstruction=function(value){if(this._cbs.onprocessinginstruction){var name=this._getInstructionName(value);this._cbs.onprocessinginstruction(\"?\"+name,\"?\"+value)}};Parser.prototype.oncomment=function(value){this._updatePosition(4);if(this._cbs.oncomment)this._cbs.oncomment(value);if(this._cbs.oncommentend)this._cbs.oncommentend()};Parser.prototype.oncdata=function(value){this._updatePosition(1);if(this._options.xmlMode||this._options.recognizeCDATA){if(this._cbs.oncdatastart)this._cbs.oncdatastart();if(this._cbs.ontext)this._cbs.ontext(value);if(this._cbs.oncdataend)this._cbs.oncdataend()}else{this.oncomment(\"[CDATA[\"+value+\"]]\")}};Parser.prototype.onerror=function(err){if(this._cbs.onerror)this._cbs.onerror(err)};Parser.prototype.onend=function(){if(this._cbs.onclosetag){for(var i=this._stack.length;i>0;this._cbs.onclosetag(this._stack[--i]));}if(this._cbs.onend)this._cbs.onend()};Parser.prototype.reset=function(){if(this._cbs.onreset)this._cbs.onreset();this._tokenizer.reset();this._tagname=\"\";this._attribname=\"\";this._attribs=null;this._stack=[];if(this._cbs.onparserinit)this._cbs.onparserinit(this)};Parser.prototype.parseComplete=function(data){this.reset();this.end(data)};Parser.prototype.write=function(chunk){this._tokenizer.write(chunk)};Parser.prototype.end=function(chunk){this._tokenizer.end(chunk)};Parser.prototype.pause=function(){this._tokenizer.pause()};Parser.prototype.resume=function(){this._tokenizer.resume()};Parser.prototype.parseChunk=Parser.prototype.write;Parser.prototype.done=Parser.prototype.end;module.exports=Parser},{\"./Tokenizer.js\":34,events:28,inherits:38}],32:[function(require,module,exports){module.exports=ProxyHandler;function ProxyHandler(cbs){this._cbs=cbs||{}}var EVENTS=require(\"./\").EVENTS;Object.keys(EVENTS).forEach(function(name){if(EVENTS[name]===0){name=\"on\"+name;ProxyHandler.prototype[name]=function(){if(this._cbs[name])this._cbs[name]()}}else if(EVENTS[name]===1){name=\"on\"+name;ProxyHandler.prototype[name]=function(a){if(this._cbs[name])this._cbs[name](a)}}else if(EVENTS[name]===2){name=\"on\"+name;ProxyHandler.prototype[name]=function(a,b){if(this._cbs[name])this._cbs[name](a,b)}}else{throw Error(\"wrong number of arguments\")}})},{\"./\":36}],33:[function(require,module,exports){module.exports=Stream;var Parser=require(\"./WritableStream.js\");function Stream(options){Parser.call(this,new Cbs(this),options)}require(\"inherits\")(Stream,Parser);Stream.prototype.readable=true;function Cbs(scope){this.scope=scope}var EVENTS=require(\"../\").EVENTS;Object.keys(EVENTS).forEach(function(name){if(EVENTS[name]===0){Cbs.prototype[\"on\"+name]=function(){this.scope.emit(name)}}else if(EVENTS[name]===1){Cbs.prototype[\"on\"+name]=function(a){this.scope.emit(name,a)}}else if(EVENTS[name]===2){Cbs.prototype[\"on\"+name]=function(a,b){this.scope.emit(name,a,b)}}else{throw Error(\"wrong number of arguments!\")}})},{\"../\":36,\"./WritableStream.js\":35,inherits:38}],34:[function(require,module,exports){module.exports=Tokenizer;var decodeCodePoint=require(\"entities/lib/decode_codepoint.js\"),entityMap=require(\"entities/maps/entities.json\"),legacyMap=require(\"entities/maps/legacy.json\"),xmlMap=require(\"entities/maps/xml.json\"),i=0,TEXT=i++,BEFORE_TAG_NAME=i++,IN_TAG_NAME=i++,IN_SELF_CLOSING_TAG=i++,BEFORE_CLOSING_TAG_NAME=i++,IN_CLOSING_TAG_NAME=i++,AFTER_CLOSING_TAG_NAME=i++,BEFORE_ATTRIBUTE_NAME=i++,IN_ATTRIBUTE_NAME=i++,AFTER_ATTRIBUTE_NAME=i++,BEFORE_ATTRIBUTE_VALUE=i++,IN_ATTRIBUTE_VALUE_DQ=i++,IN_ATTRIBUTE_VALUE_SQ=i++,IN_ATTRIBUTE_VALUE_NQ=i++,BEFORE_DECLARATION=i++,IN_DECLARATION=i++,IN_PROCESSING_INSTRUCTION=i++,BEFORE_COMMENT=i++,IN_COMMENT=i++,AFTER_COMMENT_1=i++,AFTER_COMMENT_2=i++,BEFORE_CDATA_1=i++,BEFORE_CDATA_2=i++,BEFORE_CDATA_3=i++,BEFORE_CDATA_4=i++,BEFORE_CDATA_5=i++,BEFORE_CDATA_6=i++,IN_CDATA=i++,AFTER_CDATA_1=i++,AFTER_CDATA_2=i++,BEFORE_SPECIAL=i++,BEFORE_SPECIAL_END=i++,BEFORE_SCRIPT_1=i++,BEFORE_SCRIPT_2=i++,BEFORE_SCRIPT_3=i++,BEFORE_SCRIPT_4=i++,BEFORE_SCRIPT_5=i++,AFTER_SCRIPT_1=i++,AFTER_SCRIPT_2=i++,AFTER_SCRIPT_3=i++,AFTER_SCRIPT_4=i++,AFTER_SCRIPT_5=i++,BEFORE_STYLE_1=i++,BEFORE_STYLE_2=i++,BEFORE_STYLE_3=i++,BEFORE_STYLE_4=i++,AFTER_STYLE_1=i++,AFTER_STYLE_2=i++,AFTER_STYLE_3=i++,AFTER_STYLE_4=i++,BEFORE_ENTITY=i++,BEFORE_NUMERIC_ENTITY=i++,IN_NAMED_ENTITY=i++,IN_NUMERIC_ENTITY=i++,IN_HEX_ENTITY=i++,j=0,SPECIAL_NONE=j++,SPECIAL_SCRIPT=j++,SPECIAL_STYLE=j++;function whitespace(c){return c===\" \"||c===\"\\n\"||c===\"\\t\"||c===\"\\f\"||c===\"\\r\"}function characterState(char,SUCCESS){return function(c){if(c===char)this._state=SUCCESS}}function ifElseState(upper,SUCCESS,FAILURE){var lower=upper.toLowerCase();if(upper===lower){return function(c){if(c===lower){this._state=SUCCESS}else{this._state=FAILURE;this._index--}}}else{return function(c){if(c===lower||c===upper){this._state=SUCCESS}else{this._state=FAILURE;this._index--}}}}function consumeSpecialNameChar(upper,NEXT_STATE){var lower=upper.toLowerCase();return function(c){if(c===lower||c===upper){this._state=NEXT_STATE}else{this._state=IN_TAG_NAME;this._index--}}}function Tokenizer(options,cbs){this._state=TEXT;this._buffer=\"\";this._sectionStart=0;this._index=0;this._bufferOffset=0;this._baseState=TEXT;this._special=SPECIAL_NONE;this._cbs=cbs;this._running=true;this._ended=false;this._xmlMode=!!(options&&options.xmlMode);this._decodeEntities=!!(options&&options.decodeEntities)}Tokenizer.prototype._stateText=function(c){if(c===\"<\"){if(this._index>this._sectionStart){this._cbs.ontext(this._getSection())}this._state=BEFORE_TAG_NAME;this._sectionStart=this._index}else if(this._decodeEntities&&this._special===SPECIAL_NONE&&c===\"&\"){if(this._index>this._sectionStart){this._cbs.ontext(this._getSection())}this._baseState=TEXT;this._state=BEFORE_ENTITY;this._sectionStart=this._index}};Tokenizer.prototype._stateBeforeTagName=function(c){if(c===\"/\"){this._state=BEFORE_CLOSING_TAG_NAME}else if(c===\"<\"){this._cbs.ontext(this._getSection());this._sectionStart=this._index}else if(c===\">\"||this._special!==SPECIAL_NONE||whitespace(c)){this._state=TEXT}else if(c===\"!\"){this._state=BEFORE_DECLARATION;this._sectionStart=this._index+1}else if(c===\"?\"){this._state=IN_PROCESSING_INSTRUCTION;this._sectionStart=this._index+1}else{this._state=!this._xmlMode&&(c===\"s\"||c===\"S\")?BEFORE_SPECIAL:IN_TAG_NAME;this._sectionStart=this._index}};Tokenizer.prototype._stateInTagName=function(c){if(c===\"/\"||c===\">\"||whitespace(c)){this._emitToken(\"onopentagname\");this._state=BEFORE_ATTRIBUTE_NAME;this._index--}};Tokenizer.prototype._stateBeforeCloseingTagName=function(c){if(whitespace(c));else if(c===\">\"){this._state=TEXT}else if(this._special!==SPECIAL_NONE){if(c===\"s\"||c===\"S\"){this._state=BEFORE_SPECIAL_END}else{this._state=TEXT;this._index--}}else{this._state=IN_CLOSING_TAG_NAME;this._sectionStart=this._index}};Tokenizer.prototype._stateInCloseingTagName=function(c){if(c===\">\"||whitespace(c)){this._emitToken(\"onclosetag\");this._state=AFTER_CLOSING_TAG_NAME;this._index--}};Tokenizer.prototype._stateAfterCloseingTagName=function(c){if(c===\">\"){this._state=TEXT;this._sectionStart=this._index+1}};Tokenizer.prototype._stateBeforeAttributeName=function(c){if(c===\">\"){this._cbs.onopentagend();this._state=TEXT;this._sectionStart=this._index+1}else if(c===\"/\"){this._state=IN_SELF_CLOSING_TAG}else if(!whitespace(c)){this._state=IN_ATTRIBUTE_NAME;this._sectionStart=this._index}};Tokenizer.prototype._stateInSelfClosingTag=function(c){if(c===\">\"){this._cbs.onselfclosingtag();this._state=TEXT;this._sectionStart=this._index+1}else if(!whitespace(c)){this._state=BEFORE_ATTRIBUTE_NAME;this._index--}};Tokenizer.prototype._stateInAttributeName=function(c){if(c===\"=\"||c===\"/\"||c===\">\"||whitespace(c)){this._cbs.onattribname(this._getSection());this._sectionStart=-1;this._state=AFTER_ATTRIBUTE_NAME;this._index--}};Tokenizer.prototype._stateAfterAttributeName=function(c){if(c===\"=\"){this._state=BEFORE_ATTRIBUTE_VALUE}else if(c===\"/\"||c===\">\"){this._cbs.onattribend();this._state=BEFORE_ATTRIBUTE_NAME;this._index--}else if(!whitespace(c)){this._cbs.onattribend();this._state=IN_ATTRIBUTE_NAME;this._sectionStart=this._index}};Tokenizer.prototype._stateBeforeAttributeValue=function(c){if(c==='\"'){this._state=IN_ATTRIBUTE_VALUE_DQ;this._sectionStart=this._index+1}else if(c===\"'\"){this._state=IN_ATTRIBUTE_VALUE_SQ;this._sectionStart=this._index+1}else if(!whitespace(c)){this._state=IN_ATTRIBUTE_VALUE_NQ;this._sectionStart=this._index;this._index--}};Tokenizer.prototype._stateInAttributeValueDoubleQuotes=function(c){if(c==='\"'){this._emitToken(\"onattribdata\");this._cbs.onattribend();this._state=BEFORE_ATTRIBUTE_NAME}else if(this._decodeEntities&&c===\"&\"){this._emitToken(\"onattribdata\");this._baseState=this._state;this._state=BEFORE_ENTITY;this._sectionStart=this._index}};Tokenizer.prototype._stateInAttributeValueSingleQuotes=function(c){if(c===\"'\"){this._emitToken(\"onattribdata\");this._cbs.onattribend();this._state=BEFORE_ATTRIBUTE_NAME}else if(this._decodeEntities&&c===\"&\"){this._emitToken(\"onattribdata\");this._baseState=this._state;this._state=BEFORE_ENTITY;this._sectionStart=this._index}};Tokenizer.prototype._stateInAttributeValueNoQuotes=function(c){if(whitespace(c)||c===\">\"){this._emitToken(\"onattribdata\");this._cbs.onattribend();this._state=BEFORE_ATTRIBUTE_NAME;this._index--}else if(this._decodeEntities&&c===\"&\"){this._emitToken(\"onattribdata\");this._baseState=this._state;this._state=BEFORE_ENTITY;this._sectionStart=this._index}};Tokenizer.prototype._stateBeforeDeclaration=function(c){this._state=c===\"[\"?BEFORE_CDATA_1:c===\"-\"?BEFORE_COMMENT:IN_DECLARATION};Tokenizer.prototype._stateInDeclaration=function(c){if(c===\">\"){this._cbs.ondeclaration(this._getSection());this._state=TEXT;this._sectionStart=this._index+1}};Tokenizer.prototype._stateInProcessingInstruction=function(c){if(c===\">\"){this._cbs.onprocessinginstruction(this._getSection());this._state=TEXT;this._sectionStart=this._index+1}};Tokenizer.prototype._stateBeforeComment=function(c){if(c===\"-\"){this._state=IN_COMMENT;this._sectionStart=this._index+1}else{this._state=IN_DECLARATION}};Tokenizer.prototype._stateInComment=function(c){if(c===\"-\")this._state=AFTER_COMMENT_1};Tokenizer.prototype._stateAfterComment1=function(c){if(c===\"-\"){this._state=AFTER_COMMENT_2}else{this._state=IN_COMMENT}};Tokenizer.prototype._stateAfterComment2=function(c){if(c===\">\"){this._cbs.oncomment(this._buffer.substring(this._sectionStart,this._index-2));this._state=TEXT;this._sectionStart=this._index+1}else if(c!==\"-\"){this._state=IN_COMMENT}};Tokenizer.prototype._stateBeforeCdata1=ifElseState(\"C\",BEFORE_CDATA_2,IN_DECLARATION);Tokenizer.prototype._stateBeforeCdata2=ifElseState(\"D\",BEFORE_CDATA_3,IN_DECLARATION);Tokenizer.prototype._stateBeforeCdata3=ifElseState(\"A\",BEFORE_CDATA_4,IN_DECLARATION);Tokenizer.prototype._stateBeforeCdata4=ifElseState(\"T\",BEFORE_CDATA_5,IN_DECLARATION);Tokenizer.prototype._stateBeforeCdata5=ifElseState(\"A\",BEFORE_CDATA_6,IN_DECLARATION);Tokenizer.prototype._stateBeforeCdata6=function(c){if(c===\"[\"){this._state=IN_CDATA;this._sectionStart=this._index+1}else{this._state=IN_DECLARATION;this._index--}};Tokenizer.prototype._stateInCdata=function(c){if(c===\"]\")this._state=AFTER_CDATA_1};Tokenizer.prototype._stateAfterCdata1=characterState(\"]\",AFTER_CDATA_2);Tokenizer.prototype._stateAfterCdata2=function(c){if(c===\">\"){this._cbs.oncdata(this._buffer.substring(this._sectionStart,this._index-2));this._state=TEXT;this._sectionStart=this._index+1}else if(c!==\"]\"){this._state=IN_CDATA}};Tokenizer.prototype._stateBeforeSpecial=function(c){if(c===\"c\"||c===\"C\"){this._state=BEFORE_SCRIPT_1}else if(c===\"t\"||c===\"T\"){this._state=BEFORE_STYLE_1}else{this._state=IN_TAG_NAME;this._index--}};Tokenizer.prototype._stateBeforeSpecialEnd=function(c){if(this._special===SPECIAL_SCRIPT&&(c===\"c\"||c===\"C\")){this._state=AFTER_SCRIPT_1}else if(this._special===SPECIAL_STYLE&&(c===\"t\"||c===\"T\")){this._state=AFTER_STYLE_1}else this._state=TEXT};Tokenizer.prototype._stateBeforeScript1=consumeSpecialNameChar(\"R\",BEFORE_SCRIPT_2);Tokenizer.prototype._stateBeforeScript2=consumeSpecialNameChar(\"I\",BEFORE_SCRIPT_3);Tokenizer.prototype._stateBeforeScript3=consumeSpecialNameChar(\"P\",BEFORE_SCRIPT_4);Tokenizer.prototype._stateBeforeScript4=consumeSpecialNameChar(\"T\",BEFORE_SCRIPT_5);Tokenizer.prototype._stateBeforeScript5=function(c){if(c===\"/\"||c===\">\"||whitespace(c)){this._special=SPECIAL_SCRIPT}this._state=IN_TAG_NAME;this._index--};Tokenizer.prototype._stateAfterScript1=ifElseState(\"R\",AFTER_SCRIPT_2,TEXT);Tokenizer.prototype._stateAfterScript2=ifElseState(\"I\",AFTER_SCRIPT_3,TEXT);Tokenizer.prototype._stateAfterScript3=ifElseState(\"P\",AFTER_SCRIPT_4,TEXT);Tokenizer.prototype._stateAfterScript4=ifElseState(\"T\",AFTER_SCRIPT_5,TEXT);Tokenizer.prototype._stateAfterScript5=function(c){if(c===\">\"||whitespace(c)){this._special=SPECIAL_NONE;this._state=IN_CLOSING_TAG_NAME;this._sectionStart=this._index-6;this._index--}else this._state=TEXT};Tokenizer.prototype._stateBeforeStyle1=consumeSpecialNameChar(\"Y\",BEFORE_STYLE_2);Tokenizer.prototype._stateBeforeStyle2=consumeSpecialNameChar(\"L\",BEFORE_STYLE_3);Tokenizer.prototype._stateBeforeStyle3=consumeSpecialNameChar(\"E\",BEFORE_STYLE_4);Tokenizer.prototype._stateBeforeStyle4=function(c){if(c===\"/\"||c===\">\"||whitespace(c)){this._special=SPECIAL_STYLE}this._state=IN_TAG_NAME;this._index--};Tokenizer.prototype._stateAfterStyle1=ifElseState(\"Y\",AFTER_STYLE_2,TEXT);Tokenizer.prototype._stateAfterStyle2=ifElseState(\"L\",AFTER_STYLE_3,TEXT);Tokenizer.prototype._stateAfterStyle3=ifElseState(\"E\",AFTER_STYLE_4,TEXT);Tokenizer.prototype._stateAfterStyle4=function(c){if(c===\">\"||whitespace(c)){this._special=SPECIAL_NONE;this._state=IN_CLOSING_TAG_NAME;this._sectionStart=this._index-5;this._index--}else this._state=TEXT};Tokenizer.prototype._stateBeforeEntity=ifElseState(\"#\",BEFORE_NUMERIC_ENTITY,IN_NAMED_ENTITY);Tokenizer.prototype._stateBeforeNumericEntity=ifElseState(\"X\",IN_HEX_ENTITY,IN_NUMERIC_ENTITY);Tokenizer.prototype._parseNamedEntityStrict=function(){if(this._sectionStart+1<this._index){var entity=this._buffer.substring(this._sectionStart+1,this._index),map=this._xmlMode?xmlMap:entityMap;if(map.hasOwnProperty(entity)){this._emitPartial(map[entity]);this._sectionStart=this._index+1}}};Tokenizer.prototype._parseLegacyEntity=function(){var start=this._sectionStart+1,limit=this._index-start;if(limit>6)limit=6;while(limit>=2){var entity=this._buffer.substr(start,limit);if(legacyMap.hasOwnProperty(entity)){this._emitPartial(legacyMap[entity]);this._sectionStart+=limit+1;return}else{limit--}}};Tokenizer.prototype._stateInNamedEntity=function(c){if(c===\";\"){this._parseNamedEntityStrict();if(this._sectionStart+1<this._index&&!this._xmlMode){this._parseLegacyEntity()}this._state=this._baseState}else if((c<\"a\"||c>\"z\")&&(c<\"A\"||c>\"Z\")&&(c<\"0\"||c>\"9\")){if(this._xmlMode);else if(this._sectionStart+1===this._index);else if(this._baseState!==TEXT){if(c!==\"=\"){this._parseNamedEntityStrict()}}else{this._parseLegacyEntity()}this._state=this._baseState;this._index--}};Tokenizer.prototype._decodeNumericEntity=function(offset,base){var sectionStart=this._sectionStart+offset;if(sectionStart!==this._index){var entity=this._buffer.substring(sectionStart,this._index);var parsed=parseInt(entity,base);this._emitPartial(decodeCodePoint(parsed));this._sectionStart=this._index}else{this._sectionStart--}this._state=this._baseState};Tokenizer.prototype._stateInNumericEntity=function(c){if(c===\";\"){this._decodeNumericEntity(2,10);this._sectionStart++}else if(c<\"0\"||c>\"9\"){if(!this._xmlMode){this._decodeNumericEntity(2,10)}else{this._state=this._baseState}this._index--}};Tokenizer.prototype._stateInHexEntity=function(c){if(c===\";\"){this._decodeNumericEntity(3,16);this._sectionStart++}else if((c<\"a\"||c>\"f\")&&(c<\"A\"||c>\"F\")&&(c<\"0\"||c>\"9\")){if(!this._xmlMode){this._decodeNumericEntity(3,16)}else{this._state=this._baseState}this._index--}};Tokenizer.prototype._cleanup=function(){if(this._sectionStart<0){this._buffer=\"\";this._index=0;this._bufferOffset+=this._index}else if(this._running){if(this._state===TEXT){if(this._sectionStart!==this._index){this._cbs.ontext(this._buffer.substr(this._sectionStart))}this._buffer=\"\";this._bufferOffset+=this._index;this._index=0}else if(this._sectionStart===this._index){this._buffer=\"\";this._bufferOffset+=this._index;this._index=0}else{this._buffer=this._buffer.substr(this._sectionStart);this._index-=this._sectionStart;this._bufferOffset+=this._sectionStart}this._sectionStart=0}};Tokenizer.prototype.write=function(chunk){if(this._ended)this._cbs.onerror(Error(\".write() after done!\"));this._buffer+=chunk;this._parse()};Tokenizer.prototype._parse=function(){while(this._index<this._buffer.length&&this._running){var c=this._buffer.charAt(this._index);if(this._state===TEXT){this._stateText(c)}else if(this._state===BEFORE_TAG_NAME){this._stateBeforeTagName(c)}else if(this._state===IN_TAG_NAME){this._stateInTagName(c)}else if(this._state===BEFORE_CLOSING_TAG_NAME){this._stateBeforeCloseingTagName(c)}else if(this._state===IN_CLOSING_TAG_NAME){this._stateInCloseingTagName(c)}else if(this._state===AFTER_CLOSING_TAG_NAME){this._stateAfterCloseingTagName(c)}else if(this._state===IN_SELF_CLOSING_TAG){this._stateInSelfClosingTag(c)}else if(this._state===BEFORE_ATTRIBUTE_NAME){this._stateBeforeAttributeName(c)}else if(this._state===IN_ATTRIBUTE_NAME){this._stateInAttributeName(c)}else if(this._state===AFTER_ATTRIBUTE_NAME){this._stateAfterAttributeName(c)}else if(this._state===BEFORE_ATTRIBUTE_VALUE){this._stateBeforeAttributeValue(c)}else if(this._state===IN_ATTRIBUTE_VALUE_DQ){this._stateInAttributeValueDoubleQuotes(c)}else if(this._state===IN_ATTRIBUTE_VALUE_SQ){this._stateInAttributeValueSingleQuotes(c)}else if(this._state===IN_ATTRIBUTE_VALUE_NQ){this._stateInAttributeValueNoQuotes(c)}else if(this._state===BEFORE_DECLARATION){this._stateBeforeDeclaration(c)}else if(this._state===IN_DECLARATION){this._stateInDeclaration(c)}else if(this._state===IN_PROCESSING_INSTRUCTION){this._stateInProcessingInstruction(c)}else if(this._state===BEFORE_COMMENT){this._stateBeforeComment(c)}else if(this._state===IN_COMMENT){this._stateInComment(c)}else if(this._state===AFTER_COMMENT_1){this._stateAfterComment1(c)}else if(this._state===AFTER_COMMENT_2){this._stateAfterComment2(c)}else if(this._state===BEFORE_CDATA_1){this._stateBeforeCdata1(c)}else if(this._state===BEFORE_CDATA_2){this._stateBeforeCdata2(c)}else if(this._state===BEFORE_CDATA_3){this._stateBeforeCdata3(c)}else if(this._state===BEFORE_CDATA_4){this._stateBeforeCdata4(c)}else if(this._state===BEFORE_CDATA_5){this._stateBeforeCdata5(c)}else if(this._state===BEFORE_CDATA_6){this._stateBeforeCdata6(c)}else if(this._state===IN_CDATA){this._stateInCdata(c)}else if(this._state===AFTER_CDATA_1){this._stateAfterCdata1(c)}else if(this._state===AFTER_CDATA_2){this._stateAfterCdata2(c)}else if(this._state===BEFORE_SPECIAL){this._stateBeforeSpecial(c)}else if(this._state===BEFORE_SPECIAL_END){this._stateBeforeSpecialEnd(c)}else if(this._state===BEFORE_SCRIPT_1){this._stateBeforeScript1(c)}else if(this._state===BEFORE_SCRIPT_2){this._stateBeforeScript2(c)}else if(this._state===BEFORE_SCRIPT_3){this._stateBeforeScript3(c)}else if(this._state===BEFORE_SCRIPT_4){this._stateBeforeScript4(c)}else if(this._state===BEFORE_SCRIPT_5){this._stateBeforeScript5(c)}else if(this._state===AFTER_SCRIPT_1){this._stateAfterScript1(c)}else if(this._state===AFTER_SCRIPT_2){this._stateAfterScript2(c)}else if(this._state===AFTER_SCRIPT_3){this._stateAfterScript3(c)}else if(this._state===AFTER_SCRIPT_4){this._stateAfterScript4(c)}else if(this._state===AFTER_SCRIPT_5){this._stateAfterScript5(c)}else if(this._state===BEFORE_STYLE_1){this._stateBeforeStyle1(c)}else if(this._state===BEFORE_STYLE_2){this._stateBeforeStyle2(c)}else if(this._state===BEFORE_STYLE_3){this._stateBeforeStyle3(c)}else if(this._state===BEFORE_STYLE_4){this._stateBeforeStyle4(c)}else if(this._state===AFTER_STYLE_1){this._stateAfterStyle1(c)}else if(this._state===AFTER_STYLE_2){this._stateAfterStyle2(c)}else if(this._state===AFTER_STYLE_3){this._stateAfterStyle3(c)}else if(this._state===AFTER_STYLE_4){this._stateAfterStyle4(c)}else if(this._state===BEFORE_ENTITY){this._stateBeforeEntity(c)}else if(this._state===BEFORE_NUMERIC_ENTITY){this._stateBeforeNumericEntity(c)}else if(this._state===IN_NAMED_ENTITY){this._stateInNamedEntity(c)}else if(this._state===IN_NUMERIC_ENTITY){this._stateInNumericEntity(c)}else if(this._state===IN_HEX_ENTITY){this._stateInHexEntity(c)}else{this._cbs.onerror(Error(\"unknown _state\"),this._state)}this._index++}this._cleanup()};Tokenizer.prototype.pause=function(){this._running=false};Tokenizer.prototype.resume=function(){this._running=true;if(this._index<this._buffer.length){this._parse()}if(this._ended){this._finish()}};Tokenizer.prototype.end=function(chunk){if(this._ended)this._cbs.onerror(Error(\".end() after done!\"));if(chunk)this.write(chunk);this._ended=true;if(this._running)this._finish()};Tokenizer.prototype._finish=function(){if(this._sectionStart<this._index){this._handleTrailingData()}this._cbs.onend()};Tokenizer.prototype._handleTrailingData=function(){var data=this._buffer.substr(this._sectionStart);if(this._state===IN_CDATA||this._state===AFTER_CDATA_1||this._state===AFTER_CDATA_2){this._cbs.oncdata(data)}else if(this._state===IN_COMMENT||this._state===AFTER_COMMENT_1||this._state===AFTER_COMMENT_2){this._cbs.oncomment(data)}else if(this._state===IN_NAMED_ENTITY&&!this._xmlMode){this._parseLegacyEntity();if(this._sectionStart<this._index){this._state=this._baseState;this._handleTrailingData()}}else if(this._state===IN_NUMERIC_ENTITY&&!this._xmlMode){this._decodeNumericEntity(2,10);if(this._sectionStart<this._index){this._state=this._baseState;this._handleTrailingData()}}else if(this._state===IN_HEX_ENTITY&&!this._xmlMode){this._decodeNumericEntity(3,16);if(this._sectionStart<this._index){this._state=this._baseState;this._handleTrailingData()}}else if(this._state!==IN_TAG_NAME&&this._state!==BEFORE_ATTRIBUTE_NAME&&this._state!==BEFORE_ATTRIBUTE_VALUE&&this._state!==AFTER_ATTRIBUTE_NAME&&this._state!==IN_ATTRIBUTE_NAME&&this._state!==IN_ATTRIBUTE_VALUE_SQ&&this._state!==IN_ATTRIBUTE_VALUE_DQ&&this._state!==IN_ATTRIBUTE_VALUE_NQ&&this._state!==IN_CLOSING_TAG_NAME){\nthis._cbs.ontext(data)}};Tokenizer.prototype.reset=function(){Tokenizer.call(this,{xmlMode:this._xmlMode,decodeEntities:this._decodeEntities},this._cbs)};Tokenizer.prototype.getAbsoluteIndex=function(){return this._bufferOffset+this._index};Tokenizer.prototype._getSection=function(){return this._buffer.substring(this._sectionStart,this._index)};Tokenizer.prototype._emitToken=function(name){this._cbs[name](this._getSection());this._sectionStart=-1};Tokenizer.prototype._emitPartial=function(value){if(this._baseState!==TEXT){this._cbs.onattribdata(value)}else{this._cbs.ontext(value)}}},{\"entities/lib/decode_codepoint.js\":22,\"entities/maps/entities.json\":25,\"entities/maps/legacy.json\":26,\"entities/maps/xml.json\":27}],35:[function(require,module,exports){module.exports=Stream;var Parser=require(\"./Parser.js\"),WritableStream=require(\"stream\").Writable||require(\"readable-stream\").Writable,StringDecoder=require(\"string_decoder\").StringDecoder,Buffer=require(\"buffer\").Buffer;function Stream(cbs,options){var parser=this._parser=new Parser(cbs,options);var decoder=this._decoder=new StringDecoder;WritableStream.call(this,{decodeStrings:false});this.once(\"finish\",function(){parser.end(decoder.end())})}require(\"inherits\")(Stream,WritableStream);WritableStream.prototype._write=function(chunk,encoding,cb){if(chunk instanceof Buffer)chunk=this._decoder.write(chunk);this._parser.write(chunk);cb()}},{\"./Parser.js\":31,buffer:5,inherits:38,\"readable-stream\":3,stream:55,string_decoder:56}],36:[function(require,module,exports){var Parser=require(\"./Parser.js\"),DomHandler=require(\"domhandler\");function defineProp(name,value){delete module.exports[name];module.exports[name]=value;return value}module.exports={Parser:Parser,Tokenizer:require(\"./Tokenizer.js\"),ElementType:require(\"domelementtype\"),DomHandler:DomHandler,get FeedHandler(){return defineProp(\"FeedHandler\",require(\"./FeedHandler.js\"))},get Stream(){return defineProp(\"Stream\",require(\"./Stream.js\"))},get WritableStream(){return defineProp(\"WritableStream\",require(\"./WritableStream.js\"))},get ProxyHandler(){return defineProp(\"ProxyHandler\",require(\"./ProxyHandler.js\"))},get DomUtils(){return defineProp(\"DomUtils\",require(\"domutils\"))},get CollectingHandler(){return defineProp(\"CollectingHandler\",require(\"./CollectingHandler.js\"))},DefaultHandler:DomHandler,get RssHandler(){return defineProp(\"RssHandler\",this.FeedHandler)},parseDOM:function(data,options){var handler=new DomHandler(options);new Parser(handler,options).end(data);return handler.dom},parseFeed:function(feed,options){var handler=new module.exports.FeedHandler(options);new Parser(handler,options).end(feed);return handler.dom},createDomStream:function(cb,options,elementCb){var handler=new DomHandler(cb,options,elementCb);return new Parser(handler,options)},EVENTS:{attribute:2,cdatastart:0,cdataend:0,text:1,processinginstruction:2,comment:1,commentend:0,closetag:1,opentag:2,opentagname:1,error:1,end:0}}},{\"./CollectingHandler.js\":29,\"./FeedHandler.js\":30,\"./Parser.js\":31,\"./ProxyHandler.js\":32,\"./Stream.js\":33,\"./Tokenizer.js\":34,\"./WritableStream.js\":35,domelementtype:9,domhandler:10,domutils:13}],37:[function(require,module,exports){exports.read=function(buffer,offset,isLE,mLen,nBytes){var e,m;var eLen=nBytes*8-mLen-1;var eMax=(1<<eLen)-1;var eBias=eMax>>1;var nBits=-7;var i=isLE?nBytes-1:0;var d=isLE?-1:1;var s=buffer[offset+i];i+=d;e=s&(1<<-nBits)-1;s>>=-nBits;nBits+=eLen;for(;nBits>0;e=e*256+buffer[offset+i],i+=d,nBits-=8){}m=e&(1<<-nBits)-1;e>>=-nBits;nBits+=mLen;for(;nBits>0;m=m*256+buffer[offset+i],i+=d,nBits-=8){}if(e===0){e=1-eBias}else if(e===eMax){return m?NaN:(s?-1:1)*Infinity}else{m=m+Math.pow(2,mLen);e=e-eBias}return(s?-1:1)*m*Math.pow(2,e-mLen)};exports.write=function(buffer,value,offset,isLE,mLen,nBytes){var e,m,c;var eLen=nBytes*8-mLen-1;var eMax=(1<<eLen)-1;var eBias=eMax>>1;var rt=mLen===23?Math.pow(2,-24)-Math.pow(2,-77):0;var i=isLE?0:nBytes-1;var d=isLE?1:-1;var s=value<0||value===0&&1/value<0?1:0;value=Math.abs(value);if(isNaN(value)||value===Infinity){m=isNaN(value)?1:0;e=eMax}else{e=Math.floor(Math.log(value)/Math.LN2);if(value*(c=Math.pow(2,-e))<1){e--;c*=2}if(e+eBias>=1){value+=rt/c}else{value+=rt*Math.pow(2,1-eBias)}if(value*c>=2){e++;c/=2}if(e+eBias>=eMax){m=0;e=eMax}else if(e+eBias>=1){m=(value*c-1)*Math.pow(2,mLen);e=e+eBias}else{m=value*Math.pow(2,eBias-1)*Math.pow(2,mLen);e=0}}for(;mLen>=8;buffer[offset+i]=m&255,i+=d,m/=256,mLen-=8){}e=e<<mLen|m;eLen+=mLen;for(;eLen>0;buffer[offset+i]=e&255,i+=d,e/=256,eLen-=8){}buffer[offset+i-d]|=s*128}},{}],38:[function(require,module,exports){if(typeof Object.create===\"function\"){module.exports=function inherits(ctor,superCtor){ctor.super_=superCtor;ctor.prototype=Object.create(superCtor.prototype,{constructor:{value:ctor,enumerable:false,writable:true,configurable:true}})}}else{module.exports=function inherits(ctor,superCtor){ctor.super_=superCtor;var TempCtor=function(){};TempCtor.prototype=superCtor.prototype;ctor.prototype=new TempCtor;ctor.prototype.constructor=ctor}}},{}],39:[function(require,module,exports){module.exports=function(obj){return obj!=null&&(isBuffer(obj)||isSlowBuffer(obj)||!!obj._isBuffer)};function isBuffer(obj){return!!obj.constructor&&typeof obj.constructor.isBuffer===\"function\"&&obj.constructor.isBuffer(obj)}function isSlowBuffer(obj){return typeof obj.readFloatLE===\"function\"&&typeof obj.slice===\"function\"&&isBuffer(obj.slice(0,0))}},{}],40:[function(require,module,exports){var toString={}.toString;module.exports=Array.isArray||function(arr){return toString.call(arr)==\"[object Array]\"}},{}],41:[function(require,module,exports){(function(process){\"use strict\";if(!process.version||process.version.indexOf(\"v0.\")===0||process.version.indexOf(\"v1.\")===0&&process.version.indexOf(\"v1.8.\")!==0){module.exports=nextTick}else{module.exports=process.nextTick}function nextTick(fn,arg1,arg2,arg3){if(typeof fn!==\"function\"){throw new TypeError('\"callback\" argument must be a function')}var len=arguments.length;var args,i;switch(len){case 0:case 1:return process.nextTick(fn);case 2:return process.nextTick(function afterTickOne(){fn.call(null,arg1)});case 3:return process.nextTick(function afterTickTwo(){fn.call(null,arg1,arg2)});case 4:return process.nextTick(function afterTickThree(){fn.call(null,arg1,arg2,arg3)});default:args=new Array(len-1);i=0;while(i<args.length){args[i++]=arguments[i]}return process.nextTick(function afterTick(){fn.apply(null,args)})}}}).call(this,require(\"_process\"))},{_process:42}],42:[function(require,module,exports){var process=module.exports={};var cachedSetTimeout;var cachedClearTimeout;function defaultSetTimout(){throw new Error(\"setTimeout has not been defined\")}function defaultClearTimeout(){throw new Error(\"clearTimeout has not been defined\")}(function(){try{if(typeof setTimeout===\"function\"){cachedSetTimeout=setTimeout}else{cachedSetTimeout=defaultSetTimout}}catch(e){cachedSetTimeout=defaultSetTimout}try{if(typeof clearTimeout===\"function\"){cachedClearTimeout=clearTimeout}else{cachedClearTimeout=defaultClearTimeout}}catch(e){cachedClearTimeout=defaultClearTimeout}})();function runTimeout(fun){if(cachedSetTimeout===setTimeout){return setTimeout(fun,0)}if((cachedSetTimeout===defaultSetTimout||!cachedSetTimeout)&&setTimeout){cachedSetTimeout=setTimeout;return setTimeout(fun,0)}try{return cachedSetTimeout(fun,0)}catch(e){try{return cachedSetTimeout.call(null,fun,0)}catch(e){return cachedSetTimeout.call(this,fun,0)}}}function runClearTimeout(marker){if(cachedClearTimeout===clearTimeout){return clearTimeout(marker)}if((cachedClearTimeout===defaultClearTimeout||!cachedClearTimeout)&&clearTimeout){cachedClearTimeout=clearTimeout;return clearTimeout(marker)}try{return cachedClearTimeout(marker)}catch(e){try{return cachedClearTimeout.call(null,marker)}catch(e){return cachedClearTimeout.call(this,marker)}}}var queue=[];var draining=false;var currentQueue;var queueIndex=-1;function cleanUpNextTick(){if(!draining||!currentQueue){return}draining=false;if(currentQueue.length){queue=currentQueue.concat(queue)}else{queueIndex=-1}if(queue.length){drainQueue()}}function drainQueue(){if(draining){return}var timeout=runTimeout(cleanUpNextTick);draining=true;var len=queue.length;while(len){currentQueue=queue;queue=[];while(++queueIndex<len){if(currentQueue){currentQueue[queueIndex].run()}}queueIndex=-1;len=queue.length}currentQueue=null;draining=false;runClearTimeout(timeout)}process.nextTick=function(fun){var args=new Array(arguments.length-1);if(arguments.length>1){for(var i=1;i<arguments.length;i++){args[i-1]=arguments[i]}}queue.push(new Item(fun,args));if(queue.length===1&&!draining){runTimeout(drainQueue)}};function Item(fun,array){this.fun=fun;this.array=array}Item.prototype.run=function(){this.fun.apply(null,this.array)};process.title=\"browser\";process.browser=true;process.env={};process.argv=[];process.version=\"\";process.versions={};function noop(){}process.on=noop;process.addListener=noop;process.once=noop;process.off=noop;process.removeListener=noop;process.removeAllListeners=noop;process.emit=noop;process.binding=function(name){throw new Error(\"process.binding is not supported\")};process.cwd=function(){return\"/\"};process.chdir=function(dir){throw new Error(\"process.chdir is not supported\")};process.umask=function(){return 0}},{}],43:[function(require,module,exports){module.exports=require(\"./lib/_stream_duplex.js\")},{\"./lib/_stream_duplex.js\":44}],44:[function(require,module,exports){\"use strict\";var objectKeys=Object.keys||function(obj){var keys=[];for(var key in obj){keys.push(key)}return keys};module.exports=Duplex;var processNextTick=require(\"process-nextick-args\");var util=require(\"core-util-is\");util.inherits=require(\"inherits\");var Readable=require(\"./_stream_readable\");var Writable=require(\"./_stream_writable\");util.inherits(Duplex,Readable);var keys=objectKeys(Writable.prototype);for(var v=0;v<keys.length;v++){var method=keys[v];if(!Duplex.prototype[method])Duplex.prototype[method]=Writable.prototype[method]}function Duplex(options){if(!(this instanceof Duplex))return new Duplex(options);Readable.call(this,options);Writable.call(this,options);if(options&&options.readable===false)this.readable=false;if(options&&options.writable===false)this.writable=false;this.allowHalfOpen=true;if(options&&options.allowHalfOpen===false)this.allowHalfOpen=false;this.once(\"end\",onend)}function onend(){if(this.allowHalfOpen||this._writableState.ended)return;processNextTick(onEndNT,this)}function onEndNT(self){self.end()}function forEach(xs,f){for(var i=0,l=xs.length;i<l;i++){f(xs[i],i)}}},{\"./_stream_readable\":46,\"./_stream_writable\":48,\"core-util-is\":6,inherits:38,\"process-nextick-args\":41}],45:[function(require,module,exports){\"use strict\";module.exports=PassThrough;var Transform=require(\"./_stream_transform\");var util=require(\"core-util-is\");util.inherits=require(\"inherits\");util.inherits(PassThrough,Transform);function PassThrough(options){if(!(this instanceof PassThrough))return new PassThrough(options);Transform.call(this,options)}PassThrough.prototype._transform=function(chunk,encoding,cb){cb(null,chunk)}},{\"./_stream_transform\":47,\"core-util-is\":6,inherits:38}],46:[function(require,module,exports){(function(process){\"use strict\";module.exports=Readable;var processNextTick=require(\"process-nextick-args\");var isArray=require(\"isarray\");Readable.ReadableState=ReadableState;var EE=require(\"events\").EventEmitter;var EElistenerCount=function(emitter,type){return emitter.listeners(type).length};var Stream;(function(){try{Stream=require(\"st\"+\"ream\")}catch(_){}finally{if(!Stream)Stream=require(\"events\").EventEmitter}})();var Buffer=require(\"buffer\").Buffer;var bufferShim=require(\"buffer-shims\");var util=require(\"core-util-is\");util.inherits=require(\"inherits\");var debugUtil=require(\"util\");var debug=void 0;if(debugUtil&&debugUtil.debuglog){debug=debugUtil.debuglog(\"stream\")}else{debug=function(){}}var BufferList=require(\"./internal/streams/BufferList\");var StringDecoder;util.inherits(Readable,Stream);function prependListener(emitter,event,fn){if(typeof emitter.prependListener===\"function\"){return emitter.prependListener(event,fn)}else{if(!emitter._events||!emitter._events[event])emitter.on(event,fn);else if(isArray(emitter._events[event]))emitter._events[event].unshift(fn);else emitter._events[event]=[fn,emitter._events[event]]}}var Duplex;function ReadableState(options,stream){Duplex=Duplex||require(\"./_stream_duplex\");options=options||{};this.objectMode=!!options.objectMode;if(stream instanceof Duplex)this.objectMode=this.objectMode||!!options.readableObjectMode;var hwm=options.highWaterMark;var defaultHwm=this.objectMode?16:16*1024;this.highWaterMark=hwm||hwm===0?hwm:defaultHwm;this.highWaterMark=~~this.highWaterMark;this.buffer=new BufferList;this.length=0;this.pipes=null;this.pipesCount=0;this.flowing=null;this.ended=false;this.endEmitted=false;this.reading=false;this.sync=true;this.needReadable=false;this.emittedReadable=false;this.readableListening=false;this.resumeScheduled=false;this.defaultEncoding=options.defaultEncoding||\"utf8\";this.ranOut=false;this.awaitDrain=0;this.readingMore=false;this.decoder=null;this.encoding=null;if(options.encoding){if(!StringDecoder)StringDecoder=require(\"string_decoder/\").StringDecoder;this.decoder=new StringDecoder(options.encoding);this.encoding=options.encoding}}var Duplex;function Readable(options){Duplex=Duplex||require(\"./_stream_duplex\");if(!(this instanceof Readable))return new Readable(options);this._readableState=new ReadableState(options,this);this.readable=true;if(options&&typeof options.read===\"function\")this._read=options.read;Stream.call(this)}Readable.prototype.push=function(chunk,encoding){var state=this._readableState;if(!state.objectMode&&typeof chunk===\"string\"){encoding=encoding||state.defaultEncoding;if(encoding!==state.encoding){chunk=bufferShim.from(chunk,encoding);encoding=\"\"}}return readableAddChunk(this,state,chunk,encoding,false)};Readable.prototype.unshift=function(chunk){var state=this._readableState;return readableAddChunk(this,state,chunk,\"\",true)};Readable.prototype.isPaused=function(){return this._readableState.flowing===false};function readableAddChunk(stream,state,chunk,encoding,addToFront){var er=chunkInvalid(state,chunk);if(er){stream.emit(\"error\",er)}else if(chunk===null){state.reading=false;onEofChunk(stream,state)}else if(state.objectMode||chunk&&chunk.length>0){if(state.ended&&!addToFront){var e=new Error(\"stream.push() after EOF\");stream.emit(\"error\",e)}else if(state.endEmitted&&addToFront){var _e=new Error(\"stream.unshift() after end event\");stream.emit(\"error\",_e)}else{var skipAdd;if(state.decoder&&!addToFront&&!encoding){chunk=state.decoder.write(chunk);skipAdd=!state.objectMode&&chunk.length===0}if(!addToFront)state.reading=false;if(!skipAdd){if(state.flowing&&state.length===0&&!state.sync){stream.emit(\"data\",chunk);stream.read(0)}else{state.length+=state.objectMode?1:chunk.length;if(addToFront)state.buffer.unshift(chunk);else state.buffer.push(chunk);if(state.needReadable)emitReadable(stream)}}maybeReadMore(stream,state)}}else if(!addToFront){state.reading=false}return needMoreData(state)}function needMoreData(state){return!state.ended&&(state.needReadable||state.length<state.highWaterMark||state.length===0)}Readable.prototype.setEncoding=function(enc){if(!StringDecoder)StringDecoder=require(\"string_decoder/\").StringDecoder;this._readableState.decoder=new StringDecoder(enc);this._readableState.encoding=enc;return this};var MAX_HWM=8388608;function computeNewHighWaterMark(n){if(n>=MAX_HWM){n=MAX_HWM}else{n--;n|=n>>>1;n|=n>>>2;n|=n>>>4;n|=n>>>8;n|=n>>>16;n++}return n}function howMuchToRead(n,state){if(n<=0||state.length===0&&state.ended)return 0;if(state.objectMode)return 1;if(n!==n){if(state.flowing&&state.length)return state.buffer.head.data.length;else return state.length}if(n>state.highWaterMark)state.highWaterMark=computeNewHighWaterMark(n);if(n<=state.length)return n;if(!state.ended){state.needReadable=true;return 0}return state.length}Readable.prototype.read=function(n){debug(\"read\",n);n=parseInt(n,10);var state=this._readableState;var nOrig=n;if(n!==0)state.emittedReadable=false;if(n===0&&state.needReadable&&(state.length>=state.highWaterMark||state.ended)){debug(\"read: emitReadable\",state.length,state.ended);if(state.length===0&&state.ended)endReadable(this);else emitReadable(this);return null}n=howMuchToRead(n,state);if(n===0&&state.ended){if(state.length===0)endReadable(this);return null}var doRead=state.needReadable;debug(\"need readable\",doRead);if(state.length===0||state.length-n<state.highWaterMark){doRead=true;debug(\"length less than watermark\",doRead)}if(state.ended||state.reading){doRead=false;debug(\"reading or ended\",doRead)}else if(doRead){debug(\"do read\");state.reading=true;state.sync=true;if(state.length===0)state.needReadable=true;this._read(state.highWaterMark);state.sync=false;if(!state.reading)n=howMuchToRead(nOrig,state)}var ret;if(n>0)ret=fromList(n,state);else ret=null;if(ret===null){state.needReadable=true;n=0}else{state.length-=n}if(state.length===0){if(!state.ended)state.needReadable=true;if(nOrig!==n&&state.ended)endReadable(this)}if(ret!==null)this.emit(\"data\",ret);return ret};function chunkInvalid(state,chunk){var er=null;if(!Buffer.isBuffer(chunk)&&typeof chunk!==\"string\"&&chunk!==null&&chunk!==undefined&&!state.objectMode){er=new TypeError(\"Invalid non-string/buffer chunk\")}return er}function onEofChunk(stream,state){if(state.ended)return;if(state.decoder){var chunk=state.decoder.end();if(chunk&&chunk.length){state.buffer.push(chunk);state.length+=state.objectMode?1:chunk.length}}state.ended=true;emitReadable(stream)}function emitReadable(stream){var state=stream._readableState;state.needReadable=false;if(!state.emittedReadable){debug(\"emitReadable\",state.flowing);state.emittedReadable=true;if(state.sync)processNextTick(emitReadable_,stream);else emitReadable_(stream)}}function emitReadable_(stream){debug(\"emit readable\");stream.emit(\"readable\");flow(stream)}function maybeReadMore(stream,state){if(!state.readingMore){state.readingMore=true;processNextTick(maybeReadMore_,stream,state)}}function maybeReadMore_(stream,state){var len=state.length;while(!state.reading&&!state.flowing&&!state.ended&&state.length<state.highWaterMark){debug(\"maybeReadMore read 0\");stream.read(0);if(len===state.length)break;else len=state.length}state.readingMore=false}Readable.prototype._read=function(n){this.emit(\"error\",new Error(\"not implemented\"))};Readable.prototype.pipe=function(dest,pipeOpts){var src=this;var state=this._readableState;switch(state.pipesCount){case 0:state.pipes=dest;break;case 1:state.pipes=[state.pipes,dest];break;default:state.pipes.push(dest);break}state.pipesCount+=1;debug(\"pipe count=%d opts=%j\",state.pipesCount,pipeOpts);var doEnd=(!pipeOpts||pipeOpts.end!==false)&&dest!==process.stdout&&dest!==process.stderr;var endFn=doEnd?onend:cleanup;if(state.endEmitted)processNextTick(endFn);else src.once(\"end\",endFn);dest.on(\"unpipe\",onunpipe);function onunpipe(readable){debug(\"onunpipe\");if(readable===src){cleanup()}}function onend(){debug(\"onend\");dest.end()}var ondrain=pipeOnDrain(src);dest.on(\"drain\",ondrain);var cleanedUp=false;function cleanup(){debug(\"cleanup\");dest.removeListener(\"close\",onclose);dest.removeListener(\"finish\",onfinish);dest.removeListener(\"drain\",ondrain);dest.removeListener(\"error\",onerror);dest.removeListener(\"unpipe\",onunpipe);src.removeListener(\"end\",onend);src.removeListener(\"end\",cleanup);src.removeListener(\"data\",ondata);cleanedUp=true;if(state.awaitDrain&&(!dest._writableState||dest._writableState.needDrain))ondrain()}var increasedAwaitDrain=false;src.on(\"data\",ondata);function ondata(chunk){debug(\"ondata\");increasedAwaitDrain=false;var ret=dest.write(chunk);if(false===ret&&!increasedAwaitDrain){if((state.pipesCount===1&&state.pipes===dest||state.pipesCount>1&&indexOf(state.pipes,dest)!==-1)&&!cleanedUp){debug(\"false write response, pause\",src._readableState.awaitDrain);src._readableState.awaitDrain++;increasedAwaitDrain=true}src.pause()}}function onerror(er){debug(\"onerror\",er);unpipe();dest.removeListener(\"error\",onerror);if(EElistenerCount(dest,\"error\")===0)dest.emit(\"error\",er)}prependListener(dest,\"error\",onerror);function onclose(){dest.removeListener(\"finish\",onfinish);unpipe()}dest.once(\"close\",onclose);function onfinish(){debug(\"onfinish\");dest.removeListener(\"close\",onclose);unpipe()}dest.once(\"finish\",onfinish);function unpipe(){debug(\"unpipe\");src.unpipe(dest)}dest.emit(\"pipe\",src);if(!state.flowing){debug(\"pipe resume\");src.resume()}return dest};function pipeOnDrain(src){return function(){var state=src._readableState;debug(\"pipeOnDrain\",state.awaitDrain);if(state.awaitDrain)state.awaitDrain--;if(state.awaitDrain===0&&EElistenerCount(src,\"data\")){state.flowing=true;flow(src)}}}Readable.prototype.unpipe=function(dest){var state=this._readableState;if(state.pipesCount===0)return this;if(state.pipesCount===1){if(dest&&dest!==state.pipes)return this;if(!dest)dest=state.pipes;state.pipes=null;state.pipesCount=0;state.flowing=false;if(dest)dest.emit(\"unpipe\",this);return this}if(!dest){var dests=state.pipes;var len=state.pipesCount;state.pipes=null;state.pipesCount=0;state.flowing=false;for(var _i=0;_i<len;_i++){dests[_i].emit(\"unpipe\",this)}return this}var i=indexOf(state.pipes,dest);if(i===-1)return this;state.pipes.splice(i,1);state.pipesCount-=1;if(state.pipesCount===1)state.pipes=state.pipes[0];dest.emit(\"unpipe\",this);return this};Readable.prototype.on=function(ev,fn){var res=Stream.prototype.on.call(this,ev,fn);if(ev===\"data\"){if(this._readableState.flowing!==false)this.resume()}else if(ev===\"readable\"){var state=this._readableState;if(!state.endEmitted&&!state.readableListening){state.readableListening=state.needReadable=true;state.emittedReadable=false;if(!state.reading){processNextTick(nReadingNextTick,this)}else if(state.length){emitReadable(this,state)}}}return res};Readable.prototype.addListener=Readable.prototype.on;function nReadingNextTick(self){debug(\"readable nexttick read 0\");self.read(0)}Readable.prototype.resume=function(){var state=this._readableState;if(!state.flowing){debug(\"resume\");state.flowing=true;resume(this,state)}return this};function resume(stream,state){if(!state.resumeScheduled){state.resumeScheduled=true;processNextTick(resume_,stream,state)}}function resume_(stream,state){if(!state.reading){debug(\"resume read 0\");stream.read(0)}state.resumeScheduled=false;state.awaitDrain=0;stream.emit(\"resume\");flow(stream);if(state.flowing&&!state.reading)stream.read(0)}Readable.prototype.pause=function(){debug(\"call pause flowing=%j\",this._readableState.flowing);if(false!==this._readableState.flowing){debug(\"pause\");this._readableState.flowing=false;this.emit(\"pause\")}return this};function flow(stream){var state=stream._readableState;debug(\"flow\",state.flowing);while(state.flowing&&stream.read()!==null){}}Readable.prototype.wrap=function(stream){var state=this._readableState;var paused=false;var self=this;stream.on(\"end\",function(){debug(\"wrapped end\");if(state.decoder&&!state.ended){var chunk=state.decoder.end();if(chunk&&chunk.length)self.push(chunk)}self.push(null)});stream.on(\"data\",function(chunk){debug(\"wrapped data\");if(state.decoder)chunk=state.decoder.write(chunk);if(state.objectMode&&(chunk===null||chunk===undefined))return;else if(!state.objectMode&&(!chunk||!chunk.length))return;var ret=self.push(chunk);if(!ret){paused=true;stream.pause()}});for(var i in stream){if(this[i]===undefined&&typeof stream[i]===\"function\"){this[i]=function(method){return function(){return stream[method].apply(stream,arguments)}}(i)}}var events=[\"error\",\"close\",\"destroy\",\"pause\",\"resume\"];forEach(events,function(ev){stream.on(ev,self.emit.bind(self,ev))});self._read=function(n){debug(\"wrapped _read\",n);if(paused){paused=false;stream.resume()}};return self};Readable._fromList=fromList;function fromList(n,state){if(state.length===0)return null;var ret;if(state.objectMode)ret=state.buffer.shift();else if(!n||n>=state.length){if(state.decoder)ret=state.buffer.join(\"\");else if(state.buffer.length===1)ret=state.buffer.head.data;else ret=state.buffer.concat(state.length);state.buffer.clear()}else{ret=fromListPartial(n,state.buffer,state.decoder)}return ret}function fromListPartial(n,list,hasStrings){var ret;if(n<list.head.data.length){ret=list.head.data.slice(0,n);list.head.data=list.head.data.slice(n)}else if(n===list.head.data.length){ret=list.shift()}else{ret=hasStrings?copyFromBufferString(n,list):copyFromBuffer(n,list)}return ret}function copyFromBufferString(n,list){var p=list.head;var c=1;var ret=p.data;n-=ret.length;while(p=p.next){var str=p.data;var nb=n>str.length?str.length:n;if(nb===str.length)ret+=str;else ret+=str.slice(0,n);n-=nb;if(n===0){if(nb===str.length){++c;if(p.next)list.head=p.next;else list.head=list.tail=null}else{list.head=p;p.data=str.slice(nb)}break}++c}list.length-=c;return ret}function copyFromBuffer(n,list){var ret=bufferShim.allocUnsafe(n);var p=list.head;var c=1;p.data.copy(ret);n-=p.data.length;while(p=p.next){var buf=p.data;var nb=n>buf.length?buf.length:n;buf.copy(ret,ret.length-n,0,nb);n-=nb;if(n===0){if(nb===buf.length){++c;if(p.next)list.head=p.next;else list.head=list.tail=null}else{list.head=p;p.data=buf.slice(nb)}break}++c}list.length-=c;return ret}function endReadable(stream){var state=stream._readableState;if(state.length>0)throw new Error('\"endReadable()\" called on non-empty stream');if(!state.endEmitted){state.ended=true;processNextTick(endReadableNT,state,stream)}}function endReadableNT(state,stream){if(!state.endEmitted&&state.length===0){state.endEmitted=true;stream.readable=false;stream.emit(\"end\")}}function forEach(xs,f){for(var i=0,l=xs.length;i<l;i++){f(xs[i],i)}}function indexOf(xs,x){for(var i=0,l=xs.length;i<l;i++){if(xs[i]===x)return i}return-1}}).call(this,require(\"_process\"))},{\"./_stream_duplex\":44,\"./internal/streams/BufferList\":49,_process:42,buffer:5,\"buffer-shims\":4,\"core-util-is\":6,events:28,inherits:38,isarray:40,\"process-nextick-args\":41,\"string_decoder/\":56,util:3}],47:[function(require,module,exports){\"use strict\";module.exports=Transform;var Duplex=require(\"./_stream_duplex\");var util=require(\"core-util-is\");util.inherits=require(\"inherits\");util.inherits(Transform,Duplex);function TransformState(stream){this.afterTransform=function(er,data){return afterTransform(stream,er,data)};this.needTransform=false;this.transforming=false;this.writecb=null;this.writechunk=null;this.writeencoding=null}function afterTransform(stream,er,data){var ts=stream._transformState;ts.transforming=false;var cb=ts.writecb;if(!cb)return stream.emit(\"error\",new Error(\"no writecb in Transform class\"));ts.writechunk=null;ts.writecb=null;if(data!==null&&data!==undefined)stream.push(data);cb(er);var rs=stream._readableState;rs.reading=false;if(rs.needReadable||rs.length<rs.highWaterMark){stream._read(rs.highWaterMark)}}function Transform(options){if(!(this instanceof Transform))return new Transform(options);Duplex.call(this,options);this._transformState=new TransformState(this);var stream=this;this._readableState.needReadable=true;this._readableState.sync=false;if(options){if(typeof options.transform===\"function\")this._transform=options.transform;if(typeof options.flush===\"function\")this._flush=options.flush}this.once(\"prefinish\",function(){if(typeof this._flush===\"function\")this._flush(function(er){done(stream,er)});else done(stream)})}Transform.prototype.push=function(chunk,encoding){this._transformState.needTransform=false;return Duplex.prototype.push.call(this,chunk,encoding)};Transform.prototype._transform=function(chunk,encoding,cb){throw new Error(\"Not implemented\")};Transform.prototype._write=function(chunk,encoding,cb){var ts=this._transformState;ts.writecb=cb;ts.writechunk=chunk;ts.writeencoding=encoding;if(!ts.transforming){var rs=this._readableState;if(ts.needTransform||rs.needReadable||rs.length<rs.highWaterMark)this._read(rs.highWaterMark)}};Transform.prototype._read=function(n){var ts=this._transformState;if(ts.writechunk!==null&&ts.writecb&&!ts.transforming){ts.transforming=true;this._transform(ts.writechunk,ts.writeencoding,ts.afterTransform)}else{ts.needTransform=true}};function done(stream,er){if(er)return stream.emit(\"error\",er);var ws=stream._writableState;var ts=stream._transformState;if(ws.length)throw new Error(\"Calling transform done when ws.length != 0\");if(ts.transforming)throw new Error(\"Calling transform done when still transforming\");return stream.push(null)}},{\"./_stream_duplex\":44,\"core-util-is\":6,inherits:38}],48:[function(require,module,exports){(function(process){\"use strict\";module.exports=Writable;var processNextTick=require(\"process-nextick-args\");var asyncWrite=!process.browser&&[\"v0.10\",\"v0.9.\"].indexOf(process.version.slice(0,5))>-1?setImmediate:processNextTick;Writable.WritableState=WritableState;var util=require(\"core-util-is\");util.inherits=require(\"inherits\");var internalUtil={deprecate:require(\"util-deprecate\")};var Stream;(function(){try{Stream=require(\"st\"+\"ream\")}catch(_){}finally{if(!Stream)Stream=require(\"events\").EventEmitter}})();var Buffer=require(\"buffer\").Buffer;var bufferShim=require(\"buffer-shims\");util.inherits(Writable,Stream);function nop(){}function WriteReq(chunk,encoding,cb){this.chunk=chunk;this.encoding=encoding;this.callback=cb;this.next=null}var Duplex;function WritableState(options,stream){Duplex=Duplex||require(\"./_stream_duplex\");options=options||{};this.objectMode=!!options.objectMode;if(stream instanceof Duplex)this.objectMode=this.objectMode||!!options.writableObjectMode;var hwm=options.highWaterMark;var defaultHwm=this.objectMode?16:16*1024;this.highWaterMark=hwm||hwm===0?hwm:defaultHwm;this.highWaterMark=~~this.highWaterMark;this.needDrain=false;this.ending=false;this.ended=false;this.finished=false;var noDecode=options.decodeStrings===false;this.decodeStrings=!noDecode;this.defaultEncoding=options.defaultEncoding||\"utf8\";this.length=0;this.writing=false;this.corked=0;this.sync=true;this.bufferProcessing=false;this.onwrite=function(er){onwrite(stream,er)};this.writecb=null;this.writelen=0;this.bufferedRequest=null;this.lastBufferedRequest=null;this.pendingcb=0;this.prefinished=false;this.errorEmitted=false;this.bufferedRequestCount=0;this.corkedRequestsFree=new CorkedRequest(this)}WritableState.prototype.getBuffer=function writableStateGetBuffer(){var current=this.bufferedRequest;var out=[];while(current){out.push(current);current=current.next}return out};(function(){try{Object.defineProperty(WritableState.prototype,\"buffer\",{get:internalUtil.deprecate(function(){return this.getBuffer()},\"_writableState.buffer is deprecated. Use _writableState.getBuffer \"+\"instead.\")})}catch(_){}})();var Duplex;function Writable(options){Duplex=Duplex||require(\"./_stream_duplex\");if(!(this instanceof Writable)&&!(this instanceof Duplex))return new Writable(options);this._writableState=new WritableState(options,this);this.writable=true;if(options){if(typeof options.write===\"function\")this._write=options.write;if(typeof options.writev===\"function\")this._writev=options.writev}Stream.call(this)}Writable.prototype.pipe=function(){this.emit(\"error\",new Error(\"Cannot pipe, not readable\"))};function writeAfterEnd(stream,cb){var er=new Error(\"write after end\");stream.emit(\"error\",er);processNextTick(cb,er)}function validChunk(stream,state,chunk,cb){var valid=true;var er=false;if(chunk===null){er=new TypeError(\"May not write null values to stream\")}else if(!Buffer.isBuffer(chunk)&&typeof chunk!==\"string\"&&chunk!==undefined&&!state.objectMode){er=new TypeError(\"Invalid non-string/buffer chunk\")}if(er){stream.emit(\"error\",er);processNextTick(cb,er);valid=false}return valid}Writable.prototype.write=function(chunk,encoding,cb){var state=this._writableState;var ret=false;if(typeof encoding===\"function\"){cb=encoding;encoding=null}if(Buffer.isBuffer(chunk))encoding=\"buffer\";else if(!encoding)encoding=state.defaultEncoding;if(typeof cb!==\"function\")cb=nop;if(state.ended)writeAfterEnd(this,cb);else if(validChunk(this,state,chunk,cb)){\nstate.pendingcb++;ret=writeOrBuffer(this,state,chunk,encoding,cb)}return ret};Writable.prototype.cork=function(){var state=this._writableState;state.corked++};Writable.prototype.uncork=function(){var state=this._writableState;if(state.corked){state.corked--;if(!state.writing&&!state.corked&&!state.finished&&!state.bufferProcessing&&state.bufferedRequest)clearBuffer(this,state)}};Writable.prototype.setDefaultEncoding=function setDefaultEncoding(encoding){if(typeof encoding===\"string\")encoding=encoding.toLowerCase();if(!([\"hex\",\"utf8\",\"utf-8\",\"ascii\",\"binary\",\"base64\",\"ucs2\",\"ucs-2\",\"utf16le\",\"utf-16le\",\"raw\"].indexOf((encoding+\"\").toLowerCase())>-1))throw new TypeError(\"Unknown encoding: \"+encoding);this._writableState.defaultEncoding=encoding;return this};function decodeChunk(state,chunk,encoding){if(!state.objectMode&&state.decodeStrings!==false&&typeof chunk===\"string\"){chunk=bufferShim.from(chunk,encoding)}return chunk}function writeOrBuffer(stream,state,chunk,encoding,cb){chunk=decodeChunk(state,chunk,encoding);if(Buffer.isBuffer(chunk))encoding=\"buffer\";var len=state.objectMode?1:chunk.length;state.length+=len;var ret=state.length<state.highWaterMark;if(!ret)state.needDrain=true;if(state.writing||state.corked){var last=state.lastBufferedRequest;state.lastBufferedRequest=new WriteReq(chunk,encoding,cb);if(last){last.next=state.lastBufferedRequest}else{state.bufferedRequest=state.lastBufferedRequest}state.bufferedRequestCount+=1}else{doWrite(stream,state,false,len,chunk,encoding,cb)}return ret}function doWrite(stream,state,writev,len,chunk,encoding,cb){state.writelen=len;state.writecb=cb;state.writing=true;state.sync=true;if(writev)stream._writev(chunk,state.onwrite);else stream._write(chunk,encoding,state.onwrite);state.sync=false}function onwriteError(stream,state,sync,er,cb){--state.pendingcb;if(sync)processNextTick(cb,er);else cb(er);stream._writableState.errorEmitted=true;stream.emit(\"error\",er)}function onwriteStateUpdate(state){state.writing=false;state.writecb=null;state.length-=state.writelen;state.writelen=0}function onwrite(stream,er){var state=stream._writableState;var sync=state.sync;var cb=state.writecb;onwriteStateUpdate(state);if(er)onwriteError(stream,state,sync,er,cb);else{var finished=needFinish(state);if(!finished&&!state.corked&&!state.bufferProcessing&&state.bufferedRequest){clearBuffer(stream,state)}if(sync){asyncWrite(afterWrite,stream,state,finished,cb)}else{afterWrite(stream,state,finished,cb)}}}function afterWrite(stream,state,finished,cb){if(!finished)onwriteDrain(stream,state);state.pendingcb--;cb();finishMaybe(stream,state)}function onwriteDrain(stream,state){if(state.length===0&&state.needDrain){state.needDrain=false;stream.emit(\"drain\")}}function clearBuffer(stream,state){state.bufferProcessing=true;var entry=state.bufferedRequest;if(stream._writev&&entry&&entry.next){var l=state.bufferedRequestCount;var buffer=new Array(l);var holder=state.corkedRequestsFree;holder.entry=entry;var count=0;while(entry){buffer[count]=entry;entry=entry.next;count+=1}doWrite(stream,state,true,state.length,buffer,\"\",holder.finish);state.pendingcb++;state.lastBufferedRequest=null;if(holder.next){state.corkedRequestsFree=holder.next;holder.next=null}else{state.corkedRequestsFree=new CorkedRequest(state)}}else{while(entry){var chunk=entry.chunk;var encoding=entry.encoding;var cb=entry.callback;var len=state.objectMode?1:chunk.length;doWrite(stream,state,false,len,chunk,encoding,cb);entry=entry.next;if(state.writing){break}}if(entry===null)state.lastBufferedRequest=null}state.bufferedRequestCount=0;state.bufferedRequest=entry;state.bufferProcessing=false}Writable.prototype._write=function(chunk,encoding,cb){cb(new Error(\"not implemented\"))};Writable.prototype._writev=null;Writable.prototype.end=function(chunk,encoding,cb){var state=this._writableState;if(typeof chunk===\"function\"){cb=chunk;chunk=null;encoding=null}else if(typeof encoding===\"function\"){cb=encoding;encoding=null}if(chunk!==null&&chunk!==undefined)this.write(chunk,encoding);if(state.corked){state.corked=1;this.uncork()}if(!state.ending&&!state.finished)endWritable(this,state,cb)};function needFinish(state){return state.ending&&state.length===0&&state.bufferedRequest===null&&!state.finished&&!state.writing}function prefinish(stream,state){if(!state.prefinished){state.prefinished=true;stream.emit(\"prefinish\")}}function finishMaybe(stream,state){var need=needFinish(state);if(need){if(state.pendingcb===0){prefinish(stream,state);state.finished=true;stream.emit(\"finish\")}else{prefinish(stream,state)}}return need}function endWritable(stream,state,cb){state.ending=true;finishMaybe(stream,state);if(cb){if(state.finished)processNextTick(cb);else stream.once(\"finish\",cb)}state.ended=true;stream.writable=false}function CorkedRequest(state){var _this=this;this.next=null;this.entry=null;this.finish=function(err){var entry=_this.entry;_this.entry=null;while(entry){var cb=entry.callback;state.pendingcb--;cb(err);entry=entry.next}if(state.corkedRequestsFree){state.corkedRequestsFree.next=_this}else{state.corkedRequestsFree=_this}}}}).call(this,require(\"_process\"))},{\"./_stream_duplex\":44,_process:42,buffer:5,\"buffer-shims\":4,\"core-util-is\":6,events:28,inherits:38,\"process-nextick-args\":41,\"util-deprecate\":57}],49:[function(require,module,exports){\"use strict\";var Buffer=require(\"buffer\").Buffer;var bufferShim=require(\"buffer-shims\");module.exports=BufferList;function BufferList(){this.head=null;this.tail=null;this.length=0}BufferList.prototype.push=function(v){var entry={data:v,next:null};if(this.length>0)this.tail.next=entry;else this.head=entry;this.tail=entry;++this.length};BufferList.prototype.unshift=function(v){var entry={data:v,next:this.head};if(this.length===0)this.tail=entry;this.head=entry;++this.length};BufferList.prototype.shift=function(){if(this.length===0)return;var ret=this.head.data;if(this.length===1)this.head=this.tail=null;else this.head=this.head.next;--this.length;return ret};BufferList.prototype.clear=function(){this.head=this.tail=null;this.length=0};BufferList.prototype.join=function(s){if(this.length===0)return\"\";var p=this.head;var ret=\"\"+p.data;while(p=p.next){ret+=s+p.data}return ret};BufferList.prototype.concat=function(n){if(this.length===0)return bufferShim.alloc(0);if(this.length===1)return this.head.data;var ret=bufferShim.allocUnsafe(n>>>0);var p=this.head;var i=0;while(p){p.data.copy(ret,i);i+=p.data.length;p=p.next}return ret}},{buffer:5,\"buffer-shims\":4}],50:[function(require,module,exports){module.exports=require(\"./lib/_stream_passthrough.js\")},{\"./lib/_stream_passthrough.js\":45}],51:[function(require,module,exports){(function(process){var Stream=function(){try{return require(\"st\"+\"ream\")}catch(_){}}();exports=module.exports=require(\"./lib/_stream_readable.js\");exports.Stream=Stream||exports;exports.Readable=exports;exports.Writable=require(\"./lib/_stream_writable.js\");exports.Duplex=require(\"./lib/_stream_duplex.js\");exports.Transform=require(\"./lib/_stream_transform.js\");exports.PassThrough=require(\"./lib/_stream_passthrough.js\");if(!process.browser&&process.env.READABLE_STREAM===\"disable\"&&Stream){module.exports=Stream}}).call(this,require(\"_process\"))},{\"./lib/_stream_duplex.js\":44,\"./lib/_stream_passthrough.js\":45,\"./lib/_stream_readable.js\":46,\"./lib/_stream_transform.js\":47,\"./lib/_stream_writable.js\":48,_process:42}],52:[function(require,module,exports){module.exports=require(\"./lib/_stream_transform.js\")},{\"./lib/_stream_transform.js\":47}],53:[function(require,module,exports){module.exports=require(\"./lib/_stream_writable.js\")},{\"./lib/_stream_writable.js\":48}],54:[function(require,module,exports){module.exports=function(string){return string.replace(/[-\\\\^$*+?.()|[\\]{}]/g,\"\\\\$&\")}},{}],55:[function(require,module,exports){module.exports=Stream;var EE=require(\"events\").EventEmitter;var inherits=require(\"inherits\");inherits(Stream,EE);Stream.Readable=require(\"readable-stream/readable.js\");Stream.Writable=require(\"readable-stream/writable.js\");Stream.Duplex=require(\"readable-stream/duplex.js\");Stream.Transform=require(\"readable-stream/transform.js\");Stream.PassThrough=require(\"readable-stream/passthrough.js\");Stream.Stream=Stream;function Stream(){EE.call(this)}Stream.prototype.pipe=function(dest,options){var source=this;function ondata(chunk){if(dest.writable){if(false===dest.write(chunk)&&source.pause){source.pause()}}}source.on(\"data\",ondata);function ondrain(){if(source.readable&&source.resume){source.resume()}}dest.on(\"drain\",ondrain);if(!dest._isStdio&&(!options||options.end!==false)){source.on(\"end\",onend);source.on(\"close\",onclose)}var didOnEnd=false;function onend(){if(didOnEnd)return;didOnEnd=true;dest.end()}function onclose(){if(didOnEnd)return;didOnEnd=true;if(typeof dest.destroy===\"function\")dest.destroy()}function onerror(er){cleanup();if(EE.listenerCount(this,\"error\")===0){throw er}}source.on(\"error\",onerror);dest.on(\"error\",onerror);function cleanup(){source.removeListener(\"data\",ondata);dest.removeListener(\"drain\",ondrain);source.removeListener(\"end\",onend);source.removeListener(\"close\",onclose);source.removeListener(\"error\",onerror);dest.removeListener(\"error\",onerror);source.removeListener(\"end\",cleanup);source.removeListener(\"close\",cleanup);dest.removeListener(\"close\",cleanup)}source.on(\"end\",cleanup);source.on(\"close\",cleanup);dest.on(\"close\",cleanup);dest.emit(\"pipe\",source);return dest}},{events:28,inherits:38,\"readable-stream/duplex.js\":43,\"readable-stream/passthrough.js\":50,\"readable-stream/readable.js\":51,\"readable-stream/transform.js\":52,\"readable-stream/writable.js\":53}],56:[function(require,module,exports){var Buffer=require(\"buffer\").Buffer;var isBufferEncoding=Buffer.isEncoding||function(encoding){switch(encoding&&encoding.toLowerCase()){case\"hex\":case\"utf8\":case\"utf-8\":case\"ascii\":case\"binary\":case\"base64\":case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":case\"raw\":return true;default:return false}};function assertEncoding(encoding){if(encoding&&!isBufferEncoding(encoding)){throw new Error(\"Unknown encoding: \"+encoding)}}var StringDecoder=exports.StringDecoder=function(encoding){this.encoding=(encoding||\"utf8\").toLowerCase().replace(/[-_]/,\"\");assertEncoding(encoding);switch(this.encoding){case\"utf8\":this.surrogateSize=3;break;case\"ucs2\":case\"utf16le\":this.surrogateSize=2;this.detectIncompleteChar=utf16DetectIncompleteChar;break;case\"base64\":this.surrogateSize=3;this.detectIncompleteChar=base64DetectIncompleteChar;break;default:this.write=passThroughWrite;return}this.charBuffer=new Buffer(6);this.charReceived=0;this.charLength=0};StringDecoder.prototype.write=function(buffer){var charStr=\"\";while(this.charLength){var available=buffer.length>=this.charLength-this.charReceived?this.charLength-this.charReceived:buffer.length;buffer.copy(this.charBuffer,this.charReceived,0,available);this.charReceived+=available;if(this.charReceived<this.charLength){return\"\"}buffer=buffer.slice(available,buffer.length);charStr=this.charBuffer.slice(0,this.charLength).toString(this.encoding);var charCode=charStr.charCodeAt(charStr.length-1);if(charCode>=55296&&charCode<=56319){this.charLength+=this.surrogateSize;charStr=\"\";continue}this.charReceived=this.charLength=0;if(buffer.length===0){return charStr}break}this.detectIncompleteChar(buffer);var end=buffer.length;if(this.charLength){buffer.copy(this.charBuffer,0,buffer.length-this.charReceived,end);end-=this.charReceived}charStr+=buffer.toString(this.encoding,0,end);var end=charStr.length-1;var charCode=charStr.charCodeAt(end);if(charCode>=55296&&charCode<=56319){var size=this.surrogateSize;this.charLength+=size;this.charReceived+=size;this.charBuffer.copy(this.charBuffer,size,0,size);buffer.copy(this.charBuffer,0,0,size);return charStr.substring(0,end)}return charStr};StringDecoder.prototype.detectIncompleteChar=function(buffer){var i=buffer.length>=3?3:buffer.length;for(;i>0;i--){var c=buffer[buffer.length-i];if(i==1&&c>>5==6){this.charLength=2;break}if(i<=2&&c>>4==14){this.charLength=3;break}if(i<=3&&c>>3==30){this.charLength=4;break}}this.charReceived=i};StringDecoder.prototype.end=function(buffer){var res=\"\";if(buffer&&buffer.length)res=this.write(buffer);if(this.charReceived){var cr=this.charReceived;var buf=this.charBuffer;var enc=this.encoding;res+=buf.slice(0,cr).toString(enc)}return res};function passThroughWrite(buffer){return buffer.toString(this.encoding)}function utf16DetectIncompleteChar(buffer){this.charReceived=buffer.length%2;this.charLength=this.charReceived?2:0}function base64DetectIncompleteChar(buffer){this.charReceived=buffer.length%3;this.charLength=this.charReceived?3:0}},{buffer:5}],57:[function(require,module,exports){(function(global){module.exports=deprecate;function deprecate(fn,msg){if(config(\"noDeprecation\")){return fn}var warned=false;function deprecated(){if(!warned){if(config(\"throwDeprecation\")){throw new Error(msg)}else if(config(\"traceDeprecation\")){console.trace(msg)}else{console.warn(msg)}warned=true}return fn.apply(this,arguments)}return deprecated}function config(name){try{if(!global.localStorage)return false}catch(_){return false}var val=global.localStorage[name];if(null==val)return false;return String(val).toLowerCase()===\"true\"}}).call(this,typeof global!==\"undefined\"?global:typeof self!==\"undefined\"?self:typeof window!==\"undefined\"?window:{})},{}],58:[function(require,module,exports){module.exports=extend;var hasOwnProperty=Object.prototype.hasOwnProperty;function extend(){var target={};for(var i=0;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(hasOwnProperty.call(source,key)){target[key]=source[key]}}}return target}},{}]},{},[1])(1)});\n\n/**\n * swagger-client - swagger-client is a javascript client for use with swaggering APIs.\n * @version v2.1.23\n * @link http://swagger.io\n * @license Apache-2.0\n */\n(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.SwaggerClient = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){\n'use strict';\n\nvar auth = require('./lib/auth');\nvar helpers = require('./lib/helpers');\nvar SwaggerClient = require('./lib/client');\nvar deprecationWrapper = function (url, options) {\n  helpers.log('This is deprecated, use \"new SwaggerClient\" instead.');\n\n  return new SwaggerClient(url, options);\n};\n\n/* Here for IE8 Support */\nif (!Array.prototype.indexOf) {\n  Array.prototype.indexOf = function(obj, start) {\n    for (var i = (start || 0), j = this.length; i < j; i++) {\n      if (this[i] === obj) { return i; }\n    }\n    return -1;\n  };\n}\n\n/* Here for IE8 Support */\nif (!String.prototype.trim) {\n  String.prototype.trim = function () {\n    return this.replace(/^\\s+|\\s+$/g, '');\n  };\n}\n\n/* Here for node 10.x support */\nif (!String.prototype.endsWith) {\n  String.prototype.endsWith = function(suffix) {\n    return this.indexOf(suffix, this.length - suffix.length) !== -1;\n  };\n}\n\nmodule.exports = SwaggerClient;\n\nSwaggerClient.ApiKeyAuthorization = auth.ApiKeyAuthorization;\nSwaggerClient.PasswordAuthorization = auth.PasswordAuthorization;\nSwaggerClient.CookieAuthorization = auth.CookieAuthorization;\nSwaggerClient.SwaggerApi = deprecationWrapper;\nSwaggerClient.SwaggerClient = deprecationWrapper;\nSwaggerClient.SchemaMarkup = require('./lib/schema-markup');\n\n},{\"./lib/auth\":2,\"./lib/client\":3,\"./lib/helpers\":4,\"./lib/schema-markup\":7}],2:[function(require,module,exports){\n'use strict';\n\nvar helpers = require('./helpers');\nvar btoa = require('btoa'); // jshint ignore:line\nvar CookieJar = require('cookiejar').CookieJar;\nvar _ = {\n  each: require('lodash-compat/collection/each'),\n  includes: require('lodash-compat/collection/includes'),\n  isObject: require('lodash-compat/lang/isObject'),\n  isArray: require('lodash-compat/lang/isArray')\n};\n\n/**\n * SwaggerAuthorizations applies the correct authorization to an operation being executed\n */\nvar SwaggerAuthorizations = module.exports.SwaggerAuthorizations = function (authz) {\n  this.authz = authz || {};\n};\n\n/**\n * Add auths to the hash\n * Will overwrite any existing\n *\n */\nSwaggerAuthorizations.prototype.add = function (name, auth) {\n  if(_.isObject(name)) {\n    for (var key in name) {\n      this.authz[key] = name[key];\n    }\n  } else if(typeof name === 'string' ){\n    this.authz[name] = auth;\n  }\n\n  return auth;\n};\n\nSwaggerAuthorizations.prototype.remove = function (name) {\n  return delete this.authz[name];\n};\n\nSwaggerAuthorizations.prototype.apply = function (obj, securities) {\n  var status = true;\n  var applyAll = !securities;\n  var flattenedSecurities = [];\n\n  // favor the object-level authorizations over global\n  var authz = obj.clientAuthorizations || this.authz;\n\n  // Securities could be [ {} ]\n  _.each(securities, function (obj, key) {\n\n    // Make sure we account for securities being [ str ]\n    if(typeof key === 'string') {\n      flattenedSecurities.push(key);\n    }\n\n    // Flatten keys in to our array\n    _.each(obj, function (val, key) {\n      flattenedSecurities.push(key);\n    });\n  });\n\n  _.each(authz, function (auth, authName) {\n    if(applyAll || _.includes(flattenedSecurities, authName)) {\n      var newStatus = auth.apply(obj);\n      status = status && !!newStatus; // logical ORs regarding status\n    }\n  });\n\n  return status;\n};\n\n/**\n * ApiKeyAuthorization allows a query param or header to be injected\n */\nvar ApiKeyAuthorization = module.exports.ApiKeyAuthorization = function (name, value, type) {\n  this.name = name;\n  this.value = value;\n  this.type = type;\n};\n\nApiKeyAuthorization.prototype.apply = function (obj) {\n  if (this.type === 'query') {\n    // see if already applied.  If so, don't do it again\n\n    var qp;\n    if (obj.url.indexOf('?') > 0) {\n      qp = obj.url.substring(obj.url.indexOf('?') + 1);\n      var parts = qp.split('&');\n      if(parts && parts.length > 0) {\n        for(var i = 0; i < parts.length; i++) {\n          var kv = parts[i].split('=');\n          if(kv && kv.length > 0) {\n            if (kv[0] === this.name) {\n              // skip it\n              return false;\n            }\n          }\n        }\n      }\n    }\n\n    if (obj.url.indexOf('?') > 0) {\n      obj.url = obj.url + '&' + this.name + '=' + this.value;\n    } else {\n      obj.url = obj.url + '?' + this.name + '=' + this.value;\n    }\n\n    return true;\n  } else if (this.type === 'header') {\n    if(typeof obj.headers[this.name] === 'undefined') {\n      obj.headers[this.name] = this.value;\n    }\n\n    return true;\n  }\n};\n\nvar CookieAuthorization = module.exports.CookieAuthorization = function (cookie) {\n  this.cookie = cookie;\n};\n\nCookieAuthorization.prototype.apply = function (obj) {\n  obj.cookieJar = obj.cookieJar || new CookieJar();\n  obj.cookieJar.setCookie(this.cookie);\n\n  return true;\n};\n\n/**\n * Password Authorization is a basic auth implementation\n */\nvar PasswordAuthorization = module.exports.PasswordAuthorization = function (username, password) {\n  if (arguments.length === 3) {\n    helpers.log('PasswordAuthorization: the \\'name\\' argument has been removed, pass only username and password');\n    username = arguments[1];\n    password = arguments[2];\n  }\n  this.username = username;\n  this.password = password;\n};\n\nPasswordAuthorization.prototype.apply = function (obj) {\n  if(typeof obj.headers.Authorization === 'undefined') {\n    obj.headers.Authorization = 'Basic ' + btoa(this.username + ':' + this.password);\n  }\n\n  return true;\n};\n\n},{\"./helpers\":4,\"btoa\":13,\"cookiejar\":18,\"lodash-compat/collection/each\":52,\"lodash-compat/collection/includes\":55,\"lodash-compat/lang/isArray\":140,\"lodash-compat/lang/isObject\":144}],3:[function(require,module,exports){\n'use strict';\n\nvar _ = {\n  bind: require('lodash-compat/function/bind'),\n  cloneDeep: require('lodash-compat/lang/cloneDeep'),\n  find: require('lodash-compat/collection/find'),\n  forEach: require('lodash-compat/collection/forEach'),\n  indexOf: require('lodash-compat/array/indexOf'),\n  isArray: require('lodash-compat/lang/isArray'),\n  isObject: require('lodash-compat/lang/isObject'),\n  isFunction: require('lodash-compat/lang/isFunction'),\n  isPlainObject: require('lodash-compat/lang/isPlainObject'),\n  isUndefined: require('lodash-compat/lang/isUndefined')\n};\nvar auth = require('./auth');\nvar helpers = require('./helpers');\nvar Model = require('./types/model');\nvar Operation = require('./types/operation');\nvar OperationGroup = require('./types/operationGroup');\nvar Resolver = require('./resolver');\nvar SwaggerHttp = require('./http');\nvar SwaggerSpecConverter = require('./spec-converter');\nvar Q = require('q');\n\n// We have to keep track of the function/property names to avoid collisions for tag names which are used to allow the\n// following usage: 'client.{tagName}'\nvar reservedClientTags = [\n  'apis',\n  'authorizationScheme',\n  'authorizations',\n  'basePath',\n  'build',\n  'buildFrom1_1Spec',\n  'buildFrom1_2Spec',\n  'buildFromSpec',\n  'clientAuthorizations',\n  'convertInfo',\n  'debug',\n  'defaultErrorCallback',\n  'defaultSuccessCallback',\n  'enableCookies',\n  'fail',\n  'failure',\n  'finish',\n  'help',\n  'host',\n  'idFromOp',\n  'info',\n  'initialize',\n  'isBuilt',\n  'isValid',\n  'modelPropertyMacro',\n  'models',\n  'modelsArray',\n  'options',\n  'parameterMacro',\n  'parseUri',\n  'progress',\n  'resourceCount',\n  'sampleModels',\n  'selfReflect',\n  'setConsolidatedModels',\n  'spec',\n  'supportedSubmitMethods',\n  'swaggerRequestHeaders',\n  'tagFromLabel',\n  'title',\n  'url',\n  'useJQuery',\n  'jqueryAjaxCache'\n];\n// We have to keep track of the function/property names to avoid collisions for tag names which are used to allow the\n// following usage: 'client.apis.{tagName}'\nvar reservedApiTags = [\n  'apis',\n  'asCurl',\n  'description',\n  'externalDocs',\n  'help',\n  'label',\n  'name',\n  'operation',\n  'operations',\n  'operationsArray',\n  'path',\n  'tag'\n];\nvar supportedOperationMethods = ['delete', 'get', 'head', 'options', 'patch', 'post', 'put'];\nvar SwaggerClient = module.exports = function (url, options) {\n  this.authorizations = null;\n  this.authorizationScheme = null;\n  this.basePath = null;\n  this.debug = false;\n  this.enableCookies = false;\n  this.info = null;\n  this.isBuilt = false;\n  this.isValid = false;\n  this.modelsArray = [];\n  this.resourceCount = 0;\n  this.url = null;\n  this.useJQuery = false;\n  this.jqueryAjaxCache = false;\n  this.swaggerObject = {};\n  this.deferredClient = undefined;\n\n  this.clientAuthorizations = new auth.SwaggerAuthorizations();\n\n  if (typeof url !== 'undefined') {\n    return this.initialize(url, options);\n  } else {\n    return this;\n  }\n};\n\nSwaggerClient.prototype.initialize = function (url, options) {\n  this.models = {};\n  this.sampleModels = {};\n\n  if (typeof url === 'string') {\n    this.url = url;\n  } else if (_.isObject(url)) {\n    options = url;\n    this.url = options.url;\n  }\n\n  if(this.url && this.url.indexOf('http:') === -1 && this.url.indexOf('https:') === -1) {\n    // no protocol, so we can only use window if it exists\n    if(typeof(window) !== 'undefined' && window && window.location) {\n      this.url = window.location.origin + this.url;\n    }\n  }\n\n  options = options || {};\n  this.clientAuthorizations.add(options.authorizations);\n  this.swaggerRequestHeaders = options.swaggerRequestHeaders || 'application/json;charset=utf-8,*/*';\n  this.defaultSuccessCallback = options.defaultSuccessCallback || null;\n  this.defaultErrorCallback = options.defaultErrorCallback || null;\n  this.modelPropertyMacro = options.modelPropertyMacro || null;\n  this.connectionAgent = options.connectionAgent || null;\n  this.parameterMacro = options.parameterMacro || null;\n  this.usePromise = options.usePromise || null;\n\n  // operation request timeout default\n  this.timeout = options.timeout || null;\n  // default to request timeout when not specified\n  this.fetchSpecTimeout = typeof options.fetchSpecTimeout !== 'undefined' ?\n      options.fetchSpecTimeout : options.timeout || null;\n\n  if(this.usePromise) {\n    this.deferredClient = Q.defer();\n  }\n\n  if (typeof options.success === 'function') {\n    this.success = options.success;\n  }\n  if (options.useJQuery) {\n    this.useJQuery = options.useJQuery;\n  }\n\n  if (options.jqueryAjaxCache) {\n    this.jqueryAjaxCache = options.jqueryAjaxCache;\n  }\n\n  if (options.enableCookies) {\n    this.enableCookies = options.enableCookies;\n  }\n\n  this.options = options || {};\n\n  // maybe don't need this?\n  this.options.timeout = this.timeout;\n  this.options.fetchSpecTimeout = this.fetchSpecTimeout;\n\n  this.supportedSubmitMethods = options.supportedSubmitMethods || [];\n  this.failure = options.failure || function (err) { throw err; };\n  this.progress = options.progress || function () {};\n  this.spec = _.cloneDeep(options.spec); // Clone so we do not alter the provided document\n\n  if (options.scheme) {\n    this.scheme = options.scheme;\n  }\n\n  if (this.usePromise || typeof options.success === 'function') {\n    this.ready = true;\n    return this.build();\n  }\n};\n\nSwaggerClient.prototype.build = function (mock) {\n  if (this.isBuilt) {\n    return this;\n  }\n\n  var self = this;\n\n  if (this.spec) {\n    this.progress('fetching resource list; Please wait.');\n  } else {\n    this.progress('fetching resource list: ' + this.url + '; Please wait.');\n  }\n\n  var obj = {\n    useJQuery: this.useJQuery,\n    jqueryAjaxCache: this.jqueryAjaxCache,\n    connectionAgent: this.connectionAgent,\n    url: this.url,\n    method: 'get',\n    headers: {\n      accept: this.swaggerRequestHeaders\n    },\n    on: {\n      error: function (response) {\n        if (self.url.substring(0, 4) !== 'http') {\n          return self.fail('Please specify the protocol for ' + self.url);\n        } else if (response.errObj && (response.errObj.code === 'ECONNABORTED' || response.errObj.message.indexOf('timeout') !== -1)) {\n          return self.fail('Request timed out after ' + self.fetchSpecTimeout + 'ms');\n        } else if (response.status === 0) {\n          return self.fail('Can\\'t read from server.  It may not have the appropriate access-control-origin settings.');\n        } else if (response.status === 404) {\n          return self.fail('Can\\'t read swagger JSON from ' + self.url);\n        } else {\n          return self.fail(response.status + ' : ' + response.statusText + ' ' + self.url);\n        }\n      },\n      response: function (resp) {\n\n        var responseObj = resp.obj;\n        if(!responseObj) {\n          return self.fail('failed to parse JSON/YAML response');\n        }\n\n        self.swaggerVersion = responseObj.swaggerVersion;\n        self.swaggerObject = responseObj;\n\n        if (responseObj.swagger && parseInt(responseObj.swagger) === 2) {\n          self.swaggerVersion = responseObj.swagger;\n\n          new Resolver().resolve(responseObj, self.url, self.buildFromSpec, self);\n\n          self.isValid = true;\n        } else {\n          var converter = new SwaggerSpecConverter();\n          self.oldSwaggerObject = self.swaggerObject;\n\n          converter.setDocumentationLocation(self.url);\n          converter.convert(responseObj, self.clientAuthorizations, self.options, function(spec) {\n            self.swaggerObject = spec;\n            new Resolver().resolve(spec, self.url, self.buildFromSpec, self);\n            self.isValid = true;\n          });\n        }\n      }\n    }\n  };\n\n  // only set timeout when specified\n  if (this.fetchSpecTimeout) {\n    obj.timeout = this.fetchSpecTimeout;\n  }\n\n  if (this.spec) {\n    self.swaggerObject = this.spec;\n    setTimeout(function () {\n      new Resolver().resolve(self.spec, self.url, self.buildFromSpec, self);\n    }, 10);\n  } else {\n    this.clientAuthorizations.apply(obj);\n\n    if (mock) {\n      return obj;\n    }\n\n    new SwaggerHttp().execute(obj, this.options);\n  }\n\n  return (this.usePromise) ? this.deferredClient.promise : this;\n};\n\nSwaggerClient.prototype.buildFromSpec = function (response) {\n  if (this.isBuilt) {\n    return this;\n  }\n\n  this.apis = {};\n  this.apisArray = [];\n  this.basePath = response.basePath || '';\n  this.consumes = response.consumes;\n  this.host = response.host || '';\n  this.info = response.info || {};\n  this.produces = response.produces;\n  this.schemes = response.schemes || [];\n  this.securityDefinitions = _.cloneDeep(response.securityDefinitions);\n  this.security = response.security;\n  this.title = response.title || '';\n\n  var key, definedTags = {}, k, location, self = this, i;\n\n  if (response.externalDocs) {\n    this.externalDocs = response.externalDocs;\n  }\n\n  // legacy support\n  this.authSchemes = this.securityDefinitions;\n\n  if(this.securityDefinitions) {\n    for(key in this.securityDefinitions) {\n      var securityDefinition = this.securityDefinitions[key];\n      securityDefinition.vendorExtensions = {};\n      for(var ext in securityDefinition) {\n        helpers.extractExtensions(ext, securityDefinition);\n        if (ext === 'scopes') {\n          var scopes = securityDefinition[ext];\n          if(typeof scopes === 'object') {\n            scopes.vendorExtensions = {};\n            for (var s in scopes) {\n              helpers.extractExtensions(s, scopes);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (Array.isArray(response.tags)) {\n    definedTags = {};\n\n    for (k = 0; k < response.tags.length; k++) {\n      var t = _.cloneDeep(response.tags[k]);\n      definedTags[t.name] = t;\n      for(i in t) {\n        if(i === 'externalDocs' && typeof t[i] === 'object') {\n          for(var j in t[i]) {\n            helpers.extractExtensions(j, t[i]);\n          }\n        }\n        helpers.extractExtensions(i, t);\n      }\n    }\n  }\n\n\n  if (typeof this.url === 'string') {\n    location = this.parseUri(this.url);\n    if (typeof this.scheme === 'undefined' && typeof this.schemes === 'undefined' || this.schemes.length === 0) {\n      if(typeof window !== 'undefined') {\n        // use the window scheme\n        this.scheme = window.location.protocol.replace(':','');\n      }\n      else {\n        this.scheme = location.scheme || 'http';\n      }\n    } else if (typeof this.scheme === 'undefined') {\n      if(typeof window !== 'undefined') {\n        var scheme = window.location.protocol.replace(':','');\n        if(scheme === 'https' && this.schemes.indexOf(scheme) === -1) {\n          // can't call http from https served page in a browser!\n          helpers.log('Cannot call a http server from https inside a browser!');\n          this.scheme = 'http';\n        }\n        else if(this.schemes.indexOf(scheme) !== -1) {\n          this.scheme = scheme;\n        }\n        else {\n          if(this.schemes.indexOf('https') !== -1) {\n            this.scheme = 'https';\n          }\n          else {\n            this.scheme = 'http';\n          }\n        }\n      }\n      else {\n        this.scheme = this.schemes[0] || location.scheme;\n      }\n    }\n\n    if (typeof this.host === 'undefined' || this.host === '') {\n      this.host = location.host;\n\n      if (location.port) {\n        this.host = this.host + ':' + location.port;\n      }\n    }\n  }\n  else {\n    if (typeof this.schemes === 'undefined' || this.schemes.length === 0) {\n      this.scheme = 'http';\n    }\n    else if (typeof this.scheme === 'undefined') {\n      this.scheme = this.schemes[0];\n    }\n  }\n\n  this.definitions = response.definitions;\n\n  for (key in this.definitions) {\n    var model = new Model(key, this.definitions[key], this.models, this.modelPropertyMacro);\n\n    if (model) {\n      this.models[key] = model;\n    }\n  }\n\n  // get paths, create functions for each operationId\n  \n  // Bind help to 'client.apis'\n  self.apis.help = _.bind(self.help, self);\n\n  _.forEach(response.paths, function (pathObj, path) {\n    // Only process a path if it's an object\n    if (!_.isPlainObject(pathObj)) {\n      return;\n    }\n\n    _.forEach(supportedOperationMethods, function (method) {\n      var operation = pathObj[method];\n\n      if (_.isUndefined(operation)) {\n        // Operation does not exist\n        return;\n      } else if (!_.isPlainObject(operation)) {\n        // Operation exists but it is not an Operation Object.  Since this is invalid, log it.\n        helpers.log('The \\'' + method + '\\' operation for \\'' + path + '\\' path is not an Operation Object');\n\n        return;\n      }\n\n      var tags = operation.tags;\n\n      if (_.isUndefined(tags) || !_.isArray(tags) || tags.length === 0) {\n        tags = operation.tags = [ 'default' ];\n      }\n\n      var operationId = self.idFromOp(path, method, operation);\n\n      var operationObject = new Operation(self,\n        operation.scheme,\n        operationId,\n        method,\n        path,\n        operation,\n        self.definitions,\n        self.models,\n        self.clientAuthorizations);\n\n      operationObject.vendorExtensions = {};\n      for(i in operation) {\n        helpers.extractExtensions(i, operationObject, operation[i]);\n      }\n      operationObject.externalDocs = operation.externalDocs;\n      if(operationObject.externalDocs) {\n        operationObject.externalDocs = _.cloneDeep(operationObject.externalDocs);\n        operationObject.externalDocs.vendorExtensions = {};\n        for(i in operationObject.externalDocs) {\n          helpers.extractExtensions(i, operationObject.externalDocs);\n        }\n      }\n\n      // bind self operation's execute command to the api\n      _.forEach(tags, function (tag) {\n        var clientProperty = _.indexOf(reservedClientTags, tag) > -1 ? '_' + tag : tag;\n        var apiProperty = _.indexOf(reservedApiTags, tag) > -1 ? '_' + tag : tag;\n        var operationGroup = self[clientProperty];\n\n        if (clientProperty !== tag) {\n          helpers.log('The \\'' + tag + '\\' tag conflicts with a SwaggerClient function/property name.  Use \\'client.' +\n                      clientProperty + '\\' or \\'client.apis.' + tag + '\\' instead of \\'client.' + tag + '\\'.');\n        }\n\n        if (apiProperty !== tag) {\n          helpers.log('The \\'' + tag + '\\' tag conflicts with a SwaggerClient operation function/property name.  Use ' +\n                      '\\'client.apis.' + apiProperty + '\\' instead of \\'client.apis.' + tag + '\\'.');\n        }\n\n        if (_.indexOf(reservedApiTags, operationId) > -1) {\n          helpers.log('The \\'' + operationId + '\\' operationId conflicts with a SwaggerClient operation ' +\n                      'function/property name.  Use \\'client.apis.' + apiProperty + '._' + operationId +\n                      '\\' instead of \\'client.apis.' + apiProperty + '.' + operationId + '\\'.');\n\n          operationId = '_' + operationId;\n          operationObject.nickname = operationId; // So 'client.apis.[tag].operationId.help() works properly\n        }\n\n        if (_.isUndefined(operationGroup)) {\n          operationGroup = self[clientProperty] = self.apis[apiProperty] = {};\n\n          operationGroup.operations = {};\n          operationGroup.label = apiProperty;\n          operationGroup.apis = {};\n\n          var tagDef = definedTags[tag];\n\n          if (!_.isUndefined(tagDef)) {\n            operationGroup.description = tagDef.description;\n            operationGroup.externalDocs = tagDef.externalDocs;\n            operationGroup.vendorExtensions = tagDef.vendorExtensions;\n          }\n\n          self[clientProperty].help = _.bind(self.help, operationGroup);\n          self.apisArray.push(new OperationGroup(tag, operationGroup.description, operationGroup.externalDocs, operationObject));\n        }\n\n        operationId = self.makeUniqueOperationId(operationId, self.apis[apiProperty]);\n\n        // Bind tag help\n        if (!_.isFunction(operationGroup.help)) {\n          operationGroup.help = _.bind(self.help, operationGroup);\n        }\n\n        // bind to the apis object\n        self.apis[apiProperty][operationId] = operationGroup[operationId] = _.bind(operationObject.execute,\n                                                                                  operationObject);\n        self.apis[apiProperty][operationId].help = operationGroup[operationId].help = _.bind(operationObject.help,\n                                                                                             operationObject);\n        self.apis[apiProperty][operationId].asCurl = operationGroup[operationId].asCurl = _.bind(operationObject.asCurl,\n                                                                                                 operationObject);\n\n        operationGroup.apis[operationId] = operationGroup.operations[operationId] = operationObject;\n\n        // legacy UI feature\n        var api = _.find(self.apisArray, function (api) {\n          return api.tag === tag;\n        });\n\n        if (api) {\n          api.operationsArray.push(operationObject);\n        }\n      });\n    });\n  });\n\n  // sort the apisArray according to the tags\n  var sortedApis = [];\n  _.forEach(Object.keys(definedTags), function (tag) {\n    var pos;\n    for(pos in self.apisArray) {\n      var _api = self.apisArray[pos];\n      if(_api && tag === _api.name) {\n        sortedApis.push(_api);\n        self.apisArray[pos] = null;\n      }\n    }\n  });\n  // add anything left\n  _.forEach(self.apisArray, function (api) {\n    if(api) {\n      sortedApis.push(api);\n    }\n  });\n  self.apisArray = sortedApis;\n\n  _.forEach(response.definitions, function (definitionObj, definition) {\n    definitionObj.id = definition.toLowerCase();\n    definitionObj.name = definition;\n    self.modelsArray.push(definitionObj);\n  });\n\n  this.isBuilt = true;\n\n  if (this.usePromise) {\n    this.isValid = true;\n    this.isBuilt = true;\n    this.deferredClient.resolve(this);\n\n    return this.deferredClient.promise;\n  }\n\n  if (this.success) {\n    this.success();\n  }\n\n  return this;\n};\n\nSwaggerClient.prototype.makeUniqueOperationId = function(operationId, api) {\n  var count = 0;\n  var name = operationId;\n\n  // make unique across this operation group\n  while(true) {\n    var matched = false;\n    _.forEach(api.operations, function (operation) {\n      if(operation.nickname === name) {\n        matched = true;\n      }\n    });\n    if(!matched) {\n      return name;\n    }\n    name = operationId + '_' + count;\n    count ++;\n  }\n\n  return operationId;\n};\n\nSwaggerClient.prototype.parseUri = function (uri) {\n  var urlParseRE = /^(((([^:\\/#\\?]+:)?(?:(\\/\\/)((?:(([^:@\\/#\\?]+)(?:\\:([^:@\\/#\\?]+))?)@)?(([^:\\/#\\?\\]\\[]+|\\[[^\\/\\]@#?]+\\])(?:\\:([0-9]+))?))?)?)?((\\/?(?:[^\\/\\?#]+\\/+)*)([^\\?#]*)))?(\\?[^#]+)?)(#.*)?/;\n  var parts = urlParseRE.exec(uri);\n\n  return {\n    scheme: parts[4] ? parts[4].replace(':','') : undefined,\n    host: parts[11],\n    port: parts[12],\n    path: parts[15]\n  };\n};\n\nSwaggerClient.prototype.help = function (dontPrint) {\n  var output = '';\n\n  if (this instanceof SwaggerClient) {\n    _.forEach(this.apis, function (api, name) {\n      if (_.isPlainObject(api)) {\n        output += 'operations for the \\'' + name + '\\' tag\\n';\n\n        _.forEach(api.operations, function (operation, name) {\n          output += '  * ' + name + ': ' + operation.summary + '\\n';\n        });\n      }\n    });\n  } else if (this instanceof OperationGroup || _.isPlainObject(this)) {\n    output += 'operations for the \\'' + this.label + '\\' tag\\n';\n\n    _.forEach(this.apis, function (operation, name) {\n      output += '  * ' + name + ': ' + operation.summary + '\\n';\n    });\n  }\n\n  if (dontPrint) {\n    return output;\n  } else {\n    helpers.log(output);\n\n    return output;\n  }\n};\n\nSwaggerClient.prototype.tagFromLabel = function (label) {\n  return label;\n};\n\nSwaggerClient.prototype.idFromOp = function (path, httpMethod, op) {\n  if(!op || !op.operationId) {\n    op = op || {};\n    op.operationId = httpMethod + '_' + path;\n  }\n  var opId = op.operationId.replace(/[\\s!@#$%^&*()_+=\\[{\\]};:<>|.\\/?,\\\\'\"\"-]/g, '_') || (path.substring(1) + '_' + httpMethod);\n\n  opId = opId.replace(/((_){2,})/g, '_');\n  opId = opId.replace(/^(_)*/g, '');\n  opId = opId.replace(/([_])*$/g, '');\n\n  return opId;\n};\n\nSwaggerClient.prototype.setHost = function (host) {\n  this.host = host;\n\n  if(this.apis) {\n    _.forEach(this.apis, function(api) {\n      if(api.operations) {\n        _.forEach(api.operations, function(operation) {\n          operation.host = host;\n        });\n      }\n    });\n  }\n};\n\nSwaggerClient.prototype.setBasePath = function (basePath) {\n  this.basePath = basePath;\n\n  if(this.apis) {\n    _.forEach(this.apis, function(api) {\n      if(api.operations) {\n        _.forEach(api.operations, function(operation) {\n          operation.basePath = basePath;\n        });\n      }\n    });\n  }\n};\n\nSwaggerClient.prototype.setSchemes = function (schemes) {\n  this.schemes = schemes;\n\n  if(schemes && schemes.length > 0) {\n    if(this.apis) {\n      _.forEach(this.apis, function (api) {\n        if (api.operations) {\n          _.forEach(api.operations, function (operation) {\n            operation.scheme = schemes[0];\n          });\n        }\n      });\n    }\n  }\n};\n\nSwaggerClient.prototype.fail = function (message) {\n  if (this.usePromise) {\n    this.deferredClient.reject(message);\n    return this.deferredClient.promise;\n  } else {\n    if (this.failure) {\n      this.failure(message);\n    }\n    else {\n      this.failure(message);\n    }\n  }\n};\n\n},{\"./auth\":2,\"./helpers\":4,\"./http\":5,\"./resolver\":6,\"./spec-converter\":8,\"./types/model\":9,\"./types/operation\":10,\"./types/operationGroup\":11,\"lodash-compat/array/indexOf\":49,\"lodash-compat/collection/find\":53,\"lodash-compat/collection/forEach\":54,\"lodash-compat/function/bind\":58,\"lodash-compat/lang/cloneDeep\":138,\"lodash-compat/lang/isArray\":140,\"lodash-compat/lang/isFunction\":142,\"lodash-compat/lang/isObject\":144,\"lodash-compat/lang/isPlainObject\":145,\"lodash-compat/lang/isUndefined\":148,\"q\":157}],4:[function(require,module,exports){\n(function (process){\n'use strict';\n\nvar _ = {\n  isPlainObject: require('lodash-compat/lang/isPlainObject'),\n  indexOf: require('lodash-compat/array/indexOf')\n};\n\nmodule.exports.__bind = function (fn, me) {\n  return function(){\n    return fn.apply(me, arguments);\n  };\n};\n\nvar log = module.exports.log = function() {\n  // Only log if available and we're not testing\n  if (console && process.env.NODE_ENV !== 'test') {\n    console.log(Array.prototype.slice.call(arguments)[0]);\n  }\n};\n\nmodule.exports.fail = function (message) {\n  log(message);\n};\n\nmodule.exports.optionHtml = function (label, value) {\n  return '<tr><td class=\"optionName\">' + label + ':</td><td>' + value + '</td></tr>';\n};\n\nvar resolveSchema = module.exports.resolveSchema = function (schema) {\n  if (_.isPlainObject(schema.schema)) {\n    schema = resolveSchema(schema.schema);\n  }\n\n  return schema;\n};\n\nmodule.exports.simpleRef = function (name) {\n  if (typeof name === 'undefined') {\n    return null;\n  }\n\n  if (name.indexOf('#/definitions/') === 0) {\n    return name.substring('#/definitions/'.length);\n  } else {\n    return name;\n  }\n};\n\n/**\n * helper to remove extensions and add them to an object\n *\n * @param keyname\n * @param obj\n */\nmodule.exports.extractExtensions = function (keyname, obj, value) {\n  if(!keyname || !obj) {\n    return;\n  }\n\n  if (typeof keyname === 'string' && keyname.indexOf('x-') === 0) {\n    obj.vendorExtensions = obj.vendorExtensions || {};\n    if(value) {\n      obj.vendorExtensions[keyname] = value;\n    }\n    else {\n      obj.vendorExtensions[keyname] = obj[keyname];\n    }\n  }\n};\n}).call(this,require('_process'))\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImxpYi9oZWxwZXJzLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIid1c2Ugc3RyaWN0JztcblxudmFyIF8gPSB7XG4gIGlzUGxhaW5PYmplY3Q6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9pc1BsYWluT2JqZWN0JyksXG4gIGluZGV4T2Y6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvYXJyYXkvaW5kZXhPZicpXG59O1xuXG5tb2R1bGUuZXhwb3J0cy5fX2JpbmQgPSBmdW5jdGlvbiAoZm4sIG1lKSB7XG4gIHJldHVybiBmdW5jdGlvbigpe1xuICAgIHJldHVybiBmbi5hcHBseShtZSwgYXJndW1lbnRzKTtcbiAgfTtcbn07XG5cbnZhciBsb2cgPSBtb2R1bGUuZXhwb3J0cy5sb2cgPSBmdW5jdGlvbigpIHtcbiAgLy8gT25seSBsb2cgaWYgYXZhaWxhYmxlIGFuZCB3ZSdyZSBub3QgdGVzdGluZ1xuICBpZiAoY29uc29sZSAmJiBwcm9jZXNzLmVudi5OT0RFX0VOViAhPT0gJ3Rlc3QnKSB7XG4gICAgY29uc29sZS5sb2coQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoYXJndW1lbnRzKVswXSk7XG4gIH1cbn07XG5cbm1vZHVsZS5leHBvcnRzLmZhaWwgPSBmdW5jdGlvbiAobWVzc2FnZSkge1xuICBsb2cobWVzc2FnZSk7XG59O1xuXG5tb2R1bGUuZXhwb3J0cy5vcHRpb25IdG1sID0gZnVuY3Rpb24gKGxhYmVsLCB2YWx1ZSkge1xuICByZXR1cm4gJzx0cj48dGQgY2xhc3M9XCJvcHRpb25OYW1lXCI+JyArIGxhYmVsICsgJzo8L3RkPjx0ZD4nICsgdmFsdWUgKyAnPC90ZD48L3RyPic7XG59O1xuXG52YXIgcmVzb2x2ZVNjaGVtYSA9IG1vZHVsZS5leHBvcnRzLnJlc29sdmVTY2hlbWEgPSBmdW5jdGlvbiAoc2NoZW1hKSB7XG4gIGlmIChfLmlzUGxhaW5PYmplY3Qoc2NoZW1hLnNjaGVtYSkpIHtcbiAgICBzY2hlbWEgPSByZXNvbHZlU2NoZW1hKHNjaGVtYS5zY2hlbWEpO1xuICB9XG5cbiAgcmV0dXJuIHNjaGVtYTtcbn07XG5cbm1vZHVsZS5leHBvcnRzLnNpbXBsZVJlZiA9IGZ1bmN0aW9uIChuYW1lKSB7XG4gIGlmICh0eXBlb2YgbmFtZSA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIGlmIChuYW1lLmluZGV4T2YoJyMvZGVmaW5pdGlvbnMvJykgPT09IDApIHtcbiAgICByZXR1cm4gbmFtZS5zdWJzdHJpbmcoJyMvZGVmaW5pdGlvbnMvJy5sZW5ndGgpO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBuYW1lO1xuICB9XG59O1xuXG4vKipcbiAqIGhlbHBlciB0byByZW1vdmUgZXh0ZW5zaW9ucyBhbmQgYWRkIHRoZW0gdG8gYW4gb2JqZWN0XG4gKlxuICogQHBhcmFtIGtleW5hbWVcbiAqIEBwYXJhbSBvYmpcbiAqL1xubW9kdWxlLmV4cG9ydHMuZXh0cmFjdEV4dGVuc2lvbnMgPSBmdW5jdGlvbiAoa2V5bmFtZSwgb2JqLCB2YWx1ZSkge1xuICBpZigha2V5bmFtZSB8fCAhb2JqKSB7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgaWYgKHR5cGVvZiBrZXluYW1lID09PSAnc3RyaW5nJyAmJiBrZXluYW1lLmluZGV4T2YoJ3gtJykgPT09IDApIHtcbiAgICBvYmoudmVuZG9yRXh0ZW5zaW9ucyA9IG9iai52ZW5kb3JFeHRlbnNpb25zIHx8IHt9O1xuICAgIGlmKHZhbHVlKSB7XG4gICAgICBvYmoudmVuZG9yRXh0ZW5zaW9uc1trZXluYW1lXSA9IHZhbHVlO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIG9iai52ZW5kb3JFeHRlbnNpb25zW2tleW5hbWVdID0gb2JqW2tleW5hbWVdO1xuICAgIH1cbiAgfVxufTsiXX0=\n},{\"_process\":12,\"lodash-compat/array/indexOf\":49,\"lodash-compat/lang/isPlainObject\":145}],5:[function(require,module,exports){\n(function (Buffer){\n'use strict';\n\nvar helpers = require('./helpers');\nvar request = require('superagent');\nvar jsyaml = require('js-yaml');\nvar _ = {\n  isObject: require('lodash-compat/lang/isObject'),\n  keys: require('lodash-compat/object/keys')\n};\n\n/*\n * JQueryHttpClient is a light-weight, node or browser HTTP client\n */\nvar JQueryHttpClient = function () {\n  this.type = 'JQueryHttpClient';\n};\n\n/*\n * SuperagentHttpClient is a light-weight, node or browser HTTP client\n */\nvar SuperagentHttpClient = function () {\n  this.type = 'SuperagentHttpClient';\n};\n\n/**\n * SwaggerHttp is a wrapper for executing requests\n */\nvar SwaggerHttp = module.exports = function () {};\n\nSwaggerHttp.prototype.execute = function (obj, opts) {\n  var client;\n\n  if(opts && opts.client) {\n    client = opts.client;\n  }\n  else {\n    client = new SuperagentHttpClient(opts);\n  }\n  client.opts = opts || {};\n\n  // legacy support\n  var hasJQuery = false;\n  if(typeof window !== 'undefined') {\n    if(typeof window.jQuery !== 'undefined') {\n      hasJQuery = true;\n    }\n  }\n  // OPTIONS support\n  if(obj.method.toLowerCase() === 'options' && client.type === 'SuperagentHttpClient') {\n    log('forcing jQuery as OPTIONS are not supported by SuperAgent');\n    obj.useJQuery = true;\n  }\n  if(this.isInternetExplorer() && (obj.useJQuery === false || !hasJQuery )) {\n    throw new Error('Unsupported configuration! JQuery is required but not available');\n  }\n  if ((obj && obj.useJQuery === true) || this.isInternetExplorer() && hasJQuery) {\n    client = new JQueryHttpClient(opts);\n  }\n\n  var success = obj.on.response;\n  var error = obj.on.error;\n\n  var requestInterceptor = function(data) {\n    if(opts && opts.requestInterceptor) {\n      data = opts.requestInterceptor.apply(data);\n    }\n    return data;\n  };\n\n  var responseInterceptor = function(data) {\n    if(opts && opts.responseInterceptor) {\n      data = opts.responseInterceptor.apply(data);\n    }\n    return success(data);\n  };\n\n  var errorInterceptor = function(data) {\n    if(opts && opts.responseInterceptor) {\n      data = opts.responseInterceptor.apply(data);\n    }\n    error(data);\n  };\n\n  obj.on.error = function(data) {\n    errorInterceptor(data);\n  };\n\n  obj.on.response = function(data) {\n    responseInterceptor(data);\n  };\n\n  if (_.isObject(obj) && _.isObject(obj.body)) {\n    // special processing for file uploads via jquery\n    if (obj.body.type && obj.body.type === 'formData'){\n      if(opts.useJQuery) {\n        obj.contentType = false;\n        obj.processData = false;\n        delete obj.headers['Content-Type'];\n      }\n    }\n  }\n\n  obj = requestInterceptor(obj) || obj;\n  if (obj.beforeSend) {\n    obj.beforeSend(function(_obj) {\n      client.execute(_obj || obj);\n    });\n  } else {\n    client.execute(obj);\n  }\n\n  return (obj.deferred) ? obj.deferred.promise : obj;\n};\n\nSwaggerHttp.prototype.isInternetExplorer = function () {\n  var detectedIE = false;\n\n  if (typeof navigator !== 'undefined' && navigator.userAgent) {\n    var nav = navigator.userAgent.toLowerCase();\n\n    if (nav.indexOf('msie') !== -1) {\n      var version = parseInt(nav.split('msie')[1]);\n\n      if (version <= 8) {\n        detectedIE = true;\n      }\n    }\n  }\n\n  return detectedIE;\n};\n\nJQueryHttpClient.prototype.execute = function (obj) {\n  var jq = this.jQuery || (typeof window !== 'undefined' && window.jQuery);\n  var cb = obj.on;\n  var request = obj;\n\n  if(typeof jq === 'undefined' || jq === false) {\n    throw new Error('Unsupported configuration! JQuery is required but not available');\n  }\n\n  obj.type = obj.method;\n  obj.cache = obj.jqueryAjaxCache;\n  obj.data = obj.body;\n  delete obj.jqueryAjaxCache;\n  delete obj.useJQuery;\n  delete obj.body;\n\n  obj.complete = function (response) {\n    var headers = {};\n    var headerArray = response.getAllResponseHeaders().split('\\n');\n\n    for (var i = 0; i < headerArray.length; i++) {\n      var toSplit = headerArray[i].trim();\n\n      if (toSplit.length === 0) {\n        continue;\n      }\n\n      var separator = toSplit.indexOf(':');\n\n      if (separator === -1) {\n        // Name but no value in the header\n        headers[toSplit] = null;\n\n        continue;\n      }\n\n      var name = toSplit.substring(0, separator).trim();\n      var value = toSplit.substring(separator + 1).trim();\n\n      headers[name] = value;\n    }\n\n    var out = {\n      url: request.url,\n      method: request.method,\n      status: response.status,\n      statusText: response.statusText,\n      data: response.responseText,\n      headers: headers\n    };\n\n    try {\n      var possibleObj =  response.responseJSON || jsyaml.safeLoad(response.responseText);\n      out.obj = (typeof possibleObj === 'string') ? {} : possibleObj;\n    } catch (ex) {\n      // do not set out.obj\n      helpers.log('unable to parse JSON/YAML content');\n    }\n\n    // I can throw, or parse null?\n    out.obj = out.obj || null;\n\n    if (response.status >= 200 && response.status < 300) {\n      cb.response(out);\n    } else if (response.status === 0 || (response.status >= 400 && response.status < 599)) {\n      cb.error(out);\n    } else {\n      return cb.response(out);\n    }\n  };\n\n  jq.support.cors = true;\n\n  return jq.ajax(obj);\n};\n\nSuperagentHttpClient.prototype.execute = function (obj) {\n  var connectionAgent = obj.connectionAgent;\n  var method = obj.method.toLowerCase();\n  var timeout = obj.timeout;\n\n  if (method === 'delete') {\n    method = 'del';\n  }\n  var headers = obj.headers || {};\n  var r = request[method](obj.url);\n\n  if (connectionAgent) {\n    r.agent(connectionAgent);\n  }\n  \n  if (timeout) {\n    r.timeout(timeout);\n  }\n\n  if (obj.enableCookies) {\n    r.withCredentials();\n  }\n\n  var accept = obj.headers.Accept;\n\n  if(this.binaryRequest(accept)) {\n    r.on('request', function () {\n      if(this.xhr) {\n        this.xhr.responseType = 'blob';\n      }\n    });\n  }\n\n  if(obj.body) {\n    if(_.isObject(obj.body)) {\n      var contentType = obj.headers['Content-Type'] || '';\n      if (contentType.indexOf('multipart/form-data') === 0) {\n        delete headers['Content-Type'];\n        if({}.toString.apply(obj.body) === '[object FormData]') {\n          r.send(obj.body);\n        }\n        else {\n          var keyname, value, v;\n          for (keyname in obj.body) {\n            value = obj.body[keyname];\n            if(Array.isArray(value)) {\n              for(v in value) {\n                r.field(keyname, v);\n              }\n            }\n            else {\n              r.field(keyname, value);\n            }\n          }\n        }\n      }\n      else if (_.isObject(obj.body)) {\n        // non multipart/form-data\n        obj.body = JSON.stringify(obj.body);\n        r.send(obj.body);\n      }\n    }\n    else {\n      r.send(obj.body);\n    }\n  }\n\n  var name;\n  for (name in headers) {\n    r.set(name, headers[name]);\n  }\n\n  if(typeof r.buffer === 'function') {\n    r.buffer(); // force superagent to populate res.text with the raw response data\n  }\n\n  r.end(function (err, res) {\n    res = res || {\n      status: 0,\n      headers: {error: 'no response from server'}\n    };\n    var response = {\n      url: obj.url,\n      method: obj.method,\n      headers: res.headers\n    };\n    var cb;\n\n    if (!err && res.error) {\n      err = res.error;\n    }\n\n    if (err && obj.on && obj.on.error) {\n      response.errObj = err;\n      response.status = res ? res.status : 500;\n      response.statusText = res ? res.text : err.message;\n      if (res.headers && res.headers['content-type']) {\n        if (res.headers['content-type'].indexOf('application/json') >= 0) {\n          try {\n            response.obj = JSON.parse(response.statusText);\n          }\n          catch (e) {\n            response.obj = null;\n          }\n        }\n      }\n      cb = obj.on.error;\n    } else if (res && obj.on && obj.on.response) {\n      var possibleObj;\n\n      // Already parsed by by superagent?\n      if (res.body && _.keys(res.body).length > 0) {\n        possibleObj = res.body;\n      } else {\n        try {\n          possibleObj = jsyaml.safeLoad(res.text);\n          // can parse into a string... which we don't need running around in the system\n          possibleObj = (typeof possibleObj === 'string') ? null : possibleObj;\n        } catch (e) {\n          helpers.log('cannot parse JSON/YAML content');\n        }\n      }\n\n      // null means we can't parse into object\n      if(typeof Buffer === 'function' && Buffer.isBuffer(possibleObj)) {\n        response.data = possibleObj;\n      }\n      else {\n        response.obj = (typeof possibleObj === 'object') ? possibleObj : null;\n      }\n\n      response.status = res.status;\n      response.statusText = res.text;\n      cb = obj.on.response;\n    }\n    if (res.xhr && res.xhr.response) {\n      response.data = res.xhr.response;\n    }\n    else if(!response.data) {\n      response.data = response.statusText;\n    }\n\n    if (cb) {\n      cb(response);\n    }\n  });\n};\n\nSuperagentHttpClient.prototype. binaryRequest = function (accept) {\n  if(!accept) {\n    return false;\n  }\n  return (/^image/i).test(accept) || (/^application\\/pdf/).test(accept);\n};\n\n}).call(this,require(\"buffer\").Buffer)\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImxpYi9odHRwLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIid1c2Ugc3RyaWN0JztcblxudmFyIGhlbHBlcnMgPSByZXF1aXJlKCcuL2hlbHBlcnMnKTtcbnZhciByZXF1ZXN0ID0gcmVxdWlyZSgnc3VwZXJhZ2VudCcpO1xudmFyIGpzeWFtbCA9IHJlcXVpcmUoJ2pzLXlhbWwnKTtcbnZhciBfID0ge1xuICBpc09iamVjdDogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9sYW5nL2lzT2JqZWN0JyksXG4gIGtleXM6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvb2JqZWN0L2tleXMnKVxufTtcblxuLypcbiAqIEpRdWVyeUh0dHBDbGllbnQgaXMgYSBsaWdodC13ZWlnaHQsIG5vZGUgb3IgYnJvd3NlciBIVFRQIGNsaWVudFxuICovXG52YXIgSlF1ZXJ5SHR0cENsaWVudCA9IGZ1bmN0aW9uICgpIHtcbiAgdGhpcy50eXBlID0gJ0pRdWVyeUh0dHBDbGllbnQnO1xufTtcblxuLypcbiAqIFN1cGVyYWdlbnRIdHRwQ2xpZW50IGlzIGEgbGlnaHQtd2VpZ2h0LCBub2RlIG9yIGJyb3dzZXIgSFRUUCBjbGllbnRcbiAqL1xudmFyIFN1cGVyYWdlbnRIdHRwQ2xpZW50ID0gZnVuY3Rpb24gKCkge1xuICB0aGlzLnR5cGUgPSAnU3VwZXJhZ2VudEh0dHBDbGllbnQnO1xufTtcblxuLyoqXG4gKiBTd2FnZ2VySHR0cCBpcyBhIHdyYXBwZXIgZm9yIGV4ZWN1dGluZyByZXF1ZXN0c1xuICovXG52YXIgU3dhZ2dlckh0dHAgPSBtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uICgpIHt9O1xuXG5Td2FnZ2VySHR0cC5wcm90b3R5cGUuZXhlY3V0ZSA9IGZ1bmN0aW9uIChvYmosIG9wdHMpIHtcbiAgdmFyIGNsaWVudDtcblxuICBpZihvcHRzICYmIG9wdHMuY2xpZW50KSB7XG4gICAgY2xpZW50ID0gb3B0cy5jbGllbnQ7XG4gIH1cbiAgZWxzZSB7XG4gICAgY2xpZW50ID0gbmV3IFN1cGVyYWdlbnRIdHRwQ2xpZW50KG9wdHMpO1xuICB9XG4gIGNsaWVudC5vcHRzID0gb3B0cyB8fCB7fTtcblxuICAvLyBsZWdhY3kgc3VwcG9ydFxuICB2YXIgaGFzSlF1ZXJ5ID0gZmFsc2U7XG4gIGlmKHR5cGVvZiB3aW5kb3cgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgaWYodHlwZW9mIHdpbmRvdy5qUXVlcnkgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgICBoYXNKUXVlcnkgPSB0cnVlO1xuICAgIH1cbiAgfVxuICAvLyBPUFRJT05TIHN1cHBvcnRcbiAgaWYob2JqLm1ldGhvZC50b0xvd2VyQ2FzZSgpID09PSAnb3B0aW9ucycgJiYgY2xpZW50LnR5cGUgPT09ICdTdXBlcmFnZW50SHR0cENsaWVudCcpIHtcbiAgICBsb2coJ2ZvcmNpbmcgalF1ZXJ5IGFzIE9QVElPTlMgYXJlIG5vdCBzdXBwb3J0ZWQgYnkgU3VwZXJBZ2VudCcpO1xuICAgIG9iai51c2VKUXVlcnkgPSB0cnVlO1xuICB9XG4gIGlmKHRoaXMuaXNJbnRlcm5ldEV4cGxvcmVyKCkgJiYgKG9iai51c2VKUXVlcnkgPT09IGZhbHNlIHx8ICFoYXNKUXVlcnkgKSkge1xuICAgIHRocm93IG5ldyBFcnJvcignVW5zdXBwb3J0ZWQgY29uZmlndXJhdGlvbiEgSlF1ZXJ5IGlzIHJlcXVpcmVkIGJ1dCBub3QgYXZhaWxhYmxlJyk7XG4gIH1cbiAgaWYgKChvYmogJiYgb2JqLnVzZUpRdWVyeSA9PT0gdHJ1ZSkgfHwgdGhpcy5pc0ludGVybmV0RXhwbG9yZXIoKSAmJiBoYXNKUXVlcnkpIHtcbiAgICBjbGllbnQgPSBuZXcgSlF1ZXJ5SHR0cENsaWVudChvcHRzKTtcbiAgfVxuXG4gIHZhciBzdWNjZXNzID0gb2JqLm9uLnJlc3BvbnNlO1xuICB2YXIgZXJyb3IgPSBvYmoub24uZXJyb3I7XG5cbiAgdmFyIHJlcXVlc3RJbnRlcmNlcHRvciA9IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICBpZihvcHRzICYmIG9wdHMucmVxdWVzdEludGVyY2VwdG9yKSB7XG4gICAgICBkYXRhID0gb3B0cy5yZXF1ZXN0SW50ZXJjZXB0b3IuYXBwbHkoZGF0YSk7XG4gICAgfVxuICAgIHJldHVybiBkYXRhO1xuICB9O1xuXG4gIHZhciByZXNwb25zZUludGVyY2VwdG9yID0gZnVuY3Rpb24oZGF0YSkge1xuICAgIGlmKG9wdHMgJiYgb3B0cy5yZXNwb25zZUludGVyY2VwdG9yKSB7XG4gICAgICBkYXRhID0gb3B0cy5yZXNwb25zZUludGVyY2VwdG9yLmFwcGx5KGRhdGEpO1xuICAgIH1cbiAgICByZXR1cm4gc3VjY2VzcyhkYXRhKTtcbiAgfTtcblxuICB2YXIgZXJyb3JJbnRlcmNlcHRvciA9IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICBpZihvcHRzICYmIG9wdHMucmVzcG9uc2VJbnRlcmNlcHRvcikge1xuICAgICAgZGF0YSA9IG9wdHMucmVzcG9uc2VJbnRlcmNlcHRvci5hcHBseShkYXRhKTtcbiAgICB9XG4gICAgZXJyb3IoZGF0YSk7XG4gIH07XG5cbiAgb2JqLm9uLmVycm9yID0gZnVuY3Rpb24oZGF0YSkge1xuICAgIGVycm9ySW50ZXJjZXB0b3IoZGF0YSk7XG4gIH07XG5cbiAgb2JqLm9uLnJlc3BvbnNlID0gZnVuY3Rpb24oZGF0YSkge1xuICAgIHJlc3BvbnNlSW50ZXJjZXB0b3IoZGF0YSk7XG4gIH07XG5cbiAgaWYgKF8uaXNPYmplY3Qob2JqKSAmJiBfLmlzT2JqZWN0KG9iai5ib2R5KSkge1xuICAgIC8vIHNwZWNpYWwgcHJvY2Vzc2luZyBmb3IgZmlsZSB1cGxvYWRzIHZpYSBqcXVlcnlcbiAgICBpZiAob2JqLmJvZHkudHlwZSAmJiBvYmouYm9keS50eXBlID09PSAnZm9ybURhdGEnKXtcbiAgICAgIGlmKG9wdHMudXNlSlF1ZXJ5KSB7XG4gICAgICAgIG9iai5jb250ZW50VHlwZSA9IGZhbHNlO1xuICAgICAgICBvYmoucHJvY2Vzc0RhdGEgPSBmYWxzZTtcbiAgICAgICAgZGVsZXRlIG9iai5oZWFkZXJzWydDb250ZW50LVR5cGUnXTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBvYmogPSByZXF1ZXN0SW50ZXJjZXB0b3Iob2JqKSB8fCBvYmo7XG4gIGlmIChvYmouYmVmb3JlU2VuZCkge1xuICAgIG9iai5iZWZvcmVTZW5kKGZ1bmN0aW9uKF9vYmopIHtcbiAgICAgIGNsaWVudC5leGVjdXRlKF9vYmogfHwgb2JqKTtcbiAgICB9KTtcbiAgfSBlbHNlIHtcbiAgICBjbGllbnQuZXhlY3V0ZShvYmopO1xuICB9XG5cbiAgcmV0dXJuIChvYmouZGVmZXJyZWQpID8gb2JqLmRlZmVycmVkLnByb21pc2UgOiBvYmo7XG59O1xuXG5Td2FnZ2VySHR0cC5wcm90b3R5cGUuaXNJbnRlcm5ldEV4cGxvcmVyID0gZnVuY3Rpb24gKCkge1xuICB2YXIgZGV0ZWN0ZWRJRSA9IGZhbHNlO1xuXG4gIGlmICh0eXBlb2YgbmF2aWdhdG9yICE9PSAndW5kZWZpbmVkJyAmJiBuYXZpZ2F0b3IudXNlckFnZW50KSB7XG4gICAgdmFyIG5hdiA9IG5hdmlnYXRvci51c2VyQWdlbnQudG9Mb3dlckNhc2UoKTtcblxuICAgIGlmIChuYXYuaW5kZXhPZignbXNpZScpICE9PSAtMSkge1xuICAgICAgdmFyIHZlcnNpb24gPSBwYXJzZUludChuYXYuc3BsaXQoJ21zaWUnKVsxXSk7XG5cbiAgICAgIGlmICh2ZXJzaW9uIDw9IDgpIHtcbiAgICAgICAgZGV0ZWN0ZWRJRSA9IHRydWU7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIGRldGVjdGVkSUU7XG59O1xuXG5KUXVlcnlIdHRwQ2xpZW50LnByb3RvdHlwZS5leGVjdXRlID0gZnVuY3Rpb24gKG9iaikge1xuICB2YXIganEgPSB0aGlzLmpRdWVyeSB8fCAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiYgd2luZG93LmpRdWVyeSk7XG4gIHZhciBjYiA9IG9iai5vbjtcbiAgdmFyIHJlcXVlc3QgPSBvYmo7XG5cbiAgaWYodHlwZW9mIGpxID09PSAndW5kZWZpbmVkJyB8fCBqcSA9PT0gZmFsc2UpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ1Vuc3VwcG9ydGVkIGNvbmZpZ3VyYXRpb24hIEpRdWVyeSBpcyByZXF1aXJlZCBidXQgbm90IGF2YWlsYWJsZScpO1xuICB9XG5cbiAgb2JqLnR5cGUgPSBvYmoubWV0aG9kO1xuICBvYmouY2FjaGUgPSBvYmouanF1ZXJ5QWpheENhY2hlO1xuICBvYmouZGF0YSA9IG9iai5ib2R5O1xuICBkZWxldGUgb2JqLmpxdWVyeUFqYXhDYWNoZTtcbiAgZGVsZXRlIG9iai51c2VKUXVlcnk7XG4gIGRlbGV0ZSBvYmouYm9keTtcblxuICBvYmouY29tcGxldGUgPSBmdW5jdGlvbiAocmVzcG9uc2UpIHtcbiAgICB2YXIgaGVhZGVycyA9IHt9O1xuICAgIHZhciBoZWFkZXJBcnJheSA9IHJlc3BvbnNlLmdldEFsbFJlc3BvbnNlSGVhZGVycygpLnNwbGl0KCdcXG4nKTtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgaGVhZGVyQXJyYXkubGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciB0b1NwbGl0ID0gaGVhZGVyQXJyYXlbaV0udHJpbSgpO1xuXG4gICAgICBpZiAodG9TcGxpdC5sZW5ndGggPT09IDApIHtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIHZhciBzZXBhcmF0b3IgPSB0b1NwbGl0LmluZGV4T2YoJzonKTtcblxuICAgICAgaWYgKHNlcGFyYXRvciA9PT0gLTEpIHtcbiAgICAgICAgLy8gTmFtZSBidXQgbm8gdmFsdWUgaW4gdGhlIGhlYWRlclxuICAgICAgICBoZWFkZXJzW3RvU3BsaXRdID0gbnVsbDtcblxuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cblxuICAgICAgdmFyIG5hbWUgPSB0b1NwbGl0LnN1YnN0cmluZygwLCBzZXBhcmF0b3IpLnRyaW0oKTtcbiAgICAgIHZhciB2YWx1ZSA9IHRvU3BsaXQuc3Vic3RyaW5nKHNlcGFyYXRvciArIDEpLnRyaW0oKTtcblxuICAgICAgaGVhZGVyc1tuYW1lXSA9IHZhbHVlO1xuICAgIH1cblxuICAgIHZhciBvdXQgPSB7XG4gICAgICB1cmw6IHJlcXVlc3QudXJsLFxuICAgICAgbWV0aG9kOiByZXF1ZXN0Lm1ldGhvZCxcbiAgICAgIHN0YXR1czogcmVzcG9uc2Uuc3RhdHVzLFxuICAgICAgc3RhdHVzVGV4dDogcmVzcG9uc2Uuc3RhdHVzVGV4dCxcbiAgICAgIGRhdGE6IHJlc3BvbnNlLnJlc3BvbnNlVGV4dCxcbiAgICAgIGhlYWRlcnM6IGhlYWRlcnNcbiAgICB9O1xuXG4gICAgdHJ5IHtcbiAgICAgIHZhciBwb3NzaWJsZU9iaiA9ICByZXNwb25zZS5yZXNwb25zZUpTT04gfHwganN5YW1sLnNhZmVMb2FkKHJlc3BvbnNlLnJlc3BvbnNlVGV4dCk7XG4gICAgICBvdXQub2JqID0gKHR5cGVvZiBwb3NzaWJsZU9iaiA9PT0gJ3N0cmluZycpID8ge30gOiBwb3NzaWJsZU9iajtcbiAgICB9IGNhdGNoIChleCkge1xuICAgICAgLy8gZG8gbm90IHNldCBvdXQub2JqXG4gICAgICBoZWxwZXJzLmxvZygndW5hYmxlIHRvIHBhcnNlIEpTT04vWUFNTCBjb250ZW50Jyk7XG4gICAgfVxuXG4gICAgLy8gSSBjYW4gdGhyb3csIG9yIHBhcnNlIG51bGw/XG4gICAgb3V0Lm9iaiA9IG91dC5vYmogfHwgbnVsbDtcblxuICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPj0gMjAwICYmIHJlc3BvbnNlLnN0YXR1cyA8IDMwMCkge1xuICAgICAgY2IucmVzcG9uc2Uob3V0KTtcbiAgICB9IGVsc2UgaWYgKHJlc3BvbnNlLnN0YXR1cyA9PT0gMCB8fCAocmVzcG9uc2Uuc3RhdHVzID49IDQwMCAmJiByZXNwb25zZS5zdGF0dXMgPCA1OTkpKSB7XG4gICAgICBjYi5lcnJvcihvdXQpO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gY2IucmVzcG9uc2Uob3V0KTtcbiAgICB9XG4gIH07XG5cbiAganEuc3VwcG9ydC5jb3JzID0gdHJ1ZTtcblxuICByZXR1cm4ganEuYWpheChvYmopO1xufTtcblxuU3VwZXJhZ2VudEh0dHBDbGllbnQucHJvdG90eXBlLmV4ZWN1dGUgPSBmdW5jdGlvbiAob2JqKSB7XG4gIHZhciBjb25uZWN0aW9uQWdlbnQgPSBvYmouY29ubmVjdGlvbkFnZW50O1xuICB2YXIgbWV0aG9kID0gb2JqLm1ldGhvZC50b0xvd2VyQ2FzZSgpO1xuICB2YXIgdGltZW91dCA9IG9iai50aW1lb3V0O1xuXG4gIGlmIChtZXRob2QgPT09ICdkZWxldGUnKSB7XG4gICAgbWV0aG9kID0gJ2RlbCc7XG4gIH1cbiAgdmFyIGhlYWRlcnMgPSBvYmouaGVhZGVycyB8fCB7fTtcbiAgdmFyIHIgPSByZXF1ZXN0W21ldGhvZF0ob2JqLnVybCk7XG5cbiAgaWYgKGNvbm5lY3Rpb25BZ2VudCkge1xuICAgIHIuYWdlbnQoY29ubmVjdGlvbkFnZW50KTtcbiAgfVxuICBcbiAgaWYgKHRpbWVvdXQpIHtcbiAgICByLnRpbWVvdXQodGltZW91dCk7XG4gIH1cblxuICBpZiAob2JqLmVuYWJsZUNvb2tpZXMpIHtcbiAgICByLndpdGhDcmVkZW50aWFscygpO1xuICB9XG5cbiAgdmFyIGFjY2VwdCA9IG9iai5oZWFkZXJzLkFjY2VwdDtcblxuICBpZih0aGlzLmJpbmFyeVJlcXVlc3QoYWNjZXB0KSkge1xuICAgIHIub24oJ3JlcXVlc3QnLCBmdW5jdGlvbiAoKSB7XG4gICAgICBpZih0aGlzLnhocikge1xuICAgICAgICB0aGlzLnhoci5yZXNwb25zZVR5cGUgPSAnYmxvYic7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBpZihvYmouYm9keSkge1xuICAgIGlmKF8uaXNPYmplY3Qob2JqLmJvZHkpKSB7XG4gICAgICB2YXIgY29udGVudFR5cGUgPSBvYmouaGVhZGVyc1snQ29udGVudC1UeXBlJ10gfHwgJyc7XG4gICAgICBpZiAoY29udGVudFR5cGUuaW5kZXhPZignbXVsdGlwYXJ0L2Zvcm0tZGF0YScpID09PSAwKSB7XG4gICAgICAgIGRlbGV0ZSBoZWFkZXJzWydDb250ZW50LVR5cGUnXTtcbiAgICAgICAgaWYoe30udG9TdHJpbmcuYXBwbHkob2JqLmJvZHkpID09PSAnW29iamVjdCBGb3JtRGF0YV0nKSB7XG4gICAgICAgICAgci5zZW5kKG9iai5ib2R5KTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICB2YXIga2V5bmFtZSwgdmFsdWUsIHY7XG4gICAgICAgICAgZm9yIChrZXluYW1lIGluIG9iai5ib2R5KSB7XG4gICAgICAgICAgICB2YWx1ZSA9IG9iai5ib2R5W2tleW5hbWVdO1xuICAgICAgICAgICAgaWYoQXJyYXkuaXNBcnJheSh2YWx1ZSkpIHtcbiAgICAgICAgICAgICAgZm9yKHYgaW4gdmFsdWUpIHtcbiAgICAgICAgICAgICAgICByLmZpZWxkKGtleW5hbWUsIHYpO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgci5maWVsZChrZXluYW1lLCB2YWx1ZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgICBlbHNlIGlmIChfLmlzT2JqZWN0KG9iai5ib2R5KSkge1xuICAgICAgICAvLyBub24gbXVsdGlwYXJ0L2Zvcm0tZGF0YVxuICAgICAgICBvYmouYm9keSA9IEpTT04uc3RyaW5naWZ5KG9iai5ib2R5KTtcbiAgICAgICAgci5zZW5kKG9iai5ib2R5KTtcbiAgICAgIH1cbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICByLnNlbmQob2JqLmJvZHkpO1xuICAgIH1cbiAgfVxuXG4gIHZhciBuYW1lO1xuICBmb3IgKG5hbWUgaW4gaGVhZGVycykge1xuICAgIHIuc2V0KG5hbWUsIGhlYWRlcnNbbmFtZV0pO1xuICB9XG5cbiAgaWYodHlwZW9mIHIuYnVmZmVyID09PSAnZnVuY3Rpb24nKSB7XG4gICAgci5idWZmZXIoKTsgLy8gZm9yY2Ugc3VwZXJhZ2VudCB0byBwb3B1bGF0ZSByZXMudGV4dCB3aXRoIHRoZSByYXcgcmVzcG9uc2UgZGF0YVxuICB9XG5cbiAgci5lbmQoZnVuY3Rpb24gKGVyciwgcmVzKSB7XG4gICAgcmVzID0gcmVzIHx8IHtcbiAgICAgIHN0YXR1czogMCxcbiAgICAgIGhlYWRlcnM6IHtlcnJvcjogJ25vIHJlc3BvbnNlIGZyb20gc2VydmVyJ31cbiAgICB9O1xuICAgIHZhciByZXNwb25zZSA9IHtcbiAgICAgIHVybDogb2JqLnVybCxcbiAgICAgIG1ldGhvZDogb2JqLm1ldGhvZCxcbiAgICAgIGhlYWRlcnM6IHJlcy5oZWFkZXJzXG4gICAgfTtcbiAgICB2YXIgY2I7XG5cbiAgICBpZiAoIWVyciAmJiByZXMuZXJyb3IpIHtcbiAgICAgIGVyciA9IHJlcy5lcnJvcjtcbiAgICB9XG5cbiAgICBpZiAoZXJyICYmIG9iai5vbiAmJiBvYmoub24uZXJyb3IpIHtcbiAgICAgIHJlc3BvbnNlLmVyck9iaiA9IGVycjtcbiAgICAgIHJlc3BvbnNlLnN0YXR1cyA9IHJlcyA/IHJlcy5zdGF0dXMgOiA1MDA7XG4gICAgICByZXNwb25zZS5zdGF0dXNUZXh0ID0gcmVzID8gcmVzLnRleHQgOiBlcnIubWVzc2FnZTtcbiAgICAgIGlmIChyZXMuaGVhZGVycyAmJiByZXMuaGVhZGVyc1snY29udGVudC10eXBlJ10pIHtcbiAgICAgICAgaWYgKHJlcy5oZWFkZXJzWydjb250ZW50LXR5cGUnXS5pbmRleE9mKCdhcHBsaWNhdGlvbi9qc29uJykgPj0gMCkge1xuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICByZXNwb25zZS5vYmogPSBKU09OLnBhcnNlKHJlc3BvbnNlLnN0YXR1c1RleHQpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBjYXRjaCAoZSkge1xuICAgICAgICAgICAgcmVzcG9uc2Uub2JqID0gbnVsbDtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGNiID0gb2JqLm9uLmVycm9yO1xuICAgIH0gZWxzZSBpZiAocmVzICYmIG9iai5vbiAmJiBvYmoub24ucmVzcG9uc2UpIHtcbiAgICAgIHZhciBwb3NzaWJsZU9iajtcblxuICAgICAgLy8gQWxyZWFkeSBwYXJzZWQgYnkgYnkgc3VwZXJhZ2VudD9cbiAgICAgIGlmIChyZXMuYm9keSAmJiBfLmtleXMocmVzLmJvZHkpLmxlbmd0aCA+IDApIHtcbiAgICAgICAgcG9zc2libGVPYmogPSByZXMuYm9keTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgcG9zc2libGVPYmogPSBqc3lhbWwuc2FmZUxvYWQocmVzLnRleHQpO1xuICAgICAgICAgIC8vIGNhbiBwYXJzZSBpbnRvIGEgc3RyaW5nLi4uIHdoaWNoIHdlIGRvbid0IG5lZWQgcnVubmluZyBhcm91bmQgaW4gdGhlIHN5c3RlbVxuICAgICAgICAgIHBvc3NpYmxlT2JqID0gKHR5cGVvZiBwb3NzaWJsZU9iaiA9PT0gJ3N0cmluZycpID8gbnVsbCA6IHBvc3NpYmxlT2JqO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgaGVscGVycy5sb2coJ2Nhbm5vdCBwYXJzZSBKU09OL1lBTUwgY29udGVudCcpO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIG51bGwgbWVhbnMgd2UgY2FuJ3QgcGFyc2UgaW50byBvYmplY3RcbiAgICAgIGlmKHR5cGVvZiBCdWZmZXIgPT09ICdmdW5jdGlvbicgJiYgQnVmZmVyLmlzQnVmZmVyKHBvc3NpYmxlT2JqKSkge1xuICAgICAgICByZXNwb25zZS5kYXRhID0gcG9zc2libGVPYmo7XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgcmVzcG9uc2Uub2JqID0gKHR5cGVvZiBwb3NzaWJsZU9iaiA9PT0gJ29iamVjdCcpID8gcG9zc2libGVPYmogOiBudWxsO1xuICAgICAgfVxuXG4gICAgICByZXNwb25zZS5zdGF0dXMgPSByZXMuc3RhdHVzO1xuICAgICAgcmVzcG9uc2Uuc3RhdHVzVGV4dCA9IHJlcy50ZXh0O1xuICAgICAgY2IgPSBvYmoub24ucmVzcG9uc2U7XG4gICAgfVxuICAgIGlmIChyZXMueGhyICYmIHJlcy54aHIucmVzcG9uc2UpIHtcbiAgICAgIHJlc3BvbnNlLmRhdGEgPSByZXMueGhyLnJlc3BvbnNlO1xuICAgIH1cbiAgICBlbHNlIGlmKCFyZXNwb25zZS5kYXRhKSB7XG4gICAgICByZXNwb25zZS5kYXRhID0gcmVzcG9uc2Uuc3RhdHVzVGV4dDtcbiAgICB9XG5cbiAgICBpZiAoY2IpIHtcbiAgICAgIGNiKHJlc3BvbnNlKTtcbiAgICB9XG4gIH0pO1xufTtcblxuU3VwZXJhZ2VudEh0dHBDbGllbnQucHJvdG90eXBlLiBiaW5hcnlSZXF1ZXN0ID0gZnVuY3Rpb24gKGFjY2VwdCkge1xuICBpZighYWNjZXB0KSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIHJldHVybiAoL15pbWFnZS9pKS50ZXN0KGFjY2VwdCkgfHwgKC9eYXBwbGljYXRpb25cXC9wZGYvKS50ZXN0KGFjY2VwdCk7XG59O1xuIl19\n},{\"./helpers\":4,\"buffer\":14,\"js-yaml\":19,\"lodash-compat/lang/isObject\":144,\"lodash-compat/object/keys\":149,\"superagent\":158}],6:[function(require,module,exports){\n'use strict';\n\nvar SwaggerHttp = require('./http');\nvar _ = {\n  isObject: require('lodash-compat/lang/isObject'),\n  cloneDeep: require('lodash-compat/lang/cloneDeep'),\n  isArray: require('lodash-compat/lang/isArray'),\n  isString: require('lodash-compat/lang/isString')\n};\n\n\n/**\n * Resolves a spec's remote references\n */\nvar Resolver = module.exports = function () {\n  this.failedUrls = [];\n  this.resolverCache = {};\n  this.pendingUrls = {};\n};\n\nResolver.prototype.processAllOf = function(root, name, definition, resolutionTable, unresolvedRefs, spec) {\n  var i, location, property;\n\n  definition['x-resolved-from'] = [ '#/definitions/' + name ];\n  var allOf = definition.allOf;\n  // the refs go first\n  allOf.sort(function(a, b) {\n    if(a.$ref && b.$ref) { return 0; }\n    else if(a.$ref) { return -1; }\n    else { return 1; }\n  });\n  for (i = 0; i < allOf.length; i++) {\n    property = allOf[i];\n    location = '/definitions/' + name + '/allOf';\n    this.resolveInline(root, spec, property, resolutionTable, unresolvedRefs, location);\n  }\n};\n\nResolver.prototype.resolve = function (spec, arg1, arg2, arg3) {\n  this.spec = spec;\n  var root = arg1, callback = arg2, scope = arg3, opts = {}, location, i;\n  if(typeof arg1 === 'function') {\n    root = null;\n    callback = arg1;\n    scope = arg2;\n  }\n  var _root = root, modelName;\n  this.scope = (scope || this);\n  this.iteration = this.iteration || 0;\n\n  if(this.scope.options && this.scope.options.requestInterceptor){\n    opts.requestInterceptor = this.scope.options.requestInterceptor;\n  }\n\n  if(this.scope.options && this.scope.options.responseInterceptor){\n    opts.responseInterceptor = this.scope.options.responseInterceptor;\n  }\n\n  var name, path, property, propertyName, parameter, done, counter;\n  var processedCalls = 0, resolvedRefs = {}, unresolvedRefs = {};\n  var resolutionTable = []; // store objects for dereferencing\n\n  spec.definitions = spec.definitions || {};\n  // definitions\n  for (name in spec.definitions) {\n    var definition = spec.definitions[name];\n    if(definition.$ref) {\n      this.resolveInline(root, spec, definition, resolutionTable, unresolvedRefs, definition);\n    }\n    else {\n      for (propertyName in definition.properties) {\n        property = definition.properties[propertyName];\n        if (_.isArray(property.allOf)) {\n          this.processAllOf(root, name, property, resolutionTable, unresolvedRefs, spec);\n        }\n        else {\n          this.resolveTo(root, property, resolutionTable, '/definitions');\n        }\n      }\n\n      if (definition.allOf) {\n        this.processAllOf(root, name, definition, resolutionTable, unresolvedRefs, spec);\n      }\n    }\n  }\n\n  // shared parameters\n  spec.parameters = spec.parameters || {};\n  for(name in spec.parameters) {\n    parameter = spec.parameters[name];\n    if (parameter.in === 'body' && parameter.schema) {\n      if(_.isArray(parameter.schema.allOf)) {\n        // move to a definition\n        modelName = 'inline_model';\n        var _name = modelName;\n        done = false; counter = 0;\n        while(!done) {\n          if(typeof spec.definitions[_name] === 'undefined') {\n            done = true;\n            break;\n          }\n          _name = modelName + '_' + counter;\n          counter ++;\n        }\n        spec.definitions[_name] = { allOf: parameter.schema.allOf };\n        delete parameter.schema.allOf;\n        parameter.schema.$ref = '#/definitions/' + _name;\n        this.processAllOf(root, _name, spec.definitions[_name], resolutionTable, unresolvedRefs, spec);\n      }\n      else {\n        this.resolveTo(root, parameter.schema, resolutionTable, location);\n      }\n    }\n\n    if (parameter.$ref) {\n      // parameter reference\n      this.resolveInline(root, spec, parameter, resolutionTable, unresolvedRefs, parameter.$ref);\n    }\n  }\n\n  // operations\n  for (name in spec.paths) {\n    var method, operation, responseCode;\n    path = spec.paths[name];\n\n    if(typeof path === 'object') {\n      for (method in path) {\n        // operation reference\n        if (method === '$ref') {\n          // location = path[method];\n          location = '/paths' + name;\n          this.resolveInline(root, spec, path, resolutionTable, unresolvedRefs, location);\n        }\n        else {\n          operation = path[method];\n          var sharedParameters = path.parameters || [];\n          var parameters = operation.parameters || [];\n\n          for (i in sharedParameters) {\n            parameter = sharedParameters[i];\n            parameters.unshift(parameter);\n          }\n          if (method !== 'parameters' && _.isObject(operation)) {\n            operation.parameters = operation.parameters || parameters;\n          }\n\n          for (i in parameters) {\n            parameter = parameters[i];\n            location = '/paths' + name + '/' + method + '/parameters';\n\n            if (parameter.in === 'body' && parameter.schema) {\n              if (_.isArray(parameter.schema.allOf)) {\n                // move to a definition\n                modelName = 'inline_model';\n                name = modelName;\n                done = false;\n                counter = 0;\n                while (!done) {\n                  if (typeof spec.definitions[name] === 'undefined') {\n                    done = true;\n                    break;\n                  }\n                  name = modelName + '_' + counter;\n                  counter++;\n                }\n                spec.definitions[name] = {allOf: parameter.schema.allOf};\n                delete parameter.schema.allOf;\n                parameter.schema.$ref = '#/definitions/' + name;\n                this.processAllOf(root, name, spec.definitions[name], resolutionTable, unresolvedRefs, spec);\n              }\n              else {\n                this.resolveTo(root, parameter.schema, resolutionTable, location);\n              }\n            }\n\n            if (parameter.$ref) {\n              // parameter reference\n              this.resolveInline(root, spec, parameter, resolutionTable, unresolvedRefs, parameter.$ref);\n            }\n          }\n\n          for (responseCode in operation.responses) {\n            var response = operation.responses[responseCode];\n            location = '/paths' + name + '/' + method + '/responses/' + responseCode;\n\n            if (_.isObject(response)) {\n              if (response.$ref) {\n                // response reference\n                this.resolveInline(root, spec, response, resolutionTable, unresolvedRefs, location);\n              }\n              if (response.schema) {\n                var responseObj = response;\n                if (_.isArray(responseObj.schema.allOf)) {\n                  // move to a definition\n                  modelName = 'inline_model';\n                  name = modelName;\n                  done = false;\n                  counter = 0;\n                  while (!done) {\n                    if (typeof spec.definitions[name] === 'undefined') {\n                      done = true;\n                      break;\n                    }\n                    name = modelName + '_' + counter;\n                    counter++;\n                  }\n                  spec.definitions[name] = {allOf: responseObj.schema.allOf};\n                  delete responseObj.schema.allOf;\n                  delete responseObj.schema.type;\n                  responseObj.schema.$ref = '#/definitions/' + name;\n                  this.processAllOf(root, name, spec.definitions[name], resolutionTable, unresolvedRefs, spec);\n                }\n                else if ('array' === responseObj.schema.type) {\n                  if (responseObj.schema.items && responseObj.schema.items.$ref) {\n                    // response reference\n                    this.resolveInline(root, spec, responseObj.schema.items, resolutionTable, unresolvedRefs, location);\n                  }\n                }\n                else {\n                  this.resolveTo(root, response.schema, resolutionTable, location);\n                }\n              }\n            }\n          }\n        }\n      }\n      // clear them out to avoid multiple resolutions\n      path.parameters = [];\n    }\n  }\n\n  var expectedCalls = 0, toResolve = [];\n  // if the root is same as obj[i].root we can resolve locally\n  var all = resolutionTable;\n\n  var parts;\n  for(i = 0; i < all.length; i++) {\n    var a = all[i];\n    if(root === a.root) {\n      if(a.resolveAs === 'ref') {\n        // resolve any path walking\n        var joined = ((a.root || '') + '/' + a.key).split('/');\n        var normalized = [];\n        var url = '';\n        var k;\n\n        if(a.key.indexOf('../') >= 0) {\n          for(var j = 0; j < joined.length; j++) {\n            if(joined[j] === '..') {\n              normalized = normalized.slice(0, normalized.length-1);\n            }\n            else {\n              normalized.push(joined[j]);\n            }\n          }\n          for(k = 0; k < normalized.length; k ++) {\n            if(k > 0) {\n              url += '/';\n            }\n            url += normalized[k];\n          }\n          // we now have to remote resolve this because the path has changed\n          a.root = url;\n          toResolve.push(a);\n        }\n        else {\n          parts = a.key.split('#');\n          if(parts.length === 2) {\n            if(parts[0].indexOf('http:') === 0 || parts[0].indexOf('https:') === 0) {\n              a.root = parts[0];\n            }\n            location = parts[1].split('/');\n            var r;\n            var s = spec;\n            for(k = 0; k < location.length; k++) {\n              var part = location[k];\n              if(part !== '') {\n                s = s[part];\n                if(typeof s !== 'undefined') {\n                  r = s;\n                }\n                else {\n                  r = null;\n                  break;\n                }\n              }\n            }\n            if(r === null) {\n              // must resolve this too\n              toResolve.push(a);\n            }\n          }\n        }\n      }\n      else {\n        if (a.resolveAs === 'inline') {\n          if(a.key && a.key.indexOf('#') === -1 && a.key.charAt(0) !== '/') {\n            // handle relative schema\n            parts = a.root.split('/');\n            location = '';\n            for(i = 0; i < parts.length - 1; i++) {\n              location += parts[i] + '/';\n            }\n            location += a.key;\n            a.root = location;\n            a.location = '';\n          }\n          toResolve.push(a);\n        }\n      }\n    }\n    else {\n      toResolve.push(a);\n    }\n  }\n  expectedCalls = toResolve.length;\n\n  // resolve anything that is local\n\n  var lock = {};\n  for(var ii = 0; ii < toResolve.length; ii++) {\n    (function(item, spec, self, lock, ii) {\n      if(!item.root || item.root === root) {\n        // local resolve\n        self.resolveItem(spec, _root, resolutionTable, resolvedRefs, unresolvedRefs, item);\n        processedCalls += 1;\n\n        if(processedCalls === expectedCalls) {\n          self.finish(spec, root, resolutionTable, resolvedRefs, unresolvedRefs, callback, true);\n        }\n      }\n      else if(self.failedUrls.indexOf(item.root) === -1) {\n        var obj = {\n          useJQuery: false,  // TODO\n          url: item.root,\n          method: 'get',\n          headers: {\n            accept: self.scope.swaggerRequestHeaders || 'application/json'\n          },\n          on: {\n            error: function (error) {\n              processedCalls += 1;\n              console.log('failed url: ' + obj.url);\n              self.failedUrls.push(obj.url);\n              if (lock) {\n                delete lock[item.root];\n              }\n              unresolvedRefs[item.key] = {\n                root: item.root,\n                location: item.location\n              };\n\n              if (processedCalls === expectedCalls) {\n                self.finish(spec, _root, resolutionTable, resolvedRefs, unresolvedRefs, callback);\n              }\n            },  // jshint ignore:line\n            response: function (response) {\n              var swagger = response.obj;\n              if (lock) {\n                delete lock[item.root];\n              }\n              if (self.resolverCache) {\n                self.resolverCache[item.root] = swagger;\n              }\n              self.resolveItem(swagger, item.root, resolutionTable, resolvedRefs, unresolvedRefs, item);\n              processedCalls += 1;\n\n              if (processedCalls === expectedCalls) {\n                self.finish(spec, _root, resolutionTable, resolvedRefs, unresolvedRefs, callback);\n              }\n            }\n          } // jshint ignore:line\n        };\n\n        // apply timeout only when specified\n        if (scope && scope.fetchSpecTimeout) {\n          obj.timeout = scope.fetchSpecTimeout;\n        }\n\n        if (scope && scope.clientAuthorizations) {\n          scope.clientAuthorizations.apply(obj);\n        }\n\n        (function waitForUnlock() {\n          setTimeout(function() {\n            if (lock[obj.url]) {\n              waitForUnlock();\n            }\n            else {\n              var cached = self.resolverCache[obj.url];\n              if (_.isObject(cached)) {\n                obj.on.response({obj: cached});\n              }\n              else {\n                lock[obj.url] = true;\n                new SwaggerHttp().execute(obj, opts);\n              }\n            }\n          }, 0);\n        })();\n      }\n\n      else {\n        processedCalls += 1;\n        unresolvedRefs[item.key] = {\n          root: item.root,\n          location: item.location\n        };\n        if (processedCalls === expectedCalls) {\n          self.finish(spec, _root, resolutionTable, resolvedRefs, unresolvedRefs, callback);\n        }\n      }\n    }(toResolve[ii], spec, this, lock, ii));\n  }\n\n  if (Object.keys(toResolve).length === 0) {\n    this.finish(spec, _root, resolutionTable, resolvedRefs, unresolvedRefs, callback);\n  }\n};\n\nResolver.prototype.resolveItem = function(spec, root, resolutionTable, resolvedRefs, unresolvedRefs, item) {\n  var path = item.location;\n  var location = spec, parts = path.split('/');\n  if(path !== '') {\n    for (var j = 0; j < parts.length; j++) {\n      var segment = parts[j];\n      if (segment.indexOf('~1') !== -1) {\n        segment = parts[j].replace(/~0/g, '~').replace(/~1/g, '/');\n        if (segment.charAt(0) !== '/') {\n          segment = '/' + segment;\n        }\n      }\n      if (typeof location === 'undefined' || location === null) {\n        break;\n      }\n      if (segment === '' && j === (parts.length - 1) && parts.length > 1) {\n        location = null;\n        break;\n      }\n      if (segment.length > 0) {\n        location = location[segment];\n      }\n    }\n  }\n  var resolved = item.key;\n  parts = item.key.split('/');\n  var resolvedName = parts[parts.length-1];\n\n  if(resolvedName.indexOf('#') >= 0) {\n    resolvedName = resolvedName.split('#')[1];\n  }\n\n  if (location !== null && typeof location !== 'undefined') {\n    resolvedRefs[resolved] = {\n      name: resolvedName,\n      obj: location,\n      key: item.key,\n      root: item.root\n    };\n  } else {\n    unresolvedRefs[resolved] = {\n      root: item.root,\n      location: item.location\n    };\n  }\n};\n\nResolver.prototype.finish = function (spec, root, resolutionTable, resolvedRefs, unresolvedRefs, callback, localResolve) {\n  // walk resolution table and replace with resolved refs\n  var ref, abs;\n  for (ref in resolutionTable) {\n    var item = resolutionTable[ref];\n\n    var key = item.key;\n    var resolvedTo = resolvedRefs[key];\n    if (resolvedTo) {\n      spec.definitions = spec.definitions || {};\n      if (item.resolveAs === 'ref') {\n        if (localResolve !== true) {\n          // don't retain root for local definitions\n          for (key in resolvedTo.obj) {\n            abs = this.retainRoot(key, resolvedTo.obj[key], item.root);\n            resolvedTo.obj[key] = abs;\n          }\n        }\n        spec.definitions[resolvedTo.name] = resolvedTo.obj;\n        item.obj.$ref = '#/definitions/' + resolvedTo.name;\n      } else if (item.resolveAs === 'inline') {\n        var targetObj = item.obj;\n        targetObj['x-resolved-from'] = [ item.key ];\n        delete targetObj.$ref;\n\n        for (key in resolvedTo.obj) {\n          abs = resolvedTo.obj[key];\n\n          if (localResolve !== true) {\n            // don't retain root for local definitions\n            abs = this.retainRoot(key, resolvedTo.obj[key], item.root);\n          }\n          targetObj[key] = abs;\n        }\n      }\n    }\n  }\n  var existingUnresolved = this.countUnresolvedRefs(spec);\n\n  if(existingUnresolved === 0 || this.iteration > 5) {\n    this.resolveAllOf(spec.definitions);\n    this.resolverCache = null;\n    callback.call(this.scope, spec, unresolvedRefs);\n  }\n  else {\n    this.iteration += 1;\n    this.resolve(spec, root, callback, this.scope);\n  }\n};\n\nResolver.prototype.countUnresolvedRefs = function(spec) {\n  var i;\n  var refs = this.getRefs(spec);\n  var keys = [];\n  var unresolvedKeys = [];\n  for(i in refs) {\n    if(i.indexOf('#') === 0) {\n      keys.push(i.substring(1));\n    }\n    else {\n      unresolvedKeys.push(i);\n    }\n  }\n\n  // verify possible keys\n  for (i = 0; i < keys.length; i++) {\n    var part = keys[i];\n    var parts = part.split('/');\n    var obj = spec;\n\n    for (var k = 0; k < parts.length; k++) {\n      var key = parts[k];\n      if(key !== '') {\n        obj = obj[key];\n        if(typeof obj === 'undefined') {\n          unresolvedKeys.push(part);\n          break;\n        }\n      }\n    }\n  }\n  return unresolvedKeys.length;\n};\n\nResolver.prototype.getRefs = function(spec, obj) {\n  obj = obj || spec;\n  var output = {};\n  for(var key in obj) {\n    if (!obj.hasOwnProperty(key)) {\n      continue;\n    }\n    var item = obj[key];\n    if(key === '$ref' && typeof item === 'string') {\n      output[item] = null;\n    }\n    else if(_.isObject(item)) {\n      var o = this.getRefs(item);\n      for(var k in o) {\n        output[k] = null;\n      }\n    }\n  }\n  return output;\n};\n\nfunction splitUrl(url) {\n  var result = {};\n  var proto = /[a-z]+:\\/\\//i.exec(url);\n  if (proto) {\n    result.proto = proto[0].slice(0, -3);\n    url = url.slice(result.proto.length + 1);\n  }\n  if (url.slice(0, 2) === '//') {\n    result.domain = url.slice(2).split('/')[0];\n    url = url.slice(2 + result.domain.length);\n  }\n  var p = url.split('#');\n  if (p[0].length) {\n    result.path = p[0];\n  }\n  if (p.length > 1) {\n    result.fragment = p.slice(1).join('#');\n  }\n  return result;\n}\n\nfunction unsplitUrl(url) {\n  var result = url.path;\n  if (result === undefined) {\n    result = '';\n  }\n  if (url.fragment !== undefined) {\n    result += '#' + url.fragment;\n  }\n  if (url.domain !== undefined) {\n    if (result.slice(0, 1) === '/') {\n      result = result.slice(1);\n    }\n    result = '//' + url.domain + '/' + result;\n    if (url.proto !== undefined) {\n      result = url.proto + ':' + result;\n    }\n  }\n  return result;\n}\n\nfunction joinUrl(base, rel) {\n  var relsp = splitUrl(rel);\n  if (relsp.domain !== undefined) {\n    return rel;\n  }\n  var result = splitUrl(base);\n  if (relsp.path === undefined) {\n    // change only fragment part\n    result.fragment = relsp.fragment;\n  } else if (relsp.path.slice(0, 1) === '/') {\n    // relative to domain\n    result.path = relsp.path;\n    result.fragment = relsp.fragment;\n  } else {\n    // relative to path\n    var path = result.path === undefined ? [] : result.path.split('/');\n    var relpath = relsp.path.split('/');\n    if (path.length) {\n      path.pop();\n    }\n    while (relpath[0] === '..' || relpath[0] === '.') {\n      if (relpath[0] === '..') {\n        path.pop();\n      }\n      relpath.shift();\n    }\n    result.path = path.concat(relpath).join('/');\n    result.fragment = relsp.fragment;\n  }\n  return unsplitUrl(result);\n}\n\nResolver.prototype.retainRoot = function(origKey, obj, root) {\n  // walk object and look for relative $refs\n  if(_.isObject(obj)) {\n    for(var key in obj) {\n      var item = obj[key];\n      if (key === '$ref' && typeof item === 'string') {\n        obj[key] = joinUrl(root, item);\n      }\n      else if (_.isObject(item)) {\n        this.retainRoot(key, item, root);\n      }\n    }\n  }\n  else if(_.isString(obj) && origKey === '$ref') {\n    obj = joinUrl(root, obj);\n  }\n  return obj;\n};\n\n/**\n * immediately in-lines local refs, queues remote refs\n * for inline resolution\n */\nResolver.prototype.resolveInline = function (root, spec, property, resolutionTable, unresolvedRefs, location) {\n  var key = property.$ref, ref = property.$ref, i, p, p2, rs;\n  var rootTrimmed = false;\n\n  root = root || ''; // Guard against .split. @fehguy, you'll need to check if this logic fits\n  // More imporantly is how do we gracefully handle relative urls, when provided just a 'spec', not a 'url' ?\n\n  if (ref) {\n    if(ref.indexOf('../') === 0) {\n      // reset root\n      p = ref.split('../');\n      p2 = root.split('/');\n      ref = '';\n      for(i = 0; i < p.length; i++) {\n        if(p[i] === '') {\n          p2 = p2.slice(0, p2.length-1);\n        }\n        else {\n          ref += p[i];\n        }\n      }\n      root = '';\n      for(i = 0; i < p2.length - 1; i++) {\n        if(i > 0) { root += '/'; }\n        root += p2[i];\n      }\n      rootTrimmed = true;\n    }\n    if(ref.indexOf('#') >= 0) {\n      if(ref.indexOf('/') === 0) {\n        rs = ref.split('#');\n        p  = root.split('//');\n        p2 = p[1].split('/');\n        root = p[0] + '//' + p2[0] + rs[0];\n        location = rs[1];\n      }\n      else {\n        rs = ref.split('#');\n        if(rs[0] !== '') {\n          p2 = root.split('/');\n          p2 = p2.slice(0, p2.length - 1);\n          if(!rootTrimmed) {\n            root = '';\n            for (var k = 0; k < p2.length; k++) {\n              if(k > 0) { root += '/'; }\n              root += p2[k];\n            }\n          }\n          root += '/' + ref.split('#')[0];\n        }\n        location = rs[1];\n      }\n    }\n    if (ref.indexOf('http:') === 0 || ref.indexOf('https:') === 0) {\n      if(ref.indexOf('#') >= 0) {\n        root = ref.split('#')[0];\n        location = ref.split('#')[1];\n      }\n      else {\n        root = ref;\n        location = '';\n      }\n      resolutionTable.push({obj: property, resolveAs: 'inline', root: root, key: key, location: location});\n    } else if (ref.indexOf('#') === 0) {\n      location = ref.split('#')[1];\n      resolutionTable.push({obj: property, resolveAs: 'inline', root: root, key: key, location: location});\n    } else if (ref.indexOf('/') === 0 && ref.indexOf('#') === -1) {\n      location = ref;\n      var matches = root.match(/^https?\\:\\/\\/([^\\/?#]+)(?:[\\/?#]|$)/i);\n      if(matches) {\n        root = matches[0] + ref.substring(1);\n        location = '';\n      }\n      resolutionTable.push({obj: property, resolveAs: 'inline', root: root, key: key, location: location});\n    }\n    else {\n      resolutionTable.push({obj: property, resolveAs: 'inline', root: root, key: key, location: location});\n    }\n  }\n  else if (property.type === 'array') {\n    this.resolveTo(root, property.items, resolutionTable, location);\n  }\n};\n\nResolver.prototype.resolveTo = function (root, property, resolutionTable, location) {\n  var sp, i;\n  var ref = property.$ref;\n  var lroot = root;\n  if ((typeof ref !== 'undefined') && (ref !== null)) {\n    if(ref.indexOf('#') >= 0) {\n      var parts = ref.split('#');\n\n      // #/definitions/foo\n      // foo.json#/bar\n      if(parts[0] && ref.indexOf('/') === 0) {\n\n      }\n      else if(parts[0] && (parts[0].indexOf('http:') === 0 || parts[0].indexOf('https:') === 0)) {\n        lroot = parts[0];\n        ref = parts[1];\n      }\n      else if(parts[0] && parts[0].length > 0) {\n        // relative file\n        sp = root.split('/');\n        lroot = '';\n        for(i = 0; i < sp.length - 1; i++) {\n          lroot += sp[i] + '/';\n        }\n        lroot += parts[0];\n      }\n      else {\n\n      }\n\n      location = parts[1];\n    }\n    else if (ref.indexOf('http:') === 0 || ref.indexOf('https:') === 0) {\n      lroot = ref;\n      location = '';\n    }\n    else {\n      // relative file\n      sp = root.split('/');\n      lroot = '';\n      for(i = 0; i < sp.length - 1; i++) {\n        lroot += sp[i] + '/';\n      }\n      lroot += ref;\n      location = '';\n    }\n    resolutionTable.push({\n      obj: property, resolveAs: 'ref', root: lroot, key: ref, location: location\n    });\n  } else if (property.type === 'array') {\n    var items = property.items;\n    this.resolveTo(root, items, resolutionTable, location);\n  } else {\n    if(property && (property.properties || property.additionalProperties)) {\n      var name = this.uniqueName('inline_model');\n      if (property.title) {\n        name = this.uniqueName(property.title);\n      }\n      delete property.title;\n      this.spec.definitions[name] = _.cloneDeep(property);\n      property.$ref = '#/definitions/' + name;\n      delete property.type;\n      delete property.properties;\n    }\n  }\n};\n\nResolver.prototype.uniqueName = function(base) {\n  var name = base;\n  var count = 0;\n  while(true) {\n    if(!_.isObject(this.spec.definitions[name])) {\n      return name;\n    }\n    name = base + '_' + count;\n    count++;\n  }\n};\n\nResolver.prototype.resolveAllOf = function(spec, obj, depth) {\n  depth = depth || 0;\n  obj = obj || spec;\n  var name;\n  for(var key in obj) {\n    if (!obj.hasOwnProperty(key)) {\n      continue;\n    }\n    var item = obj[key];\n    if(item === null) {\n      throw new TypeError('Swagger 2.0 does not support null types (' + obj + ').  See https://github.com/swagger-api/swagger-spec/issues/229.');\n    }\n    if(typeof item === 'object') {\n      this.resolveAllOf(spec, item, depth + 1);\n    }\n    if(item && typeof item.allOf !== 'undefined') {\n      var allOf = item.allOf;\n      if(_.isArray(allOf)) {\n        var output = _.cloneDeep(item);\n        delete output.allOf;\n\n        output['x-composed'] = true;\n        if (typeof item['x-resolved-from'] !== 'undefined') {\n          output['x-resolved-from'] = item['x-resolved-from'];\n        }\n\n        for(var i = 0; i < allOf.length; i++) {\n          var component = allOf[i];\n          var source = 'self';\n          if(typeof component['x-resolved-from'] !== 'undefined') {\n            source = component['x-resolved-from'][0];\n          }\n\n          for(var part in component) {\n            if(!output.hasOwnProperty(part)) {\n              output[part] = _.cloneDeep(component[part]);\n              if(part === 'properties') {\n                for(name in output[part]) {\n                  output[part][name]['x-resolved-from'] = source;\n                }\n              }\n            }\n            else {\n              if(part === 'properties') {\n                var properties = component[part];\n                for(name in properties) {\n                  output.properties[name] = _.cloneDeep(properties[name]);\n                  var resolvedFrom = properties[name]['x-resolved-from'];\n                  if (typeof resolvedFrom === 'undefined' || resolvedFrom === 'self') {\n                    resolvedFrom = source;\n                  }\n                  output.properties[name]['x-resolved-from'] = resolvedFrom;\n                }\n              }\n              else if(part === 'required') {\n                // merge & dedup the required array\n                var a = output.required.concat(component[part]);\n                for(var k = 0; k < a.length; ++k) {\n                  for(var j = k + 1; j < a.length; ++j) {\n                    if(a[k] === a[j]) { a.splice(j--, 1); }\n                  }\n                }\n                output.required = a;\n              }\n              else if(part === 'x-resolved-from') {\n                output['x-resolved-from'].push(source);\n              }\n              else {\n                // TODO: need to merge this property\n                // console.log('what to do with ' + part)\n              }\n            }\n          }\n        }\n        obj[key] = output;\n      }\n    }\n  }\n};\n\n},{\"./http\":5,\"lodash-compat/lang/cloneDeep\":138,\"lodash-compat/lang/isArray\":140,\"lodash-compat/lang/isObject\":144,\"lodash-compat/lang/isString\":146}],7:[function(require,module,exports){\n'use strict';\n\nvar Helpers = require('./helpers');\n\nvar _ = {\n  isPlainObject: require('lodash-compat/lang/isPlainObject'),\n  isUndefined: require('lodash-compat/lang/isUndefined'),\n  isArray: require('lodash-compat/lang/isArray'),\n  isObject: require('lodash-compat/lang/isObject'),\n  isEmpty: require('lodash-compat/lang/isEmpty'),\n  map: require('lodash-compat/collection/map'),\n  indexOf: require('lodash-compat/array/indexOf'),\n  cloneDeep: require('lodash-compat/lang/cloneDeep'),\n  keys: require('lodash-compat/object/keys'),\n  forEach: require('lodash-compat/collection/forEach')\n};\n\nvar optionHtml = module.exports.optionHtml = function  (label, value) {\n  return '<tr><td class=\"optionName\">' + label + ':</td><td>' + value + '</td></tr>';\n};\n\nmodule.exports.typeFromJsonSchema = function (type, format) {\n  var str;\n\n  if (type === 'integer' && format === 'int32') {\n    str = 'integer';\n  } else if (type === 'integer' && format === 'int64') {\n    str = 'long';\n  } else if (type === 'integer' && typeof format === 'undefined') {\n    str = 'long';\n  } else if (type === 'string' && format === 'date-time') {\n    str = 'date-time';\n  } else if (type === 'string' && format === 'date') {\n    str = 'date';\n  } else if (type === 'number' && format === 'float') {\n    str = 'float';\n  } else if (type === 'number' && format === 'double') {\n    str = 'double';\n  } else if (type === 'number' && typeof format === 'undefined') {\n    str = 'double';\n  } else if (type === 'boolean') {\n    str = 'boolean';\n  } else if (type === 'string') {\n    str = 'string';\n  }\n\n  return str;\n};\n\nvar getStringSignature = module.exports.getStringSignature = function (obj, baseComponent) {\n  var str = '';\n\n  if (typeof obj.$ref !== 'undefined') {\n    str += Helpers.simpleRef(obj.$ref);\n  } else if (typeof obj.type === 'undefined') {\n    str += 'object';\n  } else if (obj.type === 'array') {\n    if (baseComponent) {\n      str += getStringSignature((obj.items || obj.$ref || {}));\n    } else {\n      str += 'Array[';\n      str += getStringSignature((obj.items || obj.$ref || {}));\n      str += ']';\n    }\n  } else if (obj.type === 'integer' && obj.format === 'int32') {\n    str += 'integer';\n  } else if (obj.type === 'integer' && obj.format === 'int64') {\n    str += 'long';\n  } else if (obj.type === 'integer' && typeof obj.format === 'undefined') {\n    str += 'long';\n  } else if (obj.type === 'string' && obj.format === 'date-time') {\n    str += 'date-time';\n  } else if (obj.type === 'string' && obj.format === 'date') {\n    str += 'date';\n  } else if (obj.type === 'string' && typeof obj.format === 'undefined') {\n    str += 'string';\n  } else if (obj.type === 'number' && obj.format === 'float') {\n    str += 'float';\n  } else if (obj.type === 'number' && obj.format === 'double') {\n    str += 'double';\n  } else if (obj.type === 'number' && typeof obj.format === 'undefined') {\n    str += 'double';\n  } else if (obj.type === 'boolean') {\n    str += 'boolean';\n  } else if (obj.$ref) {\n    str += Helpers.simpleRef(obj.$ref);\n  } else {\n    str += obj.type;\n  }\n\n  return str;\n};\n\nvar schemaToJSON = module.exports.schemaToJSON = function (schema, models, modelsToIgnore, modelPropertyMacro) {\n  // Resolve the schema (Handle nested schemas)\n  schema = Helpers.resolveSchema(schema);\n\n  if(typeof modelPropertyMacro !== 'function') {\n    modelPropertyMacro = function(prop){\n      return (prop || {}).default;\n    };\n  }\n\n  modelsToIgnore= modelsToIgnore || {};\n\n  var type = schema.type || 'object';\n  var format = schema.format;\n  var model;\n  var output;\n\n  if (!_.isUndefined(schema.example)) {\n    output = schema.example;\n  } else if (_.isUndefined(schema.items) && _.isArray(schema.enum)) {\n    output = schema.enum[0];\n  }\n\n  if (_.isUndefined(output)) {\n    if (schema.$ref) {\n      model = models[Helpers.simpleRef(schema.$ref)];\n\n      if (!_.isUndefined(model)) {\n        if (_.isUndefined(modelsToIgnore[model.name])) {\n          modelsToIgnore[model.name] = model;\n          output = schemaToJSON(model.definition, models, modelsToIgnore, modelPropertyMacro);\n          delete modelsToIgnore[model.name];\n        } else {\n          if (model.type === 'array') {\n            output = [];\n          } else {\n            output = {};\n          }\n        }\n      }\n    } else if (!_.isUndefined(schema.default)) {\n      output = schema.default;\n    } else if (type === 'string') {\n      if (format === 'date-time') {\n        output = new Date().toISOString();\n      } else if (format === 'date') {\n        output = new Date().toISOString().split('T')[0];\n      } else {\n        output = 'string';\n      }\n    } else if (type === 'integer') {\n      output = 0;\n    } else if (type === 'number') {\n      output = 0.0;\n    } else if (type === 'boolean') {\n      output = true;\n    } else if (type === 'object') {\n      output = {};\n\n      _.forEach(schema.properties, function (property, name) {\n        var cProperty = _.cloneDeep(property);\n\n        // Allow macro to set the default value\n        cProperty.default = modelPropertyMacro(property);\n\n        output[name] = schemaToJSON(cProperty, models, modelsToIgnore, modelPropertyMacro);\n      });\n    } else if (type === 'array') {\n      output = [];\n\n      if (_.isArray(schema.items)) {\n        _.forEach(schema.items, function (item) {\n          output.push(schemaToJSON(item, models, modelsToIgnore, modelPropertyMacro));\n        });\n      } else if (_.isPlainObject(schema.items)) {\n        output.push(schemaToJSON(schema.items, models, modelsToIgnore, modelPropertyMacro));\n      } else if (_.isUndefined(schema.items)) {\n        output.push({});\n      } else {\n        Helpers.log('Array type\\'s \\'items\\' property is not an array or an object, cannot process');\n      }\n    }\n  }\n\n  return output;\n};\n\nmodule.exports.schemaToHTML =function (name, schema, models, modelPropertyMacro) {\n  var strongOpen = '<span class=\"strong\">';\n  var strongClose = '</span>';\n\n  // Allow for ignoring the 'name' argument.... shifting the rest\n  if(_.isObject(arguments[0])) {\n    name = void 0;\n    schema = arguments[0];\n    models = arguments[1];\n    modelPropertyMacro = arguments[2];\n  }\n\n  models = models || {};\n\n  // Resolve the schema (Handle nested schemas)\n  schema = Helpers.resolveSchema(schema);\n\n  // Return for empty object\n  if(_.isEmpty(schema)) {\n    return strongOpen + 'Empty' + strongClose;\n  }\n\n  // Dereference $ref from 'models'\n  if(typeof schema.$ref === 'string') {\n    name = Helpers.simpleRef(schema.$ref);\n    schema = models[name];\n    if(typeof schema === 'undefined')\n    {\n      return strongOpen + name + ' is not defined!' + strongClose;\n    }\n  }\n\n  if(typeof name !== 'string') {\n    name = schema.title || 'Inline Model';\n  }\n\n  // If we are a Model object... adjust accordingly\n  if(schema.definition) {\n    schema = schema.definition;\n  }\n\n  if(typeof modelPropertyMacro !== 'function') {\n    modelPropertyMacro = function(prop){\n      return (prop || {}).default;\n    };\n  }\n\n  var references = {};\n  var seenModels = [];\n  var inlineModels = 0;\n\n\n\n  // Generate current HTML\n  var html = processModel(schema, name);\n\n  // Generate references HTML\n  while (_.keys(references).length > 0) {\n    /* jshint ignore:start */\n    _.forEach(references, function (schema, name) {\n      var seenModel = _.indexOf(seenModels, name) > -1;\n\n      delete references[name];\n\n      if (!seenModel) {\n        seenModels.push(name);\n\n        html += '<br />' + processModel(schema, name);\n      }\n    });\n    /* jshint ignore:end */\n  }\n\n  return html;\n\n  /////////////////////////////////\n\n  function addReference(schema, name, skipRef) {\n    var modelName = name;\n    var model;\n\n    if (schema.$ref) {\n      modelName = schema.title || Helpers.simpleRef(schema.$ref);\n      model = models[modelName];\n    } else if (_.isUndefined(name)) {\n      modelName = schema.title || 'Inline Model ' + (++inlineModels);\n      model = {definition: schema};\n    }\n\n    if (skipRef !== true) {\n      references[modelName] = _.isUndefined(model) ? {} : model.definition;\n    }\n\n    return modelName;\n  }\n\n  function primitiveToHTML(schema) {\n    var html = '<span class=\"propType\">';\n    var type = schema.type || 'object';\n\n    if (schema.$ref) {\n      html += addReference(schema, Helpers.simpleRef(schema.$ref));\n    } else if (type === 'object') {\n      if (!_.isUndefined(schema.properties)) {\n        html += addReference(schema);\n      } else {\n        html += 'object';\n      }\n    } else if (type === 'array') {\n      html += 'Array[';\n\n      if (_.isArray(schema.items)) {\n        html += _.map(schema.items, addReference).join(',');\n      } else if (_.isPlainObject(schema.items)) {\n        if (_.isUndefined(schema.items.$ref)) {\n          if (!_.isUndefined(schema.items.type) && _.indexOf(['array', 'object'], schema.items.type) === -1) {\n            html += schema.items.type;\n          } else {\n            html += addReference(schema.items);\n          }\n        } else {\n          html += addReference(schema.items, Helpers.simpleRef(schema.items.$ref));\n        }\n      } else {\n        Helpers.log('Array type\\'s \\'items\\' schema is not an array or an object, cannot process');\n        html += 'object';\n      }\n\n      html += ']';\n    } else {\n      html += schema.type;\n    }\n\n    html += '</span>';\n\n    return html;\n  }\n\n  function primitiveToOptionsHTML(schema, html) {\n    var options = '';\n    var type = schema.type || 'object';\n    var isArray = type === 'array';\n\n    if (isArray) {\n      if (_.isPlainObject(schema.items) && !_.isUndefined(schema.items.type)) {\n        type = schema.items.type;\n      } else {\n        type = 'object';\n      }\n    }\n\n    if (!_.isUndefined(schema.default)) {\n      options += optionHtml('Default', schema.default);\n    }\n\n    switch (type) {\n    case 'string':\n      if (schema.minLength) {\n        options += optionHtml('Min. Length', schema.minLength);\n      }\n\n      if (schema.maxLength) {\n        options += optionHtml('Max. Length', schema.maxLength);\n      }\n\n      if (schema.pattern) {\n        options += optionHtml('Reg. Exp.', schema.pattern);\n      }\n      break;\n    case 'integer':\n    case 'number':\n      if (schema.minimum) {\n        options += optionHtml('Min. Value', schema.minimum);\n      }\n\n      if (schema.exclusiveMinimum) {\n        options += optionHtml('Exclusive Min.', 'true');\n      }\n\n      if (schema.maximum) {\n        options += optionHtml('Max. Value', schema.maximum);\n      }\n\n      if (schema.exclusiveMaximum) {\n        options += optionHtml('Exclusive Max.', 'true');\n      }\n\n      if (schema.multipleOf) {\n        options += optionHtml('Multiple Of', schema.multipleOf);\n      }\n\n      break;\n    }\n\n    if (isArray) {\n      if (schema.minItems) {\n        options += optionHtml('Min. Items', schema.minItems);\n      }\n\n      if (schema.maxItems) {\n        options += optionHtml('Max. Items', schema.maxItems);\n      }\n\n      if (schema.uniqueItems) {\n        options += optionHtml('Unique Items', 'true');\n      }\n\n      if (schema.collectionFormat) {\n        options += optionHtml('Coll. Format', schema.collectionFormat);\n      }\n    }\n\n    if (_.isUndefined(schema.items)) {\n      if (_.isArray(schema.enum)) {\n        var enumString;\n\n        if (type === 'number' || type === 'integer') {\n          enumString = schema.enum.join(', ');\n        } else {\n          enumString = '\"' + schema.enum.join('\", \"') + '\"';\n        }\n\n        options += optionHtml('Enum', enumString);\n      }\n    }\n\n    if (options.length > 0) {\n      html = '<span class=\"propWrap\">' + html + '<table class=\"optionsWrapper\"><tr><th colspan=\"2\">' + type + '</th></tr>' + options + '</table></span>';\n    }\n\n    return html;\n  }\n\n  function processModel(schema, name) {\n    var type = schema.type || 'object';\n    var isArray = schema.type === 'array';\n    var html = strongOpen + name + ' ' + (isArray ? '[' : '{') + strongClose;\n\n    if (name) {\n      seenModels.push(name);\n    }\n\n    if (isArray) {\n      if (_.isArray(schema.items)) {\n        html += '<div>' + _.map(schema.items, function (item) {\n          var type = item.type || 'object';\n\n          if (_.isUndefined(item.$ref)) {\n            if (_.indexOf(['array', 'object'], type) > -1) {\n              if (type === 'object' && _.isUndefined(item.properties)) {\n                return 'object';\n              } else {\n                return addReference(item);\n              }\n            } else {\n              return primitiveToOptionsHTML(item, type);\n            }\n          } else {\n            return addReference(item, Helpers.simpleRef(item.$ref));\n          }\n        }).join(',</div><div>');\n      } else if (_.isPlainObject(schema.items)) {\n        if (_.isUndefined(schema.items.$ref)) {\n          if (_.indexOf(['array', 'object'], schema.items.type || 'object') > -1) {\n            if ((_.isUndefined(schema.items.type) || schema.items.type === 'object') && _.isUndefined(schema.items.properties)) {\n              html += '<div>object</div>';\n            } else {\n              html += '<div>' + addReference(schema.items) + '</div>';\n            }\n          } else {\n            html += '<div>' + primitiveToOptionsHTML(schema.items, schema.items.type) + '</div>';\n          }\n        } else {\n          html += '<div>' + addReference(schema.items, Helpers.simpleRef(schema.items.$ref)) + '</div>';\n        }\n      } else {\n        Helpers.log('Array type\\'s \\'items\\' property is not an array or an object, cannot process');\n        html += '<div>object</div>';\n      }\n    } else {\n      if (schema.$ref) {\n        html += '<div>' + addReference(schema, name) + '</div>';\n      } else if (type === 'object') {\n        if (_.isPlainObject(schema.properties)) {\n          var contents = _.map(schema.properties, function (property, name) {\n            var propertyIsRequired = (_.indexOf(schema.required, name) >= 0);\n            var cProperty = _.cloneDeep(property);\n\n            var requiredClass = propertyIsRequired ? 'required' : '';\n            var html = '<span class=\"propName ' + requiredClass + '\">' + name + '</span> (';\n            var model;\n            var propDescription;\n\n            // Allow macro to set the default value\n            cProperty.default = modelPropertyMacro(cProperty);\n\n            // Resolve the schema (Handle nested schemas)\n            cProperty = Helpers.resolveSchema(cProperty);\n\n            propDescription = property.description || cProperty.description;\n\n            // We need to handle property references to primitives (Issue 339)\n            if (!_.isUndefined(cProperty.$ref)) {\n              model = models[Helpers.simpleRef(cProperty.$ref)];\n\n              if (!_.isUndefined(model) && _.indexOf([undefined, 'array', 'object'], model.definition.type) === -1) {\n                // Use referenced schema\n                cProperty = Helpers.resolveSchema(model.definition);\n              }\n            }\n\n            html += primitiveToHTML(cProperty);\n\n            if(!propertyIsRequired) {\n              html += ', <span class=\"propOptKey\">optional</span>';\n            }\n\n            if(property.readOnly) {\n                html += ', <span class=\"propReadOnly\">read only</span>';\n            }\n\n            html += ')';\n\n            if (!_.isUndefined(propDescription)) {\n              html += ': ' + '<span class=\"propDesc\">' + propDescription + '</span>';\n            }\n\n            if (cProperty.enum) {\n              html += ' = <span class=\"propVals\">[\\'' + cProperty.enum.join('\\', \\'') + '\\']</span>';\n            }\n\n            return '<div' + (property.readOnly ? ' class=\"readOnly\"' : '') + '>' + primitiveToOptionsHTML(cProperty, html);\n          }).join(',</div>');\n\n          if (contents) {\n            html += contents + '</div>';\n          }\n        }\n      } else {\n        html += '<div>' + primitiveToOptionsHTML(schema, type) + '</div>';\n      }\n    }\n\n    return html + strongOpen + (isArray ? ']' : '}') + strongClose;\n  }\n};\n},{\"./helpers\":4,\"lodash-compat/array/indexOf\":49,\"lodash-compat/collection/forEach\":54,\"lodash-compat/collection/map\":56,\"lodash-compat/lang/cloneDeep\":138,\"lodash-compat/lang/isArray\":140,\"lodash-compat/lang/isEmpty\":141,\"lodash-compat/lang/isObject\":144,\"lodash-compat/lang/isPlainObject\":145,\"lodash-compat/lang/isUndefined\":148,\"lodash-compat/object/keys\":149}],8:[function(require,module,exports){\n'use strict';\n\nvar SwaggerHttp = require('./http');\nvar _ = {\n  isObject: require('lodash-compat/lang/isObject')\n};\n\nvar SwaggerSpecConverter = module.exports = function () {\n  this.errors = [];\n  this.warnings = [];\n  this.modelMap = {};\n};\n\nSwaggerSpecConverter.prototype.setDocumentationLocation = function (location) {\n  this.docLocation = location;\n};\n\n/**\n * converts a resource listing OR api declaration\n **/\nSwaggerSpecConverter.prototype.convert = function (obj, clientAuthorizations, opts, callback) {\n  // not a valid spec\n  if(!obj || !Array.isArray(obj.apis)) {\n    return this.finish(callback, null);\n  }\n  this.clientAuthorizations = clientAuthorizations;\n\n  // create a new swagger object to return\n  var swagger = { swagger: '2.0' };\n\n  swagger.originalVersion = obj.swaggerVersion;\n\n  // add the info\n  this.apiInfo(obj, swagger);\n\n  // add security definitions\n  this.securityDefinitions(obj, swagger);\n\n  // take basePath into account\n  if (obj.basePath) {\n    this.setDocumentationLocation(obj.basePath);\n  }\n\n  // see if this is a single-file swagger definition\n  var isSingleFileSwagger = false;\n  var i;\n  for(i = 0; i < obj.apis.length; i++) {\n    var api = obj.apis[i];\n    if(Array.isArray(api.operations)) {\n      isSingleFileSwagger = true;\n    }\n  }\n  if(isSingleFileSwagger) {\n    this.declaration(obj, swagger);\n    this.finish(callback, swagger);\n  }\n  else {\n    this.resourceListing(obj, swagger, opts, callback);\n  }\n};\n\nSwaggerSpecConverter.prototype.declaration = function(obj, swagger) {\n  var name, i, p, pos;\n  if(!obj.apis) {\n    return;\n  }\n\n  if (obj.basePath.indexOf('http://') === 0) {\n    p = obj.basePath.substring('http://'.length);\n    pos = p.indexOf('/');\n    if (pos > 0) {\n      swagger.host = p.substring(0, pos);\n      swagger.basePath = p.substring(pos);\n    }\n    else {\n      swagger.host = p;\n      swagger.basePath = '/';\n    }\n  } else if (obj.basePath.indexOf('https://') === 0) {\n    p = obj.basePath.substring('https://'.length);\n    pos = p.indexOf('/');\n    if (pos > 0) {\n      swagger.host = p.substring(0, pos);\n      swagger.basePath = p.substring(pos);\n    }\n    else {\n      swagger.host = p;\n      swagger.basePath = '/';\n    }\n  } else {\n    swagger.basePath = obj.basePath;\n  }\n\n  var resourceLevelAuth;\n  if(obj.authorizations) {\n    resourceLevelAuth = obj.authorizations;\n  }\n  if(obj.consumes) {\n    swagger.consumes = obj.consumes;\n  }\n  if(obj.produces) {\n    swagger.produces = obj.produces;\n  }\n\n  // build a mapping of id to name for 1.0 model resolutions\n  if(_.isObject(obj)) {\n    for(name in obj.models) {\n      var existingModel = obj.models[name];\n      var key = (existingModel.id || name);\n      this.modelMap[key] = name;\n    }\n  }\n\n  for(i = 0; i < obj.apis.length; i++) {\n    var api = obj.apis[i];\n    var path = api.path;\n    var operations = api.operations;\n    this.operations(path, obj.resourcePath, operations, resourceLevelAuth, swagger);\n  }\n\n  var models = obj.models || {};\n  this.models(models, swagger);\n};\n\nSwaggerSpecConverter.prototype.models = function(obj, swagger) {\n  if(!_.isObject(obj)) {\n    return;\n  }\n  var name;\n\n  swagger.definitions = swagger.definitions || {};\n  for(name in obj) {\n    var existingModel = obj[name];\n    var _required = [];\n    var schema = { properties: {}};\n    var propertyName;\n    for(propertyName in existingModel.properties) {\n      var existingProperty = existingModel.properties[propertyName];\n      var property = {};\n      this.dataType(existingProperty, property);\n      if(existingProperty.description) {\n        property.description = existingProperty.description;\n      }\n      if(existingProperty['enum']) {\n        property['enum'] = existingProperty['enum'];\n      }\n      if(typeof existingProperty.required === 'boolean' && existingProperty.required === true) {\n        _required.push(propertyName);\n      }\n      if(typeof existingProperty.required === 'string' && existingProperty.required === 'true') {\n        _required.push(propertyName);\n      }\n      schema.properties[propertyName] = property;\n    }\n    if(_required.length > 0) {\n      schema.required = _required;\n    } else {\n      schema.required = existingModel.required;\n    }\n    swagger.definitions[name] = schema;\n  }\n};\n\nSwaggerSpecConverter.prototype.extractTag = function(resourcePath) {\n  var pathString = resourcePath || 'default';\n  if(pathString.indexOf('http:') === 0 || pathString.indexOf('https:') === 0) {\n    pathString = pathString.split(['/']);\n    pathString = pathString[pathString.length -1].substring();\n  }\n  if(pathString.endsWith('.json')) {\n    pathString = pathString.substring(0, pathString.length - '.json'.length);\n  }\n  return pathString.replace('/','');\n};\n\nSwaggerSpecConverter.prototype.operations = function(path, resourcePath, obj, resourceLevelAuth, swagger) {\n  if(!Array.isArray(obj)) {\n    return;\n  }\n  var i;\n\n  if(!swagger.paths) {\n    swagger.paths = {};\n  }\n\n  var pathObj = swagger.paths[path] || {};\n  var tag = this.extractTag(resourcePath);\n  swagger.tags = swagger.tags || [];\n  var matched = false;\n  for(i = 0; i < swagger.tags.length; i++) {\n    var tagObject = swagger.tags[i];\n    if(tagObject.name === tag) {\n      matched = true;\n    }\n  }\n  if(!matched) {\n    swagger.tags.push({name: tag});\n  }\n\n  for(i = 0; i < obj.length; i++) {\n    var existingOperation = obj[i];\n    var method = (existingOperation.method || existingOperation.httpMethod).toLowerCase();\n    var operation = {tags: [tag]};\n    var existingAuthorizations = existingOperation.authorizations;\n\n    if(existingAuthorizations && Object.keys(existingAuthorizations).length === 0) {\n      existingAuthorizations = resourceLevelAuth;\n    }\n\n    if(typeof existingAuthorizations !== 'undefined') {\n      var scopesObject;\n      for(var key in existingAuthorizations) {\n        operation.security = operation.security || [];\n        var scopes = existingAuthorizations[key];\n        if(scopes) {\n          var securityScopes = [];\n          for(var j in scopes) {\n            securityScopes.push(scopes[j].scope);\n          }\n          scopesObject = {};\n          scopesObject[key] = securityScopes;\n          operation.security.push(scopesObject);\n        }\n        else {\n          scopesObject = {};\n          scopesObject[key] = [];\n          operation.security.push(scopesObject);\n        }\n      }\n    }\n\n    if(existingOperation.consumes) {\n      operation.consumes = existingOperation.consumes;\n    }\n    else if(swagger.consumes) {\n      operation.consumes = swagger.consumes;\n    }\n    if(existingOperation.produces) {\n      operation.produces = existingOperation.produces;\n    }\n    else if(swagger.produces) {\n      operation.produces = swagger.produces;\n    }\n    if(existingOperation.summary) {\n      operation.summary = existingOperation.summary;\n    }\n    if(existingOperation.notes) {\n      operation.description = existingOperation.notes;\n    }\n    if(existingOperation.nickname) {\n      operation.operationId = existingOperation.nickname;\n    }\n    if(existingOperation.deprecated) {\n      operation.deprecated = existingOperation.deprecated;\n    }\n\n    this.authorizations(existingAuthorizations, swagger);\n    this.parameters(operation, existingOperation.parameters, swagger);\n    this.responseMessages(operation, existingOperation, swagger);\n\n    pathObj[method] = operation;\n  }\n\n  swagger.paths[path] = pathObj;\n};\n\nSwaggerSpecConverter.prototype.responseMessages = function(operation, existingOperation) {\n  if(!_.isObject(existingOperation)) {\n    return;\n  }\n  // build default response from the operation (1.x)\n  var defaultResponse = {};\n  this.dataType(existingOperation, defaultResponse);\n  // TODO: look into the real problem of rendering responses in swagger-ui\n  // ....should reponseType have an implicit schema?\n  if(!defaultResponse.schema && defaultResponse.type) {\n    defaultResponse = {schema: defaultResponse};\n  }\n\n  operation.responses = operation.responses || {};\n\n  // grab from responseMessages (1.2)\n  var has200 = false;\n  if(Array.isArray(existingOperation.responseMessages)) {\n    var i;\n    var existingResponses = existingOperation.responseMessages;\n    for(i = 0; i < existingResponses.length; i++) {\n      var existingResponse = existingResponses[i];\n      var response = { description: existingResponse.message };\n      if(existingResponse.code === 200) {\n        has200 = true;\n      }\n      // Convert responseModel -> schema{$ref: responseModel}\n      if(existingResponse.responseModel) {\n        response.schema = {'$ref': '#/definitions/' + existingResponse.responseModel};\n      }\n      operation.responses['' + existingResponse.code] = response;\n    }\n  }\n\n  if(has200) {\n    operation.responses['default'] = defaultResponse;\n  }\n  else {\n    operation.responses['200'] = defaultResponse;\n  }\n};\n\nSwaggerSpecConverter.prototype.authorizations = function(obj) {\n  // TODO\n  if(!_.isObject(obj)) {\n    return;\n  }\n};\n\nSwaggerSpecConverter.prototype.parameters = function(operation, obj) {\n  if(!Array.isArray(obj)) {\n    return;\n  }\n  var i;\n  for(i = 0; i < obj.length; i++) {\n    var existingParameter = obj[i];\n    var parameter = {};\n    parameter.name = existingParameter.name;\n    parameter.description = existingParameter.description;\n    parameter.required = existingParameter.required;\n    parameter.in = existingParameter.paramType;\n\n    // per #168\n    if(parameter.in === 'body') {\n      parameter.name = 'body';\n    }\n    if(parameter.in === 'form') {\n      parameter.in = 'formData';\n    }\n\n    if(existingParameter.enum) {\n      parameter.enum = existingParameter.enum;\n    }\n\n    if(existingParameter.allowMultiple === true || existingParameter.allowMultiple === 'true') {\n      var innerType = {};\n      this.dataType(existingParameter, innerType);\n      parameter.type = 'array';\n      parameter.items = innerType;\n\n      if(existingParameter.allowableValues) {\n        var av = existingParameter.allowableValues;\n        if(av.valueType === 'LIST') {\n          parameter['enum'] = av.values;\n        }\n      }\n    }\n    else {\n      this.dataType(existingParameter, parameter);\n    }\n    if(typeof existingParameter.defaultValue !== 'undefined') {\n      parameter.default = existingParameter.defaultValue;\n    }\n\n    operation.parameters = operation.parameters || [];\n    operation.parameters.push(parameter);\n  }\n};\n\nSwaggerSpecConverter.prototype.dataType = function(source, target) {\n  if(!_.isObject(source)) {\n    return;\n  }\n\n  if(source.minimum) {\n    target.minimum = source.minimum;\n  }\n  if(source.maximum) {\n    target.maximum = source.maximum;\n  }\n  if (source.format) {\n    target.format = source.format;\n  }\n\n  // default can be 'false'\n  if(typeof source.defaultValue !== 'undefined') {\n    target.default = source.defaultValue;\n  }\n\n  var jsonSchemaType = this.toJsonSchema(source);\n  if(jsonSchemaType) {\n    target = target || {};\n    if(jsonSchemaType.type) {\n      target.type = jsonSchemaType.type;\n    }\n    if(jsonSchemaType.format) {\n      target.format = jsonSchemaType.format;\n    }\n    if(jsonSchemaType.$ref) {\n      target.schema = {$ref: jsonSchemaType.$ref};\n    }\n    if(jsonSchemaType.items) {\n      target.items = jsonSchemaType.items;\n    }\n  }\n};\n\nSwaggerSpecConverter.prototype.toJsonSchema = function(source) {\n  if(!source) {\n    return 'object';\n  }\n  var detectedType = (source.type || source.dataType || source.responseClass || '');\n  var lcType = detectedType.toLowerCase();\n  var format = (source.format || '').toLowerCase();\n\n  if(lcType.indexOf('list[') === 0) {\n    var innerType = detectedType.substring(5, detectedType.length - 1);\n    var jsonType = this.toJsonSchema({type: innerType});\n    return {type: 'array', items: jsonType};\n  } else if(lcType === 'int' || (lcType === 'integer' && format === 'int32')) {\n    {return {type: 'integer', format: 'int32'};}\n  } else if(lcType === 'long' || (lcType === 'integer' && format === 'int64')) {\n    {return {type: 'integer', format: 'int64'};}\n  } else if(lcType === 'integer') {\n    {return {type: 'integer', format: 'int64'};}\n  } else if(lcType === 'float' || (lcType === 'number' && format === 'float')) {\n    {return {type: 'number', format: 'float'};}\n  } else if(lcType === 'double' || (lcType === 'number' && format === 'double')) {\n    {return {type: 'number', format: 'double'};}\n  } else if((lcType === 'string' && format === 'date-time') || (lcType === 'date')) {\n    {return {type: 'string', format: 'date-time'};}\n  } else if(lcType === 'string') {\n    {return {type: 'string'};}\n  } else if(lcType === 'file') {\n    {return {type: 'file'};}\n  } else if(lcType === 'boolean') {\n    {return {type: 'boolean'};}\n  } else if(lcType === 'boolean') {\n    {return {type: 'boolean'};}\n  } else if(lcType === 'array' || lcType === 'list') {\n    if(source.items) {\n      var it = this.toJsonSchema(source.items);\n      return {type: 'array', items: it};\n    }\n    else {\n      return {type: 'array', items: {type: 'object'}};\n    }\n  } else if(source.$ref) {\n    return {$ref: this.modelMap[source.$ref] ? '#/definitions/' + this.modelMap[source.$ref] : source.$ref};\n  } else if(lcType === 'void' || lcType === '') {\n    {return {};}\n  } else if (this.modelMap[source.type]) {\n    // If this a model using `type` instead of `$ref`, that's fine.\n    return {$ref: '#/definitions/' + this.modelMap[source.type]};\n  } else {\n    // Unknown model type or 'object', pass it along.\n    return {type: source.type};\n  }\n};\n\nSwaggerSpecConverter.prototype.resourceListing = function(obj, swagger, opts, callback) {\n  var i;\n  var processedCount = 0;   // jshint ignore:line\n  var self = this;          // jshint ignore:line\n  var expectedCount = obj.apis.length;\n  var _swagger = swagger;   // jshint ignore:line\n  var _opts = {};\n\n  if(opts && opts.requestInterceptor){\n    _opts.requestInterceptor = opts.requestInterceptor;\n  }\n\n  if(opts && opts.responseInterceptor){\n    _opts.responseInterceptor = opts.responseInterceptor;\n  }\n\n  var swaggerRequestHeaders = 'application/json';\n\n  if(opts && opts.swaggerRequestHeaders) {\n    swaggerRequestHeaders = opts.swaggerRequestHeaders;\n  }\n\n  if(expectedCount === 0) {\n    this.finish(callback, swagger);\n  }\n\n  for(i = 0; i < expectedCount; i++) {\n    var api = obj.apis[i];\n    var path = api.path;\n    var absolutePath = this.getAbsolutePath(obj.swaggerVersion, this.docLocation, path);\n\n    if(api.description) {\n      swagger.tags = swagger.tags || [];\n      swagger.tags.push({\n        name : this.extractTag(api.path),\n        description : api.description || ''\n      });\n    }\n    var http = {\n      url: absolutePath,\n      headers: { accept: swaggerRequestHeaders },\n      on: {},\n      method: 'get',\n      timeout: opts.timeout\n    };\n    /* jshint ignore:start */\n    http.on.response = function(data) {\n      processedCount += 1;\n      var obj = data.obj;\n      if(obj) {\n        self.declaration(obj, _swagger);\n      }\n      if(processedCount === expectedCount) {\n        self.finish(callback, _swagger);\n      }\n    };\n    http.on.error = function(data) {\n      console.error(data);\n      processedCount += 1;\n      if(processedCount === expectedCount) {\n        self.finish(callback, _swagger);\n      }\n    };\n    /* jshint ignore:end */\n\n    if(this.clientAuthorizations && typeof this.clientAuthorizations.apply === 'function') {\n      this.clientAuthorizations.apply(http);\n    }\n\n    new SwaggerHttp().execute(http, _opts);\n  }\n};\n\nSwaggerSpecConverter.prototype.getAbsolutePath = function(version, docLocation, path)  {\n  if(version === '1.0') {\n    if(docLocation.endsWith('.json')) {\n      // get root path\n      var pos = docLocation.lastIndexOf('/');\n      if(pos > 0) {\n        docLocation = docLocation.substring(0, pos);\n      }\n    }\n  }\n\n  var location = docLocation;\n  if(path.indexOf('http:') === 0 || path.indexOf('https:') === 0) {\n    location = path;\n  }\n  else {\n    if(docLocation.endsWith('/')) {\n      location = docLocation.substring(0, docLocation.length - 1);\n    }\n    location += path;\n  }\n  location = location.replace('{format}', 'json');\n  return location;\n};\n\nSwaggerSpecConverter.prototype.securityDefinitions = function(obj, swagger) {\n  if(obj.authorizations) {\n    var name;\n    for(name in obj.authorizations) {\n      var isValid = false;\n      var securityDefinition = {\n        vendorExtensions: {}\n      };\n      var definition = obj.authorizations[name];\n      if(definition.type === 'apiKey') {\n        securityDefinition.type = 'apiKey';\n        securityDefinition.in = definition.passAs;\n        securityDefinition.name = definition.keyname || name;\n        isValid = true;\n      }\n      else if(definition.type === 'basicAuth') {\n        securityDefinition.type = 'basicAuth';\n        isValid = true;\n      }\n      else if(definition.type === 'oauth2') {\n        var existingScopes = definition.scopes || [];\n        var scopes = {};\n        var i;\n        for(i in existingScopes) {\n          var scope = existingScopes[i];\n          scopes[scope.scope] = scope.description;\n        }\n        securityDefinition.type = 'oauth2';\n        if(i > 0) {\n          securityDefinition.scopes = scopes;\n        }\n        if(definition.grantTypes) {\n          if(definition.grantTypes.implicit) {\n            var implicit = definition.grantTypes.implicit;\n            securityDefinition.flow = 'implicit';\n            securityDefinition.authorizationUrl = implicit.loginEndpoint;\n            isValid = true;\n          }\n          /* jshint ignore:start */\n          if(definition.grantTypes['authorization_code']) {\n            if(!securityDefinition.flow) {\n              // cannot set if flow is already defined\n              var authCode = definition.grantTypes['authorization_code'];\n              securityDefinition.flow = 'accessCode';\n              securityDefinition.authorizationUrl = authCode.tokenRequestEndpoint.url;\n              securityDefinition.tokenUrl = authCode.tokenEndpoint.url;\n              isValid = true;\n            }\n          }\n          /* jshint ignore:end */\n        }\n      }\n      if(isValid) {\n        swagger.securityDefinitions = swagger.securityDefinitions || {};\n        swagger.securityDefinitions[name] = securityDefinition;\n      }\n    }\n  }\n};\n\nSwaggerSpecConverter.prototype.apiInfo = function(obj, swagger) {\n  // info section\n  if(obj.info) {\n    var info = obj.info;\n    swagger.info = {};\n\n    if(info.contact) {\n      swagger.info.contact = {};\n      swagger.info.contact.email = info.contact;\n    }\n    if(info.description) {\n      swagger.info.description = info.description;\n    }\n    if(info.title) {\n      swagger.info.title = info.title;\n    }\n    if(info.termsOfServiceUrl) {\n      swagger.info.termsOfService = info.termsOfServiceUrl;\n    }\n    if(info.license || info.licenseUrl) {\n      swagger.license = {};\n      if(info.license) {\n        swagger.license.name = info.license;\n      }\n      if(info.licenseUrl) {\n        swagger.license.url = info.licenseUrl;\n      }\n    }\n  }\n  else {\n    this.warnings.push('missing info section');\n  }\n};\n\nSwaggerSpecConverter.prototype.finish = function (callback, obj) {\n  callback(obj);\n};\n\n},{\"./http\":5,\"lodash-compat/lang/isObject\":144}],9:[function(require,module,exports){\n'use strict';\n\nvar log = require('../helpers').log;\nvar _ = {\n  isPlainObject: require('lodash-compat/lang/isPlainObject'),\n  isString: require('lodash-compat/lang/isString'),\n};\n\nvar SchemaMarkup = require('../schema-markup.js');\nvar jsyaml = require('js-yaml');\n\nvar Model = module.exports = function (name, definition, models, modelPropertyMacro) {\n  this.definition = definition || {};\n  this.isArray = definition.type === 'array';\n  this.models = models || {};\n  this.name = name || definition.title || 'Inline Model';\n  this.modelPropertyMacro = modelPropertyMacro || function (property) {\n    return property.default;\n  };\n\n  return this;\n};\n\n// Note!  This function will be removed in 2.2.x!\nModel.prototype.createJSONSample = Model.prototype.getSampleValue = function (modelsToIgnore) {\n  modelsToIgnore = modelsToIgnore || {};\n\n  modelsToIgnore[this.name] = this;\n\n  // Response support\n  if (this.examples && _.isPlainObject(this.examples) && this.examples['application/json']) {\n    this.definition.example = this.examples['application/json'];\n\n    if (_.isString(this.definition.example)) {\n      this.definition.example = jsyaml.safeLoad(this.definition.example);\n    }\n  } else if (!this.definition.example) {\n    this.definition.example = this.examples;\n  }\n\n  return SchemaMarkup.schemaToJSON(this.definition, this.models, modelsToIgnore, this.modelPropertyMacro);\n};\n\nModel.prototype.getMockSignature = function () {\n  return SchemaMarkup.schemaToHTML(this.name, this.definition, this.models, this.modelPropertyMacro);\n};\n\n},{\"../helpers\":4,\"../schema-markup.js\":7,\"js-yaml\":19,\"lodash-compat/lang/isPlainObject\":145,\"lodash-compat/lang/isString\":146}],10:[function(require,module,exports){\n'use strict';\n\nvar _ = {\n  cloneDeep: require('lodash-compat/lang/cloneDeep'),\n  isUndefined: require('lodash-compat/lang/isUndefined'),\n  isEmpty: require('lodash-compat/lang/isEmpty'),\n  isObject: require('lodash-compat/lang/isObject')\n};\nvar helpers = require('../helpers');\nvar Model = require('./model');\nvar SwaggerHttp = require('../http');\nvar Q = require('q');\n\nvar Operation = module.exports = function (parent, scheme, operationId, httpMethod, path, args, definitions, models, clientAuthorizations) {\n  var errors = [];\n\n  parent = parent || {};\n  args = args || {};\n\n  if(parent && parent.options) {\n    this.client = parent.options.client || null;\n    this.requestInterceptor = parent.options.requestInterceptor || null;\n    this.responseInterceptor = parent.options.responseInterceptor || null;\n  }\n  this.authorizations = args.security;\n  this.basePath = parent.basePath || '/';\n  this.clientAuthorizations = clientAuthorizations;\n  this.consumes = args.consumes || parent.consumes || ['application/json'];\n  this.produces = args.produces || parent.produces || ['application/json'];\n  this.deprecated = args.deprecated;\n  this.description = args.description;\n  this.host = parent.host;\n  this.method = (httpMethod || errors.push('Operation ' + operationId + ' is missing method.'));\n  this.models = models || {};\n  this.nickname = (operationId || errors.push('Operations must have a nickname.'));\n  this.operation = args;\n  this.operations = {};\n  this.parameters = args !== null ? (args.parameters || []) : {};\n  this.parent = parent;\n  this.path = (path || errors.push('Operation ' + this.nickname + ' is missing path.'));\n  this.responses = (args.responses || {});\n  this.scheme = scheme || parent.scheme || 'http';\n  this.schemes = args.schemes || parent.schemes;\n  this.security = args.security || parent.security;\n  this.summary = args.summary || '';\n  this.timeout = parent.timeout;\n  this.type = null;\n  this.useJQuery = parent.useJQuery;\n  this.jqueryAjaxCache = parent.jqueryAjaxCache;\n  this.enableCookies = parent.enableCookies;\n\n  var key;\n\n  if(!this.host) {\n    if(typeof window !== 'undefined') {\n      this.host = window.location.host;\n    }\n    else {\n      this.host = 'localhost';\n    }\n  }\n  this.parameterMacro = parent.parameterMacro || function (operation, parameter) {\n    return parameter.default;\n  };\n\n  this.inlineModels = [];\n\n  if(this.basePath !== '/' && this.basePath.slice(-1) === '/') {\n    this.basePath = this.basePath.slice(0, -1);\n  }\n\n  if (typeof this.deprecated === 'string') {\n    switch(this.deprecated.toLowerCase()) {\n      case 'true': case 'yes': case '1': {\n        this.deprecated = true;\n        break;\n      }\n\n      case 'false': case 'no': case '0': case null: {\n        this.deprecated = false;\n        break;\n      }\n\n      default: this.deprecated = Boolean(this.deprecated);\n    }\n  }\n\n  var i, model;\n\n  if (definitions) {\n    // add to global models\n    for (key in definitions) {\n      model = new Model(key, definitions[key], this.models, parent.modelPropertyMacro);\n\n      if (model) {\n        this.models[key] = model;\n      }\n    }\n  }\n  else {\n    definitions = {};\n  }\n\n  for (i = 0; i < this.parameters.length; i++) {\n    var d, param = this.parameters[i];\n\n    // Allow macro to set the default value\n    param.default = this.parameterMacro(this, param);\n\n    if (param.type === 'array') {\n      param.isList = true;\n      param.allowMultiple = true;\n    }\n\n    var innerType = this.getType(param);\n\n    if (innerType && innerType.toString().toLowerCase() === 'boolean') {\n      param.allowableValues = {};\n      param.isList = true;\n      param['enum'] = [true, false]; // use actual primitives\n    }\n\n    for(key in param) {\n      helpers.extractExtensions(key, param);\n    }\n    if(typeof param['x-example'] !== 'undefined') {\n      d = param['x-example'];\n      param.default = d;\n    }\n    if(param['x-examples']) {\n      d = param['x-examples'].default;\n      if(typeof d !== 'undefined') {\n        param.default = d;\n      }\n    }\n\n    var enumValues = param['enum'] || (param.items && param.items['enum']);\n\n    if (typeof enumValues !== 'undefined') {\n      var id;\n\n      param.allowableValues = {};\n      param.allowableValues.values = [];\n      param.allowableValues.descriptiveValues = [];\n\n      for (id = 0; id < enumValues.length; id++) {\n        var value = enumValues[id];\n        var isDefault = (value === param.default || value+'' === param.default);\n\n        param.allowableValues.values.push(value);\n        // Always have string for descriptive values....\n        param.allowableValues.descriptiveValues.push({value : value+'', isDefault: isDefault});\n      }\n    }\n\n    if (param.type === 'array') {\n      innerType = [innerType];\n\n      if (typeof param.allowableValues === 'undefined') {\n        // can't show as a list if no values to select from\n        delete param.isList;\n        delete param.allowMultiple;\n      }\n    }\n\n    param.modelSignature = {type: innerType, definitions: this.models};\n    param.signature = this.getModelSignature(innerType, this.models).toString();\n    param.sampleJSON = this.getModelSampleJSON(innerType, this.models);\n    param.responseClassSignature = param.signature;\n  }\n\n  var keyname, defaultResponseCode, response, responses = this.responses;\n\n  if (responses['200']) {\n    response = responses['200'];\n    defaultResponseCode = '200';\n  } else if (responses['201']) {\n    response = responses['201'];\n    defaultResponseCode = '201';\n  } else if (responses['202']) {\n    response = responses['202'];\n    defaultResponseCode = '202';\n  } else if (responses['203']) {\n    response = responses['203'];\n    defaultResponseCode = '203';\n  } else if (responses['204']) {\n    response = responses['204'];\n    defaultResponseCode = '204';\n  } else if (responses['205']) {\n    response = responses['205'];\n    defaultResponseCode = '205';\n  } else if (responses['206']) {\n    response = responses['206'];\n    defaultResponseCode = '206';\n  } else if (responses['default']) {\n    response = responses['default'];\n    defaultResponseCode = 'default';\n  }\n\n  for(keyname in responses) {\n    helpers.extractExtensions(keyname, responses);\n    if(typeof keyname === 'string' && keyname.indexOf('x-') === -1) {\n      var responseObject = responses[keyname];\n      if(typeof responseObject === 'object' && typeof responseObject.headers === 'object') {\n        var headers = responseObject.headers;\n        for(var headerName in headers) {\n          var header = headers[headerName];\n          if(typeof header === 'object') {\n            for(var headerKey in header) {\n              helpers.extractExtensions(headerKey, header);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (response) {\n    for(keyname in response) {\n      helpers.extractExtensions(keyname, response);\n    }\n  }\n\n  if (response && response.schema) {\n    var resolvedModel = this.resolveModel(response.schema, definitions);\n    var successResponse;\n\n    delete responses[defaultResponseCode];\n\n    if (resolvedModel) {\n      this.successResponse = {};\n      successResponse = this.successResponse[defaultResponseCode] = resolvedModel;\n    } else if (!response.schema.type || response.schema.type === 'object' || response.schema.type === 'array') {\n      // Inline model\n      this.successResponse = {};\n      successResponse = this.successResponse[defaultResponseCode] = new Model(undefined, response.schema || {}, this.models, parent.modelPropertyMacro);\n    } else {\n      // Primitive\n      this.successResponse = {};\n      successResponse = this.successResponse[defaultResponseCode] = response.schema;\n    }\n\n    if (successResponse) {\n      successResponse.vendorExtensions = response.vendorExtensions;\n      // Attach response properties\n      if (response.description) {\n        successResponse.description = response.description;\n      }\n\n      if (response.examples) {\n        successResponse.examples = response.examples;\n      }\n\n      if (response.headers) {\n        successResponse.headers = response.headers;\n      }\n    }\n\n    this.type = response;\n  }\n\n  if (errors.length > 0) {\n    if (this.resource && this.resource.api && this.resource.api.fail) {\n      this.resource.api.fail(errors);\n    }\n  }\n\n  return this;\n};\n\nOperation.prototype.isDefaultArrayItemValue = function(value, param) {\n  if (param.default && Array.isArray(param.default)) {\n    return param.default.indexOf(value) !== -1;\n  }\n  return value === param.default;\n};\n\nOperation.prototype.getType = function (param) {\n  var type = param.type;\n  var format = param.format;\n  var isArray = false;\n  var str;\n\n  if (type === 'integer' && format === 'int32') {\n    str = 'integer';\n  } else if (type === 'integer' && format === 'int64') {\n    str = 'long';\n  } else if (type === 'integer') {\n    str = 'integer';\n  } else if (type === 'string') {\n    if (format === 'date-time') {\n      str = 'date-time';\n    } else if (format === 'date') {\n      str = 'date';\n    } else {\n      str = 'string';\n    }\n  } else if (type === 'number' && format === 'float') {\n    str = 'float';\n  } else if (type === 'number' && format === 'double') {\n    str = 'double';\n  } else if (type === 'number') {\n    str = 'double';\n  } else if (type === 'boolean') {\n    str = 'boolean';\n  } else if (type === 'array') {\n    isArray = true;\n\n    if (param.items) {\n      str = this.getType(param.items);\n    }\n  } else if (type === 'file') {\n    str = 'file';\n  }\n\n  if (param.$ref) {\n    str = helpers.simpleRef(param.$ref);\n  }\n\n  var schema = param.schema;\n\n  if (schema) {\n    var ref = schema.$ref;\n\n    if (ref) {\n      ref = helpers.simpleRef(ref);\n\n      if (isArray) {\n        return [ ref ];\n      } else {\n        return ref;\n      }\n    } else {\n      // If inline schema, we add it our interal hash -> which gives us it's ID (int)\n      if(schema.type === 'object') {\n        return this.addInlineModel(schema);\n      }\n      return this.getType(schema);\n    }\n  }\n  if (isArray) {\n    return [ str ];\n  } else {\n    return str;\n  }\n};\n\n/**\n * adds an inline schema (model) to a hash, where we can ref it later\n * @param {object} schema a schema\n * @return {number} the ID of the schema being added, or null\n **/\nOperation.prototype.addInlineModel = function (schema) {\n  var len = this.inlineModels.length;\n  var model = this.resolveModel(schema, {});\n  if(model) {\n    this.inlineModels.push(model);\n    return 'Inline Model '+len; // return string ref of the inline model (used with #getInlineModel)\n  }\n  return null; // report errors?\n};\n\n/**\n * gets the internal ref to an inline model\n * @param {string} inline_str a string reference to an inline model\n * @return {Model} the model being referenced. Or null\n **/\nOperation.prototype.getInlineModel = function(inlineStr) {\n  if(/^Inline Model \\d+$/.test(inlineStr)) {\n    var id = parseInt(inlineStr.substr('Inline Model'.length).trim(),10); //\n    var model = this.inlineModels[id];\n    return model;\n  }\n  // I'm returning null here, should I rather throw an error?\n  return null;\n};\n\nOperation.prototype.resolveModel = function (schema, definitions) {\n  if (typeof schema.$ref !== 'undefined') {\n    var ref = schema.$ref;\n\n    if (ref.indexOf('#/definitions/') === 0) {\n      ref = ref.substring('#/definitions/'.length);\n    }\n\n    if (definitions[ref]) {\n      return new Model(ref, definitions[ref], this.models, this.parent.modelPropertyMacro);\n    }\n  // schema must at least be an object to get resolved to an inline Model\n  } else if (schema && typeof schema === 'object' &&\n            (schema.type === 'object' || _.isUndefined(schema.type))) {\n    return new Model(undefined, schema, this.models, this.parent.modelPropertyMacro);\n  }\n\n  return null;\n};\n\nOperation.prototype.help = function (dontPrint) {\n  var out = this.nickname + ': ' + this.summary + '\\n';\n\n  for (var i = 0; i < this.parameters.length; i++) {\n    var param = this.parameters[i];\n    var typeInfo = param.signature;\n\n    out += '\\n  * ' + param.name + ' (' + typeInfo + '): ' + param.description;\n  }\n\n  if (typeof dontPrint === 'undefined') {\n    helpers.log(out);\n  }\n\n  return out;\n};\n\nOperation.prototype.getModelSignature = function (type, definitions) {\n  var isPrimitive, listType;\n\n  if (type instanceof Array) {\n    listType = true;\n    type = type[0];\n  }\n\n  // Convert undefined to string of 'undefined'\n  if (typeof type === 'undefined') {\n    type = 'undefined';\n    isPrimitive = true;\n\n  } else if (definitions[type]){\n    // a model def exists?\n    type = definitions[type]; /* Model */\n    isPrimitive = false;\n\n  } else if (this.getInlineModel(type)) {\n    type = this.getInlineModel(type); /* Model */\n    isPrimitive = false;\n\n  } else {\n    // We default to primitive\n    isPrimitive = true;\n  }\n\n  if (isPrimitive) {\n    if (listType) {\n      return 'Array[' + type + ']';\n    } else {\n      return type.toString();\n    }\n  } else {\n    if (listType) {\n      return 'Array[' + type.getMockSignature() + ']';\n    } else {\n      return type.getMockSignature();\n    }\n  }\n};\n\nOperation.prototype.supportHeaderParams = function () {\n  return true;\n};\n\nOperation.prototype.supportedSubmitMethods = function () {\n  return this.parent.supportedSubmitMethods;\n};\n\nOperation.prototype.getHeaderParams = function (args) {\n  var headers = this.setContentTypes(args, {});\n  var headerParamsByLowerCase = {};\n\n  for (var i = 0; i < this.parameters.length; i++) {\n    var param = this.parameters[i];\n\n    if (param.in === 'header') {\n      headerParamsByLowerCase[param.name.toLowerCase()] = param;\n    }\n  }\n\n  for (var arg in args) {\n    var headerParam = headerParamsByLowerCase[arg.toLowerCase()];\n    if (typeof headerParam !== 'undefined') {\n      var value = args[arg];\n\n      if (Array.isArray(value)) {\n        value = value.toString();\n      }\n\n      headers[headerParam.name] = value;\n    }\n  }\n\n  return headers;\n};\n\nOperation.prototype.urlify = function (args) {\n  var formParams = {};\n  var requestUrl = this.path.replace(/#.*/, ''); // remove URL fragment\n  var querystring = ''; // grab params from the args, build the querystring along the way\n\n  for (var i = 0; i < this.parameters.length; i++) {\n    var param = this.parameters[i];\n\n    if (typeof args[param.name] !== 'undefined') {\n      if (param.in === 'path') {\n        var reg = new RegExp('\\{' + param.name + '\\}', 'gi');\n        var value = args[param.name];\n\n        if (Array.isArray(value)) {\n          value = this.encodePathCollection(param.collectionFormat, param.name, value);\n        } else {\n          value = this.encodePathParam(value);\n        }\n\n        requestUrl = requestUrl.replace(reg, value);\n      } else if (param.in === 'query' && typeof args[param.name] !== 'undefined') {\n        if (querystring === '' && requestUrl.indexOf('?') < 0) {\n          querystring += '?';\n        } else {\n          querystring += '&';\n        }\n\n        if (typeof param.collectionFormat !== 'undefined') {\n          var qp = args[param.name];\n\n          if (Array.isArray(qp)) {\n            querystring += this.encodeQueryCollection(param.collectionFormat, param.name, qp);\n          } else {\n            querystring += this.encodeQueryKey(param.name) + '=' + this.encodeQueryParam(args[param.name]);\n          }\n        } else {\n          querystring += this.encodeQueryKey(param.name) + '=' + this.encodeQueryParam(args[param.name]);\n        }\n      } else if (param.in === 'formData') {\n        formParams[param.name] = args[param.name];\n      }\n    }\n  }\n  var url = this.scheme + '://' + this.host;\n\n  if (this.basePath !== '/') {\n    url += this.basePath;\n  }\n  return url + requestUrl + querystring;\n};\n\nOperation.prototype.getMissingParams = function (args) {\n  var missingParams = []; // check required params, track the ones that are missing\n  var i;\n\n  for (i = 0; i < this.parameters.length; i++) {\n    var param = this.parameters[i];\n\n    if (param.required === true) {\n      if (typeof args[param.name] === 'undefined') {\n        missingParams = param.name;\n      }\n    }\n  }\n\n  return missingParams;\n};\n\nOperation.prototype.getBody = function (headers, args) {\n  var formParams = {}, hasFormParams, param, body, key, value, hasBody = false;\n\n  // look at each param and put form params in an object\n  for (var i = 0; i < this.parameters.length; i++) {\n    param = this.parameters[i];\n    if (typeof args[param.name] !== 'undefined') {\n      if (param.in === 'body') {\n        body = args[param.name];\n      } else if (param.in === 'formData') {\n        formParams[param.name] = {\n          param: param,\n          value: args[param.name]\n        };\n        hasFormParams = true;\n      }\n    }\n    else {\n      if(param.in === 'body') {\n        hasBody = true;\n      }\n    }\n  }\n\n  // if body is null and hasBody is true, AND a JSON body is requested, send empty {}\n  if(hasBody && typeof body === 'undefined') {\n    var contentType = headers['Content-Type'];\n    if(contentType && contentType.indexOf('application/json') === 0) {\n      body = '{}';\n    }\n  }\n\n  var isMultiPart = false;\n  if(headers['Content-Type'] && headers['Content-Type'].indexOf('multipart/form-data') >= 0) {\n    isMultiPart = true;\n  }\n\n  // handle form params\n  if (hasFormParams && !isMultiPart) {\n    var encoded = '';\n\n    for (key in formParams) {\n      param = formParams[key].param;\n      value = formParams[key].value;\n\n      if (typeof value !== 'undefined') {\n        if (Array.isArray(value)) {\n          if (encoded !== '') {\n            encoded += '&';\n          }\n          encoded += this.encodeQueryCollection(param.collectionFormat, key, value);\n        }\n        else {\n          if (encoded !== '') {\n            encoded += '&';\n          }\n\n          encoded += encodeURIComponent(key) + '=' + encodeURIComponent(value);\n        }\n      }\n    }\n\n    body = encoded;\n  } else if (isMultiPart) {\n    var bodyParam;\n    if (typeof FormData === 'function') {\n      bodyParam = new FormData();\n\n      bodyParam.type = 'formData';\n\n      for (key in formParams) {\n        param = formParams[key].param;\n        value = args[key];\n\n        if (typeof value !== 'undefined') {\n          if({}.toString.apply(value) === '[object File]') {\n            bodyParam.append(key, value);\n          }\n          else if (value.type === 'file' && value.value) {\n            bodyParam.append(key, value.value);\n          } else {\n            if (Array.isArray(value)) {\n              if(param.collectionFormat === 'multi') {\n                bodyParam.delete(key);\n                for(var v in value) {\n                  bodyParam.append(key, value[v]);\n                }\n              }\n              else {\n                bodyParam.append(key, this.encodeQueryCollection(param.collectionFormat, key, value).split('=').slice(1).join('='));\n              }\n            }\n            else {\n              bodyParam.append(key, value);\n            }\n          }\n        }\n      }\n      body = bodyParam;\n    }\n    else {\n      bodyParam = {};\n      for (key in formParams) {\n        value = args[key];\n        if (Array.isArray(value)) {\n          var delimeter;\n          var format = param.collectionFormat || 'multi';\n          if(format === 'ssv') {\n            delimeter = ' ';\n          }\n          else if(format === 'pipes') {\n            delimeter = '|';\n          }\n          else if(format === 'tsv') {\n            delimeter = '\\t';\n          }\n          else if(format === 'multi') {\n            bodyParam[key] = value;\n            break;\n          }\n          else {\n            delimeter = ',';\n          }\n          var data;\n          value.forEach(function(v) {\n            if(data) {\n              data += delimeter;\n            }\n            else {\n              data = '';\n            }\n            data += v;\n          });\n          bodyParam[key] = data;\n        }\n        else {\n          bodyParam[key] = value;\n        }\n      }\n      body = bodyParam;\n    }\n    headers['Content-Type'] = 'multipart/form-data';\n  }\n\n  return body;\n};\n\n/**\n * gets sample response for a single operation\n **/\nOperation.prototype.getModelSampleJSON = function (type, models) {\n  var listType, sampleJson, innerType;\n  models = models || {};\n\n  listType = (type instanceof Array);\n  innerType = listType ? type[0] : type;\n\n  if(models[innerType]) {\n    sampleJson = models[innerType].createJSONSample();\n  } else if (this.getInlineModel(innerType)){\n    sampleJson = this.getInlineModel(innerType).createJSONSample(); // may return null, if type isn't correct\n  }\n\n\n  if (sampleJson) {\n    sampleJson = listType ? [sampleJson] : sampleJson;\n\n    if (typeof sampleJson === 'string') {\n      return sampleJson;\n    } else if (_.isObject(sampleJson)) {\n      var t = sampleJson;\n\n      if (sampleJson instanceof Array && sampleJson.length > 0) {\n        t = sampleJson[0];\n      }\n\n      if (t.nodeName && typeof t === 'Node') {\n        var xmlString = new XMLSerializer().serializeToString(t);\n\n        return this.formatXml(xmlString);\n      } else {\n        return JSON.stringify(sampleJson, null, 2);\n      }\n    } else {\n      return sampleJson;\n    }\n  }\n};\n\n/**\n * legacy binding\n **/\nOperation.prototype.do = function (args, opts, callback, error, parent) {\n  return this.execute(args, opts, callback, error, parent);\n};\n\n/**\n * executes an operation\n **/\nOperation.prototype.execute = function (arg1, arg2, arg3, arg4, parent) {\n  var args = arg1 || {};\n  var opts = {}, success, error, deferred, timeout;\n\n  if (_.isObject(arg2)) {\n    opts = arg2;\n    success = arg3;\n    error = arg4;\n  }\n\n  timeout = typeof opts.timeout !== 'undefined' ? opts.timeout : this.timeout;\n\n  if(this.client) {\n    opts.client = this.client;\n  }\n\n  // add the request interceptor from parent, if none sent from client\n  if(!opts.requestInterceptor && this.requestInterceptor ) {\n    opts.requestInterceptor = this.requestInterceptor ;\n  }\n\n  if(!opts.responseInterceptor && this.responseInterceptor) {\n    opts.responseInterceptor = this.responseInterceptor;\n  }\n\n  if (typeof arg2 === 'function') {\n    success = arg2;\n    error = arg3;\n  }\n\n  if (this.parent.usePromise) {\n    deferred = Q.defer();\n  } else {\n    success = (success || this.parent.defaultSuccessCallback || helpers.log);\n    error = (error || this.parent.defaultErrorCallback || helpers.log);\n  }\n\n  if (typeof opts.useJQuery === 'undefined') {\n    opts.useJQuery = this.useJQuery;\n  }\n\n  if (typeof opts.jqueryAjaxCache === 'undefined') {\n    opts.jqueryAjaxCache = this.jqueryAjaxCache;\n  }\n\n  if (typeof opts.enableCookies === 'undefined') {\n    opts.enableCookies = this.enableCookies;\n  }\n\n  var missingParams = this.getMissingParams(args);\n\n  if (missingParams.length > 0) {\n    var message = 'missing required params: ' + missingParams;\n\n    helpers.fail(message);\n\n    if (this.parent.usePromise) {\n      deferred.reject(message);\n      return deferred.promise;\n    } else {\n      error(message, parent);\n      return {};\n    }\n  }\n\n  var allHeaders = this.getHeaderParams(args);\n  var contentTypeHeaders = this.setContentTypes(args, opts);\n  var headers = {}, attrname;\n\n  for (attrname in allHeaders) { headers[attrname] = allHeaders[attrname]; }\n  for (attrname in contentTypeHeaders) { headers[attrname] = contentTypeHeaders[attrname]; }\n\n  var body = this.getBody(contentTypeHeaders, args, opts);\n  var url = this.urlify(args);\n\n  if(url.indexOf('.{format}') > 0) {\n    if(headers) {\n      var format = headers.Accept || headers.accept;\n      if(format && format.indexOf('json') > 0) {\n        url = url.replace('.{format}', '.json');\n      }\n      else if(format && format.indexOf('xml') > 0) {\n        url = url.replace('.{format}', '.xml');\n      }\n    }\n  }\n\n  var obj = {\n    url: url,\n    method: this.method.toUpperCase(),\n    body: body,\n    enableCookies: opts.enableCookies,\n    useJQuery: opts.useJQuery,\n    jqueryAjaxCache: opts.jqueryAjaxCache,\n    deferred: deferred,\n    headers: headers,\n    clientAuthorizations: opts.clientAuthorizations,\n    on: {\n      response: function (response) {\n        if (deferred) {\n          deferred.resolve(response);\n          return deferred.promise;\n        } else {\n          return success(response, parent);\n        }\n      },\n      error: function (response) {\n        if (deferred) {\n          deferred.reject(response);\n          return deferred.promise;\n        } else {\n          return error(response, parent);\n        }\n      }\n    }\n  };\n\n  if (timeout) {\n    obj.timeout = timeout;\n  }\n\n  this.clientAuthorizations.apply(obj, this.operation.security);\n  if (opts.mock === true) {\n    return obj;\n  } else {\n    return new SwaggerHttp().execute(obj, opts);\n  }\n};\n\nfunction itemByPriority(col, itemPriority) {\n\n  // No priorities? return first...\n  if(_.isEmpty(itemPriority)) {\n    return col[0];\n  }\n\n  for (var i = 0, len = itemPriority.length; i < len; i++) {\n    if(col.indexOf(itemPriority[i]) > -1) {\n      return itemPriority[i];\n    }\n  }\n\n  // Otherwise return first\n  return col[0];\n}\n\nOperation.prototype.setContentTypes = function (args, opts) {\n  // default type\n  var allDefinedParams = this.parameters;\n  var body;\n  var consumes = args.parameterContentType || itemByPriority(this.consumes, ['application/json', 'application/yaml']);\n  var accepts = opts.responseContentType || itemByPriority(this.produces, ['application/json', 'application/yaml']);\n  var definedFileParams = [];\n  var definedFormParams = [];\n  var headers = {};\n  var i;\n\n  // get params from the operation and set them in definedFileParams, definedFormParams, headers\n  for (i = 0; i < allDefinedParams.length; i++) {\n    var param = allDefinedParams[i];\n\n    if (param.in === 'formData') {\n      if (param.type === 'file') {\n        definedFileParams.push(param);\n      } else {\n        definedFormParams.push(param);\n      }\n    } else if (param.in === 'header' && opts) {\n      var key = param.name;\n      var headerValue = opts[param.name];\n\n      if (typeof opts[param.name] !== 'undefined') {\n        headers[key] = headerValue;\n      }\n    } else if (param.in === 'body' && typeof args[param.name] !== 'undefined') {\n      body = args[param.name];\n    }\n  }\n\n  // if there's a body, need to set the consumes header via requestContentType\n  var hasBody = body || definedFileParams.length || definedFormParams.length;\n  if (this.method === 'post' || this.method === 'put' || this.method === 'patch' ||\n      ((this.method === 'delete' || this.method === 'get') && hasBody)) {\n    if (opts.requestContentType) {\n      consumes = opts.requestContentType;\n    }\n    // if any form params, content type must be set\n    if (definedFormParams.length > 0) {\n      consumes = undefined;\n      if (opts.requestContentType) {             // override if set\n        consumes = opts.requestContentType;\n      } else if (definedFileParams.length > 0) { // if a file, must be multipart/form-data\n        consumes = 'multipart/form-data';\n      } else {\n        if (this.consumes && this.consumes.length > 0) {\n          // use the consumes setting\n          for(var c in this.consumes) {\n            var chk = this.consumes[c];\n            if(chk.indexOf('application/x-www-form-urlencoded') === 0 || chk.indexOf('multipart/form-data') === 0) {\n              consumes = chk;\n            }\n          }\n        }\n      }\n      if(typeof consumes === 'undefined') {\n        // default to x-www-from-urlencoded\n        consumes = 'application/x-www-form-urlencoded';\n      }\n    }\n  }\n  else {\n    consumes = null;\n  }\n\n  if (consumes && this.consumes) {\n    if (this.consumes.indexOf(consumes) === -1) {\n      helpers.log('server doesn\\'t consume ' + consumes + ', try ' + JSON.stringify(this.consumes));\n    }\n  }\n\n  if (!this.matchesAccept(accepts)) {\n    helpers.log('server can\\'t produce ' + accepts);\n  }\n\n  if ((consumes && body !== '') || (consumes === 'application/x-www-form-urlencoded')) {\n    headers['Content-Type'] = consumes;\n  }\n  else if(this.consumes && this.consumes.length > 0 && this.consumes[0] === 'application/x-www-form-urlencoded') {\n    headers['Content-Type'] = this.consumes[0];\n  }\n\n  if (accepts) {\n    headers.Accept = accepts;\n  }\n\n  return headers;\n};\n\n/**\n * Returns true if the request accepts header matches anything in this.produces.\n *  If this.produces contains * / *, ignore the accept header.\n * @param {string=} accepts The client request accept header.\n * @return {boolean}\n */\nOperation.prototype.matchesAccept = function(accepts) {\n  // no accepts or produces, no problem!\n  if (!accepts || !this.produces) {\n    return true;\n  }\n  return this.produces.indexOf(accepts) !== -1 || this.produces.indexOf('*/*') !== -1;\n};\n\nOperation.prototype.asCurl = function (args1, args2) {\n  var opts = {mock: true};\n  if (typeof args2 === 'object') {\n    for (var argKey in args2) {\n      opts[argKey] = args2[argKey];\n    }\n  }\n  var obj = this.execute(args1, opts);\n\n  this.clientAuthorizations.apply(obj, this.operation.security);\n\n  var results = [];\n\n  results.push('-X ' + this.method.toUpperCase());\n\n  if (typeof obj.headers !== 'undefined') {\n    var key;\n\n    for (key in obj.headers) {\n      var value = obj.headers[key];\n      if(typeof value === 'string'){\n        value = value.replace(/\\'/g, '\\\\u0027');\n      }\n      results.push('--header \\'' + key + ': ' + value + '\\'');\n    }\n  }\n  var isFormData = false;\n  var isMultipart = false;\n\n  var type = obj.headers['Content-Type'];\n  if(type && type.indexOf('application/x-www-form-urlencoded') === 0) {\n    isFormData = true;\n  }\n  else if (type && type.indexOf('multipart/form-data') === 0) {\n    isFormData = true;\n    isMultipart = true;\n  }\n\n  if (obj.body) {\n    var body;\n    if (_.isObject(obj.body)) {\n      if(isMultipart) {\n        isMultipart = true;\n        // add the form data\n        for(var i = 0; i < this.parameters.length; i++) {\n          var parameter = this.parameters[i];\n          if(parameter.in === 'formData') {\n            if (!body) {\n              body = '';\n            }\n\n            var paramValue;\n            if(typeof FormData === 'function' && obj.body instanceof FormData) {\n              paramValue = obj.body.getAll(parameter.name);\n            }\n            else {\n              paramValue = obj.body[parameter.name];\n            }\n            if (paramValue) {\n              if (parameter.type === 'file') {\n                if(paramValue.name) {\n                  body += '-F ' + parameter.name + '=@\"' + paramValue.name + '\" ';\n                }\n              }\n              else {\n                if (Array.isArray(paramValue)) {\n                  if(parameter.collectionFormat === 'multi') {\n                    for(var v in paramValue) {\n                      body += '-F ' + this.encodeQueryKey(parameter.name) + '=' + paramValue[v] + ' ';\n                    }\n                  }\n                  else {\n                    body += '-F ' + this.encodeQueryCollection(parameter.collectionFormat, parameter.name, paramValue) + ' ';\n                  }\n                } else {\n                  body += '-F ' + this.encodeQueryKey(parameter.name) + '=' + paramValue + ' ';\n                }\n              }\n            }\n          }\n        }\n      }\n      if(!body) {\n        body = JSON.stringify(obj.body);\n      }\n    } else {\n      body = obj.body;\n    }\n    // escape @ => %40, ' => %27\n    body = body.replace(/\\'/g, '%27').replace(/\\n/g, ' \\\\ \\n ');\n\n    if(!isFormData) {\n      // escape & => %26\n      body = body.replace(/&/g, '%26');\n    }\n    if(isMultipart) {\n      results.push(body);\n    }\n    else {\n      results.push('-d \\'' + body.replace(/@/g, '%40') + '\\'');\n    }\n  }\n\n  return 'curl ' + (results.join(' ')) + ' \\'' + obj.url + '\\'';\n};\n\nOperation.prototype.encodePathCollection = function (type, name, value) {\n  var encoded = '';\n  var i;\n  var separator = '';\n\n  if (type === 'ssv') {\n    separator = '%20';\n  } else if (type === 'tsv') {\n    separator = '%09';\n  } else if (type === 'pipes') {\n    separator = '|';\n  } else {\n    separator = ',';\n  }\n\n  for (i = 0; i < value.length; i++) {\n    if (i === 0) {\n      encoded = this.encodeQueryParam(value[i]);\n    } else {\n      encoded += separator + this.encodeQueryParam(value[i]);\n    }\n  }\n\n  return encoded;\n};\n\nOperation.prototype.encodeQueryCollection = function (type, name, value) {\n  var encoded = '';\n  var i;\n\n  type = type || 'default';\n  if (type === 'default' || type === 'multi') {\n    for (i = 0; i < value.length; i++) {\n      if (i > 0) {encoded += '&';}\n\n      encoded += this.encodeQueryKey(name) + '=' + this.encodeQueryParam(value[i]);\n    }\n  } else {\n    var separator = '';\n\n    if (type === 'csv') {\n      separator = ',';\n    } else if (type === 'ssv') {\n      separator = '%20';\n    } else if (type === 'tsv') {\n      separator = '%09';\n    } else if (type === 'pipes') {\n      separator = '|';\n    } else if (type === 'brackets') {\n      for (i = 0; i < value.length; i++) {\n        if (i !== 0) {\n          encoded += '&';\n        }\n\n        encoded += this.encodeQueryKey(name) + '[]=' + this.encodeQueryParam(value[i]);\n      }\n    }\n\n    if (separator !== '') {\n      for (i = 0; i < value.length; i++) {\n        if (i === 0) {\n          encoded = this.encodeQueryKey(name) + '=' + this.encodeQueryParam(value[i]);\n        } else {\n          encoded += separator + this.encodeQueryParam(value[i]);\n        }\n      }\n    }\n  }\n\n  return encoded;\n};\n\nOperation.prototype.encodeQueryKey = function (arg) {\n  return encodeURIComponent(arg)\n      .replace('%5B','[').replace('%5D', ']').replace('%24', '$');\n};\n\nOperation.prototype.encodeQueryParam = function (arg) {\n  return encodeURIComponent(arg);\n};\n\n/**\n * TODO revisit, might not want to leave '/'\n **/\nOperation.prototype.encodePathParam = function (pathParam) {\n  return encodeURIComponent(pathParam);\n};\n\n},{\"../helpers\":4,\"../http\":5,\"./model\":9,\"lodash-compat/lang/cloneDeep\":138,\"lodash-compat/lang/isEmpty\":141,\"lodash-compat/lang/isObject\":144,\"lodash-compat/lang/isUndefined\":148,\"q\":157}],11:[function(require,module,exports){\n'use strict';\n\nvar OperationGroup = module.exports = function (tag, description, externalDocs, operation) {\n  this.description = description;\n  this.externalDocs = externalDocs;\n  this.name = tag;\n  this.operation = operation;\n  this.operationsArray = [];\n  this.path = tag;\n  this.tag = tag;\n};\n\nOperationGroup.prototype.sort = function () {\n\n};\n\n\n},{}],12:[function(require,module,exports){\n// shim for using process in browser\n\nvar process = module.exports = {};\nvar queue = [];\nvar draining = false;\n\nfunction drainQueue() {\n    if (draining) {\n        return;\n    }\n    draining = true;\n    var currentQueue;\n    var len = queue.length;\n    while(len) {\n        currentQueue = queue;\n        queue = [];\n        var i = -1;\n        while (++i < len) {\n            currentQueue[i]();\n        }\n        len = queue.length;\n    }\n    draining = false;\n}\nprocess.nextTick = function (fun) {\n    queue.push(fun);\n    if (!draining) {\n        setTimeout(drainQueue, 0);\n    }\n};\n\nprocess.title = 'browser';\nprocess.browser = true;\nprocess.env = {};\nprocess.argv = [];\nprocess.version = ''; // empty string to avoid regexp issues\nprocess.versions = {};\n\nfunction noop() {}\n\nprocess.on = noop;\nprocess.addListener = noop;\nprocess.once = noop;\nprocess.off = noop;\nprocess.removeListener = noop;\nprocess.removeAllListeners = noop;\nprocess.emit = noop;\n\nprocess.binding = function (name) {\n    throw new Error('process.binding is not supported');\n};\n\n// TODO(shtylman)\nprocess.cwd = function () { return '/' };\nprocess.chdir = function (dir) {\n    throw new Error('process.chdir is not supported');\n};\nprocess.umask = function() { return 0; };\n\n},{}],13:[function(require,module,exports){\n(function (Buffer){\n(function () {\n  \"use strict\";\n\n  function btoa(str) {\n    var buffer\n      ;\n\n    if (str instanceof Buffer) {\n      buffer = str;\n    } else {\n      buffer = new Buffer(str.toString(), 'binary');\n    }\n\n    return buffer.toString('base64');\n  }\n\n  module.exports = btoa;\n}());\n\n}).call(this,require(\"buffer\").Buffer)\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9idG9hL2luZGV4LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uICgpIHtcbiAgXCJ1c2Ugc3RyaWN0XCI7XG5cbiAgZnVuY3Rpb24gYnRvYShzdHIpIHtcbiAgICB2YXIgYnVmZmVyXG4gICAgICA7XG5cbiAgICBpZiAoc3RyIGluc3RhbmNlb2YgQnVmZmVyKSB7XG4gICAgICBidWZmZXIgPSBzdHI7XG4gICAgfSBlbHNlIHtcbiAgICAgIGJ1ZmZlciA9IG5ldyBCdWZmZXIoc3RyLnRvU3RyaW5nKCksICdiaW5hcnknKTtcbiAgICB9XG5cbiAgICByZXR1cm4gYnVmZmVyLnRvU3RyaW5nKCdiYXNlNjQnKTtcbiAgfVxuXG4gIG1vZHVsZS5leHBvcnRzID0gYnRvYTtcbn0oKSk7XG4iXX0=\n},{\"buffer\":14}],14:[function(require,module,exports){\n/*!\n * The buffer module from node.js, for the browser.\n *\n * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>\n * @license  MIT\n */\n\nvar base64 = require('base64-js')\nvar ieee754 = require('ieee754')\nvar isArray = require('is-array')\n\nexports.Buffer = Buffer\nexports.SlowBuffer = SlowBuffer\nexports.INSPECT_MAX_BYTES = 50\nBuffer.poolSize = 8192 // not used by this implementation\n\nvar rootParent = {}\n\n/**\n * If `Buffer.TYPED_ARRAY_SUPPORT`:\n *   === true    Use Uint8Array implementation (fastest)\n *   === false   Use Object implementation (most compatible, even IE6)\n *\n * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,\n * Opera 11.6+, iOS 4.2+.\n *\n * Due to various browser bugs, sometimes the Object implementation will be used even\n * when the browser supports typed arrays.\n *\n * Note:\n *\n *   - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,\n *     See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.\n *\n *   - Safari 5-7 lacks support for changing the `Object.prototype.constructor` property\n *     on objects.\n *\n *   - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.\n *\n *   - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of\n *     incorrect length in some situations.\n\n * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they\n * get the Object implementation, which is slower but behaves correctly.\n */\nBuffer.TYPED_ARRAY_SUPPORT = (function () {\n  function Bar () {}\n  try {\n    var arr = new Uint8Array(1)\n    arr.foo = function () { return 42 }\n    arr.constructor = Bar\n    return arr.foo() === 42 && // typed array instances can be augmented\n        arr.constructor === Bar && // constructor can be set\n        typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`\n        arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`\n  } catch (e) {\n    return false\n  }\n})()\n\nfunction kMaxLength () {\n  return Buffer.TYPED_ARRAY_SUPPORT\n    ? 0x7fffffff\n    : 0x3fffffff\n}\n\n/**\n * Class: Buffer\n * =============\n *\n * The Buffer constructor returns instances of `Uint8Array` that are augmented\n * with function properties for all the node `Buffer` API functions. We use\n * `Uint8Array` so that square bracket notation works as expected -- it returns\n * a single octet.\n *\n * By augmenting the instances, we can avoid modifying the `Uint8Array`\n * prototype.\n */\nfunction Buffer (arg) {\n  if (!(this instanceof Buffer)) {\n    // Avoid going through an ArgumentsAdaptorTrampoline in the common case.\n    if (arguments.length > 1) return new Buffer(arg, arguments[1])\n    return new Buffer(arg)\n  }\n\n  this.length = 0\n  this.parent = undefined\n\n  // Common case.\n  if (typeof arg === 'number') {\n    return fromNumber(this, arg)\n  }\n\n  // Slightly less common case.\n  if (typeof arg === 'string') {\n    return fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8')\n  }\n\n  // Unusual.\n  return fromObject(this, arg)\n}\n\nfunction fromNumber (that, length) {\n  that = allocate(that, length < 0 ? 0 : checked(length) | 0)\n  if (!Buffer.TYPED_ARRAY_SUPPORT) {\n    for (var i = 0; i < length; i++) {\n      that[i] = 0\n    }\n  }\n  return that\n}\n\nfunction fromString (that, string, encoding) {\n  if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8'\n\n  // Assumption: byteLength() return value is always < kMaxLength.\n  var length = byteLength(string, encoding) | 0\n  that = allocate(that, length)\n\n  that.write(string, encoding)\n  return that\n}\n\nfunction fromObject (that, object) {\n  if (Buffer.isBuffer(object)) return fromBuffer(that, object)\n\n  if (isArray(object)) return fromArray(that, object)\n\n  if (object == null) {\n    throw new TypeError('must start with number, buffer, array or string')\n  }\n\n  if (typeof ArrayBuffer !== 'undefined') {\n    if (object.buffer instanceof ArrayBuffer) {\n      return fromTypedArray(that, object)\n    }\n    if (object instanceof ArrayBuffer) {\n      return fromArrayBuffer(that, object)\n    }\n  }\n\n  if (object.length) return fromArrayLike(that, object)\n\n  return fromJsonObject(that, object)\n}\n\nfunction fromBuffer (that, buffer) {\n  var length = checked(buffer.length) | 0\n  that = allocate(that, length)\n  buffer.copy(that, 0, 0, length)\n  return that\n}\n\nfunction fromArray (that, array) {\n  var length = checked(array.length) | 0\n  that = allocate(that, length)\n  for (var i = 0; i < length; i += 1) {\n    that[i] = array[i] & 255\n  }\n  return that\n}\n\n// Duplicate of fromArray() to keep fromArray() monomorphic.\nfunction fromTypedArray (that, array) {\n  var length = checked(array.length) | 0\n  that = allocate(that, length)\n  // Truncating the elements is probably not what people expect from typed\n  // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior\n  // of the old Buffer constructor.\n  for (var i = 0; i < length; i += 1) {\n    that[i] = array[i] & 255\n  }\n  return that\n}\n\nfunction fromArrayBuffer (that, array) {\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    // Return an augmented `Uint8Array` instance, for best performance\n    array.byteLength\n    that = Buffer._augment(new Uint8Array(array))\n  } else {\n    // Fallback: Return an object instance of the Buffer class\n    that = fromTypedArray(that, new Uint8Array(array))\n  }\n  return that\n}\n\nfunction fromArrayLike (that, array) {\n  var length = checked(array.length) | 0\n  that = allocate(that, length)\n  for (var i = 0; i < length; i += 1) {\n    that[i] = array[i] & 255\n  }\n  return that\n}\n\n// Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object.\n// Returns a zero-length buffer for inputs that don't conform to the spec.\nfunction fromJsonObject (that, object) {\n  var array\n  var length = 0\n\n  if (object.type === 'Buffer' && isArray(object.data)) {\n    array = object.data\n    length = checked(array.length) | 0\n  }\n  that = allocate(that, length)\n\n  for (var i = 0; i < length; i += 1) {\n    that[i] = array[i] & 255\n  }\n  return that\n}\n\nfunction allocate (that, length) {\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    // Return an augmented `Uint8Array` instance, for best performance\n    that = Buffer._augment(new Uint8Array(length))\n  } else {\n    // Fallback: Return an object instance of the Buffer class\n    that.length = length\n    that._isBuffer = true\n  }\n\n  var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1\n  if (fromPool) that.parent = rootParent\n\n  return that\n}\n\nfunction checked (length) {\n  // Note: cannot use `length < kMaxLength` here because that fails when\n  // length is NaN (which is otherwise coerced to zero.)\n  if (length >= kMaxLength()) {\n    throw new RangeError('Attempt to allocate Buffer larger than maximum ' +\n                         'size: 0x' + kMaxLength().toString(16) + ' bytes')\n  }\n  return length | 0\n}\n\nfunction SlowBuffer (subject, encoding) {\n  if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding)\n\n  var buf = new Buffer(subject, encoding)\n  delete buf.parent\n  return buf\n}\n\nBuffer.isBuffer = function isBuffer (b) {\n  return !!(b != null && b._isBuffer)\n}\n\nBuffer.compare = function compare (a, b) {\n  if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {\n    throw new TypeError('Arguments must be Buffers')\n  }\n\n  if (a === b) return 0\n\n  var x = a.length\n  var y = b.length\n\n  var i = 0\n  var len = Math.min(x, y)\n  while (i < len) {\n    if (a[i] !== b[i]) break\n\n    ++i\n  }\n\n  if (i !== len) {\n    x = a[i]\n    y = b[i]\n  }\n\n  if (x < y) return -1\n  if (y < x) return 1\n  return 0\n}\n\nBuffer.isEncoding = function isEncoding (encoding) {\n  switch (String(encoding).toLowerCase()) {\n    case 'hex':\n    case 'utf8':\n    case 'utf-8':\n    case 'ascii':\n    case 'binary':\n    case 'base64':\n    case 'raw':\n    case 'ucs2':\n    case 'ucs-2':\n    case 'utf16le':\n    case 'utf-16le':\n      return true\n    default:\n      return false\n  }\n}\n\nBuffer.concat = function concat (list, length) {\n  if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.')\n\n  if (list.length === 0) {\n    return new Buffer(0)\n  }\n\n  var i\n  if (length === undefined) {\n    length = 0\n    for (i = 0; i < list.length; i++) {\n      length += list[i].length\n    }\n  }\n\n  var buf = new Buffer(length)\n  var pos = 0\n  for (i = 0; i < list.length; i++) {\n    var item = list[i]\n    item.copy(buf, pos)\n    pos += item.length\n  }\n  return buf\n}\n\nfunction byteLength (string, encoding) {\n  if (typeof string !== 'string') string = '' + string\n\n  var len = string.length\n  if (len === 0) return 0\n\n  // Use a for loop to avoid recursion\n  var loweredCase = false\n  for (;;) {\n    switch (encoding) {\n      case 'ascii':\n      case 'binary':\n      // Deprecated\n      case 'raw':\n      case 'raws':\n        return len\n      case 'utf8':\n      case 'utf-8':\n        return utf8ToBytes(string).length\n      case 'ucs2':\n      case 'ucs-2':\n      case 'utf16le':\n      case 'utf-16le':\n        return len * 2\n      case 'hex':\n        return len >>> 1\n      case 'base64':\n        return base64ToBytes(string).length\n      default:\n        if (loweredCase) return utf8ToBytes(string).length // assume utf8\n        encoding = ('' + encoding).toLowerCase()\n        loweredCase = true\n    }\n  }\n}\nBuffer.byteLength = byteLength\n\n// pre-set for values that may exist in the future\nBuffer.prototype.length = undefined\nBuffer.prototype.parent = undefined\n\nfunction slowToString (encoding, start, end) {\n  var loweredCase = false\n\n  start = start | 0\n  end = end === undefined || end === Infinity ? this.length : end | 0\n\n  if (!encoding) encoding = 'utf8'\n  if (start < 0) start = 0\n  if (end > this.length) end = this.length\n  if (end <= start) return ''\n\n  while (true) {\n    switch (encoding) {\n      case 'hex':\n        return hexSlice(this, start, end)\n\n      case 'utf8':\n      case 'utf-8':\n        return utf8Slice(this, start, end)\n\n      case 'ascii':\n        return asciiSlice(this, start, end)\n\n      case 'binary':\n        return binarySlice(this, start, end)\n\n      case 'base64':\n        return base64Slice(this, start, end)\n\n      case 'ucs2':\n      case 'ucs-2':\n      case 'utf16le':\n      case 'utf-16le':\n        return utf16leSlice(this, start, end)\n\n      default:\n        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n        encoding = (encoding + '').toLowerCase()\n        loweredCase = true\n    }\n  }\n}\n\nBuffer.prototype.toString = function toString () {\n  var length = this.length | 0\n  if (length === 0) return ''\n  if (arguments.length === 0) return utf8Slice(this, 0, length)\n  return slowToString.apply(this, arguments)\n}\n\nBuffer.prototype.equals = function equals (b) {\n  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')\n  if (this === b) return true\n  return Buffer.compare(this, b) === 0\n}\n\nBuffer.prototype.inspect = function inspect () {\n  var str = ''\n  var max = exports.INSPECT_MAX_BYTES\n  if (this.length > 0) {\n    str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')\n    if (this.length > max) str += ' ... '\n  }\n  return '<Buffer ' + str + '>'\n}\n\nBuffer.prototype.compare = function compare (b) {\n  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')\n  if (this === b) return 0\n  return Buffer.compare(this, b)\n}\n\nBuffer.prototype.indexOf = function indexOf (val, byteOffset) {\n  if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff\n  else if (byteOffset < -0x80000000) byteOffset = -0x80000000\n  byteOffset >>= 0\n\n  if (this.length === 0) return -1\n  if (byteOffset >= this.length) return -1\n\n  // Negative offsets start from the end of the buffer\n  if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0)\n\n  if (typeof val === 'string') {\n    if (val.length === 0) return -1 // special case: looking for empty string always fails\n    return String.prototype.indexOf.call(this, val, byteOffset)\n  }\n  if (Buffer.isBuffer(val)) {\n    return arrayIndexOf(this, val, byteOffset)\n  }\n  if (typeof val === 'number') {\n    if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') {\n      return Uint8Array.prototype.indexOf.call(this, val, byteOffset)\n    }\n    return arrayIndexOf(this, [ val ], byteOffset)\n  }\n\n  function arrayIndexOf (arr, val, byteOffset) {\n    var foundIndex = -1\n    for (var i = 0; byteOffset + i < arr.length; i++) {\n      if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) {\n        if (foundIndex === -1) foundIndex = i\n        if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex\n      } else {\n        foundIndex = -1\n      }\n    }\n    return -1\n  }\n\n  throw new TypeError('val must be string, number or Buffer')\n}\n\n// `get` is deprecated\nBuffer.prototype.get = function get (offset) {\n  console.log('.get() is deprecated. Access using array indexes instead.')\n  return this.readUInt8(offset)\n}\n\n// `set` is deprecated\nBuffer.prototype.set = function set (v, offset) {\n  console.log('.set() is deprecated. Access using array indexes instead.')\n  return this.writeUInt8(v, offset)\n}\n\nfunction hexWrite (buf, string, offset, length) {\n  offset = Number(offset) || 0\n  var remaining = buf.length - offset\n  if (!length) {\n    length = remaining\n  } else {\n    length = Number(length)\n    if (length > remaining) {\n      length = remaining\n    }\n  }\n\n  // must be an even number of digits\n  var strLen = string.length\n  if (strLen % 2 !== 0) throw new Error('Invalid hex string')\n\n  if (length > strLen / 2) {\n    length = strLen / 2\n  }\n  for (var i = 0; i < length; i++) {\n    var parsed = parseInt(string.substr(i * 2, 2), 16)\n    if (isNaN(parsed)) throw new Error('Invalid hex string')\n    buf[offset + i] = parsed\n  }\n  return i\n}\n\nfunction utf8Write (buf, string, offset, length) {\n  return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nfunction asciiWrite (buf, string, offset, length) {\n  return blitBuffer(asciiToBytes(string), buf, offset, length)\n}\n\nfunction binaryWrite (buf, string, offset, length) {\n  return asciiWrite(buf, string, offset, length)\n}\n\nfunction base64Write (buf, string, offset, length) {\n  return blitBuffer(base64ToBytes(string), buf, offset, length)\n}\n\nfunction ucs2Write (buf, string, offset, length) {\n  return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nBuffer.prototype.write = function write (string, offset, length, encoding) {\n  // Buffer#write(string)\n  if (offset === undefined) {\n    encoding = 'utf8'\n    length = this.length\n    offset = 0\n  // Buffer#write(string, encoding)\n  } else if (length === undefined && typeof offset === 'string') {\n    encoding = offset\n    length = this.length\n    offset = 0\n  // Buffer#write(string, offset[, length][, encoding])\n  } else if (isFinite(offset)) {\n    offset = offset | 0\n    if (isFinite(length)) {\n      length = length | 0\n      if (encoding === undefined) encoding = 'utf8'\n    } else {\n      encoding = length\n      length = undefined\n    }\n  // legacy write(string, encoding, offset, length) - remove in v0.13\n  } else {\n    var swap = encoding\n    encoding = offset\n    offset = length | 0\n    length = swap\n  }\n\n  var remaining = this.length - offset\n  if (length === undefined || length > remaining) length = remaining\n\n  if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {\n    throw new RangeError('attempt to write outside buffer bounds')\n  }\n\n  if (!encoding) encoding = 'utf8'\n\n  var loweredCase = false\n  for (;;) {\n    switch (encoding) {\n      case 'hex':\n        return hexWrite(this, string, offset, length)\n\n      case 'utf8':\n      case 'utf-8':\n        return utf8Write(this, string, offset, length)\n\n      case 'ascii':\n        return asciiWrite(this, string, offset, length)\n\n      case 'binary':\n        return binaryWrite(this, string, offset, length)\n\n      case 'base64':\n        // Warning: maxLength not taken into account in base64Write\n        return base64Write(this, string, offset, length)\n\n      case 'ucs2':\n      case 'ucs-2':\n      case 'utf16le':\n      case 'utf-16le':\n        return ucs2Write(this, string, offset, length)\n\n      default:\n        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n        encoding = ('' + encoding).toLowerCase()\n        loweredCase = true\n    }\n  }\n}\n\nBuffer.prototype.toJSON = function toJSON () {\n  return {\n    type: 'Buffer',\n    data: Array.prototype.slice.call(this._arr || this, 0)\n  }\n}\n\nfunction base64Slice (buf, start, end) {\n  if (start === 0 && end === buf.length) {\n    return base64.fromByteArray(buf)\n  } else {\n    return base64.fromByteArray(buf.slice(start, end))\n  }\n}\n\nfunction utf8Slice (buf, start, end) {\n  end = Math.min(buf.length, end)\n  var res = []\n\n  var i = start\n  while (i < end) {\n    var firstByte = buf[i]\n    var codePoint = null\n    var bytesPerSequence = (firstByte > 0xEF) ? 4\n      : (firstByte > 0xDF) ? 3\n      : (firstByte > 0xBF) ? 2\n      : 1\n\n    if (i + bytesPerSequence <= end) {\n      var secondByte, thirdByte, fourthByte, tempCodePoint\n\n      switch (bytesPerSequence) {\n        case 1:\n          if (firstByte < 0x80) {\n            codePoint = firstByte\n          }\n          break\n        case 2:\n          secondByte = buf[i + 1]\n          if ((secondByte & 0xC0) === 0x80) {\n            tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)\n            if (tempCodePoint > 0x7F) {\n              codePoint = tempCodePoint\n            }\n          }\n          break\n        case 3:\n          secondByte = buf[i + 1]\n          thirdByte = buf[i + 2]\n          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {\n            tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)\n            if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {\n              codePoint = tempCodePoint\n            }\n          }\n          break\n        case 4:\n          secondByte = buf[i + 1]\n          thirdByte = buf[i + 2]\n          fourthByte = buf[i + 3]\n          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {\n            tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)\n            if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {\n              codePoint = tempCodePoint\n            }\n          }\n      }\n    }\n\n    if (codePoint === null) {\n      // we did not generate a valid codePoint so insert a\n      // replacement char (U+FFFD) and advance only 1 byte\n      codePoint = 0xFFFD\n      bytesPerSequence = 1\n    } else if (codePoint > 0xFFFF) {\n      // encode to utf16 (surrogate pair dance)\n      codePoint -= 0x10000\n      res.push(codePoint >>> 10 & 0x3FF | 0xD800)\n      codePoint = 0xDC00 | codePoint & 0x3FF\n    }\n\n    res.push(codePoint)\n    i += bytesPerSequence\n  }\n\n  return decodeCodePointsArray(res)\n}\n\n// Based on http://stackoverflow.com/a/22747272/680742, the browser with\n// the lowest limit is Chrome, with 0x10000 args.\n// We go 1 magnitude less, for safety\nvar MAX_ARGUMENTS_LENGTH = 0x1000\n\nfunction decodeCodePointsArray (codePoints) {\n  var len = codePoints.length\n  if (len <= MAX_ARGUMENTS_LENGTH) {\n    return String.fromCharCode.apply(String, codePoints) // avoid extra slice()\n  }\n\n  // Decode in chunks to avoid \"call stack size exceeded\".\n  var res = ''\n  var i = 0\n  while (i < len) {\n    res += String.fromCharCode.apply(\n      String,\n      codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)\n    )\n  }\n  return res\n}\n\nfunction asciiSlice (buf, start, end) {\n  var ret = ''\n  end = Math.min(buf.length, end)\n\n  for (var i = start; i < end; i++) {\n    ret += String.fromCharCode(buf[i] & 0x7F)\n  }\n  return ret\n}\n\nfunction binarySlice (buf, start, end) {\n  var ret = ''\n  end = Math.min(buf.length, end)\n\n  for (var i = start; i < end; i++) {\n    ret += String.fromCharCode(buf[i])\n  }\n  return ret\n}\n\nfunction hexSlice (buf, start, end) {\n  var len = buf.length\n\n  if (!start || start < 0) start = 0\n  if (!end || end < 0 || end > len) end = len\n\n  var out = ''\n  for (var i = start; i < end; i++) {\n    out += toHex(buf[i])\n  }\n  return out\n}\n\nfunction utf16leSlice (buf, start, end) {\n  var bytes = buf.slice(start, end)\n  var res = ''\n  for (var i = 0; i < bytes.length; i += 2) {\n    res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)\n  }\n  return res\n}\n\nBuffer.prototype.slice = function slice (start, end) {\n  var len = this.length\n  start = ~~start\n  end = end === undefined ? len : ~~end\n\n  if (start < 0) {\n    start += len\n    if (start < 0) start = 0\n  } else if (start > len) {\n    start = len\n  }\n\n  if (end < 0) {\n    end += len\n    if (end < 0) end = 0\n  } else if (end > len) {\n    end = len\n  }\n\n  if (end < start) end = start\n\n  var newBuf\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    newBuf = Buffer._augment(this.subarray(start, end))\n  } else {\n    var sliceLen = end - start\n    newBuf = new Buffer(sliceLen, undefined)\n    for (var i = 0; i < sliceLen; i++) {\n      newBuf[i] = this[i + start]\n    }\n  }\n\n  if (newBuf.length) newBuf.parent = this.parent || this\n\n  return newBuf\n}\n\n/*\n * Need to make sure that buffer isn't trying to write out of bounds.\n */\nfunction checkOffset (offset, ext, length) {\n  if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')\n  if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')\n}\n\nBuffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {\n  offset = offset | 0\n  byteLength = byteLength | 0\n  if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n  var val = this[offset]\n  var mul = 1\n  var i = 0\n  while (++i < byteLength && (mul *= 0x100)) {\n    val += this[offset + i] * mul\n  }\n\n  return val\n}\n\nBuffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {\n  offset = offset | 0\n  byteLength = byteLength | 0\n  if (!noAssert) {\n    checkOffset(offset, byteLength, this.length)\n  }\n\n  var val = this[offset + --byteLength]\n  var mul = 1\n  while (byteLength > 0 && (mul *= 0x100)) {\n    val += this[offset + --byteLength] * mul\n  }\n\n  return val\n}\n\nBuffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 1, this.length)\n  return this[offset]\n}\n\nBuffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 2, this.length)\n  return this[offset] | (this[offset + 1] << 8)\n}\n\nBuffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 2, this.length)\n  return (this[offset] << 8) | this[offset + 1]\n}\n\nBuffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 4, this.length)\n\n  return ((this[offset]) |\n      (this[offset + 1] << 8) |\n      (this[offset + 2] << 16)) +\n      (this[offset + 3] * 0x1000000)\n}\n\nBuffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 4, this.length)\n\n  return (this[offset] * 0x1000000) +\n    ((this[offset + 1] << 16) |\n    (this[offset + 2] << 8) |\n    this[offset + 3])\n}\n\nBuffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {\n  offset = offset | 0\n  byteLength = byteLength | 0\n  if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n  var val = this[offset]\n  var mul = 1\n  var i = 0\n  while (++i < byteLength && (mul *= 0x100)) {\n    val += this[offset + i] * mul\n  }\n  mul *= 0x80\n\n  if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n  return val\n}\n\nBuffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {\n  offset = offset | 0\n  byteLength = byteLength | 0\n  if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n  var i = byteLength\n  var mul = 1\n  var val = this[offset + --i]\n  while (i > 0 && (mul *= 0x100)) {\n    val += this[offset + --i] * mul\n  }\n  mul *= 0x80\n\n  if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n  return val\n}\n\nBuffer.prototype.readInt8 = function readInt8 (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 1, this.length)\n  if (!(this[offset] & 0x80)) return (this[offset])\n  return ((0xff - this[offset] + 1) * -1)\n}\n\nBuffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 2, this.length)\n  var val = this[offset] | (this[offset + 1] << 8)\n  return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 2, this.length)\n  var val = this[offset + 1] | (this[offset] << 8)\n  return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 4, this.length)\n\n  return (this[offset]) |\n    (this[offset + 1] << 8) |\n    (this[offset + 2] << 16) |\n    (this[offset + 3] << 24)\n}\n\nBuffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 4, this.length)\n\n  return (this[offset] << 24) |\n    (this[offset + 1] << 16) |\n    (this[offset + 2] << 8) |\n    (this[offset + 3])\n}\n\nBuffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 4, this.length)\n  return ieee754.read(this, offset, true, 23, 4)\n}\n\nBuffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 4, this.length)\n  return ieee754.read(this, offset, false, 23, 4)\n}\n\nBuffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 8, this.length)\n  return ieee754.read(this, offset, true, 52, 8)\n}\n\nBuffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {\n  if (!noAssert) checkOffset(offset, 8, this.length)\n  return ieee754.read(this, offset, false, 52, 8)\n}\n\nfunction checkInt (buf, value, offset, ext, max, min) {\n  if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance')\n  if (value > max || value < min) throw new RangeError('value is out of bounds')\n  if (offset + ext > buf.length) throw new RangeError('index out of range')\n}\n\nBuffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {\n  value = +value\n  offset = offset | 0\n  byteLength = byteLength | 0\n  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)\n\n  var mul = 1\n  var i = 0\n  this[offset] = value & 0xFF\n  while (++i < byteLength && (mul *= 0x100)) {\n    this[offset + i] = (value / mul) & 0xFF\n  }\n\n  return offset + byteLength\n}\n\nBuffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {\n  value = +value\n  offset = offset | 0\n  byteLength = byteLength | 0\n  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)\n\n  var i = byteLength - 1\n  var mul = 1\n  this[offset + i] = value & 0xFF\n  while (--i >= 0 && (mul *= 0x100)) {\n    this[offset + i] = (value / mul) & 0xFF\n  }\n\n  return offset + byteLength\n}\n\nBuffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)\n  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)\n  this[offset] = value\n  return offset + 1\n}\n\nfunction objectWriteUInt16 (buf, value, offset, littleEndian) {\n  if (value < 0) value = 0xffff + value + 1\n  for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) {\n    buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>\n      (littleEndian ? i : 1 - i) * 8\n  }\n}\n\nBuffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    this[offset] = value\n    this[offset + 1] = (value >>> 8)\n  } else {\n    objectWriteUInt16(this, value, offset, true)\n  }\n  return offset + 2\n}\n\nBuffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    this[offset] = (value >>> 8)\n    this[offset + 1] = value\n  } else {\n    objectWriteUInt16(this, value, offset, false)\n  }\n  return offset + 2\n}\n\nfunction objectWriteUInt32 (buf, value, offset, littleEndian) {\n  if (value < 0) value = 0xffffffff + value + 1\n  for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) {\n    buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff\n  }\n}\n\nBuffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    this[offset + 3] = (value >>> 24)\n    this[offset + 2] = (value >>> 16)\n    this[offset + 1] = (value >>> 8)\n    this[offset] = value\n  } else {\n    objectWriteUInt32(this, value, offset, true)\n  }\n  return offset + 4\n}\n\nBuffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    this[offset] = (value >>> 24)\n    this[offset + 1] = (value >>> 16)\n    this[offset + 2] = (value >>> 8)\n    this[offset + 3] = value\n  } else {\n    objectWriteUInt32(this, value, offset, false)\n  }\n  return offset + 4\n}\n\nBuffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) {\n    var limit = Math.pow(2, 8 * byteLength - 1)\n\n    checkInt(this, value, offset, byteLength, limit - 1, -limit)\n  }\n\n  var i = 0\n  var mul = 1\n  var sub = value < 0 ? 1 : 0\n  this[offset] = value & 0xFF\n  while (++i < byteLength && (mul *= 0x100)) {\n    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n  }\n\n  return offset + byteLength\n}\n\nBuffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) {\n    var limit = Math.pow(2, 8 * byteLength - 1)\n\n    checkInt(this, value, offset, byteLength, limit - 1, -limit)\n  }\n\n  var i = byteLength - 1\n  var mul = 1\n  var sub = value < 0 ? 1 : 0\n  this[offset + i] = value & 0xFF\n  while (--i >= 0 && (mul *= 0x100)) {\n    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n  }\n\n  return offset + byteLength\n}\n\nBuffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)\n  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)\n  if (value < 0) value = 0xff + value + 1\n  this[offset] = value\n  return offset + 1\n}\n\nBuffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    this[offset] = value\n    this[offset + 1] = (value >>> 8)\n  } else {\n    objectWriteUInt16(this, value, offset, true)\n  }\n  return offset + 2\n}\n\nBuffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    this[offset] = (value >>> 8)\n    this[offset + 1] = value\n  } else {\n    objectWriteUInt16(this, value, offset, false)\n  }\n  return offset + 2\n}\n\nBuffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    this[offset] = value\n    this[offset + 1] = (value >>> 8)\n    this[offset + 2] = (value >>> 16)\n    this[offset + 3] = (value >>> 24)\n  } else {\n    objectWriteUInt32(this, value, offset, true)\n  }\n  return offset + 4\n}\n\nBuffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {\n  value = +value\n  offset = offset | 0\n  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n  if (value < 0) value = 0xffffffff + value + 1\n  if (Buffer.TYPED_ARRAY_SUPPORT) {\n    this[offset] = (value >>> 24)\n    this[offset + 1] = (value >>> 16)\n    this[offset + 2] = (value >>> 8)\n    this[offset + 3] = value\n  } else {\n    objectWriteUInt32(this, value, offset, false)\n  }\n  return offset + 4\n}\n\nfunction checkIEEE754 (buf, value, offset, ext, max, min) {\n  if (value > max || value < min) throw new RangeError('value is out of bounds')\n  if (offset + ext > buf.length) throw new RangeError('index out of range')\n  if (offset < 0) throw new RangeError('index out of range')\n}\n\nfunction writeFloat (buf, value, offset, littleEndian, noAssert) {\n  if (!noAssert) {\n    checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)\n  }\n  ieee754.write(buf, value, offset, littleEndian, 23, 4)\n  return offset + 4\n}\n\nBuffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {\n  return writeFloat(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {\n  return writeFloat(this, value, offset, false, noAssert)\n}\n\nfunction writeDouble (buf, value, offset, littleEndian, noAssert) {\n  if (!noAssert) {\n    checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)\n  }\n  ieee754.write(buf, value, offset, littleEndian, 52, 8)\n  return offset + 8\n}\n\nBuffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {\n  return writeDouble(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {\n  return writeDouble(this, value, offset, false, noAssert)\n}\n\n// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)\nBuffer.prototype.copy = function copy (target, targetStart, start, end) {\n  if (!start) start = 0\n  if (!end && end !== 0) end = this.length\n  if (targetStart >= target.length) targetStart = target.length\n  if (!targetStart) targetStart = 0\n  if (end > 0 && end < start) end = start\n\n  // Copy 0 bytes; we're done\n  if (end === start) return 0\n  if (target.length === 0 || this.length === 0) return 0\n\n  // Fatal error conditions\n  if (targetStart < 0) {\n    throw new RangeError('targetStart out of bounds')\n  }\n  if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')\n  if (end < 0) throw new RangeError('sourceEnd out of bounds')\n\n  // Are we oob?\n  if (end > this.length) end = this.length\n  if (target.length - targetStart < end - start) {\n    end = target.length - targetStart + start\n  }\n\n  var len = end - start\n  var i\n\n  if (this === target && start < targetStart && targetStart < end) {\n    // descending copy from end\n    for (i = len - 1; i >= 0; i--) {\n      target[i + targetStart] = this[i + start]\n    }\n  } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {\n    // ascending copy from start\n    for (i = 0; i < len; i++) {\n      target[i + targetStart] = this[i + start]\n    }\n  } else {\n    target._set(this.subarray(start, start + len), targetStart)\n  }\n\n  return len\n}\n\n// fill(value, start=0, end=buffer.length)\nBuffer.prototype.fill = function fill (value, start, end) {\n  if (!value) value = 0\n  if (!start) start = 0\n  if (!end) end = this.length\n\n  if (end < start) throw new RangeError('end < start')\n\n  // Fill 0 bytes; we're done\n  if (end === start) return\n  if (this.length === 0) return\n\n  if (start < 0 || start >= this.length) throw new RangeError('start out of bounds')\n  if (end < 0 || end > this.length) throw new RangeError('end out of bounds')\n\n  var i\n  if (typeof value === 'number') {\n    for (i = start; i < end; i++) {\n      this[i] = value\n    }\n  } else {\n    var bytes = utf8ToBytes(value.toString())\n    var len = bytes.length\n    for (i = start; i < end; i++) {\n      this[i] = bytes[i % len]\n    }\n  }\n\n  return this\n}\n\n/**\n * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance.\n * Added in Node 0.12. Only available in browsers that support ArrayBuffer.\n */\nBuffer.prototype.toArrayBuffer = function toArrayBuffer () {\n  if (typeof Uint8Array !== 'undefined') {\n    if (Buffer.TYPED_ARRAY_SUPPORT) {\n      return (new Buffer(this)).buffer\n    } else {\n      var buf = new Uint8Array(this.length)\n      for (var i = 0, len = buf.length; i < len; i += 1) {\n        buf[i] = this[i]\n      }\n      return buf.buffer\n    }\n  } else {\n    throw new TypeError('Buffer.toArrayBuffer not supported in this browser')\n  }\n}\n\n// HELPER FUNCTIONS\n// ================\n\nvar BP = Buffer.prototype\n\n/**\n * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods\n */\nBuffer._augment = function _augment (arr) {\n  arr.constructor = Buffer\n  arr._isBuffer = true\n\n  // save reference to original Uint8Array set method before overwriting\n  arr._set = arr.set\n\n  // deprecated\n  arr.get = BP.get\n  arr.set = BP.set\n\n  arr.write = BP.write\n  arr.toString = BP.toString\n  arr.toLocaleString = BP.toString\n  arr.toJSON = BP.toJSON\n  arr.equals = BP.equals\n  arr.compare = BP.compare\n  arr.indexOf = BP.indexOf\n  arr.copy = BP.copy\n  arr.slice = BP.slice\n  arr.readUIntLE = BP.readUIntLE\n  arr.readUIntBE = BP.readUIntBE\n  arr.readUInt8 = BP.readUInt8\n  arr.readUInt16LE = BP.readUInt16LE\n  arr.readUInt16BE = BP.readUInt16BE\n  arr.readUInt32LE = BP.readUInt32LE\n  arr.readUInt32BE = BP.readUInt32BE\n  arr.readIntLE = BP.readIntLE\n  arr.readIntBE = BP.readIntBE\n  arr.readInt8 = BP.readInt8\n  arr.readInt16LE = BP.readInt16LE\n  arr.readInt16BE = BP.readInt16BE\n  arr.readInt32LE = BP.readInt32LE\n  arr.readInt32BE = BP.readInt32BE\n  arr.readFloatLE = BP.readFloatLE\n  arr.readFloatBE = BP.readFloatBE\n  arr.readDoubleLE = BP.readDoubleLE\n  arr.readDoubleBE = BP.readDoubleBE\n  arr.writeUInt8 = BP.writeUInt8\n  arr.writeUIntLE = BP.writeUIntLE\n  arr.writeUIntBE = BP.writeUIntBE\n  arr.writeUInt16LE = BP.writeUInt16LE\n  arr.writeUInt16BE = BP.writeUInt16BE\n  arr.writeUInt32LE = BP.writeUInt32LE\n  arr.writeUInt32BE = BP.writeUInt32BE\n  arr.writeIntLE = BP.writeIntLE\n  arr.writeIntBE = BP.writeIntBE\n  arr.writeInt8 = BP.writeInt8\n  arr.writeInt16LE = BP.writeInt16LE\n  arr.writeInt16BE = BP.writeInt16BE\n  arr.writeInt32LE = BP.writeInt32LE\n  arr.writeInt32BE = BP.writeInt32BE\n  arr.writeFloatLE = BP.writeFloatLE\n  arr.writeFloatBE = BP.writeFloatBE\n  arr.writeDoubleLE = BP.writeDoubleLE\n  arr.writeDoubleBE = BP.writeDoubleBE\n  arr.fill = BP.fill\n  arr.inspect = BP.inspect\n  arr.toArrayBuffer = BP.toArrayBuffer\n\n  return arr\n}\n\nvar INVALID_BASE64_RE = /[^+\\/0-9A-Za-z-_]/g\n\nfunction base64clean (str) {\n  // Node strips out invalid characters like \\n and \\t from the string, base64-js does not\n  str = stringtrim(str).replace(INVALID_BASE64_RE, '')\n  // Node converts strings with length < 2 to ''\n  if (str.length < 2) return ''\n  // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not\n  while (str.length % 4 !== 0) {\n    str = str + '='\n  }\n  return str\n}\n\nfunction stringtrim (str) {\n  if (str.trim) return str.trim()\n  return str.replace(/^\\s+|\\s+$/g, '')\n}\n\nfunction toHex (n) {\n  if (n < 16) return '0' + n.toString(16)\n  return n.toString(16)\n}\n\nfunction utf8ToBytes (string, units) {\n  units = units || Infinity\n  var codePoint\n  var length = string.length\n  var leadSurrogate = null\n  var bytes = []\n\n  for (var i = 0; i < length; i++) {\n    codePoint = string.charCodeAt(i)\n\n    // is surrogate component\n    if (codePoint > 0xD7FF && codePoint < 0xE000) {\n      // last char was a lead\n      if (!leadSurrogate) {\n        // no lead yet\n        if (codePoint > 0xDBFF) {\n          // unexpected trail\n          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n          continue\n        } else if (i + 1 === length) {\n          // unpaired lead\n          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n          continue\n        }\n\n        // valid lead\n        leadSurrogate = codePoint\n\n        continue\n      }\n\n      // 2 leads in a row\n      if (codePoint < 0xDC00) {\n        if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n        leadSurrogate = codePoint\n        continue\n      }\n\n      // valid surrogate pair\n      codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000\n    } else if (leadSurrogate) {\n      // valid bmp char, but last char was a lead\n      if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n    }\n\n    leadSurrogate = null\n\n    // encode utf8\n    if (codePoint < 0x80) {\n      if ((units -= 1) < 0) break\n      bytes.push(codePoint)\n    } else if (codePoint < 0x800) {\n      if ((units -= 2) < 0) break\n      bytes.push(\n        codePoint >> 0x6 | 0xC0,\n        codePoint & 0x3F | 0x80\n      )\n    } else if (codePoint < 0x10000) {\n      if ((units -= 3) < 0) break\n      bytes.push(\n        codePoint >> 0xC | 0xE0,\n        codePoint >> 0x6 & 0x3F | 0x80,\n        codePoint & 0x3F | 0x80\n      )\n    } else if (codePoint < 0x110000) {\n      if ((units -= 4) < 0) break\n      bytes.push(\n        codePoint >> 0x12 | 0xF0,\n        codePoint >> 0xC & 0x3F | 0x80,\n        codePoint >> 0x6 & 0x3F | 0x80,\n        codePoint & 0x3F | 0x80\n      )\n    } else {\n      throw new Error('Invalid code point')\n    }\n  }\n\n  return bytes\n}\n\nfunction asciiToBytes (str) {\n  var byteArray = []\n  for (var i = 0; i < str.length; i++) {\n    // Node's code seems to be doing this and not & 0x7F..\n    byteArray.push(str.charCodeAt(i) & 0xFF)\n  }\n  return byteArray\n}\n\nfunction utf16leToBytes (str, units) {\n  var c, hi, lo\n  var byteArray = []\n  for (var i = 0; i < str.length; i++) {\n    if ((units -= 2) < 0) break\n\n    c = str.charCodeAt(i)\n    hi = c >> 8\n    lo = c % 256\n    byteArray.push(lo)\n    byteArray.push(hi)\n  }\n\n  return byteArray\n}\n\nfunction base64ToBytes (str) {\n  return base64.toByteArray(base64clean(str))\n}\n\nfunction blitBuffer (src, dst, offset, length) {\n  for (var i = 0; i < length; i++) {\n    if ((i + offset >= dst.length) || (i >= src.length)) break\n    dst[i + offset] = src[i]\n  }\n  return i\n}\n\n},{\"base64-js\":15,\"ieee754\":16,\"is-array\":17}],15:[function(require,module,exports){\nvar lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n;(function (exports) {\n\t'use strict';\n\n  var Arr = (typeof Uint8Array !== 'undefined')\n    ? Uint8Array\n    : Array\n\n\tvar PLUS   = '+'.charCodeAt(0)\n\tvar SLASH  = '/'.charCodeAt(0)\n\tvar NUMBER = '0'.charCodeAt(0)\n\tvar LOWER  = 'a'.charCodeAt(0)\n\tvar UPPER  = 'A'.charCodeAt(0)\n\tvar PLUS_URL_SAFE = '-'.charCodeAt(0)\n\tvar SLASH_URL_SAFE = '_'.charCodeAt(0)\n\n\tfunction decode (elt) {\n\t\tvar code = elt.charCodeAt(0)\n\t\tif (code === PLUS ||\n\t\t    code === PLUS_URL_SAFE)\n\t\t\treturn 62 // '+'\n\t\tif (code === SLASH ||\n\t\t    code === SLASH_URL_SAFE)\n\t\t\treturn 63 // '/'\n\t\tif (code < NUMBER)\n\t\t\treturn -1 //no match\n\t\tif (code < NUMBER + 10)\n\t\t\treturn code - NUMBER + 26 + 26\n\t\tif (code < UPPER + 26)\n\t\t\treturn code - UPPER\n\t\tif (code < LOWER + 26)\n\t\t\treturn code - LOWER + 26\n\t}\n\n\tfunction b64ToByteArray (b64) {\n\t\tvar i, j, l, tmp, placeHolders, arr\n\n\t\tif (b64.length % 4 > 0) {\n\t\t\tthrow new Error('Invalid string. Length must be a multiple of 4')\n\t\t}\n\n\t\t// the number of equal signs (place holders)\n\t\t// if there are two placeholders, than the two characters before it\n\t\t// represent one byte\n\t\t// if there is only one, then the three characters before it represent 2 bytes\n\t\t// this is just a cheap hack to not do indexOf twice\n\t\tvar len = b64.length\n\t\tplaceHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0\n\n\t\t// base64 is 4/3 + up to two characters of the original data\n\t\tarr = new Arr(b64.length * 3 / 4 - placeHolders)\n\n\t\t// if there are placeholders, only get up to the last complete 4 chars\n\t\tl = placeHolders > 0 ? b64.length - 4 : b64.length\n\n\t\tvar L = 0\n\n\t\tfunction push (v) {\n\t\t\tarr[L++] = v\n\t\t}\n\n\t\tfor (i = 0, j = 0; i < l; i += 4, j += 3) {\n\t\t\ttmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3))\n\t\t\tpush((tmp & 0xFF0000) >> 16)\n\t\t\tpush((tmp & 0xFF00) >> 8)\n\t\t\tpush(tmp & 0xFF)\n\t\t}\n\n\t\tif (placeHolders === 2) {\n\t\t\ttmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4)\n\t\t\tpush(tmp & 0xFF)\n\t\t} else if (placeHolders === 1) {\n\t\t\ttmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2)\n\t\t\tpush((tmp >> 8) & 0xFF)\n\t\t\tpush(tmp & 0xFF)\n\t\t}\n\n\t\treturn arr\n\t}\n\n\tfunction uint8ToBase64 (uint8) {\n\t\tvar i,\n\t\t\textraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes\n\t\t\toutput = \"\",\n\t\t\ttemp, length\n\n\t\tfunction encode (num) {\n\t\t\treturn lookup.charAt(num)\n\t\t}\n\n\t\tfunction tripletToBase64 (num) {\n\t\t\treturn encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F)\n\t\t}\n\n\t\t// go through the array every three bytes, we'll deal with trailing stuff later\n\t\tfor (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {\n\t\t\ttemp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])\n\t\t\toutput += tripletToBase64(temp)\n\t\t}\n\n\t\t// pad the end with zeros, but make sure to not forget the extra bytes\n\t\tswitch (extraBytes) {\n\t\t\tcase 1:\n\t\t\t\ttemp = uint8[uint8.length - 1]\n\t\t\t\toutput += encode(temp >> 2)\n\t\t\t\toutput += encode((temp << 4) & 0x3F)\n\t\t\t\toutput += '=='\n\t\t\t\tbreak\n\t\t\tcase 2:\n\t\t\t\ttemp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])\n\t\t\t\toutput += encode(temp >> 10)\n\t\t\t\toutput += encode((temp >> 4) & 0x3F)\n\t\t\t\toutput += encode((temp << 2) & 0x3F)\n\t\t\t\toutput += '='\n\t\t\t\tbreak\n\t\t}\n\n\t\treturn output\n\t}\n\n\texports.toByteArray = b64ToByteArray\n\texports.fromByteArray = uint8ToBase64\n}(typeof exports === 'undefined' ? (this.base64js = {}) : exports))\n\n},{}],16:[function(require,module,exports){\nexports.read = function (buffer, offset, isLE, mLen, nBytes) {\n  var e, m\n  var eLen = nBytes * 8 - mLen - 1\n  var eMax = (1 << eLen) - 1\n  var eBias = eMax >> 1\n  var nBits = -7\n  var i = isLE ? (nBytes - 1) : 0\n  var d = isLE ? -1 : 1\n  var s = buffer[offset + i]\n\n  i += d\n\n  e = s & ((1 << (-nBits)) - 1)\n  s >>= (-nBits)\n  nBits += eLen\n  for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}\n\n  m = e & ((1 << (-nBits)) - 1)\n  e >>= (-nBits)\n  nBits += mLen\n  for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}\n\n  if (e === 0) {\n    e = 1 - eBias\n  } else if (e === eMax) {\n    return m ? NaN : ((s ? -1 : 1) * Infinity)\n  } else {\n    m = m + Math.pow(2, mLen)\n    e = e - eBias\n  }\n  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)\n}\n\nexports.write = function (buffer, value, offset, isLE, mLen, nBytes) {\n  var e, m, c\n  var eLen = nBytes * 8 - mLen - 1\n  var eMax = (1 << eLen) - 1\n  var eBias = eMax >> 1\n  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)\n  var i = isLE ? 0 : (nBytes - 1)\n  var d = isLE ? 1 : -1\n  var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0\n\n  value = Math.abs(value)\n\n  if (isNaN(value) || value === Infinity) {\n    m = isNaN(value) ? 1 : 0\n    e = eMax\n  } else {\n    e = Math.floor(Math.log(value) / Math.LN2)\n    if (value * (c = Math.pow(2, -e)) < 1) {\n      e--\n      c *= 2\n    }\n    if (e + eBias >= 1) {\n      value += rt / c\n    } else {\n      value += rt * Math.pow(2, 1 - eBias)\n    }\n    if (value * c >= 2) {\n      e++\n      c /= 2\n    }\n\n    if (e + eBias >= eMax) {\n      m = 0\n      e = eMax\n    } else if (e + eBias >= 1) {\n      m = (value * c - 1) * Math.pow(2, mLen)\n      e = e + eBias\n    } else {\n      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)\n      e = 0\n    }\n  }\n\n  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}\n\n  e = (e << mLen) | m\n  eLen += mLen\n  for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}\n\n  buffer[offset + i - d] |= s * 128\n}\n\n},{}],17:[function(require,module,exports){\n\n/**\n * isArray\n */\n\nvar isArray = Array.isArray;\n\n/**\n * toString\n */\n\nvar str = Object.prototype.toString;\n\n/**\n * Whether or not the given `val`\n * is an array.\n *\n * example:\n *\n *        isArray([]);\n *        // > true\n *        isArray(arguments);\n *        // > false\n *        isArray('');\n *        // > false\n *\n * @param {mixed} val\n * @return {bool}\n */\n\nmodule.exports = isArray || function (val) {\n  return !! val && '[object Array]' == str.call(val);\n};\n\n},{}],18:[function(require,module,exports){\n/* jshint node: true */\n(function () {\n    \"use strict\";\n\n    function CookieAccessInfo(domain, path, secure, script) {\n        if (this instanceof CookieAccessInfo) {\n            this.domain = domain || undefined;\n            this.path = path || \"/\";\n            this.secure = !!secure;\n            this.script = !!script;\n            return this;\n        }\n        return new CookieAccessInfo(domain, path, secure, script);\n    }\n    exports.CookieAccessInfo = CookieAccessInfo;\n\n    function Cookie(cookiestr, request_domain, request_path) {\n        if (cookiestr instanceof Cookie) {\n            return cookiestr;\n        }\n        if (this instanceof Cookie) {\n            this.name = null;\n            this.value = null;\n            this.expiration_date = Infinity;\n            this.path = String(request_path || \"/\");\n            this.explicit_path = false;\n            this.domain = request_domain || null;\n            this.explicit_domain = false;\n            this.secure = false; //how to define default?\n            this.noscript = false; //httponly\n            if (cookiestr) {\n                this.parse(cookiestr, request_domain, request_path);\n            }\n            return this;\n        }\n        return new Cookie(cookiestr, request_domain, request_path);\n    }\n    exports.Cookie = Cookie;\n\n    Cookie.prototype.toString = function toString() {\n        var str = [this.name + \"=\" + this.value];\n        if (this.expiration_date !== Infinity) {\n            str.push(\"expires=\" + (new Date(this.expiration_date)).toGMTString());\n        }\n        if (this.domain) {\n            str.push(\"domain=\" + this.domain);\n        }\n        if (this.path) {\n            str.push(\"path=\" + this.path);\n        }\n        if (this.secure) {\n            str.push(\"secure\");\n        }\n        if (this.noscript) {\n            str.push(\"httponly\");\n        }\n        return str.join(\"; \");\n    };\n\n    Cookie.prototype.toValueString = function toValueString() {\n        return this.name + \"=\" + this.value;\n    };\n\n    var cookie_str_splitter = /[:](?=\\s*[a-zA-Z0-9_\\-]+\\s*[=])/g;\n    Cookie.prototype.parse = function parse(str, request_domain, request_path) {\n        if (this instanceof Cookie) {\n            var parts = str.split(\";\").filter(function (value) {\n                    return !!value;\n                }),\n                pair = parts[0].match(/([^=]+)=([\\s\\S]*)/),\n                key = pair[1],\n                value = pair[2],\n                i;\n            this.name = key;\n            this.value = value;\n\n            for (i = 1; i < parts.length; i += 1) {\n                pair = parts[i].match(/([^=]+)(?:=([\\s\\S]*))?/);\n                key = pair[1].trim().toLowerCase();\n                value = pair[2];\n                switch (key) {\n                case \"httponly\":\n                    this.noscript = true;\n                    break;\n                case \"expires\":\n                    this.expiration_date = value ?\n                            Number(Date.parse(value)) :\n                            Infinity;\n                    break;\n                case \"path\":\n                    this.path = value ?\n                            value.trim() :\n                            \"\";\n                    this.explicit_path = true;\n                    break;\n                case \"domain\":\n                    this.domain = value ?\n                            value.trim() :\n                            \"\";\n                    this.explicit_domain = !!this.domain;\n                    break;\n                case \"secure\":\n                    this.secure = true;\n                    break;\n                }\n            }\n\n            if (!this.explicit_path) {\n               this.path = request_path || \"/\";\n            }\n            if (!this.explicit_domain) {\n               this.domain = request_domain;\n            }\n\n            return this;\n        }\n        return new Cookie().parse(str, request_domain, request_path);\n    };\n\n    Cookie.prototype.matches = function matches(access_info) {\n        if (this.noscript && access_info.script ||\n                this.secure && !access_info.secure ||\n                !this.collidesWith(access_info)) {\n            return false;\n        }\n        return true;\n    };\n\n    Cookie.prototype.collidesWith = function collidesWith(access_info) {\n        if ((this.path && !access_info.path) || (this.domain && !access_info.domain)) {\n            return false;\n        }\n        if (this.path && access_info.path.indexOf(this.path) !== 0) {\n            return false;\n        }\n        if (this.explicit_path && access_info.path.indexOf( this.path ) !== 0) {\n           return false;\n        }\n        var access_domain = access_info.domain && access_info.domain.replace(/^[\\.]/,'');\n        var cookie_domain = this.domain && this.domain.replace(/^[\\.]/,'');\n        if (cookie_domain === access_domain) {\n            return true;\n        }\n        if (cookie_domain) {\n            if (!this.explicit_domain) {\n                return false; // we already checked if the domains were exactly the same\n            }\n            var wildcard = access_domain.indexOf(cookie_domain);\n            if (wildcard === -1 || wildcard !== access_domain.length - cookie_domain.length) {\n                return false;\n            }\n            return true;\n        }\n        return true;\n    };\n\n    function CookieJar() {\n        var cookies, cookies_list, collidable_cookie;\n        if (this instanceof CookieJar) {\n            cookies = Object.create(null); //name: [Cookie]\n\n            this.setCookie = function setCookie(cookie, request_domain, request_path) {\n                var remove, i;\n                cookie = new Cookie(cookie, request_domain, request_path);\n                //Delete the cookie if the set is past the current time\n                remove = cookie.expiration_date <= Date.now();\n                if (cookies[cookie.name] !== undefined) {\n                    cookies_list = cookies[cookie.name];\n                    for (i = 0; i < cookies_list.length; i += 1) {\n                        collidable_cookie = cookies_list[i];\n                        if (collidable_cookie.collidesWith(cookie)) {\n                            if (remove) {\n                                cookies_list.splice(i, 1);\n                                if (cookies_list.length === 0) {\n                                    delete cookies[cookie.name];\n                                }\n                                return false;\n                            }\n                            cookies_list[i] = cookie;\n                            return cookie;\n                        }\n                    }\n                    if (remove) {\n                        return false;\n                    }\n                    cookies_list.push(cookie);\n                    return cookie;\n                }\n                if (remove) {\n                    return false;\n                }\n                cookies[cookie.name] = [cookie];\n                return cookies[cookie.name];\n            };\n            //returns a cookie\n            this.getCookie = function getCookie(cookie_name, access_info) {\n                var cookie, i;\n                cookies_list = cookies[cookie_name];\n                if (!cookies_list) {\n                    return;\n                }\n                for (i = 0; i < cookies_list.length; i += 1) {\n                    cookie = cookies_list[i];\n                    if (cookie.expiration_date <= Date.now()) {\n                        if (cookies_list.length === 0) {\n                            delete cookies[cookie.name];\n                        }\n                        continue;\n                    }\n\n                    if (cookie.matches(access_info)) {\n                        return cookie;\n                    }\n                }\n            };\n            //returns a list of cookies\n            this.getCookies = function getCookies(access_info) {\n                var matches = [], cookie_name, cookie;\n                for (cookie_name in cookies) {\n                    cookie = this.getCookie(cookie_name, access_info);\n                    if (cookie) {\n                        matches.push(cookie);\n                    }\n                }\n                matches.toString = function toString() {\n                    return matches.join(\":\");\n                };\n                matches.toValueString = function toValueString() {\n                    return matches.map(function (c) {\n                        return c.toValueString();\n                    }).join(';');\n                };\n                return matches;\n            };\n\n            return this;\n        }\n        return new CookieJar();\n    }\n    exports.CookieJar = CookieJar;\n\n    //returns list of cookies that were set correctly. Cookies that are expired and removed are not returned.\n    CookieJar.prototype.setCookies = function setCookies(cookies, request_domain, request_path) {\n        cookies = Array.isArray(cookies) ?\n                cookies :\n                cookies.split(cookie_str_splitter);\n        var successful = [],\n            i,\n            cookie;\n        cookies = cookies.map(function(item){\n            return new Cookie(item, request_domain, request_path);\n        });\n        for (i = 0; i < cookies.length; i += 1) {\n            cookie = cookies[i];\n            if (this.setCookie(cookie, request_domain, request_path)) {\n                successful.push(cookie);\n            }\n        }\n        return successful;\n    };\n}());\n\n},{}],19:[function(require,module,exports){\n'use strict';\n\n\nvar yaml = require('./lib/js-yaml.js');\n\n\nmodule.exports = yaml;\n\n},{\"./lib/js-yaml.js\":20}],20:[function(require,module,exports){\n'use strict';\n\n\nvar loader = require('./js-yaml/loader');\nvar dumper = require('./js-yaml/dumper');\n\n\nfunction deprecated(name) {\n  return function () {\n    throw new Error('Function ' + name + ' is deprecated and cannot be used.');\n  };\n}\n\n\nmodule.exports.Type                = require('./js-yaml/type');\nmodule.exports.Schema              = require('./js-yaml/schema');\nmodule.exports.FAILSAFE_SCHEMA     = require('./js-yaml/schema/failsafe');\nmodule.exports.JSON_SCHEMA         = require('./js-yaml/schema/json');\nmodule.exports.CORE_SCHEMA         = require('./js-yaml/schema/core');\nmodule.exports.DEFAULT_SAFE_SCHEMA = require('./js-yaml/schema/default_safe');\nmodule.exports.DEFAULT_FULL_SCHEMA = require('./js-yaml/schema/default_full');\nmodule.exports.load                = loader.load;\nmodule.exports.loadAll             = loader.loadAll;\nmodule.exports.safeLoad            = loader.safeLoad;\nmodule.exports.safeLoadAll         = loader.safeLoadAll;\nmodule.exports.dump                = dumper.dump;\nmodule.exports.safeDump            = dumper.safeDump;\nmodule.exports.YAMLException       = require('./js-yaml/exception');\n\n// Deprecated schema names from JS-YAML 2.0.x\nmodule.exports.MINIMAL_SCHEMA = require('./js-yaml/schema/failsafe');\nmodule.exports.SAFE_SCHEMA    = require('./js-yaml/schema/default_safe');\nmodule.exports.DEFAULT_SCHEMA = require('./js-yaml/schema/default_full');\n\n// Deprecated functions from JS-YAML 1.x.x\nmodule.exports.scan           = deprecated('scan');\nmodule.exports.parse          = deprecated('parse');\nmodule.exports.compose        = deprecated('compose');\nmodule.exports.addConstructor = deprecated('addConstructor');\n\n},{\"./js-yaml/dumper\":22,\"./js-yaml/exception\":23,\"./js-yaml/loader\":24,\"./js-yaml/schema\":26,\"./js-yaml/schema/core\":27,\"./js-yaml/schema/default_full\":28,\"./js-yaml/schema/default_safe\":29,\"./js-yaml/schema/failsafe\":30,\"./js-yaml/schema/json\":31,\"./js-yaml/type\":32}],21:[function(require,module,exports){\n'use strict';\n\n\nfunction isNothing(subject) {\n  return (typeof subject === 'undefined') || (subject === null);\n}\n\n\nfunction isObject(subject) {\n  return (typeof subject === 'object') && (subject !== null);\n}\n\n\nfunction toArray(sequence) {\n  if (Array.isArray(sequence)) return sequence;\n  else if (isNothing(sequence)) return [];\n\n  return [ sequence ];\n}\n\n\nfunction extend(target, source) {\n  var index, length, key, sourceKeys;\n\n  if (source) {\n    sourceKeys = Object.keys(source);\n\n    for (index = 0, length = sourceKeys.length; index < length; index += 1) {\n      key = sourceKeys[index];\n      target[key] = source[key];\n    }\n  }\n\n  return target;\n}\n\n\nfunction repeat(string, count) {\n  var result = '', cycle;\n\n  for (cycle = 0; cycle < count; cycle += 1) {\n    result += string;\n  }\n\n  return result;\n}\n\n\nfunction isNegativeZero(number) {\n  return (number === 0) && (Number.NEGATIVE_INFINITY === 1 / number);\n}\n\n\nmodule.exports.isNothing      = isNothing;\nmodule.exports.isObject       = isObject;\nmodule.exports.toArray        = toArray;\nmodule.exports.repeat         = repeat;\nmodule.exports.isNegativeZero = isNegativeZero;\nmodule.exports.extend         = extend;\n\n},{}],22:[function(require,module,exports){\n'use strict';\n\n/*eslint-disable no-use-before-define*/\n\nvar common              = require('./common');\nvar YAMLException       = require('./exception');\nvar DEFAULT_FULL_SCHEMA = require('./schema/default_full');\nvar DEFAULT_SAFE_SCHEMA = require('./schema/default_safe');\n\nvar _toString       = Object.prototype.toString;\nvar _hasOwnProperty = Object.prototype.hasOwnProperty;\n\nvar CHAR_TAB                  = 0x09; /* Tab */\nvar CHAR_LINE_FEED            = 0x0A; /* LF */\nvar CHAR_SPACE                = 0x20; /* Space */\nvar CHAR_EXCLAMATION          = 0x21; /* ! */\nvar CHAR_DOUBLE_QUOTE         = 0x22; /* \" */\nvar CHAR_SHARP                = 0x23; /* # */\nvar CHAR_PERCENT              = 0x25; /* % */\nvar CHAR_AMPERSAND            = 0x26; /* & */\nvar CHAR_SINGLE_QUOTE         = 0x27; /* ' */\nvar CHAR_ASTERISK             = 0x2A; /* * */\nvar CHAR_COMMA                = 0x2C; /* , */\nvar CHAR_MINUS                = 0x2D; /* - */\nvar CHAR_COLON                = 0x3A; /* : */\nvar CHAR_GREATER_THAN         = 0x3E; /* > */\nvar CHAR_QUESTION             = 0x3F; /* ? */\nvar CHAR_COMMERCIAL_AT        = 0x40; /* @ */\nvar CHAR_LEFT_SQUARE_BRACKET  = 0x5B; /* [ */\nvar CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */\nvar CHAR_GRAVE_ACCENT         = 0x60; /* ` */\nvar CHAR_LEFT_CURLY_BRACKET   = 0x7B; /* { */\nvar CHAR_VERTICAL_LINE        = 0x7C; /* | */\nvar CHAR_RIGHT_CURLY_BRACKET  = 0x7D; /* } */\n\nvar ESCAPE_SEQUENCES = {};\n\nESCAPE_SEQUENCES[0x00]   = '\\\\0';\nESCAPE_SEQUENCES[0x07]   = '\\\\a';\nESCAPE_SEQUENCES[0x08]   = '\\\\b';\nESCAPE_SEQUENCES[0x09]   = '\\\\t';\nESCAPE_SEQUENCES[0x0A]   = '\\\\n';\nESCAPE_SEQUENCES[0x0B]   = '\\\\v';\nESCAPE_SEQUENCES[0x0C]   = '\\\\f';\nESCAPE_SEQUENCES[0x0D]   = '\\\\r';\nESCAPE_SEQUENCES[0x1B]   = '\\\\e';\nESCAPE_SEQUENCES[0x22]   = '\\\\\"';\nESCAPE_SEQUENCES[0x5C]   = '\\\\\\\\';\nESCAPE_SEQUENCES[0x85]   = '\\\\N';\nESCAPE_SEQUENCES[0xA0]   = '\\\\_';\nESCAPE_SEQUENCES[0x2028] = '\\\\L';\nESCAPE_SEQUENCES[0x2029] = '\\\\P';\n\nvar DEPRECATED_BOOLEANS_SYNTAX = [\n  'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON',\n  'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF'\n];\n\nfunction compileStyleMap(schema, map) {\n  var result, keys, index, length, tag, style, type;\n\n  if (map === null) return {};\n\n  result = {};\n  keys = Object.keys(map);\n\n  for (index = 0, length = keys.length; index < length; index += 1) {\n    tag = keys[index];\n    style = String(map[tag]);\n\n    if (tag.slice(0, 2) === '!!') {\n      tag = 'tag:yaml.org,2002:' + tag.slice(2);\n    }\n\n    type = schema.compiledTypeMap[tag];\n\n    if (type && _hasOwnProperty.call(type.styleAliases, style)) {\n      style = type.styleAliases[style];\n    }\n\n    result[tag] = style;\n  }\n\n  return result;\n}\n\nfunction encodeHex(character) {\n  var string, handle, length;\n\n  string = character.toString(16).toUpperCase();\n\n  if (character <= 0xFF) {\n    handle = 'x';\n    length = 2;\n  } else if (character <= 0xFFFF) {\n    handle = 'u';\n    length = 4;\n  } else if (character <= 0xFFFFFFFF) {\n    handle = 'U';\n    length = 8;\n  } else {\n    throw new YAMLException('code point within a string may not be greater than 0xFFFFFFFF');\n  }\n\n  return '\\\\' + handle + common.repeat('0', length - string.length) + string;\n}\n\nfunction State(options) {\n  this.schema       = options['schema'] || DEFAULT_FULL_SCHEMA;\n  this.indent       = Math.max(1, (options['indent'] || 2));\n  this.skipInvalid  = options['skipInvalid'] || false;\n  this.flowLevel    = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']);\n  this.styleMap     = compileStyleMap(this.schema, options['styles'] || null);\n  this.sortKeys     = options['sortKeys'] || false;\n  this.lineWidth    = options['lineWidth'] || 80;\n  this.noRefs       = options['noRefs'] || false;\n  this.noCompatMode = options['noCompatMode'] || false;\n\n  this.implicitTypes = this.schema.compiledImplicit;\n  this.explicitTypes = this.schema.compiledExplicit;\n\n  this.tag = null;\n  this.result = '';\n\n  this.duplicates = [];\n  this.usedDuplicates = null;\n}\n\n// Indents every line in a string. Empty lines (\\n only) are not indented.\nfunction indentString(string, spaces) {\n  var ind = common.repeat(' ', spaces),\n      position = 0,\n      next = -1,\n      result = '',\n      line,\n      length = string.length;\n\n  while (position < length) {\n    next = string.indexOf('\\n', position);\n    if (next === -1) {\n      line = string.slice(position);\n      position = length;\n    } else {\n      line = string.slice(position, next + 1);\n      position = next + 1;\n    }\n\n    if (line.length && line !== '\\n') result += ind;\n\n    result += line;\n  }\n\n  return result;\n}\n\nfunction generateNextLine(state, level) {\n  return '\\n' + common.repeat(' ', state.indent * level);\n}\n\nfunction testImplicitResolving(state, str) {\n  var index, length, type;\n\n  for (index = 0, length = state.implicitTypes.length; index < length; index += 1) {\n    type = state.implicitTypes[index];\n\n    if (type.resolve(str)) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\n// [33] s-white ::= s-space | s-tab\nfunction isWhitespace(c) {\n  return c === CHAR_SPACE || c === CHAR_TAB;\n}\n\n// Returns true if the character can be printed without escaping.\n// From YAML 1.2: \"any allowed characters known to be non-printable\n// should also be escaped. [However,] This isn’t mandatory\"\n// Derived from nb-char - \\t - #x85 - #xA0 - #x2028 - #x2029.\nfunction isPrintable(c) {\n  return  (0x00020 <= c && c <= 0x00007E)\n      || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029)\n      || ((0x0E000 <= c && c <= 0x00FFFD) && c !== 0xFEFF /* BOM */)\n      ||  (0x10000 <= c && c <= 0x10FFFF);\n}\n\n// Simplified test for values allowed after the first character in plain style.\nfunction isPlainSafe(c) {\n  // Uses a subset of nb-char - c-flow-indicator - \":\" - \"#\"\n  // where nb-char ::= c-printable - b-char - c-byte-order-mark.\n  return isPrintable(c) && c !== 0xFEFF\n    // - c-flow-indicator\n    && c !== CHAR_COMMA\n    && c !== CHAR_LEFT_SQUARE_BRACKET\n    && c !== CHAR_RIGHT_SQUARE_BRACKET\n    && c !== CHAR_LEFT_CURLY_BRACKET\n    && c !== CHAR_RIGHT_CURLY_BRACKET\n    // - \":\" - \"#\"\n    && c !== CHAR_COLON\n    && c !== CHAR_SHARP;\n}\n\n// Simplified test for values allowed as the first character in plain style.\nfunction isPlainSafeFirst(c) {\n  // Uses a subset of ns-char - c-indicator\n  // where ns-char = nb-char - s-white.\n  return isPrintable(c) && c !== 0xFEFF\n    && !isWhitespace(c) // - s-white\n    // - (c-indicator ::=\n    // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}”\n    && c !== CHAR_MINUS\n    && c !== CHAR_QUESTION\n    && c !== CHAR_COLON\n    && c !== CHAR_COMMA\n    && c !== CHAR_LEFT_SQUARE_BRACKET\n    && c !== CHAR_RIGHT_SQUARE_BRACKET\n    && c !== CHAR_LEFT_CURLY_BRACKET\n    && c !== CHAR_RIGHT_CURLY_BRACKET\n    // | “#” | “&” | “*” | “!” | “|” | “>” | “'” | “\"”\n    && c !== CHAR_SHARP\n    && c !== CHAR_AMPERSAND\n    && c !== CHAR_ASTERISK\n    && c !== CHAR_EXCLAMATION\n    && c !== CHAR_VERTICAL_LINE\n    && c !== CHAR_GREATER_THAN\n    && c !== CHAR_SINGLE_QUOTE\n    && c !== CHAR_DOUBLE_QUOTE\n    // | “%” | “@” | “`”)\n    && c !== CHAR_PERCENT\n    && c !== CHAR_COMMERCIAL_AT\n    && c !== CHAR_GRAVE_ACCENT;\n}\n\nvar STYLE_PLAIN   = 1,\n    STYLE_SINGLE  = 2,\n    STYLE_LITERAL = 3,\n    STYLE_FOLDED  = 4,\n    STYLE_DOUBLE  = 5;\n\n// Determines which scalar styles are possible and returns the preferred style.\n// lineWidth = -1 => no limit.\n// Pre-conditions: str.length > 0.\n// Post-conditions:\n//    STYLE_PLAIN or STYLE_SINGLE => no \\n are in the string.\n//    STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1).\n//    STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1).\nfunction chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType) {\n  var i;\n  var char;\n  var hasLineBreak = false;\n  var hasFoldableLine = false; // only checked if shouldTrackWidth\n  var shouldTrackWidth = lineWidth !== -1;\n  var previousLineBreak = -1; // count the first line correctly\n  var plain = isPlainSafeFirst(string.charCodeAt(0))\n          && !isWhitespace(string.charCodeAt(string.length - 1));\n\n  if (singleLineOnly) {\n    // Case: no block styles.\n    // Check for disallowed characters to rule out plain and single.\n    for (i = 0; i < string.length; i++) {\n      char = string.charCodeAt(i);\n      if (!isPrintable(char)) {\n        return STYLE_DOUBLE;\n      }\n      plain = plain && isPlainSafe(char);\n    }\n  } else {\n    // Case: block styles permitted.\n    for (i = 0; i < string.length; i++) {\n      char = string.charCodeAt(i);\n      if (char === CHAR_LINE_FEED) {\n        hasLineBreak = true;\n        // Check if any line can be folded.\n        if (shouldTrackWidth) {\n          hasFoldableLine = hasFoldableLine ||\n            // Foldable line = too long, and not more-indented.\n            (i - previousLineBreak - 1 > lineWidth &&\n             string[previousLineBreak + 1] !== ' ');\n          previousLineBreak = i;\n        }\n      } else if (!isPrintable(char)) {\n        return STYLE_DOUBLE;\n      }\n      plain = plain && isPlainSafe(char);\n    }\n    // in case the end is missing a \\n\n    hasFoldableLine = hasFoldableLine || (shouldTrackWidth &&\n      (i - previousLineBreak - 1 > lineWidth &&\n       string[previousLineBreak + 1] !== ' '));\n  }\n  // Although every style can represent \\n without escaping, prefer block styles\n  // for multiline, since they're more readable and they don't add empty lines.\n  // Also prefer folding a super-long line.\n  if (!hasLineBreak && !hasFoldableLine) {\n    // Strings interpretable as another type have to be quoted;\n    // e.g. the string 'true' vs. the boolean true.\n    return plain && !testAmbiguousType(string)\n      ? STYLE_PLAIN : STYLE_SINGLE;\n  }\n  // Edge case: block indentation indicator can only have one digit.\n  if (string[0] === ' ' && indentPerLevel > 9) {\n    return STYLE_DOUBLE;\n  }\n  // At this point we know block styles are valid.\n  // Prefer literal style unless we want to fold.\n  return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL;\n}\n\n// Note: line breaking/folding is implemented for only the folded style.\n// NB. We drop the last trailing newline (if any) of a returned block scalar\n//  since the dumper adds its own newline. This always works:\n//    • No ending newline => unaffected; already using strip \"-\" chomping.\n//    • Ending newline    => removed then restored.\n//  Importantly, this keeps the \"+\" chomp indicator from gaining an extra line.\nfunction writeScalar(state, string, level, iskey) {\n  state.dump = (function () {\n    if (string.length === 0) {\n      return \"''\";\n    }\n    if (!state.noCompatMode &&\n        DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1) {\n      return \"'\" + string + \"'\";\n    }\n\n    var indent = state.indent * Math.max(1, level); // no 0-indent scalars\n    // As indentation gets deeper, let the width decrease monotonically\n    // to the lower bound min(state.lineWidth, 40).\n    // Note that this implies\n    //  state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound.\n    //  state.lineWidth > 40 + state.indent: width decreases until the lower bound.\n    // This behaves better than a constant minimum width which disallows narrower options,\n    // or an indent threshold which causes the width to suddenly increase.\n    var lineWidth = state.lineWidth === -1\n      ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent);\n\n    // Without knowing if keys are implicit/explicit, assume implicit for safety.\n    var singleLineOnly = iskey\n      // No block styles in flow mode.\n      || (state.flowLevel > -1 && level >= state.flowLevel);\n    function testAmbiguity(string) {\n      return testImplicitResolving(state, string);\n    }\n\n    switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, testAmbiguity)) {\n      case STYLE_PLAIN:\n        return string;\n      case STYLE_SINGLE:\n        return \"'\" + string.replace(/'/g, \"''\") + \"'\";\n      case STYLE_LITERAL:\n        return '|' + blockHeader(string, state.indent)\n          + dropEndingNewline(indentString(string, indent));\n      case STYLE_FOLDED:\n        return '>' + blockHeader(string, state.indent)\n          + dropEndingNewline(indentString(foldString(string, lineWidth), indent));\n      case STYLE_DOUBLE:\n        return '\"' + escapeString(string, lineWidth) + '\"';\n      default:\n        throw new YAMLException('impossible error: invalid scalar style');\n    }\n  }());\n}\n\n// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9.\nfunction blockHeader(string, indentPerLevel) {\n  var indentIndicator = (string[0] === ' ') ? String(indentPerLevel) : '';\n\n  // note the special case: the string '\\n' counts as a \"trailing\" empty line.\n  var clip =          string[string.length - 1] === '\\n';\n  var keep = clip && (string[string.length - 2] === '\\n' || string === '\\n');\n  var chomp = keep ? '+' : (clip ? '' : '-');\n\n  return indentIndicator + chomp + '\\n';\n}\n\n// (See the note for writeScalar.)\nfunction dropEndingNewline(string) {\n  return string[string.length - 1] === '\\n' ? string.slice(0, -1) : string;\n}\n\n// Note: a long line without a suitable break point will exceed the width limit.\n// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0.\nfunction foldString(string, width) {\n  // In folded style, $k$ consecutive newlines output as $k+1$ newlines—\n  // unless they're before or after a more-indented line, or at the very\n  // beginning or end, in which case $k$ maps to $k$.\n  // Therefore, parse each chunk as newline(s) followed by a content line.\n  var lineRe = /(\\n+)([^\\n]*)/g;\n\n  // first line (possibly an empty line)\n  var result = (function () {\n    var nextLF = string.indexOf('\\n');\n    nextLF = nextLF !== -1 ? nextLF : string.length;\n    lineRe.lastIndex = nextLF;\n    return foldLine(string.slice(0, nextLF), width);\n  }());\n  // If we haven't reached the first content line yet, don't add an extra \\n.\n  var prevMoreIndented = string[0] === '\\n' || string[0] === ' ';\n  var moreIndented;\n\n  // rest of the lines\n  var match;\n  while ((match = lineRe.exec(string))) {\n    var prefix = match[1], line = match[2];\n    moreIndented = (line[0] === ' ');\n    result += prefix\n      + (!prevMoreIndented && !moreIndented && line !== ''\n        ? '\\n' : '')\n      + foldLine(line, width);\n    prevMoreIndented = moreIndented;\n  }\n\n  return result;\n}\n\n// Greedy line breaking.\n// Picks the longest line under the limit each time,\n// otherwise settles for the shortest line over the limit.\n// NB. More-indented lines *cannot* be folded, as that would add an extra \\n.\nfunction foldLine(line, width) {\n  if (line === '' || line[0] === ' ') return line;\n\n  // Since a more-indented line adds a \\n, breaks can't be followed by a space.\n  var breakRe = / [^ ]/g; // note: the match index will always be <= length-2.\n  var match;\n  // start is an inclusive index. end, curr, and next are exclusive.\n  var start = 0, end, curr = 0, next = 0;\n  var result = '';\n\n  // Invariants: 0 <= start <= length-1.\n  //   0 <= curr <= next <= max(0, length-2). curr - start <= width.\n  // Inside the loop:\n  //   A match implies length >= 2, so curr and next are <= length-2.\n  while ((match = breakRe.exec(line))) {\n    next = match.index;\n    // maintain invariant: curr - start <= width\n    if (next - start > width) {\n      end = (curr > start) ? curr : next; // derive end <= length-2\n      result += '\\n' + line.slice(start, end);\n      // skip the space that was output as \\n\n      start = end + 1;                    // derive start <= length-1\n    }\n    curr = next;\n  }\n\n  // By the invariants, start <= length-1, so there is something left over.\n  // It is either the whole string or a part starting from non-whitespace.\n  result += '\\n';\n  // Insert a break if the remainder is too long and there is a break available.\n  if (line.length - start > width && curr > start) {\n    result += line.slice(start, curr) + '\\n' + line.slice(curr + 1);\n  } else {\n    result += line.slice(start);\n  }\n\n  return result.slice(1); // drop extra \\n joiner\n}\n\n// Escapes a double-quoted string.\nfunction escapeString(string) {\n  var result = '';\n  var char;\n  var escapeSeq;\n\n  for (var i = 0; i < string.length; i++) {\n    char = string.charCodeAt(i);\n    escapeSeq = ESCAPE_SEQUENCES[char];\n    result += !escapeSeq && isPrintable(char)\n      ? string[i]\n      : escapeSeq || encodeHex(char);\n  }\n\n  return result;\n}\n\nfunction writeFlowSequence(state, level, object) {\n  var _result = '',\n      _tag    = state.tag,\n      index,\n      length;\n\n  for (index = 0, length = object.length; index < length; index += 1) {\n    // Write only valid elements.\n    if (writeNode(state, level, object[index], false, false)) {\n      if (index !== 0) _result += ', ';\n      _result += state.dump;\n    }\n  }\n\n  state.tag = _tag;\n  state.dump = '[' + _result + ']';\n}\n\nfunction writeBlockSequence(state, level, object, compact) {\n  var _result = '',\n      _tag    = state.tag,\n      index,\n      length;\n\n  for (index = 0, length = object.length; index < length; index += 1) {\n    // Write only valid elements.\n    if (writeNode(state, level + 1, object[index], true, true)) {\n      if (!compact || index !== 0) {\n        _result += generateNextLine(state, level);\n      }\n      _result += '- ' + state.dump;\n    }\n  }\n\n  state.tag = _tag;\n  state.dump = _result || '[]'; // Empty sequence if no valid values.\n}\n\nfunction writeFlowMapping(state, level, object) {\n  var _result       = '',\n      _tag          = state.tag,\n      objectKeyList = Object.keys(object),\n      index,\n      length,\n      objectKey,\n      objectValue,\n      pairBuffer;\n\n  for (index = 0, length = objectKeyList.length; index < length; index += 1) {\n    pairBuffer = '';\n\n    if (index !== 0) pairBuffer += ', ';\n\n    objectKey = objectKeyList[index];\n    objectValue = object[objectKey];\n\n    if (!writeNode(state, level, objectKey, false, false)) {\n      continue; // Skip this pair because of invalid key;\n    }\n\n    if (state.dump.length > 1024) pairBuffer += '? ';\n\n    pairBuffer += state.dump + ': ';\n\n    if (!writeNode(state, level, objectValue, false, false)) {\n      continue; // Skip this pair because of invalid value.\n    }\n\n    pairBuffer += state.dump;\n\n    // Both key and value are valid.\n    _result += pairBuffer;\n  }\n\n  state.tag = _tag;\n  state.dump = '{' + _result + '}';\n}\n\nfunction writeBlockMapping(state, level, object, compact) {\n  var _result       = '',\n      _tag          = state.tag,\n      objectKeyList = Object.keys(object),\n      index,\n      length,\n      objectKey,\n      objectValue,\n      explicitPair,\n      pairBuffer;\n\n  // Allow sorting keys so that the output file is deterministic\n  if (state.sortKeys === true) {\n    // Default sorting\n    objectKeyList.sort();\n  } else if (typeof state.sortKeys === 'function') {\n    // Custom sort function\n    objectKeyList.sort(state.sortKeys);\n  } else if (state.sortKeys) {\n    // Something is wrong\n    throw new YAMLException('sortKeys must be a boolean or a function');\n  }\n\n  for (index = 0, length = objectKeyList.length; index < length; index += 1) {\n    pairBuffer = '';\n\n    if (!compact || index !== 0) {\n      pairBuffer += generateNextLine(state, level);\n    }\n\n    objectKey = objectKeyList[index];\n    objectValue = object[objectKey];\n\n    if (!writeNode(state, level + 1, objectKey, true, true, true)) {\n      continue; // Skip this pair because of invalid key.\n    }\n\n    explicitPair = (state.tag !== null && state.tag !== '?') ||\n                   (state.dump && state.dump.length > 1024);\n\n    if (explicitPair) {\n      if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {\n        pairBuffer += '?';\n      } else {\n        pairBuffer += '? ';\n      }\n    }\n\n    pairBuffer += state.dump;\n\n    if (explicitPair) {\n      pairBuffer += generateNextLine(state, level);\n    }\n\n    if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {\n      continue; // Skip this pair because of invalid value.\n    }\n\n    if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {\n      pairBuffer += ':';\n    } else {\n      pairBuffer += ': ';\n    }\n\n    pairBuffer += state.dump;\n\n    // Both key and value are valid.\n    _result += pairBuffer;\n  }\n\n  state.tag = _tag;\n  state.dump = _result || '{}'; // Empty mapping if no valid pairs.\n}\n\nfunction detectType(state, object, explicit) {\n  var _result, typeList, index, length, type, style;\n\n  typeList = explicit ? state.explicitTypes : state.implicitTypes;\n\n  for (index = 0, length = typeList.length; index < length; index += 1) {\n    type = typeList[index];\n\n    if ((type.instanceOf  || type.predicate) &&\n        (!type.instanceOf || ((typeof object === 'object') && (object instanceof type.instanceOf))) &&\n        (!type.predicate  || type.predicate(object))) {\n\n      state.tag = explicit ? type.tag : '?';\n\n      if (type.represent) {\n        style = state.styleMap[type.tag] || type.defaultStyle;\n\n        if (_toString.call(type.represent) === '[object Function]') {\n          _result = type.represent(object, style);\n        } else if (_hasOwnProperty.call(type.represent, style)) {\n          _result = type.represent[style](object, style);\n        } else {\n          throw new YAMLException('!<' + type.tag + '> tag resolver accepts not \"' + style + '\" style');\n        }\n\n        state.dump = _result;\n      }\n\n      return true;\n    }\n  }\n\n  return false;\n}\n\n// Serializes `object` and writes it to global `result`.\n// Returns true on success, or false on invalid object.\n//\nfunction writeNode(state, level, object, block, compact, iskey) {\n  state.tag = null;\n  state.dump = object;\n\n  if (!detectType(state, object, false)) {\n    detectType(state, object, true);\n  }\n\n  var type = _toString.call(state.dump);\n\n  if (block) {\n    block = (state.flowLevel < 0 || state.flowLevel > level);\n  }\n\n  var objectOrArray = type === '[object Object]' || type === '[object Array]',\n      duplicateIndex,\n      duplicate;\n\n  if (objectOrArray) {\n    duplicateIndex = state.duplicates.indexOf(object);\n    duplicate = duplicateIndex !== -1;\n  }\n\n  if ((state.tag !== null && state.tag !== '?') || duplicate || (state.indent !== 2 && level > 0)) {\n    compact = false;\n  }\n\n  if (duplicate && state.usedDuplicates[duplicateIndex]) {\n    state.dump = '*ref_' + duplicateIndex;\n  } else {\n    if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {\n      state.usedDuplicates[duplicateIndex] = true;\n    }\n    if (type === '[object Object]') {\n      if (block && (Object.keys(state.dump).length !== 0)) {\n        writeBlockMapping(state, level, state.dump, compact);\n        if (duplicate) {\n          state.dump = '&ref_' + duplicateIndex + state.dump;\n        }\n      } else {\n        writeFlowMapping(state, level, state.dump);\n        if (duplicate) {\n          state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;\n        }\n      }\n    } else if (type === '[object Array]') {\n      if (block && (state.dump.length !== 0)) {\n        writeBlockSequence(state, level, state.dump, compact);\n        if (duplicate) {\n          state.dump = '&ref_' + duplicateIndex + state.dump;\n        }\n      } else {\n        writeFlowSequence(state, level, state.dump);\n        if (duplicate) {\n          state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;\n        }\n      }\n    } else if (type === '[object String]') {\n      if (state.tag !== '?') {\n        writeScalar(state, state.dump, level, iskey);\n      }\n    } else {\n      if (state.skipInvalid) return false;\n      throw new YAMLException('unacceptable kind of an object to dump ' + type);\n    }\n\n    if (state.tag !== null && state.tag !== '?') {\n      state.dump = '!<' + state.tag + '> ' + state.dump;\n    }\n  }\n\n  return true;\n}\n\nfunction getDuplicateReferences(object, state) {\n  var objects = [],\n      duplicatesIndexes = [],\n      index,\n      length;\n\n  inspectNode(object, objects, duplicatesIndexes);\n\n  for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) {\n    state.duplicates.push(objects[duplicatesIndexes[index]]);\n  }\n  state.usedDuplicates = new Array(length);\n}\n\nfunction inspectNode(object, objects, duplicatesIndexes) {\n  var objectKeyList,\n      index,\n      length;\n\n  if (object !== null && typeof object === 'object') {\n    index = objects.indexOf(object);\n    if (index !== -1) {\n      if (duplicatesIndexes.indexOf(index) === -1) {\n        duplicatesIndexes.push(index);\n      }\n    } else {\n      objects.push(object);\n\n      if (Array.isArray(object)) {\n        for (index = 0, length = object.length; index < length; index += 1) {\n          inspectNode(object[index], objects, duplicatesIndexes);\n        }\n      } else {\n        objectKeyList = Object.keys(object);\n\n        for (index = 0, length = objectKeyList.length; index < length; index += 1) {\n          inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);\n        }\n      }\n    }\n  }\n}\n\nfunction dump(input, options) {\n  options = options || {};\n\n  var state = new State(options);\n\n  if (!state.noRefs) getDuplicateReferences(input, state);\n\n  if (writeNode(state, 0, input, true, true)) return state.dump + '\\n';\n\n  return '';\n}\n\nfunction safeDump(input, options) {\n  return dump(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));\n}\n\nmodule.exports.dump     = dump;\nmodule.exports.safeDump = safeDump;\n\n},{\"./common\":21,\"./exception\":23,\"./schema/default_full\":28,\"./schema/default_safe\":29}],23:[function(require,module,exports){\n// YAML error class. http://stackoverflow.com/questions/8458984\n//\n'use strict';\n\nfunction YAMLException(reason, mark) {\n  // Super constructor\n  Error.call(this);\n\n  // Include stack trace in error object\n  if (Error.captureStackTrace) {\n    // Chrome and NodeJS\n    Error.captureStackTrace(this, this.constructor);\n  } else {\n    // FF, IE 10+ and Safari 6+. Fallback for others\n    this.stack = (new Error()).stack || '';\n  }\n\n  this.name = 'YAMLException';\n  this.reason = reason;\n  this.mark = mark;\n  this.message = (this.reason || '(unknown reason)') + (this.mark ? ' ' + this.mark.toString() : '');\n}\n\n\n// Inherit from Error\nYAMLException.prototype = Object.create(Error.prototype);\nYAMLException.prototype.constructor = YAMLException;\n\n\nYAMLException.prototype.toString = function toString(compact) {\n  var result = this.name + ': ';\n\n  result += this.reason || '(unknown reason)';\n\n  if (!compact && this.mark) {\n    result += ' ' + this.mark.toString();\n  }\n\n  return result;\n};\n\n\nmodule.exports = YAMLException;\n\n},{}],24:[function(require,module,exports){\n'use strict';\n\n/*eslint-disable max-len,no-use-before-define*/\n\nvar common              = require('./common');\nvar YAMLException       = require('./exception');\nvar Mark                = require('./mark');\nvar DEFAULT_SAFE_SCHEMA = require('./schema/default_safe');\nvar DEFAULT_FULL_SCHEMA = require('./schema/default_full');\n\n\nvar _hasOwnProperty = Object.prototype.hasOwnProperty;\n\n\nvar CONTEXT_FLOW_IN   = 1;\nvar CONTEXT_FLOW_OUT  = 2;\nvar CONTEXT_BLOCK_IN  = 3;\nvar CONTEXT_BLOCK_OUT = 4;\n\n\nvar CHOMPING_CLIP  = 1;\nvar CHOMPING_STRIP = 2;\nvar CHOMPING_KEEP  = 3;\n\n\nvar PATTERN_NON_PRINTABLE         = /[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F-\\x84\\x86-\\x9F\\uFFFE\\uFFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]/;\nvar PATTERN_NON_ASCII_LINE_BREAKS = /[\\x85\\u2028\\u2029]/;\nvar PATTERN_FLOW_INDICATORS       = /[,\\[\\]\\{\\}]/;\nvar PATTERN_TAG_HANDLE            = /^(?:!|!!|![a-z\\-]+!)$/i;\nvar PATTERN_TAG_URI               = /^(?:!|[^,\\[\\]\\{\\}])(?:%[0-9a-f]{2}|[0-9a-z\\-#;\\/\\?:@&=\\+\\$,_\\.!~\\*'\\(\\)\\[\\]])*$/i;\n\n\nfunction is_EOL(c) {\n  return (c === 0x0A/* LF */) || (c === 0x0D/* CR */);\n}\n\nfunction is_WHITE_SPACE(c) {\n  return (c === 0x09/* Tab */) || (c === 0x20/* Space */);\n}\n\nfunction is_WS_OR_EOL(c) {\n  return (c === 0x09/* Tab */) ||\n         (c === 0x20/* Space */) ||\n         (c === 0x0A/* LF */) ||\n         (c === 0x0D/* CR */);\n}\n\nfunction is_FLOW_INDICATOR(c) {\n  return c === 0x2C/* , */ ||\n         c === 0x5B/* [ */ ||\n         c === 0x5D/* ] */ ||\n         c === 0x7B/* { */ ||\n         c === 0x7D/* } */;\n}\n\nfunction fromHexCode(c) {\n  var lc;\n\n  if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) {\n    return c - 0x30;\n  }\n\n  /*eslint-disable no-bitwise*/\n  lc = c | 0x20;\n\n  if ((0x61/* a */ <= lc) && (lc <= 0x66/* f */)) {\n    return lc - 0x61 + 10;\n  }\n\n  return -1;\n}\n\nfunction escapedHexLen(c) {\n  if (c === 0x78/* x */) { return 2; }\n  if (c === 0x75/* u */) { return 4; }\n  if (c === 0x55/* U */) { return 8; }\n  return 0;\n}\n\nfunction fromDecimalCode(c) {\n  if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) {\n    return c - 0x30;\n  }\n\n  return -1;\n}\n\nfunction simpleEscapeSequence(c) {\n  return (c === 0x30/* 0 */) ? '\\x00' :\n        (c === 0x61/* a */) ? '\\x07' :\n        (c === 0x62/* b */) ? '\\x08' :\n        (c === 0x74/* t */) ? '\\x09' :\n        (c === 0x09/* Tab */) ? '\\x09' :\n        (c === 0x6E/* n */) ? '\\x0A' :\n        (c === 0x76/* v */) ? '\\x0B' :\n        (c === 0x66/* f */) ? '\\x0C' :\n        (c === 0x72/* r */) ? '\\x0D' :\n        (c === 0x65/* e */) ? '\\x1B' :\n        (c === 0x20/* Space */) ? ' ' :\n        (c === 0x22/* \" */) ? '\\x22' :\n        (c === 0x2F/* / */) ? '/' :\n        (c === 0x5C/* \\ */) ? '\\x5C' :\n        (c === 0x4E/* N */) ? '\\x85' :\n        (c === 0x5F/* _ */) ? '\\xA0' :\n        (c === 0x4C/* L */) ? '\\u2028' :\n        (c === 0x50/* P */) ? '\\u2029' : '';\n}\n\nfunction charFromCodepoint(c) {\n  if (c <= 0xFFFF) {\n    return String.fromCharCode(c);\n  }\n  // Encode UTF-16 surrogate pair\n  // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF\n  return String.fromCharCode(((c - 0x010000) >> 10) + 0xD800,\n                             ((c - 0x010000) & 0x03FF) + 0xDC00);\n}\n\nvar simpleEscapeCheck = new Array(256); // integer, for fast access\nvar simpleEscapeMap = new Array(256);\nfor (var i = 0; i < 256; i++) {\n  simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0;\n  simpleEscapeMap[i] = simpleEscapeSequence(i);\n}\n\n\nfunction State(input, options) {\n  this.input = input;\n\n  this.filename  = options['filename']  || null;\n  this.schema    = options['schema']    || DEFAULT_FULL_SCHEMA;\n  this.onWarning = options['onWarning'] || null;\n  this.legacy    = options['legacy']    || false;\n  this.json      = options['json']      || false;\n  this.listener  = options['listener']  || null;\n\n  this.implicitTypes = this.schema.compiledImplicit;\n  this.typeMap       = this.schema.compiledTypeMap;\n\n  this.length     = input.length;\n  this.position   = 0;\n  this.line       = 0;\n  this.lineStart  = 0;\n  this.lineIndent = 0;\n\n  this.documents = [];\n\n  /*\n  this.version;\n  this.checkLineBreaks;\n  this.tagMap;\n  this.anchorMap;\n  this.tag;\n  this.anchor;\n  this.kind;\n  this.result;*/\n\n}\n\n\nfunction generateError(state, message) {\n  return new YAMLException(\n    message,\n    new Mark(state.filename, state.input, state.position, state.line, (state.position - state.lineStart)));\n}\n\nfunction throwError(state, message) {\n  throw generateError(state, message);\n}\n\nfunction throwWarning(state, message) {\n  if (state.onWarning) {\n    state.onWarning.call(null, generateError(state, message));\n  }\n}\n\n\nvar directiveHandlers = {\n\n  YAML: function handleYamlDirective(state, name, args) {\n\n    var match, major, minor;\n\n    if (state.version !== null) {\n      throwError(state, 'duplication of %YAML directive');\n    }\n\n    if (args.length !== 1) {\n      throwError(state, 'YAML directive accepts exactly one argument');\n    }\n\n    match = /^([0-9]+)\\.([0-9]+)$/.exec(args[0]);\n\n    if (match === null) {\n      throwError(state, 'ill-formed argument of the YAML directive');\n    }\n\n    major = parseInt(match[1], 10);\n    minor = parseInt(match[2], 10);\n\n    if (major !== 1) {\n      throwError(state, 'unacceptable YAML version of the document');\n    }\n\n    state.version = args[0];\n    state.checkLineBreaks = (minor < 2);\n\n    if (minor !== 1 && minor !== 2) {\n      throwWarning(state, 'unsupported YAML version of the document');\n    }\n  },\n\n  TAG: function handleTagDirective(state, name, args) {\n\n    var handle, prefix;\n\n    if (args.length !== 2) {\n      throwError(state, 'TAG directive accepts exactly two arguments');\n    }\n\n    handle = args[0];\n    prefix = args[1];\n\n    if (!PATTERN_TAG_HANDLE.test(handle)) {\n      throwError(state, 'ill-formed tag handle (first argument) of the TAG directive');\n    }\n\n    if (_hasOwnProperty.call(state.tagMap, handle)) {\n      throwError(state, 'there is a previously declared suffix for \"' + handle + '\" tag handle');\n    }\n\n    if (!PATTERN_TAG_URI.test(prefix)) {\n      throwError(state, 'ill-formed tag prefix (second argument) of the TAG directive');\n    }\n\n    state.tagMap[handle] = prefix;\n  }\n};\n\n\nfunction captureSegment(state, start, end, checkJson) {\n  var _position, _length, _character, _result;\n\n  if (start < end) {\n    _result = state.input.slice(start, end);\n\n    if (checkJson) {\n      for (_position = 0, _length = _result.length;\n           _position < _length;\n           _position += 1) {\n        _character = _result.charCodeAt(_position);\n        if (!(_character === 0x09 ||\n              (0x20 <= _character && _character <= 0x10FFFF))) {\n          throwError(state, 'expected valid JSON character');\n        }\n      }\n    } else if (PATTERN_NON_PRINTABLE.test(_result)) {\n      throwError(state, 'the stream contains non-printable characters');\n    }\n\n    state.result += _result;\n  }\n}\n\nfunction mergeMappings(state, destination, source, overridableKeys) {\n  var sourceKeys, key, index, quantity;\n\n  if (!common.isObject(source)) {\n    throwError(state, 'cannot merge mappings; the provided source object is unacceptable');\n  }\n\n  sourceKeys = Object.keys(source);\n\n  for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {\n    key = sourceKeys[index];\n\n    if (!_hasOwnProperty.call(destination, key)) {\n      destination[key] = source[key];\n      overridableKeys[key] = true;\n    }\n  }\n}\n\nfunction storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode) {\n  var index, quantity;\n\n  keyNode = String(keyNode);\n\n  if (_result === null) {\n    _result = {};\n  }\n\n  if (keyTag === 'tag:yaml.org,2002:merge') {\n    if (Array.isArray(valueNode)) {\n      for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {\n        mergeMappings(state, _result, valueNode[index], overridableKeys);\n      }\n    } else {\n      mergeMappings(state, _result, valueNode, overridableKeys);\n    }\n  } else {\n    if (!state.json &&\n        !_hasOwnProperty.call(overridableKeys, keyNode) &&\n        _hasOwnProperty.call(_result, keyNode)) {\n      throwError(state, 'duplicated mapping key');\n    }\n    _result[keyNode] = valueNode;\n    delete overridableKeys[keyNode];\n  }\n\n  return _result;\n}\n\nfunction readLineBreak(state) {\n  var ch;\n\n  ch = state.input.charCodeAt(state.position);\n\n  if (ch === 0x0A/* LF */) {\n    state.position++;\n  } else if (ch === 0x0D/* CR */) {\n    state.position++;\n    if (state.input.charCodeAt(state.position) === 0x0A/* LF */) {\n      state.position++;\n    }\n  } else {\n    throwError(state, 'a line break is expected');\n  }\n\n  state.line += 1;\n  state.lineStart = state.position;\n}\n\nfunction skipSeparationSpace(state, allowComments, checkIndent) {\n  var lineBreaks = 0,\n      ch = state.input.charCodeAt(state.position);\n\n  while (ch !== 0) {\n    while (is_WHITE_SPACE(ch)) {\n      ch = state.input.charCodeAt(++state.position);\n    }\n\n    if (allowComments && ch === 0x23/* # */) {\n      do {\n        ch = state.input.charCodeAt(++state.position);\n      } while (ch !== 0x0A/* LF */ && ch !== 0x0D/* CR */ && ch !== 0);\n    }\n\n    if (is_EOL(ch)) {\n      readLineBreak(state);\n\n      ch = state.input.charCodeAt(state.position);\n      lineBreaks++;\n      state.lineIndent = 0;\n\n      while (ch === 0x20/* Space */) {\n        state.lineIndent++;\n        ch = state.input.charCodeAt(++state.position);\n      }\n    } else {\n      break;\n    }\n  }\n\n  if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) {\n    throwWarning(state, 'deficient indentation');\n  }\n\n  return lineBreaks;\n}\n\nfunction testDocumentSeparator(state) {\n  var _position = state.position,\n      ch;\n\n  ch = state.input.charCodeAt(_position);\n\n  // Condition state.position === state.lineStart is tested\n  // in parent on each call, for efficiency. No needs to test here again.\n  if ((ch === 0x2D/* - */ || ch === 0x2E/* . */) &&\n      ch === state.input.charCodeAt(_position + 1) &&\n      ch === state.input.charCodeAt(_position + 2)) {\n\n    _position += 3;\n\n    ch = state.input.charCodeAt(_position);\n\n    if (ch === 0 || is_WS_OR_EOL(ch)) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nfunction writeFoldedLines(state, count) {\n  if (count === 1) {\n    state.result += ' ';\n  } else if (count > 1) {\n    state.result += common.repeat('\\n', count - 1);\n  }\n}\n\n\nfunction readPlainScalar(state, nodeIndent, withinFlowCollection) {\n  var preceding,\n      following,\n      captureStart,\n      captureEnd,\n      hasPendingContent,\n      _line,\n      _lineStart,\n      _lineIndent,\n      _kind = state.kind,\n      _result = state.result,\n      ch;\n\n  ch = state.input.charCodeAt(state.position);\n\n  if (is_WS_OR_EOL(ch)      ||\n      is_FLOW_INDICATOR(ch) ||\n      ch === 0x23/* # */    ||\n      ch === 0x26/* & */    ||\n      ch === 0x2A/* * */    ||\n      ch === 0x21/* ! */    ||\n      ch === 0x7C/* | */    ||\n      ch === 0x3E/* > */    ||\n      ch === 0x27/* ' */    ||\n      ch === 0x22/* \" */    ||\n      ch === 0x25/* % */    ||\n      ch === 0x40/* @ */    ||\n      ch === 0x60/* ` */) {\n    return false;\n  }\n\n  if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) {\n    following = state.input.charCodeAt(state.position + 1);\n\n    if (is_WS_OR_EOL(following) ||\n        withinFlowCollection && is_FLOW_INDICATOR(following)) {\n      return false;\n    }\n  }\n\n  state.kind = 'scalar';\n  state.result = '';\n  captureStart = captureEnd = state.position;\n  hasPendingContent = false;\n\n  while (ch !== 0) {\n    if (ch === 0x3A/* : */) {\n      following = state.input.charCodeAt(state.position + 1);\n\n      if (is_WS_OR_EOL(following) ||\n          withinFlowCollection && is_FLOW_INDICATOR(following)) {\n        break;\n      }\n\n    } else if (ch === 0x23/* # */) {\n      preceding = state.input.charCodeAt(state.position - 1);\n\n      if (is_WS_OR_EOL(preceding)) {\n        break;\n      }\n\n    } else if ((state.position === state.lineStart && testDocumentSeparator(state)) ||\n               withinFlowCollection && is_FLOW_INDICATOR(ch)) {\n      break;\n\n    } else if (is_EOL(ch)) {\n      _line = state.line;\n      _lineStart = state.lineStart;\n      _lineIndent = state.lineIndent;\n      skipSeparationSpace(state, false, -1);\n\n      if (state.lineIndent >= nodeIndent) {\n        hasPendingContent = true;\n        ch = state.input.charCodeAt(state.position);\n        continue;\n      } else {\n        state.position = captureEnd;\n        state.line = _line;\n        state.lineStart = _lineStart;\n        state.lineIndent = _lineIndent;\n        break;\n      }\n    }\n\n    if (hasPendingContent) {\n      captureSegment(state, captureStart, captureEnd, false);\n      writeFoldedLines(state, state.line - _line);\n      captureStart = captureEnd = state.position;\n      hasPendingContent = false;\n    }\n\n    if (!is_WHITE_SPACE(ch)) {\n      captureEnd = state.position + 1;\n    }\n\n    ch = state.input.charCodeAt(++state.position);\n  }\n\n  captureSegment(state, captureStart, captureEnd, false);\n\n  if (state.result) {\n    return true;\n  }\n\n  state.kind = _kind;\n  state.result = _result;\n  return false;\n}\n\nfunction readSingleQuotedScalar(state, nodeIndent) {\n  var ch,\n      captureStart, captureEnd;\n\n  ch = state.input.charCodeAt(state.position);\n\n  if (ch !== 0x27/* ' */) {\n    return false;\n  }\n\n  state.kind = 'scalar';\n  state.result = '';\n  state.position++;\n  captureStart = captureEnd = state.position;\n\n  while ((ch = state.input.charCodeAt(state.position)) !== 0) {\n    if (ch === 0x27/* ' */) {\n      captureSegment(state, captureStart, state.position, true);\n      ch = state.input.charCodeAt(++state.position);\n\n      if (ch === 0x27/* ' */) {\n        captureStart = captureEnd = state.position;\n        state.position++;\n      } else {\n        return true;\n      }\n\n    } else if (is_EOL(ch)) {\n      captureSegment(state, captureStart, captureEnd, true);\n      writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));\n      captureStart = captureEnd = state.position;\n\n    } else if (state.position === state.lineStart && testDocumentSeparator(state)) {\n      throwError(state, 'unexpected end of the document within a single quoted scalar');\n\n    } else {\n      state.position++;\n      captureEnd = state.position;\n    }\n  }\n\n  throwError(state, 'unexpected end of the stream within a single quoted scalar');\n}\n\nfunction readDoubleQuotedScalar(state, nodeIndent) {\n  var captureStart,\n      captureEnd,\n      hexLength,\n      hexResult,\n      tmp,\n      ch;\n\n  ch = state.input.charCodeAt(state.position);\n\n  if (ch !== 0x22/* \" */) {\n    return false;\n  }\n\n  state.kind = 'scalar';\n  state.result = '';\n  state.position++;\n  captureStart = captureEnd = state.position;\n\n  while ((ch = state.input.charCodeAt(state.position)) !== 0) {\n    if (ch === 0x22/* \" */) {\n      captureSegment(state, captureStart, state.position, true);\n      state.position++;\n      return true;\n\n    } else if (ch === 0x5C/* \\ */) {\n      captureSegment(state, captureStart, state.position, true);\n      ch = state.input.charCodeAt(++state.position);\n\n      if (is_EOL(ch)) {\n        skipSeparationSpace(state, false, nodeIndent);\n\n        // TODO: rework to inline fn with no type cast?\n      } else if (ch < 256 && simpleEscapeCheck[ch]) {\n        state.result += simpleEscapeMap[ch];\n        state.position++;\n\n      } else if ((tmp = escapedHexLen(ch)) > 0) {\n        hexLength = tmp;\n        hexResult = 0;\n\n        for (; hexLength > 0; hexLength--) {\n          ch = state.input.charCodeAt(++state.position);\n\n          if ((tmp = fromHexCode(ch)) >= 0) {\n            hexResult = (hexResult << 4) + tmp;\n\n          } else {\n            throwError(state, 'expected hexadecimal character');\n          }\n        }\n\n        state.result += charFromCodepoint(hexResult);\n\n        state.position++;\n\n      } else {\n        throwError(state, 'unknown escape sequence');\n      }\n\n      captureStart = captureEnd = state.position;\n\n    } else if (is_EOL(ch)) {\n      captureSegment(state, captureStart, captureEnd, true);\n      writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));\n      captureStart = captureEnd = state.position;\n\n    } else if (state.position === state.lineStart && testDocumentSeparator(state)) {\n      throwError(state, 'unexpected end of the document within a double quoted scalar');\n\n    } else {\n      state.position++;\n      captureEnd = state.position;\n    }\n  }\n\n  throwError(state, 'unexpected end of the stream within a double quoted scalar');\n}\n\nfunction readFlowCollection(state, nodeIndent) {\n  var readNext = true,\n      _line,\n      _tag     = state.tag,\n      _result,\n      _anchor  = state.anchor,\n      following,\n      terminator,\n      isPair,\n      isExplicitPair,\n      isMapping,\n      overridableKeys = {},\n      keyNode,\n      keyTag,\n      valueNode,\n      ch;\n\n  ch = state.input.charCodeAt(state.position);\n\n  if (ch === 0x5B/* [ */) {\n    terminator = 0x5D;/* ] */\n    isMapping = false;\n    _result = [];\n  } else if (ch === 0x7B/* { */) {\n    terminator = 0x7D;/* } */\n    isMapping = true;\n    _result = {};\n  } else {\n    return false;\n  }\n\n  if (state.anchor !== null) {\n    state.anchorMap[state.anchor] = _result;\n  }\n\n  ch = state.input.charCodeAt(++state.position);\n\n  while (ch !== 0) {\n    skipSeparationSpace(state, true, nodeIndent);\n\n    ch = state.input.charCodeAt(state.position);\n\n    if (ch === terminator) {\n      state.position++;\n      state.tag = _tag;\n      state.anchor = _anchor;\n      state.kind = isMapping ? 'mapping' : 'sequence';\n      state.result = _result;\n      return true;\n    } else if (!readNext) {\n      throwError(state, 'missed comma between flow collection entries');\n    }\n\n    keyTag = keyNode = valueNode = null;\n    isPair = isExplicitPair = false;\n\n    if (ch === 0x3F/* ? */) {\n      following = state.input.charCodeAt(state.position + 1);\n\n      if (is_WS_OR_EOL(following)) {\n        isPair = isExplicitPair = true;\n        state.position++;\n        skipSeparationSpace(state, true, nodeIndent);\n      }\n    }\n\n    _line = state.line;\n    composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);\n    keyTag = state.tag;\n    keyNode = state.result;\n    skipSeparationSpace(state, true, nodeIndent);\n\n    ch = state.input.charCodeAt(state.position);\n\n    if ((isExplicitPair || state.line === _line) && ch === 0x3A/* : */) {\n      isPair = true;\n      ch = state.input.charCodeAt(++state.position);\n      skipSeparationSpace(state, true, nodeIndent);\n      composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);\n      valueNode = state.result;\n    }\n\n    if (isMapping) {\n      storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode);\n    } else if (isPair) {\n      _result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode));\n    } else {\n      _result.push(keyNode);\n    }\n\n    skipSeparationSpace(state, true, nodeIndent);\n\n    ch = state.input.charCodeAt(state.position);\n\n    if (ch === 0x2C/* , */) {\n      readNext = true;\n      ch = state.input.charCodeAt(++state.position);\n    } else {\n      readNext = false;\n    }\n  }\n\n  throwError(state, 'unexpected end of the stream within a flow collection');\n}\n\nfunction readBlockScalar(state, nodeIndent) {\n  var captureStart,\n      folding,\n      chomping       = CHOMPING_CLIP,\n      didReadContent = false,\n      detectedIndent = false,\n      textIndent     = nodeIndent,\n      emptyLines     = 0,\n      atMoreIndented = false,\n      tmp,\n      ch;\n\n  ch = state.input.charCodeAt(state.position);\n\n  if (ch === 0x7C/* | */) {\n    folding = false;\n  } else if (ch === 0x3E/* > */) {\n    folding = true;\n  } else {\n    return false;\n  }\n\n  state.kind = 'scalar';\n  state.result = '';\n\n  while (ch !== 0) {\n    ch = state.input.charCodeAt(++state.position);\n\n    if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {\n      if (CHOMPING_CLIP === chomping) {\n        chomping = (ch === 0x2B/* + */) ? CHOMPING_KEEP : CHOMPING_STRIP;\n      } else {\n        throwError(state, 'repeat of a chomping mode identifier');\n      }\n\n    } else if ((tmp = fromDecimalCode(ch)) >= 0) {\n      if (tmp === 0) {\n        throwError(state, 'bad explicit indentation width of a block scalar; it cannot be less than one');\n      } else if (!detectedIndent) {\n        textIndent = nodeIndent + tmp - 1;\n        detectedIndent = true;\n      } else {\n        throwError(state, 'repeat of an indentation width identifier');\n      }\n\n    } else {\n      break;\n    }\n  }\n\n  if (is_WHITE_SPACE(ch)) {\n    do { ch = state.input.charCodeAt(++state.position); }\n    while (is_WHITE_SPACE(ch));\n\n    if (ch === 0x23/* # */) {\n      do { ch = state.input.charCodeAt(++state.position); }\n      while (!is_EOL(ch) && (ch !== 0));\n    }\n  }\n\n  while (ch !== 0) {\n    readLineBreak(state);\n    state.lineIndent = 0;\n\n    ch = state.input.charCodeAt(state.position);\n\n    while ((!detectedIndent || state.lineIndent < textIndent) &&\n           (ch === 0x20/* Space */)) {\n      state.lineIndent++;\n      ch = state.input.charCodeAt(++state.position);\n    }\n\n    if (!detectedIndent && state.lineIndent > textIndent) {\n      textIndent = state.lineIndent;\n    }\n\n    if (is_EOL(ch)) {\n      emptyLines++;\n      continue;\n    }\n\n    // End of the scalar.\n    if (state.lineIndent < textIndent) {\n\n      // Perform the chomping.\n      if (chomping === CHOMPING_KEEP) {\n        state.result += common.repeat('\\n', didReadContent ? 1 + emptyLines : emptyLines);\n      } else if (chomping === CHOMPING_CLIP) {\n        if (didReadContent) { // i.e. only if the scalar is not empty.\n          state.result += '\\n';\n        }\n      }\n\n      // Break this `while` cycle and go to the funciton's epilogue.\n      break;\n    }\n\n    // Folded style: use fancy rules to handle line breaks.\n    if (folding) {\n\n      // Lines starting with white space characters (more-indented lines) are not folded.\n      if (is_WHITE_SPACE(ch)) {\n        atMoreIndented = true;\n        // except for the first content line (cf. Example 8.1)\n        state.result += common.repeat('\\n', didReadContent ? 1 + emptyLines : emptyLines);\n\n      // End of more-indented block.\n      } else if (atMoreIndented) {\n        atMoreIndented = false;\n        state.result += common.repeat('\\n', emptyLines + 1);\n\n      // Just one line break - perceive as the same line.\n      } else if (emptyLines === 0) {\n        if (didReadContent) { // i.e. only if we have already read some scalar content.\n          state.result += ' ';\n        }\n\n      // Several line breaks - perceive as different lines.\n      } else {\n        state.result += common.repeat('\\n', emptyLines);\n      }\n\n    // Literal style: just add exact number of line breaks between content lines.\n    } else {\n      // Keep all line breaks except the header line break.\n      state.result += common.repeat('\\n', didReadContent ? 1 + emptyLines : emptyLines);\n    }\n\n    didReadContent = true;\n    detectedIndent = true;\n    emptyLines = 0;\n    captureStart = state.position;\n\n    while (!is_EOL(ch) && (ch !== 0)) {\n      ch = state.input.charCodeAt(++state.position);\n    }\n\n    captureSegment(state, captureStart, state.position, false);\n  }\n\n  return true;\n}\n\nfunction readBlockSequence(state, nodeIndent) {\n  var _line,\n      _tag      = state.tag,\n      _anchor   = state.anchor,\n      _result   = [],\n      following,\n      detected  = false,\n      ch;\n\n  if (state.anchor !== null) {\n    state.anchorMap[state.anchor] = _result;\n  }\n\n  ch = state.input.charCodeAt(state.position);\n\n  while (ch !== 0) {\n\n    if (ch !== 0x2D/* - */) {\n      break;\n    }\n\n    following = state.input.charCodeAt(state.position + 1);\n\n    if (!is_WS_OR_EOL(following)) {\n      break;\n    }\n\n    detected = true;\n    state.position++;\n\n    if (skipSeparationSpace(state, true, -1)) {\n      if (state.lineIndent <= nodeIndent) {\n        _result.push(null);\n        ch = state.input.charCodeAt(state.position);\n        continue;\n      }\n    }\n\n    _line = state.line;\n    composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true);\n    _result.push(state.result);\n    skipSeparationSpace(state, true, -1);\n\n    ch = state.input.charCodeAt(state.position);\n\n    if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) {\n      throwError(state, 'bad indentation of a sequence entry');\n    } else if (state.lineIndent < nodeIndent) {\n      break;\n    }\n  }\n\n  if (detected) {\n    state.tag = _tag;\n    state.anchor = _anchor;\n    state.kind = 'sequence';\n    state.result = _result;\n    return true;\n  }\n  return false;\n}\n\nfunction readBlockMapping(state, nodeIndent, flowIndent) {\n  var following,\n      allowCompact,\n      _line,\n      _tag          = state.tag,\n      _anchor       = state.anchor,\n      _result       = {},\n      overridableKeys = {},\n      keyTag        = null,\n      keyNode       = null,\n      valueNode     = null,\n      atExplicitKey = false,\n      detected      = false,\n      ch;\n\n  if (state.anchor !== null) {\n    state.anchorMap[state.anchor] = _result;\n  }\n\n  ch = state.input.charCodeAt(state.position);\n\n  while (ch !== 0) {\n    following = state.input.charCodeAt(state.position + 1);\n    _line = state.line; // Save the current line.\n\n    //\n    // Explicit notation case. There are two separate blocks:\n    // first for the key (denoted by \"?\") and second for the value (denoted by \":\")\n    //\n    if ((ch === 0x3F/* ? */ || ch === 0x3A/* : */) && is_WS_OR_EOL(following)) {\n\n      if (ch === 0x3F/* ? */) {\n        if (atExplicitKey) {\n          storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null);\n          keyTag = keyNode = valueNode = null;\n        }\n\n        detected = true;\n        atExplicitKey = true;\n        allowCompact = true;\n\n      } else if (atExplicitKey) {\n        // i.e. 0x3A/* : */ === character after the explicit key.\n        atExplicitKey = false;\n        allowCompact = true;\n\n      } else {\n        throwError(state, 'incomplete explicit mapping pair; a key node is missed');\n      }\n\n      state.position += 1;\n      ch = following;\n\n    //\n    // Implicit notation case. Flow-style node as the key first, then \":\", and the value.\n    //\n    } else if (composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) {\n\n      if (state.line === _line) {\n        ch = state.input.charCodeAt(state.position);\n\n        while (is_WHITE_SPACE(ch)) {\n          ch = state.input.charCodeAt(++state.position);\n        }\n\n        if (ch === 0x3A/* : */) {\n          ch = state.input.charCodeAt(++state.position);\n\n          if (!is_WS_OR_EOL(ch)) {\n            throwError(state, 'a whitespace character is expected after the key-value separator within a block mapping');\n          }\n\n          if (atExplicitKey) {\n            storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null);\n            keyTag = keyNode = valueNode = null;\n          }\n\n          detected = true;\n          atExplicitKey = false;\n          allowCompact = false;\n          keyTag = state.tag;\n          keyNode = state.result;\n\n        } else if (detected) {\n          throwError(state, 'can not read an implicit mapping pair; a colon is missed');\n\n        } else {\n          state.tag = _tag;\n          state.anchor = _anchor;\n          return true; // Keep the result of `composeNode`.\n        }\n\n      } else if (detected) {\n        throwError(state, 'can not read a block mapping entry; a multiline key may not be an implicit key');\n\n      } else {\n        state.tag = _tag;\n        state.anchor = _anchor;\n        return true; // Keep the result of `composeNode`.\n      }\n\n    } else {\n      break; // Reading is done. Go to the epilogue.\n    }\n\n    //\n    // Common reading code for both explicit and implicit notations.\n    //\n    if (state.line === _line || state.lineIndent > nodeIndent) {\n      if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) {\n        if (atExplicitKey) {\n          keyNode = state.result;\n        } else {\n          valueNode = state.result;\n        }\n      }\n\n      if (!atExplicitKey) {\n        storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode);\n        keyTag = keyNode = valueNode = null;\n      }\n\n      skipSeparationSpace(state, true, -1);\n      ch = state.input.charCodeAt(state.position);\n    }\n\n    if (state.lineIndent > nodeIndent && (ch !== 0)) {\n      throwError(state, 'bad indentation of a mapping entry');\n    } else if (state.lineIndent < nodeIndent) {\n      break;\n    }\n  }\n\n  //\n  // Epilogue.\n  //\n\n  // Special case: last mapping's node contains only the key in explicit notation.\n  if (atExplicitKey) {\n    storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null);\n  }\n\n  // Expose the resulting mapping.\n  if (detected) {\n    state.tag = _tag;\n    state.anchor = _anchor;\n    state.kind = 'mapping';\n    state.result = _result;\n  }\n\n  return detected;\n}\n\nfunction readTagProperty(state) {\n  var _position,\n      isVerbatim = false,\n      isNamed    = false,\n      tagHandle,\n      tagName,\n      ch;\n\n  ch = state.input.charCodeAt(state.position);\n\n  if (ch !== 0x21/* ! */) return false;\n\n  if (state.tag !== null) {\n    throwError(state, 'duplication of a tag property');\n  }\n\n  ch = state.input.charCodeAt(++state.position);\n\n  if (ch === 0x3C/* < */) {\n    isVerbatim = true;\n    ch = state.input.charCodeAt(++state.position);\n\n  } else if (ch === 0x21/* ! */) {\n    isNamed = true;\n    tagHandle = '!!';\n    ch = state.input.charCodeAt(++state.position);\n\n  } else {\n    tagHandle = '!';\n  }\n\n  _position = state.position;\n\n  if (isVerbatim) {\n    do { ch = state.input.charCodeAt(++state.position); }\n    while (ch !== 0 && ch !== 0x3E/* > */);\n\n    if (state.position < state.length) {\n      tagName = state.input.slice(_position, state.position);\n      ch = state.input.charCodeAt(++state.position);\n    } else {\n      throwError(state, 'unexpected end of the stream within a verbatim tag');\n    }\n  } else {\n    while (ch !== 0 && !is_WS_OR_EOL(ch)) {\n\n      if (ch === 0x21/* ! */) {\n        if (!isNamed) {\n          tagHandle = state.input.slice(_position - 1, state.position + 1);\n\n          if (!PATTERN_TAG_HANDLE.test(tagHandle)) {\n            throwError(state, 'named tag handle cannot contain such characters');\n          }\n\n          isNamed = true;\n          _position = state.position + 1;\n        } else {\n          throwError(state, 'tag suffix cannot contain exclamation marks');\n        }\n      }\n\n      ch = state.input.charCodeAt(++state.position);\n    }\n\n    tagName = state.input.slice(_position, state.position);\n\n    if (PATTERN_FLOW_INDICATORS.test(tagName)) {\n      throwError(state, 'tag suffix cannot contain flow indicator characters');\n    }\n  }\n\n  if (tagName && !PATTERN_TAG_URI.test(tagName)) {\n    throwError(state, 'tag name cannot contain such characters: ' + tagName);\n  }\n\n  if (isVerbatim) {\n    state.tag = tagName;\n\n  } else if (_hasOwnProperty.call(state.tagMap, tagHandle)) {\n    state.tag = state.tagMap[tagHandle] + tagName;\n\n  } else if (tagHandle === '!') {\n    state.tag = '!' + tagName;\n\n  } else if (tagHandle === '!!') {\n    state.tag = 'tag:yaml.org,2002:' + tagName;\n\n  } else {\n    throwError(state, 'undeclared tag handle \"' + tagHandle + '\"');\n  }\n\n  return true;\n}\n\nfunction readAnchorProperty(state) {\n  var _position,\n      ch;\n\n  ch = state.input.charCodeAt(state.position);\n\n  if (ch !== 0x26/* & */) return false;\n\n  if (state.anchor !== null) {\n    throwError(state, 'duplication of an anchor property');\n  }\n\n  ch = state.input.charCodeAt(++state.position);\n  _position = state.position;\n\n  while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {\n    ch = state.input.charCodeAt(++state.position);\n  }\n\n  if (state.position === _position) {\n    throwError(state, 'name of an anchor node must contain at least one character');\n  }\n\n  state.anchor = state.input.slice(_position, state.position);\n  return true;\n}\n\nfunction readAlias(state) {\n  var _position, alias,\n      ch;\n\n  ch = state.input.charCodeAt(state.position);\n\n  if (ch !== 0x2A/* * */) return false;\n\n  ch = state.input.charCodeAt(++state.position);\n  _position = state.position;\n\n  while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {\n    ch = state.input.charCodeAt(++state.position);\n  }\n\n  if (state.position === _position) {\n    throwError(state, 'name of an alias node must contain at least one character');\n  }\n\n  alias = state.input.slice(_position, state.position);\n\n  if (!state.anchorMap.hasOwnProperty(alias)) {\n    throwError(state, 'unidentified alias \"' + alias + '\"');\n  }\n\n  state.result = state.anchorMap[alias];\n  skipSeparationSpace(state, true, -1);\n  return true;\n}\n\nfunction composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) {\n  var allowBlockStyles,\n      allowBlockScalars,\n      allowBlockCollections,\n      indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this<parent\n      atNewLine  = false,\n      hasContent = false,\n      typeIndex,\n      typeQuantity,\n      type,\n      flowIndent,\n      blockIndent;\n\n  if (state.listener !== null) {\n    state.listener('open', state);\n  }\n\n  state.tag    = null;\n  state.anchor = null;\n  state.kind   = null;\n  state.result = null;\n\n  allowBlockStyles = allowBlockScalars = allowBlockCollections =\n    CONTEXT_BLOCK_OUT === nodeContext ||\n    CONTEXT_BLOCK_IN  === nodeContext;\n\n  if (allowToSeek) {\n    if (skipSeparationSpace(state, true, -1)) {\n      atNewLine = true;\n\n      if (state.lineIndent > parentIndent) {\n        indentStatus = 1;\n      } else if (state.lineIndent === parentIndent) {\n        indentStatus = 0;\n      } else if (state.lineIndent < parentIndent) {\n        indentStatus = -1;\n      }\n    }\n  }\n\n  if (indentStatus === 1) {\n    while (readTagProperty(state) || readAnchorProperty(state)) {\n      if (skipSeparationSpace(state, true, -1)) {\n        atNewLine = true;\n        allowBlockCollections = allowBlockStyles;\n\n        if (state.lineIndent > parentIndent) {\n          indentStatus = 1;\n        } else if (state.lineIndent === parentIndent) {\n          indentStatus = 0;\n        } else if (state.lineIndent < parentIndent) {\n          indentStatus = -1;\n        }\n      } else {\n        allowBlockCollections = false;\n      }\n    }\n  }\n\n  if (allowBlockCollections) {\n    allowBlockCollections = atNewLine || allowCompact;\n  }\n\n  if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) {\n    if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) {\n      flowIndent = parentIndent;\n    } else {\n      flowIndent = parentIndent + 1;\n    }\n\n    blockIndent = state.position - state.lineStart;\n\n    if (indentStatus === 1) {\n      if (allowBlockCollections &&\n          (readBlockSequence(state, blockIndent) ||\n           readBlockMapping(state, blockIndent, flowIndent)) ||\n          readFlowCollection(state, flowIndent)) {\n        hasContent = true;\n      } else {\n        if ((allowBlockScalars && readBlockScalar(state, flowIndent)) ||\n            readSingleQuotedScalar(state, flowIndent) ||\n            readDoubleQuotedScalar(state, flowIndent)) {\n          hasContent = true;\n\n        } else if (readAlias(state)) {\n          hasContent = true;\n\n          if (state.tag !== null || state.anchor !== null) {\n            throwError(state, 'alias node should not have any properties');\n          }\n\n        } else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) {\n          hasContent = true;\n\n          if (state.tag === null) {\n            state.tag = '?';\n          }\n        }\n\n        if (state.anchor !== null) {\n          state.anchorMap[state.anchor] = state.result;\n        }\n      }\n    } else if (indentStatus === 0) {\n      // Special case: block sequences are allowed to have same indentation level as the parent.\n      // http://www.yaml.org/spec/1.2/spec.html#id2799784\n      hasContent = allowBlockCollections && readBlockSequence(state, blockIndent);\n    }\n  }\n\n  if (state.tag !== null && state.tag !== '!') {\n    if (state.tag === '?') {\n      for (typeIndex = 0, typeQuantity = state.implicitTypes.length;\n           typeIndex < typeQuantity;\n           typeIndex += 1) {\n        type = state.implicitTypes[typeIndex];\n\n        // Implicit resolving is not allowed for non-scalar types, and '?'\n        // non-specific tag is only assigned to plain scalars. So, it isn't\n        // needed to check for 'kind' conformity.\n\n        if (type.resolve(state.result)) { // `state.result` updated in resolver if matched\n          state.result = type.construct(state.result);\n          state.tag = type.tag;\n          if (state.anchor !== null) {\n            state.anchorMap[state.anchor] = state.result;\n          }\n          break;\n        }\n      }\n    } else if (_hasOwnProperty.call(state.typeMap, state.tag)) {\n      type = state.typeMap[state.tag];\n\n      if (state.result !== null && type.kind !== state.kind) {\n        throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be \"' + type.kind + '\", not \"' + state.kind + '\"');\n      }\n\n      if (!type.resolve(state.result)) { // `state.result` updated in resolver if matched\n        throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag');\n      } else {\n        state.result = type.construct(state.result);\n        if (state.anchor !== null) {\n          state.anchorMap[state.anchor] = state.result;\n        }\n      }\n    } else {\n      throwError(state, 'unknown tag !<' + state.tag + '>');\n    }\n  }\n\n  if (state.listener !== null) {\n    state.listener('close', state);\n  }\n  return state.tag !== null ||  state.anchor !== null || hasContent;\n}\n\nfunction readDocument(state) {\n  var documentStart = state.position,\n      _position,\n      directiveName,\n      directiveArgs,\n      hasDirectives = false,\n      ch;\n\n  state.version = null;\n  state.checkLineBreaks = state.legacy;\n  state.tagMap = {};\n  state.anchorMap = {};\n\n  while ((ch = state.input.charCodeAt(state.position)) !== 0) {\n    skipSeparationSpace(state, true, -1);\n\n    ch = state.input.charCodeAt(state.position);\n\n    if (state.lineIndent > 0 || ch !== 0x25/* % */) {\n      break;\n    }\n\n    hasDirectives = true;\n    ch = state.input.charCodeAt(++state.position);\n    _position = state.position;\n\n    while (ch !== 0 && !is_WS_OR_EOL(ch)) {\n      ch = state.input.charCodeAt(++state.position);\n    }\n\n    directiveName = state.input.slice(_position, state.position);\n    directiveArgs = [];\n\n    if (directiveName.length < 1) {\n      throwError(state, 'directive name must not be less than one character in length');\n    }\n\n    while (ch !== 0) {\n      while (is_WHITE_SPACE(ch)) {\n        ch = state.input.charCodeAt(++state.position);\n      }\n\n      if (ch === 0x23/* # */) {\n        do { ch = state.input.charCodeAt(++state.position); }\n        while (ch !== 0 && !is_EOL(ch));\n        break;\n      }\n\n      if (is_EOL(ch)) break;\n\n      _position = state.position;\n\n      while (ch !== 0 && !is_WS_OR_EOL(ch)) {\n        ch = state.input.charCodeAt(++state.position);\n      }\n\n      directiveArgs.push(state.input.slice(_position, state.position));\n    }\n\n    if (ch !== 0) readLineBreak(state);\n\n    if (_hasOwnProperty.call(directiveHandlers, directiveName)) {\n      directiveHandlers[directiveName](state, directiveName, directiveArgs);\n    } else {\n      throwWarning(state, 'unknown document directive \"' + directiveName + '\"');\n    }\n  }\n\n  skipSeparationSpace(state, true, -1);\n\n  if (state.lineIndent === 0 &&\n      state.input.charCodeAt(state.position)     === 0x2D/* - */ &&\n      state.input.charCodeAt(state.position + 1) === 0x2D/* - */ &&\n      state.input.charCodeAt(state.position + 2) === 0x2D/* - */) {\n    state.position += 3;\n    skipSeparationSpace(state, true, -1);\n\n  } else if (hasDirectives) {\n    throwError(state, 'directives end mark is expected');\n  }\n\n  composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true);\n  skipSeparationSpace(state, true, -1);\n\n  if (state.checkLineBreaks &&\n      PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) {\n    throwWarning(state, 'non-ASCII line breaks are interpreted as content');\n  }\n\n  state.documents.push(state.result);\n\n  if (state.position === state.lineStart && testDocumentSeparator(state)) {\n\n    if (state.input.charCodeAt(state.position) === 0x2E/* . */) {\n      state.position += 3;\n      skipSeparationSpace(state, true, -1);\n    }\n    return;\n  }\n\n  if (state.position < (state.length - 1)) {\n    throwError(state, 'end of the stream or a document separator is expected');\n  } else {\n    return;\n  }\n}\n\n\nfunction loadDocuments(input, options) {\n  input = String(input);\n  options = options || {};\n\n  if (input.length !== 0) {\n\n    // Add tailing `\\n` if not exists\n    if (input.charCodeAt(input.length - 1) !== 0x0A/* LF */ &&\n        input.charCodeAt(input.length - 1) !== 0x0D/* CR */) {\n      input += '\\n';\n    }\n\n    // Strip BOM\n    if (input.charCodeAt(0) === 0xFEFF) {\n      input = input.slice(1);\n    }\n  }\n\n  var state = new State(input, options);\n\n  // Use 0 as string terminator. That significantly simplifies bounds check.\n  state.input += '\\0';\n\n  while (state.input.charCodeAt(state.position) === 0x20/* Space */) {\n    state.lineIndent += 1;\n    state.position += 1;\n  }\n\n  while (state.position < (state.length - 1)) {\n    readDocument(state);\n  }\n\n  return state.documents;\n}\n\n\nfunction loadAll(input, iterator, options) {\n  var documents = loadDocuments(input, options), index, length;\n\n  for (index = 0, length = documents.length; index < length; index += 1) {\n    iterator(documents[index]);\n  }\n}\n\n\nfunction load(input, options) {\n  var documents = loadDocuments(input, options);\n\n  if (documents.length === 0) {\n    /*eslint-disable no-undefined*/\n    return undefined;\n  } else if (documents.length === 1) {\n    return documents[0];\n  }\n  throw new YAMLException('expected a single document in the stream, but found more');\n}\n\n\nfunction safeLoadAll(input, output, options) {\n  loadAll(input, output, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));\n}\n\n\nfunction safeLoad(input, options) {\n  return load(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));\n}\n\n\nmodule.exports.loadAll     = loadAll;\nmodule.exports.load        = load;\nmodule.exports.safeLoadAll = safeLoadAll;\nmodule.exports.safeLoad    = safeLoad;\n\n},{\"./common\":21,\"./exception\":23,\"./mark\":25,\"./schema/default_full\":28,\"./schema/default_safe\":29}],25:[function(require,module,exports){\n'use strict';\n\n\nvar common = require('./common');\n\n\nfunction Mark(name, buffer, position, line, column) {\n  this.name     = name;\n  this.buffer   = buffer;\n  this.position = position;\n  this.line     = line;\n  this.column   = column;\n}\n\n\nMark.prototype.getSnippet = function getSnippet(indent, maxLength) {\n  var head, start, tail, end, snippet;\n\n  if (!this.buffer) return null;\n\n  indent = indent || 4;\n  maxLength = maxLength || 75;\n\n  head = '';\n  start = this.position;\n\n  while (start > 0 && '\\x00\\r\\n\\x85\\u2028\\u2029'.indexOf(this.buffer.charAt(start - 1)) === -1) {\n    start -= 1;\n    if (this.position - start > (maxLength / 2 - 1)) {\n      head = ' ... ';\n      start += 5;\n      break;\n    }\n  }\n\n  tail = '';\n  end = this.position;\n\n  while (end < this.buffer.length && '\\x00\\r\\n\\x85\\u2028\\u2029'.indexOf(this.buffer.charAt(end)) === -1) {\n    end += 1;\n    if (end - this.position > (maxLength / 2 - 1)) {\n      tail = ' ... ';\n      end -= 5;\n      break;\n    }\n  }\n\n  snippet = this.buffer.slice(start, end);\n\n  return common.repeat(' ', indent) + head + snippet + tail + '\\n' +\n         common.repeat(' ', indent + this.position - start + head.length) + '^';\n};\n\n\nMark.prototype.toString = function toString(compact) {\n  var snippet, where = '';\n\n  if (this.name) {\n    where += 'in \"' + this.name + '\" ';\n  }\n\n  where += 'at line ' + (this.line + 1) + ', column ' + (this.column + 1);\n\n  if (!compact) {\n    snippet = this.getSnippet();\n\n    if (snippet) {\n      where += ':\\n' + snippet;\n    }\n  }\n\n  return where;\n};\n\n\nmodule.exports = Mark;\n\n},{\"./common\":21}],26:[function(require,module,exports){\n'use strict';\n\n/*eslint-disable max-len*/\n\nvar common        = require('./common');\nvar YAMLException = require('./exception');\nvar Type          = require('./type');\n\n\nfunction compileList(schema, name, result) {\n  var exclude = [];\n\n  schema.include.forEach(function (includedSchema) {\n    result = compileList(includedSchema, name, result);\n  });\n\n  schema[name].forEach(function (currentType) {\n    result.forEach(function (previousType, previousIndex) {\n      if (previousType.tag === currentType.tag) {\n        exclude.push(previousIndex);\n      }\n    });\n\n    result.push(currentType);\n  });\n\n  return result.filter(function (type, index) {\n    return exclude.indexOf(index) === -1;\n  });\n}\n\n\nfunction compileMap(/* lists... */) {\n  var result = {}, index, length;\n\n  function collectType(type) {\n    result[type.tag] = type;\n  }\n\n  for (index = 0, length = arguments.length; index < length; index += 1) {\n    arguments[index].forEach(collectType);\n  }\n\n  return result;\n}\n\n\nfunction Schema(definition) {\n  this.include  = definition.include  || [];\n  this.implicit = definition.implicit || [];\n  this.explicit = definition.explicit || [];\n\n  this.implicit.forEach(function (type) {\n    if (type.loadKind && type.loadKind !== 'scalar') {\n      throw new YAMLException('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.');\n    }\n  });\n\n  this.compiledImplicit = compileList(this, 'implicit', []);\n  this.compiledExplicit = compileList(this, 'explicit', []);\n  this.compiledTypeMap  = compileMap(this.compiledImplicit, this.compiledExplicit);\n}\n\n\nSchema.DEFAULT = null;\n\n\nSchema.create = function createSchema() {\n  var schemas, types;\n\n  switch (arguments.length) {\n    case 1:\n      schemas = Schema.DEFAULT;\n      types = arguments[0];\n      break;\n\n    case 2:\n      schemas = arguments[0];\n      types = arguments[1];\n      break;\n\n    default:\n      throw new YAMLException('Wrong number of arguments for Schema.create function');\n  }\n\n  schemas = common.toArray(schemas);\n  types = common.toArray(types);\n\n  if (!schemas.every(function (schema) { return schema instanceof Schema; })) {\n    throw new YAMLException('Specified list of super schemas (or a single Schema object) contains a non-Schema object.');\n  }\n\n  if (!types.every(function (type) { return type instanceof Type; })) {\n    throw new YAMLException('Specified list of YAML types (or a single Type object) contains a non-Type object.');\n  }\n\n  return new Schema({\n    include: schemas,\n    explicit: types\n  });\n};\n\n\nmodule.exports = Schema;\n\n},{\"./common\":21,\"./exception\":23,\"./type\":32}],27:[function(require,module,exports){\n// Standard YAML's Core schema.\n// http://www.yaml.org/spec/1.2/spec.html#id2804923\n//\n// NOTE: JS-YAML does not support schema-specific tag resolution restrictions.\n// So, Core schema has no distinctions from JSON schema is JS-YAML.\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = new Schema({\n  include: [\n    require('./json')\n  ]\n});\n\n},{\"../schema\":26,\"./json\":31}],28:[function(require,module,exports){\n// JS-YAML's default schema for `load` function.\n// It is not described in the YAML specification.\n//\n// This schema is based on JS-YAML's default safe schema and includes\n// JavaScript-specific types: !!js/undefined, !!js/regexp and !!js/function.\n//\n// Also this schema is used as default base schema at `Schema.create` function.\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = Schema.DEFAULT = new Schema({\n  include: [\n    require('./default_safe')\n  ],\n  explicit: [\n    require('../type/js/undefined'),\n    require('../type/js/regexp'),\n    require('../type/js/function')\n  ]\n});\n\n},{\"../schema\":26,\"../type/js/function\":37,\"../type/js/regexp\":38,\"../type/js/undefined\":39,\"./default_safe\":29}],29:[function(require,module,exports){\n// JS-YAML's default schema for `safeLoad` function.\n// It is not described in the YAML specification.\n//\n// This schema is based on standard YAML's Core schema and includes most of\n// extra types described at YAML tag repository. (http://yaml.org/type/)\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = new Schema({\n  include: [\n    require('./core')\n  ],\n  implicit: [\n    require('../type/timestamp'),\n    require('../type/merge')\n  ],\n  explicit: [\n    require('../type/binary'),\n    require('../type/omap'),\n    require('../type/pairs'),\n    require('../type/set')\n  ]\n});\n\n},{\"../schema\":26,\"../type/binary\":33,\"../type/merge\":41,\"../type/omap\":43,\"../type/pairs\":44,\"../type/set\":46,\"../type/timestamp\":48,\"./core\":27}],30:[function(require,module,exports){\n// Standard YAML's Failsafe schema.\n// http://www.yaml.org/spec/1.2/spec.html#id2802346\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = new Schema({\n  explicit: [\n    require('../type/str'),\n    require('../type/seq'),\n    require('../type/map')\n  ]\n});\n\n},{\"../schema\":26,\"../type/map\":40,\"../type/seq\":45,\"../type/str\":47}],31:[function(require,module,exports){\n// Standard YAML's JSON schema.\n// http://www.yaml.org/spec/1.2/spec.html#id2803231\n//\n// NOTE: JS-YAML does not support schema-specific tag resolution restrictions.\n// So, this schema is not such strict as defined in the YAML specification.\n// It allows numbers in binary notaion, use `Null` and `NULL` as `null`, etc.\n\n\n'use strict';\n\n\nvar Schema = require('../schema');\n\n\nmodule.exports = new Schema({\n  include: [\n    require('./failsafe')\n  ],\n  implicit: [\n    require('../type/null'),\n    require('../type/bool'),\n    require('../type/int'),\n    require('../type/float')\n  ]\n});\n\n},{\"../schema\":26,\"../type/bool\":34,\"../type/float\":35,\"../type/int\":36,\"../type/null\":42,\"./failsafe\":30}],32:[function(require,module,exports){\n'use strict';\n\nvar YAMLException = require('./exception');\n\nvar TYPE_CONSTRUCTOR_OPTIONS = [\n  'kind',\n  'resolve',\n  'construct',\n  'instanceOf',\n  'predicate',\n  'represent',\n  'defaultStyle',\n  'styleAliases'\n];\n\nvar YAML_NODE_KINDS = [\n  'scalar',\n  'sequence',\n  'mapping'\n];\n\nfunction compileStyleAliases(map) {\n  var result = {};\n\n  if (map !== null) {\n    Object.keys(map).forEach(function (style) {\n      map[style].forEach(function (alias) {\n        result[String(alias)] = style;\n      });\n    });\n  }\n\n  return result;\n}\n\nfunction Type(tag, options) {\n  options = options || {};\n\n  Object.keys(options).forEach(function (name) {\n    if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) {\n      throw new YAMLException('Unknown option \"' + name + '\" is met in definition of \"' + tag + '\" YAML type.');\n    }\n  });\n\n  // TODO: Add tag format check.\n  this.tag          = tag;\n  this.kind         = options['kind']         || null;\n  this.resolve      = options['resolve']      || function () { return true; };\n  this.construct    = options['construct']    || function (data) { return data; };\n  this.instanceOf   = options['instanceOf']   || null;\n  this.predicate    = options['predicate']    || null;\n  this.represent    = options['represent']    || null;\n  this.defaultStyle = options['defaultStyle'] || null;\n  this.styleAliases = compileStyleAliases(options['styleAliases'] || null);\n\n  if (YAML_NODE_KINDS.indexOf(this.kind) === -1) {\n    throw new YAMLException('Unknown kind \"' + this.kind + '\" is specified for \"' + tag + '\" YAML type.');\n  }\n}\n\nmodule.exports = Type;\n\n},{\"./exception\":23}],33:[function(require,module,exports){\n'use strict';\n\n/*eslint-disable no-bitwise*/\n\nvar NodeBuffer;\n\ntry {\n  // A trick for browserified version, to not include `Buffer` shim\n  var _require = require;\n  NodeBuffer = _require('buffer').Buffer;\n} catch (__) {}\n\nvar Type       = require('../type');\n\n\n// [ 64, 65, 66 ] -> [ padding, CR, LF ]\nvar BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\\n\\r';\n\n\nfunction resolveYamlBinary(data) {\n  if (data === null) return false;\n\n  var code, idx, bitlen = 0, max = data.length, map = BASE64_MAP;\n\n  // Convert one by one.\n  for (idx = 0; idx < max; idx++) {\n    code = map.indexOf(data.charAt(idx));\n\n    // Skip CR/LF\n    if (code > 64) continue;\n\n    // Fail on illegal characters\n    if (code < 0) return false;\n\n    bitlen += 6;\n  }\n\n  // If there are any bits left, source was corrupted\n  return (bitlen % 8) === 0;\n}\n\nfunction constructYamlBinary(data) {\n  var idx, tailbits,\n      input = data.replace(/[\\r\\n=]/g, ''), // remove CR/LF & padding to simplify scan\n      max = input.length,\n      map = BASE64_MAP,\n      bits = 0,\n      result = [];\n\n  // Collect by 6*4 bits (3 bytes)\n\n  for (idx = 0; idx < max; idx++) {\n    if ((idx % 4 === 0) && idx) {\n      result.push((bits >> 16) & 0xFF);\n      result.push((bits >> 8) & 0xFF);\n      result.push(bits & 0xFF);\n    }\n\n    bits = (bits << 6) | map.indexOf(input.charAt(idx));\n  }\n\n  // Dump tail\n\n  tailbits = (max % 4) * 6;\n\n  if (tailbits === 0) {\n    result.push((bits >> 16) & 0xFF);\n    result.push((bits >> 8) & 0xFF);\n    result.push(bits & 0xFF);\n  } else if (tailbits === 18) {\n    result.push((bits >> 10) & 0xFF);\n    result.push((bits >> 2) & 0xFF);\n  } else if (tailbits === 12) {\n    result.push((bits >> 4) & 0xFF);\n  }\n\n  // Wrap into Buffer for NodeJS and leave Array for browser\n  if (NodeBuffer) return new NodeBuffer(result);\n\n  return result;\n}\n\nfunction representYamlBinary(object /*, style*/) {\n  var result = '', bits = 0, idx, tail,\n      max = object.length,\n      map = BASE64_MAP;\n\n  // Convert every three bytes to 4 ASCII characters.\n\n  for (idx = 0; idx < max; idx++) {\n    if ((idx % 3 === 0) && idx) {\n      result += map[(bits >> 18) & 0x3F];\n      result += map[(bits >> 12) & 0x3F];\n      result += map[(bits >> 6) & 0x3F];\n      result += map[bits & 0x3F];\n    }\n\n    bits = (bits << 8) + object[idx];\n  }\n\n  // Dump tail\n\n  tail = max % 3;\n\n  if (tail === 0) {\n    result += map[(bits >> 18) & 0x3F];\n    result += map[(bits >> 12) & 0x3F];\n    result += map[(bits >> 6) & 0x3F];\n    result += map[bits & 0x3F];\n  } else if (tail === 2) {\n    result += map[(bits >> 10) & 0x3F];\n    result += map[(bits >> 4) & 0x3F];\n    result += map[(bits << 2) & 0x3F];\n    result += map[64];\n  } else if (tail === 1) {\n    result += map[(bits >> 2) & 0x3F];\n    result += map[(bits << 4) & 0x3F];\n    result += map[64];\n    result += map[64];\n  }\n\n  return result;\n}\n\nfunction isBinary(object) {\n  return NodeBuffer && NodeBuffer.isBuffer(object);\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:binary', {\n  kind: 'scalar',\n  resolve: resolveYamlBinary,\n  construct: constructYamlBinary,\n  predicate: isBinary,\n  represent: representYamlBinary\n});\n\n},{\"../type\":32}],34:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nfunction resolveYamlBoolean(data) {\n  if (data === null) return false;\n\n  var max = data.length;\n\n  return (max === 4 && (data === 'true' || data === 'True' || data === 'TRUE')) ||\n         (max === 5 && (data === 'false' || data === 'False' || data === 'FALSE'));\n}\n\nfunction constructYamlBoolean(data) {\n  return data === 'true' ||\n         data === 'True' ||\n         data === 'TRUE';\n}\n\nfunction isBoolean(object) {\n  return Object.prototype.toString.call(object) === '[object Boolean]';\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:bool', {\n  kind: 'scalar',\n  resolve: resolveYamlBoolean,\n  construct: constructYamlBoolean,\n  predicate: isBoolean,\n  represent: {\n    lowercase: function (object) { return object ? 'true' : 'false'; },\n    uppercase: function (object) { return object ? 'TRUE' : 'FALSE'; },\n    camelcase: function (object) { return object ? 'True' : 'False'; }\n  },\n  defaultStyle: 'lowercase'\n});\n\n},{\"../type\":32}],35:[function(require,module,exports){\n'use strict';\n\nvar common = require('../common');\nvar Type   = require('../type');\n\nvar YAML_FLOAT_PATTERN = new RegExp(\n  '^(?:[-+]?(?:[0-9][0-9_]*)\\\\.[0-9_]*(?:[eE][-+][0-9]+)?' +\n  '|\\\\.[0-9_]+(?:[eE][-+][0-9]+)?' +\n  '|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\\\.[0-9_]*' +\n  '|[-+]?\\\\.(?:inf|Inf|INF)' +\n  '|\\\\.(?:nan|NaN|NAN))$');\n\nfunction resolveYamlFloat(data) {\n  if (data === null) return false;\n\n  if (!YAML_FLOAT_PATTERN.test(data)) return false;\n\n  return true;\n}\n\nfunction constructYamlFloat(data) {\n  var value, sign, base, digits;\n\n  value  = data.replace(/_/g, '').toLowerCase();\n  sign   = value[0] === '-' ? -1 : 1;\n  digits = [];\n\n  if ('+-'.indexOf(value[0]) >= 0) {\n    value = value.slice(1);\n  }\n\n  if (value === '.inf') {\n    return (sign === 1) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;\n\n  } else if (value === '.nan') {\n    return NaN;\n\n  } else if (value.indexOf(':') >= 0) {\n    value.split(':').forEach(function (v) {\n      digits.unshift(parseFloat(v, 10));\n    });\n\n    value = 0.0;\n    base = 1;\n\n    digits.forEach(function (d) {\n      value += d * base;\n      base *= 60;\n    });\n\n    return sign * value;\n\n  }\n  return sign * parseFloat(value, 10);\n}\n\n\nvar SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/;\n\nfunction representYamlFloat(object, style) {\n  var res;\n\n  if (isNaN(object)) {\n    switch (style) {\n      case 'lowercase': return '.nan';\n      case 'uppercase': return '.NAN';\n      case 'camelcase': return '.NaN';\n    }\n  } else if (Number.POSITIVE_INFINITY === object) {\n    switch (style) {\n      case 'lowercase': return '.inf';\n      case 'uppercase': return '.INF';\n      case 'camelcase': return '.Inf';\n    }\n  } else if (Number.NEGATIVE_INFINITY === object) {\n    switch (style) {\n      case 'lowercase': return '-.inf';\n      case 'uppercase': return '-.INF';\n      case 'camelcase': return '-.Inf';\n    }\n  } else if (common.isNegativeZero(object)) {\n    return '-0.0';\n  }\n\n  res = object.toString(10);\n\n  // JS stringifier can build scientific format without dots: 5e-100,\n  // while YAML requres dot: 5.e-100. Fix it with simple hack\n\n  return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace('e', '.e') : res;\n}\n\nfunction isFloat(object) {\n  return (Object.prototype.toString.call(object) === '[object Number]') &&\n         (object % 1 !== 0 || common.isNegativeZero(object));\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:float', {\n  kind: 'scalar',\n  resolve: resolveYamlFloat,\n  construct: constructYamlFloat,\n  predicate: isFloat,\n  represent: representYamlFloat,\n  defaultStyle: 'lowercase'\n});\n\n},{\"../common\":21,\"../type\":32}],36:[function(require,module,exports){\n'use strict';\n\nvar common = require('../common');\nvar Type   = require('../type');\n\nfunction isHexCode(c) {\n  return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) ||\n         ((0x41/* A */ <= c) && (c <= 0x46/* F */)) ||\n         ((0x61/* a */ <= c) && (c <= 0x66/* f */));\n}\n\nfunction isOctCode(c) {\n  return ((0x30/* 0 */ <= c) && (c <= 0x37/* 7 */));\n}\n\nfunction isDecCode(c) {\n  return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */));\n}\n\nfunction resolveYamlInteger(data) {\n  if (data === null) return false;\n\n  var max = data.length,\n      index = 0,\n      hasDigits = false,\n      ch;\n\n  if (!max) return false;\n\n  ch = data[index];\n\n  // sign\n  if (ch === '-' || ch === '+') {\n    ch = data[++index];\n  }\n\n  if (ch === '0') {\n    // 0\n    if (index + 1 === max) return true;\n    ch = data[++index];\n\n    // base 2, base 8, base 16\n\n    if (ch === 'b') {\n      // base 2\n      index++;\n\n      for (; index < max; index++) {\n        ch = data[index];\n        if (ch === '_') continue;\n        if (ch !== '0' && ch !== '1') return false;\n        hasDigits = true;\n      }\n      return hasDigits;\n    }\n\n\n    if (ch === 'x') {\n      // base 16\n      index++;\n\n      for (; index < max; index++) {\n        ch = data[index];\n        if (ch === '_') continue;\n        if (!isHexCode(data.charCodeAt(index))) return false;\n        hasDigits = true;\n      }\n      return hasDigits;\n    }\n\n    // base 8\n    for (; index < max; index++) {\n      ch = data[index];\n      if (ch === '_') continue;\n      if (!isOctCode(data.charCodeAt(index))) return false;\n      hasDigits = true;\n    }\n    return hasDigits;\n  }\n\n  // base 10 (except 0) or base 60\n\n  for (; index < max; index++) {\n    ch = data[index];\n    if (ch === '_') continue;\n    if (ch === ':') break;\n    if (!isDecCode(data.charCodeAt(index))) {\n      return false;\n    }\n    hasDigits = true;\n  }\n\n  if (!hasDigits) return false;\n\n  // if !base60 - done;\n  if (ch !== ':') return true;\n\n  // base60 almost not used, no needs to optimize\n  return /^(:[0-5]?[0-9])+$/.test(data.slice(index));\n}\n\nfunction constructYamlInteger(data) {\n  var value = data, sign = 1, ch, base, digits = [];\n\n  if (value.indexOf('_') !== -1) {\n    value = value.replace(/_/g, '');\n  }\n\n  ch = value[0];\n\n  if (ch === '-' || ch === '+') {\n    if (ch === '-') sign = -1;\n    value = value.slice(1);\n    ch = value[0];\n  }\n\n  if (value === '0') return 0;\n\n  if (ch === '0') {\n    if (value[1] === 'b') return sign * parseInt(value.slice(2), 2);\n    if (value[1] === 'x') return sign * parseInt(value, 16);\n    return sign * parseInt(value, 8);\n  }\n\n  if (value.indexOf(':') !== -1) {\n    value.split(':').forEach(function (v) {\n      digits.unshift(parseInt(v, 10));\n    });\n\n    value = 0;\n    base = 1;\n\n    digits.forEach(function (d) {\n      value += (d * base);\n      base *= 60;\n    });\n\n    return sign * value;\n\n  }\n\n  return sign * parseInt(value, 10);\n}\n\nfunction isInteger(object) {\n  return (Object.prototype.toString.call(object)) === '[object Number]' &&\n         (object % 1 === 0 && !common.isNegativeZero(object));\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:int', {\n  kind: 'scalar',\n  resolve: resolveYamlInteger,\n  construct: constructYamlInteger,\n  predicate: isInteger,\n  represent: {\n    binary:      function (object) { return '0b' + object.toString(2); },\n    octal:       function (object) { return '0'  + object.toString(8); },\n    decimal:     function (object) { return        object.toString(10); },\n    hexadecimal: function (object) { return '0x' + object.toString(16).toUpperCase(); }\n  },\n  defaultStyle: 'decimal',\n  styleAliases: {\n    binary:      [ 2,  'bin' ],\n    octal:       [ 8,  'oct' ],\n    decimal:     [ 10, 'dec' ],\n    hexadecimal: [ 16, 'hex' ]\n  }\n});\n\n},{\"../common\":21,\"../type\":32}],37:[function(require,module,exports){\n'use strict';\n\nvar esprima;\n\n// Browserified version does not have esprima\n//\n// 1. For node.js just require module as deps\n// 2. For browser try to require mudule via external AMD system.\n//    If not found - try to fallback to window.esprima. If not\n//    found too - then fail to parse.\n//\ntry {\n  // workaround to exclude package from browserify list.\n  var _require = require;\n  esprima = _require('esprima');\n} catch (_) {\n  /*global window */\n  if (typeof window !== 'undefined') esprima = window.esprima;\n}\n\nvar Type = require('../../type');\n\nfunction resolveJavascriptFunction(data) {\n  if (data === null) return false;\n\n  try {\n    var source = '(' + data + ')',\n        ast    = esprima.parse(source, { range: true });\n\n    if (ast.type                    !== 'Program'             ||\n        ast.body.length             !== 1                     ||\n        ast.body[0].type            !== 'ExpressionStatement' ||\n        ast.body[0].expression.type !== 'FunctionExpression') {\n      return false;\n    }\n\n    return true;\n  } catch (err) {\n    return false;\n  }\n}\n\nfunction constructJavascriptFunction(data) {\n  /*jslint evil:true*/\n\n  var source = '(' + data + ')',\n      ast    = esprima.parse(source, { range: true }),\n      params = [],\n      body;\n\n  if (ast.type                    !== 'Program'             ||\n      ast.body.length             !== 1                     ||\n      ast.body[0].type            !== 'ExpressionStatement' ||\n      ast.body[0].expression.type !== 'FunctionExpression') {\n    throw new Error('Failed to resolve function');\n  }\n\n  ast.body[0].expression.params.forEach(function (param) {\n    params.push(param.name);\n  });\n\n  body = ast.body[0].expression.body.range;\n\n  // Esprima's ranges include the first '{' and the last '}' characters on\n  // function expressions. So cut them out.\n  /*eslint-disable no-new-func*/\n  return new Function(params, source.slice(body[0] + 1, body[1] - 1));\n}\n\nfunction representJavascriptFunction(object /*, style*/) {\n  return object.toString();\n}\n\nfunction isFunction(object) {\n  return Object.prototype.toString.call(object) === '[object Function]';\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:js/function', {\n  kind: 'scalar',\n  resolve: resolveJavascriptFunction,\n  construct: constructJavascriptFunction,\n  predicate: isFunction,\n  represent: representJavascriptFunction\n});\n\n},{\"../../type\":32}],38:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../../type');\n\nfunction resolveJavascriptRegExp(data) {\n  if (data === null) return false;\n  if (data.length === 0) return false;\n\n  var regexp = data,\n      tail   = /\\/([gim]*)$/.exec(data),\n      modifiers = '';\n\n  // if regexp starts with '/' it can have modifiers and must be properly closed\n  // `/foo/gim` - modifiers tail can be maximum 3 chars\n  if (regexp[0] === '/') {\n    if (tail) modifiers = tail[1];\n\n    if (modifiers.length > 3) return false;\n    // if expression starts with /, is should be properly terminated\n    if (regexp[regexp.length - modifiers.length - 1] !== '/') return false;\n  }\n\n  return true;\n}\n\nfunction constructJavascriptRegExp(data) {\n  var regexp = data,\n      tail   = /\\/([gim]*)$/.exec(data),\n      modifiers = '';\n\n  // `/foo/gim` - tail can be maximum 4 chars\n  if (regexp[0] === '/') {\n    if (tail) modifiers = tail[1];\n    regexp = regexp.slice(1, regexp.length - modifiers.length - 1);\n  }\n\n  return new RegExp(regexp, modifiers);\n}\n\nfunction representJavascriptRegExp(object /*, style*/) {\n  var result = '/' + object.source + '/';\n\n  if (object.global) result += 'g';\n  if (object.multiline) result += 'm';\n  if (object.ignoreCase) result += 'i';\n\n  return result;\n}\n\nfunction isRegExp(object) {\n  return Object.prototype.toString.call(object) === '[object RegExp]';\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:js/regexp', {\n  kind: 'scalar',\n  resolve: resolveJavascriptRegExp,\n  construct: constructJavascriptRegExp,\n  predicate: isRegExp,\n  represent: representJavascriptRegExp\n});\n\n},{\"../../type\":32}],39:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../../type');\n\nfunction resolveJavascriptUndefined() {\n  return true;\n}\n\nfunction constructJavascriptUndefined() {\n  /*eslint-disable no-undefined*/\n  return undefined;\n}\n\nfunction representJavascriptUndefined() {\n  return '';\n}\n\nfunction isUndefined(object) {\n  return typeof object === 'undefined';\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:js/undefined', {\n  kind: 'scalar',\n  resolve: resolveJavascriptUndefined,\n  construct: constructJavascriptUndefined,\n  predicate: isUndefined,\n  represent: representJavascriptUndefined\n});\n\n},{\"../../type\":32}],40:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nmodule.exports = new Type('tag:yaml.org,2002:map', {\n  kind: 'mapping',\n  construct: function (data) { return data !== null ? data : {}; }\n});\n\n},{\"../type\":32}],41:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nfunction resolveYamlMerge(data) {\n  return data === '<<' || data === null;\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:merge', {\n  kind: 'scalar',\n  resolve: resolveYamlMerge\n});\n\n},{\"../type\":32}],42:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nfunction resolveYamlNull(data) {\n  if (data === null) return true;\n\n  var max = data.length;\n\n  return (max === 1 && data === '~') ||\n         (max === 4 && (data === 'null' || data === 'Null' || data === 'NULL'));\n}\n\nfunction constructYamlNull() {\n  return null;\n}\n\nfunction isNull(object) {\n  return object === null;\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:null', {\n  kind: 'scalar',\n  resolve: resolveYamlNull,\n  construct: constructYamlNull,\n  predicate: isNull,\n  represent: {\n    canonical: function () { return '~';    },\n    lowercase: function () { return 'null'; },\n    uppercase: function () { return 'NULL'; },\n    camelcase: function () { return 'Null'; }\n  },\n  defaultStyle: 'lowercase'\n});\n\n},{\"../type\":32}],43:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nvar _hasOwnProperty = Object.prototype.hasOwnProperty;\nvar _toString       = Object.prototype.toString;\n\nfunction resolveYamlOmap(data) {\n  if (data === null) return true;\n\n  var objectKeys = [], index, length, pair, pairKey, pairHasKey,\n      object = data;\n\n  for (index = 0, length = object.length; index < length; index += 1) {\n    pair = object[index];\n    pairHasKey = false;\n\n    if (_toString.call(pair) !== '[object Object]') return false;\n\n    for (pairKey in pair) {\n      if (_hasOwnProperty.call(pair, pairKey)) {\n        if (!pairHasKey) pairHasKey = true;\n        else return false;\n      }\n    }\n\n    if (!pairHasKey) return false;\n\n    if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey);\n    else return false;\n  }\n\n  return true;\n}\n\nfunction constructYamlOmap(data) {\n  return data !== null ? data : [];\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:omap', {\n  kind: 'sequence',\n  resolve: resolveYamlOmap,\n  construct: constructYamlOmap\n});\n\n},{\"../type\":32}],44:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nvar _toString = Object.prototype.toString;\n\nfunction resolveYamlPairs(data) {\n  if (data === null) return true;\n\n  var index, length, pair, keys, result,\n      object = data;\n\n  result = new Array(object.length);\n\n  for (index = 0, length = object.length; index < length; index += 1) {\n    pair = object[index];\n\n    if (_toString.call(pair) !== '[object Object]') return false;\n\n    keys = Object.keys(pair);\n\n    if (keys.length !== 1) return false;\n\n    result[index] = [ keys[0], pair[keys[0]] ];\n  }\n\n  return true;\n}\n\nfunction constructYamlPairs(data) {\n  if (data === null) return [];\n\n  var index, length, pair, keys, result,\n      object = data;\n\n  result = new Array(object.length);\n\n  for (index = 0, length = object.length; index < length; index += 1) {\n    pair = object[index];\n\n    keys = Object.keys(pair);\n\n    result[index] = [ keys[0], pair[keys[0]] ];\n  }\n\n  return result;\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:pairs', {\n  kind: 'sequence',\n  resolve: resolveYamlPairs,\n  construct: constructYamlPairs\n});\n\n},{\"../type\":32}],45:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nmodule.exports = new Type('tag:yaml.org,2002:seq', {\n  kind: 'sequence',\n  construct: function (data) { return data !== null ? data : []; }\n});\n\n},{\"../type\":32}],46:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nvar _hasOwnProperty = Object.prototype.hasOwnProperty;\n\nfunction resolveYamlSet(data) {\n  if (data === null) return true;\n\n  var key, object = data;\n\n  for (key in object) {\n    if (_hasOwnProperty.call(object, key)) {\n      if (object[key] !== null) return false;\n    }\n  }\n\n  return true;\n}\n\nfunction constructYamlSet(data) {\n  return data !== null ? data : {};\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:set', {\n  kind: 'mapping',\n  resolve: resolveYamlSet,\n  construct: constructYamlSet\n});\n\n},{\"../type\":32}],47:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nmodule.exports = new Type('tag:yaml.org,2002:str', {\n  kind: 'scalar',\n  construct: function (data) { return data !== null ? data : ''; }\n});\n\n},{\"../type\":32}],48:[function(require,module,exports){\n'use strict';\n\nvar Type = require('../type');\n\nvar YAML_DATE_REGEXP = new RegExp(\n  '^([0-9][0-9][0-9][0-9])'          + // [1] year\n  '-([0-9][0-9])'                    + // [2] month\n  '-([0-9][0-9])$');                   // [3] day\n\nvar YAML_TIMESTAMP_REGEXP = new RegExp(\n  '^([0-9][0-9][0-9][0-9])'          + // [1] year\n  '-([0-9][0-9]?)'                   + // [2] month\n  '-([0-9][0-9]?)'                   + // [3] day\n  '(?:[Tt]|[ \\\\t]+)'                 + // ...\n  '([0-9][0-9]?)'                    + // [4] hour\n  ':([0-9][0-9])'                    + // [5] minute\n  ':([0-9][0-9])'                    + // [6] second\n  '(?:\\\\.([0-9]*))?'                 + // [7] fraction\n  '(?:[ \\\\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour\n  '(?::([0-9][0-9]))?))?$');           // [11] tz_minute\n\nfunction resolveYamlTimestamp(data) {\n  if (data === null) return false;\n  if (YAML_DATE_REGEXP.exec(data) !== null) return true;\n  if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true;\n  return false;\n}\n\nfunction constructYamlTimestamp(data) {\n  var match, year, month, day, hour, minute, second, fraction = 0,\n      delta = null, tz_hour, tz_minute, date;\n\n  match = YAML_DATE_REGEXP.exec(data);\n  if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data);\n\n  if (match === null) throw new Error('Date resolve error');\n\n  // match: [1] year [2] month [3] day\n\n  year = +(match[1]);\n  month = +(match[2]) - 1; // JS month starts with 0\n  day = +(match[3]);\n\n  if (!match[4]) { // no hour\n    return new Date(Date.UTC(year, month, day));\n  }\n\n  // match: [4] hour [5] minute [6] second [7] fraction\n\n  hour = +(match[4]);\n  minute = +(match[5]);\n  second = +(match[6]);\n\n  if (match[7]) {\n    fraction = match[7].slice(0, 3);\n    while (fraction.length < 3) { // milli-seconds\n      fraction += '0';\n    }\n    fraction = +fraction;\n  }\n\n  // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute\n\n  if (match[9]) {\n    tz_hour = +(match[10]);\n    tz_minute = +(match[11] || 0);\n    delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds\n    if (match[9] === '-') delta = -delta;\n  }\n\n  date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));\n\n  if (delta) date.setTime(date.getTime() - delta);\n\n  return date;\n}\n\nfunction representYamlTimestamp(object /*, style*/) {\n  return object.toISOString();\n}\n\nmodule.exports = new Type('tag:yaml.org,2002:timestamp', {\n  kind: 'scalar',\n  resolve: resolveYamlTimestamp,\n  construct: constructYamlTimestamp,\n  instanceOf: Date,\n  represent: representYamlTimestamp\n});\n\n},{\"../type\":32}],49:[function(require,module,exports){\nvar baseIndexOf = require('../internal/baseIndexOf'),\n    binaryIndex = require('../internal/binaryIndex');\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max;\n\n/**\n * Gets the index at which the first occurrence of `value` is found in `array`\n * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * for equality comparisons. If `fromIndex` is negative, it's used as the offset\n * from the end of `array`. If `array` is sorted providing `true` for `fromIndex`\n * performs a faster binary search.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to search.\n * @param {*} value The value to search for.\n * @param {boolean|number} [fromIndex=0] The index to search from or `true`\n *  to perform a binary search on a sorted array.\n * @returns {number} Returns the index of the matched value, else `-1`.\n * @example\n *\n * _.indexOf([1, 2, 1, 2], 2);\n * // => 1\n *\n * // using `fromIndex`\n * _.indexOf([1, 2, 1, 2], 2, 2);\n * // => 3\n *\n * // performing a binary search\n * _.indexOf([1, 1, 2, 2], 2, true);\n * // => 2\n */\nfunction indexOf(array, value, fromIndex) {\n  var length = array ? array.length : 0;\n  if (!length) {\n    return -1;\n  }\n  if (typeof fromIndex == 'number') {\n    fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : fromIndex;\n  } else if (fromIndex) {\n    var index = binaryIndex(array, value);\n    if (index < length &&\n        (value === value ? (value === array[index]) : (array[index] !== array[index]))) {\n      return index;\n    }\n    return -1;\n  }\n  return baseIndexOf(array, value, fromIndex || 0);\n}\n\nmodule.exports = indexOf;\n\n},{\"../internal/baseIndexOf\":78,\"../internal/binaryIndex\":92}],50:[function(require,module,exports){\n/**\n * Gets the last element of `array`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to query.\n * @returns {*} Returns the last element of `array`.\n * @example\n *\n * _.last([1, 2, 3]);\n * // => 3\n */\nfunction last(array) {\n  var length = array ? array.length : 0;\n  return length ? array[length - 1] : undefined;\n}\n\nmodule.exports = last;\n\n},{}],51:[function(require,module,exports){\nvar LazyWrapper = require('../internal/LazyWrapper'),\n    LodashWrapper = require('../internal/LodashWrapper'),\n    baseLodash = require('../internal/baseLodash'),\n    isArray = require('../lang/isArray'),\n    isObjectLike = require('../internal/isObjectLike'),\n    wrapperClone = require('../internal/wrapperClone');\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Creates a `lodash` object which wraps `value` to enable implicit chaining.\n * Methods that operate on and return arrays, collections, and functions can\n * be chained together. Methods that retrieve a single value or may return a\n * primitive value will automatically end the chain returning the unwrapped\n * value. Explicit chaining may be enabled using `_.chain`. The execution of\n * chained methods is lazy, that is, execution is deferred until `_#value`\n * is implicitly or explicitly called.\n *\n * Lazy evaluation allows several methods to support shortcut fusion. Shortcut\n * fusion is an optimization strategy which merge iteratee calls; this can help\n * to avoid the creation of intermediate data structures and greatly reduce the\n * number of iteratee executions.\n *\n * Chaining is supported in custom builds as long as the `_#value` method is\n * directly or indirectly included in the build.\n *\n * In addition to lodash methods, wrappers have `Array` and `String` methods.\n *\n * The wrapper `Array` methods are:\n * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`,\n * `splice`, and `unshift`\n *\n * The wrapper `String` methods are:\n * `replace` and `split`\n *\n * The wrapper methods that support shortcut fusion are:\n * `compact`, `drop`, `dropRight`, `dropRightWhile`, `dropWhile`, `filter`,\n * `first`, `initial`, `last`, `map`, `pluck`, `reject`, `rest`, `reverse`,\n * `slice`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `toArray`,\n * and `where`\n *\n * The chainable wrapper methods are:\n * `after`, `ary`, `assign`, `at`, `before`, `bind`, `bindAll`, `bindKey`,\n * `callback`, `chain`, `chunk`, `commit`, `compact`, `concat`, `constant`,\n * `countBy`, `create`, `curry`, `debounce`, `defaults`, `defaultsDeep`,\n * `defer`, `delay`, `difference`, `drop`, `dropRight`, `dropRightWhile`,\n * `dropWhile`, `fill`, `filter`, `flatten`, `flattenDeep`, `flow`, `flowRight`,\n * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,\n * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,\n * `invoke`, `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`,\n * `matchesProperty`, `memoize`, `merge`, `method`, `methodOf`, `mixin`,\n * `modArgs`, `negate`, `omit`, `once`, `pairs`, `partial`, `partialRight`,\n * `partition`, `pick`, `plant`, `pluck`, `property`, `propertyOf`, `pull`,\n * `pullAt`, `push`, `range`, `rearg`, `reject`, `remove`, `rest`, `restParam`,\n * `reverse`, `set`, `shuffle`, `slice`, `sort`, `sortBy`, `sortByAll`,\n * `sortByOrder`, `splice`, `spread`, `take`, `takeRight`, `takeRightWhile`,\n * `takeWhile`, `tap`, `throttle`, `thru`, `times`, `toArray`, `toPlainObject`,\n * `transform`, `union`, `uniq`, `unshift`, `unzip`, `unzipWith`, `values`,\n * `valuesIn`, `where`, `without`, `wrap`, `xor`, `zip`, `zipObject`, `zipWith`\n *\n * The wrapper methods that are **not** chainable by default are:\n * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clone`, `cloneDeep`,\n * `deburr`, `endsWith`, `escape`, `escapeRegExp`, `every`, `find`, `findIndex`,\n * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `findWhere`, `first`,\n * `floor`, `get`, `gt`, `gte`, `has`, `identity`, `includes`, `indexOf`,\n * `inRange`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,\n * `isEmpty`, `isEqual`, `isError`, `isFinite` `isFunction`, `isMatch`,\n * `isNative`, `isNaN`, `isNull`, `isNumber`, `isObject`, `isPlainObject`,\n * `isRegExp`, `isString`, `isUndefined`, `isTypedArray`, `join`, `kebabCase`,\n * `last`, `lastIndexOf`, `lt`, `lte`, `max`, `min`, `noConflict`, `noop`,\n * `now`, `pad`, `padLeft`, `padRight`, `parseInt`, `pop`, `random`, `reduce`,\n * `reduceRight`, `repeat`, `result`, `round`, `runInContext`, `shift`, `size`,\n * `snakeCase`, `some`, `sortedIndex`, `sortedLastIndex`, `startCase`,\n * `startsWith`, `sum`, `template`, `trim`, `trimLeft`, `trimRight`, `trunc`,\n * `unescape`, `uniqueId`, `value`, and `words`\n *\n * The wrapper method `sample` will return a wrapped value when `n` is provided,\n * otherwise an unwrapped value is returned.\n *\n * @name _\n * @constructor\n * @category Chain\n * @param {*} value The value to wrap in a `lodash` instance.\n * @returns {Object} Returns the new `lodash` wrapper instance.\n * @example\n *\n * var wrapped = _([1, 2, 3]);\n *\n * // returns an unwrapped value\n * wrapped.reduce(function(total, n) {\n *   return total + n;\n * });\n * // => 6\n *\n * // returns a wrapped value\n * var squares = wrapped.map(function(n) {\n *   return n * n;\n * });\n *\n * _.isArray(squares);\n * // => false\n *\n * _.isArray(squares.value());\n * // => true\n */\nfunction lodash(value) {\n  if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {\n    if (value instanceof LodashWrapper) {\n      return value;\n    }\n    if (hasOwnProperty.call(value, '__chain__') && hasOwnProperty.call(value, '__wrapped__')) {\n      return wrapperClone(value);\n    }\n  }\n  return new LodashWrapper(value);\n}\n\n// Ensure wrappers are instances of `baseLodash`.\nlodash.prototype = baseLodash.prototype;\n\nmodule.exports = lodash;\n\n},{\"../internal/LazyWrapper\":60,\"../internal/LodashWrapper\":61,\"../internal/baseLodash\":82,\"../internal/isObjectLike\":126,\"../internal/wrapperClone\":137,\"../lang/isArray\":140}],52:[function(require,module,exports){\nmodule.exports = require('./forEach');\n\n},{\"./forEach\":54}],53:[function(require,module,exports){\nvar baseEach = require('../internal/baseEach'),\n    createFind = require('../internal/createFind');\n\n/**\n * Iterates over elements of `collection`, returning the first element\n * `predicate` returns truthy for. The predicate is bound to `thisArg` and\n * invoked with three arguments: (value, index|key, collection).\n *\n * If a property name is provided for `predicate` the created `_.property`\n * style callback returns the property value of the given element.\n *\n * If a value is also provided for `thisArg` the created `_.matchesProperty`\n * style callback returns `true` for elements that have a matching property\n * value, else `false`.\n *\n * If an object is provided for `predicate` the created `_.matches` style\n * callback returns `true` for elements that have the properties of the given\n * object, else `false`.\n *\n * @static\n * @memberOf _\n * @alias detect\n * @category Collection\n * @param {Array|Object|string} collection The collection to search.\n * @param {Function|Object|string} [predicate=_.identity] The function invoked\n *  per iteration.\n * @param {*} [thisArg] The `this` binding of `predicate`.\n * @returns {*} Returns the matched element, else `undefined`.\n * @example\n *\n * var users = [\n *   { 'user': 'barney',  'age': 36, 'active': true },\n *   { 'user': 'fred',    'age': 40, 'active': false },\n *   { 'user': 'pebbles', 'age': 1,  'active': true }\n * ];\n *\n * _.result(_.find(users, function(chr) {\n *   return chr.age < 40;\n * }), 'user');\n * // => 'barney'\n *\n * // using the `_.matches` callback shorthand\n * _.result(_.find(users, { 'age': 1, 'active': true }), 'user');\n * // => 'pebbles'\n *\n * // using the `_.matchesProperty` callback shorthand\n * _.result(_.find(users, 'active', false), 'user');\n * // => 'fred'\n *\n * // using the `_.property` callback shorthand\n * _.result(_.find(users, 'active'), 'user');\n * // => 'barney'\n */\nvar find = createFind(baseEach);\n\nmodule.exports = find;\n\n},{\"../internal/baseEach\":71,\"../internal/createFind\":102}],54:[function(require,module,exports){\nvar arrayEach = require('../internal/arrayEach'),\n    baseEach = require('../internal/baseEach'),\n    createForEach = require('../internal/createForEach');\n\n/**\n * Iterates over elements of `collection` invoking `iteratee` for each element.\n * The `iteratee` is bound to `thisArg` and invoked with three arguments:\n * (value, index|key, collection). Iteratee functions may exit iteration early\n * by explicitly returning `false`.\n *\n * **Note:** As with other \"Collections\" methods, objects with a \"length\" property\n * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`\n * may be used for object iteration.\n *\n * @static\n * @memberOf _\n * @alias each\n * @category Collection\n * @param {Array|Object|string} collection The collection to iterate over.\n * @param {Function} [iteratee=_.identity] The function invoked per iteration.\n * @param {*} [thisArg] The `this` binding of `iteratee`.\n * @returns {Array|Object|string} Returns `collection`.\n * @example\n *\n * _([1, 2]).forEach(function(n) {\n *   console.log(n);\n * }).value();\n * // => logs each value from left to right and returns the array\n *\n * _.forEach({ 'a': 1, 'b': 2 }, function(n, key) {\n *   console.log(n, key);\n * });\n * // => logs each value-key pair and returns the object (iteration order is not guaranteed)\n */\nvar forEach = createForEach(arrayEach, baseEach);\n\nmodule.exports = forEach;\n\n},{\"../internal/arrayEach\":63,\"../internal/baseEach\":71,\"../internal/createForEach\":103}],55:[function(require,module,exports){\nvar baseIndexOf = require('../internal/baseIndexOf'),\n    getLength = require('../internal/getLength'),\n    isArray = require('../lang/isArray'),\n    isIterateeCall = require('../internal/isIterateeCall'),\n    isLength = require('../internal/isLength'),\n    isString = require('../lang/isString'),\n    values = require('../object/values');\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max;\n\n/**\n * Checks if `target` is in `collection` using\n * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * for equality comparisons. If `fromIndex` is negative, it's used as the offset\n * from the end of `collection`.\n *\n * @static\n * @memberOf _\n * @alias contains, include\n * @category Collection\n * @param {Array|Object|string} collection The collection to search.\n * @param {*} target The value to search for.\n * @param {number} [fromIndex=0] The index to search from.\n * @param- {Object} [guard] Enables use as a callback for functions like `_.reduce`.\n * @returns {boolean} Returns `true` if a matching element is found, else `false`.\n * @example\n *\n * _.includes([1, 2, 3], 1);\n * // => true\n *\n * _.includes([1, 2, 3], 1, 2);\n * // => false\n *\n * _.includes({ 'user': 'fred', 'age': 40 }, 'fred');\n * // => true\n *\n * _.includes('pebbles', 'eb');\n * // => true\n */\nfunction includes(collection, target, fromIndex, guard) {\n  var length = collection ? getLength(collection) : 0;\n  if (!isLength(length)) {\n    collection = values(collection);\n    length = collection.length;\n  }\n  if (typeof fromIndex != 'number' || (guard && isIterateeCall(target, fromIndex, guard))) {\n    fromIndex = 0;\n  } else {\n    fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0);\n  }\n  return (typeof collection == 'string' || !isArray(collection) && isString(collection))\n    ? (fromIndex <= length && collection.indexOf(target, fromIndex) > -1)\n    : (!!length && baseIndexOf(collection, target, fromIndex) > -1);\n}\n\nmodule.exports = includes;\n\n},{\"../internal/baseIndexOf\":78,\"../internal/getLength\":112,\"../internal/isIterateeCall\":122,\"../internal/isLength\":125,\"../lang/isArray\":140,\"../lang/isString\":146,\"../object/values\":152}],56:[function(require,module,exports){\nvar arrayMap = require('../internal/arrayMap'),\n    baseCallback = require('../internal/baseCallback'),\n    baseMap = require('../internal/baseMap'),\n    isArray = require('../lang/isArray');\n\n/**\n * Creates an array of values by running each element in `collection` through\n * `iteratee`. The `iteratee` is bound to `thisArg` and invoked with three\n * arguments: (value, index|key, collection).\n *\n * If a property name is provided for `iteratee` the created `_.property`\n * style callback returns the property value of the given element.\n *\n * If a value is also provided for `thisArg` the created `_.matchesProperty`\n * style callback returns `true` for elements that have a matching property\n * value, else `false`.\n *\n * If an object is provided for `iteratee` the created `_.matches` style\n * callback returns `true` for elements that have the properties of the given\n * object, else `false`.\n *\n * Many lodash methods are guarded to work as iteratees for methods like\n * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.\n *\n * The guarded methods are:\n * `ary`, `callback`, `chunk`, `clone`, `create`, `curry`, `curryRight`,\n * `drop`, `dropRight`, `every`, `fill`, `flatten`, `invert`, `max`, `min`,\n * `parseInt`, `slice`, `sortBy`, `take`, `takeRight`, `template`, `trim`,\n * `trimLeft`, `trimRight`, `trunc`, `random`, `range`, `sample`, `some`,\n * `sum`, `uniq`, and `words`\n *\n * @static\n * @memberOf _\n * @alias collect\n * @category Collection\n * @param {Array|Object|string} collection The collection to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The function invoked\n *  per iteration.\n * @param {*} [thisArg] The `this` binding of `iteratee`.\n * @returns {Array} Returns the new mapped array.\n * @example\n *\n * function timesThree(n) {\n *   return n * 3;\n * }\n *\n * _.map([1, 2], timesThree);\n * // => [3, 6]\n *\n * _.map({ 'a': 1, 'b': 2 }, timesThree);\n * // => [3, 6] (iteration order is not guaranteed)\n *\n * var users = [\n *   { 'user': 'barney' },\n *   { 'user': 'fred' }\n * ];\n *\n * // using the `_.property` callback shorthand\n * _.map(users, 'user');\n * // => ['barney', 'fred']\n */\nfunction map(collection, iteratee, thisArg) {\n  var func = isArray(collection) ? arrayMap : baseMap;\n  iteratee = baseCallback(iteratee, thisArg, 3);\n  return func(collection, iteratee);\n}\n\nmodule.exports = map;\n\n},{\"../internal/arrayMap\":64,\"../internal/baseCallback\":67,\"../internal/baseMap\":83,\"../lang/isArray\":140}],57:[function(require,module,exports){\nvar getNative = require('../internal/getNative');\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeNow = getNative(Date, 'now');\n\n/**\n * Gets the number of milliseconds that have elapsed since the Unix epoch\n * (1 January 1970 00:00:00 UTC).\n *\n * @static\n * @memberOf _\n * @category Date\n * @example\n *\n * _.defer(function(stamp) {\n *   console.log(_.now() - stamp);\n * }, _.now());\n * // => logs the number of milliseconds it took for the deferred function to be invoked\n */\nvar now = nativeNow || function() {\n  return new Date().getTime();\n};\n\nmodule.exports = now;\n\n},{\"../internal/getNative\":114}],58:[function(require,module,exports){\nvar createWrapper = require('../internal/createWrapper'),\n    replaceHolders = require('../internal/replaceHolders'),\n    restParam = require('./restParam');\n\n/** Used to compose bitmasks for wrapper metadata. */\nvar BIND_FLAG = 1,\n    PARTIAL_FLAG = 32;\n\n/**\n * Creates a function that invokes `func` with the `this` binding of `thisArg`\n * and prepends any additional `_.bind` arguments to those provided to the\n * bound function.\n *\n * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,\n * may be used as a placeholder for partially applied arguments.\n *\n * **Note:** Unlike native `Function#bind` this method does not set the \"length\"\n * property of bound functions.\n *\n * @static\n * @memberOf _\n * @category Function\n * @param {Function} func The function to bind.\n * @param {*} thisArg The `this` binding of `func`.\n * @param {...*} [partials] The arguments to be partially applied.\n * @returns {Function} Returns the new bound function.\n * @example\n *\n * var greet = function(greeting, punctuation) {\n *   return greeting + ' ' + this.user + punctuation;\n * };\n *\n * var object = { 'user': 'fred' };\n *\n * var bound = _.bind(greet, object, 'hi');\n * bound('!');\n * // => 'hi fred!'\n *\n * // using placeholders\n * var bound = _.bind(greet, object, _, '!');\n * bound('hi');\n * // => 'hi fred!'\n */\nvar bind = restParam(function(func, thisArg, partials) {\n  var bitmask = BIND_FLAG;\n  if (partials.length) {\n    var holders = replaceHolders(partials, bind.placeholder);\n    bitmask |= PARTIAL_FLAG;\n  }\n  return createWrapper(func, bitmask, thisArg, partials, holders);\n});\n\n// Assign default placeholders.\nbind.placeholder = {};\n\nmodule.exports = bind;\n\n},{\"../internal/createWrapper\":106,\"../internal/replaceHolders\":132,\"./restParam\":59}],59:[function(require,module,exports){\n/** Used as the `TypeError` message for \"Functions\" methods. */\nvar FUNC_ERROR_TEXT = 'Expected a function';\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max;\n\n/**\n * Creates a function that invokes `func` with the `this` binding of the\n * created function and arguments from `start` and beyond provided as an array.\n *\n * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/Web/JavaScript/Reference/Functions/rest_parameters).\n *\n * @static\n * @memberOf _\n * @category Function\n * @param {Function} func The function to apply a rest parameter to.\n * @param {number} [start=func.length-1] The start position of the rest parameter.\n * @returns {Function} Returns the new function.\n * @example\n *\n * var say = _.restParam(function(what, names) {\n *   return what + ' ' + _.initial(names).join(', ') +\n *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);\n * });\n *\n * say('hello', 'fred', 'barney', 'pebbles');\n * // => 'hello fred, barney, & pebbles'\n */\nfunction restParam(func, start) {\n  if (typeof func != 'function') {\n    throw new TypeError(FUNC_ERROR_TEXT);\n  }\n  start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0);\n  return function() {\n    var args = arguments,\n        index = -1,\n        length = nativeMax(args.length - start, 0),\n        rest = Array(length);\n\n    while (++index < length) {\n      rest[index] = args[start + index];\n    }\n    switch (start) {\n      case 0: return func.call(this, rest);\n      case 1: return func.call(this, args[0], rest);\n      case 2: return func.call(this, args[0], args[1], rest);\n    }\n    var otherArgs = Array(start + 1);\n    index = -1;\n    while (++index < start) {\n      otherArgs[index] = args[index];\n    }\n    otherArgs[start] = rest;\n    return func.apply(this, otherArgs);\n  };\n}\n\nmodule.exports = restParam;\n\n},{}],60:[function(require,module,exports){\nvar baseCreate = require('./baseCreate'),\n    baseLodash = require('./baseLodash');\n\n/** Used as references for `-Infinity` and `Infinity`. */\nvar POSITIVE_INFINITY = Number.POSITIVE_INFINITY;\n\n/**\n * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.\n *\n * @private\n * @param {*} value The value to wrap.\n */\nfunction LazyWrapper(value) {\n  this.__wrapped__ = value;\n  this.__actions__ = [];\n  this.__dir__ = 1;\n  this.__filtered__ = false;\n  this.__iteratees__ = [];\n  this.__takeCount__ = POSITIVE_INFINITY;\n  this.__views__ = [];\n}\n\nLazyWrapper.prototype = baseCreate(baseLodash.prototype);\nLazyWrapper.prototype.constructor = LazyWrapper;\n\nmodule.exports = LazyWrapper;\n\n},{\"./baseCreate\":70,\"./baseLodash\":82}],61:[function(require,module,exports){\nvar baseCreate = require('./baseCreate'),\n    baseLodash = require('./baseLodash');\n\n/**\n * The base constructor for creating `lodash` wrapper objects.\n *\n * @private\n * @param {*} value The value to wrap.\n * @param {boolean} [chainAll] Enable chaining for all wrapper methods.\n * @param {Array} [actions=[]] Actions to peform to resolve the unwrapped value.\n */\nfunction LodashWrapper(value, chainAll, actions) {\n  this.__wrapped__ = value;\n  this.__actions__ = actions || [];\n  this.__chain__ = !!chainAll;\n}\n\nLodashWrapper.prototype = baseCreate(baseLodash.prototype);\nLodashWrapper.prototype.constructor = LodashWrapper;\n\nmodule.exports = LodashWrapper;\n\n},{\"./baseCreate\":70,\"./baseLodash\":82}],62:[function(require,module,exports){\n/**\n * Copies the values of `source` to `array`.\n *\n * @private\n * @param {Array} source The array to copy values from.\n * @param {Array} [array=[]] The array to copy values to.\n * @returns {Array} Returns `array`.\n */\nfunction arrayCopy(source, array) {\n  var index = -1,\n      length = source.length;\n\n  array || (array = Array(length));\n  while (++index < length) {\n    array[index] = source[index];\n  }\n  return array;\n}\n\nmodule.exports = arrayCopy;\n\n},{}],63:[function(require,module,exports){\n/**\n * A specialized version of `_.forEach` for arrays without support for callback\n * shorthands and `this` binding.\n *\n * @private\n * @param {Array} array The array to iterate over.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Array} Returns `array`.\n */\nfunction arrayEach(array, iteratee) {\n  var index = -1,\n      length = array.length;\n\n  while (++index < length) {\n    if (iteratee(array[index], index, array) === false) {\n      break;\n    }\n  }\n  return array;\n}\n\nmodule.exports = arrayEach;\n\n},{}],64:[function(require,module,exports){\n/**\n * A specialized version of `_.map` for arrays without support for callback\n * shorthands and `this` binding.\n *\n * @private\n * @param {Array} array The array to iterate over.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Array} Returns the new mapped array.\n */\nfunction arrayMap(array, iteratee) {\n  var index = -1,\n      length = array.length,\n      result = Array(length);\n\n  while (++index < length) {\n    result[index] = iteratee(array[index], index, array);\n  }\n  return result;\n}\n\nmodule.exports = arrayMap;\n\n},{}],65:[function(require,module,exports){\n/**\n * A specialized version of `_.some` for arrays without support for callback\n * shorthands and `this` binding.\n *\n * @private\n * @param {Array} array The array to iterate over.\n * @param {Function} predicate The function invoked per iteration.\n * @returns {boolean} Returns `true` if any element passes the predicate check,\n *  else `false`.\n */\nfunction arraySome(array, predicate) {\n  var index = -1,\n      length = array.length;\n\n  while (++index < length) {\n    if (predicate(array[index], index, array)) {\n      return true;\n    }\n  }\n  return false;\n}\n\nmodule.exports = arraySome;\n\n},{}],66:[function(require,module,exports){\nvar baseCopy = require('./baseCopy'),\n    keys = require('../object/keys');\n\n/**\n * The base implementation of `_.assign` without support for argument juggling,\n * multiple sources, and `customizer` functions.\n *\n * @private\n * @param {Object} object The destination object.\n * @param {Object} source The source object.\n * @returns {Object} Returns `object`.\n */\nfunction baseAssign(object, source) {\n  return source == null\n    ? object\n    : baseCopy(source, keys(source), object);\n}\n\nmodule.exports = baseAssign;\n\n},{\"../object/keys\":149,\"./baseCopy\":69}],67:[function(require,module,exports){\nvar baseMatches = require('./baseMatches'),\n    baseMatchesProperty = require('./baseMatchesProperty'),\n    bindCallback = require('./bindCallback'),\n    identity = require('../utility/identity'),\n    property = require('../utility/property');\n\n/**\n * The base implementation of `_.callback` which supports specifying the\n * number of arguments to provide to `func`.\n *\n * @private\n * @param {*} [func=_.identity] The value to convert to a callback.\n * @param {*} [thisArg] The `this` binding of `func`.\n * @param {number} [argCount] The number of arguments to provide to `func`.\n * @returns {Function} Returns the callback.\n */\nfunction baseCallback(func, thisArg, argCount) {\n  var type = typeof func;\n  if (type == 'function') {\n    return thisArg === undefined\n      ? func\n      : bindCallback(func, thisArg, argCount);\n  }\n  if (func == null) {\n    return identity;\n  }\n  if (type == 'object') {\n    return baseMatches(func);\n  }\n  return thisArg === undefined\n    ? property(func)\n    : baseMatchesProperty(func, thisArg);\n}\n\nmodule.exports = baseCallback;\n\n},{\"../utility/identity\":154,\"../utility/property\":156,\"./baseMatches\":84,\"./baseMatchesProperty\":85,\"./bindCallback\":94}],68:[function(require,module,exports){\nvar arrayCopy = require('./arrayCopy'),\n    arrayEach = require('./arrayEach'),\n    baseAssign = require('./baseAssign'),\n    baseForOwn = require('./baseForOwn'),\n    initCloneArray = require('./initCloneArray'),\n    initCloneByTag = require('./initCloneByTag'),\n    initCloneObject = require('./initCloneObject'),\n    isArray = require('../lang/isArray'),\n    isHostObject = require('./isHostObject'),\n    isObject = require('../lang/isObject');\n\n/** `Object#toString` result references. */\nvar argsTag = '[object Arguments]',\n    arrayTag = '[object Array]',\n    boolTag = '[object Boolean]',\n    dateTag = '[object Date]',\n    errorTag = '[object Error]',\n    funcTag = '[object Function]',\n    mapTag = '[object Map]',\n    numberTag = '[object Number]',\n    objectTag = '[object Object]',\n    regexpTag = '[object RegExp]',\n    setTag = '[object Set]',\n    stringTag = '[object String]',\n    weakMapTag = '[object WeakMap]';\n\nvar arrayBufferTag = '[object ArrayBuffer]',\n    float32Tag = '[object Float32Array]',\n    float64Tag = '[object Float64Array]',\n    int8Tag = '[object Int8Array]',\n    int16Tag = '[object Int16Array]',\n    int32Tag = '[object Int32Array]',\n    uint8Tag = '[object Uint8Array]',\n    uint8ClampedTag = '[object Uint8ClampedArray]',\n    uint16Tag = '[object Uint16Array]',\n    uint32Tag = '[object Uint32Array]';\n\n/** Used to identify `toStringTag` values supported by `_.clone`. */\nvar cloneableTags = {};\ncloneableTags[argsTag] = cloneableTags[arrayTag] =\ncloneableTags[arrayBufferTag] = cloneableTags[boolTag] =\ncloneableTags[dateTag] = cloneableTags[float32Tag] =\ncloneableTags[float64Tag] = cloneableTags[int8Tag] =\ncloneableTags[int16Tag] = cloneableTags[int32Tag] =\ncloneableTags[numberTag] = cloneableTags[objectTag] =\ncloneableTags[regexpTag] = cloneableTags[stringTag] =\ncloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =\ncloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;\ncloneableTags[errorTag] = cloneableTags[funcTag] =\ncloneableTags[mapTag] = cloneableTags[setTag] =\ncloneableTags[weakMapTag] = false;\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/**\n * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objToString = objectProto.toString;\n\n/**\n * The base implementation of `_.clone` without support for argument juggling\n * and `this` binding `customizer` functions.\n *\n * @private\n * @param {*} value The value to clone.\n * @param {boolean} [isDeep] Specify a deep clone.\n * @param {Function} [customizer] The function to customize cloning values.\n * @param {string} [key] The key of `value`.\n * @param {Object} [object] The object `value` belongs to.\n * @param {Array} [stackA=[]] Tracks traversed source objects.\n * @param {Array} [stackB=[]] Associates clones with source counterparts.\n * @returns {*} Returns the cloned value.\n */\nfunction baseClone(value, isDeep, customizer, key, object, stackA, stackB) {\n  var result;\n  if (customizer) {\n    result = object ? customizer(value, key, object) : customizer(value);\n  }\n  if (result !== undefined) {\n    return result;\n  }\n  if (!isObject(value)) {\n    return value;\n  }\n  var isArr = isArray(value);\n  if (isArr) {\n    result = initCloneArray(value);\n    if (!isDeep) {\n      return arrayCopy(value, result);\n    }\n  } else {\n    var tag = objToString.call(value),\n        isFunc = tag == funcTag;\n\n    if (tag == objectTag || tag == argsTag || (isFunc && !object)) {\n      if (isHostObject(value)) {\n        return object ? value : {};\n      }\n      result = initCloneObject(isFunc ? {} : value);\n      if (!isDeep) {\n        return baseAssign(result, value);\n      }\n    } else {\n      return cloneableTags[tag]\n        ? initCloneByTag(value, tag, isDeep)\n        : (object ? value : {});\n    }\n  }\n  // Check for circular references and return its corresponding clone.\n  stackA || (stackA = []);\n  stackB || (stackB = []);\n\n  var length = stackA.length;\n  while (length--) {\n    if (stackA[length] == value) {\n      return stackB[length];\n    }\n  }\n  // Add the source value to the stack of traversed objects and associate it with its clone.\n  stackA.push(value);\n  stackB.push(result);\n\n  // Recursively populate clone (susceptible to call stack limits).\n  (isArr ? arrayEach : baseForOwn)(value, function(subValue, key) {\n    result[key] = baseClone(subValue, isDeep, customizer, key, value, stackA, stackB);\n  });\n  return result;\n}\n\nmodule.exports = baseClone;\n\n},{\"../lang/isArray\":140,\"../lang/isObject\":144,\"./arrayCopy\":62,\"./arrayEach\":63,\"./baseAssign\":66,\"./baseForOwn\":76,\"./initCloneArray\":116,\"./initCloneByTag\":117,\"./initCloneObject\":118,\"./isHostObject\":120}],69:[function(require,module,exports){\n/**\n * Copies properties of `source` to `object`.\n *\n * @private\n * @param {Object} source The object to copy properties from.\n * @param {Array} props The property names to copy.\n * @param {Object} [object={}] The object to copy properties to.\n * @returns {Object} Returns `object`.\n */\nfunction baseCopy(source, props, object) {\n  object || (object = {});\n\n  var index = -1,\n      length = props.length;\n\n  while (++index < length) {\n    var key = props[index];\n    object[key] = source[key];\n  }\n  return object;\n}\n\nmodule.exports = baseCopy;\n\n},{}],70:[function(require,module,exports){\nvar isObject = require('../lang/isObject');\n\n/**\n * The base implementation of `_.create` without support for assigning\n * properties to the created object.\n *\n * @private\n * @param {Object} prototype The object to inherit from.\n * @returns {Object} Returns the new object.\n */\nvar baseCreate = (function() {\n  function object() {}\n  return function(prototype) {\n    if (isObject(prototype)) {\n      object.prototype = prototype;\n      var result = new object;\n      object.prototype = undefined;\n    }\n    return result || {};\n  };\n}());\n\nmodule.exports = baseCreate;\n\n},{\"../lang/isObject\":144}],71:[function(require,module,exports){\nvar baseForOwn = require('./baseForOwn'),\n    createBaseEach = require('./createBaseEach');\n\n/**\n * The base implementation of `_.forEach` without support for callback\n * shorthands and `this` binding.\n *\n * @private\n * @param {Array|Object|string} collection The collection to iterate over.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Array|Object|string} Returns `collection`.\n */\nvar baseEach = createBaseEach(baseForOwn);\n\nmodule.exports = baseEach;\n\n},{\"./baseForOwn\":76,\"./createBaseEach\":98}],72:[function(require,module,exports){\n/**\n * The base implementation of `_.find`, `_.findLast`, `_.findKey`, and `_.findLastKey`,\n * without support for callback shorthands and `this` binding, which iterates\n * over `collection` using the provided `eachFunc`.\n *\n * @private\n * @param {Array|Object|string} collection The collection to search.\n * @param {Function} predicate The function invoked per iteration.\n * @param {Function} eachFunc The function to iterate over `collection`.\n * @param {boolean} [retKey] Specify returning the key of the found element\n *  instead of the element itself.\n * @returns {*} Returns the found element or its key, else `undefined`.\n */\nfunction baseFind(collection, predicate, eachFunc, retKey) {\n  var result;\n  eachFunc(collection, function(value, key, collection) {\n    if (predicate(value, key, collection)) {\n      result = retKey ? key : value;\n      return false;\n    }\n  });\n  return result;\n}\n\nmodule.exports = baseFind;\n\n},{}],73:[function(require,module,exports){\n/**\n * The base implementation of `_.findIndex` and `_.findLastIndex` without\n * support for callback shorthands and `this` binding.\n *\n * @private\n * @param {Array} array The array to search.\n * @param {Function} predicate The function invoked per iteration.\n * @param {boolean} [fromRight] Specify iterating from right to left.\n * @returns {number} Returns the index of the matched value, else `-1`.\n */\nfunction baseFindIndex(array, predicate, fromRight) {\n  var length = array.length,\n      index = fromRight ? length : -1;\n\n  while ((fromRight ? index-- : ++index < length)) {\n    if (predicate(array[index], index, array)) {\n      return index;\n    }\n  }\n  return -1;\n}\n\nmodule.exports = baseFindIndex;\n\n},{}],74:[function(require,module,exports){\nvar createBaseFor = require('./createBaseFor');\n\n/**\n * The base implementation of `baseForIn` and `baseForOwn` which iterates\n * over `object` properties returned by `keysFunc` invoking `iteratee` for\n * each property. Iteratee functions may exit iteration early by explicitly\n * returning `false`.\n *\n * @private\n * @param {Object} object The object to iterate over.\n * @param {Function} iteratee The function invoked per iteration.\n * @param {Function} keysFunc The function to get the keys of `object`.\n * @returns {Object} Returns `object`.\n */\nvar baseFor = createBaseFor();\n\nmodule.exports = baseFor;\n\n},{\"./createBaseFor\":99}],75:[function(require,module,exports){\nvar baseFor = require('./baseFor'),\n    keysIn = require('../object/keysIn');\n\n/**\n * The base implementation of `_.forIn` without support for callback\n * shorthands and `this` binding.\n *\n * @private\n * @param {Object} object The object to iterate over.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Object} Returns `object`.\n */\nfunction baseForIn(object, iteratee) {\n  return baseFor(object, iteratee, keysIn);\n}\n\nmodule.exports = baseForIn;\n\n},{\"../object/keysIn\":150,\"./baseFor\":74}],76:[function(require,module,exports){\nvar baseFor = require('./baseFor'),\n    keys = require('../object/keys');\n\n/**\n * The base implementation of `_.forOwn` without support for callback\n * shorthands and `this` binding.\n *\n * @private\n * @param {Object} object The object to iterate over.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Object} Returns `object`.\n */\nfunction baseForOwn(object, iteratee) {\n  return baseFor(object, iteratee, keys);\n}\n\nmodule.exports = baseForOwn;\n\n},{\"../object/keys\":149,\"./baseFor\":74}],77:[function(require,module,exports){\nvar toObject = require('./toObject');\n\n/**\n * The base implementation of `get` without support for string paths\n * and default values.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {Array} path The path of the property to get.\n * @param {string} [pathKey] The key representation of path.\n * @returns {*} Returns the resolved value.\n */\nfunction baseGet(object, path, pathKey) {\n  if (object == null) {\n    return;\n  }\n  object = toObject(object);\n  if (pathKey !== undefined && pathKey in object) {\n    path = [pathKey];\n  }\n  var index = 0,\n      length = path.length;\n\n  while (object != null && index < length) {\n    object = toObject(object)[path[index++]];\n  }\n  return (index && index == length) ? object : undefined;\n}\n\nmodule.exports = baseGet;\n\n},{\"./toObject\":135}],78:[function(require,module,exports){\nvar indexOfNaN = require('./indexOfNaN');\n\n/**\n * The base implementation of `_.indexOf` without support for binary searches.\n *\n * @private\n * @param {Array} array The array to search.\n * @param {*} value The value to search for.\n * @param {number} fromIndex The index to search from.\n * @returns {number} Returns the index of the matched value, else `-1`.\n */\nfunction baseIndexOf(array, value, fromIndex) {\n  if (value !== value) {\n    return indexOfNaN(array, fromIndex);\n  }\n  var index = fromIndex - 1,\n      length = array.length;\n\n  while (++index < length) {\n    if (array[index] === value) {\n      return index;\n    }\n  }\n  return -1;\n}\n\nmodule.exports = baseIndexOf;\n\n},{\"./indexOfNaN\":115}],79:[function(require,module,exports){\nvar baseIsEqualDeep = require('./baseIsEqualDeep'),\n    isObject = require('../lang/isObject'),\n    isObjectLike = require('./isObjectLike');\n\n/**\n * The base implementation of `_.isEqual` without support for `this` binding\n * `customizer` functions.\n *\n * @private\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @param {Function} [customizer] The function to customize comparing values.\n * @param {boolean} [isLoose] Specify performing partial comparisons.\n * @param {Array} [stackA] Tracks traversed `value` objects.\n * @param {Array} [stackB] Tracks traversed `other` objects.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n */\nfunction baseIsEqual(value, other, customizer, isLoose, stackA, stackB) {\n  if (value === other) {\n    return true;\n  }\n  if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {\n    return value !== value && other !== other;\n  }\n  return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB);\n}\n\nmodule.exports = baseIsEqual;\n\n},{\"../lang/isObject\":144,\"./baseIsEqualDeep\":80,\"./isObjectLike\":126}],80:[function(require,module,exports){\nvar equalArrays = require('./equalArrays'),\n    equalByTag = require('./equalByTag'),\n    equalObjects = require('./equalObjects'),\n    isArray = require('../lang/isArray'),\n    isHostObject = require('./isHostObject'),\n    isTypedArray = require('../lang/isTypedArray');\n\n/** `Object#toString` result references. */\nvar argsTag = '[object Arguments]',\n    arrayTag = '[object Array]',\n    objectTag = '[object Object]';\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objToString = objectProto.toString;\n\n/**\n * A specialized version of `baseIsEqual` for arrays and objects which performs\n * deep comparisons and tracks traversed objects enabling objects with circular\n * references to be compared.\n *\n * @private\n * @param {Object} object The object to compare.\n * @param {Object} other The other object to compare.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Function} [customizer] The function to customize comparing objects.\n * @param {boolean} [isLoose] Specify performing partial comparisons.\n * @param {Array} [stackA=[]] Tracks traversed `value` objects.\n * @param {Array} [stackB=[]] Tracks traversed `other` objects.\n * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.\n */\nfunction baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) {\n  var objIsArr = isArray(object),\n      othIsArr = isArray(other),\n      objTag = arrayTag,\n      othTag = arrayTag;\n\n  if (!objIsArr) {\n    objTag = objToString.call(object);\n    if (objTag == argsTag) {\n      objTag = objectTag;\n    } else if (objTag != objectTag) {\n      objIsArr = isTypedArray(object);\n    }\n  }\n  if (!othIsArr) {\n    othTag = objToString.call(other);\n    if (othTag == argsTag) {\n      othTag = objectTag;\n    } else if (othTag != objectTag) {\n      othIsArr = isTypedArray(other);\n    }\n  }\n  var objIsObj = objTag == objectTag && !isHostObject(object),\n      othIsObj = othTag == objectTag && !isHostObject(other),\n      isSameTag = objTag == othTag;\n\n  if (isSameTag && !(objIsArr || objIsObj)) {\n    return equalByTag(object, other, objTag);\n  }\n  if (!isLoose) {\n    var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),\n        othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');\n\n    if (objIsWrapped || othIsWrapped) {\n      return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, isLoose, stackA, stackB);\n    }\n  }\n  if (!isSameTag) {\n    return false;\n  }\n  // Assume cyclic values are equal.\n  // For more information on detecting circular references see https://es5.github.io/#JO.\n  stackA || (stackA = []);\n  stackB || (stackB = []);\n\n  var length = stackA.length;\n  while (length--) {\n    if (stackA[length] == object) {\n      return stackB[length] == other;\n    }\n  }\n  // Add `object` and `other` to the stack of traversed objects.\n  stackA.push(object);\n  stackB.push(other);\n\n  var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB);\n\n  stackA.pop();\n  stackB.pop();\n\n  return result;\n}\n\nmodule.exports = baseIsEqualDeep;\n\n},{\"../lang/isArray\":140,\"../lang/isTypedArray\":147,\"./equalArrays\":107,\"./equalByTag\":108,\"./equalObjects\":109,\"./isHostObject\":120}],81:[function(require,module,exports){\nvar baseIsEqual = require('./baseIsEqual'),\n    toObject = require('./toObject');\n\n/**\n * The base implementation of `_.isMatch` without support for callback\n * shorthands and `this` binding.\n *\n * @private\n * @param {Object} object The object to inspect.\n * @param {Array} matchData The propery names, values, and compare flags to match.\n * @param {Function} [customizer] The function to customize comparing objects.\n * @returns {boolean} Returns `true` if `object` is a match, else `false`.\n */\nfunction baseIsMatch(object, matchData, customizer) {\n  var index = matchData.length,\n      length = index,\n      noCustomizer = !customizer;\n\n  if (object == null) {\n    return !length;\n  }\n  object = toObject(object);\n  while (index--) {\n    var data = matchData[index];\n    if ((noCustomizer && data[2])\n          ? data[1] !== object[data[0]]\n          : !(data[0] in object)\n        ) {\n      return false;\n    }\n  }\n  while (++index < length) {\n    data = matchData[index];\n    var key = data[0],\n        objValue = object[key],\n        srcValue = data[1];\n\n    if (noCustomizer && data[2]) {\n      if (objValue === undefined && !(key in object)) {\n        return false;\n      }\n    } else {\n      var result = customizer ? customizer(objValue, srcValue, key) : undefined;\n      if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, true) : result)) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\nmodule.exports = baseIsMatch;\n\n},{\"./baseIsEqual\":79,\"./toObject\":135}],82:[function(require,module,exports){\n/**\n * The function whose prototype all chaining wrappers inherit from.\n *\n * @private\n */\nfunction baseLodash() {\n  // No operation performed.\n}\n\nmodule.exports = baseLodash;\n\n},{}],83:[function(require,module,exports){\nvar baseEach = require('./baseEach'),\n    isArrayLike = require('./isArrayLike');\n\n/**\n * The base implementation of `_.map` without support for callback shorthands\n * and `this` binding.\n *\n * @private\n * @param {Array|Object|string} collection The collection to iterate over.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Array} Returns the new mapped array.\n */\nfunction baseMap(collection, iteratee) {\n  var index = -1,\n      result = isArrayLike(collection) ? Array(collection.length) : [];\n\n  baseEach(collection, function(value, key, collection) {\n    result[++index] = iteratee(value, key, collection);\n  });\n  return result;\n}\n\nmodule.exports = baseMap;\n\n},{\"./baseEach\":71,\"./isArrayLike\":119}],84:[function(require,module,exports){\nvar baseIsMatch = require('./baseIsMatch'),\n    getMatchData = require('./getMatchData'),\n    toObject = require('./toObject');\n\n/**\n * The base implementation of `_.matches` which does not clone `source`.\n *\n * @private\n * @param {Object} source The object of property values to match.\n * @returns {Function} Returns the new function.\n */\nfunction baseMatches(source) {\n  var matchData = getMatchData(source);\n  if (matchData.length == 1 && matchData[0][2]) {\n    var key = matchData[0][0],\n        value = matchData[0][1];\n\n    return function(object) {\n      if (object == null) {\n        return false;\n      }\n      object = toObject(object);\n      return object[key] === value && (value !== undefined || (key in object));\n    };\n  }\n  return function(object) {\n    return baseIsMatch(object, matchData);\n  };\n}\n\nmodule.exports = baseMatches;\n\n},{\"./baseIsMatch\":81,\"./getMatchData\":113,\"./toObject\":135}],85:[function(require,module,exports){\nvar baseGet = require('./baseGet'),\n    baseIsEqual = require('./baseIsEqual'),\n    baseSlice = require('./baseSlice'),\n    isArray = require('../lang/isArray'),\n    isKey = require('./isKey'),\n    isStrictComparable = require('./isStrictComparable'),\n    last = require('../array/last'),\n    toObject = require('./toObject'),\n    toPath = require('./toPath');\n\n/**\n * The base implementation of `_.matchesProperty` which does not clone `srcValue`.\n *\n * @private\n * @param {string} path The path of the property to get.\n * @param {*} srcValue The value to compare.\n * @returns {Function} Returns the new function.\n */\nfunction baseMatchesProperty(path, srcValue) {\n  var isArr = isArray(path),\n      isCommon = isKey(path) && isStrictComparable(srcValue),\n      pathKey = (path + '');\n\n  path = toPath(path);\n  return function(object) {\n    if (object == null) {\n      return false;\n    }\n    var key = pathKey;\n    object = toObject(object);\n    if ((isArr || !isCommon) && !(key in object)) {\n      object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));\n      if (object == null) {\n        return false;\n      }\n      key = last(path);\n      object = toObject(object);\n    }\n    return object[key] === srcValue\n      ? (srcValue !== undefined || (key in object))\n      : baseIsEqual(srcValue, object[key], undefined, true);\n  };\n}\n\nmodule.exports = baseMatchesProperty;\n\n},{\"../array/last\":50,\"../lang/isArray\":140,\"./baseGet\":77,\"./baseIsEqual\":79,\"./baseSlice\":89,\"./isKey\":123,\"./isStrictComparable\":127,\"./toObject\":135,\"./toPath\":136}],86:[function(require,module,exports){\nvar toObject = require('./toObject');\n\n/**\n * The base implementation of `_.property` without support for deep paths.\n *\n * @private\n * @param {string} key The key of the property to get.\n * @returns {Function} Returns the new function.\n */\nfunction baseProperty(key) {\n  return function(object) {\n    return object == null ? undefined : toObject(object)[key];\n  };\n}\n\nmodule.exports = baseProperty;\n\n},{\"./toObject\":135}],87:[function(require,module,exports){\nvar baseGet = require('./baseGet'),\n    toPath = require('./toPath');\n\n/**\n * A specialized version of `baseProperty` which supports deep paths.\n *\n * @private\n * @param {Array|string} path The path of the property to get.\n * @returns {Function} Returns the new function.\n */\nfunction basePropertyDeep(path) {\n  var pathKey = (path + '');\n  path = toPath(path);\n  return function(object) {\n    return baseGet(object, path, pathKey);\n  };\n}\n\nmodule.exports = basePropertyDeep;\n\n},{\"./baseGet\":77,\"./toPath\":136}],88:[function(require,module,exports){\nvar identity = require('../utility/identity'),\n    metaMap = require('./metaMap');\n\n/**\n * The base implementation of `setData` without support for hot loop detection.\n *\n * @private\n * @param {Function} func The function to associate metadata with.\n * @param {*} data The metadata.\n * @returns {Function} Returns `func`.\n */\nvar baseSetData = !metaMap ? identity : function(func, data) {\n  metaMap.set(func, data);\n  return func;\n};\n\nmodule.exports = baseSetData;\n\n},{\"../utility/identity\":154,\"./metaMap\":129}],89:[function(require,module,exports){\n/**\n * The base implementation of `_.slice` without an iteratee call guard.\n *\n * @private\n * @param {Array} array The array to slice.\n * @param {number} [start=0] The start position.\n * @param {number} [end=array.length] The end position.\n * @returns {Array} Returns the slice of `array`.\n */\nfunction baseSlice(array, start, end) {\n  var index = -1,\n      length = array.length;\n\n  start = start == null ? 0 : (+start || 0);\n  if (start < 0) {\n    start = -start > length ? 0 : (length + start);\n  }\n  end = (end === undefined || end > length) ? length : (+end || 0);\n  if (end < 0) {\n    end += length;\n  }\n  length = start > end ? 0 : ((end - start) >>> 0);\n  start >>>= 0;\n\n  var result = Array(length);\n  while (++index < length) {\n    result[index] = array[index + start];\n  }\n  return result;\n}\n\nmodule.exports = baseSlice;\n\n},{}],90:[function(require,module,exports){\n/**\n * Converts `value` to a string if it's not one. An empty string is returned\n * for `null` or `undefined` values.\n *\n * @private\n * @param {*} value The value to process.\n * @returns {string} Returns the string.\n */\nfunction baseToString(value) {\n  return value == null ? '' : (value + '');\n}\n\nmodule.exports = baseToString;\n\n},{}],91:[function(require,module,exports){\n/**\n * The base implementation of `_.values` and `_.valuesIn` which creates an\n * array of `object` property values corresponding to the property names\n * of `props`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {Array} props The property names to get values for.\n * @returns {Object} Returns the array of property values.\n */\nfunction baseValues(object, props) {\n  var index = -1,\n      length = props.length,\n      result = Array(length);\n\n  while (++index < length) {\n    result[index] = object[props[index]];\n  }\n  return result;\n}\n\nmodule.exports = baseValues;\n\n},{}],92:[function(require,module,exports){\nvar binaryIndexBy = require('./binaryIndexBy'),\n    identity = require('../utility/identity');\n\n/** Used as references for the maximum length and index of an array. */\nvar MAX_ARRAY_LENGTH = 4294967295,\n    HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;\n\n/**\n * Performs a binary search of `array` to determine the index at which `value`\n * should be inserted into `array` in order to maintain its sort order.\n *\n * @private\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @param {boolean} [retHighest] Specify returning the highest qualified index.\n * @returns {number} Returns the index at which `value` should be inserted\n *  into `array`.\n */\nfunction binaryIndex(array, value, retHighest) {\n  var low = 0,\n      high = array ? array.length : low;\n\n  if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {\n    while (low < high) {\n      var mid = (low + high) >>> 1,\n          computed = array[mid];\n\n      if ((retHighest ? (computed <= value) : (computed < value)) && computed !== null) {\n        low = mid + 1;\n      } else {\n        high = mid;\n      }\n    }\n    return high;\n  }\n  return binaryIndexBy(array, value, identity, retHighest);\n}\n\nmodule.exports = binaryIndex;\n\n},{\"../utility/identity\":154,\"./binaryIndexBy\":93}],93:[function(require,module,exports){\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeFloor = Math.floor,\n    nativeMin = Math.min;\n\n/** Used as references for the maximum length and index of an array. */\nvar MAX_ARRAY_LENGTH = 4294967295,\n    MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1;\n\n/**\n * This function is like `binaryIndex` except that it invokes `iteratee` for\n * `value` and each element of `array` to compute their sort ranking. The\n * iteratee is invoked with one argument; (value).\n *\n * @private\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @param {Function} iteratee The function invoked per iteration.\n * @param {boolean} [retHighest] Specify returning the highest qualified index.\n * @returns {number} Returns the index at which `value` should be inserted\n *  into `array`.\n */\nfunction binaryIndexBy(array, value, iteratee, retHighest) {\n  value = iteratee(value);\n\n  var low = 0,\n      high = array ? array.length : 0,\n      valIsNaN = value !== value,\n      valIsNull = value === null,\n      valIsUndef = value === undefined;\n\n  while (low < high) {\n    var mid = nativeFloor((low + high) / 2),\n        computed = iteratee(array[mid]),\n        isDef = computed !== undefined,\n        isReflexive = computed === computed;\n\n    if (valIsNaN) {\n      var setLow = isReflexive || retHighest;\n    } else if (valIsNull) {\n      setLow = isReflexive && isDef && (retHighest || computed != null);\n    } else if (valIsUndef) {\n      setLow = isReflexive && (retHighest || isDef);\n    } else if (computed == null) {\n      setLow = false;\n    } else {\n      setLow = retHighest ? (computed <= value) : (computed < value);\n    }\n    if (setLow) {\n      low = mid + 1;\n    } else {\n      high = mid;\n    }\n  }\n  return nativeMin(high, MAX_ARRAY_INDEX);\n}\n\nmodule.exports = binaryIndexBy;\n\n},{}],94:[function(require,module,exports){\nvar identity = require('../utility/identity');\n\n/**\n * A specialized version of `baseCallback` which only supports `this` binding\n * and specifying the number of arguments to provide to `func`.\n *\n * @private\n * @param {Function} func The function to bind.\n * @param {*} thisArg The `this` binding of `func`.\n * @param {number} [argCount] The number of arguments to provide to `func`.\n * @returns {Function} Returns the callback.\n */\nfunction bindCallback(func, thisArg, argCount) {\n  if (typeof func != 'function') {\n    return identity;\n  }\n  if (thisArg === undefined) {\n    return func;\n  }\n  switch (argCount) {\n    case 1: return function(value) {\n      return func.call(thisArg, value);\n    };\n    case 3: return function(value, index, collection) {\n      return func.call(thisArg, value, index, collection);\n    };\n    case 4: return function(accumulator, value, index, collection) {\n      return func.call(thisArg, accumulator, value, index, collection);\n    };\n    case 5: return function(value, other, key, object, source) {\n      return func.call(thisArg, value, other, key, object, source);\n    };\n  }\n  return function() {\n    return func.apply(thisArg, arguments);\n  };\n}\n\nmodule.exports = bindCallback;\n\n},{\"../utility/identity\":154}],95:[function(require,module,exports){\n(function (global){\n/** Native method references. */\nvar ArrayBuffer = global.ArrayBuffer,\n    Uint8Array = global.Uint8Array;\n\n/**\n * Creates a clone of the given array buffer.\n *\n * @private\n * @param {ArrayBuffer} buffer The array buffer to clone.\n * @returns {ArrayBuffer} Returns the cloned array buffer.\n */\nfunction bufferClone(buffer) {\n  var result = new ArrayBuffer(buffer.byteLength),\n      view = new Uint8Array(result);\n\n  view.set(new Uint8Array(buffer));\n  return result;\n}\n\nmodule.exports = bufferClone;\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2J1ZmZlckNsb25lLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIi8qKiBOYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgQXJyYXlCdWZmZXIgPSBnbG9iYWwuQXJyYXlCdWZmZXIsXG4gICAgVWludDhBcnJheSA9IGdsb2JhbC5VaW50OEFycmF5O1xuXG4vKipcbiAqIENyZWF0ZXMgYSBjbG9uZSBvZiB0aGUgZ2l2ZW4gYXJyYXkgYnVmZmVyLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5QnVmZmVyfSBidWZmZXIgVGhlIGFycmF5IGJ1ZmZlciB0byBjbG9uZS5cbiAqIEByZXR1cm5zIHtBcnJheUJ1ZmZlcn0gUmV0dXJucyB0aGUgY2xvbmVkIGFycmF5IGJ1ZmZlci5cbiAqL1xuZnVuY3Rpb24gYnVmZmVyQ2xvbmUoYnVmZmVyKSB7XG4gIHZhciByZXN1bHQgPSBuZXcgQXJyYXlCdWZmZXIoYnVmZmVyLmJ5dGVMZW5ndGgpLFxuICAgICAgdmlldyA9IG5ldyBVaW50OEFycmF5KHJlc3VsdCk7XG5cbiAgdmlldy5zZXQobmV3IFVpbnQ4QXJyYXkoYnVmZmVyKSk7XG4gIHJldHVybiByZXN1bHQ7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYnVmZmVyQ2xvbmU7XG4iXX0=\n},{}],96:[function(require,module,exports){\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max;\n\n/**\n * Creates an array that is the composition of partially applied arguments,\n * placeholders, and provided arguments into a single array of arguments.\n *\n * @private\n * @param {Array|Object} args The provided arguments.\n * @param {Array} partials The arguments to prepend to those provided.\n * @param {Array} holders The `partials` placeholder indexes.\n * @returns {Array} Returns the new array of composed arguments.\n */\nfunction composeArgs(args, partials, holders) {\n  var holdersLength = holders.length,\n      argsIndex = -1,\n      argsLength = nativeMax(args.length - holdersLength, 0),\n      leftIndex = -1,\n      leftLength = partials.length,\n      result = Array(leftLength + argsLength);\n\n  while (++leftIndex < leftLength) {\n    result[leftIndex] = partials[leftIndex];\n  }\n  while (++argsIndex < holdersLength) {\n    result[holders[argsIndex]] = args[argsIndex];\n  }\n  while (argsLength--) {\n    result[leftIndex++] = args[argsIndex++];\n  }\n  return result;\n}\n\nmodule.exports = composeArgs;\n\n},{}],97:[function(require,module,exports){\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max;\n\n/**\n * This function is like `composeArgs` except that the arguments composition\n * is tailored for `_.partialRight`.\n *\n * @private\n * @param {Array|Object} args The provided arguments.\n * @param {Array} partials The arguments to append to those provided.\n * @param {Array} holders The `partials` placeholder indexes.\n * @returns {Array} Returns the new array of composed arguments.\n */\nfunction composeArgsRight(args, partials, holders) {\n  var holdersIndex = -1,\n      holdersLength = holders.length,\n      argsIndex = -1,\n      argsLength = nativeMax(args.length - holdersLength, 0),\n      rightIndex = -1,\n      rightLength = partials.length,\n      result = Array(argsLength + rightLength);\n\n  while (++argsIndex < argsLength) {\n    result[argsIndex] = args[argsIndex];\n  }\n  var offset = argsIndex;\n  while (++rightIndex < rightLength) {\n    result[offset + rightIndex] = partials[rightIndex];\n  }\n  while (++holdersIndex < holdersLength) {\n    result[offset + holders[holdersIndex]] = args[argsIndex++];\n  }\n  return result;\n}\n\nmodule.exports = composeArgsRight;\n\n},{}],98:[function(require,module,exports){\nvar getLength = require('./getLength'),\n    isLength = require('./isLength'),\n    toObject = require('./toObject');\n\n/**\n * Creates a `baseEach` or `baseEachRight` function.\n *\n * @private\n * @param {Function} eachFunc The function to iterate over a collection.\n * @param {boolean} [fromRight] Specify iterating from right to left.\n * @returns {Function} Returns the new base function.\n */\nfunction createBaseEach(eachFunc, fromRight) {\n  return function(collection, iteratee) {\n    var length = collection ? getLength(collection) : 0;\n    if (!isLength(length)) {\n      return eachFunc(collection, iteratee);\n    }\n    var index = fromRight ? length : -1,\n        iterable = toObject(collection);\n\n    while ((fromRight ? index-- : ++index < length)) {\n      if (iteratee(iterable[index], index, iterable) === false) {\n        break;\n      }\n    }\n    return collection;\n  };\n}\n\nmodule.exports = createBaseEach;\n\n},{\"./getLength\":112,\"./isLength\":125,\"./toObject\":135}],99:[function(require,module,exports){\nvar toObject = require('./toObject');\n\n/**\n * Creates a base function for `_.forIn` or `_.forInRight`.\n *\n * @private\n * @param {boolean} [fromRight] Specify iterating from right to left.\n * @returns {Function} Returns the new base function.\n */\nfunction createBaseFor(fromRight) {\n  return function(object, iteratee, keysFunc) {\n    var iterable = toObject(object),\n        props = keysFunc(object),\n        length = props.length,\n        index = fromRight ? length : -1;\n\n    while ((fromRight ? index-- : ++index < length)) {\n      var key = props[index];\n      if (iteratee(iterable[key], key, iterable) === false) {\n        break;\n      }\n    }\n    return object;\n  };\n}\n\nmodule.exports = createBaseFor;\n\n},{\"./toObject\":135}],100:[function(require,module,exports){\n(function (global){\nvar createCtorWrapper = require('./createCtorWrapper');\n\n/**\n * Creates a function that wraps `func` and invokes it with the `this`\n * binding of `thisArg`.\n *\n * @private\n * @param {Function} func The function to bind.\n * @param {*} [thisArg] The `this` binding of `func`.\n * @returns {Function} Returns the new bound function.\n */\nfunction createBindWrapper(func, thisArg) {\n  var Ctor = createCtorWrapper(func);\n\n  function wrapper() {\n    var fn = (this && this !== global && this instanceof wrapper) ? Ctor : func;\n    return fn.apply(thisArg, arguments);\n  }\n  return wrapper;\n}\n\nmodule.exports = createBindWrapper;\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2NyZWF0ZUJpbmRXcmFwcGVyLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgY3JlYXRlQ3RvcldyYXBwZXIgPSByZXF1aXJlKCcuL2NyZWF0ZUN0b3JXcmFwcGVyJyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGZ1bmN0aW9uIHRoYXQgd3JhcHMgYGZ1bmNgIGFuZCBpbnZva2VzIGl0IHdpdGggdGhlIGB0aGlzYFxuICogYmluZGluZyBvZiBgdGhpc0FyZ2AuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGJpbmQuXG4gKiBAcGFyYW0geyp9IFt0aGlzQXJnXSBUaGUgYHRoaXNgIGJpbmRpbmcgb2YgYGZ1bmNgLlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgYm91bmQgZnVuY3Rpb24uXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZUJpbmRXcmFwcGVyKGZ1bmMsIHRoaXNBcmcpIHtcbiAgdmFyIEN0b3IgPSBjcmVhdGVDdG9yV3JhcHBlcihmdW5jKTtcblxuICBmdW5jdGlvbiB3cmFwcGVyKCkge1xuICAgIHZhciBmbiA9ICh0aGlzICYmIHRoaXMgIT09IGdsb2JhbCAmJiB0aGlzIGluc3RhbmNlb2Ygd3JhcHBlcikgPyBDdG9yIDogZnVuYztcbiAgICByZXR1cm4gZm4uYXBwbHkodGhpc0FyZywgYXJndW1lbnRzKTtcbiAgfVxuICByZXR1cm4gd3JhcHBlcjtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBjcmVhdGVCaW5kV3JhcHBlcjtcbiJdfQ==\n},{\"./createCtorWrapper\":101}],101:[function(require,module,exports){\nvar baseCreate = require('./baseCreate'),\n    isObject = require('../lang/isObject');\n\n/**\n * Creates a function that produces an instance of `Ctor` regardless of\n * whether it was invoked as part of a `new` expression or by `call` or `apply`.\n *\n * @private\n * @param {Function} Ctor The constructor to wrap.\n * @returns {Function} Returns the new wrapped function.\n */\nfunction createCtorWrapper(Ctor) {\n  return function() {\n    // Use a `switch` statement to work with class constructors.\n    // See http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist\n    // for more details.\n    var args = arguments;\n    switch (args.length) {\n      case 0: return new Ctor;\n      case 1: return new Ctor(args[0]);\n      case 2: return new Ctor(args[0], args[1]);\n      case 3: return new Ctor(args[0], args[1], args[2]);\n      case 4: return new Ctor(args[0], args[1], args[2], args[3]);\n      case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);\n      case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);\n      case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);\n    }\n    var thisBinding = baseCreate(Ctor.prototype),\n        result = Ctor.apply(thisBinding, args);\n\n    // Mimic the constructor's `return` behavior.\n    // See https://es5.github.io/#x13.2.2 for more details.\n    return isObject(result) ? result : thisBinding;\n  };\n}\n\nmodule.exports = createCtorWrapper;\n\n},{\"../lang/isObject\":144,\"./baseCreate\":70}],102:[function(require,module,exports){\nvar baseCallback = require('./baseCallback'),\n    baseFind = require('./baseFind'),\n    baseFindIndex = require('./baseFindIndex'),\n    isArray = require('../lang/isArray');\n\n/**\n * Creates a `_.find` or `_.findLast` function.\n *\n * @private\n * @param {Function} eachFunc The function to iterate over a collection.\n * @param {boolean} [fromRight] Specify iterating from right to left.\n * @returns {Function} Returns the new find function.\n */\nfunction createFind(eachFunc, fromRight) {\n  return function(collection, predicate, thisArg) {\n    predicate = baseCallback(predicate, thisArg, 3);\n    if (isArray(collection)) {\n      var index = baseFindIndex(collection, predicate, fromRight);\n      return index > -1 ? collection[index] : undefined;\n    }\n    return baseFind(collection, predicate, eachFunc);\n  };\n}\n\nmodule.exports = createFind;\n\n},{\"../lang/isArray\":140,\"./baseCallback\":67,\"./baseFind\":72,\"./baseFindIndex\":73}],103:[function(require,module,exports){\nvar bindCallback = require('./bindCallback'),\n    isArray = require('../lang/isArray');\n\n/**\n * Creates a function for `_.forEach` or `_.forEachRight`.\n *\n * @private\n * @param {Function} arrayFunc The function to iterate over an array.\n * @param {Function} eachFunc The function to iterate over a collection.\n * @returns {Function} Returns the new each function.\n */\nfunction createForEach(arrayFunc, eachFunc) {\n  return function(collection, iteratee, thisArg) {\n    return (typeof iteratee == 'function' && thisArg === undefined && isArray(collection))\n      ? arrayFunc(collection, iteratee)\n      : eachFunc(collection, bindCallback(iteratee, thisArg, 3));\n  };\n}\n\nmodule.exports = createForEach;\n\n},{\"../lang/isArray\":140,\"./bindCallback\":94}],104:[function(require,module,exports){\n(function (global){\nvar arrayCopy = require('./arrayCopy'),\n    composeArgs = require('./composeArgs'),\n    composeArgsRight = require('./composeArgsRight'),\n    createCtorWrapper = require('./createCtorWrapper'),\n    isLaziable = require('./isLaziable'),\n    reorder = require('./reorder'),\n    replaceHolders = require('./replaceHolders'),\n    setData = require('./setData');\n\n/** Used to compose bitmasks for wrapper metadata. */\nvar BIND_FLAG = 1,\n    BIND_KEY_FLAG = 2,\n    CURRY_BOUND_FLAG = 4,\n    CURRY_FLAG = 8,\n    CURRY_RIGHT_FLAG = 16,\n    PARTIAL_FLAG = 32,\n    PARTIAL_RIGHT_FLAG = 64,\n    ARY_FLAG = 128;\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max;\n\n/**\n * Creates a function that wraps `func` and invokes it with optional `this`\n * binding of, partial application, and currying.\n *\n * @private\n * @param {Function|string} func The function or method name to reference.\n * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.\n * @param {*} [thisArg] The `this` binding of `func`.\n * @param {Array} [partials] The arguments to prepend to those provided to the new function.\n * @param {Array} [holders] The `partials` placeholder indexes.\n * @param {Array} [partialsRight] The arguments to append to those provided to the new function.\n * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.\n * @param {Array} [argPos] The argument positions of the new function.\n * @param {number} [ary] The arity cap of `func`.\n * @param {number} [arity] The arity of `func`.\n * @returns {Function} Returns the new wrapped function.\n */\nfunction createHybridWrapper(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {\n  var isAry = bitmask & ARY_FLAG,\n      isBind = bitmask & BIND_FLAG,\n      isBindKey = bitmask & BIND_KEY_FLAG,\n      isCurry = bitmask & CURRY_FLAG,\n      isCurryBound = bitmask & CURRY_BOUND_FLAG,\n      isCurryRight = bitmask & CURRY_RIGHT_FLAG,\n      Ctor = isBindKey ? undefined : createCtorWrapper(func);\n\n  function wrapper() {\n    // Avoid `arguments` object use disqualifying optimizations by\n    // converting it to an array before providing it to other functions.\n    var length = arguments.length,\n        index = length,\n        args = Array(length);\n\n    while (index--) {\n      args[index] = arguments[index];\n    }\n    if (partials) {\n      args = composeArgs(args, partials, holders);\n    }\n    if (partialsRight) {\n      args = composeArgsRight(args, partialsRight, holdersRight);\n    }\n    if (isCurry || isCurryRight) {\n      var placeholder = wrapper.placeholder,\n          argsHolders = replaceHolders(args, placeholder);\n\n      length -= argsHolders.length;\n      if (length < arity) {\n        var newArgPos = argPos ? arrayCopy(argPos) : undefined,\n            newArity = nativeMax(arity - length, 0),\n            newsHolders = isCurry ? argsHolders : undefined,\n            newHoldersRight = isCurry ? undefined : argsHolders,\n            newPartials = isCurry ? args : undefined,\n            newPartialsRight = isCurry ? undefined : args;\n\n        bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG);\n        bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG);\n\n        if (!isCurryBound) {\n          bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);\n        }\n        var newData = [func, bitmask, thisArg, newPartials, newsHolders, newPartialsRight, newHoldersRight, newArgPos, ary, newArity],\n            result = createHybridWrapper.apply(undefined, newData);\n\n        if (isLaziable(func)) {\n          setData(result, newData);\n        }\n        result.placeholder = placeholder;\n        return result;\n      }\n    }\n    var thisBinding = isBind ? thisArg : this,\n        fn = isBindKey ? thisBinding[func] : func;\n\n    if (argPos) {\n      args = reorder(args, argPos);\n    }\n    if (isAry && ary < args.length) {\n      args.length = ary;\n    }\n    if (this && this !== global && this instanceof wrapper) {\n      fn = Ctor || createCtorWrapper(func);\n    }\n    return fn.apply(thisBinding, args);\n  }\n  return wrapper;\n}\n\nmodule.exports = createHybridWrapper;\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2NyZWF0ZUh5YnJpZFdyYXBwZXIuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgYXJyYXlDb3B5ID0gcmVxdWlyZSgnLi9hcnJheUNvcHknKSxcbiAgICBjb21wb3NlQXJncyA9IHJlcXVpcmUoJy4vY29tcG9zZUFyZ3MnKSxcbiAgICBjb21wb3NlQXJnc1JpZ2h0ID0gcmVxdWlyZSgnLi9jb21wb3NlQXJnc1JpZ2h0JyksXG4gICAgY3JlYXRlQ3RvcldyYXBwZXIgPSByZXF1aXJlKCcuL2NyZWF0ZUN0b3JXcmFwcGVyJyksXG4gICAgaXNMYXppYWJsZSA9IHJlcXVpcmUoJy4vaXNMYXppYWJsZScpLFxuICAgIHJlb3JkZXIgPSByZXF1aXJlKCcuL3Jlb3JkZXInKSxcbiAgICByZXBsYWNlSG9sZGVycyA9IHJlcXVpcmUoJy4vcmVwbGFjZUhvbGRlcnMnKSxcbiAgICBzZXREYXRhID0gcmVxdWlyZSgnLi9zZXREYXRhJyk7XG5cbi8qKiBVc2VkIHRvIGNvbXBvc2UgYml0bWFza3MgZm9yIHdyYXBwZXIgbWV0YWRhdGEuICovXG52YXIgQklORF9GTEFHID0gMSxcbiAgICBCSU5EX0tFWV9GTEFHID0gMixcbiAgICBDVVJSWV9CT1VORF9GTEFHID0gNCxcbiAgICBDVVJSWV9GTEFHID0gOCxcbiAgICBDVVJSWV9SSUdIVF9GTEFHID0gMTYsXG4gICAgUEFSVElBTF9GTEFHID0gMzIsXG4gICAgUEFSVElBTF9SSUdIVF9GTEFHID0gNjQsXG4gICAgQVJZX0ZMQUcgPSAxMjg7XG5cbi8qIE5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXg7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGZ1bmN0aW9uIHRoYXQgd3JhcHMgYGZ1bmNgIGFuZCBpbnZva2VzIGl0IHdpdGggb3B0aW9uYWwgYHRoaXNgXG4gKiBiaW5kaW5nIG9mLCBwYXJ0aWFsIGFwcGxpY2F0aW9uLCBhbmQgY3VycnlpbmcuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7RnVuY3Rpb258c3RyaW5nfSBmdW5jIFRoZSBmdW5jdGlvbiBvciBtZXRob2QgbmFtZSB0byByZWZlcmVuY2UuXG4gKiBAcGFyYW0ge251bWJlcn0gYml0bWFzayBUaGUgYml0bWFzayBvZiBmbGFncy4gU2VlIGBjcmVhdGVXcmFwcGVyYCBmb3IgbW9yZSBkZXRhaWxzLlxuICogQHBhcmFtIHsqfSBbdGhpc0FyZ10gVGhlIGB0aGlzYCBiaW5kaW5nIG9mIGBmdW5jYC5cbiAqIEBwYXJhbSB7QXJyYXl9IFtwYXJ0aWFsc10gVGhlIGFyZ3VtZW50cyB0byBwcmVwZW5kIHRvIHRob3NlIHByb3ZpZGVkIHRvIHRoZSBuZXcgZnVuY3Rpb24uXG4gKiBAcGFyYW0ge0FycmF5fSBbaG9sZGVyc10gVGhlIGBwYXJ0aWFsc2AgcGxhY2Vob2xkZXIgaW5kZXhlcy5cbiAqIEBwYXJhbSB7QXJyYXl9IFtwYXJ0aWFsc1JpZ2h0XSBUaGUgYXJndW1lbnRzIHRvIGFwcGVuZCB0byB0aG9zZSBwcm92aWRlZCB0byB0aGUgbmV3IGZ1bmN0aW9uLlxuICogQHBhcmFtIHtBcnJheX0gW2hvbGRlcnNSaWdodF0gVGhlIGBwYXJ0aWFsc1JpZ2h0YCBwbGFjZWhvbGRlciBpbmRleGVzLlxuICogQHBhcmFtIHtBcnJheX0gW2FyZ1Bvc10gVGhlIGFyZ3VtZW50IHBvc2l0aW9ucyBvZiB0aGUgbmV3IGZ1bmN0aW9uLlxuICogQHBhcmFtIHtudW1iZXJ9IFthcnldIFRoZSBhcml0eSBjYXAgb2YgYGZ1bmNgLlxuICogQHBhcmFtIHtudW1iZXJ9IFthcml0eV0gVGhlIGFyaXR5IG9mIGBmdW5jYC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IHdyYXBwZWQgZnVuY3Rpb24uXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZUh5YnJpZFdyYXBwZXIoZnVuYywgYml0bWFzaywgdGhpc0FyZywgcGFydGlhbHMsIGhvbGRlcnMsIHBhcnRpYWxzUmlnaHQsIGhvbGRlcnNSaWdodCwgYXJnUG9zLCBhcnksIGFyaXR5KSB7XG4gIHZhciBpc0FyeSA9IGJpdG1hc2sgJiBBUllfRkxBRyxcbiAgICAgIGlzQmluZCA9IGJpdG1hc2sgJiBCSU5EX0ZMQUcsXG4gICAgICBpc0JpbmRLZXkgPSBiaXRtYXNrICYgQklORF9LRVlfRkxBRyxcbiAgICAgIGlzQ3VycnkgPSBiaXRtYXNrICYgQ1VSUllfRkxBRyxcbiAgICAgIGlzQ3VycnlCb3VuZCA9IGJpdG1hc2sgJiBDVVJSWV9CT1VORF9GTEFHLFxuICAgICAgaXNDdXJyeVJpZ2h0ID0gYml0bWFzayAmIENVUlJZX1JJR0hUX0ZMQUcsXG4gICAgICBDdG9yID0gaXNCaW5kS2V5ID8gdW5kZWZpbmVkIDogY3JlYXRlQ3RvcldyYXBwZXIoZnVuYyk7XG5cbiAgZnVuY3Rpb24gd3JhcHBlcigpIHtcbiAgICAvLyBBdm9pZCBgYXJndW1lbnRzYCBvYmplY3QgdXNlIGRpc3F1YWxpZnlpbmcgb3B0aW1pemF0aW9ucyBieVxuICAgIC8vIGNvbnZlcnRpbmcgaXQgdG8gYW4gYXJyYXkgYmVmb3JlIHByb3ZpZGluZyBpdCB0byBvdGhlciBmdW5jdGlvbnMuXG4gICAgdmFyIGxlbmd0aCA9IGFyZ3VtZW50cy5sZW5ndGgsXG4gICAgICAgIGluZGV4ID0gbGVuZ3RoLFxuICAgICAgICBhcmdzID0gQXJyYXkobGVuZ3RoKTtcblxuICAgIHdoaWxlIChpbmRleC0tKSB7XG4gICAgICBhcmdzW2luZGV4XSA9IGFyZ3VtZW50c1tpbmRleF07XG4gICAgfVxuICAgIGlmIChwYXJ0aWFscykge1xuICAgICAgYXJncyA9IGNvbXBvc2VBcmdzKGFyZ3MsIHBhcnRpYWxzLCBob2xkZXJzKTtcbiAgICB9XG4gICAgaWYgKHBhcnRpYWxzUmlnaHQpIHtcbiAgICAgIGFyZ3MgPSBjb21wb3NlQXJnc1JpZ2h0KGFyZ3MsIHBhcnRpYWxzUmlnaHQsIGhvbGRlcnNSaWdodCk7XG4gICAgfVxuICAgIGlmIChpc0N1cnJ5IHx8IGlzQ3VycnlSaWdodCkge1xuICAgICAgdmFyIHBsYWNlaG9sZGVyID0gd3JhcHBlci5wbGFjZWhvbGRlcixcbiAgICAgICAgICBhcmdzSG9sZGVycyA9IHJlcGxhY2VIb2xkZXJzKGFyZ3MsIHBsYWNlaG9sZGVyKTtcblxuICAgICAgbGVuZ3RoIC09IGFyZ3NIb2xkZXJzLmxlbmd0aDtcbiAgICAgIGlmIChsZW5ndGggPCBhcml0eSkge1xuICAgICAgICB2YXIgbmV3QXJnUG9zID0gYXJnUG9zID8gYXJyYXlDb3B5KGFyZ1BvcykgOiB1bmRlZmluZWQsXG4gICAgICAgICAgICBuZXdBcml0eSA9IG5hdGl2ZU1heChhcml0eSAtIGxlbmd0aCwgMCksXG4gICAgICAgICAgICBuZXdzSG9sZGVycyA9IGlzQ3VycnkgPyBhcmdzSG9sZGVycyA6IHVuZGVmaW5lZCxcbiAgICAgICAgICAgIG5ld0hvbGRlcnNSaWdodCA9IGlzQ3VycnkgPyB1bmRlZmluZWQgOiBhcmdzSG9sZGVycyxcbiAgICAgICAgICAgIG5ld1BhcnRpYWxzID0gaXNDdXJyeSA/IGFyZ3MgOiB1bmRlZmluZWQsXG4gICAgICAgICAgICBuZXdQYXJ0aWFsc1JpZ2h0ID0gaXNDdXJyeSA/IHVuZGVmaW5lZCA6IGFyZ3M7XG5cbiAgICAgICAgYml0bWFzayB8PSAoaXNDdXJyeSA/IFBBUlRJQUxfRkxBRyA6IFBBUlRJQUxfUklHSFRfRkxBRyk7XG4gICAgICAgIGJpdG1hc2sgJj0gfihpc0N1cnJ5ID8gUEFSVElBTF9SSUdIVF9GTEFHIDogUEFSVElBTF9GTEFHKTtcblxuICAgICAgICBpZiAoIWlzQ3VycnlCb3VuZCkge1xuICAgICAgICAgIGJpdG1hc2sgJj0gfihCSU5EX0ZMQUcgfCBCSU5EX0tFWV9GTEFHKTtcbiAgICAgICAgfVxuICAgICAgICB2YXIgbmV3RGF0YSA9IFtmdW5jLCBiaXRtYXNrLCB0aGlzQXJnLCBuZXdQYXJ0aWFscywgbmV3c0hvbGRlcnMsIG5ld1BhcnRpYWxzUmlnaHQsIG5ld0hvbGRlcnNSaWdodCwgbmV3QXJnUG9zLCBhcnksIG5ld0FyaXR5XSxcbiAgICAgICAgICAgIHJlc3VsdCA9IGNyZWF0ZUh5YnJpZFdyYXBwZXIuYXBwbHkodW5kZWZpbmVkLCBuZXdEYXRhKTtcblxuICAgICAgICBpZiAoaXNMYXppYWJsZShmdW5jKSkge1xuICAgICAgICAgIHNldERhdGEocmVzdWx0LCBuZXdEYXRhKTtcbiAgICAgICAgfVxuICAgICAgICByZXN1bHQucGxhY2Vob2xkZXIgPSBwbGFjZWhvbGRlcjtcbiAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgIH1cbiAgICB9XG4gICAgdmFyIHRoaXNCaW5kaW5nID0gaXNCaW5kID8gdGhpc0FyZyA6IHRoaXMsXG4gICAgICAgIGZuID0gaXNCaW5kS2V5ID8gdGhpc0JpbmRpbmdbZnVuY10gOiBmdW5jO1xuXG4gICAgaWYgKGFyZ1Bvcykge1xuICAgICAgYXJncyA9IHJlb3JkZXIoYXJncywgYXJnUG9zKTtcbiAgICB9XG4gICAgaWYgKGlzQXJ5ICYmIGFyeSA8IGFyZ3MubGVuZ3RoKSB7XG4gICAgICBhcmdzLmxlbmd0aCA9IGFyeTtcbiAgICB9XG4gICAgaWYgKHRoaXMgJiYgdGhpcyAhPT0gZ2xvYmFsICYmIHRoaXMgaW5zdGFuY2VvZiB3cmFwcGVyKSB7XG4gICAgICBmbiA9IEN0b3IgfHwgY3JlYXRlQ3RvcldyYXBwZXIoZnVuYyk7XG4gICAgfVxuICAgIHJldHVybiBmbi5hcHBseSh0aGlzQmluZGluZywgYXJncyk7XG4gIH1cbiAgcmV0dXJuIHdyYXBwZXI7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gY3JlYXRlSHlicmlkV3JhcHBlcjtcbiJdfQ==\n},{\"./arrayCopy\":62,\"./composeArgs\":96,\"./composeArgsRight\":97,\"./createCtorWrapper\":101,\"./isLaziable\":124,\"./reorder\":131,\"./replaceHolders\":132,\"./setData\":133}],105:[function(require,module,exports){\n(function (global){\nvar createCtorWrapper = require('./createCtorWrapper');\n\n/** Used to compose bitmasks for wrapper metadata. */\nvar BIND_FLAG = 1;\n\n/**\n * Creates a function that wraps `func` and invokes it with the optional `this`\n * binding of `thisArg` and the `partials` prepended to those provided to\n * the wrapper.\n *\n * @private\n * @param {Function} func The function to partially apply arguments to.\n * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.\n * @param {*} thisArg The `this` binding of `func`.\n * @param {Array} partials The arguments to prepend to those provided to the new function.\n * @returns {Function} Returns the new bound function.\n */\nfunction createPartialWrapper(func, bitmask, thisArg, partials) {\n  var isBind = bitmask & BIND_FLAG,\n      Ctor = createCtorWrapper(func);\n\n  function wrapper() {\n    // Avoid `arguments` object use disqualifying optimizations by\n    // converting it to an array before providing it `func`.\n    var argsIndex = -1,\n        argsLength = arguments.length,\n        leftIndex = -1,\n        leftLength = partials.length,\n        args = Array(leftLength + argsLength);\n\n    while (++leftIndex < leftLength) {\n      args[leftIndex] = partials[leftIndex];\n    }\n    while (argsLength--) {\n      args[leftIndex++] = arguments[++argsIndex];\n    }\n    var fn = (this && this !== global && this instanceof wrapper) ? Ctor : func;\n    return fn.apply(isBind ? thisArg : this, args);\n  }\n  return wrapper;\n}\n\nmodule.exports = createPartialWrapper;\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2NyZWF0ZVBhcnRpYWxXcmFwcGVyLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgY3JlYXRlQ3RvcldyYXBwZXIgPSByZXF1aXJlKCcuL2NyZWF0ZUN0b3JXcmFwcGVyJyk7XG5cbi8qKiBVc2VkIHRvIGNvbXBvc2UgYml0bWFza3MgZm9yIHdyYXBwZXIgbWV0YWRhdGEuICovXG52YXIgQklORF9GTEFHID0gMTtcblxuLyoqXG4gKiBDcmVhdGVzIGEgZnVuY3Rpb24gdGhhdCB3cmFwcyBgZnVuY2AgYW5kIGludm9rZXMgaXQgd2l0aCB0aGUgb3B0aW9uYWwgYHRoaXNgXG4gKiBiaW5kaW5nIG9mIGB0aGlzQXJnYCBhbmQgdGhlIGBwYXJ0aWFsc2AgcHJlcGVuZGVkIHRvIHRob3NlIHByb3ZpZGVkIHRvXG4gKiB0aGUgd3JhcHBlci5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gcGFydGlhbGx5IGFwcGx5IGFyZ3VtZW50cyB0by5cbiAqIEBwYXJhbSB7bnVtYmVyfSBiaXRtYXNrIFRoZSBiaXRtYXNrIG9mIGZsYWdzLiBTZWUgYGNyZWF0ZVdyYXBwZXJgIGZvciBtb3JlIGRldGFpbHMuXG4gKiBAcGFyYW0geyp9IHRoaXNBcmcgVGhlIGB0aGlzYCBiaW5kaW5nIG9mIGBmdW5jYC5cbiAqIEBwYXJhbSB7QXJyYXl9IHBhcnRpYWxzIFRoZSBhcmd1bWVudHMgdG8gcHJlcGVuZCB0byB0aG9zZSBwcm92aWRlZCB0byB0aGUgbmV3IGZ1bmN0aW9uLlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgYm91bmQgZnVuY3Rpb24uXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZVBhcnRpYWxXcmFwcGVyKGZ1bmMsIGJpdG1hc2ssIHRoaXNBcmcsIHBhcnRpYWxzKSB7XG4gIHZhciBpc0JpbmQgPSBiaXRtYXNrICYgQklORF9GTEFHLFxuICAgICAgQ3RvciA9IGNyZWF0ZUN0b3JXcmFwcGVyKGZ1bmMpO1xuXG4gIGZ1bmN0aW9uIHdyYXBwZXIoKSB7XG4gICAgLy8gQXZvaWQgYGFyZ3VtZW50c2Agb2JqZWN0IHVzZSBkaXNxdWFsaWZ5aW5nIG9wdGltaXphdGlvbnMgYnlcbiAgICAvLyBjb252ZXJ0aW5nIGl0IHRvIGFuIGFycmF5IGJlZm9yZSBwcm92aWRpbmcgaXQgYGZ1bmNgLlxuICAgIHZhciBhcmdzSW5kZXggPSAtMSxcbiAgICAgICAgYXJnc0xlbmd0aCA9IGFyZ3VtZW50cy5sZW5ndGgsXG4gICAgICAgIGxlZnRJbmRleCA9IC0xLFxuICAgICAgICBsZWZ0TGVuZ3RoID0gcGFydGlhbHMubGVuZ3RoLFxuICAgICAgICBhcmdzID0gQXJyYXkobGVmdExlbmd0aCArIGFyZ3NMZW5ndGgpO1xuXG4gICAgd2hpbGUgKCsrbGVmdEluZGV4IDwgbGVmdExlbmd0aCkge1xuICAgICAgYXJnc1tsZWZ0SW5kZXhdID0gcGFydGlhbHNbbGVmdEluZGV4XTtcbiAgICB9XG4gICAgd2hpbGUgKGFyZ3NMZW5ndGgtLSkge1xuICAgICAgYXJnc1tsZWZ0SW5kZXgrK10gPSBhcmd1bWVudHNbKythcmdzSW5kZXhdO1xuICAgIH1cbiAgICB2YXIgZm4gPSAodGhpcyAmJiB0aGlzICE9PSBnbG9iYWwgJiYgdGhpcyBpbnN0YW5jZW9mIHdyYXBwZXIpID8gQ3RvciA6IGZ1bmM7XG4gICAgcmV0dXJuIGZuLmFwcGx5KGlzQmluZCA/IHRoaXNBcmcgOiB0aGlzLCBhcmdzKTtcbiAgfVxuICByZXR1cm4gd3JhcHBlcjtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBjcmVhdGVQYXJ0aWFsV3JhcHBlcjtcbiJdfQ==\n},{\"./createCtorWrapper\":101}],106:[function(require,module,exports){\nvar baseSetData = require('./baseSetData'),\n    createBindWrapper = require('./createBindWrapper'),\n    createHybridWrapper = require('./createHybridWrapper'),\n    createPartialWrapper = require('./createPartialWrapper'),\n    getData = require('./getData'),\n    mergeData = require('./mergeData'),\n    setData = require('./setData');\n\n/** Used to compose bitmasks for wrapper metadata. */\nvar BIND_FLAG = 1,\n    BIND_KEY_FLAG = 2,\n    PARTIAL_FLAG = 32,\n    PARTIAL_RIGHT_FLAG = 64;\n\n/** Used as the `TypeError` message for \"Functions\" methods. */\nvar FUNC_ERROR_TEXT = 'Expected a function';\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max;\n\n/**\n * Creates a function that either curries or invokes `func` with optional\n * `this` binding and partially applied arguments.\n *\n * @private\n * @param {Function|string} func The function or method name to reference.\n * @param {number} bitmask The bitmask of flags.\n *  The bitmask may be composed of the following flags:\n *     1 - `_.bind`\n *     2 - `_.bindKey`\n *     4 - `_.curry` or `_.curryRight` of a bound function\n *     8 - `_.curry`\n *    16 - `_.curryRight`\n *    32 - `_.partial`\n *    64 - `_.partialRight`\n *   128 - `_.rearg`\n *   256 - `_.ary`\n * @param {*} [thisArg] The `this` binding of `func`.\n * @param {Array} [partials] The arguments to be partially applied.\n * @param {Array} [holders] The `partials` placeholder indexes.\n * @param {Array} [argPos] The argument positions of the new function.\n * @param {number} [ary] The arity cap of `func`.\n * @param {number} [arity] The arity of `func`.\n * @returns {Function} Returns the new wrapped function.\n */\nfunction createWrapper(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {\n  var isBindKey = bitmask & BIND_KEY_FLAG;\n  if (!isBindKey && typeof func != 'function') {\n    throw new TypeError(FUNC_ERROR_TEXT);\n  }\n  var length = partials ? partials.length : 0;\n  if (!length) {\n    bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG);\n    partials = holders = undefined;\n  }\n  length -= (holders ? holders.length : 0);\n  if (bitmask & PARTIAL_RIGHT_FLAG) {\n    var partialsRight = partials,\n        holdersRight = holders;\n\n    partials = holders = undefined;\n  }\n  var data = isBindKey ? undefined : getData(func),\n      newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];\n\n  if (data) {\n    mergeData(newData, data);\n    bitmask = newData[1];\n    arity = newData[9];\n  }\n  newData[9] = arity == null\n    ? (isBindKey ? 0 : func.length)\n    : (nativeMax(arity - length, 0) || 0);\n\n  if (bitmask == BIND_FLAG) {\n    var result = createBindWrapper(newData[0], newData[2]);\n  } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !newData[4].length) {\n    result = createPartialWrapper.apply(undefined, newData);\n  } else {\n    result = createHybridWrapper.apply(undefined, newData);\n  }\n  var setter = data ? baseSetData : setData;\n  return setter(result, newData);\n}\n\nmodule.exports = createWrapper;\n\n},{\"./baseSetData\":88,\"./createBindWrapper\":100,\"./createHybridWrapper\":104,\"./createPartialWrapper\":105,\"./getData\":110,\"./mergeData\":128,\"./setData\":133}],107:[function(require,module,exports){\nvar arraySome = require('./arraySome');\n\n/**\n * A specialized version of `baseIsEqualDeep` for arrays with support for\n * partial deep comparisons.\n *\n * @private\n * @param {Array} array The array to compare.\n * @param {Array} other The other array to compare.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Function} [customizer] The function to customize comparing arrays.\n * @param {boolean} [isLoose] Specify performing partial comparisons.\n * @param {Array} [stackA] Tracks traversed `value` objects.\n * @param {Array} [stackB] Tracks traversed `other` objects.\n * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.\n */\nfunction equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) {\n  var index = -1,\n      arrLength = array.length,\n      othLength = other.length;\n\n  if (arrLength != othLength && !(isLoose && othLength > arrLength)) {\n    return false;\n  }\n  // Ignore non-index properties.\n  while (++index < arrLength) {\n    var arrValue = array[index],\n        othValue = other[index],\n        result = customizer ? customizer(isLoose ? othValue : arrValue, isLoose ? arrValue : othValue, index) : undefined;\n\n    if (result !== undefined) {\n      if (result) {\n        continue;\n      }\n      return false;\n    }\n    // Recursively compare arrays (susceptible to call stack limits).\n    if (isLoose) {\n      if (!arraySome(other, function(othValue) {\n            return arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);\n          })) {\n        return false;\n      }\n    } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB))) {\n      return false;\n    }\n  }\n  return true;\n}\n\nmodule.exports = equalArrays;\n\n},{\"./arraySome\":65}],108:[function(require,module,exports){\n/** `Object#toString` result references. */\nvar boolTag = '[object Boolean]',\n    dateTag = '[object Date]',\n    errorTag = '[object Error]',\n    numberTag = '[object Number]',\n    regexpTag = '[object RegExp]',\n    stringTag = '[object String]';\n\n/**\n * A specialized version of `baseIsEqualDeep` for comparing objects of\n * the same `toStringTag`.\n *\n * **Note:** This function only supports comparing values with tags of\n * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.\n *\n * @private\n * @param {Object} object The object to compare.\n * @param {Object} other The other object to compare.\n * @param {string} tag The `toStringTag` of the objects to compare.\n * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.\n */\nfunction equalByTag(object, other, tag) {\n  switch (tag) {\n    case boolTag:\n    case dateTag:\n      // Coerce dates and booleans to numbers, dates to milliseconds and booleans\n      // to `1` or `0` treating invalid dates coerced to `NaN` as not equal.\n      return +object == +other;\n\n    case errorTag:\n      return object.name == other.name && object.message == other.message;\n\n    case numberTag:\n      // Treat `NaN` vs. `NaN` as equal.\n      return (object != +object)\n        ? other != +other\n        : object == +other;\n\n    case regexpTag:\n    case stringTag:\n      // Coerce regexes to strings and treat strings primitives and string\n      // objects as equal. See https://es5.github.io/#x15.10.6.4 for more details.\n      return object == (other + '');\n  }\n  return false;\n}\n\nmodule.exports = equalByTag;\n\n},{}],109:[function(require,module,exports){\nvar keys = require('../object/keys');\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * A specialized version of `baseIsEqualDeep` for objects with support for\n * partial deep comparisons.\n *\n * @private\n * @param {Object} object The object to compare.\n * @param {Object} other The other object to compare.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Function} [customizer] The function to customize comparing values.\n * @param {boolean} [isLoose] Specify performing partial comparisons.\n * @param {Array} [stackA] Tracks traversed `value` objects.\n * @param {Array} [stackB] Tracks traversed `other` objects.\n * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.\n */\nfunction equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) {\n  var objProps = keys(object),\n      objLength = objProps.length,\n      othProps = keys(other),\n      othLength = othProps.length;\n\n  if (objLength != othLength && !isLoose) {\n    return false;\n  }\n  var index = objLength;\n  while (index--) {\n    var key = objProps[index];\n    if (!(isLoose ? key in other : hasOwnProperty.call(other, key))) {\n      return false;\n    }\n  }\n  var skipCtor = isLoose;\n  while (++index < objLength) {\n    key = objProps[index];\n    var objValue = object[key],\n        othValue = other[key],\n        result = customizer ? customizer(isLoose ? othValue : objValue, isLoose? objValue : othValue, key) : undefined;\n\n    // Recursively compare objects (susceptible to call stack limits).\n    if (!(result === undefined ? equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB) : result)) {\n      return false;\n    }\n    skipCtor || (skipCtor = key == 'constructor');\n  }\n  if (!skipCtor) {\n    var objCtor = object.constructor,\n        othCtor = other.constructor;\n\n    // Non `Object` object instances with different constructors are not equal.\n    if (objCtor != othCtor &&\n        ('constructor' in object && 'constructor' in other) &&\n        !(typeof objCtor == 'function' && objCtor instanceof objCtor &&\n          typeof othCtor == 'function' && othCtor instanceof othCtor)) {\n      return false;\n    }\n  }\n  return true;\n}\n\nmodule.exports = equalObjects;\n\n},{\"../object/keys\":149}],110:[function(require,module,exports){\nvar metaMap = require('./metaMap'),\n    noop = require('../utility/noop');\n\n/**\n * Gets metadata for `func`.\n *\n * @private\n * @param {Function} func The function to query.\n * @returns {*} Returns the metadata for `func`.\n */\nvar getData = !metaMap ? noop : function(func) {\n  return metaMap.get(func);\n};\n\nmodule.exports = getData;\n\n},{\"../utility/noop\":155,\"./metaMap\":129}],111:[function(require,module,exports){\nvar realNames = require('./realNames');\n\n/**\n * Gets the name of `func`.\n *\n * @private\n * @param {Function} func The function to query.\n * @returns {string} Returns the function name.\n */\nfunction getFuncName(func) {\n  var result = (func.name + ''),\n      array = realNames[result],\n      length = array ? array.length : 0;\n\n  while (length--) {\n    var data = array[length],\n        otherFunc = data.func;\n    if (otherFunc == null || otherFunc == func) {\n      return data.name;\n    }\n  }\n  return result;\n}\n\nmodule.exports = getFuncName;\n\n},{\"./realNames\":130}],112:[function(require,module,exports){\nvar baseProperty = require('./baseProperty');\n\n/**\n * Gets the \"length\" property value of `object`.\n *\n * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)\n * that affects Safari on at least iOS 8.1-8.3 ARM64.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {*} Returns the \"length\" value.\n */\nvar getLength = baseProperty('length');\n\nmodule.exports = getLength;\n\n},{\"./baseProperty\":86}],113:[function(require,module,exports){\nvar isStrictComparable = require('./isStrictComparable'),\n    pairs = require('../object/pairs');\n\n/**\n * Gets the propery names, values, and compare flags of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the match data of `object`.\n */\nfunction getMatchData(object) {\n  var result = pairs(object),\n      length = result.length;\n\n  while (length--) {\n    result[length][2] = isStrictComparable(result[length][1]);\n  }\n  return result;\n}\n\nmodule.exports = getMatchData;\n\n},{\"../object/pairs\":151,\"./isStrictComparable\":127}],114:[function(require,module,exports){\nvar isNative = require('../lang/isNative');\n\n/**\n * Gets the native function at `key` of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {string} key The key of the method to get.\n * @returns {*} Returns the function if it's native, else `undefined`.\n */\nfunction getNative(object, key) {\n  var value = object == null ? undefined : object[key];\n  return isNative(value) ? value : undefined;\n}\n\nmodule.exports = getNative;\n\n},{\"../lang/isNative\":143}],115:[function(require,module,exports){\n/**\n * Gets the index at which the first occurrence of `NaN` is found in `array`.\n *\n * @private\n * @param {Array} array The array to search.\n * @param {number} fromIndex The index to search from.\n * @param {boolean} [fromRight] Specify iterating from right to left.\n * @returns {number} Returns the index of the matched `NaN`, else `-1`.\n */\nfunction indexOfNaN(array, fromIndex, fromRight) {\n  var length = array.length,\n      index = fromIndex + (fromRight ? 0 : -1);\n\n  while ((fromRight ? index-- : ++index < length)) {\n    var other = array[index];\n    if (other !== other) {\n      return index;\n    }\n  }\n  return -1;\n}\n\nmodule.exports = indexOfNaN;\n\n},{}],116:[function(require,module,exports){\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Initializes an array clone.\n *\n * @private\n * @param {Array} array The array to clone.\n * @returns {Array} Returns the initialized clone.\n */\nfunction initCloneArray(array) {\n  var length = array.length,\n      result = new array.constructor(length);\n\n  // Add array properties assigned by `RegExp#exec`.\n  if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {\n    result.index = array.index;\n    result.input = array.input;\n  }\n  return result;\n}\n\nmodule.exports = initCloneArray;\n\n},{}],117:[function(require,module,exports){\n(function (global){\nvar bufferClone = require('./bufferClone');\n\n/** `Object#toString` result references. */\nvar boolTag = '[object Boolean]',\n    dateTag = '[object Date]',\n    numberTag = '[object Number]',\n    regexpTag = '[object RegExp]',\n    stringTag = '[object String]';\n\nvar arrayBufferTag = '[object ArrayBuffer]',\n    float32Tag = '[object Float32Array]',\n    float64Tag = '[object Float64Array]',\n    int8Tag = '[object Int8Array]',\n    int16Tag = '[object Int16Array]',\n    int32Tag = '[object Int32Array]',\n    uint8Tag = '[object Uint8Array]',\n    uint8ClampedTag = '[object Uint8ClampedArray]',\n    uint16Tag = '[object Uint16Array]',\n    uint32Tag = '[object Uint32Array]';\n\n/** Used to match `RegExp` flags from their coerced string values. */\nvar reFlags = /\\w*$/;\n\n/** Native method references. */\nvar Uint8Array = global.Uint8Array;\n\n/** Used to lookup a type array constructors by `toStringTag`. */\nvar ctorByTag = {};\nctorByTag[float32Tag] = global.Float32Array;\nctorByTag[float64Tag] = global.Float64Array;\nctorByTag[int8Tag] = global.Int8Array;\nctorByTag[int16Tag] = global.Int16Array;\nctorByTag[int32Tag] = global.Int32Array;\nctorByTag[uint8Tag] = Uint8Array;\nctorByTag[uint8ClampedTag] = global.Uint8ClampedArray;\nctorByTag[uint16Tag] = global.Uint16Array;\nctorByTag[uint32Tag] = global.Uint32Array;\n\n/**\n * Initializes an object clone based on its `toStringTag`.\n *\n * **Note:** This function only supports cloning values with tags of\n * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.\n *\n * @private\n * @param {Object} object The object to clone.\n * @param {string} tag The `toStringTag` of the object to clone.\n * @param {boolean} [isDeep] Specify a deep clone.\n * @returns {Object} Returns the initialized clone.\n */\nfunction initCloneByTag(object, tag, isDeep) {\n  var Ctor = object.constructor;\n  switch (tag) {\n    case arrayBufferTag:\n      return bufferClone(object);\n\n    case boolTag:\n    case dateTag:\n      return new Ctor(+object);\n\n    case float32Tag: case float64Tag:\n    case int8Tag: case int16Tag: case int32Tag:\n    case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:\n      // Safari 5 mobile incorrectly has `Object` as the constructor of typed arrays.\n      if (Ctor instanceof Ctor) {\n        Ctor = ctorByTag[tag];\n      }\n      var buffer = object.buffer;\n      return new Ctor(isDeep ? bufferClone(buffer) : buffer, object.byteOffset, object.length);\n\n    case numberTag:\n    case stringTag:\n      return new Ctor(object);\n\n    case regexpTag:\n      var result = new Ctor(object.source, reFlags.exec(object));\n      result.lastIndex = object.lastIndex;\n  }\n  return result;\n}\n\nmodule.exports = initCloneByTag;\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2luaXRDbG9uZUJ5VGFnLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgYnVmZmVyQ2xvbmUgPSByZXF1aXJlKCcuL2J1ZmZlckNsb25lJyk7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBib29sVGFnID0gJ1tvYmplY3QgQm9vbGVhbl0nLFxuICAgIGRhdGVUYWcgPSAnW29iamVjdCBEYXRlXScsXG4gICAgbnVtYmVyVGFnID0gJ1tvYmplY3QgTnVtYmVyXScsXG4gICAgcmVnZXhwVGFnID0gJ1tvYmplY3QgUmVnRXhwXScsXG4gICAgc3RyaW5nVGFnID0gJ1tvYmplY3QgU3RyaW5nXSc7XG5cbnZhciBhcnJheUJ1ZmZlclRhZyA9ICdbb2JqZWN0IEFycmF5QnVmZmVyXScsXG4gICAgZmxvYXQzMlRhZyA9ICdbb2JqZWN0IEZsb2F0MzJBcnJheV0nLFxuICAgIGZsb2F0NjRUYWcgPSAnW29iamVjdCBGbG9hdDY0QXJyYXldJyxcbiAgICBpbnQ4VGFnID0gJ1tvYmplY3QgSW50OEFycmF5XScsXG4gICAgaW50MTZUYWcgPSAnW29iamVjdCBJbnQxNkFycmF5XScsXG4gICAgaW50MzJUYWcgPSAnW29iamVjdCBJbnQzMkFycmF5XScsXG4gICAgdWludDhUYWcgPSAnW29iamVjdCBVaW50OEFycmF5XScsXG4gICAgdWludDhDbGFtcGVkVGFnID0gJ1tvYmplY3QgVWludDhDbGFtcGVkQXJyYXldJyxcbiAgICB1aW50MTZUYWcgPSAnW29iamVjdCBVaW50MTZBcnJheV0nLFxuICAgIHVpbnQzMlRhZyA9ICdbb2JqZWN0IFVpbnQzMkFycmF5XSc7XG5cbi8qKiBVc2VkIHRvIG1hdGNoIGBSZWdFeHBgIGZsYWdzIGZyb20gdGhlaXIgY29lcmNlZCBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlRmxhZ3MgPSAvXFx3KiQvO1xuXG4vKiogTmF0aXZlIG1ldGhvZCByZWZlcmVuY2VzLiAqL1xudmFyIFVpbnQ4QXJyYXkgPSBnbG9iYWwuVWludDhBcnJheTtcblxuLyoqIFVzZWQgdG8gbG9va3VwIGEgdHlwZSBhcnJheSBjb25zdHJ1Y3RvcnMgYnkgYHRvU3RyaW5nVGFnYC4gKi9cbnZhciBjdG9yQnlUYWcgPSB7fTtcbmN0b3JCeVRhZ1tmbG9hdDMyVGFnXSA9IGdsb2JhbC5GbG9hdDMyQXJyYXk7XG5jdG9yQnlUYWdbZmxvYXQ2NFRhZ10gPSBnbG9iYWwuRmxvYXQ2NEFycmF5O1xuY3RvckJ5VGFnW2ludDhUYWddID0gZ2xvYmFsLkludDhBcnJheTtcbmN0b3JCeVRhZ1tpbnQxNlRhZ10gPSBnbG9iYWwuSW50MTZBcnJheTtcbmN0b3JCeVRhZ1tpbnQzMlRhZ10gPSBnbG9iYWwuSW50MzJBcnJheTtcbmN0b3JCeVRhZ1t1aW50OFRhZ10gPSBVaW50OEFycmF5O1xuY3RvckJ5VGFnW3VpbnQ4Q2xhbXBlZFRhZ10gPSBnbG9iYWwuVWludDhDbGFtcGVkQXJyYXk7XG5jdG9yQnlUYWdbdWludDE2VGFnXSA9IGdsb2JhbC5VaW50MTZBcnJheTtcbmN0b3JCeVRhZ1t1aW50MzJUYWddID0gZ2xvYmFsLlVpbnQzMkFycmF5O1xuXG4vKipcbiAqIEluaXRpYWxpemVzIGFuIG9iamVjdCBjbG9uZSBiYXNlZCBvbiBpdHMgYHRvU3RyaW5nVGFnYC5cbiAqXG4gKiAqKk5vdGU6KiogVGhpcyBmdW5jdGlvbiBvbmx5IHN1cHBvcnRzIGNsb25pbmcgdmFsdWVzIHdpdGggdGFncyBvZlxuICogYEJvb2xlYW5gLCBgRGF0ZWAsIGBFcnJvcmAsIGBOdW1iZXJgLCBgUmVnRXhwYCwgb3IgYFN0cmluZ2AuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmplY3QgVGhlIG9iamVjdCB0byBjbG9uZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSB0YWcgVGhlIGB0b1N0cmluZ1RhZ2Agb2YgdGhlIG9iamVjdCB0byBjbG9uZS5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW2lzRGVlcF0gU3BlY2lmeSBhIGRlZXAgY2xvbmUuXG4gKiBAcmV0dXJucyB7T2JqZWN0fSBSZXR1cm5zIHRoZSBpbml0aWFsaXplZCBjbG9uZS5cbiAqL1xuZnVuY3Rpb24gaW5pdENsb25lQnlUYWcob2JqZWN0LCB0YWcsIGlzRGVlcCkge1xuICB2YXIgQ3RvciA9IG9iamVjdC5jb25zdHJ1Y3RvcjtcbiAgc3dpdGNoICh0YWcpIHtcbiAgICBjYXNlIGFycmF5QnVmZmVyVGFnOlxuICAgICAgcmV0dXJuIGJ1ZmZlckNsb25lKG9iamVjdCk7XG5cbiAgICBjYXNlIGJvb2xUYWc6XG4gICAgY2FzZSBkYXRlVGFnOlxuICAgICAgcmV0dXJuIG5ldyBDdG9yKCtvYmplY3QpO1xuXG4gICAgY2FzZSBmbG9hdDMyVGFnOiBjYXNlIGZsb2F0NjRUYWc6XG4gICAgY2FzZSBpbnQ4VGFnOiBjYXNlIGludDE2VGFnOiBjYXNlIGludDMyVGFnOlxuICAgIGNhc2UgdWludDhUYWc6IGNhc2UgdWludDhDbGFtcGVkVGFnOiBjYXNlIHVpbnQxNlRhZzogY2FzZSB1aW50MzJUYWc6XG4gICAgICAvLyBTYWZhcmkgNSBtb2JpbGUgaW5jb3JyZWN0bHkgaGFzIGBPYmplY3RgIGFzIHRoZSBjb25zdHJ1Y3RvciBvZiB0eXBlZCBhcnJheXMuXG4gICAgICBpZiAoQ3RvciBpbnN0YW5jZW9mIEN0b3IpIHtcbiAgICAgICAgQ3RvciA9IGN0b3JCeVRhZ1t0YWddO1xuICAgICAgfVxuICAgICAgdmFyIGJ1ZmZlciA9IG9iamVjdC5idWZmZXI7XG4gICAgICByZXR1cm4gbmV3IEN0b3IoaXNEZWVwID8gYnVmZmVyQ2xvbmUoYnVmZmVyKSA6IGJ1ZmZlciwgb2JqZWN0LmJ5dGVPZmZzZXQsIG9iamVjdC5sZW5ndGgpO1xuXG4gICAgY2FzZSBudW1iZXJUYWc6XG4gICAgY2FzZSBzdHJpbmdUYWc6XG4gICAgICByZXR1cm4gbmV3IEN0b3Iob2JqZWN0KTtcblxuICAgIGNhc2UgcmVnZXhwVGFnOlxuICAgICAgdmFyIHJlc3VsdCA9IG5ldyBDdG9yKG9iamVjdC5zb3VyY2UsIHJlRmxhZ3MuZXhlYyhvYmplY3QpKTtcbiAgICAgIHJlc3VsdC5sYXN0SW5kZXggPSBvYmplY3QubGFzdEluZGV4O1xuICB9XG4gIHJldHVybiByZXN1bHQ7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaW5pdENsb25lQnlUYWc7XG4iXX0=\n},{\"./bufferClone\":95}],118:[function(require,module,exports){\n/**\n * Initializes an object clone.\n *\n * @private\n * @param {Object} object The object to clone.\n * @returns {Object} Returns the initialized clone.\n */\nfunction initCloneObject(object) {\n  var Ctor = object.constructor;\n  if (!(typeof Ctor == 'function' && Ctor instanceof Ctor)) {\n    Ctor = Object;\n  }\n  return new Ctor;\n}\n\nmodule.exports = initCloneObject;\n\n},{}],119:[function(require,module,exports){\nvar getLength = require('./getLength'),\n    isLength = require('./isLength');\n\n/**\n * Checks if `value` is array-like.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is array-like, else `false`.\n */\nfunction isArrayLike(value) {\n  return value != null && isLength(getLength(value));\n}\n\nmodule.exports = isArrayLike;\n\n},{\"./getLength\":112,\"./isLength\":125}],120:[function(require,module,exports){\n/**\n * Checks if `value` is a host object in IE < 9.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a host object, else `false`.\n */\nvar isHostObject = (function() {\n  try {\n    Object({ 'toString': 0 } + '');\n  } catch(e) {\n    return function() { return false; };\n  }\n  return function(value) {\n    // IE < 9 presents many host objects as `Object` objects that can coerce\n    // to strings despite having improperly defined `toString` methods.\n    return typeof value.toString != 'function' && typeof (value + '') == 'string';\n  };\n}());\n\nmodule.exports = isHostObject;\n\n},{}],121:[function(require,module,exports){\n/** Used to detect unsigned integer values. */\nvar reIsUint = /^\\d+$/;\n\n/**\n * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)\n * of an array-like value.\n */\nvar MAX_SAFE_INTEGER = 9007199254740991;\n\n/**\n * Checks if `value` is a valid array-like index.\n *\n * @private\n * @param {*} value The value to check.\n * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.\n * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.\n */\nfunction isIndex(value, length) {\n  value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;\n  length = length == null ? MAX_SAFE_INTEGER : length;\n  return value > -1 && value % 1 == 0 && value < length;\n}\n\nmodule.exports = isIndex;\n\n},{}],122:[function(require,module,exports){\nvar isArrayLike = require('./isArrayLike'),\n    isIndex = require('./isIndex'),\n    isObject = require('../lang/isObject');\n\n/**\n * Checks if the provided arguments are from an iteratee call.\n *\n * @private\n * @param {*} value The potential iteratee value argument.\n * @param {*} index The potential iteratee index or key argument.\n * @param {*} object The potential iteratee object argument.\n * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.\n */\nfunction isIterateeCall(value, index, object) {\n  if (!isObject(object)) {\n    return false;\n  }\n  var type = typeof index;\n  if (type == 'number'\n      ? (isArrayLike(object) && isIndex(index, object.length))\n      : (type == 'string' && index in object)) {\n    var other = object[index];\n    return value === value ? (value === other) : (other !== other);\n  }\n  return false;\n}\n\nmodule.exports = isIterateeCall;\n\n},{\"../lang/isObject\":144,\"./isArrayLike\":119,\"./isIndex\":121}],123:[function(require,module,exports){\nvar isArray = require('../lang/isArray'),\n    toObject = require('./toObject');\n\n/** Used to match property names within property paths. */\nvar reIsDeepProp = /\\.|\\[(?:[^[\\]]*|([\"'])(?:(?!\\1)[^\\n\\\\]|\\\\.)*?\\1)\\]/,\n    reIsPlainProp = /^\\w*$/;\n\n/**\n * Checks if `value` is a property name and not a property path.\n *\n * @private\n * @param {*} value The value to check.\n * @param {Object} [object] The object to query keys on.\n * @returns {boolean} Returns `true` if `value` is a property name, else `false`.\n */\nfunction isKey(value, object) {\n  var type = typeof value;\n  if ((type == 'string' && reIsPlainProp.test(value)) || type == 'number') {\n    return true;\n  }\n  if (isArray(value)) {\n    return false;\n  }\n  var result = !reIsDeepProp.test(value);\n  return result || (object != null && value in toObject(object));\n}\n\nmodule.exports = isKey;\n\n},{\"../lang/isArray\":140,\"./toObject\":135}],124:[function(require,module,exports){\nvar LazyWrapper = require('./LazyWrapper'),\n    getData = require('./getData'),\n    getFuncName = require('./getFuncName'),\n    lodash = require('../chain/lodash');\n\n/**\n * Checks if `func` has a lazy counterpart.\n *\n * @private\n * @param {Function} func The function to check.\n * @returns {boolean} Returns `true` if `func` has a lazy counterpart, else `false`.\n */\nfunction isLaziable(func) {\n  var funcName = getFuncName(func),\n      other = lodash[funcName];\n\n  if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {\n    return false;\n  }\n  if (func === other) {\n    return true;\n  }\n  var data = getData(other);\n  return !!data && func === data[0];\n}\n\nmodule.exports = isLaziable;\n\n},{\"../chain/lodash\":51,\"./LazyWrapper\":60,\"./getData\":110,\"./getFuncName\":111}],125:[function(require,module,exports){\n/**\n * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)\n * of an array-like value.\n */\nvar MAX_SAFE_INTEGER = 9007199254740991;\n\n/**\n * Checks if `value` is a valid array-like length.\n *\n * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.\n */\nfunction isLength(value) {\n  return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;\n}\n\nmodule.exports = isLength;\n\n},{}],126:[function(require,module,exports){\n/**\n * Checks if `value` is object-like.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n */\nfunction isObjectLike(value) {\n  return !!value && typeof value == 'object';\n}\n\nmodule.exports = isObjectLike;\n\n},{}],127:[function(require,module,exports){\nvar isObject = require('../lang/isObject');\n\n/**\n * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` if suitable for strict\n *  equality comparisons, else `false`.\n */\nfunction isStrictComparable(value) {\n  return value === value && !isObject(value);\n}\n\nmodule.exports = isStrictComparable;\n\n},{\"../lang/isObject\":144}],128:[function(require,module,exports){\nvar arrayCopy = require('./arrayCopy'),\n    composeArgs = require('./composeArgs'),\n    composeArgsRight = require('./composeArgsRight'),\n    replaceHolders = require('./replaceHolders');\n\n/** Used to compose bitmasks for wrapper metadata. */\nvar BIND_FLAG = 1,\n    CURRY_BOUND_FLAG = 4,\n    CURRY_FLAG = 8,\n    ARY_FLAG = 128,\n    REARG_FLAG = 256;\n\n/** Used as the internal argument placeholder. */\nvar PLACEHOLDER = '__lodash_placeholder__';\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeMin = Math.min;\n\n/**\n * Merges the function metadata of `source` into `data`.\n *\n * Merging metadata reduces the number of wrappers required to invoke a function.\n * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`\n * may be applied regardless of execution order. Methods like `_.ary` and `_.rearg`\n * augment function arguments, making the order in which they are executed important,\n * preventing the merging of metadata. However, we make an exception for a safe\n * common case where curried functions have `_.ary` and or `_.rearg` applied.\n *\n * @private\n * @param {Array} data The destination metadata.\n * @param {Array} source The source metadata.\n * @returns {Array} Returns `data`.\n */\nfunction mergeData(data, source) {\n  var bitmask = data[1],\n      srcBitmask = source[1],\n      newBitmask = bitmask | srcBitmask,\n      isCommon = newBitmask < ARY_FLAG;\n\n  var isCombo =\n    (srcBitmask == ARY_FLAG && bitmask == CURRY_FLAG) ||\n    (srcBitmask == ARY_FLAG && bitmask == REARG_FLAG && data[7].length <= source[8]) ||\n    (srcBitmask == (ARY_FLAG | REARG_FLAG) && bitmask == CURRY_FLAG);\n\n  // Exit early if metadata can't be merged.\n  if (!(isCommon || isCombo)) {\n    return data;\n  }\n  // Use source `thisArg` if available.\n  if (srcBitmask & BIND_FLAG) {\n    data[2] = source[2];\n    // Set when currying a bound function.\n    newBitmask |= (bitmask & BIND_FLAG) ? 0 : CURRY_BOUND_FLAG;\n  }\n  // Compose partial arguments.\n  var value = source[3];\n  if (value) {\n    var partials = data[3];\n    data[3] = partials ? composeArgs(partials, value, source[4]) : arrayCopy(value);\n    data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : arrayCopy(source[4]);\n  }\n  // Compose partial right arguments.\n  value = source[5];\n  if (value) {\n    partials = data[5];\n    data[5] = partials ? composeArgsRight(partials, value, source[6]) : arrayCopy(value);\n    data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : arrayCopy(source[6]);\n  }\n  // Use source `argPos` if available.\n  value = source[7];\n  if (value) {\n    data[7] = arrayCopy(value);\n  }\n  // Use source `ary` if it's smaller.\n  if (srcBitmask & ARY_FLAG) {\n    data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);\n  }\n  // Use source `arity` if one is not provided.\n  if (data[9] == null) {\n    data[9] = source[9];\n  }\n  // Use source `func` and merge bitmasks.\n  data[0] = source[0];\n  data[1] = newBitmask;\n\n  return data;\n}\n\nmodule.exports = mergeData;\n\n},{\"./arrayCopy\":62,\"./composeArgs\":96,\"./composeArgsRight\":97,\"./replaceHolders\":132}],129:[function(require,module,exports){\n(function (global){\nvar getNative = require('./getNative');\n\n/** Native method references. */\nvar WeakMap = getNative(global, 'WeakMap');\n\n/** Used to store function metadata. */\nvar metaMap = WeakMap && new WeakMap;\n\nmodule.exports = metaMap;\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL21ldGFNYXAuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgZ2V0TmF0aXZlID0gcmVxdWlyZSgnLi9nZXROYXRpdmUnKTtcblxuLyoqIE5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBXZWFrTWFwID0gZ2V0TmF0aXZlKGdsb2JhbCwgJ1dlYWtNYXAnKTtcblxuLyoqIFVzZWQgdG8gc3RvcmUgZnVuY3Rpb24gbWV0YWRhdGEuICovXG52YXIgbWV0YU1hcCA9IFdlYWtNYXAgJiYgbmV3IFdlYWtNYXA7XG5cbm1vZHVsZS5leHBvcnRzID0gbWV0YU1hcDtcbiJdfQ==\n},{\"./getNative\":114}],130:[function(require,module,exports){\n/** Used to lookup unminified function names. */\nvar realNames = {};\n\nmodule.exports = realNames;\n\n},{}],131:[function(require,module,exports){\nvar arrayCopy = require('./arrayCopy'),\n    isIndex = require('./isIndex');\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeMin = Math.min;\n\n/**\n * Reorder `array` according to the specified indexes where the element at\n * the first index is assigned as the first element, the element at\n * the second index is assigned as the second element, and so on.\n *\n * @private\n * @param {Array} array The array to reorder.\n * @param {Array} indexes The arranged array indexes.\n * @returns {Array} Returns `array`.\n */\nfunction reorder(array, indexes) {\n  var arrLength = array.length,\n      length = nativeMin(indexes.length, arrLength),\n      oldArray = arrayCopy(array);\n\n  while (length--) {\n    var index = indexes[length];\n    array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;\n  }\n  return array;\n}\n\nmodule.exports = reorder;\n\n},{\"./arrayCopy\":62,\"./isIndex\":121}],132:[function(require,module,exports){\n/** Used as the internal argument placeholder. */\nvar PLACEHOLDER = '__lodash_placeholder__';\n\n/**\n * Replaces all `placeholder` elements in `array` with an internal placeholder\n * and returns an array of their indexes.\n *\n * @private\n * @param {Array} array The array to modify.\n * @param {*} placeholder The placeholder to replace.\n * @returns {Array} Returns the new array of placeholder indexes.\n */\nfunction replaceHolders(array, placeholder) {\n  var index = -1,\n      length = array.length,\n      resIndex = -1,\n      result = [];\n\n  while (++index < length) {\n    if (array[index] === placeholder) {\n      array[index] = PLACEHOLDER;\n      result[++resIndex] = index;\n    }\n  }\n  return result;\n}\n\nmodule.exports = replaceHolders;\n\n},{}],133:[function(require,module,exports){\nvar baseSetData = require('./baseSetData'),\n    now = require('../date/now');\n\n/** Used to detect when a function becomes hot. */\nvar HOT_COUNT = 150,\n    HOT_SPAN = 16;\n\n/**\n * Sets metadata for `func`.\n *\n * **Note:** If this function becomes hot, i.e. is invoked a lot in a short\n * period of time, it will trip its breaker and transition to an identity function\n * to avoid garbage collection pauses in V8. See [V8 issue 2070](https://code.google.com/p/v8/issues/detail?id=2070)\n * for more details.\n *\n * @private\n * @param {Function} func The function to associate metadata with.\n * @param {*} data The metadata.\n * @returns {Function} Returns `func`.\n */\nvar setData = (function() {\n  var count = 0,\n      lastCalled = 0;\n\n  return function(key, value) {\n    var stamp = now(),\n        remaining = HOT_SPAN - (stamp - lastCalled);\n\n    lastCalled = stamp;\n    if (remaining > 0) {\n      if (++count >= HOT_COUNT) {\n        return key;\n      }\n    } else {\n      count = 0;\n    }\n    return baseSetData(key, value);\n  };\n}());\n\nmodule.exports = setData;\n\n},{\"../date/now\":57,\"./baseSetData\":88}],134:[function(require,module,exports){\nvar isArguments = require('../lang/isArguments'),\n    isArray = require('../lang/isArray'),\n    isIndex = require('./isIndex'),\n    isLength = require('./isLength'),\n    isString = require('../lang/isString'),\n    keysIn = require('../object/keysIn');\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * A fallback implementation of `Object.keys` which creates an array of the\n * own enumerable property names of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names.\n */\nfunction shimKeys(object) {\n  var props = keysIn(object),\n      propsLength = props.length,\n      length = propsLength && object.length;\n\n  var allowIndexes = !!length && isLength(length) &&\n    (isArray(object) || isArguments(object) || isString(object));\n\n  var index = -1,\n      result = [];\n\n  while (++index < propsLength) {\n    var key = props[index];\n    if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) {\n      result.push(key);\n    }\n  }\n  return result;\n}\n\nmodule.exports = shimKeys;\n\n},{\"../lang/isArguments\":139,\"../lang/isArray\":140,\"../lang/isString\":146,\"../object/keysIn\":150,\"./isIndex\":121,\"./isLength\":125}],135:[function(require,module,exports){\nvar isObject = require('../lang/isObject'),\n    isString = require('../lang/isString'),\n    support = require('../support');\n\n/**\n * Converts `value` to an object if it's not one.\n *\n * @private\n * @param {*} value The value to process.\n * @returns {Object} Returns the object.\n */\nfunction toObject(value) {\n  if (support.unindexedChars && isString(value)) {\n    var index = -1,\n        length = value.length,\n        result = Object(value);\n\n    while (++index < length) {\n      result[index] = value.charAt(index);\n    }\n    return result;\n  }\n  return isObject(value) ? value : Object(value);\n}\n\nmodule.exports = toObject;\n\n},{\"../lang/isObject\":144,\"../lang/isString\":146,\"../support\":153}],136:[function(require,module,exports){\nvar baseToString = require('./baseToString'),\n    isArray = require('../lang/isArray');\n\n/** Used to match property names within property paths. */\nvar rePropName = /[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|([\"'])((?:(?!\\2)[^\\n\\\\]|\\\\.)*?)\\2)\\]/g;\n\n/** Used to match backslashes in property paths. */\nvar reEscapeChar = /\\\\(\\\\)?/g;\n\n/**\n * Converts `value` to property path array if it's not one.\n *\n * @private\n * @param {*} value The value to process.\n * @returns {Array} Returns the property path array.\n */\nfunction toPath(value) {\n  if (isArray(value)) {\n    return value;\n  }\n  var result = [];\n  baseToString(value).replace(rePropName, function(match, number, quote, string) {\n    result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));\n  });\n  return result;\n}\n\nmodule.exports = toPath;\n\n},{\"../lang/isArray\":140,\"./baseToString\":90}],137:[function(require,module,exports){\nvar LazyWrapper = require('./LazyWrapper'),\n    LodashWrapper = require('./LodashWrapper'),\n    arrayCopy = require('./arrayCopy');\n\n/**\n * Creates a clone of `wrapper`.\n *\n * @private\n * @param {Object} wrapper The wrapper to clone.\n * @returns {Object} Returns the cloned wrapper.\n */\nfunction wrapperClone(wrapper) {\n  return wrapper instanceof LazyWrapper\n    ? wrapper.clone()\n    : new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__, arrayCopy(wrapper.__actions__));\n}\n\nmodule.exports = wrapperClone;\n\n},{\"./LazyWrapper\":60,\"./LodashWrapper\":61,\"./arrayCopy\":62}],138:[function(require,module,exports){\nvar baseClone = require('../internal/baseClone'),\n    bindCallback = require('../internal/bindCallback');\n\n/**\n * Creates a deep clone of `value`. If `customizer` is provided it's invoked\n * to produce the cloned values. If `customizer` returns `undefined` cloning\n * is handled by the method instead. The `customizer` is bound to `thisArg`\n * and invoked with up to three argument; (value [, index|key, object]).\n *\n * **Note:** This method is loosely based on the\n * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).\n * The enumerable properties of `arguments` objects and objects created by\n * constructors other than `Object` are cloned to plain `Object` objects. An\n * empty object is returned for uncloneable values such as functions, DOM nodes,\n * Maps, Sets, and WeakMaps.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to deep clone.\n * @param {Function} [customizer] The function to customize cloning values.\n * @param {*} [thisArg] The `this` binding of `customizer`.\n * @returns {*} Returns the deep cloned value.\n * @example\n *\n * var users = [\n *   { 'user': 'barney' },\n *   { 'user': 'fred' }\n * ];\n *\n * var deep = _.cloneDeep(users);\n * deep[0] === users[0];\n * // => false\n *\n * // using a customizer callback\n * var el = _.cloneDeep(document.body, function(value) {\n *   if (_.isElement(value)) {\n *     return value.cloneNode(true);\n *   }\n * });\n *\n * el === document.body\n * // => false\n * el.nodeName\n * // => BODY\n * el.childNodes.length;\n * // => 20\n */\nfunction cloneDeep(value, customizer, thisArg) {\n  return typeof customizer == 'function'\n    ? baseClone(value, true, bindCallback(customizer, thisArg, 3))\n    : baseClone(value, true);\n}\n\nmodule.exports = cloneDeep;\n\n},{\"../internal/baseClone\":68,\"../internal/bindCallback\":94}],139:[function(require,module,exports){\nvar isArrayLike = require('../internal/isArrayLike'),\n    isObjectLike = require('../internal/isObjectLike');\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/** Native method references. */\nvar propertyIsEnumerable = objectProto.propertyIsEnumerable;\n\n/**\n * Checks if `value` is classified as an `arguments` object.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.\n * @example\n *\n * _.isArguments(function() { return arguments; }());\n * // => true\n *\n * _.isArguments([1, 2, 3]);\n * // => false\n */\nfunction isArguments(value) {\n  return isObjectLike(value) && isArrayLike(value) &&\n    hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');\n}\n\nmodule.exports = isArguments;\n\n},{\"../internal/isArrayLike\":119,\"../internal/isObjectLike\":126}],140:[function(require,module,exports){\nvar getNative = require('../internal/getNative'),\n    isLength = require('../internal/isLength'),\n    isObjectLike = require('../internal/isObjectLike');\n\n/** `Object#toString` result references. */\nvar arrayTag = '[object Array]';\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/**\n * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objToString = objectProto.toString;\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeIsArray = getNative(Array, 'isArray');\n\n/**\n * Checks if `value` is classified as an `Array` object.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.\n * @example\n *\n * _.isArray([1, 2, 3]);\n * // => true\n *\n * _.isArray(function() { return arguments; }());\n * // => false\n */\nvar isArray = nativeIsArray || function(value) {\n  return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;\n};\n\nmodule.exports = isArray;\n\n},{\"../internal/getNative\":114,\"../internal/isLength\":125,\"../internal/isObjectLike\":126}],141:[function(require,module,exports){\nvar isArguments = require('./isArguments'),\n    isArray = require('./isArray'),\n    isArrayLike = require('../internal/isArrayLike'),\n    isFunction = require('./isFunction'),\n    isObjectLike = require('../internal/isObjectLike'),\n    isString = require('./isString'),\n    keys = require('../object/keys');\n\n/**\n * Checks if `value` is empty. A value is considered empty unless it's an\n * `arguments` object, array, string, or jQuery-like collection with a length\n * greater than `0` or an object with own enumerable properties.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {Array|Object|string} value The value to inspect.\n * @returns {boolean} Returns `true` if `value` is empty, else `false`.\n * @example\n *\n * _.isEmpty(null);\n * // => true\n *\n * _.isEmpty(true);\n * // => true\n *\n * _.isEmpty(1);\n * // => true\n *\n * _.isEmpty([1, 2, 3]);\n * // => false\n *\n * _.isEmpty({ 'a': 1 });\n * // => false\n */\nfunction isEmpty(value) {\n  if (value == null) {\n    return true;\n  }\n  if (isArrayLike(value) && (isArray(value) || isString(value) || isArguments(value) ||\n      (isObjectLike(value) && isFunction(value.splice)))) {\n    return !value.length;\n  }\n  return !keys(value).length;\n}\n\nmodule.exports = isEmpty;\n\n},{\"../internal/isArrayLike\":119,\"../internal/isObjectLike\":126,\"../object/keys\":149,\"./isArguments\":139,\"./isArray\":140,\"./isFunction\":142,\"./isString\":146}],142:[function(require,module,exports){\nvar isObject = require('./isObject');\n\n/** `Object#toString` result references. */\nvar funcTag = '[object Function]';\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/**\n * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objToString = objectProto.toString;\n\n/**\n * Checks if `value` is classified as a `Function` object.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.\n * @example\n *\n * _.isFunction(_);\n * // => true\n *\n * _.isFunction(/abc/);\n * // => false\n */\nfunction isFunction(value) {\n  // The use of `Object#toString` avoids issues with the `typeof` operator\n  // in older versions of Chrome and Safari which return 'function' for regexes\n  // and Safari 8 which returns 'object' for typed array constructors.\n  return isObject(value) && objToString.call(value) == funcTag;\n}\n\nmodule.exports = isFunction;\n\n},{\"./isObject\":144}],143:[function(require,module,exports){\nvar isFunction = require('./isFunction'),\n    isHostObject = require('../internal/isHostObject'),\n    isObjectLike = require('../internal/isObjectLike');\n\n/** Used to detect host constructors (Safari > 5). */\nvar reIsHostCtor = /^\\[object .+?Constructor\\]$/;\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/** Used to resolve the decompiled source of functions. */\nvar fnToString = Function.prototype.toString;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/** Used to detect if a method is native. */\nvar reIsNative = RegExp('^' +\n  fnToString.call(hasOwnProperty).replace(/[\\\\^$.*+?()[\\]{}|]/g, '\\\\$&')\n  .replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, '$1.*?') + '$'\n);\n\n/**\n * Checks if `value` is a native function.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a native function, else `false`.\n * @example\n *\n * _.isNative(Array.prototype.push);\n * // => true\n *\n * _.isNative(_);\n * // => false\n */\nfunction isNative(value) {\n  if (value == null) {\n    return false;\n  }\n  if (isFunction(value)) {\n    return reIsNative.test(fnToString.call(value));\n  }\n  return isObjectLike(value) && (isHostObject(value) ? reIsNative : reIsHostCtor).test(value);\n}\n\nmodule.exports = isNative;\n\n},{\"../internal/isHostObject\":120,\"../internal/isObjectLike\":126,\"./isFunction\":142}],144:[function(require,module,exports){\n/**\n * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.\n * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an object, else `false`.\n * @example\n *\n * _.isObject({});\n * // => true\n *\n * _.isObject([1, 2, 3]);\n * // => true\n *\n * _.isObject(1);\n * // => false\n */\nfunction isObject(value) {\n  // Avoid a V8 JIT bug in Chrome 19-20.\n  // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.\n  var type = typeof value;\n  return !!value && (type == 'object' || type == 'function');\n}\n\nmodule.exports = isObject;\n\n},{}],145:[function(require,module,exports){\nvar baseForIn = require('../internal/baseForIn'),\n    isArguments = require('./isArguments'),\n    isHostObject = require('../internal/isHostObject'),\n    isObjectLike = require('../internal/isObjectLike'),\n    support = require('../support');\n\n/** `Object#toString` result references. */\nvar objectTag = '[object Object]';\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objToString = objectProto.toString;\n\n/**\n * Checks if `value` is a plain object, that is, an object created by the\n * `Object` constructor or one with a `[[Prototype]]` of `null`.\n *\n * **Note:** This method assumes objects created by the `Object` constructor\n * have no inherited enumerable properties.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.\n * @example\n *\n * function Foo() {\n *   this.a = 1;\n * }\n *\n * _.isPlainObject(new Foo);\n * // => false\n *\n * _.isPlainObject([1, 2, 3]);\n * // => false\n *\n * _.isPlainObject({ 'x': 0, 'y': 0 });\n * // => true\n *\n * _.isPlainObject(Object.create(null));\n * // => true\n */\nfunction isPlainObject(value) {\n  var Ctor;\n\n  // Exit early for non `Object` objects.\n  if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostObject(value) && !isArguments(value)) ||\n      (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {\n    return false;\n  }\n  // IE < 9 iterates inherited properties before own properties. If the first\n  // iterated property is an object's own property then there are no inherited\n  // enumerable properties.\n  var result;\n  if (support.ownLast) {\n    baseForIn(value, function(subValue, key, object) {\n      result = hasOwnProperty.call(object, key);\n      return false;\n    });\n    return result !== false;\n  }\n  // In most environments an object's own properties are iterated before\n  // its inherited properties. If the last iterated property is an object's\n  // own property then there are no inherited enumerable properties.\n  baseForIn(value, function(subValue, key) {\n    result = key;\n  });\n  return result === undefined || hasOwnProperty.call(value, result);\n}\n\nmodule.exports = isPlainObject;\n\n},{\"../internal/baseForIn\":75,\"../internal/isHostObject\":120,\"../internal/isObjectLike\":126,\"../support\":153,\"./isArguments\":139}],146:[function(require,module,exports){\nvar isObjectLike = require('../internal/isObjectLike');\n\n/** `Object#toString` result references. */\nvar stringTag = '[object String]';\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/**\n * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objToString = objectProto.toString;\n\n/**\n * Checks if `value` is classified as a `String` primitive or object.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.\n * @example\n *\n * _.isString('abc');\n * // => true\n *\n * _.isString(1);\n * // => false\n */\nfunction isString(value) {\n  return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag);\n}\n\nmodule.exports = isString;\n\n},{\"../internal/isObjectLike\":126}],147:[function(require,module,exports){\nvar isLength = require('../internal/isLength'),\n    isObjectLike = require('../internal/isObjectLike');\n\n/** `Object#toString` result references. */\nvar argsTag = '[object Arguments]',\n    arrayTag = '[object Array]',\n    boolTag = '[object Boolean]',\n    dateTag = '[object Date]',\n    errorTag = '[object Error]',\n    funcTag = '[object Function]',\n    mapTag = '[object Map]',\n    numberTag = '[object Number]',\n    objectTag = '[object Object]',\n    regexpTag = '[object RegExp]',\n    setTag = '[object Set]',\n    stringTag = '[object String]',\n    weakMapTag = '[object WeakMap]';\n\nvar arrayBufferTag = '[object ArrayBuffer]',\n    float32Tag = '[object Float32Array]',\n    float64Tag = '[object Float64Array]',\n    int8Tag = '[object Int8Array]',\n    int16Tag = '[object Int16Array]',\n    int32Tag = '[object Int32Array]',\n    uint8Tag = '[object Uint8Array]',\n    uint8ClampedTag = '[object Uint8ClampedArray]',\n    uint16Tag = '[object Uint16Array]',\n    uint32Tag = '[object Uint32Array]';\n\n/** Used to identify `toStringTag` values of typed arrays. */\nvar typedArrayTags = {};\ntypedArrayTags[float32Tag] = typedArrayTags[float64Tag] =\ntypedArrayTags[int8Tag] = typedArrayTags[int16Tag] =\ntypedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =\ntypedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =\ntypedArrayTags[uint32Tag] = true;\ntypedArrayTags[argsTag] = typedArrayTags[arrayTag] =\ntypedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =\ntypedArrayTags[dateTag] = typedArrayTags[errorTag] =\ntypedArrayTags[funcTag] = typedArrayTags[mapTag] =\ntypedArrayTags[numberTag] = typedArrayTags[objectTag] =\ntypedArrayTags[regexpTag] = typedArrayTags[setTag] =\ntypedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;\n\n/** Used for native method references. */\nvar objectProto = Object.prototype;\n\n/**\n * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objToString = objectProto.toString;\n\n/**\n * Checks if `value` is classified as a typed array.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.\n * @example\n *\n * _.isTypedArray(new Uint8Array);\n * // => true\n *\n * _.isTypedArray([]);\n * // => false\n */\nfunction isTypedArray(value) {\n  return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)];\n}\n\nmodule.exports = isTypedArray;\n\n},{\"../internal/isLength\":125,\"../internal/isObjectLike\":126}],148:[function(require,module,exports){\n/**\n * Checks if `value` is `undefined`.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.\n * @example\n *\n * _.isUndefined(void 0);\n * // => true\n *\n * _.isUndefined(null);\n * // => false\n */\nfunction isUndefined(value) {\n  return value === undefined;\n}\n\nmodule.exports = isUndefined;\n\n},{}],149:[function(require,module,exports){\nvar getNative = require('../internal/getNative'),\n    isArrayLike = require('../internal/isArrayLike'),\n    isObject = require('../lang/isObject'),\n    shimKeys = require('../internal/shimKeys'),\n    support = require('../support');\n\n/* Native method references for those with the same name as other `lodash` methods. */\nvar nativeKeys = getNative(Object, 'keys');\n\n/**\n * Creates an array of the own enumerable property names of `object`.\n *\n * **Note:** Non-object values are coerced to objects. See the\n * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)\n * for more details.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names.\n * @example\n *\n * function Foo() {\n *   this.a = 1;\n *   this.b = 2;\n * }\n *\n * Foo.prototype.c = 3;\n *\n * _.keys(new Foo);\n * // => ['a', 'b'] (iteration order is not guaranteed)\n *\n * _.keys('hi');\n * // => ['0', '1']\n */\nvar keys = !nativeKeys ? shimKeys : function(object) {\n  var Ctor = object == null ? undefined : object.constructor;\n  if ((typeof Ctor == 'function' && Ctor.prototype === object) ||\n      (typeof object == 'function' ? support.enumPrototypes : isArrayLike(object))) {\n    return shimKeys(object);\n  }\n  return isObject(object) ? nativeKeys(object) : [];\n};\n\nmodule.exports = keys;\n\n},{\"../internal/getNative\":114,\"../internal/isArrayLike\":119,\"../internal/shimKeys\":134,\"../lang/isObject\":144,\"../support\":153}],150:[function(require,module,exports){\nvar arrayEach = require('../internal/arrayEach'),\n    isArguments = require('../lang/isArguments'),\n    isArray = require('../lang/isArray'),\n    isFunction = require('../lang/isFunction'),\n    isIndex = require('../internal/isIndex'),\n    isLength = require('../internal/isLength'),\n    isObject = require('../lang/isObject'),\n    isString = require('../lang/isString'),\n    support = require('../support');\n\n/** `Object#toString` result references. */\nvar arrayTag = '[object Array]',\n    boolTag = '[object Boolean]',\n    dateTag = '[object Date]',\n    errorTag = '[object Error]',\n    funcTag = '[object Function]',\n    numberTag = '[object Number]',\n    objectTag = '[object Object]',\n    regexpTag = '[object RegExp]',\n    stringTag = '[object String]';\n\n/** Used to fix the JScript `[[DontEnum]]` bug. */\nvar shadowProps = [\n  'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',\n  'toLocaleString', 'toString', 'valueOf'\n];\n\n/** Used for native method references. */\nvar errorProto = Error.prototype,\n    objectProto = Object.prototype,\n    stringProto = String.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objToString = objectProto.toString;\n\n/** Used to avoid iterating over non-enumerable properties in IE < 9. */\nvar nonEnumProps = {};\nnonEnumProps[arrayTag] = nonEnumProps[dateTag] = nonEnumProps[numberTag] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true };\nnonEnumProps[boolTag] = nonEnumProps[stringTag] = { 'constructor': true, 'toString': true, 'valueOf': true };\nnonEnumProps[errorTag] = nonEnumProps[funcTag] = nonEnumProps[regexpTag] = { 'constructor': true, 'toString': true };\nnonEnumProps[objectTag] = { 'constructor': true };\n\narrayEach(shadowProps, function(key) {\n  for (var tag in nonEnumProps) {\n    if (hasOwnProperty.call(nonEnumProps, tag)) {\n      var props = nonEnumProps[tag];\n      props[key] = hasOwnProperty.call(props, key);\n    }\n  }\n});\n\n/**\n * Creates an array of the own and inherited enumerable property names of `object`.\n *\n * **Note:** Non-object values are coerced to objects.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names.\n * @example\n *\n * function Foo() {\n *   this.a = 1;\n *   this.b = 2;\n * }\n *\n * Foo.prototype.c = 3;\n *\n * _.keysIn(new Foo);\n * // => ['a', 'b', 'c'] (iteration order is not guaranteed)\n */\nfunction keysIn(object) {\n  if (object == null) {\n    return [];\n  }\n  if (!isObject(object)) {\n    object = Object(object);\n  }\n  var length = object.length;\n\n  length = (length && isLength(length) &&\n    (isArray(object) || isArguments(object) || isString(object)) && length) || 0;\n\n  var Ctor = object.constructor,\n      index = -1,\n      proto = (isFunction(Ctor) && Ctor.prototype) || objectProto,\n      isProto = proto === object,\n      result = Array(length),\n      skipIndexes = length > 0,\n      skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error),\n      skipProto = support.enumPrototypes && isFunction(object);\n\n  while (++index < length) {\n    result[index] = (index + '');\n  }\n  // lodash skips the `constructor` property when it infers it's iterating\n  // over a `prototype` object because IE < 9 can't set the `[[Enumerable]]`\n  // attribute of an existing property and the `constructor` property of a\n  // prototype defaults to non-enumerable.\n  for (var key in object) {\n    if (!(skipProto && key == 'prototype') &&\n        !(skipErrorProps && (key == 'message' || key == 'name')) &&\n        !(skipIndexes && isIndex(key, length)) &&\n        !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {\n      result.push(key);\n    }\n  }\n  if (support.nonEnumShadows && object !== objectProto) {\n    var tag = object === stringProto ? stringTag : (object === errorProto ? errorTag : objToString.call(object)),\n        nonEnums = nonEnumProps[tag] || nonEnumProps[objectTag];\n\n    if (tag == objectTag) {\n      proto = objectProto;\n    }\n    length = shadowProps.length;\n    while (length--) {\n      key = shadowProps[length];\n      var nonEnum = nonEnums[key];\n      if (!(isProto && nonEnum) &&\n          (nonEnum ? hasOwnProperty.call(object, key) : object[key] !== proto[key])) {\n        result.push(key);\n      }\n    }\n  }\n  return result;\n}\n\nmodule.exports = keysIn;\n\n},{\"../internal/arrayEach\":63,\"../internal/isIndex\":121,\"../internal/isLength\":125,\"../lang/isArguments\":139,\"../lang/isArray\":140,\"../lang/isFunction\":142,\"../lang/isObject\":144,\"../lang/isString\":146,\"../support\":153}],151:[function(require,module,exports){\nvar keys = require('./keys'),\n    toObject = require('../internal/toObject');\n\n/**\n * Creates a two dimensional array of the key-value pairs for `object`,\n * e.g. `[[key1, value1], [key2, value2]]`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @returns {Array} Returns the new array of key-value pairs.\n * @example\n *\n * _.pairs({ 'barney': 36, 'fred': 40 });\n * // => [['barney', 36], ['fred', 40]] (iteration order is not guaranteed)\n */\nfunction pairs(object) {\n  object = toObject(object);\n\n  var index = -1,\n      props = keys(object),\n      length = props.length,\n      result = Array(length);\n\n  while (++index < length) {\n    var key = props[index];\n    result[index] = [key, object[key]];\n  }\n  return result;\n}\n\nmodule.exports = pairs;\n\n},{\"../internal/toObject\":135,\"./keys\":149}],152:[function(require,module,exports){\nvar baseValues = require('../internal/baseValues'),\n    keys = require('./keys');\n\n/**\n * Creates an array of the own enumerable property values of `object`.\n *\n * **Note:** Non-object values are coerced to objects.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property values.\n * @example\n *\n * function Foo() {\n *   this.a = 1;\n *   this.b = 2;\n * }\n *\n * Foo.prototype.c = 3;\n *\n * _.values(new Foo);\n * // => [1, 2] (iteration order is not guaranteed)\n *\n * _.values('hi');\n * // => ['h', 'i']\n */\nfunction values(object) {\n  return baseValues(object, keys(object));\n}\n\nmodule.exports = values;\n\n},{\"../internal/baseValues\":91,\"./keys\":149}],153:[function(require,module,exports){\n/** Used for native method references. */\nvar arrayProto = Array.prototype,\n    errorProto = Error.prototype,\n    objectProto = Object.prototype;\n\n/** Native method references. */\nvar propertyIsEnumerable = objectProto.propertyIsEnumerable,\n    splice = arrayProto.splice;\n\n/**\n * An object environment feature flags.\n *\n * @static\n * @memberOf _\n * @type Object\n */\nvar support = {};\n\n(function(x) {\n  var Ctor = function() { this.x = x; },\n      object = { '0': x, 'length': x },\n      props = [];\n\n  Ctor.prototype = { 'valueOf': x, 'y': x };\n  for (var key in new Ctor) { props.push(key); }\n\n  /**\n   * Detect if `name` or `message` properties of `Error.prototype` are\n   * enumerable by default (IE < 9, Safari < 5.1).\n   *\n   * @memberOf _.support\n   * @type boolean\n   */\n  support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') ||\n    propertyIsEnumerable.call(errorProto, 'name');\n\n  /**\n   * Detect if `prototype` properties are enumerable by default.\n   *\n   * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1\n   * (if the prototype or a property on the prototype has been set)\n   * incorrectly set the `[[Enumerable]]` value of a function's `prototype`\n   * property to `true`.\n   *\n   * @memberOf _.support\n   * @type boolean\n   */\n  support.enumPrototypes = propertyIsEnumerable.call(Ctor, 'prototype');\n\n  /**\n   * Detect if properties shadowing those on `Object.prototype` are non-enumerable.\n   *\n   * In IE < 9 an object's own properties, shadowing non-enumerable ones,\n   * are made non-enumerable as well (a.k.a the JScript `[[DontEnum]]` bug).\n   *\n   * @memberOf _.support\n   * @type boolean\n   */\n  support.nonEnumShadows = !/valueOf/.test(props);\n\n  /**\n   * Detect if own properties are iterated after inherited properties (IE < 9).\n   *\n   * @memberOf _.support\n   * @type boolean\n   */\n  support.ownLast = props[0] != 'x';\n\n  /**\n   * Detect if `Array#shift` and `Array#splice` augment array-like objects\n   * correctly.\n   *\n   * Firefox < 10, compatibility modes of IE 8, and IE < 9 have buggy Array\n   * `shift()` and `splice()` functions that fail to remove the last element,\n   * `value[0]`, of array-like objects even though the \"length\" property is\n   * set to `0`. The `shift()` method is buggy in compatibility modes of IE 8,\n   * while `splice()` is buggy regardless of mode in IE < 9.\n   *\n   * @memberOf _.support\n   * @type boolean\n   */\n  support.spliceObjects = (splice.call(object, 0, 1), !object[0]);\n\n  /**\n   * Detect lack of support for accessing string characters by index.\n   *\n   * IE < 8 can't access characters by index. IE 8 can only access characters\n   * by index on string literals, not string objects.\n   *\n   * @memberOf _.support\n   * @type boolean\n   */\n  support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx';\n}(1, 0));\n\nmodule.exports = support;\n\n},{}],154:[function(require,module,exports){\n/**\n * This method returns the first argument provided to it.\n *\n * @static\n * @memberOf _\n * @category Utility\n * @param {*} value Any value.\n * @returns {*} Returns `value`.\n * @example\n *\n * var object = { 'user': 'fred' };\n *\n * _.identity(object) === object;\n * // => true\n */\nfunction identity(value) {\n  return value;\n}\n\nmodule.exports = identity;\n\n},{}],155:[function(require,module,exports){\n/**\n * A no-operation function that returns `undefined` regardless of the\n * arguments it receives.\n *\n * @static\n * @memberOf _\n * @category Utility\n * @example\n *\n * var object = { 'user': 'fred' };\n *\n * _.noop(object) === undefined;\n * // => true\n */\nfunction noop() {\n  // No operation performed.\n}\n\nmodule.exports = noop;\n\n},{}],156:[function(require,module,exports){\nvar baseProperty = require('../internal/baseProperty'),\n    basePropertyDeep = require('../internal/basePropertyDeep'),\n    isKey = require('../internal/isKey');\n\n/**\n * Creates a function that returns the property value at `path` on a\n * given object.\n *\n * @static\n * @memberOf _\n * @category Utility\n * @param {Array|string} path The path of the property to get.\n * @returns {Function} Returns the new function.\n * @example\n *\n * var objects = [\n *   { 'a': { 'b': { 'c': 2 } } },\n *   { 'a': { 'b': { 'c': 1 } } }\n * ];\n *\n * _.map(objects, _.property('a.b.c'));\n * // => [2, 1]\n *\n * _.pluck(_.sortBy(objects, _.property(['a', 'b', 'c'])), 'a.b.c');\n * // => [1, 2]\n */\nfunction property(path) {\n  return isKey(path) ? baseProperty(path) : basePropertyDeep(path);\n}\n\nmodule.exports = property;\n\n},{\"../internal/baseProperty\":86,\"../internal/basePropertyDeep\":87,\"../internal/isKey\":123}],157:[function(require,module,exports){\n(function (process){\n// vim:ts=4:sts=4:sw=4:\n/*!\n *\n * Copyright 2009-2012 Kris Kowal under the terms of the MIT\n * license found at http://github.com/kriskowal/q/raw/master/LICENSE\n *\n * With parts by Tyler Close\n * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found\n * at http://www.opensource.org/licenses/mit-license.html\n * Forked at ref_send.js version: 2009-05-11\n *\n * With parts by Mark Miller\n * Copyright (C) 2011 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n(function (definition) {\n    \"use strict\";\n\n    // This file will function properly as a <script> tag, or a module\n    // using CommonJS and NodeJS or RequireJS module formats.  In\n    // Common/Node/RequireJS, the module exports the Q API and when\n    // executed as a simple <script>, it creates a Q global instead.\n\n    // Montage Require\n    if (typeof bootstrap === \"function\") {\n        bootstrap(\"promise\", definition);\n\n    // CommonJS\n    } else if (typeof exports === \"object\" && typeof module === \"object\") {\n        module.exports = definition();\n\n    // RequireJS\n    } else if (typeof define === \"function\" && define.amd) {\n        define(definition);\n\n    // SES (Secure EcmaScript)\n    } else if (typeof ses !== \"undefined\") {\n        if (!ses.ok()) {\n            return;\n        } else {\n            ses.makeQ = definition;\n        }\n\n    // <script>\n    } else if (typeof window !== \"undefined\" || typeof self !== \"undefined\") {\n        // Prefer window over self for add-on scripts. Use self for\n        // non-windowed contexts.\n        var global = typeof window !== \"undefined\" ? window : self;\n\n        // Get the `window` object, save the previous Q global\n        // and initialize Q as a global.\n        var previousQ = global.Q;\n        global.Q = definition();\n\n        // Add a noConflict function so Q can be removed from the\n        // global namespace.\n        global.Q.noConflict = function () {\n            global.Q = previousQ;\n            return this;\n        };\n\n    } else {\n        throw new Error(\"This environment was not anticipated by Q. Please file a bug.\");\n    }\n\n})(function () {\n\"use strict\";\n\nvar hasStacks = false;\ntry {\n    throw new Error();\n} catch (e) {\n    hasStacks = !!e.stack;\n}\n\n// All code after this point will be filtered from stack traces reported\n// by Q.\nvar qStartingLine = captureLine();\nvar qFileName;\n\n// shims\n\n// used for fallback in \"allResolved\"\nvar noop = function () {};\n\n// Use the fastest possible means to execute a task in a future turn\n// of the event loop.\nvar nextTick =(function () {\n    // linked list of tasks (single, with head node)\n    var head = {task: void 0, next: null};\n    var tail = head;\n    var flushing = false;\n    var requestTick = void 0;\n    var isNodeJS = false;\n    // queue for late tasks, used by unhandled rejection tracking\n    var laterQueue = [];\n\n    function flush() {\n        /* jshint loopfunc: true */\n        var task, domain;\n\n        while (head.next) {\n            head = head.next;\n            task = head.task;\n            head.task = void 0;\n            domain = head.domain;\n\n            if (domain) {\n                head.domain = void 0;\n                domain.enter();\n            }\n            runSingle(task, domain);\n\n        }\n        while (laterQueue.length) {\n            task = laterQueue.pop();\n            runSingle(task);\n        }\n        flushing = false;\n    }\n    // runs a single function in the async queue\n    function runSingle(task, domain) {\n        try {\n            task();\n\n        } catch (e) {\n            if (isNodeJS) {\n                // In node, uncaught exceptions are considered fatal errors.\n                // Re-throw them synchronously to interrupt flushing!\n\n                // Ensure continuation if the uncaught exception is suppressed\n                // listening \"uncaughtException\" events (as domains does).\n                // Continue in next event to avoid tick recursion.\n                if (domain) {\n                    domain.exit();\n                }\n                setTimeout(flush, 0);\n                if (domain) {\n                    domain.enter();\n                }\n\n                throw e;\n\n            } else {\n                // In browsers, uncaught exceptions are not fatal.\n                // Re-throw them asynchronously to avoid slow-downs.\n                setTimeout(function () {\n                    throw e;\n                }, 0);\n            }\n        }\n\n        if (domain) {\n            domain.exit();\n        }\n    }\n\n    nextTick = function (task) {\n        tail = tail.next = {\n            task: task,\n            domain: isNodeJS && process.domain,\n            next: null\n        };\n\n        if (!flushing) {\n            flushing = true;\n            requestTick();\n        }\n    };\n\n    if (typeof process === \"object\" &&\n        process.toString() === \"[object process]\" && process.nextTick) {\n        // Ensure Q is in a real Node environment, with a `process.nextTick`.\n        // To see through fake Node environments:\n        // * Mocha test runner - exposes a `process` global without a `nextTick`\n        // * Browserify - exposes a `process.nexTick` function that uses\n        //   `setTimeout`. In this case `setImmediate` is preferred because\n        //    it is faster. Browserify's `process.toString()` yields\n        //   \"[object Object]\", while in a real Node environment\n        //   `process.nextTick()` yields \"[object process]\".\n        isNodeJS = true;\n\n        requestTick = function () {\n            process.nextTick(flush);\n        };\n\n    } else if (typeof setImmediate === \"function\") {\n        // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate\n        if (typeof window !== \"undefined\") {\n            requestTick = setImmediate.bind(window, flush);\n        } else {\n            requestTick = function () {\n                setImmediate(flush);\n            };\n        }\n\n    } else if (typeof MessageChannel !== \"undefined\") {\n        // modern browsers\n        // http://www.nonblocking.io/2011/06/windownexttick.html\n        var channel = new MessageChannel();\n        // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create\n        // working message ports the first time a page loads.\n        channel.port1.onmessage = function () {\n            requestTick = requestPortTick;\n            channel.port1.onmessage = flush;\n            flush();\n        };\n        var requestPortTick = function () {\n            // Opera requires us to provide a message payload, regardless of\n            // whether we use it.\n            channel.port2.postMessage(0);\n        };\n        requestTick = function () {\n            setTimeout(flush, 0);\n            requestPortTick();\n        };\n\n    } else {\n        // old browsers\n        requestTick = function () {\n            setTimeout(flush, 0);\n        };\n    }\n    // runs a task after all other tasks have been run\n    // this is useful for unhandled rejection tracking that needs to happen\n    // after all `then`d tasks have been run.\n    nextTick.runAfter = function (task) {\n        laterQueue.push(task);\n        if (!flushing) {\n            flushing = true;\n            requestTick();\n        }\n    };\n    return nextTick;\n})();\n\n// Attempt to make generics safe in the face of downstream\n// modifications.\n// There is no situation where this is necessary.\n// If you need a security guarantee, these primordials need to be\n// deeply frozen anyway, and if you don’t need a security guarantee,\n// this is just plain paranoid.\n// However, this **might** have the nice side-effect of reducing the size of\n// the minified code by reducing x.call() to merely x()\n// See Mark Miller’s explanation of what this does.\n// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming\nvar call = Function.call;\nfunction uncurryThis(f) {\n    return function () {\n        return call.apply(f, arguments);\n    };\n}\n// This is equivalent, but slower:\n// uncurryThis = Function_bind.bind(Function_bind.call);\n// http://jsperf.com/uncurrythis\n\nvar array_slice = uncurryThis(Array.prototype.slice);\n\nvar array_reduce = uncurryThis(\n    Array.prototype.reduce || function (callback, basis) {\n        var index = 0,\n            length = this.length;\n        // concerning the initial value, if one is not provided\n        if (arguments.length === 1) {\n            // seek to the first value in the array, accounting\n            // for the possibility that is is a sparse array\n            do {\n                if (index in this) {\n                    basis = this[index++];\n                    break;\n                }\n                if (++index >= length) {\n                    throw new TypeError();\n                }\n            } while (1);\n        }\n        // reduce\n        for (; index < length; index++) {\n            // account for the possibility that the array is sparse\n            if (index in this) {\n                basis = callback(basis, this[index], index);\n            }\n        }\n        return basis;\n    }\n);\n\nvar array_indexOf = uncurryThis(\n    Array.prototype.indexOf || function (value) {\n        // not a very good shim, but good enough for our one use of it\n        for (var i = 0; i < this.length; i++) {\n            if (this[i] === value) {\n                return i;\n            }\n        }\n        return -1;\n    }\n);\n\nvar array_map = uncurryThis(\n    Array.prototype.map || function (callback, thisp) {\n        var self = this;\n        var collect = [];\n        array_reduce(self, function (undefined, value, index) {\n            collect.push(callback.call(thisp, value, index, self));\n        }, void 0);\n        return collect;\n    }\n);\n\nvar object_create = Object.create || function (prototype) {\n    function Type() { }\n    Type.prototype = prototype;\n    return new Type();\n};\n\nvar object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);\n\nvar object_keys = Object.keys || function (object) {\n    var keys = [];\n    for (var key in object) {\n        if (object_hasOwnProperty(object, key)) {\n            keys.push(key);\n        }\n    }\n    return keys;\n};\n\nvar object_toString = uncurryThis(Object.prototype.toString);\n\nfunction isObject(value) {\n    return value === Object(value);\n}\n\n// generator related shims\n\n// FIXME: Remove this function once ES6 generators are in SpiderMonkey.\nfunction isStopIteration(exception) {\n    return (\n        object_toString(exception) === \"[object StopIteration]\" ||\n        exception instanceof QReturnValue\n    );\n}\n\n// FIXME: Remove this helper and Q.return once ES6 generators are in\n// SpiderMonkey.\nvar QReturnValue;\nif (typeof ReturnValue !== \"undefined\") {\n    QReturnValue = ReturnValue;\n} else {\n    QReturnValue = function (value) {\n        this.value = value;\n    };\n}\n\n// long stack traces\n\nvar STACK_JUMP_SEPARATOR = \"From previous event:\";\n\nfunction makeStackTraceLong(error, promise) {\n    // If possible, transform the error stack trace by removing Node and Q\n    // cruft, then concatenating with the stack trace of `promise`. See #57.\n    if (hasStacks &&\n        promise.stack &&\n        typeof error === \"object\" &&\n        error !== null &&\n        error.stack &&\n        error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1\n    ) {\n        var stacks = [];\n        for (var p = promise; !!p; p = p.source) {\n            if (p.stack) {\n                stacks.unshift(p.stack);\n            }\n        }\n        stacks.unshift(error.stack);\n\n        var concatedStacks = stacks.join(\"\\n\" + STACK_JUMP_SEPARATOR + \"\\n\");\n        error.stack = filterStackString(concatedStacks);\n    }\n}\n\nfunction filterStackString(stackString) {\n    var lines = stackString.split(\"\\n\");\n    var desiredLines = [];\n    for (var i = 0; i < lines.length; ++i) {\n        var line = lines[i];\n\n        if (!isInternalFrame(line) && !isNodeFrame(line) && line) {\n            desiredLines.push(line);\n        }\n    }\n    return desiredLines.join(\"\\n\");\n}\n\nfunction isNodeFrame(stackLine) {\n    return stackLine.indexOf(\"(module.js:\") !== -1 ||\n           stackLine.indexOf(\"(node.js:\") !== -1;\n}\n\nfunction getFileNameAndLineNumber(stackLine) {\n    // Named functions: \"at functionName (filename:lineNumber:columnNumber)\"\n    // In IE10 function name can have spaces (\"Anonymous function\") O_o\n    var attempt1 = /at .+ \\((.+):(\\d+):(?:\\d+)\\)$/.exec(stackLine);\n    if (attempt1) {\n        return [attempt1[1], Number(attempt1[2])];\n    }\n\n    // Anonymous functions: \"at filename:lineNumber:columnNumber\"\n    var attempt2 = /at ([^ ]+):(\\d+):(?:\\d+)$/.exec(stackLine);\n    if (attempt2) {\n        return [attempt2[1], Number(attempt2[2])];\n    }\n\n    // Firefox style: \"function@filename:lineNumber or @filename:lineNumber\"\n    var attempt3 = /.*@(.+):(\\d+)$/.exec(stackLine);\n    if (attempt3) {\n        return [attempt3[1], Number(attempt3[2])];\n    }\n}\n\nfunction isInternalFrame(stackLine) {\n    var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);\n\n    if (!fileNameAndLineNumber) {\n        return false;\n    }\n\n    var fileName = fileNameAndLineNumber[0];\n    var lineNumber = fileNameAndLineNumber[1];\n\n    return fileName === qFileName &&\n        lineNumber >= qStartingLine &&\n        lineNumber <= qEndingLine;\n}\n\n// discover own file name and line number range for filtering stack\n// traces\nfunction captureLine() {\n    if (!hasStacks) {\n        return;\n    }\n\n    try {\n        throw new Error();\n    } catch (e) {\n        var lines = e.stack.split(\"\\n\");\n        var firstLine = lines[0].indexOf(\"@\") > 0 ? lines[1] : lines[2];\n        var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);\n        if (!fileNameAndLineNumber) {\n            return;\n        }\n\n        qFileName = fileNameAndLineNumber[0];\n        return fileNameAndLineNumber[1];\n    }\n}\n\nfunction deprecate(callback, name, alternative) {\n    return function () {\n        if (typeof console !== \"undefined\" &&\n            typeof console.warn === \"function\") {\n            console.warn(name + \" is deprecated, use \" + alternative +\n                         \" instead.\", new Error(\"\").stack);\n        }\n        return callback.apply(callback, arguments);\n    };\n}\n\n// end of shims\n// beginning of real work\n\n/**\n * Constructs a promise for an immediate reference, passes promises through, or\n * coerces promises from different systems.\n * @param value immediate reference or promise\n */\nfunction Q(value) {\n    // If the object is already a Promise, return it directly.  This enables\n    // the resolve function to both be used to created references from objects,\n    // but to tolerably coerce non-promises to promises.\n    if (value instanceof Promise) {\n        return value;\n    }\n\n    // assimilate thenables\n    if (isPromiseAlike(value)) {\n        return coerce(value);\n    } else {\n        return fulfill(value);\n    }\n}\nQ.resolve = Q;\n\n/**\n * Performs a task in a future turn of the event loop.\n * @param {Function} task\n */\nQ.nextTick = nextTick;\n\n/**\n * Controls whether or not long stack traces will be on\n */\nQ.longStackSupport = false;\n\n// enable long stacks if Q_DEBUG is set\nif (typeof process === \"object\" && process && process.env && process.env.Q_DEBUG) {\n    Q.longStackSupport = true;\n}\n\n/**\n * Constructs a {promise, resolve, reject} object.\n *\n * `resolve` is a callback to invoke with a more resolved value for the\n * promise. To fulfill the promise, invoke `resolve` with any value that is\n * not a thenable. To reject the promise, invoke `resolve` with a rejected\n * thenable, or invoke `reject` with the reason directly. To resolve the\n * promise to another thenable, thus putting it in the same state, invoke\n * `resolve` with that other thenable.\n */\nQ.defer = defer;\nfunction defer() {\n    // if \"messages\" is an \"Array\", that indicates that the promise has not yet\n    // been resolved.  If it is \"undefined\", it has been resolved.  Each\n    // element of the messages array is itself an array of complete arguments to\n    // forward to the resolved promise.  We coerce the resolution value to a\n    // promise using the `resolve` function because it handles both fully\n    // non-thenable values and other thenables gracefully.\n    var messages = [], progressListeners = [], resolvedPromise;\n\n    var deferred = object_create(defer.prototype);\n    var promise = object_create(Promise.prototype);\n\n    promise.promiseDispatch = function (resolve, op, operands) {\n        var args = array_slice(arguments);\n        if (messages) {\n            messages.push(args);\n            if (op === \"when\" && operands[1]) { // progress operand\n                progressListeners.push(operands[1]);\n            }\n        } else {\n            Q.nextTick(function () {\n                resolvedPromise.promiseDispatch.apply(resolvedPromise, args);\n            });\n        }\n    };\n\n    // XXX deprecated\n    promise.valueOf = function () {\n        if (messages) {\n            return promise;\n        }\n        var nearerValue = nearer(resolvedPromise);\n        if (isPromise(nearerValue)) {\n            resolvedPromise = nearerValue; // shorten chain\n        }\n        return nearerValue;\n    };\n\n    promise.inspect = function () {\n        if (!resolvedPromise) {\n            return { state: \"pending\" };\n        }\n        return resolvedPromise.inspect();\n    };\n\n    if (Q.longStackSupport && hasStacks) {\n        try {\n            throw new Error();\n        } catch (e) {\n            // NOTE: don't try to use `Error.captureStackTrace` or transfer the\n            // accessor around; that causes memory leaks as per GH-111. Just\n            // reify the stack trace as a string ASAP.\n            //\n            // At the same time, cut off the first line; it's always just\n            // \"[object Promise]\\n\", as per the `toString`.\n            promise.stack = e.stack.substring(e.stack.indexOf(\"\\n\") + 1);\n        }\n    }\n\n    // NOTE: we do the checks for `resolvedPromise` in each method, instead of\n    // consolidating them into `become`, since otherwise we'd create new\n    // promises with the lines `become(whatever(value))`. See e.g. GH-252.\n\n    function become(newPromise) {\n        resolvedPromise = newPromise;\n        promise.source = newPromise;\n\n        array_reduce(messages, function (undefined, message) {\n            Q.nextTick(function () {\n                newPromise.promiseDispatch.apply(newPromise, message);\n            });\n        }, void 0);\n\n        messages = void 0;\n        progressListeners = void 0;\n    }\n\n    deferred.promise = promise;\n    deferred.resolve = function (value) {\n        if (resolvedPromise) {\n            return;\n        }\n\n        become(Q(value));\n    };\n\n    deferred.fulfill = function (value) {\n        if (resolvedPromise) {\n            return;\n        }\n\n        become(fulfill(value));\n    };\n    deferred.reject = function (reason) {\n        if (resolvedPromise) {\n            return;\n        }\n\n        become(reject(reason));\n    };\n    deferred.notify = function (progress) {\n        if (resolvedPromise) {\n            return;\n        }\n\n        array_reduce(progressListeners, function (undefined, progressListener) {\n            Q.nextTick(function () {\n                progressListener(progress);\n            });\n        }, void 0);\n    };\n\n    return deferred;\n}\n\n/**\n * Creates a Node-style callback that will resolve or reject the deferred\n * promise.\n * @returns a nodeback\n */\ndefer.prototype.makeNodeResolver = function () {\n    var self = this;\n    return function (error, value) {\n        if (error) {\n            self.reject(error);\n        } else if (arguments.length > 2) {\n            self.resolve(array_slice(arguments, 1));\n        } else {\n            self.resolve(value);\n        }\n    };\n};\n\n/**\n * @param resolver {Function} a function that returns nothing and accepts\n * the resolve, reject, and notify functions for a deferred.\n * @returns a promise that may be resolved with the given resolve and reject\n * functions, or rejected by a thrown exception in resolver\n */\nQ.Promise = promise; // ES6\nQ.promise = promise;\nfunction promise(resolver) {\n    if (typeof resolver !== \"function\") {\n        throw new TypeError(\"resolver must be a function.\");\n    }\n    var deferred = defer();\n    try {\n        resolver(deferred.resolve, deferred.reject, deferred.notify);\n    } catch (reason) {\n        deferred.reject(reason);\n    }\n    return deferred.promise;\n}\n\npromise.race = race; // ES6\npromise.all = all; // ES6\npromise.reject = reject; // ES6\npromise.resolve = Q; // ES6\n\n// XXX experimental.  This method is a way to denote that a local value is\n// serializable and should be immediately dispatched to a remote upon request,\n// instead of passing a reference.\nQ.passByCopy = function (object) {\n    //freeze(object);\n    //passByCopies.set(object, true);\n    return object;\n};\n\nPromise.prototype.passByCopy = function () {\n    //freeze(object);\n    //passByCopies.set(object, true);\n    return this;\n};\n\n/**\n * If two promises eventually fulfill to the same value, promises that value,\n * but otherwise rejects.\n * @param x {Any*}\n * @param y {Any*}\n * @returns {Any*} a promise for x and y if they are the same, but a rejection\n * otherwise.\n *\n */\nQ.join = function (x, y) {\n    return Q(x).join(y);\n};\n\nPromise.prototype.join = function (that) {\n    return Q([this, that]).spread(function (x, y) {\n        if (x === y) {\n            // TODO: \"===\" should be Object.is or equiv\n            return x;\n        } else {\n            throw new Error(\"Can't join: not the same: \" + x + \" \" + y);\n        }\n    });\n};\n\n/**\n * Returns a promise for the first of an array of promises to become settled.\n * @param answers {Array[Any*]} promises to race\n * @returns {Any*} the first promise to be settled\n */\nQ.race = race;\nfunction race(answerPs) {\n    return promise(function (resolve, reject) {\n        // Switch to this once we can assume at least ES5\n        // answerPs.forEach(function (answerP) {\n        //     Q(answerP).then(resolve, reject);\n        // });\n        // Use this in the meantime\n        for (var i = 0, len = answerPs.length; i < len; i++) {\n            Q(answerPs[i]).then(resolve, reject);\n        }\n    });\n}\n\nPromise.prototype.race = function () {\n    return this.then(Q.race);\n};\n\n/**\n * Constructs a Promise with a promise descriptor object and optional fallback\n * function.  The descriptor contains methods like when(rejected), get(name),\n * set(name, value), post(name, args), and delete(name), which all\n * return either a value, a promise for a value, or a rejection.  The fallback\n * accepts the operation name, a resolver, and any further arguments that would\n * have been forwarded to the appropriate method above had a method been\n * provided with the proper name.  The API makes no guarantees about the nature\n * of the returned object, apart from that it is usable whereever promises are\n * bought and sold.\n */\nQ.makePromise = Promise;\nfunction Promise(descriptor, fallback, inspect) {\n    if (fallback === void 0) {\n        fallback = function (op) {\n            return reject(new Error(\n                \"Promise does not support operation: \" + op\n            ));\n        };\n    }\n    if (inspect === void 0) {\n        inspect = function () {\n            return {state: \"unknown\"};\n        };\n    }\n\n    var promise = object_create(Promise.prototype);\n\n    promise.promiseDispatch = function (resolve, op, args) {\n        var result;\n        try {\n            if (descriptor[op]) {\n                result = descriptor[op].apply(promise, args);\n            } else {\n                result = fallback.call(promise, op, args);\n            }\n        } catch (exception) {\n            result = reject(exception);\n        }\n        if (resolve) {\n            resolve(result);\n        }\n    };\n\n    promise.inspect = inspect;\n\n    // XXX deprecated `valueOf` and `exception` support\n    if (inspect) {\n        var inspected = inspect();\n        if (inspected.state === \"rejected\") {\n            promise.exception = inspected.reason;\n        }\n\n        promise.valueOf = function () {\n            var inspected = inspect();\n            if (inspected.state === \"pending\" ||\n                inspected.state === \"rejected\") {\n                return promise;\n            }\n            return inspected.value;\n        };\n    }\n\n    return promise;\n}\n\nPromise.prototype.toString = function () {\n    return \"[object Promise]\";\n};\n\nPromise.prototype.then = function (fulfilled, rejected, progressed) {\n    var self = this;\n    var deferred = defer();\n    var done = false;   // ensure the untrusted promise makes at most a\n                        // single call to one of the callbacks\n\n    function _fulfilled(value) {\n        try {\n            return typeof fulfilled === \"function\" ? fulfilled(value) : value;\n        } catch (exception) {\n            return reject(exception);\n        }\n    }\n\n    function _rejected(exception) {\n        if (typeof rejected === \"function\") {\n            makeStackTraceLong(exception, self);\n            try {\n                return rejected(exception);\n            } catch (newException) {\n                return reject(newException);\n            }\n        }\n        return reject(exception);\n    }\n\n    function _progressed(value) {\n        return typeof progressed === \"function\" ? progressed(value) : value;\n    }\n\n    Q.nextTick(function () {\n        self.promiseDispatch(function (value) {\n            if (done) {\n                return;\n            }\n            done = true;\n\n            deferred.resolve(_fulfilled(value));\n        }, \"when\", [function (exception) {\n            if (done) {\n                return;\n            }\n            done = true;\n\n            deferred.resolve(_rejected(exception));\n        }]);\n    });\n\n    // Progress propagator need to be attached in the current tick.\n    self.promiseDispatch(void 0, \"when\", [void 0, function (value) {\n        var newValue;\n        var threw = false;\n        try {\n            newValue = _progressed(value);\n        } catch (e) {\n            threw = true;\n            if (Q.onerror) {\n                Q.onerror(e);\n            } else {\n                throw e;\n            }\n        }\n\n        if (!threw) {\n            deferred.notify(newValue);\n        }\n    }]);\n\n    return deferred.promise;\n};\n\nQ.tap = function (promise, callback) {\n    return Q(promise).tap(callback);\n};\n\n/**\n * Works almost like \"finally\", but not called for rejections.\n * Original resolution value is passed through callback unaffected.\n * Callback may return a promise that will be awaited for.\n * @param {Function} callback\n * @returns {Q.Promise}\n * @example\n * doSomething()\n *   .then(...)\n *   .tap(console.log)\n *   .then(...);\n */\nPromise.prototype.tap = function (callback) {\n    callback = Q(callback);\n\n    return this.then(function (value) {\n        return callback.fcall(value).thenResolve(value);\n    });\n};\n\n/**\n * Registers an observer on a promise.\n *\n * Guarantees:\n *\n * 1. that fulfilled and rejected will be called only once.\n * 2. that either the fulfilled callback or the rejected callback will be\n *    called, but not both.\n * 3. that fulfilled and rejected will not be called in this turn.\n *\n * @param value      promise or immediate reference to observe\n * @param fulfilled  function to be called with the fulfilled value\n * @param rejected   function to be called with the rejection exception\n * @param progressed function to be called on any progress notifications\n * @return promise for the return value from the invoked callback\n */\nQ.when = when;\nfunction when(value, fulfilled, rejected, progressed) {\n    return Q(value).then(fulfilled, rejected, progressed);\n}\n\nPromise.prototype.thenResolve = function (value) {\n    return this.then(function () { return value; });\n};\n\nQ.thenResolve = function (promise, value) {\n    return Q(promise).thenResolve(value);\n};\n\nPromise.prototype.thenReject = function (reason) {\n    return this.then(function () { throw reason; });\n};\n\nQ.thenReject = function (promise, reason) {\n    return Q(promise).thenReject(reason);\n};\n\n/**\n * If an object is not a promise, it is as \"near\" as possible.\n * If a promise is rejected, it is as \"near\" as possible too.\n * If it’s a fulfilled promise, the fulfillment value is nearer.\n * If it’s a deferred promise and the deferred has been resolved, the\n * resolution is \"nearer\".\n * @param object\n * @returns most resolved (nearest) form of the object\n */\n\n// XXX should we re-do this?\nQ.nearer = nearer;\nfunction nearer(value) {\n    if (isPromise(value)) {\n        var inspected = value.inspect();\n        if (inspected.state === \"fulfilled\") {\n            return inspected.value;\n        }\n    }\n    return value;\n}\n\n/**\n * @returns whether the given object is a promise.\n * Otherwise it is a fulfilled value.\n */\nQ.isPromise = isPromise;\nfunction isPromise(object) {\n    return object instanceof Promise;\n}\n\nQ.isPromiseAlike = isPromiseAlike;\nfunction isPromiseAlike(object) {\n    return isObject(object) && typeof object.then === \"function\";\n}\n\n/**\n * @returns whether the given object is a pending promise, meaning not\n * fulfilled or rejected.\n */\nQ.isPending = isPending;\nfunction isPending(object) {\n    return isPromise(object) && object.inspect().state === \"pending\";\n}\n\nPromise.prototype.isPending = function () {\n    return this.inspect().state === \"pending\";\n};\n\n/**\n * @returns whether the given object is a value or fulfilled\n * promise.\n */\nQ.isFulfilled = isFulfilled;\nfunction isFulfilled(object) {\n    return !isPromise(object) || object.inspect().state === \"fulfilled\";\n}\n\nPromise.prototype.isFulfilled = function () {\n    return this.inspect().state === \"fulfilled\";\n};\n\n/**\n * @returns whether the given object is a rejected promise.\n */\nQ.isRejected = isRejected;\nfunction isRejected(object) {\n    return isPromise(object) && object.inspect().state === \"rejected\";\n}\n\nPromise.prototype.isRejected = function () {\n    return this.inspect().state === \"rejected\";\n};\n\n//// BEGIN UNHANDLED REJECTION TRACKING\n\n// This promise library consumes exceptions thrown in handlers so they can be\n// handled by a subsequent promise.  The exceptions get added to this array when\n// they are created, and removed when they are handled.  Note that in ES6 or\n// shimmed environments, this would naturally be a `Set`.\nvar unhandledReasons = [];\nvar unhandledRejections = [];\nvar reportedUnhandledRejections = [];\nvar trackUnhandledRejections = true;\n\nfunction resetUnhandledRejections() {\n    unhandledReasons.length = 0;\n    unhandledRejections.length = 0;\n\n    if (!trackUnhandledRejections) {\n        trackUnhandledRejections = true;\n    }\n}\n\nfunction trackRejection(promise, reason) {\n    if (!trackUnhandledRejections) {\n        return;\n    }\n    if (typeof process === \"object\" && typeof process.emit === \"function\") {\n        Q.nextTick.runAfter(function () {\n            if (array_indexOf(unhandledRejections, promise) !== -1) {\n                process.emit(\"unhandledRejection\", reason, promise);\n                reportedUnhandledRejections.push(promise);\n            }\n        });\n    }\n\n    unhandledRejections.push(promise);\n    if (reason && typeof reason.stack !== \"undefined\") {\n        unhandledReasons.push(reason.stack);\n    } else {\n        unhandledReasons.push(\"(no stack) \" + reason);\n    }\n}\n\nfunction untrackRejection(promise) {\n    if (!trackUnhandledRejections) {\n        return;\n    }\n\n    var at = array_indexOf(unhandledRejections, promise);\n    if (at !== -1) {\n        if (typeof process === \"object\" && typeof process.emit === \"function\") {\n            Q.nextTick.runAfter(function () {\n                var atReport = array_indexOf(reportedUnhandledRejections, promise);\n                if (atReport !== -1) {\n                    process.emit(\"rejectionHandled\", unhandledReasons[at], promise);\n                    reportedUnhandledRejections.splice(atReport, 1);\n                }\n            });\n        }\n        unhandledRejections.splice(at, 1);\n        unhandledReasons.splice(at, 1);\n    }\n}\n\nQ.resetUnhandledRejections = resetUnhandledRejections;\n\nQ.getUnhandledReasons = function () {\n    // Make a copy so that consumers can't interfere with our internal state.\n    return unhandledReasons.slice();\n};\n\nQ.stopUnhandledRejectionTracking = function () {\n    resetUnhandledRejections();\n    trackUnhandledRejections = false;\n};\n\nresetUnhandledRejections();\n\n//// END UNHANDLED REJECTION TRACKING\n\n/**\n * Constructs a rejected promise.\n * @param reason value describing the failure\n */\nQ.reject = reject;\nfunction reject(reason) {\n    var rejection = Promise({\n        \"when\": function (rejected) {\n            // note that the error has been handled\n            if (rejected) {\n                untrackRejection(this);\n            }\n            return rejected ? rejected(reason) : this;\n        }\n    }, function fallback() {\n        return this;\n    }, function inspect() {\n        return { state: \"rejected\", reason: reason };\n    });\n\n    // Note that the reason has not been handled.\n    trackRejection(rejection, reason);\n\n    return rejection;\n}\n\n/**\n * Constructs a fulfilled promise for an immediate reference.\n * @param value immediate reference\n */\nQ.fulfill = fulfill;\nfunction fulfill(value) {\n    return Promise({\n        \"when\": function () {\n            return value;\n        },\n        \"get\": function (name) {\n            return value[name];\n        },\n        \"set\": function (name, rhs) {\n            value[name] = rhs;\n        },\n        \"delete\": function (name) {\n            delete value[name];\n        },\n        \"post\": function (name, args) {\n            // Mark Miller proposes that post with no name should apply a\n            // promised function.\n            if (name === null || name === void 0) {\n                return value.apply(void 0, args);\n            } else {\n                return value[name].apply(value, args);\n            }\n        },\n        \"apply\": function (thisp, args) {\n            return value.apply(thisp, args);\n        },\n        \"keys\": function () {\n            return object_keys(value);\n        }\n    }, void 0, function inspect() {\n        return { state: \"fulfilled\", value: value };\n    });\n}\n\n/**\n * Converts thenables to Q promises.\n * @param promise thenable promise\n * @returns a Q promise\n */\nfunction coerce(promise) {\n    var deferred = defer();\n    Q.nextTick(function () {\n        try {\n            promise.then(deferred.resolve, deferred.reject, deferred.notify);\n        } catch (exception) {\n            deferred.reject(exception);\n        }\n    });\n    return deferred.promise;\n}\n\n/**\n * Annotates an object such that it will never be\n * transferred away from this process over any promise\n * communication channel.\n * @param object\n * @returns promise a wrapping of that object that\n * additionally responds to the \"isDef\" message\n * without a rejection.\n */\nQ.master = master;\nfunction master(object) {\n    return Promise({\n        \"isDef\": function () {}\n    }, function fallback(op, args) {\n        return dispatch(object, op, args);\n    }, function () {\n        return Q(object).inspect();\n    });\n}\n\n/**\n * Spreads the values of a promised array of arguments into the\n * fulfillment callback.\n * @param fulfilled callback that receives variadic arguments from the\n * promised array\n * @param rejected callback that receives the exception if the promise\n * is rejected.\n * @returns a promise for the return value or thrown exception of\n * either callback.\n */\nQ.spread = spread;\nfunction spread(value, fulfilled, rejected) {\n    return Q(value).spread(fulfilled, rejected);\n}\n\nPromise.prototype.spread = function (fulfilled, rejected) {\n    return this.all().then(function (array) {\n        return fulfilled.apply(void 0, array);\n    }, rejected);\n};\n\n/**\n * The async function is a decorator for generator functions, turning\n * them into asynchronous generators.  Although generators are only part\n * of the newest ECMAScript 6 drafts, this code does not cause syntax\n * errors in older engines.  This code should continue to work and will\n * in fact improve over time as the language improves.\n *\n * ES6 generators are currently part of V8 version 3.19 with the\n * --harmony-generators runtime flag enabled.  SpiderMonkey has had them\n * for longer, but under an older Python-inspired form.  This function\n * works on both kinds of generators.\n *\n * Decorates a generator function such that:\n *  - it may yield promises\n *  - execution will continue when that promise is fulfilled\n *  - the value of the yield expression will be the fulfilled value\n *  - it returns a promise for the return value (when the generator\n *    stops iterating)\n *  - the decorated function returns a promise for the return value\n *    of the generator or the first rejected promise among those\n *    yielded.\n *  - if an error is thrown in the generator, it propagates through\n *    every following yield until it is caught, or until it escapes\n *    the generator function altogether, and is translated into a\n *    rejection for the promise returned by the decorated generator.\n */\nQ.async = async;\nfunction async(makeGenerator) {\n    return function () {\n        // when verb is \"send\", arg is a value\n        // when verb is \"throw\", arg is an exception\n        function continuer(verb, arg) {\n            var result;\n\n            // Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only\n            // engine that has a deployed base of browsers that support generators.\n            // However, SM's generators use the Python-inspired semantics of\n            // outdated ES6 drafts.  We would like to support ES6, but we'd also\n            // like to make it possible to use generators in deployed browsers, so\n            // we also support Python-style generators.  At some point we can remove\n            // this block.\n\n            if (typeof StopIteration === \"undefined\") {\n                // ES6 Generators\n                try {\n                    result = generator[verb](arg);\n                } catch (exception) {\n                    return reject(exception);\n                }\n                if (result.done) {\n                    return Q(result.value);\n                } else {\n                    return when(result.value, callback, errback);\n                }\n            } else {\n                // SpiderMonkey Generators\n                // FIXME: Remove this case when SM does ES6 generators.\n                try {\n                    result = generator[verb](arg);\n                } catch (exception) {\n                    if (isStopIteration(exception)) {\n                        return Q(exception.value);\n                    } else {\n                        return reject(exception);\n                    }\n                }\n                return when(result, callback, errback);\n            }\n        }\n        var generator = makeGenerator.apply(this, arguments);\n        var callback = continuer.bind(continuer, \"next\");\n        var errback = continuer.bind(continuer, \"throw\");\n        return callback();\n    };\n}\n\n/**\n * The spawn function is a small wrapper around async that immediately\n * calls the generator and also ends the promise chain, so that any\n * unhandled errors are thrown instead of forwarded to the error\n * handler. This is useful because it's extremely common to run\n * generators at the top-level to work with libraries.\n */\nQ.spawn = spawn;\nfunction spawn(makeGenerator) {\n    Q.done(Q.async(makeGenerator)());\n}\n\n// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.\n/**\n * Throws a ReturnValue exception to stop an asynchronous generator.\n *\n * This interface is a stop-gap measure to support generator return\n * values in older Firefox/SpiderMonkey.  In browsers that support ES6\n * generators like Chromium 29, just use \"return\" in your generator\n * functions.\n *\n * @param value the return value for the surrounding generator\n * @throws ReturnValue exception with the value.\n * @example\n * // ES6 style\n * Q.async(function* () {\n *      var foo = yield getFooPromise();\n *      var bar = yield getBarPromise();\n *      return foo + bar;\n * })\n * // Older SpiderMonkey style\n * Q.async(function () {\n *      var foo = yield getFooPromise();\n *      var bar = yield getBarPromise();\n *      Q.return(foo + bar);\n * })\n */\nQ[\"return\"] = _return;\nfunction _return(value) {\n    throw new QReturnValue(value);\n}\n\n/**\n * The promised function decorator ensures that any promise arguments\n * are settled and passed as values (`this` is also settled and passed\n * as a value).  It will also ensure that the result of a function is\n * always a promise.\n *\n * @example\n * var add = Q.promised(function (a, b) {\n *     return a + b;\n * });\n * add(Q(a), Q(B));\n *\n * @param {function} callback The function to decorate\n * @returns {function} a function that has been decorated.\n */\nQ.promised = promised;\nfunction promised(callback) {\n    return function () {\n        return spread([this, all(arguments)], function (self, args) {\n            return callback.apply(self, args);\n        });\n    };\n}\n\n/**\n * sends a message to a value in a future turn\n * @param object* the recipient\n * @param op the name of the message operation, e.g., \"when\",\n * @param args further arguments to be forwarded to the operation\n * @returns result {Promise} a promise for the result of the operation\n */\nQ.dispatch = dispatch;\nfunction dispatch(object, op, args) {\n    return Q(object).dispatch(op, args);\n}\n\nPromise.prototype.dispatch = function (op, args) {\n    var self = this;\n    var deferred = defer();\n    Q.nextTick(function () {\n        self.promiseDispatch(deferred.resolve, op, args);\n    });\n    return deferred.promise;\n};\n\n/**\n * Gets the value of a property in a future turn.\n * @param object    promise or immediate reference for target object\n * @param name      name of property to get\n * @return promise for the property value\n */\nQ.get = function (object, key) {\n    return Q(object).dispatch(\"get\", [key]);\n};\n\nPromise.prototype.get = function (key) {\n    return this.dispatch(\"get\", [key]);\n};\n\n/**\n * Sets the value of a property in a future turn.\n * @param object    promise or immediate reference for object object\n * @param name      name of property to set\n * @param value     new value of property\n * @return promise for the return value\n */\nQ.set = function (object, key, value) {\n    return Q(object).dispatch(\"set\", [key, value]);\n};\n\nPromise.prototype.set = function (key, value) {\n    return this.dispatch(\"set\", [key, value]);\n};\n\n/**\n * Deletes a property in a future turn.\n * @param object    promise or immediate reference for target object\n * @param name      name of property to delete\n * @return promise for the return value\n */\nQ.del = // XXX legacy\nQ[\"delete\"] = function (object, key) {\n    return Q(object).dispatch(\"delete\", [key]);\n};\n\nPromise.prototype.del = // XXX legacy\nPromise.prototype[\"delete\"] = function (key) {\n    return this.dispatch(\"delete\", [key]);\n};\n\n/**\n * Invokes a method in a future turn.\n * @param object    promise or immediate reference for target object\n * @param name      name of method to invoke\n * @param value     a value to post, typically an array of\n *                  invocation arguments for promises that\n *                  are ultimately backed with `resolve` values,\n *                  as opposed to those backed with URLs\n *                  wherein the posted value can be any\n *                  JSON serializable object.\n * @return promise for the return value\n */\n// bound locally because it is used by other methods\nQ.mapply = // XXX As proposed by \"Redsandro\"\nQ.post = function (object, name, args) {\n    return Q(object).dispatch(\"post\", [name, args]);\n};\n\nPromise.prototype.mapply = // XXX As proposed by \"Redsandro\"\nPromise.prototype.post = function (name, args) {\n    return this.dispatch(\"post\", [name, args]);\n};\n\n/**\n * Invokes a method in a future turn.\n * @param object    promise or immediate reference for target object\n * @param name      name of method to invoke\n * @param ...args   array of invocation arguments\n * @return promise for the return value\n */\nQ.send = // XXX Mark Miller's proposed parlance\nQ.mcall = // XXX As proposed by \"Redsandro\"\nQ.invoke = function (object, name /*...args*/) {\n    return Q(object).dispatch(\"post\", [name, array_slice(arguments, 2)]);\n};\n\nPromise.prototype.send = // XXX Mark Miller's proposed parlance\nPromise.prototype.mcall = // XXX As proposed by \"Redsandro\"\nPromise.prototype.invoke = function (name /*...args*/) {\n    return this.dispatch(\"post\", [name, array_slice(arguments, 1)]);\n};\n\n/**\n * Applies the promised function in a future turn.\n * @param object    promise or immediate reference for target function\n * @param args      array of application arguments\n */\nQ.fapply = function (object, args) {\n    return Q(object).dispatch(\"apply\", [void 0, args]);\n};\n\nPromise.prototype.fapply = function (args) {\n    return this.dispatch(\"apply\", [void 0, args]);\n};\n\n/**\n * Calls the promised function in a future turn.\n * @param object    promise or immediate reference for target function\n * @param ...args   array of application arguments\n */\nQ[\"try\"] =\nQ.fcall = function (object /* ...args*/) {\n    return Q(object).dispatch(\"apply\", [void 0, array_slice(arguments, 1)]);\n};\n\nPromise.prototype.fcall = function (/*...args*/) {\n    return this.dispatch(\"apply\", [void 0, array_slice(arguments)]);\n};\n\n/**\n * Binds the promised function, transforming return values into a fulfilled\n * promise and thrown errors into a rejected one.\n * @param object    promise or immediate reference for target function\n * @param ...args   array of application arguments\n */\nQ.fbind = function (object /*...args*/) {\n    var promise = Q(object);\n    var args = array_slice(arguments, 1);\n    return function fbound() {\n        return promise.dispatch(\"apply\", [\n            this,\n            args.concat(array_slice(arguments))\n        ]);\n    };\n};\nPromise.prototype.fbind = function (/*...args*/) {\n    var promise = this;\n    var args = array_slice(arguments);\n    return function fbound() {\n        return promise.dispatch(\"apply\", [\n            this,\n            args.concat(array_slice(arguments))\n        ]);\n    };\n};\n\n/**\n * Requests the names of the owned properties of a promised\n * object in a future turn.\n * @param object    promise or immediate reference for target object\n * @return promise for the keys of the eventually settled object\n */\nQ.keys = function (object) {\n    return Q(object).dispatch(\"keys\", []);\n};\n\nPromise.prototype.keys = function () {\n    return this.dispatch(\"keys\", []);\n};\n\n/**\n * Turns an array of promises into a promise for an array.  If any of\n * the promises gets rejected, the whole array is rejected immediately.\n * @param {Array*} an array (or promise for an array) of values (or\n * promises for values)\n * @returns a promise for an array of the corresponding values\n */\n// By Mark Miller\n// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled\nQ.all = all;\nfunction all(promises) {\n    return when(promises, function (promises) {\n        var pendingCount = 0;\n        var deferred = defer();\n        array_reduce(promises, function (undefined, promise, index) {\n            var snapshot;\n            if (\n                isPromise(promise) &&\n                (snapshot = promise.inspect()).state === \"fulfilled\"\n            ) {\n                promises[index] = snapshot.value;\n            } else {\n                ++pendingCount;\n                when(\n                    promise,\n                    function (value) {\n                        promises[index] = value;\n                        if (--pendingCount === 0) {\n                            deferred.resolve(promises);\n                        }\n                    },\n                    deferred.reject,\n                    function (progress) {\n                        deferred.notify({ index: index, value: progress });\n                    }\n                );\n            }\n        }, void 0);\n        if (pendingCount === 0) {\n            deferred.resolve(promises);\n        }\n        return deferred.promise;\n    });\n}\n\nPromise.prototype.all = function () {\n    return all(this);\n};\n\n/**\n * Returns the first resolved promise of an array. Prior rejected promises are\n * ignored.  Rejects only if all promises are rejected.\n * @param {Array*} an array containing values or promises for values\n * @returns a promise fulfilled with the value of the first resolved promise,\n * or a rejected promise if all promises are rejected.\n */\nQ.any = any;\n\nfunction any(promises) {\n    if (promises.length === 0) {\n        return Q.resolve();\n    }\n\n    var deferred = Q.defer();\n    var pendingCount = 0;\n    array_reduce(promises, function (prev, current, index) {\n        var promise = promises[index];\n\n        pendingCount++;\n\n        when(promise, onFulfilled, onRejected, onProgress);\n        function onFulfilled(result) {\n            deferred.resolve(result);\n        }\n        function onRejected() {\n            pendingCount--;\n            if (pendingCount === 0) {\n                deferred.reject(new Error(\n                    \"Can't get fulfillment value from any promise, all \" +\n                    \"promises were rejected.\"\n                ));\n            }\n        }\n        function onProgress(progress) {\n            deferred.notify({\n                index: index,\n                value: progress\n            });\n        }\n    }, undefined);\n\n    return deferred.promise;\n}\n\nPromise.prototype.any = function () {\n    return any(this);\n};\n\n/**\n * Waits for all promises to be settled, either fulfilled or\n * rejected.  This is distinct from `all` since that would stop\n * waiting at the first rejection.  The promise returned by\n * `allResolved` will never be rejected.\n * @param promises a promise for an array (or an array) of promises\n * (or values)\n * @return a promise for an array of promises\n */\nQ.allResolved = deprecate(allResolved, \"allResolved\", \"allSettled\");\nfunction allResolved(promises) {\n    return when(promises, function (promises) {\n        promises = array_map(promises, Q);\n        return when(all(array_map(promises, function (promise) {\n            return when(promise, noop, noop);\n        })), function () {\n            return promises;\n        });\n    });\n}\n\nPromise.prototype.allResolved = function () {\n    return allResolved(this);\n};\n\n/**\n * @see Promise#allSettled\n */\nQ.allSettled = allSettled;\nfunction allSettled(promises) {\n    return Q(promises).allSettled();\n}\n\n/**\n * Turns an array of promises into a promise for an array of their states (as\n * returned by `inspect`) when they have all settled.\n * @param {Array[Any*]} values an array (or promise for an array) of values (or\n * promises for values)\n * @returns {Array[State]} an array of states for the respective values.\n */\nPromise.prototype.allSettled = function () {\n    return this.then(function (promises) {\n        return all(array_map(promises, function (promise) {\n            promise = Q(promise);\n            function regardless() {\n                return promise.inspect();\n            }\n            return promise.then(regardless, regardless);\n        }));\n    });\n};\n\n/**\n * Captures the failure of a promise, giving an oportunity to recover\n * with a callback.  If the given promise is fulfilled, the returned\n * promise is fulfilled.\n * @param {Any*} promise for something\n * @param {Function} callback to fulfill the returned promise if the\n * given promise is rejected\n * @returns a promise for the return value of the callback\n */\nQ.fail = // XXX legacy\nQ[\"catch\"] = function (object, rejected) {\n    return Q(object).then(void 0, rejected);\n};\n\nPromise.prototype.fail = // XXX legacy\nPromise.prototype[\"catch\"] = function (rejected) {\n    return this.then(void 0, rejected);\n};\n\n/**\n * Attaches a listener that can respond to progress notifications from a\n * promise's originating deferred. This listener receives the exact arguments\n * passed to ``deferred.notify``.\n * @param {Any*} promise for something\n * @param {Function} callback to receive any progress notifications\n * @returns the given promise, unchanged\n */\nQ.progress = progress;\nfunction progress(object, progressed) {\n    return Q(object).then(void 0, void 0, progressed);\n}\n\nPromise.prototype.progress = function (progressed) {\n    return this.then(void 0, void 0, progressed);\n};\n\n/**\n * Provides an opportunity to observe the settling of a promise,\n * regardless of whether the promise is fulfilled or rejected.  Forwards\n * the resolution to the returned promise when the callback is done.\n * The callback can return a promise to defer completion.\n * @param {Any*} promise\n * @param {Function} callback to observe the resolution of the given\n * promise, takes no arguments.\n * @returns a promise for the resolution of the given promise when\n * ``fin`` is done.\n */\nQ.fin = // XXX legacy\nQ[\"finally\"] = function (object, callback) {\n    return Q(object)[\"finally\"](callback);\n};\n\nPromise.prototype.fin = // XXX legacy\nPromise.prototype[\"finally\"] = function (callback) {\n    callback = Q(callback);\n    return this.then(function (value) {\n        return callback.fcall().then(function () {\n            return value;\n        });\n    }, function (reason) {\n        // TODO attempt to recycle the rejection with \"this\".\n        return callback.fcall().then(function () {\n            throw reason;\n        });\n    });\n};\n\n/**\n * Terminates a chain of promises, forcing rejections to be\n * thrown as exceptions.\n * @param {Any*} promise at the end of a chain of promises\n * @returns nothing\n */\nQ.done = function (object, fulfilled, rejected, progress) {\n    return Q(object).done(fulfilled, rejected, progress);\n};\n\nPromise.prototype.done = function (fulfilled, rejected, progress) {\n    var onUnhandledError = function (error) {\n        // forward to a future turn so that ``when``\n        // does not catch it and turn it into a rejection.\n        Q.nextTick(function () {\n            makeStackTraceLong(error, promise);\n            if (Q.onerror) {\n                Q.onerror(error);\n            } else {\n                throw error;\n            }\n        });\n    };\n\n    // Avoid unnecessary `nextTick`ing via an unnecessary `when`.\n    var promise = fulfilled || rejected || progress ?\n        this.then(fulfilled, rejected, progress) :\n        this;\n\n    if (typeof process === \"object\" && process && process.domain) {\n        onUnhandledError = process.domain.bind(onUnhandledError);\n    }\n\n    promise.then(void 0, onUnhandledError);\n};\n\n/**\n * Causes a promise to be rejected if it does not get fulfilled before\n * some milliseconds time out.\n * @param {Any*} promise\n * @param {Number} milliseconds timeout\n * @param {Any*} custom error message or Error object (optional)\n * @returns a promise for the resolution of the given promise if it is\n * fulfilled before the timeout, otherwise rejected.\n */\nQ.timeout = function (object, ms, error) {\n    return Q(object).timeout(ms, error);\n};\n\nPromise.prototype.timeout = function (ms, error) {\n    var deferred = defer();\n    var timeoutId = setTimeout(function () {\n        if (!error || \"string\" === typeof error) {\n            error = new Error(error || \"Timed out after \" + ms + \" ms\");\n            error.code = \"ETIMEDOUT\";\n        }\n        deferred.reject(error);\n    }, ms);\n\n    this.then(function (value) {\n        clearTimeout(timeoutId);\n        deferred.resolve(value);\n    }, function (exception) {\n        clearTimeout(timeoutId);\n        deferred.reject(exception);\n    }, deferred.notify);\n\n    return deferred.promise;\n};\n\n/**\n * Returns a promise for the given value (or promised value), some\n * milliseconds after it resolved. Passes rejections immediately.\n * @param {Any*} promise\n * @param {Number} milliseconds\n * @returns a promise for the resolution of the given promise after milliseconds\n * time has elapsed since the resolution of the given promise.\n * If the given promise rejects, that is passed immediately.\n */\nQ.delay = function (object, timeout) {\n    if (timeout === void 0) {\n        timeout = object;\n        object = void 0;\n    }\n    return Q(object).delay(timeout);\n};\n\nPromise.prototype.delay = function (timeout) {\n    return this.then(function (value) {\n        var deferred = defer();\n        setTimeout(function () {\n            deferred.resolve(value);\n        }, timeout);\n        return deferred.promise;\n    });\n};\n\n/**\n * Passes a continuation to a Node function, which is called with the given\n * arguments provided as an array, and returns a promise.\n *\n *      Q.nfapply(FS.readFile, [__filename])\n *      .then(function (content) {\n *      })\n *\n */\nQ.nfapply = function (callback, args) {\n    return Q(callback).nfapply(args);\n};\n\nPromise.prototype.nfapply = function (args) {\n    var deferred = defer();\n    var nodeArgs = array_slice(args);\n    nodeArgs.push(deferred.makeNodeResolver());\n    this.fapply(nodeArgs).fail(deferred.reject);\n    return deferred.promise;\n};\n\n/**\n * Passes a continuation to a Node function, which is called with the given\n * arguments provided individually, and returns a promise.\n * @example\n * Q.nfcall(FS.readFile, __filename)\n * .then(function (content) {\n * })\n *\n */\nQ.nfcall = function (callback /*...args*/) {\n    var args = array_slice(arguments, 1);\n    return Q(callback).nfapply(args);\n};\n\nPromise.prototype.nfcall = function (/*...args*/) {\n    var nodeArgs = array_slice(arguments);\n    var deferred = defer();\n    nodeArgs.push(deferred.makeNodeResolver());\n    this.fapply(nodeArgs).fail(deferred.reject);\n    return deferred.promise;\n};\n\n/**\n * Wraps a NodeJS continuation passing function and returns an equivalent\n * version that returns a promise.\n * @example\n * Q.nfbind(FS.readFile, __filename)(\"utf-8\")\n * .then(console.log)\n * .done()\n */\nQ.nfbind =\nQ.denodeify = function (callback /*...args*/) {\n    var baseArgs = array_slice(arguments, 1);\n    return function () {\n        var nodeArgs = baseArgs.concat(array_slice(arguments));\n        var deferred = defer();\n        nodeArgs.push(deferred.makeNodeResolver());\n        Q(callback).fapply(nodeArgs).fail(deferred.reject);\n        return deferred.promise;\n    };\n};\n\nPromise.prototype.nfbind =\nPromise.prototype.denodeify = function (/*...args*/) {\n    var args = array_slice(arguments);\n    args.unshift(this);\n    return Q.denodeify.apply(void 0, args);\n};\n\nQ.nbind = function (callback, thisp /*...args*/) {\n    var baseArgs = array_slice(arguments, 2);\n    return function () {\n        var nodeArgs = baseArgs.concat(array_slice(arguments));\n        var deferred = defer();\n        nodeArgs.push(deferred.makeNodeResolver());\n        function bound() {\n            return callback.apply(thisp, arguments);\n        }\n        Q(bound).fapply(nodeArgs).fail(deferred.reject);\n        return deferred.promise;\n    };\n};\n\nPromise.prototype.nbind = function (/*thisp, ...args*/) {\n    var args = array_slice(arguments, 0);\n    args.unshift(this);\n    return Q.nbind.apply(void 0, args);\n};\n\n/**\n * Calls a method of a Node-style object that accepts a Node-style\n * callback with a given array of arguments, plus a provided callback.\n * @param object an object that has the named method\n * @param {String} name name of the method of object\n * @param {Array} args arguments to pass to the method; the callback\n * will be provided by Q and appended to these arguments.\n * @returns a promise for the value or error\n */\nQ.nmapply = // XXX As proposed by \"Redsandro\"\nQ.npost = function (object, name, args) {\n    return Q(object).npost(name, args);\n};\n\nPromise.prototype.nmapply = // XXX As proposed by \"Redsandro\"\nPromise.prototype.npost = function (name, args) {\n    var nodeArgs = array_slice(args || []);\n    var deferred = defer();\n    nodeArgs.push(deferred.makeNodeResolver());\n    this.dispatch(\"post\", [name, nodeArgs]).fail(deferred.reject);\n    return deferred.promise;\n};\n\n/**\n * Calls a method of a Node-style object that accepts a Node-style\n * callback, forwarding the given variadic arguments, plus a provided\n * callback argument.\n * @param object an object that has the named method\n * @param {String} name name of the method of object\n * @param ...args arguments to pass to the method; the callback will\n * be provided by Q and appended to these arguments.\n * @returns a promise for the value or error\n */\nQ.nsend = // XXX Based on Mark Miller's proposed \"send\"\nQ.nmcall = // XXX Based on \"Redsandro's\" proposal\nQ.ninvoke = function (object, name /*...args*/) {\n    var nodeArgs = array_slice(arguments, 2);\n    var deferred = defer();\n    nodeArgs.push(deferred.makeNodeResolver());\n    Q(object).dispatch(\"post\", [name, nodeArgs]).fail(deferred.reject);\n    return deferred.promise;\n};\n\nPromise.prototype.nsend = // XXX Based on Mark Miller's proposed \"send\"\nPromise.prototype.nmcall = // XXX Based on \"Redsandro's\" proposal\nPromise.prototype.ninvoke = function (name /*...args*/) {\n    var nodeArgs = array_slice(arguments, 1);\n    var deferred = defer();\n    nodeArgs.push(deferred.makeNodeResolver());\n    this.dispatch(\"post\", [name, nodeArgs]).fail(deferred.reject);\n    return deferred.promise;\n};\n\n/**\n * If a function would like to support both Node continuation-passing-style and\n * promise-returning-style, it can end its internal promise chain with\n * `nodeify(nodeback)`, forwarding the optional nodeback argument.  If the user\n * elects to use a nodeback, the result will be sent there.  If they do not\n * pass a nodeback, they will receive the result promise.\n * @param object a result (or a promise for a result)\n * @param {Function} nodeback a Node.js-style callback\n * @returns either the promise or nothing\n */\nQ.nodeify = nodeify;\nfunction nodeify(object, nodeback) {\n    return Q(object).nodeify(nodeback);\n}\n\nPromise.prototype.nodeify = function (nodeback) {\n    if (nodeback) {\n        this.then(function (value) {\n            Q.nextTick(function () {\n                nodeback(null, value);\n            });\n        }, function (error) {\n            Q.nextTick(function () {\n                nodeback(error);\n            });\n        });\n    } else {\n        return this;\n    }\n};\n\nQ.noConflict = function() {\n    throw new Error(\"Q.noConflict only works when Q is used as a global\");\n};\n\n// All code before this point will be filtered from stack traces.\nvar qEndingLine = captureLine();\n\nreturn Q;\n\n});\n\n}).call(this,require('_process'))\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9xL3EuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiLy8gdmltOnRzPTQ6c3RzPTQ6c3c9NDpcbi8qIVxuICpcbiAqIENvcHlyaWdodCAyMDA5LTIwMTIgS3JpcyBLb3dhbCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIE1JVFxuICogbGljZW5zZSBmb3VuZCBhdCBodHRwOi8vZ2l0aHViLmNvbS9rcmlza293YWwvcS9yYXcvbWFzdGVyL0xJQ0VOU0VcbiAqXG4gKiBXaXRoIHBhcnRzIGJ5IFR5bGVyIENsb3NlXG4gKiBDb3B5cmlnaHQgMjAwNy0yMDA5IFR5bGVyIENsb3NlIHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgTUlUIFggbGljZW5zZSBmb3VuZFxuICogYXQgaHR0cDovL3d3dy5vcGVuc291cmNlLm9yZy9saWNlbnNlcy9taXQtbGljZW5zZS5odG1sXG4gKiBGb3JrZWQgYXQgcmVmX3NlbmQuanMgdmVyc2lvbjogMjAwOS0wNS0xMVxuICpcbiAqIFdpdGggcGFydHMgYnkgTWFyayBNaWxsZXJcbiAqIENvcHlyaWdodCAoQykgMjAxMSBHb29nbGUgSW5jLlxuICpcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSBcIkxpY2Vuc2VcIik7XG4gKiB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuXG4gKiBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXRcbiAqXG4gKiBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjBcbiAqXG4gKiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlXG4gKiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiBcIkFTIElTXCIgQkFTSVMsXG4gKiBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC5cbiAqIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmRcbiAqIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLlxuICpcbiAqL1xuXG4oZnVuY3Rpb24gKGRlZmluaXRpb24pIHtcbiAgICBcInVzZSBzdHJpY3RcIjtcblxuICAgIC8vIFRoaXMgZmlsZSB3aWxsIGZ1bmN0aW9uIHByb3Blcmx5IGFzIGEgPHNjcmlwdD4gdGFnLCBvciBhIG1vZHVsZVxuICAgIC8vIHVzaW5nIENvbW1vbkpTIGFuZCBOb2RlSlMgb3IgUmVxdWlyZUpTIG1vZHVsZSBmb3JtYXRzLiAgSW5cbiAgICAvLyBDb21tb24vTm9kZS9SZXF1aXJlSlMsIHRoZSBtb2R1bGUgZXhwb3J0cyB0aGUgUSBBUEkgYW5kIHdoZW5cbiAgICAvLyBleGVjdXRlZCBhcyBhIHNpbXBsZSA8c2NyaXB0PiwgaXQgY3JlYXRlcyBhIFEgZ2xvYmFsIGluc3RlYWQuXG5cbiAgICAvLyBNb250YWdlIFJlcXVpcmVcbiAgICBpZiAodHlwZW9mIGJvb3RzdHJhcCA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgIGJvb3RzdHJhcChcInByb21pc2VcIiwgZGVmaW5pdGlvbik7XG5cbiAgICAvLyBDb21tb25KU1xuICAgIH0gZWxzZSBpZiAodHlwZW9mIGV4cG9ydHMgPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIG1vZHVsZSA9PT0gXCJvYmplY3RcIikge1xuICAgICAgICBtb2R1bGUuZXhwb3J0cyA9IGRlZmluaXRpb24oKTtcblxuICAgIC8vIFJlcXVpcmVKU1xuICAgIH0gZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gXCJmdW5jdGlvblwiICYmIGRlZmluZS5hbWQpIHtcbiAgICAgICAgZGVmaW5lKGRlZmluaXRpb24pO1xuXG4gICAgLy8gU0VTIChTZWN1cmUgRWNtYVNjcmlwdClcbiAgICB9IGVsc2UgaWYgKHR5cGVvZiBzZXMgIT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgaWYgKCFzZXMub2soKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgc2VzLm1ha2VRID0gZGVmaW5pdGlvbjtcbiAgICAgICAgfVxuXG4gICAgLy8gPHNjcmlwdD5cbiAgICB9IGVsc2UgaWYgKHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIgfHwgdHlwZW9mIHNlbGYgIT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgLy8gUHJlZmVyIHdpbmRvdyBvdmVyIHNlbGYgZm9yIGFkZC1vbiBzY3JpcHRzLiBVc2Ugc2VsZiBmb3JcbiAgICAgICAgLy8gbm9uLXdpbmRvd2VkIGNvbnRleHRzLlxuICAgICAgICB2YXIgZ2xvYmFsID0gdHlwZW9mIHdpbmRvdyAhPT0gXCJ1bmRlZmluZWRcIiA/IHdpbmRvdyA6IHNlbGY7XG5cbiAgICAgICAgLy8gR2V0IHRoZSBgd2luZG93YCBvYmplY3QsIHNhdmUgdGhlIHByZXZpb3VzIFEgZ2xvYmFsXG4gICAgICAgIC8vIGFuZCBpbml0aWFsaXplIFEgYXMgYSBnbG9iYWwuXG4gICAgICAgIHZhciBwcmV2aW91c1EgPSBnbG9iYWwuUTtcbiAgICAgICAgZ2xvYmFsLlEgPSBkZWZpbml0aW9uKCk7XG5cbiAgICAgICAgLy8gQWRkIGEgbm9Db25mbGljdCBmdW5jdGlvbiBzbyBRIGNhbiBiZSByZW1vdmVkIGZyb20gdGhlXG4gICAgICAgIC8vIGdsb2JhbCBuYW1lc3BhY2UuXG4gICAgICAgIGdsb2JhbC5RLm5vQ29uZmxpY3QgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBnbG9iYWwuUSA9IHByZXZpb3VzUTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9O1xuXG4gICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVGhpcyBlbnZpcm9ubWVudCB3YXMgbm90IGFudGljaXBhdGVkIGJ5IFEuIFBsZWFzZSBmaWxlIGEgYnVnLlwiKTtcbiAgICB9XG5cbn0pKGZ1bmN0aW9uICgpIHtcblwidXNlIHN0cmljdFwiO1xuXG52YXIgaGFzU3RhY2tzID0gZmFsc2U7XG50cnkge1xuICAgIHRocm93IG5ldyBFcnJvcigpO1xufSBjYXRjaCAoZSkge1xuICAgIGhhc1N0YWNrcyA9ICEhZS5zdGFjaztcbn1cblxuLy8gQWxsIGNvZGUgYWZ0ZXIgdGhpcyBwb2ludCB3aWxsIGJlIGZpbHRlcmVkIGZyb20gc3RhY2sgdHJhY2VzIHJlcG9ydGVkXG4vLyBieSBRLlxudmFyIHFTdGFydGluZ0xpbmUgPSBjYXB0dXJlTGluZSgpO1xudmFyIHFGaWxlTmFtZTtcblxuLy8gc2hpbXNcblxuLy8gdXNlZCBmb3IgZmFsbGJhY2sgaW4gXCJhbGxSZXNvbHZlZFwiXG52YXIgbm9vcCA9IGZ1bmN0aW9uICgpIHt9O1xuXG4vLyBVc2UgdGhlIGZhc3Rlc3QgcG9zc2libGUgbWVhbnMgdG8gZXhlY3V0ZSBhIHRhc2sgaW4gYSBmdXR1cmUgdHVyblxuLy8gb2YgdGhlIGV2ZW50IGxvb3AuXG52YXIgbmV4dFRpY2sgPShmdW5jdGlvbiAoKSB7XG4gICAgLy8gbGlua2VkIGxpc3Qgb2YgdGFza3MgKHNpbmdsZSwgd2l0aCBoZWFkIG5vZGUpXG4gICAgdmFyIGhlYWQgPSB7dGFzazogdm9pZCAwLCBuZXh0OiBudWxsfTtcbiAgICB2YXIgdGFpbCA9IGhlYWQ7XG4gICAgdmFyIGZsdXNoaW5nID0gZmFsc2U7XG4gICAgdmFyIHJlcXVlc3RUaWNrID0gdm9pZCAwO1xuICAgIHZhciBpc05vZGVKUyA9IGZhbHNlO1xuICAgIC8vIHF1ZXVlIGZvciBsYXRlIHRhc2tzLCB1c2VkIGJ5IHVuaGFuZGxlZCByZWplY3Rpb24gdHJhY2tpbmdcbiAgICB2YXIgbGF0ZXJRdWV1ZSA9IFtdO1xuXG4gICAgZnVuY3Rpb24gZmx1c2goKSB7XG4gICAgICAgIC8qIGpzaGludCBsb29wZnVuYzogdHJ1ZSAqL1xuICAgICAgICB2YXIgdGFzaywgZG9tYWluO1xuXG4gICAgICAgIHdoaWxlIChoZWFkLm5leHQpIHtcbiAgICAgICAgICAgIGhlYWQgPSBoZWFkLm5leHQ7XG4gICAgICAgICAgICB0YXNrID0gaGVhZC50YXNrO1xuICAgICAgICAgICAgaGVhZC50YXNrID0gdm9pZCAwO1xuICAgICAgICAgICAgZG9tYWluID0gaGVhZC5kb21haW47XG5cbiAgICAgICAgICAgIGlmIChkb21haW4pIHtcbiAgICAgICAgICAgICAgICBoZWFkLmRvbWFpbiA9IHZvaWQgMDtcbiAgICAgICAgICAgICAgICBkb21haW4uZW50ZXIoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJ1blNpbmdsZSh0YXNrLCBkb21haW4pO1xuXG4gICAgICAgIH1cbiAgICAgICAgd2hpbGUgKGxhdGVyUXVldWUubGVuZ3RoKSB7XG4gICAgICAgICAgICB0YXNrID0gbGF0ZXJRdWV1ZS5wb3AoKTtcbiAgICAgICAgICAgIHJ1blNpbmdsZSh0YXNrKTtcbiAgICAgICAgfVxuICAgICAgICBmbHVzaGluZyA9IGZhbHNlO1xuICAgIH1cbiAgICAvLyBydW5zIGEgc2luZ2xlIGZ1bmN0aW9uIGluIHRoZSBhc3luYyBxdWV1ZVxuICAgIGZ1bmN0aW9uIHJ1blNpbmdsZSh0YXNrLCBkb21haW4pIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHRhc2soKTtcblxuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICBpZiAoaXNOb2RlSlMpIHtcbiAgICAgICAgICAgICAgICAvLyBJbiBub2RlLCB1bmNhdWdodCBleGNlcHRpb25zIGFyZSBjb25zaWRlcmVkIGZhdGFsIGVycm9ycy5cbiAgICAgICAgICAgICAgICAvLyBSZS10aHJvdyB0aGVtIHN5bmNocm9ub3VzbHkgdG8gaW50ZXJydXB0IGZsdXNoaW5nIVxuXG4gICAgICAgICAgICAgICAgLy8gRW5zdXJlIGNvbnRpbnVhdGlvbiBpZiB0aGUgdW5jYXVnaHQgZXhjZXB0aW9uIGlzIHN1cHByZXNzZWRcbiAgICAgICAgICAgICAgICAvLyBsaXN0ZW5pbmcgXCJ1bmNhdWdodEV4Y2VwdGlvblwiIGV2ZW50cyAoYXMgZG9tYWlucyBkb2VzKS5cbiAgICAgICAgICAgICAgICAvLyBDb250aW51ZSBpbiBuZXh0IGV2ZW50IHRvIGF2b2lkIHRpY2sgcmVjdXJzaW9uLlxuICAgICAgICAgICAgICAgIGlmIChkb21haW4pIHtcbiAgICAgICAgICAgICAgICAgICAgZG9tYWluLmV4aXQoKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgc2V0VGltZW91dChmbHVzaCwgMCk7XG4gICAgICAgICAgICAgICAgaWYgKGRvbWFpbikge1xuICAgICAgICAgICAgICAgICAgICBkb21haW4uZW50ZXIoKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICB0aHJvdyBlO1xuXG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIEluIGJyb3dzZXJzLCB1bmNhdWdodCBleGNlcHRpb25zIGFyZSBub3QgZmF0YWwuXG4gICAgICAgICAgICAgICAgLy8gUmUtdGhyb3cgdGhlbSBhc3luY2hyb25vdXNseSB0byBhdm9pZCBzbG93LWRvd25zLlxuICAgICAgICAgICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICAgICAgICAgIH0sIDApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGRvbWFpbikge1xuICAgICAgICAgICAgZG9tYWluLmV4aXQoKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIG5leHRUaWNrID0gZnVuY3Rpb24gKHRhc2spIHtcbiAgICAgICAgdGFpbCA9IHRhaWwubmV4dCA9IHtcbiAgICAgICAgICAgIHRhc2s6IHRhc2ssXG4gICAgICAgICAgICBkb21haW46IGlzTm9kZUpTICYmIHByb2Nlc3MuZG9tYWluLFxuICAgICAgICAgICAgbmV4dDogbnVsbFxuICAgICAgICB9O1xuXG4gICAgICAgIGlmICghZmx1c2hpbmcpIHtcbiAgICAgICAgICAgIGZsdXNoaW5nID0gdHJ1ZTtcbiAgICAgICAgICAgIHJlcXVlc3RUaWNrKCk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgaWYgKHR5cGVvZiBwcm9jZXNzID09PSBcIm9iamVjdFwiICYmXG4gICAgICAgIHByb2Nlc3MudG9TdHJpbmcoKSA9PT0gXCJbb2JqZWN0IHByb2Nlc3NdXCIgJiYgcHJvY2Vzcy5uZXh0VGljaykge1xuICAgICAgICAvLyBFbnN1cmUgUSBpcyBpbiBhIHJlYWwgTm9kZSBlbnZpcm9ubWVudCwgd2l0aCBhIGBwcm9jZXNzLm5leHRUaWNrYC5cbiAgICAgICAgLy8gVG8gc2VlIHRocm91Z2ggZmFrZSBOb2RlIGVudmlyb25tZW50czpcbiAgICAgICAgLy8gKiBNb2NoYSB0ZXN0IHJ1bm5lciAtIGV4cG9zZXMgYSBgcHJvY2Vzc2AgZ2xvYmFsIHdpdGhvdXQgYSBgbmV4dFRpY2tgXG4gICAgICAgIC8vICogQnJvd3NlcmlmeSAtIGV4cG9zZXMgYSBgcHJvY2Vzcy5uZXhUaWNrYCBmdW5jdGlvbiB0aGF0IHVzZXNcbiAgICAgICAgLy8gICBgc2V0VGltZW91dGAuIEluIHRoaXMgY2FzZSBgc2V0SW1tZWRpYXRlYCBpcyBwcmVmZXJyZWQgYmVjYXVzZVxuICAgICAgICAvLyAgICBpdCBpcyBmYXN0ZXIuIEJyb3dzZXJpZnkncyBgcHJvY2Vzcy50b1N0cmluZygpYCB5aWVsZHNcbiAgICAgICAgLy8gICBcIltvYmplY3QgT2JqZWN0XVwiLCB3aGlsZSBpbiBhIHJlYWwgTm9kZSBlbnZpcm9ubWVudFxuICAgICAgICAvLyAgIGBwcm9jZXNzLm5leHRUaWNrKClgIHlpZWxkcyBcIltvYmplY3QgcHJvY2Vzc11cIi5cbiAgICAgICAgaXNOb2RlSlMgPSB0cnVlO1xuXG4gICAgICAgIHJlcXVlc3RUaWNrID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgcHJvY2Vzcy5uZXh0VGljayhmbHVzaCk7XG4gICAgICAgIH07XG5cbiAgICB9IGVsc2UgaWYgKHR5cGVvZiBzZXRJbW1lZGlhdGUgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICAvLyBJbiBJRTEwLCBOb2RlLmpzIDAuOSssIG9yIGh0dHBzOi8vZ2l0aHViLmNvbS9Ob2JsZUpTL3NldEltbWVkaWF0ZVxuICAgICAgICBpZiAodHlwZW9mIHdpbmRvdyAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgcmVxdWVzdFRpY2sgPSBzZXRJbW1lZGlhdGUuYmluZCh3aW5kb3csIGZsdXNoKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJlcXVlc3RUaWNrID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIHNldEltbWVkaWF0ZShmbHVzaCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG5cbiAgICB9IGVsc2UgaWYgKHR5cGVvZiBNZXNzYWdlQ2hhbm5lbCAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAvLyBtb2Rlcm4gYnJvd3NlcnNcbiAgICAgICAgLy8gaHR0cDovL3d3dy5ub25ibG9ja2luZy5pby8yMDExLzA2L3dpbmRvd25leHR0aWNrLmh0bWxcbiAgICAgICAgdmFyIGNoYW5uZWwgPSBuZXcgTWVzc2FnZUNoYW5uZWwoKTtcbiAgICAgICAgLy8gQXQgbGVhc3QgU2FmYXJpIFZlcnNpb24gNi4wLjUgKDg1MzYuMzAuMSkgaW50ZXJtaXR0ZW50bHkgY2Fubm90IGNyZWF0ZVxuICAgICAgICAvLyB3b3JraW5nIG1lc3NhZ2UgcG9ydHMgdGhlIGZpcnN0IHRpbWUgYSBwYWdlIGxvYWRzLlxuICAgICAgICBjaGFubmVsLnBvcnQxLm9ubWVzc2FnZSA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHJlcXVlc3RUaWNrID0gcmVxdWVzdFBvcnRUaWNrO1xuICAgICAgICAgICAgY2hhbm5lbC5wb3J0MS5vbm1lc3NhZ2UgPSBmbHVzaDtcbiAgICAgICAgICAgIGZsdXNoKCk7XG4gICAgICAgIH07XG4gICAgICAgIHZhciByZXF1ZXN0UG9ydFRpY2sgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAvLyBPcGVyYSByZXF1aXJlcyB1cyB0byBwcm92aWRlIGEgbWVzc2FnZSBwYXlsb2FkLCByZWdhcmRsZXNzIG9mXG4gICAgICAgICAgICAvLyB3aGV0aGVyIHdlIHVzZSBpdC5cbiAgICAgICAgICAgIGNoYW5uZWwucG9ydDIucG9zdE1lc3NhZ2UoMCk7XG4gICAgICAgIH07XG4gICAgICAgIHJlcXVlc3RUaWNrID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgc2V0VGltZW91dChmbHVzaCwgMCk7XG4gICAgICAgICAgICByZXF1ZXN0UG9ydFRpY2soKTtcbiAgICAgICAgfTtcblxuICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIG9sZCBicm93c2Vyc1xuICAgICAgICByZXF1ZXN0VGljayA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHNldFRpbWVvdXQoZmx1c2gsIDApO1xuICAgICAgICB9O1xuICAgIH1cbiAgICAvLyBydW5zIGEgdGFzayBhZnRlciBhbGwgb3RoZXIgdGFza3MgaGF2ZSBiZWVuIHJ1blxuICAgIC8vIHRoaXMgaXMgdXNlZnVsIGZvciB1bmhhbmRsZWQgcmVqZWN0aW9uIHRyYWNraW5nIHRoYXQgbmVlZHMgdG8gaGFwcGVuXG4gICAgLy8gYWZ0ZXIgYWxsIGB0aGVuYGQgdGFza3MgaGF2ZSBiZWVuIHJ1bi5cbiAgICBuZXh0VGljay5ydW5BZnRlciA9IGZ1bmN0aW9uICh0YXNrKSB7XG4gICAgICAgIGxhdGVyUXVldWUucHVzaCh0YXNrKTtcbiAgICAgICAgaWYgKCFmbHVzaGluZykge1xuICAgICAgICAgICAgZmx1c2hpbmcgPSB0cnVlO1xuICAgICAgICAgICAgcmVxdWVzdFRpY2soKTtcbiAgICAgICAgfVxuICAgIH07XG4gICAgcmV0dXJuIG5leHRUaWNrO1xufSkoKTtcblxuLy8gQXR0ZW1wdCB0byBtYWtlIGdlbmVyaWNzIHNhZmUgaW4gdGhlIGZhY2Ugb2YgZG93bnN0cmVhbVxuLy8gbW9kaWZpY2F0aW9ucy5cbi8vIFRoZXJlIGlzIG5vIHNpdHVhdGlvbiB3aGVyZSB0aGlzIGlzIG5lY2Vzc2FyeS5cbi8vIElmIHlvdSBuZWVkIGEgc2VjdXJpdHkgZ3VhcmFudGVlLCB0aGVzZSBwcmltb3JkaWFscyBuZWVkIHRvIGJlXG4vLyBkZWVwbHkgZnJvemVuIGFueXdheSwgYW5kIGlmIHlvdSBkb27igJl0IG5lZWQgYSBzZWN1cml0eSBndWFyYW50ZWUsXG4vLyB0aGlzIGlzIGp1c3QgcGxhaW4gcGFyYW5vaWQuXG4vLyBIb3dldmVyLCB0aGlzICoqbWlnaHQqKiBoYXZlIHRoZSBuaWNlIHNpZGUtZWZmZWN0IG9mIHJlZHVjaW5nIHRoZSBzaXplIG9mXG4vLyB0aGUgbWluaWZpZWQgY29kZSBieSByZWR1Y2luZyB4LmNhbGwoKSB0byBtZXJlbHkgeCgpXG4vLyBTZWUgTWFyayBNaWxsZXLigJlzIGV4cGxhbmF0aW9uIG9mIHdoYXQgdGhpcyBkb2VzLlxuLy8gaHR0cDovL3dpa2kuZWNtYXNjcmlwdC5vcmcvZG9rdS5waHA/aWQ9Y29udmVudGlvbnM6c2FmZV9tZXRhX3Byb2dyYW1taW5nXG52YXIgY2FsbCA9IEZ1bmN0aW9uLmNhbGw7XG5mdW5jdGlvbiB1bmN1cnJ5VGhpcyhmKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIGNhbGwuYXBwbHkoZiwgYXJndW1lbnRzKTtcbiAgICB9O1xufVxuLy8gVGhpcyBpcyBlcXVpdmFsZW50LCBidXQgc2xvd2VyOlxuLy8gdW5jdXJyeVRoaXMgPSBGdW5jdGlvbl9iaW5kLmJpbmQoRnVuY3Rpb25fYmluZC5jYWxsKTtcbi8vIGh0dHA6Ly9qc3BlcmYuY29tL3VuY3Vycnl0aGlzXG5cbnZhciBhcnJheV9zbGljZSA9IHVuY3VycnlUaGlzKEFycmF5LnByb3RvdHlwZS5zbGljZSk7XG5cbnZhciBhcnJheV9yZWR1Y2UgPSB1bmN1cnJ5VGhpcyhcbiAgICBBcnJheS5wcm90b3R5cGUucmVkdWNlIHx8IGZ1bmN0aW9uIChjYWxsYmFjaywgYmFzaXMpIHtcbiAgICAgICAgdmFyIGluZGV4ID0gMCxcbiAgICAgICAgICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoO1xuICAgICAgICAvLyBjb25jZXJuaW5nIHRoZSBpbml0aWFsIHZhbHVlLCBpZiBvbmUgaXMgbm90IHByb3ZpZGVkXG4gICAgICAgIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAxKSB7XG4gICAgICAgICAgICAvLyBzZWVrIHRvIHRoZSBmaXJzdCB2YWx1ZSBpbiB0aGUgYXJyYXksIGFjY291bnRpbmdcbiAgICAgICAgICAgIC8vIGZvciB0aGUgcG9zc2liaWxpdHkgdGhhdCBpcyBpcyBhIHNwYXJzZSBhcnJheVxuICAgICAgICAgICAgZG8ge1xuICAgICAgICAgICAgICAgIGlmIChpbmRleCBpbiB0aGlzKSB7XG4gICAgICAgICAgICAgICAgICAgIGJhc2lzID0gdGhpc1tpbmRleCsrXTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmICgrK2luZGV4ID49IGxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSB3aGlsZSAoMSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gcmVkdWNlXG4gICAgICAgIGZvciAoOyBpbmRleCA8IGxlbmd0aDsgaW5kZXgrKykge1xuICAgICAgICAgICAgLy8gYWNjb3VudCBmb3IgdGhlIHBvc3NpYmlsaXR5IHRoYXQgdGhlIGFycmF5IGlzIHNwYXJzZVxuICAgICAgICAgICAgaWYgKGluZGV4IGluIHRoaXMpIHtcbiAgICAgICAgICAgICAgICBiYXNpcyA9IGNhbGxiYWNrKGJhc2lzLCB0aGlzW2luZGV4XSwgaW5kZXgpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBiYXNpcztcbiAgICB9XG4pO1xuXG52YXIgYXJyYXlfaW5kZXhPZiA9IHVuY3VycnlUaGlzKFxuICAgIEFycmF5LnByb3RvdHlwZS5pbmRleE9mIHx8IGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICAvLyBub3QgYSB2ZXJ5IGdvb2Qgc2hpbSwgYnV0IGdvb2QgZW5vdWdoIGZvciBvdXIgb25lIHVzZSBvZiBpdFxuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGlmICh0aGlzW2ldID09PSB2YWx1ZSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiAtMTtcbiAgICB9XG4pO1xuXG52YXIgYXJyYXlfbWFwID0gdW5jdXJyeVRoaXMoXG4gICAgQXJyYXkucHJvdG90eXBlLm1hcCB8fCBmdW5jdGlvbiAoY2FsbGJhY2ssIHRoaXNwKSB7XG4gICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgdmFyIGNvbGxlY3QgPSBbXTtcbiAgICAgICAgYXJyYXlfcmVkdWNlKHNlbGYsIGZ1bmN0aW9uICh1bmRlZmluZWQsIHZhbHVlLCBpbmRleCkge1xuICAgICAgICAgICAgY29sbGVjdC5wdXNoKGNhbGxiYWNrLmNhbGwodGhpc3AsIHZhbHVlLCBpbmRleCwgc2VsZikpO1xuICAgICAgICB9LCB2b2lkIDApO1xuICAgICAgICByZXR1cm4gY29sbGVjdDtcbiAgICB9XG4pO1xuXG52YXIgb2JqZWN0X2NyZWF0ZSA9IE9iamVjdC5jcmVhdGUgfHwgZnVuY3Rpb24gKHByb3RvdHlwZSkge1xuICAgIGZ1bmN0aW9uIFR5cGUoKSB7IH1cbiAgICBUeXBlLnByb3RvdHlwZSA9IHByb3RvdHlwZTtcbiAgICByZXR1cm4gbmV3IFR5cGUoKTtcbn07XG5cbnZhciBvYmplY3RfaGFzT3duUHJvcGVydHkgPSB1bmN1cnJ5VGhpcyhPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5KTtcblxudmFyIG9iamVjdF9rZXlzID0gT2JqZWN0LmtleXMgfHwgZnVuY3Rpb24gKG9iamVjdCkge1xuICAgIHZhciBrZXlzID0gW107XG4gICAgZm9yICh2YXIga2V5IGluIG9iamVjdCkge1xuICAgICAgICBpZiAob2JqZWN0X2hhc093blByb3BlcnR5KG9iamVjdCwga2V5KSkge1xuICAgICAgICAgICAga2V5cy5wdXNoKGtleSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGtleXM7XG59O1xuXG52YXIgb2JqZWN0X3RvU3RyaW5nID0gdW5jdXJyeVRoaXMoT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZyk7XG5cbmZ1bmN0aW9uIGlzT2JqZWN0KHZhbHVlKSB7XG4gICAgcmV0dXJuIHZhbHVlID09PSBPYmplY3QodmFsdWUpO1xufVxuXG4vLyBnZW5lcmF0b3IgcmVsYXRlZCBzaGltc1xuXG4vLyBGSVhNRTogUmVtb3ZlIHRoaXMgZnVuY3Rpb24gb25jZSBFUzYgZ2VuZXJhdG9ycyBhcmUgaW4gU3BpZGVyTW9ua2V5LlxuZnVuY3Rpb24gaXNTdG9wSXRlcmF0aW9uKGV4Y2VwdGlvbikge1xuICAgIHJldHVybiAoXG4gICAgICAgIG9iamVjdF90b1N0cmluZyhleGNlcHRpb24pID09PSBcIltvYmplY3QgU3RvcEl0ZXJhdGlvbl1cIiB8fFxuICAgICAgICBleGNlcHRpb24gaW5zdGFuY2VvZiBRUmV0dXJuVmFsdWVcbiAgICApO1xufVxuXG4vLyBGSVhNRTogUmVtb3ZlIHRoaXMgaGVscGVyIGFuZCBRLnJldHVybiBvbmNlIEVTNiBnZW5lcmF0b3JzIGFyZSBpblxuLy8gU3BpZGVyTW9ua2V5LlxudmFyIFFSZXR1cm5WYWx1ZTtcbmlmICh0eXBlb2YgUmV0dXJuVmFsdWUgIT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICBRUmV0dXJuVmFsdWUgPSBSZXR1cm5WYWx1ZTtcbn0gZWxzZSB7XG4gICAgUVJldHVyblZhbHVlID0gZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIHRoaXMudmFsdWUgPSB2YWx1ZTtcbiAgICB9O1xufVxuXG4vLyBsb25nIHN0YWNrIHRyYWNlc1xuXG52YXIgU1RBQ0tfSlVNUF9TRVBBUkFUT1IgPSBcIkZyb20gcHJldmlvdXMgZXZlbnQ6XCI7XG5cbmZ1bmN0aW9uIG1ha2VTdGFja1RyYWNlTG9uZyhlcnJvciwgcHJvbWlzZSkge1xuICAgIC8vIElmIHBvc3NpYmxlLCB0cmFuc2Zvcm0gdGhlIGVycm9yIHN0YWNrIHRyYWNlIGJ5IHJlbW92aW5nIE5vZGUgYW5kIFFcbiAgICAvLyBjcnVmdCwgdGhlbiBjb25jYXRlbmF0aW5nIHdpdGggdGhlIHN0YWNrIHRyYWNlIG9mIGBwcm9taXNlYC4gU2VlICM1Ny5cbiAgICBpZiAoaGFzU3RhY2tzICYmXG4gICAgICAgIHByb21pc2Uuc3RhY2sgJiZcbiAgICAgICAgdHlwZW9mIGVycm9yID09PSBcIm9iamVjdFwiICYmXG4gICAgICAgIGVycm9yICE9PSBudWxsICYmXG4gICAgICAgIGVycm9yLnN0YWNrICYmXG4gICAgICAgIGVycm9yLnN0YWNrLmluZGV4T2YoU1RBQ0tfSlVNUF9TRVBBUkFUT1IpID09PSAtMVxuICAgICkge1xuICAgICAgICB2YXIgc3RhY2tzID0gW107XG4gICAgICAgIGZvciAodmFyIHAgPSBwcm9taXNlOyAhIXA7IHAgPSBwLnNvdXJjZSkge1xuICAgICAgICAgICAgaWYgKHAuc3RhY2spIHtcbiAgICAgICAgICAgICAgICBzdGFja3MudW5zaGlmdChwLnN0YWNrKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBzdGFja3MudW5zaGlmdChlcnJvci5zdGFjayk7XG5cbiAgICAgICAgdmFyIGNvbmNhdGVkU3RhY2tzID0gc3RhY2tzLmpvaW4oXCJcXG5cIiArIFNUQUNLX0pVTVBfU0VQQVJBVE9SICsgXCJcXG5cIik7XG4gICAgICAgIGVycm9yLnN0YWNrID0gZmlsdGVyU3RhY2tTdHJpbmcoY29uY2F0ZWRTdGFja3MpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gZmlsdGVyU3RhY2tTdHJpbmcoc3RhY2tTdHJpbmcpIHtcbiAgICB2YXIgbGluZXMgPSBzdGFja1N0cmluZy5zcGxpdChcIlxcblwiKTtcbiAgICB2YXIgZGVzaXJlZExpbmVzID0gW107XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBsaW5lcy5sZW5ndGg7ICsraSkge1xuICAgICAgICB2YXIgbGluZSA9IGxpbmVzW2ldO1xuXG4gICAgICAgIGlmICghaXNJbnRlcm5hbEZyYW1lKGxpbmUpICYmICFpc05vZGVGcmFtZShsaW5lKSAmJiBsaW5lKSB7XG4gICAgICAgICAgICBkZXNpcmVkTGluZXMucHVzaChsaW5lKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZGVzaXJlZExpbmVzLmpvaW4oXCJcXG5cIik7XG59XG5cbmZ1bmN0aW9uIGlzTm9kZUZyYW1lKHN0YWNrTGluZSkge1xuICAgIHJldHVybiBzdGFja0xpbmUuaW5kZXhPZihcIihtb2R1bGUuanM6XCIpICE9PSAtMSB8fFxuICAgICAgICAgICBzdGFja0xpbmUuaW5kZXhPZihcIihub2RlLmpzOlwiKSAhPT0gLTE7XG59XG5cbmZ1bmN0aW9uIGdldEZpbGVOYW1lQW5kTGluZU51bWJlcihzdGFja0xpbmUpIHtcbiAgICAvLyBOYW1lZCBmdW5jdGlvbnM6IFwiYXQgZnVuY3Rpb25OYW1lIChmaWxlbmFtZTpsaW5lTnVtYmVyOmNvbHVtbk51bWJlcilcIlxuICAgIC8vIEluIElFMTAgZnVuY3Rpb24gbmFtZSBjYW4gaGF2ZSBzcGFjZXMgKFwiQW5vbnltb3VzIGZ1bmN0aW9uXCIpIE9fb1xuICAgIHZhciBhdHRlbXB0MSA9IC9hdCAuKyBcXCgoLispOihcXGQrKTooPzpcXGQrKVxcKSQvLmV4ZWMoc3RhY2tMaW5lKTtcbiAgICBpZiAoYXR0ZW1wdDEpIHtcbiAgICAgICAgcmV0dXJuIFthdHRlbXB0MVsxXSwgTnVtYmVyKGF0dGVtcHQxWzJdKV07XG4gICAgfVxuXG4gICAgLy8gQW5vbnltb3VzIGZ1bmN0aW9uczogXCJhdCBmaWxlbmFtZTpsaW5lTnVtYmVyOmNvbHVtbk51bWJlclwiXG4gICAgdmFyIGF0dGVtcHQyID0gL2F0IChbXiBdKyk6KFxcZCspOig/OlxcZCspJC8uZXhlYyhzdGFja0xpbmUpO1xuICAgIGlmIChhdHRlbXB0Mikge1xuICAgICAgICByZXR1cm4gW2F0dGVtcHQyWzFdLCBOdW1iZXIoYXR0ZW1wdDJbMl0pXTtcbiAgICB9XG5cbiAgICAvLyBGaXJlZm94IHN0eWxlOiBcImZ1bmN0aW9uQGZpbGVuYW1lOmxpbmVOdW1iZXIgb3IgQGZpbGVuYW1lOmxpbmVOdW1iZXJcIlxuICAgIHZhciBhdHRlbXB0MyA9IC8uKkAoLispOihcXGQrKSQvLmV4ZWMoc3RhY2tMaW5lKTtcbiAgICBpZiAoYXR0ZW1wdDMpIHtcbiAgICAgICAgcmV0dXJuIFthdHRlbXB0M1sxXSwgTnVtYmVyKGF0dGVtcHQzWzJdKV07XG4gICAgfVxufVxuXG5mdW5jdGlvbiBpc0ludGVybmFsRnJhbWUoc3RhY2tMaW5lKSB7XG4gICAgdmFyIGZpbGVOYW1lQW5kTGluZU51bWJlciA9IGdldEZpbGVOYW1lQW5kTGluZU51bWJlcihzdGFja0xpbmUpO1xuXG4gICAgaWYgKCFmaWxlTmFtZUFuZExpbmVOdW1iZXIpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIHZhciBmaWxlTmFtZSA9IGZpbGVOYW1lQW5kTGluZU51bWJlclswXTtcbiAgICB2YXIgbGluZU51bWJlciA9IGZpbGVOYW1lQW5kTGluZU51bWJlclsxXTtcblxuICAgIHJldHVybiBmaWxlTmFtZSA9PT0gcUZpbGVOYW1lICYmXG4gICAgICAgIGxpbmVOdW1iZXIgPj0gcVN0YXJ0aW5nTGluZSAmJlxuICAgICAgICBsaW5lTnVtYmVyIDw9IHFFbmRpbmdMaW5lO1xufVxuXG4vLyBkaXNjb3ZlciBvd24gZmlsZSBuYW1lIGFuZCBsaW5lIG51bWJlciByYW5nZSBmb3IgZmlsdGVyaW5nIHN0YWNrXG4vLyB0cmFjZXNcbmZ1bmN0aW9uIGNhcHR1cmVMaW5lKCkge1xuICAgIGlmICghaGFzU3RhY2tzKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0cnkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIHZhciBsaW5lcyA9IGUuc3RhY2suc3BsaXQoXCJcXG5cIik7XG4gICAgICAgIHZhciBmaXJzdExpbmUgPSBsaW5lc1swXS5pbmRleE9mKFwiQFwiKSA+IDAgPyBsaW5lc1sxXSA6IGxpbmVzWzJdO1xuICAgICAgICB2YXIgZmlsZU5hbWVBbmRMaW5lTnVtYmVyID0gZ2V0RmlsZU5hbWVBbmRMaW5lTnVtYmVyKGZpcnN0TGluZSk7XG4gICAgICAgIGlmICghZmlsZU5hbWVBbmRMaW5lTnVtYmVyKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBxRmlsZU5hbWUgPSBmaWxlTmFtZUFuZExpbmVOdW1iZXJbMF07XG4gICAgICAgIHJldHVybiBmaWxlTmFtZUFuZExpbmVOdW1iZXJbMV07XG4gICAgfVxufVxuXG5mdW5jdGlvbiBkZXByZWNhdGUoY2FsbGJhY2ssIG5hbWUsIGFsdGVybmF0aXZlKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgaWYgKHR5cGVvZiBjb25zb2xlICE9PSBcInVuZGVmaW5lZFwiICYmXG4gICAgICAgICAgICB0eXBlb2YgY29uc29sZS53YXJuID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgICAgIGNvbnNvbGUud2FybihuYW1lICsgXCIgaXMgZGVwcmVjYXRlZCwgdXNlIFwiICsgYWx0ZXJuYXRpdmUgK1xuICAgICAgICAgICAgICAgICAgICAgICAgIFwiIGluc3RlYWQuXCIsIG5ldyBFcnJvcihcIlwiKS5zdGFjayk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGNhbGxiYWNrLmFwcGx5KGNhbGxiYWNrLCBhcmd1bWVudHMpO1xuICAgIH07XG59XG5cbi8vIGVuZCBvZiBzaGltc1xuLy8gYmVnaW5uaW5nIG9mIHJlYWwgd29ya1xuXG4vKipcbiAqIENvbnN0cnVjdHMgYSBwcm9taXNlIGZvciBhbiBpbW1lZGlhdGUgcmVmZXJlbmNlLCBwYXNzZXMgcHJvbWlzZXMgdGhyb3VnaCwgb3JcbiAqIGNvZXJjZXMgcHJvbWlzZXMgZnJvbSBkaWZmZXJlbnQgc3lzdGVtcy5cbiAqIEBwYXJhbSB2YWx1ZSBpbW1lZGlhdGUgcmVmZXJlbmNlIG9yIHByb21pc2VcbiAqL1xuZnVuY3Rpb24gUSh2YWx1ZSkge1xuICAgIC8vIElmIHRoZSBvYmplY3QgaXMgYWxyZWFkeSBhIFByb21pc2UsIHJldHVybiBpdCBkaXJlY3RseS4gIFRoaXMgZW5hYmxlc1xuICAgIC8vIHRoZSByZXNvbHZlIGZ1bmN0aW9uIHRvIGJvdGggYmUgdXNlZCB0byBjcmVhdGVkIHJlZmVyZW5jZXMgZnJvbSBvYmplY3RzLFxuICAgIC8vIGJ1dCB0byB0b2xlcmFibHkgY29lcmNlIG5vbi1wcm9taXNlcyB0byBwcm9taXNlcy5cbiAgICBpZiAodmFsdWUgaW5zdGFuY2VvZiBQcm9taXNlKSB7XG4gICAgICAgIHJldHVybiB2YWx1ZTtcbiAgICB9XG5cbiAgICAvLyBhc3NpbWlsYXRlIHRoZW5hYmxlc1xuICAgIGlmIChpc1Byb21pc2VBbGlrZSh2YWx1ZSkpIHtcbiAgICAgICAgcmV0dXJuIGNvZXJjZSh2YWx1ZSk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIGZ1bGZpbGwodmFsdWUpO1xuICAgIH1cbn1cblEucmVzb2x2ZSA9IFE7XG5cbi8qKlxuICogUGVyZm9ybXMgYSB0YXNrIGluIGEgZnV0dXJlIHR1cm4gb2YgdGhlIGV2ZW50IGxvb3AuXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSB0YXNrXG4gKi9cblEubmV4dFRpY2sgPSBuZXh0VGljaztcblxuLyoqXG4gKiBDb250cm9scyB3aGV0aGVyIG9yIG5vdCBsb25nIHN0YWNrIHRyYWNlcyB3aWxsIGJlIG9uXG4gKi9cblEubG9uZ1N0YWNrU3VwcG9ydCA9IGZhbHNlO1xuXG4vLyBlbmFibGUgbG9uZyBzdGFja3MgaWYgUV9ERUJVRyBpcyBzZXRcbmlmICh0eXBlb2YgcHJvY2VzcyA9PT0gXCJvYmplY3RcIiAmJiBwcm9jZXNzICYmIHByb2Nlc3MuZW52ICYmIHByb2Nlc3MuZW52LlFfREVCVUcpIHtcbiAgICBRLmxvbmdTdGFja1N1cHBvcnQgPSB0cnVlO1xufVxuXG4vKipcbiAqIENvbnN0cnVjdHMgYSB7cHJvbWlzZSwgcmVzb2x2ZSwgcmVqZWN0fSBvYmplY3QuXG4gKlxuICogYHJlc29sdmVgIGlzIGEgY2FsbGJhY2sgdG8gaW52b2tlIHdpdGggYSBtb3JlIHJlc29sdmVkIHZhbHVlIGZvciB0aGVcbiAqIHByb21pc2UuIFRvIGZ1bGZpbGwgdGhlIHByb21pc2UsIGludm9rZSBgcmVzb2x2ZWAgd2l0aCBhbnkgdmFsdWUgdGhhdCBpc1xuICogbm90IGEgdGhlbmFibGUuIFRvIHJlamVjdCB0aGUgcHJvbWlzZSwgaW52b2tlIGByZXNvbHZlYCB3aXRoIGEgcmVqZWN0ZWRcbiAqIHRoZW5hYmxlLCBvciBpbnZva2UgYHJlamVjdGAgd2l0aCB0aGUgcmVhc29uIGRpcmVjdGx5LiBUbyByZXNvbHZlIHRoZVxuICogcHJvbWlzZSB0byBhbm90aGVyIHRoZW5hYmxlLCB0aHVzIHB1dHRpbmcgaXQgaW4gdGhlIHNhbWUgc3RhdGUsIGludm9rZVxuICogYHJlc29sdmVgIHdpdGggdGhhdCBvdGhlciB0aGVuYWJsZS5cbiAqL1xuUS5kZWZlciA9IGRlZmVyO1xuZnVuY3Rpb24gZGVmZXIoKSB7XG4gICAgLy8gaWYgXCJtZXNzYWdlc1wiIGlzIGFuIFwiQXJyYXlcIiwgdGhhdCBpbmRpY2F0ZXMgdGhhdCB0aGUgcHJvbWlzZSBoYXMgbm90IHlldFxuICAgIC8vIGJlZW4gcmVzb2x2ZWQuICBJZiBpdCBpcyBcInVuZGVmaW5lZFwiLCBpdCBoYXMgYmVlbiByZXNvbHZlZC4gIEVhY2hcbiAgICAvLyBlbGVtZW50IG9mIHRoZSBtZXNzYWdlcyBhcnJheSBpcyBpdHNlbGYgYW4gYXJyYXkgb2YgY29tcGxldGUgYXJndW1lbnRzIHRvXG4gICAgLy8gZm9yd2FyZCB0byB0aGUgcmVzb2x2ZWQgcHJvbWlzZS4gIFdlIGNvZXJjZSB0aGUgcmVzb2x1dGlvbiB2YWx1ZSB0byBhXG4gICAgLy8gcHJvbWlzZSB1c2luZyB0aGUgYHJlc29sdmVgIGZ1bmN0aW9uIGJlY2F1c2UgaXQgaGFuZGxlcyBib3RoIGZ1bGx5XG4gICAgLy8gbm9uLXRoZW5hYmxlIHZhbHVlcyBhbmQgb3RoZXIgdGhlbmFibGVzIGdyYWNlZnVsbHkuXG4gICAgdmFyIG1lc3NhZ2VzID0gW10sIHByb2dyZXNzTGlzdGVuZXJzID0gW10sIHJlc29sdmVkUHJvbWlzZTtcblxuICAgIHZhciBkZWZlcnJlZCA9IG9iamVjdF9jcmVhdGUoZGVmZXIucHJvdG90eXBlKTtcbiAgICB2YXIgcHJvbWlzZSA9IG9iamVjdF9jcmVhdGUoUHJvbWlzZS5wcm90b3R5cGUpO1xuXG4gICAgcHJvbWlzZS5wcm9taXNlRGlzcGF0Y2ggPSBmdW5jdGlvbiAocmVzb2x2ZSwgb3AsIG9wZXJhbmRzKSB7XG4gICAgICAgIHZhciBhcmdzID0gYXJyYXlfc2xpY2UoYXJndW1lbnRzKTtcbiAgICAgICAgaWYgKG1lc3NhZ2VzKSB7XG4gICAgICAgICAgICBtZXNzYWdlcy5wdXNoKGFyZ3MpO1xuICAgICAgICAgICAgaWYgKG9wID09PSBcIndoZW5cIiAmJiBvcGVyYW5kc1sxXSkgeyAvLyBwcm9ncmVzcyBvcGVyYW5kXG4gICAgICAgICAgICAgICAgcHJvZ3Jlc3NMaXN0ZW5lcnMucHVzaChvcGVyYW5kc1sxXSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBRLm5leHRUaWNrKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICByZXNvbHZlZFByb21pc2UucHJvbWlzZURpc3BhdGNoLmFwcGx5KHJlc29sdmVkUHJvbWlzZSwgYXJncyk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvLyBYWFggZGVwcmVjYXRlZFxuICAgIHByb21pc2UudmFsdWVPZiA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgaWYgKG1lc3NhZ2VzKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZTtcbiAgICAgICAgfVxuICAgICAgICB2YXIgbmVhcmVyVmFsdWUgPSBuZWFyZXIocmVzb2x2ZWRQcm9taXNlKTtcbiAgICAgICAgaWYgKGlzUHJvbWlzZShuZWFyZXJWYWx1ZSkpIHtcbiAgICAgICAgICAgIHJlc29sdmVkUHJvbWlzZSA9IG5lYXJlclZhbHVlOyAvLyBzaG9ydGVuIGNoYWluXG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG5lYXJlclZhbHVlO1xuICAgIH07XG5cbiAgICBwcm9taXNlLmluc3BlY3QgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGlmICghcmVzb2x2ZWRQcm9taXNlKSB7XG4gICAgICAgICAgICByZXR1cm4geyBzdGF0ZTogXCJwZW5kaW5nXCIgfTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gcmVzb2x2ZWRQcm9taXNlLmluc3BlY3QoKTtcbiAgICB9O1xuXG4gICAgaWYgKFEubG9uZ1N0YWNrU3VwcG9ydCAmJiBoYXNTdGFja3MpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcigpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAvLyBOT1RFOiBkb24ndCB0cnkgdG8gdXNlIGBFcnJvci5jYXB0dXJlU3RhY2tUcmFjZWAgb3IgdHJhbnNmZXIgdGhlXG4gICAgICAgICAgICAvLyBhY2Nlc3NvciBhcm91bmQ7IHRoYXQgY2F1c2VzIG1lbW9yeSBsZWFrcyBhcyBwZXIgR0gtMTExLiBKdXN0XG4gICAgICAgICAgICAvLyByZWlmeSB0aGUgc3RhY2sgdHJhY2UgYXMgYSBzdHJpbmcgQVNBUC5cbiAgICAgICAgICAgIC8vXG4gICAgICAgICAgICAvLyBBdCB0aGUgc2FtZSB0aW1lLCBjdXQgb2ZmIHRoZSBmaXJzdCBsaW5lOyBpdCdzIGFsd2F5cyBqdXN0XG4gICAgICAgICAgICAvLyBcIltvYmplY3QgUHJvbWlzZV1cXG5cIiwgYXMgcGVyIHRoZSBgdG9TdHJpbmdgLlxuICAgICAgICAgICAgcHJvbWlzZS5zdGFjayA9IGUuc3RhY2suc3Vic3RyaW5nKGUuc3RhY2suaW5kZXhPZihcIlxcblwiKSArIDEpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLy8gTk9URTogd2UgZG8gdGhlIGNoZWNrcyBmb3IgYHJlc29sdmVkUHJvbWlzZWAgaW4gZWFjaCBtZXRob2QsIGluc3RlYWQgb2ZcbiAgICAvLyBjb25zb2xpZGF0aW5nIHRoZW0gaW50byBgYmVjb21lYCwgc2luY2Ugb3RoZXJ3aXNlIHdlJ2QgY3JlYXRlIG5ld1xuICAgIC8vIHByb21pc2VzIHdpdGggdGhlIGxpbmVzIGBiZWNvbWUod2hhdGV2ZXIodmFsdWUpKWAuIFNlZSBlLmcuIEdILTI1Mi5cblxuICAgIGZ1bmN0aW9uIGJlY29tZShuZXdQcm9taXNlKSB7XG4gICAgICAgIHJlc29sdmVkUHJvbWlzZSA9IG5ld1Byb21pc2U7XG4gICAgICAgIHByb21pc2Uuc291cmNlID0gbmV3UHJvbWlzZTtcblxuICAgICAgICBhcnJheV9yZWR1Y2UobWVzc2FnZXMsIGZ1bmN0aW9uICh1bmRlZmluZWQsIG1lc3NhZ2UpIHtcbiAgICAgICAgICAgIFEubmV4dFRpY2soZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIG5ld1Byb21pc2UucHJvbWlzZURpc3BhdGNoLmFwcGx5KG5ld1Byb21pc2UsIG1lc3NhZ2UpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH0sIHZvaWQgMCk7XG5cbiAgICAgICAgbWVzc2FnZXMgPSB2b2lkIDA7XG4gICAgICAgIHByb2dyZXNzTGlzdGVuZXJzID0gdm9pZCAwO1xuICAgIH1cblxuICAgIGRlZmVycmVkLnByb21pc2UgPSBwcm9taXNlO1xuICAgIGRlZmVycmVkLnJlc29sdmUgPSBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgaWYgKHJlc29sdmVkUHJvbWlzZSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgYmVjb21lKFEodmFsdWUpKTtcbiAgICB9O1xuXG4gICAgZGVmZXJyZWQuZnVsZmlsbCA9IGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICBpZiAocmVzb2x2ZWRQcm9taXNlKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBiZWNvbWUoZnVsZmlsbCh2YWx1ZSkpO1xuICAgIH07XG4gICAgZGVmZXJyZWQucmVqZWN0ID0gZnVuY3Rpb24gKHJlYXNvbikge1xuICAgICAgICBpZiAocmVzb2x2ZWRQcm9taXNlKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBiZWNvbWUocmVqZWN0KHJlYXNvbikpO1xuICAgIH07XG4gICAgZGVmZXJyZWQubm90aWZ5ID0gZnVuY3Rpb24gKHByb2dyZXNzKSB7XG4gICAgICAgIGlmIChyZXNvbHZlZFByb21pc2UpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGFycmF5X3JlZHVjZShwcm9ncmVzc0xpc3RlbmVycywgZnVuY3Rpb24gKHVuZGVmaW5lZCwgcHJvZ3Jlc3NMaXN0ZW5lcikge1xuICAgICAgICAgICAgUS5uZXh0VGljayhmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgcHJvZ3Jlc3NMaXN0ZW5lcihwcm9ncmVzcyk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSwgdm9pZCAwKTtcbiAgICB9O1xuXG4gICAgcmV0dXJuIGRlZmVycmVkO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSBOb2RlLXN0eWxlIGNhbGxiYWNrIHRoYXQgd2lsbCByZXNvbHZlIG9yIHJlamVjdCB0aGUgZGVmZXJyZWRcbiAqIHByb21pc2UuXG4gKiBAcmV0dXJucyBhIG5vZGViYWNrXG4gKi9cbmRlZmVyLnByb3RvdHlwZS5tYWtlTm9kZVJlc29sdmVyID0gZnVuY3Rpb24gKCkge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICByZXR1cm4gZnVuY3Rpb24gKGVycm9yLCB2YWx1ZSkge1xuICAgICAgICBpZiAoZXJyb3IpIHtcbiAgICAgICAgICAgIHNlbGYucmVqZWN0KGVycm9yKTtcbiAgICAgICAgfSBlbHNlIGlmIChhcmd1bWVudHMubGVuZ3RoID4gMikge1xuICAgICAgICAgICAgc2VsZi5yZXNvbHZlKGFycmF5X3NsaWNlKGFyZ3VtZW50cywgMSkpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgc2VsZi5yZXNvbHZlKHZhbHVlKTtcbiAgICAgICAgfVxuICAgIH07XG59O1xuXG4vKipcbiAqIEBwYXJhbSByZXNvbHZlciB7RnVuY3Rpb259IGEgZnVuY3Rpb24gdGhhdCByZXR1cm5zIG5vdGhpbmcgYW5kIGFjY2VwdHNcbiAqIHRoZSByZXNvbHZlLCByZWplY3QsIGFuZCBub3RpZnkgZnVuY3Rpb25zIGZvciBhIGRlZmVycmVkLlxuICogQHJldHVybnMgYSBwcm9taXNlIHRoYXQgbWF5IGJlIHJlc29sdmVkIHdpdGggdGhlIGdpdmVuIHJlc29sdmUgYW5kIHJlamVjdFxuICogZnVuY3Rpb25zLCBvciByZWplY3RlZCBieSBhIHRocm93biBleGNlcHRpb24gaW4gcmVzb2x2ZXJcbiAqL1xuUS5Qcm9taXNlID0gcHJvbWlzZTsgLy8gRVM2XG5RLnByb21pc2UgPSBwcm9taXNlO1xuZnVuY3Rpb24gcHJvbWlzZShyZXNvbHZlcikge1xuICAgIGlmICh0eXBlb2YgcmVzb2x2ZXIgIT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwicmVzb2x2ZXIgbXVzdCBiZSBhIGZ1bmN0aW9uLlwiKTtcbiAgICB9XG4gICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICB0cnkge1xuICAgICAgICByZXNvbHZlcihkZWZlcnJlZC5yZXNvbHZlLCBkZWZlcnJlZC5yZWplY3QsIGRlZmVycmVkLm5vdGlmeSk7XG4gICAgfSBjYXRjaCAocmVhc29uKSB7XG4gICAgICAgIGRlZmVycmVkLnJlamVjdChyZWFzb24pO1xuICAgIH1cbiAgICByZXR1cm4gZGVmZXJyZWQucHJvbWlzZTtcbn1cblxucHJvbWlzZS5yYWNlID0gcmFjZTsgLy8gRVM2XG5wcm9taXNlLmFsbCA9IGFsbDsgLy8gRVM2XG5wcm9taXNlLnJlamVjdCA9IHJlamVjdDsgLy8gRVM2XG5wcm9taXNlLnJlc29sdmUgPSBROyAvLyBFUzZcblxuLy8gWFhYIGV4cGVyaW1lbnRhbC4gIFRoaXMgbWV0aG9kIGlzIGEgd2F5IHRvIGRlbm90ZSB0aGF0IGEgbG9jYWwgdmFsdWUgaXNcbi8vIHNlcmlhbGl6YWJsZSBhbmQgc2hvdWxkIGJlIGltbWVkaWF0ZWx5IGRpc3BhdGNoZWQgdG8gYSByZW1vdGUgdXBvbiByZXF1ZXN0LFxuLy8gaW5zdGVhZCBvZiBwYXNzaW5nIGEgcmVmZXJlbmNlLlxuUS5wYXNzQnlDb3B5ID0gZnVuY3Rpb24gKG9iamVjdCkge1xuICAgIC8vZnJlZXplKG9iamVjdCk7XG4gICAgLy9wYXNzQnlDb3BpZXMuc2V0KG9iamVjdCwgdHJ1ZSk7XG4gICAgcmV0dXJuIG9iamVjdDtcbn07XG5cblByb21pc2UucHJvdG90eXBlLnBhc3NCeUNvcHkgPSBmdW5jdGlvbiAoKSB7XG4gICAgLy9mcmVlemUob2JqZWN0KTtcbiAgICAvL3Bhc3NCeUNvcGllcy5zZXQob2JqZWN0LCB0cnVlKTtcbiAgICByZXR1cm4gdGhpcztcbn07XG5cbi8qKlxuICogSWYgdHdvIHByb21pc2VzIGV2ZW50dWFsbHkgZnVsZmlsbCB0byB0aGUgc2FtZSB2YWx1ZSwgcHJvbWlzZXMgdGhhdCB2YWx1ZSxcbiAqIGJ1dCBvdGhlcndpc2UgcmVqZWN0cy5cbiAqIEBwYXJhbSB4IHtBbnkqfVxuICogQHBhcmFtIHkge0FueSp9XG4gKiBAcmV0dXJucyB7QW55Kn0gYSBwcm9taXNlIGZvciB4IGFuZCB5IGlmIHRoZXkgYXJlIHRoZSBzYW1lLCBidXQgYSByZWplY3Rpb25cbiAqIG90aGVyd2lzZS5cbiAqXG4gKi9cblEuam9pbiA9IGZ1bmN0aW9uICh4LCB5KSB7XG4gICAgcmV0dXJuIFEoeCkuam9pbih5KTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLmpvaW4gPSBmdW5jdGlvbiAodGhhdCkge1xuICAgIHJldHVybiBRKFt0aGlzLCB0aGF0XSkuc3ByZWFkKGZ1bmN0aW9uICh4LCB5KSB7XG4gICAgICAgIGlmICh4ID09PSB5KSB7XG4gICAgICAgICAgICAvLyBUT0RPOiBcIj09PVwiIHNob3VsZCBiZSBPYmplY3QuaXMgb3IgZXF1aXZcbiAgICAgICAgICAgIHJldHVybiB4O1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiQ2FuJ3Qgam9pbjogbm90IHRoZSBzYW1lOiBcIiArIHggKyBcIiBcIiArIHkpO1xuICAgICAgICB9XG4gICAgfSk7XG59O1xuXG4vKipcbiAqIFJldHVybnMgYSBwcm9taXNlIGZvciB0aGUgZmlyc3Qgb2YgYW4gYXJyYXkgb2YgcHJvbWlzZXMgdG8gYmVjb21lIHNldHRsZWQuXG4gKiBAcGFyYW0gYW5zd2VycyB7QXJyYXlbQW55Kl19IHByb21pc2VzIHRvIHJhY2VcbiAqIEByZXR1cm5zIHtBbnkqfSB0aGUgZmlyc3QgcHJvbWlzZSB0byBiZSBzZXR0bGVkXG4gKi9cblEucmFjZSA9IHJhY2U7XG5mdW5jdGlvbiByYWNlKGFuc3dlclBzKSB7XG4gICAgcmV0dXJuIHByb21pc2UoZnVuY3Rpb24gKHJlc29sdmUsIHJlamVjdCkge1xuICAgICAgICAvLyBTd2l0Y2ggdG8gdGhpcyBvbmNlIHdlIGNhbiBhc3N1bWUgYXQgbGVhc3QgRVM1XG4gICAgICAgIC8vIGFuc3dlclBzLmZvckVhY2goZnVuY3Rpb24gKGFuc3dlclApIHtcbiAgICAgICAgLy8gICAgIFEoYW5zd2VyUCkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICAvLyB9KTtcbiAgICAgICAgLy8gVXNlIHRoaXMgaW4gdGhlIG1lYW50aW1lXG4gICAgICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBhbnN3ZXJQcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgUShhbnN3ZXJQc1tpXSkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICB9XG4gICAgfSk7XG59XG5cblByb21pc2UucHJvdG90eXBlLnJhY2UgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIHRoaXMudGhlbihRLnJhY2UpO1xufTtcblxuLyoqXG4gKiBDb25zdHJ1Y3RzIGEgUHJvbWlzZSB3aXRoIGEgcHJvbWlzZSBkZXNjcmlwdG9yIG9iamVjdCBhbmQgb3B0aW9uYWwgZmFsbGJhY2tcbiAqIGZ1bmN0aW9uLiAgVGhlIGRlc2NyaXB0b3IgY29udGFpbnMgbWV0aG9kcyBsaWtlIHdoZW4ocmVqZWN0ZWQpLCBnZXQobmFtZSksXG4gKiBzZXQobmFtZSwgdmFsdWUpLCBwb3N0KG5hbWUsIGFyZ3MpLCBhbmQgZGVsZXRlKG5hbWUpLCB3aGljaCBhbGxcbiAqIHJldHVybiBlaXRoZXIgYSB2YWx1ZSwgYSBwcm9taXNlIGZvciBhIHZhbHVlLCBvciBhIHJlamVjdGlvbi4gIFRoZSBmYWxsYmFja1xuICogYWNjZXB0cyB0aGUgb3BlcmF0aW9uIG5hbWUsIGEgcmVzb2x2ZXIsIGFuZCBhbnkgZnVydGhlciBhcmd1bWVudHMgdGhhdCB3b3VsZFxuICogaGF2ZSBiZWVuIGZvcndhcmRlZCB0byB0aGUgYXBwcm9wcmlhdGUgbWV0aG9kIGFib3ZlIGhhZCBhIG1ldGhvZCBiZWVuXG4gKiBwcm92aWRlZCB3aXRoIHRoZSBwcm9wZXIgbmFtZS4gIFRoZSBBUEkgbWFrZXMgbm8gZ3VhcmFudGVlcyBhYm91dCB0aGUgbmF0dXJlXG4gKiBvZiB0aGUgcmV0dXJuZWQgb2JqZWN0LCBhcGFydCBmcm9tIHRoYXQgaXQgaXMgdXNhYmxlIHdoZXJlZXZlciBwcm9taXNlcyBhcmVcbiAqIGJvdWdodCBhbmQgc29sZC5cbiAqL1xuUS5tYWtlUHJvbWlzZSA9IFByb21pc2U7XG5mdW5jdGlvbiBQcm9taXNlKGRlc2NyaXB0b3IsIGZhbGxiYWNrLCBpbnNwZWN0KSB7XG4gICAgaWYgKGZhbGxiYWNrID09PSB2b2lkIDApIHtcbiAgICAgICAgZmFsbGJhY2sgPSBmdW5jdGlvbiAob3ApIHtcbiAgICAgICAgICAgIHJldHVybiByZWplY3QobmV3IEVycm9yKFxuICAgICAgICAgICAgICAgIFwiUHJvbWlzZSBkb2VzIG5vdCBzdXBwb3J0IG9wZXJhdGlvbjogXCIgKyBvcFxuICAgICAgICAgICAgKSk7XG4gICAgICAgIH07XG4gICAgfVxuICAgIGlmIChpbnNwZWN0ID09PSB2b2lkIDApIHtcbiAgICAgICAgaW5zcGVjdCA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHJldHVybiB7c3RhdGU6IFwidW5rbm93blwifTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICB2YXIgcHJvbWlzZSA9IG9iamVjdF9jcmVhdGUoUHJvbWlzZS5wcm90b3R5cGUpO1xuXG4gICAgcHJvbWlzZS5wcm9taXNlRGlzcGF0Y2ggPSBmdW5jdGlvbiAocmVzb2x2ZSwgb3AsIGFyZ3MpIHtcbiAgICAgICAgdmFyIHJlc3VsdDtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGlmIChkZXNjcmlwdG9yW29wXSkge1xuICAgICAgICAgICAgICAgIHJlc3VsdCA9IGRlc2NyaXB0b3Jbb3BdLmFwcGx5KHByb21pc2UsIGFyZ3MpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICByZXN1bHQgPSBmYWxsYmFjay5jYWxsKHByb21pc2UsIG9wLCBhcmdzKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBjYXRjaCAoZXhjZXB0aW9uKSB7XG4gICAgICAgICAgICByZXN1bHQgPSByZWplY3QoZXhjZXB0aW9uKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAocmVzb2x2ZSkge1xuICAgICAgICAgICAgcmVzb2x2ZShyZXN1bHQpO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIHByb21pc2UuaW5zcGVjdCA9IGluc3BlY3Q7XG5cbiAgICAvLyBYWFggZGVwcmVjYXRlZCBgdmFsdWVPZmAgYW5kIGBleGNlcHRpb25gIHN1cHBvcnRcbiAgICBpZiAoaW5zcGVjdCkge1xuICAgICAgICB2YXIgaW5zcGVjdGVkID0gaW5zcGVjdCgpO1xuICAgICAgICBpZiAoaW5zcGVjdGVkLnN0YXRlID09PSBcInJlamVjdGVkXCIpIHtcbiAgICAgICAgICAgIHByb21pc2UuZXhjZXB0aW9uID0gaW5zcGVjdGVkLnJlYXNvbjtcbiAgICAgICAgfVxuXG4gICAgICAgIHByb21pc2UudmFsdWVPZiA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHZhciBpbnNwZWN0ZWQgPSBpbnNwZWN0KCk7XG4gICAgICAgICAgICBpZiAoaW5zcGVjdGVkLnN0YXRlID09PSBcInBlbmRpbmdcIiB8fFxuICAgICAgICAgICAgICAgIGluc3BlY3RlZC5zdGF0ZSA9PT0gXCJyZWplY3RlZFwiKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gaW5zcGVjdGVkLnZhbHVlO1xuICAgICAgICB9O1xuICAgIH1cblxuICAgIHJldHVybiBwcm9taXNlO1xufVxuXG5Qcm9taXNlLnByb3RvdHlwZS50b1N0cmluZyA9IGZ1bmN0aW9uICgpIHtcbiAgICByZXR1cm4gXCJbb2JqZWN0IFByb21pc2VdXCI7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS50aGVuID0gZnVuY3Rpb24gKGZ1bGZpbGxlZCwgcmVqZWN0ZWQsIHByb2dyZXNzZWQpIHtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICB2YXIgZG9uZSA9IGZhbHNlOyAgIC8vIGVuc3VyZSB0aGUgdW50cnVzdGVkIHByb21pc2UgbWFrZXMgYXQgbW9zdCBhXG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBzaW5nbGUgY2FsbCB0byBvbmUgb2YgdGhlIGNhbGxiYWNrc1xuXG4gICAgZnVuY3Rpb24gX2Z1bGZpbGxlZCh2YWx1ZSkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgcmV0dXJuIHR5cGVvZiBmdWxmaWxsZWQgPT09IFwiZnVuY3Rpb25cIiA/IGZ1bGZpbGxlZCh2YWx1ZSkgOiB2YWx1ZTtcbiAgICAgICAgfSBjYXRjaCAoZXhjZXB0aW9uKSB7XG4gICAgICAgICAgICByZXR1cm4gcmVqZWN0KGV4Y2VwdGlvbik7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBmdW5jdGlvbiBfcmVqZWN0ZWQoZXhjZXB0aW9uKSB7XG4gICAgICAgIGlmICh0eXBlb2YgcmVqZWN0ZWQgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICAgICAgbWFrZVN0YWNrVHJhY2VMb25nKGV4Y2VwdGlvbiwgc2VsZik7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIHJldHVybiByZWplY3RlZChleGNlcHRpb24pO1xuICAgICAgICAgICAgfSBjYXRjaCAobmV3RXhjZXB0aW9uKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHJlamVjdChuZXdFeGNlcHRpb24pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiByZWplY3QoZXhjZXB0aW9uKTtcbiAgICB9XG5cbiAgICBmdW5jdGlvbiBfcHJvZ3Jlc3NlZCh2YWx1ZSkge1xuICAgICAgICByZXR1cm4gdHlwZW9mIHByb2dyZXNzZWQgPT09IFwiZnVuY3Rpb25cIiA/IHByb2dyZXNzZWQodmFsdWUpIDogdmFsdWU7XG4gICAgfVxuXG4gICAgUS5uZXh0VGljayhmdW5jdGlvbiAoKSB7XG4gICAgICAgIHNlbGYucHJvbWlzZURpc3BhdGNoKGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICAgICAgaWYgKGRvbmUpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBkb25lID0gdHJ1ZTtcblxuICAgICAgICAgICAgZGVmZXJyZWQucmVzb2x2ZShfZnVsZmlsbGVkKHZhbHVlKSk7XG4gICAgICAgIH0sIFwid2hlblwiLCBbZnVuY3Rpb24gKGV4Y2VwdGlvbikge1xuICAgICAgICAgICAgaWYgKGRvbmUpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBkb25lID0gdHJ1ZTtcblxuICAgICAgICAgICAgZGVmZXJyZWQucmVzb2x2ZShfcmVqZWN0ZWQoZXhjZXB0aW9uKSk7XG4gICAgICAgIH1dKTtcbiAgICB9KTtcblxuICAgIC8vIFByb2dyZXNzIHByb3BhZ2F0b3IgbmVlZCB0byBiZSBhdHRhY2hlZCBpbiB0aGUgY3VycmVudCB0aWNrLlxuICAgIHNlbGYucHJvbWlzZURpc3BhdGNoKHZvaWQgMCwgXCJ3aGVuXCIsIFt2b2lkIDAsIGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICB2YXIgbmV3VmFsdWU7XG4gICAgICAgIHZhciB0aHJldyA9IGZhbHNlO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgbmV3VmFsdWUgPSBfcHJvZ3Jlc3NlZCh2YWx1ZSk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHRocmV3ID0gdHJ1ZTtcbiAgICAgICAgICAgIGlmIChRLm9uZXJyb3IpIHtcbiAgICAgICAgICAgICAgICBRLm9uZXJyb3IoZSk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHRocm93IGU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIXRocmV3KSB7XG4gICAgICAgICAgICBkZWZlcnJlZC5ub3RpZnkobmV3VmFsdWUpO1xuICAgICAgICB9XG4gICAgfV0pO1xuXG4gICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG59O1xuXG5RLnRhcCA9IGZ1bmN0aW9uIChwcm9taXNlLCBjYWxsYmFjaykge1xuICAgIHJldHVybiBRKHByb21pc2UpLnRhcChjYWxsYmFjayk7XG59O1xuXG4vKipcbiAqIFdvcmtzIGFsbW9zdCBsaWtlIFwiZmluYWxseVwiLCBidXQgbm90IGNhbGxlZCBmb3IgcmVqZWN0aW9ucy5cbiAqIE9yaWdpbmFsIHJlc29sdXRpb24gdmFsdWUgaXMgcGFzc2VkIHRocm91Z2ggY2FsbGJhY2sgdW5hZmZlY3RlZC5cbiAqIENhbGxiYWNrIG1heSByZXR1cm4gYSBwcm9taXNlIHRoYXQgd2lsbCBiZSBhd2FpdGVkIGZvci5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGNhbGxiYWNrXG4gKiBAcmV0dXJucyB7US5Qcm9taXNlfVxuICogQGV4YW1wbGVcbiAqIGRvU29tZXRoaW5nKClcbiAqICAgLnRoZW4oLi4uKVxuICogICAudGFwKGNvbnNvbGUubG9nKVxuICogICAudGhlbiguLi4pO1xuICovXG5Qcm9taXNlLnByb3RvdHlwZS50YXAgPSBmdW5jdGlvbiAoY2FsbGJhY2spIHtcbiAgICBjYWxsYmFjayA9IFEoY2FsbGJhY2spO1xuXG4gICAgcmV0dXJuIHRoaXMudGhlbihmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgcmV0dXJuIGNhbGxiYWNrLmZjYWxsKHZhbHVlKS50aGVuUmVzb2x2ZSh2YWx1ZSk7XG4gICAgfSk7XG59O1xuXG4vKipcbiAqIFJlZ2lzdGVycyBhbiBvYnNlcnZlciBvbiBhIHByb21pc2UuXG4gKlxuICogR3VhcmFudGVlczpcbiAqXG4gKiAxLiB0aGF0IGZ1bGZpbGxlZCBhbmQgcmVqZWN0ZWQgd2lsbCBiZSBjYWxsZWQgb25seSBvbmNlLlxuICogMi4gdGhhdCBlaXRoZXIgdGhlIGZ1bGZpbGxlZCBjYWxsYmFjayBvciB0aGUgcmVqZWN0ZWQgY2FsbGJhY2sgd2lsbCBiZVxuICogICAgY2FsbGVkLCBidXQgbm90IGJvdGguXG4gKiAzLiB0aGF0IGZ1bGZpbGxlZCBhbmQgcmVqZWN0ZWQgd2lsbCBub3QgYmUgY2FsbGVkIGluIHRoaXMgdHVybi5cbiAqXG4gKiBAcGFyYW0gdmFsdWUgICAgICBwcm9taXNlIG9yIGltbWVkaWF0ZSByZWZlcmVuY2UgdG8gb2JzZXJ2ZVxuICogQHBhcmFtIGZ1bGZpbGxlZCAgZnVuY3Rpb24gdG8gYmUgY2FsbGVkIHdpdGggdGhlIGZ1bGZpbGxlZCB2YWx1ZVxuICogQHBhcmFtIHJlamVjdGVkICAgZnVuY3Rpb24gdG8gYmUgY2FsbGVkIHdpdGggdGhlIHJlamVjdGlvbiBleGNlcHRpb25cbiAqIEBwYXJhbSBwcm9ncmVzc2VkIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCBvbiBhbnkgcHJvZ3Jlc3Mgbm90aWZpY2F0aW9uc1xuICogQHJldHVybiBwcm9taXNlIGZvciB0aGUgcmV0dXJuIHZhbHVlIGZyb20gdGhlIGludm9rZWQgY2FsbGJhY2tcbiAqL1xuUS53aGVuID0gd2hlbjtcbmZ1bmN0aW9uIHdoZW4odmFsdWUsIGZ1bGZpbGxlZCwgcmVqZWN0ZWQsIHByb2dyZXNzZWQpIHtcbiAgICByZXR1cm4gUSh2YWx1ZSkudGhlbihmdWxmaWxsZWQsIHJlamVjdGVkLCBwcm9ncmVzc2VkKTtcbn1cblxuUHJvbWlzZS5wcm90b3R5cGUudGhlblJlc29sdmUgPSBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICByZXR1cm4gdGhpcy50aGVuKGZ1bmN0aW9uICgpIHsgcmV0dXJuIHZhbHVlOyB9KTtcbn07XG5cblEudGhlblJlc29sdmUgPSBmdW5jdGlvbiAocHJvbWlzZSwgdmFsdWUpIHtcbiAgICByZXR1cm4gUShwcm9taXNlKS50aGVuUmVzb2x2ZSh2YWx1ZSk7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS50aGVuUmVqZWN0ID0gZnVuY3Rpb24gKHJlYXNvbikge1xuICAgIHJldHVybiB0aGlzLnRoZW4oZnVuY3Rpb24gKCkgeyB0aHJvdyByZWFzb247IH0pO1xufTtcblxuUS50aGVuUmVqZWN0ID0gZnVuY3Rpb24gKHByb21pc2UsIHJlYXNvbikge1xuICAgIHJldHVybiBRKHByb21pc2UpLnRoZW5SZWplY3QocmVhc29uKTtcbn07XG5cbi8qKlxuICogSWYgYW4gb2JqZWN0IGlzIG5vdCBhIHByb21pc2UsIGl0IGlzIGFzIFwibmVhclwiIGFzIHBvc3NpYmxlLlxuICogSWYgYSBwcm9taXNlIGlzIHJlamVjdGVkLCBpdCBpcyBhcyBcIm5lYXJcIiBhcyBwb3NzaWJsZSB0b28uXG4gKiBJZiBpdOKAmXMgYSBmdWxmaWxsZWQgcHJvbWlzZSwgdGhlIGZ1bGZpbGxtZW50IHZhbHVlIGlzIG5lYXJlci5cbiAqIElmIGl04oCZcyBhIGRlZmVycmVkIHByb21pc2UgYW5kIHRoZSBkZWZlcnJlZCBoYXMgYmVlbiByZXNvbHZlZCwgdGhlXG4gKiByZXNvbHV0aW9uIGlzIFwibmVhcmVyXCIuXG4gKiBAcGFyYW0gb2JqZWN0XG4gKiBAcmV0dXJucyBtb3N0IHJlc29sdmVkIChuZWFyZXN0KSBmb3JtIG9mIHRoZSBvYmplY3RcbiAqL1xuXG4vLyBYWFggc2hvdWxkIHdlIHJlLWRvIHRoaXM/XG5RLm5lYXJlciA9IG5lYXJlcjtcbmZ1bmN0aW9uIG5lYXJlcih2YWx1ZSkge1xuICAgIGlmIChpc1Byb21pc2UodmFsdWUpKSB7XG4gICAgICAgIHZhciBpbnNwZWN0ZWQgPSB2YWx1ZS5pbnNwZWN0KCk7XG4gICAgICAgIGlmIChpbnNwZWN0ZWQuc3RhdGUgPT09IFwiZnVsZmlsbGVkXCIpIHtcbiAgICAgICAgICAgIHJldHVybiBpbnNwZWN0ZWQudmFsdWU7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHZhbHVlO1xufVxuXG4vKipcbiAqIEByZXR1cm5zIHdoZXRoZXIgdGhlIGdpdmVuIG9iamVjdCBpcyBhIHByb21pc2UuXG4gKiBPdGhlcndpc2UgaXQgaXMgYSBmdWxmaWxsZWQgdmFsdWUuXG4gKi9cblEuaXNQcm9taXNlID0gaXNQcm9taXNlO1xuZnVuY3Rpb24gaXNQcm9taXNlKG9iamVjdCkge1xuICAgIHJldHVybiBvYmplY3QgaW5zdGFuY2VvZiBQcm9taXNlO1xufVxuXG5RLmlzUHJvbWlzZUFsaWtlID0gaXNQcm9taXNlQWxpa2U7XG5mdW5jdGlvbiBpc1Byb21pc2VBbGlrZShvYmplY3QpIHtcbiAgICByZXR1cm4gaXNPYmplY3Qob2JqZWN0KSAmJiB0eXBlb2Ygb2JqZWN0LnRoZW4gPT09IFwiZnVuY3Rpb25cIjtcbn1cblxuLyoqXG4gKiBAcmV0dXJucyB3aGV0aGVyIHRoZSBnaXZlbiBvYmplY3QgaXMgYSBwZW5kaW5nIHByb21pc2UsIG1lYW5pbmcgbm90XG4gKiBmdWxmaWxsZWQgb3IgcmVqZWN0ZWQuXG4gKi9cblEuaXNQZW5kaW5nID0gaXNQZW5kaW5nO1xuZnVuY3Rpb24gaXNQZW5kaW5nKG9iamVjdCkge1xuICAgIHJldHVybiBpc1Byb21pc2Uob2JqZWN0KSAmJiBvYmplY3QuaW5zcGVjdCgpLnN0YXRlID09PSBcInBlbmRpbmdcIjtcbn1cblxuUHJvbWlzZS5wcm90b3R5cGUuaXNQZW5kaW5nID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiB0aGlzLmluc3BlY3QoKS5zdGF0ZSA9PT0gXCJwZW5kaW5nXCI7XG59O1xuXG4vKipcbiAqIEByZXR1cm5zIHdoZXRoZXIgdGhlIGdpdmVuIG9iamVjdCBpcyBhIHZhbHVlIG9yIGZ1bGZpbGxlZFxuICogcHJvbWlzZS5cbiAqL1xuUS5pc0Z1bGZpbGxlZCA9IGlzRnVsZmlsbGVkO1xuZnVuY3Rpb24gaXNGdWxmaWxsZWQob2JqZWN0KSB7XG4gICAgcmV0dXJuICFpc1Byb21pc2Uob2JqZWN0KSB8fCBvYmplY3QuaW5zcGVjdCgpLnN0YXRlID09PSBcImZ1bGZpbGxlZFwiO1xufVxuXG5Qcm9taXNlLnByb3RvdHlwZS5pc0Z1bGZpbGxlZCA9IGZ1bmN0aW9uICgpIHtcbiAgICByZXR1cm4gdGhpcy5pbnNwZWN0KCkuc3RhdGUgPT09IFwiZnVsZmlsbGVkXCI7XG59O1xuXG4vKipcbiAqIEByZXR1cm5zIHdoZXRoZXIgdGhlIGdpdmVuIG9iamVjdCBpcyBhIHJlamVjdGVkIHByb21pc2UuXG4gKi9cblEuaXNSZWplY3RlZCA9IGlzUmVqZWN0ZWQ7XG5mdW5jdGlvbiBpc1JlamVjdGVkKG9iamVjdCkge1xuICAgIHJldHVybiBpc1Byb21pc2Uob2JqZWN0KSAmJiBvYmplY3QuaW5zcGVjdCgpLnN0YXRlID09PSBcInJlamVjdGVkXCI7XG59XG5cblByb21pc2UucHJvdG90eXBlLmlzUmVqZWN0ZWQgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIHRoaXMuaW5zcGVjdCgpLnN0YXRlID09PSBcInJlamVjdGVkXCI7XG59O1xuXG4vLy8vIEJFR0lOIFVOSEFORExFRCBSRUpFQ1RJT04gVFJBQ0tJTkdcblxuLy8gVGhpcyBwcm9taXNlIGxpYnJhcnkgY29uc3VtZXMgZXhjZXB0aW9ucyB0aHJvd24gaW4gaGFuZGxlcnMgc28gdGhleSBjYW4gYmVcbi8vIGhhbmRsZWQgYnkgYSBzdWJzZXF1ZW50IHByb21pc2UuICBUaGUgZXhjZXB0aW9ucyBnZXQgYWRkZWQgdG8gdGhpcyBhcnJheSB3aGVuXG4vLyB0aGV5IGFyZSBjcmVhdGVkLCBhbmQgcmVtb3ZlZCB3aGVuIHRoZXkgYXJlIGhhbmRsZWQuICBOb3RlIHRoYXQgaW4gRVM2IG9yXG4vLyBzaGltbWVkIGVudmlyb25tZW50cywgdGhpcyB3b3VsZCBuYXR1cmFsbHkgYmUgYSBgU2V0YC5cbnZhciB1bmhhbmRsZWRSZWFzb25zID0gW107XG52YXIgdW5oYW5kbGVkUmVqZWN0aW9ucyA9IFtdO1xudmFyIHJlcG9ydGVkVW5oYW5kbGVkUmVqZWN0aW9ucyA9IFtdO1xudmFyIHRyYWNrVW5oYW5kbGVkUmVqZWN0aW9ucyA9IHRydWU7XG5cbmZ1bmN0aW9uIHJlc2V0VW5oYW5kbGVkUmVqZWN0aW9ucygpIHtcbiAgICB1bmhhbmRsZWRSZWFzb25zLmxlbmd0aCA9IDA7XG4gICAgdW5oYW5kbGVkUmVqZWN0aW9ucy5sZW5ndGggPSAwO1xuXG4gICAgaWYgKCF0cmFja1VuaGFuZGxlZFJlamVjdGlvbnMpIHtcbiAgICAgICAgdHJhY2tVbmhhbmRsZWRSZWplY3Rpb25zID0gdHJ1ZTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIHRyYWNrUmVqZWN0aW9uKHByb21pc2UsIHJlYXNvbikge1xuICAgIGlmICghdHJhY2tVbmhhbmRsZWRSZWplY3Rpb25zKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKHR5cGVvZiBwcm9jZXNzID09PSBcIm9iamVjdFwiICYmIHR5cGVvZiBwcm9jZXNzLmVtaXQgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICBRLm5leHRUaWNrLnJ1bkFmdGVyKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGlmIChhcnJheV9pbmRleE9mKHVuaGFuZGxlZFJlamVjdGlvbnMsIHByb21pc2UpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgIHByb2Nlc3MuZW1pdChcInVuaGFuZGxlZFJlamVjdGlvblwiLCByZWFzb24sIHByb21pc2UpO1xuICAgICAgICAgICAgICAgIHJlcG9ydGVkVW5oYW5kbGVkUmVqZWN0aW9ucy5wdXNoKHByb21pc2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICB1bmhhbmRsZWRSZWplY3Rpb25zLnB1c2gocHJvbWlzZSk7XG4gICAgaWYgKHJlYXNvbiAmJiB0eXBlb2YgcmVhc29uLnN0YWNrICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgIHVuaGFuZGxlZFJlYXNvbnMucHVzaChyZWFzb24uc3RhY2spO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHVuaGFuZGxlZFJlYXNvbnMucHVzaChcIihubyBzdGFjaykgXCIgKyByZWFzb24pO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gdW50cmFja1JlamVjdGlvbihwcm9taXNlKSB7XG4gICAgaWYgKCF0cmFja1VuaGFuZGxlZFJlamVjdGlvbnMpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHZhciBhdCA9IGFycmF5X2luZGV4T2YodW5oYW5kbGVkUmVqZWN0aW9ucywgcHJvbWlzZSk7XG4gICAgaWYgKGF0ICE9PSAtMSkge1xuICAgICAgICBpZiAodHlwZW9mIHByb2Nlc3MgPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIHByb2Nlc3MuZW1pdCA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICBRLm5leHRUaWNrLnJ1bkFmdGVyKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICB2YXIgYXRSZXBvcnQgPSBhcnJheV9pbmRleE9mKHJlcG9ydGVkVW5oYW5kbGVkUmVqZWN0aW9ucywgcHJvbWlzZSk7XG4gICAgICAgICAgICAgICAgaWYgKGF0UmVwb3J0ICE9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgICBwcm9jZXNzLmVtaXQoXCJyZWplY3Rpb25IYW5kbGVkXCIsIHVuaGFuZGxlZFJlYXNvbnNbYXRdLCBwcm9taXNlKTtcbiAgICAgICAgICAgICAgICAgICAgcmVwb3J0ZWRVbmhhbmRsZWRSZWplY3Rpb25zLnNwbGljZShhdFJlcG9ydCwgMSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgICAgdW5oYW5kbGVkUmVqZWN0aW9ucy5zcGxpY2UoYXQsIDEpO1xuICAgICAgICB1bmhhbmRsZWRSZWFzb25zLnNwbGljZShhdCwgMSk7XG4gICAgfVxufVxuXG5RLnJlc2V0VW5oYW5kbGVkUmVqZWN0aW9ucyA9IHJlc2V0VW5oYW5kbGVkUmVqZWN0aW9ucztcblxuUS5nZXRVbmhhbmRsZWRSZWFzb25zID0gZnVuY3Rpb24gKCkge1xuICAgIC8vIE1ha2UgYSBjb3B5IHNvIHRoYXQgY29uc3VtZXJzIGNhbid0IGludGVyZmVyZSB3aXRoIG91ciBpbnRlcm5hbCBzdGF0ZS5cbiAgICByZXR1cm4gdW5oYW5kbGVkUmVhc29ucy5zbGljZSgpO1xufTtcblxuUS5zdG9wVW5oYW5kbGVkUmVqZWN0aW9uVHJhY2tpbmcgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmVzZXRVbmhhbmRsZWRSZWplY3Rpb25zKCk7XG4gICAgdHJhY2tVbmhhbmRsZWRSZWplY3Rpb25zID0gZmFsc2U7XG59O1xuXG5yZXNldFVuaGFuZGxlZFJlamVjdGlvbnMoKTtcblxuLy8vLyBFTkQgVU5IQU5ETEVEIFJFSkVDVElPTiBUUkFDS0lOR1xuXG4vKipcbiAqIENvbnN0cnVjdHMgYSByZWplY3RlZCBwcm9taXNlLlxuICogQHBhcmFtIHJlYXNvbiB2YWx1ZSBkZXNjcmliaW5nIHRoZSBmYWlsdXJlXG4gKi9cblEucmVqZWN0ID0gcmVqZWN0O1xuZnVuY3Rpb24gcmVqZWN0KHJlYXNvbikge1xuICAgIHZhciByZWplY3Rpb24gPSBQcm9taXNlKHtcbiAgICAgICAgXCJ3aGVuXCI6IGZ1bmN0aW9uIChyZWplY3RlZCkge1xuICAgICAgICAgICAgLy8gbm90ZSB0aGF0IHRoZSBlcnJvciBoYXMgYmVlbiBoYW5kbGVkXG4gICAgICAgICAgICBpZiAocmVqZWN0ZWQpIHtcbiAgICAgICAgICAgICAgICB1bnRyYWNrUmVqZWN0aW9uKHRoaXMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIHJlamVjdGVkID8gcmVqZWN0ZWQocmVhc29uKSA6IHRoaXM7XG4gICAgICAgIH1cbiAgICB9LCBmdW5jdGlvbiBmYWxsYmFjaygpIHtcbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfSwgZnVuY3Rpb24gaW5zcGVjdCgpIHtcbiAgICAgICAgcmV0dXJuIHsgc3RhdGU6IFwicmVqZWN0ZWRcIiwgcmVhc29uOiByZWFzb24gfTtcbiAgICB9KTtcblxuICAgIC8vIE5vdGUgdGhhdCB0aGUgcmVhc29uIGhhcyBub3QgYmVlbiBoYW5kbGVkLlxuICAgIHRyYWNrUmVqZWN0aW9uKHJlamVjdGlvbiwgcmVhc29uKTtcblxuICAgIHJldHVybiByZWplY3Rpb247XG59XG5cbi8qKlxuICogQ29uc3RydWN0cyBhIGZ1bGZpbGxlZCBwcm9taXNlIGZvciBhbiBpbW1lZGlhdGUgcmVmZXJlbmNlLlxuICogQHBhcmFtIHZhbHVlIGltbWVkaWF0ZSByZWZlcmVuY2VcbiAqL1xuUS5mdWxmaWxsID0gZnVsZmlsbDtcbmZ1bmN0aW9uIGZ1bGZpbGwodmFsdWUpIHtcbiAgICByZXR1cm4gUHJvbWlzZSh7XG4gICAgICAgIFwid2hlblwiOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICByZXR1cm4gdmFsdWU7XG4gICAgICAgIH0sXG4gICAgICAgIFwiZ2V0XCI6IGZ1bmN0aW9uIChuYW1lKSB7XG4gICAgICAgICAgICByZXR1cm4gdmFsdWVbbmFtZV07XG4gICAgICAgIH0sXG4gICAgICAgIFwic2V0XCI6IGZ1bmN0aW9uIChuYW1lLCByaHMpIHtcbiAgICAgICAgICAgIHZhbHVlW25hbWVdID0gcmhzO1xuICAgICAgICB9LFxuICAgICAgICBcImRlbGV0ZVwiOiBmdW5jdGlvbiAobmFtZSkge1xuICAgICAgICAgICAgZGVsZXRlIHZhbHVlW25hbWVdO1xuICAgICAgICB9LFxuICAgICAgICBcInBvc3RcIjogZnVuY3Rpb24gKG5hbWUsIGFyZ3MpIHtcbiAgICAgICAgICAgIC8vIE1hcmsgTWlsbGVyIHByb3Bvc2VzIHRoYXQgcG9zdCB3aXRoIG5vIG5hbWUgc2hvdWxkIGFwcGx5IGFcbiAgICAgICAgICAgIC8vIHByb21pc2VkIGZ1bmN0aW9uLlxuICAgICAgICAgICAgaWYgKG5hbWUgPT09IG51bGwgfHwgbmFtZSA9PT0gdm9pZCAwKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHZhbHVlLmFwcGx5KHZvaWQgMCwgYXJncyk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHJldHVybiB2YWx1ZVtuYW1lXS5hcHBseSh2YWx1ZSwgYXJncyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIFwiYXBwbHlcIjogZnVuY3Rpb24gKHRoaXNwLCBhcmdzKSB7XG4gICAgICAgICAgICByZXR1cm4gdmFsdWUuYXBwbHkodGhpc3AsIGFyZ3MpO1xuICAgICAgICB9LFxuICAgICAgICBcImtleXNcIjogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgcmV0dXJuIG9iamVjdF9rZXlzKHZhbHVlKTtcbiAgICAgICAgfVxuICAgIH0sIHZvaWQgMCwgZnVuY3Rpb24gaW5zcGVjdCgpIHtcbiAgICAgICAgcmV0dXJuIHsgc3RhdGU6IFwiZnVsZmlsbGVkXCIsIHZhbHVlOiB2YWx1ZSB9O1xuICAgIH0pO1xufVxuXG4vKipcbiAqIENvbnZlcnRzIHRoZW5hYmxlcyB0byBRIHByb21pc2VzLlxuICogQHBhcmFtIHByb21pc2UgdGhlbmFibGUgcHJvbWlzZVxuICogQHJldHVybnMgYSBRIHByb21pc2VcbiAqL1xuZnVuY3Rpb24gY29lcmNlKHByb21pc2UpIHtcbiAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgIFEubmV4dFRpY2soZnVuY3Rpb24gKCkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgcHJvbWlzZS50aGVuKGRlZmVycmVkLnJlc29sdmUsIGRlZmVycmVkLnJlamVjdCwgZGVmZXJyZWQubm90aWZ5KTtcbiAgICAgICAgfSBjYXRjaCAoZXhjZXB0aW9uKSB7XG4gICAgICAgICAgICBkZWZlcnJlZC5yZWplY3QoZXhjZXB0aW9uKTtcbiAgICAgICAgfVxuICAgIH0pO1xuICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xufVxuXG4vKipcbiAqIEFubm90YXRlcyBhbiBvYmplY3Qgc3VjaCB0aGF0IGl0IHdpbGwgbmV2ZXIgYmVcbiAqIHRyYW5zZmVycmVkIGF3YXkgZnJvbSB0aGlzIHByb2Nlc3Mgb3ZlciBhbnkgcHJvbWlzZVxuICogY29tbXVuaWNhdGlvbiBjaGFubmVsLlxuICogQHBhcmFtIG9iamVjdFxuICogQHJldHVybnMgcHJvbWlzZSBhIHdyYXBwaW5nIG9mIHRoYXQgb2JqZWN0IHRoYXRcbiAqIGFkZGl0aW9uYWxseSByZXNwb25kcyB0byB0aGUgXCJpc0RlZlwiIG1lc3NhZ2VcbiAqIHdpdGhvdXQgYSByZWplY3Rpb24uXG4gKi9cblEubWFzdGVyID0gbWFzdGVyO1xuZnVuY3Rpb24gbWFzdGVyKG9iamVjdCkge1xuICAgIHJldHVybiBQcm9taXNlKHtcbiAgICAgICAgXCJpc0RlZlwiOiBmdW5jdGlvbiAoKSB7fVxuICAgIH0sIGZ1bmN0aW9uIGZhbGxiYWNrKG9wLCBhcmdzKSB7XG4gICAgICAgIHJldHVybiBkaXNwYXRjaChvYmplY3QsIG9wLCBhcmdzKTtcbiAgICB9LCBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBRKG9iamVjdCkuaW5zcGVjdCgpO1xuICAgIH0pO1xufVxuXG4vKipcbiAqIFNwcmVhZHMgdGhlIHZhbHVlcyBvZiBhIHByb21pc2VkIGFycmF5IG9mIGFyZ3VtZW50cyBpbnRvIHRoZVxuICogZnVsZmlsbG1lbnQgY2FsbGJhY2suXG4gKiBAcGFyYW0gZnVsZmlsbGVkIGNhbGxiYWNrIHRoYXQgcmVjZWl2ZXMgdmFyaWFkaWMgYXJndW1lbnRzIGZyb20gdGhlXG4gKiBwcm9taXNlZCBhcnJheVxuICogQHBhcmFtIHJlamVjdGVkIGNhbGxiYWNrIHRoYXQgcmVjZWl2ZXMgdGhlIGV4Y2VwdGlvbiBpZiB0aGUgcHJvbWlzZVxuICogaXMgcmVqZWN0ZWQuXG4gKiBAcmV0dXJucyBhIHByb21pc2UgZm9yIHRoZSByZXR1cm4gdmFsdWUgb3IgdGhyb3duIGV4Y2VwdGlvbiBvZlxuICogZWl0aGVyIGNhbGxiYWNrLlxuICovXG5RLnNwcmVhZCA9IHNwcmVhZDtcbmZ1bmN0aW9uIHNwcmVhZCh2YWx1ZSwgZnVsZmlsbGVkLCByZWplY3RlZCkge1xuICAgIHJldHVybiBRKHZhbHVlKS5zcHJlYWQoZnVsZmlsbGVkLCByZWplY3RlZCk7XG59XG5cblByb21pc2UucHJvdG90eXBlLnNwcmVhZCA9IGZ1bmN0aW9uIChmdWxmaWxsZWQsIHJlamVjdGVkKSB7XG4gICAgcmV0dXJuIHRoaXMuYWxsKCkudGhlbihmdW5jdGlvbiAoYXJyYXkpIHtcbiAgICAgICAgcmV0dXJuIGZ1bGZpbGxlZC5hcHBseSh2b2lkIDAsIGFycmF5KTtcbiAgICB9LCByZWplY3RlZCk7XG59O1xuXG4vKipcbiAqIFRoZSBhc3luYyBmdW5jdGlvbiBpcyBhIGRlY29yYXRvciBmb3IgZ2VuZXJhdG9yIGZ1bmN0aW9ucywgdHVybmluZ1xuICogdGhlbSBpbnRvIGFzeW5jaHJvbm91cyBnZW5lcmF0b3JzLiAgQWx0aG91Z2ggZ2VuZXJhdG9ycyBhcmUgb25seSBwYXJ0XG4gKiBvZiB0aGUgbmV3ZXN0IEVDTUFTY3JpcHQgNiBkcmFmdHMsIHRoaXMgY29kZSBkb2VzIG5vdCBjYXVzZSBzeW50YXhcbiAqIGVycm9ycyBpbiBvbGRlciBlbmdpbmVzLiAgVGhpcyBjb2RlIHNob3VsZCBjb250aW51ZSB0byB3b3JrIGFuZCB3aWxsXG4gKiBpbiBmYWN0IGltcHJvdmUgb3ZlciB0aW1lIGFzIHRoZSBsYW5ndWFnZSBpbXByb3Zlcy5cbiAqXG4gKiBFUzYgZ2VuZXJhdG9ycyBhcmUgY3VycmVudGx5IHBhcnQgb2YgVjggdmVyc2lvbiAzLjE5IHdpdGggdGhlXG4gKiAtLWhhcm1vbnktZ2VuZXJhdG9ycyBydW50aW1lIGZsYWcgZW5hYmxlZC4gIFNwaWRlck1vbmtleSBoYXMgaGFkIHRoZW1cbiAqIGZvciBsb25nZXIsIGJ1dCB1bmRlciBhbiBvbGRlciBQeXRob24taW5zcGlyZWQgZm9ybS4gIFRoaXMgZnVuY3Rpb25cbiAqIHdvcmtzIG9uIGJvdGgga2luZHMgb2YgZ2VuZXJhdG9ycy5cbiAqXG4gKiBEZWNvcmF0ZXMgYSBnZW5lcmF0b3IgZnVuY3Rpb24gc3VjaCB0aGF0OlxuICogIC0gaXQgbWF5IHlpZWxkIHByb21pc2VzXG4gKiAgLSBleGVjdXRpb24gd2lsbCBjb250aW51ZSB3aGVuIHRoYXQgcHJvbWlzZSBpcyBmdWxmaWxsZWRcbiAqICAtIHRoZSB2YWx1ZSBvZiB0aGUgeWllbGQgZXhwcmVzc2lvbiB3aWxsIGJlIHRoZSBmdWxmaWxsZWQgdmFsdWVcbiAqICAtIGl0IHJldHVybnMgYSBwcm9taXNlIGZvciB0aGUgcmV0dXJuIHZhbHVlICh3aGVuIHRoZSBnZW5lcmF0b3JcbiAqICAgIHN0b3BzIGl0ZXJhdGluZylcbiAqICAtIHRoZSBkZWNvcmF0ZWQgZnVuY3Rpb24gcmV0dXJucyBhIHByb21pc2UgZm9yIHRoZSByZXR1cm4gdmFsdWVcbiAqICAgIG9mIHRoZSBnZW5lcmF0b3Igb3IgdGhlIGZpcnN0IHJlamVjdGVkIHByb21pc2UgYW1vbmcgdGhvc2VcbiAqICAgIHlpZWxkZWQuXG4gKiAgLSBpZiBhbiBlcnJvciBpcyB0aHJvd24gaW4gdGhlIGdlbmVyYXRvciwgaXQgcHJvcGFnYXRlcyB0aHJvdWdoXG4gKiAgICBldmVyeSBmb2xsb3dpbmcgeWllbGQgdW50aWwgaXQgaXMgY2F1Z2h0LCBvciB1bnRpbCBpdCBlc2NhcGVzXG4gKiAgICB0aGUgZ2VuZXJhdG9yIGZ1bmN0aW9uIGFsdG9nZXRoZXIsIGFuZCBpcyB0cmFuc2xhdGVkIGludG8gYVxuICogICAgcmVqZWN0aW9uIGZvciB0aGUgcHJvbWlzZSByZXR1cm5lZCBieSB0aGUgZGVjb3JhdGVkIGdlbmVyYXRvci5cbiAqL1xuUS5hc3luYyA9IGFzeW5jO1xuZnVuY3Rpb24gYXN5bmMobWFrZUdlbmVyYXRvcikge1xuICAgIHJldHVybiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIC8vIHdoZW4gdmVyYiBpcyBcInNlbmRcIiwgYXJnIGlzIGEgdmFsdWVcbiAgICAgICAgLy8gd2hlbiB2ZXJiIGlzIFwidGhyb3dcIiwgYXJnIGlzIGFuIGV4Y2VwdGlvblxuICAgICAgICBmdW5jdGlvbiBjb250aW51ZXIodmVyYiwgYXJnKSB7XG4gICAgICAgICAgICB2YXIgcmVzdWx0O1xuXG4gICAgICAgICAgICAvLyBVbnRpbCBWOCAzLjE5IC8gQ2hyb21pdW0gMjkgaXMgcmVsZWFzZWQsIFNwaWRlck1vbmtleSBpcyB0aGUgb25seVxuICAgICAgICAgICAgLy8gZW5naW5lIHRoYXQgaGFzIGEgZGVwbG95ZWQgYmFzZSBvZiBicm93c2VycyB0aGF0IHN1cHBvcnQgZ2VuZXJhdG9ycy5cbiAgICAgICAgICAgIC8vIEhvd2V2ZXIsIFNNJ3MgZ2VuZXJhdG9ycyB1c2UgdGhlIFB5dGhvbi1pbnNwaXJlZCBzZW1hbnRpY3Mgb2ZcbiAgICAgICAgICAgIC8vIG91dGRhdGVkIEVTNiBkcmFmdHMuICBXZSB3b3VsZCBsaWtlIHRvIHN1cHBvcnQgRVM2LCBidXQgd2UnZCBhbHNvXG4gICAgICAgICAgICAvLyBsaWtlIHRvIG1ha2UgaXQgcG9zc2libGUgdG8gdXNlIGdlbmVyYXRvcnMgaW4gZGVwbG95ZWQgYnJvd3NlcnMsIHNvXG4gICAgICAgICAgICAvLyB3ZSBhbHNvIHN1cHBvcnQgUHl0aG9uLXN0eWxlIGdlbmVyYXRvcnMuICBBdCBzb21lIHBvaW50IHdlIGNhbiByZW1vdmVcbiAgICAgICAgICAgIC8vIHRoaXMgYmxvY2suXG5cbiAgICAgICAgICAgIGlmICh0eXBlb2YgU3RvcEl0ZXJhdGlvbiA9PT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICAgICAgICAgIC8vIEVTNiBHZW5lcmF0b3JzXG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gZ2VuZXJhdG9yW3ZlcmJdKGFyZyk7XG4gICAgICAgICAgICAgICAgfSBjYXRjaCAoZXhjZXB0aW9uKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiByZWplY3QoZXhjZXB0aW9uKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKHJlc3VsdC5kb25lKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBRKHJlc3VsdC52YWx1ZSk7XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHdoZW4ocmVzdWx0LnZhbHVlLCBjYWxsYmFjaywgZXJyYmFjayk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAvLyBTcGlkZXJNb25rZXkgR2VuZXJhdG9yc1xuICAgICAgICAgICAgICAgIC8vIEZJWE1FOiBSZW1vdmUgdGhpcyBjYXNlIHdoZW4gU00gZG9lcyBFUzYgZ2VuZXJhdG9ycy5cbiAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICByZXN1bHQgPSBnZW5lcmF0b3JbdmVyYl0oYXJnKTtcbiAgICAgICAgICAgICAgICB9IGNhdGNoIChleGNlcHRpb24pIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKGlzU3RvcEl0ZXJhdGlvbihleGNlcHRpb24pKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gUShleGNlcHRpb24udmFsdWUpO1xuICAgICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHJlamVjdChleGNlcHRpb24pO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiB3aGVuKHJlc3VsdCwgY2FsbGJhY2ssIGVycmJhY2spO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHZhciBnZW5lcmF0b3IgPSBtYWtlR2VuZXJhdG9yLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgICAgIHZhciBjYWxsYmFjayA9IGNvbnRpbnVlci5iaW5kKGNvbnRpbnVlciwgXCJuZXh0XCIpO1xuICAgICAgICB2YXIgZXJyYmFjayA9IGNvbnRpbnVlci5iaW5kKGNvbnRpbnVlciwgXCJ0aHJvd1wiKTtcbiAgICAgICAgcmV0dXJuIGNhbGxiYWNrKCk7XG4gICAgfTtcbn1cblxuLyoqXG4gKiBUaGUgc3Bhd24gZnVuY3Rpb24gaXMgYSBzbWFsbCB3cmFwcGVyIGFyb3VuZCBhc3luYyB0aGF0IGltbWVkaWF0ZWx5XG4gKiBjYWxscyB0aGUgZ2VuZXJhdG9yIGFuZCBhbHNvIGVuZHMgdGhlIHByb21pc2UgY2hhaW4sIHNvIHRoYXQgYW55XG4gKiB1bmhhbmRsZWQgZXJyb3JzIGFyZSB0aHJvd24gaW5zdGVhZCBvZiBmb3J3YXJkZWQgdG8gdGhlIGVycm9yXG4gKiBoYW5kbGVyLiBUaGlzIGlzIHVzZWZ1bCBiZWNhdXNlIGl0J3MgZXh0cmVtZWx5IGNvbW1vbiB0byBydW5cbiAqIGdlbmVyYXRvcnMgYXQgdGhlIHRvcC1sZXZlbCB0byB3b3JrIHdpdGggbGlicmFyaWVzLlxuICovXG5RLnNwYXduID0gc3Bhd247XG5mdW5jdGlvbiBzcGF3bihtYWtlR2VuZXJhdG9yKSB7XG4gICAgUS5kb25lKFEuYXN5bmMobWFrZUdlbmVyYXRvcikoKSk7XG59XG5cbi8vIEZJWE1FOiBSZW1vdmUgdGhpcyBpbnRlcmZhY2Ugb25jZSBFUzYgZ2VuZXJhdG9ycyBhcmUgaW4gU3BpZGVyTW9ua2V5LlxuLyoqXG4gKiBUaHJvd3MgYSBSZXR1cm5WYWx1ZSBleGNlcHRpb24gdG8gc3RvcCBhbiBhc3luY2hyb25vdXMgZ2VuZXJhdG9yLlxuICpcbiAqIFRoaXMgaW50ZXJmYWNlIGlzIGEgc3RvcC1nYXAgbWVhc3VyZSB0byBzdXBwb3J0IGdlbmVyYXRvciByZXR1cm5cbiAqIHZhbHVlcyBpbiBvbGRlciBGaXJlZm94L1NwaWRlck1vbmtleS4gIEluIGJyb3dzZXJzIHRoYXQgc3VwcG9ydCBFUzZcbiAqIGdlbmVyYXRvcnMgbGlrZSBDaHJvbWl1bSAyOSwganVzdCB1c2UgXCJyZXR1cm5cIiBpbiB5b3VyIGdlbmVyYXRvclxuICogZnVuY3Rpb25zLlxuICpcbiAqIEBwYXJhbSB2YWx1ZSB0aGUgcmV0dXJuIHZhbHVlIGZvciB0aGUgc3Vycm91bmRpbmcgZ2VuZXJhdG9yXG4gKiBAdGhyb3dzIFJldHVyblZhbHVlIGV4Y2VwdGlvbiB3aXRoIHRoZSB2YWx1ZS5cbiAqIEBleGFtcGxlXG4gKiAvLyBFUzYgc3R5bGVcbiAqIFEuYXN5bmMoZnVuY3Rpb24qICgpIHtcbiAqICAgICAgdmFyIGZvbyA9IHlpZWxkIGdldEZvb1Byb21pc2UoKTtcbiAqICAgICAgdmFyIGJhciA9IHlpZWxkIGdldEJhclByb21pc2UoKTtcbiAqICAgICAgcmV0dXJuIGZvbyArIGJhcjtcbiAqIH0pXG4gKiAvLyBPbGRlciBTcGlkZXJNb25rZXkgc3R5bGVcbiAqIFEuYXN5bmMoZnVuY3Rpb24gKCkge1xuICogICAgICB2YXIgZm9vID0geWllbGQgZ2V0Rm9vUHJvbWlzZSgpO1xuICogICAgICB2YXIgYmFyID0geWllbGQgZ2V0QmFyUHJvbWlzZSgpO1xuICogICAgICBRLnJldHVybihmb28gKyBiYXIpO1xuICogfSlcbiAqL1xuUVtcInJldHVyblwiXSA9IF9yZXR1cm47XG5mdW5jdGlvbiBfcmV0dXJuKHZhbHVlKSB7XG4gICAgdGhyb3cgbmV3IFFSZXR1cm5WYWx1ZSh2YWx1ZSk7XG59XG5cbi8qKlxuICogVGhlIHByb21pc2VkIGZ1bmN0aW9uIGRlY29yYXRvciBlbnN1cmVzIHRoYXQgYW55IHByb21pc2UgYXJndW1lbnRzXG4gKiBhcmUgc2V0dGxlZCBhbmQgcGFzc2VkIGFzIHZhbHVlcyAoYHRoaXNgIGlzIGFsc28gc2V0dGxlZCBhbmQgcGFzc2VkXG4gKiBhcyBhIHZhbHVlKS4gIEl0IHdpbGwgYWxzbyBlbnN1cmUgdGhhdCB0aGUgcmVzdWx0IG9mIGEgZnVuY3Rpb24gaXNcbiAqIGFsd2F5cyBhIHByb21pc2UuXG4gKlxuICogQGV4YW1wbGVcbiAqIHZhciBhZGQgPSBRLnByb21pc2VkKGZ1bmN0aW9uIChhLCBiKSB7XG4gKiAgICAgcmV0dXJuIGEgKyBiO1xuICogfSk7XG4gKiBhZGQoUShhKSwgUShCKSk7XG4gKlxuICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2sgVGhlIGZ1bmN0aW9uIHRvIGRlY29yYXRlXG4gKiBAcmV0dXJucyB7ZnVuY3Rpb259IGEgZnVuY3Rpb24gdGhhdCBoYXMgYmVlbiBkZWNvcmF0ZWQuXG4gKi9cblEucHJvbWlzZWQgPSBwcm9taXNlZDtcbmZ1bmN0aW9uIHByb21pc2VkKGNhbGxiYWNrKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHNwcmVhZChbdGhpcywgYWxsKGFyZ3VtZW50cyldLCBmdW5jdGlvbiAoc2VsZiwgYXJncykge1xuICAgICAgICAgICAgcmV0dXJuIGNhbGxiYWNrLmFwcGx5KHNlbGYsIGFyZ3MpO1xuICAgICAgICB9KTtcbiAgICB9O1xufVxuXG4vKipcbiAqIHNlbmRzIGEgbWVzc2FnZSB0byBhIHZhbHVlIGluIGEgZnV0dXJlIHR1cm5cbiAqIEBwYXJhbSBvYmplY3QqIHRoZSByZWNpcGllbnRcbiAqIEBwYXJhbSBvcCB0aGUgbmFtZSBvZiB0aGUgbWVzc2FnZSBvcGVyYXRpb24sIGUuZy4sIFwid2hlblwiLFxuICogQHBhcmFtIGFyZ3MgZnVydGhlciBhcmd1bWVudHMgdG8gYmUgZm9yd2FyZGVkIHRvIHRoZSBvcGVyYXRpb25cbiAqIEByZXR1cm5zIHJlc3VsdCB7UHJvbWlzZX0gYSBwcm9taXNlIGZvciB0aGUgcmVzdWx0IG9mIHRoZSBvcGVyYXRpb25cbiAqL1xuUS5kaXNwYXRjaCA9IGRpc3BhdGNoO1xuZnVuY3Rpb24gZGlzcGF0Y2gob2JqZWN0LCBvcCwgYXJncykge1xuICAgIHJldHVybiBRKG9iamVjdCkuZGlzcGF0Y2gob3AsIGFyZ3MpO1xufVxuXG5Qcm9taXNlLnByb3RvdHlwZS5kaXNwYXRjaCA9IGZ1bmN0aW9uIChvcCwgYXJncykge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgIFEubmV4dFRpY2soZnVuY3Rpb24gKCkge1xuICAgICAgICBzZWxmLnByb21pc2VEaXNwYXRjaChkZWZlcnJlZC5yZXNvbHZlLCBvcCwgYXJncyk7XG4gICAgfSk7XG4gICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG59O1xuXG4vKipcbiAqIEdldHMgdGhlIHZhbHVlIG9mIGEgcHJvcGVydHkgaW4gYSBmdXR1cmUgdHVybi5cbiAqIEBwYXJhbSBvYmplY3QgICAgcHJvbWlzZSBvciBpbW1lZGlhdGUgcmVmZXJlbmNlIGZvciB0YXJnZXQgb2JqZWN0XG4gKiBAcGFyYW0gbmFtZSAgICAgIG5hbWUgb2YgcHJvcGVydHkgdG8gZ2V0XG4gKiBAcmV0dXJuIHByb21pc2UgZm9yIHRoZSBwcm9wZXJ0eSB2YWx1ZVxuICovXG5RLmdldCA9IGZ1bmN0aW9uIChvYmplY3QsIGtleSkge1xuICAgIHJldHVybiBRKG9iamVjdCkuZGlzcGF0Y2goXCJnZXRcIiwgW2tleV0pO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUuZ2V0ID0gZnVuY3Rpb24gKGtleSkge1xuICAgIHJldHVybiB0aGlzLmRpc3BhdGNoKFwiZ2V0XCIsIFtrZXldKTtcbn07XG5cbi8qKlxuICogU2V0cyB0aGUgdmFsdWUgb2YgYSBwcm9wZXJ0eSBpbiBhIGZ1dHVyZSB0dXJuLlxuICogQHBhcmFtIG9iamVjdCAgICBwcm9taXNlIG9yIGltbWVkaWF0ZSByZWZlcmVuY2UgZm9yIG9iamVjdCBvYmplY3RcbiAqIEBwYXJhbSBuYW1lICAgICAgbmFtZSBvZiBwcm9wZXJ0eSB0byBzZXRcbiAqIEBwYXJhbSB2YWx1ZSAgICAgbmV3IHZhbHVlIG9mIHByb3BlcnR5XG4gKiBAcmV0dXJuIHByb21pc2UgZm9yIHRoZSByZXR1cm4gdmFsdWVcbiAqL1xuUS5zZXQgPSBmdW5jdGlvbiAob2JqZWN0LCBrZXksIHZhbHVlKSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS5kaXNwYXRjaChcInNldFwiLCBba2V5LCB2YWx1ZV0pO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUuc2V0ID0gZnVuY3Rpb24gKGtleSwgdmFsdWUpIHtcbiAgICByZXR1cm4gdGhpcy5kaXNwYXRjaChcInNldFwiLCBba2V5LCB2YWx1ZV0pO1xufTtcblxuLyoqXG4gKiBEZWxldGVzIGEgcHJvcGVydHkgaW4gYSBmdXR1cmUgdHVybi5cbiAqIEBwYXJhbSBvYmplY3QgICAgcHJvbWlzZSBvciBpbW1lZGlhdGUgcmVmZXJlbmNlIGZvciB0YXJnZXQgb2JqZWN0XG4gKiBAcGFyYW0gbmFtZSAgICAgIG5hbWUgb2YgcHJvcGVydHkgdG8gZGVsZXRlXG4gKiBAcmV0dXJuIHByb21pc2UgZm9yIHRoZSByZXR1cm4gdmFsdWVcbiAqL1xuUS5kZWwgPSAvLyBYWFggbGVnYWN5XG5RW1wiZGVsZXRlXCJdID0gZnVuY3Rpb24gKG9iamVjdCwga2V5KSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS5kaXNwYXRjaChcImRlbGV0ZVwiLCBba2V5XSk7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS5kZWwgPSAvLyBYWFggbGVnYWN5XG5Qcm9taXNlLnByb3RvdHlwZVtcImRlbGV0ZVwiXSA9IGZ1bmN0aW9uIChrZXkpIHtcbiAgICByZXR1cm4gdGhpcy5kaXNwYXRjaChcImRlbGV0ZVwiLCBba2V5XSk7XG59O1xuXG4vKipcbiAqIEludm9rZXMgYSBtZXRob2QgaW4gYSBmdXR1cmUgdHVybi5cbiAqIEBwYXJhbSBvYmplY3QgICAgcHJvbWlzZSBvciBpbW1lZGlhdGUgcmVmZXJlbmNlIGZvciB0YXJnZXQgb2JqZWN0XG4gKiBAcGFyYW0gbmFtZSAgICAgIG5hbWUgb2YgbWV0aG9kIHRvIGludm9rZVxuICogQHBhcmFtIHZhbHVlICAgICBhIHZhbHVlIHRvIHBvc3QsIHR5cGljYWxseSBhbiBhcnJheSBvZlxuICogICAgICAgICAgICAgICAgICBpbnZvY2F0aW9uIGFyZ3VtZW50cyBmb3IgcHJvbWlzZXMgdGhhdFxuICogICAgICAgICAgICAgICAgICBhcmUgdWx0aW1hdGVseSBiYWNrZWQgd2l0aCBgcmVzb2x2ZWAgdmFsdWVzLFxuICogICAgICAgICAgICAgICAgICBhcyBvcHBvc2VkIHRvIHRob3NlIGJhY2tlZCB3aXRoIFVSTHNcbiAqICAgICAgICAgICAgICAgICAgd2hlcmVpbiB0aGUgcG9zdGVkIHZhbHVlIGNhbiBiZSBhbnlcbiAqICAgICAgICAgICAgICAgICAgSlNPTiBzZXJpYWxpemFibGUgb2JqZWN0LlxuICogQHJldHVybiBwcm9taXNlIGZvciB0aGUgcmV0dXJuIHZhbHVlXG4gKi9cbi8vIGJvdW5kIGxvY2FsbHkgYmVjYXVzZSBpdCBpcyB1c2VkIGJ5IG90aGVyIG1ldGhvZHNcblEubWFwcGx5ID0gLy8gWFhYIEFzIHByb3Bvc2VkIGJ5IFwiUmVkc2FuZHJvXCJcblEucG9zdCA9IGZ1bmN0aW9uIChvYmplY3QsIG5hbWUsIGFyZ3MpIHtcbiAgICByZXR1cm4gUShvYmplY3QpLmRpc3BhdGNoKFwicG9zdFwiLCBbbmFtZSwgYXJnc10pO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUubWFwcGx5ID0gLy8gWFhYIEFzIHByb3Bvc2VkIGJ5IFwiUmVkc2FuZHJvXCJcblByb21pc2UucHJvdG90eXBlLnBvc3QgPSBmdW5jdGlvbiAobmFtZSwgYXJncykge1xuICAgIHJldHVybiB0aGlzLmRpc3BhdGNoKFwicG9zdFwiLCBbbmFtZSwgYXJnc10pO1xufTtcblxuLyoqXG4gKiBJbnZva2VzIGEgbWV0aG9kIGluIGEgZnV0dXJlIHR1cm4uXG4gKiBAcGFyYW0gb2JqZWN0ICAgIHByb21pc2Ugb3IgaW1tZWRpYXRlIHJlZmVyZW5jZSBmb3IgdGFyZ2V0IG9iamVjdFxuICogQHBhcmFtIG5hbWUgICAgICBuYW1lIG9mIG1ldGhvZCB0byBpbnZva2VcbiAqIEBwYXJhbSAuLi5hcmdzICAgYXJyYXkgb2YgaW52b2NhdGlvbiBhcmd1bWVudHNcbiAqIEByZXR1cm4gcHJvbWlzZSBmb3IgdGhlIHJldHVybiB2YWx1ZVxuICovXG5RLnNlbmQgPSAvLyBYWFggTWFyayBNaWxsZXIncyBwcm9wb3NlZCBwYXJsYW5jZVxuUS5tY2FsbCA9IC8vIFhYWCBBcyBwcm9wb3NlZCBieSBcIlJlZHNhbmRyb1wiXG5RLmludm9rZSA9IGZ1bmN0aW9uIChvYmplY3QsIG5hbWUgLyouLi5hcmdzKi8pIHtcbiAgICByZXR1cm4gUShvYmplY3QpLmRpc3BhdGNoKFwicG9zdFwiLCBbbmFtZSwgYXJyYXlfc2xpY2UoYXJndW1lbnRzLCAyKV0pO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUuc2VuZCA9IC8vIFhYWCBNYXJrIE1pbGxlcidzIHByb3Bvc2VkIHBhcmxhbmNlXG5Qcm9taXNlLnByb3RvdHlwZS5tY2FsbCA9IC8vIFhYWCBBcyBwcm9wb3NlZCBieSBcIlJlZHNhbmRyb1wiXG5Qcm9taXNlLnByb3RvdHlwZS5pbnZva2UgPSBmdW5jdGlvbiAobmFtZSAvKi4uLmFyZ3MqLykge1xuICAgIHJldHVybiB0aGlzLmRpc3BhdGNoKFwicG9zdFwiLCBbbmFtZSwgYXJyYXlfc2xpY2UoYXJndW1lbnRzLCAxKV0pO1xufTtcblxuLyoqXG4gKiBBcHBsaWVzIHRoZSBwcm9taXNlZCBmdW5jdGlvbiBpbiBhIGZ1dHVyZSB0dXJuLlxuICogQHBhcmFtIG9iamVjdCAgICBwcm9taXNlIG9yIGltbWVkaWF0ZSByZWZlcmVuY2UgZm9yIHRhcmdldCBmdW5jdGlvblxuICogQHBhcmFtIGFyZ3MgICAgICBhcnJheSBvZiBhcHBsaWNhdGlvbiBhcmd1bWVudHNcbiAqL1xuUS5mYXBwbHkgPSBmdW5jdGlvbiAob2JqZWN0LCBhcmdzKSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS5kaXNwYXRjaChcImFwcGx5XCIsIFt2b2lkIDAsIGFyZ3NdKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLmZhcHBseSA9IGZ1bmN0aW9uIChhcmdzKSB7XG4gICAgcmV0dXJuIHRoaXMuZGlzcGF0Y2goXCJhcHBseVwiLCBbdm9pZCAwLCBhcmdzXSk7XG59O1xuXG4vKipcbiAqIENhbGxzIHRoZSBwcm9taXNlZCBmdW5jdGlvbiBpbiBhIGZ1dHVyZSB0dXJuLlxuICogQHBhcmFtIG9iamVjdCAgICBwcm9taXNlIG9yIGltbWVkaWF0ZSByZWZlcmVuY2UgZm9yIHRhcmdldCBmdW5jdGlvblxuICogQHBhcmFtIC4uLmFyZ3MgICBhcnJheSBvZiBhcHBsaWNhdGlvbiBhcmd1bWVudHNcbiAqL1xuUVtcInRyeVwiXSA9XG5RLmZjYWxsID0gZnVuY3Rpb24gKG9iamVjdCAvKiAuLi5hcmdzKi8pIHtcbiAgICByZXR1cm4gUShvYmplY3QpLmRpc3BhdGNoKFwiYXBwbHlcIiwgW3ZvaWQgMCwgYXJyYXlfc2xpY2UoYXJndW1lbnRzLCAxKV0pO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUuZmNhbGwgPSBmdW5jdGlvbiAoLyouLi5hcmdzKi8pIHtcbiAgICByZXR1cm4gdGhpcy5kaXNwYXRjaChcImFwcGx5XCIsIFt2b2lkIDAsIGFycmF5X3NsaWNlKGFyZ3VtZW50cyldKTtcbn07XG5cbi8qKlxuICogQmluZHMgdGhlIHByb21pc2VkIGZ1bmN0aW9uLCB0cmFuc2Zvcm1pbmcgcmV0dXJuIHZhbHVlcyBpbnRvIGEgZnVsZmlsbGVkXG4gKiBwcm9taXNlIGFuZCB0aHJvd24gZXJyb3JzIGludG8gYSByZWplY3RlZCBvbmUuXG4gKiBAcGFyYW0gb2JqZWN0ICAgIHByb21pc2Ugb3IgaW1tZWRpYXRlIHJlZmVyZW5jZSBmb3IgdGFyZ2V0IGZ1bmN0aW9uXG4gKiBAcGFyYW0gLi4uYXJncyAgIGFycmF5IG9mIGFwcGxpY2F0aW9uIGFyZ3VtZW50c1xuICovXG5RLmZiaW5kID0gZnVuY3Rpb24gKG9iamVjdCAvKi4uLmFyZ3MqLykge1xuICAgIHZhciBwcm9taXNlID0gUShvYmplY3QpO1xuICAgIHZhciBhcmdzID0gYXJyYXlfc2xpY2UoYXJndW1lbnRzLCAxKTtcbiAgICByZXR1cm4gZnVuY3Rpb24gZmJvdW5kKCkge1xuICAgICAgICByZXR1cm4gcHJvbWlzZS5kaXNwYXRjaChcImFwcGx5XCIsIFtcbiAgICAgICAgICAgIHRoaXMsXG4gICAgICAgICAgICBhcmdzLmNvbmNhdChhcnJheV9zbGljZShhcmd1bWVudHMpKVxuICAgICAgICBdKTtcbiAgICB9O1xufTtcblByb21pc2UucHJvdG90eXBlLmZiaW5kID0gZnVuY3Rpb24gKC8qLi4uYXJncyovKSB7XG4gICAgdmFyIHByb21pc2UgPSB0aGlzO1xuICAgIHZhciBhcmdzID0gYXJyYXlfc2xpY2UoYXJndW1lbnRzKTtcbiAgICByZXR1cm4gZnVuY3Rpb24gZmJvdW5kKCkge1xuICAgICAgICByZXR1cm4gcHJvbWlzZS5kaXNwYXRjaChcImFwcGx5XCIsIFtcbiAgICAgICAgICAgIHRoaXMsXG4gICAgICAgICAgICBhcmdzLmNvbmNhdChhcnJheV9zbGljZShhcmd1bWVudHMpKVxuICAgICAgICBdKTtcbiAgICB9O1xufTtcblxuLyoqXG4gKiBSZXF1ZXN0cyB0aGUgbmFtZXMgb2YgdGhlIG93bmVkIHByb3BlcnRpZXMgb2YgYSBwcm9taXNlZFxuICogb2JqZWN0IGluIGEgZnV0dXJlIHR1cm4uXG4gKiBAcGFyYW0gb2JqZWN0ICAgIHByb21pc2Ugb3IgaW1tZWRpYXRlIHJlZmVyZW5jZSBmb3IgdGFyZ2V0IG9iamVjdFxuICogQHJldHVybiBwcm9taXNlIGZvciB0aGUga2V5cyBvZiB0aGUgZXZlbnR1YWxseSBzZXR0bGVkIG9iamVjdFxuICovXG5RLmtleXMgPSBmdW5jdGlvbiAob2JqZWN0KSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS5kaXNwYXRjaChcImtleXNcIiwgW10pO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUua2V5cyA9IGZ1bmN0aW9uICgpIHtcbiAgICByZXR1cm4gdGhpcy5kaXNwYXRjaChcImtleXNcIiwgW10pO1xufTtcblxuLyoqXG4gKiBUdXJucyBhbiBhcnJheSBvZiBwcm9taXNlcyBpbnRvIGEgcHJvbWlzZSBmb3IgYW4gYXJyYXkuICBJZiBhbnkgb2ZcbiAqIHRoZSBwcm9taXNlcyBnZXRzIHJlamVjdGVkLCB0aGUgd2hvbGUgYXJyYXkgaXMgcmVqZWN0ZWQgaW1tZWRpYXRlbHkuXG4gKiBAcGFyYW0ge0FycmF5Kn0gYW4gYXJyYXkgKG9yIHByb21pc2UgZm9yIGFuIGFycmF5KSBvZiB2YWx1ZXMgKG9yXG4gKiBwcm9taXNlcyBmb3IgdmFsdWVzKVxuICogQHJldHVybnMgYSBwcm9taXNlIGZvciBhbiBhcnJheSBvZiB0aGUgY29ycmVzcG9uZGluZyB2YWx1ZXNcbiAqL1xuLy8gQnkgTWFyayBNaWxsZXJcbi8vIGh0dHA6Ly93aWtpLmVjbWFzY3JpcHQub3JnL2Rva3UucGhwP2lkPXN0cmF3bWFuOmNvbmN1cnJlbmN5JnJldj0xMzA4Nzc2NTIxI2FsbGZ1bGZpbGxlZFxuUS5hbGwgPSBhbGw7XG5mdW5jdGlvbiBhbGwocHJvbWlzZXMpIHtcbiAgICByZXR1cm4gd2hlbihwcm9taXNlcywgZnVuY3Rpb24gKHByb21pc2VzKSB7XG4gICAgICAgIHZhciBwZW5kaW5nQ291bnQgPSAwO1xuICAgICAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgICAgICBhcnJheV9yZWR1Y2UocHJvbWlzZXMsIGZ1bmN0aW9uICh1bmRlZmluZWQsIHByb21pc2UsIGluZGV4KSB7XG4gICAgICAgICAgICB2YXIgc25hcHNob3Q7XG4gICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgICAgaXNQcm9taXNlKHByb21pc2UpICYmXG4gICAgICAgICAgICAgICAgKHNuYXBzaG90ID0gcHJvbWlzZS5pbnNwZWN0KCkpLnN0YXRlID09PSBcImZ1bGZpbGxlZFwiXG4gICAgICAgICAgICApIHtcbiAgICAgICAgICAgICAgICBwcm9taXNlc1tpbmRleF0gPSBzbmFwc2hvdC52YWx1ZTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgKytwZW5kaW5nQ291bnQ7XG4gICAgICAgICAgICAgICAgd2hlbihcbiAgICAgICAgICAgICAgICAgICAgcHJvbWlzZSxcbiAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBwcm9taXNlc1tpbmRleF0gPSB2YWx1ZTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmICgtLXBlbmRpbmdDb3VudCA9PT0gMCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZmVycmVkLnJlc29sdmUocHJvbWlzZXMpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICBkZWZlcnJlZC5yZWplY3QsXG4gICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChwcm9ncmVzcykge1xuICAgICAgICAgICAgICAgICAgICAgICAgZGVmZXJyZWQubm90aWZ5KHsgaW5kZXg6IGluZGV4LCB2YWx1ZTogcHJvZ3Jlc3MgfSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LCB2b2lkIDApO1xuICAgICAgICBpZiAocGVuZGluZ0NvdW50ID09PSAwKSB7XG4gICAgICAgICAgICBkZWZlcnJlZC5yZXNvbHZlKHByb21pc2VzKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZGVmZXJyZWQucHJvbWlzZTtcbiAgICB9KTtcbn1cblxuUHJvbWlzZS5wcm90b3R5cGUuYWxsID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiBhbGwodGhpcyk7XG59O1xuXG4vKipcbiAqIFJldHVybnMgdGhlIGZpcnN0IHJlc29sdmVkIHByb21pc2Ugb2YgYW4gYXJyYXkuIFByaW9yIHJlamVjdGVkIHByb21pc2VzIGFyZVxuICogaWdub3JlZC4gIFJlamVjdHMgb25seSBpZiBhbGwgcHJvbWlzZXMgYXJlIHJlamVjdGVkLlxuICogQHBhcmFtIHtBcnJheSp9IGFuIGFycmF5IGNvbnRhaW5pbmcgdmFsdWVzIG9yIHByb21pc2VzIGZvciB2YWx1ZXNcbiAqIEByZXR1cm5zIGEgcHJvbWlzZSBmdWxmaWxsZWQgd2l0aCB0aGUgdmFsdWUgb2YgdGhlIGZpcnN0IHJlc29sdmVkIHByb21pc2UsXG4gKiBvciBhIHJlamVjdGVkIHByb21pc2UgaWYgYWxsIHByb21pc2VzIGFyZSByZWplY3RlZC5cbiAqL1xuUS5hbnkgPSBhbnk7XG5cbmZ1bmN0aW9uIGFueShwcm9taXNlcykge1xuICAgIGlmIChwcm9taXNlcy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgcmV0dXJuIFEucmVzb2x2ZSgpO1xuICAgIH1cblxuICAgIHZhciBkZWZlcnJlZCA9IFEuZGVmZXIoKTtcbiAgICB2YXIgcGVuZGluZ0NvdW50ID0gMDtcbiAgICBhcnJheV9yZWR1Y2UocHJvbWlzZXMsIGZ1bmN0aW9uIChwcmV2LCBjdXJyZW50LCBpbmRleCkge1xuICAgICAgICB2YXIgcHJvbWlzZSA9IHByb21pc2VzW2luZGV4XTtcblxuICAgICAgICBwZW5kaW5nQ291bnQrKztcblxuICAgICAgICB3aGVuKHByb21pc2UsIG9uRnVsZmlsbGVkLCBvblJlamVjdGVkLCBvblByb2dyZXNzKTtcbiAgICAgICAgZnVuY3Rpb24gb25GdWxmaWxsZWQocmVzdWx0KSB7XG4gICAgICAgICAgICBkZWZlcnJlZC5yZXNvbHZlKHJlc3VsdCk7XG4gICAgICAgIH1cbiAgICAgICAgZnVuY3Rpb24gb25SZWplY3RlZCgpIHtcbiAgICAgICAgICAgIHBlbmRpbmdDb3VudC0tO1xuICAgICAgICAgICAgaWYgKHBlbmRpbmdDb3VudCA9PT0gMCkge1xuICAgICAgICAgICAgICAgIGRlZmVycmVkLnJlamVjdChuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgICAgIFwiQ2FuJ3QgZ2V0IGZ1bGZpbGxtZW50IHZhbHVlIGZyb20gYW55IHByb21pc2UsIGFsbCBcIiArXG4gICAgICAgICAgICAgICAgICAgIFwicHJvbWlzZXMgd2VyZSByZWplY3RlZC5cIlxuICAgICAgICAgICAgICAgICkpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGZ1bmN0aW9uIG9uUHJvZ3Jlc3MocHJvZ3Jlc3MpIHtcbiAgICAgICAgICAgIGRlZmVycmVkLm5vdGlmeSh7XG4gICAgICAgICAgICAgICAgaW5kZXg6IGluZGV4LFxuICAgICAgICAgICAgICAgIHZhbHVlOiBwcm9ncmVzc1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9LCB1bmRlZmluZWQpO1xuXG4gICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG59XG5cblByb21pc2UucHJvdG90eXBlLmFueSA9IGZ1bmN0aW9uICgpIHtcbiAgICByZXR1cm4gYW55KHRoaXMpO1xufTtcblxuLyoqXG4gKiBXYWl0cyBmb3IgYWxsIHByb21pc2VzIHRvIGJlIHNldHRsZWQsIGVpdGhlciBmdWxmaWxsZWQgb3JcbiAqIHJlamVjdGVkLiAgVGhpcyBpcyBkaXN0aW5jdCBmcm9tIGBhbGxgIHNpbmNlIHRoYXQgd291bGQgc3RvcFxuICogd2FpdGluZyBhdCB0aGUgZmlyc3QgcmVqZWN0aW9uLiAgVGhlIHByb21pc2UgcmV0dXJuZWQgYnlcbiAqIGBhbGxSZXNvbHZlZGAgd2lsbCBuZXZlciBiZSByZWplY3RlZC5cbiAqIEBwYXJhbSBwcm9taXNlcyBhIHByb21pc2UgZm9yIGFuIGFycmF5IChvciBhbiBhcnJheSkgb2YgcHJvbWlzZXNcbiAqIChvciB2YWx1ZXMpXG4gKiBAcmV0dXJuIGEgcHJvbWlzZSBmb3IgYW4gYXJyYXkgb2YgcHJvbWlzZXNcbiAqL1xuUS5hbGxSZXNvbHZlZCA9IGRlcHJlY2F0ZShhbGxSZXNvbHZlZCwgXCJhbGxSZXNvbHZlZFwiLCBcImFsbFNldHRsZWRcIik7XG5mdW5jdGlvbiBhbGxSZXNvbHZlZChwcm9taXNlcykge1xuICAgIHJldHVybiB3aGVuKHByb21pc2VzLCBmdW5jdGlvbiAocHJvbWlzZXMpIHtcbiAgICAgICAgcHJvbWlzZXMgPSBhcnJheV9tYXAocHJvbWlzZXMsIFEpO1xuICAgICAgICByZXR1cm4gd2hlbihhbGwoYXJyYXlfbWFwKHByb21pc2VzLCBmdW5jdGlvbiAocHJvbWlzZSkge1xuICAgICAgICAgICAgcmV0dXJuIHdoZW4ocHJvbWlzZSwgbm9vcCwgbm9vcCk7XG4gICAgICAgIH0pKSwgZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VzO1xuICAgICAgICB9KTtcbiAgICB9KTtcbn1cblxuUHJvbWlzZS5wcm90b3R5cGUuYWxsUmVzb2x2ZWQgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIGFsbFJlc29sdmVkKHRoaXMpO1xufTtcblxuLyoqXG4gKiBAc2VlIFByb21pc2UjYWxsU2V0dGxlZFxuICovXG5RLmFsbFNldHRsZWQgPSBhbGxTZXR0bGVkO1xuZnVuY3Rpb24gYWxsU2V0dGxlZChwcm9taXNlcykge1xuICAgIHJldHVybiBRKHByb21pc2VzKS5hbGxTZXR0bGVkKCk7XG59XG5cbi8qKlxuICogVHVybnMgYW4gYXJyYXkgb2YgcHJvbWlzZXMgaW50byBhIHByb21pc2UgZm9yIGFuIGFycmF5IG9mIHRoZWlyIHN0YXRlcyAoYXNcbiAqIHJldHVybmVkIGJ5IGBpbnNwZWN0YCkgd2hlbiB0aGV5IGhhdmUgYWxsIHNldHRsZWQuXG4gKiBAcGFyYW0ge0FycmF5W0FueSpdfSB2YWx1ZXMgYW4gYXJyYXkgKG9yIHByb21pc2UgZm9yIGFuIGFycmF5KSBvZiB2YWx1ZXMgKG9yXG4gKiBwcm9taXNlcyBmb3IgdmFsdWVzKVxuICogQHJldHVybnMge0FycmF5W1N0YXRlXX0gYW4gYXJyYXkgb2Ygc3RhdGVzIGZvciB0aGUgcmVzcGVjdGl2ZSB2YWx1ZXMuXG4gKi9cblByb21pc2UucHJvdG90eXBlLmFsbFNldHRsZWQgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIHRoaXMudGhlbihmdW5jdGlvbiAocHJvbWlzZXMpIHtcbiAgICAgICAgcmV0dXJuIGFsbChhcnJheV9tYXAocHJvbWlzZXMsIGZ1bmN0aW9uIChwcm9taXNlKSB7XG4gICAgICAgICAgICBwcm9taXNlID0gUShwcm9taXNlKTtcbiAgICAgICAgICAgIGZ1bmN0aW9uIHJlZ2FyZGxlc3MoKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHByb21pc2UuaW5zcGVjdCgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIHByb21pc2UudGhlbihyZWdhcmRsZXNzLCByZWdhcmRsZXNzKTtcbiAgICAgICAgfSkpO1xuICAgIH0pO1xufTtcblxuLyoqXG4gKiBDYXB0dXJlcyB0aGUgZmFpbHVyZSBvZiBhIHByb21pc2UsIGdpdmluZyBhbiBvcG9ydHVuaXR5IHRvIHJlY292ZXJcbiAqIHdpdGggYSBjYWxsYmFjay4gIElmIHRoZSBnaXZlbiBwcm9taXNlIGlzIGZ1bGZpbGxlZCwgdGhlIHJldHVybmVkXG4gKiBwcm9taXNlIGlzIGZ1bGZpbGxlZC5cbiAqIEBwYXJhbSB7QW55Kn0gcHJvbWlzZSBmb3Igc29tZXRoaW5nXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBjYWxsYmFjayB0byBmdWxmaWxsIHRoZSByZXR1cm5lZCBwcm9taXNlIGlmIHRoZVxuICogZ2l2ZW4gcHJvbWlzZSBpcyByZWplY3RlZFxuICogQHJldHVybnMgYSBwcm9taXNlIGZvciB0aGUgcmV0dXJuIHZhbHVlIG9mIHRoZSBjYWxsYmFja1xuICovXG5RLmZhaWwgPSAvLyBYWFggbGVnYWN5XG5RW1wiY2F0Y2hcIl0gPSBmdW5jdGlvbiAob2JqZWN0LCByZWplY3RlZCkge1xuICAgIHJldHVybiBRKG9iamVjdCkudGhlbih2b2lkIDAsIHJlamVjdGVkKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLmZhaWwgPSAvLyBYWFggbGVnYWN5XG5Qcm9taXNlLnByb3RvdHlwZVtcImNhdGNoXCJdID0gZnVuY3Rpb24gKHJlamVjdGVkKSB7XG4gICAgcmV0dXJuIHRoaXMudGhlbih2b2lkIDAsIHJlamVjdGVkKTtcbn07XG5cbi8qKlxuICogQXR0YWNoZXMgYSBsaXN0ZW5lciB0aGF0IGNhbiByZXNwb25kIHRvIHByb2dyZXNzIG5vdGlmaWNhdGlvbnMgZnJvbSBhXG4gKiBwcm9taXNlJ3Mgb3JpZ2luYXRpbmcgZGVmZXJyZWQuIFRoaXMgbGlzdGVuZXIgcmVjZWl2ZXMgdGhlIGV4YWN0IGFyZ3VtZW50c1xuICogcGFzc2VkIHRvIGBgZGVmZXJyZWQubm90aWZ5YGAuXG4gKiBAcGFyYW0ge0FueSp9IHByb21pc2UgZm9yIHNvbWV0aGluZ1xuICogQHBhcmFtIHtGdW5jdGlvbn0gY2FsbGJhY2sgdG8gcmVjZWl2ZSBhbnkgcHJvZ3Jlc3Mgbm90aWZpY2F0aW9uc1xuICogQHJldHVybnMgdGhlIGdpdmVuIHByb21pc2UsIHVuY2hhbmdlZFxuICovXG5RLnByb2dyZXNzID0gcHJvZ3Jlc3M7XG5mdW5jdGlvbiBwcm9ncmVzcyhvYmplY3QsIHByb2dyZXNzZWQpIHtcbiAgICByZXR1cm4gUShvYmplY3QpLnRoZW4odm9pZCAwLCB2b2lkIDAsIHByb2dyZXNzZWQpO1xufVxuXG5Qcm9taXNlLnByb3RvdHlwZS5wcm9ncmVzcyA9IGZ1bmN0aW9uIChwcm9ncmVzc2VkKSB7XG4gICAgcmV0dXJuIHRoaXMudGhlbih2b2lkIDAsIHZvaWQgMCwgcHJvZ3Jlc3NlZCk7XG59O1xuXG4vKipcbiAqIFByb3ZpZGVzIGFuIG9wcG9ydHVuaXR5IHRvIG9ic2VydmUgdGhlIHNldHRsaW5nIG9mIGEgcHJvbWlzZSxcbiAqIHJlZ2FyZGxlc3Mgb2Ygd2hldGhlciB0aGUgcHJvbWlzZSBpcyBmdWxmaWxsZWQgb3IgcmVqZWN0ZWQuICBGb3J3YXJkc1xuICogdGhlIHJlc29sdXRpb24gdG8gdGhlIHJldHVybmVkIHByb21pc2Ugd2hlbiB0aGUgY2FsbGJhY2sgaXMgZG9uZS5cbiAqIFRoZSBjYWxsYmFjayBjYW4gcmV0dXJuIGEgcHJvbWlzZSB0byBkZWZlciBjb21wbGV0aW9uLlxuICogQHBhcmFtIHtBbnkqfSBwcm9taXNlXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBjYWxsYmFjayB0byBvYnNlcnZlIHRoZSByZXNvbHV0aW9uIG9mIHRoZSBnaXZlblxuICogcHJvbWlzZSwgdGFrZXMgbm8gYXJndW1lbnRzLlxuICogQHJldHVybnMgYSBwcm9taXNlIGZvciB0aGUgcmVzb2x1dGlvbiBvZiB0aGUgZ2l2ZW4gcHJvbWlzZSB3aGVuXG4gKiBgYGZpbmBgIGlzIGRvbmUuXG4gKi9cblEuZmluID0gLy8gWFhYIGxlZ2FjeVxuUVtcImZpbmFsbHlcIl0gPSBmdW5jdGlvbiAob2JqZWN0LCBjYWxsYmFjaykge1xuICAgIHJldHVybiBRKG9iamVjdClbXCJmaW5hbGx5XCJdKGNhbGxiYWNrKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLmZpbiA9IC8vIFhYWCBsZWdhY3lcblByb21pc2UucHJvdG90eXBlW1wiZmluYWxseVwiXSA9IGZ1bmN0aW9uIChjYWxsYmFjaykge1xuICAgIGNhbGxiYWNrID0gUShjYWxsYmFjayk7XG4gICAgcmV0dXJuIHRoaXMudGhlbihmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgcmV0dXJuIGNhbGxiYWNrLmZjYWxsKCkudGhlbihmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICByZXR1cm4gdmFsdWU7XG4gICAgICAgIH0pO1xuICAgIH0sIGZ1bmN0aW9uIChyZWFzb24pIHtcbiAgICAgICAgLy8gVE9ETyBhdHRlbXB0IHRvIHJlY3ljbGUgdGhlIHJlamVjdGlvbiB3aXRoIFwidGhpc1wiLlxuICAgICAgICByZXR1cm4gY2FsbGJhY2suZmNhbGwoKS50aGVuKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHRocm93IHJlYXNvbjtcbiAgICAgICAgfSk7XG4gICAgfSk7XG59O1xuXG4vKipcbiAqIFRlcm1pbmF0ZXMgYSBjaGFpbiBvZiBwcm9taXNlcywgZm9yY2luZyByZWplY3Rpb25zIHRvIGJlXG4gKiB0aHJvd24gYXMgZXhjZXB0aW9ucy5cbiAqIEBwYXJhbSB7QW55Kn0gcHJvbWlzZSBhdCB0aGUgZW5kIG9mIGEgY2hhaW4gb2YgcHJvbWlzZXNcbiAqIEByZXR1cm5zIG5vdGhpbmdcbiAqL1xuUS5kb25lID0gZnVuY3Rpb24gKG9iamVjdCwgZnVsZmlsbGVkLCByZWplY3RlZCwgcHJvZ3Jlc3MpIHtcbiAgICByZXR1cm4gUShvYmplY3QpLmRvbmUoZnVsZmlsbGVkLCByZWplY3RlZCwgcHJvZ3Jlc3MpO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUuZG9uZSA9IGZ1bmN0aW9uIChmdWxmaWxsZWQsIHJlamVjdGVkLCBwcm9ncmVzcykge1xuICAgIHZhciBvblVuaGFuZGxlZEVycm9yID0gZnVuY3Rpb24gKGVycm9yKSB7XG4gICAgICAgIC8vIGZvcndhcmQgdG8gYSBmdXR1cmUgdHVybiBzbyB0aGF0IGBgd2hlbmBgXG4gICAgICAgIC8vIGRvZXMgbm90IGNhdGNoIGl0IGFuZCB0dXJuIGl0IGludG8gYSByZWplY3Rpb24uXG4gICAgICAgIFEubmV4dFRpY2soZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgbWFrZVN0YWNrVHJhY2VMb25nKGVycm9yLCBwcm9taXNlKTtcbiAgICAgICAgICAgIGlmIChRLm9uZXJyb3IpIHtcbiAgICAgICAgICAgICAgICBRLm9uZXJyb3IoZXJyb3IpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIC8vIEF2b2lkIHVubmVjZXNzYXJ5IGBuZXh0VGlja2BpbmcgdmlhIGFuIHVubmVjZXNzYXJ5IGB3aGVuYC5cbiAgICB2YXIgcHJvbWlzZSA9IGZ1bGZpbGxlZCB8fCByZWplY3RlZCB8fCBwcm9ncmVzcyA/XG4gICAgICAgIHRoaXMudGhlbihmdWxmaWxsZWQsIHJlamVjdGVkLCBwcm9ncmVzcykgOlxuICAgICAgICB0aGlzO1xuXG4gICAgaWYgKHR5cGVvZiBwcm9jZXNzID09PSBcIm9iamVjdFwiICYmIHByb2Nlc3MgJiYgcHJvY2Vzcy5kb21haW4pIHtcbiAgICAgICAgb25VbmhhbmRsZWRFcnJvciA9IHByb2Nlc3MuZG9tYWluLmJpbmQob25VbmhhbmRsZWRFcnJvcik7XG4gICAgfVxuXG4gICAgcHJvbWlzZS50aGVuKHZvaWQgMCwgb25VbmhhbmRsZWRFcnJvcik7XG59O1xuXG4vKipcbiAqIENhdXNlcyBhIHByb21pc2UgdG8gYmUgcmVqZWN0ZWQgaWYgaXQgZG9lcyBub3QgZ2V0IGZ1bGZpbGxlZCBiZWZvcmVcbiAqIHNvbWUgbWlsbGlzZWNvbmRzIHRpbWUgb3V0LlxuICogQHBhcmFtIHtBbnkqfSBwcm9taXNlXG4gKiBAcGFyYW0ge051bWJlcn0gbWlsbGlzZWNvbmRzIHRpbWVvdXRcbiAqIEBwYXJhbSB7QW55Kn0gY3VzdG9tIGVycm9yIG1lc3NhZ2Ugb3IgRXJyb3Igb2JqZWN0IChvcHRpb25hbClcbiAqIEByZXR1cm5zIGEgcHJvbWlzZSBmb3IgdGhlIHJlc29sdXRpb24gb2YgdGhlIGdpdmVuIHByb21pc2UgaWYgaXQgaXNcbiAqIGZ1bGZpbGxlZCBiZWZvcmUgdGhlIHRpbWVvdXQsIG90aGVyd2lzZSByZWplY3RlZC5cbiAqL1xuUS50aW1lb3V0ID0gZnVuY3Rpb24gKG9iamVjdCwgbXMsIGVycm9yKSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS50aW1lb3V0KG1zLCBlcnJvcik7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS50aW1lb3V0ID0gZnVuY3Rpb24gKG1zLCBlcnJvcikge1xuICAgIHZhciBkZWZlcnJlZCA9IGRlZmVyKCk7XG4gICAgdmFyIHRpbWVvdXRJZCA9IHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAoIWVycm9yIHx8IFwic3RyaW5nXCIgPT09IHR5cGVvZiBlcnJvcikge1xuICAgICAgICAgICAgZXJyb3IgPSBuZXcgRXJyb3IoZXJyb3IgfHwgXCJUaW1lZCBvdXQgYWZ0ZXIgXCIgKyBtcyArIFwiIG1zXCIpO1xuICAgICAgICAgICAgZXJyb3IuY29kZSA9IFwiRVRJTUVET1VUXCI7XG4gICAgICAgIH1cbiAgICAgICAgZGVmZXJyZWQucmVqZWN0KGVycm9yKTtcbiAgICB9LCBtcyk7XG5cbiAgICB0aGlzLnRoZW4oZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SWQpO1xuICAgICAgICBkZWZlcnJlZC5yZXNvbHZlKHZhbHVlKTtcbiAgICB9LCBmdW5jdGlvbiAoZXhjZXB0aW9uKSB7XG4gICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SWQpO1xuICAgICAgICBkZWZlcnJlZC5yZWplY3QoZXhjZXB0aW9uKTtcbiAgICB9LCBkZWZlcnJlZC5ub3RpZnkpO1xuXG4gICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG59O1xuXG4vKipcbiAqIFJldHVybnMgYSBwcm9taXNlIGZvciB0aGUgZ2l2ZW4gdmFsdWUgKG9yIHByb21pc2VkIHZhbHVlKSwgc29tZVxuICogbWlsbGlzZWNvbmRzIGFmdGVyIGl0IHJlc29sdmVkLiBQYXNzZXMgcmVqZWN0aW9ucyBpbW1lZGlhdGVseS5cbiAqIEBwYXJhbSB7QW55Kn0gcHJvbWlzZVxuICogQHBhcmFtIHtOdW1iZXJ9IG1pbGxpc2Vjb25kc1xuICogQHJldHVybnMgYSBwcm9taXNlIGZvciB0aGUgcmVzb2x1dGlvbiBvZiB0aGUgZ2l2ZW4gcHJvbWlzZSBhZnRlciBtaWxsaXNlY29uZHNcbiAqIHRpbWUgaGFzIGVsYXBzZWQgc2luY2UgdGhlIHJlc29sdXRpb24gb2YgdGhlIGdpdmVuIHByb21pc2UuXG4gKiBJZiB0aGUgZ2l2ZW4gcHJvbWlzZSByZWplY3RzLCB0aGF0IGlzIHBhc3NlZCBpbW1lZGlhdGVseS5cbiAqL1xuUS5kZWxheSA9IGZ1bmN0aW9uIChvYmplY3QsIHRpbWVvdXQpIHtcbiAgICBpZiAodGltZW91dCA9PT0gdm9pZCAwKSB7XG4gICAgICAgIHRpbWVvdXQgPSBvYmplY3Q7XG4gICAgICAgIG9iamVjdCA9IHZvaWQgMDtcbiAgICB9XG4gICAgcmV0dXJuIFEob2JqZWN0KS5kZWxheSh0aW1lb3V0KTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLmRlbGF5ID0gZnVuY3Rpb24gKHRpbWVvdXQpIHtcbiAgICByZXR1cm4gdGhpcy50aGVuKGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgICAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGRlZmVycmVkLnJlc29sdmUodmFsdWUpO1xuICAgICAgICB9LCB0aW1lb3V0KTtcbiAgICAgICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG4gICAgfSk7XG59O1xuXG4vKipcbiAqIFBhc3NlcyBhIGNvbnRpbnVhdGlvbiB0byBhIE5vZGUgZnVuY3Rpb24sIHdoaWNoIGlzIGNhbGxlZCB3aXRoIHRoZSBnaXZlblxuICogYXJndW1lbnRzIHByb3ZpZGVkIGFzIGFuIGFycmF5LCBhbmQgcmV0dXJucyBhIHByb21pc2UuXG4gKlxuICogICAgICBRLm5mYXBwbHkoRlMucmVhZEZpbGUsIFtfX2ZpbGVuYW1lXSlcbiAqICAgICAgLnRoZW4oZnVuY3Rpb24gKGNvbnRlbnQpIHtcbiAqICAgICAgfSlcbiAqXG4gKi9cblEubmZhcHBseSA9IGZ1bmN0aW9uIChjYWxsYmFjaywgYXJncykge1xuICAgIHJldHVybiBRKGNhbGxiYWNrKS5uZmFwcGx5KGFyZ3MpO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUubmZhcHBseSA9IGZ1bmN0aW9uIChhcmdzKSB7XG4gICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICB2YXIgbm9kZUFyZ3MgPSBhcnJheV9zbGljZShhcmdzKTtcbiAgICBub2RlQXJncy5wdXNoKGRlZmVycmVkLm1ha2VOb2RlUmVzb2x2ZXIoKSk7XG4gICAgdGhpcy5mYXBwbHkobm9kZUFyZ3MpLmZhaWwoZGVmZXJyZWQucmVqZWN0KTtcbiAgICByZXR1cm4gZGVmZXJyZWQucHJvbWlzZTtcbn07XG5cbi8qKlxuICogUGFzc2VzIGEgY29udGludWF0aW9uIHRvIGEgTm9kZSBmdW5jdGlvbiwgd2hpY2ggaXMgY2FsbGVkIHdpdGggdGhlIGdpdmVuXG4gKiBhcmd1bWVudHMgcHJvdmlkZWQgaW5kaXZpZHVhbGx5LCBhbmQgcmV0dXJucyBhIHByb21pc2UuXG4gKiBAZXhhbXBsZVxuICogUS5uZmNhbGwoRlMucmVhZEZpbGUsIF9fZmlsZW5hbWUpXG4gKiAudGhlbihmdW5jdGlvbiAoY29udGVudCkge1xuICogfSlcbiAqXG4gKi9cblEubmZjYWxsID0gZnVuY3Rpb24gKGNhbGxiYWNrIC8qLi4uYXJncyovKSB7XG4gICAgdmFyIGFyZ3MgPSBhcnJheV9zbGljZShhcmd1bWVudHMsIDEpO1xuICAgIHJldHVybiBRKGNhbGxiYWNrKS5uZmFwcGx5KGFyZ3MpO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUubmZjYWxsID0gZnVuY3Rpb24gKC8qLi4uYXJncyovKSB7XG4gICAgdmFyIG5vZGVBcmdzID0gYXJyYXlfc2xpY2UoYXJndW1lbnRzKTtcbiAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgIG5vZGVBcmdzLnB1c2goZGVmZXJyZWQubWFrZU5vZGVSZXNvbHZlcigpKTtcbiAgICB0aGlzLmZhcHBseShub2RlQXJncykuZmFpbChkZWZlcnJlZC5yZWplY3QpO1xuICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xufTtcblxuLyoqXG4gKiBXcmFwcyBhIE5vZGVKUyBjb250aW51YXRpb24gcGFzc2luZyBmdW5jdGlvbiBhbmQgcmV0dXJucyBhbiBlcXVpdmFsZW50XG4gKiB2ZXJzaW9uIHRoYXQgcmV0dXJucyBhIHByb21pc2UuXG4gKiBAZXhhbXBsZVxuICogUS5uZmJpbmQoRlMucmVhZEZpbGUsIF9fZmlsZW5hbWUpKFwidXRmLThcIilcbiAqIC50aGVuKGNvbnNvbGUubG9nKVxuICogLmRvbmUoKVxuICovXG5RLm5mYmluZCA9XG5RLmRlbm9kZWlmeSA9IGZ1bmN0aW9uIChjYWxsYmFjayAvKi4uLmFyZ3MqLykge1xuICAgIHZhciBiYXNlQXJncyA9IGFycmF5X3NsaWNlKGFyZ3VtZW50cywgMSk7XG4gICAgcmV0dXJuIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIG5vZGVBcmdzID0gYmFzZUFyZ3MuY29uY2F0KGFycmF5X3NsaWNlKGFyZ3VtZW50cykpO1xuICAgICAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgICAgICBub2RlQXJncy5wdXNoKGRlZmVycmVkLm1ha2VOb2RlUmVzb2x2ZXIoKSk7XG4gICAgICAgIFEoY2FsbGJhY2spLmZhcHBseShub2RlQXJncykuZmFpbChkZWZlcnJlZC5yZWplY3QpO1xuICAgICAgICByZXR1cm4gZGVmZXJyZWQucHJvbWlzZTtcbiAgICB9O1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUubmZiaW5kID1cblByb21pc2UucHJvdG90eXBlLmRlbm9kZWlmeSA9IGZ1bmN0aW9uICgvKi4uLmFyZ3MqLykge1xuICAgIHZhciBhcmdzID0gYXJyYXlfc2xpY2UoYXJndW1lbnRzKTtcbiAgICBhcmdzLnVuc2hpZnQodGhpcyk7XG4gICAgcmV0dXJuIFEuZGVub2RlaWZ5LmFwcGx5KHZvaWQgMCwgYXJncyk7XG59O1xuXG5RLm5iaW5kID0gZnVuY3Rpb24gKGNhbGxiYWNrLCB0aGlzcCAvKi4uLmFyZ3MqLykge1xuICAgIHZhciBiYXNlQXJncyA9IGFycmF5X3NsaWNlKGFyZ3VtZW50cywgMik7XG4gICAgcmV0dXJuIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIG5vZGVBcmdzID0gYmFzZUFyZ3MuY29uY2F0KGFycmF5X3NsaWNlKGFyZ3VtZW50cykpO1xuICAgICAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgICAgICBub2RlQXJncy5wdXNoKGRlZmVycmVkLm1ha2VOb2RlUmVzb2x2ZXIoKSk7XG4gICAgICAgIGZ1bmN0aW9uIGJvdW5kKCkge1xuICAgICAgICAgICAgcmV0dXJuIGNhbGxiYWNrLmFwcGx5KHRoaXNwLCBhcmd1bWVudHMpO1xuICAgICAgICB9XG4gICAgICAgIFEoYm91bmQpLmZhcHBseShub2RlQXJncykuZmFpbChkZWZlcnJlZC5yZWplY3QpO1xuICAgICAgICByZXR1cm4gZGVmZXJyZWQucHJvbWlzZTtcbiAgICB9O1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUubmJpbmQgPSBmdW5jdGlvbiAoLyp0aGlzcCwgLi4uYXJncyovKSB7XG4gICAgdmFyIGFyZ3MgPSBhcnJheV9zbGljZShhcmd1bWVudHMsIDApO1xuICAgIGFyZ3MudW5zaGlmdCh0aGlzKTtcbiAgICByZXR1cm4gUS5uYmluZC5hcHBseSh2b2lkIDAsIGFyZ3MpO1xufTtcblxuLyoqXG4gKiBDYWxscyBhIG1ldGhvZCBvZiBhIE5vZGUtc3R5bGUgb2JqZWN0IHRoYXQgYWNjZXB0cyBhIE5vZGUtc3R5bGVcbiAqIGNhbGxiYWNrIHdpdGggYSBnaXZlbiBhcnJheSBvZiBhcmd1bWVudHMsIHBsdXMgYSBwcm92aWRlZCBjYWxsYmFjay5cbiAqIEBwYXJhbSBvYmplY3QgYW4gb2JqZWN0IHRoYXQgaGFzIHRoZSBuYW1lZCBtZXRob2RcbiAqIEBwYXJhbSB7U3RyaW5nfSBuYW1lIG5hbWUgb2YgdGhlIG1ldGhvZCBvZiBvYmplY3RcbiAqIEBwYXJhbSB7QXJyYXl9IGFyZ3MgYXJndW1lbnRzIHRvIHBhc3MgdG8gdGhlIG1ldGhvZDsgdGhlIGNhbGxiYWNrXG4gKiB3aWxsIGJlIHByb3ZpZGVkIGJ5IFEgYW5kIGFwcGVuZGVkIHRvIHRoZXNlIGFyZ3VtZW50cy5cbiAqIEByZXR1cm5zIGEgcHJvbWlzZSBmb3IgdGhlIHZhbHVlIG9yIGVycm9yXG4gKi9cblEubm1hcHBseSA9IC8vIFhYWCBBcyBwcm9wb3NlZCBieSBcIlJlZHNhbmRyb1wiXG5RLm5wb3N0ID0gZnVuY3Rpb24gKG9iamVjdCwgbmFtZSwgYXJncykge1xuICAgIHJldHVybiBRKG9iamVjdCkubnBvc3QobmFtZSwgYXJncyk7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS5ubWFwcGx5ID0gLy8gWFhYIEFzIHByb3Bvc2VkIGJ5IFwiUmVkc2FuZHJvXCJcblByb21pc2UucHJvdG90eXBlLm5wb3N0ID0gZnVuY3Rpb24gKG5hbWUsIGFyZ3MpIHtcbiAgICB2YXIgbm9kZUFyZ3MgPSBhcnJheV9zbGljZShhcmdzIHx8IFtdKTtcbiAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgIG5vZGVBcmdzLnB1c2goZGVmZXJyZWQubWFrZU5vZGVSZXNvbHZlcigpKTtcbiAgICB0aGlzLmRpc3BhdGNoKFwicG9zdFwiLCBbbmFtZSwgbm9kZUFyZ3NdKS5mYWlsKGRlZmVycmVkLnJlamVjdCk7XG4gICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG59O1xuXG4vKipcbiAqIENhbGxzIGEgbWV0aG9kIG9mIGEgTm9kZS1zdHlsZSBvYmplY3QgdGhhdCBhY2NlcHRzIGEgTm9kZS1zdHlsZVxuICogY2FsbGJhY2ssIGZvcndhcmRpbmcgdGhlIGdpdmVuIHZhcmlhZGljIGFyZ3VtZW50cywgcGx1cyBhIHByb3ZpZGVkXG4gKiBjYWxsYmFjayBhcmd1bWVudC5cbiAqIEBwYXJhbSBvYmplY3QgYW4gb2JqZWN0IHRoYXQgaGFzIHRoZSBuYW1lZCBtZXRob2RcbiAqIEBwYXJhbSB7U3RyaW5nfSBuYW1lIG5hbWUgb2YgdGhlIG1ldGhvZCBvZiBvYmplY3RcbiAqIEBwYXJhbSAuLi5hcmdzIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSBtZXRob2Q7IHRoZSBjYWxsYmFjayB3aWxsXG4gKiBiZSBwcm92aWRlZCBieSBRIGFuZCBhcHBlbmRlZCB0byB0aGVzZSBhcmd1bWVudHMuXG4gKiBAcmV0dXJucyBhIHByb21pc2UgZm9yIHRoZSB2YWx1ZSBvciBlcnJvclxuICovXG5RLm5zZW5kID0gLy8gWFhYIEJhc2VkIG9uIE1hcmsgTWlsbGVyJ3MgcHJvcG9zZWQgXCJzZW5kXCJcblEubm1jYWxsID0gLy8gWFhYIEJhc2VkIG9uIFwiUmVkc2FuZHJvJ3NcIiBwcm9wb3NhbFxuUS5uaW52b2tlID0gZnVuY3Rpb24gKG9iamVjdCwgbmFtZSAvKi4uLmFyZ3MqLykge1xuICAgIHZhciBub2RlQXJncyA9IGFycmF5X3NsaWNlKGFyZ3VtZW50cywgMik7XG4gICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICBub2RlQXJncy5wdXNoKGRlZmVycmVkLm1ha2VOb2RlUmVzb2x2ZXIoKSk7XG4gICAgUShvYmplY3QpLmRpc3BhdGNoKFwicG9zdFwiLCBbbmFtZSwgbm9kZUFyZ3NdKS5mYWlsKGRlZmVycmVkLnJlamVjdCk7XG4gICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS5uc2VuZCA9IC8vIFhYWCBCYXNlZCBvbiBNYXJrIE1pbGxlcidzIHByb3Bvc2VkIFwic2VuZFwiXG5Qcm9taXNlLnByb3RvdHlwZS5ubWNhbGwgPSAvLyBYWFggQmFzZWQgb24gXCJSZWRzYW5kcm8nc1wiIHByb3Bvc2FsXG5Qcm9taXNlLnByb3RvdHlwZS5uaW52b2tlID0gZnVuY3Rpb24gKG5hbWUgLyouLi5hcmdzKi8pIHtcbiAgICB2YXIgbm9kZUFyZ3MgPSBhcnJheV9zbGljZShhcmd1bWVudHMsIDEpO1xuICAgIHZhciBkZWZlcnJlZCA9IGRlZmVyKCk7XG4gICAgbm9kZUFyZ3MucHVzaChkZWZlcnJlZC5tYWtlTm9kZVJlc29sdmVyKCkpO1xuICAgIHRoaXMuZGlzcGF0Y2goXCJwb3N0XCIsIFtuYW1lLCBub2RlQXJnc10pLmZhaWwoZGVmZXJyZWQucmVqZWN0KTtcbiAgICByZXR1cm4gZGVmZXJyZWQucHJvbWlzZTtcbn07XG5cbi8qKlxuICogSWYgYSBmdW5jdGlvbiB3b3VsZCBsaWtlIHRvIHN1cHBvcnQgYm90aCBOb2RlIGNvbnRpbnVhdGlvbi1wYXNzaW5nLXN0eWxlIGFuZFxuICogcHJvbWlzZS1yZXR1cm5pbmctc3R5bGUsIGl0IGNhbiBlbmQgaXRzIGludGVybmFsIHByb21pc2UgY2hhaW4gd2l0aFxuICogYG5vZGVpZnkobm9kZWJhY2spYCwgZm9yd2FyZGluZyB0aGUgb3B0aW9uYWwgbm9kZWJhY2sgYXJndW1lbnQuICBJZiB0aGUgdXNlclxuICogZWxlY3RzIHRvIHVzZSBhIG5vZGViYWNrLCB0aGUgcmVzdWx0IHdpbGwgYmUgc2VudCB0aGVyZS4gIElmIHRoZXkgZG8gbm90XG4gKiBwYXNzIGEgbm9kZWJhY2ssIHRoZXkgd2lsbCByZWNlaXZlIHRoZSByZXN1bHQgcHJvbWlzZS5cbiAqIEBwYXJhbSBvYmplY3QgYSByZXN1bHQgKG9yIGEgcHJvbWlzZSBmb3IgYSByZXN1bHQpXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBub2RlYmFjayBhIE5vZGUuanMtc3R5bGUgY2FsbGJhY2tcbiAqIEByZXR1cm5zIGVpdGhlciB0aGUgcHJvbWlzZSBvciBub3RoaW5nXG4gKi9cblEubm9kZWlmeSA9IG5vZGVpZnk7XG5mdW5jdGlvbiBub2RlaWZ5KG9iamVjdCwgbm9kZWJhY2spIHtcbiAgICByZXR1cm4gUShvYmplY3QpLm5vZGVpZnkobm9kZWJhY2spO1xufVxuXG5Qcm9taXNlLnByb3RvdHlwZS5ub2RlaWZ5ID0gZnVuY3Rpb24gKG5vZGViYWNrKSB7XG4gICAgaWYgKG5vZGViYWNrKSB7XG4gICAgICAgIHRoaXMudGhlbihmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgICAgIFEubmV4dFRpY2soZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIG5vZGViYWNrKG51bGwsIHZhbHVlKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9LCBmdW5jdGlvbiAoZXJyb3IpIHtcbiAgICAgICAgICAgIFEubmV4dFRpY2soZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIG5vZGViYWNrKGVycm9yKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcbiAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG59O1xuXG5RLm5vQ29uZmxpY3QgPSBmdW5jdGlvbigpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXCJRLm5vQ29uZmxpY3Qgb25seSB3b3JrcyB3aGVuIFEgaXMgdXNlZCBhcyBhIGdsb2JhbFwiKTtcbn07XG5cbi8vIEFsbCBjb2RlIGJlZm9yZSB0aGlzIHBvaW50IHdpbGwgYmUgZmlsdGVyZWQgZnJvbSBzdGFjayB0cmFjZXMuXG52YXIgcUVuZGluZ0xpbmUgPSBjYXB0dXJlTGluZSgpO1xuXG5yZXR1cm4gUTtcblxufSk7XG4iXX0=\n},{\"_process\":12}],158:[function(require,module,exports){\n/**\n * Root reference for iframes.\n */\n\nvar root;\nif (typeof window !== 'undefined') { // Browser window\n  root = window;\n} else if (typeof self !== 'undefined') { // Web Worker\n  root = self;\n} else { // Other environments\n  console.warn(\"Using browser-only version of superagent in non-browser environment\");\n  root = this;\n}\n\nvar Emitter = require('emitter');\nvar requestBase = require('./request-base');\nvar isObject = require('./is-object');\n\n/**\n * Noop.\n */\n\nfunction noop(){};\n\n/**\n * Expose `request`.\n */\n\nvar request = module.exports = require('./request').bind(null, Request);\n\n/**\n * Determine XHR.\n */\n\nrequest.getXHR = function () {\n  if (root.XMLHttpRequest\n      && (!root.location || 'file:' != root.location.protocol\n          || !root.ActiveXObject)) {\n    return new XMLHttpRequest;\n  } else {\n    try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {}\n    try { return new ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch(e) {}\n    try { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch(e) {}\n    try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {}\n  }\n  throw Error(\"Browser-only verison of superagent could not find XHR\");\n};\n\n/**\n * Removes leading and trailing whitespace, added to support IE.\n *\n * @param {String} s\n * @return {String}\n * @api private\n */\n\nvar trim = ''.trim\n  ? function(s) { return s.trim(); }\n  : function(s) { return s.replace(/(^\\s*|\\s*$)/g, ''); };\n\n/**\n * Serialize the given `obj`.\n *\n * @param {Object} obj\n * @return {String}\n * @api private\n */\n\nfunction serialize(obj) {\n  if (!isObject(obj)) return obj;\n  var pairs = [];\n  for (var key in obj) {\n    pushEncodedKeyValuePair(pairs, key, obj[key]);\n  }\n  return pairs.join('&');\n}\n\n/**\n * Helps 'serialize' with serializing arrays.\n * Mutates the pairs array.\n *\n * @param {Array} pairs\n * @param {String} key\n * @param {Mixed} val\n */\n\nfunction pushEncodedKeyValuePair(pairs, key, val) {\n  if (val != null) {\n    if (Array.isArray(val)) {\n      val.forEach(function(v) {\n        pushEncodedKeyValuePair(pairs, key, v);\n      });\n    } else if (isObject(val)) {\n      for(var subkey in val) {\n        pushEncodedKeyValuePair(pairs, key + '[' + subkey + ']', val[subkey]);\n      }\n    } else {\n      pairs.push(encodeURIComponent(key)\n        + '=' + encodeURIComponent(val));\n    }\n  } else if (val === null) {\n    pairs.push(encodeURIComponent(key));\n  }\n}\n\n/**\n * Expose serialization method.\n */\n\n request.serializeObject = serialize;\n\n /**\n  * Parse the given x-www-form-urlencoded `str`.\n  *\n  * @param {String} str\n  * @return {Object}\n  * @api private\n  */\n\nfunction parseString(str) {\n  var obj = {};\n  var pairs = str.split('&');\n  var pair;\n  var pos;\n\n  for (var i = 0, len = pairs.length; i < len; ++i) {\n    pair = pairs[i];\n    pos = pair.indexOf('=');\n    if (pos == -1) {\n      obj[decodeURIComponent(pair)] = '';\n    } else {\n      obj[decodeURIComponent(pair.slice(0, pos))] =\n        decodeURIComponent(pair.slice(pos + 1));\n    }\n  }\n\n  return obj;\n}\n\n/**\n * Expose parser.\n */\n\nrequest.parseString = parseString;\n\n/**\n * Default MIME type map.\n *\n *     superagent.types.xml = 'application/xml';\n *\n */\n\nrequest.types = {\n  html: 'text/html',\n  json: 'application/json',\n  xml: 'application/xml',\n  urlencoded: 'application/x-www-form-urlencoded',\n  'form': 'application/x-www-form-urlencoded',\n  'form-data': 'application/x-www-form-urlencoded'\n};\n\n/**\n * Default serialization map.\n *\n *     superagent.serialize['application/xml'] = function(obj){\n *       return 'generated xml here';\n *     };\n *\n */\n\n request.serialize = {\n   'application/x-www-form-urlencoded': serialize,\n   'application/json': JSON.stringify\n };\n\n /**\n  * Default parsers.\n  *\n  *     superagent.parse['application/xml'] = function(str){\n  *       return { object parsed from str };\n  *     };\n  *\n  */\n\nrequest.parse = {\n  'application/x-www-form-urlencoded': parseString,\n  'application/json': JSON.parse\n};\n\n/**\n * Parse the given header `str` into\n * an object containing the mapped fields.\n *\n * @param {String} str\n * @return {Object}\n * @api private\n */\n\nfunction parseHeader(str) {\n  var lines = str.split(/\\r?\\n/);\n  var fields = {};\n  var index;\n  var line;\n  var field;\n  var val;\n\n  lines.pop(); // trailing CRLF\n\n  for (var i = 0, len = lines.length; i < len; ++i) {\n    line = lines[i];\n    index = line.indexOf(':');\n    field = line.slice(0, index).toLowerCase();\n    val = trim(line.slice(index + 1));\n    fields[field] = val;\n  }\n\n  return fields;\n}\n\n/**\n * Check if `mime` is json or has +json structured syntax suffix.\n *\n * @param {String} mime\n * @return {Boolean}\n * @api private\n */\n\nfunction isJSON(mime) {\n  return /[\\/+]json\\b/.test(mime);\n}\n\n/**\n * Return the mime type for the given `str`.\n *\n * @param {String} str\n * @return {String}\n * @api private\n */\n\nfunction type(str){\n  return str.split(/ *; */).shift();\n};\n\n/**\n * Return header field parameters.\n *\n * @param {String} str\n * @return {Object}\n * @api private\n */\n\nfunction params(str){\n  return str.split(/ *; */).reduce(function(obj, str){\n    var parts = str.split(/ *= */),\n        key = parts.shift(),\n        val = parts.shift();\n\n    if (key && val) obj[key] = val;\n    return obj;\n  }, {});\n};\n\n/**\n * Initialize a new `Response` with the given `xhr`.\n *\n *  - set flags (.ok, .error, etc)\n *  - parse header\n *\n * Examples:\n *\n *  Aliasing `superagent` as `request` is nice:\n *\n *      request = superagent;\n *\n *  We can use the promise-like API, or pass callbacks:\n *\n *      request.get('/').end(function(res){});\n *      request.get('/', function(res){});\n *\n *  Sending data can be chained:\n *\n *      request\n *        .post('/user')\n *        .send({ name: 'tj' })\n *        .end(function(res){});\n *\n *  Or passed to `.send()`:\n *\n *      request\n *        .post('/user')\n *        .send({ name: 'tj' }, function(res){});\n *\n *  Or passed to `.post()`:\n *\n *      request\n *        .post('/user', { name: 'tj' })\n *        .end(function(res){});\n *\n * Or further reduced to a single call for simple cases:\n *\n *      request\n *        .post('/user', { name: 'tj' }, function(res){});\n *\n * @param {XMLHTTPRequest} xhr\n * @param {Object} options\n * @api private\n */\n\nfunction Response(req, options) {\n  options = options || {};\n  this.req = req;\n  this.xhr = this.req.xhr;\n  // responseText is accessible only if responseType is '' or 'text' and on older browsers\n  this.text = ((this.req.method !='HEAD' && (this.xhr.responseType === '' || this.xhr.responseType === 'text')) || typeof this.xhr.responseType === 'undefined')\n     ? this.xhr.responseText\n     : null;\n  this.statusText = this.req.xhr.statusText;\n  this._setStatusProperties(this.xhr.status);\n  this.header = this.headers = parseHeader(this.xhr.getAllResponseHeaders());\n  // getAllResponseHeaders sometimes falsely returns \"\" for CORS requests, but\n  // getResponseHeader still works. so we get content-type even if getting\n  // other headers fails.\n  this.header['content-type'] = this.xhr.getResponseHeader('content-type');\n  this._setHeaderProperties(this.header);\n  this.body = this.req.method != 'HEAD'\n    ? this._parseBody(this.text ? this.text : this.xhr.response)\n    : null;\n}\n\n/**\n * Get case-insensitive `field` value.\n *\n * @param {String} field\n * @return {String}\n * @api public\n */\n\nResponse.prototype.get = function(field){\n  return this.header[field.toLowerCase()];\n};\n\n/**\n * Set header related properties:\n *\n *   - `.type` the content type without params\n *\n * A response of \"Content-Type: text/plain; charset=utf-8\"\n * will provide you with a `.type` of \"text/plain\".\n *\n * @param {Object} header\n * @api private\n */\n\nResponse.prototype._setHeaderProperties = function(header){\n  // content-type\n  var ct = this.header['content-type'] || '';\n  this.type = type(ct);\n\n  // params\n  var obj = params(ct);\n  for (var key in obj) this[key] = obj[key];\n};\n\n/**\n * Parse the given body `str`.\n *\n * Used for auto-parsing of bodies. Parsers\n * are defined on the `superagent.parse` object.\n *\n * @param {String} str\n * @return {Mixed}\n * @api private\n */\n\nResponse.prototype._parseBody = function(str){\n  var parse = request.parse[this.type];\n  if (!parse && isJSON(this.type)) {\n    parse = request.parse['application/json'];\n  }\n  return parse && str && (str.length || str instanceof Object)\n    ? parse(str)\n    : null;\n};\n\n/**\n * Set flags such as `.ok` based on `status`.\n *\n * For example a 2xx response will give you a `.ok` of __true__\n * whereas 5xx will be __false__ and `.error` will be __true__. The\n * `.clientError` and `.serverError` are also available to be more\n * specific, and `.statusType` is the class of error ranging from 1..5\n * sometimes useful for mapping respond colors etc.\n *\n * \"sugar\" properties are also defined for common cases. Currently providing:\n *\n *   - .noContent\n *   - .badRequest\n *   - .unauthorized\n *   - .notAcceptable\n *   - .notFound\n *\n * @param {Number} status\n * @api private\n */\n\nResponse.prototype._setStatusProperties = function(status){\n  // handle IE9 bug: http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request\n  if (status === 1223) {\n    status = 204;\n  }\n\n  var type = status / 100 | 0;\n\n  // status / class\n  this.status = this.statusCode = status;\n  this.statusType = type;\n\n  // basics\n  this.info = 1 == type;\n  this.ok = 2 == type;\n  this.clientError = 4 == type;\n  this.serverError = 5 == type;\n  this.error = (4 == type || 5 == type)\n    ? this.toError()\n    : false;\n\n  // sugar\n  this.accepted = 202 == status;\n  this.noContent = 204 == status;\n  this.badRequest = 400 == status;\n  this.unauthorized = 401 == status;\n  this.notAcceptable = 406 == status;\n  this.notFound = 404 == status;\n  this.forbidden = 403 == status;\n};\n\n/**\n * Return an `Error` representative of this response.\n *\n * @return {Error}\n * @api public\n */\n\nResponse.prototype.toError = function(){\n  var req = this.req;\n  var method = req.method;\n  var url = req.url;\n\n  var msg = 'cannot ' + method + ' ' + url + ' (' + this.status + ')';\n  var err = new Error(msg);\n  err.status = this.status;\n  err.method = method;\n  err.url = url;\n\n  return err;\n};\n\n/**\n * Expose `Response`.\n */\n\nrequest.Response = Response;\n\n/**\n * Initialize a new `Request` with the given `method` and `url`.\n *\n * @param {String} method\n * @param {String} url\n * @api public\n */\n\nfunction Request(method, url) {\n  var self = this;\n  this._query = this._query || [];\n  this.method = method;\n  this.url = url;\n  this.header = {}; // preserves header name case\n  this._header = {}; // coerces header names to lowercase\n  this.on('end', function(){\n    var err = null;\n    var res = null;\n\n    try {\n      res = new Response(self);\n    } catch(e) {\n      err = new Error('Parser is unable to parse the response');\n      err.parse = true;\n      err.original = e;\n      // issue #675: return the raw response if the response parsing fails\n      err.rawResponse = self.xhr && self.xhr.responseText ? self.xhr.responseText : null;\n      // issue #876: return the http status code if the response parsing fails\n      err.statusCode = self.xhr && self.xhr.status ? self.xhr.status : null;\n      return self.callback(err);\n    }\n\n    self.emit('response', res);\n\n    var new_err;\n    try {\n      if (res.status < 200 || res.status >= 300) {\n        new_err = new Error(res.statusText || 'Unsuccessful HTTP response');\n        new_err.original = err;\n        new_err.response = res;\n        new_err.status = res.status;\n      }\n    } catch(e) {\n      new_err = e; // #985 touching res may cause INVALID_STATE_ERR on old Android\n    }\n\n    // #1000 don't catch errors from the callback to avoid double calling it\n    if (new_err) {\n      self.callback(new_err, res);\n    } else {\n      self.callback(null, res);\n    }\n  });\n}\n\n/**\n * Mixin `Emitter` and `requestBase`.\n */\n\nEmitter(Request.prototype);\nfor (var key in requestBase) {\n  Request.prototype[key] = requestBase[key];\n}\n\n/**\n * Set Content-Type to `type`, mapping values from `request.types`.\n *\n * Examples:\n *\n *      superagent.types.xml = 'application/xml';\n *\n *      request.post('/')\n *        .type('xml')\n *        .send(xmlstring)\n *        .end(callback);\n *\n *      request.post('/')\n *        .type('application/xml')\n *        .send(xmlstring)\n *        .end(callback);\n *\n * @param {String} type\n * @return {Request} for chaining\n * @api public\n */\n\nRequest.prototype.type = function(type){\n  this.set('Content-Type', request.types[type] || type);\n  return this;\n};\n\n/**\n * Set responseType to `val`. Presently valid responseTypes are 'blob' and\n * 'arraybuffer'.\n *\n * Examples:\n *\n *      req.get('/')\n *        .responseType('blob')\n *        .end(callback);\n *\n * @param {String} val\n * @return {Request} for chaining\n * @api public\n */\n\nRequest.prototype.responseType = function(val){\n  this._responseType = val;\n  return this;\n};\n\n/**\n * Set Accept to `type`, mapping values from `request.types`.\n *\n * Examples:\n *\n *      superagent.types.json = 'application/json';\n *\n *      request.get('/agent')\n *        .accept('json')\n *        .end(callback);\n *\n *      request.get('/agent')\n *        .accept('application/json')\n *        .end(callback);\n *\n * @param {String} accept\n * @return {Request} for chaining\n * @api public\n */\n\nRequest.prototype.accept = function(type){\n  this.set('Accept', request.types[type] || type);\n  return this;\n};\n\n/**\n * Set Authorization field value with `user` and `pass`.\n *\n * @param {String} user\n * @param {String} pass\n * @param {Object} options with 'type' property 'auto' or 'basic' (default 'basic')\n * @return {Request} for chaining\n * @api public\n */\n\nRequest.prototype.auth = function(user, pass, options){\n  if (!options) {\n    options = {\n      type: 'basic'\n    }\n  }\n\n  switch (options.type) {\n    case 'basic':\n      var str = btoa(user + ':' + pass);\n      this.set('Authorization', 'Basic ' + str);\n    break;\n\n    case 'auto':\n      this.username = user;\n      this.password = pass;\n    break;\n  }\n  return this;\n};\n\n/**\n* Add query-string `val`.\n*\n* Examples:\n*\n*   request.get('/shoes')\n*     .query('size=10')\n*     .query({ color: 'blue' })\n*\n* @param {Object|String} val\n* @return {Request} for chaining\n* @api public\n*/\n\nRequest.prototype.query = function(val){\n  if ('string' != typeof val) val = serialize(val);\n  if (val) this._query.push(val);\n  return this;\n};\n\n/**\n * Queue the given `file` as an attachment to the specified `field`,\n * with optional `filename`.\n *\n * ``` js\n * request.post('/upload')\n *   .attach('content', new Blob(['<a id=\"a\"><b id=\"b\">hey!</b></a>'], { type: \"text/html\"}))\n *   .end(callback);\n * ```\n *\n * @param {String} field\n * @param {Blob|File} file\n * @param {String} filename\n * @return {Request} for chaining\n * @api public\n */\n\nRequest.prototype.attach = function(field, file, filename){\n  this._getFormData().append(field, file, filename || file.name);\n  return this;\n};\n\nRequest.prototype._getFormData = function(){\n  if (!this._formData) {\n    this._formData = new root.FormData();\n  }\n  return this._formData;\n};\n\n/**\n * Invoke the callback with `err` and `res`\n * and handle arity check.\n *\n * @param {Error} err\n * @param {Response} res\n * @api private\n */\n\nRequest.prototype.callback = function(err, res){\n  var fn = this._callback;\n  this.clearTimeout();\n  fn(err, res);\n};\n\n/**\n * Invoke callback with x-domain error.\n *\n * @api private\n */\n\nRequest.prototype.crossDomainError = function(){\n  var err = new Error('Request has been terminated\\nPossible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.');\n  err.crossDomain = true;\n\n  err.status = this.status;\n  err.method = this.method;\n  err.url = this.url;\n\n  this.callback(err);\n};\n\n/**\n * Invoke callback with timeout error.\n *\n * @api private\n */\n\nRequest.prototype._timeoutError = function(){\n  var timeout = this._timeout;\n  var err = new Error('timeout of ' + timeout + 'ms exceeded');\n  err.timeout = timeout;\n  this.callback(err);\n};\n\n/**\n * Compose querystring to append to req.url\n *\n * @api private\n */\n\nRequest.prototype._appendQueryString = function(){\n  var query = this._query.join('&');\n  if (query) {\n    this.url += ~this.url.indexOf('?')\n      ? '&' + query\n      : '?' + query;\n  }\n};\n\n/**\n * Initiate request, invoking callback `fn(res)`\n * with an instanceof `Response`.\n *\n * @param {Function} fn\n * @return {Request} for chaining\n * @api public\n */\n\nRequest.prototype.end = function(fn){\n  var self = this;\n  var xhr = this.xhr = request.getXHR();\n  var timeout = this._timeout;\n  var data = this._formData || this._data;\n\n  // store callback\n  this._callback = fn || noop;\n\n  // state change\n  xhr.onreadystatechange = function(){\n    if (4 != xhr.readyState) return;\n\n    // In IE9, reads to any property (e.g. status) off of an aborted XHR will\n    // result in the error \"Could not complete the operation due to error c00c023f\"\n    var status;\n    try { status = xhr.status } catch(e) { status = 0; }\n\n    if (0 == status) {\n      if (self.timedout) return self._timeoutError();\n      if (self._aborted) return;\n      return self.crossDomainError();\n    }\n    self.emit('end');\n  };\n\n  // progress\n  var handleProgress = function(e){\n    if (e.total > 0) {\n      e.percent = e.loaded / e.total * 100;\n    }\n    e.direction = 'download';\n    self.emit('progress', e);\n  };\n  if (this.hasListeners('progress')) {\n    xhr.onprogress = handleProgress;\n  }\n  try {\n    if (xhr.upload && this.hasListeners('progress')) {\n      xhr.upload.onprogress = handleProgress;\n    }\n  } catch(e) {\n    // Accessing xhr.upload fails in IE from a web worker, so just pretend it doesn't exist.\n    // Reported here:\n    // https://connect.microsoft.com/IE/feedback/details/837245/xmlhttprequest-upload-throws-invalid-argument-when-used-from-web-worker-context\n  }\n\n  // timeout\n  if (timeout && !this._timer) {\n    this._timer = setTimeout(function(){\n      self.timedout = true;\n      self.abort();\n    }, timeout);\n  }\n\n  // querystring\n  this._appendQueryString();\n\n  // initiate request\n  if (this.username && this.password) {\n    xhr.open(this.method, this.url, true, this.username, this.password);\n  } else {\n    xhr.open(this.method, this.url, true);\n  }\n\n  // CORS\n  if (this._withCredentials) xhr.withCredentials = true;\n\n  // body\n  if ('GET' != this.method && 'HEAD' != this.method && 'string' != typeof data && !this._isHost(data)) {\n    // serialize stuff\n    var contentType = this._header['content-type'];\n    var serialize = this._serializer || request.serialize[contentType ? contentType.split(';')[0] : ''];\n    if (!serialize && isJSON(contentType)) serialize = request.serialize['application/json'];\n    if (serialize) data = serialize(data);\n  }\n\n  // set header fields\n  for (var field in this.header) {\n    if (null == this.header[field]) continue;\n    xhr.setRequestHeader(field, this.header[field]);\n  }\n\n  if (this._responseType) {\n    xhr.responseType = this._responseType;\n  }\n\n  // send stuff\n  this.emit('request', this);\n\n  // IE11 xhr.send(undefined) sends 'undefined' string as POST payload (instead of nothing)\n  // We need null here if data is undefined\n  xhr.send(typeof data !== 'undefined' ? data : null);\n  return this;\n};\n\n\n/**\n * Expose `Request`.\n */\n\nrequest.Request = Request;\n\n/**\n * GET `url` with optional callback `fn(res)`.\n *\n * @param {String} url\n * @param {Mixed|Function} [data] or fn\n * @param {Function} [fn]\n * @return {Request}\n * @api public\n */\n\nrequest.get = function(url, data, fn){\n  var req = request('GET', url);\n  if ('function' == typeof data) fn = data, data = null;\n  if (data) req.query(data);\n  if (fn) req.end(fn);\n  return req;\n};\n\n/**\n * HEAD `url` with optional callback `fn(res)`.\n *\n * @param {String} url\n * @param {Mixed|Function} [data] or fn\n * @param {Function} [fn]\n * @return {Request}\n * @api public\n */\n\nrequest.head = function(url, data, fn){\n  var req = request('HEAD', url);\n  if ('function' == typeof data) fn = data, data = null;\n  if (data) req.send(data);\n  if (fn) req.end(fn);\n  return req;\n};\n\n/**\n * OPTIONS query to `url` with optional callback `fn(res)`.\n *\n * @param {String} url\n * @param {Mixed|Function} [data] or fn\n * @param {Function} [fn]\n * @return {Request}\n * @api public\n */\n\nrequest.options = function(url, data, fn){\n  var req = request('OPTIONS', url);\n  if ('function' == typeof data) fn = data, data = null;\n  if (data) req.send(data);\n  if (fn) req.end(fn);\n  return req;\n};\n\n/**\n * DELETE `url` with optional callback `fn(res)`.\n *\n * @param {String} url\n * @param {Function} [fn]\n * @return {Request}\n * @api public\n */\n\nfunction del(url, fn){\n  var req = request('DELETE', url);\n  if (fn) req.end(fn);\n  return req;\n};\n\nrequest['del'] = del;\nrequest['delete'] = del;\n\n/**\n * PATCH `url` with optional `data` and callback `fn(res)`.\n *\n * @param {String} url\n * @param {Mixed} [data]\n * @param {Function} [fn]\n * @return {Request}\n * @api public\n */\n\nrequest.patch = function(url, data, fn){\n  var req = request('PATCH', url);\n  if ('function' == typeof data) fn = data, data = null;\n  if (data) req.send(data);\n  if (fn) req.end(fn);\n  return req;\n};\n\n/**\n * POST `url` with optional `data` and callback `fn(res)`.\n *\n * @param {String} url\n * @param {Mixed} [data]\n * @param {Function} [fn]\n * @return {Request}\n * @api public\n */\n\nrequest.post = function(url, data, fn){\n  var req = request('POST', url);\n  if ('function' == typeof data) fn = data, data = null;\n  if (data) req.send(data);\n  if (fn) req.end(fn);\n  return req;\n};\n\n/**\n * PUT `url` with optional `data` and callback `fn(res)`.\n *\n * @param {String} url\n * @param {Mixed|Function} [data] or fn\n * @param {Function} [fn]\n * @return {Request}\n * @api public\n */\n\nrequest.put = function(url, data, fn){\n  var req = request('PUT', url);\n  if ('function' == typeof data) fn = data, data = null;\n  if (data) req.send(data);\n  if (fn) req.end(fn);\n  return req;\n};\n\n},{\"./is-object\":159,\"./request\":161,\"./request-base\":160,\"emitter\":162}],159:[function(require,module,exports){\n/**\n * Check if `obj` is an object.\n *\n * @param {Object} obj\n * @return {Boolean}\n * @api private\n */\n\nfunction isObject(obj) {\n  return null !== obj && 'object' === typeof obj;\n}\n\nmodule.exports = isObject;\n\n},{}],160:[function(require,module,exports){\n/**\n * Module of mixed-in functions shared between node and client code\n */\nvar isObject = require('./is-object');\n\n/**\n * Clear previous timeout.\n *\n * @return {Request} for chaining\n * @api public\n */\n\nexports.clearTimeout = function _clearTimeout(){\n  this._timeout = 0;\n  clearTimeout(this._timer);\n  return this;\n};\n\n/**\n * Override default response body parser\n *\n * This function will be called to convert incoming data into request.body\n *\n * @param {Function}\n * @api public\n */\n\nexports.parse = function parse(fn){\n  this._parser = fn;\n  return this;\n};\n\n/**\n * Override default request body serializer\n *\n * This function will be called to convert data set via .send or .attach into payload to send\n *\n * @param {Function}\n * @api public\n */\n\nexports.serialize = function serialize(fn){\n  this._serializer = fn;\n  return this;\n};\n\n/**\n * Set timeout to `ms`.\n *\n * @param {Number} ms\n * @return {Request} for chaining\n * @api public\n */\n\nexports.timeout = function timeout(ms){\n  this._timeout = ms;\n  return this;\n};\n\n/**\n * Promise support\n *\n * @param {Function} resolve\n * @param {Function} reject\n * @return {Request}\n */\n\nexports.then = function then(resolve, reject) {\n  if (!this._fullfilledPromise) {\n    var self = this;\n    this._fullfilledPromise = new Promise(function(innerResolve, innerReject){\n      self.end(function(err, res){\n        if (err) innerReject(err); else innerResolve(res);\n      });\n    });\n  }\n  return this._fullfilledPromise.then(resolve, reject);\n}\n\n/**\n * Allow for extension\n */\n\nexports.use = function use(fn) {\n  fn(this);\n  return this;\n}\n\n\n/**\n * Get request header `field`.\n * Case-insensitive.\n *\n * @param {String} field\n * @return {String}\n * @api public\n */\n\nexports.get = function(field){\n  return this._header[field.toLowerCase()];\n};\n\n/**\n * Get case-insensitive header `field` value.\n * This is a deprecated internal API. Use `.get(field)` instead.\n *\n * (getHeader is no longer used internally by the superagent code base)\n *\n * @param {String} field\n * @return {String}\n * @api private\n * @deprecated\n */\n\nexports.getHeader = exports.get;\n\n/**\n * Set header `field` to `val`, or multiple fields with one object.\n * Case-insensitive.\n *\n * Examples:\n *\n *      req.get('/')\n *        .set('Accept', 'application/json')\n *        .set('X-API-Key', 'foobar')\n *        .end(callback);\n *\n *      req.get('/')\n *        .set({ Accept: 'application/json', 'X-API-Key': 'foobar' })\n *        .end(callback);\n *\n * @param {String|Object} field\n * @param {String} val\n * @return {Request} for chaining\n * @api public\n */\n\nexports.set = function(field, val){\n  if (isObject(field)) {\n    for (var key in field) {\n      this.set(key, field[key]);\n    }\n    return this;\n  }\n  this._header[field.toLowerCase()] = val;\n  this.header[field] = val;\n  return this;\n};\n\n/**\n * Remove header `field`.\n * Case-insensitive.\n *\n * Example:\n *\n *      req.get('/')\n *        .unset('User-Agent')\n *        .end(callback);\n *\n * @param {String} field\n */\nexports.unset = function(field){\n  delete this._header[field.toLowerCase()];\n  delete this.header[field];\n  return this;\n};\n\n/**\n * Write the field `name` and `val` for \"multipart/form-data\"\n * request bodies.\n *\n * ``` js\n * request.post('/upload')\n *   .field('foo', 'bar')\n *   .end(callback);\n * ```\n *\n * @param {String} name\n * @param {String|Blob|File|Buffer|fs.ReadStream} val\n * @return {Request} for chaining\n * @api public\n */\nexports.field = function(name, val) {\n  this._getFormData().append(name, val);\n  return this;\n};\n\n/**\n * Abort the request, and clear potential timeout.\n *\n * @return {Request}\n * @api public\n */\nexports.abort = function(){\n  if (this._aborted) {\n    return this;\n  }\n  this._aborted = true;\n  this.xhr && this.xhr.abort(); // browser\n  this.req && this.req.abort(); // node\n  this.clearTimeout();\n  this.emit('abort');\n  return this;\n};\n\n/**\n * Enable transmission of cookies with x-domain requests.\n *\n * Note that for this to work the origin must not be\n * using \"Access-Control-Allow-Origin\" with a wildcard,\n * and also must set \"Access-Control-Allow-Credentials\"\n * to \"true\".\n *\n * @api public\n */\n\nexports.withCredentials = function(){\n  // This is browser-only functionality. Node side is no-op.\n  this._withCredentials = true;\n  return this;\n};\n\n/**\n * Set the max redirects to `n`. Does noting in browser XHR implementation.\n *\n * @param {Number} n\n * @return {Request} for chaining\n * @api public\n */\n\nexports.redirects = function(n){\n  this._maxRedirects = n;\n  return this;\n};\n\n/**\n * Convert to a plain javascript object (not JSON string) of scalar properties.\n * Note as this method is designed to return a useful non-this value,\n * it cannot be chained.\n *\n * @return {Object} describing method, url, and data of this request\n * @api public\n */\n\nexports.toJSON = function(){\n  return {\n    method: this.method,\n    url: this.url,\n    data: this._data,\n    headers: this._header\n  };\n};\n\n/**\n * Check if `obj` is a host object,\n * we don't want to serialize these :)\n *\n * TODO: future proof, move to compoent land\n *\n * @param {Object} obj\n * @return {Boolean}\n * @api private\n */\n\nexports._isHost = function _isHost(obj) {\n  var str = {}.toString.call(obj);\n\n  switch (str) {\n    case '[object File]':\n    case '[object Blob]':\n    case '[object FormData]':\n      return true;\n    default:\n      return false;\n  }\n}\n\n/**\n * Send `data` as the request body, defaulting the `.type()` to \"json\" when\n * an object is given.\n *\n * Examples:\n *\n *       // manual json\n *       request.post('/user')\n *         .type('json')\n *         .send('{\"name\":\"tj\"}')\n *         .end(callback)\n *\n *       // auto json\n *       request.post('/user')\n *         .send({ name: 'tj' })\n *         .end(callback)\n *\n *       // manual x-www-form-urlencoded\n *       request.post('/user')\n *         .type('form')\n *         .send('name=tj')\n *         .end(callback)\n *\n *       // auto x-www-form-urlencoded\n *       request.post('/user')\n *         .type('form')\n *         .send({ name: 'tj' })\n *         .end(callback)\n *\n *       // defaults to x-www-form-urlencoded\n *      request.post('/user')\n *        .send('name=tobi')\n *        .send('species=ferret')\n *        .end(callback)\n *\n * @param {String|Object} data\n * @return {Request} for chaining\n * @api public\n */\n\nexports.send = function(data){\n  var obj = isObject(data);\n  var type = this._header['content-type'];\n\n  // merge\n  if (obj && isObject(this._data)) {\n    for (var key in data) {\n      this._data[key] = data[key];\n    }\n  } else if ('string' == typeof data) {\n    // default to x-www-form-urlencoded\n    if (!type) this.type('form');\n    type = this._header['content-type'];\n    if ('application/x-www-form-urlencoded' == type) {\n      this._data = this._data\n        ? this._data + '&' + data\n        : data;\n    } else {\n      this._data = (this._data || '') + data;\n    }\n  } else {\n    this._data = data;\n  }\n\n  if (!obj || this._isHost(data)) return this;\n\n  // default to json\n  if (!type) this.type('json');\n  return this;\n};\n\n},{\"./is-object\":159}],161:[function(require,module,exports){\n// The node and browser modules expose versions of this with the\n// appropriate constructor function bound as first argument\n/**\n * Issue a request:\n *\n * Examples:\n *\n *    request('GET', '/users').end(callback)\n *    request('/users').end(callback)\n *    request('/users', callback)\n *\n * @param {String} method\n * @param {String|Function} url or callback\n * @return {Request}\n * @api public\n */\n\nfunction request(RequestConstructor, method, url) {\n  // callback\n  if ('function' == typeof url) {\n    return new RequestConstructor('GET', method).end(url);\n  }\n\n  // url first\n  if (2 == arguments.length) {\n    return new RequestConstructor('GET', method);\n  }\n\n  return new RequestConstructor(method, url);\n}\n\nmodule.exports = request;\n\n},{}],162:[function(require,module,exports){\n\n/**\n * Expose `Emitter`.\n */\n\nif (typeof module !== 'undefined') {\n  module.exports = Emitter;\n}\n\n/**\n * Initialize a new `Emitter`.\n *\n * @api public\n */\n\nfunction Emitter(obj) {\n  if (obj) return mixin(obj);\n};\n\n/**\n * Mixin the emitter properties.\n *\n * @param {Object} obj\n * @return {Object}\n * @api private\n */\n\nfunction mixin(obj) {\n  for (var key in Emitter.prototype) {\n    obj[key] = Emitter.prototype[key];\n  }\n  return obj;\n}\n\n/**\n * Listen on the given `event` with `fn`.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n */\n\nEmitter.prototype.on =\nEmitter.prototype.addEventListener = function(event, fn){\n  this._callbacks = this._callbacks || {};\n  (this._callbacks['$' + event] = this._callbacks['$' + event] || [])\n    .push(fn);\n  return this;\n};\n\n/**\n * Adds an `event` listener that will be invoked a single\n * time then automatically removed.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n */\n\nEmitter.prototype.once = function(event, fn){\n  function on() {\n    this.off(event, on);\n    fn.apply(this, arguments);\n  }\n\n  on.fn = fn;\n  this.on(event, on);\n  return this;\n};\n\n/**\n * Remove the given callback for `event` or all\n * registered callbacks.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n */\n\nEmitter.prototype.off =\nEmitter.prototype.removeListener =\nEmitter.prototype.removeAllListeners =\nEmitter.prototype.removeEventListener = function(event, fn){\n  this._callbacks = this._callbacks || {};\n\n  // all\n  if (0 == arguments.length) {\n    this._callbacks = {};\n    return this;\n  }\n\n  // specific event\n  var callbacks = this._callbacks['$' + event];\n  if (!callbacks) return this;\n\n  // remove all handlers\n  if (1 == arguments.length) {\n    delete this._callbacks['$' + event];\n    return this;\n  }\n\n  // remove specific handler\n  var cb;\n  for (var i = 0; i < callbacks.length; i++) {\n    cb = callbacks[i];\n    if (cb === fn || cb.fn === fn) {\n      callbacks.splice(i, 1);\n      break;\n    }\n  }\n  return this;\n};\n\n/**\n * Emit `event` with the given args.\n *\n * @param {String} event\n * @param {Mixed} ...\n * @return {Emitter}\n */\n\nEmitter.prototype.emit = function(event){\n  this._callbacks = this._callbacks || {};\n  var args = [].slice.call(arguments, 1)\n    , callbacks = this._callbacks['$' + event];\n\n  if (callbacks) {\n    callbacks = callbacks.slice(0);\n    for (var i = 0, len = callbacks.length; i < len; ++i) {\n      callbacks[i].apply(this, args);\n    }\n  }\n\n  return this;\n};\n\n/**\n * Return array of callbacks for `event`.\n *\n * @param {String} event\n * @return {Array}\n * @api public\n */\n\nEmitter.prototype.listeners = function(event){\n  this._callbacks = this._callbacks || {};\n  return this._callbacks['$' + event] || [];\n};\n\n/**\n * Check if this emitter has `event` handlers.\n *\n * @param {String} event\n * @return {Boolean}\n * @api public\n */\n\nEmitter.prototype.hasListeners = function(event){\n  return !! this.listeners(event).length;\n};\n\n},{}]},{},[1])(1)\n});\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJpbmRleC5qcyIsImxpYi9hdXRoLmpzIiwibGliL2NsaWVudC5qcyIsImxpYi9oZWxwZXJzLmpzIiwibGliL2h0dHAuanMiLCJsaWIvcmVzb2x2ZXIuanMiLCJsaWIvc2NoZW1hLW1hcmt1cC5qcyIsImxpYi9zcGVjLWNvbnZlcnRlci5qcyIsImxpYi90eXBlcy9tb2RlbC5qcyIsImxpYi90eXBlcy9vcGVyYXRpb24uanMiLCJsaWIvdHlwZXMvb3BlcmF0aW9uR3JvdXAuanMiLCJub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvcHJvY2Vzcy9icm93c2VyLmpzIiwibm9kZV9tb2R1bGVzL2J0b2EvaW5kZXguanMiLCJub2RlX21vZHVsZXMvYnVmZmVyL2luZGV4LmpzIiwibm9kZV9tb2R1bGVzL2J1ZmZlci9ub2RlX21vZHVsZXMvYmFzZTY0LWpzL2xpYi9iNjQuanMiLCJub2RlX21vZHVsZXMvYnVmZmVyL25vZGVfbW9kdWxlcy9pZWVlNzU0L2luZGV4LmpzIiwibm9kZV9tb2R1bGVzL2J1ZmZlci9ub2RlX21vZHVsZXMvaXMtYXJyYXkvaW5kZXguanMiLCJub2RlX21vZHVsZXMvY29va2llamFyL2Nvb2tpZWphci5qcyIsIm5vZGVfbW9kdWxlcy9qcy15YW1sL2luZGV4LmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwuanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC9jb21tb24uanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC9kdW1wZXIuanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC9leGNlcHRpb24uanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC9sb2FkZXIuanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC9tYXJrLmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvc2NoZW1hLmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvc2NoZW1hL2NvcmUuanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC9zY2hlbWEvZGVmYXVsdF9mdWxsLmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvc2NoZW1hL2RlZmF1bHRfc2FmZS5qcyIsIm5vZGVfbW9kdWxlcy9qcy15YW1sL2xpYi9qcy15YW1sL3NjaGVtYS9mYWlsc2FmZS5qcyIsIm5vZGVfbW9kdWxlcy9qcy15YW1sL2xpYi9qcy15YW1sL3NjaGVtYS9qc29uLmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvdHlwZS5qcyIsIm5vZGVfbW9kdWxlcy9qcy15YW1sL2xpYi9qcy15YW1sL3R5cGUvYmluYXJ5LmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvdHlwZS9ib29sLmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvdHlwZS9mbG9hdC5qcyIsIm5vZGVfbW9kdWxlcy9qcy15YW1sL2xpYi9qcy15YW1sL3R5cGUvaW50LmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvdHlwZS9qcy9mdW5jdGlvbi5qcyIsIm5vZGVfbW9kdWxlcy9qcy15YW1sL2xpYi9qcy15YW1sL3R5cGUvanMvcmVnZXhwLmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvdHlwZS9qcy91bmRlZmluZWQuanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC90eXBlL21hcC5qcyIsIm5vZGVfbW9kdWxlcy9qcy15YW1sL2xpYi9qcy15YW1sL3R5cGUvbWVyZ2UuanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC90eXBlL251bGwuanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC90eXBlL29tYXAuanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC90eXBlL3BhaXJzLmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvdHlwZS9zZXEuanMiLCJub2RlX21vZHVsZXMvanMteWFtbC9saWIvanMteWFtbC90eXBlL3NldC5qcyIsIm5vZGVfbW9kdWxlcy9qcy15YW1sL2xpYi9qcy15YW1sL3R5cGUvc3RyLmpzIiwibm9kZV9tb2R1bGVzL2pzLXlhbWwvbGliL2pzLXlhbWwvdHlwZS90aW1lc3RhbXAuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9hcnJheS9pbmRleE9mLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvYXJyYXkvbGFzdC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2NoYWluL2xvZGFzaC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2NvbGxlY3Rpb24vZWFjaC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2NvbGxlY3Rpb24vZmluZC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2NvbGxlY3Rpb24vZm9yRWFjaC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2NvbGxlY3Rpb24vaW5jbHVkZXMuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9jb2xsZWN0aW9uL21hcC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2RhdGUvbm93LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvZnVuY3Rpb24vYmluZC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2Z1bmN0aW9uL3Jlc3RQYXJhbS5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL0xhenlXcmFwcGVyLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvTG9kYXNoV3JhcHBlci5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2FycmF5Q29weS5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2FycmF5RWFjaC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2FycmF5TWFwLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYXJyYXlTb21lLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYmFzZUFzc2lnbi5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VDYWxsYmFjay5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VDbG9uZS5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VDb3B5LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYmFzZUNyZWF0ZS5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VFYWNoLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYmFzZUZpbmQuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iYXNlRmluZEluZGV4LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYmFzZUZvci5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VGb3JJbi5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VGb3JPd24uanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iYXNlR2V0LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYmFzZUluZGV4T2YuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iYXNlSXNFcXVhbC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VJc0VxdWFsRGVlcC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VJc01hdGNoLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYmFzZUxvZGFzaC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VNYXAuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iYXNlTWF0Y2hlcy5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2Jhc2VNYXRjaGVzUHJvcGVydHkuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iYXNlUHJvcGVydHkuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iYXNlUHJvcGVydHlEZWVwLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYmFzZVNldERhdGEuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iYXNlU2xpY2UuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iYXNlVG9TdHJpbmcuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iYXNlVmFsdWVzLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYmluYXJ5SW5kZXguanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9iaW5hcnlJbmRleEJ5LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYmluZENhbGxiYWNrLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvYnVmZmVyQ2xvbmUuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9jb21wb3NlQXJncy5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2NvbXBvc2VBcmdzUmlnaHQuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9jcmVhdGVCYXNlRWFjaC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2NyZWF0ZUJhc2VGb3IuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9jcmVhdGVCaW5kV3JhcHBlci5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2NyZWF0ZUN0b3JXcmFwcGVyLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvY3JlYXRlRmluZC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2NyZWF0ZUZvckVhY2guanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9jcmVhdGVIeWJyaWRXcmFwcGVyLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvY3JlYXRlUGFydGlhbFdyYXBwZXIuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9jcmVhdGVXcmFwcGVyLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvZXF1YWxBcnJheXMuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9lcXVhbEJ5VGFnLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvZXF1YWxPYmplY3RzLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvZ2V0RGF0YS5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2dldEZ1bmNOYW1lLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvZ2V0TGVuZ3RoLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvZ2V0TWF0Y2hEYXRhLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvZ2V0TmF0aXZlLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvaW5kZXhPZk5hTi5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2luaXRDbG9uZUFycmF5LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvaW5pdENsb25lQnlUYWcuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9pbml0Q2xvbmVPYmplY3QuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9pc0FycmF5TGlrZS5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2lzSG9zdE9iamVjdC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2lzSW5kZXguanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9pc0l0ZXJhdGVlQ2FsbC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2lzS2V5LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvaXNMYXppYWJsZS5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL2lzTGVuZ3RoLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvaXNPYmplY3RMaWtlLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvaXNTdHJpY3RDb21wYXJhYmxlLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvbWVyZ2VEYXRhLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvbWV0YU1hcC5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL3JlYWxOYW1lcy5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL3Jlb3JkZXIuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9yZXBsYWNlSG9sZGVycy5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL3NldERhdGEuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9pbnRlcm5hbC9zaGltS2V5cy5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2ludGVybmFsL3RvT2JqZWN0LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvdG9QYXRoLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvaW50ZXJuYWwvd3JhcHBlckNsb25lLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvbGFuZy9jbG9uZURlZXAuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9sYW5nL2lzQXJndW1lbnRzLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvbGFuZy9pc0FycmF5LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvbGFuZy9pc0VtcHR5LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvbGFuZy9pc0Z1bmN0aW9uLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvbGFuZy9pc05hdGl2ZS5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L2xhbmcvaXNPYmplY3QuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9sYW5nL2lzUGxhaW5PYmplY3QuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9sYW5nL2lzU3RyaW5nLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvbGFuZy9pc1R5cGVkQXJyYXkuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9sYW5nL2lzVW5kZWZpbmVkLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvb2JqZWN0L2tleXMuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC9vYmplY3Qva2V5c0luLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvb2JqZWN0L3BhaXJzLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvb2JqZWN0L3ZhbHVlcy5qcyIsIm5vZGVfbW9kdWxlcy9sb2Rhc2gtY29tcGF0L3N1cHBvcnQuanMiLCJub2RlX21vZHVsZXMvbG9kYXNoLWNvbXBhdC91dGlsaXR5L2lkZW50aXR5LmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvdXRpbGl0eS9ub29wLmpzIiwibm9kZV9tb2R1bGVzL2xvZGFzaC1jb21wYXQvdXRpbGl0eS9wcm9wZXJ0eS5qcyIsIm5vZGVfbW9kdWxlcy9xL3EuanMiLCJub2RlX21vZHVsZXMvc3VwZXJhZ2VudC9saWIvY2xpZW50LmpzIiwibm9kZV9tb2R1bGVzL3N1cGVyYWdlbnQvbGliL2lzLW9iamVjdC5qcyIsIm5vZGVfbW9kdWxlcy9zdXBlcmFnZW50L2xpYi9yZXF1ZXN0LWJhc2UuanMiLCJub2RlX21vZHVsZXMvc3VwZXJhZ2VudC9saWIvcmVxdWVzdC5qcyIsIm5vZGVfbW9kdWxlcy9zdXBlcmFnZW50L25vZGVfbW9kdWxlcy9jb21wb25lbnQtZW1pdHRlci9pbmRleC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDM0NBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNySkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3ZFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN1dBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOTRCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3Z0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzNvQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM5Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwckNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMURBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3JCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3L0NBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUhBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3BGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNqQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDclFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDUEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdkNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNseUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDM0NBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNsakRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4R0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNqQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3REE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdklBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3hLQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNURBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNSQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNaQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2xDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDeEZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzdIQTtBQUNBOztBQ0RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4REE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDeEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4REE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMxREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzFCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3BCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3ZCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25CQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbkNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3BJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdkJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDZkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3ZCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDakJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNqQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2pCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM5QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDM0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdkdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcERBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDVkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3ZCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9CQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNoQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2pCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaENBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDYkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN0QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdkNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3ZCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2xDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMvQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDM0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDckNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbEhBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN0RkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbkRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2hEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25FQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNmQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNmQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNoQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3ZCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDckZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2ZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3JCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDM0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDWkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDZkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNaQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ0pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMxQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzFCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzVCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNsQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2REE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNsQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9DQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdENBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDakRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNoRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25DQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3JCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzlDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3hJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNqQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDakNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2hHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbkJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuZ0VBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaDlCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzVkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2hDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIid1c2Ugc3RyaWN0JztcblxudmFyIGF1dGggPSByZXF1aXJlKCcuL2xpYi9hdXRoJyk7XG52YXIgaGVscGVycyA9IHJlcXVpcmUoJy4vbGliL2hlbHBlcnMnKTtcbnZhciBTd2FnZ2VyQ2xpZW50ID0gcmVxdWlyZSgnLi9saWIvY2xpZW50Jyk7XG52YXIgZGVwcmVjYXRpb25XcmFwcGVyID0gZnVuY3Rpb24gKHVybCwgb3B0aW9ucykge1xuICBoZWxwZXJzLmxvZygnVGhpcyBpcyBkZXByZWNhdGVkLCB1c2UgXCJuZXcgU3dhZ2dlckNsaWVudFwiIGluc3RlYWQuJyk7XG5cbiAgcmV0dXJuIG5ldyBTd2FnZ2VyQ2xpZW50KHVybCwgb3B0aW9ucyk7XG59O1xuXG4vKiBIZXJlIGZvciBJRTggU3VwcG9ydCAqL1xuaWYgKCFBcnJheS5wcm90b3R5cGUuaW5kZXhPZikge1xuICBBcnJheS5wcm90b3R5cGUuaW5kZXhPZiA9IGZ1bmN0aW9uKG9iaiwgc3RhcnQpIHtcbiAgICBmb3IgKHZhciBpID0gKHN0YXJ0IHx8IDApLCBqID0gdGhpcy5sZW5ndGg7IGkgPCBqOyBpKyspIHtcbiAgICAgIGlmICh0aGlzW2ldID09PSBvYmopIHsgcmV0dXJuIGk7IH1cbiAgICB9XG4gICAgcmV0dXJuIC0xO1xuICB9O1xufVxuXG4vKiBIZXJlIGZvciBJRTggU3VwcG9ydCAqL1xuaWYgKCFTdHJpbmcucHJvdG90eXBlLnRyaW0pIHtcbiAgU3RyaW5nLnByb3RvdHlwZS50cmltID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiB0aGlzLnJlcGxhY2UoL15cXHMrfFxccyskL2csICcnKTtcbiAgfTtcbn1cblxuLyogSGVyZSBmb3Igbm9kZSAxMC54IHN1cHBvcnQgKi9cbmlmICghU3RyaW5nLnByb3RvdHlwZS5lbmRzV2l0aCkge1xuICBTdHJpbmcucHJvdG90eXBlLmVuZHNXaXRoID0gZnVuY3Rpb24oc3VmZml4KSB7XG4gICAgcmV0dXJuIHRoaXMuaW5kZXhPZihzdWZmaXgsIHRoaXMubGVuZ3RoIC0gc3VmZml4Lmxlbmd0aCkgIT09IC0xO1xuICB9O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IFN3YWdnZXJDbGllbnQ7XG5cblN3YWdnZXJDbGllbnQuQXBpS2V5QXV0aG9yaXphdGlvbiA9IGF1dGguQXBpS2V5QXV0aG9yaXphdGlvbjtcblN3YWdnZXJDbGllbnQuUGFzc3dvcmRBdXRob3JpemF0aW9uID0gYXV0aC5QYXNzd29yZEF1dGhvcml6YXRpb247XG5Td2FnZ2VyQ2xpZW50LkNvb2tpZUF1dGhvcml6YXRpb24gPSBhdXRoLkNvb2tpZUF1dGhvcml6YXRpb247XG5Td2FnZ2VyQ2xpZW50LlN3YWdnZXJBcGkgPSBkZXByZWNhdGlvbldyYXBwZXI7XG5Td2FnZ2VyQ2xpZW50LlN3YWdnZXJDbGllbnQgPSBkZXByZWNhdGlvbldyYXBwZXI7XG5Td2FnZ2VyQ2xpZW50LlNjaGVtYU1hcmt1cCA9IHJlcXVpcmUoJy4vbGliL3NjaGVtYS1tYXJrdXAnKTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIGhlbHBlcnMgPSByZXF1aXJlKCcuL2hlbHBlcnMnKTtcbnZhciBidG9hID0gcmVxdWlyZSgnYnRvYScpOyAvLyBqc2hpbnQgaWdub3JlOmxpbmVcbnZhciBDb29raWVKYXIgPSByZXF1aXJlKCdjb29raWVqYXInKS5Db29raWVKYXI7XG52YXIgXyA9IHtcbiAgZWFjaDogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9jb2xsZWN0aW9uL2VhY2gnKSxcbiAgaW5jbHVkZXM6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvY29sbGVjdGlvbi9pbmNsdWRlcycpLFxuICBpc09iamVjdDogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9sYW5nL2lzT2JqZWN0JyksXG4gIGlzQXJyYXk6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9pc0FycmF5Jylcbn07XG5cbi8qKlxuICogU3dhZ2dlckF1dGhvcml6YXRpb25zIGFwcGxpZXMgdGhlIGNvcnJlY3QgYXV0aG9yaXphdGlvbiB0byBhbiBvcGVyYXRpb24gYmVpbmcgZXhlY3V0ZWRcbiAqL1xudmFyIFN3YWdnZXJBdXRob3JpemF0aW9ucyA9IG1vZHVsZS5leHBvcnRzLlN3YWdnZXJBdXRob3JpemF0aW9ucyA9IGZ1bmN0aW9uIChhdXRoeikge1xuICB0aGlzLmF1dGh6ID0gYXV0aHogfHwge307XG59O1xuXG4vKipcbiAqIEFkZCBhdXRocyB0byB0aGUgaGFzaFxuICogV2lsbCBvdmVyd3JpdGUgYW55IGV4aXN0aW5nXG4gKlxuICovXG5Td2FnZ2VyQXV0aG9yaXphdGlvbnMucHJvdG90eXBlLmFkZCA9IGZ1bmN0aW9uIChuYW1lLCBhdXRoKSB7XG4gIGlmKF8uaXNPYmplY3QobmFtZSkpIHtcbiAgICBmb3IgKHZhciBrZXkgaW4gbmFtZSkge1xuICAgICAgdGhpcy5hdXRoeltrZXldID0gbmFtZVtrZXldO1xuICAgIH1cbiAgfSBlbHNlIGlmKHR5cGVvZiBuYW1lID09PSAnc3RyaW5nJyApe1xuICAgIHRoaXMuYXV0aHpbbmFtZV0gPSBhdXRoO1xuICB9XG5cbiAgcmV0dXJuIGF1dGg7XG59O1xuXG5Td2FnZ2VyQXV0aG9yaXphdGlvbnMucHJvdG90eXBlLnJlbW92ZSA9IGZ1bmN0aW9uIChuYW1lKSB7XG4gIHJldHVybiBkZWxldGUgdGhpcy5hdXRoeltuYW1lXTtcbn07XG5cblN3YWdnZXJBdXRob3JpemF0aW9ucy5wcm90b3R5cGUuYXBwbHkgPSBmdW5jdGlvbiAob2JqLCBzZWN1cml0aWVzKSB7XG4gIHZhciBzdGF0dXMgPSB0cnVlO1xuICB2YXIgYXBwbHlBbGwgPSAhc2VjdXJpdGllcztcbiAgdmFyIGZsYXR0ZW5lZFNlY3VyaXRpZXMgPSBbXTtcblxuICAvLyBmYXZvciB0aGUgb2JqZWN0LWxldmVsIGF1dGhvcml6YXRpb25zIG92ZXIgZ2xvYmFsXG4gIHZhciBhdXRoeiA9IG9iai5jbGllbnRBdXRob3JpemF0aW9ucyB8fCB0aGlzLmF1dGh6O1xuXG4gIC8vIFNlY3VyaXRpZXMgY291bGQgYmUgWyB7fSBdXG4gIF8uZWFjaChzZWN1cml0aWVzLCBmdW5jdGlvbiAob2JqLCBrZXkpIHtcblxuICAgIC8vIE1ha2Ugc3VyZSB3ZSBhY2NvdW50IGZvciBzZWN1cml0aWVzIGJlaW5nIFsgc3RyIF1cbiAgICBpZih0eXBlb2Yga2V5ID09PSAnc3RyaW5nJykge1xuICAgICAgZmxhdHRlbmVkU2VjdXJpdGllcy5wdXNoKGtleSk7XG4gICAgfVxuXG4gICAgLy8gRmxhdHRlbiBrZXlzIGluIHRvIG91ciBhcnJheVxuICAgIF8uZWFjaChvYmosIGZ1bmN0aW9uICh2YWwsIGtleSkge1xuICAgICAgZmxhdHRlbmVkU2VjdXJpdGllcy5wdXNoKGtleSk7XG4gICAgfSk7XG4gIH0pO1xuXG4gIF8uZWFjaChhdXRoeiwgZnVuY3Rpb24gKGF1dGgsIGF1dGhOYW1lKSB7XG4gICAgaWYoYXBwbHlBbGwgfHwgXy5pbmNsdWRlcyhmbGF0dGVuZWRTZWN1cml0aWVzLCBhdXRoTmFtZSkpIHtcbiAgICAgIHZhciBuZXdTdGF0dXMgPSBhdXRoLmFwcGx5KG9iaik7XG4gICAgICBzdGF0dXMgPSBzdGF0dXMgJiYgISFuZXdTdGF0dXM7IC8vIGxvZ2ljYWwgT1JzIHJlZ2FyZGluZyBzdGF0dXNcbiAgICB9XG4gIH0pO1xuXG4gIHJldHVybiBzdGF0dXM7XG59O1xuXG4vKipcbiAqIEFwaUtleUF1dGhvcml6YXRpb24gYWxsb3dzIGEgcXVlcnkgcGFyYW0gb3IgaGVhZGVyIHRvIGJlIGluamVjdGVkXG4gKi9cbnZhciBBcGlLZXlBdXRob3JpemF0aW9uID0gbW9kdWxlLmV4cG9ydHMuQXBpS2V5QXV0aG9yaXphdGlvbiA9IGZ1bmN0aW9uIChuYW1lLCB2YWx1ZSwgdHlwZSkge1xuICB0aGlzLm5hbWUgPSBuYW1lO1xuICB0aGlzLnZhbHVlID0gdmFsdWU7XG4gIHRoaXMudHlwZSA9IHR5cGU7XG59O1xuXG5BcGlLZXlBdXRob3JpemF0aW9uLnByb3RvdHlwZS5hcHBseSA9IGZ1bmN0aW9uIChvYmopIHtcbiAgaWYgKHRoaXMudHlwZSA9PT0gJ3F1ZXJ5Jykge1xuICAgIC8vIHNlZSBpZiBhbHJlYWR5IGFwcGxpZWQuICBJZiBzbywgZG9uJ3QgZG8gaXQgYWdhaW5cblxuICAgIHZhciBxcDtcbiAgICBpZiAob2JqLnVybC5pbmRleE9mKCc/JykgPiAwKSB7XG4gICAgICBxcCA9IG9iai51cmwuc3Vic3RyaW5nKG9iai51cmwuaW5kZXhPZignPycpICsgMSk7XG4gICAgICB2YXIgcGFydHMgPSBxcC5zcGxpdCgnJicpO1xuICAgICAgaWYocGFydHMgJiYgcGFydHMubGVuZ3RoID4gMCkge1xuICAgICAgICBmb3IodmFyIGkgPSAwOyBpIDwgcGFydHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICB2YXIga3YgPSBwYXJ0c1tpXS5zcGxpdCgnPScpO1xuICAgICAgICAgIGlmKGt2ICYmIGt2Lmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIGlmIChrdlswXSA9PT0gdGhpcy5uYW1lKSB7XG4gICAgICAgICAgICAgIC8vIHNraXAgaXRcbiAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChvYmoudXJsLmluZGV4T2YoJz8nKSA+IDApIHtcbiAgICAgIG9iai51cmwgPSBvYmoudXJsICsgJyYnICsgdGhpcy5uYW1lICsgJz0nICsgdGhpcy52YWx1ZTtcbiAgICB9IGVsc2Uge1xuICAgICAgb2JqLnVybCA9IG9iai51cmwgKyAnPycgKyB0aGlzLm5hbWUgKyAnPScgKyB0aGlzLnZhbHVlO1xuICAgIH1cblxuICAgIHJldHVybiB0cnVlO1xuICB9IGVsc2UgaWYgKHRoaXMudHlwZSA9PT0gJ2hlYWRlcicpIHtcbiAgICBpZih0eXBlb2Ygb2JqLmhlYWRlcnNbdGhpcy5uYW1lXSA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIG9iai5oZWFkZXJzW3RoaXMubmFtZV0gPSB0aGlzLnZhbHVlO1xuICAgIH1cblxuICAgIHJldHVybiB0cnVlO1xuICB9XG59O1xuXG52YXIgQ29va2llQXV0aG9yaXphdGlvbiA9IG1vZHVsZS5leHBvcnRzLkNvb2tpZUF1dGhvcml6YXRpb24gPSBmdW5jdGlvbiAoY29va2llKSB7XG4gIHRoaXMuY29va2llID0gY29va2llO1xufTtcblxuQ29va2llQXV0aG9yaXphdGlvbi5wcm90b3R5cGUuYXBwbHkgPSBmdW5jdGlvbiAob2JqKSB7XG4gIG9iai5jb29raWVKYXIgPSBvYmouY29va2llSmFyIHx8IG5ldyBDb29raWVKYXIoKTtcbiAgb2JqLmNvb2tpZUphci5zZXRDb29raWUodGhpcy5jb29raWUpO1xuXG4gIHJldHVybiB0cnVlO1xufTtcblxuLyoqXG4gKiBQYXNzd29yZCBBdXRob3JpemF0aW9uIGlzIGEgYmFzaWMgYXV0aCBpbXBsZW1lbnRhdGlvblxuICovXG52YXIgUGFzc3dvcmRBdXRob3JpemF0aW9uID0gbW9kdWxlLmV4cG9ydHMuUGFzc3dvcmRBdXRob3JpemF0aW9uID0gZnVuY3Rpb24gKHVzZXJuYW1lLCBwYXNzd29yZCkge1xuICBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PT0gMykge1xuICAgIGhlbHBlcnMubG9nKCdQYXNzd29yZEF1dGhvcml6YXRpb246IHRoZSBcXCduYW1lXFwnIGFyZ3VtZW50IGhhcyBiZWVuIHJlbW92ZWQsIHBhc3Mgb25seSB1c2VybmFtZSBhbmQgcGFzc3dvcmQnKTtcbiAgICB1c2VybmFtZSA9IGFyZ3VtZW50c1sxXTtcbiAgICBwYXNzd29yZCA9IGFyZ3VtZW50c1syXTtcbiAgfVxuICB0aGlzLnVzZXJuYW1lID0gdXNlcm5hbWU7XG4gIHRoaXMucGFzc3dvcmQgPSBwYXNzd29yZDtcbn07XG5cblBhc3N3b3JkQXV0aG9yaXphdGlvbi5wcm90b3R5cGUuYXBwbHkgPSBmdW5jdGlvbiAob2JqKSB7XG4gIGlmKHR5cGVvZiBvYmouaGVhZGVycy5BdXRob3JpemF0aW9uID09PSAndW5kZWZpbmVkJykge1xuICAgIG9iai5oZWFkZXJzLkF1dGhvcml6YXRpb24gPSAnQmFzaWMgJyArIGJ0b2EodGhpcy51c2VybmFtZSArICc6JyArIHRoaXMucGFzc3dvcmQpO1xuICB9XG5cbiAgcmV0dXJuIHRydWU7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgXyA9IHtcbiAgYmluZDogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9mdW5jdGlvbi9iaW5kJyksXG4gIGNsb25lRGVlcDogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9sYW5nL2Nsb25lRGVlcCcpLFxuICBmaW5kOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2NvbGxlY3Rpb24vZmluZCcpLFxuICBmb3JFYWNoOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2NvbGxlY3Rpb24vZm9yRWFjaCcpLFxuICBpbmRleE9mOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2FycmF5L2luZGV4T2YnKSxcbiAgaXNBcnJheTogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9sYW5nL2lzQXJyYXknKSxcbiAgaXNPYmplY3Q6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9pc09iamVjdCcpLFxuICBpc0Z1bmN0aW9uOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvaXNGdW5jdGlvbicpLFxuICBpc1BsYWluT2JqZWN0OiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvaXNQbGFpbk9iamVjdCcpLFxuICBpc1VuZGVmaW5lZDogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9sYW5nL2lzVW5kZWZpbmVkJylcbn07XG52YXIgYXV0aCA9IHJlcXVpcmUoJy4vYXV0aCcpO1xudmFyIGhlbHBlcnMgPSByZXF1aXJlKCcuL2hlbHBlcnMnKTtcbnZhciBNb2RlbCA9IHJlcXVpcmUoJy4vdHlwZXMvbW9kZWwnKTtcbnZhciBPcGVyYXRpb24gPSByZXF1aXJlKCcuL3R5cGVzL29wZXJhdGlvbicpO1xudmFyIE9wZXJhdGlvbkdyb3VwID0gcmVxdWlyZSgnLi90eXBlcy9vcGVyYXRpb25Hcm91cCcpO1xudmFyIFJlc29sdmVyID0gcmVxdWlyZSgnLi9yZXNvbHZlcicpO1xudmFyIFN3YWdnZXJIdHRwID0gcmVxdWlyZSgnLi9odHRwJyk7XG52YXIgU3dhZ2dlclNwZWNDb252ZXJ0ZXIgPSByZXF1aXJlKCcuL3NwZWMtY29udmVydGVyJyk7XG52YXIgUSA9IHJlcXVpcmUoJ3EnKTtcblxuLy8gV2UgaGF2ZSB0byBrZWVwIHRyYWNrIG9mIHRoZSBmdW5jdGlvbi9wcm9wZXJ0eSBuYW1lcyB0byBhdm9pZCBjb2xsaXNpb25zIGZvciB0YWcgbmFtZXMgd2hpY2ggYXJlIHVzZWQgdG8gYWxsb3cgdGhlXG4vLyBmb2xsb3dpbmcgdXNhZ2U6ICdjbGllbnQue3RhZ05hbWV9J1xudmFyIHJlc2VydmVkQ2xpZW50VGFncyA9IFtcbiAgJ2FwaXMnLFxuICAnYXV0aG9yaXphdGlvblNjaGVtZScsXG4gICdhdXRob3JpemF0aW9ucycsXG4gICdiYXNlUGF0aCcsXG4gICdidWlsZCcsXG4gICdidWlsZEZyb20xXzFTcGVjJyxcbiAgJ2J1aWxkRnJvbTFfMlNwZWMnLFxuICAnYnVpbGRGcm9tU3BlYycsXG4gICdjbGllbnRBdXRob3JpemF0aW9ucycsXG4gICdjb252ZXJ0SW5mbycsXG4gICdkZWJ1ZycsXG4gICdkZWZhdWx0RXJyb3JDYWxsYmFjaycsXG4gICdkZWZhdWx0U3VjY2Vzc0NhbGxiYWNrJyxcbiAgJ2VuYWJsZUNvb2tpZXMnLFxuICAnZmFpbCcsXG4gICdmYWlsdXJlJyxcbiAgJ2ZpbmlzaCcsXG4gICdoZWxwJyxcbiAgJ2hvc3QnLFxuICAnaWRGcm9tT3AnLFxuICAnaW5mbycsXG4gICdpbml0aWFsaXplJyxcbiAgJ2lzQnVpbHQnLFxuICAnaXNWYWxpZCcsXG4gICdtb2RlbFByb3BlcnR5TWFjcm8nLFxuICAnbW9kZWxzJyxcbiAgJ21vZGVsc0FycmF5JyxcbiAgJ29wdGlvbnMnLFxuICAncGFyYW1ldGVyTWFjcm8nLFxuICAncGFyc2VVcmknLFxuICAncHJvZ3Jlc3MnLFxuICAncmVzb3VyY2VDb3VudCcsXG4gICdzYW1wbGVNb2RlbHMnLFxuICAnc2VsZlJlZmxlY3QnLFxuICAnc2V0Q29uc29saWRhdGVkTW9kZWxzJyxcbiAgJ3NwZWMnLFxuICAnc3VwcG9ydGVkU3VibWl0TWV0aG9kcycsXG4gICdzd2FnZ2VyUmVxdWVzdEhlYWRlcnMnLFxuICAndGFnRnJvbUxhYmVsJyxcbiAgJ3RpdGxlJyxcbiAgJ3VybCcsXG4gICd1c2VKUXVlcnknLFxuICAnanF1ZXJ5QWpheENhY2hlJ1xuXTtcbi8vIFdlIGhhdmUgdG8ga2VlcCB0cmFjayBvZiB0aGUgZnVuY3Rpb24vcHJvcGVydHkgbmFtZXMgdG8gYXZvaWQgY29sbGlzaW9ucyBmb3IgdGFnIG5hbWVzIHdoaWNoIGFyZSB1c2VkIHRvIGFsbG93IHRoZVxuLy8gZm9sbG93aW5nIHVzYWdlOiAnY2xpZW50LmFwaXMue3RhZ05hbWV9J1xudmFyIHJlc2VydmVkQXBpVGFncyA9IFtcbiAgJ2FwaXMnLFxuICAnYXNDdXJsJyxcbiAgJ2Rlc2NyaXB0aW9uJyxcbiAgJ2V4dGVybmFsRG9jcycsXG4gICdoZWxwJyxcbiAgJ2xhYmVsJyxcbiAgJ25hbWUnLFxuICAnb3BlcmF0aW9uJyxcbiAgJ29wZXJhdGlvbnMnLFxuICAnb3BlcmF0aW9uc0FycmF5JyxcbiAgJ3BhdGgnLFxuICAndGFnJ1xuXTtcbnZhciBzdXBwb3J0ZWRPcGVyYXRpb25NZXRob2RzID0gWydkZWxldGUnLCAnZ2V0JywgJ2hlYWQnLCAnb3B0aW9ucycsICdwYXRjaCcsICdwb3N0JywgJ3B1dCddO1xudmFyIFN3YWdnZXJDbGllbnQgPSBtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uICh1cmwsIG9wdGlvbnMpIHtcbiAgdGhpcy5hdXRob3JpemF0aW9ucyA9IG51bGw7XG4gIHRoaXMuYXV0aG9yaXphdGlvblNjaGVtZSA9IG51bGw7XG4gIHRoaXMuYmFzZVBhdGggPSBudWxsO1xuICB0aGlzLmRlYnVnID0gZmFsc2U7XG4gIHRoaXMuZW5hYmxlQ29va2llcyA9IGZhbHNlO1xuICB0aGlzLmluZm8gPSBudWxsO1xuICB0aGlzLmlzQnVpbHQgPSBmYWxzZTtcbiAgdGhpcy5pc1ZhbGlkID0gZmFsc2U7XG4gIHRoaXMubW9kZWxzQXJyYXkgPSBbXTtcbiAgdGhpcy5yZXNvdXJjZUNvdW50ID0gMDtcbiAgdGhpcy51cmwgPSBudWxsO1xuICB0aGlzLnVzZUpRdWVyeSA9IGZhbHNlO1xuICB0aGlzLmpxdWVyeUFqYXhDYWNoZSA9IGZhbHNlO1xuICB0aGlzLnN3YWdnZXJPYmplY3QgPSB7fTtcbiAgdGhpcy5kZWZlcnJlZENsaWVudCA9IHVuZGVmaW5lZDtcblxuICB0aGlzLmNsaWVudEF1dGhvcml6YXRpb25zID0gbmV3IGF1dGguU3dhZ2dlckF1dGhvcml6YXRpb25zKCk7XG5cbiAgaWYgKHR5cGVvZiB1cmwgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgcmV0dXJuIHRoaXMuaW5pdGlhbGl6ZSh1cmwsIG9wdGlvbnMpO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiB0aGlzO1xuICB9XG59O1xuXG5Td2FnZ2VyQ2xpZW50LnByb3RvdHlwZS5pbml0aWFsaXplID0gZnVuY3Rpb24gKHVybCwgb3B0aW9ucykge1xuICB0aGlzLm1vZGVscyA9IHt9O1xuICB0aGlzLnNhbXBsZU1vZGVscyA9IHt9O1xuXG4gIGlmICh0eXBlb2YgdXJsID09PSAnc3RyaW5nJykge1xuICAgIHRoaXMudXJsID0gdXJsO1xuICB9IGVsc2UgaWYgKF8uaXNPYmplY3QodXJsKSkge1xuICAgIG9wdGlvbnMgPSB1cmw7XG4gICAgdGhpcy51cmwgPSBvcHRpb25zLnVybDtcbiAgfVxuXG4gIGlmKHRoaXMudXJsICYmIHRoaXMudXJsLmluZGV4T2YoJ2h0dHA6JykgPT09IC0xICYmIHRoaXMudXJsLmluZGV4T2YoJ2h0dHBzOicpID09PSAtMSkge1xuICAgIC8vIG5vIHByb3RvY29sLCBzbyB3ZSBjYW4gb25seSB1c2Ugd2luZG93IGlmIGl0IGV4aXN0c1xuICAgIGlmKHR5cGVvZih3aW5kb3cpICE9PSAndW5kZWZpbmVkJyAmJiB3aW5kb3cgJiYgd2luZG93LmxvY2F0aW9uKSB7XG4gICAgICB0aGlzLnVybCA9IHdpbmRvdy5sb2NhdGlvbi5vcmlnaW4gKyB0aGlzLnVybDtcbiAgICB9XG4gIH1cblxuICBvcHRpb25zID0gb3B0aW9ucyB8fCB7fTtcbiAgdGhpcy5jbGllbnRBdXRob3JpemF0aW9ucy5hZGQob3B0aW9ucy5hdXRob3JpemF0aW9ucyk7XG4gIHRoaXMuc3dhZ2dlclJlcXVlc3RIZWFkZXJzID0gb3B0aW9ucy5zd2FnZ2VyUmVxdWVzdEhlYWRlcnMgfHwgJ2FwcGxpY2F0aW9uL2pzb247Y2hhcnNldD11dGYtOCwqLyonO1xuICB0aGlzLmRlZmF1bHRTdWNjZXNzQ2FsbGJhY2sgPSBvcHRpb25zLmRlZmF1bHRTdWNjZXNzQ2FsbGJhY2sgfHwgbnVsbDtcbiAgdGhpcy5kZWZhdWx0RXJyb3JDYWxsYmFjayA9IG9wdGlvbnMuZGVmYXVsdEVycm9yQ2FsbGJhY2sgfHwgbnVsbDtcbiAgdGhpcy5tb2RlbFByb3BlcnR5TWFjcm8gPSBvcHRpb25zLm1vZGVsUHJvcGVydHlNYWNybyB8fCBudWxsO1xuICB0aGlzLmNvbm5lY3Rpb25BZ2VudCA9IG9wdGlvbnMuY29ubmVjdGlvbkFnZW50IHx8IG51bGw7XG4gIHRoaXMucGFyYW1ldGVyTWFjcm8gPSBvcHRpb25zLnBhcmFtZXRlck1hY3JvIHx8IG51bGw7XG4gIHRoaXMudXNlUHJvbWlzZSA9IG9wdGlvbnMudXNlUHJvbWlzZSB8fCBudWxsO1xuXG4gIC8vIG9wZXJhdGlvbiByZXF1ZXN0IHRpbWVvdXQgZGVmYXVsdFxuICB0aGlzLnRpbWVvdXQgPSBvcHRpb25zLnRpbWVvdXQgfHwgbnVsbDtcbiAgLy8gZGVmYXVsdCB0byByZXF1ZXN0IHRpbWVvdXQgd2hlbiBub3Qgc3BlY2lmaWVkXG4gIHRoaXMuZmV0Y2hTcGVjVGltZW91dCA9IHR5cGVvZiBvcHRpb25zLmZldGNoU3BlY1RpbWVvdXQgIT09ICd1bmRlZmluZWQnID9cbiAgICAgIG9wdGlvbnMuZmV0Y2hTcGVjVGltZW91dCA6IG9wdGlvbnMudGltZW91dCB8fCBudWxsO1xuXG4gIGlmKHRoaXMudXNlUHJvbWlzZSkge1xuICAgIHRoaXMuZGVmZXJyZWRDbGllbnQgPSBRLmRlZmVyKCk7XG4gIH1cblxuICBpZiAodHlwZW9mIG9wdGlvbnMuc3VjY2VzcyA9PT0gJ2Z1bmN0aW9uJykge1xuICAgIHRoaXMuc3VjY2VzcyA9IG9wdGlvbnMuc3VjY2VzcztcbiAgfVxuICBpZiAob3B0aW9ucy51c2VKUXVlcnkpIHtcbiAgICB0aGlzLnVzZUpRdWVyeSA9IG9wdGlvbnMudXNlSlF1ZXJ5O1xuICB9XG5cbiAgaWYgKG9wdGlvbnMuanF1ZXJ5QWpheENhY2hlKSB7XG4gICAgdGhpcy5qcXVlcnlBamF4Q2FjaGUgPSBvcHRpb25zLmpxdWVyeUFqYXhDYWNoZTtcbiAgfVxuXG4gIGlmIChvcHRpb25zLmVuYWJsZUNvb2tpZXMpIHtcbiAgICB0aGlzLmVuYWJsZUNvb2tpZXMgPSBvcHRpb25zLmVuYWJsZUNvb2tpZXM7XG4gIH1cblxuICB0aGlzLm9wdGlvbnMgPSBvcHRpb25zIHx8IHt9O1xuXG4gIC8vIG1heWJlIGRvbid0IG5lZWQgdGhpcz9cbiAgdGhpcy5vcHRpb25zLnRpbWVvdXQgPSB0aGlzLnRpbWVvdXQ7XG4gIHRoaXMub3B0aW9ucy5mZXRjaFNwZWNUaW1lb3V0ID0gdGhpcy5mZXRjaFNwZWNUaW1lb3V0O1xuXG4gIHRoaXMuc3VwcG9ydGVkU3VibWl0TWV0aG9kcyA9IG9wdGlvbnMuc3VwcG9ydGVkU3VibWl0TWV0aG9kcyB8fCBbXTtcbiAgdGhpcy5mYWlsdXJlID0gb3B0aW9ucy5mYWlsdXJlIHx8IGZ1bmN0aW9uIChlcnIpIHsgdGhyb3cgZXJyOyB9O1xuICB0aGlzLnByb2dyZXNzID0gb3B0aW9ucy5wcm9ncmVzcyB8fCBmdW5jdGlvbiAoKSB7fTtcbiAgdGhpcy5zcGVjID0gXy5jbG9uZURlZXAob3B0aW9ucy5zcGVjKTsgLy8gQ2xvbmUgc28gd2UgZG8gbm90IGFsdGVyIHRoZSBwcm92aWRlZCBkb2N1bWVudFxuXG4gIGlmIChvcHRpb25zLnNjaGVtZSkge1xuICAgIHRoaXMuc2NoZW1lID0gb3B0aW9ucy5zY2hlbWU7XG4gIH1cblxuICBpZiAodGhpcy51c2VQcm9taXNlIHx8IHR5cGVvZiBvcHRpb25zLnN1Y2Nlc3MgPT09ICdmdW5jdGlvbicpIHtcbiAgICB0aGlzLnJlYWR5ID0gdHJ1ZTtcbiAgICByZXR1cm4gdGhpcy5idWlsZCgpO1xuICB9XG59O1xuXG5Td2FnZ2VyQ2xpZW50LnByb3RvdHlwZS5idWlsZCA9IGZ1bmN0aW9uIChtb2NrKSB7XG4gIGlmICh0aGlzLmlzQnVpbHQpIHtcbiAgICByZXR1cm4gdGhpcztcbiAgfVxuXG4gIHZhciBzZWxmID0gdGhpcztcblxuICBpZiAodGhpcy5zcGVjKSB7XG4gICAgdGhpcy5wcm9ncmVzcygnZmV0Y2hpbmcgcmVzb3VyY2UgbGlzdDsgUGxlYXNlIHdhaXQuJyk7XG4gIH0gZWxzZSB7XG4gICAgdGhpcy5wcm9ncmVzcygnZmV0Y2hpbmcgcmVzb3VyY2UgbGlzdDogJyArIHRoaXMudXJsICsgJzsgUGxlYXNlIHdhaXQuJyk7XG4gIH1cblxuICB2YXIgb2JqID0ge1xuICAgIHVzZUpRdWVyeTogdGhpcy51c2VKUXVlcnksXG4gICAganF1ZXJ5QWpheENhY2hlOiB0aGlzLmpxdWVyeUFqYXhDYWNoZSxcbiAgICBjb25uZWN0aW9uQWdlbnQ6IHRoaXMuY29ubmVjdGlvbkFnZW50LFxuICAgIHVybDogdGhpcy51cmwsXG4gICAgbWV0aG9kOiAnZ2V0JyxcbiAgICBoZWFkZXJzOiB7XG4gICAgICBhY2NlcHQ6IHRoaXMuc3dhZ2dlclJlcXVlc3RIZWFkZXJzXG4gICAgfSxcbiAgICBvbjoge1xuICAgICAgZXJyb3I6IGZ1bmN0aW9uIChyZXNwb25zZSkge1xuICAgICAgICBpZiAoc2VsZi51cmwuc3Vic3RyaW5nKDAsIDQpICE9PSAnaHR0cCcpIHtcbiAgICAgICAgICByZXR1cm4gc2VsZi5mYWlsKCdQbGVhc2Ugc3BlY2lmeSB0aGUgcHJvdG9jb2wgZm9yICcgKyBzZWxmLnVybCk7XG4gICAgICAgIH0gZWxzZSBpZiAocmVzcG9uc2UuZXJyT2JqICYmIChyZXNwb25zZS5lcnJPYmouY29kZSA9PT0gJ0VDT05OQUJPUlRFRCcgfHwgcmVzcG9uc2UuZXJyT2JqLm1lc3NhZ2UuaW5kZXhPZigndGltZW91dCcpICE9PSAtMSkpIHtcbiAgICAgICAgICByZXR1cm4gc2VsZi5mYWlsKCdSZXF1ZXN0IHRpbWVkIG91dCBhZnRlciAnICsgc2VsZi5mZXRjaFNwZWNUaW1lb3V0ICsgJ21zJyk7XG4gICAgICAgIH0gZWxzZSBpZiAocmVzcG9uc2Uuc3RhdHVzID09PSAwKSB7XG4gICAgICAgICAgcmV0dXJuIHNlbGYuZmFpbCgnQ2FuXFwndCByZWFkIGZyb20gc2VydmVyLiAgSXQgbWF5IG5vdCBoYXZlIHRoZSBhcHByb3ByaWF0ZSBhY2Nlc3MtY29udHJvbC1vcmlnaW4gc2V0dGluZ3MuJyk7XG4gICAgICAgIH0gZWxzZSBpZiAocmVzcG9uc2Uuc3RhdHVzID09PSA0MDQpIHtcbiAgICAgICAgICByZXR1cm4gc2VsZi5mYWlsKCdDYW5cXCd0IHJlYWQgc3dhZ2dlciBKU09OIGZyb20gJyArIHNlbGYudXJsKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICByZXR1cm4gc2VsZi5mYWlsKHJlc3BvbnNlLnN0YXR1cyArICcgOiAnICsgcmVzcG9uc2Uuc3RhdHVzVGV4dCArICcgJyArIHNlbGYudXJsKTtcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIHJlc3BvbnNlOiBmdW5jdGlvbiAocmVzcCkge1xuXG4gICAgICAgIHZhciByZXNwb25zZU9iaiA9IHJlc3Aub2JqO1xuICAgICAgICBpZighcmVzcG9uc2VPYmopIHtcbiAgICAgICAgICByZXR1cm4gc2VsZi5mYWlsKCdmYWlsZWQgdG8gcGFyc2UgSlNPTi9ZQU1MIHJlc3BvbnNlJyk7XG4gICAgICAgIH1cblxuICAgICAgICBzZWxmLnN3YWdnZXJWZXJzaW9uID0gcmVzcG9uc2VPYmouc3dhZ2dlclZlcnNpb247XG4gICAgICAgIHNlbGYuc3dhZ2dlck9iamVjdCA9IHJlc3BvbnNlT2JqO1xuXG4gICAgICAgIGlmIChyZXNwb25zZU9iai5zd2FnZ2VyICYmIHBhcnNlSW50KHJlc3BvbnNlT2JqLnN3YWdnZXIpID09PSAyKSB7XG4gICAgICAgICAgc2VsZi5zd2FnZ2VyVmVyc2lvbiA9IHJlc3BvbnNlT2JqLnN3YWdnZXI7XG5cbiAgICAgICAgICBuZXcgUmVzb2x2ZXIoKS5yZXNvbHZlKHJlc3BvbnNlT2JqLCBzZWxmLnVybCwgc2VsZi5idWlsZEZyb21TcGVjLCBzZWxmKTtcblxuICAgICAgICAgIHNlbGYuaXNWYWxpZCA9IHRydWU7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdmFyIGNvbnZlcnRlciA9IG5ldyBTd2FnZ2VyU3BlY0NvbnZlcnRlcigpO1xuICAgICAgICAgIHNlbGYub2xkU3dhZ2dlck9iamVjdCA9IHNlbGYuc3dhZ2dlck9iamVjdDtcblxuICAgICAgICAgIGNvbnZlcnRlci5zZXREb2N1bWVudGF0aW9uTG9jYXRpb24oc2VsZi51cmwpO1xuICAgICAgICAgIGNvbnZlcnRlci5jb252ZXJ0KHJlc3BvbnNlT2JqLCBzZWxmLmNsaWVudEF1dGhvcml6YXRpb25zLCBzZWxmLm9wdGlvbnMsIGZ1bmN0aW9uKHNwZWMpIHtcbiAgICAgICAgICAgIHNlbGYuc3dhZ2dlck9iamVjdCA9IHNwZWM7XG4gICAgICAgICAgICBuZXcgUmVzb2x2ZXIoKS5yZXNvbHZlKHNwZWMsIHNlbGYudXJsLCBzZWxmLmJ1aWxkRnJvbVNwZWMsIHNlbGYpO1xuICAgICAgICAgICAgc2VsZi5pc1ZhbGlkID0gdHJ1ZTtcbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfTtcblxuICAvLyBvbmx5IHNldCB0aW1lb3V0IHdoZW4gc3BlY2lmaWVkXG4gIGlmICh0aGlzLmZldGNoU3BlY1RpbWVvdXQpIHtcbiAgICBvYmoudGltZW91dCA9IHRoaXMuZmV0Y2hTcGVjVGltZW91dDtcbiAgfVxuXG4gIGlmICh0aGlzLnNwZWMpIHtcbiAgICBzZWxmLnN3YWdnZXJPYmplY3QgPSB0aGlzLnNwZWM7XG4gICAgc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICBuZXcgUmVzb2x2ZXIoKS5yZXNvbHZlKHNlbGYuc3BlYywgc2VsZi51cmwsIHNlbGYuYnVpbGRGcm9tU3BlYywgc2VsZik7XG4gICAgfSwgMTApO1xuICB9IGVsc2Uge1xuICAgIHRoaXMuY2xpZW50QXV0aG9yaXphdGlvbnMuYXBwbHkob2JqKTtcblxuICAgIGlmIChtb2NrKSB7XG4gICAgICByZXR1cm4gb2JqO1xuICAgIH1cblxuICAgIG5ldyBTd2FnZ2VySHR0cCgpLmV4ZWN1dGUob2JqLCB0aGlzLm9wdGlvbnMpO1xuICB9XG5cbiAgcmV0dXJuICh0aGlzLnVzZVByb21pc2UpID8gdGhpcy5kZWZlcnJlZENsaWVudC5wcm9taXNlIDogdGhpcztcbn07XG5cblN3YWdnZXJDbGllbnQucHJvdG90eXBlLmJ1aWxkRnJvbVNwZWMgPSBmdW5jdGlvbiAocmVzcG9uc2UpIHtcbiAgaWYgKHRoaXMuaXNCdWlsdCkge1xuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgdGhpcy5hcGlzID0ge307XG4gIHRoaXMuYXBpc0FycmF5ID0gW107XG4gIHRoaXMuYmFzZVBhdGggPSByZXNwb25zZS5iYXNlUGF0aCB8fCAnJztcbiAgdGhpcy5jb25zdW1lcyA9IHJlc3BvbnNlLmNvbnN1bWVzO1xuICB0aGlzLmhvc3QgPSByZXNwb25zZS5ob3N0IHx8ICcnO1xuICB0aGlzLmluZm8gPSByZXNwb25zZS5pbmZvIHx8IHt9O1xuICB0aGlzLnByb2R1Y2VzID0gcmVzcG9uc2UucHJvZHVjZXM7XG4gIHRoaXMuc2NoZW1lcyA9IHJlc3BvbnNlLnNjaGVtZXMgfHwgW107XG4gIHRoaXMuc2VjdXJpdHlEZWZpbml0aW9ucyA9IF8uY2xvbmVEZWVwKHJlc3BvbnNlLnNlY3VyaXR5RGVmaW5pdGlvbnMpO1xuICB0aGlzLnNlY3VyaXR5ID0gcmVzcG9uc2Uuc2VjdXJpdHk7XG4gIHRoaXMudGl0bGUgPSByZXNwb25zZS50aXRsZSB8fCAnJztcblxuICB2YXIga2V5LCBkZWZpbmVkVGFncyA9IHt9LCBrLCBsb2NhdGlvbiwgc2VsZiA9IHRoaXMsIGk7XG5cbiAgaWYgKHJlc3BvbnNlLmV4dGVybmFsRG9jcykge1xuICAgIHRoaXMuZXh0ZXJuYWxEb2NzID0gcmVzcG9uc2UuZXh0ZXJuYWxEb2NzO1xuICB9XG5cbiAgLy8gbGVnYWN5IHN1cHBvcnRcbiAgdGhpcy5hdXRoU2NoZW1lcyA9IHRoaXMuc2VjdXJpdHlEZWZpbml0aW9ucztcblxuICBpZih0aGlzLnNlY3VyaXR5RGVmaW5pdGlvbnMpIHtcbiAgICBmb3Ioa2V5IGluIHRoaXMuc2VjdXJpdHlEZWZpbml0aW9ucykge1xuICAgICAgdmFyIHNlY3VyaXR5RGVmaW5pdGlvbiA9IHRoaXMuc2VjdXJpdHlEZWZpbml0aW9uc1trZXldO1xuICAgICAgc2VjdXJpdHlEZWZpbml0aW9uLnZlbmRvckV4dGVuc2lvbnMgPSB7fTtcbiAgICAgIGZvcih2YXIgZXh0IGluIHNlY3VyaXR5RGVmaW5pdGlvbikge1xuICAgICAgICBoZWxwZXJzLmV4dHJhY3RFeHRlbnNpb25zKGV4dCwgc2VjdXJpdHlEZWZpbml0aW9uKTtcbiAgICAgICAgaWYgKGV4dCA9PT0gJ3Njb3BlcycpIHtcbiAgICAgICAgICB2YXIgc2NvcGVzID0gc2VjdXJpdHlEZWZpbml0aW9uW2V4dF07XG4gICAgICAgICAgaWYodHlwZW9mIHNjb3BlcyA9PT0gJ29iamVjdCcpIHtcbiAgICAgICAgICAgIHNjb3Blcy52ZW5kb3JFeHRlbnNpb25zID0ge307XG4gICAgICAgICAgICBmb3IgKHZhciBzIGluIHNjb3Blcykge1xuICAgICAgICAgICAgICBoZWxwZXJzLmV4dHJhY3RFeHRlbnNpb25zKHMsIHNjb3Blcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgaWYgKEFycmF5LmlzQXJyYXkocmVzcG9uc2UudGFncykpIHtcbiAgICBkZWZpbmVkVGFncyA9IHt9O1xuXG4gICAgZm9yIChrID0gMDsgayA8IHJlc3BvbnNlLnRhZ3MubGVuZ3RoOyBrKyspIHtcbiAgICAgIHZhciB0ID0gXy5jbG9uZURlZXAocmVzcG9uc2UudGFnc1trXSk7XG4gICAgICBkZWZpbmVkVGFnc1t0Lm5hbWVdID0gdDtcbiAgICAgIGZvcihpIGluIHQpIHtcbiAgICAgICAgaWYoaSA9PT0gJ2V4dGVybmFsRG9jcycgJiYgdHlwZW9mIHRbaV0gPT09ICdvYmplY3QnKSB7XG4gICAgICAgICAgZm9yKHZhciBqIGluIHRbaV0pIHtcbiAgICAgICAgICAgIGhlbHBlcnMuZXh0cmFjdEV4dGVuc2lvbnMoaiwgdFtpXSk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGhlbHBlcnMuZXh0cmFjdEV4dGVuc2lvbnMoaSwgdCk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cblxuICBpZiAodHlwZW9mIHRoaXMudXJsID09PSAnc3RyaW5nJykge1xuICAgIGxvY2F0aW9uID0gdGhpcy5wYXJzZVVyaSh0aGlzLnVybCk7XG4gICAgaWYgKHR5cGVvZiB0aGlzLnNjaGVtZSA9PT0gJ3VuZGVmaW5lZCcgJiYgdHlwZW9mIHRoaXMuc2NoZW1lcyA9PT0gJ3VuZGVmaW5lZCcgfHwgdGhpcy5zY2hlbWVzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgaWYodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgLy8gdXNlIHRoZSB3aW5kb3cgc2NoZW1lXG4gICAgICAgIHRoaXMuc2NoZW1lID0gd2luZG93LmxvY2F0aW9uLnByb3RvY29sLnJlcGxhY2UoJzonLCcnKTtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICB0aGlzLnNjaGVtZSA9IGxvY2F0aW9uLnNjaGVtZSB8fCAnaHR0cCc7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlb2YgdGhpcy5zY2hlbWUgPT09ICd1bmRlZmluZWQnKSB7XG4gICAgICBpZih0eXBlb2Ygd2luZG93ICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgICB2YXIgc2NoZW1lID0gd2luZG93LmxvY2F0aW9uLnByb3RvY29sLnJlcGxhY2UoJzonLCcnKTtcbiAgICAgICAgaWYoc2NoZW1lID09PSAnaHR0cHMnICYmIHRoaXMuc2NoZW1lcy5pbmRleE9mKHNjaGVtZSkgPT09IC0xKSB7XG4gICAgICAgICAgLy8gY2FuJ3QgY2FsbCBodHRwIGZyb20gaHR0cHMgc2VydmVkIHBhZ2UgaW4gYSBicm93c2VyIVxuICAgICAgICAgIGhlbHBlcnMubG9nKCdDYW5ub3QgY2FsbCBhIGh0dHAgc2VydmVyIGZyb20gaHR0cHMgaW5zaWRlIGEgYnJvd3NlciEnKTtcbiAgICAgICAgICB0aGlzLnNjaGVtZSA9ICdodHRwJztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmKHRoaXMuc2NoZW1lcy5pbmRleE9mKHNjaGVtZSkgIT09IC0xKSB7XG4gICAgICAgICAgdGhpcy5zY2hlbWUgPSBzY2hlbWU7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgaWYodGhpcy5zY2hlbWVzLmluZGV4T2YoJ2h0dHBzJykgIT09IC0xKSB7XG4gICAgICAgICAgICB0aGlzLnNjaGVtZSA9ICdodHRwcyc7XG4gICAgICAgICAgfVxuICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5zY2hlbWUgPSAnaHR0cCc7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgdGhpcy5zY2hlbWUgPSB0aGlzLnNjaGVtZXNbMF0gfHwgbG9jYXRpb24uc2NoZW1lO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmICh0eXBlb2YgdGhpcy5ob3N0ID09PSAndW5kZWZpbmVkJyB8fCB0aGlzLmhvc3QgPT09ICcnKSB7XG4gICAgICB0aGlzLmhvc3QgPSBsb2NhdGlvbi5ob3N0O1xuXG4gICAgICBpZiAobG9jYXRpb24ucG9ydCkge1xuICAgICAgICB0aGlzLmhvc3QgPSB0aGlzLmhvc3QgKyAnOicgKyBsb2NhdGlvbi5wb3J0O1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBlbHNlIHtcbiAgICBpZiAodHlwZW9mIHRoaXMuc2NoZW1lcyA9PT0gJ3VuZGVmaW5lZCcgfHwgdGhpcy5zY2hlbWVzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgdGhpcy5zY2hlbWUgPSAnaHR0cCc7XG4gICAgfVxuICAgIGVsc2UgaWYgKHR5cGVvZiB0aGlzLnNjaGVtZSA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIHRoaXMuc2NoZW1lID0gdGhpcy5zY2hlbWVzWzBdO1xuICAgIH1cbiAgfVxuXG4gIHRoaXMuZGVmaW5pdGlvbnMgPSByZXNwb25zZS5kZWZpbml0aW9ucztcblxuICBmb3IgKGtleSBpbiB0aGlzLmRlZmluaXRpb25zKSB7XG4gICAgdmFyIG1vZGVsID0gbmV3IE1vZGVsKGtleSwgdGhpcy5kZWZpbml0aW9uc1trZXldLCB0aGlzLm1vZGVscywgdGhpcy5tb2RlbFByb3BlcnR5TWFjcm8pO1xuXG4gICAgaWYgKG1vZGVsKSB7XG4gICAgICB0aGlzLm1vZGVsc1trZXldID0gbW9kZWw7XG4gICAgfVxuICB9XG5cbiAgLy8gZ2V0IHBhdGhzLCBjcmVhdGUgZnVuY3Rpb25zIGZvciBlYWNoIG9wZXJhdGlvbklkXG4gIFxuICAvLyBCaW5kIGhlbHAgdG8gJ2NsaWVudC5hcGlzJ1xuICBzZWxmLmFwaXMuaGVscCA9IF8uYmluZChzZWxmLmhlbHAsIHNlbGYpO1xuXG4gIF8uZm9yRWFjaChyZXNwb25zZS5wYXRocywgZnVuY3Rpb24gKHBhdGhPYmosIHBhdGgpIHtcbiAgICAvLyBPbmx5IHByb2Nlc3MgYSBwYXRoIGlmIGl0J3MgYW4gb2JqZWN0XG4gICAgaWYgKCFfLmlzUGxhaW5PYmplY3QocGF0aE9iaikpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBfLmZvckVhY2goc3VwcG9ydGVkT3BlcmF0aW9uTWV0aG9kcywgZnVuY3Rpb24gKG1ldGhvZCkge1xuICAgICAgdmFyIG9wZXJhdGlvbiA9IHBhdGhPYmpbbWV0aG9kXTtcblxuICAgICAgaWYgKF8uaXNVbmRlZmluZWQob3BlcmF0aW9uKSkge1xuICAgICAgICAvLyBPcGVyYXRpb24gZG9lcyBub3QgZXhpc3RcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfSBlbHNlIGlmICghXy5pc1BsYWluT2JqZWN0KG9wZXJhdGlvbikpIHtcbiAgICAgICAgLy8gT3BlcmF0aW9uIGV4aXN0cyBidXQgaXQgaXMgbm90IGFuIE9wZXJhdGlvbiBPYmplY3QuICBTaW5jZSB0aGlzIGlzIGludmFsaWQsIGxvZyBpdC5cbiAgICAgICAgaGVscGVycy5sb2coJ1RoZSBcXCcnICsgbWV0aG9kICsgJ1xcJyBvcGVyYXRpb24gZm9yIFxcJycgKyBwYXRoICsgJ1xcJyBwYXRoIGlzIG5vdCBhbiBPcGVyYXRpb24gT2JqZWN0Jyk7XG5cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICB2YXIgdGFncyA9IG9wZXJhdGlvbi50YWdzO1xuXG4gICAgICBpZiAoXy5pc1VuZGVmaW5lZCh0YWdzKSB8fCAhXy5pc0FycmF5KHRhZ3MpIHx8IHRhZ3MubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIHRhZ3MgPSBvcGVyYXRpb24udGFncyA9IFsgJ2RlZmF1bHQnIF07XG4gICAgICB9XG5cbiAgICAgIHZhciBvcGVyYXRpb25JZCA9IHNlbGYuaWRGcm9tT3AocGF0aCwgbWV0aG9kLCBvcGVyYXRpb24pO1xuXG4gICAgICB2YXIgb3BlcmF0aW9uT2JqZWN0ID0gbmV3IE9wZXJhdGlvbihzZWxmLFxuICAgICAgICBvcGVyYXRpb24uc2NoZW1lLFxuICAgICAgICBvcGVyYXRpb25JZCxcbiAgICAgICAgbWV0aG9kLFxuICAgICAgICBwYXRoLFxuICAgICAgICBvcGVyYXRpb24sXG4gICAgICAgIHNlbGYuZGVmaW5pdGlvbnMsXG4gICAgICAgIHNlbGYubW9kZWxzLFxuICAgICAgICBzZWxmLmNsaWVudEF1dGhvcml6YXRpb25zKTtcblxuICAgICAgb3BlcmF0aW9uT2JqZWN0LnZlbmRvckV4dGVuc2lvbnMgPSB7fTtcbiAgICAgIGZvcihpIGluIG9wZXJhdGlvbikge1xuICAgICAgICBoZWxwZXJzLmV4dHJhY3RFeHRlbnNpb25zKGksIG9wZXJhdGlvbk9iamVjdCwgb3BlcmF0aW9uW2ldKTtcbiAgICAgIH1cbiAgICAgIG9wZXJhdGlvbk9iamVjdC5leHRlcm5hbERvY3MgPSBvcGVyYXRpb24uZXh0ZXJuYWxEb2NzO1xuICAgICAgaWYob3BlcmF0aW9uT2JqZWN0LmV4dGVybmFsRG9jcykge1xuICAgICAgICBvcGVyYXRpb25PYmplY3QuZXh0ZXJuYWxEb2NzID0gXy5jbG9uZURlZXAob3BlcmF0aW9uT2JqZWN0LmV4dGVybmFsRG9jcyk7XG4gICAgICAgIG9wZXJhdGlvbk9iamVjdC5leHRlcm5hbERvY3MudmVuZG9yRXh0ZW5zaW9ucyA9IHt9O1xuICAgICAgICBmb3IoaSBpbiBvcGVyYXRpb25PYmplY3QuZXh0ZXJuYWxEb2NzKSB7XG4gICAgICAgICAgaGVscGVycy5leHRyYWN0RXh0ZW5zaW9ucyhpLCBvcGVyYXRpb25PYmplY3QuZXh0ZXJuYWxEb2NzKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBiaW5kIHNlbGYgb3BlcmF0aW9uJ3MgZXhlY3V0ZSBjb21tYW5kIHRvIHRoZSBhcGlcbiAgICAgIF8uZm9yRWFjaCh0YWdzLCBmdW5jdGlvbiAodGFnKSB7XG4gICAgICAgIHZhciBjbGllbnRQcm9wZXJ0eSA9IF8uaW5kZXhPZihyZXNlcnZlZENsaWVudFRhZ3MsIHRhZykgPiAtMSA/ICdfJyArIHRhZyA6IHRhZztcbiAgICAgICAgdmFyIGFwaVByb3BlcnR5ID0gXy5pbmRleE9mKHJlc2VydmVkQXBpVGFncywgdGFnKSA+IC0xID8gJ18nICsgdGFnIDogdGFnO1xuICAgICAgICB2YXIgb3BlcmF0aW9uR3JvdXAgPSBzZWxmW2NsaWVudFByb3BlcnR5XTtcblxuICAgICAgICBpZiAoY2xpZW50UHJvcGVydHkgIT09IHRhZykge1xuICAgICAgICAgIGhlbHBlcnMubG9nKCdUaGUgXFwnJyArIHRhZyArICdcXCcgdGFnIGNvbmZsaWN0cyB3aXRoIGEgU3dhZ2dlckNsaWVudCBmdW5jdGlvbi9wcm9wZXJ0eSBuYW1lLiAgVXNlIFxcJ2NsaWVudC4nICtcbiAgICAgICAgICAgICAgICAgICAgICBjbGllbnRQcm9wZXJ0eSArICdcXCcgb3IgXFwnY2xpZW50LmFwaXMuJyArIHRhZyArICdcXCcgaW5zdGVhZCBvZiBcXCdjbGllbnQuJyArIHRhZyArICdcXCcuJyk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoYXBpUHJvcGVydHkgIT09IHRhZykge1xuICAgICAgICAgIGhlbHBlcnMubG9nKCdUaGUgXFwnJyArIHRhZyArICdcXCcgdGFnIGNvbmZsaWN0cyB3aXRoIGEgU3dhZ2dlckNsaWVudCBvcGVyYXRpb24gZnVuY3Rpb24vcHJvcGVydHkgbmFtZS4gIFVzZSAnICtcbiAgICAgICAgICAgICAgICAgICAgICAnXFwnY2xpZW50LmFwaXMuJyArIGFwaVByb3BlcnR5ICsgJ1xcJyBpbnN0ZWFkIG9mIFxcJ2NsaWVudC5hcGlzLicgKyB0YWcgKyAnXFwnLicpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKF8uaW5kZXhPZihyZXNlcnZlZEFwaVRhZ3MsIG9wZXJhdGlvbklkKSA+IC0xKSB7XG4gICAgICAgICAgaGVscGVycy5sb2coJ1RoZSBcXCcnICsgb3BlcmF0aW9uSWQgKyAnXFwnIG9wZXJhdGlvbklkIGNvbmZsaWN0cyB3aXRoIGEgU3dhZ2dlckNsaWVudCBvcGVyYXRpb24gJyArXG4gICAgICAgICAgICAgICAgICAgICAgJ2Z1bmN0aW9uL3Byb3BlcnR5IG5hbWUuICBVc2UgXFwnY2xpZW50LmFwaXMuJyArIGFwaVByb3BlcnR5ICsgJy5fJyArIG9wZXJhdGlvbklkICtcbiAgICAgICAgICAgICAgICAgICAgICAnXFwnIGluc3RlYWQgb2YgXFwnY2xpZW50LmFwaXMuJyArIGFwaVByb3BlcnR5ICsgJy4nICsgb3BlcmF0aW9uSWQgKyAnXFwnLicpO1xuXG4gICAgICAgICAgb3BlcmF0aW9uSWQgPSAnXycgKyBvcGVyYXRpb25JZDtcbiAgICAgICAgICBvcGVyYXRpb25PYmplY3Qubmlja25hbWUgPSBvcGVyYXRpb25JZDsgLy8gU28gJ2NsaWVudC5hcGlzLlt0YWddLm9wZXJhdGlvbklkLmhlbHAoKSB3b3JrcyBwcm9wZXJseVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKF8uaXNVbmRlZmluZWQob3BlcmF0aW9uR3JvdXApKSB7XG4gICAgICAgICAgb3BlcmF0aW9uR3JvdXAgPSBzZWxmW2NsaWVudFByb3BlcnR5XSA9IHNlbGYuYXBpc1thcGlQcm9wZXJ0eV0gPSB7fTtcblxuICAgICAgICAgIG9wZXJhdGlvbkdyb3VwLm9wZXJhdGlvbnMgPSB7fTtcbiAgICAgICAgICBvcGVyYXRpb25Hcm91cC5sYWJlbCA9IGFwaVByb3BlcnR5O1xuICAgICAgICAgIG9wZXJhdGlvbkdyb3VwLmFwaXMgPSB7fTtcblxuICAgICAgICAgIHZhciB0YWdEZWYgPSBkZWZpbmVkVGFnc1t0YWddO1xuXG4gICAgICAgICAgaWYgKCFfLmlzVW5kZWZpbmVkKHRhZ0RlZikpIHtcbiAgICAgICAgICAgIG9wZXJhdGlvbkdyb3VwLmRlc2NyaXB0aW9uID0gdGFnRGVmLmRlc2NyaXB0aW9uO1xuICAgICAgICAgICAgb3BlcmF0aW9uR3JvdXAuZXh0ZXJuYWxEb2NzID0gdGFnRGVmLmV4dGVybmFsRG9jcztcbiAgICAgICAgICAgIG9wZXJhdGlvbkdyb3VwLnZlbmRvckV4dGVuc2lvbnMgPSB0YWdEZWYudmVuZG9yRXh0ZW5zaW9ucztcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBzZWxmW2NsaWVudFByb3BlcnR5XS5oZWxwID0gXy5iaW5kKHNlbGYuaGVscCwgb3BlcmF0aW9uR3JvdXApO1xuICAgICAgICAgIHNlbGYuYXBpc0FycmF5LnB1c2gobmV3IE9wZXJhdGlvbkdyb3VwKHRhZywgb3BlcmF0aW9uR3JvdXAuZGVzY3JpcHRpb24sIG9wZXJhdGlvbkdyb3VwLmV4dGVybmFsRG9jcywgb3BlcmF0aW9uT2JqZWN0KSk7XG4gICAgICAgIH1cblxuICAgICAgICBvcGVyYXRpb25JZCA9IHNlbGYubWFrZVVuaXF1ZU9wZXJhdGlvbklkKG9wZXJhdGlvbklkLCBzZWxmLmFwaXNbYXBpUHJvcGVydHldKTtcblxuICAgICAgICAvLyBCaW5kIHRhZyBoZWxwXG4gICAgICAgIGlmICghXy5pc0Z1bmN0aW9uKG9wZXJhdGlvbkdyb3VwLmhlbHApKSB7XG4gICAgICAgICAgb3BlcmF0aW9uR3JvdXAuaGVscCA9IF8uYmluZChzZWxmLmhlbHAsIG9wZXJhdGlvbkdyb3VwKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGJpbmQgdG8gdGhlIGFwaXMgb2JqZWN0XG4gICAgICAgIHNlbGYuYXBpc1thcGlQcm9wZXJ0eV1bb3BlcmF0aW9uSWRdID0gb3BlcmF0aW9uR3JvdXBbb3BlcmF0aW9uSWRdID0gXy5iaW5kKG9wZXJhdGlvbk9iamVjdC5leGVjdXRlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9wZXJhdGlvbk9iamVjdCk7XG4gICAgICAgIHNlbGYuYXBpc1thcGlQcm9wZXJ0eV1bb3BlcmF0aW9uSWRdLmhlbHAgPSBvcGVyYXRpb25Hcm91cFtvcGVyYXRpb25JZF0uaGVscCA9IF8uYmluZChvcGVyYXRpb25PYmplY3QuaGVscCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9wZXJhdGlvbk9iamVjdCk7XG4gICAgICAgIHNlbGYuYXBpc1thcGlQcm9wZXJ0eV1bb3BlcmF0aW9uSWRdLmFzQ3VybCA9IG9wZXJhdGlvbkdyb3VwW29wZXJhdGlvbklkXS5hc0N1cmwgPSBfLmJpbmQob3BlcmF0aW9uT2JqZWN0LmFzQ3VybCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcGVyYXRpb25PYmplY3QpO1xuXG4gICAgICAgIG9wZXJhdGlvbkdyb3VwLmFwaXNbb3BlcmF0aW9uSWRdID0gb3BlcmF0aW9uR3JvdXAub3BlcmF0aW9uc1tvcGVyYXRpb25JZF0gPSBvcGVyYXRpb25PYmplY3Q7XG5cbiAgICAgICAgLy8gbGVnYWN5IFVJIGZlYXR1cmVcbiAgICAgICAgdmFyIGFwaSA9IF8uZmluZChzZWxmLmFwaXNBcnJheSwgZnVuY3Rpb24gKGFwaSkge1xuICAgICAgICAgIHJldHVybiBhcGkudGFnID09PSB0YWc7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGlmIChhcGkpIHtcbiAgICAgICAgICBhcGkub3BlcmF0aW9uc0FycmF5LnB1c2gob3BlcmF0aW9uT2JqZWN0KTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfSk7XG4gIH0pO1xuXG4gIC8vIHNvcnQgdGhlIGFwaXNBcnJheSBhY2NvcmRpbmcgdG8gdGhlIHRhZ3NcbiAgdmFyIHNvcnRlZEFwaXMgPSBbXTtcbiAgXy5mb3JFYWNoKE9iamVjdC5rZXlzKGRlZmluZWRUYWdzKSwgZnVuY3Rpb24gKHRhZykge1xuICAgIHZhciBwb3M7XG4gICAgZm9yKHBvcyBpbiBzZWxmLmFwaXNBcnJheSkge1xuICAgICAgdmFyIF9hcGkgPSBzZWxmLmFwaXNBcnJheVtwb3NdO1xuICAgICAgaWYoX2FwaSAmJiB0YWcgPT09IF9hcGkubmFtZSkge1xuICAgICAgICBzb3J0ZWRBcGlzLnB1c2goX2FwaSk7XG4gICAgICAgIHNlbGYuYXBpc0FycmF5W3Bvc10gPSBudWxsO1xuICAgICAgfVxuICAgIH1cbiAgfSk7XG4gIC8vIGFkZCBhbnl0aGluZyBsZWZ0XG4gIF8uZm9yRWFjaChzZWxmLmFwaXNBcnJheSwgZnVuY3Rpb24gKGFwaSkge1xuICAgIGlmKGFwaSkge1xuICAgICAgc29ydGVkQXBpcy5wdXNoKGFwaSk7XG4gICAgfVxuICB9KTtcbiAgc2VsZi5hcGlzQXJyYXkgPSBzb3J0ZWRBcGlzO1xuXG4gIF8uZm9yRWFjaChyZXNwb25zZS5kZWZpbml0aW9ucywgZnVuY3Rpb24gKGRlZmluaXRpb25PYmosIGRlZmluaXRpb24pIHtcbiAgICBkZWZpbml0aW9uT2JqLmlkID0gZGVmaW5pdGlvbi50b0xvd2VyQ2FzZSgpO1xuICAgIGRlZmluaXRpb25PYmoubmFtZSA9IGRlZmluaXRpb247XG4gICAgc2VsZi5tb2RlbHNBcnJheS5wdXNoKGRlZmluaXRpb25PYmopO1xuICB9KTtcblxuICB0aGlzLmlzQnVpbHQgPSB0cnVlO1xuXG4gIGlmICh0aGlzLnVzZVByb21pc2UpIHtcbiAgICB0aGlzLmlzVmFsaWQgPSB0cnVlO1xuICAgIHRoaXMuaXNCdWlsdCA9IHRydWU7XG4gICAgdGhpcy5kZWZlcnJlZENsaWVudC5yZXNvbHZlKHRoaXMpO1xuXG4gICAgcmV0dXJuIHRoaXMuZGVmZXJyZWRDbGllbnQucHJvbWlzZTtcbiAgfVxuXG4gIGlmICh0aGlzLnN1Y2Nlc3MpIHtcbiAgICB0aGlzLnN1Y2Nlc3MoKTtcbiAgfVxuXG4gIHJldHVybiB0aGlzO1xufTtcblxuU3dhZ2dlckNsaWVudC5wcm90b3R5cGUubWFrZVVuaXF1ZU9wZXJhdGlvbklkID0gZnVuY3Rpb24ob3BlcmF0aW9uSWQsIGFwaSkge1xuICB2YXIgY291bnQgPSAwO1xuICB2YXIgbmFtZSA9IG9wZXJhdGlvbklkO1xuXG4gIC8vIG1ha2UgdW5pcXVlIGFjcm9zcyB0aGlzIG9wZXJhdGlvbiBncm91cFxuICB3aGlsZSh0cnVlKSB7XG4gICAgdmFyIG1hdGNoZWQgPSBmYWxzZTtcbiAgICBfLmZvckVhY2goYXBpLm9wZXJhdGlvbnMsIGZ1bmN0aW9uIChvcGVyYXRpb24pIHtcbiAgICAgIGlmKG9wZXJhdGlvbi5uaWNrbmFtZSA9PT0gbmFtZSkge1xuICAgICAgICBtYXRjaGVkID0gdHJ1ZTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICBpZighbWF0Y2hlZCkge1xuICAgICAgcmV0dXJuIG5hbWU7XG4gICAgfVxuICAgIG5hbWUgPSBvcGVyYXRpb25JZCArICdfJyArIGNvdW50O1xuICAgIGNvdW50ICsrO1xuICB9XG5cbiAgcmV0dXJuIG9wZXJhdGlvbklkO1xufTtcblxuU3dhZ2dlckNsaWVudC5wcm90b3R5cGUucGFyc2VVcmkgPSBmdW5jdGlvbiAodXJpKSB7XG4gIHZhciB1cmxQYXJzZVJFID0gL14oKCgoW146XFwvI1xcP10rOik/KD86KFxcL1xcLykoKD86KChbXjpAXFwvI1xcP10rKSg/OlxcOihbXjpAXFwvI1xcP10rKSk/KUApPygoW146XFwvI1xcP1xcXVxcW10rfFxcW1teXFwvXFxdQCM/XStcXF0pKD86XFw6KFswLTldKykpPykpPyk/KT8oKFxcLz8oPzpbXlxcL1xcPyNdK1xcLyspKikoW15cXD8jXSopKSk/KFxcP1teI10rKT8pKCMuKik/LztcbiAgdmFyIHBhcnRzID0gdXJsUGFyc2VSRS5leGVjKHVyaSk7XG5cbiAgcmV0dXJuIHtcbiAgICBzY2hlbWU6IHBhcnRzWzRdID8gcGFydHNbNF0ucmVwbGFjZSgnOicsJycpIDogdW5kZWZpbmVkLFxuICAgIGhvc3Q6IHBhcnRzWzExXSxcbiAgICBwb3J0OiBwYXJ0c1sxMl0sXG4gICAgcGF0aDogcGFydHNbMTVdXG4gIH07XG59O1xuXG5Td2FnZ2VyQ2xpZW50LnByb3RvdHlwZS5oZWxwID0gZnVuY3Rpb24gKGRvbnRQcmludCkge1xuICB2YXIgb3V0cHV0ID0gJyc7XG5cbiAgaWYgKHRoaXMgaW5zdGFuY2VvZiBTd2FnZ2VyQ2xpZW50KSB7XG4gICAgXy5mb3JFYWNoKHRoaXMuYXBpcywgZnVuY3Rpb24gKGFwaSwgbmFtZSkge1xuICAgICAgaWYgKF8uaXNQbGFpbk9iamVjdChhcGkpKSB7XG4gICAgICAgIG91dHB1dCArPSAnb3BlcmF0aW9ucyBmb3IgdGhlIFxcJycgKyBuYW1lICsgJ1xcJyB0YWdcXG4nO1xuXG4gICAgICAgIF8uZm9yRWFjaChhcGkub3BlcmF0aW9ucywgZnVuY3Rpb24gKG9wZXJhdGlvbiwgbmFtZSkge1xuICAgICAgICAgIG91dHB1dCArPSAnICAqICcgKyBuYW1lICsgJzogJyArIG9wZXJhdGlvbi5zdW1tYXJ5ICsgJ1xcbic7XG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH0pO1xuICB9IGVsc2UgaWYgKHRoaXMgaW5zdGFuY2VvZiBPcGVyYXRpb25Hcm91cCB8fCBfLmlzUGxhaW5PYmplY3QodGhpcykpIHtcbiAgICBvdXRwdXQgKz0gJ29wZXJhdGlvbnMgZm9yIHRoZSBcXCcnICsgdGhpcy5sYWJlbCArICdcXCcgdGFnXFxuJztcblxuICAgIF8uZm9yRWFjaCh0aGlzLmFwaXMsIGZ1bmN0aW9uIChvcGVyYXRpb24sIG5hbWUpIHtcbiAgICAgIG91dHB1dCArPSAnICAqICcgKyBuYW1lICsgJzogJyArIG9wZXJhdGlvbi5zdW1tYXJ5ICsgJ1xcbic7XG4gICAgfSk7XG4gIH1cblxuICBpZiAoZG9udFByaW50KSB7XG4gICAgcmV0dXJuIG91dHB1dDtcbiAgfSBlbHNlIHtcbiAgICBoZWxwZXJzLmxvZyhvdXRwdXQpO1xuXG4gICAgcmV0dXJuIG91dHB1dDtcbiAgfVxufTtcblxuU3dhZ2dlckNsaWVudC5wcm90b3R5cGUudGFnRnJvbUxhYmVsID0gZnVuY3Rpb24gKGxhYmVsKSB7XG4gIHJldHVybiBsYWJlbDtcbn07XG5cblN3YWdnZXJDbGllbnQucHJvdG90eXBlLmlkRnJvbU9wID0gZnVuY3Rpb24gKHBhdGgsIGh0dHBNZXRob2QsIG9wKSB7XG4gIGlmKCFvcCB8fCAhb3Aub3BlcmF0aW9uSWQpIHtcbiAgICBvcCA9IG9wIHx8IHt9O1xuICAgIG9wLm9wZXJhdGlvbklkID0gaHR0cE1ldGhvZCArICdfJyArIHBhdGg7XG4gIH1cbiAgdmFyIG9wSWQgPSBvcC5vcGVyYXRpb25JZC5yZXBsYWNlKC9bXFxzIUAjJCVeJiooKV8rPVxcW3tcXF19Ozo8PnwuXFwvPyxcXFxcJ1wiXCItXS9nLCAnXycpIHx8IChwYXRoLnN1YnN0cmluZygxKSArICdfJyArIGh0dHBNZXRob2QpO1xuXG4gIG9wSWQgPSBvcElkLnJlcGxhY2UoLygoXyl7Mix9KS9nLCAnXycpO1xuICBvcElkID0gb3BJZC5yZXBsYWNlKC9eKF8pKi9nLCAnJyk7XG4gIG9wSWQgPSBvcElkLnJlcGxhY2UoLyhbX10pKiQvZywgJycpO1xuXG4gIHJldHVybiBvcElkO1xufTtcblxuU3dhZ2dlckNsaWVudC5wcm90b3R5cGUuc2V0SG9zdCA9IGZ1bmN0aW9uIChob3N0KSB7XG4gIHRoaXMuaG9zdCA9IGhvc3Q7XG5cbiAgaWYodGhpcy5hcGlzKSB7XG4gICAgXy5mb3JFYWNoKHRoaXMuYXBpcywgZnVuY3Rpb24oYXBpKSB7XG4gICAgICBpZihhcGkub3BlcmF0aW9ucykge1xuICAgICAgICBfLmZvckVhY2goYXBpLm9wZXJhdGlvbnMsIGZ1bmN0aW9uKG9wZXJhdGlvbikge1xuICAgICAgICAgIG9wZXJhdGlvbi5ob3N0ID0gaG9zdDtcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cbn07XG5cblN3YWdnZXJDbGllbnQucHJvdG90eXBlLnNldEJhc2VQYXRoID0gZnVuY3Rpb24gKGJhc2VQYXRoKSB7XG4gIHRoaXMuYmFzZVBhdGggPSBiYXNlUGF0aDtcblxuICBpZih0aGlzLmFwaXMpIHtcbiAgICBfLmZvckVhY2godGhpcy5hcGlzLCBmdW5jdGlvbihhcGkpIHtcbiAgICAgIGlmKGFwaS5vcGVyYXRpb25zKSB7XG4gICAgICAgIF8uZm9yRWFjaChhcGkub3BlcmF0aW9ucywgZnVuY3Rpb24ob3BlcmF0aW9uKSB7XG4gICAgICAgICAgb3BlcmF0aW9uLmJhc2VQYXRoID0gYmFzZVBhdGg7XG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG59O1xuXG5Td2FnZ2VyQ2xpZW50LnByb3RvdHlwZS5zZXRTY2hlbWVzID0gZnVuY3Rpb24gKHNjaGVtZXMpIHtcbiAgdGhpcy5zY2hlbWVzID0gc2NoZW1lcztcblxuICBpZihzY2hlbWVzICYmIHNjaGVtZXMubGVuZ3RoID4gMCkge1xuICAgIGlmKHRoaXMuYXBpcykge1xuICAgICAgXy5mb3JFYWNoKHRoaXMuYXBpcywgZnVuY3Rpb24gKGFwaSkge1xuICAgICAgICBpZiAoYXBpLm9wZXJhdGlvbnMpIHtcbiAgICAgICAgICBfLmZvckVhY2goYXBpLm9wZXJhdGlvbnMsIGZ1bmN0aW9uIChvcGVyYXRpb24pIHtcbiAgICAgICAgICAgIG9wZXJhdGlvbi5zY2hlbWUgPSBzY2hlbWVzWzBdO1xuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICB9XG4gIH1cbn07XG5cblN3YWdnZXJDbGllbnQucHJvdG90eXBlLmZhaWwgPSBmdW5jdGlvbiAobWVzc2FnZSkge1xuICBpZiAodGhpcy51c2VQcm9taXNlKSB7XG4gICAgdGhpcy5kZWZlcnJlZENsaWVudC5yZWplY3QobWVzc2FnZSk7XG4gICAgcmV0dXJuIHRoaXMuZGVmZXJyZWRDbGllbnQucHJvbWlzZTtcbiAgfSBlbHNlIHtcbiAgICBpZiAodGhpcy5mYWlsdXJlKSB7XG4gICAgICB0aGlzLmZhaWx1cmUobWVzc2FnZSk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgdGhpcy5mYWlsdXJlKG1lc3NhZ2UpO1xuICAgIH1cbiAgfVxufTtcbiIsIihmdW5jdGlvbiAocHJvY2Vzcyl7XG4ndXNlIHN0cmljdCc7XG5cbnZhciBfID0ge1xuICBpc1BsYWluT2JqZWN0OiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvaXNQbGFpbk9iamVjdCcpLFxuICBpbmRleE9mOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2FycmF5L2luZGV4T2YnKVxufTtcblxubW9kdWxlLmV4cG9ydHMuX19iaW5kID0gZnVuY3Rpb24gKGZuLCBtZSkge1xuICByZXR1cm4gZnVuY3Rpb24oKXtcbiAgICByZXR1cm4gZm4uYXBwbHkobWUsIGFyZ3VtZW50cyk7XG4gIH07XG59O1xuXG52YXIgbG9nID0gbW9kdWxlLmV4cG9ydHMubG9nID0gZnVuY3Rpb24oKSB7XG4gIC8vIE9ubHkgbG9nIGlmIGF2YWlsYWJsZSBhbmQgd2UncmUgbm90IHRlc3RpbmdcbiAgaWYgKGNvbnNvbGUgJiYgcHJvY2Vzcy5lbnYuTk9ERV9FTlYgIT09ICd0ZXN0Jykge1xuICAgIGNvbnNvbGUubG9nKEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGFyZ3VtZW50cylbMF0pO1xuICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cy5mYWlsID0gZnVuY3Rpb24gKG1lc3NhZ2UpIHtcbiAgbG9nKG1lc3NhZ2UpO1xufTtcblxubW9kdWxlLmV4cG9ydHMub3B0aW9uSHRtbCA9IGZ1bmN0aW9uIChsYWJlbCwgdmFsdWUpIHtcbiAgcmV0dXJuICc8dHI+PHRkIGNsYXNzPVwib3B0aW9uTmFtZVwiPicgKyBsYWJlbCArICc6PC90ZD48dGQ+JyArIHZhbHVlICsgJzwvdGQ+PC90cj4nO1xufTtcblxudmFyIHJlc29sdmVTY2hlbWEgPSBtb2R1bGUuZXhwb3J0cy5yZXNvbHZlU2NoZW1hID0gZnVuY3Rpb24gKHNjaGVtYSkge1xuICBpZiAoXy5pc1BsYWluT2JqZWN0KHNjaGVtYS5zY2hlbWEpKSB7XG4gICAgc2NoZW1hID0gcmVzb2x2ZVNjaGVtYShzY2hlbWEuc2NoZW1hKTtcbiAgfVxuXG4gIHJldHVybiBzY2hlbWE7XG59O1xuXG5tb2R1bGUuZXhwb3J0cy5zaW1wbGVSZWYgPSBmdW5jdGlvbiAobmFtZSkge1xuICBpZiAodHlwZW9mIG5hbWUgPT09ICd1bmRlZmluZWQnKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICBpZiAobmFtZS5pbmRleE9mKCcjL2RlZmluaXRpb25zLycpID09PSAwKSB7XG4gICAgcmV0dXJuIG5hbWUuc3Vic3RyaW5nKCcjL2RlZmluaXRpb25zLycubGVuZ3RoKTtcbiAgfSBlbHNlIHtcbiAgICByZXR1cm4gbmFtZTtcbiAgfVxufTtcblxuLyoqXG4gKiBoZWxwZXIgdG8gcmVtb3ZlIGV4dGVuc2lvbnMgYW5kIGFkZCB0aGVtIHRvIGFuIG9iamVjdFxuICpcbiAqIEBwYXJhbSBrZXluYW1lXG4gKiBAcGFyYW0gb2JqXG4gKi9cbm1vZHVsZS5leHBvcnRzLmV4dHJhY3RFeHRlbnNpb25zID0gZnVuY3Rpb24gKGtleW5hbWUsIG9iaiwgdmFsdWUpIHtcbiAgaWYoIWtleW5hbWUgfHwgIW9iaikge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGlmICh0eXBlb2Yga2V5bmFtZSA9PT0gJ3N0cmluZycgJiYga2V5bmFtZS5pbmRleE9mKCd4LScpID09PSAwKSB7XG4gICAgb2JqLnZlbmRvckV4dGVuc2lvbnMgPSBvYmoudmVuZG9yRXh0ZW5zaW9ucyB8fCB7fTtcbiAgICBpZih2YWx1ZSkge1xuICAgICAgb2JqLnZlbmRvckV4dGVuc2lvbnNba2V5bmFtZV0gPSB2YWx1ZTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICBvYmoudmVuZG9yRXh0ZW5zaW9uc1trZXluYW1lXSA9IG9ialtrZXluYW1lXTtcbiAgICB9XG4gIH1cbn07XG59KS5jYWxsKHRoaXMscmVxdWlyZSgnX3Byb2Nlc3MnKSlcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPWRhdGE6YXBwbGljYXRpb24vanNvbjtjaGFyc2V0OnV0Zi04O2Jhc2U2NCxleUoyWlhKemFXOXVJam96TENKemIzVnlZMlZ6SWpwYklteHBZaTlvWld4d1pYSnpMbXB6SWwwc0ltNWhiV1Z6SWpwYlhTd2liV0Z3Y0dsdVozTWlPaUk3UVVGQlFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFaUxDSm1hV3hsSWpvaVoyVnVaWEpoZEdWa0xtcHpJaXdpYzI5MWNtTmxVbTl2ZENJNklpSXNJbk52ZFhKalpYTkRiMjUwWlc1MElqcGJJaWQxYzJVZ2MzUnlhV04wSnp0Y2JseHVkbUZ5SUY4Z1BTQjdYRzRnSUdselVHeGhhVzVQWW1wbFkzUTZJSEpsY1hWcGNtVW9KMnh2WkdGemFDMWpiMjF3WVhRdmJHRnVaeTlwYzFCc1lXbHVUMkpxWldOMEp5a3NYRzRnSUdsdVpHVjRUMlk2SUhKbGNYVnBjbVVvSjJ4dlpHRnphQzFqYjIxd1lYUXZZWEp5WVhrdmFXNWtaWGhQWmljcFhHNTlPMXh1WEc1dGIyUjFiR1V1Wlhod2IzSjBjeTVmWDJKcGJtUWdQU0JtZFc1amRHbHZiaUFvWm00c0lHMWxLU0I3WEc0Z0lISmxkSFZ5YmlCbWRXNWpkR2x2YmlncGUxeHVJQ0FnSUhKbGRIVnliaUJtYmk1aGNIQnNlU2h0WlN3Z1lYSm5kVzFsYm5SektUdGNiaUFnZlR0Y2JuMDdYRzVjYm5aaGNpQnNiMmNnUFNCdGIyUjFiR1V1Wlhod2IzSjBjeTVzYjJjZ1BTQm1kVzVqZEdsdmJpZ3BJSHRjYmlBZ0x5OGdUMjVzZVNCc2IyY2dhV1lnWVhaaGFXeGhZbXhsSUdGdVpDQjNaU2R5WlNCdWIzUWdkR1Z6ZEdsdVoxeHVJQ0JwWmlBb1kyOXVjMjlzWlNBbUppQndjbTlqWlhOekxtVnVkaTVPVDBSRlgwVk9WaUFoUFQwZ0ozUmxjM1FuS1NCN1hHNGdJQ0FnWTI5dWMyOXNaUzVzYjJjb1FYSnlZWGt1Y0hKdmRHOTBlWEJsTG5Oc2FXTmxMbU5oYkd3b1lYSm5kVzFsYm5SektWc3dYU2s3WEc0Z0lIMWNibjA3WEc1Y2JtMXZaSFZzWlM1bGVIQnZjblJ6TG1aaGFXd2dQU0JtZFc1amRHbHZiaUFvYldWemMyRm5aU2tnZTF4dUlDQnNiMmNvYldWemMyRm5aU2s3WEc1OU8xeHVYRzV0YjJSMWJHVXVaWGh3YjNKMGN5NXZjSFJwYjI1SWRHMXNJRDBnWm5WdVkzUnBiMjRnS0d4aFltVnNMQ0IyWVd4MVpTa2dlMXh1SUNCeVpYUjFjbTRnSnp4MGNqNDhkR1FnWTJ4aGMzTTlYQ0p2Y0hScGIyNU9ZVzFsWENJK0p5QXJJR3hoWW1Wc0lDc2dKem84TDNSa1BqeDBaRDRuSUNzZ2RtRnNkV1VnS3lBblBDOTBaRDQ4TDNSeVBpYzdYRzU5TzF4dVhHNTJZWElnY21WemIyeDJaVk5qYUdWdFlTQTlJRzF2WkhWc1pTNWxlSEJ2Y25SekxuSmxjMjlzZG1WVFkyaGxiV0VnUFNCbWRXNWpkR2x2YmlBb2MyTm9aVzFoS1NCN1hHNGdJR2xtSUNoZkxtbHpVR3hoYVc1UFltcGxZM1FvYzJOb1pXMWhMbk5qYUdWdFlTa3BJSHRjYmlBZ0lDQnpZMmhsYldFZ1BTQnlaWE52YkhabFUyTm9aVzFoS0hOamFHVnRZUzV6WTJobGJXRXBPMXh1SUNCOVhHNWNiaUFnY21WMGRYSnVJSE5qYUdWdFlUdGNibjA3WEc1Y2JtMXZaSFZzWlM1bGVIQnZjblJ6TG5OcGJYQnNaVkpsWmlBOUlHWjFibU4wYVc5dUlDaHVZVzFsS1NCN1hHNGdJR2xtSUNoMGVYQmxiMllnYm1GdFpTQTlQVDBnSjNWdVpHVm1hVzVsWkNjcElIdGNiaUFnSUNCeVpYUjFjbTRnYm5Wc2JEdGNiaUFnZlZ4dVhHNGdJR2xtSUNodVlXMWxMbWx1WkdWNFQyWW9KeU12WkdWbWFXNXBkR2x2Ym5Ndkp5a2dQVDA5SURBcElIdGNiaUFnSUNCeVpYUjFjbTRnYm1GdFpTNXpkV0p6ZEhKcGJtY29KeU12WkdWbWFXNXBkR2x2Ym5Ndkp5NXNaVzVuZEdncE8xeHVJQ0I5SUdWc2MyVWdlMXh1SUNBZ0lISmxkSFZ5YmlCdVlXMWxPMXh1SUNCOVhHNTlPMXh1WEc0dktpcGNiaUFxSUdobGJIQmxjaUIwYnlCeVpXMXZkbVVnWlhoMFpXNXphVzl1Y3lCaGJtUWdZV1JrSUhSb1pXMGdkRzhnWVc0Z2IySnFaV04wWEc0Z0tseHVJQ29nUUhCaGNtRnRJR3RsZVc1aGJXVmNiaUFxSUVCd1lYSmhiU0J2WW1wY2JpQXFMMXh1Ylc5a2RXeGxMbVY0Y0c5eWRITXVaWGgwY21GamRFVjRkR1Z1YzJsdmJuTWdQU0JtZFc1amRHbHZiaUFvYTJWNWJtRnRaU3dnYjJKcUxDQjJZV3gxWlNrZ2UxeHVJQ0JwWmlnaGEyVjVibUZ0WlNCOGZDQWhiMkpxS1NCN1hHNGdJQ0FnY21WMGRYSnVPMXh1SUNCOVhHNWNiaUFnYVdZZ0tIUjVjR1Z2WmlCclpYbHVZVzFsSUQwOVBTQW5jM1J5YVc1bkp5QW1KaUJyWlhsdVlXMWxMbWx1WkdWNFQyWW9KM2d0SnlrZ1BUMDlJREFwSUh0Y2JpQWdJQ0J2WW1vdWRtVnVaRzl5UlhoMFpXNXphVzl1Y3lBOUlHOWlhaTUyWlc1a2IzSkZlSFJsYm5OcGIyNXpJSHg4SUh0OU8xeHVJQ0FnSUdsbUtIWmhiSFZsS1NCN1hHNGdJQ0FnSUNCdlltb3VkbVZ1Wkc5eVJYaDBaVzV6YVc5dWMxdHJaWGx1WVcxbFhTQTlJSFpoYkhWbE8xeHVJQ0FnSUgxY2JpQWdJQ0JsYkhObElIdGNiaUFnSUNBZ0lHOWlhaTUyWlc1a2IzSkZlSFJsYm5OcGIyNXpXMnRsZVc1aGJXVmRJRDBnYjJKcVcydGxlVzVoYldWZE8xeHVJQ0FnSUgxY2JpQWdmVnh1ZlRzaVhYMD0iLCIoZnVuY3Rpb24gKEJ1ZmZlcil7XG4ndXNlIHN0cmljdCc7XG5cbnZhciBoZWxwZXJzID0gcmVxdWlyZSgnLi9oZWxwZXJzJyk7XG52YXIgcmVxdWVzdCA9IHJlcXVpcmUoJ3N1cGVyYWdlbnQnKTtcbnZhciBqc3lhbWwgPSByZXF1aXJlKCdqcy15YW1sJyk7XG52YXIgXyA9IHtcbiAgaXNPYmplY3Q6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9pc09iamVjdCcpLFxuICBrZXlzOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L29iamVjdC9rZXlzJylcbn07XG5cbi8qXG4gKiBKUXVlcnlIdHRwQ2xpZW50IGlzIGEgbGlnaHQtd2VpZ2h0LCBub2RlIG9yIGJyb3dzZXIgSFRUUCBjbGllbnRcbiAqL1xudmFyIEpRdWVyeUh0dHBDbGllbnQgPSBmdW5jdGlvbiAoKSB7XG4gIHRoaXMudHlwZSA9ICdKUXVlcnlIdHRwQ2xpZW50Jztcbn07XG5cbi8qXG4gKiBTdXBlcmFnZW50SHR0cENsaWVudCBpcyBhIGxpZ2h0LXdlaWdodCwgbm9kZSBvciBicm93c2VyIEhUVFAgY2xpZW50XG4gKi9cbnZhciBTdXBlcmFnZW50SHR0cENsaWVudCA9IGZ1bmN0aW9uICgpIHtcbiAgdGhpcy50eXBlID0gJ1N1cGVyYWdlbnRIdHRwQ2xpZW50Jztcbn07XG5cbi8qKlxuICogU3dhZ2dlckh0dHAgaXMgYSB3cmFwcGVyIGZvciBleGVjdXRpbmcgcmVxdWVzdHNcbiAqL1xudmFyIFN3YWdnZXJIdHRwID0gbW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoKSB7fTtcblxuU3dhZ2dlckh0dHAucHJvdG90eXBlLmV4ZWN1dGUgPSBmdW5jdGlvbiAob2JqLCBvcHRzKSB7XG4gIHZhciBjbGllbnQ7XG5cbiAgaWYob3B0cyAmJiBvcHRzLmNsaWVudCkge1xuICAgIGNsaWVudCA9IG9wdHMuY2xpZW50O1xuICB9XG4gIGVsc2Uge1xuICAgIGNsaWVudCA9IG5ldyBTdXBlcmFnZW50SHR0cENsaWVudChvcHRzKTtcbiAgfVxuICBjbGllbnQub3B0cyA9IG9wdHMgfHwge307XG5cbiAgLy8gbGVnYWN5IHN1cHBvcnRcbiAgdmFyIGhhc0pRdWVyeSA9IGZhbHNlO1xuICBpZih0eXBlb2Ygd2luZG93ICE9PSAndW5kZWZpbmVkJykge1xuICAgIGlmKHR5cGVvZiB3aW5kb3cualF1ZXJ5ICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgaGFzSlF1ZXJ5ID0gdHJ1ZTtcbiAgICB9XG4gIH1cbiAgLy8gT1BUSU9OUyBzdXBwb3J0XG4gIGlmKG9iai5tZXRob2QudG9Mb3dlckNhc2UoKSA9PT0gJ29wdGlvbnMnICYmIGNsaWVudC50eXBlID09PSAnU3VwZXJhZ2VudEh0dHBDbGllbnQnKSB7XG4gICAgbG9nKCdmb3JjaW5nIGpRdWVyeSBhcyBPUFRJT05TIGFyZSBub3Qgc3VwcG9ydGVkIGJ5IFN1cGVyQWdlbnQnKTtcbiAgICBvYmoudXNlSlF1ZXJ5ID0gdHJ1ZTtcbiAgfVxuICBpZih0aGlzLmlzSW50ZXJuZXRFeHBsb3JlcigpICYmIChvYmoudXNlSlF1ZXJ5ID09PSBmYWxzZSB8fCAhaGFzSlF1ZXJ5ICkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ1Vuc3VwcG9ydGVkIGNvbmZpZ3VyYXRpb24hIEpRdWVyeSBpcyByZXF1aXJlZCBidXQgbm90IGF2YWlsYWJsZScpO1xuICB9XG4gIGlmICgob2JqICYmIG9iai51c2VKUXVlcnkgPT09IHRydWUpIHx8IHRoaXMuaXNJbnRlcm5ldEV4cGxvcmVyKCkgJiYgaGFzSlF1ZXJ5KSB7XG4gICAgY2xpZW50ID0gbmV3IEpRdWVyeUh0dHBDbGllbnQob3B0cyk7XG4gIH1cblxuICB2YXIgc3VjY2VzcyA9IG9iai5vbi5yZXNwb25zZTtcbiAgdmFyIGVycm9yID0gb2JqLm9uLmVycm9yO1xuXG4gIHZhciByZXF1ZXN0SW50ZXJjZXB0b3IgPSBmdW5jdGlvbihkYXRhKSB7XG4gICAgaWYob3B0cyAmJiBvcHRzLnJlcXVlc3RJbnRlcmNlcHRvcikge1xuICAgICAgZGF0YSA9IG9wdHMucmVxdWVzdEludGVyY2VwdG9yLmFwcGx5KGRhdGEpO1xuICAgIH1cbiAgICByZXR1cm4gZGF0YTtcbiAgfTtcblxuICB2YXIgcmVzcG9uc2VJbnRlcmNlcHRvciA9IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICBpZihvcHRzICYmIG9wdHMucmVzcG9uc2VJbnRlcmNlcHRvcikge1xuICAgICAgZGF0YSA9IG9wdHMucmVzcG9uc2VJbnRlcmNlcHRvci5hcHBseShkYXRhKTtcbiAgICB9XG4gICAgcmV0dXJuIHN1Y2Nlc3MoZGF0YSk7XG4gIH07XG5cbiAgdmFyIGVycm9ySW50ZXJjZXB0b3IgPSBmdW5jdGlvbihkYXRhKSB7XG4gICAgaWYob3B0cyAmJiBvcHRzLnJlc3BvbnNlSW50ZXJjZXB0b3IpIHtcbiAgICAgIGRhdGEgPSBvcHRzLnJlc3BvbnNlSW50ZXJjZXB0b3IuYXBwbHkoZGF0YSk7XG4gICAgfVxuICAgIGVycm9yKGRhdGEpO1xuICB9O1xuXG4gIG9iai5vbi5lcnJvciA9IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICBlcnJvckludGVyY2VwdG9yKGRhdGEpO1xuICB9O1xuXG4gIG9iai5vbi5yZXNwb25zZSA9IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICByZXNwb25zZUludGVyY2VwdG9yKGRhdGEpO1xuICB9O1xuXG4gIGlmIChfLmlzT2JqZWN0KG9iaikgJiYgXy5pc09iamVjdChvYmouYm9keSkpIHtcbiAgICAvLyBzcGVjaWFsIHByb2Nlc3NpbmcgZm9yIGZpbGUgdXBsb2FkcyB2aWEganF1ZXJ5XG4gICAgaWYgKG9iai5ib2R5LnR5cGUgJiYgb2JqLmJvZHkudHlwZSA9PT0gJ2Zvcm1EYXRhJyl7XG4gICAgICBpZihvcHRzLnVzZUpRdWVyeSkge1xuICAgICAgICBvYmouY29udGVudFR5cGUgPSBmYWxzZTtcbiAgICAgICAgb2JqLnByb2Nlc3NEYXRhID0gZmFsc2U7XG4gICAgICAgIGRlbGV0ZSBvYmouaGVhZGVyc1snQ29udGVudC1UeXBlJ107XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgb2JqID0gcmVxdWVzdEludGVyY2VwdG9yKG9iaikgfHwgb2JqO1xuICBpZiAob2JqLmJlZm9yZVNlbmQpIHtcbiAgICBvYmouYmVmb3JlU2VuZChmdW5jdGlvbihfb2JqKSB7XG4gICAgICBjbGllbnQuZXhlY3V0ZShfb2JqIHx8IG9iaik7XG4gICAgfSk7XG4gIH0gZWxzZSB7XG4gICAgY2xpZW50LmV4ZWN1dGUob2JqKTtcbiAgfVxuXG4gIHJldHVybiAob2JqLmRlZmVycmVkKSA/IG9iai5kZWZlcnJlZC5wcm9taXNlIDogb2JqO1xufTtcblxuU3dhZ2dlckh0dHAucHJvdG90eXBlLmlzSW50ZXJuZXRFeHBsb3JlciA9IGZ1bmN0aW9uICgpIHtcbiAgdmFyIGRldGVjdGVkSUUgPSBmYWxzZTtcblxuICBpZiAodHlwZW9mIG5hdmlnYXRvciAhPT0gJ3VuZGVmaW5lZCcgJiYgbmF2aWdhdG9yLnVzZXJBZ2VudCkge1xuICAgIHZhciBuYXYgPSBuYXZpZ2F0b3IudXNlckFnZW50LnRvTG93ZXJDYXNlKCk7XG5cbiAgICBpZiAobmF2LmluZGV4T2YoJ21zaWUnKSAhPT0gLTEpIHtcbiAgICAgIHZhciB2ZXJzaW9uID0gcGFyc2VJbnQobmF2LnNwbGl0KCdtc2llJylbMV0pO1xuXG4gICAgICBpZiAodmVyc2lvbiA8PSA4KSB7XG4gICAgICAgIGRldGVjdGVkSUUgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiBkZXRlY3RlZElFO1xufTtcblxuSlF1ZXJ5SHR0cENsaWVudC5wcm90b3R5cGUuZXhlY3V0ZSA9IGZ1bmN0aW9uIChvYmopIHtcbiAgdmFyIGpxID0gdGhpcy5qUXVlcnkgfHwgKHR5cGVvZiB3aW5kb3cgIT09ICd1bmRlZmluZWQnICYmIHdpbmRvdy5qUXVlcnkpO1xuICB2YXIgY2IgPSBvYmoub247XG4gIHZhciByZXF1ZXN0ID0gb2JqO1xuXG4gIGlmKHR5cGVvZiBqcSA9PT0gJ3VuZGVmaW5lZCcgfHwganEgPT09IGZhbHNlKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdVbnN1cHBvcnRlZCBjb25maWd1cmF0aW9uISBKUXVlcnkgaXMgcmVxdWlyZWQgYnV0IG5vdCBhdmFpbGFibGUnKTtcbiAgfVxuXG4gIG9iai50eXBlID0gb2JqLm1ldGhvZDtcbiAgb2JqLmNhY2hlID0gb2JqLmpxdWVyeUFqYXhDYWNoZTtcbiAgb2JqLmRhdGEgPSBvYmouYm9keTtcbiAgZGVsZXRlIG9iai5qcXVlcnlBamF4Q2FjaGU7XG4gIGRlbGV0ZSBvYmoudXNlSlF1ZXJ5O1xuICBkZWxldGUgb2JqLmJvZHk7XG5cbiAgb2JqLmNvbXBsZXRlID0gZnVuY3Rpb24gKHJlc3BvbnNlKSB7XG4gICAgdmFyIGhlYWRlcnMgPSB7fTtcbiAgICB2YXIgaGVhZGVyQXJyYXkgPSByZXNwb25zZS5nZXRBbGxSZXNwb25zZUhlYWRlcnMoKS5zcGxpdCgnXFxuJyk7XG5cbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGhlYWRlckFycmF5Lmxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgdG9TcGxpdCA9IGhlYWRlckFycmF5W2ldLnRyaW0oKTtcblxuICAgICAgaWYgKHRvU3BsaXQubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuXG4gICAgICB2YXIgc2VwYXJhdG9yID0gdG9TcGxpdC5pbmRleE9mKCc6Jyk7XG5cbiAgICAgIGlmIChzZXBhcmF0b3IgPT09IC0xKSB7XG4gICAgICAgIC8vIE5hbWUgYnV0IG5vIHZhbHVlIGluIHRoZSBoZWFkZXJcbiAgICAgICAgaGVhZGVyc1t0b1NwbGl0XSA9IG51bGw7XG5cbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIHZhciBuYW1lID0gdG9TcGxpdC5zdWJzdHJpbmcoMCwgc2VwYXJhdG9yKS50cmltKCk7XG4gICAgICB2YXIgdmFsdWUgPSB0b1NwbGl0LnN1YnN0cmluZyhzZXBhcmF0b3IgKyAxKS50cmltKCk7XG5cbiAgICAgIGhlYWRlcnNbbmFtZV0gPSB2YWx1ZTtcbiAgICB9XG5cbiAgICB2YXIgb3V0ID0ge1xuICAgICAgdXJsOiByZXF1ZXN0LnVybCxcbiAgICAgIG1ldGhvZDogcmVxdWVzdC5tZXRob2QsXG4gICAgICBzdGF0dXM6IHJlc3BvbnNlLnN0YXR1cyxcbiAgICAgIHN0YXR1c1RleHQ6IHJlc3BvbnNlLnN0YXR1c1RleHQsXG4gICAgICBkYXRhOiByZXNwb25zZS5yZXNwb25zZVRleHQsXG4gICAgICBoZWFkZXJzOiBoZWFkZXJzXG4gICAgfTtcblxuICAgIHRyeSB7XG4gICAgICB2YXIgcG9zc2libGVPYmogPSAgcmVzcG9uc2UucmVzcG9uc2VKU09OIHx8IGpzeWFtbC5zYWZlTG9hZChyZXNwb25zZS5yZXNwb25zZVRleHQpO1xuICAgICAgb3V0Lm9iaiA9ICh0eXBlb2YgcG9zc2libGVPYmogPT09ICdzdHJpbmcnKSA/IHt9IDogcG9zc2libGVPYmo7XG4gICAgfSBjYXRjaCAoZXgpIHtcbiAgICAgIC8vIGRvIG5vdCBzZXQgb3V0Lm9ialxuICAgICAgaGVscGVycy5sb2coJ3VuYWJsZSB0byBwYXJzZSBKU09OL1lBTUwgY29udGVudCcpO1xuICAgIH1cblxuICAgIC8vIEkgY2FuIHRocm93LCBvciBwYXJzZSBudWxsP1xuICAgIG91dC5vYmogPSBvdXQub2JqIHx8IG51bGw7XG5cbiAgICBpZiAocmVzcG9uc2Uuc3RhdHVzID49IDIwMCAmJiByZXNwb25zZS5zdGF0dXMgPCAzMDApIHtcbiAgICAgIGNiLnJlc3BvbnNlKG91dCk7XG4gICAgfSBlbHNlIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDAgfHwgKHJlc3BvbnNlLnN0YXR1cyA+PSA0MDAgJiYgcmVzcG9uc2Uuc3RhdHVzIDwgNTk5KSkge1xuICAgICAgY2IuZXJyb3Iob3V0KTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIGNiLnJlc3BvbnNlKG91dCk7XG4gICAgfVxuICB9O1xuXG4gIGpxLnN1cHBvcnQuY29ycyA9IHRydWU7XG5cbiAgcmV0dXJuIGpxLmFqYXgob2JqKTtcbn07XG5cblN1cGVyYWdlbnRIdHRwQ2xpZW50LnByb3RvdHlwZS5leGVjdXRlID0gZnVuY3Rpb24gKG9iaikge1xuICB2YXIgY29ubmVjdGlvbkFnZW50ID0gb2JqLmNvbm5lY3Rpb25BZ2VudDtcbiAgdmFyIG1ldGhvZCA9IG9iai5tZXRob2QudG9Mb3dlckNhc2UoKTtcbiAgdmFyIHRpbWVvdXQgPSBvYmoudGltZW91dDtcblxuICBpZiAobWV0aG9kID09PSAnZGVsZXRlJykge1xuICAgIG1ldGhvZCA9ICdkZWwnO1xuICB9XG4gIHZhciBoZWFkZXJzID0gb2JqLmhlYWRlcnMgfHwge307XG4gIHZhciByID0gcmVxdWVzdFttZXRob2RdKG9iai51cmwpO1xuXG4gIGlmIChjb25uZWN0aW9uQWdlbnQpIHtcbiAgICByLmFnZW50KGNvbm5lY3Rpb25BZ2VudCk7XG4gIH1cbiAgXG4gIGlmICh0aW1lb3V0KSB7XG4gICAgci50aW1lb3V0KHRpbWVvdXQpO1xuICB9XG5cbiAgaWYgKG9iai5lbmFibGVDb29raWVzKSB7XG4gICAgci53aXRoQ3JlZGVudGlhbHMoKTtcbiAgfVxuXG4gIHZhciBhY2NlcHQgPSBvYmouaGVhZGVycy5BY2NlcHQ7XG5cbiAgaWYodGhpcy5iaW5hcnlSZXF1ZXN0KGFjY2VwdCkpIHtcbiAgICByLm9uKCdyZXF1ZXN0JywgZnVuY3Rpb24gKCkge1xuICAgICAgaWYodGhpcy54aHIpIHtcbiAgICAgICAgdGhpcy54aHIucmVzcG9uc2VUeXBlID0gJ2Jsb2InO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG5cbiAgaWYob2JqLmJvZHkpIHtcbiAgICBpZihfLmlzT2JqZWN0KG9iai5ib2R5KSkge1xuICAgICAgdmFyIGNvbnRlbnRUeXBlID0gb2JqLmhlYWRlcnNbJ0NvbnRlbnQtVHlwZSddIHx8ICcnO1xuICAgICAgaWYgKGNvbnRlbnRUeXBlLmluZGV4T2YoJ211bHRpcGFydC9mb3JtLWRhdGEnKSA9PT0gMCkge1xuICAgICAgICBkZWxldGUgaGVhZGVyc1snQ29udGVudC1UeXBlJ107XG4gICAgICAgIGlmKHt9LnRvU3RyaW5nLmFwcGx5KG9iai5ib2R5KSA9PT0gJ1tvYmplY3QgRm9ybURhdGFdJykge1xuICAgICAgICAgIHIuc2VuZChvYmouYm9keSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgdmFyIGtleW5hbWUsIHZhbHVlLCB2O1xuICAgICAgICAgIGZvciAoa2V5bmFtZSBpbiBvYmouYm9keSkge1xuICAgICAgICAgICAgdmFsdWUgPSBvYmouYm9keVtrZXluYW1lXTtcbiAgICAgICAgICAgIGlmKEFycmF5LmlzQXJyYXkodmFsdWUpKSB7XG4gICAgICAgICAgICAgIGZvcih2IGluIHZhbHVlKSB7XG4gICAgICAgICAgICAgICAgci5maWVsZChrZXluYW1lLCB2KTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgIHIuZmllbGQoa2V5bmFtZSwgdmFsdWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgZWxzZSBpZiAoXy5pc09iamVjdChvYmouYm9keSkpIHtcbiAgICAgICAgLy8gbm9uIG11bHRpcGFydC9mb3JtLWRhdGFcbiAgICAgICAgb2JqLmJvZHkgPSBKU09OLnN0cmluZ2lmeShvYmouYm9keSk7XG4gICAgICAgIHIuc2VuZChvYmouYm9keSk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgci5zZW5kKG9iai5ib2R5KTtcbiAgICB9XG4gIH1cblxuICB2YXIgbmFtZTtcbiAgZm9yIChuYW1lIGluIGhlYWRlcnMpIHtcbiAgICByLnNldChuYW1lLCBoZWFkZXJzW25hbWVdKTtcbiAgfVxuXG4gIGlmKHR5cGVvZiByLmJ1ZmZlciA9PT0gJ2Z1bmN0aW9uJykge1xuICAgIHIuYnVmZmVyKCk7IC8vIGZvcmNlIHN1cGVyYWdlbnQgdG8gcG9wdWxhdGUgcmVzLnRleHQgd2l0aCB0aGUgcmF3IHJlc3BvbnNlIGRhdGFcbiAgfVxuXG4gIHIuZW5kKGZ1bmN0aW9uIChlcnIsIHJlcykge1xuICAgIHJlcyA9IHJlcyB8fCB7XG4gICAgICBzdGF0dXM6IDAsXG4gICAgICBoZWFkZXJzOiB7ZXJyb3I6ICdubyByZXNwb25zZSBmcm9tIHNlcnZlcid9XG4gICAgfTtcbiAgICB2YXIgcmVzcG9uc2UgPSB7XG4gICAgICB1cmw6IG9iai51cmwsXG4gICAgICBtZXRob2Q6IG9iai5tZXRob2QsXG4gICAgICBoZWFkZXJzOiByZXMuaGVhZGVyc1xuICAgIH07XG4gICAgdmFyIGNiO1xuXG4gICAgaWYgKCFlcnIgJiYgcmVzLmVycm9yKSB7XG4gICAgICBlcnIgPSByZXMuZXJyb3I7XG4gICAgfVxuXG4gICAgaWYgKGVyciAmJiBvYmoub24gJiYgb2JqLm9uLmVycm9yKSB7XG4gICAgICByZXNwb25zZS5lcnJPYmogPSBlcnI7XG4gICAgICByZXNwb25zZS5zdGF0dXMgPSByZXMgPyByZXMuc3RhdHVzIDogNTAwO1xuICAgICAgcmVzcG9uc2Uuc3RhdHVzVGV4dCA9IHJlcyA/IHJlcy50ZXh0IDogZXJyLm1lc3NhZ2U7XG4gICAgICBpZiAocmVzLmhlYWRlcnMgJiYgcmVzLmhlYWRlcnNbJ2NvbnRlbnQtdHlwZSddKSB7XG4gICAgICAgIGlmIChyZXMuaGVhZGVyc1snY29udGVudC10eXBlJ10uaW5kZXhPZignYXBwbGljYXRpb24vanNvbicpID49IDApIHtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgcmVzcG9uc2Uub2JqID0gSlNPTi5wYXJzZShyZXNwb25zZS5zdGF0dXNUZXh0KTtcbiAgICAgICAgICB9XG4gICAgICAgICAgY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHJlc3BvbnNlLm9iaiA9IG51bGw7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgICBjYiA9IG9iai5vbi5lcnJvcjtcbiAgICB9IGVsc2UgaWYgKHJlcyAmJiBvYmoub24gJiYgb2JqLm9uLnJlc3BvbnNlKSB7XG4gICAgICB2YXIgcG9zc2libGVPYmo7XG5cbiAgICAgIC8vIEFscmVhZHkgcGFyc2VkIGJ5IGJ5IHN1cGVyYWdlbnQ/XG4gICAgICBpZiAocmVzLmJvZHkgJiYgXy5rZXlzKHJlcy5ib2R5KS5sZW5ndGggPiAwKSB7XG4gICAgICAgIHBvc3NpYmxlT2JqID0gcmVzLmJvZHk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIHBvc3NpYmxlT2JqID0ganN5YW1sLnNhZmVMb2FkKHJlcy50ZXh0KTtcbiAgICAgICAgICAvLyBjYW4gcGFyc2UgaW50byBhIHN0cmluZy4uLiB3aGljaCB3ZSBkb24ndCBuZWVkIHJ1bm5pbmcgYXJvdW5kIGluIHRoZSBzeXN0ZW1cbiAgICAgICAgICBwb3NzaWJsZU9iaiA9ICh0eXBlb2YgcG9zc2libGVPYmogPT09ICdzdHJpbmcnKSA/IG51bGwgOiBwb3NzaWJsZU9iajtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgIGhlbHBlcnMubG9nKCdjYW5ub3QgcGFyc2UgSlNPTi9ZQU1MIGNvbnRlbnQnKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBudWxsIG1lYW5zIHdlIGNhbid0IHBhcnNlIGludG8gb2JqZWN0XG4gICAgICBpZih0eXBlb2YgQnVmZmVyID09PSAnZnVuY3Rpb24nICYmIEJ1ZmZlci5pc0J1ZmZlcihwb3NzaWJsZU9iaikpIHtcbiAgICAgICAgcmVzcG9uc2UuZGF0YSA9IHBvc3NpYmxlT2JqO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIHJlc3BvbnNlLm9iaiA9ICh0eXBlb2YgcG9zc2libGVPYmogPT09ICdvYmplY3QnKSA/IHBvc3NpYmxlT2JqIDogbnVsbDtcbiAgICAgIH1cblxuICAgICAgcmVzcG9uc2Uuc3RhdHVzID0gcmVzLnN0YXR1cztcbiAgICAgIHJlc3BvbnNlLnN0YXR1c1RleHQgPSByZXMudGV4dDtcbiAgICAgIGNiID0gb2JqLm9uLnJlc3BvbnNlO1xuICAgIH1cbiAgICBpZiAocmVzLnhociAmJiByZXMueGhyLnJlc3BvbnNlKSB7XG4gICAgICByZXNwb25zZS5kYXRhID0gcmVzLnhoci5yZXNwb25zZTtcbiAgICB9XG4gICAgZWxzZSBpZighcmVzcG9uc2UuZGF0YSkge1xuICAgICAgcmVzcG9uc2UuZGF0YSA9IHJlc3BvbnNlLnN0YXR1c1RleHQ7XG4gICAgfVxuXG4gICAgaWYgKGNiKSB7XG4gICAgICBjYihyZXNwb25zZSk7XG4gICAgfVxuICB9KTtcbn07XG5cblN1cGVyYWdlbnRIdHRwQ2xpZW50LnByb3RvdHlwZS4gYmluYXJ5UmVxdWVzdCA9IGZ1bmN0aW9uIChhY2NlcHQpIHtcbiAgaWYoIWFjY2VwdCkge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICByZXR1cm4gKC9eaW1hZ2UvaSkudGVzdChhY2NlcHQpIHx8ICgvXmFwcGxpY2F0aW9uXFwvcGRmLykudGVzdChhY2NlcHQpO1xufTtcblxufSkuY2FsbCh0aGlzLHJlcXVpcmUoXCJidWZmZXJcIikuQnVmZmVyKVxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9ZGF0YTphcHBsaWNhdGlvbi9qc29uO2NoYXJzZXQ6dXRmLTg7YmFzZTY0LGV5SjJaWEp6YVc5dUlqb3pMQ0p6YjNWeVkyVnpJanBiSW14cFlpOW9kSFJ3TG1weklsMHNJbTVoYldWeklqcGJYU3dpYldGd2NHbHVaM01pT2lJN1FVRkJRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEVpTENKbWFXeGxJam9pWjJWdVpYSmhkR1ZrTG1weklpd2ljMjkxY21ObFVtOXZkQ0k2SWlJc0luTnZkWEpqWlhORGIyNTBaVzUwSWpwYklpZDFjMlVnYzNSeWFXTjBKenRjYmx4dWRtRnlJR2hsYkhCbGNuTWdQU0J5WlhGMWFYSmxLQ2N1TDJobGJIQmxjbk1uS1R0Y2JuWmhjaUJ5WlhGMVpYTjBJRDBnY21WeGRXbHlaU2duYzNWd1pYSmhaMlZ1ZENjcE8xeHVkbUZ5SUdwemVXRnRiQ0E5SUhKbGNYVnBjbVVvSjJwekxYbGhiV3duS1R0Y2JuWmhjaUJmSUQwZ2UxeHVJQ0JwYzA5aWFtVmpkRG9nY21WeGRXbHlaU2duYkc5a1lYTm9MV052YlhCaGRDOXNZVzVuTDJselQySnFaV04wSnlrc1hHNGdJR3RsZVhNNklISmxjWFZwY21Vb0oyeHZaR0Z6YUMxamIyMXdZWFF2YjJKcVpXTjBMMnRsZVhNbktWeHVmVHRjYmx4dUx5cGNiaUFxSUVwUmRXVnllVWgwZEhCRGJHbGxiblFnYVhNZ1lTQnNhV2RvZEMxM1pXbG5hSFFzSUc1dlpHVWdiM0lnWW5KdmQzTmxjaUJJVkZSUUlHTnNhV1Z1ZEZ4dUlDb3ZYRzUyWVhJZ1NsRjFaWEo1U0hSMGNFTnNhV1Z1ZENBOUlHWjFibU4wYVc5dUlDZ3BJSHRjYmlBZ2RHaHBjeTUwZVhCbElEMGdKMHBSZFdWeWVVaDBkSEJEYkdsbGJuUW5PMXh1ZlR0Y2JseHVMeXBjYmlBcUlGTjFjR1Z5WVdkbGJuUklkSFJ3UTJ4cFpXNTBJR2x6SUdFZ2JHbG5hSFF0ZDJWcFoyaDBMQ0J1YjJSbElHOXlJR0p5YjNkelpYSWdTRlJVVUNCamJHbGxiblJjYmlBcUwxeHVkbUZ5SUZOMWNHVnlZV2RsYm5SSWRIUndRMnhwWlc1MElEMGdablZ1WTNScGIyNGdLQ2tnZTF4dUlDQjBhR2x6TG5SNWNHVWdQU0FuVTNWd1pYSmhaMlZ1ZEVoMGRIQkRiR2xsYm5Rbk8xeHVmVHRjYmx4dUx5b3FYRzRnS2lCVGQyRm5aMlZ5U0hSMGNDQnBjeUJoSUhkeVlYQndaWElnWm05eUlHVjRaV04xZEdsdVp5QnlaWEYxWlhOMGMxeHVJQ292WEc1MllYSWdVM2RoWjJkbGNraDBkSEFnUFNCdGIyUjFiR1V1Wlhod2IzSjBjeUE5SUdaMWJtTjBhVzl1SUNncElIdDlPMXh1WEc1VGQyRm5aMlZ5U0hSMGNDNXdjbTkwYjNSNWNHVXVaWGhsWTNWMFpTQTlJR1oxYm1OMGFXOXVJQ2h2WW1vc0lHOXdkSE1wSUh0Y2JpQWdkbUZ5SUdOc2FXVnVkRHRjYmx4dUlDQnBaaWh2Y0hSeklDWW1JRzl3ZEhNdVkyeHBaVzUwS1NCN1hHNGdJQ0FnWTJ4cFpXNTBJRDBnYjNCMGN5NWpiR2xsYm5RN1hHNGdJSDFjYmlBZ1pXeHpaU0I3WEc0Z0lDQWdZMnhwWlc1MElEMGdibVYzSUZOMWNHVnlZV2RsYm5SSWRIUndRMnhwWlc1MEtHOXdkSE1wTzF4dUlDQjlYRzRnSUdOc2FXVnVkQzV2Y0hSeklEMGdiM0IwY3lCOGZDQjdmVHRjYmx4dUlDQXZMeUJzWldkaFkza2djM1Z3Y0c5eWRGeHVJQ0IyWVhJZ2FHRnpTbEYxWlhKNUlEMGdabUZzYzJVN1hHNGdJR2xtS0hSNWNHVnZaaUIzYVc1a2IzY2dJVDA5SUNkMWJtUmxabWx1WldRbktTQjdYRzRnSUNBZ2FXWW9kSGx3Wlc5bUlIZHBibVJ2ZHk1cVVYVmxjbmtnSVQwOUlDZDFibVJsWm1sdVpXUW5LU0I3WEc0Z0lDQWdJQ0JvWVhOS1VYVmxjbmtnUFNCMGNuVmxPMXh1SUNBZ0lIMWNiaUFnZlZ4dUlDQXZMeUJQVUZSSlQwNVRJSE4xY0hCdmNuUmNiaUFnYVdZb2IySnFMbTFsZEdodlpDNTBiMHh2ZDJWeVEyRnpaU2dwSUQwOVBTQW5iM0IwYVc5dWN5Y2dKaVlnWTJ4cFpXNTBMblI1Y0dVZ1BUMDlJQ2RUZFhCbGNtRm5aVzUwU0hSMGNFTnNhV1Z1ZENjcElIdGNiaUFnSUNCc2IyY29KMlp2Y21OcGJtY2dhbEYxWlhKNUlHRnpJRTlRVkVsUFRsTWdZWEpsSUc1dmRDQnpkWEJ3YjNKMFpXUWdZbmtnVTNWd1pYSkJaMlZ1ZENjcE8xeHVJQ0FnSUc5aWFpNTFjMlZLVVhWbGNua2dQU0IwY25WbE8xeHVJQ0I5WEc0Z0lHbG1LSFJvYVhNdWFYTkpiblJsY201bGRFVjRjR3h2Y21WeUtDa2dKaVlnS0c5aWFpNTFjMlZLVVhWbGNua2dQVDA5SUdaaGJITmxJSHg4SUNGb1lYTktVWFZsY25rZ0tTa2dlMXh1SUNBZ0lIUm9jbTkzSUc1bGR5QkZjbkp2Y2lnblZXNXpkWEJ3YjNKMFpXUWdZMjl1Wm1sbmRYSmhkR2x2YmlFZ1NsRjFaWEo1SUdseklISmxjWFZwY21Wa0lHSjFkQ0J1YjNRZ1lYWmhhV3hoWW14bEp5azdYRzRnSUgxY2JpQWdhV1lnS0Nodlltb2dKaVlnYjJKcUxuVnpaVXBSZFdWeWVTQTlQVDBnZEhKMVpTa2dmSHdnZEdocGN5NXBjMGx1ZEdWeWJtVjBSWGh3Ykc5eVpYSW9LU0FtSmlCb1lYTktVWFZsY25rcElIdGNiaUFnSUNCamJHbGxiblFnUFNCdVpYY2dTbEYxWlhKNVNIUjBjRU5zYVdWdWRDaHZjSFJ6S1R0Y2JpQWdmVnh1WEc0Z0lIWmhjaUJ6ZFdOalpYTnpJRDBnYjJKcUxtOXVMbkpsYzNCdmJuTmxPMXh1SUNCMllYSWdaWEp5YjNJZ1BTQnZZbW91YjI0dVpYSnliM0k3WEc1Y2JpQWdkbUZ5SUhKbGNYVmxjM1JKYm5SbGNtTmxjSFJ2Y2lBOUlHWjFibU4wYVc5dUtHUmhkR0VwSUh0Y2JpQWdJQ0JwWmlodmNIUnpJQ1ltSUc5d2RITXVjbVZ4ZFdWemRFbHVkR1Z5WTJWd2RHOXlLU0I3WEc0Z0lDQWdJQ0JrWVhSaElEMGdiM0IwY3k1eVpYRjFaWE4wU1c1MFpYSmpaWEIwYjNJdVlYQndiSGtvWkdGMFlTazdYRzRnSUNBZ2ZWeHVJQ0FnSUhKbGRIVnliaUJrWVhSaE8xeHVJQ0I5TzF4dVhHNGdJSFpoY2lCeVpYTndiMjV6WlVsdWRHVnlZMlZ3ZEc5eUlEMGdablZ1WTNScGIyNG9aR0YwWVNrZ2UxeHVJQ0FnSUdsbUtHOXdkSE1nSmlZZ2IzQjBjeTV5WlhOd2IyNXpaVWx1ZEdWeVkyVndkRzl5S1NCN1hHNGdJQ0FnSUNCa1lYUmhJRDBnYjNCMGN5NXlaWE53YjI1elpVbHVkR1Z5WTJWd2RHOXlMbUZ3Y0d4NUtHUmhkR0VwTzF4dUlDQWdJSDFjYmlBZ0lDQnlaWFIxY200Z2MzVmpZMlZ6Y3loa1lYUmhLVHRjYmlBZ2ZUdGNibHh1SUNCMllYSWdaWEp5YjNKSmJuUmxjbU5sY0hSdmNpQTlJR1oxYm1OMGFXOXVLR1JoZEdFcElIdGNiaUFnSUNCcFppaHZjSFJ6SUNZbUlHOXdkSE11Y21WemNHOXVjMlZKYm5SbGNtTmxjSFJ2Y2lrZ2UxeHVJQ0FnSUNBZ1pHRjBZU0E5SUc5d2RITXVjbVZ6Y0c5dWMyVkpiblJsY21ObGNIUnZjaTVoY0hCc2VTaGtZWFJoS1R0Y2JpQWdJQ0I5WEc0Z0lDQWdaWEp5YjNJb1pHRjBZU2s3WEc0Z0lIMDdYRzVjYmlBZ2IySnFMbTl1TG1WeWNtOXlJRDBnWm5WdVkzUnBiMjRvWkdGMFlTa2dlMXh1SUNBZ0lHVnljbTl5U1c1MFpYSmpaWEIwYjNJb1pHRjBZU2s3WEc0Z0lIMDdYRzVjYmlBZ2IySnFMbTl1TG5KbGMzQnZibk5sSUQwZ1puVnVZM1JwYjI0b1pHRjBZU2tnZTF4dUlDQWdJSEpsYzNCdmJuTmxTVzUwWlhKalpYQjBiM0lvWkdGMFlTazdYRzRnSUgwN1hHNWNiaUFnYVdZZ0tGOHVhWE5QWW1wbFkzUW9iMkpxS1NBbUppQmZMbWx6VDJKcVpXTjBLRzlpYWk1aWIyUjVLU2tnZTF4dUlDQWdJQzh2SUhOd1pXTnBZV3dnY0hKdlkyVnpjMmx1WnlCbWIzSWdabWxzWlNCMWNHeHZZV1J6SUhacFlTQnFjWFZsY25sY2JpQWdJQ0JwWmlBb2IySnFMbUp2WkhrdWRIbHdaU0FtSmlCdlltb3VZbTlrZVM1MGVYQmxJRDA5UFNBblptOXliVVJoZEdFbktYdGNiaUFnSUNBZ0lHbG1LRzl3ZEhNdWRYTmxTbEYxWlhKNUtTQjdYRzRnSUNBZ0lDQWdJRzlpYWk1amIyNTBaVzUwVkhsd1pTQTlJR1poYkhObE8xeHVJQ0FnSUNBZ0lDQnZZbW91Y0hKdlkyVnpjMFJoZEdFZ1BTQm1ZV3h6WlR0Y2JpQWdJQ0FnSUNBZ1pHVnNaWFJsSUc5aWFpNW9aV0ZrWlhKeld5ZERiMjUwWlc1MExWUjVjR1VuWFR0Y2JpQWdJQ0FnSUgxY2JpQWdJQ0I5WEc0Z0lIMWNibHh1SUNCdlltb2dQU0J5WlhGMVpYTjBTVzUwWlhKalpYQjBiM0lvYjJKcUtTQjhmQ0J2WW1vN1hHNGdJR2xtSUNodlltb3VZbVZtYjNKbFUyVnVaQ2tnZTF4dUlDQWdJRzlpYWk1aVpXWnZjbVZUWlc1a0tHWjFibU4wYVc5dUtGOXZZbW9wSUh0Y2JpQWdJQ0FnSUdOc2FXVnVkQzVsZUdWamRYUmxLRjl2WW1vZ2ZId2diMkpxS1R0Y2JpQWdJQ0I5S1R0Y2JpQWdmU0JsYkhObElIdGNiaUFnSUNCamJHbGxiblF1WlhobFkzVjBaU2h2WW1vcE8xeHVJQ0I5WEc1Y2JpQWdjbVYwZFhKdUlDaHZZbW91WkdWbVpYSnlaV1FwSUQ4Z2IySnFMbVJsWm1WeWNtVmtMbkJ5YjIxcGMyVWdPaUJ2WW1vN1hHNTlPMXh1WEc1VGQyRm5aMlZ5U0hSMGNDNXdjbTkwYjNSNWNHVXVhWE5KYm5SbGNtNWxkRVY0Y0d4dmNtVnlJRDBnWm5WdVkzUnBiMjRnS0NrZ2UxeHVJQ0IyWVhJZ1pHVjBaV04wWldSSlJTQTlJR1poYkhObE8xeHVYRzRnSUdsbUlDaDBlWEJsYjJZZ2JtRjJhV2RoZEc5eUlDRTlQU0FuZFc1a1pXWnBibVZrSnlBbUppQnVZWFpwWjJGMGIzSXVkWE5sY2tGblpXNTBLU0I3WEc0Z0lDQWdkbUZ5SUc1aGRpQTlJRzVoZG1sbllYUnZjaTUxYzJWeVFXZGxiblF1ZEc5TWIzZGxja05oYzJVb0tUdGNibHh1SUNBZ0lHbG1JQ2h1WVhZdWFXNWtaWGhQWmlnbmJYTnBaU2NwSUNFOVBTQXRNU2tnZTF4dUlDQWdJQ0FnZG1GeUlIWmxjbk5wYjI0Z1BTQndZWEp6WlVsdWRDaHVZWFl1YzNCc2FYUW9KMjF6YVdVbktWc3hYU2s3WEc1Y2JpQWdJQ0FnSUdsbUlDaDJaWEp6YVc5dUlEdzlJRGdwSUh0Y2JpQWdJQ0FnSUNBZ1pHVjBaV04wWldSSlJTQTlJSFJ5ZFdVN1hHNGdJQ0FnSUNCOVhHNGdJQ0FnZlZ4dUlDQjlYRzVjYmlBZ2NtVjBkWEp1SUdSbGRHVmpkR1ZrU1VVN1hHNTlPMXh1WEc1S1VYVmxjbmxJZEhSd1EyeHBaVzUwTG5CeWIzUnZkSGx3WlM1bGVHVmpkWFJsSUQwZ1puVnVZM1JwYjI0Z0tHOWlhaWtnZTF4dUlDQjJZWElnYW5FZ1BTQjBhR2x6TG1wUmRXVnllU0I4ZkNBb2RIbHdaVzltSUhkcGJtUnZkeUFoUFQwZ0ozVnVaR1ZtYVc1bFpDY2dKaVlnZDJsdVpHOTNMbXBSZFdWeWVTazdYRzRnSUhaaGNpQmpZaUE5SUc5aWFpNXZianRjYmlBZ2RtRnlJSEpsY1hWbGMzUWdQU0J2WW1vN1hHNWNiaUFnYVdZb2RIbHdaVzltSUdweElEMDlQU0FuZFc1a1pXWnBibVZrSnlCOGZDQnFjU0E5UFQwZ1ptRnNjMlVwSUh0Y2JpQWdJQ0IwYUhKdmR5QnVaWGNnUlhKeWIzSW9KMVZ1YzNWd2NHOXlkR1ZrSUdOdmJtWnBaM1Z5WVhScGIyNGhJRXBSZFdWeWVTQnBjeUJ5WlhGMWFYSmxaQ0JpZFhRZ2JtOTBJR0YyWVdsc1lXSnNaU2NwTzF4dUlDQjlYRzVjYmlBZ2IySnFMblI1Y0dVZ1BTQnZZbW91YldWMGFHOWtPMXh1SUNCdlltb3VZMkZqYUdVZ1BTQnZZbW91YW5GMVpYSjVRV3BoZUVOaFkyaGxPMXh1SUNCdlltb3VaR0YwWVNBOUlHOWlhaTVpYjJSNU8xeHVJQ0JrWld4bGRHVWdiMkpxTG1weGRXVnllVUZxWVhoRFlXTm9aVHRjYmlBZ1pHVnNaWFJsSUc5aWFpNTFjMlZLVVhWbGNuazdYRzRnSUdSbGJHVjBaU0J2WW1vdVltOWtlVHRjYmx4dUlDQnZZbW91WTI5dGNHeGxkR1VnUFNCbWRXNWpkR2x2YmlBb2NtVnpjRzl1YzJVcElIdGNiaUFnSUNCMllYSWdhR1ZoWkdWeWN5QTlJSHQ5TzF4dUlDQWdJSFpoY2lCb1pXRmtaWEpCY25KaGVTQTlJSEpsYzNCdmJuTmxMbWRsZEVGc2JGSmxjM0J2Ym5ObFNHVmhaR1Z5Y3lncExuTndiR2wwS0NkY1hHNG5LVHRjYmx4dUlDQWdJR1p2Y2lBb2RtRnlJR2tnUFNBd095QnBJRHdnYUdWaFpHVnlRWEp5WVhrdWJHVnVaM1JvT3lCcEt5c3BJSHRjYmlBZ0lDQWdJSFpoY2lCMGIxTndiR2wwSUQwZ2FHVmhaR1Z5UVhKeVlYbGJhVjB1ZEhKcGJTZ3BPMXh1WEc0Z0lDQWdJQ0JwWmlBb2RHOVRjR3hwZEM1c1pXNW5kR2dnUFQwOUlEQXBJSHRjYmlBZ0lDQWdJQ0FnWTI5dWRHbHVkV1U3WEc0Z0lDQWdJQ0I5WEc1Y2JpQWdJQ0FnSUhaaGNpQnpaWEJoY21GMGIzSWdQU0IwYjFOd2JHbDBMbWx1WkdWNFQyWW9Kem9uS1R0Y2JseHVJQ0FnSUNBZ2FXWWdLSE5sY0dGeVlYUnZjaUE5UFQwZ0xURXBJSHRjYmlBZ0lDQWdJQ0FnTHk4Z1RtRnRaU0JpZFhRZ2JtOGdkbUZzZFdVZ2FXNGdkR2hsSUdobFlXUmxjbHh1SUNBZ0lDQWdJQ0JvWldGa1pYSnpXM1J2VTNCc2FYUmRJRDBnYm5Wc2JEdGNibHh1SUNBZ0lDQWdJQ0JqYjI1MGFXNTFaVHRjYmlBZ0lDQWdJSDFjYmx4dUlDQWdJQ0FnZG1GeUlHNWhiV1VnUFNCMGIxTndiR2wwTG5OMVluTjBjbWx1Wnlnd0xDQnpaWEJoY21GMGIzSXBMblJ5YVcwb0tUdGNiaUFnSUNBZ0lIWmhjaUIyWVd4MVpTQTlJSFJ2VTNCc2FYUXVjM1ZpYzNSeWFXNW5LSE5sY0dGeVlYUnZjaUFySURFcExuUnlhVzBvS1R0Y2JseHVJQ0FnSUNBZ2FHVmhaR1Z5YzF0dVlXMWxYU0E5SUhaaGJIVmxPMXh1SUNBZ0lIMWNibHh1SUNBZ0lIWmhjaUJ2ZFhRZ1BTQjdYRzRnSUNBZ0lDQjFjbXc2SUhKbGNYVmxjM1F1ZFhKc0xGeHVJQ0FnSUNBZ2JXVjBhRzlrT2lCeVpYRjFaWE4wTG0xbGRHaHZaQ3hjYmlBZ0lDQWdJSE4wWVhSMWN6b2djbVZ6Y0c5dWMyVXVjM1JoZEhWekxGeHVJQ0FnSUNBZ2MzUmhkSFZ6VkdWNGREb2djbVZ6Y0c5dWMyVXVjM1JoZEhWelZHVjRkQ3hjYmlBZ0lDQWdJR1JoZEdFNklISmxjM0J2Ym5ObExuSmxjM0J2Ym5ObFZHVjRkQ3hjYmlBZ0lDQWdJR2hsWVdSbGNuTTZJR2hsWVdSbGNuTmNiaUFnSUNCOU8xeHVYRzRnSUNBZ2RISjVJSHRjYmlBZ0lDQWdJSFpoY2lCd2IzTnphV0pzWlU5aWFpQTlJQ0J5WlhOd2IyNXpaUzV5WlhOd2IyNXpaVXBUVDA0Z2ZId2dhbk41WVcxc0xuTmhabVZNYjJGa0tISmxjM0J2Ym5ObExuSmxjM0J2Ym5ObFZHVjRkQ2s3WEc0Z0lDQWdJQ0J2ZFhRdWIySnFJRDBnS0hSNWNHVnZaaUJ3YjNOemFXSnNaVTlpYWlBOVBUMGdKM04wY21sdVp5Y3BJRDhnZTMwZ09pQndiM056YVdKc1pVOWlhanRjYmlBZ0lDQjlJR05oZEdOb0lDaGxlQ2tnZTF4dUlDQWdJQ0FnTHk4Z1pHOGdibTkwSUhObGRDQnZkWFF1YjJKcVhHNGdJQ0FnSUNCb1pXeHdaWEp6TG14dlp5Z25kVzVoWW14bElIUnZJSEJoY25ObElFcFRUMDR2V1VGTlRDQmpiMjUwWlc1MEp5azdYRzRnSUNBZ2ZWeHVYRzRnSUNBZ0x5OGdTU0JqWVc0Z2RHaHliM2NzSUc5eUlIQmhjbk5sSUc1MWJHdy9YRzRnSUNBZ2IzVjBMbTlpYWlBOUlHOTFkQzV2WW1vZ2ZId2diblZzYkR0Y2JseHVJQ0FnSUdsbUlDaHlaWE53YjI1elpTNXpkR0YwZFhNZ1BqMGdNakF3SUNZbUlISmxjM0J2Ym5ObExuTjBZWFIxY3lBOElETXdNQ2tnZTF4dUlDQWdJQ0FnWTJJdWNtVnpjRzl1YzJVb2IzVjBLVHRjYmlBZ0lDQjlJR1ZzYzJVZ2FXWWdLSEpsYzNCdmJuTmxMbk4wWVhSMWN5QTlQVDBnTUNCOGZDQW9jbVZ6Y0c5dWMyVXVjM1JoZEhWeklENDlJRFF3TUNBbUppQnlaWE53YjI1elpTNXpkR0YwZFhNZ1BDQTFPVGtwS1NCN1hHNGdJQ0FnSUNCallpNWxjbkp2Y2lodmRYUXBPMXh1SUNBZ0lIMGdaV3h6WlNCN1hHNGdJQ0FnSUNCeVpYUjFjbTRnWTJJdWNtVnpjRzl1YzJVb2IzVjBLVHRjYmlBZ0lDQjlYRzRnSUgwN1hHNWNiaUFnYW5FdWMzVndjRzl5ZEM1amIzSnpJRDBnZEhKMVpUdGNibHh1SUNCeVpYUjFjbTRnYW5FdVlXcGhlQ2h2WW1vcE8xeHVmVHRjYmx4dVUzVndaWEpoWjJWdWRFaDBkSEJEYkdsbGJuUXVjSEp2ZEc5MGVYQmxMbVY0WldOMWRHVWdQU0JtZFc1amRHbHZiaUFvYjJKcUtTQjdYRzRnSUhaaGNpQmpiMjV1WldOMGFXOXVRV2RsYm5RZ1BTQnZZbW91WTI5dWJtVmpkR2x2YmtGblpXNTBPMXh1SUNCMllYSWdiV1YwYUc5a0lEMGdiMkpxTG0xbGRHaHZaQzUwYjB4dmQyVnlRMkZ6WlNncE8xeHVJQ0IyWVhJZ2RHbHRaVzkxZENBOUlHOWlhaTUwYVcxbGIzVjBPMXh1WEc0Z0lHbG1JQ2h0WlhSb2IyUWdQVDA5SUNka1pXeGxkR1VuS1NCN1hHNGdJQ0FnYldWMGFHOWtJRDBnSjJSbGJDYzdYRzRnSUgxY2JpQWdkbUZ5SUdobFlXUmxjbk1nUFNCdlltb3VhR1ZoWkdWeWN5QjhmQ0I3ZlR0Y2JpQWdkbUZ5SUhJZ1BTQnlaWEYxWlhOMFcyMWxkR2h2WkYwb2IySnFMblZ5YkNrN1hHNWNiaUFnYVdZZ0tHTnZibTVsWTNScGIyNUJaMlZ1ZENrZ2UxeHVJQ0FnSUhJdVlXZGxiblFvWTI5dWJtVmpkR2x2YmtGblpXNTBLVHRjYmlBZ2ZWeHVJQ0JjYmlBZ2FXWWdLSFJwYldWdmRYUXBJSHRjYmlBZ0lDQnlMblJwYldWdmRYUW9kR2x0Wlc5MWRDazdYRzRnSUgxY2JseHVJQ0JwWmlBb2IySnFMbVZ1WVdKc1pVTnZiMnRwWlhNcElIdGNiaUFnSUNCeUxuZHBkR2hEY21Wa1pXNTBhV0ZzY3lncE8xeHVJQ0I5WEc1Y2JpQWdkbUZ5SUdGalkyVndkQ0E5SUc5aWFpNW9aV0ZrWlhKekxrRmpZMlZ3ZER0Y2JseHVJQ0JwWmloMGFHbHpMbUpwYm1GeWVWSmxjWFZsYzNRb1lXTmpaWEIwS1NrZ2UxeHVJQ0FnSUhJdWIyNG9KM0psY1hWbGMzUW5MQ0JtZFc1amRHbHZiaUFvS1NCN1hHNGdJQ0FnSUNCcFppaDBhR2x6TG5ob2Npa2dlMXh1SUNBZ0lDQWdJQ0IwYUdsekxuaG9jaTV5WlhOd2IyNXpaVlI1Y0dVZ1BTQW5ZbXh2WWljN1hHNGdJQ0FnSUNCOVhHNGdJQ0FnZlNrN1hHNGdJSDFjYmx4dUlDQnBaaWh2WW1vdVltOWtlU2tnZTF4dUlDQWdJR2xtS0Y4dWFYTlBZbXBsWTNRb2IySnFMbUp2WkhrcEtTQjdYRzRnSUNBZ0lDQjJZWElnWTI5dWRHVnVkRlI1Y0dVZ1BTQnZZbW91YUdWaFpHVnljMXNuUTI5dWRHVnVkQzFVZVhCbEoxMGdmSHdnSnljN1hHNGdJQ0FnSUNCcFppQW9ZMjl1ZEdWdWRGUjVjR1V1YVc1a1pYaFBaaWduYlhWc2RHbHdZWEowTDJadmNtMHRaR0YwWVNjcElEMDlQU0F3S1NCN1hHNGdJQ0FnSUNBZ0lHUmxiR1YwWlNCb1pXRmtaWEp6V3lkRGIyNTBaVzUwTFZSNWNHVW5YVHRjYmlBZ0lDQWdJQ0FnYVdZb2UzMHVkRzlUZEhKcGJtY3VZWEJ3Ykhrb2IySnFMbUp2WkhrcElEMDlQU0FuVzI5aWFtVmpkQ0JHYjNKdFJHRjBZVjBuS1NCN1hHNGdJQ0FnSUNBZ0lDQWdjaTV6Wlc1a0tHOWlhaTVpYjJSNUtUdGNiaUFnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0JsYkhObElIdGNiaUFnSUNBZ0lDQWdJQ0IyWVhJZ2EyVjVibUZ0WlN3Z2RtRnNkV1VzSUhZN1hHNGdJQ0FnSUNBZ0lDQWdabTl5SUNoclpYbHVZVzFsSUdsdUlHOWlhaTVpYjJSNUtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCMllXeDFaU0E5SUc5aWFpNWliMlI1VzJ0bGVXNWhiV1ZkTzF4dUlDQWdJQ0FnSUNBZ0lDQWdhV1lvUVhKeVlYa3VhWE5CY25KaGVTaDJZV3gxWlNrcElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ1ptOXlLSFlnYVc0Z2RtRnNkV1VwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCeUxtWnBaV3hrS0d0bGVXNWhiV1VzSUhZcE8xeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNCOVhHNGdJQ0FnSUNBZ0lDQWdJQ0I5WEc0Z0lDQWdJQ0FnSUNBZ0lDQmxiSE5sSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnY2k1bWFXVnNaQ2hyWlhsdVlXMWxMQ0IyWVd4MVpTazdYRzRnSUNBZ0lDQWdJQ0FnSUNCOVhHNGdJQ0FnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0I5WEc0Z0lDQWdJQ0I5WEc0Z0lDQWdJQ0JsYkhObElHbG1JQ2hmTG1selQySnFaV04wS0c5aWFpNWliMlI1S1NrZ2UxeHVJQ0FnSUNBZ0lDQXZMeUJ1YjI0Z2JYVnNkR2x3WVhKMEwyWnZjbTB0WkdGMFlWeHVJQ0FnSUNBZ0lDQnZZbW91WW05a2VTQTlJRXBUVDA0dWMzUnlhVzVuYVdaNUtHOWlhaTVpYjJSNUtUdGNiaUFnSUNBZ0lDQWdjaTV6Wlc1a0tHOWlhaTVpYjJSNUtUdGNiaUFnSUNBZ0lIMWNiaUFnSUNCOVhHNGdJQ0FnWld4elpTQjdYRzRnSUNBZ0lDQnlMbk5sYm1Rb2IySnFMbUp2WkhrcE8xeHVJQ0FnSUgxY2JpQWdmVnh1WEc0Z0lIWmhjaUJ1WVcxbE8xeHVJQ0JtYjNJZ0tHNWhiV1VnYVc0Z2FHVmhaR1Z5Y3lrZ2UxeHVJQ0FnSUhJdWMyVjBLRzVoYldVc0lHaGxZV1JsY25OYmJtRnRaVjBwTzF4dUlDQjlYRzVjYmlBZ2FXWW9kSGx3Wlc5bUlISXVZblZtWm1WeUlEMDlQU0FuWm5WdVkzUnBiMjRuS1NCN1hHNGdJQ0FnY2k1aWRXWm1aWElvS1RzZ0x5OGdabTl5WTJVZ2MzVndaWEpoWjJWdWRDQjBieUJ3YjNCMWJHRjBaU0J5WlhNdWRHVjRkQ0IzYVhSb0lIUm9aU0J5WVhjZ2NtVnpjRzl1YzJVZ1pHRjBZVnh1SUNCOVhHNWNiaUFnY2k1bGJtUW9ablZ1WTNScGIyNGdLR1Z5Y2l3Z2NtVnpLU0I3WEc0Z0lDQWdjbVZ6SUQwZ2NtVnpJSHg4SUh0Y2JpQWdJQ0FnSUhOMFlYUjFjem9nTUN4Y2JpQWdJQ0FnSUdobFlXUmxjbk02SUh0bGNuSnZjam9nSjI1dklISmxjM0J2Ym5ObElHWnliMjBnYzJWeWRtVnlKMzFjYmlBZ0lDQjlPMXh1SUNBZ0lIWmhjaUJ5WlhOd2IyNXpaU0E5SUh0Y2JpQWdJQ0FnSUhWeWJEb2diMkpxTG5WeWJDeGNiaUFnSUNBZ0lHMWxkR2h2WkRvZ2IySnFMbTFsZEdodlpDeGNiaUFnSUNBZ0lHaGxZV1JsY25NNklISmxjeTVvWldGa1pYSnpYRzRnSUNBZ2ZUdGNiaUFnSUNCMllYSWdZMkk3WEc1Y2JpQWdJQ0JwWmlBb0lXVnljaUFtSmlCeVpYTXVaWEp5YjNJcElIdGNiaUFnSUNBZ0lHVnljaUE5SUhKbGN5NWxjbkp2Y2p0Y2JpQWdJQ0I5WEc1Y2JpQWdJQ0JwWmlBb1pYSnlJQ1ltSUc5aWFpNXZiaUFtSmlCdlltb3ViMjR1WlhKeWIzSXBJSHRjYmlBZ0lDQWdJSEpsYzNCdmJuTmxMbVZ5Y2s5aWFpQTlJR1Z5Y2p0Y2JpQWdJQ0FnSUhKbGMzQnZibk5sTG5OMFlYUjFjeUE5SUhKbGN5QS9JSEpsY3k1emRHRjBkWE1nT2lBMU1EQTdYRzRnSUNBZ0lDQnlaWE53YjI1elpTNXpkR0YwZFhOVVpYaDBJRDBnY21WeklEOGdjbVZ6TG5SbGVIUWdPaUJsY25JdWJXVnpjMkZuWlR0Y2JpQWdJQ0FnSUdsbUlDaHlaWE11YUdWaFpHVnljeUFtSmlCeVpYTXVhR1ZoWkdWeWMxc25ZMjl1ZEdWdWRDMTBlWEJsSjEwcElIdGNiaUFnSUNBZ0lDQWdhV1lnS0hKbGN5NW9aV0ZrWlhKeld5ZGpiMjUwWlc1MExYUjVjR1VuWFM1cGJtUmxlRTltS0NkaGNIQnNhV05oZEdsdmJpOXFjMjl1SnlrZ1BqMGdNQ2tnZTF4dUlDQWdJQ0FnSUNBZ0lIUnllU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQnlaWE53YjI1elpTNXZZbW9nUFNCS1UwOU9MbkJoY25ObEtISmxjM0J2Ym5ObExuTjBZWFIxYzFSbGVIUXBPMXh1SUNBZ0lDQWdJQ0FnSUgxY2JpQWdJQ0FnSUNBZ0lDQmpZWFJqYUNBb1pTa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2NtVnpjRzl1YzJVdWIySnFJRDBnYm5Wc2JEdGNiaUFnSUNBZ0lDQWdJQ0I5WEc0Z0lDQWdJQ0FnSUgxY2JpQWdJQ0FnSUgxY2JpQWdJQ0FnSUdOaUlEMGdiMkpxTG05dUxtVnljbTl5TzF4dUlDQWdJSDBnWld4elpTQnBaaUFvY21WeklDWW1JRzlpYWk1dmJpQW1KaUJ2WW1vdWIyNHVjbVZ6Y0c5dWMyVXBJSHRjYmlBZ0lDQWdJSFpoY2lCd2IzTnphV0pzWlU5aWFqdGNibHh1SUNBZ0lDQWdMeThnUVd4eVpXRmtlU0J3WVhKelpXUWdZbmtnWW5rZ2MzVndaWEpoWjJWdWREOWNiaUFnSUNBZ0lHbG1JQ2h5WlhNdVltOWtlU0FtSmlCZkxtdGxlWE1vY21WekxtSnZaSGtwTG14bGJtZDBhQ0ErSURBcElIdGNiaUFnSUNBZ0lDQWdjRzl6YzJsaWJHVlBZbW9nUFNCeVpYTXVZbTlrZVR0Y2JpQWdJQ0FnSUgwZ1pXeHpaU0I3WEc0Z0lDQWdJQ0FnSUhSeWVTQjdYRzRnSUNBZ0lDQWdJQ0FnY0c5emMybGliR1ZQWW1vZ1BTQnFjM2xoYld3dWMyRm1aVXh2WVdRb2NtVnpMblJsZUhRcE8xeHVJQ0FnSUNBZ0lDQWdJQzh2SUdOaGJpQndZWEp6WlNCcGJuUnZJR0VnYzNSeWFXNW5MaTR1SUhkb2FXTm9JSGRsSUdSdmJpZDBJRzVsWldRZ2NuVnVibWx1WnlCaGNtOTFibVFnYVc0Z2RHaGxJSE41YzNSbGJWeHVJQ0FnSUNBZ0lDQWdJSEJ2YzNOcFlteGxUMkpxSUQwZ0tIUjVjR1Z2WmlCd2IzTnphV0pzWlU5aWFpQTlQVDBnSjNOMGNtbHVaeWNwSUQ4Z2JuVnNiQ0E2SUhCdmMzTnBZbXhsVDJKcU8xeHVJQ0FnSUNBZ0lDQjlJR05oZEdOb0lDaGxLU0I3WEc0Z0lDQWdJQ0FnSUNBZ2FHVnNjR1Z5Y3k1c2IyY29KMk5oYm01dmRDQndZWEp6WlNCS1UwOU9MMWxCVFV3Z1kyOXVkR1Z1ZENjcE8xeHVJQ0FnSUNBZ0lDQjlYRzRnSUNBZ0lDQjlYRzVjYmlBZ0lDQWdJQzh2SUc1MWJHd2diV1ZoYm5NZ2QyVWdZMkZ1SjNRZ2NHRnljMlVnYVc1MGJ5QnZZbXBsWTNSY2JpQWdJQ0FnSUdsbUtIUjVjR1Z2WmlCQ2RXWm1aWElnUFQwOUlDZG1kVzVqZEdsdmJpY2dKaVlnUW5WbVptVnlMbWx6UW5WbVptVnlLSEJ2YzNOcFlteGxUMkpxS1NrZ2UxeHVJQ0FnSUNBZ0lDQnlaWE53YjI1elpTNWtZWFJoSUQwZ2NHOXpjMmxpYkdWUFltbzdYRzRnSUNBZ0lDQjlYRzRnSUNBZ0lDQmxiSE5sSUh0Y2JpQWdJQ0FnSUNBZ2NtVnpjRzl1YzJVdWIySnFJRDBnS0hSNWNHVnZaaUJ3YjNOemFXSnNaVTlpYWlBOVBUMGdKMjlpYW1WamRDY3BJRDhnY0c5emMybGliR1ZQWW1vZ09pQnVkV3hzTzF4dUlDQWdJQ0FnZlZ4dVhHNGdJQ0FnSUNCeVpYTndiMjV6WlM1emRHRjBkWE1nUFNCeVpYTXVjM1JoZEhWek8xeHVJQ0FnSUNBZ2NtVnpjRzl1YzJVdWMzUmhkSFZ6VkdWNGRDQTlJSEpsY3k1MFpYaDBPMXh1SUNBZ0lDQWdZMklnUFNCdlltb3ViMjR1Y21WemNHOXVjMlU3WEc0Z0lDQWdmVnh1SUNBZ0lHbG1JQ2h5WlhNdWVHaHlJQ1ltSUhKbGN5NTRhSEl1Y21WemNHOXVjMlVwSUh0Y2JpQWdJQ0FnSUhKbGMzQnZibk5sTG1SaGRHRWdQU0J5WlhNdWVHaHlMbkpsYzNCdmJuTmxPMXh1SUNBZ0lIMWNiaUFnSUNCbGJITmxJR2xtS0NGeVpYTndiMjV6WlM1a1lYUmhLU0I3WEc0Z0lDQWdJQ0J5WlhOd2IyNXpaUzVrWVhSaElEMGdjbVZ6Y0c5dWMyVXVjM1JoZEhWelZHVjRkRHRjYmlBZ0lDQjlYRzVjYmlBZ0lDQnBaaUFvWTJJcElIdGNiaUFnSUNBZ0lHTmlLSEpsYzNCdmJuTmxLVHRjYmlBZ0lDQjlYRzRnSUgwcE8xeHVmVHRjYmx4dVUzVndaWEpoWjJWdWRFaDBkSEJEYkdsbGJuUXVjSEp2ZEc5MGVYQmxMaUJpYVc1aGNubFNaWEYxWlhOMElEMGdablZ1WTNScGIyNGdLR0ZqWTJWd2RDa2dlMXh1SUNCcFppZ2hZV05qWlhCMEtTQjdYRzRnSUNBZ2NtVjBkWEp1SUdaaGJITmxPMXh1SUNCOVhHNGdJSEpsZEhWeWJpQW9MMTVwYldGblpTOXBLUzUwWlhOMEtHRmpZMlZ3ZENrZ2ZId2dLQzllWVhCd2JHbGpZWFJwYjI1Y1hDOXdaR1l2S1M1MFpYTjBLR0ZqWTJWd2RDazdYRzU5TzF4dUlsMTkiLCIndXNlIHN0cmljdCc7XG5cbnZhciBTd2FnZ2VySHR0cCA9IHJlcXVpcmUoJy4vaHR0cCcpO1xudmFyIF8gPSB7XG4gIGlzT2JqZWN0OiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvaXNPYmplY3QnKSxcbiAgY2xvbmVEZWVwOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvY2xvbmVEZWVwJyksXG4gIGlzQXJyYXk6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9pc0FycmF5JyksXG4gIGlzU3RyaW5nOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvaXNTdHJpbmcnKVxufTtcblxuXG4vKipcbiAqIFJlc29sdmVzIGEgc3BlYydzIHJlbW90ZSByZWZlcmVuY2VzXG4gKi9cbnZhciBSZXNvbHZlciA9IG1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKCkge1xuICB0aGlzLmZhaWxlZFVybHMgPSBbXTtcbiAgdGhpcy5yZXNvbHZlckNhY2hlID0ge307XG4gIHRoaXMucGVuZGluZ1VybHMgPSB7fTtcbn07XG5cblJlc29sdmVyLnByb3RvdHlwZS5wcm9jZXNzQWxsT2YgPSBmdW5jdGlvbihyb290LCBuYW1lLCBkZWZpbml0aW9uLCByZXNvbHV0aW9uVGFibGUsIHVucmVzb2x2ZWRSZWZzLCBzcGVjKSB7XG4gIHZhciBpLCBsb2NhdGlvbiwgcHJvcGVydHk7XG5cbiAgZGVmaW5pdGlvblsneC1yZXNvbHZlZC1mcm9tJ10gPSBbICcjL2RlZmluaXRpb25zLycgKyBuYW1lIF07XG4gIHZhciBhbGxPZiA9IGRlZmluaXRpb24uYWxsT2Y7XG4gIC8vIHRoZSByZWZzIGdvIGZpcnN0XG4gIGFsbE9mLnNvcnQoZnVuY3Rpb24oYSwgYikge1xuICAgIGlmKGEuJHJlZiAmJiBiLiRyZWYpIHsgcmV0dXJuIDA7IH1cbiAgICBlbHNlIGlmKGEuJHJlZikgeyByZXR1cm4gLTE7IH1cbiAgICBlbHNlIHsgcmV0dXJuIDE7IH1cbiAgfSk7XG4gIGZvciAoaSA9IDA7IGkgPCBhbGxPZi5sZW5ndGg7IGkrKykge1xuICAgIHByb3BlcnR5ID0gYWxsT2ZbaV07XG4gICAgbG9jYXRpb24gPSAnL2RlZmluaXRpb25zLycgKyBuYW1lICsgJy9hbGxPZic7XG4gICAgdGhpcy5yZXNvbHZlSW5saW5lKHJvb3QsIHNwZWMsIHByb3BlcnR5LCByZXNvbHV0aW9uVGFibGUsIHVucmVzb2x2ZWRSZWZzLCBsb2NhdGlvbik7XG4gIH1cbn07XG5cblJlc29sdmVyLnByb3RvdHlwZS5yZXNvbHZlID0gZnVuY3Rpb24gKHNwZWMsIGFyZzEsIGFyZzIsIGFyZzMpIHtcbiAgdGhpcy5zcGVjID0gc3BlYztcbiAgdmFyIHJvb3QgPSBhcmcxLCBjYWxsYmFjayA9IGFyZzIsIHNjb3BlID0gYXJnMywgb3B0cyA9IHt9LCBsb2NhdGlvbiwgaTtcbiAgaWYodHlwZW9mIGFyZzEgPT09ICdmdW5jdGlvbicpIHtcbiAgICByb290ID0gbnVsbDtcbiAgICBjYWxsYmFjayA9IGFyZzE7XG4gICAgc2NvcGUgPSBhcmcyO1xuICB9XG4gIHZhciBfcm9vdCA9IHJvb3QsIG1vZGVsTmFtZTtcbiAgdGhpcy5zY29wZSA9IChzY29wZSB8fCB0aGlzKTtcbiAgdGhpcy5pdGVyYXRpb24gPSB0aGlzLml0ZXJhdGlvbiB8fCAwO1xuXG4gIGlmKHRoaXMuc2NvcGUub3B0aW9ucyAmJiB0aGlzLnNjb3BlLm9wdGlvbnMucmVxdWVzdEludGVyY2VwdG9yKXtcbiAgICBvcHRzLnJlcXVlc3RJbnRlcmNlcHRvciA9IHRoaXMuc2NvcGUub3B0aW9ucy5yZXF1ZXN0SW50ZXJjZXB0b3I7XG4gIH1cblxuICBpZih0aGlzLnNjb3BlLm9wdGlvbnMgJiYgdGhpcy5zY29wZS5vcHRpb25zLnJlc3BvbnNlSW50ZXJjZXB0b3Ipe1xuICAgIG9wdHMucmVzcG9uc2VJbnRlcmNlcHRvciA9IHRoaXMuc2NvcGUub3B0aW9ucy5yZXNwb25zZUludGVyY2VwdG9yO1xuICB9XG5cbiAgdmFyIG5hbWUsIHBhdGgsIHByb3BlcnR5LCBwcm9wZXJ0eU5hbWUsIHBhcmFtZXRlciwgZG9uZSwgY291bnRlcjtcbiAgdmFyIHByb2Nlc3NlZENhbGxzID0gMCwgcmVzb2x2ZWRSZWZzID0ge30sIHVucmVzb2x2ZWRSZWZzID0ge307XG4gIHZhciByZXNvbHV0aW9uVGFibGUgPSBbXTsgLy8gc3RvcmUgb2JqZWN0cyBmb3IgZGVyZWZlcmVuY2luZ1xuXG4gIHNwZWMuZGVmaW5pdGlvbnMgPSBzcGVjLmRlZmluaXRpb25zIHx8IHt9O1xuICAvLyBkZWZpbml0aW9uc1xuICBmb3IgKG5hbWUgaW4gc3BlYy5kZWZpbml0aW9ucykge1xuICAgIHZhciBkZWZpbml0aW9uID0gc3BlYy5kZWZpbml0aW9uc1tuYW1lXTtcbiAgICBpZihkZWZpbml0aW9uLiRyZWYpIHtcbiAgICAgIHRoaXMucmVzb2x2ZUlubGluZShyb290LCBzcGVjLCBkZWZpbml0aW9uLCByZXNvbHV0aW9uVGFibGUsIHVucmVzb2x2ZWRSZWZzLCBkZWZpbml0aW9uKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICBmb3IgKHByb3BlcnR5TmFtZSBpbiBkZWZpbml0aW9uLnByb3BlcnRpZXMpIHtcbiAgICAgICAgcHJvcGVydHkgPSBkZWZpbml0aW9uLnByb3BlcnRpZXNbcHJvcGVydHlOYW1lXTtcbiAgICAgICAgaWYgKF8uaXNBcnJheShwcm9wZXJ0eS5hbGxPZikpIHtcbiAgICAgICAgICB0aGlzLnByb2Nlc3NBbGxPZihyb290LCBuYW1lLCBwcm9wZXJ0eSwgcmVzb2x1dGlvblRhYmxlLCB1bnJlc29sdmVkUmVmcywgc3BlYyk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgdGhpcy5yZXNvbHZlVG8ocm9vdCwgcHJvcGVydHksIHJlc29sdXRpb25UYWJsZSwgJy9kZWZpbml0aW9ucycpO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGlmIChkZWZpbml0aW9uLmFsbE9mKSB7XG4gICAgICAgIHRoaXMucHJvY2Vzc0FsbE9mKHJvb3QsIG5hbWUsIGRlZmluaXRpb24sIHJlc29sdXRpb25UYWJsZSwgdW5yZXNvbHZlZFJlZnMsIHNwZWMpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIHNoYXJlZCBwYXJhbWV0ZXJzXG4gIHNwZWMucGFyYW1ldGVycyA9IHNwZWMucGFyYW1ldGVycyB8fCB7fTtcbiAgZm9yKG5hbWUgaW4gc3BlYy5wYXJhbWV0ZXJzKSB7XG4gICAgcGFyYW1ldGVyID0gc3BlYy5wYXJhbWV0ZXJzW25hbWVdO1xuICAgIGlmIChwYXJhbWV0ZXIuaW4gPT09ICdib2R5JyAmJiBwYXJhbWV0ZXIuc2NoZW1hKSB7XG4gICAgICBpZihfLmlzQXJyYXkocGFyYW1ldGVyLnNjaGVtYS5hbGxPZikpIHtcbiAgICAgICAgLy8gbW92ZSB0byBhIGRlZmluaXRpb25cbiAgICAgICAgbW9kZWxOYW1lID0gJ2lubGluZV9tb2RlbCc7XG4gICAgICAgIHZhciBfbmFtZSA9IG1vZGVsTmFtZTtcbiAgICAgICAgZG9uZSA9IGZhbHNlOyBjb3VudGVyID0gMDtcbiAgICAgICAgd2hpbGUoIWRvbmUpIHtcbiAgICAgICAgICBpZih0eXBlb2Ygc3BlYy5kZWZpbml0aW9uc1tfbmFtZV0gPT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgICAgICBkb25lID0gdHJ1ZTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH1cbiAgICAgICAgICBfbmFtZSA9IG1vZGVsTmFtZSArICdfJyArIGNvdW50ZXI7XG4gICAgICAgICAgY291bnRlciArKztcbiAgICAgICAgfVxuICAgICAgICBzcGVjLmRlZmluaXRpb25zW19uYW1lXSA9IHsgYWxsT2Y6IHBhcmFtZXRlci5zY2hlbWEuYWxsT2YgfTtcbiAgICAgICAgZGVsZXRlIHBhcmFtZXRlci5zY2hlbWEuYWxsT2Y7XG4gICAgICAgIHBhcmFtZXRlci5zY2hlbWEuJHJlZiA9ICcjL2RlZmluaXRpb25zLycgKyBfbmFtZTtcbiAgICAgICAgdGhpcy5wcm9jZXNzQWxsT2Yocm9vdCwgX25hbWUsIHNwZWMuZGVmaW5pdGlvbnNbX25hbWVdLCByZXNvbHV0aW9uVGFibGUsIHVucmVzb2x2ZWRSZWZzLCBzcGVjKTtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICB0aGlzLnJlc29sdmVUbyhyb290LCBwYXJhbWV0ZXIuc2NoZW1hLCByZXNvbHV0aW9uVGFibGUsIGxvY2F0aW9uKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAocGFyYW1ldGVyLiRyZWYpIHtcbiAgICAgIC8vIHBhcmFtZXRlciByZWZlcmVuY2VcbiAgICAgIHRoaXMucmVzb2x2ZUlubGluZShyb290LCBzcGVjLCBwYXJhbWV0ZXIsIHJlc29sdXRpb25UYWJsZSwgdW5yZXNvbHZlZFJlZnMsIHBhcmFtZXRlci4kcmVmKTtcbiAgICB9XG4gIH1cblxuICAvLyBvcGVyYXRpb25zXG4gIGZvciAobmFtZSBpbiBzcGVjLnBhdGhzKSB7XG4gICAgdmFyIG1ldGhvZCwgb3BlcmF0aW9uLCByZXNwb25zZUNvZGU7XG4gICAgcGF0aCA9IHNwZWMucGF0aHNbbmFtZV07XG5cbiAgICBpZih0eXBlb2YgcGF0aCA9PT0gJ29iamVjdCcpIHtcbiAgICAgIGZvciAobWV0aG9kIGluIHBhdGgpIHtcbiAgICAgICAgLy8gb3BlcmF0aW9uIHJlZmVyZW5jZVxuICAgICAgICBpZiAobWV0aG9kID09PSAnJHJlZicpIHtcbiAgICAgICAgICAvLyBsb2NhdGlvbiA9IHBhdGhbbWV0aG9kXTtcbiAgICAgICAgICBsb2NhdGlvbiA9ICcvcGF0aHMnICsgbmFtZTtcbiAgICAgICAgICB0aGlzLnJlc29sdmVJbmxpbmUocm9vdCwgc3BlYywgcGF0aCwgcmVzb2x1dGlvblRhYmxlLCB1bnJlc29sdmVkUmVmcywgbG9jYXRpb24pO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgIG9wZXJhdGlvbiA9IHBhdGhbbWV0aG9kXTtcbiAgICAgICAgICB2YXIgc2hhcmVkUGFyYW1ldGVycyA9IHBhdGgucGFyYW1ldGVycyB8fCBbXTtcbiAgICAgICAgICB2YXIgcGFyYW1ldGVycyA9IG9wZXJhdGlvbi5wYXJhbWV0ZXJzIHx8IFtdO1xuXG4gICAgICAgICAgZm9yIChpIGluIHNoYXJlZFBhcmFtZXRlcnMpIHtcbiAgICAgICAgICAgIHBhcmFtZXRlciA9IHNoYXJlZFBhcmFtZXRlcnNbaV07XG4gICAgICAgICAgICBwYXJhbWV0ZXJzLnVuc2hpZnQocGFyYW1ldGVyKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKG1ldGhvZCAhPT0gJ3BhcmFtZXRlcnMnICYmIF8uaXNPYmplY3Qob3BlcmF0aW9uKSkge1xuICAgICAgICAgICAgb3BlcmF0aW9uLnBhcmFtZXRlcnMgPSBvcGVyYXRpb24ucGFyYW1ldGVycyB8fCBwYXJhbWV0ZXJzO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGZvciAoaSBpbiBwYXJhbWV0ZXJzKSB7XG4gICAgICAgICAgICBwYXJhbWV0ZXIgPSBwYXJhbWV0ZXJzW2ldO1xuICAgICAgICAgICAgbG9jYXRpb24gPSAnL3BhdGhzJyArIG5hbWUgKyAnLycgKyBtZXRob2QgKyAnL3BhcmFtZXRlcnMnO1xuXG4gICAgICAgICAgICBpZiAocGFyYW1ldGVyLmluID09PSAnYm9keScgJiYgcGFyYW1ldGVyLnNjaGVtYSkge1xuICAgICAgICAgICAgICBpZiAoXy5pc0FycmF5KHBhcmFtZXRlci5zY2hlbWEuYWxsT2YpKSB7XG4gICAgICAgICAgICAgICAgLy8gbW92ZSB0byBhIGRlZmluaXRpb25cbiAgICAgICAgICAgICAgICBtb2RlbE5hbWUgPSAnaW5saW5lX21vZGVsJztcbiAgICAgICAgICAgICAgICBuYW1lID0gbW9kZWxOYW1lO1xuICAgICAgICAgICAgICAgIGRvbmUgPSBmYWxzZTtcbiAgICAgICAgICAgICAgICBjb3VudGVyID0gMDtcbiAgICAgICAgICAgICAgICB3aGlsZSAoIWRvbmUpIHtcbiAgICAgICAgICAgICAgICAgIGlmICh0eXBlb2Ygc3BlYy5kZWZpbml0aW9uc1tuYW1lXSA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgICAgICAgICAgICAgZG9uZSA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgbmFtZSA9IG1vZGVsTmFtZSArICdfJyArIGNvdW50ZXI7XG4gICAgICAgICAgICAgICAgICBjb3VudGVyKys7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHNwZWMuZGVmaW5pdGlvbnNbbmFtZV0gPSB7YWxsT2Y6IHBhcmFtZXRlci5zY2hlbWEuYWxsT2Z9O1xuICAgICAgICAgICAgICAgIGRlbGV0ZSBwYXJhbWV0ZXIuc2NoZW1hLmFsbE9mO1xuICAgICAgICAgICAgICAgIHBhcmFtZXRlci5zY2hlbWEuJHJlZiA9ICcjL2RlZmluaXRpb25zLycgKyBuYW1lO1xuICAgICAgICAgICAgICAgIHRoaXMucHJvY2Vzc0FsbE9mKHJvb3QsIG5hbWUsIHNwZWMuZGVmaW5pdGlvbnNbbmFtZV0sIHJlc29sdXRpb25UYWJsZSwgdW5yZXNvbHZlZFJlZnMsIHNwZWMpO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHRoaXMucmVzb2x2ZVRvKHJvb3QsIHBhcmFtZXRlci5zY2hlbWEsIHJlc29sdXRpb25UYWJsZSwgbG9jYXRpb24pO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChwYXJhbWV0ZXIuJHJlZikge1xuICAgICAgICAgICAgICAvLyBwYXJhbWV0ZXIgcmVmZXJlbmNlXG4gICAgICAgICAgICAgIHRoaXMucmVzb2x2ZUlubGluZShyb290LCBzcGVjLCBwYXJhbWV0ZXIsIHJlc29sdXRpb25UYWJsZSwgdW5yZXNvbHZlZFJlZnMsIHBhcmFtZXRlci4kcmVmKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG5cbiAgICAgICAgICBmb3IgKHJlc3BvbnNlQ29kZSBpbiBvcGVyYXRpb24ucmVzcG9uc2VzKSB7XG4gICAgICAgICAgICB2YXIgcmVzcG9uc2UgPSBvcGVyYXRpb24ucmVzcG9uc2VzW3Jlc3BvbnNlQ29kZV07XG4gICAgICAgICAgICBsb2NhdGlvbiA9ICcvcGF0aHMnICsgbmFtZSArICcvJyArIG1ldGhvZCArICcvcmVzcG9uc2VzLycgKyByZXNwb25zZUNvZGU7XG5cbiAgICAgICAgICAgIGlmIChfLmlzT2JqZWN0KHJlc3BvbnNlKSkge1xuICAgICAgICAgICAgICBpZiAocmVzcG9uc2UuJHJlZikge1xuICAgICAgICAgICAgICAgIC8vIHJlc3BvbnNlIHJlZmVyZW5jZVxuICAgICAgICAgICAgICAgIHRoaXMucmVzb2x2ZUlubGluZShyb290LCBzcGVjLCByZXNwb25zZSwgcmVzb2x1dGlvblRhYmxlLCB1bnJlc29sdmVkUmVmcywgbG9jYXRpb24pO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlmIChyZXNwb25zZS5zY2hlbWEpIHtcbiAgICAgICAgICAgICAgICB2YXIgcmVzcG9uc2VPYmogPSByZXNwb25zZTtcbiAgICAgICAgICAgICAgICBpZiAoXy5pc0FycmF5KHJlc3BvbnNlT2JqLnNjaGVtYS5hbGxPZikpIHtcbiAgICAgICAgICAgICAgICAgIC8vIG1vdmUgdG8gYSBkZWZpbml0aW9uXG4gICAgICAgICAgICAgICAgICBtb2RlbE5hbWUgPSAnaW5saW5lX21vZGVsJztcbiAgICAgICAgICAgICAgICAgIG5hbWUgPSBtb2RlbE5hbWU7XG4gICAgICAgICAgICAgICAgICBkb25lID0gZmFsc2U7XG4gICAgICAgICAgICAgICAgICBjb3VudGVyID0gMDtcbiAgICAgICAgICAgICAgICAgIHdoaWxlICghZG9uZSkge1xuICAgICAgICAgICAgICAgICAgICBpZiAodHlwZW9mIHNwZWMuZGVmaW5pdGlvbnNbbmFtZV0gPT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgZG9uZSA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgbmFtZSA9IG1vZGVsTmFtZSArICdfJyArIGNvdW50ZXI7XG4gICAgICAgICAgICAgICAgICAgIGNvdW50ZXIrKztcbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIHNwZWMuZGVmaW5pdGlvbnNbbmFtZV0gPSB7YWxsT2Y6IHJlc3BvbnNlT2JqLnNjaGVtYS5hbGxPZn07XG4gICAgICAgICAgICAgICAgICBkZWxldGUgcmVzcG9uc2VPYmouc2NoZW1hLmFsbE9mO1xuICAgICAgICAgICAgICAgICAgZGVsZXRlIHJlc3BvbnNlT2JqLnNjaGVtYS50eXBlO1xuICAgICAgICAgICAgICAgICAgcmVzcG9uc2VPYmouc2NoZW1hLiRyZWYgPSAnIy9kZWZpbml0aW9ucy8nICsgbmFtZTtcbiAgICAgICAgICAgICAgICAgIHRoaXMucHJvY2Vzc0FsbE9mKHJvb3QsIG5hbWUsIHNwZWMuZGVmaW5pdGlvbnNbbmFtZV0sIHJlc29sdXRpb25UYWJsZSwgdW5yZXNvbHZlZFJlZnMsIHNwZWMpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmICgnYXJyYXknID09PSByZXNwb25zZU9iai5zY2hlbWEudHlwZSkge1xuICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlT2JqLnNjaGVtYS5pdGVtcyAmJiByZXNwb25zZU9iai5zY2hlbWEuaXRlbXMuJHJlZikge1xuICAgICAgICAgICAgICAgICAgICAvLyByZXNwb25zZSByZWZlcmVuY2VcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5yZXNvbHZlSW5saW5lKHJvb3QsIHNwZWMsIHJlc3BvbnNlT2JqLnNjaGVtYS5pdGVtcywgcmVzb2x1dGlvblRhYmxlLCB1bnJlc29sdmVkUmVmcywgbG9jYXRpb24pO1xuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgIHRoaXMucmVzb2x2ZVRvKHJvb3QsIHJlc3BvbnNlLnNjaGVtYSwgcmVzb2x1dGlvblRhYmxlLCBsb2NhdGlvbik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgICAvLyBjbGVhciB0aGVtIG91dCB0byBhdm9pZCBtdWx0aXBsZSByZXNvbHV0aW9uc1xuICAgICAgcGF0aC5wYXJhbWV0ZXJzID0gW107XG4gICAgfVxuICB9XG5cbiAgdmFyIGV4cGVjdGVkQ2FsbHMgPSAwLCB0b1Jlc29sdmUgPSBbXTtcbiAgLy8gaWYgdGhlIHJvb3QgaXMgc2FtZSBhcyBvYmpbaV0ucm9vdCB3ZSBjYW4gcmVzb2x2ZSBsb2NhbGx5XG4gIHZhciBhbGwgPSByZXNvbHV0aW9uVGFibGU7XG5cbiAgdmFyIHBhcnRzO1xuICBmb3IoaSA9IDA7IGkgPCBhbGwubGVuZ3RoOyBpKyspIHtcbiAgICB2YXIgYSA9IGFsbFtpXTtcbiAgICBpZihyb290ID09PSBhLnJvb3QpIHtcbiAgICAgIGlmKGEucmVzb2x2ZUFzID09PSAncmVmJykge1xuICAgICAgICAvLyByZXNvbHZlIGFueSBwYXRoIHdhbGtpbmdcbiAgICAgICAgdmFyIGpvaW5lZCA9ICgoYS5yb290IHx8ICcnKSArICcvJyArIGEua2V5KS5zcGxpdCgnLycpO1xuICAgICAgICB2YXIgbm9ybWFsaXplZCA9IFtdO1xuICAgICAgICB2YXIgdXJsID0gJyc7XG4gICAgICAgIHZhciBrO1xuXG4gICAgICAgIGlmKGEua2V5LmluZGV4T2YoJy4uLycpID49IDApIHtcbiAgICAgICAgICBmb3IodmFyIGogPSAwOyBqIDwgam9pbmVkLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICBpZihqb2luZWRbal0gPT09ICcuLicpIHtcbiAgICAgICAgICAgICAgbm9ybWFsaXplZCA9IG5vcm1hbGl6ZWQuc2xpY2UoMCwgbm9ybWFsaXplZC5sZW5ndGgtMSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgbm9ybWFsaXplZC5wdXNoKGpvaW5lZFtqXSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIGZvcihrID0gMDsgayA8IG5vcm1hbGl6ZWQubGVuZ3RoOyBrICsrKSB7XG4gICAgICAgICAgICBpZihrID4gMCkge1xuICAgICAgICAgICAgICB1cmwgKz0gJy8nO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdXJsICs9IG5vcm1hbGl6ZWRba107XG4gICAgICAgICAgfVxuICAgICAgICAgIC8vIHdlIG5vdyBoYXZlIHRvIHJlbW90ZSByZXNvbHZlIHRoaXMgYmVjYXVzZSB0aGUgcGF0aCBoYXMgY2hhbmdlZFxuICAgICAgICAgIGEucm9vdCA9IHVybDtcbiAgICAgICAgICB0b1Jlc29sdmUucHVzaChhKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICBwYXJ0cyA9IGEua2V5LnNwbGl0KCcjJyk7XG4gICAgICAgICAgaWYocGFydHMubGVuZ3RoID09PSAyKSB7XG4gICAgICAgICAgICBpZihwYXJ0c1swXS5pbmRleE9mKCdodHRwOicpID09PSAwIHx8IHBhcnRzWzBdLmluZGV4T2YoJ2h0dHBzOicpID09PSAwKSB7XG4gICAgICAgICAgICAgIGEucm9vdCA9IHBhcnRzWzBdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgbG9jYXRpb24gPSBwYXJ0c1sxXS5zcGxpdCgnLycpO1xuICAgICAgICAgICAgdmFyIHI7XG4gICAgICAgICAgICB2YXIgcyA9IHNwZWM7XG4gICAgICAgICAgICBmb3IoayA9IDA7IGsgPCBsb2NhdGlvbi5sZW5ndGg7IGsrKykge1xuICAgICAgICAgICAgICB2YXIgcGFydCA9IGxvY2F0aW9uW2tdO1xuICAgICAgICAgICAgICBpZihwYXJ0ICE9PSAnJykge1xuICAgICAgICAgICAgICAgIHMgPSBzW3BhcnRdO1xuICAgICAgICAgICAgICAgIGlmKHR5cGVvZiBzICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgICAgICAgICAgICAgciA9IHM7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgciA9IG51bGw7XG4gICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmKHIgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgLy8gbXVzdCByZXNvbHZlIHRoaXMgdG9vXG4gICAgICAgICAgICAgIHRvUmVzb2x2ZS5wdXNoKGEpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIGlmIChhLnJlc29sdmVBcyA9PT0gJ2lubGluZScpIHtcbiAgICAgICAgICBpZihhLmtleSAmJiBhLmtleS5pbmRleE9mKCcjJykgPT09IC0xICYmIGEua2V5LmNoYXJBdCgwKSAhPT0gJy8nKSB7XG4gICAgICAgICAgICAvLyBoYW5kbGUgcmVsYXRpdmUgc2NoZW1hXG4gICAgICAgICAgICBwYXJ0cyA9IGEucm9vdC5zcGxpdCgnLycpO1xuICAgICAgICAgICAgbG9jYXRpb24gPSAnJztcbiAgICAgICAgICAgIGZvcihpID0gMDsgaSA8IHBhcnRzLmxlbmd0aCAtIDE7IGkrKykge1xuICAgICAgICAgICAgICBsb2NhdGlvbiArPSBwYXJ0c1tpXSArICcvJztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxvY2F0aW9uICs9IGEua2V5O1xuICAgICAgICAgICAgYS5yb290ID0gbG9jYXRpb247XG4gICAgICAgICAgICBhLmxvY2F0aW9uID0gJyc7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRvUmVzb2x2ZS5wdXNoKGEpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgdG9SZXNvbHZlLnB1c2goYSk7XG4gICAgfVxuICB9XG4gIGV4cGVjdGVkQ2FsbHMgPSB0b1Jlc29sdmUubGVuZ3RoO1xuXG4gIC8vIHJlc29sdmUgYW55dGhpbmcgdGhhdCBpcyBsb2NhbFxuXG4gIHZhciBsb2NrID0ge307XG4gIGZvcih2YXIgaWkgPSAwOyBpaSA8IHRvUmVzb2x2ZS5sZW5ndGg7IGlpKyspIHtcbiAgICAoZnVuY3Rpb24oaXRlbSwgc3BlYywgc2VsZiwgbG9jaywgaWkpIHtcbiAgICAgIGlmKCFpdGVtLnJvb3QgfHwgaXRlbS5yb290ID09PSByb290KSB7XG4gICAgICAgIC8vIGxvY2FsIHJlc29sdmVcbiAgICAgICAgc2VsZi5yZXNvbHZlSXRlbShzcGVjLCBfcm9vdCwgcmVzb2x1dGlvblRhYmxlLCByZXNvbHZlZFJlZnMsIHVucmVzb2x2ZWRSZWZzLCBpdGVtKTtcbiAgICAgICAgcHJvY2Vzc2VkQ2FsbHMgKz0gMTtcblxuICAgICAgICBpZihwcm9jZXNzZWRDYWxscyA9PT0gZXhwZWN0ZWRDYWxscykge1xuICAgICAgICAgIHNlbGYuZmluaXNoKHNwZWMsIHJvb3QsIHJlc29sdXRpb25UYWJsZSwgcmVzb2x2ZWRSZWZzLCB1bnJlc29sdmVkUmVmcywgY2FsbGJhY2ssIHRydWUpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBlbHNlIGlmKHNlbGYuZmFpbGVkVXJscy5pbmRleE9mKGl0ZW0ucm9vdCkgPT09IC0xKSB7XG4gICAgICAgIHZhciBvYmogPSB7XG4gICAgICAgICAgdXNlSlF1ZXJ5OiBmYWxzZSwgIC8vIFRPRE9cbiAgICAgICAgICB1cmw6IGl0ZW0ucm9vdCxcbiAgICAgICAgICBtZXRob2Q6ICdnZXQnLFxuICAgICAgICAgIGhlYWRlcnM6IHtcbiAgICAgICAgICAgIGFjY2VwdDogc2VsZi5zY29wZS5zd2FnZ2VyUmVxdWVzdEhlYWRlcnMgfHwgJ2FwcGxpY2F0aW9uL2pzb24nXG4gICAgICAgICAgfSxcbiAgICAgICAgICBvbjoge1xuICAgICAgICAgICAgZXJyb3I6IGZ1bmN0aW9uIChlcnJvcikge1xuICAgICAgICAgICAgICBwcm9jZXNzZWRDYWxscyArPSAxO1xuICAgICAgICAgICAgICBjb25zb2xlLmxvZygnZmFpbGVkIHVybDogJyArIG9iai51cmwpO1xuICAgICAgICAgICAgICBzZWxmLmZhaWxlZFVybHMucHVzaChvYmoudXJsKTtcbiAgICAgICAgICAgICAgaWYgKGxvY2spIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgbG9ja1tpdGVtLnJvb3RdO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIHVucmVzb2x2ZWRSZWZzW2l0ZW0ua2V5XSA9IHtcbiAgICAgICAgICAgICAgICByb290OiBpdGVtLnJvb3QsXG4gICAgICAgICAgICAgICAgbG9jYXRpb246IGl0ZW0ubG9jYXRpb25cbiAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICBpZiAocHJvY2Vzc2VkQ2FsbHMgPT09IGV4cGVjdGVkQ2FsbHMpIHtcbiAgICAgICAgICAgICAgICBzZWxmLmZpbmlzaChzcGVjLCBfcm9vdCwgcmVzb2x1dGlvblRhYmxlLCByZXNvbHZlZFJlZnMsIHVucmVzb2x2ZWRSZWZzLCBjYWxsYmFjayk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sICAvLyBqc2hpbnQgaWdub3JlOmxpbmVcbiAgICAgICAgICAgIHJlc3BvbnNlOiBmdW5jdGlvbiAocmVzcG9uc2UpIHtcbiAgICAgICAgICAgICAgdmFyIHN3YWdnZXIgPSByZXNwb25zZS5vYmo7XG4gICAgICAgICAgICAgIGlmIChsb2NrKSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIGxvY2tbaXRlbS5yb290XTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBpZiAoc2VsZi5yZXNvbHZlckNhY2hlKSB7XG4gICAgICAgICAgICAgICAgc2VsZi5yZXNvbHZlckNhY2hlW2l0ZW0ucm9vdF0gPSBzd2FnZ2VyO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIHNlbGYucmVzb2x2ZUl0ZW0oc3dhZ2dlciwgaXRlbS5yb290LCByZXNvbHV0aW9uVGFibGUsIHJlc29sdmVkUmVmcywgdW5yZXNvbHZlZFJlZnMsIGl0ZW0pO1xuICAgICAgICAgICAgICBwcm9jZXNzZWRDYWxscyArPSAxO1xuXG4gICAgICAgICAgICAgIGlmIChwcm9jZXNzZWRDYWxscyA9PT0gZXhwZWN0ZWRDYWxscykge1xuICAgICAgICAgICAgICAgIHNlbGYuZmluaXNoKHNwZWMsIF9yb290LCByZXNvbHV0aW9uVGFibGUsIHJlc29sdmVkUmVmcywgdW5yZXNvbHZlZFJlZnMsIGNhbGxiYWNrKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gLy8ganNoaW50IGlnbm9yZTpsaW5lXG4gICAgICAgIH07XG5cbiAgICAgICAgLy8gYXBwbHkgdGltZW91dCBvbmx5IHdoZW4gc3BlY2lmaWVkXG4gICAgICAgIGlmIChzY29wZSAmJiBzY29wZS5mZXRjaFNwZWNUaW1lb3V0KSB7XG4gICAgICAgICAgb2JqLnRpbWVvdXQgPSBzY29wZS5mZXRjaFNwZWNUaW1lb3V0O1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHNjb3BlICYmIHNjb3BlLmNsaWVudEF1dGhvcml6YXRpb25zKSB7XG4gICAgICAgICAgc2NvcGUuY2xpZW50QXV0aG9yaXphdGlvbnMuYXBwbHkob2JqKTtcbiAgICAgICAgfVxuXG4gICAgICAgIChmdW5jdGlvbiB3YWl0Rm9yVW5sb2NrKCkge1xuICAgICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24oKSB7XG4gICAgICAgICAgICBpZiAobG9ja1tvYmoudXJsXSkge1xuICAgICAgICAgICAgICB3YWl0Rm9yVW5sb2NrKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgdmFyIGNhY2hlZCA9IHNlbGYucmVzb2x2ZXJDYWNoZVtvYmoudXJsXTtcbiAgICAgICAgICAgICAgaWYgKF8uaXNPYmplY3QoY2FjaGVkKSkge1xuICAgICAgICAgICAgICAgIG9iai5vbi5yZXNwb25zZSh7b2JqOiBjYWNoZWR9KTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICBsb2NrW29iai51cmxdID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICBuZXcgU3dhZ2dlckh0dHAoKS5leGVjdXRlKG9iaiwgb3B0cyk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LCAwKTtcbiAgICAgICAgfSkoKTtcbiAgICAgIH1cblxuICAgICAgZWxzZSB7XG4gICAgICAgIHByb2Nlc3NlZENhbGxzICs9IDE7XG4gICAgICAgIHVucmVzb2x2ZWRSZWZzW2l0ZW0ua2V5XSA9IHtcbiAgICAgICAgICByb290OiBpdGVtLnJvb3QsXG4gICAgICAgICAgbG9jYXRpb246IGl0ZW0ubG9jYXRpb25cbiAgICAgICAgfTtcbiAgICAgICAgaWYgKHByb2Nlc3NlZENhbGxzID09PSBleHBlY3RlZENhbGxzKSB7XG4gICAgICAgICAgc2VsZi5maW5pc2goc3BlYywgX3Jvb3QsIHJlc29sdXRpb25UYWJsZSwgcmVzb2x2ZWRSZWZzLCB1bnJlc29sdmVkUmVmcywgY2FsbGJhY2spO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSh0b1Jlc29sdmVbaWldLCBzcGVjLCB0aGlzLCBsb2NrLCBpaSkpO1xuICB9XG5cbiAgaWYgKE9iamVjdC5rZXlzKHRvUmVzb2x2ZSkubGVuZ3RoID09PSAwKSB7XG4gICAgdGhpcy5maW5pc2goc3BlYywgX3Jvb3QsIHJlc29sdXRpb25UYWJsZSwgcmVzb2x2ZWRSZWZzLCB1bnJlc29sdmVkUmVmcywgY2FsbGJhY2spO1xuICB9XG59O1xuXG5SZXNvbHZlci5wcm90b3R5cGUucmVzb2x2ZUl0ZW0gPSBmdW5jdGlvbihzcGVjLCByb290LCByZXNvbHV0aW9uVGFibGUsIHJlc29sdmVkUmVmcywgdW5yZXNvbHZlZFJlZnMsIGl0ZW0pIHtcbiAgdmFyIHBhdGggPSBpdGVtLmxvY2F0aW9uO1xuICB2YXIgbG9jYXRpb24gPSBzcGVjLCBwYXJ0cyA9IHBhdGguc3BsaXQoJy8nKTtcbiAgaWYocGF0aCAhPT0gJycpIHtcbiAgICBmb3IgKHZhciBqID0gMDsgaiA8IHBhcnRzLmxlbmd0aDsgaisrKSB7XG4gICAgICB2YXIgc2VnbWVudCA9IHBhcnRzW2pdO1xuICAgICAgaWYgKHNlZ21lbnQuaW5kZXhPZignfjEnKSAhPT0gLTEpIHtcbiAgICAgICAgc2VnbWVudCA9IHBhcnRzW2pdLnJlcGxhY2UoL34wL2csICd+JykucmVwbGFjZSgvfjEvZywgJy8nKTtcbiAgICAgICAgaWYgKHNlZ21lbnQuY2hhckF0KDApICE9PSAnLycpIHtcbiAgICAgICAgICBzZWdtZW50ID0gJy8nICsgc2VnbWVudDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKHR5cGVvZiBsb2NhdGlvbiA9PT0gJ3VuZGVmaW5lZCcgfHwgbG9jYXRpb24gPT09IG51bGwpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBpZiAoc2VnbWVudCA9PT0gJycgJiYgaiA9PT0gKHBhcnRzLmxlbmd0aCAtIDEpICYmIHBhcnRzLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgbG9jYXRpb24gPSBudWxsO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICAgIGlmIChzZWdtZW50Lmxlbmd0aCA+IDApIHtcbiAgICAgICAgbG9jYXRpb24gPSBsb2NhdGlvbltzZWdtZW50XTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgdmFyIHJlc29sdmVkID0gaXRlbS5rZXk7XG4gIHBhcnRzID0gaXRlbS5rZXkuc3BsaXQoJy8nKTtcbiAgdmFyIHJlc29sdmVkTmFtZSA9IHBhcnRzW3BhcnRzLmxlbmd0aC0xXTtcblxuICBpZihyZXNvbHZlZE5hbWUuaW5kZXhPZignIycpID49IDApIHtcbiAgICByZXNvbHZlZE5hbWUgPSByZXNvbHZlZE5hbWUuc3BsaXQoJyMnKVsxXTtcbiAgfVxuXG4gIGlmIChsb2NhdGlvbiAhPT0gbnVsbCAmJiB0eXBlb2YgbG9jYXRpb24gIT09ICd1bmRlZmluZWQnKSB7XG4gICAgcmVzb2x2ZWRSZWZzW3Jlc29sdmVkXSA9IHtcbiAgICAgIG5hbWU6IHJlc29sdmVkTmFtZSxcbiAgICAgIG9iajogbG9jYXRpb24sXG4gICAgICBrZXk6IGl0ZW0ua2V5LFxuICAgICAgcm9vdDogaXRlbS5yb290XG4gICAgfTtcbiAgfSBlbHNlIHtcbiAgICB1bnJlc29sdmVkUmVmc1tyZXNvbHZlZF0gPSB7XG4gICAgICByb290OiBpdGVtLnJvb3QsXG4gICAgICBsb2NhdGlvbjogaXRlbS5sb2NhdGlvblxuICAgIH07XG4gIH1cbn07XG5cblJlc29sdmVyLnByb3RvdHlwZS5maW5pc2ggPSBmdW5jdGlvbiAoc3BlYywgcm9vdCwgcmVzb2x1dGlvblRhYmxlLCByZXNvbHZlZFJlZnMsIHVucmVzb2x2ZWRSZWZzLCBjYWxsYmFjaywgbG9jYWxSZXNvbHZlKSB7XG4gIC8vIHdhbGsgcmVzb2x1dGlvbiB0YWJsZSBhbmQgcmVwbGFjZSB3aXRoIHJlc29sdmVkIHJlZnNcbiAgdmFyIHJlZiwgYWJzO1xuICBmb3IgKHJlZiBpbiByZXNvbHV0aW9uVGFibGUpIHtcbiAgICB2YXIgaXRlbSA9IHJlc29sdXRpb25UYWJsZVtyZWZdO1xuXG4gICAgdmFyIGtleSA9IGl0ZW0ua2V5O1xuICAgIHZhciByZXNvbHZlZFRvID0gcmVzb2x2ZWRSZWZzW2tleV07XG4gICAgaWYgKHJlc29sdmVkVG8pIHtcbiAgICAgIHNwZWMuZGVmaW5pdGlvbnMgPSBzcGVjLmRlZmluaXRpb25zIHx8IHt9O1xuICAgICAgaWYgKGl0ZW0ucmVzb2x2ZUFzID09PSAncmVmJykge1xuICAgICAgICBpZiAobG9jYWxSZXNvbHZlICE9PSB0cnVlKSB7XG4gICAgICAgICAgLy8gZG9uJ3QgcmV0YWluIHJvb3QgZm9yIGxvY2FsIGRlZmluaXRpb25zXG4gICAgICAgICAgZm9yIChrZXkgaW4gcmVzb2x2ZWRUby5vYmopIHtcbiAgICAgICAgICAgIGFicyA9IHRoaXMucmV0YWluUm9vdChrZXksIHJlc29sdmVkVG8ub2JqW2tleV0sIGl0ZW0ucm9vdCk7XG4gICAgICAgICAgICByZXNvbHZlZFRvLm9ialtrZXldID0gYWJzO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBzcGVjLmRlZmluaXRpb25zW3Jlc29sdmVkVG8ubmFtZV0gPSByZXNvbHZlZFRvLm9iajtcbiAgICAgICAgaXRlbS5vYmouJHJlZiA9ICcjL2RlZmluaXRpb25zLycgKyByZXNvbHZlZFRvLm5hbWU7XG4gICAgICB9IGVsc2UgaWYgKGl0ZW0ucmVzb2x2ZUFzID09PSAnaW5saW5lJykge1xuICAgICAgICB2YXIgdGFyZ2V0T2JqID0gaXRlbS5vYmo7XG4gICAgICAgIHRhcmdldE9ialsneC1yZXNvbHZlZC1mcm9tJ10gPSBbIGl0ZW0ua2V5IF07XG4gICAgICAgIGRlbGV0ZSB0YXJnZXRPYmouJHJlZjtcblxuICAgICAgICBmb3IgKGtleSBpbiByZXNvbHZlZFRvLm9iaikge1xuICAgICAgICAgIGFicyA9IHJlc29sdmVkVG8ub2JqW2tleV07XG5cbiAgICAgICAgICBpZiAobG9jYWxSZXNvbHZlICE9PSB0cnVlKSB7XG4gICAgICAgICAgICAvLyBkb24ndCByZXRhaW4gcm9vdCBmb3IgbG9jYWwgZGVmaW5pdGlvbnNcbiAgICAgICAgICAgIGFicyA9IHRoaXMucmV0YWluUm9vdChrZXksIHJlc29sdmVkVG8ub2JqW2tleV0sIGl0ZW0ucm9vdCk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRhcmdldE9ialtrZXldID0gYWJzO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHZhciBleGlzdGluZ1VucmVzb2x2ZWQgPSB0aGlzLmNvdW50VW5yZXNvbHZlZFJlZnMoc3BlYyk7XG5cbiAgaWYoZXhpc3RpbmdVbnJlc29sdmVkID09PSAwIHx8IHRoaXMuaXRlcmF0aW9uID4gNSkge1xuICAgIHRoaXMucmVzb2x2ZUFsbE9mKHNwZWMuZGVmaW5pdGlvbnMpO1xuICAgIHRoaXMucmVzb2x2ZXJDYWNoZSA9IG51bGw7XG4gICAgY2FsbGJhY2suY2FsbCh0aGlzLnNjb3BlLCBzcGVjLCB1bnJlc29sdmVkUmVmcyk7XG4gIH1cbiAgZWxzZSB7XG4gICAgdGhpcy5pdGVyYXRpb24gKz0gMTtcbiAgICB0aGlzLnJlc29sdmUoc3BlYywgcm9vdCwgY2FsbGJhY2ssIHRoaXMuc2NvcGUpO1xuICB9XG59O1xuXG5SZXNvbHZlci5wcm90b3R5cGUuY291bnRVbnJlc29sdmVkUmVmcyA9IGZ1bmN0aW9uKHNwZWMpIHtcbiAgdmFyIGk7XG4gIHZhciByZWZzID0gdGhpcy5nZXRSZWZzKHNwZWMpO1xuICB2YXIga2V5cyA9IFtdO1xuICB2YXIgdW5yZXNvbHZlZEtleXMgPSBbXTtcbiAgZm9yKGkgaW4gcmVmcykge1xuICAgIGlmKGkuaW5kZXhPZignIycpID09PSAwKSB7XG4gICAgICBrZXlzLnB1c2goaS5zdWJzdHJpbmcoMSkpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHVucmVzb2x2ZWRLZXlzLnB1c2goaSk7XG4gICAgfVxuICB9XG5cbiAgLy8gdmVyaWZ5IHBvc3NpYmxlIGtleXNcbiAgZm9yIChpID0gMDsgaSA8IGtleXMubGVuZ3RoOyBpKyspIHtcbiAgICB2YXIgcGFydCA9IGtleXNbaV07XG4gICAgdmFyIHBhcnRzID0gcGFydC5zcGxpdCgnLycpO1xuICAgIHZhciBvYmogPSBzcGVjO1xuXG4gICAgZm9yICh2YXIgayA9IDA7IGsgPCBwYXJ0cy5sZW5ndGg7IGsrKykge1xuICAgICAgdmFyIGtleSA9IHBhcnRzW2tdO1xuICAgICAgaWYoa2V5ICE9PSAnJykge1xuICAgICAgICBvYmogPSBvYmpba2V5XTtcbiAgICAgICAgaWYodHlwZW9mIG9iaiA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgICB1bnJlc29sdmVkS2V5cy5wdXNoKHBhcnQpO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiB1bnJlc29sdmVkS2V5cy5sZW5ndGg7XG59O1xuXG5SZXNvbHZlci5wcm90b3R5cGUuZ2V0UmVmcyA9IGZ1bmN0aW9uKHNwZWMsIG9iaikge1xuICBvYmogPSBvYmogfHwgc3BlYztcbiAgdmFyIG91dHB1dCA9IHt9O1xuICBmb3IodmFyIGtleSBpbiBvYmopIHtcbiAgICBpZiAoIW9iai5oYXNPd25Qcm9wZXJ0eShrZXkpKSB7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG4gICAgdmFyIGl0ZW0gPSBvYmpba2V5XTtcbiAgICBpZihrZXkgPT09ICckcmVmJyAmJiB0eXBlb2YgaXRlbSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIG91dHB1dFtpdGVtXSA9IG51bGw7XG4gICAgfVxuICAgIGVsc2UgaWYoXy5pc09iamVjdChpdGVtKSkge1xuICAgICAgdmFyIG8gPSB0aGlzLmdldFJlZnMoaXRlbSk7XG4gICAgICBmb3IodmFyIGsgaW4gbykge1xuICAgICAgICBvdXRwdXRba10gPSBudWxsO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gb3V0cHV0O1xufTtcblxuZnVuY3Rpb24gc3BsaXRVcmwodXJsKSB7XG4gIHZhciByZXN1bHQgPSB7fTtcbiAgdmFyIHByb3RvID0gL1thLXpdKzpcXC9cXC8vaS5leGVjKHVybCk7XG4gIGlmIChwcm90bykge1xuICAgIHJlc3VsdC5wcm90byA9IHByb3RvWzBdLnNsaWNlKDAsIC0zKTtcbiAgICB1cmwgPSB1cmwuc2xpY2UocmVzdWx0LnByb3RvLmxlbmd0aCArIDEpO1xuICB9XG4gIGlmICh1cmwuc2xpY2UoMCwgMikgPT09ICcvLycpIHtcbiAgICByZXN1bHQuZG9tYWluID0gdXJsLnNsaWNlKDIpLnNwbGl0KCcvJylbMF07XG4gICAgdXJsID0gdXJsLnNsaWNlKDIgKyByZXN1bHQuZG9tYWluLmxlbmd0aCk7XG4gIH1cbiAgdmFyIHAgPSB1cmwuc3BsaXQoJyMnKTtcbiAgaWYgKHBbMF0ubGVuZ3RoKSB7XG4gICAgcmVzdWx0LnBhdGggPSBwWzBdO1xuICB9XG4gIGlmIChwLmxlbmd0aCA+IDEpIHtcbiAgICByZXN1bHQuZnJhZ21lbnQgPSBwLnNsaWNlKDEpLmpvaW4oJyMnKTtcbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5mdW5jdGlvbiB1bnNwbGl0VXJsKHVybCkge1xuICB2YXIgcmVzdWx0ID0gdXJsLnBhdGg7XG4gIGlmIChyZXN1bHQgPT09IHVuZGVmaW5lZCkge1xuICAgIHJlc3VsdCA9ICcnO1xuICB9XG4gIGlmICh1cmwuZnJhZ21lbnQgIT09IHVuZGVmaW5lZCkge1xuICAgIHJlc3VsdCArPSAnIycgKyB1cmwuZnJhZ21lbnQ7XG4gIH1cbiAgaWYgKHVybC5kb21haW4gIT09IHVuZGVmaW5lZCkge1xuICAgIGlmIChyZXN1bHQuc2xpY2UoMCwgMSkgPT09ICcvJykge1xuICAgICAgcmVzdWx0ID0gcmVzdWx0LnNsaWNlKDEpO1xuICAgIH1cbiAgICByZXN1bHQgPSAnLy8nICsgdXJsLmRvbWFpbiArICcvJyArIHJlc3VsdDtcbiAgICBpZiAodXJsLnByb3RvICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIHJlc3VsdCA9IHVybC5wcm90byArICc6JyArIHJlc3VsdDtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxuZnVuY3Rpb24gam9pblVybChiYXNlLCByZWwpIHtcbiAgdmFyIHJlbHNwID0gc3BsaXRVcmwocmVsKTtcbiAgaWYgKHJlbHNwLmRvbWFpbiAhPT0gdW5kZWZpbmVkKSB7XG4gICAgcmV0dXJuIHJlbDtcbiAgfVxuICB2YXIgcmVzdWx0ID0gc3BsaXRVcmwoYmFzZSk7XG4gIGlmIChyZWxzcC5wYXRoID09PSB1bmRlZmluZWQpIHtcbiAgICAvLyBjaGFuZ2Ugb25seSBmcmFnbWVudCBwYXJ0XG4gICAgcmVzdWx0LmZyYWdtZW50ID0gcmVsc3AuZnJhZ21lbnQ7XG4gIH0gZWxzZSBpZiAocmVsc3AucGF0aC5zbGljZSgwLCAxKSA9PT0gJy8nKSB7XG4gICAgLy8gcmVsYXRpdmUgdG8gZG9tYWluXG4gICAgcmVzdWx0LnBhdGggPSByZWxzcC5wYXRoO1xuICAgIHJlc3VsdC5mcmFnbWVudCA9IHJlbHNwLmZyYWdtZW50O1xuICB9IGVsc2Uge1xuICAgIC8vIHJlbGF0aXZlIHRvIHBhdGhcbiAgICB2YXIgcGF0aCA9IHJlc3VsdC5wYXRoID09PSB1bmRlZmluZWQgPyBbXSA6IHJlc3VsdC5wYXRoLnNwbGl0KCcvJyk7XG4gICAgdmFyIHJlbHBhdGggPSByZWxzcC5wYXRoLnNwbGl0KCcvJyk7XG4gICAgaWYgKHBhdGgubGVuZ3RoKSB7XG4gICAgICBwYXRoLnBvcCgpO1xuICAgIH1cbiAgICB3aGlsZSAocmVscGF0aFswXSA9PT0gJy4uJyB8fCByZWxwYXRoWzBdID09PSAnLicpIHtcbiAgICAgIGlmIChyZWxwYXRoWzBdID09PSAnLi4nKSB7XG4gICAgICAgIHBhdGgucG9wKCk7XG4gICAgICB9XG4gICAgICByZWxwYXRoLnNoaWZ0KCk7XG4gICAgfVxuICAgIHJlc3VsdC5wYXRoID0gcGF0aC5jb25jYXQocmVscGF0aCkuam9pbignLycpO1xuICAgIHJlc3VsdC5mcmFnbWVudCA9IHJlbHNwLmZyYWdtZW50O1xuICB9XG4gIHJldHVybiB1bnNwbGl0VXJsKHJlc3VsdCk7XG59XG5cblJlc29sdmVyLnByb3RvdHlwZS5yZXRhaW5Sb290ID0gZnVuY3Rpb24ob3JpZ0tleSwgb2JqLCByb290KSB7XG4gIC8vIHdhbGsgb2JqZWN0IGFuZCBsb29rIGZvciByZWxhdGl2ZSAkcmVmc1xuICBpZihfLmlzT2JqZWN0KG9iaikpIHtcbiAgICBmb3IodmFyIGtleSBpbiBvYmopIHtcbiAgICAgIHZhciBpdGVtID0gb2JqW2tleV07XG4gICAgICBpZiAoa2V5ID09PSAnJHJlZicgJiYgdHlwZW9mIGl0ZW0gPT09ICdzdHJpbmcnKSB7XG4gICAgICAgIG9ialtrZXldID0gam9pblVybChyb290LCBpdGVtKTtcbiAgICAgIH1cbiAgICAgIGVsc2UgaWYgKF8uaXNPYmplY3QoaXRlbSkpIHtcbiAgICAgICAgdGhpcy5yZXRhaW5Sb290KGtleSwgaXRlbSwgcm9vdCk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGVsc2UgaWYoXy5pc1N0cmluZyhvYmopICYmIG9yaWdLZXkgPT09ICckcmVmJykge1xuICAgIG9iaiA9IGpvaW5Vcmwocm9vdCwgb2JqKTtcbiAgfVxuICByZXR1cm4gb2JqO1xufTtcblxuLyoqXG4gKiBpbW1lZGlhdGVseSBpbi1saW5lcyBsb2NhbCByZWZzLCBxdWV1ZXMgcmVtb3RlIHJlZnNcbiAqIGZvciBpbmxpbmUgcmVzb2x1dGlvblxuICovXG5SZXNvbHZlci5wcm90b3R5cGUucmVzb2x2ZUlubGluZSA9IGZ1bmN0aW9uIChyb290LCBzcGVjLCBwcm9wZXJ0eSwgcmVzb2x1dGlvblRhYmxlLCB1bnJlc29sdmVkUmVmcywgbG9jYXRpb24pIHtcbiAgdmFyIGtleSA9IHByb3BlcnR5LiRyZWYsIHJlZiA9IHByb3BlcnR5LiRyZWYsIGksIHAsIHAyLCBycztcbiAgdmFyIHJvb3RUcmltbWVkID0gZmFsc2U7XG5cbiAgcm9vdCA9IHJvb3QgfHwgJyc7IC8vIEd1YXJkIGFnYWluc3QgLnNwbGl0LiBAZmVoZ3V5LCB5b3UnbGwgbmVlZCB0byBjaGVjayBpZiB0aGlzIGxvZ2ljIGZpdHNcbiAgLy8gTW9yZSBpbXBvcmFudGx5IGlzIGhvdyBkbyB3ZSBncmFjZWZ1bGx5IGhhbmRsZSByZWxhdGl2ZSB1cmxzLCB3aGVuIHByb3ZpZGVkIGp1c3QgYSAnc3BlYycsIG5vdCBhICd1cmwnID9cblxuICBpZiAocmVmKSB7XG4gICAgaWYocmVmLmluZGV4T2YoJy4uLycpID09PSAwKSB7XG4gICAgICAvLyByZXNldCByb290XG4gICAgICBwID0gcmVmLnNwbGl0KCcuLi8nKTtcbiAgICAgIHAyID0gcm9vdC5zcGxpdCgnLycpO1xuICAgICAgcmVmID0gJyc7XG4gICAgICBmb3IoaSA9IDA7IGkgPCBwLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGlmKHBbaV0gPT09ICcnKSB7XG4gICAgICAgICAgcDIgPSBwMi5zbGljZSgwLCBwMi5sZW5ndGgtMSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgcmVmICs9IHBbaV07XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJvb3QgPSAnJztcbiAgICAgIGZvcihpID0gMDsgaSA8IHAyLmxlbmd0aCAtIDE7IGkrKykge1xuICAgICAgICBpZihpID4gMCkgeyByb290ICs9ICcvJzsgfVxuICAgICAgICByb290ICs9IHAyW2ldO1xuICAgICAgfVxuICAgICAgcm9vdFRyaW1tZWQgPSB0cnVlO1xuICAgIH1cbiAgICBpZihyZWYuaW5kZXhPZignIycpID49IDApIHtcbiAgICAgIGlmKHJlZi5pbmRleE9mKCcvJykgPT09IDApIHtcbiAgICAgICAgcnMgPSByZWYuc3BsaXQoJyMnKTtcbiAgICAgICAgcCAgPSByb290LnNwbGl0KCcvLycpO1xuICAgICAgICBwMiA9IHBbMV0uc3BsaXQoJy8nKTtcbiAgICAgICAgcm9vdCA9IHBbMF0gKyAnLy8nICsgcDJbMF0gKyByc1swXTtcbiAgICAgICAgbG9jYXRpb24gPSByc1sxXTtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICBycyA9IHJlZi5zcGxpdCgnIycpO1xuICAgICAgICBpZihyc1swXSAhPT0gJycpIHtcbiAgICAgICAgICBwMiA9IHJvb3Quc3BsaXQoJy8nKTtcbiAgICAgICAgICBwMiA9IHAyLnNsaWNlKDAsIHAyLmxlbmd0aCAtIDEpO1xuICAgICAgICAgIGlmKCFyb290VHJpbW1lZCkge1xuICAgICAgICAgICAgcm9vdCA9ICcnO1xuICAgICAgICAgICAgZm9yICh2YXIgayA9IDA7IGsgPCBwMi5sZW5ndGg7IGsrKykge1xuICAgICAgICAgICAgICBpZihrID4gMCkgeyByb290ICs9ICcvJzsgfVxuICAgICAgICAgICAgICByb290ICs9IHAyW2tdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICByb290ICs9ICcvJyArIHJlZi5zcGxpdCgnIycpWzBdO1xuICAgICAgICB9XG4gICAgICAgIGxvY2F0aW9uID0gcnNbMV07XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChyZWYuaW5kZXhPZignaHR0cDonKSA9PT0gMCB8fCByZWYuaW5kZXhPZignaHR0cHM6JykgPT09IDApIHtcbiAgICAgIGlmKHJlZi5pbmRleE9mKCcjJykgPj0gMCkge1xuICAgICAgICByb290ID0gcmVmLnNwbGl0KCcjJylbMF07XG4gICAgICAgIGxvY2F0aW9uID0gcmVmLnNwbGl0KCcjJylbMV07XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgcm9vdCA9IHJlZjtcbiAgICAgICAgbG9jYXRpb24gPSAnJztcbiAgICAgIH1cbiAgICAgIHJlc29sdXRpb25UYWJsZS5wdXNoKHtvYmo6IHByb3BlcnR5LCByZXNvbHZlQXM6ICdpbmxpbmUnLCByb290OiByb290LCBrZXk6IGtleSwgbG9jYXRpb246IGxvY2F0aW9ufSk7XG4gICAgfSBlbHNlIGlmIChyZWYuaW5kZXhPZignIycpID09PSAwKSB7XG4gICAgICBsb2NhdGlvbiA9IHJlZi5zcGxpdCgnIycpWzFdO1xuICAgICAgcmVzb2x1dGlvblRhYmxlLnB1c2goe29iajogcHJvcGVydHksIHJlc29sdmVBczogJ2lubGluZScsIHJvb3Q6IHJvb3QsIGtleToga2V5LCBsb2NhdGlvbjogbG9jYXRpb259KTtcbiAgICB9IGVsc2UgaWYgKHJlZi5pbmRleE9mKCcvJykgPT09IDAgJiYgcmVmLmluZGV4T2YoJyMnKSA9PT0gLTEpIHtcbiAgICAgIGxvY2F0aW9uID0gcmVmO1xuICAgICAgdmFyIG1hdGNoZXMgPSByb290Lm1hdGNoKC9eaHR0cHM/XFw6XFwvXFwvKFteXFwvPyNdKykoPzpbXFwvPyNdfCQpL2kpO1xuICAgICAgaWYobWF0Y2hlcykge1xuICAgICAgICByb290ID0gbWF0Y2hlc1swXSArIHJlZi5zdWJzdHJpbmcoMSk7XG4gICAgICAgIGxvY2F0aW9uID0gJyc7XG4gICAgICB9XG4gICAgICByZXNvbHV0aW9uVGFibGUucHVzaCh7b2JqOiBwcm9wZXJ0eSwgcmVzb2x2ZUFzOiAnaW5saW5lJywgcm9vdDogcm9vdCwga2V5OiBrZXksIGxvY2F0aW9uOiBsb2NhdGlvbn0pO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHJlc29sdXRpb25UYWJsZS5wdXNoKHtvYmo6IHByb3BlcnR5LCByZXNvbHZlQXM6ICdpbmxpbmUnLCByb290OiByb290LCBrZXk6IGtleSwgbG9jYXRpb246IGxvY2F0aW9ufSk7XG4gICAgfVxuICB9XG4gIGVsc2UgaWYgKHByb3BlcnR5LnR5cGUgPT09ICdhcnJheScpIHtcbiAgICB0aGlzLnJlc29sdmVUbyhyb290LCBwcm9wZXJ0eS5pdGVtcywgcmVzb2x1dGlvblRhYmxlLCBsb2NhdGlvbik7XG4gIH1cbn07XG5cblJlc29sdmVyLnByb3RvdHlwZS5yZXNvbHZlVG8gPSBmdW5jdGlvbiAocm9vdCwgcHJvcGVydHksIHJlc29sdXRpb25UYWJsZSwgbG9jYXRpb24pIHtcbiAgdmFyIHNwLCBpO1xuICB2YXIgcmVmID0gcHJvcGVydHkuJHJlZjtcbiAgdmFyIGxyb290ID0gcm9vdDtcbiAgaWYgKCh0eXBlb2YgcmVmICE9PSAndW5kZWZpbmVkJykgJiYgKHJlZiAhPT0gbnVsbCkpIHtcbiAgICBpZihyZWYuaW5kZXhPZignIycpID49IDApIHtcbiAgICAgIHZhciBwYXJ0cyA9IHJlZi5zcGxpdCgnIycpO1xuXG4gICAgICAvLyAjL2RlZmluaXRpb25zL2Zvb1xuICAgICAgLy8gZm9vLmpzb24jL2JhclxuICAgICAgaWYocGFydHNbMF0gJiYgcmVmLmluZGV4T2YoJy8nKSA9PT0gMCkge1xuXG4gICAgICB9XG4gICAgICBlbHNlIGlmKHBhcnRzWzBdICYmIChwYXJ0c1swXS5pbmRleE9mKCdodHRwOicpID09PSAwIHx8IHBhcnRzWzBdLmluZGV4T2YoJ2h0dHBzOicpID09PSAwKSkge1xuICAgICAgICBscm9vdCA9IHBhcnRzWzBdO1xuICAgICAgICByZWYgPSBwYXJ0c1sxXTtcbiAgICAgIH1cbiAgICAgIGVsc2UgaWYocGFydHNbMF0gJiYgcGFydHNbMF0ubGVuZ3RoID4gMCkge1xuICAgICAgICAvLyByZWxhdGl2ZSBmaWxlXG4gICAgICAgIHNwID0gcm9vdC5zcGxpdCgnLycpO1xuICAgICAgICBscm9vdCA9ICcnO1xuICAgICAgICBmb3IoaSA9IDA7IGkgPCBzcC5sZW5ndGggLSAxOyBpKyspIHtcbiAgICAgICAgICBscm9vdCArPSBzcFtpXSArICcvJztcbiAgICAgICAgfVxuICAgICAgICBscm9vdCArPSBwYXJ0c1swXTtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuXG4gICAgICB9XG5cbiAgICAgIGxvY2F0aW9uID0gcGFydHNbMV07XG4gICAgfVxuICAgIGVsc2UgaWYgKHJlZi5pbmRleE9mKCdodHRwOicpID09PSAwIHx8IHJlZi5pbmRleE9mKCdodHRwczonKSA9PT0gMCkge1xuICAgICAgbHJvb3QgPSByZWY7XG4gICAgICBsb2NhdGlvbiA9ICcnO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIC8vIHJlbGF0aXZlIGZpbGVcbiAgICAgIHNwID0gcm9vdC5zcGxpdCgnLycpO1xuICAgICAgbHJvb3QgPSAnJztcbiAgICAgIGZvcihpID0gMDsgaSA8IHNwLmxlbmd0aCAtIDE7IGkrKykge1xuICAgICAgICBscm9vdCArPSBzcFtpXSArICcvJztcbiAgICAgIH1cbiAgICAgIGxyb290ICs9IHJlZjtcbiAgICAgIGxvY2F0aW9uID0gJyc7XG4gICAgfVxuICAgIHJlc29sdXRpb25UYWJsZS5wdXNoKHtcbiAgICAgIG9iajogcHJvcGVydHksIHJlc29sdmVBczogJ3JlZicsIHJvb3Q6IGxyb290LCBrZXk6IHJlZiwgbG9jYXRpb246IGxvY2F0aW9uXG4gICAgfSk7XG4gIH0gZWxzZSBpZiAocHJvcGVydHkudHlwZSA9PT0gJ2FycmF5Jykge1xuICAgIHZhciBpdGVtcyA9IHByb3BlcnR5Lml0ZW1zO1xuICAgIHRoaXMucmVzb2x2ZVRvKHJvb3QsIGl0ZW1zLCByZXNvbHV0aW9uVGFibGUsIGxvY2F0aW9uKTtcbiAgfSBlbHNlIHtcbiAgICBpZihwcm9wZXJ0eSAmJiAocHJvcGVydHkucHJvcGVydGllcyB8fCBwcm9wZXJ0eS5hZGRpdGlvbmFsUHJvcGVydGllcykpIHtcbiAgICAgIHZhciBuYW1lID0gdGhpcy51bmlxdWVOYW1lKCdpbmxpbmVfbW9kZWwnKTtcbiAgICAgIGlmIChwcm9wZXJ0eS50aXRsZSkge1xuICAgICAgICBuYW1lID0gdGhpcy51bmlxdWVOYW1lKHByb3BlcnR5LnRpdGxlKTtcbiAgICAgIH1cbiAgICAgIGRlbGV0ZSBwcm9wZXJ0eS50aXRsZTtcbiAgICAgIHRoaXMuc3BlYy5kZWZpbml0aW9uc1tuYW1lXSA9IF8uY2xvbmVEZWVwKHByb3BlcnR5KTtcbiAgICAgIHByb3BlcnR5LiRyZWYgPSAnIy9kZWZpbml0aW9ucy8nICsgbmFtZTtcbiAgICAgIGRlbGV0ZSBwcm9wZXJ0eS50eXBlO1xuICAgICAgZGVsZXRlIHByb3BlcnR5LnByb3BlcnRpZXM7XG4gICAgfVxuICB9XG59O1xuXG5SZXNvbHZlci5wcm90b3R5cGUudW5pcXVlTmFtZSA9IGZ1bmN0aW9uKGJhc2UpIHtcbiAgdmFyIG5hbWUgPSBiYXNlO1xuICB2YXIgY291bnQgPSAwO1xuICB3aGlsZSh0cnVlKSB7XG4gICAgaWYoIV8uaXNPYmplY3QodGhpcy5zcGVjLmRlZmluaXRpb25zW25hbWVdKSkge1xuICAgICAgcmV0dXJuIG5hbWU7XG4gICAgfVxuICAgIG5hbWUgPSBiYXNlICsgJ18nICsgY291bnQ7XG4gICAgY291bnQrKztcbiAgfVxufTtcblxuUmVzb2x2ZXIucHJvdG90eXBlLnJlc29sdmVBbGxPZiA9IGZ1bmN0aW9uKHNwZWMsIG9iaiwgZGVwdGgpIHtcbiAgZGVwdGggPSBkZXB0aCB8fCAwO1xuICBvYmogPSBvYmogfHwgc3BlYztcbiAgdmFyIG5hbWU7XG4gIGZvcih2YXIga2V5IGluIG9iaikge1xuICAgIGlmICghb2JqLmhhc093blByb3BlcnR5KGtleSkpIHtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICB2YXIgaXRlbSA9IG9ialtrZXldO1xuICAgIGlmKGl0ZW0gPT09IG51bGwpIHtcbiAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ1N3YWdnZXIgMi4wIGRvZXMgbm90IHN1cHBvcnQgbnVsbCB0eXBlcyAoJyArIG9iaiArICcpLiAgU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9zd2FnZ2VyLWFwaS9zd2FnZ2VyLXNwZWMvaXNzdWVzLzIyOS4nKTtcbiAgICB9XG4gICAgaWYodHlwZW9mIGl0ZW0gPT09ICdvYmplY3QnKSB7XG4gICAgICB0aGlzLnJlc29sdmVBbGxPZihzcGVjLCBpdGVtLCBkZXB0aCArIDEpO1xuICAgIH1cbiAgICBpZihpdGVtICYmIHR5cGVvZiBpdGVtLmFsbE9mICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgdmFyIGFsbE9mID0gaXRlbS5hbGxPZjtcbiAgICAgIGlmKF8uaXNBcnJheShhbGxPZikpIHtcbiAgICAgICAgdmFyIG91dHB1dCA9IF8uY2xvbmVEZWVwKGl0ZW0pO1xuICAgICAgICBkZWxldGUgb3V0cHV0LmFsbE9mO1xuXG4gICAgICAgIG91dHB1dFsneC1jb21wb3NlZCddID0gdHJ1ZTtcbiAgICAgICAgaWYgKHR5cGVvZiBpdGVtWyd4LXJlc29sdmVkLWZyb20nXSAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgICBvdXRwdXRbJ3gtcmVzb2x2ZWQtZnJvbSddID0gaXRlbVsneC1yZXNvbHZlZC1mcm9tJ107XG4gICAgICAgIH1cblxuICAgICAgICBmb3IodmFyIGkgPSAwOyBpIDwgYWxsT2YubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICB2YXIgY29tcG9uZW50ID0gYWxsT2ZbaV07XG4gICAgICAgICAgdmFyIHNvdXJjZSA9ICdzZWxmJztcbiAgICAgICAgICBpZih0eXBlb2YgY29tcG9uZW50Wyd4LXJlc29sdmVkLWZyb20nXSAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgICAgIHNvdXJjZSA9IGNvbXBvbmVudFsneC1yZXNvbHZlZC1mcm9tJ11bMF07XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgZm9yKHZhciBwYXJ0IGluIGNvbXBvbmVudCkge1xuICAgICAgICAgICAgaWYoIW91dHB1dC5oYXNPd25Qcm9wZXJ0eShwYXJ0KSkge1xuICAgICAgICAgICAgICBvdXRwdXRbcGFydF0gPSBfLmNsb25lRGVlcChjb21wb25lbnRbcGFydF0pO1xuICAgICAgICAgICAgICBpZihwYXJ0ID09PSAncHJvcGVydGllcycpIHtcbiAgICAgICAgICAgICAgICBmb3IobmFtZSBpbiBvdXRwdXRbcGFydF0pIHtcbiAgICAgICAgICAgICAgICAgIG91dHB1dFtwYXJ0XVtuYW1lXVsneC1yZXNvbHZlZC1mcm9tJ10gPSBzb3VyY2U7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgaWYocGFydCA9PT0gJ3Byb3BlcnRpZXMnKSB7XG4gICAgICAgICAgICAgICAgdmFyIHByb3BlcnRpZXMgPSBjb21wb25lbnRbcGFydF07XG4gICAgICAgICAgICAgICAgZm9yKG5hbWUgaW4gcHJvcGVydGllcykge1xuICAgICAgICAgICAgICAgICAgb3V0cHV0LnByb3BlcnRpZXNbbmFtZV0gPSBfLmNsb25lRGVlcChwcm9wZXJ0aWVzW25hbWVdKTtcbiAgICAgICAgICAgICAgICAgIHZhciByZXNvbHZlZEZyb20gPSBwcm9wZXJ0aWVzW25hbWVdWyd4LXJlc29sdmVkLWZyb20nXTtcbiAgICAgICAgICAgICAgICAgIGlmICh0eXBlb2YgcmVzb2x2ZWRGcm9tID09PSAndW5kZWZpbmVkJyB8fCByZXNvbHZlZEZyb20gPT09ICdzZWxmJykge1xuICAgICAgICAgICAgICAgICAgICByZXNvbHZlZEZyb20gPSBzb3VyY2U7XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICBvdXRwdXQucHJvcGVydGllc1tuYW1lXVsneC1yZXNvbHZlZC1mcm9tJ10gPSByZXNvbHZlZEZyb207XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGVsc2UgaWYocGFydCA9PT0gJ3JlcXVpcmVkJykge1xuICAgICAgICAgICAgICAgIC8vIG1lcmdlICYgZGVkdXAgdGhlIHJlcXVpcmVkIGFycmF5XG4gICAgICAgICAgICAgICAgdmFyIGEgPSBvdXRwdXQucmVxdWlyZWQuY29uY2F0KGNvbXBvbmVudFtwYXJ0XSk7XG4gICAgICAgICAgICAgICAgZm9yKHZhciBrID0gMDsgayA8IGEubGVuZ3RoOyArK2spIHtcbiAgICAgICAgICAgICAgICAgIGZvcih2YXIgaiA9IGsgKyAxOyBqIDwgYS5sZW5ndGg7ICsraikge1xuICAgICAgICAgICAgICAgICAgICBpZihhW2tdID09PSBhW2pdKSB7IGEuc3BsaWNlKGotLSwgMSk7IH1cbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgb3V0cHV0LnJlcXVpcmVkID0gYTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBlbHNlIGlmKHBhcnQgPT09ICd4LXJlc29sdmVkLWZyb20nKSB7XG4gICAgICAgICAgICAgICAgb3V0cHV0Wyd4LXJlc29sdmVkLWZyb20nXS5wdXNoKHNvdXJjZSk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgLy8gVE9ETzogbmVlZCB0byBtZXJnZSB0aGlzIHByb3BlcnR5XG4gICAgICAgICAgICAgICAgLy8gY29uc29sZS5sb2coJ3doYXQgdG8gZG8gd2l0aCAnICsgcGFydClcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBvYmpba2V5XSA9IG91dHB1dDtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBIZWxwZXJzID0gcmVxdWlyZSgnLi9oZWxwZXJzJyk7XG5cbnZhciBfID0ge1xuICBpc1BsYWluT2JqZWN0OiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvaXNQbGFpbk9iamVjdCcpLFxuICBpc1VuZGVmaW5lZDogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9sYW5nL2lzVW5kZWZpbmVkJyksXG4gIGlzQXJyYXk6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9pc0FycmF5JyksXG4gIGlzT2JqZWN0OiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvaXNPYmplY3QnKSxcbiAgaXNFbXB0eTogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9sYW5nL2lzRW1wdHknKSxcbiAgbWFwOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2NvbGxlY3Rpb24vbWFwJyksXG4gIGluZGV4T2Y6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvYXJyYXkvaW5kZXhPZicpLFxuICBjbG9uZURlZXA6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9jbG9uZURlZXAnKSxcbiAga2V5czogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9vYmplY3Qva2V5cycpLFxuICBmb3JFYWNoOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2NvbGxlY3Rpb24vZm9yRWFjaCcpXG59O1xuXG52YXIgb3B0aW9uSHRtbCA9IG1vZHVsZS5leHBvcnRzLm9wdGlvbkh0bWwgPSBmdW5jdGlvbiAgKGxhYmVsLCB2YWx1ZSkge1xuICByZXR1cm4gJzx0cj48dGQgY2xhc3M9XCJvcHRpb25OYW1lXCI+JyArIGxhYmVsICsgJzo8L3RkPjx0ZD4nICsgdmFsdWUgKyAnPC90ZD48L3RyPic7XG59O1xuXG5tb2R1bGUuZXhwb3J0cy50eXBlRnJvbUpzb25TY2hlbWEgPSBmdW5jdGlvbiAodHlwZSwgZm9ybWF0KSB7XG4gIHZhciBzdHI7XG5cbiAgaWYgKHR5cGUgPT09ICdpbnRlZ2VyJyAmJiBmb3JtYXQgPT09ICdpbnQzMicpIHtcbiAgICBzdHIgPSAnaW50ZWdlcic7XG4gIH0gZWxzZSBpZiAodHlwZSA9PT0gJ2ludGVnZXInICYmIGZvcm1hdCA9PT0gJ2ludDY0Jykge1xuICAgIHN0ciA9ICdsb25nJztcbiAgfSBlbHNlIGlmICh0eXBlID09PSAnaW50ZWdlcicgJiYgdHlwZW9mIGZvcm1hdCA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBzdHIgPSAnbG9uZyc7XG4gIH0gZWxzZSBpZiAodHlwZSA9PT0gJ3N0cmluZycgJiYgZm9ybWF0ID09PSAnZGF0ZS10aW1lJykge1xuICAgIHN0ciA9ICdkYXRlLXRpbWUnO1xuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdzdHJpbmcnICYmIGZvcm1hdCA9PT0gJ2RhdGUnKSB7XG4gICAgc3RyID0gJ2RhdGUnO1xuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdudW1iZXInICYmIGZvcm1hdCA9PT0gJ2Zsb2F0Jykge1xuICAgIHN0ciA9ICdmbG9hdCc7XG4gIH0gZWxzZSBpZiAodHlwZSA9PT0gJ251bWJlcicgJiYgZm9ybWF0ID09PSAnZG91YmxlJykge1xuICAgIHN0ciA9ICdkb3VibGUnO1xuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdudW1iZXInICYmIHR5cGVvZiBmb3JtYXQgPT09ICd1bmRlZmluZWQnKSB7XG4gICAgc3RyID0gJ2RvdWJsZSc7XG4gIH0gZWxzZSBpZiAodHlwZSA9PT0gJ2Jvb2xlYW4nKSB7XG4gICAgc3RyID0gJ2Jvb2xlYW4nO1xuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdzdHJpbmcnKSB7XG4gICAgc3RyID0gJ3N0cmluZyc7XG4gIH1cblxuICByZXR1cm4gc3RyO1xufTtcblxudmFyIGdldFN0cmluZ1NpZ25hdHVyZSA9IG1vZHVsZS5leHBvcnRzLmdldFN0cmluZ1NpZ25hdHVyZSA9IGZ1bmN0aW9uIChvYmosIGJhc2VDb21wb25lbnQpIHtcbiAgdmFyIHN0ciA9ICcnO1xuXG4gIGlmICh0eXBlb2Ygb2JqLiRyZWYgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgc3RyICs9IEhlbHBlcnMuc2ltcGxlUmVmKG9iai4kcmVmKTtcbiAgfSBlbHNlIGlmICh0eXBlb2Ygb2JqLnR5cGUgPT09ICd1bmRlZmluZWQnKSB7XG4gICAgc3RyICs9ICdvYmplY3QnO1xuICB9IGVsc2UgaWYgKG9iai50eXBlID09PSAnYXJyYXknKSB7XG4gICAgaWYgKGJhc2VDb21wb25lbnQpIHtcbiAgICAgIHN0ciArPSBnZXRTdHJpbmdTaWduYXR1cmUoKG9iai5pdGVtcyB8fCBvYmouJHJlZiB8fCB7fSkpO1xuICAgIH0gZWxzZSB7XG4gICAgICBzdHIgKz0gJ0FycmF5Wyc7XG4gICAgICBzdHIgKz0gZ2V0U3RyaW5nU2lnbmF0dXJlKChvYmouaXRlbXMgfHwgb2JqLiRyZWYgfHwge30pKTtcbiAgICAgIHN0ciArPSAnXSc7XG4gICAgfVxuICB9IGVsc2UgaWYgKG9iai50eXBlID09PSAnaW50ZWdlcicgJiYgb2JqLmZvcm1hdCA9PT0gJ2ludDMyJykge1xuICAgIHN0ciArPSAnaW50ZWdlcic7XG4gIH0gZWxzZSBpZiAob2JqLnR5cGUgPT09ICdpbnRlZ2VyJyAmJiBvYmouZm9ybWF0ID09PSAnaW50NjQnKSB7XG4gICAgc3RyICs9ICdsb25nJztcbiAgfSBlbHNlIGlmIChvYmoudHlwZSA9PT0gJ2ludGVnZXInICYmIHR5cGVvZiBvYmouZm9ybWF0ID09PSAndW5kZWZpbmVkJykge1xuICAgIHN0ciArPSAnbG9uZyc7XG4gIH0gZWxzZSBpZiAob2JqLnR5cGUgPT09ICdzdHJpbmcnICYmIG9iai5mb3JtYXQgPT09ICdkYXRlLXRpbWUnKSB7XG4gICAgc3RyICs9ICdkYXRlLXRpbWUnO1xuICB9IGVsc2UgaWYgKG9iai50eXBlID09PSAnc3RyaW5nJyAmJiBvYmouZm9ybWF0ID09PSAnZGF0ZScpIHtcbiAgICBzdHIgKz0gJ2RhdGUnO1xuICB9IGVsc2UgaWYgKG9iai50eXBlID09PSAnc3RyaW5nJyAmJiB0eXBlb2Ygb2JqLmZvcm1hdCA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBzdHIgKz0gJ3N0cmluZyc7XG4gIH0gZWxzZSBpZiAob2JqLnR5cGUgPT09ICdudW1iZXInICYmIG9iai5mb3JtYXQgPT09ICdmbG9hdCcpIHtcbiAgICBzdHIgKz0gJ2Zsb2F0JztcbiAgfSBlbHNlIGlmIChvYmoudHlwZSA9PT0gJ251bWJlcicgJiYgb2JqLmZvcm1hdCA9PT0gJ2RvdWJsZScpIHtcbiAgICBzdHIgKz0gJ2RvdWJsZSc7XG4gIH0gZWxzZSBpZiAob2JqLnR5cGUgPT09ICdudW1iZXInICYmIHR5cGVvZiBvYmouZm9ybWF0ID09PSAndW5kZWZpbmVkJykge1xuICAgIHN0ciArPSAnZG91YmxlJztcbiAgfSBlbHNlIGlmIChvYmoudHlwZSA9PT0gJ2Jvb2xlYW4nKSB7XG4gICAgc3RyICs9ICdib29sZWFuJztcbiAgfSBlbHNlIGlmIChvYmouJHJlZikge1xuICAgIHN0ciArPSBIZWxwZXJzLnNpbXBsZVJlZihvYmouJHJlZik7XG4gIH0gZWxzZSB7XG4gICAgc3RyICs9IG9iai50eXBlO1xuICB9XG5cbiAgcmV0dXJuIHN0cjtcbn07XG5cbnZhciBzY2hlbWFUb0pTT04gPSBtb2R1bGUuZXhwb3J0cy5zY2hlbWFUb0pTT04gPSBmdW5jdGlvbiAoc2NoZW1hLCBtb2RlbHMsIG1vZGVsc1RvSWdub3JlLCBtb2RlbFByb3BlcnR5TWFjcm8pIHtcbiAgLy8gUmVzb2x2ZSB0aGUgc2NoZW1hIChIYW5kbGUgbmVzdGVkIHNjaGVtYXMpXG4gIHNjaGVtYSA9IEhlbHBlcnMucmVzb2x2ZVNjaGVtYShzY2hlbWEpO1xuXG4gIGlmKHR5cGVvZiBtb2RlbFByb3BlcnR5TWFjcm8gIT09ICdmdW5jdGlvbicpIHtcbiAgICBtb2RlbFByb3BlcnR5TWFjcm8gPSBmdW5jdGlvbihwcm9wKXtcbiAgICAgIHJldHVybiAocHJvcCB8fCB7fSkuZGVmYXVsdDtcbiAgICB9O1xuICB9XG5cbiAgbW9kZWxzVG9JZ25vcmU9IG1vZGVsc1RvSWdub3JlIHx8IHt9O1xuXG4gIHZhciB0eXBlID0gc2NoZW1hLnR5cGUgfHwgJ29iamVjdCc7XG4gIHZhciBmb3JtYXQgPSBzY2hlbWEuZm9ybWF0O1xuICB2YXIgbW9kZWw7XG4gIHZhciBvdXRwdXQ7XG5cbiAgaWYgKCFfLmlzVW5kZWZpbmVkKHNjaGVtYS5leGFtcGxlKSkge1xuICAgIG91dHB1dCA9IHNjaGVtYS5leGFtcGxlO1xuICB9IGVsc2UgaWYgKF8uaXNVbmRlZmluZWQoc2NoZW1hLml0ZW1zKSAmJiBfLmlzQXJyYXkoc2NoZW1hLmVudW0pKSB7XG4gICAgb3V0cHV0ID0gc2NoZW1hLmVudW1bMF07XG4gIH1cblxuICBpZiAoXy5pc1VuZGVmaW5lZChvdXRwdXQpKSB7XG4gICAgaWYgKHNjaGVtYS4kcmVmKSB7XG4gICAgICBtb2RlbCA9IG1vZGVsc1tIZWxwZXJzLnNpbXBsZVJlZihzY2hlbWEuJHJlZildO1xuXG4gICAgICBpZiAoIV8uaXNVbmRlZmluZWQobW9kZWwpKSB7XG4gICAgICAgIGlmIChfLmlzVW5kZWZpbmVkKG1vZGVsc1RvSWdub3JlW21vZGVsLm5hbWVdKSkge1xuICAgICAgICAgIG1vZGVsc1RvSWdub3JlW21vZGVsLm5hbWVdID0gbW9kZWw7XG4gICAgICAgICAgb3V0cHV0ID0gc2NoZW1hVG9KU09OKG1vZGVsLmRlZmluaXRpb24sIG1vZGVscywgbW9kZWxzVG9JZ25vcmUsIG1vZGVsUHJvcGVydHlNYWNybyk7XG4gICAgICAgICAgZGVsZXRlIG1vZGVsc1RvSWdub3JlW21vZGVsLm5hbWVdO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGlmIChtb2RlbC50eXBlID09PSAnYXJyYXknKSB7XG4gICAgICAgICAgICBvdXRwdXQgPSBbXTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgb3V0cHV0ID0ge307XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICghXy5pc1VuZGVmaW5lZChzY2hlbWEuZGVmYXVsdCkpIHtcbiAgICAgIG91dHB1dCA9IHNjaGVtYS5kZWZhdWx0O1xuICAgIH0gZWxzZSBpZiAodHlwZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGlmIChmb3JtYXQgPT09ICdkYXRlLXRpbWUnKSB7XG4gICAgICAgIG91dHB1dCA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKTtcbiAgICAgIH0gZWxzZSBpZiAoZm9ybWF0ID09PSAnZGF0ZScpIHtcbiAgICAgICAgb3V0cHV0ID0gbmV3IERhdGUoKS50b0lTT1N0cmluZygpLnNwbGl0KCdUJylbMF07XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBvdXRwdXQgPSAnc3RyaW5nJztcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdpbnRlZ2VyJykge1xuICAgICAgb3V0cHV0ID0gMDtcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdudW1iZXInKSB7XG4gICAgICBvdXRwdXQgPSAwLjA7XG4gICAgfSBlbHNlIGlmICh0eXBlID09PSAnYm9vbGVhbicpIHtcbiAgICAgIG91dHB1dCA9IHRydWU7XG4gICAgfSBlbHNlIGlmICh0eXBlID09PSAnb2JqZWN0Jykge1xuICAgICAgb3V0cHV0ID0ge307XG5cbiAgICAgIF8uZm9yRWFjaChzY2hlbWEucHJvcGVydGllcywgZnVuY3Rpb24gKHByb3BlcnR5LCBuYW1lKSB7XG4gICAgICAgIHZhciBjUHJvcGVydHkgPSBfLmNsb25lRGVlcChwcm9wZXJ0eSk7XG5cbiAgICAgICAgLy8gQWxsb3cgbWFjcm8gdG8gc2V0IHRoZSBkZWZhdWx0IHZhbHVlXG4gICAgICAgIGNQcm9wZXJ0eS5kZWZhdWx0ID0gbW9kZWxQcm9wZXJ0eU1hY3JvKHByb3BlcnR5KTtcblxuICAgICAgICBvdXRwdXRbbmFtZV0gPSBzY2hlbWFUb0pTT04oY1Byb3BlcnR5LCBtb2RlbHMsIG1vZGVsc1RvSWdub3JlLCBtb2RlbFByb3BlcnR5TWFjcm8pO1xuICAgICAgfSk7XG4gICAgfSBlbHNlIGlmICh0eXBlID09PSAnYXJyYXknKSB7XG4gICAgICBvdXRwdXQgPSBbXTtcblxuICAgICAgaWYgKF8uaXNBcnJheShzY2hlbWEuaXRlbXMpKSB7XG4gICAgICAgIF8uZm9yRWFjaChzY2hlbWEuaXRlbXMsIGZ1bmN0aW9uIChpdGVtKSB7XG4gICAgICAgICAgb3V0cHV0LnB1c2goc2NoZW1hVG9KU09OKGl0ZW0sIG1vZGVscywgbW9kZWxzVG9JZ25vcmUsIG1vZGVsUHJvcGVydHlNYWNybykpO1xuICAgICAgICB9KTtcbiAgICAgIH0gZWxzZSBpZiAoXy5pc1BsYWluT2JqZWN0KHNjaGVtYS5pdGVtcykpIHtcbiAgICAgICAgb3V0cHV0LnB1c2goc2NoZW1hVG9KU09OKHNjaGVtYS5pdGVtcywgbW9kZWxzLCBtb2RlbHNUb0lnbm9yZSwgbW9kZWxQcm9wZXJ0eU1hY3JvKSk7XG4gICAgICB9IGVsc2UgaWYgKF8uaXNVbmRlZmluZWQoc2NoZW1hLml0ZW1zKSkge1xuICAgICAgICBvdXRwdXQucHVzaCh7fSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBIZWxwZXJzLmxvZygnQXJyYXkgdHlwZVxcJ3MgXFwnaXRlbXNcXCcgcHJvcGVydHkgaXMgbm90IGFuIGFycmF5IG9yIGFuIG9iamVjdCwgY2Fubm90IHByb2Nlc3MnKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4gb3V0cHV0O1xufTtcblxubW9kdWxlLmV4cG9ydHMuc2NoZW1hVG9IVE1MID1mdW5jdGlvbiAobmFtZSwgc2NoZW1hLCBtb2RlbHMsIG1vZGVsUHJvcGVydHlNYWNybykge1xuICB2YXIgc3Ryb25nT3BlbiA9ICc8c3BhbiBjbGFzcz1cInN0cm9uZ1wiPic7XG4gIHZhciBzdHJvbmdDbG9zZSA9ICc8L3NwYW4+JztcblxuICAvLyBBbGxvdyBmb3IgaWdub3JpbmcgdGhlICduYW1lJyBhcmd1bWVudC4uLi4gc2hpZnRpbmcgdGhlIHJlc3RcbiAgaWYoXy5pc09iamVjdChhcmd1bWVudHNbMF0pKSB7XG4gICAgbmFtZSA9IHZvaWQgMDtcbiAgICBzY2hlbWEgPSBhcmd1bWVudHNbMF07XG4gICAgbW9kZWxzID0gYXJndW1lbnRzWzFdO1xuICAgIG1vZGVsUHJvcGVydHlNYWNybyA9IGFyZ3VtZW50c1syXTtcbiAgfVxuXG4gIG1vZGVscyA9IG1vZGVscyB8fCB7fTtcblxuICAvLyBSZXNvbHZlIHRoZSBzY2hlbWEgKEhhbmRsZSBuZXN0ZWQgc2NoZW1hcylcbiAgc2NoZW1hID0gSGVscGVycy5yZXNvbHZlU2NoZW1hKHNjaGVtYSk7XG5cbiAgLy8gUmV0dXJuIGZvciBlbXB0eSBvYmplY3RcbiAgaWYoXy5pc0VtcHR5KHNjaGVtYSkpIHtcbiAgICByZXR1cm4gc3Ryb25nT3BlbiArICdFbXB0eScgKyBzdHJvbmdDbG9zZTtcbiAgfVxuXG4gIC8vIERlcmVmZXJlbmNlICRyZWYgZnJvbSAnbW9kZWxzJ1xuICBpZih0eXBlb2Ygc2NoZW1hLiRyZWYgPT09ICdzdHJpbmcnKSB7XG4gICAgbmFtZSA9IEhlbHBlcnMuc2ltcGxlUmVmKHNjaGVtYS4kcmVmKTtcbiAgICBzY2hlbWEgPSBtb2RlbHNbbmFtZV07XG4gICAgaWYodHlwZW9mIHNjaGVtYSA9PT0gJ3VuZGVmaW5lZCcpXG4gICAge1xuICAgICAgcmV0dXJuIHN0cm9uZ09wZW4gKyBuYW1lICsgJyBpcyBub3QgZGVmaW5lZCEnICsgc3Ryb25nQ2xvc2U7XG4gICAgfVxuICB9XG5cbiAgaWYodHlwZW9mIG5hbWUgIT09ICdzdHJpbmcnKSB7XG4gICAgbmFtZSA9IHNjaGVtYS50aXRsZSB8fCAnSW5saW5lIE1vZGVsJztcbiAgfVxuXG4gIC8vIElmIHdlIGFyZSBhIE1vZGVsIG9iamVjdC4uLiBhZGp1c3QgYWNjb3JkaW5nbHlcbiAgaWYoc2NoZW1hLmRlZmluaXRpb24pIHtcbiAgICBzY2hlbWEgPSBzY2hlbWEuZGVmaW5pdGlvbjtcbiAgfVxuXG4gIGlmKHR5cGVvZiBtb2RlbFByb3BlcnR5TWFjcm8gIT09ICdmdW5jdGlvbicpIHtcbiAgICBtb2RlbFByb3BlcnR5TWFjcm8gPSBmdW5jdGlvbihwcm9wKXtcbiAgICAgIHJldHVybiAocHJvcCB8fCB7fSkuZGVmYXVsdDtcbiAgICB9O1xuICB9XG5cbiAgdmFyIHJlZmVyZW5jZXMgPSB7fTtcbiAgdmFyIHNlZW5Nb2RlbHMgPSBbXTtcbiAgdmFyIGlubGluZU1vZGVscyA9IDA7XG5cblxuXG4gIC8vIEdlbmVyYXRlIGN1cnJlbnQgSFRNTFxuICB2YXIgaHRtbCA9IHByb2Nlc3NNb2RlbChzY2hlbWEsIG5hbWUpO1xuXG4gIC8vIEdlbmVyYXRlIHJlZmVyZW5jZXMgSFRNTFxuICB3aGlsZSAoXy5rZXlzKHJlZmVyZW5jZXMpLmxlbmd0aCA+IDApIHtcbiAgICAvKiBqc2hpbnQgaWdub3JlOnN0YXJ0ICovXG4gICAgXy5mb3JFYWNoKHJlZmVyZW5jZXMsIGZ1bmN0aW9uIChzY2hlbWEsIG5hbWUpIHtcbiAgICAgIHZhciBzZWVuTW9kZWwgPSBfLmluZGV4T2Yoc2Vlbk1vZGVscywgbmFtZSkgPiAtMTtcblxuICAgICAgZGVsZXRlIHJlZmVyZW5jZXNbbmFtZV07XG5cbiAgICAgIGlmICghc2Vlbk1vZGVsKSB7XG4gICAgICAgIHNlZW5Nb2RlbHMucHVzaChuYW1lKTtcblxuICAgICAgICBodG1sICs9ICc8YnIgLz4nICsgcHJvY2Vzc01vZGVsKHNjaGVtYSwgbmFtZSk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgLyoganNoaW50IGlnbm9yZTplbmQgKi9cbiAgfVxuXG4gIHJldHVybiBodG1sO1xuXG4gIC8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vL1xuXG4gIGZ1bmN0aW9uIGFkZFJlZmVyZW5jZShzY2hlbWEsIG5hbWUsIHNraXBSZWYpIHtcbiAgICB2YXIgbW9kZWxOYW1lID0gbmFtZTtcbiAgICB2YXIgbW9kZWw7XG5cbiAgICBpZiAoc2NoZW1hLiRyZWYpIHtcbiAgICAgIG1vZGVsTmFtZSA9IHNjaGVtYS50aXRsZSB8fCBIZWxwZXJzLnNpbXBsZVJlZihzY2hlbWEuJHJlZik7XG4gICAgICBtb2RlbCA9IG1vZGVsc1ttb2RlbE5hbWVdO1xuICAgIH0gZWxzZSBpZiAoXy5pc1VuZGVmaW5lZChuYW1lKSkge1xuICAgICAgbW9kZWxOYW1lID0gc2NoZW1hLnRpdGxlIHx8ICdJbmxpbmUgTW9kZWwgJyArICgrK2lubGluZU1vZGVscyk7XG4gICAgICBtb2RlbCA9IHtkZWZpbml0aW9uOiBzY2hlbWF9O1xuICAgIH1cblxuICAgIGlmIChza2lwUmVmICE9PSB0cnVlKSB7XG4gICAgICByZWZlcmVuY2VzW21vZGVsTmFtZV0gPSBfLmlzVW5kZWZpbmVkKG1vZGVsKSA/IHt9IDogbW9kZWwuZGVmaW5pdGlvbjtcbiAgICB9XG5cbiAgICByZXR1cm4gbW9kZWxOYW1lO1xuICB9XG5cbiAgZnVuY3Rpb24gcHJpbWl0aXZlVG9IVE1MKHNjaGVtYSkge1xuICAgIHZhciBodG1sID0gJzxzcGFuIGNsYXNzPVwicHJvcFR5cGVcIj4nO1xuICAgIHZhciB0eXBlID0gc2NoZW1hLnR5cGUgfHwgJ29iamVjdCc7XG5cbiAgICBpZiAoc2NoZW1hLiRyZWYpIHtcbiAgICAgIGh0bWwgKz0gYWRkUmVmZXJlbmNlKHNjaGVtYSwgSGVscGVycy5zaW1wbGVSZWYoc2NoZW1hLiRyZWYpKTtcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdvYmplY3QnKSB7XG4gICAgICBpZiAoIV8uaXNVbmRlZmluZWQoc2NoZW1hLnByb3BlcnRpZXMpKSB7XG4gICAgICAgIGh0bWwgKz0gYWRkUmVmZXJlbmNlKHNjaGVtYSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBodG1sICs9ICdvYmplY3QnO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZiAodHlwZSA9PT0gJ2FycmF5Jykge1xuICAgICAgaHRtbCArPSAnQXJyYXlbJztcblxuICAgICAgaWYgKF8uaXNBcnJheShzY2hlbWEuaXRlbXMpKSB7XG4gICAgICAgIGh0bWwgKz0gXy5tYXAoc2NoZW1hLml0ZW1zLCBhZGRSZWZlcmVuY2UpLmpvaW4oJywnKTtcbiAgICAgIH0gZWxzZSBpZiAoXy5pc1BsYWluT2JqZWN0KHNjaGVtYS5pdGVtcykpIHtcbiAgICAgICAgaWYgKF8uaXNVbmRlZmluZWQoc2NoZW1hLml0ZW1zLiRyZWYpKSB7XG4gICAgICAgICAgaWYgKCFfLmlzVW5kZWZpbmVkKHNjaGVtYS5pdGVtcy50eXBlKSAmJiBfLmluZGV4T2YoWydhcnJheScsICdvYmplY3QnXSwgc2NoZW1hLml0ZW1zLnR5cGUpID09PSAtMSkge1xuICAgICAgICAgICAgaHRtbCArPSBzY2hlbWEuaXRlbXMudHlwZTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgaHRtbCArPSBhZGRSZWZlcmVuY2Uoc2NoZW1hLml0ZW1zKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgaHRtbCArPSBhZGRSZWZlcmVuY2Uoc2NoZW1hLml0ZW1zLCBIZWxwZXJzLnNpbXBsZVJlZihzY2hlbWEuaXRlbXMuJHJlZikpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBIZWxwZXJzLmxvZygnQXJyYXkgdHlwZVxcJ3MgXFwnaXRlbXNcXCcgc2NoZW1hIGlzIG5vdCBhbiBhcnJheSBvciBhbiBvYmplY3QsIGNhbm5vdCBwcm9jZXNzJyk7XG4gICAgICAgIGh0bWwgKz0gJ29iamVjdCc7XG4gICAgICB9XG5cbiAgICAgIGh0bWwgKz0gJ10nO1xuICAgIH0gZWxzZSB7XG4gICAgICBodG1sICs9IHNjaGVtYS50eXBlO1xuICAgIH1cblxuICAgIGh0bWwgKz0gJzwvc3Bhbj4nO1xuXG4gICAgcmV0dXJuIGh0bWw7XG4gIH1cblxuICBmdW5jdGlvbiBwcmltaXRpdmVUb09wdGlvbnNIVE1MKHNjaGVtYSwgaHRtbCkge1xuICAgIHZhciBvcHRpb25zID0gJyc7XG4gICAgdmFyIHR5cGUgPSBzY2hlbWEudHlwZSB8fCAnb2JqZWN0JztcbiAgICB2YXIgaXNBcnJheSA9IHR5cGUgPT09ICdhcnJheSc7XG5cbiAgICBpZiAoaXNBcnJheSkge1xuICAgICAgaWYgKF8uaXNQbGFpbk9iamVjdChzY2hlbWEuaXRlbXMpICYmICFfLmlzVW5kZWZpbmVkKHNjaGVtYS5pdGVtcy50eXBlKSkge1xuICAgICAgICB0eXBlID0gc2NoZW1hLml0ZW1zLnR5cGU7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0eXBlID0gJ29iamVjdCc7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKCFfLmlzVW5kZWZpbmVkKHNjaGVtYS5kZWZhdWx0KSkge1xuICAgICAgb3B0aW9ucyArPSBvcHRpb25IdG1sKCdEZWZhdWx0Jywgc2NoZW1hLmRlZmF1bHQpO1xuICAgIH1cblxuICAgIHN3aXRjaCAodHlwZSkge1xuICAgIGNhc2UgJ3N0cmluZyc6XG4gICAgICBpZiAoc2NoZW1hLm1pbkxlbmd0aCkge1xuICAgICAgICBvcHRpb25zICs9IG9wdGlvbkh0bWwoJ01pbi4gTGVuZ3RoJywgc2NoZW1hLm1pbkxlbmd0aCk7XG4gICAgICB9XG5cbiAgICAgIGlmIChzY2hlbWEubWF4TGVuZ3RoKSB7XG4gICAgICAgIG9wdGlvbnMgKz0gb3B0aW9uSHRtbCgnTWF4LiBMZW5ndGgnLCBzY2hlbWEubWF4TGVuZ3RoKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHNjaGVtYS5wYXR0ZXJuKSB7XG4gICAgICAgIG9wdGlvbnMgKz0gb3B0aW9uSHRtbCgnUmVnLiBFeHAuJywgc2NoZW1hLnBhdHRlcm4pO1xuICAgICAgfVxuICAgICAgYnJlYWs7XG4gICAgY2FzZSAnaW50ZWdlcic6XG4gICAgY2FzZSAnbnVtYmVyJzpcbiAgICAgIGlmIChzY2hlbWEubWluaW11bSkge1xuICAgICAgICBvcHRpb25zICs9IG9wdGlvbkh0bWwoJ01pbi4gVmFsdWUnLCBzY2hlbWEubWluaW11bSk7XG4gICAgICB9XG5cbiAgICAgIGlmIChzY2hlbWEuZXhjbHVzaXZlTWluaW11bSkge1xuICAgICAgICBvcHRpb25zICs9IG9wdGlvbkh0bWwoJ0V4Y2x1c2l2ZSBNaW4uJywgJ3RydWUnKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHNjaGVtYS5tYXhpbXVtKSB7XG4gICAgICAgIG9wdGlvbnMgKz0gb3B0aW9uSHRtbCgnTWF4LiBWYWx1ZScsIHNjaGVtYS5tYXhpbXVtKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHNjaGVtYS5leGNsdXNpdmVNYXhpbXVtKSB7XG4gICAgICAgIG9wdGlvbnMgKz0gb3B0aW9uSHRtbCgnRXhjbHVzaXZlIE1heC4nLCAndHJ1ZScpO1xuICAgICAgfVxuXG4gICAgICBpZiAoc2NoZW1hLm11bHRpcGxlT2YpIHtcbiAgICAgICAgb3B0aW9ucyArPSBvcHRpb25IdG1sKCdNdWx0aXBsZSBPZicsIHNjaGVtYS5tdWx0aXBsZU9mKTtcbiAgICAgIH1cblxuICAgICAgYnJlYWs7XG4gICAgfVxuXG4gICAgaWYgKGlzQXJyYXkpIHtcbiAgICAgIGlmIChzY2hlbWEubWluSXRlbXMpIHtcbiAgICAgICAgb3B0aW9ucyArPSBvcHRpb25IdG1sKCdNaW4uIEl0ZW1zJywgc2NoZW1hLm1pbkl0ZW1zKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHNjaGVtYS5tYXhJdGVtcykge1xuICAgICAgICBvcHRpb25zICs9IG9wdGlvbkh0bWwoJ01heC4gSXRlbXMnLCBzY2hlbWEubWF4SXRlbXMpO1xuICAgICAgfVxuXG4gICAgICBpZiAoc2NoZW1hLnVuaXF1ZUl0ZW1zKSB7XG4gICAgICAgIG9wdGlvbnMgKz0gb3B0aW9uSHRtbCgnVW5pcXVlIEl0ZW1zJywgJ3RydWUnKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHNjaGVtYS5jb2xsZWN0aW9uRm9ybWF0KSB7XG4gICAgICAgIG9wdGlvbnMgKz0gb3B0aW9uSHRtbCgnQ29sbC4gRm9ybWF0Jywgc2NoZW1hLmNvbGxlY3Rpb25Gb3JtYXQpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChfLmlzVW5kZWZpbmVkKHNjaGVtYS5pdGVtcykpIHtcbiAgICAgIGlmIChfLmlzQXJyYXkoc2NoZW1hLmVudW0pKSB7XG4gICAgICAgIHZhciBlbnVtU3RyaW5nO1xuXG4gICAgICAgIGlmICh0eXBlID09PSAnbnVtYmVyJyB8fCB0eXBlID09PSAnaW50ZWdlcicpIHtcbiAgICAgICAgICBlbnVtU3RyaW5nID0gc2NoZW1hLmVudW0uam9pbignLCAnKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBlbnVtU3RyaW5nID0gJ1wiJyArIHNjaGVtYS5lbnVtLmpvaW4oJ1wiLCBcIicpICsgJ1wiJztcbiAgICAgICAgfVxuXG4gICAgICAgIG9wdGlvbnMgKz0gb3B0aW9uSHRtbCgnRW51bScsIGVudW1TdHJpbmcpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChvcHRpb25zLmxlbmd0aCA+IDApIHtcbiAgICAgIGh0bWwgPSAnPHNwYW4gY2xhc3M9XCJwcm9wV3JhcFwiPicgKyBodG1sICsgJzx0YWJsZSBjbGFzcz1cIm9wdGlvbnNXcmFwcGVyXCI+PHRyPjx0aCBjb2xzcGFuPVwiMlwiPicgKyB0eXBlICsgJzwvdGg+PC90cj4nICsgb3B0aW9ucyArICc8L3RhYmxlPjwvc3Bhbj4nO1xuICAgIH1cblxuICAgIHJldHVybiBodG1sO1xuICB9XG5cbiAgZnVuY3Rpb24gcHJvY2Vzc01vZGVsKHNjaGVtYSwgbmFtZSkge1xuICAgIHZhciB0eXBlID0gc2NoZW1hLnR5cGUgfHwgJ29iamVjdCc7XG4gICAgdmFyIGlzQXJyYXkgPSBzY2hlbWEudHlwZSA9PT0gJ2FycmF5JztcbiAgICB2YXIgaHRtbCA9IHN0cm9uZ09wZW4gKyBuYW1lICsgJyAnICsgKGlzQXJyYXkgPyAnWycgOiAneycpICsgc3Ryb25nQ2xvc2U7XG5cbiAgICBpZiAobmFtZSkge1xuICAgICAgc2Vlbk1vZGVscy5wdXNoKG5hbWUpO1xuICAgIH1cblxuICAgIGlmIChpc0FycmF5KSB7XG4gICAgICBpZiAoXy5pc0FycmF5KHNjaGVtYS5pdGVtcykpIHtcbiAgICAgICAgaHRtbCArPSAnPGRpdj4nICsgXy5tYXAoc2NoZW1hLml0ZW1zLCBmdW5jdGlvbiAoaXRlbSkge1xuICAgICAgICAgIHZhciB0eXBlID0gaXRlbS50eXBlIHx8ICdvYmplY3QnO1xuXG4gICAgICAgICAgaWYgKF8uaXNVbmRlZmluZWQoaXRlbS4kcmVmKSkge1xuICAgICAgICAgICAgaWYgKF8uaW5kZXhPZihbJ2FycmF5JywgJ29iamVjdCddLCB0eXBlKSA+IC0xKSB7XG4gICAgICAgICAgICAgIGlmICh0eXBlID09PSAnb2JqZWN0JyAmJiBfLmlzVW5kZWZpbmVkKGl0ZW0ucHJvcGVydGllcykpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gJ29iamVjdCc7XG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGFkZFJlZmVyZW5jZShpdGVtKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgcmV0dXJuIHByaW1pdGl2ZVRvT3B0aW9uc0hUTUwoaXRlbSwgdHlwZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJldHVybiBhZGRSZWZlcmVuY2UoaXRlbSwgSGVscGVycy5zaW1wbGVSZWYoaXRlbS4kcmVmKSk7XG4gICAgICAgICAgfVxuICAgICAgICB9KS5qb2luKCcsPC9kaXY+PGRpdj4nKTtcbiAgICAgIH0gZWxzZSBpZiAoXy5pc1BsYWluT2JqZWN0KHNjaGVtYS5pdGVtcykpIHtcbiAgICAgICAgaWYgKF8uaXNVbmRlZmluZWQoc2NoZW1hLml0ZW1zLiRyZWYpKSB7XG4gICAgICAgICAgaWYgKF8uaW5kZXhPZihbJ2FycmF5JywgJ29iamVjdCddLCBzY2hlbWEuaXRlbXMudHlwZSB8fCAnb2JqZWN0JykgPiAtMSkge1xuICAgICAgICAgICAgaWYgKChfLmlzVW5kZWZpbmVkKHNjaGVtYS5pdGVtcy50eXBlKSB8fCBzY2hlbWEuaXRlbXMudHlwZSA9PT0gJ29iamVjdCcpICYmIF8uaXNVbmRlZmluZWQoc2NoZW1hLml0ZW1zLnByb3BlcnRpZXMpKSB7XG4gICAgICAgICAgICAgIGh0bWwgKz0gJzxkaXY+b2JqZWN0PC9kaXY+JztcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIGh0bWwgKz0gJzxkaXY+JyArIGFkZFJlZmVyZW5jZShzY2hlbWEuaXRlbXMpICsgJzwvZGl2Pic7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGh0bWwgKz0gJzxkaXY+JyArIHByaW1pdGl2ZVRvT3B0aW9uc0hUTUwoc2NoZW1hLml0ZW1zLCBzY2hlbWEuaXRlbXMudHlwZSkgKyAnPC9kaXY+JztcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgaHRtbCArPSAnPGRpdj4nICsgYWRkUmVmZXJlbmNlKHNjaGVtYS5pdGVtcywgSGVscGVycy5zaW1wbGVSZWYoc2NoZW1hLml0ZW1zLiRyZWYpKSArICc8L2Rpdj4nO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBIZWxwZXJzLmxvZygnQXJyYXkgdHlwZVxcJ3MgXFwnaXRlbXNcXCcgcHJvcGVydHkgaXMgbm90IGFuIGFycmF5IG9yIGFuIG9iamVjdCwgY2Fubm90IHByb2Nlc3MnKTtcbiAgICAgICAgaHRtbCArPSAnPGRpdj5vYmplY3Q8L2Rpdj4nO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBpZiAoc2NoZW1hLiRyZWYpIHtcbiAgICAgICAgaHRtbCArPSAnPGRpdj4nICsgYWRkUmVmZXJlbmNlKHNjaGVtYSwgbmFtZSkgKyAnPC9kaXY+JztcbiAgICAgIH0gZWxzZSBpZiAodHlwZSA9PT0gJ29iamVjdCcpIHtcbiAgICAgICAgaWYgKF8uaXNQbGFpbk9iamVjdChzY2hlbWEucHJvcGVydGllcykpIHtcbiAgICAgICAgICB2YXIgY29udGVudHMgPSBfLm1hcChzY2hlbWEucHJvcGVydGllcywgZnVuY3Rpb24gKHByb3BlcnR5LCBuYW1lKSB7XG4gICAgICAgICAgICB2YXIgcHJvcGVydHlJc1JlcXVpcmVkID0gKF8uaW5kZXhPZihzY2hlbWEucmVxdWlyZWQsIG5hbWUpID49IDApO1xuICAgICAgICAgICAgdmFyIGNQcm9wZXJ0eSA9IF8uY2xvbmVEZWVwKHByb3BlcnR5KTtcblxuICAgICAgICAgICAgdmFyIHJlcXVpcmVkQ2xhc3MgPSBwcm9wZXJ0eUlzUmVxdWlyZWQgPyAncmVxdWlyZWQnIDogJyc7XG4gICAgICAgICAgICB2YXIgaHRtbCA9ICc8c3BhbiBjbGFzcz1cInByb3BOYW1lICcgKyByZXF1aXJlZENsYXNzICsgJ1wiPicgKyBuYW1lICsgJzwvc3Bhbj4gKCc7XG4gICAgICAgICAgICB2YXIgbW9kZWw7XG4gICAgICAgICAgICB2YXIgcHJvcERlc2NyaXB0aW9uO1xuXG4gICAgICAgICAgICAvLyBBbGxvdyBtYWNybyB0byBzZXQgdGhlIGRlZmF1bHQgdmFsdWVcbiAgICAgICAgICAgIGNQcm9wZXJ0eS5kZWZhdWx0ID0gbW9kZWxQcm9wZXJ0eU1hY3JvKGNQcm9wZXJ0eSk7XG5cbiAgICAgICAgICAgIC8vIFJlc29sdmUgdGhlIHNjaGVtYSAoSGFuZGxlIG5lc3RlZCBzY2hlbWFzKVxuICAgICAgICAgICAgY1Byb3BlcnR5ID0gSGVscGVycy5yZXNvbHZlU2NoZW1hKGNQcm9wZXJ0eSk7XG5cbiAgICAgICAgICAgIHByb3BEZXNjcmlwdGlvbiA9IHByb3BlcnR5LmRlc2NyaXB0aW9uIHx8IGNQcm9wZXJ0eS5kZXNjcmlwdGlvbjtcblxuICAgICAgICAgICAgLy8gV2UgbmVlZCB0byBoYW5kbGUgcHJvcGVydHkgcmVmZXJlbmNlcyB0byBwcmltaXRpdmVzIChJc3N1ZSAzMzkpXG4gICAgICAgICAgICBpZiAoIV8uaXNVbmRlZmluZWQoY1Byb3BlcnR5LiRyZWYpKSB7XG4gICAgICAgICAgICAgIG1vZGVsID0gbW9kZWxzW0hlbHBlcnMuc2ltcGxlUmVmKGNQcm9wZXJ0eS4kcmVmKV07XG5cbiAgICAgICAgICAgICAgaWYgKCFfLmlzVW5kZWZpbmVkKG1vZGVsKSAmJiBfLmluZGV4T2YoW3VuZGVmaW5lZCwgJ2FycmF5JywgJ29iamVjdCddLCBtb2RlbC5kZWZpbml0aW9uLnR5cGUpID09PSAtMSkge1xuICAgICAgICAgICAgICAgIC8vIFVzZSByZWZlcmVuY2VkIHNjaGVtYVxuICAgICAgICAgICAgICAgIGNQcm9wZXJ0eSA9IEhlbHBlcnMucmVzb2x2ZVNjaGVtYShtb2RlbC5kZWZpbml0aW9uKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBodG1sICs9IHByaW1pdGl2ZVRvSFRNTChjUHJvcGVydHkpO1xuXG4gICAgICAgICAgICBpZighcHJvcGVydHlJc1JlcXVpcmVkKSB7XG4gICAgICAgICAgICAgIGh0bWwgKz0gJywgPHNwYW4gY2xhc3M9XCJwcm9wT3B0S2V5XCI+b3B0aW9uYWw8L3NwYW4+JztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYocHJvcGVydHkucmVhZE9ubHkpIHtcbiAgICAgICAgICAgICAgICBodG1sICs9ICcsIDxzcGFuIGNsYXNzPVwicHJvcFJlYWRPbmx5XCI+cmVhZCBvbmx5PC9zcGFuPic7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGh0bWwgKz0gJyknO1xuXG4gICAgICAgICAgICBpZiAoIV8uaXNVbmRlZmluZWQocHJvcERlc2NyaXB0aW9uKSkge1xuICAgICAgICAgICAgICBodG1sICs9ICc6ICcgKyAnPHNwYW4gY2xhc3M9XCJwcm9wRGVzY1wiPicgKyBwcm9wRGVzY3JpcHRpb24gKyAnPC9zcGFuPic7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChjUHJvcGVydHkuZW51bSkge1xuICAgICAgICAgICAgICBodG1sICs9ICcgPSA8c3BhbiBjbGFzcz1cInByb3BWYWxzXCI+W1xcJycgKyBjUHJvcGVydHkuZW51bS5qb2luKCdcXCcsIFxcJycpICsgJ1xcJ108L3NwYW4+JztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuICc8ZGl2JyArIChwcm9wZXJ0eS5yZWFkT25seSA/ICcgY2xhc3M9XCJyZWFkT25seVwiJyA6ICcnKSArICc+JyArIHByaW1pdGl2ZVRvT3B0aW9uc0hUTUwoY1Byb3BlcnR5LCBodG1sKTtcbiAgICAgICAgICB9KS5qb2luKCcsPC9kaXY+Jyk7XG5cbiAgICAgICAgICBpZiAoY29udGVudHMpIHtcbiAgICAgICAgICAgIGh0bWwgKz0gY29udGVudHMgKyAnPC9kaXY+JztcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGh0bWwgKz0gJzxkaXY+JyArIHByaW1pdGl2ZVRvT3B0aW9uc0hUTUwoc2NoZW1hLCB0eXBlKSArICc8L2Rpdj4nO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBodG1sICsgc3Ryb25nT3BlbiArIChpc0FycmF5ID8gJ10nIDogJ30nKSArIHN0cm9uZ0Nsb3NlO1xuICB9XG59OyIsIid1c2Ugc3RyaWN0JztcblxudmFyIFN3YWdnZXJIdHRwID0gcmVxdWlyZSgnLi9odHRwJyk7XG52YXIgXyA9IHtcbiAgaXNPYmplY3Q6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9pc09iamVjdCcpXG59O1xuXG52YXIgU3dhZ2dlclNwZWNDb252ZXJ0ZXIgPSBtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uICgpIHtcbiAgdGhpcy5lcnJvcnMgPSBbXTtcbiAgdGhpcy53YXJuaW5ncyA9IFtdO1xuICB0aGlzLm1vZGVsTWFwID0ge307XG59O1xuXG5Td2FnZ2VyU3BlY0NvbnZlcnRlci5wcm90b3R5cGUuc2V0RG9jdW1lbnRhdGlvbkxvY2F0aW9uID0gZnVuY3Rpb24gKGxvY2F0aW9uKSB7XG4gIHRoaXMuZG9jTG9jYXRpb24gPSBsb2NhdGlvbjtcbn07XG5cbi8qKlxuICogY29udmVydHMgYSByZXNvdXJjZSBsaXN0aW5nIE9SIGFwaSBkZWNsYXJhdGlvblxuICoqL1xuU3dhZ2dlclNwZWNDb252ZXJ0ZXIucHJvdG90eXBlLmNvbnZlcnQgPSBmdW5jdGlvbiAob2JqLCBjbGllbnRBdXRob3JpemF0aW9ucywgb3B0cywgY2FsbGJhY2spIHtcbiAgLy8gbm90IGEgdmFsaWQgc3BlY1xuICBpZighb2JqIHx8ICFBcnJheS5pc0FycmF5KG9iai5hcGlzKSkge1xuICAgIHJldHVybiB0aGlzLmZpbmlzaChjYWxsYmFjaywgbnVsbCk7XG4gIH1cbiAgdGhpcy5jbGllbnRBdXRob3JpemF0aW9ucyA9IGNsaWVudEF1dGhvcml6YXRpb25zO1xuXG4gIC8vIGNyZWF0ZSBhIG5ldyBzd2FnZ2VyIG9iamVjdCB0byByZXR1cm5cbiAgdmFyIHN3YWdnZXIgPSB7IHN3YWdnZXI6ICcyLjAnIH07XG5cbiAgc3dhZ2dlci5vcmlnaW5hbFZlcnNpb24gPSBvYmouc3dhZ2dlclZlcnNpb247XG5cbiAgLy8gYWRkIHRoZSBpbmZvXG4gIHRoaXMuYXBpSW5mbyhvYmosIHN3YWdnZXIpO1xuXG4gIC8vIGFkZCBzZWN1cml0eSBkZWZpbml0aW9uc1xuICB0aGlzLnNlY3VyaXR5RGVmaW5pdGlvbnMob2JqLCBzd2FnZ2VyKTtcblxuICAvLyB0YWtlIGJhc2VQYXRoIGludG8gYWNjb3VudFxuICBpZiAob2JqLmJhc2VQYXRoKSB7XG4gICAgdGhpcy5zZXREb2N1bWVudGF0aW9uTG9jYXRpb24ob2JqLmJhc2VQYXRoKTtcbiAgfVxuXG4gIC8vIHNlZSBpZiB0aGlzIGlzIGEgc2luZ2xlLWZpbGUgc3dhZ2dlciBkZWZpbml0aW9uXG4gIHZhciBpc1NpbmdsZUZpbGVTd2FnZ2VyID0gZmFsc2U7XG4gIHZhciBpO1xuICBmb3IoaSA9IDA7IGkgPCBvYmouYXBpcy5sZW5ndGg7IGkrKykge1xuICAgIHZhciBhcGkgPSBvYmouYXBpc1tpXTtcbiAgICBpZihBcnJheS5pc0FycmF5KGFwaS5vcGVyYXRpb25zKSkge1xuICAgICAgaXNTaW5nbGVGaWxlU3dhZ2dlciA9IHRydWU7XG4gICAgfVxuICB9XG4gIGlmKGlzU2luZ2xlRmlsZVN3YWdnZXIpIHtcbiAgICB0aGlzLmRlY2xhcmF0aW9uKG9iaiwgc3dhZ2dlcik7XG4gICAgdGhpcy5maW5pc2goY2FsbGJhY2ssIHN3YWdnZXIpO1xuICB9XG4gIGVsc2Uge1xuICAgIHRoaXMucmVzb3VyY2VMaXN0aW5nKG9iaiwgc3dhZ2dlciwgb3B0cywgY2FsbGJhY2spO1xuICB9XG59O1xuXG5Td2FnZ2VyU3BlY0NvbnZlcnRlci5wcm90b3R5cGUuZGVjbGFyYXRpb24gPSBmdW5jdGlvbihvYmosIHN3YWdnZXIpIHtcbiAgdmFyIG5hbWUsIGksIHAsIHBvcztcbiAgaWYoIW9iai5hcGlzKSB7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgaWYgKG9iai5iYXNlUGF0aC5pbmRleE9mKCdodHRwOi8vJykgPT09IDApIHtcbiAgICBwID0gb2JqLmJhc2VQYXRoLnN1YnN0cmluZygnaHR0cDovLycubGVuZ3RoKTtcbiAgICBwb3MgPSBwLmluZGV4T2YoJy8nKTtcbiAgICBpZiAocG9zID4gMCkge1xuICAgICAgc3dhZ2dlci5ob3N0ID0gcC5zdWJzdHJpbmcoMCwgcG9zKTtcbiAgICAgIHN3YWdnZXIuYmFzZVBhdGggPSBwLnN1YnN0cmluZyhwb3MpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHN3YWdnZXIuaG9zdCA9IHA7XG4gICAgICBzd2FnZ2VyLmJhc2VQYXRoID0gJy8nO1xuICAgIH1cbiAgfSBlbHNlIGlmIChvYmouYmFzZVBhdGguaW5kZXhPZignaHR0cHM6Ly8nKSA9PT0gMCkge1xuICAgIHAgPSBvYmouYmFzZVBhdGguc3Vic3RyaW5nKCdodHRwczovLycubGVuZ3RoKTtcbiAgICBwb3MgPSBwLmluZGV4T2YoJy8nKTtcbiAgICBpZiAocG9zID4gMCkge1xuICAgICAgc3dhZ2dlci5ob3N0ID0gcC5zdWJzdHJpbmcoMCwgcG9zKTtcbiAgICAgIHN3YWdnZXIuYmFzZVBhdGggPSBwLnN1YnN0cmluZyhwb3MpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHN3YWdnZXIuaG9zdCA9IHA7XG4gICAgICBzd2FnZ2VyLmJhc2VQYXRoID0gJy8nO1xuICAgIH1cbiAgfSBlbHNlIHtcbiAgICBzd2FnZ2VyLmJhc2VQYXRoID0gb2JqLmJhc2VQYXRoO1xuICB9XG5cbiAgdmFyIHJlc291cmNlTGV2ZWxBdXRoO1xuICBpZihvYmouYXV0aG9yaXphdGlvbnMpIHtcbiAgICByZXNvdXJjZUxldmVsQXV0aCA9IG9iai5hdXRob3JpemF0aW9ucztcbiAgfVxuICBpZihvYmouY29uc3VtZXMpIHtcbiAgICBzd2FnZ2VyLmNvbnN1bWVzID0gb2JqLmNvbnN1bWVzO1xuICB9XG4gIGlmKG9iai5wcm9kdWNlcykge1xuICAgIHN3YWdnZXIucHJvZHVjZXMgPSBvYmoucHJvZHVjZXM7XG4gIH1cblxuICAvLyBidWlsZCBhIG1hcHBpbmcgb2YgaWQgdG8gbmFtZSBmb3IgMS4wIG1vZGVsIHJlc29sdXRpb25zXG4gIGlmKF8uaXNPYmplY3Qob2JqKSkge1xuICAgIGZvcihuYW1lIGluIG9iai5tb2RlbHMpIHtcbiAgICAgIHZhciBleGlzdGluZ01vZGVsID0gb2JqLm1vZGVsc1tuYW1lXTtcbiAgICAgIHZhciBrZXkgPSAoZXhpc3RpbmdNb2RlbC5pZCB8fCBuYW1lKTtcbiAgICAgIHRoaXMubW9kZWxNYXBba2V5XSA9IG5hbWU7XG4gICAgfVxuICB9XG5cbiAgZm9yKGkgPSAwOyBpIDwgb2JqLmFwaXMubGVuZ3RoOyBpKyspIHtcbiAgICB2YXIgYXBpID0gb2JqLmFwaXNbaV07XG4gICAgdmFyIHBhdGggPSBhcGkucGF0aDtcbiAgICB2YXIgb3BlcmF0aW9ucyA9IGFwaS5vcGVyYXRpb25zO1xuICAgIHRoaXMub3BlcmF0aW9ucyhwYXRoLCBvYmoucmVzb3VyY2VQYXRoLCBvcGVyYXRpb25zLCByZXNvdXJjZUxldmVsQXV0aCwgc3dhZ2dlcik7XG4gIH1cblxuICB2YXIgbW9kZWxzID0gb2JqLm1vZGVscyB8fCB7fTtcbiAgdGhpcy5tb2RlbHMobW9kZWxzLCBzd2FnZ2VyKTtcbn07XG5cblN3YWdnZXJTcGVjQ29udmVydGVyLnByb3RvdHlwZS5tb2RlbHMgPSBmdW5jdGlvbihvYmosIHN3YWdnZXIpIHtcbiAgaWYoIV8uaXNPYmplY3Qob2JqKSkge1xuICAgIHJldHVybjtcbiAgfVxuICB2YXIgbmFtZTtcblxuICBzd2FnZ2VyLmRlZmluaXRpb25zID0gc3dhZ2dlci5kZWZpbml0aW9ucyB8fCB7fTtcbiAgZm9yKG5hbWUgaW4gb2JqKSB7XG4gICAgdmFyIGV4aXN0aW5nTW9kZWwgPSBvYmpbbmFtZV07XG4gICAgdmFyIF9yZXF1aXJlZCA9IFtdO1xuICAgIHZhciBzY2hlbWEgPSB7IHByb3BlcnRpZXM6IHt9fTtcbiAgICB2YXIgcHJvcGVydHlOYW1lO1xuICAgIGZvcihwcm9wZXJ0eU5hbWUgaW4gZXhpc3RpbmdNb2RlbC5wcm9wZXJ0aWVzKSB7XG4gICAgICB2YXIgZXhpc3RpbmdQcm9wZXJ0eSA9IGV4aXN0aW5nTW9kZWwucHJvcGVydGllc1twcm9wZXJ0eU5hbWVdO1xuICAgICAgdmFyIHByb3BlcnR5ID0ge307XG4gICAgICB0aGlzLmRhdGFUeXBlKGV4aXN0aW5nUHJvcGVydHksIHByb3BlcnR5KTtcbiAgICAgIGlmKGV4aXN0aW5nUHJvcGVydHkuZGVzY3JpcHRpb24pIHtcbiAgICAgICAgcHJvcGVydHkuZGVzY3JpcHRpb24gPSBleGlzdGluZ1Byb3BlcnR5LmRlc2NyaXB0aW9uO1xuICAgICAgfVxuICAgICAgaWYoZXhpc3RpbmdQcm9wZXJ0eVsnZW51bSddKSB7XG4gICAgICAgIHByb3BlcnR5WydlbnVtJ10gPSBleGlzdGluZ1Byb3BlcnR5WydlbnVtJ107XG4gICAgICB9XG4gICAgICBpZih0eXBlb2YgZXhpc3RpbmdQcm9wZXJ0eS5yZXF1aXJlZCA9PT0gJ2Jvb2xlYW4nICYmIGV4aXN0aW5nUHJvcGVydHkucmVxdWlyZWQgPT09IHRydWUpIHtcbiAgICAgICAgX3JlcXVpcmVkLnB1c2gocHJvcGVydHlOYW1lKTtcbiAgICAgIH1cbiAgICAgIGlmKHR5cGVvZiBleGlzdGluZ1Byb3BlcnR5LnJlcXVpcmVkID09PSAnc3RyaW5nJyAmJiBleGlzdGluZ1Byb3BlcnR5LnJlcXVpcmVkID09PSAndHJ1ZScpIHtcbiAgICAgICAgX3JlcXVpcmVkLnB1c2gocHJvcGVydHlOYW1lKTtcbiAgICAgIH1cbiAgICAgIHNjaGVtYS5wcm9wZXJ0aWVzW3Byb3BlcnR5TmFtZV0gPSBwcm9wZXJ0eTtcbiAgICB9XG4gICAgaWYoX3JlcXVpcmVkLmxlbmd0aCA+IDApIHtcbiAgICAgIHNjaGVtYS5yZXF1aXJlZCA9IF9yZXF1aXJlZDtcbiAgICB9IGVsc2Uge1xuICAgICAgc2NoZW1hLnJlcXVpcmVkID0gZXhpc3RpbmdNb2RlbC5yZXF1aXJlZDtcbiAgICB9XG4gICAgc3dhZ2dlci5kZWZpbml0aW9uc1tuYW1lXSA9IHNjaGVtYTtcbiAgfVxufTtcblxuU3dhZ2dlclNwZWNDb252ZXJ0ZXIucHJvdG90eXBlLmV4dHJhY3RUYWcgPSBmdW5jdGlvbihyZXNvdXJjZVBhdGgpIHtcbiAgdmFyIHBhdGhTdHJpbmcgPSByZXNvdXJjZVBhdGggfHwgJ2RlZmF1bHQnO1xuICBpZihwYXRoU3RyaW5nLmluZGV4T2YoJ2h0dHA6JykgPT09IDAgfHwgcGF0aFN0cmluZy5pbmRleE9mKCdodHRwczonKSA9PT0gMCkge1xuICAgIHBhdGhTdHJpbmcgPSBwYXRoU3RyaW5nLnNwbGl0KFsnLyddKTtcbiAgICBwYXRoU3RyaW5nID0gcGF0aFN0cmluZ1twYXRoU3RyaW5nLmxlbmd0aCAtMV0uc3Vic3RyaW5nKCk7XG4gIH1cbiAgaWYocGF0aFN0cmluZy5lbmRzV2l0aCgnLmpzb24nKSkge1xuICAgIHBhdGhTdHJpbmcgPSBwYXRoU3RyaW5nLnN1YnN0cmluZygwLCBwYXRoU3RyaW5nLmxlbmd0aCAtICcuanNvbicubGVuZ3RoKTtcbiAgfVxuICByZXR1cm4gcGF0aFN0cmluZy5yZXBsYWNlKCcvJywnJyk7XG59O1xuXG5Td2FnZ2VyU3BlY0NvbnZlcnRlci5wcm90b3R5cGUub3BlcmF0aW9ucyA9IGZ1bmN0aW9uKHBhdGgsIHJlc291cmNlUGF0aCwgb2JqLCByZXNvdXJjZUxldmVsQXV0aCwgc3dhZ2dlcikge1xuICBpZighQXJyYXkuaXNBcnJheShvYmopKSB7XG4gICAgcmV0dXJuO1xuICB9XG4gIHZhciBpO1xuXG4gIGlmKCFzd2FnZ2VyLnBhdGhzKSB7XG4gICAgc3dhZ2dlci5wYXRocyA9IHt9O1xuICB9XG5cbiAgdmFyIHBhdGhPYmogPSBzd2FnZ2VyLnBhdGhzW3BhdGhdIHx8IHt9O1xuICB2YXIgdGFnID0gdGhpcy5leHRyYWN0VGFnKHJlc291cmNlUGF0aCk7XG4gIHN3YWdnZXIudGFncyA9IHN3YWdnZXIudGFncyB8fCBbXTtcbiAgdmFyIG1hdGNoZWQgPSBmYWxzZTtcbiAgZm9yKGkgPSAwOyBpIDwgc3dhZ2dlci50YWdzLmxlbmd0aDsgaSsrKSB7XG4gICAgdmFyIHRhZ09iamVjdCA9IHN3YWdnZXIudGFnc1tpXTtcbiAgICBpZih0YWdPYmplY3QubmFtZSA9PT0gdGFnKSB7XG4gICAgICBtYXRjaGVkID0gdHJ1ZTtcbiAgICB9XG4gIH1cbiAgaWYoIW1hdGNoZWQpIHtcbiAgICBzd2FnZ2VyLnRhZ3MucHVzaCh7bmFtZTogdGFnfSk7XG4gIH1cblxuICBmb3IoaSA9IDA7IGkgPCBvYmoubGVuZ3RoOyBpKyspIHtcbiAgICB2YXIgZXhpc3RpbmdPcGVyYXRpb24gPSBvYmpbaV07XG4gICAgdmFyIG1ldGhvZCA9IChleGlzdGluZ09wZXJhdGlvbi5tZXRob2QgfHwgZXhpc3RpbmdPcGVyYXRpb24uaHR0cE1ldGhvZCkudG9Mb3dlckNhc2UoKTtcbiAgICB2YXIgb3BlcmF0aW9uID0ge3RhZ3M6IFt0YWddfTtcbiAgICB2YXIgZXhpc3RpbmdBdXRob3JpemF0aW9ucyA9IGV4aXN0aW5nT3BlcmF0aW9uLmF1dGhvcml6YXRpb25zO1xuXG4gICAgaWYoZXhpc3RpbmdBdXRob3JpemF0aW9ucyAmJiBPYmplY3Qua2V5cyhleGlzdGluZ0F1dGhvcml6YXRpb25zKS5sZW5ndGggPT09IDApIHtcbiAgICAgIGV4aXN0aW5nQXV0aG9yaXphdGlvbnMgPSByZXNvdXJjZUxldmVsQXV0aDtcbiAgICB9XG5cbiAgICBpZih0eXBlb2YgZXhpc3RpbmdBdXRob3JpemF0aW9ucyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIHZhciBzY29wZXNPYmplY3Q7XG4gICAgICBmb3IodmFyIGtleSBpbiBleGlzdGluZ0F1dGhvcml6YXRpb25zKSB7XG4gICAgICAgIG9wZXJhdGlvbi5zZWN1cml0eSA9IG9wZXJhdGlvbi5zZWN1cml0eSB8fCBbXTtcbiAgICAgICAgdmFyIHNjb3BlcyA9IGV4aXN0aW5nQXV0aG9yaXphdGlvbnNba2V5XTtcbiAgICAgICAgaWYoc2NvcGVzKSB7XG4gICAgICAgICAgdmFyIHNlY3VyaXR5U2NvcGVzID0gW107XG4gICAgICAgICAgZm9yKHZhciBqIGluIHNjb3Blcykge1xuICAgICAgICAgICAgc2VjdXJpdHlTY29wZXMucHVzaChzY29wZXNbal0uc2NvcGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBzY29wZXNPYmplY3QgPSB7fTtcbiAgICAgICAgICBzY29wZXNPYmplY3Rba2V5XSA9IHNlY3VyaXR5U2NvcGVzO1xuICAgICAgICAgIG9wZXJhdGlvbi5zZWN1cml0eS5wdXNoKHNjb3Blc09iamVjdCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgc2NvcGVzT2JqZWN0ID0ge307XG4gICAgICAgICAgc2NvcGVzT2JqZWN0W2tleV0gPSBbXTtcbiAgICAgICAgICBvcGVyYXRpb24uc2VjdXJpdHkucHVzaChzY29wZXNPYmplY3QpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYoZXhpc3RpbmdPcGVyYXRpb24uY29uc3VtZXMpIHtcbiAgICAgIG9wZXJhdGlvbi5jb25zdW1lcyA9IGV4aXN0aW5nT3BlcmF0aW9uLmNvbnN1bWVzO1xuICAgIH1cbiAgICBlbHNlIGlmKHN3YWdnZXIuY29uc3VtZXMpIHtcbiAgICAgIG9wZXJhdGlvbi5jb25zdW1lcyA9IHN3YWdnZXIuY29uc3VtZXM7XG4gICAgfVxuICAgIGlmKGV4aXN0aW5nT3BlcmF0aW9uLnByb2R1Y2VzKSB7XG4gICAgICBvcGVyYXRpb24ucHJvZHVjZXMgPSBleGlzdGluZ09wZXJhdGlvbi5wcm9kdWNlcztcbiAgICB9XG4gICAgZWxzZSBpZihzd2FnZ2VyLnByb2R1Y2VzKSB7XG4gICAgICBvcGVyYXRpb24ucHJvZHVjZXMgPSBzd2FnZ2VyLnByb2R1Y2VzO1xuICAgIH1cbiAgICBpZihleGlzdGluZ09wZXJhdGlvbi5zdW1tYXJ5KSB7XG4gICAgICBvcGVyYXRpb24uc3VtbWFyeSA9IGV4aXN0aW5nT3BlcmF0aW9uLnN1bW1hcnk7XG4gICAgfVxuICAgIGlmKGV4aXN0aW5nT3BlcmF0aW9uLm5vdGVzKSB7XG4gICAgICBvcGVyYXRpb24uZGVzY3JpcHRpb24gPSBleGlzdGluZ09wZXJhdGlvbi5ub3RlcztcbiAgICB9XG4gICAgaWYoZXhpc3RpbmdPcGVyYXRpb24ubmlja25hbWUpIHtcbiAgICAgIG9wZXJhdGlvbi5vcGVyYXRpb25JZCA9IGV4aXN0aW5nT3BlcmF0aW9uLm5pY2tuYW1lO1xuICAgIH1cbiAgICBpZihleGlzdGluZ09wZXJhdGlvbi5kZXByZWNhdGVkKSB7XG4gICAgICBvcGVyYXRpb24uZGVwcmVjYXRlZCA9IGV4aXN0aW5nT3BlcmF0aW9uLmRlcHJlY2F0ZWQ7XG4gICAgfVxuXG4gICAgdGhpcy5hdXRob3JpemF0aW9ucyhleGlzdGluZ0F1dGhvcml6YXRpb25zLCBzd2FnZ2VyKTtcbiAgICB0aGlzLnBhcmFtZXRlcnMob3BlcmF0aW9uLCBleGlzdGluZ09wZXJhdGlvbi5wYXJhbWV0ZXJzLCBzd2FnZ2VyKTtcbiAgICB0aGlzLnJlc3BvbnNlTWVzc2FnZXMob3BlcmF0aW9uLCBleGlzdGluZ09wZXJhdGlvbiwgc3dhZ2dlcik7XG5cbiAgICBwYXRoT2JqW21ldGhvZF0gPSBvcGVyYXRpb247XG4gIH1cblxuICBzd2FnZ2VyLnBhdGhzW3BhdGhdID0gcGF0aE9iajtcbn07XG5cblN3YWdnZXJTcGVjQ29udmVydGVyLnByb3RvdHlwZS5yZXNwb25zZU1lc3NhZ2VzID0gZnVuY3Rpb24ob3BlcmF0aW9uLCBleGlzdGluZ09wZXJhdGlvbikge1xuICBpZighXy5pc09iamVjdChleGlzdGluZ09wZXJhdGlvbikpIHtcbiAgICByZXR1cm47XG4gIH1cbiAgLy8gYnVpbGQgZGVmYXVsdCByZXNwb25zZSBmcm9tIHRoZSBvcGVyYXRpb24gKDEueClcbiAgdmFyIGRlZmF1bHRSZXNwb25zZSA9IHt9O1xuICB0aGlzLmRhdGFUeXBlKGV4aXN0aW5nT3BlcmF0aW9uLCBkZWZhdWx0UmVzcG9uc2UpO1xuICAvLyBUT0RPOiBsb29rIGludG8gdGhlIHJlYWwgcHJvYmxlbSBvZiByZW5kZXJpbmcgcmVzcG9uc2VzIGluIHN3YWdnZXItdWlcbiAgLy8gLi4uLnNob3VsZCByZXBvbnNlVHlwZSBoYXZlIGFuIGltcGxpY2l0IHNjaGVtYT9cbiAgaWYoIWRlZmF1bHRSZXNwb25zZS5zY2hlbWEgJiYgZGVmYXVsdFJlc3BvbnNlLnR5cGUpIHtcbiAgICBkZWZhdWx0UmVzcG9uc2UgPSB7c2NoZW1hOiBkZWZhdWx0UmVzcG9uc2V9O1xuICB9XG5cbiAgb3BlcmF0aW9uLnJlc3BvbnNlcyA9IG9wZXJhdGlvbi5yZXNwb25zZXMgfHwge307XG5cbiAgLy8gZ3JhYiBmcm9tIHJlc3BvbnNlTWVzc2FnZXMgKDEuMilcbiAgdmFyIGhhczIwMCA9IGZhbHNlO1xuICBpZihBcnJheS5pc0FycmF5KGV4aXN0aW5nT3BlcmF0aW9uLnJlc3BvbnNlTWVzc2FnZXMpKSB7XG4gICAgdmFyIGk7XG4gICAgdmFyIGV4aXN0aW5nUmVzcG9uc2VzID0gZXhpc3RpbmdPcGVyYXRpb24ucmVzcG9uc2VNZXNzYWdlcztcbiAgICBmb3IoaSA9IDA7IGkgPCBleGlzdGluZ1Jlc3BvbnNlcy5sZW5ndGg7IGkrKykge1xuICAgICAgdmFyIGV4aXN0aW5nUmVzcG9uc2UgPSBleGlzdGluZ1Jlc3BvbnNlc1tpXTtcbiAgICAgIHZhciByZXNwb25zZSA9IHsgZGVzY3JpcHRpb246IGV4aXN0aW5nUmVzcG9uc2UubWVzc2FnZSB9O1xuICAgICAgaWYoZXhpc3RpbmdSZXNwb25zZS5jb2RlID09PSAyMDApIHtcbiAgICAgICAgaGFzMjAwID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIC8vIENvbnZlcnQgcmVzcG9uc2VNb2RlbCAtPiBzY2hlbWF7JHJlZjogcmVzcG9uc2VNb2RlbH1cbiAgICAgIGlmKGV4aXN0aW5nUmVzcG9uc2UucmVzcG9uc2VNb2RlbCkge1xuICAgICAgICByZXNwb25zZS5zY2hlbWEgPSB7JyRyZWYnOiAnIy9kZWZpbml0aW9ucy8nICsgZXhpc3RpbmdSZXNwb25zZS5yZXNwb25zZU1vZGVsfTtcbiAgICAgIH1cbiAgICAgIG9wZXJhdGlvbi5yZXNwb25zZXNbJycgKyBleGlzdGluZ1Jlc3BvbnNlLmNvZGVdID0gcmVzcG9uc2U7XG4gICAgfVxuICB9XG5cbiAgaWYoaGFzMjAwKSB7XG4gICAgb3BlcmF0aW9uLnJlc3BvbnNlc1snZGVmYXVsdCddID0gZGVmYXVsdFJlc3BvbnNlO1xuICB9XG4gIGVsc2Uge1xuICAgIG9wZXJhdGlvbi5yZXNwb25zZXNbJzIwMCddID0gZGVmYXVsdFJlc3BvbnNlO1xuICB9XG59O1xuXG5Td2FnZ2VyU3BlY0NvbnZlcnRlci5wcm90b3R5cGUuYXV0aG9yaXphdGlvbnMgPSBmdW5jdGlvbihvYmopIHtcbiAgLy8gVE9ET1xuICBpZighXy5pc09iamVjdChvYmopKSB7XG4gICAgcmV0dXJuO1xuICB9XG59O1xuXG5Td2FnZ2VyU3BlY0NvbnZlcnRlci5wcm90b3R5cGUucGFyYW1ldGVycyA9IGZ1bmN0aW9uKG9wZXJhdGlvbiwgb2JqKSB7XG4gIGlmKCFBcnJheS5pc0FycmF5KG9iaikpIHtcbiAgICByZXR1cm47XG4gIH1cbiAgdmFyIGk7XG4gIGZvcihpID0gMDsgaSA8IG9iai5sZW5ndGg7IGkrKykge1xuICAgIHZhciBleGlzdGluZ1BhcmFtZXRlciA9IG9ialtpXTtcbiAgICB2YXIgcGFyYW1ldGVyID0ge307XG4gICAgcGFyYW1ldGVyLm5hbWUgPSBleGlzdGluZ1BhcmFtZXRlci5uYW1lO1xuICAgIHBhcmFtZXRlci5kZXNjcmlwdGlvbiA9IGV4aXN0aW5nUGFyYW1ldGVyLmRlc2NyaXB0aW9uO1xuICAgIHBhcmFtZXRlci5yZXF1aXJlZCA9IGV4aXN0aW5nUGFyYW1ldGVyLnJlcXVpcmVkO1xuICAgIHBhcmFtZXRlci5pbiA9IGV4aXN0aW5nUGFyYW1ldGVyLnBhcmFtVHlwZTtcblxuICAgIC8vIHBlciAjMTY4XG4gICAgaWYocGFyYW1ldGVyLmluID09PSAnYm9keScpIHtcbiAgICAgIHBhcmFtZXRlci5uYW1lID0gJ2JvZHknO1xuICAgIH1cbiAgICBpZihwYXJhbWV0ZXIuaW4gPT09ICdmb3JtJykge1xuICAgICAgcGFyYW1ldGVyLmluID0gJ2Zvcm1EYXRhJztcbiAgICB9XG5cbiAgICBpZihleGlzdGluZ1BhcmFtZXRlci5lbnVtKSB7XG4gICAgICBwYXJhbWV0ZXIuZW51bSA9IGV4aXN0aW5nUGFyYW1ldGVyLmVudW07XG4gICAgfVxuXG4gICAgaWYoZXhpc3RpbmdQYXJhbWV0ZXIuYWxsb3dNdWx0aXBsZSA9PT0gdHJ1ZSB8fCBleGlzdGluZ1BhcmFtZXRlci5hbGxvd011bHRpcGxlID09PSAndHJ1ZScpIHtcbiAgICAgIHZhciBpbm5lclR5cGUgPSB7fTtcbiAgICAgIHRoaXMuZGF0YVR5cGUoZXhpc3RpbmdQYXJhbWV0ZXIsIGlubmVyVHlwZSk7XG4gICAgICBwYXJhbWV0ZXIudHlwZSA9ICdhcnJheSc7XG4gICAgICBwYXJhbWV0ZXIuaXRlbXMgPSBpbm5lclR5cGU7XG5cbiAgICAgIGlmKGV4aXN0aW5nUGFyYW1ldGVyLmFsbG93YWJsZVZhbHVlcykge1xuICAgICAgICB2YXIgYXYgPSBleGlzdGluZ1BhcmFtZXRlci5hbGxvd2FibGVWYWx1ZXM7XG4gICAgICAgIGlmKGF2LnZhbHVlVHlwZSA9PT0gJ0xJU1QnKSB7XG4gICAgICAgICAgcGFyYW1ldGVyWydlbnVtJ10gPSBhdi52YWx1ZXM7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICB0aGlzLmRhdGFUeXBlKGV4aXN0aW5nUGFyYW1ldGVyLCBwYXJhbWV0ZXIpO1xuICAgIH1cbiAgICBpZih0eXBlb2YgZXhpc3RpbmdQYXJhbWV0ZXIuZGVmYXVsdFZhbHVlICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgcGFyYW1ldGVyLmRlZmF1bHQgPSBleGlzdGluZ1BhcmFtZXRlci5kZWZhdWx0VmFsdWU7XG4gICAgfVxuXG4gICAgb3BlcmF0aW9uLnBhcmFtZXRlcnMgPSBvcGVyYXRpb24ucGFyYW1ldGVycyB8fCBbXTtcbiAgICBvcGVyYXRpb24ucGFyYW1ldGVycy5wdXNoKHBhcmFtZXRlcik7XG4gIH1cbn07XG5cblN3YWdnZXJTcGVjQ29udmVydGVyLnByb3RvdHlwZS5kYXRhVHlwZSA9IGZ1bmN0aW9uKHNvdXJjZSwgdGFyZ2V0KSB7XG4gIGlmKCFfLmlzT2JqZWN0KHNvdXJjZSkpIHtcbiAgICByZXR1cm47XG4gIH1cblxuICBpZihzb3VyY2UubWluaW11bSkge1xuICAgIHRhcmdldC5taW5pbXVtID0gc291cmNlLm1pbmltdW07XG4gIH1cbiAgaWYoc291cmNlLm1heGltdW0pIHtcbiAgICB0YXJnZXQubWF4aW11bSA9IHNvdXJjZS5tYXhpbXVtO1xuICB9XG4gIGlmIChzb3VyY2UuZm9ybWF0KSB7XG4gICAgdGFyZ2V0LmZvcm1hdCA9IHNvdXJjZS5mb3JtYXQ7XG4gIH1cblxuICAvLyBkZWZhdWx0IGNhbiBiZSAnZmFsc2UnXG4gIGlmKHR5cGVvZiBzb3VyY2UuZGVmYXVsdFZhbHVlICE9PSAndW5kZWZpbmVkJykge1xuICAgIHRhcmdldC5kZWZhdWx0ID0gc291cmNlLmRlZmF1bHRWYWx1ZTtcbiAgfVxuXG4gIHZhciBqc29uU2NoZW1hVHlwZSA9IHRoaXMudG9Kc29uU2NoZW1hKHNvdXJjZSk7XG4gIGlmKGpzb25TY2hlbWFUeXBlKSB7XG4gICAgdGFyZ2V0ID0gdGFyZ2V0IHx8IHt9O1xuICAgIGlmKGpzb25TY2hlbWFUeXBlLnR5cGUpIHtcbiAgICAgIHRhcmdldC50eXBlID0ganNvblNjaGVtYVR5cGUudHlwZTtcbiAgICB9XG4gICAgaWYoanNvblNjaGVtYVR5cGUuZm9ybWF0KSB7XG4gICAgICB0YXJnZXQuZm9ybWF0ID0ganNvblNjaGVtYVR5cGUuZm9ybWF0O1xuICAgIH1cbiAgICBpZihqc29uU2NoZW1hVHlwZS4kcmVmKSB7XG4gICAgICB0YXJnZXQuc2NoZW1hID0geyRyZWY6IGpzb25TY2hlbWFUeXBlLiRyZWZ9O1xuICAgIH1cbiAgICBpZihqc29uU2NoZW1hVHlwZS5pdGVtcykge1xuICAgICAgdGFyZ2V0Lml0ZW1zID0ganNvblNjaGVtYVR5cGUuaXRlbXM7XG4gICAgfVxuICB9XG59O1xuXG5Td2FnZ2VyU3BlY0NvbnZlcnRlci5wcm90b3R5cGUudG9Kc29uU2NoZW1hID0gZnVuY3Rpb24oc291cmNlKSB7XG4gIGlmKCFzb3VyY2UpIHtcbiAgICByZXR1cm4gJ29iamVjdCc7XG4gIH1cbiAgdmFyIGRldGVjdGVkVHlwZSA9IChzb3VyY2UudHlwZSB8fCBzb3VyY2UuZGF0YVR5cGUgfHwgc291cmNlLnJlc3BvbnNlQ2xhc3MgfHwgJycpO1xuICB2YXIgbGNUeXBlID0gZGV0ZWN0ZWRUeXBlLnRvTG93ZXJDYXNlKCk7XG4gIHZhciBmb3JtYXQgPSAoc291cmNlLmZvcm1hdCB8fCAnJykudG9Mb3dlckNhc2UoKTtcblxuICBpZihsY1R5cGUuaW5kZXhPZignbGlzdFsnKSA9PT0gMCkge1xuICAgIHZhciBpbm5lclR5cGUgPSBkZXRlY3RlZFR5cGUuc3Vic3RyaW5nKDUsIGRldGVjdGVkVHlwZS5sZW5ndGggLSAxKTtcbiAgICB2YXIganNvblR5cGUgPSB0aGlzLnRvSnNvblNjaGVtYSh7dHlwZTogaW5uZXJUeXBlfSk7XG4gICAgcmV0dXJuIHt0eXBlOiAnYXJyYXknLCBpdGVtczoganNvblR5cGV9O1xuICB9IGVsc2UgaWYobGNUeXBlID09PSAnaW50JyB8fCAobGNUeXBlID09PSAnaW50ZWdlcicgJiYgZm9ybWF0ID09PSAnaW50MzInKSkge1xuICAgIHtyZXR1cm4ge3R5cGU6ICdpbnRlZ2VyJywgZm9ybWF0OiAnaW50MzInfTt9XG4gIH0gZWxzZSBpZihsY1R5cGUgPT09ICdsb25nJyB8fCAobGNUeXBlID09PSAnaW50ZWdlcicgJiYgZm9ybWF0ID09PSAnaW50NjQnKSkge1xuICAgIHtyZXR1cm4ge3R5cGU6ICdpbnRlZ2VyJywgZm9ybWF0OiAnaW50NjQnfTt9XG4gIH0gZWxzZSBpZihsY1R5cGUgPT09ICdpbnRlZ2VyJykge1xuICAgIHtyZXR1cm4ge3R5cGU6ICdpbnRlZ2VyJywgZm9ybWF0OiAnaW50NjQnfTt9XG4gIH0gZWxzZSBpZihsY1R5cGUgPT09ICdmbG9hdCcgfHwgKGxjVHlwZSA9PT0gJ251bWJlcicgJiYgZm9ybWF0ID09PSAnZmxvYXQnKSkge1xuICAgIHtyZXR1cm4ge3R5cGU6ICdudW1iZXInLCBmb3JtYXQ6ICdmbG9hdCd9O31cbiAgfSBlbHNlIGlmKGxjVHlwZSA9PT0gJ2RvdWJsZScgfHwgKGxjVHlwZSA9PT0gJ251bWJlcicgJiYgZm9ybWF0ID09PSAnZG91YmxlJykpIHtcbiAgICB7cmV0dXJuIHt0eXBlOiAnbnVtYmVyJywgZm9ybWF0OiAnZG91YmxlJ307fVxuICB9IGVsc2UgaWYoKGxjVHlwZSA9PT0gJ3N0cmluZycgJiYgZm9ybWF0ID09PSAnZGF0ZS10aW1lJykgfHwgKGxjVHlwZSA9PT0gJ2RhdGUnKSkge1xuICAgIHtyZXR1cm4ge3R5cGU6ICdzdHJpbmcnLCBmb3JtYXQ6ICdkYXRlLXRpbWUnfTt9XG4gIH0gZWxzZSBpZihsY1R5cGUgPT09ICdzdHJpbmcnKSB7XG4gICAge3JldHVybiB7dHlwZTogJ3N0cmluZyd9O31cbiAgfSBlbHNlIGlmKGxjVHlwZSA9PT0gJ2ZpbGUnKSB7XG4gICAge3JldHVybiB7dHlwZTogJ2ZpbGUnfTt9XG4gIH0gZWxzZSBpZihsY1R5cGUgPT09ICdib29sZWFuJykge1xuICAgIHtyZXR1cm4ge3R5cGU6ICdib29sZWFuJ307fVxuICB9IGVsc2UgaWYobGNUeXBlID09PSAnYm9vbGVhbicpIHtcbiAgICB7cmV0dXJuIHt0eXBlOiAnYm9vbGVhbid9O31cbiAgfSBlbHNlIGlmKGxjVHlwZSA9PT0gJ2FycmF5JyB8fCBsY1R5cGUgPT09ICdsaXN0Jykge1xuICAgIGlmKHNvdXJjZS5pdGVtcykge1xuICAgICAgdmFyIGl0ID0gdGhpcy50b0pzb25TY2hlbWEoc291cmNlLml0ZW1zKTtcbiAgICAgIHJldHVybiB7dHlwZTogJ2FycmF5JywgaXRlbXM6IGl0fTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICByZXR1cm4ge3R5cGU6ICdhcnJheScsIGl0ZW1zOiB7dHlwZTogJ29iamVjdCd9fTtcbiAgICB9XG4gIH0gZWxzZSBpZihzb3VyY2UuJHJlZikge1xuICAgIHJldHVybiB7JHJlZjogdGhpcy5tb2RlbE1hcFtzb3VyY2UuJHJlZl0gPyAnIy9kZWZpbml0aW9ucy8nICsgdGhpcy5tb2RlbE1hcFtzb3VyY2UuJHJlZl0gOiBzb3VyY2UuJHJlZn07XG4gIH0gZWxzZSBpZihsY1R5cGUgPT09ICd2b2lkJyB8fCBsY1R5cGUgPT09ICcnKSB7XG4gICAge3JldHVybiB7fTt9XG4gIH0gZWxzZSBpZiAodGhpcy5tb2RlbE1hcFtzb3VyY2UudHlwZV0pIHtcbiAgICAvLyBJZiB0aGlzIGEgbW9kZWwgdXNpbmcgYHR5cGVgIGluc3RlYWQgb2YgYCRyZWZgLCB0aGF0J3MgZmluZS5cbiAgICByZXR1cm4geyRyZWY6ICcjL2RlZmluaXRpb25zLycgKyB0aGlzLm1vZGVsTWFwW3NvdXJjZS50eXBlXX07XG4gIH0gZWxzZSB7XG4gICAgLy8gVW5rbm93biBtb2RlbCB0eXBlIG9yICdvYmplY3QnLCBwYXNzIGl0IGFsb25nLlxuICAgIHJldHVybiB7dHlwZTogc291cmNlLnR5cGV9O1xuICB9XG59O1xuXG5Td2FnZ2VyU3BlY0NvbnZlcnRlci5wcm90b3R5cGUucmVzb3VyY2VMaXN0aW5nID0gZnVuY3Rpb24ob2JqLCBzd2FnZ2VyLCBvcHRzLCBjYWxsYmFjaykge1xuICB2YXIgaTtcbiAgdmFyIHByb2Nlc3NlZENvdW50ID0gMDsgICAvLyBqc2hpbnQgaWdub3JlOmxpbmVcbiAgdmFyIHNlbGYgPSB0aGlzOyAgICAgICAgICAvLyBqc2hpbnQgaWdub3JlOmxpbmVcbiAgdmFyIGV4cGVjdGVkQ291bnQgPSBvYmouYXBpcy5sZW5ndGg7XG4gIHZhciBfc3dhZ2dlciA9IHN3YWdnZXI7ICAgLy8ganNoaW50IGlnbm9yZTpsaW5lXG4gIHZhciBfb3B0cyA9IHt9O1xuXG4gIGlmKG9wdHMgJiYgb3B0cy5yZXF1ZXN0SW50ZXJjZXB0b3Ipe1xuICAgIF9vcHRzLnJlcXVlc3RJbnRlcmNlcHRvciA9IG9wdHMucmVxdWVzdEludGVyY2VwdG9yO1xuICB9XG5cbiAgaWYob3B0cyAmJiBvcHRzLnJlc3BvbnNlSW50ZXJjZXB0b3Ipe1xuICAgIF9vcHRzLnJlc3BvbnNlSW50ZXJjZXB0b3IgPSBvcHRzLnJlc3BvbnNlSW50ZXJjZXB0b3I7XG4gIH1cblxuICB2YXIgc3dhZ2dlclJlcXVlc3RIZWFkZXJzID0gJ2FwcGxpY2F0aW9uL2pzb24nO1xuXG4gIGlmKG9wdHMgJiYgb3B0cy5zd2FnZ2VyUmVxdWVzdEhlYWRlcnMpIHtcbiAgICBzd2FnZ2VyUmVxdWVzdEhlYWRlcnMgPSBvcHRzLnN3YWdnZXJSZXF1ZXN0SGVhZGVycztcbiAgfVxuXG4gIGlmKGV4cGVjdGVkQ291bnQgPT09IDApIHtcbiAgICB0aGlzLmZpbmlzaChjYWxsYmFjaywgc3dhZ2dlcik7XG4gIH1cblxuICBmb3IoaSA9IDA7IGkgPCBleHBlY3RlZENvdW50OyBpKyspIHtcbiAgICB2YXIgYXBpID0gb2JqLmFwaXNbaV07XG4gICAgdmFyIHBhdGggPSBhcGkucGF0aDtcbiAgICB2YXIgYWJzb2x1dGVQYXRoID0gdGhpcy5nZXRBYnNvbHV0ZVBhdGgob2JqLnN3YWdnZXJWZXJzaW9uLCB0aGlzLmRvY0xvY2F0aW9uLCBwYXRoKTtcblxuICAgIGlmKGFwaS5kZXNjcmlwdGlvbikge1xuICAgICAgc3dhZ2dlci50YWdzID0gc3dhZ2dlci50YWdzIHx8IFtdO1xuICAgICAgc3dhZ2dlci50YWdzLnB1c2goe1xuICAgICAgICBuYW1lIDogdGhpcy5leHRyYWN0VGFnKGFwaS5wYXRoKSxcbiAgICAgICAgZGVzY3JpcHRpb24gOiBhcGkuZGVzY3JpcHRpb24gfHwgJydcbiAgICAgIH0pO1xuICAgIH1cbiAgICB2YXIgaHR0cCA9IHtcbiAgICAgIHVybDogYWJzb2x1dGVQYXRoLFxuICAgICAgaGVhZGVyczogeyBhY2NlcHQ6IHN3YWdnZXJSZXF1ZXN0SGVhZGVycyB9LFxuICAgICAgb246IHt9LFxuICAgICAgbWV0aG9kOiAnZ2V0JyxcbiAgICAgIHRpbWVvdXQ6IG9wdHMudGltZW91dFxuICAgIH07XG4gICAgLyoganNoaW50IGlnbm9yZTpzdGFydCAqL1xuICAgIGh0dHAub24ucmVzcG9uc2UgPSBmdW5jdGlvbihkYXRhKSB7XG4gICAgICBwcm9jZXNzZWRDb3VudCArPSAxO1xuICAgICAgdmFyIG9iaiA9IGRhdGEub2JqO1xuICAgICAgaWYob2JqKSB7XG4gICAgICAgIHNlbGYuZGVjbGFyYXRpb24ob2JqLCBfc3dhZ2dlcik7XG4gICAgICB9XG4gICAgICBpZihwcm9jZXNzZWRDb3VudCA9PT0gZXhwZWN0ZWRDb3VudCkge1xuICAgICAgICBzZWxmLmZpbmlzaChjYWxsYmFjaywgX3N3YWdnZXIpO1xuICAgICAgfVxuICAgIH07XG4gICAgaHR0cC5vbi5lcnJvciA9IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoZGF0YSk7XG4gICAgICBwcm9jZXNzZWRDb3VudCArPSAxO1xuICAgICAgaWYocHJvY2Vzc2VkQ291bnQgPT09IGV4cGVjdGVkQ291bnQpIHtcbiAgICAgICAgc2VsZi5maW5pc2goY2FsbGJhY2ssIF9zd2FnZ2VyKTtcbiAgICAgIH1cbiAgICB9O1xuICAgIC8qIGpzaGludCBpZ25vcmU6ZW5kICovXG5cbiAgICBpZih0aGlzLmNsaWVudEF1dGhvcml6YXRpb25zICYmIHR5cGVvZiB0aGlzLmNsaWVudEF1dGhvcml6YXRpb25zLmFwcGx5ID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICB0aGlzLmNsaWVudEF1dGhvcml6YXRpb25zLmFwcGx5KGh0dHApO1xuICAgIH1cblxuICAgIG5ldyBTd2FnZ2VySHR0cCgpLmV4ZWN1dGUoaHR0cCwgX29wdHMpO1xuICB9XG59O1xuXG5Td2FnZ2VyU3BlY0NvbnZlcnRlci5wcm90b3R5cGUuZ2V0QWJzb2x1dGVQYXRoID0gZnVuY3Rpb24odmVyc2lvbiwgZG9jTG9jYXRpb24sIHBhdGgpICB7XG4gIGlmKHZlcnNpb24gPT09ICcxLjAnKSB7XG4gICAgaWYoZG9jTG9jYXRpb24uZW5kc1dpdGgoJy5qc29uJykpIHtcbiAgICAgIC8vIGdldCByb290IHBhdGhcbiAgICAgIHZhciBwb3MgPSBkb2NMb2NhdGlvbi5sYXN0SW5kZXhPZignLycpO1xuICAgICAgaWYocG9zID4gMCkge1xuICAgICAgICBkb2NMb2NhdGlvbiA9IGRvY0xvY2F0aW9uLnN1YnN0cmluZygwLCBwb3MpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHZhciBsb2NhdGlvbiA9IGRvY0xvY2F0aW9uO1xuICBpZihwYXRoLmluZGV4T2YoJ2h0dHA6JykgPT09IDAgfHwgcGF0aC5pbmRleE9mKCdodHRwczonKSA9PT0gMCkge1xuICAgIGxvY2F0aW9uID0gcGF0aDtcbiAgfVxuICBlbHNlIHtcbiAgICBpZihkb2NMb2NhdGlvbi5lbmRzV2l0aCgnLycpKSB7XG4gICAgICBsb2NhdGlvbiA9IGRvY0xvY2F0aW9uLnN1YnN0cmluZygwLCBkb2NMb2NhdGlvbi5sZW5ndGggLSAxKTtcbiAgICB9XG4gICAgbG9jYXRpb24gKz0gcGF0aDtcbiAgfVxuICBsb2NhdGlvbiA9IGxvY2F0aW9uLnJlcGxhY2UoJ3tmb3JtYXR9JywgJ2pzb24nKTtcbiAgcmV0dXJuIGxvY2F0aW9uO1xufTtcblxuU3dhZ2dlclNwZWNDb252ZXJ0ZXIucHJvdG90eXBlLnNlY3VyaXR5RGVmaW5pdGlvbnMgPSBmdW5jdGlvbihvYmosIHN3YWdnZXIpIHtcbiAgaWYob2JqLmF1dGhvcml6YXRpb25zKSB7XG4gICAgdmFyIG5hbWU7XG4gICAgZm9yKG5hbWUgaW4gb2JqLmF1dGhvcml6YXRpb25zKSB7XG4gICAgICB2YXIgaXNWYWxpZCA9IGZhbHNlO1xuICAgICAgdmFyIHNlY3VyaXR5RGVmaW5pdGlvbiA9IHtcbiAgICAgICAgdmVuZG9yRXh0ZW5zaW9uczoge31cbiAgICAgIH07XG4gICAgICB2YXIgZGVmaW5pdGlvbiA9IG9iai5hdXRob3JpemF0aW9uc1tuYW1lXTtcbiAgICAgIGlmKGRlZmluaXRpb24udHlwZSA9PT0gJ2FwaUtleScpIHtcbiAgICAgICAgc2VjdXJpdHlEZWZpbml0aW9uLnR5cGUgPSAnYXBpS2V5JztcbiAgICAgICAgc2VjdXJpdHlEZWZpbml0aW9uLmluID0gZGVmaW5pdGlvbi5wYXNzQXM7XG4gICAgICAgIHNlY3VyaXR5RGVmaW5pdGlvbi5uYW1lID0gZGVmaW5pdGlvbi5rZXluYW1lIHx8IG5hbWU7XG4gICAgICAgIGlzVmFsaWQgPSB0cnVlO1xuICAgICAgfVxuICAgICAgZWxzZSBpZihkZWZpbml0aW9uLnR5cGUgPT09ICdiYXNpY0F1dGgnKSB7XG4gICAgICAgIHNlY3VyaXR5RGVmaW5pdGlvbi50eXBlID0gJ2Jhc2ljQXV0aCc7XG4gICAgICAgIGlzVmFsaWQgPSB0cnVlO1xuICAgICAgfVxuICAgICAgZWxzZSBpZihkZWZpbml0aW9uLnR5cGUgPT09ICdvYXV0aDInKSB7XG4gICAgICAgIHZhciBleGlzdGluZ1Njb3BlcyA9IGRlZmluaXRpb24uc2NvcGVzIHx8IFtdO1xuICAgICAgICB2YXIgc2NvcGVzID0ge307XG4gICAgICAgIHZhciBpO1xuICAgICAgICBmb3IoaSBpbiBleGlzdGluZ1Njb3Blcykge1xuICAgICAgICAgIHZhciBzY29wZSA9IGV4aXN0aW5nU2NvcGVzW2ldO1xuICAgICAgICAgIHNjb3Blc1tzY29wZS5zY29wZV0gPSBzY29wZS5kZXNjcmlwdGlvbjtcbiAgICAgICAgfVxuICAgICAgICBzZWN1cml0eURlZmluaXRpb24udHlwZSA9ICdvYXV0aDInO1xuICAgICAgICBpZihpID4gMCkge1xuICAgICAgICAgIHNlY3VyaXR5RGVmaW5pdGlvbi5zY29wZXMgPSBzY29wZXM7XG4gICAgICAgIH1cbiAgICAgICAgaWYoZGVmaW5pdGlvbi5ncmFudFR5cGVzKSB7XG4gICAgICAgICAgaWYoZGVmaW5pdGlvbi5ncmFudFR5cGVzLmltcGxpY2l0KSB7XG4gICAgICAgICAgICB2YXIgaW1wbGljaXQgPSBkZWZpbml0aW9uLmdyYW50VHlwZXMuaW1wbGljaXQ7XG4gICAgICAgICAgICBzZWN1cml0eURlZmluaXRpb24uZmxvdyA9ICdpbXBsaWNpdCc7XG4gICAgICAgICAgICBzZWN1cml0eURlZmluaXRpb24uYXV0aG9yaXphdGlvblVybCA9IGltcGxpY2l0LmxvZ2luRW5kcG9pbnQ7XG4gICAgICAgICAgICBpc1ZhbGlkID0gdHJ1ZTtcbiAgICAgICAgICB9XG4gICAgICAgICAgLyoganNoaW50IGlnbm9yZTpzdGFydCAqL1xuICAgICAgICAgIGlmKGRlZmluaXRpb24uZ3JhbnRUeXBlc1snYXV0aG9yaXphdGlvbl9jb2RlJ10pIHtcbiAgICAgICAgICAgIGlmKCFzZWN1cml0eURlZmluaXRpb24uZmxvdykge1xuICAgICAgICAgICAgICAvLyBjYW5ub3Qgc2V0IGlmIGZsb3cgaXMgYWxyZWFkeSBkZWZpbmVkXG4gICAgICAgICAgICAgIHZhciBhdXRoQ29kZSA9IGRlZmluaXRpb24uZ3JhbnRUeXBlc1snYXV0aG9yaXphdGlvbl9jb2RlJ107XG4gICAgICAgICAgICAgIHNlY3VyaXR5RGVmaW5pdGlvbi5mbG93ID0gJ2FjY2Vzc0NvZGUnO1xuICAgICAgICAgICAgICBzZWN1cml0eURlZmluaXRpb24uYXV0aG9yaXphdGlvblVybCA9IGF1dGhDb2RlLnRva2VuUmVxdWVzdEVuZHBvaW50LnVybDtcbiAgICAgICAgICAgICAgc2VjdXJpdHlEZWZpbml0aW9uLnRva2VuVXJsID0gYXV0aENvZGUudG9rZW5FbmRwb2ludC51cmw7XG4gICAgICAgICAgICAgIGlzVmFsaWQgPSB0cnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICAvKiBqc2hpbnQgaWdub3JlOmVuZCAqL1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZihpc1ZhbGlkKSB7XG4gICAgICAgIHN3YWdnZXIuc2VjdXJpdHlEZWZpbml0aW9ucyA9IHN3YWdnZXIuc2VjdXJpdHlEZWZpbml0aW9ucyB8fCB7fTtcbiAgICAgICAgc3dhZ2dlci5zZWN1cml0eURlZmluaXRpb25zW25hbWVdID0gc2VjdXJpdHlEZWZpbml0aW9uO1xuICAgICAgfVxuICAgIH1cbiAgfVxufTtcblxuU3dhZ2dlclNwZWNDb252ZXJ0ZXIucHJvdG90eXBlLmFwaUluZm8gPSBmdW5jdGlvbihvYmosIHN3YWdnZXIpIHtcbiAgLy8gaW5mbyBzZWN0aW9uXG4gIGlmKG9iai5pbmZvKSB7XG4gICAgdmFyIGluZm8gPSBvYmouaW5mbztcbiAgICBzd2FnZ2VyLmluZm8gPSB7fTtcblxuICAgIGlmKGluZm8uY29udGFjdCkge1xuICAgICAgc3dhZ2dlci5pbmZvLmNvbnRhY3QgPSB7fTtcbiAgICAgIHN3YWdnZXIuaW5mby5jb250YWN0LmVtYWlsID0gaW5mby5jb250YWN0O1xuICAgIH1cbiAgICBpZihpbmZvLmRlc2NyaXB0aW9uKSB7XG4gICAgICBzd2FnZ2VyLmluZm8uZGVzY3JpcHRpb24gPSBpbmZvLmRlc2NyaXB0aW9uO1xuICAgIH1cbiAgICBpZihpbmZvLnRpdGxlKSB7XG4gICAgICBzd2FnZ2VyLmluZm8udGl0bGUgPSBpbmZvLnRpdGxlO1xuICAgIH1cbiAgICBpZihpbmZvLnRlcm1zT2ZTZXJ2aWNlVXJsKSB7XG4gICAgICBzd2FnZ2VyLmluZm8udGVybXNPZlNlcnZpY2UgPSBpbmZvLnRlcm1zT2ZTZXJ2aWNlVXJsO1xuICAgIH1cbiAgICBpZihpbmZvLmxpY2Vuc2UgfHwgaW5mby5saWNlbnNlVXJsKSB7XG4gICAgICBzd2FnZ2VyLmxpY2Vuc2UgPSB7fTtcbiAgICAgIGlmKGluZm8ubGljZW5zZSkge1xuICAgICAgICBzd2FnZ2VyLmxpY2Vuc2UubmFtZSA9IGluZm8ubGljZW5zZTtcbiAgICAgIH1cbiAgICAgIGlmKGluZm8ubGljZW5zZVVybCkge1xuICAgICAgICBzd2FnZ2VyLmxpY2Vuc2UudXJsID0gaW5mby5saWNlbnNlVXJsO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBlbHNlIHtcbiAgICB0aGlzLndhcm5pbmdzLnB1c2goJ21pc3NpbmcgaW5mbyBzZWN0aW9uJyk7XG4gIH1cbn07XG5cblN3YWdnZXJTcGVjQ29udmVydGVyLnByb3RvdHlwZS5maW5pc2ggPSBmdW5jdGlvbiAoY2FsbGJhY2ssIG9iaikge1xuICBjYWxsYmFjayhvYmopO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIGxvZyA9IHJlcXVpcmUoJy4uL2hlbHBlcnMnKS5sb2c7XG52YXIgXyA9IHtcbiAgaXNQbGFpbk9iamVjdDogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9sYW5nL2lzUGxhaW5PYmplY3QnKSxcbiAgaXNTdHJpbmc6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9pc1N0cmluZycpLFxufTtcblxudmFyIFNjaGVtYU1hcmt1cCA9IHJlcXVpcmUoJy4uL3NjaGVtYS1tYXJrdXAuanMnKTtcbnZhciBqc3lhbWwgPSByZXF1aXJlKCdqcy15YW1sJyk7XG5cbnZhciBNb2RlbCA9IG1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG5hbWUsIGRlZmluaXRpb24sIG1vZGVscywgbW9kZWxQcm9wZXJ0eU1hY3JvKSB7XG4gIHRoaXMuZGVmaW5pdGlvbiA9IGRlZmluaXRpb24gfHwge307XG4gIHRoaXMuaXNBcnJheSA9IGRlZmluaXRpb24udHlwZSA9PT0gJ2FycmF5JztcbiAgdGhpcy5tb2RlbHMgPSBtb2RlbHMgfHwge307XG4gIHRoaXMubmFtZSA9IG5hbWUgfHwgZGVmaW5pdGlvbi50aXRsZSB8fCAnSW5saW5lIE1vZGVsJztcbiAgdGhpcy5tb2RlbFByb3BlcnR5TWFjcm8gPSBtb2RlbFByb3BlcnR5TWFjcm8gfHwgZnVuY3Rpb24gKHByb3BlcnR5KSB7XG4gICAgcmV0dXJuIHByb3BlcnR5LmRlZmF1bHQ7XG4gIH07XG5cbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vLyBOb3RlISAgVGhpcyBmdW5jdGlvbiB3aWxsIGJlIHJlbW92ZWQgaW4gMi4yLnghXG5Nb2RlbC5wcm90b3R5cGUuY3JlYXRlSlNPTlNhbXBsZSA9IE1vZGVsLnByb3RvdHlwZS5nZXRTYW1wbGVWYWx1ZSA9IGZ1bmN0aW9uIChtb2RlbHNUb0lnbm9yZSkge1xuICBtb2RlbHNUb0lnbm9yZSA9IG1vZGVsc1RvSWdub3JlIHx8IHt9O1xuXG4gIG1vZGVsc1RvSWdub3JlW3RoaXMubmFtZV0gPSB0aGlzO1xuXG4gIC8vIFJlc3BvbnNlIHN1cHBvcnRcbiAgaWYgKHRoaXMuZXhhbXBsZXMgJiYgXy5pc1BsYWluT2JqZWN0KHRoaXMuZXhhbXBsZXMpICYmIHRoaXMuZXhhbXBsZXNbJ2FwcGxpY2F0aW9uL2pzb24nXSkge1xuICAgIHRoaXMuZGVmaW5pdGlvbi5leGFtcGxlID0gdGhpcy5leGFtcGxlc1snYXBwbGljYXRpb24vanNvbiddO1xuXG4gICAgaWYgKF8uaXNTdHJpbmcodGhpcy5kZWZpbml0aW9uLmV4YW1wbGUpKSB7XG4gICAgICB0aGlzLmRlZmluaXRpb24uZXhhbXBsZSA9IGpzeWFtbC5zYWZlTG9hZCh0aGlzLmRlZmluaXRpb24uZXhhbXBsZSk7XG4gICAgfVxuICB9IGVsc2UgaWYgKCF0aGlzLmRlZmluaXRpb24uZXhhbXBsZSkge1xuICAgIHRoaXMuZGVmaW5pdGlvbi5leGFtcGxlID0gdGhpcy5leGFtcGxlcztcbiAgfVxuXG4gIHJldHVybiBTY2hlbWFNYXJrdXAuc2NoZW1hVG9KU09OKHRoaXMuZGVmaW5pdGlvbiwgdGhpcy5tb2RlbHMsIG1vZGVsc1RvSWdub3JlLCB0aGlzLm1vZGVsUHJvcGVydHlNYWNybyk7XG59O1xuXG5Nb2RlbC5wcm90b3R5cGUuZ2V0TW9ja1NpZ25hdHVyZSA9IGZ1bmN0aW9uICgpIHtcbiAgcmV0dXJuIFNjaGVtYU1hcmt1cC5zY2hlbWFUb0hUTUwodGhpcy5uYW1lLCB0aGlzLmRlZmluaXRpb24sIHRoaXMubW9kZWxzLCB0aGlzLm1vZGVsUHJvcGVydHlNYWNybyk7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgXyA9IHtcbiAgY2xvbmVEZWVwOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvY2xvbmVEZWVwJyksXG4gIGlzVW5kZWZpbmVkOiByZXF1aXJlKCdsb2Rhc2gtY29tcGF0L2xhbmcvaXNVbmRlZmluZWQnKSxcbiAgaXNFbXB0eTogcmVxdWlyZSgnbG9kYXNoLWNvbXBhdC9sYW5nL2lzRW1wdHknKSxcbiAgaXNPYmplY3Q6IHJlcXVpcmUoJ2xvZGFzaC1jb21wYXQvbGFuZy9pc09iamVjdCcpXG59O1xudmFyIGhlbHBlcnMgPSByZXF1aXJlKCcuLi9oZWxwZXJzJyk7XG52YXIgTW9kZWwgPSByZXF1aXJlKCcuL21vZGVsJyk7XG52YXIgU3dhZ2dlckh0dHAgPSByZXF1aXJlKCcuLi9odHRwJyk7XG52YXIgUSA9IHJlcXVpcmUoJ3EnKTtcblxudmFyIE9wZXJhdGlvbiA9IG1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKHBhcmVudCwgc2NoZW1lLCBvcGVyYXRpb25JZCwgaHR0cE1ldGhvZCwgcGF0aCwgYXJncywgZGVmaW5pdGlvbnMsIG1vZGVscywgY2xpZW50QXV0aG9yaXphdGlvbnMpIHtcbiAgdmFyIGVycm9ycyA9IFtdO1xuXG4gIHBhcmVudCA9IHBhcmVudCB8fCB7fTtcbiAgYXJncyA9IGFyZ3MgfHwge307XG5cbiAgaWYocGFyZW50ICYmIHBhcmVudC5vcHRpb25zKSB7XG4gICAgdGhpcy5jbGllbnQgPSBwYXJlbnQub3B0aW9ucy5jbGllbnQgfHwgbnVsbDtcbiAgICB0aGlzLnJlcXVlc3RJbnRlcmNlcHRvciA9IHBhcmVudC5vcHRpb25zLnJlcXVlc3RJbnRlcmNlcHRvciB8fCBudWxsO1xuICAgIHRoaXMucmVzcG9uc2VJbnRlcmNlcHRvciA9IHBhcmVudC5vcHRpb25zLnJlc3BvbnNlSW50ZXJjZXB0b3IgfHwgbnVsbDtcbiAgfVxuICB0aGlzLmF1dGhvcml6YXRpb25zID0gYXJncy5zZWN1cml0eTtcbiAgdGhpcy5iYXNlUGF0aCA9IHBhcmVudC5iYXNlUGF0aCB8fCAnLyc7XG4gIHRoaXMuY2xpZW50QXV0aG9yaXphdGlvbnMgPSBjbGllbnRBdXRob3JpemF0aW9ucztcbiAgdGhpcy5jb25zdW1lcyA9IGFyZ3MuY29uc3VtZXMgfHwgcGFyZW50LmNvbnN1bWVzIHx8IFsnYXBwbGljYXRpb24vanNvbiddO1xuICB0aGlzLnByb2R1Y2VzID0gYXJncy5wcm9kdWNlcyB8fCBwYXJlbnQucHJvZHVjZXMgfHwgWydhcHBsaWNhdGlvbi9qc29uJ107XG4gIHRoaXMuZGVwcmVjYXRlZCA9IGFyZ3MuZGVwcmVjYXRlZDtcbiAgdGhpcy5kZXNjcmlwdGlvbiA9IGFyZ3MuZGVzY3JpcHRpb247XG4gIHRoaXMuaG9zdCA9IHBhcmVudC5ob3N0O1xuICB0aGlzLm1ldGhvZCA9IChodHRwTWV0aG9kIHx8IGVycm9ycy5wdXNoKCdPcGVyYXRpb24gJyArIG9wZXJhdGlvbklkICsgJyBpcyBtaXNzaW5nIG1ldGhvZC4nKSk7XG4gIHRoaXMubW9kZWxzID0gbW9kZWxzIHx8IHt9O1xuICB0aGlzLm5pY2tuYW1lID0gKG9wZXJhdGlvbklkIHx8IGVycm9ycy5wdXNoKCdPcGVyYXRpb25zIG11c3QgaGF2ZSBhIG5pY2tuYW1lLicpKTtcbiAgdGhpcy5vcGVyYXRpb24gPSBhcmdzO1xuICB0aGlzLm9wZXJhdGlvbnMgPSB7fTtcbiAgdGhpcy5wYXJhbWV0ZXJzID0gYXJncyAhPT0gbnVsbCA/IChhcmdzLnBhcmFtZXRlcnMgfHwgW10pIDoge307XG4gIHRoaXMucGFyZW50ID0gcGFyZW50O1xuICB0aGlzLnBhdGggPSAocGF0aCB8fCBlcnJvcnMucHVzaCgnT3BlcmF0aW9uICcgKyB0aGlzLm5pY2tuYW1lICsgJyBpcyBtaXNzaW5nIHBhdGguJykpO1xuICB0aGlzLnJlc3BvbnNlcyA9IChhcmdzLnJlc3BvbnNlcyB8fCB7fSk7XG4gIHRoaXMuc2NoZW1lID0gc2NoZW1lIHx8IHBhcmVudC5zY2hlbWUgfHwgJ2h0dHAnO1xuICB0aGlzLnNjaGVtZXMgPSBhcmdzLnNjaGVtZXMgfHwgcGFyZW50LnNjaGVtZXM7XG4gIHRoaXMuc2VjdXJpdHkgPSBhcmdzLnNlY3VyaXR5IHx8IHBhcmVudC5zZWN1cml0eTtcbiAgdGhpcy5zdW1tYXJ5ID0gYXJncy5zdW1tYXJ5IHx8ICcnO1xuICB0aGlzLnRpbWVvdXQgPSBwYXJlbnQudGltZW91dDtcbiAgdGhpcy50eXBlID0gbnVsbDtcbiAgdGhpcy51c2VKUXVlcnkgPSBwYXJlbnQudXNlSlF1ZXJ5O1xuICB0aGlzLmpxdWVyeUFqYXhDYWNoZSA9IHBhcmVudC5qcXVlcnlBamF4Q2FjaGU7XG4gIHRoaXMuZW5hYmxlQ29va2llcyA9IHBhcmVudC5lbmFibGVDb29raWVzO1xuXG4gIHZhciBrZXk7XG5cbiAgaWYoIXRoaXMuaG9zdCkge1xuICAgIGlmKHR5cGVvZiB3aW5kb3cgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgICB0aGlzLmhvc3QgPSB3aW5kb3cubG9jYXRpb24uaG9zdDtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICB0aGlzLmhvc3QgPSAnbG9jYWxob3N0JztcbiAgICB9XG4gIH1cbiAgdGhpcy5wYXJhbWV0ZXJNYWNybyA9IHBhcmVudC5wYXJhbWV0ZXJNYWNybyB8fCBmdW5jdGlvbiAob3BlcmF0aW9uLCBwYXJhbWV0ZXIpIHtcbiAgICByZXR1cm4gcGFyYW1ldGVyLmRlZmF1bHQ7XG4gIH07XG5cbiAgdGhpcy5pbmxpbmVNb2RlbHMgPSBbXTtcblxuICBpZih0aGlzLmJhc2VQYXRoICE9PSAnLycgJiYgdGhpcy5iYXNlUGF0aC5zbGljZSgtMSkgPT09ICcvJykge1xuICAgIHRoaXMuYmFzZVBhdGggPSB0aGlzLmJhc2VQYXRoLnNsaWNlKDAsIC0xKTtcbiAgfVxuXG4gIGlmICh0eXBlb2YgdGhpcy5kZXByZWNhdGVkID09PSAnc3RyaW5nJykge1xuICAgIHN3aXRjaCh0aGlzLmRlcHJlY2F0ZWQudG9Mb3dlckNhc2UoKSkge1xuICAgICAgY2FzZSAndHJ1ZSc6IGNhc2UgJ3llcyc6IGNhc2UgJzEnOiB7XG4gICAgICAgIHRoaXMuZGVwcmVjYXRlZCA9IHRydWU7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuXG4gICAgICBjYXNlICdmYWxzZSc6IGNhc2UgJ25vJzogY2FzZSAnMCc6IGNhc2UgbnVsbDoge1xuICAgICAgICB0aGlzLmRlcHJlY2F0ZWQgPSBmYWxzZTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG5cbiAgICAgIGRlZmF1bHQ6IHRoaXMuZGVwcmVjYXRlZCA9IEJvb2xlYW4odGhpcy5kZXByZWNhdGVkKTtcbiAgICB9XG4gIH1cblxuICB2YXIgaSwgbW9kZWw7XG5cbiAgaWYgKGRlZmluaXRpb25zKSB7XG4gICAgLy8gYWRkIHRvIGdsb2JhbCBtb2RlbHNcbiAgICBmb3IgKGtleSBpbiBkZWZpbml0aW9ucykge1xuICAgICAgbW9kZWwgPSBuZXcgTW9kZWwoa2V5LCBkZWZpbml0aW9uc1trZXldLCB0aGlzLm1vZGVscywgcGFyZW50Lm1vZGVsUHJvcGVydHlNYWNybyk7XG5cbiAgICAgIGlmIChtb2RlbCkge1xuICAgICAgICB0aGlzLm1vZGVsc1trZXldID0gbW9kZWw7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGVsc2Uge1xuICAgIGRlZmluaXRpb25zID0ge307XG4gIH1cblxuICBmb3IgKGkgPSAwOyBpIDwgdGhpcy5wYXJhbWV0ZXJzLmxlbmd0aDsgaSsrKSB7XG4gICAgdmFyIGQsIHBhcmFtID0gdGhpcy5wYXJhbWV0ZXJzW2ldO1xuXG4gICAgLy8gQWxsb3cgbWFjcm8gdG8gc2V0IHRoZSBkZWZhdWx0IHZhbHVlXG4gICAgcGFyYW0uZGVmYXVsdCA9IHRoaXMucGFyYW1ldGVyTWFjcm8odGhpcywgcGFyYW0pO1xuXG4gICAgaWYgKHBhcmFtLnR5cGUgPT09ICdhcnJheScpIHtcbiAgICAgIHBhcmFtLmlzTGlzdCA9IHRydWU7XG4gICAgICBwYXJhbS5hbGxvd011bHRpcGxlID0gdHJ1ZTtcbiAgICB9XG5cbiAgICB2YXIgaW5uZXJUeXBlID0gdGhpcy5nZXRUeXBlKHBhcmFtKTtcblxuICAgIGlmIChpbm5lclR5cGUgJiYgaW5uZXJUeXBlLnRvU3RyaW5nKCkudG9Mb3dlckNhc2UoKSA9PT0gJ2Jvb2xlYW4nKSB7XG4gICAgICBwYXJhbS5hbGxvd2FibGVWYWx1ZXMgPSB7fTtcbiAgICAgIHBhcmFtLmlzTGlzdCA9IHRydWU7XG4gICAgICBwYXJhbVsnZW51bSddID0gW3RydWUsIGZhbHNlXTsgLy8gdXNlIGFjdHVhbCBwcmltaXRpdmVzXG4gICAgfVxuXG4gICAgZm9yKGtleSBpbiBwYXJhbSkge1xuICAgICAgaGVscGVycy5leHRyYWN0RXh0ZW5zaW9ucyhrZXksIHBhcmFtKTtcbiAgICB9XG4gICAgaWYodHlwZW9mIHBhcmFtWyd4LWV4YW1wbGUnXSAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIGQgPSBwYXJhbVsneC1leGFtcGxlJ107XG4gICAgICBwYXJhbS5kZWZhdWx0ID0gZDtcbiAgICB9XG4gICAgaWYocGFyYW1bJ3gtZXhhbXBsZXMnXSkge1xuICAgICAgZCA9IHBhcmFtWyd4LWV4YW1wbGVzJ10uZGVmYXVsdDtcbiAgICAgIGlmKHR5cGVvZiBkICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgICBwYXJhbS5kZWZhdWx0ID0gZDtcbiAgICAgIH1cbiAgICB9XG5cbiAgICB2YXIgZW51bVZhbHVlcyA9IHBhcmFtWydlbnVtJ10gfHwgKHBhcmFtLml0ZW1zICYmIHBhcmFtLml0ZW1zWydlbnVtJ10pO1xuXG4gICAgaWYgKHR5cGVvZiBlbnVtVmFsdWVzICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgdmFyIGlkO1xuXG4gICAgICBwYXJhbS5hbGxvd2FibGVWYWx1ZXMgPSB7fTtcbiAgICAgIHBhcmFtLmFsbG93YWJsZVZhbHVlcy52YWx1ZXMgPSBbXTtcbiAgICAgIHBhcmFtLmFsbG93YWJsZVZhbHVlcy5kZXNjcmlwdGl2ZVZhbHVlcyA9IFtdO1xuXG4gICAgICBmb3IgKGlkID0gMDsgaWQgPCBlbnVtVmFsdWVzLmxlbmd0aDsgaWQrKykge1xuICAgICAgICB2YXIgdmFsdWUgPSBlbnVtVmFsdWVzW2lkXTtcbiAgICAgICAgdmFyIGlzRGVmYXVsdCA9ICh2YWx1ZSA9PT0gcGFyYW0uZGVmYXVsdCB8fCB2YWx1ZSsnJyA9PT0gcGFyYW0uZGVmYXVsdCk7XG5cbiAgICAgICAgcGFyYW0uYWxsb3dhYmxlVmFsdWVzLnZhbHVlcy5wdXNoKHZhbHVlKTtcbiAgICAgICAgLy8gQWx3YXlzIGhhdmUgc3RyaW5nIGZvciBkZXNjcmlwdGl2ZSB2YWx1ZXMuLi4uXG4gICAgICAgIHBhcmFtLmFsbG93YWJsZVZhbHVlcy5kZXNjcmlwdGl2ZVZhbHVlcy5wdXNoKHt2YWx1ZSA6IHZhbHVlKycnLCBpc0RlZmF1bHQ6IGlzRGVmYXVsdH0pO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChwYXJhbS50eXBlID09PSAnYXJyYXknKSB7XG4gICAgICBpbm5lclR5cGUgPSBbaW5uZXJUeXBlXTtcblxuICAgICAgaWYgKHR5cGVvZiBwYXJhbS5hbGxvd2FibGVWYWx1ZXMgPT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgIC8vIGNhbid0IHNob3cgYXMgYSBsaXN0IGlmIG5vIHZhbHVlcyB0byBzZWxlY3QgZnJvbVxuICAgICAgICBkZWxldGUgcGFyYW0uaXNMaXN0O1xuICAgICAgICBkZWxldGUgcGFyYW0uYWxsb3dNdWx0aXBsZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBwYXJhbS5tb2RlbFNpZ25hdHVyZSA9IHt0eXBlOiBpbm5lclR5cGUsIGRlZmluaXRpb25zOiB0aGlzLm1vZGVsc307XG4gICAgcGFyYW0uc2lnbmF0dXJlID0gdGhpcy5nZXRNb2RlbFNpZ25hdHVyZShpbm5lclR5cGUsIHRoaXMubW9kZWxzKS50b1N0cmluZygpO1xuICAgIHBhcmFtLnNhbXBsZUpTT04gPSB0aGlzLmdldE1vZGVsU2FtcGxlSlNPTihpbm5lclR5cGUsIHRoaXMubW9kZWxzKTtcbiAgICBwYXJhbS5yZXNwb25zZUNsYXNzU2lnbmF0dXJlID0gcGFyYW0uc2lnbmF0dXJlO1xuICB9XG5cbiAgdmFyIGtleW5hbWUsIGRlZmF1bHRSZXNwb25zZUNvZGUsIHJlc3BvbnNlLCByZXNwb25zZXMgPSB0aGlzLnJlc3BvbnNlcztcblxuICBpZiAocmVzcG9uc2VzWycyMDAnXSkge1xuICAgIHJlc3BvbnNlID0gcmVzcG9uc2VzWycyMDAnXTtcbiAgICBkZWZhdWx0UmVzcG9uc2VDb2RlID0gJzIwMCc7XG4gIH0gZWxzZSBpZiAocmVzcG9uc2VzWycyMDEnXSkge1xuICAgIHJlc3BvbnNlID0gcmVzcG9uc2VzWycyMDEnXTtcbiAgICBkZWZhdWx0UmVzcG9uc2VDb2RlID0gJzIwMSc7XG4gIH0gZWxzZSBpZiAocmVzcG9uc2VzWycyMDInXSkge1xuICAgIHJlc3BvbnNlID0gcmVzcG9uc2VzWycyMDInXTtcbiAgICBkZWZhdWx0UmVzcG9uc2VDb2RlID0gJzIwMic7XG4gIH0gZWxzZSBpZiAocmVzcG9uc2VzWycyMDMnXSkge1xuICAgIHJlc3BvbnNlID0gcmVzcG9uc2VzWycyMDMnXTtcbiAgICBkZWZhdWx0UmVzcG9uc2VDb2RlID0gJzIwMyc7XG4gIH0gZWxzZSBpZiAocmVzcG9uc2VzWycyMDQnXSkge1xuICAgIHJlc3BvbnNlID0gcmVzcG9uc2VzWycyMDQnXTtcbiAgICBkZWZhdWx0UmVzcG9uc2VDb2RlID0gJzIwNCc7XG4gIH0gZWxzZSBpZiAocmVzcG9uc2VzWycyMDUnXSkge1xuICAgIHJlc3BvbnNlID0gcmVzcG9uc2VzWycyMDUnXTtcbiAgICBkZWZhdWx0UmVzcG9uc2VDb2RlID0gJzIwNSc7XG4gIH0gZWxzZSBpZiAocmVzcG9uc2VzWycyMDYnXSkge1xuICAgIHJlc3BvbnNlID0gcmVzcG9uc2VzWycyMDYnXTtcbiAgICBkZWZhdWx0UmVzcG9uc2VDb2RlID0gJzIwNic7XG4gIH0gZWxzZSBpZiAocmVzcG9uc2VzWydkZWZhdWx0J10pIHtcbiAgICByZXNwb25zZSA9IHJlc3BvbnNlc1snZGVmYXVsdCddO1xuICAgIGRlZmF1bHRSZXNwb25zZUNvZGUgPSAnZGVmYXVsdCc7XG4gIH1cblxuICBmb3Ioa2V5bmFtZSBpbiByZXNwb25zZXMpIHtcbiAgICBoZWxwZXJzLmV4dHJhY3RFeHRlbnNpb25zKGtleW5hbWUsIHJlc3BvbnNlcyk7XG4gICAgaWYodHlwZW9mIGtleW5hbWUgPT09ICdzdHJpbmcnICYmIGtleW5hbWUuaW5kZXhPZigneC0nKSA9PT0gLTEpIHtcbiAgICAgIHZhciByZXNwb25zZU9iamVjdCA9IHJlc3BvbnNlc1trZXluYW1lXTtcbiAgICAgIGlmKHR5cGVvZiByZXNwb25zZU9iamVjdCA9PT0gJ29iamVjdCcgJiYgdHlwZW9mIHJlc3BvbnNlT2JqZWN0LmhlYWRlcnMgPT09ICdvYmplY3QnKSB7XG4gICAgICAgIHZhciBoZWFkZXJzID0gcmVzcG9uc2VPYmplY3QuaGVhZGVycztcbiAgICAgICAgZm9yKHZhciBoZWFkZXJOYW1lIGluIGhlYWRlcnMpIHtcbiAgICAgICAgICB2YXIgaGVhZGVyID0gaGVhZGVyc1toZWFkZXJOYW1lXTtcbiAgICAgICAgICBpZih0eXBlb2YgaGVhZGVyID09PSAnb2JqZWN0Jykge1xuICAgICAgICAgICAgZm9yKHZhciBoZWFkZXJLZXkgaW4gaGVhZGVyKSB7XG4gICAgICAgICAgICAgIGhlbHBlcnMuZXh0cmFjdEV4dGVuc2lvbnMoaGVhZGVyS2V5LCBoZWFkZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIGlmIChyZXNwb25zZSkge1xuICAgIGZvcihrZXluYW1lIGluIHJlc3BvbnNlKSB7XG4gICAgICBoZWxwZXJzLmV4dHJhY3RFeHRlbnNpb25zKGtleW5hbWUsIHJlc3BvbnNlKTtcbiAgICB9XG4gIH1cblxuICBpZiAocmVzcG9uc2UgJiYgcmVzcG9uc2Uuc2NoZW1hKSB7XG4gICAgdmFyIHJlc29sdmVkTW9kZWwgPSB0aGlzLnJlc29sdmVNb2RlbChyZXNwb25zZS5zY2hlbWEsIGRlZmluaXRpb25zKTtcbiAgICB2YXIgc3VjY2Vzc1Jlc3BvbnNlO1xuXG4gICAgZGVsZXRlIHJlc3BvbnNlc1tkZWZhdWx0UmVzcG9uc2VDb2RlXTtcblxuICAgIGlmIChyZXNvbHZlZE1vZGVsKSB7XG4gICAgICB0aGlzLnN1Y2Nlc3NSZXNwb25zZSA9IHt9O1xuICAgICAgc3VjY2Vzc1Jlc3BvbnNlID0gdGhpcy5zdWNjZXNzUmVzcG9uc2VbZGVmYXVsdFJlc3BvbnNlQ29kZV0gPSByZXNvbHZlZE1vZGVsO1xuICAgIH0gZWxzZSBpZiAoIXJlc3BvbnNlLnNjaGVtYS50eXBlIHx8IHJlc3BvbnNlLnNjaGVtYS50eXBlID09PSAnb2JqZWN0JyB8fCByZXNwb25zZS5zY2hlbWEudHlwZSA9PT0gJ2FycmF5Jykge1xuICAgICAgLy8gSW5saW5lIG1vZGVsXG4gICAgICB0aGlzLnN1Y2Nlc3NSZXNwb25zZSA9IHt9O1xuICAgICAgc3VjY2Vzc1Jlc3BvbnNlID0gdGhpcy5zdWNjZXNzUmVzcG9uc2VbZGVmYXVsdFJlc3BvbnNlQ29kZV0gPSBuZXcgTW9kZWwodW5kZWZpbmVkLCByZXNwb25zZS5zY2hlbWEgfHwge30sIHRoaXMubW9kZWxzLCBwYXJlbnQubW9kZWxQcm9wZXJ0eU1hY3JvKTtcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gUHJpbWl0aXZlXG4gICAgICB0aGlzLnN1Y2Nlc3NSZXNwb25zZSA9IHt9O1xuICAgICAgc3VjY2Vzc1Jlc3BvbnNlID0gdGhpcy5zdWNjZXNzUmVzcG9uc2VbZGVmYXVsdFJlc3BvbnNlQ29kZV0gPSByZXNwb25zZS5zY2hlbWE7XG4gICAgfVxuXG4gICAgaWYgKHN1Y2Nlc3NSZXNwb25zZSkge1xuICAgICAgc3VjY2Vzc1Jlc3BvbnNlLnZlbmRvckV4dGVuc2lvbnMgPSByZXNwb25zZS52ZW5kb3JFeHRlbnNpb25zO1xuICAgICAgLy8gQXR0YWNoIHJlc3BvbnNlIHByb3BlcnRpZXNcbiAgICAgIGlmIChyZXNwb25zZS5kZXNjcmlwdGlvbikge1xuICAgICAgICBzdWNjZXNzUmVzcG9uc2UuZGVzY3JpcHRpb24gPSByZXNwb25zZS5kZXNjcmlwdGlvbjtcbiAgICAgIH1cblxuICAgICAgaWYgKHJlc3BvbnNlLmV4YW1wbGVzKSB7XG4gICAgICAgIHN1Y2Nlc3NSZXNwb25zZS5leGFtcGxlcyA9IHJlc3BvbnNlLmV4YW1wbGVzO1xuICAgICAgfVxuXG4gICAgICBpZiAocmVzcG9uc2UuaGVhZGVycykge1xuICAgICAgICBzdWNjZXNzUmVzcG9uc2UuaGVhZGVycyA9IHJlc3BvbnNlLmhlYWRlcnM7XG4gICAgICB9XG4gICAgfVxuXG4gICAgdGhpcy50eXBlID0gcmVzcG9uc2U7XG4gIH1cblxuICBpZiAoZXJyb3JzLmxlbmd0aCA+IDApIHtcbiAgICBpZiAodGhpcy5yZXNvdXJjZSAmJiB0aGlzLnJlc291cmNlLmFwaSAmJiB0aGlzLnJlc291cmNlLmFwaS5mYWlsKSB7XG4gICAgICB0aGlzLnJlc291cmNlLmFwaS5mYWlsKGVycm9ycyk7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHRoaXM7XG59O1xuXG5PcGVyYXRpb24ucHJvdG90eXBlLmlzRGVmYXVsdEFycmF5SXRlbVZhbHVlID0gZnVuY3Rpb24odmFsdWUsIHBhcmFtKSB7XG4gIGlmIChwYXJhbS5kZWZhdWx0ICYmIEFycmF5LmlzQXJyYXkocGFyYW0uZGVmYXVsdCkpIHtcbiAgICByZXR1cm4gcGFyYW0uZGVmYXVsdC5pbmRleE9mKHZhbHVlKSAhPT0gLTE7XG4gIH1cbiAgcmV0dXJuIHZhbHVlID09PSBwYXJhbS5kZWZhdWx0O1xufTtcblxuT3BlcmF0aW9uLnByb3RvdHlwZS5nZXRUeXBlID0gZnVuY3Rpb24gKHBhcmFtKSB7XG4gIHZhciB0eXBlID0gcGFyYW0udHlwZTtcbiAgdmFyIGZvcm1hdCA9IHBhcmFtLmZvcm1hdDtcbiAgdmFyIGlzQXJyYXkgPSBmYWxzZTtcbiAgdmFyIHN0cjtcblxuICBpZiAodHlwZSA9PT0gJ2ludGVnZXInICYmIGZvcm1hdCA9PT0gJ2ludDMyJykge1xuICAgIHN0ciA9ICdpbnRlZ2VyJztcbiAgfSBlbHNlIGlmICh0eXBlID09PSAnaW50ZWdlcicgJiYgZm9ybWF0ID09PSAnaW50NjQnKSB7XG4gICAgc3RyID0gJ2xvbmcnO1xuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdpbnRlZ2VyJykge1xuICAgIHN0ciA9ICdpbnRlZ2VyJztcbiAgfSBlbHNlIGlmICh0eXBlID09PSAnc3RyaW5nJykge1xuICAgIGlmIChmb3JtYXQgPT09ICdkYXRlLXRpbWUnKSB7XG4gICAgICBzdHIgPSAnZGF0ZS10aW1lJztcbiAgICB9IGVsc2UgaWYgKGZvcm1hdCA9PT0gJ2RhdGUnKSB7XG4gICAgICBzdHIgPSAnZGF0ZSc7XG4gICAgfSBlbHNlIHtcbiAgICAgIHN0ciA9ICdzdHJpbmcnO1xuICAgIH1cbiAgfSBlbHNlIGlmICh0eXBlID09PSAnbnVtYmVyJyAmJiBmb3JtYXQgPT09ICdmbG9hdCcpIHtcbiAgICBzdHIgPSAnZmxvYXQnO1xuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdudW1iZXInICYmIGZvcm1hdCA9PT0gJ2RvdWJsZScpIHtcbiAgICBzdHIgPSAnZG91YmxlJztcbiAgfSBlbHNlIGlmICh0eXBlID09PSAnbnVtYmVyJykge1xuICAgIHN0ciA9ICdkb3VibGUnO1xuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdib29sZWFuJykge1xuICAgIHN0ciA9ICdib29sZWFuJztcbiAgfSBlbHNlIGlmICh0eXBlID09PSAnYXJyYXknKSB7XG4gICAgaXNBcnJheSA9IHRydWU7XG5cbiAgICBpZiAocGFyYW0uaXRlbXMpIHtcbiAgICAgIHN0ciA9IHRoaXMuZ2V0VHlwZShwYXJhbS5pdGVtcyk7XG4gICAgfVxuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdmaWxlJykge1xuICAgIHN0ciA9ICdmaWxlJztcbiAgfVxuXG4gIGlmIChwYXJhbS4kcmVmKSB7XG4gICAgc3RyID0gaGVscGVycy5zaW1wbGVSZWYocGFyYW0uJHJlZik7XG4gIH1cblxuICB2YXIgc2NoZW1hID0gcGFyYW0uc2NoZW1hO1xuXG4gIGlmIChzY2hlbWEpIHtcbiAgICB2YXIgcmVmID0gc2NoZW1hLiRyZWY7XG5cbiAgICBpZiAocmVmKSB7XG4gICAgICByZWYgPSBoZWxwZXJzLnNpbXBsZVJlZihyZWYpO1xuXG4gICAgICBpZiAoaXNBcnJheSkge1xuICAgICAgICByZXR1cm4gWyByZWYgXTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiByZWY7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIElmIGlubGluZSBzY2hlbWEsIHdlIGFkZCBpdCBvdXIgaW50ZXJhbCBoYXNoIC0+IHdoaWNoIGdpdmVzIHVzIGl0J3MgSUQgKGludClcbiAgICAgIGlmKHNjaGVtYS50eXBlID09PSAnb2JqZWN0Jykge1xuICAgICAgICByZXR1cm4gdGhpcy5hZGRJbmxpbmVNb2RlbChzY2hlbWEpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRoaXMuZ2V0VHlwZShzY2hlbWEpO1xuICAgIH1cbiAgfVxuICBpZiAoaXNBcnJheSkge1xuICAgIHJldHVybiBbIHN0ciBdO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBzdHI7XG4gIH1cbn07XG5cbi8qKlxuICogYWRkcyBhbiBpbmxpbmUgc2NoZW1hIChtb2RlbCkgdG8gYSBoYXNoLCB3aGVyZSB3ZSBjYW4gcmVmIGl0IGxhdGVyXG4gKiBAcGFyYW0ge29iamVjdH0gc2NoZW1hIGEgc2NoZW1hXG4gKiBAcmV0dXJuIHtudW1iZXJ9IHRoZSBJRCBvZiB0aGUgc2NoZW1hIGJlaW5nIGFkZGVkLCBvciBudWxsXG4gKiovXG5PcGVyYXRpb24ucHJvdG90eXBlLmFkZElubGluZU1vZGVsID0gZnVuY3Rpb24gKHNjaGVtYSkge1xuICB2YXIgbGVuID0gdGhpcy5pbmxpbmVNb2RlbHMubGVuZ3RoO1xuICB2YXIgbW9kZWwgPSB0aGlzLnJlc29sdmVNb2RlbChzY2hlbWEsIHt9KTtcbiAgaWYobW9kZWwpIHtcbiAgICB0aGlzLmlubGluZU1vZGVscy5wdXNoKG1vZGVsKTtcbiAgICByZXR1cm4gJ0lubGluZSBNb2RlbCAnK2xlbjsgLy8gcmV0dXJuIHN0cmluZyByZWYgb2YgdGhlIGlubGluZSBtb2RlbCAodXNlZCB3aXRoICNnZXRJbmxpbmVNb2RlbClcbiAgfVxuICByZXR1cm4gbnVsbDsgLy8gcmVwb3J0IGVycm9ycz9cbn07XG5cbi8qKlxuICogZ2V0cyB0aGUgaW50ZXJuYWwgcmVmIHRvIGFuIGlubGluZSBtb2RlbFxuICogQHBhcmFtIHtzdHJpbmd9IGlubGluZV9zdHIgYSBzdHJpbmcgcmVmZXJlbmNlIHRvIGFuIGlubGluZSBtb2RlbFxuICogQHJldHVybiB7TW9kZWx9IHRoZSBtb2RlbCBiZWluZyByZWZlcmVuY2VkLiBPciBudWxsXG4gKiovXG5PcGVyYXRpb24ucHJvdG90eXBlLmdldElubGluZU1vZGVsID0gZnVuY3Rpb24oaW5saW5lU3RyKSB7XG4gIGlmKC9eSW5saW5lIE1vZGVsIFxcZCskLy50ZXN0KGlubGluZVN0cikpIHtcbiAgICB2YXIgaWQgPSBwYXJzZUludChpbmxpbmVTdHIuc3Vic3RyKCdJbmxpbmUgTW9kZWwnLmxlbmd0aCkudHJpbSgpLDEwKTsgLy9cbiAgICB2YXIgbW9kZWwgPSB0aGlzLmlubGluZU1vZGVsc1tpZF07XG4gICAgcmV0dXJuIG1vZGVsO1xuICB9XG4gIC8vIEknbSByZXR1cm5pbmcgbnVsbCBoZXJlLCBzaG91bGQgSSByYXRoZXIgdGhyb3cgYW4gZXJyb3I/XG4gIHJldHVybiBudWxsO1xufTtcblxuT3BlcmF0aW9uLnByb3RvdHlwZS5yZXNvbHZlTW9kZWwgPSBmdW5jdGlvbiAoc2NoZW1hLCBkZWZpbml0aW9ucykge1xuICBpZiAodHlwZW9mIHNjaGVtYS4kcmVmICE9PSAndW5kZWZpbmVkJykge1xuICAgIHZhciByZWYgPSBzY2hlbWEuJHJlZjtcblxuICAgIGlmIChyZWYuaW5kZXhPZignIy9kZWZpbml0aW9ucy8nKSA9PT0gMCkge1xuICAgICAgcmVmID0gcmVmLnN1YnN0cmluZygnIy9kZWZpbml0aW9ucy8nLmxlbmd0aCk7XG4gICAgfVxuXG4gICAgaWYgKGRlZmluaXRpb25zW3JlZl0pIHtcbiAgICAgIHJldHVybiBuZXcgTW9kZWwocmVmLCBkZWZpbml0aW9uc1tyZWZdLCB0aGlzLm1vZGVscywgdGhpcy5wYXJlbnQubW9kZWxQcm9wZXJ0eU1hY3JvKTtcbiAgICB9XG4gIC8vIHNjaGVtYSBtdXN0IGF0IGxlYXN0IGJlIGFuIG9iamVjdCB0byBnZXQgcmVzb2x2ZWQgdG8gYW4gaW5saW5lIE1vZGVsXG4gIH0gZWxzZSBpZiAoc2NoZW1hICYmIHR5cGVvZiBzY2hlbWEgPT09ICdvYmplY3QnICYmXG4gICAgICAgICAgICAoc2NoZW1hLnR5cGUgPT09ICdvYmplY3QnIHx8IF8uaXNVbmRlZmluZWQoc2NoZW1hLnR5cGUpKSkge1xuICAgIHJldHVybiBuZXcgTW9kZWwodW5kZWZpbmVkLCBzY2hlbWEsIHRoaXMubW9kZWxzLCB0aGlzLnBhcmVudC5tb2RlbFByb3BlcnR5TWFjcm8pO1xuICB9XG5cbiAgcmV0dXJuIG51bGw7XG59O1xuXG5PcGVyYXRpb24ucHJvdG90eXBlLmhlbHAgPSBmdW5jdGlvbiAoZG9udFByaW50KSB7XG4gIHZhciBvdXQgPSB0aGlzLm5pY2tuYW1lICsgJzogJyArIHRoaXMuc3VtbWFyeSArICdcXG4nO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5wYXJhbWV0ZXJzLmxlbmd0aDsgaSsrKSB7XG4gICAgdmFyIHBhcmFtID0gdGhpcy5wYXJhbWV0ZXJzW2ldO1xuICAgIHZhciB0eXBlSW5mbyA9IHBhcmFtLnNpZ25hdHVyZTtcblxuICAgIG91dCArPSAnXFxuICAqICcgKyBwYXJhbS5uYW1lICsgJyAoJyArIHR5cGVJbmZvICsgJyk6ICcgKyBwYXJhbS5kZXNjcmlwdGlvbjtcbiAgfVxuXG4gIGlmICh0eXBlb2YgZG9udFByaW50ID09PSAndW5kZWZpbmVkJykge1xuICAgIGhlbHBlcnMubG9nKG91dCk7XG4gIH1cblxuICByZXR1cm4gb3V0O1xufTtcblxuT3BlcmF0aW9uLnByb3RvdHlwZS5nZXRNb2RlbFNpZ25hdHVyZSA9IGZ1bmN0aW9uICh0eXBlLCBkZWZpbml0aW9ucykge1xuICB2YXIgaXNQcmltaXRpdmUsIGxpc3RUeXBlO1xuXG4gIGlmICh0eXBlIGluc3RhbmNlb2YgQXJyYXkpIHtcbiAgICBsaXN0VHlwZSA9IHRydWU7XG4gICAgdHlwZSA9IHR5cGVbMF07XG4gIH1cblxuICAvLyBDb252ZXJ0IHVuZGVmaW5lZCB0byBzdHJpbmcgb2YgJ3VuZGVmaW5lZCdcbiAgaWYgKHR5cGVvZiB0eXBlID09PSAndW5kZWZpbmVkJykge1xuICAgIHR5cGUgPSAndW5kZWZpbmVkJztcbiAgICBpc1ByaW1pdGl2ZSA9IHRydWU7XG5cbiAgfSBlbHNlIGlmIChkZWZpbml0aW9uc1t0eXBlXSl7XG4gICAgLy8gYSBtb2RlbCBkZWYgZXhpc3RzP1xuICAgIHR5cGUgPSBkZWZpbml0aW9uc1t0eXBlXTsgLyogTW9kZWwgKi9cbiAgICBpc1ByaW1pdGl2ZSA9IGZhbHNlO1xuXG4gIH0gZWxzZSBpZiAodGhpcy5nZXRJbmxpbmVNb2RlbCh0eXBlKSkge1xuICAgIHR5cGUgPSB0aGlzLmdldElubGluZU1vZGVsKHR5cGUpOyAvKiBNb2RlbCAqL1xuICAgIGlzUHJpbWl0aXZlID0gZmFsc2U7XG5cbiAgfSBlbHNlIHtcbiAgICAvLyBXZSBkZWZhdWx0IHRvIHByaW1pdGl2ZVxuICAgIGlzUHJpbWl0aXZlID0gdHJ1ZTtcbiAgfVxuXG4gIGlmIChpc1ByaW1pdGl2ZSkge1xuICAgIGlmIChsaXN0VHlwZSkge1xuICAgICAgcmV0dXJuICdBcnJheVsnICsgdHlwZSArICddJztcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHR5cGUudG9TdHJpbmcoKTtcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAgaWYgKGxpc3RUeXBlKSB7XG4gICAgICByZXR1cm4gJ0FycmF5WycgKyB0eXBlLmdldE1vY2tTaWduYXR1cmUoKSArICddJztcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHR5cGUuZ2V0TW9ja1NpZ25hdHVyZSgpO1xuICAgIH1cbiAgfVxufTtcblxuT3BlcmF0aW9uLnByb3RvdHlwZS5zdXBwb3J0SGVhZGVyUGFyYW1zID0gZnVuY3Rpb24gKCkge1xuICByZXR1cm4gdHJ1ZTtcbn07XG5cbk9wZXJhdGlvbi5wcm90b3R5cGUuc3VwcG9ydGVkU3VibWl0TWV0aG9kcyA9IGZ1bmN0aW9uICgpIHtcbiAgcmV0dXJuIHRoaXMucGFyZW50LnN1cHBvcnRlZFN1Ym1pdE1ldGhvZHM7XG59O1xuXG5PcGVyYXRpb24ucHJvdG90eXBlLmdldEhlYWRlclBhcmFtcyA9IGZ1bmN0aW9uIChhcmdzKSB7XG4gIHZhciBoZWFkZXJzID0gdGhpcy5zZXRDb250ZW50VHlwZXMoYXJncywge30pO1xuICB2YXIgaGVhZGVyUGFyYW1zQnlMb3dlckNhc2UgPSB7fTtcblxuICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMucGFyYW1ldGVycy5sZW5ndGg7IGkrKykge1xuICAgIHZhciBwYXJhbSA9IHRoaXMucGFyYW1ldGVyc1tpXTtcblxuICAgIGlmIChwYXJhbS5pbiA9PT0gJ2hlYWRlcicpIHtcbiAgICAgIGhlYWRlclBhcmFtc0J5TG93ZXJDYXNlW3BhcmFtLm5hbWUudG9Mb3dlckNhc2UoKV0gPSBwYXJhbTtcbiAgICB9XG4gIH1cblxuICBmb3IgKHZhciBhcmcgaW4gYXJncykge1xuICAgIHZhciBoZWFkZXJQYXJhbSA9IGhlYWRlclBhcmFtc0J5TG93ZXJDYXNlW2FyZy50b0xvd2VyQ2FzZSgpXTtcbiAgICBpZiAodHlwZW9mIGhlYWRlclBhcmFtICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgdmFyIHZhbHVlID0gYXJnc1thcmddO1xuXG4gICAgICBpZiAoQXJyYXkuaXNBcnJheSh2YWx1ZSkpIHtcbiAgICAgICAgdmFsdWUgPSB2YWx1ZS50b1N0cmluZygpO1xuICAgICAgfVxuXG4gICAgICBoZWFkZXJzW2hlYWRlclBhcmFtLm5hbWVdID0gdmFsdWU7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIGhlYWRlcnM7XG59O1xuXG5PcGVyYXRpb24ucHJvdG90eXBlLnVybGlmeSA9IGZ1bmN0aW9uIChhcmdzKSB7XG4gIHZhciBmb3JtUGFyYW1zID0ge307XG4gIHZhciByZXF1ZXN0VXJsID0gdGhpcy5wYXRoLnJlcGxhY2UoLyMuKi8sICcnKTsgLy8gcmVtb3ZlIFVSTCBmcmFnbWVudFxuICB2YXIgcXVlcnlzdHJpbmcgPSAnJzsgLy8gZ3JhYiBwYXJhbXMgZnJvbSB0aGUgYXJncywgYnVpbGQgdGhlIHF1ZXJ5c3RyaW5nIGFsb25nIHRoZSB3YXlcblxuICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMucGFyYW1ldGVycy5sZW5ndGg7IGkrKykge1xuICAgIHZhciBwYXJhbSA9IHRoaXMucGFyYW1ldGVyc1tpXTtcblxuICAgIGlmICh0eXBlb2YgYXJnc1twYXJhbS5uYW1lXSAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIGlmIChwYXJhbS5pbiA9PT0gJ3BhdGgnKSB7XG4gICAgICAgIHZhciByZWcgPSBuZXcgUmVnRXhwKCdcXHsnICsgcGFyYW0ubmFtZSArICdcXH0nLCAnZ2knKTtcbiAgICAgICAgdmFyIHZhbHVlID0gYXJnc1twYXJhbS5uYW1lXTtcblxuICAgICAgICBpZiAoQXJyYXkuaXNBcnJheSh2YWx1ZSkpIHtcbiAgICAgICAgICB2YWx1ZSA9IHRoaXMuZW5jb2RlUGF0aENvbGxlY3Rpb24ocGFyYW0uY29sbGVjdGlvbkZvcm1hdCwgcGFyYW0ubmFtZSwgdmFsdWUpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHZhbHVlID0gdGhpcy5lbmNvZGVQYXRoUGFyYW0odmFsdWUpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmVxdWVzdFVybCA9IHJlcXVlc3RVcmwucmVwbGFjZShyZWcsIHZhbHVlKTtcbiAgICAgIH0gZWxzZSBpZiAocGFyYW0uaW4gPT09ICdxdWVyeScgJiYgdHlwZW9mIGFyZ3NbcGFyYW0ubmFtZV0gIT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgIGlmIChxdWVyeXN0cmluZyA9PT0gJycgJiYgcmVxdWVzdFVybC5pbmRleE9mKCc/JykgPCAwKSB7XG4gICAgICAgICAgcXVlcnlzdHJpbmcgKz0gJz8nO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHF1ZXJ5c3RyaW5nICs9ICcmJztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0eXBlb2YgcGFyYW0uY29sbGVjdGlvbkZvcm1hdCAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgICB2YXIgcXAgPSBhcmdzW3BhcmFtLm5hbWVdO1xuXG4gICAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkocXApKSB7XG4gICAgICAgICAgICBxdWVyeXN0cmluZyArPSB0aGlzLmVuY29kZVF1ZXJ5Q29sbGVjdGlvbihwYXJhbS5jb2xsZWN0aW9uRm9ybWF0LCBwYXJhbS5uYW1lLCBxcCk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHF1ZXJ5c3RyaW5nICs9IHRoaXMuZW5jb2RlUXVlcnlLZXkocGFyYW0ubmFtZSkgKyAnPScgKyB0aGlzLmVuY29kZVF1ZXJ5UGFyYW0oYXJnc1twYXJhbS5uYW1lXSk7XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHF1ZXJ5c3RyaW5nICs9IHRoaXMuZW5jb2RlUXVlcnlLZXkocGFyYW0ubmFtZSkgKyAnPScgKyB0aGlzLmVuY29kZVF1ZXJ5UGFyYW0oYXJnc1twYXJhbS5uYW1lXSk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSBpZiAocGFyYW0uaW4gPT09ICdmb3JtRGF0YScpIHtcbiAgICAgICAgZm9ybVBhcmFtc1twYXJhbS5uYW1lXSA9IGFyZ3NbcGFyYW0ubmFtZV07XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHZhciB1cmwgPSB0aGlzLnNjaGVtZSArICc6Ly8nICsgdGhpcy5ob3N0O1xuXG4gIGlmICh0aGlzLmJhc2VQYXRoICE9PSAnLycpIHtcbiAgICB1cmwgKz0gdGhpcy5iYXNlUGF0aDtcbiAgfVxuICByZXR1cm4gdXJsICsgcmVxdWVzdFVybCArIHF1ZXJ5c3RyaW5nO1xufTtcblxuT3BlcmF0aW9uLnByb3RvdHlwZS5nZXRNaXNzaW5nUGFyYW1zID0gZnVuY3Rpb24gKGFyZ3MpIHtcbiAgdmFyIG1pc3NpbmdQYXJhbXMgPSBbXTsgLy8gY2hlY2sgcmVxdWlyZWQgcGFyYW1zLCB0cmFjayB0aGUgb25lcyB0aGF0IGFyZSBtaXNzaW5nXG4gIHZhciBpO1xuXG4gIGZvciAoaSA9IDA7IGkgPCB0aGlzLnBhcmFtZXRlcnMubGVuZ3RoOyBpKyspIHtcbiAgICB2YXIgcGFyYW0gPSB0aGlzLnBhcmFtZXRlcnNbaV07XG5cbiAgICBpZiAocGFyYW0ucmVxdWlyZWQgPT09IHRydWUpIHtcbiAgICAgIGlmICh0eXBlb2YgYXJnc1twYXJhbS5uYW1lXSA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgbWlzc2luZ1BhcmFtcyA9IHBhcmFtLm5hbWU7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIG1pc3NpbmdQYXJhbXM7XG59O1xuXG5PcGVyYXRpb24ucHJvdG90eXBlLmdldEJvZHkgPSBmdW5jdGlvbiAoaGVhZGVycywgYXJncykge1xuICB2YXIgZm9ybVBhcmFtcyA9IHt9LCBoYXNGb3JtUGFyYW1zLCBwYXJhbSwgYm9keSwga2V5LCB2YWx1ZSwgaGFzQm9keSA9IGZhbHNlO1xuXG4gIC8vIGxvb2sgYXQgZWFjaCBwYXJhbSBhbmQgcHV0IGZvcm0gcGFyYW1zIGluIGFuIG9iamVjdFxuICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMucGFyYW1ldGVycy5sZW5ndGg7IGkrKykge1xuICAgIHBhcmFtID0gdGhpcy5wYXJhbWV0ZXJzW2ldO1xuICAgIGlmICh0eXBlb2YgYXJnc1twYXJhbS5uYW1lXSAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIGlmIChwYXJhbS5pbiA9PT0gJ2JvZHknKSB7XG4gICAgICAgIGJvZHkgPSBhcmdzW3BhcmFtLm5hbWVdO1xuICAgICAgfSBlbHNlIGlmIChwYXJhbS5pbiA9PT0gJ2Zvcm1EYXRhJykge1xuICAgICAgICBmb3JtUGFyYW1zW3BhcmFtLm5hbWVdID0ge1xuICAgICAgICAgIHBhcmFtOiBwYXJhbSxcbiAgICAgICAgICB2YWx1ZTogYXJnc1twYXJhbS5uYW1lXVxuICAgICAgICB9O1xuICAgICAgICBoYXNGb3JtUGFyYW1zID0gdHJ1ZTtcbiAgICAgIH1cbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICBpZihwYXJhbS5pbiA9PT0gJ2JvZHknKSB7XG4gICAgICAgIGhhc0JvZHkgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIGlmIGJvZHkgaXMgbnVsbCBhbmQgaGFzQm9keSBpcyB0cnVlLCBBTkQgYSBKU09OIGJvZHkgaXMgcmVxdWVzdGVkLCBzZW5kIGVtcHR5IHt9XG4gIGlmKGhhc0JvZHkgJiYgdHlwZW9mIGJvZHkgPT09ICd1bmRlZmluZWQnKSB7XG4gICAgdmFyIGNvbnRlbnRUeXBlID0gaGVhZGVyc1snQ29udGVudC1UeXBlJ107XG4gICAgaWYoY29udGVudFR5cGUgJiYgY29udGVudFR5cGUuaW5kZXhPZignYXBwbGljYXRpb24vanNvbicpID09PSAwKSB7XG4gICAgICBib2R5ID0gJ3t9JztcbiAgICB9XG4gIH1cblxuICB2YXIgaXNNdWx0aVBhcnQgPSBmYWxzZTtcbiAgaWYoaGVhZGVyc1snQ29udGVudC1UeXBlJ10gJiYgaGVhZGVyc1snQ29udGVudC1UeXBlJ10uaW5kZXhPZignbXVsdGlwYXJ0L2Zvcm0tZGF0YScpID49IDApIHtcbiAgICBpc011bHRpUGFydCA9IHRydWU7XG4gIH1cblxuICAvLyBoYW5kbGUgZm9ybSBwYXJhbXNcbiAgaWYgKGhhc0Zvcm1QYXJhbXMgJiYgIWlzTXVsdGlQYXJ0KSB7XG4gICAgdmFyIGVuY29kZWQgPSAnJztcblxuICAgIGZvciAoa2V5IGluIGZvcm1QYXJhbXMpIHtcbiAgICAgIHBhcmFtID0gZm9ybVBhcmFtc1trZXldLnBhcmFtO1xuICAgICAgdmFsdWUgPSBmb3JtUGFyYW1zW2tleV0udmFsdWU7XG5cbiAgICAgIGlmICh0eXBlb2YgdmFsdWUgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgIGlmIChBcnJheS5pc0FycmF5KHZhbHVlKSkge1xuICAgICAgICAgIGlmIChlbmNvZGVkICE9PSAnJykge1xuICAgICAgICAgICAgZW5jb2RlZCArPSAnJic7XG4gICAgICAgICAgfVxuICAgICAgICAgIGVuY29kZWQgKz0gdGhpcy5lbmNvZGVRdWVyeUNvbGxlY3Rpb24ocGFyYW0uY29sbGVjdGlvbkZvcm1hdCwga2V5LCB2YWx1ZSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgaWYgKGVuY29kZWQgIT09ICcnKSB7XG4gICAgICAgICAgICBlbmNvZGVkICs9ICcmJztcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBlbmNvZGVkICs9IGVuY29kZVVSSUNvbXBvbmVudChrZXkpICsgJz0nICsgZW5jb2RlVVJJQ29tcG9uZW50KHZhbHVlKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGJvZHkgPSBlbmNvZGVkO1xuICB9IGVsc2UgaWYgKGlzTXVsdGlQYXJ0KSB7XG4gICAgdmFyIGJvZHlQYXJhbTtcbiAgICBpZiAodHlwZW9mIEZvcm1EYXRhID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICBib2R5UGFyYW0gPSBuZXcgRm9ybURhdGEoKTtcblxuICAgICAgYm9keVBhcmFtLnR5cGUgPSAnZm9ybURhdGEnO1xuXG4gICAgICBmb3IgKGtleSBpbiBmb3JtUGFyYW1zKSB7XG4gICAgICAgIHBhcmFtID0gZm9ybVBhcmFtc1trZXldLnBhcmFtO1xuICAgICAgICB2YWx1ZSA9IGFyZ3Nba2V5XTtcblxuICAgICAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgICAgIGlmKHt9LnRvU3RyaW5nLmFwcGx5KHZhbHVlKSA9PT0gJ1tvYmplY3QgRmlsZV0nKSB7XG4gICAgICAgICAgICBib2R5UGFyYW0uYXBwZW5kKGtleSwgdmFsdWUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBlbHNlIGlmICh2YWx1ZS50eXBlID09PSAnZmlsZScgJiYgdmFsdWUudmFsdWUpIHtcbiAgICAgICAgICAgIGJvZHlQYXJhbS5hcHBlbmQoa2V5LCB2YWx1ZS52YWx1ZSk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGlmIChBcnJheS5pc0FycmF5KHZhbHVlKSkge1xuICAgICAgICAgICAgICBpZihwYXJhbS5jb2xsZWN0aW9uRm9ybWF0ID09PSAnbXVsdGknKSB7XG4gICAgICAgICAgICAgICAgYm9keVBhcmFtLmRlbGV0ZShrZXkpO1xuICAgICAgICAgICAgICAgIGZvcih2YXIgdiBpbiB2YWx1ZSkge1xuICAgICAgICAgICAgICAgICAgYm9keVBhcmFtLmFwcGVuZChrZXksIHZhbHVlW3ZdKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgYm9keVBhcmFtLmFwcGVuZChrZXksIHRoaXMuZW5jb2RlUXVlcnlDb2xsZWN0aW9uKHBhcmFtLmNvbGxlY3Rpb25Gb3JtYXQsIGtleSwgdmFsdWUpLnNwbGl0KCc9Jykuc2xpY2UoMSkuam9pbignPScpKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgIGJvZHlQYXJhbS5hcHBlbmQoa2V5LCB2YWx1ZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgICBib2R5ID0gYm9keVBhcmFtO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIGJvZHlQYXJhbSA9IHt9O1xuICAgICAgZm9yIChrZXkgaW4gZm9ybVBhcmFtcykge1xuICAgICAgICB2YWx1ZSA9IGFyZ3Nba2V5XTtcbiAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSB7XG4gICAgICAgICAgdmFyIGRlbGltZXRlcjtcbiAgICAgICAgICB2YXIgZm9ybWF0ID0gcGFyYW0uY29sbGVjdGlvbkZvcm1hdCB8fCAnbXVsdGknO1xuICAgICAgICAgIGlmKGZvcm1hdCA9PT0gJ3NzdicpIHtcbiAgICAgICAgICAgIGRlbGltZXRlciA9ICcgJztcbiAgICAgICAgICB9XG4gICAgICAgICAgZWxzZSBpZihmb3JtYXQgPT09ICdwaXBlcycpIHtcbiAgICAgICAgICAgIGRlbGltZXRlciA9ICd8JztcbiAgICAgICAgICB9XG4gICAgICAgICAgZWxzZSBpZihmb3JtYXQgPT09ICd0c3YnKSB7XG4gICAgICAgICAgICBkZWxpbWV0ZXIgPSAnXFx0JztcbiAgICAgICAgICB9XG4gICAgICAgICAgZWxzZSBpZihmb3JtYXQgPT09ICdtdWx0aScpIHtcbiAgICAgICAgICAgIGJvZHlQYXJhbVtrZXldID0gdmFsdWU7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICB9XG4gICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBkZWxpbWV0ZXIgPSAnLCc7XG4gICAgICAgICAgfVxuICAgICAgICAgIHZhciBkYXRhO1xuICAgICAgICAgIHZhbHVlLmZvckVhY2goZnVuY3Rpb24odikge1xuICAgICAgICAgICAgaWYoZGF0YSkge1xuICAgICAgICAgICAgICBkYXRhICs9IGRlbGltZXRlcjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICBkYXRhID0gJyc7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBkYXRhICs9IHY7XG4gICAgICAgICAgfSk7XG4gICAgICAgICAgYm9keVBhcmFtW2tleV0gPSBkYXRhO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgIGJvZHlQYXJhbVtrZXldID0gdmFsdWU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGJvZHkgPSBib2R5UGFyYW07XG4gICAgfVxuICAgIGhlYWRlcnNbJ0NvbnRlbnQtVHlwZSddID0gJ211bHRpcGFydC9mb3JtLWRhdGEnO1xuICB9XG5cbiAgcmV0dXJuIGJvZHk7XG59O1xuXG4vKipcbiAqIGdldHMgc2FtcGxlIHJlc3BvbnNlIGZvciBhIHNpbmdsZSBvcGVyYXRpb25cbiAqKi9cbk9wZXJhdGlvbi5wcm90b3R5cGUuZ2V0TW9kZWxTYW1wbGVKU09OID0gZnVuY3Rpb24gKHR5cGUsIG1vZGVscykge1xuICB2YXIgbGlzdFR5cGUsIHNhbXBsZUpzb24sIGlubmVyVHlwZTtcbiAgbW9kZWxzID0gbW9kZWxzIHx8IHt9O1xuXG4gIGxpc3RUeXBlID0gKHR5cGUgaW5zdGFuY2VvZiBBcnJheSk7XG4gIGlubmVyVHlwZSA9IGxpc3RUeXBlID8gdHlwZVswXSA6IHR5cGU7XG5cbiAgaWYobW9kZWxzW2lubmVyVHlwZV0pIHtcbiAgICBzYW1wbGVKc29uID0gbW9kZWxzW2lubmVyVHlwZV0uY3JlYXRlSlNPTlNhbXBsZSgpO1xuICB9IGVsc2UgaWYgKHRoaXMuZ2V0SW5saW5lTW9kZWwoaW5uZXJUeXBlKSl7XG4gICAgc2FtcGxlSnNvbiA9IHRoaXMuZ2V0SW5saW5lTW9kZWwoaW5uZXJUeXBlKS5jcmVhdGVKU09OU2FtcGxlKCk7IC8vIG1heSByZXR1cm4gbnVsbCwgaWYgdHlwZSBpc24ndCBjb3JyZWN0XG4gIH1cblxuXG4gIGlmIChzYW1wbGVKc29uKSB7XG4gICAgc2FtcGxlSnNvbiA9IGxpc3RUeXBlID8gW3NhbXBsZUpzb25dIDogc2FtcGxlSnNvbjtcblxuICAgIGlmICh0eXBlb2Ygc2FtcGxlSnNvbiA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHJldHVybiBzYW1wbGVKc29uO1xuICAgIH0gZWxzZSBpZiAoXy5pc09iamVjdChzYW1wbGVKc29uKSkge1xuICAgICAgdmFyIHQgPSBzYW1wbGVKc29uO1xuXG4gICAgICBpZiAoc2FtcGxlSnNvbiBpbnN0YW5jZW9mIEFycmF5ICYmIHNhbXBsZUpzb24ubGVuZ3RoID4gMCkge1xuICAgICAgICB0ID0gc2FtcGxlSnNvblswXTtcbiAgICAgIH1cblxuICAgICAgaWYgKHQubm9kZU5hbWUgJiYgdHlwZW9mIHQgPT09ICdOb2RlJykge1xuICAgICAgICB2YXIgeG1sU3RyaW5nID0gbmV3IFhNTFNlcmlhbGl6ZXIoKS5zZXJpYWxpemVUb1N0cmluZyh0KTtcblxuICAgICAgICByZXR1cm4gdGhpcy5mb3JtYXRYbWwoeG1sU3RyaW5nKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiBKU09OLnN0cmluZ2lmeShzYW1wbGVKc29uLCBudWxsLCAyKTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHNhbXBsZUpzb247XG4gICAgfVxuICB9XG59O1xuXG4vKipcbiAqIGxlZ2FjeSBiaW5kaW5nXG4gKiovXG5PcGVyYXRpb24ucHJvdG90eXBlLmRvID0gZnVuY3Rpb24gKGFyZ3MsIG9wdHMsIGNhbGxiYWNrLCBlcnJvciwgcGFyZW50KSB7XG4gIHJldHVybiB0aGlzLmV4ZWN1dGUoYXJncywgb3B0cywgY2FsbGJhY2ssIGVycm9yLCBwYXJlbnQpO1xufTtcblxuLyoqXG4gKiBleGVjdXRlcyBhbiBvcGVyYXRpb25cbiAqKi9cbk9wZXJhdGlvbi5wcm90b3R5cGUuZXhlY3V0ZSA9IGZ1bmN0aW9uIChhcmcxLCBhcmcyLCBhcmczLCBhcmc0LCBwYXJlbnQpIHtcbiAgdmFyIGFyZ3MgPSBhcmcxIHx8IHt9O1xuICB2YXIgb3B0cyA9IHt9LCBzdWNjZXNzLCBlcnJvciwgZGVmZXJyZWQsIHRpbWVvdXQ7XG5cbiAgaWYgKF8uaXNPYmplY3QoYXJnMikpIHtcbiAgICBvcHRzID0gYXJnMjtcbiAgICBzdWNjZXNzID0gYXJnMztcbiAgICBlcnJvciA9IGFyZzQ7XG4gIH1cblxuICB0aW1lb3V0ID0gdHlwZW9mIG9wdHMudGltZW91dCAhPT0gJ3VuZGVmaW5lZCcgPyBvcHRzLnRpbWVvdXQgOiB0aGlzLnRpbWVvdXQ7XG5cbiAgaWYodGhpcy5jbGllbnQpIHtcbiAgICBvcHRzLmNsaWVudCA9IHRoaXMuY2xpZW50O1xuICB9XG5cbiAgLy8gYWRkIHRoZSByZXF1ZXN0IGludGVyY2VwdG9yIGZyb20gcGFyZW50LCBpZiBub25lIHNlbnQgZnJvbSBjbGllbnRcbiAgaWYoIW9wdHMucmVxdWVzdEludGVyY2VwdG9yICYmIHRoaXMucmVxdWVzdEludGVyY2VwdG9yICkge1xuICAgIG9wdHMucmVxdWVzdEludGVyY2VwdG9yID0gdGhpcy5yZXF1ZXN0SW50ZXJjZXB0b3IgO1xuICB9XG5cbiAgaWYoIW9wdHMucmVzcG9uc2VJbnRlcmNlcHRvciAmJiB0aGlzLnJlc3BvbnNlSW50ZXJjZXB0b3IpIHtcbiAgICBvcHRzLnJlc3BvbnNlSW50ZXJjZXB0b3IgPSB0aGlzLnJlc3BvbnNlSW50ZXJjZXB0b3I7XG4gIH1cblxuICBpZiAodHlwZW9mIGFyZzIgPT09ICdmdW5jdGlvbicpIHtcbiAgICBzdWNjZXNzID0gYXJnMjtcbiAgICBlcnJvciA9IGFyZzM7XG4gIH1cblxuICBpZiAodGhpcy5wYXJlbnQudXNlUHJvbWlzZSkge1xuICAgIGRlZmVycmVkID0gUS5kZWZlcigpO1xuICB9IGVsc2Uge1xuICAgIHN1Y2Nlc3MgPSAoc3VjY2VzcyB8fCB0aGlzLnBhcmVudC5kZWZhdWx0U3VjY2Vzc0NhbGxiYWNrIHx8IGhlbHBlcnMubG9nKTtcbiAgICBlcnJvciA9IChlcnJvciB8fCB0aGlzLnBhcmVudC5kZWZhdWx0RXJyb3JDYWxsYmFjayB8fCBoZWxwZXJzLmxvZyk7XG4gIH1cblxuICBpZiAodHlwZW9mIG9wdHMudXNlSlF1ZXJ5ID09PSAndW5kZWZpbmVkJykge1xuICAgIG9wdHMudXNlSlF1ZXJ5ID0gdGhpcy51c2VKUXVlcnk7XG4gIH1cblxuICBpZiAodHlwZW9mIG9wdHMuanF1ZXJ5QWpheENhY2hlID09PSAndW5kZWZpbmVkJykge1xuICAgIG9wdHMuanF1ZXJ5QWpheENhY2hlID0gdGhpcy5qcXVlcnlBamF4Q2FjaGU7XG4gIH1cblxuICBpZiAodHlwZW9mIG9wdHMuZW5hYmxlQ29va2llcyA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBvcHRzLmVuYWJsZUNvb2tpZXMgPSB0aGlzLmVuYWJsZUNvb2tpZXM7XG4gIH1cblxuICB2YXIgbWlzc2luZ1BhcmFtcyA9IHRoaXMuZ2V0TWlzc2luZ1BhcmFtcyhhcmdzKTtcblxuICBpZiAobWlzc2luZ1BhcmFtcy5sZW5ndGggPiAwKSB7XG4gICAgdmFyIG1lc3NhZ2UgPSAnbWlzc2luZyByZXF1aXJlZCBwYXJhbXM6ICcgKyBtaXNzaW5nUGFyYW1zO1xuXG4gICAgaGVscGVycy5mYWlsKG1lc3NhZ2UpO1xuXG4gICAgaWYgKHRoaXMucGFyZW50LnVzZVByb21pc2UpIHtcbiAgICAgIGRlZmVycmVkLnJlamVjdChtZXNzYWdlKTtcbiAgICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xuICAgIH0gZWxzZSB7XG4gICAgICBlcnJvcihtZXNzYWdlLCBwYXJlbnQpO1xuICAgICAgcmV0dXJuIHt9O1xuICAgIH1cbiAgfVxuXG4gIHZhciBhbGxIZWFkZXJzID0gdGhpcy5nZXRIZWFkZXJQYXJhbXMoYXJncyk7XG4gIHZhciBjb250ZW50VHlwZUhlYWRlcnMgPSB0aGlzLnNldENvbnRlbnRUeXBlcyhhcmdzLCBvcHRzKTtcbiAgdmFyIGhlYWRlcnMgPSB7fSwgYXR0cm5hbWU7XG5cbiAgZm9yIChhdHRybmFtZSBpbiBhbGxIZWFkZXJzKSB7IGhlYWRlcnNbYXR0cm5hbWVdID0gYWxsSGVhZGVyc1thdHRybmFtZV07IH1cbiAgZm9yIChhdHRybmFtZSBpbiBjb250ZW50VHlwZUhlYWRlcnMpIHsgaGVhZGVyc1thdHRybmFtZV0gPSBjb250ZW50VHlwZUhlYWRlcnNbYXR0cm5hbWVdOyB9XG5cbiAgdmFyIGJvZHkgPSB0aGlzLmdldEJvZHkoY29udGVudFR5cGVIZWFkZXJzLCBhcmdzLCBvcHRzKTtcbiAgdmFyIHVybCA9IHRoaXMudXJsaWZ5KGFyZ3MpO1xuXG4gIGlmKHVybC5pbmRleE9mKCcue2Zvcm1hdH0nKSA+IDApIHtcbiAgICBpZihoZWFkZXJzKSB7XG4gICAgICB2YXIgZm9ybWF0ID0gaGVhZGVycy5BY2NlcHQgfHwgaGVhZGVycy5hY2NlcHQ7XG4gICAgICBpZihmb3JtYXQgJiYgZm9ybWF0LmluZGV4T2YoJ2pzb24nKSA+IDApIHtcbiAgICAgICAgdXJsID0gdXJsLnJlcGxhY2UoJy57Zm9ybWF0fScsICcuanNvbicpO1xuICAgICAgfVxuICAgICAgZWxzZSBpZihmb3JtYXQgJiYgZm9ybWF0LmluZGV4T2YoJ3htbCcpID4gMCkge1xuICAgICAgICB1cmwgPSB1cmwucmVwbGFjZSgnLntmb3JtYXR9JywgJy54bWwnKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICB2YXIgb2JqID0ge1xuICAgIHVybDogdXJsLFxuICAgIG1ldGhvZDogdGhpcy5tZXRob2QudG9VcHBlckNhc2UoKSxcbiAgICBib2R5OiBib2R5LFxuICAgIGVuYWJsZUNvb2tpZXM6IG9wdHMuZW5hYmxlQ29va2llcyxcbiAgICB1c2VKUXVlcnk6IG9wdHMudXNlSlF1ZXJ5LFxuICAgIGpxdWVyeUFqYXhDYWNoZTogb3B0cy5qcXVlcnlBamF4Q2FjaGUsXG4gICAgZGVmZXJyZWQ6IGRlZmVycmVkLFxuICAgIGhlYWRlcnM6IGhlYWRlcnMsXG4gICAgY2xpZW50QXV0aG9yaXphdGlvbnM6IG9wdHMuY2xpZW50QXV0aG9yaXphdGlvbnMsXG4gICAgb246IHtcbiAgICAgIHJlc3BvbnNlOiBmdW5jdGlvbiAocmVzcG9uc2UpIHtcbiAgICAgICAgaWYgKGRlZmVycmVkKSB7XG4gICAgICAgICAgZGVmZXJyZWQucmVzb2x2ZShyZXNwb25zZSk7XG4gICAgICAgICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmV0dXJuIHN1Y2Nlc3MocmVzcG9uc2UsIHBhcmVudCk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICBlcnJvcjogZnVuY3Rpb24gKHJlc3BvbnNlKSB7XG4gICAgICAgIGlmIChkZWZlcnJlZCkge1xuICAgICAgICAgIGRlZmVycmVkLnJlamVjdChyZXNwb25zZSk7XG4gICAgICAgICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmV0dXJuIGVycm9yKHJlc3BvbnNlLCBwYXJlbnQpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9O1xuXG4gIGlmICh0aW1lb3V0KSB7XG4gICAgb2JqLnRpbWVvdXQgPSB0aW1lb3V0O1xuICB9XG5cbiAgdGhpcy5jbGllbnRBdXRob3JpemF0aW9ucy5hcHBseShvYmosIHRoaXMub3BlcmF0aW9uLnNlY3VyaXR5KTtcbiAgaWYgKG9wdHMubW9jayA9PT0gdHJ1ZSkge1xuICAgIHJldHVybiBvYmo7XG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIG5ldyBTd2FnZ2VySHR0cCgpLmV4ZWN1dGUob2JqLCBvcHRzKTtcbiAgfVxufTtcblxuZnVuY3Rpb24gaXRlbUJ5UHJpb3JpdHkoY29sLCBpdGVtUHJpb3JpdHkpIHtcblxuICAvLyBObyBwcmlvcml0aWVzPyByZXR1cm4gZmlyc3QuLi5cbiAgaWYoXy5pc0VtcHR5KGl0ZW1Qcmlvcml0eSkpIHtcbiAgICByZXR1cm4gY29sWzBdO1xuICB9XG5cbiAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGl0ZW1Qcmlvcml0eS5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgIGlmKGNvbC5pbmRleE9mKGl0ZW1Qcmlvcml0eVtpXSkgPiAtMSkge1xuICAgICAgcmV0dXJuIGl0ZW1Qcmlvcml0eVtpXTtcbiAgICB9XG4gIH1cblxuICAvLyBPdGhlcndpc2UgcmV0dXJuIGZpcnN0XG4gIHJldHVybiBjb2xbMF07XG59XG5cbk9wZXJhdGlvbi5wcm90b3R5cGUuc2V0Q29udGVudFR5cGVzID0gZnVuY3Rpb24gKGFyZ3MsIG9wdHMpIHtcbiAgLy8gZGVmYXVsdCB0eXBlXG4gIHZhciBhbGxEZWZpbmVkUGFyYW1zID0gdGhpcy5wYXJhbWV0ZXJzO1xuICB2YXIgYm9keTtcbiAgdmFyIGNvbnN1bWVzID0gYXJncy5wYXJhbWV0ZXJDb250ZW50VHlwZSB8fCBpdGVtQnlQcmlvcml0eSh0aGlzLmNvbnN1bWVzLCBbJ2FwcGxpY2F0aW9uL2pzb24nLCAnYXBwbGljYXRpb24veWFtbCddKTtcbiAgdmFyIGFjY2VwdHMgPSBvcHRzLnJlc3BvbnNlQ29udGVudFR5cGUgfHwgaXRlbUJ5UHJpb3JpdHkodGhpcy5wcm9kdWNlcywgWydhcHBsaWNhdGlvbi9qc29uJywgJ2FwcGxpY2F0aW9uL3lhbWwnXSk7XG4gIHZhciBkZWZpbmVkRmlsZVBhcmFtcyA9IFtdO1xuICB2YXIgZGVmaW5lZEZvcm1QYXJhbXMgPSBbXTtcbiAgdmFyIGhlYWRlcnMgPSB7fTtcbiAgdmFyIGk7XG5cbiAgLy8gZ2V0IHBhcmFtcyBmcm9tIHRoZSBvcGVyYXRpb24gYW5kIHNldCB0aGVtIGluIGRlZmluZWRGaWxlUGFyYW1zLCBkZWZpbmVkRm9ybVBhcmFtcywgaGVhZGVyc1xuICBmb3IgKGkgPSAwOyBpIDwgYWxsRGVmaW5lZFBhcmFtcy5sZW5ndGg7IGkrKykge1xuICAgIHZhciBwYXJhbSA9IGFsbERlZmluZWRQYXJhbXNbaV07XG5cbiAgICBpZiAocGFyYW0uaW4gPT09ICdmb3JtRGF0YScpIHtcbiAgICAgIGlmIChwYXJhbS50eXBlID09PSAnZmlsZScpIHtcbiAgICAgICAgZGVmaW5lZEZpbGVQYXJhbXMucHVzaChwYXJhbSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBkZWZpbmVkRm9ybVBhcmFtcy5wdXNoKHBhcmFtKTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKHBhcmFtLmluID09PSAnaGVhZGVyJyAmJiBvcHRzKSB7XG4gICAgICB2YXIga2V5ID0gcGFyYW0ubmFtZTtcbiAgICAgIHZhciBoZWFkZXJWYWx1ZSA9IG9wdHNbcGFyYW0ubmFtZV07XG5cbiAgICAgIGlmICh0eXBlb2Ygb3B0c1twYXJhbS5uYW1lXSAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgaGVhZGVyc1trZXldID0gaGVhZGVyVmFsdWU7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmIChwYXJhbS5pbiA9PT0gJ2JvZHknICYmIHR5cGVvZiBhcmdzW3BhcmFtLm5hbWVdICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgYm9keSA9IGFyZ3NbcGFyYW0ubmFtZV07XG4gICAgfVxuICB9XG5cbiAgLy8gaWYgdGhlcmUncyBhIGJvZHksIG5lZWQgdG8gc2V0IHRoZSBjb25zdW1lcyBoZWFkZXIgdmlhIHJlcXVlc3RDb250ZW50VHlwZVxuICB2YXIgaGFzQm9keSA9IGJvZHkgfHwgZGVmaW5lZEZpbGVQYXJhbXMubGVuZ3RoIHx8IGRlZmluZWRGb3JtUGFyYW1zLmxlbmd0aDtcbiAgaWYgKHRoaXMubWV0aG9kID09PSAncG9zdCcgfHwgdGhpcy5tZXRob2QgPT09ICdwdXQnIHx8IHRoaXMubWV0aG9kID09PSAncGF0Y2gnIHx8XG4gICAgICAoKHRoaXMubWV0aG9kID09PSAnZGVsZXRlJyB8fCB0aGlzLm1ldGhvZCA9PT0gJ2dldCcpICYmIGhhc0JvZHkpKSB7XG4gICAgaWYgKG9wdHMucmVxdWVzdENvbnRlbnRUeXBlKSB7XG4gICAgICBjb25zdW1lcyA9IG9wdHMucmVxdWVzdENvbnRlbnRUeXBlO1xuICAgIH1cbiAgICAvLyBpZiBhbnkgZm9ybSBwYXJhbXMsIGNvbnRlbnQgdHlwZSBtdXN0IGJlIHNldFxuICAgIGlmIChkZWZpbmVkRm9ybVBhcmFtcy5sZW5ndGggPiAwKSB7XG4gICAgICBjb25zdW1lcyA9IHVuZGVmaW5lZDtcbiAgICAgIGlmIChvcHRzLnJlcXVlc3RDb250ZW50VHlwZSkgeyAgICAgICAgICAgICAvLyBvdmVycmlkZSBpZiBzZXRcbiAgICAgICAgY29uc3VtZXMgPSBvcHRzLnJlcXVlc3RDb250ZW50VHlwZTtcbiAgICAgIH0gZWxzZSBpZiAoZGVmaW5lZEZpbGVQYXJhbXMubGVuZ3RoID4gMCkgeyAvLyBpZiBhIGZpbGUsIG11c3QgYmUgbXVsdGlwYXJ0L2Zvcm0tZGF0YVxuICAgICAgICBjb25zdW1lcyA9ICdtdWx0aXBhcnQvZm9ybS1kYXRhJztcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGlmICh0aGlzLmNvbnN1bWVzICYmIHRoaXMuY29uc3VtZXMubGVuZ3RoID4gMCkge1xuICAgICAgICAgIC8vIHVzZSB0aGUgY29uc3VtZXMgc2V0dGluZ1xuICAgICAgICAgIGZvcih2YXIgYyBpbiB0aGlzLmNvbnN1bWVzKSB7XG4gICAgICAgICAgICB2YXIgY2hrID0gdGhpcy5jb25zdW1lc1tjXTtcbiAgICAgICAgICAgIGlmKGNoay5pbmRleE9mKCdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQnKSA9PT0gMCB8fCBjaGsuaW5kZXhPZignbXVsdGlwYXJ0L2Zvcm0tZGF0YScpID09PSAwKSB7XG4gICAgICAgICAgICAgIGNvbnN1bWVzID0gY2hrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYodHlwZW9mIGNvbnN1bWVzID09PSAndW5kZWZpbmVkJykge1xuICAgICAgICAvLyBkZWZhdWx0IHRvIHgtd3d3LWZyb20tdXJsZW5jb2RlZFxuICAgICAgICBjb25zdW1lcyA9ICdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQnO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBlbHNlIHtcbiAgICBjb25zdW1lcyA9IG51bGw7XG4gIH1cblxuICBpZiAoY29uc3VtZXMgJiYgdGhpcy5jb25zdW1lcykge1xuICAgIGlmICh0aGlzLmNvbnN1bWVzLmluZGV4T2YoY29uc3VtZXMpID09PSAtMSkge1xuICAgICAgaGVscGVycy5sb2coJ3NlcnZlciBkb2VzblxcJ3QgY29uc3VtZSAnICsgY29uc3VtZXMgKyAnLCB0cnkgJyArIEpTT04uc3RyaW5naWZ5KHRoaXMuY29uc3VtZXMpKTtcbiAgICB9XG4gIH1cblxuICBpZiAoIXRoaXMubWF0Y2hlc0FjY2VwdChhY2NlcHRzKSkge1xuICAgIGhlbHBlcnMubG9nKCdzZXJ2ZXIgY2FuXFwndCBwcm9kdWNlICcgKyBhY2NlcHRzKTtcbiAgfVxuXG4gIGlmICgoY29uc3VtZXMgJiYgYm9keSAhPT0gJycpIHx8IChjb25zdW1lcyA9PT0gJ2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCcpKSB7XG4gICAgaGVhZGVyc1snQ29udGVudC1UeXBlJ10gPSBjb25zdW1lcztcbiAgfVxuICBlbHNlIGlmKHRoaXMuY29uc3VtZXMgJiYgdGhpcy5jb25zdW1lcy5sZW5ndGggPiAwICYmIHRoaXMuY29uc3VtZXNbMF0gPT09ICdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQnKSB7XG4gICAgaGVhZGVyc1snQ29udGVudC1UeXBlJ10gPSB0aGlzLmNvbnN1bWVzWzBdO1xuICB9XG5cbiAgaWYgKGFjY2VwdHMpIHtcbiAgICBoZWFkZXJzLkFjY2VwdCA9IGFjY2VwdHM7XG4gIH1cblxuICByZXR1cm4gaGVhZGVycztcbn07XG5cbi8qKlxuICogUmV0dXJucyB0cnVlIGlmIHRoZSByZXF1ZXN0IGFjY2VwdHMgaGVhZGVyIG1hdGNoZXMgYW55dGhpbmcgaW4gdGhpcy5wcm9kdWNlcy5cbiAqICBJZiB0aGlzLnByb2R1Y2VzIGNvbnRhaW5zICogLyAqLCBpZ25vcmUgdGhlIGFjY2VwdCBoZWFkZXIuXG4gKiBAcGFyYW0ge3N0cmluZz19IGFjY2VwdHMgVGhlIGNsaWVudCByZXF1ZXN0IGFjY2VwdCBoZWFkZXIuXG4gKiBAcmV0dXJuIHtib29sZWFufVxuICovXG5PcGVyYXRpb24ucHJvdG90eXBlLm1hdGNoZXNBY2NlcHQgPSBmdW5jdGlvbihhY2NlcHRzKSB7XG4gIC8vIG5vIGFjY2VwdHMgb3IgcHJvZHVjZXMsIG5vIHByb2JsZW0hXG4gIGlmICghYWNjZXB0cyB8fCAhdGhpcy5wcm9kdWNlcykge1xuICAgIHJldHVybiB0cnVlO1xuICB9XG4gIHJldHVybiB0aGlzLnByb2R1Y2VzLmluZGV4T2YoYWNjZXB0cykgIT09IC0xIHx8IHRoaXMucHJvZHVjZXMuaW5kZXhPZignKi8qJykgIT09IC0xO1xufTtcblxuT3BlcmF0aW9uLnByb3RvdHlwZS5hc0N1cmwgPSBmdW5jdGlvbiAoYXJnczEsIGFyZ3MyKSB7XG4gIHZhciBvcHRzID0ge21vY2s6IHRydWV9O1xuICBpZiAodHlwZW9mIGFyZ3MyID09PSAnb2JqZWN0Jykge1xuICAgIGZvciAodmFyIGFyZ0tleSBpbiBhcmdzMikge1xuICAgICAgb3B0c1thcmdLZXldID0gYXJnczJbYXJnS2V5XTtcbiAgICB9XG4gIH1cbiAgdmFyIG9iaiA9IHRoaXMuZXhlY3V0ZShhcmdzMSwgb3B0cyk7XG5cbiAgdGhpcy5jbGllbnRBdXRob3JpemF0aW9ucy5hcHBseShvYmosIHRoaXMub3BlcmF0aW9uLnNlY3VyaXR5KTtcblxuICB2YXIgcmVzdWx0cyA9IFtdO1xuXG4gIHJlc3VsdHMucHVzaCgnLVggJyArIHRoaXMubWV0aG9kLnRvVXBwZXJDYXNlKCkpO1xuXG4gIGlmICh0eXBlb2Ygb2JqLmhlYWRlcnMgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgdmFyIGtleTtcblxuICAgIGZvciAoa2V5IGluIG9iai5oZWFkZXJzKSB7XG4gICAgICB2YXIgdmFsdWUgPSBvYmouaGVhZGVyc1trZXldO1xuICAgICAgaWYodHlwZW9mIHZhbHVlID09PSAnc3RyaW5nJyl7XG4gICAgICAgIHZhbHVlID0gdmFsdWUucmVwbGFjZSgvXFwnL2csICdcXFxcdTAwMjcnKTtcbiAgICAgIH1cbiAgICAgIHJlc3VsdHMucHVzaCgnLS1oZWFkZXIgXFwnJyArIGtleSArICc6ICcgKyB2YWx1ZSArICdcXCcnKTtcbiAgICB9XG4gIH1cbiAgdmFyIGlzRm9ybURhdGEgPSBmYWxzZTtcbiAgdmFyIGlzTXVsdGlwYXJ0ID0gZmFsc2U7XG5cbiAgdmFyIHR5cGUgPSBvYmouaGVhZGVyc1snQ29udGVudC1UeXBlJ107XG4gIGlmKHR5cGUgJiYgdHlwZS5pbmRleE9mKCdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQnKSA9PT0gMCkge1xuICAgIGlzRm9ybURhdGEgPSB0cnVlO1xuICB9XG4gIGVsc2UgaWYgKHR5cGUgJiYgdHlwZS5pbmRleE9mKCdtdWx0aXBhcnQvZm9ybS1kYXRhJykgPT09IDApIHtcbiAgICBpc0Zvcm1EYXRhID0gdHJ1ZTtcbiAgICBpc011bHRpcGFydCA9IHRydWU7XG4gIH1cblxuICBpZiAob2JqLmJvZHkpIHtcbiAgICB2YXIgYm9keTtcbiAgICBpZiAoXy5pc09iamVjdChvYmouYm9keSkpIHtcbiAgICAgIGlmKGlzTXVsdGlwYXJ0KSB7XG4gICAgICAgIGlzTXVsdGlwYXJ0ID0gdHJ1ZTtcbiAgICAgICAgLy8gYWRkIHRoZSBmb3JtIGRhdGFcbiAgICAgICAgZm9yKHZhciBpID0gMDsgaSA8IHRoaXMucGFyYW1ldGVycy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgIHZhciBwYXJhbWV0ZXIgPSB0aGlzLnBhcmFtZXRlcnNbaV07XG4gICAgICAgICAgaWYocGFyYW1ldGVyLmluID09PSAnZm9ybURhdGEnKSB7XG4gICAgICAgICAgICBpZiAoIWJvZHkpIHtcbiAgICAgICAgICAgICAgYm9keSA9ICcnO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgcGFyYW1WYWx1ZTtcbiAgICAgICAgICAgIGlmKHR5cGVvZiBGb3JtRGF0YSA9PT0gJ2Z1bmN0aW9uJyAmJiBvYmouYm9keSBpbnN0YW5jZW9mIEZvcm1EYXRhKSB7XG4gICAgICAgICAgICAgIHBhcmFtVmFsdWUgPSBvYmouYm9keS5nZXRBbGwocGFyYW1ldGVyLm5hbWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgIHBhcmFtVmFsdWUgPSBvYmouYm9keVtwYXJhbWV0ZXIubmFtZV07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAocGFyYW1WYWx1ZSkge1xuICAgICAgICAgICAgICBpZiAocGFyYW1ldGVyLnR5cGUgPT09ICdmaWxlJykge1xuICAgICAgICAgICAgICAgIGlmKHBhcmFtVmFsdWUubmFtZSkge1xuICAgICAgICAgICAgICAgICAgYm9keSArPSAnLUYgJyArIHBhcmFtZXRlci5uYW1lICsgJz1AXCInICsgcGFyYW1WYWx1ZS5uYW1lICsgJ1wiICc7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGlmIChBcnJheS5pc0FycmF5KHBhcmFtVmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgICBpZihwYXJhbWV0ZXIuY29sbGVjdGlvbkZvcm1hdCA9PT0gJ211bHRpJykge1xuICAgICAgICAgICAgICAgICAgICBmb3IodmFyIHYgaW4gcGFyYW1WYWx1ZSkge1xuICAgICAgICAgICAgICAgICAgICAgIGJvZHkgKz0gJy1GICcgKyB0aGlzLmVuY29kZVF1ZXJ5S2V5KHBhcmFtZXRlci5uYW1lKSArICc9JyArIHBhcmFtVmFsdWVbdl0gKyAnICc7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBib2R5ICs9ICctRiAnICsgdGhpcy5lbmNvZGVRdWVyeUNvbGxlY3Rpb24ocGFyYW1ldGVyLmNvbGxlY3Rpb25Gb3JtYXQsIHBhcmFtZXRlci5uYW1lLCBwYXJhbVZhbHVlKSArICcgJztcbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgYm9keSArPSAnLUYgJyArIHRoaXMuZW5jb2RlUXVlcnlLZXkocGFyYW1ldGVyLm5hbWUpICsgJz0nICsgcGFyYW1WYWx1ZSArICcgJztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmKCFib2R5KSB7XG4gICAgICAgIGJvZHkgPSBKU09OLnN0cmluZ2lmeShvYmouYm9keSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGJvZHkgPSBvYmouYm9keTtcbiAgICB9XG4gICAgLy8gZXNjYXBlIEAgPT4gJTQwLCAnID0+ICUyN1xuICAgIGJvZHkgPSBib2R5LnJlcGxhY2UoL1xcJy9nLCAnJTI3JykucmVwbGFjZSgvXFxuL2csICcgXFxcXCBcXG4gJyk7XG5cbiAgICBpZighaXNGb3JtRGF0YSkge1xuICAgICAgLy8gZXNjYXBlICYgPT4gJTI2XG4gICAgICBib2R5ID0gYm9keS5yZXBsYWNlKC8mL2csICclMjYnKTtcbiAgICB9XG4gICAgaWYoaXNNdWx0aXBhcnQpIHtcbiAgICAgIHJlc3VsdHMucHVzaChib2R5KTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICByZXN1bHRzLnB1c2goJy1kIFxcJycgKyBib2R5LnJlcGxhY2UoL0AvZywgJyU0MCcpICsgJ1xcJycpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiAnY3VybCAnICsgKHJlc3VsdHMuam9pbignICcpKSArICcgXFwnJyArIG9iai51cmwgKyAnXFwnJztcbn07XG5cbk9wZXJhdGlvbi5wcm90b3R5cGUuZW5jb2RlUGF0aENvbGxlY3Rpb24gPSBmdW5jdGlvbiAodHlwZSwgbmFtZSwgdmFsdWUpIHtcbiAgdmFyIGVuY29kZWQgPSAnJztcbiAgdmFyIGk7XG4gIHZhciBzZXBhcmF0b3IgPSAnJztcblxuICBpZiAodHlwZSA9PT0gJ3NzdicpIHtcbiAgICBzZXBhcmF0b3IgPSAnJTIwJztcbiAgfSBlbHNlIGlmICh0eXBlID09PSAndHN2Jykge1xuICAgIHNlcGFyYXRvciA9ICclMDknO1xuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdwaXBlcycpIHtcbiAgICBzZXBhcmF0b3IgPSAnfCc7XG4gIH0gZWxzZSB7XG4gICAgc2VwYXJhdG9yID0gJywnO1xuICB9XG5cbiAgZm9yIChpID0gMDsgaSA8IHZhbHVlLmxlbmd0aDsgaSsrKSB7XG4gICAgaWYgKGkgPT09IDApIHtcbiAgICAgIGVuY29kZWQgPSB0aGlzLmVuY29kZVF1ZXJ5UGFyYW0odmFsdWVbaV0pO1xuICAgIH0gZWxzZSB7XG4gICAgICBlbmNvZGVkICs9IHNlcGFyYXRvciArIHRoaXMuZW5jb2RlUXVlcnlQYXJhbSh2YWx1ZVtpXSk7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIGVuY29kZWQ7XG59O1xuXG5PcGVyYXRpb24ucHJvdG90eXBlLmVuY29kZVF1ZXJ5Q29sbGVjdGlvbiA9IGZ1bmN0aW9uICh0eXBlLCBuYW1lLCB2YWx1ZSkge1xuICB2YXIgZW5jb2RlZCA9ICcnO1xuICB2YXIgaTtcblxuICB0eXBlID0gdHlwZSB8fCAnZGVmYXVsdCc7XG4gIGlmICh0eXBlID09PSAnZGVmYXVsdCcgfHwgdHlwZSA9PT0gJ211bHRpJykge1xuICAgIGZvciAoaSA9IDA7IGkgPCB2YWx1ZS5sZW5ndGg7IGkrKykge1xuICAgICAgaWYgKGkgPiAwKSB7ZW5jb2RlZCArPSAnJic7fVxuXG4gICAgICBlbmNvZGVkICs9IHRoaXMuZW5jb2RlUXVlcnlLZXkobmFtZSkgKyAnPScgKyB0aGlzLmVuY29kZVF1ZXJ5UGFyYW0odmFsdWVbaV0pO1xuICAgIH1cbiAgfSBlbHNlIHtcbiAgICB2YXIgc2VwYXJhdG9yID0gJyc7XG5cbiAgICBpZiAodHlwZSA9PT0gJ2NzdicpIHtcbiAgICAgIHNlcGFyYXRvciA9ICcsJztcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdzc3YnKSB7XG4gICAgICBzZXBhcmF0b3IgPSAnJTIwJztcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICd0c3YnKSB7XG4gICAgICBzZXBhcmF0b3IgPSAnJTA5JztcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdwaXBlcycpIHtcbiAgICAgIHNlcGFyYXRvciA9ICd8JztcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdicmFja2V0cycpIHtcbiAgICAgIGZvciAoaSA9IDA7IGkgPCB2YWx1ZS5sZW5ndGg7IGkrKykge1xuICAgICAgICBpZiAoaSAhPT0gMCkge1xuICAgICAgICAgIGVuY29kZWQgKz0gJyYnO1xuICAgICAgICB9XG5cbiAgICAgICAgZW5jb2RlZCArPSB0aGlzLmVuY29kZVF1ZXJ5S2V5KG5hbWUpICsgJ1tdPScgKyB0aGlzLmVuY29kZVF1ZXJ5UGFyYW0odmFsdWVbaV0pO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChzZXBhcmF0b3IgIT09ICcnKSB7XG4gICAgICBmb3IgKGkgPSAwOyBpIDwgdmFsdWUubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgaWYgKGkgPT09IDApIHtcbiAgICAgICAgICBlbmNvZGVkID0gdGhpcy5lbmNvZGVRdWVyeUtleShuYW1lKSArICc9JyArIHRoaXMuZW5jb2RlUXVlcnlQYXJhbSh2YWx1ZVtpXSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgZW5jb2RlZCArPSBzZXBhcmF0b3IgKyB0aGlzLmVuY29kZVF1ZXJ5UGFyYW0odmFsdWVbaV0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIGVuY29kZWQ7XG59O1xuXG5PcGVyYXRpb24ucHJvdG90eXBlLmVuY29kZVF1ZXJ5S2V5ID0gZnVuY3Rpb24gKGFyZykge1xuICByZXR1cm4gZW5jb2RlVVJJQ29tcG9uZW50KGFyZylcbiAgICAgIC5yZXBsYWNlKCclNUInLCdbJykucmVwbGFjZSgnJTVEJywgJ10nKS5yZXBsYWNlKCclMjQnLCAnJCcpO1xufTtcblxuT3BlcmF0aW9uLnByb3RvdHlwZS5lbmNvZGVRdWVyeVBhcmFtID0gZnVuY3Rpb24gKGFyZykge1xuICByZXR1cm4gZW5jb2RlVVJJQ29tcG9uZW50KGFyZyk7XG59O1xuXG4vKipcbiAqIFRPRE8gcmV2aXNpdCwgbWlnaHQgbm90IHdhbnQgdG8gbGVhdmUgJy8nXG4gKiovXG5PcGVyYXRpb24ucHJvdG90eXBlLmVuY29kZVBhdGhQYXJhbSA9IGZ1bmN0aW9uIChwYXRoUGFyYW0pIHtcbiAgcmV0dXJuIGVuY29kZVVSSUNvbXBvbmVudChwYXRoUGFyYW0pO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIE9wZXJhdGlvbkdyb3VwID0gbW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAodGFnLCBkZXNjcmlwdGlvbiwgZXh0ZXJuYWxEb2NzLCBvcGVyYXRpb24pIHtcbiAgdGhpcy5kZXNjcmlwdGlvbiA9IGRlc2NyaXB0aW9uO1xuICB0aGlzLmV4dGVybmFsRG9jcyA9IGV4dGVybmFsRG9jcztcbiAgdGhpcy5uYW1lID0gdGFnO1xuICB0aGlzLm9wZXJhdGlvbiA9IG9wZXJhdGlvbjtcbiAgdGhpcy5vcGVyYXRpb25zQXJyYXkgPSBbXTtcbiAgdGhpcy5wYXRoID0gdGFnO1xuICB0aGlzLnRhZyA9IHRhZztcbn07XG5cbk9wZXJhdGlvbkdyb3VwLnByb3RvdHlwZS5zb3J0ID0gZnVuY3Rpb24gKCkge1xuXG59O1xuXG4iLCIvLyBzaGltIGZvciB1c2luZyBwcm9jZXNzIGluIGJyb3dzZXJcblxudmFyIHByb2Nlc3MgPSBtb2R1bGUuZXhwb3J0cyA9IHt9O1xudmFyIHF1ZXVlID0gW107XG52YXIgZHJhaW5pbmcgPSBmYWxzZTtcblxuZnVuY3Rpb24gZHJhaW5RdWV1ZSgpIHtcbiAgICBpZiAoZHJhaW5pbmcpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBkcmFpbmluZyA9IHRydWU7XG4gICAgdmFyIGN1cnJlbnRRdWV1ZTtcbiAgICB2YXIgbGVuID0gcXVldWUubGVuZ3RoO1xuICAgIHdoaWxlKGxlbikge1xuICAgICAgICBjdXJyZW50UXVldWUgPSBxdWV1ZTtcbiAgICAgICAgcXVldWUgPSBbXTtcbiAgICAgICAgdmFyIGkgPSAtMTtcbiAgICAgICAgd2hpbGUgKCsraSA8IGxlbikge1xuICAgICAgICAgICAgY3VycmVudFF1ZXVlW2ldKCk7XG4gICAgICAgIH1cbiAgICAgICAgbGVuID0gcXVldWUubGVuZ3RoO1xuICAgIH1cbiAgICBkcmFpbmluZyA9IGZhbHNlO1xufVxucHJvY2Vzcy5uZXh0VGljayA9IGZ1bmN0aW9uIChmdW4pIHtcbiAgICBxdWV1ZS5wdXNoKGZ1bik7XG4gICAgaWYgKCFkcmFpbmluZykge1xuICAgICAgICBzZXRUaW1lb3V0KGRyYWluUXVldWUsIDApO1xuICAgIH1cbn07XG5cbnByb2Nlc3MudGl0bGUgPSAnYnJvd3Nlcic7XG5wcm9jZXNzLmJyb3dzZXIgPSB0cnVlO1xucHJvY2Vzcy5lbnYgPSB7fTtcbnByb2Nlc3MuYXJndiA9IFtdO1xucHJvY2Vzcy52ZXJzaW9uID0gJyc7IC8vIGVtcHR5IHN0cmluZyB0byBhdm9pZCByZWdleHAgaXNzdWVzXG5wcm9jZXNzLnZlcnNpb25zID0ge307XG5cbmZ1bmN0aW9uIG5vb3AoKSB7fVxuXG5wcm9jZXNzLm9uID0gbm9vcDtcbnByb2Nlc3MuYWRkTGlzdGVuZXIgPSBub29wO1xucHJvY2Vzcy5vbmNlID0gbm9vcDtcbnByb2Nlc3Mub2ZmID0gbm9vcDtcbnByb2Nlc3MucmVtb3ZlTGlzdGVuZXIgPSBub29wO1xucHJvY2Vzcy5yZW1vdmVBbGxMaXN0ZW5lcnMgPSBub29wO1xucHJvY2Vzcy5lbWl0ID0gbm9vcDtcblxucHJvY2Vzcy5iaW5kaW5nID0gZnVuY3Rpb24gKG5hbWUpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ3Byb2Nlc3MuYmluZGluZyBpcyBub3Qgc3VwcG9ydGVkJyk7XG59O1xuXG4vLyBUT0RPKHNodHlsbWFuKVxucHJvY2Vzcy5jd2QgPSBmdW5jdGlvbiAoKSB7IHJldHVybiAnLycgfTtcbnByb2Nlc3MuY2hkaXIgPSBmdW5jdGlvbiAoZGlyKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdwcm9jZXNzLmNoZGlyIGlzIG5vdCBzdXBwb3J0ZWQnKTtcbn07XG5wcm9jZXNzLnVtYXNrID0gZnVuY3Rpb24oKSB7IHJldHVybiAwOyB9O1xuIiwiKGZ1bmN0aW9uIChCdWZmZXIpe1xuKGZ1bmN0aW9uICgpIHtcbiAgXCJ1c2Ugc3RyaWN0XCI7XG5cbiAgZnVuY3Rpb24gYnRvYShzdHIpIHtcbiAgICB2YXIgYnVmZmVyXG4gICAgICA7XG5cbiAgICBpZiAoc3RyIGluc3RhbmNlb2YgQnVmZmVyKSB7XG4gICAgICBidWZmZXIgPSBzdHI7XG4gICAgfSBlbHNlIHtcbiAgICAgIGJ1ZmZlciA9IG5ldyBCdWZmZXIoc3RyLnRvU3RyaW5nKCksICdiaW5hcnknKTtcbiAgICB9XG5cbiAgICByZXR1cm4gYnVmZmVyLnRvU3RyaW5nKCdiYXNlNjQnKTtcbiAgfVxuXG4gIG1vZHVsZS5leHBvcnRzID0gYnRvYTtcbn0oKSk7XG5cbn0pLmNhbGwodGhpcyxyZXF1aXJlKFwiYnVmZmVyXCIpLkJ1ZmZlcilcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPWRhdGE6YXBwbGljYXRpb24vanNvbjtjaGFyc2V0OnV0Zi04O2Jhc2U2NCxleUoyWlhKemFXOXVJam96TENKemIzVnlZMlZ6SWpwYkltNXZaR1ZmYlc5a2RXeGxjeTlpZEc5aEwybHVaR1Y0TG1weklsMHNJbTVoYldWeklqcGJYU3dpYldGd2NHbHVaM01pT2lJN1FVRkJRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRU0lzSW1acGJHVWlPaUpuWlc1bGNtRjBaV1F1YW5NaUxDSnpiM1Z5WTJWU2IyOTBJam9pSWl3aWMyOTFjbU5sYzBOdmJuUmxiblFpT2xzaUtHWjFibU4wYVc5dUlDZ3BJSHRjYmlBZ1hDSjFjMlVnYzNSeWFXTjBYQ0k3WEc1Y2JpQWdablZ1WTNScGIyNGdZblJ2WVNoemRISXBJSHRjYmlBZ0lDQjJZWElnWW5WbVptVnlYRzRnSUNBZ0lDQTdYRzVjYmlBZ0lDQnBaaUFvYzNSeUlHbHVjM1JoYm1ObGIyWWdRblZtWm1WeUtTQjdYRzRnSUNBZ0lDQmlkV1ptWlhJZ1BTQnpkSEk3WEc0Z0lDQWdmU0JsYkhObElIdGNiaUFnSUNBZ0lHSjFabVpsY2lBOUlHNWxkeUJDZFdabVpYSW9jM1J5TG5SdlUzUnlhVzVuS0Nrc0lDZGlhVzVoY25rbktUdGNiaUFnSUNCOVhHNWNiaUFnSUNCeVpYUjFjbTRnWW5WbVptVnlMblJ2VTNSeWFXNW5LQ2RpWVhObE5qUW5LVHRjYmlBZ2ZWeHVYRzRnSUcxdlpIVnNaUzVsZUhCdmNuUnpJRDBnWW5SdllUdGNibjBvS1NrN1hHNGlYWDA9IiwiLyohXG4gKiBUaGUgYnVmZmVyIG1vZHVsZSBmcm9tIG5vZGUuanMsIGZvciB0aGUgYnJvd3Nlci5cbiAqXG4gKiBAYXV0aG9yICAgRmVyb3NzIEFib3VraGFkaWplaCA8ZmVyb3NzQGZlcm9zcy5vcmc+IDxodHRwOi8vZmVyb3NzLm9yZz5cbiAqIEBsaWNlbnNlICBNSVRcbiAqL1xuXG52YXIgYmFzZTY0ID0gcmVxdWlyZSgnYmFzZTY0LWpzJylcbnZhciBpZWVlNzU0ID0gcmVxdWlyZSgnaWVlZTc1NCcpXG52YXIgaXNBcnJheSA9IHJlcXVpcmUoJ2lzLWFycmF5JylcblxuZXhwb3J0cy5CdWZmZXIgPSBCdWZmZXJcbmV4cG9ydHMuU2xvd0J1ZmZlciA9IFNsb3dCdWZmZXJcbmV4cG9ydHMuSU5TUEVDVF9NQVhfQllURVMgPSA1MFxuQnVmZmVyLnBvb2xTaXplID0gODE5MiAvLyBub3QgdXNlZCBieSB0aGlzIGltcGxlbWVudGF0aW9uXG5cbnZhciByb290UGFyZW50ID0ge31cblxuLyoqXG4gKiBJZiBgQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlRgOlxuICogICA9PT0gdHJ1ZSAgICBVc2UgVWludDhBcnJheSBpbXBsZW1lbnRhdGlvbiAoZmFzdGVzdClcbiAqICAgPT09IGZhbHNlICAgVXNlIE9iamVjdCBpbXBsZW1lbnRhdGlvbiAobW9zdCBjb21wYXRpYmxlLCBldmVuIElFNilcbiAqXG4gKiBCcm93c2VycyB0aGF0IHN1cHBvcnQgdHlwZWQgYXJyYXlzIGFyZSBJRSAxMCssIEZpcmVmb3ggNCssIENocm9tZSA3KywgU2FmYXJpIDUuMSssXG4gKiBPcGVyYSAxMS42KywgaU9TIDQuMisuXG4gKlxuICogRHVlIHRvIHZhcmlvdXMgYnJvd3NlciBidWdzLCBzb21ldGltZXMgdGhlIE9iamVjdCBpbXBsZW1lbnRhdGlvbiB3aWxsIGJlIHVzZWQgZXZlblxuICogd2hlbiB0aGUgYnJvd3NlciBzdXBwb3J0cyB0eXBlZCBhcnJheXMuXG4gKlxuICogTm90ZTpcbiAqXG4gKiAgIC0gRmlyZWZveCA0LTI5IGxhY2tzIHN1cHBvcnQgZm9yIGFkZGluZyBuZXcgcHJvcGVydGllcyB0byBgVWludDhBcnJheWAgaW5zdGFuY2VzLFxuICogICAgIFNlZTogaHR0cHM6Ly9idWd6aWxsYS5tb3ppbGxhLm9yZy9zaG93X2J1Zy5jZ2k/aWQ9Njk1NDM4LlxuICpcbiAqICAgLSBTYWZhcmkgNS03IGxhY2tzIHN1cHBvcnQgZm9yIGNoYW5naW5nIHRoZSBgT2JqZWN0LnByb3RvdHlwZS5jb25zdHJ1Y3RvcmAgcHJvcGVydHlcbiAqICAgICBvbiBvYmplY3RzLlxuICpcbiAqICAgLSBDaHJvbWUgOS0xMCBpcyBtaXNzaW5nIHRoZSBgVHlwZWRBcnJheS5wcm90b3R5cGUuc3ViYXJyYXlgIGZ1bmN0aW9uLlxuICpcbiAqICAgLSBJRTEwIGhhcyBhIGJyb2tlbiBgVHlwZWRBcnJheS5wcm90b3R5cGUuc3ViYXJyYXlgIGZ1bmN0aW9uIHdoaWNoIHJldHVybnMgYXJyYXlzIG9mXG4gKiAgICAgaW5jb3JyZWN0IGxlbmd0aCBpbiBzb21lIHNpdHVhdGlvbnMuXG5cbiAqIFdlIGRldGVjdCB0aGVzZSBidWdneSBicm93c2VycyBhbmQgc2V0IGBCdWZmZXIuVFlQRURfQVJSQVlfU1VQUE9SVGAgdG8gYGZhbHNlYCBzbyB0aGV5XG4gKiBnZXQgdGhlIE9iamVjdCBpbXBsZW1lbnRhdGlvbiwgd2hpY2ggaXMgc2xvd2VyIGJ1dCBiZWhhdmVzIGNvcnJlY3RseS5cbiAqL1xuQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlQgPSAoZnVuY3Rpb24gKCkge1xuICBmdW5jdGlvbiBCYXIgKCkge31cbiAgdHJ5IHtcbiAgICB2YXIgYXJyID0gbmV3IFVpbnQ4QXJyYXkoMSlcbiAgICBhcnIuZm9vID0gZnVuY3Rpb24gKCkgeyByZXR1cm4gNDIgfVxuICAgIGFyci5jb25zdHJ1Y3RvciA9IEJhclxuICAgIHJldHVybiBhcnIuZm9vKCkgPT09IDQyICYmIC8vIHR5cGVkIGFycmF5IGluc3RhbmNlcyBjYW4gYmUgYXVnbWVudGVkXG4gICAgICAgIGFyci5jb25zdHJ1Y3RvciA9PT0gQmFyICYmIC8vIGNvbnN0cnVjdG9yIGNhbiBiZSBzZXRcbiAgICAgICAgdHlwZW9mIGFyci5zdWJhcnJheSA9PT0gJ2Z1bmN0aW9uJyAmJiAvLyBjaHJvbWUgOS0xMCBsYWNrIGBzdWJhcnJheWBcbiAgICAgICAgYXJyLnN1YmFycmF5KDEsIDEpLmJ5dGVMZW5ndGggPT09IDAgLy8gaWUxMCBoYXMgYnJva2VuIGBzdWJhcnJheWBcbiAgfSBjYXRjaCAoZSkge1xuICAgIHJldHVybiBmYWxzZVxuICB9XG59KSgpXG5cbmZ1bmN0aW9uIGtNYXhMZW5ndGggKCkge1xuICByZXR1cm4gQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlRcbiAgICA/IDB4N2ZmZmZmZmZcbiAgICA6IDB4M2ZmZmZmZmZcbn1cblxuLyoqXG4gKiBDbGFzczogQnVmZmVyXG4gKiA9PT09PT09PT09PT09XG4gKlxuICogVGhlIEJ1ZmZlciBjb25zdHJ1Y3RvciByZXR1cm5zIGluc3RhbmNlcyBvZiBgVWludDhBcnJheWAgdGhhdCBhcmUgYXVnbWVudGVkXG4gKiB3aXRoIGZ1bmN0aW9uIHByb3BlcnRpZXMgZm9yIGFsbCB0aGUgbm9kZSBgQnVmZmVyYCBBUEkgZnVuY3Rpb25zLiBXZSB1c2VcbiAqIGBVaW50OEFycmF5YCBzbyB0aGF0IHNxdWFyZSBicmFja2V0IG5vdGF0aW9uIHdvcmtzIGFzIGV4cGVjdGVkIC0tIGl0IHJldHVybnNcbiAqIGEgc2luZ2xlIG9jdGV0LlxuICpcbiAqIEJ5IGF1Z21lbnRpbmcgdGhlIGluc3RhbmNlcywgd2UgY2FuIGF2b2lkIG1vZGlmeWluZyB0aGUgYFVpbnQ4QXJyYXlgXG4gKiBwcm90b3R5cGUuXG4gKi9cbmZ1bmN0aW9uIEJ1ZmZlciAoYXJnKSB7XG4gIGlmICghKHRoaXMgaW5zdGFuY2VvZiBCdWZmZXIpKSB7XG4gICAgLy8gQXZvaWQgZ29pbmcgdGhyb3VnaCBhbiBBcmd1bWVudHNBZGFwdG9yVHJhbXBvbGluZSBpbiB0aGUgY29tbW9uIGNhc2UuXG4gICAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPiAxKSByZXR1cm4gbmV3IEJ1ZmZlcihhcmcsIGFyZ3VtZW50c1sxXSlcbiAgICByZXR1cm4gbmV3IEJ1ZmZlcihhcmcpXG4gIH1cblxuICB0aGlzLmxlbmd0aCA9IDBcbiAgdGhpcy5wYXJlbnQgPSB1bmRlZmluZWRcblxuICAvLyBDb21tb24gY2FzZS5cbiAgaWYgKHR5cGVvZiBhcmcgPT09ICdudW1iZXInKSB7XG4gICAgcmV0dXJuIGZyb21OdW1iZXIodGhpcywgYXJnKVxuICB9XG5cbiAgLy8gU2xpZ2h0bHkgbGVzcyBjb21tb24gY2FzZS5cbiAgaWYgKHR5cGVvZiBhcmcgPT09ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIGZyb21TdHJpbmcodGhpcywgYXJnLCBhcmd1bWVudHMubGVuZ3RoID4gMSA/IGFyZ3VtZW50c1sxXSA6ICd1dGY4JylcbiAgfVxuXG4gIC8vIFVudXN1YWwuXG4gIHJldHVybiBmcm9tT2JqZWN0KHRoaXMsIGFyZylcbn1cblxuZnVuY3Rpb24gZnJvbU51bWJlciAodGhhdCwgbGVuZ3RoKSB7XG4gIHRoYXQgPSBhbGxvY2F0ZSh0aGF0LCBsZW5ndGggPCAwID8gMCA6IGNoZWNrZWQobGVuZ3RoKSB8IDApXG4gIGlmICghQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlQpIHtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICB0aGF0W2ldID0gMFxuICAgIH1cbiAgfVxuICByZXR1cm4gdGhhdFxufVxuXG5mdW5jdGlvbiBmcm9tU3RyaW5nICh0aGF0LCBzdHJpbmcsIGVuY29kaW5nKSB7XG4gIGlmICh0eXBlb2YgZW5jb2RpbmcgIT09ICdzdHJpbmcnIHx8IGVuY29kaW5nID09PSAnJykgZW5jb2RpbmcgPSAndXRmOCdcblxuICAvLyBBc3N1bXB0aW9uOiBieXRlTGVuZ3RoKCkgcmV0dXJuIHZhbHVlIGlzIGFsd2F5cyA8IGtNYXhMZW5ndGguXG4gIHZhciBsZW5ndGggPSBieXRlTGVuZ3RoKHN0cmluZywgZW5jb2RpbmcpIHwgMFxuICB0aGF0ID0gYWxsb2NhdGUodGhhdCwgbGVuZ3RoKVxuXG4gIHRoYXQud3JpdGUoc3RyaW5nLCBlbmNvZGluZylcbiAgcmV0dXJuIHRoYXRcbn1cblxuZnVuY3Rpb24gZnJvbU9iamVjdCAodGhhdCwgb2JqZWN0KSB7XG4gIGlmIChCdWZmZXIuaXNCdWZmZXIob2JqZWN0KSkgcmV0dXJuIGZyb21CdWZmZXIodGhhdCwgb2JqZWN0KVxuXG4gIGlmIChpc0FycmF5KG9iamVjdCkpIHJldHVybiBmcm9tQXJyYXkodGhhdCwgb2JqZWN0KVxuXG4gIGlmIChvYmplY3QgPT0gbnVsbCkge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ211c3Qgc3RhcnQgd2l0aCBudW1iZXIsIGJ1ZmZlciwgYXJyYXkgb3Igc3RyaW5nJylcbiAgfVxuXG4gIGlmICh0eXBlb2YgQXJyYXlCdWZmZXIgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgaWYgKG9iamVjdC5idWZmZXIgaW5zdGFuY2VvZiBBcnJheUJ1ZmZlcikge1xuICAgICAgcmV0dXJuIGZyb21UeXBlZEFycmF5KHRoYXQsIG9iamVjdClcbiAgICB9XG4gICAgaWYgKG9iamVjdCBpbnN0YW5jZW9mIEFycmF5QnVmZmVyKSB7XG4gICAgICByZXR1cm4gZnJvbUFycmF5QnVmZmVyKHRoYXQsIG9iamVjdClcbiAgICB9XG4gIH1cblxuICBpZiAob2JqZWN0Lmxlbmd0aCkgcmV0dXJuIGZyb21BcnJheUxpa2UodGhhdCwgb2JqZWN0KVxuXG4gIHJldHVybiBmcm9tSnNvbk9iamVjdCh0aGF0LCBvYmplY3QpXG59XG5cbmZ1bmN0aW9uIGZyb21CdWZmZXIgKHRoYXQsIGJ1ZmZlcikge1xuICB2YXIgbGVuZ3RoID0gY2hlY2tlZChidWZmZXIubGVuZ3RoKSB8IDBcbiAgdGhhdCA9IGFsbG9jYXRlKHRoYXQsIGxlbmd0aClcbiAgYnVmZmVyLmNvcHkodGhhdCwgMCwgMCwgbGVuZ3RoKVxuICByZXR1cm4gdGhhdFxufVxuXG5mdW5jdGlvbiBmcm9tQXJyYXkgKHRoYXQsIGFycmF5KSB7XG4gIHZhciBsZW5ndGggPSBjaGVja2VkKGFycmF5Lmxlbmd0aCkgfCAwXG4gIHRoYXQgPSBhbGxvY2F0ZSh0aGF0LCBsZW5ndGgpXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbGVuZ3RoOyBpICs9IDEpIHtcbiAgICB0aGF0W2ldID0gYXJyYXlbaV0gJiAyNTVcbiAgfVxuICByZXR1cm4gdGhhdFxufVxuXG4vLyBEdXBsaWNhdGUgb2YgZnJvbUFycmF5KCkgdG8ga2VlcCBmcm9tQXJyYXkoKSBtb25vbW9ycGhpYy5cbmZ1bmN0aW9uIGZyb21UeXBlZEFycmF5ICh0aGF0LCBhcnJheSkge1xuICB2YXIgbGVuZ3RoID0gY2hlY2tlZChhcnJheS5sZW5ndGgpIHwgMFxuICB0aGF0ID0gYWxsb2NhdGUodGhhdCwgbGVuZ3RoKVxuICAvLyBUcnVuY2F0aW5nIHRoZSBlbGVtZW50cyBpcyBwcm9iYWJseSBub3Qgd2hhdCBwZW9wbGUgZXhwZWN0IGZyb20gdHlwZWRcbiAgLy8gYXJyYXlzIHdpdGggQllURVNfUEVSX0VMRU1FTlQgPiAxIGJ1dCBpdCdzIGNvbXBhdGlibGUgd2l0aCB0aGUgYmVoYXZpb3JcbiAgLy8gb2YgdGhlIG9sZCBCdWZmZXIgY29uc3RydWN0b3IuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbGVuZ3RoOyBpICs9IDEpIHtcbiAgICB0aGF0W2ldID0gYXJyYXlbaV0gJiAyNTVcbiAgfVxuICByZXR1cm4gdGhhdFxufVxuXG5mdW5jdGlvbiBmcm9tQXJyYXlCdWZmZXIgKHRoYXQsIGFycmF5KSB7XG4gIGlmIChCdWZmZXIuVFlQRURfQVJSQVlfU1VQUE9SVCkge1xuICAgIC8vIFJldHVybiBhbiBhdWdtZW50ZWQgYFVpbnQ4QXJyYXlgIGluc3RhbmNlLCBmb3IgYmVzdCBwZXJmb3JtYW5jZVxuICAgIGFycmF5LmJ5dGVMZW5ndGhcbiAgICB0aGF0ID0gQnVmZmVyLl9hdWdtZW50KG5ldyBVaW50OEFycmF5KGFycmF5KSlcbiAgfSBlbHNlIHtcbiAgICAvLyBGYWxsYmFjazogUmV0dXJuIGFuIG9iamVjdCBpbnN0YW5jZSBvZiB0aGUgQnVmZmVyIGNsYXNzXG4gICAgdGhhdCA9IGZyb21UeXBlZEFycmF5KHRoYXQsIG5ldyBVaW50OEFycmF5KGFycmF5KSlcbiAgfVxuICByZXR1cm4gdGhhdFxufVxuXG5mdW5jdGlvbiBmcm9tQXJyYXlMaWtlICh0aGF0LCBhcnJheSkge1xuICB2YXIgbGVuZ3RoID0gY2hlY2tlZChhcnJheS5sZW5ndGgpIHwgMFxuICB0aGF0ID0gYWxsb2NhdGUodGhhdCwgbGVuZ3RoKVxuICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbmd0aDsgaSArPSAxKSB7XG4gICAgdGhhdFtpXSA9IGFycmF5W2ldICYgMjU1XG4gIH1cbiAgcmV0dXJuIHRoYXRcbn1cblxuLy8gRGVzZXJpYWxpemUgeyB0eXBlOiAnQnVmZmVyJywgZGF0YTogWzEsMiwzLC4uLl0gfSBpbnRvIGEgQnVmZmVyIG9iamVjdC5cbi8vIFJldHVybnMgYSB6ZXJvLWxlbmd0aCBidWZmZXIgZm9yIGlucHV0cyB0aGF0IGRvbid0IGNvbmZvcm0gdG8gdGhlIHNwZWMuXG5mdW5jdGlvbiBmcm9tSnNvbk9iamVjdCAodGhhdCwgb2JqZWN0KSB7XG4gIHZhciBhcnJheVxuICB2YXIgbGVuZ3RoID0gMFxuXG4gIGlmIChvYmplY3QudHlwZSA9PT0gJ0J1ZmZlcicgJiYgaXNBcnJheShvYmplY3QuZGF0YSkpIHtcbiAgICBhcnJheSA9IG9iamVjdC5kYXRhXG4gICAgbGVuZ3RoID0gY2hlY2tlZChhcnJheS5sZW5ndGgpIHwgMFxuICB9XG4gIHRoYXQgPSBhbGxvY2F0ZSh0aGF0LCBsZW5ndGgpXG5cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBsZW5ndGg7IGkgKz0gMSkge1xuICAgIHRoYXRbaV0gPSBhcnJheVtpXSAmIDI1NVxuICB9XG4gIHJldHVybiB0aGF0XG59XG5cbmZ1bmN0aW9uIGFsbG9jYXRlICh0aGF0LCBsZW5ndGgpIHtcbiAgaWYgKEJ1ZmZlci5UWVBFRF9BUlJBWV9TVVBQT1JUKSB7XG4gICAgLy8gUmV0dXJuIGFuIGF1Z21lbnRlZCBgVWludDhBcnJheWAgaW5zdGFuY2UsIGZvciBiZXN0IHBlcmZvcm1hbmNlXG4gICAgdGhhdCA9IEJ1ZmZlci5fYXVnbWVudChuZXcgVWludDhBcnJheShsZW5ndGgpKVxuICB9IGVsc2Uge1xuICAgIC8vIEZhbGxiYWNrOiBSZXR1cm4gYW4gb2JqZWN0IGluc3RhbmNlIG9mIHRoZSBCdWZmZXIgY2xhc3NcbiAgICB0aGF0Lmxlbmd0aCA9IGxlbmd0aFxuICAgIHRoYXQuX2lzQnVmZmVyID0gdHJ1ZVxuICB9XG5cbiAgdmFyIGZyb21Qb29sID0gbGVuZ3RoICE9PSAwICYmIGxlbmd0aCA8PSBCdWZmZXIucG9vbFNpemUgPj4+IDFcbiAgaWYgKGZyb21Qb29sKSB0aGF0LnBhcmVudCA9IHJvb3RQYXJlbnRcblxuICByZXR1cm4gdGhhdFxufVxuXG5mdW5jdGlvbiBjaGVja2VkIChsZW5ndGgpIHtcbiAgLy8gTm90ZTogY2Fubm90IHVzZSBgbGVuZ3RoIDwga01heExlbmd0aGAgaGVyZSBiZWNhdXNlIHRoYXQgZmFpbHMgd2hlblxuICAvLyBsZW5ndGggaXMgTmFOICh3aGljaCBpcyBvdGhlcndpc2UgY29lcmNlZCB0byB6ZXJvLilcbiAgaWYgKGxlbmd0aCA+PSBrTWF4TGVuZ3RoKCkpIHtcbiAgICB0aHJvdyBuZXcgUmFuZ2VFcnJvcignQXR0ZW1wdCB0byBhbGxvY2F0ZSBCdWZmZXIgbGFyZ2VyIHRoYW4gbWF4aW11bSAnICtcbiAgICAgICAgICAgICAgICAgICAgICAgICAnc2l6ZTogMHgnICsga01heExlbmd0aCgpLnRvU3RyaW5nKDE2KSArICcgYnl0ZXMnKVxuICB9XG4gIHJldHVybiBsZW5ndGggfCAwXG59XG5cbmZ1bmN0aW9uIFNsb3dCdWZmZXIgKHN1YmplY3QsIGVuY29kaW5nKSB7XG4gIGlmICghKHRoaXMgaW5zdGFuY2VvZiBTbG93QnVmZmVyKSkgcmV0dXJuIG5ldyBTbG93QnVmZmVyKHN1YmplY3QsIGVuY29kaW5nKVxuXG4gIHZhciBidWYgPSBuZXcgQnVmZmVyKHN1YmplY3QsIGVuY29kaW5nKVxuICBkZWxldGUgYnVmLnBhcmVudFxuICByZXR1cm4gYnVmXG59XG5cbkJ1ZmZlci5pc0J1ZmZlciA9IGZ1bmN0aW9uIGlzQnVmZmVyIChiKSB7XG4gIHJldHVybiAhIShiICE9IG51bGwgJiYgYi5faXNCdWZmZXIpXG59XG5cbkJ1ZmZlci5jb21wYXJlID0gZnVuY3Rpb24gY29tcGFyZSAoYSwgYikge1xuICBpZiAoIUJ1ZmZlci5pc0J1ZmZlcihhKSB8fCAhQnVmZmVyLmlzQnVmZmVyKGIpKSB7XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcignQXJndW1lbnRzIG11c3QgYmUgQnVmZmVycycpXG4gIH1cblxuICBpZiAoYSA9PT0gYikgcmV0dXJuIDBcblxuICB2YXIgeCA9IGEubGVuZ3RoXG4gIHZhciB5ID0gYi5sZW5ndGhcblxuICB2YXIgaSA9IDBcbiAgdmFyIGxlbiA9IE1hdGgubWluKHgsIHkpXG4gIHdoaWxlIChpIDwgbGVuKSB7XG4gICAgaWYgKGFbaV0gIT09IGJbaV0pIGJyZWFrXG5cbiAgICArK2lcbiAgfVxuXG4gIGlmIChpICE9PSBsZW4pIHtcbiAgICB4ID0gYVtpXVxuICAgIHkgPSBiW2ldXG4gIH1cblxuICBpZiAoeCA8IHkpIHJldHVybiAtMVxuICBpZiAoeSA8IHgpIHJldHVybiAxXG4gIHJldHVybiAwXG59XG5cbkJ1ZmZlci5pc0VuY29kaW5nID0gZnVuY3Rpb24gaXNFbmNvZGluZyAoZW5jb2RpbmcpIHtcbiAgc3dpdGNoIChTdHJpbmcoZW5jb2RpbmcpLnRvTG93ZXJDYXNlKCkpIHtcbiAgICBjYXNlICdoZXgnOlxuICAgIGNhc2UgJ3V0ZjgnOlxuICAgIGNhc2UgJ3V0Zi04JzpcbiAgICBjYXNlICdhc2NpaSc6XG4gICAgY2FzZSAnYmluYXJ5JzpcbiAgICBjYXNlICdiYXNlNjQnOlxuICAgIGNhc2UgJ3Jhdyc6XG4gICAgY2FzZSAndWNzMic6XG4gICAgY2FzZSAndWNzLTInOlxuICAgIGNhc2UgJ3V0ZjE2bGUnOlxuICAgIGNhc2UgJ3V0Zi0xNmxlJzpcbiAgICAgIHJldHVybiB0cnVlXG4gICAgZGVmYXVsdDpcbiAgICAgIHJldHVybiBmYWxzZVxuICB9XG59XG5cbkJ1ZmZlci5jb25jYXQgPSBmdW5jdGlvbiBjb25jYXQgKGxpc3QsIGxlbmd0aCkge1xuICBpZiAoIWlzQXJyYXkobGlzdCkpIHRocm93IG5ldyBUeXBlRXJyb3IoJ2xpc3QgYXJndW1lbnQgbXVzdCBiZSBhbiBBcnJheSBvZiBCdWZmZXJzLicpXG5cbiAgaWYgKGxpc3QubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIG5ldyBCdWZmZXIoMClcbiAgfVxuXG4gIHZhciBpXG4gIGlmIChsZW5ndGggPT09IHVuZGVmaW5lZCkge1xuICAgIGxlbmd0aCA9IDBcbiAgICBmb3IgKGkgPSAwOyBpIDwgbGlzdC5sZW5ndGg7IGkrKykge1xuICAgICAgbGVuZ3RoICs9IGxpc3RbaV0ubGVuZ3RoXG4gICAgfVxuICB9XG5cbiAgdmFyIGJ1ZiA9IG5ldyBCdWZmZXIobGVuZ3RoKVxuICB2YXIgcG9zID0gMFxuICBmb3IgKGkgPSAwOyBpIDwgbGlzdC5sZW5ndGg7IGkrKykge1xuICAgIHZhciBpdGVtID0gbGlzdFtpXVxuICAgIGl0ZW0uY29weShidWYsIHBvcylcbiAgICBwb3MgKz0gaXRlbS5sZW5ndGhcbiAgfVxuICByZXR1cm4gYnVmXG59XG5cbmZ1bmN0aW9uIGJ5dGVMZW5ndGggKHN0cmluZywgZW5jb2RpbmcpIHtcbiAgaWYgKHR5cGVvZiBzdHJpbmcgIT09ICdzdHJpbmcnKSBzdHJpbmcgPSAnJyArIHN0cmluZ1xuXG4gIHZhciBsZW4gPSBzdHJpbmcubGVuZ3RoXG4gIGlmIChsZW4gPT09IDApIHJldHVybiAwXG5cbiAgLy8gVXNlIGEgZm9yIGxvb3AgdG8gYXZvaWQgcmVjdXJzaW9uXG4gIHZhciBsb3dlcmVkQ2FzZSA9IGZhbHNlXG4gIGZvciAoOzspIHtcbiAgICBzd2l0Y2ggKGVuY29kaW5nKSB7XG4gICAgICBjYXNlICdhc2NpaSc6XG4gICAgICBjYXNlICdiaW5hcnknOlxuICAgICAgLy8gRGVwcmVjYXRlZFxuICAgICAgY2FzZSAncmF3JzpcbiAgICAgIGNhc2UgJ3Jhd3MnOlxuICAgICAgICByZXR1cm4gbGVuXG4gICAgICBjYXNlICd1dGY4JzpcbiAgICAgIGNhc2UgJ3V0Zi04JzpcbiAgICAgICAgcmV0dXJuIHV0ZjhUb0J5dGVzKHN0cmluZykubGVuZ3RoXG4gICAgICBjYXNlICd1Y3MyJzpcbiAgICAgIGNhc2UgJ3Vjcy0yJzpcbiAgICAgIGNhc2UgJ3V0ZjE2bGUnOlxuICAgICAgY2FzZSAndXRmLTE2bGUnOlxuICAgICAgICByZXR1cm4gbGVuICogMlxuICAgICAgY2FzZSAnaGV4JzpcbiAgICAgICAgcmV0dXJuIGxlbiA+Pj4gMVxuICAgICAgY2FzZSAnYmFzZTY0JzpcbiAgICAgICAgcmV0dXJuIGJhc2U2NFRvQnl0ZXMoc3RyaW5nKS5sZW5ndGhcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGlmIChsb3dlcmVkQ2FzZSkgcmV0dXJuIHV0ZjhUb0J5dGVzKHN0cmluZykubGVuZ3RoIC8vIGFzc3VtZSB1dGY4XG4gICAgICAgIGVuY29kaW5nID0gKCcnICsgZW5jb2RpbmcpLnRvTG93ZXJDYXNlKClcbiAgICAgICAgbG93ZXJlZENhc2UgPSB0cnVlXG4gICAgfVxuICB9XG59XG5CdWZmZXIuYnl0ZUxlbmd0aCA9IGJ5dGVMZW5ndGhcblxuLy8gcHJlLXNldCBmb3IgdmFsdWVzIHRoYXQgbWF5IGV4aXN0IGluIHRoZSBmdXR1cmVcbkJ1ZmZlci5wcm90b3R5cGUubGVuZ3RoID0gdW5kZWZpbmVkXG5CdWZmZXIucHJvdG90eXBlLnBhcmVudCA9IHVuZGVmaW5lZFxuXG5mdW5jdGlvbiBzbG93VG9TdHJpbmcgKGVuY29kaW5nLCBzdGFydCwgZW5kKSB7XG4gIHZhciBsb3dlcmVkQ2FzZSA9IGZhbHNlXG5cbiAgc3RhcnQgPSBzdGFydCB8IDBcbiAgZW5kID0gZW5kID09PSB1bmRlZmluZWQgfHwgZW5kID09PSBJbmZpbml0eSA/IHRoaXMubGVuZ3RoIDogZW5kIHwgMFxuXG4gIGlmICghZW5jb2RpbmcpIGVuY29kaW5nID0gJ3V0ZjgnXG4gIGlmIChzdGFydCA8IDApIHN0YXJ0ID0gMFxuICBpZiAoZW5kID4gdGhpcy5sZW5ndGgpIGVuZCA9IHRoaXMubGVuZ3RoXG4gIGlmIChlbmQgPD0gc3RhcnQpIHJldHVybiAnJ1xuXG4gIHdoaWxlICh0cnVlKSB7XG4gICAgc3dpdGNoIChlbmNvZGluZykge1xuICAgICAgY2FzZSAnaGV4JzpcbiAgICAgICAgcmV0dXJuIGhleFNsaWNlKHRoaXMsIHN0YXJ0LCBlbmQpXG5cbiAgICAgIGNhc2UgJ3V0ZjgnOlxuICAgICAgY2FzZSAndXRmLTgnOlxuICAgICAgICByZXR1cm4gdXRmOFNsaWNlKHRoaXMsIHN0YXJ0LCBlbmQpXG5cbiAgICAgIGNhc2UgJ2FzY2lpJzpcbiAgICAgICAgcmV0dXJuIGFzY2lpU2xpY2UodGhpcywgc3RhcnQsIGVuZClcblxuICAgICAgY2FzZSAnYmluYXJ5JzpcbiAgICAgICAgcmV0dXJuIGJpbmFyeVNsaWNlKHRoaXMsIHN0YXJ0LCBlbmQpXG5cbiAgICAgIGNhc2UgJ2Jhc2U2NCc6XG4gICAgICAgIHJldHVybiBiYXNlNjRTbGljZSh0aGlzLCBzdGFydCwgZW5kKVxuXG4gICAgICBjYXNlICd1Y3MyJzpcbiAgICAgIGNhc2UgJ3Vjcy0yJzpcbiAgICAgIGNhc2UgJ3V0ZjE2bGUnOlxuICAgICAgY2FzZSAndXRmLTE2bGUnOlxuICAgICAgICByZXR1cm4gdXRmMTZsZVNsaWNlKHRoaXMsIHN0YXJ0LCBlbmQpXG5cbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGlmIChsb3dlcmVkQ2FzZSkgdGhyb3cgbmV3IFR5cGVFcnJvcignVW5rbm93biBlbmNvZGluZzogJyArIGVuY29kaW5nKVxuICAgICAgICBlbmNvZGluZyA9IChlbmNvZGluZyArICcnKS50b0xvd2VyQ2FzZSgpXG4gICAgICAgIGxvd2VyZWRDYXNlID0gdHJ1ZVxuICAgIH1cbiAgfVxufVxuXG5CdWZmZXIucHJvdG90eXBlLnRvU3RyaW5nID0gZnVuY3Rpb24gdG9TdHJpbmcgKCkge1xuICB2YXIgbGVuZ3RoID0gdGhpcy5sZW5ndGggfCAwXG4gIGlmIChsZW5ndGggPT09IDApIHJldHVybiAnJ1xuICBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PT0gMCkgcmV0dXJuIHV0ZjhTbGljZSh0aGlzLCAwLCBsZW5ndGgpXG4gIHJldHVybiBzbG93VG9TdHJpbmcuYXBwbHkodGhpcywgYXJndW1lbnRzKVxufVxuXG5CdWZmZXIucHJvdG90eXBlLmVxdWFscyA9IGZ1bmN0aW9uIGVxdWFscyAoYikge1xuICBpZiAoIUJ1ZmZlci5pc0J1ZmZlcihiKSkgdGhyb3cgbmV3IFR5cGVFcnJvcignQXJndW1lbnQgbXVzdCBiZSBhIEJ1ZmZlcicpXG4gIGlmICh0aGlzID09PSBiKSByZXR1cm4gdHJ1ZVxuICByZXR1cm4gQnVmZmVyLmNvbXBhcmUodGhpcywgYikgPT09IDBcbn1cblxuQnVmZmVyLnByb3RvdHlwZS5pbnNwZWN0ID0gZnVuY3Rpb24gaW5zcGVjdCAoKSB7XG4gIHZhciBzdHIgPSAnJ1xuICB2YXIgbWF4ID0gZXhwb3J0cy5JTlNQRUNUX01BWF9CWVRFU1xuICBpZiAodGhpcy5sZW5ndGggPiAwKSB7XG4gICAgc3RyID0gdGhpcy50b1N0cmluZygnaGV4JywgMCwgbWF4KS5tYXRjaCgvLnsyfS9nKS5qb2luKCcgJylcbiAgICBpZiAodGhpcy5sZW5ndGggPiBtYXgpIHN0ciArPSAnIC4uLiAnXG4gIH1cbiAgcmV0dXJuICc8QnVmZmVyICcgKyBzdHIgKyAnPidcbn1cblxuQnVmZmVyLnByb3RvdHlwZS5jb21wYXJlID0gZnVuY3Rpb24gY29tcGFyZSAoYikge1xuICBpZiAoIUJ1ZmZlci5pc0J1ZmZlcihiKSkgdGhyb3cgbmV3IFR5cGVFcnJvcignQXJndW1lbnQgbXVzdCBiZSBhIEJ1ZmZlcicpXG4gIGlmICh0aGlzID09PSBiKSByZXR1cm4gMFxuICByZXR1cm4gQnVmZmVyLmNvbXBhcmUodGhpcywgYilcbn1cblxuQnVmZmVyLnByb3RvdHlwZS5pbmRleE9mID0gZnVuY3Rpb24gaW5kZXhPZiAodmFsLCBieXRlT2Zmc2V0KSB7XG4gIGlmIChieXRlT2Zmc2V0ID4gMHg3ZmZmZmZmZikgYnl0ZU9mZnNldCA9IDB4N2ZmZmZmZmZcbiAgZWxzZSBpZiAoYnl0ZU9mZnNldCA8IC0weDgwMDAwMDAwKSBieXRlT2Zmc2V0ID0gLTB4ODAwMDAwMDBcbiAgYnl0ZU9mZnNldCA+Pj0gMFxuXG4gIGlmICh0aGlzLmxlbmd0aCA9PT0gMCkgcmV0dXJuIC0xXG4gIGlmIChieXRlT2Zmc2V0ID49IHRoaXMubGVuZ3RoKSByZXR1cm4gLTFcblxuICAvLyBOZWdhdGl2ZSBvZmZzZXRzIHN0YXJ0IGZyb20gdGhlIGVuZCBvZiB0aGUgYnVmZmVyXG4gIGlmIChieXRlT2Zmc2V0IDwgMCkgYnl0ZU9mZnNldCA9IE1hdGgubWF4KHRoaXMubGVuZ3RoICsgYnl0ZU9mZnNldCwgMClcblxuICBpZiAodHlwZW9mIHZhbCA9PT0gJ3N0cmluZycpIHtcbiAgICBpZiAodmFsLmxlbmd0aCA9PT0gMCkgcmV0dXJuIC0xIC8vIHNwZWNpYWwgY2FzZTogbG9va2luZyBmb3IgZW1wdHkgc3RyaW5nIGFsd2F5cyBmYWlsc1xuICAgIHJldHVybiBTdHJpbmcucHJvdG90eXBlLmluZGV4T2YuY2FsbCh0aGlzLCB2YWwsIGJ5dGVPZmZzZXQpXG4gIH1cbiAgaWYgKEJ1ZmZlci5pc0J1ZmZlcih2YWwpKSB7XG4gICAgcmV0dXJuIGFycmF5SW5kZXhPZih0aGlzLCB2YWwsIGJ5dGVPZmZzZXQpXG4gIH1cbiAgaWYgKHR5cGVvZiB2YWwgPT09ICdudW1iZXInKSB7XG4gICAgaWYgKEJ1ZmZlci5UWVBFRF9BUlJBWV9TVVBQT1JUICYmIFVpbnQ4QXJyYXkucHJvdG90eXBlLmluZGV4T2YgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgIHJldHVybiBVaW50OEFycmF5LnByb3RvdHlwZS5pbmRleE9mLmNhbGwodGhpcywgdmFsLCBieXRlT2Zmc2V0KVxuICAgIH1cbiAgICByZXR1cm4gYXJyYXlJbmRleE9mKHRoaXMsIFsgdmFsIF0sIGJ5dGVPZmZzZXQpXG4gIH1cblxuICBmdW5jdGlvbiBhcnJheUluZGV4T2YgKGFyciwgdmFsLCBieXRlT2Zmc2V0KSB7XG4gICAgdmFyIGZvdW5kSW5kZXggPSAtMVxuICAgIGZvciAodmFyIGkgPSAwOyBieXRlT2Zmc2V0ICsgaSA8IGFyci5sZW5ndGg7IGkrKykge1xuICAgICAgaWYgKGFycltieXRlT2Zmc2V0ICsgaV0gPT09IHZhbFtmb3VuZEluZGV4ID09PSAtMSA/IDAgOiBpIC0gZm91bmRJbmRleF0pIHtcbiAgICAgICAgaWYgKGZvdW5kSW5kZXggPT09IC0xKSBmb3VuZEluZGV4ID0gaVxuICAgICAgICBpZiAoaSAtIGZvdW5kSW5kZXggKyAxID09PSB2YWwubGVuZ3RoKSByZXR1cm4gYnl0ZU9mZnNldCArIGZvdW5kSW5kZXhcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGZvdW5kSW5kZXggPSAtMVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gLTFcbiAgfVxuXG4gIHRocm93IG5ldyBUeXBlRXJyb3IoJ3ZhbCBtdXN0IGJlIHN0cmluZywgbnVtYmVyIG9yIEJ1ZmZlcicpXG59XG5cbi8vIGBnZXRgIGlzIGRlcHJlY2F0ZWRcbkJ1ZmZlci5wcm90b3R5cGUuZ2V0ID0gZnVuY3Rpb24gZ2V0IChvZmZzZXQpIHtcbiAgY29uc29sZS5sb2coJy5nZXQoKSBpcyBkZXByZWNhdGVkLiBBY2Nlc3MgdXNpbmcgYXJyYXkgaW5kZXhlcyBpbnN0ZWFkLicpXG4gIHJldHVybiB0aGlzLnJlYWRVSW50OChvZmZzZXQpXG59XG5cbi8vIGBzZXRgIGlzIGRlcHJlY2F0ZWRcbkJ1ZmZlci5wcm90b3R5cGUuc2V0ID0gZnVuY3Rpb24gc2V0ICh2LCBvZmZzZXQpIHtcbiAgY29uc29sZS5sb2coJy5zZXQoKSBpcyBkZXByZWNhdGVkLiBBY2Nlc3MgdXNpbmcgYXJyYXkgaW5kZXhlcyBpbnN0ZWFkLicpXG4gIHJldHVybiB0aGlzLndyaXRlVUludDgodiwgb2Zmc2V0KVxufVxuXG5mdW5jdGlvbiBoZXhXcml0ZSAoYnVmLCBzdHJpbmcsIG9mZnNldCwgbGVuZ3RoKSB7XG4gIG9mZnNldCA9IE51bWJlcihvZmZzZXQpIHx8IDBcbiAgdmFyIHJlbWFpbmluZyA9IGJ1Zi5sZW5ndGggLSBvZmZzZXRcbiAgaWYgKCFsZW5ndGgpIHtcbiAgICBsZW5ndGggPSByZW1haW5pbmdcbiAgfSBlbHNlIHtcbiAgICBsZW5ndGggPSBOdW1iZXIobGVuZ3RoKVxuICAgIGlmIChsZW5ndGggPiByZW1haW5pbmcpIHtcbiAgICAgIGxlbmd0aCA9IHJlbWFpbmluZ1xuICAgIH1cbiAgfVxuXG4gIC8vIG11c3QgYmUgYW4gZXZlbiBudW1iZXIgb2YgZGlnaXRzXG4gIHZhciBzdHJMZW4gPSBzdHJpbmcubGVuZ3RoXG4gIGlmIChzdHJMZW4gJSAyICE9PSAwKSB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgaGV4IHN0cmluZycpXG5cbiAgaWYgKGxlbmd0aCA+IHN0ckxlbiAvIDIpIHtcbiAgICBsZW5ndGggPSBzdHJMZW4gLyAyXG4gIH1cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgIHZhciBwYXJzZWQgPSBwYXJzZUludChzdHJpbmcuc3Vic3RyKGkgKiAyLCAyKSwgMTYpXG4gICAgaWYgKGlzTmFOKHBhcnNlZCkpIHRocm93IG5ldyBFcnJvcignSW52YWxpZCBoZXggc3RyaW5nJylcbiAgICBidWZbb2Zmc2V0ICsgaV0gPSBwYXJzZWRcbiAgfVxuICByZXR1cm4gaVxufVxuXG5mdW5jdGlvbiB1dGY4V3JpdGUgKGJ1Ziwgc3RyaW5nLCBvZmZzZXQsIGxlbmd0aCkge1xuICByZXR1cm4gYmxpdEJ1ZmZlcih1dGY4VG9CeXRlcyhzdHJpbmcsIGJ1Zi5sZW5ndGggLSBvZmZzZXQpLCBidWYsIG9mZnNldCwgbGVuZ3RoKVxufVxuXG5mdW5jdGlvbiBhc2NpaVdyaXRlIChidWYsIHN0cmluZywgb2Zmc2V0LCBsZW5ndGgpIHtcbiAgcmV0dXJuIGJsaXRCdWZmZXIoYXNjaWlUb0J5dGVzKHN0cmluZyksIGJ1Ziwgb2Zmc2V0LCBsZW5ndGgpXG59XG5cbmZ1bmN0aW9uIGJpbmFyeVdyaXRlIChidWYsIHN0cmluZywgb2Zmc2V0LCBsZW5ndGgpIHtcbiAgcmV0dXJuIGFzY2lpV3JpdGUoYnVmLCBzdHJpbmcsIG9mZnNldCwgbGVuZ3RoKVxufVxuXG5mdW5jdGlvbiBiYXNlNjRXcml0ZSAoYnVmLCBzdHJpbmcsIG9mZnNldCwgbGVuZ3RoKSB7XG4gIHJldHVybiBibGl0QnVmZmVyKGJhc2U2NFRvQnl0ZXMoc3RyaW5nKSwgYnVmLCBvZmZzZXQsIGxlbmd0aClcbn1cblxuZnVuY3Rpb24gdWNzMldyaXRlIChidWYsIHN0cmluZywgb2Zmc2V0LCBsZW5ndGgpIHtcbiAgcmV0dXJuIGJsaXRCdWZmZXIodXRmMTZsZVRvQnl0ZXMoc3RyaW5nLCBidWYubGVuZ3RoIC0gb2Zmc2V0KSwgYnVmLCBvZmZzZXQsIGxlbmd0aClcbn1cblxuQnVmZmVyLnByb3RvdHlwZS53cml0ZSA9IGZ1bmN0aW9uIHdyaXRlIChzdHJpbmcsIG9mZnNldCwgbGVuZ3RoLCBlbmNvZGluZykge1xuICAvLyBCdWZmZXIjd3JpdGUoc3RyaW5nKVxuICBpZiAob2Zmc2V0ID09PSB1bmRlZmluZWQpIHtcbiAgICBlbmNvZGluZyA9ICd1dGY4J1xuICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoXG4gICAgb2Zmc2V0ID0gMFxuICAvLyBCdWZmZXIjd3JpdGUoc3RyaW5nLCBlbmNvZGluZylcbiAgfSBlbHNlIGlmIChsZW5ndGggPT09IHVuZGVmaW5lZCAmJiB0eXBlb2Ygb2Zmc2V0ID09PSAnc3RyaW5nJykge1xuICAgIGVuY29kaW5nID0gb2Zmc2V0XG4gICAgbGVuZ3RoID0gdGhpcy5sZW5ndGhcbiAgICBvZmZzZXQgPSAwXG4gIC8vIEJ1ZmZlciN3cml0ZShzdHJpbmcsIG9mZnNldFssIGxlbmd0aF1bLCBlbmNvZGluZ10pXG4gIH0gZWxzZSBpZiAoaXNGaW5pdGUob2Zmc2V0KSkge1xuICAgIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgICBpZiAoaXNGaW5pdGUobGVuZ3RoKSkge1xuICAgICAgbGVuZ3RoID0gbGVuZ3RoIHwgMFxuICAgICAgaWYgKGVuY29kaW5nID09PSB1bmRlZmluZWQpIGVuY29kaW5nID0gJ3V0ZjgnXG4gICAgfSBlbHNlIHtcbiAgICAgIGVuY29kaW5nID0gbGVuZ3RoXG4gICAgICBsZW5ndGggPSB1bmRlZmluZWRcbiAgICB9XG4gIC8vIGxlZ2FjeSB3cml0ZShzdHJpbmcsIGVuY29kaW5nLCBvZmZzZXQsIGxlbmd0aCkgLSByZW1vdmUgaW4gdjAuMTNcbiAgfSBlbHNlIHtcbiAgICB2YXIgc3dhcCA9IGVuY29kaW5nXG4gICAgZW5jb2RpbmcgPSBvZmZzZXRcbiAgICBvZmZzZXQgPSBsZW5ndGggfCAwXG4gICAgbGVuZ3RoID0gc3dhcFxuICB9XG5cbiAgdmFyIHJlbWFpbmluZyA9IHRoaXMubGVuZ3RoIC0gb2Zmc2V0XG4gIGlmIChsZW5ndGggPT09IHVuZGVmaW5lZCB8fCBsZW5ndGggPiByZW1haW5pbmcpIGxlbmd0aCA9IHJlbWFpbmluZ1xuXG4gIGlmICgoc3RyaW5nLmxlbmd0aCA+IDAgJiYgKGxlbmd0aCA8IDAgfHwgb2Zmc2V0IDwgMCkpIHx8IG9mZnNldCA+IHRoaXMubGVuZ3RoKSB7XG4gICAgdGhyb3cgbmV3IFJhbmdlRXJyb3IoJ2F0dGVtcHQgdG8gd3JpdGUgb3V0c2lkZSBidWZmZXIgYm91bmRzJylcbiAgfVxuXG4gIGlmICghZW5jb2RpbmcpIGVuY29kaW5nID0gJ3V0ZjgnXG5cbiAgdmFyIGxvd2VyZWRDYXNlID0gZmFsc2VcbiAgZm9yICg7Oykge1xuICAgIHN3aXRjaCAoZW5jb2RpbmcpIHtcbiAgICAgIGNhc2UgJ2hleCc6XG4gICAgICAgIHJldHVybiBoZXhXcml0ZSh0aGlzLCBzdHJpbmcsIG9mZnNldCwgbGVuZ3RoKVxuXG4gICAgICBjYXNlICd1dGY4JzpcbiAgICAgIGNhc2UgJ3V0Zi04JzpcbiAgICAgICAgcmV0dXJuIHV0ZjhXcml0ZSh0aGlzLCBzdHJpbmcsIG9mZnNldCwgbGVuZ3RoKVxuXG4gICAgICBjYXNlICdhc2NpaSc6XG4gICAgICAgIHJldHVybiBhc2NpaVdyaXRlKHRoaXMsIHN0cmluZywgb2Zmc2V0LCBsZW5ndGgpXG5cbiAgICAgIGNhc2UgJ2JpbmFyeSc6XG4gICAgICAgIHJldHVybiBiaW5hcnlXcml0ZSh0aGlzLCBzdHJpbmcsIG9mZnNldCwgbGVuZ3RoKVxuXG4gICAgICBjYXNlICdiYXNlNjQnOlxuICAgICAgICAvLyBXYXJuaW5nOiBtYXhMZW5ndGggbm90IHRha2VuIGludG8gYWNjb3VudCBpbiBiYXNlNjRXcml0ZVxuICAgICAgICByZXR1cm4gYmFzZTY0V3JpdGUodGhpcywgc3RyaW5nLCBvZmZzZXQsIGxlbmd0aClcblxuICAgICAgY2FzZSAndWNzMic6XG4gICAgICBjYXNlICd1Y3MtMic6XG4gICAgICBjYXNlICd1dGYxNmxlJzpcbiAgICAgIGNhc2UgJ3V0Zi0xNmxlJzpcbiAgICAgICAgcmV0dXJuIHVjczJXcml0ZSh0aGlzLCBzdHJpbmcsIG9mZnNldCwgbGVuZ3RoKVxuXG4gICAgICBkZWZhdWx0OlxuICAgICAgICBpZiAobG93ZXJlZENhc2UpIHRocm93IG5ldyBUeXBlRXJyb3IoJ1Vua25vd24gZW5jb2Rpbmc6ICcgKyBlbmNvZGluZylcbiAgICAgICAgZW5jb2RpbmcgPSAoJycgKyBlbmNvZGluZykudG9Mb3dlckNhc2UoKVxuICAgICAgICBsb3dlcmVkQ2FzZSA9IHRydWVcbiAgICB9XG4gIH1cbn1cblxuQnVmZmVyLnByb3RvdHlwZS50b0pTT04gPSBmdW5jdGlvbiB0b0pTT04gKCkge1xuICByZXR1cm4ge1xuICAgIHR5cGU6ICdCdWZmZXInLFxuICAgIGRhdGE6IEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKHRoaXMuX2FyciB8fCB0aGlzLCAwKVxuICB9XG59XG5cbmZ1bmN0aW9uIGJhc2U2NFNsaWNlIChidWYsIHN0YXJ0LCBlbmQpIHtcbiAgaWYgKHN0YXJ0ID09PSAwICYmIGVuZCA9PT0gYnVmLmxlbmd0aCkge1xuICAgIHJldHVybiBiYXNlNjQuZnJvbUJ5dGVBcnJheShidWYpXG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIGJhc2U2NC5mcm9tQnl0ZUFycmF5KGJ1Zi5zbGljZShzdGFydCwgZW5kKSlcbiAgfVxufVxuXG5mdW5jdGlvbiB1dGY4U2xpY2UgKGJ1Ziwgc3RhcnQsIGVuZCkge1xuICBlbmQgPSBNYXRoLm1pbihidWYubGVuZ3RoLCBlbmQpXG4gIHZhciByZXMgPSBbXVxuXG4gIHZhciBpID0gc3RhcnRcbiAgd2hpbGUgKGkgPCBlbmQpIHtcbiAgICB2YXIgZmlyc3RCeXRlID0gYnVmW2ldXG4gICAgdmFyIGNvZGVQb2ludCA9IG51bGxcbiAgICB2YXIgYnl0ZXNQZXJTZXF1ZW5jZSA9IChmaXJzdEJ5dGUgPiAweEVGKSA/IDRcbiAgICAgIDogKGZpcnN0Qnl0ZSA+IDB4REYpID8gM1xuICAgICAgOiAoZmlyc3RCeXRlID4gMHhCRikgPyAyXG4gICAgICA6IDFcblxuICAgIGlmIChpICsgYnl0ZXNQZXJTZXF1ZW5jZSA8PSBlbmQpIHtcbiAgICAgIHZhciBzZWNvbmRCeXRlLCB0aGlyZEJ5dGUsIGZvdXJ0aEJ5dGUsIHRlbXBDb2RlUG9pbnRcblxuICAgICAgc3dpdGNoIChieXRlc1BlclNlcXVlbmNlKSB7XG4gICAgICAgIGNhc2UgMTpcbiAgICAgICAgICBpZiAoZmlyc3RCeXRlIDwgMHg4MCkge1xuICAgICAgICAgICAgY29kZVBvaW50ID0gZmlyc3RCeXRlXG4gICAgICAgICAgfVxuICAgICAgICAgIGJyZWFrXG4gICAgICAgIGNhc2UgMjpcbiAgICAgICAgICBzZWNvbmRCeXRlID0gYnVmW2kgKyAxXVxuICAgICAgICAgIGlmICgoc2Vjb25kQnl0ZSAmIDB4QzApID09PSAweDgwKSB7XG4gICAgICAgICAgICB0ZW1wQ29kZVBvaW50ID0gKGZpcnN0Qnl0ZSAmIDB4MUYpIDw8IDB4NiB8IChzZWNvbmRCeXRlICYgMHgzRilcbiAgICAgICAgICAgIGlmICh0ZW1wQ29kZVBvaW50ID4gMHg3Rikge1xuICAgICAgICAgICAgICBjb2RlUG9pbnQgPSB0ZW1wQ29kZVBvaW50XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIGJyZWFrXG4gICAgICAgIGNhc2UgMzpcbiAgICAgICAgICBzZWNvbmRCeXRlID0gYnVmW2kgKyAxXVxuICAgICAgICAgIHRoaXJkQnl0ZSA9IGJ1ZltpICsgMl1cbiAgICAgICAgICBpZiAoKHNlY29uZEJ5dGUgJiAweEMwKSA9PT0gMHg4MCAmJiAodGhpcmRCeXRlICYgMHhDMCkgPT09IDB4ODApIHtcbiAgICAgICAgICAgIHRlbXBDb2RlUG9pbnQgPSAoZmlyc3RCeXRlICYgMHhGKSA8PCAweEMgfCAoc2Vjb25kQnl0ZSAmIDB4M0YpIDw8IDB4NiB8ICh0aGlyZEJ5dGUgJiAweDNGKVxuICAgICAgICAgICAgaWYgKHRlbXBDb2RlUG9pbnQgPiAweDdGRiAmJiAodGVtcENvZGVQb2ludCA8IDB4RDgwMCB8fCB0ZW1wQ29kZVBvaW50ID4gMHhERkZGKSkge1xuICAgICAgICAgICAgICBjb2RlUG9pbnQgPSB0ZW1wQ29kZVBvaW50XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIGJyZWFrXG4gICAgICAgIGNhc2UgNDpcbiAgICAgICAgICBzZWNvbmRCeXRlID0gYnVmW2kgKyAxXVxuICAgICAgICAgIHRoaXJkQnl0ZSA9IGJ1ZltpICsgMl1cbiAgICAgICAgICBmb3VydGhCeXRlID0gYnVmW2kgKyAzXVxuICAgICAgICAgIGlmICgoc2Vjb25kQnl0ZSAmIDB4QzApID09PSAweDgwICYmICh0aGlyZEJ5dGUgJiAweEMwKSA9PT0gMHg4MCAmJiAoZm91cnRoQnl0ZSAmIDB4QzApID09PSAweDgwKSB7XG4gICAgICAgICAgICB0ZW1wQ29kZVBvaW50ID0gKGZpcnN0Qnl0ZSAmIDB4RikgPDwgMHgxMiB8IChzZWNvbmRCeXRlICYgMHgzRikgPDwgMHhDIHwgKHRoaXJkQnl0ZSAmIDB4M0YpIDw8IDB4NiB8IChmb3VydGhCeXRlICYgMHgzRilcbiAgICAgICAgICAgIGlmICh0ZW1wQ29kZVBvaW50ID4gMHhGRkZGICYmIHRlbXBDb2RlUG9pbnQgPCAweDExMDAwMCkge1xuICAgICAgICAgICAgICBjb2RlUG9pbnQgPSB0ZW1wQ29kZVBvaW50XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChjb2RlUG9pbnQgPT09IG51bGwpIHtcbiAgICAgIC8vIHdlIGRpZCBub3QgZ2VuZXJhdGUgYSB2YWxpZCBjb2RlUG9pbnQgc28gaW5zZXJ0IGFcbiAgICAgIC8vIHJlcGxhY2VtZW50IGNoYXIgKFUrRkZGRCkgYW5kIGFkdmFuY2Ugb25seSAxIGJ5dGVcbiAgICAgIGNvZGVQb2ludCA9IDB4RkZGRFxuICAgICAgYnl0ZXNQZXJTZXF1ZW5jZSA9IDFcbiAgICB9IGVsc2UgaWYgKGNvZGVQb2ludCA+IDB4RkZGRikge1xuICAgICAgLy8gZW5jb2RlIHRvIHV0ZjE2IChzdXJyb2dhdGUgcGFpciBkYW5jZSlcbiAgICAgIGNvZGVQb2ludCAtPSAweDEwMDAwXG4gICAgICByZXMucHVzaChjb2RlUG9pbnQgPj4+IDEwICYgMHgzRkYgfCAweEQ4MDApXG4gICAgICBjb2RlUG9pbnQgPSAweERDMDAgfCBjb2RlUG9pbnQgJiAweDNGRlxuICAgIH1cblxuICAgIHJlcy5wdXNoKGNvZGVQb2ludClcbiAgICBpICs9IGJ5dGVzUGVyU2VxdWVuY2VcbiAgfVxuXG4gIHJldHVybiBkZWNvZGVDb2RlUG9pbnRzQXJyYXkocmVzKVxufVxuXG4vLyBCYXNlZCBvbiBodHRwOi8vc3RhY2tvdmVyZmxvdy5jb20vYS8yMjc0NzI3Mi82ODA3NDIsIHRoZSBicm93c2VyIHdpdGhcbi8vIHRoZSBsb3dlc3QgbGltaXQgaXMgQ2hyb21lLCB3aXRoIDB4MTAwMDAgYXJncy5cbi8vIFdlIGdvIDEgbWFnbml0dWRlIGxlc3MsIGZvciBzYWZldHlcbnZhciBNQVhfQVJHVU1FTlRTX0xFTkdUSCA9IDB4MTAwMFxuXG5mdW5jdGlvbiBkZWNvZGVDb2RlUG9pbnRzQXJyYXkgKGNvZGVQb2ludHMpIHtcbiAgdmFyIGxlbiA9IGNvZGVQb2ludHMubGVuZ3RoXG4gIGlmIChsZW4gPD0gTUFYX0FSR1VNRU5UU19MRU5HVEgpIHtcbiAgICByZXR1cm4gU3RyaW5nLmZyb21DaGFyQ29kZS5hcHBseShTdHJpbmcsIGNvZGVQb2ludHMpIC8vIGF2b2lkIGV4dHJhIHNsaWNlKClcbiAgfVxuXG4gIC8vIERlY29kZSBpbiBjaHVua3MgdG8gYXZvaWQgXCJjYWxsIHN0YWNrIHNpemUgZXhjZWVkZWRcIi5cbiAgdmFyIHJlcyA9ICcnXG4gIHZhciBpID0gMFxuICB3aGlsZSAoaSA8IGxlbikge1xuICAgIHJlcyArPSBTdHJpbmcuZnJvbUNoYXJDb2RlLmFwcGx5KFxuICAgICAgU3RyaW5nLFxuICAgICAgY29kZVBvaW50cy5zbGljZShpLCBpICs9IE1BWF9BUkdVTUVOVFNfTEVOR1RIKVxuICAgIClcbiAgfVxuICByZXR1cm4gcmVzXG59XG5cbmZ1bmN0aW9uIGFzY2lpU2xpY2UgKGJ1Ziwgc3RhcnQsIGVuZCkge1xuICB2YXIgcmV0ID0gJydcbiAgZW5kID0gTWF0aC5taW4oYnVmLmxlbmd0aCwgZW5kKVxuXG4gIGZvciAodmFyIGkgPSBzdGFydDsgaSA8IGVuZDsgaSsrKSB7XG4gICAgcmV0ICs9IFN0cmluZy5mcm9tQ2hhckNvZGUoYnVmW2ldICYgMHg3RilcbiAgfVxuICByZXR1cm4gcmV0XG59XG5cbmZ1bmN0aW9uIGJpbmFyeVNsaWNlIChidWYsIHN0YXJ0LCBlbmQpIHtcbiAgdmFyIHJldCA9ICcnXG4gIGVuZCA9IE1hdGgubWluKGJ1Zi5sZW5ndGgsIGVuZClcblxuICBmb3IgKHZhciBpID0gc3RhcnQ7IGkgPCBlbmQ7IGkrKykge1xuICAgIHJldCArPSBTdHJpbmcuZnJvbUNoYXJDb2RlKGJ1ZltpXSlcbiAgfVxuICByZXR1cm4gcmV0XG59XG5cbmZ1bmN0aW9uIGhleFNsaWNlIChidWYsIHN0YXJ0LCBlbmQpIHtcbiAgdmFyIGxlbiA9IGJ1Zi5sZW5ndGhcblxuICBpZiAoIXN0YXJ0IHx8IHN0YXJ0IDwgMCkgc3RhcnQgPSAwXG4gIGlmICghZW5kIHx8IGVuZCA8IDAgfHwgZW5kID4gbGVuKSBlbmQgPSBsZW5cblxuICB2YXIgb3V0ID0gJydcbiAgZm9yICh2YXIgaSA9IHN0YXJ0OyBpIDwgZW5kOyBpKyspIHtcbiAgICBvdXQgKz0gdG9IZXgoYnVmW2ldKVxuICB9XG4gIHJldHVybiBvdXRcbn1cblxuZnVuY3Rpb24gdXRmMTZsZVNsaWNlIChidWYsIHN0YXJ0LCBlbmQpIHtcbiAgdmFyIGJ5dGVzID0gYnVmLnNsaWNlKHN0YXJ0LCBlbmQpXG4gIHZhciByZXMgPSAnJ1xuICBmb3IgKHZhciBpID0gMDsgaSA8IGJ5dGVzLmxlbmd0aDsgaSArPSAyKSB7XG4gICAgcmVzICs9IFN0cmluZy5mcm9tQ2hhckNvZGUoYnl0ZXNbaV0gKyBieXRlc1tpICsgMV0gKiAyNTYpXG4gIH1cbiAgcmV0dXJuIHJlc1xufVxuXG5CdWZmZXIucHJvdG90eXBlLnNsaWNlID0gZnVuY3Rpb24gc2xpY2UgKHN0YXJ0LCBlbmQpIHtcbiAgdmFyIGxlbiA9IHRoaXMubGVuZ3RoXG4gIHN0YXJ0ID0gfn5zdGFydFxuICBlbmQgPSBlbmQgPT09IHVuZGVmaW5lZCA/IGxlbiA6IH5+ZW5kXG5cbiAgaWYgKHN0YXJ0IDwgMCkge1xuICAgIHN0YXJ0ICs9IGxlblxuICAgIGlmIChzdGFydCA8IDApIHN0YXJ0ID0gMFxuICB9IGVsc2UgaWYgKHN0YXJ0ID4gbGVuKSB7XG4gICAgc3RhcnQgPSBsZW5cbiAgfVxuXG4gIGlmIChlbmQgPCAwKSB7XG4gICAgZW5kICs9IGxlblxuICAgIGlmIChlbmQgPCAwKSBlbmQgPSAwXG4gIH0gZWxzZSBpZiAoZW5kID4gbGVuKSB7XG4gICAgZW5kID0gbGVuXG4gIH1cblxuICBpZiAoZW5kIDwgc3RhcnQpIGVuZCA9IHN0YXJ0XG5cbiAgdmFyIG5ld0J1ZlxuICBpZiAoQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlQpIHtcbiAgICBuZXdCdWYgPSBCdWZmZXIuX2F1Z21lbnQodGhpcy5zdWJhcnJheShzdGFydCwgZW5kKSlcbiAgfSBlbHNlIHtcbiAgICB2YXIgc2xpY2VMZW4gPSBlbmQgLSBzdGFydFxuICAgIG5ld0J1ZiA9IG5ldyBCdWZmZXIoc2xpY2VMZW4sIHVuZGVmaW5lZClcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHNsaWNlTGVuOyBpKyspIHtcbiAgICAgIG5ld0J1ZltpXSA9IHRoaXNbaSArIHN0YXJ0XVxuICAgIH1cbiAgfVxuXG4gIGlmIChuZXdCdWYubGVuZ3RoKSBuZXdCdWYucGFyZW50ID0gdGhpcy5wYXJlbnQgfHwgdGhpc1xuXG4gIHJldHVybiBuZXdCdWZcbn1cblxuLypcbiAqIE5lZWQgdG8gbWFrZSBzdXJlIHRoYXQgYnVmZmVyIGlzbid0IHRyeWluZyB0byB3cml0ZSBvdXQgb2YgYm91bmRzLlxuICovXG5mdW5jdGlvbiBjaGVja09mZnNldCAob2Zmc2V0LCBleHQsIGxlbmd0aCkge1xuICBpZiAoKG9mZnNldCAlIDEpICE9PSAwIHx8IG9mZnNldCA8IDApIHRocm93IG5ldyBSYW5nZUVycm9yKCdvZmZzZXQgaXMgbm90IHVpbnQnKVxuICBpZiAob2Zmc2V0ICsgZXh0ID4gbGVuZ3RoKSB0aHJvdyBuZXcgUmFuZ2VFcnJvcignVHJ5aW5nIHRvIGFjY2VzcyBiZXlvbmQgYnVmZmVyIGxlbmd0aCcpXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZFVJbnRMRSA9IGZ1bmN0aW9uIHJlYWRVSW50TEUgKG9mZnNldCwgYnl0ZUxlbmd0aCwgbm9Bc3NlcnQpIHtcbiAgb2Zmc2V0ID0gb2Zmc2V0IHwgMFxuICBieXRlTGVuZ3RoID0gYnl0ZUxlbmd0aCB8IDBcbiAgaWYgKCFub0Fzc2VydCkgY2hlY2tPZmZzZXQob2Zmc2V0LCBieXRlTGVuZ3RoLCB0aGlzLmxlbmd0aClcblxuICB2YXIgdmFsID0gdGhpc1tvZmZzZXRdXG4gIHZhciBtdWwgPSAxXG4gIHZhciBpID0gMFxuICB3aGlsZSAoKytpIDwgYnl0ZUxlbmd0aCAmJiAobXVsICo9IDB4MTAwKSkge1xuICAgIHZhbCArPSB0aGlzW29mZnNldCArIGldICogbXVsXG4gIH1cblxuICByZXR1cm4gdmFsXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZFVJbnRCRSA9IGZ1bmN0aW9uIHJlYWRVSW50QkUgKG9mZnNldCwgYnl0ZUxlbmd0aCwgbm9Bc3NlcnQpIHtcbiAgb2Zmc2V0ID0gb2Zmc2V0IHwgMFxuICBieXRlTGVuZ3RoID0gYnl0ZUxlbmd0aCB8IDBcbiAgaWYgKCFub0Fzc2VydCkge1xuICAgIGNoZWNrT2Zmc2V0KG9mZnNldCwgYnl0ZUxlbmd0aCwgdGhpcy5sZW5ndGgpXG4gIH1cblxuICB2YXIgdmFsID0gdGhpc1tvZmZzZXQgKyAtLWJ5dGVMZW5ndGhdXG4gIHZhciBtdWwgPSAxXG4gIHdoaWxlIChieXRlTGVuZ3RoID4gMCAmJiAobXVsICo9IDB4MTAwKSkge1xuICAgIHZhbCArPSB0aGlzW29mZnNldCArIC0tYnl0ZUxlbmd0aF0gKiBtdWxcbiAgfVxuXG4gIHJldHVybiB2YWxcbn1cblxuQnVmZmVyLnByb3RvdHlwZS5yZWFkVUludDggPSBmdW5jdGlvbiByZWFkVUludDggKG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgaWYgKCFub0Fzc2VydCkgY2hlY2tPZmZzZXQob2Zmc2V0LCAxLCB0aGlzLmxlbmd0aClcbiAgcmV0dXJuIHRoaXNbb2Zmc2V0XVxufVxuXG5CdWZmZXIucHJvdG90eXBlLnJlYWRVSW50MTZMRSA9IGZ1bmN0aW9uIHJlYWRVSW50MTZMRSAob2Zmc2V0LCBub0Fzc2VydCkge1xuICBpZiAoIW5vQXNzZXJ0KSBjaGVja09mZnNldChvZmZzZXQsIDIsIHRoaXMubGVuZ3RoKVxuICByZXR1cm4gdGhpc1tvZmZzZXRdIHwgKHRoaXNbb2Zmc2V0ICsgMV0gPDwgOClcbn1cblxuQnVmZmVyLnByb3RvdHlwZS5yZWFkVUludDE2QkUgPSBmdW5jdGlvbiByZWFkVUludDE2QkUgKG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgaWYgKCFub0Fzc2VydCkgY2hlY2tPZmZzZXQob2Zmc2V0LCAyLCB0aGlzLmxlbmd0aClcbiAgcmV0dXJuICh0aGlzW29mZnNldF0gPDwgOCkgfCB0aGlzW29mZnNldCArIDFdXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZFVJbnQzMkxFID0gZnVuY3Rpb24gcmVhZFVJbnQzMkxFIChvZmZzZXQsIG5vQXNzZXJ0KSB7XG4gIGlmICghbm9Bc3NlcnQpIGNoZWNrT2Zmc2V0KG9mZnNldCwgNCwgdGhpcy5sZW5ndGgpXG5cbiAgcmV0dXJuICgodGhpc1tvZmZzZXRdKSB8XG4gICAgICAodGhpc1tvZmZzZXQgKyAxXSA8PCA4KSB8XG4gICAgICAodGhpc1tvZmZzZXQgKyAyXSA8PCAxNikpICtcbiAgICAgICh0aGlzW29mZnNldCArIDNdICogMHgxMDAwMDAwKVxufVxuXG5CdWZmZXIucHJvdG90eXBlLnJlYWRVSW50MzJCRSA9IGZ1bmN0aW9uIHJlYWRVSW50MzJCRSAob2Zmc2V0LCBub0Fzc2VydCkge1xuICBpZiAoIW5vQXNzZXJ0KSBjaGVja09mZnNldChvZmZzZXQsIDQsIHRoaXMubGVuZ3RoKVxuXG4gIHJldHVybiAodGhpc1tvZmZzZXRdICogMHgxMDAwMDAwKSArXG4gICAgKCh0aGlzW29mZnNldCArIDFdIDw8IDE2KSB8XG4gICAgKHRoaXNbb2Zmc2V0ICsgMl0gPDwgOCkgfFxuICAgIHRoaXNbb2Zmc2V0ICsgM10pXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZEludExFID0gZnVuY3Rpb24gcmVhZEludExFIChvZmZzZXQsIGJ5dGVMZW5ndGgsIG5vQXNzZXJ0KSB7XG4gIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgYnl0ZUxlbmd0aCA9IGJ5dGVMZW5ndGggfCAwXG4gIGlmICghbm9Bc3NlcnQpIGNoZWNrT2Zmc2V0KG9mZnNldCwgYnl0ZUxlbmd0aCwgdGhpcy5sZW5ndGgpXG5cbiAgdmFyIHZhbCA9IHRoaXNbb2Zmc2V0XVxuICB2YXIgbXVsID0gMVxuICB2YXIgaSA9IDBcbiAgd2hpbGUgKCsraSA8IGJ5dGVMZW5ndGggJiYgKG11bCAqPSAweDEwMCkpIHtcbiAgICB2YWwgKz0gdGhpc1tvZmZzZXQgKyBpXSAqIG11bFxuICB9XG4gIG11bCAqPSAweDgwXG5cbiAgaWYgKHZhbCA+PSBtdWwpIHZhbCAtPSBNYXRoLnBvdygyLCA4ICogYnl0ZUxlbmd0aClcblxuICByZXR1cm4gdmFsXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZEludEJFID0gZnVuY3Rpb24gcmVhZEludEJFIChvZmZzZXQsIGJ5dGVMZW5ndGgsIG5vQXNzZXJ0KSB7XG4gIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgYnl0ZUxlbmd0aCA9IGJ5dGVMZW5ndGggfCAwXG4gIGlmICghbm9Bc3NlcnQpIGNoZWNrT2Zmc2V0KG9mZnNldCwgYnl0ZUxlbmd0aCwgdGhpcy5sZW5ndGgpXG5cbiAgdmFyIGkgPSBieXRlTGVuZ3RoXG4gIHZhciBtdWwgPSAxXG4gIHZhciB2YWwgPSB0aGlzW29mZnNldCArIC0taV1cbiAgd2hpbGUgKGkgPiAwICYmIChtdWwgKj0gMHgxMDApKSB7XG4gICAgdmFsICs9IHRoaXNbb2Zmc2V0ICsgLS1pXSAqIG11bFxuICB9XG4gIG11bCAqPSAweDgwXG5cbiAgaWYgKHZhbCA+PSBtdWwpIHZhbCAtPSBNYXRoLnBvdygyLCA4ICogYnl0ZUxlbmd0aClcblxuICByZXR1cm4gdmFsXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZEludDggPSBmdW5jdGlvbiByZWFkSW50OCAob2Zmc2V0LCBub0Fzc2VydCkge1xuICBpZiAoIW5vQXNzZXJ0KSBjaGVja09mZnNldChvZmZzZXQsIDEsIHRoaXMubGVuZ3RoKVxuICBpZiAoISh0aGlzW29mZnNldF0gJiAweDgwKSkgcmV0dXJuICh0aGlzW29mZnNldF0pXG4gIHJldHVybiAoKDB4ZmYgLSB0aGlzW29mZnNldF0gKyAxKSAqIC0xKVxufVxuXG5CdWZmZXIucHJvdG90eXBlLnJlYWRJbnQxNkxFID0gZnVuY3Rpb24gcmVhZEludDE2TEUgKG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgaWYgKCFub0Fzc2VydCkgY2hlY2tPZmZzZXQob2Zmc2V0LCAyLCB0aGlzLmxlbmd0aClcbiAgdmFyIHZhbCA9IHRoaXNbb2Zmc2V0XSB8ICh0aGlzW29mZnNldCArIDFdIDw8IDgpXG4gIHJldHVybiAodmFsICYgMHg4MDAwKSA/IHZhbCB8IDB4RkZGRjAwMDAgOiB2YWxcbn1cblxuQnVmZmVyLnByb3RvdHlwZS5yZWFkSW50MTZCRSA9IGZ1bmN0aW9uIHJlYWRJbnQxNkJFIChvZmZzZXQsIG5vQXNzZXJ0KSB7XG4gIGlmICghbm9Bc3NlcnQpIGNoZWNrT2Zmc2V0KG9mZnNldCwgMiwgdGhpcy5sZW5ndGgpXG4gIHZhciB2YWwgPSB0aGlzW29mZnNldCArIDFdIHwgKHRoaXNbb2Zmc2V0XSA8PCA4KVxuICByZXR1cm4gKHZhbCAmIDB4ODAwMCkgPyB2YWwgfCAweEZGRkYwMDAwIDogdmFsXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZEludDMyTEUgPSBmdW5jdGlvbiByZWFkSW50MzJMRSAob2Zmc2V0LCBub0Fzc2VydCkge1xuICBpZiAoIW5vQXNzZXJ0KSBjaGVja09mZnNldChvZmZzZXQsIDQsIHRoaXMubGVuZ3RoKVxuXG4gIHJldHVybiAodGhpc1tvZmZzZXRdKSB8XG4gICAgKHRoaXNbb2Zmc2V0ICsgMV0gPDwgOCkgfFxuICAgICh0aGlzW29mZnNldCArIDJdIDw8IDE2KSB8XG4gICAgKHRoaXNbb2Zmc2V0ICsgM10gPDwgMjQpXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZEludDMyQkUgPSBmdW5jdGlvbiByZWFkSW50MzJCRSAob2Zmc2V0LCBub0Fzc2VydCkge1xuICBpZiAoIW5vQXNzZXJ0KSBjaGVja09mZnNldChvZmZzZXQsIDQsIHRoaXMubGVuZ3RoKVxuXG4gIHJldHVybiAodGhpc1tvZmZzZXRdIDw8IDI0KSB8XG4gICAgKHRoaXNbb2Zmc2V0ICsgMV0gPDwgMTYpIHxcbiAgICAodGhpc1tvZmZzZXQgKyAyXSA8PCA4KSB8XG4gICAgKHRoaXNbb2Zmc2V0ICsgM10pXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZEZsb2F0TEUgPSBmdW5jdGlvbiByZWFkRmxvYXRMRSAob2Zmc2V0LCBub0Fzc2VydCkge1xuICBpZiAoIW5vQXNzZXJ0KSBjaGVja09mZnNldChvZmZzZXQsIDQsIHRoaXMubGVuZ3RoKVxuICByZXR1cm4gaWVlZTc1NC5yZWFkKHRoaXMsIG9mZnNldCwgdHJ1ZSwgMjMsIDQpXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZEZsb2F0QkUgPSBmdW5jdGlvbiByZWFkRmxvYXRCRSAob2Zmc2V0LCBub0Fzc2VydCkge1xuICBpZiAoIW5vQXNzZXJ0KSBjaGVja09mZnNldChvZmZzZXQsIDQsIHRoaXMubGVuZ3RoKVxuICByZXR1cm4gaWVlZTc1NC5yZWFkKHRoaXMsIG9mZnNldCwgZmFsc2UsIDIzLCA0KVxufVxuXG5CdWZmZXIucHJvdG90eXBlLnJlYWREb3VibGVMRSA9IGZ1bmN0aW9uIHJlYWREb3VibGVMRSAob2Zmc2V0LCBub0Fzc2VydCkge1xuICBpZiAoIW5vQXNzZXJ0KSBjaGVja09mZnNldChvZmZzZXQsIDgsIHRoaXMubGVuZ3RoKVxuICByZXR1cm4gaWVlZTc1NC5yZWFkKHRoaXMsIG9mZnNldCwgdHJ1ZSwgNTIsIDgpXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUucmVhZERvdWJsZUJFID0gZnVuY3Rpb24gcmVhZERvdWJsZUJFIChvZmZzZXQsIG5vQXNzZXJ0KSB7XG4gIGlmICghbm9Bc3NlcnQpIGNoZWNrT2Zmc2V0KG9mZnNldCwgOCwgdGhpcy5sZW5ndGgpXG4gIHJldHVybiBpZWVlNzU0LnJlYWQodGhpcywgb2Zmc2V0LCBmYWxzZSwgNTIsIDgpXG59XG5cbmZ1bmN0aW9uIGNoZWNrSW50IChidWYsIHZhbHVlLCBvZmZzZXQsIGV4dCwgbWF4LCBtaW4pIHtcbiAgaWYgKCFCdWZmZXIuaXNCdWZmZXIoYnVmKSkgdGhyb3cgbmV3IFR5cGVFcnJvcignYnVmZmVyIG11c3QgYmUgYSBCdWZmZXIgaW5zdGFuY2UnKVxuICBpZiAodmFsdWUgPiBtYXggfHwgdmFsdWUgPCBtaW4pIHRocm93IG5ldyBSYW5nZUVycm9yKCd2YWx1ZSBpcyBvdXQgb2YgYm91bmRzJylcbiAgaWYgKG9mZnNldCArIGV4dCA+IGJ1Zi5sZW5ndGgpIHRocm93IG5ldyBSYW5nZUVycm9yKCdpbmRleCBvdXQgb2YgcmFuZ2UnKVxufVxuXG5CdWZmZXIucHJvdG90eXBlLndyaXRlVUludExFID0gZnVuY3Rpb24gd3JpdGVVSW50TEUgKHZhbHVlLCBvZmZzZXQsIGJ5dGVMZW5ndGgsIG5vQXNzZXJ0KSB7XG4gIHZhbHVlID0gK3ZhbHVlXG4gIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgYnl0ZUxlbmd0aCA9IGJ5dGVMZW5ndGggfCAwXG4gIGlmICghbm9Bc3NlcnQpIGNoZWNrSW50KHRoaXMsIHZhbHVlLCBvZmZzZXQsIGJ5dGVMZW5ndGgsIE1hdGgucG93KDIsIDggKiBieXRlTGVuZ3RoKSwgMClcblxuICB2YXIgbXVsID0gMVxuICB2YXIgaSA9IDBcbiAgdGhpc1tvZmZzZXRdID0gdmFsdWUgJiAweEZGXG4gIHdoaWxlICgrK2kgPCBieXRlTGVuZ3RoICYmIChtdWwgKj0gMHgxMDApKSB7XG4gICAgdGhpc1tvZmZzZXQgKyBpXSA9ICh2YWx1ZSAvIG11bCkgJiAweEZGXG4gIH1cblxuICByZXR1cm4gb2Zmc2V0ICsgYnl0ZUxlbmd0aFxufVxuXG5CdWZmZXIucHJvdG90eXBlLndyaXRlVUludEJFID0gZnVuY3Rpb24gd3JpdGVVSW50QkUgKHZhbHVlLCBvZmZzZXQsIGJ5dGVMZW5ndGgsIG5vQXNzZXJ0KSB7XG4gIHZhbHVlID0gK3ZhbHVlXG4gIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgYnl0ZUxlbmd0aCA9IGJ5dGVMZW5ndGggfCAwXG4gIGlmICghbm9Bc3NlcnQpIGNoZWNrSW50KHRoaXMsIHZhbHVlLCBvZmZzZXQsIGJ5dGVMZW5ndGgsIE1hdGgucG93KDIsIDggKiBieXRlTGVuZ3RoKSwgMClcblxuICB2YXIgaSA9IGJ5dGVMZW5ndGggLSAxXG4gIHZhciBtdWwgPSAxXG4gIHRoaXNbb2Zmc2V0ICsgaV0gPSB2YWx1ZSAmIDB4RkZcbiAgd2hpbGUgKC0taSA+PSAwICYmIChtdWwgKj0gMHgxMDApKSB7XG4gICAgdGhpc1tvZmZzZXQgKyBpXSA9ICh2YWx1ZSAvIG11bCkgJiAweEZGXG4gIH1cblxuICByZXR1cm4gb2Zmc2V0ICsgYnl0ZUxlbmd0aFxufVxuXG5CdWZmZXIucHJvdG90eXBlLndyaXRlVUludDggPSBmdW5jdGlvbiB3cml0ZVVJbnQ4ICh2YWx1ZSwgb2Zmc2V0LCBub0Fzc2VydCkge1xuICB2YWx1ZSA9ICt2YWx1ZVxuICBvZmZzZXQgPSBvZmZzZXQgfCAwXG4gIGlmICghbm9Bc3NlcnQpIGNoZWNrSW50KHRoaXMsIHZhbHVlLCBvZmZzZXQsIDEsIDB4ZmYsIDApXG4gIGlmICghQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlQpIHZhbHVlID0gTWF0aC5mbG9vcih2YWx1ZSlcbiAgdGhpc1tvZmZzZXRdID0gdmFsdWVcbiAgcmV0dXJuIG9mZnNldCArIDFcbn1cblxuZnVuY3Rpb24gb2JqZWN0V3JpdGVVSW50MTYgKGJ1ZiwgdmFsdWUsIG9mZnNldCwgbGl0dGxlRW5kaWFuKSB7XG4gIGlmICh2YWx1ZSA8IDApIHZhbHVlID0gMHhmZmZmICsgdmFsdWUgKyAxXG4gIGZvciAodmFyIGkgPSAwLCBqID0gTWF0aC5taW4oYnVmLmxlbmd0aCAtIG9mZnNldCwgMik7IGkgPCBqOyBpKyspIHtcbiAgICBidWZbb2Zmc2V0ICsgaV0gPSAodmFsdWUgJiAoMHhmZiA8PCAoOCAqIChsaXR0bGVFbmRpYW4gPyBpIDogMSAtIGkpKSkpID4+PlxuICAgICAgKGxpdHRsZUVuZGlhbiA/IGkgOiAxIC0gaSkgKiA4XG4gIH1cbn1cblxuQnVmZmVyLnByb3RvdHlwZS53cml0ZVVJbnQxNkxFID0gZnVuY3Rpb24gd3JpdGVVSW50MTZMRSAodmFsdWUsIG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgdmFsdWUgPSArdmFsdWVcbiAgb2Zmc2V0ID0gb2Zmc2V0IHwgMFxuICBpZiAoIW5vQXNzZXJ0KSBjaGVja0ludCh0aGlzLCB2YWx1ZSwgb2Zmc2V0LCAyLCAweGZmZmYsIDApXG4gIGlmIChCdWZmZXIuVFlQRURfQVJSQVlfU1VQUE9SVCkge1xuICAgIHRoaXNbb2Zmc2V0XSA9IHZhbHVlXG4gICAgdGhpc1tvZmZzZXQgKyAxXSA9ICh2YWx1ZSA+Pj4gOClcbiAgfSBlbHNlIHtcbiAgICBvYmplY3RXcml0ZVVJbnQxNih0aGlzLCB2YWx1ZSwgb2Zmc2V0LCB0cnVlKVxuICB9XG4gIHJldHVybiBvZmZzZXQgKyAyXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUud3JpdGVVSW50MTZCRSA9IGZ1bmN0aW9uIHdyaXRlVUludDE2QkUgKHZhbHVlLCBvZmZzZXQsIG5vQXNzZXJ0KSB7XG4gIHZhbHVlID0gK3ZhbHVlXG4gIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgaWYgKCFub0Fzc2VydCkgY2hlY2tJbnQodGhpcywgdmFsdWUsIG9mZnNldCwgMiwgMHhmZmZmLCAwKVxuICBpZiAoQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlQpIHtcbiAgICB0aGlzW29mZnNldF0gPSAodmFsdWUgPj4+IDgpXG4gICAgdGhpc1tvZmZzZXQgKyAxXSA9IHZhbHVlXG4gIH0gZWxzZSB7XG4gICAgb2JqZWN0V3JpdGVVSW50MTYodGhpcywgdmFsdWUsIG9mZnNldCwgZmFsc2UpXG4gIH1cbiAgcmV0dXJuIG9mZnNldCArIDJcbn1cblxuZnVuY3Rpb24gb2JqZWN0V3JpdGVVSW50MzIgKGJ1ZiwgdmFsdWUsIG9mZnNldCwgbGl0dGxlRW5kaWFuKSB7XG4gIGlmICh2YWx1ZSA8IDApIHZhbHVlID0gMHhmZmZmZmZmZiArIHZhbHVlICsgMVxuICBmb3IgKHZhciBpID0gMCwgaiA9IE1hdGgubWluKGJ1Zi5sZW5ndGggLSBvZmZzZXQsIDQpOyBpIDwgajsgaSsrKSB7XG4gICAgYnVmW29mZnNldCArIGldID0gKHZhbHVlID4+PiAobGl0dGxlRW5kaWFuID8gaSA6IDMgLSBpKSAqIDgpICYgMHhmZlxuICB9XG59XG5cbkJ1ZmZlci5wcm90b3R5cGUud3JpdGVVSW50MzJMRSA9IGZ1bmN0aW9uIHdyaXRlVUludDMyTEUgKHZhbHVlLCBvZmZzZXQsIG5vQXNzZXJ0KSB7XG4gIHZhbHVlID0gK3ZhbHVlXG4gIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgaWYgKCFub0Fzc2VydCkgY2hlY2tJbnQodGhpcywgdmFsdWUsIG9mZnNldCwgNCwgMHhmZmZmZmZmZiwgMClcbiAgaWYgKEJ1ZmZlci5UWVBFRF9BUlJBWV9TVVBQT1JUKSB7XG4gICAgdGhpc1tvZmZzZXQgKyAzXSA9ICh2YWx1ZSA+Pj4gMjQpXG4gICAgdGhpc1tvZmZzZXQgKyAyXSA9ICh2YWx1ZSA+Pj4gMTYpXG4gICAgdGhpc1tvZmZzZXQgKyAxXSA9ICh2YWx1ZSA+Pj4gOClcbiAgICB0aGlzW29mZnNldF0gPSB2YWx1ZVxuICB9IGVsc2Uge1xuICAgIG9iamVjdFdyaXRlVUludDMyKHRoaXMsIHZhbHVlLCBvZmZzZXQsIHRydWUpXG4gIH1cbiAgcmV0dXJuIG9mZnNldCArIDRcbn1cblxuQnVmZmVyLnByb3RvdHlwZS53cml0ZVVJbnQzMkJFID0gZnVuY3Rpb24gd3JpdGVVSW50MzJCRSAodmFsdWUsIG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgdmFsdWUgPSArdmFsdWVcbiAgb2Zmc2V0ID0gb2Zmc2V0IHwgMFxuICBpZiAoIW5vQXNzZXJ0KSBjaGVja0ludCh0aGlzLCB2YWx1ZSwgb2Zmc2V0LCA0LCAweGZmZmZmZmZmLCAwKVxuICBpZiAoQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlQpIHtcbiAgICB0aGlzW29mZnNldF0gPSAodmFsdWUgPj4+IDI0KVxuICAgIHRoaXNbb2Zmc2V0ICsgMV0gPSAodmFsdWUgPj4+IDE2KVxuICAgIHRoaXNbb2Zmc2V0ICsgMl0gPSAodmFsdWUgPj4+IDgpXG4gICAgdGhpc1tvZmZzZXQgKyAzXSA9IHZhbHVlXG4gIH0gZWxzZSB7XG4gICAgb2JqZWN0V3JpdGVVSW50MzIodGhpcywgdmFsdWUsIG9mZnNldCwgZmFsc2UpXG4gIH1cbiAgcmV0dXJuIG9mZnNldCArIDRcbn1cblxuQnVmZmVyLnByb3RvdHlwZS53cml0ZUludExFID0gZnVuY3Rpb24gd3JpdGVJbnRMRSAodmFsdWUsIG9mZnNldCwgYnl0ZUxlbmd0aCwgbm9Bc3NlcnQpIHtcbiAgdmFsdWUgPSArdmFsdWVcbiAgb2Zmc2V0ID0gb2Zmc2V0IHwgMFxuICBpZiAoIW5vQXNzZXJ0KSB7XG4gICAgdmFyIGxpbWl0ID0gTWF0aC5wb3coMiwgOCAqIGJ5dGVMZW5ndGggLSAxKVxuXG4gICAgY2hlY2tJbnQodGhpcywgdmFsdWUsIG9mZnNldCwgYnl0ZUxlbmd0aCwgbGltaXQgLSAxLCAtbGltaXQpXG4gIH1cblxuICB2YXIgaSA9IDBcbiAgdmFyIG11bCA9IDFcbiAgdmFyIHN1YiA9IHZhbHVlIDwgMCA/IDEgOiAwXG4gIHRoaXNbb2Zmc2V0XSA9IHZhbHVlICYgMHhGRlxuICB3aGlsZSAoKytpIDwgYnl0ZUxlbmd0aCAmJiAobXVsICo9IDB4MTAwKSkge1xuICAgIHRoaXNbb2Zmc2V0ICsgaV0gPSAoKHZhbHVlIC8gbXVsKSA+PiAwKSAtIHN1YiAmIDB4RkZcbiAgfVxuXG4gIHJldHVybiBvZmZzZXQgKyBieXRlTGVuZ3RoXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUud3JpdGVJbnRCRSA9IGZ1bmN0aW9uIHdyaXRlSW50QkUgKHZhbHVlLCBvZmZzZXQsIGJ5dGVMZW5ndGgsIG5vQXNzZXJ0KSB7XG4gIHZhbHVlID0gK3ZhbHVlXG4gIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgaWYgKCFub0Fzc2VydCkge1xuICAgIHZhciBsaW1pdCA9IE1hdGgucG93KDIsIDggKiBieXRlTGVuZ3RoIC0gMSlcblxuICAgIGNoZWNrSW50KHRoaXMsIHZhbHVlLCBvZmZzZXQsIGJ5dGVMZW5ndGgsIGxpbWl0IC0gMSwgLWxpbWl0KVxuICB9XG5cbiAgdmFyIGkgPSBieXRlTGVuZ3RoIC0gMVxuICB2YXIgbXVsID0gMVxuICB2YXIgc3ViID0gdmFsdWUgPCAwID8gMSA6IDBcbiAgdGhpc1tvZmZzZXQgKyBpXSA9IHZhbHVlICYgMHhGRlxuICB3aGlsZSAoLS1pID49IDAgJiYgKG11bCAqPSAweDEwMCkpIHtcbiAgICB0aGlzW29mZnNldCArIGldID0gKCh2YWx1ZSAvIG11bCkgPj4gMCkgLSBzdWIgJiAweEZGXG4gIH1cblxuICByZXR1cm4gb2Zmc2V0ICsgYnl0ZUxlbmd0aFxufVxuXG5CdWZmZXIucHJvdG90eXBlLndyaXRlSW50OCA9IGZ1bmN0aW9uIHdyaXRlSW50OCAodmFsdWUsIG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgdmFsdWUgPSArdmFsdWVcbiAgb2Zmc2V0ID0gb2Zmc2V0IHwgMFxuICBpZiAoIW5vQXNzZXJ0KSBjaGVja0ludCh0aGlzLCB2YWx1ZSwgb2Zmc2V0LCAxLCAweDdmLCAtMHg4MClcbiAgaWYgKCFCdWZmZXIuVFlQRURfQVJSQVlfU1VQUE9SVCkgdmFsdWUgPSBNYXRoLmZsb29yKHZhbHVlKVxuICBpZiAodmFsdWUgPCAwKSB2YWx1ZSA9IDB4ZmYgKyB2YWx1ZSArIDFcbiAgdGhpc1tvZmZzZXRdID0gdmFsdWVcbiAgcmV0dXJuIG9mZnNldCArIDFcbn1cblxuQnVmZmVyLnByb3RvdHlwZS53cml0ZUludDE2TEUgPSBmdW5jdGlvbiB3cml0ZUludDE2TEUgKHZhbHVlLCBvZmZzZXQsIG5vQXNzZXJ0KSB7XG4gIHZhbHVlID0gK3ZhbHVlXG4gIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgaWYgKCFub0Fzc2VydCkgY2hlY2tJbnQodGhpcywgdmFsdWUsIG9mZnNldCwgMiwgMHg3ZmZmLCAtMHg4MDAwKVxuICBpZiAoQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlQpIHtcbiAgICB0aGlzW29mZnNldF0gPSB2YWx1ZVxuICAgIHRoaXNbb2Zmc2V0ICsgMV0gPSAodmFsdWUgPj4+IDgpXG4gIH0gZWxzZSB7XG4gICAgb2JqZWN0V3JpdGVVSW50MTYodGhpcywgdmFsdWUsIG9mZnNldCwgdHJ1ZSlcbiAgfVxuICByZXR1cm4gb2Zmc2V0ICsgMlxufVxuXG5CdWZmZXIucHJvdG90eXBlLndyaXRlSW50MTZCRSA9IGZ1bmN0aW9uIHdyaXRlSW50MTZCRSAodmFsdWUsIG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgdmFsdWUgPSArdmFsdWVcbiAgb2Zmc2V0ID0gb2Zmc2V0IHwgMFxuICBpZiAoIW5vQXNzZXJ0KSBjaGVja0ludCh0aGlzLCB2YWx1ZSwgb2Zmc2V0LCAyLCAweDdmZmYsIC0weDgwMDApXG4gIGlmIChCdWZmZXIuVFlQRURfQVJSQVlfU1VQUE9SVCkge1xuICAgIHRoaXNbb2Zmc2V0XSA9ICh2YWx1ZSA+Pj4gOClcbiAgICB0aGlzW29mZnNldCArIDFdID0gdmFsdWVcbiAgfSBlbHNlIHtcbiAgICBvYmplY3RXcml0ZVVJbnQxNih0aGlzLCB2YWx1ZSwgb2Zmc2V0LCBmYWxzZSlcbiAgfVxuICByZXR1cm4gb2Zmc2V0ICsgMlxufVxuXG5CdWZmZXIucHJvdG90eXBlLndyaXRlSW50MzJMRSA9IGZ1bmN0aW9uIHdyaXRlSW50MzJMRSAodmFsdWUsIG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgdmFsdWUgPSArdmFsdWVcbiAgb2Zmc2V0ID0gb2Zmc2V0IHwgMFxuICBpZiAoIW5vQXNzZXJ0KSBjaGVja0ludCh0aGlzLCB2YWx1ZSwgb2Zmc2V0LCA0LCAweDdmZmZmZmZmLCAtMHg4MDAwMDAwMClcbiAgaWYgKEJ1ZmZlci5UWVBFRF9BUlJBWV9TVVBQT1JUKSB7XG4gICAgdGhpc1tvZmZzZXRdID0gdmFsdWVcbiAgICB0aGlzW29mZnNldCArIDFdID0gKHZhbHVlID4+PiA4KVxuICAgIHRoaXNbb2Zmc2V0ICsgMl0gPSAodmFsdWUgPj4+IDE2KVxuICAgIHRoaXNbb2Zmc2V0ICsgM10gPSAodmFsdWUgPj4+IDI0KVxuICB9IGVsc2Uge1xuICAgIG9iamVjdFdyaXRlVUludDMyKHRoaXMsIHZhbHVlLCBvZmZzZXQsIHRydWUpXG4gIH1cbiAgcmV0dXJuIG9mZnNldCArIDRcbn1cblxuQnVmZmVyLnByb3RvdHlwZS53cml0ZUludDMyQkUgPSBmdW5jdGlvbiB3cml0ZUludDMyQkUgKHZhbHVlLCBvZmZzZXQsIG5vQXNzZXJ0KSB7XG4gIHZhbHVlID0gK3ZhbHVlXG4gIG9mZnNldCA9IG9mZnNldCB8IDBcbiAgaWYgKCFub0Fzc2VydCkgY2hlY2tJbnQodGhpcywgdmFsdWUsIG9mZnNldCwgNCwgMHg3ZmZmZmZmZiwgLTB4ODAwMDAwMDApXG4gIGlmICh2YWx1ZSA8IDApIHZhbHVlID0gMHhmZmZmZmZmZiArIHZhbHVlICsgMVxuICBpZiAoQnVmZmVyLlRZUEVEX0FSUkFZX1NVUFBPUlQpIHtcbiAgICB0aGlzW29mZnNldF0gPSAodmFsdWUgPj4+IDI0KVxuICAgIHRoaXNbb2Zmc2V0ICsgMV0gPSAodmFsdWUgPj4+IDE2KVxuICAgIHRoaXNbb2Zmc2V0ICsgMl0gPSAodmFsdWUgPj4+IDgpXG4gICAgdGhpc1tvZmZzZXQgKyAzXSA9IHZhbHVlXG4gIH0gZWxzZSB7XG4gICAgb2JqZWN0V3JpdGVVSW50MzIodGhpcywgdmFsdWUsIG9mZnNldCwgZmFsc2UpXG4gIH1cbiAgcmV0dXJuIG9mZnNldCArIDRcbn1cblxuZnVuY3Rpb24gY2hlY2tJRUVFNzU0IChidWYsIHZhbHVlLCBvZmZzZXQsIGV4dCwgbWF4LCBtaW4pIHtcbiAgaWYgKHZhbHVlID4gbWF4IHx8IHZhbHVlIDwgbWluKSB0aHJvdyBuZXcgUmFuZ2VFcnJvcigndmFsdWUgaXMgb3V0IG9mIGJvdW5kcycpXG4gIGlmIChvZmZzZXQgKyBleHQgPiBidWYubGVuZ3RoKSB0aHJvdyBuZXcgUmFuZ2VFcnJvcignaW5kZXggb3V0IG9mIHJhbmdlJylcbiAgaWYgKG9mZnNldCA8IDApIHRocm93IG5ldyBSYW5nZUVycm9yKCdpbmRleCBvdXQgb2YgcmFuZ2UnKVxufVxuXG5mdW5jdGlvbiB3cml0ZUZsb2F0IChidWYsIHZhbHVlLCBvZmZzZXQsIGxpdHRsZUVuZGlhbiwgbm9Bc3NlcnQpIHtcbiAgaWYgKCFub0Fzc2VydCkge1xuICAgIGNoZWNrSUVFRTc1NChidWYsIHZhbHVlLCBvZmZzZXQsIDQsIDMuNDAyODIzNDY2Mzg1Mjg4NmUrMzgsIC0zLjQwMjgyMzQ2NjM4NTI4ODZlKzM4KVxuICB9XG4gIGllZWU3NTQud3JpdGUoYnVmLCB2YWx1ZSwgb2Zmc2V0LCBsaXR0bGVFbmRpYW4sIDIzLCA0KVxuICByZXR1cm4gb2Zmc2V0ICsgNFxufVxuXG5CdWZmZXIucHJvdG90eXBlLndyaXRlRmxvYXRMRSA9IGZ1bmN0aW9uIHdyaXRlRmxvYXRMRSAodmFsdWUsIG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgcmV0dXJuIHdyaXRlRmxvYXQodGhpcywgdmFsdWUsIG9mZnNldCwgdHJ1ZSwgbm9Bc3NlcnQpXG59XG5cbkJ1ZmZlci5wcm90b3R5cGUud3JpdGVGbG9hdEJFID0gZnVuY3Rpb24gd3JpdGVGbG9hdEJFICh2YWx1ZSwgb2Zmc2V0LCBub0Fzc2VydCkge1xuICByZXR1cm4gd3JpdGVGbG9hdCh0aGlzLCB2YWx1ZSwgb2Zmc2V0LCBmYWxzZSwgbm9Bc3NlcnQpXG59XG5cbmZ1bmN0aW9uIHdyaXRlRG91YmxlIChidWYsIHZhbHVlLCBvZmZzZXQsIGxpdHRsZUVuZGlhbiwgbm9Bc3NlcnQpIHtcbiAgaWYgKCFub0Fzc2VydCkge1xuICAgIGNoZWNrSUVFRTc1NChidWYsIHZhbHVlLCBvZmZzZXQsIDgsIDEuNzk3NjkzMTM0ODYyMzE1N0UrMzA4LCAtMS43OTc2OTMxMzQ4NjIzMTU3RSszMDgpXG4gIH1cbiAgaWVlZTc1NC53cml0ZShidWYsIHZhbHVlLCBvZmZzZXQsIGxpdHRsZUVuZGlhbiwgNTIsIDgpXG4gIHJldHVybiBvZmZzZXQgKyA4XG59XG5cbkJ1ZmZlci5wcm90b3R5cGUud3JpdGVEb3VibGVMRSA9IGZ1bmN0aW9uIHdyaXRlRG91YmxlTEUgKHZhbHVlLCBvZmZzZXQsIG5vQXNzZXJ0KSB7XG4gIHJldHVybiB3cml0ZURvdWJsZSh0aGlzLCB2YWx1ZSwgb2Zmc2V0LCB0cnVlLCBub0Fzc2VydClcbn1cblxuQnVmZmVyLnByb3RvdHlwZS53cml0ZURvdWJsZUJFID0gZnVuY3Rpb24gd3JpdGVEb3VibGVCRSAodmFsdWUsIG9mZnNldCwgbm9Bc3NlcnQpIHtcbiAgcmV0dXJuIHdyaXRlRG91YmxlKHRoaXMsIHZhbHVlLCBvZmZzZXQsIGZhbHNlLCBub0Fzc2VydClcbn1cblxuLy8gY29weSh0YXJnZXRCdWZmZXIsIHRhcmdldFN0YXJ0PTAsIHNvdXJjZVN0YXJ0PTAsIHNvdXJjZUVuZD1idWZmZXIubGVuZ3RoKVxuQnVmZmVyLnByb3RvdHlwZS5jb3B5ID0gZnVuY3Rpb24gY29weSAodGFyZ2V0LCB0YXJnZXRTdGFydCwgc3RhcnQsIGVuZCkge1xuICBpZiAoIXN0YXJ0KSBzdGFydCA9IDBcbiAgaWYgKCFlbmQgJiYgZW5kICE9PSAwKSBlbmQgPSB0aGlzLmxlbmd0aFxuICBpZiAodGFyZ2V0U3RhcnQgPj0gdGFyZ2V0Lmxlbmd0aCkgdGFyZ2V0U3RhcnQgPSB0YXJnZXQubGVuZ3RoXG4gIGlmICghdGFyZ2V0U3RhcnQpIHRhcmdldFN0YXJ0ID0gMFxuICBpZiAoZW5kID4gMCAmJiBlbmQgPCBzdGFydCkgZW5kID0gc3RhcnRcblxuICAvLyBDb3B5IDAgYnl0ZXM7IHdlJ3JlIGRvbmVcbiAgaWYgKGVuZCA9PT0gc3RhcnQpIHJldHVybiAwXG4gIGlmICh0YXJnZXQubGVuZ3RoID09PSAwIHx8IHRoaXMubGVuZ3RoID09PSAwKSByZXR1cm4gMFxuXG4gIC8vIEZhdGFsIGVycm9yIGNvbmRpdGlvbnNcbiAgaWYgKHRhcmdldFN0YXJ0IDwgMCkge1xuICAgIHRocm93IG5ldyBSYW5nZUVycm9yKCd0YXJnZXRTdGFydCBvdXQgb2YgYm91bmRzJylcbiAgfVxuICBpZiAoc3RhcnQgPCAwIHx8IHN0YXJ0ID49IHRoaXMubGVuZ3RoKSB0aHJvdyBuZXcgUmFuZ2VFcnJvcignc291cmNlU3RhcnQgb3V0IG9mIGJvdW5kcycpXG4gIGlmIChlbmQgPCAwKSB0aHJvdyBuZXcgUmFuZ2VFcnJvcignc291cmNlRW5kIG91dCBvZiBib3VuZHMnKVxuXG4gIC8vIEFyZSB3ZSBvb2I/XG4gIGlmIChlbmQgPiB0aGlzLmxlbmd0aCkgZW5kID0gdGhpcy5sZW5ndGhcbiAgaWYgKHRhcmdldC5sZW5ndGggLSB0YXJnZXRTdGFydCA8IGVuZCAtIHN0YXJ0KSB7XG4gICAgZW5kID0gdGFyZ2V0Lmxlbmd0aCAtIHRhcmdldFN0YXJ0ICsgc3RhcnRcbiAgfVxuXG4gIHZhciBsZW4gPSBlbmQgLSBzdGFydFxuICB2YXIgaVxuXG4gIGlmICh0aGlzID09PSB0YXJnZXQgJiYgc3RhcnQgPCB0YXJnZXRTdGFydCAmJiB0YXJnZXRTdGFydCA8IGVuZCkge1xuICAgIC8vIGRlc2NlbmRpbmcgY29weSBmcm9tIGVuZFxuICAgIGZvciAoaSA9IGxlbiAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICB0YXJnZXRbaSArIHRhcmdldFN0YXJ0XSA9IHRoaXNbaSArIHN0YXJ0XVxuICAgIH1cbiAgfSBlbHNlIGlmIChsZW4gPCAxMDAwIHx8ICFCdWZmZXIuVFlQRURfQVJSQVlfU1VQUE9SVCkge1xuICAgIC8vIGFzY2VuZGluZyBjb3B5IGZyb20gc3RhcnRcbiAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIHRhcmdldFtpICsgdGFyZ2V0U3RhcnRdID0gdGhpc1tpICsgc3RhcnRdXG4gICAgfVxuICB9IGVsc2Uge1xuICAgIHRhcmdldC5fc2V0KHRoaXMuc3ViYXJyYXkoc3RhcnQsIHN0YXJ0ICsgbGVuKSwgdGFyZ2V0U3RhcnQpXG4gIH1cblxuICByZXR1cm4gbGVuXG59XG5cbi8vIGZpbGwodmFsdWUsIHN0YXJ0PTAsIGVuZD1idWZmZXIubGVuZ3RoKVxuQnVmZmVyLnByb3RvdHlwZS5maWxsID0gZnVuY3Rpb24gZmlsbCAodmFsdWUsIHN0YXJ0LCBlbmQpIHtcbiAgaWYgKCF2YWx1ZSkgdmFsdWUgPSAwXG4gIGlmICghc3RhcnQpIHN0YXJ0ID0gMFxuICBpZiAoIWVuZCkgZW5kID0gdGhpcy5sZW5ndGhcblxuICBpZiAoZW5kIDwgc3RhcnQpIHRocm93IG5ldyBSYW5nZUVycm9yKCdlbmQgPCBzdGFydCcpXG5cbiAgLy8gRmlsbCAwIGJ5dGVzOyB3ZSdyZSBkb25lXG4gIGlmIChlbmQgPT09IHN0YXJ0KSByZXR1cm5cbiAgaWYgKHRoaXMubGVuZ3RoID09PSAwKSByZXR1cm5cblxuICBpZiAoc3RhcnQgPCAwIHx8IHN0YXJ0ID49IHRoaXMubGVuZ3RoKSB0aHJvdyBuZXcgUmFuZ2VFcnJvcignc3RhcnQgb3V0IG9mIGJvdW5kcycpXG4gIGlmIChlbmQgPCAwIHx8IGVuZCA+IHRoaXMubGVuZ3RoKSB0aHJvdyBuZXcgUmFuZ2VFcnJvcignZW5kIG91dCBvZiBib3VuZHMnKVxuXG4gIHZhciBpXG4gIGlmICh0eXBlb2YgdmFsdWUgPT09ICdudW1iZXInKSB7XG4gICAgZm9yIChpID0gc3RhcnQ7IGkgPCBlbmQ7IGkrKykge1xuICAgICAgdGhpc1tpXSA9IHZhbHVlXG4gICAgfVxuICB9IGVsc2Uge1xuICAgIHZhciBieXRlcyA9IHV0ZjhUb0J5dGVzKHZhbHVlLnRvU3RyaW5nKCkpXG4gICAgdmFyIGxlbiA9IGJ5dGVzLmxlbmd0aFxuICAgIGZvciAoaSA9IHN0YXJ0OyBpIDwgZW5kOyBpKyspIHtcbiAgICAgIHRoaXNbaV0gPSBieXRlc1tpICUgbGVuXVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiB0aGlzXG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIG5ldyBgQXJyYXlCdWZmZXJgIHdpdGggdGhlICpjb3BpZWQqIG1lbW9yeSBvZiB0aGUgYnVmZmVyIGluc3RhbmNlLlxuICogQWRkZWQgaW4gTm9kZSAwLjEyLiBPbmx5IGF2YWlsYWJsZSBpbiBicm93c2VycyB0aGF0IHN1cHBvcnQgQXJyYXlCdWZmZXIuXG4gKi9cbkJ1ZmZlci5wcm90b3R5cGUudG9BcnJheUJ1ZmZlciA9IGZ1bmN0aW9uIHRvQXJyYXlCdWZmZXIgKCkge1xuICBpZiAodHlwZW9mIFVpbnQ4QXJyYXkgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgaWYgKEJ1ZmZlci5UWVBFRF9BUlJBWV9TVVBQT1JUKSB7XG4gICAgICByZXR1cm4gKG5ldyBCdWZmZXIodGhpcykpLmJ1ZmZlclxuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgYnVmID0gbmV3IFVpbnQ4QXJyYXkodGhpcy5sZW5ndGgpXG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gYnVmLmxlbmd0aDsgaSA8IGxlbjsgaSArPSAxKSB7XG4gICAgICAgIGJ1ZltpXSA9IHRoaXNbaV1cbiAgICAgIH1cbiAgICAgIHJldHVybiBidWYuYnVmZmVyXG4gICAgfVxuICB9IGVsc2Uge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0J1ZmZlci50b0FycmF5QnVmZmVyIG5vdCBzdXBwb3J0ZWQgaW4gdGhpcyBicm93c2VyJylcbiAgfVxufVxuXG4vLyBIRUxQRVIgRlVOQ1RJT05TXG4vLyA9PT09PT09PT09PT09PT09XG5cbnZhciBCUCA9IEJ1ZmZlci5wcm90b3R5cGVcblxuLyoqXG4gKiBBdWdtZW50IGEgVWludDhBcnJheSAqaW5zdGFuY2UqIChub3QgdGhlIFVpbnQ4QXJyYXkgY2xhc3MhKSB3aXRoIEJ1ZmZlciBtZXRob2RzXG4gKi9cbkJ1ZmZlci5fYXVnbWVudCA9IGZ1bmN0aW9uIF9hdWdtZW50IChhcnIpIHtcbiAgYXJyLmNvbnN0cnVjdG9yID0gQnVmZmVyXG4gIGFyci5faXNCdWZmZXIgPSB0cnVlXG5cbiAgLy8gc2F2ZSByZWZlcmVuY2UgdG8gb3JpZ2luYWwgVWludDhBcnJheSBzZXQgbWV0aG9kIGJlZm9yZSBvdmVyd3JpdGluZ1xuICBhcnIuX3NldCA9IGFyci5zZXRcblxuICAvLyBkZXByZWNhdGVkXG4gIGFyci5nZXQgPSBCUC5nZXRcbiAgYXJyLnNldCA9IEJQLnNldFxuXG4gIGFyci53cml0ZSA9IEJQLndyaXRlXG4gIGFyci50b1N0cmluZyA9IEJQLnRvU3RyaW5nXG4gIGFyci50b0xvY2FsZVN0cmluZyA9IEJQLnRvU3RyaW5nXG4gIGFyci50b0pTT04gPSBCUC50b0pTT05cbiAgYXJyLmVxdWFscyA9IEJQLmVxdWFsc1xuICBhcnIuY29tcGFyZSA9IEJQLmNvbXBhcmVcbiAgYXJyLmluZGV4T2YgPSBCUC5pbmRleE9mXG4gIGFyci5jb3B5ID0gQlAuY29weVxuICBhcnIuc2xpY2UgPSBCUC5zbGljZVxuICBhcnIucmVhZFVJbnRMRSA9IEJQLnJlYWRVSW50TEVcbiAgYXJyLnJlYWRVSW50QkUgPSBCUC5yZWFkVUludEJFXG4gIGFyci5yZWFkVUludDggPSBCUC5yZWFkVUludDhcbiAgYXJyLnJlYWRVSW50MTZMRSA9IEJQLnJlYWRVSW50MTZMRVxuICBhcnIucmVhZFVJbnQxNkJFID0gQlAucmVhZFVJbnQxNkJFXG4gIGFyci5yZWFkVUludDMyTEUgPSBCUC5yZWFkVUludDMyTEVcbiAgYXJyLnJlYWRVSW50MzJCRSA9IEJQLnJlYWRVSW50MzJCRVxuICBhcnIucmVhZEludExFID0gQlAucmVhZEludExFXG4gIGFyci5yZWFkSW50QkUgPSBCUC5yZWFkSW50QkVcbiAgYXJyLnJlYWRJbnQ4ID0gQlAucmVhZEludDhcbiAgYXJyLnJlYWRJbnQxNkxFID0gQlAucmVhZEludDE2TEVcbiAgYXJyLnJlYWRJbnQxNkJFID0gQlAucmVhZEludDE2QkVcbiAgYXJyLnJlYWRJbnQzMkxFID0gQlAucmVhZEludDMyTEVcbiAgYXJyLnJlYWRJbnQzMkJFID0gQlAucmVhZEludDMyQkVcbiAgYXJyLnJlYWRGbG9hdExFID0gQlAucmVhZEZsb2F0TEVcbiAgYXJyLnJlYWRGbG9hdEJFID0gQlAucmVhZEZsb2F0QkVcbiAgYXJyLnJlYWREb3VibGVMRSA9IEJQLnJlYWREb3VibGVMRVxuICBhcnIucmVhZERvdWJsZUJFID0gQlAucmVhZERvdWJsZUJFXG4gIGFyci53cml0ZVVJbnQ4ID0gQlAud3JpdGVVSW50OFxuICBhcnIud3JpdGVVSW50TEUgPSBCUC53cml0ZVVJbnRMRVxuICBhcnIud3JpdGVVSW50QkUgPSBCUC53cml0ZVVJbnRCRVxuICBhcnIud3JpdGVVSW50MTZMRSA9IEJQLndyaXRlVUludDE2TEVcbiAgYXJyLndyaXRlVUludDE2QkUgPSBCUC53cml0ZVVJbnQxNkJFXG4gIGFyci53cml0ZVVJbnQzMkxFID0gQlAud3JpdGVVSW50MzJMRVxuICBhcnIud3JpdGVVSW50MzJCRSA9IEJQLndyaXRlVUludDMyQkVcbiAgYXJyLndyaXRlSW50TEUgPSBCUC53cml0ZUludExFXG4gIGFyci53cml0ZUludEJFID0gQlAud3JpdGVJbnRCRVxuICBhcnIud3JpdGVJbnQ4ID0gQlAud3JpdGVJbnQ4XG4gIGFyci53cml0ZUludDE2TEUgPSBCUC53cml0ZUludDE2TEVcbiAgYXJyLndyaXRlSW50MTZCRSA9IEJQLndyaXRlSW50MTZCRVxuICBhcnIud3JpdGVJbnQzMkxFID0gQlAud3JpdGVJbnQzMkxFXG4gIGFyci53cml0ZUludDMyQkUgPSBCUC53cml0ZUludDMyQkVcbiAgYXJyLndyaXRlRmxvYXRMRSA9IEJQLndyaXRlRmxvYXRMRVxuICBhcnIud3JpdGVGbG9hdEJFID0gQlAud3JpdGVGbG9hdEJFXG4gIGFyci53cml0ZURvdWJsZUxFID0gQlAud3JpdGVEb3VibGVMRVxuICBhcnIud3JpdGVEb3VibGVCRSA9IEJQLndyaXRlRG91YmxlQkVcbiAgYXJyLmZpbGwgPSBCUC5maWxsXG4gIGFyci5pbnNwZWN0ID0gQlAuaW5zcGVjdFxuICBhcnIudG9BcnJheUJ1ZmZlciA9IEJQLnRvQXJyYXlCdWZmZXJcblxuICByZXR1cm4gYXJyXG59XG5cbnZhciBJTlZBTElEX0JBU0U2NF9SRSA9IC9bXitcXC8wLTlBLVphLXotX10vZ1xuXG5mdW5jdGlvbiBiYXNlNjRjbGVhbiAoc3RyKSB7XG4gIC8vIE5vZGUgc3RyaXBzIG91dCBpbnZhbGlkIGNoYXJhY3RlcnMgbGlrZSBcXG4gYW5kIFxcdCBmcm9tIHRoZSBzdHJpbmcsIGJhc2U2NC1qcyBkb2VzIG5vdFxuICBzdHIgPSBzdHJpbmd0cmltKHN0cikucmVwbGFjZShJTlZBTElEX0JBU0U2NF9SRSwgJycpXG4gIC8vIE5vZGUgY29udmVydHMgc3RyaW5ncyB3aXRoIGxlbmd0aCA8IDIgdG8gJydcbiAgaWYgKHN0ci5sZW5ndGggPCAyKSByZXR1cm4gJydcbiAgLy8gTm9kZSBhbGxvd3MgZm9yIG5vbi1wYWRkZWQgYmFzZTY0IHN0cmluZ3MgKG1pc3NpbmcgdHJhaWxpbmcgPT09KSwgYmFzZTY0LWpzIGRvZXMgbm90XG4gIHdoaWxlIChzdHIubGVuZ3RoICUgNCAhPT0gMCkge1xuICAgIHN0ciA9IHN0ciArICc9J1xuICB9XG4gIHJldHVybiBzdHJcbn1cblxuZnVuY3Rpb24gc3RyaW5ndHJpbSAoc3RyKSB7XG4gIGlmIChzdHIudHJpbSkgcmV0dXJuIHN0ci50cmltKClcbiAgcmV0dXJuIHN0ci5yZXBsYWNlKC9eXFxzK3xcXHMrJC9nLCAnJylcbn1cblxuZnVuY3Rpb24gdG9IZXggKG4pIHtcbiAgaWYgKG4gPCAxNikgcmV0dXJuICcwJyArIG4udG9TdHJpbmcoMTYpXG4gIHJldHVybiBuLnRvU3RyaW5nKDE2KVxufVxuXG5mdW5jdGlvbiB1dGY4VG9CeXRlcyAoc3RyaW5nLCB1bml0cykge1xuICB1bml0cyA9IHVuaXRzIHx8IEluZmluaXR5XG4gIHZhciBjb2RlUG9pbnRcbiAgdmFyIGxlbmd0aCA9IHN0cmluZy5sZW5ndGhcbiAgdmFyIGxlYWRTdXJyb2dhdGUgPSBudWxsXG4gIHZhciBieXRlcyA9IFtdXG5cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgIGNvZGVQb2ludCA9IHN0cmluZy5jaGFyQ29kZUF0KGkpXG5cbiAgICAvLyBpcyBzdXJyb2dhdGUgY29tcG9uZW50XG4gICAgaWYgKGNvZGVQb2ludCA+IDB4RDdGRiAmJiBjb2RlUG9pbnQgPCAweEUwMDApIHtcbiAgICAgIC8vIGxhc3QgY2hhciB3YXMgYSBsZWFkXG4gICAgICBpZiAoIWxlYWRTdXJyb2dhdGUpIHtcbiAgICAgICAgLy8gbm8gbGVhZCB5ZXRcbiAgICAgICAgaWYgKGNvZGVQb2ludCA+IDB4REJGRikge1xuICAgICAgICAgIC8vIHVuZXhwZWN0ZWQgdHJhaWxcbiAgICAgICAgICBpZiAoKHVuaXRzIC09IDMpID4gLTEpIGJ5dGVzLnB1c2goMHhFRiwgMHhCRiwgMHhCRClcbiAgICAgICAgICBjb250aW51ZVxuICAgICAgICB9IGVsc2UgaWYgKGkgKyAxID09PSBsZW5ndGgpIHtcbiAgICAgICAgICAvLyB1bnBhaXJlZCBsZWFkXG4gICAgICAgICAgaWYgKCh1bml0cyAtPSAzKSA+IC0xKSBieXRlcy5wdXNoKDB4RUYsIDB4QkYsIDB4QkQpXG4gICAgICAgICAgY29udGludWVcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHZhbGlkIGxlYWRcbiAgICAgICAgbGVhZFN1cnJvZ2F0ZSA9IGNvZGVQb2ludFxuXG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB9XG5cbiAgICAgIC8vIDIgbGVhZHMgaW4gYSByb3dcbiAgICAgIGlmIChjb2RlUG9pbnQgPCAweERDMDApIHtcbiAgICAgICAgaWYgKCh1bml0cyAtPSAzKSA+IC0xKSBieXRlcy5wdXNoKDB4RUYsIDB4QkYsIDB4QkQpXG4gICAgICAgIGxlYWRTdXJyb2dhdGUgPSBjb2RlUG9pbnRcbiAgICAgICAgY29udGludWVcbiAgICAgIH1cblxuICAgICAgLy8gdmFsaWQgc3Vycm9nYXRlIHBhaXJcbiAgICAgIGNvZGVQb2ludCA9IGxlYWRTdXJyb2dhdGUgLSAweEQ4MDAgPDwgMTAgfCBjb2RlUG9pbnQgLSAweERDMDAgfCAweDEwMDAwXG4gICAgfSBlbHNlIGlmIChsZWFkU3Vycm9nYXRlKSB7XG4gICAgICAvLyB2YWxpZCBibXAgY2hhciwgYnV0IGxhc3QgY2hhciB3YXMgYSBsZWFkXG4gICAgICBpZiAoKHVuaXRzIC09IDMpID4gLTEpIGJ5dGVzLnB1c2goMHhFRiwgMHhCRiwgMHhCRClcbiAgICB9XG5cbiAgICBsZWFkU3Vycm9nYXRlID0gbnVsbFxuXG4gICAgLy8gZW5jb2RlIHV0ZjhcbiAgICBpZiAoY29kZVBvaW50IDwgMHg4MCkge1xuICAgICAgaWYgKCh1bml0cyAtPSAxKSA8IDApIGJyZWFrXG4gICAgICBieXRlcy5wdXNoKGNvZGVQb2ludClcbiAgICB9IGVsc2UgaWYgKGNvZGVQb2ludCA8IDB4ODAwKSB7XG4gICAgICBpZiAoKHVuaXRzIC09IDIpIDwgMCkgYnJlYWtcbiAgICAgIGJ5dGVzLnB1c2goXG4gICAgICAgIGNvZGVQb2ludCA+PiAweDYgfCAweEMwLFxuICAgICAgICBjb2RlUG9pbnQgJiAweDNGIHwgMHg4MFxuICAgICAgKVxuICAgIH0gZWxzZSBpZiAoY29kZVBvaW50IDwgMHgxMDAwMCkge1xuICAgICAgaWYgKCh1bml0cyAtPSAzKSA8IDApIGJyZWFrXG4gICAgICBieXRlcy5wdXNoKFxuICAgICAgICBjb2RlUG9pbnQgPj4gMHhDIHwgMHhFMCxcbiAgICAgICAgY29kZVBvaW50ID4+IDB4NiAmIDB4M0YgfCAweDgwLFxuICAgICAgICBjb2RlUG9pbnQgJiAweDNGIHwgMHg4MFxuICAgICAgKVxuICAgIH0gZWxzZSBpZiAoY29kZVBvaW50IDwgMHgxMTAwMDApIHtcbiAgICAgIGlmICgodW5pdHMgLT0gNCkgPCAwKSBicmVha1xuICAgICAgYnl0ZXMucHVzaChcbiAgICAgICAgY29kZVBvaW50ID4+IDB4MTIgfCAweEYwLFxuICAgICAgICBjb2RlUG9pbnQgPj4gMHhDICYgMHgzRiB8IDB4ODAsXG4gICAgICAgIGNvZGVQb2ludCA+PiAweDYgJiAweDNGIHwgMHg4MCxcbiAgICAgICAgY29kZVBvaW50ICYgMHgzRiB8IDB4ODBcbiAgICAgIClcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdJbnZhbGlkIGNvZGUgcG9pbnQnKVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiBieXRlc1xufVxuXG5mdW5jdGlvbiBhc2NpaVRvQnl0ZXMgKHN0cikge1xuICB2YXIgYnl0ZUFycmF5ID0gW11cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzdHIubGVuZ3RoOyBpKyspIHtcbiAgICAvLyBOb2RlJ3MgY29kZSBzZWVtcyB0byBiZSBkb2luZyB0aGlzIGFuZCBub3QgJiAweDdGLi5cbiAgICBieXRlQXJyYXkucHVzaChzdHIuY2hhckNvZGVBdChpKSAmIDB4RkYpXG4gIH1cbiAgcmV0dXJuIGJ5dGVBcnJheVxufVxuXG5mdW5jdGlvbiB1dGYxNmxlVG9CeXRlcyAoc3RyLCB1bml0cykge1xuICB2YXIgYywgaGksIGxvXG4gIHZhciBieXRlQXJyYXkgPSBbXVxuICBmb3IgKHZhciBpID0gMDsgaSA8IHN0ci5sZW5ndGg7IGkrKykge1xuICAgIGlmICgodW5pdHMgLT0gMikgPCAwKSBicmVha1xuXG4gICAgYyA9IHN0ci5jaGFyQ29kZUF0KGkpXG4gICAgaGkgPSBjID4+IDhcbiAgICBsbyA9IGMgJSAyNTZcbiAgICBieXRlQXJyYXkucHVzaChsbylcbiAgICBieXRlQXJyYXkucHVzaChoaSlcbiAgfVxuXG4gIHJldHVybiBieXRlQXJyYXlcbn1cblxuZnVuY3Rpb24gYmFzZTY0VG9CeXRlcyAoc3RyKSB7XG4gIHJldHVybiBiYXNlNjQudG9CeXRlQXJyYXkoYmFzZTY0Y2xlYW4oc3RyKSlcbn1cblxuZnVuY3Rpb24gYmxpdEJ1ZmZlciAoc3JjLCBkc3QsIG9mZnNldCwgbGVuZ3RoKSB7XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICBpZiAoKGkgKyBvZmZzZXQgPj0gZHN0Lmxlbmd0aCkgfHwgKGkgPj0gc3JjLmxlbmd0aCkpIGJyZWFrXG4gICAgZHN0W2kgKyBvZmZzZXRdID0gc3JjW2ldXG4gIH1cbiAgcmV0dXJuIGlcbn1cbiIsInZhciBsb29rdXAgPSAnQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLyc7XG5cbjsoZnVuY3Rpb24gKGV4cG9ydHMpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG4gIHZhciBBcnIgPSAodHlwZW9mIFVpbnQ4QXJyYXkgIT09ICd1bmRlZmluZWQnKVxuICAgID8gVWludDhBcnJheVxuICAgIDogQXJyYXlcblxuXHR2YXIgUExVUyAgID0gJysnLmNoYXJDb2RlQXQoMClcblx0dmFyIFNMQVNIICA9ICcvJy5jaGFyQ29kZUF0KDApXG5cdHZhciBOVU1CRVIgPSAnMCcuY2hhckNvZGVBdCgwKVxuXHR2YXIgTE9XRVIgID0gJ2EnLmNoYXJDb2RlQXQoMClcblx0dmFyIFVQUEVSICA9ICdBJy5jaGFyQ29kZUF0KDApXG5cdHZhciBQTFVTX1VSTF9TQUZFID0gJy0nLmNoYXJDb2RlQXQoMClcblx0dmFyIFNMQVNIX1VSTF9TQUZFID0gJ18nLmNoYXJDb2RlQXQoMClcblxuXHRmdW5jdGlvbiBkZWNvZGUgKGVsdCkge1xuXHRcdHZhciBjb2RlID0gZWx0LmNoYXJDb2RlQXQoMClcblx0XHRpZiAoY29kZSA9PT0gUExVUyB8fFxuXHRcdCAgICBjb2RlID09PSBQTFVTX1VSTF9TQUZFKVxuXHRcdFx0cmV0dXJuIDYyIC8vICcrJ1xuXHRcdGlmIChjb2RlID09PSBTTEFTSCB8fFxuXHRcdCAgICBjb2RlID09PSBTTEFTSF9VUkxfU0FGRSlcblx0XHRcdHJldHVybiA2MyAvLyAnLydcblx0XHRpZiAoY29kZSA8IE5VTUJFUilcblx0XHRcdHJldHVybiAtMSAvL25vIG1hdGNoXG5cdFx0aWYgKGNvZGUgPCBOVU1CRVIgKyAxMClcblx0XHRcdHJldHVybiBjb2RlIC0gTlVNQkVSICsgMjYgKyAyNlxuXHRcdGlmIChjb2RlIDwgVVBQRVIgKyAyNilcblx0XHRcdHJldHVybiBjb2RlIC0gVVBQRVJcblx0XHRpZiAoY29kZSA8IExPV0VSICsgMjYpXG5cdFx0XHRyZXR1cm4gY29kZSAtIExPV0VSICsgMjZcblx0fVxuXG5cdGZ1bmN0aW9uIGI2NFRvQnl0ZUFycmF5IChiNjQpIHtcblx0XHR2YXIgaSwgaiwgbCwgdG1wLCBwbGFjZUhvbGRlcnMsIGFyclxuXG5cdFx0aWYgKGI2NC5sZW5ndGggJSA0ID4gMCkge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKCdJbnZhbGlkIHN0cmluZy4gTGVuZ3RoIG11c3QgYmUgYSBtdWx0aXBsZSBvZiA0Jylcblx0XHR9XG5cblx0XHQvLyB0aGUgbnVtYmVyIG9mIGVxdWFsIHNpZ25zIChwbGFjZSBob2xkZXJzKVxuXHRcdC8vIGlmIHRoZXJlIGFyZSB0d28gcGxhY2Vob2xkZXJzLCB0aGFuIHRoZSB0d28gY2hhcmFjdGVycyBiZWZvcmUgaXRcblx0XHQvLyByZXByZXNlbnQgb25lIGJ5dGVcblx0XHQvLyBpZiB0aGVyZSBpcyBvbmx5IG9uZSwgdGhlbiB0aGUgdGhyZWUgY2hhcmFjdGVycyBiZWZvcmUgaXQgcmVwcmVzZW50IDIgYnl0ZXNcblx0XHQvLyB0aGlzIGlzIGp1c3QgYSBjaGVhcCBoYWNrIHRvIG5vdCBkbyBpbmRleE9mIHR3aWNlXG5cdFx0dmFyIGxlbiA9IGI2NC5sZW5ndGhcblx0XHRwbGFjZUhvbGRlcnMgPSAnPScgPT09IGI2NC5jaGFyQXQobGVuIC0gMikgPyAyIDogJz0nID09PSBiNjQuY2hhckF0KGxlbiAtIDEpID8gMSA6IDBcblxuXHRcdC8vIGJhc2U2NCBpcyA0LzMgKyB1cCB0byB0d28gY2hhcmFjdGVycyBvZiB0aGUgb3JpZ2luYWwgZGF0YVxuXHRcdGFyciA9IG5ldyBBcnIoYjY0Lmxlbmd0aCAqIDMgLyA0IC0gcGxhY2VIb2xkZXJzKVxuXG5cdFx0Ly8gaWYgdGhlcmUgYXJlIHBsYWNlaG9sZGVycywgb25seSBnZXQgdXAgdG8gdGhlIGxhc3QgY29tcGxldGUgNCBjaGFyc1xuXHRcdGwgPSBwbGFjZUhvbGRlcnMgPiAwID8gYjY0Lmxlbmd0aCAtIDQgOiBiNjQubGVuZ3RoXG5cblx0XHR2YXIgTCA9IDBcblxuXHRcdGZ1bmN0aW9uIHB1c2ggKHYpIHtcblx0XHRcdGFycltMKytdID0gdlxuXHRcdH1cblxuXHRcdGZvciAoaSA9IDAsIGogPSAwOyBpIDwgbDsgaSArPSA0LCBqICs9IDMpIHtcblx0XHRcdHRtcCA9IChkZWNvZGUoYjY0LmNoYXJBdChpKSkgPDwgMTgpIHwgKGRlY29kZShiNjQuY2hhckF0KGkgKyAxKSkgPDwgMTIpIHwgKGRlY29kZShiNjQuY2hhckF0KGkgKyAyKSkgPDwgNikgfCBkZWNvZGUoYjY0LmNoYXJBdChpICsgMykpXG5cdFx0XHRwdXNoKCh0bXAgJiAweEZGMDAwMCkgPj4gMTYpXG5cdFx0XHRwdXNoKCh0bXAgJiAweEZGMDApID4+IDgpXG5cdFx0XHRwdXNoKHRtcCAmIDB4RkYpXG5cdFx0fVxuXG5cdFx0aWYgKHBsYWNlSG9sZGVycyA9PT0gMikge1xuXHRcdFx0dG1wID0gKGRlY29kZShiNjQuY2hhckF0KGkpKSA8PCAyKSB8IChkZWNvZGUoYjY0LmNoYXJBdChpICsgMSkpID4+IDQpXG5cdFx0XHRwdXNoKHRtcCAmIDB4RkYpXG5cdFx0fSBlbHNlIGlmIChwbGFjZUhvbGRlcnMgPT09IDEpIHtcblx0XHRcdHRtcCA9IChkZWNvZGUoYjY0LmNoYXJBdChpKSkgPDwgMTApIHwgKGRlY29kZShiNjQuY2hhckF0KGkgKyAxKSkgPDwgNCkgfCAoZGVjb2RlKGI2NC5jaGFyQXQoaSArIDIpKSA+PiAyKVxuXHRcdFx0cHVzaCgodG1wID4+IDgpICYgMHhGRilcblx0XHRcdHB1c2godG1wICYgMHhGRilcblx0XHR9XG5cblx0XHRyZXR1cm4gYXJyXG5cdH1cblxuXHRmdW5jdGlvbiB1aW50OFRvQmFzZTY0ICh1aW50OCkge1xuXHRcdHZhciBpLFxuXHRcdFx0ZXh0cmFCeXRlcyA9IHVpbnQ4Lmxlbmd0aCAlIDMsIC8vIGlmIHdlIGhhdmUgMSBieXRlIGxlZnQsIHBhZCAyIGJ5dGVzXG5cdFx0XHRvdXRwdXQgPSBcIlwiLFxuXHRcdFx0dGVtcCwgbGVuZ3RoXG5cblx0XHRmdW5jdGlvbiBlbmNvZGUgKG51bSkge1xuXHRcdFx0cmV0dXJuIGxvb2t1cC5jaGFyQXQobnVtKVxuXHRcdH1cblxuXHRcdGZ1bmN0aW9uIHRyaXBsZXRUb0Jhc2U2NCAobnVtKSB7XG5cdFx0XHRyZXR1cm4gZW5jb2RlKG51bSA+PiAxOCAmIDB4M0YpICsgZW5jb2RlKG51bSA+PiAxMiAmIDB4M0YpICsgZW5jb2RlKG51bSA+PiA2ICYgMHgzRikgKyBlbmNvZGUobnVtICYgMHgzRilcblx0XHR9XG5cblx0XHQvLyBnbyB0aHJvdWdoIHRoZSBhcnJheSBldmVyeSB0aHJlZSBieXRlcywgd2UnbGwgZGVhbCB3aXRoIHRyYWlsaW5nIHN0dWZmIGxhdGVyXG5cdFx0Zm9yIChpID0gMCwgbGVuZ3RoID0gdWludDgubGVuZ3RoIC0gZXh0cmFCeXRlczsgaSA8IGxlbmd0aDsgaSArPSAzKSB7XG5cdFx0XHR0ZW1wID0gKHVpbnQ4W2ldIDw8IDE2KSArICh1aW50OFtpICsgMV0gPDwgOCkgKyAodWludDhbaSArIDJdKVxuXHRcdFx0b3V0cHV0ICs9IHRyaXBsZXRUb0Jhc2U2NCh0ZW1wKVxuXHRcdH1cblxuXHRcdC8vIHBhZCB0aGUgZW5kIHdpdGggemVyb3MsIGJ1dCBtYWtlIHN1cmUgdG8gbm90IGZvcmdldCB0aGUgZXh0cmEgYnl0ZXNcblx0XHRzd2l0Y2ggKGV4dHJhQnl0ZXMpIHtcblx0XHRcdGNhc2UgMTpcblx0XHRcdFx0dGVtcCA9IHVpbnQ4W3VpbnQ4Lmxlbmd0aCAtIDFdXG5cdFx0XHRcdG91dHB1dCArPSBlbmNvZGUodGVtcCA+PiAyKVxuXHRcdFx0XHRvdXRwdXQgKz0gZW5jb2RlKCh0ZW1wIDw8IDQpICYgMHgzRilcblx0XHRcdFx0b3V0cHV0ICs9ICc9PSdcblx0XHRcdFx0YnJlYWtcblx0XHRcdGNhc2UgMjpcblx0XHRcdFx0dGVtcCA9ICh1aW50OFt1aW50OC5sZW5ndGggLSAyXSA8PCA4KSArICh1aW50OFt1aW50OC5sZW5ndGggLSAxXSlcblx0XHRcdFx0b3V0cHV0ICs9IGVuY29kZSh0ZW1wID4+IDEwKVxuXHRcdFx0XHRvdXRwdXQgKz0gZW5jb2RlKCh0ZW1wID4+IDQpICYgMHgzRilcblx0XHRcdFx0b3V0cHV0ICs9IGVuY29kZSgodGVtcCA8PCAyKSAmIDB4M0YpXG5cdFx0XHRcdG91dHB1dCArPSAnPSdcblx0XHRcdFx0YnJlYWtcblx0XHR9XG5cblx0XHRyZXR1cm4gb3V0cHV0XG5cdH1cblxuXHRleHBvcnRzLnRvQnl0ZUFycmF5ID0gYjY0VG9CeXRlQXJyYXlcblx0ZXhwb3J0cy5mcm9tQnl0ZUFycmF5ID0gdWludDhUb0Jhc2U2NFxufSh0eXBlb2YgZXhwb3J0cyA9PT0gJ3VuZGVmaW5lZCcgPyAodGhpcy5iYXNlNjRqcyA9IHt9KSA6IGV4cG9ydHMpKVxuIiwiZXhwb3J0cy5yZWFkID0gZnVuY3Rpb24gKGJ1ZmZlciwgb2Zmc2V0LCBpc0xFLCBtTGVuLCBuQnl0ZXMpIHtcbiAgdmFyIGUsIG1cbiAgdmFyIGVMZW4gPSBuQnl0ZXMgKiA4IC0gbUxlbiAtIDFcbiAgdmFyIGVNYXggPSAoMSA8PCBlTGVuKSAtIDFcbiAgdmFyIGVCaWFzID0gZU1heCA+PiAxXG4gIHZhciBuQml0cyA9IC03XG4gIHZhciBpID0gaXNMRSA/IChuQnl0ZXMgLSAxKSA6IDBcbiAgdmFyIGQgPSBpc0xFID8gLTEgOiAxXG4gIHZhciBzID0gYnVmZmVyW29mZnNldCArIGldXG5cbiAgaSArPSBkXG5cbiAgZSA9IHMgJiAoKDEgPDwgKC1uQml0cykpIC0gMSlcbiAgcyA+Pj0gKC1uQml0cylcbiAgbkJpdHMgKz0gZUxlblxuICBmb3IgKDsgbkJpdHMgPiAwOyBlID0gZSAqIDI1NiArIGJ1ZmZlcltvZmZzZXQgKyBpXSwgaSArPSBkLCBuQml0cyAtPSA4KSB7fVxuXG4gIG0gPSBlICYgKCgxIDw8ICgtbkJpdHMpKSAtIDEpXG4gIGUgPj49ICgtbkJpdHMpXG4gIG5CaXRzICs9IG1MZW5cbiAgZm9yICg7IG5CaXRzID4gMDsgbSA9IG0gKiAyNTYgKyBidWZmZXJbb2Zmc2V0ICsgaV0sIGkgKz0gZCwgbkJpdHMgLT0gOCkge31cblxuICBpZiAoZSA9PT0gMCkge1xuICAgIGUgPSAxIC0gZUJpYXNcbiAgfSBlbHNlIGlmIChlID09PSBlTWF4KSB7XG4gICAgcmV0dXJuIG0gPyBOYU4gOiAoKHMgPyAtMSA6IDEpICogSW5maW5pdHkpXG4gIH0gZWxzZSB7XG4gICAgbSA9IG0gKyBNYXRoLnBvdygyLCBtTGVuKVxuICAgIGUgPSBlIC0gZUJpYXNcbiAgfVxuICByZXR1cm4gKHMgPyAtMSA6IDEpICogbSAqIE1hdGgucG93KDIsIGUgLSBtTGVuKVxufVxuXG5leHBvcnRzLndyaXRlID0gZnVuY3Rpb24gKGJ1ZmZlciwgdmFsdWUsIG9mZnNldCwgaXNMRSwgbUxlbiwgbkJ5dGVzKSB7XG4gIHZhciBlLCBtLCBjXG4gIHZhciBlTGVuID0gbkJ5dGVzICogOCAtIG1MZW4gLSAxXG4gIHZhciBlTWF4ID0gKDEgPDwgZUxlbikgLSAxXG4gIHZhciBlQmlhcyA9IGVNYXggPj4gMVxuICB2YXIgcnQgPSAobUxlbiA9PT0gMjMgPyBNYXRoLnBvdygyLCAtMjQpIC0gTWF0aC5wb3coMiwgLTc3KSA6IDApXG4gIHZhciBpID0gaXNMRSA/IDAgOiAobkJ5dGVzIC0gMSlcbiAgdmFyIGQgPSBpc0xFID8gMSA6IC0xXG4gIHZhciBzID0gdmFsdWUgPCAwIHx8ICh2YWx1ZSA9PT0gMCAmJiAxIC8gdmFsdWUgPCAwKSA/IDEgOiAwXG5cbiAgdmFsdWUgPSBNYXRoLmFicyh2YWx1ZSlcblxuICBpZiAoaXNOYU4odmFsdWUpIHx8IHZhbHVlID09PSBJbmZpbml0eSkge1xuICAgIG0gPSBpc05hTih2YWx1ZSkgPyAxIDogMFxuICAgIGUgPSBlTWF4XG4gIH0gZWxzZSB7XG4gICAgZSA9IE1hdGguZmxvb3IoTWF0aC5sb2codmFsdWUpIC8gTWF0aC5MTjIpXG4gICAgaWYgKHZhbHVlICogKGMgPSBNYXRoLnBvdygyLCAtZSkpIDwgMSkge1xuICAgICAgZS0tXG4gICAgICBjICo9IDJcbiAgICB9XG4gICAgaWYgKGUgKyBlQmlhcyA+PSAxKSB7XG4gICAgICB2YWx1ZSArPSBydCAvIGNcbiAgICB9IGVsc2Uge1xuICAgICAgdmFsdWUgKz0gcnQgKiBNYXRoLnBvdygyLCAxIC0gZUJpYXMpXG4gICAgfVxuICAgIGlmICh2YWx1ZSAqIGMgPj0gMikge1xuICAgICAgZSsrXG4gICAgICBjIC89IDJcbiAgICB9XG5cbiAgICBpZiAoZSArIGVCaWFzID49IGVNYXgpIHtcbiAgICAgIG0gPSAwXG4gICAgICBlID0gZU1heFxuICAgIH0gZWxzZSBpZiAoZSArIGVCaWFzID49IDEpIHtcbiAgICAgIG0gPSAodmFsdWUgKiBjIC0gMSkgKiBNYXRoLnBvdygyLCBtTGVuKVxuICAgICAgZSA9IGUgKyBlQmlhc1xuICAgIH0gZWxzZSB7XG4gICAgICBtID0gdmFsdWUgKiBNYXRoLnBvdygyLCBlQmlhcyAtIDEpICogTWF0aC5wb3coMiwgbUxlbilcbiAgICAgIGUgPSAwXG4gICAgfVxuICB9XG5cbiAgZm9yICg7IG1MZW4gPj0gODsgYnVmZmVyW29mZnNldCArIGldID0gbSAmIDB4ZmYsIGkgKz0gZCwgbSAvPSAyNTYsIG1MZW4gLT0gOCkge31cblxuICBlID0gKGUgPDwgbUxlbikgfCBtXG4gIGVMZW4gKz0gbUxlblxuICBmb3IgKDsgZUxlbiA+IDA7IGJ1ZmZlcltvZmZzZXQgKyBpXSA9IGUgJiAweGZmLCBpICs9IGQsIGUgLz0gMjU2LCBlTGVuIC09IDgpIHt9XG5cbiAgYnVmZmVyW29mZnNldCArIGkgLSBkXSB8PSBzICogMTI4XG59XG4iLCJcbi8qKlxuICogaXNBcnJheVxuICovXG5cbnZhciBpc0FycmF5ID0gQXJyYXkuaXNBcnJheTtcblxuLyoqXG4gKiB0b1N0cmluZ1xuICovXG5cbnZhciBzdHIgPSBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nO1xuXG4vKipcbiAqIFdoZXRoZXIgb3Igbm90IHRoZSBnaXZlbiBgdmFsYFxuICogaXMgYW4gYXJyYXkuXG4gKlxuICogZXhhbXBsZTpcbiAqXG4gKiAgICAgICAgaXNBcnJheShbXSk7XG4gKiAgICAgICAgLy8gPiB0cnVlXG4gKiAgICAgICAgaXNBcnJheShhcmd1bWVudHMpO1xuICogICAgICAgIC8vID4gZmFsc2VcbiAqICAgICAgICBpc0FycmF5KCcnKTtcbiAqICAgICAgICAvLyA+IGZhbHNlXG4gKlxuICogQHBhcmFtIHttaXhlZH0gdmFsXG4gKiBAcmV0dXJuIHtib29sfVxuICovXG5cbm1vZHVsZS5leHBvcnRzID0gaXNBcnJheSB8fCBmdW5jdGlvbiAodmFsKSB7XG4gIHJldHVybiAhISB2YWwgJiYgJ1tvYmplY3QgQXJyYXldJyA9PSBzdHIuY2FsbCh2YWwpO1xufTtcbiIsIi8qIGpzaGludCBub2RlOiB0cnVlICovXG4oZnVuY3Rpb24gKCkge1xuICAgIFwidXNlIHN0cmljdFwiO1xuXG4gICAgZnVuY3Rpb24gQ29va2llQWNjZXNzSW5mbyhkb21haW4sIHBhdGgsIHNlY3VyZSwgc2NyaXB0KSB7XG4gICAgICAgIGlmICh0aGlzIGluc3RhbmNlb2YgQ29va2llQWNjZXNzSW5mbykge1xuICAgICAgICAgICAgdGhpcy5kb21haW4gPSBkb21haW4gfHwgdW5kZWZpbmVkO1xuICAgICAgICAgICAgdGhpcy5wYXRoID0gcGF0aCB8fCBcIi9cIjtcbiAgICAgICAgICAgIHRoaXMuc2VjdXJlID0gISFzZWN1cmU7XG4gICAgICAgICAgICB0aGlzLnNjcmlwdCA9ICEhc2NyaXB0O1xuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG5ldyBDb29raWVBY2Nlc3NJbmZvKGRvbWFpbiwgcGF0aCwgc2VjdXJlLCBzY3JpcHQpO1xuICAgIH1cbiAgICBleHBvcnRzLkNvb2tpZUFjY2Vzc0luZm8gPSBDb29raWVBY2Nlc3NJbmZvO1xuXG4gICAgZnVuY3Rpb24gQ29va2llKGNvb2tpZXN0ciwgcmVxdWVzdF9kb21haW4sIHJlcXVlc3RfcGF0aCkge1xuICAgICAgICBpZiAoY29va2llc3RyIGluc3RhbmNlb2YgQ29va2llKSB7XG4gICAgICAgICAgICByZXR1cm4gY29va2llc3RyO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzIGluc3RhbmNlb2YgQ29va2llKSB7XG4gICAgICAgICAgICB0aGlzLm5hbWUgPSBudWxsO1xuICAgICAgICAgICAgdGhpcy52YWx1ZSA9IG51bGw7XG4gICAgICAgICAgICB0aGlzLmV4cGlyYXRpb25fZGF0ZSA9IEluZmluaXR5O1xuICAgICAgICAgICAgdGhpcy5wYXRoID0gU3RyaW5nKHJlcXVlc3RfcGF0aCB8fCBcIi9cIik7XG4gICAgICAgICAgICB0aGlzLmV4cGxpY2l0X3BhdGggPSBmYWxzZTtcbiAgICAgICAgICAgIHRoaXMuZG9tYWluID0gcmVxdWVzdF9kb21haW4gfHwgbnVsbDtcbiAgICAgICAgICAgIHRoaXMuZXhwbGljaXRfZG9tYWluID0gZmFsc2U7XG4gICAgICAgICAgICB0aGlzLnNlY3VyZSA9IGZhbHNlOyAvL2hvdyB0byBkZWZpbmUgZGVmYXVsdD9cbiAgICAgICAgICAgIHRoaXMubm9zY3JpcHQgPSBmYWxzZTsgLy9odHRwb25seVxuICAgICAgICAgICAgaWYgKGNvb2tpZXN0cikge1xuICAgICAgICAgICAgICAgIHRoaXMucGFyc2UoY29va2llc3RyLCByZXF1ZXN0X2RvbWFpbiwgcmVxdWVzdF9wYXRoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBuZXcgQ29va2llKGNvb2tpZXN0ciwgcmVxdWVzdF9kb21haW4sIHJlcXVlc3RfcGF0aCk7XG4gICAgfVxuICAgIGV4cG9ydHMuQ29va2llID0gQ29va2llO1xuXG4gICAgQ29va2llLnByb3RvdHlwZS50b1N0cmluZyA9IGZ1bmN0aW9uIHRvU3RyaW5nKCkge1xuICAgICAgICB2YXIgc3RyID0gW3RoaXMubmFtZSArIFwiPVwiICsgdGhpcy52YWx1ZV07XG4gICAgICAgIGlmICh0aGlzLmV4cGlyYXRpb25fZGF0ZSAhPT0gSW5maW5pdHkpIHtcbiAgICAgICAgICAgIHN0ci5wdXNoKFwiZXhwaXJlcz1cIiArIChuZXcgRGF0ZSh0aGlzLmV4cGlyYXRpb25fZGF0ZSkpLnRvR01UU3RyaW5nKCkpO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzLmRvbWFpbikge1xuICAgICAgICAgICAgc3RyLnB1c2goXCJkb21haW49XCIgKyB0aGlzLmRvbWFpbik7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMucGF0aCkge1xuICAgICAgICAgICAgc3RyLnB1c2goXCJwYXRoPVwiICsgdGhpcy5wYXRoKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAodGhpcy5zZWN1cmUpIHtcbiAgICAgICAgICAgIHN0ci5wdXNoKFwic2VjdXJlXCIpO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzLm5vc2NyaXB0KSB7XG4gICAgICAgICAgICBzdHIucHVzaChcImh0dHBvbmx5XCIpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzdHIuam9pbihcIjsgXCIpO1xuICAgIH07XG5cbiAgICBDb29raWUucHJvdG90eXBlLnRvVmFsdWVTdHJpbmcgPSBmdW5jdGlvbiB0b1ZhbHVlU3RyaW5nKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5uYW1lICsgXCI9XCIgKyB0aGlzLnZhbHVlO1xuICAgIH07XG5cbiAgICB2YXIgY29va2llX3N0cl9zcGxpdHRlciA9IC9bOl0oPz1cXHMqW2EtekEtWjAtOV9cXC1dK1xccypbPV0pL2c7XG4gICAgQ29va2llLnByb3RvdHlwZS5wYXJzZSA9IGZ1bmN0aW9uIHBhcnNlKHN0ciwgcmVxdWVzdF9kb21haW4sIHJlcXVlc3RfcGF0aCkge1xuICAgICAgICBpZiAodGhpcyBpbnN0YW5jZW9mIENvb2tpZSkge1xuICAgICAgICAgICAgdmFyIHBhcnRzID0gc3RyLnNwbGl0KFwiO1wiKS5maWx0ZXIoZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiAhIXZhbHVlO1xuICAgICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgICAgIHBhaXIgPSBwYXJ0c1swXS5tYXRjaCgvKFtePV0rKT0oW1xcc1xcU10qKS8pLFxuICAgICAgICAgICAgICAgIGtleSA9IHBhaXJbMV0sXG4gICAgICAgICAgICAgICAgdmFsdWUgPSBwYWlyWzJdLFxuICAgICAgICAgICAgICAgIGk7XG4gICAgICAgICAgICB0aGlzLm5hbWUgPSBrZXk7XG4gICAgICAgICAgICB0aGlzLnZhbHVlID0gdmFsdWU7XG5cbiAgICAgICAgICAgIGZvciAoaSA9IDE7IGkgPCBwYXJ0cy5sZW5ndGg7IGkgKz0gMSkge1xuICAgICAgICAgICAgICAgIHBhaXIgPSBwYXJ0c1tpXS5tYXRjaCgvKFtePV0rKSg/Oj0oW1xcc1xcU10qKSk/Lyk7XG4gICAgICAgICAgICAgICAga2V5ID0gcGFpclsxXS50cmltKCkudG9Mb3dlckNhc2UoKTtcbiAgICAgICAgICAgICAgICB2YWx1ZSA9IHBhaXJbMl07XG4gICAgICAgICAgICAgICAgc3dpdGNoIChrZXkpIHtcbiAgICAgICAgICAgICAgICBjYXNlIFwiaHR0cG9ubHlcIjpcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5ub3NjcmlwdCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIGNhc2UgXCJleHBpcmVzXCI6XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuZXhwaXJhdGlvbl9kYXRlID0gdmFsdWUgP1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIE51bWJlcihEYXRlLnBhcnNlKHZhbHVlKSkgOlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIEluZmluaXR5O1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlIFwicGF0aFwiOlxuICAgICAgICAgICAgICAgICAgICB0aGlzLnBhdGggPSB2YWx1ZSA/XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUudHJpbSgpIDpcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBcIlwiO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmV4cGxpY2l0X3BhdGggPSB0cnVlO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlIFwiZG9tYWluXCI6XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuZG9tYWluID0gdmFsdWUgP1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlLnRyaW0oKSA6XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJcIjtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5leHBsaWNpdF9kb21haW4gPSAhIXRoaXMuZG9tYWluO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlIFwic2VjdXJlXCI6XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuc2VjdXJlID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoIXRoaXMuZXhwbGljaXRfcGF0aCkge1xuICAgICAgICAgICAgICAgdGhpcy5wYXRoID0gcmVxdWVzdF9wYXRoIHx8IFwiL1wiO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKCF0aGlzLmV4cGxpY2l0X2RvbWFpbikge1xuICAgICAgICAgICAgICAgdGhpcy5kb21haW4gPSByZXF1ZXN0X2RvbWFpbjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG5ldyBDb29raWUoKS5wYXJzZShzdHIsIHJlcXVlc3RfZG9tYWluLCByZXF1ZXN0X3BhdGgpO1xuICAgIH07XG5cbiAgICBDb29raWUucHJvdG90eXBlLm1hdGNoZXMgPSBmdW5jdGlvbiBtYXRjaGVzKGFjY2Vzc19pbmZvKSB7XG4gICAgICAgIGlmICh0aGlzLm5vc2NyaXB0ICYmIGFjY2Vzc19pbmZvLnNjcmlwdCB8fFxuICAgICAgICAgICAgICAgIHRoaXMuc2VjdXJlICYmICFhY2Nlc3NfaW5mby5zZWN1cmUgfHxcbiAgICAgICAgICAgICAgICAhdGhpcy5jb2xsaWRlc1dpdGgoYWNjZXNzX2luZm8pKSB7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfTtcblxuICAgIENvb2tpZS5wcm90b3R5cGUuY29sbGlkZXNXaXRoID0gZnVuY3Rpb24gY29sbGlkZXNXaXRoKGFjY2Vzc19pbmZvKSB7XG4gICAgICAgIGlmICgodGhpcy5wYXRoICYmICFhY2Nlc3NfaW5mby5wYXRoKSB8fCAodGhpcy5kb21haW4gJiYgIWFjY2Vzc19pbmZvLmRvbWFpbikpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgICBpZiAodGhpcy5wYXRoICYmIGFjY2Vzc19pbmZvLnBhdGguaW5kZXhPZih0aGlzLnBhdGgpICE9PSAwKSB7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuZXhwbGljaXRfcGF0aCAmJiBhY2Nlc3NfaW5mby5wYXRoLmluZGV4T2YoIHRoaXMucGF0aCApICE9PSAwKSB7XG4gICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgICB2YXIgYWNjZXNzX2RvbWFpbiA9IGFjY2Vzc19pbmZvLmRvbWFpbiAmJiBhY2Nlc3NfaW5mby5kb21haW4ucmVwbGFjZSgvXltcXC5dLywnJyk7XG4gICAgICAgIHZhciBjb29raWVfZG9tYWluID0gdGhpcy5kb21haW4gJiYgdGhpcy5kb21haW4ucmVwbGFjZSgvXltcXC5dLywnJyk7XG4gICAgICAgIGlmIChjb29raWVfZG9tYWluID09PSBhY2Nlc3NfZG9tYWluKSB7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoY29va2llX2RvbWFpbikge1xuICAgICAgICAgICAgaWYgKCF0aGlzLmV4cGxpY2l0X2RvbWFpbikge1xuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTsgLy8gd2UgYWxyZWFkeSBjaGVja2VkIGlmIHRoZSBkb21haW5zIHdlcmUgZXhhY3RseSB0aGUgc2FtZVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdmFyIHdpbGRjYXJkID0gYWNjZXNzX2RvbWFpbi5pbmRleE9mKGNvb2tpZV9kb21haW4pO1xuICAgICAgICAgICAgaWYgKHdpbGRjYXJkID09PSAtMSB8fCB3aWxkY2FyZCAhPT0gYWNjZXNzX2RvbWFpbi5sZW5ndGggLSBjb29raWVfZG9tYWluLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH07XG5cbiAgICBmdW5jdGlvbiBDb29raWVKYXIoKSB7XG4gICAgICAgIHZhciBjb29raWVzLCBjb29raWVzX2xpc3QsIGNvbGxpZGFibGVfY29va2llO1xuICAgICAgICBpZiAodGhpcyBpbnN0YW5jZW9mIENvb2tpZUphcikge1xuICAgICAgICAgICAgY29va2llcyA9IE9iamVjdC5jcmVhdGUobnVsbCk7IC8vbmFtZTogW0Nvb2tpZV1cblxuICAgICAgICAgICAgdGhpcy5zZXRDb29raWUgPSBmdW5jdGlvbiBzZXRDb29raWUoY29va2llLCByZXF1ZXN0X2RvbWFpbiwgcmVxdWVzdF9wYXRoKSB7XG4gICAgICAgICAgICAgICAgdmFyIHJlbW92ZSwgaTtcbiAgICAgICAgICAgICAgICBjb29raWUgPSBuZXcgQ29va2llKGNvb2tpZSwgcmVxdWVzdF9kb21haW4sIHJlcXVlc3RfcGF0aCk7XG4gICAgICAgICAgICAgICAgLy9EZWxldGUgdGhlIGNvb2tpZSBpZiB0aGUgc2V0IGlzIHBhc3QgdGhlIGN1cnJlbnQgdGltZVxuICAgICAgICAgICAgICAgIHJlbW92ZSA9IGNvb2tpZS5leHBpcmF0aW9uX2RhdGUgPD0gRGF0ZS5ub3coKTtcbiAgICAgICAgICAgICAgICBpZiAoY29va2llc1tjb29raWUubmFtZV0gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgICAgICBjb29raWVzX2xpc3QgPSBjb29raWVzW2Nvb2tpZS5uYW1lXTtcbiAgICAgICAgICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGNvb2tpZXNfbGlzdC5sZW5ndGg7IGkgKz0gMSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgY29sbGlkYWJsZV9jb29raWUgPSBjb29raWVzX2xpc3RbaV07XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoY29sbGlkYWJsZV9jb29raWUuY29sbGlkZXNXaXRoKGNvb2tpZSkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAocmVtb3ZlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvb2tpZXNfbGlzdC5zcGxpY2UoaSwgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChjb29raWVzX2xpc3QubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxldGUgY29va2llc1tjb29raWUubmFtZV07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb29raWVzX2xpc3RbaV0gPSBjb29raWU7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGNvb2tpZTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBpZiAocmVtb3ZlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgY29va2llc19saXN0LnB1c2goY29va2llKTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGNvb2tpZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKHJlbW92ZSkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGNvb2tpZXNbY29va2llLm5hbWVdID0gW2Nvb2tpZV07XG4gICAgICAgICAgICAgICAgcmV0dXJuIGNvb2tpZXNbY29va2llLm5hbWVdO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIC8vcmV0dXJucyBhIGNvb2tpZVxuICAgICAgICAgICAgdGhpcy5nZXRDb29raWUgPSBmdW5jdGlvbiBnZXRDb29raWUoY29va2llX25hbWUsIGFjY2Vzc19pbmZvKSB7XG4gICAgICAgICAgICAgICAgdmFyIGNvb2tpZSwgaTtcbiAgICAgICAgICAgICAgICBjb29raWVzX2xpc3QgPSBjb29raWVzW2Nvb2tpZV9uYW1lXTtcbiAgICAgICAgICAgICAgICBpZiAoIWNvb2tpZXNfbGlzdCkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBjb29raWVzX2xpc3QubGVuZ3RoOyBpICs9IDEpIHtcbiAgICAgICAgICAgICAgICAgICAgY29va2llID0gY29va2llc19saXN0W2ldO1xuICAgICAgICAgICAgICAgICAgICBpZiAoY29va2llLmV4cGlyYXRpb25fZGF0ZSA8PSBEYXRlLm5vdygpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoY29va2llc19saXN0Lmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlbGV0ZSBjb29raWVzW2Nvb2tpZS5uYW1lXTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKGNvb2tpZS5tYXRjaGVzKGFjY2Vzc19pbmZvKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGNvb2tpZTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICAvL3JldHVybnMgYSBsaXN0IG9mIGNvb2tpZXNcbiAgICAgICAgICAgIHRoaXMuZ2V0Q29va2llcyA9IGZ1bmN0aW9uIGdldENvb2tpZXMoYWNjZXNzX2luZm8pIHtcbiAgICAgICAgICAgICAgICB2YXIgbWF0Y2hlcyA9IFtdLCBjb29raWVfbmFtZSwgY29va2llO1xuICAgICAgICAgICAgICAgIGZvciAoY29va2llX25hbWUgaW4gY29va2llcykge1xuICAgICAgICAgICAgICAgICAgICBjb29raWUgPSB0aGlzLmdldENvb2tpZShjb29raWVfbmFtZSwgYWNjZXNzX2luZm8pO1xuICAgICAgICAgICAgICAgICAgICBpZiAoY29va2llKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBtYXRjaGVzLnB1c2goY29va2llKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBtYXRjaGVzLnRvU3RyaW5nID0gZnVuY3Rpb24gdG9TdHJpbmcoKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBtYXRjaGVzLmpvaW4oXCI6XCIpO1xuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgbWF0Y2hlcy50b1ZhbHVlU3RyaW5nID0gZnVuY3Rpb24gdG9WYWx1ZVN0cmluZygpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG1hdGNoZXMubWFwKGZ1bmN0aW9uIChjKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gYy50b1ZhbHVlU3RyaW5nKCk7XG4gICAgICAgICAgICAgICAgICAgIH0pLmpvaW4oJzsnKTtcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgIHJldHVybiBtYXRjaGVzO1xuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG5ldyBDb29raWVKYXIoKTtcbiAgICB9XG4gICAgZXhwb3J0cy5Db29raWVKYXIgPSBDb29raWVKYXI7XG5cbiAgICAvL3JldHVybnMgbGlzdCBvZiBjb29raWVzIHRoYXQgd2VyZSBzZXQgY29ycmVjdGx5LiBDb29raWVzIHRoYXQgYXJlIGV4cGlyZWQgYW5kIHJlbW92ZWQgYXJlIG5vdCByZXR1cm5lZC5cbiAgICBDb29raWVKYXIucHJvdG90eXBlLnNldENvb2tpZXMgPSBmdW5jdGlvbiBzZXRDb29raWVzKGNvb2tpZXMsIHJlcXVlc3RfZG9tYWluLCByZXF1ZXN0X3BhdGgpIHtcbiAgICAgICAgY29va2llcyA9IEFycmF5LmlzQXJyYXkoY29va2llcykgP1xuICAgICAgICAgICAgICAgIGNvb2tpZXMgOlxuICAgICAgICAgICAgICAgIGNvb2tpZXMuc3BsaXQoY29va2llX3N0cl9zcGxpdHRlcik7XG4gICAgICAgIHZhciBzdWNjZXNzZnVsID0gW10sXG4gICAgICAgICAgICBpLFxuICAgICAgICAgICAgY29va2llO1xuICAgICAgICBjb29raWVzID0gY29va2llcy5tYXAoZnVuY3Rpb24oaXRlbSl7XG4gICAgICAgICAgICByZXR1cm4gbmV3IENvb2tpZShpdGVtLCByZXF1ZXN0X2RvbWFpbiwgcmVxdWVzdF9wYXRoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBjb29raWVzLmxlbmd0aDsgaSArPSAxKSB7XG4gICAgICAgICAgICBjb29raWUgPSBjb29raWVzW2ldO1xuICAgICAgICAgICAgaWYgKHRoaXMuc2V0Q29va2llKGNvb2tpZSwgcmVxdWVzdF9kb21haW4sIHJlcXVlc3RfcGF0aCkpIHtcbiAgICAgICAgICAgICAgICBzdWNjZXNzZnVsLnB1c2goY29va2llKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc3VjY2Vzc2Z1bDtcbiAgICB9O1xufSgpKTtcbiIsIid1c2Ugc3RyaWN0JztcblxuXG52YXIgeWFtbCA9IHJlcXVpcmUoJy4vbGliL2pzLXlhbWwuanMnKTtcblxuXG5tb2R1bGUuZXhwb3J0cyA9IHlhbWw7XG4iLCIndXNlIHN0cmljdCc7XG5cblxudmFyIGxvYWRlciA9IHJlcXVpcmUoJy4vanMteWFtbC9sb2FkZXInKTtcbnZhciBkdW1wZXIgPSByZXF1aXJlKCcuL2pzLXlhbWwvZHVtcGVyJyk7XG5cblxuZnVuY3Rpb24gZGVwcmVjYXRlZChuYW1lKSB7XG4gIHJldHVybiBmdW5jdGlvbiAoKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdGdW5jdGlvbiAnICsgbmFtZSArICcgaXMgZGVwcmVjYXRlZCBhbmQgY2Fubm90IGJlIHVzZWQuJyk7XG4gIH07XG59XG5cblxubW9kdWxlLmV4cG9ydHMuVHlwZSAgICAgICAgICAgICAgICA9IHJlcXVpcmUoJy4vanMteWFtbC90eXBlJyk7XG5tb2R1bGUuZXhwb3J0cy5TY2hlbWEgICAgICAgICAgICAgID0gcmVxdWlyZSgnLi9qcy15YW1sL3NjaGVtYScpO1xubW9kdWxlLmV4cG9ydHMuRkFJTFNBRkVfU0NIRU1BICAgICA9IHJlcXVpcmUoJy4vanMteWFtbC9zY2hlbWEvZmFpbHNhZmUnKTtcbm1vZHVsZS5leHBvcnRzLkpTT05fU0NIRU1BICAgICAgICAgPSByZXF1aXJlKCcuL2pzLXlhbWwvc2NoZW1hL2pzb24nKTtcbm1vZHVsZS5leHBvcnRzLkNPUkVfU0NIRU1BICAgICAgICAgPSByZXF1aXJlKCcuL2pzLXlhbWwvc2NoZW1hL2NvcmUnKTtcbm1vZHVsZS5leHBvcnRzLkRFRkFVTFRfU0FGRV9TQ0hFTUEgPSByZXF1aXJlKCcuL2pzLXlhbWwvc2NoZW1hL2RlZmF1bHRfc2FmZScpO1xubW9kdWxlLmV4cG9ydHMuREVGQVVMVF9GVUxMX1NDSEVNQSA9IHJlcXVpcmUoJy4vanMteWFtbC9zY2hlbWEvZGVmYXVsdF9mdWxsJyk7XG5tb2R1bGUuZXhwb3J0cy5sb2FkICAgICAgICAgICAgICAgID0gbG9hZGVyLmxvYWQ7XG5tb2R1bGUuZXhwb3J0cy5sb2FkQWxsICAgICAgICAgICAgID0gbG9hZGVyLmxvYWRBbGw7XG5tb2R1bGUuZXhwb3J0cy5zYWZlTG9hZCAgICAgICAgICAgID0gbG9hZGVyLnNhZmVMb2FkO1xubW9kdWxlLmV4cG9ydHMuc2FmZUxvYWRBbGwgICAgICAgICA9IGxvYWRlci5zYWZlTG9hZEFsbDtcbm1vZHVsZS5leHBvcnRzLmR1bXAgICAgICAgICAgICAgICAgPSBkdW1wZXIuZHVtcDtcbm1vZHVsZS5leHBvcnRzLnNhZmVEdW1wICAgICAgICAgICAgPSBkdW1wZXIuc2FmZUR1bXA7XG5tb2R1bGUuZXhwb3J0cy5ZQU1MRXhjZXB0aW9uICAgICAgID0gcmVxdWlyZSgnLi9qcy15YW1sL2V4Y2VwdGlvbicpO1xuXG4vLyBEZXByZWNhdGVkIHNjaGVtYSBuYW1lcyBmcm9tIEpTLVlBTUwgMi4wLnhcbm1vZHVsZS5leHBvcnRzLk1JTklNQUxfU0NIRU1BID0gcmVxdWlyZSgnLi9qcy15YW1sL3NjaGVtYS9mYWlsc2FmZScpO1xubW9kdWxlLmV4cG9ydHMuU0FGRV9TQ0hFTUEgICAgPSByZXF1aXJlKCcuL2pzLXlhbWwvc2NoZW1hL2RlZmF1bHRfc2FmZScpO1xubW9kdWxlLmV4cG9ydHMuREVGQVVMVF9TQ0hFTUEgPSByZXF1aXJlKCcuL2pzLXlhbWwvc2NoZW1hL2RlZmF1bHRfZnVsbCcpO1xuXG4vLyBEZXByZWNhdGVkIGZ1bmN0aW9ucyBmcm9tIEpTLVlBTUwgMS54Lnhcbm1vZHVsZS5leHBvcnRzLnNjYW4gICAgICAgICAgID0gZGVwcmVjYXRlZCgnc2NhbicpO1xubW9kdWxlLmV4cG9ydHMucGFyc2UgICAgICAgICAgPSBkZXByZWNhdGVkKCdwYXJzZScpO1xubW9kdWxlLmV4cG9ydHMuY29tcG9zZSAgICAgICAgPSBkZXByZWNhdGVkKCdjb21wb3NlJyk7XG5tb2R1bGUuZXhwb3J0cy5hZGRDb25zdHJ1Y3RvciA9IGRlcHJlY2F0ZWQoJ2FkZENvbnN0cnVjdG9yJyk7XG4iLCIndXNlIHN0cmljdCc7XG5cblxuZnVuY3Rpb24gaXNOb3RoaW5nKHN1YmplY3QpIHtcbiAgcmV0dXJuICh0eXBlb2Ygc3ViamVjdCA9PT0gJ3VuZGVmaW5lZCcpIHx8IChzdWJqZWN0ID09PSBudWxsKTtcbn1cblxuXG5mdW5jdGlvbiBpc09iamVjdChzdWJqZWN0KSB7XG4gIHJldHVybiAodHlwZW9mIHN1YmplY3QgPT09ICdvYmplY3QnKSAmJiAoc3ViamVjdCAhPT0gbnVsbCk7XG59XG5cblxuZnVuY3Rpb24gdG9BcnJheShzZXF1ZW5jZSkge1xuICBpZiAoQXJyYXkuaXNBcnJheShzZXF1ZW5jZSkpIHJldHVybiBzZXF1ZW5jZTtcbiAgZWxzZSBpZiAoaXNOb3RoaW5nKHNlcXVlbmNlKSkgcmV0dXJuIFtdO1xuXG4gIHJldHVybiBbIHNlcXVlbmNlIF07XG59XG5cblxuZnVuY3Rpb24gZXh0ZW5kKHRhcmdldCwgc291cmNlKSB7XG4gIHZhciBpbmRleCwgbGVuZ3RoLCBrZXksIHNvdXJjZUtleXM7XG5cbiAgaWYgKHNvdXJjZSkge1xuICAgIHNvdXJjZUtleXMgPSBPYmplY3Qua2V5cyhzb3VyY2UpO1xuXG4gICAgZm9yIChpbmRleCA9IDAsIGxlbmd0aCA9IHNvdXJjZUtleXMubGVuZ3RoOyBpbmRleCA8IGxlbmd0aDsgaW5kZXggKz0gMSkge1xuICAgICAga2V5ID0gc291cmNlS2V5c1tpbmRleF07XG4gICAgICB0YXJnZXRba2V5XSA9IHNvdXJjZVtrZXldO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiB0YXJnZXQ7XG59XG5cblxuZnVuY3Rpb24gcmVwZWF0KHN0cmluZywgY291bnQpIHtcbiAgdmFyIHJlc3VsdCA9ICcnLCBjeWNsZTtcblxuICBmb3IgKGN5Y2xlID0gMDsgY3ljbGUgPCBjb3VudDsgY3ljbGUgKz0gMSkge1xuICAgIHJlc3VsdCArPSBzdHJpbmc7XG4gIH1cblxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5cbmZ1bmN0aW9uIGlzTmVnYXRpdmVaZXJvKG51bWJlcikge1xuICByZXR1cm4gKG51bWJlciA9PT0gMCkgJiYgKE51bWJlci5ORUdBVElWRV9JTkZJTklUWSA9PT0gMSAvIG51bWJlcik7XG59XG5cblxubW9kdWxlLmV4cG9ydHMuaXNOb3RoaW5nICAgICAgPSBpc05vdGhpbmc7XG5tb2R1bGUuZXhwb3J0cy5pc09iamVjdCAgICAgICA9IGlzT2JqZWN0O1xubW9kdWxlLmV4cG9ydHMudG9BcnJheSAgICAgICAgPSB0b0FycmF5O1xubW9kdWxlLmV4cG9ydHMucmVwZWF0ICAgICAgICAgPSByZXBlYXQ7XG5tb2R1bGUuZXhwb3J0cy5pc05lZ2F0aXZlWmVybyA9IGlzTmVnYXRpdmVaZXJvO1xubW9kdWxlLmV4cG9ydHMuZXh0ZW5kICAgICAgICAgPSBleHRlbmQ7XG4iLCIndXNlIHN0cmljdCc7XG5cbi8qZXNsaW50LWRpc2FibGUgbm8tdXNlLWJlZm9yZS1kZWZpbmUqL1xuXG52YXIgY29tbW9uICAgICAgICAgICAgICA9IHJlcXVpcmUoJy4vY29tbW9uJyk7XG52YXIgWUFNTEV4Y2VwdGlvbiAgICAgICA9IHJlcXVpcmUoJy4vZXhjZXB0aW9uJyk7XG52YXIgREVGQVVMVF9GVUxMX1NDSEVNQSA9IHJlcXVpcmUoJy4vc2NoZW1hL2RlZmF1bHRfZnVsbCcpO1xudmFyIERFRkFVTFRfU0FGRV9TQ0hFTUEgPSByZXF1aXJlKCcuL3NjaGVtYS9kZWZhdWx0X3NhZmUnKTtcblxudmFyIF90b1N0cmluZyAgICAgICA9IE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmc7XG52YXIgX2hhc093blByb3BlcnR5ID0gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eTtcblxudmFyIENIQVJfVEFCICAgICAgICAgICAgICAgICAgPSAweDA5OyAvKiBUYWIgKi9cbnZhciBDSEFSX0xJTkVfRkVFRCAgICAgICAgICAgID0gMHgwQTsgLyogTEYgKi9cbnZhciBDSEFSX1NQQUNFICAgICAgICAgICAgICAgID0gMHgyMDsgLyogU3BhY2UgKi9cbnZhciBDSEFSX0VYQ0xBTUFUSU9OICAgICAgICAgID0gMHgyMTsgLyogISAqL1xudmFyIENIQVJfRE9VQkxFX1FVT1RFICAgICAgICAgPSAweDIyOyAvKiBcIiAqL1xudmFyIENIQVJfU0hBUlAgICAgICAgICAgICAgICAgPSAweDIzOyAvKiAjICovXG52YXIgQ0hBUl9QRVJDRU5UICAgICAgICAgICAgICA9IDB4MjU7IC8qICUgKi9cbnZhciBDSEFSX0FNUEVSU0FORCAgICAgICAgICAgID0gMHgyNjsgLyogJiAqL1xudmFyIENIQVJfU0lOR0xFX1FVT1RFICAgICAgICAgPSAweDI3OyAvKiAnICovXG52YXIgQ0hBUl9BU1RFUklTSyAgICAgICAgICAgICA9IDB4MkE7IC8qICogKi9cbnZhciBDSEFSX0NPTU1BICAgICAgICAgICAgICAgID0gMHgyQzsgLyogLCAqL1xudmFyIENIQVJfTUlOVVMgICAgICAgICAgICAgICAgPSAweDJEOyAvKiAtICovXG52YXIgQ0hBUl9DT0xPTiAgICAgICAgICAgICAgICA9IDB4M0E7IC8qIDogKi9cbnZhciBDSEFSX0dSRUFURVJfVEhBTiAgICAgICAgID0gMHgzRTsgLyogPiAqL1xudmFyIENIQVJfUVVFU1RJT04gICAgICAgICAgICAgPSAweDNGOyAvKiA/ICovXG52YXIgQ0hBUl9DT01NRVJDSUFMX0FUICAgICAgICA9IDB4NDA7IC8qIEAgKi9cbnZhciBDSEFSX0xFRlRfU1FVQVJFX0JSQUNLRVQgID0gMHg1QjsgLyogWyAqL1xudmFyIENIQVJfUklHSFRfU1FVQVJFX0JSQUNLRVQgPSAweDVEOyAvKiBdICovXG52YXIgQ0hBUl9HUkFWRV9BQ0NFTlQgICAgICAgICA9IDB4NjA7IC8qIGAgKi9cbnZhciBDSEFSX0xFRlRfQ1VSTFlfQlJBQ0tFVCAgID0gMHg3QjsgLyogeyAqL1xudmFyIENIQVJfVkVSVElDQUxfTElORSAgICAgICAgPSAweDdDOyAvKiB8ICovXG52YXIgQ0hBUl9SSUdIVF9DVVJMWV9CUkFDS0VUICA9IDB4N0Q7IC8qIH0gKi9cblxudmFyIEVTQ0FQRV9TRVFVRU5DRVMgPSB7fTtcblxuRVNDQVBFX1NFUVVFTkNFU1sweDAwXSAgID0gJ1xcXFwwJztcbkVTQ0FQRV9TRVFVRU5DRVNbMHgwN10gICA9ICdcXFxcYSc7XG5FU0NBUEVfU0VRVUVOQ0VTWzB4MDhdICAgPSAnXFxcXGInO1xuRVNDQVBFX1NFUVVFTkNFU1sweDA5XSAgID0gJ1xcXFx0JztcbkVTQ0FQRV9TRVFVRU5DRVNbMHgwQV0gICA9ICdcXFxcbic7XG5FU0NBUEVfU0VRVUVOQ0VTWzB4MEJdICAgPSAnXFxcXHYnO1xuRVNDQVBFX1NFUVVFTkNFU1sweDBDXSAgID0gJ1xcXFxmJztcbkVTQ0FQRV9TRVFVRU5DRVNbMHgwRF0gICA9ICdcXFxccic7XG5FU0NBUEVfU0VRVUVOQ0VTWzB4MUJdICAgPSAnXFxcXGUnO1xuRVNDQVBFX1NFUVVFTkNFU1sweDIyXSAgID0gJ1xcXFxcIic7XG5FU0NBUEVfU0VRVUVOQ0VTWzB4NUNdICAgPSAnXFxcXFxcXFwnO1xuRVNDQVBFX1NFUVVFTkNFU1sweDg1XSAgID0gJ1xcXFxOJztcbkVTQ0FQRV9TRVFVRU5DRVNbMHhBMF0gICA9ICdcXFxcXyc7XG5FU0NBUEVfU0VRVUVOQ0VTWzB4MjAyOF0gPSAnXFxcXEwnO1xuRVNDQVBFX1NFUVVFTkNFU1sweDIwMjldID0gJ1xcXFxQJztcblxudmFyIERFUFJFQ0FURURfQk9PTEVBTlNfU1lOVEFYID0gW1xuICAneScsICdZJywgJ3llcycsICdZZXMnLCAnWUVTJywgJ29uJywgJ09uJywgJ09OJyxcbiAgJ24nLCAnTicsICdubycsICdObycsICdOTycsICdvZmYnLCAnT2ZmJywgJ09GRidcbl07XG5cbmZ1bmN0aW9uIGNvbXBpbGVTdHlsZU1hcChzY2hlbWEsIG1hcCkge1xuICB2YXIgcmVzdWx0LCBrZXlzLCBpbmRleCwgbGVuZ3RoLCB0YWcsIHN0eWxlLCB0eXBlO1xuXG4gIGlmIChtYXAgPT09IG51bGwpIHJldHVybiB7fTtcblxuICByZXN1bHQgPSB7fTtcbiAga2V5cyA9IE9iamVjdC5rZXlzKG1hcCk7XG5cbiAgZm9yIChpbmRleCA9IDAsIGxlbmd0aCA9IGtleXMubGVuZ3RoOyBpbmRleCA8IGxlbmd0aDsgaW5kZXggKz0gMSkge1xuICAgIHRhZyA9IGtleXNbaW5kZXhdO1xuICAgIHN0eWxlID0gU3RyaW5nKG1hcFt0YWddKTtcblxuICAgIGlmICh0YWcuc2xpY2UoMCwgMikgPT09ICchIScpIHtcbiAgICAgIHRhZyA9ICd0YWc6eWFtbC5vcmcsMjAwMjonICsgdGFnLnNsaWNlKDIpO1xuICAgIH1cblxuICAgIHR5cGUgPSBzY2hlbWEuY29tcGlsZWRUeXBlTWFwW3RhZ107XG5cbiAgICBpZiAodHlwZSAmJiBfaGFzT3duUHJvcGVydHkuY2FsbCh0eXBlLnN0eWxlQWxpYXNlcywgc3R5bGUpKSB7XG4gICAgICBzdHlsZSA9IHR5cGUuc3R5bGVBbGlhc2VzW3N0eWxlXTtcbiAgICB9XG5cbiAgICByZXN1bHRbdGFnXSA9IHN0eWxlO1xuICB9XG5cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxuZnVuY3Rpb24gZW5jb2RlSGV4KGNoYXJhY3Rlcikge1xuICB2YXIgc3RyaW5nLCBoYW5kbGUsIGxlbmd0aDtcblxuICBzdHJpbmcgPSBjaGFyYWN0ZXIudG9TdHJpbmcoMTYpLnRvVXBwZXJDYXNlKCk7XG5cbiAgaWYgKGNoYXJhY3RlciA8PSAweEZGKSB7XG4gICAgaGFuZGxlID0gJ3gnO1xuICAgIGxlbmd0aCA9IDI7XG4gIH0gZWxzZSBpZiAoY2hhcmFjdGVyIDw9IDB4RkZGRikge1xuICAgIGhhbmRsZSA9ICd1JztcbiAgICBsZW5ndGggPSA0O1xuICB9IGVsc2UgaWYgKGNoYXJhY3RlciA8PSAweEZGRkZGRkZGKSB7XG4gICAgaGFuZGxlID0gJ1UnO1xuICAgIGxlbmd0aCA9IDg7XG4gIH0gZWxzZSB7XG4gICAgdGhyb3cgbmV3IFlBTUxFeGNlcHRpb24oJ2NvZGUgcG9pbnQgd2l0aGluIGEgc3RyaW5nIG1heSBub3QgYmUgZ3JlYXRlciB0aGFuIDB4RkZGRkZGRkYnKTtcbiAgfVxuXG4gIHJldHVybiAnXFxcXCcgKyBoYW5kbGUgKyBjb21tb24ucmVwZWF0KCcwJywgbGVuZ3RoIC0gc3RyaW5nLmxlbmd0aCkgKyBzdHJpbmc7XG59XG5cbmZ1bmN0aW9uIFN0YXRlKG9wdGlvbnMpIHtcbiAgdGhpcy5zY2hlbWEgICAgICAgPSBvcHRpb25zWydzY2hlbWEnXSB8fCBERUZBVUxUX0ZVTExfU0NIRU1BO1xuICB0aGlzLmluZGVudCAgICAgICA9IE1hdGgubWF4KDEsIChvcHRpb25zWydpbmRlbnQnXSB8fCAyKSk7XG4gIHRoaXMuc2tpcEludmFsaWQgID0gb3B0aW9uc1snc2tpcEludmFsaWQnXSB8fCBmYWxzZTtcbiAgdGhpcy5mbG93TGV2ZWwgICAgPSAoY29tbW9uLmlzTm90aGluZyhvcHRpb25zWydmbG93TGV2ZWwnXSkgPyAtMSA6IG9wdGlvbnNbJ2Zsb3dMZXZlbCddKTtcbiAgdGhpcy5zdHlsZU1hcCAgICAgPSBjb21waWxlU3R5bGVNYXAodGhpcy5zY2hlbWEsIG9wdGlvbnNbJ3N0eWxlcyddIHx8IG51bGwpO1xuICB0aGlzLnNvcnRLZXlzICAgICA9IG9wdGlvbnNbJ3NvcnRLZXlzJ10gfHwgZmFsc2U7XG4gIHRoaXMubGluZVdpZHRoICAgID0gb3B0aW9uc1snbGluZVdpZHRoJ10gfHwgODA7XG4gIHRoaXMubm9SZWZzICAgICAgID0gb3B0aW9uc1snbm9SZWZzJ10gfHwgZmFsc2U7XG4gIHRoaXMubm9Db21wYXRNb2RlID0gb3B0aW9uc1snbm9Db21wYXRNb2RlJ10gfHwgZmFsc2U7XG5cbiAgdGhpcy5pbXBsaWNpdFR5cGVzID0gdGhpcy5zY2hlbWEuY29tcGlsZWRJbXBsaWNpdDtcbiAgdGhpcy5leHBsaWNpdFR5cGVzID0gdGhpcy5zY2hlbWEuY29tcGlsZWRFeHBsaWNpdDtcblxuICB0aGlzLnRhZyA9IG51bGw7XG4gIHRoaXMucmVzdWx0ID0gJyc7XG5cbiAgdGhpcy5kdXBsaWNhdGVzID0gW107XG4gIHRoaXMudXNlZER1cGxpY2F0ZXMgPSBudWxsO1xufVxuXG4vLyBJbmRlbnRzIGV2ZXJ5IGxpbmUgaW4gYSBzdHJpbmcuIEVtcHR5IGxpbmVzIChcXG4gb25seSkgYXJlIG5vdCBpbmRlbnRlZC5cbmZ1bmN0aW9uIGluZGVudFN0cmluZyhzdHJpbmcsIHNwYWNlcykge1xuICB2YXIgaW5kID0gY29tbW9uLnJlcGVhdCgnICcsIHNwYWNlcyksXG4gICAgICBwb3NpdGlvbiA9IDAsXG4gICAgICBuZXh0ID0gLTEsXG4gICAgICByZXN1bHQgPSAnJyxcbiAgICAgIGxpbmUsXG4gICAgICBsZW5ndGggPSBzdHJpbmcubGVuZ3RoO1xuXG4gIHdoaWxlIChwb3NpdGlvbiA8IGxlbmd0aCkge1xuICAgIG5leHQgPSBzdHJpbmcuaW5kZXhPZignXFxuJywgcG9zaXRpb24pO1xuICAgIGlmIChuZXh0ID09PSAtMSkge1xuICAgICAgbGluZSA9IHN0cmluZy5zbGljZShwb3NpdGlvbik7XG4gICAgICBwb3NpdGlvbiA9IGxlbmd0aDtcbiAgICB9IGVsc2Uge1xuICAgICAgbGluZSA9IHN0cmluZy5zbGljZShwb3NpdGlvbiwgbmV4dCArIDEpO1xuICAgICAgcG9zaXRpb24gPSBuZXh0ICsgMTtcbiAgICB9XG5cbiAgICBpZiAobGluZS5sZW5ndGggJiYgbGluZSAhPT0gJ1xcbicpIHJlc3VsdCArPSBpbmQ7XG5cbiAgICByZXN1bHQgKz0gbGluZTtcbiAgfVxuXG4gIHJldHVybiByZXN1bHQ7XG59XG5cbmZ1bmN0aW9uIGdlbmVyYXRlTmV4dExpbmUoc3RhdGUsIGxldmVsKSB7XG4gIHJldHVybiAnXFxuJyArIGNvbW1vbi5yZXBlYXQoJyAnLCBzdGF0ZS5pbmRlbnQgKiBsZXZlbCk7XG59XG5cbmZ1bmN0aW9uIHRlc3RJbXBsaWNpdFJlc29sdmluZyhzdGF0ZSwgc3RyKSB7XG4gIHZhciBpbmRleCwgbGVuZ3RoLCB0eXBlO1xuXG4gIGZvciAoaW5kZXggPSAwLCBsZW5ndGggPSBzdGF0ZS5pbXBsaWNpdFR5cGVzLmxlbmd0aDsgaW5kZXggPCBsZW5ndGg7IGluZGV4ICs9IDEpIHtcbiAgICB0eXBlID0gc3RhdGUuaW1wbGljaXRUeXBlc1tpbmRleF07XG5cbiAgICBpZiAodHlwZS5yZXNvbHZlKHN0cikpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBmYWxzZTtcbn1cblxuLy8gWzMzXSBzLXdoaXRlIDo6PSBzLXNwYWNlIHwgcy10YWJcbmZ1bmN0aW9uIGlzV2hpdGVzcGFjZShjKSB7XG4gIHJldHVybiBjID09PSBDSEFSX1NQQUNFIHx8IGMgPT09IENIQVJfVEFCO1xufVxuXG4vLyBSZXR1cm5zIHRydWUgaWYgdGhlIGNoYXJhY3RlciBjYW4gYmUgcHJpbnRlZCB3aXRob3V0IGVzY2FwaW5nLlxuLy8gRnJvbSBZQU1MIDEuMjogXCJhbnkgYWxsb3dlZCBjaGFyYWN0ZXJzIGtub3duIHRvIGJlIG5vbi1wcmludGFibGVcbi8vIHNob3VsZCBhbHNvIGJlIGVzY2FwZWQuIFtIb3dldmVyLF0gVGhpcyBpc27igJl0IG1hbmRhdG9yeVwiXG4vLyBEZXJpdmVkIGZyb20gbmItY2hhciAtIFxcdCAtICN4ODUgLSAjeEEwIC0gI3gyMDI4IC0gI3gyMDI5LlxuZnVuY3Rpb24gaXNQcmludGFibGUoYykge1xuICByZXR1cm4gICgweDAwMDIwIDw9IGMgJiYgYyA8PSAweDAwMDA3RSlcbiAgICAgIHx8ICgoMHgwMDBBMSA8PSBjICYmIGMgPD0gMHgwMEQ3RkYpICYmIGMgIT09IDB4MjAyOCAmJiBjICE9PSAweDIwMjkpXG4gICAgICB8fCAoKDB4MEUwMDAgPD0gYyAmJiBjIDw9IDB4MDBGRkZEKSAmJiBjICE9PSAweEZFRkYgLyogQk9NICovKVxuICAgICAgfHwgICgweDEwMDAwIDw9IGMgJiYgYyA8PSAweDEwRkZGRik7XG59XG5cbi8vIFNpbXBsaWZpZWQgdGVzdCBmb3IgdmFsdWVzIGFsbG93ZWQgYWZ0ZXIgdGhlIGZpcnN0IGNoYXJhY3RlciBpbiBwbGFpbiBzdHlsZS5cbmZ1bmN0aW9uIGlzUGxhaW5TYWZlKGMpIHtcbiAgLy8gVXNlcyBhIHN1YnNldCBvZiBuYi1jaGFyIC0gYy1mbG93LWluZGljYXRvciAtIFwiOlwiIC0gXCIjXCJcbiAgLy8gd2hlcmUgbmItY2hhciA6Oj0gYy1wcmludGFibGUgLSBiLWNoYXIgLSBjLWJ5dGUtb3JkZXItbWFyay5cbiAgcmV0dXJuIGlzUHJpbnRhYmxlKGMpICYmIGMgIT09IDB4RkVGRlxuICAgIC8vIC0gYy1mbG93LWluZGljYXRvclxuICAgICYmIGMgIT09IENIQVJfQ09NTUFcbiAgICAmJiBjICE9PSBDSEFSX0xFRlRfU1FVQVJFX0JSQUNLRVRcbiAgICAmJiBjICE9PSBDSEFSX1JJR0hUX1NRVUFSRV9CUkFDS0VUXG4gICAgJiYgYyAhPT0gQ0hBUl9MRUZUX0NVUkxZX0JSQUNLRVRcbiAgICAmJiBjICE9PSBDSEFSX1JJR0hUX0NVUkxZX0JSQUNLRVRcbiAgICAvLyAtIFwiOlwiIC0gXCIjXCJcbiAgICAmJiBjICE9PSBDSEFSX0NPTE9OXG4gICAgJiYgYyAhPT0gQ0hBUl9TSEFSUDtcbn1cblxuLy8gU2ltcGxpZmllZCB0ZXN0IGZvciB2YWx1ZXMgYWxsb3dlZCBhcyB0aGUgZmlyc3QgY2hhcmFjdGVyIGluIHBsYWluIHN0eWxlLlxuZnVuY3Rpb24gaXNQbGFpblNhZmVGaXJzdChjKSB7XG4gIC8vIFVzZXMgYSBzdWJzZXQgb2YgbnMtY2hhciAtIGMtaW5kaWNhdG9yXG4gIC8vIHdoZXJlIG5zLWNoYXIgPSBuYi1jaGFyIC0gcy13aGl0ZS5cbiAgcmV0dXJuIGlzUHJpbnRhYmxlKGMpICYmIGMgIT09IDB4RkVGRlxuICAgICYmICFpc1doaXRlc3BhY2UoYykgLy8gLSBzLXdoaXRlXG4gICAgLy8gLSAoYy1pbmRpY2F0b3IgOjo9XG4gICAgLy8g4oCcLeKAnSB8IOKAnD/igJ0gfCDigJw64oCdIHwg4oCcLOKAnSB8IOKAnFvigJ0gfCDigJxd4oCdIHwg4oCce+KAnSB8IOKAnH3igJ1cbiAgICAmJiBjICE9PSBDSEFSX01JTlVTXG4gICAgJiYgYyAhPT0gQ0hBUl9RVUVTVElPTlxuICAgICYmIGMgIT09IENIQVJfQ09MT05cbiAgICAmJiBjICE9PSBDSEFSX0NPTU1BXG4gICAgJiYgYyAhPT0gQ0hBUl9MRUZUX1NRVUFSRV9CUkFDS0VUXG4gICAgJiYgYyAhPT0gQ0hBUl9SSUdIVF9TUVVBUkVfQlJBQ0tFVFxuICAgICYmIGMgIT09IENIQVJfTEVGVF9DVVJMWV9CUkFDS0VUXG4gICAgJiYgYyAhPT0gQ0hBUl9SSUdIVF9DVVJMWV9CUkFDS0VUXG4gICAgLy8gfCDigJwj4oCdIHwg4oCcJuKAnSB8IOKAnCrigJ0gfCDigJwh4oCdIHwg4oCcfOKAnSB8IOKAnD7igJ0gfCDigJwn4oCdIHwg4oCcXCLigJ1cbiAgICAmJiBjICE9PSBDSEFSX1NIQVJQXG4gICAgJiYgYyAhPT0gQ0hBUl9BTVBFUlNBTkRcbiAgICAmJiBjICE9PSBDSEFSX0FTVEVSSVNLXG4gICAgJiYgYyAhPT0gQ0hBUl9FWENMQU1BVElPTlxuICAgICYmIGMgIT09IENIQVJfVkVSVElDQUxfTElORVxuICAgICYmIGMgIT09IENIQVJfR1JFQVRFUl9USEFOXG4gICAgJiYgYyAhPT0gQ0hBUl9TSU5HTEVfUVVPVEVcbiAgICAmJiBjICE9PSBDSEFSX0RPVUJMRV9RVU9URVxuICAgIC8vIHwg4oCcJeKAnSB8IOKAnEDigJ0gfCDigJxg4oCdKVxuICAgICYmIGMgIT09IENIQVJfUEVSQ0VOVFxuICAgICYmIGMgIT09IENIQVJfQ09NTUVSQ0lBTF9BVFxuICAgICYmIGMgIT09IENIQVJfR1JBVkVfQUNDRU5UO1xufVxuXG52YXIgU1RZTEVfUExBSU4gICA9IDEsXG4gICAgU1RZTEVfU0lOR0xFICA9IDIsXG4gICAgU1RZTEVfTElURVJBTCA9IDMsXG4gICAgU1RZTEVfRk9MREVEICA9IDQsXG4gICAgU1RZTEVfRE9VQkxFICA9IDU7XG5cbi8vIERldGVybWluZXMgd2hpY2ggc2NhbGFyIHN0eWxlcyBhcmUgcG9zc2libGUgYW5kIHJldHVybnMgdGhlIHByZWZlcnJlZCBzdHlsZS5cbi8vIGxpbmVXaWR0aCA9IC0xID0+IG5vIGxpbWl0LlxuLy8gUHJlLWNvbmRpdGlvbnM6IHN0ci5sZW5ndGggPiAwLlxuLy8gUG9zdC1jb25kaXRpb25zOlxuLy8gICAgU1RZTEVfUExBSU4gb3IgU1RZTEVfU0lOR0xFID0+IG5vIFxcbiBhcmUgaW4gdGhlIHN0cmluZy5cbi8vICAgIFNUWUxFX0xJVEVSQUwgPT4gbm8gbGluZXMgYXJlIHN1aXRhYmxlIGZvciBmb2xkaW5nIChvciBsaW5lV2lkdGggaXMgLTEpLlxuLy8gICAgU1RZTEVfRk9MREVEID0+IGEgbGluZSA+IGxpbmVXaWR0aCBhbmQgY2FuIGJlIGZvbGRlZCAoYW5kIGxpbmVXaWR0aCAhPSAtMSkuXG5mdW5jdGlvbiBjaG9vc2VTY2FsYXJTdHlsZShzdHJpbmcsIHNpbmdsZUxpbmVPbmx5LCBpbmRlbnRQZXJMZXZlbCwgbGluZVdpZHRoLCB0ZXN0QW1iaWd1b3VzVHlwZSkge1xuICB2YXIgaTtcbiAgdmFyIGNoYXI7XG4gIHZhciBoYXNMaW5lQnJlYWsgPSBmYWxzZTtcbiAgdmFyIGhhc0ZvbGRhYmxlTGluZSA9IGZhbHNlOyAvLyBvbmx5IGNoZWNrZWQgaWYgc2hvdWxkVHJhY2tXaWR0aFxuICB2YXIgc2hvdWxkVHJhY2tXaWR0aCA9IGxpbmVXaWR0aCAhPT0gLTE7XG4gIHZhciBwcmV2aW91c0xpbmVCcmVhayA9IC0xOyAvLyBjb3VudCB0aGUgZmlyc3QgbGluZSBjb3JyZWN0bHlcbiAgdmFyIHBsYWluID0gaXNQbGFpblNhZmVGaXJzdChzdHJpbmcuY2hhckNvZGVBdCgwKSlcbiAgICAgICAgICAmJiAhaXNXaGl0ZXNwYWNlKHN0cmluZy5jaGFyQ29kZUF0KHN0cmluZy5sZW5ndGggLSAxKSk7XG5cbiAgaWYgKHNpbmdsZUxpbmVPbmx5KSB7XG4gICAgLy8gQ2FzZTogbm8gYmxvY2sgc3R5bGVzLlxuICAgIC8vIENoZWNrIGZvciBkaXNhbGxvd2VkIGNoYXJhY3RlcnMgdG8gcnVsZSBvdXQgcGxhaW4gYW5kIHNpbmdsZS5cbiAgICBmb3IgKGkgPSAwOyBpIDwgc3RyaW5nLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjaGFyID0gc3RyaW5nLmNoYXJDb2RlQXQoaSk7XG4gICAgICBpZiAoIWlzUHJpbnRhYmxlKGNoYXIpKSB7XG4gICAgICAgIHJldHVybiBTVFlMRV9ET1VCTEU7XG4gICAgICB9XG4gICAgICBwbGFpbiA9IHBsYWluICYmIGlzUGxhaW5TYWZlKGNoYXIpO1xuICAgIH1cbiAgfSBlbHNlIHtcbiAgICAvLyBDYXNlOiBibG9jayBzdHlsZXMgcGVybWl0dGVkLlxuICAgIGZvciAoaSA9IDA7IGkgPCBzdHJpbmcubGVuZ3RoOyBpKyspIHtcbiAgICAgIGNoYXIgPSBzdHJpbmcuY2hhckNvZGVBdChpKTtcbiAgICAgIGlmIChjaGFyID09PSBDSEFSX0xJTkVfRkVFRCkge1xuICAgICAgICBoYXNMaW5lQnJlYWsgPSB0cnVlO1xuICAgICAgICAvLyBDaGVjayBpZiBhbnkgbGluZSBjYW4gYmUgZm9sZGVkLlxuICAgICAgICBpZiAoc2hvdWxkVHJhY2tXaWR0aCkge1xuICAgICAgICAgIGhhc0ZvbGRhYmxlTGluZSA9IGhhc0ZvbGRhYmxlTGluZSB8fFxuICAgICAgICAgICAgLy8gRm9sZGFibGUgbGluZSA9IHRvbyBsb25nLCBhbmQgbm90IG1vcmUtaW5kZW50ZWQuXG4gICAgICAgICAgICAoaSAtIHByZXZpb3VzTGluZUJyZWFrIC0gMSA+IGxpbmVXaWR0aCAmJlxuICAgICAgICAgICAgIHN0cmluZ1twcmV2aW91c0xpbmVCcmVhayArIDFdICE9PSAnICcpO1xuICAgICAgICAgIHByZXZpb3VzTGluZUJyZWFrID0gaTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIGlmICghaXNQcmludGFibGUoY2hhcikpIHtcbiAgICAgICAgcmV0dXJuIFNUWUxFX0RPVUJMRTtcbiAgICAgIH1cbiAgICAgIHBsYWluID0gcGxhaW4gJiYgaXNQbGFpblNhZmUoY2hhcik7XG4gICAgfVxuICAgIC8vIGluIGNhc2UgdGhlIGVuZCBpcyBtaXNzaW5nIGEgXFxuXG4gICAgaGFzRm9sZGFibGVMaW5lID0gaGFzRm9sZGFibGVMaW5lIHx8IChzaG91bGRUcmFja1dpZHRoICYmXG4gICAgICAoaSAtIHByZXZpb3VzTGluZUJyZWFrIC0gMSA+IGxpbmVXaWR0aCAmJlxuICAgICAgIHN0cmluZ1twcmV2aW91c0xpbmVCcmVhayArIDFdICE9PSAnICcpKTtcbiAgfVxuICAvLyBBbHRob3VnaCBldmVyeSBzdHlsZSBjYW4gcmVwcmVzZW50IFxcbiB3aXRob3V0IGVzY2FwaW5nLCBwcmVmZXIgYmxvY2sgc3R5bGVzXG4gIC8vIGZvciBtdWx0aWxpbmUsIHNpbmNlIHRoZXkncmUgbW9yZSByZWFkYWJsZSBhbmQgdGhleSBkb24ndCBhZGQgZW1wdHkgbGluZXMuXG4gIC8vIEFsc28gcHJlZmVyIGZvbGRpbmcgYSBzdXBlci1sb25nIGxpbmUuXG4gIGlmICghaGFzTGluZUJyZWFrICYmICFoYXNGb2xkYWJsZUxpbmUpIHtcbiAgICAvLyBTdHJpbmdzIGludGVycHJldGFibGUgYXMgYW5vdGhlciB0eXBlIGhhdmUgdG8gYmUgcXVvdGVkO1xuICAgIC8vIGUuZy4gdGhlIHN0cmluZyAndHJ1ZScgdnMuIHRoZSBib29sZWFuIHRydWUuXG4gICAgcmV0dXJuIHBsYWluICYmICF0ZXN0QW1iaWd1b3VzVHlwZShzdHJpbmcpXG4gICAgICA/IFNUWUxFX1BMQUlOIDogU1RZTEVfU0lOR0xFO1xuICB9XG4gIC8vIEVkZ2UgY2FzZTogYmxvY2sgaW5kZW50YXRpb24gaW5kaWNhdG9yIGNhbiBvbmx5IGhhdmUgb25lIGRpZ2l0LlxuICBpZiAoc3RyaW5nWzBdID09PSAnICcgJiYgaW5kZW50UGVyTGV2ZWwgPiA5KSB7XG4gICAgcmV0dXJuIFNUWUxFX0RPVUJMRTtcbiAgfVxuICAvLyBBdCB0aGlzIHBvaW50IHdlIGtub3cgYmxvY2sgc3R5bGVzIGFyZSB2YWxpZC5cbiAgLy8gUHJlZmVyIGxpdGVyYWwgc3R5bGUgdW5sZXNzIHdlIHdhbnQgdG8gZm9sZC5cbiAgcmV0dXJuIGhhc0ZvbGRhYmxlTGluZSA/IFNUWUxFX0ZPTERFRCA6IFNUWUxFX0xJVEVSQUw7XG59XG5cbi8vIE5vdGU6IGxpbmUgYnJlYWtpbmcvZm9sZGluZyBpcyBpbXBsZW1lbnRlZCBmb3Igb25seSB0aGUgZm9sZGVkIHN0eWxlLlxuLy8gTkIuIFdlIGRyb3AgdGhlIGxhc3QgdHJhaWxpbmcgbmV3bGluZSAoaWYgYW55KSBvZiBhIHJldHVybmVkIGJsb2NrIHNjYWxhclxuLy8gIHNpbmNlIHRoZSBkdW1wZXIgYWRkcyBpdHMgb3duIG5ld2xpbmUuIFRoaXMgYWx3YXlzIHdvcmtzOlxuLy8gICAg4oCiIE5vIGVuZGluZyBuZXdsaW5lID0+IHVuYWZmZWN0ZWQ7IGFscmVhZHkgdXNpbmcgc3RyaXAgXCItXCIgY2hvbXBpbmcuXG4vLyAgICDigKIgRW5kaW5nIG5ld2xpbmUgICAgPT4gcmVtb3ZlZCB0aGVuIHJlc3RvcmVkLlxuLy8gIEltcG9ydGFudGx5LCB0aGlzIGtlZXBzIHRoZSBcIitcIiBjaG9tcCBpbmRpY2F0b3IgZnJvbSBnYWluaW5nIGFuIGV4dHJhIGxpbmUuXG5mdW5jdGlvbiB3cml0ZVNjYWxhcihzdGF0ZSwgc3RyaW5nLCBsZXZlbCwgaXNrZXkpIHtcbiAgc3RhdGUuZHVtcCA9IChmdW5jdGlvbiAoKSB7XG4gICAgaWYgKHN0cmluZy5sZW5ndGggPT09IDApIHtcbiAgICAgIHJldHVybiBcIicnXCI7XG4gICAgfVxuICAgIGlmICghc3RhdGUubm9Db21wYXRNb2RlICYmXG4gICAgICAgIERFUFJFQ0FURURfQk9PTEVBTlNfU1lOVEFYLmluZGV4T2Yoc3RyaW5nKSAhPT0gLTEpIHtcbiAgICAgIHJldHVybiBcIidcIiArIHN0cmluZyArIFwiJ1wiO1xuICAgIH1cblxuICAgIHZhciBpbmRlbnQgPSBzdGF0ZS5pbmRlbnQgKiBNYXRoLm1heCgxLCBsZXZlbCk7IC8vIG5vIDAtaW5kZW50IHNjYWxhcnNcbiAgICAvLyBBcyBpbmRlbnRhdGlvbiBnZXRzIGRlZXBlciwgbGV0IHRoZSB3aWR0aCBkZWNyZWFzZSBtb25vdG9uaWNhbGx5XG4gICAgLy8gdG8gdGhlIGxvd2VyIGJvdW5kIG1pbihzdGF0ZS5saW5lV2lkdGgsIDQwKS5cbiAgICAvLyBOb3RlIHRoYXQgdGhpcyBpbXBsaWVzXG4gICAgLy8gIHN0YXRlLmxpbmVXaWR0aCDiiaQgNDAgKyBzdGF0ZS5pbmRlbnQ6IHdpZHRoIGlzIGZpeGVkIGF0IHRoZSBsb3dlciBib3VuZC5cbiAgICAvLyAgc3RhdGUubGluZVdpZHRoID4gNDAgKyBzdGF0ZS5pbmRlbnQ6IHdpZHRoIGRlY3JlYXNlcyB1bnRpbCB0aGUgbG93ZXIgYm91bmQuXG4gICAgLy8gVGhpcyBiZWhhdmVzIGJldHRlciB0aGFuIGEgY29uc3RhbnQgbWluaW11bSB3aWR0aCB3aGljaCBkaXNhbGxvd3MgbmFycm93ZXIgb3B0aW9ucyxcbiAgICAvLyBvciBhbiBpbmRlbnQgdGhyZXNob2xkIHdoaWNoIGNhdXNlcyB0aGUgd2lkdGggdG8gc3VkZGVubHkgaW5jcmVhc2UuXG4gICAgdmFyIGxpbmVXaWR0aCA9IHN0YXRlLmxpbmVXaWR0aCA9PT0gLTFcbiAgICAgID8gLTEgOiBNYXRoLm1heChNYXRoLm1pbihzdGF0ZS5saW5lV2lkdGgsIDQwKSwgc3RhdGUubGluZVdpZHRoIC0gaW5kZW50KTtcblxuICAgIC8vIFdpdGhvdXQga25vd2luZyBpZiBrZXlzIGFyZSBpbXBsaWNpdC9leHBsaWNpdCwgYXNzdW1lIGltcGxpY2l0IGZvciBzYWZldHkuXG4gICAgdmFyIHNpbmdsZUxpbmVPbmx5ID0gaXNrZXlcbiAgICAgIC8vIE5vIGJsb2NrIHN0eWxlcyBpbiBmbG93IG1vZGUuXG4gICAgICB8fCAoc3RhdGUuZmxvd0xldmVsID4gLTEgJiYgbGV2ZWwgPj0gc3RhdGUuZmxvd0xldmVsKTtcbiAgICBmdW5jdGlvbiB0ZXN0QW1iaWd1aXR5KHN0cmluZykge1xuICAgICAgcmV0dXJuIHRlc3RJbXBsaWNpdFJlc29sdmluZyhzdGF0ZSwgc3RyaW5nKTtcbiAgICB9XG5cbiAgICBzd2l0Y2ggKGNob29zZVNjYWxhclN0eWxlKHN0cmluZywgc2luZ2xlTGluZU9ubHksIHN0YXRlLmluZGVudCwgbGluZVdpZHRoLCB0ZXN0QW1iaWd1aXR5KSkge1xuICAgICAgY2FzZSBTVFlMRV9QTEFJTjpcbiAgICAgICAgcmV0dXJuIHN0cmluZztcbiAgICAgIGNhc2UgU1RZTEVfU0lOR0xFOlxuICAgICAgICByZXR1cm4gXCInXCIgKyBzdHJpbmcucmVwbGFjZSgvJy9nLCBcIicnXCIpICsgXCInXCI7XG4gICAgICBjYXNlIFNUWUxFX0xJVEVSQUw6XG4gICAgICAgIHJldHVybiAnfCcgKyBibG9ja0hlYWRlcihzdHJpbmcsIHN0YXRlLmluZGVudClcbiAgICAgICAgICArIGRyb3BFbmRpbmdOZXdsaW5lKGluZGVudFN0cmluZyhzdHJpbmcsIGluZGVudCkpO1xuICAgICAgY2FzZSBTVFlMRV9GT0xERUQ6XG4gICAgICAgIHJldHVybiAnPicgKyBibG9ja0hlYWRlcihzdHJpbmcsIHN0YXRlLmluZGVudClcbiAgICAgICAgICArIGRyb3BFbmRpbmdOZXdsaW5lKGluZGVudFN0cmluZyhmb2xkU3RyaW5nKHN0cmluZywgbGluZVdpZHRoKSwgaW5kZW50KSk7XG4gICAgICBjYXNlIFNUWUxFX0RPVUJMRTpcbiAgICAgICAgcmV0dXJuICdcIicgKyBlc2NhcGVTdHJpbmcoc3RyaW5nLCBsaW5lV2lkdGgpICsgJ1wiJztcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHRocm93IG5ldyBZQU1MRXhjZXB0aW9uKCdpbXBvc3NpYmxlIGVycm9yOiBpbnZhbGlkIHNjYWxhciBzdHlsZScpO1xuICAgIH1cbiAgfSgpKTtcbn1cblxuLy8gUHJlLWNvbmRpdGlvbnM6IHN0cmluZyBpcyB2YWxpZCBmb3IgYSBibG9jayBzY2FsYXIsIDEgPD0gaW5kZW50UGVyTGV2ZWwgPD0gOS5cbmZ1bmN0aW9uIGJsb2NrSGVhZGVyKHN0cmluZywgaW5kZW50UGVyTGV2ZWwpIHtcbiAgdmFyIGluZGVudEluZGljYXRvciA9IChzdHJpbmdbMF0gPT09ICcgJykgPyBTdHJpbmcoaW5kZW50UGVyTGV2ZWwpIDogJyc7XG5cbiAgLy8gbm90ZSB0aGUgc3BlY2lhbCBjYXNlOiB0aGUgc3RyaW5nICdcXG4nIGNvdW50cyBhcyBhIFwidHJhaWxpbmdcIiBlbXB0eSBsaW5lLlxuICB2YXIgY2xpcCA9ICAgICAgICAgIHN0cmluZ1tzdHJpbmcubGVuZ3RoIC0gMV0gPT09ICdcXG4nO1xuICB2YXIga2VlcCA9IGNsaXAgJiYgKHN0cmluZ1tzdHJpbmcubGVuZ3RoIC0gMl0gPT09ICdcXG4nIHx8IHN0cmluZyA9PT0gJ1xcbicpO1xuICB2YXIgY2hvbXAgPSBrZWVwID8gJysnIDogKGNsaXAgPyAnJyA6ICctJyk7XG5cbiAgcmV0dXJuIGluZGVudEluZGljYXRvciArIGNob21wICsgJ1xcbic7XG59XG5cbi8vIChTZWUgdGhlIG5vdGUgZm9yIHdyaXRlU2NhbGFyLilcbmZ1bmN0aW9uIGRyb3BFbmRpbmdOZXdsaW5lKHN0cmluZykge1xuICByZXR1cm4gc3RyaW5nW3N0cmluZy5sZW5ndGggLSAxXSA9PT0gJ1xcbicgPyBzdHJpbmcuc2xpY2UoMCwgLTEpIDogc3RyaW5nO1xufVxuXG4vLyBOb3RlOiBhIGxvbmcgbGluZSB3aXRob3V0IGEgc3VpdGFibGUgYnJlYWsgcG9pbnQgd2lsbCBleGNlZWQgdGhlIHdpZHRoIGxpbWl0LlxuLy8gUHJlLWNvbmRpdGlvbnM6IGV2ZXJ5IGNoYXIgaW4gc3RyIGlzUHJpbnRhYmxlLCBzdHIubGVuZ3RoID4gMCwgd2lkdGggPiAwLlxuZnVuY3Rpb24gZm9sZFN0cmluZyhzdHJpbmcsIHdpZHRoKSB7XG4gIC8vIEluIGZvbGRlZCBzdHlsZSwgJGskIGNvbnNlY3V0aXZlIG5ld2xpbmVzIG91dHB1dCBhcyAkaysxJCBuZXdsaW5lc+KAlFxuICAvLyB1bmxlc3MgdGhleSdyZSBiZWZvcmUgb3IgYWZ0ZXIgYSBtb3JlLWluZGVudGVkIGxpbmUsIG9yIGF0IHRoZSB2ZXJ5XG4gIC8vIGJlZ2lubmluZyBvciBlbmQsIGluIHdoaWNoIGNhc2UgJGskIG1hcHMgdG8gJGskLlxuICAvLyBUaGVyZWZvcmUsIHBhcnNlIGVhY2ggY2h1bmsgYXMgbmV3bGluZShzKSBmb2xsb3dlZCBieSBhIGNvbnRlbnQgbGluZS5cbiAgdmFyIGxpbmVSZSA9IC8oXFxuKykoW15cXG5dKikvZztcblxuICAvLyBmaXJzdCBsaW5lIChwb3NzaWJseSBhbiBlbXB0eSBsaW5lKVxuICB2YXIgcmVzdWx0ID0gKGZ1bmN0aW9uICgpIHtcbiAgICB2YXIgbmV4dExGID0gc3RyaW5nLmluZGV4T2YoJ1xcbicpO1xuICAgIG5leHRMRiA9IG5leHRMRiAhPT0gLTEgPyBuZXh0TEYgOiBzdHJpbmcubGVuZ3RoO1xuICAgIGxpbmVSZS5sYXN0SW5kZXggPSBuZXh0TEY7XG4gICAgcmV0dXJuIGZvbGRMaW5lKHN0cmluZy5zbGljZSgwLCBuZXh0TEYpLCB3aWR0aCk7XG4gIH0oKSk7XG4gIC8vIElmIHdlIGhhdmVuJ3QgcmVhY2hlZCB0aGUgZmlyc3QgY29udGVudCBsaW5lIHlldCwgZG9uJ3QgYWRkIGFuIGV4dHJhIFxcbi5cbiAgdmFyIHByZXZNb3JlSW5kZW50ZWQgPSBzdHJpbmdbMF0gPT09ICdcXG4nIHx8IHN0cmluZ1swXSA9PT0gJyAnO1xuICB2YXIgbW9yZUluZGVudGVkO1xuXG4gIC8vIHJlc3Qgb2YgdGhlIGxpbmVzXG4gIHZhciBtYXRjaDtcbiAgd2hpbGUgKChtYXRjaCA9IGxpbmVSZS5leGVjKHN0cmluZykpKSB7XG4gICAgdmFyIHByZWZpeCA9IG1hdGNoWzFdLCBsaW5lID0gbWF0Y2hbMl07XG4gICAgbW9yZUluZGVudGVkID0gKGxpbmVbMF0gPT09ICcgJyk7XG4gICAgcmVzdWx0ICs9IHByZWZpeFxuICAgICAgKyAoIXByZXZNb3JlSW5kZW50ZWQgJiYgIW1vcmVJbmRlbnRlZCAmJiBsaW5lICE9PSAnJ1xuICAgICAgICA/ICdcXG4nIDogJycpXG4gICAgICArIGZvbGRMaW5lKGxpbmUsIHdpZHRoKTtcbiAgICBwcmV2TW9yZUluZGVudGVkID0gbW9yZUluZGVudGVkO1xuICB9XG5cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxuLy8gR3JlZWR5IGxpbmUgYnJlYWtpbmcuXG4vLyBQaWNrcyB0aGUgbG9uZ2VzdCBsaW5lIHVuZGVyIHRoZSBsaW1pdCBlYWNoIHRpbWUsXG4vLyBvdGhlcndpc2Ugc2V0dGxlcyBmb3IgdGhlIHNob3J0ZXN0IGxpbmUgb3ZlciB0aGUgbGltaXQuXG4vLyBOQi4gTW9yZS1pbmRlbnRlZCBsaW5lcyAqY2Fubm90KiBiZSBmb2xkZWQsIGFzIHRoYXQgd291bGQgYWRkIGFuIGV4dHJhIFxcbi5cbmZ1bmN0aW9uIGZvbGRMaW5lKGxpbmUsIHdpZHRoKSB7XG4gIGlmIChsaW5lID09PSAnJyB8fCBsaW5lWzBdID09PSAnICcpIHJldHVybiBsaW5lO1xuXG4gIC8vIFNpbmNlIGEgbW9yZS1pbmRlbnRlZCBsaW5lIGFkZHMgYSBcXG4sIGJyZWFrcyBjYW4ndCBiZSBmb2xsb3dlZCBieSBhIHNwYWNlLlxuICB2YXIgYnJlYWtSZSA9IC8gW14gXS9nOyAvLyBub3RlOiB0aGUgbWF0Y2ggaW5kZXggd2lsbCBhbHdheXMgYmUgPD0gbGVuZ3RoLTIuXG4gIHZhciBtYXRjaDtcbiAgLy8gc3RhcnQgaXMgYW4gaW5jbHVzaXZlIGluZGV4LiBlbmQsIGN1cnIsIGFuZCBuZXh0IGFyZSBleGNsdXNpdmUuXG4gIHZhciBzdGFydCA9IDAsIGVuZCwgY3VyciA9IDAsIG5leHQgPSAwO1xuICB2YXIgcmVzdWx0ID0gJyc7XG5cbiAgLy8gSW52YXJpYW50czogMCA8PSBzdGFydCA8PSBsZW5ndGgtMS5cbiAgLy8gICAwIDw9IGN1cnIgPD0gbmV4dCA8PSBtYXgoMCwgbGVuZ3RoLTIpLiBjdXJyIC0gc3RhcnQgPD0gd2lkdGguXG4gIC8vIEluc2lkZSB0aGUgbG9vcDpcbiAgLy8gICBBIG1hdGNoIGltcGxpZXMgbGVuZ3RoID49IDIsIHNvIGN1cnIgYW5kIG5leHQgYXJlIDw9IGxlbmd0aC0yLlxuICB3aGlsZSAoKG1hdGNoID0gYnJlYWtSZS5leGVjKGxpbmUpKSkge1xuICAgIG5leHQgPSBtYXRjaC5pbmRleDtcbiAgICAvLyBtYWludGFpbiBpbnZhcmlhbnQ6IGN1cnIgLSBzdGFydCA8PSB3aWR0aFxuICAgIGlmIChuZXh0IC0gc3RhcnQgPiB3aWR0aCkge1xuICAgICAgZW5kID0gKGN1cnIgPiBzdGFydCkgPyBjdXJyIDogbmV4dDsgLy8gZGVyaXZlIGVuZCA8PSBsZW5ndGgtMlxuICAgICAgcmVzdWx0ICs9ICdcXG4nICsgbGluZS5zbGljZShzdGFydCwgZW5kKTtcbiAgICAgIC8vIHNraXAgdGhlIHNwYWNlIHRoYXQgd2FzIG91dHB1dCBhcyBcXG5cbiAgICAgIHN0YXJ0ID0gZW5kICsgMTsgICAgICAgICAgICAgICAgICAgIC8vIGRlcml2ZSBzdGFydCA8PSBsZW5ndGgtMVxuICAgIH1cbiAgICBjdXJyID0gbmV4dDtcbiAgfVxuXG4gIC8vIEJ5IHRoZSBpbnZhcmlhbnRzLCBzdGFydCA8PSBsZW5ndGgtMSwgc28gdGhlcmUgaXMgc29tZXRoaW5nIGxlZnQgb3Zlci5cbiAgLy8gSXQgaXMgZWl0aGVyIHRoZSB3aG9sZSBzdHJpbmcgb3IgYSBwYXJ0IHN0YXJ0aW5nIGZyb20gbm9uLXdoaXRlc3BhY2UuXG4gIHJlc3VsdCArPSAnXFxuJztcbiAgLy8gSW5zZXJ0IGEgYnJlYWsgaWYgdGhlIHJlbWFpbmRlciBpcyB0b28gbG9uZyBhbmQgdGhlcmUgaXMgYSBicmVhayBhdmFpbGFibGUuXG4gIGlmIChsaW5lLmxlbmd0aCAtIHN0YXJ0ID4gd2lkdGggJiYgY3VyciA+IHN0YXJ0KSB7XG4gICAgcmVzdWx0ICs9IGxpbmUuc2xpY2Uoc3RhcnQsIGN1cnIpICsgJ1xcbicgKyBsaW5lLnNsaWNlKGN1cnIgKyAxKTtcbiAgfSBlbHNlIHtcbiAgICByZXN1bHQgKz0gbGluZS5zbGljZShzdGFydCk7XG4gIH1cblxuICByZXR1cm4gcmVzdWx0LnNsaWNlKDEpOyAvLyBkcm9wIGV4dHJhIFxcbiBqb2luZXJcbn1cblxuLy8gRXNjYXBlcyBhIGRvdWJsZS1xdW90ZWQgc3RyaW5nLlxuZnVuY3Rpb24gZXNjYXBlU3RyaW5nKHN0cmluZykge1xuICB2YXIgcmVzdWx0ID0gJyc7XG4gIHZhciBjaGFyO1xuICB2YXIgZXNjYXBlU2VxO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgc3RyaW5nLmxlbmd0aDsgaSsrKSB7XG4gICAgY2hhciA9IHN0cmluZy5jaGFyQ29kZUF0KGkpO1xuICAgIGVzY2FwZVNlcSA9IEVTQ0FQRV9TRVFVRU5DRVNbY2hhcl07XG4gICAgcmVzdWx0ICs9ICFlc2NhcGVTZXEgJiYgaXNQcmludGFibGUoY2hhcilcbiAgICAgID8gc3RyaW5nW2ldXG4gICAgICA6IGVzY2FwZVNlcSB8fCBlbmNvZGVIZXgoY2hhcik7XG4gIH1cblxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5mdW5jdGlvbiB3cml0ZUZsb3dTZXF1ZW5jZShzdGF0ZSwgbGV2ZWwsIG9iamVjdCkge1xuICB2YXIgX3Jlc3VsdCA9ICcnLFxuICAgICAgX3RhZyAgICA9IHN0YXRlLnRhZyxcbiAgICAgIGluZGV4LFxuICAgICAgbGVuZ3RoO1xuXG4gIGZvciAoaW5kZXggPSAwLCBsZW5ndGggPSBvYmplY3QubGVuZ3RoOyBpbmRleCA8IGxlbmd0aDsgaW5kZXggKz0gMSkge1xuICAgIC8vIFdyaXRlIG9ubHkgdmFsaWQgZWxlbWVudHMuXG4gICAgaWYgKHdyaXRlTm9kZShzdGF0ZSwgbGV2ZWwsIG9iamVjdFtpbmRleF0sIGZhbHNlLCBmYWxzZSkpIHtcbiAgICAgIGlmIChpbmRleCAhPT0gMCkgX3Jlc3VsdCArPSAnLCAnO1xuICAgICAgX3Jlc3VsdCArPSBzdGF0ZS5kdW1wO1xuICAgIH1cbiAgfVxuXG4gIHN0YXRlLnRhZyA9IF90YWc7XG4gIHN0YXRlLmR1bXAgPSAnWycgKyBfcmVzdWx0ICsgJ10nO1xufVxuXG5mdW5jdGlvbiB3cml0ZUJsb2NrU2VxdWVuY2Uoc3RhdGUsIGxldmVsLCBvYmplY3QsIGNvbXBhY3QpIHtcbiAgdmFyIF9yZXN1bHQgPSAnJyxcbiAgICAgIF90YWcgICAgPSBzdGF0ZS50YWcsXG4gICAgICBpbmRleCxcbiAgICAgIGxlbmd0aDtcblxuICBmb3IgKGluZGV4ID0gMCwgbGVuZ3RoID0gb2JqZWN0Lmxlbmd0aDsgaW5kZXggPCBsZW5ndGg7IGluZGV4ICs9IDEpIHtcbiAgICAvLyBXcml0ZSBvbmx5IHZhbGlkIGVsZW1lbnRzLlxuICAgIGlmICh3cml0ZU5vZGUoc3RhdGUsIGxldmVsICsgMSwgb2JqZWN0W2luZGV4XSwgdHJ1ZSwgdHJ1ZSkpIHtcbiAgICAgIGlmICghY29tcGFjdCB8fCBpbmRleCAhPT0gMCkge1xuICAgICAgICBfcmVzdWx0ICs9IGdlbmVyYXRlTmV4dExpbmUoc3RhdGUsIGxldmVsKTtcbiAgICAgIH1cbiAgICAgIF9yZXN1bHQgKz0gJy0gJyArIHN0YXRlLmR1bXA7XG4gICAgfVxuICB9XG5cbiAgc3RhdGUudGFnID0gX3RhZztcbiAgc3RhdGUuZHVtcCA9IF9yZXN1bHQgfHwgJ1tdJzsgLy8gRW1wdHkgc2VxdWVuY2UgaWYgbm8gdmFsaWQgdmFsdWVzLlxufVxuXG5mdW5jdGlvbiB3cml0ZUZsb3dNYXBwaW5nKHN0YXRlLCBsZXZlbCwgb2JqZWN0KSB7XG4gIHZhciBfcmVzdWx0ICAgICAgID0gJycsXG4gICAgICBfdGFnICAgICAgICAgID0gc3RhdGUudGFnLFxuICAgICAgb2JqZWN0S2V5TGlzdCA9IE9iamVjdC5rZXlzKG9iamVjdCksXG4gICAgICBpbmRleCxcbiAgICAgIGxlbmd0aCxcbiAgICAgIG9iamVjdEtleSxcbiAgICAgIG9iamVjdFZhbHVlLFxuICAgICAgcGFpckJ1ZmZlcjtcblxuICBmb3IgKGluZGV4ID0gMCwgbGVuZ3RoID0gb2JqZWN0S2V5TGlzdC5sZW5ndGg7IGluZGV4IDwgbGVuZ3RoOyBpbmRleCArPSAxKSB7XG4gICAgcGFpckJ1ZmZlciA9ICcnO1xuXG4gICAgaWYgKGluZGV4ICE9PSAwKSBwYWlyQnVmZmVyICs9ICcsICc7XG5cbiAgICBvYmplY3RLZXkgPSBvYmplY3RLZXlMaXN0W2luZGV4XTtcbiAgICBvYmplY3RWYWx1ZSA9IG9iamVjdFtvYmplY3RLZXldO1xuXG4gICAgaWYgKCF3cml0ZU5vZGUoc3RhdGUsIGxldmVsLCBvYmplY3RLZXksIGZhbHNlLCBmYWxzZSkpIHtcbiAgICAgIGNvbnRpbnVlOyAvLyBTa2lwIHRoaXMgcGFpciBiZWNhdXNlIG9mIGludmFsaWQga2V5O1xuICAgIH1cblxuICAgIGlmIChzdGF0ZS5kdW1wLmxlbmd0aCA+IDEwMjQpIHBhaXJCdWZmZXIgKz0gJz8gJztcblxuICAgIHBhaXJCdWZmZXIgKz0gc3RhdGUuZHVtcCArICc6ICc7XG5cbiAgICBpZiAoIXdyaXRlTm9kZShzdGF0ZSwgbGV2ZWwsIG9iamVjdFZhbHVlLCBmYWxzZSwgZmFsc2UpKSB7XG4gICAgICBjb250aW51ZTsgLy8gU2tpcCB0aGlzIHBhaXIgYmVjYXVzZSBvZiBpbnZhbGlkIHZhbHVlLlxuICAgIH1cblxuICAgIHBhaXJCdWZmZXIgKz0gc3RhdGUuZHVtcDtcblxuICAgIC8vIEJvdGgga2V5IGFuZCB2YWx1ZSBhcmUgdmFsaWQuXG4gICAgX3Jlc3VsdCArPSBwYWlyQnVmZmVyO1xuICB9XG5cbiAgc3RhdGUudGFnID0gX3RhZztcbiAgc3RhdGUuZHVtcCA9ICd7JyArIF9yZXN1bHQgKyAnfSc7XG59XG5cbmZ1bmN0aW9uIHdyaXRlQmxvY2tNYXBwaW5nKHN0YXRlLCBsZXZlbCwgb2JqZWN0LCBjb21wYWN0KSB7XG4gIHZhciBfcmVzdWx0ICAgICAgID0gJycsXG4gICAgICBfdGFnICAgICAgICAgID0gc3RhdGUudGFnLFxuICAgICAgb2JqZWN0S2V5TGlzdCA9IE9iamVjdC5rZXlzKG9iamVjdCksXG4gICAgICBpbmRleCxcbiAgICAgIGxlbmd0aCxcbiAgICAgIG9iamVjdEtleSxcbiAgICAgIG9iamVjdFZhbHVlLFxuICAgICAgZXhwbGljaXRQYWlyLFxuICAgICAgcGFpckJ1ZmZlcjtcblxuICAvLyBBbGxvdyBzb3J0aW5nIGtleXMgc28gdGhhdCB0aGUgb3V0cHV0IGZpbGUgaXMgZGV0ZXJtaW5pc3RpY1xuICBpZiAoc3RhdGUuc29ydEtleXMgPT09IHRydWUpIHtcbiAgICAvLyBEZWZhdWx0IHNvcnRpbmdcbiAgICBvYmplY3RLZXlMaXN0LnNvcnQoKTtcbiAgfSBlbHNlIGlmICh0eXBlb2Ygc3RhdGUuc29ydEtleXMgPT09ICdmdW5jdGlvbicpIHtcbiAgICAvLyBDdXN0b20gc29ydCBmdW5jdGlvblxuICAgIG9iamVjdEtleUxpc3Quc29ydChzdGF0ZS5zb3J0S2V5cyk7XG4gIH0gZWxzZSBpZiAoc3RhdGUuc29ydEtleXMpIHtcbiAgICAvLyBTb21ldGhpbmcgaXMgd3JvbmdcbiAgICB0aHJvdyBuZXcgWUFNTEV4Y2VwdGlvbignc29ydEtleXMgbXVzdCBiZSBhIGJvb2xlYW4gb3IgYSBmdW5jdGlvbicpO1xuICB9XG5cbiAgZm9yIChpbmRleCA9IDAsIGxlbmd0aCA9IG9iamVjdEtleUxpc3QubGVuZ3RoOyBpbmRleCA8IGxlbmd0aDsgaW5kZXggKz0gMSkge1xuICAgIHBhaXJCdWZmZXIgPSAnJztcblxuICAgIGlmICghY29tcGFjdCB8fCBpbmRleCAhPT0gMCkge1xuICAgICAgcGFpckJ1ZmZlciArPSBnZW5lcmF0ZU5leHRMaW5lKHN0YXRlLCBsZXZlbCk7XG4gICAgfVxuXG4gICAgb2JqZWN0S2V5ID0gb2JqZWN0S2V5TGlzdFtpbmRleF07XG4gICAgb2JqZWN0VmFsdWUgPSBvYmplY3Rbb2JqZWN0S2V5XTtcblxuICAgIGlmICghd3JpdGVOb2RlKHN0YXRlLCBsZXZlbCArIDEsIG9iamVjdEtleSwgdHJ1ZSwgdHJ1ZSwgdHJ1ZSkpIHtcbiAgICAgIGNvbnRpbnVlOyAvLyBTa2lwIHRoaXMgcGFpciBiZWNhdXNlIG9mIGludmFsaWQga2V5LlxuICAgIH1cblxuICAgIGV4cGxpY2l0UGFpciA9IChzdGF0ZS50YWcgIT09IG51bGwgJiYgc3RhdGUudGFnICE9PSAnPycpIHx8XG4gICAgICAgICAgICAgICAgICAgKHN0YXRlLmR1bXAgJiYgc3RhdGUuZHVtcC5sZW5ndGggPiAxMDI0KTtcblxuICAgIGlmIChleHBsaWNpdFBhaXIpIHtcbiAgICAgIGlmIChzdGF0ZS5kdW1wICYmIENIQVJfTElORV9GRUVEID09PSBzdGF0ZS5kdW1wLmNoYXJDb2RlQXQoMCkpIHtcbiAgICAgICAgcGFpckJ1ZmZlciArPSAnPyc7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBwYWlyQnVmZmVyICs9ICc/ICc7XG4gICAgICB9XG4gICAgfVxuXG4gICAgcGFpckJ1ZmZlciArPSBzdGF0ZS5kdW1wO1xuXG4gICAgaWYgKGV4cGxpY2l0UGFpcikge1xuICAgICAgcGFpckJ1ZmZlciArPSBnZW5lcmF0ZU5leHRMaW5lKHN0YXRlLCBsZXZlbCk7XG4gICAgfVxuXG4gICAgaWYgKCF3cml0ZU5vZGUoc3RhdGUsIGxldmVsICsgMSwgb2JqZWN0VmFsdWUsIHRydWUsIGV4cGxpY2l0UGFpcikpIHtcbiAgICAgIGNvbnRpbnVlOyAvLyBTa2lwIHRoaXMgcGFpciBiZWNhdXNlIG9mIGludmFsaWQgdmFsdWUuXG4gICAgfVxuXG4gICAgaWYgKHN0YXRlLmR1bXAgJiYgQ0hBUl9MSU5FX0ZFRUQgPT09IHN0YXRlLmR1bXAuY2hhckNvZGVBdCgwKSkge1xuICAgICAgcGFpckJ1ZmZlciArPSAnOic7XG4gICAgfSBlbHNlIHtcbiAgICAgIHBhaXJCdWZmZXIgKz0gJzogJztcbiAgICB9XG5cbiAgICBwYWlyQnVmZmVyICs9IHN0YXRlLmR1bXA7XG5cbiAgICAvLyBCb3RoIGtleSBhbmQgdmFsdWUgYXJlIHZhbGlkLlxuICAgIF9yZXN1bHQgKz0gcGFpckJ1ZmZlcjtcbiAgfVxuXG4gIHN0YXRlLnRhZyA9IF90YWc7XG4gIHN0YXRlLmR1bXAgPSBfcmVzdWx0IHx8ICd7fSc7IC8vIEVtcHR5IG1hcHBpbmcgaWYgbm8gdmFsaWQgcGFpcnMuXG59XG5cbmZ1bmN0aW9uIGRldGVjdFR5cGUoc3RhdGUsIG9iamVjdCwgZXhwbGljaXQpIHtcbiAgdmFyIF9yZXN1bHQsIHR5cGVMaXN0LCBpbmRleCwgbGVuZ3RoLCB0eXBlLCBzdHlsZTtcblxuICB0eXBlTGlzdCA9IGV4cGxpY2l0ID8gc3RhdGUuZXhwbGljaXRUeXBlcyA6IHN0YXRlLmltcGxpY2l0VHlwZXM7XG5cbiAgZm9yIChpbmRleCA9IDAsIGxlbmd0aCA9IHR5cGVMaXN0Lmxlbmd0aDsgaW5kZXggPCBsZW5ndGg7IGluZGV4ICs9IDEpIHtcbiAgICB0eXBlID0gdHlwZUxpc3RbaW5kZXhdO1xuXG4gICAgaWYgKCh0eXBlLmluc3RhbmNlT2YgIHx8IHR5cGUucHJlZGljYXRlKSAmJlxuICAgICAgICAoIXR5cGUuaW5zdGFuY2VPZiB8fCAoKHR5cGVvZiBvYmplY3QgPT09ICdvYmplY3QnKSAmJiAob2JqZWN0IGluc3RhbmNlb2YgdHlwZS5pbnN0YW5jZU9mKSkpICYmXG4gICAgICAgICghdHlwZS5wcmVkaWNhdGUgIHx8IHR5cGUucHJlZGljYXRlKG9iamVjdCkpKSB7XG5cbiAgICAgIHN0YXRlLnRhZyA9IGV4cGxpY2l0ID8gdHlwZS50YWcgOiAnPyc7XG5cbiAgICAgIGlmICh0eXBlLnJlcHJlc2VudCkge1xuICAgICAgICBzdHlsZSA9IHN0YXRlLnN0eWxlTWFwW3R5cGUudGFnXSB8fCB0eXBlLmRlZmF1bHRTdHlsZTtcblxuICAgICAgICBpZiAoX3RvU3RyaW5nLmNhbGwodHlwZS5yZXByZXNlbnQpID09PSAnW29iamVjdCBGdW5jdGlvbl0nKSB7XG4gICAgICAgICAgX3Jlc3VsdCA9IHR5cGUucmVwcmVzZW50KG9iamVjdCwgc3R5bGUpO1xuICAgICAgICB9IGVsc2UgaWYgKF9oYXNPd25Qcm9wZXJ0eS5jYWxsKHR5cGUucmVwcmVzZW50LCBzdHlsZSkpIHtcbiAgICAgICAgICBfcmVzdWx0ID0gdHlwZS5yZXByZXNlbnRbc3R5bGVdKG9iamVjdCwgc3R5bGUpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHRocm93IG5ldyBZQU1MRXhjZXB0aW9uKCchPCcgKyB0eXBlLnRhZyArICc+IHRhZyByZXNvbHZlciBhY2NlcHRzIG5vdCBcIicgKyBzdHlsZSArICdcIiBzdHlsZScpO1xuICAgICAgICB9XG5cbiAgICAgICAgc3RhdGUuZHVtcCA9IF9yZXN1bHQ7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBmYWxzZTtcbn1cblxuLy8gU2VyaWFsaXplcyBgb2JqZWN0YCBhbmQgd3JpdGVzIGl0IHRvIGdsb2JhbCBgcmVzdWx0YC5cbi8vIFJldHVybnMgdHJ1ZSBvbiBzdWNjZXNzLCBvciBmYWxzZSBvbiBpbnZhbGlkIG9iamVjdC5cbi8vXG5mdW5jdGlvbiB3cml0ZU5vZGUoc3RhdGUsIGxldmVsLCBvYmplY3QsIGJsb2NrLCBjb21wYWN0LCBpc2tleSkge1xuICBzdGF0ZS50YWcgPSBudWxsO1xuICBzdGF0ZS5kdW1wID0gb2JqZWN0O1xuXG4gIGlmICghZGV0ZWN0VHlwZShzdGF0ZSwgb2JqZWN0LCBmYWxzZSkpIHtcbiAgICBkZXRlY3RUeXBlKHN0YXRlLCBvYmplY3QsIHRydWUpO1xuICB9XG5cbiAgdmFyIHR5cGUgPSBfdG9TdHJpbmcuY2FsbChzdGF0ZS5kdW1wKTtcblxuICBpZiAoYmxvY2spIHtcbiAgICBibG9jayA9IChzdGF0ZS5mbG93TGV2ZWwgPCAwIHx8IHN0YXRlLmZsb3dMZXZlbCA+IGxldmVsKTtcbiAgfVxuXG4gIHZhciBvYmplY3RPckFycmF5ID0gdHlwZSA9PT0gJ1tvYmplY3QgT2JqZWN0XScgfHwgdHlwZSA9PT0gJ1tvYmplY3QgQXJyYXldJyxcbiAgICAgIGR1cGxpY2F0ZUluZGV4LFxuICAgICAgZHVwbGljYXRlO1xuXG4gIGlmIChvYmplY3RPckFycmF5KSB7XG4gICAgZHVwbGljYXRlSW5kZXggPSBzdGF0ZS5kdXBsaWNhdGVzLmluZGV4T2Yob2JqZWN0KTtcbiAgICBkdXBsaWNhdGUgPSBkdXBsaWNhdGVJbmRleCAhPT0gLTE7XG4gIH1cblxuICBpZiAoKHN0YXRlLnRhZyAhPT0gbnVsbCAmJiBzdGF0ZS50YWcgIT09ICc/JykgfHwgZHVwbGljYXRlIHx8IChzdGF0ZS5pbmRlbnQgIT09IDIgJiYgbGV2ZWwgPiAwKSkge1xuICAgIGNvbXBhY3QgPSBmYWxzZTtcbiAgfVxuXG4gIGlmIChkdXBsaWNhdGUgJiYgc3RhdGUudXNlZER1cGxpY2F0ZXNbZHVwbGljYXRlSW5kZXhdKSB7XG4gICAgc3RhdGUuZHVtcCA9ICcqcmVmXycgKyBkdXBsaWNhdGVJbmRleDtcbiAgfSBlbHNlIHtcbiAgICBpZiAob2JqZWN0T3JBcnJheSAmJiBkdXBsaWNhdGUgJiYgIXN0YXRlLnVzZWREdXBsaWNhdGVzW2R1cGxpY2F0ZUluZGV4XSkge1xuICAgICAgc3RhdGUudXNlZER1cGxpY2F0ZXNbZHVwbGljYXRlSW5kZXhdID0gdHJ1ZTtcbiAgICB9XG4gICAgaWYgKHR5cGUgPT09ICdbb2JqZWN0IE9iamVjdF0nKSB7XG4gICAgICBpZiAoYmxvY2sgJiYgKE9iamVjdC5rZXlzKHN0YXRlLmR1bXApLmxlbmd0aCAhPT0gMCkpIHtcbiAgICAgICAgd3JpdGVCbG9ja01hcHBpbmcoc3RhdGUsIGxldmVsLCBzdGF0ZS5kdW1wLCBjb21wYWN0KTtcbiAgICAgICAgaWYgKGR1cGxpY2F0ZSkge1xuICAgICAgICAgIHN0YXRlLmR1bXAgPSAnJnJlZl8nICsgZHVwbGljYXRlSW5kZXggKyBzdGF0ZS5kdW1wO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB3cml0ZUZsb3dNYXBwaW5nKHN0YXRlLCBsZXZlbCwgc3RhdGUuZHVtcCk7XG4gICAgICAgIGlmIChkdXBsaWNhdGUpIHtcbiAgICAgICAgICBzdGF0ZS5kdW1wID0gJyZyZWZfJyArIGR1cGxpY2F0ZUluZGV4ICsgJyAnICsgc3RhdGUuZHVtcDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSBpZiAodHlwZSA9PT0gJ1tvYmplY3QgQXJyYXldJykge1xuICAgICAgaWYgKGJsb2NrICYmIChzdGF0ZS5kdW1wLmxlbmd0aCAhPT0gMCkpIHtcbiAgICAgICAgd3JpdGVCbG9ja1NlcXVlbmNlKHN0YXRlLCBsZXZlbCwgc3RhdGUuZHVtcCwgY29tcGFjdCk7XG4gICAgICAgIGlmIChkdXBsaWNhdGUpIHtcbiAgICAgICAgICBzdGF0ZS5kdW1wID0gJyZyZWZfJyArIGR1cGxpY2F0ZUluZGV4ICsgc3RhdGUuZHVtcDtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgd3JpdGVGbG93U2VxdWVuY2Uoc3RhdGUsIGxldmVsLCBzdGF0ZS5kdW1wKTtcbiAgICAgICAgaWYgKGR1cGxpY2F0ZSkge1xuICAgICAgICAgIHN0YXRlLmR1bXAgPSAnJnJlZl8nICsgZHVwbGljYXRlSW5kZXggKyAnICcgKyBzdGF0ZS5kdW1wO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlID09PSAnW29iamVjdCBTdHJpbmddJykge1xuICAgICAgaWYgKHN0YXRlLnRhZyAhPT0gJz8nKSB7XG4gICAgICAgIHdyaXRlU2NhbGFyKHN0YXRlLCBzdGF0ZS5kdW1wLCBsZXZlbCwgaXNrZXkpO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBpZiAoc3RhdGUuc2tpcEludmFsaWQpIHJldHVybiBmYWxzZTtcbiAgICAgIHRocm93IG5ldyBZQU1MRXhjZXB0aW9uKCd1bmFjY2VwdGFibGUga2luZCBvZiBhbiBvYmplY3QgdG8gZHVtcCAnICsgdHlwZSk7XG4gICAgfVxuXG4gICAgaWYgKHN0YXRlLnRhZyAhPT0gbnVsbCAmJiBzdGF0ZS50YWcgIT09ICc/Jykge1xuICAgICAgc3RhdGUuZHVtcCA9ICchPCcgKyBzdGF0ZS50YWcgKyAnPiAnICsgc3RhdGUuZHVtcDtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn1cblxuZnVuY3Rpb24gZ2V0RHVwbGljYXRlUmVmZXJlbmNlcyhvYmplY3QsIHN0YXRlKSB7XG4gIHZhciBvYmplY3RzID0gW10sXG4gICAgICBkdXBsaWNhdGVzSW5kZXhlcyA9IFtdLFxuICAgICAgaW5kZXgsXG4gICAgICBsZW5ndGg7XG5cbiAgaW5zcGVjdE5vZGUob2JqZWN0LCBvYmplY3RzLCBkdXBsaWNhdGVzSW5kZXhlcyk7XG5cbiAgZm9yIChpbmRleCA9IDAsIGxlbmd0aCA9IGR1cGxpY2F0ZXNJbmRleGVzLmxlbmd0aDsgaW5kZXggPCBsZW5ndGg7IGluZGV4ICs9IDEpIHtcbiAgICBzdGF0ZS5kdXBsaWNhdGVzLnB1c2gob2JqZWN0c1tkdXBsaWNhdGVzSW5kZXhlc1tpbmRleF1dKTtcbiAgfVxuICBzdGF0ZS51c2VkRHVwbGljYXRlcyA9IG5ldyBBcnJheShsZW5ndGgpO1xufVxuXG5mdW5jdGlvbiBpbnNwZWN0Tm9kZShvYmplY3QsIG9iamVjdHMsIGR1cGxpY2F0ZXNJbmRleGVzKSB7XG4gIHZhciBvYmplY3RLZXlMaXN0LFxuICAgICAgaW5kZXgsXG4gICAgICBsZW5ndGg7XG5cbiAgaWYgKG9iamVjdCAhPT0gbnVsbCAmJiB0eXBlb2Ygb2JqZWN0ID09PSAnb2JqZWN0Jykge1xuICAgIGluZGV4ID0gb2JqZWN0cy5pbmRleE9mKG9iamVjdCk7XG4gICAgaWYgKGluZGV4ICE9PSAtMSkge1xuICAgICAgaWYgKGR1cGxpY2F0ZXNJbmRleGVzLmluZGV4T2YoaW5kZXgpID09PSAtMSkge1xuICAgICAgICBkdXBsaWNhdGVzSW5kZXhlcy5wdXNoKGluZGV4KTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgb2JqZWN0cy5wdXNoKG9iamVjdCk7XG5cbiAgICAgIGlmIChBcnJheS5pc0FycmF5KG9iamVjdCkpIHtcbiAgICAgICAgZm9yIChpbmRleCA9IDAsIGxlbmd0aCA9IG9iamVjdC5sZW5ndGg7IGluZGV4IDwgbGVuZ3RoOyBpbmRleCArPSAxKSB7XG4gICAgICAgICAgaW5zcGVjdE5vZGUob2JqZWN0W2luZGV4XSwgb2JqZWN0cywgZHVwbGljYXRlc0luZGV4ZXMpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBvYmplY3RLZXlMaXN0ID0gT2JqZWN0LmtleXMob2JqZWN0KTtcblxuICAgICAgICBmb3IgKGluZGV4ID0gMCwgbGVuZ3RoID0gb2JqZWN0S2V5TGlzdC5sZW5ndGg7IGluZGV4IDwgbGVuZ3RoOyBpbmRleCArPSAxKSB7XG4gICAgICAgICAgaW5zcGVjdE5vZGUob2JqZWN0W29iamVjdEtleUxpc3RbaW5kZXhdXSwgb2JqZWN0cywgZHVwbGljYXRlc0luZGV4ZXMpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5cbmZ1bmN0aW9uIGR1bXAoaW5wdXQsIG9wdGlvbnMpIHtcbiAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XG5cbiAgdmFyIHN0YXRlID0gbmV3IFN0YXRlKG9wdGlvbnMpO1xuXG4gIGlmICghc3RhdGUubm9SZWZzKSBnZXREdXBsaWNhdGVSZWZlcmVuY2VzKGlucHV0LCBzdGF0ZSk7XG5cbiAgaWYgKHdyaXRlTm9kZShzdGF0ZSwgMCwgaW5wdXQsIHRydWUsIHRydWUpKSByZXR1cm4gc3RhdGUuZHVtcCArICdcXG4nO1xuXG4gIHJldHVybiAnJztcbn1cblxuZnVuY3Rpb24gc2FmZUR1bXAoaW5wdXQsIG9wdGlvbnMpIHtcbiAgcmV0dXJuIGR1bXAoaW5wdXQsIGNvbW1vbi5leHRlbmQoeyBzY2hlbWE6IERFRkFVTFRfU0FGRV9TQ0hFTUEgfSwgb3B0aW9ucykpO1xufVxuXG5tb2R1bGUuZXhwb3J0cy5kdW1wICAgICA9IGR1bXA7XG5tb2R1bGUuZXhwb3J0cy5zYWZlRHVtcCA9IHNhZmVEdW1wO1xuIiwiLy8gWUFNTCBlcnJvciBjbGFzcy4gaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy84NDU4OTg0XG4vL1xuJ3VzZSBzdHJpY3QnO1xuXG5mdW5jdGlvbiBZQU1MRXhjZXB0aW9uKHJlYXNvbiwgbWFyaykge1xuICAvLyBTdXBlciBjb25zdHJ1Y3RvclxuICBFcnJvci5jYWxsKHRoaXMpO1xuXG4gIC8vIEluY2x1ZGUgc3RhY2sgdHJhY2UgaW4gZXJyb3Igb2JqZWN0XG4gIGlmIChFcnJvci5jYXB0dXJlU3RhY2tUcmFjZSkge1xuICAgIC8vIENocm9tZSBhbmQgTm9kZUpTXG4gICAgRXJyb3IuY2FwdHVyZVN0YWNrVHJhY2UodGhpcywgdGhpcy5jb25zdHJ1Y3Rvcik7XG4gIH0gZWxzZSB7XG4gICAgLy8gRkYsIElFIDEwKyBhbmQgU2FmYXJpIDYrLiBGYWxsYmFjayBmb3Igb3RoZXJzXG4gICAgdGhpcy5zdGFjayA9IChuZXcgRXJyb3IoKSkuc3RhY2sgfHwgJyc7XG4gIH1cblxuICB0aGlzLm5hbWUgPSAnWUFNTEV4Y2VwdGlvbic7XG4gIHRoaXMucmVhc29uID0gcmVhc29uO1xuICB0aGlzLm1hcmsgPSBtYXJrO1xuICB0aGlzLm1lc3NhZ2UgPSAodGhpcy5yZWFzb24gfHwgJyh1bmtub3duIHJlYXNvbiknKSArICh0aGlzLm1hcmsgPyAnICcgKyB0aGlzLm1hcmsudG9TdHJpbmcoKSA6ICcnKTtcbn1cblxuXG4vLyBJbmhlcml0IGZyb20gRXJyb3JcbllBTUxFeGNlcHRpb24ucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShFcnJvci5wcm90b3R5cGUpO1xuWUFNTEV4Y2VwdGlvbi5wcm90b3R5cGUuY29uc3RydWN0b3IgPSBZQU1MRXhjZXB0aW9uO1xuXG5cbllBTUxFeGNlcHRpb24ucHJvdG90eXBlLnRvU3RyaW5nID0gZnVuY3Rpb24gdG9TdHJpbmcoY29tcGFjdCkge1xuICB2YXIgcmVzdWx0ID0gdGhpcy5uYW1lICsgJzogJztcblxuICByZXN1bHQgKz0gdGhpcy5yZWFzb24gfHwgJyh1bmtub3duIHJlYXNvbiknO1xuXG4gIGlmICghY29tcGFjdCAmJiB0aGlzLm1hcmspIHtcbiAgICByZXN1bHQgKz0gJyAnICsgdGhpcy5tYXJrLnRvU3RyaW5nKCk7XG4gIH1cblxuICByZXR1cm4gcmVzdWx0O1xufTtcblxuXG5tb2R1bGUuZXhwb3J0cyA9IFlBTUxFeGNlcHRpb247XG4iLCIndXNlIHN0cmljdCc7XG5cbi8qZXNsaW50LWRpc2FibGUgbWF4LWxlbixuby11c2UtYmVmb3JlLWRlZmluZSovXG5cbnZhciBjb21tb24gICAgICAgICAgICAgID0gcmVxdWlyZSgnLi9jb21tb24nKTtcbnZhciBZQU1MRXhjZXB0aW9uICAgICAgID0gcmVxdWlyZSgnLi9leGNlcHRpb24nKTtcbnZhciBNYXJrICAgICAgICAgICAgICAgID0gcmVxdWlyZSgnLi9tYXJrJyk7XG52YXIgREVGQVVMVF9TQUZFX1NDSEVNQSA9IHJlcXVpcmUoJy4vc2NoZW1hL2RlZmF1bHRfc2FmZScpO1xudmFyIERFRkFVTFRfRlVMTF9TQ0hFTUEgPSByZXF1aXJlKCcuL3NjaGVtYS9kZWZhdWx0X2Z1bGwnKTtcblxuXG52YXIgX2hhc093blByb3BlcnR5ID0gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eTtcblxuXG52YXIgQ09OVEVYVF9GTE9XX0lOICAgPSAxO1xudmFyIENPTlRFWFRfRkxPV19PVVQgID0gMjtcbnZhciBDT05URVhUX0JMT0NLX0lOICA9IDM7XG52YXIgQ09OVEVYVF9CTE9DS19PVVQgPSA0O1xuXG5cbnZhciBDSE9NUElOR19DTElQICA9IDE7XG52YXIgQ0hPTVBJTkdfU1RSSVAgPSAyO1xudmFyIENIT01QSU5HX0tFRVAgID0gMztcblxuXG52YXIgUEFUVEVSTl9OT05fUFJJTlRBQkxFICAgICAgICAgPSAvW1xceDAwLVxceDA4XFx4MEJcXHgwQ1xceDBFLVxceDFGXFx4N0YtXFx4ODRcXHg4Ni1cXHg5RlxcdUZGRkVcXHVGRkZGXXxbXFx1RDgwMC1cXHVEQkZGXSg/IVtcXHVEQzAwLVxcdURGRkZdKXwoPzpbXlxcdUQ4MDAtXFx1REJGRl18XilbXFx1REMwMC1cXHVERkZGXS87XG52YXIgUEFUVEVSTl9OT05fQVNDSUlfTElORV9CUkVBS1MgPSAvW1xceDg1XFx1MjAyOFxcdTIwMjldLztcbnZhciBQQVRURVJOX0ZMT1dfSU5ESUNBVE9SUyAgICAgICA9IC9bLFxcW1xcXVxce1xcfV0vO1xudmFyIFBBVFRFUk5fVEFHX0hBTkRMRSAgICAgICAgICAgID0gL14oPzohfCEhfCFbYS16XFwtXSshKSQvaTtcbnZhciBQQVRURVJOX1RBR19VUkkgICAgICAgICAgICAgICA9IC9eKD86IXxbXixcXFtcXF1cXHtcXH1dKSg/OiVbMC05YS1mXXsyfXxbMC05YS16XFwtIztcXC9cXD86QCY9XFwrXFwkLF9cXC4hflxcKidcXChcXClcXFtcXF1dKSokL2k7XG5cblxuZnVuY3Rpb24gaXNfRU9MKGMpIHtcbiAgcmV0dXJuIChjID09PSAweDBBLyogTEYgKi8pIHx8IChjID09PSAweDBELyogQ1IgKi8pO1xufVxuXG5mdW5jdGlvbiBpc19XSElURV9TUEFDRShjKSB7XG4gIHJldHVybiAoYyA9PT0gMHgwOS8qIFRhYiAqLykgfHwgKGMgPT09IDB4MjAvKiBTcGFjZSAqLyk7XG59XG5cbmZ1bmN0aW9uIGlzX1dTX09SX0VPTChjKSB7XG4gIHJldHVybiAoYyA9PT0gMHgwOS8qIFRhYiAqLykgfHxcbiAgICAgICAgIChjID09PSAweDIwLyogU3BhY2UgKi8pIHx8XG4gICAgICAgICAoYyA9PT0gMHgwQS8qIExGICovKSB8fFxuICAgICAgICAgKGMgPT09IDB4MEQvKiBDUiAqLyk7XG59XG5cbmZ1bmN0aW9uIGlzX0ZMT1dfSU5ESUNBVE9SKGMpIHtcbiAgcmV0dXJuIGMgPT09IDB4MkMvKiAsICovIHx8XG4gICAgICAgICBjID09PSAweDVCLyogWyAqLyB8fFxuICAgICAgICAgYyA9PT0gMHg1RC8qIF0gKi8gfHxcbiAgICAgICAgIGMgPT09IDB4N0IvKiB7ICovIHx8XG4gICAgICAgICBjID09PSAweDdELyogfSAqLztcbn1cblxuZnVuY3Rpb24gZnJvbUhleENvZGUoYykge1xuICB2YXIgbGM7XG5cbiAgaWYgKCgweDMwLyogMCAqLyA8PSBjKSAmJiAoYyA8PSAweDM5LyogOSAqLykpIHtcbiAgICByZXR1cm4gYyAtIDB4MzA7XG4gIH1cblxuICAvKmVzbGludC1kaXNhYmxlIG5vLWJpdHdpc2UqL1xuICBsYyA9IGMgfCAweDIwO1xuXG4gIGlmICgoMHg2MS8qIGEgKi8gPD0gbGMpICYmIChsYyA8PSAweDY2LyogZiAqLykpIHtcbiAgICByZXR1cm4gbGMgLSAweDYxICsgMTA7XG4gIH1cblxuICByZXR1cm4gLTE7XG59XG5cbmZ1bmN0aW9uIGVzY2FwZWRIZXhMZW4oYykge1xuICBpZiAoYyA9PT0gMHg3OC8qIHggKi8pIHsgcmV0dXJuIDI7IH1cbiAgaWYgKGMgPT09IDB4NzUvKiB1ICovKSB7IHJldHVybiA0OyB9XG4gIGlmIChjID09PSAweDU1LyogVSAqLykgeyByZXR1cm4gODsgfVxuICByZXR1cm4gMDtcbn1cblxuZnVuY3Rpb24gZnJvbURlY2ltYWxDb2RlKGMpIHtcbiAgaWYgKCgweDMwLyogMCAqLyA8PSBjKSAmJiAoYyA8PSAweDM5LyogOSAqLykpIHtcbiAgICByZXR1cm4gYyAtIDB4MzA7XG4gIH1cblxuICByZXR1cm4gLTE7XG59XG5cbmZ1bmN0aW9uIHNpbXBsZUVzY2FwZVNlcXVlbmNlKGMpIHtcbiAgcmV0dXJuIChjID09PSAweDMwLyogMCAqLykgPyAnXFx4MDAnIDpcbiAgICAgICAgKGMgPT09IDB4NjEvKiBhICovKSA/ICdcXHgwNycgOlxuICAgICAgICAoYyA9PT0gMHg2Mi8qIGIgKi8pID8gJ1xceDA4JyA6XG4gICAgICAgIChjID09PSAweDc0LyogdCAqLykgPyAnXFx4MDknIDpcbiAgICAgICAgKGMgPT09IDB4MDkvKiBUYWIgKi8pID8gJ1xceDA5JyA6XG4gICAgICAgIChjID09PSAweDZFLyogbiAqLykgPyAnXFx4MEEnIDpcbiAgICAgICAgKGMgPT09IDB4NzYvKiB2ICovKSA/ICdcXHgwQicgOlxuICAgICAgICAoYyA9PT0gMHg2Ni8qIGYgKi8pID8gJ1xceDBDJyA6XG4gICAgICAgIChjID09PSAweDcyLyogciAqLykgPyAnXFx4MEQnIDpcbiAgICAgICAgKGMgPT09IDB4NjUvKiBlICovKSA/ICdcXHgxQicgOlxuICAgICAgICAoYyA9PT0gMHgyMC8qIFNwYWNlICovKSA/ICcgJyA6XG4gICAgICAgIChjID09PSAweDIyLyogXCIgKi8pID8gJ1xceDIyJyA6XG4gICAgICAgIChjID09PSAweDJGLyogLyAqLykgPyAnLycgOlxuICAgICAgICAoYyA9PT0gMHg1Qy8qIFxcICovKSA/ICdcXHg1QycgOlxuICAgICAgICAoYyA9PT0gMHg0RS8qIE4gKi8pID8gJ1xceDg1JyA6XG4gICAgICAgIChjID09PSAweDVGLyogXyAqLykgPyAnXFx4QTAnIDpcbiAgICAgICAgKGMgPT09IDB4NEMvKiBMICovKSA/ICdcXHUyMDI4JyA6XG4gICAgICAgIChjID09PSAweDUwLyogUCAqLykgPyAnXFx1MjAyOScgOiAnJztcbn1cblxuZnVuY3Rpb24gY2hhckZyb21Db2RlcG9pbnQoYykge1xuICBpZiAoYyA8PSAweEZGRkYpIHtcbiAgICByZXR1cm4gU3RyaW5nLmZyb21DaGFyQ29kZShjKTtcbiAgfVxuICAvLyBFbmNvZGUgVVRGLTE2IHN1cnJvZ2F0ZSBwYWlyXG4gIC8vIGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1VURi0xNiNDb2RlX3BvaW50c19VLjJCMDEwMDAwX3RvX1UuMkIxMEZGRkZcbiAgcmV0dXJuIFN0cmluZy5mcm9tQ2hhckNvZGUoKChjIC0gMHgwMTAwMDApID4+IDEwKSArIDB4RDgwMCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKChjIC0gMHgwMTAwMDApICYgMHgwM0ZGKSArIDB4REMwMCk7XG59XG5cbnZhciBzaW1wbGVFc2NhcGVDaGVjayA9IG5ldyBBcnJheSgyNTYpOyAvLyBpbnRlZ2VyLCBmb3IgZmFzdCBhY2Nlc3NcbnZhciBzaW1wbGVFc2NhcGVNYXAgPSBuZXcgQXJyYXkoMjU2KTtcbmZvciAodmFyIGkgPSAwOyBpIDwgMjU2OyBpKyspIHtcbiAgc2ltcGxlRXNjYXBlQ2hlY2tbaV0gPSBzaW1wbGVFc2NhcGVTZXF1ZW5jZShpKSA/IDEgOiAwO1xuICBzaW1wbGVFc2NhcGVNYXBbaV0gPSBzaW1wbGVFc2NhcGVTZXF1ZW5jZShpKTtcbn1cblxuXG5mdW5jdGlvbiBTdGF0ZShpbnB1dCwgb3B0aW9ucykge1xuICB0aGlzLmlucHV0ID0gaW5wdXQ7XG5cbiAgdGhpcy5maWxlbmFtZSAgPSBvcHRpb25zWydmaWxlbmFtZSddICB8fCBudWxsO1xuICB0aGlzLnNjaGVtYSAgICA9IG9wdGlvbnNbJ3NjaGVtYSddICAgIHx8IERFRkFVTFRfRlVMTF9TQ0hFTUE7XG4gIHRoaXMub25XYXJuaW5nID0gb3B0aW9uc1snb25XYXJuaW5nJ10gfHwgbnVsbDtcbiAgdGhpcy5sZWdhY3kgICAgPSBvcHRpb25zWydsZWdhY3knXSAgICB8fCBmYWxzZTtcbiAgdGhpcy5qc29uICAgICAgPSBvcHRpb25zWydqc29uJ10gICAgICB8fCBmYWxzZTtcbiAgdGhpcy5saXN0ZW5lciAgPSBvcHRpb25zWydsaXN0ZW5lciddICB8fCBudWxsO1xuXG4gIHRoaXMuaW1wbGljaXRUeXBlcyA9IHRoaXMuc2NoZW1hLmNvbXBpbGVkSW1wbGljaXQ7XG4gIHRoaXMudHlwZU1hcCAgICAgICA9IHRoaXMuc2NoZW1hLmNvbXBpbGVkVHlwZU1hcDtcblxuICB0aGlzLmxlbmd0aCAgICAgPSBpbnB1dC5sZW5ndGg7XG4gIHRoaXMucG9zaXRpb24gICA9IDA7XG4gIHRoaXMubGluZSAgICAgICA9IDA7XG4gIHRoaXMubGluZVN0YXJ0ICA9IDA7XG4gIHRoaXMubGluZUluZGVudCA9IDA7XG5cbiAgdGhpcy5kb2N1bWVudHMgPSBbXTtcblxuICAvKlxuICB0aGlzLnZlcnNpb247XG4gIHRoaXMuY2hlY2tMaW5lQnJlYWtzO1xuICB0aGlzLnRhZ01hcDtcbiAgdGhpcy5hbmNob3JNYXA7XG4gIHRoaXMudGFnO1xuICB0aGlzLmFuY2hvcjtcbiAgdGhpcy5raW5kO1xuICB0aGlzLnJlc3VsdDsqL1xuXG59XG5cblxuZnVuY3Rpb24gZ2VuZXJhdGVFcnJvcihzdGF0ZSwgbWVzc2FnZSkge1xuICByZXR1cm4gbmV3IFlBTUxFeGNlcHRpb24oXG4gICAgbWVzc2FnZSxcbiAgICBuZXcgTWFyayhzdGF0ZS5maWxlbmFtZSwgc3RhdGUuaW5wdXQsIHN0YXRlLnBvc2l0aW9uLCBzdGF0ZS5saW5lLCAoc3RhdGUucG9zaXRpb24gLSBzdGF0ZS5saW5lU3RhcnQpKSk7XG59XG5cbmZ1bmN0aW9uIHRocm93RXJyb3Ioc3RhdGUsIG1lc3NhZ2UpIHtcbiAgdGhyb3cgZ2VuZXJhdGVFcnJvcihzdGF0ZSwgbWVzc2FnZSk7XG59XG5cbmZ1bmN0aW9uIHRocm93V2FybmluZyhzdGF0ZSwgbWVzc2FnZSkge1xuICBpZiAoc3RhdGUub25XYXJuaW5nKSB7XG4gICAgc3RhdGUub25XYXJuaW5nLmNhbGwobnVsbCwgZ2VuZXJhdGVFcnJvcihzdGF0ZSwgbWVzc2FnZSkpO1xuICB9XG59XG5cblxudmFyIGRpcmVjdGl2ZUhhbmRsZXJzID0ge1xuXG4gIFlBTUw6IGZ1bmN0aW9uIGhhbmRsZVlhbWxEaXJlY3RpdmUoc3RhdGUsIG5hbWUsIGFyZ3MpIHtcblxuICAgIHZhciBtYXRjaCwgbWFqb3IsIG1pbm9yO1xuXG4gICAgaWYgKHN0YXRlLnZlcnNpb24gIT09IG51bGwpIHtcbiAgICAgIHRocm93RXJyb3Ioc3RhdGUsICdkdXBsaWNhdGlvbiBvZiAlWUFNTCBkaXJlY3RpdmUnKTtcbiAgICB9XG5cbiAgICBpZiAoYXJncy5sZW5ndGggIT09IDEpIHtcbiAgICAgIHRocm93RXJyb3Ioc3RhdGUsICdZQU1MIGRpcmVjdGl2ZSBhY2NlcHRzIGV4YWN0bHkgb25lIGFyZ3VtZW50Jyk7XG4gICAgfVxuXG4gICAgbWF0Y2ggPSAvXihbMC05XSspXFwuKFswLTldKykkLy5leGVjKGFyZ3NbMF0pO1xuXG4gICAgaWYgKG1hdGNoID09PSBudWxsKSB7XG4gICAgICB0aHJvd0Vycm9yKHN0YXRlLCAnaWxsLWZvcm1lZCBhcmd1bWVudCBvZiB0aGUgWUFNTCBkaXJlY3RpdmUnKTtcbiAgICB9XG5cbiAgICBtYWpvciA9IHBhcnNlSW50KG1hdGNoWzFdLCAxMCk7XG4gICAgbWlub3IgPSBwYXJzZUludChtYXRjaFsyXSwgMTApO1xuXG4gICAgaWYgKG1ham9yICE9PSAxKSB7XG4gICAgICB0aHJvd0Vycm9yKHN0YXRlLCAndW5hY2NlcHRhYmxlIFlBTUwgdmVyc2lvbiBvZiB0aGUgZG9jdW1lbnQnKTtcbiAgICB9XG5cbiAgICBzdGF0ZS52ZXJzaW9uID0gYXJnc1swXTtcbiAgICBzdGF0ZS5jaGVja0xpbmVCcmVha3MgPSAobWlub3IgPCAyKTtcblxuICAgIGlmIChtaW5vciAhPT0gMSAmJiBtaW5vciAhPT0gMikge1xuICAgICAgdGhyb3dXYXJuaW5nKHN0YXRlLCAndW5zdXBwb3J0ZWQgWUFNTCB2ZXJzaW9uIG9mIHRoZSBkb2N1bWVudCcpO1xuICAgIH1cbiAgfSxcblxuICBUQUc6IGZ1bmN0aW9uIGhhbmRsZVRhZ0RpcmVjdGl2ZShzdGF0ZSwgbmFtZSwgYXJncykge1xuXG4gICAgdmFyIGhhbmRsZSwgcHJlZml4O1xuXG4gICAgaWYgKGFyZ3MubGVuZ3RoICE9PSAyKSB7XG4gICAgICB0aHJvd0Vycm9yKHN0YXRlLCAnVEFHIGRpcmVjdGl2ZSBhY2NlcHRzIGV4YWN0bHkgdHdvIGFyZ3VtZW50cycpO1xuICAgIH1cblxuICAgIGhhbmRsZSA9IGFyZ3NbMF07XG4gICAgcHJlZml4ID0gYXJnc1sxXTtcblxuICAgIGlmICghUEFUVEVSTl9UQUdfSEFORExFLnRlc3QoaGFuZGxlKSkge1xuICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ2lsbC1mb3JtZWQgdGFnIGhhbmRsZSAoZmlyc3QgYXJndW1lbnQpIG9mIHRoZSBUQUcgZGlyZWN0aXZlJyk7XG4gICAgfVxuXG4gICAgaWYgKF9oYXNPd25Qcm9wZXJ0eS5jYWxsKHN0YXRlLnRhZ01hcCwgaGFuZGxlKSkge1xuICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ3RoZXJlIGlzIGEgcHJldmlvdXNseSBkZWNsYXJlZCBzdWZmaXggZm9yIFwiJyArIGhhbmRsZSArICdcIiB0YWcgaGFuZGxlJyk7XG4gICAgfVxuXG4gICAgaWYgKCFQQVRURVJOX1RBR19VUkkudGVzdChwcmVmaXgpKSB7XG4gICAgICB0aHJvd0Vycm9yKHN0YXRlLCAnaWxsLWZvcm1lZCB0YWcgcHJlZml4IChzZWNvbmQgYXJndW1lbnQpIG9mIHRoZSBUQUcgZGlyZWN0aXZlJyk7XG4gICAgfVxuXG4gICAgc3RhdGUudGFnTWFwW2hhbmRsZV0gPSBwcmVmaXg7XG4gIH1cbn07XG5cblxuZnVuY3Rpb24gY2FwdHVyZVNlZ21lbnQoc3RhdGUsIHN0YXJ0LCBlbmQsIGNoZWNrSnNvbikge1xuICB2YXIgX3Bvc2l0aW9uLCBfbGVuZ3RoLCBfY2hhcmFjdGVyLCBfcmVzdWx0O1xuXG4gIGlmIChzdGFydCA8IGVuZCkge1xuICAgIF9yZXN1bHQgPSBzdGF0ZS5pbnB1dC5zbGljZShzdGFydCwgZW5kKTtcblxuICAgIGlmIChjaGVja0pzb24pIHtcbiAgICAgIGZvciAoX3Bvc2l0aW9uID0gMCwgX2xlbmd0aCA9IF9yZXN1bHQubGVuZ3RoO1xuICAgICAgICAgICBfcG9zaXRpb24gPCBfbGVuZ3RoO1xuICAgICAgICAgICBfcG9zaXRpb24gKz0gMSkge1xuICAgICAgICBfY2hhcmFjdGVyID0gX3Jlc3VsdC5jaGFyQ29kZUF0KF9wb3NpdGlvbik7XG4gICAgICAgIGlmICghKF9jaGFyYWN0ZXIgPT09IDB4MDkgfHxcbiAgICAgICAgICAgICAgKDB4MjAgPD0gX2NoYXJhY3RlciAmJiBfY2hhcmFjdGVyIDw9IDB4MTBGRkZGKSkpIHtcbiAgICAgICAgICB0aHJvd0Vycm9yKHN0YXRlLCAnZXhwZWN0ZWQgdmFsaWQgSlNPTiBjaGFyYWN0ZXInKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoUEFUVEVSTl9OT05fUFJJTlRBQkxFLnRlc3QoX3Jlc3VsdCkpIHtcbiAgICAgIHRocm93RXJyb3Ioc3RhdGUsICd0aGUgc3RyZWFtIGNvbnRhaW5zIG5vbi1wcmludGFibGUgY2hhcmFjdGVycycpO1xuICAgIH1cblxuICAgIHN0YXRlLnJlc3VsdCArPSBfcmVzdWx0O1xuICB9XG59XG5cbmZ1bmN0aW9uIG1lcmdlTWFwcGluZ3Moc3RhdGUsIGRlc3RpbmF0aW9uLCBzb3VyY2UsIG92ZXJyaWRhYmxlS2V5cykge1xuICB2YXIgc291cmNlS2V5cywga2V5LCBpbmRleCwgcXVhbnRpdHk7XG5cbiAgaWYgKCFjb21tb24uaXNPYmplY3Qoc291cmNlKSkge1xuICAgIHRocm93RXJyb3Ioc3RhdGUsICdjYW5ub3QgbWVyZ2UgbWFwcGluZ3M7IHRoZSBwcm92aWRlZCBzb3VyY2Ugb2JqZWN0IGlzIHVuYWNjZXB0YWJsZScpO1xuICB9XG5cbiAgc291cmNlS2V5cyA9IE9iamVjdC5rZXlzKHNvdXJjZSk7XG5cbiAgZm9yIChpbmRleCA9IDAsIHF1YW50aXR5ID0gc291cmNlS2V5cy5sZW5ndGg7IGluZGV4IDwgcXVhbnRpdHk7IGluZGV4ICs9IDEpIHtcbiAgICBrZXkgPSBzb3VyY2VLZXlzW2luZGV4XTtcblxuICAgIGlmICghX2hhc093blByb3BlcnR5LmNhbGwoZGVzdGluYXRpb24sIGtleSkpIHtcbiAgICAgIGRlc3RpbmF0aW9uW2tleV0gPSBzb3VyY2Vba2V5XTtcbiAgICAgIG92ZXJyaWRhYmxlS2V5c1trZXldID0gdHJ1ZTtcbiAgICB9XG4gIH1cbn1cblxuZnVuY3Rpb24gc3RvcmVNYXBwaW5nUGFpcihzdGF0ZSwgX3Jlc3VsdCwgb3ZlcnJpZGFibGVLZXlzLCBrZXlUYWcsIGtleU5vZGUsIHZhbHVlTm9kZSkge1xuICB2YXIgaW5kZXgsIHF1YW50aXR5O1xuXG4gIGtleU5vZGUgPSBTdHJpbmcoa2V5Tm9kZSk7XG5cbiAgaWYgKF9yZXN1bHQgPT09IG51bGwpIHtcbiAgICBfcmVzdWx0ID0ge307XG4gIH1cblxuICBpZiAoa2V5VGFnID09PSAndGFnOnlhbWwub3JnLDIwMDI6bWVyZ2UnKSB7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWVOb2RlKSkge1xuICAgICAgZm9yIChpbmRleCA9IDAsIHF1YW50aXR5ID0gdmFsdWVOb2RlLmxlbmd0aDsgaW5kZXggPCBxdWFudGl0eTsgaW5kZXggKz0gMSkge1xuICAgICAgICBtZXJnZU1hcHBpbmdzKHN0YXRlLCBfcmVzdWx0LCB2YWx1ZU5vZGVbaW5kZXhdLCBvdmVycmlkYWJsZUtleXMpO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBtZXJnZU1hcHBpbmdzKHN0YXRlLCBfcmVzdWx0LCB2YWx1ZU5vZGUsIG92ZXJyaWRhYmxlS2V5cyk7XG4gICAgfVxuICB9IGVsc2Uge1xuICAgIGlmICghc3RhdGUuanNvbiAmJlxuICAgICAgICAhX2hhc093blByb3BlcnR5LmNhbGwob3ZlcnJpZGFibGVLZXlzLCBrZXlOb2RlKSAmJlxuICAgICAgICBfaGFzT3duUHJvcGVydHkuY2FsbChfcmVzdWx0LCBrZXlOb2RlKSkge1xuICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ2R1cGxpY2F0ZWQgbWFwcGluZyBrZXknKTtcbiAgICB9XG4gICAgX3Jlc3VsdFtrZXlOb2RlXSA9IHZhbHVlTm9kZTtcbiAgICBkZWxldGUgb3ZlcnJpZGFibGVLZXlzW2tleU5vZGVdO1xuICB9XG5cbiAgcmV0dXJuIF9yZXN1bHQ7XG59XG5cbmZ1bmN0aW9uIHJlYWRMaW5lQnJlYWsoc3RhdGUpIHtcbiAgdmFyIGNoO1xuXG4gIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG5cbiAgaWYgKGNoID09PSAweDBBLyogTEYgKi8pIHtcbiAgICBzdGF0ZS5wb3NpdGlvbisrO1xuICB9IGVsc2UgaWYgKGNoID09PSAweDBELyogQ1IgKi8pIHtcbiAgICBzdGF0ZS5wb3NpdGlvbisrO1xuICAgIGlmIChzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uKSA9PT0gMHgwQS8qIExGICovKSB7XG4gICAgICBzdGF0ZS5wb3NpdGlvbisrO1xuICAgIH1cbiAgfSBlbHNlIHtcbiAgICB0aHJvd0Vycm9yKHN0YXRlLCAnYSBsaW5lIGJyZWFrIGlzIGV4cGVjdGVkJyk7XG4gIH1cblxuICBzdGF0ZS5saW5lICs9IDE7XG4gIHN0YXRlLmxpbmVTdGFydCA9IHN0YXRlLnBvc2l0aW9uO1xufVxuXG5mdW5jdGlvbiBza2lwU2VwYXJhdGlvblNwYWNlKHN0YXRlLCBhbGxvd0NvbW1lbnRzLCBjaGVja0luZGVudCkge1xuICB2YXIgbGluZUJyZWFrcyA9IDAsXG4gICAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24pO1xuXG4gIHdoaWxlIChjaCAhPT0gMCkge1xuICAgIHdoaWxlIChpc19XSElURV9TUEFDRShjaCkpIHtcbiAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcbiAgICB9XG5cbiAgICBpZiAoYWxsb3dDb21tZW50cyAmJiBjaCA9PT0gMHgyMy8qICMgKi8pIHtcbiAgICAgIGRvIHtcbiAgICAgICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuICAgICAgfSB3aGlsZSAoY2ggIT09IDB4MEEvKiBMRiAqLyAmJiBjaCAhPT0gMHgwRC8qIENSICovICYmIGNoICE9PSAwKTtcbiAgICB9XG5cbiAgICBpZiAoaXNfRU9MKGNoKSkge1xuICAgICAgcmVhZExpbmVCcmVhayhzdGF0ZSk7XG5cbiAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG4gICAgICBsaW5lQnJlYWtzKys7XG4gICAgICBzdGF0ZS5saW5lSW5kZW50ID0gMDtcblxuICAgICAgd2hpbGUgKGNoID09PSAweDIwLyogU3BhY2UgKi8pIHtcbiAgICAgICAgc3RhdGUubGluZUluZGVudCsrO1xuICAgICAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoKytzdGF0ZS5wb3NpdGlvbik7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGJyZWFrO1xuICAgIH1cbiAgfVxuXG4gIGlmIChjaGVja0luZGVudCAhPT0gLTEgJiYgbGluZUJyZWFrcyAhPT0gMCAmJiBzdGF0ZS5saW5lSW5kZW50IDwgY2hlY2tJbmRlbnQpIHtcbiAgICB0aHJvd1dhcm5pbmcoc3RhdGUsICdkZWZpY2llbnQgaW5kZW50YXRpb24nKTtcbiAgfVxuXG4gIHJldHVybiBsaW5lQnJlYWtzO1xufVxuXG5mdW5jdGlvbiB0ZXN0RG9jdW1lbnRTZXBhcmF0b3Ioc3RhdGUpIHtcbiAgdmFyIF9wb3NpdGlvbiA9IHN0YXRlLnBvc2l0aW9uLFxuICAgICAgY2g7XG5cbiAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KF9wb3NpdGlvbik7XG5cbiAgLy8gQ29uZGl0aW9uIHN0YXRlLnBvc2l0aW9uID09PSBzdGF0ZS5saW5lU3RhcnQgaXMgdGVzdGVkXG4gIC8vIGluIHBhcmVudCBvbiBlYWNoIGNhbGwsIGZvciBlZmZpY2llbmN5LiBObyBuZWVkcyB0byB0ZXN0IGhlcmUgYWdhaW4uXG4gIGlmICgoY2ggPT09IDB4MkQvKiAtICovIHx8IGNoID09PSAweDJFLyogLiAqLykgJiZcbiAgICAgIGNoID09PSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KF9wb3NpdGlvbiArIDEpICYmXG4gICAgICBjaCA9PT0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChfcG9zaXRpb24gKyAyKSkge1xuXG4gICAgX3Bvc2l0aW9uICs9IDM7XG5cbiAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoX3Bvc2l0aW9uKTtcblxuICAgIGlmIChjaCA9PT0gMCB8fCBpc19XU19PUl9FT0woY2gpKSB7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gZmFsc2U7XG59XG5cbmZ1bmN0aW9uIHdyaXRlRm9sZGVkTGluZXMoc3RhdGUsIGNvdW50KSB7XG4gIGlmIChjb3VudCA9PT0gMSkge1xuICAgIHN0YXRlLnJlc3VsdCArPSAnICc7XG4gIH0gZWxzZSBpZiAoY291bnQgPiAxKSB7XG4gICAgc3RhdGUucmVzdWx0ICs9IGNvbW1vbi5yZXBlYXQoJ1xcbicsIGNvdW50IC0gMSk7XG4gIH1cbn1cblxuXG5mdW5jdGlvbiByZWFkUGxhaW5TY2FsYXIoc3RhdGUsIG5vZGVJbmRlbnQsIHdpdGhpbkZsb3dDb2xsZWN0aW9uKSB7XG4gIHZhciBwcmVjZWRpbmcsXG4gICAgICBmb2xsb3dpbmcsXG4gICAgICBjYXB0dXJlU3RhcnQsXG4gICAgICBjYXB0dXJlRW5kLFxuICAgICAgaGFzUGVuZGluZ0NvbnRlbnQsXG4gICAgICBfbGluZSxcbiAgICAgIF9saW5lU3RhcnQsXG4gICAgICBfbGluZUluZGVudCxcbiAgICAgIF9raW5kID0gc3RhdGUua2luZCxcbiAgICAgIF9yZXN1bHQgPSBzdGF0ZS5yZXN1bHQsXG4gICAgICBjaDtcblxuICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24pO1xuXG4gIGlmIChpc19XU19PUl9FT0woY2gpICAgICAgfHxcbiAgICAgIGlzX0ZMT1dfSU5ESUNBVE9SKGNoKSB8fFxuICAgICAgY2ggPT09IDB4MjMvKiAjICovICAgIHx8XG4gICAgICBjaCA9PT0gMHgyNi8qICYgKi8gICAgfHxcbiAgICAgIGNoID09PSAweDJBLyogKiAqLyAgICB8fFxuICAgICAgY2ggPT09IDB4MjEvKiAhICovICAgIHx8XG4gICAgICBjaCA9PT0gMHg3Qy8qIHwgKi8gICAgfHxcbiAgICAgIGNoID09PSAweDNFLyogPiAqLyAgICB8fFxuICAgICAgY2ggPT09IDB4MjcvKiAnICovICAgIHx8XG4gICAgICBjaCA9PT0gMHgyMi8qIFwiICovICAgIHx8XG4gICAgICBjaCA9PT0gMHgyNS8qICUgKi8gICAgfHxcbiAgICAgIGNoID09PSAweDQwLyogQCAqLyAgICB8fFxuICAgICAgY2ggPT09IDB4NjAvKiBgICovKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgaWYgKGNoID09PSAweDNGLyogPyAqLyB8fCBjaCA9PT0gMHgyRC8qIC0gKi8pIHtcbiAgICBmb2xsb3dpbmcgPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uICsgMSk7XG5cbiAgICBpZiAoaXNfV1NfT1JfRU9MKGZvbGxvd2luZykgfHxcbiAgICAgICAgd2l0aGluRmxvd0NvbGxlY3Rpb24gJiYgaXNfRkxPV19JTkRJQ0FUT1IoZm9sbG93aW5nKSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIHN0YXRlLmtpbmQgPSAnc2NhbGFyJztcbiAgc3RhdGUucmVzdWx0ID0gJyc7XG4gIGNhcHR1cmVTdGFydCA9IGNhcHR1cmVFbmQgPSBzdGF0ZS5wb3NpdGlvbjtcbiAgaGFzUGVuZGluZ0NvbnRlbnQgPSBmYWxzZTtcblxuICB3aGlsZSAoY2ggIT09IDApIHtcbiAgICBpZiAoY2ggPT09IDB4M0EvKiA6ICovKSB7XG4gICAgICBmb2xsb3dpbmcgPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uICsgMSk7XG5cbiAgICAgIGlmIChpc19XU19PUl9FT0woZm9sbG93aW5nKSB8fFxuICAgICAgICAgIHdpdGhpbkZsb3dDb2xsZWN0aW9uICYmIGlzX0ZMT1dfSU5ESUNBVE9SKGZvbGxvd2luZykpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG5cbiAgICB9IGVsc2UgaWYgKGNoID09PSAweDIzLyogIyAqLykge1xuICAgICAgcHJlY2VkaW5nID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbiAtIDEpO1xuXG4gICAgICBpZiAoaXNfV1NfT1JfRU9MKHByZWNlZGluZykpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG5cbiAgICB9IGVsc2UgaWYgKChzdGF0ZS5wb3NpdGlvbiA9PT0gc3RhdGUubGluZVN0YXJ0ICYmIHRlc3REb2N1bWVudFNlcGFyYXRvcihzdGF0ZSkpIHx8XG4gICAgICAgICAgICAgICB3aXRoaW5GbG93Q29sbGVjdGlvbiAmJiBpc19GTE9XX0lORElDQVRPUihjaCkpIHtcbiAgICAgIGJyZWFrO1xuXG4gICAgfSBlbHNlIGlmIChpc19FT0woY2gpKSB7XG4gICAgICBfbGluZSA9IHN0YXRlLmxpbmU7XG4gICAgICBfbGluZVN0YXJ0ID0gc3RhdGUubGluZVN0YXJ0O1xuICAgICAgX2xpbmVJbmRlbnQgPSBzdGF0ZS5saW5lSW5kZW50O1xuICAgICAgc2tpcFNlcGFyYXRpb25TcGFjZShzdGF0ZSwgZmFsc2UsIC0xKTtcblxuICAgICAgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPj0gbm9kZUluZGVudCkge1xuICAgICAgICBoYXNQZW5kaW5nQ29udGVudCA9IHRydWU7XG4gICAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgc3RhdGUucG9zaXRpb24gPSBjYXB0dXJlRW5kO1xuICAgICAgICBzdGF0ZS5saW5lID0gX2xpbmU7XG4gICAgICAgIHN0YXRlLmxpbmVTdGFydCA9IF9saW5lU3RhcnQ7XG4gICAgICAgIHN0YXRlLmxpbmVJbmRlbnQgPSBfbGluZUluZGVudDtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGhhc1BlbmRpbmdDb250ZW50KSB7XG4gICAgICBjYXB0dXJlU2VnbWVudChzdGF0ZSwgY2FwdHVyZVN0YXJ0LCBjYXB0dXJlRW5kLCBmYWxzZSk7XG4gICAgICB3cml0ZUZvbGRlZExpbmVzKHN0YXRlLCBzdGF0ZS5saW5lIC0gX2xpbmUpO1xuICAgICAgY2FwdHVyZVN0YXJ0ID0gY2FwdHVyZUVuZCA9IHN0YXRlLnBvc2l0aW9uO1xuICAgICAgaGFzUGVuZGluZ0NvbnRlbnQgPSBmYWxzZTtcbiAgICB9XG5cbiAgICBpZiAoIWlzX1dISVRFX1NQQUNFKGNoKSkge1xuICAgICAgY2FwdHVyZUVuZCA9IHN0YXRlLnBvc2l0aW9uICsgMTtcbiAgICB9XG5cbiAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoKytzdGF0ZS5wb3NpdGlvbik7XG4gIH1cblxuICBjYXB0dXJlU2VnbWVudChzdGF0ZSwgY2FwdHVyZVN0YXJ0LCBjYXB0dXJlRW5kLCBmYWxzZSk7XG5cbiAgaWYgKHN0YXRlLnJlc3VsdCkge1xuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgc3RhdGUua2luZCA9IF9raW5kO1xuICBzdGF0ZS5yZXN1bHQgPSBfcmVzdWx0O1xuICByZXR1cm4gZmFsc2U7XG59XG5cbmZ1bmN0aW9uIHJlYWRTaW5nbGVRdW90ZWRTY2FsYXIoc3RhdGUsIG5vZGVJbmRlbnQpIHtcbiAgdmFyIGNoLFxuICAgICAgY2FwdHVyZVN0YXJ0LCBjYXB0dXJlRW5kO1xuXG4gIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG5cbiAgaWYgKGNoICE9PSAweDI3LyogJyAqLykge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIHN0YXRlLmtpbmQgPSAnc2NhbGFyJztcbiAgc3RhdGUucmVzdWx0ID0gJyc7XG4gIHN0YXRlLnBvc2l0aW9uKys7XG4gIGNhcHR1cmVTdGFydCA9IGNhcHR1cmVFbmQgPSBzdGF0ZS5wb3NpdGlvbjtcblxuICB3aGlsZSAoKGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbikpICE9PSAwKSB7XG4gICAgaWYgKGNoID09PSAweDI3LyogJyAqLykge1xuICAgICAgY2FwdHVyZVNlZ21lbnQoc3RhdGUsIGNhcHR1cmVTdGFydCwgc3RhdGUucG9zaXRpb24sIHRydWUpO1xuICAgICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuXG4gICAgICBpZiAoY2ggPT09IDB4MjcvKiAnICovKSB7XG4gICAgICAgIGNhcHR1cmVTdGFydCA9IGNhcHR1cmVFbmQgPSBzdGF0ZS5wb3NpdGlvbjtcbiAgICAgICAgc3RhdGUucG9zaXRpb24rKztcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuXG4gICAgfSBlbHNlIGlmIChpc19FT0woY2gpKSB7XG4gICAgICBjYXB0dXJlU2VnbWVudChzdGF0ZSwgY2FwdHVyZVN0YXJ0LCBjYXB0dXJlRW5kLCB0cnVlKTtcbiAgICAgIHdyaXRlRm9sZGVkTGluZXMoc3RhdGUsIHNraXBTZXBhcmF0aW9uU3BhY2Uoc3RhdGUsIGZhbHNlLCBub2RlSW5kZW50KSk7XG4gICAgICBjYXB0dXJlU3RhcnQgPSBjYXB0dXJlRW5kID0gc3RhdGUucG9zaXRpb247XG5cbiAgICB9IGVsc2UgaWYgKHN0YXRlLnBvc2l0aW9uID09PSBzdGF0ZS5saW5lU3RhcnQgJiYgdGVzdERvY3VtZW50U2VwYXJhdG9yKHN0YXRlKSkge1xuICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ3VuZXhwZWN0ZWQgZW5kIG9mIHRoZSBkb2N1bWVudCB3aXRoaW4gYSBzaW5nbGUgcXVvdGVkIHNjYWxhcicpO1xuXG4gICAgfSBlbHNlIHtcbiAgICAgIHN0YXRlLnBvc2l0aW9uKys7XG4gICAgICBjYXB0dXJlRW5kID0gc3RhdGUucG9zaXRpb247XG4gICAgfVxuICB9XG5cbiAgdGhyb3dFcnJvcihzdGF0ZSwgJ3VuZXhwZWN0ZWQgZW5kIG9mIHRoZSBzdHJlYW0gd2l0aGluIGEgc2luZ2xlIHF1b3RlZCBzY2FsYXInKTtcbn1cblxuZnVuY3Rpb24gcmVhZERvdWJsZVF1b3RlZFNjYWxhcihzdGF0ZSwgbm9kZUluZGVudCkge1xuICB2YXIgY2FwdHVyZVN0YXJ0LFxuICAgICAgY2FwdHVyZUVuZCxcbiAgICAgIGhleExlbmd0aCxcbiAgICAgIGhleFJlc3VsdCxcbiAgICAgIHRtcCxcbiAgICAgIGNoO1xuXG4gIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG5cbiAgaWYgKGNoICE9PSAweDIyLyogXCIgKi8pIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBzdGF0ZS5raW5kID0gJ3NjYWxhcic7XG4gIHN0YXRlLnJlc3VsdCA9ICcnO1xuICBzdGF0ZS5wb3NpdGlvbisrO1xuICBjYXB0dXJlU3RhcnQgPSBjYXB0dXJlRW5kID0gc3RhdGUucG9zaXRpb247XG5cbiAgd2hpbGUgKChjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24pKSAhPT0gMCkge1xuICAgIGlmIChjaCA9PT0gMHgyMi8qIFwiICovKSB7XG4gICAgICBjYXB0dXJlU2VnbWVudChzdGF0ZSwgY2FwdHVyZVN0YXJ0LCBzdGF0ZS5wb3NpdGlvbiwgdHJ1ZSk7XG4gICAgICBzdGF0ZS5wb3NpdGlvbisrO1xuICAgICAgcmV0dXJuIHRydWU7XG5cbiAgICB9IGVsc2UgaWYgKGNoID09PSAweDVDLyogXFwgKi8pIHtcbiAgICAgIGNhcHR1cmVTZWdtZW50KHN0YXRlLCBjYXB0dXJlU3RhcnQsIHN0YXRlLnBvc2l0aW9uLCB0cnVlKTtcbiAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcblxuICAgICAgaWYgKGlzX0VPTChjaCkpIHtcbiAgICAgICAgc2tpcFNlcGFyYXRpb25TcGFjZShzdGF0ZSwgZmFsc2UsIG5vZGVJbmRlbnQpO1xuXG4gICAgICAgIC8vIFRPRE86IHJld29yayB0byBpbmxpbmUgZm4gd2l0aCBubyB0eXBlIGNhc3Q/XG4gICAgICB9IGVsc2UgaWYgKGNoIDwgMjU2ICYmIHNpbXBsZUVzY2FwZUNoZWNrW2NoXSkge1xuICAgICAgICBzdGF0ZS5yZXN1bHQgKz0gc2ltcGxlRXNjYXBlTWFwW2NoXTtcbiAgICAgICAgc3RhdGUucG9zaXRpb24rKztcblxuICAgICAgfSBlbHNlIGlmICgodG1wID0gZXNjYXBlZEhleExlbihjaCkpID4gMCkge1xuICAgICAgICBoZXhMZW5ndGggPSB0bXA7XG4gICAgICAgIGhleFJlc3VsdCA9IDA7XG5cbiAgICAgICAgZm9yICg7IGhleExlbmd0aCA+IDA7IGhleExlbmd0aC0tKSB7XG4gICAgICAgICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuXG4gICAgICAgICAgaWYgKCh0bXAgPSBmcm9tSGV4Q29kZShjaCkpID49IDApIHtcbiAgICAgICAgICAgIGhleFJlc3VsdCA9IChoZXhSZXN1bHQgPDwgNCkgKyB0bXA7XG5cbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ2V4cGVjdGVkIGhleGFkZWNpbWFsIGNoYXJhY3RlcicpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXRlLnJlc3VsdCArPSBjaGFyRnJvbUNvZGVwb2ludChoZXhSZXN1bHQpO1xuXG4gICAgICAgIHN0YXRlLnBvc2l0aW9uKys7XG5cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRocm93RXJyb3Ioc3RhdGUsICd1bmtub3duIGVzY2FwZSBzZXF1ZW5jZScpO1xuICAgICAgfVxuXG4gICAgICBjYXB0dXJlU3RhcnQgPSBjYXB0dXJlRW5kID0gc3RhdGUucG9zaXRpb247XG5cbiAgICB9IGVsc2UgaWYgKGlzX0VPTChjaCkpIHtcbiAgICAgIGNhcHR1cmVTZWdtZW50KHN0YXRlLCBjYXB0dXJlU3RhcnQsIGNhcHR1cmVFbmQsIHRydWUpO1xuICAgICAgd3JpdGVGb2xkZWRMaW5lcyhzdGF0ZSwgc2tpcFNlcGFyYXRpb25TcGFjZShzdGF0ZSwgZmFsc2UsIG5vZGVJbmRlbnQpKTtcbiAgICAgIGNhcHR1cmVTdGFydCA9IGNhcHR1cmVFbmQgPSBzdGF0ZS5wb3NpdGlvbjtcblxuICAgIH0gZWxzZSBpZiAoc3RhdGUucG9zaXRpb24gPT09IHN0YXRlLmxpbmVTdGFydCAmJiB0ZXN0RG9jdW1lbnRTZXBhcmF0b3Ioc3RhdGUpKSB7XG4gICAgICB0aHJvd0Vycm9yKHN0YXRlLCAndW5leHBlY3RlZCBlbmQgb2YgdGhlIGRvY3VtZW50IHdpdGhpbiBhIGRvdWJsZSBxdW90ZWQgc2NhbGFyJyk7XG5cbiAgICB9IGVsc2Uge1xuICAgICAgc3RhdGUucG9zaXRpb24rKztcbiAgICAgIGNhcHR1cmVFbmQgPSBzdGF0ZS5wb3NpdGlvbjtcbiAgICB9XG4gIH1cblxuICB0aHJvd0Vycm9yKHN0YXRlLCAndW5leHBlY3RlZCBlbmQgb2YgdGhlIHN0cmVhbSB3aXRoaW4gYSBkb3VibGUgcXVvdGVkIHNjYWxhcicpO1xufVxuXG5mdW5jdGlvbiByZWFkRmxvd0NvbGxlY3Rpb24oc3RhdGUsIG5vZGVJbmRlbnQpIHtcbiAgdmFyIHJlYWROZXh0ID0gdHJ1ZSxcbiAgICAgIF9saW5lLFxuICAgICAgX3RhZyAgICAgPSBzdGF0ZS50YWcsXG4gICAgICBfcmVzdWx0LFxuICAgICAgX2FuY2hvciAgPSBzdGF0ZS5hbmNob3IsXG4gICAgICBmb2xsb3dpbmcsXG4gICAgICB0ZXJtaW5hdG9yLFxuICAgICAgaXNQYWlyLFxuICAgICAgaXNFeHBsaWNpdFBhaXIsXG4gICAgICBpc01hcHBpbmcsXG4gICAgICBvdmVycmlkYWJsZUtleXMgPSB7fSxcbiAgICAgIGtleU5vZGUsXG4gICAgICBrZXlUYWcsXG4gICAgICB2YWx1ZU5vZGUsXG4gICAgICBjaDtcblxuICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24pO1xuXG4gIGlmIChjaCA9PT0gMHg1Qi8qIFsgKi8pIHtcbiAgICB0ZXJtaW5hdG9yID0gMHg1RDsvKiBdICovXG4gICAgaXNNYXBwaW5nID0gZmFsc2U7XG4gICAgX3Jlc3VsdCA9IFtdO1xuICB9IGVsc2UgaWYgKGNoID09PSAweDdCLyogeyAqLykge1xuICAgIHRlcm1pbmF0b3IgPSAweDdEOy8qIH0gKi9cbiAgICBpc01hcHBpbmcgPSB0cnVlO1xuICAgIF9yZXN1bHQgPSB7fTtcbiAgfSBlbHNlIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBpZiAoc3RhdGUuYW5jaG9yICE9PSBudWxsKSB7XG4gICAgc3RhdGUuYW5jaG9yTWFwW3N0YXRlLmFuY2hvcl0gPSBfcmVzdWx0O1xuICB9XG5cbiAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuXG4gIHdoaWxlIChjaCAhPT0gMCkge1xuICAgIHNraXBTZXBhcmF0aW9uU3BhY2Uoc3RhdGUsIHRydWUsIG5vZGVJbmRlbnQpO1xuXG4gICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uKTtcblxuICAgIGlmIChjaCA9PT0gdGVybWluYXRvcikge1xuICAgICAgc3RhdGUucG9zaXRpb24rKztcbiAgICAgIHN0YXRlLnRhZyA9IF90YWc7XG4gICAgICBzdGF0ZS5hbmNob3IgPSBfYW5jaG9yO1xuICAgICAgc3RhdGUua2luZCA9IGlzTWFwcGluZyA/ICdtYXBwaW5nJyA6ICdzZXF1ZW5jZSc7XG4gICAgICBzdGF0ZS5yZXN1bHQgPSBfcmVzdWx0O1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfSBlbHNlIGlmICghcmVhZE5leHQpIHtcbiAgICAgIHRocm93RXJyb3Ioc3RhdGUsICdtaXNzZWQgY29tbWEgYmV0d2VlbiBmbG93IGNvbGxlY3Rpb24gZW50cmllcycpO1xuICAgIH1cblxuICAgIGtleVRhZyA9IGtleU5vZGUgPSB2YWx1ZU5vZGUgPSBudWxsO1xuICAgIGlzUGFpciA9IGlzRXhwbGljaXRQYWlyID0gZmFsc2U7XG5cbiAgICBpZiAoY2ggPT09IDB4M0YvKiA/ICovKSB7XG4gICAgICBmb2xsb3dpbmcgPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uICsgMSk7XG5cbiAgICAgIGlmIChpc19XU19PUl9FT0woZm9sbG93aW5nKSkge1xuICAgICAgICBpc1BhaXIgPSBpc0V4cGxpY2l0UGFpciA9IHRydWU7XG4gICAgICAgIHN0YXRlLnBvc2l0aW9uKys7XG4gICAgICAgIHNraXBTZXBhcmF0aW9uU3BhY2Uoc3RhdGUsIHRydWUsIG5vZGVJbmRlbnQpO1xuICAgICAgfVxuICAgIH1cblxuICAgIF9saW5lID0gc3RhdGUubGluZTtcbiAgICBjb21wb3NlTm9kZShzdGF0ZSwgbm9kZUluZGVudCwgQ09OVEVYVF9GTE9XX0lOLCBmYWxzZSwgdHJ1ZSk7XG4gICAga2V5VGFnID0gc3RhdGUudGFnO1xuICAgIGtleU5vZGUgPSBzdGF0ZS5yZXN1bHQ7XG4gICAgc2tpcFNlcGFyYXRpb25TcGFjZShzdGF0ZSwgdHJ1ZSwgbm9kZUluZGVudCk7XG5cbiAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24pO1xuXG4gICAgaWYgKChpc0V4cGxpY2l0UGFpciB8fCBzdGF0ZS5saW5lID09PSBfbGluZSkgJiYgY2ggPT09IDB4M0EvKiA6ICovKSB7XG4gICAgICBpc1BhaXIgPSB0cnVlO1xuICAgICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuICAgICAgc2tpcFNlcGFyYXRpb25TcGFjZShzdGF0ZSwgdHJ1ZSwgbm9kZUluZGVudCk7XG4gICAgICBjb21wb3NlTm9kZShzdGF0ZSwgbm9kZUluZGVudCwgQ09OVEVYVF9GTE9XX0lOLCBmYWxzZSwgdHJ1ZSk7XG4gICAgICB2YWx1ZU5vZGUgPSBzdGF0ZS5yZXN1bHQ7XG4gICAgfVxuXG4gICAgaWYgKGlzTWFwcGluZykge1xuICAgICAgc3RvcmVNYXBwaW5nUGFpcihzdGF0ZSwgX3Jlc3VsdCwgb3ZlcnJpZGFibGVLZXlzLCBrZXlUYWcsIGtleU5vZGUsIHZhbHVlTm9kZSk7XG4gICAgfSBlbHNlIGlmIChpc1BhaXIpIHtcbiAgICAgIF9yZXN1bHQucHVzaChzdG9yZU1hcHBpbmdQYWlyKHN0YXRlLCBudWxsLCBvdmVycmlkYWJsZUtleXMsIGtleVRhZywga2V5Tm9kZSwgdmFsdWVOb2RlKSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIF9yZXN1bHQucHVzaChrZXlOb2RlKTtcbiAgICB9XG5cbiAgICBza2lwU2VwYXJhdGlvblNwYWNlKHN0YXRlLCB0cnVlLCBub2RlSW5kZW50KTtcblxuICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG5cbiAgICBpZiAoY2ggPT09IDB4MkMvKiAsICovKSB7XG4gICAgICByZWFkTmV4dCA9IHRydWU7XG4gICAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoKytzdGF0ZS5wb3NpdGlvbik7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJlYWROZXh0ID0gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgdGhyb3dFcnJvcihzdGF0ZSwgJ3VuZXhwZWN0ZWQgZW5kIG9mIHRoZSBzdHJlYW0gd2l0aGluIGEgZmxvdyBjb2xsZWN0aW9uJyk7XG59XG5cbmZ1bmN0aW9uIHJlYWRCbG9ja1NjYWxhcihzdGF0ZSwgbm9kZUluZGVudCkge1xuICB2YXIgY2FwdHVyZVN0YXJ0LFxuICAgICAgZm9sZGluZyxcbiAgICAgIGNob21waW5nICAgICAgID0gQ0hPTVBJTkdfQ0xJUCxcbiAgICAgIGRpZFJlYWRDb250ZW50ID0gZmFsc2UsXG4gICAgICBkZXRlY3RlZEluZGVudCA9IGZhbHNlLFxuICAgICAgdGV4dEluZGVudCAgICAgPSBub2RlSW5kZW50LFxuICAgICAgZW1wdHlMaW5lcyAgICAgPSAwLFxuICAgICAgYXRNb3JlSW5kZW50ZWQgPSBmYWxzZSxcbiAgICAgIHRtcCxcbiAgICAgIGNoO1xuXG4gIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG5cbiAgaWYgKGNoID09PSAweDdDLyogfCAqLykge1xuICAgIGZvbGRpbmcgPSBmYWxzZTtcbiAgfSBlbHNlIGlmIChjaCA9PT0gMHgzRS8qID4gKi8pIHtcbiAgICBmb2xkaW5nID0gdHJ1ZTtcbiAgfSBlbHNlIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBzdGF0ZS5raW5kID0gJ3NjYWxhcic7XG4gIHN0YXRlLnJlc3VsdCA9ICcnO1xuXG4gIHdoaWxlIChjaCAhPT0gMCkge1xuICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcblxuICAgIGlmIChjaCA9PT0gMHgyQi8qICsgKi8gfHwgY2ggPT09IDB4MkQvKiAtICovKSB7XG4gICAgICBpZiAoQ0hPTVBJTkdfQ0xJUCA9PT0gY2hvbXBpbmcpIHtcbiAgICAgICAgY2hvbXBpbmcgPSAoY2ggPT09IDB4MkIvKiArICovKSA/IENIT01QSU5HX0tFRVAgOiBDSE9NUElOR19TVFJJUDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRocm93RXJyb3Ioc3RhdGUsICdyZXBlYXQgb2YgYSBjaG9tcGluZyBtb2RlIGlkZW50aWZpZXInKTtcbiAgICAgIH1cblxuICAgIH0gZWxzZSBpZiAoKHRtcCA9IGZyb21EZWNpbWFsQ29kZShjaCkpID49IDApIHtcbiAgICAgIGlmICh0bXAgPT09IDApIHtcbiAgICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ2JhZCBleHBsaWNpdCBpbmRlbnRhdGlvbiB3aWR0aCBvZiBhIGJsb2NrIHNjYWxhcjsgaXQgY2Fubm90IGJlIGxlc3MgdGhhbiBvbmUnKTtcbiAgICAgIH0gZWxzZSBpZiAoIWRldGVjdGVkSW5kZW50KSB7XG4gICAgICAgIHRleHRJbmRlbnQgPSBub2RlSW5kZW50ICsgdG1wIC0gMTtcbiAgICAgICAgZGV0ZWN0ZWRJbmRlbnQgPSB0cnVlO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ3JlcGVhdCBvZiBhbiBpbmRlbnRhdGlvbiB3aWR0aCBpZGVudGlmaWVyJyk7XG4gICAgICB9XG5cbiAgICB9IGVsc2Uge1xuICAgICAgYnJlYWs7XG4gICAgfVxuICB9XG5cbiAgaWYgKGlzX1dISVRFX1NQQUNFKGNoKSkge1xuICAgIGRvIHsgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pOyB9XG4gICAgd2hpbGUgKGlzX1dISVRFX1NQQUNFKGNoKSk7XG5cbiAgICBpZiAoY2ggPT09IDB4MjMvKiAjICovKSB7XG4gICAgICBkbyB7IGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTsgfVxuICAgICAgd2hpbGUgKCFpc19FT0woY2gpICYmIChjaCAhPT0gMCkpO1xuICAgIH1cbiAgfVxuXG4gIHdoaWxlIChjaCAhPT0gMCkge1xuICAgIHJlYWRMaW5lQnJlYWsoc3RhdGUpO1xuICAgIHN0YXRlLmxpbmVJbmRlbnQgPSAwO1xuXG4gICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uKTtcblxuICAgIHdoaWxlICgoIWRldGVjdGVkSW5kZW50IHx8IHN0YXRlLmxpbmVJbmRlbnQgPCB0ZXh0SW5kZW50KSAmJlxuICAgICAgICAgICAoY2ggPT09IDB4MjAvKiBTcGFjZSAqLykpIHtcbiAgICAgIHN0YXRlLmxpbmVJbmRlbnQrKztcbiAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcbiAgICB9XG5cbiAgICBpZiAoIWRldGVjdGVkSW5kZW50ICYmIHN0YXRlLmxpbmVJbmRlbnQgPiB0ZXh0SW5kZW50KSB7XG4gICAgICB0ZXh0SW5kZW50ID0gc3RhdGUubGluZUluZGVudDtcbiAgICB9XG5cbiAgICBpZiAoaXNfRU9MKGNoKSkge1xuICAgICAgZW1wdHlMaW5lcysrO1xuICAgICAgY29udGludWU7XG4gICAgfVxuXG4gICAgLy8gRW5kIG9mIHRoZSBzY2FsYXIuXG4gICAgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPCB0ZXh0SW5kZW50KSB7XG5cbiAgICAgIC8vIFBlcmZvcm0gdGhlIGNob21waW5nLlxuICAgICAgaWYgKGNob21waW5nID09PSBDSE9NUElOR19LRUVQKSB7XG4gICAgICAgIHN0YXRlLnJlc3VsdCArPSBjb21tb24ucmVwZWF0KCdcXG4nLCBkaWRSZWFkQ29udGVudCA/IDEgKyBlbXB0eUxpbmVzIDogZW1wdHlMaW5lcyk7XG4gICAgICB9IGVsc2UgaWYgKGNob21waW5nID09PSBDSE9NUElOR19DTElQKSB7XG4gICAgICAgIGlmIChkaWRSZWFkQ29udGVudCkgeyAvLyBpLmUuIG9ubHkgaWYgdGhlIHNjYWxhciBpcyBub3QgZW1wdHkuXG4gICAgICAgICAgc3RhdGUucmVzdWx0ICs9ICdcXG4nO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIEJyZWFrIHRoaXMgYHdoaWxlYCBjeWNsZSBhbmQgZ28gdG8gdGhlIGZ1bmNpdG9uJ3MgZXBpbG9ndWUuXG4gICAgICBicmVhaztcbiAgICB9XG5cbiAgICAvLyBGb2xkZWQgc3R5bGU6IHVzZSBmYW5jeSBydWxlcyB0byBoYW5kbGUgbGluZSBicmVha3MuXG4gICAgaWYgKGZvbGRpbmcpIHtcblxuICAgICAgLy8gTGluZXMgc3RhcnRpbmcgd2l0aCB3aGl0ZSBzcGFjZSBjaGFyYWN0ZXJzIChtb3JlLWluZGVudGVkIGxpbmVzKSBhcmUgbm90IGZvbGRlZC5cbiAgICAgIGlmIChpc19XSElURV9TUEFDRShjaCkpIHtcbiAgICAgICAgYXRNb3JlSW5kZW50ZWQgPSB0cnVlO1xuICAgICAgICAvLyBleGNlcHQgZm9yIHRoZSBmaXJzdCBjb250ZW50IGxpbmUgKGNmLiBFeGFtcGxlIDguMSlcbiAgICAgICAgc3RhdGUucmVzdWx0ICs9IGNvbW1vbi5yZXBlYXQoJ1xcbicsIGRpZFJlYWRDb250ZW50ID8gMSArIGVtcHR5TGluZXMgOiBlbXB0eUxpbmVzKTtcblxuICAgICAgLy8gRW5kIG9mIG1vcmUtaW5kZW50ZWQgYmxvY2suXG4gICAgICB9IGVsc2UgaWYgKGF0TW9yZUluZGVudGVkKSB7XG4gICAgICAgIGF0TW9yZUluZGVudGVkID0gZmFsc2U7XG4gICAgICAgIHN0YXRlLnJlc3VsdCArPSBjb21tb24ucmVwZWF0KCdcXG4nLCBlbXB0eUxpbmVzICsgMSk7XG5cbiAgICAgIC8vIEp1c3Qgb25lIGxpbmUgYnJlYWsgLSBwZXJjZWl2ZSBhcyB0aGUgc2FtZSBsaW5lLlxuICAgICAgfSBlbHNlIGlmIChlbXB0eUxpbmVzID09PSAwKSB7XG4gICAgICAgIGlmIChkaWRSZWFkQ29udGVudCkgeyAvLyBpLmUuIG9ubHkgaWYgd2UgaGF2ZSBhbHJlYWR5IHJlYWQgc29tZSBzY2FsYXIgY29udGVudC5cbiAgICAgICAgICBzdGF0ZS5yZXN1bHQgKz0gJyAnO1xuICAgICAgICB9XG5cbiAgICAgIC8vIFNldmVyYWwgbGluZSBicmVha3MgLSBwZXJjZWl2ZSBhcyBkaWZmZXJlbnQgbGluZXMuXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBzdGF0ZS5yZXN1bHQgKz0gY29tbW9uLnJlcGVhdCgnXFxuJywgZW1wdHlMaW5lcyk7XG4gICAgICB9XG5cbiAgICAvLyBMaXRlcmFsIHN0eWxlOiBqdXN0IGFkZCBleGFjdCBudW1iZXIgb2YgbGluZSBicmVha3MgYmV0d2VlbiBjb250ZW50IGxpbmVzLlxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBLZWVwIGFsbCBsaW5lIGJyZWFrcyBleGNlcHQgdGhlIGhlYWRlciBsaW5lIGJyZWFrLlxuICAgICAgc3RhdGUucmVzdWx0ICs9IGNvbW1vbi5yZXBlYXQoJ1xcbicsIGRpZFJlYWRDb250ZW50ID8gMSArIGVtcHR5TGluZXMgOiBlbXB0eUxpbmVzKTtcbiAgICB9XG5cbiAgICBkaWRSZWFkQ29udGVudCA9IHRydWU7XG4gICAgZGV0ZWN0ZWRJbmRlbnQgPSB0cnVlO1xuICAgIGVtcHR5TGluZXMgPSAwO1xuICAgIGNhcHR1cmVTdGFydCA9IHN0YXRlLnBvc2l0aW9uO1xuXG4gICAgd2hpbGUgKCFpc19FT0woY2gpICYmIChjaCAhPT0gMCkpIHtcbiAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcbiAgICB9XG5cbiAgICBjYXB0dXJlU2VnbWVudChzdGF0ZSwgY2FwdHVyZVN0YXJ0LCBzdGF0ZS5wb3NpdGlvbiwgZmFsc2UpO1xuICB9XG5cbiAgcmV0dXJuIHRydWU7XG59XG5cbmZ1bmN0aW9uIHJlYWRCbG9ja1NlcXVlbmNlKHN0YXRlLCBub2RlSW5kZW50KSB7XG4gIHZhciBfbGluZSxcbiAgICAgIF90YWcgICAgICA9IHN0YXRlLnRhZyxcbiAgICAgIF9hbmNob3IgICA9IHN0YXRlLmFuY2hvcixcbiAgICAgIF9yZXN1bHQgICA9IFtdLFxuICAgICAgZm9sbG93aW5nLFxuICAgICAgZGV0ZWN0ZWQgID0gZmFsc2UsXG4gICAgICBjaDtcblxuICBpZiAoc3RhdGUuYW5jaG9yICE9PSBudWxsKSB7XG4gICAgc3RhdGUuYW5jaG9yTWFwW3N0YXRlLmFuY2hvcl0gPSBfcmVzdWx0O1xuICB9XG5cbiAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uKTtcblxuICB3aGlsZSAoY2ggIT09IDApIHtcblxuICAgIGlmIChjaCAhPT0gMHgyRC8qIC0gKi8pIHtcbiAgICAgIGJyZWFrO1xuICAgIH1cblxuICAgIGZvbGxvd2luZyA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24gKyAxKTtcblxuICAgIGlmICghaXNfV1NfT1JfRU9MKGZvbGxvd2luZykpIHtcbiAgICAgIGJyZWFrO1xuICAgIH1cblxuICAgIGRldGVjdGVkID0gdHJ1ZTtcbiAgICBzdGF0ZS5wb3NpdGlvbisrO1xuXG4gICAgaWYgKHNraXBTZXBhcmF0aW9uU3BhY2Uoc3RhdGUsIHRydWUsIC0xKSkge1xuICAgICAgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPD0gbm9kZUluZGVudCkge1xuICAgICAgICBfcmVzdWx0LnB1c2gobnVsbCk7XG4gICAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIF9saW5lID0gc3RhdGUubGluZTtcbiAgICBjb21wb3NlTm9kZShzdGF0ZSwgbm9kZUluZGVudCwgQ09OVEVYVF9CTE9DS19JTiwgZmFsc2UsIHRydWUpO1xuICAgIF9yZXN1bHQucHVzaChzdGF0ZS5yZXN1bHQpO1xuICAgIHNraXBTZXBhcmF0aW9uU3BhY2Uoc3RhdGUsIHRydWUsIC0xKTtcblxuICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG5cbiAgICBpZiAoKHN0YXRlLmxpbmUgPT09IF9saW5lIHx8IHN0YXRlLmxpbmVJbmRlbnQgPiBub2RlSW5kZW50KSAmJiAoY2ggIT09IDApKSB7XG4gICAgICB0aHJvd0Vycm9yKHN0YXRlLCAnYmFkIGluZGVudGF0aW9uIG9mIGEgc2VxdWVuY2UgZW50cnknKTtcbiAgICB9IGVsc2UgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPCBub2RlSW5kZW50KSB7XG4gICAgICBicmVhaztcbiAgICB9XG4gIH1cblxuICBpZiAoZGV0ZWN0ZWQpIHtcbiAgICBzdGF0ZS50YWcgPSBfdGFnO1xuICAgIHN0YXRlLmFuY2hvciA9IF9hbmNob3I7XG4gICAgc3RhdGUua2luZCA9ICdzZXF1ZW5jZSc7XG4gICAgc3RhdGUucmVzdWx0ID0gX3Jlc3VsdDtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuICByZXR1cm4gZmFsc2U7XG59XG5cbmZ1bmN0aW9uIHJlYWRCbG9ja01hcHBpbmcoc3RhdGUsIG5vZGVJbmRlbnQsIGZsb3dJbmRlbnQpIHtcbiAgdmFyIGZvbGxvd2luZyxcbiAgICAgIGFsbG93Q29tcGFjdCxcbiAgICAgIF9saW5lLFxuICAgICAgX3RhZyAgICAgICAgICA9IHN0YXRlLnRhZyxcbiAgICAgIF9hbmNob3IgICAgICAgPSBzdGF0ZS5hbmNob3IsXG4gICAgICBfcmVzdWx0ICAgICAgID0ge30sXG4gICAgICBvdmVycmlkYWJsZUtleXMgPSB7fSxcbiAgICAgIGtleVRhZyAgICAgICAgPSBudWxsLFxuICAgICAga2V5Tm9kZSAgICAgICA9IG51bGwsXG4gICAgICB2YWx1ZU5vZGUgICAgID0gbnVsbCxcbiAgICAgIGF0RXhwbGljaXRLZXkgPSBmYWxzZSxcbiAgICAgIGRldGVjdGVkICAgICAgPSBmYWxzZSxcbiAgICAgIGNoO1xuXG4gIGlmIChzdGF0ZS5hbmNob3IgIT09IG51bGwpIHtcbiAgICBzdGF0ZS5hbmNob3JNYXBbc3RhdGUuYW5jaG9yXSA9IF9yZXN1bHQ7XG4gIH1cblxuICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24pO1xuXG4gIHdoaWxlIChjaCAhPT0gMCkge1xuICAgIGZvbGxvd2luZyA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24gKyAxKTtcbiAgICBfbGluZSA9IHN0YXRlLmxpbmU7IC8vIFNhdmUgdGhlIGN1cnJlbnQgbGluZS5cblxuICAgIC8vXG4gICAgLy8gRXhwbGljaXQgbm90YXRpb24gY2FzZS4gVGhlcmUgYXJlIHR3byBzZXBhcmF0ZSBibG9ja3M6XG4gICAgLy8gZmlyc3QgZm9yIHRoZSBrZXkgKGRlbm90ZWQgYnkgXCI/XCIpIGFuZCBzZWNvbmQgZm9yIHRoZSB2YWx1ZSAoZGVub3RlZCBieSBcIjpcIilcbiAgICAvL1xuICAgIGlmICgoY2ggPT09IDB4M0YvKiA/ICovIHx8IGNoID09PSAweDNBLyogOiAqLykgJiYgaXNfV1NfT1JfRU9MKGZvbGxvd2luZykpIHtcblxuICAgICAgaWYgKGNoID09PSAweDNGLyogPyAqLykge1xuICAgICAgICBpZiAoYXRFeHBsaWNpdEtleSkge1xuICAgICAgICAgIHN0b3JlTWFwcGluZ1BhaXIoc3RhdGUsIF9yZXN1bHQsIG92ZXJyaWRhYmxlS2V5cywga2V5VGFnLCBrZXlOb2RlLCBudWxsKTtcbiAgICAgICAgICBrZXlUYWcgPSBrZXlOb2RlID0gdmFsdWVOb2RlID0gbnVsbDtcbiAgICAgICAgfVxuXG4gICAgICAgIGRldGVjdGVkID0gdHJ1ZTtcbiAgICAgICAgYXRFeHBsaWNpdEtleSA9IHRydWU7XG4gICAgICAgIGFsbG93Q29tcGFjdCA9IHRydWU7XG5cbiAgICAgIH0gZWxzZSBpZiAoYXRFeHBsaWNpdEtleSkge1xuICAgICAgICAvLyBpLmUuIDB4M0EvKiA6ICovID09PSBjaGFyYWN0ZXIgYWZ0ZXIgdGhlIGV4cGxpY2l0IGtleS5cbiAgICAgICAgYXRFeHBsaWNpdEtleSA9IGZhbHNlO1xuICAgICAgICBhbGxvd0NvbXBhY3QgPSB0cnVlO1xuXG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aHJvd0Vycm9yKHN0YXRlLCAnaW5jb21wbGV0ZSBleHBsaWNpdCBtYXBwaW5nIHBhaXI7IGEga2V5IG5vZGUgaXMgbWlzc2VkJyk7XG4gICAgICB9XG5cbiAgICAgIHN0YXRlLnBvc2l0aW9uICs9IDE7XG4gICAgICBjaCA9IGZvbGxvd2luZztcblxuICAgIC8vXG4gICAgLy8gSW1wbGljaXQgbm90YXRpb24gY2FzZS4gRmxvdy1zdHlsZSBub2RlIGFzIHRoZSBrZXkgZmlyc3QsIHRoZW4gXCI6XCIsIGFuZCB0aGUgdmFsdWUuXG4gICAgLy9cbiAgICB9IGVsc2UgaWYgKGNvbXBvc2VOb2RlKHN0YXRlLCBmbG93SW5kZW50LCBDT05URVhUX0ZMT1dfT1VULCBmYWxzZSwgdHJ1ZSkpIHtcblxuICAgICAgaWYgKHN0YXRlLmxpbmUgPT09IF9saW5lKSB7XG4gICAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG5cbiAgICAgICAgd2hpbGUgKGlzX1dISVRFX1NQQUNFKGNoKSkge1xuICAgICAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChjaCA9PT0gMHgzQS8qIDogKi8pIHtcbiAgICAgICAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoKytzdGF0ZS5wb3NpdGlvbik7XG5cbiAgICAgICAgICBpZiAoIWlzX1dTX09SX0VPTChjaCkpIHtcbiAgICAgICAgICAgIHRocm93RXJyb3Ioc3RhdGUsICdhIHdoaXRlc3BhY2UgY2hhcmFjdGVyIGlzIGV4cGVjdGVkIGFmdGVyIHRoZSBrZXktdmFsdWUgc2VwYXJhdG9yIHdpdGhpbiBhIGJsb2NrIG1hcHBpbmcnKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBpZiAoYXRFeHBsaWNpdEtleSkge1xuICAgICAgICAgICAgc3RvcmVNYXBwaW5nUGFpcihzdGF0ZSwgX3Jlc3VsdCwgb3ZlcnJpZGFibGVLZXlzLCBrZXlUYWcsIGtleU5vZGUsIG51bGwpO1xuICAgICAgICAgICAga2V5VGFnID0ga2V5Tm9kZSA9IHZhbHVlTm9kZSA9IG51bGw7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgZGV0ZWN0ZWQgPSB0cnVlO1xuICAgICAgICAgIGF0RXhwbGljaXRLZXkgPSBmYWxzZTtcbiAgICAgICAgICBhbGxvd0NvbXBhY3QgPSBmYWxzZTtcbiAgICAgICAgICBrZXlUYWcgPSBzdGF0ZS50YWc7XG4gICAgICAgICAga2V5Tm9kZSA9IHN0YXRlLnJlc3VsdDtcblxuICAgICAgICB9IGVsc2UgaWYgKGRldGVjdGVkKSB7XG4gICAgICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ2NhbiBub3QgcmVhZCBhbiBpbXBsaWNpdCBtYXBwaW5nIHBhaXI7IGEgY29sb24gaXMgbWlzc2VkJyk7XG5cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBzdGF0ZS50YWcgPSBfdGFnO1xuICAgICAgICAgIHN0YXRlLmFuY2hvciA9IF9hbmNob3I7XG4gICAgICAgICAgcmV0dXJuIHRydWU7IC8vIEtlZXAgdGhlIHJlc3VsdCBvZiBgY29tcG9zZU5vZGVgLlxuICAgICAgICB9XG5cbiAgICAgIH0gZWxzZSBpZiAoZGV0ZWN0ZWQpIHtcbiAgICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ2NhbiBub3QgcmVhZCBhIGJsb2NrIG1hcHBpbmcgZW50cnk7IGEgbXVsdGlsaW5lIGtleSBtYXkgbm90IGJlIGFuIGltcGxpY2l0IGtleScpO1xuXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBzdGF0ZS50YWcgPSBfdGFnO1xuICAgICAgICBzdGF0ZS5hbmNob3IgPSBfYW5jaG9yO1xuICAgICAgICByZXR1cm4gdHJ1ZTsgLy8gS2VlcCB0aGUgcmVzdWx0IG9mIGBjb21wb3NlTm9kZWAuXG4gICAgICB9XG5cbiAgICB9IGVsc2Uge1xuICAgICAgYnJlYWs7IC8vIFJlYWRpbmcgaXMgZG9uZS4gR28gdG8gdGhlIGVwaWxvZ3VlLlxuICAgIH1cblxuICAgIC8vXG4gICAgLy8gQ29tbW9uIHJlYWRpbmcgY29kZSBmb3IgYm90aCBleHBsaWNpdCBhbmQgaW1wbGljaXQgbm90YXRpb25zLlxuICAgIC8vXG4gICAgaWYgKHN0YXRlLmxpbmUgPT09IF9saW5lIHx8IHN0YXRlLmxpbmVJbmRlbnQgPiBub2RlSW5kZW50KSB7XG4gICAgICBpZiAoY29tcG9zZU5vZGUoc3RhdGUsIG5vZGVJbmRlbnQsIENPTlRFWFRfQkxPQ0tfT1VULCB0cnVlLCBhbGxvd0NvbXBhY3QpKSB7XG4gICAgICAgIGlmIChhdEV4cGxpY2l0S2V5KSB7XG4gICAgICAgICAga2V5Tm9kZSA9IHN0YXRlLnJlc3VsdDtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB2YWx1ZU5vZGUgPSBzdGF0ZS5yZXN1bHQ7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKCFhdEV4cGxpY2l0S2V5KSB7XG4gICAgICAgIHN0b3JlTWFwcGluZ1BhaXIoc3RhdGUsIF9yZXN1bHQsIG92ZXJyaWRhYmxlS2V5cywga2V5VGFnLCBrZXlOb2RlLCB2YWx1ZU5vZGUpO1xuICAgICAgICBrZXlUYWcgPSBrZXlOb2RlID0gdmFsdWVOb2RlID0gbnVsbDtcbiAgICAgIH1cblxuICAgICAgc2tpcFNlcGFyYXRpb25TcGFjZShzdGF0ZSwgdHJ1ZSwgLTEpO1xuICAgICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uKTtcbiAgICB9XG5cbiAgICBpZiAoc3RhdGUubGluZUluZGVudCA+IG5vZGVJbmRlbnQgJiYgKGNoICE9PSAwKSkge1xuICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ2JhZCBpbmRlbnRhdGlvbiBvZiBhIG1hcHBpbmcgZW50cnknKTtcbiAgICB9IGVsc2UgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPCBub2RlSW5kZW50KSB7XG4gICAgICBicmVhaztcbiAgICB9XG4gIH1cblxuICAvL1xuICAvLyBFcGlsb2d1ZS5cbiAgLy9cblxuICAvLyBTcGVjaWFsIGNhc2U6IGxhc3QgbWFwcGluZydzIG5vZGUgY29udGFpbnMgb25seSB0aGUga2V5IGluIGV4cGxpY2l0IG5vdGF0aW9uLlxuICBpZiAoYXRFeHBsaWNpdEtleSkge1xuICAgIHN0b3JlTWFwcGluZ1BhaXIoc3RhdGUsIF9yZXN1bHQsIG92ZXJyaWRhYmxlS2V5cywga2V5VGFnLCBrZXlOb2RlLCBudWxsKTtcbiAgfVxuXG4gIC8vIEV4cG9zZSB0aGUgcmVzdWx0aW5nIG1hcHBpbmcuXG4gIGlmIChkZXRlY3RlZCkge1xuICAgIHN0YXRlLnRhZyA9IF90YWc7XG4gICAgc3RhdGUuYW5jaG9yID0gX2FuY2hvcjtcbiAgICBzdGF0ZS5raW5kID0gJ21hcHBpbmcnO1xuICAgIHN0YXRlLnJlc3VsdCA9IF9yZXN1bHQ7XG4gIH1cblxuICByZXR1cm4gZGV0ZWN0ZWQ7XG59XG5cbmZ1bmN0aW9uIHJlYWRUYWdQcm9wZXJ0eShzdGF0ZSkge1xuICB2YXIgX3Bvc2l0aW9uLFxuICAgICAgaXNWZXJiYXRpbSA9IGZhbHNlLFxuICAgICAgaXNOYW1lZCAgICA9IGZhbHNlLFxuICAgICAgdGFnSGFuZGxlLFxuICAgICAgdGFnTmFtZSxcbiAgICAgIGNoO1xuXG4gIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbik7XG5cbiAgaWYgKGNoICE9PSAweDIxLyogISAqLykgcmV0dXJuIGZhbHNlO1xuXG4gIGlmIChzdGF0ZS50YWcgIT09IG51bGwpIHtcbiAgICB0aHJvd0Vycm9yKHN0YXRlLCAnZHVwbGljYXRpb24gb2YgYSB0YWcgcHJvcGVydHknKTtcbiAgfVxuXG4gIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcblxuICBpZiAoY2ggPT09IDB4M0MvKiA8ICovKSB7XG4gICAgaXNWZXJiYXRpbSA9IHRydWU7XG4gICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuXG4gIH0gZWxzZSBpZiAoY2ggPT09IDB4MjEvKiAhICovKSB7XG4gICAgaXNOYW1lZCA9IHRydWU7XG4gICAgdGFnSGFuZGxlID0gJyEhJztcbiAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoKytzdGF0ZS5wb3NpdGlvbik7XG5cbiAgfSBlbHNlIHtcbiAgICB0YWdIYW5kbGUgPSAnISc7XG4gIH1cblxuICBfcG9zaXRpb24gPSBzdGF0ZS5wb3NpdGlvbjtcblxuICBpZiAoaXNWZXJiYXRpbSkge1xuICAgIGRvIHsgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pOyB9XG4gICAgd2hpbGUgKGNoICE9PSAwICYmIGNoICE9PSAweDNFLyogPiAqLyk7XG5cbiAgICBpZiAoc3RhdGUucG9zaXRpb24gPCBzdGF0ZS5sZW5ndGgpIHtcbiAgICAgIHRhZ05hbWUgPSBzdGF0ZS5pbnB1dC5zbGljZShfcG9zaXRpb24sIHN0YXRlLnBvc2l0aW9uKTtcbiAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ3VuZXhwZWN0ZWQgZW5kIG9mIHRoZSBzdHJlYW0gd2l0aGluIGEgdmVyYmF0aW0gdGFnJyk7XG4gICAgfVxuICB9IGVsc2Uge1xuICAgIHdoaWxlIChjaCAhPT0gMCAmJiAhaXNfV1NfT1JfRU9MKGNoKSkge1xuXG4gICAgICBpZiAoY2ggPT09IDB4MjEvKiAhICovKSB7XG4gICAgICAgIGlmICghaXNOYW1lZCkge1xuICAgICAgICAgIHRhZ0hhbmRsZSA9IHN0YXRlLmlucHV0LnNsaWNlKF9wb3NpdGlvbiAtIDEsIHN0YXRlLnBvc2l0aW9uICsgMSk7XG5cbiAgICAgICAgICBpZiAoIVBBVFRFUk5fVEFHX0hBTkRMRS50ZXN0KHRhZ0hhbmRsZSkpIHtcbiAgICAgICAgICAgIHRocm93RXJyb3Ioc3RhdGUsICduYW1lZCB0YWcgaGFuZGxlIGNhbm5vdCBjb250YWluIHN1Y2ggY2hhcmFjdGVycycpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGlzTmFtZWQgPSB0cnVlO1xuICAgICAgICAgIF9wb3NpdGlvbiA9IHN0YXRlLnBvc2l0aW9uICsgMTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aHJvd0Vycm9yKHN0YXRlLCAndGFnIHN1ZmZpeCBjYW5ub3QgY29udGFpbiBleGNsYW1hdGlvbiBtYXJrcycpO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcbiAgICB9XG5cbiAgICB0YWdOYW1lID0gc3RhdGUuaW5wdXQuc2xpY2UoX3Bvc2l0aW9uLCBzdGF0ZS5wb3NpdGlvbik7XG5cbiAgICBpZiAoUEFUVEVSTl9GTE9XX0lORElDQVRPUlMudGVzdCh0YWdOYW1lKSkge1xuICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ3RhZyBzdWZmaXggY2Fubm90IGNvbnRhaW4gZmxvdyBpbmRpY2F0b3IgY2hhcmFjdGVycycpO1xuICAgIH1cbiAgfVxuXG4gIGlmICh0YWdOYW1lICYmICFQQVRURVJOX1RBR19VUkkudGVzdCh0YWdOYW1lKSkge1xuICAgIHRocm93RXJyb3Ioc3RhdGUsICd0YWcgbmFtZSBjYW5ub3QgY29udGFpbiBzdWNoIGNoYXJhY3RlcnM6ICcgKyB0YWdOYW1lKTtcbiAgfVxuXG4gIGlmIChpc1ZlcmJhdGltKSB7XG4gICAgc3RhdGUudGFnID0gdGFnTmFtZTtcblxuICB9IGVsc2UgaWYgKF9oYXNPd25Qcm9wZXJ0eS5jYWxsKHN0YXRlLnRhZ01hcCwgdGFnSGFuZGxlKSkge1xuICAgIHN0YXRlLnRhZyA9IHN0YXRlLnRhZ01hcFt0YWdIYW5kbGVdICsgdGFnTmFtZTtcblxuICB9IGVsc2UgaWYgKHRhZ0hhbmRsZSA9PT0gJyEnKSB7XG4gICAgc3RhdGUudGFnID0gJyEnICsgdGFnTmFtZTtcblxuICB9IGVsc2UgaWYgKHRhZ0hhbmRsZSA9PT0gJyEhJykge1xuICAgIHN0YXRlLnRhZyA9ICd0YWc6eWFtbC5vcmcsMjAwMjonICsgdGFnTmFtZTtcblxuICB9IGVsc2Uge1xuICAgIHRocm93RXJyb3Ioc3RhdGUsICd1bmRlY2xhcmVkIHRhZyBoYW5kbGUgXCInICsgdGFnSGFuZGxlICsgJ1wiJyk7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn1cblxuZnVuY3Rpb24gcmVhZEFuY2hvclByb3BlcnR5KHN0YXRlKSB7XG4gIHZhciBfcG9zaXRpb24sXG4gICAgICBjaDtcblxuICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24pO1xuXG4gIGlmIChjaCAhPT0gMHgyNi8qICYgKi8pIHJldHVybiBmYWxzZTtcblxuICBpZiAoc3RhdGUuYW5jaG9yICE9PSBudWxsKSB7XG4gICAgdGhyb3dFcnJvcihzdGF0ZSwgJ2R1cGxpY2F0aW9uIG9mIGFuIGFuY2hvciBwcm9wZXJ0eScpO1xuICB9XG5cbiAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuICBfcG9zaXRpb24gPSBzdGF0ZS5wb3NpdGlvbjtcblxuICB3aGlsZSAoY2ggIT09IDAgJiYgIWlzX1dTX09SX0VPTChjaCkgJiYgIWlzX0ZMT1dfSU5ESUNBVE9SKGNoKSkge1xuICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcbiAgfVxuXG4gIGlmIChzdGF0ZS5wb3NpdGlvbiA9PT0gX3Bvc2l0aW9uKSB7XG4gICAgdGhyb3dFcnJvcihzdGF0ZSwgJ25hbWUgb2YgYW4gYW5jaG9yIG5vZGUgbXVzdCBjb250YWluIGF0IGxlYXN0IG9uZSBjaGFyYWN0ZXInKTtcbiAgfVxuXG4gIHN0YXRlLmFuY2hvciA9IHN0YXRlLmlucHV0LnNsaWNlKF9wb3NpdGlvbiwgc3RhdGUucG9zaXRpb24pO1xuICByZXR1cm4gdHJ1ZTtcbn1cblxuZnVuY3Rpb24gcmVhZEFsaWFzKHN0YXRlKSB7XG4gIHZhciBfcG9zaXRpb24sIGFsaWFzLFxuICAgICAgY2g7XG5cbiAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uKTtcblxuICBpZiAoY2ggIT09IDB4MkEvKiAqICovKSByZXR1cm4gZmFsc2U7XG5cbiAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuICBfcG9zaXRpb24gPSBzdGF0ZS5wb3NpdGlvbjtcblxuICB3aGlsZSAoY2ggIT09IDAgJiYgIWlzX1dTX09SX0VPTChjaCkgJiYgIWlzX0ZMT1dfSU5ESUNBVE9SKGNoKSkge1xuICAgIGNoID0gc3RhdGUuaW5wdXQuY2hhckNvZGVBdCgrK3N0YXRlLnBvc2l0aW9uKTtcbiAgfVxuXG4gIGlmIChzdGF0ZS5wb3NpdGlvbiA9PT0gX3Bvc2l0aW9uKSB7XG4gICAgdGhyb3dFcnJvcihzdGF0ZSwgJ25hbWUgb2YgYW4gYWxpYXMgbm9kZSBtdXN0IGNvbnRhaW4gYXQgbGVhc3Qgb25lIGNoYXJhY3RlcicpO1xuICB9XG5cbiAgYWxpYXMgPSBzdGF0ZS5pbnB1dC5zbGljZShfcG9zaXRpb24sIHN0YXRlLnBvc2l0aW9uKTtcblxuICBpZiAoIXN0YXRlLmFuY2hvck1hcC5oYXNPd25Qcm9wZXJ0eShhbGlhcykpIHtcbiAgICB0aHJvd0Vycm9yKHN0YXRlLCAndW5pZGVudGlmaWVkIGFsaWFzIFwiJyArIGFsaWFzICsgJ1wiJyk7XG4gIH1cblxuICBzdGF0ZS5yZXN1bHQgPSBzdGF0ZS5hbmNob3JNYXBbYWxpYXNdO1xuICBza2lwU2VwYXJhdGlvblNwYWNlKHN0YXRlLCB0cnVlLCAtMSk7XG4gIHJldHVybiB0cnVlO1xufVxuXG5mdW5jdGlvbiBjb21wb3NlTm9kZShzdGF0ZSwgcGFyZW50SW5kZW50LCBub2RlQ29udGV4dCwgYWxsb3dUb1NlZWssIGFsbG93Q29tcGFjdCkge1xuICB2YXIgYWxsb3dCbG9ja1N0eWxlcyxcbiAgICAgIGFsbG93QmxvY2tTY2FsYXJzLFxuICAgICAgYWxsb3dCbG9ja0NvbGxlY3Rpb25zLFxuICAgICAgaW5kZW50U3RhdHVzID0gMSwgLy8gMTogdGhpcz5wYXJlbnQsIDA6IHRoaXM9cGFyZW50LCAtMTogdGhpczxwYXJlbnRcbiAgICAgIGF0TmV3TGluZSAgPSBmYWxzZSxcbiAgICAgIGhhc0NvbnRlbnQgPSBmYWxzZSxcbiAgICAgIHR5cGVJbmRleCxcbiAgICAgIHR5cGVRdWFudGl0eSxcbiAgICAgIHR5cGUsXG4gICAgICBmbG93SW5kZW50LFxuICAgICAgYmxvY2tJbmRlbnQ7XG5cbiAgaWYgKHN0YXRlLmxpc3RlbmVyICE9PSBudWxsKSB7XG4gICAgc3RhdGUubGlzdGVuZXIoJ29wZW4nLCBzdGF0ZSk7XG4gIH1cblxuICBzdGF0ZS50YWcgICAgPSBudWxsO1xuICBzdGF0ZS5hbmNob3IgPSBudWxsO1xuICBzdGF0ZS5raW5kICAgPSBudWxsO1xuICBzdGF0ZS5yZXN1bHQgPSBudWxsO1xuXG4gIGFsbG93QmxvY2tTdHlsZXMgPSBhbGxvd0Jsb2NrU2NhbGFycyA9IGFsbG93QmxvY2tDb2xsZWN0aW9ucyA9XG4gICAgQ09OVEVYVF9CTE9DS19PVVQgPT09IG5vZGVDb250ZXh0IHx8XG4gICAgQ09OVEVYVF9CTE9DS19JTiAgPT09IG5vZGVDb250ZXh0O1xuXG4gIGlmIChhbGxvd1RvU2Vlaykge1xuICAgIGlmIChza2lwU2VwYXJhdGlvblNwYWNlKHN0YXRlLCB0cnVlLCAtMSkpIHtcbiAgICAgIGF0TmV3TGluZSA9IHRydWU7XG5cbiAgICAgIGlmIChzdGF0ZS5saW5lSW5kZW50ID4gcGFyZW50SW5kZW50KSB7XG4gICAgICAgIGluZGVudFN0YXR1cyA9IDE7XG4gICAgICB9IGVsc2UgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPT09IHBhcmVudEluZGVudCkge1xuICAgICAgICBpbmRlbnRTdGF0dXMgPSAwO1xuICAgICAgfSBlbHNlIGlmIChzdGF0ZS5saW5lSW5kZW50IDwgcGFyZW50SW5kZW50KSB7XG4gICAgICAgIGluZGVudFN0YXR1cyA9IC0xO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIGlmIChpbmRlbnRTdGF0dXMgPT09IDEpIHtcbiAgICB3aGlsZSAocmVhZFRhZ1Byb3BlcnR5KHN0YXRlKSB8fCByZWFkQW5jaG9yUHJvcGVydHkoc3RhdGUpKSB7XG4gICAgICBpZiAoc2tpcFNlcGFyYXRpb25TcGFjZShzdGF0ZSwgdHJ1ZSwgLTEpKSB7XG4gICAgICAgIGF0TmV3TGluZSA9IHRydWU7XG4gICAgICAgIGFsbG93QmxvY2tDb2xsZWN0aW9ucyA9IGFsbG93QmxvY2tTdHlsZXM7XG5cbiAgICAgICAgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPiBwYXJlbnRJbmRlbnQpIHtcbiAgICAgICAgICBpbmRlbnRTdGF0dXMgPSAxO1xuICAgICAgICB9IGVsc2UgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPT09IHBhcmVudEluZGVudCkge1xuICAgICAgICAgIGluZGVudFN0YXR1cyA9IDA7XG4gICAgICAgIH0gZWxzZSBpZiAoc3RhdGUubGluZUluZGVudCA8IHBhcmVudEluZGVudCkge1xuICAgICAgICAgIGluZGVudFN0YXR1cyA9IC0xO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBhbGxvd0Jsb2NrQ29sbGVjdGlvbnMgPSBmYWxzZTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBpZiAoYWxsb3dCbG9ja0NvbGxlY3Rpb25zKSB7XG4gICAgYWxsb3dCbG9ja0NvbGxlY3Rpb25zID0gYXROZXdMaW5lIHx8IGFsbG93Q29tcGFjdDtcbiAgfVxuXG4gIGlmIChpbmRlbnRTdGF0dXMgPT09IDEgfHwgQ09OVEVYVF9CTE9DS19PVVQgPT09IG5vZGVDb250ZXh0KSB7XG4gICAgaWYgKENPTlRFWFRfRkxPV19JTiA9PT0gbm9kZUNvbnRleHQgfHwgQ09OVEVYVF9GTE9XX09VVCA9PT0gbm9kZUNvbnRleHQpIHtcbiAgICAgIGZsb3dJbmRlbnQgPSBwYXJlbnRJbmRlbnQ7XG4gICAgfSBlbHNlIHtcbiAgICAgIGZsb3dJbmRlbnQgPSBwYXJlbnRJbmRlbnQgKyAxO1xuICAgIH1cblxuICAgIGJsb2NrSW5kZW50ID0gc3RhdGUucG9zaXRpb24gLSBzdGF0ZS5saW5lU3RhcnQ7XG5cbiAgICBpZiAoaW5kZW50U3RhdHVzID09PSAxKSB7XG4gICAgICBpZiAoYWxsb3dCbG9ja0NvbGxlY3Rpb25zICYmXG4gICAgICAgICAgKHJlYWRCbG9ja1NlcXVlbmNlKHN0YXRlLCBibG9ja0luZGVudCkgfHxcbiAgICAgICAgICAgcmVhZEJsb2NrTWFwcGluZyhzdGF0ZSwgYmxvY2tJbmRlbnQsIGZsb3dJbmRlbnQpKSB8fFxuICAgICAgICAgIHJlYWRGbG93Q29sbGVjdGlvbihzdGF0ZSwgZmxvd0luZGVudCkpIHtcbiAgICAgICAgaGFzQ29udGVudCA9IHRydWU7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpZiAoKGFsbG93QmxvY2tTY2FsYXJzICYmIHJlYWRCbG9ja1NjYWxhcihzdGF0ZSwgZmxvd0luZGVudCkpIHx8XG4gICAgICAgICAgICByZWFkU2luZ2xlUXVvdGVkU2NhbGFyKHN0YXRlLCBmbG93SW5kZW50KSB8fFxuICAgICAgICAgICAgcmVhZERvdWJsZVF1b3RlZFNjYWxhcihzdGF0ZSwgZmxvd0luZGVudCkpIHtcbiAgICAgICAgICBoYXNDb250ZW50ID0gdHJ1ZTtcblxuICAgICAgICB9IGVsc2UgaWYgKHJlYWRBbGlhcyhzdGF0ZSkpIHtcbiAgICAgICAgICBoYXNDb250ZW50ID0gdHJ1ZTtcblxuICAgICAgICAgIGlmIChzdGF0ZS50YWcgIT09IG51bGwgfHwgc3RhdGUuYW5jaG9yICE9PSBudWxsKSB7XG4gICAgICAgICAgICB0aHJvd0Vycm9yKHN0YXRlLCAnYWxpYXMgbm9kZSBzaG91bGQgbm90IGhhdmUgYW55IHByb3BlcnRpZXMnKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgfSBlbHNlIGlmIChyZWFkUGxhaW5TY2FsYXIoc3RhdGUsIGZsb3dJbmRlbnQsIENPTlRFWFRfRkxPV19JTiA9PT0gbm9kZUNvbnRleHQpKSB7XG4gICAgICAgICAgaGFzQ29udGVudCA9IHRydWU7XG5cbiAgICAgICAgICBpZiAoc3RhdGUudGFnID09PSBudWxsKSB7XG4gICAgICAgICAgICBzdGF0ZS50YWcgPSAnPyc7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHN0YXRlLmFuY2hvciAhPT0gbnVsbCkge1xuICAgICAgICAgIHN0YXRlLmFuY2hvck1hcFtzdGF0ZS5hbmNob3JdID0gc3RhdGUucmVzdWx0O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmIChpbmRlbnRTdGF0dXMgPT09IDApIHtcbiAgICAgIC8vIFNwZWNpYWwgY2FzZTogYmxvY2sgc2VxdWVuY2VzIGFyZSBhbGxvd2VkIHRvIGhhdmUgc2FtZSBpbmRlbnRhdGlvbiBsZXZlbCBhcyB0aGUgcGFyZW50LlxuICAgICAgLy8gaHR0cDovL3d3dy55YW1sLm9yZy9zcGVjLzEuMi9zcGVjLmh0bWwjaWQyNzk5Nzg0XG4gICAgICBoYXNDb250ZW50ID0gYWxsb3dCbG9ja0NvbGxlY3Rpb25zICYmIHJlYWRCbG9ja1NlcXVlbmNlKHN0YXRlLCBibG9ja0luZGVudCk7XG4gICAgfVxuICB9XG5cbiAgaWYgKHN0YXRlLnRhZyAhPT0gbnVsbCAmJiBzdGF0ZS50YWcgIT09ICchJykge1xuICAgIGlmIChzdGF0ZS50YWcgPT09ICc/Jykge1xuICAgICAgZm9yICh0eXBlSW5kZXggPSAwLCB0eXBlUXVhbnRpdHkgPSBzdGF0ZS5pbXBsaWNpdFR5cGVzLmxlbmd0aDtcbiAgICAgICAgICAgdHlwZUluZGV4IDwgdHlwZVF1YW50aXR5O1xuICAgICAgICAgICB0eXBlSW5kZXggKz0gMSkge1xuICAgICAgICB0eXBlID0gc3RhdGUuaW1wbGljaXRUeXBlc1t0eXBlSW5kZXhdO1xuXG4gICAgICAgIC8vIEltcGxpY2l0IHJlc29sdmluZyBpcyBub3QgYWxsb3dlZCBmb3Igbm9uLXNjYWxhciB0eXBlcywgYW5kICc/J1xuICAgICAgICAvLyBub24tc3BlY2lmaWMgdGFnIGlzIG9ubHkgYXNzaWduZWQgdG8gcGxhaW4gc2NhbGFycy4gU28sIGl0IGlzbid0XG4gICAgICAgIC8vIG5lZWRlZCB0byBjaGVjayBmb3IgJ2tpbmQnIGNvbmZvcm1pdHkuXG5cbiAgICAgICAgaWYgKHR5cGUucmVzb2x2ZShzdGF0ZS5yZXN1bHQpKSB7IC8vIGBzdGF0ZS5yZXN1bHRgIHVwZGF0ZWQgaW4gcmVzb2x2ZXIgaWYgbWF0Y2hlZFxuICAgICAgICAgIHN0YXRlLnJlc3VsdCA9IHR5cGUuY29uc3RydWN0KHN0YXRlLnJlc3VsdCk7XG4gICAgICAgICAgc3RhdGUudGFnID0gdHlwZS50YWc7XG4gICAgICAgICAgaWYgKHN0YXRlLmFuY2hvciAhPT0gbnVsbCkge1xuICAgICAgICAgICAgc3RhdGUuYW5jaG9yTWFwW3N0YXRlLmFuY2hvcl0gPSBzdGF0ZS5yZXN1bHQ7XG4gICAgICAgICAgfVxuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIGlmIChfaGFzT3duUHJvcGVydHkuY2FsbChzdGF0ZS50eXBlTWFwLCBzdGF0ZS50YWcpKSB7XG4gICAgICB0eXBlID0gc3RhdGUudHlwZU1hcFtzdGF0ZS50YWddO1xuXG4gICAgICBpZiAoc3RhdGUucmVzdWx0ICE9PSBudWxsICYmIHR5cGUua2luZCAhPT0gc3RhdGUua2luZCkge1xuICAgICAgICB0aHJvd0Vycm9yKHN0YXRlLCAndW5hY2NlcHRhYmxlIG5vZGUga2luZCBmb3IgITwnICsgc3RhdGUudGFnICsgJz4gdGFnOyBpdCBzaG91bGQgYmUgXCInICsgdHlwZS5raW5kICsgJ1wiLCBub3QgXCInICsgc3RhdGUua2luZCArICdcIicpO1xuICAgICAgfVxuXG4gICAgICBpZiAoIXR5cGUucmVzb2x2ZShzdGF0ZS5yZXN1bHQpKSB7IC8vIGBzdGF0ZS5yZXN1bHRgIHVwZGF0ZWQgaW4gcmVzb2x2ZXIgaWYgbWF0Y2hlZFxuICAgICAgICB0aHJvd0Vycm9yKHN0YXRlLCAnY2Fubm90IHJlc29sdmUgYSBub2RlIHdpdGggITwnICsgc3RhdGUudGFnICsgJz4gZXhwbGljaXQgdGFnJyk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBzdGF0ZS5yZXN1bHQgPSB0eXBlLmNvbnN0cnVjdChzdGF0ZS5yZXN1bHQpO1xuICAgICAgICBpZiAoc3RhdGUuYW5jaG9yICE9PSBudWxsKSB7XG4gICAgICAgICAgc3RhdGUuYW5jaG9yTWFwW3N0YXRlLmFuY2hvcl0gPSBzdGF0ZS5yZXN1bHQ7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3dFcnJvcihzdGF0ZSwgJ3Vua25vd24gdGFnICE8JyArIHN0YXRlLnRhZyArICc+Jyk7XG4gICAgfVxuICB9XG5cbiAgaWYgKHN0YXRlLmxpc3RlbmVyICE9PSBudWxsKSB7XG4gICAgc3RhdGUubGlzdGVuZXIoJ2Nsb3NlJywgc3RhdGUpO1xuICB9XG4gIHJldHVybiBzdGF0ZS50YWcgIT09IG51bGwgfHwgIHN0YXRlLmFuY2hvciAhPT0gbnVsbCB8fCBoYXNDb250ZW50O1xufVxuXG5mdW5jdGlvbiByZWFkRG9jdW1lbnQoc3RhdGUpIHtcbiAgdmFyIGRvY3VtZW50U3RhcnQgPSBzdGF0ZS5wb3NpdGlvbixcbiAgICAgIF9wb3NpdGlvbixcbiAgICAgIGRpcmVjdGl2ZU5hbWUsXG4gICAgICBkaXJlY3RpdmVBcmdzLFxuICAgICAgaGFzRGlyZWN0aXZlcyA9IGZhbHNlLFxuICAgICAgY2g7XG5cbiAgc3RhdGUudmVyc2lvbiA9IG51bGw7XG4gIHN0YXRlLmNoZWNrTGluZUJyZWFrcyA9IHN0YXRlLmxlZ2FjeTtcbiAgc3RhdGUudGFnTWFwID0ge307XG4gIHN0YXRlLmFuY2hvck1hcCA9IHt9O1xuXG4gIHdoaWxlICgoY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uKSkgIT09IDApIHtcbiAgICBza2lwU2VwYXJhdGlvblNwYWNlKHN0YXRlLCB0cnVlLCAtMSk7XG5cbiAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24pO1xuXG4gICAgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPiAwIHx8IGNoICE9PSAweDI1LyogJSAqLykge1xuICAgICAgYnJlYWs7XG4gICAgfVxuXG4gICAgaGFzRGlyZWN0aXZlcyA9IHRydWU7XG4gICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuICAgIF9wb3NpdGlvbiA9IHN0YXRlLnBvc2l0aW9uO1xuXG4gICAgd2hpbGUgKGNoICE9PSAwICYmICFpc19XU19PUl9FT0woY2gpKSB7XG4gICAgICBjaCA9IHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoKytzdGF0ZS5wb3NpdGlvbik7XG4gICAgfVxuXG4gICAgZGlyZWN0aXZlTmFtZSA9IHN0YXRlLmlucHV0LnNsaWNlKF9wb3NpdGlvbiwgc3RhdGUucG9zaXRpb24pO1xuICAgIGRpcmVjdGl2ZUFyZ3MgPSBbXTtcblxuICAgIGlmIChkaXJlY3RpdmVOYW1lLmxlbmd0aCA8IDEpIHtcbiAgICAgIHRocm93RXJyb3Ioc3RhdGUsICdkaXJlY3RpdmUgbmFtZSBtdXN0IG5vdCBiZSBsZXNzIHRoYW4gb25lIGNoYXJhY3RlciBpbiBsZW5ndGgnKTtcbiAgICB9XG5cbiAgICB3aGlsZSAoY2ggIT09IDApIHtcbiAgICAgIHdoaWxlIChpc19XSElURV9TUEFDRShjaCkpIHtcbiAgICAgICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuICAgICAgfVxuXG4gICAgICBpZiAoY2ggPT09IDB4MjMvKiAjICovKSB7XG4gICAgICAgIGRvIHsgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pOyB9XG4gICAgICAgIHdoaWxlIChjaCAhPT0gMCAmJiAhaXNfRU9MKGNoKSk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuXG4gICAgICBpZiAoaXNfRU9MKGNoKSkgYnJlYWs7XG5cbiAgICAgIF9wb3NpdGlvbiA9IHN0YXRlLnBvc2l0aW9uO1xuXG4gICAgICB3aGlsZSAoY2ggIT09IDAgJiYgIWlzX1dTX09SX0VPTChjaCkpIHtcbiAgICAgICAgY2ggPSBzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KCsrc3RhdGUucG9zaXRpb24pO1xuICAgICAgfVxuXG4gICAgICBkaXJlY3RpdmVBcmdzLnB1c2goc3RhdGUuaW5wdXQuc2xpY2UoX3Bvc2l0aW9uLCBzdGF0ZS5wb3NpdGlvbikpO1xuICAgIH1cblxuICAgIGlmIChjaCAhPT0gMCkgcmVhZExpbmVCcmVhayhzdGF0ZSk7XG5cbiAgICBpZiAoX2hhc093blByb3BlcnR5LmNhbGwoZGlyZWN0aXZlSGFuZGxlcnMsIGRpcmVjdGl2ZU5hbWUpKSB7XG4gICAgICBkaXJlY3RpdmVIYW5kbGVyc1tkaXJlY3RpdmVOYW1lXShzdGF0ZSwgZGlyZWN0aXZlTmFtZSwgZGlyZWN0aXZlQXJncyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRocm93V2FybmluZyhzdGF0ZSwgJ3Vua25vd24gZG9jdW1lbnQgZGlyZWN0aXZlIFwiJyArIGRpcmVjdGl2ZU5hbWUgKyAnXCInKTtcbiAgICB9XG4gIH1cblxuICBza2lwU2VwYXJhdGlvblNwYWNlKHN0YXRlLCB0cnVlLCAtMSk7XG5cbiAgaWYgKHN0YXRlLmxpbmVJbmRlbnQgPT09IDAgJiZcbiAgICAgIHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24pICAgICA9PT0gMHgyRC8qIC0gKi8gJiZcbiAgICAgIHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24gKyAxKSA9PT0gMHgyRC8qIC0gKi8gJiZcbiAgICAgIHN0YXRlLmlucHV0LmNoYXJDb2RlQXQoc3RhdGUucG9zaXRpb24gKyAyKSA9PT0gMHgyRC8qIC0gKi8pIHtcbiAgICBzdGF0ZS5wb3NpdGlvbiArPSAzO1xuICAgIHNraXBTZXBhcmF0aW9uU3BhY2Uoc3RhdGUsIHRydWUsIC0xKTtcblxuICB9IGVsc2UgaWYgKGhhc0RpcmVjdGl2ZXMpIHtcbiAgICB0aHJvd0Vycm9yKHN0YXRlLCAnZGlyZWN0aXZlcyBlbmQgbWFyayBpcyBleHBlY3RlZCcpO1xuICB9XG5cbiAgY29tcG9zZU5vZGUoc3RhdGUsIHN0YXRlLmxpbmVJbmRlbnQgLSAxLCBDT05URVhUX0JMT0NLX09VVCwgZmFsc2UsIHRydWUpO1xuICBza2lwU2VwYXJhdGlvblNwYWNlKHN0YXRlLCB0cnVlLCAtMSk7XG5cbiAgaWYgKHN0YXRlLmNoZWNrTGluZUJyZWFrcyAmJlxuICAgICAgUEFUVEVSTl9OT05fQVNDSUlfTElORV9CUkVBS1MudGVzdChzdGF0ZS5pbnB1dC5zbGljZShkb2N1bWVudFN0YXJ0LCBzdGF0ZS5wb3NpdGlvbikpKSB7XG4gICAgdGhyb3dXYXJuaW5nKHN0YXRlLCAnbm9uLUFTQ0lJIGxpbmUgYnJlYWtzIGFyZSBpbnRlcnByZXRlZCBhcyBjb250ZW50Jyk7XG4gIH1cblxuICBzdGF0ZS5kb2N1bWVudHMucHVzaChzdGF0ZS5yZXN1bHQpO1xuXG4gIGlmIChzdGF0ZS5wb3NpdGlvbiA9PT0gc3RhdGUubGluZVN0YXJ0ICYmIHRlc3REb2N1bWVudFNlcGFyYXRvcihzdGF0ZSkpIHtcblxuICAgIGlmIChzdGF0ZS5pbnB1dC5jaGFyQ29kZUF0KHN0YXRlLnBvc2l0aW9uKSA9PT0gMHgyRS8qIC4gKi8pIHtcbiAgICAgIHN0YXRlLnBvc2l0aW9uICs9IDM7XG4gICAgICBza2lwU2VwYXJhdGlvblNwYWNlKHN0YXRlLCB0cnVlLCAtMSk7XG4gICAgfVxuICAgIHJldHVybjtcbiAgfVxuXG4gIGlmIChzdGF0ZS5wb3NpdGlvbiA8IChzdGF0ZS5sZW5ndGggLSAxKSkge1xuICAgIHRocm93RXJyb3Ioc3RhdGUsICdlbmQgb2YgdGhlIHN0cmVhbSBvciBhIGRvY3VtZW50IHNlcGFyYXRvciBpcyBleHBlY3RlZCcpO1xuICB9IGVsc2Uge1xuICAgIHJldHVybjtcbiAgfVxufVxuXG5cbmZ1bmN0aW9uIGxvYWREb2N1bWVudHMoaW5wdXQsIG9wdGlvbnMpIHtcbiAgaW5wdXQgPSBTdHJpbmcoaW5wdXQpO1xuICBvcHRpb25zID0gb3B0aW9ucyB8fCB7fTtcblxuICBpZiAoaW5wdXQubGVuZ3RoICE9PSAwKSB7XG5cbiAgICAvLyBBZGQgdGFpbGluZyBgXFxuYCBpZiBub3QgZXhpc3RzXG4gICAgaWYgKGlucHV0LmNoYXJDb2RlQXQoaW5wdXQubGVuZ3RoIC0gMSkgIT09IDB4MEEvKiBMRiAqLyAmJlxuICAgICAgICBpbnB1dC5jaGFyQ29kZUF0KGlucHV0Lmxlbmd0aCAtIDEpICE9PSAweDBELyogQ1IgKi8pIHtcbiAgICAgIGlucHV0ICs9ICdcXG4nO1xuICAgIH1cblxuICAgIC8vIFN0cmlwIEJPTVxuICAgIGlmIChpbnB1dC5jaGFyQ29kZUF0KDApID09PSAweEZFRkYpIHtcbiAgICAgIGlucHV0ID0gaW5wdXQuc2xpY2UoMSk7XG4gICAgfVxuICB9XG5cbiAgdmFyIHN0YXRlID0gbmV3IFN0YXRlKGlucHV0LCBvcHRpb25zKTtcblxuICAvLyBVc2UgMCBhcyBzdHJpbmcgdGVybWluYXRvci4gVGhhdCBzaWduaWZpY2FudGx5IHNpbXBsaWZpZXMgYm91bmRzIGNoZWNrLlxuICBzdGF0ZS5pbnB1dCArPSAnXFwwJztcblxuICB3aGlsZSAoc3RhdGUuaW5wdXQuY2hhckNvZGVBdChzdGF0ZS5wb3NpdGlvbikgPT09IDB4MjAvKiBTcGFjZSAqLykge1xuICAgIHN0YXRlLmxpbmVJbmRlbnQgKz0gMTtcbiAgICBzdGF0ZS5wb3NpdGlvbiArPSAxO1xuICB9XG5cbiAgd2hpbGUgKHN0YXRlLnBvc2l0aW9uIDwgKHN0YXRlLmxlbmd0aCAtIDEpKSB7XG4gICAgcmVhZERvY3VtZW50KHN0YXRlKTtcbiAgfVxuXG4gIHJldHVybiBzdGF0ZS5kb2N1bWVudHM7XG59XG5cblxuZnVuY3Rpb24gbG9hZEFsbChpbnB1dCwgaXRlcmF0b3IsIG9wdGlvbnMpIHtcbiAgdmFyIGRvY3VtZW50cyA9IGxvYWREb2N1bWVudHMoaW5wdXQsIG9wdGlvbnMpLCBpbmRleCwgbGVuZ3RoO1xuXG4gIGZvciAoaW5kZXggPSAwLCBsZW5ndGggPSBkb2N1bWVudHMubGVuZ3RoOyBpbmRleCA8IGxlbmd0aDsgaW5kZXggKz0gMSkge1xuICAgIGl0ZXJhdG9yKGRvY3VtZW50c1tpbmRleF0pO1xuICB9XG59XG5cblxuZnVuY3Rpb24gbG9hZChpbnB1dCwgb3B0aW9ucykge1xuICB2YXIgZG9jdW1lbnRzID0gbG9hZERvY3VtZW50cyhpbnB1dCwgb3B0aW9ucyk7XG5cbiAgaWYgKGRvY3VtZW50cy5sZW5ndGggPT09IDApIHtcbiAgICAvKmVzbGludC1kaXNhYmxlIG5vLXVuZGVmaW5lZCovXG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfSBlbHNlIGlmIChkb2N1bWVudHMubGVuZ3RoID09PSAxKSB7XG4gICAgcmV0dXJuIGRvY3VtZW50c1swXTtcbiAgfVxuICB0aHJvdyBuZXcgWUFNTEV4Y2VwdGlvbignZXhwZWN0ZWQgYSBzaW5nbGUgZG9jdW1lbnQgaW4gdGhlIHN0cmVhbSwgYnV0IGZvdW5kIG1vcmUnKTtcbn1cblxuXG5mdW5jdGlvbiBzYWZlTG9hZEFsbChpbnB1dCwgb3V0cHV0LCBvcHRpb25zKSB7XG4gIGxvYWRBbGwoaW5wdXQsIG91dHB1dCwgY29tbW9uLmV4dGVuZCh7IHNjaGVtYTogREVGQVVMVF9TQUZFX1NDSEVNQSB9LCBvcHRpb25zKSk7XG59XG5cblxuZnVuY3Rpb24gc2FmZUxvYWQoaW5wdXQsIG9wdGlvbnMpIHtcbiAgcmV0dXJuIGxvYWQoaW5wdXQsIGNvbW1vbi5leHRlbmQoeyBzY2hlbWE6IERFRkFVTFRfU0FGRV9TQ0hFTUEgfSwgb3B0aW9ucykpO1xufVxuXG5cbm1vZHVsZS5leHBvcnRzLmxvYWRBbGwgICAgID0gbG9hZEFsbDtcbm1vZHVsZS5leHBvcnRzLmxvYWQgICAgICAgID0gbG9hZDtcbm1vZHVsZS5leHBvcnRzLnNhZmVMb2FkQWxsID0gc2FmZUxvYWRBbGw7XG5tb2R1bGUuZXhwb3J0cy5zYWZlTG9hZCAgICA9IHNhZmVMb2FkO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5cbnZhciBjb21tb24gPSByZXF1aXJlKCcuL2NvbW1vbicpO1xuXG5cbmZ1bmN0aW9uIE1hcmsobmFtZSwgYnVmZmVyLCBwb3NpdGlvbiwgbGluZSwgY29sdW1uKSB7XG4gIHRoaXMubmFtZSAgICAgPSBuYW1lO1xuICB0aGlzLmJ1ZmZlciAgID0gYnVmZmVyO1xuICB0aGlzLnBvc2l0aW9uID0gcG9zaXRpb247XG4gIHRoaXMubGluZSAgICAgPSBsaW5lO1xuICB0aGlzLmNvbHVtbiAgID0gY29sdW1uO1xufVxuXG5cbk1hcmsucHJvdG90eXBlLmdldFNuaXBwZXQgPSBmdW5jdGlvbiBnZXRTbmlwcGV0KGluZGVudCwgbWF4TGVuZ3RoKSB7XG4gIHZhciBoZWFkLCBzdGFydCwgdGFpbCwgZW5kLCBzbmlwcGV0O1xuXG4gIGlmICghdGhpcy5idWZmZXIpIHJldHVybiBudWxsO1xuXG4gIGluZGVudCA9IGluZGVudCB8fCA0O1xuICBtYXhMZW5ndGggPSBtYXhMZW5ndGggfHwgNzU7XG5cbiAgaGVhZCA9ICcnO1xuICBzdGFydCA9IHRoaXMucG9zaXRpb247XG5cbiAgd2hpbGUgKHN0YXJ0ID4gMCAmJiAnXFx4MDBcXHJcXG5cXHg4NVxcdTIwMjhcXHUyMDI5Jy5pbmRleE9mKHRoaXMuYnVmZmVyLmNoYXJBdChzdGFydCAtIDEpKSA9PT0gLTEpIHtcbiAgICBzdGFydCAtPSAxO1xuICAgIGlmICh0aGlzLnBvc2l0aW9uIC0gc3RhcnQgPiAobWF4TGVuZ3RoIC8gMiAtIDEpKSB7XG4gICAgICBoZWFkID0gJyAuLi4gJztcbiAgICAgIHN0YXJ0ICs9IDU7XG4gICAgICBicmVhaztcbiAgICB9XG4gIH1cblxuICB0YWlsID0gJyc7XG4gIGVuZCA9IHRoaXMucG9zaXRpb247XG5cbiAgd2hpbGUgKGVuZCA8IHRoaXMuYnVmZmVyLmxlbmd0aCAmJiAnXFx4MDBcXHJcXG5cXHg4NVxcdTIwMjhcXHUyMDI5Jy5pbmRleE9mKHRoaXMuYnVmZmVyLmNoYXJBdChlbmQpKSA9PT0gLTEpIHtcbiAgICBlbmQgKz0gMTtcbiAgICBpZiAoZW5kIC0gdGhpcy5wb3NpdGlvbiA+IChtYXhMZW5ndGggLyAyIC0gMSkpIHtcbiAgICAgIHRhaWwgPSAnIC4uLiAnO1xuICAgICAgZW5kIC09IDU7XG4gICAgICBicmVhaztcbiAgICB9XG4gIH1cblxuICBzbmlwcGV0ID0gdGhpcy5idWZmZXIuc2xpY2Uoc3RhcnQsIGVuZCk7XG5cbiAgcmV0dXJuIGNvbW1vbi5yZXBlYXQoJyAnLCBpbmRlbnQpICsgaGVhZCArIHNuaXBwZXQgKyB0YWlsICsgJ1xcbicgK1xuICAgICAgICAgY29tbW9uLnJlcGVhdCgnICcsIGluZGVudCArIHRoaXMucG9zaXRpb24gLSBzdGFydCArIGhlYWQubGVuZ3RoKSArICdeJztcbn07XG5cblxuTWFyay5wcm90b3R5cGUudG9TdHJpbmcgPSBmdW5jdGlvbiB0b1N0cmluZyhjb21wYWN0KSB7XG4gIHZhciBzbmlwcGV0LCB3aGVyZSA9ICcnO1xuXG4gIGlmICh0aGlzLm5hbWUpIHtcbiAgICB3aGVyZSArPSAnaW4gXCInICsgdGhpcy5uYW1lICsgJ1wiICc7XG4gIH1cblxuICB3aGVyZSArPSAnYXQgbGluZSAnICsgKHRoaXMubGluZSArIDEpICsgJywgY29sdW1uICcgKyAodGhpcy5jb2x1bW4gKyAxKTtcblxuICBpZiAoIWNvbXBhY3QpIHtcbiAgICBzbmlwcGV0ID0gdGhpcy5nZXRTbmlwcGV0KCk7XG5cbiAgICBpZiAoc25pcHBldCkge1xuICAgICAgd2hlcmUgKz0gJzpcXG4nICsgc25pcHBldDtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gd2hlcmU7XG59O1xuXG5cbm1vZHVsZS5leHBvcnRzID0gTWFyaztcbiIsIid1c2Ugc3RyaWN0JztcblxuLyplc2xpbnQtZGlzYWJsZSBtYXgtbGVuKi9cblxudmFyIGNvbW1vbiAgICAgICAgPSByZXF1aXJlKCcuL2NvbW1vbicpO1xudmFyIFlBTUxFeGNlcHRpb24gPSByZXF1aXJlKCcuL2V4Y2VwdGlvbicpO1xudmFyIFR5cGUgICAgICAgICAgPSByZXF1aXJlKCcuL3R5cGUnKTtcblxuXG5mdW5jdGlvbiBjb21waWxlTGlzdChzY2hlbWEsIG5hbWUsIHJlc3VsdCkge1xuICB2YXIgZXhjbHVkZSA9IFtdO1xuXG4gIHNjaGVtYS5pbmNsdWRlLmZvckVhY2goZnVuY3Rpb24gKGluY2x1ZGVkU2NoZW1hKSB7XG4gICAgcmVzdWx0ID0gY29tcGlsZUxpc3QoaW5jbHVkZWRTY2hlbWEsIG5hbWUsIHJlc3VsdCk7XG4gIH0pO1xuXG4gIHNjaGVtYVtuYW1lXS5mb3JFYWNoKGZ1bmN0aW9uIChjdXJyZW50VHlwZSkge1xuICAgIHJlc3VsdC5mb3JFYWNoKGZ1bmN0aW9uIChwcmV2aW91c1R5cGUsIHByZXZpb3VzSW5kZXgpIHtcbiAgICAgIGlmIChwcmV2aW91c1R5cGUudGFnID09PSBjdXJyZW50VHlwZS50YWcpIHtcbiAgICAgICAgZXhjbHVkZS5wdXNoKHByZXZpb3VzSW5kZXgpO1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgcmVzdWx0LnB1c2goY3VycmVudFR5cGUpO1xuICB9KTtcblxuICByZXR1cm4gcmVzdWx0LmZpbHRlcihmdW5jdGlvbiAodHlwZSwgaW5kZXgpIHtcbiAgICByZXR1cm4gZXhjbHVkZS5pbmRleE9mKGluZGV4KSA9PT0gLTE7XG4gIH0pO1xufVxuXG5cbmZ1bmN0aW9uIGNvbXBpbGVNYXAoLyogbGlzdHMuLi4gKi8pIHtcbiAgdmFyIHJlc3VsdCA9IHt9LCBpbmRleCwgbGVuZ3RoO1xuXG4gIGZ1bmN0aW9uIGNvbGxlY3RUeXBlKHR5cGUpIHtcbiAgICByZXN1bHRbdHlwZS50YWddID0gdHlwZTtcbiAgfVxuXG4gIGZvciAoaW5kZXggPSAwLCBsZW5ndGggPSBhcmd1bWVudHMubGVuZ3RoOyBpbmRleCA8IGxlbmd0aDsgaW5kZXggKz0gMSkge1xuICAgIGFyZ3VtZW50c1tpbmRleF0uZm9yRWFjaChjb2xsZWN0VHlwZSk7XG4gIH1cblxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5cbmZ1bmN0aW9uIFNjaGVtYShkZWZpbml0aW9uKSB7XG4gIHRoaXMuaW5jbHVkZSAgPSBkZWZpbml0aW9uLmluY2x1ZGUgIHx8IFtdO1xuICB0aGlzLmltcGxpY2l0ID0gZGVmaW5pdGlvbi5pbXBsaWNpdCB8fCBbXTtcbiAgdGhpcy5leHBsaWNpdCA9IGRlZmluaXRpb24uZXhwbGljaXQgfHwgW107XG5cbiAgdGhpcy5pbXBsaWNpdC5mb3JFYWNoKGZ1bmN0aW9uICh0eXBlKSB7XG4gICAgaWYgKHR5cGUubG9hZEtpbmQgJiYgdHlwZS5sb2FkS2luZCAhPT0gJ3NjYWxhcicpIHtcbiAgICAgIHRocm93IG5ldyBZQU1MRXhjZXB0aW9uKCdUaGVyZSBpcyBhIG5vbi1zY2FsYXIgdHlwZSBpbiB0aGUgaW1wbGljaXQgbGlzdCBvZiBhIHNjaGVtYS4gSW1wbGljaXQgcmVzb2x2aW5nIG9mIHN1Y2ggdHlwZXMgaXMgbm90IHN1cHBvcnRlZC4nKTtcbiAgICB9XG4gIH0pO1xuXG4gIHRoaXMuY29tcGlsZWRJbXBsaWNpdCA9IGNvbXBpbGVMaXN0KHRoaXMsICdpbXBsaWNpdCcsIFtdKTtcbiAgdGhpcy5jb21waWxlZEV4cGxpY2l0ID0gY29tcGlsZUxpc3QodGhpcywgJ2V4cGxpY2l0JywgW10pO1xuICB0aGlzLmNvbXBpbGVkVHlwZU1hcCAgPSBjb21waWxlTWFwKHRoaXMuY29tcGlsZWRJbXBsaWNpdCwgdGhpcy5jb21waWxlZEV4cGxpY2l0KTtcbn1cblxuXG5TY2hlbWEuREVGQVVMVCA9IG51bGw7XG5cblxuU2NoZW1hLmNyZWF0ZSA9IGZ1bmN0aW9uIGNyZWF0ZVNjaGVtYSgpIHtcbiAgdmFyIHNjaGVtYXMsIHR5cGVzO1xuXG4gIHN3aXRjaCAoYXJndW1lbnRzLmxlbmd0aCkge1xuICAgIGNhc2UgMTpcbiAgICAgIHNjaGVtYXMgPSBTY2hlbWEuREVGQVVMVDtcbiAgICAgIHR5cGVzID0gYXJndW1lbnRzWzBdO1xuICAgICAgYnJlYWs7XG5cbiAgICBjYXNlIDI6XG4gICAgICBzY2hlbWFzID0gYXJndW1lbnRzWzBdO1xuICAgICAgdHlwZXMgPSBhcmd1bWVudHNbMV07XG4gICAgICBicmVhaztcblxuICAgIGRlZmF1bHQ6XG4gICAgICB0aHJvdyBuZXcgWUFNTEV4Y2VwdGlvbignV3JvbmcgbnVtYmVyIG9mIGFyZ3VtZW50cyBmb3IgU2NoZW1hLmNyZWF0ZSBmdW5jdGlvbicpO1xuICB9XG5cbiAgc2NoZW1hcyA9IGNvbW1vbi50b0FycmF5KHNjaGVtYXMpO1xuICB0eXBlcyA9IGNvbW1vbi50b0FycmF5KHR5cGVzKTtcblxuICBpZiAoIXNjaGVtYXMuZXZlcnkoZnVuY3Rpb24gKHNjaGVtYSkgeyByZXR1cm4gc2NoZW1hIGluc3RhbmNlb2YgU2NoZW1hOyB9KSkge1xuICAgIHRocm93IG5ldyBZQU1MRXhjZXB0aW9uKCdTcGVjaWZpZWQgbGlzdCBvZiBzdXBlciBzY2hlbWFzIChvciBhIHNpbmdsZSBTY2hlbWEgb2JqZWN0KSBjb250YWlucyBhIG5vbi1TY2hlbWEgb2JqZWN0LicpO1xuICB9XG5cbiAgaWYgKCF0eXBlcy5ldmVyeShmdW5jdGlvbiAodHlwZSkgeyByZXR1cm4gdHlwZSBpbnN0YW5jZW9mIFR5cGU7IH0pKSB7XG4gICAgdGhyb3cgbmV3IFlBTUxFeGNlcHRpb24oJ1NwZWNpZmllZCBsaXN0IG9mIFlBTUwgdHlwZXMgKG9yIGEgc2luZ2xlIFR5cGUgb2JqZWN0KSBjb250YWlucyBhIG5vbi1UeXBlIG9iamVjdC4nKTtcbiAgfVxuXG4gIHJldHVybiBuZXcgU2NoZW1hKHtcbiAgICBpbmNsdWRlOiBzY2hlbWFzLFxuICAgIGV4cGxpY2l0OiB0eXBlc1xuICB9KTtcbn07XG5cblxubW9kdWxlLmV4cG9ydHMgPSBTY2hlbWE7XG4iLCIvLyBTdGFuZGFyZCBZQU1MJ3MgQ29yZSBzY2hlbWEuXG4vLyBodHRwOi8vd3d3LnlhbWwub3JnL3NwZWMvMS4yL3NwZWMuaHRtbCNpZDI4MDQ5MjNcbi8vXG4vLyBOT1RFOiBKUy1ZQU1MIGRvZXMgbm90IHN1cHBvcnQgc2NoZW1hLXNwZWNpZmljIHRhZyByZXNvbHV0aW9uIHJlc3RyaWN0aW9ucy5cbi8vIFNvLCBDb3JlIHNjaGVtYSBoYXMgbm8gZGlzdGluY3Rpb25zIGZyb20gSlNPTiBzY2hlbWEgaXMgSlMtWUFNTC5cblxuXG4ndXNlIHN0cmljdCc7XG5cblxudmFyIFNjaGVtYSA9IHJlcXVpcmUoJy4uL3NjaGVtYScpO1xuXG5cbm1vZHVsZS5leHBvcnRzID0gbmV3IFNjaGVtYSh7XG4gIGluY2x1ZGU6IFtcbiAgICByZXF1aXJlKCcuL2pzb24nKVxuICBdXG59KTtcbiIsIi8vIEpTLVlBTUwncyBkZWZhdWx0IHNjaGVtYSBmb3IgYGxvYWRgIGZ1bmN0aW9uLlxuLy8gSXQgaXMgbm90IGRlc2NyaWJlZCBpbiB0aGUgWUFNTCBzcGVjaWZpY2F0aW9uLlxuLy9cbi8vIFRoaXMgc2NoZW1hIGlzIGJhc2VkIG9uIEpTLVlBTUwncyBkZWZhdWx0IHNhZmUgc2NoZW1hIGFuZCBpbmNsdWRlc1xuLy8gSmF2YVNjcmlwdC1zcGVjaWZpYyB0eXBlczogISFqcy91bmRlZmluZWQsICEhanMvcmVnZXhwIGFuZCAhIWpzL2Z1bmN0aW9uLlxuLy9cbi8vIEFsc28gdGhpcyBzY2hlbWEgaXMgdXNlZCBhcyBkZWZhdWx0IGJhc2Ugc2NoZW1hIGF0IGBTY2hlbWEuY3JlYXRlYCBmdW5jdGlvbi5cblxuXG4ndXNlIHN0cmljdCc7XG5cblxudmFyIFNjaGVtYSA9IHJlcXVpcmUoJy4uL3NjaGVtYScpO1xuXG5cbm1vZHVsZS5leHBvcnRzID0gU2NoZW1hLkRFRkFVTFQgPSBuZXcgU2NoZW1hKHtcbiAgaW5jbHVkZTogW1xuICAgIHJlcXVpcmUoJy4vZGVmYXVsdF9zYWZlJylcbiAgXSxcbiAgZXhwbGljaXQ6IFtcbiAgICByZXF1aXJlKCcuLi90eXBlL2pzL3VuZGVmaW5lZCcpLFxuICAgIHJlcXVpcmUoJy4uL3R5cGUvanMvcmVnZXhwJyksXG4gICAgcmVxdWlyZSgnLi4vdHlwZS9qcy9mdW5jdGlvbicpXG4gIF1cbn0pO1xuIiwiLy8gSlMtWUFNTCdzIGRlZmF1bHQgc2NoZW1hIGZvciBgc2FmZUxvYWRgIGZ1bmN0aW9uLlxuLy8gSXQgaXMgbm90IGRlc2NyaWJlZCBpbiB0aGUgWUFNTCBzcGVjaWZpY2F0aW9uLlxuLy9cbi8vIFRoaXMgc2NoZW1hIGlzIGJhc2VkIG9uIHN0YW5kYXJkIFlBTUwncyBDb3JlIHNjaGVtYSBhbmQgaW5jbHVkZXMgbW9zdCBvZlxuLy8gZXh0cmEgdHlwZXMgZGVzY3JpYmVkIGF0IFlBTUwgdGFnIHJlcG9zaXRvcnkuIChodHRwOi8veWFtbC5vcmcvdHlwZS8pXG5cblxuJ3VzZSBzdHJpY3QnO1xuXG5cbnZhciBTY2hlbWEgPSByZXF1aXJlKCcuLi9zY2hlbWEnKTtcblxuXG5tb2R1bGUuZXhwb3J0cyA9IG5ldyBTY2hlbWEoe1xuICBpbmNsdWRlOiBbXG4gICAgcmVxdWlyZSgnLi9jb3JlJylcbiAgXSxcbiAgaW1wbGljaXQ6IFtcbiAgICByZXF1aXJlKCcuLi90eXBlL3RpbWVzdGFtcCcpLFxuICAgIHJlcXVpcmUoJy4uL3R5cGUvbWVyZ2UnKVxuICBdLFxuICBleHBsaWNpdDogW1xuICAgIHJlcXVpcmUoJy4uL3R5cGUvYmluYXJ5JyksXG4gICAgcmVxdWlyZSgnLi4vdHlwZS9vbWFwJyksXG4gICAgcmVxdWlyZSgnLi4vdHlwZS9wYWlycycpLFxuICAgIHJlcXVpcmUoJy4uL3R5cGUvc2V0JylcbiAgXVxufSk7XG4iLCIvLyBTdGFuZGFyZCBZQU1MJ3MgRmFpbHNhZmUgc2NoZW1hLlxuLy8gaHR0cDovL3d3dy55YW1sLm9yZy9zcGVjLzEuMi9zcGVjLmh0bWwjaWQyODAyMzQ2XG5cblxuJ3VzZSBzdHJpY3QnO1xuXG5cbnZhciBTY2hlbWEgPSByZXF1aXJlKCcuLi9zY2hlbWEnKTtcblxuXG5tb2R1bGUuZXhwb3J0cyA9IG5ldyBTY2hlbWEoe1xuICBleHBsaWNpdDogW1xuICAgIHJlcXVpcmUoJy4uL3R5cGUvc3RyJyksXG4gICAgcmVxdWlyZSgnLi4vdHlwZS9zZXEnKSxcbiAgICByZXF1aXJlKCcuLi90eXBlL21hcCcpXG4gIF1cbn0pO1xuIiwiLy8gU3RhbmRhcmQgWUFNTCdzIEpTT04gc2NoZW1hLlxuLy8gaHR0cDovL3d3dy55YW1sLm9yZy9zcGVjLzEuMi9zcGVjLmh0bWwjaWQyODAzMjMxXG4vL1xuLy8gTk9URTogSlMtWUFNTCBkb2VzIG5vdCBzdXBwb3J0IHNjaGVtYS1zcGVjaWZpYyB0YWcgcmVzb2x1dGlvbiByZXN0cmljdGlvbnMuXG4vLyBTbywgdGhpcyBzY2hlbWEgaXMgbm90IHN1Y2ggc3RyaWN0IGFzIGRlZmluZWQgaW4gdGhlIFlBTUwgc3BlY2lmaWNhdGlvbi5cbi8vIEl0IGFsbG93cyBudW1iZXJzIGluIGJpbmFyeSBub3RhaW9uLCB1c2UgYE51bGxgIGFuZCBgTlVMTGAgYXMgYG51bGxgLCBldGMuXG5cblxuJ3VzZSBzdHJpY3QnO1xuXG5cbnZhciBTY2hlbWEgPSByZXF1aXJlKCcuLi9zY2hlbWEnKTtcblxuXG5tb2R1bGUuZXhwb3J0cyA9IG5ldyBTY2hlbWEoe1xuICBpbmNsdWRlOiBbXG4gICAgcmVxdWlyZSgnLi9mYWlsc2FmZScpXG4gIF0sXG4gIGltcGxpY2l0OiBbXG4gICAgcmVxdWlyZSgnLi4vdHlwZS9udWxsJyksXG4gICAgcmVxdWlyZSgnLi4vdHlwZS9ib29sJyksXG4gICAgcmVxdWlyZSgnLi4vdHlwZS9pbnQnKSxcbiAgICByZXF1aXJlKCcuLi90eXBlL2Zsb2F0JylcbiAgXVxufSk7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBZQU1MRXhjZXB0aW9uID0gcmVxdWlyZSgnLi9leGNlcHRpb24nKTtcblxudmFyIFRZUEVfQ09OU1RSVUNUT1JfT1BUSU9OUyA9IFtcbiAgJ2tpbmQnLFxuICAncmVzb2x2ZScsXG4gICdjb25zdHJ1Y3QnLFxuICAnaW5zdGFuY2VPZicsXG4gICdwcmVkaWNhdGUnLFxuICAncmVwcmVzZW50JyxcbiAgJ2RlZmF1bHRTdHlsZScsXG4gICdzdHlsZUFsaWFzZXMnXG5dO1xuXG52YXIgWUFNTF9OT0RFX0tJTkRTID0gW1xuICAnc2NhbGFyJyxcbiAgJ3NlcXVlbmNlJyxcbiAgJ21hcHBpbmcnXG5dO1xuXG5mdW5jdGlvbiBjb21waWxlU3R5bGVBbGlhc2VzKG1hcCkge1xuICB2YXIgcmVzdWx0ID0ge307XG5cbiAgaWYgKG1hcCAhPT0gbnVsbCkge1xuICAgIE9iamVjdC5rZXlzKG1hcCkuZm9yRWFjaChmdW5jdGlvbiAoc3R5bGUpIHtcbiAgICAgIG1hcFtzdHlsZV0uZm9yRWFjaChmdW5jdGlvbiAoYWxpYXMpIHtcbiAgICAgICAgcmVzdWx0W1N0cmluZyhhbGlhcyldID0gc3R5bGU7XG4gICAgICB9KTtcbiAgICB9KTtcbiAgfVxuXG4gIHJldHVybiByZXN1bHQ7XG59XG5cbmZ1bmN0aW9uIFR5cGUodGFnLCBvcHRpb25zKSB7XG4gIG9wdGlvbnMgPSBvcHRpb25zIHx8IHt9O1xuXG4gIE9iamVjdC5rZXlzKG9wdGlvbnMpLmZvckVhY2goZnVuY3Rpb24gKG5hbWUpIHtcbiAgICBpZiAoVFlQRV9DT05TVFJVQ1RPUl9PUFRJT05TLmluZGV4T2YobmFtZSkgPT09IC0xKSB7XG4gICAgICB0aHJvdyBuZXcgWUFNTEV4Y2VwdGlvbignVW5rbm93biBvcHRpb24gXCInICsgbmFtZSArICdcIiBpcyBtZXQgaW4gZGVmaW5pdGlvbiBvZiBcIicgKyB0YWcgKyAnXCIgWUFNTCB0eXBlLicpO1xuICAgIH1cbiAgfSk7XG5cbiAgLy8gVE9ETzogQWRkIHRhZyBmb3JtYXQgY2hlY2suXG4gIHRoaXMudGFnICAgICAgICAgID0gdGFnO1xuICB0aGlzLmtpbmQgICAgICAgICA9IG9wdGlvbnNbJ2tpbmQnXSAgICAgICAgIHx8IG51bGw7XG4gIHRoaXMucmVzb2x2ZSAgICAgID0gb3B0aW9uc1sncmVzb2x2ZSddICAgICAgfHwgZnVuY3Rpb24gKCkgeyByZXR1cm4gdHJ1ZTsgfTtcbiAgdGhpcy5jb25zdHJ1Y3QgICAgPSBvcHRpb25zWydjb25zdHJ1Y3QnXSAgICB8fCBmdW5jdGlvbiAoZGF0YSkgeyByZXR1cm4gZGF0YTsgfTtcbiAgdGhpcy5pbnN0YW5jZU9mICAgPSBvcHRpb25zWydpbnN0YW5jZU9mJ10gICB8fCBudWxsO1xuICB0aGlzLnByZWRpY2F0ZSAgICA9IG9wdGlvbnNbJ3ByZWRpY2F0ZSddICAgIHx8IG51bGw7XG4gIHRoaXMucmVwcmVzZW50ICAgID0gb3B0aW9uc1sncmVwcmVzZW50J10gICAgfHwgbnVsbDtcbiAgdGhpcy5kZWZhdWx0U3R5bGUgPSBvcHRpb25zWydkZWZhdWx0U3R5bGUnXSB8fCBudWxsO1xuICB0aGlzLnN0eWxlQWxpYXNlcyA9IGNvbXBpbGVTdHlsZUFsaWFzZXMob3B0aW9uc1snc3R5bGVBbGlhc2VzJ10gfHwgbnVsbCk7XG5cbiAgaWYgKFlBTUxfTk9ERV9LSU5EUy5pbmRleE9mKHRoaXMua2luZCkgPT09IC0xKSB7XG4gICAgdGhyb3cgbmV3IFlBTUxFeGNlcHRpb24oJ1Vua25vd24ga2luZCBcIicgKyB0aGlzLmtpbmQgKyAnXCIgaXMgc3BlY2lmaWVkIGZvciBcIicgKyB0YWcgKyAnXCIgWUFNTCB0eXBlLicpO1xuICB9XG59XG5cbm1vZHVsZS5leHBvcnRzID0gVHlwZTtcbiIsIid1c2Ugc3RyaWN0JztcblxuLyplc2xpbnQtZGlzYWJsZSBuby1iaXR3aXNlKi9cblxudmFyIE5vZGVCdWZmZXI7XG5cbnRyeSB7XG4gIC8vIEEgdHJpY2sgZm9yIGJyb3dzZXJpZmllZCB2ZXJzaW9uLCB0byBub3QgaW5jbHVkZSBgQnVmZmVyYCBzaGltXG4gIHZhciBfcmVxdWlyZSA9IHJlcXVpcmU7XG4gIE5vZGVCdWZmZXIgPSBfcmVxdWlyZSgnYnVmZmVyJykuQnVmZmVyO1xufSBjYXRjaCAoX18pIHt9XG5cbnZhciBUeXBlICAgICAgID0gcmVxdWlyZSgnLi4vdHlwZScpO1xuXG5cbi8vIFsgNjQsIDY1LCA2NiBdIC0+IFsgcGFkZGluZywgQ1IsIExGIF1cbnZhciBCQVNFNjRfTUFQID0gJ0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Ky89XFxuXFxyJztcblxuXG5mdW5jdGlvbiByZXNvbHZlWWFtbEJpbmFyeShkYXRhKSB7XG4gIGlmIChkYXRhID09PSBudWxsKSByZXR1cm4gZmFsc2U7XG5cbiAgdmFyIGNvZGUsIGlkeCwgYml0bGVuID0gMCwgbWF4ID0gZGF0YS5sZW5ndGgsIG1hcCA9IEJBU0U2NF9NQVA7XG5cbiAgLy8gQ29udmVydCBvbmUgYnkgb25lLlxuICBmb3IgKGlkeCA9IDA7IGlkeCA8IG1heDsgaWR4KyspIHtcbiAgICBjb2RlID0gbWFwLmluZGV4T2YoZGF0YS5jaGFyQXQoaWR4KSk7XG5cbiAgICAvLyBTa2lwIENSL0xGXG4gICAgaWYgKGNvZGUgPiA2NCkgY29udGludWU7XG5cbiAgICAvLyBGYWlsIG9uIGlsbGVnYWwgY2hhcmFjdGVyc1xuICAgIGlmIChjb2RlIDwgMCkgcmV0dXJuIGZhbHNlO1xuXG4gICAgYml0bGVuICs9IDY7XG4gIH1cblxuICAvLyBJZiB0aGVyZSBhcmUgYW55IGJpdHMgbGVmdCwgc291cmNlIHdhcyBjb3JydXB0ZWRcbiAgcmV0dXJuIChiaXRsZW4gJSA4KSA9PT0gMDtcbn1cblxuZnVuY3Rpb24gY29uc3RydWN0WWFtbEJpbmFyeShkYXRhKSB7XG4gIHZhciBpZHgsIHRhaWxiaXRzLFxuICAgICAgaW5wdXQgPSBkYXRhLnJlcGxhY2UoL1tcXHJcXG49XS9nLCAnJyksIC8vIHJlbW92ZSBDUi9MRiAmIHBhZGRpbmcgdG8gc2ltcGxpZnkgc2NhblxuICAgICAgbWF4ID0gaW5wdXQubGVuZ3RoLFxuICAgICAgbWFwID0gQkFTRTY0X01BUCxcbiAgICAgIGJpdHMgPSAwLFxuICAgICAgcmVzdWx0ID0gW107XG5cbiAgLy8gQ29sbGVjdCBieSA2KjQgYml0cyAoMyBieXRlcylcblxuICBmb3IgKGlkeCA9IDA7IGlkeCA8IG1heDsgaWR4KyspIHtcbiAgICBpZiAoKGlkeCAlIDQgPT09IDApICYmIGlkeCkge1xuICAgICAgcmVzdWx0LnB1c2goKGJpdHMgPj4gMTYpICYgMHhGRik7XG4gICAgICByZXN1bHQucHVzaCgoYml0cyA+PiA4KSAmIDB4RkYpO1xuICAgICAgcmVzdWx0LnB1c2goYml0cyAmIDB4RkYpO1xuICAgIH1cblxuICAgIGJpdHMgPSAoYml0cyA8PCA2KSB8IG1hcC5pbmRleE9mKGlucHV0LmNoYXJBdChpZHgpKTtcbiAgfVxuXG4gIC8vIER1bXAgdGFpbFxuXG4gIHRhaWxiaXRzID0gKG1heCAlIDQpICogNjtcblxuICBpZiAodGFpbGJpdHMgPT09IDApIHtcbiAgICByZXN1bHQucHVzaCgoYml0cyA+PiAxNikgJiAweEZGKTtcbiAgICByZXN1bHQucHVzaCgoYml0cyA+PiA4KSAmIDB4RkYpO1xuICAgIHJlc3VsdC5wdXNoKGJpdHMgJiAweEZGKTtcbiAgfSBlbHNlIGlmICh0YWlsYml0cyA9PT0gMTgpIHtcbiAgICByZXN1bHQucHVzaCgoYml0cyA+PiAxMCkgJiAweEZGKTtcbiAgICByZXN1bHQucHVzaCgoYml0cyA+PiAyKSAmIDB4RkYpO1xuICB9IGVsc2UgaWYgKHRhaWxiaXRzID09PSAxMikge1xuICAgIHJlc3VsdC5wdXNoKChiaXRzID4+IDQpICYgMHhGRik7XG4gIH1cblxuICAvLyBXcmFwIGludG8gQnVmZmVyIGZvciBOb2RlSlMgYW5kIGxlYXZlIEFycmF5IGZvciBicm93c2VyXG4gIGlmIChOb2RlQnVmZmVyKSByZXR1cm4gbmV3IE5vZGVCdWZmZXIocmVzdWx0KTtcblxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5mdW5jdGlvbiByZXByZXNlbnRZYW1sQmluYXJ5KG9iamVjdCAvKiwgc3R5bGUqLykge1xuICB2YXIgcmVzdWx0ID0gJycsIGJpdHMgPSAwLCBpZHgsIHRhaWwsXG4gICAgICBtYXggPSBvYmplY3QubGVuZ3RoLFxuICAgICAgbWFwID0gQkFTRTY0X01BUDtcblxuICAvLyBDb252ZXJ0IGV2ZXJ5IHRocmVlIGJ5dGVzIHRvIDQgQVNDSUkgY2hhcmFjdGVycy5cblxuICBmb3IgKGlkeCA9IDA7IGlkeCA8IG1heDsgaWR4KyspIHtcbiAgICBpZiAoKGlkeCAlIDMgPT09IDApICYmIGlkeCkge1xuICAgICAgcmVzdWx0ICs9IG1hcFsoYml0cyA+PiAxOCkgJiAweDNGXTtcbiAgICAgIHJlc3VsdCArPSBtYXBbKGJpdHMgPj4gMTIpICYgMHgzRl07XG4gICAgICByZXN1bHQgKz0gbWFwWyhiaXRzID4+IDYpICYgMHgzRl07XG4gICAgICByZXN1bHQgKz0gbWFwW2JpdHMgJiAweDNGXTtcbiAgICB9XG5cbiAgICBiaXRzID0gKGJpdHMgPDwgOCkgKyBvYmplY3RbaWR4XTtcbiAgfVxuXG4gIC8vIER1bXAgdGFpbFxuXG4gIHRhaWwgPSBtYXggJSAzO1xuXG4gIGlmICh0YWlsID09PSAwKSB7XG4gICAgcmVzdWx0ICs9IG1hcFsoYml0cyA+PiAxOCkgJiAweDNGXTtcbiAgICByZXN1bHQgKz0gbWFwWyhiaXRzID4+IDEyKSAmIDB4M0ZdO1xuICAgIHJlc3VsdCArPSBtYXBbKGJpdHMgPj4gNikgJiAweDNGXTtcbiAgICByZXN1bHQgKz0gbWFwW2JpdHMgJiAweDNGXTtcbiAgfSBlbHNlIGlmICh0YWlsID09PSAyKSB7XG4gICAgcmVzdWx0ICs9IG1hcFsoYml0cyA+PiAxMCkgJiAweDNGXTtcbiAgICByZXN1bHQgKz0gbWFwWyhiaXRzID4+IDQpICYgMHgzRl07XG4gICAgcmVzdWx0ICs9IG1hcFsoYml0cyA8PCAyKSAmIDB4M0ZdO1xuICAgIHJlc3VsdCArPSBtYXBbNjRdO1xuICB9IGVsc2UgaWYgKHRhaWwgPT09IDEpIHtcbiAgICByZXN1bHQgKz0gbWFwWyhiaXRzID4+IDIpICYgMHgzRl07XG4gICAgcmVzdWx0ICs9IG1hcFsoYml0cyA8PCA0KSAmIDB4M0ZdO1xuICAgIHJlc3VsdCArPSBtYXBbNjRdO1xuICAgIHJlc3VsdCArPSBtYXBbNjRdO1xuICB9XG5cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxuZnVuY3Rpb24gaXNCaW5hcnkob2JqZWN0KSB7XG4gIHJldHVybiBOb2RlQnVmZmVyICYmIE5vZGVCdWZmZXIuaXNCdWZmZXIob2JqZWN0KTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBuZXcgVHlwZSgndGFnOnlhbWwub3JnLDIwMDI6YmluYXJ5Jywge1xuICBraW5kOiAnc2NhbGFyJyxcbiAgcmVzb2x2ZTogcmVzb2x2ZVlhbWxCaW5hcnksXG4gIGNvbnN0cnVjdDogY29uc3RydWN0WWFtbEJpbmFyeSxcbiAgcHJlZGljYXRlOiBpc0JpbmFyeSxcbiAgcmVwcmVzZW50OiByZXByZXNlbnRZYW1sQmluYXJ5XG59KTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIFR5cGUgPSByZXF1aXJlKCcuLi90eXBlJyk7XG5cbmZ1bmN0aW9uIHJlc29sdmVZYW1sQm9vbGVhbihkYXRhKSB7XG4gIGlmIChkYXRhID09PSBudWxsKSByZXR1cm4gZmFsc2U7XG5cbiAgdmFyIG1heCA9IGRhdGEubGVuZ3RoO1xuXG4gIHJldHVybiAobWF4ID09PSA0ICYmIChkYXRhID09PSAndHJ1ZScgfHwgZGF0YSA9PT0gJ1RydWUnIHx8IGRhdGEgPT09ICdUUlVFJykpIHx8XG4gICAgICAgICAobWF4ID09PSA1ICYmIChkYXRhID09PSAnZmFsc2UnIHx8IGRhdGEgPT09ICdGYWxzZScgfHwgZGF0YSA9PT0gJ0ZBTFNFJykpO1xufVxuXG5mdW5jdGlvbiBjb25zdHJ1Y3RZYW1sQm9vbGVhbihkYXRhKSB7XG4gIHJldHVybiBkYXRhID09PSAndHJ1ZScgfHxcbiAgICAgICAgIGRhdGEgPT09ICdUcnVlJyB8fFxuICAgICAgICAgZGF0YSA9PT0gJ1RSVUUnO1xufVxuXG5mdW5jdGlvbiBpc0Jvb2xlYW4ob2JqZWN0KSB7XG4gIHJldHVybiBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nLmNhbGwob2JqZWN0KSA9PT0gJ1tvYmplY3QgQm9vbGVhbl0nO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IG5ldyBUeXBlKCd0YWc6eWFtbC5vcmcsMjAwMjpib29sJywge1xuICBraW5kOiAnc2NhbGFyJyxcbiAgcmVzb2x2ZTogcmVzb2x2ZVlhbWxCb29sZWFuLFxuICBjb25zdHJ1Y3Q6IGNvbnN0cnVjdFlhbWxCb29sZWFuLFxuICBwcmVkaWNhdGU6IGlzQm9vbGVhbixcbiAgcmVwcmVzZW50OiB7XG4gICAgbG93ZXJjYXNlOiBmdW5jdGlvbiAob2JqZWN0KSB7IHJldHVybiBvYmplY3QgPyAndHJ1ZScgOiAnZmFsc2UnOyB9LFxuICAgIHVwcGVyY2FzZTogZnVuY3Rpb24gKG9iamVjdCkgeyByZXR1cm4gb2JqZWN0ID8gJ1RSVUUnIDogJ0ZBTFNFJzsgfSxcbiAgICBjYW1lbGNhc2U6IGZ1bmN0aW9uIChvYmplY3QpIHsgcmV0dXJuIG9iamVjdCA/ICdUcnVlJyA6ICdGYWxzZSc7IH1cbiAgfSxcbiAgZGVmYXVsdFN0eWxlOiAnbG93ZXJjYXNlJ1xufSk7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBjb21tb24gPSByZXF1aXJlKCcuLi9jb21tb24nKTtcbnZhciBUeXBlICAgPSByZXF1aXJlKCcuLi90eXBlJyk7XG5cbnZhciBZQU1MX0ZMT0FUX1BBVFRFUk4gPSBuZXcgUmVnRXhwKFxuICAnXig/OlstK10/KD86WzAtOV1bMC05X10qKVxcXFwuWzAtOV9dKig/OltlRV1bLStdWzAtOV0rKT8nICtcbiAgJ3xcXFxcLlswLTlfXSsoPzpbZUVdWy0rXVswLTldKyk/JyArXG4gICd8Wy0rXT9bMC05XVswLTlfXSooPzo6WzAtNV0/WzAtOV0pK1xcXFwuWzAtOV9dKicgK1xuICAnfFstK10/XFxcXC4oPzppbmZ8SW5mfElORiknICtcbiAgJ3xcXFxcLig/Om5hbnxOYU58TkFOKSkkJyk7XG5cbmZ1bmN0aW9uIHJlc29sdmVZYW1sRmxvYXQoZGF0YSkge1xuICBpZiAoZGF0YSA9PT0gbnVsbCkgcmV0dXJuIGZhbHNlO1xuXG4gIGlmICghWUFNTF9GTE9BVF9QQVRURVJOLnRlc3QoZGF0YSkpIHJldHVybiBmYWxzZTtcblxuICByZXR1cm4gdHJ1ZTtcbn1cblxuZnVuY3Rpb24gY29uc3RydWN0WWFtbEZsb2F0KGRhdGEpIHtcbiAgdmFyIHZhbHVlLCBzaWduLCBiYXNlLCBkaWdpdHM7XG5cbiAgdmFsdWUgID0gZGF0YS5yZXBsYWNlKC9fL2csICcnKS50b0xvd2VyQ2FzZSgpO1xuICBzaWduICAgPSB2YWx1ZVswXSA9PT0gJy0nID8gLTEgOiAxO1xuICBkaWdpdHMgPSBbXTtcblxuICBpZiAoJystJy5pbmRleE9mKHZhbHVlWzBdKSA+PSAwKSB7XG4gICAgdmFsdWUgPSB2YWx1ZS5zbGljZSgxKTtcbiAgfVxuXG4gIGlmICh2YWx1ZSA9PT0gJy5pbmYnKSB7XG4gICAgcmV0dXJuIChzaWduID09PSAxKSA/IE51bWJlci5QT1NJVElWRV9JTkZJTklUWSA6IE51bWJlci5ORUdBVElWRV9JTkZJTklUWTtcblxuICB9IGVsc2UgaWYgKHZhbHVlID09PSAnLm5hbicpIHtcbiAgICByZXR1cm4gTmFOO1xuXG4gIH0gZWxzZSBpZiAodmFsdWUuaW5kZXhPZignOicpID49IDApIHtcbiAgICB2YWx1ZS5zcGxpdCgnOicpLmZvckVhY2goZnVuY3Rpb24gKHYpIHtcbiAgICAgIGRpZ2l0cy51bnNoaWZ0KHBhcnNlRmxvYXQodiwgMTApKTtcbiAgICB9KTtcblxuICAgIHZhbHVlID0gMC4wO1xuICAgIGJhc2UgPSAxO1xuXG4gICAgZGlnaXRzLmZvckVhY2goZnVuY3Rpb24gKGQpIHtcbiAgICAgIHZhbHVlICs9IGQgKiBiYXNlO1xuICAgICAgYmFzZSAqPSA2MDtcbiAgICB9KTtcblxuICAgIHJldHVybiBzaWduICogdmFsdWU7XG5cbiAgfVxuICByZXR1cm4gc2lnbiAqIHBhcnNlRmxvYXQodmFsdWUsIDEwKTtcbn1cblxuXG52YXIgU0NJRU5USUZJQ19XSVRIT1VUX0RPVCA9IC9eWy0rXT9bMC05XStlLztcblxuZnVuY3Rpb24gcmVwcmVzZW50WWFtbEZsb2F0KG9iamVjdCwgc3R5bGUpIHtcbiAgdmFyIHJlcztcblxuICBpZiAoaXNOYU4ob2JqZWN0KSkge1xuICAgIHN3aXRjaCAoc3R5bGUpIHtcbiAgICAgIGNhc2UgJ2xvd2VyY2FzZSc6IHJldHVybiAnLm5hbic7XG4gICAgICBjYXNlICd1cHBlcmNhc2UnOiByZXR1cm4gJy5OQU4nO1xuICAgICAgY2FzZSAnY2FtZWxjYXNlJzogcmV0dXJuICcuTmFOJztcbiAgICB9XG4gIH0gZWxzZSBpZiAoTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZID09PSBvYmplY3QpIHtcbiAgICBzd2l0Y2ggKHN0eWxlKSB7XG4gICAgICBjYXNlICdsb3dlcmNhc2UnOiByZXR1cm4gJy5pbmYnO1xuICAgICAgY2FzZSAndXBwZXJjYXNlJzogcmV0dXJuICcuSU5GJztcbiAgICAgIGNhc2UgJ2NhbWVsY2FzZSc6IHJldHVybiAnLkluZic7XG4gICAgfVxuICB9IGVsc2UgaWYgKE51bWJlci5ORUdBVElWRV9JTkZJTklUWSA9PT0gb2JqZWN0KSB7XG4gICAgc3dpdGNoIChzdHlsZSkge1xuICAgICAgY2FzZSAnbG93ZXJjYXNlJzogcmV0dXJuICctLmluZic7XG4gICAgICBjYXNlICd1cHBlcmNhc2UnOiByZXR1cm4gJy0uSU5GJztcbiAgICAgIGNhc2UgJ2NhbWVsY2FzZSc6IHJldHVybiAnLS5JbmYnO1xuICAgIH1cbiAgfSBlbHNlIGlmIChjb21tb24uaXNOZWdhdGl2ZVplcm8ob2JqZWN0KSkge1xuICAgIHJldHVybiAnLTAuMCc7XG4gIH1cblxuICByZXMgPSBvYmplY3QudG9TdHJpbmcoMTApO1xuXG4gIC8vIEpTIHN0cmluZ2lmaWVyIGNhbiBidWlsZCBzY2llbnRpZmljIGZvcm1hdCB3aXRob3V0IGRvdHM6IDVlLTEwMCxcbiAgLy8gd2hpbGUgWUFNTCByZXF1cmVzIGRvdDogNS5lLTEwMC4gRml4IGl0IHdpdGggc2ltcGxlIGhhY2tcblxuICByZXR1cm4gU0NJRU5USUZJQ19XSVRIT1VUX0RPVC50ZXN0KHJlcykgPyByZXMucmVwbGFjZSgnZScsICcuZScpIDogcmVzO1xufVxuXG5mdW5jdGlvbiBpc0Zsb2F0KG9iamVjdCkge1xuICByZXR1cm4gKE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChvYmplY3QpID09PSAnW29iamVjdCBOdW1iZXJdJykgJiZcbiAgICAgICAgIChvYmplY3QgJSAxICE9PSAwIHx8IGNvbW1vbi5pc05lZ2F0aXZlWmVybyhvYmplY3QpKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBuZXcgVHlwZSgndGFnOnlhbWwub3JnLDIwMDI6ZmxvYXQnLCB7XG4gIGtpbmQ6ICdzY2FsYXInLFxuICByZXNvbHZlOiByZXNvbHZlWWFtbEZsb2F0LFxuICBjb25zdHJ1Y3Q6IGNvbnN0cnVjdFlhbWxGbG9hdCxcbiAgcHJlZGljYXRlOiBpc0Zsb2F0LFxuICByZXByZXNlbnQ6IHJlcHJlc2VudFlhbWxGbG9hdCxcbiAgZGVmYXVsdFN0eWxlOiAnbG93ZXJjYXNlJ1xufSk7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBjb21tb24gPSByZXF1aXJlKCcuLi9jb21tb24nKTtcbnZhciBUeXBlICAgPSByZXF1aXJlKCcuLi90eXBlJyk7XG5cbmZ1bmN0aW9uIGlzSGV4Q29kZShjKSB7XG4gIHJldHVybiAoKDB4MzAvKiAwICovIDw9IGMpICYmIChjIDw9IDB4MzkvKiA5ICovKSkgfHxcbiAgICAgICAgICgoMHg0MS8qIEEgKi8gPD0gYykgJiYgKGMgPD0gMHg0Ni8qIEYgKi8pKSB8fFxuICAgICAgICAgKCgweDYxLyogYSAqLyA8PSBjKSAmJiAoYyA8PSAweDY2LyogZiAqLykpO1xufVxuXG5mdW5jdGlvbiBpc09jdENvZGUoYykge1xuICByZXR1cm4gKCgweDMwLyogMCAqLyA8PSBjKSAmJiAoYyA8PSAweDM3LyogNyAqLykpO1xufVxuXG5mdW5jdGlvbiBpc0RlY0NvZGUoYykge1xuICByZXR1cm4gKCgweDMwLyogMCAqLyA8PSBjKSAmJiAoYyA8PSAweDM5LyogOSAqLykpO1xufVxuXG5mdW5jdGlvbiByZXNvbHZlWWFtbEludGVnZXIoZGF0YSkge1xuICBpZiAoZGF0YSA9PT0gbnVsbCkgcmV0dXJuIGZhbHNlO1xuXG4gIHZhciBtYXggPSBkYXRhLmxlbmd0aCxcbiAgICAgIGluZGV4ID0gMCxcbiAgICAgIGhhc0RpZ2l0cyA9IGZhbHNlLFxuICAgICAgY2g7XG5cbiAgaWYgKCFtYXgpIHJldHVybiBmYWxzZTtcblxuICBjaCA9IGRhdGFbaW5kZXhdO1xuXG4gIC8vIHNpZ25cbiAgaWYgKGNoID09PSAnLScgfHwgY2ggPT09ICcrJykge1xuICAgIGNoID0gZGF0YVsrK2luZGV4XTtcbiAgfVxuXG4gIGlmIChjaCA9PT0gJzAnKSB7XG4gICAgLy8gMFxuICAgIGlmIChpbmRleCArIDEgPT09IG1heCkgcmV0dXJuIHRydWU7XG4gICAgY2ggPSBkYXRhWysraW5kZXhdO1xuXG4gICAgLy8gYmFzZSAyLCBiYXNlIDgsIGJhc2UgMTZcblxuICAgIGlmIChjaCA9PT0gJ2InKSB7XG4gICAgICAvLyBiYXNlIDJcbiAgICAgIGluZGV4Kys7XG5cbiAgICAgIGZvciAoOyBpbmRleCA8IG1heDsgaW5kZXgrKykge1xuICAgICAgICBjaCA9IGRhdGFbaW5kZXhdO1xuICAgICAgICBpZiAoY2ggPT09ICdfJykgY29udGludWU7XG4gICAgICAgIGlmIChjaCAhPT0gJzAnICYmIGNoICE9PSAnMScpIHJldHVybiBmYWxzZTtcbiAgICAgICAgaGFzRGlnaXRzID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBoYXNEaWdpdHM7XG4gICAgfVxuXG5cbiAgICBpZiAoY2ggPT09ICd4Jykge1xuICAgICAgLy8gYmFzZSAxNlxuICAgICAgaW5kZXgrKztcblxuICAgICAgZm9yICg7IGluZGV4IDwgbWF4OyBpbmRleCsrKSB7XG4gICAgICAgIGNoID0gZGF0YVtpbmRleF07XG4gICAgICAgIGlmIChjaCA9PT0gJ18nKSBjb250aW51ZTtcbiAgICAgICAgaWYgKCFpc0hleENvZGUoZGF0YS5jaGFyQ29kZUF0KGluZGV4KSkpIHJldHVybiBmYWxzZTtcbiAgICAgICAgaGFzRGlnaXRzID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBoYXNEaWdpdHM7XG4gICAgfVxuXG4gICAgLy8gYmFzZSA4XG4gICAgZm9yICg7IGluZGV4IDwgbWF4OyBpbmRleCsrKSB7XG4gICAgICBjaCA9IGRhdGFbaW5kZXhdO1xuICAgICAgaWYgKGNoID09PSAnXycpIGNvbnRpbnVlO1xuICAgICAgaWYgKCFpc09jdENvZGUoZGF0YS5jaGFyQ29kZUF0KGluZGV4KSkpIHJldHVybiBmYWxzZTtcbiAgICAgIGhhc0RpZ2l0cyA9IHRydWU7XG4gICAgfVxuICAgIHJldHVybiBoYXNEaWdpdHM7XG4gIH1cblxuICAvLyBiYXNlIDEwIChleGNlcHQgMCkgb3IgYmFzZSA2MFxuXG4gIGZvciAoOyBpbmRleCA8IG1heDsgaW5kZXgrKykge1xuICAgIGNoID0gZGF0YVtpbmRleF07XG4gICAgaWYgKGNoID09PSAnXycpIGNvbnRpbnVlO1xuICAgIGlmIChjaCA9PT0gJzonKSBicmVhaztcbiAgICBpZiAoIWlzRGVjQ29kZShkYXRhLmNoYXJDb2RlQXQoaW5kZXgpKSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBoYXNEaWdpdHMgPSB0cnVlO1xuICB9XG5cbiAgaWYgKCFoYXNEaWdpdHMpIHJldHVybiBmYWxzZTtcblxuICAvLyBpZiAhYmFzZTYwIC0gZG9uZTtcbiAgaWYgKGNoICE9PSAnOicpIHJldHVybiB0cnVlO1xuXG4gIC8vIGJhc2U2MCBhbG1vc3Qgbm90IHVzZWQsIG5vIG5lZWRzIHRvIG9wdGltaXplXG4gIHJldHVybiAvXig6WzAtNV0/WzAtOV0pKyQvLnRlc3QoZGF0YS5zbGljZShpbmRleCkpO1xufVxuXG5mdW5jdGlvbiBjb25zdHJ1Y3RZYW1sSW50ZWdlcihkYXRhKSB7XG4gIHZhciB2YWx1ZSA9IGRhdGEsIHNpZ24gPSAxLCBjaCwgYmFzZSwgZGlnaXRzID0gW107XG5cbiAgaWYgKHZhbHVlLmluZGV4T2YoJ18nKSAhPT0gLTEpIHtcbiAgICB2YWx1ZSA9IHZhbHVlLnJlcGxhY2UoL18vZywgJycpO1xuICB9XG5cbiAgY2ggPSB2YWx1ZVswXTtcblxuICBpZiAoY2ggPT09ICctJyB8fCBjaCA9PT0gJysnKSB7XG4gICAgaWYgKGNoID09PSAnLScpIHNpZ24gPSAtMTtcbiAgICB2YWx1ZSA9IHZhbHVlLnNsaWNlKDEpO1xuICAgIGNoID0gdmFsdWVbMF07XG4gIH1cblxuICBpZiAodmFsdWUgPT09ICcwJykgcmV0dXJuIDA7XG5cbiAgaWYgKGNoID09PSAnMCcpIHtcbiAgICBpZiAodmFsdWVbMV0gPT09ICdiJykgcmV0dXJuIHNpZ24gKiBwYXJzZUludCh2YWx1ZS5zbGljZSgyKSwgMik7XG4gICAgaWYgKHZhbHVlWzFdID09PSAneCcpIHJldHVybiBzaWduICogcGFyc2VJbnQodmFsdWUsIDE2KTtcbiAgICByZXR1cm4gc2lnbiAqIHBhcnNlSW50KHZhbHVlLCA4KTtcbiAgfVxuXG4gIGlmICh2YWx1ZS5pbmRleE9mKCc6JykgIT09IC0xKSB7XG4gICAgdmFsdWUuc3BsaXQoJzonKS5mb3JFYWNoKGZ1bmN0aW9uICh2KSB7XG4gICAgICBkaWdpdHMudW5zaGlmdChwYXJzZUludCh2LCAxMCkpO1xuICAgIH0pO1xuXG4gICAgdmFsdWUgPSAwO1xuICAgIGJhc2UgPSAxO1xuXG4gICAgZGlnaXRzLmZvckVhY2goZnVuY3Rpb24gKGQpIHtcbiAgICAgIHZhbHVlICs9IChkICogYmFzZSk7XG4gICAgICBiYXNlICo9IDYwO1xuICAgIH0pO1xuXG4gICAgcmV0dXJuIHNpZ24gKiB2YWx1ZTtcblxuICB9XG5cbiAgcmV0dXJuIHNpZ24gKiBwYXJzZUludCh2YWx1ZSwgMTApO1xufVxuXG5mdW5jdGlvbiBpc0ludGVnZXIob2JqZWN0KSB7XG4gIHJldHVybiAoT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKG9iamVjdCkpID09PSAnW29iamVjdCBOdW1iZXJdJyAmJlxuICAgICAgICAgKG9iamVjdCAlIDEgPT09IDAgJiYgIWNvbW1vbi5pc05lZ2F0aXZlWmVybyhvYmplY3QpKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBuZXcgVHlwZSgndGFnOnlhbWwub3JnLDIwMDI6aW50Jywge1xuICBraW5kOiAnc2NhbGFyJyxcbiAgcmVzb2x2ZTogcmVzb2x2ZVlhbWxJbnRlZ2VyLFxuICBjb25zdHJ1Y3Q6IGNvbnN0cnVjdFlhbWxJbnRlZ2VyLFxuICBwcmVkaWNhdGU6IGlzSW50ZWdlcixcbiAgcmVwcmVzZW50OiB7XG4gICAgYmluYXJ5OiAgICAgIGZ1bmN0aW9uIChvYmplY3QpIHsgcmV0dXJuICcwYicgKyBvYmplY3QudG9TdHJpbmcoMik7IH0sXG4gICAgb2N0YWw6ICAgICAgIGZ1bmN0aW9uIChvYmplY3QpIHsgcmV0dXJuICcwJyAgKyBvYmplY3QudG9TdHJpbmcoOCk7IH0sXG4gICAgZGVjaW1hbDogICAgIGZ1bmN0aW9uIChvYmplY3QpIHsgcmV0dXJuICAgICAgICBvYmplY3QudG9TdHJpbmcoMTApOyB9LFxuICAgIGhleGFkZWNpbWFsOiBmdW5jdGlvbiAob2JqZWN0KSB7IHJldHVybiAnMHgnICsgb2JqZWN0LnRvU3RyaW5nKDE2KS50b1VwcGVyQ2FzZSgpOyB9XG4gIH0sXG4gIGRlZmF1bHRTdHlsZTogJ2RlY2ltYWwnLFxuICBzdHlsZUFsaWFzZXM6IHtcbiAgICBiaW5hcnk6ICAgICAgWyAyLCAgJ2JpbicgXSxcbiAgICBvY3RhbDogICAgICAgWyA4LCAgJ29jdCcgXSxcbiAgICBkZWNpbWFsOiAgICAgWyAxMCwgJ2RlYycgXSxcbiAgICBoZXhhZGVjaW1hbDogWyAxNiwgJ2hleCcgXVxuICB9XG59KTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIGVzcHJpbWE7XG5cbi8vIEJyb3dzZXJpZmllZCB2ZXJzaW9uIGRvZXMgbm90IGhhdmUgZXNwcmltYVxuLy9cbi8vIDEuIEZvciBub2RlLmpzIGp1c3QgcmVxdWlyZSBtb2R1bGUgYXMgZGVwc1xuLy8gMi4gRm9yIGJyb3dzZXIgdHJ5IHRvIHJlcXVpcmUgbXVkdWxlIHZpYSBleHRlcm5hbCBBTUQgc3lzdGVtLlxuLy8gICAgSWYgbm90IGZvdW5kIC0gdHJ5IHRvIGZhbGxiYWNrIHRvIHdpbmRvdy5lc3ByaW1hLiBJZiBub3Rcbi8vICAgIGZvdW5kIHRvbyAtIHRoZW4gZmFpbCB0byBwYXJzZS5cbi8vXG50cnkge1xuICAvLyB3b3JrYXJvdW5kIHRvIGV4Y2x1ZGUgcGFja2FnZSBmcm9tIGJyb3dzZXJpZnkgbGlzdC5cbiAgdmFyIF9yZXF1aXJlID0gcmVxdWlyZTtcbiAgZXNwcmltYSA9IF9yZXF1aXJlKCdlc3ByaW1hJyk7XG59IGNhdGNoIChfKSB7XG4gIC8qZ2xvYmFsIHdpbmRvdyAqL1xuICBpZiAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcpIGVzcHJpbWEgPSB3aW5kb3cuZXNwcmltYTtcbn1cblxudmFyIFR5cGUgPSByZXF1aXJlKCcuLi8uLi90eXBlJyk7XG5cbmZ1bmN0aW9uIHJlc29sdmVKYXZhc2NyaXB0RnVuY3Rpb24oZGF0YSkge1xuICBpZiAoZGF0YSA9PT0gbnVsbCkgcmV0dXJuIGZhbHNlO1xuXG4gIHRyeSB7XG4gICAgdmFyIHNvdXJjZSA9ICcoJyArIGRhdGEgKyAnKScsXG4gICAgICAgIGFzdCAgICA9IGVzcHJpbWEucGFyc2Uoc291cmNlLCB7IHJhbmdlOiB0cnVlIH0pO1xuXG4gICAgaWYgKGFzdC50eXBlICAgICAgICAgICAgICAgICAgICAhPT0gJ1Byb2dyYW0nICAgICAgICAgICAgIHx8XG4gICAgICAgIGFzdC5ib2R5Lmxlbmd0aCAgICAgICAgICAgICAhPT0gMSAgICAgICAgICAgICAgICAgICAgIHx8XG4gICAgICAgIGFzdC5ib2R5WzBdLnR5cGUgICAgICAgICAgICAhPT0gJ0V4cHJlc3Npb25TdGF0ZW1lbnQnIHx8XG4gICAgICAgIGFzdC5ib2R5WzBdLmV4cHJlc3Npb24udHlwZSAhPT0gJ0Z1bmN0aW9uRXhwcmVzc2lvbicpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbmZ1bmN0aW9uIGNvbnN0cnVjdEphdmFzY3JpcHRGdW5jdGlvbihkYXRhKSB7XG4gIC8qanNsaW50IGV2aWw6dHJ1ZSovXG5cbiAgdmFyIHNvdXJjZSA9ICcoJyArIGRhdGEgKyAnKScsXG4gICAgICBhc3QgICAgPSBlc3ByaW1hLnBhcnNlKHNvdXJjZSwgeyByYW5nZTogdHJ1ZSB9KSxcbiAgICAgIHBhcmFtcyA9IFtdLFxuICAgICAgYm9keTtcblxuICBpZiAoYXN0LnR5cGUgICAgICAgICAgICAgICAgICAgICE9PSAnUHJvZ3JhbScgICAgICAgICAgICAgfHxcbiAgICAgIGFzdC5ib2R5Lmxlbmd0aCAgICAgICAgICAgICAhPT0gMSAgICAgICAgICAgICAgICAgICAgIHx8XG4gICAgICBhc3QuYm9keVswXS50eXBlICAgICAgICAgICAgIT09ICdFeHByZXNzaW9uU3RhdGVtZW50JyB8fFxuICAgICAgYXN0LmJvZHlbMF0uZXhwcmVzc2lvbi50eXBlICE9PSAnRnVuY3Rpb25FeHByZXNzaW9uJykge1xuICAgIHRocm93IG5ldyBFcnJvcignRmFpbGVkIHRvIHJlc29sdmUgZnVuY3Rpb24nKTtcbiAgfVxuXG4gIGFzdC5ib2R5WzBdLmV4cHJlc3Npb24ucGFyYW1zLmZvckVhY2goZnVuY3Rpb24gKHBhcmFtKSB7XG4gICAgcGFyYW1zLnB1c2gocGFyYW0ubmFtZSk7XG4gIH0pO1xuXG4gIGJvZHkgPSBhc3QuYm9keVswXS5leHByZXNzaW9uLmJvZHkucmFuZ2U7XG5cbiAgLy8gRXNwcmltYSdzIHJhbmdlcyBpbmNsdWRlIHRoZSBmaXJzdCAneycgYW5kIHRoZSBsYXN0ICd9JyBjaGFyYWN0ZXJzIG9uXG4gIC8vIGZ1bmN0aW9uIGV4cHJlc3Npb25zLiBTbyBjdXQgdGhlbSBvdXQuXG4gIC8qZXNsaW50LWRpc2FibGUgbm8tbmV3LWZ1bmMqL1xuICByZXR1cm4gbmV3IEZ1bmN0aW9uKHBhcmFtcywgc291cmNlLnNsaWNlKGJvZHlbMF0gKyAxLCBib2R5WzFdIC0gMSkpO1xufVxuXG5mdW5jdGlvbiByZXByZXNlbnRKYXZhc2NyaXB0RnVuY3Rpb24ob2JqZWN0IC8qLCBzdHlsZSovKSB7XG4gIHJldHVybiBvYmplY3QudG9TdHJpbmcoKTtcbn1cblxuZnVuY3Rpb24gaXNGdW5jdGlvbihvYmplY3QpIHtcbiAgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChvYmplY3QpID09PSAnW29iamVjdCBGdW5jdGlvbl0nO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IG5ldyBUeXBlKCd0YWc6eWFtbC5vcmcsMjAwMjpqcy9mdW5jdGlvbicsIHtcbiAga2luZDogJ3NjYWxhcicsXG4gIHJlc29sdmU6IHJlc29sdmVKYXZhc2NyaXB0RnVuY3Rpb24sXG4gIGNvbnN0cnVjdDogY29uc3RydWN0SmF2YXNjcmlwdEZ1bmN0aW9uLFxuICBwcmVkaWNhdGU6IGlzRnVuY3Rpb24sXG4gIHJlcHJlc2VudDogcmVwcmVzZW50SmF2YXNjcmlwdEZ1bmN0aW9uXG59KTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIFR5cGUgPSByZXF1aXJlKCcuLi8uLi90eXBlJyk7XG5cbmZ1bmN0aW9uIHJlc29sdmVKYXZhc2NyaXB0UmVnRXhwKGRhdGEpIHtcbiAgaWYgKGRhdGEgPT09IG51bGwpIHJldHVybiBmYWxzZTtcbiAgaWYgKGRhdGEubGVuZ3RoID09PSAwKSByZXR1cm4gZmFsc2U7XG5cbiAgdmFyIHJlZ2V4cCA9IGRhdGEsXG4gICAgICB0YWlsICAgPSAvXFwvKFtnaW1dKikkLy5leGVjKGRhdGEpLFxuICAgICAgbW9kaWZpZXJzID0gJyc7XG5cbiAgLy8gaWYgcmVnZXhwIHN0YXJ0cyB3aXRoICcvJyBpdCBjYW4gaGF2ZSBtb2RpZmllcnMgYW5kIG11c3QgYmUgcHJvcGVybHkgY2xvc2VkXG4gIC8vIGAvZm9vL2dpbWAgLSBtb2RpZmllcnMgdGFpbCBjYW4gYmUgbWF4aW11bSAzIGNoYXJzXG4gIGlmIChyZWdleHBbMF0gPT09ICcvJykge1xuICAgIGlmICh0YWlsKSBtb2RpZmllcnMgPSB0YWlsWzFdO1xuXG4gICAgaWYgKG1vZGlmaWVycy5sZW5ndGggPiAzKSByZXR1cm4gZmFsc2U7XG4gICAgLy8gaWYgZXhwcmVzc2lvbiBzdGFydHMgd2l0aCAvLCBpcyBzaG91bGQgYmUgcHJvcGVybHkgdGVybWluYXRlZFxuICAgIGlmIChyZWdleHBbcmVnZXhwLmxlbmd0aCAtIG1vZGlmaWVycy5sZW5ndGggLSAxXSAhPT0gJy8nKSByZXR1cm4gZmFsc2U7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn1cblxuZnVuY3Rpb24gY29uc3RydWN0SmF2YXNjcmlwdFJlZ0V4cChkYXRhKSB7XG4gIHZhciByZWdleHAgPSBkYXRhLFxuICAgICAgdGFpbCAgID0gL1xcLyhbZ2ltXSopJC8uZXhlYyhkYXRhKSxcbiAgICAgIG1vZGlmaWVycyA9ICcnO1xuXG4gIC8vIGAvZm9vL2dpbWAgLSB0YWlsIGNhbiBiZSBtYXhpbXVtIDQgY2hhcnNcbiAgaWYgKHJlZ2V4cFswXSA9PT0gJy8nKSB7XG4gICAgaWYgKHRhaWwpIG1vZGlmaWVycyA9IHRhaWxbMV07XG4gICAgcmVnZXhwID0gcmVnZXhwLnNsaWNlKDEsIHJlZ2V4cC5sZW5ndGggLSBtb2RpZmllcnMubGVuZ3RoIC0gMSk7XG4gIH1cblxuICByZXR1cm4gbmV3IFJlZ0V4cChyZWdleHAsIG1vZGlmaWVycyk7XG59XG5cbmZ1bmN0aW9uIHJlcHJlc2VudEphdmFzY3JpcHRSZWdFeHAob2JqZWN0IC8qLCBzdHlsZSovKSB7XG4gIHZhciByZXN1bHQgPSAnLycgKyBvYmplY3Quc291cmNlICsgJy8nO1xuXG4gIGlmIChvYmplY3QuZ2xvYmFsKSByZXN1bHQgKz0gJ2cnO1xuICBpZiAob2JqZWN0Lm11bHRpbGluZSkgcmVzdWx0ICs9ICdtJztcbiAgaWYgKG9iamVjdC5pZ25vcmVDYXNlKSByZXN1bHQgKz0gJ2knO1xuXG4gIHJldHVybiByZXN1bHQ7XG59XG5cbmZ1bmN0aW9uIGlzUmVnRXhwKG9iamVjdCkge1xuICByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKG9iamVjdCkgPT09ICdbb2JqZWN0IFJlZ0V4cF0nO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IG5ldyBUeXBlKCd0YWc6eWFtbC5vcmcsMjAwMjpqcy9yZWdleHAnLCB7XG4gIGtpbmQ6ICdzY2FsYXInLFxuICByZXNvbHZlOiByZXNvbHZlSmF2YXNjcmlwdFJlZ0V4cCxcbiAgY29uc3RydWN0OiBjb25zdHJ1Y3RKYXZhc2NyaXB0UmVnRXhwLFxuICBwcmVkaWNhdGU6IGlzUmVnRXhwLFxuICByZXByZXNlbnQ6IHJlcHJlc2VudEphdmFzY3JpcHRSZWdFeHBcbn0pO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgVHlwZSA9IHJlcXVpcmUoJy4uLy4uL3R5cGUnKTtcblxuZnVuY3Rpb24gcmVzb2x2ZUphdmFzY3JpcHRVbmRlZmluZWQoKSB7XG4gIHJldHVybiB0cnVlO1xufVxuXG5mdW5jdGlvbiBjb25zdHJ1Y3RKYXZhc2NyaXB0VW5kZWZpbmVkKCkge1xuICAvKmVzbGludC1kaXNhYmxlIG5vLXVuZGVmaW5lZCovXG4gIHJldHVybiB1bmRlZmluZWQ7XG59XG5cbmZ1bmN0aW9uIHJlcHJlc2VudEphdmFzY3JpcHRVbmRlZmluZWQoKSB7XG4gIHJldHVybiAnJztcbn1cblxuZnVuY3Rpb24gaXNVbmRlZmluZWQob2JqZWN0KSB7XG4gIHJldHVybiB0eXBlb2Ygb2JqZWN0ID09PSAndW5kZWZpbmVkJztcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBuZXcgVHlwZSgndGFnOnlhbWwub3JnLDIwMDI6anMvdW5kZWZpbmVkJywge1xuICBraW5kOiAnc2NhbGFyJyxcbiAgcmVzb2x2ZTogcmVzb2x2ZUphdmFzY3JpcHRVbmRlZmluZWQsXG4gIGNvbnN0cnVjdDogY29uc3RydWN0SmF2YXNjcmlwdFVuZGVmaW5lZCxcbiAgcHJlZGljYXRlOiBpc1VuZGVmaW5lZCxcbiAgcmVwcmVzZW50OiByZXByZXNlbnRKYXZhc2NyaXB0VW5kZWZpbmVkXG59KTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIFR5cGUgPSByZXF1aXJlKCcuLi90eXBlJyk7XG5cbm1vZHVsZS5leHBvcnRzID0gbmV3IFR5cGUoJ3RhZzp5YW1sLm9yZywyMDAyOm1hcCcsIHtcbiAga2luZDogJ21hcHBpbmcnLFxuICBjb25zdHJ1Y3Q6IGZ1bmN0aW9uIChkYXRhKSB7IHJldHVybiBkYXRhICE9PSBudWxsID8gZGF0YSA6IHt9OyB9XG59KTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIFR5cGUgPSByZXF1aXJlKCcuLi90eXBlJyk7XG5cbmZ1bmN0aW9uIHJlc29sdmVZYW1sTWVyZ2UoZGF0YSkge1xuICByZXR1cm4gZGF0YSA9PT0gJzw8JyB8fCBkYXRhID09PSBudWxsO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IG5ldyBUeXBlKCd0YWc6eWFtbC5vcmcsMjAwMjptZXJnZScsIHtcbiAga2luZDogJ3NjYWxhcicsXG4gIHJlc29sdmU6IHJlc29sdmVZYW1sTWVyZ2Vcbn0pO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgVHlwZSA9IHJlcXVpcmUoJy4uL3R5cGUnKTtcblxuZnVuY3Rpb24gcmVzb2x2ZVlhbWxOdWxsKGRhdGEpIHtcbiAgaWYgKGRhdGEgPT09IG51bGwpIHJldHVybiB0cnVlO1xuXG4gIHZhciBtYXggPSBkYXRhLmxlbmd0aDtcblxuICByZXR1cm4gKG1heCA9PT0gMSAmJiBkYXRhID09PSAnficpIHx8XG4gICAgICAgICAobWF4ID09PSA0ICYmIChkYXRhID09PSAnbnVsbCcgfHwgZGF0YSA9PT0gJ051bGwnIHx8IGRhdGEgPT09ICdOVUxMJykpO1xufVxuXG5mdW5jdGlvbiBjb25zdHJ1Y3RZYW1sTnVsbCgpIHtcbiAgcmV0dXJuIG51bGw7XG59XG5cbmZ1bmN0aW9uIGlzTnVsbChvYmplY3QpIHtcbiAgcmV0dXJuIG9iamVjdCA9PT0gbnVsbDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBuZXcgVHlwZSgndGFnOnlhbWwub3JnLDIwMDI6bnVsbCcsIHtcbiAga2luZDogJ3NjYWxhcicsXG4gIHJlc29sdmU6IHJlc29sdmVZYW1sTnVsbCxcbiAgY29uc3RydWN0OiBjb25zdHJ1Y3RZYW1sTnVsbCxcbiAgcHJlZGljYXRlOiBpc051bGwsXG4gIHJlcHJlc2VudDoge1xuICAgIGNhbm9uaWNhbDogZnVuY3Rpb24gKCkgeyByZXR1cm4gJ34nOyAgICB9LFxuICAgIGxvd2VyY2FzZTogZnVuY3Rpb24gKCkgeyByZXR1cm4gJ251bGwnOyB9LFxuICAgIHVwcGVyY2FzZTogZnVuY3Rpb24gKCkgeyByZXR1cm4gJ05VTEwnOyB9LFxuICAgIGNhbWVsY2FzZTogZnVuY3Rpb24gKCkgeyByZXR1cm4gJ051bGwnOyB9XG4gIH0sXG4gIGRlZmF1bHRTdHlsZTogJ2xvd2VyY2FzZSdcbn0pO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgVHlwZSA9IHJlcXVpcmUoJy4uL3R5cGUnKTtcblxudmFyIF9oYXNPd25Qcm9wZXJ0eSA9IE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHk7XG52YXIgX3RvU3RyaW5nICAgICAgID0gT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZztcblxuZnVuY3Rpb24gcmVzb2x2ZVlhbWxPbWFwKGRhdGEpIHtcbiAgaWYgKGRhdGEgPT09IG51bGwpIHJldHVybiB0cnVlO1xuXG4gIHZhciBvYmplY3RLZXlzID0gW10sIGluZGV4LCBsZW5ndGgsIHBhaXIsIHBhaXJLZXksIHBhaXJIYXNLZXksXG4gICAgICBvYmplY3QgPSBkYXRhO1xuXG4gIGZvciAoaW5kZXggPSAwLCBsZW5ndGggPSBvYmplY3QubGVuZ3RoOyBpbmRleCA8IGxlbmd0aDsgaW5kZXggKz0gMSkge1xuICAgIHBhaXIgPSBvYmplY3RbaW5kZXhdO1xuICAgIHBhaXJIYXNLZXkgPSBmYWxzZTtcblxuICAgIGlmIChfdG9TdHJpbmcuY2FsbChwYWlyKSAhPT0gJ1tvYmplY3QgT2JqZWN0XScpIHJldHVybiBmYWxzZTtcblxuICAgIGZvciAocGFpcktleSBpbiBwYWlyKSB7XG4gICAgICBpZiAoX2hhc093blByb3BlcnR5LmNhbGwocGFpciwgcGFpcktleSkpIHtcbiAgICAgICAgaWYgKCFwYWlySGFzS2V5KSBwYWlySGFzS2V5ID0gdHJ1ZTtcbiAgICAgICAgZWxzZSByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKCFwYWlySGFzS2V5KSByZXR1cm4gZmFsc2U7XG5cbiAgICBpZiAob2JqZWN0S2V5cy5pbmRleE9mKHBhaXJLZXkpID09PSAtMSkgb2JqZWN0S2V5cy5wdXNoKHBhaXJLZXkpO1xuICAgIGVsc2UgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgcmV0dXJuIHRydWU7XG59XG5cbmZ1bmN0aW9uIGNvbnN0cnVjdFlhbWxPbWFwKGRhdGEpIHtcbiAgcmV0dXJuIGRhdGEgIT09IG51bGwgPyBkYXRhIDogW107XG59XG5cbm1vZHVsZS5leHBvcnRzID0gbmV3IFR5cGUoJ3RhZzp5YW1sLm9yZywyMDAyOm9tYXAnLCB7XG4gIGtpbmQ6ICdzZXF1ZW5jZScsXG4gIHJlc29sdmU6IHJlc29sdmVZYW1sT21hcCxcbiAgY29uc3RydWN0OiBjb25zdHJ1Y3RZYW1sT21hcFxufSk7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBUeXBlID0gcmVxdWlyZSgnLi4vdHlwZScpO1xuXG52YXIgX3RvU3RyaW5nID0gT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZztcblxuZnVuY3Rpb24gcmVzb2x2ZVlhbWxQYWlycyhkYXRhKSB7XG4gIGlmIChkYXRhID09PSBudWxsKSByZXR1cm4gdHJ1ZTtcblxuICB2YXIgaW5kZXgsIGxlbmd0aCwgcGFpciwga2V5cywgcmVzdWx0LFxuICAgICAgb2JqZWN0ID0gZGF0YTtcblxuICByZXN1bHQgPSBuZXcgQXJyYXkob2JqZWN0Lmxlbmd0aCk7XG5cbiAgZm9yIChpbmRleCA9IDAsIGxlbmd0aCA9IG9iamVjdC5sZW5ndGg7IGluZGV4IDwgbGVuZ3RoOyBpbmRleCArPSAxKSB7XG4gICAgcGFpciA9IG9iamVjdFtpbmRleF07XG5cbiAgICBpZiAoX3RvU3RyaW5nLmNhbGwocGFpcikgIT09ICdbb2JqZWN0IE9iamVjdF0nKSByZXR1cm4gZmFsc2U7XG5cbiAgICBrZXlzID0gT2JqZWN0LmtleXMocGFpcik7XG5cbiAgICBpZiAoa2V5cy5sZW5ndGggIT09IDEpIHJldHVybiBmYWxzZTtcblxuICAgIHJlc3VsdFtpbmRleF0gPSBbIGtleXNbMF0sIHBhaXJba2V5c1swXV0gXTtcbiAgfVxuXG4gIHJldHVybiB0cnVlO1xufVxuXG5mdW5jdGlvbiBjb25zdHJ1Y3RZYW1sUGFpcnMoZGF0YSkge1xuICBpZiAoZGF0YSA9PT0gbnVsbCkgcmV0dXJuIFtdO1xuXG4gIHZhciBpbmRleCwgbGVuZ3RoLCBwYWlyLCBrZXlzLCByZXN1bHQsXG4gICAgICBvYmplY3QgPSBkYXRhO1xuXG4gIHJlc3VsdCA9IG5ldyBBcnJheShvYmplY3QubGVuZ3RoKTtcblxuICBmb3IgKGluZGV4ID0gMCwgbGVuZ3RoID0gb2JqZWN0Lmxlbmd0aDsgaW5kZXggPCBsZW5ndGg7IGluZGV4ICs9IDEpIHtcbiAgICBwYWlyID0gb2JqZWN0W2luZGV4XTtcblxuICAgIGtleXMgPSBPYmplY3Qua2V5cyhwYWlyKTtcblxuICAgIHJlc3VsdFtpbmRleF0gPSBbIGtleXNbMF0sIHBhaXJba2V5c1swXV0gXTtcbiAgfVxuXG4gIHJldHVybiByZXN1bHQ7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gbmV3IFR5cGUoJ3RhZzp5YW1sLm9yZywyMDAyOnBhaXJzJywge1xuICBraW5kOiAnc2VxdWVuY2UnLFxuICByZXNvbHZlOiByZXNvbHZlWWFtbFBhaXJzLFxuICBjb25zdHJ1Y3Q6IGNvbnN0cnVjdFlhbWxQYWlyc1xufSk7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBUeXBlID0gcmVxdWlyZSgnLi4vdHlwZScpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IG5ldyBUeXBlKCd0YWc6eWFtbC5vcmcsMjAwMjpzZXEnLCB7XG4gIGtpbmQ6ICdzZXF1ZW5jZScsXG4gIGNvbnN0cnVjdDogZnVuY3Rpb24gKGRhdGEpIHsgcmV0dXJuIGRhdGEgIT09IG51bGwgPyBkYXRhIDogW107IH1cbn0pO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgVHlwZSA9IHJlcXVpcmUoJy4uL3R5cGUnKTtcblxudmFyIF9oYXNPd25Qcm9wZXJ0eSA9IE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHk7XG5cbmZ1bmN0aW9uIHJlc29sdmVZYW1sU2V0KGRhdGEpIHtcbiAgaWYgKGRhdGEgPT09IG51bGwpIHJldHVybiB0cnVlO1xuXG4gIHZhciBrZXksIG9iamVjdCA9IGRhdGE7XG5cbiAgZm9yIChrZXkgaW4gb2JqZWN0KSB7XG4gICAgaWYgKF9oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwga2V5KSkge1xuICAgICAgaWYgKG9iamVjdFtrZXldICE9PSBudWxsKSByZXR1cm4gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHRydWU7XG59XG5cbmZ1bmN0aW9uIGNvbnN0cnVjdFlhbWxTZXQoZGF0YSkge1xuICByZXR1cm4gZGF0YSAhPT0gbnVsbCA/IGRhdGEgOiB7fTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBuZXcgVHlwZSgndGFnOnlhbWwub3JnLDIwMDI6c2V0Jywge1xuICBraW5kOiAnbWFwcGluZycsXG4gIHJlc29sdmU6IHJlc29sdmVZYW1sU2V0LFxuICBjb25zdHJ1Y3Q6IGNvbnN0cnVjdFlhbWxTZXRcbn0pO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgVHlwZSA9IHJlcXVpcmUoJy4uL3R5cGUnKTtcblxubW9kdWxlLmV4cG9ydHMgPSBuZXcgVHlwZSgndGFnOnlhbWwub3JnLDIwMDI6c3RyJywge1xuICBraW5kOiAnc2NhbGFyJyxcbiAgY29uc3RydWN0OiBmdW5jdGlvbiAoZGF0YSkgeyByZXR1cm4gZGF0YSAhPT0gbnVsbCA/IGRhdGEgOiAnJzsgfVxufSk7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBUeXBlID0gcmVxdWlyZSgnLi4vdHlwZScpO1xuXG52YXIgWUFNTF9EQVRFX1JFR0VYUCA9IG5ldyBSZWdFeHAoXG4gICdeKFswLTldWzAtOV1bMC05XVswLTldKScgICAgICAgICAgKyAvLyBbMV0geWVhclxuICAnLShbMC05XVswLTldKScgICAgICAgICAgICAgICAgICAgICsgLy8gWzJdIG1vbnRoXG4gICctKFswLTldWzAtOV0pJCcpOyAgICAgICAgICAgICAgICAgICAvLyBbM10gZGF5XG5cbnZhciBZQU1MX1RJTUVTVEFNUF9SRUdFWFAgPSBuZXcgUmVnRXhwKFxuICAnXihbMC05XVswLTldWzAtOV1bMC05XSknICAgICAgICAgICsgLy8gWzFdIHllYXJcbiAgJy0oWzAtOV1bMC05XT8pJyAgICAgICAgICAgICAgICAgICArIC8vIFsyXSBtb250aFxuICAnLShbMC05XVswLTldPyknICAgICAgICAgICAgICAgICAgICsgLy8gWzNdIGRheVxuICAnKD86W1R0XXxbIFxcXFx0XSspJyAgICAgICAgICAgICAgICAgKyAvLyAuLi5cbiAgJyhbMC05XVswLTldPyknICAgICAgICAgICAgICAgICAgICArIC8vIFs0XSBob3VyXG4gICc6KFswLTldWzAtOV0pJyAgICAgICAgICAgICAgICAgICAgKyAvLyBbNV0gbWludXRlXG4gICc6KFswLTldWzAtOV0pJyAgICAgICAgICAgICAgICAgICAgKyAvLyBbNl0gc2Vjb25kXG4gICcoPzpcXFxcLihbMC05XSopKT8nICAgICAgICAgICAgICAgICArIC8vIFs3XSBmcmFjdGlvblxuICAnKD86WyBcXFxcdF0qKFp8KFstK10pKFswLTldWzAtOV0/KScgKyAvLyBbOF0gdHogWzldIHR6X3NpZ24gWzEwXSB0el9ob3VyXG4gICcoPzo6KFswLTldWzAtOV0pKT8pKT8kJyk7ICAgICAgICAgICAvLyBbMTFdIHR6X21pbnV0ZVxuXG5mdW5jdGlvbiByZXNvbHZlWWFtbFRpbWVzdGFtcChkYXRhKSB7XG4gIGlmIChkYXRhID09PSBudWxsKSByZXR1cm4gZmFsc2U7XG4gIGlmIChZQU1MX0RBVEVfUkVHRVhQLmV4ZWMoZGF0YSkgIT09IG51bGwpIHJldHVybiB0cnVlO1xuICBpZiAoWUFNTF9USU1FU1RBTVBfUkVHRVhQLmV4ZWMoZGF0YSkgIT09IG51bGwpIHJldHVybiB0cnVlO1xuICByZXR1cm4gZmFsc2U7XG59XG5cbmZ1bmN0aW9uIGNvbnN0cnVjdFlhbWxUaW1lc3RhbXAoZGF0YSkge1xuICB2YXIgbWF0Y2gsIHllYXIsIG1vbnRoLCBkYXksIGhvdXIsIG1pbnV0ZSwgc2Vjb25kLCBmcmFjdGlvbiA9IDAsXG4gICAgICBkZWx0YSA9IG51bGwsIHR6X2hvdXIsIHR6X21pbnV0ZSwgZGF0ZTtcblxuICBtYXRjaCA9IFlBTUxfREFURV9SRUdFWFAuZXhlYyhkYXRhKTtcbiAgaWYgKG1hdGNoID09PSBudWxsKSBtYXRjaCA9IFlBTUxfVElNRVNUQU1QX1JFR0VYUC5leGVjKGRhdGEpO1xuXG4gIGlmIChtYXRjaCA9PT0gbnVsbCkgdGhyb3cgbmV3IEVycm9yKCdEYXRlIHJlc29sdmUgZXJyb3InKTtcblxuICAvLyBtYXRjaDogWzFdIHllYXIgWzJdIG1vbnRoIFszXSBkYXlcblxuICB5ZWFyID0gKyhtYXRjaFsxXSk7XG4gIG1vbnRoID0gKyhtYXRjaFsyXSkgLSAxOyAvLyBKUyBtb250aCBzdGFydHMgd2l0aCAwXG4gIGRheSA9ICsobWF0Y2hbM10pO1xuXG4gIGlmICghbWF0Y2hbNF0pIHsgLy8gbm8gaG91clxuICAgIHJldHVybiBuZXcgRGF0ZShEYXRlLlVUQyh5ZWFyLCBtb250aCwgZGF5KSk7XG4gIH1cblxuICAvLyBtYXRjaDogWzRdIGhvdXIgWzVdIG1pbnV0ZSBbNl0gc2Vjb25kIFs3XSBmcmFjdGlvblxuXG4gIGhvdXIgPSArKG1hdGNoWzRdKTtcbiAgbWludXRlID0gKyhtYXRjaFs1XSk7XG4gIHNlY29uZCA9ICsobWF0Y2hbNl0pO1xuXG4gIGlmIChtYXRjaFs3XSkge1xuICAgIGZyYWN0aW9uID0gbWF0Y2hbN10uc2xpY2UoMCwgMyk7XG4gICAgd2hpbGUgKGZyYWN0aW9uLmxlbmd0aCA8IDMpIHsgLy8gbWlsbGktc2Vjb25kc1xuICAgICAgZnJhY3Rpb24gKz0gJzAnO1xuICAgIH1cbiAgICBmcmFjdGlvbiA9ICtmcmFjdGlvbjtcbiAgfVxuXG4gIC8vIG1hdGNoOiBbOF0gdHogWzldIHR6X3NpZ24gWzEwXSB0el9ob3VyIFsxMV0gdHpfbWludXRlXG5cbiAgaWYgKG1hdGNoWzldKSB7XG4gICAgdHpfaG91ciA9ICsobWF0Y2hbMTBdKTtcbiAgICB0el9taW51dGUgPSArKG1hdGNoWzExXSB8fCAwKTtcbiAgICBkZWx0YSA9ICh0el9ob3VyICogNjAgKyB0el9taW51dGUpICogNjAwMDA7IC8vIGRlbHRhIGluIG1pbGktc2Vjb25kc1xuICAgIGlmIChtYXRjaFs5XSA9PT0gJy0nKSBkZWx0YSA9IC1kZWx0YTtcbiAgfVxuXG4gIGRhdGUgPSBuZXcgRGF0ZShEYXRlLlVUQyh5ZWFyLCBtb250aCwgZGF5LCBob3VyLCBtaW51dGUsIHNlY29uZCwgZnJhY3Rpb24pKTtcblxuICBpZiAoZGVsdGEpIGRhdGUuc2V0VGltZShkYXRlLmdldFRpbWUoKSAtIGRlbHRhKTtcblxuICByZXR1cm4gZGF0ZTtcbn1cblxuZnVuY3Rpb24gcmVwcmVzZW50WWFtbFRpbWVzdGFtcChvYmplY3QgLyosIHN0eWxlKi8pIHtcbiAgcmV0dXJuIG9iamVjdC50b0lTT1N0cmluZygpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IG5ldyBUeXBlKCd0YWc6eWFtbC5vcmcsMjAwMjp0aW1lc3RhbXAnLCB7XG4gIGtpbmQ6ICdzY2FsYXInLFxuICByZXNvbHZlOiByZXNvbHZlWWFtbFRpbWVzdGFtcCxcbiAgY29uc3RydWN0OiBjb25zdHJ1Y3RZYW1sVGltZXN0YW1wLFxuICBpbnN0YW5jZU9mOiBEYXRlLFxuICByZXByZXNlbnQ6IHJlcHJlc2VudFlhbWxUaW1lc3RhbXBcbn0pO1xuIiwidmFyIGJhc2VJbmRleE9mID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvYmFzZUluZGV4T2YnKSxcbiAgICBiaW5hcnlJbmRleCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2JpbmFyeUluZGV4Jyk7XG5cbi8qIE5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXg7XG5cbi8qKlxuICogR2V0cyB0aGUgaW5kZXggYXQgd2hpY2ggdGhlIGZpcnN0IG9jY3VycmVuY2Ugb2YgYHZhbHVlYCBpcyBmb3VuZCBpbiBgYXJyYXlgXG4gKiB1c2luZyBbYFNhbWVWYWx1ZVplcm9gXShodHRwOi8vZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi82LjAvI3NlYy1zYW1ldmFsdWV6ZXJvKVxuICogZm9yIGVxdWFsaXR5IGNvbXBhcmlzb25zLiBJZiBgZnJvbUluZGV4YCBpcyBuZWdhdGl2ZSwgaXQncyB1c2VkIGFzIHRoZSBvZmZzZXRcbiAqIGZyb20gdGhlIGVuZCBvZiBgYXJyYXlgLiBJZiBgYXJyYXlgIGlzIHNvcnRlZCBwcm92aWRpbmcgYHRydWVgIGZvciBgZnJvbUluZGV4YFxuICogcGVyZm9ybXMgYSBmYXN0ZXIgYmluYXJ5IHNlYXJjaC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IEFycmF5XG4gKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgdG8gc2VhcmNoLlxuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gc2VhcmNoIGZvci5cbiAqIEBwYXJhbSB7Ym9vbGVhbnxudW1iZXJ9IFtmcm9tSW5kZXg9MF0gVGhlIGluZGV4IHRvIHNlYXJjaCBmcm9tIG9yIGB0cnVlYFxuICogIHRvIHBlcmZvcm0gYSBiaW5hcnkgc2VhcmNoIG9uIGEgc29ydGVkIGFycmF5LlxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgaW5kZXggb2YgdGhlIG1hdGNoZWQgdmFsdWUsIGVsc2UgYC0xYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pbmRleE9mKFsxLCAyLCAxLCAyXSwgMik7XG4gKiAvLyA9PiAxXG4gKlxuICogLy8gdXNpbmcgYGZyb21JbmRleGBcbiAqIF8uaW5kZXhPZihbMSwgMiwgMSwgMl0sIDIsIDIpO1xuICogLy8gPT4gM1xuICpcbiAqIC8vIHBlcmZvcm1pbmcgYSBiaW5hcnkgc2VhcmNoXG4gKiBfLmluZGV4T2YoWzEsIDEsIDIsIDJdLCAyLCB0cnVlKTtcbiAqIC8vID0+IDJcbiAqL1xuZnVuY3Rpb24gaW5kZXhPZihhcnJheSwgdmFsdWUsIGZyb21JbmRleCkge1xuICB2YXIgbGVuZ3RoID0gYXJyYXkgPyBhcnJheS5sZW5ndGggOiAwO1xuICBpZiAoIWxlbmd0aCkge1xuICAgIHJldHVybiAtMTtcbiAgfVxuICBpZiAodHlwZW9mIGZyb21JbmRleCA9PSAnbnVtYmVyJykge1xuICAgIGZyb21JbmRleCA9IGZyb21JbmRleCA8IDAgPyBuYXRpdmVNYXgobGVuZ3RoICsgZnJvbUluZGV4LCAwKSA6IGZyb21JbmRleDtcbiAgfSBlbHNlIGlmIChmcm9tSW5kZXgpIHtcbiAgICB2YXIgaW5kZXggPSBiaW5hcnlJbmRleChhcnJheSwgdmFsdWUpO1xuICAgIGlmIChpbmRleCA8IGxlbmd0aCAmJlxuICAgICAgICAodmFsdWUgPT09IHZhbHVlID8gKHZhbHVlID09PSBhcnJheVtpbmRleF0pIDogKGFycmF5W2luZGV4XSAhPT0gYXJyYXlbaW5kZXhdKSkpIHtcbiAgICAgIHJldHVybiBpbmRleDtcbiAgICB9XG4gICAgcmV0dXJuIC0xO1xuICB9XG4gIHJldHVybiBiYXNlSW5kZXhPZihhcnJheSwgdmFsdWUsIGZyb21JbmRleCB8fCAwKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBpbmRleE9mO1xuIiwiLyoqXG4gKiBHZXRzIHRoZSBsYXN0IGVsZW1lbnQgb2YgYGFycmF5YC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IEFycmF5XG4gKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgdG8gcXVlcnkuXG4gKiBAcmV0dXJucyB7Kn0gUmV0dXJucyB0aGUgbGFzdCBlbGVtZW50IG9mIGBhcnJheWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8ubGFzdChbMSwgMiwgM10pO1xuICogLy8gPT4gM1xuICovXG5mdW5jdGlvbiBsYXN0KGFycmF5KSB7XG4gIHZhciBsZW5ndGggPSBhcnJheSA/IGFycmF5Lmxlbmd0aCA6IDA7XG4gIHJldHVybiBsZW5ndGggPyBhcnJheVtsZW5ndGggLSAxXSA6IHVuZGVmaW5lZDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBsYXN0O1xuIiwidmFyIExhenlXcmFwcGVyID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvTGF6eVdyYXBwZXInKSxcbiAgICBMb2Rhc2hXcmFwcGVyID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvTG9kYXNoV3JhcHBlcicpLFxuICAgIGJhc2VMb2Rhc2ggPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9iYXNlTG9kYXNoJyksXG4gICAgaXNBcnJheSA9IHJlcXVpcmUoJy4uL2xhbmcvaXNBcnJheScpLFxuICAgIGlzT2JqZWN0TGlrZSA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2lzT2JqZWN0TGlrZScpLFxuICAgIHdyYXBwZXJDbG9uZSA9IHJlcXVpcmUoJy4uL2ludGVybmFsL3dyYXBwZXJDbG9uZScpO1xuXG4vKiogVXNlZCBmb3IgbmF0aXZlIG1ldGhvZCByZWZlcmVuY2VzLiAqL1xudmFyIG9iamVjdFByb3RvID0gT2JqZWN0LnByb3RvdHlwZTtcblxuLyoqIFVzZWQgdG8gY2hlY2sgb2JqZWN0cyBmb3Igb3duIHByb3BlcnRpZXMuICovXG52YXIgaGFzT3duUHJvcGVydHkgPSBvYmplY3RQcm90by5oYXNPd25Qcm9wZXJ0eTtcblxuLyoqXG4gKiBDcmVhdGVzIGEgYGxvZGFzaGAgb2JqZWN0IHdoaWNoIHdyYXBzIGB2YWx1ZWAgdG8gZW5hYmxlIGltcGxpY2l0IGNoYWluaW5nLlxuICogTWV0aG9kcyB0aGF0IG9wZXJhdGUgb24gYW5kIHJldHVybiBhcnJheXMsIGNvbGxlY3Rpb25zLCBhbmQgZnVuY3Rpb25zIGNhblxuICogYmUgY2hhaW5lZCB0b2dldGhlci4gTWV0aG9kcyB0aGF0IHJldHJpZXZlIGEgc2luZ2xlIHZhbHVlIG9yIG1heSByZXR1cm4gYVxuICogcHJpbWl0aXZlIHZhbHVlIHdpbGwgYXV0b21hdGljYWxseSBlbmQgdGhlIGNoYWluIHJldHVybmluZyB0aGUgdW53cmFwcGVkXG4gKiB2YWx1ZS4gRXhwbGljaXQgY2hhaW5pbmcgbWF5IGJlIGVuYWJsZWQgdXNpbmcgYF8uY2hhaW5gLiBUaGUgZXhlY3V0aW9uIG9mXG4gKiBjaGFpbmVkIG1ldGhvZHMgaXMgbGF6eSwgdGhhdCBpcywgZXhlY3V0aW9uIGlzIGRlZmVycmVkIHVudGlsIGBfI3ZhbHVlYFxuICogaXMgaW1wbGljaXRseSBvciBleHBsaWNpdGx5IGNhbGxlZC5cbiAqXG4gKiBMYXp5IGV2YWx1YXRpb24gYWxsb3dzIHNldmVyYWwgbWV0aG9kcyB0byBzdXBwb3J0IHNob3J0Y3V0IGZ1c2lvbi4gU2hvcnRjdXRcbiAqIGZ1c2lvbiBpcyBhbiBvcHRpbWl6YXRpb24gc3RyYXRlZ3kgd2hpY2ggbWVyZ2UgaXRlcmF0ZWUgY2FsbHM7IHRoaXMgY2FuIGhlbHBcbiAqIHRvIGF2b2lkIHRoZSBjcmVhdGlvbiBvZiBpbnRlcm1lZGlhdGUgZGF0YSBzdHJ1Y3R1cmVzIGFuZCBncmVhdGx5IHJlZHVjZSB0aGVcbiAqIG51bWJlciBvZiBpdGVyYXRlZSBleGVjdXRpb25zLlxuICpcbiAqIENoYWluaW5nIGlzIHN1cHBvcnRlZCBpbiBjdXN0b20gYnVpbGRzIGFzIGxvbmcgYXMgdGhlIGBfI3ZhbHVlYCBtZXRob2QgaXNcbiAqIGRpcmVjdGx5IG9yIGluZGlyZWN0bHkgaW5jbHVkZWQgaW4gdGhlIGJ1aWxkLlxuICpcbiAqIEluIGFkZGl0aW9uIHRvIGxvZGFzaCBtZXRob2RzLCB3cmFwcGVycyBoYXZlIGBBcnJheWAgYW5kIGBTdHJpbmdgIG1ldGhvZHMuXG4gKlxuICogVGhlIHdyYXBwZXIgYEFycmF5YCBtZXRob2RzIGFyZTpcbiAqIGBjb25jYXRgLCBgam9pbmAsIGBwb3BgLCBgcHVzaGAsIGByZXZlcnNlYCwgYHNoaWZ0YCwgYHNsaWNlYCwgYHNvcnRgLFxuICogYHNwbGljZWAsIGFuZCBgdW5zaGlmdGBcbiAqXG4gKiBUaGUgd3JhcHBlciBgU3RyaW5nYCBtZXRob2RzIGFyZTpcbiAqIGByZXBsYWNlYCBhbmQgYHNwbGl0YFxuICpcbiAqIFRoZSB3cmFwcGVyIG1ldGhvZHMgdGhhdCBzdXBwb3J0IHNob3J0Y3V0IGZ1c2lvbiBhcmU6XG4gKiBgY29tcGFjdGAsIGBkcm9wYCwgYGRyb3BSaWdodGAsIGBkcm9wUmlnaHRXaGlsZWAsIGBkcm9wV2hpbGVgLCBgZmlsdGVyYCxcbiAqIGBmaXJzdGAsIGBpbml0aWFsYCwgYGxhc3RgLCBgbWFwYCwgYHBsdWNrYCwgYHJlamVjdGAsIGByZXN0YCwgYHJldmVyc2VgLFxuICogYHNsaWNlYCwgYHRha2VgLCBgdGFrZVJpZ2h0YCwgYHRha2VSaWdodFdoaWxlYCwgYHRha2VXaGlsZWAsIGB0b0FycmF5YCxcbiAqIGFuZCBgd2hlcmVgXG4gKlxuICogVGhlIGNoYWluYWJsZSB3cmFwcGVyIG1ldGhvZHMgYXJlOlxuICogYGFmdGVyYCwgYGFyeWAsIGBhc3NpZ25gLCBgYXRgLCBgYmVmb3JlYCwgYGJpbmRgLCBgYmluZEFsbGAsIGBiaW5kS2V5YCxcbiAqIGBjYWxsYmFja2AsIGBjaGFpbmAsIGBjaHVua2AsIGBjb21taXRgLCBgY29tcGFjdGAsIGBjb25jYXRgLCBgY29uc3RhbnRgLFxuICogYGNvdW50QnlgLCBgY3JlYXRlYCwgYGN1cnJ5YCwgYGRlYm91bmNlYCwgYGRlZmF1bHRzYCwgYGRlZmF1bHRzRGVlcGAsXG4gKiBgZGVmZXJgLCBgZGVsYXlgLCBgZGlmZmVyZW5jZWAsIGBkcm9wYCwgYGRyb3BSaWdodGAsIGBkcm9wUmlnaHRXaGlsZWAsXG4gKiBgZHJvcFdoaWxlYCwgYGZpbGxgLCBgZmlsdGVyYCwgYGZsYXR0ZW5gLCBgZmxhdHRlbkRlZXBgLCBgZmxvd2AsIGBmbG93UmlnaHRgLFxuICogYGZvckVhY2hgLCBgZm9yRWFjaFJpZ2h0YCwgYGZvckluYCwgYGZvckluUmlnaHRgLCBgZm9yT3duYCwgYGZvck93blJpZ2h0YCxcbiAqIGBmdW5jdGlvbnNgLCBgZ3JvdXBCeWAsIGBpbmRleEJ5YCwgYGluaXRpYWxgLCBgaW50ZXJzZWN0aW9uYCwgYGludmVydGAsXG4gKiBgaW52b2tlYCwgYGtleXNgLCBga2V5c0luYCwgYG1hcGAsIGBtYXBLZXlzYCwgYG1hcFZhbHVlc2AsIGBtYXRjaGVzYCxcbiAqIGBtYXRjaGVzUHJvcGVydHlgLCBgbWVtb2l6ZWAsIGBtZXJnZWAsIGBtZXRob2RgLCBgbWV0aG9kT2ZgLCBgbWl4aW5gLFxuICogYG1vZEFyZ3NgLCBgbmVnYXRlYCwgYG9taXRgLCBgb25jZWAsIGBwYWlyc2AsIGBwYXJ0aWFsYCwgYHBhcnRpYWxSaWdodGAsXG4gKiBgcGFydGl0aW9uYCwgYHBpY2tgLCBgcGxhbnRgLCBgcGx1Y2tgLCBgcHJvcGVydHlgLCBgcHJvcGVydHlPZmAsIGBwdWxsYCxcbiAqIGBwdWxsQXRgLCBgcHVzaGAsIGByYW5nZWAsIGByZWFyZ2AsIGByZWplY3RgLCBgcmVtb3ZlYCwgYHJlc3RgLCBgcmVzdFBhcmFtYCxcbiAqIGByZXZlcnNlYCwgYHNldGAsIGBzaHVmZmxlYCwgYHNsaWNlYCwgYHNvcnRgLCBgc29ydEJ5YCwgYHNvcnRCeUFsbGAsXG4gKiBgc29ydEJ5T3JkZXJgLCBgc3BsaWNlYCwgYHNwcmVhZGAsIGB0YWtlYCwgYHRha2VSaWdodGAsIGB0YWtlUmlnaHRXaGlsZWAsXG4gKiBgdGFrZVdoaWxlYCwgYHRhcGAsIGB0aHJvdHRsZWAsIGB0aHJ1YCwgYHRpbWVzYCwgYHRvQXJyYXlgLCBgdG9QbGFpbk9iamVjdGAsXG4gKiBgdHJhbnNmb3JtYCwgYHVuaW9uYCwgYHVuaXFgLCBgdW5zaGlmdGAsIGB1bnppcGAsIGB1bnppcFdpdGhgLCBgdmFsdWVzYCxcbiAqIGB2YWx1ZXNJbmAsIGB3aGVyZWAsIGB3aXRob3V0YCwgYHdyYXBgLCBgeG9yYCwgYHppcGAsIGB6aXBPYmplY3RgLCBgemlwV2l0aGBcbiAqXG4gKiBUaGUgd3JhcHBlciBtZXRob2RzIHRoYXQgYXJlICoqbm90KiogY2hhaW5hYmxlIGJ5IGRlZmF1bHQgYXJlOlxuICogYGFkZGAsIGBhdHRlbXB0YCwgYGNhbWVsQ2FzZWAsIGBjYXBpdGFsaXplYCwgYGNlaWxgLCBgY2xvbmVgLCBgY2xvbmVEZWVwYCxcbiAqIGBkZWJ1cnJgLCBgZW5kc1dpdGhgLCBgZXNjYXBlYCwgYGVzY2FwZVJlZ0V4cGAsIGBldmVyeWAsIGBmaW5kYCwgYGZpbmRJbmRleGAsXG4gKiBgZmluZEtleWAsIGBmaW5kTGFzdGAsIGBmaW5kTGFzdEluZGV4YCwgYGZpbmRMYXN0S2V5YCwgYGZpbmRXaGVyZWAsIGBmaXJzdGAsXG4gKiBgZmxvb3JgLCBgZ2V0YCwgYGd0YCwgYGd0ZWAsIGBoYXNgLCBgaWRlbnRpdHlgLCBgaW5jbHVkZXNgLCBgaW5kZXhPZmAsXG4gKiBgaW5SYW5nZWAsIGBpc0FyZ3VtZW50c2AsIGBpc0FycmF5YCwgYGlzQm9vbGVhbmAsIGBpc0RhdGVgLCBgaXNFbGVtZW50YCxcbiAqIGBpc0VtcHR5YCwgYGlzRXF1YWxgLCBgaXNFcnJvcmAsIGBpc0Zpbml0ZWAgYGlzRnVuY3Rpb25gLCBgaXNNYXRjaGAsXG4gKiBgaXNOYXRpdmVgLCBgaXNOYU5gLCBgaXNOdWxsYCwgYGlzTnVtYmVyYCwgYGlzT2JqZWN0YCwgYGlzUGxhaW5PYmplY3RgLFxuICogYGlzUmVnRXhwYCwgYGlzU3RyaW5nYCwgYGlzVW5kZWZpbmVkYCwgYGlzVHlwZWRBcnJheWAsIGBqb2luYCwgYGtlYmFiQ2FzZWAsXG4gKiBgbGFzdGAsIGBsYXN0SW5kZXhPZmAsIGBsdGAsIGBsdGVgLCBgbWF4YCwgYG1pbmAsIGBub0NvbmZsaWN0YCwgYG5vb3BgLFxuICogYG5vd2AsIGBwYWRgLCBgcGFkTGVmdGAsIGBwYWRSaWdodGAsIGBwYXJzZUludGAsIGBwb3BgLCBgcmFuZG9tYCwgYHJlZHVjZWAsXG4gKiBgcmVkdWNlUmlnaHRgLCBgcmVwZWF0YCwgYHJlc3VsdGAsIGByb3VuZGAsIGBydW5JbkNvbnRleHRgLCBgc2hpZnRgLCBgc2l6ZWAsXG4gKiBgc25ha2VDYXNlYCwgYHNvbWVgLCBgc29ydGVkSW5kZXhgLCBgc29ydGVkTGFzdEluZGV4YCwgYHN0YXJ0Q2FzZWAsXG4gKiBgc3RhcnRzV2l0aGAsIGBzdW1gLCBgdGVtcGxhdGVgLCBgdHJpbWAsIGB0cmltTGVmdGAsIGB0cmltUmlnaHRgLCBgdHJ1bmNgLFxuICogYHVuZXNjYXBlYCwgYHVuaXF1ZUlkYCwgYHZhbHVlYCwgYW5kIGB3b3Jkc2BcbiAqXG4gKiBUaGUgd3JhcHBlciBtZXRob2QgYHNhbXBsZWAgd2lsbCByZXR1cm4gYSB3cmFwcGVkIHZhbHVlIHdoZW4gYG5gIGlzIHByb3ZpZGVkLFxuICogb3RoZXJ3aXNlIGFuIHVud3JhcHBlZCB2YWx1ZSBpcyByZXR1cm5lZC5cbiAqXG4gKiBAbmFtZSBfXG4gKiBAY29uc3RydWN0b3JcbiAqIEBjYXRlZ29yeSBDaGFpblxuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gd3JhcCBpbiBhIGBsb2Rhc2hgIGluc3RhbmNlLlxuICogQHJldHVybnMge09iamVjdH0gUmV0dXJucyB0aGUgbmV3IGBsb2Rhc2hgIHdyYXBwZXIgaW5zdGFuY2UuXG4gKiBAZXhhbXBsZVxuICpcbiAqIHZhciB3cmFwcGVkID0gXyhbMSwgMiwgM10pO1xuICpcbiAqIC8vIHJldHVybnMgYW4gdW53cmFwcGVkIHZhbHVlXG4gKiB3cmFwcGVkLnJlZHVjZShmdW5jdGlvbih0b3RhbCwgbikge1xuICogICByZXR1cm4gdG90YWwgKyBuO1xuICogfSk7XG4gKiAvLyA9PiA2XG4gKlxuICogLy8gcmV0dXJucyBhIHdyYXBwZWQgdmFsdWVcbiAqIHZhciBzcXVhcmVzID0gd3JhcHBlZC5tYXAoZnVuY3Rpb24obikge1xuICogICByZXR1cm4gbiAqIG47XG4gKiB9KTtcbiAqXG4gKiBfLmlzQXJyYXkoc3F1YXJlcyk7XG4gKiAvLyA9PiBmYWxzZVxuICpcbiAqIF8uaXNBcnJheShzcXVhcmVzLnZhbHVlKCkpO1xuICogLy8gPT4gdHJ1ZVxuICovXG5mdW5jdGlvbiBsb2Rhc2godmFsdWUpIHtcbiAgaWYgKGlzT2JqZWN0TGlrZSh2YWx1ZSkgJiYgIWlzQXJyYXkodmFsdWUpICYmICEodmFsdWUgaW5zdGFuY2VvZiBMYXp5V3JhcHBlcikpIHtcbiAgICBpZiAodmFsdWUgaW5zdGFuY2VvZiBMb2Rhc2hXcmFwcGVyKSB7XG4gICAgICByZXR1cm4gdmFsdWU7XG4gICAgfVxuICAgIGlmIChoYXNPd25Qcm9wZXJ0eS5jYWxsKHZhbHVlLCAnX19jaGFpbl9fJykgJiYgaGFzT3duUHJvcGVydHkuY2FsbCh2YWx1ZSwgJ19fd3JhcHBlZF9fJykpIHtcbiAgICAgIHJldHVybiB3cmFwcGVyQ2xvbmUodmFsdWUpO1xuICAgIH1cbiAgfVxuICByZXR1cm4gbmV3IExvZGFzaFdyYXBwZXIodmFsdWUpO1xufVxuXG4vLyBFbnN1cmUgd3JhcHBlcnMgYXJlIGluc3RhbmNlcyBvZiBgYmFzZUxvZGFzaGAuXG5sb2Rhc2gucHJvdG90eXBlID0gYmFzZUxvZGFzaC5wcm90b3R5cGU7XG5cbm1vZHVsZS5leHBvcnRzID0gbG9kYXNoO1xuIiwibW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2ZvckVhY2gnKTtcbiIsInZhciBiYXNlRWFjaCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2Jhc2VFYWNoJyksXG4gICAgY3JlYXRlRmluZCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2NyZWF0ZUZpbmQnKTtcblxuLyoqXG4gKiBJdGVyYXRlcyBvdmVyIGVsZW1lbnRzIG9mIGBjb2xsZWN0aW9uYCwgcmV0dXJuaW5nIHRoZSBmaXJzdCBlbGVtZW50XG4gKiBgcHJlZGljYXRlYCByZXR1cm5zIHRydXRoeSBmb3IuIFRoZSBwcmVkaWNhdGUgaXMgYm91bmQgdG8gYHRoaXNBcmdgIGFuZFxuICogaW52b2tlZCB3aXRoIHRocmVlIGFyZ3VtZW50czogKHZhbHVlLCBpbmRleHxrZXksIGNvbGxlY3Rpb24pLlxuICpcbiAqIElmIGEgcHJvcGVydHkgbmFtZSBpcyBwcm92aWRlZCBmb3IgYHByZWRpY2F0ZWAgdGhlIGNyZWF0ZWQgYF8ucHJvcGVydHlgXG4gKiBzdHlsZSBjYWxsYmFjayByZXR1cm5zIHRoZSBwcm9wZXJ0eSB2YWx1ZSBvZiB0aGUgZ2l2ZW4gZWxlbWVudC5cbiAqXG4gKiBJZiBhIHZhbHVlIGlzIGFsc28gcHJvdmlkZWQgZm9yIGB0aGlzQXJnYCB0aGUgY3JlYXRlZCBgXy5tYXRjaGVzUHJvcGVydHlgXG4gKiBzdHlsZSBjYWxsYmFjayByZXR1cm5zIGB0cnVlYCBmb3IgZWxlbWVudHMgdGhhdCBoYXZlIGEgbWF0Y2hpbmcgcHJvcGVydHlcbiAqIHZhbHVlLCBlbHNlIGBmYWxzZWAuXG4gKlxuICogSWYgYW4gb2JqZWN0IGlzIHByb3ZpZGVkIGZvciBgcHJlZGljYXRlYCB0aGUgY3JlYXRlZCBgXy5tYXRjaGVzYCBzdHlsZVxuICogY2FsbGJhY2sgcmV0dXJucyBgdHJ1ZWAgZm9yIGVsZW1lbnRzIHRoYXQgaGF2ZSB0aGUgcHJvcGVydGllcyBvZiB0aGUgZ2l2ZW5cbiAqIG9iamVjdCwgZWxzZSBgZmFsc2VgLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAYWxpYXMgZGV0ZWN0XG4gKiBAY2F0ZWdvcnkgQ29sbGVjdGlvblxuICogQHBhcmFtIHtBcnJheXxPYmplY3R8c3RyaW5nfSBjb2xsZWN0aW9uIFRoZSBjb2xsZWN0aW9uIHRvIHNlYXJjaC5cbiAqIEBwYXJhbSB7RnVuY3Rpb258T2JqZWN0fHN0cmluZ30gW3ByZWRpY2F0ZT1fLmlkZW50aXR5XSBUaGUgZnVuY3Rpb24gaW52b2tlZFxuICogIHBlciBpdGVyYXRpb24uXG4gKiBAcGFyYW0geyp9IFt0aGlzQXJnXSBUaGUgYHRoaXNgIGJpbmRpbmcgb2YgYHByZWRpY2F0ZWAuXG4gKiBAcmV0dXJucyB7Kn0gUmV0dXJucyB0aGUgbWF0Y2hlZCBlbGVtZW50LCBlbHNlIGB1bmRlZmluZWRgLlxuICogQGV4YW1wbGVcbiAqXG4gKiB2YXIgdXNlcnMgPSBbXG4gKiAgIHsgJ3VzZXInOiAnYmFybmV5JywgICdhZ2UnOiAzNiwgJ2FjdGl2ZSc6IHRydWUgfSxcbiAqICAgeyAndXNlcic6ICdmcmVkJywgICAgJ2FnZSc6IDQwLCAnYWN0aXZlJzogZmFsc2UgfSxcbiAqICAgeyAndXNlcic6ICdwZWJibGVzJywgJ2FnZSc6IDEsICAnYWN0aXZlJzogdHJ1ZSB9XG4gKiBdO1xuICpcbiAqIF8ucmVzdWx0KF8uZmluZCh1c2VycywgZnVuY3Rpb24oY2hyKSB7XG4gKiAgIHJldHVybiBjaHIuYWdlIDwgNDA7XG4gKiB9KSwgJ3VzZXInKTtcbiAqIC8vID0+ICdiYXJuZXknXG4gKlxuICogLy8gdXNpbmcgdGhlIGBfLm1hdGNoZXNgIGNhbGxiYWNrIHNob3J0aGFuZFxuICogXy5yZXN1bHQoXy5maW5kKHVzZXJzLCB7ICdhZ2UnOiAxLCAnYWN0aXZlJzogdHJ1ZSB9KSwgJ3VzZXInKTtcbiAqIC8vID0+ICdwZWJibGVzJ1xuICpcbiAqIC8vIHVzaW5nIHRoZSBgXy5tYXRjaGVzUHJvcGVydHlgIGNhbGxiYWNrIHNob3J0aGFuZFxuICogXy5yZXN1bHQoXy5maW5kKHVzZXJzLCAnYWN0aXZlJywgZmFsc2UpLCAndXNlcicpO1xuICogLy8gPT4gJ2ZyZWQnXG4gKlxuICogLy8gdXNpbmcgdGhlIGBfLnByb3BlcnR5YCBjYWxsYmFjayBzaG9ydGhhbmRcbiAqIF8ucmVzdWx0KF8uZmluZCh1c2VycywgJ2FjdGl2ZScpLCAndXNlcicpO1xuICogLy8gPT4gJ2Jhcm5leSdcbiAqL1xudmFyIGZpbmQgPSBjcmVhdGVGaW5kKGJhc2VFYWNoKTtcblxubW9kdWxlLmV4cG9ydHMgPSBmaW5kO1xuIiwidmFyIGFycmF5RWFjaCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2FycmF5RWFjaCcpLFxuICAgIGJhc2VFYWNoID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvYmFzZUVhY2gnKSxcbiAgICBjcmVhdGVGb3JFYWNoID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvY3JlYXRlRm9yRWFjaCcpO1xuXG4vKipcbiAqIEl0ZXJhdGVzIG92ZXIgZWxlbWVudHMgb2YgYGNvbGxlY3Rpb25gIGludm9raW5nIGBpdGVyYXRlZWAgZm9yIGVhY2ggZWxlbWVudC5cbiAqIFRoZSBgaXRlcmF0ZWVgIGlzIGJvdW5kIHRvIGB0aGlzQXJnYCBhbmQgaW52b2tlZCB3aXRoIHRocmVlIGFyZ3VtZW50czpcbiAqICh2YWx1ZSwgaW5kZXh8a2V5LCBjb2xsZWN0aW9uKS4gSXRlcmF0ZWUgZnVuY3Rpb25zIG1heSBleGl0IGl0ZXJhdGlvbiBlYXJseVxuICogYnkgZXhwbGljaXRseSByZXR1cm5pbmcgYGZhbHNlYC5cbiAqXG4gKiAqKk5vdGU6KiogQXMgd2l0aCBvdGhlciBcIkNvbGxlY3Rpb25zXCIgbWV0aG9kcywgb2JqZWN0cyB3aXRoIGEgXCJsZW5ndGhcIiBwcm9wZXJ0eVxuICogYXJlIGl0ZXJhdGVkIGxpa2UgYXJyYXlzLiBUbyBhdm9pZCB0aGlzIGJlaGF2aW9yIGBfLmZvckluYCBvciBgXy5mb3JPd25gXG4gKiBtYXkgYmUgdXNlZCBmb3Igb2JqZWN0IGl0ZXJhdGlvbi5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGFsaWFzIGVhY2hcbiAqIEBjYXRlZ29yeSBDb2xsZWN0aW9uXG4gKiBAcGFyYW0ge0FycmF5fE9iamVjdHxzdHJpbmd9IGNvbGxlY3Rpb24gVGhlIGNvbGxlY3Rpb24gdG8gaXRlcmF0ZSBvdmVyLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gW2l0ZXJhdGVlPV8uaWRlbnRpdHldIFRoZSBmdW5jdGlvbiBpbnZva2VkIHBlciBpdGVyYXRpb24uXG4gKiBAcGFyYW0geyp9IFt0aGlzQXJnXSBUaGUgYHRoaXNgIGJpbmRpbmcgb2YgYGl0ZXJhdGVlYC5cbiAqIEByZXR1cm5zIHtBcnJheXxPYmplY3R8c3RyaW5nfSBSZXR1cm5zIGBjb2xsZWN0aW9uYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXyhbMSwgMl0pLmZvckVhY2goZnVuY3Rpb24obikge1xuICogICBjb25zb2xlLmxvZyhuKTtcbiAqIH0pLnZhbHVlKCk7XG4gKiAvLyA9PiBsb2dzIGVhY2ggdmFsdWUgZnJvbSBsZWZ0IHRvIHJpZ2h0IGFuZCByZXR1cm5zIHRoZSBhcnJheVxuICpcbiAqIF8uZm9yRWFjaCh7ICdhJzogMSwgJ2InOiAyIH0sIGZ1bmN0aW9uKG4sIGtleSkge1xuICogICBjb25zb2xlLmxvZyhuLCBrZXkpO1xuICogfSk7XG4gKiAvLyA9PiBsb2dzIGVhY2ggdmFsdWUta2V5IHBhaXIgYW5kIHJldHVybnMgdGhlIG9iamVjdCAoaXRlcmF0aW9uIG9yZGVyIGlzIG5vdCBndWFyYW50ZWVkKVxuICovXG52YXIgZm9yRWFjaCA9IGNyZWF0ZUZvckVhY2goYXJyYXlFYWNoLCBiYXNlRWFjaCk7XG5cbm1vZHVsZS5leHBvcnRzID0gZm9yRWFjaDtcbiIsInZhciBiYXNlSW5kZXhPZiA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2Jhc2VJbmRleE9mJyksXG4gICAgZ2V0TGVuZ3RoID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvZ2V0TGVuZ3RoJyksXG4gICAgaXNBcnJheSA9IHJlcXVpcmUoJy4uL2xhbmcvaXNBcnJheScpLFxuICAgIGlzSXRlcmF0ZWVDYWxsID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvaXNJdGVyYXRlZUNhbGwnKSxcbiAgICBpc0xlbmd0aCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2lzTGVuZ3RoJyksXG4gICAgaXNTdHJpbmcgPSByZXF1aXJlKCcuLi9sYW5nL2lzU3RyaW5nJyksXG4gICAgdmFsdWVzID0gcmVxdWlyZSgnLi4vb2JqZWN0L3ZhbHVlcycpO1xuXG4vKiBOYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMgZm9yIHRob3NlIHdpdGggdGhlIHNhbWUgbmFtZSBhcyBvdGhlciBgbG9kYXNoYCBtZXRob2RzLiAqL1xudmFyIG5hdGl2ZU1heCA9IE1hdGgubWF4O1xuXG4vKipcbiAqIENoZWNrcyBpZiBgdGFyZ2V0YCBpcyBpbiBgY29sbGVjdGlvbmAgdXNpbmdcbiAqIFtgU2FtZVZhbHVlWmVyb2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzYuMC8jc2VjLXNhbWV2YWx1ZXplcm8pXG4gKiBmb3IgZXF1YWxpdHkgY29tcGFyaXNvbnMuIElmIGBmcm9tSW5kZXhgIGlzIG5lZ2F0aXZlLCBpdCdzIHVzZWQgYXMgdGhlIG9mZnNldFxuICogZnJvbSB0aGUgZW5kIG9mIGBjb2xsZWN0aW9uYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGFsaWFzIGNvbnRhaW5zLCBpbmNsdWRlXG4gKiBAY2F0ZWdvcnkgQ29sbGVjdGlvblxuICogQHBhcmFtIHtBcnJheXxPYmplY3R8c3RyaW5nfSBjb2xsZWN0aW9uIFRoZSBjb2xsZWN0aW9uIHRvIHNlYXJjaC5cbiAqIEBwYXJhbSB7Kn0gdGFyZ2V0IFRoZSB2YWx1ZSB0byBzZWFyY2ggZm9yLlxuICogQHBhcmFtIHtudW1iZXJ9IFtmcm9tSW5kZXg9MF0gVGhlIGluZGV4IHRvIHNlYXJjaCBmcm9tLlxuICogQHBhcmFtLSB7T2JqZWN0fSBbZ3VhcmRdIEVuYWJsZXMgdXNlIGFzIGEgY2FsbGJhY2sgZm9yIGZ1bmN0aW9ucyBsaWtlIGBfLnJlZHVjZWAuXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYSBtYXRjaGluZyBlbGVtZW50IGlzIGZvdW5kLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaW5jbHVkZXMoWzEsIDIsIDNdLCAxKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmluY2x1ZGVzKFsxLCAyLCAzXSwgMSwgMik7XG4gKiAvLyA9PiBmYWxzZVxuICpcbiAqIF8uaW5jbHVkZXMoeyAndXNlcic6ICdmcmVkJywgJ2FnZSc6IDQwIH0sICdmcmVkJyk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pbmNsdWRlcygncGViYmxlcycsICdlYicpO1xuICogLy8gPT4gdHJ1ZVxuICovXG5mdW5jdGlvbiBpbmNsdWRlcyhjb2xsZWN0aW9uLCB0YXJnZXQsIGZyb21JbmRleCwgZ3VhcmQpIHtcbiAgdmFyIGxlbmd0aCA9IGNvbGxlY3Rpb24gPyBnZXRMZW5ndGgoY29sbGVjdGlvbikgOiAwO1xuICBpZiAoIWlzTGVuZ3RoKGxlbmd0aCkpIHtcbiAgICBjb2xsZWN0aW9uID0gdmFsdWVzKGNvbGxlY3Rpb24pO1xuICAgIGxlbmd0aCA9IGNvbGxlY3Rpb24ubGVuZ3RoO1xuICB9XG4gIGlmICh0eXBlb2YgZnJvbUluZGV4ICE9ICdudW1iZXInIHx8IChndWFyZCAmJiBpc0l0ZXJhdGVlQ2FsbCh0YXJnZXQsIGZyb21JbmRleCwgZ3VhcmQpKSkge1xuICAgIGZyb21JbmRleCA9IDA7XG4gIH0gZWxzZSB7XG4gICAgZnJvbUluZGV4ID0gZnJvbUluZGV4IDwgMCA/IG5hdGl2ZU1heChsZW5ndGggKyBmcm9tSW5kZXgsIDApIDogKGZyb21JbmRleCB8fCAwKTtcbiAgfVxuICByZXR1cm4gKHR5cGVvZiBjb2xsZWN0aW9uID09ICdzdHJpbmcnIHx8ICFpc0FycmF5KGNvbGxlY3Rpb24pICYmIGlzU3RyaW5nKGNvbGxlY3Rpb24pKVxuICAgID8gKGZyb21JbmRleCA8PSBsZW5ndGggJiYgY29sbGVjdGlvbi5pbmRleE9mKHRhcmdldCwgZnJvbUluZGV4KSA+IC0xKVxuICAgIDogKCEhbGVuZ3RoICYmIGJhc2VJbmRleE9mKGNvbGxlY3Rpb24sIHRhcmdldCwgZnJvbUluZGV4KSA+IC0xKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBpbmNsdWRlcztcbiIsInZhciBhcnJheU1hcCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2FycmF5TWFwJyksXG4gICAgYmFzZUNhbGxiYWNrID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvYmFzZUNhbGxiYWNrJyksXG4gICAgYmFzZU1hcCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2Jhc2VNYXAnKSxcbiAgICBpc0FycmF5ID0gcmVxdWlyZSgnLi4vbGFuZy9pc0FycmF5Jyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhbiBhcnJheSBvZiB2YWx1ZXMgYnkgcnVubmluZyBlYWNoIGVsZW1lbnQgaW4gYGNvbGxlY3Rpb25gIHRocm91Z2hcbiAqIGBpdGVyYXRlZWAuIFRoZSBgaXRlcmF0ZWVgIGlzIGJvdW5kIHRvIGB0aGlzQXJnYCBhbmQgaW52b2tlZCB3aXRoIHRocmVlXG4gKiBhcmd1bWVudHM6ICh2YWx1ZSwgaW5kZXh8a2V5LCBjb2xsZWN0aW9uKS5cbiAqXG4gKiBJZiBhIHByb3BlcnR5IG5hbWUgaXMgcHJvdmlkZWQgZm9yIGBpdGVyYXRlZWAgdGhlIGNyZWF0ZWQgYF8ucHJvcGVydHlgXG4gKiBzdHlsZSBjYWxsYmFjayByZXR1cm5zIHRoZSBwcm9wZXJ0eSB2YWx1ZSBvZiB0aGUgZ2l2ZW4gZWxlbWVudC5cbiAqXG4gKiBJZiBhIHZhbHVlIGlzIGFsc28gcHJvdmlkZWQgZm9yIGB0aGlzQXJnYCB0aGUgY3JlYXRlZCBgXy5tYXRjaGVzUHJvcGVydHlgXG4gKiBzdHlsZSBjYWxsYmFjayByZXR1cm5zIGB0cnVlYCBmb3IgZWxlbWVudHMgdGhhdCBoYXZlIGEgbWF0Y2hpbmcgcHJvcGVydHlcbiAqIHZhbHVlLCBlbHNlIGBmYWxzZWAuXG4gKlxuICogSWYgYW4gb2JqZWN0IGlzIHByb3ZpZGVkIGZvciBgaXRlcmF0ZWVgIHRoZSBjcmVhdGVkIGBfLm1hdGNoZXNgIHN0eWxlXG4gKiBjYWxsYmFjayByZXR1cm5zIGB0cnVlYCBmb3IgZWxlbWVudHMgdGhhdCBoYXZlIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBnaXZlblxuICogb2JqZWN0LCBlbHNlIGBmYWxzZWAuXG4gKlxuICogTWFueSBsb2Rhc2ggbWV0aG9kcyBhcmUgZ3VhcmRlZCB0byB3b3JrIGFzIGl0ZXJhdGVlcyBmb3IgbWV0aG9kcyBsaWtlXG4gKiBgXy5ldmVyeWAsIGBfLmZpbHRlcmAsIGBfLm1hcGAsIGBfLm1hcFZhbHVlc2AsIGBfLnJlamVjdGAsIGFuZCBgXy5zb21lYC5cbiAqXG4gKiBUaGUgZ3VhcmRlZCBtZXRob2RzIGFyZTpcbiAqIGBhcnlgLCBgY2FsbGJhY2tgLCBgY2h1bmtgLCBgY2xvbmVgLCBgY3JlYXRlYCwgYGN1cnJ5YCwgYGN1cnJ5UmlnaHRgLFxuICogYGRyb3BgLCBgZHJvcFJpZ2h0YCwgYGV2ZXJ5YCwgYGZpbGxgLCBgZmxhdHRlbmAsIGBpbnZlcnRgLCBgbWF4YCwgYG1pbmAsXG4gKiBgcGFyc2VJbnRgLCBgc2xpY2VgLCBgc29ydEJ5YCwgYHRha2VgLCBgdGFrZVJpZ2h0YCwgYHRlbXBsYXRlYCwgYHRyaW1gLFxuICogYHRyaW1MZWZ0YCwgYHRyaW1SaWdodGAsIGB0cnVuY2AsIGByYW5kb21gLCBgcmFuZ2VgLCBgc2FtcGxlYCwgYHNvbWVgLFxuICogYHN1bWAsIGB1bmlxYCwgYW5kIGB3b3Jkc2BcbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGFsaWFzIGNvbGxlY3RcbiAqIEBjYXRlZ29yeSBDb2xsZWN0aW9uXG4gKiBAcGFyYW0ge0FycmF5fE9iamVjdHxzdHJpbmd9IGNvbGxlY3Rpb24gVGhlIGNvbGxlY3Rpb24gdG8gaXRlcmF0ZSBvdmVyLlxuICogQHBhcmFtIHtGdW5jdGlvbnxPYmplY3R8c3RyaW5nfSBbaXRlcmF0ZWU9Xy5pZGVudGl0eV0gVGhlIGZ1bmN0aW9uIGludm9rZWRcbiAqICBwZXIgaXRlcmF0aW9uLlxuICogQHBhcmFtIHsqfSBbdGhpc0FyZ10gVGhlIGB0aGlzYCBiaW5kaW5nIG9mIGBpdGVyYXRlZWAuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIG5ldyBtYXBwZWQgYXJyYXkuXG4gKiBAZXhhbXBsZVxuICpcbiAqIGZ1bmN0aW9uIHRpbWVzVGhyZWUobikge1xuICogICByZXR1cm4gbiAqIDM7XG4gKiB9XG4gKlxuICogXy5tYXAoWzEsIDJdLCB0aW1lc1RocmVlKTtcbiAqIC8vID0+IFszLCA2XVxuICpcbiAqIF8ubWFwKHsgJ2EnOiAxLCAnYic6IDIgfSwgdGltZXNUaHJlZSk7XG4gKiAvLyA9PiBbMywgNl0gKGl0ZXJhdGlvbiBvcmRlciBpcyBub3QgZ3VhcmFudGVlZClcbiAqXG4gKiB2YXIgdXNlcnMgPSBbXG4gKiAgIHsgJ3VzZXInOiAnYmFybmV5JyB9LFxuICogICB7ICd1c2VyJzogJ2ZyZWQnIH1cbiAqIF07XG4gKlxuICogLy8gdXNpbmcgdGhlIGBfLnByb3BlcnR5YCBjYWxsYmFjayBzaG9ydGhhbmRcbiAqIF8ubWFwKHVzZXJzLCAndXNlcicpO1xuICogLy8gPT4gWydiYXJuZXknLCAnZnJlZCddXG4gKi9cbmZ1bmN0aW9uIG1hcChjb2xsZWN0aW9uLCBpdGVyYXRlZSwgdGhpc0FyZykge1xuICB2YXIgZnVuYyA9IGlzQXJyYXkoY29sbGVjdGlvbikgPyBhcnJheU1hcCA6IGJhc2VNYXA7XG4gIGl0ZXJhdGVlID0gYmFzZUNhbGxiYWNrKGl0ZXJhdGVlLCB0aGlzQXJnLCAzKTtcbiAgcmV0dXJuIGZ1bmMoY29sbGVjdGlvbiwgaXRlcmF0ZWUpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IG1hcDtcbiIsInZhciBnZXROYXRpdmUgPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9nZXROYXRpdmUnKTtcblxuLyogTmF0aXZlIG1ldGhvZCByZWZlcmVuY2VzIGZvciB0aG9zZSB3aXRoIHRoZSBzYW1lIG5hbWUgYXMgb3RoZXIgYGxvZGFzaGAgbWV0aG9kcy4gKi9cbnZhciBuYXRpdmVOb3cgPSBnZXROYXRpdmUoRGF0ZSwgJ25vdycpO1xuXG4vKipcbiAqIEdldHMgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdGhhdCBoYXZlIGVsYXBzZWQgc2luY2UgdGhlIFVuaXggZXBvY2hcbiAqICgxIEphbnVhcnkgMTk3MCAwMDowMDowMCBVVEMpLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAY2F0ZWdvcnkgRGF0ZVxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmRlZmVyKGZ1bmN0aW9uKHN0YW1wKSB7XG4gKiAgIGNvbnNvbGUubG9nKF8ubm93KCkgLSBzdGFtcCk7XG4gKiB9LCBfLm5vdygpKTtcbiAqIC8vID0+IGxvZ3MgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgaXQgdG9vayBmb3IgdGhlIGRlZmVycmVkIGZ1bmN0aW9uIHRvIGJlIGludm9rZWRcbiAqL1xudmFyIG5vdyA9IG5hdGl2ZU5vdyB8fCBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBub3c7XG4iLCJ2YXIgY3JlYXRlV3JhcHBlciA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2NyZWF0ZVdyYXBwZXInKSxcbiAgICByZXBsYWNlSG9sZGVycyA9IHJlcXVpcmUoJy4uL2ludGVybmFsL3JlcGxhY2VIb2xkZXJzJyksXG4gICAgcmVzdFBhcmFtID0gcmVxdWlyZSgnLi9yZXN0UGFyYW0nKTtcblxuLyoqIFVzZWQgdG8gY29tcG9zZSBiaXRtYXNrcyBmb3Igd3JhcHBlciBtZXRhZGF0YS4gKi9cbnZhciBCSU5EX0ZMQUcgPSAxLFxuICAgIFBBUlRJQUxfRkxBRyA9IDMyO1xuXG4vKipcbiAqIENyZWF0ZXMgYSBmdW5jdGlvbiB0aGF0IGludm9rZXMgYGZ1bmNgIHdpdGggdGhlIGB0aGlzYCBiaW5kaW5nIG9mIGB0aGlzQXJnYFxuICogYW5kIHByZXBlbmRzIGFueSBhZGRpdGlvbmFsIGBfLmJpbmRgIGFyZ3VtZW50cyB0byB0aG9zZSBwcm92aWRlZCB0byB0aGVcbiAqIGJvdW5kIGZ1bmN0aW9uLlxuICpcbiAqIFRoZSBgXy5iaW5kLnBsYWNlaG9sZGVyYCB2YWx1ZSwgd2hpY2ggZGVmYXVsdHMgdG8gYF9gIGluIG1vbm9saXRoaWMgYnVpbGRzLFxuICogbWF5IGJlIHVzZWQgYXMgYSBwbGFjZWhvbGRlciBmb3IgcGFydGlhbGx5IGFwcGxpZWQgYXJndW1lbnRzLlxuICpcbiAqICoqTm90ZToqKiBVbmxpa2UgbmF0aXZlIGBGdW5jdGlvbiNiaW5kYCB0aGlzIG1ldGhvZCBkb2VzIG5vdCBzZXQgdGhlIFwibGVuZ3RoXCJcbiAqIHByb3BlcnR5IG9mIGJvdW5kIGZ1bmN0aW9ucy5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IEZ1bmN0aW9uXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmdW5jIFRoZSBmdW5jdGlvbiB0byBiaW5kLlxuICogQHBhcmFtIHsqfSB0aGlzQXJnIFRoZSBgdGhpc2AgYmluZGluZyBvZiBgZnVuY2AuXG4gKiBAcGFyYW0gey4uLip9IFtwYXJ0aWFsc10gVGhlIGFyZ3VtZW50cyB0byBiZSBwYXJ0aWFsbHkgYXBwbGllZC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGJvdW5kIGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiB2YXIgZ3JlZXQgPSBmdW5jdGlvbihncmVldGluZywgcHVuY3R1YXRpb24pIHtcbiAqICAgcmV0dXJuIGdyZWV0aW5nICsgJyAnICsgdGhpcy51c2VyICsgcHVuY3R1YXRpb247XG4gKiB9O1xuICpcbiAqIHZhciBvYmplY3QgPSB7ICd1c2VyJzogJ2ZyZWQnIH07XG4gKlxuICogdmFyIGJvdW5kID0gXy5iaW5kKGdyZWV0LCBvYmplY3QsICdoaScpO1xuICogYm91bmQoJyEnKTtcbiAqIC8vID0+ICdoaSBmcmVkISdcbiAqXG4gKiAvLyB1c2luZyBwbGFjZWhvbGRlcnNcbiAqIHZhciBib3VuZCA9IF8uYmluZChncmVldCwgb2JqZWN0LCBfLCAnIScpO1xuICogYm91bmQoJ2hpJyk7XG4gKiAvLyA9PiAnaGkgZnJlZCEnXG4gKi9cbnZhciBiaW5kID0gcmVzdFBhcmFtKGZ1bmN0aW9uKGZ1bmMsIHRoaXNBcmcsIHBhcnRpYWxzKSB7XG4gIHZhciBiaXRtYXNrID0gQklORF9GTEFHO1xuICBpZiAocGFydGlhbHMubGVuZ3RoKSB7XG4gICAgdmFyIGhvbGRlcnMgPSByZXBsYWNlSG9sZGVycyhwYXJ0aWFscywgYmluZC5wbGFjZWhvbGRlcik7XG4gICAgYml0bWFzayB8PSBQQVJUSUFMX0ZMQUc7XG4gIH1cbiAgcmV0dXJuIGNyZWF0ZVdyYXBwZXIoZnVuYywgYml0bWFzaywgdGhpc0FyZywgcGFydGlhbHMsIGhvbGRlcnMpO1xufSk7XG5cbi8vIEFzc2lnbiBkZWZhdWx0IHBsYWNlaG9sZGVycy5cbmJpbmQucGxhY2Vob2xkZXIgPSB7fTtcblxubW9kdWxlLmV4cG9ydHMgPSBiaW5kO1xuIiwiLyoqIFVzZWQgYXMgdGhlIGBUeXBlRXJyb3JgIG1lc3NhZ2UgZm9yIFwiRnVuY3Rpb25zXCIgbWV0aG9kcy4gKi9cbnZhciBGVU5DX0VSUk9SX1RFWFQgPSAnRXhwZWN0ZWQgYSBmdW5jdGlvbic7XG5cbi8qIE5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXg7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGZ1bmN0aW9uIHRoYXQgaW52b2tlcyBgZnVuY2Agd2l0aCB0aGUgYHRoaXNgIGJpbmRpbmcgb2YgdGhlXG4gKiBjcmVhdGVkIGZ1bmN0aW9uIGFuZCBhcmd1bWVudHMgZnJvbSBgc3RhcnRgIGFuZCBiZXlvbmQgcHJvdmlkZWQgYXMgYW4gYXJyYXkuXG4gKlxuICogKipOb3RlOioqIFRoaXMgbWV0aG9kIGlzIGJhc2VkIG9uIHRoZSBbcmVzdCBwYXJhbWV0ZXJdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL1dlYi9KYXZhU2NyaXB0L1JlZmVyZW5jZS9GdW5jdGlvbnMvcmVzdF9wYXJhbWV0ZXJzKS5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IEZ1bmN0aW9uXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmdW5jIFRoZSBmdW5jdGlvbiB0byBhcHBseSBhIHJlc3QgcGFyYW1ldGVyIHRvLlxuICogQHBhcmFtIHtudW1iZXJ9IFtzdGFydD1mdW5jLmxlbmd0aC0xXSBUaGUgc3RhcnQgcG9zaXRpb24gb2YgdGhlIHJlc3QgcGFyYW1ldGVyLlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgZnVuY3Rpb24uXG4gKiBAZXhhbXBsZVxuICpcbiAqIHZhciBzYXkgPSBfLnJlc3RQYXJhbShmdW5jdGlvbih3aGF0LCBuYW1lcykge1xuICogICByZXR1cm4gd2hhdCArICcgJyArIF8uaW5pdGlhbChuYW1lcykuam9pbignLCAnKSArXG4gKiAgICAgKF8uc2l6ZShuYW1lcykgPiAxID8gJywgJiAnIDogJycpICsgXy5sYXN0KG5hbWVzKTtcbiAqIH0pO1xuICpcbiAqIHNheSgnaGVsbG8nLCAnZnJlZCcsICdiYXJuZXknLCAncGViYmxlcycpO1xuICogLy8gPT4gJ2hlbGxvIGZyZWQsIGJhcm5leSwgJiBwZWJibGVzJ1xuICovXG5mdW5jdGlvbiByZXN0UGFyYW0oZnVuYywgc3RhcnQpIHtcbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgc3RhcnQgPSBuYXRpdmVNYXgoc3RhcnQgPT09IHVuZGVmaW5lZCA/IChmdW5jLmxlbmd0aCAtIDEpIDogKCtzdGFydCB8fCAwKSwgMCk7XG4gIHJldHVybiBmdW5jdGlvbigpIHtcbiAgICB2YXIgYXJncyA9IGFyZ3VtZW50cyxcbiAgICAgICAgaW5kZXggPSAtMSxcbiAgICAgICAgbGVuZ3RoID0gbmF0aXZlTWF4KGFyZ3MubGVuZ3RoIC0gc3RhcnQsIDApLFxuICAgICAgICByZXN0ID0gQXJyYXkobGVuZ3RoKTtcblxuICAgIHdoaWxlICgrK2luZGV4IDwgbGVuZ3RoKSB7XG4gICAgICByZXN0W2luZGV4XSA9IGFyZ3Nbc3RhcnQgKyBpbmRleF07XG4gICAgfVxuICAgIHN3aXRjaCAoc3RhcnQpIHtcbiAgICAgIGNhc2UgMDogcmV0dXJuIGZ1bmMuY2FsbCh0aGlzLCByZXN0KTtcbiAgICAgIGNhc2UgMTogcmV0dXJuIGZ1bmMuY2FsbCh0aGlzLCBhcmdzWzBdLCByZXN0KTtcbiAgICAgIGNhc2UgMjogcmV0dXJuIGZ1bmMuY2FsbCh0aGlzLCBhcmdzWzBdLCBhcmdzWzFdLCByZXN0KTtcbiAgICB9XG4gICAgdmFyIG90aGVyQXJncyA9IEFycmF5KHN0YXJ0ICsgMSk7XG4gICAgaW5kZXggPSAtMTtcbiAgICB3aGlsZSAoKytpbmRleCA8IHN0YXJ0KSB7XG4gICAgICBvdGhlckFyZ3NbaW5kZXhdID0gYXJnc1tpbmRleF07XG4gICAgfVxuICAgIG90aGVyQXJnc1tzdGFydF0gPSByZXN0O1xuICAgIHJldHVybiBmdW5jLmFwcGx5KHRoaXMsIG90aGVyQXJncyk7XG4gIH07XG59XG5cbm1vZHVsZS5leHBvcnRzID0gcmVzdFBhcmFtO1xuIiwidmFyIGJhc2VDcmVhdGUgPSByZXF1aXJlKCcuL2Jhc2VDcmVhdGUnKSxcbiAgICBiYXNlTG9kYXNoID0gcmVxdWlyZSgnLi9iYXNlTG9kYXNoJyk7XG5cbi8qKiBVc2VkIGFzIHJlZmVyZW5jZXMgZm9yIGAtSW5maW5pdHlgIGFuZCBgSW5maW5pdHlgLiAqL1xudmFyIFBPU0lUSVZFX0lORklOSVRZID0gTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZO1xuXG4vKipcbiAqIENyZWF0ZXMgYSBsYXp5IHdyYXBwZXIgb2JqZWN0IHdoaWNoIHdyYXBzIGB2YWx1ZWAgdG8gZW5hYmxlIGxhenkgZXZhbHVhdGlvbi5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gd3JhcC5cbiAqL1xuZnVuY3Rpb24gTGF6eVdyYXBwZXIodmFsdWUpIHtcbiAgdGhpcy5fX3dyYXBwZWRfXyA9IHZhbHVlO1xuICB0aGlzLl9fYWN0aW9uc19fID0gW107XG4gIHRoaXMuX19kaXJfXyA9IDE7XG4gIHRoaXMuX19maWx0ZXJlZF9fID0gZmFsc2U7XG4gIHRoaXMuX19pdGVyYXRlZXNfXyA9IFtdO1xuICB0aGlzLl9fdGFrZUNvdW50X18gPSBQT1NJVElWRV9JTkZJTklUWTtcbiAgdGhpcy5fX3ZpZXdzX18gPSBbXTtcbn1cblxuTGF6eVdyYXBwZXIucHJvdG90eXBlID0gYmFzZUNyZWF0ZShiYXNlTG9kYXNoLnByb3RvdHlwZSk7XG5MYXp5V3JhcHBlci5wcm90b3R5cGUuY29uc3RydWN0b3IgPSBMYXp5V3JhcHBlcjtcblxubW9kdWxlLmV4cG9ydHMgPSBMYXp5V3JhcHBlcjtcbiIsInZhciBiYXNlQ3JlYXRlID0gcmVxdWlyZSgnLi9iYXNlQ3JlYXRlJyksXG4gICAgYmFzZUxvZGFzaCA9IHJlcXVpcmUoJy4vYmFzZUxvZGFzaCcpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGNvbnN0cnVjdG9yIGZvciBjcmVhdGluZyBgbG9kYXNoYCB3cmFwcGVyIG9iamVjdHMuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIHdyYXAuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtjaGFpbkFsbF0gRW5hYmxlIGNoYWluaW5nIGZvciBhbGwgd3JhcHBlciBtZXRob2RzLlxuICogQHBhcmFtIHtBcnJheX0gW2FjdGlvbnM9W11dIEFjdGlvbnMgdG8gcGVmb3JtIHRvIHJlc29sdmUgdGhlIHVud3JhcHBlZCB2YWx1ZS5cbiAqL1xuZnVuY3Rpb24gTG9kYXNoV3JhcHBlcih2YWx1ZSwgY2hhaW5BbGwsIGFjdGlvbnMpIHtcbiAgdGhpcy5fX3dyYXBwZWRfXyA9IHZhbHVlO1xuICB0aGlzLl9fYWN0aW9uc19fID0gYWN0aW9ucyB8fCBbXTtcbiAgdGhpcy5fX2NoYWluX18gPSAhIWNoYWluQWxsO1xufVxuXG5Mb2Rhc2hXcmFwcGVyLnByb3RvdHlwZSA9IGJhc2VDcmVhdGUoYmFzZUxvZGFzaC5wcm90b3R5cGUpO1xuTG9kYXNoV3JhcHBlci5wcm90b3R5cGUuY29uc3RydWN0b3IgPSBMb2Rhc2hXcmFwcGVyO1xuXG5tb2R1bGUuZXhwb3J0cyA9IExvZGFzaFdyYXBwZXI7XG4iLCIvKipcbiAqIENvcGllcyB0aGUgdmFsdWVzIG9mIGBzb3VyY2VgIHRvIGBhcnJheWAuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl9IHNvdXJjZSBUaGUgYXJyYXkgdG8gY29weSB2YWx1ZXMgZnJvbS5cbiAqIEBwYXJhbSB7QXJyYXl9IFthcnJheT1bXV0gVGhlIGFycmF5IHRvIGNvcHkgdmFsdWVzIHRvLlxuICogQHJldHVybnMge0FycmF5fSBSZXR1cm5zIGBhcnJheWAuXG4gKi9cbmZ1bmN0aW9uIGFycmF5Q29weShzb3VyY2UsIGFycmF5KSB7XG4gIHZhciBpbmRleCA9IC0xLFxuICAgICAgbGVuZ3RoID0gc291cmNlLmxlbmd0aDtcblxuICBhcnJheSB8fCAoYXJyYXkgPSBBcnJheShsZW5ndGgpKTtcbiAgd2hpbGUgKCsraW5kZXggPCBsZW5ndGgpIHtcbiAgICBhcnJheVtpbmRleF0gPSBzb3VyY2VbaW5kZXhdO1xuICB9XG4gIHJldHVybiBhcnJheTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBhcnJheUNvcHk7XG4iLCIvKipcbiAqIEEgc3BlY2lhbGl6ZWQgdmVyc2lvbiBvZiBgXy5mb3JFYWNoYCBmb3IgYXJyYXlzIHdpdGhvdXQgc3VwcG9ydCBmb3IgY2FsbGJhY2tcbiAqIHNob3J0aGFuZHMgYW5kIGB0aGlzYCBiaW5kaW5nLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgdG8gaXRlcmF0ZSBvdmVyLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gaXRlcmF0ZWUgVGhlIGZ1bmN0aW9uIGludm9rZWQgcGVyIGl0ZXJhdGlvbi5cbiAqIEByZXR1cm5zIHtBcnJheX0gUmV0dXJucyBgYXJyYXlgLlxuICovXG5mdW5jdGlvbiBhcnJheUVhY2goYXJyYXksIGl0ZXJhdGVlKSB7XG4gIHZhciBpbmRleCA9IC0xLFxuICAgICAgbGVuZ3RoID0gYXJyYXkubGVuZ3RoO1xuXG4gIHdoaWxlICgrK2luZGV4IDwgbGVuZ3RoKSB7XG4gICAgaWYgKGl0ZXJhdGVlKGFycmF5W2luZGV4XSwgaW5kZXgsIGFycmF5KSA9PT0gZmFsc2UpIHtcbiAgICAgIGJyZWFrO1xuICAgIH1cbiAgfVxuICByZXR1cm4gYXJyYXk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYXJyYXlFYWNoO1xuIiwiLyoqXG4gKiBBIHNwZWNpYWxpemVkIHZlcnNpb24gb2YgYF8ubWFwYCBmb3IgYXJyYXlzIHdpdGhvdXQgc3VwcG9ydCBmb3IgY2FsbGJhY2tcbiAqIHNob3J0aGFuZHMgYW5kIGB0aGlzYCBiaW5kaW5nLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgdG8gaXRlcmF0ZSBvdmVyLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gaXRlcmF0ZWUgVGhlIGZ1bmN0aW9uIGludm9rZWQgcGVyIGl0ZXJhdGlvbi5cbiAqIEByZXR1cm5zIHtBcnJheX0gUmV0dXJucyB0aGUgbmV3IG1hcHBlZCBhcnJheS5cbiAqL1xuZnVuY3Rpb24gYXJyYXlNYXAoYXJyYXksIGl0ZXJhdGVlKSB7XG4gIHZhciBpbmRleCA9IC0xLFxuICAgICAgbGVuZ3RoID0gYXJyYXkubGVuZ3RoLFxuICAgICAgcmVzdWx0ID0gQXJyYXkobGVuZ3RoKTtcblxuICB3aGlsZSAoKytpbmRleCA8IGxlbmd0aCkge1xuICAgIHJlc3VsdFtpbmRleF0gPSBpdGVyYXRlZShhcnJheVtpbmRleF0sIGluZGV4LCBhcnJheSk7XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBhcnJheU1hcDtcbiIsIi8qKlxuICogQSBzcGVjaWFsaXplZCB2ZXJzaW9uIG9mIGBfLnNvbWVgIGZvciBhcnJheXMgd2l0aG91dCBzdXBwb3J0IGZvciBjYWxsYmFja1xuICogc2hvcnRoYW5kcyBhbmQgYHRoaXNgIGJpbmRpbmcuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl9IGFycmF5IFRoZSBhcnJheSB0byBpdGVyYXRlIG92ZXIuXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBwcmVkaWNhdGUgVGhlIGZ1bmN0aW9uIGludm9rZWQgcGVyIGl0ZXJhdGlvbi5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBhbnkgZWxlbWVudCBwYXNzZXMgdGhlIHByZWRpY2F0ZSBjaGVjayxcbiAqICBlbHNlIGBmYWxzZWAuXG4gKi9cbmZ1bmN0aW9uIGFycmF5U29tZShhcnJheSwgcHJlZGljYXRlKSB7XG4gIHZhciBpbmRleCA9IC0xLFxuICAgICAgbGVuZ3RoID0gYXJyYXkubGVuZ3RoO1xuXG4gIHdoaWxlICgrK2luZGV4IDwgbGVuZ3RoKSB7XG4gICAgaWYgKHByZWRpY2F0ZShhcnJheVtpbmRleF0sIGluZGV4LCBhcnJheSkpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgfVxuICByZXR1cm4gZmFsc2U7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYXJyYXlTb21lO1xuIiwidmFyIGJhc2VDb3B5ID0gcmVxdWlyZSgnLi9iYXNlQ29weScpLFxuICAgIGtleXMgPSByZXF1aXJlKCcuLi9vYmplY3Qva2V5cycpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLmFzc2lnbmAgd2l0aG91dCBzdXBwb3J0IGZvciBhcmd1bWVudCBqdWdnbGluZyxcbiAqIG11bHRpcGxlIHNvdXJjZXMsIGFuZCBgY3VzdG9taXplcmAgZnVuY3Rpb25zLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBkZXN0aW5hdGlvbiBvYmplY3QuXG4gKiBAcGFyYW0ge09iamVjdH0gc291cmNlIFRoZSBzb3VyY2Ugb2JqZWN0LlxuICogQHJldHVybnMge09iamVjdH0gUmV0dXJucyBgb2JqZWN0YC5cbiAqL1xuZnVuY3Rpb24gYmFzZUFzc2lnbihvYmplY3QsIHNvdXJjZSkge1xuICByZXR1cm4gc291cmNlID09IG51bGxcbiAgICA/IG9iamVjdFxuICAgIDogYmFzZUNvcHkoc291cmNlLCBrZXlzKHNvdXJjZSksIG9iamVjdCk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZUFzc2lnbjtcbiIsInZhciBiYXNlTWF0Y2hlcyA9IHJlcXVpcmUoJy4vYmFzZU1hdGNoZXMnKSxcbiAgICBiYXNlTWF0Y2hlc1Byb3BlcnR5ID0gcmVxdWlyZSgnLi9iYXNlTWF0Y2hlc1Byb3BlcnR5JyksXG4gICAgYmluZENhbGxiYWNrID0gcmVxdWlyZSgnLi9iaW5kQ2FsbGJhY2snKSxcbiAgICBpZGVudGl0eSA9IHJlcXVpcmUoJy4uL3V0aWxpdHkvaWRlbnRpdHknKSxcbiAgICBwcm9wZXJ0eSA9IHJlcXVpcmUoJy4uL3V0aWxpdHkvcHJvcGVydHknKTtcblxuLyoqXG4gKiBUaGUgYmFzZSBpbXBsZW1lbnRhdGlvbiBvZiBgXy5jYWxsYmFja2Agd2hpY2ggc3VwcG9ydHMgc3BlY2lmeWluZyB0aGVcbiAqIG51bWJlciBvZiBhcmd1bWVudHMgdG8gcHJvdmlkZSB0byBgZnVuY2AuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7Kn0gW2Z1bmM9Xy5pZGVudGl0eV0gVGhlIHZhbHVlIHRvIGNvbnZlcnQgdG8gYSBjYWxsYmFjay5cbiAqIEBwYXJhbSB7Kn0gW3RoaXNBcmddIFRoZSBgdGhpc2AgYmluZGluZyBvZiBgZnVuY2AuXG4gKiBAcGFyYW0ge251bWJlcn0gW2FyZ0NvdW50XSBUaGUgbnVtYmVyIG9mIGFyZ3VtZW50cyB0byBwcm92aWRlIHRvIGBmdW5jYC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgY2FsbGJhY2suXG4gKi9cbmZ1bmN0aW9uIGJhc2VDYWxsYmFjayhmdW5jLCB0aGlzQXJnLCBhcmdDb3VudCkge1xuICB2YXIgdHlwZSA9IHR5cGVvZiBmdW5jO1xuICBpZiAodHlwZSA9PSAnZnVuY3Rpb24nKSB7XG4gICAgcmV0dXJuIHRoaXNBcmcgPT09IHVuZGVmaW5lZFxuICAgICAgPyBmdW5jXG4gICAgICA6IGJpbmRDYWxsYmFjayhmdW5jLCB0aGlzQXJnLCBhcmdDb3VudCk7XG4gIH1cbiAgaWYgKGZ1bmMgPT0gbnVsbCkge1xuICAgIHJldHVybiBpZGVudGl0eTtcbiAgfVxuICBpZiAodHlwZSA9PSAnb2JqZWN0Jykge1xuICAgIHJldHVybiBiYXNlTWF0Y2hlcyhmdW5jKTtcbiAgfVxuICByZXR1cm4gdGhpc0FyZyA9PT0gdW5kZWZpbmVkXG4gICAgPyBwcm9wZXJ0eShmdW5jKVxuICAgIDogYmFzZU1hdGNoZXNQcm9wZXJ0eShmdW5jLCB0aGlzQXJnKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBiYXNlQ2FsbGJhY2s7XG4iLCJ2YXIgYXJyYXlDb3B5ID0gcmVxdWlyZSgnLi9hcnJheUNvcHknKSxcbiAgICBhcnJheUVhY2ggPSByZXF1aXJlKCcuL2FycmF5RWFjaCcpLFxuICAgIGJhc2VBc3NpZ24gPSByZXF1aXJlKCcuL2Jhc2VBc3NpZ24nKSxcbiAgICBiYXNlRm9yT3duID0gcmVxdWlyZSgnLi9iYXNlRm9yT3duJyksXG4gICAgaW5pdENsb25lQXJyYXkgPSByZXF1aXJlKCcuL2luaXRDbG9uZUFycmF5JyksXG4gICAgaW5pdENsb25lQnlUYWcgPSByZXF1aXJlKCcuL2luaXRDbG9uZUJ5VGFnJyksXG4gICAgaW5pdENsb25lT2JqZWN0ID0gcmVxdWlyZSgnLi9pbml0Q2xvbmVPYmplY3QnKSxcbiAgICBpc0FycmF5ID0gcmVxdWlyZSgnLi4vbGFuZy9pc0FycmF5JyksXG4gICAgaXNIb3N0T2JqZWN0ID0gcmVxdWlyZSgnLi9pc0hvc3RPYmplY3QnKSxcbiAgICBpc09iamVjdCA9IHJlcXVpcmUoJy4uL2xhbmcvaXNPYmplY3QnKTtcblxuLyoqIGBPYmplY3QjdG9TdHJpbmdgIHJlc3VsdCByZWZlcmVuY2VzLiAqL1xudmFyIGFyZ3NUYWcgPSAnW29iamVjdCBBcmd1bWVudHNdJyxcbiAgICBhcnJheVRhZyA9ICdbb2JqZWN0IEFycmF5XScsXG4gICAgYm9vbFRhZyA9ICdbb2JqZWN0IEJvb2xlYW5dJyxcbiAgICBkYXRlVGFnID0gJ1tvYmplY3QgRGF0ZV0nLFxuICAgIGVycm9yVGFnID0gJ1tvYmplY3QgRXJyb3JdJyxcbiAgICBmdW5jVGFnID0gJ1tvYmplY3QgRnVuY3Rpb25dJyxcbiAgICBtYXBUYWcgPSAnW29iamVjdCBNYXBdJyxcbiAgICBudW1iZXJUYWcgPSAnW29iamVjdCBOdW1iZXJdJyxcbiAgICBvYmplY3RUYWcgPSAnW29iamVjdCBPYmplY3RdJyxcbiAgICByZWdleHBUYWcgPSAnW29iamVjdCBSZWdFeHBdJyxcbiAgICBzZXRUYWcgPSAnW29iamVjdCBTZXRdJyxcbiAgICBzdHJpbmdUYWcgPSAnW29iamVjdCBTdHJpbmddJyxcbiAgICB3ZWFrTWFwVGFnID0gJ1tvYmplY3QgV2Vha01hcF0nO1xuXG52YXIgYXJyYXlCdWZmZXJUYWcgPSAnW29iamVjdCBBcnJheUJ1ZmZlcl0nLFxuICAgIGZsb2F0MzJUYWcgPSAnW29iamVjdCBGbG9hdDMyQXJyYXldJyxcbiAgICBmbG9hdDY0VGFnID0gJ1tvYmplY3QgRmxvYXQ2NEFycmF5XScsXG4gICAgaW50OFRhZyA9ICdbb2JqZWN0IEludDhBcnJheV0nLFxuICAgIGludDE2VGFnID0gJ1tvYmplY3QgSW50MTZBcnJheV0nLFxuICAgIGludDMyVGFnID0gJ1tvYmplY3QgSW50MzJBcnJheV0nLFxuICAgIHVpbnQ4VGFnID0gJ1tvYmplY3QgVWludDhBcnJheV0nLFxuICAgIHVpbnQ4Q2xhbXBlZFRhZyA9ICdbb2JqZWN0IFVpbnQ4Q2xhbXBlZEFycmF5XScsXG4gICAgdWludDE2VGFnID0gJ1tvYmplY3QgVWludDE2QXJyYXldJyxcbiAgICB1aW50MzJUYWcgPSAnW29iamVjdCBVaW50MzJBcnJheV0nO1xuXG4vKiogVXNlZCB0byBpZGVudGlmeSBgdG9TdHJpbmdUYWdgIHZhbHVlcyBzdXBwb3J0ZWQgYnkgYF8uY2xvbmVgLiAqL1xudmFyIGNsb25lYWJsZVRhZ3MgPSB7fTtcbmNsb25lYWJsZVRhZ3NbYXJnc1RhZ10gPSBjbG9uZWFibGVUYWdzW2FycmF5VGFnXSA9XG5jbG9uZWFibGVUYWdzW2FycmF5QnVmZmVyVGFnXSA9IGNsb25lYWJsZVRhZ3NbYm9vbFRhZ10gPVxuY2xvbmVhYmxlVGFnc1tkYXRlVGFnXSA9IGNsb25lYWJsZVRhZ3NbZmxvYXQzMlRhZ10gPVxuY2xvbmVhYmxlVGFnc1tmbG9hdDY0VGFnXSA9IGNsb25lYWJsZVRhZ3NbaW50OFRhZ10gPVxuY2xvbmVhYmxlVGFnc1tpbnQxNlRhZ10gPSBjbG9uZWFibGVUYWdzW2ludDMyVGFnXSA9XG5jbG9uZWFibGVUYWdzW251bWJlclRhZ10gPSBjbG9uZWFibGVUYWdzW29iamVjdFRhZ10gPVxuY2xvbmVhYmxlVGFnc1tyZWdleHBUYWddID0gY2xvbmVhYmxlVGFnc1tzdHJpbmdUYWddID1cbmNsb25lYWJsZVRhZ3NbdWludDhUYWddID0gY2xvbmVhYmxlVGFnc1t1aW50OENsYW1wZWRUYWddID1cbmNsb25lYWJsZVRhZ3NbdWludDE2VGFnXSA9IGNsb25lYWJsZVRhZ3NbdWludDMyVGFnXSA9IHRydWU7XG5jbG9uZWFibGVUYWdzW2Vycm9yVGFnXSA9IGNsb25lYWJsZVRhZ3NbZnVuY1RhZ10gPVxuY2xvbmVhYmxlVGFnc1ttYXBUYWddID0gY2xvbmVhYmxlVGFnc1tzZXRUYWddID1cbmNsb25lYWJsZVRhZ3Nbd2Vha01hcFRhZ10gPSBmYWxzZTtcblxuLyoqIFVzZWQgZm9yIG5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZSBbYHRvU3RyaW5nVGFnYF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNi4wLyNzZWMtb2JqZWN0LnByb3RvdHlwZS50b3N0cmluZylcbiAqIG9mIHZhbHVlcy5cbiAqL1xudmFyIG9ialRvU3RyaW5nID0gb2JqZWN0UHJvdG8udG9TdHJpbmc7XG5cbi8qKlxuICogVGhlIGJhc2UgaW1wbGVtZW50YXRpb24gb2YgYF8uY2xvbmVgIHdpdGhvdXQgc3VwcG9ydCBmb3IgYXJndW1lbnQganVnZ2xpbmdcbiAqIGFuZCBgdGhpc2AgYmluZGluZyBgY3VzdG9taXplcmAgZnVuY3Rpb25zLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjbG9uZS5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW2lzRGVlcF0gU3BlY2lmeSBhIGRlZXAgY2xvbmUuXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBbY3VzdG9taXplcl0gVGhlIGZ1bmN0aW9uIHRvIGN1c3RvbWl6ZSBjbG9uaW5nIHZhbHVlcy5cbiAqIEBwYXJhbSB7c3RyaW5nfSBba2V5XSBUaGUga2V5IG9mIGB2YWx1ZWAuXG4gKiBAcGFyYW0ge09iamVjdH0gW29iamVjdF0gVGhlIG9iamVjdCBgdmFsdWVgIGJlbG9uZ3MgdG8uXG4gKiBAcGFyYW0ge0FycmF5fSBbc3RhY2tBPVtdXSBUcmFja3MgdHJhdmVyc2VkIHNvdXJjZSBvYmplY3RzLlxuICogQHBhcmFtIHtBcnJheX0gW3N0YWNrQj1bXV0gQXNzb2NpYXRlcyBjbG9uZXMgd2l0aCBzb3VyY2UgY291bnRlcnBhcnRzLlxuICogQHJldHVybnMgeyp9IFJldHVybnMgdGhlIGNsb25lZCB2YWx1ZS5cbiAqL1xuZnVuY3Rpb24gYmFzZUNsb25lKHZhbHVlLCBpc0RlZXAsIGN1c3RvbWl6ZXIsIGtleSwgb2JqZWN0LCBzdGFja0EsIHN0YWNrQikge1xuICB2YXIgcmVzdWx0O1xuICBpZiAoY3VzdG9taXplcikge1xuICAgIHJlc3VsdCA9IG9iamVjdCA/IGN1c3RvbWl6ZXIodmFsdWUsIGtleSwgb2JqZWN0KSA6IGN1c3RvbWl6ZXIodmFsdWUpO1xuICB9XG4gIGlmIChyZXN1bHQgIT09IHVuZGVmaW5lZCkge1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cbiAgaWYgKCFpc09iamVjdCh2YWx1ZSkpIHtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbiAgdmFyIGlzQXJyID0gaXNBcnJheSh2YWx1ZSk7XG4gIGlmIChpc0Fycikge1xuICAgIHJlc3VsdCA9IGluaXRDbG9uZUFycmF5KHZhbHVlKTtcbiAgICBpZiAoIWlzRGVlcCkge1xuICAgICAgcmV0dXJuIGFycmF5Q29weSh2YWx1ZSwgcmVzdWx0KTtcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAgdmFyIHRhZyA9IG9ialRvU3RyaW5nLmNhbGwodmFsdWUpLFxuICAgICAgICBpc0Z1bmMgPSB0YWcgPT0gZnVuY1RhZztcblxuICAgIGlmICh0YWcgPT0gb2JqZWN0VGFnIHx8IHRhZyA9PSBhcmdzVGFnIHx8IChpc0Z1bmMgJiYgIW9iamVjdCkpIHtcbiAgICAgIGlmIChpc0hvc3RPYmplY3QodmFsdWUpKSB7XG4gICAgICAgIHJldHVybiBvYmplY3QgPyB2YWx1ZSA6IHt9O1xuICAgICAgfVxuICAgICAgcmVzdWx0ID0gaW5pdENsb25lT2JqZWN0KGlzRnVuYyA/IHt9IDogdmFsdWUpO1xuICAgICAgaWYgKCFpc0RlZXApIHtcbiAgICAgICAgcmV0dXJuIGJhc2VBc3NpZ24ocmVzdWx0LCB2YWx1ZSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHJldHVybiBjbG9uZWFibGVUYWdzW3RhZ11cbiAgICAgICAgPyBpbml0Q2xvbmVCeVRhZyh2YWx1ZSwgdGFnLCBpc0RlZXApXG4gICAgICAgIDogKG9iamVjdCA/IHZhbHVlIDoge30pO1xuICAgIH1cbiAgfVxuICAvLyBDaGVjayBmb3IgY2lyY3VsYXIgcmVmZXJlbmNlcyBhbmQgcmV0dXJuIGl0cyBjb3JyZXNwb25kaW5nIGNsb25lLlxuICBzdGFja0EgfHwgKHN0YWNrQSA9IFtdKTtcbiAgc3RhY2tCIHx8IChzdGFja0IgPSBbXSk7XG5cbiAgdmFyIGxlbmd0aCA9IHN0YWNrQS5sZW5ndGg7XG4gIHdoaWxlIChsZW5ndGgtLSkge1xuICAgIGlmIChzdGFja0FbbGVuZ3RoXSA9PSB2YWx1ZSkge1xuICAgICAgcmV0dXJuIHN0YWNrQltsZW5ndGhdO1xuICAgIH1cbiAgfVxuICAvLyBBZGQgdGhlIHNvdXJjZSB2YWx1ZSB0byB0aGUgc3RhY2sgb2YgdHJhdmVyc2VkIG9iamVjdHMgYW5kIGFzc29jaWF0ZSBpdCB3aXRoIGl0cyBjbG9uZS5cbiAgc3RhY2tBLnB1c2godmFsdWUpO1xuICBzdGFja0IucHVzaChyZXN1bHQpO1xuXG4gIC8vIFJlY3Vyc2l2ZWx5IHBvcHVsYXRlIGNsb25lIChzdXNjZXB0aWJsZSB0byBjYWxsIHN0YWNrIGxpbWl0cykuXG4gIChpc0FyciA/IGFycmF5RWFjaCA6IGJhc2VGb3JPd24pKHZhbHVlLCBmdW5jdGlvbihzdWJWYWx1ZSwga2V5KSB7XG4gICAgcmVzdWx0W2tleV0gPSBiYXNlQ2xvbmUoc3ViVmFsdWUsIGlzRGVlcCwgY3VzdG9taXplciwga2V5LCB2YWx1ZSwgc3RhY2tBLCBzdGFja0IpO1xuICB9KTtcbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBiYXNlQ2xvbmU7XG4iLCIvKipcbiAqIENvcGllcyBwcm9wZXJ0aWVzIG9mIGBzb3VyY2VgIHRvIGBvYmplY3RgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gc291cmNlIFRoZSBvYmplY3QgdG8gY29weSBwcm9wZXJ0aWVzIGZyb20uXG4gKiBAcGFyYW0ge0FycmF5fSBwcm9wcyBUaGUgcHJvcGVydHkgbmFtZXMgdG8gY29weS5cbiAqIEBwYXJhbSB7T2JqZWN0fSBbb2JqZWN0PXt9XSBUaGUgb2JqZWN0IHRvIGNvcHkgcHJvcGVydGllcyB0by5cbiAqIEByZXR1cm5zIHtPYmplY3R9IFJldHVybnMgYG9iamVjdGAuXG4gKi9cbmZ1bmN0aW9uIGJhc2VDb3B5KHNvdXJjZSwgcHJvcHMsIG9iamVjdCkge1xuICBvYmplY3QgfHwgKG9iamVjdCA9IHt9KTtcblxuICB2YXIgaW5kZXggPSAtMSxcbiAgICAgIGxlbmd0aCA9IHByb3BzLmxlbmd0aDtcblxuICB3aGlsZSAoKytpbmRleCA8IGxlbmd0aCkge1xuICAgIHZhciBrZXkgPSBwcm9wc1tpbmRleF07XG4gICAgb2JqZWN0W2tleV0gPSBzb3VyY2Vba2V5XTtcbiAgfVxuICByZXR1cm4gb2JqZWN0O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJhc2VDb3B5O1xuIiwidmFyIGlzT2JqZWN0ID0gcmVxdWlyZSgnLi4vbGFuZy9pc09iamVjdCcpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLmNyZWF0ZWAgd2l0aG91dCBzdXBwb3J0IGZvciBhc3NpZ25pbmdcbiAqIHByb3BlcnRpZXMgdG8gdGhlIGNyZWF0ZWQgb2JqZWN0LlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gcHJvdG90eXBlIFRoZSBvYmplY3QgdG8gaW5oZXJpdCBmcm9tLlxuICogQHJldHVybnMge09iamVjdH0gUmV0dXJucyB0aGUgbmV3IG9iamVjdC5cbiAqL1xudmFyIGJhc2VDcmVhdGUgPSAoZnVuY3Rpb24oKSB7XG4gIGZ1bmN0aW9uIG9iamVjdCgpIHt9XG4gIHJldHVybiBmdW5jdGlvbihwcm90b3R5cGUpIHtcbiAgICBpZiAoaXNPYmplY3QocHJvdG90eXBlKSkge1xuICAgICAgb2JqZWN0LnByb3RvdHlwZSA9IHByb3RvdHlwZTtcbiAgICAgIHZhciByZXN1bHQgPSBuZXcgb2JqZWN0O1xuICAgICAgb2JqZWN0LnByb3RvdHlwZSA9IHVuZGVmaW5lZDtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdCB8fCB7fTtcbiAgfTtcbn0oKSk7XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZUNyZWF0ZTtcbiIsInZhciBiYXNlRm9yT3duID0gcmVxdWlyZSgnLi9iYXNlRm9yT3duJyksXG4gICAgY3JlYXRlQmFzZUVhY2ggPSByZXF1aXJlKCcuL2NyZWF0ZUJhc2VFYWNoJyk7XG5cbi8qKlxuICogVGhlIGJhc2UgaW1wbGVtZW50YXRpb24gb2YgYF8uZm9yRWFjaGAgd2l0aG91dCBzdXBwb3J0IGZvciBjYWxsYmFja1xuICogc2hvcnRoYW5kcyBhbmQgYHRoaXNgIGJpbmRpbmcuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl8T2JqZWN0fHN0cmluZ30gY29sbGVjdGlvbiBUaGUgY29sbGVjdGlvbiB0byBpdGVyYXRlIG92ZXIuXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBpdGVyYXRlZSBUaGUgZnVuY3Rpb24gaW52b2tlZCBwZXIgaXRlcmF0aW9uLlxuICogQHJldHVybnMge0FycmF5fE9iamVjdHxzdHJpbmd9IFJldHVybnMgYGNvbGxlY3Rpb25gLlxuICovXG52YXIgYmFzZUVhY2ggPSBjcmVhdGVCYXNlRWFjaChiYXNlRm9yT3duKTtcblxubW9kdWxlLmV4cG9ydHMgPSBiYXNlRWFjaDtcbiIsIi8qKlxuICogVGhlIGJhc2UgaW1wbGVtZW50YXRpb24gb2YgYF8uZmluZGAsIGBfLmZpbmRMYXN0YCwgYF8uZmluZEtleWAsIGFuZCBgXy5maW5kTGFzdEtleWAsXG4gKiB3aXRob3V0IHN1cHBvcnQgZm9yIGNhbGxiYWNrIHNob3J0aGFuZHMgYW5kIGB0aGlzYCBiaW5kaW5nLCB3aGljaCBpdGVyYXRlc1xuICogb3ZlciBgY29sbGVjdGlvbmAgdXNpbmcgdGhlIHByb3ZpZGVkIGBlYWNoRnVuY2AuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl8T2JqZWN0fHN0cmluZ30gY29sbGVjdGlvbiBUaGUgY29sbGVjdGlvbiB0byBzZWFyY2guXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBwcmVkaWNhdGUgVGhlIGZ1bmN0aW9uIGludm9rZWQgcGVyIGl0ZXJhdGlvbi5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGVhY2hGdW5jIFRoZSBmdW5jdGlvbiB0byBpdGVyYXRlIG92ZXIgYGNvbGxlY3Rpb25gLlxuICogQHBhcmFtIHtib29sZWFufSBbcmV0S2V5XSBTcGVjaWZ5IHJldHVybmluZyB0aGUga2V5IG9mIHRoZSBmb3VuZCBlbGVtZW50XG4gKiAgaW5zdGVhZCBvZiB0aGUgZWxlbWVudCBpdHNlbGYuXG4gKiBAcmV0dXJucyB7Kn0gUmV0dXJucyB0aGUgZm91bmQgZWxlbWVudCBvciBpdHMga2V5LCBlbHNlIGB1bmRlZmluZWRgLlxuICovXG5mdW5jdGlvbiBiYXNlRmluZChjb2xsZWN0aW9uLCBwcmVkaWNhdGUsIGVhY2hGdW5jLCByZXRLZXkpIHtcbiAgdmFyIHJlc3VsdDtcbiAgZWFjaEZ1bmMoY29sbGVjdGlvbiwgZnVuY3Rpb24odmFsdWUsIGtleSwgY29sbGVjdGlvbikge1xuICAgIGlmIChwcmVkaWNhdGUodmFsdWUsIGtleSwgY29sbGVjdGlvbikpIHtcbiAgICAgIHJlc3VsdCA9IHJldEtleSA/IGtleSA6IHZhbHVlO1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfSk7XG4gIHJldHVybiByZXN1bHQ7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZUZpbmQ7XG4iLCIvKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLmZpbmRJbmRleGAgYW5kIGBfLmZpbmRMYXN0SW5kZXhgIHdpdGhvdXRcbiAqIHN1cHBvcnQgZm9yIGNhbGxiYWNrIHNob3J0aGFuZHMgYW5kIGB0aGlzYCBiaW5kaW5nLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgdG8gc2VhcmNoLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gcHJlZGljYXRlIFRoZSBmdW5jdGlvbiBpbnZva2VkIHBlciBpdGVyYXRpb24uXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtmcm9tUmlnaHRdIFNwZWNpZnkgaXRlcmF0aW5nIGZyb20gcmlnaHQgdG8gbGVmdC5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIGluZGV4IG9mIHRoZSBtYXRjaGVkIHZhbHVlLCBlbHNlIGAtMWAuXG4gKi9cbmZ1bmN0aW9uIGJhc2VGaW5kSW5kZXgoYXJyYXksIHByZWRpY2F0ZSwgZnJvbVJpZ2h0KSB7XG4gIHZhciBsZW5ndGggPSBhcnJheS5sZW5ndGgsXG4gICAgICBpbmRleCA9IGZyb21SaWdodCA/IGxlbmd0aCA6IC0xO1xuXG4gIHdoaWxlICgoZnJvbVJpZ2h0ID8gaW5kZXgtLSA6ICsraW5kZXggPCBsZW5ndGgpKSB7XG4gICAgaWYgKHByZWRpY2F0ZShhcnJheVtpbmRleF0sIGluZGV4LCBhcnJheSkpIHtcbiAgICAgIHJldHVybiBpbmRleDtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIC0xO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJhc2VGaW5kSW5kZXg7XG4iLCJ2YXIgY3JlYXRlQmFzZUZvciA9IHJlcXVpcmUoJy4vY3JlYXRlQmFzZUZvcicpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBiYXNlRm9ySW5gIGFuZCBgYmFzZUZvck93bmAgd2hpY2ggaXRlcmF0ZXNcbiAqIG92ZXIgYG9iamVjdGAgcHJvcGVydGllcyByZXR1cm5lZCBieSBga2V5c0Z1bmNgIGludm9raW5nIGBpdGVyYXRlZWAgZm9yXG4gKiBlYWNoIHByb3BlcnR5LiBJdGVyYXRlZSBmdW5jdGlvbnMgbWF5IGV4aXQgaXRlcmF0aW9uIGVhcmx5IGJ5IGV4cGxpY2l0bHlcbiAqIHJldHVybmluZyBgZmFsc2VgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gaXRlcmF0ZSBvdmVyLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gaXRlcmF0ZWUgVGhlIGZ1bmN0aW9uIGludm9rZWQgcGVyIGl0ZXJhdGlvbi5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGtleXNGdW5jIFRoZSBmdW5jdGlvbiB0byBnZXQgdGhlIGtleXMgb2YgYG9iamVjdGAuXG4gKiBAcmV0dXJucyB7T2JqZWN0fSBSZXR1cm5zIGBvYmplY3RgLlxuICovXG52YXIgYmFzZUZvciA9IGNyZWF0ZUJhc2VGb3IoKTtcblxubW9kdWxlLmV4cG9ydHMgPSBiYXNlRm9yO1xuIiwidmFyIGJhc2VGb3IgPSByZXF1aXJlKCcuL2Jhc2VGb3InKSxcbiAgICBrZXlzSW4gPSByZXF1aXJlKCcuLi9vYmplY3Qva2V5c0luJyk7XG5cbi8qKlxuICogVGhlIGJhc2UgaW1wbGVtZW50YXRpb24gb2YgYF8uZm9ySW5gIHdpdGhvdXQgc3VwcG9ydCBmb3IgY2FsbGJhY2tcbiAqIHNob3J0aGFuZHMgYW5kIGB0aGlzYCBiaW5kaW5nLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gaXRlcmF0ZSBvdmVyLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gaXRlcmF0ZWUgVGhlIGZ1bmN0aW9uIGludm9rZWQgcGVyIGl0ZXJhdGlvbi5cbiAqIEByZXR1cm5zIHtPYmplY3R9IFJldHVybnMgYG9iamVjdGAuXG4gKi9cbmZ1bmN0aW9uIGJhc2VGb3JJbihvYmplY3QsIGl0ZXJhdGVlKSB7XG4gIHJldHVybiBiYXNlRm9yKG9iamVjdCwgaXRlcmF0ZWUsIGtleXNJbik7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZUZvckluO1xuIiwidmFyIGJhc2VGb3IgPSByZXF1aXJlKCcuL2Jhc2VGb3InKSxcbiAgICBrZXlzID0gcmVxdWlyZSgnLi4vb2JqZWN0L2tleXMnKTtcblxuLyoqXG4gKiBUaGUgYmFzZSBpbXBsZW1lbnRhdGlvbiBvZiBgXy5mb3JPd25gIHdpdGhvdXQgc3VwcG9ydCBmb3IgY2FsbGJhY2tcbiAqIHNob3J0aGFuZHMgYW5kIGB0aGlzYCBiaW5kaW5nLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gaXRlcmF0ZSBvdmVyLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gaXRlcmF0ZWUgVGhlIGZ1bmN0aW9uIGludm9rZWQgcGVyIGl0ZXJhdGlvbi5cbiAqIEByZXR1cm5zIHtPYmplY3R9IFJldHVybnMgYG9iamVjdGAuXG4gKi9cbmZ1bmN0aW9uIGJhc2VGb3JPd24ob2JqZWN0LCBpdGVyYXRlZSkge1xuICByZXR1cm4gYmFzZUZvcihvYmplY3QsIGl0ZXJhdGVlLCBrZXlzKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBiYXNlRm9yT3duO1xuIiwidmFyIHRvT2JqZWN0ID0gcmVxdWlyZSgnLi90b09iamVjdCcpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBnZXRgIHdpdGhvdXQgc3VwcG9ydCBmb3Igc3RyaW5nIHBhdGhzXG4gKiBhbmQgZGVmYXVsdCB2YWx1ZXMuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmplY3QgVGhlIG9iamVjdCB0byBxdWVyeS5cbiAqIEBwYXJhbSB7QXJyYXl9IHBhdGggVGhlIHBhdGggb2YgdGhlIHByb3BlcnR5IHRvIGdldC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBbcGF0aEtleV0gVGhlIGtleSByZXByZXNlbnRhdGlvbiBvZiBwYXRoLlxuICogQHJldHVybnMgeyp9IFJldHVybnMgdGhlIHJlc29sdmVkIHZhbHVlLlxuICovXG5mdW5jdGlvbiBiYXNlR2V0KG9iamVjdCwgcGF0aCwgcGF0aEtleSkge1xuICBpZiAob2JqZWN0ID09IG51bGwpIHtcbiAgICByZXR1cm47XG4gIH1cbiAgb2JqZWN0ID0gdG9PYmplY3Qob2JqZWN0KTtcbiAgaWYgKHBhdGhLZXkgIT09IHVuZGVmaW5lZCAmJiBwYXRoS2V5IGluIG9iamVjdCkge1xuICAgIHBhdGggPSBbcGF0aEtleV07XG4gIH1cbiAgdmFyIGluZGV4ID0gMCxcbiAgICAgIGxlbmd0aCA9IHBhdGgubGVuZ3RoO1xuXG4gIHdoaWxlIChvYmplY3QgIT0gbnVsbCAmJiBpbmRleCA8IGxlbmd0aCkge1xuICAgIG9iamVjdCA9IHRvT2JqZWN0KG9iamVjdClbcGF0aFtpbmRleCsrXV07XG4gIH1cbiAgcmV0dXJuIChpbmRleCAmJiBpbmRleCA9PSBsZW5ndGgpID8gb2JqZWN0IDogdW5kZWZpbmVkO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJhc2VHZXQ7XG4iLCJ2YXIgaW5kZXhPZk5hTiA9IHJlcXVpcmUoJy4vaW5kZXhPZk5hTicpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLmluZGV4T2ZgIHdpdGhvdXQgc3VwcG9ydCBmb3IgYmluYXJ5IHNlYXJjaGVzLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgdG8gc2VhcmNoLlxuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gc2VhcmNoIGZvci5cbiAqIEBwYXJhbSB7bnVtYmVyfSBmcm9tSW5kZXggVGhlIGluZGV4IHRvIHNlYXJjaCBmcm9tLlxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgaW5kZXggb2YgdGhlIG1hdGNoZWQgdmFsdWUsIGVsc2UgYC0xYC5cbiAqL1xuZnVuY3Rpb24gYmFzZUluZGV4T2YoYXJyYXksIHZhbHVlLCBmcm9tSW5kZXgpIHtcbiAgaWYgKHZhbHVlICE9PSB2YWx1ZSkge1xuICAgIHJldHVybiBpbmRleE9mTmFOKGFycmF5LCBmcm9tSW5kZXgpO1xuICB9XG4gIHZhciBpbmRleCA9IGZyb21JbmRleCAtIDEsXG4gICAgICBsZW5ndGggPSBhcnJheS5sZW5ndGg7XG5cbiAgd2hpbGUgKCsraW5kZXggPCBsZW5ndGgpIHtcbiAgICBpZiAoYXJyYXlbaW5kZXhdID09PSB2YWx1ZSkge1xuICAgICAgcmV0dXJuIGluZGV4O1xuICAgIH1cbiAgfVxuICByZXR1cm4gLTE7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZUluZGV4T2Y7XG4iLCJ2YXIgYmFzZUlzRXF1YWxEZWVwID0gcmVxdWlyZSgnLi9iYXNlSXNFcXVhbERlZXAnKSxcbiAgICBpc09iamVjdCA9IHJlcXVpcmUoJy4uL2xhbmcvaXNPYmplY3QnKSxcbiAgICBpc09iamVjdExpa2UgPSByZXF1aXJlKCcuL2lzT2JqZWN0TGlrZScpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLmlzRXF1YWxgIHdpdGhvdXQgc3VwcG9ydCBmb3IgYHRoaXNgIGJpbmRpbmdcbiAqIGBjdXN0b21pemVyYCBmdW5jdGlvbnMuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNvbXBhcmUuXG4gKiBAcGFyYW0geyp9IG90aGVyIFRoZSBvdGhlciB2YWx1ZSB0byBjb21wYXJlLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gW2N1c3RvbWl6ZXJdIFRoZSBmdW5jdGlvbiB0byBjdXN0b21pemUgY29tcGFyaW5nIHZhbHVlcy5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW2lzTG9vc2VdIFNwZWNpZnkgcGVyZm9ybWluZyBwYXJ0aWFsIGNvbXBhcmlzb25zLlxuICogQHBhcmFtIHtBcnJheX0gW3N0YWNrQV0gVHJhY2tzIHRyYXZlcnNlZCBgdmFsdWVgIG9iamVjdHMuXG4gKiBAcGFyYW0ge0FycmF5fSBbc3RhY2tCXSBUcmFja3MgdHJhdmVyc2VkIGBvdGhlcmAgb2JqZWN0cy5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiB0aGUgdmFsdWVzIGFyZSBlcXVpdmFsZW50LCBlbHNlIGBmYWxzZWAuXG4gKi9cbmZ1bmN0aW9uIGJhc2VJc0VxdWFsKHZhbHVlLCBvdGhlciwgY3VzdG9taXplciwgaXNMb29zZSwgc3RhY2tBLCBzdGFja0IpIHtcbiAgaWYgKHZhbHVlID09PSBvdGhlcikge1xuICAgIHJldHVybiB0cnVlO1xuICB9XG4gIGlmICh2YWx1ZSA9PSBudWxsIHx8IG90aGVyID09IG51bGwgfHwgKCFpc09iamVjdCh2YWx1ZSkgJiYgIWlzT2JqZWN0TGlrZShvdGhlcikpKSB7XG4gICAgcmV0dXJuIHZhbHVlICE9PSB2YWx1ZSAmJiBvdGhlciAhPT0gb3RoZXI7XG4gIH1cbiAgcmV0dXJuIGJhc2VJc0VxdWFsRGVlcCh2YWx1ZSwgb3RoZXIsIGJhc2VJc0VxdWFsLCBjdXN0b21pemVyLCBpc0xvb3NlLCBzdGFja0EsIHN0YWNrQik7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZUlzRXF1YWw7XG4iLCJ2YXIgZXF1YWxBcnJheXMgPSByZXF1aXJlKCcuL2VxdWFsQXJyYXlzJyksXG4gICAgZXF1YWxCeVRhZyA9IHJlcXVpcmUoJy4vZXF1YWxCeVRhZycpLFxuICAgIGVxdWFsT2JqZWN0cyA9IHJlcXVpcmUoJy4vZXF1YWxPYmplY3RzJyksXG4gICAgaXNBcnJheSA9IHJlcXVpcmUoJy4uL2xhbmcvaXNBcnJheScpLFxuICAgIGlzSG9zdE9iamVjdCA9IHJlcXVpcmUoJy4vaXNIb3N0T2JqZWN0JyksXG4gICAgaXNUeXBlZEFycmF5ID0gcmVxdWlyZSgnLi4vbGFuZy9pc1R5cGVkQXJyYXknKTtcblxuLyoqIGBPYmplY3QjdG9TdHJpbmdgIHJlc3VsdCByZWZlcmVuY2VzLiAqL1xudmFyIGFyZ3NUYWcgPSAnW29iamVjdCBBcmd1bWVudHNdJyxcbiAgICBhcnJheVRhZyA9ICdbb2JqZWN0IEFycmF5XScsXG4gICAgb2JqZWN0VGFnID0gJ1tvYmplY3QgT2JqZWN0XSc7XG5cbi8qKiBVc2VkIGZvciBuYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgb2JqZWN0UHJvdG8gPSBPYmplY3QucHJvdG90eXBlO1xuXG4vKiogVXNlZCB0byBjaGVjayBvYmplY3RzIGZvciBvd24gcHJvcGVydGllcy4gKi9cbnZhciBoYXNPd25Qcm9wZXJ0eSA9IG9iamVjdFByb3RvLmhhc093blByb3BlcnR5O1xuXG4vKipcbiAqIFVzZWQgdG8gcmVzb2x2ZSB0aGUgW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzYuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmpUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKipcbiAqIEEgc3BlY2lhbGl6ZWQgdmVyc2lvbiBvZiBgYmFzZUlzRXF1YWxgIGZvciBhcnJheXMgYW5kIG9iamVjdHMgd2hpY2ggcGVyZm9ybXNcbiAqIGRlZXAgY29tcGFyaXNvbnMgYW5kIHRyYWNrcyB0cmF2ZXJzZWQgb2JqZWN0cyBlbmFibGluZyBvYmplY3RzIHdpdGggY2lyY3VsYXJcbiAqIHJlZmVyZW5jZXMgdG8gYmUgY29tcGFyZWQuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmplY3QgVGhlIG9iamVjdCB0byBjb21wYXJlLlxuICogQHBhcmFtIHtPYmplY3R9IG90aGVyIFRoZSBvdGhlciBvYmplY3QgdG8gY29tcGFyZS5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGVxdWFsRnVuYyBUaGUgZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIGVxdWl2YWxlbnRzIG9mIHZhbHVlcy5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IFtjdXN0b21pemVyXSBUaGUgZnVuY3Rpb24gdG8gY3VzdG9taXplIGNvbXBhcmluZyBvYmplY3RzLlxuICogQHBhcmFtIHtib29sZWFufSBbaXNMb29zZV0gU3BlY2lmeSBwZXJmb3JtaW5nIHBhcnRpYWwgY29tcGFyaXNvbnMuXG4gKiBAcGFyYW0ge0FycmF5fSBbc3RhY2tBPVtdXSBUcmFja3MgdHJhdmVyc2VkIGB2YWx1ZWAgb2JqZWN0cy5cbiAqIEBwYXJhbSB7QXJyYXl9IFtzdGFja0I9W11dIFRyYWNrcyB0cmF2ZXJzZWQgYG90aGVyYCBvYmplY3RzLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIHRoZSBvYmplY3RzIGFyZSBlcXVpdmFsZW50LCBlbHNlIGBmYWxzZWAuXG4gKi9cbmZ1bmN0aW9uIGJhc2VJc0VxdWFsRGVlcChvYmplY3QsIG90aGVyLCBlcXVhbEZ1bmMsIGN1c3RvbWl6ZXIsIGlzTG9vc2UsIHN0YWNrQSwgc3RhY2tCKSB7XG4gIHZhciBvYmpJc0FyciA9IGlzQXJyYXkob2JqZWN0KSxcbiAgICAgIG90aElzQXJyID0gaXNBcnJheShvdGhlciksXG4gICAgICBvYmpUYWcgPSBhcnJheVRhZyxcbiAgICAgIG90aFRhZyA9IGFycmF5VGFnO1xuXG4gIGlmICghb2JqSXNBcnIpIHtcbiAgICBvYmpUYWcgPSBvYmpUb1N0cmluZy5jYWxsKG9iamVjdCk7XG4gICAgaWYgKG9ialRhZyA9PSBhcmdzVGFnKSB7XG4gICAgICBvYmpUYWcgPSBvYmplY3RUYWc7XG4gICAgfSBlbHNlIGlmIChvYmpUYWcgIT0gb2JqZWN0VGFnKSB7XG4gICAgICBvYmpJc0FyciA9IGlzVHlwZWRBcnJheShvYmplY3QpO1xuICAgIH1cbiAgfVxuICBpZiAoIW90aElzQXJyKSB7XG4gICAgb3RoVGFnID0gb2JqVG9TdHJpbmcuY2FsbChvdGhlcik7XG4gICAgaWYgKG90aFRhZyA9PSBhcmdzVGFnKSB7XG4gICAgICBvdGhUYWcgPSBvYmplY3RUYWc7XG4gICAgfSBlbHNlIGlmIChvdGhUYWcgIT0gb2JqZWN0VGFnKSB7XG4gICAgICBvdGhJc0FyciA9IGlzVHlwZWRBcnJheShvdGhlcik7XG4gICAgfVxuICB9XG4gIHZhciBvYmpJc09iaiA9IG9ialRhZyA9PSBvYmplY3RUYWcgJiYgIWlzSG9zdE9iamVjdChvYmplY3QpLFxuICAgICAgb3RoSXNPYmogPSBvdGhUYWcgPT0gb2JqZWN0VGFnICYmICFpc0hvc3RPYmplY3Qob3RoZXIpLFxuICAgICAgaXNTYW1lVGFnID0gb2JqVGFnID09IG90aFRhZztcblxuICBpZiAoaXNTYW1lVGFnICYmICEob2JqSXNBcnIgfHwgb2JqSXNPYmopKSB7XG4gICAgcmV0dXJuIGVxdWFsQnlUYWcob2JqZWN0LCBvdGhlciwgb2JqVGFnKTtcbiAgfVxuICBpZiAoIWlzTG9vc2UpIHtcbiAgICB2YXIgb2JqSXNXcmFwcGVkID0gb2JqSXNPYmogJiYgaGFzT3duUHJvcGVydHkuY2FsbChvYmplY3QsICdfX3dyYXBwZWRfXycpLFxuICAgICAgICBvdGhJc1dyYXBwZWQgPSBvdGhJc09iaiAmJiBoYXNPd25Qcm9wZXJ0eS5jYWxsKG90aGVyLCAnX193cmFwcGVkX18nKTtcblxuICAgIGlmIChvYmpJc1dyYXBwZWQgfHwgb3RoSXNXcmFwcGVkKSB7XG4gICAgICByZXR1cm4gZXF1YWxGdW5jKG9iaklzV3JhcHBlZCA/IG9iamVjdC52YWx1ZSgpIDogb2JqZWN0LCBvdGhJc1dyYXBwZWQgPyBvdGhlci52YWx1ZSgpIDogb3RoZXIsIGN1c3RvbWl6ZXIsIGlzTG9vc2UsIHN0YWNrQSwgc3RhY2tCKTtcbiAgICB9XG4gIH1cbiAgaWYgKCFpc1NhbWVUYWcpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgLy8gQXNzdW1lIGN5Y2xpYyB2YWx1ZXMgYXJlIGVxdWFsLlxuICAvLyBGb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiBkZXRlY3RpbmcgY2lyY3VsYXIgcmVmZXJlbmNlcyBzZWUgaHR0cHM6Ly9lczUuZ2l0aHViLmlvLyNKTy5cbiAgc3RhY2tBIHx8IChzdGFja0EgPSBbXSk7XG4gIHN0YWNrQiB8fCAoc3RhY2tCID0gW10pO1xuXG4gIHZhciBsZW5ndGggPSBzdGFja0EubGVuZ3RoO1xuICB3aGlsZSAobGVuZ3RoLS0pIHtcbiAgICBpZiAoc3RhY2tBW2xlbmd0aF0gPT0gb2JqZWN0KSB7XG4gICAgICByZXR1cm4gc3RhY2tCW2xlbmd0aF0gPT0gb3RoZXI7XG4gICAgfVxuICB9XG4gIC8vIEFkZCBgb2JqZWN0YCBhbmQgYG90aGVyYCB0byB0aGUgc3RhY2sgb2YgdHJhdmVyc2VkIG9iamVjdHMuXG4gIHN0YWNrQS5wdXNoKG9iamVjdCk7XG4gIHN0YWNrQi5wdXNoKG90aGVyKTtcblxuICB2YXIgcmVzdWx0ID0gKG9iaklzQXJyID8gZXF1YWxBcnJheXMgOiBlcXVhbE9iamVjdHMpKG9iamVjdCwgb3RoZXIsIGVxdWFsRnVuYywgY3VzdG9taXplciwgaXNMb29zZSwgc3RhY2tBLCBzdGFja0IpO1xuXG4gIHN0YWNrQS5wb3AoKTtcbiAgc3RhY2tCLnBvcCgpO1xuXG4gIHJldHVybiByZXN1bHQ7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZUlzRXF1YWxEZWVwO1xuIiwidmFyIGJhc2VJc0VxdWFsID0gcmVxdWlyZSgnLi9iYXNlSXNFcXVhbCcpLFxuICAgIHRvT2JqZWN0ID0gcmVxdWlyZSgnLi90b09iamVjdCcpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLmlzTWF0Y2hgIHdpdGhvdXQgc3VwcG9ydCBmb3IgY2FsbGJhY2tcbiAqIHNob3J0aGFuZHMgYW5kIGB0aGlzYCBiaW5kaW5nLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gaW5zcGVjdC5cbiAqIEBwYXJhbSB7QXJyYXl9IG1hdGNoRGF0YSBUaGUgcHJvcGVyeSBuYW1lcywgdmFsdWVzLCBhbmQgY29tcGFyZSBmbGFncyB0byBtYXRjaC5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IFtjdXN0b21pemVyXSBUaGUgZnVuY3Rpb24gdG8gY3VzdG9taXplIGNvbXBhcmluZyBvYmplY3RzLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGBvYmplY3RgIGlzIGEgbWF0Y2gsIGVsc2UgYGZhbHNlYC5cbiAqL1xuZnVuY3Rpb24gYmFzZUlzTWF0Y2gob2JqZWN0LCBtYXRjaERhdGEsIGN1c3RvbWl6ZXIpIHtcbiAgdmFyIGluZGV4ID0gbWF0Y2hEYXRhLmxlbmd0aCxcbiAgICAgIGxlbmd0aCA9IGluZGV4LFxuICAgICAgbm9DdXN0b21pemVyID0gIWN1c3RvbWl6ZXI7XG5cbiAgaWYgKG9iamVjdCA9PSBudWxsKSB7XG4gICAgcmV0dXJuICFsZW5ndGg7XG4gIH1cbiAgb2JqZWN0ID0gdG9PYmplY3Qob2JqZWN0KTtcbiAgd2hpbGUgKGluZGV4LS0pIHtcbiAgICB2YXIgZGF0YSA9IG1hdGNoRGF0YVtpbmRleF07XG4gICAgaWYgKChub0N1c3RvbWl6ZXIgJiYgZGF0YVsyXSlcbiAgICAgICAgICA/IGRhdGFbMV0gIT09IG9iamVjdFtkYXRhWzBdXVxuICAgICAgICAgIDogIShkYXRhWzBdIGluIG9iamVjdClcbiAgICAgICAgKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICB9XG4gIHdoaWxlICgrK2luZGV4IDwgbGVuZ3RoKSB7XG4gICAgZGF0YSA9IG1hdGNoRGF0YVtpbmRleF07XG4gICAgdmFyIGtleSA9IGRhdGFbMF0sXG4gICAgICAgIG9ialZhbHVlID0gb2JqZWN0W2tleV0sXG4gICAgICAgIHNyY1ZhbHVlID0gZGF0YVsxXTtcblxuICAgIGlmIChub0N1c3RvbWl6ZXIgJiYgZGF0YVsyXSkge1xuICAgICAgaWYgKG9ialZhbHVlID09PSB1bmRlZmluZWQgJiYgIShrZXkgaW4gb2JqZWN0KSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciByZXN1bHQgPSBjdXN0b21pemVyID8gY3VzdG9taXplcihvYmpWYWx1ZSwgc3JjVmFsdWUsIGtleSkgOiB1bmRlZmluZWQ7XG4gICAgICBpZiAoIShyZXN1bHQgPT09IHVuZGVmaW5lZCA/IGJhc2VJc0VxdWFsKHNyY1ZhbHVlLCBvYmpWYWx1ZSwgY3VzdG9taXplciwgdHJ1ZSkgOiByZXN1bHQpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0dXJuIHRydWU7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZUlzTWF0Y2g7XG4iLCIvKipcbiAqIFRoZSBmdW5jdGlvbiB3aG9zZSBwcm90b3R5cGUgYWxsIGNoYWluaW5nIHdyYXBwZXJzIGluaGVyaXQgZnJvbS5cbiAqXG4gKiBAcHJpdmF0ZVxuICovXG5mdW5jdGlvbiBiYXNlTG9kYXNoKCkge1xuICAvLyBObyBvcGVyYXRpb24gcGVyZm9ybWVkLlxufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJhc2VMb2Rhc2g7XG4iLCJ2YXIgYmFzZUVhY2ggPSByZXF1aXJlKCcuL2Jhc2VFYWNoJyksXG4gICAgaXNBcnJheUxpa2UgPSByZXF1aXJlKCcuL2lzQXJyYXlMaWtlJyk7XG5cbi8qKlxuICogVGhlIGJhc2UgaW1wbGVtZW50YXRpb24gb2YgYF8ubWFwYCB3aXRob3V0IHN1cHBvcnQgZm9yIGNhbGxiYWNrIHNob3J0aGFuZHNcbiAqIGFuZCBgdGhpc2AgYmluZGluZy5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtBcnJheXxPYmplY3R8c3RyaW5nfSBjb2xsZWN0aW9uIFRoZSBjb2xsZWN0aW9uIHRvIGl0ZXJhdGUgb3Zlci5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGl0ZXJhdGVlIFRoZSBmdW5jdGlvbiBpbnZva2VkIHBlciBpdGVyYXRpb24uXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIG5ldyBtYXBwZWQgYXJyYXkuXG4gKi9cbmZ1bmN0aW9uIGJhc2VNYXAoY29sbGVjdGlvbiwgaXRlcmF0ZWUpIHtcbiAgdmFyIGluZGV4ID0gLTEsXG4gICAgICByZXN1bHQgPSBpc0FycmF5TGlrZShjb2xsZWN0aW9uKSA/IEFycmF5KGNvbGxlY3Rpb24ubGVuZ3RoKSA6IFtdO1xuXG4gIGJhc2VFYWNoKGNvbGxlY3Rpb24sIGZ1bmN0aW9uKHZhbHVlLCBrZXksIGNvbGxlY3Rpb24pIHtcbiAgICByZXN1bHRbKytpbmRleF0gPSBpdGVyYXRlZSh2YWx1ZSwga2V5LCBjb2xsZWN0aW9uKTtcbiAgfSk7XG4gIHJldHVybiByZXN1bHQ7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZU1hcDtcbiIsInZhciBiYXNlSXNNYXRjaCA9IHJlcXVpcmUoJy4vYmFzZUlzTWF0Y2gnKSxcbiAgICBnZXRNYXRjaERhdGEgPSByZXF1aXJlKCcuL2dldE1hdGNoRGF0YScpLFxuICAgIHRvT2JqZWN0ID0gcmVxdWlyZSgnLi90b09iamVjdCcpO1xuXG4vKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLm1hdGNoZXNgIHdoaWNoIGRvZXMgbm90IGNsb25lIGBzb3VyY2VgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gc291cmNlIFRoZSBvYmplY3Qgb2YgcHJvcGVydHkgdmFsdWVzIHRvIG1hdGNoLlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgZnVuY3Rpb24uXG4gKi9cbmZ1bmN0aW9uIGJhc2VNYXRjaGVzKHNvdXJjZSkge1xuICB2YXIgbWF0Y2hEYXRhID0gZ2V0TWF0Y2hEYXRhKHNvdXJjZSk7XG4gIGlmIChtYXRjaERhdGEubGVuZ3RoID09IDEgJiYgbWF0Y2hEYXRhWzBdWzJdKSB7XG4gICAgdmFyIGtleSA9IG1hdGNoRGF0YVswXVswXSxcbiAgICAgICAgdmFsdWUgPSBtYXRjaERhdGFbMF1bMV07XG5cbiAgICByZXR1cm4gZnVuY3Rpb24ob2JqZWN0KSB7XG4gICAgICBpZiAob2JqZWN0ID09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgb2JqZWN0ID0gdG9PYmplY3Qob2JqZWN0KTtcbiAgICAgIHJldHVybiBvYmplY3Rba2V5XSA9PT0gdmFsdWUgJiYgKHZhbHVlICE9PSB1bmRlZmluZWQgfHwgKGtleSBpbiBvYmplY3QpKTtcbiAgICB9O1xuICB9XG4gIHJldHVybiBmdW5jdGlvbihvYmplY3QpIHtcbiAgICByZXR1cm4gYmFzZUlzTWF0Y2gob2JqZWN0LCBtYXRjaERhdGEpO1xuICB9O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJhc2VNYXRjaGVzO1xuIiwidmFyIGJhc2VHZXQgPSByZXF1aXJlKCcuL2Jhc2VHZXQnKSxcbiAgICBiYXNlSXNFcXVhbCA9IHJlcXVpcmUoJy4vYmFzZUlzRXF1YWwnKSxcbiAgICBiYXNlU2xpY2UgPSByZXF1aXJlKCcuL2Jhc2VTbGljZScpLFxuICAgIGlzQXJyYXkgPSByZXF1aXJlKCcuLi9sYW5nL2lzQXJyYXknKSxcbiAgICBpc0tleSA9IHJlcXVpcmUoJy4vaXNLZXknKSxcbiAgICBpc1N0cmljdENvbXBhcmFibGUgPSByZXF1aXJlKCcuL2lzU3RyaWN0Q29tcGFyYWJsZScpLFxuICAgIGxhc3QgPSByZXF1aXJlKCcuLi9hcnJheS9sYXN0JyksXG4gICAgdG9PYmplY3QgPSByZXF1aXJlKCcuL3RvT2JqZWN0JyksXG4gICAgdG9QYXRoID0gcmVxdWlyZSgnLi90b1BhdGgnKTtcblxuLyoqXG4gKiBUaGUgYmFzZSBpbXBsZW1lbnRhdGlvbiBvZiBgXy5tYXRjaGVzUHJvcGVydHlgIHdoaWNoIGRvZXMgbm90IGNsb25lIGBzcmNWYWx1ZWAuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7c3RyaW5nfSBwYXRoIFRoZSBwYXRoIG9mIHRoZSBwcm9wZXJ0eSB0byBnZXQuXG4gKiBAcGFyYW0geyp9IHNyY1ZhbHVlIFRoZSB2YWx1ZSB0byBjb21wYXJlLlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgZnVuY3Rpb24uXG4gKi9cbmZ1bmN0aW9uIGJhc2VNYXRjaGVzUHJvcGVydHkocGF0aCwgc3JjVmFsdWUpIHtcbiAgdmFyIGlzQXJyID0gaXNBcnJheShwYXRoKSxcbiAgICAgIGlzQ29tbW9uID0gaXNLZXkocGF0aCkgJiYgaXNTdHJpY3RDb21wYXJhYmxlKHNyY1ZhbHVlKSxcbiAgICAgIHBhdGhLZXkgPSAocGF0aCArICcnKTtcblxuICBwYXRoID0gdG9QYXRoKHBhdGgpO1xuICByZXR1cm4gZnVuY3Rpb24ob2JqZWN0KSB7XG4gICAgaWYgKG9iamVjdCA9PSBudWxsKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHZhciBrZXkgPSBwYXRoS2V5O1xuICAgIG9iamVjdCA9IHRvT2JqZWN0KG9iamVjdCk7XG4gICAgaWYgKChpc0FyciB8fCAhaXNDb21tb24pICYmICEoa2V5IGluIG9iamVjdCkpIHtcbiAgICAgIG9iamVjdCA9IHBhdGgubGVuZ3RoID09IDEgPyBvYmplY3QgOiBiYXNlR2V0KG9iamVjdCwgYmFzZVNsaWNlKHBhdGgsIDAsIC0xKSk7XG4gICAgICBpZiAob2JqZWN0ID09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAga2V5ID0gbGFzdChwYXRoKTtcbiAgICAgIG9iamVjdCA9IHRvT2JqZWN0KG9iamVjdCk7XG4gICAgfVxuICAgIHJldHVybiBvYmplY3Rba2V5XSA9PT0gc3JjVmFsdWVcbiAgICAgID8gKHNyY1ZhbHVlICE9PSB1bmRlZmluZWQgfHwgKGtleSBpbiBvYmplY3QpKVxuICAgICAgOiBiYXNlSXNFcXVhbChzcmNWYWx1ZSwgb2JqZWN0W2tleV0sIHVuZGVmaW5lZCwgdHJ1ZSk7XG4gIH07XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZU1hdGNoZXNQcm9wZXJ0eTtcbiIsInZhciB0b09iamVjdCA9IHJlcXVpcmUoJy4vdG9PYmplY3QnKTtcblxuLyoqXG4gKiBUaGUgYmFzZSBpbXBsZW1lbnRhdGlvbiBvZiBgXy5wcm9wZXJ0eWAgd2l0aG91dCBzdXBwb3J0IGZvciBkZWVwIHBhdGhzLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge3N0cmluZ30ga2V5IFRoZSBrZXkgb2YgdGhlIHByb3BlcnR5IHRvIGdldC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGZ1bmN0aW9uLlxuICovXG5mdW5jdGlvbiBiYXNlUHJvcGVydHkoa2V5KSB7XG4gIHJldHVybiBmdW5jdGlvbihvYmplY3QpIHtcbiAgICByZXR1cm4gb2JqZWN0ID09IG51bGwgPyB1bmRlZmluZWQgOiB0b09iamVjdChvYmplY3QpW2tleV07XG4gIH07XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZVByb3BlcnR5O1xuIiwidmFyIGJhc2VHZXQgPSByZXF1aXJlKCcuL2Jhc2VHZXQnKSxcbiAgICB0b1BhdGggPSByZXF1aXJlKCcuL3RvUGF0aCcpO1xuXG4vKipcbiAqIEEgc3BlY2lhbGl6ZWQgdmVyc2lvbiBvZiBgYmFzZVByb3BlcnR5YCB3aGljaCBzdXBwb3J0cyBkZWVwIHBhdGhzLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5fHN0cmluZ30gcGF0aCBUaGUgcGF0aCBvZiB0aGUgcHJvcGVydHkgdG8gZ2V0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgZnVuY3Rpb24uXG4gKi9cbmZ1bmN0aW9uIGJhc2VQcm9wZXJ0eURlZXAocGF0aCkge1xuICB2YXIgcGF0aEtleSA9IChwYXRoICsgJycpO1xuICBwYXRoID0gdG9QYXRoKHBhdGgpO1xuICByZXR1cm4gZnVuY3Rpb24ob2JqZWN0KSB7XG4gICAgcmV0dXJuIGJhc2VHZXQob2JqZWN0LCBwYXRoLCBwYXRoS2V5KTtcbiAgfTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBiYXNlUHJvcGVydHlEZWVwO1xuIiwidmFyIGlkZW50aXR5ID0gcmVxdWlyZSgnLi4vdXRpbGl0eS9pZGVudGl0eScpLFxuICAgIG1ldGFNYXAgPSByZXF1aXJlKCcuL21ldGFNYXAnKTtcblxuLyoqXG4gKiBUaGUgYmFzZSBpbXBsZW1lbnRhdGlvbiBvZiBgc2V0RGF0YWAgd2l0aG91dCBzdXBwb3J0IGZvciBob3QgbG9vcCBkZXRlY3Rpb24uXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGFzc29jaWF0ZSBtZXRhZGF0YSB3aXRoLlxuICogQHBhcmFtIHsqfSBkYXRhIFRoZSBtZXRhZGF0YS5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyBgZnVuY2AuXG4gKi9cbnZhciBiYXNlU2V0RGF0YSA9ICFtZXRhTWFwID8gaWRlbnRpdHkgOiBmdW5jdGlvbihmdW5jLCBkYXRhKSB7XG4gIG1ldGFNYXAuc2V0KGZ1bmMsIGRhdGEpO1xuICByZXR1cm4gZnVuYztcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gYmFzZVNldERhdGE7XG4iLCIvKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLnNsaWNlYCB3aXRob3V0IGFuIGl0ZXJhdGVlIGNhbGwgZ3VhcmQuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl9IGFycmF5IFRoZSBhcnJheSB0byBzbGljZS5cbiAqIEBwYXJhbSB7bnVtYmVyfSBbc3RhcnQ9MF0gVGhlIHN0YXJ0IHBvc2l0aW9uLlxuICogQHBhcmFtIHtudW1iZXJ9IFtlbmQ9YXJyYXkubGVuZ3RoXSBUaGUgZW5kIHBvc2l0aW9uLlxuICogQHJldHVybnMge0FycmF5fSBSZXR1cm5zIHRoZSBzbGljZSBvZiBgYXJyYXlgLlxuICovXG5mdW5jdGlvbiBiYXNlU2xpY2UoYXJyYXksIHN0YXJ0LCBlbmQpIHtcbiAgdmFyIGluZGV4ID0gLTEsXG4gICAgICBsZW5ndGggPSBhcnJheS5sZW5ndGg7XG5cbiAgc3RhcnQgPSBzdGFydCA9PSBudWxsID8gMCA6ICgrc3RhcnQgfHwgMCk7XG4gIGlmIChzdGFydCA8IDApIHtcbiAgICBzdGFydCA9IC1zdGFydCA+IGxlbmd0aCA/IDAgOiAobGVuZ3RoICsgc3RhcnQpO1xuICB9XG4gIGVuZCA9IChlbmQgPT09IHVuZGVmaW5lZCB8fCBlbmQgPiBsZW5ndGgpID8gbGVuZ3RoIDogKCtlbmQgfHwgMCk7XG4gIGlmIChlbmQgPCAwKSB7XG4gICAgZW5kICs9IGxlbmd0aDtcbiAgfVxuICBsZW5ndGggPSBzdGFydCA+IGVuZCA/IDAgOiAoKGVuZCAtIHN0YXJ0KSA+Pj4gMCk7XG4gIHN0YXJ0ID4+Pj0gMDtcblxuICB2YXIgcmVzdWx0ID0gQXJyYXkobGVuZ3RoKTtcbiAgd2hpbGUgKCsraW5kZXggPCBsZW5ndGgpIHtcbiAgICByZXN1bHRbaW5kZXhdID0gYXJyYXlbaW5kZXggKyBzdGFydF07XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBiYXNlU2xpY2U7XG4iLCIvKipcbiAqIENvbnZlcnRzIGB2YWx1ZWAgdG8gYSBzdHJpbmcgaWYgaXQncyBub3Qgb25lLiBBbiBlbXB0eSBzdHJpbmcgaXMgcmV0dXJuZWRcbiAqIGZvciBgbnVsbGAgb3IgYHVuZGVmaW5lZGAgdmFsdWVzLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBwcm9jZXNzLlxuICogQHJldHVybnMge3N0cmluZ30gUmV0dXJucyB0aGUgc3RyaW5nLlxuICovXG5mdW5jdGlvbiBiYXNlVG9TdHJpbmcodmFsdWUpIHtcbiAgcmV0dXJuIHZhbHVlID09IG51bGwgPyAnJyA6ICh2YWx1ZSArICcnKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBiYXNlVG9TdHJpbmc7XG4iLCIvKipcbiAqIFRoZSBiYXNlIGltcGxlbWVudGF0aW9uIG9mIGBfLnZhbHVlc2AgYW5kIGBfLnZhbHVlc0luYCB3aGljaCBjcmVhdGVzIGFuXG4gKiBhcnJheSBvZiBgb2JqZWN0YCBwcm9wZXJ0eSB2YWx1ZXMgY29ycmVzcG9uZGluZyB0byB0aGUgcHJvcGVydHkgbmFtZXNcbiAqIG9mIGBwcm9wc2AuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmplY3QgVGhlIG9iamVjdCB0byBxdWVyeS5cbiAqIEBwYXJhbSB7QXJyYXl9IHByb3BzIFRoZSBwcm9wZXJ0eSBuYW1lcyB0byBnZXQgdmFsdWVzIGZvci5cbiAqIEByZXR1cm5zIHtPYmplY3R9IFJldHVybnMgdGhlIGFycmF5IG9mIHByb3BlcnR5IHZhbHVlcy5cbiAqL1xuZnVuY3Rpb24gYmFzZVZhbHVlcyhvYmplY3QsIHByb3BzKSB7XG4gIHZhciBpbmRleCA9IC0xLFxuICAgICAgbGVuZ3RoID0gcHJvcHMubGVuZ3RoLFxuICAgICAgcmVzdWx0ID0gQXJyYXkobGVuZ3RoKTtcblxuICB3aGlsZSAoKytpbmRleCA8IGxlbmd0aCkge1xuICAgIHJlc3VsdFtpbmRleF0gPSBvYmplY3RbcHJvcHNbaW5kZXhdXTtcbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJhc2VWYWx1ZXM7XG4iLCJ2YXIgYmluYXJ5SW5kZXhCeSA9IHJlcXVpcmUoJy4vYmluYXJ5SW5kZXhCeScpLFxuICAgIGlkZW50aXR5ID0gcmVxdWlyZSgnLi4vdXRpbGl0eS9pZGVudGl0eScpO1xuXG4vKiogVXNlZCBhcyByZWZlcmVuY2VzIGZvciB0aGUgbWF4aW11bSBsZW5ndGggYW5kIGluZGV4IG9mIGFuIGFycmF5LiAqL1xudmFyIE1BWF9BUlJBWV9MRU5HVEggPSA0Mjk0OTY3Mjk1LFxuICAgIEhBTEZfTUFYX0FSUkFZX0xFTkdUSCA9IE1BWF9BUlJBWV9MRU5HVEggPj4+IDE7XG5cbi8qKlxuICogUGVyZm9ybXMgYSBiaW5hcnkgc2VhcmNoIG9mIGBhcnJheWAgdG8gZGV0ZXJtaW5lIHRoZSBpbmRleCBhdCB3aGljaCBgdmFsdWVgXG4gKiBzaG91bGQgYmUgaW5zZXJ0ZWQgaW50byBgYXJyYXlgIGluIG9yZGVyIHRvIG1haW50YWluIGl0cyBzb3J0IG9yZGVyLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgc29ydGVkIGFycmF5IHRvIGluc3BlY3QuXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBldmFsdWF0ZS5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW3JldEhpZ2hlc3RdIFNwZWNpZnkgcmV0dXJuaW5nIHRoZSBoaWdoZXN0IHF1YWxpZmllZCBpbmRleC5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIGluZGV4IGF0IHdoaWNoIGB2YWx1ZWAgc2hvdWxkIGJlIGluc2VydGVkXG4gKiAgaW50byBgYXJyYXlgLlxuICovXG5mdW5jdGlvbiBiaW5hcnlJbmRleChhcnJheSwgdmFsdWUsIHJldEhpZ2hlc3QpIHtcbiAgdmFyIGxvdyA9IDAsXG4gICAgICBoaWdoID0gYXJyYXkgPyBhcnJheS5sZW5ndGggOiBsb3c7XG5cbiAgaWYgKHR5cGVvZiB2YWx1ZSA9PSAnbnVtYmVyJyAmJiB2YWx1ZSA9PT0gdmFsdWUgJiYgaGlnaCA8PSBIQUxGX01BWF9BUlJBWV9MRU5HVEgpIHtcbiAgICB3aGlsZSAobG93IDwgaGlnaCkge1xuICAgICAgdmFyIG1pZCA9IChsb3cgKyBoaWdoKSA+Pj4gMSxcbiAgICAgICAgICBjb21wdXRlZCA9IGFycmF5W21pZF07XG5cbiAgICAgIGlmICgocmV0SGlnaGVzdCA/IChjb21wdXRlZCA8PSB2YWx1ZSkgOiAoY29tcHV0ZWQgPCB2YWx1ZSkpICYmIGNvbXB1dGVkICE9PSBudWxsKSB7XG4gICAgICAgIGxvdyA9IG1pZCArIDE7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBoaWdoID0gbWlkO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gaGlnaDtcbiAgfVxuICByZXR1cm4gYmluYXJ5SW5kZXhCeShhcnJheSwgdmFsdWUsIGlkZW50aXR5LCByZXRIaWdoZXN0KTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBiaW5hcnlJbmRleDtcbiIsIi8qIE5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlRmxvb3IgPSBNYXRoLmZsb29yLFxuICAgIG5hdGl2ZU1pbiA9IE1hdGgubWluO1xuXG4vKiogVXNlZCBhcyByZWZlcmVuY2VzIGZvciB0aGUgbWF4aW11bSBsZW5ndGggYW5kIGluZGV4IG9mIGFuIGFycmF5LiAqL1xudmFyIE1BWF9BUlJBWV9MRU5HVEggPSA0Mjk0OTY3Mjk1LFxuICAgIE1BWF9BUlJBWV9JTkRFWCA9IE1BWF9BUlJBWV9MRU5HVEggLSAxO1xuXG4vKipcbiAqIFRoaXMgZnVuY3Rpb24gaXMgbGlrZSBgYmluYXJ5SW5kZXhgIGV4Y2VwdCB0aGF0IGl0IGludm9rZXMgYGl0ZXJhdGVlYCBmb3JcbiAqIGB2YWx1ZWAgYW5kIGVhY2ggZWxlbWVudCBvZiBgYXJyYXlgIHRvIGNvbXB1dGUgdGhlaXIgc29ydCByYW5raW5nLiBUaGVcbiAqIGl0ZXJhdGVlIGlzIGludm9rZWQgd2l0aCBvbmUgYXJndW1lbnQ7ICh2YWx1ZSkuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl9IGFycmF5IFRoZSBzb3J0ZWQgYXJyYXkgdG8gaW5zcGVjdC5cbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGV2YWx1YXRlLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gaXRlcmF0ZWUgVGhlIGZ1bmN0aW9uIGludm9rZWQgcGVyIGl0ZXJhdGlvbi5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW3JldEhpZ2hlc3RdIFNwZWNpZnkgcmV0dXJuaW5nIHRoZSBoaWdoZXN0IHF1YWxpZmllZCBpbmRleC5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIGluZGV4IGF0IHdoaWNoIGB2YWx1ZWAgc2hvdWxkIGJlIGluc2VydGVkXG4gKiAgaW50byBgYXJyYXlgLlxuICovXG5mdW5jdGlvbiBiaW5hcnlJbmRleEJ5KGFycmF5LCB2YWx1ZSwgaXRlcmF0ZWUsIHJldEhpZ2hlc3QpIHtcbiAgdmFsdWUgPSBpdGVyYXRlZSh2YWx1ZSk7XG5cbiAgdmFyIGxvdyA9IDAsXG4gICAgICBoaWdoID0gYXJyYXkgPyBhcnJheS5sZW5ndGggOiAwLFxuICAgICAgdmFsSXNOYU4gPSB2YWx1ZSAhPT0gdmFsdWUsXG4gICAgICB2YWxJc051bGwgPSB2YWx1ZSA9PT0gbnVsbCxcbiAgICAgIHZhbElzVW5kZWYgPSB2YWx1ZSA9PT0gdW5kZWZpbmVkO1xuXG4gIHdoaWxlIChsb3cgPCBoaWdoKSB7XG4gICAgdmFyIG1pZCA9IG5hdGl2ZUZsb29yKChsb3cgKyBoaWdoKSAvIDIpLFxuICAgICAgICBjb21wdXRlZCA9IGl0ZXJhdGVlKGFycmF5W21pZF0pLFxuICAgICAgICBpc0RlZiA9IGNvbXB1dGVkICE9PSB1bmRlZmluZWQsXG4gICAgICAgIGlzUmVmbGV4aXZlID0gY29tcHV0ZWQgPT09IGNvbXB1dGVkO1xuXG4gICAgaWYgKHZhbElzTmFOKSB7XG4gICAgICB2YXIgc2V0TG93ID0gaXNSZWZsZXhpdmUgfHwgcmV0SGlnaGVzdDtcbiAgICB9IGVsc2UgaWYgKHZhbElzTnVsbCkge1xuICAgICAgc2V0TG93ID0gaXNSZWZsZXhpdmUgJiYgaXNEZWYgJiYgKHJldEhpZ2hlc3QgfHwgY29tcHV0ZWQgIT0gbnVsbCk7XG4gICAgfSBlbHNlIGlmICh2YWxJc1VuZGVmKSB7XG4gICAgICBzZXRMb3cgPSBpc1JlZmxleGl2ZSAmJiAocmV0SGlnaGVzdCB8fCBpc0RlZik7XG4gICAgfSBlbHNlIGlmIChjb21wdXRlZCA9PSBudWxsKSB7XG4gICAgICBzZXRMb3cgPSBmYWxzZTtcbiAgICB9IGVsc2Uge1xuICAgICAgc2V0TG93ID0gcmV0SGlnaGVzdCA/IChjb21wdXRlZCA8PSB2YWx1ZSkgOiAoY29tcHV0ZWQgPCB2YWx1ZSk7XG4gICAgfVxuICAgIGlmIChzZXRMb3cpIHtcbiAgICAgIGxvdyA9IG1pZCArIDE7XG4gICAgfSBlbHNlIHtcbiAgICAgIGhpZ2ggPSBtaWQ7XG4gICAgfVxuICB9XG4gIHJldHVybiBuYXRpdmVNaW4oaGlnaCwgTUFYX0FSUkFZX0lOREVYKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBiaW5hcnlJbmRleEJ5O1xuIiwidmFyIGlkZW50aXR5ID0gcmVxdWlyZSgnLi4vdXRpbGl0eS9pZGVudGl0eScpO1xuXG4vKipcbiAqIEEgc3BlY2lhbGl6ZWQgdmVyc2lvbiBvZiBgYmFzZUNhbGxiYWNrYCB3aGljaCBvbmx5IHN1cHBvcnRzIGB0aGlzYCBiaW5kaW5nXG4gKiBhbmQgc3BlY2lmeWluZyB0aGUgbnVtYmVyIG9mIGFyZ3VtZW50cyB0byBwcm92aWRlIHRvIGBmdW5jYC5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gYmluZC5cbiAqIEBwYXJhbSB7Kn0gdGhpc0FyZyBUaGUgYHRoaXNgIGJpbmRpbmcgb2YgYGZ1bmNgLlxuICogQHBhcmFtIHtudW1iZXJ9IFthcmdDb3VudF0gVGhlIG51bWJlciBvZiBhcmd1bWVudHMgdG8gcHJvdmlkZSB0byBgZnVuY2AuXG4gKiBAcmV0dXJucyB7RnVuY3Rpb259IFJldHVybnMgdGhlIGNhbGxiYWNrLlxuICovXG5mdW5jdGlvbiBiaW5kQ2FsbGJhY2soZnVuYywgdGhpc0FyZywgYXJnQ291bnQpIHtcbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICByZXR1cm4gaWRlbnRpdHk7XG4gIH1cbiAgaWYgKHRoaXNBcmcgPT09IHVuZGVmaW5lZCkge1xuICAgIHJldHVybiBmdW5jO1xuICB9XG4gIHN3aXRjaCAoYXJnQ291bnQpIHtcbiAgICBjYXNlIDE6IHJldHVybiBmdW5jdGlvbih2YWx1ZSkge1xuICAgICAgcmV0dXJuIGZ1bmMuY2FsbCh0aGlzQXJnLCB2YWx1ZSk7XG4gICAgfTtcbiAgICBjYXNlIDM6IHJldHVybiBmdW5jdGlvbih2YWx1ZSwgaW5kZXgsIGNvbGxlY3Rpb24pIHtcbiAgICAgIHJldHVybiBmdW5jLmNhbGwodGhpc0FyZywgdmFsdWUsIGluZGV4LCBjb2xsZWN0aW9uKTtcbiAgICB9O1xuICAgIGNhc2UgNDogcmV0dXJuIGZ1bmN0aW9uKGFjY3VtdWxhdG9yLCB2YWx1ZSwgaW5kZXgsIGNvbGxlY3Rpb24pIHtcbiAgICAgIHJldHVybiBmdW5jLmNhbGwodGhpc0FyZywgYWNjdW11bGF0b3IsIHZhbHVlLCBpbmRleCwgY29sbGVjdGlvbik7XG4gICAgfTtcbiAgICBjYXNlIDU6IHJldHVybiBmdW5jdGlvbih2YWx1ZSwgb3RoZXIsIGtleSwgb2JqZWN0LCBzb3VyY2UpIHtcbiAgICAgIHJldHVybiBmdW5jLmNhbGwodGhpc0FyZywgdmFsdWUsIG90aGVyLCBrZXksIG9iamVjdCwgc291cmNlKTtcbiAgICB9O1xuICB9XG4gIHJldHVybiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gZnVuYy5hcHBseSh0aGlzQXJnLCBhcmd1bWVudHMpO1xuICB9O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJpbmRDYWxsYmFjaztcbiIsIihmdW5jdGlvbiAoZ2xvYmFsKXtcbi8qKiBOYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgQXJyYXlCdWZmZXIgPSBnbG9iYWwuQXJyYXlCdWZmZXIsXG4gICAgVWludDhBcnJheSA9IGdsb2JhbC5VaW50OEFycmF5O1xuXG4vKipcbiAqIENyZWF0ZXMgYSBjbG9uZSBvZiB0aGUgZ2l2ZW4gYXJyYXkgYnVmZmVyLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5QnVmZmVyfSBidWZmZXIgVGhlIGFycmF5IGJ1ZmZlciB0byBjbG9uZS5cbiAqIEByZXR1cm5zIHtBcnJheUJ1ZmZlcn0gUmV0dXJucyB0aGUgY2xvbmVkIGFycmF5IGJ1ZmZlci5cbiAqL1xuZnVuY3Rpb24gYnVmZmVyQ2xvbmUoYnVmZmVyKSB7XG4gIHZhciByZXN1bHQgPSBuZXcgQXJyYXlCdWZmZXIoYnVmZmVyLmJ5dGVMZW5ndGgpLFxuICAgICAgdmlldyA9IG5ldyBVaW50OEFycmF5KHJlc3VsdCk7XG5cbiAgdmlldy5zZXQobmV3IFVpbnQ4QXJyYXkoYnVmZmVyKSk7XG4gIHJldHVybiByZXN1bHQ7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gYnVmZmVyQ2xvbmU7XG5cbn0pLmNhbGwodGhpcyx0eXBlb2YgZ2xvYmFsICE9PSBcInVuZGVmaW5lZFwiID8gZ2xvYmFsIDogdHlwZW9mIHNlbGYgIT09IFwidW5kZWZpbmVkXCIgPyBzZWxmIDogdHlwZW9mIHdpbmRvdyAhPT0gXCJ1bmRlZmluZWRcIiA/IHdpbmRvdyA6IHt9KVxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9ZGF0YTphcHBsaWNhdGlvbi9qc29uO2NoYXJzZXQ6dXRmLTg7YmFzZTY0LGV5SjJaWEp6YVc5dUlqb3pMQ0p6YjNWeVkyVnpJanBiSW01dlpHVmZiVzlrZFd4bGN5OXNiMlJoYzJndFkyOXRjR0YwTDJsdWRHVnlibUZzTDJKMVptWmxja05zYjI1bExtcHpJbDBzSW01aGJXVnpJanBiWFN3aWJXRndjR2x1WjNNaU9pSTdRVUZCUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFaUxDSm1hV3hsSWpvaVoyVnVaWEpoZEdWa0xtcHpJaXdpYzI5MWNtTmxVbTl2ZENJNklpSXNJbk52ZFhKalpYTkRiMjUwWlc1MElqcGJJaThxS2lCT1lYUnBkbVVnYldWMGFHOWtJSEpsWm1WeVpXNWpaWE11SUNvdlhHNTJZWElnUVhKeVlYbENkV1ptWlhJZ1BTQm5iRzlpWVd3dVFYSnlZWGxDZFdabVpYSXNYRzRnSUNBZ1ZXbHVkRGhCY25KaGVTQTlJR2RzYjJKaGJDNVZhVzUwT0VGeWNtRjVPMXh1WEc0dktpcGNiaUFxSUVOeVpXRjBaWE1nWVNCamJHOXVaU0J2WmlCMGFHVWdaMmwyWlc0Z1lYSnlZWGtnWW5WbVptVnlMbHh1SUNwY2JpQXFJRUJ3Y21sMllYUmxYRzRnS2lCQWNHRnlZVzBnZTBGeWNtRjVRblZtWm1WeWZTQmlkV1ptWlhJZ1ZHaGxJR0Z5Y21GNUlHSjFabVpsY2lCMGJ5QmpiRzl1WlM1Y2JpQXFJRUJ5WlhSMWNtNXpJSHRCY25KaGVVSjFabVpsY24wZ1VtVjBkWEp1Y3lCMGFHVWdZMnh2Ym1Wa0lHRnljbUY1SUdKMVptWmxjaTVjYmlBcUwxeHVablZ1WTNScGIyNGdZblZtWm1WeVEyeHZibVVvWW5WbVptVnlLU0I3WEc0Z0lIWmhjaUJ5WlhOMWJIUWdQU0J1WlhjZ1FYSnlZWGxDZFdabVpYSW9ZblZtWm1WeUxtSjVkR1ZNWlc1bmRHZ3BMRnh1SUNBZ0lDQWdkbWxsZHlBOUlHNWxkeUJWYVc1ME9FRnljbUY1S0hKbGMzVnNkQ2s3WEc1Y2JpQWdkbWxsZHk1elpYUW9ibVYzSUZWcGJuUTRRWEp5WVhrb1luVm1abVZ5S1NrN1hHNGdJSEpsZEhWeWJpQnlaWE4xYkhRN1hHNTlYRzVjYm0xdlpIVnNaUzVsZUhCdmNuUnpJRDBnWW5WbVptVnlRMnh2Ym1VN1hHNGlYWDA9IiwiLyogTmF0aXZlIG1ldGhvZCByZWZlcmVuY2VzIGZvciB0aG9zZSB3aXRoIHRoZSBzYW1lIG5hbWUgYXMgb3RoZXIgYGxvZGFzaGAgbWV0aG9kcy4gKi9cbnZhciBuYXRpdmVNYXggPSBNYXRoLm1heDtcblxuLyoqXG4gKiBDcmVhdGVzIGFuIGFycmF5IHRoYXQgaXMgdGhlIGNvbXBvc2l0aW9uIG9mIHBhcnRpYWxseSBhcHBsaWVkIGFyZ3VtZW50cyxcbiAqIHBsYWNlaG9sZGVycywgYW5kIHByb3ZpZGVkIGFyZ3VtZW50cyBpbnRvIGEgc2luZ2xlIGFycmF5IG9mIGFyZ3VtZW50cy5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtBcnJheXxPYmplY3R9IGFyZ3MgVGhlIHByb3ZpZGVkIGFyZ3VtZW50cy5cbiAqIEBwYXJhbSB7QXJyYXl9IHBhcnRpYWxzIFRoZSBhcmd1bWVudHMgdG8gcHJlcGVuZCB0byB0aG9zZSBwcm92aWRlZC5cbiAqIEBwYXJhbSB7QXJyYXl9IGhvbGRlcnMgVGhlIGBwYXJ0aWFsc2AgcGxhY2Vob2xkZXIgaW5kZXhlcy5cbiAqIEByZXR1cm5zIHtBcnJheX0gUmV0dXJucyB0aGUgbmV3IGFycmF5IG9mIGNvbXBvc2VkIGFyZ3VtZW50cy5cbiAqL1xuZnVuY3Rpb24gY29tcG9zZUFyZ3MoYXJncywgcGFydGlhbHMsIGhvbGRlcnMpIHtcbiAgdmFyIGhvbGRlcnNMZW5ndGggPSBob2xkZXJzLmxlbmd0aCxcbiAgICAgIGFyZ3NJbmRleCA9IC0xLFxuICAgICAgYXJnc0xlbmd0aCA9IG5hdGl2ZU1heChhcmdzLmxlbmd0aCAtIGhvbGRlcnNMZW5ndGgsIDApLFxuICAgICAgbGVmdEluZGV4ID0gLTEsXG4gICAgICBsZWZ0TGVuZ3RoID0gcGFydGlhbHMubGVuZ3RoLFxuICAgICAgcmVzdWx0ID0gQXJyYXkobGVmdExlbmd0aCArIGFyZ3NMZW5ndGgpO1xuXG4gIHdoaWxlICgrK2xlZnRJbmRleCA8IGxlZnRMZW5ndGgpIHtcbiAgICByZXN1bHRbbGVmdEluZGV4XSA9IHBhcnRpYWxzW2xlZnRJbmRleF07XG4gIH1cbiAgd2hpbGUgKCsrYXJnc0luZGV4IDwgaG9sZGVyc0xlbmd0aCkge1xuICAgIHJlc3VsdFtob2xkZXJzW2FyZ3NJbmRleF1dID0gYXJnc1thcmdzSW5kZXhdO1xuICB9XG4gIHdoaWxlIChhcmdzTGVuZ3RoLS0pIHtcbiAgICByZXN1bHRbbGVmdEluZGV4KytdID0gYXJnc1thcmdzSW5kZXgrK107XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBjb21wb3NlQXJncztcbiIsIi8qIE5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXg7XG5cbi8qKlxuICogVGhpcyBmdW5jdGlvbiBpcyBsaWtlIGBjb21wb3NlQXJnc2AgZXhjZXB0IHRoYXQgdGhlIGFyZ3VtZW50cyBjb21wb3NpdGlvblxuICogaXMgdGFpbG9yZWQgZm9yIGBfLnBhcnRpYWxSaWdodGAuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl8T2JqZWN0fSBhcmdzIFRoZSBwcm92aWRlZCBhcmd1bWVudHMuXG4gKiBAcGFyYW0ge0FycmF5fSBwYXJ0aWFscyBUaGUgYXJndW1lbnRzIHRvIGFwcGVuZCB0byB0aG9zZSBwcm92aWRlZC5cbiAqIEBwYXJhbSB7QXJyYXl9IGhvbGRlcnMgVGhlIGBwYXJ0aWFsc2AgcGxhY2Vob2xkZXIgaW5kZXhlcy5cbiAqIEByZXR1cm5zIHtBcnJheX0gUmV0dXJucyB0aGUgbmV3IGFycmF5IG9mIGNvbXBvc2VkIGFyZ3VtZW50cy5cbiAqL1xuZnVuY3Rpb24gY29tcG9zZUFyZ3NSaWdodChhcmdzLCBwYXJ0aWFscywgaG9sZGVycykge1xuICB2YXIgaG9sZGVyc0luZGV4ID0gLTEsXG4gICAgICBob2xkZXJzTGVuZ3RoID0gaG9sZGVycy5sZW5ndGgsXG4gICAgICBhcmdzSW5kZXggPSAtMSxcbiAgICAgIGFyZ3NMZW5ndGggPSBuYXRpdmVNYXgoYXJncy5sZW5ndGggLSBob2xkZXJzTGVuZ3RoLCAwKSxcbiAgICAgIHJpZ2h0SW5kZXggPSAtMSxcbiAgICAgIHJpZ2h0TGVuZ3RoID0gcGFydGlhbHMubGVuZ3RoLFxuICAgICAgcmVzdWx0ID0gQXJyYXkoYXJnc0xlbmd0aCArIHJpZ2h0TGVuZ3RoKTtcblxuICB3aGlsZSAoKythcmdzSW5kZXggPCBhcmdzTGVuZ3RoKSB7XG4gICAgcmVzdWx0W2FyZ3NJbmRleF0gPSBhcmdzW2FyZ3NJbmRleF07XG4gIH1cbiAgdmFyIG9mZnNldCA9IGFyZ3NJbmRleDtcbiAgd2hpbGUgKCsrcmlnaHRJbmRleCA8IHJpZ2h0TGVuZ3RoKSB7XG4gICAgcmVzdWx0W29mZnNldCArIHJpZ2h0SW5kZXhdID0gcGFydGlhbHNbcmlnaHRJbmRleF07XG4gIH1cbiAgd2hpbGUgKCsraG9sZGVyc0luZGV4IDwgaG9sZGVyc0xlbmd0aCkge1xuICAgIHJlc3VsdFtvZmZzZXQgKyBob2xkZXJzW2hvbGRlcnNJbmRleF1dID0gYXJnc1thcmdzSW5kZXgrK107XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBjb21wb3NlQXJnc1JpZ2h0O1xuIiwidmFyIGdldExlbmd0aCA9IHJlcXVpcmUoJy4vZ2V0TGVuZ3RoJyksXG4gICAgaXNMZW5ndGggPSByZXF1aXJlKCcuL2lzTGVuZ3RoJyksXG4gICAgdG9PYmplY3QgPSByZXF1aXJlKCcuL3RvT2JqZWN0Jyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGBiYXNlRWFjaGAgb3IgYGJhc2VFYWNoUmlnaHRgIGZ1bmN0aW9uLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBlYWNoRnVuYyBUaGUgZnVuY3Rpb24gdG8gaXRlcmF0ZSBvdmVyIGEgY29sbGVjdGlvbi5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW2Zyb21SaWdodF0gU3BlY2lmeSBpdGVyYXRpbmcgZnJvbSByaWdodCB0byBsZWZ0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgYmFzZSBmdW5jdGlvbi5cbiAqL1xuZnVuY3Rpb24gY3JlYXRlQmFzZUVhY2goZWFjaEZ1bmMsIGZyb21SaWdodCkge1xuICByZXR1cm4gZnVuY3Rpb24oY29sbGVjdGlvbiwgaXRlcmF0ZWUpIHtcbiAgICB2YXIgbGVuZ3RoID0gY29sbGVjdGlvbiA/IGdldExlbmd0aChjb2xsZWN0aW9uKSA6IDA7XG4gICAgaWYgKCFpc0xlbmd0aChsZW5ndGgpKSB7XG4gICAgICByZXR1cm4gZWFjaEZ1bmMoY29sbGVjdGlvbiwgaXRlcmF0ZWUpO1xuICAgIH1cbiAgICB2YXIgaW5kZXggPSBmcm9tUmlnaHQgPyBsZW5ndGggOiAtMSxcbiAgICAgICAgaXRlcmFibGUgPSB0b09iamVjdChjb2xsZWN0aW9uKTtcblxuICAgIHdoaWxlICgoZnJvbVJpZ2h0ID8gaW5kZXgtLSA6ICsraW5kZXggPCBsZW5ndGgpKSB7XG4gICAgICBpZiAoaXRlcmF0ZWUoaXRlcmFibGVbaW5kZXhdLCBpbmRleCwgaXRlcmFibGUpID09PSBmYWxzZSkge1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGNvbGxlY3Rpb247XG4gIH07XG59XG5cbm1vZHVsZS5leHBvcnRzID0gY3JlYXRlQmFzZUVhY2g7XG4iLCJ2YXIgdG9PYmplY3QgPSByZXF1aXJlKCcuL3RvT2JqZWN0Jyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGJhc2UgZnVuY3Rpb24gZm9yIGBfLmZvckluYCBvciBgXy5mb3JJblJpZ2h0YC5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtib29sZWFufSBbZnJvbVJpZ2h0XSBTcGVjaWZ5IGl0ZXJhdGluZyBmcm9tIHJpZ2h0IHRvIGxlZnQuXG4gKiBAcmV0dXJucyB7RnVuY3Rpb259IFJldHVybnMgdGhlIG5ldyBiYXNlIGZ1bmN0aW9uLlxuICovXG5mdW5jdGlvbiBjcmVhdGVCYXNlRm9yKGZyb21SaWdodCkge1xuICByZXR1cm4gZnVuY3Rpb24ob2JqZWN0LCBpdGVyYXRlZSwga2V5c0Z1bmMpIHtcbiAgICB2YXIgaXRlcmFibGUgPSB0b09iamVjdChvYmplY3QpLFxuICAgICAgICBwcm9wcyA9IGtleXNGdW5jKG9iamVjdCksXG4gICAgICAgIGxlbmd0aCA9IHByb3BzLmxlbmd0aCxcbiAgICAgICAgaW5kZXggPSBmcm9tUmlnaHQgPyBsZW5ndGggOiAtMTtcblxuICAgIHdoaWxlICgoZnJvbVJpZ2h0ID8gaW5kZXgtLSA6ICsraW5kZXggPCBsZW5ndGgpKSB7XG4gICAgICB2YXIga2V5ID0gcHJvcHNbaW5kZXhdO1xuICAgICAgaWYgKGl0ZXJhdGVlKGl0ZXJhYmxlW2tleV0sIGtleSwgaXRlcmFibGUpID09PSBmYWxzZSkge1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG9iamVjdDtcbiAgfTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBjcmVhdGVCYXNlRm9yO1xuIiwiKGZ1bmN0aW9uIChnbG9iYWwpe1xudmFyIGNyZWF0ZUN0b3JXcmFwcGVyID0gcmVxdWlyZSgnLi9jcmVhdGVDdG9yV3JhcHBlcicpO1xuXG4vKipcbiAqIENyZWF0ZXMgYSBmdW5jdGlvbiB0aGF0IHdyYXBzIGBmdW5jYCBhbmQgaW52b2tlcyBpdCB3aXRoIHRoZSBgdGhpc2BcbiAqIGJpbmRpbmcgb2YgYHRoaXNBcmdgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmdW5jIFRoZSBmdW5jdGlvbiB0byBiaW5kLlxuICogQHBhcmFtIHsqfSBbdGhpc0FyZ10gVGhlIGB0aGlzYCBiaW5kaW5nIG9mIGBmdW5jYC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGJvdW5kIGZ1bmN0aW9uLlxuICovXG5mdW5jdGlvbiBjcmVhdGVCaW5kV3JhcHBlcihmdW5jLCB0aGlzQXJnKSB7XG4gIHZhciBDdG9yID0gY3JlYXRlQ3RvcldyYXBwZXIoZnVuYyk7XG5cbiAgZnVuY3Rpb24gd3JhcHBlcigpIHtcbiAgICB2YXIgZm4gPSAodGhpcyAmJiB0aGlzICE9PSBnbG9iYWwgJiYgdGhpcyBpbnN0YW5jZW9mIHdyYXBwZXIpID8gQ3RvciA6IGZ1bmM7XG4gICAgcmV0dXJuIGZuLmFwcGx5KHRoaXNBcmcsIGFyZ3VtZW50cyk7XG4gIH1cbiAgcmV0dXJuIHdyYXBwZXI7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gY3JlYXRlQmluZFdyYXBwZXI7XG5cbn0pLmNhbGwodGhpcyx0eXBlb2YgZ2xvYmFsICE9PSBcInVuZGVmaW5lZFwiID8gZ2xvYmFsIDogdHlwZW9mIHNlbGYgIT09IFwidW5kZWZpbmVkXCIgPyBzZWxmIDogdHlwZW9mIHdpbmRvdyAhPT0gXCJ1bmRlZmluZWRcIiA/IHdpbmRvdyA6IHt9KVxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9ZGF0YTphcHBsaWNhdGlvbi9qc29uO2NoYXJzZXQ6dXRmLTg7YmFzZTY0LGV5SjJaWEp6YVc5dUlqb3pMQ0p6YjNWeVkyVnpJanBiSW01dlpHVmZiVzlrZFd4bGN5OXNiMlJoYzJndFkyOXRjR0YwTDJsdWRHVnlibUZzTDJOeVpXRjBaVUpwYm1SWGNtRndjR1Z5TG1weklsMHNJbTVoYldWeklqcGJYU3dpYldGd2NHbHVaM01pT2lJN1FVRkJRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CSWl3aVptbHNaU0k2SW1kbGJtVnlZWFJsWkM1cWN5SXNJbk52ZFhKalpWSnZiM1FpT2lJaUxDSnpiM1Z5WTJWelEyOXVkR1Z1ZENJNld5SjJZWElnWTNKbFlYUmxRM1J2Y2xkeVlYQndaWElnUFNCeVpYRjFhWEpsS0NjdUwyTnlaV0YwWlVOMGIzSlhjbUZ3Y0dWeUp5azdYRzVjYmk4cUtseHVJQ29nUTNKbFlYUmxjeUJoSUdaMWJtTjBhVzl1SUhSb1lYUWdkM0poY0hNZ1lHWjFibU5nSUdGdVpDQnBiblp2YTJWeklHbDBJSGRwZEdnZ2RHaGxJR0IwYUdsellGeHVJQ29nWW1sdVpHbHVaeUJ2WmlCZ2RHaHBjMEZ5WjJBdVhHNGdLbHh1SUNvZ1FIQnlhWFpoZEdWY2JpQXFJRUJ3WVhKaGJTQjdSblZ1WTNScGIyNTlJR1oxYm1NZ1ZHaGxJR1oxYm1OMGFXOXVJSFJ2SUdKcGJtUXVYRzRnS2lCQWNHRnlZVzBnZXlwOUlGdDBhR2x6UVhKblhTQlVhR1VnWUhSb2FYTmdJR0pwYm1ScGJtY2diMllnWUdaMWJtTmdMbHh1SUNvZ1FISmxkSFZ5Ym5NZ2UwWjFibU4wYVc5dWZTQlNaWFIxY201eklIUm9aU0J1WlhjZ1ltOTFibVFnWm5WdVkzUnBiMjR1WEc0Z0tpOWNibVoxYm1OMGFXOXVJR055WldGMFpVSnBibVJYY21Gd2NHVnlLR1oxYm1Nc0lIUm9hWE5CY21jcElIdGNiaUFnZG1GeUlFTjBiM0lnUFNCamNtVmhkR1ZEZEc5eVYzSmhjSEJsY2lobWRXNWpLVHRjYmx4dUlDQm1kVzVqZEdsdmJpQjNjbUZ3Y0dWeUtDa2dlMXh1SUNBZ0lIWmhjaUJtYmlBOUlDaDBhR2x6SUNZbUlIUm9hWE1nSVQwOUlHZHNiMkpoYkNBbUppQjBhR2x6SUdsdWMzUmhibU5sYjJZZ2QzSmhjSEJsY2lrZ1B5QkRkRzl5SURvZ1puVnVZenRjYmlBZ0lDQnlaWFIxY200Z1ptNHVZWEJ3Ykhrb2RHaHBjMEZ5Wnl3Z1lYSm5kVzFsYm5SektUdGNiaUFnZlZ4dUlDQnlaWFIxY200Z2QzSmhjSEJsY2p0Y2JuMWNibHh1Ylc5a2RXeGxMbVY0Y0c5eWRITWdQU0JqY21WaGRHVkNhVzVrVjNKaGNIQmxjanRjYmlKZGZRPT0iLCJ2YXIgYmFzZUNyZWF0ZSA9IHJlcXVpcmUoJy4vYmFzZUNyZWF0ZScpLFxuICAgIGlzT2JqZWN0ID0gcmVxdWlyZSgnLi4vbGFuZy9pc09iamVjdCcpO1xuXG4vKipcbiAqIENyZWF0ZXMgYSBmdW5jdGlvbiB0aGF0IHByb2R1Y2VzIGFuIGluc3RhbmNlIG9mIGBDdG9yYCByZWdhcmRsZXNzIG9mXG4gKiB3aGV0aGVyIGl0IHdhcyBpbnZva2VkIGFzIHBhcnQgb2YgYSBgbmV3YCBleHByZXNzaW9uIG9yIGJ5IGBjYWxsYCBvciBgYXBwbHlgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBDdG9yIFRoZSBjb25zdHJ1Y3RvciB0byB3cmFwLlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgd3JhcHBlZCBmdW5jdGlvbi5cbiAqL1xuZnVuY3Rpb24gY3JlYXRlQ3RvcldyYXBwZXIoQ3Rvcikge1xuICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgLy8gVXNlIGEgYHN3aXRjaGAgc3RhdGVtZW50IHRvIHdvcmsgd2l0aCBjbGFzcyBjb25zdHJ1Y3RvcnMuXG4gICAgLy8gU2VlIGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzYuMC8jc2VjLWVjbWFzY3JpcHQtZnVuY3Rpb24tb2JqZWN0cy1jYWxsLXRoaXNhcmd1bWVudC1hcmd1bWVudHNsaXN0XG4gICAgLy8gZm9yIG1vcmUgZGV0YWlscy5cbiAgICB2YXIgYXJncyA9IGFyZ3VtZW50cztcbiAgICBzd2l0Y2ggKGFyZ3MubGVuZ3RoKSB7XG4gICAgICBjYXNlIDA6IHJldHVybiBuZXcgQ3RvcjtcbiAgICAgIGNhc2UgMTogcmV0dXJuIG5ldyBDdG9yKGFyZ3NbMF0pO1xuICAgICAgY2FzZSAyOiByZXR1cm4gbmV3IEN0b3IoYXJnc1swXSwgYXJnc1sxXSk7XG4gICAgICBjYXNlIDM6IHJldHVybiBuZXcgQ3RvcihhcmdzWzBdLCBhcmdzWzFdLCBhcmdzWzJdKTtcbiAgICAgIGNhc2UgNDogcmV0dXJuIG5ldyBDdG9yKGFyZ3NbMF0sIGFyZ3NbMV0sIGFyZ3NbMl0sIGFyZ3NbM10pO1xuICAgICAgY2FzZSA1OiByZXR1cm4gbmV3IEN0b3IoYXJnc1swXSwgYXJnc1sxXSwgYXJnc1syXSwgYXJnc1szXSwgYXJnc1s0XSk7XG4gICAgICBjYXNlIDY6IHJldHVybiBuZXcgQ3RvcihhcmdzWzBdLCBhcmdzWzFdLCBhcmdzWzJdLCBhcmdzWzNdLCBhcmdzWzRdLCBhcmdzWzVdKTtcbiAgICAgIGNhc2UgNzogcmV0dXJuIG5ldyBDdG9yKGFyZ3NbMF0sIGFyZ3NbMV0sIGFyZ3NbMl0sIGFyZ3NbM10sIGFyZ3NbNF0sIGFyZ3NbNV0sIGFyZ3NbNl0pO1xuICAgIH1cbiAgICB2YXIgdGhpc0JpbmRpbmcgPSBiYXNlQ3JlYXRlKEN0b3IucHJvdG90eXBlKSxcbiAgICAgICAgcmVzdWx0ID0gQ3Rvci5hcHBseSh0aGlzQmluZGluZywgYXJncyk7XG5cbiAgICAvLyBNaW1pYyB0aGUgY29uc3RydWN0b3IncyBgcmV0dXJuYCBiZWhhdmlvci5cbiAgICAvLyBTZWUgaHR0cHM6Ly9lczUuZ2l0aHViLmlvLyN4MTMuMi4yIGZvciBtb3JlIGRldGFpbHMuXG4gICAgcmV0dXJuIGlzT2JqZWN0KHJlc3VsdCkgPyByZXN1bHQgOiB0aGlzQmluZGluZztcbiAgfTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBjcmVhdGVDdG9yV3JhcHBlcjtcbiIsInZhciBiYXNlQ2FsbGJhY2sgPSByZXF1aXJlKCcuL2Jhc2VDYWxsYmFjaycpLFxuICAgIGJhc2VGaW5kID0gcmVxdWlyZSgnLi9iYXNlRmluZCcpLFxuICAgIGJhc2VGaW5kSW5kZXggPSByZXF1aXJlKCcuL2Jhc2VGaW5kSW5kZXgnKSxcbiAgICBpc0FycmF5ID0gcmVxdWlyZSgnLi4vbGFuZy9pc0FycmF5Jyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGBfLmZpbmRgIG9yIGBfLmZpbmRMYXN0YCBmdW5jdGlvbi5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtGdW5jdGlvbn0gZWFjaEZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGl0ZXJhdGUgb3ZlciBhIGNvbGxlY3Rpb24uXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtmcm9tUmlnaHRdIFNwZWNpZnkgaXRlcmF0aW5nIGZyb20gcmlnaHQgdG8gbGVmdC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGZpbmQgZnVuY3Rpb24uXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZUZpbmQoZWFjaEZ1bmMsIGZyb21SaWdodCkge1xuICByZXR1cm4gZnVuY3Rpb24oY29sbGVjdGlvbiwgcHJlZGljYXRlLCB0aGlzQXJnKSB7XG4gICAgcHJlZGljYXRlID0gYmFzZUNhbGxiYWNrKHByZWRpY2F0ZSwgdGhpc0FyZywgMyk7XG4gICAgaWYgKGlzQXJyYXkoY29sbGVjdGlvbikpIHtcbiAgICAgIHZhciBpbmRleCA9IGJhc2VGaW5kSW5kZXgoY29sbGVjdGlvbiwgcHJlZGljYXRlLCBmcm9tUmlnaHQpO1xuICAgICAgcmV0dXJuIGluZGV4ID4gLTEgPyBjb2xsZWN0aW9uW2luZGV4XSA6IHVuZGVmaW5lZDtcbiAgICB9XG4gICAgcmV0dXJuIGJhc2VGaW5kKGNvbGxlY3Rpb24sIHByZWRpY2F0ZSwgZWFjaEZ1bmMpO1xuICB9O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGNyZWF0ZUZpbmQ7XG4iLCJ2YXIgYmluZENhbGxiYWNrID0gcmVxdWlyZSgnLi9iaW5kQ2FsbGJhY2snKSxcbiAgICBpc0FycmF5ID0gcmVxdWlyZSgnLi4vbGFuZy9pc0FycmF5Jyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGZ1bmN0aW9uIGZvciBgXy5mb3JFYWNoYCBvciBgXy5mb3JFYWNoUmlnaHRgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBhcnJheUZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGl0ZXJhdGUgb3ZlciBhbiBhcnJheS5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGVhY2hGdW5jIFRoZSBmdW5jdGlvbiB0byBpdGVyYXRlIG92ZXIgYSBjb2xsZWN0aW9uLlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgZWFjaCBmdW5jdGlvbi5cbiAqL1xuZnVuY3Rpb24gY3JlYXRlRm9yRWFjaChhcnJheUZ1bmMsIGVhY2hGdW5jKSB7XG4gIHJldHVybiBmdW5jdGlvbihjb2xsZWN0aW9uLCBpdGVyYXRlZSwgdGhpc0FyZykge1xuICAgIHJldHVybiAodHlwZW9mIGl0ZXJhdGVlID09ICdmdW5jdGlvbicgJiYgdGhpc0FyZyA9PT0gdW5kZWZpbmVkICYmIGlzQXJyYXkoY29sbGVjdGlvbikpXG4gICAgICA/IGFycmF5RnVuYyhjb2xsZWN0aW9uLCBpdGVyYXRlZSlcbiAgICAgIDogZWFjaEZ1bmMoY29sbGVjdGlvbiwgYmluZENhbGxiYWNrKGl0ZXJhdGVlLCB0aGlzQXJnLCAzKSk7XG4gIH07XG59XG5cbm1vZHVsZS5leHBvcnRzID0gY3JlYXRlRm9yRWFjaDtcbiIsIihmdW5jdGlvbiAoZ2xvYmFsKXtcbnZhciBhcnJheUNvcHkgPSByZXF1aXJlKCcuL2FycmF5Q29weScpLFxuICAgIGNvbXBvc2VBcmdzID0gcmVxdWlyZSgnLi9jb21wb3NlQXJncycpLFxuICAgIGNvbXBvc2VBcmdzUmlnaHQgPSByZXF1aXJlKCcuL2NvbXBvc2VBcmdzUmlnaHQnKSxcbiAgICBjcmVhdGVDdG9yV3JhcHBlciA9IHJlcXVpcmUoJy4vY3JlYXRlQ3RvcldyYXBwZXInKSxcbiAgICBpc0xhemlhYmxlID0gcmVxdWlyZSgnLi9pc0xhemlhYmxlJyksXG4gICAgcmVvcmRlciA9IHJlcXVpcmUoJy4vcmVvcmRlcicpLFxuICAgIHJlcGxhY2VIb2xkZXJzID0gcmVxdWlyZSgnLi9yZXBsYWNlSG9sZGVycycpLFxuICAgIHNldERhdGEgPSByZXF1aXJlKCcuL3NldERhdGEnKTtcblxuLyoqIFVzZWQgdG8gY29tcG9zZSBiaXRtYXNrcyBmb3Igd3JhcHBlciBtZXRhZGF0YS4gKi9cbnZhciBCSU5EX0ZMQUcgPSAxLFxuICAgIEJJTkRfS0VZX0ZMQUcgPSAyLFxuICAgIENVUlJZX0JPVU5EX0ZMQUcgPSA0LFxuICAgIENVUlJZX0ZMQUcgPSA4LFxuICAgIENVUlJZX1JJR0hUX0ZMQUcgPSAxNixcbiAgICBQQVJUSUFMX0ZMQUcgPSAzMixcbiAgICBQQVJUSUFMX1JJR0hUX0ZMQUcgPSA2NCxcbiAgICBBUllfRkxBRyA9IDEyODtcblxuLyogTmF0aXZlIG1ldGhvZCByZWZlcmVuY2VzIGZvciB0aG9zZSB3aXRoIHRoZSBzYW1lIG5hbWUgYXMgb3RoZXIgYGxvZGFzaGAgbWV0aG9kcy4gKi9cbnZhciBuYXRpdmVNYXggPSBNYXRoLm1heDtcblxuLyoqXG4gKiBDcmVhdGVzIGEgZnVuY3Rpb24gdGhhdCB3cmFwcyBgZnVuY2AgYW5kIGludm9rZXMgaXQgd2l0aCBvcHRpb25hbCBgdGhpc2BcbiAqIGJpbmRpbmcgb2YsIHBhcnRpYWwgYXBwbGljYXRpb24sIGFuZCBjdXJyeWluZy5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtGdW5jdGlvbnxzdHJpbmd9IGZ1bmMgVGhlIGZ1bmN0aW9uIG9yIG1ldGhvZCBuYW1lIHRvIHJlZmVyZW5jZS5cbiAqIEBwYXJhbSB7bnVtYmVyfSBiaXRtYXNrIFRoZSBiaXRtYXNrIG9mIGZsYWdzLiBTZWUgYGNyZWF0ZVdyYXBwZXJgIGZvciBtb3JlIGRldGFpbHMuXG4gKiBAcGFyYW0geyp9IFt0aGlzQXJnXSBUaGUgYHRoaXNgIGJpbmRpbmcgb2YgYGZ1bmNgLlxuICogQHBhcmFtIHtBcnJheX0gW3BhcnRpYWxzXSBUaGUgYXJndW1lbnRzIHRvIHByZXBlbmQgdG8gdGhvc2UgcHJvdmlkZWQgdG8gdGhlIG5ldyBmdW5jdGlvbi5cbiAqIEBwYXJhbSB7QXJyYXl9IFtob2xkZXJzXSBUaGUgYHBhcnRpYWxzYCBwbGFjZWhvbGRlciBpbmRleGVzLlxuICogQHBhcmFtIHtBcnJheX0gW3BhcnRpYWxzUmlnaHRdIFRoZSBhcmd1bWVudHMgdG8gYXBwZW5kIHRvIHRob3NlIHByb3ZpZGVkIHRvIHRoZSBuZXcgZnVuY3Rpb24uXG4gKiBAcGFyYW0ge0FycmF5fSBbaG9sZGVyc1JpZ2h0XSBUaGUgYHBhcnRpYWxzUmlnaHRgIHBsYWNlaG9sZGVyIGluZGV4ZXMuXG4gKiBAcGFyYW0ge0FycmF5fSBbYXJnUG9zXSBUaGUgYXJndW1lbnQgcG9zaXRpb25zIG9mIHRoZSBuZXcgZnVuY3Rpb24uXG4gKiBAcGFyYW0ge251bWJlcn0gW2FyeV0gVGhlIGFyaXR5IGNhcCBvZiBgZnVuY2AuXG4gKiBAcGFyYW0ge251bWJlcn0gW2FyaXR5XSBUaGUgYXJpdHkgb2YgYGZ1bmNgLlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgd3JhcHBlZCBmdW5jdGlvbi5cbiAqL1xuZnVuY3Rpb24gY3JlYXRlSHlicmlkV3JhcHBlcihmdW5jLCBiaXRtYXNrLCB0aGlzQXJnLCBwYXJ0aWFscywgaG9sZGVycywgcGFydGlhbHNSaWdodCwgaG9sZGVyc1JpZ2h0LCBhcmdQb3MsIGFyeSwgYXJpdHkpIHtcbiAgdmFyIGlzQXJ5ID0gYml0bWFzayAmIEFSWV9GTEFHLFxuICAgICAgaXNCaW5kID0gYml0bWFzayAmIEJJTkRfRkxBRyxcbiAgICAgIGlzQmluZEtleSA9IGJpdG1hc2sgJiBCSU5EX0tFWV9GTEFHLFxuICAgICAgaXNDdXJyeSA9IGJpdG1hc2sgJiBDVVJSWV9GTEFHLFxuICAgICAgaXNDdXJyeUJvdW5kID0gYml0bWFzayAmIENVUlJZX0JPVU5EX0ZMQUcsXG4gICAgICBpc0N1cnJ5UmlnaHQgPSBiaXRtYXNrICYgQ1VSUllfUklHSFRfRkxBRyxcbiAgICAgIEN0b3IgPSBpc0JpbmRLZXkgPyB1bmRlZmluZWQgOiBjcmVhdGVDdG9yV3JhcHBlcihmdW5jKTtcblxuICBmdW5jdGlvbiB3cmFwcGVyKCkge1xuICAgIC8vIEF2b2lkIGBhcmd1bWVudHNgIG9iamVjdCB1c2UgZGlzcXVhbGlmeWluZyBvcHRpbWl6YXRpb25zIGJ5XG4gICAgLy8gY29udmVydGluZyBpdCB0byBhbiBhcnJheSBiZWZvcmUgcHJvdmlkaW5nIGl0IHRvIG90aGVyIGZ1bmN0aW9ucy5cbiAgICB2YXIgbGVuZ3RoID0gYXJndW1lbnRzLmxlbmd0aCxcbiAgICAgICAgaW5kZXggPSBsZW5ndGgsXG4gICAgICAgIGFyZ3MgPSBBcnJheShsZW5ndGgpO1xuXG4gICAgd2hpbGUgKGluZGV4LS0pIHtcbiAgICAgIGFyZ3NbaW5kZXhdID0gYXJndW1lbnRzW2luZGV4XTtcbiAgICB9XG4gICAgaWYgKHBhcnRpYWxzKSB7XG4gICAgICBhcmdzID0gY29tcG9zZUFyZ3MoYXJncywgcGFydGlhbHMsIGhvbGRlcnMpO1xuICAgIH1cbiAgICBpZiAocGFydGlhbHNSaWdodCkge1xuICAgICAgYXJncyA9IGNvbXBvc2VBcmdzUmlnaHQoYXJncywgcGFydGlhbHNSaWdodCwgaG9sZGVyc1JpZ2h0KTtcbiAgICB9XG4gICAgaWYgKGlzQ3VycnkgfHwgaXNDdXJyeVJpZ2h0KSB7XG4gICAgICB2YXIgcGxhY2Vob2xkZXIgPSB3cmFwcGVyLnBsYWNlaG9sZGVyLFxuICAgICAgICAgIGFyZ3NIb2xkZXJzID0gcmVwbGFjZUhvbGRlcnMoYXJncywgcGxhY2Vob2xkZXIpO1xuXG4gICAgICBsZW5ndGggLT0gYXJnc0hvbGRlcnMubGVuZ3RoO1xuICAgICAgaWYgKGxlbmd0aCA8IGFyaXR5KSB7XG4gICAgICAgIHZhciBuZXdBcmdQb3MgPSBhcmdQb3MgPyBhcnJheUNvcHkoYXJnUG9zKSA6IHVuZGVmaW5lZCxcbiAgICAgICAgICAgIG5ld0FyaXR5ID0gbmF0aXZlTWF4KGFyaXR5IC0gbGVuZ3RoLCAwKSxcbiAgICAgICAgICAgIG5ld3NIb2xkZXJzID0gaXNDdXJyeSA/IGFyZ3NIb2xkZXJzIDogdW5kZWZpbmVkLFxuICAgICAgICAgICAgbmV3SG9sZGVyc1JpZ2h0ID0gaXNDdXJyeSA/IHVuZGVmaW5lZCA6IGFyZ3NIb2xkZXJzLFxuICAgICAgICAgICAgbmV3UGFydGlhbHMgPSBpc0N1cnJ5ID8gYXJncyA6IHVuZGVmaW5lZCxcbiAgICAgICAgICAgIG5ld1BhcnRpYWxzUmlnaHQgPSBpc0N1cnJ5ID8gdW5kZWZpbmVkIDogYXJncztcblxuICAgICAgICBiaXRtYXNrIHw9IChpc0N1cnJ5ID8gUEFSVElBTF9GTEFHIDogUEFSVElBTF9SSUdIVF9GTEFHKTtcbiAgICAgICAgYml0bWFzayAmPSB+KGlzQ3VycnkgPyBQQVJUSUFMX1JJR0hUX0ZMQUcgOiBQQVJUSUFMX0ZMQUcpO1xuXG4gICAgICAgIGlmICghaXNDdXJyeUJvdW5kKSB7XG4gICAgICAgICAgYml0bWFzayAmPSB+KEJJTkRfRkxBRyB8IEJJTkRfS0VZX0ZMQUcpO1xuICAgICAgICB9XG4gICAgICAgIHZhciBuZXdEYXRhID0gW2Z1bmMsIGJpdG1hc2ssIHRoaXNBcmcsIG5ld1BhcnRpYWxzLCBuZXdzSG9sZGVycywgbmV3UGFydGlhbHNSaWdodCwgbmV3SG9sZGVyc1JpZ2h0LCBuZXdBcmdQb3MsIGFyeSwgbmV3QXJpdHldLFxuICAgICAgICAgICAgcmVzdWx0ID0gY3JlYXRlSHlicmlkV3JhcHBlci5hcHBseSh1bmRlZmluZWQsIG5ld0RhdGEpO1xuXG4gICAgICAgIGlmIChpc0xhemlhYmxlKGZ1bmMpKSB7XG4gICAgICAgICAgc2V0RGF0YShyZXN1bHQsIG5ld0RhdGEpO1xuICAgICAgICB9XG4gICAgICAgIHJlc3VsdC5wbGFjZWhvbGRlciA9IHBsYWNlaG9sZGVyO1xuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgfVxuICAgIH1cbiAgICB2YXIgdGhpc0JpbmRpbmcgPSBpc0JpbmQgPyB0aGlzQXJnIDogdGhpcyxcbiAgICAgICAgZm4gPSBpc0JpbmRLZXkgPyB0aGlzQmluZGluZ1tmdW5jXSA6IGZ1bmM7XG5cbiAgICBpZiAoYXJnUG9zKSB7XG4gICAgICBhcmdzID0gcmVvcmRlcihhcmdzLCBhcmdQb3MpO1xuICAgIH1cbiAgICBpZiAoaXNBcnkgJiYgYXJ5IDwgYXJncy5sZW5ndGgpIHtcbiAgICAgIGFyZ3MubGVuZ3RoID0gYXJ5O1xuICAgIH1cbiAgICBpZiAodGhpcyAmJiB0aGlzICE9PSBnbG9iYWwgJiYgdGhpcyBpbnN0YW5jZW9mIHdyYXBwZXIpIHtcbiAgICAgIGZuID0gQ3RvciB8fCBjcmVhdGVDdG9yV3JhcHBlcihmdW5jKTtcbiAgICB9XG4gICAgcmV0dXJuIGZuLmFwcGx5KHRoaXNCaW5kaW5nLCBhcmdzKTtcbiAgfVxuICByZXR1cm4gd3JhcHBlcjtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBjcmVhdGVIeWJyaWRXcmFwcGVyO1xuXG59KS5jYWxsKHRoaXMsdHlwZW9mIGdsb2JhbCAhPT0gXCJ1bmRlZmluZWRcIiA/IGdsb2JhbCA6IHR5cGVvZiBzZWxmICE9PSBcInVuZGVmaW5lZFwiID8gc2VsZiA6IHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIgPyB3aW5kb3cgOiB7fSlcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPWRhdGE6YXBwbGljYXRpb24vanNvbjtjaGFyc2V0OnV0Zi04O2Jhc2U2NCxleUoyWlhKemFXOXVJam96TENKemIzVnlZMlZ6SWpwYkltNXZaR1ZmYlc5a2RXeGxjeTlzYjJSaGMyZ3RZMjl0Y0dGMEwybHVkR1Z5Ym1Gc0wyTnlaV0YwWlVoNVluSnBaRmR5WVhCd1pYSXVhbk1pWFN3aWJtRnRaWE1pT2x0ZExDSnRZWEJ3YVc1bmN5STZJanRCUVVGQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CSWl3aVptbHNaU0k2SW1kbGJtVnlZWFJsWkM1cWN5SXNJbk52ZFhKalpWSnZiM1FpT2lJaUxDSnpiM1Z5WTJWelEyOXVkR1Z1ZENJNld5SjJZWElnWVhKeVlYbERiM0I1SUQwZ2NtVnhkV2x5WlNnbkxpOWhjbkpoZVVOdmNIa25LU3hjYmlBZ0lDQmpiMjF3YjNObFFYSm5jeUE5SUhKbGNYVnBjbVVvSnk0dlkyOXRjRzl6WlVGeVozTW5LU3hjYmlBZ0lDQmpiMjF3YjNObFFYSm5jMUpwWjJoMElEMGdjbVZ4ZFdseVpTZ25MaTlqYjIxd2IzTmxRWEpuYzFKcFoyaDBKeWtzWEc0Z0lDQWdZM0psWVhSbFEzUnZjbGR5WVhCd1pYSWdQU0J5WlhGMWFYSmxLQ2N1TDJOeVpXRjBaVU4wYjNKWGNtRndjR1Z5Snlrc1hHNGdJQ0FnYVhOTVlYcHBZV0pzWlNBOUlISmxjWFZwY21Vb0p5NHZhWE5NWVhwcFlXSnNaU2NwTEZ4dUlDQWdJSEpsYjNKa1pYSWdQU0J5WlhGMWFYSmxLQ2N1TDNKbGIzSmtaWEluS1N4Y2JpQWdJQ0J5WlhCc1lXTmxTRzlzWkdWeWN5QTlJSEpsY1hWcGNtVW9KeTR2Y21Wd2JHRmpaVWh2YkdSbGNuTW5LU3hjYmlBZ0lDQnpaWFJFWVhSaElEMGdjbVZ4ZFdseVpTZ25MaTl6WlhSRVlYUmhKeWs3WEc1Y2JpOHFLaUJWYzJWa0lIUnZJR052YlhCdmMyVWdZbWwwYldGemEzTWdabTl5SUhkeVlYQndaWElnYldWMFlXUmhkR0V1SUNvdlhHNTJZWElnUWtsT1JGOUdURUZISUQwZ01TeGNiaUFnSUNCQ1NVNUVYMHRGV1Y5R1RFRkhJRDBnTWl4Y2JpQWdJQ0JEVlZKU1dWOUNUMVZPUkY5R1RFRkhJRDBnTkN4Y2JpQWdJQ0JEVlZKU1dWOUdURUZISUQwZ09DeGNiaUFnSUNCRFZWSlNXVjlTU1VkSVZGOUdURUZISUQwZ01UWXNYRzRnSUNBZ1VFRlNWRWxCVEY5R1RFRkhJRDBnTXpJc1hHNGdJQ0FnVUVGU1ZFbEJURjlTU1VkSVZGOUdURUZISUQwZ05qUXNYRzRnSUNBZ1FWSlpYMFpNUVVjZ1BTQXhNamc3WEc1Y2JpOHFJRTVoZEdsMlpTQnRaWFJvYjJRZ2NtVm1aWEpsYm1ObGN5Qm1iM0lnZEdodmMyVWdkMmwwYUNCMGFHVWdjMkZ0WlNCdVlXMWxJR0Z6SUc5MGFHVnlJR0JzYjJSaGMyaGdJRzFsZEdodlpITXVJQ292WEc1MllYSWdibUYwYVhabFRXRjRJRDBnVFdGMGFDNXRZWGc3WEc1Y2JpOHFLbHh1SUNvZ1EzSmxZWFJsY3lCaElHWjFibU4wYVc5dUlIUm9ZWFFnZDNKaGNITWdZR1oxYm1OZ0lHRnVaQ0JwYm5admEyVnpJR2wwSUhkcGRHZ2diM0IwYVc5dVlXd2dZSFJvYVhOZ1hHNGdLaUJpYVc1a2FXNW5JRzltTENCd1lYSjBhV0ZzSUdGd2NHeHBZMkYwYVc5dUxDQmhibVFnWTNWeWNubHBibWN1WEc0Z0tseHVJQ29nUUhCeWFYWmhkR1ZjYmlBcUlFQndZWEpoYlNCN1JuVnVZM1JwYjI1OGMzUnlhVzVuZlNCbWRXNWpJRlJvWlNCbWRXNWpkR2x2YmlCdmNpQnRaWFJvYjJRZ2JtRnRaU0IwYnlCeVpXWmxjbVZ1WTJVdVhHNGdLaUJBY0dGeVlXMGdlMjUxYldKbGNuMGdZbWwwYldGemF5QlVhR1VnWW1sMGJXRnpheUJ2WmlCbWJHRm5jeTRnVTJWbElHQmpjbVZoZEdWWGNtRndjR1Z5WUNCbWIzSWdiVzl5WlNCa1pYUmhhV3h6TGx4dUlDb2dRSEJoY21GdElIc3FmU0JiZEdocGMwRnlaMTBnVkdobElHQjBhR2x6WUNCaWFXNWthVzVuSUc5bUlHQm1kVzVqWUM1Y2JpQXFJRUJ3WVhKaGJTQjdRWEp5WVhsOUlGdHdZWEowYVdGc2MxMGdWR2hsSUdGeVozVnRaVzUwY3lCMGJ5QndjbVZ3Wlc1a0lIUnZJSFJvYjNObElIQnliM1pwWkdWa0lIUnZJSFJvWlNCdVpYY2dablZ1WTNScGIyNHVYRzRnS2lCQWNHRnlZVzBnZTBGeWNtRjVmU0JiYUc5c1pHVnljMTBnVkdobElHQndZWEowYVdGc2MyQWdjR3hoWTJWb2IyeGtaWElnYVc1a1pYaGxjeTVjYmlBcUlFQndZWEpoYlNCN1FYSnlZWGw5SUZ0d1lYSjBhV0ZzYzFKcFoyaDBYU0JVYUdVZ1lYSm5kVzFsYm5SeklIUnZJR0Z3Y0dWdVpDQjBieUIwYUc5elpTQndjbTkyYVdSbFpDQjBieUIwYUdVZ2JtVjNJR1oxYm1OMGFXOXVMbHh1SUNvZ1FIQmhjbUZ0SUh0QmNuSmhlWDBnVzJodmJHUmxjbk5TYVdkb2RGMGdWR2hsSUdCd1lYSjBhV0ZzYzFKcFoyaDBZQ0J3YkdGalpXaHZiR1JsY2lCcGJtUmxlR1Z6TGx4dUlDb2dRSEJoY21GdElIdEJjbkpoZVgwZ1cyRnlaMUJ2YzEwZ1ZHaGxJR0Z5WjNWdFpXNTBJSEJ2YzJsMGFXOXVjeUJ2WmlCMGFHVWdibVYzSUdaMWJtTjBhVzl1TGx4dUlDb2dRSEJoY21GdElIdHVkVzFpWlhKOUlGdGhjbmxkSUZSb1pTQmhjbWwwZVNCallYQWdiMllnWUdaMWJtTmdMbHh1SUNvZ1FIQmhjbUZ0SUh0dWRXMWlaWEo5SUZ0aGNtbDBlVjBnVkdobElHRnlhWFI1SUc5bUlHQm1kVzVqWUM1Y2JpQXFJRUJ5WlhSMWNtNXpJSHRHZFc1amRHbHZibjBnVW1WMGRYSnVjeUIwYUdVZ2JtVjNJSGR5WVhCd1pXUWdablZ1WTNScGIyNHVYRzRnS2k5Y2JtWjFibU4wYVc5dUlHTnlaV0YwWlVoNVluSnBaRmR5WVhCd1pYSW9ablZ1WXl3Z1ltbDBiV0Z6YXl3Z2RHaHBjMEZ5Wnl3Z2NHRnlkR2xoYkhNc0lHaHZiR1JsY25Nc0lIQmhjblJwWVd4elVtbG5hSFFzSUdodmJHUmxjbk5TYVdkb2RDd2dZWEpuVUc5ekxDQmhjbmtzSUdGeWFYUjVLU0I3WEc0Z0lIWmhjaUJwYzBGeWVTQTlJR0pwZEcxaGMyc2dKaUJCVWxsZlJreEJSeXhjYmlBZ0lDQWdJR2x6UW1sdVpDQTlJR0pwZEcxaGMyc2dKaUJDU1U1RVgwWk1RVWNzWEc0Z0lDQWdJQ0JwYzBKcGJtUkxaWGtnUFNCaWFYUnRZWE5ySUNZZ1FrbE9SRjlMUlZsZlJreEJSeXhjYmlBZ0lDQWdJR2x6UTNWeWNua2dQU0JpYVhSdFlYTnJJQ1lnUTFWU1VsbGZSa3hCUnl4Y2JpQWdJQ0FnSUdselEzVnljbmxDYjNWdVpDQTlJR0pwZEcxaGMyc2dKaUJEVlZKU1dWOUNUMVZPUkY5R1RFRkhMRnh1SUNBZ0lDQWdhWE5EZFhKeWVWSnBaMmgwSUQwZ1ltbDBiV0Z6YXlBbUlFTlZVbEpaWDFKSlIwaFVYMFpNUVVjc1hHNGdJQ0FnSUNCRGRHOXlJRDBnYVhOQ2FXNWtTMlY1SUQ4Z2RXNWtaV1pwYm1Wa0lEb2dZM0psWVhSbFEzUnZjbGR5WVhCd1pYSW9ablZ1WXlrN1hHNWNiaUFnWm5WdVkzUnBiMjRnZDNKaGNIQmxjaWdwSUh0Y2JpQWdJQ0F2THlCQmRtOXBaQ0JnWVhKbmRXMWxiblJ6WUNCdlltcGxZM1FnZFhObElHUnBjM0YxWVd4cFpubHBibWNnYjNCMGFXMXBlbUYwYVc5dWN5QmllVnh1SUNBZ0lDOHZJR052Ym5abGNuUnBibWNnYVhRZ2RHOGdZVzRnWVhKeVlYa2dZbVZtYjNKbElIQnliM1pwWkdsdVp5QnBkQ0IwYnlCdmRHaGxjaUJtZFc1amRHbHZibk11WEc0Z0lDQWdkbUZ5SUd4bGJtZDBhQ0E5SUdGeVozVnRaVzUwY3k1c1pXNW5kR2dzWEc0Z0lDQWdJQ0FnSUdsdVpHVjRJRDBnYkdWdVozUm9MRnh1SUNBZ0lDQWdJQ0JoY21keklEMGdRWEp5WVhrb2JHVnVaM1JvS1R0Y2JseHVJQ0FnSUhkb2FXeGxJQ2hwYm1SbGVDMHRLU0I3WEc0Z0lDQWdJQ0JoY21kelcybHVaR1Y0WFNBOUlHRnlaM1Z0Wlc1MGMxdHBibVJsZUYwN1hHNGdJQ0FnZlZ4dUlDQWdJR2xtSUNod1lYSjBhV0ZzY3lrZ2UxeHVJQ0FnSUNBZ1lYSm5jeUE5SUdOdmJYQnZjMlZCY21kektHRnlaM01zSUhCaGNuUnBZV3h6TENCb2IyeGtaWEp6S1R0Y2JpQWdJQ0I5WEc0Z0lDQWdhV1lnS0hCaGNuUnBZV3h6VW1sbmFIUXBJSHRjYmlBZ0lDQWdJR0Z5WjNNZ1BTQmpiMjF3YjNObFFYSm5jMUpwWjJoMEtHRnlaM01zSUhCaGNuUnBZV3h6VW1sbmFIUXNJR2h2YkdSbGNuTlNhV2RvZENrN1hHNGdJQ0FnZlZ4dUlDQWdJR2xtSUNocGMwTjFjbko1SUh4OElHbHpRM1Z5Y25sU2FXZG9kQ2tnZTF4dUlDQWdJQ0FnZG1GeUlIQnNZV05sYUc5c1pHVnlJRDBnZDNKaGNIQmxjaTV3YkdGalpXaHZiR1JsY2l4Y2JpQWdJQ0FnSUNBZ0lDQmhjbWR6U0c5c1pHVnljeUE5SUhKbGNHeGhZMlZJYjJ4a1pYSnpLR0Z5WjNNc0lIQnNZV05sYUc5c1pHVnlLVHRjYmx4dUlDQWdJQ0FnYkdWdVozUm9JQzA5SUdGeVozTkliMnhrWlhKekxteGxibWQwYUR0Y2JpQWdJQ0FnSUdsbUlDaHNaVzVuZEdnZ1BDQmhjbWwwZVNrZ2UxeHVJQ0FnSUNBZ0lDQjJZWElnYm1WM1FYSm5VRzl6SUQwZ1lYSm5VRzl6SUQ4Z1lYSnlZWGxEYjNCNUtHRnlaMUJ2Y3lrZ09pQjFibVJsWm1sdVpXUXNYRzRnSUNBZ0lDQWdJQ0FnSUNCdVpYZEJjbWwwZVNBOUlHNWhkR2wyWlUxaGVDaGhjbWwwZVNBdElHeGxibWQwYUN3Z01Da3NYRzRnSUNBZ0lDQWdJQ0FnSUNCdVpYZHpTRzlzWkdWeWN5QTlJR2x6UTNWeWNua2dQeUJoY21kelNHOXNaR1Z5Y3lBNklIVnVaR1ZtYVc1bFpDeGNiaUFnSUNBZ0lDQWdJQ0FnSUc1bGQwaHZiR1JsY25OU2FXZG9kQ0E5SUdselEzVnljbmtnUHlCMWJtUmxabWx1WldRZ09pQmhjbWR6U0c5c1pHVnljeXhjYmlBZ0lDQWdJQ0FnSUNBZ0lHNWxkMUJoY25ScFlXeHpJRDBnYVhORGRYSnllU0EvSUdGeVozTWdPaUIxYm1SbFptbHVaV1FzWEc0Z0lDQWdJQ0FnSUNBZ0lDQnVaWGRRWVhKMGFXRnNjMUpwWjJoMElEMGdhWE5EZFhKeWVTQS9JSFZ1WkdWbWFXNWxaQ0E2SUdGeVozTTdYRzVjYmlBZ0lDQWdJQ0FnWW1sMGJXRnpheUI4UFNBb2FYTkRkWEp5ZVNBL0lGQkJVbFJKUVV4ZlJreEJSeUE2SUZCQlVsUkpRVXhmVWtsSFNGUmZSa3hCUnlrN1hHNGdJQ0FnSUNBZ0lHSnBkRzFoYzJzZ0pqMGdmaWhwYzBOMWNuSjVJRDhnVUVGU1ZFbEJURjlTU1VkSVZGOUdURUZISURvZ1VFRlNWRWxCVEY5R1RFRkhLVHRjYmx4dUlDQWdJQ0FnSUNCcFppQW9JV2x6UTNWeWNubENiM1Z1WkNrZ2UxeHVJQ0FnSUNBZ0lDQWdJR0pwZEcxaGMyc2dKajBnZmloQ1NVNUVYMFpNUVVjZ2ZDQkNTVTVFWDB0RldWOUdURUZIS1R0Y2JpQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUNBZ0lDQjJZWElnYm1WM1JHRjBZU0E5SUZ0bWRXNWpMQ0JpYVhSdFlYTnJMQ0IwYUdselFYSm5MQ0J1WlhkUVlYSjBhV0ZzY3l3Z2JtVjNjMGh2YkdSbGNuTXNJRzVsZDFCaGNuUnBZV3h6VW1sbmFIUXNJRzVsZDBodmJHUmxjbk5TYVdkb2RDd2dibVYzUVhKblVHOXpMQ0JoY25rc0lHNWxkMEZ5YVhSNVhTeGNiaUFnSUNBZ0lDQWdJQ0FnSUhKbGMzVnNkQ0E5SUdOeVpXRjBaVWg1WW5KcFpGZHlZWEJ3WlhJdVlYQndiSGtvZFc1a1pXWnBibVZrTENCdVpYZEVZWFJoS1R0Y2JseHVJQ0FnSUNBZ0lDQnBaaUFvYVhOTVlYcHBZV0pzWlNobWRXNWpLU2tnZTF4dUlDQWdJQ0FnSUNBZ0lITmxkRVJoZEdFb2NtVnpkV3gwTENCdVpYZEVZWFJoS1R0Y2JpQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUNBZ0lDQnlaWE4xYkhRdWNHeGhZMlZvYjJ4a1pYSWdQU0J3YkdGalpXaHZiR1JsY2p0Y2JpQWdJQ0FnSUNBZ2NtVjBkWEp1SUhKbGMzVnNkRHRjYmlBZ0lDQWdJSDFjYmlBZ0lDQjlYRzRnSUNBZ2RtRnlJSFJvYVhOQ2FXNWthVzVuSUQwZ2FYTkNhVzVrSUQ4Z2RHaHBjMEZ5WnlBNklIUm9hWE1zWEc0Z0lDQWdJQ0FnSUdadUlEMGdhWE5DYVc1a1MyVjVJRDhnZEdocGMwSnBibVJwYm1kYlpuVnVZMTBnT2lCbWRXNWpPMXh1WEc0Z0lDQWdhV1lnS0dGeVoxQnZjeWtnZTF4dUlDQWdJQ0FnWVhKbmN5QTlJSEpsYjNKa1pYSW9ZWEpuY3l3Z1lYSm5VRzl6S1R0Y2JpQWdJQ0I5WEc0Z0lDQWdhV1lnS0dselFYSjVJQ1ltSUdGeWVTQThJR0Z5WjNNdWJHVnVaM1JvS1NCN1hHNGdJQ0FnSUNCaGNtZHpMbXhsYm1kMGFDQTlJR0Z5ZVR0Y2JpQWdJQ0I5WEc0Z0lDQWdhV1lnS0hSb2FYTWdKaVlnZEdocGN5QWhQVDBnWjJ4dlltRnNJQ1ltSUhSb2FYTWdhVzV6ZEdGdVkyVnZaaUIzY21Gd2NHVnlLU0I3WEc0Z0lDQWdJQ0JtYmlBOUlFTjBiM0lnZkh3Z1kzSmxZWFJsUTNSdmNsZHlZWEJ3WlhJb1puVnVZeWs3WEc0Z0lDQWdmVnh1SUNBZ0lISmxkSFZ5YmlCbWJpNWhjSEJzZVNoMGFHbHpRbWx1WkdsdVp5d2dZWEpuY3lrN1hHNGdJSDFjYmlBZ2NtVjBkWEp1SUhkeVlYQndaWEk3WEc1OVhHNWNibTF2WkhWc1pTNWxlSEJ2Y25SeklEMGdZM0psWVhSbFNIbGljbWxrVjNKaGNIQmxjanRjYmlKZGZRPT0iLCIoZnVuY3Rpb24gKGdsb2JhbCl7XG52YXIgY3JlYXRlQ3RvcldyYXBwZXIgPSByZXF1aXJlKCcuL2NyZWF0ZUN0b3JXcmFwcGVyJyk7XG5cbi8qKiBVc2VkIHRvIGNvbXBvc2UgYml0bWFza3MgZm9yIHdyYXBwZXIgbWV0YWRhdGEuICovXG52YXIgQklORF9GTEFHID0gMTtcblxuLyoqXG4gKiBDcmVhdGVzIGEgZnVuY3Rpb24gdGhhdCB3cmFwcyBgZnVuY2AgYW5kIGludm9rZXMgaXQgd2l0aCB0aGUgb3B0aW9uYWwgYHRoaXNgXG4gKiBiaW5kaW5nIG9mIGB0aGlzQXJnYCBhbmQgdGhlIGBwYXJ0aWFsc2AgcHJlcGVuZGVkIHRvIHRob3NlIHByb3ZpZGVkIHRvXG4gKiB0aGUgd3JhcHBlci5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gcGFydGlhbGx5IGFwcGx5IGFyZ3VtZW50cyB0by5cbiAqIEBwYXJhbSB7bnVtYmVyfSBiaXRtYXNrIFRoZSBiaXRtYXNrIG9mIGZsYWdzLiBTZWUgYGNyZWF0ZVdyYXBwZXJgIGZvciBtb3JlIGRldGFpbHMuXG4gKiBAcGFyYW0geyp9IHRoaXNBcmcgVGhlIGB0aGlzYCBiaW5kaW5nIG9mIGBmdW5jYC5cbiAqIEBwYXJhbSB7QXJyYXl9IHBhcnRpYWxzIFRoZSBhcmd1bWVudHMgdG8gcHJlcGVuZCB0byB0aG9zZSBwcm92aWRlZCB0byB0aGUgbmV3IGZ1bmN0aW9uLlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgYm91bmQgZnVuY3Rpb24uXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZVBhcnRpYWxXcmFwcGVyKGZ1bmMsIGJpdG1hc2ssIHRoaXNBcmcsIHBhcnRpYWxzKSB7XG4gIHZhciBpc0JpbmQgPSBiaXRtYXNrICYgQklORF9GTEFHLFxuICAgICAgQ3RvciA9IGNyZWF0ZUN0b3JXcmFwcGVyKGZ1bmMpO1xuXG4gIGZ1bmN0aW9uIHdyYXBwZXIoKSB7XG4gICAgLy8gQXZvaWQgYGFyZ3VtZW50c2Agb2JqZWN0IHVzZSBkaXNxdWFsaWZ5aW5nIG9wdGltaXphdGlvbnMgYnlcbiAgICAvLyBjb252ZXJ0aW5nIGl0IHRvIGFuIGFycmF5IGJlZm9yZSBwcm92aWRpbmcgaXQgYGZ1bmNgLlxuICAgIHZhciBhcmdzSW5kZXggPSAtMSxcbiAgICAgICAgYXJnc0xlbmd0aCA9IGFyZ3VtZW50cy5sZW5ndGgsXG4gICAgICAgIGxlZnRJbmRleCA9IC0xLFxuICAgICAgICBsZWZ0TGVuZ3RoID0gcGFydGlhbHMubGVuZ3RoLFxuICAgICAgICBhcmdzID0gQXJyYXkobGVmdExlbmd0aCArIGFyZ3NMZW5ndGgpO1xuXG4gICAgd2hpbGUgKCsrbGVmdEluZGV4IDwgbGVmdExlbmd0aCkge1xuICAgICAgYXJnc1tsZWZ0SW5kZXhdID0gcGFydGlhbHNbbGVmdEluZGV4XTtcbiAgICB9XG4gICAgd2hpbGUgKGFyZ3NMZW5ndGgtLSkge1xuICAgICAgYXJnc1tsZWZ0SW5kZXgrK10gPSBhcmd1bWVudHNbKythcmdzSW5kZXhdO1xuICAgIH1cbiAgICB2YXIgZm4gPSAodGhpcyAmJiB0aGlzICE9PSBnbG9iYWwgJiYgdGhpcyBpbnN0YW5jZW9mIHdyYXBwZXIpID8gQ3RvciA6IGZ1bmM7XG4gICAgcmV0dXJuIGZuLmFwcGx5KGlzQmluZCA/IHRoaXNBcmcgOiB0aGlzLCBhcmdzKTtcbiAgfVxuICByZXR1cm4gd3JhcHBlcjtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBjcmVhdGVQYXJ0aWFsV3JhcHBlcjtcblxufSkuY2FsbCh0aGlzLHR5cGVvZiBnbG9iYWwgIT09IFwidW5kZWZpbmVkXCIgPyBnbG9iYWwgOiB0eXBlb2Ygc2VsZiAhPT0gXCJ1bmRlZmluZWRcIiA/IHNlbGYgOiB0eXBlb2Ygd2luZG93ICE9PSBcInVuZGVmaW5lZFwiID8gd2luZG93IDoge30pXG4vLyMgc291cmNlTWFwcGluZ1VSTD1kYXRhOmFwcGxpY2F0aW9uL2pzb247Y2hhcnNldDp1dGYtODtiYXNlNjQsZXlKMlpYSnphVzl1SWpvekxDSnpiM1Z5WTJWeklqcGJJbTV2WkdWZmJXOWtkV3hsY3k5c2IyUmhjMmd0WTI5dGNHRjBMMmx1ZEdWeWJtRnNMMk55WldGMFpWQmhjblJwWVd4WGNtRndjR1Z5TG1weklsMHNJbTVoYldWeklqcGJYU3dpYldGd2NHbHVaM01pT2lJN1FVRkJRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJJaXdpWm1sc1pTSTZJbWRsYm1WeVlYUmxaQzVxY3lJc0luTnZkWEpqWlZKdmIzUWlPaUlpTENKemIzVnlZMlZ6UTI5dWRHVnVkQ0k2V3lKMllYSWdZM0psWVhSbFEzUnZjbGR5WVhCd1pYSWdQU0J5WlhGMWFYSmxLQ2N1TDJOeVpXRjBaVU4wYjNKWGNtRndjR1Z5SnlrN1hHNWNiaThxS2lCVmMyVmtJSFJ2SUdOdmJYQnZjMlVnWW1sMGJXRnphM01nWm05eUlIZHlZWEJ3WlhJZ2JXVjBZV1JoZEdFdUlDb3ZYRzUyWVhJZ1FrbE9SRjlHVEVGSElEMGdNVHRjYmx4dUx5b3FYRzRnS2lCRGNtVmhkR1Z6SUdFZ1puVnVZM1JwYjI0Z2RHaGhkQ0IzY21Gd2N5QmdablZ1WTJBZ1lXNWtJR2x1ZG05clpYTWdhWFFnZDJsMGFDQjBhR1VnYjNCMGFXOXVZV3dnWUhSb2FYTmdYRzRnS2lCaWFXNWthVzVuSUc5bUlHQjBhR2x6UVhKbllDQmhibVFnZEdobElHQndZWEowYVdGc2MyQWdjSEpsY0dWdVpHVmtJSFJ2SUhSb2IzTmxJSEJ5YjNacFpHVmtJSFJ2WEc0Z0tpQjBhR1VnZDNKaGNIQmxjaTVjYmlBcVhHNGdLaUJBY0hKcGRtRjBaVnh1SUNvZ1FIQmhjbUZ0SUh0R2RXNWpkR2x2Ym4wZ1puVnVZeUJVYUdVZ1puVnVZM1JwYjI0Z2RHOGdjR0Z5ZEdsaGJHeDVJR0Z3Y0d4NUlHRnlaM1Z0Wlc1MGN5QjBieTVjYmlBcUlFQndZWEpoYlNCN2JuVnRZbVZ5ZlNCaWFYUnRZWE5ySUZSb1pTQmlhWFJ0WVhOcklHOW1JR1pzWVdkekxpQlRaV1VnWUdOeVpXRjBaVmR5WVhCd1pYSmdJR1p2Y2lCdGIzSmxJR1JsZEdGcGJITXVYRzRnS2lCQWNHRnlZVzBnZXlwOUlIUm9hWE5CY21jZ1ZHaGxJR0IwYUdsellDQmlhVzVrYVc1bklHOW1JR0JtZFc1allDNWNiaUFxSUVCd1lYSmhiU0I3UVhKeVlYbDlJSEJoY25ScFlXeHpJRlJvWlNCaGNtZDFiV1Z1ZEhNZ2RHOGdjSEpsY0dWdVpDQjBieUIwYUc5elpTQndjbTkyYVdSbFpDQjBieUIwYUdVZ2JtVjNJR1oxYm1OMGFXOXVMbHh1SUNvZ1FISmxkSFZ5Ym5NZ2UwWjFibU4wYVc5dWZTQlNaWFIxY201eklIUm9aU0J1WlhjZ1ltOTFibVFnWm5WdVkzUnBiMjR1WEc0Z0tpOWNibVoxYm1OMGFXOXVJR055WldGMFpWQmhjblJwWVd4WGNtRndjR1Z5S0daMWJtTXNJR0pwZEcxaGMyc3NJSFJvYVhOQmNtY3NJSEJoY25ScFlXeHpLU0I3WEc0Z0lIWmhjaUJwYzBKcGJtUWdQU0JpYVhSdFlYTnJJQ1lnUWtsT1JGOUdURUZITEZ4dUlDQWdJQ0FnUTNSdmNpQTlJR055WldGMFpVTjBiM0pYY21Gd2NHVnlLR1oxYm1NcE8xeHVYRzRnSUdaMWJtTjBhVzl1SUhkeVlYQndaWElvS1NCN1hHNGdJQ0FnTHk4Z1FYWnZhV1FnWUdGeVozVnRaVzUwYzJBZ2IySnFaV04wSUhWelpTQmthWE54ZFdGc2FXWjVhVzVuSUc5d2RHbHRhWHBoZEdsdmJuTWdZbmxjYmlBZ0lDQXZMeUJqYjI1MlpYSjBhVzVuSUdsMElIUnZJR0Z1SUdGeWNtRjVJR0psWm05eVpTQndjbTkyYVdScGJtY2dhWFFnWUdaMWJtTmdMbHh1SUNBZ0lIWmhjaUJoY21kelNXNWtaWGdnUFNBdE1TeGNiaUFnSUNBZ0lDQWdZWEpuYzB4bGJtZDBhQ0E5SUdGeVozVnRaVzUwY3k1c1pXNW5kR2dzWEc0Z0lDQWdJQ0FnSUd4bFpuUkpibVJsZUNBOUlDMHhMRnh1SUNBZ0lDQWdJQ0JzWldaMFRHVnVaM1JvSUQwZ2NHRnlkR2xoYkhNdWJHVnVaM1JvTEZ4dUlDQWdJQ0FnSUNCaGNtZHpJRDBnUVhKeVlYa29iR1ZtZEV4bGJtZDBhQ0FySUdGeVozTk1aVzVuZEdncE8xeHVYRzRnSUNBZ2QyaHBiR1VnS0NzcmJHVm1kRWx1WkdWNElEd2diR1ZtZEV4bGJtZDBhQ2tnZTF4dUlDQWdJQ0FnWVhKbmMxdHNaV1owU1c1a1pYaGRJRDBnY0dGeWRHbGhiSE5iYkdWbWRFbHVaR1Y0WFR0Y2JpQWdJQ0I5WEc0Z0lDQWdkMmhwYkdVZ0tHRnlaM05NWlc1bmRHZ3RMU2tnZTF4dUlDQWdJQ0FnWVhKbmMxdHNaV1owU1c1a1pYZ3JLMTBnUFNCaGNtZDFiV1Z1ZEhOYkt5dGhjbWR6U1c1a1pYaGRPMXh1SUNBZ0lIMWNiaUFnSUNCMllYSWdabTRnUFNBb2RHaHBjeUFtSmlCMGFHbHpJQ0U5UFNCbmJHOWlZV3dnSmlZZ2RHaHBjeUJwYm5OMFlXNWpaVzltSUhkeVlYQndaWElwSUQ4Z1EzUnZjaUE2SUdaMWJtTTdYRzRnSUNBZ2NtVjBkWEp1SUdadUxtRndjR3g1S0dselFtbHVaQ0EvSUhSb2FYTkJjbWNnT2lCMGFHbHpMQ0JoY21kektUdGNiaUFnZlZ4dUlDQnlaWFIxY200Z2QzSmhjSEJsY2p0Y2JuMWNibHh1Ylc5a2RXeGxMbVY0Y0c5eWRITWdQU0JqY21WaGRHVlFZWEowYVdGc1YzSmhjSEJsY2p0Y2JpSmRmUT09IiwidmFyIGJhc2VTZXREYXRhID0gcmVxdWlyZSgnLi9iYXNlU2V0RGF0YScpLFxuICAgIGNyZWF0ZUJpbmRXcmFwcGVyID0gcmVxdWlyZSgnLi9jcmVhdGVCaW5kV3JhcHBlcicpLFxuICAgIGNyZWF0ZUh5YnJpZFdyYXBwZXIgPSByZXF1aXJlKCcuL2NyZWF0ZUh5YnJpZFdyYXBwZXInKSxcbiAgICBjcmVhdGVQYXJ0aWFsV3JhcHBlciA9IHJlcXVpcmUoJy4vY3JlYXRlUGFydGlhbFdyYXBwZXInKSxcbiAgICBnZXREYXRhID0gcmVxdWlyZSgnLi9nZXREYXRhJyksXG4gICAgbWVyZ2VEYXRhID0gcmVxdWlyZSgnLi9tZXJnZURhdGEnKSxcbiAgICBzZXREYXRhID0gcmVxdWlyZSgnLi9zZXREYXRhJyk7XG5cbi8qKiBVc2VkIHRvIGNvbXBvc2UgYml0bWFza3MgZm9yIHdyYXBwZXIgbWV0YWRhdGEuICovXG52YXIgQklORF9GTEFHID0gMSxcbiAgICBCSU5EX0tFWV9GTEFHID0gMixcbiAgICBQQVJUSUFMX0ZMQUcgPSAzMixcbiAgICBQQVJUSUFMX1JJR0hUX0ZMQUcgPSA2NDtcblxuLyoqIFVzZWQgYXMgdGhlIGBUeXBlRXJyb3JgIG1lc3NhZ2UgZm9yIFwiRnVuY3Rpb25zXCIgbWV0aG9kcy4gKi9cbnZhciBGVU5DX0VSUk9SX1RFWFQgPSAnRXhwZWN0ZWQgYSBmdW5jdGlvbic7XG5cbi8qIE5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXg7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGZ1bmN0aW9uIHRoYXQgZWl0aGVyIGN1cnJpZXMgb3IgaW52b2tlcyBgZnVuY2Agd2l0aCBvcHRpb25hbFxuICogYHRoaXNgIGJpbmRpbmcgYW5kIHBhcnRpYWxseSBhcHBsaWVkIGFyZ3VtZW50cy5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtGdW5jdGlvbnxzdHJpbmd9IGZ1bmMgVGhlIGZ1bmN0aW9uIG9yIG1ldGhvZCBuYW1lIHRvIHJlZmVyZW5jZS5cbiAqIEBwYXJhbSB7bnVtYmVyfSBiaXRtYXNrIFRoZSBiaXRtYXNrIG9mIGZsYWdzLlxuICogIFRoZSBiaXRtYXNrIG1heSBiZSBjb21wb3NlZCBvZiB0aGUgZm9sbG93aW5nIGZsYWdzOlxuICogICAgIDEgLSBgXy5iaW5kYFxuICogICAgIDIgLSBgXy5iaW5kS2V5YFxuICogICAgIDQgLSBgXy5jdXJyeWAgb3IgYF8uY3VycnlSaWdodGAgb2YgYSBib3VuZCBmdW5jdGlvblxuICogICAgIDggLSBgXy5jdXJyeWBcbiAqICAgIDE2IC0gYF8uY3VycnlSaWdodGBcbiAqICAgIDMyIC0gYF8ucGFydGlhbGBcbiAqICAgIDY0IC0gYF8ucGFydGlhbFJpZ2h0YFxuICogICAxMjggLSBgXy5yZWFyZ2BcbiAqICAgMjU2IC0gYF8uYXJ5YFxuICogQHBhcmFtIHsqfSBbdGhpc0FyZ10gVGhlIGB0aGlzYCBiaW5kaW5nIG9mIGBmdW5jYC5cbiAqIEBwYXJhbSB7QXJyYXl9IFtwYXJ0aWFsc10gVGhlIGFyZ3VtZW50cyB0byBiZSBwYXJ0aWFsbHkgYXBwbGllZC5cbiAqIEBwYXJhbSB7QXJyYXl9IFtob2xkZXJzXSBUaGUgYHBhcnRpYWxzYCBwbGFjZWhvbGRlciBpbmRleGVzLlxuICogQHBhcmFtIHtBcnJheX0gW2FyZ1Bvc10gVGhlIGFyZ3VtZW50IHBvc2l0aW9ucyBvZiB0aGUgbmV3IGZ1bmN0aW9uLlxuICogQHBhcmFtIHtudW1iZXJ9IFthcnldIFRoZSBhcml0eSBjYXAgb2YgYGZ1bmNgLlxuICogQHBhcmFtIHtudW1iZXJ9IFthcml0eV0gVGhlIGFyaXR5IG9mIGBmdW5jYC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IHdyYXBwZWQgZnVuY3Rpb24uXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZVdyYXBwZXIoZnVuYywgYml0bWFzaywgdGhpc0FyZywgcGFydGlhbHMsIGhvbGRlcnMsIGFyZ1BvcywgYXJ5LCBhcml0eSkge1xuICB2YXIgaXNCaW5kS2V5ID0gYml0bWFzayAmIEJJTkRfS0VZX0ZMQUc7XG4gIGlmICghaXNCaW5kS2V5ICYmIHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgdmFyIGxlbmd0aCA9IHBhcnRpYWxzID8gcGFydGlhbHMubGVuZ3RoIDogMDtcbiAgaWYgKCFsZW5ndGgpIHtcbiAgICBiaXRtYXNrICY9IH4oUEFSVElBTF9GTEFHIHwgUEFSVElBTF9SSUdIVF9GTEFHKTtcbiAgICBwYXJ0aWFscyA9IGhvbGRlcnMgPSB1bmRlZmluZWQ7XG4gIH1cbiAgbGVuZ3RoIC09IChob2xkZXJzID8gaG9sZGVycy5sZW5ndGggOiAwKTtcbiAgaWYgKGJpdG1hc2sgJiBQQVJUSUFMX1JJR0hUX0ZMQUcpIHtcbiAgICB2YXIgcGFydGlhbHNSaWdodCA9IHBhcnRpYWxzLFxuICAgICAgICBob2xkZXJzUmlnaHQgPSBob2xkZXJzO1xuXG4gICAgcGFydGlhbHMgPSBob2xkZXJzID0gdW5kZWZpbmVkO1xuICB9XG4gIHZhciBkYXRhID0gaXNCaW5kS2V5ID8gdW5kZWZpbmVkIDogZ2V0RGF0YShmdW5jKSxcbiAgICAgIG5ld0RhdGEgPSBbZnVuYywgYml0bWFzaywgdGhpc0FyZywgcGFydGlhbHMsIGhvbGRlcnMsIHBhcnRpYWxzUmlnaHQsIGhvbGRlcnNSaWdodCwgYXJnUG9zLCBhcnksIGFyaXR5XTtcblxuICBpZiAoZGF0YSkge1xuICAgIG1lcmdlRGF0YShuZXdEYXRhLCBkYXRhKTtcbiAgICBiaXRtYXNrID0gbmV3RGF0YVsxXTtcbiAgICBhcml0eSA9IG5ld0RhdGFbOV07XG4gIH1cbiAgbmV3RGF0YVs5XSA9IGFyaXR5ID09IG51bGxcbiAgICA/IChpc0JpbmRLZXkgPyAwIDogZnVuYy5sZW5ndGgpXG4gICAgOiAobmF0aXZlTWF4KGFyaXR5IC0gbGVuZ3RoLCAwKSB8fCAwKTtcblxuICBpZiAoYml0bWFzayA9PSBCSU5EX0ZMQUcpIHtcbiAgICB2YXIgcmVzdWx0ID0gY3JlYXRlQmluZFdyYXBwZXIobmV3RGF0YVswXSwgbmV3RGF0YVsyXSk7XG4gIH0gZWxzZSBpZiAoKGJpdG1hc2sgPT0gUEFSVElBTF9GTEFHIHx8IGJpdG1hc2sgPT0gKEJJTkRfRkxBRyB8IFBBUlRJQUxfRkxBRykpICYmICFuZXdEYXRhWzRdLmxlbmd0aCkge1xuICAgIHJlc3VsdCA9IGNyZWF0ZVBhcnRpYWxXcmFwcGVyLmFwcGx5KHVuZGVmaW5lZCwgbmV3RGF0YSk7XG4gIH0gZWxzZSB7XG4gICAgcmVzdWx0ID0gY3JlYXRlSHlicmlkV3JhcHBlci5hcHBseSh1bmRlZmluZWQsIG5ld0RhdGEpO1xuICB9XG4gIHZhciBzZXR0ZXIgPSBkYXRhID8gYmFzZVNldERhdGEgOiBzZXREYXRhO1xuICByZXR1cm4gc2V0dGVyKHJlc3VsdCwgbmV3RGF0YSk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gY3JlYXRlV3JhcHBlcjtcbiIsInZhciBhcnJheVNvbWUgPSByZXF1aXJlKCcuL2FycmF5U29tZScpO1xuXG4vKipcbiAqIEEgc3BlY2lhbGl6ZWQgdmVyc2lvbiBvZiBgYmFzZUlzRXF1YWxEZWVwYCBmb3IgYXJyYXlzIHdpdGggc3VwcG9ydCBmb3JcbiAqIHBhcnRpYWwgZGVlcCBjb21wYXJpc29ucy5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtBcnJheX0gYXJyYXkgVGhlIGFycmF5IHRvIGNvbXBhcmUuXG4gKiBAcGFyYW0ge0FycmF5fSBvdGhlciBUaGUgb3RoZXIgYXJyYXkgdG8gY29tcGFyZS5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGVxdWFsRnVuYyBUaGUgZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIGVxdWl2YWxlbnRzIG9mIHZhbHVlcy5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IFtjdXN0b21pemVyXSBUaGUgZnVuY3Rpb24gdG8gY3VzdG9taXplIGNvbXBhcmluZyBhcnJheXMuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtpc0xvb3NlXSBTcGVjaWZ5IHBlcmZvcm1pbmcgcGFydGlhbCBjb21wYXJpc29ucy5cbiAqIEBwYXJhbSB7QXJyYXl9IFtzdGFja0FdIFRyYWNrcyB0cmF2ZXJzZWQgYHZhbHVlYCBvYmplY3RzLlxuICogQHBhcmFtIHtBcnJheX0gW3N0YWNrQl0gVHJhY2tzIHRyYXZlcnNlZCBgb3RoZXJgIG9iamVjdHMuXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgdGhlIGFycmF5cyBhcmUgZXF1aXZhbGVudCwgZWxzZSBgZmFsc2VgLlxuICovXG5mdW5jdGlvbiBlcXVhbEFycmF5cyhhcnJheSwgb3RoZXIsIGVxdWFsRnVuYywgY3VzdG9taXplciwgaXNMb29zZSwgc3RhY2tBLCBzdGFja0IpIHtcbiAgdmFyIGluZGV4ID0gLTEsXG4gICAgICBhcnJMZW5ndGggPSBhcnJheS5sZW5ndGgsXG4gICAgICBvdGhMZW5ndGggPSBvdGhlci5sZW5ndGg7XG5cbiAgaWYgKGFyckxlbmd0aCAhPSBvdGhMZW5ndGggJiYgIShpc0xvb3NlICYmIG90aExlbmd0aCA+IGFyckxlbmd0aCkpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgLy8gSWdub3JlIG5vbi1pbmRleCBwcm9wZXJ0aWVzLlxuICB3aGlsZSAoKytpbmRleCA8IGFyckxlbmd0aCkge1xuICAgIHZhciBhcnJWYWx1ZSA9IGFycmF5W2luZGV4XSxcbiAgICAgICAgb3RoVmFsdWUgPSBvdGhlcltpbmRleF0sXG4gICAgICAgIHJlc3VsdCA9IGN1c3RvbWl6ZXIgPyBjdXN0b21pemVyKGlzTG9vc2UgPyBvdGhWYWx1ZSA6IGFyclZhbHVlLCBpc0xvb3NlID8gYXJyVmFsdWUgOiBvdGhWYWx1ZSwgaW5kZXgpIDogdW5kZWZpbmVkO1xuXG4gICAgaWYgKHJlc3VsdCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBpZiAocmVzdWx0KSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICAvLyBSZWN1cnNpdmVseSBjb21wYXJlIGFycmF5cyAoc3VzY2VwdGlibGUgdG8gY2FsbCBzdGFjayBsaW1pdHMpLlxuICAgIGlmIChpc0xvb3NlKSB7XG4gICAgICBpZiAoIWFycmF5U29tZShvdGhlciwgZnVuY3Rpb24ob3RoVmFsdWUpIHtcbiAgICAgICAgICAgIHJldHVybiBhcnJWYWx1ZSA9PT0gb3RoVmFsdWUgfHwgZXF1YWxGdW5jKGFyclZhbHVlLCBvdGhWYWx1ZSwgY3VzdG9taXplciwgaXNMb29zZSwgc3RhY2tBLCBzdGFja0IpO1xuICAgICAgICAgIH0pKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKCEoYXJyVmFsdWUgPT09IG90aFZhbHVlIHx8IGVxdWFsRnVuYyhhcnJWYWx1ZSwgb3RoVmFsdWUsIGN1c3RvbWl6ZXIsIGlzTG9vc2UsIHN0YWNrQSwgc3RhY2tCKSkpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHRydWU7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gZXF1YWxBcnJheXM7XG4iLCIvKiogYE9iamVjdCN0b1N0cmluZ2AgcmVzdWx0IHJlZmVyZW5jZXMuICovXG52YXIgYm9vbFRhZyA9ICdbb2JqZWN0IEJvb2xlYW5dJyxcbiAgICBkYXRlVGFnID0gJ1tvYmplY3QgRGF0ZV0nLFxuICAgIGVycm9yVGFnID0gJ1tvYmplY3QgRXJyb3JdJyxcbiAgICBudW1iZXJUYWcgPSAnW29iamVjdCBOdW1iZXJdJyxcbiAgICByZWdleHBUYWcgPSAnW29iamVjdCBSZWdFeHBdJyxcbiAgICBzdHJpbmdUYWcgPSAnW29iamVjdCBTdHJpbmddJztcblxuLyoqXG4gKiBBIHNwZWNpYWxpemVkIHZlcnNpb24gb2YgYGJhc2VJc0VxdWFsRGVlcGAgZm9yIGNvbXBhcmluZyBvYmplY3RzIG9mXG4gKiB0aGUgc2FtZSBgdG9TdHJpbmdUYWdgLlxuICpcbiAqICoqTm90ZToqKiBUaGlzIGZ1bmN0aW9uIG9ubHkgc3VwcG9ydHMgY29tcGFyaW5nIHZhbHVlcyB3aXRoIHRhZ3Mgb2ZcbiAqIGBCb29sZWFuYCwgYERhdGVgLCBgRXJyb3JgLCBgTnVtYmVyYCwgYFJlZ0V4cGAsIG9yIGBTdHJpbmdgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gY29tcGFyZS5cbiAqIEBwYXJhbSB7T2JqZWN0fSBvdGhlciBUaGUgb3RoZXIgb2JqZWN0IHRvIGNvbXBhcmUuXG4gKiBAcGFyYW0ge3N0cmluZ30gdGFnIFRoZSBgdG9TdHJpbmdUYWdgIG9mIHRoZSBvYmplY3RzIHRvIGNvbXBhcmUuXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgdGhlIG9iamVjdHMgYXJlIGVxdWl2YWxlbnQsIGVsc2UgYGZhbHNlYC5cbiAqL1xuZnVuY3Rpb24gZXF1YWxCeVRhZyhvYmplY3QsIG90aGVyLCB0YWcpIHtcbiAgc3dpdGNoICh0YWcpIHtcbiAgICBjYXNlIGJvb2xUYWc6XG4gICAgY2FzZSBkYXRlVGFnOlxuICAgICAgLy8gQ29lcmNlIGRhdGVzIGFuZCBib29sZWFucyB0byBudW1iZXJzLCBkYXRlcyB0byBtaWxsaXNlY29uZHMgYW5kIGJvb2xlYW5zXG4gICAgICAvLyB0byBgMWAgb3IgYDBgIHRyZWF0aW5nIGludmFsaWQgZGF0ZXMgY29lcmNlZCB0byBgTmFOYCBhcyBub3QgZXF1YWwuXG4gICAgICByZXR1cm4gK29iamVjdCA9PSArb3RoZXI7XG5cbiAgICBjYXNlIGVycm9yVGFnOlxuICAgICAgcmV0dXJuIG9iamVjdC5uYW1lID09IG90aGVyLm5hbWUgJiYgb2JqZWN0Lm1lc3NhZ2UgPT0gb3RoZXIubWVzc2FnZTtcblxuICAgIGNhc2UgbnVtYmVyVGFnOlxuICAgICAgLy8gVHJlYXQgYE5hTmAgdnMuIGBOYU5gIGFzIGVxdWFsLlxuICAgICAgcmV0dXJuIChvYmplY3QgIT0gK29iamVjdClcbiAgICAgICAgPyBvdGhlciAhPSArb3RoZXJcbiAgICAgICAgOiBvYmplY3QgPT0gK290aGVyO1xuXG4gICAgY2FzZSByZWdleHBUYWc6XG4gICAgY2FzZSBzdHJpbmdUYWc6XG4gICAgICAvLyBDb2VyY2UgcmVnZXhlcyB0byBzdHJpbmdzIGFuZCB0cmVhdCBzdHJpbmdzIHByaW1pdGl2ZXMgYW5kIHN0cmluZ1xuICAgICAgLy8gb2JqZWN0cyBhcyBlcXVhbC4gU2VlIGh0dHBzOi8vZXM1LmdpdGh1Yi5pby8jeDE1LjEwLjYuNCBmb3IgbW9yZSBkZXRhaWxzLlxuICAgICAgcmV0dXJuIG9iamVjdCA9PSAob3RoZXIgKyAnJyk7XG4gIH1cbiAgcmV0dXJuIGZhbHNlO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGVxdWFsQnlUYWc7XG4iLCJ2YXIga2V5cyA9IHJlcXVpcmUoJy4uL29iamVjdC9rZXlzJyk7XG5cbi8qKiBVc2VkIGZvciBuYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgb2JqZWN0UHJvdG8gPSBPYmplY3QucHJvdG90eXBlO1xuXG4vKiogVXNlZCB0byBjaGVjayBvYmplY3RzIGZvciBvd24gcHJvcGVydGllcy4gKi9cbnZhciBoYXNPd25Qcm9wZXJ0eSA9IG9iamVjdFByb3RvLmhhc093blByb3BlcnR5O1xuXG4vKipcbiAqIEEgc3BlY2lhbGl6ZWQgdmVyc2lvbiBvZiBgYmFzZUlzRXF1YWxEZWVwYCBmb3Igb2JqZWN0cyB3aXRoIHN1cHBvcnQgZm9yXG4gKiBwYXJ0aWFsIGRlZXAgY29tcGFyaXNvbnMuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmplY3QgVGhlIG9iamVjdCB0byBjb21wYXJlLlxuICogQHBhcmFtIHtPYmplY3R9IG90aGVyIFRoZSBvdGhlciBvYmplY3QgdG8gY29tcGFyZS5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGVxdWFsRnVuYyBUaGUgZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIGVxdWl2YWxlbnRzIG9mIHZhbHVlcy5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IFtjdXN0b21pemVyXSBUaGUgZnVuY3Rpb24gdG8gY3VzdG9taXplIGNvbXBhcmluZyB2YWx1ZXMuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtpc0xvb3NlXSBTcGVjaWZ5IHBlcmZvcm1pbmcgcGFydGlhbCBjb21wYXJpc29ucy5cbiAqIEBwYXJhbSB7QXJyYXl9IFtzdGFja0FdIFRyYWNrcyB0cmF2ZXJzZWQgYHZhbHVlYCBvYmplY3RzLlxuICogQHBhcmFtIHtBcnJheX0gW3N0YWNrQl0gVHJhY2tzIHRyYXZlcnNlZCBgb3RoZXJgIG9iamVjdHMuXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgdGhlIG9iamVjdHMgYXJlIGVxdWl2YWxlbnQsIGVsc2UgYGZhbHNlYC5cbiAqL1xuZnVuY3Rpb24gZXF1YWxPYmplY3RzKG9iamVjdCwgb3RoZXIsIGVxdWFsRnVuYywgY3VzdG9taXplciwgaXNMb29zZSwgc3RhY2tBLCBzdGFja0IpIHtcbiAgdmFyIG9ialByb3BzID0ga2V5cyhvYmplY3QpLFxuICAgICAgb2JqTGVuZ3RoID0gb2JqUHJvcHMubGVuZ3RoLFxuICAgICAgb3RoUHJvcHMgPSBrZXlzKG90aGVyKSxcbiAgICAgIG90aExlbmd0aCA9IG90aFByb3BzLmxlbmd0aDtcblxuICBpZiAob2JqTGVuZ3RoICE9IG90aExlbmd0aCAmJiAhaXNMb29zZSkge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICB2YXIgaW5kZXggPSBvYmpMZW5ndGg7XG4gIHdoaWxlIChpbmRleC0tKSB7XG4gICAgdmFyIGtleSA9IG9ialByb3BzW2luZGV4XTtcbiAgICBpZiAoIShpc0xvb3NlID8ga2V5IGluIG90aGVyIDogaGFzT3duUHJvcGVydHkuY2FsbChvdGhlciwga2V5KSkpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gIH1cbiAgdmFyIHNraXBDdG9yID0gaXNMb29zZTtcbiAgd2hpbGUgKCsraW5kZXggPCBvYmpMZW5ndGgpIHtcbiAgICBrZXkgPSBvYmpQcm9wc1tpbmRleF07XG4gICAgdmFyIG9ialZhbHVlID0gb2JqZWN0W2tleV0sXG4gICAgICAgIG90aFZhbHVlID0gb3RoZXJba2V5XSxcbiAgICAgICAgcmVzdWx0ID0gY3VzdG9taXplciA/IGN1c3RvbWl6ZXIoaXNMb29zZSA/IG90aFZhbHVlIDogb2JqVmFsdWUsIGlzTG9vc2U/IG9ialZhbHVlIDogb3RoVmFsdWUsIGtleSkgOiB1bmRlZmluZWQ7XG5cbiAgICAvLyBSZWN1cnNpdmVseSBjb21wYXJlIG9iamVjdHMgKHN1c2NlcHRpYmxlIHRvIGNhbGwgc3RhY2sgbGltaXRzKS5cbiAgICBpZiAoIShyZXN1bHQgPT09IHVuZGVmaW5lZCA/IGVxdWFsRnVuYyhvYmpWYWx1ZSwgb3RoVmFsdWUsIGN1c3RvbWl6ZXIsIGlzTG9vc2UsIHN0YWNrQSwgc3RhY2tCKSA6IHJlc3VsdCkpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgc2tpcEN0b3IgfHwgKHNraXBDdG9yID0ga2V5ID09ICdjb25zdHJ1Y3RvcicpO1xuICB9XG4gIGlmICghc2tpcEN0b3IpIHtcbiAgICB2YXIgb2JqQ3RvciA9IG9iamVjdC5jb25zdHJ1Y3RvcixcbiAgICAgICAgb3RoQ3RvciA9IG90aGVyLmNvbnN0cnVjdG9yO1xuXG4gICAgLy8gTm9uIGBPYmplY3RgIG9iamVjdCBpbnN0YW5jZXMgd2l0aCBkaWZmZXJlbnQgY29uc3RydWN0b3JzIGFyZSBub3QgZXF1YWwuXG4gICAgaWYgKG9iakN0b3IgIT0gb3RoQ3RvciAmJlxuICAgICAgICAoJ2NvbnN0cnVjdG9yJyBpbiBvYmplY3QgJiYgJ2NvbnN0cnVjdG9yJyBpbiBvdGhlcikgJiZcbiAgICAgICAgISh0eXBlb2Ygb2JqQ3RvciA9PSAnZnVuY3Rpb24nICYmIG9iakN0b3IgaW5zdGFuY2VvZiBvYmpDdG9yICYmXG4gICAgICAgICAgdHlwZW9mIG90aEN0b3IgPT0gJ2Z1bmN0aW9uJyAmJiBvdGhDdG9yIGluc3RhbmNlb2Ygb3RoQ3RvcikpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHRydWU7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gZXF1YWxPYmplY3RzO1xuIiwidmFyIG1ldGFNYXAgPSByZXF1aXJlKCcuL21ldGFNYXAnKSxcbiAgICBub29wID0gcmVxdWlyZSgnLi4vdXRpbGl0eS9ub29wJyk7XG5cbi8qKlxuICogR2V0cyBtZXRhZGF0YSBmb3IgYGZ1bmNgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmdW5jIFRoZSBmdW5jdGlvbiB0byBxdWVyeS5cbiAqIEByZXR1cm5zIHsqfSBSZXR1cm5zIHRoZSBtZXRhZGF0YSBmb3IgYGZ1bmNgLlxuICovXG52YXIgZ2V0RGF0YSA9ICFtZXRhTWFwID8gbm9vcCA6IGZ1bmN0aW9uKGZ1bmMpIHtcbiAgcmV0dXJuIG1ldGFNYXAuZ2V0KGZ1bmMpO1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBnZXREYXRhO1xuIiwidmFyIHJlYWxOYW1lcyA9IHJlcXVpcmUoJy4vcmVhbE5hbWVzJyk7XG5cbi8qKlxuICogR2V0cyB0aGUgbmFtZSBvZiBgZnVuY2AuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIHF1ZXJ5LlxuICogQHJldHVybnMge3N0cmluZ30gUmV0dXJucyB0aGUgZnVuY3Rpb24gbmFtZS5cbiAqL1xuZnVuY3Rpb24gZ2V0RnVuY05hbWUoZnVuYykge1xuICB2YXIgcmVzdWx0ID0gKGZ1bmMubmFtZSArICcnKSxcbiAgICAgIGFycmF5ID0gcmVhbE5hbWVzW3Jlc3VsdF0sXG4gICAgICBsZW5ndGggPSBhcnJheSA/IGFycmF5Lmxlbmd0aCA6IDA7XG5cbiAgd2hpbGUgKGxlbmd0aC0tKSB7XG4gICAgdmFyIGRhdGEgPSBhcnJheVtsZW5ndGhdLFxuICAgICAgICBvdGhlckZ1bmMgPSBkYXRhLmZ1bmM7XG4gICAgaWYgKG90aGVyRnVuYyA9PSBudWxsIHx8IG90aGVyRnVuYyA9PSBmdW5jKSB7XG4gICAgICByZXR1cm4gZGF0YS5uYW1lO1xuICAgIH1cbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGdldEZ1bmNOYW1lO1xuIiwidmFyIGJhc2VQcm9wZXJ0eSA9IHJlcXVpcmUoJy4vYmFzZVByb3BlcnR5Jyk7XG5cbi8qKlxuICogR2V0cyB0aGUgXCJsZW5ndGhcIiBwcm9wZXJ0eSB2YWx1ZSBvZiBgb2JqZWN0YC5cbiAqXG4gKiAqKk5vdGU6KiogVGhpcyBmdW5jdGlvbiBpcyB1c2VkIHRvIGF2b2lkIGEgW0pJVCBidWddKGh0dHBzOi8vYnVncy53ZWJraXQub3JnL3Nob3dfYnVnLmNnaT9pZD0xNDI3OTIpXG4gKiB0aGF0IGFmZmVjdHMgU2FmYXJpIG9uIGF0IGxlYXN0IGlPUyA4LjEtOC4zIEFSTTY0LlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gcXVlcnkuXG4gKiBAcmV0dXJucyB7Kn0gUmV0dXJucyB0aGUgXCJsZW5ndGhcIiB2YWx1ZS5cbiAqL1xudmFyIGdldExlbmd0aCA9IGJhc2VQcm9wZXJ0eSgnbGVuZ3RoJyk7XG5cbm1vZHVsZS5leHBvcnRzID0gZ2V0TGVuZ3RoO1xuIiwidmFyIGlzU3RyaWN0Q29tcGFyYWJsZSA9IHJlcXVpcmUoJy4vaXNTdHJpY3RDb21wYXJhYmxlJyksXG4gICAgcGFpcnMgPSByZXF1aXJlKCcuLi9vYmplY3QvcGFpcnMnKTtcblxuLyoqXG4gKiBHZXRzIHRoZSBwcm9wZXJ5IG5hbWVzLCB2YWx1ZXMsIGFuZCBjb21wYXJlIGZsYWdzIG9mIGBvYmplY3RgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gcXVlcnkuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIG1hdGNoIGRhdGEgb2YgYG9iamVjdGAuXG4gKi9cbmZ1bmN0aW9uIGdldE1hdGNoRGF0YShvYmplY3QpIHtcbiAgdmFyIHJlc3VsdCA9IHBhaXJzKG9iamVjdCksXG4gICAgICBsZW5ndGggPSByZXN1bHQubGVuZ3RoO1xuXG4gIHdoaWxlIChsZW5ndGgtLSkge1xuICAgIHJlc3VsdFtsZW5ndGhdWzJdID0gaXNTdHJpY3RDb21wYXJhYmxlKHJlc3VsdFtsZW5ndGhdWzFdKTtcbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGdldE1hdGNoRGF0YTtcbiIsInZhciBpc05hdGl2ZSA9IHJlcXVpcmUoJy4uL2xhbmcvaXNOYXRpdmUnKTtcblxuLyoqXG4gKiBHZXRzIHRoZSBuYXRpdmUgZnVuY3Rpb24gYXQgYGtleWAgb2YgYG9iamVjdGAuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmplY3QgVGhlIG9iamVjdCB0byBxdWVyeS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBrZXkgVGhlIGtleSBvZiB0aGUgbWV0aG9kIHRvIGdldC5cbiAqIEByZXR1cm5zIHsqfSBSZXR1cm5zIHRoZSBmdW5jdGlvbiBpZiBpdCdzIG5hdGl2ZSwgZWxzZSBgdW5kZWZpbmVkYC5cbiAqL1xuZnVuY3Rpb24gZ2V0TmF0aXZlKG9iamVjdCwga2V5KSB7XG4gIHZhciB2YWx1ZSA9IG9iamVjdCA9PSBudWxsID8gdW5kZWZpbmVkIDogb2JqZWN0W2tleV07XG4gIHJldHVybiBpc05hdGl2ZSh2YWx1ZSkgPyB2YWx1ZSA6IHVuZGVmaW5lZDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBnZXROYXRpdmU7XG4iLCIvKipcbiAqIEdldHMgdGhlIGluZGV4IGF0IHdoaWNoIHRoZSBmaXJzdCBvY2N1cnJlbmNlIG9mIGBOYU5gIGlzIGZvdW5kIGluIGBhcnJheWAuXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl9IGFycmF5IFRoZSBhcnJheSB0byBzZWFyY2guXG4gKiBAcGFyYW0ge251bWJlcn0gZnJvbUluZGV4IFRoZSBpbmRleCB0byBzZWFyY2ggZnJvbS5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW2Zyb21SaWdodF0gU3BlY2lmeSBpdGVyYXRpbmcgZnJvbSByaWdodCB0byBsZWZ0LlxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgaW5kZXggb2YgdGhlIG1hdGNoZWQgYE5hTmAsIGVsc2UgYC0xYC5cbiAqL1xuZnVuY3Rpb24gaW5kZXhPZk5hTihhcnJheSwgZnJvbUluZGV4LCBmcm9tUmlnaHQpIHtcbiAgdmFyIGxlbmd0aCA9IGFycmF5Lmxlbmd0aCxcbiAgICAgIGluZGV4ID0gZnJvbUluZGV4ICsgKGZyb21SaWdodCA/IDAgOiAtMSk7XG5cbiAgd2hpbGUgKChmcm9tUmlnaHQgPyBpbmRleC0tIDogKytpbmRleCA8IGxlbmd0aCkpIHtcbiAgICB2YXIgb3RoZXIgPSBhcnJheVtpbmRleF07XG4gICAgaWYgKG90aGVyICE9PSBvdGhlcikge1xuICAgICAgcmV0dXJuIGluZGV4O1xuICAgIH1cbiAgfVxuICByZXR1cm4gLTE7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaW5kZXhPZk5hTjtcbiIsIi8qKiBVc2VkIGZvciBuYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgb2JqZWN0UHJvdG8gPSBPYmplY3QucHJvdG90eXBlO1xuXG4vKiogVXNlZCB0byBjaGVjayBvYmplY3RzIGZvciBvd24gcHJvcGVydGllcy4gKi9cbnZhciBoYXNPd25Qcm9wZXJ0eSA9IG9iamVjdFByb3RvLmhhc093blByb3BlcnR5O1xuXG4vKipcbiAqIEluaXRpYWxpemVzIGFuIGFycmF5IGNsb25lLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5fSBhcnJheSBUaGUgYXJyYXkgdG8gY2xvbmUuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIGluaXRpYWxpemVkIGNsb25lLlxuICovXG5mdW5jdGlvbiBpbml0Q2xvbmVBcnJheShhcnJheSkge1xuICB2YXIgbGVuZ3RoID0gYXJyYXkubGVuZ3RoLFxuICAgICAgcmVzdWx0ID0gbmV3IGFycmF5LmNvbnN0cnVjdG9yKGxlbmd0aCk7XG5cbiAgLy8gQWRkIGFycmF5IHByb3BlcnRpZXMgYXNzaWduZWQgYnkgYFJlZ0V4cCNleGVjYC5cbiAgaWYgKGxlbmd0aCAmJiB0eXBlb2YgYXJyYXlbMF0gPT0gJ3N0cmluZycgJiYgaGFzT3duUHJvcGVydHkuY2FsbChhcnJheSwgJ2luZGV4JykpIHtcbiAgICByZXN1bHQuaW5kZXggPSBhcnJheS5pbmRleDtcbiAgICByZXN1bHQuaW5wdXQgPSBhcnJheS5pbnB1dDtcbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGluaXRDbG9uZUFycmF5O1xuIiwiKGZ1bmN0aW9uIChnbG9iYWwpe1xudmFyIGJ1ZmZlckNsb25lID0gcmVxdWlyZSgnLi9idWZmZXJDbG9uZScpO1xuXG4vKiogYE9iamVjdCN0b1N0cmluZ2AgcmVzdWx0IHJlZmVyZW5jZXMuICovXG52YXIgYm9vbFRhZyA9ICdbb2JqZWN0IEJvb2xlYW5dJyxcbiAgICBkYXRlVGFnID0gJ1tvYmplY3QgRGF0ZV0nLFxuICAgIG51bWJlclRhZyA9ICdbb2JqZWN0IE51bWJlcl0nLFxuICAgIHJlZ2V4cFRhZyA9ICdbb2JqZWN0IFJlZ0V4cF0nLFxuICAgIHN0cmluZ1RhZyA9ICdbb2JqZWN0IFN0cmluZ10nO1xuXG52YXIgYXJyYXlCdWZmZXJUYWcgPSAnW29iamVjdCBBcnJheUJ1ZmZlcl0nLFxuICAgIGZsb2F0MzJUYWcgPSAnW29iamVjdCBGbG9hdDMyQXJyYXldJyxcbiAgICBmbG9hdDY0VGFnID0gJ1tvYmplY3QgRmxvYXQ2NEFycmF5XScsXG4gICAgaW50OFRhZyA9ICdbb2JqZWN0IEludDhBcnJheV0nLFxuICAgIGludDE2VGFnID0gJ1tvYmplY3QgSW50MTZBcnJheV0nLFxuICAgIGludDMyVGFnID0gJ1tvYmplY3QgSW50MzJBcnJheV0nLFxuICAgIHVpbnQ4VGFnID0gJ1tvYmplY3QgVWludDhBcnJheV0nLFxuICAgIHVpbnQ4Q2xhbXBlZFRhZyA9ICdbb2JqZWN0IFVpbnQ4Q2xhbXBlZEFycmF5XScsXG4gICAgdWludDE2VGFnID0gJ1tvYmplY3QgVWludDE2QXJyYXldJyxcbiAgICB1aW50MzJUYWcgPSAnW29iamVjdCBVaW50MzJBcnJheV0nO1xuXG4vKiogVXNlZCB0byBtYXRjaCBgUmVnRXhwYCBmbGFncyBmcm9tIHRoZWlyIGNvZXJjZWQgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUZsYWdzID0gL1xcdyokLztcblxuLyoqIE5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBVaW50OEFycmF5ID0gZ2xvYmFsLlVpbnQ4QXJyYXk7XG5cbi8qKiBVc2VkIHRvIGxvb2t1cCBhIHR5cGUgYXJyYXkgY29uc3RydWN0b3JzIGJ5IGB0b1N0cmluZ1RhZ2AuICovXG52YXIgY3RvckJ5VGFnID0ge307XG5jdG9yQnlUYWdbZmxvYXQzMlRhZ10gPSBnbG9iYWwuRmxvYXQzMkFycmF5O1xuY3RvckJ5VGFnW2Zsb2F0NjRUYWddID0gZ2xvYmFsLkZsb2F0NjRBcnJheTtcbmN0b3JCeVRhZ1tpbnQ4VGFnXSA9IGdsb2JhbC5JbnQ4QXJyYXk7XG5jdG9yQnlUYWdbaW50MTZUYWddID0gZ2xvYmFsLkludDE2QXJyYXk7XG5jdG9yQnlUYWdbaW50MzJUYWddID0gZ2xvYmFsLkludDMyQXJyYXk7XG5jdG9yQnlUYWdbdWludDhUYWddID0gVWludDhBcnJheTtcbmN0b3JCeVRhZ1t1aW50OENsYW1wZWRUYWddID0gZ2xvYmFsLlVpbnQ4Q2xhbXBlZEFycmF5O1xuY3RvckJ5VGFnW3VpbnQxNlRhZ10gPSBnbG9iYWwuVWludDE2QXJyYXk7XG5jdG9yQnlUYWdbdWludDMyVGFnXSA9IGdsb2JhbC5VaW50MzJBcnJheTtcblxuLyoqXG4gKiBJbml0aWFsaXplcyBhbiBvYmplY3QgY2xvbmUgYmFzZWQgb24gaXRzIGB0b1N0cmluZ1RhZ2AuXG4gKlxuICogKipOb3RlOioqIFRoaXMgZnVuY3Rpb24gb25seSBzdXBwb3J0cyBjbG9uaW5nIHZhbHVlcyB3aXRoIHRhZ3Mgb2ZcbiAqIGBCb29sZWFuYCwgYERhdGVgLCBgRXJyb3JgLCBgTnVtYmVyYCwgYFJlZ0V4cGAsIG9yIGBTdHJpbmdgLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gY2xvbmUuXG4gKiBAcGFyYW0ge3N0cmluZ30gdGFnIFRoZSBgdG9TdHJpbmdUYWdgIG9mIHRoZSBvYmplY3QgdG8gY2xvbmUuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtpc0RlZXBdIFNwZWNpZnkgYSBkZWVwIGNsb25lLlxuICogQHJldHVybnMge09iamVjdH0gUmV0dXJucyB0aGUgaW5pdGlhbGl6ZWQgY2xvbmUuXG4gKi9cbmZ1bmN0aW9uIGluaXRDbG9uZUJ5VGFnKG9iamVjdCwgdGFnLCBpc0RlZXApIHtcbiAgdmFyIEN0b3IgPSBvYmplY3QuY29uc3RydWN0b3I7XG4gIHN3aXRjaCAodGFnKSB7XG4gICAgY2FzZSBhcnJheUJ1ZmZlclRhZzpcbiAgICAgIHJldHVybiBidWZmZXJDbG9uZShvYmplY3QpO1xuXG4gICAgY2FzZSBib29sVGFnOlxuICAgIGNhc2UgZGF0ZVRhZzpcbiAgICAgIHJldHVybiBuZXcgQ3Rvcigrb2JqZWN0KTtcblxuICAgIGNhc2UgZmxvYXQzMlRhZzogY2FzZSBmbG9hdDY0VGFnOlxuICAgIGNhc2UgaW50OFRhZzogY2FzZSBpbnQxNlRhZzogY2FzZSBpbnQzMlRhZzpcbiAgICBjYXNlIHVpbnQ4VGFnOiBjYXNlIHVpbnQ4Q2xhbXBlZFRhZzogY2FzZSB1aW50MTZUYWc6IGNhc2UgdWludDMyVGFnOlxuICAgICAgLy8gU2FmYXJpIDUgbW9iaWxlIGluY29ycmVjdGx5IGhhcyBgT2JqZWN0YCBhcyB0aGUgY29uc3RydWN0b3Igb2YgdHlwZWQgYXJyYXlzLlxuICAgICAgaWYgKEN0b3IgaW5zdGFuY2VvZiBDdG9yKSB7XG4gICAgICAgIEN0b3IgPSBjdG9yQnlUYWdbdGFnXTtcbiAgICAgIH1cbiAgICAgIHZhciBidWZmZXIgPSBvYmplY3QuYnVmZmVyO1xuICAgICAgcmV0dXJuIG5ldyBDdG9yKGlzRGVlcCA/IGJ1ZmZlckNsb25lKGJ1ZmZlcikgOiBidWZmZXIsIG9iamVjdC5ieXRlT2Zmc2V0LCBvYmplY3QubGVuZ3RoKTtcblxuICAgIGNhc2UgbnVtYmVyVGFnOlxuICAgIGNhc2Ugc3RyaW5nVGFnOlxuICAgICAgcmV0dXJuIG5ldyBDdG9yKG9iamVjdCk7XG5cbiAgICBjYXNlIHJlZ2V4cFRhZzpcbiAgICAgIHZhciByZXN1bHQgPSBuZXcgQ3RvcihvYmplY3Quc291cmNlLCByZUZsYWdzLmV4ZWMob2JqZWN0KSk7XG4gICAgICByZXN1bHQubGFzdEluZGV4ID0gb2JqZWN0Lmxhc3RJbmRleDtcbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGluaXRDbG9uZUJ5VGFnO1xuXG59KS5jYWxsKHRoaXMsdHlwZW9mIGdsb2JhbCAhPT0gXCJ1bmRlZmluZWRcIiA/IGdsb2JhbCA6IHR5cGVvZiBzZWxmICE9PSBcInVuZGVmaW5lZFwiID8gc2VsZiA6IHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIgPyB3aW5kb3cgOiB7fSlcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPWRhdGE6YXBwbGljYXRpb24vanNvbjtjaGFyc2V0OnV0Zi04O2Jhc2U2NCxleUoyWlhKemFXOXVJam96TENKemIzVnlZMlZ6SWpwYkltNXZaR1ZmYlc5a2RXeGxjeTlzYjJSaGMyZ3RZMjl0Y0dGMEwybHVkR1Z5Ym1Gc0wybHVhWFJEYkc5dVpVSjVWR0ZuTG1weklsMHNJbTVoYldWeklqcGJYU3dpYldGd2NHbHVaM01pT2lJN1FVRkJRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQklpd2labWxzWlNJNkltZGxibVZ5WVhSbFpDNXFjeUlzSW5OdmRYSmpaVkp2YjNRaU9pSWlMQ0p6YjNWeVkyVnpRMjl1ZEdWdWRDSTZXeUoyWVhJZ1luVm1abVZ5UTJ4dmJtVWdQU0J5WlhGMWFYSmxLQ2N1TDJKMVptWmxja05zYjI1bEp5azdYRzVjYmk4cUtpQmdUMkpxWldOMEkzUnZVM1J5YVc1bllDQnlaWE4xYkhRZ2NtVm1aWEpsYm1ObGN5NGdLaTljYm5aaGNpQmliMjlzVkdGbklEMGdKMXR2WW1wbFkzUWdRbTl2YkdWaGJsMG5MRnh1SUNBZ0lHUmhkR1ZVWVdjZ1BTQW5XMjlpYW1WamRDQkVZWFJsWFNjc1hHNGdJQ0FnYm5WdFltVnlWR0ZuSUQwZ0oxdHZZbXBsWTNRZ1RuVnRZbVZ5WFNjc1hHNGdJQ0FnY21WblpYaHdWR0ZuSUQwZ0oxdHZZbXBsWTNRZ1VtVm5SWGh3WFNjc1hHNGdJQ0FnYzNSeWFXNW5WR0ZuSUQwZ0oxdHZZbXBsWTNRZ1UzUnlhVzVuWFNjN1hHNWNiblpoY2lCaGNuSmhlVUoxWm1abGNsUmhaeUE5SUNkYmIySnFaV04wSUVGeWNtRjVRblZtWm1WeVhTY3NYRzRnSUNBZ1pteHZZWFF6TWxSaFp5QTlJQ2RiYjJKcVpXTjBJRVpzYjJGME16SkJjbkpoZVYwbkxGeHVJQ0FnSUdac2IyRjBOalJVWVdjZ1BTQW5XMjlpYW1WamRDQkdiRzloZERZMFFYSnlZWGxkSnl4Y2JpQWdJQ0JwYm5RNFZHRm5JRDBnSjF0dlltcGxZM1FnU1c1ME9FRnljbUY1WFNjc1hHNGdJQ0FnYVc1ME1UWlVZV2NnUFNBblcyOWlhbVZqZENCSmJuUXhOa0Z5Y21GNVhTY3NYRzRnSUNBZ2FXNTBNekpVWVdjZ1BTQW5XMjlpYW1WamRDQkpiblF6TWtGeWNtRjVYU2NzWEc0Z0lDQWdkV2x1ZERoVVlXY2dQU0FuVzI5aWFtVmpkQ0JWYVc1ME9FRnljbUY1WFNjc1hHNGdJQ0FnZFdsdWREaERiR0Z0Y0dWa1ZHRm5JRDBnSjF0dlltcGxZM1FnVldsdWREaERiR0Z0Y0dWa1FYSnlZWGxkSnl4Y2JpQWdJQ0IxYVc1ME1UWlVZV2NnUFNBblcyOWlhbVZqZENCVmFXNTBNVFpCY25KaGVWMG5MRnh1SUNBZ0lIVnBiblF6TWxSaFp5QTlJQ2RiYjJKcVpXTjBJRlZwYm5Rek1rRnljbUY1WFNjN1hHNWNiaThxS2lCVmMyVmtJSFJ2SUcxaGRHTm9JR0JTWldkRmVIQmdJR1pzWVdkeklHWnliMjBnZEdobGFYSWdZMjlsY21ObFpDQnpkSEpwYm1jZ2RtRnNkV1Z6TGlBcUwxeHVkbUZ5SUhKbFJteGhaM01nUFNBdlhGeDNLaVF2TzF4dVhHNHZLaW9nVG1GMGFYWmxJRzFsZEdodlpDQnlaV1psY21WdVkyVnpMaUFxTDF4dWRtRnlJRlZwYm5RNFFYSnlZWGtnUFNCbmJHOWlZV3d1VldsdWREaEJjbkpoZVR0Y2JseHVMeW9xSUZWelpXUWdkRzhnYkc5dmEzVndJR0VnZEhsd1pTQmhjbkpoZVNCamIyNXpkSEoxWTNSdmNuTWdZbmtnWUhSdlUzUnlhVzVuVkdGbllDNGdLaTljYm5aaGNpQmpkRzl5UW5sVVlXY2dQU0I3ZlR0Y2JtTjBiM0pDZVZSaFoxdG1iRzloZERNeVZHRm5YU0E5SUdkc2IySmhiQzVHYkc5aGRETXlRWEp5WVhrN1hHNWpkRzl5UW5sVVlXZGJabXh2WVhRMk5GUmhaMTBnUFNCbmJHOWlZV3d1Um14dllYUTJORUZ5Y21GNU8xeHVZM1J2Y2tKNVZHRm5XMmx1ZERoVVlXZGRJRDBnWjJ4dlltRnNMa2x1ZERoQmNuSmhlVHRjYm1OMGIzSkNlVlJoWjF0cGJuUXhObFJoWjEwZ1BTQm5iRzlpWVd3dVNXNTBNVFpCY25KaGVUdGNibU4wYjNKQ2VWUmhaMXRwYm5Rek1sUmhaMTBnUFNCbmJHOWlZV3d1U1c1ME16SkJjbkpoZVR0Y2JtTjBiM0pDZVZSaFoxdDFhVzUwT0ZSaFoxMGdQU0JWYVc1ME9FRnljbUY1TzF4dVkzUnZja0o1VkdGblczVnBiblE0UTJ4aGJYQmxaRlJoWjEwZ1BTQm5iRzlpWVd3dVZXbHVkRGhEYkdGdGNHVmtRWEp5WVhrN1hHNWpkRzl5UW5sVVlXZGJkV2x1ZERFMlZHRm5YU0E5SUdkc2IySmhiQzVWYVc1ME1UWkJjbkpoZVR0Y2JtTjBiM0pDZVZSaFoxdDFhVzUwTXpKVVlXZGRJRDBnWjJ4dlltRnNMbFZwYm5Rek1rRnljbUY1TzF4dVhHNHZLaXBjYmlBcUlFbHVhWFJwWVd4cGVtVnpJR0Z1SUc5aWFtVmpkQ0JqYkc5dVpTQmlZWE5sWkNCdmJpQnBkSE1nWUhSdlUzUnlhVzVuVkdGbllDNWNiaUFxWEc0Z0tpQXFLazV2ZEdVNktpb2dWR2hwY3lCbWRXNWpkR2x2YmlCdmJteDVJSE4xY0hCdmNuUnpJR05zYjI1cGJtY2dkbUZzZFdWeklIZHBkR2dnZEdGbmN5QnZabHh1SUNvZ1lFSnZiMnhsWVc1Z0xDQmdSR0YwWldBc0lHQkZjbkp2Y21Bc0lHQk9kVzFpWlhKZ0xDQmdVbVZuUlhod1lDd2diM0lnWUZOMGNtbHVaMkF1WEc0Z0tseHVJQ29nUUhCeWFYWmhkR1ZjYmlBcUlFQndZWEpoYlNCN1QySnFaV04wZlNCdlltcGxZM1FnVkdobElHOWlhbVZqZENCMGJ5QmpiRzl1WlM1Y2JpQXFJRUJ3WVhKaGJTQjdjM1J5YVc1bmZTQjBZV2NnVkdobElHQjBiMU4wY21sdVoxUmhaMkFnYjJZZ2RHaGxJRzlpYW1WamRDQjBieUJqYkc5dVpTNWNiaUFxSUVCd1lYSmhiU0I3WW05dmJHVmhibjBnVzJselJHVmxjRjBnVTNCbFkybG1lU0JoSUdSbFpYQWdZMnh2Ym1VdVhHNGdLaUJBY21WMGRYSnVjeUI3VDJKcVpXTjBmU0JTWlhSMWNtNXpJSFJvWlNCcGJtbDBhV0ZzYVhwbFpDQmpiRzl1WlM1Y2JpQXFMMXh1Wm5WdVkzUnBiMjRnYVc1cGRFTnNiMjVsUW5sVVlXY29iMkpxWldOMExDQjBZV2NzSUdselJHVmxjQ2tnZTF4dUlDQjJZWElnUTNSdmNpQTlJRzlpYW1WamRDNWpiMjV6ZEhKMVkzUnZjanRjYmlBZ2MzZHBkR05vSUNoMFlXY3BJSHRjYmlBZ0lDQmpZWE5sSUdGeWNtRjVRblZtWm1WeVZHRm5PbHh1SUNBZ0lDQWdjbVYwZFhKdUlHSjFabVpsY2tOc2IyNWxLRzlpYW1WamRDazdYRzVjYmlBZ0lDQmpZWE5sSUdKdmIyeFVZV2M2WEc0Z0lDQWdZMkZ6WlNCa1lYUmxWR0ZuT2x4dUlDQWdJQ0FnY21WMGRYSnVJRzVsZHlCRGRHOXlLQ3R2WW1wbFkzUXBPMXh1WEc0Z0lDQWdZMkZ6WlNCbWJHOWhkRE15VkdGbk9pQmpZWE5sSUdac2IyRjBOalJVWVdjNlhHNGdJQ0FnWTJGelpTQnBiblE0VkdGbk9pQmpZWE5sSUdsdWRERTJWR0ZuT2lCallYTmxJR2x1ZERNeVZHRm5PbHh1SUNBZ0lHTmhjMlVnZFdsdWREaFVZV2M2SUdOaGMyVWdkV2x1ZERoRGJHRnRjR1ZrVkdGbk9pQmpZWE5sSUhWcGJuUXhObFJoWnpvZ1kyRnpaU0IxYVc1ME16SlVZV2M2WEc0Z0lDQWdJQ0F2THlCVFlXWmhjbWtnTlNCdGIySnBiR1VnYVc1amIzSnlaV04wYkhrZ2FHRnpJR0JQWW1wbFkzUmdJR0Z6SUhSb1pTQmpiMjV6ZEhKMVkzUnZjaUJ2WmlCMGVYQmxaQ0JoY25KaGVYTXVYRzRnSUNBZ0lDQnBaaUFvUTNSdmNpQnBibk4wWVc1alpXOW1JRU4wYjNJcElIdGNiaUFnSUNBZ0lDQWdRM1J2Y2lBOUlHTjBiM0pDZVZSaFoxdDBZV2RkTzF4dUlDQWdJQ0FnZlZ4dUlDQWdJQ0FnZG1GeUlHSjFabVpsY2lBOUlHOWlhbVZqZEM1aWRXWm1aWEk3WEc0Z0lDQWdJQ0J5WlhSMWNtNGdibVYzSUVOMGIzSW9hWE5FWldWd0lEOGdZblZtWm1WeVEyeHZibVVvWW5WbVptVnlLU0E2SUdKMVptWmxjaXdnYjJKcVpXTjBMbUo1ZEdWUFptWnpaWFFzSUc5aWFtVmpkQzVzWlc1bmRHZ3BPMXh1WEc0Z0lDQWdZMkZ6WlNCdWRXMWlaWEpVWVdjNlhHNGdJQ0FnWTJGelpTQnpkSEpwYm1kVVlXYzZYRzRnSUNBZ0lDQnlaWFIxY200Z2JtVjNJRU4wYjNJb2IySnFaV04wS1R0Y2JseHVJQ0FnSUdOaGMyVWdjbVZuWlhod1ZHRm5PbHh1SUNBZ0lDQWdkbUZ5SUhKbGMzVnNkQ0E5SUc1bGR5QkRkRzl5S0c5aWFtVmpkQzV6YjNWeVkyVXNJSEpsUm14aFozTXVaWGhsWXlodlltcGxZM1FwS1R0Y2JpQWdJQ0FnSUhKbGMzVnNkQzVzWVhOMFNXNWtaWGdnUFNCdlltcGxZM1F1YkdGemRFbHVaR1Y0TzF4dUlDQjlYRzRnSUhKbGRIVnliaUJ5WlhOMWJIUTdYRzU5WEc1Y2JtMXZaSFZzWlM1bGVIQnZjblJ6SUQwZ2FXNXBkRU5zYjI1bFFubFVZV2M3WEc0aVhYMD0iLCIvKipcbiAqIEluaXRpYWxpemVzIGFuIG9iamVjdCBjbG9uZS5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtPYmplY3R9IG9iamVjdCBUaGUgb2JqZWN0IHRvIGNsb25lLlxuICogQHJldHVybnMge09iamVjdH0gUmV0dXJucyB0aGUgaW5pdGlhbGl6ZWQgY2xvbmUuXG4gKi9cbmZ1bmN0aW9uIGluaXRDbG9uZU9iamVjdChvYmplY3QpIHtcbiAgdmFyIEN0b3IgPSBvYmplY3QuY29uc3RydWN0b3I7XG4gIGlmICghKHR5cGVvZiBDdG9yID09ICdmdW5jdGlvbicgJiYgQ3RvciBpbnN0YW5jZW9mIEN0b3IpKSB7XG4gICAgQ3RvciA9IE9iamVjdDtcbiAgfVxuICByZXR1cm4gbmV3IEN0b3I7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaW5pdENsb25lT2JqZWN0O1xuIiwidmFyIGdldExlbmd0aCA9IHJlcXVpcmUoJy4vZ2V0TGVuZ3RoJyksXG4gICAgaXNMZW5ndGggPSByZXF1aXJlKCcuL2lzTGVuZ3RoJyk7XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgYXJyYXktbGlrZS5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBhcnJheS1saWtlLCBlbHNlIGBmYWxzZWAuXG4gKi9cbmZ1bmN0aW9uIGlzQXJyYXlMaWtlKHZhbHVlKSB7XG4gIHJldHVybiB2YWx1ZSAhPSBudWxsICYmIGlzTGVuZ3RoKGdldExlbmd0aCh2YWx1ZSkpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGlzQXJyYXlMaWtlO1xuIiwiLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBhIGhvc3Qgb2JqZWN0IGluIElFIDwgOS5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBhIGhvc3Qgb2JqZWN0LCBlbHNlIGBmYWxzZWAuXG4gKi9cbnZhciBpc0hvc3RPYmplY3QgPSAoZnVuY3Rpb24oKSB7XG4gIHRyeSB7XG4gICAgT2JqZWN0KHsgJ3RvU3RyaW5nJzogMCB9ICsgJycpO1xuICB9IGNhdGNoKGUpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24oKSB7IHJldHVybiBmYWxzZTsgfTtcbiAgfVxuICByZXR1cm4gZnVuY3Rpb24odmFsdWUpIHtcbiAgICAvLyBJRSA8IDkgcHJlc2VudHMgbWFueSBob3N0IG9iamVjdHMgYXMgYE9iamVjdGAgb2JqZWN0cyB0aGF0IGNhbiBjb2VyY2VcbiAgICAvLyB0byBzdHJpbmdzIGRlc3BpdGUgaGF2aW5nIGltcHJvcGVybHkgZGVmaW5lZCBgdG9TdHJpbmdgIG1ldGhvZHMuXG4gICAgcmV0dXJuIHR5cGVvZiB2YWx1ZS50b1N0cmluZyAhPSAnZnVuY3Rpb24nICYmIHR5cGVvZiAodmFsdWUgKyAnJykgPT0gJ3N0cmluZyc7XG4gIH07XG59KCkpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IGlzSG9zdE9iamVjdDtcbiIsIi8qKiBVc2VkIHRvIGRldGVjdCB1bnNpZ25lZCBpbnRlZ2VyIHZhbHVlcy4gKi9cbnZhciByZUlzVWludCA9IC9eXFxkKyQvO1xuXG4vKipcbiAqIFVzZWQgYXMgdGhlIFttYXhpbXVtIGxlbmd0aF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNi4wLyNzZWMtbnVtYmVyLm1heF9zYWZlX2ludGVnZXIpXG4gKiBvZiBhbiBhcnJheS1saWtlIHZhbHVlLlxuICovXG52YXIgTUFYX1NBRkVfSU5URUdFUiA9IDkwMDcxOTkyNTQ3NDA5OTE7XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgYSB2YWxpZCBhcnJheS1saWtlIGluZGV4LlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEBwYXJhbSB7bnVtYmVyfSBbbGVuZ3RoPU1BWF9TQUZFX0lOVEVHRVJdIFRoZSB1cHBlciBib3VuZHMgb2YgYSB2YWxpZCBpbmRleC5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGEgdmFsaWQgaW5kZXgsIGVsc2UgYGZhbHNlYC5cbiAqL1xuZnVuY3Rpb24gaXNJbmRleCh2YWx1ZSwgbGVuZ3RoKSB7XG4gIHZhbHVlID0gKHR5cGVvZiB2YWx1ZSA9PSAnbnVtYmVyJyB8fCByZUlzVWludC50ZXN0KHZhbHVlKSkgPyArdmFsdWUgOiAtMTtcbiAgbGVuZ3RoID0gbGVuZ3RoID09IG51bGwgPyBNQVhfU0FGRV9JTlRFR0VSIDogbGVuZ3RoO1xuICByZXR1cm4gdmFsdWUgPiAtMSAmJiB2YWx1ZSAlIDEgPT0gMCAmJiB2YWx1ZSA8IGxlbmd0aDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBpc0luZGV4O1xuIiwidmFyIGlzQXJyYXlMaWtlID0gcmVxdWlyZSgnLi9pc0FycmF5TGlrZScpLFxuICAgIGlzSW5kZXggPSByZXF1aXJlKCcuL2lzSW5kZXgnKSxcbiAgICBpc09iamVjdCA9IHJlcXVpcmUoJy4uL2xhbmcvaXNPYmplY3QnKTtcblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIHByb3ZpZGVkIGFyZ3VtZW50cyBhcmUgZnJvbSBhbiBpdGVyYXRlZSBjYWxsLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSBwb3RlbnRpYWwgaXRlcmF0ZWUgdmFsdWUgYXJndW1lbnQuXG4gKiBAcGFyYW0geyp9IGluZGV4IFRoZSBwb3RlbnRpYWwgaXRlcmF0ZWUgaW5kZXggb3Iga2V5IGFyZ3VtZW50LlxuICogQHBhcmFtIHsqfSBvYmplY3QgVGhlIHBvdGVudGlhbCBpdGVyYXRlZSBvYmplY3QgYXJndW1lbnQuXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgdGhlIGFyZ3VtZW50cyBhcmUgZnJvbSBhbiBpdGVyYXRlZSBjYWxsLCBlbHNlIGBmYWxzZWAuXG4gKi9cbmZ1bmN0aW9uIGlzSXRlcmF0ZWVDYWxsKHZhbHVlLCBpbmRleCwgb2JqZWN0KSB7XG4gIGlmICghaXNPYmplY3Qob2JqZWN0KSkge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICB2YXIgdHlwZSA9IHR5cGVvZiBpbmRleDtcbiAgaWYgKHR5cGUgPT0gJ251bWJlcidcbiAgICAgID8gKGlzQXJyYXlMaWtlKG9iamVjdCkgJiYgaXNJbmRleChpbmRleCwgb2JqZWN0Lmxlbmd0aCkpXG4gICAgICA6ICh0eXBlID09ICdzdHJpbmcnICYmIGluZGV4IGluIG9iamVjdCkpIHtcbiAgICB2YXIgb3RoZXIgPSBvYmplY3RbaW5kZXhdO1xuICAgIHJldHVybiB2YWx1ZSA9PT0gdmFsdWUgPyAodmFsdWUgPT09IG90aGVyKSA6IChvdGhlciAhPT0gb3RoZXIpO1xuICB9XG4gIHJldHVybiBmYWxzZTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBpc0l0ZXJhdGVlQ2FsbDtcbiIsInZhciBpc0FycmF5ID0gcmVxdWlyZSgnLi4vbGFuZy9pc0FycmF5JyksXG4gICAgdG9PYmplY3QgPSByZXF1aXJlKCcuL3RvT2JqZWN0Jyk7XG5cbi8qKiBVc2VkIHRvIG1hdGNoIHByb3BlcnR5IG5hbWVzIHdpdGhpbiBwcm9wZXJ0eSBwYXRocy4gKi9cbnZhciByZUlzRGVlcFByb3AgPSAvXFwufFxcWyg/OlteW1xcXV0qfChbXCInXSkoPzooPyFcXDEpW15cXG5cXFxcXXxcXFxcLikqP1xcMSlcXF0vLFxuICAgIHJlSXNQbGFpblByb3AgPSAvXlxcdyokLztcblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBhIHByb3BlcnR5IG5hbWUgYW5kIG5vdCBhIHByb3BlcnR5IHBhdGguXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHBhcmFtIHtPYmplY3R9IFtvYmplY3RdIFRoZSBvYmplY3QgdG8gcXVlcnkga2V5cyBvbi5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGEgcHJvcGVydHkgbmFtZSwgZWxzZSBgZmFsc2VgLlxuICovXG5mdW5jdGlvbiBpc0tleSh2YWx1ZSwgb2JqZWN0KSB7XG4gIHZhciB0eXBlID0gdHlwZW9mIHZhbHVlO1xuICBpZiAoKHR5cGUgPT0gJ3N0cmluZycgJiYgcmVJc1BsYWluUHJvcC50ZXN0KHZhbHVlKSkgfHwgdHlwZSA9PSAnbnVtYmVyJykge1xuICAgIHJldHVybiB0cnVlO1xuICB9XG4gIGlmIChpc0FycmF5KHZhbHVlKSkge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICB2YXIgcmVzdWx0ID0gIXJlSXNEZWVwUHJvcC50ZXN0KHZhbHVlKTtcbiAgcmV0dXJuIHJlc3VsdCB8fCAob2JqZWN0ICE9IG51bGwgJiYgdmFsdWUgaW4gdG9PYmplY3Qob2JqZWN0KSk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaXNLZXk7XG4iLCJ2YXIgTGF6eVdyYXBwZXIgPSByZXF1aXJlKCcuL0xhenlXcmFwcGVyJyksXG4gICAgZ2V0RGF0YSA9IHJlcXVpcmUoJy4vZ2V0RGF0YScpLFxuICAgIGdldEZ1bmNOYW1lID0gcmVxdWlyZSgnLi9nZXRGdW5jTmFtZScpLFxuICAgIGxvZGFzaCA9IHJlcXVpcmUoJy4uL2NoYWluL2xvZGFzaCcpO1xuXG4vKipcbiAqIENoZWNrcyBpZiBgZnVuY2AgaGFzIGEgbGF6eSBjb3VudGVycGFydC5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYGZ1bmNgIGhhcyBhIGxhenkgY291bnRlcnBhcnQsIGVsc2UgYGZhbHNlYC5cbiAqL1xuZnVuY3Rpb24gaXNMYXppYWJsZShmdW5jKSB7XG4gIHZhciBmdW5jTmFtZSA9IGdldEZ1bmNOYW1lKGZ1bmMpLFxuICAgICAgb3RoZXIgPSBsb2Rhc2hbZnVuY05hbWVdO1xuXG4gIGlmICh0eXBlb2Ygb3RoZXIgIT0gJ2Z1bmN0aW9uJyB8fCAhKGZ1bmNOYW1lIGluIExhenlXcmFwcGVyLnByb3RvdHlwZSkpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgaWYgKGZ1bmMgPT09IG90aGVyKSB7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cbiAgdmFyIGRhdGEgPSBnZXREYXRhKG90aGVyKTtcbiAgcmV0dXJuICEhZGF0YSAmJiBmdW5jID09PSBkYXRhWzBdO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGlzTGF6aWFibGU7XG4iLCIvKipcbiAqIFVzZWQgYXMgdGhlIFttYXhpbXVtIGxlbmd0aF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNi4wLyNzZWMtbnVtYmVyLm1heF9zYWZlX2ludGVnZXIpXG4gKiBvZiBhbiBhcnJheS1saWtlIHZhbHVlLlxuICovXG52YXIgTUFYX1NBRkVfSU5URUdFUiA9IDkwMDcxOTkyNTQ3NDA5OTE7XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgYSB2YWxpZCBhcnJheS1saWtlIGxlbmd0aC5cbiAqXG4gKiAqKk5vdGU6KiogVGhpcyBmdW5jdGlvbiBpcyBiYXNlZCBvbiBbYFRvTGVuZ3RoYF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNi4wLyNzZWMtdG9sZW5ndGgpLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGEgdmFsaWQgbGVuZ3RoLCBlbHNlIGBmYWxzZWAuXG4gKi9cbmZ1bmN0aW9uIGlzTGVuZ3RoKHZhbHVlKSB7XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT0gJ251bWJlcicgJiYgdmFsdWUgPiAtMSAmJiB2YWx1ZSAlIDEgPT0gMCAmJiB2YWx1ZSA8PSBNQVhfU0FGRV9JTlRFR0VSO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGlzTGVuZ3RoO1xuIiwiLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZS5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZSwgZWxzZSBgZmFsc2VgLlxuICovXG5mdW5jdGlvbiBpc09iamVjdExpa2UodmFsdWUpIHtcbiAgcmV0dXJuICEhdmFsdWUgJiYgdHlwZW9mIHZhbHVlID09ICdvYmplY3QnO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGlzT2JqZWN0TGlrZTtcbiIsInZhciBpc09iamVjdCA9IHJlcXVpcmUoJy4uL2xhbmcvaXNPYmplY3QnKTtcblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBzdWl0YWJsZSBmb3Igc3RyaWN0IGVxdWFsaXR5IGNvbXBhcmlzb25zLCBpLmUuIGA9PT1gLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlmIHN1aXRhYmxlIGZvciBzdHJpY3RcbiAqICBlcXVhbGl0eSBjb21wYXJpc29ucywgZWxzZSBgZmFsc2VgLlxuICovXG5mdW5jdGlvbiBpc1N0cmljdENvbXBhcmFibGUodmFsdWUpIHtcbiAgcmV0dXJuIHZhbHVlID09PSB2YWx1ZSAmJiAhaXNPYmplY3QodmFsdWUpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGlzU3RyaWN0Q29tcGFyYWJsZTtcbiIsInZhciBhcnJheUNvcHkgPSByZXF1aXJlKCcuL2FycmF5Q29weScpLFxuICAgIGNvbXBvc2VBcmdzID0gcmVxdWlyZSgnLi9jb21wb3NlQXJncycpLFxuICAgIGNvbXBvc2VBcmdzUmlnaHQgPSByZXF1aXJlKCcuL2NvbXBvc2VBcmdzUmlnaHQnKSxcbiAgICByZXBsYWNlSG9sZGVycyA9IHJlcXVpcmUoJy4vcmVwbGFjZUhvbGRlcnMnKTtcblxuLyoqIFVzZWQgdG8gY29tcG9zZSBiaXRtYXNrcyBmb3Igd3JhcHBlciBtZXRhZGF0YS4gKi9cbnZhciBCSU5EX0ZMQUcgPSAxLFxuICAgIENVUlJZX0JPVU5EX0ZMQUcgPSA0LFxuICAgIENVUlJZX0ZMQUcgPSA4LFxuICAgIEFSWV9GTEFHID0gMTI4LFxuICAgIFJFQVJHX0ZMQUcgPSAyNTY7XG5cbi8qKiBVc2VkIGFzIHRoZSBpbnRlcm5hbCBhcmd1bWVudCBwbGFjZWhvbGRlci4gKi9cbnZhciBQTEFDRUhPTERFUiA9ICdfX2xvZGFzaF9wbGFjZWhvbGRlcl9fJztcblxuLyogTmF0aXZlIG1ldGhvZCByZWZlcmVuY2VzIGZvciB0aG9zZSB3aXRoIHRoZSBzYW1lIG5hbWUgYXMgb3RoZXIgYGxvZGFzaGAgbWV0aG9kcy4gKi9cbnZhciBuYXRpdmVNaW4gPSBNYXRoLm1pbjtcblxuLyoqXG4gKiBNZXJnZXMgdGhlIGZ1bmN0aW9uIG1ldGFkYXRhIG9mIGBzb3VyY2VgIGludG8gYGRhdGFgLlxuICpcbiAqIE1lcmdpbmcgbWV0YWRhdGEgcmVkdWNlcyB0aGUgbnVtYmVyIG9mIHdyYXBwZXJzIHJlcXVpcmVkIHRvIGludm9rZSBhIGZ1bmN0aW9uLlxuICogVGhpcyBpcyBwb3NzaWJsZSBiZWNhdXNlIG1ldGhvZHMgbGlrZSBgXy5iaW5kYCwgYF8uY3VycnlgLCBhbmQgYF8ucGFydGlhbGBcbiAqIG1heSBiZSBhcHBsaWVkIHJlZ2FyZGxlc3Mgb2YgZXhlY3V0aW9uIG9yZGVyLiBNZXRob2RzIGxpa2UgYF8uYXJ5YCBhbmQgYF8ucmVhcmdgXG4gKiBhdWdtZW50IGZ1bmN0aW9uIGFyZ3VtZW50cywgbWFraW5nIHRoZSBvcmRlciBpbiB3aGljaCB0aGV5IGFyZSBleGVjdXRlZCBpbXBvcnRhbnQsXG4gKiBwcmV2ZW50aW5nIHRoZSBtZXJnaW5nIG9mIG1ldGFkYXRhLiBIb3dldmVyLCB3ZSBtYWtlIGFuIGV4Y2VwdGlvbiBmb3IgYSBzYWZlXG4gKiBjb21tb24gY2FzZSB3aGVyZSBjdXJyaWVkIGZ1bmN0aW9ucyBoYXZlIGBfLmFyeWAgYW5kIG9yIGBfLnJlYXJnYCBhcHBsaWVkLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0FycmF5fSBkYXRhIFRoZSBkZXN0aW5hdGlvbiBtZXRhZGF0YS5cbiAqIEBwYXJhbSB7QXJyYXl9IHNvdXJjZSBUaGUgc291cmNlIG1ldGFkYXRhLlxuICogQHJldHVybnMge0FycmF5fSBSZXR1cm5zIGBkYXRhYC5cbiAqL1xuZnVuY3Rpb24gbWVyZ2VEYXRhKGRhdGEsIHNvdXJjZSkge1xuICB2YXIgYml0bWFzayA9IGRhdGFbMV0sXG4gICAgICBzcmNCaXRtYXNrID0gc291cmNlWzFdLFxuICAgICAgbmV3Qml0bWFzayA9IGJpdG1hc2sgfCBzcmNCaXRtYXNrLFxuICAgICAgaXNDb21tb24gPSBuZXdCaXRtYXNrIDwgQVJZX0ZMQUc7XG5cbiAgdmFyIGlzQ29tYm8gPVxuICAgIChzcmNCaXRtYXNrID09IEFSWV9GTEFHICYmIGJpdG1hc2sgPT0gQ1VSUllfRkxBRykgfHxcbiAgICAoc3JjQml0bWFzayA9PSBBUllfRkxBRyAmJiBiaXRtYXNrID09IFJFQVJHX0ZMQUcgJiYgZGF0YVs3XS5sZW5ndGggPD0gc291cmNlWzhdKSB8fFxuICAgIChzcmNCaXRtYXNrID09IChBUllfRkxBRyB8IFJFQVJHX0ZMQUcpICYmIGJpdG1hc2sgPT0gQ1VSUllfRkxBRyk7XG5cbiAgLy8gRXhpdCBlYXJseSBpZiBtZXRhZGF0YSBjYW4ndCBiZSBtZXJnZWQuXG4gIGlmICghKGlzQ29tbW9uIHx8IGlzQ29tYm8pKSB7XG4gICAgcmV0dXJuIGRhdGE7XG4gIH1cbiAgLy8gVXNlIHNvdXJjZSBgdGhpc0FyZ2AgaWYgYXZhaWxhYmxlLlxuICBpZiAoc3JjQml0bWFzayAmIEJJTkRfRkxBRykge1xuICAgIGRhdGFbMl0gPSBzb3VyY2VbMl07XG4gICAgLy8gU2V0IHdoZW4gY3VycnlpbmcgYSBib3VuZCBmdW5jdGlvbi5cbiAgICBuZXdCaXRtYXNrIHw9IChiaXRtYXNrICYgQklORF9GTEFHKSA/IDAgOiBDVVJSWV9CT1VORF9GTEFHO1xuICB9XG4gIC8vIENvbXBvc2UgcGFydGlhbCBhcmd1bWVudHMuXG4gIHZhciB2YWx1ZSA9IHNvdXJjZVszXTtcbiAgaWYgKHZhbHVlKSB7XG4gICAgdmFyIHBhcnRpYWxzID0gZGF0YVszXTtcbiAgICBkYXRhWzNdID0gcGFydGlhbHMgPyBjb21wb3NlQXJncyhwYXJ0aWFscywgdmFsdWUsIHNvdXJjZVs0XSkgOiBhcnJheUNvcHkodmFsdWUpO1xuICAgIGRhdGFbNF0gPSBwYXJ0aWFscyA/IHJlcGxhY2VIb2xkZXJzKGRhdGFbM10sIFBMQUNFSE9MREVSKSA6IGFycmF5Q29weShzb3VyY2VbNF0pO1xuICB9XG4gIC8vIENvbXBvc2UgcGFydGlhbCByaWdodCBhcmd1bWVudHMuXG4gIHZhbHVlID0gc291cmNlWzVdO1xuICBpZiAodmFsdWUpIHtcbiAgICBwYXJ0aWFscyA9IGRhdGFbNV07XG4gICAgZGF0YVs1XSA9IHBhcnRpYWxzID8gY29tcG9zZUFyZ3NSaWdodChwYXJ0aWFscywgdmFsdWUsIHNvdXJjZVs2XSkgOiBhcnJheUNvcHkodmFsdWUpO1xuICAgIGRhdGFbNl0gPSBwYXJ0aWFscyA/IHJlcGxhY2VIb2xkZXJzKGRhdGFbNV0sIFBMQUNFSE9MREVSKSA6IGFycmF5Q29weShzb3VyY2VbNl0pO1xuICB9XG4gIC8vIFVzZSBzb3VyY2UgYGFyZ1Bvc2AgaWYgYXZhaWxhYmxlLlxuICB2YWx1ZSA9IHNvdXJjZVs3XTtcbiAgaWYgKHZhbHVlKSB7XG4gICAgZGF0YVs3XSA9IGFycmF5Q29weSh2YWx1ZSk7XG4gIH1cbiAgLy8gVXNlIHNvdXJjZSBgYXJ5YCBpZiBpdCdzIHNtYWxsZXIuXG4gIGlmIChzcmNCaXRtYXNrICYgQVJZX0ZMQUcpIHtcbiAgICBkYXRhWzhdID0gZGF0YVs4XSA9PSBudWxsID8gc291cmNlWzhdIDogbmF0aXZlTWluKGRhdGFbOF0sIHNvdXJjZVs4XSk7XG4gIH1cbiAgLy8gVXNlIHNvdXJjZSBgYXJpdHlgIGlmIG9uZSBpcyBub3QgcHJvdmlkZWQuXG4gIGlmIChkYXRhWzldID09IG51bGwpIHtcbiAgICBkYXRhWzldID0gc291cmNlWzldO1xuICB9XG4gIC8vIFVzZSBzb3VyY2UgYGZ1bmNgIGFuZCBtZXJnZSBiaXRtYXNrcy5cbiAgZGF0YVswXSA9IHNvdXJjZVswXTtcbiAgZGF0YVsxXSA9IG5ld0JpdG1hc2s7XG5cbiAgcmV0dXJuIGRhdGE7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gbWVyZ2VEYXRhO1xuIiwiKGZ1bmN0aW9uIChnbG9iYWwpe1xudmFyIGdldE5hdGl2ZSA9IHJlcXVpcmUoJy4vZ2V0TmF0aXZlJyk7XG5cbi8qKiBOYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgV2Vha01hcCA9IGdldE5hdGl2ZShnbG9iYWwsICdXZWFrTWFwJyk7XG5cbi8qKiBVc2VkIHRvIHN0b3JlIGZ1bmN0aW9uIG1ldGFkYXRhLiAqL1xudmFyIG1ldGFNYXAgPSBXZWFrTWFwICYmIG5ldyBXZWFrTWFwO1xuXG5tb2R1bGUuZXhwb3J0cyA9IG1ldGFNYXA7XG5cbn0pLmNhbGwodGhpcyx0eXBlb2YgZ2xvYmFsICE9PSBcInVuZGVmaW5lZFwiID8gZ2xvYmFsIDogdHlwZW9mIHNlbGYgIT09IFwidW5kZWZpbmVkXCIgPyBzZWxmIDogdHlwZW9mIHdpbmRvdyAhPT0gXCJ1bmRlZmluZWRcIiA/IHdpbmRvdyA6IHt9KVxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9ZGF0YTphcHBsaWNhdGlvbi9qc29uO2NoYXJzZXQ6dXRmLTg7YmFzZTY0LGV5SjJaWEp6YVc5dUlqb3pMQ0p6YjNWeVkyVnpJanBiSW01dlpHVmZiVzlrZFd4bGN5OXNiMlJoYzJndFkyOXRjR0YwTDJsdWRHVnlibUZzTDIxbGRHRk5ZWEF1YW5NaVhTd2libUZ0WlhNaU9sdGRMQ0p0WVhCd2FXNW5jeUk2SWp0QlFVRkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJJaXdpWm1sc1pTSTZJbWRsYm1WeVlYUmxaQzVxY3lJc0luTnZkWEpqWlZKdmIzUWlPaUlpTENKemIzVnlZMlZ6UTI5dWRHVnVkQ0k2V3lKMllYSWdaMlYwVG1GMGFYWmxJRDBnY21WeGRXbHlaU2duTGk5blpYUk9ZWFJwZG1VbktUdGNibHh1THlvcUlFNWhkR2wyWlNCdFpYUm9iMlFnY21WbVpYSmxibU5sY3k0Z0tpOWNiblpoY2lCWFpXRnJUV0Z3SUQwZ1oyVjBUbUYwYVhabEtHZHNiMkpoYkN3Z0oxZGxZV3ROWVhBbktUdGNibHh1THlvcUlGVnpaV1FnZEc4Z2MzUnZjbVVnWm5WdVkzUnBiMjRnYldWMFlXUmhkR0V1SUNvdlhHNTJZWElnYldWMFlVMWhjQ0E5SUZkbFlXdE5ZWEFnSmlZZ2JtVjNJRmRsWVd0TllYQTdYRzVjYm0xdlpIVnNaUzVsZUhCdmNuUnpJRDBnYldWMFlVMWhjRHRjYmlKZGZRPT0iLCIvKiogVXNlZCB0byBsb29rdXAgdW5taW5pZmllZCBmdW5jdGlvbiBuYW1lcy4gKi9cbnZhciByZWFsTmFtZXMgPSB7fTtcblxubW9kdWxlLmV4cG9ydHMgPSByZWFsTmFtZXM7XG4iLCJ2YXIgYXJyYXlDb3B5ID0gcmVxdWlyZSgnLi9hcnJheUNvcHknKSxcbiAgICBpc0luZGV4ID0gcmVxdWlyZSgnLi9pc0luZGV4Jyk7XG5cbi8qIE5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWluID0gTWF0aC5taW47XG5cbi8qKlxuICogUmVvcmRlciBgYXJyYXlgIGFjY29yZGluZyB0byB0aGUgc3BlY2lmaWVkIGluZGV4ZXMgd2hlcmUgdGhlIGVsZW1lbnQgYXRcbiAqIHRoZSBmaXJzdCBpbmRleCBpcyBhc3NpZ25lZCBhcyB0aGUgZmlyc3QgZWxlbWVudCwgdGhlIGVsZW1lbnQgYXRcbiAqIHRoZSBzZWNvbmQgaW5kZXggaXMgYXNzaWduZWQgYXMgdGhlIHNlY29uZCBlbGVtZW50LCBhbmQgc28gb24uXG4gKlxuICogQHByaXZhdGVcbiAqIEBwYXJhbSB7QXJyYXl9IGFycmF5IFRoZSBhcnJheSB0byByZW9yZGVyLlxuICogQHBhcmFtIHtBcnJheX0gaW5kZXhlcyBUaGUgYXJyYW5nZWQgYXJyYXkgaW5kZXhlcy5cbiAqIEByZXR1cm5zIHtBcnJheX0gUmV0dXJucyBgYXJyYXlgLlxuICovXG5mdW5jdGlvbiByZW9yZGVyKGFycmF5LCBpbmRleGVzKSB7XG4gIHZhciBhcnJMZW5ndGggPSBhcnJheS5sZW5ndGgsXG4gICAgICBsZW5ndGggPSBuYXRpdmVNaW4oaW5kZXhlcy5sZW5ndGgsIGFyckxlbmd0aCksXG4gICAgICBvbGRBcnJheSA9IGFycmF5Q29weShhcnJheSk7XG5cbiAgd2hpbGUgKGxlbmd0aC0tKSB7XG4gICAgdmFyIGluZGV4ID0gaW5kZXhlc1tsZW5ndGhdO1xuICAgIGFycmF5W2xlbmd0aF0gPSBpc0luZGV4KGluZGV4LCBhcnJMZW5ndGgpID8gb2xkQXJyYXlbaW5kZXhdIDogdW5kZWZpbmVkO1xuICB9XG4gIHJldHVybiBhcnJheTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSByZW9yZGVyO1xuIiwiLyoqIFVzZWQgYXMgdGhlIGludGVybmFsIGFyZ3VtZW50IHBsYWNlaG9sZGVyLiAqL1xudmFyIFBMQUNFSE9MREVSID0gJ19fbG9kYXNoX3BsYWNlaG9sZGVyX18nO1xuXG4vKipcbiAqIFJlcGxhY2VzIGFsbCBgcGxhY2Vob2xkZXJgIGVsZW1lbnRzIGluIGBhcnJheWAgd2l0aCBhbiBpbnRlcm5hbCBwbGFjZWhvbGRlclxuICogYW5kIHJldHVybnMgYW4gYXJyYXkgb2YgdGhlaXIgaW5kZXhlcy5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtBcnJheX0gYXJyYXkgVGhlIGFycmF5IHRvIG1vZGlmeS5cbiAqIEBwYXJhbSB7Kn0gcGxhY2Vob2xkZXIgVGhlIHBsYWNlaG9sZGVyIHRvIHJlcGxhY2UuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIG5ldyBhcnJheSBvZiBwbGFjZWhvbGRlciBpbmRleGVzLlxuICovXG5mdW5jdGlvbiByZXBsYWNlSG9sZGVycyhhcnJheSwgcGxhY2Vob2xkZXIpIHtcbiAgdmFyIGluZGV4ID0gLTEsXG4gICAgICBsZW5ndGggPSBhcnJheS5sZW5ndGgsXG4gICAgICByZXNJbmRleCA9IC0xLFxuICAgICAgcmVzdWx0ID0gW107XG5cbiAgd2hpbGUgKCsraW5kZXggPCBsZW5ndGgpIHtcbiAgICBpZiAoYXJyYXlbaW5kZXhdID09PSBwbGFjZWhvbGRlcikge1xuICAgICAgYXJyYXlbaW5kZXhdID0gUExBQ0VIT0xERVI7XG4gICAgICByZXN1bHRbKytyZXNJbmRleF0gPSBpbmRleDtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSByZXBsYWNlSG9sZGVycztcbiIsInZhciBiYXNlU2V0RGF0YSA9IHJlcXVpcmUoJy4vYmFzZVNldERhdGEnKSxcbiAgICBub3cgPSByZXF1aXJlKCcuLi9kYXRlL25vdycpO1xuXG4vKiogVXNlZCB0byBkZXRlY3Qgd2hlbiBhIGZ1bmN0aW9uIGJlY29tZXMgaG90LiAqL1xudmFyIEhPVF9DT1VOVCA9IDE1MCxcbiAgICBIT1RfU1BBTiA9IDE2O1xuXG4vKipcbiAqIFNldHMgbWV0YWRhdGEgZm9yIGBmdW5jYC5cbiAqXG4gKiAqKk5vdGU6KiogSWYgdGhpcyBmdW5jdGlvbiBiZWNvbWVzIGhvdCwgaS5lLiBpcyBpbnZva2VkIGEgbG90IGluIGEgc2hvcnRcbiAqIHBlcmlvZCBvZiB0aW1lLCBpdCB3aWxsIHRyaXAgaXRzIGJyZWFrZXIgYW5kIHRyYW5zaXRpb24gdG8gYW4gaWRlbnRpdHkgZnVuY3Rpb25cbiAqIHRvIGF2b2lkIGdhcmJhZ2UgY29sbGVjdGlvbiBwYXVzZXMgaW4gVjguIFNlZSBbVjggaXNzdWUgMjA3MF0oaHR0cHM6Ly9jb2RlLmdvb2dsZS5jb20vcC92OC9pc3N1ZXMvZGV0YWlsP2lkPTIwNzApXG4gKiBmb3IgbW9yZSBkZXRhaWxzLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmdW5jIFRoZSBmdW5jdGlvbiB0byBhc3NvY2lhdGUgbWV0YWRhdGEgd2l0aC5cbiAqIEBwYXJhbSB7Kn0gZGF0YSBUaGUgbWV0YWRhdGEuXG4gKiBAcmV0dXJucyB7RnVuY3Rpb259IFJldHVybnMgYGZ1bmNgLlxuICovXG52YXIgc2V0RGF0YSA9IChmdW5jdGlvbigpIHtcbiAgdmFyIGNvdW50ID0gMCxcbiAgICAgIGxhc3RDYWxsZWQgPSAwO1xuXG4gIHJldHVybiBmdW5jdGlvbihrZXksIHZhbHVlKSB7XG4gICAgdmFyIHN0YW1wID0gbm93KCksXG4gICAgICAgIHJlbWFpbmluZyA9IEhPVF9TUEFOIC0gKHN0YW1wIC0gbGFzdENhbGxlZCk7XG5cbiAgICBsYXN0Q2FsbGVkID0gc3RhbXA7XG4gICAgaWYgKHJlbWFpbmluZyA+IDApIHtcbiAgICAgIGlmICgrK2NvdW50ID49IEhPVF9DT1VOVCkge1xuICAgICAgICByZXR1cm4ga2V5O1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBjb3VudCA9IDA7XG4gICAgfVxuICAgIHJldHVybiBiYXNlU2V0RGF0YShrZXksIHZhbHVlKTtcbiAgfTtcbn0oKSk7XG5cbm1vZHVsZS5leHBvcnRzID0gc2V0RGF0YTtcbiIsInZhciBpc0FyZ3VtZW50cyA9IHJlcXVpcmUoJy4uL2xhbmcvaXNBcmd1bWVudHMnKSxcbiAgICBpc0FycmF5ID0gcmVxdWlyZSgnLi4vbGFuZy9pc0FycmF5JyksXG4gICAgaXNJbmRleCA9IHJlcXVpcmUoJy4vaXNJbmRleCcpLFxuICAgIGlzTGVuZ3RoID0gcmVxdWlyZSgnLi9pc0xlbmd0aCcpLFxuICAgIGlzU3RyaW5nID0gcmVxdWlyZSgnLi4vbGFuZy9pc1N0cmluZycpLFxuICAgIGtleXNJbiA9IHJlcXVpcmUoJy4uL29iamVjdC9rZXlzSW4nKTtcblxuLyoqIFVzZWQgZm9yIG5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKiBVc2VkIHRvIGNoZWNrIG9iamVjdHMgZm9yIG93biBwcm9wZXJ0aWVzLiAqL1xudmFyIGhhc093blByb3BlcnR5ID0gb2JqZWN0UHJvdG8uaGFzT3duUHJvcGVydHk7XG5cbi8qKlxuICogQSBmYWxsYmFjayBpbXBsZW1lbnRhdGlvbiBvZiBgT2JqZWN0LmtleXNgIHdoaWNoIGNyZWF0ZXMgYW4gYXJyYXkgb2YgdGhlXG4gKiBvd24gZW51bWVyYWJsZSBwcm9wZXJ0eSBuYW1lcyBvZiBgb2JqZWN0YC5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtPYmplY3R9IG9iamVjdCBUaGUgb2JqZWN0IHRvIHF1ZXJ5LlxuICogQHJldHVybnMge0FycmF5fSBSZXR1cm5zIHRoZSBhcnJheSBvZiBwcm9wZXJ0eSBuYW1lcy5cbiAqL1xuZnVuY3Rpb24gc2hpbUtleXMob2JqZWN0KSB7XG4gIHZhciBwcm9wcyA9IGtleXNJbihvYmplY3QpLFxuICAgICAgcHJvcHNMZW5ndGggPSBwcm9wcy5sZW5ndGgsXG4gICAgICBsZW5ndGggPSBwcm9wc0xlbmd0aCAmJiBvYmplY3QubGVuZ3RoO1xuXG4gIHZhciBhbGxvd0luZGV4ZXMgPSAhIWxlbmd0aCAmJiBpc0xlbmd0aChsZW5ndGgpICYmXG4gICAgKGlzQXJyYXkob2JqZWN0KSB8fCBpc0FyZ3VtZW50cyhvYmplY3QpIHx8IGlzU3RyaW5nKG9iamVjdCkpO1xuXG4gIHZhciBpbmRleCA9IC0xLFxuICAgICAgcmVzdWx0ID0gW107XG5cbiAgd2hpbGUgKCsraW5kZXggPCBwcm9wc0xlbmd0aCkge1xuICAgIHZhciBrZXkgPSBwcm9wc1tpbmRleF07XG4gICAgaWYgKChhbGxvd0luZGV4ZXMgJiYgaXNJbmRleChrZXksIGxlbmd0aCkpIHx8IGhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBrZXkpKSB7XG4gICAgICByZXN1bHQucHVzaChrZXkpO1xuICAgIH1cbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHNoaW1LZXlzO1xuIiwidmFyIGlzT2JqZWN0ID0gcmVxdWlyZSgnLi4vbGFuZy9pc09iamVjdCcpLFxuICAgIGlzU3RyaW5nID0gcmVxdWlyZSgnLi4vbGFuZy9pc1N0cmluZycpLFxuICAgIHN1cHBvcnQgPSByZXF1aXJlKCcuLi9zdXBwb3J0Jyk7XG5cbi8qKlxuICogQ29udmVydHMgYHZhbHVlYCB0byBhbiBvYmplY3QgaWYgaXQncyBub3Qgb25lLlxuICpcbiAqIEBwcml2YXRlXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBwcm9jZXNzLlxuICogQHJldHVybnMge09iamVjdH0gUmV0dXJucyB0aGUgb2JqZWN0LlxuICovXG5mdW5jdGlvbiB0b09iamVjdCh2YWx1ZSkge1xuICBpZiAoc3VwcG9ydC51bmluZGV4ZWRDaGFycyAmJiBpc1N0cmluZyh2YWx1ZSkpIHtcbiAgICB2YXIgaW5kZXggPSAtMSxcbiAgICAgICAgbGVuZ3RoID0gdmFsdWUubGVuZ3RoLFxuICAgICAgICByZXN1bHQgPSBPYmplY3QodmFsdWUpO1xuXG4gICAgd2hpbGUgKCsraW5kZXggPCBsZW5ndGgpIHtcbiAgICAgIHJlc3VsdFtpbmRleF0gPSB2YWx1ZS5jaGFyQXQoaW5kZXgpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIHJldHVybiBpc09iamVjdCh2YWx1ZSkgPyB2YWx1ZSA6IE9iamVjdCh2YWx1ZSk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gdG9PYmplY3Q7XG4iLCJ2YXIgYmFzZVRvU3RyaW5nID0gcmVxdWlyZSgnLi9iYXNlVG9TdHJpbmcnKSxcbiAgICBpc0FycmF5ID0gcmVxdWlyZSgnLi4vbGFuZy9pc0FycmF5Jyk7XG5cbi8qKiBVc2VkIHRvIG1hdGNoIHByb3BlcnR5IG5hbWVzIHdpdGhpbiBwcm9wZXJ0eSBwYXRocy4gKi9cbnZhciByZVByb3BOYW1lID0gL1teLltcXF1dK3xcXFsoPzooLT9cXGQrKD86XFwuXFxkKyk/KXwoW1wiJ10pKCg/Oig/IVxcMilbXlxcblxcXFxdfFxcXFwuKSo/KVxcMilcXF0vZztcblxuLyoqIFVzZWQgdG8gbWF0Y2ggYmFja3NsYXNoZXMgaW4gcHJvcGVydHkgcGF0aHMuICovXG52YXIgcmVFc2NhcGVDaGFyID0gL1xcXFwoXFxcXCk/L2c7XG5cbi8qKlxuICogQ29udmVydHMgYHZhbHVlYCB0byBwcm9wZXJ0eSBwYXRoIGFycmF5IGlmIGl0J3Mgbm90IG9uZS5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gcHJvY2Vzcy5cbiAqIEByZXR1cm5zIHtBcnJheX0gUmV0dXJucyB0aGUgcHJvcGVydHkgcGF0aCBhcnJheS5cbiAqL1xuZnVuY3Rpb24gdG9QYXRoKHZhbHVlKSB7XG4gIGlmIChpc0FycmF5KHZhbHVlKSkge1xuICAgIHJldHVybiB2YWx1ZTtcbiAgfVxuICB2YXIgcmVzdWx0ID0gW107XG4gIGJhc2VUb1N0cmluZyh2YWx1ZSkucmVwbGFjZShyZVByb3BOYW1lLCBmdW5jdGlvbihtYXRjaCwgbnVtYmVyLCBxdW90ZSwgc3RyaW5nKSB7XG4gICAgcmVzdWx0LnB1c2gocXVvdGUgPyBzdHJpbmcucmVwbGFjZShyZUVzY2FwZUNoYXIsICckMScpIDogKG51bWJlciB8fCBtYXRjaCkpO1xuICB9KTtcbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB0b1BhdGg7XG4iLCJ2YXIgTGF6eVdyYXBwZXIgPSByZXF1aXJlKCcuL0xhenlXcmFwcGVyJyksXG4gICAgTG9kYXNoV3JhcHBlciA9IHJlcXVpcmUoJy4vTG9kYXNoV3JhcHBlcicpLFxuICAgIGFycmF5Q29weSA9IHJlcXVpcmUoJy4vYXJyYXlDb3B5Jyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGNsb25lIG9mIGB3cmFwcGVyYC5cbiAqXG4gKiBAcHJpdmF0ZVxuICogQHBhcmFtIHtPYmplY3R9IHdyYXBwZXIgVGhlIHdyYXBwZXIgdG8gY2xvbmUuXG4gKiBAcmV0dXJucyB7T2JqZWN0fSBSZXR1cm5zIHRoZSBjbG9uZWQgd3JhcHBlci5cbiAqL1xuZnVuY3Rpb24gd3JhcHBlckNsb25lKHdyYXBwZXIpIHtcbiAgcmV0dXJuIHdyYXBwZXIgaW5zdGFuY2VvZiBMYXp5V3JhcHBlclxuICAgID8gd3JhcHBlci5jbG9uZSgpXG4gICAgOiBuZXcgTG9kYXNoV3JhcHBlcih3cmFwcGVyLl9fd3JhcHBlZF9fLCB3cmFwcGVyLl9fY2hhaW5fXywgYXJyYXlDb3B5KHdyYXBwZXIuX19hY3Rpb25zX18pKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB3cmFwcGVyQ2xvbmU7XG4iLCJ2YXIgYmFzZUNsb25lID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvYmFzZUNsb25lJyksXG4gICAgYmluZENhbGxiYWNrID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvYmluZENhbGxiYWNrJyk7XG5cbi8qKlxuICogQ3JlYXRlcyBhIGRlZXAgY2xvbmUgb2YgYHZhbHVlYC4gSWYgYGN1c3RvbWl6ZXJgIGlzIHByb3ZpZGVkIGl0J3MgaW52b2tlZFxuICogdG8gcHJvZHVjZSB0aGUgY2xvbmVkIHZhbHVlcy4gSWYgYGN1c3RvbWl6ZXJgIHJldHVybnMgYHVuZGVmaW5lZGAgY2xvbmluZ1xuICogaXMgaGFuZGxlZCBieSB0aGUgbWV0aG9kIGluc3RlYWQuIFRoZSBgY3VzdG9taXplcmAgaXMgYm91bmQgdG8gYHRoaXNBcmdgXG4gKiBhbmQgaW52b2tlZCB3aXRoIHVwIHRvIHRocmVlIGFyZ3VtZW50OyAodmFsdWUgWywgaW5kZXh8a2V5LCBvYmplY3RdKS5cbiAqXG4gKiAqKk5vdGU6KiogVGhpcyBtZXRob2QgaXMgbG9vc2VseSBiYXNlZCBvbiB0aGVcbiAqIFtzdHJ1Y3R1cmVkIGNsb25lIGFsZ29yaXRobV0oaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDUvaW5mcmFzdHJ1Y3R1cmUuaHRtbCNpbnRlcm5hbC1zdHJ1Y3R1cmVkLWNsb25pbmctYWxnb3JpdGhtKS5cbiAqIFRoZSBlbnVtZXJhYmxlIHByb3BlcnRpZXMgb2YgYGFyZ3VtZW50c2Agb2JqZWN0cyBhbmQgb2JqZWN0cyBjcmVhdGVkIGJ5XG4gKiBjb25zdHJ1Y3RvcnMgb3RoZXIgdGhhbiBgT2JqZWN0YCBhcmUgY2xvbmVkIHRvIHBsYWluIGBPYmplY3RgIG9iamVjdHMuIEFuXG4gKiBlbXB0eSBvYmplY3QgaXMgcmV0dXJuZWQgZm9yIHVuY2xvbmVhYmxlIHZhbHVlcyBzdWNoIGFzIGZ1bmN0aW9ucywgRE9NIG5vZGVzLFxuICogTWFwcywgU2V0cywgYW5kIFdlYWtNYXBzLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gZGVlcCBjbG9uZS5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IFtjdXN0b21pemVyXSBUaGUgZnVuY3Rpb24gdG8gY3VzdG9taXplIGNsb25pbmcgdmFsdWVzLlxuICogQHBhcmFtIHsqfSBbdGhpc0FyZ10gVGhlIGB0aGlzYCBiaW5kaW5nIG9mIGBjdXN0b21pemVyYC5cbiAqIEByZXR1cm5zIHsqfSBSZXR1cm5zIHRoZSBkZWVwIGNsb25lZCB2YWx1ZS5cbiAqIEBleGFtcGxlXG4gKlxuICogdmFyIHVzZXJzID0gW1xuICogICB7ICd1c2VyJzogJ2Jhcm5leScgfSxcbiAqICAgeyAndXNlcic6ICdmcmVkJyB9XG4gKiBdO1xuICpcbiAqIHZhciBkZWVwID0gXy5jbG9uZURlZXAodXNlcnMpO1xuICogZGVlcFswXSA9PT0gdXNlcnNbMF07XG4gKiAvLyA9PiBmYWxzZVxuICpcbiAqIC8vIHVzaW5nIGEgY3VzdG9taXplciBjYWxsYmFja1xuICogdmFyIGVsID0gXy5jbG9uZURlZXAoZG9jdW1lbnQuYm9keSwgZnVuY3Rpb24odmFsdWUpIHtcbiAqICAgaWYgKF8uaXNFbGVtZW50KHZhbHVlKSkge1xuICogICAgIHJldHVybiB2YWx1ZS5jbG9uZU5vZGUodHJ1ZSk7XG4gKiAgIH1cbiAqIH0pO1xuICpcbiAqIGVsID09PSBkb2N1bWVudC5ib2R5XG4gKiAvLyA9PiBmYWxzZVxuICogZWwubm9kZU5hbWVcbiAqIC8vID0+IEJPRFlcbiAqIGVsLmNoaWxkTm9kZXMubGVuZ3RoO1xuICogLy8gPT4gMjBcbiAqL1xuZnVuY3Rpb24gY2xvbmVEZWVwKHZhbHVlLCBjdXN0b21pemVyLCB0aGlzQXJnKSB7XG4gIHJldHVybiB0eXBlb2YgY3VzdG9taXplciA9PSAnZnVuY3Rpb24nXG4gICAgPyBiYXNlQ2xvbmUodmFsdWUsIHRydWUsIGJpbmRDYWxsYmFjayhjdXN0b21pemVyLCB0aGlzQXJnLCAzKSlcbiAgICA6IGJhc2VDbG9uZSh2YWx1ZSwgdHJ1ZSk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gY2xvbmVEZWVwO1xuIiwidmFyIGlzQXJyYXlMaWtlID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvaXNBcnJheUxpa2UnKSxcbiAgICBpc09iamVjdExpa2UgPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9pc09iamVjdExpa2UnKTtcblxuLyoqIFVzZWQgZm9yIG5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKiBVc2VkIHRvIGNoZWNrIG9iamVjdHMgZm9yIG93biBwcm9wZXJ0aWVzLiAqL1xudmFyIGhhc093blByb3BlcnR5ID0gb2JqZWN0UHJvdG8uaGFzT3duUHJvcGVydHk7XG5cbi8qKiBOYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgcHJvcGVydHlJc0VudW1lcmFibGUgPSBvYmplY3RQcm90by5wcm9wZXJ0eUlzRW51bWVyYWJsZTtcblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBjbGFzc2lmaWVkIGFzIGFuIGBhcmd1bWVudHNgIG9iamVjdC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgY29ycmVjdGx5IGNsYXNzaWZpZWQsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc0FyZ3VtZW50cyhmdW5jdGlvbigpIHsgcmV0dXJuIGFyZ3VtZW50czsgfSgpKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzQXJndW1lbnRzKFsxLCAyLCAzXSk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc0FyZ3VtZW50cyh2YWx1ZSkge1xuICByZXR1cm4gaXNPYmplY3RMaWtlKHZhbHVlKSAmJiBpc0FycmF5TGlrZSh2YWx1ZSkgJiZcbiAgICBoYXNPd25Qcm9wZXJ0eS5jYWxsKHZhbHVlLCAnY2FsbGVlJykgJiYgIXByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwodmFsdWUsICdjYWxsZWUnKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBpc0FyZ3VtZW50cztcbiIsInZhciBnZXROYXRpdmUgPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9nZXROYXRpdmUnKSxcbiAgICBpc0xlbmd0aCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2lzTGVuZ3RoJyksXG4gICAgaXNPYmplY3RMaWtlID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvaXNPYmplY3RMaWtlJyk7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBhcnJheVRhZyA9ICdbb2JqZWN0IEFycmF5XSc7XG5cbi8qKiBVc2VkIGZvciBuYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgb2JqZWN0UHJvdG8gPSBPYmplY3QucHJvdG90eXBlO1xuXG4vKipcbiAqIFVzZWQgdG8gcmVzb2x2ZSB0aGUgW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzYuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmpUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKiBOYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMgZm9yIHRob3NlIHdpdGggdGhlIHNhbWUgbmFtZSBhcyBvdGhlciBgbG9kYXNoYCBtZXRob2RzLiAqL1xudmFyIG5hdGl2ZUlzQXJyYXkgPSBnZXROYXRpdmUoQXJyYXksICdpc0FycmF5Jyk7XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgY2xhc3NpZmllZCBhcyBhbiBgQXJyYXlgIG9iamVjdC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgY29ycmVjdGx5IGNsYXNzaWZpZWQsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc0FycmF5KFsxLCAyLCAzXSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc0FycmF5KGZ1bmN0aW9uKCkgeyByZXR1cm4gYXJndW1lbnRzOyB9KCkpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xudmFyIGlzQXJyYXkgPSBuYXRpdmVJc0FycmF5IHx8IGZ1bmN0aW9uKHZhbHVlKSB7XG4gIHJldHVybiBpc09iamVjdExpa2UodmFsdWUpICYmIGlzTGVuZ3RoKHZhbHVlLmxlbmd0aCkgJiYgb2JqVG9TdHJpbmcuY2FsbCh2YWx1ZSkgPT0gYXJyYXlUYWc7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IGlzQXJyYXk7XG4iLCJ2YXIgaXNBcmd1bWVudHMgPSByZXF1aXJlKCcuL2lzQXJndW1lbnRzJyksXG4gICAgaXNBcnJheSA9IHJlcXVpcmUoJy4vaXNBcnJheScpLFxuICAgIGlzQXJyYXlMaWtlID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvaXNBcnJheUxpa2UnKSxcbiAgICBpc0Z1bmN0aW9uID0gcmVxdWlyZSgnLi9pc0Z1bmN0aW9uJyksXG4gICAgaXNPYmplY3RMaWtlID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvaXNPYmplY3RMaWtlJyksXG4gICAgaXNTdHJpbmcgPSByZXF1aXJlKCcuL2lzU3RyaW5nJyksXG4gICAga2V5cyA9IHJlcXVpcmUoJy4uL29iamVjdC9rZXlzJyk7XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgZW1wdHkuIEEgdmFsdWUgaXMgY29uc2lkZXJlZCBlbXB0eSB1bmxlc3MgaXQncyBhblxuICogYGFyZ3VtZW50c2Agb2JqZWN0LCBhcnJheSwgc3RyaW5nLCBvciBqUXVlcnktbGlrZSBjb2xsZWN0aW9uIHdpdGggYSBsZW5ndGhcbiAqIGdyZWF0ZXIgdGhhbiBgMGAgb3IgYW4gb2JqZWN0IHdpdGggb3duIGVudW1lcmFibGUgcHJvcGVydGllcy5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7QXJyYXl8T2JqZWN0fHN0cmluZ30gdmFsdWUgVGhlIHZhbHVlIHRvIGluc3BlY3QuXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBlbXB0eSwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzRW1wdHkobnVsbCk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc0VtcHR5KHRydWUpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNFbXB0eSgxKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzRW1wdHkoWzEsIDIsIDNdKTtcbiAqIC8vID0+IGZhbHNlXG4gKlxuICogXy5pc0VtcHR5KHsgJ2EnOiAxIH0pO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNFbXB0eSh2YWx1ZSkge1xuICBpZiAodmFsdWUgPT0gbnVsbCkge1xuICAgIHJldHVybiB0cnVlO1xuICB9XG4gIGlmIChpc0FycmF5TGlrZSh2YWx1ZSkgJiYgKGlzQXJyYXkodmFsdWUpIHx8IGlzU3RyaW5nKHZhbHVlKSB8fCBpc0FyZ3VtZW50cyh2YWx1ZSkgfHxcbiAgICAgIChpc09iamVjdExpa2UodmFsdWUpICYmIGlzRnVuY3Rpb24odmFsdWUuc3BsaWNlKSkpKSB7XG4gICAgcmV0dXJuICF2YWx1ZS5sZW5ndGg7XG4gIH1cbiAgcmV0dXJuICFrZXlzKHZhbHVlKS5sZW5ndGg7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaXNFbXB0eTtcbiIsInZhciBpc09iamVjdCA9IHJlcXVpcmUoJy4vaXNPYmplY3QnKTtcblxuLyoqIGBPYmplY3QjdG9TdHJpbmdgIHJlc3VsdCByZWZlcmVuY2VzLiAqL1xudmFyIGZ1bmNUYWcgPSAnW29iamVjdCBGdW5jdGlvbl0nO1xuXG4vKiogVXNlZCBmb3IgbmF0aXZlIG1ldGhvZCByZWZlcmVuY2VzLiAqL1xudmFyIG9iamVjdFByb3RvID0gT2JqZWN0LnByb3RvdHlwZTtcblxuLyoqXG4gKiBVc2VkIHRvIHJlc29sdmUgdGhlIFtgdG9TdHJpbmdUYWdgXShodHRwOi8vZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi82LjAvI3NlYy1vYmplY3QucHJvdG90eXBlLnRvc3RyaW5nKVxuICogb2YgdmFsdWVzLlxuICovXG52YXIgb2JqVG9TdHJpbmcgPSBvYmplY3RQcm90by50b1N0cmluZztcblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBjbGFzc2lmaWVkIGFzIGEgYEZ1bmN0aW9uYCBvYmplY3QuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGNvcnJlY3RseSBjbGFzc2lmaWVkLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNGdW5jdGlvbihfKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzRnVuY3Rpb24oL2FiYy8pO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNGdW5jdGlvbih2YWx1ZSkge1xuICAvLyBUaGUgdXNlIG9mIGBPYmplY3QjdG9TdHJpbmdgIGF2b2lkcyBpc3N1ZXMgd2l0aCB0aGUgYHR5cGVvZmAgb3BlcmF0b3JcbiAgLy8gaW4gb2xkZXIgdmVyc2lvbnMgb2YgQ2hyb21lIGFuZCBTYWZhcmkgd2hpY2ggcmV0dXJuICdmdW5jdGlvbicgZm9yIHJlZ2V4ZXNcbiAgLy8gYW5kIFNhZmFyaSA4IHdoaWNoIHJldHVybnMgJ29iamVjdCcgZm9yIHR5cGVkIGFycmF5IGNvbnN0cnVjdG9ycy5cbiAgcmV0dXJuIGlzT2JqZWN0KHZhbHVlKSAmJiBvYmpUb1N0cmluZy5jYWxsKHZhbHVlKSA9PSBmdW5jVGFnO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGlzRnVuY3Rpb247XG4iLCJ2YXIgaXNGdW5jdGlvbiA9IHJlcXVpcmUoJy4vaXNGdW5jdGlvbicpLFxuICAgIGlzSG9zdE9iamVjdCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2lzSG9zdE9iamVjdCcpLFxuICAgIGlzT2JqZWN0TGlrZSA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2lzT2JqZWN0TGlrZScpO1xuXG4vKiogVXNlZCB0byBkZXRlY3QgaG9zdCBjb25zdHJ1Y3RvcnMgKFNhZmFyaSA+IDUpLiAqL1xudmFyIHJlSXNIb3N0Q3RvciA9IC9eXFxbb2JqZWN0IC4rP0NvbnN0cnVjdG9yXFxdJC87XG5cbi8qKiBVc2VkIGZvciBuYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgb2JqZWN0UHJvdG8gPSBPYmplY3QucHJvdG90eXBlO1xuXG4vKiogVXNlZCB0byByZXNvbHZlIHRoZSBkZWNvbXBpbGVkIHNvdXJjZSBvZiBmdW5jdGlvbnMuICovXG52YXIgZm5Ub1N0cmluZyA9IEZ1bmN0aW9uLnByb3RvdHlwZS50b1N0cmluZztcblxuLyoqIFVzZWQgdG8gY2hlY2sgb2JqZWN0cyBmb3Igb3duIHByb3BlcnRpZXMuICovXG52YXIgaGFzT3duUHJvcGVydHkgPSBvYmplY3RQcm90by5oYXNPd25Qcm9wZXJ0eTtcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGlmIGEgbWV0aG9kIGlzIG5hdGl2ZS4gKi9cbnZhciByZUlzTmF0aXZlID0gUmVnRXhwKCdeJyArXG4gIGZuVG9TdHJpbmcuY2FsbChoYXNPd25Qcm9wZXJ0eSkucmVwbGFjZSgvW1xcXFxeJC4qKz8oKVtcXF17fXxdL2csICdcXFxcJCYnKVxuICAucmVwbGFjZSgvaGFzT3duUHJvcGVydHl8KGZ1bmN0aW9uKS4qPyg/PVxcXFxcXCgpfCBmb3IgLis/KD89XFxcXFxcXSkvZywgJyQxLio/JykgKyAnJCdcbik7XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgYSBuYXRpdmUgZnVuY3Rpb24uXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGEgbmF0aXZlIGZ1bmN0aW9uLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNOYXRpdmUoQXJyYXkucHJvdG90eXBlLnB1c2gpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNOYXRpdmUoXyk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc05hdGl2ZSh2YWx1ZSkge1xuICBpZiAodmFsdWUgPT0gbnVsbCkge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBpZiAoaXNGdW5jdGlvbih2YWx1ZSkpIHtcbiAgICByZXR1cm4gcmVJc05hdGl2ZS50ZXN0KGZuVG9TdHJpbmcuY2FsbCh2YWx1ZSkpO1xuICB9XG4gIHJldHVybiBpc09iamVjdExpa2UodmFsdWUpICYmIChpc0hvc3RPYmplY3QodmFsdWUpID8gcmVJc05hdGl2ZSA6IHJlSXNIb3N0Q3RvcikudGVzdCh2YWx1ZSk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaXNOYXRpdmU7XG4iLCIvKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIHRoZSBbbGFuZ3VhZ2UgdHlwZV0oaHR0cHM6Ly9lczUuZ2l0aHViLmlvLyN4OCkgb2YgYE9iamVjdGAuXG4gKiAoZS5nLiBhcnJheXMsIGZ1bmN0aW9ucywgb2JqZWN0cywgcmVnZXhlcywgYG5ldyBOdW1iZXIoMClgLCBhbmQgYG5ldyBTdHJpbmcoJycpYClcbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYW4gb2JqZWN0LCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNPYmplY3Qoe30pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KDEpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNPYmplY3QodmFsdWUpIHtcbiAgLy8gQXZvaWQgYSBWOCBKSVQgYnVnIGluIENocm9tZSAxOS0yMC5cbiAgLy8gU2VlIGh0dHBzOi8vY29kZS5nb29nbGUuY29tL3AvdjgvaXNzdWVzL2RldGFpbD9pZD0yMjkxIGZvciBtb3JlIGRldGFpbHMuXG4gIHZhciB0eXBlID0gdHlwZW9mIHZhbHVlO1xuICByZXR1cm4gISF2YWx1ZSAmJiAodHlwZSA9PSAnb2JqZWN0JyB8fCB0eXBlID09ICdmdW5jdGlvbicpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGlzT2JqZWN0O1xuIiwidmFyIGJhc2VGb3JJbiA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2Jhc2VGb3JJbicpLFxuICAgIGlzQXJndW1lbnRzID0gcmVxdWlyZSgnLi9pc0FyZ3VtZW50cycpLFxuICAgIGlzSG9zdE9iamVjdCA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2lzSG9zdE9iamVjdCcpLFxuICAgIGlzT2JqZWN0TGlrZSA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2lzT2JqZWN0TGlrZScpLFxuICAgIHN1cHBvcnQgPSByZXF1aXJlKCcuLi9zdXBwb3J0Jyk7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RUYWcgPSAnW29iamVjdCBPYmplY3RdJztcblxuLyoqIFVzZWQgZm9yIG5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKiBVc2VkIHRvIGNoZWNrIG9iamVjdHMgZm9yIG93biBwcm9wZXJ0aWVzLiAqL1xudmFyIGhhc093blByb3BlcnR5ID0gb2JqZWN0UHJvdG8uaGFzT3duUHJvcGVydHk7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZSBbYHRvU3RyaW5nVGFnYF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNi4wLyNzZWMtb2JqZWN0LnByb3RvdHlwZS50b3N0cmluZylcbiAqIG9mIHZhbHVlcy5cbiAqL1xudmFyIG9ialRvU3RyaW5nID0gb2JqZWN0UHJvdG8udG9TdHJpbmc7XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgYSBwbGFpbiBvYmplY3QsIHRoYXQgaXMsIGFuIG9iamVjdCBjcmVhdGVkIGJ5IHRoZVxuICogYE9iamVjdGAgY29uc3RydWN0b3Igb3Igb25lIHdpdGggYSBgW1tQcm90b3R5cGVdXWAgb2YgYG51bGxgLlxuICpcbiAqICoqTm90ZToqKiBUaGlzIG1ldGhvZCBhc3N1bWVzIG9iamVjdHMgY3JlYXRlZCBieSB0aGUgYE9iamVjdGAgY29uc3RydWN0b3JcbiAqIGhhdmUgbm8gaW5oZXJpdGVkIGVudW1lcmFibGUgcHJvcGVydGllcy5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYSBwbGFpbiBvYmplY3QsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogZnVuY3Rpb24gRm9vKCkge1xuICogICB0aGlzLmEgPSAxO1xuICogfVxuICpcbiAqIF8uaXNQbGFpbk9iamVjdChuZXcgRm9vKTtcbiAqIC8vID0+IGZhbHNlXG4gKlxuICogXy5pc1BsYWluT2JqZWN0KFsxLCAyLCAzXSk7XG4gKiAvLyA9PiBmYWxzZVxuICpcbiAqIF8uaXNQbGFpbk9iamVjdCh7ICd4JzogMCwgJ3knOiAwIH0pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNQbGFpbk9iamVjdChPYmplY3QuY3JlYXRlKG51bGwpKTtcbiAqIC8vID0+IHRydWVcbiAqL1xuZnVuY3Rpb24gaXNQbGFpbk9iamVjdCh2YWx1ZSkge1xuICB2YXIgQ3RvcjtcblxuICAvLyBFeGl0IGVhcmx5IGZvciBub24gYE9iamVjdGAgb2JqZWN0cy5cbiAgaWYgKCEoaXNPYmplY3RMaWtlKHZhbHVlKSAmJiBvYmpUb1N0cmluZy5jYWxsKHZhbHVlKSA9PSBvYmplY3RUYWcgJiYgIWlzSG9zdE9iamVjdCh2YWx1ZSkgJiYgIWlzQXJndW1lbnRzKHZhbHVlKSkgfHxcbiAgICAgICghaGFzT3duUHJvcGVydHkuY2FsbCh2YWx1ZSwgJ2NvbnN0cnVjdG9yJykgJiYgKEN0b3IgPSB2YWx1ZS5jb25zdHJ1Y3RvciwgdHlwZW9mIEN0b3IgPT0gJ2Z1bmN0aW9uJyAmJiAhKEN0b3IgaW5zdGFuY2VvZiBDdG9yKSkpKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIC8vIElFIDwgOSBpdGVyYXRlcyBpbmhlcml0ZWQgcHJvcGVydGllcyBiZWZvcmUgb3duIHByb3BlcnRpZXMuIElmIHRoZSBmaXJzdFxuICAvLyBpdGVyYXRlZCBwcm9wZXJ0eSBpcyBhbiBvYmplY3QncyBvd24gcHJvcGVydHkgdGhlbiB0aGVyZSBhcmUgbm8gaW5oZXJpdGVkXG4gIC8vIGVudW1lcmFibGUgcHJvcGVydGllcy5cbiAgdmFyIHJlc3VsdDtcbiAgaWYgKHN1cHBvcnQub3duTGFzdCkge1xuICAgIGJhc2VGb3JJbih2YWx1ZSwgZnVuY3Rpb24oc3ViVmFsdWUsIGtleSwgb2JqZWN0KSB7XG4gICAgICByZXN1bHQgPSBoYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwga2V5KTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9KTtcbiAgICByZXR1cm4gcmVzdWx0ICE9PSBmYWxzZTtcbiAgfVxuICAvLyBJbiBtb3N0IGVudmlyb25tZW50cyBhbiBvYmplY3QncyBvd24gcHJvcGVydGllcyBhcmUgaXRlcmF0ZWQgYmVmb3JlXG4gIC8vIGl0cyBpbmhlcml0ZWQgcHJvcGVydGllcy4gSWYgdGhlIGxhc3QgaXRlcmF0ZWQgcHJvcGVydHkgaXMgYW4gb2JqZWN0J3NcbiAgLy8gb3duIHByb3BlcnR5IHRoZW4gdGhlcmUgYXJlIG5vIGluaGVyaXRlZCBlbnVtZXJhYmxlIHByb3BlcnRpZXMuXG4gIGJhc2VGb3JJbih2YWx1ZSwgZnVuY3Rpb24oc3ViVmFsdWUsIGtleSkge1xuICAgIHJlc3VsdCA9IGtleTtcbiAgfSk7XG4gIHJldHVybiByZXN1bHQgPT09IHVuZGVmaW5lZCB8fCBoYXNPd25Qcm9wZXJ0eS5jYWxsKHZhbHVlLCByZXN1bHQpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGlzUGxhaW5PYmplY3Q7XG4iLCJ2YXIgaXNPYmplY3RMaWtlID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvaXNPYmplY3RMaWtlJyk7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBzdHJpbmdUYWcgPSAnW29iamVjdCBTdHJpbmddJztcblxuLyoqIFVzZWQgZm9yIG5hdGl2ZSBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZSBbYHRvU3RyaW5nVGFnYF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNi4wLyNzZWMtb2JqZWN0LnByb3RvdHlwZS50b3N0cmluZylcbiAqIG9mIHZhbHVlcy5cbiAqL1xudmFyIG9ialRvU3RyaW5nID0gb2JqZWN0UHJvdG8udG9TdHJpbmc7XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgY2xhc3NpZmllZCBhcyBhIGBTdHJpbmdgIHByaW1pdGl2ZSBvciBvYmplY3QuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGNvcnJlY3RseSBjbGFzc2lmaWVkLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNTdHJpbmcoJ2FiYycpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNTdHJpbmcoMSk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc1N0cmluZyh2YWx1ZSkge1xuICByZXR1cm4gdHlwZW9mIHZhbHVlID09ICdzdHJpbmcnIHx8IChpc09iamVjdExpa2UodmFsdWUpICYmIG9ialRvU3RyaW5nLmNhbGwodmFsdWUpID09IHN0cmluZ1RhZyk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaXNTdHJpbmc7XG4iLCJ2YXIgaXNMZW5ndGggPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9pc0xlbmd0aCcpLFxuICAgIGlzT2JqZWN0TGlrZSA9IHJlcXVpcmUoJy4uL2ludGVybmFsL2lzT2JqZWN0TGlrZScpO1xuXG4vKiogYE9iamVjdCN0b1N0cmluZ2AgcmVzdWx0IHJlZmVyZW5jZXMuICovXG52YXIgYXJnc1RhZyA9ICdbb2JqZWN0IEFyZ3VtZW50c10nLFxuICAgIGFycmF5VGFnID0gJ1tvYmplY3QgQXJyYXldJyxcbiAgICBib29sVGFnID0gJ1tvYmplY3QgQm9vbGVhbl0nLFxuICAgIGRhdGVUYWcgPSAnW29iamVjdCBEYXRlXScsXG4gICAgZXJyb3JUYWcgPSAnW29iamVjdCBFcnJvcl0nLFxuICAgIGZ1bmNUYWcgPSAnW29iamVjdCBGdW5jdGlvbl0nLFxuICAgIG1hcFRhZyA9ICdbb2JqZWN0IE1hcF0nLFxuICAgIG51bWJlclRhZyA9ICdbb2JqZWN0IE51bWJlcl0nLFxuICAgIG9iamVjdFRhZyA9ICdbb2JqZWN0IE9iamVjdF0nLFxuICAgIHJlZ2V4cFRhZyA9ICdbb2JqZWN0IFJlZ0V4cF0nLFxuICAgIHNldFRhZyA9ICdbb2JqZWN0IFNldF0nLFxuICAgIHN0cmluZ1RhZyA9ICdbb2JqZWN0IFN0cmluZ10nLFxuICAgIHdlYWtNYXBUYWcgPSAnW29iamVjdCBXZWFrTWFwXSc7XG5cbnZhciBhcnJheUJ1ZmZlclRhZyA9ICdbb2JqZWN0IEFycmF5QnVmZmVyXScsXG4gICAgZmxvYXQzMlRhZyA9ICdbb2JqZWN0IEZsb2F0MzJBcnJheV0nLFxuICAgIGZsb2F0NjRUYWcgPSAnW29iamVjdCBGbG9hdDY0QXJyYXldJyxcbiAgICBpbnQ4VGFnID0gJ1tvYmplY3QgSW50OEFycmF5XScsXG4gICAgaW50MTZUYWcgPSAnW29iamVjdCBJbnQxNkFycmF5XScsXG4gICAgaW50MzJUYWcgPSAnW29iamVjdCBJbnQzMkFycmF5XScsXG4gICAgdWludDhUYWcgPSAnW29iamVjdCBVaW50OEFycmF5XScsXG4gICAgdWludDhDbGFtcGVkVGFnID0gJ1tvYmplY3QgVWludDhDbGFtcGVkQXJyYXldJyxcbiAgICB1aW50MTZUYWcgPSAnW29iamVjdCBVaW50MTZBcnJheV0nLFxuICAgIHVpbnQzMlRhZyA9ICdbb2JqZWN0IFVpbnQzMkFycmF5XSc7XG5cbi8qKiBVc2VkIHRvIGlkZW50aWZ5IGB0b1N0cmluZ1RhZ2AgdmFsdWVzIG9mIHR5cGVkIGFycmF5cy4gKi9cbnZhciB0eXBlZEFycmF5VGFncyA9IHt9O1xudHlwZWRBcnJheVRhZ3NbZmxvYXQzMlRhZ10gPSB0eXBlZEFycmF5VGFnc1tmbG9hdDY0VGFnXSA9XG50eXBlZEFycmF5VGFnc1tpbnQ4VGFnXSA9IHR5cGVkQXJyYXlUYWdzW2ludDE2VGFnXSA9XG50eXBlZEFycmF5VGFnc1tpbnQzMlRhZ10gPSB0eXBlZEFycmF5VGFnc1t1aW50OFRhZ10gPVxudHlwZWRBcnJheVRhZ3NbdWludDhDbGFtcGVkVGFnXSA9IHR5cGVkQXJyYXlUYWdzW3VpbnQxNlRhZ10gPVxudHlwZWRBcnJheVRhZ3NbdWludDMyVGFnXSA9IHRydWU7XG50eXBlZEFycmF5VGFnc1thcmdzVGFnXSA9IHR5cGVkQXJyYXlUYWdzW2FycmF5VGFnXSA9XG50eXBlZEFycmF5VGFnc1thcnJheUJ1ZmZlclRhZ10gPSB0eXBlZEFycmF5VGFnc1tib29sVGFnXSA9XG50eXBlZEFycmF5VGFnc1tkYXRlVGFnXSA9IHR5cGVkQXJyYXlUYWdzW2Vycm9yVGFnXSA9XG50eXBlZEFycmF5VGFnc1tmdW5jVGFnXSA9IHR5cGVkQXJyYXlUYWdzW21hcFRhZ10gPVxudHlwZWRBcnJheVRhZ3NbbnVtYmVyVGFnXSA9IHR5cGVkQXJyYXlUYWdzW29iamVjdFRhZ10gPVxudHlwZWRBcnJheVRhZ3NbcmVnZXhwVGFnXSA9IHR5cGVkQXJyYXlUYWdzW3NldFRhZ10gPVxudHlwZWRBcnJheVRhZ3Nbc3RyaW5nVGFnXSA9IHR5cGVkQXJyYXlUYWdzW3dlYWtNYXBUYWddID0gZmFsc2U7XG5cbi8qKiBVc2VkIGZvciBuYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgb2JqZWN0UHJvdG8gPSBPYmplY3QucHJvdG90eXBlO1xuXG4vKipcbiAqIFVzZWQgdG8gcmVzb2x2ZSB0aGUgW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzYuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmpUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIGNsYXNzaWZpZWQgYXMgYSB0eXBlZCBhcnJheS5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgY29ycmVjdGx5IGNsYXNzaWZpZWQsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc1R5cGVkQXJyYXkobmV3IFVpbnQ4QXJyYXkpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNUeXBlZEFycmF5KFtdKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzVHlwZWRBcnJheSh2YWx1ZSkge1xuICByZXR1cm4gaXNPYmplY3RMaWtlKHZhbHVlKSAmJiBpc0xlbmd0aCh2YWx1ZS5sZW5ndGgpICYmICEhdHlwZWRBcnJheVRhZ3Nbb2JqVG9TdHJpbmcuY2FsbCh2YWx1ZSldO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGlzVHlwZWRBcnJheTtcbiIsIi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgYHVuZGVmaW5lZGAuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGB1bmRlZmluZWRgLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNVbmRlZmluZWQodm9pZCAwKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzVW5kZWZpbmVkKG51bGwpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNVbmRlZmluZWQodmFsdWUpIHtcbiAgcmV0dXJuIHZhbHVlID09PSB1bmRlZmluZWQ7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaXNVbmRlZmluZWQ7XG4iLCJ2YXIgZ2V0TmF0aXZlID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvZ2V0TmF0aXZlJyksXG4gICAgaXNBcnJheUxpa2UgPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9pc0FycmF5TGlrZScpLFxuICAgIGlzT2JqZWN0ID0gcmVxdWlyZSgnLi4vbGFuZy9pc09iamVjdCcpLFxuICAgIHNoaW1LZXlzID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvc2hpbUtleXMnKSxcbiAgICBzdXBwb3J0ID0gcmVxdWlyZSgnLi4vc3VwcG9ydCcpO1xuXG4vKiBOYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMgZm9yIHRob3NlIHdpdGggdGhlIHNhbWUgbmFtZSBhcyBvdGhlciBgbG9kYXNoYCBtZXRob2RzLiAqL1xudmFyIG5hdGl2ZUtleXMgPSBnZXROYXRpdmUoT2JqZWN0LCAna2V5cycpO1xuXG4vKipcbiAqIENyZWF0ZXMgYW4gYXJyYXkgb2YgdGhlIG93biBlbnVtZXJhYmxlIHByb3BlcnR5IG5hbWVzIG9mIGBvYmplY3RgLlxuICpcbiAqICoqTm90ZToqKiBOb24tb2JqZWN0IHZhbHVlcyBhcmUgY29lcmNlZCB0byBvYmplY3RzLiBTZWUgdGhlXG4gKiBbRVMgc3BlY10oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNi4wLyNzZWMtb2JqZWN0LmtleXMpXG4gKiBmb3IgbW9yZSBkZXRhaWxzLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAY2F0ZWdvcnkgT2JqZWN0XG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gcXVlcnkuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIGFycmF5IG9mIHByb3BlcnR5IG5hbWVzLlxuICogQGV4YW1wbGVcbiAqXG4gKiBmdW5jdGlvbiBGb28oKSB7XG4gKiAgIHRoaXMuYSA9IDE7XG4gKiAgIHRoaXMuYiA9IDI7XG4gKiB9XG4gKlxuICogRm9vLnByb3RvdHlwZS5jID0gMztcbiAqXG4gKiBfLmtleXMobmV3IEZvbyk7XG4gKiAvLyA9PiBbJ2EnLCAnYiddIChpdGVyYXRpb24gb3JkZXIgaXMgbm90IGd1YXJhbnRlZWQpXG4gKlxuICogXy5rZXlzKCdoaScpO1xuICogLy8gPT4gWycwJywgJzEnXVxuICovXG52YXIga2V5cyA9ICFuYXRpdmVLZXlzID8gc2hpbUtleXMgOiBmdW5jdGlvbihvYmplY3QpIHtcbiAgdmFyIEN0b3IgPSBvYmplY3QgPT0gbnVsbCA/IHVuZGVmaW5lZCA6IG9iamVjdC5jb25zdHJ1Y3RvcjtcbiAgaWYgKCh0eXBlb2YgQ3RvciA9PSAnZnVuY3Rpb24nICYmIEN0b3IucHJvdG90eXBlID09PSBvYmplY3QpIHx8XG4gICAgICAodHlwZW9mIG9iamVjdCA9PSAnZnVuY3Rpb24nID8gc3VwcG9ydC5lbnVtUHJvdG90eXBlcyA6IGlzQXJyYXlMaWtlKG9iamVjdCkpKSB7XG4gICAgcmV0dXJuIHNoaW1LZXlzKG9iamVjdCk7XG4gIH1cbiAgcmV0dXJuIGlzT2JqZWN0KG9iamVjdCkgPyBuYXRpdmVLZXlzKG9iamVjdCkgOiBbXTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0ga2V5cztcbiIsInZhciBhcnJheUVhY2ggPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9hcnJheUVhY2gnKSxcbiAgICBpc0FyZ3VtZW50cyA9IHJlcXVpcmUoJy4uL2xhbmcvaXNBcmd1bWVudHMnKSxcbiAgICBpc0FycmF5ID0gcmVxdWlyZSgnLi4vbGFuZy9pc0FycmF5JyksXG4gICAgaXNGdW5jdGlvbiA9IHJlcXVpcmUoJy4uL2xhbmcvaXNGdW5jdGlvbicpLFxuICAgIGlzSW5kZXggPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9pc0luZGV4JyksXG4gICAgaXNMZW5ndGggPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9pc0xlbmd0aCcpLFxuICAgIGlzT2JqZWN0ID0gcmVxdWlyZSgnLi4vbGFuZy9pc09iamVjdCcpLFxuICAgIGlzU3RyaW5nID0gcmVxdWlyZSgnLi4vbGFuZy9pc1N0cmluZycpLFxuICAgIHN1cHBvcnQgPSByZXF1aXJlKCcuLi9zdXBwb3J0Jyk7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBhcnJheVRhZyA9ICdbb2JqZWN0IEFycmF5XScsXG4gICAgYm9vbFRhZyA9ICdbb2JqZWN0IEJvb2xlYW5dJyxcbiAgICBkYXRlVGFnID0gJ1tvYmplY3QgRGF0ZV0nLFxuICAgIGVycm9yVGFnID0gJ1tvYmplY3QgRXJyb3JdJyxcbiAgICBmdW5jVGFnID0gJ1tvYmplY3QgRnVuY3Rpb25dJyxcbiAgICBudW1iZXJUYWcgPSAnW29iamVjdCBOdW1iZXJdJyxcbiAgICBvYmplY3RUYWcgPSAnW29iamVjdCBPYmplY3RdJyxcbiAgICByZWdleHBUYWcgPSAnW29iamVjdCBSZWdFeHBdJyxcbiAgICBzdHJpbmdUYWcgPSAnW29iamVjdCBTdHJpbmddJztcblxuLyoqIFVzZWQgdG8gZml4IHRoZSBKU2NyaXB0IGBbW0RvbnRFbnVtXV1gIGJ1Zy4gKi9cbnZhciBzaGFkb3dQcm9wcyA9IFtcbiAgJ2NvbnN0cnVjdG9yJywgJ2hhc093blByb3BlcnR5JywgJ2lzUHJvdG90eXBlT2YnLCAncHJvcGVydHlJc0VudW1lcmFibGUnLFxuICAndG9Mb2NhbGVTdHJpbmcnLCAndG9TdHJpbmcnLCAndmFsdWVPZidcbl07XG5cbi8qKiBVc2VkIGZvciBuYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgZXJyb3JQcm90byA9IEVycm9yLnByb3RvdHlwZSxcbiAgICBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGUsXG4gICAgc3RyaW5nUHJvdG8gPSBTdHJpbmcucHJvdG90eXBlO1xuXG4vKiogVXNlZCB0byBjaGVjayBvYmplY3RzIGZvciBvd24gcHJvcGVydGllcy4gKi9cbnZhciBoYXNPd25Qcm9wZXJ0eSA9IG9iamVjdFByb3RvLmhhc093blByb3BlcnR5O1xuXG4vKipcbiAqIFVzZWQgdG8gcmVzb2x2ZSB0aGUgW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzYuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmpUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKiogVXNlZCB0byBhdm9pZCBpdGVyYXRpbmcgb3ZlciBub24tZW51bWVyYWJsZSBwcm9wZXJ0aWVzIGluIElFIDwgOS4gKi9cbnZhciBub25FbnVtUHJvcHMgPSB7fTtcbm5vbkVudW1Qcm9wc1thcnJheVRhZ10gPSBub25FbnVtUHJvcHNbZGF0ZVRhZ10gPSBub25FbnVtUHJvcHNbbnVtYmVyVGFnXSA9IHsgJ2NvbnN0cnVjdG9yJzogdHJ1ZSwgJ3RvTG9jYWxlU3RyaW5nJzogdHJ1ZSwgJ3RvU3RyaW5nJzogdHJ1ZSwgJ3ZhbHVlT2YnOiB0cnVlIH07XG5ub25FbnVtUHJvcHNbYm9vbFRhZ10gPSBub25FbnVtUHJvcHNbc3RyaW5nVGFnXSA9IHsgJ2NvbnN0cnVjdG9yJzogdHJ1ZSwgJ3RvU3RyaW5nJzogdHJ1ZSwgJ3ZhbHVlT2YnOiB0cnVlIH07XG5ub25FbnVtUHJvcHNbZXJyb3JUYWddID0gbm9uRW51bVByb3BzW2Z1bmNUYWddID0gbm9uRW51bVByb3BzW3JlZ2V4cFRhZ10gPSB7ICdjb25zdHJ1Y3Rvcic6IHRydWUsICd0b1N0cmluZyc6IHRydWUgfTtcbm5vbkVudW1Qcm9wc1tvYmplY3RUYWddID0geyAnY29uc3RydWN0b3InOiB0cnVlIH07XG5cbmFycmF5RWFjaChzaGFkb3dQcm9wcywgZnVuY3Rpb24oa2V5KSB7XG4gIGZvciAodmFyIHRhZyBpbiBub25FbnVtUHJvcHMpIHtcbiAgICBpZiAoaGFzT3duUHJvcGVydHkuY2FsbChub25FbnVtUHJvcHMsIHRhZykpIHtcbiAgICAgIHZhciBwcm9wcyA9IG5vbkVudW1Qcm9wc1t0YWddO1xuICAgICAgcHJvcHNba2V5XSA9IGhhc093blByb3BlcnR5LmNhbGwocHJvcHMsIGtleSk7XG4gICAgfVxuICB9XG59KTtcblxuLyoqXG4gKiBDcmVhdGVzIGFuIGFycmF5IG9mIHRoZSBvd24gYW5kIGluaGVyaXRlZCBlbnVtZXJhYmxlIHByb3BlcnR5IG5hbWVzIG9mIGBvYmplY3RgLlxuICpcbiAqICoqTm90ZToqKiBOb24tb2JqZWN0IHZhbHVlcyBhcmUgY29lcmNlZCB0byBvYmplY3RzLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAY2F0ZWdvcnkgT2JqZWN0XG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gcXVlcnkuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIGFycmF5IG9mIHByb3BlcnR5IG5hbWVzLlxuICogQGV4YW1wbGVcbiAqXG4gKiBmdW5jdGlvbiBGb28oKSB7XG4gKiAgIHRoaXMuYSA9IDE7XG4gKiAgIHRoaXMuYiA9IDI7XG4gKiB9XG4gKlxuICogRm9vLnByb3RvdHlwZS5jID0gMztcbiAqXG4gKiBfLmtleXNJbihuZXcgRm9vKTtcbiAqIC8vID0+IFsnYScsICdiJywgJ2MnXSAoaXRlcmF0aW9uIG9yZGVyIGlzIG5vdCBndWFyYW50ZWVkKVxuICovXG5mdW5jdGlvbiBrZXlzSW4ob2JqZWN0KSB7XG4gIGlmIChvYmplY3QgPT0gbnVsbCkge1xuICAgIHJldHVybiBbXTtcbiAgfVxuICBpZiAoIWlzT2JqZWN0KG9iamVjdCkpIHtcbiAgICBvYmplY3QgPSBPYmplY3Qob2JqZWN0KTtcbiAgfVxuICB2YXIgbGVuZ3RoID0gb2JqZWN0Lmxlbmd0aDtcblxuICBsZW5ndGggPSAobGVuZ3RoICYmIGlzTGVuZ3RoKGxlbmd0aCkgJiZcbiAgICAoaXNBcnJheShvYmplY3QpIHx8IGlzQXJndW1lbnRzKG9iamVjdCkgfHwgaXNTdHJpbmcob2JqZWN0KSkgJiYgbGVuZ3RoKSB8fCAwO1xuXG4gIHZhciBDdG9yID0gb2JqZWN0LmNvbnN0cnVjdG9yLFxuICAgICAgaW5kZXggPSAtMSxcbiAgICAgIHByb3RvID0gKGlzRnVuY3Rpb24oQ3RvcikgJiYgQ3Rvci5wcm90b3R5cGUpIHx8IG9iamVjdFByb3RvLFxuICAgICAgaXNQcm90byA9IHByb3RvID09PSBvYmplY3QsXG4gICAgICByZXN1bHQgPSBBcnJheShsZW5ndGgpLFxuICAgICAgc2tpcEluZGV4ZXMgPSBsZW5ndGggPiAwLFxuICAgICAgc2tpcEVycm9yUHJvcHMgPSBzdXBwb3J0LmVudW1FcnJvclByb3BzICYmIChvYmplY3QgPT09IGVycm9yUHJvdG8gfHwgb2JqZWN0IGluc3RhbmNlb2YgRXJyb3IpLFxuICAgICAgc2tpcFByb3RvID0gc3VwcG9ydC5lbnVtUHJvdG90eXBlcyAmJiBpc0Z1bmN0aW9uKG9iamVjdCk7XG5cbiAgd2hpbGUgKCsraW5kZXggPCBsZW5ndGgpIHtcbiAgICByZXN1bHRbaW5kZXhdID0gKGluZGV4ICsgJycpO1xuICB9XG4gIC8vIGxvZGFzaCBza2lwcyB0aGUgYGNvbnN0cnVjdG9yYCBwcm9wZXJ0eSB3aGVuIGl0IGluZmVycyBpdCdzIGl0ZXJhdGluZ1xuICAvLyBvdmVyIGEgYHByb3RvdHlwZWAgb2JqZWN0IGJlY2F1c2UgSUUgPCA5IGNhbid0IHNldCB0aGUgYFtbRW51bWVyYWJsZV1dYFxuICAvLyBhdHRyaWJ1dGUgb2YgYW4gZXhpc3RpbmcgcHJvcGVydHkgYW5kIHRoZSBgY29uc3RydWN0b3JgIHByb3BlcnR5IG9mIGFcbiAgLy8gcHJvdG90eXBlIGRlZmF1bHRzIHRvIG5vbi1lbnVtZXJhYmxlLlxuICBmb3IgKHZhciBrZXkgaW4gb2JqZWN0KSB7XG4gICAgaWYgKCEoc2tpcFByb3RvICYmIGtleSA9PSAncHJvdG90eXBlJykgJiZcbiAgICAgICAgIShza2lwRXJyb3JQcm9wcyAmJiAoa2V5ID09ICdtZXNzYWdlJyB8fCBrZXkgPT0gJ25hbWUnKSkgJiZcbiAgICAgICAgIShza2lwSW5kZXhlcyAmJiBpc0luZGV4KGtleSwgbGVuZ3RoKSkgJiZcbiAgICAgICAgIShrZXkgPT0gJ2NvbnN0cnVjdG9yJyAmJiAoaXNQcm90byB8fCAhaGFzT3duUHJvcGVydHkuY2FsbChvYmplY3QsIGtleSkpKSkge1xuICAgICAgcmVzdWx0LnB1c2goa2V5KTtcbiAgICB9XG4gIH1cbiAgaWYgKHN1cHBvcnQubm9uRW51bVNoYWRvd3MgJiYgb2JqZWN0ICE9PSBvYmplY3RQcm90bykge1xuICAgIHZhciB0YWcgPSBvYmplY3QgPT09IHN0cmluZ1Byb3RvID8gc3RyaW5nVGFnIDogKG9iamVjdCA9PT0gZXJyb3JQcm90byA/IGVycm9yVGFnIDogb2JqVG9TdHJpbmcuY2FsbChvYmplY3QpKSxcbiAgICAgICAgbm9uRW51bXMgPSBub25FbnVtUHJvcHNbdGFnXSB8fCBub25FbnVtUHJvcHNbb2JqZWN0VGFnXTtcblxuICAgIGlmICh0YWcgPT0gb2JqZWN0VGFnKSB7XG4gICAgICBwcm90byA9IG9iamVjdFByb3RvO1xuICAgIH1cbiAgICBsZW5ndGggPSBzaGFkb3dQcm9wcy5sZW5ndGg7XG4gICAgd2hpbGUgKGxlbmd0aC0tKSB7XG4gICAgICBrZXkgPSBzaGFkb3dQcm9wc1tsZW5ndGhdO1xuICAgICAgdmFyIG5vbkVudW0gPSBub25FbnVtc1trZXldO1xuICAgICAgaWYgKCEoaXNQcm90byAmJiBub25FbnVtKSAmJlxuICAgICAgICAgIChub25FbnVtID8gaGFzT3duUHJvcGVydHkuY2FsbChvYmplY3QsIGtleSkgOiBvYmplY3Rba2V5XSAhPT0gcHJvdG9ba2V5XSkpIHtcbiAgICAgICAgcmVzdWx0LnB1c2goa2V5KTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBrZXlzSW47XG4iLCJ2YXIga2V5cyA9IHJlcXVpcmUoJy4va2V5cycpLFxuICAgIHRvT2JqZWN0ID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvdG9PYmplY3QnKTtcblxuLyoqXG4gKiBDcmVhdGVzIGEgdHdvIGRpbWVuc2lvbmFsIGFycmF5IG9mIHRoZSBrZXktdmFsdWUgcGFpcnMgZm9yIGBvYmplY3RgLFxuICogZS5nLiBgW1trZXkxLCB2YWx1ZTFdLCBba2V5MiwgdmFsdWUyXV1gLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAY2F0ZWdvcnkgT2JqZWN0XG4gKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgdG8gcXVlcnkuXG4gKiBAcmV0dXJucyB7QXJyYXl9IFJldHVybnMgdGhlIG5ldyBhcnJheSBvZiBrZXktdmFsdWUgcGFpcnMuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8ucGFpcnMoeyAnYmFybmV5JzogMzYsICdmcmVkJzogNDAgfSk7XG4gKiAvLyA9PiBbWydiYXJuZXknLCAzNl0sIFsnZnJlZCcsIDQwXV0gKGl0ZXJhdGlvbiBvcmRlciBpcyBub3QgZ3VhcmFudGVlZClcbiAqL1xuZnVuY3Rpb24gcGFpcnMob2JqZWN0KSB7XG4gIG9iamVjdCA9IHRvT2JqZWN0KG9iamVjdCk7XG5cbiAgdmFyIGluZGV4ID0gLTEsXG4gICAgICBwcm9wcyA9IGtleXMob2JqZWN0KSxcbiAgICAgIGxlbmd0aCA9IHByb3BzLmxlbmd0aCxcbiAgICAgIHJlc3VsdCA9IEFycmF5KGxlbmd0aCk7XG5cbiAgd2hpbGUgKCsraW5kZXggPCBsZW5ndGgpIHtcbiAgICB2YXIga2V5ID0gcHJvcHNbaW5kZXhdO1xuICAgIHJlc3VsdFtpbmRleF0gPSBba2V5LCBvYmplY3Rba2V5XV07XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBwYWlycztcbiIsInZhciBiYXNlVmFsdWVzID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvYmFzZVZhbHVlcycpLFxuICAgIGtleXMgPSByZXF1aXJlKCcuL2tleXMnKTtcblxuLyoqXG4gKiBDcmVhdGVzIGFuIGFycmF5IG9mIHRoZSBvd24gZW51bWVyYWJsZSBwcm9wZXJ0eSB2YWx1ZXMgb2YgYG9iamVjdGAuXG4gKlxuICogKipOb3RlOioqIE5vbi1vYmplY3QgdmFsdWVzIGFyZSBjb2VyY2VkIHRvIG9iamVjdHMuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBjYXRlZ29yeSBPYmplY3RcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmplY3QgVGhlIG9iamVjdCB0byBxdWVyeS5cbiAqIEByZXR1cm5zIHtBcnJheX0gUmV0dXJucyB0aGUgYXJyYXkgb2YgcHJvcGVydHkgdmFsdWVzLlxuICogQGV4YW1wbGVcbiAqXG4gKiBmdW5jdGlvbiBGb28oKSB7XG4gKiAgIHRoaXMuYSA9IDE7XG4gKiAgIHRoaXMuYiA9IDI7XG4gKiB9XG4gKlxuICogRm9vLnByb3RvdHlwZS5jID0gMztcbiAqXG4gKiBfLnZhbHVlcyhuZXcgRm9vKTtcbiAqIC8vID0+IFsxLCAyXSAoaXRlcmF0aW9uIG9yZGVyIGlzIG5vdCBndWFyYW50ZWVkKVxuICpcbiAqIF8udmFsdWVzKCdoaScpO1xuICogLy8gPT4gWydoJywgJ2knXVxuICovXG5mdW5jdGlvbiB2YWx1ZXMob2JqZWN0KSB7XG4gIHJldHVybiBiYXNlVmFsdWVzKG9iamVjdCwga2V5cyhvYmplY3QpKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB2YWx1ZXM7XG4iLCIvKiogVXNlZCBmb3IgbmF0aXZlIG1ldGhvZCByZWZlcmVuY2VzLiAqL1xudmFyIGFycmF5UHJvdG8gPSBBcnJheS5wcm90b3R5cGUsXG4gICAgZXJyb3JQcm90byA9IEVycm9yLnByb3RvdHlwZSxcbiAgICBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKiBOYXRpdmUgbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgcHJvcGVydHlJc0VudW1lcmFibGUgPSBvYmplY3RQcm90by5wcm9wZXJ0eUlzRW51bWVyYWJsZSxcbiAgICBzcGxpY2UgPSBhcnJheVByb3RvLnNwbGljZTtcblxuLyoqXG4gKiBBbiBvYmplY3QgZW52aXJvbm1lbnQgZmVhdHVyZSBmbGFncy5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHR5cGUgT2JqZWN0XG4gKi9cbnZhciBzdXBwb3J0ID0ge307XG5cbihmdW5jdGlvbih4KSB7XG4gIHZhciBDdG9yID0gZnVuY3Rpb24oKSB7IHRoaXMueCA9IHg7IH0sXG4gICAgICBvYmplY3QgPSB7ICcwJzogeCwgJ2xlbmd0aCc6IHggfSxcbiAgICAgIHByb3BzID0gW107XG5cbiAgQ3Rvci5wcm90b3R5cGUgPSB7ICd2YWx1ZU9mJzogeCwgJ3knOiB4IH07XG4gIGZvciAodmFyIGtleSBpbiBuZXcgQ3RvcikgeyBwcm9wcy5wdXNoKGtleSk7IH1cblxuICAvKipcbiAgICogRGV0ZWN0IGlmIGBuYW1lYCBvciBgbWVzc2FnZWAgcHJvcGVydGllcyBvZiBgRXJyb3IucHJvdG90eXBlYCBhcmVcbiAgICogZW51bWVyYWJsZSBieSBkZWZhdWx0IChJRSA8IDksIFNhZmFyaSA8IDUuMSkuXG4gICAqXG4gICAqIEBtZW1iZXJPZiBfLnN1cHBvcnRcbiAgICogQHR5cGUgYm9vbGVhblxuICAgKi9cbiAgc3VwcG9ydC5lbnVtRXJyb3JQcm9wcyA9IHByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwoZXJyb3JQcm90bywgJ21lc3NhZ2UnKSB8fFxuICAgIHByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwoZXJyb3JQcm90bywgJ25hbWUnKTtcblxuICAvKipcbiAgICogRGV0ZWN0IGlmIGBwcm90b3R5cGVgIHByb3BlcnRpZXMgYXJlIGVudW1lcmFibGUgYnkgZGVmYXVsdC5cbiAgICpcbiAgICogRmlyZWZveCA8IDMuNiwgT3BlcmEgPiA5LjUwIC0gT3BlcmEgPCAxMS42MCwgYW5kIFNhZmFyaSA8IDUuMVxuICAgKiAoaWYgdGhlIHByb3RvdHlwZSBvciBhIHByb3BlcnR5IG9uIHRoZSBwcm90b3R5cGUgaGFzIGJlZW4gc2V0KVxuICAgKiBpbmNvcnJlY3RseSBzZXQgdGhlIGBbW0VudW1lcmFibGVdXWAgdmFsdWUgb2YgYSBmdW5jdGlvbidzIGBwcm90b3R5cGVgXG4gICAqIHByb3BlcnR5IHRvIGB0cnVlYC5cbiAgICpcbiAgICogQG1lbWJlck9mIF8uc3VwcG9ydFxuICAgKiBAdHlwZSBib29sZWFuXG4gICAqL1xuICBzdXBwb3J0LmVudW1Qcm90b3R5cGVzID0gcHJvcGVydHlJc0VudW1lcmFibGUuY2FsbChDdG9yLCAncHJvdG90eXBlJyk7XG5cbiAgLyoqXG4gICAqIERldGVjdCBpZiBwcm9wZXJ0aWVzIHNoYWRvd2luZyB0aG9zZSBvbiBgT2JqZWN0LnByb3RvdHlwZWAgYXJlIG5vbi1lbnVtZXJhYmxlLlxuICAgKlxuICAgKiBJbiBJRSA8IDkgYW4gb2JqZWN0J3Mgb3duIHByb3BlcnRpZXMsIHNoYWRvd2luZyBub24tZW51bWVyYWJsZSBvbmVzLFxuICAgKiBhcmUgbWFkZSBub24tZW51bWVyYWJsZSBhcyB3ZWxsIChhLmsuYSB0aGUgSlNjcmlwdCBgW1tEb250RW51bV1dYCBidWcpLlxuICAgKlxuICAgKiBAbWVtYmVyT2YgXy5zdXBwb3J0XG4gICAqIEB0eXBlIGJvb2xlYW5cbiAgICovXG4gIHN1cHBvcnQubm9uRW51bVNoYWRvd3MgPSAhL3ZhbHVlT2YvLnRlc3QocHJvcHMpO1xuXG4gIC8qKlxuICAgKiBEZXRlY3QgaWYgb3duIHByb3BlcnRpZXMgYXJlIGl0ZXJhdGVkIGFmdGVyIGluaGVyaXRlZCBwcm9wZXJ0aWVzIChJRSA8IDkpLlxuICAgKlxuICAgKiBAbWVtYmVyT2YgXy5zdXBwb3J0XG4gICAqIEB0eXBlIGJvb2xlYW5cbiAgICovXG4gIHN1cHBvcnQub3duTGFzdCA9IHByb3BzWzBdICE9ICd4JztcblxuICAvKipcbiAgICogRGV0ZWN0IGlmIGBBcnJheSNzaGlmdGAgYW5kIGBBcnJheSNzcGxpY2VgIGF1Z21lbnQgYXJyYXktbGlrZSBvYmplY3RzXG4gICAqIGNvcnJlY3RseS5cbiAgICpcbiAgICogRmlyZWZveCA8IDEwLCBjb21wYXRpYmlsaXR5IG1vZGVzIG9mIElFIDgsIGFuZCBJRSA8IDkgaGF2ZSBidWdneSBBcnJheVxuICAgKiBgc2hpZnQoKWAgYW5kIGBzcGxpY2UoKWAgZnVuY3Rpb25zIHRoYXQgZmFpbCB0byByZW1vdmUgdGhlIGxhc3QgZWxlbWVudCxcbiAgICogYHZhbHVlWzBdYCwgb2YgYXJyYXktbGlrZSBvYmplY3RzIGV2ZW4gdGhvdWdoIHRoZSBcImxlbmd0aFwiIHByb3BlcnR5IGlzXG4gICAqIHNldCB0byBgMGAuIFRoZSBgc2hpZnQoKWAgbWV0aG9kIGlzIGJ1Z2d5IGluIGNvbXBhdGliaWxpdHkgbW9kZXMgb2YgSUUgOCxcbiAgICogd2hpbGUgYHNwbGljZSgpYCBpcyBidWdneSByZWdhcmRsZXNzIG9mIG1vZGUgaW4gSUUgPCA5LlxuICAgKlxuICAgKiBAbWVtYmVyT2YgXy5zdXBwb3J0XG4gICAqIEB0eXBlIGJvb2xlYW5cbiAgICovXG4gIHN1cHBvcnQuc3BsaWNlT2JqZWN0cyA9IChzcGxpY2UuY2FsbChvYmplY3QsIDAsIDEpLCAhb2JqZWN0WzBdKTtcblxuICAvKipcbiAgICogRGV0ZWN0IGxhY2sgb2Ygc3VwcG9ydCBmb3IgYWNjZXNzaW5nIHN0cmluZyBjaGFyYWN0ZXJzIGJ5IGluZGV4LlxuICAgKlxuICAgKiBJRSA8IDggY2FuJ3QgYWNjZXNzIGNoYXJhY3RlcnMgYnkgaW5kZXguIElFIDggY2FuIG9ubHkgYWNjZXNzIGNoYXJhY3RlcnNcbiAgICogYnkgaW5kZXggb24gc3RyaW5nIGxpdGVyYWxzLCBub3Qgc3RyaW5nIG9iamVjdHMuXG4gICAqXG4gICAqIEBtZW1iZXJPZiBfLnN1cHBvcnRcbiAgICogQHR5cGUgYm9vbGVhblxuICAgKi9cbiAgc3VwcG9ydC51bmluZGV4ZWRDaGFycyA9ICgneCdbMF0gKyBPYmplY3QoJ3gnKVswXSkgIT0gJ3h4Jztcbn0oMSwgMCkpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IHN1cHBvcnQ7XG4iLCIvKipcbiAqIFRoaXMgbWV0aG9kIHJldHVybnMgdGhlIGZpcnN0IGFyZ3VtZW50IHByb3ZpZGVkIHRvIGl0LlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAY2F0ZWdvcnkgVXRpbGl0eVxuICogQHBhcmFtIHsqfSB2YWx1ZSBBbnkgdmFsdWUuXG4gKiBAcmV0dXJucyB7Kn0gUmV0dXJucyBgdmFsdWVgLlxuICogQGV4YW1wbGVcbiAqXG4gKiB2YXIgb2JqZWN0ID0geyAndXNlcic6ICdmcmVkJyB9O1xuICpcbiAqIF8uaWRlbnRpdHkob2JqZWN0KSA9PT0gb2JqZWN0O1xuICogLy8gPT4gdHJ1ZVxuICovXG5mdW5jdGlvbiBpZGVudGl0eSh2YWx1ZSkge1xuICByZXR1cm4gdmFsdWU7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaWRlbnRpdHk7XG4iLCIvKipcbiAqIEEgbm8tb3BlcmF0aW9uIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyBgdW5kZWZpbmVkYCByZWdhcmRsZXNzIG9mIHRoZVxuICogYXJndW1lbnRzIGl0IHJlY2VpdmVzLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAY2F0ZWdvcnkgVXRpbGl0eVxuICogQGV4YW1wbGVcbiAqXG4gKiB2YXIgb2JqZWN0ID0geyAndXNlcic6ICdmcmVkJyB9O1xuICpcbiAqIF8ubm9vcChvYmplY3QpID09PSB1bmRlZmluZWQ7XG4gKiAvLyA9PiB0cnVlXG4gKi9cbmZ1bmN0aW9uIG5vb3AoKSB7XG4gIC8vIE5vIG9wZXJhdGlvbiBwZXJmb3JtZWQuXG59XG5cbm1vZHVsZS5leHBvcnRzID0gbm9vcDtcbiIsInZhciBiYXNlUHJvcGVydHkgPSByZXF1aXJlKCcuLi9pbnRlcm5hbC9iYXNlUHJvcGVydHknKSxcbiAgICBiYXNlUHJvcGVydHlEZWVwID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvYmFzZVByb3BlcnR5RGVlcCcpLFxuICAgIGlzS2V5ID0gcmVxdWlyZSgnLi4vaW50ZXJuYWwvaXNLZXknKTtcblxuLyoqXG4gKiBDcmVhdGVzIGEgZnVuY3Rpb24gdGhhdCByZXR1cm5zIHRoZSBwcm9wZXJ0eSB2YWx1ZSBhdCBgcGF0aGAgb24gYVxuICogZ2l2ZW4gb2JqZWN0LlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAY2F0ZWdvcnkgVXRpbGl0eVxuICogQHBhcmFtIHtBcnJheXxzdHJpbmd9IHBhdGggVGhlIHBhdGggb2YgdGhlIHByb3BlcnR5IHRvIGdldC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiB2YXIgb2JqZWN0cyA9IFtcbiAqICAgeyAnYSc6IHsgJ2InOiB7ICdjJzogMiB9IH0gfSxcbiAqICAgeyAnYSc6IHsgJ2InOiB7ICdjJzogMSB9IH0gfVxuICogXTtcbiAqXG4gKiBfLm1hcChvYmplY3RzLCBfLnByb3BlcnR5KCdhLmIuYycpKTtcbiAqIC8vID0+IFsyLCAxXVxuICpcbiAqIF8ucGx1Y2soXy5zb3J0Qnkob2JqZWN0cywgXy5wcm9wZXJ0eShbJ2EnLCAnYicsICdjJ10pKSwgJ2EuYi5jJyk7XG4gKiAvLyA9PiBbMSwgMl1cbiAqL1xuZnVuY3Rpb24gcHJvcGVydHkocGF0aCkge1xuICByZXR1cm4gaXNLZXkocGF0aCkgPyBiYXNlUHJvcGVydHkocGF0aCkgOiBiYXNlUHJvcGVydHlEZWVwKHBhdGgpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHByb3BlcnR5O1xuIiwiKGZ1bmN0aW9uIChwcm9jZXNzKXtcbi8vIHZpbTp0cz00OnN0cz00OnN3PTQ6XG4vKiFcbiAqXG4gKiBDb3B5cmlnaHQgMjAwOS0yMDEyIEtyaXMgS293YWwgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBNSVRcbiAqIGxpY2Vuc2UgZm91bmQgYXQgaHR0cDovL2dpdGh1Yi5jb20va3Jpc2tvd2FsL3EvcmF3L21hc3Rlci9MSUNFTlNFXG4gKlxuICogV2l0aCBwYXJ0cyBieSBUeWxlciBDbG9zZVxuICogQ29weXJpZ2h0IDIwMDctMjAwOSBUeWxlciBDbG9zZSB1bmRlciB0aGUgdGVybXMgb2YgdGhlIE1JVCBYIGxpY2Vuc2UgZm91bmRcbiAqIGF0IGh0dHA6Ly93d3cub3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvbWl0LWxpY2Vuc2UuaHRtbFxuICogRm9ya2VkIGF0IHJlZl9zZW5kLmpzIHZlcnNpb246IDIwMDktMDUtMTFcbiAqXG4gKiBXaXRoIHBhcnRzIGJ5IE1hcmsgTWlsbGVyXG4gKiBDb3B5cmlnaHQgKEMpIDIwMTEgR29vZ2xlIEluYy5cbiAqXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgXCJMaWNlbnNlXCIpO1xuICogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLlxuICogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4gKlxuICogaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wXG4gKlxuICogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZVxuICogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gXCJBUyBJU1wiIEJBU0lTLFxuICogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuXG4gKiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kXG4gKiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS5cbiAqXG4gKi9cblxuKGZ1bmN0aW9uIChkZWZpbml0aW9uKSB7XG4gICAgXCJ1c2Ugc3RyaWN0XCI7XG5cbiAgICAvLyBUaGlzIGZpbGUgd2lsbCBmdW5jdGlvbiBwcm9wZXJseSBhcyBhIDxzY3JpcHQ+IHRhZywgb3IgYSBtb2R1bGVcbiAgICAvLyB1c2luZyBDb21tb25KUyBhbmQgTm9kZUpTIG9yIFJlcXVpcmVKUyBtb2R1bGUgZm9ybWF0cy4gIEluXG4gICAgLy8gQ29tbW9uL05vZGUvUmVxdWlyZUpTLCB0aGUgbW9kdWxlIGV4cG9ydHMgdGhlIFEgQVBJIGFuZCB3aGVuXG4gICAgLy8gZXhlY3V0ZWQgYXMgYSBzaW1wbGUgPHNjcmlwdD4sIGl0IGNyZWF0ZXMgYSBRIGdsb2JhbCBpbnN0ZWFkLlxuXG4gICAgLy8gTW9udGFnZSBSZXF1aXJlXG4gICAgaWYgKHR5cGVvZiBib290c3RyYXAgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICBib290c3RyYXAoXCJwcm9taXNlXCIsIGRlZmluaXRpb24pO1xuXG4gICAgLy8gQ29tbW9uSlNcbiAgICB9IGVsc2UgaWYgKHR5cGVvZiBleHBvcnRzID09PSBcIm9iamVjdFwiICYmIHR5cGVvZiBtb2R1bGUgPT09IFwib2JqZWN0XCIpIHtcbiAgICAgICAgbW9kdWxlLmV4cG9ydHMgPSBkZWZpbml0aW9uKCk7XG5cbiAgICAvLyBSZXF1aXJlSlNcbiAgICB9IGVsc2UgaWYgKHR5cGVvZiBkZWZpbmUgPT09IFwiZnVuY3Rpb25cIiAmJiBkZWZpbmUuYW1kKSB7XG4gICAgICAgIGRlZmluZShkZWZpbml0aW9uKTtcblxuICAgIC8vIFNFUyAoU2VjdXJlIEVjbWFTY3JpcHQpXG4gICAgfSBlbHNlIGlmICh0eXBlb2Ygc2VzICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgIGlmICghc2VzLm9rKCkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHNlcy5tYWtlUSA9IGRlZmluaXRpb247XG4gICAgICAgIH1cblxuICAgIC8vIDxzY3JpcHQ+XG4gICAgfSBlbHNlIGlmICh0eXBlb2Ygd2luZG93ICE9PSBcInVuZGVmaW5lZFwiIHx8IHR5cGVvZiBzZWxmICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgICAgIC8vIFByZWZlciB3aW5kb3cgb3ZlciBzZWxmIGZvciBhZGQtb24gc2NyaXB0cy4gVXNlIHNlbGYgZm9yXG4gICAgICAgIC8vIG5vbi13aW5kb3dlZCBjb250ZXh0cy5cbiAgICAgICAgdmFyIGdsb2JhbCA9IHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIgPyB3aW5kb3cgOiBzZWxmO1xuXG4gICAgICAgIC8vIEdldCB0aGUgYHdpbmRvd2Agb2JqZWN0LCBzYXZlIHRoZSBwcmV2aW91cyBRIGdsb2JhbFxuICAgICAgICAvLyBhbmQgaW5pdGlhbGl6ZSBRIGFzIGEgZ2xvYmFsLlxuICAgICAgICB2YXIgcHJldmlvdXNRID0gZ2xvYmFsLlE7XG4gICAgICAgIGdsb2JhbC5RID0gZGVmaW5pdGlvbigpO1xuXG4gICAgICAgIC8vIEFkZCBhIG5vQ29uZmxpY3QgZnVuY3Rpb24gc28gUSBjYW4gYmUgcmVtb3ZlZCBmcm9tIHRoZVxuICAgICAgICAvLyBnbG9iYWwgbmFtZXNwYWNlLlxuICAgICAgICBnbG9iYWwuUS5ub0NvbmZsaWN0ID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgZ2xvYmFsLlEgPSBwcmV2aW91c1E7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfTtcblxuICAgIH0gZWxzZSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIlRoaXMgZW52aXJvbm1lbnQgd2FzIG5vdCBhbnRpY2lwYXRlZCBieSBRLiBQbGVhc2UgZmlsZSBhIGJ1Zy5cIik7XG4gICAgfVxuXG59KShmdW5jdGlvbiAoKSB7XG5cInVzZSBzdHJpY3RcIjtcblxudmFyIGhhc1N0YWNrcyA9IGZhbHNlO1xudHJ5IHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoKTtcbn0gY2F0Y2ggKGUpIHtcbiAgICBoYXNTdGFja3MgPSAhIWUuc3RhY2s7XG59XG5cbi8vIEFsbCBjb2RlIGFmdGVyIHRoaXMgcG9pbnQgd2lsbCBiZSBmaWx0ZXJlZCBmcm9tIHN0YWNrIHRyYWNlcyByZXBvcnRlZFxuLy8gYnkgUS5cbnZhciBxU3RhcnRpbmdMaW5lID0gY2FwdHVyZUxpbmUoKTtcbnZhciBxRmlsZU5hbWU7XG5cbi8vIHNoaW1zXG5cbi8vIHVzZWQgZm9yIGZhbGxiYWNrIGluIFwiYWxsUmVzb2x2ZWRcIlxudmFyIG5vb3AgPSBmdW5jdGlvbiAoKSB7fTtcblxuLy8gVXNlIHRoZSBmYXN0ZXN0IHBvc3NpYmxlIG1lYW5zIHRvIGV4ZWN1dGUgYSB0YXNrIGluIGEgZnV0dXJlIHR1cm5cbi8vIG9mIHRoZSBldmVudCBsb29wLlxudmFyIG5leHRUaWNrID0oZnVuY3Rpb24gKCkge1xuICAgIC8vIGxpbmtlZCBsaXN0IG9mIHRhc2tzIChzaW5nbGUsIHdpdGggaGVhZCBub2RlKVxuICAgIHZhciBoZWFkID0ge3Rhc2s6IHZvaWQgMCwgbmV4dDogbnVsbH07XG4gICAgdmFyIHRhaWwgPSBoZWFkO1xuICAgIHZhciBmbHVzaGluZyA9IGZhbHNlO1xuICAgIHZhciByZXF1ZXN0VGljayA9IHZvaWQgMDtcbiAgICB2YXIgaXNOb2RlSlMgPSBmYWxzZTtcbiAgICAvLyBxdWV1ZSBmb3IgbGF0ZSB0YXNrcywgdXNlZCBieSB1bmhhbmRsZWQgcmVqZWN0aW9uIHRyYWNraW5nXG4gICAgdmFyIGxhdGVyUXVldWUgPSBbXTtcblxuICAgIGZ1bmN0aW9uIGZsdXNoKCkge1xuICAgICAgICAvKiBqc2hpbnQgbG9vcGZ1bmM6IHRydWUgKi9cbiAgICAgICAgdmFyIHRhc2ssIGRvbWFpbjtcblxuICAgICAgICB3aGlsZSAoaGVhZC5uZXh0KSB7XG4gICAgICAgICAgICBoZWFkID0gaGVhZC5uZXh0O1xuICAgICAgICAgICAgdGFzayA9IGhlYWQudGFzaztcbiAgICAgICAgICAgIGhlYWQudGFzayA9IHZvaWQgMDtcbiAgICAgICAgICAgIGRvbWFpbiA9IGhlYWQuZG9tYWluO1xuXG4gICAgICAgICAgICBpZiAoZG9tYWluKSB7XG4gICAgICAgICAgICAgICAgaGVhZC5kb21haW4gPSB2b2lkIDA7XG4gICAgICAgICAgICAgICAgZG9tYWluLmVudGVyKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBydW5TaW5nbGUodGFzaywgZG9tYWluKTtcblxuICAgICAgICB9XG4gICAgICAgIHdoaWxlIChsYXRlclF1ZXVlLmxlbmd0aCkge1xuICAgICAgICAgICAgdGFzayA9IGxhdGVyUXVldWUucG9wKCk7XG4gICAgICAgICAgICBydW5TaW5nbGUodGFzayk7XG4gICAgICAgIH1cbiAgICAgICAgZmx1c2hpbmcgPSBmYWxzZTtcbiAgICB9XG4gICAgLy8gcnVucyBhIHNpbmdsZSBmdW5jdGlvbiBpbiB0aGUgYXN5bmMgcXVldWVcbiAgICBmdW5jdGlvbiBydW5TaW5nbGUodGFzaywgZG9tYWluKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICB0YXNrKCk7XG5cbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgaWYgKGlzTm9kZUpTKSB7XG4gICAgICAgICAgICAgICAgLy8gSW4gbm9kZSwgdW5jYXVnaHQgZXhjZXB0aW9ucyBhcmUgY29uc2lkZXJlZCBmYXRhbCBlcnJvcnMuXG4gICAgICAgICAgICAgICAgLy8gUmUtdGhyb3cgdGhlbSBzeW5jaHJvbm91c2x5IHRvIGludGVycnVwdCBmbHVzaGluZyFcblxuICAgICAgICAgICAgICAgIC8vIEVuc3VyZSBjb250aW51YXRpb24gaWYgdGhlIHVuY2F1Z2h0IGV4Y2VwdGlvbiBpcyBzdXBwcmVzc2VkXG4gICAgICAgICAgICAgICAgLy8gbGlzdGVuaW5nIFwidW5jYXVnaHRFeGNlcHRpb25cIiBldmVudHMgKGFzIGRvbWFpbnMgZG9lcykuXG4gICAgICAgICAgICAgICAgLy8gQ29udGludWUgaW4gbmV4dCBldmVudCB0byBhdm9pZCB0aWNrIHJlY3Vyc2lvbi5cbiAgICAgICAgICAgICAgICBpZiAoZG9tYWluKSB7XG4gICAgICAgICAgICAgICAgICAgIGRvbWFpbi5leGl0KCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHNldFRpbWVvdXQoZmx1c2gsIDApO1xuICAgICAgICAgICAgICAgIGlmIChkb21haW4pIHtcbiAgICAgICAgICAgICAgICAgICAgZG9tYWluLmVudGVyKCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgdGhyb3cgZTtcblxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAvLyBJbiBicm93c2VycywgdW5jYXVnaHQgZXhjZXB0aW9ucyBhcmUgbm90IGZhdGFsLlxuICAgICAgICAgICAgICAgIC8vIFJlLXRocm93IHRoZW0gYXN5bmNocm9ub3VzbHkgdG8gYXZvaWQgc2xvdy1kb3ducy5cbiAgICAgICAgICAgICAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgZTtcbiAgICAgICAgICAgICAgICB9LCAwKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChkb21haW4pIHtcbiAgICAgICAgICAgIGRvbWFpbi5leGl0KCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBuZXh0VGljayA9IGZ1bmN0aW9uICh0YXNrKSB7XG4gICAgICAgIHRhaWwgPSB0YWlsLm5leHQgPSB7XG4gICAgICAgICAgICB0YXNrOiB0YXNrLFxuICAgICAgICAgICAgZG9tYWluOiBpc05vZGVKUyAmJiBwcm9jZXNzLmRvbWFpbixcbiAgICAgICAgICAgIG5leHQ6IG51bGxcbiAgICAgICAgfTtcblxuICAgICAgICBpZiAoIWZsdXNoaW5nKSB7XG4gICAgICAgICAgICBmbHVzaGluZyA9IHRydWU7XG4gICAgICAgICAgICByZXF1ZXN0VGljaygpO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIGlmICh0eXBlb2YgcHJvY2VzcyA9PT0gXCJvYmplY3RcIiAmJlxuICAgICAgICBwcm9jZXNzLnRvU3RyaW5nKCkgPT09IFwiW29iamVjdCBwcm9jZXNzXVwiICYmIHByb2Nlc3MubmV4dFRpY2spIHtcbiAgICAgICAgLy8gRW5zdXJlIFEgaXMgaW4gYSByZWFsIE5vZGUgZW52aXJvbm1lbnQsIHdpdGggYSBgcHJvY2Vzcy5uZXh0VGlja2AuXG4gICAgICAgIC8vIFRvIHNlZSB0aHJvdWdoIGZha2UgTm9kZSBlbnZpcm9ubWVudHM6XG4gICAgICAgIC8vICogTW9jaGEgdGVzdCBydW5uZXIgLSBleHBvc2VzIGEgYHByb2Nlc3NgIGdsb2JhbCB3aXRob3V0IGEgYG5leHRUaWNrYFxuICAgICAgICAvLyAqIEJyb3dzZXJpZnkgLSBleHBvc2VzIGEgYHByb2Nlc3MubmV4VGlja2AgZnVuY3Rpb24gdGhhdCB1c2VzXG4gICAgICAgIC8vICAgYHNldFRpbWVvdXRgLiBJbiB0aGlzIGNhc2UgYHNldEltbWVkaWF0ZWAgaXMgcHJlZmVycmVkIGJlY2F1c2VcbiAgICAgICAgLy8gICAgaXQgaXMgZmFzdGVyLiBCcm93c2VyaWZ5J3MgYHByb2Nlc3MudG9TdHJpbmcoKWAgeWllbGRzXG4gICAgICAgIC8vICAgXCJbb2JqZWN0IE9iamVjdF1cIiwgd2hpbGUgaW4gYSByZWFsIE5vZGUgZW52aXJvbm1lbnRcbiAgICAgICAgLy8gICBgcHJvY2Vzcy5uZXh0VGljaygpYCB5aWVsZHMgXCJbb2JqZWN0IHByb2Nlc3NdXCIuXG4gICAgICAgIGlzTm9kZUpTID0gdHJ1ZTtcblxuICAgICAgICByZXF1ZXN0VGljayA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHByb2Nlc3MubmV4dFRpY2soZmx1c2gpO1xuICAgICAgICB9O1xuXG4gICAgfSBlbHNlIGlmICh0eXBlb2Ygc2V0SW1tZWRpYXRlID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgLy8gSW4gSUUxMCwgTm9kZS5qcyAwLjkrLCBvciBodHRwczovL2dpdGh1Yi5jb20vTm9ibGVKUy9zZXRJbW1lZGlhdGVcbiAgICAgICAgaWYgKHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgIHJlcXVlc3RUaWNrID0gc2V0SW1tZWRpYXRlLmJpbmQod2luZG93LCBmbHVzaCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICByZXF1ZXN0VGljayA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICBzZXRJbW1lZGlhdGUoZmx1c2gpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfVxuXG4gICAgfSBlbHNlIGlmICh0eXBlb2YgTWVzc2FnZUNoYW5uZWwgIT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgLy8gbW9kZXJuIGJyb3dzZXJzXG4gICAgICAgIC8vIGh0dHA6Ly93d3cubm9uYmxvY2tpbmcuaW8vMjAxMS8wNi93aW5kb3duZXh0dGljay5odG1sXG4gICAgICAgIHZhciBjaGFubmVsID0gbmV3IE1lc3NhZ2VDaGFubmVsKCk7XG4gICAgICAgIC8vIEF0IGxlYXN0IFNhZmFyaSBWZXJzaW9uIDYuMC41ICg4NTM2LjMwLjEpIGludGVybWl0dGVudGx5IGNhbm5vdCBjcmVhdGVcbiAgICAgICAgLy8gd29ya2luZyBtZXNzYWdlIHBvcnRzIHRoZSBmaXJzdCB0aW1lIGEgcGFnZSBsb2Fkcy5cbiAgICAgICAgY2hhbm5lbC5wb3J0MS5vbm1lc3NhZ2UgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICByZXF1ZXN0VGljayA9IHJlcXVlc3RQb3J0VGljaztcbiAgICAgICAgICAgIGNoYW5uZWwucG9ydDEub25tZXNzYWdlID0gZmx1c2g7XG4gICAgICAgICAgICBmbHVzaCgpO1xuICAgICAgICB9O1xuICAgICAgICB2YXIgcmVxdWVzdFBvcnRUaWNrID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgLy8gT3BlcmEgcmVxdWlyZXMgdXMgdG8gcHJvdmlkZSBhIG1lc3NhZ2UgcGF5bG9hZCwgcmVnYXJkbGVzcyBvZlxuICAgICAgICAgICAgLy8gd2hldGhlciB3ZSB1c2UgaXQuXG4gICAgICAgICAgICBjaGFubmVsLnBvcnQyLnBvc3RNZXNzYWdlKDApO1xuICAgICAgICB9O1xuICAgICAgICByZXF1ZXN0VGljayA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHNldFRpbWVvdXQoZmx1c2gsIDApO1xuICAgICAgICAgICAgcmVxdWVzdFBvcnRUaWNrKCk7XG4gICAgICAgIH07XG5cbiAgICB9IGVsc2Uge1xuICAgICAgICAvLyBvbGQgYnJvd3NlcnNcbiAgICAgICAgcmVxdWVzdFRpY2sgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBzZXRUaW1lb3V0KGZsdXNoLCAwKTtcbiAgICAgICAgfTtcbiAgICB9XG4gICAgLy8gcnVucyBhIHRhc2sgYWZ0ZXIgYWxsIG90aGVyIHRhc2tzIGhhdmUgYmVlbiBydW5cbiAgICAvLyB0aGlzIGlzIHVzZWZ1bCBmb3IgdW5oYW5kbGVkIHJlamVjdGlvbiB0cmFja2luZyB0aGF0IG5lZWRzIHRvIGhhcHBlblxuICAgIC8vIGFmdGVyIGFsbCBgdGhlbmBkIHRhc2tzIGhhdmUgYmVlbiBydW4uXG4gICAgbmV4dFRpY2sucnVuQWZ0ZXIgPSBmdW5jdGlvbiAodGFzaykge1xuICAgICAgICBsYXRlclF1ZXVlLnB1c2godGFzayk7XG4gICAgICAgIGlmICghZmx1c2hpbmcpIHtcbiAgICAgICAgICAgIGZsdXNoaW5nID0gdHJ1ZTtcbiAgICAgICAgICAgIHJlcXVlc3RUaWNrKCk7XG4gICAgICAgIH1cbiAgICB9O1xuICAgIHJldHVybiBuZXh0VGljaztcbn0pKCk7XG5cbi8vIEF0dGVtcHQgdG8gbWFrZSBnZW5lcmljcyBzYWZlIGluIHRoZSBmYWNlIG9mIGRvd25zdHJlYW1cbi8vIG1vZGlmaWNhdGlvbnMuXG4vLyBUaGVyZSBpcyBubyBzaXR1YXRpb24gd2hlcmUgdGhpcyBpcyBuZWNlc3NhcnkuXG4vLyBJZiB5b3UgbmVlZCBhIHNlY3VyaXR5IGd1YXJhbnRlZSwgdGhlc2UgcHJpbW9yZGlhbHMgbmVlZCB0byBiZVxuLy8gZGVlcGx5IGZyb3plbiBhbnl3YXksIGFuZCBpZiB5b3UgZG9u4oCZdCBuZWVkIGEgc2VjdXJpdHkgZ3VhcmFudGVlLFxuLy8gdGhpcyBpcyBqdXN0IHBsYWluIHBhcmFub2lkLlxuLy8gSG93ZXZlciwgdGhpcyAqKm1pZ2h0KiogaGF2ZSB0aGUgbmljZSBzaWRlLWVmZmVjdCBvZiByZWR1Y2luZyB0aGUgc2l6ZSBvZlxuLy8gdGhlIG1pbmlmaWVkIGNvZGUgYnkgcmVkdWNpbmcgeC5jYWxsKCkgdG8gbWVyZWx5IHgoKVxuLy8gU2VlIE1hcmsgTWlsbGVy4oCZcyBleHBsYW5hdGlvbiBvZiB3aGF0IHRoaXMgZG9lcy5cbi8vIGh0dHA6Ly93aWtpLmVjbWFzY3JpcHQub3JnL2Rva3UucGhwP2lkPWNvbnZlbnRpb25zOnNhZmVfbWV0YV9wcm9ncmFtbWluZ1xudmFyIGNhbGwgPSBGdW5jdGlvbi5jYWxsO1xuZnVuY3Rpb24gdW5jdXJyeVRoaXMoZikge1xuICAgIHJldHVybiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBjYWxsLmFwcGx5KGYsIGFyZ3VtZW50cyk7XG4gICAgfTtcbn1cbi8vIFRoaXMgaXMgZXF1aXZhbGVudCwgYnV0IHNsb3dlcjpcbi8vIHVuY3VycnlUaGlzID0gRnVuY3Rpb25fYmluZC5iaW5kKEZ1bmN0aW9uX2JpbmQuY2FsbCk7XG4vLyBodHRwOi8vanNwZXJmLmNvbS91bmN1cnJ5dGhpc1xuXG52YXIgYXJyYXlfc2xpY2UgPSB1bmN1cnJ5VGhpcyhBcnJheS5wcm90b3R5cGUuc2xpY2UpO1xuXG52YXIgYXJyYXlfcmVkdWNlID0gdW5jdXJyeVRoaXMoXG4gICAgQXJyYXkucHJvdG90eXBlLnJlZHVjZSB8fCBmdW5jdGlvbiAoY2FsbGJhY2ssIGJhc2lzKSB7XG4gICAgICAgIHZhciBpbmRleCA9IDAsXG4gICAgICAgICAgICBsZW5ndGggPSB0aGlzLmxlbmd0aDtcbiAgICAgICAgLy8gY29uY2VybmluZyB0aGUgaW5pdGlhbCB2YWx1ZSwgaWYgb25lIGlzIG5vdCBwcm92aWRlZFxuICAgICAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PT0gMSkge1xuICAgICAgICAgICAgLy8gc2VlayB0byB0aGUgZmlyc3QgdmFsdWUgaW4gdGhlIGFycmF5LCBhY2NvdW50aW5nXG4gICAgICAgICAgICAvLyBmb3IgdGhlIHBvc3NpYmlsaXR5IHRoYXQgaXMgaXMgYSBzcGFyc2UgYXJyYXlcbiAgICAgICAgICAgIGRvIHtcbiAgICAgICAgICAgICAgICBpZiAoaW5kZXggaW4gdGhpcykge1xuICAgICAgICAgICAgICAgICAgICBiYXNpcyA9IHRoaXNbaW5kZXgrK107XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAoKytpbmRleCA+PSBsZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcigpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gd2hpbGUgKDEpO1xuICAgICAgICB9XG4gICAgICAgIC8vIHJlZHVjZVxuICAgICAgICBmb3IgKDsgaW5kZXggPCBsZW5ndGg7IGluZGV4KyspIHtcbiAgICAgICAgICAgIC8vIGFjY291bnQgZm9yIHRoZSBwb3NzaWJpbGl0eSB0aGF0IHRoZSBhcnJheSBpcyBzcGFyc2VcbiAgICAgICAgICAgIGlmIChpbmRleCBpbiB0aGlzKSB7XG4gICAgICAgICAgICAgICAgYmFzaXMgPSBjYWxsYmFjayhiYXNpcywgdGhpc1tpbmRleF0sIGluZGV4KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gYmFzaXM7XG4gICAgfVxuKTtcblxudmFyIGFycmF5X2luZGV4T2YgPSB1bmN1cnJ5VGhpcyhcbiAgICBBcnJheS5wcm90b3R5cGUuaW5kZXhPZiB8fCBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgLy8gbm90IGEgdmVyeSBnb29kIHNoaW0sIGJ1dCBnb29kIGVub3VnaCBmb3Igb3VyIG9uZSB1c2Ugb2YgaXRcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpZiAodGhpc1tpXSA9PT0gdmFsdWUpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gaTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gLTE7XG4gICAgfVxuKTtcblxudmFyIGFycmF5X21hcCA9IHVuY3VycnlUaGlzKFxuICAgIEFycmF5LnByb3RvdHlwZS5tYXAgfHwgZnVuY3Rpb24gKGNhbGxiYWNrLCB0aGlzcCkge1xuICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgIHZhciBjb2xsZWN0ID0gW107XG4gICAgICAgIGFycmF5X3JlZHVjZShzZWxmLCBmdW5jdGlvbiAodW5kZWZpbmVkLCB2YWx1ZSwgaW5kZXgpIHtcbiAgICAgICAgICAgIGNvbGxlY3QucHVzaChjYWxsYmFjay5jYWxsKHRoaXNwLCB2YWx1ZSwgaW5kZXgsIHNlbGYpKTtcbiAgICAgICAgfSwgdm9pZCAwKTtcbiAgICAgICAgcmV0dXJuIGNvbGxlY3Q7XG4gICAgfVxuKTtcblxudmFyIG9iamVjdF9jcmVhdGUgPSBPYmplY3QuY3JlYXRlIHx8IGZ1bmN0aW9uIChwcm90b3R5cGUpIHtcbiAgICBmdW5jdGlvbiBUeXBlKCkgeyB9XG4gICAgVHlwZS5wcm90b3R5cGUgPSBwcm90b3R5cGU7XG4gICAgcmV0dXJuIG5ldyBUeXBlKCk7XG59O1xuXG52YXIgb2JqZWN0X2hhc093blByb3BlcnR5ID0gdW5jdXJyeVRoaXMoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eSk7XG5cbnZhciBvYmplY3Rfa2V5cyA9IE9iamVjdC5rZXlzIHx8IGZ1bmN0aW9uIChvYmplY3QpIHtcbiAgICB2YXIga2V5cyA9IFtdO1xuICAgIGZvciAodmFyIGtleSBpbiBvYmplY3QpIHtcbiAgICAgICAgaWYgKG9iamVjdF9oYXNPd25Qcm9wZXJ0eShvYmplY3QsIGtleSkpIHtcbiAgICAgICAgICAgIGtleXMucHVzaChrZXkpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiBrZXlzO1xufTtcblxudmFyIG9iamVjdF90b1N0cmluZyA9IHVuY3VycnlUaGlzKE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcpO1xuXG5mdW5jdGlvbiBpc09iamVjdCh2YWx1ZSkge1xuICAgIHJldHVybiB2YWx1ZSA9PT0gT2JqZWN0KHZhbHVlKTtcbn1cblxuLy8gZ2VuZXJhdG9yIHJlbGF0ZWQgc2hpbXNcblxuLy8gRklYTUU6IFJlbW92ZSB0aGlzIGZ1bmN0aW9uIG9uY2UgRVM2IGdlbmVyYXRvcnMgYXJlIGluIFNwaWRlck1vbmtleS5cbmZ1bmN0aW9uIGlzU3RvcEl0ZXJhdGlvbihleGNlcHRpb24pIHtcbiAgICByZXR1cm4gKFxuICAgICAgICBvYmplY3RfdG9TdHJpbmcoZXhjZXB0aW9uKSA9PT0gXCJbb2JqZWN0IFN0b3BJdGVyYXRpb25dXCIgfHxcbiAgICAgICAgZXhjZXB0aW9uIGluc3RhbmNlb2YgUVJldHVyblZhbHVlXG4gICAgKTtcbn1cblxuLy8gRklYTUU6IFJlbW92ZSB0aGlzIGhlbHBlciBhbmQgUS5yZXR1cm4gb25jZSBFUzYgZ2VuZXJhdG9ycyBhcmUgaW5cbi8vIFNwaWRlck1vbmtleS5cbnZhciBRUmV0dXJuVmFsdWU7XG5pZiAodHlwZW9mIFJldHVyblZhbHVlICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgUVJldHVyblZhbHVlID0gUmV0dXJuVmFsdWU7XG59IGVsc2Uge1xuICAgIFFSZXR1cm5WYWx1ZSA9IGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICB0aGlzLnZhbHVlID0gdmFsdWU7XG4gICAgfTtcbn1cblxuLy8gbG9uZyBzdGFjayB0cmFjZXNcblxudmFyIFNUQUNLX0pVTVBfU0VQQVJBVE9SID0gXCJGcm9tIHByZXZpb3VzIGV2ZW50OlwiO1xuXG5mdW5jdGlvbiBtYWtlU3RhY2tUcmFjZUxvbmcoZXJyb3IsIHByb21pc2UpIHtcbiAgICAvLyBJZiBwb3NzaWJsZSwgdHJhbnNmb3JtIHRoZSBlcnJvciBzdGFjayB0cmFjZSBieSByZW1vdmluZyBOb2RlIGFuZCBRXG4gICAgLy8gY3J1ZnQsIHRoZW4gY29uY2F0ZW5hdGluZyB3aXRoIHRoZSBzdGFjayB0cmFjZSBvZiBgcHJvbWlzZWAuIFNlZSAjNTcuXG4gICAgaWYgKGhhc1N0YWNrcyAmJlxuICAgICAgICBwcm9taXNlLnN0YWNrICYmXG4gICAgICAgIHR5cGVvZiBlcnJvciA9PT0gXCJvYmplY3RcIiAmJlxuICAgICAgICBlcnJvciAhPT0gbnVsbCAmJlxuICAgICAgICBlcnJvci5zdGFjayAmJlxuICAgICAgICBlcnJvci5zdGFjay5pbmRleE9mKFNUQUNLX0pVTVBfU0VQQVJBVE9SKSA9PT0gLTFcbiAgICApIHtcbiAgICAgICAgdmFyIHN0YWNrcyA9IFtdO1xuICAgICAgICBmb3IgKHZhciBwID0gcHJvbWlzZTsgISFwOyBwID0gcC5zb3VyY2UpIHtcbiAgICAgICAgICAgIGlmIChwLnN0YWNrKSB7XG4gICAgICAgICAgICAgICAgc3RhY2tzLnVuc2hpZnQocC5zdGFjayk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgc3RhY2tzLnVuc2hpZnQoZXJyb3Iuc3RhY2spO1xuXG4gICAgICAgIHZhciBjb25jYXRlZFN0YWNrcyA9IHN0YWNrcy5qb2luKFwiXFxuXCIgKyBTVEFDS19KVU1QX1NFUEFSQVRPUiArIFwiXFxuXCIpO1xuICAgICAgICBlcnJvci5zdGFjayA9IGZpbHRlclN0YWNrU3RyaW5nKGNvbmNhdGVkU3RhY2tzKTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGZpbHRlclN0YWNrU3RyaW5nKHN0YWNrU3RyaW5nKSB7XG4gICAgdmFyIGxpbmVzID0gc3RhY2tTdHJpbmcuc3BsaXQoXCJcXG5cIik7XG4gICAgdmFyIGRlc2lyZWRMaW5lcyA9IFtdO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbGluZXMubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgdmFyIGxpbmUgPSBsaW5lc1tpXTtcblxuICAgICAgICBpZiAoIWlzSW50ZXJuYWxGcmFtZShsaW5lKSAmJiAhaXNOb2RlRnJhbWUobGluZSkgJiYgbGluZSkge1xuICAgICAgICAgICAgZGVzaXJlZExpbmVzLnB1c2gobGluZSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGRlc2lyZWRMaW5lcy5qb2luKFwiXFxuXCIpO1xufVxuXG5mdW5jdGlvbiBpc05vZGVGcmFtZShzdGFja0xpbmUpIHtcbiAgICByZXR1cm4gc3RhY2tMaW5lLmluZGV4T2YoXCIobW9kdWxlLmpzOlwiKSAhPT0gLTEgfHxcbiAgICAgICAgICAgc3RhY2tMaW5lLmluZGV4T2YoXCIobm9kZS5qczpcIikgIT09IC0xO1xufVxuXG5mdW5jdGlvbiBnZXRGaWxlTmFtZUFuZExpbmVOdW1iZXIoc3RhY2tMaW5lKSB7XG4gICAgLy8gTmFtZWQgZnVuY3Rpb25zOiBcImF0IGZ1bmN0aW9uTmFtZSAoZmlsZW5hbWU6bGluZU51bWJlcjpjb2x1bW5OdW1iZXIpXCJcbiAgICAvLyBJbiBJRTEwIGZ1bmN0aW9uIG5hbWUgY2FuIGhhdmUgc3BhY2VzIChcIkFub255bW91cyBmdW5jdGlvblwiKSBPX29cbiAgICB2YXIgYXR0ZW1wdDEgPSAvYXQgLisgXFwoKC4rKTooXFxkKyk6KD86XFxkKylcXCkkLy5leGVjKHN0YWNrTGluZSk7XG4gICAgaWYgKGF0dGVtcHQxKSB7XG4gICAgICAgIHJldHVybiBbYXR0ZW1wdDFbMV0sIE51bWJlcihhdHRlbXB0MVsyXSldO1xuICAgIH1cblxuICAgIC8vIEFub255bW91cyBmdW5jdGlvbnM6IFwiYXQgZmlsZW5hbWU6bGluZU51bWJlcjpjb2x1bW5OdW1iZXJcIlxuICAgIHZhciBhdHRlbXB0MiA9IC9hdCAoW14gXSspOihcXGQrKTooPzpcXGQrKSQvLmV4ZWMoc3RhY2tMaW5lKTtcbiAgICBpZiAoYXR0ZW1wdDIpIHtcbiAgICAgICAgcmV0dXJuIFthdHRlbXB0MlsxXSwgTnVtYmVyKGF0dGVtcHQyWzJdKV07XG4gICAgfVxuXG4gICAgLy8gRmlyZWZveCBzdHlsZTogXCJmdW5jdGlvbkBmaWxlbmFtZTpsaW5lTnVtYmVyIG9yIEBmaWxlbmFtZTpsaW5lTnVtYmVyXCJcbiAgICB2YXIgYXR0ZW1wdDMgPSAvLipAKC4rKTooXFxkKykkLy5leGVjKHN0YWNrTGluZSk7XG4gICAgaWYgKGF0dGVtcHQzKSB7XG4gICAgICAgIHJldHVybiBbYXR0ZW1wdDNbMV0sIE51bWJlcihhdHRlbXB0M1syXSldO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gaXNJbnRlcm5hbEZyYW1lKHN0YWNrTGluZSkge1xuICAgIHZhciBmaWxlTmFtZUFuZExpbmVOdW1iZXIgPSBnZXRGaWxlTmFtZUFuZExpbmVOdW1iZXIoc3RhY2tMaW5lKTtcblxuICAgIGlmICghZmlsZU5hbWVBbmRMaW5lTnVtYmVyKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICB2YXIgZmlsZU5hbWUgPSBmaWxlTmFtZUFuZExpbmVOdW1iZXJbMF07XG4gICAgdmFyIGxpbmVOdW1iZXIgPSBmaWxlTmFtZUFuZExpbmVOdW1iZXJbMV07XG5cbiAgICByZXR1cm4gZmlsZU5hbWUgPT09IHFGaWxlTmFtZSAmJlxuICAgICAgICBsaW5lTnVtYmVyID49IHFTdGFydGluZ0xpbmUgJiZcbiAgICAgICAgbGluZU51bWJlciA8PSBxRW5kaW5nTGluZTtcbn1cblxuLy8gZGlzY292ZXIgb3duIGZpbGUgbmFtZSBhbmQgbGluZSBudW1iZXIgcmFuZ2UgZm9yIGZpbHRlcmluZyBzdGFja1xuLy8gdHJhY2VzXG5mdW5jdGlvbiBjYXB0dXJlTGluZSgpIHtcbiAgICBpZiAoIWhhc1N0YWNrcykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICB2YXIgbGluZXMgPSBlLnN0YWNrLnNwbGl0KFwiXFxuXCIpO1xuICAgICAgICB2YXIgZmlyc3RMaW5lID0gbGluZXNbMF0uaW5kZXhPZihcIkBcIikgPiAwID8gbGluZXNbMV0gOiBsaW5lc1syXTtcbiAgICAgICAgdmFyIGZpbGVOYW1lQW5kTGluZU51bWJlciA9IGdldEZpbGVOYW1lQW5kTGluZU51bWJlcihmaXJzdExpbmUpO1xuICAgICAgICBpZiAoIWZpbGVOYW1lQW5kTGluZU51bWJlcikge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgcUZpbGVOYW1lID0gZmlsZU5hbWVBbmRMaW5lTnVtYmVyWzBdO1xuICAgICAgICByZXR1cm4gZmlsZU5hbWVBbmRMaW5lTnVtYmVyWzFdO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gZGVwcmVjYXRlKGNhbGxiYWNrLCBuYW1lLCBhbHRlcm5hdGl2ZSkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGlmICh0eXBlb2YgY29uc29sZSAhPT0gXCJ1bmRlZmluZWRcIiAmJlxuICAgICAgICAgICAgdHlwZW9mIGNvbnNvbGUud2FybiA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICBjb25zb2xlLndhcm4obmFtZSArIFwiIGlzIGRlcHJlY2F0ZWQsIHVzZSBcIiArIGFsdGVybmF0aXZlICtcbiAgICAgICAgICAgICAgICAgICAgICAgICBcIiBpbnN0ZWFkLlwiLCBuZXcgRXJyb3IoXCJcIikuc3RhY2spO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBjYWxsYmFjay5hcHBseShjYWxsYmFjaywgYXJndW1lbnRzKTtcbiAgICB9O1xufVxuXG4vLyBlbmQgb2Ygc2hpbXNcbi8vIGJlZ2lubmluZyBvZiByZWFsIHdvcmtcblxuLyoqXG4gKiBDb25zdHJ1Y3RzIGEgcHJvbWlzZSBmb3IgYW4gaW1tZWRpYXRlIHJlZmVyZW5jZSwgcGFzc2VzIHByb21pc2VzIHRocm91Z2gsIG9yXG4gKiBjb2VyY2VzIHByb21pc2VzIGZyb20gZGlmZmVyZW50IHN5c3RlbXMuXG4gKiBAcGFyYW0gdmFsdWUgaW1tZWRpYXRlIHJlZmVyZW5jZSBvciBwcm9taXNlXG4gKi9cbmZ1bmN0aW9uIFEodmFsdWUpIHtcbiAgICAvLyBJZiB0aGUgb2JqZWN0IGlzIGFscmVhZHkgYSBQcm9taXNlLCByZXR1cm4gaXQgZGlyZWN0bHkuICBUaGlzIGVuYWJsZXNcbiAgICAvLyB0aGUgcmVzb2x2ZSBmdW5jdGlvbiB0byBib3RoIGJlIHVzZWQgdG8gY3JlYXRlZCByZWZlcmVuY2VzIGZyb20gb2JqZWN0cyxcbiAgICAvLyBidXQgdG8gdG9sZXJhYmx5IGNvZXJjZSBub24tcHJvbWlzZXMgdG8gcHJvbWlzZXMuXG4gICAgaWYgKHZhbHVlIGluc3RhbmNlb2YgUHJvbWlzZSkge1xuICAgICAgICByZXR1cm4gdmFsdWU7XG4gICAgfVxuXG4gICAgLy8gYXNzaW1pbGF0ZSB0aGVuYWJsZXNcbiAgICBpZiAoaXNQcm9taXNlQWxpa2UodmFsdWUpKSB7XG4gICAgICAgIHJldHVybiBjb2VyY2UodmFsdWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiBmdWxmaWxsKHZhbHVlKTtcbiAgICB9XG59XG5RLnJlc29sdmUgPSBRO1xuXG4vKipcbiAqIFBlcmZvcm1zIGEgdGFzayBpbiBhIGZ1dHVyZSB0dXJuIG9mIHRoZSBldmVudCBsb29wLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gdGFza1xuICovXG5RLm5leHRUaWNrID0gbmV4dFRpY2s7XG5cbi8qKlxuICogQ29udHJvbHMgd2hldGhlciBvciBub3QgbG9uZyBzdGFjayB0cmFjZXMgd2lsbCBiZSBvblxuICovXG5RLmxvbmdTdGFja1N1cHBvcnQgPSBmYWxzZTtcblxuLy8gZW5hYmxlIGxvbmcgc3RhY2tzIGlmIFFfREVCVUcgaXMgc2V0XG5pZiAodHlwZW9mIHByb2Nlc3MgPT09IFwib2JqZWN0XCIgJiYgcHJvY2VzcyAmJiBwcm9jZXNzLmVudiAmJiBwcm9jZXNzLmVudi5RX0RFQlVHKSB7XG4gICAgUS5sb25nU3RhY2tTdXBwb3J0ID0gdHJ1ZTtcbn1cblxuLyoqXG4gKiBDb25zdHJ1Y3RzIGEge3Byb21pc2UsIHJlc29sdmUsIHJlamVjdH0gb2JqZWN0LlxuICpcbiAqIGByZXNvbHZlYCBpcyBhIGNhbGxiYWNrIHRvIGludm9rZSB3aXRoIGEgbW9yZSByZXNvbHZlZCB2YWx1ZSBmb3IgdGhlXG4gKiBwcm9taXNlLiBUbyBmdWxmaWxsIHRoZSBwcm9taXNlLCBpbnZva2UgYHJlc29sdmVgIHdpdGggYW55IHZhbHVlIHRoYXQgaXNcbiAqIG5vdCBhIHRoZW5hYmxlLiBUbyByZWplY3QgdGhlIHByb21pc2UsIGludm9rZSBgcmVzb2x2ZWAgd2l0aCBhIHJlamVjdGVkXG4gKiB0aGVuYWJsZSwgb3IgaW52b2tlIGByZWplY3RgIHdpdGggdGhlIHJlYXNvbiBkaXJlY3RseS4gVG8gcmVzb2x2ZSB0aGVcbiAqIHByb21pc2UgdG8gYW5vdGhlciB0aGVuYWJsZSwgdGh1cyBwdXR0aW5nIGl0IGluIHRoZSBzYW1lIHN0YXRlLCBpbnZva2VcbiAqIGByZXNvbHZlYCB3aXRoIHRoYXQgb3RoZXIgdGhlbmFibGUuXG4gKi9cblEuZGVmZXIgPSBkZWZlcjtcbmZ1bmN0aW9uIGRlZmVyKCkge1xuICAgIC8vIGlmIFwibWVzc2FnZXNcIiBpcyBhbiBcIkFycmF5XCIsIHRoYXQgaW5kaWNhdGVzIHRoYXQgdGhlIHByb21pc2UgaGFzIG5vdCB5ZXRcbiAgICAvLyBiZWVuIHJlc29sdmVkLiAgSWYgaXQgaXMgXCJ1bmRlZmluZWRcIiwgaXQgaGFzIGJlZW4gcmVzb2x2ZWQuICBFYWNoXG4gICAgLy8gZWxlbWVudCBvZiB0aGUgbWVzc2FnZXMgYXJyYXkgaXMgaXRzZWxmIGFuIGFycmF5IG9mIGNvbXBsZXRlIGFyZ3VtZW50cyB0b1xuICAgIC8vIGZvcndhcmQgdG8gdGhlIHJlc29sdmVkIHByb21pc2UuICBXZSBjb2VyY2UgdGhlIHJlc29sdXRpb24gdmFsdWUgdG8gYVxuICAgIC8vIHByb21pc2UgdXNpbmcgdGhlIGByZXNvbHZlYCBmdW5jdGlvbiBiZWNhdXNlIGl0IGhhbmRsZXMgYm90aCBmdWxseVxuICAgIC8vIG5vbi10aGVuYWJsZSB2YWx1ZXMgYW5kIG90aGVyIHRoZW5hYmxlcyBncmFjZWZ1bGx5LlxuICAgIHZhciBtZXNzYWdlcyA9IFtdLCBwcm9ncmVzc0xpc3RlbmVycyA9IFtdLCByZXNvbHZlZFByb21pc2U7XG5cbiAgICB2YXIgZGVmZXJyZWQgPSBvYmplY3RfY3JlYXRlKGRlZmVyLnByb3RvdHlwZSk7XG4gICAgdmFyIHByb21pc2UgPSBvYmplY3RfY3JlYXRlKFByb21pc2UucHJvdG90eXBlKTtcblxuICAgIHByb21pc2UucHJvbWlzZURpc3BhdGNoID0gZnVuY3Rpb24gKHJlc29sdmUsIG9wLCBvcGVyYW5kcykge1xuICAgICAgICB2YXIgYXJncyA9IGFycmF5X3NsaWNlKGFyZ3VtZW50cyk7XG4gICAgICAgIGlmIChtZXNzYWdlcykge1xuICAgICAgICAgICAgbWVzc2FnZXMucHVzaChhcmdzKTtcbiAgICAgICAgICAgIGlmIChvcCA9PT0gXCJ3aGVuXCIgJiYgb3BlcmFuZHNbMV0pIHsgLy8gcHJvZ3Jlc3Mgb3BlcmFuZFxuICAgICAgICAgICAgICAgIHByb2dyZXNzTGlzdGVuZXJzLnB1c2gob3BlcmFuZHNbMV0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgUS5uZXh0VGljayhmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZWRQcm9taXNlLnByb21pc2VEaXNwYXRjaC5hcHBseShyZXNvbHZlZFByb21pc2UsIGFyZ3MpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgLy8gWFhYIGRlcHJlY2F0ZWRcbiAgICBwcm9taXNlLnZhbHVlT2YgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGlmIChtZXNzYWdlcykge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgICAgIH1cbiAgICAgICAgdmFyIG5lYXJlclZhbHVlID0gbmVhcmVyKHJlc29sdmVkUHJvbWlzZSk7XG4gICAgICAgIGlmIChpc1Byb21pc2UobmVhcmVyVmFsdWUpKSB7XG4gICAgICAgICAgICByZXNvbHZlZFByb21pc2UgPSBuZWFyZXJWYWx1ZTsgLy8gc2hvcnRlbiBjaGFpblxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBuZWFyZXJWYWx1ZTtcbiAgICB9O1xuXG4gICAgcHJvbWlzZS5pbnNwZWN0ID0gZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAoIXJlc29sdmVkUHJvbWlzZSkge1xuICAgICAgICAgICAgcmV0dXJuIHsgc3RhdGU6IFwicGVuZGluZ1wiIH07XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHJlc29sdmVkUHJvbWlzZS5pbnNwZWN0KCk7XG4gICAgfTtcblxuICAgIGlmIChRLmxvbmdTdGFja1N1cHBvcnQgJiYgaGFzU3RhY2tzKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgLy8gTk9URTogZG9uJ3QgdHJ5IHRvIHVzZSBgRXJyb3IuY2FwdHVyZVN0YWNrVHJhY2VgIG9yIHRyYW5zZmVyIHRoZVxuICAgICAgICAgICAgLy8gYWNjZXNzb3IgYXJvdW5kOyB0aGF0IGNhdXNlcyBtZW1vcnkgbGVha3MgYXMgcGVyIEdILTExMS4gSnVzdFxuICAgICAgICAgICAgLy8gcmVpZnkgdGhlIHN0YWNrIHRyYWNlIGFzIGEgc3RyaW5nIEFTQVAuXG4gICAgICAgICAgICAvL1xuICAgICAgICAgICAgLy8gQXQgdGhlIHNhbWUgdGltZSwgY3V0IG9mZiB0aGUgZmlyc3QgbGluZTsgaXQncyBhbHdheXMganVzdFxuICAgICAgICAgICAgLy8gXCJbb2JqZWN0IFByb21pc2VdXFxuXCIsIGFzIHBlciB0aGUgYHRvU3RyaW5nYC5cbiAgICAgICAgICAgIHByb21pc2Uuc3RhY2sgPSBlLnN0YWNrLnN1YnN0cmluZyhlLnN0YWNrLmluZGV4T2YoXCJcXG5cIikgKyAxKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIE5PVEU6IHdlIGRvIHRoZSBjaGVja3MgZm9yIGByZXNvbHZlZFByb21pc2VgIGluIGVhY2ggbWV0aG9kLCBpbnN0ZWFkIG9mXG4gICAgLy8gY29uc29saWRhdGluZyB0aGVtIGludG8gYGJlY29tZWAsIHNpbmNlIG90aGVyd2lzZSB3ZSdkIGNyZWF0ZSBuZXdcbiAgICAvLyBwcm9taXNlcyB3aXRoIHRoZSBsaW5lcyBgYmVjb21lKHdoYXRldmVyKHZhbHVlKSlgLiBTZWUgZS5nLiBHSC0yNTIuXG5cbiAgICBmdW5jdGlvbiBiZWNvbWUobmV3UHJvbWlzZSkge1xuICAgICAgICByZXNvbHZlZFByb21pc2UgPSBuZXdQcm9taXNlO1xuICAgICAgICBwcm9taXNlLnNvdXJjZSA9IG5ld1Byb21pc2U7XG5cbiAgICAgICAgYXJyYXlfcmVkdWNlKG1lc3NhZ2VzLCBmdW5jdGlvbiAodW5kZWZpbmVkLCBtZXNzYWdlKSB7XG4gICAgICAgICAgICBRLm5leHRUaWNrKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICBuZXdQcm9taXNlLnByb21pc2VEaXNwYXRjaC5hcHBseShuZXdQcm9taXNlLCBtZXNzYWdlKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9LCB2b2lkIDApO1xuXG4gICAgICAgIG1lc3NhZ2VzID0gdm9pZCAwO1xuICAgICAgICBwcm9ncmVzc0xpc3RlbmVycyA9IHZvaWQgMDtcbiAgICB9XG5cbiAgICBkZWZlcnJlZC5wcm9taXNlID0gcHJvbWlzZTtcbiAgICBkZWZlcnJlZC5yZXNvbHZlID0gZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIGlmIChyZXNvbHZlZFByb21pc2UpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGJlY29tZShRKHZhbHVlKSk7XG4gICAgfTtcblxuICAgIGRlZmVycmVkLmZ1bGZpbGwgPSBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgaWYgKHJlc29sdmVkUHJvbWlzZSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgYmVjb21lKGZ1bGZpbGwodmFsdWUpKTtcbiAgICB9O1xuICAgIGRlZmVycmVkLnJlamVjdCA9IGZ1bmN0aW9uIChyZWFzb24pIHtcbiAgICAgICAgaWYgKHJlc29sdmVkUHJvbWlzZSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgYmVjb21lKHJlamVjdChyZWFzb24pKTtcbiAgICB9O1xuICAgIGRlZmVycmVkLm5vdGlmeSA9IGZ1bmN0aW9uIChwcm9ncmVzcykge1xuICAgICAgICBpZiAocmVzb2x2ZWRQcm9taXNlKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBhcnJheV9yZWR1Y2UocHJvZ3Jlc3NMaXN0ZW5lcnMsIGZ1bmN0aW9uICh1bmRlZmluZWQsIHByb2dyZXNzTGlzdGVuZXIpIHtcbiAgICAgICAgICAgIFEubmV4dFRpY2soZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIHByb2dyZXNzTGlzdGVuZXIocHJvZ3Jlc3MpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH0sIHZvaWQgMCk7XG4gICAgfTtcblxuICAgIHJldHVybiBkZWZlcnJlZDtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIGEgTm9kZS1zdHlsZSBjYWxsYmFjayB0aGF0IHdpbGwgcmVzb2x2ZSBvciByZWplY3QgdGhlIGRlZmVycmVkXG4gKiBwcm9taXNlLlxuICogQHJldHVybnMgYSBub2RlYmFja1xuICovXG5kZWZlci5wcm90b3R5cGUubWFrZU5vZGVSZXNvbHZlciA9IGZ1bmN0aW9uICgpIHtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgcmV0dXJuIGZ1bmN0aW9uIChlcnJvciwgdmFsdWUpIHtcbiAgICAgICAgaWYgKGVycm9yKSB7XG4gICAgICAgICAgICBzZWxmLnJlamVjdChlcnJvcik7XG4gICAgICAgIH0gZWxzZSBpZiAoYXJndW1lbnRzLmxlbmd0aCA+IDIpIHtcbiAgICAgICAgICAgIHNlbGYucmVzb2x2ZShhcnJheV9zbGljZShhcmd1bWVudHMsIDEpKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHNlbGYucmVzb2x2ZSh2YWx1ZSk7XG4gICAgICAgIH1cbiAgICB9O1xufTtcblxuLyoqXG4gKiBAcGFyYW0gcmVzb2x2ZXIge0Z1bmN0aW9ufSBhIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyBub3RoaW5nIGFuZCBhY2NlcHRzXG4gKiB0aGUgcmVzb2x2ZSwgcmVqZWN0LCBhbmQgbm90aWZ5IGZ1bmN0aW9ucyBmb3IgYSBkZWZlcnJlZC5cbiAqIEByZXR1cm5zIGEgcHJvbWlzZSB0aGF0IG1heSBiZSByZXNvbHZlZCB3aXRoIHRoZSBnaXZlbiByZXNvbHZlIGFuZCByZWplY3RcbiAqIGZ1bmN0aW9ucywgb3IgcmVqZWN0ZWQgYnkgYSB0aHJvd24gZXhjZXB0aW9uIGluIHJlc29sdmVyXG4gKi9cblEuUHJvbWlzZSA9IHByb21pc2U7IC8vIEVTNlxuUS5wcm9taXNlID0gcHJvbWlzZTtcbmZ1bmN0aW9uIHByb21pc2UocmVzb2x2ZXIpIHtcbiAgICBpZiAodHlwZW9mIHJlc29sdmVyICE9PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcInJlc29sdmVyIG11c3QgYmUgYSBmdW5jdGlvbi5cIik7XG4gICAgfVxuICAgIHZhciBkZWZlcnJlZCA9IGRlZmVyKCk7XG4gICAgdHJ5IHtcbiAgICAgICAgcmVzb2x2ZXIoZGVmZXJyZWQucmVzb2x2ZSwgZGVmZXJyZWQucmVqZWN0LCBkZWZlcnJlZC5ub3RpZnkpO1xuICAgIH0gY2F0Y2ggKHJlYXNvbikge1xuICAgICAgICBkZWZlcnJlZC5yZWplY3QocmVhc29uKTtcbiAgICB9XG4gICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG59XG5cbnByb21pc2UucmFjZSA9IHJhY2U7IC8vIEVTNlxucHJvbWlzZS5hbGwgPSBhbGw7IC8vIEVTNlxucHJvbWlzZS5yZWplY3QgPSByZWplY3Q7IC8vIEVTNlxucHJvbWlzZS5yZXNvbHZlID0gUTsgLy8gRVM2XG5cbi8vIFhYWCBleHBlcmltZW50YWwuICBUaGlzIG1ldGhvZCBpcyBhIHdheSB0byBkZW5vdGUgdGhhdCBhIGxvY2FsIHZhbHVlIGlzXG4vLyBzZXJpYWxpemFibGUgYW5kIHNob3VsZCBiZSBpbW1lZGlhdGVseSBkaXNwYXRjaGVkIHRvIGEgcmVtb3RlIHVwb24gcmVxdWVzdCxcbi8vIGluc3RlYWQgb2YgcGFzc2luZyBhIHJlZmVyZW5jZS5cblEucGFzc0J5Q29weSA9IGZ1bmN0aW9uIChvYmplY3QpIHtcbiAgICAvL2ZyZWV6ZShvYmplY3QpO1xuICAgIC8vcGFzc0J5Q29waWVzLnNldChvYmplY3QsIHRydWUpO1xuICAgIHJldHVybiBvYmplY3Q7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS5wYXNzQnlDb3B5ID0gZnVuY3Rpb24gKCkge1xuICAgIC8vZnJlZXplKG9iamVjdCk7XG4gICAgLy9wYXNzQnlDb3BpZXMuc2V0KG9iamVjdCwgdHJ1ZSk7XG4gICAgcmV0dXJuIHRoaXM7XG59O1xuXG4vKipcbiAqIElmIHR3byBwcm9taXNlcyBldmVudHVhbGx5IGZ1bGZpbGwgdG8gdGhlIHNhbWUgdmFsdWUsIHByb21pc2VzIHRoYXQgdmFsdWUsXG4gKiBidXQgb3RoZXJ3aXNlIHJlamVjdHMuXG4gKiBAcGFyYW0geCB7QW55Kn1cbiAqIEBwYXJhbSB5IHtBbnkqfVxuICogQHJldHVybnMge0FueSp9IGEgcHJvbWlzZSBmb3IgeCBhbmQgeSBpZiB0aGV5IGFyZSB0aGUgc2FtZSwgYnV0IGEgcmVqZWN0aW9uXG4gKiBvdGhlcndpc2UuXG4gKlxuICovXG5RLmpvaW4gPSBmdW5jdGlvbiAoeCwgeSkge1xuICAgIHJldHVybiBRKHgpLmpvaW4oeSk7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS5qb2luID0gZnVuY3Rpb24gKHRoYXQpIHtcbiAgICByZXR1cm4gUShbdGhpcywgdGhhdF0pLnNwcmVhZChmdW5jdGlvbiAoeCwgeSkge1xuICAgICAgICBpZiAoeCA9PT0geSkge1xuICAgICAgICAgICAgLy8gVE9ETzogXCI9PT1cIiBzaG91bGQgYmUgT2JqZWN0LmlzIG9yIGVxdWl2XG4gICAgICAgICAgICByZXR1cm4geDtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcIkNhbid0IGpvaW46IG5vdCB0aGUgc2FtZTogXCIgKyB4ICsgXCIgXCIgKyB5KTtcbiAgICAgICAgfVxuICAgIH0pO1xufTtcblxuLyoqXG4gKiBSZXR1cm5zIGEgcHJvbWlzZSBmb3IgdGhlIGZpcnN0IG9mIGFuIGFycmF5IG9mIHByb21pc2VzIHRvIGJlY29tZSBzZXR0bGVkLlxuICogQHBhcmFtIGFuc3dlcnMge0FycmF5W0FueSpdfSBwcm9taXNlcyB0byByYWNlXG4gKiBAcmV0dXJucyB7QW55Kn0gdGhlIGZpcnN0IHByb21pc2UgdG8gYmUgc2V0dGxlZFxuICovXG5RLnJhY2UgPSByYWNlO1xuZnVuY3Rpb24gcmFjZShhbnN3ZXJQcykge1xuICAgIHJldHVybiBwcm9taXNlKGZ1bmN0aW9uIChyZXNvbHZlLCByZWplY3QpIHtcbiAgICAgICAgLy8gU3dpdGNoIHRvIHRoaXMgb25jZSB3ZSBjYW4gYXNzdW1lIGF0IGxlYXN0IEVTNVxuICAgICAgICAvLyBhbnN3ZXJQcy5mb3JFYWNoKGZ1bmN0aW9uIChhbnN3ZXJQKSB7XG4gICAgICAgIC8vICAgICBRKGFuc3dlclApLnRoZW4ocmVzb2x2ZSwgcmVqZWN0KTtcbiAgICAgICAgLy8gfSk7XG4gICAgICAgIC8vIFVzZSB0aGlzIGluIHRoZSBtZWFudGltZVxuICAgICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gYW5zd2VyUHMubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIFEoYW5zd2VyUHNbaV0pLnRoZW4ocmVzb2x2ZSwgcmVqZWN0KTtcbiAgICAgICAgfVxuICAgIH0pO1xufVxuXG5Qcm9taXNlLnByb3RvdHlwZS5yYWNlID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiB0aGlzLnRoZW4oUS5yYWNlKTtcbn07XG5cbi8qKlxuICogQ29uc3RydWN0cyBhIFByb21pc2Ugd2l0aCBhIHByb21pc2UgZGVzY3JpcHRvciBvYmplY3QgYW5kIG9wdGlvbmFsIGZhbGxiYWNrXG4gKiBmdW5jdGlvbi4gIFRoZSBkZXNjcmlwdG9yIGNvbnRhaW5zIG1ldGhvZHMgbGlrZSB3aGVuKHJlamVjdGVkKSwgZ2V0KG5hbWUpLFxuICogc2V0KG5hbWUsIHZhbHVlKSwgcG9zdChuYW1lLCBhcmdzKSwgYW5kIGRlbGV0ZShuYW1lKSwgd2hpY2ggYWxsXG4gKiByZXR1cm4gZWl0aGVyIGEgdmFsdWUsIGEgcHJvbWlzZSBmb3IgYSB2YWx1ZSwgb3IgYSByZWplY3Rpb24uICBUaGUgZmFsbGJhY2tcbiAqIGFjY2VwdHMgdGhlIG9wZXJhdGlvbiBuYW1lLCBhIHJlc29sdmVyLCBhbmQgYW55IGZ1cnRoZXIgYXJndW1lbnRzIHRoYXQgd291bGRcbiAqIGhhdmUgYmVlbiBmb3J3YXJkZWQgdG8gdGhlIGFwcHJvcHJpYXRlIG1ldGhvZCBhYm92ZSBoYWQgYSBtZXRob2QgYmVlblxuICogcHJvdmlkZWQgd2l0aCB0aGUgcHJvcGVyIG5hbWUuICBUaGUgQVBJIG1ha2VzIG5vIGd1YXJhbnRlZXMgYWJvdXQgdGhlIG5hdHVyZVxuICogb2YgdGhlIHJldHVybmVkIG9iamVjdCwgYXBhcnQgZnJvbSB0aGF0IGl0IGlzIHVzYWJsZSB3aGVyZWV2ZXIgcHJvbWlzZXMgYXJlXG4gKiBib3VnaHQgYW5kIHNvbGQuXG4gKi9cblEubWFrZVByb21pc2UgPSBQcm9taXNlO1xuZnVuY3Rpb24gUHJvbWlzZShkZXNjcmlwdG9yLCBmYWxsYmFjaywgaW5zcGVjdCkge1xuICAgIGlmIChmYWxsYmFjayA9PT0gdm9pZCAwKSB7XG4gICAgICAgIGZhbGxiYWNrID0gZnVuY3Rpb24gKG9wKSB7XG4gICAgICAgICAgICByZXR1cm4gcmVqZWN0KG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBcIlByb21pc2UgZG9lcyBub3Qgc3VwcG9ydCBvcGVyYXRpb246IFwiICsgb3BcbiAgICAgICAgICAgICkpO1xuICAgICAgICB9O1xuICAgIH1cbiAgICBpZiAoaW5zcGVjdCA9PT0gdm9pZCAwKSB7XG4gICAgICAgIGluc3BlY3QgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICByZXR1cm4ge3N0YXRlOiBcInVua25vd25cIn07XG4gICAgICAgIH07XG4gICAgfVxuXG4gICAgdmFyIHByb21pc2UgPSBvYmplY3RfY3JlYXRlKFByb21pc2UucHJvdG90eXBlKTtcblxuICAgIHByb21pc2UucHJvbWlzZURpc3BhdGNoID0gZnVuY3Rpb24gKHJlc29sdmUsIG9wLCBhcmdzKSB7XG4gICAgICAgIHZhciByZXN1bHQ7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBpZiAoZGVzY3JpcHRvcltvcF0pIHtcbiAgICAgICAgICAgICAgICByZXN1bHQgPSBkZXNjcmlwdG9yW29wXS5hcHBseShwcm9taXNlLCBhcmdzKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgcmVzdWx0ID0gZmFsbGJhY2suY2FsbChwcm9taXNlLCBvcCwgYXJncyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0gY2F0Y2ggKGV4Y2VwdGlvbikge1xuICAgICAgICAgICAgcmVzdWx0ID0gcmVqZWN0KGV4Y2VwdGlvbik7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHJlc29sdmUpIHtcbiAgICAgICAgICAgIHJlc29sdmUocmVzdWx0KTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICBwcm9taXNlLmluc3BlY3QgPSBpbnNwZWN0O1xuXG4gICAgLy8gWFhYIGRlcHJlY2F0ZWQgYHZhbHVlT2ZgIGFuZCBgZXhjZXB0aW9uYCBzdXBwb3J0XG4gICAgaWYgKGluc3BlY3QpIHtcbiAgICAgICAgdmFyIGluc3BlY3RlZCA9IGluc3BlY3QoKTtcbiAgICAgICAgaWYgKGluc3BlY3RlZC5zdGF0ZSA9PT0gXCJyZWplY3RlZFwiKSB7XG4gICAgICAgICAgICBwcm9taXNlLmV4Y2VwdGlvbiA9IGluc3BlY3RlZC5yZWFzb247XG4gICAgICAgIH1cblxuICAgICAgICBwcm9taXNlLnZhbHVlT2YgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICB2YXIgaW5zcGVjdGVkID0gaW5zcGVjdCgpO1xuICAgICAgICAgICAgaWYgKGluc3BlY3RlZC5zdGF0ZSA9PT0gXCJwZW5kaW5nXCIgfHxcbiAgICAgICAgICAgICAgICBpbnNwZWN0ZWQuc3RhdGUgPT09IFwicmVqZWN0ZWRcIikge1xuICAgICAgICAgICAgICAgIHJldHVybiBwcm9taXNlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIGluc3BlY3RlZC52YWx1ZTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICByZXR1cm4gcHJvbWlzZTtcbn1cblxuUHJvbWlzZS5wcm90b3R5cGUudG9TdHJpbmcgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIFwiW29iamVjdCBQcm9taXNlXVwiO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUudGhlbiA9IGZ1bmN0aW9uIChmdWxmaWxsZWQsIHJlamVjdGVkLCBwcm9ncmVzc2VkKSB7XG4gICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgIHZhciBkZWZlcnJlZCA9IGRlZmVyKCk7XG4gICAgdmFyIGRvbmUgPSBmYWxzZTsgICAvLyBlbnN1cmUgdGhlIHVudHJ1c3RlZCBwcm9taXNlIG1ha2VzIGF0IG1vc3QgYVxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gc2luZ2xlIGNhbGwgdG8gb25lIG9mIHRoZSBjYWxsYmFja3NcblxuICAgIGZ1bmN0aW9uIF9mdWxmaWxsZWQodmFsdWUpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHJldHVybiB0eXBlb2YgZnVsZmlsbGVkID09PSBcImZ1bmN0aW9uXCIgPyBmdWxmaWxsZWQodmFsdWUpIDogdmFsdWU7XG4gICAgICAgIH0gY2F0Y2ggKGV4Y2VwdGlvbikge1xuICAgICAgICAgICAgcmV0dXJuIHJlamVjdChleGNlcHRpb24pO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gX3JlamVjdGVkKGV4Y2VwdGlvbikge1xuICAgICAgICBpZiAodHlwZW9mIHJlamVjdGVkID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgICAgIG1ha2VTdGFja1RyYWNlTG9uZyhleGNlcHRpb24sIHNlbGYpO1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICByZXR1cm4gcmVqZWN0ZWQoZXhjZXB0aW9uKTtcbiAgICAgICAgICAgIH0gY2F0Y2ggKG5ld0V4Y2VwdGlvbikge1xuICAgICAgICAgICAgICAgIHJldHVybiByZWplY3QobmV3RXhjZXB0aW9uKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gcmVqZWN0KGV4Y2VwdGlvbik7XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gX3Byb2dyZXNzZWQodmFsdWUpIHtcbiAgICAgICAgcmV0dXJuIHR5cGVvZiBwcm9ncmVzc2VkID09PSBcImZ1bmN0aW9uXCIgPyBwcm9ncmVzc2VkKHZhbHVlKSA6IHZhbHVlO1xuICAgIH1cblxuICAgIFEubmV4dFRpY2soZnVuY3Rpb24gKCkge1xuICAgICAgICBzZWxmLnByb21pc2VEaXNwYXRjaChmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChkb25lKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZG9uZSA9IHRydWU7XG5cbiAgICAgICAgICAgIGRlZmVycmVkLnJlc29sdmUoX2Z1bGZpbGxlZCh2YWx1ZSkpO1xuICAgICAgICB9LCBcIndoZW5cIiwgW2Z1bmN0aW9uIChleGNlcHRpb24pIHtcbiAgICAgICAgICAgIGlmIChkb25lKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZG9uZSA9IHRydWU7XG5cbiAgICAgICAgICAgIGRlZmVycmVkLnJlc29sdmUoX3JlamVjdGVkKGV4Y2VwdGlvbikpO1xuICAgICAgICB9XSk7XG4gICAgfSk7XG5cbiAgICAvLyBQcm9ncmVzcyBwcm9wYWdhdG9yIG5lZWQgdG8gYmUgYXR0YWNoZWQgaW4gdGhlIGN1cnJlbnQgdGljay5cbiAgICBzZWxmLnByb21pc2VEaXNwYXRjaCh2b2lkIDAsIFwid2hlblwiLCBbdm9pZCAwLCBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgdmFyIG5ld1ZhbHVlO1xuICAgICAgICB2YXIgdGhyZXcgPSBmYWxzZTtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIG5ld1ZhbHVlID0gX3Byb2dyZXNzZWQodmFsdWUpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICB0aHJldyA9IHRydWU7XG4gICAgICAgICAgICBpZiAoUS5vbmVycm9yKSB7XG4gICAgICAgICAgICAgICAgUS5vbmVycm9yKGUpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCF0aHJldykge1xuICAgICAgICAgICAgZGVmZXJyZWQubm90aWZ5KG5ld1ZhbHVlKTtcbiAgICAgICAgfVxuICAgIH1dKTtcblxuICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xufTtcblxuUS50YXAgPSBmdW5jdGlvbiAocHJvbWlzZSwgY2FsbGJhY2spIHtcbiAgICByZXR1cm4gUShwcm9taXNlKS50YXAoY2FsbGJhY2spO1xufTtcblxuLyoqXG4gKiBXb3JrcyBhbG1vc3QgbGlrZSBcImZpbmFsbHlcIiwgYnV0IG5vdCBjYWxsZWQgZm9yIHJlamVjdGlvbnMuXG4gKiBPcmlnaW5hbCByZXNvbHV0aW9uIHZhbHVlIGlzIHBhc3NlZCB0aHJvdWdoIGNhbGxiYWNrIHVuYWZmZWN0ZWQuXG4gKiBDYWxsYmFjayBtYXkgcmV0dXJuIGEgcHJvbWlzZSB0aGF0IHdpbGwgYmUgYXdhaXRlZCBmb3IuXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBjYWxsYmFja1xuICogQHJldHVybnMge1EuUHJvbWlzZX1cbiAqIEBleGFtcGxlXG4gKiBkb1NvbWV0aGluZygpXG4gKiAgIC50aGVuKC4uLilcbiAqICAgLnRhcChjb25zb2xlLmxvZylcbiAqICAgLnRoZW4oLi4uKTtcbiAqL1xuUHJvbWlzZS5wcm90b3R5cGUudGFwID0gZnVuY3Rpb24gKGNhbGxiYWNrKSB7XG4gICAgY2FsbGJhY2sgPSBRKGNhbGxiYWNrKTtcblxuICAgIHJldHVybiB0aGlzLnRoZW4oZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIHJldHVybiBjYWxsYmFjay5mY2FsbCh2YWx1ZSkudGhlblJlc29sdmUodmFsdWUpO1xuICAgIH0pO1xufTtcblxuLyoqXG4gKiBSZWdpc3RlcnMgYW4gb2JzZXJ2ZXIgb24gYSBwcm9taXNlLlxuICpcbiAqIEd1YXJhbnRlZXM6XG4gKlxuICogMS4gdGhhdCBmdWxmaWxsZWQgYW5kIHJlamVjdGVkIHdpbGwgYmUgY2FsbGVkIG9ubHkgb25jZS5cbiAqIDIuIHRoYXQgZWl0aGVyIHRoZSBmdWxmaWxsZWQgY2FsbGJhY2sgb3IgdGhlIHJlamVjdGVkIGNhbGxiYWNrIHdpbGwgYmVcbiAqICAgIGNhbGxlZCwgYnV0IG5vdCBib3RoLlxuICogMy4gdGhhdCBmdWxmaWxsZWQgYW5kIHJlamVjdGVkIHdpbGwgbm90IGJlIGNhbGxlZCBpbiB0aGlzIHR1cm4uXG4gKlxuICogQHBhcmFtIHZhbHVlICAgICAgcHJvbWlzZSBvciBpbW1lZGlhdGUgcmVmZXJlbmNlIHRvIG9ic2VydmVcbiAqIEBwYXJhbSBmdWxmaWxsZWQgIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCB3aXRoIHRoZSBmdWxmaWxsZWQgdmFsdWVcbiAqIEBwYXJhbSByZWplY3RlZCAgIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCB3aXRoIHRoZSByZWplY3Rpb24gZXhjZXB0aW9uXG4gKiBAcGFyYW0gcHJvZ3Jlc3NlZCBmdW5jdGlvbiB0byBiZSBjYWxsZWQgb24gYW55IHByb2dyZXNzIG5vdGlmaWNhdGlvbnNcbiAqIEByZXR1cm4gcHJvbWlzZSBmb3IgdGhlIHJldHVybiB2YWx1ZSBmcm9tIHRoZSBpbnZva2VkIGNhbGxiYWNrXG4gKi9cblEud2hlbiA9IHdoZW47XG5mdW5jdGlvbiB3aGVuKHZhbHVlLCBmdWxmaWxsZWQsIHJlamVjdGVkLCBwcm9ncmVzc2VkKSB7XG4gICAgcmV0dXJuIFEodmFsdWUpLnRoZW4oZnVsZmlsbGVkLCByZWplY3RlZCwgcHJvZ3Jlc3NlZCk7XG59XG5cblByb21pc2UucHJvdG90eXBlLnRoZW5SZXNvbHZlID0gZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgcmV0dXJuIHRoaXMudGhlbihmdW5jdGlvbiAoKSB7IHJldHVybiB2YWx1ZTsgfSk7XG59O1xuXG5RLnRoZW5SZXNvbHZlID0gZnVuY3Rpb24gKHByb21pc2UsIHZhbHVlKSB7XG4gICAgcmV0dXJuIFEocHJvbWlzZSkudGhlblJlc29sdmUodmFsdWUpO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUudGhlblJlamVjdCA9IGZ1bmN0aW9uIChyZWFzb24pIHtcbiAgICByZXR1cm4gdGhpcy50aGVuKGZ1bmN0aW9uICgpIHsgdGhyb3cgcmVhc29uOyB9KTtcbn07XG5cblEudGhlblJlamVjdCA9IGZ1bmN0aW9uIChwcm9taXNlLCByZWFzb24pIHtcbiAgICByZXR1cm4gUShwcm9taXNlKS50aGVuUmVqZWN0KHJlYXNvbik7XG59O1xuXG4vKipcbiAqIElmIGFuIG9iamVjdCBpcyBub3QgYSBwcm9taXNlLCBpdCBpcyBhcyBcIm5lYXJcIiBhcyBwb3NzaWJsZS5cbiAqIElmIGEgcHJvbWlzZSBpcyByZWplY3RlZCwgaXQgaXMgYXMgXCJuZWFyXCIgYXMgcG9zc2libGUgdG9vLlxuICogSWYgaXTigJlzIGEgZnVsZmlsbGVkIHByb21pc2UsIHRoZSBmdWxmaWxsbWVudCB2YWx1ZSBpcyBuZWFyZXIuXG4gKiBJZiBpdOKAmXMgYSBkZWZlcnJlZCBwcm9taXNlIGFuZCB0aGUgZGVmZXJyZWQgaGFzIGJlZW4gcmVzb2x2ZWQsIHRoZVxuICogcmVzb2x1dGlvbiBpcyBcIm5lYXJlclwiLlxuICogQHBhcmFtIG9iamVjdFxuICogQHJldHVybnMgbW9zdCByZXNvbHZlZCAobmVhcmVzdCkgZm9ybSBvZiB0aGUgb2JqZWN0XG4gKi9cblxuLy8gWFhYIHNob3VsZCB3ZSByZS1kbyB0aGlzP1xuUS5uZWFyZXIgPSBuZWFyZXI7XG5mdW5jdGlvbiBuZWFyZXIodmFsdWUpIHtcbiAgICBpZiAoaXNQcm9taXNlKHZhbHVlKSkge1xuICAgICAgICB2YXIgaW5zcGVjdGVkID0gdmFsdWUuaW5zcGVjdCgpO1xuICAgICAgICBpZiAoaW5zcGVjdGVkLnN0YXRlID09PSBcImZ1bGZpbGxlZFwiKSB7XG4gICAgICAgICAgICByZXR1cm4gaW5zcGVjdGVkLnZhbHVlO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiB2YWx1ZTtcbn1cblxuLyoqXG4gKiBAcmV0dXJucyB3aGV0aGVyIHRoZSBnaXZlbiBvYmplY3QgaXMgYSBwcm9taXNlLlxuICogT3RoZXJ3aXNlIGl0IGlzIGEgZnVsZmlsbGVkIHZhbHVlLlxuICovXG5RLmlzUHJvbWlzZSA9IGlzUHJvbWlzZTtcbmZ1bmN0aW9uIGlzUHJvbWlzZShvYmplY3QpIHtcbiAgICByZXR1cm4gb2JqZWN0IGluc3RhbmNlb2YgUHJvbWlzZTtcbn1cblxuUS5pc1Byb21pc2VBbGlrZSA9IGlzUHJvbWlzZUFsaWtlO1xuZnVuY3Rpb24gaXNQcm9taXNlQWxpa2Uob2JqZWN0KSB7XG4gICAgcmV0dXJuIGlzT2JqZWN0KG9iamVjdCkgJiYgdHlwZW9mIG9iamVjdC50aGVuID09PSBcImZ1bmN0aW9uXCI7XG59XG5cbi8qKlxuICogQHJldHVybnMgd2hldGhlciB0aGUgZ2l2ZW4gb2JqZWN0IGlzIGEgcGVuZGluZyBwcm9taXNlLCBtZWFuaW5nIG5vdFxuICogZnVsZmlsbGVkIG9yIHJlamVjdGVkLlxuICovXG5RLmlzUGVuZGluZyA9IGlzUGVuZGluZztcbmZ1bmN0aW9uIGlzUGVuZGluZyhvYmplY3QpIHtcbiAgICByZXR1cm4gaXNQcm9taXNlKG9iamVjdCkgJiYgb2JqZWN0Lmluc3BlY3QoKS5zdGF0ZSA9PT0gXCJwZW5kaW5nXCI7XG59XG5cblByb21pc2UucHJvdG90eXBlLmlzUGVuZGluZyA9IGZ1bmN0aW9uICgpIHtcbiAgICByZXR1cm4gdGhpcy5pbnNwZWN0KCkuc3RhdGUgPT09IFwicGVuZGluZ1wiO1xufTtcblxuLyoqXG4gKiBAcmV0dXJucyB3aGV0aGVyIHRoZSBnaXZlbiBvYmplY3QgaXMgYSB2YWx1ZSBvciBmdWxmaWxsZWRcbiAqIHByb21pc2UuXG4gKi9cblEuaXNGdWxmaWxsZWQgPSBpc0Z1bGZpbGxlZDtcbmZ1bmN0aW9uIGlzRnVsZmlsbGVkKG9iamVjdCkge1xuICAgIHJldHVybiAhaXNQcm9taXNlKG9iamVjdCkgfHwgb2JqZWN0Lmluc3BlY3QoKS5zdGF0ZSA9PT0gXCJmdWxmaWxsZWRcIjtcbn1cblxuUHJvbWlzZS5wcm90b3R5cGUuaXNGdWxmaWxsZWQgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIHRoaXMuaW5zcGVjdCgpLnN0YXRlID09PSBcImZ1bGZpbGxlZFwiO1xufTtcblxuLyoqXG4gKiBAcmV0dXJucyB3aGV0aGVyIHRoZSBnaXZlbiBvYmplY3QgaXMgYSByZWplY3RlZCBwcm9taXNlLlxuICovXG5RLmlzUmVqZWN0ZWQgPSBpc1JlamVjdGVkO1xuZnVuY3Rpb24gaXNSZWplY3RlZChvYmplY3QpIHtcbiAgICByZXR1cm4gaXNQcm9taXNlKG9iamVjdCkgJiYgb2JqZWN0Lmluc3BlY3QoKS5zdGF0ZSA9PT0gXCJyZWplY3RlZFwiO1xufVxuXG5Qcm9taXNlLnByb3RvdHlwZS5pc1JlamVjdGVkID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiB0aGlzLmluc3BlY3QoKS5zdGF0ZSA9PT0gXCJyZWplY3RlZFwiO1xufTtcblxuLy8vLyBCRUdJTiBVTkhBTkRMRUQgUkVKRUNUSU9OIFRSQUNLSU5HXG5cbi8vIFRoaXMgcHJvbWlzZSBsaWJyYXJ5IGNvbnN1bWVzIGV4Y2VwdGlvbnMgdGhyb3duIGluIGhhbmRsZXJzIHNvIHRoZXkgY2FuIGJlXG4vLyBoYW5kbGVkIGJ5IGEgc3Vic2VxdWVudCBwcm9taXNlLiAgVGhlIGV4Y2VwdGlvbnMgZ2V0IGFkZGVkIHRvIHRoaXMgYXJyYXkgd2hlblxuLy8gdGhleSBhcmUgY3JlYXRlZCwgYW5kIHJlbW92ZWQgd2hlbiB0aGV5IGFyZSBoYW5kbGVkLiAgTm90ZSB0aGF0IGluIEVTNiBvclxuLy8gc2hpbW1lZCBlbnZpcm9ubWVudHMsIHRoaXMgd291bGQgbmF0dXJhbGx5IGJlIGEgYFNldGAuXG52YXIgdW5oYW5kbGVkUmVhc29ucyA9IFtdO1xudmFyIHVuaGFuZGxlZFJlamVjdGlvbnMgPSBbXTtcbnZhciByZXBvcnRlZFVuaGFuZGxlZFJlamVjdGlvbnMgPSBbXTtcbnZhciB0cmFja1VuaGFuZGxlZFJlamVjdGlvbnMgPSB0cnVlO1xuXG5mdW5jdGlvbiByZXNldFVuaGFuZGxlZFJlamVjdGlvbnMoKSB7XG4gICAgdW5oYW5kbGVkUmVhc29ucy5sZW5ndGggPSAwO1xuICAgIHVuaGFuZGxlZFJlamVjdGlvbnMubGVuZ3RoID0gMDtcblxuICAgIGlmICghdHJhY2tVbmhhbmRsZWRSZWplY3Rpb25zKSB7XG4gICAgICAgIHRyYWNrVW5oYW5kbGVkUmVqZWN0aW9ucyA9IHRydWU7XG4gICAgfVxufVxuXG5mdW5jdGlvbiB0cmFja1JlamVjdGlvbihwcm9taXNlLCByZWFzb24pIHtcbiAgICBpZiAoIXRyYWNrVW5oYW5kbGVkUmVqZWN0aW9ucykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICh0eXBlb2YgcHJvY2VzcyA9PT0gXCJvYmplY3RcIiAmJiB0eXBlb2YgcHJvY2Vzcy5lbWl0ID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgUS5uZXh0VGljay5ydW5BZnRlcihmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBpZiAoYXJyYXlfaW5kZXhPZih1bmhhbmRsZWRSZWplY3Rpb25zLCBwcm9taXNlKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICBwcm9jZXNzLmVtaXQoXCJ1bmhhbmRsZWRSZWplY3Rpb25cIiwgcmVhc29uLCBwcm9taXNlKTtcbiAgICAgICAgICAgICAgICByZXBvcnRlZFVuaGFuZGxlZFJlamVjdGlvbnMucHVzaChwcm9taXNlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgdW5oYW5kbGVkUmVqZWN0aW9ucy5wdXNoKHByb21pc2UpO1xuICAgIGlmIChyZWFzb24gJiYgdHlwZW9mIHJlYXNvbi5zdGFjayAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgICAgICB1bmhhbmRsZWRSZWFzb25zLnB1c2gocmVhc29uLnN0YWNrKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICB1bmhhbmRsZWRSZWFzb25zLnB1c2goXCIobm8gc3RhY2spIFwiICsgcmVhc29uKTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIHVudHJhY2tSZWplY3Rpb24ocHJvbWlzZSkge1xuICAgIGlmICghdHJhY2tVbmhhbmRsZWRSZWplY3Rpb25zKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB2YXIgYXQgPSBhcnJheV9pbmRleE9mKHVuaGFuZGxlZFJlamVjdGlvbnMsIHByb21pc2UpO1xuICAgIGlmIChhdCAhPT0gLTEpIHtcbiAgICAgICAgaWYgKHR5cGVvZiBwcm9jZXNzID09PSBcIm9iamVjdFwiICYmIHR5cGVvZiBwcm9jZXNzLmVtaXQgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICAgICAgUS5uZXh0VGljay5ydW5BZnRlcihmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgdmFyIGF0UmVwb3J0ID0gYXJyYXlfaW5kZXhPZihyZXBvcnRlZFVuaGFuZGxlZFJlamVjdGlvbnMsIHByb21pc2UpO1xuICAgICAgICAgICAgICAgIGlmIChhdFJlcG9ydCAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICAgICAgcHJvY2Vzcy5lbWl0KFwicmVqZWN0aW9uSGFuZGxlZFwiLCB1bmhhbmRsZWRSZWFzb25zW2F0XSwgcHJvbWlzZSk7XG4gICAgICAgICAgICAgICAgICAgIHJlcG9ydGVkVW5oYW5kbGVkUmVqZWN0aW9ucy5zcGxpY2UoYXRSZXBvcnQsIDEpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIHVuaGFuZGxlZFJlamVjdGlvbnMuc3BsaWNlKGF0LCAxKTtcbiAgICAgICAgdW5oYW5kbGVkUmVhc29ucy5zcGxpY2UoYXQsIDEpO1xuICAgIH1cbn1cblxuUS5yZXNldFVuaGFuZGxlZFJlamVjdGlvbnMgPSByZXNldFVuaGFuZGxlZFJlamVjdGlvbnM7XG5cblEuZ2V0VW5oYW5kbGVkUmVhc29ucyA9IGZ1bmN0aW9uICgpIHtcbiAgICAvLyBNYWtlIGEgY29weSBzbyB0aGF0IGNvbnN1bWVycyBjYW4ndCBpbnRlcmZlcmUgd2l0aCBvdXIgaW50ZXJuYWwgc3RhdGUuXG4gICAgcmV0dXJuIHVuaGFuZGxlZFJlYXNvbnMuc2xpY2UoKTtcbn07XG5cblEuc3RvcFVuaGFuZGxlZFJlamVjdGlvblRyYWNraW5nID0gZnVuY3Rpb24gKCkge1xuICAgIHJlc2V0VW5oYW5kbGVkUmVqZWN0aW9ucygpO1xuICAgIHRyYWNrVW5oYW5kbGVkUmVqZWN0aW9ucyA9IGZhbHNlO1xufTtcblxucmVzZXRVbmhhbmRsZWRSZWplY3Rpb25zKCk7XG5cbi8vLy8gRU5EIFVOSEFORExFRCBSRUpFQ1RJT04gVFJBQ0tJTkdcblxuLyoqXG4gKiBDb25zdHJ1Y3RzIGEgcmVqZWN0ZWQgcHJvbWlzZS5cbiAqIEBwYXJhbSByZWFzb24gdmFsdWUgZGVzY3JpYmluZyB0aGUgZmFpbHVyZVxuICovXG5RLnJlamVjdCA9IHJlamVjdDtcbmZ1bmN0aW9uIHJlamVjdChyZWFzb24pIHtcbiAgICB2YXIgcmVqZWN0aW9uID0gUHJvbWlzZSh7XG4gICAgICAgIFwid2hlblwiOiBmdW5jdGlvbiAocmVqZWN0ZWQpIHtcbiAgICAgICAgICAgIC8vIG5vdGUgdGhhdCB0aGUgZXJyb3IgaGFzIGJlZW4gaGFuZGxlZFxuICAgICAgICAgICAgaWYgKHJlamVjdGVkKSB7XG4gICAgICAgICAgICAgICAgdW50cmFja1JlamVjdGlvbih0aGlzKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiByZWplY3RlZCA/IHJlamVjdGVkKHJlYXNvbikgOiB0aGlzO1xuICAgICAgICB9XG4gICAgfSwgZnVuY3Rpb24gZmFsbGJhY2soKSB7XG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH0sIGZ1bmN0aW9uIGluc3BlY3QoKSB7XG4gICAgICAgIHJldHVybiB7IHN0YXRlOiBcInJlamVjdGVkXCIsIHJlYXNvbjogcmVhc29uIH07XG4gICAgfSk7XG5cbiAgICAvLyBOb3RlIHRoYXQgdGhlIHJlYXNvbiBoYXMgbm90IGJlZW4gaGFuZGxlZC5cbiAgICB0cmFja1JlamVjdGlvbihyZWplY3Rpb24sIHJlYXNvbik7XG5cbiAgICByZXR1cm4gcmVqZWN0aW9uO1xufVxuXG4vKipcbiAqIENvbnN0cnVjdHMgYSBmdWxmaWxsZWQgcHJvbWlzZSBmb3IgYW4gaW1tZWRpYXRlIHJlZmVyZW5jZS5cbiAqIEBwYXJhbSB2YWx1ZSBpbW1lZGlhdGUgcmVmZXJlbmNlXG4gKi9cblEuZnVsZmlsbCA9IGZ1bGZpbGw7XG5mdW5jdGlvbiBmdWxmaWxsKHZhbHVlKSB7XG4gICAgcmV0dXJuIFByb21pc2Uoe1xuICAgICAgICBcIndoZW5cIjogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgICB9LFxuICAgICAgICBcImdldFwiOiBmdW5jdGlvbiAobmFtZSkge1xuICAgICAgICAgICAgcmV0dXJuIHZhbHVlW25hbWVdO1xuICAgICAgICB9LFxuICAgICAgICBcInNldFwiOiBmdW5jdGlvbiAobmFtZSwgcmhzKSB7XG4gICAgICAgICAgICB2YWx1ZVtuYW1lXSA9IHJocztcbiAgICAgICAgfSxcbiAgICAgICAgXCJkZWxldGVcIjogZnVuY3Rpb24gKG5hbWUpIHtcbiAgICAgICAgICAgIGRlbGV0ZSB2YWx1ZVtuYW1lXTtcbiAgICAgICAgfSxcbiAgICAgICAgXCJwb3N0XCI6IGZ1bmN0aW9uIChuYW1lLCBhcmdzKSB7XG4gICAgICAgICAgICAvLyBNYXJrIE1pbGxlciBwcm9wb3NlcyB0aGF0IHBvc3Qgd2l0aCBubyBuYW1lIHNob3VsZCBhcHBseSBhXG4gICAgICAgICAgICAvLyBwcm9taXNlZCBmdW5jdGlvbi5cbiAgICAgICAgICAgIGlmIChuYW1lID09PSBudWxsIHx8IG5hbWUgPT09IHZvaWQgMCkge1xuICAgICAgICAgICAgICAgIHJldHVybiB2YWx1ZS5hcHBseSh2b2lkIDAsIGFyZ3MpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdmFsdWVbbmFtZV0uYXBwbHkodmFsdWUsIGFyZ3MpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcImFwcGx5XCI6IGZ1bmN0aW9uICh0aGlzcCwgYXJncykge1xuICAgICAgICAgICAgcmV0dXJuIHZhbHVlLmFwcGx5KHRoaXNwLCBhcmdzKTtcbiAgICAgICAgfSxcbiAgICAgICAgXCJrZXlzXCI6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHJldHVybiBvYmplY3Rfa2V5cyh2YWx1ZSk7XG4gICAgICAgIH1cbiAgICB9LCB2b2lkIDAsIGZ1bmN0aW9uIGluc3BlY3QoKSB7XG4gICAgICAgIHJldHVybiB7IHN0YXRlOiBcImZ1bGZpbGxlZFwiLCB2YWx1ZTogdmFsdWUgfTtcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBDb252ZXJ0cyB0aGVuYWJsZXMgdG8gUSBwcm9taXNlcy5cbiAqIEBwYXJhbSBwcm9taXNlIHRoZW5hYmxlIHByb21pc2VcbiAqIEByZXR1cm5zIGEgUSBwcm9taXNlXG4gKi9cbmZ1bmN0aW9uIGNvZXJjZShwcm9taXNlKSB7XG4gICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICBRLm5leHRUaWNrKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHByb21pc2UudGhlbihkZWZlcnJlZC5yZXNvbHZlLCBkZWZlcnJlZC5yZWplY3QsIGRlZmVycmVkLm5vdGlmeSk7XG4gICAgICAgIH0gY2F0Y2ggKGV4Y2VwdGlvbikge1xuICAgICAgICAgICAgZGVmZXJyZWQucmVqZWN0KGV4Y2VwdGlvbik7XG4gICAgICAgIH1cbiAgICB9KTtcbiAgICByZXR1cm4gZGVmZXJyZWQucHJvbWlzZTtcbn1cblxuLyoqXG4gKiBBbm5vdGF0ZXMgYW4gb2JqZWN0IHN1Y2ggdGhhdCBpdCB3aWxsIG5ldmVyIGJlXG4gKiB0cmFuc2ZlcnJlZCBhd2F5IGZyb20gdGhpcyBwcm9jZXNzIG92ZXIgYW55IHByb21pc2VcbiAqIGNvbW11bmljYXRpb24gY2hhbm5lbC5cbiAqIEBwYXJhbSBvYmplY3RcbiAqIEByZXR1cm5zIHByb21pc2UgYSB3cmFwcGluZyBvZiB0aGF0IG9iamVjdCB0aGF0XG4gKiBhZGRpdGlvbmFsbHkgcmVzcG9uZHMgdG8gdGhlIFwiaXNEZWZcIiBtZXNzYWdlXG4gKiB3aXRob3V0IGEgcmVqZWN0aW9uLlxuICovXG5RLm1hc3RlciA9IG1hc3RlcjtcbmZ1bmN0aW9uIG1hc3RlcihvYmplY3QpIHtcbiAgICByZXR1cm4gUHJvbWlzZSh7XG4gICAgICAgIFwiaXNEZWZcIjogZnVuY3Rpb24gKCkge31cbiAgICB9LCBmdW5jdGlvbiBmYWxsYmFjayhvcCwgYXJncykge1xuICAgICAgICByZXR1cm4gZGlzcGF0Y2gob2JqZWN0LCBvcCwgYXJncyk7XG4gICAgfSwgZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gUShvYmplY3QpLmluc3BlY3QoKTtcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBTcHJlYWRzIHRoZSB2YWx1ZXMgb2YgYSBwcm9taXNlZCBhcnJheSBvZiBhcmd1bWVudHMgaW50byB0aGVcbiAqIGZ1bGZpbGxtZW50IGNhbGxiYWNrLlxuICogQHBhcmFtIGZ1bGZpbGxlZCBjYWxsYmFjayB0aGF0IHJlY2VpdmVzIHZhcmlhZGljIGFyZ3VtZW50cyBmcm9tIHRoZVxuICogcHJvbWlzZWQgYXJyYXlcbiAqIEBwYXJhbSByZWplY3RlZCBjYWxsYmFjayB0aGF0IHJlY2VpdmVzIHRoZSBleGNlcHRpb24gaWYgdGhlIHByb21pc2VcbiAqIGlzIHJlamVjdGVkLlxuICogQHJldHVybnMgYSBwcm9taXNlIGZvciB0aGUgcmV0dXJuIHZhbHVlIG9yIHRocm93biBleGNlcHRpb24gb2ZcbiAqIGVpdGhlciBjYWxsYmFjay5cbiAqL1xuUS5zcHJlYWQgPSBzcHJlYWQ7XG5mdW5jdGlvbiBzcHJlYWQodmFsdWUsIGZ1bGZpbGxlZCwgcmVqZWN0ZWQpIHtcbiAgICByZXR1cm4gUSh2YWx1ZSkuc3ByZWFkKGZ1bGZpbGxlZCwgcmVqZWN0ZWQpO1xufVxuXG5Qcm9taXNlLnByb3RvdHlwZS5zcHJlYWQgPSBmdW5jdGlvbiAoZnVsZmlsbGVkLCByZWplY3RlZCkge1xuICAgIHJldHVybiB0aGlzLmFsbCgpLnRoZW4oZnVuY3Rpb24gKGFycmF5KSB7XG4gICAgICAgIHJldHVybiBmdWxmaWxsZWQuYXBwbHkodm9pZCAwLCBhcnJheSk7XG4gICAgfSwgcmVqZWN0ZWQpO1xufTtcblxuLyoqXG4gKiBUaGUgYXN5bmMgZnVuY3Rpb24gaXMgYSBkZWNvcmF0b3IgZm9yIGdlbmVyYXRvciBmdW5jdGlvbnMsIHR1cm5pbmdcbiAqIHRoZW0gaW50byBhc3luY2hyb25vdXMgZ2VuZXJhdG9ycy4gIEFsdGhvdWdoIGdlbmVyYXRvcnMgYXJlIG9ubHkgcGFydFxuICogb2YgdGhlIG5ld2VzdCBFQ01BU2NyaXB0IDYgZHJhZnRzLCB0aGlzIGNvZGUgZG9lcyBub3QgY2F1c2Ugc3ludGF4XG4gKiBlcnJvcnMgaW4gb2xkZXIgZW5naW5lcy4gIFRoaXMgY29kZSBzaG91bGQgY29udGludWUgdG8gd29yayBhbmQgd2lsbFxuICogaW4gZmFjdCBpbXByb3ZlIG92ZXIgdGltZSBhcyB0aGUgbGFuZ3VhZ2UgaW1wcm92ZXMuXG4gKlxuICogRVM2IGdlbmVyYXRvcnMgYXJlIGN1cnJlbnRseSBwYXJ0IG9mIFY4IHZlcnNpb24gMy4xOSB3aXRoIHRoZVxuICogLS1oYXJtb255LWdlbmVyYXRvcnMgcnVudGltZSBmbGFnIGVuYWJsZWQuICBTcGlkZXJNb25rZXkgaGFzIGhhZCB0aGVtXG4gKiBmb3IgbG9uZ2VyLCBidXQgdW5kZXIgYW4gb2xkZXIgUHl0aG9uLWluc3BpcmVkIGZvcm0uICBUaGlzIGZ1bmN0aW9uXG4gKiB3b3JrcyBvbiBib3RoIGtpbmRzIG9mIGdlbmVyYXRvcnMuXG4gKlxuICogRGVjb3JhdGVzIGEgZ2VuZXJhdG9yIGZ1bmN0aW9uIHN1Y2ggdGhhdDpcbiAqICAtIGl0IG1heSB5aWVsZCBwcm9taXNlc1xuICogIC0gZXhlY3V0aW9uIHdpbGwgY29udGludWUgd2hlbiB0aGF0IHByb21pc2UgaXMgZnVsZmlsbGVkXG4gKiAgLSB0aGUgdmFsdWUgb2YgdGhlIHlpZWxkIGV4cHJlc3Npb24gd2lsbCBiZSB0aGUgZnVsZmlsbGVkIHZhbHVlXG4gKiAgLSBpdCByZXR1cm5zIGEgcHJvbWlzZSBmb3IgdGhlIHJldHVybiB2YWx1ZSAod2hlbiB0aGUgZ2VuZXJhdG9yXG4gKiAgICBzdG9wcyBpdGVyYXRpbmcpXG4gKiAgLSB0aGUgZGVjb3JhdGVkIGZ1bmN0aW9uIHJldHVybnMgYSBwcm9taXNlIGZvciB0aGUgcmV0dXJuIHZhbHVlXG4gKiAgICBvZiB0aGUgZ2VuZXJhdG9yIG9yIHRoZSBmaXJzdCByZWplY3RlZCBwcm9taXNlIGFtb25nIHRob3NlXG4gKiAgICB5aWVsZGVkLlxuICogIC0gaWYgYW4gZXJyb3IgaXMgdGhyb3duIGluIHRoZSBnZW5lcmF0b3IsIGl0IHByb3BhZ2F0ZXMgdGhyb3VnaFxuICogICAgZXZlcnkgZm9sbG93aW5nIHlpZWxkIHVudGlsIGl0IGlzIGNhdWdodCwgb3IgdW50aWwgaXQgZXNjYXBlc1xuICogICAgdGhlIGdlbmVyYXRvciBmdW5jdGlvbiBhbHRvZ2V0aGVyLCBhbmQgaXMgdHJhbnNsYXRlZCBpbnRvIGFcbiAqICAgIHJlamVjdGlvbiBmb3IgdGhlIHByb21pc2UgcmV0dXJuZWQgYnkgdGhlIGRlY29yYXRlZCBnZW5lcmF0b3IuXG4gKi9cblEuYXN5bmMgPSBhc3luYztcbmZ1bmN0aW9uIGFzeW5jKG1ha2VHZW5lcmF0b3IpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKCkge1xuICAgICAgICAvLyB3aGVuIHZlcmIgaXMgXCJzZW5kXCIsIGFyZyBpcyBhIHZhbHVlXG4gICAgICAgIC8vIHdoZW4gdmVyYiBpcyBcInRocm93XCIsIGFyZyBpcyBhbiBleGNlcHRpb25cbiAgICAgICAgZnVuY3Rpb24gY29udGludWVyKHZlcmIsIGFyZykge1xuICAgICAgICAgICAgdmFyIHJlc3VsdDtcblxuICAgICAgICAgICAgLy8gVW50aWwgVjggMy4xOSAvIENocm9taXVtIDI5IGlzIHJlbGVhc2VkLCBTcGlkZXJNb25rZXkgaXMgdGhlIG9ubHlcbiAgICAgICAgICAgIC8vIGVuZ2luZSB0aGF0IGhhcyBhIGRlcGxveWVkIGJhc2Ugb2YgYnJvd3NlcnMgdGhhdCBzdXBwb3J0IGdlbmVyYXRvcnMuXG4gICAgICAgICAgICAvLyBIb3dldmVyLCBTTSdzIGdlbmVyYXRvcnMgdXNlIHRoZSBQeXRob24taW5zcGlyZWQgc2VtYW50aWNzIG9mXG4gICAgICAgICAgICAvLyBvdXRkYXRlZCBFUzYgZHJhZnRzLiAgV2Ugd291bGQgbGlrZSB0byBzdXBwb3J0IEVTNiwgYnV0IHdlJ2QgYWxzb1xuICAgICAgICAgICAgLy8gbGlrZSB0byBtYWtlIGl0IHBvc3NpYmxlIHRvIHVzZSBnZW5lcmF0b3JzIGluIGRlcGxveWVkIGJyb3dzZXJzLCBzb1xuICAgICAgICAgICAgLy8gd2UgYWxzbyBzdXBwb3J0IFB5dGhvbi1zdHlsZSBnZW5lcmF0b3JzLiAgQXQgc29tZSBwb2ludCB3ZSBjYW4gcmVtb3ZlXG4gICAgICAgICAgICAvLyB0aGlzIGJsb2NrLlxuXG4gICAgICAgICAgICBpZiAodHlwZW9mIFN0b3BJdGVyYXRpb24gPT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICAgICAgICAgICAgICAvLyBFUzYgR2VuZXJhdG9yc1xuICAgICAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IGdlbmVyYXRvclt2ZXJiXShhcmcpO1xuICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGV4Y2VwdGlvbikge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmVqZWN0KGV4Y2VwdGlvbik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmIChyZXN1bHQuZG9uZSkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gUShyZXN1bHQudmFsdWUpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiB3aGVuKHJlc3VsdC52YWx1ZSwgY2FsbGJhY2ssIGVycmJhY2spO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgLy8gU3BpZGVyTW9ua2V5IEdlbmVyYXRvcnNcbiAgICAgICAgICAgICAgICAvLyBGSVhNRTogUmVtb3ZlIHRoaXMgY2FzZSB3aGVuIFNNIGRvZXMgRVM2IGdlbmVyYXRvcnMuXG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gZ2VuZXJhdG9yW3ZlcmJdKGFyZyk7XG4gICAgICAgICAgICAgICAgfSBjYXRjaCAoZXhjZXB0aW9uKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChpc1N0b3BJdGVyYXRpb24oZXhjZXB0aW9uKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIFEoZXhjZXB0aW9uLnZhbHVlKTtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZWplY3QoZXhjZXB0aW9uKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm4gd2hlbihyZXN1bHQsIGNhbGxiYWNrLCBlcnJiYWNrKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICB2YXIgZ2VuZXJhdG9yID0gbWFrZUdlbmVyYXRvci5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgICAgICB2YXIgY2FsbGJhY2sgPSBjb250aW51ZXIuYmluZChjb250aW51ZXIsIFwibmV4dFwiKTtcbiAgICAgICAgdmFyIGVycmJhY2sgPSBjb250aW51ZXIuYmluZChjb250aW51ZXIsIFwidGhyb3dcIik7XG4gICAgICAgIHJldHVybiBjYWxsYmFjaygpO1xuICAgIH07XG59XG5cbi8qKlxuICogVGhlIHNwYXduIGZ1bmN0aW9uIGlzIGEgc21hbGwgd3JhcHBlciBhcm91bmQgYXN5bmMgdGhhdCBpbW1lZGlhdGVseVxuICogY2FsbHMgdGhlIGdlbmVyYXRvciBhbmQgYWxzbyBlbmRzIHRoZSBwcm9taXNlIGNoYWluLCBzbyB0aGF0IGFueVxuICogdW5oYW5kbGVkIGVycm9ycyBhcmUgdGhyb3duIGluc3RlYWQgb2YgZm9yd2FyZGVkIHRvIHRoZSBlcnJvclxuICogaGFuZGxlci4gVGhpcyBpcyB1c2VmdWwgYmVjYXVzZSBpdCdzIGV4dHJlbWVseSBjb21tb24gdG8gcnVuXG4gKiBnZW5lcmF0b3JzIGF0IHRoZSB0b3AtbGV2ZWwgdG8gd29yayB3aXRoIGxpYnJhcmllcy5cbiAqL1xuUS5zcGF3biA9IHNwYXduO1xuZnVuY3Rpb24gc3Bhd24obWFrZUdlbmVyYXRvcikge1xuICAgIFEuZG9uZShRLmFzeW5jKG1ha2VHZW5lcmF0b3IpKCkpO1xufVxuXG4vLyBGSVhNRTogUmVtb3ZlIHRoaXMgaW50ZXJmYWNlIG9uY2UgRVM2IGdlbmVyYXRvcnMgYXJlIGluIFNwaWRlck1vbmtleS5cbi8qKlxuICogVGhyb3dzIGEgUmV0dXJuVmFsdWUgZXhjZXB0aW9uIHRvIHN0b3AgYW4gYXN5bmNocm9ub3VzIGdlbmVyYXRvci5cbiAqXG4gKiBUaGlzIGludGVyZmFjZSBpcyBhIHN0b3AtZ2FwIG1lYXN1cmUgdG8gc3VwcG9ydCBnZW5lcmF0b3IgcmV0dXJuXG4gKiB2YWx1ZXMgaW4gb2xkZXIgRmlyZWZveC9TcGlkZXJNb25rZXkuICBJbiBicm93c2VycyB0aGF0IHN1cHBvcnQgRVM2XG4gKiBnZW5lcmF0b3JzIGxpa2UgQ2hyb21pdW0gMjksIGp1c3QgdXNlIFwicmV0dXJuXCIgaW4geW91ciBnZW5lcmF0b3JcbiAqIGZ1bmN0aW9ucy5cbiAqXG4gKiBAcGFyYW0gdmFsdWUgdGhlIHJldHVybiB2YWx1ZSBmb3IgdGhlIHN1cnJvdW5kaW5nIGdlbmVyYXRvclxuICogQHRocm93cyBSZXR1cm5WYWx1ZSBleGNlcHRpb24gd2l0aCB0aGUgdmFsdWUuXG4gKiBAZXhhbXBsZVxuICogLy8gRVM2IHN0eWxlXG4gKiBRLmFzeW5jKGZ1bmN0aW9uKiAoKSB7XG4gKiAgICAgIHZhciBmb28gPSB5aWVsZCBnZXRGb29Qcm9taXNlKCk7XG4gKiAgICAgIHZhciBiYXIgPSB5aWVsZCBnZXRCYXJQcm9taXNlKCk7XG4gKiAgICAgIHJldHVybiBmb28gKyBiYXI7XG4gKiB9KVxuICogLy8gT2xkZXIgU3BpZGVyTW9ua2V5IHN0eWxlXG4gKiBRLmFzeW5jKGZ1bmN0aW9uICgpIHtcbiAqICAgICAgdmFyIGZvbyA9IHlpZWxkIGdldEZvb1Byb21pc2UoKTtcbiAqICAgICAgdmFyIGJhciA9IHlpZWxkIGdldEJhclByb21pc2UoKTtcbiAqICAgICAgUS5yZXR1cm4oZm9vICsgYmFyKTtcbiAqIH0pXG4gKi9cblFbXCJyZXR1cm5cIl0gPSBfcmV0dXJuO1xuZnVuY3Rpb24gX3JldHVybih2YWx1ZSkge1xuICAgIHRocm93IG5ldyBRUmV0dXJuVmFsdWUodmFsdWUpO1xufVxuXG4vKipcbiAqIFRoZSBwcm9taXNlZCBmdW5jdGlvbiBkZWNvcmF0b3IgZW5zdXJlcyB0aGF0IGFueSBwcm9taXNlIGFyZ3VtZW50c1xuICogYXJlIHNldHRsZWQgYW5kIHBhc3NlZCBhcyB2YWx1ZXMgKGB0aGlzYCBpcyBhbHNvIHNldHRsZWQgYW5kIHBhc3NlZFxuICogYXMgYSB2YWx1ZSkuICBJdCB3aWxsIGFsc28gZW5zdXJlIHRoYXQgdGhlIHJlc3VsdCBvZiBhIGZ1bmN0aW9uIGlzXG4gKiBhbHdheXMgYSBwcm9taXNlLlxuICpcbiAqIEBleGFtcGxlXG4gKiB2YXIgYWRkID0gUS5wcm9taXNlZChmdW5jdGlvbiAoYSwgYikge1xuICogICAgIHJldHVybiBhICsgYjtcbiAqIH0pO1xuICogYWRkKFEoYSksIFEoQikpO1xuICpcbiAqIEBwYXJhbSB7ZnVuY3Rpb259IGNhbGxiYWNrIFRoZSBmdW5jdGlvbiB0byBkZWNvcmF0ZVxuICogQHJldHVybnMge2Z1bmN0aW9ufSBhIGZ1bmN0aW9uIHRoYXQgaGFzIGJlZW4gZGVjb3JhdGVkLlxuICovXG5RLnByb21pc2VkID0gcHJvbWlzZWQ7XG5mdW5jdGlvbiBwcm9taXNlZChjYWxsYmFjaykge1xuICAgIHJldHVybiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBzcHJlYWQoW3RoaXMsIGFsbChhcmd1bWVudHMpXSwgZnVuY3Rpb24gKHNlbGYsIGFyZ3MpIHtcbiAgICAgICAgICAgIHJldHVybiBjYWxsYmFjay5hcHBseShzZWxmLCBhcmdzKTtcbiAgICAgICAgfSk7XG4gICAgfTtcbn1cblxuLyoqXG4gKiBzZW5kcyBhIG1lc3NhZ2UgdG8gYSB2YWx1ZSBpbiBhIGZ1dHVyZSB0dXJuXG4gKiBAcGFyYW0gb2JqZWN0KiB0aGUgcmVjaXBpZW50XG4gKiBAcGFyYW0gb3AgdGhlIG5hbWUgb2YgdGhlIG1lc3NhZ2Ugb3BlcmF0aW9uLCBlLmcuLCBcIndoZW5cIixcbiAqIEBwYXJhbSBhcmdzIGZ1cnRoZXIgYXJndW1lbnRzIHRvIGJlIGZvcndhcmRlZCB0byB0aGUgb3BlcmF0aW9uXG4gKiBAcmV0dXJucyByZXN1bHQge1Byb21pc2V9IGEgcHJvbWlzZSBmb3IgdGhlIHJlc3VsdCBvZiB0aGUgb3BlcmF0aW9uXG4gKi9cblEuZGlzcGF0Y2ggPSBkaXNwYXRjaDtcbmZ1bmN0aW9uIGRpc3BhdGNoKG9iamVjdCwgb3AsIGFyZ3MpIHtcbiAgICByZXR1cm4gUShvYmplY3QpLmRpc3BhdGNoKG9wLCBhcmdzKTtcbn1cblxuUHJvbWlzZS5wcm90b3R5cGUuZGlzcGF0Y2ggPSBmdW5jdGlvbiAob3AsIGFyZ3MpIHtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICBRLm5leHRUaWNrKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgc2VsZi5wcm9taXNlRGlzcGF0Y2goZGVmZXJyZWQucmVzb2x2ZSwgb3AsIGFyZ3MpO1xuICAgIH0pO1xuICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xufTtcblxuLyoqXG4gKiBHZXRzIHRoZSB2YWx1ZSBvZiBhIHByb3BlcnR5IGluIGEgZnV0dXJlIHR1cm4uXG4gKiBAcGFyYW0gb2JqZWN0ICAgIHByb21pc2Ugb3IgaW1tZWRpYXRlIHJlZmVyZW5jZSBmb3IgdGFyZ2V0IG9iamVjdFxuICogQHBhcmFtIG5hbWUgICAgICBuYW1lIG9mIHByb3BlcnR5IHRvIGdldFxuICogQHJldHVybiBwcm9taXNlIGZvciB0aGUgcHJvcGVydHkgdmFsdWVcbiAqL1xuUS5nZXQgPSBmdW5jdGlvbiAob2JqZWN0LCBrZXkpIHtcbiAgICByZXR1cm4gUShvYmplY3QpLmRpc3BhdGNoKFwiZ2V0XCIsIFtrZXldKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLmdldCA9IGZ1bmN0aW9uIChrZXkpIHtcbiAgICByZXR1cm4gdGhpcy5kaXNwYXRjaChcImdldFwiLCBba2V5XSk7XG59O1xuXG4vKipcbiAqIFNldHMgdGhlIHZhbHVlIG9mIGEgcHJvcGVydHkgaW4gYSBmdXR1cmUgdHVybi5cbiAqIEBwYXJhbSBvYmplY3QgICAgcHJvbWlzZSBvciBpbW1lZGlhdGUgcmVmZXJlbmNlIGZvciBvYmplY3Qgb2JqZWN0XG4gKiBAcGFyYW0gbmFtZSAgICAgIG5hbWUgb2YgcHJvcGVydHkgdG8gc2V0XG4gKiBAcGFyYW0gdmFsdWUgICAgIG5ldyB2YWx1ZSBvZiBwcm9wZXJ0eVxuICogQHJldHVybiBwcm9taXNlIGZvciB0aGUgcmV0dXJuIHZhbHVlXG4gKi9cblEuc2V0ID0gZnVuY3Rpb24gKG9iamVjdCwga2V5LCB2YWx1ZSkge1xuICAgIHJldHVybiBRKG9iamVjdCkuZGlzcGF0Y2goXCJzZXRcIiwgW2tleSwgdmFsdWVdKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLnNldCA9IGZ1bmN0aW9uIChrZXksIHZhbHVlKSB7XG4gICAgcmV0dXJuIHRoaXMuZGlzcGF0Y2goXCJzZXRcIiwgW2tleSwgdmFsdWVdKTtcbn07XG5cbi8qKlxuICogRGVsZXRlcyBhIHByb3BlcnR5IGluIGEgZnV0dXJlIHR1cm4uXG4gKiBAcGFyYW0gb2JqZWN0ICAgIHByb21pc2Ugb3IgaW1tZWRpYXRlIHJlZmVyZW5jZSBmb3IgdGFyZ2V0IG9iamVjdFxuICogQHBhcmFtIG5hbWUgICAgICBuYW1lIG9mIHByb3BlcnR5IHRvIGRlbGV0ZVxuICogQHJldHVybiBwcm9taXNlIGZvciB0aGUgcmV0dXJuIHZhbHVlXG4gKi9cblEuZGVsID0gLy8gWFhYIGxlZ2FjeVxuUVtcImRlbGV0ZVwiXSA9IGZ1bmN0aW9uIChvYmplY3QsIGtleSkge1xuICAgIHJldHVybiBRKG9iamVjdCkuZGlzcGF0Y2goXCJkZWxldGVcIiwgW2tleV0pO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUuZGVsID0gLy8gWFhYIGxlZ2FjeVxuUHJvbWlzZS5wcm90b3R5cGVbXCJkZWxldGVcIl0gPSBmdW5jdGlvbiAoa2V5KSB7XG4gICAgcmV0dXJuIHRoaXMuZGlzcGF0Y2goXCJkZWxldGVcIiwgW2tleV0pO1xufTtcblxuLyoqXG4gKiBJbnZva2VzIGEgbWV0aG9kIGluIGEgZnV0dXJlIHR1cm4uXG4gKiBAcGFyYW0gb2JqZWN0ICAgIHByb21pc2Ugb3IgaW1tZWRpYXRlIHJlZmVyZW5jZSBmb3IgdGFyZ2V0IG9iamVjdFxuICogQHBhcmFtIG5hbWUgICAgICBuYW1lIG9mIG1ldGhvZCB0byBpbnZva2VcbiAqIEBwYXJhbSB2YWx1ZSAgICAgYSB2YWx1ZSB0byBwb3N0LCB0eXBpY2FsbHkgYW4gYXJyYXkgb2ZcbiAqICAgICAgICAgICAgICAgICAgaW52b2NhdGlvbiBhcmd1bWVudHMgZm9yIHByb21pc2VzIHRoYXRcbiAqICAgICAgICAgICAgICAgICAgYXJlIHVsdGltYXRlbHkgYmFja2VkIHdpdGggYHJlc29sdmVgIHZhbHVlcyxcbiAqICAgICAgICAgICAgICAgICAgYXMgb3Bwb3NlZCB0byB0aG9zZSBiYWNrZWQgd2l0aCBVUkxzXG4gKiAgICAgICAgICAgICAgICAgIHdoZXJlaW4gdGhlIHBvc3RlZCB2YWx1ZSBjYW4gYmUgYW55XG4gKiAgICAgICAgICAgICAgICAgIEpTT04gc2VyaWFsaXphYmxlIG9iamVjdC5cbiAqIEByZXR1cm4gcHJvbWlzZSBmb3IgdGhlIHJldHVybiB2YWx1ZVxuICovXG4vLyBib3VuZCBsb2NhbGx5IGJlY2F1c2UgaXQgaXMgdXNlZCBieSBvdGhlciBtZXRob2RzXG5RLm1hcHBseSA9IC8vIFhYWCBBcyBwcm9wb3NlZCBieSBcIlJlZHNhbmRyb1wiXG5RLnBvc3QgPSBmdW5jdGlvbiAob2JqZWN0LCBuYW1lLCBhcmdzKSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS5kaXNwYXRjaChcInBvc3RcIiwgW25hbWUsIGFyZ3NdKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLm1hcHBseSA9IC8vIFhYWCBBcyBwcm9wb3NlZCBieSBcIlJlZHNhbmRyb1wiXG5Qcm9taXNlLnByb3RvdHlwZS5wb3N0ID0gZnVuY3Rpb24gKG5hbWUsIGFyZ3MpIHtcbiAgICByZXR1cm4gdGhpcy5kaXNwYXRjaChcInBvc3RcIiwgW25hbWUsIGFyZ3NdKTtcbn07XG5cbi8qKlxuICogSW52b2tlcyBhIG1ldGhvZCBpbiBhIGZ1dHVyZSB0dXJuLlxuICogQHBhcmFtIG9iamVjdCAgICBwcm9taXNlIG9yIGltbWVkaWF0ZSByZWZlcmVuY2UgZm9yIHRhcmdldCBvYmplY3RcbiAqIEBwYXJhbSBuYW1lICAgICAgbmFtZSBvZiBtZXRob2QgdG8gaW52b2tlXG4gKiBAcGFyYW0gLi4uYXJncyAgIGFycmF5IG9mIGludm9jYXRpb24gYXJndW1lbnRzXG4gKiBAcmV0dXJuIHByb21pc2UgZm9yIHRoZSByZXR1cm4gdmFsdWVcbiAqL1xuUS5zZW5kID0gLy8gWFhYIE1hcmsgTWlsbGVyJ3MgcHJvcG9zZWQgcGFybGFuY2VcblEubWNhbGwgPSAvLyBYWFggQXMgcHJvcG9zZWQgYnkgXCJSZWRzYW5kcm9cIlxuUS5pbnZva2UgPSBmdW5jdGlvbiAob2JqZWN0LCBuYW1lIC8qLi4uYXJncyovKSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS5kaXNwYXRjaChcInBvc3RcIiwgW25hbWUsIGFycmF5X3NsaWNlKGFyZ3VtZW50cywgMildKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLnNlbmQgPSAvLyBYWFggTWFyayBNaWxsZXIncyBwcm9wb3NlZCBwYXJsYW5jZVxuUHJvbWlzZS5wcm90b3R5cGUubWNhbGwgPSAvLyBYWFggQXMgcHJvcG9zZWQgYnkgXCJSZWRzYW5kcm9cIlxuUHJvbWlzZS5wcm90b3R5cGUuaW52b2tlID0gZnVuY3Rpb24gKG5hbWUgLyouLi5hcmdzKi8pIHtcbiAgICByZXR1cm4gdGhpcy5kaXNwYXRjaChcInBvc3RcIiwgW25hbWUsIGFycmF5X3NsaWNlKGFyZ3VtZW50cywgMSldKTtcbn07XG5cbi8qKlxuICogQXBwbGllcyB0aGUgcHJvbWlzZWQgZnVuY3Rpb24gaW4gYSBmdXR1cmUgdHVybi5cbiAqIEBwYXJhbSBvYmplY3QgICAgcHJvbWlzZSBvciBpbW1lZGlhdGUgcmVmZXJlbmNlIGZvciB0YXJnZXQgZnVuY3Rpb25cbiAqIEBwYXJhbSBhcmdzICAgICAgYXJyYXkgb2YgYXBwbGljYXRpb24gYXJndW1lbnRzXG4gKi9cblEuZmFwcGx5ID0gZnVuY3Rpb24gKG9iamVjdCwgYXJncykge1xuICAgIHJldHVybiBRKG9iamVjdCkuZGlzcGF0Y2goXCJhcHBseVwiLCBbdm9pZCAwLCBhcmdzXSk7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS5mYXBwbHkgPSBmdW5jdGlvbiAoYXJncykge1xuICAgIHJldHVybiB0aGlzLmRpc3BhdGNoKFwiYXBwbHlcIiwgW3ZvaWQgMCwgYXJnc10pO1xufTtcblxuLyoqXG4gKiBDYWxscyB0aGUgcHJvbWlzZWQgZnVuY3Rpb24gaW4gYSBmdXR1cmUgdHVybi5cbiAqIEBwYXJhbSBvYmplY3QgICAgcHJvbWlzZSBvciBpbW1lZGlhdGUgcmVmZXJlbmNlIGZvciB0YXJnZXQgZnVuY3Rpb25cbiAqIEBwYXJhbSAuLi5hcmdzICAgYXJyYXkgb2YgYXBwbGljYXRpb24gYXJndW1lbnRzXG4gKi9cblFbXCJ0cnlcIl0gPVxuUS5mY2FsbCA9IGZ1bmN0aW9uIChvYmplY3QgLyogLi4uYXJncyovKSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS5kaXNwYXRjaChcImFwcGx5XCIsIFt2b2lkIDAsIGFycmF5X3NsaWNlKGFyZ3VtZW50cywgMSldKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLmZjYWxsID0gZnVuY3Rpb24gKC8qLi4uYXJncyovKSB7XG4gICAgcmV0dXJuIHRoaXMuZGlzcGF0Y2goXCJhcHBseVwiLCBbdm9pZCAwLCBhcnJheV9zbGljZShhcmd1bWVudHMpXSk7XG59O1xuXG4vKipcbiAqIEJpbmRzIHRoZSBwcm9taXNlZCBmdW5jdGlvbiwgdHJhbnNmb3JtaW5nIHJldHVybiB2YWx1ZXMgaW50byBhIGZ1bGZpbGxlZFxuICogcHJvbWlzZSBhbmQgdGhyb3duIGVycm9ycyBpbnRvIGEgcmVqZWN0ZWQgb25lLlxuICogQHBhcmFtIG9iamVjdCAgICBwcm9taXNlIG9yIGltbWVkaWF0ZSByZWZlcmVuY2UgZm9yIHRhcmdldCBmdW5jdGlvblxuICogQHBhcmFtIC4uLmFyZ3MgICBhcnJheSBvZiBhcHBsaWNhdGlvbiBhcmd1bWVudHNcbiAqL1xuUS5mYmluZCA9IGZ1bmN0aW9uIChvYmplY3QgLyouLi5hcmdzKi8pIHtcbiAgICB2YXIgcHJvbWlzZSA9IFEob2JqZWN0KTtcbiAgICB2YXIgYXJncyA9IGFycmF5X3NsaWNlKGFyZ3VtZW50cywgMSk7XG4gICAgcmV0dXJuIGZ1bmN0aW9uIGZib3VuZCgpIHtcbiAgICAgICAgcmV0dXJuIHByb21pc2UuZGlzcGF0Y2goXCJhcHBseVwiLCBbXG4gICAgICAgICAgICB0aGlzLFxuICAgICAgICAgICAgYXJncy5jb25jYXQoYXJyYXlfc2xpY2UoYXJndW1lbnRzKSlcbiAgICAgICAgXSk7XG4gICAgfTtcbn07XG5Qcm9taXNlLnByb3RvdHlwZS5mYmluZCA9IGZ1bmN0aW9uICgvKi4uLmFyZ3MqLykge1xuICAgIHZhciBwcm9taXNlID0gdGhpcztcbiAgICB2YXIgYXJncyA9IGFycmF5X3NsaWNlKGFyZ3VtZW50cyk7XG4gICAgcmV0dXJuIGZ1bmN0aW9uIGZib3VuZCgpIHtcbiAgICAgICAgcmV0dXJuIHByb21pc2UuZGlzcGF0Y2goXCJhcHBseVwiLCBbXG4gICAgICAgICAgICB0aGlzLFxuICAgICAgICAgICAgYXJncy5jb25jYXQoYXJyYXlfc2xpY2UoYXJndW1lbnRzKSlcbiAgICAgICAgXSk7XG4gICAgfTtcbn07XG5cbi8qKlxuICogUmVxdWVzdHMgdGhlIG5hbWVzIG9mIHRoZSBvd25lZCBwcm9wZXJ0aWVzIG9mIGEgcHJvbWlzZWRcbiAqIG9iamVjdCBpbiBhIGZ1dHVyZSB0dXJuLlxuICogQHBhcmFtIG9iamVjdCAgICBwcm9taXNlIG9yIGltbWVkaWF0ZSByZWZlcmVuY2UgZm9yIHRhcmdldCBvYmplY3RcbiAqIEByZXR1cm4gcHJvbWlzZSBmb3IgdGhlIGtleXMgb2YgdGhlIGV2ZW50dWFsbHkgc2V0dGxlZCBvYmplY3RcbiAqL1xuUS5rZXlzID0gZnVuY3Rpb24gKG9iamVjdCkge1xuICAgIHJldHVybiBRKG9iamVjdCkuZGlzcGF0Y2goXCJrZXlzXCIsIFtdKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLmtleXMgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIHRoaXMuZGlzcGF0Y2goXCJrZXlzXCIsIFtdKTtcbn07XG5cbi8qKlxuICogVHVybnMgYW4gYXJyYXkgb2YgcHJvbWlzZXMgaW50byBhIHByb21pc2UgZm9yIGFuIGFycmF5LiAgSWYgYW55IG9mXG4gKiB0aGUgcHJvbWlzZXMgZ2V0cyByZWplY3RlZCwgdGhlIHdob2xlIGFycmF5IGlzIHJlamVjdGVkIGltbWVkaWF0ZWx5LlxuICogQHBhcmFtIHtBcnJheSp9IGFuIGFycmF5IChvciBwcm9taXNlIGZvciBhbiBhcnJheSkgb2YgdmFsdWVzIChvclxuICogcHJvbWlzZXMgZm9yIHZhbHVlcylcbiAqIEByZXR1cm5zIGEgcHJvbWlzZSBmb3IgYW4gYXJyYXkgb2YgdGhlIGNvcnJlc3BvbmRpbmcgdmFsdWVzXG4gKi9cbi8vIEJ5IE1hcmsgTWlsbGVyXG4vLyBodHRwOi8vd2lraS5lY21hc2NyaXB0Lm9yZy9kb2t1LnBocD9pZD1zdHJhd21hbjpjb25jdXJyZW5jeSZyZXY9MTMwODc3NjUyMSNhbGxmdWxmaWxsZWRcblEuYWxsID0gYWxsO1xuZnVuY3Rpb24gYWxsKHByb21pc2VzKSB7XG4gICAgcmV0dXJuIHdoZW4ocHJvbWlzZXMsIGZ1bmN0aW9uIChwcm9taXNlcykge1xuICAgICAgICB2YXIgcGVuZGluZ0NvdW50ID0gMDtcbiAgICAgICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICAgICAgYXJyYXlfcmVkdWNlKHByb21pc2VzLCBmdW5jdGlvbiAodW5kZWZpbmVkLCBwcm9taXNlLCBpbmRleCkge1xuICAgICAgICAgICAgdmFyIHNuYXBzaG90O1xuICAgICAgICAgICAgaWYgKFxuICAgICAgICAgICAgICAgIGlzUHJvbWlzZShwcm9taXNlKSAmJlxuICAgICAgICAgICAgICAgIChzbmFwc2hvdCA9IHByb21pc2UuaW5zcGVjdCgpKS5zdGF0ZSA9PT0gXCJmdWxmaWxsZWRcIlxuICAgICAgICAgICAgKSB7XG4gICAgICAgICAgICAgICAgcHJvbWlzZXNbaW5kZXhdID0gc25hcHNob3QudmFsdWU7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICsrcGVuZGluZ0NvdW50O1xuICAgICAgICAgICAgICAgIHdoZW4oXG4gICAgICAgICAgICAgICAgICAgIHByb21pc2UsXG4gICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcHJvbWlzZXNbaW5kZXhdID0gdmFsdWU7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoLS1wZW5kaW5nQ291bnQgPT09IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWZlcnJlZC5yZXNvbHZlKHByb21pc2VzKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgZGVmZXJyZWQucmVqZWN0LFxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbiAocHJvZ3Jlc3MpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGRlZmVycmVkLm5vdGlmeSh7IGluZGV4OiBpbmRleCwgdmFsdWU6IHByb2dyZXNzIH0pO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSwgdm9pZCAwKTtcbiAgICAgICAgaWYgKHBlbmRpbmdDb3VudCA9PT0gMCkge1xuICAgICAgICAgICAgZGVmZXJyZWQucmVzb2x2ZShwcm9taXNlcyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG4gICAgfSk7XG59XG5cblByb21pc2UucHJvdG90eXBlLmFsbCA9IGZ1bmN0aW9uICgpIHtcbiAgICByZXR1cm4gYWxsKHRoaXMpO1xufTtcblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBmaXJzdCByZXNvbHZlZCBwcm9taXNlIG9mIGFuIGFycmF5LiBQcmlvciByZWplY3RlZCBwcm9taXNlcyBhcmVcbiAqIGlnbm9yZWQuICBSZWplY3RzIG9ubHkgaWYgYWxsIHByb21pc2VzIGFyZSByZWplY3RlZC5cbiAqIEBwYXJhbSB7QXJyYXkqfSBhbiBhcnJheSBjb250YWluaW5nIHZhbHVlcyBvciBwcm9taXNlcyBmb3IgdmFsdWVzXG4gKiBAcmV0dXJucyBhIHByb21pc2UgZnVsZmlsbGVkIHdpdGggdGhlIHZhbHVlIG9mIHRoZSBmaXJzdCByZXNvbHZlZCBwcm9taXNlLFxuICogb3IgYSByZWplY3RlZCBwcm9taXNlIGlmIGFsbCBwcm9taXNlcyBhcmUgcmVqZWN0ZWQuXG4gKi9cblEuYW55ID0gYW55O1xuXG5mdW5jdGlvbiBhbnkocHJvbWlzZXMpIHtcbiAgICBpZiAocHJvbWlzZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIHJldHVybiBRLnJlc29sdmUoKTtcbiAgICB9XG5cbiAgICB2YXIgZGVmZXJyZWQgPSBRLmRlZmVyKCk7XG4gICAgdmFyIHBlbmRpbmdDb3VudCA9IDA7XG4gICAgYXJyYXlfcmVkdWNlKHByb21pc2VzLCBmdW5jdGlvbiAocHJldiwgY3VycmVudCwgaW5kZXgpIHtcbiAgICAgICAgdmFyIHByb21pc2UgPSBwcm9taXNlc1tpbmRleF07XG5cbiAgICAgICAgcGVuZGluZ0NvdW50Kys7XG5cbiAgICAgICAgd2hlbihwcm9taXNlLCBvbkZ1bGZpbGxlZCwgb25SZWplY3RlZCwgb25Qcm9ncmVzcyk7XG4gICAgICAgIGZ1bmN0aW9uIG9uRnVsZmlsbGVkKHJlc3VsdCkge1xuICAgICAgICAgICAgZGVmZXJyZWQucmVzb2x2ZShyZXN1bHQpO1xuICAgICAgICB9XG4gICAgICAgIGZ1bmN0aW9uIG9uUmVqZWN0ZWQoKSB7XG4gICAgICAgICAgICBwZW5kaW5nQ291bnQtLTtcbiAgICAgICAgICAgIGlmIChwZW5kaW5nQ291bnQgPT09IDApIHtcbiAgICAgICAgICAgICAgICBkZWZlcnJlZC5yZWplY3QobmV3IEVycm9yKFxuICAgICAgICAgICAgICAgICAgICBcIkNhbid0IGdldCBmdWxmaWxsbWVudCB2YWx1ZSBmcm9tIGFueSBwcm9taXNlLCBhbGwgXCIgK1xuICAgICAgICAgICAgICAgICAgICBcInByb21pc2VzIHdlcmUgcmVqZWN0ZWQuXCJcbiAgICAgICAgICAgICAgICApKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBmdW5jdGlvbiBvblByb2dyZXNzKHByb2dyZXNzKSB7XG4gICAgICAgICAgICBkZWZlcnJlZC5ub3RpZnkoe1xuICAgICAgICAgICAgICAgIGluZGV4OiBpbmRleCxcbiAgICAgICAgICAgICAgICB2YWx1ZTogcHJvZ3Jlc3NcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgfSwgdW5kZWZpbmVkKTtcblxuICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xufVxuXG5Qcm9taXNlLnByb3RvdHlwZS5hbnkgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIGFueSh0aGlzKTtcbn07XG5cbi8qKlxuICogV2FpdHMgZm9yIGFsbCBwcm9taXNlcyB0byBiZSBzZXR0bGVkLCBlaXRoZXIgZnVsZmlsbGVkIG9yXG4gKiByZWplY3RlZC4gIFRoaXMgaXMgZGlzdGluY3QgZnJvbSBgYWxsYCBzaW5jZSB0aGF0IHdvdWxkIHN0b3BcbiAqIHdhaXRpbmcgYXQgdGhlIGZpcnN0IHJlamVjdGlvbi4gIFRoZSBwcm9taXNlIHJldHVybmVkIGJ5XG4gKiBgYWxsUmVzb2x2ZWRgIHdpbGwgbmV2ZXIgYmUgcmVqZWN0ZWQuXG4gKiBAcGFyYW0gcHJvbWlzZXMgYSBwcm9taXNlIGZvciBhbiBhcnJheSAob3IgYW4gYXJyYXkpIG9mIHByb21pc2VzXG4gKiAob3IgdmFsdWVzKVxuICogQHJldHVybiBhIHByb21pc2UgZm9yIGFuIGFycmF5IG9mIHByb21pc2VzXG4gKi9cblEuYWxsUmVzb2x2ZWQgPSBkZXByZWNhdGUoYWxsUmVzb2x2ZWQsIFwiYWxsUmVzb2x2ZWRcIiwgXCJhbGxTZXR0bGVkXCIpO1xuZnVuY3Rpb24gYWxsUmVzb2x2ZWQocHJvbWlzZXMpIHtcbiAgICByZXR1cm4gd2hlbihwcm9taXNlcywgZnVuY3Rpb24gKHByb21pc2VzKSB7XG4gICAgICAgIHByb21pc2VzID0gYXJyYXlfbWFwKHByb21pc2VzLCBRKTtcbiAgICAgICAgcmV0dXJuIHdoZW4oYWxsKGFycmF5X21hcChwcm9taXNlcywgZnVuY3Rpb24gKHByb21pc2UpIHtcbiAgICAgICAgICAgIHJldHVybiB3aGVuKHByb21pc2UsIG5vb3AsIG5vb3ApO1xuICAgICAgICB9KSksIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlcztcbiAgICAgICAgfSk7XG4gICAgfSk7XG59XG5cblByb21pc2UucHJvdG90eXBlLmFsbFJlc29sdmVkID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiBhbGxSZXNvbHZlZCh0aGlzKTtcbn07XG5cbi8qKlxuICogQHNlZSBQcm9taXNlI2FsbFNldHRsZWRcbiAqL1xuUS5hbGxTZXR0bGVkID0gYWxsU2V0dGxlZDtcbmZ1bmN0aW9uIGFsbFNldHRsZWQocHJvbWlzZXMpIHtcbiAgICByZXR1cm4gUShwcm9taXNlcykuYWxsU2V0dGxlZCgpO1xufVxuXG4vKipcbiAqIFR1cm5zIGFuIGFycmF5IG9mIHByb21pc2VzIGludG8gYSBwcm9taXNlIGZvciBhbiBhcnJheSBvZiB0aGVpciBzdGF0ZXMgKGFzXG4gKiByZXR1cm5lZCBieSBgaW5zcGVjdGApIHdoZW4gdGhleSBoYXZlIGFsbCBzZXR0bGVkLlxuICogQHBhcmFtIHtBcnJheVtBbnkqXX0gdmFsdWVzIGFuIGFycmF5IChvciBwcm9taXNlIGZvciBhbiBhcnJheSkgb2YgdmFsdWVzIChvclxuICogcHJvbWlzZXMgZm9yIHZhbHVlcylcbiAqIEByZXR1cm5zIHtBcnJheVtTdGF0ZV19IGFuIGFycmF5IG9mIHN0YXRlcyBmb3IgdGhlIHJlc3BlY3RpdmUgdmFsdWVzLlxuICovXG5Qcm9taXNlLnByb3RvdHlwZS5hbGxTZXR0bGVkID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiB0aGlzLnRoZW4oZnVuY3Rpb24gKHByb21pc2VzKSB7XG4gICAgICAgIHJldHVybiBhbGwoYXJyYXlfbWFwKHByb21pc2VzLCBmdW5jdGlvbiAocHJvbWlzZSkge1xuICAgICAgICAgICAgcHJvbWlzZSA9IFEocHJvbWlzZSk7XG4gICAgICAgICAgICBmdW5jdGlvbiByZWdhcmRsZXNzKCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBwcm9taXNlLmluc3BlY3QoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlLnRoZW4ocmVnYXJkbGVzcywgcmVnYXJkbGVzcyk7XG4gICAgICAgIH0pKTtcbiAgICB9KTtcbn07XG5cbi8qKlxuICogQ2FwdHVyZXMgdGhlIGZhaWx1cmUgb2YgYSBwcm9taXNlLCBnaXZpbmcgYW4gb3BvcnR1bml0eSB0byByZWNvdmVyXG4gKiB3aXRoIGEgY2FsbGJhY2suICBJZiB0aGUgZ2l2ZW4gcHJvbWlzZSBpcyBmdWxmaWxsZWQsIHRoZSByZXR1cm5lZFxuICogcHJvbWlzZSBpcyBmdWxmaWxsZWQuXG4gKiBAcGFyYW0ge0FueSp9IHByb21pc2UgZm9yIHNvbWV0aGluZ1xuICogQHBhcmFtIHtGdW5jdGlvbn0gY2FsbGJhY2sgdG8gZnVsZmlsbCB0aGUgcmV0dXJuZWQgcHJvbWlzZSBpZiB0aGVcbiAqIGdpdmVuIHByb21pc2UgaXMgcmVqZWN0ZWRcbiAqIEByZXR1cm5zIGEgcHJvbWlzZSBmb3IgdGhlIHJldHVybiB2YWx1ZSBvZiB0aGUgY2FsbGJhY2tcbiAqL1xuUS5mYWlsID0gLy8gWFhYIGxlZ2FjeVxuUVtcImNhdGNoXCJdID0gZnVuY3Rpb24gKG9iamVjdCwgcmVqZWN0ZWQpIHtcbiAgICByZXR1cm4gUShvYmplY3QpLnRoZW4odm9pZCAwLCByZWplY3RlZCk7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS5mYWlsID0gLy8gWFhYIGxlZ2FjeVxuUHJvbWlzZS5wcm90b3R5cGVbXCJjYXRjaFwiXSA9IGZ1bmN0aW9uIChyZWplY3RlZCkge1xuICAgIHJldHVybiB0aGlzLnRoZW4odm9pZCAwLCByZWplY3RlZCk7XG59O1xuXG4vKipcbiAqIEF0dGFjaGVzIGEgbGlzdGVuZXIgdGhhdCBjYW4gcmVzcG9uZCB0byBwcm9ncmVzcyBub3RpZmljYXRpb25zIGZyb20gYVxuICogcHJvbWlzZSdzIG9yaWdpbmF0aW5nIGRlZmVycmVkLiBUaGlzIGxpc3RlbmVyIHJlY2VpdmVzIHRoZSBleGFjdCBhcmd1bWVudHNcbiAqIHBhc3NlZCB0byBgYGRlZmVycmVkLm5vdGlmeWBgLlxuICogQHBhcmFtIHtBbnkqfSBwcm9taXNlIGZvciBzb21ldGhpbmdcbiAqIEBwYXJhbSB7RnVuY3Rpb259IGNhbGxiYWNrIHRvIHJlY2VpdmUgYW55IHByb2dyZXNzIG5vdGlmaWNhdGlvbnNcbiAqIEByZXR1cm5zIHRoZSBnaXZlbiBwcm9taXNlLCB1bmNoYW5nZWRcbiAqL1xuUS5wcm9ncmVzcyA9IHByb2dyZXNzO1xuZnVuY3Rpb24gcHJvZ3Jlc3Mob2JqZWN0LCBwcm9ncmVzc2VkKSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS50aGVuKHZvaWQgMCwgdm9pZCAwLCBwcm9ncmVzc2VkKTtcbn1cblxuUHJvbWlzZS5wcm90b3R5cGUucHJvZ3Jlc3MgPSBmdW5jdGlvbiAocHJvZ3Jlc3NlZCkge1xuICAgIHJldHVybiB0aGlzLnRoZW4odm9pZCAwLCB2b2lkIDAsIHByb2dyZXNzZWQpO1xufTtcblxuLyoqXG4gKiBQcm92aWRlcyBhbiBvcHBvcnR1bml0eSB0byBvYnNlcnZlIHRoZSBzZXR0bGluZyBvZiBhIHByb21pc2UsXG4gKiByZWdhcmRsZXNzIG9mIHdoZXRoZXIgdGhlIHByb21pc2UgaXMgZnVsZmlsbGVkIG9yIHJlamVjdGVkLiAgRm9yd2FyZHNcbiAqIHRoZSByZXNvbHV0aW9uIHRvIHRoZSByZXR1cm5lZCBwcm9taXNlIHdoZW4gdGhlIGNhbGxiYWNrIGlzIGRvbmUuXG4gKiBUaGUgY2FsbGJhY2sgY2FuIHJldHVybiBhIHByb21pc2UgdG8gZGVmZXIgY29tcGxldGlvbi5cbiAqIEBwYXJhbSB7QW55Kn0gcHJvbWlzZVxuICogQHBhcmFtIHtGdW5jdGlvbn0gY2FsbGJhY2sgdG8gb2JzZXJ2ZSB0aGUgcmVzb2x1dGlvbiBvZiB0aGUgZ2l2ZW5cbiAqIHByb21pc2UsIHRha2VzIG5vIGFyZ3VtZW50cy5cbiAqIEByZXR1cm5zIGEgcHJvbWlzZSBmb3IgdGhlIHJlc29sdXRpb24gb2YgdGhlIGdpdmVuIHByb21pc2Ugd2hlblxuICogYGBmaW5gYCBpcyBkb25lLlxuICovXG5RLmZpbiA9IC8vIFhYWCBsZWdhY3lcblFbXCJmaW5hbGx5XCJdID0gZnVuY3Rpb24gKG9iamVjdCwgY2FsbGJhY2spIHtcbiAgICByZXR1cm4gUShvYmplY3QpW1wiZmluYWxseVwiXShjYWxsYmFjayk7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS5maW4gPSAvLyBYWFggbGVnYWN5XG5Qcm9taXNlLnByb3RvdHlwZVtcImZpbmFsbHlcIl0gPSBmdW5jdGlvbiAoY2FsbGJhY2spIHtcbiAgICBjYWxsYmFjayA9IFEoY2FsbGJhY2spO1xuICAgIHJldHVybiB0aGlzLnRoZW4oZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIHJldHVybiBjYWxsYmFjay5mY2FsbCgpLnRoZW4oZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgICB9KTtcbiAgICB9LCBmdW5jdGlvbiAocmVhc29uKSB7XG4gICAgICAgIC8vIFRPRE8gYXR0ZW1wdCB0byByZWN5Y2xlIHRoZSByZWplY3Rpb24gd2l0aCBcInRoaXNcIi5cbiAgICAgICAgcmV0dXJuIGNhbGxiYWNrLmZjYWxsKCkudGhlbihmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICB0aHJvdyByZWFzb247XG4gICAgICAgIH0pO1xuICAgIH0pO1xufTtcblxuLyoqXG4gKiBUZXJtaW5hdGVzIGEgY2hhaW4gb2YgcHJvbWlzZXMsIGZvcmNpbmcgcmVqZWN0aW9ucyB0byBiZVxuICogdGhyb3duIGFzIGV4Y2VwdGlvbnMuXG4gKiBAcGFyYW0ge0FueSp9IHByb21pc2UgYXQgdGhlIGVuZCBvZiBhIGNoYWluIG9mIHByb21pc2VzXG4gKiBAcmV0dXJucyBub3RoaW5nXG4gKi9cblEuZG9uZSA9IGZ1bmN0aW9uIChvYmplY3QsIGZ1bGZpbGxlZCwgcmVqZWN0ZWQsIHByb2dyZXNzKSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS5kb25lKGZ1bGZpbGxlZCwgcmVqZWN0ZWQsIHByb2dyZXNzKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLmRvbmUgPSBmdW5jdGlvbiAoZnVsZmlsbGVkLCByZWplY3RlZCwgcHJvZ3Jlc3MpIHtcbiAgICB2YXIgb25VbmhhbmRsZWRFcnJvciA9IGZ1bmN0aW9uIChlcnJvcikge1xuICAgICAgICAvLyBmb3J3YXJkIHRvIGEgZnV0dXJlIHR1cm4gc28gdGhhdCBgYHdoZW5gYFxuICAgICAgICAvLyBkb2VzIG5vdCBjYXRjaCBpdCBhbmQgdHVybiBpdCBpbnRvIGEgcmVqZWN0aW9uLlxuICAgICAgICBRLm5leHRUaWNrKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIG1ha2VTdGFja1RyYWNlTG9uZyhlcnJvciwgcHJvbWlzZSk7XG4gICAgICAgICAgICBpZiAoUS5vbmVycm9yKSB7XG4gICAgICAgICAgICAgICAgUS5vbmVycm9yKGVycm9yKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICAvLyBBdm9pZCB1bm5lY2Vzc2FyeSBgbmV4dFRpY2tgaW5nIHZpYSBhbiB1bm5lY2Vzc2FyeSBgd2hlbmAuXG4gICAgdmFyIHByb21pc2UgPSBmdWxmaWxsZWQgfHwgcmVqZWN0ZWQgfHwgcHJvZ3Jlc3MgP1xuICAgICAgICB0aGlzLnRoZW4oZnVsZmlsbGVkLCByZWplY3RlZCwgcHJvZ3Jlc3MpIDpcbiAgICAgICAgdGhpcztcblxuICAgIGlmICh0eXBlb2YgcHJvY2VzcyA9PT0gXCJvYmplY3RcIiAmJiBwcm9jZXNzICYmIHByb2Nlc3MuZG9tYWluKSB7XG4gICAgICAgIG9uVW5oYW5kbGVkRXJyb3IgPSBwcm9jZXNzLmRvbWFpbi5iaW5kKG9uVW5oYW5kbGVkRXJyb3IpO1xuICAgIH1cblxuICAgIHByb21pc2UudGhlbih2b2lkIDAsIG9uVW5oYW5kbGVkRXJyb3IpO1xufTtcblxuLyoqXG4gKiBDYXVzZXMgYSBwcm9taXNlIHRvIGJlIHJlamVjdGVkIGlmIGl0IGRvZXMgbm90IGdldCBmdWxmaWxsZWQgYmVmb3JlXG4gKiBzb21lIG1pbGxpc2Vjb25kcyB0aW1lIG91dC5cbiAqIEBwYXJhbSB7QW55Kn0gcHJvbWlzZVxuICogQHBhcmFtIHtOdW1iZXJ9IG1pbGxpc2Vjb25kcyB0aW1lb3V0XG4gKiBAcGFyYW0ge0FueSp9IGN1c3RvbSBlcnJvciBtZXNzYWdlIG9yIEVycm9yIG9iamVjdCAob3B0aW9uYWwpXG4gKiBAcmV0dXJucyBhIHByb21pc2UgZm9yIHRoZSByZXNvbHV0aW9uIG9mIHRoZSBnaXZlbiBwcm9taXNlIGlmIGl0IGlzXG4gKiBmdWxmaWxsZWQgYmVmb3JlIHRoZSB0aW1lb3V0LCBvdGhlcndpc2UgcmVqZWN0ZWQuXG4gKi9cblEudGltZW91dCA9IGZ1bmN0aW9uIChvYmplY3QsIG1zLCBlcnJvcikge1xuICAgIHJldHVybiBRKG9iamVjdCkudGltZW91dChtcywgZXJyb3IpO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUudGltZW91dCA9IGZ1bmN0aW9uIChtcywgZXJyb3IpIHtcbiAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgIHZhciB0aW1lb3V0SWQgPSBzZXRUaW1lb3V0KGZ1bmN0aW9uICgpIHtcbiAgICAgICAgaWYgKCFlcnJvciB8fCBcInN0cmluZ1wiID09PSB0eXBlb2YgZXJyb3IpIHtcbiAgICAgICAgICAgIGVycm9yID0gbmV3IEVycm9yKGVycm9yIHx8IFwiVGltZWQgb3V0IGFmdGVyIFwiICsgbXMgKyBcIiBtc1wiKTtcbiAgICAgICAgICAgIGVycm9yLmNvZGUgPSBcIkVUSU1FRE9VVFwiO1xuICAgICAgICB9XG4gICAgICAgIGRlZmVycmVkLnJlamVjdChlcnJvcik7XG4gICAgfSwgbXMpO1xuXG4gICAgdGhpcy50aGVuKGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dElkKTtcbiAgICAgICAgZGVmZXJyZWQucmVzb2x2ZSh2YWx1ZSk7XG4gICAgfSwgZnVuY3Rpb24gKGV4Y2VwdGlvbikge1xuICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dElkKTtcbiAgICAgICAgZGVmZXJyZWQucmVqZWN0KGV4Y2VwdGlvbik7XG4gICAgfSwgZGVmZXJyZWQubm90aWZ5KTtcblxuICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xufTtcblxuLyoqXG4gKiBSZXR1cm5zIGEgcHJvbWlzZSBmb3IgdGhlIGdpdmVuIHZhbHVlIChvciBwcm9taXNlZCB2YWx1ZSksIHNvbWVcbiAqIG1pbGxpc2Vjb25kcyBhZnRlciBpdCByZXNvbHZlZC4gUGFzc2VzIHJlamVjdGlvbnMgaW1tZWRpYXRlbHkuXG4gKiBAcGFyYW0ge0FueSp9IHByb21pc2VcbiAqIEBwYXJhbSB7TnVtYmVyfSBtaWxsaXNlY29uZHNcbiAqIEByZXR1cm5zIGEgcHJvbWlzZSBmb3IgdGhlIHJlc29sdXRpb24gb2YgdGhlIGdpdmVuIHByb21pc2UgYWZ0ZXIgbWlsbGlzZWNvbmRzXG4gKiB0aW1lIGhhcyBlbGFwc2VkIHNpbmNlIHRoZSByZXNvbHV0aW9uIG9mIHRoZSBnaXZlbiBwcm9taXNlLlxuICogSWYgdGhlIGdpdmVuIHByb21pc2UgcmVqZWN0cywgdGhhdCBpcyBwYXNzZWQgaW1tZWRpYXRlbHkuXG4gKi9cblEuZGVsYXkgPSBmdW5jdGlvbiAob2JqZWN0LCB0aW1lb3V0KSB7XG4gICAgaWYgKHRpbWVvdXQgPT09IHZvaWQgMCkge1xuICAgICAgICB0aW1lb3V0ID0gb2JqZWN0O1xuICAgICAgICBvYmplY3QgPSB2b2lkIDA7XG4gICAgfVxuICAgIHJldHVybiBRKG9iamVjdCkuZGVsYXkodGltZW91dCk7XG59O1xuXG5Qcm9taXNlLnByb3RvdHlwZS5kZWxheSA9IGZ1bmN0aW9uICh0aW1lb3V0KSB7XG4gICAgcmV0dXJuIHRoaXMudGhlbihmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICAgICAgc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBkZWZlcnJlZC5yZXNvbHZlKHZhbHVlKTtcbiAgICAgICAgfSwgdGltZW91dCk7XG4gICAgICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xuICAgIH0pO1xufTtcblxuLyoqXG4gKiBQYXNzZXMgYSBjb250aW51YXRpb24gdG8gYSBOb2RlIGZ1bmN0aW9uLCB3aGljaCBpcyBjYWxsZWQgd2l0aCB0aGUgZ2l2ZW5cbiAqIGFyZ3VtZW50cyBwcm92aWRlZCBhcyBhbiBhcnJheSwgYW5kIHJldHVybnMgYSBwcm9taXNlLlxuICpcbiAqICAgICAgUS5uZmFwcGx5KEZTLnJlYWRGaWxlLCBbX19maWxlbmFtZV0pXG4gKiAgICAgIC50aGVuKGZ1bmN0aW9uIChjb250ZW50KSB7XG4gKiAgICAgIH0pXG4gKlxuICovXG5RLm5mYXBwbHkgPSBmdW5jdGlvbiAoY2FsbGJhY2ssIGFyZ3MpIHtcbiAgICByZXR1cm4gUShjYWxsYmFjaykubmZhcHBseShhcmdzKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLm5mYXBwbHkgPSBmdW5jdGlvbiAoYXJncykge1xuICAgIHZhciBkZWZlcnJlZCA9IGRlZmVyKCk7XG4gICAgdmFyIG5vZGVBcmdzID0gYXJyYXlfc2xpY2UoYXJncyk7XG4gICAgbm9kZUFyZ3MucHVzaChkZWZlcnJlZC5tYWtlTm9kZVJlc29sdmVyKCkpO1xuICAgIHRoaXMuZmFwcGx5KG5vZGVBcmdzKS5mYWlsKGRlZmVycmVkLnJlamVjdCk7XG4gICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG59O1xuXG4vKipcbiAqIFBhc3NlcyBhIGNvbnRpbnVhdGlvbiB0byBhIE5vZGUgZnVuY3Rpb24sIHdoaWNoIGlzIGNhbGxlZCB3aXRoIHRoZSBnaXZlblxuICogYXJndW1lbnRzIHByb3ZpZGVkIGluZGl2aWR1YWxseSwgYW5kIHJldHVybnMgYSBwcm9taXNlLlxuICogQGV4YW1wbGVcbiAqIFEubmZjYWxsKEZTLnJlYWRGaWxlLCBfX2ZpbGVuYW1lKVxuICogLnRoZW4oZnVuY3Rpb24gKGNvbnRlbnQpIHtcbiAqIH0pXG4gKlxuICovXG5RLm5mY2FsbCA9IGZ1bmN0aW9uIChjYWxsYmFjayAvKi4uLmFyZ3MqLykge1xuICAgIHZhciBhcmdzID0gYXJyYXlfc2xpY2UoYXJndW1lbnRzLCAxKTtcbiAgICByZXR1cm4gUShjYWxsYmFjaykubmZhcHBseShhcmdzKTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLm5mY2FsbCA9IGZ1bmN0aW9uICgvKi4uLmFyZ3MqLykge1xuICAgIHZhciBub2RlQXJncyA9IGFycmF5X3NsaWNlKGFyZ3VtZW50cyk7XG4gICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICBub2RlQXJncy5wdXNoKGRlZmVycmVkLm1ha2VOb2RlUmVzb2x2ZXIoKSk7XG4gICAgdGhpcy5mYXBwbHkobm9kZUFyZ3MpLmZhaWwoZGVmZXJyZWQucmVqZWN0KTtcbiAgICByZXR1cm4gZGVmZXJyZWQucHJvbWlzZTtcbn07XG5cbi8qKlxuICogV3JhcHMgYSBOb2RlSlMgY29udGludWF0aW9uIHBhc3NpbmcgZnVuY3Rpb24gYW5kIHJldHVybnMgYW4gZXF1aXZhbGVudFxuICogdmVyc2lvbiB0aGF0IHJldHVybnMgYSBwcm9taXNlLlxuICogQGV4YW1wbGVcbiAqIFEubmZiaW5kKEZTLnJlYWRGaWxlLCBfX2ZpbGVuYW1lKShcInV0Zi04XCIpXG4gKiAudGhlbihjb25zb2xlLmxvZylcbiAqIC5kb25lKClcbiAqL1xuUS5uZmJpbmQgPVxuUS5kZW5vZGVpZnkgPSBmdW5jdGlvbiAoY2FsbGJhY2sgLyouLi5hcmdzKi8pIHtcbiAgICB2YXIgYmFzZUFyZ3MgPSBhcnJheV9zbGljZShhcmd1bWVudHMsIDEpO1xuICAgIHJldHVybiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBub2RlQXJncyA9IGJhc2VBcmdzLmNvbmNhdChhcnJheV9zbGljZShhcmd1bWVudHMpKTtcbiAgICAgICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICAgICAgbm9kZUFyZ3MucHVzaChkZWZlcnJlZC5tYWtlTm9kZVJlc29sdmVyKCkpO1xuICAgICAgICBRKGNhbGxiYWNrKS5mYXBwbHkobm9kZUFyZ3MpLmZhaWwoZGVmZXJyZWQucmVqZWN0KTtcbiAgICAgICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG4gICAgfTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLm5mYmluZCA9XG5Qcm9taXNlLnByb3RvdHlwZS5kZW5vZGVpZnkgPSBmdW5jdGlvbiAoLyouLi5hcmdzKi8pIHtcbiAgICB2YXIgYXJncyA9IGFycmF5X3NsaWNlKGFyZ3VtZW50cyk7XG4gICAgYXJncy51bnNoaWZ0KHRoaXMpO1xuICAgIHJldHVybiBRLmRlbm9kZWlmeS5hcHBseSh2b2lkIDAsIGFyZ3MpO1xufTtcblxuUS5uYmluZCA9IGZ1bmN0aW9uIChjYWxsYmFjaywgdGhpc3AgLyouLi5hcmdzKi8pIHtcbiAgICB2YXIgYmFzZUFyZ3MgPSBhcnJheV9zbGljZShhcmd1bWVudHMsIDIpO1xuICAgIHJldHVybiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBub2RlQXJncyA9IGJhc2VBcmdzLmNvbmNhdChhcnJheV9zbGljZShhcmd1bWVudHMpKTtcbiAgICAgICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICAgICAgbm9kZUFyZ3MucHVzaChkZWZlcnJlZC5tYWtlTm9kZVJlc29sdmVyKCkpO1xuICAgICAgICBmdW5jdGlvbiBib3VuZCgpIHtcbiAgICAgICAgICAgIHJldHVybiBjYWxsYmFjay5hcHBseSh0aGlzcCwgYXJndW1lbnRzKTtcbiAgICAgICAgfVxuICAgICAgICBRKGJvdW5kKS5mYXBwbHkobm9kZUFyZ3MpLmZhaWwoZGVmZXJyZWQucmVqZWN0KTtcbiAgICAgICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG4gICAgfTtcbn07XG5cblByb21pc2UucHJvdG90eXBlLm5iaW5kID0gZnVuY3Rpb24gKC8qdGhpc3AsIC4uLmFyZ3MqLykge1xuICAgIHZhciBhcmdzID0gYXJyYXlfc2xpY2UoYXJndW1lbnRzLCAwKTtcbiAgICBhcmdzLnVuc2hpZnQodGhpcyk7XG4gICAgcmV0dXJuIFEubmJpbmQuYXBwbHkodm9pZCAwLCBhcmdzKTtcbn07XG5cbi8qKlxuICogQ2FsbHMgYSBtZXRob2Qgb2YgYSBOb2RlLXN0eWxlIG9iamVjdCB0aGF0IGFjY2VwdHMgYSBOb2RlLXN0eWxlXG4gKiBjYWxsYmFjayB3aXRoIGEgZ2l2ZW4gYXJyYXkgb2YgYXJndW1lbnRzLCBwbHVzIGEgcHJvdmlkZWQgY2FsbGJhY2suXG4gKiBAcGFyYW0gb2JqZWN0IGFuIG9iamVjdCB0aGF0IGhhcyB0aGUgbmFtZWQgbWV0aG9kXG4gKiBAcGFyYW0ge1N0cmluZ30gbmFtZSBuYW1lIG9mIHRoZSBtZXRob2Qgb2Ygb2JqZWN0XG4gKiBAcGFyYW0ge0FycmF5fSBhcmdzIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSBtZXRob2Q7IHRoZSBjYWxsYmFja1xuICogd2lsbCBiZSBwcm92aWRlZCBieSBRIGFuZCBhcHBlbmRlZCB0byB0aGVzZSBhcmd1bWVudHMuXG4gKiBAcmV0dXJucyBhIHByb21pc2UgZm9yIHRoZSB2YWx1ZSBvciBlcnJvclxuICovXG5RLm5tYXBwbHkgPSAvLyBYWFggQXMgcHJvcG9zZWQgYnkgXCJSZWRzYW5kcm9cIlxuUS5ucG9zdCA9IGZ1bmN0aW9uIChvYmplY3QsIG5hbWUsIGFyZ3MpIHtcbiAgICByZXR1cm4gUShvYmplY3QpLm5wb3N0KG5hbWUsIGFyZ3MpO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUubm1hcHBseSA9IC8vIFhYWCBBcyBwcm9wb3NlZCBieSBcIlJlZHNhbmRyb1wiXG5Qcm9taXNlLnByb3RvdHlwZS5ucG9zdCA9IGZ1bmN0aW9uIChuYW1lLCBhcmdzKSB7XG4gICAgdmFyIG5vZGVBcmdzID0gYXJyYXlfc2xpY2UoYXJncyB8fCBbXSk7XG4gICAgdmFyIGRlZmVycmVkID0gZGVmZXIoKTtcbiAgICBub2RlQXJncy5wdXNoKGRlZmVycmVkLm1ha2VOb2RlUmVzb2x2ZXIoKSk7XG4gICAgdGhpcy5kaXNwYXRjaChcInBvc3RcIiwgW25hbWUsIG5vZGVBcmdzXSkuZmFpbChkZWZlcnJlZC5yZWplY3QpO1xuICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xufTtcblxuLyoqXG4gKiBDYWxscyBhIG1ldGhvZCBvZiBhIE5vZGUtc3R5bGUgb2JqZWN0IHRoYXQgYWNjZXB0cyBhIE5vZGUtc3R5bGVcbiAqIGNhbGxiYWNrLCBmb3J3YXJkaW5nIHRoZSBnaXZlbiB2YXJpYWRpYyBhcmd1bWVudHMsIHBsdXMgYSBwcm92aWRlZFxuICogY2FsbGJhY2sgYXJndW1lbnQuXG4gKiBAcGFyYW0gb2JqZWN0IGFuIG9iamVjdCB0aGF0IGhhcyB0aGUgbmFtZWQgbWV0aG9kXG4gKiBAcGFyYW0ge1N0cmluZ30gbmFtZSBuYW1lIG9mIHRoZSBtZXRob2Qgb2Ygb2JqZWN0XG4gKiBAcGFyYW0gLi4uYXJncyBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgbWV0aG9kOyB0aGUgY2FsbGJhY2sgd2lsbFxuICogYmUgcHJvdmlkZWQgYnkgUSBhbmQgYXBwZW5kZWQgdG8gdGhlc2UgYXJndW1lbnRzLlxuICogQHJldHVybnMgYSBwcm9taXNlIGZvciB0aGUgdmFsdWUgb3IgZXJyb3JcbiAqL1xuUS5uc2VuZCA9IC8vIFhYWCBCYXNlZCBvbiBNYXJrIE1pbGxlcidzIHByb3Bvc2VkIFwic2VuZFwiXG5RLm5tY2FsbCA9IC8vIFhYWCBCYXNlZCBvbiBcIlJlZHNhbmRybydzXCIgcHJvcG9zYWxcblEubmludm9rZSA9IGZ1bmN0aW9uIChvYmplY3QsIG5hbWUgLyouLi5hcmdzKi8pIHtcbiAgICB2YXIgbm9kZUFyZ3MgPSBhcnJheV9zbGljZShhcmd1bWVudHMsIDIpO1xuICAgIHZhciBkZWZlcnJlZCA9IGRlZmVyKCk7XG4gICAgbm9kZUFyZ3MucHVzaChkZWZlcnJlZC5tYWtlTm9kZVJlc29sdmVyKCkpO1xuICAgIFEob2JqZWN0KS5kaXNwYXRjaChcInBvc3RcIiwgW25hbWUsIG5vZGVBcmdzXSkuZmFpbChkZWZlcnJlZC5yZWplY3QpO1xuICAgIHJldHVybiBkZWZlcnJlZC5wcm9taXNlO1xufTtcblxuUHJvbWlzZS5wcm90b3R5cGUubnNlbmQgPSAvLyBYWFggQmFzZWQgb24gTWFyayBNaWxsZXIncyBwcm9wb3NlZCBcInNlbmRcIlxuUHJvbWlzZS5wcm90b3R5cGUubm1jYWxsID0gLy8gWFhYIEJhc2VkIG9uIFwiUmVkc2FuZHJvJ3NcIiBwcm9wb3NhbFxuUHJvbWlzZS5wcm90b3R5cGUubmludm9rZSA9IGZ1bmN0aW9uIChuYW1lIC8qLi4uYXJncyovKSB7XG4gICAgdmFyIG5vZGVBcmdzID0gYXJyYXlfc2xpY2UoYXJndW1lbnRzLCAxKTtcbiAgICB2YXIgZGVmZXJyZWQgPSBkZWZlcigpO1xuICAgIG5vZGVBcmdzLnB1c2goZGVmZXJyZWQubWFrZU5vZGVSZXNvbHZlcigpKTtcbiAgICB0aGlzLmRpc3BhdGNoKFwicG9zdFwiLCBbbmFtZSwgbm9kZUFyZ3NdKS5mYWlsKGRlZmVycmVkLnJlamVjdCk7XG4gICAgcmV0dXJuIGRlZmVycmVkLnByb21pc2U7XG59O1xuXG4vKipcbiAqIElmIGEgZnVuY3Rpb24gd291bGQgbGlrZSB0byBzdXBwb3J0IGJvdGggTm9kZSBjb250aW51YXRpb24tcGFzc2luZy1zdHlsZSBhbmRcbiAqIHByb21pc2UtcmV0dXJuaW5nLXN0eWxlLCBpdCBjYW4gZW5kIGl0cyBpbnRlcm5hbCBwcm9taXNlIGNoYWluIHdpdGhcbiAqIGBub2RlaWZ5KG5vZGViYWNrKWAsIGZvcndhcmRpbmcgdGhlIG9wdGlvbmFsIG5vZGViYWNrIGFyZ3VtZW50LiAgSWYgdGhlIHVzZXJcbiAqIGVsZWN0cyB0byB1c2UgYSBub2RlYmFjaywgdGhlIHJlc3VsdCB3aWxsIGJlIHNlbnQgdGhlcmUuICBJZiB0aGV5IGRvIG5vdFxuICogcGFzcyBhIG5vZGViYWNrLCB0aGV5IHdpbGwgcmVjZWl2ZSB0aGUgcmVzdWx0IHByb21pc2UuXG4gKiBAcGFyYW0gb2JqZWN0IGEgcmVzdWx0IChvciBhIHByb21pc2UgZm9yIGEgcmVzdWx0KVxuICogQHBhcmFtIHtGdW5jdGlvbn0gbm9kZWJhY2sgYSBOb2RlLmpzLXN0eWxlIGNhbGxiYWNrXG4gKiBAcmV0dXJucyBlaXRoZXIgdGhlIHByb21pc2Ugb3Igbm90aGluZ1xuICovXG5RLm5vZGVpZnkgPSBub2RlaWZ5O1xuZnVuY3Rpb24gbm9kZWlmeShvYmplY3QsIG5vZGViYWNrKSB7XG4gICAgcmV0dXJuIFEob2JqZWN0KS5ub2RlaWZ5KG5vZGViYWNrKTtcbn1cblxuUHJvbWlzZS5wcm90b3R5cGUubm9kZWlmeSA9IGZ1bmN0aW9uIChub2RlYmFjaykge1xuICAgIGlmIChub2RlYmFjaykge1xuICAgICAgICB0aGlzLnRoZW4oZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgICAgICBRLm5leHRUaWNrKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICBub2RlYmFjayhudWxsLCB2YWx1ZSk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSwgZnVuY3Rpb24gKGVycm9yKSB7XG4gICAgICAgICAgICBRLm5leHRUaWNrKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICBub2RlYmFjayhlcnJvcik7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxufTtcblxuUS5ub0NvbmZsaWN0ID0gZnVuY3Rpb24oKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFwiUS5ub0NvbmZsaWN0IG9ubHkgd29ya3Mgd2hlbiBRIGlzIHVzZWQgYXMgYSBnbG9iYWxcIik7XG59O1xuXG4vLyBBbGwgY29kZSBiZWZvcmUgdGhpcyBwb2ludCB3aWxsIGJlIGZpbHRlcmVkIGZyb20gc3RhY2sgdHJhY2VzLlxudmFyIHFFbmRpbmdMaW5lID0gY2FwdHVyZUxpbmUoKTtcblxucmV0dXJuIFE7XG5cbn0pO1xuXG59KS5jYWxsKHRoaXMscmVxdWlyZSgnX3Byb2Nlc3MnKSlcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPWRhdGE6YXBwbGljYXRpb24vanNvbjtjaGFyc2V0OnV0Zi04O2Jhc2U2NCxleUoyWlhKemFXOXVJam96TENKemIzVnlZMlZ6SWpwYkltNXZaR1ZmYlc5a2RXeGxjeTl4TDNFdWFuTWlYU3dpYm1GdFpYTWlPbHRkTENKdFlYQndhVzVuY3lJNklqdEJRVUZCTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRVHRCUVVOQk8wRkJRMEU3UVVGRFFUdEJRVU5CTzBGQlEwRTdRVUZEUVR0QlFVTkJPMEZCUTBFN1FVRkRRU0lzSW1acGJHVWlPaUpuWlc1bGNtRjBaV1F1YW5NaUxDSnpiM1Z5WTJWU2IyOTBJam9pSWl3aWMyOTFjbU5sYzBOdmJuUmxiblFpT2xzaUx5OGdkbWx0T25SelBUUTZjM1J6UFRRNmMzYzlORHBjYmk4cUlWeHVJQ3BjYmlBcUlFTnZjSGx5YVdkb2RDQXlNREE1TFRJd01USWdTM0pwY3lCTGIzZGhiQ0IxYm1SbGNpQjBhR1VnZEdWeWJYTWdiMllnZEdobElFMUpWRnh1SUNvZ2JHbGpaVzV6WlNCbWIzVnVaQ0JoZENCb2RIUndPaTh2WjJsMGFIVmlMbU52YlM5cmNtbHphMjkzWVd3dmNTOXlZWGN2YldGemRHVnlMMHhKUTBWT1UwVmNiaUFxWEc0Z0tpQlhhWFJvSUhCaGNuUnpJR0o1SUZSNWJHVnlJRU5zYjNObFhHNGdLaUJEYjNCNWNtbG5hSFFnTWpBd055MHlNREE1SUZSNWJHVnlJRU5zYjNObElIVnVaR1Z5SUhSb1pTQjBaWEp0Y3lCdlppQjBhR1VnVFVsVUlGZ2diR2xqWlc1elpTQm1iM1Z1WkZ4dUlDb2dZWFFnYUhSMGNEb3ZMM2QzZHk1dmNHVnVjMjkxY21ObExtOXlaeTlzYVdObGJuTmxjeTl0YVhRdGJHbGpaVzV6WlM1b2RHMXNYRzRnS2lCR2IzSnJaV1FnWVhRZ2NtVm1YM05sYm1RdWFuTWdkbVZ5YzJsdmJqb2dNakF3T1Mwd05TMHhNVnh1SUNwY2JpQXFJRmRwZEdnZ2NHRnlkSE1nWW5rZ1RXRnlheUJOYVd4c1pYSmNiaUFxSUVOdmNIbHlhV2RvZENBb1F5a2dNakF4TVNCSGIyOW5iR1VnU1c1akxseHVJQ3BjYmlBcUlFeHBZMlZ1YzJWa0lIVnVaR1Z5SUhSb1pTQkJjR0ZqYUdVZ1RHbGpaVzV6WlN3Z1ZtVnljMmx2YmlBeUxqQWdLSFJvWlNCY0lreHBZMlZ1YzJWY0lpazdYRzRnS2lCNWIzVWdiV0Y1SUc1dmRDQjFjMlVnZEdocGN5Qm1hV3hsSUdWNFkyVndkQ0JwYmlCamIyMXdiR2xoYm1ObElIZHBkR2dnZEdobElFeHBZMlZ1YzJVdVhHNGdLaUJaYjNVZ2JXRjVJRzlpZEdGcGJpQmhJR052Y0hrZ2IyWWdkR2hsSUV4cFkyVnVjMlVnWVhSY2JpQXFYRzRnS2lCb2RIUndPaTh2ZDNkM0xtRndZV05vWlM1dmNtY3ZiR2xqWlc1elpYTXZURWxEUlU1VFJTMHlMakJjYmlBcVhHNGdLaUJWYm14bGMzTWdjbVZ4ZFdseVpXUWdZbmtnWVhCd2JHbGpZV0pzWlNCc1lYY2diM0lnWVdkeVpXVmtJSFJ2SUdsdUlIZHlhWFJwYm1jc0lITnZablIzWVhKbFhHNGdLaUJrYVhOMGNtbGlkWFJsWkNCMWJtUmxjaUIwYUdVZ1RHbGpaVzV6WlNCcGN5QmthWE4wY21saWRYUmxaQ0J2YmlCaGJpQmNJa0ZUSUVsVFhDSWdRa0ZUU1ZNc1hHNGdLaUJYU1ZSSVQxVlVJRmRCVWxKQlRsUkpSVk1nVDFJZ1EwOU9SRWxVU1U5T1V5QlBSaUJCVGxrZ1MwbE9SQ3dnWldsMGFHVnlJR1Y0Y0hKbGMzTWdiM0lnYVcxd2JHbGxaQzVjYmlBcUlGTmxaU0IwYUdVZ1RHbGpaVzV6WlNCbWIzSWdkR2hsSUhOd1pXTnBabWxqSUd4aGJtZDFZV2RsSUdkdmRtVnlibWx1WnlCd1pYSnRhWE56YVc5dWN5QmhibVJjYmlBcUlHeHBiV2wwWVhScGIyNXpJSFZ1WkdWeUlIUm9aU0JNYVdObGJuTmxMbHh1SUNwY2JpQXFMMXh1WEc0b1puVnVZM1JwYjI0Z0tHUmxabWx1YVhScGIyNHBJSHRjYmlBZ0lDQmNJblZ6WlNCemRISnBZM1JjSWp0Y2JseHVJQ0FnSUM4dklGUm9hWE1nWm1sc1pTQjNhV3hzSUdaMWJtTjBhVzl1SUhCeWIzQmxjbXg1SUdGeklHRWdQSE5qY21sd2RENGdkR0ZuTENCdmNpQmhJRzF2WkhWc1pWeHVJQ0FnSUM4dklIVnphVzVuSUVOdmJXMXZia3BUSUdGdVpDQk9iMlJsU2xNZ2IzSWdVbVZ4ZFdseVpVcFRJRzF2WkhWc1pTQm1iM0p0WVhSekxpQWdTVzVjYmlBZ0lDQXZMeUJEYjIxdGIyNHZUbTlrWlM5U1pYRjFhWEpsU2xNc0lIUm9aU0J0YjJSMWJHVWdaWGh3YjNKMGN5QjBhR1VnVVNCQlVFa2dZVzVrSUhkb1pXNWNiaUFnSUNBdkx5QmxlR1ZqZFhSbFpDQmhjeUJoSUhOcGJYQnNaU0E4YzJOeWFYQjBQaXdnYVhRZ1kzSmxZWFJsY3lCaElGRWdaMnh2WW1Gc0lHbHVjM1JsWVdRdVhHNWNiaUFnSUNBdkx5Qk5iMjUwWVdkbElGSmxjWFZwY21WY2JpQWdJQ0JwWmlBb2RIbHdaVzltSUdKdmIzUnpkSEpoY0NBOVBUMGdYQ0ptZFc1amRHbHZibHdpS1NCN1hHNGdJQ0FnSUNBZ0lHSnZiM1J6ZEhKaGNDaGNJbkJ5YjIxcGMyVmNJaXdnWkdWbWFXNXBkR2x2YmlrN1hHNWNiaUFnSUNBdkx5QkRiMjF0YjI1S1UxeHVJQ0FnSUgwZ1pXeHpaU0JwWmlBb2RIbHdaVzltSUdWNGNHOXlkSE1nUFQwOUlGd2liMkpxWldOMFhDSWdKaVlnZEhsd1pXOW1JRzF2WkhWc1pTQTlQVDBnWENKdlltcGxZM1JjSWlrZ2UxeHVJQ0FnSUNBZ0lDQnRiMlIxYkdVdVpYaHdiM0owY3lBOUlHUmxabWx1YVhScGIyNG9LVHRjYmx4dUlDQWdJQzh2SUZKbGNYVnBjbVZLVTF4dUlDQWdJSDBnWld4elpTQnBaaUFvZEhsd1pXOW1JR1JsWm1sdVpTQTlQVDBnWENKbWRXNWpkR2x2Ymx3aUlDWW1JR1JsWm1sdVpTNWhiV1FwSUh0Y2JpQWdJQ0FnSUNBZ1pHVm1hVzVsS0dSbFptbHVhWFJwYjI0cE8xeHVYRzRnSUNBZ0x5OGdVMFZUSUNoVFpXTjFjbVVnUldOdFlWTmpjbWx3ZENsY2JpQWdJQ0I5SUdWc2MyVWdhV1lnS0hSNWNHVnZaaUJ6WlhNZ0lUMDlJRndpZFc1a1pXWnBibVZrWENJcElIdGNiaUFnSUNBZ0lDQWdhV1lnS0NGelpYTXViMnNvS1NrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnY21WMGRYSnVPMXh1SUNBZ0lDQWdJQ0I5SUdWc2MyVWdlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2MyVnpMbTFoYTJWUklEMGdaR1ZtYVc1cGRHbHZianRjYmlBZ0lDQWdJQ0FnZlZ4dVhHNGdJQ0FnTHk4Z1BITmpjbWx3ZEQ1Y2JpQWdJQ0I5SUdWc2MyVWdhV1lnS0hSNWNHVnZaaUIzYVc1a2IzY2dJVDA5SUZ3aWRXNWtaV1pwYm1Wa1hDSWdmSHdnZEhsd1pXOW1JSE5sYkdZZ0lUMDlJRndpZFc1a1pXWnBibVZrWENJcElIdGNiaUFnSUNBZ0lDQWdMeThnVUhKbFptVnlJSGRwYm1SdmR5QnZkbVZ5SUhObGJHWWdabTl5SUdGa1pDMXZiaUJ6WTNKcGNIUnpMaUJWYzJVZ2MyVnNaaUJtYjNKY2JpQWdJQ0FnSUNBZ0x5OGdibTl1TFhkcGJtUnZkMlZrSUdOdmJuUmxlSFJ6TGx4dUlDQWdJQ0FnSUNCMllYSWdaMnh2WW1Gc0lEMGdkSGx3Wlc5bUlIZHBibVJ2ZHlBaFBUMGdYQ0oxYm1SbFptbHVaV1JjSWlBL0lIZHBibVJ2ZHlBNklITmxiR1k3WEc1Y2JpQWdJQ0FnSUNBZ0x5OGdSMlYwSUhSb1pTQmdkMmx1Wkc5M1lDQnZZbXBsWTNRc0lITmhkbVVnZEdobElIQnlaWFpwYjNWeklGRWdaMnh2WW1Gc1hHNGdJQ0FnSUNBZ0lDOHZJR0Z1WkNCcGJtbDBhV0ZzYVhwbElGRWdZWE1nWVNCbmJHOWlZV3d1WEc0Z0lDQWdJQ0FnSUhaaGNpQndjbVYyYVc5MWMxRWdQU0JuYkc5aVlXd3VVVHRjYmlBZ0lDQWdJQ0FnWjJ4dlltRnNMbEVnUFNCa1pXWnBibWwwYVc5dUtDazdYRzVjYmlBZ0lDQWdJQ0FnTHk4Z1FXUmtJR0VnYm05RGIyNW1iR2xqZENCbWRXNWpkR2x2YmlCemJ5QlJJR05oYmlCaVpTQnlaVzF2ZG1Wa0lHWnliMjBnZEdobFhHNGdJQ0FnSUNBZ0lDOHZJR2RzYjJKaGJDQnVZVzFsYzNCaFkyVXVYRzRnSUNBZ0lDQWdJR2RzYjJKaGJDNVJMbTV2UTI5dVpteHBZM1FnUFNCbWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCbmJHOWlZV3d1VVNBOUlIQnlaWFpwYjNWelVUdGNiaUFnSUNBZ0lDQWdJQ0FnSUhKbGRIVnliaUIwYUdsek8xeHVJQ0FnSUNBZ0lDQjlPMXh1WEc0Z0lDQWdmU0JsYkhObElIdGNiaUFnSUNBZ0lDQWdkR2h5YjNjZ2JtVjNJRVZ5Y205eUtGd2lWR2hwY3lCbGJuWnBjbTl1YldWdWRDQjNZWE1nYm05MElHRnVkR2xqYVhCaGRHVmtJR0o1SUZFdUlGQnNaV0Z6WlNCbWFXeGxJR0VnWW5Wbkxsd2lLVHRjYmlBZ0lDQjlYRzVjYm4wcEtHWjFibU4wYVc5dUlDZ3BJSHRjYmx3aWRYTmxJSE4wY21samRGd2lPMXh1WEc1MllYSWdhR0Z6VTNSaFkydHpJRDBnWm1Gc2MyVTdYRzUwY25rZ2UxeHVJQ0FnSUhSb2NtOTNJRzVsZHlCRmNuSnZjaWdwTzF4dWZTQmpZWFJqYUNBb1pTa2dlMXh1SUNBZ0lHaGhjMU4wWVdOcmN5QTlJQ0VoWlM1emRHRmphenRjYm4xY2JseHVMeThnUVd4c0lHTnZaR1VnWVdaMFpYSWdkR2hwY3lCd2IybHVkQ0IzYVd4c0lHSmxJR1pwYkhSbGNtVmtJR1p5YjIwZ2MzUmhZMnNnZEhKaFkyVnpJSEpsY0c5eWRHVmtYRzR2THlCaWVTQlJMbHh1ZG1GeUlIRlRkR0Z5ZEdsdVoweHBibVVnUFNCallYQjBkWEpsVEdsdVpTZ3BPMXh1ZG1GeUlIRkdhV3hsVG1GdFpUdGNibHh1THk4Z2MyaHBiWE5jYmx4dUx5OGdkWE5sWkNCbWIzSWdabUZzYkdKaFkyc2dhVzRnWENKaGJHeFNaWE52YkhabFpGd2lYRzUyWVhJZ2JtOXZjQ0E5SUdaMWJtTjBhVzl1SUNncElIdDlPMXh1WEc0dkx5QlZjMlVnZEdobElHWmhjM1JsYzNRZ2NHOXpjMmxpYkdVZ2JXVmhibk1nZEc4Z1pYaGxZM1YwWlNCaElIUmhjMnNnYVc0Z1lTQm1kWFIxY21VZ2RIVnlibHh1THk4Z2IyWWdkR2hsSUdWMlpXNTBJR3h2YjNBdVhHNTJZWElnYm1WNGRGUnBZMnNnUFNobWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ0x5OGdiR2x1YTJWa0lHeHBjM1FnYjJZZ2RHRnphM01nS0hOcGJtZHNaU3dnZDJsMGFDQm9aV0ZrSUc1dlpHVXBYRzRnSUNBZ2RtRnlJR2hsWVdRZ1BTQjdkR0Z6YXpvZ2RtOXBaQ0F3TENCdVpYaDBPaUJ1ZFd4c2ZUdGNiaUFnSUNCMllYSWdkR0ZwYkNBOUlHaGxZV1E3WEc0Z0lDQWdkbUZ5SUdac2RYTm9hVzVuSUQwZ1ptRnNjMlU3WEc0Z0lDQWdkbUZ5SUhKbGNYVmxjM1JVYVdOcklEMGdkbTlwWkNBd08xeHVJQ0FnSUhaaGNpQnBjMDV2WkdWS1V5QTlJR1poYkhObE8xeHVJQ0FnSUM4dklIRjFaWFZsSUdadmNpQnNZWFJsSUhSaGMydHpMQ0IxYzJWa0lHSjVJSFZ1YUdGdVpHeGxaQ0J5WldwbFkzUnBiMjRnZEhKaFkydHBibWRjYmlBZ0lDQjJZWElnYkdGMFpYSlJkV1YxWlNBOUlGdGRPMXh1WEc0Z0lDQWdablZ1WTNScGIyNGdabXgxYzJnb0tTQjdYRzRnSUNBZ0lDQWdJQzhxSUdwemFHbHVkQ0JzYjI5d1puVnVZem9nZEhKMVpTQXFMMXh1SUNBZ0lDQWdJQ0IyWVhJZ2RHRnpheXdnWkc5dFlXbHVPMXh1WEc0Z0lDQWdJQ0FnSUhkb2FXeGxJQ2hvWldGa0xtNWxlSFFwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJR2hsWVdRZ1BTQm9aV0ZrTG01bGVIUTdYRzRnSUNBZ0lDQWdJQ0FnSUNCMFlYTnJJRDBnYUdWaFpDNTBZWE5yTzF4dUlDQWdJQ0FnSUNBZ0lDQWdhR1ZoWkM1MFlYTnJJRDBnZG05cFpDQXdPMXh1SUNBZ0lDQWdJQ0FnSUNBZ1pHOXRZV2x1SUQwZ2FHVmhaQzVrYjIxaGFXNDdYRzVjYmlBZ0lDQWdJQ0FnSUNBZ0lHbG1JQ2hrYjIxaGFXNHBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0JvWldGa0xtUnZiV0ZwYmlBOUlIWnZhV1FnTUR0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCa2IyMWhhVzR1Wlc1MFpYSW9LVHRjYmlBZ0lDQWdJQ0FnSUNBZ0lIMWNiaUFnSUNBZ0lDQWdJQ0FnSUhKMWJsTnBibWRzWlNoMFlYTnJMQ0JrYjIxaGFXNHBPMXh1WEc0Z0lDQWdJQ0FnSUgxY2JpQWdJQ0FnSUNBZ2QyaHBiR1VnS0d4aGRHVnlVWFZsZFdVdWJHVnVaM1JvS1NCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0IwWVhOcklEMGdiR0YwWlhKUmRXVjFaUzV3YjNBb0tUdGNiaUFnSUNBZ0lDQWdJQ0FnSUhKMWJsTnBibWRzWlNoMFlYTnJLVHRjYmlBZ0lDQWdJQ0FnZlZ4dUlDQWdJQ0FnSUNCbWJIVnphR2x1WnlBOUlHWmhiSE5sTzF4dUlDQWdJSDFjYmlBZ0lDQXZMeUJ5ZFc1eklHRWdjMmx1WjJ4bElHWjFibU4wYVc5dUlHbHVJSFJvWlNCaGMzbHVZeUJ4ZFdWMVpWeHVJQ0FnSUdaMWJtTjBhVzl1SUhKMWJsTnBibWRzWlNoMFlYTnJMQ0JrYjIxaGFXNHBJSHRjYmlBZ0lDQWdJQ0FnZEhKNUlIdGNiaUFnSUNBZ0lDQWdJQ0FnSUhSaGMyc29LVHRjYmx4dUlDQWdJQ0FnSUNCOUlHTmhkR05vSUNobEtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCcFppQW9hWE5PYjJSbFNsTXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0F2THlCSmJpQnViMlJsTENCMWJtTmhkV2RvZENCbGVHTmxjSFJwYjI1eklHRnlaU0JqYjI1emFXUmxjbVZrSUdaaGRHRnNJR1Z5Y205eWN5NWNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQXZMeUJTWlMxMGFISnZkeUIwYUdWdElITjVibU5vY205dWIzVnpiSGtnZEc4Z2FXNTBaWEp5ZFhCMElHWnNkWE5vYVc1bklWeHVYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdMeThnUlc1emRYSmxJR052Ym5ScGJuVmhkR2x2YmlCcFppQjBhR1VnZFc1allYVm5hSFFnWlhoalpYQjBhVzl1SUdseklITjFjSEJ5WlhOelpXUmNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQXZMeUJzYVhOMFpXNXBibWNnWENKMWJtTmhkV2RvZEVWNFkyVndkR2x2Ymx3aUlHVjJaVzUwY3lBb1lYTWdaRzl0WVdsdWN5QmtiMlZ6S1M1Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBdkx5QkRiMjUwYVc1MVpTQnBiaUJ1WlhoMElHVjJaVzUwSUhSdklHRjJiMmxrSUhScFkyc2djbVZqZFhKemFXOXVMbHh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJR2xtSUNoa2IyMWhhVzRwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdaRzl0WVdsdUxtVjRhWFFvS1R0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCOVhHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ2MyVjBWR2x0Wlc5MWRDaG1iSFZ6YUN3Z01DazdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdhV1lnS0dSdmJXRnBiaWtnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQmtiMjFoYVc0dVpXNTBaWElvS1R0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCOVhHNWNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQjBhSEp2ZHlCbE8xeHVYRzRnSUNBZ0lDQWdJQ0FnSUNCOUlHVnNjMlVnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUM4dklFbHVJR0p5YjNkelpYSnpMQ0IxYm1OaGRXZG9kQ0JsZUdObGNIUnBiMjV6SUdGeVpTQnViM1FnWm1GMFlXd3VYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdMeThnVW1VdGRHaHliM2NnZEdobGJTQmhjM2x1WTJoeWIyNXZkWE5zZVNCMGJ5QmhkbTlwWkNCemJHOTNMV1J2ZDI1ekxseHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lITmxkRlJwYldWdmRYUW9ablZ1WTNScGIyNGdLQ2tnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQjBhSEp2ZHlCbE8xeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lIMHNJREFwTzF4dUlDQWdJQ0FnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0I5WEc1Y2JpQWdJQ0FnSUNBZ2FXWWdLR1J2YldGcGJpa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ1pHOXRZV2x1TG1WNGFYUW9LVHRjYmlBZ0lDQWdJQ0FnZlZ4dUlDQWdJSDFjYmx4dUlDQWdJRzVsZUhSVWFXTnJJRDBnWm5WdVkzUnBiMjRnS0hSaGMyc3BJSHRjYmlBZ0lDQWdJQ0FnZEdGcGJDQTlJSFJoYVd3dWJtVjRkQ0E5SUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJSFJoYzJzNklIUmhjMnNzWEc0Z0lDQWdJQ0FnSUNBZ0lDQmtiMjFoYVc0NklHbHpUbTlrWlVwVElDWW1JSEJ5YjJObGMzTXVaRzl0WVdsdUxGeHVJQ0FnSUNBZ0lDQWdJQ0FnYm1WNGREb2diblZzYkZ4dUlDQWdJQ0FnSUNCOU8xeHVYRzRnSUNBZ0lDQWdJR2xtSUNnaFpteDFjMmhwYm1jcElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUdac2RYTm9hVzVuSUQwZ2RISjFaVHRjYmlBZ0lDQWdJQ0FnSUNBZ0lISmxjWFZsYzNSVWFXTnJLQ2s3WEc0Z0lDQWdJQ0FnSUgxY2JpQWdJQ0I5TzF4dVhHNGdJQ0FnYVdZZ0tIUjVjR1Z2WmlCd2NtOWpaWE56SUQwOVBTQmNJbTlpYW1WamRGd2lJQ1ltWEc0Z0lDQWdJQ0FnSUhCeWIyTmxjM011ZEc5VGRISnBibWNvS1NBOVBUMGdYQ0piYjJKcVpXTjBJSEJ5YjJObGMzTmRYQ0lnSmlZZ2NISnZZMlZ6Y3k1dVpYaDBWR2xqYXlrZ2UxeHVJQ0FnSUNBZ0lDQXZMeUJGYm5OMWNtVWdVU0JwY3lCcGJpQmhJSEpsWVd3Z1RtOWtaU0JsYm5acGNtOXViV1Z1ZEN3Z2QybDBhQ0JoSUdCd2NtOWpaWE56TG01bGVIUlVhV05yWUM1Y2JpQWdJQ0FnSUNBZ0x5OGdWRzhnYzJWbElIUm9jbTkxWjJnZ1ptRnJaU0JPYjJSbElHVnVkbWx5YjI1dFpXNTBjenBjYmlBZ0lDQWdJQ0FnTHk4Z0tpQk5iMk5vWVNCMFpYTjBJSEoxYm01bGNpQXRJR1Y0Y0c5elpYTWdZU0JnY0hKdlkyVnpjMkFnWjJ4dlltRnNJSGRwZEdodmRYUWdZU0JnYm1WNGRGUnBZMnRnWEc0Z0lDQWdJQ0FnSUM4dklDb2dRbkp2ZDNObGNtbG1lU0F0SUdWNGNHOXpaWE1nWVNCZ2NISnZZMlZ6Y3k1dVpYaFVhV05yWUNCbWRXNWpkR2x2YmlCMGFHRjBJSFZ6WlhOY2JpQWdJQ0FnSUNBZ0x5OGdJQ0JnYzJWMFZHbHRaVzkxZEdBdUlFbHVJSFJvYVhNZ1kyRnpaU0JnYzJWMFNXMXRaV1JwWVhSbFlDQnBjeUJ3Y21WbVpYSnlaV1FnWW1WallYVnpaVnh1SUNBZ0lDQWdJQ0F2THlBZ0lDQnBkQ0JwY3lCbVlYTjBaWEl1SUVKeWIzZHpaWEpwWm5rbmN5QmdjSEp2WTJWemN5NTBiMU4wY21sdVp5Z3BZQ0I1YVdWc1pITmNiaUFnSUNBZ0lDQWdMeThnSUNCY0lsdHZZbXBsWTNRZ1QySnFaV04wWFZ3aUxDQjNhR2xzWlNCcGJpQmhJSEpsWVd3Z1RtOWtaU0JsYm5acGNtOXViV1Z1ZEZ4dUlDQWdJQ0FnSUNBdkx5QWdJR0J3Y205alpYTnpMbTVsZUhSVWFXTnJLQ2xnSUhscFpXeGtjeUJjSWx0dlltcGxZM1FnY0hKdlkyVnpjMTFjSWk1Y2JpQWdJQ0FnSUNBZ2FYTk9iMlJsU2xNZ1BTQjBjblZsTzF4dVhHNGdJQ0FnSUNBZ0lISmxjWFZsYzNSVWFXTnJJRDBnWm5WdVkzUnBiMjRnS0NrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnY0hKdlkyVnpjeTV1WlhoMFZHbGpheWhtYkhWemFDazdYRzRnSUNBZ0lDQWdJSDA3WEc1Y2JpQWdJQ0I5SUdWc2MyVWdhV1lnS0hSNWNHVnZaaUJ6WlhSSmJXMWxaR2xoZEdVZ1BUMDlJRndpWm5WdVkzUnBiMjVjSWlrZ2UxeHVJQ0FnSUNBZ0lDQXZMeUJKYmlCSlJURXdMQ0JPYjJSbExtcHpJREF1T1Nzc0lHOXlJR2gwZEhCek9pOHZaMmwwYUhWaUxtTnZiUzlPYjJKc1pVcFRMM05sZEVsdGJXVmthV0YwWlZ4dUlDQWdJQ0FnSUNCcFppQW9kSGx3Wlc5bUlIZHBibVJ2ZHlBaFBUMGdYQ0oxYm1SbFptbHVaV1JjSWlrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnY21WeGRXVnpkRlJwWTJzZ1BTQnpaWFJKYlcxbFpHbGhkR1V1WW1sdVpDaDNhVzVrYjNjc0lHWnNkWE5vS1R0Y2JpQWdJQ0FnSUNBZ2ZTQmxiSE5sSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJSEpsY1hWbGMzUlVhV05ySUQwZ1puVnVZM1JwYjI0Z0tDa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJSE5sZEVsdGJXVmthV0YwWlNobWJIVnphQ2s3WEc0Z0lDQWdJQ0FnSUNBZ0lDQjlPMXh1SUNBZ0lDQWdJQ0I5WEc1Y2JpQWdJQ0I5SUdWc2MyVWdhV1lnS0hSNWNHVnZaaUJOWlhOellXZGxRMmhoYm01bGJDQWhQVDBnWENKMWJtUmxabWx1WldSY0lpa2dlMXh1SUNBZ0lDQWdJQ0F2THlCdGIyUmxjbTRnWW5KdmQzTmxjbk5jYmlBZ0lDQWdJQ0FnTHk4Z2FIUjBjRG92TDNkM2R5NXViMjVpYkc5amEybHVaeTVwYnk4eU1ERXhMekEyTDNkcGJtUnZkMjVsZUhSMGFXTnJMbWgwYld4Y2JpQWdJQ0FnSUNBZ2RtRnlJR05vWVc1dVpXd2dQU0J1WlhjZ1RXVnpjMkZuWlVOb1lXNXVaV3dvS1R0Y2JpQWdJQ0FnSUNBZ0x5OGdRWFFnYkdWaGMzUWdVMkZtWVhKcElGWmxjbk5wYjI0Z05pNHdMalVnS0RnMU16WXVNekF1TVNrZ2FXNTBaWEp0YVhSMFpXNTBiSGtnWTJGdWJtOTBJR055WldGMFpWeHVJQ0FnSUNBZ0lDQXZMeUIzYjNKcmFXNW5JRzFsYzNOaFoyVWdjRzl5ZEhNZ2RHaGxJR1pwY25OMElIUnBiV1VnWVNCd1lXZGxJR3h2WVdSekxseHVJQ0FnSUNBZ0lDQmphR0Z1Ym1Wc0xuQnZjblF4TG05dWJXVnpjMkZuWlNBOUlHWjFibU4wYVc5dUlDZ3BJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lISmxjWFZsYzNSVWFXTnJJRDBnY21WeGRXVnpkRkJ2Y25SVWFXTnJPMXh1SUNBZ0lDQWdJQ0FnSUNBZ1kyaGhibTVsYkM1d2IzSjBNUzV2Ym0xbGMzTmhaMlVnUFNCbWJIVnphRHRjYmlBZ0lDQWdJQ0FnSUNBZ0lHWnNkWE5vS0NrN1hHNGdJQ0FnSUNBZ0lIMDdYRzRnSUNBZ0lDQWdJSFpoY2lCeVpYRjFaWE4wVUc5eWRGUnBZMnNnUFNCbWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNBdkx5QlBjR1Z5WVNCeVpYRjFhWEpsY3lCMWN5QjBieUJ3Y205MmFXUmxJR0VnYldWemMyRm5aU0J3WVhsc2IyRmtMQ0J5WldkaGNtUnNaWE56SUc5bVhHNGdJQ0FnSUNBZ0lDQWdJQ0F2THlCM2FHVjBhR1Z5SUhkbElIVnpaU0JwZEM1Y2JpQWdJQ0FnSUNBZ0lDQWdJR05vWVc1dVpXd3VjRzl5ZERJdWNHOXpkRTFsYzNOaFoyVW9NQ2s3WEc0Z0lDQWdJQ0FnSUgwN1hHNGdJQ0FnSUNBZ0lISmxjWFZsYzNSVWFXTnJJRDBnWm5WdVkzUnBiMjRnS0NrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnYzJWMFZHbHRaVzkxZENobWJIVnphQ3dnTUNrN1hHNGdJQ0FnSUNBZ0lDQWdJQ0J5WlhGMVpYTjBVRzl5ZEZScFkyc29LVHRjYmlBZ0lDQWdJQ0FnZlR0Y2JseHVJQ0FnSUgwZ1pXeHpaU0I3WEc0Z0lDQWdJQ0FnSUM4dklHOXNaQ0JpY205M2MyVnljMXh1SUNBZ0lDQWdJQ0J5WlhGMVpYTjBWR2xqYXlBOUlHWjFibU4wYVc5dUlDZ3BJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lITmxkRlJwYldWdmRYUW9abXgxYzJnc0lEQXBPMXh1SUNBZ0lDQWdJQ0I5TzF4dUlDQWdJSDFjYmlBZ0lDQXZMeUJ5ZFc1eklHRWdkR0Z6YXlCaFpuUmxjaUJoYkd3Z2IzUm9aWElnZEdGemEzTWdhR0YyWlNCaVpXVnVJSEoxYmx4dUlDQWdJQzh2SUhSb2FYTWdhWE1nZFhObFpuVnNJR1p2Y2lCMWJtaGhibVJzWldRZ2NtVnFaV04wYVc5dUlIUnlZV05yYVc1bklIUm9ZWFFnYm1WbFpITWdkRzhnYUdGd2NHVnVYRzRnSUNBZ0x5OGdZV1owWlhJZ1lXeHNJR0IwYUdWdVlHUWdkR0Z6YTNNZ2FHRjJaU0JpWldWdUlISjFiaTVjYmlBZ0lDQnVaWGgwVkdsamF5NXlkVzVCWm5SbGNpQTlJR1oxYm1OMGFXOXVJQ2gwWVhOcktTQjdYRzRnSUNBZ0lDQWdJR3hoZEdWeVVYVmxkV1V1Y0hWemFDaDBZWE5yS1R0Y2JpQWdJQ0FnSUNBZ2FXWWdLQ0ZtYkhWemFHbHVaeWtnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdabXgxYzJocGJtY2dQU0IwY25WbE8xeHVJQ0FnSUNBZ0lDQWdJQ0FnY21WeGRXVnpkRlJwWTJzb0tUdGNiaUFnSUNBZ0lDQWdmVnh1SUNBZ0lIMDdYRzRnSUNBZ2NtVjBkWEp1SUc1bGVIUlVhV05yTzF4dWZTa29LVHRjYmx4dUx5OGdRWFIwWlcxd2RDQjBieUJ0WVd0bElHZGxibVZ5YVdOeklITmhabVVnYVc0Z2RHaGxJR1poWTJVZ2IyWWdaRzkzYm5OMGNtVmhiVnh1THk4Z2JXOWthV1pwWTJGMGFXOXVjeTVjYmk4dklGUm9aWEpsSUdseklHNXZJSE5wZEhWaGRHbHZiaUIzYUdWeVpTQjBhR2x6SUdseklHNWxZMlZ6YzJGeWVTNWNiaTh2SUVsbUlIbHZkU0J1WldWa0lHRWdjMlZqZFhKcGRIa2daM1ZoY21GdWRHVmxMQ0IwYUdWelpTQndjbWx0YjNKa2FXRnNjeUJ1WldWa0lIUnZJR0psWEc0dkx5QmtaV1Z3YkhrZ1puSnZlbVZ1SUdGdWVYZGhlU3dnWVc1a0lHbG1JSGx2ZFNCa2IyN2lnSmwwSUc1bFpXUWdZU0J6WldOMWNtbDBlU0JuZFdGeVlXNTBaV1VzWEc0dkx5QjBhR2x6SUdseklHcDFjM1FnY0d4aGFXNGdjR0Z5WVc1dmFXUXVYRzR2THlCSWIzZGxkbVZ5TENCMGFHbHpJQ29xYldsbmFIUXFLaUJvWVhabElIUm9aU0J1YVdObElITnBaR1V0WldabVpXTjBJRzltSUhKbFpIVmphVzVuSUhSb1pTQnphWHBsSUc5bVhHNHZMeUIwYUdVZ2JXbHVhV1pwWldRZ1kyOWtaU0JpZVNCeVpXUjFZMmx1WnlCNExtTmhiR3dvS1NCMGJ5QnRaWEpsYkhrZ2VDZ3BYRzR2THlCVFpXVWdUV0Z5YXlCTmFXeHNaWExpZ0pseklHVjRjR3hoYm1GMGFXOXVJRzltSUhkb1lYUWdkR2hwY3lCa2IyVnpMbHh1THk4Z2FIUjBjRG92TDNkcGEya3VaV050WVhOamNtbHdkQzV2Y21jdlpHOXJkUzV3YUhBL2FXUTlZMjl1ZG1WdWRHbHZibk02YzJGbVpWOXRaWFJoWDNCeWIyZHlZVzF0YVc1blhHNTJZWElnWTJGc2JDQTlJRVoxYm1OMGFXOXVMbU5oYkd3N1hHNW1kVzVqZEdsdmJpQjFibU4xY25KNVZHaHBjeWhtS1NCN1hHNGdJQ0FnY21WMGRYSnVJR1oxYm1OMGFXOXVJQ2dwSUh0Y2JpQWdJQ0FnSUNBZ2NtVjBkWEp1SUdOaGJHd3VZWEJ3Ykhrb1ppd2dZWEpuZFcxbGJuUnpLVHRjYmlBZ0lDQjlPMXh1ZlZ4dUx5OGdWR2hwY3lCcGN5QmxjWFZwZG1Gc1pXNTBMQ0JpZFhRZ2MyeHZkMlZ5T2x4dUx5OGdkVzVqZFhKeWVWUm9hWE1nUFNCR2RXNWpkR2x2Ymw5aWFXNWtMbUpwYm1Rb1JuVnVZM1JwYjI1ZlltbHVaQzVqWVd4c0tUdGNiaTh2SUdoMGRIQTZMeTlxYzNCbGNtWXVZMjl0TDNWdVkzVnljbmwwYUdselhHNWNiblpoY2lCaGNuSmhlVjl6YkdsalpTQTlJSFZ1WTNWeWNubFVhR2x6S0VGeWNtRjVMbkJ5YjNSdmRIbHdaUzV6YkdsalpTazdYRzVjYm5aaGNpQmhjbkpoZVY5eVpXUjFZMlVnUFNCMWJtTjFjbko1VkdocGN5aGNiaUFnSUNCQmNuSmhlUzV3Y205MGIzUjVjR1V1Y21Wa2RXTmxJSHg4SUdaMWJtTjBhVzl1SUNoallXeHNZbUZqYXl3Z1ltRnphWE1wSUh0Y2JpQWdJQ0FnSUNBZ2RtRnlJR2x1WkdWNElEMGdNQ3hjYmlBZ0lDQWdJQ0FnSUNBZ0lHeGxibWQwYUNBOUlIUm9hWE11YkdWdVozUm9PMXh1SUNBZ0lDQWdJQ0F2THlCamIyNWpaWEp1YVc1bklIUm9aU0JwYm1sMGFXRnNJSFpoYkhWbExDQnBaaUJ2Ym1VZ2FYTWdibTkwSUhCeWIzWnBaR1ZrWEc0Z0lDQWdJQ0FnSUdsbUlDaGhjbWQxYldWdWRITXViR1Z1WjNSb0lEMDlQU0F4S1NCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0F2THlCelpXVnJJSFJ2SUhSb1pTQm1hWEp6ZENCMllXeDFaU0JwYmlCMGFHVWdZWEp5WVhrc0lHRmpZMjkxYm5ScGJtZGNiaUFnSUNBZ0lDQWdJQ0FnSUM4dklHWnZjaUIwYUdVZ2NHOXpjMmxpYVd4cGRIa2dkR2hoZENCcGN5QnBjeUJoSUhOd1lYSnpaU0JoY25KaGVWeHVJQ0FnSUNBZ0lDQWdJQ0FnWkc4Z2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lHbG1JQ2hwYm1SbGVDQnBiaUIwYUdsektTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUdKaGMybHpJRDBnZEdocGMxdHBibVJsZUNzclhUdGNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnWW5KbFlXczdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJR2xtSUNncksybHVaR1Y0SUQ0OUlHeGxibWQwYUNrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0IwYUhKdmR5QnVaWGNnVkhsd1pVVnljbTl5S0NrN1hHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUNBZ0lDQWdJQ0FnZlNCM2FHbHNaU0FvTVNrN1hHNGdJQ0FnSUNBZ0lIMWNiaUFnSUNBZ0lDQWdMeThnY21Wa2RXTmxYRzRnSUNBZ0lDQWdJR1p2Y2lBb095QnBibVJsZUNBOElHeGxibWQwYURzZ2FXNWtaWGdyS3lrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnTHk4Z1lXTmpiM1Z1ZENCbWIzSWdkR2hsSUhCdmMzTnBZbWxzYVhSNUlIUm9ZWFFnZEdobElHRnljbUY1SUdseklITndZWEp6WlZ4dUlDQWdJQ0FnSUNBZ0lDQWdhV1lnS0dsdVpHVjRJR2x1SUhSb2FYTXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0JpWVhOcGN5QTlJR05oYkd4aVlXTnJLR0poYzJsekxDQjBhR2x6VzJsdVpHVjRYU3dnYVc1a1pYZ3BPMXh1SUNBZ0lDQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUNBZ0lDQjlYRzRnSUNBZ0lDQWdJSEpsZEhWeWJpQmlZWE5wY3p0Y2JpQWdJQ0I5WEc0cE8xeHVYRzUyWVhJZ1lYSnlZWGxmYVc1a1pYaFBaaUE5SUhWdVkzVnljbmxVYUdsektGeHVJQ0FnSUVGeWNtRjVMbkJ5YjNSdmRIbHdaUzVwYm1SbGVFOW1JSHg4SUdaMWJtTjBhVzl1SUNoMllXeDFaU2tnZTF4dUlDQWdJQ0FnSUNBdkx5QnViM1FnWVNCMlpYSjVJR2R2YjJRZ2MyaHBiU3dnWW5WMElHZHZiMlFnWlc1dmRXZG9JR1p2Y2lCdmRYSWdiMjVsSUhWelpTQnZaaUJwZEZ4dUlDQWdJQ0FnSUNCbWIzSWdLSFpoY2lCcElEMGdNRHNnYVNBOElIUm9hWE11YkdWdVozUm9PeUJwS3lzcElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUdsbUlDaDBhR2x6VzJsZElEMDlQU0IyWVd4MVpTa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJSEpsZEhWeWJpQnBPMXh1SUNBZ0lDQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUNBZ0lDQjlYRzRnSUNBZ0lDQWdJSEpsZEhWeWJpQXRNVHRjYmlBZ0lDQjlYRzRwTzF4dVhHNTJZWElnWVhKeVlYbGZiV0Z3SUQwZ2RXNWpkWEp5ZVZSb2FYTW9YRzRnSUNBZ1FYSnlZWGt1Y0hKdmRHOTBlWEJsTG0xaGNDQjhmQ0JtZFc1amRHbHZiaUFvWTJGc2JHSmhZMnNzSUhSb2FYTndLU0I3WEc0Z0lDQWdJQ0FnSUhaaGNpQnpaV3htSUQwZ2RHaHBjenRjYmlBZ0lDQWdJQ0FnZG1GeUlHTnZiR3hsWTNRZ1BTQmJYVHRjYmlBZ0lDQWdJQ0FnWVhKeVlYbGZjbVZrZFdObEtITmxiR1lzSUdaMWJtTjBhVzl1SUNoMWJtUmxabWx1WldRc0lIWmhiSFZsTENCcGJtUmxlQ2tnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdZMjlzYkdWamRDNXdkWE5vS0dOaGJHeGlZV05yTG1OaGJHd29kR2hwYzNBc0lIWmhiSFZsTENCcGJtUmxlQ3dnYzJWc1ppa3BPMXh1SUNBZ0lDQWdJQ0I5TENCMmIybGtJREFwTzF4dUlDQWdJQ0FnSUNCeVpYUjFjbTRnWTI5c2JHVmpkRHRjYmlBZ0lDQjlYRzRwTzF4dVhHNTJZWElnYjJKcVpXTjBYMk55WldGMFpTQTlJRTlpYW1WamRDNWpjbVZoZEdVZ2ZId2dablZ1WTNScGIyNGdLSEJ5YjNSdmRIbHdaU2tnZTF4dUlDQWdJR1oxYm1OMGFXOXVJRlI1Y0dVb0tTQjdJSDFjYmlBZ0lDQlVlWEJsTG5CeWIzUnZkSGx3WlNBOUlIQnliM1J2ZEhsd1pUdGNiaUFnSUNCeVpYUjFjbTRnYm1WM0lGUjVjR1VvS1R0Y2JuMDdYRzVjYm5aaGNpQnZZbXBsWTNSZmFHRnpUM2R1VUhKdmNHVnlkSGtnUFNCMWJtTjFjbko1VkdocGN5aFBZbXBsWTNRdWNISnZkRzkwZVhCbExtaGhjMDkzYmxCeWIzQmxjblI1S1R0Y2JseHVkbUZ5SUc5aWFtVmpkRjlyWlhseklEMGdUMkpxWldOMExtdGxlWE1nZkh3Z1puVnVZM1JwYjI0Z0tHOWlhbVZqZENrZ2UxeHVJQ0FnSUhaaGNpQnJaWGx6SUQwZ1cxMDdYRzRnSUNBZ1ptOXlJQ2gyWVhJZ2EyVjVJR2x1SUc5aWFtVmpkQ2tnZTF4dUlDQWdJQ0FnSUNCcFppQW9iMkpxWldOMFgyaGhjMDkzYmxCeWIzQmxjblI1S0c5aWFtVmpkQ3dnYTJWNUtTa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2EyVjVjeTV3ZFhOb0tHdGxlU2s3WEc0Z0lDQWdJQ0FnSUgxY2JpQWdJQ0I5WEc0Z0lDQWdjbVYwZFhKdUlHdGxlWE03WEc1OU8xeHVYRzUyWVhJZ2IySnFaV04wWDNSdlUzUnlhVzVuSUQwZ2RXNWpkWEp5ZVZSb2FYTW9UMkpxWldOMExuQnliM1J2ZEhsd1pTNTBiMU4wY21sdVp5azdYRzVjYm1aMWJtTjBhVzl1SUdselQySnFaV04wS0haaGJIVmxLU0I3WEc0Z0lDQWdjbVYwZFhKdUlIWmhiSFZsSUQwOVBTQlBZbXBsWTNRb2RtRnNkV1VwTzF4dWZWeHVYRzR2THlCblpXNWxjbUYwYjNJZ2NtVnNZWFJsWkNCemFHbHRjMXh1WEc0dkx5QkdTVmhOUlRvZ1VtVnRiM1psSUhSb2FYTWdablZ1WTNScGIyNGdiMjVqWlNCRlV6WWdaMlZ1WlhKaGRHOXljeUJoY21VZ2FXNGdVM0JwWkdWeVRXOXVhMlY1TGx4dVpuVnVZM1JwYjI0Z2FYTlRkRzl3U1hSbGNtRjBhVzl1S0dWNFkyVndkR2x2YmlrZ2UxeHVJQ0FnSUhKbGRIVnliaUFvWEc0Z0lDQWdJQ0FnSUc5aWFtVmpkRjkwYjFOMGNtbHVaeWhsZUdObGNIUnBiMjRwSUQwOVBTQmNJbHR2WW1wbFkzUWdVM1J2Y0VsMFpYSmhkR2x2YmwxY0lpQjhmRnh1SUNBZ0lDQWdJQ0JsZUdObGNIUnBiMjRnYVc1emRHRnVZMlZ2WmlCUlVtVjBkWEp1Vm1Gc2RXVmNiaUFnSUNBcE8xeHVmVnh1WEc0dkx5QkdTVmhOUlRvZ1VtVnRiM1psSUhSb2FYTWdhR1ZzY0dWeUlHRnVaQ0JSTG5KbGRIVnliaUJ2Ym1ObElFVlROaUJuWlc1bGNtRjBiM0p6SUdGeVpTQnBibHh1THk4Z1UzQnBaR1Z5VFc5dWEyVjVMbHh1ZG1GeUlGRlNaWFIxY201V1lXeDFaVHRjYm1sbUlDaDBlWEJsYjJZZ1VtVjBkWEp1Vm1Gc2RXVWdJVDA5SUZ3aWRXNWtaV1pwYm1Wa1hDSXBJSHRjYmlBZ0lDQlJVbVYwZFhKdVZtRnNkV1VnUFNCU1pYUjFjbTVXWVd4MVpUdGNibjBnWld4elpTQjdYRzRnSUNBZ1VWSmxkSFZ5YmxaaGJIVmxJRDBnWm5WdVkzUnBiMjRnS0haaGJIVmxLU0I3WEc0Z0lDQWdJQ0FnSUhSb2FYTXVkbUZzZFdVZ1BTQjJZV3gxWlR0Y2JpQWdJQ0I5TzF4dWZWeHVYRzR2THlCc2IyNW5JSE4wWVdOcklIUnlZV05sYzF4dVhHNTJZWElnVTFSQlEwdGZTbFZOVUY5VFJWQkJVa0ZVVDFJZ1BTQmNJa1p5YjIwZ2NISmxkbWx2ZFhNZ1pYWmxiblE2WENJN1hHNWNibVoxYm1OMGFXOXVJRzFoYTJWVGRHRmphMVJ5WVdObFRHOXVaeWhsY25KdmNpd2djSEp2YldselpTa2dlMXh1SUNBZ0lDOHZJRWxtSUhCdmMzTnBZbXhsTENCMGNtRnVjMlp2Y20wZ2RHaGxJR1Z5Y205eUlITjBZV05ySUhSeVlXTmxJR0o1SUhKbGJXOTJhVzVuSUU1dlpHVWdZVzVrSUZGY2JpQWdJQ0F2THlCamNuVm1kQ3dnZEdobGJpQmpiMjVqWVhSbGJtRjBhVzVuSUhkcGRHZ2dkR2hsSUhOMFlXTnJJSFJ5WVdObElHOW1JR0J3Y205dGFYTmxZQzRnVTJWbElDTTFOeTVjYmlBZ0lDQnBaaUFvYUdGelUzUmhZMnR6SUNZbVhHNGdJQ0FnSUNBZ0lIQnliMjFwYzJVdWMzUmhZMnNnSmlaY2JpQWdJQ0FnSUNBZ2RIbHdaVzltSUdWeWNtOXlJRDA5UFNCY0ltOWlhbVZqZEZ3aUlDWW1YRzRnSUNBZ0lDQWdJR1Z5Y205eUlDRTlQU0J1ZFd4c0lDWW1YRzRnSUNBZ0lDQWdJR1Z5Y205eUxuTjBZV05ySUNZbVhHNGdJQ0FnSUNBZ0lHVnljbTl5TG5OMFlXTnJMbWx1WkdWNFQyWW9VMVJCUTB0ZlNsVk5VRjlUUlZCQlVrRlVUMUlwSUQwOVBTQXRNVnh1SUNBZ0lDa2dlMXh1SUNBZ0lDQWdJQ0IyWVhJZ2MzUmhZMnR6SUQwZ1cxMDdYRzRnSUNBZ0lDQWdJR1p2Y2lBb2RtRnlJSEFnUFNCd2NtOXRhWE5sT3lBaElYQTdJSEFnUFNCd0xuTnZkWEpqWlNrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnYVdZZ0tIQXVjM1JoWTJzcElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQnpkR0ZqYTNNdWRXNXphR2xtZENod0xuTjBZV05yS1R0Y2JpQWdJQ0FnSUNBZ0lDQWdJSDFjYmlBZ0lDQWdJQ0FnZlZ4dUlDQWdJQ0FnSUNCemRHRmphM011ZFc1emFHbG1kQ2hsY25KdmNpNXpkR0ZqYXlrN1hHNWNiaUFnSUNBZ0lDQWdkbUZ5SUdOdmJtTmhkR1ZrVTNSaFkydHpJRDBnYzNSaFkydHpMbXB2YVc0b1hDSmNYRzVjSWlBcklGTlVRVU5MWDBwVlRWQmZVMFZRUVZKQlZFOVNJQ3NnWENKY1hHNWNJaWs3WEc0Z0lDQWdJQ0FnSUdWeWNtOXlMbk4wWVdOcklEMGdabWxzZEdWeVUzUmhZMnRUZEhKcGJtY29ZMjl1WTJGMFpXUlRkR0ZqYTNNcE8xeHVJQ0FnSUgxY2JuMWNibHh1Wm5WdVkzUnBiMjRnWm1sc2RHVnlVM1JoWTJ0VGRISnBibWNvYzNSaFkydFRkSEpwYm1jcElIdGNiaUFnSUNCMllYSWdiR2x1WlhNZ1BTQnpkR0ZqYTFOMGNtbHVaeTV6Y0d4cGRDaGNJbHhjYmx3aUtUdGNiaUFnSUNCMllYSWdaR1Z6YVhKbFpFeHBibVZ6SUQwZ1cxMDdYRzRnSUNBZ1ptOXlJQ2gyWVhJZ2FTQTlJREE3SUdrZ1BDQnNhVzVsY3k1c1pXNW5kR2c3SUNzcmFTa2dlMXh1SUNBZ0lDQWdJQ0IyWVhJZ2JHbHVaU0E5SUd4cGJtVnpXMmxkTzF4dVhHNGdJQ0FnSUNBZ0lHbG1JQ2doYVhOSmJuUmxjbTVoYkVaeVlXMWxLR3hwYm1VcElDWW1JQ0ZwYzA1dlpHVkdjbUZ0WlNoc2FXNWxLU0FtSmlCc2FXNWxLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQmtaWE5wY21Wa1RHbHVaWE11Y0hWemFDaHNhVzVsS1R0Y2JpQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUgxY2JpQWdJQ0J5WlhSMWNtNGdaR1Z6YVhKbFpFeHBibVZ6TG1wdmFXNG9YQ0pjWEc1Y0lpazdYRzU5WEc1Y2JtWjFibU4wYVc5dUlHbHpUbTlrWlVaeVlXMWxLSE4wWVdOclRHbHVaU2tnZTF4dUlDQWdJSEpsZEhWeWJpQnpkR0ZqYTB4cGJtVXVhVzVrWlhoUFppaGNJaWh0YjJSMWJHVXVhbk02WENJcElDRTlQU0F0TVNCOGZGeHVJQ0FnSUNBZ0lDQWdJQ0J6ZEdGamEweHBibVV1YVc1a1pYaFBaaWhjSWlodWIyUmxMbXB6T2x3aUtTQWhQVDBnTFRFN1hHNTlYRzVjYm1aMWJtTjBhVzl1SUdkbGRFWnBiR1ZPWVcxbFFXNWtUR2x1WlU1MWJXSmxjaWh6ZEdGamEweHBibVVwSUh0Y2JpQWdJQ0F2THlCT1lXMWxaQ0JtZFc1amRHbHZibk02SUZ3aVlYUWdablZ1WTNScGIyNU9ZVzFsSUNobWFXeGxibUZ0WlRwc2FXNWxUblZ0WW1WeU9tTnZiSFZ0Yms1MWJXSmxjaWxjSWx4dUlDQWdJQzh2SUVsdUlFbEZNVEFnWm5WdVkzUnBiMjRnYm1GdFpTQmpZVzRnYUdGMlpTQnpjR0ZqWlhNZ0tGd2lRVzV2Ym5sdGIzVnpJR1oxYm1OMGFXOXVYQ0lwSUU5ZmIxeHVJQ0FnSUhaaGNpQmhkSFJsYlhCME1TQTlJQzloZENBdUt5QmNYQ2dvTGlzcE9paGNYR1FyS1Rvb1B6cGNYR1FyS1Z4Y0tTUXZMbVY0WldNb2MzUmhZMnRNYVc1bEtUdGNiaUFnSUNCcFppQW9ZWFIwWlcxd2RERXBJSHRjYmlBZ0lDQWdJQ0FnY21WMGRYSnVJRnRoZEhSbGJYQjBNVnN4WFN3Z1RuVnRZbVZ5S0dGMGRHVnRjSFF4V3pKZEtWMDdYRzRnSUNBZ2ZWeHVYRzRnSUNBZ0x5OGdRVzV2Ym5sdGIzVnpJR1oxYm1OMGFXOXVjem9nWENKaGRDQm1hV3hsYm1GdFpUcHNhVzVsVG5WdFltVnlPbU52YkhWdGJrNTFiV0psY2x3aVhHNGdJQ0FnZG1GeUlHRjBkR1Z0Y0hReUlEMGdMMkYwSUNoYlhpQmRLeWs2S0Z4Y1pDc3BPaWcvT2x4Y1pDc3BKQzh1WlhobFl5aHpkR0ZqYTB4cGJtVXBPMXh1SUNBZ0lHbG1JQ2hoZEhSbGJYQjBNaWtnZTF4dUlDQWdJQ0FnSUNCeVpYUjFjbTRnVzJGMGRHVnRjSFF5V3pGZExDQk9kVzFpWlhJb1lYUjBaVzF3ZERKYk1sMHBYVHRjYmlBZ0lDQjlYRzVjYmlBZ0lDQXZMeUJHYVhKbFptOTRJSE4wZVd4bE9pQmNJbVoxYm1OMGFXOXVRR1pwYkdWdVlXMWxPbXhwYm1WT2RXMWlaWElnYjNJZ1FHWnBiR1Z1WVcxbE9teHBibVZPZFcxaVpYSmNJbHh1SUNBZ0lIWmhjaUJoZEhSbGJYQjBNeUE5SUM4dUtrQW9MaXNwT2loY1hHUXJLU1F2TG1WNFpXTW9jM1JoWTJ0TWFXNWxLVHRjYmlBZ0lDQnBaaUFvWVhSMFpXMXdkRE1wSUh0Y2JpQWdJQ0FnSUNBZ2NtVjBkWEp1SUZ0aGRIUmxiWEIwTTFzeFhTd2dUblZ0WW1WeUtHRjBkR1Z0Y0hReld6SmRLVjA3WEc0Z0lDQWdmVnh1ZlZ4dVhHNW1kVzVqZEdsdmJpQnBjMGx1ZEdWeWJtRnNSbkpoYldVb2MzUmhZMnRNYVc1bEtTQjdYRzRnSUNBZ2RtRnlJR1pwYkdWT1lXMWxRVzVrVEdsdVpVNTFiV0psY2lBOUlHZGxkRVpwYkdWT1lXMWxRVzVrVEdsdVpVNTFiV0psY2loemRHRmphMHhwYm1VcE8xeHVYRzRnSUNBZ2FXWWdLQ0ZtYVd4bFRtRnRaVUZ1WkV4cGJtVk9kVzFpWlhJcElIdGNiaUFnSUNBZ0lDQWdjbVYwZFhKdUlHWmhiSE5sTzF4dUlDQWdJSDFjYmx4dUlDQWdJSFpoY2lCbWFXeGxUbUZ0WlNBOUlHWnBiR1ZPWVcxbFFXNWtUR2x1WlU1MWJXSmxjbHN3WFR0Y2JpQWdJQ0IyWVhJZ2JHbHVaVTUxYldKbGNpQTlJR1pwYkdWT1lXMWxRVzVrVEdsdVpVNTFiV0psY2xzeFhUdGNibHh1SUNBZ0lISmxkSFZ5YmlCbWFXeGxUbUZ0WlNBOVBUMGdjVVpwYkdWT1lXMWxJQ1ltWEc0Z0lDQWdJQ0FnSUd4cGJtVk9kVzFpWlhJZ1BqMGdjVk4wWVhKMGFXNW5UR2x1WlNBbUpseHVJQ0FnSUNBZ0lDQnNhVzVsVG5WdFltVnlJRHc5SUhGRmJtUnBibWRNYVc1bE8xeHVmVnh1WEc0dkx5QmthWE5qYjNabGNpQnZkMjRnWm1sc1pTQnVZVzFsSUdGdVpDQnNhVzVsSUc1MWJXSmxjaUJ5WVc1blpTQm1iM0lnWm1sc2RHVnlhVzVuSUhOMFlXTnJYRzR2THlCMGNtRmpaWE5jYm1aMWJtTjBhVzl1SUdOaGNIUjFjbVZNYVc1bEtDa2dlMXh1SUNBZ0lHbG1JQ2doYUdGelUzUmhZMnR6S1NCN1hHNGdJQ0FnSUNBZ0lISmxkSFZ5Ymp0Y2JpQWdJQ0I5WEc1Y2JpQWdJQ0IwY25rZ2UxeHVJQ0FnSUNBZ0lDQjBhSEp2ZHlCdVpYY2dSWEp5YjNJb0tUdGNiaUFnSUNCOUlHTmhkR05vSUNobEtTQjdYRzRnSUNBZ0lDQWdJSFpoY2lCc2FXNWxjeUE5SUdVdWMzUmhZMnN1YzNCc2FYUW9YQ0pjWEc1Y0lpazdYRzRnSUNBZ0lDQWdJSFpoY2lCbWFYSnpkRXhwYm1VZ1BTQnNhVzVsYzFzd1hTNXBibVJsZUU5bUtGd2lRRndpS1NBK0lEQWdQeUJzYVc1bGMxc3hYU0E2SUd4cGJtVnpXekpkTzF4dUlDQWdJQ0FnSUNCMllYSWdabWxzWlU1aGJXVkJibVJNYVc1bFRuVnRZbVZ5SUQwZ1oyVjBSbWxzWlU1aGJXVkJibVJNYVc1bFRuVnRZbVZ5S0dacGNuTjBUR2x1WlNrN1hHNGdJQ0FnSUNBZ0lHbG1JQ2doWm1sc1pVNWhiV1ZCYm1STWFXNWxUblZ0WW1WeUtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCeVpYUjFjbTQ3WEc0Z0lDQWdJQ0FnSUgxY2JseHVJQ0FnSUNBZ0lDQnhSbWxzWlU1aGJXVWdQU0JtYVd4bFRtRnRaVUZ1WkV4cGJtVk9kVzFpWlhKYk1GMDdYRzRnSUNBZ0lDQWdJSEpsZEhWeWJpQm1hV3hsVG1GdFpVRnVaRXhwYm1WT2RXMWlaWEpiTVYwN1hHNGdJQ0FnZlZ4dWZWeHVYRzVtZFc1amRHbHZiaUJrWlhCeVpXTmhkR1VvWTJGc2JHSmhZMnNzSUc1aGJXVXNJR0ZzZEdWeWJtRjBhWFpsS1NCN1hHNGdJQ0FnY21WMGRYSnVJR1oxYm1OMGFXOXVJQ2dwSUh0Y2JpQWdJQ0FnSUNBZ2FXWWdLSFI1Y0dWdlppQmpiMjV6YjJ4bElDRTlQU0JjSW5WdVpHVm1hVzVsWkZ3aUlDWW1YRzRnSUNBZ0lDQWdJQ0FnSUNCMGVYQmxiMllnWTI5dWMyOXNaUzUzWVhKdUlEMDlQU0JjSW1aMWJtTjBhVzl1WENJcElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUdOdmJuTnZiR1V1ZDJGeWJpaHVZVzFsSUNzZ1hDSWdhWE1nWkdWd2NtVmpZWFJsWkN3Z2RYTmxJRndpSUNzZ1lXeDBaWEp1WVhScGRtVWdLMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJRndpSUdsdWMzUmxZV1F1WENJc0lHNWxkeUJGY25KdmNpaGNJbHdpS1M1emRHRmpheWs3WEc0Z0lDQWdJQ0FnSUgxY2JpQWdJQ0FnSUNBZ2NtVjBkWEp1SUdOaGJHeGlZV05yTG1Gd2NHeDVLR05oYkd4aVlXTnJMQ0JoY21kMWJXVnVkSE1wTzF4dUlDQWdJSDA3WEc1OVhHNWNiaTh2SUdWdVpDQnZaaUJ6YUdsdGMxeHVMeThnWW1WbmFXNXVhVzVuSUc5bUlISmxZV3dnZDI5eWExeHVYRzR2S2lwY2JpQXFJRU52Ym5OMGNuVmpkSE1nWVNCd2NtOXRhWE5sSUdadmNpQmhiaUJwYlcxbFpHbGhkR1VnY21WbVpYSmxibU5sTENCd1lYTnpaWE1nY0hKdmJXbHpaWE1nZEdoeWIzVm5hQ3dnYjNKY2JpQXFJR052WlhKalpYTWdjSEp2YldselpYTWdabkp2YlNCa2FXWm1aWEpsYm5RZ2MzbHpkR1Z0Y3k1Y2JpQXFJRUJ3WVhKaGJTQjJZV3gxWlNCcGJXMWxaR2xoZEdVZ2NtVm1aWEpsYm1ObElHOXlJSEJ5YjIxcGMyVmNiaUFxTDF4dVpuVnVZM1JwYjI0Z1VTaDJZV3gxWlNrZ2UxeHVJQ0FnSUM4dklFbG1JSFJvWlNCdlltcGxZM1FnYVhNZ1lXeHlaV0ZrZVNCaElGQnliMjFwYzJVc0lISmxkSFZ5YmlCcGRDQmthWEpsWTNSc2VTNGdJRlJvYVhNZ1pXNWhZbXhsYzF4dUlDQWdJQzh2SUhSb1pTQnlaWE52YkhabElHWjFibU4wYVc5dUlIUnZJR0p2ZEdnZ1ltVWdkWE5sWkNCMGJ5QmpjbVZoZEdWa0lISmxabVZ5Wlc1alpYTWdabkp2YlNCdlltcGxZM1J6TEZ4dUlDQWdJQzh2SUdKMWRDQjBieUIwYjJ4bGNtRmliSGtnWTI5bGNtTmxJRzV2Ymkxd2NtOXRhWE5sY3lCMGJ5QndjbTl0YVhObGN5NWNiaUFnSUNCcFppQW9kbUZzZFdVZ2FXNXpkR0Z1WTJWdlppQlFjbTl0YVhObEtTQjdYRzRnSUNBZ0lDQWdJSEpsZEhWeWJpQjJZV3gxWlR0Y2JpQWdJQ0I5WEc1Y2JpQWdJQ0F2THlCaGMzTnBiV2xzWVhSbElIUm9aVzVoWW14bGMxeHVJQ0FnSUdsbUlDaHBjMUJ5YjIxcGMyVkJiR2xyWlNoMllXeDFaU2twSUh0Y2JpQWdJQ0FnSUNBZ2NtVjBkWEp1SUdOdlpYSmpaU2gyWVd4MVpTazdYRzRnSUNBZ2ZTQmxiSE5sSUh0Y2JpQWdJQ0FnSUNBZ2NtVjBkWEp1SUdaMWJHWnBiR3dvZG1Gc2RXVXBPMXh1SUNBZ0lIMWNibjFjYmxFdWNtVnpiMngyWlNBOUlGRTdYRzVjYmk4cUtseHVJQ29nVUdWeVptOXliWE1nWVNCMFlYTnJJR2x1SUdFZ1puVjBkWEpsSUhSMWNtNGdiMllnZEdobElHVjJaVzUwSUd4dmIzQXVYRzRnS2lCQWNHRnlZVzBnZTBaMWJtTjBhVzl1ZlNCMFlYTnJYRzRnS2k5Y2JsRXVibVY0ZEZScFkyc2dQU0J1WlhoMFZHbGphenRjYmx4dUx5b3FYRzRnS2lCRGIyNTBjbTlzY3lCM2FHVjBhR1Z5SUc5eUlHNXZkQ0JzYjI1bklITjBZV05ySUhSeVlXTmxjeUIzYVd4c0lHSmxJRzl1WEc0Z0tpOWNibEV1Ykc5dVoxTjBZV05yVTNWd2NHOXlkQ0E5SUdaaGJITmxPMXh1WEc0dkx5QmxibUZpYkdVZ2JHOXVaeUJ6ZEdGamEzTWdhV1lnVVY5RVJVSlZSeUJwY3lCelpYUmNibWxtSUNoMGVYQmxiMllnY0hKdlkyVnpjeUE5UFQwZ1hDSnZZbXBsWTNSY0lpQW1KaUJ3Y205alpYTnpJQ1ltSUhCeWIyTmxjM011Wlc1MklDWW1JSEJ5YjJObGMzTXVaVzUyTGxGZlJFVkNWVWNwSUh0Y2JpQWdJQ0JSTG14dmJtZFRkR0ZqYTFOMWNIQnZjblFnUFNCMGNuVmxPMXh1ZlZ4dVhHNHZLaXBjYmlBcUlFTnZibk4wY25WamRITWdZU0I3Y0hKdmJXbHpaU3dnY21WemIyeDJaU3dnY21WcVpXTjBmU0J2WW1wbFkzUXVYRzRnS2x4dUlDb2dZSEpsYzI5c2RtVmdJR2x6SUdFZ1kyRnNiR0poWTJzZ2RHOGdhVzUyYjJ0bElIZHBkR2dnWVNCdGIzSmxJSEpsYzI5c2RtVmtJSFpoYkhWbElHWnZjaUIwYUdWY2JpQXFJSEJ5YjIxcGMyVXVJRlJ2SUdaMWJHWnBiR3dnZEdobElIQnliMjFwYzJVc0lHbHVkbTlyWlNCZ2NtVnpiMngyWldBZ2QybDBhQ0JoYm5rZ2RtRnNkV1VnZEdoaGRDQnBjMXh1SUNvZ2JtOTBJR0VnZEdobGJtRmliR1V1SUZSdklISmxhbVZqZENCMGFHVWdjSEp2YldselpTd2dhVzUyYjJ0bElHQnlaWE52YkhabFlDQjNhWFJvSUdFZ2NtVnFaV04wWldSY2JpQXFJSFJvWlc1aFlteGxMQ0J2Y2lCcGJuWnZhMlVnWUhKbGFtVmpkR0FnZDJsMGFDQjBhR1VnY21WaGMyOXVJR1JwY21WamRHeDVMaUJVYnlCeVpYTnZiSFpsSUhSb1pWeHVJQ29nY0hKdmJXbHpaU0IwYnlCaGJtOTBhR1Z5SUhSb1pXNWhZbXhsTENCMGFIVnpJSEIxZEhScGJtY2dhWFFnYVc0Z2RHaGxJSE5oYldVZ2MzUmhkR1VzSUdsdWRtOXJaVnh1SUNvZ1lISmxjMjlzZG1WZ0lIZHBkR2dnZEdoaGRDQnZkR2hsY2lCMGFHVnVZV0pzWlM1Y2JpQXFMMXh1VVM1a1pXWmxjaUE5SUdSbFptVnlPMXh1Wm5WdVkzUnBiMjRnWkdWbVpYSW9LU0I3WEc0Z0lDQWdMeThnYVdZZ1hDSnRaWE56WVdkbGMxd2lJR2x6SUdGdUlGd2lRWEp5WVhsY0lpd2dkR2hoZENCcGJtUnBZMkYwWlhNZ2RHaGhkQ0IwYUdVZ2NISnZiV2x6WlNCb1lYTWdibTkwSUhsbGRGeHVJQ0FnSUM4dklHSmxaVzRnY21WemIyeDJaV1F1SUNCSlppQnBkQ0JwY3lCY0luVnVaR1ZtYVc1bFpGd2lMQ0JwZENCb1lYTWdZbVZsYmlCeVpYTnZiSFpsWkM0Z0lFVmhZMmhjYmlBZ0lDQXZMeUJsYkdWdFpXNTBJRzltSUhSb1pTQnRaWE56WVdkbGN5QmhjbkpoZVNCcGN5QnBkSE5sYkdZZ1lXNGdZWEp5WVhrZ2IyWWdZMjl0Y0d4bGRHVWdZWEpuZFcxbGJuUnpJSFJ2WEc0Z0lDQWdMeThnWm05eWQyRnlaQ0IwYnlCMGFHVWdjbVZ6YjJ4MlpXUWdjSEp2YldselpTNGdJRmRsSUdOdlpYSmpaU0IwYUdVZ2NtVnpiMngxZEdsdmJpQjJZV3gxWlNCMGJ5QmhYRzRnSUNBZ0x5OGdjSEp2YldselpTQjFjMmx1WnlCMGFHVWdZSEpsYzI5c2RtVmdJR1oxYm1OMGFXOXVJR0psWTJGMWMyVWdhWFFnYUdGdVpHeGxjeUJpYjNSb0lHWjFiR3g1WEc0Z0lDQWdMeThnYm05dUxYUm9aVzVoWW14bElIWmhiSFZsY3lCaGJtUWdiM1JvWlhJZ2RHaGxibUZpYkdWeklHZHlZV05sWm5Wc2JIa3VYRzRnSUNBZ2RtRnlJRzFsYzNOaFoyVnpJRDBnVzEwc0lIQnliMmR5WlhOelRHbHpkR1Z1WlhKeklEMGdXMTBzSUhKbGMyOXNkbVZrVUhKdmJXbHpaVHRjYmx4dUlDQWdJSFpoY2lCa1pXWmxjbkpsWkNBOUlHOWlhbVZqZEY5amNtVmhkR1VvWkdWbVpYSXVjSEp2ZEc5MGVYQmxLVHRjYmlBZ0lDQjJZWElnY0hKdmJXbHpaU0E5SUc5aWFtVmpkRjlqY21WaGRHVW9VSEp2YldselpTNXdjbTkwYjNSNWNHVXBPMXh1WEc0Z0lDQWdjSEp2YldselpTNXdjbTl0YVhObFJHbHpjR0YwWTJnZ1BTQm1kVzVqZEdsdmJpQW9jbVZ6YjJ4MlpTd2diM0FzSUc5d1pYSmhibVJ6S1NCN1hHNGdJQ0FnSUNBZ0lIWmhjaUJoY21keklEMGdZWEp5WVhsZmMyeHBZMlVvWVhKbmRXMWxiblJ6S1R0Y2JpQWdJQ0FnSUNBZ2FXWWdLRzFsYzNOaFoyVnpLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQnRaWE56WVdkbGN5NXdkWE5vS0dGeVozTXBPMXh1SUNBZ0lDQWdJQ0FnSUNBZ2FXWWdLRzl3SUQwOVBTQmNJbmRvWlc1Y0lpQW1KaUJ2Y0dWeVlXNWtjMXN4WFNrZ2V5QXZMeUJ3Y205bmNtVnpjeUJ2Y0dWeVlXNWtYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdjSEp2WjNKbGMzTk1hWE4wWlc1bGNuTXVjSFZ6YUNodmNHVnlZVzVrYzFzeFhTazdYRzRnSUNBZ0lDQWdJQ0FnSUNCOVhHNGdJQ0FnSUNBZ0lIMGdaV3h6WlNCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0JSTG01bGVIUlVhV05yS0daMWJtTjBhVzl1SUNncElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQnlaWE52YkhabFpGQnliMjFwYzJVdWNISnZiV2x6WlVScGMzQmhkR05vTG1Gd2NHeDVLSEpsYzI5c2RtVmtVSEp2YldselpTd2dZWEpuY3lrN1hHNGdJQ0FnSUNBZ0lDQWdJQ0I5S1R0Y2JpQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUgwN1hHNWNiaUFnSUNBdkx5QllXRmdnWkdWd2NtVmpZWFJsWkZ4dUlDQWdJSEJ5YjIxcGMyVXVkbUZzZFdWUFppQTlJR1oxYm1OMGFXOXVJQ2dwSUh0Y2JpQWdJQ0FnSUNBZ2FXWWdLRzFsYzNOaFoyVnpLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQnlaWFIxY200Z2NISnZiV2x6WlR0Y2JpQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUNBZ0lDQjJZWElnYm1WaGNtVnlWbUZzZFdVZ1BTQnVaV0Z5WlhJb2NtVnpiMngyWldSUWNtOXRhWE5sS1R0Y2JpQWdJQ0FnSUNBZ2FXWWdLR2x6VUhKdmJXbHpaU2h1WldGeVpYSldZV3gxWlNrcElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUhKbGMyOXNkbVZrVUhKdmJXbHpaU0E5SUc1bFlYSmxjbFpoYkhWbE95QXZMeUJ6YUc5eWRHVnVJR05vWVdsdVhHNGdJQ0FnSUNBZ0lIMWNiaUFnSUNBZ0lDQWdjbVYwZFhKdUlHNWxZWEpsY2xaaGJIVmxPMXh1SUNBZ0lIMDdYRzVjYmlBZ0lDQndjbTl0YVhObExtbHVjM0JsWTNRZ1BTQm1kVzVqZEdsdmJpQW9LU0I3WEc0Z0lDQWdJQ0FnSUdsbUlDZ2hjbVZ6YjJ4MlpXUlFjbTl0YVhObEtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCeVpYUjFjbTRnZXlCemRHRjBaVG9nWENKd1pXNWthVzVuWENJZ2ZUdGNiaUFnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0J5WlhSMWNtNGdjbVZ6YjJ4MlpXUlFjbTl0YVhObExtbHVjM0JsWTNRb0tUdGNiaUFnSUNCOU8xeHVYRzRnSUNBZ2FXWWdLRkV1Ykc5dVoxTjBZV05yVTNWd2NHOXlkQ0FtSmlCb1lYTlRkR0ZqYTNNcElIdGNiaUFnSUNBZ0lDQWdkSEo1SUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJSFJvY205M0lHNWxkeUJGY25KdmNpZ3BPMXh1SUNBZ0lDQWdJQ0I5SUdOaGRHTm9JQ2hsS1NCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0F2THlCT1QxUkZPaUJrYjI0bmRDQjBjbmtnZEc4Z2RYTmxJR0JGY25KdmNpNWpZWEIwZFhKbFUzUmhZMnRVY21GalpXQWdiM0lnZEhKaGJuTm1aWElnZEdobFhHNGdJQ0FnSUNBZ0lDQWdJQ0F2THlCaFkyTmxjM052Y2lCaGNtOTFibVE3SUhSb1lYUWdZMkYxYzJWeklHMWxiVzl5ZVNCc1pXRnJjeUJoY3lCd1pYSWdSMGd0TVRFeExpQktkWE4wWEc0Z0lDQWdJQ0FnSUNBZ0lDQXZMeUJ5WldsbWVTQjBhR1VnYzNSaFkyc2dkSEpoWTJVZ1lYTWdZU0J6ZEhKcGJtY2dRVk5CVUM1Y2JpQWdJQ0FnSUNBZ0lDQWdJQzh2WEc0Z0lDQWdJQ0FnSUNBZ0lDQXZMeUJCZENCMGFHVWdjMkZ0WlNCMGFXMWxMQ0JqZFhRZ2IyWm1JSFJvWlNCbWFYSnpkQ0JzYVc1bE95QnBkQ2R6SUdGc2QyRjVjeUJxZFhOMFhHNGdJQ0FnSUNBZ0lDQWdJQ0F2THlCY0lsdHZZbXBsWTNRZ1VISnZiV2x6WlYxY1hHNWNJaXdnWVhNZ2NHVnlJSFJvWlNCZ2RHOVRkSEpwYm1kZ0xseHVJQ0FnSUNBZ0lDQWdJQ0FnY0hKdmJXbHpaUzV6ZEdGamF5QTlJR1V1YzNSaFkyc3VjM1ZpYzNSeWFXNW5LR1V1YzNSaFkyc3VhVzVrWlhoUFppaGNJbHhjYmx3aUtTQXJJREVwTzF4dUlDQWdJQ0FnSUNCOVhHNGdJQ0FnZlZ4dVhHNGdJQ0FnTHk4Z1RrOVVSVG9nZDJVZ1pHOGdkR2hsSUdOb1pXTnJjeUJtYjNJZ1lISmxjMjlzZG1Wa1VISnZiV2x6WldBZ2FXNGdaV0ZqYUNCdFpYUm9iMlFzSUdsdWMzUmxZV1FnYjJaY2JpQWdJQ0F2THlCamIyNXpiMnhwWkdGMGFXNW5JSFJvWlcwZ2FXNTBieUJnWW1WamIyMWxZQ3dnYzJsdVkyVWdiM1JvWlhKM2FYTmxJSGRsSjJRZ1kzSmxZWFJsSUc1bGQxeHVJQ0FnSUM4dklIQnliMjFwYzJWeklIZHBkR2dnZEdobElHeHBibVZ6SUdCaVpXTnZiV1VvZDJoaGRHVjJaWElvZG1Gc2RXVXBLV0F1SUZObFpTQmxMbWN1SUVkSUxUSTFNaTVjYmx4dUlDQWdJR1oxYm1OMGFXOXVJR0psWTI5dFpTaHVaWGRRY205dGFYTmxLU0I3WEc0Z0lDQWdJQ0FnSUhKbGMyOXNkbVZrVUhKdmJXbHpaU0E5SUc1bGQxQnliMjFwYzJVN1hHNGdJQ0FnSUNBZ0lIQnliMjFwYzJVdWMyOTFjbU5sSUQwZ2JtVjNVSEp2YldselpUdGNibHh1SUNBZ0lDQWdJQ0JoY25KaGVWOXlaV1IxWTJVb2JXVnpjMkZuWlhNc0lHWjFibU4wYVc5dUlDaDFibVJsWm1sdVpXUXNJRzFsYzNOaFoyVXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lGRXVibVY0ZEZScFkyc29ablZ1WTNScGIyNGdLQ2tnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUc1bGQxQnliMjFwYzJVdWNISnZiV2x6WlVScGMzQmhkR05vTG1Gd2NHeDVLRzVsZDFCeWIyMXBjMlVzSUcxbGMzTmhaMlVwTzF4dUlDQWdJQ0FnSUNBZ0lDQWdmU2s3WEc0Z0lDQWdJQ0FnSUgwc0lIWnZhV1FnTUNrN1hHNWNiaUFnSUNBZ0lDQWdiV1Z6YzJGblpYTWdQU0IyYjJsa0lEQTdYRzRnSUNBZ0lDQWdJSEJ5YjJkeVpYTnpUR2x6ZEdWdVpYSnpJRDBnZG05cFpDQXdPMXh1SUNBZ0lIMWNibHh1SUNBZ0lHUmxabVZ5Y21Wa0xuQnliMjFwYzJVZ1BTQndjbTl0YVhObE8xeHVJQ0FnSUdSbFptVnljbVZrTG5KbGMyOXNkbVVnUFNCbWRXNWpkR2x2YmlBb2RtRnNkV1VwSUh0Y2JpQWdJQ0FnSUNBZ2FXWWdLSEpsYzI5c2RtVmtVSEp2YldselpTa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2NtVjBkWEp1TzF4dUlDQWdJQ0FnSUNCOVhHNWNiaUFnSUNBZ0lDQWdZbVZqYjIxbEtGRW9kbUZzZFdVcEtUdGNiaUFnSUNCOU8xeHVYRzRnSUNBZ1pHVm1aWEp5WldRdVpuVnNabWxzYkNBOUlHWjFibU4wYVc5dUlDaDJZV3gxWlNrZ2UxeHVJQ0FnSUNBZ0lDQnBaaUFvY21WemIyeDJaV1JRY205dGFYTmxLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQnlaWFIxY200N1hHNGdJQ0FnSUNBZ0lIMWNibHh1SUNBZ0lDQWdJQ0JpWldOdmJXVW9ablZzWm1sc2JDaDJZV3gxWlNrcE8xeHVJQ0FnSUgwN1hHNGdJQ0FnWkdWbVpYSnlaV1F1Y21WcVpXTjBJRDBnWm5WdVkzUnBiMjRnS0hKbFlYTnZiaWtnZTF4dUlDQWdJQ0FnSUNCcFppQW9jbVZ6YjJ4MlpXUlFjbTl0YVhObEtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCeVpYUjFjbTQ3WEc0Z0lDQWdJQ0FnSUgxY2JseHVJQ0FnSUNBZ0lDQmlaV052YldVb2NtVnFaV04wS0hKbFlYTnZiaWtwTzF4dUlDQWdJSDA3WEc0Z0lDQWdaR1ZtWlhKeVpXUXVibTkwYVdaNUlEMGdablZ1WTNScGIyNGdLSEJ5YjJkeVpYTnpLU0I3WEc0Z0lDQWdJQ0FnSUdsbUlDaHlaWE52YkhabFpGQnliMjFwYzJVcElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUhKbGRIVnlianRjYmlBZ0lDQWdJQ0FnZlZ4dVhHNGdJQ0FnSUNBZ0lHRnljbUY1WDNKbFpIVmpaU2h3Y205bmNtVnpjMHhwYzNSbGJtVnljeXdnWm5WdVkzUnBiMjRnS0hWdVpHVm1hVzVsWkN3Z2NISnZaM0psYzNOTWFYTjBaVzVsY2lrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnVVM1dVpYaDBWR2xqYXlobWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdjSEp2WjNKbGMzTk1hWE4wWlc1bGNpaHdjbTluY21WemN5azdYRzRnSUNBZ0lDQWdJQ0FnSUNCOUtUdGNiaUFnSUNBZ0lDQWdmU3dnZG05cFpDQXdLVHRjYmlBZ0lDQjlPMXh1WEc0Z0lDQWdjbVYwZFhKdUlHUmxabVZ5Y21Wa08xeHVmVnh1WEc0dktpcGNiaUFxSUVOeVpXRjBaWE1nWVNCT2IyUmxMWE4wZVd4bElHTmhiR3hpWVdOcklIUm9ZWFFnZDJsc2JDQnlaWE52YkhabElHOXlJSEpsYW1WamRDQjBhR1VnWkdWbVpYSnlaV1JjYmlBcUlIQnliMjFwYzJVdVhHNGdLaUJBY21WMGRYSnVjeUJoSUc1dlpHVmlZV05yWEc0Z0tpOWNibVJsWm1WeUxuQnliM1J2ZEhsd1pTNXRZV3RsVG05a1pWSmxjMjlzZG1WeUlEMGdablZ1WTNScGIyNGdLQ2tnZTF4dUlDQWdJSFpoY2lCelpXeG1JRDBnZEdocGN6dGNiaUFnSUNCeVpYUjFjbTRnWm5WdVkzUnBiMjRnS0dWeWNtOXlMQ0IyWVd4MVpTa2dlMXh1SUNBZ0lDQWdJQ0JwWmlBb1pYSnliM0lwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJSE5sYkdZdWNtVnFaV04wS0dWeWNtOXlLVHRjYmlBZ0lDQWdJQ0FnZlNCbGJITmxJR2xtSUNoaGNtZDFiV1Z1ZEhNdWJHVnVaM1JvSUQ0Z01pa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2MyVnNaaTV5WlhOdmJIWmxLR0Z5Y21GNVgzTnNhV05sS0dGeVozVnRaVzUwY3l3Z01Ta3BPMXh1SUNBZ0lDQWdJQ0I5SUdWc2MyVWdlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2MyVnNaaTV5WlhOdmJIWmxLSFpoYkhWbEtUdGNiaUFnSUNBZ0lDQWdmVnh1SUNBZ0lIMDdYRzU5TzF4dVhHNHZLaXBjYmlBcUlFQndZWEpoYlNCeVpYTnZiSFpsY2lCN1JuVnVZM1JwYjI1OUlHRWdablZ1WTNScGIyNGdkR2hoZENCeVpYUjFjbTV6SUc1dmRHaHBibWNnWVc1a0lHRmpZMlZ3ZEhOY2JpQXFJSFJvWlNCeVpYTnZiSFpsTENCeVpXcGxZM1FzSUdGdVpDQnViM1JwWm5rZ1puVnVZM1JwYjI1eklHWnZjaUJoSUdSbFptVnljbVZrTGx4dUlDb2dRSEpsZEhWeWJuTWdZU0J3Y205dGFYTmxJSFJvWVhRZ2JXRjVJR0psSUhKbGMyOXNkbVZrSUhkcGRHZ2dkR2hsSUdkcGRtVnVJSEpsYzI5c2RtVWdZVzVrSUhKbGFtVmpkRnh1SUNvZ1puVnVZM1JwYjI1ekxDQnZjaUJ5WldwbFkzUmxaQ0JpZVNCaElIUm9jbTkzYmlCbGVHTmxjSFJwYjI0Z2FXNGdjbVZ6YjJ4MlpYSmNiaUFxTDF4dVVTNVFjbTl0YVhObElEMGdjSEp2YldselpUc2dMeThnUlZNMlhHNVJMbkJ5YjIxcGMyVWdQU0J3Y205dGFYTmxPMXh1Wm5WdVkzUnBiMjRnY0hKdmJXbHpaU2h5WlhOdmJIWmxjaWtnZTF4dUlDQWdJR2xtSUNoMGVYQmxiMllnY21WemIyeDJaWElnSVQwOUlGd2lablZ1WTNScGIyNWNJaWtnZTF4dUlDQWdJQ0FnSUNCMGFISnZkeUJ1WlhjZ1ZIbHdaVVZ5Y205eUtGd2ljbVZ6YjJ4MlpYSWdiWFZ6ZENCaVpTQmhJR1oxYm1OMGFXOXVMbHdpS1R0Y2JpQWdJQ0I5WEc0Z0lDQWdkbUZ5SUdSbFptVnljbVZrSUQwZ1pHVm1aWElvS1R0Y2JpQWdJQ0IwY25rZ2UxeHVJQ0FnSUNBZ0lDQnlaWE52YkhabGNpaGtaV1psY25KbFpDNXlaWE52YkhabExDQmtaV1psY25KbFpDNXlaV3BsWTNRc0lHUmxabVZ5Y21Wa0xtNXZkR2xtZVNrN1hHNGdJQ0FnZlNCallYUmphQ0FvY21WaGMyOXVLU0I3WEc0Z0lDQWdJQ0FnSUdSbFptVnljbVZrTG5KbGFtVmpkQ2h5WldGemIyNHBPMXh1SUNBZ0lIMWNiaUFnSUNCeVpYUjFjbTRnWkdWbVpYSnlaV1F1Y0hKdmJXbHpaVHRjYm4xY2JseHVjSEp2YldselpTNXlZV05sSUQwZ2NtRmpaVHNnTHk4Z1JWTTJYRzV3Y205dGFYTmxMbUZzYkNBOUlHRnNiRHNnTHk4Z1JWTTJYRzV3Y205dGFYTmxMbkpsYW1WamRDQTlJSEpsYW1WamREc2dMeThnUlZNMlhHNXdjbTl0YVhObExuSmxjMjlzZG1VZ1BTQlJPeUF2THlCRlV6WmNibHh1THk4Z1dGaFlJR1Y0Y0dWeWFXMWxiblJoYkM0Z0lGUm9hWE1nYldWMGFHOWtJR2x6SUdFZ2QyRjVJSFJ2SUdSbGJtOTBaU0IwYUdGMElHRWdiRzlqWVd3Z2RtRnNkV1VnYVhOY2JpOHZJSE5sY21saGJHbDZZV0pzWlNCaGJtUWdjMmh2ZFd4a0lHSmxJR2x0YldWa2FXRjBaV3g1SUdScGMzQmhkR05vWldRZ2RHOGdZU0J5WlcxdmRHVWdkWEJ2YmlCeVpYRjFaWE4wTEZ4dUx5OGdhVzV6ZEdWaFpDQnZaaUJ3WVhOemFXNW5JR0VnY21WbVpYSmxibU5sTGx4dVVTNXdZWE56UW5sRGIzQjVJRDBnWm5WdVkzUnBiMjRnS0c5aWFtVmpkQ2tnZTF4dUlDQWdJQzh2Wm5KbFpYcGxLRzlpYW1WamRDazdYRzRnSUNBZ0x5OXdZWE56UW5sRGIzQnBaWE11YzJWMEtHOWlhbVZqZEN3Z2RISjFaU2s3WEc0Z0lDQWdjbVYwZFhKdUlHOWlhbVZqZER0Y2JuMDdYRzVjYmxCeWIyMXBjMlV1Y0hKdmRHOTBlWEJsTG5CaGMzTkNlVU52Y0hrZ1BTQm1kVzVqZEdsdmJpQW9LU0I3WEc0Z0lDQWdMeTltY21WbGVtVW9iMkpxWldOMEtUdGNiaUFnSUNBdkwzQmhjM05DZVVOdmNHbGxjeTV6WlhRb2IySnFaV04wTENCMGNuVmxLVHRjYmlBZ0lDQnlaWFIxY200Z2RHaHBjenRjYm4wN1hHNWNiaThxS2x4dUlDb2dTV1lnZEhkdklIQnliMjFwYzJWeklHVjJaVzUwZFdGc2JIa2dablZzWm1sc2JDQjBieUIwYUdVZ2MyRnRaU0IyWVd4MVpTd2djSEp2YldselpYTWdkR2hoZENCMllXeDFaU3hjYmlBcUlHSjFkQ0J2ZEdobGNuZHBjMlVnY21WcVpXTjBjeTVjYmlBcUlFQndZWEpoYlNCNElIdEJibmtxZlZ4dUlDb2dRSEJoY21GdElIa2dlMEZ1ZVNwOVhHNGdLaUJBY21WMGRYSnVjeUI3UVc1NUtuMGdZU0J3Y205dGFYTmxJR1p2Y2lCNElHRnVaQ0I1SUdsbUlIUm9aWGtnWVhKbElIUm9aU0J6WVcxbExDQmlkWFFnWVNCeVpXcGxZM1JwYjI1Y2JpQXFJRzkwYUdWeWQybHpaUzVjYmlBcVhHNGdLaTljYmxFdWFtOXBiaUE5SUdaMWJtTjBhVzl1SUNoNExDQjVLU0I3WEc0Z0lDQWdjbVYwZFhKdUlGRW9lQ2t1YW05cGJpaDVLVHRjYm4wN1hHNWNibEJ5YjIxcGMyVXVjSEp2ZEc5MGVYQmxMbXB2YVc0Z1BTQm1kVzVqZEdsdmJpQW9kR2hoZENrZ2UxeHVJQ0FnSUhKbGRIVnliaUJSS0Z0MGFHbHpMQ0IwYUdGMFhTa3VjM0J5WldGa0tHWjFibU4wYVc5dUlDaDRMQ0I1S1NCN1hHNGdJQ0FnSUNBZ0lHbG1JQ2g0SUQwOVBTQjVLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQXZMeUJVVDBSUE9pQmNJajA5UFZ3aUlITm9iM1ZzWkNCaVpTQlBZbXBsWTNRdWFYTWdiM0lnWlhGMWFYWmNiaUFnSUNBZ0lDQWdJQ0FnSUhKbGRIVnliaUI0TzF4dUlDQWdJQ0FnSUNCOUlHVnNjMlVnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdkR2h5YjNjZ2JtVjNJRVZ5Y205eUtGd2lRMkZ1SjNRZ2FtOXBiam9nYm05MElIUm9aU0J6WVcxbE9pQmNJaUFySUhnZ0t5QmNJaUJjSWlBcklIa3BPMXh1SUNBZ0lDQWdJQ0I5WEc0Z0lDQWdmU2s3WEc1OU8xeHVYRzR2S2lwY2JpQXFJRkpsZEhWeWJuTWdZU0J3Y205dGFYTmxJR1p2Y2lCMGFHVWdabWx5YzNRZ2IyWWdZVzRnWVhKeVlYa2diMllnY0hKdmJXbHpaWE1nZEc4Z1ltVmpiMjFsSUhObGRIUnNaV1F1WEc0Z0tpQkFjR0Z5WVcwZ1lXNXpkMlZ5Y3lCN1FYSnlZWGxiUVc1NUtsMTlJSEJ5YjIxcGMyVnpJSFJ2SUhKaFkyVmNiaUFxSUVCeVpYUjFjbTV6SUh0QmJua3FmU0IwYUdVZ1ptbHljM1FnY0hKdmJXbHpaU0IwYnlCaVpTQnpaWFIwYkdWa1hHNGdLaTljYmxFdWNtRmpaU0E5SUhKaFkyVTdYRzVtZFc1amRHbHZiaUJ5WVdObEtHRnVjM2RsY2xCektTQjdYRzRnSUNBZ2NtVjBkWEp1SUhCeWIyMXBjMlVvWm5WdVkzUnBiMjRnS0hKbGMyOXNkbVVzSUhKbGFtVmpkQ2tnZTF4dUlDQWdJQ0FnSUNBdkx5QlRkMmwwWTJnZ2RHOGdkR2hwY3lCdmJtTmxJSGRsSUdOaGJpQmhjM04xYldVZ1lYUWdiR1ZoYzNRZ1JWTTFYRzRnSUNBZ0lDQWdJQzh2SUdGdWMzZGxjbEJ6TG1admNrVmhZMmdvWm5WdVkzUnBiMjRnS0dGdWMzZGxjbEFwSUh0Y2JpQWdJQ0FnSUNBZ0x5OGdJQ0FnSUZFb1lXNXpkMlZ5VUNrdWRHaGxiaWh5WlhOdmJIWmxMQ0J5WldwbFkzUXBPMXh1SUNBZ0lDQWdJQ0F2THlCOUtUdGNiaUFnSUNBZ0lDQWdMeThnVlhObElIUm9hWE1nYVc0Z2RHaGxJRzFsWVc1MGFXMWxYRzRnSUNBZ0lDQWdJR1p2Y2lBb2RtRnlJR2tnUFNBd0xDQnNaVzRnUFNCaGJuTjNaWEpRY3k1c1pXNW5kR2c3SUdrZ1BDQnNaVzQ3SUdrckt5a2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ1VTaGhibk4zWlhKUWMxdHBYU2t1ZEdobGJpaHlaWE52YkhabExDQnlaV3BsWTNRcE8xeHVJQ0FnSUNBZ0lDQjlYRzRnSUNBZ2ZTazdYRzU5WEc1Y2JsQnliMjFwYzJVdWNISnZkRzkwZVhCbExuSmhZMlVnUFNCbWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ2NtVjBkWEp1SUhSb2FYTXVkR2hsYmloUkxuSmhZMlVwTzF4dWZUdGNibHh1THlvcVhHNGdLaUJEYjI1emRISjFZM1J6SUdFZ1VISnZiV2x6WlNCM2FYUm9JR0VnY0hKdmJXbHpaU0JrWlhOamNtbHdkRzl5SUc5aWFtVmpkQ0JoYm1RZ2IzQjBhVzl1WVd3Z1ptRnNiR0poWTJ0Y2JpQXFJR1oxYm1OMGFXOXVMaUFnVkdobElHUmxjMk55YVhCMGIzSWdZMjl1ZEdGcGJuTWdiV1YwYUc5a2N5QnNhV3RsSUhkb1pXNG9jbVZxWldOMFpXUXBMQ0JuWlhRb2JtRnRaU2tzWEc0Z0tpQnpaWFFvYm1GdFpTd2dkbUZzZFdVcExDQndiM04wS0c1aGJXVXNJR0Z5WjNNcExDQmhibVFnWkdWc1pYUmxLRzVoYldVcExDQjNhR2xqYUNCaGJHeGNiaUFxSUhKbGRIVnliaUJsYVhSb1pYSWdZU0IyWVd4MVpTd2dZU0J3Y205dGFYTmxJR1p2Y2lCaElIWmhiSFZsTENCdmNpQmhJSEpsYW1WamRHbHZiaTRnSUZSb1pTQm1ZV3hzWW1GamExeHVJQ29nWVdOalpYQjBjeUIwYUdVZ2IzQmxjbUYwYVc5dUlHNWhiV1VzSUdFZ2NtVnpiMngyWlhJc0lHRnVaQ0JoYm5rZ1puVnlkR2hsY2lCaGNtZDFiV1Z1ZEhNZ2RHaGhkQ0IzYjNWc1pGeHVJQ29nYUdGMlpTQmlaV1Z1SUdadmNuZGhjbVJsWkNCMGJ5QjBhR1VnWVhCd2NtOXdjbWxoZEdVZ2JXVjBhRzlrSUdGaWIzWmxJR2hoWkNCaElHMWxkR2h2WkNCaVpXVnVYRzRnS2lCd2NtOTJhV1JsWkNCM2FYUm9JSFJvWlNCd2NtOXdaWElnYm1GdFpTNGdJRlJvWlNCQlVFa2diV0ZyWlhNZ2JtOGdaM1ZoY21GdWRHVmxjeUJoWW05MWRDQjBhR1VnYm1GMGRYSmxYRzRnS2lCdlppQjBhR1VnY21WMGRYSnVaV1FnYjJKcVpXTjBMQ0JoY0dGeWRDQm1jbTl0SUhSb1lYUWdhWFFnYVhNZ2RYTmhZbXhsSUhkb1pYSmxaWFpsY2lCd2NtOXRhWE5sY3lCaGNtVmNiaUFxSUdKdmRXZG9kQ0JoYm1RZ2MyOXNaQzVjYmlBcUwxeHVVUzV0WVd0bFVISnZiV2x6WlNBOUlGQnliMjFwYzJVN1hHNW1kVzVqZEdsdmJpQlFjbTl0YVhObEtHUmxjMk55YVhCMGIzSXNJR1poYkd4aVlXTnJMQ0JwYm5Od1pXTjBLU0I3WEc0Z0lDQWdhV1lnS0daaGJHeGlZV05ySUQwOVBTQjJiMmxrSURBcElIdGNiaUFnSUNBZ0lDQWdabUZzYkdKaFkyc2dQU0JtZFc1amRHbHZiaUFvYjNBcElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUhKbGRIVnliaUJ5WldwbFkzUW9ibVYzSUVWeWNtOXlLRnh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJRndpVUhKdmJXbHpaU0JrYjJWeklHNXZkQ0J6ZFhCd2IzSjBJRzl3WlhKaGRHbHZiam9nWENJZ0t5QnZjRnh1SUNBZ0lDQWdJQ0FnSUNBZ0tTazdYRzRnSUNBZ0lDQWdJSDA3WEc0Z0lDQWdmVnh1SUNBZ0lHbG1JQ2hwYm5Od1pXTjBJRDA5UFNCMmIybGtJREFwSUh0Y2JpQWdJQ0FnSUNBZ2FXNXpjR1ZqZENBOUlHWjFibU4wYVc5dUlDZ3BJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lISmxkSFZ5YmlCN2MzUmhkR1U2SUZ3aWRXNXJibTkzYmx3aWZUdGNiaUFnSUNBZ0lDQWdmVHRjYmlBZ0lDQjlYRzVjYmlBZ0lDQjJZWElnY0hKdmJXbHpaU0E5SUc5aWFtVmpkRjlqY21WaGRHVW9VSEp2YldselpTNXdjbTkwYjNSNWNHVXBPMXh1WEc0Z0lDQWdjSEp2YldselpTNXdjbTl0YVhObFJHbHpjR0YwWTJnZ1BTQm1kVzVqZEdsdmJpQW9jbVZ6YjJ4MlpTd2diM0FzSUdGeVozTXBJSHRjYmlBZ0lDQWdJQ0FnZG1GeUlISmxjM1ZzZER0Y2JpQWdJQ0FnSUNBZ2RISjVJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lHbG1JQ2hrWlhOamNtbHdkRzl5VzI5d1hTa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJSEpsYzNWc2RDQTlJR1JsYzJOeWFYQjBiM0piYjNCZExtRndjR3g1S0hCeWIyMXBjMlVzSUdGeVozTXBPMXh1SUNBZ0lDQWdJQ0FnSUNBZ2ZTQmxiSE5sSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCeVpYTjFiSFFnUFNCbVlXeHNZbUZqYXk1allXeHNLSEJ5YjIxcGMyVXNJRzl3TENCaGNtZHpLVHRjYmlBZ0lDQWdJQ0FnSUNBZ0lIMWNiaUFnSUNBZ0lDQWdmU0JqWVhSamFDQW9aWGhqWlhCMGFXOXVLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQnlaWE4xYkhRZ1BTQnlaV3BsWTNRb1pYaGpaWEIwYVc5dUtUdGNiaUFnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0JwWmlBb2NtVnpiMngyWlNrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnY21WemIyeDJaU2h5WlhOMWJIUXBPMXh1SUNBZ0lDQWdJQ0I5WEc0Z0lDQWdmVHRjYmx4dUlDQWdJSEJ5YjIxcGMyVXVhVzV6Y0dWamRDQTlJR2x1YzNCbFkzUTdYRzVjYmlBZ0lDQXZMeUJZV0ZnZ1pHVndjbVZqWVhSbFpDQmdkbUZzZFdWUFptQWdZVzVrSUdCbGVHTmxjSFJwYjI1Z0lITjFjSEJ2Y25SY2JpQWdJQ0JwWmlBb2FXNXpjR1ZqZENrZ2UxeHVJQ0FnSUNBZ0lDQjJZWElnYVc1emNHVmpkR1ZrSUQwZ2FXNXpjR1ZqZENncE8xeHVJQ0FnSUNBZ0lDQnBaaUFvYVc1emNHVmpkR1ZrTG5OMFlYUmxJRDA5UFNCY0luSmxhbVZqZEdWa1hDSXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lIQnliMjFwYzJVdVpYaGpaWEIwYVc5dUlEMGdhVzV6Y0dWamRHVmtMbkpsWVhOdmJqdGNiaUFnSUNBZ0lDQWdmVnh1WEc0Z0lDQWdJQ0FnSUhCeWIyMXBjMlV1ZG1Gc2RXVlBaaUE5SUdaMWJtTjBhVzl1SUNncElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUhaaGNpQnBibk53WldOMFpXUWdQU0JwYm5Od1pXTjBLQ2s3WEc0Z0lDQWdJQ0FnSUNBZ0lDQnBaaUFvYVc1emNHVmpkR1ZrTG5OMFlYUmxJRDA5UFNCY0luQmxibVJwYm1kY0lpQjhmRnh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJR2x1YzNCbFkzUmxaQzV6ZEdGMFpTQTlQVDBnWENKeVpXcGxZM1JsWkZ3aUtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdjbVYwZFhKdUlIQnliMjFwYzJVN1hHNGdJQ0FnSUNBZ0lDQWdJQ0I5WEc0Z0lDQWdJQ0FnSUNBZ0lDQnlaWFIxY200Z2FXNXpjR1ZqZEdWa0xuWmhiSFZsTzF4dUlDQWdJQ0FnSUNCOU8xeHVJQ0FnSUgxY2JseHVJQ0FnSUhKbGRIVnliaUJ3Y205dGFYTmxPMXh1ZlZ4dVhHNVFjbTl0YVhObExuQnliM1J2ZEhsd1pTNTBiMU4wY21sdVp5QTlJR1oxYm1OMGFXOXVJQ2dwSUh0Y2JpQWdJQ0J5WlhSMWNtNGdYQ0piYjJKcVpXTjBJRkJ5YjIxcGMyVmRYQ0k3WEc1OU8xeHVYRzVRY205dGFYTmxMbkJ5YjNSdmRIbHdaUzUwYUdWdUlEMGdablZ1WTNScGIyNGdLR1oxYkdacGJHeGxaQ3dnY21WcVpXTjBaV1FzSUhCeWIyZHlaWE56WldRcElIdGNiaUFnSUNCMllYSWdjMlZzWmlBOUlIUm9hWE03WEc0Z0lDQWdkbUZ5SUdSbFptVnljbVZrSUQwZ1pHVm1aWElvS1R0Y2JpQWdJQ0IyWVhJZ1pHOXVaU0E5SUdaaGJITmxPeUFnSUM4dklHVnVjM1Z5WlNCMGFHVWdkVzUwY25WemRHVmtJSEJ5YjIxcGMyVWdiV0ZyWlhNZ1lYUWdiVzl6ZENCaFhHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBdkx5QnphVzVuYkdVZ1kyRnNiQ0IwYnlCdmJtVWdiMllnZEdobElHTmhiR3hpWVdOcmMxeHVYRzRnSUNBZ1puVnVZM1JwYjI0Z1gyWjFiR1pwYkd4bFpDaDJZV3gxWlNrZ2UxeHVJQ0FnSUNBZ0lDQjBjbmtnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdjbVYwZFhKdUlIUjVjR1Z2WmlCbWRXeG1hV3hzWldRZ1BUMDlJRndpWm5WdVkzUnBiMjVjSWlBL0lHWjFiR1pwYkd4bFpDaDJZV3gxWlNrZ09pQjJZV3gxWlR0Y2JpQWdJQ0FnSUNBZ2ZTQmpZWFJqYUNBb1pYaGpaWEIwYVc5dUtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCeVpYUjFjbTRnY21WcVpXTjBLR1Y0WTJWd2RHbHZiaWs3WEc0Z0lDQWdJQ0FnSUgxY2JpQWdJQ0I5WEc1Y2JpQWdJQ0JtZFc1amRHbHZiaUJmY21WcVpXTjBaV1FvWlhoalpYQjBhVzl1S1NCN1hHNGdJQ0FnSUNBZ0lHbG1JQ2gwZVhCbGIyWWdjbVZxWldOMFpXUWdQVDA5SUZ3aVpuVnVZM1JwYjI1Y0lpa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2JXRnJaVk4wWVdOclZISmhZMlZNYjI1bktHVjRZMlZ3ZEdsdmJpd2djMlZzWmlrN1hHNGdJQ0FnSUNBZ0lDQWdJQ0IwY25rZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lISmxkSFZ5YmlCeVpXcGxZM1JsWkNobGVHTmxjSFJwYjI0cE8xeHVJQ0FnSUNBZ0lDQWdJQ0FnZlNCallYUmphQ0FvYm1WM1JYaGpaWEIwYVc5dUtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdjbVYwZFhKdUlISmxhbVZqZENodVpYZEZlR05sY0hScGIyNHBPMXh1SUNBZ0lDQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUNBZ0lDQjlYRzRnSUNBZ0lDQWdJSEpsZEhWeWJpQnlaV3BsWTNRb1pYaGpaWEIwYVc5dUtUdGNiaUFnSUNCOVhHNWNiaUFnSUNCbWRXNWpkR2x2YmlCZmNISnZaM0psYzNObFpDaDJZV3gxWlNrZ2UxeHVJQ0FnSUNBZ0lDQnlaWFIxY200Z2RIbHdaVzltSUhCeWIyZHlaWE56WldRZ1BUMDlJRndpWm5WdVkzUnBiMjVjSWlBL0lIQnliMmR5WlhOelpXUW9kbUZzZFdVcElEb2dkbUZzZFdVN1hHNGdJQ0FnZlZ4dVhHNGdJQ0FnVVM1dVpYaDBWR2xqYXlobWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ0lDQWdJSE5sYkdZdWNISnZiV2x6WlVScGMzQmhkR05vS0daMWJtTjBhVzl1SUNoMllXeDFaU2tnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdhV1lnS0dSdmJtVXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0J5WlhSMWNtNDdYRzRnSUNBZ0lDQWdJQ0FnSUNCOVhHNGdJQ0FnSUNBZ0lDQWdJQ0JrYjI1bElEMGdkSEoxWlR0Y2JseHVJQ0FnSUNBZ0lDQWdJQ0FnWkdWbVpYSnlaV1F1Y21WemIyeDJaU2hmWm5Wc1ptbHNiR1ZrS0haaGJIVmxLU2s3WEc0Z0lDQWdJQ0FnSUgwc0lGd2lkMmhsYmx3aUxDQmJablZ1WTNScGIyNGdLR1Y0WTJWd2RHbHZiaWtnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdhV1lnS0dSdmJtVXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0J5WlhSMWNtNDdYRzRnSUNBZ0lDQWdJQ0FnSUNCOVhHNGdJQ0FnSUNBZ0lDQWdJQ0JrYjI1bElEMGdkSEoxWlR0Y2JseHVJQ0FnSUNBZ0lDQWdJQ0FnWkdWbVpYSnlaV1F1Y21WemIyeDJaU2hmY21WcVpXTjBaV1FvWlhoalpYQjBhVzl1S1NrN1hHNGdJQ0FnSUNBZ0lIMWRLVHRjYmlBZ0lDQjlLVHRjYmx4dUlDQWdJQzh2SUZCeWIyZHlaWE56SUhCeWIzQmhaMkYwYjNJZ2JtVmxaQ0IwYnlCaVpTQmhkSFJoWTJobFpDQnBiaUIwYUdVZ1kzVnljbVZ1ZENCMGFXTnJMbHh1SUNBZ0lITmxiR1l1Y0hKdmJXbHpaVVJwYzNCaGRHTm9LSFp2YVdRZ01Dd2dYQ0ozYUdWdVhDSXNJRnQyYjJsa0lEQXNJR1oxYm1OMGFXOXVJQ2gyWVd4MVpTa2dlMXh1SUNBZ0lDQWdJQ0IyWVhJZ2JtVjNWbUZzZFdVN1hHNGdJQ0FnSUNBZ0lIWmhjaUIwYUhKbGR5QTlJR1poYkhObE8xeHVJQ0FnSUNBZ0lDQjBjbmtnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdibVYzVm1Gc2RXVWdQU0JmY0hKdlozSmxjM05sWkNoMllXeDFaU2s3WEc0Z0lDQWdJQ0FnSUgwZ1kyRjBZMmdnS0dVcElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUhSb2NtVjNJRDBnZEhKMVpUdGNiaUFnSUNBZ0lDQWdJQ0FnSUdsbUlDaFJMbTl1WlhKeWIzSXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0JSTG05dVpYSnliM0lvWlNrN1hHNGdJQ0FnSUNBZ0lDQWdJQ0I5SUdWc2MyVWdlMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJSFJvY205M0lHVTdYRzRnSUNBZ0lDQWdJQ0FnSUNCOVhHNGdJQ0FnSUNBZ0lIMWNibHh1SUNBZ0lDQWdJQ0JwWmlBb0lYUm9jbVYzS1NCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0JrWldabGNuSmxaQzV1YjNScFpua29ibVYzVm1Gc2RXVXBPMXh1SUNBZ0lDQWdJQ0I5WEc0Z0lDQWdmVjBwTzF4dVhHNGdJQ0FnY21WMGRYSnVJR1JsWm1WeWNtVmtMbkJ5YjIxcGMyVTdYRzU5TzF4dVhHNVJMblJoY0NBOUlHWjFibU4wYVc5dUlDaHdjbTl0YVhObExDQmpZV3hzWW1GamF5a2dlMXh1SUNBZ0lISmxkSFZ5YmlCUktIQnliMjFwYzJVcExuUmhjQ2hqWVd4c1ltRmpheWs3WEc1OU8xeHVYRzR2S2lwY2JpQXFJRmR2Y210eklHRnNiVzl6ZENCc2FXdGxJRndpWm1sdVlXeHNlVndpTENCaWRYUWdibTkwSUdOaGJHeGxaQ0JtYjNJZ2NtVnFaV04wYVc5dWN5NWNiaUFxSUU5eWFXZHBibUZzSUhKbGMyOXNkWFJwYjI0Z2RtRnNkV1VnYVhNZ2NHRnpjMlZrSUhSb2NtOTFaMmdnWTJGc2JHSmhZMnNnZFc1aFptWmxZM1JsWkM1Y2JpQXFJRU5oYkd4aVlXTnJJRzFoZVNCeVpYUjFjbTRnWVNCd2NtOXRhWE5sSUhSb1lYUWdkMmxzYkNCaVpTQmhkMkZwZEdWa0lHWnZjaTVjYmlBcUlFQndZWEpoYlNCN1JuVnVZM1JwYjI1OUlHTmhiR3hpWVdOclhHNGdLaUJBY21WMGRYSnVjeUI3VVM1UWNtOXRhWE5sZlZ4dUlDb2dRR1Y0WVcxd2JHVmNiaUFxSUdSdlUyOXRaWFJvYVc1bktDbGNiaUFxSUNBZ0xuUm9aVzRvTGk0dUtWeHVJQ29nSUNBdWRHRndLR052Ym5OdmJHVXViRzluS1Z4dUlDb2dJQ0F1ZEdobGJpZ3VMaTRwTzF4dUlDb3ZYRzVRY205dGFYTmxMbkJ5YjNSdmRIbHdaUzUwWVhBZ1BTQm1kVzVqZEdsdmJpQW9ZMkZzYkdKaFkyc3BJSHRjYmlBZ0lDQmpZV3hzWW1GamF5QTlJRkVvWTJGc2JHSmhZMnNwTzF4dVhHNGdJQ0FnY21WMGRYSnVJSFJvYVhNdWRHaGxiaWhtZFc1amRHbHZiaUFvZG1Gc2RXVXBJSHRjYmlBZ0lDQWdJQ0FnY21WMGRYSnVJR05oYkd4aVlXTnJMbVpqWVd4c0tIWmhiSFZsS1M1MGFHVnVVbVZ6YjJ4MlpTaDJZV3gxWlNrN1hHNGdJQ0FnZlNrN1hHNTlPMXh1WEc0dktpcGNiaUFxSUZKbFoybHpkR1Z5Y3lCaGJpQnZZbk5sY25abGNpQnZiaUJoSUhCeWIyMXBjMlV1WEc0Z0tseHVJQ29nUjNWaGNtRnVkR1ZsY3pwY2JpQXFYRzRnS2lBeExpQjBhR0YwSUdaMWJHWnBiR3hsWkNCaGJtUWdjbVZxWldOMFpXUWdkMmxzYkNCaVpTQmpZV3hzWldRZ2IyNXNlU0J2Ym1ObExseHVJQ29nTWk0Z2RHaGhkQ0JsYVhSb1pYSWdkR2hsSUdaMWJHWnBiR3hsWkNCallXeHNZbUZqYXlCdmNpQjBhR1VnY21WcVpXTjBaV1FnWTJGc2JHSmhZMnNnZDJsc2JDQmlaVnh1SUNvZ0lDQWdZMkZzYkdWa0xDQmlkWFFnYm05MElHSnZkR2d1WEc0Z0tpQXpMaUIwYUdGMElHWjFiR1pwYkd4bFpDQmhibVFnY21WcVpXTjBaV1FnZDJsc2JDQnViM1FnWW1VZ1kyRnNiR1ZrSUdsdUlIUm9hWE1nZEhWeWJpNWNiaUFxWEc0Z0tpQkFjR0Z5WVcwZ2RtRnNkV1VnSUNBZ0lDQndjbTl0YVhObElHOXlJR2x0YldWa2FXRjBaU0J5WldabGNtVnVZMlVnZEc4Z2IySnpaWEoyWlZ4dUlDb2dRSEJoY21GdElHWjFiR1pwYkd4bFpDQWdablZ1WTNScGIyNGdkRzhnWW1VZ1kyRnNiR1ZrSUhkcGRHZ2dkR2hsSUdaMWJHWnBiR3hsWkNCMllXeDFaVnh1SUNvZ1FIQmhjbUZ0SUhKbGFtVmpkR1ZrSUNBZ1puVnVZM1JwYjI0Z2RHOGdZbVVnWTJGc2JHVmtJSGRwZEdnZ2RHaGxJSEpsYW1WamRHbHZiaUJsZUdObGNIUnBiMjVjYmlBcUlFQndZWEpoYlNCd2NtOW5jbVZ6YzJWa0lHWjFibU4wYVc5dUlIUnZJR0psSUdOaGJHeGxaQ0J2YmlCaGJua2djSEp2WjNKbGMzTWdibTkwYVdacFkyRjBhVzl1YzF4dUlDb2dRSEpsZEhWeWJpQndjbTl0YVhObElHWnZjaUIwYUdVZ2NtVjBkWEp1SUhaaGJIVmxJR1p5YjIwZ2RHaGxJR2x1ZG05clpXUWdZMkZzYkdKaFkydGNiaUFxTDF4dVVTNTNhR1Z1SUQwZ2QyaGxianRjYm1aMWJtTjBhVzl1SUhkb1pXNG9kbUZzZFdVc0lHWjFiR1pwYkd4bFpDd2djbVZxWldOMFpXUXNJSEJ5YjJkeVpYTnpaV1FwSUh0Y2JpQWdJQ0J5WlhSMWNtNGdVU2gyWVd4MVpTa3VkR2hsYmlobWRXeG1hV3hzWldRc0lISmxhbVZqZEdWa0xDQndjbTluY21WemMyVmtLVHRjYm4xY2JseHVVSEp2YldselpTNXdjbTkwYjNSNWNHVXVkR2hsYmxKbGMyOXNkbVVnUFNCbWRXNWpkR2x2YmlBb2RtRnNkV1VwSUh0Y2JpQWdJQ0J5WlhSMWNtNGdkR2hwY3k1MGFHVnVLR1oxYm1OMGFXOXVJQ2dwSUhzZ2NtVjBkWEp1SUhaaGJIVmxPeUI5S1R0Y2JuMDdYRzVjYmxFdWRHaGxibEpsYzI5c2RtVWdQU0JtZFc1amRHbHZiaUFvY0hKdmJXbHpaU3dnZG1Gc2RXVXBJSHRjYmlBZ0lDQnlaWFIxY200Z1VTaHdjbTl0YVhObEtTNTBhR1Z1VW1WemIyeDJaU2gyWVd4MVpTazdYRzU5TzF4dVhHNVFjbTl0YVhObExuQnliM1J2ZEhsd1pTNTBhR1Z1VW1WcVpXTjBJRDBnWm5WdVkzUnBiMjRnS0hKbFlYTnZiaWtnZTF4dUlDQWdJSEpsZEhWeWJpQjBhR2x6TG5Sb1pXNG9ablZ1WTNScGIyNGdLQ2tnZXlCMGFISnZkeUJ5WldGemIyNDdJSDBwTzF4dWZUdGNibHh1VVM1MGFHVnVVbVZxWldOMElEMGdablZ1WTNScGIyNGdLSEJ5YjIxcGMyVXNJSEpsWVhOdmJpa2dlMXh1SUNBZ0lISmxkSFZ5YmlCUktIQnliMjFwYzJVcExuUm9aVzVTWldwbFkzUW9jbVZoYzI5dUtUdGNibjA3WEc1Y2JpOHFLbHh1SUNvZ1NXWWdZVzRnYjJKcVpXTjBJR2x6SUc1dmRDQmhJSEJ5YjIxcGMyVXNJR2wwSUdseklHRnpJRndpYm1WaGNsd2lJR0Z6SUhCdmMzTnBZbXhsTGx4dUlDb2dTV1lnWVNCd2NtOXRhWE5sSUdseklISmxhbVZqZEdWa0xDQnBkQ0JwY3lCaGN5QmNJbTVsWVhKY0lpQmhjeUJ3YjNOemFXSnNaU0IwYjI4dVhHNGdLaUJKWmlCcGRPS0FtWE1nWVNCbWRXeG1hV3hzWldRZ2NISnZiV2x6WlN3Z2RHaGxJR1oxYkdacGJHeHRaVzUwSUhaaGJIVmxJR2x6SUc1bFlYSmxjaTVjYmlBcUlFbG1JR2wwNG9DWmN5QmhJR1JsWm1WeWNtVmtJSEJ5YjIxcGMyVWdZVzVrSUhSb1pTQmtaV1psY25KbFpDQm9ZWE1nWW1WbGJpQnlaWE52YkhabFpDd2dkR2hsWEc0Z0tpQnlaWE52YkhWMGFXOXVJR2x6SUZ3aWJtVmhjbVZ5WENJdVhHNGdLaUJBY0dGeVlXMGdiMkpxWldOMFhHNGdLaUJBY21WMGRYSnVjeUJ0YjNOMElISmxjMjlzZG1Wa0lDaHVaV0Z5WlhOMEtTQm1iM0p0SUc5bUlIUm9aU0J2WW1wbFkzUmNiaUFxTDF4dVhHNHZMeUJZV0ZnZ2MyaHZkV3hrSUhkbElISmxMV1J2SUhSb2FYTS9YRzVSTG01bFlYSmxjaUE5SUc1bFlYSmxjanRjYm1aMWJtTjBhVzl1SUc1bFlYSmxjaWgyWVd4MVpTa2dlMXh1SUNBZ0lHbG1JQ2hwYzFCeWIyMXBjMlVvZG1Gc2RXVXBLU0I3WEc0Z0lDQWdJQ0FnSUhaaGNpQnBibk53WldOMFpXUWdQU0IyWVd4MVpTNXBibk53WldOMEtDazdYRzRnSUNBZ0lDQWdJR2xtSUNocGJuTndaV04wWldRdWMzUmhkR1VnUFQwOUlGd2lablZzWm1sc2JHVmtYQ0lwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJSEpsZEhWeWJpQnBibk53WldOMFpXUXVkbUZzZFdVN1hHNGdJQ0FnSUNBZ0lIMWNiaUFnSUNCOVhHNGdJQ0FnY21WMGRYSnVJSFpoYkhWbE8xeHVmVnh1WEc0dktpcGNiaUFxSUVCeVpYUjFjbTV6SUhkb1pYUm9aWElnZEdobElHZHBkbVZ1SUc5aWFtVmpkQ0JwY3lCaElIQnliMjFwYzJVdVhHNGdLaUJQZEdobGNuZHBjMlVnYVhRZ2FYTWdZU0JtZFd4bWFXeHNaV1FnZG1Gc2RXVXVYRzRnS2k5Y2JsRXVhWE5RY205dGFYTmxJRDBnYVhOUWNtOXRhWE5sTzF4dVpuVnVZM1JwYjI0Z2FYTlFjbTl0YVhObEtHOWlhbVZqZENrZ2UxeHVJQ0FnSUhKbGRIVnliaUJ2WW1wbFkzUWdhVzV6ZEdGdVkyVnZaaUJRY205dGFYTmxPMXh1ZlZ4dVhHNVJMbWx6VUhKdmJXbHpaVUZzYVd0bElEMGdhWE5RY205dGFYTmxRV3hwYTJVN1hHNW1kVzVqZEdsdmJpQnBjMUJ5YjIxcGMyVkJiR2xyWlNodlltcGxZM1FwSUh0Y2JpQWdJQ0J5WlhSMWNtNGdhWE5QWW1wbFkzUW9iMkpxWldOMEtTQW1KaUIwZVhCbGIyWWdiMkpxWldOMExuUm9aVzRnUFQwOUlGd2lablZ1WTNScGIyNWNJanRjYm4xY2JseHVMeW9xWEc0Z0tpQkFjbVYwZFhKdWN5QjNhR1YwYUdWeUlIUm9aU0JuYVhabGJpQnZZbXBsWTNRZ2FYTWdZU0J3Wlc1a2FXNW5JSEJ5YjIxcGMyVXNJRzFsWVc1cGJtY2dibTkwWEc0Z0tpQm1kV3htYVd4c1pXUWdiM0lnY21WcVpXTjBaV1F1WEc0Z0tpOWNibEV1YVhOUVpXNWthVzVuSUQwZ2FYTlFaVzVrYVc1bk8xeHVablZ1WTNScGIyNGdhWE5RWlc1a2FXNW5LRzlpYW1WamRDa2dlMXh1SUNBZ0lISmxkSFZ5YmlCcGMxQnliMjFwYzJVb2IySnFaV04wS1NBbUppQnZZbXBsWTNRdWFXNXpjR1ZqZENncExuTjBZWFJsSUQwOVBTQmNJbkJsYm1ScGJtZGNJanRjYm4xY2JseHVVSEp2YldselpTNXdjbTkwYjNSNWNHVXVhWE5RWlc1a2FXNW5JRDBnWm5WdVkzUnBiMjRnS0NrZ2UxeHVJQ0FnSUhKbGRIVnliaUIwYUdsekxtbHVjM0JsWTNRb0tTNXpkR0YwWlNBOVBUMGdYQ0p3Wlc1a2FXNW5YQ0k3WEc1OU8xeHVYRzR2S2lwY2JpQXFJRUJ5WlhSMWNtNXpJSGRvWlhSb1pYSWdkR2hsSUdkcGRtVnVJRzlpYW1WamRDQnBjeUJoSUhaaGJIVmxJRzl5SUdaMWJHWnBiR3hsWkZ4dUlDb2djSEp2YldselpTNWNiaUFxTDF4dVVTNXBjMFoxYkdacGJHeGxaQ0E5SUdselJuVnNabWxzYkdWa08xeHVablZ1WTNScGIyNGdhWE5HZFd4bWFXeHNaV1FvYjJKcVpXTjBLU0I3WEc0Z0lDQWdjbVYwZFhKdUlDRnBjMUJ5YjIxcGMyVW9iMkpxWldOMEtTQjhmQ0J2WW1wbFkzUXVhVzV6Y0dWamRDZ3BMbk4wWVhSbElEMDlQU0JjSW1aMWJHWnBiR3hsWkZ3aU8xeHVmVnh1WEc1UWNtOXRhWE5sTG5CeWIzUnZkSGx3WlM1cGMwWjFiR1pwYkd4bFpDQTlJR1oxYm1OMGFXOXVJQ2dwSUh0Y2JpQWdJQ0J5WlhSMWNtNGdkR2hwY3k1cGJuTndaV04wS0NrdWMzUmhkR1VnUFQwOUlGd2lablZzWm1sc2JHVmtYQ0k3WEc1OU8xeHVYRzR2S2lwY2JpQXFJRUJ5WlhSMWNtNXpJSGRvWlhSb1pYSWdkR2hsSUdkcGRtVnVJRzlpYW1WamRDQnBjeUJoSUhKbGFtVmpkR1ZrSUhCeWIyMXBjMlV1WEc0Z0tpOWNibEV1YVhOU1pXcGxZM1JsWkNBOUlHbHpVbVZxWldOMFpXUTdYRzVtZFc1amRHbHZiaUJwYzFKbGFtVmpkR1ZrS0c5aWFtVmpkQ2tnZTF4dUlDQWdJSEpsZEhWeWJpQnBjMUJ5YjIxcGMyVW9iMkpxWldOMEtTQW1KaUJ2WW1wbFkzUXVhVzV6Y0dWamRDZ3BMbk4wWVhSbElEMDlQU0JjSW5KbGFtVmpkR1ZrWENJN1hHNTlYRzVjYmxCeWIyMXBjMlV1Y0hKdmRHOTBlWEJsTG1selVtVnFaV04wWldRZ1BTQm1kVzVqZEdsdmJpQW9LU0I3WEc0Z0lDQWdjbVYwZFhKdUlIUm9hWE11YVc1emNHVmpkQ2dwTG5OMFlYUmxJRDA5UFNCY0luSmxhbVZqZEdWa1hDSTdYRzU5TzF4dVhHNHZMeTh2SUVKRlIwbE9JRlZPU0VGT1JFeEZSQ0JTUlVwRlExUkpUMDRnVkZKQlEwdEpUa2RjYmx4dUx5OGdWR2hwY3lCd2NtOXRhWE5sSUd4cFluSmhjbmtnWTI5dWMzVnRaWE1nWlhoalpYQjBhVzl1Y3lCMGFISnZkMjRnYVc0Z2FHRnVaR3hsY25NZ2MyOGdkR2hsZVNCallXNGdZbVZjYmk4dklHaGhibVJzWldRZ1lua2dZU0J6ZFdKelpYRjFaVzUwSUhCeWIyMXBjMlV1SUNCVWFHVWdaWGhqWlhCMGFXOXVjeUJuWlhRZ1lXUmtaV1FnZEc4Z2RHaHBjeUJoY25KaGVTQjNhR1Z1WEc0dkx5QjBhR1Y1SUdGeVpTQmpjbVZoZEdWa0xDQmhibVFnY21WdGIzWmxaQ0IzYUdWdUlIUm9aWGtnWVhKbElHaGhibVJzWldRdUlDQk9iM1JsSUhSb1lYUWdhVzRnUlZNMklHOXlYRzR2THlCemFHbHRiV1ZrSUdWdWRtbHliMjV0Wlc1MGN5d2dkR2hwY3lCM2IzVnNaQ0J1WVhSMWNtRnNiSGtnWW1VZ1lTQmdVMlYwWUM1Y2JuWmhjaUIxYm1oaGJtUnNaV1JTWldGemIyNXpJRDBnVzEwN1hHNTJZWElnZFc1b1lXNWtiR1ZrVW1WcVpXTjBhVzl1Y3lBOUlGdGRPMXh1ZG1GeUlISmxjRzl5ZEdWa1ZXNW9ZVzVrYkdWa1VtVnFaV04wYVc5dWN5QTlJRnRkTzF4dWRtRnlJSFJ5WVdOclZXNW9ZVzVrYkdWa1VtVnFaV04wYVc5dWN5QTlJSFJ5ZFdVN1hHNWNibVoxYm1OMGFXOXVJSEpsYzJWMFZXNW9ZVzVrYkdWa1VtVnFaV04wYVc5dWN5Z3BJSHRjYmlBZ0lDQjFibWhoYm1Sc1pXUlNaV0Z6YjI1ekxteGxibWQwYUNBOUlEQTdYRzRnSUNBZ2RXNW9ZVzVrYkdWa1VtVnFaV04wYVc5dWN5NXNaVzVuZEdnZ1BTQXdPMXh1WEc0Z0lDQWdhV1lnS0NGMGNtRmphMVZ1YUdGdVpHeGxaRkpsYW1WamRHbHZibk1wSUh0Y2JpQWdJQ0FnSUNBZ2RISmhZMnRWYm1oaGJtUnNaV1JTWldwbFkzUnBiMjV6SUQwZ2RISjFaVHRjYmlBZ0lDQjlYRzU5WEc1Y2JtWjFibU4wYVc5dUlIUnlZV05yVW1WcVpXTjBhVzl1S0hCeWIyMXBjMlVzSUhKbFlYTnZiaWtnZTF4dUlDQWdJR2xtSUNnaGRISmhZMnRWYm1oaGJtUnNaV1JTWldwbFkzUnBiMjV6S1NCN1hHNGdJQ0FnSUNBZ0lISmxkSFZ5Ymp0Y2JpQWdJQ0I5WEc0Z0lDQWdhV1lnS0hSNWNHVnZaaUJ3Y205alpYTnpJRDA5UFNCY0ltOWlhbVZqZEZ3aUlDWW1JSFI1Y0dWdlppQndjbTlqWlhOekxtVnRhWFFnUFQwOUlGd2lablZ1WTNScGIyNWNJaWtnZTF4dUlDQWdJQ0FnSUNCUkxtNWxlSFJVYVdOckxuSjFia0ZtZEdWeUtHWjFibU4wYVc5dUlDZ3BJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lHbG1JQ2hoY25KaGVWOXBibVJsZUU5bUtIVnVhR0Z1Wkd4bFpGSmxhbVZqZEdsdmJuTXNJSEJ5YjIxcGMyVXBJQ0U5UFNBdE1Ta2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJSEJ5YjJObGMzTXVaVzFwZENoY0luVnVhR0Z1Wkd4bFpGSmxhbVZqZEdsdmJsd2lMQ0J5WldGemIyNHNJSEJ5YjIxcGMyVXBPMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJSEpsY0c5eWRHVmtWVzVvWVc1a2JHVmtVbVZxWldOMGFXOXVjeTV3ZFhOb0tIQnliMjFwYzJVcE8xeHVJQ0FnSUNBZ0lDQWdJQ0FnZlZ4dUlDQWdJQ0FnSUNCOUtUdGNiaUFnSUNCOVhHNWNiaUFnSUNCMWJtaGhibVJzWldSU1pXcGxZM1JwYjI1ekxuQjFjMmdvY0hKdmJXbHpaU2s3WEc0Z0lDQWdhV1lnS0hKbFlYTnZiaUFtSmlCMGVYQmxiMllnY21WaGMyOXVMbk4wWVdOcklDRTlQU0JjSW5WdVpHVm1hVzVsWkZ3aUtTQjdYRzRnSUNBZ0lDQWdJSFZ1YUdGdVpHeGxaRkpsWVhOdmJuTXVjSFZ6YUNoeVpXRnpiMjR1YzNSaFkyc3BPMXh1SUNBZ0lIMGdaV3h6WlNCN1hHNGdJQ0FnSUNBZ0lIVnVhR0Z1Wkd4bFpGSmxZWE52Ym5NdWNIVnphQ2hjSWlodWJ5QnpkR0ZqYXlrZ1hDSWdLeUJ5WldGemIyNHBPMXh1SUNBZ0lIMWNibjFjYmx4dVpuVnVZM1JwYjI0Z2RXNTBjbUZqYTFKbGFtVmpkR2x2Ymlod2NtOXRhWE5sS1NCN1hHNGdJQ0FnYVdZZ0tDRjBjbUZqYTFWdWFHRnVaR3hsWkZKbGFtVmpkR2x2Ym5NcElIdGNiaUFnSUNBZ0lDQWdjbVYwZFhKdU8xeHVJQ0FnSUgxY2JseHVJQ0FnSUhaaGNpQmhkQ0E5SUdGeWNtRjVYMmx1WkdWNFQyWW9kVzVvWVc1a2JHVmtVbVZxWldOMGFXOXVjeXdnY0hKdmJXbHpaU2s3WEc0Z0lDQWdhV1lnS0dGMElDRTlQU0F0TVNrZ2UxeHVJQ0FnSUNBZ0lDQnBaaUFvZEhsd1pXOW1JSEJ5YjJObGMzTWdQVDA5SUZ3aWIySnFaV04wWENJZ0ppWWdkSGx3Wlc5bUlIQnliMk5sYzNNdVpXMXBkQ0E5UFQwZ1hDSm1kVzVqZEdsdmJsd2lLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQlJMbTVsZUhSVWFXTnJMbkoxYmtGbWRHVnlLR1oxYm1OMGFXOXVJQ2dwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCMllYSWdZWFJTWlhCdmNuUWdQU0JoY25KaGVWOXBibVJsZUU5bUtISmxjRzl5ZEdWa1ZXNW9ZVzVrYkdWa1VtVnFaV04wYVc5dWN5d2djSEp2YldselpTazdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdhV1lnS0dGMFVtVndiM0owSUNFOVBTQXRNU2tnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQndjbTlqWlhOekxtVnRhWFFvWENKeVpXcGxZM1JwYjI1SVlXNWtiR1ZrWENJc0lIVnVhR0Z1Wkd4bFpGSmxZWE52Ym5OYllYUmRMQ0J3Y205dGFYTmxLVHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ2NtVndiM0owWldSVmJtaGhibVJzWldSU1pXcGxZM1JwYjI1ekxuTndiR2xqWlNoaGRGSmxjRzl5ZEN3Z01TazdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0FnSUNBZ2ZTazdYRzRnSUNBZ0lDQWdJSDFjYmlBZ0lDQWdJQ0FnZFc1b1lXNWtiR1ZrVW1WcVpXTjBhVzl1Y3k1emNHeHBZMlVvWVhRc0lERXBPMXh1SUNBZ0lDQWdJQ0IxYm1oaGJtUnNaV1JTWldGemIyNXpMbk53YkdsalpTaGhkQ3dnTVNrN1hHNGdJQ0FnZlZ4dWZWeHVYRzVSTG5KbGMyVjBWVzVvWVc1a2JHVmtVbVZxWldOMGFXOXVjeUE5SUhKbGMyVjBWVzVvWVc1a2JHVmtVbVZxWldOMGFXOXVjenRjYmx4dVVTNW5aWFJWYm1oaGJtUnNaV1JTWldGemIyNXpJRDBnWm5WdVkzUnBiMjRnS0NrZ2UxeHVJQ0FnSUM4dklFMWhhMlVnWVNCamIzQjVJSE52SUhSb1lYUWdZMjl1YzNWdFpYSnpJR05oYmlkMElHbHVkR1Z5Wm1WeVpTQjNhWFJvSUc5MWNpQnBiblJsY201aGJDQnpkR0YwWlM1Y2JpQWdJQ0J5WlhSMWNtNGdkVzVvWVc1a2JHVmtVbVZoYzI5dWN5NXpiR2xqWlNncE8xeHVmVHRjYmx4dVVTNXpkRzl3Vlc1b1lXNWtiR1ZrVW1WcVpXTjBhVzl1VkhKaFkydHBibWNnUFNCbWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ2NtVnpaWFJWYm1oaGJtUnNaV1JTWldwbFkzUnBiMjV6S0NrN1hHNGdJQ0FnZEhKaFkydFZibWhoYm1Sc1pXUlNaV3BsWTNScGIyNXpJRDBnWm1Gc2MyVTdYRzU5TzF4dVhHNXlaWE5sZEZWdWFHRnVaR3hsWkZKbGFtVmpkR2x2Ym5Nb0tUdGNibHh1THk4dkx5QkZUa1FnVlU1SVFVNUVURVZFSUZKRlNrVkRWRWxQVGlCVVVrRkRTMGxPUjF4dVhHNHZLaXBjYmlBcUlFTnZibk4wY25WamRITWdZU0J5WldwbFkzUmxaQ0J3Y205dGFYTmxMbHh1SUNvZ1FIQmhjbUZ0SUhKbFlYTnZiaUIyWVd4MVpTQmtaWE5qY21saWFXNW5JSFJvWlNCbVlXbHNkWEpsWEc0Z0tpOWNibEV1Y21WcVpXTjBJRDBnY21WcVpXTjBPMXh1Wm5WdVkzUnBiMjRnY21WcVpXTjBLSEpsWVhOdmJpa2dlMXh1SUNBZ0lIWmhjaUJ5WldwbFkzUnBiMjRnUFNCUWNtOXRhWE5sS0h0Y2JpQWdJQ0FnSUNBZ1hDSjNhR1Z1WENJNklHWjFibU4wYVc5dUlDaHlaV3BsWTNSbFpDa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ0x5OGdibTkwWlNCMGFHRjBJSFJvWlNCbGNuSnZjaUJvWVhNZ1ltVmxiaUJvWVc1a2JHVmtYRzRnSUNBZ0lDQWdJQ0FnSUNCcFppQW9jbVZxWldOMFpXUXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0IxYm5SeVlXTnJVbVZxWldOMGFXOXVLSFJvYVhNcE8xeHVJQ0FnSUNBZ0lDQWdJQ0FnZlZ4dUlDQWdJQ0FnSUNBZ0lDQWdjbVYwZFhKdUlISmxhbVZqZEdWa0lEOGdjbVZxWldOMFpXUW9jbVZoYzI5dUtTQTZJSFJvYVhNN1hHNGdJQ0FnSUNBZ0lIMWNiaUFnSUNCOUxDQm1kVzVqZEdsdmJpQm1ZV3hzWW1GamF5Z3BJSHRjYmlBZ0lDQWdJQ0FnY21WMGRYSnVJSFJvYVhNN1hHNGdJQ0FnZlN3Z1puVnVZM1JwYjI0Z2FXNXpjR1ZqZENncElIdGNiaUFnSUNBZ0lDQWdjbVYwZFhKdUlIc2djM1JoZEdVNklGd2ljbVZxWldOMFpXUmNJaXdnY21WaGMyOXVPaUJ5WldGemIyNGdmVHRjYmlBZ0lDQjlLVHRjYmx4dUlDQWdJQzh2SUU1dmRHVWdkR2hoZENCMGFHVWdjbVZoYzI5dUlHaGhjeUJ1YjNRZ1ltVmxiaUJvWVc1a2JHVmtMbHh1SUNBZ0lIUnlZV05yVW1WcVpXTjBhVzl1S0hKbGFtVmpkR2x2Yml3Z2NtVmhjMjl1S1R0Y2JseHVJQ0FnSUhKbGRIVnliaUJ5WldwbFkzUnBiMjQ3WEc1OVhHNWNiaThxS2x4dUlDb2dRMjl1YzNSeWRXTjBjeUJoSUdaMWJHWnBiR3hsWkNCd2NtOXRhWE5sSUdadmNpQmhiaUJwYlcxbFpHbGhkR1VnY21WbVpYSmxibU5sTGx4dUlDb2dRSEJoY21GdElIWmhiSFZsSUdsdGJXVmthV0YwWlNCeVpXWmxjbVZ1WTJWY2JpQXFMMXh1VVM1bWRXeG1hV3hzSUQwZ1puVnNabWxzYkR0Y2JtWjFibU4wYVc5dUlHWjFiR1pwYkd3b2RtRnNkV1VwSUh0Y2JpQWdJQ0J5WlhSMWNtNGdVSEp2YldselpTaDdYRzRnSUNBZ0lDQWdJRndpZDJobGJsd2lPaUJtZFc1amRHbHZiaUFvS1NCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0J5WlhSMWNtNGdkbUZzZFdVN1hHNGdJQ0FnSUNBZ0lIMHNYRzRnSUNBZ0lDQWdJRndpWjJWMFhDSTZJR1oxYm1OMGFXOXVJQ2h1WVcxbEtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCeVpYUjFjbTRnZG1Gc2RXVmJibUZ0WlYwN1hHNGdJQ0FnSUNBZ0lIMHNYRzRnSUNBZ0lDQWdJRndpYzJWMFhDSTZJR1oxYm1OMGFXOXVJQ2h1WVcxbExDQnlhSE1wSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJSFpoYkhWbFcyNWhiV1ZkSUQwZ2NtaHpPMXh1SUNBZ0lDQWdJQ0I5TEZ4dUlDQWdJQ0FnSUNCY0ltUmxiR1YwWlZ3aU9pQm1kVzVqZEdsdmJpQW9ibUZ0WlNrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnWkdWc1pYUmxJSFpoYkhWbFcyNWhiV1ZkTzF4dUlDQWdJQ0FnSUNCOUxGeHVJQ0FnSUNBZ0lDQmNJbkJ2YzNSY0lqb2dablZ1WTNScGIyNGdLRzVoYldVc0lHRnlaM01wSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJQzh2SUUxaGNtc2dUV2xzYkdWeUlIQnliM0J2YzJWeklIUm9ZWFFnY0c5emRDQjNhWFJvSUc1dklHNWhiV1VnYzJodmRXeGtJR0Z3Y0d4NUlHRmNiaUFnSUNBZ0lDQWdJQ0FnSUM4dklIQnliMjFwYzJWa0lHWjFibU4wYVc5dUxseHVJQ0FnSUNBZ0lDQWdJQ0FnYVdZZ0tHNWhiV1VnUFQwOUlHNTFiR3dnZkh3Z2JtRnRaU0E5UFQwZ2RtOXBaQ0F3S1NCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ2NtVjBkWEp1SUhaaGJIVmxMbUZ3Y0d4NUtIWnZhV1FnTUN3Z1lYSm5jeWs3WEc0Z0lDQWdJQ0FnSUNBZ0lDQjlJR1ZzYzJVZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lISmxkSFZ5YmlCMllXeDFaVnR1WVcxbFhTNWhjSEJzZVNoMllXeDFaU3dnWVhKbmN5azdYRzRnSUNBZ0lDQWdJQ0FnSUNCOVhHNGdJQ0FnSUNBZ0lIMHNYRzRnSUNBZ0lDQWdJRndpWVhCd2JIbGNJam9nWm5WdVkzUnBiMjRnS0hSb2FYTndMQ0JoY21kektTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCeVpYUjFjbTRnZG1Gc2RXVXVZWEJ3Ykhrb2RHaHBjM0FzSUdGeVozTXBPMXh1SUNBZ0lDQWdJQ0I5TEZ4dUlDQWdJQ0FnSUNCY0ltdGxlWE5jSWpvZ1puVnVZM1JwYjI0Z0tDa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2NtVjBkWEp1SUc5aWFtVmpkRjlyWlhsektIWmhiSFZsS1R0Y2JpQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUgwc0lIWnZhV1FnTUN3Z1puVnVZM1JwYjI0Z2FXNXpjR1ZqZENncElIdGNiaUFnSUNBZ0lDQWdjbVYwZFhKdUlIc2djM1JoZEdVNklGd2lablZzWm1sc2JHVmtYQ0lzSUhaaGJIVmxPaUIyWVd4MVpTQjlPMXh1SUNBZ0lIMHBPMXh1ZlZ4dVhHNHZLaXBjYmlBcUlFTnZiblpsY25SeklIUm9aVzVoWW14bGN5QjBieUJSSUhCeWIyMXBjMlZ6TGx4dUlDb2dRSEJoY21GdElIQnliMjFwYzJVZ2RHaGxibUZpYkdVZ2NISnZiV2x6WlZ4dUlDb2dRSEpsZEhWeWJuTWdZU0JSSUhCeWIyMXBjMlZjYmlBcUwxeHVablZ1WTNScGIyNGdZMjlsY21ObEtIQnliMjFwYzJVcElIdGNiaUFnSUNCMllYSWdaR1ZtWlhKeVpXUWdQU0JrWldabGNpZ3BPMXh1SUNBZ0lGRXVibVY0ZEZScFkyc29ablZ1WTNScGIyNGdLQ2tnZTF4dUlDQWdJQ0FnSUNCMGNua2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2NISnZiV2x6WlM1MGFHVnVLR1JsWm1WeWNtVmtMbkpsYzI5c2RtVXNJR1JsWm1WeWNtVmtMbkpsYW1WamRDd2daR1ZtWlhKeVpXUXVibTkwYVdaNUtUdGNiaUFnSUNBZ0lDQWdmU0JqWVhSamFDQW9aWGhqWlhCMGFXOXVLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQmtaV1psY25KbFpDNXlaV3BsWTNRb1pYaGpaWEIwYVc5dUtUdGNiaUFnSUNBZ0lDQWdmVnh1SUNBZ0lIMHBPMXh1SUNBZ0lISmxkSFZ5YmlCa1pXWmxjbkpsWkM1d2NtOXRhWE5sTzF4dWZWeHVYRzR2S2lwY2JpQXFJRUZ1Ym05MFlYUmxjeUJoYmlCdlltcGxZM1FnYzNWamFDQjBhR0YwSUdsMElIZHBiR3dnYm1WMlpYSWdZbVZjYmlBcUlIUnlZVzV6Wm1WeWNtVmtJR0YzWVhrZ1puSnZiU0IwYUdseklIQnliMk5sYzNNZ2IzWmxjaUJoYm5rZ2NISnZiV2x6WlZ4dUlDb2dZMjl0YlhWdWFXTmhkR2x2YmlCamFHRnVibVZzTGx4dUlDb2dRSEJoY21GdElHOWlhbVZqZEZ4dUlDb2dRSEpsZEhWeWJuTWdjSEp2YldselpTQmhJSGR5WVhCd2FXNW5JRzltSUhSb1lYUWdiMkpxWldOMElIUm9ZWFJjYmlBcUlHRmtaR2wwYVc5dVlXeHNlU0J5WlhOd2IyNWtjeUIwYnlCMGFHVWdYQ0pwYzBSbFpsd2lJRzFsYzNOaFoyVmNiaUFxSUhkcGRHaHZkWFFnWVNCeVpXcGxZM1JwYjI0dVhHNGdLaTljYmxFdWJXRnpkR1Z5SUQwZ2JXRnpkR1Z5TzF4dVpuVnVZM1JwYjI0Z2JXRnpkR1Z5S0c5aWFtVmpkQ2tnZTF4dUlDQWdJSEpsZEhWeWJpQlFjbTl0YVhObEtIdGNiaUFnSUNBZ0lDQWdYQ0pwYzBSbFpsd2lPaUJtZFc1amRHbHZiaUFvS1NCN2ZWeHVJQ0FnSUgwc0lHWjFibU4wYVc5dUlHWmhiR3hpWVdOcktHOXdMQ0JoY21kektTQjdYRzRnSUNBZ0lDQWdJSEpsZEhWeWJpQmthWE53WVhSamFDaHZZbXBsWTNRc0lHOXdMQ0JoY21kektUdGNiaUFnSUNCOUxDQm1kVzVqZEdsdmJpQW9LU0I3WEc0Z0lDQWdJQ0FnSUhKbGRIVnliaUJSS0c5aWFtVmpkQ2t1YVc1emNHVmpkQ2dwTzF4dUlDQWdJSDBwTzF4dWZWeHVYRzR2S2lwY2JpQXFJRk53Y21WaFpITWdkR2hsSUhaaGJIVmxjeUJ2WmlCaElIQnliMjFwYzJWa0lHRnljbUY1SUc5bUlHRnlaM1Z0Wlc1MGN5QnBiblJ2SUhSb1pWeHVJQ29nWm5Wc1ptbHNiRzFsYm5RZ1kyRnNiR0poWTJzdVhHNGdLaUJBY0dGeVlXMGdablZzWm1sc2JHVmtJR05oYkd4aVlXTnJJSFJvWVhRZ2NtVmpaV2wyWlhNZ2RtRnlhV0ZrYVdNZ1lYSm5kVzFsYm5SeklHWnliMjBnZEdobFhHNGdLaUJ3Y205dGFYTmxaQ0JoY25KaGVWeHVJQ29nUUhCaGNtRnRJSEpsYW1WamRHVmtJR05oYkd4aVlXTnJJSFJvWVhRZ2NtVmpaV2wyWlhNZ2RHaGxJR1Y0WTJWd2RHbHZiaUJwWmlCMGFHVWdjSEp2YldselpWeHVJQ29nYVhNZ2NtVnFaV04wWldRdVhHNGdLaUJBY21WMGRYSnVjeUJoSUhCeWIyMXBjMlVnWm05eUlIUm9aU0J5WlhSMWNtNGdkbUZzZFdVZ2IzSWdkR2h5YjNkdUlHVjRZMlZ3ZEdsdmJpQnZabHh1SUNvZ1pXbDBhR1Z5SUdOaGJHeGlZV05yTGx4dUlDb3ZYRzVSTG5Od2NtVmhaQ0E5SUhOd2NtVmhaRHRjYm1aMWJtTjBhVzl1SUhOd2NtVmhaQ2gyWVd4MVpTd2dablZzWm1sc2JHVmtMQ0J5WldwbFkzUmxaQ2tnZTF4dUlDQWdJSEpsZEhWeWJpQlJLSFpoYkhWbEtTNXpjSEpsWVdRb1puVnNabWxzYkdWa0xDQnlaV3BsWTNSbFpDazdYRzU5WEc1Y2JsQnliMjFwYzJVdWNISnZkRzkwZVhCbExuTndjbVZoWkNBOUlHWjFibU4wYVc5dUlDaG1kV3htYVd4c1pXUXNJSEpsYW1WamRHVmtLU0I3WEc0Z0lDQWdjbVYwZFhKdUlIUm9hWE11WVd4c0tDa3VkR2hsYmlobWRXNWpkR2x2YmlBb1lYSnlZWGtwSUh0Y2JpQWdJQ0FnSUNBZ2NtVjBkWEp1SUdaMWJHWnBiR3hsWkM1aGNIQnNlU2gyYjJsa0lEQXNJR0Z5Y21GNUtUdGNiaUFnSUNCOUxDQnlaV3BsWTNSbFpDazdYRzU5TzF4dVhHNHZLaXBjYmlBcUlGUm9aU0JoYzNsdVl5Qm1kVzVqZEdsdmJpQnBjeUJoSUdSbFkyOXlZWFJ2Y2lCbWIzSWdaMlZ1WlhKaGRHOXlJR1oxYm1OMGFXOXVjeXdnZEhWeWJtbHVaMXh1SUNvZ2RHaGxiU0JwYm5SdklHRnplVzVqYUhKdmJtOTFjeUJuWlc1bGNtRjBiM0p6TGlBZ1FXeDBhRzkxWjJnZ1oyVnVaWEpoZEc5eWN5QmhjbVVnYjI1c2VTQndZWEowWEc0Z0tpQnZaaUIwYUdVZ2JtVjNaWE4wSUVWRFRVRlRZM0pwY0hRZ05pQmtjbUZtZEhNc0lIUm9hWE1nWTI5a1pTQmtiMlZ6SUc1dmRDQmpZWFZ6WlNCemVXNTBZWGhjYmlBcUlHVnljbTl5Y3lCcGJpQnZiR1JsY2lCbGJtZHBibVZ6TGlBZ1ZHaHBjeUJqYjJSbElITm9iM1ZzWkNCamIyNTBhVzUxWlNCMGJ5QjNiM0pySUdGdVpDQjNhV3hzWEc0Z0tpQnBiaUJtWVdOMElHbHRjSEp2ZG1VZ2IzWmxjaUIwYVcxbElHRnpJSFJvWlNCc1lXNW5kV0ZuWlNCcGJYQnliM1psY3k1Y2JpQXFYRzRnS2lCRlV6WWdaMlZ1WlhKaGRHOXljeUJoY21VZ1kzVnljbVZ1ZEd4NUlIQmhjblFnYjJZZ1ZqZ2dkbVZ5YzJsdmJpQXpMakU1SUhkcGRHZ2dkR2hsWEc0Z0tpQXRMV2hoY20xdmJua3RaMlZ1WlhKaGRHOXljeUJ5ZFc1MGFXMWxJR1pzWVdjZ1pXNWhZbXhsWkM0Z0lGTndhV1JsY2sxdmJtdGxlU0JvWVhNZ2FHRmtJSFJvWlcxY2JpQXFJR1p2Y2lCc2IyNW5aWElzSUdKMWRDQjFibVJsY2lCaGJpQnZiR1JsY2lCUWVYUm9iMjR0YVc1emNHbHlaV1FnWm05eWJTNGdJRlJvYVhNZ1puVnVZM1JwYjI1Y2JpQXFJSGR2Y210eklHOXVJR0p2ZEdnZ2EybHVaSE1nYjJZZ1oyVnVaWEpoZEc5eWN5NWNiaUFxWEc0Z0tpQkVaV052Y21GMFpYTWdZU0JuWlc1bGNtRjBiM0lnWm5WdVkzUnBiMjRnYzNWamFDQjBhR0YwT2x4dUlDb2dJQzBnYVhRZ2JXRjVJSGxwWld4a0lIQnliMjFwYzJWelhHNGdLaUFnTFNCbGVHVmpkWFJwYjI0Z2QybHNiQ0JqYjI1MGFXNTFaU0IzYUdWdUlIUm9ZWFFnY0hKdmJXbHpaU0JwY3lCbWRXeG1hV3hzWldSY2JpQXFJQ0F0SUhSb1pTQjJZV3gxWlNCdlppQjBhR1VnZVdsbGJHUWdaWGh3Y21WemMybHZiaUIzYVd4c0lHSmxJSFJvWlNCbWRXeG1hV3hzWldRZ2RtRnNkV1ZjYmlBcUlDQXRJR2wwSUhKbGRIVnlibk1nWVNCd2NtOXRhWE5sSUdadmNpQjBhR1VnY21WMGRYSnVJSFpoYkhWbElDaDNhR1Z1SUhSb1pTQm5aVzVsY21GMGIzSmNiaUFxSUNBZ0lITjBiM0J6SUdsMFpYSmhkR2x1WnlsY2JpQXFJQ0F0SUhSb1pTQmtaV052Y21GMFpXUWdablZ1WTNScGIyNGdjbVYwZFhKdWN5QmhJSEJ5YjIxcGMyVWdabTl5SUhSb1pTQnlaWFIxY200Z2RtRnNkV1ZjYmlBcUlDQWdJRzltSUhSb1pTQm5aVzVsY21GMGIzSWdiM0lnZEdobElHWnBjbk4wSUhKbGFtVmpkR1ZrSUhCeWIyMXBjMlVnWVcxdmJtY2dkR2h2YzJWY2JpQXFJQ0FnSUhscFpXeGtaV1F1WEc0Z0tpQWdMU0JwWmlCaGJpQmxjbkp2Y2lCcGN5QjBhSEp2ZDI0Z2FXNGdkR2hsSUdkbGJtVnlZWFJ2Y2l3Z2FYUWdjSEp2Y0dGbllYUmxjeUIwYUhKdmRXZG9YRzRnS2lBZ0lDQmxkbVZ5ZVNCbWIyeHNiM2RwYm1jZ2VXbGxiR1FnZFc1MGFXd2dhWFFnYVhNZ1kyRjFaMmgwTENCdmNpQjFiblJwYkNCcGRDQmxjMk5oY0dWelhHNGdLaUFnSUNCMGFHVWdaMlZ1WlhKaGRHOXlJR1oxYm1OMGFXOXVJR0ZzZEc5blpYUm9aWElzSUdGdVpDQnBjeUIwY21GdWMyeGhkR1ZrSUdsdWRHOGdZVnh1SUNvZ0lDQWdjbVZxWldOMGFXOXVJR1p2Y2lCMGFHVWdjSEp2YldselpTQnlaWFIxY201bFpDQmllU0IwYUdVZ1pHVmpiM0poZEdWa0lHZGxibVZ5WVhSdmNpNWNiaUFxTDF4dVVTNWhjM2x1WXlBOUlHRnplVzVqTzF4dVpuVnVZM1JwYjI0Z1lYTjVibU1vYldGclpVZGxibVZ5WVhSdmNpa2dlMXh1SUNBZ0lISmxkSFZ5YmlCbWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ0lDQWdJQzh2SUhkb1pXNGdkbVZ5WWlCcGN5QmNJbk5sYm1SY0lpd2dZWEpuSUdseklHRWdkbUZzZFdWY2JpQWdJQ0FnSUNBZ0x5OGdkMmhsYmlCMlpYSmlJR2x6SUZ3aWRHaHliM2RjSWl3Z1lYSm5JR2x6SUdGdUlHVjRZMlZ3ZEdsdmJseHVJQ0FnSUNBZ0lDQm1kVzVqZEdsdmJpQmpiMjUwYVc1MVpYSW9kbVZ5WWl3Z1lYSm5LU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQjJZWElnY21WemRXeDBPMXh1WEc0Z0lDQWdJQ0FnSUNBZ0lDQXZMeUJWYm5ScGJDQldPQ0F6TGpFNUlDOGdRMmh5YjIxcGRXMGdNamtnYVhNZ2NtVnNaV0Z6WldRc0lGTndhV1JsY2sxdmJtdGxlU0JwY3lCMGFHVWdiMjVzZVZ4dUlDQWdJQ0FnSUNBZ0lDQWdMeThnWlc1bmFXNWxJSFJvWVhRZ2FHRnpJR0VnWkdWd2JHOTVaV1FnWW1GelpTQnZaaUJpY205M2MyVnljeUIwYUdGMElITjFjSEJ2Y25RZ1oyVnVaWEpoZEc5eWN5NWNiaUFnSUNBZ0lDQWdJQ0FnSUM4dklFaHZkMlYyWlhJc0lGTk5KM01nWjJWdVpYSmhkRzl5Y3lCMWMyVWdkR2hsSUZCNWRHaHZiaTFwYm5Od2FYSmxaQ0J6WlcxaGJuUnBZM01nYjJaY2JpQWdJQ0FnSUNBZ0lDQWdJQzh2SUc5MWRHUmhkR1ZrSUVWVE5pQmtjbUZtZEhNdUlDQlhaU0IzYjNWc1pDQnNhV3RsSUhSdklITjFjSEJ2Y25RZ1JWTTJMQ0JpZFhRZ2QyVW5aQ0JoYkhOdlhHNGdJQ0FnSUNBZ0lDQWdJQ0F2THlCc2FXdGxJSFJ2SUcxaGEyVWdhWFFnY0c5emMybGliR1VnZEc4Z2RYTmxJR2RsYm1WeVlYUnZjbk1nYVc0Z1pHVndiRzk1WldRZ1luSnZkM05sY25Nc0lITnZYRzRnSUNBZ0lDQWdJQ0FnSUNBdkx5QjNaU0JoYkhOdklITjFjSEJ2Y25RZ1VIbDBhRzl1TFhOMGVXeGxJR2RsYm1WeVlYUnZjbk11SUNCQmRDQnpiMjFsSUhCdmFXNTBJSGRsSUdOaGJpQnlaVzF2ZG1WY2JpQWdJQ0FnSUNBZ0lDQWdJQzh2SUhSb2FYTWdZbXh2WTJzdVhHNWNiaUFnSUNBZ0lDQWdJQ0FnSUdsbUlDaDBlWEJsYjJZZ1UzUnZjRWwwWlhKaGRHbHZiaUE5UFQwZ1hDSjFibVJsWm1sdVpXUmNJaWtnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUM4dklFVlROaUJIWlc1bGNtRjBiM0p6WEc0Z0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnZEhKNUlIdGNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnY21WemRXeDBJRDBnWjJWdVpYSmhkRzl5VzNabGNtSmRLR0Z5WnlrN1hHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ2ZTQmpZWFJqYUNBb1pYaGpaWEIwYVc5dUtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUhKbGRIVnliaUJ5WldwbFkzUW9aWGhqWlhCMGFXOXVLVHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0I5WEc0Z0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnYVdZZ0tISmxjM1ZzZEM1a2IyNWxLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lISmxkSFZ5YmlCUktISmxjM1ZzZEM1MllXeDFaU2s3WEc0Z0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnZlNCbGJITmxJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ2NtVjBkWEp1SUhkb1pXNG9jbVZ6ZFd4MExuWmhiSFZsTENCallXeHNZbUZqYXl3Z1pYSnlZbUZqYXlrN1hHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUNBZ0lDQWdJQ0FnZlNCbGJITmxJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0F2THlCVGNHbGtaWEpOYjI1clpYa2dSMlZ1WlhKaGRHOXljMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQzh2SUVaSldFMUZPaUJTWlcxdmRtVWdkR2hwY3lCallYTmxJSGRvWlc0Z1UwMGdaRzlsY3lCRlV6WWdaMlZ1WlhKaGRHOXljeTVjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0IwY25rZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0J5WlhOMWJIUWdQU0JuWlc1bGNtRjBiM0piZG1WeVlsMG9ZWEpuS1R0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCOUlHTmhkR05vSUNobGVHTmxjSFJwYjI0cElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnYVdZZ0tHbHpVM1J2Y0VsMFpYSmhkR2x2YmlobGVHTmxjSFJwYjI0cEtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQnlaWFIxY200Z1VTaGxlR05sY0hScGIyNHVkbUZzZFdVcE8xeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0I5SUdWc2MyVWdlMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdjbVYwZFhKdUlISmxhbVZqZENobGVHTmxjSFJwYjI0cE8xeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0I5WEc0Z0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnZlZ4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUhKbGRIVnliaUIzYUdWdUtISmxjM1ZzZEN3Z1kyRnNiR0poWTJzc0lHVnljbUpoWTJzcE8xeHVJQ0FnSUNBZ0lDQWdJQ0FnZlZ4dUlDQWdJQ0FnSUNCOVhHNGdJQ0FnSUNBZ0lIWmhjaUJuWlc1bGNtRjBiM0lnUFNCdFlXdGxSMlZ1WlhKaGRHOXlMbUZ3Y0d4NUtIUm9hWE1zSUdGeVozVnRaVzUwY3lrN1hHNGdJQ0FnSUNBZ0lIWmhjaUJqWVd4c1ltRmpheUE5SUdOdmJuUnBiblZsY2k1aWFXNWtLR052Ym5ScGJuVmxjaXdnWENKdVpYaDBYQ0lwTzF4dUlDQWdJQ0FnSUNCMllYSWdaWEp5WW1GamF5QTlJR052Ym5ScGJuVmxjaTVpYVc1a0tHTnZiblJwYm5WbGNpd2dYQ0owYUhKdmQxd2lLVHRjYmlBZ0lDQWdJQ0FnY21WMGRYSnVJR05oYkd4aVlXTnJLQ2s3WEc0Z0lDQWdmVHRjYm4xY2JseHVMeW9xWEc0Z0tpQlVhR1VnYzNCaGQyNGdablZ1WTNScGIyNGdhWE1nWVNCemJXRnNiQ0IzY21Gd2NHVnlJR0Z5YjNWdVpDQmhjM2x1WXlCMGFHRjBJR2x0YldWa2FXRjBaV3g1WEc0Z0tpQmpZV3hzY3lCMGFHVWdaMlZ1WlhKaGRHOXlJR0Z1WkNCaGJITnZJR1Z1WkhNZ2RHaGxJSEJ5YjIxcGMyVWdZMmhoYVc0c0lITnZJSFJvWVhRZ1lXNTVYRzRnS2lCMWJtaGhibVJzWldRZ1pYSnliM0p6SUdGeVpTQjBhSEp2ZDI0Z2FXNXpkR1ZoWkNCdlppQm1iM0ozWVhKa1pXUWdkRzhnZEdobElHVnljbTl5WEc0Z0tpQm9ZVzVrYkdWeUxpQlVhR2x6SUdseklIVnpaV1oxYkNCaVpXTmhkWE5sSUdsMEozTWdaWGgwY21WdFpXeDVJR052YlcxdmJpQjBieUJ5ZFc1Y2JpQXFJR2RsYm1WeVlYUnZjbk1nWVhRZ2RHaGxJSFJ2Y0Mxc1pYWmxiQ0IwYnlCM2IzSnJJSGRwZEdnZ2JHbGljbUZ5YVdWekxseHVJQ292WEc1UkxuTndZWGR1SUQwZ2MzQmhkMjQ3WEc1bWRXNWpkR2x2YmlCemNHRjNiaWh0WVd0bFIyVnVaWEpoZEc5eUtTQjdYRzRnSUNBZ1VTNWtiMjVsS0ZFdVlYTjVibU1vYldGclpVZGxibVZ5WVhSdmNpa29LU2s3WEc1OVhHNWNiaTh2SUVaSldFMUZPaUJTWlcxdmRtVWdkR2hwY3lCcGJuUmxjbVpoWTJVZ2IyNWpaU0JGVXpZZ1oyVnVaWEpoZEc5eWN5QmhjbVVnYVc0Z1UzQnBaR1Z5VFc5dWEyVjVMbHh1THlvcVhHNGdLaUJVYUhKdmQzTWdZU0JTWlhSMWNtNVdZV3gxWlNCbGVHTmxjSFJwYjI0Z2RHOGdjM1J2Y0NCaGJpQmhjM2x1WTJoeWIyNXZkWE1nWjJWdVpYSmhkRzl5TGx4dUlDcGNiaUFxSUZSb2FYTWdhVzUwWlhKbVlXTmxJR2x6SUdFZ2MzUnZjQzFuWVhBZ2JXVmhjM1Z5WlNCMGJ5QnpkWEJ3YjNKMElHZGxibVZ5WVhSdmNpQnlaWFIxY201Y2JpQXFJSFpoYkhWbGN5QnBiaUJ2YkdSbGNpQkdhWEpsWm05NEwxTndhV1JsY2sxdmJtdGxlUzRnSUVsdUlHSnliM2R6WlhKeklIUm9ZWFFnYzNWd2NHOXlkQ0JGVXpaY2JpQXFJR2RsYm1WeVlYUnZjbk1nYkdsclpTQkRhSEp2YldsMWJTQXlPU3dnYW5WemRDQjFjMlVnWENKeVpYUjFjbTVjSWlCcGJpQjViM1Z5SUdkbGJtVnlZWFJ2Y2x4dUlDb2dablZ1WTNScGIyNXpMbHh1SUNwY2JpQXFJRUJ3WVhKaGJTQjJZV3gxWlNCMGFHVWdjbVYwZFhKdUlIWmhiSFZsSUdadmNpQjBhR1VnYzNWeWNtOTFibVJwYm1jZ1oyVnVaWEpoZEc5eVhHNGdLaUJBZEdoeWIzZHpJRkpsZEhWeWJsWmhiSFZsSUdWNFkyVndkR2x2YmlCM2FYUm9JSFJvWlNCMllXeDFaUzVjYmlBcUlFQmxlR0Z0Y0d4bFhHNGdLaUF2THlCRlV6WWdjM1I1YkdWY2JpQXFJRkV1WVhONWJtTW9ablZ1WTNScGIyNHFJQ2dwSUh0Y2JpQXFJQ0FnSUNBZ2RtRnlJR1p2YnlBOUlIbHBaV3hrSUdkbGRFWnZiMUJ5YjIxcGMyVW9LVHRjYmlBcUlDQWdJQ0FnZG1GeUlHSmhjaUE5SUhscFpXeGtJR2RsZEVKaGNsQnliMjFwYzJVb0tUdGNiaUFxSUNBZ0lDQWdjbVYwZFhKdUlHWnZieUFySUdKaGNqdGNiaUFxSUgwcFhHNGdLaUF2THlCUGJHUmxjaUJUY0dsa1pYSk5iMjVyWlhrZ2MzUjViR1ZjYmlBcUlGRXVZWE41Ym1Nb1puVnVZM1JwYjI0Z0tDa2dlMXh1SUNvZ0lDQWdJQ0IyWVhJZ1ptOXZJRDBnZVdsbGJHUWdaMlYwUm05dlVISnZiV2x6WlNncE8xeHVJQ29nSUNBZ0lDQjJZWElnWW1GeUlEMGdlV2xsYkdRZ1oyVjBRbUZ5VUhKdmJXbHpaU2dwTzF4dUlDb2dJQ0FnSUNCUkxuSmxkSFZ5YmlobWIyOGdLeUJpWVhJcE8xeHVJQ29nZlNsY2JpQXFMMXh1VVZ0Y0luSmxkSFZ5Ymx3aVhTQTlJRjl5WlhSMWNtNDdYRzVtZFc1amRHbHZiaUJmY21WMGRYSnVLSFpoYkhWbEtTQjdYRzRnSUNBZ2RHaHliM2NnYm1WM0lGRlNaWFIxY201V1lXeDFaU2gyWVd4MVpTazdYRzU5WEc1Y2JpOHFLbHh1SUNvZ1ZHaGxJSEJ5YjIxcGMyVmtJR1oxYm1OMGFXOXVJR1JsWTI5eVlYUnZjaUJsYm5OMWNtVnpJSFJvWVhRZ1lXNTVJSEJ5YjIxcGMyVWdZWEpuZFcxbGJuUnpYRzRnS2lCaGNtVWdjMlYwZEd4bFpDQmhibVFnY0dGemMyVmtJR0Z6SUhaaGJIVmxjeUFvWUhSb2FYTmdJR2x6SUdGc2MyOGdjMlYwZEd4bFpDQmhibVFnY0dGemMyVmtYRzRnS2lCaGN5QmhJSFpoYkhWbEtTNGdJRWwwSUhkcGJHd2dZV3h6YnlCbGJuTjFjbVVnZEdoaGRDQjBhR1VnY21WemRXeDBJRzltSUdFZ1puVnVZM1JwYjI0Z2FYTmNiaUFxSUdGc2QyRjVjeUJoSUhCeWIyMXBjMlV1WEc0Z0tseHVJQ29nUUdWNFlXMXdiR1ZjYmlBcUlIWmhjaUJoWkdRZ1BTQlJMbkJ5YjIxcGMyVmtLR1oxYm1OMGFXOXVJQ2hoTENCaUtTQjdYRzRnS2lBZ0lDQWdjbVYwZFhKdUlHRWdLeUJpTzF4dUlDb2dmU2s3WEc0Z0tpQmhaR1FvVVNoaEtTd2dVU2hDS1NrN1hHNGdLbHh1SUNvZ1FIQmhjbUZ0SUh0bWRXNWpkR2x2Ym4wZ1kyRnNiR0poWTJzZ1ZHaGxJR1oxYm1OMGFXOXVJSFJ2SUdSbFkyOXlZWFJsWEc0Z0tpQkFjbVYwZFhKdWN5QjdablZ1WTNScGIyNTlJR0VnWm5WdVkzUnBiMjRnZEdoaGRDQm9ZWE1nWW1WbGJpQmtaV052Y21GMFpXUXVYRzRnS2k5Y2JsRXVjSEp2YldselpXUWdQU0J3Y205dGFYTmxaRHRjYm1aMWJtTjBhVzl1SUhCeWIyMXBjMlZrS0dOaGJHeGlZV05yS1NCN1hHNGdJQ0FnY21WMGRYSnVJR1oxYm1OMGFXOXVJQ2dwSUh0Y2JpQWdJQ0FnSUNBZ2NtVjBkWEp1SUhOd2NtVmhaQ2hiZEdocGN5d2dZV3hzS0dGeVozVnRaVzUwY3lsZExDQm1kVzVqZEdsdmJpQW9jMlZzWml3Z1lYSm5jeWtnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdjbVYwZFhKdUlHTmhiR3hpWVdOckxtRndjR3g1S0hObGJHWXNJR0Z5WjNNcE8xeHVJQ0FnSUNBZ0lDQjlLVHRjYmlBZ0lDQjlPMXh1ZlZ4dVhHNHZLaXBjYmlBcUlITmxibVJ6SUdFZ2JXVnpjMkZuWlNCMGJ5QmhJSFpoYkhWbElHbHVJR0VnWm5WMGRYSmxJSFIxY201Y2JpQXFJRUJ3WVhKaGJTQnZZbXBsWTNRcUlIUm9aU0J5WldOcGNHbGxiblJjYmlBcUlFQndZWEpoYlNCdmNDQjBhR1VnYm1GdFpTQnZaaUIwYUdVZ2JXVnpjMkZuWlNCdmNHVnlZWFJwYjI0c0lHVXVaeTRzSUZ3aWQyaGxibHdpTEZ4dUlDb2dRSEJoY21GdElHRnlaM01nWm5WeWRHaGxjaUJoY21kMWJXVnVkSE1nZEc4Z1ltVWdabTl5ZDJGeVpHVmtJSFJ2SUhSb1pTQnZjR1Z5WVhScGIyNWNiaUFxSUVCeVpYUjFjbTV6SUhKbGMzVnNkQ0I3VUhKdmJXbHpaWDBnWVNCd2NtOXRhWE5sSUdadmNpQjBhR1VnY21WemRXeDBJRzltSUhSb1pTQnZjR1Z5WVhScGIyNWNiaUFxTDF4dVVTNWthWE53WVhSamFDQTlJR1JwYzNCaGRHTm9PMXh1Wm5WdVkzUnBiMjRnWkdsemNHRjBZMmdvYjJKcVpXTjBMQ0J2Y0N3Z1lYSm5jeWtnZTF4dUlDQWdJSEpsZEhWeWJpQlJLRzlpYW1WamRDa3VaR2x6Y0dGMFkyZ29iM0FzSUdGeVozTXBPMXh1ZlZ4dVhHNVFjbTl0YVhObExuQnliM1J2ZEhsd1pTNWthWE53WVhSamFDQTlJR1oxYm1OMGFXOXVJQ2h2Y0N3Z1lYSm5jeWtnZTF4dUlDQWdJSFpoY2lCelpXeG1JRDBnZEdocGN6dGNiaUFnSUNCMllYSWdaR1ZtWlhKeVpXUWdQU0JrWldabGNpZ3BPMXh1SUNBZ0lGRXVibVY0ZEZScFkyc29ablZ1WTNScGIyNGdLQ2tnZTF4dUlDQWdJQ0FnSUNCelpXeG1MbkJ5YjIxcGMyVkVhWE53WVhSamFDaGtaV1psY25KbFpDNXlaWE52YkhabExDQnZjQ3dnWVhKbmN5azdYRzRnSUNBZ2ZTazdYRzRnSUNBZ2NtVjBkWEp1SUdSbFptVnljbVZrTG5CeWIyMXBjMlU3WEc1OU8xeHVYRzR2S2lwY2JpQXFJRWRsZEhNZ2RHaGxJSFpoYkhWbElHOW1JR0VnY0hKdmNHVnlkSGtnYVc0Z1lTQm1kWFIxY21VZ2RIVnliaTVjYmlBcUlFQndZWEpoYlNCdlltcGxZM1FnSUNBZ2NISnZiV2x6WlNCdmNpQnBiVzFsWkdsaGRHVWdjbVZtWlhKbGJtTmxJR1p2Y2lCMFlYSm5aWFFnYjJKcVpXTjBYRzRnS2lCQWNHRnlZVzBnYm1GdFpTQWdJQ0FnSUc1aGJXVWdiMllnY0hKdmNHVnlkSGtnZEc4Z1oyVjBYRzRnS2lCQWNtVjBkWEp1SUhCeWIyMXBjMlVnWm05eUlIUm9aU0J3Y205d1pYSjBlU0IyWVd4MVpWeHVJQ292WEc1UkxtZGxkQ0E5SUdaMWJtTjBhVzl1SUNodlltcGxZM1FzSUd0bGVTa2dlMXh1SUNBZ0lISmxkSFZ5YmlCUktHOWlhbVZqZENrdVpHbHpjR0YwWTJnb1hDSm5aWFJjSWl3Z1cydGxlVjBwTzF4dWZUdGNibHh1VUhKdmJXbHpaUzV3Y205MGIzUjVjR1V1WjJWMElEMGdablZ1WTNScGIyNGdLR3RsZVNrZ2UxeHVJQ0FnSUhKbGRIVnliaUIwYUdsekxtUnBjM0JoZEdOb0tGd2laMlYwWENJc0lGdHJaWGxkS1R0Y2JuMDdYRzVjYmk4cUtseHVJQ29nVTJWMGN5QjBhR1VnZG1Gc2RXVWdiMllnWVNCd2NtOXdaWEowZVNCcGJpQmhJR1oxZEhWeVpTQjBkWEp1TGx4dUlDb2dRSEJoY21GdElHOWlhbVZqZENBZ0lDQndjbTl0YVhObElHOXlJR2x0YldWa2FXRjBaU0J5WldabGNtVnVZMlVnWm05eUlHOWlhbVZqZENCdlltcGxZM1JjYmlBcUlFQndZWEpoYlNCdVlXMWxJQ0FnSUNBZ2JtRnRaU0J2WmlCd2NtOXdaWEowZVNCMGJ5QnpaWFJjYmlBcUlFQndZWEpoYlNCMllXeDFaU0FnSUNBZ2JtVjNJSFpoYkhWbElHOW1JSEJ5YjNCbGNuUjVYRzRnS2lCQWNtVjBkWEp1SUhCeWIyMXBjMlVnWm05eUlIUm9aU0J5WlhSMWNtNGdkbUZzZFdWY2JpQXFMMXh1VVM1elpYUWdQU0JtZFc1amRHbHZiaUFvYjJKcVpXTjBMQ0JyWlhrc0lIWmhiSFZsS1NCN1hHNGdJQ0FnY21WMGRYSnVJRkVvYjJKcVpXTjBLUzVrYVhOd1lYUmphQ2hjSW5ObGRGd2lMQ0JiYTJWNUxDQjJZV3gxWlYwcE8xeHVmVHRjYmx4dVVISnZiV2x6WlM1d2NtOTBiM1I1Y0dVdWMyVjBJRDBnWm5WdVkzUnBiMjRnS0d0bGVTd2dkbUZzZFdVcElIdGNiaUFnSUNCeVpYUjFjbTRnZEdocGN5NWthWE53WVhSamFDaGNJbk5sZEZ3aUxDQmJhMlY1TENCMllXeDFaVjBwTzF4dWZUdGNibHh1THlvcVhHNGdLaUJFWld4bGRHVnpJR0VnY0hKdmNHVnlkSGtnYVc0Z1lTQm1kWFIxY21VZ2RIVnliaTVjYmlBcUlFQndZWEpoYlNCdlltcGxZM1FnSUNBZ2NISnZiV2x6WlNCdmNpQnBiVzFsWkdsaGRHVWdjbVZtWlhKbGJtTmxJR1p2Y2lCMFlYSm5aWFFnYjJKcVpXTjBYRzRnS2lCQWNHRnlZVzBnYm1GdFpTQWdJQ0FnSUc1aGJXVWdiMllnY0hKdmNHVnlkSGtnZEc4Z1pHVnNaWFJsWEc0Z0tpQkFjbVYwZFhKdUlIQnliMjFwYzJVZ1ptOXlJSFJvWlNCeVpYUjFjbTRnZG1Gc2RXVmNiaUFxTDF4dVVTNWtaV3dnUFNBdkx5QllXRmdnYkdWbllXTjVYRzVSVzF3aVpHVnNaWFJsWENKZElEMGdablZ1WTNScGIyNGdLRzlpYW1WamRDd2dhMlY1S1NCN1hHNGdJQ0FnY21WMGRYSnVJRkVvYjJKcVpXTjBLUzVrYVhOd1lYUmphQ2hjSW1SbGJHVjBaVndpTENCYmEyVjVYU2s3WEc1OU8xeHVYRzVRY205dGFYTmxMbkJ5YjNSdmRIbHdaUzVrWld3Z1BTQXZMeUJZV0ZnZ2JHVm5ZV041WEc1UWNtOXRhWE5sTG5CeWIzUnZkSGx3WlZ0Y0ltUmxiR1YwWlZ3aVhTQTlJR1oxYm1OMGFXOXVJQ2hyWlhrcElIdGNiaUFnSUNCeVpYUjFjbTRnZEdocGN5NWthWE53WVhSamFDaGNJbVJsYkdWMFpWd2lMQ0JiYTJWNVhTazdYRzU5TzF4dVhHNHZLaXBjYmlBcUlFbHVkbTlyWlhNZ1lTQnRaWFJvYjJRZ2FXNGdZU0JtZFhSMWNtVWdkSFZ5Ymk1Y2JpQXFJRUJ3WVhKaGJTQnZZbXBsWTNRZ0lDQWdjSEp2YldselpTQnZjaUJwYlcxbFpHbGhkR1VnY21WbVpYSmxibU5sSUdadmNpQjBZWEpuWlhRZ2IySnFaV04wWEc0Z0tpQkFjR0Z5WVcwZ2JtRnRaU0FnSUNBZ0lHNWhiV1VnYjJZZ2JXVjBhRzlrSUhSdklHbHVkbTlyWlZ4dUlDb2dRSEJoY21GdElIWmhiSFZsSUNBZ0lDQmhJSFpoYkhWbElIUnZJSEJ2YzNRc0lIUjVjR2xqWVd4c2VTQmhiaUJoY25KaGVTQnZabHh1SUNvZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCcGJuWnZZMkYwYVc5dUlHRnlaM1Z0Wlc1MGN5Qm1iM0lnY0hKdmJXbHpaWE1nZEdoaGRGeHVJQ29nSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0JoY21VZ2RXeDBhVzFoZEdWc2VTQmlZV05yWldRZ2QybDBhQ0JnY21WemIyeDJaV0FnZG1Gc2RXVnpMRnh1SUNvZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCaGN5QnZjSEJ2YzJWa0lIUnZJSFJvYjNObElHSmhZMnRsWkNCM2FYUm9JRlZTVEhOY2JpQXFJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdkMmhsY21WcGJpQjBhR1VnY0c5emRHVmtJSFpoYkhWbElHTmhiaUJpWlNCaGJubGNiaUFxSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnU2xOUFRpQnpaWEpwWVd4cGVtRmliR1VnYjJKcVpXTjBMbHh1SUNvZ1FISmxkSFZ5YmlCd2NtOXRhWE5sSUdadmNpQjBhR1VnY21WMGRYSnVJSFpoYkhWbFhHNGdLaTljYmk4dklHSnZkVzVrSUd4dlkyRnNiSGtnWW1WallYVnpaU0JwZENCcGN5QjFjMlZrSUdKNUlHOTBhR1Z5SUcxbGRHaHZaSE5jYmxFdWJXRndjR3g1SUQwZ0x5OGdXRmhZSUVGeklIQnliM0J2YzJWa0lHSjVJRndpVW1Wa2MyRnVaSEp2WENKY2JsRXVjRzl6ZENBOUlHWjFibU4wYVc5dUlDaHZZbXBsWTNRc0lHNWhiV1VzSUdGeVozTXBJSHRjYmlBZ0lDQnlaWFIxY200Z1VTaHZZbXBsWTNRcExtUnBjM0JoZEdOb0tGd2ljRzl6ZEZ3aUxDQmJibUZ0WlN3Z1lYSm5jMTBwTzF4dWZUdGNibHh1VUhKdmJXbHpaUzV3Y205MGIzUjVjR1V1YldGd2NHeDVJRDBnTHk4Z1dGaFlJRUZ6SUhCeWIzQnZjMlZrSUdKNUlGd2lVbVZrYzJGdVpISnZYQ0pjYmxCeWIyMXBjMlV1Y0hKdmRHOTBlWEJsTG5CdmMzUWdQU0JtZFc1amRHbHZiaUFvYm1GdFpTd2dZWEpuY3lrZ2UxeHVJQ0FnSUhKbGRIVnliaUIwYUdsekxtUnBjM0JoZEdOb0tGd2ljRzl6ZEZ3aUxDQmJibUZ0WlN3Z1lYSm5jMTBwTzF4dWZUdGNibHh1THlvcVhHNGdLaUJKYm5admEyVnpJR0VnYldWMGFHOWtJR2x1SUdFZ1puVjBkWEpsSUhSMWNtNHVYRzRnS2lCQWNHRnlZVzBnYjJKcVpXTjBJQ0FnSUhCeWIyMXBjMlVnYjNJZ2FXMXRaV1JwWVhSbElISmxabVZ5Wlc1alpTQm1iM0lnZEdGeVoyVjBJRzlpYW1WamRGeHVJQ29nUUhCaGNtRnRJRzVoYldVZ0lDQWdJQ0J1WVcxbElHOW1JRzFsZEdodlpDQjBieUJwYm5admEyVmNiaUFxSUVCd1lYSmhiU0F1TGk1aGNtZHpJQ0FnWVhKeVlYa2diMllnYVc1MmIyTmhkR2x2YmlCaGNtZDFiV1Z1ZEhOY2JpQXFJRUJ5WlhSMWNtNGdjSEp2YldselpTQm1iM0lnZEdobElISmxkSFZ5YmlCMllXeDFaVnh1SUNvdlhHNVJMbk5sYm1RZ1BTQXZMeUJZV0ZnZ1RXRnlheUJOYVd4c1pYSW5jeUJ3Y205d2IzTmxaQ0J3WVhKc1lXNWpaVnh1VVM1dFkyRnNiQ0E5SUM4dklGaFlXQ0JCY3lCd2NtOXdiM05sWkNCaWVTQmNJbEpsWkhOaGJtUnliMXdpWEc1UkxtbHVkbTlyWlNBOUlHWjFibU4wYVc5dUlDaHZZbXBsWTNRc0lHNWhiV1VnTHlvdUxpNWhjbWR6S2k4cElIdGNiaUFnSUNCeVpYUjFjbTRnVVNodlltcGxZM1FwTG1ScGMzQmhkR05vS0Z3aWNHOXpkRndpTENCYmJtRnRaU3dnWVhKeVlYbGZjMnhwWTJVb1lYSm5kVzFsYm5SekxDQXlLVjBwTzF4dWZUdGNibHh1VUhKdmJXbHpaUzV3Y205MGIzUjVjR1V1YzJWdVpDQTlJQzh2SUZoWVdDQk5ZWEpySUUxcGJHeGxjaWR6SUhCeWIzQnZjMlZrSUhCaGNteGhibU5sWEc1UWNtOXRhWE5sTG5CeWIzUnZkSGx3WlM1dFkyRnNiQ0E5SUM4dklGaFlXQ0JCY3lCd2NtOXdiM05sWkNCaWVTQmNJbEpsWkhOaGJtUnliMXdpWEc1UWNtOXRhWE5sTG5CeWIzUnZkSGx3WlM1cGJuWnZhMlVnUFNCbWRXNWpkR2x2YmlBb2JtRnRaU0F2S2k0dUxtRnlaM01xTHlrZ2UxeHVJQ0FnSUhKbGRIVnliaUIwYUdsekxtUnBjM0JoZEdOb0tGd2ljRzl6ZEZ3aUxDQmJibUZ0WlN3Z1lYSnlZWGxmYzJ4cFkyVW9ZWEpuZFcxbGJuUnpMQ0F4S1YwcE8xeHVmVHRjYmx4dUx5b3FYRzRnS2lCQmNIQnNhV1Z6SUhSb1pTQndjbTl0YVhObFpDQm1kVzVqZEdsdmJpQnBiaUJoSUdaMWRIVnlaU0IwZFhKdUxseHVJQ29nUUhCaGNtRnRJRzlpYW1WamRDQWdJQ0J3Y205dGFYTmxJRzl5SUdsdGJXVmthV0YwWlNCeVpXWmxjbVZ1WTJVZ1ptOXlJSFJoY21kbGRDQm1kVzVqZEdsdmJseHVJQ29nUUhCaGNtRnRJR0Z5WjNNZ0lDQWdJQ0JoY25KaGVTQnZaaUJoY0hCc2FXTmhkR2x2YmlCaGNtZDFiV1Z1ZEhOY2JpQXFMMXh1VVM1bVlYQndiSGtnUFNCbWRXNWpkR2x2YmlBb2IySnFaV04wTENCaGNtZHpLU0I3WEc0Z0lDQWdjbVYwZFhKdUlGRW9iMkpxWldOMEtTNWthWE53WVhSamFDaGNJbUZ3Y0d4NVhDSXNJRnQyYjJsa0lEQXNJR0Z5WjNOZEtUdGNibjA3WEc1Y2JsQnliMjFwYzJVdWNISnZkRzkwZVhCbExtWmhjSEJzZVNBOUlHWjFibU4wYVc5dUlDaGhjbWR6S1NCN1hHNGdJQ0FnY21WMGRYSnVJSFJvYVhNdVpHbHpjR0YwWTJnb1hDSmhjSEJzZVZ3aUxDQmJkbTlwWkNBd0xDQmhjbWR6WFNrN1hHNTlPMXh1WEc0dktpcGNiaUFxSUVOaGJHeHpJSFJvWlNCd2NtOXRhWE5sWkNCbWRXNWpkR2x2YmlCcGJpQmhJR1oxZEhWeVpTQjBkWEp1TGx4dUlDb2dRSEJoY21GdElHOWlhbVZqZENBZ0lDQndjbTl0YVhObElHOXlJR2x0YldWa2FXRjBaU0J5WldabGNtVnVZMlVnWm05eUlIUmhjbWRsZENCbWRXNWpkR2x2Ymx4dUlDb2dRSEJoY21GdElDNHVMbUZ5WjNNZ0lDQmhjbkpoZVNCdlppQmhjSEJzYVdOaGRHbHZiaUJoY21kMWJXVnVkSE5jYmlBcUwxeHVVVnRjSW5SeWVWd2lYU0E5WEc1UkxtWmpZV3hzSUQwZ1puVnVZM1JwYjI0Z0tHOWlhbVZqZENBdktpQXVMaTVoY21kektpOHBJSHRjYmlBZ0lDQnlaWFIxY200Z1VTaHZZbXBsWTNRcExtUnBjM0JoZEdOb0tGd2lZWEJ3YkhsY0lpd2dXM1p2YVdRZ01Dd2dZWEp5WVhsZmMyeHBZMlVvWVhKbmRXMWxiblJ6TENBeEtWMHBPMXh1ZlR0Y2JseHVVSEp2YldselpTNXdjbTkwYjNSNWNHVXVabU5oYkd3Z1BTQm1kVzVqZEdsdmJpQW9MeW91TGk1aGNtZHpLaThwSUh0Y2JpQWdJQ0J5WlhSMWNtNGdkR2hwY3k1a2FYTndZWFJqYUNoY0ltRndjR3g1WENJc0lGdDJiMmxrSURBc0lHRnljbUY1WDNOc2FXTmxLR0Z5WjNWdFpXNTBjeWxkS1R0Y2JuMDdYRzVjYmk4cUtseHVJQ29nUW1sdVpITWdkR2hsSUhCeWIyMXBjMlZrSUdaMWJtTjBhVzl1TENCMGNtRnVjMlp2Y20xcGJtY2djbVYwZFhKdUlIWmhiSFZsY3lCcGJuUnZJR0VnWm5Wc1ptbHNiR1ZrWEc0Z0tpQndjbTl0YVhObElHRnVaQ0IwYUhKdmQyNGdaWEp5YjNKeklHbHVkRzhnWVNCeVpXcGxZM1JsWkNCdmJtVXVYRzRnS2lCQWNHRnlZVzBnYjJKcVpXTjBJQ0FnSUhCeWIyMXBjMlVnYjNJZ2FXMXRaV1JwWVhSbElISmxabVZ5Wlc1alpTQm1iM0lnZEdGeVoyVjBJR1oxYm1OMGFXOXVYRzRnS2lCQWNHRnlZVzBnTGk0dVlYSm5jeUFnSUdGeWNtRjVJRzltSUdGd2NHeHBZMkYwYVc5dUlHRnlaM1Z0Wlc1MGMxeHVJQ292WEc1UkxtWmlhVzVrSUQwZ1puVnVZM1JwYjI0Z0tHOWlhbVZqZENBdktpNHVMbUZ5WjNNcUx5a2dlMXh1SUNBZ0lIWmhjaUJ3Y205dGFYTmxJRDBnVVNodlltcGxZM1FwTzF4dUlDQWdJSFpoY2lCaGNtZHpJRDBnWVhKeVlYbGZjMnhwWTJVb1lYSm5kVzFsYm5SekxDQXhLVHRjYmlBZ0lDQnlaWFIxY200Z1puVnVZM1JwYjI0Z1ptSnZkVzVrS0NrZ2UxeHVJQ0FnSUNBZ0lDQnlaWFIxY200Z2NISnZiV2x6WlM1a2FYTndZWFJqYUNoY0ltRndjR3g1WENJc0lGdGNiaUFnSUNBZ0lDQWdJQ0FnSUhSb2FYTXNYRzRnSUNBZ0lDQWdJQ0FnSUNCaGNtZHpMbU52Ym1OaGRDaGhjbkpoZVY5emJHbGpaU2hoY21kMWJXVnVkSE1wS1Z4dUlDQWdJQ0FnSUNCZEtUdGNiaUFnSUNCOU8xeHVmVHRjYmxCeWIyMXBjMlV1Y0hKdmRHOTBlWEJsTG1aaWFXNWtJRDBnWm5WdVkzUnBiMjRnS0M4cUxpNHVZWEpuY3lvdktTQjdYRzRnSUNBZ2RtRnlJSEJ5YjIxcGMyVWdQU0IwYUdsek8xeHVJQ0FnSUhaaGNpQmhjbWR6SUQwZ1lYSnlZWGxmYzJ4cFkyVW9ZWEpuZFcxbGJuUnpLVHRjYmlBZ0lDQnlaWFIxY200Z1puVnVZM1JwYjI0Z1ptSnZkVzVrS0NrZ2UxeHVJQ0FnSUNBZ0lDQnlaWFIxY200Z2NISnZiV2x6WlM1a2FYTndZWFJqYUNoY0ltRndjR3g1WENJc0lGdGNiaUFnSUNBZ0lDQWdJQ0FnSUhSb2FYTXNYRzRnSUNBZ0lDQWdJQ0FnSUNCaGNtZHpMbU52Ym1OaGRDaGhjbkpoZVY5emJHbGpaU2hoY21kMWJXVnVkSE1wS1Z4dUlDQWdJQ0FnSUNCZEtUdGNiaUFnSUNCOU8xeHVmVHRjYmx4dUx5b3FYRzRnS2lCU1pYRjFaWE4wY3lCMGFHVWdibUZ0WlhNZ2IyWWdkR2hsSUc5M2JtVmtJSEJ5YjNCbGNuUnBaWE1nYjJZZ1lTQndjbTl0YVhObFpGeHVJQ29nYjJKcVpXTjBJR2x1SUdFZ1puVjBkWEpsSUhSMWNtNHVYRzRnS2lCQWNHRnlZVzBnYjJKcVpXTjBJQ0FnSUhCeWIyMXBjMlVnYjNJZ2FXMXRaV1JwWVhSbElISmxabVZ5Wlc1alpTQm1iM0lnZEdGeVoyVjBJRzlpYW1WamRGeHVJQ29nUUhKbGRIVnliaUJ3Y205dGFYTmxJR1p2Y2lCMGFHVWdhMlY1Y3lCdlppQjBhR1VnWlhabGJuUjFZV3hzZVNCelpYUjBiR1ZrSUc5aWFtVmpkRnh1SUNvdlhHNVJMbXRsZVhNZ1BTQm1kVzVqZEdsdmJpQW9iMkpxWldOMEtTQjdYRzRnSUNBZ2NtVjBkWEp1SUZFb2IySnFaV04wS1M1a2FYTndZWFJqYUNoY0ltdGxlWE5jSWl3Z1cxMHBPMXh1ZlR0Y2JseHVVSEp2YldselpTNXdjbTkwYjNSNWNHVXVhMlY1Y3lBOUlHWjFibU4wYVc5dUlDZ3BJSHRjYmlBZ0lDQnlaWFIxY200Z2RHaHBjeTVrYVhOd1lYUmphQ2hjSW10bGVYTmNJaXdnVzEwcE8xeHVmVHRjYmx4dUx5b3FYRzRnS2lCVWRYSnVjeUJoYmlCaGNuSmhlU0J2WmlCd2NtOXRhWE5sY3lCcGJuUnZJR0VnY0hKdmJXbHpaU0JtYjNJZ1lXNGdZWEp5WVhrdUlDQkpaaUJoYm5rZ2IyWmNiaUFxSUhSb1pTQndjbTl0YVhObGN5Qm5aWFJ6SUhKbGFtVmpkR1ZrTENCMGFHVWdkMmh2YkdVZ1lYSnlZWGtnYVhNZ2NtVnFaV04wWldRZ2FXMXRaV1JwWVhSbGJIa3VYRzRnS2lCQWNHRnlZVzBnZTBGeWNtRjVLbjBnWVc0Z1lYSnlZWGtnS0c5eUlIQnliMjFwYzJVZ1ptOXlJR0Z1SUdGeWNtRjVLU0J2WmlCMllXeDFaWE1nS0c5eVhHNGdLaUJ3Y205dGFYTmxjeUJtYjNJZ2RtRnNkV1Z6S1Z4dUlDb2dRSEpsZEhWeWJuTWdZU0J3Y205dGFYTmxJR1p2Y2lCaGJpQmhjbkpoZVNCdlppQjBhR1VnWTI5eWNtVnpjRzl1WkdsdVp5QjJZV3gxWlhOY2JpQXFMMXh1THk4Z1Fua2dUV0Z5YXlCTmFXeHNaWEpjYmk4dklHaDBkSEE2THk5M2FXdHBMbVZqYldGelkzSnBjSFF1YjNKbkwyUnZhM1V1Y0dod1AybGtQWE4wY21GM2JXRnVPbU52Ym1OMWNuSmxibU41Sm5KbGRqMHhNekE0TnpjMk5USXhJMkZzYkdaMWJHWnBiR3hsWkZ4dVVTNWhiR3dnUFNCaGJHdzdYRzVtZFc1amRHbHZiaUJoYkd3b2NISnZiV2x6WlhNcElIdGNiaUFnSUNCeVpYUjFjbTRnZDJobGJpaHdjbTl0YVhObGN5d2dablZ1WTNScGIyNGdLSEJ5YjIxcGMyVnpLU0I3WEc0Z0lDQWdJQ0FnSUhaaGNpQndaVzVrYVc1blEyOTFiblFnUFNBd08xeHVJQ0FnSUNBZ0lDQjJZWElnWkdWbVpYSnlaV1FnUFNCa1pXWmxjaWdwTzF4dUlDQWdJQ0FnSUNCaGNuSmhlVjl5WldSMVkyVW9jSEp2YldselpYTXNJR1oxYm1OMGFXOXVJQ2gxYm1SbFptbHVaV1FzSUhCeWIyMXBjMlVzSUdsdVpHVjRLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQjJZWElnYzI1aGNITm9iM1E3WEc0Z0lDQWdJQ0FnSUNBZ0lDQnBaaUFvWEc0Z0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnYVhOUWNtOXRhWE5sS0hCeWIyMXBjMlVwSUNZbVhHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0tITnVZWEJ6YUc5MElEMGdjSEp2YldselpTNXBibk53WldOMEtDa3BMbk4wWVhSbElEMDlQU0JjSW1aMWJHWnBiR3hsWkZ3aVhHNGdJQ0FnSUNBZ0lDQWdJQ0FwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCd2NtOXRhWE5sYzF0cGJtUmxlRjBnUFNCemJtRndjMmh2ZEM1MllXeDFaVHRjYmlBZ0lDQWdJQ0FnSUNBZ0lIMGdaV3h6WlNCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0t5dHdaVzVrYVc1blEyOTFiblE3WEc0Z0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnZDJobGJpaGNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnY0hKdmJXbHpaU3hjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ1puVnVZM1JwYjI0Z0tIWmhiSFZsS1NCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCd2NtOXRhWE5sYzF0cGJtUmxlRjBnUFNCMllXeDFaVHRjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJR2xtSUNndExYQmxibVJwYm1kRGIzVnVkQ0E5UFQwZ01Da2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUdSbFptVnljbVZrTG5KbGMyOXNkbVVvY0hKdmJXbHpaWE1wTzF4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnZlZ4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQjlMRnh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCa1pXWmxjbkpsWkM1eVpXcGxZM1FzWEc0Z0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lHWjFibU4wYVc5dUlDaHdjbTluY21WemN5a2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdaR1ZtWlhKeVpXUXVibTkwYVdaNUtIc2dhVzVrWlhnNklHbHVaR1Y0TENCMllXeDFaVG9nY0hKdlozSmxjM01nZlNrN1hHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJSDFjYmlBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FwTzF4dUlDQWdJQ0FnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0I5TENCMmIybGtJREFwTzF4dUlDQWdJQ0FnSUNCcFppQW9jR1Z1WkdsdVowTnZkVzUwSUQwOVBTQXdLU0I3WEc0Z0lDQWdJQ0FnSUNBZ0lDQmtaV1psY25KbFpDNXlaWE52YkhabEtIQnliMjFwYzJWektUdGNiaUFnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0J5WlhSMWNtNGdaR1ZtWlhKeVpXUXVjSEp2YldselpUdGNiaUFnSUNCOUtUdGNibjFjYmx4dVVISnZiV2x6WlM1d2NtOTBiM1I1Y0dVdVlXeHNJRDBnWm5WdVkzUnBiMjRnS0NrZ2UxeHVJQ0FnSUhKbGRIVnliaUJoYkd3b2RHaHBjeWs3WEc1OU8xeHVYRzR2S2lwY2JpQXFJRkpsZEhWeWJuTWdkR2hsSUdacGNuTjBJSEpsYzI5c2RtVmtJSEJ5YjIxcGMyVWdiMllnWVc0Z1lYSnlZWGt1SUZCeWFXOXlJSEpsYW1WamRHVmtJSEJ5YjIxcGMyVnpJR0Z5WlZ4dUlDb2dhV2R1YjNKbFpDNGdJRkpsYW1WamRITWdiMjVzZVNCcFppQmhiR3dnY0hKdmJXbHpaWE1nWVhKbElISmxhbVZqZEdWa0xseHVJQ29nUUhCaGNtRnRJSHRCY25KaGVTcDlJR0Z1SUdGeWNtRjVJR052Ym5SaGFXNXBibWNnZG1Gc2RXVnpJRzl5SUhCeWIyMXBjMlZ6SUdadmNpQjJZV3gxWlhOY2JpQXFJRUJ5WlhSMWNtNXpJR0VnY0hKdmJXbHpaU0JtZFd4bWFXeHNaV1FnZDJsMGFDQjBhR1VnZG1Gc2RXVWdiMllnZEdobElHWnBjbk4wSUhKbGMyOXNkbVZrSUhCeWIyMXBjMlVzWEc0Z0tpQnZjaUJoSUhKbGFtVmpkR1ZrSUhCeWIyMXBjMlVnYVdZZ1lXeHNJSEJ5YjIxcGMyVnpJR0Z5WlNCeVpXcGxZM1JsWkM1Y2JpQXFMMXh1VVM1aGJua2dQU0JoYm5rN1hHNWNibVoxYm1OMGFXOXVJR0Z1ZVNod2NtOXRhWE5sY3lrZ2UxeHVJQ0FnSUdsbUlDaHdjbTl0YVhObGN5NXNaVzVuZEdnZ1BUMDlJREFwSUh0Y2JpQWdJQ0FnSUNBZ2NtVjBkWEp1SUZFdWNtVnpiMngyWlNncE8xeHVJQ0FnSUgxY2JseHVJQ0FnSUhaaGNpQmtaV1psY25KbFpDQTlJRkV1WkdWbVpYSW9LVHRjYmlBZ0lDQjJZWElnY0dWdVpHbHVaME52ZFc1MElEMGdNRHRjYmlBZ0lDQmhjbkpoZVY5eVpXUjFZMlVvY0hKdmJXbHpaWE1zSUdaMWJtTjBhVzl1SUNod2NtVjJMQ0JqZFhKeVpXNTBMQ0JwYm1SbGVDa2dlMXh1SUNBZ0lDQWdJQ0IyWVhJZ2NISnZiV2x6WlNBOUlIQnliMjFwYzJWelcybHVaR1Y0WFR0Y2JseHVJQ0FnSUNBZ0lDQndaVzVrYVc1blEyOTFiblFyS3p0Y2JseHVJQ0FnSUNBZ0lDQjNhR1Z1S0hCeWIyMXBjMlVzSUc5dVJuVnNabWxzYkdWa0xDQnZibEpsYW1WamRHVmtMQ0J2YmxCeWIyZHlaWE56S1R0Y2JpQWdJQ0FnSUNBZ1puVnVZM1JwYjI0Z2IyNUdkV3htYVd4c1pXUW9jbVZ6ZFd4MEtTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCa1pXWmxjbkpsWkM1eVpYTnZiSFpsS0hKbGMzVnNkQ2s3WEc0Z0lDQWdJQ0FnSUgxY2JpQWdJQ0FnSUNBZ1puVnVZM1JwYjI0Z2IyNVNaV3BsWTNSbFpDZ3BJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lIQmxibVJwYm1kRGIzVnVkQzB0TzF4dUlDQWdJQ0FnSUNBZ0lDQWdhV1lnS0hCbGJtUnBibWREYjNWdWRDQTlQVDBnTUNrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lHUmxabVZ5Y21Wa0xuSmxhbVZqZENodVpYY2dSWEp5YjNJb1hHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJRndpUTJGdUozUWdaMlYwSUdaMWJHWnBiR3h0Wlc1MElIWmhiSFZsSUdaeWIyMGdZVzU1SUhCeWIyMXBjMlVzSUdGc2JDQmNJaUFyWEc0Z0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lGd2ljSEp2YldselpYTWdkMlZ5WlNCeVpXcGxZM1JsWkM1Y0lseHVJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDa3BPMXh1SUNBZ0lDQWdJQ0FnSUNBZ2ZWeHVJQ0FnSUNBZ0lDQjlYRzRnSUNBZ0lDQWdJR1oxYm1OMGFXOXVJRzl1VUhKdlozSmxjM01vY0hKdlozSmxjM01wSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJR1JsWm1WeWNtVmtMbTV2ZEdsbWVTaDdYRzRnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdhVzVrWlhnNklHbHVaR1Y0TEZ4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUhaaGJIVmxPaUJ3Y205bmNtVnpjMXh1SUNBZ0lDQWdJQ0FnSUNBZ2ZTazdYRzRnSUNBZ0lDQWdJSDFjYmlBZ0lDQjlMQ0IxYm1SbFptbHVaV1FwTzF4dVhHNGdJQ0FnY21WMGRYSnVJR1JsWm1WeWNtVmtMbkJ5YjIxcGMyVTdYRzU5WEc1Y2JsQnliMjFwYzJVdWNISnZkRzkwZVhCbExtRnVlU0E5SUdaMWJtTjBhVzl1SUNncElIdGNiaUFnSUNCeVpYUjFjbTRnWVc1NUtIUm9hWE1wTzF4dWZUdGNibHh1THlvcVhHNGdLaUJYWVdsMGN5Qm1iM0lnWVd4c0lIQnliMjFwYzJWeklIUnZJR0psSUhObGRIUnNaV1FzSUdWcGRHaGxjaUJtZFd4bWFXeHNaV1FnYjNKY2JpQXFJSEpsYW1WamRHVmtMaUFnVkdocGN5QnBjeUJrYVhOMGFXNWpkQ0JtY205dElHQmhiR3hnSUhOcGJtTmxJSFJvWVhRZ2QyOTFiR1FnYzNSdmNGeHVJQ29nZDJGcGRHbHVaeUJoZENCMGFHVWdabWx5YzNRZ2NtVnFaV04wYVc5dUxpQWdWR2hsSUhCeWIyMXBjMlVnY21WMGRYSnVaV1FnWW5sY2JpQXFJR0JoYkd4U1pYTnZiSFpsWkdBZ2QybHNiQ0J1WlhabGNpQmlaU0J5WldwbFkzUmxaQzVjYmlBcUlFQndZWEpoYlNCd2NtOXRhWE5sY3lCaElIQnliMjFwYzJVZ1ptOXlJR0Z1SUdGeWNtRjVJQ2h2Y2lCaGJpQmhjbkpoZVNrZ2IyWWdjSEp2YldselpYTmNiaUFxSUNodmNpQjJZV3gxWlhNcFhHNGdLaUJBY21WMGRYSnVJR0VnY0hKdmJXbHpaU0JtYjNJZ1lXNGdZWEp5WVhrZ2IyWWdjSEp2YldselpYTmNiaUFxTDF4dVVTNWhiR3hTWlhOdmJIWmxaQ0E5SUdSbGNISmxZMkYwWlNoaGJHeFNaWE52YkhabFpDd2dYQ0poYkd4U1pYTnZiSFpsWkZ3aUxDQmNJbUZzYkZObGRIUnNaV1JjSWlrN1hHNW1kVzVqZEdsdmJpQmhiR3hTWlhOdmJIWmxaQ2h3Y205dGFYTmxjeWtnZTF4dUlDQWdJSEpsZEhWeWJpQjNhR1Z1S0hCeWIyMXBjMlZ6TENCbWRXNWpkR2x2YmlBb2NISnZiV2x6WlhNcElIdGNiaUFnSUNBZ0lDQWdjSEp2YldselpYTWdQU0JoY25KaGVWOXRZWEFvY0hKdmJXbHpaWE1zSUZFcE8xeHVJQ0FnSUNBZ0lDQnlaWFIxY200Z2QyaGxiaWhoYkd3b1lYSnlZWGxmYldGd0tIQnliMjFwYzJWekxDQm1kVzVqZEdsdmJpQW9jSEp2YldselpTa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2NtVjBkWEp1SUhkb1pXNG9jSEp2YldselpTd2dibTl2Y0N3Z2JtOXZjQ2s3WEc0Z0lDQWdJQ0FnSUgwcEtTd2dablZ1WTNScGIyNGdLQ2tnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdjbVYwZFhKdUlIQnliMjFwYzJWek8xeHVJQ0FnSUNBZ0lDQjlLVHRjYmlBZ0lDQjlLVHRjYm4xY2JseHVVSEp2YldselpTNXdjbTkwYjNSNWNHVXVZV3hzVW1WemIyeDJaV1FnUFNCbWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ2NtVjBkWEp1SUdGc2JGSmxjMjlzZG1Wa0tIUm9hWE1wTzF4dWZUdGNibHh1THlvcVhHNGdLaUJBYzJWbElGQnliMjFwYzJVallXeHNVMlYwZEd4bFpGeHVJQ292WEc1UkxtRnNiRk5sZEhSc1pXUWdQU0JoYkd4VFpYUjBiR1ZrTzF4dVpuVnVZM1JwYjI0Z1lXeHNVMlYwZEd4bFpDaHdjbTl0YVhObGN5a2dlMXh1SUNBZ0lISmxkSFZ5YmlCUktIQnliMjFwYzJWektTNWhiR3hUWlhSMGJHVmtLQ2s3WEc1OVhHNWNiaThxS2x4dUlDb2dWSFZ5Ym5NZ1lXNGdZWEp5WVhrZ2IyWWdjSEp2YldselpYTWdhVzUwYnlCaElIQnliMjFwYzJVZ1ptOXlJR0Z1SUdGeWNtRjVJRzltSUhSb1pXbHlJSE4wWVhSbGN5QW9ZWE5jYmlBcUlISmxkSFZ5Ym1Wa0lHSjVJR0JwYm5Od1pXTjBZQ2tnZDJobGJpQjBhR1Y1SUdoaGRtVWdZV3hzSUhObGRIUnNaV1F1WEc0Z0tpQkFjR0Z5WVcwZ2UwRnljbUY1VzBGdWVTcGRmU0IyWVd4MVpYTWdZVzRnWVhKeVlYa2dLRzl5SUhCeWIyMXBjMlVnWm05eUlHRnVJR0Z5Y21GNUtTQnZaaUIyWVd4MVpYTWdLRzl5WEc0Z0tpQndjbTl0YVhObGN5Qm1iM0lnZG1Gc2RXVnpLVnh1SUNvZ1FISmxkSFZ5Ym5NZ2UwRnljbUY1VzFOMFlYUmxYWDBnWVc0Z1lYSnlZWGtnYjJZZ2MzUmhkR1Z6SUdadmNpQjBhR1VnY21WemNHVmpkR2wyWlNCMllXeDFaWE11WEc0Z0tpOWNibEJ5YjIxcGMyVXVjSEp2ZEc5MGVYQmxMbUZzYkZObGRIUnNaV1FnUFNCbWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ2NtVjBkWEp1SUhSb2FYTXVkR2hsYmlobWRXNWpkR2x2YmlBb2NISnZiV2x6WlhNcElIdGNiaUFnSUNBZ0lDQWdjbVYwZFhKdUlHRnNiQ2hoY25KaGVWOXRZWEFvY0hKdmJXbHpaWE1zSUdaMWJtTjBhVzl1SUNod2NtOXRhWE5sS1NCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0J3Y205dGFYTmxJRDBnVVNod2NtOXRhWE5sS1R0Y2JpQWdJQ0FnSUNBZ0lDQWdJR1oxYm1OMGFXOXVJSEpsWjJGeVpHeGxjM01vS1NCN1hHNGdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ2NtVjBkWEp1SUhCeWIyMXBjMlV1YVc1emNHVmpkQ2dwTzF4dUlDQWdJQ0FnSUNBZ0lDQWdmVnh1SUNBZ0lDQWdJQ0FnSUNBZ2NtVjBkWEp1SUhCeWIyMXBjMlV1ZEdobGJpaHlaV2RoY21Sc1pYTnpMQ0J5WldkaGNtUnNaWE56S1R0Y2JpQWdJQ0FnSUNBZ2ZTa3BPMXh1SUNBZ0lIMHBPMXh1ZlR0Y2JseHVMeW9xWEc0Z0tpQkRZWEIwZFhKbGN5QjBhR1VnWm1GcGJIVnlaU0J2WmlCaElIQnliMjFwYzJVc0lHZHBkbWx1WnlCaGJpQnZjRzl5ZEhWdWFYUjVJSFJ2SUhKbFkyOTJaWEpjYmlBcUlIZHBkR2dnWVNCallXeHNZbUZqYXk0Z0lFbG1JSFJvWlNCbmFYWmxiaUJ3Y205dGFYTmxJR2x6SUdaMWJHWnBiR3hsWkN3Z2RHaGxJSEpsZEhWeWJtVmtYRzRnS2lCd2NtOXRhWE5sSUdseklHWjFiR1pwYkd4bFpDNWNiaUFxSUVCd1lYSmhiU0I3UVc1NUtuMGdjSEp2YldselpTQm1iM0lnYzI5dFpYUm9hVzVuWEc0Z0tpQkFjR0Z5WVcwZ2UwWjFibU4wYVc5dWZTQmpZV3hzWW1GamF5QjBieUJtZFd4bWFXeHNJSFJvWlNCeVpYUjFjbTVsWkNCd2NtOXRhWE5sSUdsbUlIUm9aVnh1SUNvZ1oybDJaVzRnY0hKdmJXbHpaU0JwY3lCeVpXcGxZM1JsWkZ4dUlDb2dRSEpsZEhWeWJuTWdZU0J3Y205dGFYTmxJR1p2Y2lCMGFHVWdjbVYwZFhKdUlIWmhiSFZsSUc5bUlIUm9aU0JqWVd4c1ltRmphMXh1SUNvdlhHNVJMbVpoYVd3Z1BTQXZMeUJZV0ZnZ2JHVm5ZV041WEc1Ulcxd2lZMkYwWTJoY0lsMGdQU0JtZFc1amRHbHZiaUFvYjJKcVpXTjBMQ0J5WldwbFkzUmxaQ2tnZTF4dUlDQWdJSEpsZEhWeWJpQlJLRzlpYW1WamRDa3VkR2hsYmloMmIybGtJREFzSUhKbGFtVmpkR1ZrS1R0Y2JuMDdYRzVjYmxCeWIyMXBjMlV1Y0hKdmRHOTBlWEJsTG1aaGFXd2dQU0F2THlCWVdGZ2diR1ZuWVdONVhHNVFjbTl0YVhObExuQnliM1J2ZEhsd1pWdGNJbU5oZEdOb1hDSmRJRDBnWm5WdVkzUnBiMjRnS0hKbGFtVmpkR1ZrS1NCN1hHNGdJQ0FnY21WMGRYSnVJSFJvYVhNdWRHaGxiaWgyYjJsa0lEQXNJSEpsYW1WamRHVmtLVHRjYm4wN1hHNWNiaThxS2x4dUlDb2dRWFIwWVdOb1pYTWdZU0JzYVhOMFpXNWxjaUIwYUdGMElHTmhiaUJ5WlhOd2IyNWtJSFJ2SUhCeWIyZHlaWE56SUc1dmRHbG1hV05oZEdsdmJuTWdabkp2YlNCaFhHNGdLaUJ3Y205dGFYTmxKM01nYjNKcFoybHVZWFJwYm1jZ1pHVm1aWEp5WldRdUlGUm9hWE1nYkdsemRHVnVaWElnY21WalpXbDJaWE1nZEdobElHVjRZV04wSUdGeVozVnRaVzUwYzF4dUlDb2djR0Z6YzJWa0lIUnZJR0JnWkdWbVpYSnlaV1F1Ym05MGFXWjVZR0F1WEc0Z0tpQkFjR0Z5WVcwZ2UwRnVlU3A5SUhCeWIyMXBjMlVnWm05eUlITnZiV1YwYUdsdVoxeHVJQ29nUUhCaGNtRnRJSHRHZFc1amRHbHZibjBnWTJGc2JHSmhZMnNnZEc4Z2NtVmpaV2wyWlNCaGJua2djSEp2WjNKbGMzTWdibTkwYVdacFkyRjBhVzl1YzF4dUlDb2dRSEpsZEhWeWJuTWdkR2hsSUdkcGRtVnVJSEJ5YjIxcGMyVXNJSFZ1WTJoaGJtZGxaRnh1SUNvdlhHNVJMbkJ5YjJkeVpYTnpJRDBnY0hKdlozSmxjM003WEc1bWRXNWpkR2x2YmlCd2NtOW5jbVZ6Y3lodlltcGxZM1FzSUhCeWIyZHlaWE56WldRcElIdGNiaUFnSUNCeVpYUjFjbTRnVVNodlltcGxZM1FwTG5Sb1pXNG9kbTlwWkNBd0xDQjJiMmxrSURBc0lIQnliMmR5WlhOelpXUXBPMXh1ZlZ4dVhHNVFjbTl0YVhObExuQnliM1J2ZEhsd1pTNXdjbTluY21WemN5QTlJR1oxYm1OMGFXOXVJQ2h3Y205bmNtVnpjMlZrS1NCN1hHNGdJQ0FnY21WMGRYSnVJSFJvYVhNdWRHaGxiaWgyYjJsa0lEQXNJSFp2YVdRZ01Dd2djSEp2WjNKbGMzTmxaQ2s3WEc1OU8xeHVYRzR2S2lwY2JpQXFJRkJ5YjNacFpHVnpJR0Z1SUc5d2NHOXlkSFZ1YVhSNUlIUnZJRzlpYzJWeWRtVWdkR2hsSUhObGRIUnNhVzVuSUc5bUlHRWdjSEp2YldselpTeGNiaUFxSUhKbFoyRnlaR3hsYzNNZ2IyWWdkMmhsZEdobGNpQjBhR1VnY0hKdmJXbHpaU0JwY3lCbWRXeG1hV3hzWldRZ2IzSWdjbVZxWldOMFpXUXVJQ0JHYjNKM1lYSmtjMXh1SUNvZ2RHaGxJSEpsYzI5c2RYUnBiMjRnZEc4Z2RHaGxJSEpsZEhWeWJtVmtJSEJ5YjIxcGMyVWdkMmhsYmlCMGFHVWdZMkZzYkdKaFkyc2dhWE1nWkc5dVpTNWNiaUFxSUZSb1pTQmpZV3hzWW1GamF5QmpZVzRnY21WMGRYSnVJR0VnY0hKdmJXbHpaU0IwYnlCa1pXWmxjaUJqYjIxd2JHVjBhVzl1TGx4dUlDb2dRSEJoY21GdElIdEJibmtxZlNCd2NtOXRhWE5sWEc0Z0tpQkFjR0Z5WVcwZ2UwWjFibU4wYVc5dWZTQmpZV3hzWW1GamF5QjBieUJ2WW5ObGNuWmxJSFJvWlNCeVpYTnZiSFYwYVc5dUlHOW1JSFJvWlNCbmFYWmxibHh1SUNvZ2NISnZiV2x6WlN3Z2RHRnJaWE1nYm04Z1lYSm5kVzFsYm5SekxseHVJQ29nUUhKbGRIVnlibk1nWVNCd2NtOXRhWE5sSUdadmNpQjBhR1VnY21WemIyeDFkR2x2YmlCdlppQjBhR1VnWjJsMlpXNGdjSEp2YldselpTQjNhR1Z1WEc0Z0tpQmdZR1pwYm1CZ0lHbHpJR1J2Ym1VdVhHNGdLaTljYmxFdVptbHVJRDBnTHk4Z1dGaFlJR3hsWjJGamVWeHVVVnRjSW1acGJtRnNiSGxjSWwwZ1BTQm1kVzVqZEdsdmJpQW9iMkpxWldOMExDQmpZV3hzWW1GamF5a2dlMXh1SUNBZ0lISmxkSFZ5YmlCUktHOWlhbVZqZENsYlhDSm1hVzVoYkd4NVhDSmRLR05oYkd4aVlXTnJLVHRjYm4wN1hHNWNibEJ5YjIxcGMyVXVjSEp2ZEc5MGVYQmxMbVpwYmlBOUlDOHZJRmhZV0NCc1pXZGhZM2xjYmxCeWIyMXBjMlV1Y0hKdmRHOTBlWEJsVzF3aVptbHVZV3hzZVZ3aVhTQTlJR1oxYm1OMGFXOXVJQ2hqWVd4c1ltRmpheWtnZTF4dUlDQWdJR05oYkd4aVlXTnJJRDBnVVNoallXeHNZbUZqYXlrN1hHNGdJQ0FnY21WMGRYSnVJSFJvYVhNdWRHaGxiaWhtZFc1amRHbHZiaUFvZG1Gc2RXVXBJSHRjYmlBZ0lDQWdJQ0FnY21WMGRYSnVJR05oYkd4aVlXTnJMbVpqWVd4c0tDa3VkR2hsYmlobWRXNWpkR2x2YmlBb0tTQjdYRzRnSUNBZ0lDQWdJQ0FnSUNCeVpYUjFjbTRnZG1Gc2RXVTdYRzRnSUNBZ0lDQWdJSDBwTzF4dUlDQWdJSDBzSUdaMWJtTjBhVzl1SUNoeVpXRnpiMjRwSUh0Y2JpQWdJQ0FnSUNBZ0x5OGdWRTlFVHlCaGRIUmxiWEIwSUhSdklISmxZM2xqYkdVZ2RHaGxJSEpsYW1WamRHbHZiaUIzYVhSb0lGd2lkR2hwYzF3aUxseHVJQ0FnSUNBZ0lDQnlaWFIxY200Z1kyRnNiR0poWTJzdVptTmhiR3dvS1M1MGFHVnVLR1oxYm1OMGFXOXVJQ2dwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJSFJvY205M0lISmxZWE52Ymp0Y2JpQWdJQ0FnSUNBZ2ZTazdYRzRnSUNBZ2ZTazdYRzU5TzF4dVhHNHZLaXBjYmlBcUlGUmxjbTFwYm1GMFpYTWdZU0JqYUdGcGJpQnZaaUJ3Y205dGFYTmxjeXdnWm05eVkybHVaeUJ5WldwbFkzUnBiMjV6SUhSdklHSmxYRzRnS2lCMGFISnZkMjRnWVhNZ1pYaGpaWEIwYVc5dWN5NWNiaUFxSUVCd1lYSmhiU0I3UVc1NUtuMGdjSEp2YldselpTQmhkQ0IwYUdVZ1pXNWtJRzltSUdFZ1kyaGhhVzRnYjJZZ2NISnZiV2x6WlhOY2JpQXFJRUJ5WlhSMWNtNXpJRzV2ZEdocGJtZGNiaUFxTDF4dVVTNWtiMjVsSUQwZ1puVnVZM1JwYjI0Z0tHOWlhbVZqZEN3Z1puVnNabWxzYkdWa0xDQnlaV3BsWTNSbFpDd2djSEp2WjNKbGMzTXBJSHRjYmlBZ0lDQnlaWFIxY200Z1VTaHZZbXBsWTNRcExtUnZibVVvWm5Wc1ptbHNiR1ZrTENCeVpXcGxZM1JsWkN3Z2NISnZaM0psYzNNcE8xeHVmVHRjYmx4dVVISnZiV2x6WlM1d2NtOTBiM1I1Y0dVdVpHOXVaU0E5SUdaMWJtTjBhVzl1SUNobWRXeG1hV3hzWldRc0lISmxhbVZqZEdWa0xDQndjbTluY21WemN5a2dlMXh1SUNBZ0lIWmhjaUJ2YmxWdWFHRnVaR3hsWkVWeWNtOXlJRDBnWm5WdVkzUnBiMjRnS0dWeWNtOXlLU0I3WEc0Z0lDQWdJQ0FnSUM4dklHWnZjbmRoY21RZ2RHOGdZU0JtZFhSMWNtVWdkSFZ5YmlCemJ5QjBhR0YwSUdCZ2QyaGxibUJnWEc0Z0lDQWdJQ0FnSUM4dklHUnZaWE1nYm05MElHTmhkR05vSUdsMElHRnVaQ0IwZFhKdUlHbDBJR2x1ZEc4Z1lTQnlaV3BsWTNScGIyNHVYRzRnSUNBZ0lDQWdJRkV1Ym1WNGRGUnBZMnNvWm5WdVkzUnBiMjRnS0NrZ2UxeHVJQ0FnSUNBZ0lDQWdJQ0FnYldGclpWTjBZV05yVkhKaFkyVk1iMjVuS0dWeWNtOXlMQ0J3Y205dGFYTmxLVHRjYmlBZ0lDQWdJQ0FnSUNBZ0lHbG1JQ2hSTG05dVpYSnliM0lwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCUkxtOXVaWEp5YjNJb1pYSnliM0lwTzF4dUlDQWdJQ0FnSUNBZ0lDQWdmU0JsYkhObElIdGNiaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQjBhSEp2ZHlCbGNuSnZjanRjYmlBZ0lDQWdJQ0FnSUNBZ0lIMWNiaUFnSUNBZ0lDQWdmU2s3WEc0Z0lDQWdmVHRjYmx4dUlDQWdJQzh2SUVGMmIybGtJSFZ1Ym1WalpYTnpZWEo1SUdCdVpYaDBWR2xqYTJCcGJtY2dkbWxoSUdGdUlIVnVibVZqWlhOellYSjVJR0IzYUdWdVlDNWNiaUFnSUNCMllYSWdjSEp2YldselpTQTlJR1oxYkdacGJHeGxaQ0I4ZkNCeVpXcGxZM1JsWkNCOGZDQndjbTluY21WemN5QS9YRzRnSUNBZ0lDQWdJSFJvYVhNdWRHaGxiaWhtZFd4bWFXeHNaV1FzSUhKbGFtVmpkR1ZrTENCd2NtOW5jbVZ6Y3lrZ09seHVJQ0FnSUNBZ0lDQjBhR2x6TzF4dVhHNGdJQ0FnYVdZZ0tIUjVjR1Z2WmlCd2NtOWpaWE56SUQwOVBTQmNJbTlpYW1WamRGd2lJQ1ltSUhCeWIyTmxjM01nSmlZZ2NISnZZMlZ6Y3k1a2IyMWhhVzRwSUh0Y2JpQWdJQ0FnSUNBZ2IyNVZibWhoYm1Sc1pXUkZjbkp2Y2lBOUlIQnliMk5sYzNNdVpHOXRZV2x1TG1KcGJtUW9iMjVWYm1oaGJtUnNaV1JGY25KdmNpazdYRzRnSUNBZ2ZWeHVYRzRnSUNBZ2NISnZiV2x6WlM1MGFHVnVLSFp2YVdRZ01Dd2diMjVWYm1oaGJtUnNaV1JGY25KdmNpazdYRzU5TzF4dVhHNHZLaXBjYmlBcUlFTmhkWE5sY3lCaElIQnliMjFwYzJVZ2RHOGdZbVVnY21WcVpXTjBaV1FnYVdZZ2FYUWdaRzlsY3lCdWIzUWdaMlYwSUdaMWJHWnBiR3hsWkNCaVpXWnZjbVZjYmlBcUlITnZiV1VnYldsc2JHbHpaV052Ym1SeklIUnBiV1VnYjNWMExseHVJQ29nUUhCaGNtRnRJSHRCYm5rcWZTQndjbTl0YVhObFhHNGdLaUJBY0dGeVlXMGdlMDUxYldKbGNuMGdiV2xzYkdselpXTnZibVJ6SUhScGJXVnZkWFJjYmlBcUlFQndZWEpoYlNCN1FXNTVLbjBnWTNWemRHOXRJR1Z5Y205eUlHMWxjM05oWjJVZ2IzSWdSWEp5YjNJZ2IySnFaV04wSUNodmNIUnBiMjVoYkNsY2JpQXFJRUJ5WlhSMWNtNXpJR0VnY0hKdmJXbHpaU0JtYjNJZ2RHaGxJSEpsYzI5c2RYUnBiMjRnYjJZZ2RHaGxJR2RwZG1WdUlIQnliMjFwYzJVZ2FXWWdhWFFnYVhOY2JpQXFJR1oxYkdacGJHeGxaQ0JpWldadmNtVWdkR2hsSUhScGJXVnZkWFFzSUc5MGFHVnlkMmx6WlNCeVpXcGxZM1JsWkM1Y2JpQXFMMXh1VVM1MGFXMWxiM1YwSUQwZ1puVnVZM1JwYjI0Z0tHOWlhbVZqZEN3Z2JYTXNJR1Z5Y205eUtTQjdYRzRnSUNBZ2NtVjBkWEp1SUZFb2IySnFaV04wS1M1MGFXMWxiM1YwS0cxekxDQmxjbkp2Y2lrN1hHNTlPMXh1WEc1UWNtOXRhWE5sTG5CeWIzUnZkSGx3WlM1MGFXMWxiM1YwSUQwZ1puVnVZM1JwYjI0Z0tHMXpMQ0JsY25KdmNpa2dlMXh1SUNBZ0lIWmhjaUJrWldabGNuSmxaQ0E5SUdSbFptVnlLQ2s3WEc0Z0lDQWdkbUZ5SUhScGJXVnZkWFJKWkNBOUlITmxkRlJwYldWdmRYUW9ablZ1WTNScGIyNGdLQ2tnZTF4dUlDQWdJQ0FnSUNCcFppQW9JV1Z5Y205eUlIeDhJRndpYzNSeWFXNW5YQ0lnUFQwOUlIUjVjR1Z2WmlCbGNuSnZjaWtnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdaWEp5YjNJZ1BTQnVaWGNnUlhKeWIzSW9aWEp5YjNJZ2ZId2dYQ0pVYVcxbFpDQnZkWFFnWVdaMFpYSWdYQ0lnS3lCdGN5QXJJRndpSUcxelhDSXBPMXh1SUNBZ0lDQWdJQ0FnSUNBZ1pYSnliM0l1WTI5a1pTQTlJRndpUlZSSlRVVkVUMVZVWENJN1hHNGdJQ0FnSUNBZ0lIMWNiaUFnSUNBZ0lDQWdaR1ZtWlhKeVpXUXVjbVZxWldOMEtHVnljbTl5S1R0Y2JpQWdJQ0I5TENCdGN5azdYRzVjYmlBZ0lDQjBhR2x6TG5Sb1pXNG9ablZ1WTNScGIyNGdLSFpoYkhWbEtTQjdYRzRnSUNBZ0lDQWdJR05zWldGeVZHbHRaVzkxZENoMGFXMWxiM1YwU1dRcE8xeHVJQ0FnSUNBZ0lDQmtaV1psY25KbFpDNXlaWE52YkhabEtIWmhiSFZsS1R0Y2JpQWdJQ0I5TENCbWRXNWpkR2x2YmlBb1pYaGpaWEIwYVc5dUtTQjdYRzRnSUNBZ0lDQWdJR05zWldGeVZHbHRaVzkxZENoMGFXMWxiM1YwU1dRcE8xeHVJQ0FnSUNBZ0lDQmtaV1psY25KbFpDNXlaV3BsWTNRb1pYaGpaWEIwYVc5dUtUdGNiaUFnSUNCOUxDQmtaV1psY25KbFpDNXViM1JwWm5rcE8xeHVYRzRnSUNBZ2NtVjBkWEp1SUdSbFptVnljbVZrTG5CeWIyMXBjMlU3WEc1OU8xeHVYRzR2S2lwY2JpQXFJRkpsZEhWeWJuTWdZU0J3Y205dGFYTmxJR1p2Y2lCMGFHVWdaMmwyWlc0Z2RtRnNkV1VnS0c5eUlIQnliMjFwYzJWa0lIWmhiSFZsS1N3Z2MyOXRaVnh1SUNvZ2JXbHNiR2x6WldOdmJtUnpJR0ZtZEdWeUlHbDBJSEpsYzI5c2RtVmtMaUJRWVhOelpYTWdjbVZxWldOMGFXOXVjeUJwYlcxbFpHbGhkR1ZzZVM1Y2JpQXFJRUJ3WVhKaGJTQjdRVzU1S24wZ2NISnZiV2x6WlZ4dUlDb2dRSEJoY21GdElIdE9kVzFpWlhKOUlHMXBiR3hwYzJWamIyNWtjMXh1SUNvZ1FISmxkSFZ5Ym5NZ1lTQndjbTl0YVhObElHWnZjaUIwYUdVZ2NtVnpiMngxZEdsdmJpQnZaaUIwYUdVZ1oybDJaVzRnY0hKdmJXbHpaU0JoWm5SbGNpQnRhV3hzYVhObFkyOXVaSE5jYmlBcUlIUnBiV1VnYUdGeklHVnNZWEJ6WldRZ2MybHVZMlVnZEdobElISmxjMjlzZFhScGIyNGdiMllnZEdobElHZHBkbVZ1SUhCeWIyMXBjMlV1WEc0Z0tpQkpaaUIwYUdVZ1oybDJaVzRnY0hKdmJXbHpaU0J5WldwbFkzUnpMQ0IwYUdGMElHbHpJSEJoYzNObFpDQnBiVzFsWkdsaGRHVnNlUzVjYmlBcUwxeHVVUzVrWld4aGVTQTlJR1oxYm1OMGFXOXVJQ2h2WW1wbFkzUXNJSFJwYldWdmRYUXBJSHRjYmlBZ0lDQnBaaUFvZEdsdFpXOTFkQ0E5UFQwZ2RtOXBaQ0F3S1NCN1hHNGdJQ0FnSUNBZ0lIUnBiV1Z2ZFhRZ1BTQnZZbXBsWTNRN1hHNGdJQ0FnSUNBZ0lHOWlhbVZqZENBOUlIWnZhV1FnTUR0Y2JpQWdJQ0I5WEc0Z0lDQWdjbVYwZFhKdUlGRW9iMkpxWldOMEtTNWtaV3hoZVNoMGFXMWxiM1YwS1R0Y2JuMDdYRzVjYmxCeWIyMXBjMlV1Y0hKdmRHOTBlWEJsTG1SbGJHRjVJRDBnWm5WdVkzUnBiMjRnS0hScGJXVnZkWFFwSUh0Y2JpQWdJQ0J5WlhSMWNtNGdkR2hwY3k1MGFHVnVLR1oxYm1OMGFXOXVJQ2gyWVd4MVpTa2dlMXh1SUNBZ0lDQWdJQ0IyWVhJZ1pHVm1aWEp5WldRZ1BTQmtaV1psY2lncE8xeHVJQ0FnSUNBZ0lDQnpaWFJVYVcxbGIzVjBLR1oxYm1OMGFXOXVJQ2dwSUh0Y2JpQWdJQ0FnSUNBZ0lDQWdJR1JsWm1WeWNtVmtMbkpsYzI5c2RtVW9kbUZzZFdVcE8xeHVJQ0FnSUNBZ0lDQjlMQ0IwYVcxbGIzVjBLVHRjYmlBZ0lDQWdJQ0FnY21WMGRYSnVJR1JsWm1WeWNtVmtMbkJ5YjIxcGMyVTdYRzRnSUNBZ2ZTazdYRzU5TzF4dVhHNHZLaXBjYmlBcUlGQmhjM05sY3lCaElHTnZiblJwYm5WaGRHbHZiaUIwYnlCaElFNXZaR1VnWm5WdVkzUnBiMjRzSUhkb2FXTm9JR2x6SUdOaGJHeGxaQ0IzYVhSb0lIUm9aU0JuYVhabGJseHVJQ29nWVhKbmRXMWxiblJ6SUhCeWIzWnBaR1ZrSUdGeklHRnVJR0Z5Y21GNUxDQmhibVFnY21WMGRYSnVjeUJoSUhCeWIyMXBjMlV1WEc0Z0tseHVJQ29nSUNBZ0lDQlJMbTVtWVhCd2JIa29SbE11Y21WaFpFWnBiR1VzSUZ0ZlgyWnBiR1Z1WVcxbFhTbGNiaUFxSUNBZ0lDQWdMblJvWlc0b1puVnVZM1JwYjI0Z0tHTnZiblJsYm5RcElIdGNiaUFxSUNBZ0lDQWdmU2xjYmlBcVhHNGdLaTljYmxFdWJtWmhjSEJzZVNBOUlHWjFibU4wYVc5dUlDaGpZV3hzWW1GamF5d2dZWEpuY3lrZ2UxeHVJQ0FnSUhKbGRIVnliaUJSS0dOaGJHeGlZV05yS1M1dVptRndjR3g1S0dGeVozTXBPMXh1ZlR0Y2JseHVVSEp2YldselpTNXdjbTkwYjNSNWNHVXVibVpoY0hCc2VTQTlJR1oxYm1OMGFXOXVJQ2hoY21kektTQjdYRzRnSUNBZ2RtRnlJR1JsWm1WeWNtVmtJRDBnWkdWbVpYSW9LVHRjYmlBZ0lDQjJZWElnYm05a1pVRnlaM01nUFNCaGNuSmhlVjl6YkdsalpTaGhjbWR6S1R0Y2JpQWdJQ0J1YjJSbFFYSm5jeTV3ZFhOb0tHUmxabVZ5Y21Wa0xtMWhhMlZPYjJSbFVtVnpiMngyWlhJb0tTazdYRzRnSUNBZ2RHaHBjeTVtWVhCd2JIa29ibTlrWlVGeVozTXBMbVpoYVd3b1pHVm1aWEp5WldRdWNtVnFaV04wS1R0Y2JpQWdJQ0J5WlhSMWNtNGdaR1ZtWlhKeVpXUXVjSEp2YldselpUdGNibjA3WEc1Y2JpOHFLbHh1SUNvZ1VHRnpjMlZ6SUdFZ1kyOXVkR2x1ZFdGMGFXOXVJSFJ2SUdFZ1RtOWtaU0JtZFc1amRHbHZiaXdnZDJocFkyZ2dhWE1nWTJGc2JHVmtJSGRwZEdnZ2RHaGxJR2RwZG1WdVhHNGdLaUJoY21kMWJXVnVkSE1nY0hKdmRtbGtaV1FnYVc1a2FYWnBaSFZoYkd4NUxDQmhibVFnY21WMGRYSnVjeUJoSUhCeWIyMXBjMlV1WEc0Z0tpQkFaWGhoYlhCc1pWeHVJQ29nVVM1dVptTmhiR3dvUmxNdWNtVmhaRVpwYkdVc0lGOWZabWxzWlc1aGJXVXBYRzRnS2lBdWRHaGxiaWhtZFc1amRHbHZiaUFvWTI5dWRHVnVkQ2tnZTF4dUlDb2dmU2xjYmlBcVhHNGdLaTljYmxFdWJtWmpZV3hzSUQwZ1puVnVZM1JwYjI0Z0tHTmhiR3hpWVdOcklDOHFMaTR1WVhKbmN5b3ZLU0I3WEc0Z0lDQWdkbUZ5SUdGeVozTWdQU0JoY25KaGVWOXpiR2xqWlNoaGNtZDFiV1Z1ZEhNc0lERXBPMXh1SUNBZ0lISmxkSFZ5YmlCUktHTmhiR3hpWVdOcktTNXVabUZ3Y0d4NUtHRnlaM01wTzF4dWZUdGNibHh1VUhKdmJXbHpaUzV3Y205MGIzUjVjR1V1Ym1aallXeHNJRDBnWm5WdVkzUnBiMjRnS0M4cUxpNHVZWEpuY3lvdktTQjdYRzRnSUNBZ2RtRnlJRzV2WkdWQmNtZHpJRDBnWVhKeVlYbGZjMnhwWTJVb1lYSm5kVzFsYm5SektUdGNiaUFnSUNCMllYSWdaR1ZtWlhKeVpXUWdQU0JrWldabGNpZ3BPMXh1SUNBZ0lHNXZaR1ZCY21kekxuQjFjMmdvWkdWbVpYSnlaV1F1YldGclpVNXZaR1ZTWlhOdmJIWmxjaWdwS1R0Y2JpQWdJQ0IwYUdsekxtWmhjSEJzZVNodWIyUmxRWEpuY3lrdVptRnBiQ2hrWldabGNuSmxaQzV5WldwbFkzUXBPMXh1SUNBZ0lISmxkSFZ5YmlCa1pXWmxjbkpsWkM1d2NtOXRhWE5sTzF4dWZUdGNibHh1THlvcVhHNGdLaUJYY21Gd2N5QmhJRTV2WkdWS1V5QmpiMjUwYVc1MVlYUnBiMjRnY0dGemMybHVaeUJtZFc1amRHbHZiaUJoYm1RZ2NtVjBkWEp1Y3lCaGJpQmxjWFZwZG1Gc1pXNTBYRzRnS2lCMlpYSnphVzl1SUhSb1lYUWdjbVYwZFhKdWN5QmhJSEJ5YjIxcGMyVXVYRzRnS2lCQVpYaGhiWEJzWlZ4dUlDb2dVUzV1Wm1KcGJtUW9SbE11Y21WaFpFWnBiR1VzSUY5ZlptbHNaVzVoYldVcEtGd2lkWFJtTFRoY0lpbGNiaUFxSUM1MGFHVnVLR052Ym5OdmJHVXViRzluS1Z4dUlDb2dMbVJ2Ym1Vb0tWeHVJQ292WEc1UkxtNW1ZbWx1WkNBOVhHNVJMbVJsYm05a1pXbG1lU0E5SUdaMWJtTjBhVzl1SUNoallXeHNZbUZqYXlBdktpNHVMbUZ5WjNNcUx5a2dlMXh1SUNBZ0lIWmhjaUJpWVhObFFYSm5jeUE5SUdGeWNtRjVYM05zYVdObEtHRnlaM1Z0Wlc1MGN5d2dNU2s3WEc0Z0lDQWdjbVYwZFhKdUlHWjFibU4wYVc5dUlDZ3BJSHRjYmlBZ0lDQWdJQ0FnZG1GeUlHNXZaR1ZCY21keklEMGdZbUZ6WlVGeVozTXVZMjl1WTJGMEtHRnljbUY1WDNOc2FXTmxLR0Z5WjNWdFpXNTBjeWtwTzF4dUlDQWdJQ0FnSUNCMllYSWdaR1ZtWlhKeVpXUWdQU0JrWldabGNpZ3BPMXh1SUNBZ0lDQWdJQ0J1YjJSbFFYSm5jeTV3ZFhOb0tHUmxabVZ5Y21Wa0xtMWhhMlZPYjJSbFVtVnpiMngyWlhJb0tTazdYRzRnSUNBZ0lDQWdJRkVvWTJGc2JHSmhZMnNwTG1aaGNIQnNlU2h1YjJSbFFYSm5jeWt1Wm1GcGJDaGtaV1psY25KbFpDNXlaV3BsWTNRcE8xeHVJQ0FnSUNBZ0lDQnlaWFIxY200Z1pHVm1aWEp5WldRdWNISnZiV2x6WlR0Y2JpQWdJQ0I5TzF4dWZUdGNibHh1VUhKdmJXbHpaUzV3Y205MGIzUjVjR1V1Ym1aaWFXNWtJRDFjYmxCeWIyMXBjMlV1Y0hKdmRHOTBlWEJsTG1SbGJtOWtaV2xtZVNBOUlHWjFibU4wYVc5dUlDZ3ZLaTR1TG1GeVozTXFMeWtnZTF4dUlDQWdJSFpoY2lCaGNtZHpJRDBnWVhKeVlYbGZjMnhwWTJVb1lYSm5kVzFsYm5SektUdGNiaUFnSUNCaGNtZHpMblZ1YzJocFpuUW9kR2hwY3lrN1hHNGdJQ0FnY21WMGRYSnVJRkV1WkdWdWIyUmxhV1o1TG1Gd2NHeDVLSFp2YVdRZ01Dd2dZWEpuY3lrN1hHNTlPMXh1WEc1UkxtNWlhVzVrSUQwZ1puVnVZM1JwYjI0Z0tHTmhiR3hpWVdOckxDQjBhR2x6Y0NBdktpNHVMbUZ5WjNNcUx5a2dlMXh1SUNBZ0lIWmhjaUJpWVhObFFYSm5jeUE5SUdGeWNtRjVYM05zYVdObEtHRnlaM1Z0Wlc1MGN5d2dNaWs3WEc0Z0lDQWdjbVYwZFhKdUlHWjFibU4wYVc5dUlDZ3BJSHRjYmlBZ0lDQWdJQ0FnZG1GeUlHNXZaR1ZCY21keklEMGdZbUZ6WlVGeVozTXVZMjl1WTJGMEtHRnljbUY1WDNOc2FXTmxLR0Z5WjNWdFpXNTBjeWtwTzF4dUlDQWdJQ0FnSUNCMllYSWdaR1ZtWlhKeVpXUWdQU0JrWldabGNpZ3BPMXh1SUNBZ0lDQWdJQ0J1YjJSbFFYSm5jeTV3ZFhOb0tHUmxabVZ5Y21Wa0xtMWhhMlZPYjJSbFVtVnpiMngyWlhJb0tTazdYRzRnSUNBZ0lDQWdJR1oxYm1OMGFXOXVJR0p2ZFc1a0tDa2dlMXh1SUNBZ0lDQWdJQ0FnSUNBZ2NtVjBkWEp1SUdOaGJHeGlZV05yTG1Gd2NHeDVLSFJvYVhOd0xDQmhjbWQxYldWdWRITXBPMXh1SUNBZ0lDQWdJQ0I5WEc0Z0lDQWdJQ0FnSUZFb1ltOTFibVFwTG1aaGNIQnNlU2h1YjJSbFFYSm5jeWt1Wm1GcGJDaGtaV1psY25KbFpDNXlaV3BsWTNRcE8xeHVJQ0FnSUNBZ0lDQnlaWFIxY200Z1pHVm1aWEp5WldRdWNISnZiV2x6WlR0Y2JpQWdJQ0I5TzF4dWZUdGNibHh1VUhKdmJXbHpaUzV3Y205MGIzUjVjR1V1Ym1KcGJtUWdQU0JtZFc1amRHbHZiaUFvTHlwMGFHbHpjQ3dnTGk0dVlYSm5jeW92S1NCN1hHNGdJQ0FnZG1GeUlHRnlaM01nUFNCaGNuSmhlVjl6YkdsalpTaGhjbWQxYldWdWRITXNJREFwTzF4dUlDQWdJR0Z5WjNNdWRXNXphR2xtZENoMGFHbHpLVHRjYmlBZ0lDQnlaWFIxY200Z1VTNXVZbWx1WkM1aGNIQnNlU2gyYjJsa0lEQXNJR0Z5WjNNcE8xeHVmVHRjYmx4dUx5b3FYRzRnS2lCRFlXeHNjeUJoSUcxbGRHaHZaQ0J2WmlCaElFNXZaR1V0YzNSNWJHVWdiMkpxWldOMElIUm9ZWFFnWVdOalpYQjBjeUJoSUU1dlpHVXRjM1I1YkdWY2JpQXFJR05oYkd4aVlXTnJJSGRwZEdnZ1lTQm5hWFpsYmlCaGNuSmhlU0J2WmlCaGNtZDFiV1Z1ZEhNc0lIQnNkWE1nWVNCd2NtOTJhV1JsWkNCallXeHNZbUZqYXk1Y2JpQXFJRUJ3WVhKaGJTQnZZbXBsWTNRZ1lXNGdiMkpxWldOMElIUm9ZWFFnYUdGeklIUm9aU0J1WVcxbFpDQnRaWFJvYjJSY2JpQXFJRUJ3WVhKaGJTQjdVM1J5YVc1bmZTQnVZVzFsSUc1aGJXVWdiMllnZEdobElHMWxkR2h2WkNCdlppQnZZbXBsWTNSY2JpQXFJRUJ3WVhKaGJTQjdRWEp5WVhsOUlHRnlaM01nWVhKbmRXMWxiblJ6SUhSdklIQmhjM01nZEc4Z2RHaGxJRzFsZEdodlpEc2dkR2hsSUdOaGJHeGlZV05yWEc0Z0tpQjNhV3hzSUdKbElIQnliM1pwWkdWa0lHSjVJRkVnWVc1a0lHRndjR1Z1WkdWa0lIUnZJSFJvWlhObElHRnlaM1Z0Wlc1MGN5NWNiaUFxSUVCeVpYUjFjbTV6SUdFZ2NISnZiV2x6WlNCbWIzSWdkR2hsSUhaaGJIVmxJRzl5SUdWeWNtOXlYRzRnS2k5Y2JsRXVibTFoY0hCc2VTQTlJQzh2SUZoWVdDQkJjeUJ3Y205d2IzTmxaQ0JpZVNCY0lsSmxaSE5oYm1SeWIxd2lYRzVSTG01d2IzTjBJRDBnWm5WdVkzUnBiMjRnS0c5aWFtVmpkQ3dnYm1GdFpTd2dZWEpuY3lrZ2UxeHVJQ0FnSUhKbGRIVnliaUJSS0c5aWFtVmpkQ2t1Ym5CdmMzUW9ibUZ0WlN3Z1lYSm5jeWs3WEc1OU8xeHVYRzVRY205dGFYTmxMbkJ5YjNSdmRIbHdaUzV1YldGd2NHeDVJRDBnTHk4Z1dGaFlJRUZ6SUhCeWIzQnZjMlZrSUdKNUlGd2lVbVZrYzJGdVpISnZYQ0pjYmxCeWIyMXBjMlV1Y0hKdmRHOTBlWEJsTG01d2IzTjBJRDBnWm5WdVkzUnBiMjRnS0c1aGJXVXNJR0Z5WjNNcElIdGNiaUFnSUNCMllYSWdibTlrWlVGeVozTWdQU0JoY25KaGVWOXpiR2xqWlNoaGNtZHpJSHg4SUZ0ZEtUdGNiaUFnSUNCMllYSWdaR1ZtWlhKeVpXUWdQU0JrWldabGNpZ3BPMXh1SUNBZ0lHNXZaR1ZCY21kekxuQjFjMmdvWkdWbVpYSnlaV1F1YldGclpVNXZaR1ZTWlhOdmJIWmxjaWdwS1R0Y2JpQWdJQ0IwYUdsekxtUnBjM0JoZEdOb0tGd2ljRzl6ZEZ3aUxDQmJibUZ0WlN3Z2JtOWtaVUZ5WjNOZEtTNW1ZV2xzS0dSbFptVnljbVZrTG5KbGFtVmpkQ2s3WEc0Z0lDQWdjbVYwZFhKdUlHUmxabVZ5Y21Wa0xuQnliMjFwYzJVN1hHNTlPMXh1WEc0dktpcGNiaUFxSUVOaGJHeHpJR0VnYldWMGFHOWtJRzltSUdFZ1RtOWtaUzF6ZEhsc1pTQnZZbXBsWTNRZ2RHaGhkQ0JoWTJObGNIUnpJR0VnVG05a1pTMXpkSGxzWlZ4dUlDb2dZMkZzYkdKaFkyc3NJR1p2Y25kaGNtUnBibWNnZEdobElHZHBkbVZ1SUhaaGNtbGhaR2xqSUdGeVozVnRaVzUwY3l3Z2NHeDFjeUJoSUhCeWIzWnBaR1ZrWEc0Z0tpQmpZV3hzWW1GamF5QmhjbWQxYldWdWRDNWNiaUFxSUVCd1lYSmhiU0J2WW1wbFkzUWdZVzRnYjJKcVpXTjBJSFJvWVhRZ2FHRnpJSFJvWlNCdVlXMWxaQ0J0WlhSb2IyUmNiaUFxSUVCd1lYSmhiU0I3VTNSeWFXNW5mU0J1WVcxbElHNWhiV1VnYjJZZ2RHaGxJRzFsZEdodlpDQnZaaUJ2WW1wbFkzUmNiaUFxSUVCd1lYSmhiU0F1TGk1aGNtZHpJR0Z5WjNWdFpXNTBjeUIwYnlCd1lYTnpJSFJ2SUhSb1pTQnRaWFJvYjJRN0lIUm9aU0JqWVd4c1ltRmpheUIzYVd4c1hHNGdLaUJpWlNCd2NtOTJhV1JsWkNCaWVTQlJJR0Z1WkNCaGNIQmxibVJsWkNCMGJ5QjBhR1Z6WlNCaGNtZDFiV1Z1ZEhNdVhHNGdLaUJBY21WMGRYSnVjeUJoSUhCeWIyMXBjMlVnWm05eUlIUm9aU0IyWVd4MVpTQnZjaUJsY25KdmNseHVJQ292WEc1UkxtNXpaVzVrSUQwZ0x5OGdXRmhZSUVKaGMyVmtJRzl1SUUxaGNtc2dUV2xzYkdWeUozTWdjSEp2Y0c5elpXUWdYQ0p6Wlc1a1hDSmNibEV1Ym0xallXeHNJRDBnTHk4Z1dGaFlJRUpoYzJWa0lHOXVJRndpVW1Wa2MyRnVaSEp2SjNOY0lpQndjbTl3YjNOaGJGeHVVUzV1YVc1MmIydGxJRDBnWm5WdVkzUnBiMjRnS0c5aWFtVmpkQ3dnYm1GdFpTQXZLaTR1TG1GeVozTXFMeWtnZTF4dUlDQWdJSFpoY2lCdWIyUmxRWEpuY3lBOUlHRnljbUY1WDNOc2FXTmxLR0Z5WjNWdFpXNTBjeXdnTWlrN1hHNGdJQ0FnZG1GeUlHUmxabVZ5Y21Wa0lEMGdaR1ZtWlhJb0tUdGNiaUFnSUNCdWIyUmxRWEpuY3k1d2RYTm9LR1JsWm1WeWNtVmtMbTFoYTJWT2IyUmxVbVZ6YjJ4MlpYSW9LU2s3WEc0Z0lDQWdVU2h2WW1wbFkzUXBMbVJwYzNCaGRHTm9LRndpY0c5emRGd2lMQ0JiYm1GdFpTd2dibTlrWlVGeVozTmRLUzVtWVdsc0tHUmxabVZ5Y21Wa0xuSmxhbVZqZENrN1hHNGdJQ0FnY21WMGRYSnVJR1JsWm1WeWNtVmtMbkJ5YjIxcGMyVTdYRzU5TzF4dVhHNVFjbTl0YVhObExuQnliM1J2ZEhsd1pTNXVjMlZ1WkNBOUlDOHZJRmhZV0NCQ1lYTmxaQ0J2YmlCTllYSnJJRTFwYkd4bGNpZHpJSEJ5YjNCdmMyVmtJRndpYzJWdVpGd2lYRzVRY205dGFYTmxMbkJ5YjNSdmRIbHdaUzV1YldOaGJHd2dQU0F2THlCWVdGZ2dRbUZ6WldRZ2IyNGdYQ0pTWldSellXNWtjbThuYzF3aUlIQnliM0J2YzJGc1hHNVFjbTl0YVhObExuQnliM1J2ZEhsd1pTNXVhVzUyYjJ0bElEMGdablZ1WTNScGIyNGdLRzVoYldVZ0x5b3VMaTVoY21kektpOHBJSHRjYmlBZ0lDQjJZWElnYm05a1pVRnlaM01nUFNCaGNuSmhlVjl6YkdsalpTaGhjbWQxYldWdWRITXNJREVwTzF4dUlDQWdJSFpoY2lCa1pXWmxjbkpsWkNBOUlHUmxabVZ5S0NrN1hHNGdJQ0FnYm05a1pVRnlaM011Y0hWemFDaGtaV1psY25KbFpDNXRZV3RsVG05a1pWSmxjMjlzZG1WeUtDa3BPMXh1SUNBZ0lIUm9hWE11WkdsemNHRjBZMmdvWENKd2IzTjBYQ0lzSUZ0dVlXMWxMQ0J1YjJSbFFYSm5jMTBwTG1aaGFXd29aR1ZtWlhKeVpXUXVjbVZxWldOMEtUdGNiaUFnSUNCeVpYUjFjbTRnWkdWbVpYSnlaV1F1Y0hKdmJXbHpaVHRjYm4wN1hHNWNiaThxS2x4dUlDb2dTV1lnWVNCbWRXNWpkR2x2YmlCM2IzVnNaQ0JzYVd0bElIUnZJSE4xY0hCdmNuUWdZbTkwYUNCT2IyUmxJR052Ym5ScGJuVmhkR2x2Ymkxd1lYTnphVzVuTFhOMGVXeGxJR0Z1WkZ4dUlDb2djSEp2YldselpTMXlaWFIxY201cGJtY3RjM1I1YkdVc0lHbDBJR05oYmlCbGJtUWdhWFJ6SUdsdWRHVnlibUZzSUhCeWIyMXBjMlVnWTJoaGFXNGdkMmwwYUZ4dUlDb2dZRzV2WkdWcFpua29ibTlrWldKaFkyc3BZQ3dnWm05eWQyRnlaR2x1WnlCMGFHVWdiM0IwYVc5dVlXd2dibTlrWldKaFkyc2dZWEpuZFcxbGJuUXVJQ0JKWmlCMGFHVWdkWE5sY2x4dUlDb2daV3hsWTNSeklIUnZJSFZ6WlNCaElHNXZaR1ZpWVdOckxDQjBhR1VnY21WemRXeDBJSGRwYkd3Z1ltVWdjMlZ1ZENCMGFHVnlaUzRnSUVsbUlIUm9aWGtnWkc4Z2JtOTBYRzRnS2lCd1lYTnpJR0VnYm05a1pXSmhZMnNzSUhSb1pYa2dkMmxzYkNCeVpXTmxhWFpsSUhSb1pTQnlaWE4xYkhRZ2NISnZiV2x6WlM1Y2JpQXFJRUJ3WVhKaGJTQnZZbXBsWTNRZ1lTQnlaWE4xYkhRZ0tHOXlJR0VnY0hKdmJXbHpaU0JtYjNJZ1lTQnlaWE4xYkhRcFhHNGdLaUJBY0dGeVlXMGdlMFoxYm1OMGFXOXVmU0J1YjJSbFltRmpheUJoSUU1dlpHVXVhbk10YzNSNWJHVWdZMkZzYkdKaFkydGNiaUFxSUVCeVpYUjFjbTV6SUdWcGRHaGxjaUIwYUdVZ2NISnZiV2x6WlNCdmNpQnViM1JvYVc1blhHNGdLaTljYmxFdWJtOWtaV2xtZVNBOUlHNXZaR1ZwWm5rN1hHNW1kVzVqZEdsdmJpQnViMlJsYVdaNUtHOWlhbVZqZEN3Z2JtOWtaV0poWTJzcElIdGNiaUFnSUNCeVpYUjFjbTRnVVNodlltcGxZM1FwTG01dlpHVnBabmtvYm05a1pXSmhZMnNwTzF4dWZWeHVYRzVRY205dGFYTmxMbkJ5YjNSdmRIbHdaUzV1YjJSbGFXWjVJRDBnWm5WdVkzUnBiMjRnS0c1dlpHVmlZV05yS1NCN1hHNGdJQ0FnYVdZZ0tHNXZaR1ZpWVdOcktTQjdYRzRnSUNBZ0lDQWdJSFJvYVhNdWRHaGxiaWhtZFc1amRHbHZiaUFvZG1Gc2RXVXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lGRXVibVY0ZEZScFkyc29ablZ1WTNScGIyNGdLQ2tnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUc1dlpHVmlZV05yS0c1MWJHd3NJSFpoYkhWbEtUdGNiaUFnSUNBZ0lDQWdJQ0FnSUgwcE8xeHVJQ0FnSUNBZ0lDQjlMQ0JtZFc1amRHbHZiaUFvWlhKeWIzSXBJSHRjYmlBZ0lDQWdJQ0FnSUNBZ0lGRXVibVY0ZEZScFkyc29ablZ1WTNScGIyNGdLQ2tnZTF4dUlDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUc1dlpHVmlZV05yS0dWeWNtOXlLVHRjYmlBZ0lDQWdJQ0FnSUNBZ0lIMHBPMXh1SUNBZ0lDQWdJQ0I5S1R0Y2JpQWdJQ0I5SUdWc2MyVWdlMXh1SUNBZ0lDQWdJQ0J5WlhSMWNtNGdkR2hwY3p0Y2JpQWdJQ0I5WEc1OU8xeHVYRzVSTG01dlEyOXVabXhwWTNRZ1BTQm1kVzVqZEdsdmJpZ3BJSHRjYmlBZ0lDQjBhSEp2ZHlCdVpYY2dSWEp5YjNJb1hDSlJMbTV2UTI5dVpteHBZM1FnYjI1c2VTQjNiM0pyY3lCM2FHVnVJRkVnYVhNZ2RYTmxaQ0JoY3lCaElHZHNiMkpoYkZ3aUtUdGNibjA3WEc1Y2JpOHZJRUZzYkNCamIyUmxJR0psWm05eVpTQjBhR2x6SUhCdmFXNTBJSGRwYkd3Z1ltVWdabWxzZEdWeVpXUWdabkp2YlNCemRHRmpheUIwY21GalpYTXVYRzUyWVhJZ2NVVnVaR2x1WjB4cGJtVWdQU0JqWVhCMGRYSmxUR2x1WlNncE8xeHVYRzV5WlhSMWNtNGdVVHRjYmx4dWZTazdYRzRpWFgwPSIsIi8qKlxuICogUm9vdCByZWZlcmVuY2UgZm9yIGlmcmFtZXMuXG4gKi9cblxudmFyIHJvb3Q7XG5pZiAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcpIHsgLy8gQnJvd3NlciB3aW5kb3dcbiAgcm9vdCA9IHdpbmRvdztcbn0gZWxzZSBpZiAodHlwZW9mIHNlbGYgIT09ICd1bmRlZmluZWQnKSB7IC8vIFdlYiBXb3JrZXJcbiAgcm9vdCA9IHNlbGY7XG59IGVsc2UgeyAvLyBPdGhlciBlbnZpcm9ubWVudHNcbiAgY29uc29sZS53YXJuKFwiVXNpbmcgYnJvd3Nlci1vbmx5IHZlcnNpb24gb2Ygc3VwZXJhZ2VudCBpbiBub24tYnJvd3NlciBlbnZpcm9ubWVudFwiKTtcbiAgcm9vdCA9IHRoaXM7XG59XG5cbnZhciBFbWl0dGVyID0gcmVxdWlyZSgnZW1pdHRlcicpO1xudmFyIHJlcXVlc3RCYXNlID0gcmVxdWlyZSgnLi9yZXF1ZXN0LWJhc2UnKTtcbnZhciBpc09iamVjdCA9IHJlcXVpcmUoJy4vaXMtb2JqZWN0Jyk7XG5cbi8qKlxuICogTm9vcC5cbiAqL1xuXG5mdW5jdGlvbiBub29wKCl7fTtcblxuLyoqXG4gKiBFeHBvc2UgYHJlcXVlc3RgLlxuICovXG5cbnZhciByZXF1ZXN0ID0gbW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL3JlcXVlc3QnKS5iaW5kKG51bGwsIFJlcXVlc3QpO1xuXG4vKipcbiAqIERldGVybWluZSBYSFIuXG4gKi9cblxucmVxdWVzdC5nZXRYSFIgPSBmdW5jdGlvbiAoKSB7XG4gIGlmIChyb290LlhNTEh0dHBSZXF1ZXN0XG4gICAgICAmJiAoIXJvb3QubG9jYXRpb24gfHwgJ2ZpbGU6JyAhPSByb290LmxvY2F0aW9uLnByb3RvY29sXG4gICAgICAgICAgfHwgIXJvb3QuQWN0aXZlWE9iamVjdCkpIHtcbiAgICByZXR1cm4gbmV3IFhNTEh0dHBSZXF1ZXN0O1xuICB9IGVsc2Uge1xuICAgIHRyeSB7IHJldHVybiBuZXcgQWN0aXZlWE9iamVjdCgnTWljcm9zb2Z0LlhNTEhUVFAnKTsgfSBjYXRjaChlKSB7fVxuICAgIHRyeSB7IHJldHVybiBuZXcgQWN0aXZlWE9iamVjdCgnTXN4bWwyLlhNTEhUVFAuNi4wJyk7IH0gY2F0Y2goZSkge31cbiAgICB0cnkgeyByZXR1cm4gbmV3IEFjdGl2ZVhPYmplY3QoJ01zeG1sMi5YTUxIVFRQLjMuMCcpOyB9IGNhdGNoKGUpIHt9XG4gICAgdHJ5IHsgcmV0dXJuIG5ldyBBY3RpdmVYT2JqZWN0KCdNc3htbDIuWE1MSFRUUCcpOyB9IGNhdGNoKGUpIHt9XG4gIH1cbiAgdGhyb3cgRXJyb3IoXCJCcm93c2VyLW9ubHkgdmVyaXNvbiBvZiBzdXBlcmFnZW50IGNvdWxkIG5vdCBmaW5kIFhIUlwiKTtcbn07XG5cbi8qKlxuICogUmVtb3ZlcyBsZWFkaW5nIGFuZCB0cmFpbGluZyB3aGl0ZXNwYWNlLCBhZGRlZCB0byBzdXBwb3J0IElFLlxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBzXG4gKiBAcmV0dXJuIHtTdHJpbmd9XG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG52YXIgdHJpbSA9ICcnLnRyaW1cbiAgPyBmdW5jdGlvbihzKSB7IHJldHVybiBzLnRyaW0oKTsgfVxuICA6IGZ1bmN0aW9uKHMpIHsgcmV0dXJuIHMucmVwbGFjZSgvKF5cXHMqfFxccyokKS9nLCAnJyk7IH07XG5cbi8qKlxuICogU2VyaWFsaXplIHRoZSBnaXZlbiBgb2JqYC5cbiAqXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqXG4gKiBAcmV0dXJuIHtTdHJpbmd9XG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5mdW5jdGlvbiBzZXJpYWxpemUob2JqKSB7XG4gIGlmICghaXNPYmplY3Qob2JqKSkgcmV0dXJuIG9iajtcbiAgdmFyIHBhaXJzID0gW107XG4gIGZvciAodmFyIGtleSBpbiBvYmopIHtcbiAgICBwdXNoRW5jb2RlZEtleVZhbHVlUGFpcihwYWlycywga2V5LCBvYmpba2V5XSk7XG4gIH1cbiAgcmV0dXJuIHBhaXJzLmpvaW4oJyYnKTtcbn1cblxuLyoqXG4gKiBIZWxwcyAnc2VyaWFsaXplJyB3aXRoIHNlcmlhbGl6aW5nIGFycmF5cy5cbiAqIE11dGF0ZXMgdGhlIHBhaXJzIGFycmF5LlxuICpcbiAqIEBwYXJhbSB7QXJyYXl9IHBhaXJzXG4gKiBAcGFyYW0ge1N0cmluZ30ga2V5XG4gKiBAcGFyYW0ge01peGVkfSB2YWxcbiAqL1xuXG5mdW5jdGlvbiBwdXNoRW5jb2RlZEtleVZhbHVlUGFpcihwYWlycywga2V5LCB2YWwpIHtcbiAgaWYgKHZhbCAhPSBudWxsKSB7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkodmFsKSkge1xuICAgICAgdmFsLmZvckVhY2goZnVuY3Rpb24odikge1xuICAgICAgICBwdXNoRW5jb2RlZEtleVZhbHVlUGFpcihwYWlycywga2V5LCB2KTtcbiAgICAgIH0pO1xuICAgIH0gZWxzZSBpZiAoaXNPYmplY3QodmFsKSkge1xuICAgICAgZm9yKHZhciBzdWJrZXkgaW4gdmFsKSB7XG4gICAgICAgIHB1c2hFbmNvZGVkS2V5VmFsdWVQYWlyKHBhaXJzLCBrZXkgKyAnWycgKyBzdWJrZXkgKyAnXScsIHZhbFtzdWJrZXldKTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgcGFpcnMucHVzaChlbmNvZGVVUklDb21wb25lbnQoa2V5KVxuICAgICAgICArICc9JyArIGVuY29kZVVSSUNvbXBvbmVudCh2YWwpKTtcbiAgICB9XG4gIH0gZWxzZSBpZiAodmFsID09PSBudWxsKSB7XG4gICAgcGFpcnMucHVzaChlbmNvZGVVUklDb21wb25lbnQoa2V5KSk7XG4gIH1cbn1cblxuLyoqXG4gKiBFeHBvc2Ugc2VyaWFsaXphdGlvbiBtZXRob2QuXG4gKi9cblxuIHJlcXVlc3Quc2VyaWFsaXplT2JqZWN0ID0gc2VyaWFsaXplO1xuXG4gLyoqXG4gICogUGFyc2UgdGhlIGdpdmVuIHgtd3d3LWZvcm0tdXJsZW5jb2RlZCBgc3RyYC5cbiAgKlxuICAqIEBwYXJhbSB7U3RyaW5nfSBzdHJcbiAgKiBAcmV0dXJuIHtPYmplY3R9XG4gICogQGFwaSBwcml2YXRlXG4gICovXG5cbmZ1bmN0aW9uIHBhcnNlU3RyaW5nKHN0cikge1xuICB2YXIgb2JqID0ge307XG4gIHZhciBwYWlycyA9IHN0ci5zcGxpdCgnJicpO1xuICB2YXIgcGFpcjtcbiAgdmFyIHBvcztcblxuICBmb3IgKHZhciBpID0gMCwgbGVuID0gcGFpcnMubGVuZ3RoOyBpIDwgbGVuOyArK2kpIHtcbiAgICBwYWlyID0gcGFpcnNbaV07XG4gICAgcG9zID0gcGFpci5pbmRleE9mKCc9Jyk7XG4gICAgaWYgKHBvcyA9PSAtMSkge1xuICAgICAgb2JqW2RlY29kZVVSSUNvbXBvbmVudChwYWlyKV0gPSAnJztcbiAgICB9IGVsc2Uge1xuICAgICAgb2JqW2RlY29kZVVSSUNvbXBvbmVudChwYWlyLnNsaWNlKDAsIHBvcykpXSA9XG4gICAgICAgIGRlY29kZVVSSUNvbXBvbmVudChwYWlyLnNsaWNlKHBvcyArIDEpKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gb2JqO1xufVxuXG4vKipcbiAqIEV4cG9zZSBwYXJzZXIuXG4gKi9cblxucmVxdWVzdC5wYXJzZVN0cmluZyA9IHBhcnNlU3RyaW5nO1xuXG4vKipcbiAqIERlZmF1bHQgTUlNRSB0eXBlIG1hcC5cbiAqXG4gKiAgICAgc3VwZXJhZ2VudC50eXBlcy54bWwgPSAnYXBwbGljYXRpb24veG1sJztcbiAqXG4gKi9cblxucmVxdWVzdC50eXBlcyA9IHtcbiAgaHRtbDogJ3RleHQvaHRtbCcsXG4gIGpzb246ICdhcHBsaWNhdGlvbi9qc29uJyxcbiAgeG1sOiAnYXBwbGljYXRpb24veG1sJyxcbiAgdXJsZW5jb2RlZDogJ2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCcsXG4gICdmb3JtJzogJ2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCcsXG4gICdmb3JtLWRhdGEnOiAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ1xufTtcblxuLyoqXG4gKiBEZWZhdWx0IHNlcmlhbGl6YXRpb24gbWFwLlxuICpcbiAqICAgICBzdXBlcmFnZW50LnNlcmlhbGl6ZVsnYXBwbGljYXRpb24veG1sJ10gPSBmdW5jdGlvbihvYmope1xuICogICAgICAgcmV0dXJuICdnZW5lcmF0ZWQgeG1sIGhlcmUnO1xuICogICAgIH07XG4gKlxuICovXG5cbiByZXF1ZXN0LnNlcmlhbGl6ZSA9IHtcbiAgICdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQnOiBzZXJpYWxpemUsXG4gICAnYXBwbGljYXRpb24vanNvbic6IEpTT04uc3RyaW5naWZ5XG4gfTtcblxuIC8qKlxuICAqIERlZmF1bHQgcGFyc2Vycy5cbiAgKlxuICAqICAgICBzdXBlcmFnZW50LnBhcnNlWydhcHBsaWNhdGlvbi94bWwnXSA9IGZ1bmN0aW9uKHN0cil7XG4gICogICAgICAgcmV0dXJuIHsgb2JqZWN0IHBhcnNlZCBmcm9tIHN0ciB9O1xuICAqICAgICB9O1xuICAqXG4gICovXG5cbnJlcXVlc3QucGFyc2UgPSB7XG4gICdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQnOiBwYXJzZVN0cmluZyxcbiAgJ2FwcGxpY2F0aW9uL2pzb24nOiBKU09OLnBhcnNlXG59O1xuXG4vKipcbiAqIFBhcnNlIHRoZSBnaXZlbiBoZWFkZXIgYHN0cmAgaW50b1xuICogYW4gb2JqZWN0IGNvbnRhaW5pbmcgdGhlIG1hcHBlZCBmaWVsZHMuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IHN0clxuICogQHJldHVybiB7T2JqZWN0fVxuICogQGFwaSBwcml2YXRlXG4gKi9cblxuZnVuY3Rpb24gcGFyc2VIZWFkZXIoc3RyKSB7XG4gIHZhciBsaW5lcyA9IHN0ci5zcGxpdCgvXFxyP1xcbi8pO1xuICB2YXIgZmllbGRzID0ge307XG4gIHZhciBpbmRleDtcbiAgdmFyIGxpbmU7XG4gIHZhciBmaWVsZDtcbiAgdmFyIHZhbDtcblxuICBsaW5lcy5wb3AoKTsgLy8gdHJhaWxpbmcgQ1JMRlxuXG4gIGZvciAodmFyIGkgPSAwLCBsZW4gPSBsaW5lcy5sZW5ndGg7IGkgPCBsZW47ICsraSkge1xuICAgIGxpbmUgPSBsaW5lc1tpXTtcbiAgICBpbmRleCA9IGxpbmUuaW5kZXhPZignOicpO1xuICAgIGZpZWxkID0gbGluZS5zbGljZSgwLCBpbmRleCkudG9Mb3dlckNhc2UoKTtcbiAgICB2YWwgPSB0cmltKGxpbmUuc2xpY2UoaW5kZXggKyAxKSk7XG4gICAgZmllbGRzW2ZpZWxkXSA9IHZhbDtcbiAgfVxuXG4gIHJldHVybiBmaWVsZHM7XG59XG5cbi8qKlxuICogQ2hlY2sgaWYgYG1pbWVgIGlzIGpzb24gb3IgaGFzICtqc29uIHN0cnVjdHVyZWQgc3ludGF4IHN1ZmZpeC5cbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gbWltZVxuICogQHJldHVybiB7Qm9vbGVhbn1cbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cbmZ1bmN0aW9uIGlzSlNPTihtaW1lKSB7XG4gIHJldHVybiAvW1xcLytdanNvblxcYi8udGVzdChtaW1lKTtcbn1cblxuLyoqXG4gKiBSZXR1cm4gdGhlIG1pbWUgdHlwZSBmb3IgdGhlIGdpdmVuIGBzdHJgLlxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBzdHJcbiAqIEByZXR1cm4ge1N0cmluZ31cbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cbmZ1bmN0aW9uIHR5cGUoc3RyKXtcbiAgcmV0dXJuIHN0ci5zcGxpdCgvICo7ICovKS5zaGlmdCgpO1xufTtcblxuLyoqXG4gKiBSZXR1cm4gaGVhZGVyIGZpZWxkIHBhcmFtZXRlcnMuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IHN0clxuICogQHJldHVybiB7T2JqZWN0fVxuICogQGFwaSBwcml2YXRlXG4gKi9cblxuZnVuY3Rpb24gcGFyYW1zKHN0cil7XG4gIHJldHVybiBzdHIuc3BsaXQoLyAqOyAqLykucmVkdWNlKGZ1bmN0aW9uKG9iaiwgc3RyKXtcbiAgICB2YXIgcGFydHMgPSBzdHIuc3BsaXQoLyAqPSAqLyksXG4gICAgICAgIGtleSA9IHBhcnRzLnNoaWZ0KCksXG4gICAgICAgIHZhbCA9IHBhcnRzLnNoaWZ0KCk7XG5cbiAgICBpZiAoa2V5ICYmIHZhbCkgb2JqW2tleV0gPSB2YWw7XG4gICAgcmV0dXJuIG9iajtcbiAgfSwge30pO1xufTtcblxuLyoqXG4gKiBJbml0aWFsaXplIGEgbmV3IGBSZXNwb25zZWAgd2l0aCB0aGUgZ2l2ZW4gYHhocmAuXG4gKlxuICogIC0gc2V0IGZsYWdzICgub2ssIC5lcnJvciwgZXRjKVxuICogIC0gcGFyc2UgaGVhZGVyXG4gKlxuICogRXhhbXBsZXM6XG4gKlxuICogIEFsaWFzaW5nIGBzdXBlcmFnZW50YCBhcyBgcmVxdWVzdGAgaXMgbmljZTpcbiAqXG4gKiAgICAgIHJlcXVlc3QgPSBzdXBlcmFnZW50O1xuICpcbiAqICBXZSBjYW4gdXNlIHRoZSBwcm9taXNlLWxpa2UgQVBJLCBvciBwYXNzIGNhbGxiYWNrczpcbiAqXG4gKiAgICAgIHJlcXVlc3QuZ2V0KCcvJykuZW5kKGZ1bmN0aW9uKHJlcyl7fSk7XG4gKiAgICAgIHJlcXVlc3QuZ2V0KCcvJywgZnVuY3Rpb24ocmVzKXt9KTtcbiAqXG4gKiAgU2VuZGluZyBkYXRhIGNhbiBiZSBjaGFpbmVkOlxuICpcbiAqICAgICAgcmVxdWVzdFxuICogICAgICAgIC5wb3N0KCcvdXNlcicpXG4gKiAgICAgICAgLnNlbmQoeyBuYW1lOiAndGonIH0pXG4gKiAgICAgICAgLmVuZChmdW5jdGlvbihyZXMpe30pO1xuICpcbiAqICBPciBwYXNzZWQgdG8gYC5zZW5kKClgOlxuICpcbiAqICAgICAgcmVxdWVzdFxuICogICAgICAgIC5wb3N0KCcvdXNlcicpXG4gKiAgICAgICAgLnNlbmQoeyBuYW1lOiAndGonIH0sIGZ1bmN0aW9uKHJlcyl7fSk7XG4gKlxuICogIE9yIHBhc3NlZCB0byBgLnBvc3QoKWA6XG4gKlxuICogICAgICByZXF1ZXN0XG4gKiAgICAgICAgLnBvc3QoJy91c2VyJywgeyBuYW1lOiAndGonIH0pXG4gKiAgICAgICAgLmVuZChmdW5jdGlvbihyZXMpe30pO1xuICpcbiAqIE9yIGZ1cnRoZXIgcmVkdWNlZCB0byBhIHNpbmdsZSBjYWxsIGZvciBzaW1wbGUgY2FzZXM6XG4gKlxuICogICAgICByZXF1ZXN0XG4gKiAgICAgICAgLnBvc3QoJy91c2VyJywgeyBuYW1lOiAndGonIH0sIGZ1bmN0aW9uKHJlcyl7fSk7XG4gKlxuICogQHBhcmFtIHtYTUxIVFRQUmVxdWVzdH0geGhyXG4gKiBAcGFyYW0ge09iamVjdH0gb3B0aW9uc1xuICogQGFwaSBwcml2YXRlXG4gKi9cblxuZnVuY3Rpb24gUmVzcG9uc2UocmVxLCBvcHRpb25zKSB7XG4gIG9wdGlvbnMgPSBvcHRpb25zIHx8IHt9O1xuICB0aGlzLnJlcSA9IHJlcTtcbiAgdGhpcy54aHIgPSB0aGlzLnJlcS54aHI7XG4gIC8vIHJlc3BvbnNlVGV4dCBpcyBhY2Nlc3NpYmxlIG9ubHkgaWYgcmVzcG9uc2VUeXBlIGlzICcnIG9yICd0ZXh0JyBhbmQgb24gb2xkZXIgYnJvd3NlcnNcbiAgdGhpcy50ZXh0ID0gKCh0aGlzLnJlcS5tZXRob2QgIT0nSEVBRCcgJiYgKHRoaXMueGhyLnJlc3BvbnNlVHlwZSA9PT0gJycgfHwgdGhpcy54aHIucmVzcG9uc2VUeXBlID09PSAndGV4dCcpKSB8fCB0eXBlb2YgdGhpcy54aHIucmVzcG9uc2VUeXBlID09PSAndW5kZWZpbmVkJylcbiAgICAgPyB0aGlzLnhoci5yZXNwb25zZVRleHRcbiAgICAgOiBudWxsO1xuICB0aGlzLnN0YXR1c1RleHQgPSB0aGlzLnJlcS54aHIuc3RhdHVzVGV4dDtcbiAgdGhpcy5fc2V0U3RhdHVzUHJvcGVydGllcyh0aGlzLnhoci5zdGF0dXMpO1xuICB0aGlzLmhlYWRlciA9IHRoaXMuaGVhZGVycyA9IHBhcnNlSGVhZGVyKHRoaXMueGhyLmdldEFsbFJlc3BvbnNlSGVhZGVycygpKTtcbiAgLy8gZ2V0QWxsUmVzcG9uc2VIZWFkZXJzIHNvbWV0aW1lcyBmYWxzZWx5IHJldHVybnMgXCJcIiBmb3IgQ09SUyByZXF1ZXN0cywgYnV0XG4gIC8vIGdldFJlc3BvbnNlSGVhZGVyIHN0aWxsIHdvcmtzLiBzbyB3ZSBnZXQgY29udGVudC10eXBlIGV2ZW4gaWYgZ2V0dGluZ1xuICAvLyBvdGhlciBoZWFkZXJzIGZhaWxzLlxuICB0aGlzLmhlYWRlclsnY29udGVudC10eXBlJ10gPSB0aGlzLnhoci5nZXRSZXNwb25zZUhlYWRlcignY29udGVudC10eXBlJyk7XG4gIHRoaXMuX3NldEhlYWRlclByb3BlcnRpZXModGhpcy5oZWFkZXIpO1xuICB0aGlzLmJvZHkgPSB0aGlzLnJlcS5tZXRob2QgIT0gJ0hFQUQnXG4gICAgPyB0aGlzLl9wYXJzZUJvZHkodGhpcy50ZXh0ID8gdGhpcy50ZXh0IDogdGhpcy54aHIucmVzcG9uc2UpXG4gICAgOiBudWxsO1xufVxuXG4vKipcbiAqIEdldCBjYXNlLWluc2Vuc2l0aXZlIGBmaWVsZGAgdmFsdWUuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IGZpZWxkXG4gKiBAcmV0dXJuIHtTdHJpbmd9XG4gKiBAYXBpIHB1YmxpY1xuICovXG5cblJlc3BvbnNlLnByb3RvdHlwZS5nZXQgPSBmdW5jdGlvbihmaWVsZCl7XG4gIHJldHVybiB0aGlzLmhlYWRlcltmaWVsZC50b0xvd2VyQ2FzZSgpXTtcbn07XG5cbi8qKlxuICogU2V0IGhlYWRlciByZWxhdGVkIHByb3BlcnRpZXM6XG4gKlxuICogICAtIGAudHlwZWAgdGhlIGNvbnRlbnQgdHlwZSB3aXRob3V0IHBhcmFtc1xuICpcbiAqIEEgcmVzcG9uc2Ugb2YgXCJDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLThcIlxuICogd2lsbCBwcm92aWRlIHlvdSB3aXRoIGEgYC50eXBlYCBvZiBcInRleHQvcGxhaW5cIi5cbiAqXG4gKiBAcGFyYW0ge09iamVjdH0gaGVhZGVyXG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5SZXNwb25zZS5wcm90b3R5cGUuX3NldEhlYWRlclByb3BlcnRpZXMgPSBmdW5jdGlvbihoZWFkZXIpe1xuICAvLyBjb250ZW50LXR5cGVcbiAgdmFyIGN0ID0gdGhpcy5oZWFkZXJbJ2NvbnRlbnQtdHlwZSddIHx8ICcnO1xuICB0aGlzLnR5cGUgPSB0eXBlKGN0KTtcblxuICAvLyBwYXJhbXNcbiAgdmFyIG9iaiA9IHBhcmFtcyhjdCk7XG4gIGZvciAodmFyIGtleSBpbiBvYmopIHRoaXNba2V5XSA9IG9ialtrZXldO1xufTtcblxuLyoqXG4gKiBQYXJzZSB0aGUgZ2l2ZW4gYm9keSBgc3RyYC5cbiAqXG4gKiBVc2VkIGZvciBhdXRvLXBhcnNpbmcgb2YgYm9kaWVzLiBQYXJzZXJzXG4gKiBhcmUgZGVmaW5lZCBvbiB0aGUgYHN1cGVyYWdlbnQucGFyc2VgIG9iamVjdC5cbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gc3RyXG4gKiBAcmV0dXJuIHtNaXhlZH1cbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cblJlc3BvbnNlLnByb3RvdHlwZS5fcGFyc2VCb2R5ID0gZnVuY3Rpb24oc3RyKXtcbiAgdmFyIHBhcnNlID0gcmVxdWVzdC5wYXJzZVt0aGlzLnR5cGVdO1xuICBpZiAoIXBhcnNlICYmIGlzSlNPTih0aGlzLnR5cGUpKSB7XG4gICAgcGFyc2UgPSByZXF1ZXN0LnBhcnNlWydhcHBsaWNhdGlvbi9qc29uJ107XG4gIH1cbiAgcmV0dXJuIHBhcnNlICYmIHN0ciAmJiAoc3RyLmxlbmd0aCB8fCBzdHIgaW5zdGFuY2VvZiBPYmplY3QpXG4gICAgPyBwYXJzZShzdHIpXG4gICAgOiBudWxsO1xufTtcblxuLyoqXG4gKiBTZXQgZmxhZ3Mgc3VjaCBhcyBgLm9rYCBiYXNlZCBvbiBgc3RhdHVzYC5cbiAqXG4gKiBGb3IgZXhhbXBsZSBhIDJ4eCByZXNwb25zZSB3aWxsIGdpdmUgeW91IGEgYC5va2Agb2YgX190cnVlX19cbiAqIHdoZXJlYXMgNXh4IHdpbGwgYmUgX19mYWxzZV9fIGFuZCBgLmVycm9yYCB3aWxsIGJlIF9fdHJ1ZV9fLiBUaGVcbiAqIGAuY2xpZW50RXJyb3JgIGFuZCBgLnNlcnZlckVycm9yYCBhcmUgYWxzbyBhdmFpbGFibGUgdG8gYmUgbW9yZVxuICogc3BlY2lmaWMsIGFuZCBgLnN0YXR1c1R5cGVgIGlzIHRoZSBjbGFzcyBvZiBlcnJvciByYW5naW5nIGZyb20gMS4uNVxuICogc29tZXRpbWVzIHVzZWZ1bCBmb3IgbWFwcGluZyByZXNwb25kIGNvbG9ycyBldGMuXG4gKlxuICogXCJzdWdhclwiIHByb3BlcnRpZXMgYXJlIGFsc28gZGVmaW5lZCBmb3IgY29tbW9uIGNhc2VzLiBDdXJyZW50bHkgcHJvdmlkaW5nOlxuICpcbiAqICAgLSAubm9Db250ZW50XG4gKiAgIC0gLmJhZFJlcXVlc3RcbiAqICAgLSAudW5hdXRob3JpemVkXG4gKiAgIC0gLm5vdEFjY2VwdGFibGVcbiAqICAgLSAubm90Rm91bmRcbiAqXG4gKiBAcGFyYW0ge051bWJlcn0gc3RhdHVzXG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5SZXNwb25zZS5wcm90b3R5cGUuX3NldFN0YXR1c1Byb3BlcnRpZXMgPSBmdW5jdGlvbihzdGF0dXMpe1xuICAvLyBoYW5kbGUgSUU5IGJ1ZzogaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8xMDA0Njk3Mi9tc2llLXJldHVybnMtc3RhdHVzLWNvZGUtb2YtMTIyMy1mb3ItYWpheC1yZXF1ZXN0XG4gIGlmIChzdGF0dXMgPT09IDEyMjMpIHtcbiAgICBzdGF0dXMgPSAyMDQ7XG4gIH1cblxuICB2YXIgdHlwZSA9IHN0YXR1cyAvIDEwMCB8IDA7XG5cbiAgLy8gc3RhdHVzIC8gY2xhc3NcbiAgdGhpcy5zdGF0dXMgPSB0aGlzLnN0YXR1c0NvZGUgPSBzdGF0dXM7XG4gIHRoaXMuc3RhdHVzVHlwZSA9IHR5cGU7XG5cbiAgLy8gYmFzaWNzXG4gIHRoaXMuaW5mbyA9IDEgPT0gdHlwZTtcbiAgdGhpcy5vayA9IDIgPT0gdHlwZTtcbiAgdGhpcy5jbGllbnRFcnJvciA9IDQgPT0gdHlwZTtcbiAgdGhpcy5zZXJ2ZXJFcnJvciA9IDUgPT0gdHlwZTtcbiAgdGhpcy5lcnJvciA9ICg0ID09IHR5cGUgfHwgNSA9PSB0eXBlKVxuICAgID8gdGhpcy50b0Vycm9yKClcbiAgICA6IGZhbHNlO1xuXG4gIC8vIHN1Z2FyXG4gIHRoaXMuYWNjZXB0ZWQgPSAyMDIgPT0gc3RhdHVzO1xuICB0aGlzLm5vQ29udGVudCA9IDIwNCA9PSBzdGF0dXM7XG4gIHRoaXMuYmFkUmVxdWVzdCA9IDQwMCA9PSBzdGF0dXM7XG4gIHRoaXMudW5hdXRob3JpemVkID0gNDAxID09IHN0YXR1cztcbiAgdGhpcy5ub3RBY2NlcHRhYmxlID0gNDA2ID09IHN0YXR1cztcbiAgdGhpcy5ub3RGb3VuZCA9IDQwNCA9PSBzdGF0dXM7XG4gIHRoaXMuZm9yYmlkZGVuID0gNDAzID09IHN0YXR1cztcbn07XG5cbi8qKlxuICogUmV0dXJuIGFuIGBFcnJvcmAgcmVwcmVzZW50YXRpdmUgb2YgdGhpcyByZXNwb25zZS5cbiAqXG4gKiBAcmV0dXJuIHtFcnJvcn1cbiAqIEBhcGkgcHVibGljXG4gKi9cblxuUmVzcG9uc2UucHJvdG90eXBlLnRvRXJyb3IgPSBmdW5jdGlvbigpe1xuICB2YXIgcmVxID0gdGhpcy5yZXE7XG4gIHZhciBtZXRob2QgPSByZXEubWV0aG9kO1xuICB2YXIgdXJsID0gcmVxLnVybDtcblxuICB2YXIgbXNnID0gJ2Nhbm5vdCAnICsgbWV0aG9kICsgJyAnICsgdXJsICsgJyAoJyArIHRoaXMuc3RhdHVzICsgJyknO1xuICB2YXIgZXJyID0gbmV3IEVycm9yKG1zZyk7XG4gIGVyci5zdGF0dXMgPSB0aGlzLnN0YXR1cztcbiAgZXJyLm1ldGhvZCA9IG1ldGhvZDtcbiAgZXJyLnVybCA9IHVybDtcblxuICByZXR1cm4gZXJyO1xufTtcblxuLyoqXG4gKiBFeHBvc2UgYFJlc3BvbnNlYC5cbiAqL1xuXG5yZXF1ZXN0LlJlc3BvbnNlID0gUmVzcG9uc2U7XG5cbi8qKlxuICogSW5pdGlhbGl6ZSBhIG5ldyBgUmVxdWVzdGAgd2l0aCB0aGUgZ2l2ZW4gYG1ldGhvZGAgYW5kIGB1cmxgLlxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBtZXRob2RcbiAqIEBwYXJhbSB7U3RyaW5nfSB1cmxcbiAqIEBhcGkgcHVibGljXG4gKi9cblxuZnVuY3Rpb24gUmVxdWVzdChtZXRob2QsIHVybCkge1xuICB2YXIgc2VsZiA9IHRoaXM7XG4gIHRoaXMuX3F1ZXJ5ID0gdGhpcy5fcXVlcnkgfHwgW107XG4gIHRoaXMubWV0aG9kID0gbWV0aG9kO1xuICB0aGlzLnVybCA9IHVybDtcbiAgdGhpcy5oZWFkZXIgPSB7fTsgLy8gcHJlc2VydmVzIGhlYWRlciBuYW1lIGNhc2VcbiAgdGhpcy5faGVhZGVyID0ge307IC8vIGNvZXJjZXMgaGVhZGVyIG5hbWVzIHRvIGxvd2VyY2FzZVxuICB0aGlzLm9uKCdlbmQnLCBmdW5jdGlvbigpe1xuICAgIHZhciBlcnIgPSBudWxsO1xuICAgIHZhciByZXMgPSBudWxsO1xuXG4gICAgdHJ5IHtcbiAgICAgIHJlcyA9IG5ldyBSZXNwb25zZShzZWxmKTtcbiAgICB9IGNhdGNoKGUpIHtcbiAgICAgIGVyciA9IG5ldyBFcnJvcignUGFyc2VyIGlzIHVuYWJsZSB0byBwYXJzZSB0aGUgcmVzcG9uc2UnKTtcbiAgICAgIGVyci5wYXJzZSA9IHRydWU7XG4gICAgICBlcnIub3JpZ2luYWwgPSBlO1xuICAgICAgLy8gaXNzdWUgIzY3NTogcmV0dXJuIHRoZSByYXcgcmVzcG9uc2UgaWYgdGhlIHJlc3BvbnNlIHBhcnNpbmcgZmFpbHNcbiAgICAgIGVyci5yYXdSZXNwb25zZSA9IHNlbGYueGhyICYmIHNlbGYueGhyLnJlc3BvbnNlVGV4dCA/IHNlbGYueGhyLnJlc3BvbnNlVGV4dCA6IG51bGw7XG4gICAgICAvLyBpc3N1ZSAjODc2OiByZXR1cm4gdGhlIGh0dHAgc3RhdHVzIGNvZGUgaWYgdGhlIHJlc3BvbnNlIHBhcnNpbmcgZmFpbHNcbiAgICAgIGVyci5zdGF0dXNDb2RlID0gc2VsZi54aHIgJiYgc2VsZi54aHIuc3RhdHVzID8gc2VsZi54aHIuc3RhdHVzIDogbnVsbDtcbiAgICAgIHJldHVybiBzZWxmLmNhbGxiYWNrKGVycik7XG4gICAgfVxuXG4gICAgc2VsZi5lbWl0KCdyZXNwb25zZScsIHJlcyk7XG5cbiAgICB2YXIgbmV3X2VycjtcbiAgICB0cnkge1xuICAgICAgaWYgKHJlcy5zdGF0dXMgPCAyMDAgfHwgcmVzLnN0YXR1cyA+PSAzMDApIHtcbiAgICAgICAgbmV3X2VyciA9IG5ldyBFcnJvcihyZXMuc3RhdHVzVGV4dCB8fCAnVW5zdWNjZXNzZnVsIEhUVFAgcmVzcG9uc2UnKTtcbiAgICAgICAgbmV3X2Vyci5vcmlnaW5hbCA9IGVycjtcbiAgICAgICAgbmV3X2Vyci5yZXNwb25zZSA9IHJlcztcbiAgICAgICAgbmV3X2Vyci5zdGF0dXMgPSByZXMuc3RhdHVzO1xuICAgICAgfVxuICAgIH0gY2F0Y2goZSkge1xuICAgICAgbmV3X2VyciA9IGU7IC8vICM5ODUgdG91Y2hpbmcgcmVzIG1heSBjYXVzZSBJTlZBTElEX1NUQVRFX0VSUiBvbiBvbGQgQW5kcm9pZFxuICAgIH1cblxuICAgIC8vICMxMDAwIGRvbid0IGNhdGNoIGVycm9ycyBmcm9tIHRoZSBjYWxsYmFjayB0byBhdm9pZCBkb3VibGUgY2FsbGluZyBpdFxuICAgIGlmIChuZXdfZXJyKSB7XG4gICAgICBzZWxmLmNhbGxiYWNrKG5ld19lcnIsIHJlcyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHNlbGYuY2FsbGJhY2sobnVsbCwgcmVzKTtcbiAgICB9XG4gIH0pO1xufVxuXG4vKipcbiAqIE1peGluIGBFbWl0dGVyYCBhbmQgYHJlcXVlc3RCYXNlYC5cbiAqL1xuXG5FbWl0dGVyKFJlcXVlc3QucHJvdG90eXBlKTtcbmZvciAodmFyIGtleSBpbiByZXF1ZXN0QmFzZSkge1xuICBSZXF1ZXN0LnByb3RvdHlwZVtrZXldID0gcmVxdWVzdEJhc2Vba2V5XTtcbn1cblxuLyoqXG4gKiBTZXQgQ29udGVudC1UeXBlIHRvIGB0eXBlYCwgbWFwcGluZyB2YWx1ZXMgZnJvbSBgcmVxdWVzdC50eXBlc2AuXG4gKlxuICogRXhhbXBsZXM6XG4gKlxuICogICAgICBzdXBlcmFnZW50LnR5cGVzLnhtbCA9ICdhcHBsaWNhdGlvbi94bWwnO1xuICpcbiAqICAgICAgcmVxdWVzdC5wb3N0KCcvJylcbiAqICAgICAgICAudHlwZSgneG1sJylcbiAqICAgICAgICAuc2VuZCh4bWxzdHJpbmcpXG4gKiAgICAgICAgLmVuZChjYWxsYmFjayk7XG4gKlxuICogICAgICByZXF1ZXN0LnBvc3QoJy8nKVxuICogICAgICAgIC50eXBlKCdhcHBsaWNhdGlvbi94bWwnKVxuICogICAgICAgIC5zZW5kKHhtbHN0cmluZylcbiAqICAgICAgICAuZW5kKGNhbGxiYWNrKTtcbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gdHlwZVxuICogQHJldHVybiB7UmVxdWVzdH0gZm9yIGNoYWluaW5nXG4gKiBAYXBpIHB1YmxpY1xuICovXG5cblJlcXVlc3QucHJvdG90eXBlLnR5cGUgPSBmdW5jdGlvbih0eXBlKXtcbiAgdGhpcy5zZXQoJ0NvbnRlbnQtVHlwZScsIHJlcXVlc3QudHlwZXNbdHlwZV0gfHwgdHlwZSk7XG4gIHJldHVybiB0aGlzO1xufTtcblxuLyoqXG4gKiBTZXQgcmVzcG9uc2VUeXBlIHRvIGB2YWxgLiBQcmVzZW50bHkgdmFsaWQgcmVzcG9uc2VUeXBlcyBhcmUgJ2Jsb2InIGFuZFxuICogJ2FycmF5YnVmZmVyJy5cbiAqXG4gKiBFeGFtcGxlczpcbiAqXG4gKiAgICAgIHJlcS5nZXQoJy8nKVxuICogICAgICAgIC5yZXNwb25zZVR5cGUoJ2Jsb2InKVxuICogICAgICAgIC5lbmQoY2FsbGJhY2spO1xuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSB2YWxcbiAqIEByZXR1cm4ge1JlcXVlc3R9IGZvciBjaGFpbmluZ1xuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5SZXF1ZXN0LnByb3RvdHlwZS5yZXNwb25zZVR5cGUgPSBmdW5jdGlvbih2YWwpe1xuICB0aGlzLl9yZXNwb25zZVR5cGUgPSB2YWw7XG4gIHJldHVybiB0aGlzO1xufTtcblxuLyoqXG4gKiBTZXQgQWNjZXB0IHRvIGB0eXBlYCwgbWFwcGluZyB2YWx1ZXMgZnJvbSBgcmVxdWVzdC50eXBlc2AuXG4gKlxuICogRXhhbXBsZXM6XG4gKlxuICogICAgICBzdXBlcmFnZW50LnR5cGVzLmpzb24gPSAnYXBwbGljYXRpb24vanNvbic7XG4gKlxuICogICAgICByZXF1ZXN0LmdldCgnL2FnZW50JylcbiAqICAgICAgICAuYWNjZXB0KCdqc29uJylcbiAqICAgICAgICAuZW5kKGNhbGxiYWNrKTtcbiAqXG4gKiAgICAgIHJlcXVlc3QuZ2V0KCcvYWdlbnQnKVxuICogICAgICAgIC5hY2NlcHQoJ2FwcGxpY2F0aW9uL2pzb24nKVxuICogICAgICAgIC5lbmQoY2FsbGJhY2spO1xuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBhY2NlcHRcbiAqIEByZXR1cm4ge1JlcXVlc3R9IGZvciBjaGFpbmluZ1xuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5SZXF1ZXN0LnByb3RvdHlwZS5hY2NlcHQgPSBmdW5jdGlvbih0eXBlKXtcbiAgdGhpcy5zZXQoJ0FjY2VwdCcsIHJlcXVlc3QudHlwZXNbdHlwZV0gfHwgdHlwZSk7XG4gIHJldHVybiB0aGlzO1xufTtcblxuLyoqXG4gKiBTZXQgQXV0aG9yaXphdGlvbiBmaWVsZCB2YWx1ZSB3aXRoIGB1c2VyYCBhbmQgYHBhc3NgLlxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSB1c2VyXG4gKiBAcGFyYW0ge1N0cmluZ30gcGFzc1xuICogQHBhcmFtIHtPYmplY3R9IG9wdGlvbnMgd2l0aCAndHlwZScgcHJvcGVydHkgJ2F1dG8nIG9yICdiYXNpYycgKGRlZmF1bHQgJ2Jhc2ljJylcbiAqIEByZXR1cm4ge1JlcXVlc3R9IGZvciBjaGFpbmluZ1xuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5SZXF1ZXN0LnByb3RvdHlwZS5hdXRoID0gZnVuY3Rpb24odXNlciwgcGFzcywgb3B0aW9ucyl7XG4gIGlmICghb3B0aW9ucykge1xuICAgIG9wdGlvbnMgPSB7XG4gICAgICB0eXBlOiAnYmFzaWMnXG4gICAgfVxuICB9XG5cbiAgc3dpdGNoIChvcHRpb25zLnR5cGUpIHtcbiAgICBjYXNlICdiYXNpYyc6XG4gICAgICB2YXIgc3RyID0gYnRvYSh1c2VyICsgJzonICsgcGFzcyk7XG4gICAgICB0aGlzLnNldCgnQXV0aG9yaXphdGlvbicsICdCYXNpYyAnICsgc3RyKTtcbiAgICBicmVhaztcblxuICAgIGNhc2UgJ2F1dG8nOlxuICAgICAgdGhpcy51c2VybmFtZSA9IHVzZXI7XG4gICAgICB0aGlzLnBhc3N3b3JkID0gcGFzcztcbiAgICBicmVhaztcbiAgfVxuICByZXR1cm4gdGhpcztcbn07XG5cbi8qKlxuKiBBZGQgcXVlcnktc3RyaW5nIGB2YWxgLlxuKlxuKiBFeGFtcGxlczpcbipcbiogICByZXF1ZXN0LmdldCgnL3Nob2VzJylcbiogICAgIC5xdWVyeSgnc2l6ZT0xMCcpXG4qICAgICAucXVlcnkoeyBjb2xvcjogJ2JsdWUnIH0pXG4qXG4qIEBwYXJhbSB7T2JqZWN0fFN0cmluZ30gdmFsXG4qIEByZXR1cm4ge1JlcXVlc3R9IGZvciBjaGFpbmluZ1xuKiBAYXBpIHB1YmxpY1xuKi9cblxuUmVxdWVzdC5wcm90b3R5cGUucXVlcnkgPSBmdW5jdGlvbih2YWwpe1xuICBpZiAoJ3N0cmluZycgIT0gdHlwZW9mIHZhbCkgdmFsID0gc2VyaWFsaXplKHZhbCk7XG4gIGlmICh2YWwpIHRoaXMuX3F1ZXJ5LnB1c2godmFsKTtcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vKipcbiAqIFF1ZXVlIHRoZSBnaXZlbiBgZmlsZWAgYXMgYW4gYXR0YWNobWVudCB0byB0aGUgc3BlY2lmaWVkIGBmaWVsZGAsXG4gKiB3aXRoIG9wdGlvbmFsIGBmaWxlbmFtZWAuXG4gKlxuICogYGBgIGpzXG4gKiByZXF1ZXN0LnBvc3QoJy91cGxvYWQnKVxuICogICAuYXR0YWNoKCdjb250ZW50JywgbmV3IEJsb2IoWyc8YSBpZD1cImFcIj48YiBpZD1cImJcIj5oZXkhPC9iPjwvYT4nXSwgeyB0eXBlOiBcInRleHQvaHRtbFwifSkpXG4gKiAgIC5lbmQoY2FsbGJhY2spO1xuICogYGBgXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IGZpZWxkXG4gKiBAcGFyYW0ge0Jsb2J8RmlsZX0gZmlsZVxuICogQHBhcmFtIHtTdHJpbmd9IGZpbGVuYW1lXG4gKiBAcmV0dXJuIHtSZXF1ZXN0fSBmb3IgY2hhaW5pbmdcbiAqIEBhcGkgcHVibGljXG4gKi9cblxuUmVxdWVzdC5wcm90b3R5cGUuYXR0YWNoID0gZnVuY3Rpb24oZmllbGQsIGZpbGUsIGZpbGVuYW1lKXtcbiAgdGhpcy5fZ2V0Rm9ybURhdGEoKS5hcHBlbmQoZmllbGQsIGZpbGUsIGZpbGVuYW1lIHx8IGZpbGUubmFtZSk7XG4gIHJldHVybiB0aGlzO1xufTtcblxuUmVxdWVzdC5wcm90b3R5cGUuX2dldEZvcm1EYXRhID0gZnVuY3Rpb24oKXtcbiAgaWYgKCF0aGlzLl9mb3JtRGF0YSkge1xuICAgIHRoaXMuX2Zvcm1EYXRhID0gbmV3IHJvb3QuRm9ybURhdGEoKTtcbiAgfVxuICByZXR1cm4gdGhpcy5fZm9ybURhdGE7XG59O1xuXG4vKipcbiAqIEludm9rZSB0aGUgY2FsbGJhY2sgd2l0aCBgZXJyYCBhbmQgYHJlc2BcbiAqIGFuZCBoYW5kbGUgYXJpdHkgY2hlY2suXG4gKlxuICogQHBhcmFtIHtFcnJvcn0gZXJyXG4gKiBAcGFyYW0ge1Jlc3BvbnNlfSByZXNcbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cblJlcXVlc3QucHJvdG90eXBlLmNhbGxiYWNrID0gZnVuY3Rpb24oZXJyLCByZXMpe1xuICB2YXIgZm4gPSB0aGlzLl9jYWxsYmFjaztcbiAgdGhpcy5jbGVhclRpbWVvdXQoKTtcbiAgZm4oZXJyLCByZXMpO1xufTtcblxuLyoqXG4gKiBJbnZva2UgY2FsbGJhY2sgd2l0aCB4LWRvbWFpbiBlcnJvci5cbiAqXG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5SZXF1ZXN0LnByb3RvdHlwZS5jcm9zc0RvbWFpbkVycm9yID0gZnVuY3Rpb24oKXtcbiAgdmFyIGVyciA9IG5ldyBFcnJvcignUmVxdWVzdCBoYXMgYmVlbiB0ZXJtaW5hdGVkXFxuUG9zc2libGUgY2F1c2VzOiB0aGUgbmV0d29yayBpcyBvZmZsaW5lLCBPcmlnaW4gaXMgbm90IGFsbG93ZWQgYnkgQWNjZXNzLUNvbnRyb2wtQWxsb3ctT3JpZ2luLCB0aGUgcGFnZSBpcyBiZWluZyB1bmxvYWRlZCwgZXRjLicpO1xuICBlcnIuY3Jvc3NEb21haW4gPSB0cnVlO1xuXG4gIGVyci5zdGF0dXMgPSB0aGlzLnN0YXR1cztcbiAgZXJyLm1ldGhvZCA9IHRoaXMubWV0aG9kO1xuICBlcnIudXJsID0gdGhpcy51cmw7XG5cbiAgdGhpcy5jYWxsYmFjayhlcnIpO1xufTtcblxuLyoqXG4gKiBJbnZva2UgY2FsbGJhY2sgd2l0aCB0aW1lb3V0IGVycm9yLlxuICpcbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cblJlcXVlc3QucHJvdG90eXBlLl90aW1lb3V0RXJyb3IgPSBmdW5jdGlvbigpe1xuICB2YXIgdGltZW91dCA9IHRoaXMuX3RpbWVvdXQ7XG4gIHZhciBlcnIgPSBuZXcgRXJyb3IoJ3RpbWVvdXQgb2YgJyArIHRpbWVvdXQgKyAnbXMgZXhjZWVkZWQnKTtcbiAgZXJyLnRpbWVvdXQgPSB0aW1lb3V0O1xuICB0aGlzLmNhbGxiYWNrKGVycik7XG59O1xuXG4vKipcbiAqIENvbXBvc2UgcXVlcnlzdHJpbmcgdG8gYXBwZW5kIHRvIHJlcS51cmxcbiAqXG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5SZXF1ZXN0LnByb3RvdHlwZS5fYXBwZW5kUXVlcnlTdHJpbmcgPSBmdW5jdGlvbigpe1xuICB2YXIgcXVlcnkgPSB0aGlzLl9xdWVyeS5qb2luKCcmJyk7XG4gIGlmIChxdWVyeSkge1xuICAgIHRoaXMudXJsICs9IH50aGlzLnVybC5pbmRleE9mKCc/JylcbiAgICAgID8gJyYnICsgcXVlcnlcbiAgICAgIDogJz8nICsgcXVlcnk7XG4gIH1cbn07XG5cbi8qKlxuICogSW5pdGlhdGUgcmVxdWVzdCwgaW52b2tpbmcgY2FsbGJhY2sgYGZuKHJlcylgXG4gKiB3aXRoIGFuIGluc3RhbmNlb2YgYFJlc3BvbnNlYC5cbiAqXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmblxuICogQHJldHVybiB7UmVxdWVzdH0gZm9yIGNoYWluaW5nXG4gKiBAYXBpIHB1YmxpY1xuICovXG5cblJlcXVlc3QucHJvdG90eXBlLmVuZCA9IGZ1bmN0aW9uKGZuKXtcbiAgdmFyIHNlbGYgPSB0aGlzO1xuICB2YXIgeGhyID0gdGhpcy54aHIgPSByZXF1ZXN0LmdldFhIUigpO1xuICB2YXIgdGltZW91dCA9IHRoaXMuX3RpbWVvdXQ7XG4gIHZhciBkYXRhID0gdGhpcy5fZm9ybURhdGEgfHwgdGhpcy5fZGF0YTtcblxuICAvLyBzdG9yZSBjYWxsYmFja1xuICB0aGlzLl9jYWxsYmFjayA9IGZuIHx8IG5vb3A7XG5cbiAgLy8gc3RhdGUgY2hhbmdlXG4gIHhoci5vbnJlYWR5c3RhdGVjaGFuZ2UgPSBmdW5jdGlvbigpe1xuICAgIGlmICg0ICE9IHhoci5yZWFkeVN0YXRlKSByZXR1cm47XG5cbiAgICAvLyBJbiBJRTksIHJlYWRzIHRvIGFueSBwcm9wZXJ0eSAoZS5nLiBzdGF0dXMpIG9mZiBvZiBhbiBhYm9ydGVkIFhIUiB3aWxsXG4gICAgLy8gcmVzdWx0IGluIHRoZSBlcnJvciBcIkNvdWxkIG5vdCBjb21wbGV0ZSB0aGUgb3BlcmF0aW9uIGR1ZSB0byBlcnJvciBjMDBjMDIzZlwiXG4gICAgdmFyIHN0YXR1cztcbiAgICB0cnkgeyBzdGF0dXMgPSB4aHIuc3RhdHVzIH0gY2F0Y2goZSkgeyBzdGF0dXMgPSAwOyB9XG5cbiAgICBpZiAoMCA9PSBzdGF0dXMpIHtcbiAgICAgIGlmIChzZWxmLnRpbWVkb3V0KSByZXR1cm4gc2VsZi5fdGltZW91dEVycm9yKCk7XG4gICAgICBpZiAoc2VsZi5fYWJvcnRlZCkgcmV0dXJuO1xuICAgICAgcmV0dXJuIHNlbGYuY3Jvc3NEb21haW5FcnJvcigpO1xuICAgIH1cbiAgICBzZWxmLmVtaXQoJ2VuZCcpO1xuICB9O1xuXG4gIC8vIHByb2dyZXNzXG4gIHZhciBoYW5kbGVQcm9ncmVzcyA9IGZ1bmN0aW9uKGUpe1xuICAgIGlmIChlLnRvdGFsID4gMCkge1xuICAgICAgZS5wZXJjZW50ID0gZS5sb2FkZWQgLyBlLnRvdGFsICogMTAwO1xuICAgIH1cbiAgICBlLmRpcmVjdGlvbiA9ICdkb3dubG9hZCc7XG4gICAgc2VsZi5lbWl0KCdwcm9ncmVzcycsIGUpO1xuICB9O1xuICBpZiAodGhpcy5oYXNMaXN0ZW5lcnMoJ3Byb2dyZXNzJykpIHtcbiAgICB4aHIub25wcm9ncmVzcyA9IGhhbmRsZVByb2dyZXNzO1xuICB9XG4gIHRyeSB7XG4gICAgaWYgKHhoci51cGxvYWQgJiYgdGhpcy5oYXNMaXN0ZW5lcnMoJ3Byb2dyZXNzJykpIHtcbiAgICAgIHhoci51cGxvYWQub25wcm9ncmVzcyA9IGhhbmRsZVByb2dyZXNzO1xuICAgIH1cbiAgfSBjYXRjaChlKSB7XG4gICAgLy8gQWNjZXNzaW5nIHhoci51cGxvYWQgZmFpbHMgaW4gSUUgZnJvbSBhIHdlYiB3b3JrZXIsIHNvIGp1c3QgcHJldGVuZCBpdCBkb2Vzbid0IGV4aXN0LlxuICAgIC8vIFJlcG9ydGVkIGhlcmU6XG4gICAgLy8gaHR0cHM6Ly9jb25uZWN0Lm1pY3Jvc29mdC5jb20vSUUvZmVlZGJhY2svZGV0YWlscy84MzcyNDUveG1saHR0cHJlcXVlc3QtdXBsb2FkLXRocm93cy1pbnZhbGlkLWFyZ3VtZW50LXdoZW4tdXNlZC1mcm9tLXdlYi13b3JrZXItY29udGV4dFxuICB9XG5cbiAgLy8gdGltZW91dFxuICBpZiAodGltZW91dCAmJiAhdGhpcy5fdGltZXIpIHtcbiAgICB0aGlzLl90aW1lciA9IHNldFRpbWVvdXQoZnVuY3Rpb24oKXtcbiAgICAgIHNlbGYudGltZWRvdXQgPSB0cnVlO1xuICAgICAgc2VsZi5hYm9ydCgpO1xuICAgIH0sIHRpbWVvdXQpO1xuICB9XG5cbiAgLy8gcXVlcnlzdHJpbmdcbiAgdGhpcy5fYXBwZW5kUXVlcnlTdHJpbmcoKTtcblxuICAvLyBpbml0aWF0ZSByZXF1ZXN0XG4gIGlmICh0aGlzLnVzZXJuYW1lICYmIHRoaXMucGFzc3dvcmQpIHtcbiAgICB4aHIub3Blbih0aGlzLm1ldGhvZCwgdGhpcy51cmwsIHRydWUsIHRoaXMudXNlcm5hbWUsIHRoaXMucGFzc3dvcmQpO1xuICB9IGVsc2Uge1xuICAgIHhoci5vcGVuKHRoaXMubWV0aG9kLCB0aGlzLnVybCwgdHJ1ZSk7XG4gIH1cblxuICAvLyBDT1JTXG4gIGlmICh0aGlzLl93aXRoQ3JlZGVudGlhbHMpIHhoci53aXRoQ3JlZGVudGlhbHMgPSB0cnVlO1xuXG4gIC8vIGJvZHlcbiAgaWYgKCdHRVQnICE9IHRoaXMubWV0aG9kICYmICdIRUFEJyAhPSB0aGlzLm1ldGhvZCAmJiAnc3RyaW5nJyAhPSB0eXBlb2YgZGF0YSAmJiAhdGhpcy5faXNIb3N0KGRhdGEpKSB7XG4gICAgLy8gc2VyaWFsaXplIHN0dWZmXG4gICAgdmFyIGNvbnRlbnRUeXBlID0gdGhpcy5faGVhZGVyWydjb250ZW50LXR5cGUnXTtcbiAgICB2YXIgc2VyaWFsaXplID0gdGhpcy5fc2VyaWFsaXplciB8fCByZXF1ZXN0LnNlcmlhbGl6ZVtjb250ZW50VHlwZSA/IGNvbnRlbnRUeXBlLnNwbGl0KCc7JylbMF0gOiAnJ107XG4gICAgaWYgKCFzZXJpYWxpemUgJiYgaXNKU09OKGNvbnRlbnRUeXBlKSkgc2VyaWFsaXplID0gcmVxdWVzdC5zZXJpYWxpemVbJ2FwcGxpY2F0aW9uL2pzb24nXTtcbiAgICBpZiAoc2VyaWFsaXplKSBkYXRhID0gc2VyaWFsaXplKGRhdGEpO1xuICB9XG5cbiAgLy8gc2V0IGhlYWRlciBmaWVsZHNcbiAgZm9yICh2YXIgZmllbGQgaW4gdGhpcy5oZWFkZXIpIHtcbiAgICBpZiAobnVsbCA9PSB0aGlzLmhlYWRlcltmaWVsZF0pIGNvbnRpbnVlO1xuICAgIHhoci5zZXRSZXF1ZXN0SGVhZGVyKGZpZWxkLCB0aGlzLmhlYWRlcltmaWVsZF0pO1xuICB9XG5cbiAgaWYgKHRoaXMuX3Jlc3BvbnNlVHlwZSkge1xuICAgIHhoci5yZXNwb25zZVR5cGUgPSB0aGlzLl9yZXNwb25zZVR5cGU7XG4gIH1cblxuICAvLyBzZW5kIHN0dWZmXG4gIHRoaXMuZW1pdCgncmVxdWVzdCcsIHRoaXMpO1xuXG4gIC8vIElFMTEgeGhyLnNlbmQodW5kZWZpbmVkKSBzZW5kcyAndW5kZWZpbmVkJyBzdHJpbmcgYXMgUE9TVCBwYXlsb2FkIChpbnN0ZWFkIG9mIG5vdGhpbmcpXG4gIC8vIFdlIG5lZWQgbnVsbCBoZXJlIGlmIGRhdGEgaXMgdW5kZWZpbmVkXG4gIHhoci5zZW5kKHR5cGVvZiBkYXRhICE9PSAndW5kZWZpbmVkJyA/IGRhdGEgOiBudWxsKTtcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG5cbi8qKlxuICogRXhwb3NlIGBSZXF1ZXN0YC5cbiAqL1xuXG5yZXF1ZXN0LlJlcXVlc3QgPSBSZXF1ZXN0O1xuXG4vKipcbiAqIEdFVCBgdXJsYCB3aXRoIG9wdGlvbmFsIGNhbGxiYWNrIGBmbihyZXMpYC5cbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gdXJsXG4gKiBAcGFyYW0ge01peGVkfEZ1bmN0aW9ufSBbZGF0YV0gb3IgZm5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IFtmbl1cbiAqIEByZXR1cm4ge1JlcXVlc3R9XG4gKiBAYXBpIHB1YmxpY1xuICovXG5cbnJlcXVlc3QuZ2V0ID0gZnVuY3Rpb24odXJsLCBkYXRhLCBmbil7XG4gIHZhciByZXEgPSByZXF1ZXN0KCdHRVQnLCB1cmwpO1xuICBpZiAoJ2Z1bmN0aW9uJyA9PSB0eXBlb2YgZGF0YSkgZm4gPSBkYXRhLCBkYXRhID0gbnVsbDtcbiAgaWYgKGRhdGEpIHJlcS5xdWVyeShkYXRhKTtcbiAgaWYgKGZuKSByZXEuZW5kKGZuKTtcbiAgcmV0dXJuIHJlcTtcbn07XG5cbi8qKlxuICogSEVBRCBgdXJsYCB3aXRoIG9wdGlvbmFsIGNhbGxiYWNrIGBmbihyZXMpYC5cbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gdXJsXG4gKiBAcGFyYW0ge01peGVkfEZ1bmN0aW9ufSBbZGF0YV0gb3IgZm5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IFtmbl1cbiAqIEByZXR1cm4ge1JlcXVlc3R9XG4gKiBAYXBpIHB1YmxpY1xuICovXG5cbnJlcXVlc3QuaGVhZCA9IGZ1bmN0aW9uKHVybCwgZGF0YSwgZm4pe1xuICB2YXIgcmVxID0gcmVxdWVzdCgnSEVBRCcsIHVybCk7XG4gIGlmICgnZnVuY3Rpb24nID09IHR5cGVvZiBkYXRhKSBmbiA9IGRhdGEsIGRhdGEgPSBudWxsO1xuICBpZiAoZGF0YSkgcmVxLnNlbmQoZGF0YSk7XG4gIGlmIChmbikgcmVxLmVuZChmbik7XG4gIHJldHVybiByZXE7XG59O1xuXG4vKipcbiAqIE9QVElPTlMgcXVlcnkgdG8gYHVybGAgd2l0aCBvcHRpb25hbCBjYWxsYmFjayBgZm4ocmVzKWAuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IHVybFxuICogQHBhcmFtIHtNaXhlZHxGdW5jdGlvbn0gW2RhdGFdIG9yIGZuXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBbZm5dXG4gKiBAcmV0dXJuIHtSZXF1ZXN0fVxuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5yZXF1ZXN0Lm9wdGlvbnMgPSBmdW5jdGlvbih1cmwsIGRhdGEsIGZuKXtcbiAgdmFyIHJlcSA9IHJlcXVlc3QoJ09QVElPTlMnLCB1cmwpO1xuICBpZiAoJ2Z1bmN0aW9uJyA9PSB0eXBlb2YgZGF0YSkgZm4gPSBkYXRhLCBkYXRhID0gbnVsbDtcbiAgaWYgKGRhdGEpIHJlcS5zZW5kKGRhdGEpO1xuICBpZiAoZm4pIHJlcS5lbmQoZm4pO1xuICByZXR1cm4gcmVxO1xufTtcblxuLyoqXG4gKiBERUxFVEUgYHVybGAgd2l0aCBvcHRpb25hbCBjYWxsYmFjayBgZm4ocmVzKWAuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IHVybFxuICogQHBhcmFtIHtGdW5jdGlvbn0gW2ZuXVxuICogQHJldHVybiB7UmVxdWVzdH1cbiAqIEBhcGkgcHVibGljXG4gKi9cblxuZnVuY3Rpb24gZGVsKHVybCwgZm4pe1xuICB2YXIgcmVxID0gcmVxdWVzdCgnREVMRVRFJywgdXJsKTtcbiAgaWYgKGZuKSByZXEuZW5kKGZuKTtcbiAgcmV0dXJuIHJlcTtcbn07XG5cbnJlcXVlc3RbJ2RlbCddID0gZGVsO1xucmVxdWVzdFsnZGVsZXRlJ10gPSBkZWw7XG5cbi8qKlxuICogUEFUQ0ggYHVybGAgd2l0aCBvcHRpb25hbCBgZGF0YWAgYW5kIGNhbGxiYWNrIGBmbihyZXMpYC5cbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gdXJsXG4gKiBAcGFyYW0ge01peGVkfSBbZGF0YV1cbiAqIEBwYXJhbSB7RnVuY3Rpb259IFtmbl1cbiAqIEByZXR1cm4ge1JlcXVlc3R9XG4gKiBAYXBpIHB1YmxpY1xuICovXG5cbnJlcXVlc3QucGF0Y2ggPSBmdW5jdGlvbih1cmwsIGRhdGEsIGZuKXtcbiAgdmFyIHJlcSA9IHJlcXVlc3QoJ1BBVENIJywgdXJsKTtcbiAgaWYgKCdmdW5jdGlvbicgPT0gdHlwZW9mIGRhdGEpIGZuID0gZGF0YSwgZGF0YSA9IG51bGw7XG4gIGlmIChkYXRhKSByZXEuc2VuZChkYXRhKTtcbiAgaWYgKGZuKSByZXEuZW5kKGZuKTtcbiAgcmV0dXJuIHJlcTtcbn07XG5cbi8qKlxuICogUE9TVCBgdXJsYCB3aXRoIG9wdGlvbmFsIGBkYXRhYCBhbmQgY2FsbGJhY2sgYGZuKHJlcylgLlxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSB1cmxcbiAqIEBwYXJhbSB7TWl4ZWR9IFtkYXRhXVxuICogQHBhcmFtIHtGdW5jdGlvbn0gW2ZuXVxuICogQHJldHVybiB7UmVxdWVzdH1cbiAqIEBhcGkgcHVibGljXG4gKi9cblxucmVxdWVzdC5wb3N0ID0gZnVuY3Rpb24odXJsLCBkYXRhLCBmbil7XG4gIHZhciByZXEgPSByZXF1ZXN0KCdQT1NUJywgdXJsKTtcbiAgaWYgKCdmdW5jdGlvbicgPT0gdHlwZW9mIGRhdGEpIGZuID0gZGF0YSwgZGF0YSA9IG51bGw7XG4gIGlmIChkYXRhKSByZXEuc2VuZChkYXRhKTtcbiAgaWYgKGZuKSByZXEuZW5kKGZuKTtcbiAgcmV0dXJuIHJlcTtcbn07XG5cbi8qKlxuICogUFVUIGB1cmxgIHdpdGggb3B0aW9uYWwgYGRhdGFgIGFuZCBjYWxsYmFjayBgZm4ocmVzKWAuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IHVybFxuICogQHBhcmFtIHtNaXhlZHxGdW5jdGlvbn0gW2RhdGFdIG9yIGZuXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBbZm5dXG4gKiBAcmV0dXJuIHtSZXF1ZXN0fVxuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5yZXF1ZXN0LnB1dCA9IGZ1bmN0aW9uKHVybCwgZGF0YSwgZm4pe1xuICB2YXIgcmVxID0gcmVxdWVzdCgnUFVUJywgdXJsKTtcbiAgaWYgKCdmdW5jdGlvbicgPT0gdHlwZW9mIGRhdGEpIGZuID0gZGF0YSwgZGF0YSA9IG51bGw7XG4gIGlmIChkYXRhKSByZXEuc2VuZChkYXRhKTtcbiAgaWYgKGZuKSByZXEuZW5kKGZuKTtcbiAgcmV0dXJuIHJlcTtcbn07XG4iLCIvKipcbiAqIENoZWNrIGlmIGBvYmpgIGlzIGFuIG9iamVjdC5cbiAqXG4gKiBAcGFyYW0ge09iamVjdH0gb2JqXG4gKiBAcmV0dXJuIHtCb29sZWFufVxuICogQGFwaSBwcml2YXRlXG4gKi9cblxuZnVuY3Rpb24gaXNPYmplY3Qob2JqKSB7XG4gIHJldHVybiBudWxsICE9PSBvYmogJiYgJ29iamVjdCcgPT09IHR5cGVvZiBvYmo7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gaXNPYmplY3Q7XG4iLCIvKipcbiAqIE1vZHVsZSBvZiBtaXhlZC1pbiBmdW5jdGlvbnMgc2hhcmVkIGJldHdlZW4gbm9kZSBhbmQgY2xpZW50IGNvZGVcbiAqL1xudmFyIGlzT2JqZWN0ID0gcmVxdWlyZSgnLi9pcy1vYmplY3QnKTtcblxuLyoqXG4gKiBDbGVhciBwcmV2aW91cyB0aW1lb3V0LlxuICpcbiAqIEByZXR1cm4ge1JlcXVlc3R9IGZvciBjaGFpbmluZ1xuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5leHBvcnRzLmNsZWFyVGltZW91dCA9IGZ1bmN0aW9uIF9jbGVhclRpbWVvdXQoKXtcbiAgdGhpcy5fdGltZW91dCA9IDA7XG4gIGNsZWFyVGltZW91dCh0aGlzLl90aW1lcik7XG4gIHJldHVybiB0aGlzO1xufTtcblxuLyoqXG4gKiBPdmVycmlkZSBkZWZhdWx0IHJlc3BvbnNlIGJvZHkgcGFyc2VyXG4gKlxuICogVGhpcyBmdW5jdGlvbiB3aWxsIGJlIGNhbGxlZCB0byBjb252ZXJ0IGluY29taW5nIGRhdGEgaW50byByZXF1ZXN0LmJvZHlcbiAqXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufVxuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5leHBvcnRzLnBhcnNlID0gZnVuY3Rpb24gcGFyc2UoZm4pe1xuICB0aGlzLl9wYXJzZXIgPSBmbjtcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vKipcbiAqIE92ZXJyaWRlIGRlZmF1bHQgcmVxdWVzdCBib2R5IHNlcmlhbGl6ZXJcbiAqXG4gKiBUaGlzIGZ1bmN0aW9uIHdpbGwgYmUgY2FsbGVkIHRvIGNvbnZlcnQgZGF0YSBzZXQgdmlhIC5zZW5kIG9yIC5hdHRhY2ggaW50byBwYXlsb2FkIHRvIHNlbmRcbiAqXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufVxuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5leHBvcnRzLnNlcmlhbGl6ZSA9IGZ1bmN0aW9uIHNlcmlhbGl6ZShmbil7XG4gIHRoaXMuX3NlcmlhbGl6ZXIgPSBmbjtcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vKipcbiAqIFNldCB0aW1lb3V0IHRvIGBtc2AuXG4gKlxuICogQHBhcmFtIHtOdW1iZXJ9IG1zXG4gKiBAcmV0dXJuIHtSZXF1ZXN0fSBmb3IgY2hhaW5pbmdcbiAqIEBhcGkgcHVibGljXG4gKi9cblxuZXhwb3J0cy50aW1lb3V0ID0gZnVuY3Rpb24gdGltZW91dChtcyl7XG4gIHRoaXMuX3RpbWVvdXQgPSBtcztcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vKipcbiAqIFByb21pc2Ugc3VwcG9ydFxuICpcbiAqIEBwYXJhbSB7RnVuY3Rpb259IHJlc29sdmVcbiAqIEBwYXJhbSB7RnVuY3Rpb259IHJlamVjdFxuICogQHJldHVybiB7UmVxdWVzdH1cbiAqL1xuXG5leHBvcnRzLnRoZW4gPSBmdW5jdGlvbiB0aGVuKHJlc29sdmUsIHJlamVjdCkge1xuICBpZiAoIXRoaXMuX2Z1bGxmaWxsZWRQcm9taXNlKSB7XG4gICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgIHRoaXMuX2Z1bGxmaWxsZWRQcm9taXNlID0gbmV3IFByb21pc2UoZnVuY3Rpb24oaW5uZXJSZXNvbHZlLCBpbm5lclJlamVjdCl7XG4gICAgICBzZWxmLmVuZChmdW5jdGlvbihlcnIsIHJlcyl7XG4gICAgICAgIGlmIChlcnIpIGlubmVyUmVqZWN0KGVycik7IGVsc2UgaW5uZXJSZXNvbHZlKHJlcyk7XG4gICAgICB9KTtcbiAgICB9KTtcbiAgfVxuICByZXR1cm4gdGhpcy5fZnVsbGZpbGxlZFByb21pc2UudGhlbihyZXNvbHZlLCByZWplY3QpO1xufVxuXG4vKipcbiAqIEFsbG93IGZvciBleHRlbnNpb25cbiAqL1xuXG5leHBvcnRzLnVzZSA9IGZ1bmN0aW9uIHVzZShmbikge1xuICBmbih0aGlzKTtcbiAgcmV0dXJuIHRoaXM7XG59XG5cblxuLyoqXG4gKiBHZXQgcmVxdWVzdCBoZWFkZXIgYGZpZWxkYC5cbiAqIENhc2UtaW5zZW5zaXRpdmUuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IGZpZWxkXG4gKiBAcmV0dXJuIHtTdHJpbmd9XG4gKiBAYXBpIHB1YmxpY1xuICovXG5cbmV4cG9ydHMuZ2V0ID0gZnVuY3Rpb24oZmllbGQpe1xuICByZXR1cm4gdGhpcy5faGVhZGVyW2ZpZWxkLnRvTG93ZXJDYXNlKCldO1xufTtcblxuLyoqXG4gKiBHZXQgY2FzZS1pbnNlbnNpdGl2ZSBoZWFkZXIgYGZpZWxkYCB2YWx1ZS5cbiAqIFRoaXMgaXMgYSBkZXByZWNhdGVkIGludGVybmFsIEFQSS4gVXNlIGAuZ2V0KGZpZWxkKWAgaW5zdGVhZC5cbiAqXG4gKiAoZ2V0SGVhZGVyIGlzIG5vIGxvbmdlciB1c2VkIGludGVybmFsbHkgYnkgdGhlIHN1cGVyYWdlbnQgY29kZSBiYXNlKVxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBmaWVsZFxuICogQHJldHVybiB7U3RyaW5nfVxuICogQGFwaSBwcml2YXRlXG4gKiBAZGVwcmVjYXRlZFxuICovXG5cbmV4cG9ydHMuZ2V0SGVhZGVyID0gZXhwb3J0cy5nZXQ7XG5cbi8qKlxuICogU2V0IGhlYWRlciBgZmllbGRgIHRvIGB2YWxgLCBvciBtdWx0aXBsZSBmaWVsZHMgd2l0aCBvbmUgb2JqZWN0LlxuICogQ2FzZS1pbnNlbnNpdGl2ZS5cbiAqXG4gKiBFeGFtcGxlczpcbiAqXG4gKiAgICAgIHJlcS5nZXQoJy8nKVxuICogICAgICAgIC5zZXQoJ0FjY2VwdCcsICdhcHBsaWNhdGlvbi9qc29uJylcbiAqICAgICAgICAuc2V0KCdYLUFQSS1LZXknLCAnZm9vYmFyJylcbiAqICAgICAgICAuZW5kKGNhbGxiYWNrKTtcbiAqXG4gKiAgICAgIHJlcS5nZXQoJy8nKVxuICogICAgICAgIC5zZXQoeyBBY2NlcHQ6ICdhcHBsaWNhdGlvbi9qc29uJywgJ1gtQVBJLUtleSc6ICdmb29iYXInIH0pXG4gKiAgICAgICAgLmVuZChjYWxsYmFjayk7XG4gKlxuICogQHBhcmFtIHtTdHJpbmd8T2JqZWN0fSBmaWVsZFxuICogQHBhcmFtIHtTdHJpbmd9IHZhbFxuICogQHJldHVybiB7UmVxdWVzdH0gZm9yIGNoYWluaW5nXG4gKiBAYXBpIHB1YmxpY1xuICovXG5cbmV4cG9ydHMuc2V0ID0gZnVuY3Rpb24oZmllbGQsIHZhbCl7XG4gIGlmIChpc09iamVjdChmaWVsZCkpIHtcbiAgICBmb3IgKHZhciBrZXkgaW4gZmllbGQpIHtcbiAgICAgIHRoaXMuc2V0KGtleSwgZmllbGRba2V5XSk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9XG4gIHRoaXMuX2hlYWRlcltmaWVsZC50b0xvd2VyQ2FzZSgpXSA9IHZhbDtcbiAgdGhpcy5oZWFkZXJbZmllbGRdID0gdmFsO1xuICByZXR1cm4gdGhpcztcbn07XG5cbi8qKlxuICogUmVtb3ZlIGhlYWRlciBgZmllbGRgLlxuICogQ2FzZS1pbnNlbnNpdGl2ZS5cbiAqXG4gKiBFeGFtcGxlOlxuICpcbiAqICAgICAgcmVxLmdldCgnLycpXG4gKiAgICAgICAgLnVuc2V0KCdVc2VyLUFnZW50JylcbiAqICAgICAgICAuZW5kKGNhbGxiYWNrKTtcbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gZmllbGRcbiAqL1xuZXhwb3J0cy51bnNldCA9IGZ1bmN0aW9uKGZpZWxkKXtcbiAgZGVsZXRlIHRoaXMuX2hlYWRlcltmaWVsZC50b0xvd2VyQ2FzZSgpXTtcbiAgZGVsZXRlIHRoaXMuaGVhZGVyW2ZpZWxkXTtcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vKipcbiAqIFdyaXRlIHRoZSBmaWVsZCBgbmFtZWAgYW5kIGB2YWxgIGZvciBcIm11bHRpcGFydC9mb3JtLWRhdGFcIlxuICogcmVxdWVzdCBib2RpZXMuXG4gKlxuICogYGBgIGpzXG4gKiByZXF1ZXN0LnBvc3QoJy91cGxvYWQnKVxuICogICAuZmllbGQoJ2ZvbycsICdiYXInKVxuICogICAuZW5kKGNhbGxiYWNrKTtcbiAqIGBgYFxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBuYW1lXG4gKiBAcGFyYW0ge1N0cmluZ3xCbG9ifEZpbGV8QnVmZmVyfGZzLlJlYWRTdHJlYW19IHZhbFxuICogQHJldHVybiB7UmVxdWVzdH0gZm9yIGNoYWluaW5nXG4gKiBAYXBpIHB1YmxpY1xuICovXG5leHBvcnRzLmZpZWxkID0gZnVuY3Rpb24obmFtZSwgdmFsKSB7XG4gIHRoaXMuX2dldEZvcm1EYXRhKCkuYXBwZW5kKG5hbWUsIHZhbCk7XG4gIHJldHVybiB0aGlzO1xufTtcblxuLyoqXG4gKiBBYm9ydCB0aGUgcmVxdWVzdCwgYW5kIGNsZWFyIHBvdGVudGlhbCB0aW1lb3V0LlxuICpcbiAqIEByZXR1cm4ge1JlcXVlc3R9XG4gKiBAYXBpIHB1YmxpY1xuICovXG5leHBvcnRzLmFib3J0ID0gZnVuY3Rpb24oKXtcbiAgaWYgKHRoaXMuX2Fib3J0ZWQpIHtcbiAgICByZXR1cm4gdGhpcztcbiAgfVxuICB0aGlzLl9hYm9ydGVkID0gdHJ1ZTtcbiAgdGhpcy54aHIgJiYgdGhpcy54aHIuYWJvcnQoKTsgLy8gYnJvd3NlclxuICB0aGlzLnJlcSAmJiB0aGlzLnJlcS5hYm9ydCgpOyAvLyBub2RlXG4gIHRoaXMuY2xlYXJUaW1lb3V0KCk7XG4gIHRoaXMuZW1pdCgnYWJvcnQnKTtcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vKipcbiAqIEVuYWJsZSB0cmFuc21pc3Npb24gb2YgY29va2llcyB3aXRoIHgtZG9tYWluIHJlcXVlc3RzLlxuICpcbiAqIE5vdGUgdGhhdCBmb3IgdGhpcyB0byB3b3JrIHRoZSBvcmlnaW4gbXVzdCBub3QgYmVcbiAqIHVzaW5nIFwiQWNjZXNzLUNvbnRyb2wtQWxsb3ctT3JpZ2luXCIgd2l0aCBhIHdpbGRjYXJkLFxuICogYW5kIGFsc28gbXVzdCBzZXQgXCJBY2Nlc3MtQ29udHJvbC1BbGxvdy1DcmVkZW50aWFsc1wiXG4gKiB0byBcInRydWVcIi5cbiAqXG4gKiBAYXBpIHB1YmxpY1xuICovXG5cbmV4cG9ydHMud2l0aENyZWRlbnRpYWxzID0gZnVuY3Rpb24oKXtcbiAgLy8gVGhpcyBpcyBicm93c2VyLW9ubHkgZnVuY3Rpb25hbGl0eS4gTm9kZSBzaWRlIGlzIG5vLW9wLlxuICB0aGlzLl93aXRoQ3JlZGVudGlhbHMgPSB0cnVlO1xuICByZXR1cm4gdGhpcztcbn07XG5cbi8qKlxuICogU2V0IHRoZSBtYXggcmVkaXJlY3RzIHRvIGBuYC4gRG9lcyBub3RpbmcgaW4gYnJvd3NlciBYSFIgaW1wbGVtZW50YXRpb24uXG4gKlxuICogQHBhcmFtIHtOdW1iZXJ9IG5cbiAqIEByZXR1cm4ge1JlcXVlc3R9IGZvciBjaGFpbmluZ1xuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5leHBvcnRzLnJlZGlyZWN0cyA9IGZ1bmN0aW9uKG4pe1xuICB0aGlzLl9tYXhSZWRpcmVjdHMgPSBuO1xuICByZXR1cm4gdGhpcztcbn07XG5cbi8qKlxuICogQ29udmVydCB0byBhIHBsYWluIGphdmFzY3JpcHQgb2JqZWN0IChub3QgSlNPTiBzdHJpbmcpIG9mIHNjYWxhciBwcm9wZXJ0aWVzLlxuICogTm90ZSBhcyB0aGlzIG1ldGhvZCBpcyBkZXNpZ25lZCB0byByZXR1cm4gYSB1c2VmdWwgbm9uLXRoaXMgdmFsdWUsXG4gKiBpdCBjYW5ub3QgYmUgY2hhaW5lZC5cbiAqXG4gKiBAcmV0dXJuIHtPYmplY3R9IGRlc2NyaWJpbmcgbWV0aG9kLCB1cmwsIGFuZCBkYXRhIG9mIHRoaXMgcmVxdWVzdFxuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5leHBvcnRzLnRvSlNPTiA9IGZ1bmN0aW9uKCl7XG4gIHJldHVybiB7XG4gICAgbWV0aG9kOiB0aGlzLm1ldGhvZCxcbiAgICB1cmw6IHRoaXMudXJsLFxuICAgIGRhdGE6IHRoaXMuX2RhdGEsXG4gICAgaGVhZGVyczogdGhpcy5faGVhZGVyXG4gIH07XG59O1xuXG4vKipcbiAqIENoZWNrIGlmIGBvYmpgIGlzIGEgaG9zdCBvYmplY3QsXG4gKiB3ZSBkb24ndCB3YW50IHRvIHNlcmlhbGl6ZSB0aGVzZSA6KVxuICpcbiAqIFRPRE86IGZ1dHVyZSBwcm9vZiwgbW92ZSB0byBjb21wb2VudCBsYW5kXG4gKlxuICogQHBhcmFtIHtPYmplY3R9IG9ialxuICogQHJldHVybiB7Qm9vbGVhbn1cbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cbmV4cG9ydHMuX2lzSG9zdCA9IGZ1bmN0aW9uIF9pc0hvc3Qob2JqKSB7XG4gIHZhciBzdHIgPSB7fS50b1N0cmluZy5jYWxsKG9iaik7XG5cbiAgc3dpdGNoIChzdHIpIHtcbiAgICBjYXNlICdbb2JqZWN0IEZpbGVdJzpcbiAgICBjYXNlICdbb2JqZWN0IEJsb2JdJzpcbiAgICBjYXNlICdbb2JqZWN0IEZvcm1EYXRhXSc6XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICBkZWZhdWx0OlxuICAgICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbi8qKlxuICogU2VuZCBgZGF0YWAgYXMgdGhlIHJlcXVlc3QgYm9keSwgZGVmYXVsdGluZyB0aGUgYC50eXBlKClgIHRvIFwianNvblwiIHdoZW5cbiAqIGFuIG9iamVjdCBpcyBnaXZlbi5cbiAqXG4gKiBFeGFtcGxlczpcbiAqXG4gKiAgICAgICAvLyBtYW51YWwganNvblxuICogICAgICAgcmVxdWVzdC5wb3N0KCcvdXNlcicpXG4gKiAgICAgICAgIC50eXBlKCdqc29uJylcbiAqICAgICAgICAgLnNlbmQoJ3tcIm5hbWVcIjpcInRqXCJ9JylcbiAqICAgICAgICAgLmVuZChjYWxsYmFjaylcbiAqXG4gKiAgICAgICAvLyBhdXRvIGpzb25cbiAqICAgICAgIHJlcXVlc3QucG9zdCgnL3VzZXInKVxuICogICAgICAgICAuc2VuZCh7IG5hbWU6ICd0aicgfSlcbiAqICAgICAgICAgLmVuZChjYWxsYmFjaylcbiAqXG4gKiAgICAgICAvLyBtYW51YWwgeC13d3ctZm9ybS11cmxlbmNvZGVkXG4gKiAgICAgICByZXF1ZXN0LnBvc3QoJy91c2VyJylcbiAqICAgICAgICAgLnR5cGUoJ2Zvcm0nKVxuICogICAgICAgICAuc2VuZCgnbmFtZT10aicpXG4gKiAgICAgICAgIC5lbmQoY2FsbGJhY2spXG4gKlxuICogICAgICAgLy8gYXV0byB4LXd3dy1mb3JtLXVybGVuY29kZWRcbiAqICAgICAgIHJlcXVlc3QucG9zdCgnL3VzZXInKVxuICogICAgICAgICAudHlwZSgnZm9ybScpXG4gKiAgICAgICAgIC5zZW5kKHsgbmFtZTogJ3RqJyB9KVxuICogICAgICAgICAuZW5kKGNhbGxiYWNrKVxuICpcbiAqICAgICAgIC8vIGRlZmF1bHRzIHRvIHgtd3d3LWZvcm0tdXJsZW5jb2RlZFxuICogICAgICByZXF1ZXN0LnBvc3QoJy91c2VyJylcbiAqICAgICAgICAuc2VuZCgnbmFtZT10b2JpJylcbiAqICAgICAgICAuc2VuZCgnc3BlY2llcz1mZXJyZXQnKVxuICogICAgICAgIC5lbmQoY2FsbGJhY2spXG4gKlxuICogQHBhcmFtIHtTdHJpbmd8T2JqZWN0fSBkYXRhXG4gKiBAcmV0dXJuIHtSZXF1ZXN0fSBmb3IgY2hhaW5pbmdcbiAqIEBhcGkgcHVibGljXG4gKi9cblxuZXhwb3J0cy5zZW5kID0gZnVuY3Rpb24oZGF0YSl7XG4gIHZhciBvYmogPSBpc09iamVjdChkYXRhKTtcbiAgdmFyIHR5cGUgPSB0aGlzLl9oZWFkZXJbJ2NvbnRlbnQtdHlwZSddO1xuXG4gIC8vIG1lcmdlXG4gIGlmIChvYmogJiYgaXNPYmplY3QodGhpcy5fZGF0YSkpIHtcbiAgICBmb3IgKHZhciBrZXkgaW4gZGF0YSkge1xuICAgICAgdGhpcy5fZGF0YVtrZXldID0gZGF0YVtrZXldO1xuICAgIH1cbiAgfSBlbHNlIGlmICgnc3RyaW5nJyA9PSB0eXBlb2YgZGF0YSkge1xuICAgIC8vIGRlZmF1bHQgdG8geC13d3ctZm9ybS11cmxlbmNvZGVkXG4gICAgaWYgKCF0eXBlKSB0aGlzLnR5cGUoJ2Zvcm0nKTtcbiAgICB0eXBlID0gdGhpcy5faGVhZGVyWydjb250ZW50LXR5cGUnXTtcbiAgICBpZiAoJ2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCcgPT0gdHlwZSkge1xuICAgICAgdGhpcy5fZGF0YSA9IHRoaXMuX2RhdGFcbiAgICAgICAgPyB0aGlzLl9kYXRhICsgJyYnICsgZGF0YVxuICAgICAgICA6IGRhdGE7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuX2RhdGEgPSAodGhpcy5fZGF0YSB8fCAnJykgKyBkYXRhO1xuICAgIH1cbiAgfSBlbHNlIHtcbiAgICB0aGlzLl9kYXRhID0gZGF0YTtcbiAgfVxuXG4gIGlmICghb2JqIHx8IHRoaXMuX2lzSG9zdChkYXRhKSkgcmV0dXJuIHRoaXM7XG5cbiAgLy8gZGVmYXVsdCB0byBqc29uXG4gIGlmICghdHlwZSkgdGhpcy50eXBlKCdqc29uJyk7XG4gIHJldHVybiB0aGlzO1xufTtcbiIsIi8vIFRoZSBub2RlIGFuZCBicm93c2VyIG1vZHVsZXMgZXhwb3NlIHZlcnNpb25zIG9mIHRoaXMgd2l0aCB0aGVcbi8vIGFwcHJvcHJpYXRlIGNvbnN0cnVjdG9yIGZ1bmN0aW9uIGJvdW5kIGFzIGZpcnN0IGFyZ3VtZW50XG4vKipcbiAqIElzc3VlIGEgcmVxdWVzdDpcbiAqXG4gKiBFeGFtcGxlczpcbiAqXG4gKiAgICByZXF1ZXN0KCdHRVQnLCAnL3VzZXJzJykuZW5kKGNhbGxiYWNrKVxuICogICAgcmVxdWVzdCgnL3VzZXJzJykuZW5kKGNhbGxiYWNrKVxuICogICAgcmVxdWVzdCgnL3VzZXJzJywgY2FsbGJhY2spXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IG1ldGhvZFxuICogQHBhcmFtIHtTdHJpbmd8RnVuY3Rpb259IHVybCBvciBjYWxsYmFja1xuICogQHJldHVybiB7UmVxdWVzdH1cbiAqIEBhcGkgcHVibGljXG4gKi9cblxuZnVuY3Rpb24gcmVxdWVzdChSZXF1ZXN0Q29uc3RydWN0b3IsIG1ldGhvZCwgdXJsKSB7XG4gIC8vIGNhbGxiYWNrXG4gIGlmICgnZnVuY3Rpb24nID09IHR5cGVvZiB1cmwpIHtcbiAgICByZXR1cm4gbmV3IFJlcXVlc3RDb25zdHJ1Y3RvcignR0VUJywgbWV0aG9kKS5lbmQodXJsKTtcbiAgfVxuXG4gIC8vIHVybCBmaXJzdFxuICBpZiAoMiA9PSBhcmd1bWVudHMubGVuZ3RoKSB7XG4gICAgcmV0dXJuIG5ldyBSZXF1ZXN0Q29uc3RydWN0b3IoJ0dFVCcsIG1ldGhvZCk7XG4gIH1cblxuICByZXR1cm4gbmV3IFJlcXVlc3RDb25zdHJ1Y3RvcihtZXRob2QsIHVybCk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gcmVxdWVzdDtcbiIsIlxyXG4vKipcclxuICogRXhwb3NlIGBFbWl0dGVyYC5cclxuICovXHJcblxyXG5pZiAodHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcpIHtcclxuICBtb2R1bGUuZXhwb3J0cyA9IEVtaXR0ZXI7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBJbml0aWFsaXplIGEgbmV3IGBFbWl0dGVyYC5cclxuICpcclxuICogQGFwaSBwdWJsaWNcclxuICovXHJcblxyXG5mdW5jdGlvbiBFbWl0dGVyKG9iaikge1xyXG4gIGlmIChvYmopIHJldHVybiBtaXhpbihvYmopO1xyXG59O1xyXG5cclxuLyoqXHJcbiAqIE1peGluIHRoZSBlbWl0dGVyIHByb3BlcnRpZXMuXHJcbiAqXHJcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmpcclxuICogQHJldHVybiB7T2JqZWN0fVxyXG4gKiBAYXBpIHByaXZhdGVcclxuICovXHJcblxyXG5mdW5jdGlvbiBtaXhpbihvYmopIHtcclxuICBmb3IgKHZhciBrZXkgaW4gRW1pdHRlci5wcm90b3R5cGUpIHtcclxuICAgIG9ialtrZXldID0gRW1pdHRlci5wcm90b3R5cGVba2V5XTtcclxuICB9XHJcbiAgcmV0dXJuIG9iajtcclxufVxyXG5cclxuLyoqXHJcbiAqIExpc3RlbiBvbiB0aGUgZ2l2ZW4gYGV2ZW50YCB3aXRoIGBmbmAuXHJcbiAqXHJcbiAqIEBwYXJhbSB7U3RyaW5nfSBldmVudFxyXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmblxyXG4gKiBAcmV0dXJuIHtFbWl0dGVyfVxyXG4gKiBAYXBpIHB1YmxpY1xyXG4gKi9cclxuXHJcbkVtaXR0ZXIucHJvdG90eXBlLm9uID1cclxuRW1pdHRlci5wcm90b3R5cGUuYWRkRXZlbnRMaXN0ZW5lciA9IGZ1bmN0aW9uKGV2ZW50LCBmbil7XHJcbiAgdGhpcy5fY2FsbGJhY2tzID0gdGhpcy5fY2FsbGJhY2tzIHx8IHt9O1xyXG4gICh0aGlzLl9jYWxsYmFja3NbJyQnICsgZXZlbnRdID0gdGhpcy5fY2FsbGJhY2tzWyckJyArIGV2ZW50XSB8fCBbXSlcclxuICAgIC5wdXNoKGZuKTtcclxuICByZXR1cm4gdGhpcztcclxufTtcclxuXHJcbi8qKlxyXG4gKiBBZGRzIGFuIGBldmVudGAgbGlzdGVuZXIgdGhhdCB3aWxsIGJlIGludm9rZWQgYSBzaW5nbGVcclxuICogdGltZSB0aGVuIGF1dG9tYXRpY2FsbHkgcmVtb3ZlZC5cclxuICpcclxuICogQHBhcmFtIHtTdHJpbmd9IGV2ZW50XHJcbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZuXHJcbiAqIEByZXR1cm4ge0VtaXR0ZXJ9XHJcbiAqIEBhcGkgcHVibGljXHJcbiAqL1xyXG5cclxuRW1pdHRlci5wcm90b3R5cGUub25jZSA9IGZ1bmN0aW9uKGV2ZW50LCBmbil7XHJcbiAgZnVuY3Rpb24gb24oKSB7XHJcbiAgICB0aGlzLm9mZihldmVudCwgb24pO1xyXG4gICAgZm4uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcclxuICB9XHJcblxyXG4gIG9uLmZuID0gZm47XHJcbiAgdGhpcy5vbihldmVudCwgb24pO1xyXG4gIHJldHVybiB0aGlzO1xyXG59O1xyXG5cclxuLyoqXHJcbiAqIFJlbW92ZSB0aGUgZ2l2ZW4gY2FsbGJhY2sgZm9yIGBldmVudGAgb3IgYWxsXHJcbiAqIHJlZ2lzdGVyZWQgY2FsbGJhY2tzLlxyXG4gKlxyXG4gKiBAcGFyYW0ge1N0cmluZ30gZXZlbnRcclxuICogQHBhcmFtIHtGdW5jdGlvbn0gZm5cclxuICogQHJldHVybiB7RW1pdHRlcn1cclxuICogQGFwaSBwdWJsaWNcclxuICovXHJcblxyXG5FbWl0dGVyLnByb3RvdHlwZS5vZmYgPVxyXG5FbWl0dGVyLnByb3RvdHlwZS5yZW1vdmVMaXN0ZW5lciA9XHJcbkVtaXR0ZXIucHJvdG90eXBlLnJlbW92ZUFsbExpc3RlbmVycyA9XHJcbkVtaXR0ZXIucHJvdG90eXBlLnJlbW92ZUV2ZW50TGlzdGVuZXIgPSBmdW5jdGlvbihldmVudCwgZm4pe1xyXG4gIHRoaXMuX2NhbGxiYWNrcyA9IHRoaXMuX2NhbGxiYWNrcyB8fCB7fTtcclxuXHJcbiAgLy8gYWxsXHJcbiAgaWYgKDAgPT0gYXJndW1lbnRzLmxlbmd0aCkge1xyXG4gICAgdGhpcy5fY2FsbGJhY2tzID0ge307XHJcbiAgICByZXR1cm4gdGhpcztcclxuICB9XHJcblxyXG4gIC8vIHNwZWNpZmljIGV2ZW50XHJcbiAgdmFyIGNhbGxiYWNrcyA9IHRoaXMuX2NhbGxiYWNrc1snJCcgKyBldmVudF07XHJcbiAgaWYgKCFjYWxsYmFja3MpIHJldHVybiB0aGlzO1xyXG5cclxuICAvLyByZW1vdmUgYWxsIGhhbmRsZXJzXHJcbiAgaWYgKDEgPT0gYXJndW1lbnRzLmxlbmd0aCkge1xyXG4gICAgZGVsZXRlIHRoaXMuX2NhbGxiYWNrc1snJCcgKyBldmVudF07XHJcbiAgICByZXR1cm4gdGhpcztcclxuICB9XHJcblxyXG4gIC8vIHJlbW92ZSBzcGVjaWZpYyBoYW5kbGVyXHJcbiAgdmFyIGNiO1xyXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgY2FsbGJhY2tzLmxlbmd0aDsgaSsrKSB7XHJcbiAgICBjYiA9IGNhbGxiYWNrc1tpXTtcclxuICAgIGlmIChjYiA9PT0gZm4gfHwgY2IuZm4gPT09IGZuKSB7XHJcbiAgICAgIGNhbGxiYWNrcy5zcGxpY2UoaSwgMSk7XHJcbiAgICAgIGJyZWFrO1xyXG4gICAgfVxyXG4gIH1cclxuICByZXR1cm4gdGhpcztcclxufTtcclxuXHJcbi8qKlxyXG4gKiBFbWl0IGBldmVudGAgd2l0aCB0aGUgZ2l2ZW4gYXJncy5cclxuICpcclxuICogQHBhcmFtIHtTdHJpbmd9IGV2ZW50XHJcbiAqIEBwYXJhbSB7TWl4ZWR9IC4uLlxyXG4gKiBAcmV0dXJuIHtFbWl0dGVyfVxyXG4gKi9cclxuXHJcbkVtaXR0ZXIucHJvdG90eXBlLmVtaXQgPSBmdW5jdGlvbihldmVudCl7XHJcbiAgdGhpcy5fY2FsbGJhY2tzID0gdGhpcy5fY2FsbGJhY2tzIHx8IHt9O1xyXG4gIHZhciBhcmdzID0gW10uc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpXHJcbiAgICAsIGNhbGxiYWNrcyA9IHRoaXMuX2NhbGxiYWNrc1snJCcgKyBldmVudF07XHJcblxyXG4gIGlmIChjYWxsYmFja3MpIHtcclxuICAgIGNhbGxiYWNrcyA9IGNhbGxiYWNrcy5zbGljZSgwKTtcclxuICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBjYWxsYmFja3MubGVuZ3RoOyBpIDwgbGVuOyArK2kpIHtcclxuICAgICAgY2FsbGJhY2tzW2ldLmFwcGx5KHRoaXMsIGFyZ3MpO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgcmV0dXJuIHRoaXM7XHJcbn07XHJcblxyXG4vKipcclxuICogUmV0dXJuIGFycmF5IG9mIGNhbGxiYWNrcyBmb3IgYGV2ZW50YC5cclxuICpcclxuICogQHBhcmFtIHtTdHJpbmd9IGV2ZW50XHJcbiAqIEByZXR1cm4ge0FycmF5fVxyXG4gKiBAYXBpIHB1YmxpY1xyXG4gKi9cclxuXHJcbkVtaXR0ZXIucHJvdG90eXBlLmxpc3RlbmVycyA9IGZ1bmN0aW9uKGV2ZW50KXtcclxuICB0aGlzLl9jYWxsYmFja3MgPSB0aGlzLl9jYWxsYmFja3MgfHwge307XHJcbiAgcmV0dXJuIHRoaXMuX2NhbGxiYWNrc1snJCcgKyBldmVudF0gfHwgW107XHJcbn07XHJcblxyXG4vKipcclxuICogQ2hlY2sgaWYgdGhpcyBlbWl0dGVyIGhhcyBgZXZlbnRgIGhhbmRsZXJzLlxyXG4gKlxyXG4gKiBAcGFyYW0ge1N0cmluZ30gZXZlbnRcclxuICogQHJldHVybiB7Qm9vbGVhbn1cclxuICogQGFwaSBwdWJsaWNcclxuICovXHJcblxyXG5FbWl0dGVyLnByb3RvdHlwZS5oYXNMaXN0ZW5lcnMgPSBmdW5jdGlvbihldmVudCl7XHJcbiAgcmV0dXJuICEhIHRoaXMubGlzdGVuZXJzKGV2ZW50KS5sZW5ndGg7XHJcbn07XHJcbiJdfQ==\n\n /*global JSONEditor*/\n'use strict';\n\nwindow.SwaggerUi = Backbone.Router.extend({\n\n  dom_id: 'swagger_ui',\n\n  // Attributes\n  options: null,\n  api: null,\n  headerView: null,\n  mainView: null,\n\n  // SwaggerUi accepts all the same options as SwaggerApi\n  initialize: function(options) {\n    options = options || {};\n\n    if (options.defaultModelRendering !== 'model') {\n      options.defaultModelRendering = 'schema';\n    }\n\n    if (!options.highlightSizeThreshold) {\n      options.highlightSizeThreshold = 100000;\n    }\n\n    // Allow dom_id to be overridden\n    if (options.dom_id) {\n      this.dom_id = options.dom_id;\n      delete options.dom_id;\n    }\n\n    if (!options.supportedSubmitMethods){\n      options.supportedSubmitMethods = [\n        'get',\n        'put',\n        'post',\n        'delete',\n        'head',\n        'options',\n        'patch'\n      ];\n    }\n\n    if (typeof options.oauth2RedirectUrl === 'string') {\n      window.oAuthRedirectUrl = options.oauth2RedirectUrl;\n    }\n\n    // Create an empty div which contains the dom_id\n    if (! $('#' + this.dom_id).length){\n      $('body').append('<div id=\"' + this.dom_id + '\"></div>') ;\n    }\n\n    this.options = options;\n\n    // set marked options\n    marked.setOptions({gfm: true});\n\n    // Set the callbacks\n    var that = this;\n    this.options.success = function() { return that.render(); };\n    this.options.progress = function(d) { return that.showMessage(d); };\n    this.options.failure = function(d) { return that.onLoadFailure(d); };\n\n    // Create view to handle the header inputs\n    this.headerView = new SwaggerUi.Views.HeaderView({el: $('#header')});\n\n    // Event handler for when the baseUrl/apiKey is entered by user\n    this.headerView.on('update-swagger-ui', function(data) {\n      return that.updateSwaggerUi(data);\n    });\n\n    // JSon Editor custom theming\n     JSONEditor.defaults.iconlibs.swagger = JSONEditor.AbstractIconLib.extend({\n      mapping: {\n        collapse: 'collapse',\n        expand: 'expand'\n        },\n      icon_prefix: 'swagger-'\n      });\n\n  },\n\n  // Set an option after initializing\n  setOption: function(option, value) {\n    this.options[option] = value;\n  },\n\n  // Get the value of a previously set option\n  getOption: function(option) {\n    return this.options[option];\n  },\n\n  // Event handler for when url/key is received from user\n  updateSwaggerUi: function(data){\n    this.options.url = data.url;\n    this.load();\n  },\n\n  // Create an api and render\n  load: function(){\n    // Initialize the API object\n    if (this.mainView) {\n      this.mainView.clear();\n    }\n\n    if (this.authView) {\n      this.authView.remove();\n    }\n    var url = this.options.url;\n    if (url && url.indexOf('http') !== 0) {\n      url = this.buildUrl(window.location.href.toString(), url);\n    }\n    if(this.api) {\n      this.options.authorizations = this.api.clientAuthorizations.authz;\n    }\n    this.options.url = url;\n    this.headerView.update(url);\n\n    this.api = new SwaggerClient(this.options);\n  },\n\n  // collapse all sections\n  collapseAll: function(){\n    Docs.collapseEndpointListForResource('');\n  },\n\n  // list operations for all sections\n  listAll: function(){\n    Docs.collapseOperationsForResource('');\n  },\n\n  // expand operations for all sections\n  expandAll: function(){\n    Docs.expandOperationsForResource('');\n  },\n\n  // This is bound to success handler for SwaggerApi\n  //  so it gets called when SwaggerApi completes loading\n  render: function(){\n    var authsModel;\n    this.showMessage('Finished Loading Resource Information. Rendering Swagger UI...');\n    this.mainView = new SwaggerUi.Views.MainView({\n      model: this.api,\n      el: $('#' + this.dom_id),\n      swaggerOptions: this.options,\n      router: this\n    }).render();\n    if (!_.isEmpty(this.api.securityDefinitions)){\n      authsModel = _.map(this.api.securityDefinitions, function (auth, name) {\n        var result = {};\n        result[name] = auth;\n        return result;\n      });\n      this.authView = new SwaggerUi.Views.AuthButtonView({\n        data: SwaggerUi.utils.parseSecurityDefinitions(authsModel),\n        router: this\n      });\n      $('#auth_container').append(this.authView.render().el);\n    }\n    this.showMessage();\n    switch (this.options.docExpansion) {\n      case 'full':\n        this.expandAll(); break;\n      case 'list':\n        this.listAll(); break;\n      default:\n        break;\n    }\n    this.renderGFM();\n\n    if (this.options.onComplete){\n      this.options.onComplete(this.api, this);\n    }\n\n    setTimeout(Docs.shebang.bind(this), 100);\n  },\n\n  buildUrl: function(base, url){\n    if (url.indexOf('/') === 0) {\n      var parts = base.split('/');\n      base = parts[0] + '//' + parts[2];\n      return base + url;\n    } else {\n      var endOfPath = base.length;\n\n      if (base.indexOf('?') > -1){\n        endOfPath = Math.min(endOfPath, base.indexOf('?'));\n      }\n\n      if (base.indexOf('#') > -1){\n        endOfPath = Math.min(endOfPath, base.indexOf('#'));\n      }\n\n      base = base.substring(0, endOfPath);\n\n      if (base.indexOf('/', base.length - 1 ) !== -1){\n        return base + url;\n      }\n\n      return base + '/' + url;\n    }\n  },\n\n  // Shows message on topbar of the ui\n  showMessage: function(data){\n    if (data === undefined) {\n      data = '';\n    }\n    var $msgbar = $('#message-bar');\n    $msgbar.removeClass('message-fail');\n    $msgbar.addClass('message-success');\n    $msgbar.text(data);\n    if(window.SwaggerTranslator) {\n      window.SwaggerTranslator.translate($msgbar);\n    }\n  },\n\n  // shows message in red\n  onLoadFailure: function(data){\n    if (data === undefined) {\n      data = '';\n    }\n    $('#message-bar').removeClass('message-success');\n    $('#message-bar').addClass('message-fail');\n\n    var val = $('#message-bar').text(data);\n\n    if (this.options.onFailure) {\n      this.options.onFailure(data);\n    }\n\n    return val;\n  },\n\n  // Renders GFM for elements with 'markdown' class\n  renderGFM: function(){\n    $('.markdown').each(function(){\n      $(this).html(marked($(this).html()));\n    });\n\n    $('.propDesc', '.model-signature .description').each(function () {\n      $(this).html(marked($(this).html())).addClass('markdown');\n    });\n  }\n\n});\n\nwindow.SwaggerUi.Views = {};\nwindow.SwaggerUi.Models = {};\nwindow.SwaggerUi.Collections = {};\nwindow.SwaggerUi.partials = {};\nwindow.SwaggerUi.utils = {};\n\n// don't break backward compatibility with previous versions and warn users to upgrade their code\n(function(){\n  window.authorizations = {\n    add: function() {\n      warn('Using window.authorizations is deprecated. Please use SwaggerUi.api.clientAuthorizations.add().');\n\n      if (typeof window.swaggerUi === 'undefined') {\n        throw new TypeError('window.swaggerUi is not defined');\n      }\n\n      if (window.swaggerUi instanceof SwaggerUi) {\n        window.swaggerUi.api.clientAuthorizations.add.apply(window.swaggerUi.api.clientAuthorizations, arguments);\n      }\n    }\n  };\n\n  window.ApiKeyAuthorization = function() {\n    warn('window.ApiKeyAuthorization is deprecated. Please use SwaggerClient.ApiKeyAuthorization.');\n    SwaggerClient.ApiKeyAuthorization.apply(window, arguments);\n  };\n\n  window.PasswordAuthorization = function() {\n    warn('window.PasswordAuthorization is deprecated. Please use SwaggerClient.PasswordAuthorization.');\n    SwaggerClient.PasswordAuthorization.apply(window, arguments);\n  };\n\n  function warn(message) {\n    if ('console' in window && typeof window.console.warn === 'function') {\n      console.warn(message);\n    }\n  }\n})();\n\n\n// UMD\n(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n        // AMD. Register as an anonymous module.\n        define(['b'], function (b) {\n            return (root.SwaggerUi = factory(b));\n        });\n    } else if (typeof exports === 'object') {\n        // Node. Does not work with strict CommonJS, but\n        // only CommonJS-like environments that support module.exports,\n        // like Node.\n        module.exports = factory(require('b'));\n    } else {\n        // Browser globals\n        root.SwaggerUi = factory(root.b);\n    }\n}(this, function () {\n    return SwaggerUi;\n}));\n\n'use strict';\n\nwindow.SwaggerUi.utils = {\n    parseSecurityDefinitions: function (security) {\n        var auths = Object.assign({}, window.swaggerUi.api.authSchemes || window.swaggerUi.api.securityDefinitions);\n        var oauth2Arr = [];\n        var authsArr = [];\n        var scopes = [];\n        var utils = window.SwaggerUi.utils;\n\n        if (!Array.isArray(security)) { return null; }\n\n        security.forEach(function (item) {\n            var singleSecurity = {};\n            var singleOauth2Security = {};\n\n            for (var key in item) {\n                if (Array.isArray(item[key])) {\n                    if (!auths[key]) { continue; }\n                    auths[key] = auths[key] || {};\n                    if (auths[key].type === 'oauth2') {\n                        singleOauth2Security[key] = Object.assign({}, auths[key]);\n                        singleOauth2Security[key].scopes = Object.assign({}, auths[key].scopes);\n                        for (var i in singleOauth2Security[key].scopes) {\n                            if (item[key].indexOf(i) < 0) {\n                                delete singleOauth2Security[key].scopes[i];\n                            }\n                        }\n                        singleOauth2Security[key].scopes = utils.parseOauth2Scopes(singleOauth2Security[key].scopes);\n                        scopes = _.merge(scopes, singleOauth2Security[key].scopes);\n                    } else {\n                        singleSecurity[key] = Object.assign({}, auths[key]);\n                    }\n                } else {\n                    if (item[key].type === 'oauth2') {\n                        singleOauth2Security[key] = Object.assign({}, item[key]);\n                        singleOauth2Security[key].scopes = utils.parseOauth2Scopes(singleOauth2Security[key].scopes);\n                        scopes = _.merge(scopes, singleOauth2Security[key].scopes);\n                    } else {\n                        singleSecurity[key] = item[key];\n                    }\n                }\n            }\n\n            if (!_.isEmpty(singleSecurity)) {\n                authsArr.push(singleSecurity);\n            }\n\n            if (!_.isEmpty(singleOauth2Security)){\n                oauth2Arr.push(singleOauth2Security);\n            }\n        });\n\n        return {\n            auths : authsArr,\n            oauth2: oauth2Arr,\n            scopes: scopes\n        };\n    },\n\n    parseOauth2Scopes: function (data) {\n        var scopes = Object.assign({}, data);\n        var result = [];\n        var key;\n\n        for (key in scopes) {\n            result.push({scope: key, description: scopes[key]});\n        }\n\n        return result;\n    },\n\n    sanitize: function(html) {\n        // Strip the script tags from the html and inline evenhandlers\n        html = html.replace(/<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi, '');\n        html = html.replace(/(on\\w+=\"[^\"]*\")*(on\\w+='[^']*')*(on\\w+=\\w*\\(\\w*\\))*/gi, '');\n\n        return html;\n    }\n};\n'use strict';\n\nSwaggerUi.Models.ApiKeyAuthModel = Backbone.Model.extend({\n    defaults: {\n        'in': '',\n        name: '',\n        title: '',\n        value: ''\n    },\n\n    initialize: function () {\n        this.on('change', this.validate);\n    },\n\n    validate: function () {\n        var valid = !!this.get('value');\n\n        this.set('valid', valid);\n\n        return valid;\n    }\n});\n'use strict';\n\nSwaggerUi.Views.ApiKeyAuthView = Backbone.View.extend({ // TODO: append this to global SwaggerUi\n\n    events: {\n        'change .input_apiKey_entry': 'apiKeyChange'\n    },\n\n    selectors: {\n        apikeyInput: '.input_apiKey_entry'\n    },\n\n    template: Handlebars.templates.apikey_auth,\n\n    initialize: function(opts) {\n        this.options = opts || {};\n        this.router = this.options.router;\n    },\n\n    render: function (){\n        this.$el.html(this.template(this.model.toJSON()));\n\n        return this;\n    },\n\n    apiKeyChange: function (e) {\n        var val = $(e.target).val();\n        if (val) {\n            this.$(this.selectors.apikeyInput).removeClass('error');\n        }\n\n        this.model.set('value', val);\n    },\n\n    isValid: function () {\n        return this.model.validate();\n    },\n\n    highlightInvalid: function () {\n        if (!this.isValid()) {\n            this.$(this.selectors.apikeyInput).addClass('error');\n        }\n    }\n\n});\n'use strict';\n\nSwaggerUi.Views.AuthButtonView = Backbone.View.extend({\n    events: {\n        'click .authorize__btn': 'authorizeBtnClick'\n    },\n\n    tpls: {\n        popup: Handlebars.templates.popup,\n        authBtn: Handlebars.templates.auth_button,\n        authBtnOperation: Handlebars.templates.auth_button_operation\n    },\n\n    initialize: function(opts) {\n        this.options = opts || {};\n        this.options.data = this.options.data || {};\n        this.isOperation = this.options.isOperation;\n        this.model = this.model || {};\n        this.router = this.options.router;\n        this.auths = this.options.data.oauth2.concat(this.options.data.auths);\n    },\n\n    render: function () {\n        var tplName = this.isOperation ? 'authBtnOperation' : 'authBtn';\n\n        this.$authEl = this.renderAuths(this.auths);\n        this.$el.html(this.tpls[tplName](this.model));\n\n        return this;\n    },\n\n    authorizeBtnClick: function (e) {\n        var authsModel;\n\n        e.preventDefault();\n\n        authsModel = {\n            title: 'Available authorizations',\n            content: this.$authEl\n        };\n\n        // The content of the popup is removed and all events unbound after clicking the 'Cancel' button of the popup.\n        // We'll have to re-render the contents before creating a new popup view.\n        this.render();\n\n        this.popup = new SwaggerUi.Views.PopupView({model: authsModel});\n        this.popup.render();\n    },\n\n    renderAuths: function (auths) {\n        var $el = $('<div>');\n        var isLogout = false;\n\n        auths.forEach(function (auth) {\n            var authView = new SwaggerUi.Views.AuthView({data: auth, router: this.router});\n            var authEl = authView.render().el;\n            $el.append(authEl);\n            if (authView.isLogout) {\n                isLogout = true;\n            }\n        }, this);\n\n        this.model.isLogout = isLogout;\n\n        return $el;\n    }\n\n});\n\n'use strict';\n\nSwaggerUi.Collections.AuthsCollection = Backbone.Collection.extend({\n    constructor: function() {\n        var args = Array.prototype.slice.call(arguments);\n\n        args[0] = this.parse(args[0]);\n\n        Backbone.Collection.apply(this, args);\n    },\n\n    add: function (model) {\n        var args = Array.prototype.slice.call(arguments);\n\n        if (Array.isArray(model)) {\n            args[0] = _.map(model, function(val) {\n                return this.handleOne(val);\n            }, this);\n        } else {\n            args[0] = this.handleOne(model);\n        }\n\n        Backbone.Collection.prototype.add.apply(this, args);\n    },\n\n    handleOne: function (model) {\n        var result = model;\n\n        if (! (model instanceof Backbone.Model) ) {\n            switch (model.type) {\n                case 'oauth2':\n                    result = new SwaggerUi.Models.Oauth2Model(model);\n                    break;\n                case 'basic':\n                    result = new SwaggerUi.Models.BasicAuthModel(model);\n                    break;\n                case 'apiKey':\n                    result = new SwaggerUi.Models.ApiKeyAuthModel(model);\n                    break;\n                default:\n                    result = new Backbone.Model(model);\n            }\n        }\n\n        return result;\n    },\n\n    isValid: function () {\n        var valid = true;\n\n        this.models.forEach(function(model) {\n            if (!model.validate()) {\n                valid = false;\n            }\n        });\n\n        return valid;\n    },\n\n    isAuthorized: function () {\n        return this.length === this.where({ isLogout: true }).length;\n    },\n\n    isPartiallyAuthorized: function () {\n        return this.where({ isLogout: true }).length > 0;\n    },\n\n    parse: function (data) {\n        var authz = Object.assign({}, window.swaggerUi.api.clientAuthorizations.authz);\n\n        return _.map(data, function (auth, name) {\n            var isBasic = authz[name] && auth.type === 'basic' && authz[name].username && authz[name].password;\n\n            _.extend(auth, {\n                title: name\n            });\n\n            if (authz[name] || isBasic) {\n                _.extend(auth, {\n                    isLogout: true,\n                    value: isBasic ? undefined : authz[name].value,\n                    username: isBasic ? authz[name].username : undefined,\n                    password: isBasic ? authz[name].password : undefined,\n                    valid: true\n                });\n            }\n\n            return auth;\n        });\n    }\n});\n'use strict';\n\nSwaggerUi.Views.AuthsCollectionView = Backbone.View.extend({\n\n    initialize: function(opts) {\n        this.options = opts || {};\n        this.options.data = this.options.data || {};\n        this.router = this.options.router;\n\n        this.collection = new SwaggerUi.Collections.AuthsCollection(opts.data);\n\n        this.$innerEl = $('<div>');\n        this.authViews = [];\n    },\n\n    render: function () {\n        this.collection.each(function (auth) {\n            this.renderOneAuth(auth);\n        }, this);\n\n        this.$el.html(this.$innerEl.html() ? this.$innerEl : '');\n\n        return this;\n    },\n\n    renderOneAuth: function (authModel) {\n        var authViewEl, authView, authViewName;\n        var type = authModel.get('type');\n\n        if (type === 'apiKey') {\n            authViewName = 'ApiKeyAuthView';\n        } else if (type === 'basic' && this.$innerEl.find('.basic_auth_container').length === 0) {\n            authViewName = 'BasicAuthView';\n        } else if (type === 'oauth2') {\n            authViewName = 'Oauth2View';\n        }\n\n        if (authViewName) {\n            authView = new SwaggerUi.Views[authViewName]({model: authModel, router: this.router});\n            authViewEl = authView.render().el;\n            this.authViews.push(authView);\n        }\n\n        this.$innerEl.append(authViewEl);\n    },\n\n    highlightInvalid: function () {\n        this.authViews.forEach(function (view) {\n            view.highlightInvalid();\n        }, this);\n    }\n\n});\n\n'use strict';\n\n/* global redirect_uri:true */\n/* global clientId */\n/* global scopeSeparator */\n/* global additionalQueryStringParams */\n/* global clientSecret */\n/* global onOAuthComplete */\n/* global realm */\n/*jshint unused:false*/\n\nSwaggerUi.Views.AuthView = Backbone.View.extend({\n    events: {\n        'click .auth_submit__button': 'authorizeClick',\n        'click .auth_logout__button': 'logoutClick'\n    },\n\n    tpls: {\n        main: Handlebars.templates.auth_view\n    },\n\n    selectors: {\n        innerEl: '.auth_inner',\n        authBtn: '.auth_submit__button'\n    },\n\n    initialize: function(opts) {\n        this.options = opts || {};\n        opts.data = opts.data || {};\n        this.router = this.options.router;\n\n        this.authsCollectionView = new SwaggerUi.Views.AuthsCollectionView({data: opts.data});\n\n        this.$el.html(this.tpls.main({\n            isLogout: this.authsCollectionView.collection.isAuthorized(),\n            isAuthorized: this.authsCollectionView.collection.isPartiallyAuthorized()\n        }));\n        this.$innerEl = this.$(this.selectors.innerEl);\n        this.isLogout = this.authsCollectionView.collection.isPartiallyAuthorized();\n    },\n\n    render: function () {\n        this.$innerEl.html(this.authsCollectionView.render().el);\n\n        return this;\n    },\n\n    authorizeClick: function (e) {\n        e.preventDefault();\n        e.stopPropagation();\n\n        if (this.authsCollectionView.collection.isValid()) {\n            this.authorize();\n        } else {\n            this.authsCollectionView.highlightInvalid();\n        }\n    },\n\n    authorize: function () {\n        this.authsCollectionView.collection.forEach(function (auth) {\n            var keyAuth, basicAuth;\n            var type = auth.get('type');\n\n            if (type === 'apiKey') {\n                keyAuth = new SwaggerClient.ApiKeyAuthorization(\n                    auth.get('name'),\n                    auth.get('value'),\n                    auth.get('in')\n                );\n\n                this.router.api.clientAuthorizations.add(auth.get('title'), keyAuth);\n            } else if (type === 'basic') {\n                basicAuth = new SwaggerClient.PasswordAuthorization(auth.get('username'), auth.get('password'));\n                this.router.api.clientAuthorizations.add(auth.get('title'), basicAuth);\n            } else if (type === 'oauth2') {\n                this.handleOauth2Login(auth);\n            }\n        }, this);\n\n        this.router.load();\n    },\n\n    logoutClick: function (e) {\n        e.preventDefault();\n\n        this.authsCollectionView.collection.forEach(function (auth) {\n            window.swaggerUi.api.clientAuthorizations.remove(auth.get('title'));\n        });\n\n        this.router.load();\n    },\n\n    // taken from lib/swagger-oauth.js\n    handleOauth2Login: function (auth) {\n        var host = window.location;\n        var pathname = location.pathname.substring(0, location.pathname.lastIndexOf('/'));\n        var defaultRedirectUrl = host.protocol + '//' + host.host + pathname + '/o2c.html';\n        var redirectUrl = window.oAuthRedirectUrl || defaultRedirectUrl;\n        var url = null;\n        var scopes = _.map(auth.get('scopes'), function (scope) {\n            return scope.scope;\n        });\n        var state, dets, ep;\n        window.OAuthSchemeKey = auth.get('title');\n\n        window.enabledScopes = scopes;\n        var flow = auth.get('flow');\n\n        if(auth.get('type') === 'oauth2' && flow && (flow === 'implicit' || flow === 'accessCode')) {\n            dets = auth.attributes;\n            url = dets.authorizationUrl + '?response_type=' + (flow === 'implicit' ? 'token' : 'code');\n            window.swaggerUi.tokenName = dets.tokenName || 'access_token';\n            window.swaggerUi.tokenUrl = (flow === 'accessCode' ? dets.tokenUrl : null);\n            state = window.OAuthSchemeKey;\n        }\n        else if(auth.get('type') === 'oauth2' && flow && (flow === 'application')) {\n            dets = auth.attributes;\n            window.swaggerUi.tokenName = dets.tokenName || 'access_token';\n            this.clientCredentialsFlow(scopes, dets.tokenUrl, window.OAuthSchemeKey);\n            return;\n        }\n        else if(auth.get('grantTypes')) {\n            // 1.2 support\n            var o = auth.get('grantTypes');\n            for(var t in o) {\n                if(o.hasOwnProperty(t) && t === 'implicit') {\n                    dets = o[t];\n                    ep = dets.loginEndpoint.url;\n                    url = dets.loginEndpoint.url + '?response_type=token';\n                    window.swaggerUi.tokenName = dets.tokenName;\n                }\n                else if (o.hasOwnProperty(t) && t === 'accessCode') {\n                    dets = o[t];\n                    ep = dets.tokenRequestEndpoint.url;\n                    url = dets.tokenRequestEndpoint.url + '?response_type=code';\n                    window.swaggerUi.tokenName = dets.tokenName;\n                }\n            }\n        }\n\n        redirect_uri = redirectUrl;\n\n        url += '&redirect_uri=' + encodeURIComponent(redirectUrl);\n        url += '&realm=' + encodeURIComponent(realm);\n        url += '&client_id=' + encodeURIComponent(clientId);\n        url += '&scope=' + encodeURIComponent(scopes.join(scopeSeparator));\n        url += '&state=' + encodeURIComponent(state);\n        for (var key in additionalQueryStringParams) {\n            url += '&' + key + '=' + encodeURIComponent(additionalQueryStringParams[key]);\n        }\n\n        window.open(url);\n    },\n\n    // taken from lib/swagger-oauth.js\n    clientCredentialsFlow: function (scopes, tokenUrl, OAuthSchemeKey) {\n        var params = {\n            'client_id': clientId,\n            'client_secret': clientSecret,\n            'scope': scopes.join(' '),\n            'grant_type': 'client_credentials'\n        };\n        $.ajax({\n            url : tokenUrl,\n            type: 'POST',\n            data: params,\n            success: function (data)\n            {\n                onOAuthComplete(data, OAuthSchemeKey);\n            },\n            error: function ()\n            {\n                onOAuthComplete('');\n            }\n        });\n    }\n\n});\n\n'use strict';\n\nSwaggerUi.Models.BasicAuthModel = Backbone.Model.extend({\n    defaults: {\n        username: '',\n        password: '',\n        title: 'basic'\n    },\n\n    initialize: function () {\n        this.on('change', this.validate);\n    },\n\n    validate: function () {\n        var valid = !!this.get('password') && !!this.get('username');\n\n        this.set('valid', valid);\n\n        return valid;\n    }\n});\n'use strict';\n\nSwaggerUi.Views.BasicAuthView = Backbone.View.extend({\n\n    initialize: function (opts) {\n        this.options = opts || {};\n        this.router = this.options.router;\n    },\n\n    events: {\n        'change .auth_input': 'inputChange'\n    },\n\n    selectors: {\n        usernameInput: '.basic_auth__username',\n        passwordInput: '.basic_auth__password'\n    },\n\n    cls: {\n        error: 'error'\n    },\n\n    template: Handlebars.templates.basic_auth,\n\n    render: function(){\n        $(this.el).html(this.template(this.model.toJSON()));\n\n        return this;\n    },\n\n    inputChange: function (e) {\n        var $el = $(e.target);\n        var val = $el.val();\n        var attr = $el.prop('name');\n\n        if (val) {\n            $el.removeClass(this.cls.error);\n        }\n\n        this.model.set(attr, val);\n    },\n\n    isValid: function () {\n        return this.model.validate();\n    },\n\n    highlightInvalid: function () {\n        if (!this.model.get('username')) {\n            this.$(this.selectors.usernameInput).addClass(this.cls.error);\n        }\n    }\n});\n\n'use strict';\n\nSwaggerUi.Views.ContentTypeView = Backbone.View.extend({\n  initialize: function() {},\n\n  render: function(){\n  \tthis.model.contentTypeId = 'ct' + Math.random();\n    $(this.el).html(Handlebars.templates.content_type(this.model));\n    return this;\n  }\n});\n'use strict';\n\nSwaggerUi.Views.HeaderView = Backbone.View.extend({\n  events: {\n    'click #show-pet-store-icon'    : 'showPetStore',\n    'click #explore'                : 'showCustom',\n    'submit #api_selector'          : 'showCustom',\n    'keyup #input_baseUrl'          : 'showCustomOnKeyup',\n    'keyup #input_apiKey'           : 'showCustomOnKeyup'\n  },\n\n  initialize: function(){},\n\n  showPetStore: function(){\n    this.trigger('update-swagger-ui', {\n      url:'http://petstore.swagger.io/v2/swagger.json'\n    });\n  },\n\n  showCustomOnKeyup: function(e){\n    if (e.keyCode === 13) {\n      this.showCustom();\n    }\n  },\n\n  showCustom: function(e){\n    if (e) {\n      e.preventDefault();\n    }\n\n    this.trigger('update-swagger-ui', {\n      url: $('#input_baseUrl').val()\n    });\n  },\n\n  update: function(url, apiKey, trigger){\n    if (trigger === undefined) {\n      trigger = false;\n    }\n\n    $('#input_baseUrl').val(url);\n\n    if (trigger) {\n      this.trigger('update-swagger-ui', {url:url});\n    }\n  }\n});\n\n'use strict';\n\nSwaggerUi.Views.MainView = Backbone.View.extend({\n  apisSorter : {\n    alpha   : function(a,b){ return a.name.localeCompare(b.name); }\n  },\n  operationsSorters : {\n    alpha   : function(a,b){ return a.path.localeCompare(b.path); },\n    method  : function(a,b){ return a.method.localeCompare(b.method); }\n  },\n  initialize: function(opts){\n    var sorterOption, sorterFn, key, value;\n    opts = opts || {};\n\n    this.router = opts.router;\n\n    // Sort APIs\n    if (opts.swaggerOptions.apisSorter) {\n      sorterOption = opts.swaggerOptions.apisSorter;\n      if (_.isFunction(sorterOption)) {\n        sorterFn = sorterOption;\n      } else {\n        sorterFn = this.apisSorter[sorterOption];\n      }\n      if (_.isFunction(sorterFn)) {\n        this.model.apisArray.sort(sorterFn);\n      }\n    }\n    // Sort operations of each API\n    if (opts.swaggerOptions.operationsSorter) {\n      sorterOption = opts.swaggerOptions.operationsSorter;\n      if (_.isFunction(sorterOption)) {\n        sorterFn = sorterOption;\n      } else {\n        sorterFn = this.operationsSorters[sorterOption];\n      }\n      if (_.isFunction(sorterFn)) {\n        for (key in this.model.apisArray) {\n          this.model.apisArray[key].operationsArray.sort(sorterFn);\n        }\n      }\n    }\n\n    // set up the UI for input\n    this.model.auths = [];\n\n    for (key in this.model.securityDefinitions) {\n      value = this.model.securityDefinitions[key];\n\n      this.model.auths.push({\n        name: key,\n        type: value.type,\n        value: value\n      });\n    }\n\n    if ('validatorUrl' in opts.swaggerOptions) {\n      // Validator URL specified explicitly\n      this.model.validatorUrl = opts.swaggerOptions.validatorUrl;\n    } else if (this.model.url.indexOf('localhost') > 0 || this.model.url.indexOf('127.0.0.1') > 0) {\n      // Localhost override\n      this.model.validatorUrl = null;\n    } else {\n      this.model.validatorUrl = '//online.swagger.io/validator';\n    }\n\n    // JSonEditor requires type='object' to be present on defined types, we add it if it's missing\n    // is there any valid case were it should not be added ?\n    var def;\n    for(def in this.model.definitions){\n      if (!this.model.definitions[def].type){\n        this.model.definitions[def].type = 'object';\n      }\n    }\n\n  },\n\n  render: function () {\n    $(this.el).html(Handlebars.templates.main(this.model));\n    this.info = this.$('.info')[0];\n\n    if (this.info) {\n      this.info.addEventListener('click', this.onLinkClick, true);\n    }\n\n    this.model.securityDefinitions = this.model.securityDefinitions || {};\n\n    // Render each resource\n\n    var resources = {};\n    var counter = 0;\n    for (var i = 0; i < this.model.apisArray.length; i++) {\n      var resource = this.model.apisArray[i];\n      var id = resource.name;\n      while (typeof resources[id] !== 'undefined') {\n        id = id + '_' + counter;\n        counter += 1;\n      }\n      resource.id = sanitizeHtml(id);\n      resources[id] = resource;\n      this.addResource(resource, this.model.auths);\n    }\n\n    $('.propWrap').hover(function onHover(){\n      $('.optionsWrapper', $(this)).show();\n    }, function offhover(){\n      $('.optionsWrapper', $(this)).hide();\n    });\n    return this;\n  },\n\n  addResource: function(resource, auths){\n    // Render a resource and add it to resources li\n    resource.id = resource.id.replace(/\\s/g, '_');\n\n    // Make all definitions available at the root of the resource so that they can\n    // be loaded by the JSonEditor\n    resource.definitions = this.model.definitions;\n\n    var resourceView = new SwaggerUi.Views.ResourceView({\n      model: resource,\n      router: this.router,\n      tagName: 'li',\n      id: 'resource_' + resource.id,\n      className: 'resource',\n      auths: auths,\n      swaggerOptions: this.options.swaggerOptions\n    });\n    $('#resources', this.el).append(resourceView.render().el);\n  },\n\n  clear: function(){\n    $(this.el).html('');\n  },\n\n  onLinkClick: function (e) {\n    var el = e.target;\n\n    if (el.tagName === 'A' && el.href && !el.target) {\n        e.preventDefault();\n        window.open(el.href, '_blank');\n    }\n  }\n});\n\n'use strict';\n\nSwaggerUi.Models.Oauth2Model = Backbone.Model.extend({\n    defaults: {\n        scopes: {}\n    },\n\n    initialize: function () {\n        this.on('change', this.validate);\n    },\n\n    setScopes: function (name, val) {\n        var auth = _.extend({}, this.attributes);\n        var index = _.findIndex(auth.scopes, function(o) {\n            return o.scope === name;\n        });\n        auth.scopes[index].checked = val;\n\n        this.set(auth);\n        this.validate();\n    },\n\n    validate: function () {\n      var valid = false;\n      var scp = this.get('scopes');\n      var idx =  _.findIndex(scp, function (o) {\n         return o.checked === true;\n      });\n\n      if(scp.length > 0 && idx >= 0) {\n          valid = true;\n      }\n\n      if(scp.length === 0) {\n          valid = true;\n      }\n\n      this.set('valid', valid);\n\n      return valid;\n    }\n});\n\n'use strict';\n\nSwaggerUi.Views.Oauth2View = Backbone.View.extend({\n    events: {\n        'change .oauth-scope': 'scopeChange'\n    },\n\n    template: Handlebars.templates.oauth2,\n\n    render: function () {\n        this.$el.html(this.template(this.model.toJSON()));\n\n        return this;\n    },\n\n    scopeChange: function (e) {\n        var val = $(e.target).prop('checked');\n        var scope = $(e.target).data('scope');\n\n        this.model.setScopes(scope, val);\n    }\n});\n'use strict';\n\nSwaggerUi.Views.OperationView = Backbone.View.extend({\n  invocationUrl: null,\n\n  events: {\n    'submit .sandbox'         : 'submitOperation',\n    'click .submit'           : 'submitOperation',\n    'click .response_hider'   : 'hideResponse',\n    'click .toggleOperation'  : 'toggleOperationContent',\n    'mouseenter .api-ic'      : 'mouseEnter',\n    'dblclick .curl'          : 'selectText',\n    'change [name=responseContentType]' : 'showSnippet'\n  },\n\n  initialize: function(opts) {\n    opts = opts || {};\n    this.router = opts.router;\n    this.auths = opts.auths;\n    this.parentId = this.model.parentId;\n    this.nickname = this.model.nickname;\n    this.model.encodedParentId = encodeURIComponent(this.parentId);\n\n    if (opts.swaggerOptions) {\n      this.model.defaultRendering = opts.swaggerOptions.defaultModelRendering;\n\n      if (opts.swaggerOptions.showRequestHeaders) {\n        this.model.showRequestHeaders = true;\n      }\n    }\n    return this;\n  },\n\n  selectText: function(event) {\n    var doc = document,\n        text = event.target.firstChild,\n        range,\n        selection;\n    if (doc.body.createTextRange) {\n      range = document.body.createTextRange();\n      range.moveToElementText(text);\n      range.select();\n    } else if (window.getSelection) {\n      selection = window.getSelection();\n      range = document.createRange();\n      range.selectNodeContents(text);\n      selection.removeAllRanges();\n      selection.addRange(range);\n    }\n  },\n\n  mouseEnter: function(e) {\n    var elem = $(this.el).find('.content');\n    var x = e.pageX;\n    var y = e.pageY;\n    var scX = $(window).scrollLeft();\n    var scY = $(window).scrollTop();\n    var scMaxX = scX + $(window).width();\n    var scMaxY = scY + $(window).height();\n    var wd = elem.width();\n    var hgh = elem.height();\n\n    if (x + wd > scMaxX) {\n      x = scMaxX - wd;\n    }\n\n    if (x < scX) {\n      x = scX;\n    }\n\n    if (y + hgh > scMaxY) {\n      y = scMaxY - hgh;\n    }\n\n    if (y < scY) {\n      y = scY;\n    }\n\n    var pos = {};\n    pos.top = y;\n    pos.left = x;\n    elem.css(pos);\n  },\n\n  // Note: copied from CoffeeScript compiled file\n  // TODO: redactor\n  render: function() {\n    var a, auth, auths, code, contentTypeModel, isMethodSubmissionSupported, k, key, l, len, len1, len2, len3, len4, m, modelAuths, n, o, p, param, q, ref, ref1, ref2, ref3, ref4, ref5, responseContentTypeView, responseSignatureView, schema, schemaObj, scopeIndex, signatureModel, statusCode, successResponse, type, v, value, produces, isXML, isJSON;\n    isMethodSubmissionSupported = jQuery.inArray(this.model.method, this.model.supportedSubmitMethods()) >= 0;\n    if (!isMethodSubmissionSupported) {\n      this.model.isReadOnly = true;\n    }\n    this.model.description = this.model.description || this.model.notes;\n    this.model.oauth = null;\n    modelAuths = this.model.authorizations || this.model.security;\n    if (modelAuths) {\n      if (Array.isArray(modelAuths)) {\n        for (l = 0, len = modelAuths.length; l < len; l++) {\n          auths = modelAuths[l];\n          for (key in auths) {\n            for (a in this.auths) {\n              auth = this.auths[a];\n              if (key === auth.name) {\n                if (auth.type === 'oauth2') {\n                  this.model.oauth = {};\n                  this.model.oauth.scopes = [];\n                  ref1 = auth.value.scopes;\n                  for (k in ref1) {\n                    v = ref1[k];\n                    scopeIndex = auths[key].indexOf(k);\n                    if (scopeIndex >= 0) {\n                      o = {\n                        scope: k,\n                        description: v\n                      };\n                      this.model.oauth.scopes.push(o);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n      } else {\n        for (k in modelAuths) {\n          v = modelAuths[k];\n          if (k === 'oauth2') {\n            if (this.model.oauth === null) {\n              this.model.oauth = {};\n            }\n            if (this.model.oauth.scopes === void 0) {\n              this.model.oauth.scopes = [];\n            }\n            for (m = 0, len1 = v.length; m < len1; m++) {\n              o = v[m];\n              this.model.oauth.scopes.push(o);\n            }\n          }\n        }\n      }\n    }\n    if (typeof this.model.responses !== 'undefined') {\n      this.model.responseMessages = [];\n      ref2 = this.model.responses;\n      for (code in ref2) {\n        value = ref2[code];\n        schema = null;\n        schemaObj = this.model.responses[code].schema;\n        if (schemaObj && schemaObj.$ref) {\n          schema = schemaObj.$ref;\n          if (schema.indexOf('#/definitions/') !== -1) {\n            schema = schema.replace(/^.*#\\/definitions\\//, '');\n          }\n        }\n        this.model.responseMessages.push({\n          code: code,\n          message: value.description,\n          responseModel: schema,\n          headers: value.headers,\n          schema: schemaObj\n        });\n      }\n    }\n    if (typeof this.model.responseMessages === 'undefined') {\n      this.model.responseMessages = [];\n    }\n    signatureModel = null;\n    produces = this.model.produces;\n    isXML = this.contains(produces, 'xml');\n    isJSON = isXML ? this.contains(produces, 'json') : true;\n\n    if (this.model.successResponse) {\n      successResponse = this.model.successResponse;\n      for (key in successResponse) {\n        value = successResponse[key];\n        this.model.successCode = key;\n        if (typeof value === 'object' && typeof value.createJSONSample === 'function') {\n          this.model.successDescription = value.description;\n          this.model.headers = this.parseResponseHeaders(value.headers);\n          signatureModel = {\n            sampleJSON: isJSON ? JSON.stringify(SwaggerUi.partials.signature.createJSONSample(value), void 0, 2) : false,\n            isParam: false,\n            sampleXML: isXML ? SwaggerUi.partials.signature.createXMLSample(value.name, value.definition, value.models) : false,\n            signature: SwaggerUi.partials.signature.getModelSignature(value.name, value.definition, value.models, value.modelPropertyMacro)\n          };\n        } else {\n          signatureModel = {\n            signature: SwaggerUi.partials.signature.getPrimitiveSignature(value)\n          };\n        }\n      }\n    } else if (this.model.responseClassSignature && this.model.responseClassSignature !== 'string') {\n      signatureModel = {\n        sampleJSON: this.model.responseSampleJSON,\n        isParam: false,\n        signature: this.model.responseClassSignature\n      };\n    }\n    $(this.el).html(Handlebars.templates.operation(this.model));\n    if (signatureModel) {\n      signatureModel.defaultRendering = this.model.defaultRendering;\n      responseSignatureView = new SwaggerUi.Views.SignatureView({\n        model: signatureModel,\n        router: this.router,\n        tagName: 'div'\n      });\n      $('.model-signature', $(this.el)).append(responseSignatureView.render().el);\n    } else {\n      this.model.responseClassSignature = 'string';\n      $('.model-signature', $(this.el)).html(this.model.type);\n    }\n    contentTypeModel = {\n      isParam: false\n    };\n    contentTypeModel.consumes = this.model.consumes;\n    contentTypeModel.produces = this.model.produces;\n    ref3 = this.model.parameters;\n    for (n = 0, len2 = ref3.length; n < len2; n++) {\n      param = ref3[n];\n      type = param.type || param.dataType || '';\n      if (typeof type === 'undefined') {\n        schema = param.schema;\n        if (schema && schema.$ref) {\n          ref = schema.$ref;\n          if (ref.indexOf('#/definitions/') === 0) {\n            type = ref.substring('#/definitions/'.length);\n          } else {\n            type = ref;\n          }\n        }\n      }\n      if (type && type.toLowerCase() === 'file') {\n        if (!contentTypeModel.consumes) {\n          contentTypeModel.consumes = 'multipart/form-data';\n        }\n      }\n      param.type = type;\n    }\n    responseContentTypeView = new SwaggerUi.Views.ResponseContentTypeView({\n      model: contentTypeModel,\n      router: this.router\n    });\n    $('.response-content-type', $(this.el)).append(responseContentTypeView.render().el);\n    ref4 = this.model.parameters;\n    for (p = 0, len3 = ref4.length; p < len3; p++) {\n      param = ref4[p];\n      this.addParameter(param, contentTypeModel.consumes);\n    }\n    ref5 = this.model.responseMessages;\n    for (q = 0, len4 = ref5.length; q < len4; q++) {\n      statusCode = ref5[q];\n      statusCode.isXML = isXML;\n      statusCode.isJSON = isJSON;\n      if (!_.isUndefined(statusCode.headers)) {\n        statusCode.headers = this.parseHeadersType(statusCode.headers);\n      }\n      this.addStatusCode(statusCode);\n    }\n\n    if (Array.isArray(this.model.security)) {\n      var authsModel = SwaggerUi.utils.parseSecurityDefinitions(this.model.security);\n\n      authsModel.isLogout = !_.isEmpty(window.swaggerUi.api.clientAuthorizations.authz);\n      this.authView = new SwaggerUi.Views.AuthButtonView({\n        data: authsModel,\n        router: this.router,\n        isOperation: true,\n        model: {\n          scopes: authsModel.scopes\n        }\n      });\n      this.$('.authorize-wrapper').append(this.authView.render().el);\n    }\n\n    this.showSnippet();\n    return this;\n  },\n\n  parseHeadersType: function (headers) {\n    var map = {\n      'string': {\n        'date-time': 'dateTime',\n        'date'     : 'date'\n      }\n    };\n\n    _.forEach(headers, function (header) {\n      var value;\n      header = header || {};\n      value = map[header.type] && map[header.type][header.format];\n      if (!_.isUndefined(value)) {\n        header.type = value;\n      }\n    });\n\n    return headers;\n  },\n\n  contains: function (produces, type) {\n    return produces.filter(function (val) {\n      if (val.indexOf(type) > -1) {\n        return true;\n      }\n    }).length;\n  },\n\n  parseResponseHeaders: function (data) {\n    var HEADERS_SEPARATOR = '; ';\n    var headers = _.clone(data);\n\n    _.forEach(headers, function (header) {\n      var other = [];\n      _.forEach(header, function (value, key) {\n        var properties = ['type', 'description'];\n        if (properties.indexOf(key.toLowerCase()) === -1) {\n          other.push(key + ': ' + value);\n        }\n      });\n\n      other.join(HEADERS_SEPARATOR);\n      header.other = other;\n    });\n\n    return headers;\n  },\n\n  addParameter: function(param, consumes) {\n    // Render a parameter\n    param.consumes = consumes;\n    param.defaultRendering = this.model.defaultRendering;\n\n    // Copy this param JSON spec so that it will be available for JsonEditor\n    if(param.schema){\n      $.extend(true, param.schema, this.model.definitions[param.type]);\n      param.schema.definitions = this.model.definitions;\n      // This is required for JsonEditor to display the root properly\n      if(!param.schema.type){\n        param.schema.type = 'object';\n      }\n      // This is the title that will be used by JsonEditor for the root\n      // Since we already display the parameter's name in the Parameter column\n      // We set this to space, we can't set it to null or space otherwise JsonEditor\n      // will replace it with the text \"root\" which won't look good on screen\n      if(!param.schema.title){\n        param.schema.title = ' ';\n      }\n    }\n\n    var paramView = new SwaggerUi.Views.ParameterView({\n      model: param,\n      tagName: 'tr',\n      readOnly: this.model.isReadOnly,\n      swaggerOptions: this.options.swaggerOptions\n    });\n    $('.operation-params', $(this.el)).append(paramView.render().el);\n  },\n\n  addStatusCode: function(statusCode) {\n    // Render status codes\n    statusCode.defaultRendering = this.model.defaultRendering;\n    var statusCodeView = new SwaggerUi.Views.StatusCodeView({\n      model: statusCode,\n      tagName: 'tr',\n      router: this.router\n    });\n    $('.operation-status', $(this.el)).append(statusCodeView.render().el);\n  },\n\n  // Note: copied from CoffeeScript compiled file\n  // TODO: redactor\n  submitOperation: function(e) {\n    var error_free, form, isFileUpload, map, opts;\n    if (e !== null) {\n      e.preventDefault();\n    }\n    form = $('.sandbox', $(this.el));\n    error_free = true;\n    form.find('input.required').each(function() {\n      $(this).removeClass('error');\n      if (jQuery.trim($(this).val()) === '') {\n        $(this).addClass('error');\n        $(this).wiggle({\n          callback: (function(_this) {\n            return function() {\n              $(_this).focus();\n            };\n          })(this)\n        });\n        error_free = false;\n      }\n    });\n    form.find('textarea.required:visible').each(function() {\n      $(this).removeClass('error');\n      if (jQuery.trim($(this).val()) === '') {\n        $(this).addClass('error');\n        $(this).wiggle({\n          callback: (function(_this) {\n            return function() {\n              return $(_this).focus();\n            };\n          })(this)\n        });\n        error_free = false;\n      }\n    });\n    form.find('select.required').each(function() {\n      $(this).removeClass('error');\n      if (this.selectedIndex === -1) {\n        $(this).addClass('error');\n        $(this).wiggle({\n          callback: (function(_this) {\n            return function() {\n              $(_this).focus();\n            };\n          })(this)\n        });\n        error_free = false;\n      }\n    });\n    if (error_free) {\n      map = this.getInputMap(form);\n      isFileUpload = this.isFileUpload(form);\n      opts = {\n        parent: this\n      };\n      if (this.options.swaggerOptions) {\n        for(var key in this.options.swaggerOptions) {\n          opts[key] = this.options.swaggerOptions[key];\n        }\n      }\n\n      var pi;\n      for(pi = 0; pi < this.model.parameters.length; pi++){\n        var p = this.model.parameters[pi];\n        if( p.jsonEditor && p.jsonEditor.isEnabled()){\n          var json = p.jsonEditor.getValue();\n          map[p.name] = JSON.stringify(json);\n        }\n      }\n\n      opts.responseContentType = $('div select[name=responseContentType]', $(this.el)).val();\n      opts.requestContentType = $('div select[name=parameterContentType]', $(this.el)).val();\n      $('.response_throbber', $(this.el)).show();\n      if (isFileUpload) {\n        $('.request_url', $(this.el)).html('<pre></pre>');\n        $('.request_url pre', $(this.el)).text(this.invocationUrl);\n\n        opts.useJQuery = true;\n        map.parameterContentType = 'multipart/form-data';\n        this.map = map;\n        return this.model.execute(map, opts, this.showCompleteStatus, this.showErrorStatus, this);\n      } else {\n        this.map = map;\n        return this.model.execute(map, opts, this.showCompleteStatus, this.showErrorStatus, this);\n      }\n    }\n  },\n\n  getInputMap: function (form) {\n    var map, ref1, l, len, o, ref2, m, len1, val, ref3, n, len2;\n    map = {};\n    ref1 = form.find('input');\n    for (l = 0, len = ref1.length; l < len; l++) {\n      o = ref1[l];\n      if ((o.value !== null) && jQuery.trim(o.value).length > 0) {\n        map[o.name] = o.value;\n      }\n      if (o.type === 'file') {\n        map[o.name] = o.files[0];\n      }\n    }\n    ref2 = form.find('textarea');\n    for (m = 0, len1 = ref2.length; m < len1; m++) {\n      o = ref2[m];\n      val = this.getTextAreaValue(o);\n      if ((val !== null) && jQuery.trim(val).length > 0) {\n        map[o.name] = val;\n      }\n    }\n    ref3 = form.find('select');\n    for (n = 0, len2 = ref3.length; n < len2; n++) {\n      o = ref3[n];\n      val = this.getSelectedValue(o);\n      if ((val !== null) && jQuery.trim(val).length > 0) {\n        map[o.name] = val;\n      }\n    }\n    return map;\n  },\n\n  isFileUpload: function (form) {\n    var ref1, l, len, o;\n    var isFileUpload = false;\n    ref1 = form.find('input');\n    for (l = 0, len = ref1.length; l < len; l++) {\n      o = ref1[l];\n      if (o.type === 'file') {\n        isFileUpload = true;\n      }\n    }\n    return isFileUpload;\n  },\n\n  success: function(response, parent) {\n    parent.showCompleteStatus(response);\n  },\n\n  // wraps a jquery response as a shred response\n  wrap: function(data) {\n    var h, headerArray, headers, i, l, len, o;\n    headers = {};\n    headerArray = data.getAllResponseHeaders().split('\\r');\n    for (l = 0, len = headerArray.length; l < len; l++) {\n      i = headerArray[l];\n      h = i.match(/^([^:]*?):(.*)$/);\n      if (!h) {\n        h = [];\n      }\n      h.shift();\n      if (h[0] !== void 0 && h[1] !== void 0) {\n        headers[h[0].trim()] = h[1].trim();\n      }\n    }\n    o = {};\n    o.content = {};\n    o.content.data = data.responseText;\n    o.headers = headers;\n    o.request = {};\n    o.request.url = this.invocationUrl;\n    o.status = data.status;\n    return o;\n  },\n\n  getSelectedValue: function(select) {\n    if (!select.multiple) {\n      return select.value;\n    } else {\n      var options = [];\n      for (var l = 0, len = select.options.length; l < len; l++) {\n        var opt = select.options[l];\n        if (opt.selected) {\n          options.push(opt.value);\n        }\n      }\n      if (options.length > 0) {\n        return options;\n      } else {\n        return null;\n      }\n    }\n  },\n\n  // handler for hide response link\n  hideResponse: function(e) {\n    if (e) { e.preventDefault(); }\n    $('.response', $(this.el)).slideUp();\n    $('.response_hider', $(this.el)).fadeOut();\n  },\n\n  // Show response from server\n  showResponse: function(response) {\n    var prettyJson = JSON.stringify(response, null, '\\t').replace(/\\n/g, '<br>');\n    $('.response_body', $(this.el)).html(_.escape(prettyJson));\n  },\n\n  // Show error from server\n  showErrorStatus: function(data, parent) {\n    parent.showStatus(data);\n  },\n\n  // show the status codes\n  showCompleteStatus: function(data, parent){\n    parent.showStatus(data);\n  },\n\n  // Adapted from http://stackoverflow.com/a/2893259/454004\n  // Note: directly ported from CoffeeScript\n  // TODO: Cleanup CoffeeScript artifacts\n  formatXml: function(xml) {\n    var contexp, fn, formatted, indent, l, lastType, len, lines, ln, pad, reg, transitions, wsexp;\n    reg = /(>)(<)(\\/*)/g;\n    wsexp = /[ ]*(.*)[ ]+\\n/g;\n    contexp = /(<.+>)(.+\\n)/g;\n    xml = xml.replace(/\\r\\n/g, '\\n').replace(reg, '$1\\n$2$3').replace(wsexp, '$1\\n').replace(contexp, '$1\\n$2');\n    pad = 0;\n    formatted = '';\n    lines = xml.split('\\n');\n    indent = 0;\n    lastType = 'other';\n    transitions = {\n      'single->single': 0,\n      'single->closing': -1,\n      'single->opening': 0,\n      'single->other': 0,\n      'closing->single': 0,\n      'closing->closing': -1,\n      'closing->opening': 0,\n      'closing->other': 0,\n      'opening->single': 1,\n      'opening->closing': 0,\n      'opening->opening': 1,\n      'opening->other': 1,\n      'other->single': 0,\n      'other->closing': -1,\n      'other->opening': 0,\n      'other->other': 0\n    };\n    fn = function(ln) {\n      var fromTo, j, key, padding, type, types, value;\n      types = {\n        single: Boolean(ln.match(/<.+\\/>/)),\n        closing: Boolean(ln.match(/<\\/.+>/)),\n        opening: Boolean(ln.match(/<[^!?].*>/))\n      };\n      type = ((function() {\n        var results;\n        results = [];\n        for (key in types) {\n          value = types[key];\n          if (value) {\n            results.push(key);\n          }\n        }\n        return results;\n      })())[0];\n      type = type === void 0 ? 'other' : type;\n      fromTo = lastType + '->' + type;\n      lastType = type;\n      padding = '';\n      indent += transitions[fromTo];\n      padding = ((function() {\n        var m, ref1, results;\n        results = [];\n        for (j = m = 0, ref1 = indent; 0 <= ref1 ? m < ref1 : m > ref1; j = 0 <= ref1 ? ++m : --m) {\n          results.push('  ');\n        }\n        return results;\n      })()).join('');\n      if (fromTo === 'opening->closing') {\n        formatted = formatted.substr(0, formatted.length - 1) + ln + '\\n';\n      } else {\n        formatted += padding + ln + '\\n';\n      }\n    };\n    for (l = 0, len = lines.length; l < len; l++) {\n      ln = lines[l];\n      fn(ln);\n    }\n    return formatted;\n  },\n\n  // puts the response data in UI\n  showStatus: function(response) {\n    var url, content;\n    if (response.content === undefined) {\n      content = response.data;\n      url = response.url;\n    } else {\n      content = response.content.data;\n      url = response.request.url;\n    }\n    var headers = response.headers;\n    if(typeof content === 'string') {\n      content = jQuery.trim(content);\n    }\n\n    // if server is nice, and sends content-type back, we can use it\n    var contentType = null;\n    if (headers) {\n      contentType = headers['Content-Type'] || headers['content-type'];\n      if (contentType) {\n        contentType = contentType.split(';')[0].trim();\n      }\n    }\n    $('.response_body', $(this.el)).removeClass('json');\n    $('.response_body', $(this.el)).removeClass('xml');\n\n    var supportsAudioPlayback = function(contentType){\n      var audioElement = document.createElement('audio');\n      return !!(audioElement.canPlayType && audioElement.canPlayType(contentType).replace(/no/, ''));\n    };\n\n    var pre;\n    var code;\n    if (!content) {\n      code = $('<code />').text('no content');\n      pre = $('<pre class=\"json\" />').append(code);\n\n      // JSON\n    } else if (headers['Content-Disposition'] && (/attachment/).test(headers['Content-Disposition']) ||\n        headers['content-disposition'] && (/attachment/).test(headers['content-disposition']) ||\n        headers['Content-Description'] && (/File Transfer/).test(headers['Content-Description']) ||\n        headers['content-description'] && (/File Transfer/).test(headers['content-description'])) {\n\n      if ('Blob' in window) {\n        var type = contentType || 'text/html';\n        var a = document.createElement('a');\n        var href;\n\n        if({}.toString.apply(content) === '[object Blob]') {\n          href = window.URL.createObjectURL(content);\n        }\n        else {\n          var binaryData = [];\n          binaryData.push(content);\n          href = window.URL.createObjectURL(new Blob(binaryData, {type: type}));\n        }\n        var fileName = response.url.substr(response.url.lastIndexOf('/') + 1);\n        var download = [type, fileName, href].join(':');\n\n        // Use filename from response header\n        var disposition = headers['content-disposition'] || headers['Content-Disposition'];\n        if(typeof disposition !== 'undefined') {\n          var responseFilename = /filename=([^;]*);?/.exec(disposition);\n          if(responseFilename !== null && responseFilename.length > 1) {\n            download = responseFilename[1];\n          }\n        }\n\n        a.setAttribute('href', href);\n        a.setAttribute('download', download);\n        a.innerText = 'Download ' + fileName;\n\n        pre = $('<div/>').append(a);\n      } else {\n        pre = $('<pre class=\"json\" />').append('Download headers detected but your browser does not support downloading binary via XHR (Blob).');\n      }\n    } else if (contentType === 'application/json' || /\\+json$/.test(contentType)) {\n      var json = null;\n      try {\n        json = JSON.stringify(JSON.parse(content), null, '  ');\n      } catch (_error) {\n        json = 'can\\'t parse JSON.  Raw result:\\n\\n' + content;\n      }\n      code = $('<code />').text(json);\n      pre = $('<pre class=\"json\" />').append(code);\n\n      // XML\n    } else if (contentType === 'application/xml' || /\\+xml$/.test(contentType)) {\n      code = $('<code />').text(this.formatXml(content));\n      pre = $('<pre class=\"xml\" />').append(code);\n\n      // HTML\n    } else if (contentType === 'text/html') {\n      code = $('<code />').html(_.escape(content));\n      pre = $('<pre class=\"xml\" />').append(code);\n\n      // Plain Text\n    } else if (/text\\/plain/.test(contentType)) {\n      code = $('<code />').text(content);\n      pre = $('<pre class=\"plain\" />').append(code);\n\n      // Image\n    } else if (/^image\\//.test(contentType)) {\n      var urlCreator = window.URL || window.webkitURL;\n      var imageUrl = urlCreator.createObjectURL(content);\n\n      pre = $('<img>').attr( 'src', imageUrl);\n      // Audio\n    } else if (/^audio\\//.test(contentType) && supportsAudioPlayback(contentType)) {\n      pre = $('<audio controls>').append($('<source>').attr('src', url).attr('type', contentType));\n    } else if(headers.location || headers.Location) {\n      // Location header based redirect download\n      window.location = response.url;\n\n      // Anything else (CORS)\n    } else {\n      code = $('<code />').text(content);\n      pre = $('<pre class=\"json\" />').append(code);\n    }\n    var response_body = pre;\n    $('.request_url', $(this.el)).html('<pre></pre>');\n    $('.request_url pre', $(this.el)).text(url);\n    $('.response_code', $(this.el)).html('<pre>' + response.status + '</pre>');\n    $('.response_body', $(this.el)).html(response_body);\n    $('.response_headers', $(this.el)).html('<pre>' + _.escape(JSON.stringify(response.headers, null, '  ')).replace(/\\n/g, '<br>') + '</pre>');\n    $('.response', $(this.el)).slideDown();\n    $('.response_hider', $(this.el)).show();\n    $('.response_throbber', $(this.el)).hide();\n\n\n    // adds curl output\n    var curlCommand = this.model.asCurl(this.map, {responseContentType: contentType});\n    curlCommand = curlCommand.replace('!', '&#33;');\n    $( 'div.curl', $(this.el)).html('<pre>' + _.escape(curlCommand) + '</pre>');\n\n    // only highlight the response if response is less than threshold, default state is highlight response\n    var opts = this.options.swaggerOptions;\n\n    if (opts.showRequestHeaders) {\n      var form = $('.sandbox', $(this.el)),\n          map = this.getInputMap(form),\n          requestHeaders = this.model.getHeaderParams(map);\n      delete requestHeaders['Content-Type'];\n      $('.request_headers', $(this.el)).html('<pre>' + _.escape(JSON.stringify(requestHeaders, null, '  ')).replace(/\\n/g, '<br>') + '</pre>');\n    }\n\n    var response_body_el = $('.response_body', $(this.el))[0];\n    // only highlight the response if response is less than threshold, default state is highlight response\n    if (opts.highlightSizeThreshold && typeof response.data !== 'undefined' && response.data.length > opts.highlightSizeThreshold) {\n      return response_body_el;\n    } else {\n      return hljs.highlightBlock(response_body_el);\n    }\n  },\n\n  toggleOperationContent: function (event) {\n    var elem = $('#' + Docs.escapeResourceName(this.parentId + '_' + this.nickname + '_content'));\n    if (elem.is(':visible')){\n      $.bbq.pushState('#/', 2);\n      event.preventDefault();\n      Docs.collapseOperation(elem);\n    } else {\n      Docs.expandOperation(elem);\n    }\n  },\n\n  getTextAreaValue: function(textArea) {\n    var param, parsed, result, i;\n    if (textArea.value === null || jQuery.trim(textArea.value).length === 0) {\n      return null;\n    }\n    param = this.getParamByName(textArea.name);\n    if (param && param.type && param.type.toLowerCase() === 'array') {\n      parsed = textArea.value.split('\\n');\n      result = [];\n      for (i = 0; i < parsed.length; i++) {\n        if (parsed[i] !== null && jQuery.trim(parsed[i]).length > 0) {\n          result.push(parsed[i]);\n        }\n      }\n      return result.length > 0 ? result : null;\n    } else {\n      return textArea.value;\n    }\n  },\n\n  showSnippet: function () {\n    var contentTypeEl = this.$('[name=responseContentType]');\n    var xmlSnippetEl = this.$('.operation-status .snippet_xml, .response-class .snippet_xml');\n    var jsonSnippetEl = this.$('.operation-status .snippet_json, .response-class .snippet_json');\n    var contentType;\n\n    if (!contentTypeEl.length) { return; }\n    contentType = contentTypeEl.val();\n\n    if (contentType.indexOf('xml') > -1) {\n      xmlSnippetEl.show();\n      jsonSnippetEl.hide();\n    } else {\n      jsonSnippetEl.show();\n      xmlSnippetEl.hide();\n    }\n  },\n\n  getParamByName: function(name) {\n    var i;\n    if (this.model.parameters) {\n      for(i = 0; i < this.model.parameters.length; i++) {\n        if (this.model.parameters[i].name === name) {\n          return this.model.parameters[i];\n        }\n      }\n    }\n    return null;\n  }\n\n});\n\n'use strict';\n\nSwaggerUi.Views.ParameterContentTypeView = Backbone.View.extend({\n  initialize: function  () {},\n\n  render: function(){\n    this.model.parameterContentTypeId = 'pct' + Math.random();\n    $(this.el).html(Handlebars.templates.parameter_content_type(this.model));\n    return this;\n  }\n\n});\n'use strict';\n\nSwaggerUi.Views.ParameterView = Backbone.View.extend({\n  events: {\n    'change [name=parameterContentType]' : 'toggleParameterSnippet'\n  },\n\n  initialize: function(){\n    Handlebars.registerHelper('isArray', function(param, opts) {\n      var paramType = param.type && param.type.toLowerCase();\n      if (paramType === 'array' || param.allowMultiple) {\n        return opts.fn(this);\n      } else {\n        return opts.inverse(this);\n      }\n    });\n  },\n\n  render: function() {\n    var type = this.model.type || this.model.dataType;\n    var modelType = this.model.modelSignature.type;\n    var modelDefinitions = this.model.modelSignature.definitions;\n    var schema = this.model.schema || {};\n    var consumes = this.model.consumes || [];\n    var sampleJSON, signatureView;\n\n    if (typeof type === 'undefined') {\n      if (schema.$ref) {\n        var ref = schema.$ref;\n        if (ref.indexOf('#/definitions/') === 0) {\n          type = ref.substring('#/definitions/'.length);\n        } else {\n          type = ref;\n        }\n      }\n    }\n\n    this.model.type = type;\n    this.model.paramType = this.model.in || this.model.paramType;\n    this.model.isBody = this.model.paramType === 'body' || this.model.in === 'body';\n    this.model.isFile = type && type.toLowerCase() === 'file';\n\n    // Allow for default === false\n    if(typeof this.model.default === 'undefined') {\n      this.model.default = this.model.defaultValue;\n    }\n\n    this.model.hasDefault = (typeof this.model.default !== 'undefined');\n    this.model.valueId = 'm' + this.model.name + Math.random();\n\n    if (this.model.allowableValues) {\n      this.model.isList = true;\n    }\n\n    var isXML = this.contains(consumes, 'xml');\n    var isJSON = isXML ? this.contains(consumes, 'json') : true;\n    sampleJSON = SwaggerUi.partials.signature.createParameterJSONSample(modelType, modelDefinitions);\n\n    var template = this.template();\n    $(this.el).html(template(this.model));\n\n    var signatureModel = {\n      sampleJSON: isJSON ? sampleJSON : false,\n      sampleXML: sampleJSON && isXML ? SwaggerUi.partials.signature.createXMLSample('', schema, modelDefinitions, true) : false,\n      isParam: true,\n      signature: SwaggerUi.partials.signature.getParameterModelSignature(modelType, modelDefinitions),\n      defaultRendering: this.model.defaultRendering\n    };\n\n    if (sampleJSON) {\n      signatureView = new SwaggerUi.Views.SignatureView({model: signatureModel, tagName: 'div'});\n      $('.model-signature', $(this.el)).append(signatureView.render().el);\n    }\n    else {\n      $('.model-signature', $(this.el)).html(this.model.signature);\n    }\n\n    var isParam = false;\n\n    if( this.options.swaggerOptions.jsonEditor && this.model.isBody && this.model.schema){\n      var $self = $(this.el);\n      this.model.jsonEditor =\n        /* global JSONEditor */\n        new JSONEditor($('.editor_holder', $self)[0],\n                       {schema: this.model.schema, startval : this.model.default,\n                        ajax:true,\n                        disable_properties:true,\n                        disable_edit_json:true,\n                        iconlib: 'swagger' });\n      // This is so that the signature can send back the sample to the json editor\n      // TODO: SignatureView should expose an event \"onSampleClicked\" instead\n      signatureModel.jsonEditor = this.model.jsonEditor;\n      $('.body-textarea', $self).hide();\n      $('.editor_holder', $self).show();\n      $('.parameter-content-type', $self)\n        .change(function(e){\n            if(e.target.value === 'application/xml'){\n              $('.body-textarea', $self).show();\n              $('.editor_holder', $self).hide();\n              this.model.jsonEditor.disable();\n            }\n            else {\n              $('.body-textarea', $self).hide();\n              $('.editor_holder', $self).show();\n              this.model.jsonEditor.enable();\n            }\n        });\n      }\n\n\n    if (this.model.isBody) {\n      isParam = true;\n    }\n\n    var contentTypeModel = {\n      isParam: isParam\n    };\n\n    contentTypeModel.consumes = this.model.consumes;\n\n    if (isParam) {\n      var parameterContentTypeView = new SwaggerUi.Views.ParameterContentTypeView({model: contentTypeModel});\n      $('.parameter-content-type', $(this.el)).append(parameterContentTypeView.render().el);\n      this.toggleParameterSnippet();\n    }\n\n    else {\n      var responseContentTypeView = new SwaggerUi.Views.ResponseContentTypeView({model: contentTypeModel});\n      $('.response-content-type', $(this.el)).append(responseContentTypeView.render().el);\n      this.toggleResponseSnippet();\n    }\n\n    return this;\n  },\n\n  contains: function (consumes, type) {\n    return consumes.filter(function (val) {\n      if (val.indexOf(type) > -1) {\n        return true;\n      }\n    }).length;\n  },\n\n  toggleParameterSnippet: function () {\n    var contentType = this.$('[name=parameterContentType]').val();\n\n    this.toggleSnippet(contentType);\n  },\n\n  toggleResponseSnippet: function () {\n    var contentEl = this.$('[name=responseContentType]');\n\n    if (!contentEl.length) { return; }\n\n    this.toggleSnippet(contentEl.val());\n  },\n\n  toggleSnippet: function (type) {\n    type = type || '';\n    if (type.indexOf('xml') > -1) {\n      this.$('.snippet_xml').show();\n      this.$('.snippet_json').hide();\n    } else {\n      this.$('.snippet_json').show();\n      this.$('.snippet_xml').hide();\n    }\n  },\n\n  // Return an appropriate template based on if the parameter is a list, readonly, required\n  template: function(){\n    if (this.model.isList) {\n      return Handlebars.templates.param_list;\n    } else {\n      if (this.options.readOnly) {\n        if (this.model.required) {\n          return Handlebars.templates.param_readonly_required;\n        } else {\n          return Handlebars.templates.param_readonly;\n        }\n      } else {\n        if (this.model.required) {\n          return Handlebars.templates.param_required;\n        } else {\n          return Handlebars.templates.param;\n        }\n      }\n    }\n  }\n});\n\n'use strict';\n\n/* jshint -W122 */\nSwaggerUi.partials.signature = (function () {\n  // copy-pasted from swagger-js\n  var resolveSchema = function (schema) {\n    if (_.isPlainObject(schema.schema)) {\n      schema = resolveSchema(schema.schema);\n    }\n\n    return schema;\n  };\n\n  // copy-pasted from swagger-js\n  var simpleRef = function (name) {\n    if (typeof name === 'undefined') {\n      return null;\n    }\n\n    if (name.indexOf('#/definitions/') === 0) {\n      return name.substring('#/definitions/'.length);\n    } else {\n      return name;\n    }\n  };\n\n  // copy-pasted from swagger-js\n  var getInlineModel = function(inlineStr) {\n    if(/^Inline Model \\d+$/.test(inlineStr) && this.inlineModels) {\n      var id = parseInt(inlineStr.substr('Inline Model'.length).trim(),10); //\n      var model = this.inlineModels[id];\n      return model;\n    }\n    // I'm returning null here, should I rather throw an error?\n    return null;\n  };\n\n  // copy-pasted from swagger-js\n  var formatXml = function(xml) {\n    var contexp, fn, formatted, indent, l, lastType, len, lines, ln, pad, reg, transitions, wsexp;\n    reg = /(>)(<)(\\/*)/g;\n    wsexp = /[ ]*(.*)[ ]+\\n/g;\n    contexp = /(<.+>)(.+\\n)/g;\n    xml = xml.replace(reg, '$1\\n$2$3').replace(wsexp, '$1\\n').replace(contexp, '$1\\n$2');\n    pad = 0;\n    formatted = '';\n    lines = xml.split('\\n');\n    indent = 0;\n    lastType = 'other';\n    transitions = {\n      'single->single': 0,\n      'single->closing': -1,\n      'single->opening': 0,\n      'single->other': 0,\n      'closing->single': 0,\n      'closing->closing': -1,\n      'closing->opening': 0,\n      'closing->other': 0,\n      'opening->single': 1,\n      'opening->closing': 0,\n      'opening->opening': 1,\n      'opening->other': 1,\n      'other->single': 0,\n      'other->closing': -1,\n      'other->opening': 0,\n      'other->other': 0\n    };\n    fn = function(ln) {\n      var fromTo, j, key, padding, type, types, value;\n      types = {\n        single: Boolean(ln.match(/<.+\\/>/)),\n        closing: Boolean(ln.match(/<\\/.+>/)),\n        opening: Boolean(ln.match(/<[^!?].*>/))\n      };\n      type = ((function() {\n        var results;\n        results = [];\n        for (key in types) {\n          value = types[key];\n          if (value) {\n            results.push(key);\n          }\n        }\n        return results;\n      })())[0];\n      type = type === void 0 ? 'other' : type;\n      fromTo = lastType + '->' + type;\n      lastType = type;\n      padding = '';\n      indent += transitions[fromTo];\n      padding = ((function() {\n        var m, ref1, results;\n        results = [];\n        for (j = m = 0, ref1 = indent; 0 <= ref1 ? m < ref1 : m > ref1; j = 0 <= ref1 ? ++m : --m) {\n          results.push('  ');\n        }\n        return results;\n      })()).join('');\n      if (fromTo === 'opening->closing') {\n        formatted = formatted.substr(0, formatted.length - 1) + ln + '\\n';\n      } else {\n        formatted += padding + ln + '\\n';\n      }\n    };\n    for (l = 0, len = lines.length; l < len; l++) {\n      ln = lines[l];\n      fn(ln);\n    }\n    return formatted;\n  };\n\n  // copy-pasted from swagger-js\n  var getModelSignature = function (name, schema, models, modelPropertyMacro) {\n    var strongOpen = '<span class=\"strong\">';\n    var strongClose = '</span>';\n\n    var optionHtml = function (label, value) {\n      return '<tr><td class=\"optionName\">' + label + ':</td><td>' + value + '</td></tr>';\n    };\n\n\n    // Allow for ignoring the 'name' argument.... shifting the rest\n    if(_.isObject(arguments[0])) {\n      name = void 0;\n      schema = arguments[0];\n      models = arguments[1];\n      modelPropertyMacro = arguments[2];\n    }\n\n    models = models || {};\n\n    // Resolve the schema (Handle nested schemas)\n    schema = resolveSchema(schema);\n\n    // Return for empty object\n    if(_.isEmpty(schema)) {\n      return strongOpen + 'Empty' + strongClose;\n    }\n\n    // Dereference $ref from 'models'\n    if(typeof schema.$ref === 'string') {\n      name = simpleRef(schema.$ref);\n      schema = models[name];\n      if(typeof schema === 'undefined')\n      {\n        return strongOpen + name + ' is not defined!' + strongClose;\n      }\n    }\n\n    if(typeof name !== 'string') {\n      name = schema.title || 'Inline Model';\n    }\n\n    // If we are a Model object... adjust accordingly\n    if(schema.definition) {\n      schema = schema.definition;\n    }\n\n    if(typeof modelPropertyMacro !== 'function') {\n      modelPropertyMacro = function(prop){\n        return (prop || {}).default;\n      };\n    }\n\n    var references = {};\n    var seenModels = [];\n    var inlineModels = 0;\n\n    // Generate current HTML\n    var html = processModel(schema, name);\n\n    // Generate references HTML\n    while (_.keys(references).length > 0) {\n      /* jshint ignore:start */\n      _.forEach(references, function (schema, name) {\n        var seenModel = _.indexOf(seenModels, name) > -1;\n\n        delete references[name];\n\n        if (!seenModel) {\n          seenModels.push(name);\n\n          html += '<br />' + processModel(schema, name);\n        }\n      });\n      /* jshint ignore:end */\n    }\n\n    return html;\n\n\n    function addReference(schema, name, skipRef) {\n      var modelName = name;\n      var model;\n\n      if (schema.$ref) {\n        modelName = schema.title || simpleRef(schema.$ref);\n        model = models[simpleRef(schema.$ref)];\n      } else if (_.isUndefined(name)) {\n        modelName = schema.title || 'Inline Model ' + (++inlineModels);\n        model = {definition: schema};\n      }\n\n      if (skipRef !== true) {\n        references[modelName] = _.isUndefined(model) ? {} : model.definition;\n      }\n\n      return modelName;\n    }\n\n    function primitiveToHTML(schema) {\n      var html = '<span class=\"propType\">';\n      var type = schema.type || 'object';\n\n      if (schema.$ref) {\n        html += addReference(schema, simpleRef(schema.$ref));\n      } else if (type === 'object') {\n        if (!_.isUndefined(schema.properties)) {\n          html += addReference(schema);\n        } else {\n          html += 'object';\n        }\n      } else if (type === 'array') {\n        html += 'Array[';\n\n        if (_.isArray(schema.items)) {\n          html += _.map(schema.items, addReference).join(',');\n        } else if (_.isPlainObject(schema.items)) {\n          if (_.isUndefined(schema.items.$ref)) {\n            if (!_.isUndefined(schema.items.type) && _.indexOf(['array', 'object'], schema.items.type) === -1) {\n              html += schema.items.type;\n            } else {\n              html += addReference(schema.items);\n            }\n          } else {\n            html += addReference(schema.items, simpleRef(schema.items.$ref));\n          }\n        } else {\n          console.log('Array type\\'s \\'items\\' schema is not an array or an object, cannot process');\n          html += 'object';\n        }\n\n        html += ']';\n      } else {\n        html += schema.type;\n      }\n\n      html += '</span>';\n\n      return html;\n    }\n\n    function primitiveToOptionsHTML(schema, html) {\n      var options = '';\n      var type = schema.type || 'object';\n      var isArray = type === 'array';\n\n      if (!_.isUndefined(schema.description)) {\n        html += ': ' + '<span class=\"propDesc\">' + schema.description + '</span>';\n      }\n\n      if (schema.enum) {\n        html += ' = <span class=\"propVals\">[\\'' + schema.enum.join('\\', \\'') + '\\']</span>';\n      }\n\n      if (isArray) {\n        if (_.isPlainObject(schema.items) && !_.isUndefined(schema.items.type)) {\n          type = schema.items.type;\n        } else {\n          type = 'object';\n        }\n      }\n\n      if (!_.isUndefined(schema.default)) {\n        options += optionHtml('Default', schema.default);\n      }\n\n      switch (type) {\n      case 'string':\n        if (schema.minLength) {\n          options += optionHtml('Min. Length', schema.minLength);\n        }\n\n        if (schema.maxLength) {\n          options += optionHtml('Max. Length', schema.maxLength);\n        }\n\n        if (schema.pattern) {\n          options += optionHtml('Reg. Exp.', schema.pattern);\n        }\n        break;\n      case 'integer':\n      case 'number':\n        if (schema.minimum) {\n          options += optionHtml('Min. Value', schema.minimum);\n        }\n\n        if (schema.exclusiveMinimum) {\n          options += optionHtml('Exclusive Min.', 'true');\n        }\n\n        if (schema.maximum) {\n          options += optionHtml('Max. Value', schema.maximum);\n        }\n\n        if (schema.exclusiveMaximum) {\n          options += optionHtml('Exclusive Max.', 'true');\n        }\n\n        if (schema.multipleOf) {\n          options += optionHtml('Multiple Of', schema.multipleOf);\n        }\n\n        break;\n      }\n\n      if (isArray) {\n        if (schema.minItems) {\n          options += optionHtml('Min. Items', schema.minItems);\n        }\n\n        if (schema.maxItems) {\n          options += optionHtml('Max. Items', schema.maxItems);\n        }\n\n        if (schema.uniqueItems) {\n          options += optionHtml('Unique Items', 'true');\n        }\n\n        if (schema.collectionFormat) {\n          options += optionHtml('Coll. Format', schema.collectionFormat);\n        }\n      }\n\n      if (_.isUndefined(schema.items)) {\n        if (_.isArray(schema.enum)) {\n          var enumString;\n\n          if (type === 'number' || type === 'integer') {\n            enumString = schema.enum.join(', ');\n          } else {\n            enumString = '\"' + schema.enum.join('\", \"') + '\"';\n          }\n\n          options += optionHtml('Enum', enumString);\n        }\n      }\n\n      if (options.length > 0) {\n        html = '<span class=\"propWrap\">' + html + '<table class=\"optionsWrapper\"><tr><th colspan=\"2\">' + type + '</th></tr>' + options + '</table></span>';\n      }\n\n      return html;\n    }\n\n    function processModel(schema, name) {\n      var type = schema.type || 'object';\n      var isArray = schema.type === 'array';\n      var html = strongOpen + name + ' ' + (isArray ? '[' : '{') + strongClose;\n      var contents;\n\n      if (name) {\n        seenModels.push(name);\n      }\n\n      if (isArray) {\n        if (_.isArray(schema.items)) {\n          html += '<div>' + _.map(schema.items, function (item) {\n            var type = item.type || 'object';\n\n            if (_.isUndefined(item.$ref)) {\n              if (_.indexOf(['array', 'object'], type) > -1) {\n                if (type === 'object' && _.isUndefined(item.properties)) {\n                  return 'object';\n                } else {\n                  return addReference(item);\n                }\n              } else {\n                return primitiveToOptionsHTML(item, type);\n              }\n            } else {\n              return addReference(item, simpleRef(item.$ref));\n            }\n          }).join(',</div><div>');\n        } else if (_.isPlainObject(schema.items)) {\n          if (_.isUndefined(schema.items.$ref)) {\n            if (_.indexOf(['array', 'object'], schema.items.type || 'object') > -1) {\n              if ((_.isUndefined(schema.items.type) || schema.items.type === 'object') && _.isUndefined(schema.items.properties)) {\n                html += '<div>object</div>';\n              } else {\n                html += '<div>' + addReference(schema.items) + '</div>';\n              }\n            } else {\n              html += '<div>' + primitiveToOptionsHTML(schema.items, schema.items.type) + '</div>';\n            }\n          } else {\n            html += '<div>' + addReference(schema.items, simpleRef(schema.items.$ref)) + '</div>';\n          }\n        } else {\n          console.log('Array type\\'s \\'items\\' property is not an array or an object, cannot process');\n          html += '<div>object</div>';\n        }\n      } else {\n        if (schema.$ref) {\n          html += '<div>' + addReference(schema, name) + '</div>';\n        } else if (type === 'object') {\n          if (_.isPlainObject(schema.properties)) {\n            contents = _.map(schema.properties, function (property, name) {\n              var propertyIsRequired = (_.indexOf(schema.required, name) >= 0);\n              var cProperty = _.cloneDeep(property);\n\n              var requiredClass = propertyIsRequired ? 'required' : '';\n              var html = '<span class=\"propName ' + requiredClass + '\">' + name + '</span> (';\n              var model;\n\n              // Allow macro to set the default value\n              cProperty.default = modelPropertyMacro(cProperty);\n\n              // Resolve the schema (Handle nested schemas)\n              cProperty = resolveSchema(cProperty);\n\n              // We need to handle property references to primitives (Issue 339)\n              if (!_.isUndefined(cProperty.$ref)) {\n                model = models[simpleRef(cProperty.$ref)];\n\n                if (!_.isUndefined(model) && _.indexOf([undefined, 'array', 'object'], model.definition.type) === -1) {\n                  // Use referenced schema\n                  cProperty = resolveSchema(model.definition);\n                }\n              }\n\n              html += primitiveToHTML(cProperty);\n\n              if(!propertyIsRequired) {\n                html += ', <span class=\"propOptKey\">optional</span>';\n              }\n\n              if(property.readOnly) {\n                  html += ', <span class=\"propReadOnly\">read only</span>';\n              }\n\n              html += ')';\n\n              return '<div' + (property.readOnly ? ' class=\"readOnly\"' : '') + '>' + primitiveToOptionsHTML(cProperty, html);\n            }).join(',</div>');\n          }\n\n          if (contents) {\n            html += contents + '</div>';\n          }\n        } else {\n          html += '<div>' + primitiveToOptionsHTML(schema, type) + '</div>';\n        }\n      }\n\n      return html + strongOpen + (isArray ? ']' : '}') + strongClose;\n    }\n\n  };\n\n  // copy-pasted from swagger-js\n  var schemaToJSON = function (schema, models, modelsToIgnore, modelPropertyMacro) {\n    // Resolve the schema (Handle nested schemas)\n    schema = resolveSchema(schema);\n\n    if(typeof modelPropertyMacro !== 'function') {\n      modelPropertyMacro = function(prop){\n        return (prop || {}).default;\n      };\n    }\n\n    modelsToIgnore= modelsToIgnore || {};\n\n    var type = schema.type || 'object';\n    var format = schema.format;\n    var model;\n    var output;\n\n    if (!_.isUndefined(schema.example)) {\n      output = schema.example;\n    } else if (_.isUndefined(schema.items) && _.isArray(schema.enum)) {\n      output = schema.enum[0];\n    }\n\n    if (_.isUndefined(output)) {\n      if (schema.$ref) {\n        model = models[simpleRef(schema.$ref)];\n\n        if (!_.isUndefined(model)) {\n          if (_.isUndefined(modelsToIgnore[model.name])) {\n            modelsToIgnore[model.name] = model;\n            output = schemaToJSON(model.definition, models, modelsToIgnore, modelPropertyMacro);\n            delete modelsToIgnore[model.name];\n          } else {\n            if (model.type === 'array') {\n              output = [];\n            } else {\n              output = {};\n            }\n          }\n        }\n      } else if (!_.isUndefined(schema.default)) {\n        output = schema.default;\n      } else if (type === 'string') {\n        if (format === 'date-time') {\n          output = new Date().toISOString();\n        } else if (format === 'date') {\n          output = new Date().toISOString().split('T')[0];\n        } else {\n          output = 'string';\n        }\n      } else if (type === 'integer') {\n        output = 0;\n      } else if (type === 'number') {\n        output = 0.0;\n      } else if (type === 'boolean') {\n        output = true;\n      } else if (type === 'object') {\n        output = {};\n\n        _.forEach(schema.properties, function (property, name) {\n          var cProperty = _.cloneDeep(property);\n\n          // Allow macro to set the default value\n          cProperty.default = modelPropertyMacro(property);\n\n          output[name] = schemaToJSON(cProperty, models, modelsToIgnore, modelPropertyMacro);\n        });\n      } else if (type === 'array') {\n        output = [];\n\n        if (_.isArray(schema.items)) {\n          _.forEach(schema.items, function (item) {\n            output.push(schemaToJSON(item, models, modelsToIgnore, modelPropertyMacro));\n          });\n        } else if (_.isPlainObject(schema.items)) {\n          output.push(schemaToJSON(schema.items, models, modelsToIgnore, modelPropertyMacro));\n        } else if (_.isUndefined(schema.items)) {\n          output.push({});\n        } else {\n          console.log('Array type\\'s \\'items\\' property is not an array or an object, cannot process');\n        }\n      }\n    }\n\n    return output;\n  };\n\n  // copy-pasted from swagger-js\n  var createJSONSample = function (value, modelsToIgnore) {\n    modelsToIgnore = modelsToIgnore || {};\n\n    modelsToIgnore[value.name] = value;\n\n    // Response support\n    if (value.examples && _.isPlainObject(value.examples) && value.examples['application/json']) {\n      value.definition.example = value.examples['application/json'];\n\n      if (_.isString(value.definition.example)) {\n        value.definition.example = jsyaml.safeLoad(value.definition.example);\n      }\n    } else if (!value.definition.example) {\n      value.definition.example = value.examples;\n    }\n\n    return schemaToJSON(value.definition, value.models, modelsToIgnore, value.modelPropertyMacro);\n  };\n\n  // copy-pasted from swagger-js\n  var getParameterModelSignature = function (type, definitions) {\n      var isPrimitive, listType;\n\n      if (type instanceof Array) {\n        listType = true;\n        type = type[0];\n      }\n\n      // Convert undefined to string of 'undefined'\n      if (typeof type === 'undefined') {\n        type = 'undefined';\n        isPrimitive = true;\n\n      } else if (definitions[type]){\n        // a model def exists?\n        type = definitions[type]; /* Model */\n        isPrimitive = false;\n\n      } else if (getInlineModel(type)) {\n        type = getInlineModel(type); /* Model */\n        isPrimitive = false;\n\n      } else {\n        // We default to primitive\n        isPrimitive = true;\n      }\n\n      if (isPrimitive) {\n        if (listType) {\n          return 'Array[' + type + ']';\n        } else {\n          return type.toString();\n        }\n      } else {\n        if (listType) {\n          return 'Array[' + getModelSignature(type.name, type.definition, type.models, type.modelPropertyMacro) + ']';\n        } else {\n          return getModelSignature(type.name, type.definition, type.models, type.modelPropertyMacro);\n        }\n      }\n  };\n\n  // copy-pasted from swagger-js\n  var createParameterJSONSample = function (type, models) {\n    var listType, sampleJson, innerType;\n    models = models || {};\n\n    listType = (type instanceof Array);\n    innerType = listType ? type[0] : type;\n\n    if(models[innerType]) {\n      sampleJson = createJSONSample(models[innerType]);\n    } else if (getInlineModel(innerType)){\n      sampleJson = createJSONSample(getInlineModel(innerType)); // may return null, if type isn't correct\n    }\n\n\n    if (sampleJson) {\n      sampleJson = listType ? [sampleJson] : sampleJson;\n\n      if (typeof sampleJson === 'string') {\n        return sampleJson;\n      } else if (_.isObject(sampleJson)) {\n        var t = sampleJson;\n\n        if (sampleJson instanceof Array && sampleJson.length > 0) {\n          t = sampleJson[0];\n        }\n\n        if (t.nodeName && typeof t === 'Node') {\n          var xmlString = new XMLSerializer().serializeToString(t);\n\n          return formatXml(xmlString);\n        } else {\n          return JSON.stringify(sampleJson, null, 2);\n        }\n      } else {\n        return sampleJson;\n      }\n    }\n  };\n\n  var wrapTag = function (name, value, attrs) {\n    var str, attributes;\n\n    attrs = attrs || [];\n\n    attributes = attrs.map(function (attr) {\n      return ' ' + attr.name + '=\"' + attr.value + '\"';\n    }).join('');\n\n    if (!name) {\n      return getErrorMessage('Node name is not provided');\n    }\n\n    str = [\n      '<', name,\n      attributes,\n      '>',\n      value,\n      '</', name, '>'\n    ];\n\n    return str.join('');\n  };\n\n  var getName = function (name, xml) {\n    var result = name || '';\n\n    xml = xml || {};\n\n    if (xml.name) {\n      result = xml.name;\n    }\n\n    if (xml.prefix) {\n      result = xml.prefix + ':' + result;\n    }\n\n    return result;\n  };\n\n  var getNamespace = function (xml) {\n    var namespace = '';\n    var name = 'xmlns';\n\n    xml = xml || {};\n\n    if (xml.namespace) {\n      namespace = xml.namespace;\n    } else {\n      return namespace;\n    }\n\n    if (xml.prefix) {\n      name += ':' + xml.prefix;\n    }\n\n    return {\n      name: name,\n      value: namespace\n    };\n  };\n\n  var createArrayXML = function (descriptor) {\n    var name = descriptor.name;\n    var config = descriptor.config;\n    var definition = descriptor.definition;\n    var models = descriptor.models;\n    var value;\n    var items = definition.items;\n    var xml = definition.xml || {};\n    var namespace = getNamespace(xml);\n    var attributes = [];\n\n    if (!items) { return getErrorMessage(); }\n\n    value = createSchemaXML(name, items, models, config);\n\n    if (namespace) {\n      attributes.push(namespace);\n    }\n\n    if (xml.wrapped) {\n      value = wrapTag(name, value, attributes);\n    }\n\n    return value;\n  };\n\n  var getPrimitiveSignature = function (schema) {\n    var type, items;\n\n    schema = schema || {};\n    items = schema.items || {};\n    type = schema.type || '';\n\n    switch (type) {\n      case 'object': return 'Object is not a primitive';\n      case 'array' : return 'Array[' + (items.format || items.type) + ']';\n      default: return schema.format || type;\n    }\n  };\n\n  var createPrimitiveXML = function (descriptor) {\n    var name = descriptor.name;\n    var definition = descriptor.definition;\n    var primitivesMap = {\n      'string': {\n        'date': new Date(1).toISOString().split('T')[0],\n        'date-time' : new Date(1).toISOString(),\n        'default': 'string'\n      },\n      'integer': {\n        'default': 1\n      },\n      'number': {\n        'default': 1.1\n      },\n      'boolean': {\n        'default': true\n      }\n    };\n    var type = definition.type;\n    var format = definition.format;\n    var xml = definition.xml || {};\n    var namespace = getNamespace(xml);\n    var attributes = [];\n    var value;\n\n    if (_.keys(primitivesMap).indexOf(type) < 0) { return getErrorMessage(); }\n\n    if (_.isArray(definition.enum)){\n      value = definition.enum[0];\n    } else {\n      value = definition.example || primitivesMap[type][format] || primitivesMap[type].default;\n    }\n\n    if (xml.attribute) {\n      return {name: name, value: value};\n    }\n\n    if (namespace) {\n      attributes.push(namespace);\n    }\n\n    return wrapTag(name, value, attributes);\n  };\n\n  function createObjectXML (descriptor) {\n    var name = descriptor.name;\n    var definition = descriptor.definition;\n    var config = descriptor.config;\n    var models = descriptor.models;\n    var isParam = descriptor.config.isParam;\n    var serializedProperties;\n    var attrs = [];\n    var properties = definition.properties;\n    var additionalProperties = definition.additionalProperties;\n    var xml = definition.xml;\n    var namespace = getNamespace(xml);\n\n    if (namespace) {\n      attrs.push(namespace);\n    }\n\n    if (!properties && !additionalProperties) { return getErrorMessage(); }\n\n    properties = properties || {};\n\n    serializedProperties = _.map(properties, function (prop, key) {\n      var xml, result;\n\n      if (isParam && prop.readOnly) {\n        return '';\n      }\n\n      xml = prop.xml || {};\n      result = createSchemaXML(key, prop, models, config);\n\n      if (xml.attribute) {\n        attrs.push(result);\n        return '';\n      }\n\n      return result;\n    }).join('');\n\n    if (additionalProperties) {\n      serializedProperties += '<!-- additional elements allowed -->';\n    }\n\n    return wrapTag(name, serializedProperties, attrs);\n  }\n\n  function getInfiniteLoopMessage (name, loopTo) {\n    return wrapTag(name, '<!-- Infinite loop $ref:' + loopTo + ' -->');\n  }\n\n  function getErrorMessage (details) {\n    details = details ? ': ' + details : '';\n    return '<!-- invalid XML' + details + ' -->';\n  }\n\n  function createSchemaXML (name, definition, models, config) {\n    var $ref = _.isObject(definition) ? definition.$ref : null;\n    var output, index;\n    config = config || {};\n    config.modelsToIgnore = config.modelsToIgnore || [];\n    var descriptor = _.isString($ref) ? getDescriptorByRef($ref, name, models, config)\n        : getDescriptor(name, definition, models, config);\n\n    if (!descriptor) {\n      return getErrorMessage();\n    }\n\n    switch (descriptor.type) {\n      case 'array':\n        output = createArrayXML(descriptor); break;\n      case 'object':\n        output = createObjectXML(descriptor); break;\n      case 'loop':\n        output = getInfiniteLoopMessage(descriptor.name, descriptor.config.loopTo); break;\n      default:\n        output = createPrimitiveXML(descriptor);\n    }\n\n    if ($ref && descriptor.type !== 'loop') {\n      index = config.modelsToIgnore.indexOf($ref);\n      if (index > -1) {\n        config.modelsToIgnore.splice(index, 1);\n      }\n    }\n\n    return output;\n  }\n\n  function Descriptor (name, type, definition, models, config) {\n    if (arguments.length < 4) {\n      throw new Error();\n    }\n\n    this.config = config || {};\n    this.config.modelsToIgnore = this.config.modelsToIgnore || [];\n    this.name = getName(name, definition.xml);\n    this.definition = definition;\n    this.models = models;\n    this.type = type;\n  }\n\n  function getDescriptorByRef($ref, name, models, config) {\n    var modelType = simpleRef($ref);\n    var model = models[modelType] || {};\n    var type = model.definition && model.definition.type ? model.definition.type : 'object';\n    name = name || model.name;\n\n    if (config.modelsToIgnore.indexOf($ref) > -1) {\n      type = 'loop';\n      config.loopTo = modelType;\n    } else {\n      config.modelsToIgnore.push($ref);\n    }\n\n    if (!model.definition) {\n      return null;\n    }\n\n    return new Descriptor(name, type, model.definition, models, config);\n  }\n\n  function getDescriptor (name, definition, models, config){\n    var type = definition.type || 'object';\n\n    if (!definition) {\n      return null;\n    }\n\n    return new Descriptor(name, type, definition, models, config);\n  }\n\n  function createXMLSample (name, definition, models, isParam) {\n    var prolog = '<?xml version=\"1.0\"?>';\n\n    return formatXml(prolog + createSchemaXML(name, definition, models, { isParam: isParam } ));\n  }\n\n  return {\n      getModelSignature: getModelSignature,\n      createJSONSample: createJSONSample,\n      getParameterModelSignature: getParameterModelSignature,\n      createParameterJSONSample: createParameterJSONSample,\n      createSchemaXML: createSchemaXML,\n      createXMLSample: createXMLSample,\n      getPrimitiveSignature: getPrimitiveSignature\n  };\n\n})();\n\n'use strict';\n\nSwaggerUi.Views.PopupView = Backbone.View.extend({\n    events: {\n        'click .api-popup-cancel': 'cancelClick'\n    },\n\n    template: Handlebars.templates.popup,\n    className: 'api-popup-dialog',\n\n    selectors: {\n        content: '.api-popup-content',\n        main   : '#swagger-ui-container'\n    },\n\n    initialize: function(){\n        this.$el.html(this.template(this.model));\n    },\n\n    render: function () {\n        this.$(this.selectors.content).append(this.model.content);\n        $(this.selectors.main).first().append(this.el);\n        this.showPopup();\n\n        return this;\n    },\n\n    showPopup: function () {\n        this.$el.show();\n    },\n\n    cancelClick: function () {\n        this.remove();\n    }\n\n});\n\n'use strict';\n\nSwaggerUi.Views.ResourceView = Backbone.View.extend({\n  initialize: function(opts) {\n    opts = opts || {};\n    this.router = opts.router;\n    this.auths = opts.auths;\n    if ('' === this.model.description) {\n      this.model.description = null;\n    }\n    if (this.model.description) {\n      this.model.summary = this.model.description;\n    }\n    this.number = 0;\n  },\n\n  render: function(){\n    var methods = {};\n\n\n    $(this.el).html(Handlebars.templates.resource(this.model));\n\n    // Render each operation\n    for (var i = 0; i < this.model.operationsArray.length; i++) {\n      var operation = this.model.operationsArray[i];\n      var counter = 0;\n      var id = operation.nickname;\n\n      while (typeof methods[id] !== 'undefined') {\n        id = id + '_' + counter;\n        counter += 1;\n      }\n\n      methods[id] = operation;\n\n      operation.nickname = id;\n      operation.parentId = this.model.id;\n      operation.definitions = this.model.definitions; // make Json Schema available for JSonEditor in this operation\n      this.addOperation(operation);\n    }\n\n    $('.toggleEndpointList', this.el).click(this.callDocs.bind(this, 'toggleEndpointListForResource'));\n    $('.collapseResource', this.el).click(this.callDocs.bind(this, 'collapseOperationsForResource'));\n    $('.expandResource', this.el).click(this.callDocs.bind(this, 'expandOperationsForResource'));\n\n    return this;\n  },\n\n  addOperation: function(operation) {\n\n    operation.number = this.number;\n\n    // Render an operation and add it to operations li\n    var operationView = new SwaggerUi.Views.OperationView({\n      model: operation,\n      router: this.router,\n      tagName: 'li',\n      className: 'endpoint',\n      swaggerOptions: this.options.swaggerOptions,\n      auths: this.auths\n    });\n\n    $('.endpoints', $(this.el)).append(operationView.render().el);\n\n    this.number++;\n\n  },\n  // Generic Event handler (`Docs` is global)\n\n\n  callDocs: function(fnName, e) {\n    e.preventDefault();\n    Docs[fnName](e.currentTarget.getAttribute('data-id'));\n  }\n});\n\n'use strict';\n\nSwaggerUi.Views.ResponseContentTypeView = Backbone.View.extend({\n  initialize: function(){},\n\n  render: function(){\n    this.model.responseContentTypeId = 'rct' + Math.random();\n    $(this.el).html(Handlebars.templates.response_content_type(this.model));\n    return this;\n  }\n});\n'use strict';\n\nSwaggerUi.Views.SignatureView = Backbone.View.extend({\n  events: {\n    'click a.description-link'       : 'switchToDescription',\n    'click a.snippet-link'           : 'switchToSnippet',\n    'mousedown .snippet_json'          : 'jsonSnippetMouseDown',\n    'mousedown .snippet_xml'          : 'xmlSnippetMouseDown'\n  },\n\n  initialize: function () {\n  },\n\n  render: function(){\n\n    $(this.el).html(Handlebars.templates.signature(this.model));\n\n    if (this.model.defaultRendering === 'model') {\n      this.switchToDescription();\n    } else {\n      this.switchToSnippet();\n    }\n\n    return this;\n  },\n\n  // handler for show signature\n  switchToDescription: function(e){\n    if (e) { e.preventDefault(); }\n\n    $('.snippet', $(this.el)).hide();\n    $('.description', $(this.el)).show();\n    $('.description-link', $(this.el)).addClass('selected');\n    $('.snippet-link', $(this.el)).removeClass('selected');\n  },\n\n  // handler for show sample\n  switchToSnippet: function(e){\n    if (e) { e.preventDefault(); }\n\n    $('.snippet', $(this.el)).show();\n    $('.description', $(this.el)).hide();\n    $('.snippet-link', $(this.el)).addClass('selected');\n    $('.description-link', $(this.el)).removeClass('selected');\n  },\n\n  // handler for snippet to text area\n  snippetToTextArea: function(val) {\n    var textArea = $('textarea', $(this.el.parentNode.parentNode.parentNode));\n\n    // Fix for bug in IE 10/11 which causes placeholder text to be copied to \"value\"\n    if ($.trim(textArea.val()) === '' || textArea.prop('placeholder') === textArea.val()) {\n      textArea.val(val);\n      // TODO move this code outside of the view and expose an event instead\n      if( this.model.jsonEditor && this.model.jsonEditor.isEnabled()){\n        this.model.jsonEditor.setValue(JSON.parse(this.model.sampleJSON));\n      }\n    }\n  },\n\n  jsonSnippetMouseDown: function (e) {\n    if (this.model.isParam) {\n      if (e) { e.preventDefault(); }\n\n      this.snippetToTextArea(this.model.sampleJSON);\n    }\n  },\n\n  xmlSnippetMouseDown: function (e) {\n    if (this.model.isParam) {\n      if (e) { e.preventDefault(); }\n\n      this.snippetToTextArea(this.model.sampleXML);\n    }\n  }\n});\n'use strict';\n\nSwaggerUi.Views.StatusCodeView = Backbone.View.extend({\n  initialize: function (opts) {\n    this.options = opts || {};\n    this.router = this.options.router;\n  },\n\n  render: function(){\n    var responseModel, responseModelView;\n    var value = this.router.api.models[this.model.responseModel];\n    $(this.el).html(Handlebars.templates.status_code(this.model));\n\n    if (this.router.api.models.hasOwnProperty(this.model.responseModel)) {\n      responseModel = {\n        sampleJSON: JSON.stringify(SwaggerUi.partials.signature.createJSONSample(value), void 0, 2),\n        sampleXML: this.model.isXML ? SwaggerUi.partials.signature.createXMLSample('', this.model.schema, this.router.api.models) : false,\n        isParam: false,\n        signature: SwaggerUi.partials.signature.getModelSignature(this.model.responseModel, value, this.router.api.models),\n        defaultRendering: this.model.defaultRendering\n      };\n    } else {\n      responseModel = {\n        signature: SwaggerUi.partials.signature.getPrimitiveSignature(this.model.schema)\n      };\n    }\n\n    responseModelView = new SwaggerUi.Views.SignatureView({model: responseModel, tagName: 'div'});\n    $('.model-signature', this.$el).append(responseModelView.render().el);\n    return this;\n  }\n});}).call(this);"
  },
  {
    "path": "src/backend/tools/py.doc/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliopen\nwith-coverage = 1\ncover-erase = 1\n"
  },
  {
    "path": "src/backend/tools/py.doc/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_api_doc\"\n\nwith open(os.path.join(*([here] + name.split('.') + ['__init__.py']))) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S).match(v_file.read()).group(1)\n\nrequires = [\n    'caliopen_api'\n    ]\n\nextras_require = {\n    'dev': [],\n    'test': [],\n}\n\nsetup(name=name,\n      version=version,\n      description='API documentation package. It relies on caliopen_api to serve documentation on localhost.',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\"Programming Language :: Python\", ],\n      author='Caliopen contributors',\n      author_email='contact@caliopen.org',\n      url='https://caliopen.org',\n      license='AGPLv3',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      extras_require=extras_require,\n      install_requires=requires,\n      tests_require=requires,\n      )\n"
  },
  {
    "path": "src/backend/tools/py.migrate/CHANGES.rst",
    "content": "0.0.1\n-----\n\n-  Initial version\n"
  },
  {
    "path": "src/backend/tools/py.migrate/README.rst",
    "content": "caliopen.migrate\n============\n\nCaliopen migration utilities\n============================\n\nPython package for utilities related to data migration in Caliopen.\n\n"
  },
  {
    "path": "src/backend/tools/py.migrate/caliopen_migrate/__init__.py",
    "content": "\n__version__ = '0.0.1'\n"
  },
  {
    "path": "src/backend/tools/py.migrate/caliopen_migrate/cli.py",
    "content": "#!/bin/env python\n\nimport sys\nimport argparse\nimport logging\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_storage.helpers.connection import connect_storage\n\nlogging.basicConfig(level=logging.WARNING)\n\n\ndef shell(**kwargs):\n    try:\n        from IPython import embed\n        from traitlets.config.loader import Config\n        cfg = Config()\n        cfg.InteractiveShellEmbed.confirm_exit = False\n        embed(config=cfg, banner1=\"Caliopen Shell\")\n    except ImportError:\n        # try ~IPython-0.10 API\n        try:\n            from IPython.Shell import IPShellEmbed as embed  # noqa\n            ipshell = embed(banner=\"Caliopen Shell\")\n            ipshell()\n        except ImportError:\n            import code\n            code.interact(\"Caliopen migration shell\", local=locals())\n\n\ndef main(args=sys.argv):\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-f', dest='conffile', default='caliopen.yaml')\n    kwargs = parser.parse_args(args[1:])\n    kwargs = vars(kwargs)\n\n    config_uri = kwargs.pop('conffile')\n    Configuration.load(config_uri, 'global')\n    connect_storage()\n    shell(**kwargs)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "src/backend/tools/py.migrate/caliopen_migrate/shards.py",
    "content": "import logging\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_storage.helpers.connection import get_index_connection\nfrom caliopen_main.user.core.setups import setup_shard_index\nfrom caliopen_main.user.core import User\n\nlog = logging.getLogger(__name__)\n\n\ndef delete_all_shards(dry_run=True):\n    \"\"\"Delete all index shards.\"\"\"\n    client = get_index_connection()\n    shards = Configuration('global').get('elasticsearch.shards')\n\n    for shard in shards:\n        log.info('Processing shard {}'.format(shard))\n        if not shard.startswith('caliopen-'):\n            log.warn('Invalid shard name, pass')\n            continue\n        if not client.indices.exists(shard):\n            log.warn('Shard does not exist')\n            continue\n        if dry_run:\n            log.info('Delete shard but dry run do not touch')\n        else:\n            client.indices.delete(shard)\n            log.info('Index {} deleted'.format(shard))\n\n\ndef create_all_shards(dry_run=True):\n    \"\"\"Create all needed index shards.\"\"\"\n    client = get_index_connection()\n    shards = Configuration('global').get('elasticsearch.shards')\n\n    for shard_id in shards:\n        if not client.indices.exists(shard_id):\n            log.info('Creating shard {}'.format(shard_id))\n            if not dry_run:\n                setup_shard_index(shard_id)\n\n\ndef recreate_user_alias(client, user, dry_run=True):\n    \"\"\"Create an index alias mapping user_id -> shard_id.\"\"\"\n    if not user.shard_id:\n        log.error('No shard for user {}'.format(user.user_id))\n        return False\n    shards = Configuration('global').get('elasticsearch.shards')\n    alias_exists = False\n    if client.indices.exists_alias(name=user.user_id):\n        alias = client.indices.get_alias(name=user.user_id)\n        for index, alias_infos in alias.items():\n            if index not in shards:\n                if not dry_run:\n                    client.indices.delete_alias(index=index, name=user.user_id)\n                else:\n                    log.info('Alias exist {} with index {}, should delete'.\n                             format(user.user_id, index))\n            else:\n                log.info('Alias on shard exist, skipping')\n                alias_exists = True\n    if alias_exists:\n        return True\n    if not dry_run:\n        body = {'filter': {'term': {'user_id': user.user_id}}}\n        try:\n            client.indices.put_alias(index=user.shard_id,\n                                     name=user.user_id,\n                                     body=body)\n        except Exception as exc:\n            log.exception('Error during alias creation for user {} : {}'.\n                          format(user.user_id, exc))\n            return False\n    else:\n        log.info('Should create alias {}'.format(user.user_id))\n    return True\n\n\ndef recreate_all_user_aliases(dry_run=True):\n    \"\"\"Recreate alias for all users.\"\"\"\n    client = get_index_connection()\n    for user in User._model_class.all():\n        recreate_user_alias(client, user, dry_run)\n"
  },
  {
    "path": "src/backend/tools/py.migrate/caliopen_migrate/user.py",
    "content": "import logging\nimport uuid\n\nfrom cassandra.query import SimpleStatement\nfrom cassandra.cluster import Cluster\nfrom cassandra.query import dict_factory\n\n\nfrom caliopen_storage.config import Configuration\nfrom caliopen_storage.exception import NotFound\nfrom caliopen_main.user.core.user import User\nfrom caliopen_main.user.core import allocate_user_shard\nfrom caliopen_main.contact.objects.contact import Contact as ContactObject\nfrom caliopen_main.contact.objects.email import Email as EmailObject\n\nfrom caliopen_main.contact.core import ContactLookup\n\nlog = logging.getLogger(__name__)\n\nEXCLUDE_COLUMS = ['tags']\n\n\nconf = Configuration('global').configuration\ncluster_source = Cluster(conf['old_cassandra']['hosts'])\nsource = cluster_source.connect(conf['old_cassandra']['keyspace'])\nsource.row_factory = dict_factory\n\ncluster_dest = Cluster(conf['cassandra']['hosts'])\ndest = cluster_dest.connect(conf['cassandra']['keyspace'])\n\n\ndef copy_contacts(user, only_id, filter_id=None):\n    query = \"SELECT * from contact where user_id = {}\".format(user.user_id)\n    if filter_id:\n        query = \"{} AND contact_id = {}\".format(query, filter_id)\n    statement = SimpleStatement(query, fetch_size=200)\n    insert_query = \"INSERT INTO contact ({0}) VALUES ({1})\"\n\n    contact_ids = []\n    for row in source.execute(statement):\n        if not only_id:\n            columns = []\n            values = []\n            binds = []\n            for col, value in row.items():\n                if col not in EXCLUDE_COLUMS:\n                    columns.append(col)\n                    values.append(value)\n                    binds.append('?')\n\n            col_str = ','.join(columns)\n            col_bind = ','.join(binds)\n            insert_str = insert_query.format(col_str, col_bind)\n            insert = dest.prepare(insert_str)\n            bound = insert.bind(values)\n            dest.execute(bound)\n        contact_ids.append(row['contact_id'])\n    return contact_ids\n\n\ndef patch_user_contact(user, contact, new_domain, old_domain):\n    log.info('Patching user contact')\n    update = False\n    found = False\n    for old_email in contact.emails:\n        address = old_email.address.lower()\n        log.info('Testing user email addres {}'.format(address))\n        if address.endswith(new_domain):\n            found = True\n        if address.endswith(old_domain):\n            update = True\n            log.info('Remove old email {}'.format(address))\n            contact.emails.remove(old_email)\n    if not found:\n        update = True\n        local_addr = '{}@{}'.format(user.name, new_domain)\n        log.info('Creating new email {}'.format(local_addr))\n        email = EmailObject(address=local_addr, type='home')\n        contact.emails.append(email)\n\n    if update:\n        contact.marshall_db()\n        contact.save_db()\n    return update\n\n\ndef copy_user_contacts(user, new_domain, old_domain, only_index=False,\n                       filter_id=None):\n    ids = copy_contacts(user, only_index, filter_id)\n    for id in ids:\n        contact = ContactObject(user, contact_id=id)\n        try:\n            contact.get_db()\n        except NotFound:\n            log.error('Contact {} not found for user {}'.\n                      format(user.contact_id, user.user_id))\n            continue\n        contact.unmarshall_db()\n\n        if not only_index:\n            if str(user.contact_id) == str(id):\n                try:\n                    patch_user_contact(user, contact, new_domain, old_domain)\n                except Exception as exc:\n                    log.exception('Error during user contact patch {}'.\n                                  format(user.user_id))\n                continue\n            for email in contact.emails:\n                ContactLookup.create(user=user, contact_id=id,\n                                     value=email.address.lower(),\n                                     type='email')\n            for ident in contact.identities:\n                ContactLookup.create(user=user, contact_id=id,\n                                     value=ident.name.lower(),\n                                     type=ident.type.lower())\n        contact.create_index()\n\n\ndef migrate_user(user, new_domain):\n    shard_id = allocate_user_shard(uuid.UUID(user.user_id))\n    if not shard_id or not user.shard_id:\n        log.error('No valid shard')\n        return False\n    if user.shard_id != shard_id:\n        user.model.shard_id = shard_id\n        log.info('Allocate user {} to new shard {}'.\n                 format(user.user_id, shard_id))\n        try:\n            user.model.save()\n        except Exception as exc:\n            log.exception('Fail to save user with new_shard {}'.format(exc))\n            return False\n        try:\n            local_part = user.name.replace('@', '').lower()\n            local_address = '{}@{}'.format(local_part, new_domain)\n            user.add_local_identity(local_address)\n            log.debug('Created local identity {}'.format(local_address))\n        except Exception as exc:\n            log.exception('Error during identity creation for user {}: {}'.\n                          format(user.user_id, exc))\n    return True\n\n\ndef migrate_all_users(new_domain, count=None):\n    cpt = 0\n    for model in User._model_class.all():\n        user = User(model)\n        migrate_user(user, new_domain)\n        cpt += 1\n        if count and cpt >= count:\n            log.info(\"Limit to {} users, stop migration\".format(count))\n            break\n\n\ndef compute_all_shards():\n    for model in User._model_class.all():\n        shard_id = allocate_user_shard(model.user_id)\n        if model.shard_id != shard_id:\n            model.shard_id = shard_id\n            log.info('Allocate user {} to new shard {}'.\n                     format(model.user_id, shard_id))\n            model.save()\n"
  },
  {
    "path": "src/backend/tools/py.migrate/caliopen_migrate/welcome_message.py",
    "content": "\nimport tornado.ioloop\nimport tornado.gen\n\nimport json\n\nfrom nats.io.client import Client\nfrom caliopen_storage.config import Configuration\n\n\ndef make_message(user):\n    notif = {\n        'message': 'created',\n        'user_name': '{0}'.format(user.name),\n        'user_id': '{0}'.format(user.user_id)\n    }\n    return json.dumps(notif)\n\n\n@tornado.gen.coroutine\ndef main(user):\n    nc = Client()\n    try:\n        server = Configuration('global').get('message_queue')\n        opts = {\"servers\": ['nats://{}:{}'.format(server['host'],\n                                                  server['port'])]}\n        print('Connecting to {}'.format(server))\n        yield nc.connect(**opts)\n        data = make_message(user)\n        yield nc.publish('userAction', data)\n        yield nc.flush()\n        print(\"Published to '{0}'\".format(data))\n    except Exception as exc:\n        print(exc)\n        raise exc\n\n\ndef run_with_args(func, *args):\n    return func(*args)\n\n\ndef welcome_user(user):\n    tornado.ioloop.IOLoop.current().run_sync(lambda: main(user), timeout=5)\n"
  },
  {
    "path": "src/backend/tools/py.migrate/setup.cfg",
    "content": "[nosetests]\nmatch = ^test\nnocapture = 1\ncover-package = caliopen\nwith-coverage = 1\ncover-erase = 1\n"
  },
  {
    "path": "src/backend/tools/py.migrate/setup.py",
    "content": "import os\nimport re\n\nfrom setuptools import setup, find_packages\n\nhere = os.path.abspath(os.path.dirname(__file__))\nREADME = open(os.path.join(here, 'README.rst')).read()\nCHANGES = open(os.path.join(here, 'CHANGES.rst')).read()\n\nname = \"caliopen_migrate\"\n\nwith open(os.path.join(*([here] + name.split('.') + ['__init__.py']))) as v_file:\n    version = re.compile(r\".*__version__ = '(.*?)'\", re.S).match(v_file.read()).group(1)\n\nrequires = [\n]\n\nif (os.path.isfile('./requirements.deps')):\n    with open('./requirements.deps') as f_deps:\n        requires.extend(f_deps.read().split('\\n'))\n\nsetup(name=name,\n      version=version,\n      description='Caliopen Data migration utilities`',\n      long_description=README + '\\n\\n' + CHANGES,\n      classifiers=[\n        \"Programming Language :: Python\",\n        \"Topic :: Shell\",\n        ],\n      author='Caliopen Contributors',\n      author_email='',\n      url='https://github.com/Caliopen/Caliopen',\n      keywords='caliopen data migration',\n      packages=find_packages(),\n      include_package_data=True,\n      zip_safe=False,\n      install_requires=requires,\n      tests_require=requires,\n      test_suite='',\n      entry_points={\n          'console_scripts': 'caliopmigre = caliopen_migrate.cli:main',\n      })\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/cmd/idpoller/cli_cmds/root.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage cmd\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/workers/go.remoteIDs\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tconfig     PollerConfig\n\tconfigFile string\n\tconfigPath string\n\tverbose    bool\n\tversion    bool\n\tRootCmd    = &cobra.Command{\n\t\tUse:   \"idpoller\",\n\t\tShort: \"Remote Identities poller\",\n\t\tLong:  `idpoller is a daemon to monitor users's remote identities polls and send NATS messages accordingly`,\n\t\tRun:   nil,\n\t}\n)\n\nconst __version__ = \"0.23.0\"\n\nfunc init() {\n\tRootCmd.PersistentFlags().BoolVarP(&verbose, \"verbose\", \"v\", false,\n\t\t\"print out more debug information\")\n\tRootCmd.PersistentFlags().BoolVarP(&version, \"version\", \"V\", false,\n\t\t\"print out the version of this program\")\n\tRootCmd.Run = func(cmd *cobra.Command, args []string) {\n\t\tif version {\n\t\t\tlog.Infof(\"idpoller version %s\", __version__)\n\t\t}\n\t\tif len(args) == 0 {\n\t\t\tcmd.Help()\n\t\t}\n\t\treadConfig(&config)\n\t}\n\tRootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {\n\t\tif verbose {\n\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t} else {\n\t\t\tlog.SetLevel(log.InfoLevel)\n\t\t}\n\t}\n\tRootCmd.AddCommand(versionCmd)\n}\n\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print the version number of idpoller\",\n\tLong:  `All software has versions. This is idpoller's'`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tlog.Infof(\"idpoller version %s\", __version__)\n\t},\n}\n\n// ReadConfig which should be called at startup, or when a SIG_HUP is caught\nfunc readConfig(config *PollerConfig) error {\n\t// load in the main config. Reading from YAML, TOML, JSON, HCL and Java properties config files\n\tv := viper.New()\n\tv.SetConfigName(configFile)                           // name of config file (without extension)\n\tv.AddConfigPath(configPath)                           // path to look for the config file in\n\tv.AddConfigPath(\"$CALIOPENROOT/src/backend/configs/\") // call multiple times to add many search paths\n\tv.AddConfigPath(\".\")                                  // optionally look for config in the working directory\n\n\terr := v.ReadInConfig() // Find and read the config file\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not read main config file <%s>.\", configFile)\n\t\treturn err\n\t}\n\terr = v.Unmarshal(config)\n\tif err != nil {\n\t\tlog.WithError(err).Infof(\"Could not parse config file: <%s>\", configFile)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/cmd/idpoller/cli_cmds/start.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"fmt\"\n\tidpoller \"github.com/CaliOpen/Caliopen/src/backend/workers/go.remoteIDs\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\nvar (\n\tpidFile       string\n\tsignalChannel chan os.Signal // for trapping SIG_HUP\n\tcmdConfig     idpoller.PollerConfig\n\tstartCmd      = &cobra.Command{\n\t\tUse:   \"start\",\n\t\tShort: \"Starts remote identities poller daemon\",\n\t\tRun:   start,\n\t}\n)\n\nfunc init() {\n\tstartCmd.PersistentFlags().StringVarP(&configFile, \"config\", \"c\",\n\t\t\"idpoller\", \"Name of the configuration file, without extension. (YAML, TOML, JSON… allowed)\")\n\tstartCmd.PersistentFlags().StringVarP(&configPath, \"configpath\", \"\",\n\t\t\"../../../../configs/\", \"Main config file path.\")\n\tstartCmd.PersistentFlags().StringVarP(&pidFile, \"pid-file\", \"p\",\n\t\t\"/var/run/caliopen_idpoller.pid\", \"Path to the pid file\")\n\n\tRootCmd.AddCommand(startCmd)\n\tsignalChannel = make(chan os.Signal, 1)\n\tconfig = idpoller.PollerConfig{}\n}\n\nfunc sigHandler(p *idpoller.Poller) {\n\t// handle SIGHUP for reloading the configuration while running\n\tsignal.Notify(signalChannel, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGKILL)\n\n\tfor sig := range signalChannel {\n\n\t\tif sig == syscall.SIGHUP {\n\t\t\terr := readConfig(&config)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Error(\"Error while ReadConfig (reload)\")\n\t\t\t} else {\n\t\t\t\tlog.Info(\"Configuration is reloaded\")\n\t\t\t}\n\t\t\t// TODO: reinitialize poller\n\t\t} else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT {\n\t\t\tlog.Info(\"Shutdown signal caught\")\n\t\t\tp.Stop()\n\t\t\tlog.Info(\"Shutdown completed, exiting\")\n\t\t\tos.Exit(0)\n\t\t} else {\n\t\t\tos.Exit(0)\n\t\t}\n\t}\n}\n\nfunc start(cmd *cobra.Command, args []string) {\n\n\terr := readConfig(&cmdConfig)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"Error while reading config\")\n\t}\n\t// Write out our PID\n\tif len(pidFile) > 0 {\n\t\tif f, err := os.Create(pidFile); err == nil {\n\t\t\tdefer f.Close()\n\t\t\tif _, err := f.WriteString(fmt.Sprintf(\"%d\", os.Getpid())); err == nil {\n\t\t\t\tf.Sync()\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warnf(\"Error while writing pidFile (%s)\", pidFile)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.WithError(err).Warnf(\"Error while creating pidFile (%s)\", pidFile)\n\t\t}\n\t}\n\n\tpoll, err := idpoller.InitPoller(cmdConfig, verbose)\n\tif err != nil {\n\t\tlog.WithError(err).Fatal(\"can't start poller\")\n\t}\n\n\tgo poll.Start()\n\tlog.Info(\"poller started\")\n\tsigHandler(poll)\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/cmd/idpoller/main.go",
    "content": "/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\n/*\n * // Copyleft (ɔ) 2018 The Caliopen contributors.\n * // Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n * // license (AGPL) that can be found in the LICENSE file.\n */\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/workers/go.remoteIDs/cmd/idpoller/cli_cmds\"\n\t\"os\"\n)\n\nfunc main() {\n\tif err := cmd.RootCmd.Execute(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(-1)\n\t}\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/identities.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage go_remoteIDs\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/store/cassandra\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/gocql/gocql\"\n\t\"gopkg.in/robfig/cron.v2\"\n\t\"sync\"\n)\n\ntype DbHandler struct {\n\tcache           map[string]cacheEntry\n\tcacheMux        *sync.Mutex\n\tremoteProtocols []string\n\tStore           backends.IdentityStorage\n}\n\ntype cacheEntry struct {\n\tcronId         cron.EntryID\n\tiDkey          string\n\tpollInterval   string // in minutes\n\tremoteID       UUID\n\tremoteProtocol string\n\tuserID         UUID\n}\n\nfunc initDbHandler() (*DbHandler, error) {\n\thandler := new(DbHandler)\n\thandler.remoteProtocols = poller.Config.RemoteProtocols\n\tswitch poller.Config.StoreName {\n\tcase \"cassandra\":\n\t\tc := store.CassandraConfig{\n\t\t\tHosts:       poller.Config.StoreConfig.Hosts,\n\t\t\tKeyspace:    poller.Config.StoreConfig.Keyspace,\n\t\t\tConsistency: gocql.Consistency(poller.Config.StoreConfig.Consistency),\n\t\t\tSizeLimit:   poller.Config.StoreConfig.SizeLimit,\n\t\t}\n\t\tif poller.Config.StoreConfig.ObjectStore == \"s3\" {\n\t\t\tc.WithObjStore = true\n\t\t\tc.Endpoint = poller.Config.StoreConfig.OSSConfig.Endpoint\n\t\t\tc.AccessKey = poller.Config.StoreConfig.OSSConfig.AccessKey\n\t\t\tc.SecretKey = poller.Config.StoreConfig.OSSConfig.SecretKey\n\t\t\tc.RawMsgBucket = poller.Config.StoreConfig.OSSConfig.Buckets[\"raw_messages\"]\n\t\t\tc.AttachmentBucket = poller.Config.StoreConfig.OSSConfig.Buckets[\"temporary_attachments\"]\n\t\t\tc.Location = poller.Config.StoreConfig.OSSConfig.Location\n\t\t}\n\t\tdb, err := store.InitializeCassandraBackend(c)\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Warnf(\"[initDbHandler] initialization of %s backend failed\", poller.Config.StoreName)\n\t\t\treturn handler, errors.New(\"[initDbHandler] failed to init cassandra backend\")\n\t\t}\n\t\thandler.Store = db\n\t\thandler.cache = make(map[string]cacheEntry)\n\t\thandler.cacheMux = new(sync.Mutex)\n\t\treturn handler, nil\n\tdefault:\n\t\treturn handler, fmt.Errorf(\"[initDbHandler] unhandled store : %s\", poller.Config.StoreName)\n\t}\n}\n\n// SyncCache resync DbHandler's memory cache with 'active' remote identities found in db\nfunc (dbh *DbHandler) SyncCache() (added, removed, updated map[string]cacheEntry, err error) {\n\tadded = make(map[string]cacheEntry)\n\tremoved = make(map[string]cacheEntry)\n\tupdated = make(map[string]cacheEntry)\n\tactive := make(map[string]cacheEntry)\n\tconst defaultInterval = \"15\"\n\n\tremotes, err := dbh.Store.RetrieveAllRemotes(false)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[updateCache] failed to retrieve remote identities\")\n\t\terr = errors.New(\"[updateCache] failed to retrieve remote identities\")\n\t\treturn\n\t}\n\n\tdbh.cacheMux.Lock()\n\tdefer dbh.cacheMux.Unlock()\n\tfor remote := range remotes {\n\t\tif dbh.statusTypeOK(remote) {\n\t\t\tidkey := remote.UserId.String() + remote.Id.String()\n\t\t\tactive[idkey] = cacheEntry{}\n\t\t\tif entry, ok := dbh.cache[idkey]; ok {\n\t\t\t\t//check if pollinterval has changed\n\t\t\t\tvar pollInterval string\n\t\t\t\tvar ok bool\n\t\t\t\tif pollInterval, ok = remote.Infos[\"pollinterval\"]; !ok || pollInterval == \"\" {\n\t\t\t\t\t// do not resign, take a default value instead\n\t\t\t\t\tpollInterval = defaultInterval\n\t\t\t\t}\n\t\t\t\tif entry.pollInterval != pollInterval {\n\t\t\t\t\tentry.pollInterval = pollInterval\n\t\t\t\t\tupdated[idkey] = entry\n\t\t\t\t\tdbh.cache[idkey] = entry\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar pollInterval string\n\t\t\t\tvar ok bool\n\t\t\t\tif pollInterval, ok = remote.Infos[\"pollinterval\"]; !ok || pollInterval == \"\" {\n\t\t\t\t\t// do not resign, take a default value instead\n\t\t\t\t\tpollInterval = defaultInterval\n\t\t\t\t}\n\t\t\t\tentry := cacheEntry{\n\t\t\t\t\tiDkey:          idkey,\n\t\t\t\t\tpollInterval:   pollInterval,\n\t\t\t\t\tremoteID:       remote.Id,\n\t\t\t\t\tremoteProtocol: remote.Protocol,\n\t\t\t\t\tuserID:         remote.UserId,\n\t\t\t\t}\n\t\t\t\tdbh.cache[idkey] = entry\n\t\t\t\tadded[idkey] = entry\n\t\t\t}\n\t\t}\n\t}\n\tfor key, entry := range dbh.cache {\n\t\tif _, ok := active[key]; !ok {\n\t\t\tremoved[key] = entry\n\t\t\tdelete(dbh.cache, key)\n\t\t}\n\t}\n\treturn\n}\n\n// statusTypeOK checks if remote identity is 'active' and its type is within poller's scope\nfunc (dbh *DbHandler) statusTypeOK(remote *UserIdentity) bool {\n\tif remote.Status != \"active\" {\n\t\treturn false\n\t}\n\tfor _, t := range dbh.remoteProtocols {\n\t\tif t == remote.Protocol {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (dbh *DbHandler) GetCacheEntry(key string) (cacheEntry, bool) {\n\tdbh.cacheMux.Lock()\n\tentry, ok := dbh.cache[key]\n\tdbh.cacheMux.Unlock()\n\treturn entry, ok\n}\n\nfunc (dbh *DbHandler) UpdateCacheEntry(entry cacheEntry) {\n\tdbh.cacheMux.Lock()\n\tdbh.cache[entry.iDkey] = entry\n\tdbh.cacheMux.Unlock()\n}\n\nfunc (dbh *DbHandler) RemoveCacheEntry(key string) {\n\tdbh.cacheMux.Lock()\n\tdelete(dbh.cache, key)\n\tdbh.cacheMux.Unlock()\n}\n\nfunc (dbh *DbHandler) Stop() {\n\tdbh.Store.Close()\n\tdbh.cacheMux.Lock()\n\tdbh.cache = nil\n\tdbh.cacheMux.Unlock()\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/identities_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage go_remoteIDs\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/CaliOpen/Caliopen/src/backend/main/go.backends/backendstest\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\n// initDbHandlerTest returns a DbHandler with a mock IdentityStorage interface\nfunc initDbHandlerTest() (*DbHandler, error) {\n\thandler := new(DbHandler)\n\thandler.remoteProtocols = []string{\"imap\", \"email\", \"twitter\"}\n\thandler.Store = backendstest.GetIdentitiesBackend([]*objects.UserIdentity{}, []*objects.UserIdentity{})\n\thandler.cache = make(map[string]cacheEntry)\n\thandler.cacheMux = new(sync.Mutex)\n\treturn handler, nil\n}\n\nfunc TestDbHandler_SyncCache(t *testing.T) {\n\tdbh, err := initDbHandlerTest()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// test that all active remotes have been added to cache\n\tadded, removed, updated, err := dbh.SyncCache()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif len(added) != backendstest.ActiveRemotesCount() || len(dbh.cache) != backendstest.ActiveRemotesCount() {\n\t\tt.Errorf(\"not all active remotes where added to cache : expected %d, but added=%d and cache length is %d\", backendstest.ActiveRemotesCount(), len(added), len(dbh.cache))\n\t}\n\tif len(removed) != 0 {\n\t\tt.Errorf(\"expected 0 for removed, had %d\", len(removed))\n\t}\n\tif len(updated) != 0 {\n\t\tt.Errorf(\"expected 0 for updated, had %d\", len(updated))\n\t}\n\n\t// test cache entries are well-formed\n\tfor key, entry := range dbh.cache {\n\t\tif key != entry.userID.String()+entry.remoteID.String() {\n\t\t\tt.Errorf(\"key entry not equal to userID+remoteID : expected %s, got %s\", entry.userID.String()+entry.remoteID.String(), key)\n\t\t}\n\t\tif entry.pollInterval == \"\" {\n\t\t\tt.Errorf(\"poll interval is empty, it should have been set to default\")\n\t\t}\n\t}\n\n\t// sync again to check if nothing has changed into cache\n\tadded, removed, updated, err = dbh.SyncCache()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif len(added) != 0 || len(removed) != 0 || len(updated) != 0 {\n\t\tt.Errorf(\"expected empty arrays after resync, had : added=%d, removed=%d, updated=%d\", len(added), len(removed), len(updated))\n\t}\n\n\t// modifications of cache below should trigger an add, an update and a delete after syncing again\n\tremotesTestSet, _ := dbh.Store.RetrieveAllRemotes(false)\n\t// remove one identity from cache, add an inactive one and update one\n\tvar oneDelete bool\n\tvar oneInactive bool\n\tvar oneUpdate bool\n\tfor remote := range remotesTestSet {\n\t\tif dbh.statusTypeOK(remote) {\n\t\t\tif !oneDelete {\n\t\t\t\tdelete(dbh.cache, remote.UserId.String()+remote.Id.String())\n\t\t\t\toneDelete = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !oneUpdate {\n\t\t\t\tidkey := remote.UserId.String() + remote.Id.String()\n\t\t\t\tif c, ok := dbh.cache[idkey]; ok {\n\t\t\t\t\tc.pollInterval = \"\"\n\t\t\t\t\tdbh.cache[idkey] = c\n\t\t\t\t\toneUpdate = true\n\t\t\t\t\tcontinue\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"failed to retrieve cache entry %s\", idkey)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif remote.Status == \"inactive\" {\n\t\t\tidkey := remote.UserId.String() + remote.Id.String()\n\t\t\tdbh.cache[idkey] = cacheEntry{\n\t\t\t\tiDkey:          idkey,\n\t\t\t\tpollInterval:   defaultInterval,\n\t\t\t\tremoteID:       remote.Id,\n\t\t\t\tremoteProtocol: remote.Protocol,\n\t\t\t\tuserID:         remote.UserId,\n\t\t\t}\n\t\t\toneInactive = true\n\t\t}\n\t\tif oneDelete && oneInactive && oneUpdate {\n\t\t\tbreak\n\t\t}\n\t}\n\t// syncing again\n\tadded, removed, updated, err = dbh.SyncCache()\n\tif len(added) != 1 {\n\t\tt.Errorf(\"expected one identity added, got %d\", len(added))\n\t}\n\tif len(removed) != 1 {\n\t\tt.Errorf(\"expected one identity removed, got %d\", len(added))\n\t}\n\tif len(updated) != 1 {\n\t\tt.Errorf(\"expected one identity updated, got %d\", len(added))\n\t}\n}\n\nfunc TestDbHandler_GetCacheEntry(t *testing.T) {\n\tdbh, err := initDbHandlerTest()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// fill cache with entries\n\tconst count = 1000\n\tvar key string\n\tfor i := 0; i < count; i++ {\n\t\tkey = strconv.Itoa(i)\n\t\tdbh.cache[key] = cacheEntry{\n\t\t\tiDkey:        key,\n\t\t\tpollInterval: \"1\",\n\t\t}\n\t}\n\n\t// test not found entry\n\tif _, ok := dbh.GetCacheEntry(\"unvalid\"); ok {\n\t\tt.Error(\"GetCacheEntry return true, expected false\")\n\t}\n\n\t// test concurrent access\n\twg := new(sync.WaitGroup)\n\tc := make(chan struct{})\n\twg.Add(count)\n\tfor i := 0; i < count; i++ {\n\t\tgo func(key string) {\n\t\t\tif _, ok := dbh.GetCacheEntry(key); !ok {\n\t\t\t\tt.Errorf(\"failed to retrieve cache entry <%s>\", key)\n\t\t\t}\n\t\t\twg.Done()\n\t\t}(strconv.Itoa(i))\n\t}\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(c)\n\t}()\n\tselect {\n\tcase <-c:\n\t\treturn\n\tcase <-time.After(2 * time.Second):\n\t\tt.Error(\"timeout waiting for concurrent get\")\n\t}\n}\n\nfunc TestDbHandler_UpdateCacheEntry(t *testing.T) {\n\tdbh, err := initDbHandlerTest()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// fill cache with entries\n\tconst count = 1000\n\tvar key string\n\tfor i := 0; i < count; i++ {\n\t\tkey = strconv.Itoa(i)\n\t\tdbh.cache[key] = cacheEntry{\n\t\t\tiDkey:        key,\n\t\t\tpollInterval: \"1\",\n\t\t}\n\t}\n\n\t// test concurrent update\n\twg := new(sync.WaitGroup)\n\tc := make(chan struct{})\n\twg.Add(count)\n\tfor i := 0; i < count; i++ {\n\t\tgo func(interval, key string) {\n\t\t\tdbh.UpdateCacheEntry(cacheEntry{\n\t\t\t\tiDkey:        key,\n\t\t\t\tpollInterval: interval,\n\t\t\t})\n\t\t\twg.Done()\n\t\t}(strconv.Itoa(i*2), strconv.Itoa(i))\n\t}\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(c)\n\t}()\n\tselect {\n\tcase <-c:\n\t\tfor i := 0; i < count; i++ {\n\t\t\tkey := strconv.Itoa(i)\n\t\t\tif entry, ok := dbh.GetCacheEntry(key); ok {\n\t\t\t\tif entry.pollInterval != strconv.Itoa(i*2) {\n\t\t\t\t\tt.Errorf(\"expected pollintervall to be %s, got %s\", strconv.Itoa(i*2), entry.pollInterval)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tt.Error(\"failed to get cache entry 'key' to test UpdateCacheEntry\")\n\t\t\t}\n\t\t}\n\tcase <-time.After(2 * time.Second):\n\t\tt.Error(\"timeout waiting for concurrent update\")\n\t}\n}\n\nfunc TestDbHandler_RemoveCacheEntry(t *testing.T) {\n\tdbh, err := initDbHandlerTest()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// fill cache with entries\n\tconst count = 1000 // use an even number\n\tvar key string\n\tfor i := 0; i < count; i++ {\n\t\tkey = strconv.Itoa(i)\n\t\tdbh.cache[key] = cacheEntry{\n\t\t\tiDkey:        key,\n\t\t\tpollInterval: \"1\",\n\t\t}\n\t}\n\n\t// test concurrent remove\n\twg := new(sync.WaitGroup)\n\tc := make(chan struct{})\n\twg.Add(count / 2)\n\tfor i := 0; i < count/2; i++ {\n\t\tgo func(key string) {\n\t\t\tdbh.RemoveCacheEntry(key)\n\t\t\twg.Done()\n\t\t}(strconv.Itoa(i))\n\t}\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(c)\n\t}()\n\tselect {\n\tcase <-c:\n\t\tif len(dbh.cache) != count/2 {\n\t\t\tt.Errorf(\"expected cache with %d entries, got %d\", count/2, len(dbh.cache))\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Error(\"timeout waiting for concurrent remove\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/idpoller.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage go_remoteIDs\n\nimport (\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n)\n\ntype PollerConfig struct {\n\tScanInterval    uint16            `mapstructure:\"scan_interval\"`\n\tRemoteProtocols []string          `mapstructure:\"remote_protocols\"`\n\tStoreName       string            `mapstructure:\"store_name\"`\n\tStoreConfig     StoreConfig       `mapstructure:\"store_settings\"`\n\tNatsUrl         string            `mapstructure:\"nats_url\"`\n\tNatsQueue       string            `mapstructure:\"nats_queue\"`\n\tNatsTopics      map[string]string `mapstructure:\"nats_topics\"`\n}\n\nconst (\n\timapWorker      = \"imap\"\n\ttwitterWorker   = \"twitter\"\n\tmastodonWorker  = \"mastodon\"\n\tnoPendingJobErr = \"no pending job\"\n)\n\ntype Poller struct {\n\tConfig PollerConfig\n\tdbh    *DbHandler\n\tmqh    *MqHandler\n\tsched  *Scheduler\n\tjobs   *JobsHandler\n}\n\nvar poller *Poller\n\nfunc init() {\n\tpoller = new(Poller)\n}\n\nfunc InitPoller(config PollerConfig, verboseLog bool) (idpoller *Poller, err error) {\n\tif verboseLog {\n\t\tlog.SetLevel(log.DebugLevel)\n\t}\n\tpoller.Config = config\n\n\tpoller.mqh, err = InitMqHandler()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpoller.dbh, err = initDbHandler()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpoller.sched, err = initScheduler()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpoller.jobs, err = initJobsHandler()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn poller, nil\n}\n\nfunc (p *Poller) Start() error {\n\t// do a full sync with db once before starting Scheduler\n\tp.fullSync()\n\tp.sched.Start()\n\treturn nil\n}\n\nfunc (p *Poller) Stop() {\n\tp.sched.Stop()\n\tp.mqh.Stop()\n\tp.dbh.Stop()\n}\n\nfunc (p *Poller) fullSync() {\n\tlog.Info(\"[poller] starting full sync with db…\")\n\t// update dbh's cache with 'active' remote identities found in db\n\tadded, removed, updated, err := p.dbh.SyncCache()\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[dbSync] scheduler failed to update cache\")\n\t\treturn\n\t}\n\t// re-schedule jobs accordingly and update dbh's cache with jobs' cronId\n\tfor _, entry := range added {\n\t\tentry, err := p.sched.AddSyncJobFor(entry)\n\t\tif err == nil {\n\t\t\tp.dbh.UpdateCacheEntry(entry)\n\t\t}\n\t\t// TODO: add protocol worker\n\t}\n\tfor _, entry := range removed {\n\t\tp.sched.RemoveJobFor(entry)\n\t\tp.dbh.RemoveCacheEntry(entry.iDkey)\n\t\t//TODO: remove protocol worker\n\t}\n\tfor _, entry := range updated {\n\t\tentry, err := p.sched.UpdateSyncJobFor(entry)\n\t\tif err == nil {\n\t\t\tp.dbh.UpdateCacheEntry(entry)\n\t\t}\n\t\t//TODO: update protocol worker\n\t}\n\n\tlog.Infof(\"[poller] full sync ended : %d jobs added, %d jobs removed, %d jobs updated.\\n           => %d jobs scheduled in cron table.\",\n\t\tlen(added), len(removed), len(updated), len(p.sched.MainCron.Entries()))\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/idpollertest/testsreplies.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\n// package idpollertest provides routines to reply to requests sent by tests on idpoller's nats topics\npackage idpollertest\n\nimport (\n\t\"encoding/json\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tidpoller \"github.com/CaliOpen/Caliopen/src/backend/workers/go.remoteIDs\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/satori/go.uuid\"\n\t\"math/rand\"\n\t\"time\"\n)\n\nvar Mqh *idpoller.MqHandler\n\nfunc RegisterIdpollerTest(conn *nats.Conn) (*idpoller.MqHandler, error) {\n\tMqh = new(idpoller.MqHandler)\n\tsub, err := conn.Subscribe(\"twitterJobs\", twitterJobsHandler)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tMqh.NatsSubTwitter = sub\n\treturn Mqh, nil\n}\n\nfunc twitterJobsHandler(msg *nats.Msg) {\n\tvar req WorkerRequest\n\terr := json.Unmarshal(msg.Data, &req)\n\tif err != nil {\n\t\tMqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error : unable to unmarshal request\"}`))\n\t}\n\tswitch req.Order.Order {\n\tcase \"need_job\":\n\t\t// randomly give a job or no job\n\t\trand.Seed(time.Now().Unix())\n\t\tif rand.Intn(2) == 0 {\n\t\t\tMqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"no pending job\"}`))\n\t\t} else {\n\t\t\tfakeID := uuid.NewV4().String()\n\t\t\tjob := idpoller.Job{\n\t\t\t\tWorker: \"twitter\",\n\t\t\t\tOrder: BrokerOrder{\n\t\t\t\t\tMessageId:  fakeID,\n\t\t\t\t\tOrder:      \"sync\",\n\t\t\t\t\tIdentityId: fakeID,\n\t\t\t\t\tUserId:     fakeID,\n\t\t\t\t},\n\t\t\t}\n\t\t\treply, _ := json.Marshal(job.Order)\n\t\t\tMqh.NatsConn.Publish(msg.Reply, reply)\n\t\t}\n\tcase \"give_job\":\n\t\tfakeID := uuid.NewV4().String()\n\t\tjob := idpoller.Job{\n\t\t\tWorker: \"twitter\",\n\t\t\tOrder: BrokerOrder{\n\t\t\t\tMessageId:  fakeID,\n\t\t\t\tOrder:      \"sync\",\n\t\t\t\tIdentityId: fakeID,\n\t\t\t\tUserId:     fakeID,\n\t\t\t},\n\t\t}\n\t\treply, _ := json.Marshal(job.Order)\n\t\tMqh.NatsConn.Publish(msg.Reply, reply)\n\tcase \"give_no_job\":\n\t\tMqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"no pending job\"}`))\n\tcase \"give_error\":\n\t\tMqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error\"}`))\n\tdefault:\n\t\tMqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error : unknown order\"}`))\n\t}\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/jobs.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage go_remoteIDs\n\nimport (\n\t\"crypto/sha256\"\n\t\"errors\"\n\t\"fmt\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"sync\"\n)\n\ntype JobsHandler struct {\n\tpendingJobs  map[string]map[[32]byte]Job // pattern is [worker][job-hash]job\n\tjobsSequence map[string][][32]byte       // pattern is [worker][]job-hash <- FIFO ordered\n\tjobsMux      *sync.Mutex\n}\n\ntype Job struct {\n\tWorker string // protocol worker like email, twitter, etc. as defined in idpoller's config\n\tOrder  BrokerOrder\n}\n\nfunc initJobsHandler() (*JobsHandler, error) {\n\treturn &JobsHandler{\n\t\tmap[string]map[[32]byte]Job{},\n\t\tmap[string][][32]byte{},\n\t\t&sync.Mutex{},\n\t}, nil\n}\n\n// AddPendingJob adds a job to _pending_ map only if job's hash does not already exists for job's worker.\n// pending jobs are ordered in a FIFO list per each worker.\nfunc (jh *JobsHandler) AddPendingJob(job Job) {\n\tjh.jobsMux.Lock()\n\tkey := sha256.Sum256([]byte(job.Order.UserId + job.Order.IdentityId + job.Order.Order))\n\tif _, ok := jh.pendingJobs[job.Worker]; !ok {\n\t\tjh.pendingJobs[job.Worker] = make(map[[32]byte]Job)\n\t}\n\tif _, ok := jh.pendingJobs[job.Worker][key]; !ok {\n\t\tjh.pendingJobs[job.Worker][key] = job\n\t\tjh.jobsSequence[job.Worker] = append(jh.jobsSequence[job.Worker], key)\n\t}\n\tlog.Debugf(\"pending jobs list : %+v\\n\", jh.pendingJobs)\n\tlog.Debugf(\"jobs sequence list : %+v\\n\", jh.jobsSequence)\n\tlog.Infof(\"[jobsHandler] jobs for workers <%s> updated : %d job(s) pending\", job.Worker, len(jh.jobsSequence[job.Worker]))\n\tjh.jobsMux.Unlock()\n}\n\n// ConsumePendingJobFor returns first-in pending job for worker and removes it from lists\n// returns error if no pending job\nfunc (jh *JobsHandler) ConsumePendingJobFor(worker string) (Job, error) {\n\tjh.jobsMux.Lock()\n\tdefer jh.jobsMux.Unlock()\n\tif len(jh.jobsSequence[worker]) == 0 {\n\t\treturn Job{}, errors.New(noPendingJobErr)\n\t}\n\t// get most ancient job for worker\n\tjob := jh.pendingJobs[worker][jh.jobsSequence[worker][0]]\n\t// remove job from pending queue\n\tdelete(jh.pendingJobs[worker], jh.jobsSequence[worker][0])\n\t// truncate jobsSequence list\n\tjh.jobsSequence[worker] = jh.jobsSequence[worker][1:]\n\n\tlog.Infof(\"[jobsHandler] 1 <%s> job consumed  : %d job(s) pending\", job.Worker, len(jh.jobsSequence[job.Worker]))\n\tlog.Debugf(\"pending jobs list : %+v\", jh.pendingJobs)\n\tlog.Debugf(\"jobs sequence list : %+v\", jh.jobsSequence)\n\treturn job, nil\n}\n\n// Run implements cron.Job interface to add job to relevant worker's list\nfunc (j Job) Run() {\n\tpoller.jobs.AddPendingJob(j)\n}\n\nfunc buildSyncJob(entry cacheEntry) (Job, error) {\n\tvar job Job\n\n\t// need a switch to avoid copying remote protocol directly into worker's name because of legacy deprecated protocol name 'imap'\n\tswitch entry.remoteProtocol {\n\tcase \"email\", \"imap\":\n\t\tjob.Worker = imapWorker\n\tcase \"twitter\":\n\t\tjob.Worker = twitterWorker\n\tcase \"mastodon\":\n\t\tjob.Worker = mastodonWorker\n\tdefault:\n\t\treturn Job{}, fmt.Errorf(\"unhandled remote protocol : %s\", entry.remoteProtocol)\n\t}\n\tjob.Order = BrokerOrder{\n\t\tOrder:      \"sync\",\n\t\tUserId:     entry.userID.String(),\n\t\tIdentityId: entry.remoteID.String(),\n\t}\n\tlog.Debugf(\"new job built : %+v\", job)\n\treturn job, nil\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/jobs_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage go_remoteIDs\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/satori/go.uuid\"\n\t\"math/rand\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestJobsHandler_AddPendingJob(t *testing.T) {\n\tjbh, err := initJobsHandler()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tif jbh == nil {\n\t\tt.Error(\"no JobsHandler returned\")\n\t\treturn\n\t}\n\n\t// test concurrent add\n\twg := new(sync.WaitGroup)\n\tc := make(chan struct{})\n\tconst adds = 1000\n\twg.Add(adds)\n\tfor i := 0; i < adds; i++ {\n\t\tgo func() {\n\t\t\tjob := Job{\n\t\t\t\tWorker: \"worker\",\n\t\t\t\tOrder: objects.BrokerOrder{\n\t\t\t\t\tMessageId:  \"message_id\",\n\t\t\t\t\tOrder:      strconv.Itoa(i),\n\t\t\t\t\tIdentityId: \"identity_id\",\n\t\t\t\t\tUserId:     \"user_id\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tjbh.AddPendingJob(job)\n\t\t\twg.Done()\n\t\t}()\n\t}\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(c)\n\t}()\n\tselect {\n\tcase <-c:\n\t\treturn\n\tcase <-time.After(2 * time.Second):\n\t\tt.Error(\"timeout waiting for concurrent AddPendingJob\")\n\t}\n\tif len(jbh.pendingJobs) != adds {\n\t\tt.Errorf(\"expected %d pending jobs, got %d\", adds, len(jbh.pendingJobs))\n\t}\n\tif len(jbh.jobsSequence) != adds {\n\t\tt.Errorf(\"expected %d elems in sequence list, got %d\", adds, len(jbh.jobsSequence))\n\t}\n\n\t// test that pendingJobs and jobsSequence are synchronized by picking some jobs randomly\n\trand.Seed(time.Now().Unix())\n\tfor i := 0; i < adds/4; i++ {\n\t\tpick := rand.Intn(adds)\n\t\tjobHash := jbh.jobsSequence[\"worker\"][pick]\n\t\tif _, ok := jbh.pendingJobs[\"worker\"][jobHash]; !ok {\n\t\t\tt.Errorf(\"job %s is in jobsSequence map but not in pendingJobs map\", jobHash)\n\t\t}\n\t}\n\n}\n\nfunc TestJobsHandler_ConsumePendingJobFor(t *testing.T) {\n\tjbh, err := initJobsHandler()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tif jbh == nil {\n\t\tt.Error(\"no JobsHandler returned\")\n\t\treturn\n\t}\n\t// fill jobs queue for two different worker types\n\tconst count = 1000\n\tvar worker string\n\tfor i := 0; i < count; i++ {\n\t\tif i%2 == 0 {\n\t\t\tworker = \"even\"\n\t\t} else {\n\t\t\tworker = \"odd\"\n\t\t}\n\t\tjbh.AddPendingJob(Job{\n\t\t\tWorker: worker,\n\t\t\tOrder: objects.BrokerOrder{\n\t\t\t\tMessageId:  \"message_id\",\n\t\t\t\tOrder:      strconv.Itoa(i),\n\t\t\t\tIdentityId: \"identity_id\",\n\t\t\t\tUserId:     \"user_id\",\n\t\t\t},\n\t\t})\n\t}\n\t// consume jobs to check FIFO returns\n\tfor i := 0; i < count; i++ {\n\t\tif i%2 == 0 {\n\t\t\tworker = \"even\"\n\t\t} else {\n\t\t\tworker = \"odd\"\n\t\t}\n\t\tjob, err := jbh.ConsumePendingJobFor(worker)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tif job.Order.Order != strconv.Itoa(i) {\n\t\t\tt.Errorf(\"expected to have job #%d for worker '%s', got %s\", i, worker, job.Order.Order)\n\t\t}\n\t}\n\tif len(jbh.pendingJobs[\"even\"]) != 0 {\n\t\tt.Errorf(\"expected an empty pending jobs list for 'even', got %d jobs left\", len(jbh.pendingJobs))\n\t}\n\tif len(jbh.pendingJobs[\"odd\"]) != 0 {\n\t\tt.Errorf(\"expected an empty pending jobs list for 'odd', got %d jobs left\", len(jbh.pendingJobs))\n\t}\n\tif len(jbh.jobsSequence[\"even\"]) != 0 {\n\t\tt.Errorf(\"expected an empty jobs sequence list for 'even', got %d elems left\", len(jbh.jobsSequence))\n\t}\n\tif len(jbh.jobsSequence[\"odd\"]) != 0 {\n\t\tt.Errorf(\"expected an empty jobs sequence list for 'odd', got %d elems left\", len(jbh.jobsSequence))\n\t}\n}\n\nfunc TestBuildSyncJob(t *testing.T) {\n\tid := objects.UUID(uuid.NewV4())\n\tjob, err := buildSyncJob(cacheEntry{\n\t\tremoteProtocol: \"email\",\n\t\tuserID:         id,\n\t\tremoteID:       id,\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif job.Order.UserId != id.String() || job.Order.IdentityId != id.String() {\n\t\tt.Errorf(\"job's uuid and cacheEntry's uuid mismatch\")\n\t}\n\n\tif job.Worker != imapWorker {\n\t\tt.Errorf(\"expected job to be for 'imap' worker, got %s\", job.Worker)\n\t}\n\tjob, err = buildSyncJob(cacheEntry{\n\t\tremoteProtocol: \"bad_protocol\",\n\t\tuserID:         id,\n\t\tremoteID:       id,\n\t})\n\tif err == nil {\n\t\tt.Error(\"expected buildSyncJob returned error 'unhandled remote protocol', got nil\")\n\t}\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/messaging.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage go_remoteIDs\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t. \"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"github.com/nats-io/go-nats\"\n\t\"github.com/satori/go.uuid\"\n\t\"strconv\"\n)\n\ntype MqHandler struct {\n\tNatsConn          *nats.Conn\n\tNatsSubIdentities *nats.Subscription\n\tNatsSubImap       *nats.Subscription\n\tNatsSubTwitter    *nats.Subscription\n\tNatsSubMastodon   *nats.Subscription\n}\n\nconst defaultInterval = \"15\"\n\nfunc InitMqHandler() (*MqHandler, error) {\n\thandler := new(MqHandler)\n\tnatsConn, err := nats.Connect(poller.Config.NatsUrl)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[initMqHandler] : initialization of NATS connexion failed\")\n\t\treturn handler, errors.New(\"[initMqHandler] failed to init NATS connection\")\n\t}\n\thandler.NatsConn = natsConn\n\tsub, err := handler.NatsConn.QueueSubscribe(poller.Config.NatsTopics[\"id_cache\"], poller.Config.NatsQueue, handler.natsIdentitiesHandler)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[initMqHandler] : initialization of NATS subscription failed for topic id_cache\")\n\t\thandler.NatsConn = nil\n\t\treturn handler, errors.New(\"[initMqHandler] failed to init NATS subscription\")\n\t}\n\thandler.NatsSubIdentities = sub\n\n\tsub, err = handler.NatsConn.QueueSubscribe(poller.Config.NatsTopics[\"imap\"], poller.Config.NatsQueue, handler.natsImapHandler)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[initMqHandler] : initialization of NATS subscription failed for topic imap\")\n\t\thandler.NatsConn = nil\n\t\treturn handler, errors.New(\"[initMqHandler] failed to init NATS subscription\")\n\t}\n\thandler.NatsSubImap = sub\n\n\tsub, err = handler.NatsConn.QueueSubscribe(poller.Config.NatsTopics[\"twitter\"], poller.Config.NatsQueue, handler.natsTwitterHandler)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[initMqHandler] : initialization of NATS subscription failed for topic twitter\")\n\t\thandler.NatsConn = nil\n\t\treturn handler, errors.New(\"[initMqHandler] failed to init NATS subscription\")\n\t}\n\thandler.NatsSubTwitter = sub\n\n\tsub, err = handler.NatsConn.QueueSubscribe(poller.Config.NatsTopics[\"mastodon\"], poller.Config.NatsQueue, handler.natsMastodonHandler)\n\tif err != nil {\n\t\tlog.WithError(err).Warnf(\"[initMqHandler] : initialization of NATS subscription failed for topic mastodon\")\n\t\thandler.NatsConn = nil\n\t\treturn handler, errors.New(\"[initMqHandler] failed to init NATS subscription\")\n\t}\n\thandler.NatsSubMastodon = sub\n\treturn handler, nil\n}\n\n// natsIdentitiesHandler handles nats messages received by idpoller on id_cache topic\nfunc (mqh *MqHandler) natsIdentitiesHandler(msg *nats.Msg) {\n\n\tvar order RemoteIDNatsMessage\n\terr := json.Unmarshal(msg.Data, &order)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[natsIdentitiesHandler] unable to unmarshal nats order\")\n\t\treturn\n\t}\n\tswitch order.Order {\n\tcase \"update_interval\":\n\t\tidKey := order.UserId + order.IdentityId\n\t\tif entry, ok := poller.dbh.GetCacheEntry(idKey); ok {\n\t\t\tentry.pollInterval = order.OrderParam\n\t\t\tlog.Debugf(\"[natsIdentitiesHandler] updating pollIntervall for entry : %+v\", entry)\n\t\t\tpoller.dbh.UpdateCacheEntry(entry)\n\t\t\t_, err := poller.sched.UpdateSyncJobFor(entry)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"[natsIngressHandler] failed to updateSyncJobFor %+v\", entry)\n\t\t\t}\n\t\t}\n\tcase \"delete\":\n\t\tidKey := order.UserId + order.IdentityId\n\t\tif entry, ok := poller.dbh.GetCacheEntry(idKey); ok {\n\t\t\tlog.Debugf(\"[natsIdentitiesHandler] deleting cache entry : %+s\", idKey)\n\t\t\tpoller.dbh.RemoveCacheEntry(idKey)\n\t\t\tpoller.sched.RemoveJobFor(entry)\n\t\t}\n\tcase \"add\":\n\t\tidKey := order.UserId + order.IdentityId\n\t\tvar pollInterval string\n\t\t// prevent boundaries overflow : min = 1 min, max = 3 days\n\t\tif interval, err := strconv.Atoi(order.OrderParam); err == nil && interval > 0 && interval < 3*24*60 {\n\t\t\tpollInterval = order.OrderParam\n\t\t} else {\n\t\t\tpollInterval = defaultInterval\n\t\t}\n\t\tentry := cacheEntry{\n\t\t\tiDkey:          idKey,\n\t\t\tpollInterval:   pollInterval,\n\t\t\tremoteID:       UUID(uuid.FromStringOrNil(order.IdentityId)),\n\t\t\tremoteProtocol: order.Protocol,\n\t\t\tuserID:         UUID(uuid.FromStringOrNil(order.UserId)),\n\t\t}\n\t\tentry, err := poller.sched.AddSyncJobFor(entry)\n\t\tif err == nil {\n\t\t\tpoller.dbh.UpdateCacheEntry(entry)\n\t\t}\n\tdefault:\n\t\tlog.Warnf(\"no handler for order '%s' on topic '%s'\", order.Order, msg.Subject)\n\t}\n}\n\nfunc (mqh *MqHandler) natsImapHandler(msg *nats.Msg) {\n\tvar req WorkerRequest\n\terr := json.Unmarshal(msg.Data, &req)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[natsImapHandler] unable to unmarshal nats request\")\n\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error : unable to unmarshal request\"}`))\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Warn(\"[natsImapHandler] failed to publish reply on nats\")\n\t\t}\n\t}\n\n\tswitch req.Order.Order {\n\tcase \"need_job\":\n\t\tjob, err := poller.jobs.ConsumePendingJobFor(imapWorker)\n\t\tif err != nil {\n\t\t\tif err.Error() == noPendingJobErr {\n\t\t\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"no pending job\"}`))\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warn(\"[natsImapHandler] failed to publish reply on nats\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warn(\"[natsImapHandler] failed to get a job for worker\")\n\t\t\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error\"}`))\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warn(\"[natsImapHandler] failed to publish reply on nats\")\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Debugf(\"[natsImapHandler] replying to %s with job : %+v\", msg.Reply, job)\n\t\t\treply, err := json.Marshal(job.Order)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"[natsImapHandler] failed to json Marshal job : %+v\", job)\n\t\t\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error\"}`))\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warn(\"[natsImapHandler] failed to publish reply on nats\")\n\t\t\t\t}\n\t\t\t}\n\t\t\t// forwarding job to worker\n\t\t\terr = mqh.NatsConn.Publish(msg.Reply, reply)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warn(\"[natsImapHandler] failed to publish reply on nats\")\n\t\t\t}\n\t\t}\n\tdefault:\n\t\tlog.Warnf(\"[natsImapHandler] received unknown order : %s\", req.Order)\n\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error : unknown order\"}`))\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Warn(\"[natsImapHandler] failed to publish reply on nats\")\n\t\t}\n\t}\n}\n\nfunc (mqh *MqHandler) natsTwitterHandler(msg *nats.Msg) {\n\tvar req WorkerRequest\n\terr := json.Unmarshal(msg.Data, &req)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[natsTwitterHandler] unable to unmarshal nats request\")\n\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error : unable to unmarshal request\"}`))\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Warn(\"[natsTwitterHandler] failed to publish reply on nats\")\n\t\t}\n\t}\n\n\tswitch req.Order.Order {\n\tcase \"need_job\":\n\t\tjob, err := poller.jobs.ConsumePendingJobFor(twitterWorker)\n\t\tif err != nil {\n\t\t\tif err.Error() == noPendingJobErr {\n\t\t\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"no pending job\"}`))\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warn(\"[natsTwitterHandler] failed to publish reply on nats\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warn(\"[natsTwitterHandler] failed to get a job for worker\")\n\t\t\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error\"}`))\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warn(\"[natsTwitterHandler] failed to publish reply on nats\")\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Debugf(\"[natsTwitterHandler] replying to %s with job : %+v\", msg.Reply, job)\n\t\t\treply, err := json.Marshal(job.Order)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"[natsTwitterHandler] failed to json Marshal job : %+v\", job)\n\t\t\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error\"}`))\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warn(\"[natsTwitterHandler] failed to publish reply on nats\")\n\t\t\t\t}\n\t\t\t}\n\t\t\t// forwarding job to worker\n\t\t\terr = mqh.NatsConn.Publish(msg.Reply, reply)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warn(\"[natsTwitterHandler] failed to publish reply on nats\")\n\t\t\t}\n\t\t}\n\tdefault:\n\t\tlog.Warnf(\"[natsTwitterHandler] received unknown order : %s\", req.Order)\n\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error : unknown order\"}`))\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Warn(\"[natsTwitterHandler] failed to publish reply on nats\")\n\t\t}\n\t}\n}\n\nfunc (mqh *MqHandler) natsMastodonHandler(msg *nats.Msg) {\n\tvar req WorkerRequest\n\terr := json.Unmarshal(msg.Data, &req)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[natsMastodonHandler] unable to unmarshal nats request\")\n\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error : unable to unmarshal request\"}`))\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Warn(\"[natsMastodonHandler] failed to publish reply on nats\")\n\t\t}\n\t}\n\n\tswitch req.Order.Order {\n\tcase \"need_job\":\n\t\tjob, err := poller.jobs.ConsumePendingJobFor(mastodonWorker)\n\t\tif err != nil {\n\t\t\tif err.Error() == noPendingJobErr {\n\t\t\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"no pending job\"}`))\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warn(\"[natsMastodonHandler] failed to publish reply on nats\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.WithError(err).Warn(\"[natsMastodonHandler] failed to get a job for worker\")\n\t\t\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error\"}`))\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warn(\"[natsMastodonHandler] failed to publish reply on nats\")\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Debugf(\"[natsMastodonHandler] replying to %s with job : %+v\", msg.Reply, job)\n\t\t\treply, err := json.Marshal(job.Order)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warnf(\"[natsMastodonHandler] failed to json Marshal job : %+v\", job)\n\t\t\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error\"}`))\n\t\t\t\tif e != nil {\n\t\t\t\t\tlog.WithError(e).Warn(\"[natsMastodonHandler] failed to publish reply on nats\")\n\t\t\t\t}\n\t\t\t}\n\t\t\t// forwarding job to worker\n\t\t\terr = mqh.NatsConn.Publish(msg.Reply, reply)\n\t\t\tif err != nil {\n\t\t\t\tlog.WithError(err).Warn(\"[natsMastodonHandler] failed to publish reply on nats\")\n\t\t\t}\n\t\t}\n\tdefault:\n\t\tlog.Warnf(\"[natsMastodonHandler] received unknown order : %s\", req.Order)\n\t\te := mqh.NatsConn.Publish(msg.Reply, []byte(`{\"order\":\"error : unknown order\"}`))\n\t\tif e != nil {\n\t\t\tlog.WithError(e).Warn(\"[natsMastodonHandler] failed to publish reply on nats\")\n\t\t}\n\t}\n}\n\nfunc (mqh *MqHandler) Stop() {\n\tmqh.NatsSubIdentities.Unsubscribe()\n\tmqh.NatsSubImap.Unsubscribe()\n\tmqh.NatsSubTwitter.Unsubscribe()\n\tmqh.NatsSubMastodon.Unsubscribe()\n\tmqh.NatsConn.Close()\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/messaging_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage go_remoteIDs\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/nats-io/gnatsd/server\"\n\t\"github.com/phayes/freeport\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst (\n\tnatsUrl = \"0.0.0.0\"\n)\n\nfunc initMqHandlerTest() (*MqHandler, error) {\n\t// starting an embedded nats server\n\tport, err := freeport.GetFreePort()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts, err := server.NewServer(&server.Options{\n\t\tHost:     natsUrl,\n\t\tPort:     port,\n\t\tHTTPPort: -1,\n\t\tCluster:  server.ClusterOpts{Port: -1},\n\t\tNoLog:    true,\n\t\tNoSigs:   true,\n\t\tDebug:    false,\n\t\tTrace:    false,\n\t})\n\tif err != nil || s == nil {\n\t\tpanic(fmt.Sprintf(\"No NATS Server object returned: %v\", err))\n\t}\n\tgo s.Start()\n\t// Wait for accept loop(s) to be started\n\tif !s.ReadyForConnections(10 * time.Second) {\n\t\treturn nil, errors.New(\"Unable to start NATS Server in Go Routine\")\n\t}\n\n\tpoller.Config = PollerConfig{\n\t\tNatsUrl: \"nats://\" + natsUrl + \":\" + strconv.Itoa(port),\n\t\tNatsTopics: map[string]string{\n\t\t\t\"id_cache\": \"idCache\",\n\t\t\t\"imap\":     \"imapJobs\",\n\t\t\t\"twitter\":  \"twitterJobs\",\n\t\t},\n\t}\n\n\treturn InitMqHandler()\n}\n\nfunc TestInitMqHandler(t *testing.T) {\n\tmqh, err := initMqHandlerTest()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tif mqh == nil {\n\t\tt.Error(\"no mqh returned\")\n\t\treturn\n\t}\n\tif mqh.NatsConn == nil {\n\t\tt.Error(\"nats conn is nil\")\n\t}\n\tif mqh.NatsSubIdentities == nil {\n\t\tt.Error(\"nats identities subscription is nil\")\n\t}\n\tif mqh.NatsSubTwitter == nil {\n\t\tt.Error(\"nats Twitter subscription is nil\")\n\t}\n\tif mqh.NatsSubImap == nil {\n\t\tt.Error(\"nats imap subscription is nil\")\n\t}\n}\n\nfunc TestMqHandler_natsIdentitiesHandler(t *testing.T) {\n\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/scheduler.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage go_remoteIDs\n\nimport (\n\t\"errors\"\n\tlog \"github.com/Sirupsen/logrus\"\n\t\"gopkg.in/robfig/cron.v2\"\n\t\"strconv\"\n)\n\ntype Scheduler struct {\n\tMainCron *cron.Cron\n}\n\nfunc initScheduler() (*Scheduler, error) {\n\tscheduler := new(Scheduler)\n\tscheduler.MainCron = cron.New()\n\tif scheduler.MainCron == nil {\n\t\treturn nil, errors.New(\"[initScheduler] failed to create MainCron\")\n\t}\n\n\t// add the default dbSync command to cron to periodically re-sync with db\n\tcronStr := \"@every \" + strconv.Itoa(int(poller.Config.ScanInterval)) + \"m\"\n\t_, err := scheduler.MainCron.AddFunc(cronStr, poller.fullSync)\n\tif err != nil {\n\t\tlog.WithError(err)\n\t\treturn nil, errors.New(\"[initScheduler] failed to add default fullsync job\")\n\t}\n\treturn scheduler, nil\n}\n\nfunc (s *Scheduler) Start() {\n\tlog.Info(\"[scheduler] starting MainCron\")\n\ts.MainCron.Start()\n}\n\n// AddSyncJobFor builds job from cacheEntry and schedules it in MainCron\n// returns cacheEntry updated with its cronId\nfunc (s *Scheduler) AddSyncJobFor(entry cacheEntry) (cacheEntry, error) {\n\tvar err error\n\tcronStr := \"@every \" + entry.pollInterval + \"m\"\n\tjob, err := buildSyncJob(entry)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[AddSyncJobFor] failed to build job to MainCron\")\n\t\treturn entry, errors.New(\"[AddSyncJobFor] failed to build job to MainCron\")\n\t}\n\tentry.cronId, err = s.MainCron.AddJob(cronStr, job)\n\tif err != nil {\n\t\tlog.WithError(err).Warn(\"[AddSyncJobFor] failed to add job to MainCron\")\n\t\treturn entry, errors.New(\"[AddSyncJobFor] failed to add job to MainCron\")\n\t}\n\treturn entry, nil\n}\n\n// RemoveJobFor removes remote identity's job from being run in the future\nfunc (s *Scheduler) RemoveJobFor(entry cacheEntry) {\n\ts.MainCron.Remove(entry.cronId)\n}\n\n// UpdateJobFor removes remote identity's job and re-schedule it with new pollinterval\nfunc (s *Scheduler) UpdateSyncJobFor(entry cacheEntry) (cacheEntry, error) {\n\ts.RemoveJobFor(entry)\n\treturn s.AddSyncJobFor(entry)\n}\n\nfunc (s *Scheduler) Stop() {\n\ts.MainCron.Stop()\n}\n"
  },
  {
    "path": "src/backend/workers/go.remoteIDs/scheduler_test.go",
    "content": "// Copyleft (ɔ) 2019 The Caliopen contributors.\n// Use of this source code is governed by a GNU AFFERO GENERAL PUBLIC\n// license (AGPL) that can be found in the LICENSE file.\n\npackage go_remoteIDs\n\nimport (\n\t\"github.com/CaliOpen/Caliopen/src/backend/defs/go-objects\"\n\t\"github.com/satori/go.uuid\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestInitScheduler(t *testing.T) {\n\tpoller.Config = PollerConfig{\n\t\tScanInterval: 10,\n\t}\n\tsch, err := initScheduler()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\t// test if default dbSync command has been added to MainCron\n\tmainCronEntries := sch.MainCron.Entries()\n\tif len(mainCronEntries) != 1 {\n\t\tt.Errorf(\"expected scheduler has a default job in MainCron after init, got %d job(s)\", len(mainCronEntries))\n\t}\n\tif reflect.TypeOf(mainCronEntries[0].Job.Run) != reflect.TypeOf(poller.fullSync) {\n\t\tt.Errorf(\"expected to have fullSync func as first job in MainCron, got %s\", reflect.TypeOf(mainCronEntries[0].Job.Run))\n\t}\n}\n\nfunc TestScheduler_AddSyncJobFor(t *testing.T) {\n\tpoller.Config = PollerConfig{\n\t\tScanInterval: 10,\n\t}\n\tsch, err := initScheduler()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tid := objects.UUID(uuid.NewV4())\n\n\t// test invalid pollInterval string\n\t_, err = sch.AddSyncJobFor(cacheEntry{\n\t\tpollInterval:   \"\",\n\t\tremoteID:       id,\n\t\tremoteProtocol: \"email\",\n\t\tuserID:         id,\n\t})\n\tif err == nil {\n\t\tt.Error(\"expected AddSyncJobFor returned an error for empty pollInterval\")\n\t}\n\t_, err = sch.AddSyncJobFor(cacheEntry{\n\t\tpollInterval:   \"toto\",\n\t\tremoteID:       id,\n\t\tremoteProtocol: \"email\",\n\t\tuserID:         id,\n\t})\n\tif err == nil {\n\t\tt.Error(\"expected AddSyncJobFor returned an error for invalid pollInterval\")\n\t}\n\n\t// test adding a valid entry\n\tentry, err := sch.AddSyncJobFor(cacheEntry{\n\t\tpollInterval:   \"1\",\n\t\tremoteID:       id,\n\t\tremoteProtocol: \"email\",\n\t\tuserID:         id,\n\t})\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\t// test that entry has been updated with cronId\n\tif entry.cronId == 0 {\n\t\tt.Error(\"expected entry.cronId has been set, got 0\")\n\t}\n\n\t// test that MainCron has a new job\n\tif len(sch.MainCron.Entries()) != 2 {\n\t\tt.Errorf(\"expected 2 jobs in MainCron, got %d\", len(sch.MainCron.Entries()))\n\t}\n\t// test that next job from MainCron will be the syncjob just added\n\t// and that it'll run in less than a minute\n\tsch.MainCron.Start()\n\tsyncJob := sch.MainCron.Entries()[0]\n\tif reflect.TypeOf(syncJob.Job.Run) != reflect.TypeOf(func() {}) {\n\t\tt.Errorf(\"expected AddPendingJob to be the job to run, got %s\", reflect.TypeOf(syncJob.Job.Run))\n\t}\n\tdur := syncJob.Next.Sub(time.Now())\n\tif dur < 0 || dur > time.Minute {\n\t\tt.Errorf(\"expected job to be run within minute timeframe, got %d seconds\", dur/time.Second)\n\t}\n}\n\nfunc TestScheduler_RemoveJobFor(t *testing.T) {\n\tpoller.Config = PollerConfig{\n\t\tScanInterval: 10,\n\t}\n\tsch, err := initScheduler()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\t// fill-in scheduler\n\tconst count = 1000 // must be an even number\n\tentries := []cacheEntry{}\n\tfor i := 0; i < count; i++ {\n\t\tid := objects.UUID(uuid.NewV4())\n\t\tentry, err := sch.AddSyncJobFor(cacheEntry{\n\t\t\tpollInterval:   \"1\",\n\t\t\tremoteID:       id,\n\t\t\tremoteProtocol: \"email\",\n\t\t\tuserID:         id,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\n\t// sequentially remove half of jobs because cron pkg fails to handle concurrent remove\n\t// TODO : ^^^dig into this flaw^^^\n\tfor i := 0; i < count/2; i++ {\n\t\tsch.RemoveJobFor(entries[i])\n\t}\n\tif len(sch.MainCron.Entries()) != (count/2)+1 {\n\t\tt.Errorf(\"expected %d jobs left in MainCron, got %d\", (count/2)+1, len(sch.MainCron.Entries()))\n\t}\n}\n\nfunc TestScheduler_UpdateSyncJobFor(t *testing.T) {\n\tpoller.Config = PollerConfig{\n\t\tScanInterval: 10,\n\t}\n\tsch, err := initScheduler()\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\t// fill-in scheduler\n\tconst count = 1000 // must be an even number\n\tentries := []cacheEntry{}\n\tfor i := 0; i < count; i++ {\n\t\tid := objects.UUID(uuid.NewV4())\n\t\tentry, err := sch.AddSyncJobFor(cacheEntry{\n\t\t\tpollInterval:   \"1\",\n\t\t\tremoteID:       id,\n\t\t\tremoteProtocol: \"email\",\n\t\t\tuserID:         id,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\n\t// update pollinterval\n\tfor i := 0; i < count; i++ {\n\t\tentry := entries[i]\n\t\toldCronId := entry.cronId\n\t\tentry.pollInterval = strconv.Itoa(i)\n\t\tentry, err := sch.UpdateSyncJobFor(entry)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tif entry.cronId == oldCronId || entry.cronId == 0 {\n\t\t\tt.Error(\"expected a new cron id after updating entry\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/frontend/maintenance/Dockerfile",
    "content": "# This file creates a container that runs a {package} caliopen frontend\n# Important:\n# Author: Caliopen\n# Date: 2019-05-13\n\nFROM nginx\nMAINTAINER Caliopen\nCOPY src /opt/caliopen\nCOPY config/nginx-config-maintenance.conf /etc/nginx/conf.d/default.conf\n"
  },
  {
    "path": "src/frontend/maintenance/README.md",
    "content": "# Maintenance page\n\nIt provide a simple html page in a docker container.\nThis service is based on [nginx docker image](https://hub.docker.com/_/nginx/).\n\n## USAGE\n\n```\ndocker build -t caliopen-maintenance .\ndocker run --name caliopen-maintenance-container -p 8080:80 -d caliopen-maintenance\n# or eventually w/ a full nginx config\ndocker run --name caliopen-maintenance-container -p 8080:80 -v /host/path/nginx.conf:/etc/nginx/conf.d/default.conf:ro -d caliopen-maintenance\n```\n\nThe maintenance page will be available on http://localhost:8080\n"
  },
  {
    "path": "src/frontend/maintenance/config/nginx-config-maintenance.conf",
    "content": "server {\n  listen 80;\n  listen [::]:80;\n  server_name localhost;\n\n  location /assets {\n    root /opt/caliopen;\n  }\n\n  location / {\n    root /opt/caliopen;\n    rewrite ^(.*)$ /index.html break;\n  }\n}\n"
  },
  {
    "path": "src/frontend/maintenance/package.json",
    "content": "{\n  \"name\": \"maintenance\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"start:dev\": \"http-server ./src/\"\n  },\n  \"devDependencies\": {\n    \"http-server\": \"^0.11.1\"\n  }\n}\n"
  },
  {
    "path": "src/frontend/maintenance/src/assets/css/style.css",
    "content": "@font-face{src:url(\"OpenSans-Regular.ttf\") format(\"truetype\");font-family:\"Open Sans\";font-style:normal;font-weight:400}@font-face{src:url(\"OpenSans-Semibold.ttf\") format(\"truetype\");font-family:\"Open Sans\";font-style:normal;font-weight:600}@font-face{src:url(\"OpenSans-Bold.ttf\") format(\"truetype\");font-family:\"Open Sans\";font-style:normal;font-weight:700}@font-face{src:url(\"OpenSans-ExtraBold.ttf\") format(\"truetype\");font-family:\"Open Sans\";font-style:normal;font-weight:800}.row{display:flex;position:relative;z-index:1000;flex-direction:column;justify-content:space-between;width:100%;max-width:95rem;margin:auto;padding:0 2.5rem}.row--align-center{max-width:55rem;margin-right:auto;margin-left:auto}.row>.col{flex-basis:auto;flex-grow:1;flex-shrink:1;margin-right:0}.row>.col--no-margin,.row>.col:last-of-type{margin-right:0}@media (max-width: 321px){.row{padding:0 1.25rem}}@media (min-width: 44.38rem){.row{flex-direction:row;padding:0 5rem}.row>.col{margin-right:1.25rem}}*{list-style:none;margin:0;padding:0;box-sizing:border-box}a,button{cursor:pointer}figure img{width:100%}html{font-size:16px}body{position:relative;background:#1b1b1b;color:#e8e8e8;font-family:\"Open Sans\",sans-serif;font-size:1rem;line-height:1.4;overflow-wrap:normal}h1{color:#8dceec;font-size:8vw;font-weight:800;line-height:1;text-transform:uppercase}h2{color:#fff;font-size:1.375rem;font-weight:700}a{color:#8dceec}p{margin-top:1.25rem}@media (min-width: 44.38rem){h1{font-size:3.125rem}}.co-header{padding:1.25rem 0;background:#000}.co-header__menu>.col{flex:initial;flex-shrink:1;margin:0}.co-header__menu--right{display:none;text-align:right}.co-header__link{display:inline-block;margin-left:14px;font-size:14px}.co-header__link a{color:#fff;font-weight:700;text-decoration:none;text-transform:uppercase;transition:color .1s linear}.co-header__link a:hover{color:#f1b941}.co-header__link--home{margin-left:0}.co-header__link--home a{display:inline-block;min-width:130px;height:40px;background-image:url(\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KCjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0iTGF5ZXJfMSIKICAgeD0iMHB4IgogICB5PSIwcHgiCiAgIHZpZXdCb3g9IjAgMCAxMzY2IDI3NiIKICAgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTM2NiAyNzY7IgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBzb2RpcG9kaTpkb2NuYW1lPSJjYWxpb3Blbl9sb2dvLnN2ZyIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45Mi4xIHIiPjxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTMxIj48cmRmOlJERj48Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+PGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+PGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPjxkYzp0aXRsZT48L2RjOnRpdGxlPjwvY2M6V29yaz48L3JkZjpSREY+PC9tZXRhZGF0YT48ZGVmcwogICAgIGlkPSJkZWZzMjkiIC8+PHNvZGlwb2RpOm5hbWVkdmlldwogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxIgogICAgIG9iamVjdHRvbGVyYW5jZT0iMTAiCiAgICAgZ3JpZHRvbGVyYW5jZT0iMTAiCiAgICAgZ3VpZGV0b2xlcmFuY2U9IjEwIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSI5NTYiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTA0MSIKICAgICBpZD0ibmFtZWR2aWV3MjciCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjAuMjk3OTUwMjIiCiAgICAgaW5rc2NhcGU6Y3g9Ii00OTIuODUzNjQiCiAgICAgaW5rc2NhcGU6Y3k9IjEzOCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iOTYwIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIzNyIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIwIgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9IkxheWVyXzEiIC8+PHN0eWxlCiAgICAgdHlwZT0idGV4dC9jc3MiCiAgICAgaWQ9InN0eWxlMiI+Cgkuc3Qwe2ZpbGwtcnVsZTpldmVub2RkO2NsaXAtcnVsZTpldmVub2RkO30KPC9zdHlsZT48cGF0aAogICAgIGQ9Ik02NzkuMSwxMTMuNmMtMS0yLjUtMi4yLTUuMS0zLjgtNy42Yy0xMi40LTE1LjUtMjkuMy0yMy4yLTUwLjYtMjMuMmgtNS42Yy03LjMsMC0xNS4zLDEuNy0yMy44LDUuMSAgYy0yMy42LDEyLjUtMzUuNCwzMS4zLTM1LjQsNTYuNmMwLDYuMSwxLjIsMTMuMSwzLjUsMjEuMmMxMSwyNi45LDMwLjEsNDAuNCw1Ny4yLDQwLjRoNGM3LjgsMCwxNS42LTEuMywyMy4zLTQgIGMzLjMtMS4zLDYuMi0yLjcsOS00LjFjMS4xLDQuMSw0LjcsNi45LDEwLjcsOC4yaDJjNi43LDAsMTAuNi00LjUsMTEuNi0xMy42di03MS44QzY4MC45LDExNy43LDY4MC4xLDExNS40LDY3OS4xLDExMy42eiAgIE02MjQuMiwxODEuMmgtNGMtNSwwLTkuNC0wLjgtMTMuMi0yLjVjLTE0LjgtNi43LTIyLjMtMTcuOS0yMi4zLTMzLjhsMS0xMC4xYzUuNy0xOC4yLDE3LjItMjcuMywzNC40LTI3LjNoNCAgYzEzLjYsMCwyNCw0LjQsMzEuNCwxMy4xYzAuMiwyLDAuNiwzLjcsMSw1LjN2MzYuNmMtMC40LDEuNS0wLjgsMy4yLTEsNUM2NDguOSwxNzYuNyw2MzguNCwxODEuMiw2MjQuMiwxODEuMnoiCiAgICAgaWQ9InBhdGg0IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNNzE3LjksOC45aDJjNy40LDEuNSwxMS4xLDUuMiwxMS4xLDExLjF2MTQxLjVjMCwxMC40LDkuOCwxNywyOS40LDE5LjdjOS41LDAsMTUuMSwyLjcsMTYuNyw4LjFsMC41LDN2MyAgYzAsNS40LTMuNyw4LjktMTEuMSwxMC42SDc2MmMtMTkuMSwwLTM1LjMtNy4yLTQ4LjYtMjEuN2MtNC43LTUuOS03LjEtMTQuNS03LjEtMjUuOFYyNS4xYzAtOC4zLDIuNS0xMy40LDcuNi0xNS4yTDcxNy45LDguOXoiCiAgICAgaWQ9InBhdGg2IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNODA1LDguOWgyYzYuNSwwLDEwLjQsNC4yLDExLjYsMTIuNmMtMS42LDguMS01LjMsMTIuMS0xMS4xLDEyLjFIODA1Yy03LjQtMS42LTExLjEtNS4zLTExLjEtMTEuMXYtMi41ICBDNzk1LjQsMTIuNiw3OTkuMiw4LjksODA1LDguOXogTTgwNSw4Mi43aDNjNS40LDAsOC45LDQuMiwxMC42LDEyLjZ2OTdjLTEsOS4xLTQuOSwxMy42LTExLjYsMTMuNmgtMmMtNy40LTEuNi0xMS4xLTUuMy0xMS4xLTExLjEgIHYtMTAxQzc5NS40LDg2LjQsNzk5LjIsODIuNyw4MDUsODIuN3oiCiAgICAgaWQ9InBhdGg4IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNODk0LjgsODIuN2g0LjZjMTkuOCwwLDM2LjQsMTAuNCw0OS42LDMxLjNjNC40LDguOCw2LjYsMTcuMyw2LjYsMjUuOHY5LjFjMCwyMC4xLTEwLjEsMzYuOC0zMC40LDUwICBjLTkuMyw0LjctMTguMiw3LjEtMjYuOCw3LjFoLTMuNWMtMjEuNiwwLTM4LjgtMTEuMy01MS42LTMzLjhjLTMuNy04LjEtNS42LTE3LjItNS42LTI3LjN2LTFjMC0yOSwxNC4zLTQ4LjcsNDMtNTkuMSAgQzg4NS40LDgzLjQsODkwLjIsODIuNyw4OTQuOCw4Mi43eiBNODYyLjQsMTQyLjN2M2MwLDE4LjUsOS4zLDMwLjMsMjcuOCwzNS40bDQuNiwwLjVoMy41YzE0LjMsMCwyNC44LTguMywzMS40LTI0LjhsMS03LjZ2LTkuMSAgYzAtMTQuMy04LjMtMjQuOC0yNC44LTMxLjNsLTYuNi0xaC00LjZjLTEzLjcsMC0yNCw3LjktMzAuOSwyMy43Qzg2Mi45LDEzNS40LDg2Mi40LDEzOS4xLDg2Mi40LDE0Mi4zeiIKICAgICBpZD0icGF0aDEwIgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNMTAzMi43LDgxLjdjMjkuMywwLjksNDguOCwxMy45LDU4LjcsMzguOWMyLjcsNi43LDQsMTMuMSw0LDE5LjJ2Ny4xYzAsMjEuNS0xMC42LDM4LjgtMzEuOSw1MmMtOS42LDQuNC0xOS40LDYuNi0yOS40LDYuNiAgaC0zNC45djUyLjljLTEuNiw3LjctNS4zLDExLjYtMTEuMSwxMS42aC0yLjVjLTUuNywwLTkuNC0zLjktMTEuMS0xMS42VjEyNS42YzAtMjIuMywxNS43LTM2LjYsNDcuMS00Mi45TDEwMzIuNyw4MS43eiAgIE05OTkuMywxMjQuMXY1Ni42aDM1LjRjMTcuNCwwLDI4LjgtOC40LDM0LjQtMjUuM2MxLTIuNywxLjUtNS42LDEuNS04LjZ2LTIuNWwwLjUtMC41bC0wLjUtMC41di0xLjVjMC0xNy4zLTkuMS0yOC43LTI3LjMtMzQuNCAgbC05LjYtMWMtMTUuOSwwLTI3LjIsNC45LTMzLjksMTQuNkw5OTkuMywxMjQuMXoiCiAgICAgaWQ9InBhdGgxMiIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmIiAvPjxwYXRoCiAgICAgZD0iTTExNzAuMSw4Mi43aDUuNmMyNC42LDAsNDIuNyw5LjgsNTQuMiwyOS4zYzEuMyw0LjQsMi4yLDguNiwyLjUsMTIuNmMwLDcuNi01LjksMTIuMS0xNy43LDEzLjZsLTc3LjQsMTYuMiAgYzAsNS42LDQuNCwxMi41LDEzLjIsMjAuN2M3LjEsNCwxNCw2LjEsMjAuOCw2LjFoNGMxMC4xLDAsMTkuOS0zLjIsMjkuNC05LjZsNS4xLTFjNy44LDEuNywxMS42LDUuNiwxMS42LDExLjZ2Mi41ICBjMCw3LjgtMTEuMywxNC42LTMzLjksMjAuMmwtMTIuMSwxaC0zLjVjLTI2LjUsMC00NS40LTEzLTU2LjctMzguOWMtMi43LTcuOC00LTE1LjQtNC0yMi43YzAtMjUuNCwxMS44LTQ0LjMsMzUuNC01Ni42ICBDMTE1NC44LDg0LjQsMTE2Mi43LDgyLjcsMTE3MC4xLDgyLjd6IE0xMTM4LjcsMTI4LjdsNjIuOC0xMy4xVjExNWMtOC42LTUuMS0xNy03LjYtMjUuMy03LjZoLTUuMSAgQzExNTYuMSwxMDcuNSwxMTQ1LjMsMTE0LjUsMTEzOC43LDEyOC43eiIKICAgICBpZD0icGF0aDE0IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNMTMwNC44LDgyLjdjMjIuOCwwLDQwLjMsOC44LDUyLjYsMjYuM2MyLjQsNS43LDMuNSwxMC4xLDMuNSwxMy4xdjcyLjdjLTEuNiw3LjQtNS4zLDExLjEtMTEuMSwxMS4xaC0yICBjLTYuNSwwLTEwLjQtNC40LTExLjYtMTMuMXYtNjkuMmMwLTMuOS0zLjktNy43LTExLjYtMTEuNmMtNi4xLTMtMTIuOC00LjUtMjAuMi00LjVoLTRjLTE0LjIsMC0yNC42LDQuNS0zMS40LDEzLjZsLTEsNHY2OS43ICBjLTEuNyw3LjQtNS40LDExLjEtMTEuMSwxMS4xaC0yYy02LjcsMC0xMC42LTQuNS0xMS42LTEzLjZ2LTY1LjJjMC0yNC4yLDE3LjItMzksNTEuNi00NC41SDEzMDQuOHoiCiAgICAgaWQ9InBhdGgxNiIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmIiAvPjxwYXRoCiAgICAgZD0iTTUwMiw4Mi43aC01LjZjLTcuMywwLTE1LjMsMS43LTIzLjgsNS4xYy0yMy42LDEyLjMtMzUuNCwzMS4yLTM1LjQsNTYuNmMwLDcuMywxLjQsMTQuOSw0LDIyLjcgIGMxMS4zLDI1LjksMzAuMiwzOC45LDU2LjcsMzguOWgzLjVsMTIuMS0xYzIyLjYtNS42LDMzLjktMTIuNCwzMy45LTIwLjJ2LTIuNWMwLTYuMS0zLjktOS45LTExLjYtMTEuNmMtMy4xLDAtNS4xLDEtNS4xLDEgIGMtOS40LDYuNC0xOS4yLDkuNi0yOS40LDkuNmgtNGMtNi43LDAtMTMuNy0yLTIwLjgtNi4xYy04LjgtOC4yLTEzLjItMTUuMS0xMy4yLTIwLjdjLTItMTYuOCwxLjUtMjUuOCwxLjUtMjUuOCAgYzYuNi0xNC4xLDE3LjQtMjEuMiwzMi40LTIxLjJoNS4xYzguMywwLDE2LjcsMi41LDI1LjMsNy42bDEuNywxLjFjMCwwLDEuOSwxLDUuMSwxYzcuOC0xLjcsMTEuNy01LjYsMTEuNy0xMS42VjEwMyAgYzAtNy44LTExLjgtMTQuMS0zNC41LTE5LjFDNTExLjcsODMuOSw1MDkuNCw4My4xLDUwMiw4Mi43eiIKICAgICBpZD0icGF0aDE4IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBjbGFzcz0ic3QwIgogICAgIGQ9Ik0yNzkuNSwxNTcuMmM5LjgtMTAuNiwxNS44LTI0LjgsMTUuOC00MC41YzAtMzMtMjYuNy01OS43LTU5LjctNTkuN2MtMzMsMC01OS43LDI2LjctNTkuNyw1OS43ICBzMjYuNyw1OS43LDU5LjcsNTkuN2M2LjcsMCwxMy4xLTEuMSwxOS4xLTMuMWwyNy42LDkuMkwyNzkuNSwxNTcuMnoiCiAgICAgaWQ9InBhdGgyMCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmIiAvPjxwYXRoCiAgICAgY2xhc3M9InN0MCIKICAgICBkPSJNOTUuNywxMTIuNGMtMjIuMiwwLTQwLjMsMTgtNDAuMyw0MC4zYzAsMTAuMywzLjksMTkuOCwxMC4zLDI2LjlMNjUsMTk4LjRsMTYuNy03LjljNC40LDEuNiw5LjEsMi41LDE0LjEsMi41ICBjMjIuMiwwLDQwLjMtMTgsNDAuMy00MC4zUzExOCwxMTIuNCw5NS43LDExMi40eiIKICAgICBpZD0icGF0aDIyIgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBjbGFzcz0ic3QwIgogICAgIGQ9Ik0yMzUuNiw2QzE5MC4xLDYsMTUxLDMzLjQsMTMzLjksNzIuNkMxMjIuMyw2NywxMDkuNCw2NCw5NS43LDY0QzQ2LjcsNjQsNywxMDMuNyw3LDE1Mi43ICBjMCwyMC4zLDYuOCwzOS4xLDE4LjQsNTRsMi40LDQ3LjdsNDQuOS0xNmM3LjQsMiwxNS4xLDMsMjMuMSwzYzI5LjcsMCw1Ni0xNC42LDcyLjEtMzdjMTguNywxNC41LDQyLjIsMjMuMSw2Ny43LDIzLjEgIGMxMC43LDAsMjEuMS0xLjUsMzAuOS00LjRsNTUuNywxOS4xbDMuMy02MC43YzEzLjItMTguMiwyMS00MC42LDIxLTY0LjlDMzQ2LjQsNTUuNiwyOTYuOCw2LDIzNS42LDZ6IE0zMDMsMTY3LjlsLTAuOCw0Mi4zICBsLTM5LjYtMTMuMmMtOC42LDIuOS0xNy45LDQuNS0yNy41LDQuNWMtMzIuNCwwLTYwLjYtMTguMS03NS00NC43Yy0yLjIsMzMuNy0zMC4xLDYwLjMtNjQuMyw2MC4zYy03LjEsMC0xMy45LTEuMi0yMC4zLTMuMyAgbC0yNy43LDEwLjJsLTEuNS0zMC4yYy05LjMtMTEuMi0xNC45LTI1LjUtMTQuOS00MS4yYzAtMzUuNiwyOC45LTY0LjUsNjQuNS02NC41YzIyLjcsMCw0Mi42LDExLjcsNTQuMSwyOS40YzAtMC40LDAtMC45LDAtMS4zICBjMC00Ny4xLDM4LjItODUuMyw4NS4zLTg1LjNzODUuMywzOC4yLDg1LjMsODUuM0MzMjAuNCwxMzUuNywzMTMuOSwxNTMuNiwzMDMsMTY3Ljl6IgogICAgIGlkPSJwYXRoMjQiCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIgLz48L3N2Zz4K\");background-repeat:no-repeat;background-position:50% 50%;background-size:contain}.co-header__link--home a span{visibility:hidden}@media (min-width: 44.38rem){.co-header__menu{align-items:center}.co-header__menu--right{display:block}.co-header__link--home{margin-left:0}}@media (min-width: 95rem){.co-header__link{font-size:17px}}.co-front{width:100%;padding-top:84px;padding-bottom:250px;background-attachment:fixed;background-image:linear-gradient(to top, #16ccda, #1395ba);color:#fff;font-size:1.4rem}.co-front--align-center{padding-bottom:84px}.co-front__content{width:100%;max-width:44.38rem}.co-front h1{color:#fff;font-size:14vw}.co-front h2{margin-top:1.25rem;font-size:1.75rem}.co-front h2 br{display:none}.co-front__list{margin-top:2.5rem}.co-front__list-item{padding:1.25rem;padding-left:3.75rem;background-repeat:no-repeat;background-position:0 50%;background-size:2.5rem auto;font-weight:600}.co-front__list-item--control{background-image:url(\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDU2IDU2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1NiA1NjsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSI1MTJweCIgaGVpZ2h0PSI1MTJweCI+CjxnPgoJPHBhdGggZD0iTTQxLjA4LDQ4SDJjLTAuNTUzLDAtMSwwLjQ0OC0xLDFzMC40NDcsMSwxLDFoMzkuMDhjMC40ODgsMy4zODcsMy40MDEsNiw2LjkyLDZjMy44NTksMCw3LTMuMTQsNy03cy0zLjE0MS03LTctNyAgIEM0NC40ODEsNDIsNDEuNTY4LDQ0LjYxMyw0MS4wOCw0OHoiIGZpbGw9IiNGRkZGRkYiLz4KCTxwYXRoIGQ9Ik0yMC42OTUsMjdIMmMtMC41NTMsMC0xLDAuNDQ4LTEsMXMwLjQ0NywxLDEsMWgxOC42MzJjMC4zOTYsMy42MDIsMy40NTYsNi40MTQsNy4xNjEsNi40MTRzNi43NjUtMi44MTIsNy4xNjEtNi40MTRINTQgICBjMC41NTMsMCwxLTAuNDQ4LDEtMXMtMC40NDctMS0xLTFIMzQuODkxYy0wLjU3Ny0zLjQtMy41MzYtNi03LjA5OC02UzIxLjI3MiwyMy42LDIwLjY5NSwyN3oiIGZpbGw9IiNGRkZGRkYiLz4KCTxwYXRoIGQ9Ik04LDBDNC4xNDEsMCwxLDMuMTQsMSw3czMuMTQxLDcsNyw3YzMuNTE5LDAsNi40MzItMi42MTMsNi45Mi02SDU0YzAuNTUzLDAsMS0wLjQ0OCwxLTFzLTAuNDQ3LTEtMS0xSDE0LjkyICAgQzE0LjQzMiwyLjYxMywxMS41MTksMCw4LDB6IiBmaWxsPSIjRkZGRkZGIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==\")}.co-front__list-item--chat{background-image:url(\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNTExLjYyNiA1MTEuNjI2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTEuNjI2IDUxMS42MjY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMzAxLjkyNywzMjcuNjA1YzMwLjkyNi0xMy4wMzgsNTUuMzQtMzAuNzg1LDczLjIzLTUzLjI0OGMxNy44ODgtMjIuNDU4LDI2LjgzMy00Ni45MTUsMjYuODMzLTczLjM3MiAgICBjMC0yNi40NTgtOC45NDUtNTAuOTE3LTI2Ljg0LTczLjM3NmMtMTcuODg4LTIyLjQ1OS00Mi4yOTgtNDAuMjA4LTczLjIyOC01My4yNDljLTMwLjkzLTEzLjAzOS02NC41NzEtMTkuNTU2LTEwMC45MjgtMTkuNTU2ICAgIGMtMzYuMzU0LDAtNjkuOTk1LDYuNTIxLTEwMC45MjcsMTkuNTU2Yy0zMC45MjksMTMuMDQtNTUuMzQsMzAuNzg5LTczLjIyOSw1My4yNDlDOC45NDcsMTUwLjA3MiwwLDE3NC41MjcsMCwyMDAuOTg2ICAgIGMwLDIyLjY0OCw2Ljc2Nyw0My45NzUsMjAuMjgsNjMuOTZjMTMuNTEyLDE5Ljk4MSwzMi4wNzEsMzYuODI5LDU1LjY3MSw1MC41MzFjLTEuOTAyLDQuNTcyLTMuODUzLDguNzU0LTUuODUyLDEyLjU2NiAgICBjLTIsMy44MDYtNC4zNzcsNy40NjctNy4xMzksMTAuOTkxYy0yLjc2LDMuNTI1LTQuODk5LDYuMjgzLTYuNDIzLDguMjc1Yy0xLjUyMywxLjk5OC0zLjk5Nyw0LjgxMi03LjQyNSw4LjQyMiAgICBjLTMuNDI3LDMuNjE3LTUuNjE3LDUuOTk2LTYuNTY3LDcuMTM1YzAtMC4xOTEtMC4zODEsMC4yNC0xLjE0MywxLjI4N2MtMC43NjMsMS4wNDctMS4xOTEsMS41Mi0xLjI4NSwxLjQzMSAgICBjLTAuMDk2LTAuMTAzLTAuNDc3LDAuMzczLTEuMTQzLDEuNDJjLTAuNjY2LDEuMDQ4LTEsMS41NzEtMSwxLjU3MWwtMC43MTUsMS40MjNjLTAuMjgyLDAuNTc1LTAuNDc2LDEuMTM3LTAuNTcsMS43MTIgICAgYy0wLjA5NiwwLjU2Ny0wLjE0NCwxLjE5LTAuMTQ0LDEuODU0czAuMDk0LDEuMjgsMC4yODgsMS44NTRjMC4zODEsMi40NzEsMS40NzUsNC40NjYsMy4yODMsNS45OTYgICAgYzEuODA3LDEuNTIsMy43NTYsMi4yNzksNS44NTIsMi4yNzloMC44NTdjOS41MTUtMS4zMzIsMTcuNzAxLTIuODU0LDI0LjU1Mi00LjU2OWMyOS4zMTItNy42MSw1NS43NzEtMTkuNzk3LDc5LjM3Mi0zNi41NDUgICAgYzE3LjEyOSwzLjA0NiwzMy44NzksNC41NjgsNTAuMjQ3LDQuNTY4QzIzNy4zNTMsMzQ3LjE2LDI3MC45OTgsMzQwLjY0NSwzMDEuOTI3LDMyNy42MDV6IiBmaWxsPSIjRkZGRkZGIi8+CgkJPHBhdGggZD0iTTQ5MS4zNTQsMzM4LjE2NmMxMy41MTgtMTkuODg5LDIwLjI3Mi00MS4yNDcsMjAuMjcyLTY0LjA5YzAtMjMuNDE0LTcuMTQ2LTQ1LjMxNi0yMS40MTYtNjUuNjggICAgYy0xNC4yNzctMjAuMzYyLTMzLjY5NC0zNy4zMDUtNTguMjQ1LTUwLjgxOWM0LjM3NCwxNC4yNzQsNi41NjMsMjguNzM5LDYuNTYzLDQzLjM5OGMwLDI1LjUwMy02LjM2OCw0OS42NzYtMTkuMTI5LDcyLjUxOSAgICBjLTEyLjc1MiwyMi44MzYtMzEuMDI1LDQzLjAxLTU0LjgxNiw2MC41MjRjLTIyLjA4LDE1Ljk4OC00Ny4yMDUsMjguMjYxLTc1LjM3NywzNi44MjkgICAgYy0yOC4xNjQsOC41NjItNTcuNTczLDEyLjg0OC04OC4yMTgsMTIuODQ4Yy01LjcwOCwwLTE0LjA4NC0wLjM3Ny0yNS4xMjItMS4xMzdjMzguMjU2LDI1LjExOSw4My4xNzcsMzcuNjg1LDEzNC43NTYsMzcuNjg1ICAgIGMxNi4zNzEsMCwzMy4xMTktMS41MjYsNTAuMjUxLTQuNTcxYzIzLjYsMTYuNzU1LDUwLjA2LDI4LjkzMSw3OS4zNywzNi41NDljNi44NTIsMS43MTgsMTUuMDM3LDMuMjM3LDI0LjU1NCw0LjU2OCAgICBjMi4yODMsMC4xOTEsNC4zODEtMC40NzYsNi4yODMtMS45OTljMS45MDMtMS41MjIsMy4xNDItMy42MSwzLjcxLTYuMjcyYy0wLjA4OS0xLjE0MywwLTEuNzcsMC4yODctMS44NjEgICAgYzAuMjgxLTAuMDksMC4yMzMtMC43MTItMC4xNDQtMS44NTJjLTAuMzc2LTEuMTQ0LTAuNTY4LTEuNzE1LTAuNTY4LTEuNzE1bC0wLjcxMi0xLjQyNGMtMC4xOTgtMC4zNzYtMC41Mi0wLjkwMy0wLjk5OS0xLjU2NyAgICBjLTAuNDc2LTAuNjYtMC44NTUtMS4xNDMtMS4xNDMtMS40MjdjLTAuMjgtMC4yODQtMC43MDUtMC43NjMtMS4yOC0xLjQyNGMtMC41NjgtMC42Ni0wLjk1MS0xLjA5Mi0xLjE0My0xLjI4MyAgICBjLTAuOTUxLTEuMTQzLTMuMTM5LTMuNTIxLTYuNTY0LTcuMTM5Yy0zLjQyOS0zLjYxMy01Ljg5OS02LjQyLTcuNDIyLTguNDE4Yy0xLjUyMy0xLjk5OS0zLjY2NS00Ljc1Ny02LjQyNC04LjI4MiAgICBjLTIuNzU4LTMuNTE4LTUuMTQtNy4xODMtNy4xMzktMTAuOTkxYy0xLjk5OC0zLjgwNi0zLjk0OS03Ljk5NS01Ljg1Mi0xMi41NkM0NTkuMjg5LDM3NC44NTksNDc3Ljg0MywzNTguMDYyLDQ5MS4zNTQsMzM4LjE2NnoiIGZpbGw9IiNGRkZGRkYiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K\")}.co-front__list-item--happy{background-image:url(\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNTEwIDUxMCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTEwIDUxMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8Zz4KCTxnIGlkPSJtb29kIj4KCQk8cGF0aCBkPSJNMjU1LDBDMTE0Ljc1LDAsMCwxMTQuNzUsMCwyNTVzMTE0Ljc1LDI1NSwyNTUsMjU1czI1NS0xMTQuNzUsMjU1LTI1NVMzOTUuMjUsMCwyNTUsMHogTTM0NC4yNSwxNTMgICAgYzIwLjQsMCwzOC4yNSwxNy44NSwzOC4yNSwzOC4yNXMtMTcuODUsMzguMjUtMzguMjUsMzguMjVTMzA2LDIxMS42NSwzMDYsMTkxLjI1UzMyMy44NSwxNTMsMzQ0LjI1LDE1M3ogTTE2NS43NSwxNTMgICAgYzIwLjQsMCwzOC4yNSwxNy44NSwzOC4yNSwzOC4yNXMtMTcuODUsMzguMjUtMzguMjUsMzguMjVzLTM4LjI1LTE3Ljg1LTM4LjI1LTM4LjI1UzE0NS4zNSwxNTMsMTY1Ljc1LDE1M3ogTTI1NSw0MDggICAgYy02Ni4zLDAtMTIyLjQtNDMuMzUtMTQyLjgtMTAyaDI4NS42QzM3Ny40LDM2NC42NSwzMjEuMyw0MDgsMjU1LDQwOHoiIGZpbGw9IiNGRkZGRkYiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K\")}@media (min-width: 520px){.co-front h1{font-size:4.25rem}.co-front h2 br{display:inline}}@media (min-width: 44.38rem){.co-front{background-image:radial-gradient(320px 40%, #16ccda, #1395ba)}}.co-devices{display:none;position:fixed;top:4.375rem;width:100%;height:600px;z-index:900}.co-devices__list{position:relative;height:100%}.co-devices__item{position:absolute;left:53.38rem;width:380px}.co-devices__item--1{z-index:1000}.co-devices__item--2{z-index:999;margin-top:80px;margin-left:170px}@media (min-width: 44.38rem){.co-devices{display:block}}.co-body{margin-bottom:4.375rem}.co-body h2{margin-top:1.25rem}.co-body section{width:100%;max-width:44.38rem;margin-top:4.375rem}.co-body__roadmap{margin-top:1.25rem}.co-body__thumbs{display:flex;flex-direction:row;margin-top:2.5rem}.co-body__thumbs figure{flex-basis:auto;flex-grow:1;flex-shrink:1;padding-right:1.25rem}.co-body__list{margin-top:1.25rem;padding-left:1rem;color:#fff}.co-body__list li{list-style:outside url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAFklEQVQYV2P8uNPxPwMBwDiqiKggAAC8lBpEeFrDRgAAAABJRU5ErkJggg==\");margin-bottom:.3rem}.co-body__list li span{display:block;padding-left:1rem}@media (min-width: 44.38rem){.co-body>.row{flex-direction:column}}.co-form{position:relative;top:-180px;margin-bottom:-180px}.co-form__form{max-width:44.38rem;padding:1.25rem;border-radius:3px;background:white;color:#515151;font-size:1rem;box-sizing:border-box}.co-form__legend{display:inline-block;color:#115972}.co-form__tip{display:inline-block;padding-top:12px;font-size:.8125rem}.co-footer{position:relative;z-index:890;margin-top:4.375rem;padding:1.25rem 0;background:#000;font-size:.8125rem;text-align:center}.co-footer a{display:inline-block;min-width:130px;height:40px;background-image:url(\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KCjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0iTGF5ZXJfMSIKICAgeD0iMHB4IgogICB5PSIwcHgiCiAgIHZpZXdCb3g9IjAgMCAxMzY2IDI3NiIKICAgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTM2NiAyNzY7IgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBzb2RpcG9kaTpkb2NuYW1lPSJjYWxpb3Blbl9sb2dvLnN2ZyIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45Mi4xIHIiPjxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTMxIj48cmRmOlJERj48Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+PGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+PGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPjxkYzp0aXRsZT48L2RjOnRpdGxlPjwvY2M6V29yaz48L3JkZjpSREY+PC9tZXRhZGF0YT48ZGVmcwogICAgIGlkPSJkZWZzMjkiIC8+PHNvZGlwb2RpOm5hbWVkdmlldwogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxIgogICAgIG9iamVjdHRvbGVyYW5jZT0iMTAiCiAgICAgZ3JpZHRvbGVyYW5jZT0iMTAiCiAgICAgZ3VpZGV0b2xlcmFuY2U9IjEwIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSI5NTYiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTA0MSIKICAgICBpZD0ibmFtZWR2aWV3MjciCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjAuMjk3OTUwMjIiCiAgICAgaW5rc2NhcGU6Y3g9Ii00OTIuODUzNjQiCiAgICAgaW5rc2NhcGU6Y3k9IjEzOCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iOTYwIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIzNyIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIwIgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9IkxheWVyXzEiIC8+PHN0eWxlCiAgICAgdHlwZT0idGV4dC9jc3MiCiAgICAgaWQ9InN0eWxlMiI+Cgkuc3Qwe2ZpbGwtcnVsZTpldmVub2RkO2NsaXAtcnVsZTpldmVub2RkO30KPC9zdHlsZT48cGF0aAogICAgIGQ9Ik02NzkuMSwxMTMuNmMtMS0yLjUtMi4yLTUuMS0zLjgtNy42Yy0xMi40LTE1LjUtMjkuMy0yMy4yLTUwLjYtMjMuMmgtNS42Yy03LjMsMC0xNS4zLDEuNy0yMy44LDUuMSAgYy0yMy42LDEyLjUtMzUuNCwzMS4zLTM1LjQsNTYuNmMwLDYuMSwxLjIsMTMuMSwzLjUsMjEuMmMxMSwyNi45LDMwLjEsNDAuNCw1Ny4yLDQwLjRoNGM3LjgsMCwxNS42LTEuMywyMy4zLTQgIGMzLjMtMS4zLDYuMi0yLjcsOS00LjFjMS4xLDQuMSw0LjcsNi45LDEwLjcsOC4yaDJjNi43LDAsMTAuNi00LjUsMTEuNi0xMy42di03MS44QzY4MC45LDExNy43LDY4MC4xLDExNS40LDY3OS4xLDExMy42eiAgIE02MjQuMiwxODEuMmgtNGMtNSwwLTkuNC0wLjgtMTMuMi0yLjVjLTE0LjgtNi43LTIyLjMtMTcuOS0yMi4zLTMzLjhsMS0xMC4xYzUuNy0xOC4yLDE3LjItMjcuMywzNC40LTI3LjNoNCAgYzEzLjYsMCwyNCw0LjQsMzEuNCwxMy4xYzAuMiwyLDAuNiwzLjcsMSw1LjN2MzYuNmMtMC40LDEuNS0wLjgsMy4yLTEsNUM2NDguOSwxNzYuNyw2MzguNCwxODEuMiw2MjQuMiwxODEuMnoiCiAgICAgaWQ9InBhdGg0IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNNzE3LjksOC45aDJjNy40LDEuNSwxMS4xLDUuMiwxMS4xLDExLjF2MTQxLjVjMCwxMC40LDkuOCwxNywyOS40LDE5LjdjOS41LDAsMTUuMSwyLjcsMTYuNyw4LjFsMC41LDN2MyAgYzAsNS40LTMuNyw4LjktMTEuMSwxMC42SDc2MmMtMTkuMSwwLTM1LjMtNy4yLTQ4LjYtMjEuN2MtNC43LTUuOS03LjEtMTQuNS03LjEtMjUuOFYyNS4xYzAtOC4zLDIuNS0xMy40LDcuNi0xNS4yTDcxNy45LDguOXoiCiAgICAgaWQ9InBhdGg2IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNODA1LDguOWgyYzYuNSwwLDEwLjQsNC4yLDExLjYsMTIuNmMtMS42LDguMS01LjMsMTIuMS0xMS4xLDEyLjFIODA1Yy03LjQtMS42LTExLjEtNS4zLTExLjEtMTEuMXYtMi41ICBDNzk1LjQsMTIuNiw3OTkuMiw4LjksODA1LDguOXogTTgwNSw4Mi43aDNjNS40LDAsOC45LDQuMiwxMC42LDEyLjZ2OTdjLTEsOS4xLTQuOSwxMy42LTExLjYsMTMuNmgtMmMtNy40LTEuNi0xMS4xLTUuMy0xMS4xLTExLjEgIHYtMTAxQzc5NS40LDg2LjQsNzk5LjIsODIuNyw4MDUsODIuN3oiCiAgICAgaWQ9InBhdGg4IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNODk0LjgsODIuN2g0LjZjMTkuOCwwLDM2LjQsMTAuNCw0OS42LDMxLjNjNC40LDguOCw2LjYsMTcuMyw2LjYsMjUuOHY5LjFjMCwyMC4xLTEwLjEsMzYuOC0zMC40LDUwICBjLTkuMyw0LjctMTguMiw3LjEtMjYuOCw3LjFoLTMuNWMtMjEuNiwwLTM4LjgtMTEuMy01MS42LTMzLjhjLTMuNy04LjEtNS42LTE3LjItNS42LTI3LjN2LTFjMC0yOSwxNC4zLTQ4LjcsNDMtNTkuMSAgQzg4NS40LDgzLjQsODkwLjIsODIuNyw4OTQuOCw4Mi43eiBNODYyLjQsMTQyLjN2M2MwLDE4LjUsOS4zLDMwLjMsMjcuOCwzNS40bDQuNiwwLjVoMy41YzE0LjMsMCwyNC44LTguMywzMS40LTI0LjhsMS03LjZ2LTkuMSAgYzAtMTQuMy04LjMtMjQuOC0yNC44LTMxLjNsLTYuNi0xaC00LjZjLTEzLjcsMC0yNCw3LjktMzAuOSwyMy43Qzg2Mi45LDEzNS40LDg2Mi40LDEzOS4xLDg2Mi40LDE0Mi4zeiIKICAgICBpZD0icGF0aDEwIgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNMTAzMi43LDgxLjdjMjkuMywwLjksNDguOCwxMy45LDU4LjcsMzguOWMyLjcsNi43LDQsMTMuMSw0LDE5LjJ2Ny4xYzAsMjEuNS0xMC42LDM4LjgtMzEuOSw1MmMtOS42LDQuNC0xOS40LDYuNi0yOS40LDYuNiAgaC0zNC45djUyLjljLTEuNiw3LjctNS4zLDExLjYtMTEuMSwxMS42aC0yLjVjLTUuNywwLTkuNC0zLjktMTEuMS0xMS42VjEyNS42YzAtMjIuMywxNS43LTM2LjYsNDcuMS00Mi45TDEwMzIuNyw4MS43eiAgIE05OTkuMywxMjQuMXY1Ni42aDM1LjRjMTcuNCwwLDI4LjgtOC40LDM0LjQtMjUuM2MxLTIuNywxLjUtNS42LDEuNS04LjZ2LTIuNWwwLjUtMC41bC0wLjUtMC41di0xLjVjMC0xNy4zLTkuMS0yOC43LTI3LjMtMzQuNCAgbC05LjYtMWMtMTUuOSwwLTI3LjIsNC45LTMzLjksMTQuNkw5OTkuMywxMjQuMXoiCiAgICAgaWQ9InBhdGgxMiIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmIiAvPjxwYXRoCiAgICAgZD0iTTExNzAuMSw4Mi43aDUuNmMyNC42LDAsNDIuNyw5LjgsNTQuMiwyOS4zYzEuMyw0LjQsMi4yLDguNiwyLjUsMTIuNmMwLDcuNi01LjksMTIuMS0xNy43LDEzLjZsLTc3LjQsMTYuMiAgYzAsNS42LDQuNCwxMi41LDEzLjIsMjAuN2M3LjEsNCwxNCw2LjEsMjAuOCw2LjFoNGMxMC4xLDAsMTkuOS0zLjIsMjkuNC05LjZsNS4xLTFjNy44LDEuNywxMS42LDUuNiwxMS42LDExLjZ2Mi41ICBjMCw3LjgtMTEuMywxNC42LTMzLjksMjAuMmwtMTIuMSwxaC0zLjVjLTI2LjUsMC00NS40LTEzLTU2LjctMzguOWMtMi43LTcuOC00LTE1LjQtNC0yMi43YzAtMjUuNCwxMS44LTQ0LjMsMzUuNC01Ni42ICBDMTE1NC44LDg0LjQsMTE2Mi43LDgyLjcsMTE3MC4xLDgyLjd6IE0xMTM4LjcsMTI4LjdsNjIuOC0xMy4xVjExNWMtOC42LTUuMS0xNy03LjYtMjUuMy03LjZoLTUuMSAgQzExNTYuMSwxMDcuNSwxMTQ1LjMsMTE0LjUsMTEzOC43LDEyOC43eiIKICAgICBpZD0icGF0aDE0IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNMTMwNC44LDgyLjdjMjIuOCwwLDQwLjMsOC44LDUyLjYsMjYuM2MyLjQsNS43LDMuNSwxMC4xLDMuNSwxMy4xdjcyLjdjLTEuNiw3LjQtNS4zLDExLjEtMTEuMSwxMS4xaC0yICBjLTYuNSwwLTEwLjQtNC40LTExLjYtMTMuMXYtNjkuMmMwLTMuOS0zLjktNy43LTExLjYtMTEuNmMtNi4xLTMtMTIuOC00LjUtMjAuMi00LjVoLTRjLTE0LjIsMC0yNC42LDQuNS0zMS40LDEzLjZsLTEsNHY2OS43ICBjLTEuNyw3LjQtNS40LDExLjEtMTEuMSwxMS4xaC0yYy02LjcsMC0xMC42LTQuNS0xMS42LTEzLjZ2LTY1LjJjMC0yNC4yLDE3LjItMzksNTEuNi00NC41SDEzMDQuOHoiCiAgICAgaWQ9InBhdGgxNiIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmIiAvPjxwYXRoCiAgICAgZD0iTTUwMiw4Mi43aC01LjZjLTcuMywwLTE1LjMsMS43LTIzLjgsNS4xYy0yMy42LDEyLjMtMzUuNCwzMS4yLTM1LjQsNTYuNmMwLDcuMywxLjQsMTQuOSw0LDIyLjcgIGMxMS4zLDI1LjksMzAuMiwzOC45LDU2LjcsMzguOWgzLjVsMTIuMS0xYzIyLjYtNS42LDMzLjktMTIuNCwzMy45LTIwLjJ2LTIuNWMwLTYuMS0zLjktOS45LTExLjYtMTEuNmMtMy4xLDAtNS4xLDEtNS4xLDEgIGMtOS40LDYuNC0xOS4yLDkuNi0yOS40LDkuNmgtNGMtNi43LDAtMTMuNy0yLTIwLjgtNi4xYy04LjgtOC4yLTEzLjItMTUuMS0xMy4yLTIwLjdjLTItMTYuOCwxLjUtMjUuOCwxLjUtMjUuOCAgYzYuNi0xNC4xLDE3LjQtMjEuMiwzMi40LTIxLjJoNS4xYzguMywwLDE2LjcsMi41LDI1LjMsNy42bDEuNywxLjFjMCwwLDEuOSwxLDUuMSwxYzcuOC0xLjcsMTEuNy01LjYsMTEuNy0xMS42VjEwMyAgYzAtNy44LTExLjgtMTQuMS0zNC41LTE5LjFDNTExLjcsODMuOSw1MDkuNCw4My4xLDUwMiw4Mi43eiIKICAgICBpZD0icGF0aDE4IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBjbGFzcz0ic3QwIgogICAgIGQ9Ik0yNzkuNSwxNTcuMmM5LjgtMTAuNiwxNS44LTI0LjgsMTUuOC00MC41YzAtMzMtMjYuNy01OS43LTU5LjctNTkuN2MtMzMsMC01OS43LDI2LjctNTkuNyw1OS43ICBzMjYuNyw1OS43LDU5LjcsNTkuN2M2LjcsMCwxMy4xLTEuMSwxOS4xLTMuMWwyNy42LDkuMkwyNzkuNSwxNTcuMnoiCiAgICAgaWQ9InBhdGgyMCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmIiAvPjxwYXRoCiAgICAgY2xhc3M9InN0MCIKICAgICBkPSJNOTUuNywxMTIuNGMtMjIuMiwwLTQwLjMsMTgtNDAuMyw0MC4zYzAsMTAuMywzLjksMTkuOCwxMC4zLDI2LjlMNjUsMTk4LjRsMTYuNy03LjljNC40LDEuNiw5LjEsMi41LDE0LjEsMi41ICBjMjIuMiwwLDQwLjMtMTgsNDAuMy00MC4zUzExOCwxMTIuNCw5NS43LDExMi40eiIKICAgICBpZD0icGF0aDIyIgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBjbGFzcz0ic3QwIgogICAgIGQ9Ik0yMzUuNiw2QzE5MC4xLDYsMTUxLDMzLjQsMTMzLjksNzIuNkMxMjIuMyw2NywxMDkuNCw2NCw5NS43LDY0QzQ2LjcsNjQsNywxMDMuNyw3LDE1Mi43ICBjMCwyMC4zLDYuOCwzOS4xLDE4LjQsNTRsMi40LDQ3LjdsNDQuOS0xNmM3LjQsMiwxNS4xLDMsMjMuMSwzYzI5LjcsMCw1Ni0xNC42LDcyLjEtMzdjMTguNywxNC41LDQyLjIsMjMuMSw2Ny43LDIzLjEgIGMxMC43LDAsMjEuMS0xLjUsMzAuOS00LjRsNTUuNywxOS4xbDMuMy02MC43YzEzLjItMTguMiwyMS00MC42LDIxLTY0LjlDMzQ2LjQsNTUuNiwyOTYuOCw2LDIzNS42LDZ6IE0zMDMsMTY3LjlsLTAuOCw0Mi4zICBsLTM5LjYtMTMuMmMtOC42LDIuOS0xNy45LDQuNS0yNy41LDQuNWMtMzIuNCwwLTYwLjYtMTguMS03NS00NC43Yy0yLjIsMzMuNy0zMC4xLDYwLjMtNjQuMyw2MC4zYy03LjEsMC0xMy45LTEuMi0yMC4zLTMuMyAgbC0yNy43LDEwLjJsLTEuNS0zMC4yYy05LjMtMTEuMi0xNC45LTI1LjUtMTQuOS00MS4yYzAtMzUuNiwyOC45LTY0LjUsNjQuNS02NC41YzIyLjcsMCw0Mi42LDExLjcsNTQuMSwyOS40YzAtMC40LDAtMC45LDAtMS4zICBjMC00Ny4xLDM4LjItODUuMyw4NS4zLTg1LjNzODUuMywzOC4yLDg1LjMsODUuM0MzMjAuNCwxMzUuNywzMTMuOSwxNTMuNiwzMDMsMTY3Ljl6IgogICAgIGlkPSJwYXRoMjQiCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIgLz48L3N2Zz4K\");background-repeat:no-repeat;background-position:50% 50%;background-size:contain;color:#fff;font-weight:600;text-decoration:none}.co-footer a span{visibility:hidden}.mauticform_wrapper{background:white;color:#515151;font-size:1rem}.mauticform-page-wrapper{display:flex;flex-direction:row;flex-wrap:wrap}.mauticform-name{margin-bottom:1.25rem;color:#0f5a71;font-size:1.2rem;font-weight:700}.mauticform-row{flex-basis:initial;flex-grow:0;flex-shrink:0;width:100%}.mauticform-row.mauticform-button-wrapper{width:100%}.mauticform-label{display:block;padding:.625rem 0}.mauticform-row .mauticform-required .mauticform-label::after{display:inline;content:' *'}.mauticform-input{width:100%;padding:0 .625rem;border:1px solid #515151;font-family:inherit;font-size:1.1rem;line-height:2;box-sizing:border-box}.mauticform-input::-webkit-input-placeholder{color:#919191;font-size:.9rem}.mauticform-input::-moz-placeholder{color:#919191;font-size:.9rem}.mauticform-input:-ms-input-placeholder{color:#919191;font-size:.9rem}.mauticform-input:-moz-placeholder{color:#919191;font-size:.9rem}.mauticform-button-wrapper .mauticform-button{display:block;width:100%;margin-top:1.25rem;padding:18px;border:0;border-radius:3px;outline:none;background:#f1b941;box-shadow:4px 4px 0 #bb881a;font-size:1.375rem;font-weight:800;text-transform:uppercase}.mauticform-button-wrapper .mauticform-button:focus{box-shadow:0 0 0 #bb881a}.mauticform-button-wrapper .mauticform-button .btn-default[disabled]{color:rgba(255,255,255,0.7)}.mauticform-button-wrapper .mauticform-button :hover{border-bottom:5px solid #f1b941}.mauticform-description{margin-top:2px;margin-bottom:10px}.mauticform-error{margin-bottom:10px;color:red}.mauticform-message{margin-bottom:10px;color:green}.mauticform-helpmessage{display:block;font-size:0.9em;margin-bottom:3px}.mauticform-errormsg{display:block;color:red;margin-top:2px}@media (min-width: 520px){.mauticform-row{flex-basis:initial;flex-grow:0;flex-shrink:0;width:50%}.mauticform-row:first-of-type{padding-right:1.25rem}}\n"
  },
  {
    "path": "src/frontend/maintenance/src/assets/js/init.js",
    "content": "$(document).ready(function(){\n\n  parallaxDevices()\n  smoothScroll('header a');\n\n  function parallaxDevices() {\n    var $window = $(window);\n    var devices = 'figure[data-type=\"device\"]';\n\n    $(devices).each(function(){\n        var device = $(this);\n        device.css({ top: -$window.scrollTop() / device.data('speed') + '%' });\n        $(window).scroll(function() {\n          var yPos = -$window.scrollTop() / device.data('speed');\n          var coords = yPos + '%';\n          device.css({ top: coords });\n        });\n    });\n  };\n\n  function smoothScroll(link) {\n    $(link).on('click', function() {\n      var anchor = $(this).attr('href');\n      var speed = 750;\n      $('html, body').animate( { scrollTop: $(anchor).offset().top - 100 }, speed );\n      return false;\n    });\n  };\n});\n"
  },
  {
    "path": "src/frontend/maintenance/src/assets/scss/params/_fonts.scss",
    "content": "\n@mixin fontdef($FontPrefix, $FontVersion, $FontType) {\n  src:  url('#{$FontPrefix}-#{$FontType}.ttf') format('truetype');\n}\n\n// Open Sans\n$OpenSansTitle: 'Open Sans';\n$OpenSansPrefix: 'OpenSans';\n$OpenSansVersion: '1.1.0' !default;\n\n// Open Sans Regular\n@font-face {\n  @include fontdef($OpenSansPrefix, $OpenSansVersion, 'Regular');\n  font-family: $OpenSansTitle;\n  font-style: normal;\n  font-weight: 400;\n}\n\n// Open Sans Semibold\n@font-face {\n  @include fontdef($OpenSansPrefix, $OpenSansVersion, 'Semibold');\n  font-family: $OpenSansTitle;\n  font-style: normal;\n  font-weight: 600;\n}\n\n// Open Sans Bold\n@font-face {\n  @include fontdef($OpenSansPrefix, $OpenSansVersion, 'Bold');\n  font-family: $OpenSansTitle;\n  font-style: normal;\n  font-weight: 700;\n}\n\n// Open Sans ExtraBold\n@font-face {\n  @include fontdef($OpenSansPrefix, $OpenSansVersion, 'ExtraBold');\n  font-family: $OpenSansTitle;\n  font-style: normal;\n  font-weight: 800;\n}\n"
  },
  {
    "path": "src/frontend/maintenance/src/assets/scss/params/_grid.scss",
    "content": ".row {\n  display: flex;\n  position: relative;\n  z-index: 1000;\n  flex-direction: column;\n  justify-content: space-between;\n  width: 100%;\n  max-width: $co-page__width;\n  margin: auto;\n  padding: 0 $co-spacing__small * 2;\n\n  &--align-center {\n    max-width: 55rem;\n    margin-right: auto;\n    margin-left: auto;\n  }\n\n  > .col {\n    flex-basis: auto;\n    flex-grow: 1;\n    flex-shrink: 1;\n    margin-right: 0;\n\n    &--no-margin,\n    &:last-of-type {\n      margin-right: 0;\n    }\n  }\n}\n\n\n\n@include breakpoint(xsmall) {\n  .row {\n    padding: 0 $co-spacing__small;\n\n  }\n}\n\n\n@include breakpoint(medium) {\n  .row {\n    flex-direction: row;\n    padding: 0 $co-spacing__medium * 2;\n\n    > .col {\n      margin-right: $co-spacing__small;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/maintenance/src/assets/scss/params/_images.scss",
    "content": "// --- Images ---\n$co-img__logo: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KCjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0iTGF5ZXJfMSIKICAgeD0iMHB4IgogICB5PSIwcHgiCiAgIHZpZXdCb3g9IjAgMCAxMzY2IDI3NiIKICAgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTM2NiAyNzY7IgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBzb2RpcG9kaTpkb2NuYW1lPSJjYWxpb3Blbl9sb2dvLnN2ZyIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45Mi4xIHIiPjxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTMxIj48cmRmOlJERj48Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+PGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+PGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPjxkYzp0aXRsZT48L2RjOnRpdGxlPjwvY2M6V29yaz48L3JkZjpSREY+PC9tZXRhZGF0YT48ZGVmcwogICAgIGlkPSJkZWZzMjkiIC8+PHNvZGlwb2RpOm5hbWVkdmlldwogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxIgogICAgIG9iamVjdHRvbGVyYW5jZT0iMTAiCiAgICAgZ3JpZHRvbGVyYW5jZT0iMTAiCiAgICAgZ3VpZGV0b2xlcmFuY2U9IjEwIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSI5NTYiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTA0MSIKICAgICBpZD0ibmFtZWR2aWV3MjciCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjAuMjk3OTUwMjIiCiAgICAgaW5rc2NhcGU6Y3g9Ii00OTIuODUzNjQiCiAgICAgaW5rc2NhcGU6Y3k9IjEzOCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iOTYwIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIzNyIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIwIgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9IkxheWVyXzEiIC8+PHN0eWxlCiAgICAgdHlwZT0idGV4dC9jc3MiCiAgICAgaWQ9InN0eWxlMiI+Cgkuc3Qwe2ZpbGwtcnVsZTpldmVub2RkO2NsaXAtcnVsZTpldmVub2RkO30KPC9zdHlsZT48cGF0aAogICAgIGQ9Ik02NzkuMSwxMTMuNmMtMS0yLjUtMi4yLTUuMS0zLjgtNy42Yy0xMi40LTE1LjUtMjkuMy0yMy4yLTUwLjYtMjMuMmgtNS42Yy03LjMsMC0xNS4zLDEuNy0yMy44LDUuMSAgYy0yMy42LDEyLjUtMzUuNCwzMS4zLTM1LjQsNTYuNmMwLDYuMSwxLjIsMTMuMSwzLjUsMjEuMmMxMSwyNi45LDMwLjEsNDAuNCw1Ny4yLDQwLjRoNGM3LjgsMCwxNS42LTEuMywyMy4zLTQgIGMzLjMtMS4zLDYuMi0yLjcsOS00LjFjMS4xLDQuMSw0LjcsNi45LDEwLjcsOC4yaDJjNi43LDAsMTAuNi00LjUsMTEuNi0xMy42di03MS44QzY4MC45LDExNy43LDY4MC4xLDExNS40LDY3OS4xLDExMy42eiAgIE02MjQuMiwxODEuMmgtNGMtNSwwLTkuNC0wLjgtMTMuMi0yLjVjLTE0LjgtNi43LTIyLjMtMTcuOS0yMi4zLTMzLjhsMS0xMC4xYzUuNy0xOC4yLDE3LjItMjcuMywzNC40LTI3LjNoNCAgYzEzLjYsMCwyNCw0LjQsMzEuNCwxMy4xYzAuMiwyLDAuNiwzLjcsMSw1LjN2MzYuNmMtMC40LDEuNS0wLjgsMy4yLTEsNUM2NDguOSwxNzYuNyw2MzguNCwxODEuMiw2MjQuMiwxODEuMnoiCiAgICAgaWQ9InBhdGg0IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNNzE3LjksOC45aDJjNy40LDEuNSwxMS4xLDUuMiwxMS4xLDExLjF2MTQxLjVjMCwxMC40LDkuOCwxNywyOS40LDE5LjdjOS41LDAsMTUuMSwyLjcsMTYuNyw4LjFsMC41LDN2MyAgYzAsNS40LTMuNyw4LjktMTEuMSwxMC42SDc2MmMtMTkuMSwwLTM1LjMtNy4yLTQ4LjYtMjEuN2MtNC43LTUuOS03LjEtMTQuNS03LjEtMjUuOFYyNS4xYzAtOC4zLDIuNS0xMy40LDcuNi0xNS4yTDcxNy45LDguOXoiCiAgICAgaWQ9InBhdGg2IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNODA1LDguOWgyYzYuNSwwLDEwLjQsNC4yLDExLjYsMTIuNmMtMS42LDguMS01LjMsMTIuMS0xMS4xLDEyLjFIODA1Yy03LjQtMS42LTExLjEtNS4zLTExLjEtMTEuMXYtMi41ICBDNzk1LjQsMTIuNiw3OTkuMiw4LjksODA1LDguOXogTTgwNSw4Mi43aDNjNS40LDAsOC45LDQuMiwxMC42LDEyLjZ2OTdjLTEsOS4xLTQuOSwxMy42LTExLjYsMTMuNmgtMmMtNy40LTEuNi0xMS4xLTUuMy0xMS4xLTExLjEgIHYtMTAxQzc5NS40LDg2LjQsNzk5LjIsODIuNyw4MDUsODIuN3oiCiAgICAgaWQ9InBhdGg4IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNODk0LjgsODIuN2g0LjZjMTkuOCwwLDM2LjQsMTAuNCw0OS42LDMxLjNjNC40LDguOCw2LjYsMTcuMyw2LjYsMjUuOHY5LjFjMCwyMC4xLTEwLjEsMzYuOC0zMC40LDUwICBjLTkuMyw0LjctMTguMiw3LjEtMjYuOCw3LjFoLTMuNWMtMjEuNiwwLTM4LjgtMTEuMy01MS42LTMzLjhjLTMuNy04LjEtNS42LTE3LjItNS42LTI3LjN2LTFjMC0yOSwxNC4zLTQ4LjcsNDMtNTkuMSAgQzg4NS40LDgzLjQsODkwLjIsODIuNyw4OTQuOCw4Mi43eiBNODYyLjQsMTQyLjN2M2MwLDE4LjUsOS4zLDMwLjMsMjcuOCwzNS40bDQuNiwwLjVoMy41YzE0LjMsMCwyNC44LTguMywzMS40LTI0LjhsMS03LjZ2LTkuMSAgYzAtMTQuMy04LjMtMjQuOC0yNC44LTMxLjNsLTYuNi0xaC00LjZjLTEzLjcsMC0yNCw3LjktMzAuOSwyMy43Qzg2Mi45LDEzNS40LDg2Mi40LDEzOS4xLDg2Mi40LDE0Mi4zeiIKICAgICBpZD0icGF0aDEwIgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNMTAzMi43LDgxLjdjMjkuMywwLjksNDguOCwxMy45LDU4LjcsMzguOWMyLjcsNi43LDQsMTMuMSw0LDE5LjJ2Ny4xYzAsMjEuNS0xMC42LDM4LjgtMzEuOSw1MmMtOS42LDQuNC0xOS40LDYuNi0yOS40LDYuNiAgaC0zNC45djUyLjljLTEuNiw3LjctNS4zLDExLjYtMTEuMSwxMS42aC0yLjVjLTUuNywwLTkuNC0zLjktMTEuMS0xMS42VjEyNS42YzAtMjIuMywxNS43LTM2LjYsNDcuMS00Mi45TDEwMzIuNyw4MS43eiAgIE05OTkuMywxMjQuMXY1Ni42aDM1LjRjMTcuNCwwLDI4LjgtOC40LDM0LjQtMjUuM2MxLTIuNywxLjUtNS42LDEuNS04LjZ2LTIuNWwwLjUtMC41bC0wLjUtMC41di0xLjVjMC0xNy4zLTkuMS0yOC43LTI3LjMtMzQuNCAgbC05LjYtMWMtMTUuOSwwLTI3LjIsNC45LTMzLjksMTQuNkw5OTkuMywxMjQuMXoiCiAgICAgaWQ9InBhdGgxMiIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmIiAvPjxwYXRoCiAgICAgZD0iTTExNzAuMSw4Mi43aDUuNmMyNC42LDAsNDIuNyw5LjgsNTQuMiwyOS4zYzEuMyw0LjQsMi4yLDguNiwyLjUsMTIuNmMwLDcuNi01LjksMTIuMS0xNy43LDEzLjZsLTc3LjQsMTYuMiAgYzAsNS42LDQuNCwxMi41LDEzLjIsMjAuN2M3LjEsNCwxNCw2LjEsMjAuOCw2LjFoNGMxMC4xLDAsMTkuOS0zLjIsMjkuNC05LjZsNS4xLTFjNy44LDEuNywxMS42LDUuNiwxMS42LDExLjZ2Mi41ICBjMCw3LjgtMTEuMywxNC42LTMzLjksMjAuMmwtMTIuMSwxaC0zLjVjLTI2LjUsMC00NS40LTEzLTU2LjctMzguOWMtMi43LTcuOC00LTE1LjQtNC0yMi43YzAtMjUuNCwxMS44LTQ0LjMsMzUuNC01Ni42ICBDMTE1NC44LDg0LjQsMTE2Mi43LDgyLjcsMTE3MC4xLDgyLjd6IE0xMTM4LjcsMTI4LjdsNjIuOC0xMy4xVjExNWMtOC42LTUuMS0xNy03LjYtMjUuMy03LjZoLTUuMSAgQzExNTYuMSwxMDcuNSwxMTQ1LjMsMTE0LjUsMTEzOC43LDEyOC43eiIKICAgICBpZD0icGF0aDE0IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBkPSJNMTMwNC44LDgyLjdjMjIuOCwwLDQwLjMsOC44LDUyLjYsMjYuM2MyLjQsNS43LDMuNSwxMC4xLDMuNSwxMy4xdjcyLjdjLTEuNiw3LjQtNS4zLDExLjEtMTEuMSwxMS4xaC0yICBjLTYuNSwwLTEwLjQtNC40LTExLjYtMTMuMXYtNjkuMmMwLTMuOS0zLjktNy43LTExLjYtMTEuNmMtNi4xLTMtMTIuOC00LjUtMjAuMi00LjVoLTRjLTE0LjIsMC0yNC42LDQuNS0zMS40LDEzLjZsLTEsNHY2OS43ICBjLTEuNyw3LjQtNS40LDExLjEtMTEuMSwxMS4xaC0yYy02LjcsMC0xMC42LTQuNS0xMS42LTEzLjZ2LTY1LjJjMC0yNC4yLDE3LjItMzksNTEuNi00NC41SDEzMDQuOHoiCiAgICAgaWQ9InBhdGgxNiIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmIiAvPjxwYXRoCiAgICAgZD0iTTUwMiw4Mi43aC01LjZjLTcuMywwLTE1LjMsMS43LTIzLjgsNS4xYy0yMy42LDEyLjMtMzUuNCwzMS4yLTM1LjQsNTYuNmMwLDcuMywxLjQsMTQuOSw0LDIyLjcgIGMxMS4zLDI1LjksMzAuMiwzOC45LDU2LjcsMzguOWgzLjVsMTIuMS0xYzIyLjYtNS42LDMzLjktMTIuNCwzMy45LTIwLjJ2LTIuNWMwLTYuMS0zLjktOS45LTExLjYtMTEuNmMtMy4xLDAtNS4xLDEtNS4xLDEgIGMtOS40LDYuNC0xOS4yLDkuNi0yOS40LDkuNmgtNGMtNi43LDAtMTMuNy0yLTIwLjgtNi4xYy04LjgtOC4yLTEzLjItMTUuMS0xMy4yLTIwLjdjLTItMTYuOCwxLjUtMjUuOCwxLjUtMjUuOCAgYzYuNi0xNC4xLDE3LjQtMjEuMiwzMi40LTIxLjJoNS4xYzguMywwLDE2LjcsMi41LDI1LjMsNy42bDEuNywxLjFjMCwwLDEuOSwxLDUuMSwxYzcuOC0xLjcsMTEuNy01LjYsMTEuNy0xMS42VjEwMyAgYzAtNy44LTExLjgtMTQuMS0zNC41LTE5LjFDNTExLjcsODMuOSw1MDkuNCw4My4xLDUwMiw4Mi43eiIKICAgICBpZD0icGF0aDE4IgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBjbGFzcz0ic3QwIgogICAgIGQ9Ik0yNzkuNSwxNTcuMmM5LjgtMTAuNiwxNS44LTI0LjgsMTUuOC00MC41YzAtMzMtMjYuNy01OS43LTU5LjctNTkuN2MtMzMsMC01OS43LDI2LjctNTkuNyw1OS43ICBzMjYuNyw1OS43LDU5LjcsNTkuN2M2LjcsMCwxMy4xLTEuMSwxOS4xLTMuMWwyNy42LDkuMkwyNzkuNSwxNTcuMnoiCiAgICAgaWQ9InBhdGgyMCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmIiAvPjxwYXRoCiAgICAgY2xhc3M9InN0MCIKICAgICBkPSJNOTUuNywxMTIuNGMtMjIuMiwwLTQwLjMsMTgtNDAuMyw0MC4zYzAsMTAuMywzLjksMTkuOCwxMC4zLDI2LjlMNjUsMTk4LjRsMTYuNy03LjljNC40LDEuNiw5LjEsMi41LDE0LjEsMi41ICBjMjIuMiwwLDQwLjMtMTgsNDAuMy00MC4zUzExOCwxMTIuNCw5NS43LDExMi40eiIKICAgICBpZD0icGF0aDIyIgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PHBhdGgKICAgICBjbGFzcz0ic3QwIgogICAgIGQ9Ik0yMzUuNiw2QzE5MC4xLDYsMTUxLDMzLjQsMTMzLjksNzIuNkMxMjIuMyw2NywxMDkuNCw2NCw5NS43LDY0QzQ2LjcsNjQsNywxMDMuNyw3LDE1Mi43ICBjMCwyMC4zLDYuOCwzOS4xLDE4LjQsNTRsMi40LDQ3LjdsNDQuOS0xNmM3LjQsMiwxNS4xLDMsMjMuMSwzYzI5LjcsMCw1Ni0xNC42LDcyLjEtMzdjMTguNywxNC41LDQyLjIsMjMuMSw2Ny43LDIzLjEgIGMxMC43LDAsMjEuMS0xLjUsMzAuOS00LjRsNTUuNywxOS4xbDMuMy02MC43YzEzLjItMTguMiwyMS00MC42LDIxLTY0LjlDMzQ2LjQsNTUuNiwyOTYuOCw2LDIzNS42LDZ6IE0zMDMsMTY3LjlsLTAuOCw0Mi4zICBsLTM5LjYtMTMuMmMtOC42LDIuOS0xNy45LDQuNS0yNy41LDQuNWMtMzIuNCwwLTYwLjYtMTguMS03NS00NC43Yy0yLjIsMzMuNy0zMC4xLDYwLjMtNjQuMyw2MC4zYy03LjEsMC0xMy45LTEuMi0yMC4zLTMuMyAgbC0yNy43LDEwLjJsLTEuNS0zMC4yYy05LjMtMTEuMi0xNC45LTI1LjUtMTQuOS00MS4yYzAtMzUuNiwyOC45LTY0LjUsNjQuNS02NC41YzIyLjcsMCw0Mi42LDExLjcsNTQuMSwyOS40YzAtMC40LDAtMC45LDAtMS4zICBjMC00Ny4xLDM4LjItODUuMyw4NS4zLTg1LjNzODUuMywzOC4yLDg1LjMsODUuM0MzMjAuNCwxMzUuNywzMTMuOSwxNTMuNiwzMDMsMTY3Ljl6IgogICAgIGlkPSJwYXRoMjQiCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIgLz48L3N2Zz4K';\n\n$co-img__ico-control: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDU2IDU2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1NiA1NjsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSI1MTJweCIgaGVpZ2h0PSI1MTJweCI+CjxnPgoJPHBhdGggZD0iTTQxLjA4LDQ4SDJjLTAuNTUzLDAtMSwwLjQ0OC0xLDFzMC40NDcsMSwxLDFoMzkuMDhjMC40ODgsMy4zODcsMy40MDEsNiw2LjkyLDZjMy44NTksMCw3LTMuMTQsNy03cy0zLjE0MS03LTctNyAgIEM0NC40ODEsNDIsNDEuNTY4LDQ0LjYxMyw0MS4wOCw0OHoiIGZpbGw9IiNGRkZGRkYiLz4KCTxwYXRoIGQ9Ik0yMC42OTUsMjdIMmMtMC41NTMsMC0xLDAuNDQ4LTEsMXMwLjQ0NywxLDEsMWgxOC42MzJjMC4zOTYsMy42MDIsMy40NTYsNi40MTQsNy4xNjEsNi40MTRzNi43NjUtMi44MTIsNy4xNjEtNi40MTRINTQgICBjMC41NTMsMCwxLTAuNDQ4LDEtMXMtMC40NDctMS0xLTFIMzQuODkxYy0wLjU3Ny0zLjQtMy41MzYtNi03LjA5OC02UzIxLjI3MiwyMy42LDIwLjY5NSwyN3oiIGZpbGw9IiNGRkZGRkYiLz4KCTxwYXRoIGQ9Ik04LDBDNC4xNDEsMCwxLDMuMTQsMSw3czMuMTQxLDcsNyw3YzMuNTE5LDAsNi40MzItMi42MTMsNi45Mi02SDU0YzAuNTUzLDAsMS0wLjQ0OCwxLTFzLTAuNDQ3LTEtMS0xSDE0LjkyICAgQzE0LjQzMiwyLjYxMywxMS41MTksMCw4LDB6IiBmaWxsPSIjRkZGRkZGIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==';\n\n$co-img__ico-chat: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNTExLjYyNiA1MTEuNjI2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTEuNjI2IDUxMS42MjY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMzAxLjkyNywzMjcuNjA1YzMwLjkyNi0xMy4wMzgsNTUuMzQtMzAuNzg1LDczLjIzLTUzLjI0OGMxNy44ODgtMjIuNDU4LDI2LjgzMy00Ni45MTUsMjYuODMzLTczLjM3MiAgICBjMC0yNi40NTgtOC45NDUtNTAuOTE3LTI2Ljg0LTczLjM3NmMtMTcuODg4LTIyLjQ1OS00Mi4yOTgtNDAuMjA4LTczLjIyOC01My4yNDljLTMwLjkzLTEzLjAzOS02NC41NzEtMTkuNTU2LTEwMC45MjgtMTkuNTU2ICAgIGMtMzYuMzU0LDAtNjkuOTk1LDYuNTIxLTEwMC45MjcsMTkuNTU2Yy0zMC45MjksMTMuMDQtNTUuMzQsMzAuNzg5LTczLjIyOSw1My4yNDlDOC45NDcsMTUwLjA3MiwwLDE3NC41MjcsMCwyMDAuOTg2ICAgIGMwLDIyLjY0OCw2Ljc2Nyw0My45NzUsMjAuMjgsNjMuOTZjMTMuNTEyLDE5Ljk4MSwzMi4wNzEsMzYuODI5LDU1LjY3MSw1MC41MzFjLTEuOTAyLDQuNTcyLTMuODUzLDguNzU0LTUuODUyLDEyLjU2NiAgICBjLTIsMy44MDYtNC4zNzcsNy40NjctNy4xMzksMTAuOTkxYy0yLjc2LDMuNTI1LTQuODk5LDYuMjgzLTYuNDIzLDguMjc1Yy0xLjUyMywxLjk5OC0zLjk5Nyw0LjgxMi03LjQyNSw4LjQyMiAgICBjLTMuNDI3LDMuNjE3LTUuNjE3LDUuOTk2LTYuNTY3LDcuMTM1YzAtMC4xOTEtMC4zODEsMC4yNC0xLjE0MywxLjI4N2MtMC43NjMsMS4wNDctMS4xOTEsMS41Mi0xLjI4NSwxLjQzMSAgICBjLTAuMDk2LTAuMTAzLTAuNDc3LDAuMzczLTEuMTQzLDEuNDJjLTAuNjY2LDEuMDQ4LTEsMS41NzEtMSwxLjU3MWwtMC43MTUsMS40MjNjLTAuMjgyLDAuNTc1LTAuNDc2LDEuMTM3LTAuNTcsMS43MTIgICAgYy0wLjA5NiwwLjU2Ny0wLjE0NCwxLjE5LTAuMTQ0LDEuODU0czAuMDk0LDEuMjgsMC4yODgsMS44NTRjMC4zODEsMi40NzEsMS40NzUsNC40NjYsMy4yODMsNS45OTYgICAgYzEuODA3LDEuNTIsMy43NTYsMi4yNzksNS44NTIsMi4yNzloMC44NTdjOS41MTUtMS4zMzIsMTcuNzAxLTIuODU0LDI0LjU1Mi00LjU2OWMyOS4zMTItNy42MSw1NS43NzEtMTkuNzk3LDc5LjM3Mi0zNi41NDUgICAgYzE3LjEyOSwzLjA0NiwzMy44NzksNC41NjgsNTAuMjQ3LDQuNTY4QzIzNy4zNTMsMzQ3LjE2LDI3MC45OTgsMzQwLjY0NSwzMDEuOTI3LDMyNy42MDV6IiBmaWxsPSIjRkZGRkZGIi8+CgkJPHBhdGggZD0iTTQ5MS4zNTQsMzM4LjE2NmMxMy41MTgtMTkuODg5LDIwLjI3Mi00MS4yNDcsMjAuMjcyLTY0LjA5YzAtMjMuNDE0LTcuMTQ2LTQ1LjMxNi0yMS40MTYtNjUuNjggICAgYy0xNC4yNzctMjAuMzYyLTMzLjY5NC0zNy4zMDUtNTguMjQ1LTUwLjgxOWM0LjM3NCwxNC4yNzQsNi41NjMsMjguNzM5LDYuNTYzLDQzLjM5OGMwLDI1LjUwMy02LjM2OCw0OS42NzYtMTkuMTI5LDcyLjUxOSAgICBjLTEyLjc1MiwyMi44MzYtMzEuMDI1LDQzLjAxLTU0LjgxNiw2MC41MjRjLTIyLjA4LDE1Ljk4OC00Ny4yMDUsMjguMjYxLTc1LjM3NywzNi44MjkgICAgYy0yOC4xNjQsOC41NjItNTcuNTczLDEyLjg0OC04OC4yMTgsMTIuODQ4Yy01LjcwOCwwLTE0LjA4NC0wLjM3Ny0yNS4xMjItMS4xMzdjMzguMjU2LDI1LjExOSw4My4xNzcsMzcuNjg1LDEzNC43NTYsMzcuNjg1ICAgIGMxNi4zNzEsMCwzMy4xMTktMS41MjYsNTAuMjUxLTQuNTcxYzIzLjYsMTYuNzU1LDUwLjA2LDI4LjkzMSw3OS4zNywzNi41NDljNi44NTIsMS43MTgsMTUuMDM3LDMuMjM3LDI0LjU1NCw0LjU2OCAgICBjMi4yODMsMC4xOTEsNC4zODEtMC40NzYsNi4yODMtMS45OTljMS45MDMtMS41MjIsMy4xNDItMy42MSwzLjcxLTYuMjcyYy0wLjA4OS0xLjE0MywwLTEuNzcsMC4yODctMS44NjEgICAgYzAuMjgxLTAuMDksMC4yMzMtMC43MTItMC4xNDQtMS44NTJjLTAuMzc2LTEuMTQ0LTAuNTY4LTEuNzE1LTAuNTY4LTEuNzE1bC0wLjcxMi0xLjQyNGMtMC4xOTgtMC4zNzYtMC41Mi0wLjkwMy0wLjk5OS0xLjU2NyAgICBjLTAuNDc2LTAuNjYtMC44NTUtMS4xNDMtMS4xNDMtMS40MjdjLTAuMjgtMC4yODQtMC43MDUtMC43NjMtMS4yOC0xLjQyNGMtMC41NjgtMC42Ni0wLjk1MS0xLjA5Mi0xLjE0My0xLjI4MyAgICBjLTAuOTUxLTEuMTQzLTMuMTM5LTMuNTIxLTYuNTY0LTcuMTM5Yy0zLjQyOS0zLjYxMy01Ljg5OS02LjQyLTcuNDIyLTguNDE4Yy0xLjUyMy0xLjk5OS0zLjY2NS00Ljc1Ny02LjQyNC04LjI4MiAgICBjLTIuNzU4LTMuNTE4LTUuMTQtNy4xODMtNy4xMzktMTAuOTkxYy0xLjk5OC0zLjgwNi0zLjk0OS03Ljk5NS01Ljg1Mi0xMi41NkM0NTkuMjg5LDM3NC44NTksNDc3Ljg0MywzNTguMDYyLDQ5MS4zNTQsMzM4LjE2NnoiIGZpbGw9IiNGRkZGRkYiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K';\n\n$co-img__ico-happy: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNTEwIDUxMCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTEwIDUxMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8Zz4KCTxnIGlkPSJtb29kIj4KCQk8cGF0aCBkPSJNMjU1LDBDMTE0Ljc1LDAsMCwxMTQuNzUsMCwyNTVzMTE0Ljc1LDI1NSwyNTUsMjU1czI1NS0xMTQuNzUsMjU1LTI1NVMzOTUuMjUsMCwyNTUsMHogTTM0NC4yNSwxNTMgICAgYzIwLjQsMCwzOC4yNSwxNy44NSwzOC4yNSwzOC4yNXMtMTcuODUsMzguMjUtMzguMjUsMzguMjVTMzA2LDIxMS42NSwzMDYsMTkxLjI1UzMyMy44NSwxNTMsMzQ0LjI1LDE1M3ogTTE2NS43NSwxNTMgICAgYzIwLjQsMCwzOC4yNSwxNy44NSwzOC4yNSwzOC4yNXMtMTcuODUsMzguMjUtMzguMjUsMzguMjVzLTM4LjI1LTE3Ljg1LTM4LjI1LTM4LjI1UzE0NS4zNSwxNTMsMTY1Ljc1LDE1M3ogTTI1NSw0MDggICAgYy02Ni4zLDAtMTIyLjQtNDMuMzUtMTQyLjgtMTAyaDI4NS42QzM3Ny40LDM2NC42NSwzMjEuMyw0MDgsMjU1LDQwOHoiIGZpbGw9IiNGRkZGRkYiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K';\n"
  },
  {
    "path": "src/frontend/maintenance/src/assets/scss/params/_variables.scss",
    "content": "//\n// --- Colors ---\n$co-color__primary: #1395ba; // caliopen blue\n$co-color__primary--soft: #8dceec; // for titles\n$co-color__primary--bright: #16ccda; // for gradient\n$co-color__contrast: #f1b941; // yellow\n$co-color__back: #1b1b1b;\n$co-color__back--low: #000;\n$co-color__text: #e8e8e8;\n$co-color__text--high: #fff;\n$co-color__text--low: #515151;\n//\n// --- page ---\n$co-page__width: 95rem;\n$co-page-col__width: 44.38rem;\n//\n// --- spacings ---\n$co-spacing__small: 1.25rem; // 20px\n$co-spacing__medium: 2.5rem; // 40px\n$co-spacing__large: 4.375rem; // 70px\n//\n//-- fonts ---\n$co-font__family: 'Open Sans', sans-serif;\n$co-font__size--small: .8125rem; // 13px\n$co-font__size--medium: 1.375rem; // 22px\n$co-font__size--large: 3.125rem; // 50px\n$co-font__size--xlarge: 4.25rem; // 68px\n//\n// --- Breakpoinst ---\n@mixin breakpoint($class) {\n  @if $class == xsmall {\n    @media (max-width: 321px) { @content; }\n  }  @else if $class == small {\n    @media (min-width: 520px) { @content; }\n  } @else if $class == medium {\n    @media (min-width: $co-page-col__width) { @content; }\n  }  @else if $class == large {\n    @media (min-width: $co-page__width) { @content; }\n  }\n }\n"
  },
  {
    "path": "src/frontend/maintenance/src/assets/scss/style.scss",
    "content": "@import 'params/variables';\n@import 'params/images';\n@import 'params/fonts';\n@import 'params/grid';\n@import 'parts/global';\n@import 'parts/header';\n@import 'parts/front';\n@import 'parts/devices';\n@import 'parts/body';\n@import 'parts/form';\n@import 'parts/roadmap';\n@import 'parts/footer';\n@import 'parts/mautic-form';\n"
  },
  {
    "path": "src/frontend/maintenance/src/index.html",
    "content": "<!DOCTYPE HTML>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <title>Caliopen: maintenance</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <meta name=\"description\" content=\"Caliopen est en maintenance!\" />\n  <meta name=\"author\" content=\"caliopen.org\" />\n  <link rel=\"icon\" href=\"assets/img/cropped-caliopen-logo-32x32.png\" sizes=\"32x32\" />\n  <link rel=\"icon\" href=\"assets/img/cropped-caliopen-logo-192x192.png\" sizes=\"192x192\" />\n  <link rel=\"apple-touch-icon-precomposed\" href=\"assets/img/cropped-caliopen-logo-180x180.png\" />\n  <link rel=\"stylesheet\" href=\"assets/css/style.css\">\n  <!-- <link href=\"{{ getAssetUrl('themes/'~template~'/css/style.css') }}\" rel=\"stylesheet\"> -->\n</head>\n<body>\n  <header class=\"co-header\">\n    <nav class=\"row co-header__menu\">\n      <ul class=\"col\">\n        <li class=\"co-header__link co-header__link--home\">\n          <a href=\"\" title=\"Caliopen version:Beta home page\"><span>Caliopen Beta</span></a>\n        </li>\n      </ul>\n      <ul class=\"co-header__menu--right col\">\n        <li class=\"co-header__link\"><a href=\"#features\" title=\"Fonctionnalités\">Fonctionnalités</a></li>\n        <li class=\"co-header__link\"><a href=\"#roadmap\" title=\"Et ensuite\">Et ensuite</a></li>\n        <li class=\"co-header__link\"><a href=\"#feedbacks\" title=\"Dites-nous-tout\">Feedbacks</a></li>\n      </ul>\n    </nav>\n  </header>\n  <section class=\"co-front co-front--align-center\">\n    <div class=\"row row--align-center\">\n      <div id=\"alpha\" class=\"col co-front__content\">\n        <h1>Maintenance<br />en cours...</h1>\n        <h2>Nous sommes actuellement dans une phase de maintenance, merci de réessayer dans quelques minutes.</h2>\n      </div>\n    </div>\n  </section>\n\n  <div class=\"co-body\">\n    <div class=\"row row--align-center\">\n      <section id=\"features\" class=\"col col--no-margin\">\n        <h1>Fonctionnalités</h1>\n        <h2>À quoi faut-il s'attendre dans cette version Beta&nbsp;?</h2>\n        <p>Il s'agit d'une version de test qui ne reflète pas les fonctionnalités définitives, et beaucoup de choses vont évoluer entre la version Beta et la verion finale. Voici cependant les fonctionnalités actives dans cette version&nbsp;:</p>\n        <ul class=\"co-body__list\">\n          <li><span>Écrire et envoyer des messages depuis une adresse en @caliopen.app.</span></li>\n          <li><span>Un calcul de l'indice de vie privée pour chaque message entrant.</span></li>\n          <li><span>Un calcul de l'indice de vie privée sur les contacts.</span></li>\n          <li><span>Un carnet d'adresses.</span></li>\n          <li><span>Une recherche dans les messages.</span></li>\n        </ul>\n      </section>\n      <section id=\"roadmap\" class=\"col col--no-margin\">\n        <h1>Et ensuite&nbsp;?</h1>\n        <h2>Qu'est-ce qui est prévu pour les prochaines versions&nbsp;?</h2>\n        <p>Le but de cette version Beta est de récolter un maximum de retours d'expérience afin d'améliorer Caliopen. Mais nous avons aussi plein de nouvelles fonctionnalités dans les cartons&nbsp;! Voici ce qui est prévu&nbsp;:</p>\n        <ul class=\"co-body__list\">\n          <li><span>L'aggregation des messages venant de toutes vos plateformes préférées.</span></li>\n          <li><span>La possibilité de chiffrer et déchiffrer les messages.</span></li>\n          <li><span>Une refonte de l'interface graphique.</span></li>\n          <li><span>Des recherches intuitives.</span></li>\n          <li><span>Des vues personnalisées.</span></li>\n        </ul>\n        <div class=\"co-body__thumbs\">\n          <figure>\n            <a href=\"assets/img/caliopen_capture1.jpg\" target=\"_blank\" title=\"Enlarge picture\">\n              <img src=\"assets/img/caliopen_capture1.jpg\" alt=\"Caliopen light design\" />\n            </a>\n          </figure>\n          <figure>\n            <a href=\"assets/img/caliopen_capture2.jpg\" target=\"_blank\" title=\"Enlarge picture\">\n              <img src=\"assets/img/caliopen_capture2.jpg\" alt=\"Caliopen Message light design\"/>\n            </a>\n          </figure>\n          <figure>\n            <a href=\"assets/img/caliopen_capture3.jpg\" target=\"_blank\" title=\"Enlarge picture\">\n              <img src=\"assets/img/caliopen_capture3.jpg\" alt=\"Caliopen Timeline light design\"/>\n            </a>\n          </figure>\n        </div>\n      </section>\n      <section class=\"col col--no-margin\">\n        <h1>Roadmap</h1>\n        <h2>Planning prévisionnel</h2>\n        <figure class=\"co-body__roadmap\">\n          <img src=\"assets/img/timeline.png\" alt=\"Version Beta: maintenant! Version beta: Avril 2017. Version stable: Octobre 2018\" />\n        </figure>\n      </section>\n      <section id=\"feedbacks\" class=\"col col--no-margin\">\n        <h1>Dites-nous tout</h1>\n        <h2>Un horrible bug, une fonctionalité mal designée, une difficulté rentrontrée, une question, une suggestion&nbsp;?</h2>\n        <p>La raison d'être d'une version Beta est de tester l'application et de faire part des problèmes à l'équipe. Quelle que soit votre ressenti, nous avons besoin que vous nous en fassiez part.</p>\n        <p>Si c'est un avis, une suggestion ou un bug, <a href=\"https://feedback.caliopen.org/\" title=\"feedback\" target=\"_blank\">vous pouvez nous la transmettre sur Discourse</a>, si c'est un bug et que vos êtes familier avec github, <a href=\"https://github.com/CaliOpen/Caliopen\" target=\"_blank\">n'hésitez pas à créer une issue ou proposer une pull request</a>.</p>\n      </section>\n    </div>\n  </div>\n\n  <footer class=\"co-footer\">\n    <ul class=\"row\">\n      <li class=\"col\">\n        <a href=\"https://www.caliopen.org\" title=\"Caliopen. Be Good.\" target=\"_blank\"><span>Caliopen</span></a><br />\n        Be cool.\n      </li>\n    </ul>\n  </footer>\n\n  <script type=\"text/javascript\" src=\"assets/js/jquery-3.2.1.min.js\"></script>\n  <script type=\"text/javascript\" src=\"assets/js/init.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/frontend/not_found/404.html",
    "content": "<!DOCTYPE html>\n<html lang=\"fr\">\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>Page introuvable - Caliopen, be good!</title>\n    <meta name=\"apple-mobile-web-app-title\" content=\"Caliopen\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\">\n    <meta name=\"theme-color\" content=\"#ffffff\">\n    <link href=\"./assets/app.client.e4ae8e333ea54a53ba14.css\" rel=\"stylesheet\">\n  </head>\n  <body>\n    <div id=\"root\">\n      <div class=\"l-page m-background-image m-background-image--secure\">\n        <div class=\"l-header\">\n          <div class=\"l-header__container l-page-container\">\n            <a class=\"l-header__brand-link m-link m-link--text\" href=\"/\">\n              <div class=\"m-brand m-brand--responsive l-header__brand\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 350 250\" class=\"m-brand__logo\" aria-label=\"Logo Caliopen\"><path d=\"M231.7 2c-43.9 0-81.5 26.4-98 64.1A84.1 84.1 0 0 0 97 57.8a85.4 85.4 0 0 0-85.4 85.4 84.4 84.4 0 0 0 17.7 52l2.3 46 43.2-15.5a84.9 84.9 0 0 0 22.3 3 85.3 85.3 0 0 0 69.4-35.7 106 106 0 0 0 65.2 22.3c10.3 0 20.3-1.5 29.7-4.3l53.6 18.4 3.2-58.4a106 106 0 0 0 20.2-62.5A106.8 106.8 0 0 0 231.7 2zm-.3 24a82.1 82.1 0 0 1 82.1 82.1 83.2 83.2 0 0 1-17 49.8l-.7 40.7-38.2-12.7a83 83 0 0 1-26.4 4.3 82 82 0 0 1-72.2-43 62 62 0 0 1-62 58c-6.8 0-13.3-1.1-19.5-3.1L51 211.9l-1.5-29A61.8 61.8 0 0 1 35.1 143a62.1 62.1 0 0 1 62-62 62 62 0 0 1 52.1 28.2v-1.2A82.1 82.1 0 0 1 231.4 26zm-1 24.5A57.4 57.4 0 0 0 173 108a57.4 57.4 0 0 0 57.5 57.5c6.5 0 12.6-1 18.4-3l26.6 8.8-2.7-24.3a57.2 57.2 0 0 0 15.2-39 57.4 57.4 0 0 0-57.5-57.5zM95.7 104a38.8 38.8 0 0 0-28.9 64.7l-.7 18 16.1-7.6a39.5 39.5 0 0 0 13.6 2.5 38.8 38.8 0 1 0 0-77.6z\"></path></svg><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 925 250\" class=\"m-brand__brand-name\" aria-label=\"Caliopen\"><path d=\"M251 100.2a43 43 0 0 0-3.6-7.2 59 59 0 0 0-48.6-22.2h-5.4c-7 0-14.7 1.6-22.9 4.8-22.7 12-34 30-34 54.2 0 5.9 1.2 12.6 3.4 20.3 10.5 25.8 28.9 38.7 55 38.7h3.8a67.6 67.6 0 0 0 31-7.7c1 3.9 4.6 6.6 10.3 7.8h2c6.4 0 10.1-4.3 11-13V107c-.2-3-1-5.1-2-6.9zM198.4 165h-3.8c-4.8 0-9-.8-12.7-2.4-14.3-6.4-21.5-17.2-21.5-32.4l1-9.7c5.5-17.4 16.5-26 33-26h4c13 0 23 4.1 30 12.5l1 5v35a32.7 32.6 0 0 0-1 4.9c-6.3 8.8-16.3 13-30 13zm90-165h2c7.1 1.4 10.7 5 10.7 10.6v135.5c0 10 9.4 16.3 28.2 18.9 9.2 0 14.5 2.6 16 7.7l.6 2.9v2.9c0 5.1-3.6 8.5-10.7 10.1h-4.3a61 61 0 0 1-46.8-20.8 38.8 38.8 0 0 1-6.8-24.7V15.5c0-8 2.4-12.8 7.3-14.5zm83.8 0h2c6.2 0 10 4 11 12-1.5 7.8-5 11.6-10.6 11.6h-2.4c-7.1-1.5-10.7-5-10.7-10.6v-2.4C363 3.6 366.5 0 372.1 0zm0 70.7h2.9c5.2 0 8.5 4 10.2 12v92.9c-1 8.7-4.7 13-11.2 13h-1.9c-7.1-1.5-10.7-5-10.7-10.6V81.3c1.5-7 5.1-10.6 10.7-10.6zm86.3 0h4.4c19 0 35 10 47.7 30a54.5 54.5 0 0 1 6.4 24.6v8.7c0 19.3-9.8 35.3-29.3 48-8.9 4.4-17.5 6.7-25.7 6.7h-3.4c-20.8 0-37.3-10.8-49.6-32.3a62 62 0 0 1-5.4-26.2v-1c0-27.7 13.8-46.6 41.4-56.5a48 48 0 0 1 13.5-2zm-31.1 57v2.9c0 17.7 9 29 26.7 33.9l4.4.5h3.4c13.7 0 23.8-8 30.2-23.8l1-7.2v-8.8c0-13.7-8-23.7-24-30l-6.3-.9h-4.4c-13.2 0-23 7.6-29.7 22.7-.8 4.1-1.3 7.7-1.3 10.7zm163.7-58c28.1.9 46.9 13.3 56.4 37.3a48.9 48.9 0 0 1 3.8 18.3v6.8c0 20.6-10.2 37.2-30.6 49.8a67.2 67 0 0 1-28.3 6.3h-33.5V239c-1.6 7.4-5.1 11.1-10.7 11.1h-2.4c-5.5 0-9-3.7-10.7-11.1V111.7c0-21.3 15.1-35 45.3-41zm-32.1 40.6v54.2h34c16.7 0 27.7-8 33-24.2 1-2.6 1.5-5.4 1.5-8.3v-2.4l.5-.4-.5-.5v-1.4c0-16.6-8.7-27.5-26.2-33l-9.3-1c-15.3 0-26.1 4.7-32.6 14zM723 70.7h5.4c23.7 0 41 9.3 52.1 28a58.6 58.4 0 0 1 2.4 12c0 7.4-5.6 11.7-17 13.1l-74.4 15.5c0 5.4 4.2 12 12.7 19.8a40.8 40.8 0 0 0 20 5.9h3.8a50 50 0 0 0 28.3-9.2l4.9-1c7.5 1.7 11.2 5.4 11.2 11.1v2.4c0 7.5-11 14-32.6 19.4l-11.7 1h-3.3c-25.5 0-43.7-12.5-54.5-37.3a66 66 0 0 1-3.9-21.8c0-24.3 11.4-42.4 34-54.1a61 61 0 0 1 22.6-4.8zm-30.1 44l60.3-12.5v-.6a47.5 47.5 0 0 0-24.3-7.3H724a32.3 32.3 0 0 0-31.1 20.4zm159.6-44c22 0 38.8 8.4 50.6 25.1 2.3 5.5 3.3 9.7 3.3 12.6V178c-1.5 7-5 10.6-10.6 10.6h-2c-6.2 0-10-4.2-11.1-12.5v-66.3c0-3.7-3.7-7.3-11.1-11a43.4 43.4 0 0 0-19.5-4.4h-3.8c-13.7 0-23.7 4.3-30.2 13l-1 3.9V178c-1.6 7-5.1 10.6-10.6 10.6h-2c-6.4 0-10.1-4.3-11-13v-62.4c0-23.2 16.4-37.4 49.5-42.6h9.5zm-771.7 0h-5.3c-7 0-14.7 1.6-23 4.8-22.6 11.8-34 30-34 54.2 0 7 1.4 14.3 3.9 21.8 10.9 24.8 29 37.2 54.5 37.2h3.4l11.6-1c21.7-5.3 32.6-11.8 32.6-19.3V166c0-5.8-3.8-9.5-11.2-11-3 0-4.9.9-4.9.9-9 6.1-18.4 9.2-28.2 9.2h-3.9a40 40 0 0 1-20-5.9c-8.4-7.8-12.7-14.4-12.7-19.8-1.9-16 1.5-24.7 1.5-24.7a32.2 32.2 0 0 1 31.1-20.3h5c8 0 16 2.4 24.2 7.3l1.7 1s1.8 1 4.9 1c7.5-1.6 11.2-5.4 11.2-11.1V90c0-7.5-11.3-13.5-33.1-18.3 0 0-2.1-.8-9.3-1.1z\"></path></svg>\n              </div>\n            </a>\n            <div class=\"l-header__user-menu\"></div>\n          </div>\n        </div>\n        <div class=\"l-page-container\">\n          <div class=\"s-page-not-found\">\n            <h2 class=\"s-page-not-found__title\">Licorne introuvable</h2>\n            <div>Merci d'utiliser\n              <pre class=\"s-page-not-found__ascii\" aria-label=\"Caliopen est dessiné en utilisant du ASCIi art\">\n         ▄▀▀▀▀▀▀▄                     █   ▀\n   ▄▀▀▀▀▄ ▄████▄ ▌      ▄▀▀▀▀▄ ▄▀▀▀▀▄ █   ▄   ▄▀▀▀▀▄ ▄▀▀▀▀▄ ▄▀▀▀▄ ▄▀▀▀▀▄\n  ▐ ▄██▄  ██████ ▌      █      █    █ █   █   █    █ █    █ █ ▄▀  █    █\n  ▐ ▀██▀  ▀████▀ ▌      ▀▄▄▄▄▀ ▀▄▄▄▀█ ▀▄▄ ▀▄▄ ▀▄▄▄▄▀ █▄▄▄▄▀ ▀█▄▄▀ █    █\n  ▄▀▄▄▄▄▀▀▄▄▄▄▄▄▀▄                                   █\n              </pre>\n              <pre class=\"s-page-not-found__ascii-short\" aria-label=\"Logo de Caliopen en ASCIi art\">\n         ▄▀▀▀▀▀▀▄\n   ▄▀▀▀▀▄ ▄████▄ ▌\n  ▐ ▄██▄  ██████ ▌\n  ▐ ▀██▀  ▀████▀ ▌\n  ▄▀▄▄▄▄▀▀▄▄▄▄▄▄▀▄\n              </pre>\n            </div>\n          </div>\n        </div>\n        <div class=\"l-page-container\">\n          <div class=\"l-footer\">\n            <div class=\"m-brand m-brand--low l-footer__brand\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 350 250\" class=\"m-brand__logo\" aria-label=\"Logo Caliopen\"><path d=\"M231.7 2c-43.9 0-81.5 26.4-98 64.1A84.1 84.1 0 0 0 97 57.8a85.4 85.4 0 0 0-85.4 85.4 84.4 84.4 0 0 0 17.7 52l2.3 46 43.2-15.5a84.9 84.9 0 0 0 22.3 3 85.3 85.3 0 0 0 69.4-35.7 106 106 0 0 0 65.2 22.3c10.3 0 20.3-1.5 29.7-4.3l53.6 18.4 3.2-58.4a106 106 0 0 0 20.2-62.5A106.8 106.8 0 0 0 231.7 2zm-.3 24a82.1 82.1 0 0 1 82.1 82.1 83.2 83.2 0 0 1-17 49.8l-.7 40.7-38.2-12.7a83 83 0 0 1-26.4 4.3 82 82 0 0 1-72.2-43 62 62 0 0 1-62 58c-6.8 0-13.3-1.1-19.5-3.1L51 211.9l-1.5-29A61.8 61.8 0 0 1 35.1 143a62.1 62.1 0 0 1 62-62 62 62 0 0 1 52.1 28.2v-1.2A82.1 82.1 0 0 1 231.4 26zm-1 24.5A57.4 57.4 0 0 0 173 108a57.4 57.4 0 0 0 57.5 57.5c6.5 0 12.6-1 18.4-3l26.6 8.8-2.7-24.3a57.2 57.2 0 0 0 15.2-39 57.4 57.4 0 0 0-57.5-57.5zM95.7 104a38.8 38.8 0 0 0-28.9 64.7l-.7 18 16.1-7.6a39.5 39.5 0 0 0 13.6 2.5 38.8 38.8 0 1 0 0-77.6z\"></path></svg>\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 925 250\" class=\"m-brand__brand-name\" aria-label=\"Caliopen\"><path d=\"M251 100.2a43 43 0 0 0-3.6-7.2 59 59 0 0 0-48.6-22.2h-5.4c-7 0-14.7 1.6-22.9 4.8-22.7 12-34 30-34 54.2 0 5.9 1.2 12.6 3.4 20.3 10.5 25.8 28.9 38.7 55 38.7h3.8a67.6 67.6 0 0 0 31-7.7c1 3.9 4.6 6.6 10.3 7.8h2c6.4 0 10.1-4.3 11-13V107c-.2-3-1-5.1-2-6.9zM198.4 165h-3.8c-4.8 0-9-.8-12.7-2.4-14.3-6.4-21.5-17.2-21.5-32.4l1-9.7c5.5-17.4 16.5-26 33-26h4c13 0 23 4.1 30 12.5l1 5v35a32.7 32.6 0 0 0-1 4.9c-6.3 8.8-16.3 13-30 13zm90-165h2c7.1 1.4 10.7 5 10.7 10.6v135.5c0 10 9.4 16.3 28.2 18.9 9.2 0 14.5 2.6 16 7.7l.6 2.9v2.9c0 5.1-3.6 8.5-10.7 10.1h-4.3a61 61 0 0 1-46.8-20.8 38.8 38.8 0 0 1-6.8-24.7V15.5c0-8 2.4-12.8 7.3-14.5zm83.8 0h2c6.2 0 10 4 11 12-1.5 7.8-5 11.6-10.6 11.6h-2.4c-7.1-1.5-10.7-5-10.7-10.6v-2.4C363 3.6 366.5 0 372.1 0zm0 70.7h2.9c5.2 0 8.5 4 10.2 12v92.9c-1 8.7-4.7 13-11.2 13h-1.9c-7.1-1.5-10.7-5-10.7-10.6V81.3c1.5-7 5.1-10.6 10.7-10.6zm86.3 0h4.4c19 0 35 10 47.7 30a54.5 54.5 0 0 1 6.4 24.6v8.7c0 19.3-9.8 35.3-29.3 48-8.9 4.4-17.5 6.7-25.7 6.7h-3.4c-20.8 0-37.3-10.8-49.6-32.3a62 62 0 0 1-5.4-26.2v-1c0-27.7 13.8-46.6 41.4-56.5a48 48 0 0 1 13.5-2zm-31.1 57v2.9c0 17.7 9 29 26.7 33.9l4.4.5h3.4c13.7 0 23.8-8 30.2-23.8l1-7.2v-8.8c0-13.7-8-23.7-24-30l-6.3-.9h-4.4c-13.2 0-23 7.6-29.7 22.7-.8 4.1-1.3 7.7-1.3 10.7zm163.7-58c28.1.9 46.9 13.3 56.4 37.3a48.9 48.9 0 0 1 3.8 18.3v6.8c0 20.6-10.2 37.2-30.6 49.8a67.2 67 0 0 1-28.3 6.3h-33.5V239c-1.6 7.4-5.1 11.1-10.7 11.1h-2.4c-5.5 0-9-3.7-10.7-11.1V111.7c0-21.3 15.1-35 45.3-41zm-32.1 40.6v54.2h34c16.7 0 27.7-8 33-24.2 1-2.6 1.5-5.4 1.5-8.3v-2.4l.5-.4-.5-.5v-1.4c0-16.6-8.7-27.5-26.2-33l-9.3-1c-15.3 0-26.1 4.7-32.6 14zM723 70.7h5.4c23.7 0 41 9.3 52.1 28a58.6 58.4 0 0 1 2.4 12c0 7.4-5.6 11.7-17 13.1l-74.4 15.5c0 5.4 4.2 12 12.7 19.8a40.8 40.8 0 0 0 20 5.9h3.8a50 50 0 0 0 28.3-9.2l4.9-1c7.5 1.7 11.2 5.4 11.2 11.1v2.4c0 7.5-11 14-32.6 19.4l-11.7 1h-3.3c-25.5 0-43.7-12.5-54.5-37.3a66 66 0 0 1-3.9-21.8c0-24.3 11.4-42.4 34-54.1a61 61 0 0 1 22.6-4.8zm-30.1 44l60.3-12.5v-.6a47.5 47.5 0 0 0-24.3-7.3H724a32.3 32.3 0 0 0-31.1 20.4zm159.6-44c22 0 38.8 8.4 50.6 25.1 2.3 5.5 3.3 9.7 3.3 12.6V178c-1.5 7-5 10.6-10.6 10.6h-2c-6.2 0-10-4.2-11.1-12.5v-66.3c0-3.7-3.7-7.3-11.1-11a43.4 43.4 0 0 0-19.5-4.4h-3.8c-13.7 0-23.7 4.3-30.2 13l-1 3.9V178c-1.6 7-5.1 10.6-10.6 10.6h-2c-6.4 0-10.1-4.3-11-13v-62.4c0-23.2 16.4-37.4 49.5-42.6h9.5zm-771.7 0h-5.3c-7 0-14.7 1.6-23 4.8-22.6 11.8-34 30-34 54.2 0 7 1.4 14.3 3.9 21.8 10.9 24.8 29 37.2 54.5 37.2h3.4l11.6-1c21.7-5.3 32.6-11.8 32.6-19.3V166c0-5.8-3.8-9.5-11.2-11-3 0-4.9.9-4.9.9-9 6.1-18.4 9.2-28.2 9.2h-3.9a40 40 0 0 1-20-5.9c-8.4-7.8-12.7-14.4-12.7-19.8-1.9-16 1.5-24.7 1.5-24.7a32.2 32.2 0 0 1 31.1-20.3h5c8 0 16 2.4 24.2 7.3l1.7 1s1.8 1 4.9 1c7.5-1.6 11.2-5.4 11.2-11.1V90c0-7.5-11.3-13.5-33.1-18.3 0 0-2.1-.8-9.3-1.1z\"></path></svg>\n            </div>\n            <div class=\"l-footer__release\">\n              Be good. - Powered by <a href=\"https://caliopen.org\" target=\"_blank\">Caliopen</a>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "src/frontend/not_found/assets/app.client.e4ae8e333ea54a53ba14.css",
    "content": "@charset \"UTF-8\";\n.s-page-not-found { margin-top: 2rem; }.m-avatar-letter-wrapper{display:inline-block;vertical-align:middle;border-radius:.2rem;background-color:#0b7497;overflow:hidden;width:2.5rem;height:2.5rem;font-size:1.25rem;font-weight:600;line-height:2.5rem}.m-avatar-letter-wrapper__letter:before{display:block;color:#fff;text-align:center;font-size:inherit;line-height:inherit}.m-avatar-letter-wrapper--small{width:1.75rem;height:1.75rem;font-size:.875rem;font-weight:600;line-height:1.75rem}.m-avatar-letter-wrapper--small__letter:before{font-size:inherit;line-height:inherit}.m-avatar-letter-wrapper--medium{width:2.5rem;height:2.5rem;font-size:1.25rem;font-weight:600;line-height:2.5rem}.m-avatar-letter-wrapper--medium__letter:before{font-size:inherit;line-height:inherit}.m-avatar-letter-wrapper--large{width:4rem;height:4rem;font-size:2rem;font-weight:600;line-height:4rem}.m-avatar-letter-wrapper--large__letter:before{font-size:inherit;line-height:inherit}.m-avatar-letter-wrapper--xlarge{width:8rem;height:8rem;font-size:4rem;font-weight:600;line-height:8rem}.m-avatar-letter-wrapper--xlarge__letter:before{font-size:inherit;line-height:inherit}.m-avatar-letter-wrapper--round{border-radius:50%}.m-letter--none:before{content:\" \";height:100%;background-color:#4682b4}.m-letter--checked:before{content:\"✓\";background-color:#0b7497}.m-letter--plus:before{content:\"+\";background-color:#008b8b}.m-letter--a:before{content:\"A\";background-color:#6495ed}.m-letter--b:before{content:\"B\";background-color:#708090}.m-letter--c:before{content:\"C\";background-color:peru}.m-letter--d:before{content:\"D\";background-color:tomato}.m-letter--e:before{content:\"E\";background-color:#b22222}.m-letter--f:before{content:\"F\";background-color:#228b22}.m-letter--g:before{content:\"G\";background-color:#9932cc}.m-letter--h:before{content:\"H\";background-color:#483d8b}.m-letter--i:before{content:\"I\";background-color:#db7093}.m-letter--j:before{content:\"J\";background-color:#ba55d3}.m-letter--k:before{content:\"K\";background-color:#556b2f}.m-letter--l:before{content:\"L\";background-color:#87cefa}.m-letter--m:before{content:\"M\";background-color:#bdb76b}.m-letter--n:before{content:\"N\";background-color:#6b8e23}.m-letter--o:before{content:\"O\";background-color:#9370db}.m-letter--p:before{content:\"P\";background-color:#5f9ea0}.m-letter--q:before{content:\"Q\";background-color:#8b4513}.m-letter--r:before{content:\"R\";background-color:#191970}.m-letter--s:before{content:\"S\";background-color:teal}.m-letter--t:before{content:\"T\";background-color:#40e0d0}.m-letter--u:before{content:\"U\";background-color:#ff4500}.m-letter--v:before{content:\"V\";background-color:#2e8b57}.m-letter--w:before{content:\"W\";background-color:olive}.m-letter--x:before{content:\"X\";background-color:#8b008b}.m-letter--y:before{content:\"Y\";background-color:indigo}.m-letter--z:before{content:\"Z\";background-color:#d2691e}.m-user-info{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;align-items:center}.m-user-info__avatar{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.m-user-info__avatar{padding-right:.75rem;padding-left:.75rem}}.m-user-info__username{flex:1 1 0px;padding-right:.625rem;padding-left:.625rem;min-width:0;overflow:hidden;color:#444;font-weight:600}@media print,screen and (min-width:40em){.m-user-info__username{padding-right:.75rem;padding-left:.75rem}}@-webkit-keyframes m-loading__spin{0%{-webkit-transform:scale(1) rotate(0deg);transform:scale(1) rotate(0deg)}to{-webkit-transform:scale(1) rotate(1turn);transform:scale(1) rotate(1turn)}}@keyframes m-loading__spin{0%{-webkit-transform:scale(1) rotate(0deg);transform:scale(1) rotate(0deg)}to{-webkit-transform:scale(1) rotate(1turn);transform:scale(1) rotate(1turn)}}.m-spinner--inline-block{display:inline-block;padding:.75rem}.m-spinner__icon{display:inline-block;font-size:1rem;-webkit-animation:m-loading__spin .5s linear infinite;animation:m-loading__spin .5s linear infinite}.m-spinner__icon svg path{fill:#919191}.m-spinner--bright .m-spinner__icon svg path{fill:#fff}.m-action-bar{position:relative;z-index:1;background:#fff;line-height:2.8125rem}.m-action-bar--hr{border-bottom:3px solid #e8f5fb}.m-action-bar__loading{position:absolute;z-index:1;width:0;transition:width .15s ease-in-out;background:#fff;overflow:hidden}.m-action-bar__loading--is-loading{width:100%;padding-left:.625rem}.m-action-bar__actions{color:#1a5773;font-weight:700}.m-action-bar__action-btn{margin-left:.625rem;color:#0e97c3}@media print,screen and (min-width:40em){.m-action-bar{max-width:1192px;margin-right:auto;margin-left:auto}}@media screen and (min-width:75em){.m-action-bar{max-width:1120px}}\n/*!\n *  Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome\n *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n */.m-icon--spaced{margin-left:.375rem}.m-icon--right-spaced,.m-icon--spaced{margin-right:.375rem}.m-button{transition:background-color .1s ease-out,color .1s ease-out;border-color:#0f98c6;color:#0f98c6;font-weight:700;text-decoration:none;cursor:pointer;text-align:left}.m-button:disabled{cursor:default}.m-button:disabled:hover,.m-button:hover{color:#505050}.m-button:active{color:#fff}.m-button--primary{border-color:#0f98c6;color:#0f98c6}.m-button--primary-plain:not(:disabled),.m-button--primary:focus:not(:disabled),.m-button--primary:hover:not(:disabled){background-color:#084c63;color:#505050}.m-button--secondary{border-color:#13ba95;color:#13ba95}.m-button--secondary-plain:not(:disabled),.m-button--secondary:focus:not(:disabled),.m-button--secondary:hover:not(:disabled){background-color:#0a5d4b;color:#505050}.m-button--success{border-color:#3adb76;color:#3adb76}.m-button--success-plain:not(:disabled),.m-button--success:focus:not(:disabled),.m-button--success:hover:not(:disabled){background-color:#157539;color:#505050}.m-button--warning{border-color:#ffae00;color:#ffae00}.m-button--warning-plain:not(:disabled),.m-button--warning:focus:not(:disabled),.m-button--warning:hover:not(:disabled){background-color:#805700;color:#505050}.m-button--alert{border-color:#e43737;color:#e43737}.m-button--alert-plain:not(:disabled),.m-button--alert:focus:not(:disabled),.m-button--alert:hover:not(:disabled){background-color:#7d1111;color:#505050}.m-button--disabled{border-color:#ccc;color:#ccc}.m-button--disabled-plain:not(:disabled),.m-button--disabled:focus:not(:disabled),.m-button--disabled:hover:not(:disabled){background-color:#666;color:#505050}.m-button__text{margin-left:.375rem}.m-button--center{text-align:center}.m-button--inline-block{display:inline-block;padding:0 1rem;line-height:2.625rem;white-space:nowrap}.m-button--inline-block:hover{color:#505050}.m-button--inline-block:hover:not(:disabled){background-color:#0f98c6}.m-button--inline-block:active{background-color:#0a617f}.m-button--block{display:inline-block;padding:0 1rem;line-height:2.625rem;white-space:nowrap;width:100%}.m-button--block:hover{color:#505050}.m-button--block:hover:not(:disabled){background-color:#0f98c6}.m-button--block:active{background-color:#0a617f}.m-button--inline{display:inline}.m-button--text{color:#0b7497;text-decoration:underline}.m-button--text:focus,.m-button--text:hover{color:#0b7497;text-decoration:none}.m-button--expanded{display:inline-block;padding:0 1rem;line-height:2.625rem;white-space:nowrap;display:block;width:100%;line-height:inherit}.m-button--expanded:hover{color:#505050}.m-button--expanded:hover:not(:disabled){background-color:#0f98c6}.m-button--expanded:active{background-color:#0a617f}.m-button--plain,.m-button--plain.m-button--primary{background-color:#0f98c6;color:#fff}.m-button--plain.m-button--primary{border-color:transparent}.m-button--plain.m-button--primary:focus,.m-button--plain.m-button--primary:hover{background-color:#084c63;color:#fff}.m-button--plain.m-button--secondary{border-color:transparent;background-color:#13ba95;color:#fff}.m-button--plain.m-button--secondary:focus,.m-button--plain.m-button--secondary:hover{background-color:#0a5d4b;color:#fff}.m-button--plain.m-button--success{border-color:transparent;background-color:#3adb76;color:#fff}.m-button--plain.m-button--success:focus,.m-button--plain.m-button--success:hover{background-color:#157539;color:#fff}.m-button--plain.m-button--warning{border-color:transparent;background-color:#ffae00;color:#fff}.m-button--plain.m-button--warning:focus,.m-button--plain.m-button--warning:hover{background-color:#805700;color:#fff}.m-button--plain.m-button--alert{border-color:transparent;background-color:#e43737;color:#fff}.m-button--plain.m-button--alert:focus,.m-button--plain.m-button--alert:hover{background-color:#7d1111;color:#fff}.m-button--plain.m-button--disabled{border-color:transparent;background-color:#ccc;color:#fff}.m-button--plain.m-button--disabled:focus,.m-button--plain.m-button--disabled:hover{background-color:#666;color:#fff}.m-button--hollow{border-width:1px;border-style:solid}.m-button--hollow.m-button--alert:focus,.m-button--hollow.m-button--alert:hover,.m-button--hollow.m-button--disabled:focus,.m-button--hollow.m-button--disabled:hover,.m-button--hollow.m-button--primary:focus,.m-button--hollow.m-button--primary:hover,.m-button--hollow.m-button--secondary:focus,.m-button--hollow.m-button--secondary:hover,.m-button--hollow.m-button--success:focus,.m-button--hollow.m-button--success:hover,.m-button--hollow.m-button--warning:focus,.m-button--hollow.m-button--warning:hover{background-color:transparent}.m-button--active{color:#0f98c6;cursor:default;background-color:#0b7497}@media screen and (max-width:23.6875em){.m-button__text{max-width:3.9375rem;overflow:hidden;text-overflow:ellipsis;font-size:.875rem;white-space:nowrap}.m-button--icon-only .m-button__text,.m-button--text-only .m-button__icon{display:none}}.m-action-bar-wrapper{background-color:#fff}.m-action-bar-wrapper--sticky{position:fixed;top:3.125rem;right:0;left:0;z-index:1}.m-brand{display:inline-block;max-width:11rem;max-height:2rem}.m-brand path{fill:#0f98c6}.m-brand--low path{fill:#000}.m-brand--responsive .m-brand__brand-name{display:none}@media print,screen and (min-width:40em){.m-brand--responsive .m-brand__brand-name{display:inline-block}}.m-brand__brand-name,.m-brand__logo{height:100%}.m-brand__brand-name{margin-left:.4rem}@media print,screen and (min-width:40em){.m-brand{width:100%}}.m-link{transition:background-color .1s ease-out,color .1s ease-out;border-color:#0f98c6;color:#0f98c6;font-weight:700;text-decoration:none;cursor:pointer}.m-link:disabled{cursor:default}.m-link:disabled:hover,.m-link:hover{color:#505050}.m-link:active{color:#fff}.m-link--primary{border-color:#0f98c6;color:#0f98c6}.m-link--primary-plain:not(:disabled),.m-link--primary:focus:not(:disabled),.m-link--primary:hover:not(:disabled){background-color:#084c63;color:#505050}.m-link--secondary{border-color:#13ba95;color:#13ba95}.m-link--secondary-plain:not(:disabled),.m-link--secondary:focus:not(:disabled),.m-link--secondary:hover:not(:disabled){background-color:#0a5d4b;color:#505050}.m-link--success{border-color:#3adb76;color:#3adb76}.m-link--success-plain:not(:disabled),.m-link--success:focus:not(:disabled),.m-link--success:hover:not(:disabled){background-color:#157539;color:#505050}.m-link--warning{border-color:#ffae00;color:#ffae00}.m-link--warning-plain:not(:disabled),.m-link--warning:focus:not(:disabled),.m-link--warning:hover:not(:disabled){background-color:#805700;color:#505050}.m-link--alert{border-color:#e43737;color:#e43737}.m-link--alert-plain:not(:disabled),.m-link--alert:focus:not(:disabled),.m-link--alert:hover:not(:disabled){background-color:#7d1111;color:#505050}.m-link--disabled{border-color:#ccc;color:#ccc}.m-link--disabled-plain:not(:disabled),.m-link--disabled:focus:not(:disabled),.m-link--disabled:hover:not(:disabled){background-color:#666;color:#505050}.m-link--text{color:#0b7497;text-decoration:underline}.m-link--text:focus,.m-link--text:hover{color:#0b7497;text-decoration:none}.m-link--button{display:inline-block;padding:0 1rem;line-height:2.625rem;white-space:nowrap}.m-link--button:hover{color:#505050}.m-link--button:hover:not(:disabled){background-color:#0f98c6}.m-link--button:active{background-color:#0a617f}.m-link--plain{background-color:#0f98c6;color:#fff}.m-link--hollow{border-width:1px;border-style:solid}.m-link--active{color:#0f98c6;cursor:default}.m-link--expanded{display:block;width:100%;line-height:inherit;height:100%}.m-app-loader{display:flex;align-items:center;justify-content:center;height:100vh}.m-app-loader__brand{height:42px}.m-app-loader__header{padding-bottom:36px;text-align:center}.m-app-loader__content{border-top:8px solid #0f98c6;text-align:center}.m-badge{display:inline-flex;align-items:center;height:1.6rem;background:#0b7497;color:#fff;font-size:.8rem;font-weight:600;line-height:1.6rem;white-space:nowrap}.m-badge--right-spaced{margin-right:.375rem}.m-badge--is-link .m-badge__text{color:#fff;text-decoration:none}.m-badge__text{flex:1 1 0px;padding:.4rem}.m-badge__text--large{padding:.8125rem}.m-badge__text--has-button{padding-right:0}.m-badge__button{flex:0 0 auto;padding:.4rem;color:#fff;font-size:1rem}.m-badge__button--large{padding:.8125rem}.m-badge__button:hover{color:#0f98c6}.m-badge--no-radius{border-radius:0}.m-badge--normal-radius{border-radius:.2rem}.m-badge--rounded-radius{padding-right:.8rem;padding-left:.8rem;border-radius:.8rem}.m-badge--low{background:#0a617f;color:#fff}.m-badge--large{height:2.625rem;font-size:1rem;line-height:2.625rem}.m-badge--primary{background-color:#0f98c6;color:#fff}.m-badge--secondary{background-color:#13ba95;color:#fff}.m-badge--success{background-color:#3adb76;color:#fff}.m-badge--warning{background-color:#ffae00;color:#fff}.m-badge--alert{background-color:#e43737;color:#fff}.m-badge--disabled{background-color:#ccc;color:#fff}.m-callout{margin-bottom:.75rem;padding:.75rem;border:1px solid #b3b3b3;border-radius:.2rem;background-color:#fff;color:#2b2b2b}.m-callout--success{border-color:#7cf732;background-color:#abfa7b}.m-callout--info{border-color:#8fd8eb;background-color:#d0eff6}.m-callout--warning{border-color:#ffc852;background-color:#ffe09f}.m-callout--alert{border-color:#cd6b6b;background-color:#e0a4a4}.m-block-list{list-style-type:none;margin:0;padding:0}.m-block-list__item{border-top:1px solid #f9f9f9}.m-modal{position:absolute;top:50%;right:auto;bottom:auto;left:50%;margin-right:-50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);background:#fff}.m-modal__content{padding:.9375rem}.m-modal__header{max-width:expanded;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;border-bottom:1px solid #bbe8fd}.m-modal__title{flex:0 0 auto;margin:0;padding:.625rem .9375rem;color:#505050;font-weight:700}@media print,screen and (min-width:40em){.m-modal__title{padding-right:.75rem;padding-left:.75rem}}.m-modal__close{flex:0 0 auto;margin-left:auto;padding:0 .9375rem;background:none}.m-modal__close:hover{background:none}.m-modal__overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:20;background-color:rgba(0,0,0,.75)}.m-confirm{display:inline-block}.m-confirm__actions{padding-top:.5rem}.m-def-list:after,.m-def-list:before{display:table;content:\" \"}.m-def-list:after{clear:both}.m-def-list__title{margin-bottom:.625rem}.m-def-list__def{margin-bottom:1.5625rem;padding:.25rem .625rem;border:1px solid #737373;color:#444}.m-dropdown{position:absolute;visibility:hidden;overflow-y:auto}.m-dropdown--is-open{z-index:1;visibility:visible}.m-dropdown--display-first-layer{z-index:30!important}.m-dropdown--is-menu{background-color:#fff;box-shadow:0 1px .5rem rgba(0,0,0,.2)}.m-dropdown--has-triangle:before{display:block;content:\"\";position:absolute;width:0;height:0;bottom:100%;border:1rem solid transparent;border-bottom-color:#0a617f;right:1rem}.m-dropdown-menu{background-color:#fff}.m-dropdown-menu--has-triangle:before{display:block;content:\"\";position:absolute;width:0;height:0;bottom:100%;border:1rem solid transparent;border-bottom-color:#0a617f;right:1rem}.m-nav{display:block}.m-nav-list{max-width:1200px;display:flex;flex-flow:row wrap;list-style:none;margin:0;padding:0}.m-nav-list__item{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;line-height:1.4rem}@media print,screen and (min-width:40em){.m-nav-list__item{padding-right:.75rem;padding-left:.75rem}}.m-nav-list__item--large{line-height:2.625rem}.m-nav-list__item--active{font-weight:600}.m-nav-list--vertical{flex-direction:column}.m-nav-list--vertical .m-nav-list__item{flex:0 0 100%;max-width:100%;margin-bottom:.625rem;padding-right:0;padding-left:0}.m-menu-bar{background:#fff}.m-menu-bar__navlist{padding-left:1.5rem}.m-placeholder-block{height:2.625rem;border-radius:.2rem;background-color:#e6e6e6}.m-placeholder-block--line{height:.75rem;border-radius:.375rem}.m-placeholder-block--round{width:2.625rem;border-radius:50%}.m-placeholder-block--square{display:inline-block;min-width:2.625rem}.m-placeholder-block--rect{min-width:10.5rem}.m-placeholder-block--avatar{width:2.5rem;height:2.5rem;font-size:1.25rem;font-weight:600;line-height:2.5rem;border-radius:50%}.m-placeholder-block--avatar__letter:before{font-size:inherit;line-height:inherit}.m-placeholder-block--avatar.m-placeholder-block--large{width:4rem;height:4rem;font-size:2rem;font-weight:600;line-height:4rem}.m-placeholder-block--avatar.m-placeholder-block--large__letter:before{font-size:inherit;line-height:inherit}.m-placeholder-block--avatar.m-placeholder-block--small{width:1.75rem;height:1.75rem;font-size:.875rem;font-weight:600;line-height:1.75rem}.m-placeholder-block--avatar.m-placeholder-block--small__letter:before{font-size:inherit;line-height:inherit}.m-placeholder-block--inline-block{display:inline-block;min-width:5rem}.m-placeholder-block--inline-block:not():first-of-type{margin-left:.625rem}.m-placeholder-block--w-small{min-width:3rem}.m-placeholder-block--w-large{min-width:10rem}.m-placeholder-block--w-xlarge{min-width:30rem}.m-title{display:block}.m-title__text{display:inline-block;color:#0f98c6;font-size:1rem;font-weight:700;line-height:1rem}.m-title--caps{text-transform:capitalize}.m-title--hr{max-width:1200px;margin-right:auto;display:flex;flex-flow:row wrap;align-items:center;margin-left:0}.m-title--hr .m-title__text{flex:0 0 auto;margin-right:1rem}.m-title--hr:after{flex:1 1 0px;display:block;content:\"\";border-top:2px solid #8dceec}.m-title--large .m-title__text{font-size:1.25rem;line-height:1.25rem}.m-section{padding:.9375rem}.m-section--plain{background-color:#edf9fe}.m-section--separator{border-bottom:1px solid #bbe8fd}.m-section--border-disabled{border-left:5px solid #c2c2c2}.m-section--border-safe{border-left:5px solid #07b798}.m-section--border-public{border-left:5px solid #ebc064}.m-section--border-unsecure{border-left:5px solid #ec0f48}.m-section__header{margin-bottom:.9375rem}.m-sidebar-layout{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;background:#bbe8fd}.m-sidebar-layout__left{flex:0 0 100%;max-width:100%;background-color:#e8f5fb}.m-sidebar-layout__panel{flex:0 0 100%;max-width:100%;background-color:#fff}@media print,screen and (min-width:40em){.m-sidebar-layout__left{flex:0 0 auto}.m-sidebar-layout__panel{flex:1 1 0px}}.m-subtitle{display:block;line-height:2.625rem}.m-subtitle__text{display:inline;color:#0a617f;font-size:1rem;font-weight:600}.m-subtitle--hr{border-color:#b3b3b3;border-style:solid;border-width:1px 0}.m-text-block{display:block;overflow:hidden;text-overflow:ellipsis}.m-text-block--nowrap{white-space:nowrap}.m-text-block--inline{display:inline-block}.m-text-block--small{font-size:.75rem;line-height:.75rem}.m-text-block--strong{font-weight:700}.m-text-list{list-style:none;margin:0;padding:0}.m-text-list__item{color:#2b2b2b;font-size:1rem;line-height:1.4rem}.m-text-list__item--large{line-height:2.625rem}.m-vertical-menu{list-style-type:none;margin:0;padding:0}.m-vertical-menu__item--is-active{color:#505050}.m-vertical-menu__item-content{display:block;padding:0 1rem;white-space:nowrap}.m-vertical-menu__separator{background-color:#b3b3b3}.m-vertical-menu__item{min-height:2.25rem}.m-vertical-menu__item-content{line-height:2.25rem}.m-vertical-menu--small__item{min-height:1.5rem}.m-vertical-menu--small__item-content{line-height:1.5rem}.m-vertical-menu--medium__item{min-height:2.25rem}.m-vertical-menu--medium__item-content{line-height:2.25rem}.m-vertical-menu--large__item{min-height:3rem}.m-vertical-menu--large__item-content{line-height:3rem}.m-vertical-menu__separator{height:1px;margin:.375rem 0}.m-label{color:#0f98c6}.m-field-errors{list-style:none;padding-top:.25rem;color:#e43737;font-size:.75rem;font-weight:700}.m-advanced-select-field-group__label{display:block;margin-bottom:.5rem}.m-advanced-select-field-group__input{display:inline-block;min-height:2.625rem;padding:.5rem;border:0;background-color:transparent;transition:box-shadow .15s,border-color .15s ease-in-out;min-width:5rem}.m-advanced-select-field-group__input--decorated{border:1px solid #0f98c6;background-color:#fff;color:#444}.m-advanced-select-field-group__input--decorated:focus{border-color:#cfeaf7}.m-advanced-select-field-group__input--decorated:disabled{background:transparent;color:#0b7497}.m-advanced-select-field-group__input--decorated::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-advanced-select-field-group__input--decorated::-moz-placeholder{color:rgba(80,80,80,.8)}.m-advanced-select-field-group__input--decorated:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-advanced-select-field-group__input--decorated:-moz-placeholder{color:rgba(80,80,80,.8)}.m-advanced-select-field-group__select-wrapper{display:inline-block;position:relative}.m-advanced-select-field-group__select-wrapper,.m-advanced-select-field-group__select-wrapper>*{cursor:pointer}.m-advanced-select-field-group__select-wrapper:after{content:\"\";position:absolute;top:50%;right:7px;width:0;margin-top:-3px;border-color:#919191 transparent transparent;border-style:solid;border-width:6px 4px;pointer-events:none;right:auto}.m-advanced-select-field-group__option-button{height:2.625rem;text-align:left}.m-advanced-select-field-group--dark__select,.m-advanced-select-field-group--dark__select-wrapper{background-color:#262626;color:#444}.m-advanced-select-field-group--dark__select-wrapper::-webkit-input-placeholder,.m-advanced-select-field-group--dark__select::-webkit-input-placeholder{color:hsla(0,0%,100%,.8)}.m-advanced-select-field-group--dark__select-wrapper::-moz-placeholder,.m-advanced-select-field-group--dark__select::-moz-placeholder{color:hsla(0,0%,100%,.8)}.m-advanced-select-field-group--dark__select-wrapper:-ms-input-placeholder,.m-advanced-select-field-group--dark__select:-ms-input-placeholder{color:hsla(0,0%,100%,.8)}.m-advanced-select-field-group--dark__select-wrapper:-moz-placeholder,.m-advanced-select-field-group--dark__select:-moz-placeholder{color:hsla(0,0%,100%,.8)}.m-advanced-select-field-group--dark__select-wrapper:focus,.m-advanced-select-field-group--dark__select:focus{background-color:#262626;color:#fff}.m-advanced-select-field-group--dark__select-wrapper:disabled,.m-advanced-select-field-group--dark__select:disabled{background:#404040}.m-advanced-select-field-group--inline__label,.m-advanced-select-field-group--inline__select-wrapper{display:inline-block}.m-checkbox{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;align-items:center}.m-checkbox__label{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;cursor:pointer}@media print,screen and (min-width:40em){.m-checkbox__label{padding-right:.75rem;padding-left:.75rem}}.m-checkbox__input{flex:0 0 auto;width:14px;height:14px;border:1px solid #0f98c6;background:#fff;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none}.m-checkbox__input:before{display:none;content:\" \";position:absolute;right:1px;left:1px;background:#0b7497}.m-checkbox__input:checked{position:relative}.m-checkbox__input:checked:before{display:block;top:1px;bottom:1px}.m-checkbox__input:indeterminate{position:relative}.m-checkbox__input:indeterminate:before{display:block;top:4px;bottom:4px}.m-checkbox__input:disabled{position:relative;border:1px solid #d9d9d9;background:#f3f3f3;cursor:default}.m-checkbox__input:disabled:before{background:#d9d9d9}.m-switch{position:relative;outline:0;font-size:.875rem;font-weight:700;color:#fefefe;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:inline-block;vertical-align:middle;height:2rem;margin-bottom:0}.m-switch__input{position:absolute;margin-bottom:0;opacity:0}.m-switch__paddle{position:relative;display:block;width:4rem;height:2rem;border-radius:1rem;background:#cacaca;transition:all .25s ease-out;font-weight:inherit;color:inherit;cursor:pointer;background-color:#e6e6e6}input+.m-switch__paddle{margin:0}.m-switch__paddle:after{position:absolute;top:.25rem;left:.25rem;display:block;width:1.5rem;height:1.5rem;-webkit-transform:translateZ(0);transform:translateZ(0);border-radius:1rem;background:#fefefe;transition:all .25s ease-out;content:\"\"}input:checked~.m-switch__paddle{background:#0f98c6}input:checked~.m-switch__paddle:after{left:2.25rem}[data-whatinput=mouse] input:focus~.m-switch__paddle{outline:0}.m-checkbox-field-group__label{padding-left:.625rem}.m-delete-field-group{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-delete-field-group__input{flex:1 1 0px}.m-delete-field-group__button{flex:0 0 auto}.m-add-field-group{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-add-field-group__input{flex:1 1 0px}.m-add-field-group__button{flex:0 0 auto}.m-collection-field-group__delete-group{margin-top:.625rem}.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow,.react-datepicker__year-read-view--down-arrow{margin-left:-8px;position:absolute}.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle:before,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle:before,.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-read-view--down-arrow:before,.react-datepicker__month-year-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow:before,.react-datepicker__year-read-view--down-arrow,.react-datepicker__year-read-view--down-arrow:before{box-sizing:content-box;position:absolute;border:8px solid transparent;height:0;width:1px}.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle:before,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle:before,.react-datepicker__month-read-view--down-arrow:before,.react-datepicker__month-year-read-view--down-arrow:before,.react-datepicker__year-read-view--down-arrow:before{content:\"\";z-index:-1;border-width:8px;left:-8px;border-bottom-color:#aeaeae}.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle{top:0;margin-top:-8px}.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle:before{border-top:none;border-bottom-color:#f0f0f0}.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle:before{top:-1px;border-bottom-color:#aeaeae}.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow,.react-datepicker__year-read-view--down-arrow{bottom:0;margin-bottom:-8px}.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle:before,.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-read-view--down-arrow:before,.react-datepicker__month-year-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow:before,.react-datepicker__year-read-view--down-arrow,.react-datepicker__year-read-view--down-arrow:before{border-bottom:none;border-top-color:#fff}.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle:before,.react-datepicker__month-read-view--down-arrow:before,.react-datepicker__month-year-read-view--down-arrow:before,.react-datepicker__year-read-view--down-arrow:before{bottom:-1px;border-top-color:#aeaeae}.react-datepicker,.react-datepicker-wrapper{display:inline-block}.react-datepicker{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:.8rem;background-color:#fff;color:#000;border:1px solid #aeaeae;border-radius:.3rem;position:relative}.react-datepicker--time-only .react-datepicker__triangle{left:35px}.react-datepicker--time-only .react-datepicker__time-container{border-left:0}.react-datepicker--time-only .react-datepicker__time,.react-datepicker--time-only .react-datepicker__time-box{border-radius:.3rem}.react-datepicker__triangle{position:absolute;left:50px}.react-datepicker-popper{z-index:1}.react-datepicker-popper[data-placement^=bottom]{margin-top:10px}.react-datepicker-popper[data-placement^=top]{margin-bottom:10px}.react-datepicker-popper[data-placement^=right]{margin-left:8px}.react-datepicker-popper[data-placement^=right] .react-datepicker__triangle{left:auto;right:42px}.react-datepicker-popper[data-placement^=left]{margin-right:8px}.react-datepicker-popper[data-placement^=left] .react-datepicker__triangle{left:42px;right:auto}.react-datepicker__header{text-align:center;background-color:#f0f0f0;border-bottom:1px solid #aeaeae;border-top-left-radius:.3rem;border-top-right-radius:.3rem;padding-top:8px;position:relative}.react-datepicker__header--time{padding-bottom:8px;padding-left:5px;padding-right:5px}.react-datepicker__month-dropdown-container--scroll,.react-datepicker__month-dropdown-container--select,.react-datepicker__month-year-dropdown-container--scroll,.react-datepicker__month-year-dropdown-container--select,.react-datepicker__year-dropdown-container--scroll,.react-datepicker__year-dropdown-container--select{display:inline-block;margin:0 2px}.react-datepicker-time__header,.react-datepicker-year-header,.react-datepicker__current-month{margin-top:0;color:#000;font-weight:700;font-size:.944rem}.react-datepicker-time__header{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.react-datepicker__navigation{background:none;line-height:1.7rem;text-align:center;cursor:pointer;position:absolute;top:10px;width:0;padding:0;border:.45rem solid transparent;z-index:1;height:10px;width:10px;text-indent:-999em;overflow:hidden}.react-datepicker__navigation--previous{left:10px;border-right-color:#ccc}.react-datepicker__navigation--previous:hover{border-right-color:#b3b3b3}.react-datepicker__navigation--previous--disabled,.react-datepicker__navigation--previous--disabled:hover{border-right-color:#e6e6e6;cursor:default}.react-datepicker__navigation--next{right:10px;border-left-color:#ccc}.react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button){right:80px}.react-datepicker__navigation--next:hover{border-left-color:#b3b3b3}.react-datepicker__navigation--next--disabled,.react-datepicker__navigation--next--disabled:hover{border-left-color:#e6e6e6;cursor:default}.react-datepicker__navigation--years{position:relative;top:0;display:block;margin-left:auto;margin-right:auto}.react-datepicker__navigation--years-previous{top:4px;border-top-color:#ccc}.react-datepicker__navigation--years-previous:hover{border-top-color:#b3b3b3}.react-datepicker__navigation--years-upcoming{top:-4px;border-bottom-color:#ccc}.react-datepicker__navigation--years-upcoming:hover{border-bottom-color:#b3b3b3}.react-datepicker__month-container{float:left}.react-datepicker__month{margin:.4rem;text-align:center}.react-datepicker__month .react-datepicker__month-text{display:inline-block;width:4rem;margin:2px}.react-datepicker__input-time-container{clear:both;width:100%;float:left;margin:5px 0 10px 15px;text-align:left}.react-datepicker__input-time-container .react-datepicker-time__caption,.react-datepicker__input-time-container .react-datepicker-time__input-container{display:inline-block}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input{display:inline-block;margin-left:10px}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input{width:85px}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-inner-spin-button,.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]{-moz-appearance:textfield}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__delimiter{margin-left:5px;display:inline-block}.react-datepicker__time-container{float:right;border-left:1px solid #aeaeae;width:70px}.react-datepicker__time-container--with-today-button{display:inline;border:1px solid #aeaeae;border-radius:.3rem;position:absolute;right:-72px;top:0}.react-datepicker__time-container .react-datepicker__time{position:relative;background:#fff}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box{width:70px;overflow-x:hidden;margin:0 auto;text-align:center}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list{list-style:none;margin:0;height:calc(195px + .85rem);overflow-y:scroll;padding-right:0;padding-left:0;width:100%;box-sizing:content-box}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item{height:30px;padding:5px 10px}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover{cursor:pointer;background-color:#f0f0f0}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected{background-color:#216ba5;color:#fff;font-weight:700}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected:hover{background-color:#216ba5}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled{color:#ccc}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled:hover{cursor:default;background-color:transparent}.react-datepicker__week-number{color:#ccc;display:inline-block;width:1.7rem;line-height:1.7rem;text-align:center;margin:.166rem}.react-datepicker__week-number.react-datepicker__week-number--clickable{cursor:pointer}.react-datepicker__week-number.react-datepicker__week-number--clickable:hover{border-radius:.3rem;background-color:#f0f0f0}.react-datepicker__day-names,.react-datepicker__week{white-space:nowrap}.react-datepicker__day,.react-datepicker__day-name,.react-datepicker__time-name{color:#000;display:inline-block;width:1.7rem;line-height:1.7rem;text-align:center;margin:.166rem}.react-datepicker__month--in-range,.react-datepicker__month--in-selecting-range,.react-datepicker__month--selected{border-radius:.3rem;background-color:#216ba5;color:#fff}.react-datepicker__month--in-range:hover,.react-datepicker__month--in-selecting-range:hover,.react-datepicker__month--selected:hover{background-color:#1d5d90}.react-datepicker__month--disabled{color:#ccc;pointer-events:none}.react-datepicker__month--disabled:hover{cursor:default;background-color:transparent}.react-datepicker__day,.react-datepicker__month-text{cursor:pointer}.react-datepicker__day:hover,.react-datepicker__month-text:hover{border-radius:.3rem;background-color:#f0f0f0}.react-datepicker__day--today,.react-datepicker__month-text--today{font-weight:700}.react-datepicker__day--highlighted,.react-datepicker__month-text--highlighted{border-radius:.3rem;background-color:#3dcc4a;color:#fff}.react-datepicker__day--highlighted:hover,.react-datepicker__month-text--highlighted:hover{background-color:#32be3f}.react-datepicker__day--highlighted-custom-1,.react-datepicker__month-text--highlighted-custom-1{color:#f0f}.react-datepicker__day--highlighted-custom-2,.react-datepicker__month-text--highlighted-custom-2{color:green}.react-datepicker__day--in-range,.react-datepicker__day--in-selecting-range,.react-datepicker__day--selected,.react-datepicker__month-text--in-range,.react-datepicker__month-text--in-selecting-range,.react-datepicker__month-text--selected{border-radius:.3rem;background-color:#216ba5;color:#fff}.react-datepicker__day--in-range:hover,.react-datepicker__day--in-selecting-range:hover,.react-datepicker__day--selected:hover,.react-datepicker__month-text--in-range:hover,.react-datepicker__month-text--in-selecting-range:hover,.react-datepicker__month-text--selected:hover{background-color:#1d5d90}.react-datepicker__day--keyboard-selected,.react-datepicker__month-text--keyboard-selected{border-radius:.3rem;background-color:#2a87d0;color:#fff}.react-datepicker__day--keyboard-selected:hover,.react-datepicker__month-text--keyboard-selected:hover{background-color:#1d5d90}.react-datepicker__day--in-selecting-range,.react-datepicker__month-text--in-selecting-range{background-color:rgba(33,107,165,.5)}.react-datepicker__month--selecting-range .react-datepicker__day--in-range,.react-datepicker__month--selecting-range .react-datepicker__month-text--in-range{background-color:#f0f0f0;color:#000}.react-datepicker__day--disabled,.react-datepicker__month-text--disabled{cursor:default;color:#ccc}.react-datepicker__day--disabled:hover,.react-datepicker__month-text--disabled:hover{background-color:transparent}.react-datepicker__month-text.react-datepicker__month--in-range:hover,.react-datepicker__month-text.react-datepicker__month--selected:hover{background-color:#216ba5}.react-datepicker__month-text:hover{background-color:#f0f0f0}.react-datepicker__input-container{position:relative;display:inline-block}.react-datepicker__month-read-view,.react-datepicker__month-year-read-view,.react-datepicker__year-read-view{border:1px solid transparent;border-radius:.3rem}.react-datepicker__month-read-view:hover,.react-datepicker__month-year-read-view:hover,.react-datepicker__year-read-view:hover{cursor:pointer}.react-datepicker__month-read-view:hover .react-datepicker__month-read-view--down-arrow,.react-datepicker__month-read-view:hover .react-datepicker__year-read-view--down-arrow,.react-datepicker__month-year-read-view:hover .react-datepicker__month-read-view--down-arrow,.react-datepicker__month-year-read-view:hover .react-datepicker__year-read-view--down-arrow,.react-datepicker__year-read-view:hover .react-datepicker__month-read-view--down-arrow,.react-datepicker__year-read-view:hover .react-datepicker__year-read-view--down-arrow{border-top-color:#b3b3b3}.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow,.react-datepicker__year-read-view--down-arrow{border-top-color:#ccc;float:right;margin-left:20px;top:8px;position:relative;border-width:.45rem}.react-datepicker__month-dropdown,.react-datepicker__month-year-dropdown,.react-datepicker__year-dropdown{background-color:#f0f0f0;position:absolute;width:50%;left:25%;top:30px;z-index:1;text-align:center;border-radius:.3rem;border:1px solid #aeaeae}.react-datepicker__month-dropdown:hover,.react-datepicker__month-year-dropdown:hover,.react-datepicker__year-dropdown:hover{cursor:pointer}.react-datepicker__month-dropdown--scrollable,.react-datepicker__month-year-dropdown--scrollable,.react-datepicker__year-dropdown--scrollable{height:150px;overflow-y:scroll}.react-datepicker__month-option,.react-datepicker__month-year-option,.react-datepicker__year-option{line-height:20px;width:100%;display:block;margin-left:auto;margin-right:auto}.react-datepicker__month-option:first-of-type,.react-datepicker__month-year-option:first-of-type,.react-datepicker__year-option:first-of-type{border-top-left-radius:.3rem;border-top-right-radius:.3rem}.react-datepicker__month-option:last-of-type,.react-datepicker__month-year-option:last-of-type,.react-datepicker__year-option:last-of-type{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-bottom-left-radius:.3rem;border-bottom-right-radius:.3rem}.react-datepicker__month-option:hover,.react-datepicker__month-year-option:hover,.react-datepicker__year-option:hover{background-color:#ccc}.react-datepicker__month-option:hover .react-datepicker__navigation--years-upcoming,.react-datepicker__month-year-option:hover .react-datepicker__navigation--years-upcoming,.react-datepicker__year-option:hover .react-datepicker__navigation--years-upcoming{border-bottom-color:#b3b3b3}.react-datepicker__month-option:hover .react-datepicker__navigation--years-previous,.react-datepicker__month-year-option:hover .react-datepicker__navigation--years-previous,.react-datepicker__year-option:hover .react-datepicker__navigation--years-previous{border-top-color:#b3b3b3}.react-datepicker__month-option--selected,.react-datepicker__month-year-option--selected,.react-datepicker__year-option--selected{position:absolute;left:15px}.react-datepicker__close-icon{background-color:transparent;border:0;cursor:pointer;outline:0;padding:0;vertical-align:middle;position:absolute;height:16px;width:16px;top:25%;right:7px}.react-datepicker__close-icon:after{background-color:#216ba5;border-radius:50%;bottom:0;box-sizing:border-box;color:#fff;content:\"\\00d7\";cursor:pointer;font-size:12px;height:16px;width:16px;line-height:1;margin:-8px auto 0;padding:2px;position:absolute;right:0;text-align:center}.react-datepicker__today-button{background:#f0f0f0;border-top:1px solid #aeaeae;cursor:pointer;text-align:center;font-weight:700;padding:5px 0;clear:left}.react-datepicker__portal{position:fixed;width:100vw;height:100vh;background-color:rgba(0,0,0,.8);left:0;top:0;justify-content:center;align-items:center;display:flex;z-index:2147483647}.react-datepicker__portal .react-datepicker__day,.react-datepicker__portal .react-datepicker__day-name,.react-datepicker__portal .react-datepicker__time-name{width:3rem;line-height:3rem}@media (max-height:550px),(max-width:400px){.react-datepicker__portal .react-datepicker__day,.react-datepicker__portal .react-datepicker__day-name,.react-datepicker__portal .react-datepicker__time-name{width:2rem;line-height:2rem}}.react-datepicker__portal .react-datepicker-time__header,.react-datepicker__portal .react-datepicker__current-month{font-size:1.44rem}.react-datepicker__portal .react-datepicker__navigation{border:.81rem solid transparent}.react-datepicker__portal .react-datepicker__navigation--previous{border-right-color:#ccc}.react-datepicker__portal .react-datepicker__navigation--previous:hover{border-right-color:#b3b3b3}.react-datepicker__portal .react-datepicker__navigation--previous--disabled,.react-datepicker__portal .react-datepicker__navigation--previous--disabled:hover{border-right-color:#e6e6e6;cursor:default}.react-datepicker__portal .react-datepicker__navigation--next{border-left-color:#ccc}.react-datepicker__portal .react-datepicker__navigation--next:hover{border-left-color:#b3b3b3}.react-datepicker__portal .react-datepicker__navigation--next--disabled,.react-datepicker__portal .react-datepicker__navigation--next--disabled:hover{border-left-color:#e6e6e6;cursor:default}.m-date-picker-group .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle,.m-date-picker-group .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle:before{border-bottom-color:#fff}.m-date-picker-group .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.m-date-picker-group .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle:before{border-top-color:#f3f3f3}.m-date-picker-group .react-datepicker-wrapper,.m-date-picker-group .react-datepicker__input-container{width:100%}.m-date-picker-group__label{display:block}.m-date-picker-group__input{display:inline-block;min-height:2.625rem;padding:.5rem;background-color:transparent;transition:box-shadow .15s,border-color .15s ease-in-out;border:1px solid #0f98c6;background-color:#fff;color:#444}.m-date-picker-group__input:focus{border-color:#cfeaf7}.m-date-picker-group__input:disabled{background:transparent;color:#0b7497}.m-date-picker-group__input::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-date-picker-group__input::-moz-placeholder{color:rgba(80,80,80,.8)}.m-date-picker-group__input:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-date-picker-group__input:-moz-placeholder{color:rgba(80,80,80,.8)}.m-date-picker-group__calendar{border:0;border-radius:0;background-color:#f3f3f3;box-shadow:0 -1px .8rem rgba(0,0,0,.2);color:#444;font-family:inherit;font-size:.875rem}.m-date-picker-group__calendar *{border-radius:0;color:#444;font-family:inherit;font-size:.875rem}.m-date-picker-group__calendar .react-datepicker__navigation--previous{border-right-color:#444}.m-date-picker-group__calendar .react-datepicker__navigation--next{border-left-color:#444}.m-date-picker-group__calendar .react-datepicker__header{border:0;background-color:#fff}.m-date-picker-group__calendar .react-datepicker__header *{background-color:#fff}.m-date-picker-group__calendar .react-datepicker__header__dropdown{margin-top:.5rem}.m-date-picker-group__calendar .react-datepicker__header__dropdown select{padding:.25rem;border:0;background:#f3f3f3}.m-date-picker-group__calendar .react-datepicker__day{border-radius:0}.m-date-picker-group__calendar .react-datepicker__day--today{background:#fff}.m-date-picker-group__calendar .react-datepicker__day--selected,.m-date-picker-group__calendar .react-datepicker__day:hover{border-radius:0;background:#0f98c6}.m-date-picker-group__calendar .react-datepicker__day-name{margin-top:.5rem;color:#111;font-size:.75rem}.m-fieldset__legend{margin-bottom:.625rem}.m-form-grid__row{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-form-grid__row--reverse{flex-direction:row-reverse}.m-form-grid__column{flex:0 0 100%;max-width:100%;display:block}.m-form-grid__column--align-center{text-align:center}.m-form-grid__column--fluid{flex:1 1 0px;padding-right:.25rem}.m-form-grid__column--bottom-space{margin-bottom:.9375rem}@media print,screen and (min-width:40em){.m-form-grid__column--right-space{padding-right:.9375rem}.m-form-grid__column--align-right{text-align:right}.m-form-grid__column--small{flex:0 0 33.33333%;max-width:33.33333%}.m-form-grid__column--medium{flex:0 0 50%;max-width:50%}.m-form-grid__column--large{flex:0 0 66.66667%;max-width:66.66667%}.m-form-grid__column--shrink{flex:0 0 auto}}.m-input-file__input{display:block;position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:999px;text-align:right;cursor:inherit;opacity:0;filter:alpha(opacity=0)}.m-input-file__label{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;border:1px solid #0f98c6;background-color:#fff;color:#444;position:relative;margin-top:1.5rem;overflow:hidden;line-height:2.625rem;cursor:pointer}.m-input-file__label:focus{border-color:#cfeaf7}.m-input-file__label:disabled{background:transparent;color:#0b7497}.m-input-file__label::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-input-file__label::-moz-placeholder{color:rgba(80,80,80,.8)}.m-input-file__label:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-input-file__label:-moz-placeholder{color:rgba(80,80,80,.8)}.m-input-file__label__button{flex:0 0 auto}.m-input-file__label__text{flex:1 1 0px;padding-right:.625rem;padding-left:.625rem;min-width:0;text-overflow:ellipsis;white-space:nowrap}@media print,screen and (min-width:40em){.m-input-file__label__text{padding-right:.75rem;padding-left:.75rem}}.m-input-file__label__icon{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;margin-left:auto}@media print,screen and (min-width:40em){.m-input-file__label__icon{padding-right:.75rem;padding-left:.75rem}}.m-input-file-group__file{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;border:1px solid #0f98c6;background-color:#fff;color:#444;margin-top:1.5rem;line-height:2.625rem}.m-input-file-group__file:focus{border-color:#cfeaf7}.m-input-file-group__file:disabled{background:transparent;color:#0b7497}.m-input-file-group__file::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-input-file-group__file::-moz-placeholder{color:rgba(80,80,80,.8)}.m-input-file-group__file:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-input-file-group__file:-moz-placeholder{color:rgba(80,80,80,.8)}.m-input-file-group__file__name{flex:0 0 50%;padding-right:.625rem;padding-left:.625rem;max-width:50%;overflow:hidden;text-overflow:ellipsis;font-weight:600;white-space:nowrap}@media print,screen and (min-width:40em){.m-input-file-group__file__name{padding-right:.75rem;padding-left:.75rem}}.m-input-file-group__file__size{flex:0 0 33.33333%;padding-right:.625rem;padding-left:.625rem;max-width:33.33333%;overflow:hidden;text-overflow:ellipsis;color:#2b2b2b;text-align:right;white-space:nowrap}@media print,screen and (min-width:40em){.m-input-file-group__file__size{padding-right:.75rem;padding-left:.75rem}}.m-input-file-group__file__remove{flex:0 0 auto}.m-input-text{display:inline-block;min-height:2.625rem;padding:.5rem;border:0;background-color:transparent;transition:box-shadow .15s,border-color .15s ease-in-out}.m-input-text--dark{background-color:#262626;color:#444}.m-input-text--dark::-webkit-input-placeholder{color:hsla(0,0%,100%,.8)}.m-input-text--dark::-moz-placeholder{color:hsla(0,0%,100%,.8)}.m-input-text--dark:-ms-input-placeholder{color:hsla(0,0%,100%,.8)}.m-input-text--dark:-moz-placeholder{color:hsla(0,0%,100%,.8)}.m-input-text--dark:focus{background-color:#262626;color:#fff}.m-input-text--dark:disabled{background:#404040}.m-input-text--light{border:1px solid #0f98c6;background-color:#fff;color:#444}.m-input-text--light:focus{border-color:#cfeaf7}.m-input-text--light:disabled{background:transparent;color:#0b7497}.m-input-text--light::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-input-text--light::-moz-placeholder{color:rgba(80,80,80,.8)}.m-input-text--light:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-input-text--light:-moz-placeholder{color:rgba(80,80,80,.8)}.m-input-text--contrasted{border-bottom:2px solid #0f98c6;background-color:#edf9fe;color:#444}.m-input-text--contrasted:focus{border-color:#cfeaf7}.m-input-text--contrasted:disabled{background:transparent;color:#0b7497}.m-input-text--contrasted::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-input-text--contrasted::-moz-placeholder{color:rgba(80,80,80,.8)}.m-input-text--contrasted:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-input-text--contrasted:-moz-placeholder{color:rgba(80,80,80,.8)}.m-input-text--expanded{width:100%}.m-input-text--error{box-shadow:inset 0 0 0 1px #9d3535;color:#444}.m-input-text--error:focus{color:#444}.m-input-text--bottom-space{margin:0 0 .9375rem}.m-password-strength__graph{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-password-strength__icon{flex:0 0 auto;padding-right:7px}.m-password-strength__guide{flex:1 1 0px;height:3px;margin-top:7px;background-color:#e6e6e6}.m-password-strength__bar{height:3px}.m-password-strength--weak{color:#e43737}.m-password-strength--weak .m-password-strength__bar{width:15%;background-color:#e43737}.m-password-strength--moderate{color:#ffae00}.m-password-strength--moderate .m-password-strength__bar{width:50%;background-color:#ffae00}.m-password-strength--strong{color:#3adb76}.m-password-strength--strong .m-password-strength__bar{width:100%;background-color:#3adb76}.m-password-strength__text{flex:0 0 100%;max-width:100%;padding-left:.9375rem;font-size:.75rem;font-weight:700;text-transform:uppercase}.m-radio-field-group__entry{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;align-items:center}.m-radio-field-group__entry>.m-radio-field-group__entry{margin-bottom:.5rem}.m-radio-field-group__label{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;cursor:pointer}@media print,screen and (min-width:40em){.m-radio-field-group__label{padding-right:.75rem;padding-left:.75rem}}.m-radio-field-group__input{flex:0 0 auto;width:14px;height:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:1px solid #0f98c6;border-radius:50%;background:#fff;cursor:pointer}.m-radio-field-group__input:before{display:none;content:\" \";position:absolute;right:1px;left:1px;border-radius:50%;background:#0b7497}.m-radio-field-group__input:checked{position:relative}.m-radio-field-group__input:checked:before{display:block;top:1px;bottom:1px}.m-select-field-group__label{display:block;margin-bottom:.5rem}.m-select-field-group__select-wrapper{display:inline-block;position:relative}.m-select-field-group__select-wrapper,.m-select-field-group__select-wrapper>*{cursor:pointer}.m-select-field-group__select-wrapper:after{content:\"\";position:absolute;top:50%;right:7px;width:0;margin-top:-3px;border-color:#919191 transparent transparent;border-style:solid;border-width:6px 4px;pointer-events:none}.m-select-field-group__select{display:inline-block;min-height:2.625rem;padding:.5rem;border:0;background-color:transparent;transition:box-shadow .15s,border-color .15s ease-in-out;margin-right:10px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.m-select-field-group--dark__select,.m-select-field-group--dark__select-wrapper{background-color:#262626;color:#444}.m-select-field-group--dark__select-wrapper::-webkit-input-placeholder,.m-select-field-group--dark__select::-webkit-input-placeholder{color:hsla(0,0%,100%,.8)}.m-select-field-group--dark__select-wrapper::-moz-placeholder,.m-select-field-group--dark__select::-moz-placeholder{color:hsla(0,0%,100%,.8)}.m-select-field-group--dark__select-wrapper:-ms-input-placeholder,.m-select-field-group--dark__select:-ms-input-placeholder{color:hsla(0,0%,100%,.8)}.m-select-field-group--dark__select-wrapper:-moz-placeholder,.m-select-field-group--dark__select:-moz-placeholder{color:hsla(0,0%,100%,.8)}.m-select-field-group--dark__select-wrapper:focus,.m-select-field-group--dark__select:focus{background-color:#262626;color:#fff}.m-select-field-group--dark__select-wrapper:disabled,.m-select-field-group--dark__select:disabled{background:#404040}.m-select-field-group--light__select,.m-select-field-group__select{border:1px solid #0f98c6;background-color:#fff;color:#444}.m-select-field-group--light__select:focus,.m-select-field-group__select:focus{border-color:#cfeaf7}.m-select-field-group--light__select:disabled,.m-select-field-group__select:disabled{background:transparent;color:#0b7497}.m-select-field-group--light__select::-webkit-input-placeholder,.m-select-field-group__select::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-select-field-group--light__select::-moz-placeholder,.m-select-field-group__select::-moz-placeholder{color:rgba(80,80,80,.8)}.m-select-field-group--light__select:-ms-input-placeholder,.m-select-field-group__select:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-select-field-group--light__select:-moz-placeholder,.m-select-field-group__select:-moz-placeholder{color:rgba(80,80,80,.8)}.m-select-field-group--expanded__select-wrapper{width:100%}.m-text-field-group{position:relative}.m-text-field-group__label{display:block}.m-text-field-group--inline{display:flex;flex-wrap:wrap;justify-content:space-between}.m-text-field-group--inline__errors,.m-text-field-group--inline__input,.m-text-field-group--inline__label{margin:auto}.m-text-field-group--inline__label{flex:0 1;margin-right:.625rem}.m-text-field-group--inline__input{flex:1 0}.m-text-field-group--inline__errors{flex:1 0 100%}.m-textarea{display:inline-block;min-height:2.625rem;padding:.5rem;background-color:transparent;transition:box-shadow .15s,border-color .15s ease-in-out;border:1px solid #0f98c6;background-color:#fff;color:#444;min-height:7.875rem}.m-textarea:focus{border-color:#cfeaf7}.m-textarea:disabled{background:transparent;color:#0b7497}.m-textarea::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-textarea::-moz-placeholder{color:rgba(80,80,80,.8)}.m-textarea:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-textarea:-moz-placeholder{color:rgba(80,80,80,.8)}.m-textarea--expanded{width:100%}.m-textarea-field-group__label{display:block;margin-bottom:.625rem}.m-textarea-field-group__errors{margin-top:.9375rem}.m-user-menu__user-info{margin-top:.5rem}.m-user-menu__button-label{display:none}@media print,screen and (min-width:40em){.m-user-menu__button-label{display:inline}}.m-next-feature-button{background-color:#c3e4de}.s-signin__action{line-height:2.625rem}.s-signin__link{text-align:center}.s-signin__login-safe{background-color:#07b798}.s-signin__login-public{background-color:#ebc064}.s-signin__login-unsecure{background-color:#ec0f48}.s-signup__user{color:#505050;font-size:.875rem;text-align:right}.s-signup__username{color:#2b2b2b}.s-signup__recovery-label{font-size:.875rem}.s-signup__action{line-height:2.625rem}.s-signup__modal{width:100%;max-width:23.75rem;max-height:100%;overflow:auto}.s-signup__privacy{margin-bottom:.75rem;padding:.75rem;border-radius:.2rem;background-color:#fff;color:#2b2b2b;border:1px solid #8fd8eb;background-color:#d0eff6}.s-signup__privacy-checkbox{margin-top:1.5rem}.s-signup__link{text-align:center}.m-forgot-password-form__action{line-height:2.625rem;text-align:center}.m-forgot-password-form__link{text-align:center}.m-forgot-password-form__success{margin-bottom:.75rem;padding:.75rem;border-radius:.2rem;background-color:#fff;color:#2b2b2b;border:1px solid #7cf732;background-color:#abfa7b}.m-reset-password-form__action{line-height:2.625rem;text-align:center}.m-reset-password-form__error,.m-reset-password-form__success{margin-bottom:.75rem;padding:.75rem;border-radius:.2rem;background-color:#fff;color:#2b2b2b;border:1px solid #7cf732;background-color:#abfa7b}.m-provider-icon--normal{height:1rem}.m-tag-item{margin-right:.625rem;margin-bottom:.625rem}.m-tags-search{max-width:expanded;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;margin-bottom:1.5625rem}.m-tags-search__input{flex:1 1 0px}.m-tags-search__button{margin-left:1px;background:#e6e6e6;font-size:.75rem}.m-tags-form{width:100%;max-width:23.75rem}.m-tags-form__count{padding-left:.625rem;color:#111;font-weight:400}.m-tags-form__section{position:relative;margin-bottom:1.5625rem}.m-tags-form__dropdown{margin-top:2.625rem;padding:.625rem;background-color:#fff}.m-pi-graph{display:block;max-width:15rem;margin-right:auto;margin-left:auto;pointer-events:none}.m-pi-graph__line{stroke:#e6e6e6;stroke-width:.7;fill:none}.m-pi-graph__outline{fill:transparent;stroke:#e6e6e6;stroke-width:.7}.m-pi-graph__shape{fill:#97b707;stroke:none}.m-pi-ratings{max-width:17rem;font-size:.75rem;font-weight:600}.m-pi-ratings,.m-pi-ratings__item{margin-right:auto;margin-left:auto}.m-pi-ratings__item{max-width:1200px;display:flex;flex-flow:row wrap;align-items:center;padding-top:.5rem}.m-pi-ratings__item-name{flex:0 0 41.66667%;max-width:41.66667%;padding-right:.5rem;overflow:hidden;text-overflow:ellipsis;text-align:right;text-transform:uppercase}.m-pi-ratings__item-level{flex:0 0 41.66667%;max-width:41.66667%;background:#e6e6e6}.m-pi-ratings__item-level-label{flex:0 0 16.66667%;max-width:16.66667%;text-align:center}.m-pi-ratings__item-level-bar{height:.875rem;transition:width .5s;background:#97b707;line-height:.875rem}.m-pi-ratings--mini{width:24px;max-width:24px;padding:.5rem;cursor:help;box-sizing:content-box}.m-pi-ratings--mini__item{padding:1px 0}.m-pi-ratings--mini__item,.m-pi-ratings--mini__item-level{display:block;width:100%;max-width:100%}.m-pi-ratings--mini__item-level-bar{height:6px}.m-pi-ratings--mini__item-level-label,.m-pi-ratings--mini__item-name{display:none}.m-multidimensional-pi__mini-pi{display:flex;position:relative;align-items:stretch;height:100%}.m-multidimensional-pi__toggle-mini-pi{flex:auto;padding:0}.m-multidimensional-pi__mini-graph{width:17rem;padding:.5rem;background:#fff;box-shadow:0 1px .5rem rgba(0,0,0,.2)}.m-multidimensional-pi__mini-graph:before{display:block;content:\"\";position:absolute;width:0;height:0;bottom:100%;border:.5rem solid transparent;border-bottom-color:#fff;left:.5rem}.m-background-image{min-height:100vh;background-attachment:fixed;background-repeat:no-repeat;background-position:top;background-size:cover}.m-background-image--secure{background-image:url(/assets/co_bg_secure.jpg)}.s-contact-title-field{display:inline-block;min-height:2.625rem;padding:.5rem;background-color:transparent;transition:box-shadow .15s,border-color .15s ease-in-out;border:1px solid #0f98c6;background-color:#fff;color:#444}.s-contact-title-field:focus{border-color:#cfeaf7}.s-contact-title-field:disabled{background:transparent;color:#0b7497}.s-contact-title-field::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.s-contact-title-field::-moz-placeholder{color:rgba(80,80,80,.8)}.s-contact-title-field:-ms-input-placeholder{color:rgba(80,80,80,.8)}.s-contact-title-field:-moz-placeholder{color:rgba(80,80,80,.8)}.m-contact-profile-form{margin:1.5rem 0}.m-contact-profile-form__expanded-form,.m-contact-profile-form__header{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;margin-bottom:.75rem}.m-contact-profile-form__title{flex:1 1 0px}.m-contact-profile-form__expand-button{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.m-contact-profile-form__expand-button{padding-right:.75rem;padding-left:.75rem}}.m-contact-profile-form__input{flex:0 0 100%;max-width:100%;margin-bottom:.75rem}.m-contact-profile-form__birthday,.m-contact-profile-form__birthday-input{width:100%}.m-contact-profile-form__birthday-input{margin-bottom:.75rem}.m-email-form__col-button{flex:0 0 auto}.m-email-form__button{margin-left:.9375rem}@media print,screen and (min-width:40em){.m-email-form__legend{display:none}.m-email-form__button{margin-left:0}}.m-phone-form__col-button{flex:0 0 auto}.m-phone-form__button{margin-left:.9375rem}@media print,screen and (min-width:40em){.m-phone-form__legend{display:none}.m-phone-form__button{margin-left:0}}.m-im-form__col-button{flex:0 0 auto}.m-im-form__button{margin-left:.9375rem}@media print,screen and (min-width:40em){.m-im-form__legend{display:none}.m-im-form__button{margin-left:0}}.m-address-form__select{display:inline-block;min-height:2.625rem;padding:.5rem;background-color:transparent;transition:box-shadow .15s,border-color .15s ease-in-out;border:1px solid #0f98c6;background-color:#fff;color:#444}.m-address-form__select:focus{border-color:#cfeaf7}.m-address-form__select:disabled{background:transparent;color:#0b7497}.m-address-form__select::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-address-form__select::-moz-placeholder{color:rgba(80,80,80,.8)}.m-address-form__select:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-address-form__select:-moz-placeholder{color:rgba(80,80,80,.8)}.m-address-form__col-button{flex:0 0 auto}.m-address-form__button{margin-left:.9375rem}@media print,screen and (min-width:40em){.m-address-form__button{margin-left:0}}.m-public-key-form{padding-left:.9375rem;background-color:#edf9fe}.m-public-key-form__actions{display:flex}.m-public-key-form__button-cancel{margin-left:auto}.m-public-key-list__key{display:flex;align-items:center;margin-left:1.5rem}.m-public-key-list__edit-button{margin-left:auto}.m-public-key-list__quality-icon--weak{color:#ed0e47}.m-public-key-list__quality-icon--average{color:#ebc064}.m-public-key-list__quality-icon--good{color:#05b898}.m-orga-form__col-button{flex:0 0 auto}.m-orga-form__button{margin-left:.9375rem}@media print,screen and (min-width:40em){.m-orga-form__button{margin-left:0}}.m-identity-form__col-button{flex:0 0 auto}.m-identity-form__button{margin-left:.9375rem}@media print,screen and (min-width:40em){.m-identity-form__legend{display:none}.m-identity-form__button{margin-left:0}}.m-add-form-field-form__col-button{flex:0 0 auto}@media print,screen and (min-width:40em){.m-add-form-field-form__legend{display:none}}.m-address-details__postal-address{display:inline-block}.s-contact{padding-bottom:5rem;background:#fff}.s-contact__tags{margin:1rem .625rem}.s-contact__contact-details,.s-contact__form,.s-contact__keys,.s-contact__main-title{margin-right:.625rem;margin-left:.625rem}.s-contact__details-group:not:first-of-type{border-bottom:2px solid rgba(141,206,236,.2)}.s-contact__detail{margin-left:1.5rem}.s-contact__edit-bar{margin-top:1rem;text-align:right}.s-contact__action{margin-left:.625rem}@media print,screen and (min-width:40em){.s-contact__contact-details,.s-contact__form,.s-contact__keys,.s-contact__last-messages,.s-contact__main-title,.s-contact__tags{margin-right:2.625rem;margin-left:2.625rem}.s-contact__tags{text-align:right}}@media print,screen and (min-width:64em){.s-contact__contact-details,.s-contact__form,.s-contact__keys,.s-contact__last-messages,.s-contact__main-title,.s-contact__tags{margin-right:5rem;margin-left:5rem}}.s-contact-action-bar{padding-right:.625rem;text-align:right}.s-contact-main-title{display:grid;grid-template-columns:auto auto 1fr;grid-template-areas:\"avatar name\" \"pi orga\"}.s-contact-main-title__avatar{grid-area:avatar;align-self:center;margin-right:1rem}.s-contact-main-title__name{grid-area:name;color:#0f98c6;font-size:1.75rem;font-weight:700}.s-contact-main-title__pi{grid-area:pi;justify-self:center;margin-right:1rem;color:rgba(7,183,152,.8);font-size:2.625rem;font-weight:700;line-height:2.625rem}.s-contact-main-title__organizations{grid-area:orga;font-weight:700}@media print,screen and (min-width:40em){.s-contact-main-title{grid-template-areas:\"avatar name name\" \"avatar pi orga\"}}.m-contact-item{max-width:expanded;margin-left:auto;flex-flow:row wrap}.m-contact-item,.m-contact-item__title{margin-right:auto;display:flex;align-items:center}.m-contact-item__title{flex:0 0 83.33333%;max-width:83.33333%;max-width:1200px;flex-flow:row wrap;margin-left:.625rem;color:#505050}.m-contact-item__title:hover{background-color:#fff;color:#919191}.m-contact-item__title:active{background-color:#f3f3f3;color:#2b2b2b}.m-contact-item__avatar{flex:0 0 auto;margin-right:.625rem}.m-contact-item__contact{flex:0 0 75%;max-width:75%;overflow:hidden}.m-contact-item__tags{min-height:1rem;white-space:nowrap}.m-contact-item__info{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%;order:3;padding-top:.625rem;color:#919191;text-align:right}@media print,screen and (min-width:40em){.m-contact-item__info{padding-right:.75rem;padding-left:.75rem}}.m-contact-item__select{flex:0 0 auto}.m-contact-item__contact-title{margin-right:.5rem}.m-contact-item__contact-prefix,.m-contact-item__contact-suffix{margin-right:.5rem;color:#111}.m-contact-item__interactions-nb{font-weight:700;text-decoration:underline}@media print,screen and (min-width:40em){.m-contact-item__title{flex:0 0 50%;padding-right:.625rem;padding-left:.625rem;max-width:50%}}@media print,screen and (min-width:40em) and (min-width:40em){.m-contact-item__title{padding-right:.75rem;padding-left:.75rem}}@media print,screen and (min-width:40em){.m-contact-item__info{flex:0 0 33.33333%;padding-right:.625rem;padding-left:.625rem;max-width:33.33333%;order:0;padding-top:0}}@media print,screen and (min-width:40em) and (min-width:40em){.m-contact-item__info{padding-right:.75rem;padding-left:.75rem}}@media print,screen and (min-width:40em){.m-contact-item__select{flex:0 0 auto;margin-left:.625rem}}.m-tag-list__tag{color:#505050;font-weight:400;line-height:1.4rem}.m-tag-list__tag--active{color:#444;font-weight:600}.s-contact-book__sidebar{padding-right:1.5rem;padding-left:1.5rem}.s-contact-book__tags-title{margin-top:1.4rem;margin-bottom:.8rem;color:#505050;font-size:1.3rem;line-height:1rem}.s-contact-book__import{max-width:1200px;display:flex;flex-flow:row wrap;margin:12px auto 1.5rem}.s-contact-book__action-button{text-transform:uppercase}.s-contact-book__select-all{display:inline-block;vertical-align:middle;margin-left:10px}.s-contact-book__load-more{padding-top:1.5rem;padding-bottom:1.5rem;text-align:center}@media print,screen and (min-width:40em){.s-contact-book__sidebar{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}}@media print,screen and (min-width:40em) and (min-width:40em){.s-contact-book__sidebar{padding-right:.75rem;padding-left:.75rem}}.s-contact-book-menu{text-align:right}.s-contact-book-menu__action-btn{margin-left:.625rem;color:#0e97c3}.s-contact-book-menu__select-all{display:inline-block;margin-left:.625rem}.s-contact-book-menu__select-all--label-hidden{margin-right:.625rem}.m-contact-list__nav{display:flex;justify-content:space-between;margin-top:2rem;margin-right:.625rem;margin-left:.625rem}.m-contact-list__alpha-letter{color:#ccc;font-weight:400;text-decoration:none;text-transform:capitalize}.m-contact-list__alpha-letter--active{color:#9a9a9a;font-weight:600}.m-contact-list__list{margin-bottom:2.25rem}.m-contact-list__alpha-title{margin-right:.625rem;margin-left:.625rem;padding-top:3.125rem;color:#9a9a9a}.m-contact-list__contact{padding-top:1.25rem;padding-bottom:1.25rem;border-bottom:1px solid #f9f9f9}@media print,screen and (min-width:40em){.m-contact-list__contact{margin-right:2.625rem;margin-left:2.625rem}.m-contact-list__nav{margin-right:1.3125rem;margin-left:1.3125rem}.m-contact-list__alpha-title{margin-right:1.5rem;margin-left:1.5rem}}@media print,screen and (min-width:64em){.m-contact-list__contact{margin-right:5rem;margin-left:5rem}}.s-contact-association{background-color:#fff}.s-contact-association__action-bar{padding-right:.625rem}.l-auth-page{min-height:100vh;border-top:8px solid #0f98c6;background:#fff}.l-auth-page__content{max-width:23.75rem;margin:auto}.l-auth-page__header{padding-top:86px;padding-bottom:36px;text-align:center}.l-auth-page__brand{height:42px}.l-auth-page__form{margin:0 3rem}.l-auth-page__footer{padding:1.5rem 0;font-size:.875rem;text-align:center}.m-control-compose-button__label{display:none}@media print,screen and (min-width:40em){.m-control-compose-button__label{display:inline}.m-control-compose-button__icon{display:none}}.m-search-field{position:relative}.m-search-field__search-input{width:100%;padding-right:4rem;border:none}@media print,screen and (min-width:40em){.m-search-field__search-input{padding-right:14rem}}.m-search-field__search-button{position:absolute;top:.3125rem;right:.3125rem;height:2rem;line-height:2rem}.m-search-field__button-label{display:none;margin-right:.5rem}@media print,screen and (min-width:40em){.m-search-field__button-label{display:inline}}.m-page-actions{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-page-actions__action-btns{flex:0 0 auto;margin-right:1.5rem}.m-page-actions__search-field{flex:1 1 0px}.m-action-btns{display:flex}.m-action-btns__btn{margin-left:1.5rem}.m-action-btns__btn:first-child{margin-left:0}.m-item-link{display:inline-block;color:inherit}.m-item-link:hover{color:#0f98c6}.m-item-link:active{color:#0a617f}.m-item-button{display:inline-block;color:#0b7497;line-height:2.625rem}.m-item-button:hover{background:none;color:#8dceec}.m-item-button:active{color:#0a617f}.m-navbar-item{display:flex;height:3.125rem;margin-top:5px;margin-left:2px;background-color:#e1f5ff;line-height:3.125rem}.m-navbar-item:first-of-type{margin-left:0}.m-navbar-item--secondary{background-color:#f4f4f4}.m-navbar-item--contrasted{background-color:#8dceec;color:#fff}.m-navbar-item--contrasted.m-navbar-item--is-active{color:#115972}.m-navbar-item--is-active{position:relative;z-index:2;height:3.5rem;margin-top:0;margin-left:0;background-color:#fff;line-height:3.5rem}.m-navbar-item--is-active+.m-navbar-item{margin-left:0}.m-navbar-item__content{flex:1 0;max-width:12rem;padding-right:.375rem;padding-left:.75rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.m-navbar-item__action{flex:0 1}.m-tab{border-top:3px solid #0f98c6}.m-tab__icon{margin-right:.5rem}.m-tab__icon:before{display:inline-block;vertical-align:middle;width:1.75rem;height:1.75rem;border-radius:.875rem;line-height:1.75rem;text-align:center}.m-tab--disabled{border-top:3px solid #c2c2c2}.m-tab--super{border-top:3px solid #05b898}.m-tab--good{border-top:3px solid #ebc064}.m-tab--bad{border-top:3px solid #d66291}.m-tab--ugly{border-top:3px solid #ed0e47}.m-application-tab{width:3.75rem;font-size:1.25rem;text-align:center}.m-horizontal-scroll{display:flex;height:3.5rem;background-image:linear-gradient(180deg,transparent 5px,#e1f5ff 0)}.m-horizontal-scroll__anchor,.m-horizontal-scroll__visible-zone{display:inline-block}.m-horizontal-scroll__anchor{flex:0;background-color:#d9f2fe}.m-horizontal-scroll__visible-zone{position:relative;flex:1;overflow-y:hidden}@media print,screen and (min-width:40em){.m-horizontal-scroll__visible-zone{overflow:hidden}}.m-horizontal-scroll__container{display:inline-flex;position:absolute;left:0;transition:left .5s ease;background:linear-gradient(180deg,transparent 5px,#fff 0)}.m-horizontal-scroll__item{flex:shrink}.scroll-anchor--left{padding:0 3px 0 12px}.scroll-anchor--right{padding:0 12px 0 3px}.scroll-anchor-item{margin-right:0;border-top:0}.l-navigation{margin-bottom:-3px}.l-navigation:after{display:block;content:\" \";position:relative;top:-3px;z-index:1;border-bottom:3px solid #0f98c6}.l-navigation__compose-item{display:none;border-top:none}.l-navigation__compose-item--sticky{display:inline-block}.l-navigation__compose-btn{padding:0 .5rem;color:#fff}.l-footer{padding-top:3.625rem;padding-bottom:2.875rem;text-align:center}.l-footer__brand{height:32px;margin-bottom:.625rem}.l-footer__release{color:#000;font-size:.875rem}.Notification__icon___GPDHD{display:block;position:absolute;top:0;left:0;height:100%;padding-top:10px;box-sizing:border-box;width:30px;text-align:center}.Notification__content___2i_Va{border-radius:0 2px 2px 0}.Notification__item___1d3Zz,.Notification__item__btnBar___2ulqW,.Notification__item__message___3NDDt{padding:10px;font-weight:900;font-family:Lato,sans-serif;border-top:1px solid rgba(0,0,0,.1);-webkit-font-smoothing:antialiased}.Notification__item__message___3NDDt{font-size:.9rem;max-height:150px;overflow-y:auto;border-top:none}.Notification__item__message___3NDDt p:last-child{margin-bottom:0}.Notification__item__btnBar___2ulqW{margin-top:10px;padding:0}.Notification__actionBtn___3o--x{box-sizing:border-box;background:none;font-size:.9rem;font-family:Lato,sans-serif;border-right:1px solid rgba(0,0,0,.1);text-shadow:none;cursor:pointer;display:inline-block;width:50%;height:30px;line-height:30px;text-align:center}.Notification__actionBtn___3o--x:hover{background:none}.Notification__actionBtn___3o--x:last-child{border-right:none}.Notification__actionBtn___3o--x i{margin-right:7px}.Notification__close-all___3YIP0,.Notification__has-close-all--noDismiss___3oZHJ .Notification__close-all___3YIP0{position:absolute;top:7px;right:38px;background:none;font-size:.9rem;font-family:Lato,sans-serif;text-shadow:none;padding:1px 5px;border-radius:3px;cursor:pointer}.Notification__close-all___3YIP0:hover{background:none}.Notification__close___CZE_b{position:absolute;top:0;right:0;width:38px;height:38px;font-size:.7rem;line-height:38px;text-align:center;text-shadow:none;color:#000;opacity:.4;cursor:pointer}.Notification__close___CZE_b:focus,.Notification__close___CZE_b:hover{opacity:1}.Notification__close___CZE_b:active{opacity:.2}.Notification__close___CZE_b:before{content:\"\\274C\";margin:0}.Notification__no-close___3-p_Q .Notification__item--message___2JYsT{padding-right:10px}.Notification__has-close-all--noDismiss___3oZHJ .Notification__close-all___3YIP0{right:8px}.Notification__has-close-all--noDismiss___3oZHJ .Notification__item--message___2JYsT{padding-right:81px}.Notification__has-close-all___3-_VI .Notification__item--message___2JYsT{padding-right:115px}.Notification__has-close___2d--3 .Notification__item--message___2JYsT{padding-right:34px}.Notification__notification--info___1cxtE{position:relative;word-wrap:break-word;pointer-events:auto;width:300px;border-radius:5px;background:#80ccff;padding-left:30px;overflow:hidden;z-index:9999;margin-bottom:2px;max-height:150px}.Notification__notification--info___1cxtE .Notification__icon___GPDHD{color:#e8f1fa;background:#0098ff}.Notification__notification--info___1cxtE .Notification__content___2i_Va{color:#112e4d;background-color:#80ccff}.Notification__notification--info___1cxtE .Notification__actionBtn___3o--x{color:rgba(17,46,77,.6)}.Notification__notification--info___1cxtE .Notification__actionBtn___3o--x:hover{color:#112e4d}.Notification__notification--info___1cxtE .Notification__close-all___3YIP0{border:1px solid rgba(17,46,77,.3);color:rgba(17,46,77,.6)}.Notification__notification--info___1cxtE .Notification__close-all___3YIP0:hover{border-color:rgba(17,46,77,.8);color:#112e4d}.Notification__notification--success___2txWa{position:relative;word-wrap:break-word;pointer-events:auto;width:300px;border-radius:5px;background:#72efa8;padding-left:30px;overflow:hidden;z-index:9999;margin-bottom:2px;max-height:150px}.Notification__notification--success___2txWa .Notification__icon___GPDHD{color:#c6f9dd;background:#17ca65}.Notification__notification--success___2txWa .Notification__content___2i_Va{color:#05371b;background-color:#72efa8}.Notification__notification--success___2txWa .Notification__actionBtn___3o--x{color:rgba(5,55,27,.6)}.Notification__notification--success___2txWa .Notification__actionBtn___3o--x:hover{color:#05371b}.Notification__notification--success___2txWa .Notification__close-all___3YIP0{border:1px solid rgba(5,55,27,.3);color:rgba(5,55,27,.6)}.Notification__notification--success___2txWa .Notification__close-all___3YIP0:hover{border-color:rgba(5,55,27,.8);color:#05371b}.Notification__notification--warning___Wd6Y7{position:relative;word-wrap:break-word;pointer-events:auto;width:300px;border-radius:5px;background:#ffa480;padding-left:30px;overflow:hidden;z-index:9999;margin-bottom:2px;max-height:150px}.Notification__notification--warning___Wd6Y7 .Notification__icon___GPDHD{color:#fff9f6;background:#ff4800}.Notification__notification--warning___Wd6Y7 .Notification__content___2i_Va{color:#6c2c05;background-color:#ffa480}.Notification__notification--warning___Wd6Y7 .Notification__actionBtn___3o--x{color:rgba(108,44,5,.6)}.Notification__notification--warning___Wd6Y7 .Notification__actionBtn___3o--x:hover{color:#6c2c05}.Notification__notification--warning___Wd6Y7 .Notification__close-all___3YIP0{border:1px solid rgba(108,44,5,.3);color:rgba(108,44,5,.6)}.Notification__notification--warning___Wd6Y7 .Notification__close-all___3YIP0:hover{border-color:rgba(108,44,5,.8);color:#6c2c05}.Notification__notification--error___2fV5G{position:relative;word-wrap:break-word;pointer-events:auto;width:300px;border-radius:5px;background:#ff4d4d;padding-left:30px;overflow:hidden;z-index:9999;margin-bottom:2px;max-height:150px}.Notification__notification--error___2fV5G .Notification__icon___GPDHD{color:#ff8585;background:#c00}.Notification__notification--error___2fV5G .Notification__content___2i_Va{color:#000;background-color:#ff4d4d}.Notification__notification--error___2fV5G .Notification__actionBtn___3o--x{color:rgba(0,0,0,.6)}.Notification__notification--error___2fV5G .Notification__actionBtn___3o--x:hover{color:#000}.Notification__notification--error___2fV5G .Notification__close-all___3YIP0{border:1px solid rgba(0,0,0,.3);color:rgba(0,0,0,.6)}.Notification__notification--error___2fV5G .Notification__close-all___3YIP0:hover{border-color:rgba(0,0,0,.8);color:#000}@keyframes Notify__notification-show___m91vc{0%{opacity:0;transform:perspective(300px) translateY(-30px) rotateX(90deg)}to{opacity:1;transform:perspective(300px) translate(0) rotateX(0deg)}}@keyframes Notify__notification-hide___qXZMa{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.8)}}@keyframes Notify__notification-shrink___dlvYw{0%{opacity:0;max-height:150px;transform:scale(.8)}to{opacity:0;max-height:0;transform:scale(.8)}}.Notify__wrapper___Btp8G{display:-ms-flexbox;display:flex;padding:10px;font-size:1.2em;min-height:0;-ms-flex-direction:inherit;flex-direction:inherit}.Notify__containerTopRight___3Aype{z-index:9998;position:fixed;overflow-y:auto;max-height:100vh;top:0;right:0;-ms-flex-direction:column;flex-direction:column}.Notify__containerTopRight___3Aype::-webkit-scrollbar{display:none}.Notify__containerBottomRight___2LKUe{z-index:9998;position:fixed;overflow-y:auto;max-height:100vh;right:0;bottom:0;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.Notify__containerBottomRight___2LKUe::-webkit-scrollbar{display:none}.Notify__containerBottomLeft___1zAHL{z-index:9998;position:fixed;overflow-y:auto;max-height:100vh;left:0;bottom:0;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.Notify__containerBottomLeft___1zAHL::-webkit-scrollbar{display:none}.Notify__containerTopLeft___1Mczb{z-index:9998;position:fixed;overflow-y:auto;max-height:100vh;top:0;left:0;-ms-flex-direction:column;flex-direction:column}.Notify__containerTopLeft___1Mczb::-webkit-scrollbar{display:none}.Notify__enter___1lTas{animation:Notify__notification-show___m91vc .16s cubic-bezier(.175,.885,.32,1.27499)}.Notify__leave___IHSth{animation:Notify__notification-hide___qXZMa .24s cubic-bezier(.33859,-.42,1,-.22),Notify__notification-shrink___dlvYw .24s cubic-bezier(.5,0,0,1) .24s}@-webkit-keyframes notification-show{0%{opacity:0;-webkit-transform:perspective(300px) translateY(-30px) rotateX(90deg);transform:perspective(300px) translateY(-30px) rotateX(90deg)}to{opacity:1;-webkit-transform:perspective(300px) translate(0) rotateX(0deg);transform:perspective(300px) translate(0) rotateX(0deg)}}@keyframes notification-show{0%{opacity:0;-webkit-transform:perspective(300px) translateY(-30px) rotateX(90deg);transform:perspective(300px) translateY(-30px) rotateX(90deg)}to{opacity:1;-webkit-transform:perspective(300px) translate(0) rotateX(0deg);transform:perspective(300px) translate(0) rotateX(0deg)}}@-webkit-keyframes notification-hide{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}to{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}}@keyframes notification-hide{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}to{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}}@-webkit-keyframes notification-shrink{0%{opacity:0;max-height:150px;-webkit-transform:scale(.8);transform:scale(.8)}to{opacity:0;max-height:0;-webkit-transform:scale(.8);transform:scale(.8)}}@keyframes notification-shrink{0%{opacity:0;max-height:150px;-webkit-transform:scale(.8);transform:scale(.8)}to{opacity:0;max-height:0;-webkit-transform:scale(.8);transform:scale(.8)}}.wrapper{display:flex;padding:10px;font-size:1.2em;min-height:0;flex-direction:inherit}.containerTopRight{z-index:9998;position:fixed;overflow-y:auto;max-height:100vh;top:0;right:0;flex-direction:column}.containerTopRight::-webkit-scrollbar{display:none}.containerBottomRight{z-index:9998;position:fixed;overflow-y:auto;max-height:100vh;right:0;bottom:0;flex-direction:column-reverse}.containerBottomRight::-webkit-scrollbar{display:none}.containerBottomLeft{z-index:9998;position:fixed;overflow-y:auto;max-height:100vh;left:0;bottom:0;flex-direction:column-reverse}.containerBottomLeft::-webkit-scrollbar{display:none}.containerTopLeft{z-index:9998;position:fixed;overflow-y:auto;max-height:100vh;top:0;left:0;flex-direction:column}.containerTopLeft::-webkit-scrollbar{display:none}.enter{-webkit-animation:notification-show .16s cubic-bezier(.175,.885,.32,1.27499);animation:notification-show .16s cubic-bezier(.175,.885,.32,1.27499)}.leave{-webkit-animation:notification-hide .24s cubic-bezier(.33859,-.42,1,-.22),notification-shrink .24s cubic-bezier(.5,0,0,1) .24s;animation:notification-hide .24s cubic-bezier(.33859,-.42,1,-.22),notification-shrink .24s cubic-bezier(.5,0,0,1) .24s}.l-notification-center{z-index:9998;position:fixed;overflow-y:auto;max-height:100vh;top:50px;right:0;flex-direction:column}.l-notification-center::-webkit-scrollbar{display:none}.l-notification-center__notification--has-close .l-notification-center__notification-item-message{padding-right:34px}.l-notification-center__notification--has-close-all .l-notification-center__notification-item-message{padding-right:115px}.l-notification-center__notification-item-message{max-height:150px;padding:10px;font-family:Open Sans,Helvetica Neue,Helvetica,Roboto,Arial,sans-serif;font-size:.9rem;-webkit-font-smoothing:antialiased;font-weight:700;overflow-y:auto}.l-notification-center__notification-item-message p:first-child{margin-top:0}.l-notification-center__notification-item-message p:last-child{margin-bottom:0}.m-tour-portal{display:flex;flex-flow:row wrap;background:#fff;color:#111}.m-tour-portal__badge,.m-tour-portal__close{border-bottom:1px solid #bbe8fd}.m-tour-portal__badge{flex:1 0;padding-left:.9375rem;line-height:2.625rem}.m-tour-portal__close{flex:0 1}.m-tour-portal__content,.m-tour-portal__controls{flex:1 0 100%;padding-right:.9375rem;padding-left:.9375rem}.m-tour-portal__controls{padding-bottom:.625rem}.m-take-a-tour{text-align:left}.l-page-container{max-width:1200px;margin-right:auto;margin-left:auto;padding-right:0;padding-left:0}@media print,screen and (min-width:40em){.l-page-container{padding-right:4px;padding-left:4px}}@media screen and (min-width:75em){.l-page-container{padding-right:40px;padding-left:40px}}.l-page__main-actions{padding-top:1.625rem;padding-bottom:1.25rem}.l-header{height:2.625rem;background-color:#e1f5ff;line-height:2.625rem}.l-header__container{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.l-header__brand-link{flex:1 1 0px}.l-header__take-a-tour,.l-header__user-menu{flex:0 0 auto}.l-header__brand{display:inline-block;vertical-align:middle;height:29px;margin-left:.25rem}@media print,screen and (min-width:40em){.l-header__brand{margin-left:0}}.l-header__button-label{display:none}@media print,screen and (min-width:40em){.l-header__button-label{display:inline}}.l-navbar{height:3.5rem}.l-navbar__wrapper{z-index:10}.l-navbar__wrapper--sticky{position:fixed;top:0;width:100%;background-color:#d9f2fe}.l-navbar__wrapper--sticky .m-horizontal-scroll{height:3.125rem;background-image:none}.l-navbar__wrapper--sticky .m-navbar-item,.l-navbar__wrapper--sticky .tab{margin-top:0}.l-navbar__wrapper--sticky .m-navbar-item--is-active{height:3.125rem}.l-navbar__wrapper--sticky .m-navbar-item--is-active .m-item-link{line-height:3.125rem}.l-navbar__wrapper--sticky .m-horizontal-scroll__container{background:#fff}.l-settings,.l-user{background-color:#fff}.m-participants-icon{display:inline-block;vertical-align:baseline;width:25px;height:25px;border-radius:50%;overflow:hidden}.m-participants-icon__letter:before{display:block;float:left;text-align:center}.m-participants-icon__letter--1:before{width:25px;height:25px;line-height:25px}.s-discussion-item{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:stretch;border-left:.5rem solid #e6e6e6;background-color:#f3f3f3;line-height:3rem}.s-discussion-item--ugly{border-left:.5rem solid #ed0e47}.s-discussion-item--bad{border-left:.5rem solid #d66291}.s-discussion-item--good{border-left:.5rem solid #ebc064}.s-discussion-item--super{border-left:.5rem solid #05b898}.s-discussion-item--is-unread{background-color:#fff}.s-discussion-item__message-type{justify-self:flex-end;margin:0 .5rem}.s-discussion-item__tags{display:flex;flex:1 1 0;flex-direction:row;margin:0 .5rem}.s-discussion-item__tag{margin:0 .25rem;display:none}.s-discussion-item__participants{flex-shrink:1;width:auto;min-width:20%;overflow:hidden;text-overflow:ellipsis;color:#3386a2;white-space:nowrap}@media print,screen and (min-width:40em){.s-discussion-item__participants{flex:0 0 10.1rem;width:10.1rem;margin-right:.5rem}}.s-discussion-item__message_excerpt{flex:1 2 auto;min-width:30%;overflow:hidden;text-overflow:ellipsis;color:#919191;font-weight:400;text-decoration:none;white-space:nowrap}.s-discussion-item__message_subject{color:#111;font-weight:800}.s-discussion-item__message-date{flex:0 1 auto;margin:auto .5rem;overflow:hidden;text-overflow:ellipsis;text-align:right;white-space:nowrap}.s-discussion-item__select{display:inline-block;flex-grow:0;flex-shrink:0;margin:auto .5rem auto 1rem;display:none}.m-participants-icon{flex:0 0 auto;margin:auto .5rem;color:#fff;line-height:2rem}.m-participants-icon .fa{color:#a6a6a6}.s-timeline{background-color:#fff}.s-timeline__discussion-list{background-color:#d3d3d3}.s-timeline__discussion{border-top:2px solid #f9f9f9}.s-timeline__load-more{margin:1rem auto;text-align:center}.s-timeline__new-msg{background-color:#fff;text-align:center}.s-timeline-action-bar{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.s-timeline-action-bar__filters{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.s-timeline-action-bar__filters{padding-right:.75rem;padding-left:.75rem}}.m-attachement-manager{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-attachement-manager__list{flex:0 0 100%;max-width:100%}.m-attachement-manager__button{flex:0 0 auto;margin-top:.5rem;margin-left:auto}.m-attachement-manager__item{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;align-items:center;margin-top:1px;background:#8dceec;font-weight:700}.m-attachement-manager__file-name{flex:1 1 0px;padding-right:.625rem;padding-left:.625rem;min-width:0;color:#444;text-decoration:none}@media print,screen and (min-width:40em){.m-attachement-manager__file-name{padding-right:.75rem;padding-left:.75rem}}.m-attachement-manager__file-name:hover{color:#444;text-decoration:underline}.m-attachement-manager__file-size{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;margin-left:auto}@media print,screen and (min-width:40em){.m-attachement-manager__file-size{padding-right:.75rem;padding-left:.75rem}}.m-attachement-manager__file-delete{flex:0 0 auto;margin-left:auto}.m-attachement-manager__encryption-warning{color:#e43737;font-weight:700}\n/*!\n *  Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome\n *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n */.m-check-decryption{position:fixed;bottom:0;z-index:1;width:100%}.m-check-decryption-panel{padding:1rem;border-top:.625rem solid #0f98c6;background-color:#fff;overflow:hidden}.m-check-decryption-panel:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:\"\";vertical-align:middle;float:left;width:2.5rem;margin:.375rem 1rem .375rem .375rem;padding:.375rem .125rem;border-radius:1.25rem;background-color:#0f98c6;color:#fff;font-size:1.75rem;text-align:center}@media print,screen and (min-width:40em){.m-check-decryption-panel{max-width:1192px}}@media screen and (min-width:75em){.m-check-decryption-panel{max-width:1120px}}.m-encryption-locked-message{position:relative;margin:1.5rem 1.5rem 1.5rem 6.5rem;padding:1px 0;background:no-repeat 0 0/95% url(/assets/fake-text.svg)}.m-encryption-locked-message__message{position:relative;width:70%;margin:1.5rem auto;padding:.625rem;border-radius:3rem;background-color:hsla(0,0%,100%,.8);overflow:hidden}.m-encryption-locked-message__icon{vertical-align:middle;float:left;width:2.5rem;margin:.375rem 1rem .375rem .375rem;padding:.375rem .125rem;border-radius:1.25rem;background-color:#0f98c6;color:#fff;font-size:1.75rem;text-align:center}.m-encryption-locked-message__status--error{color:#e43737}.m-recipient-list{display:inline-block;padding:.5rem;background-color:transparent;transition:box-shadow .15s,border-color .15s ease-in-out;border:1px solid #0f98c6;background-color:#fff;color:#444;max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;min-height:2.625rem;margin-bottom:0}.m-recipient-list:focus{border-color:#cfeaf7}.m-recipient-list:disabled{background:transparent;color:#0b7497}.m-recipient-list::-webkit-input-placeholder{color:rgba(80,80,80,.8)}.m-recipient-list::-moz-placeholder{color:rgba(80,80,80,.8)}.m-recipient-list:-ms-input-placeholder{color:rgba(80,80,80,.8)}.m-recipient-list:-moz-placeholder{color:rgba(80,80,80,.8)}.m-recipient-list__placeholder{display:inline-block;margin-right:.5rem;line-height:2.625rem}.m-recipient-list__recipient{flex:0 0 auto;display:inline-block;margin-right:.5rem;margin-bottom:.5rem}.m-recipient-list__search{flex:1 1 0px;position:relative}.m-recipient-list__suggest{top:100%;left:0}.m-recipient-list__search-input{display:block;width:100%;min-width:6rem;border:0;background:transparent;color:#444;line-height:2.625rem}.m-recipient-list__dropdown{margin-top:2.625rem}.m-recipient-list__search-result{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;min-height:2.625rem;padding-top:.5rem;padding-bottom:.5rem;line-height:1.4rem;text-align:left}.m-recipient-list__search-result-title{flex:0 0 100%;max-width:100%;font-weight:600}.m-recipient-list__search-result-info{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%;font-size:.875rem;font-style:italic}@media print,screen and (min-width:40em){.m-recipient-list__search-result-info{padding-right:.75rem;padding-left:.75rem}}\n/*!\n *  Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome\n *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n */.m-draft-message-quick__recipient-list{margin-right:1.5rem;margin-bottom:1.5rem;margin-left:1.5rem}.m-draft-message-quick__container{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;border:1px solid #9a989b;background-color:#fff}.m-draft-message-quick__encryption{text-align:right}.m-draft-message-quick__toggle-advanced{flex:0 0 auto;width:3.438rem;height:3.438rem}.m-draft-message-quick__input{flex:1 1 0px;border:none}.m-draft-message-quick__input--encrypted{position:relative}.m-draft-message-quick__send{flex:0 0 auto;width:3.438rem;height:3.438rem}.m-draft-message-quick__send-button{position:relative;height:100%;font-size:1.3rem}.m-draft-message-quick__send-button:after{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:0;bottom:0;width:1rem;height:1rem;font-size:1rem}.m-draft-message-quick__send-button--encrypted{background-color:#05b898}.m-draft-message-quick__send-button--encrypted:after{content:\"\"}.m-draft-message-quick__send-button--unencrypted{background-color:#ebc064;color:#444}.m-draft-message-quick__send-button--unencrypted:after{content:\"\"}\n/*!\n *  Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome\n *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n */@font-face{font-family:FontAwesome;src:url(/assets/fontawesome-webfont.eot);src:url(/assets/fontawesome-webfont.eot?#iefix&v=4.7.0) format(\"embedded-opentype\"),url(/assets/fontawesome-webfont.woff2) format(\"woff2\"),url(/assets/fontawesome-webfont.woff) format(\"woff\"),url(/assets/fontawesome-webfont.ttf) format(\"truetype\"),url(/assets/fontawesome-webfont.svg#fontawesomeregular) format(\"svg\");font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:\"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)\";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:\"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)\";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:\"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)\";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:\"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)\";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:\"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)\";-webkit-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:\"\"}.fa-music:before{content:\"\"}.fa-search:before{content:\"\"}.fa-envelope-o:before{content:\"\"}.fa-heart:before{content:\"\"}.fa-star:before{content:\"\"}.fa-star-o:before{content:\"\"}.fa-user:before{content:\"\"}.fa-film:before{content:\"\"}.fa-th-large:before{content:\"\"}.fa-th:before{content:\"\"}.fa-th-list:before{content:\"\"}.fa-check:before{content:\"\"}.fa-close:before,.fa-remove:before,.fa-times:before{content:\"\"}.fa-search-plus:before{content:\"\"}.fa-search-minus:before{content:\"\"}.fa-power-off:before{content:\"\"}.fa-signal:before{content:\"\"}.fa-cog:before,.fa-gear:before{content:\"\"}.fa-trash-o:before{content:\"\"}.fa-home:before{content:\"\"}.fa-file-o:before{content:\"\"}.fa-clock-o:before{content:\"\"}.fa-road:before{content:\"\"}.fa-download:before{content:\"\"}.fa-arrow-circle-o-down:before{content:\"\"}.fa-arrow-circle-o-up:before{content:\"\"}.fa-inbox:before{content:\"\"}.fa-play-circle-o:before{content:\"\"}.fa-repeat:before,.fa-rotate-right:before{content:\"\"}.fa-refresh:before{content:\"\"}.fa-list-alt:before{content:\"\"}.fa-lock:before{content:\"\"}.fa-flag:before{content:\"\"}.fa-headphones:before{content:\"\"}.fa-volume-off:before{content:\"\"}.fa-volume-down:before{content:\"\"}.fa-volume-up:before{content:\"\"}.fa-qrcode:before{content:\"\"}.fa-barcode:before{content:\"\"}.fa-tag:before{content:\"\"}.fa-tags:before{content:\"\"}.fa-book:before{content:\"\"}.fa-bookmark:before{content:\"\"}.fa-print:before{content:\"\"}.fa-camera:before{content:\"\"}.fa-font:before{content:\"\"}.fa-bold:before{content:\"\"}.fa-italic:before{content:\"\"}.fa-text-height:before{content:\"\"}.fa-text-width:before{content:\"\"}.fa-align-left:before{content:\"\"}.fa-align-center:before{content:\"\"}.fa-align-right:before{content:\"\"}.fa-align-justify:before{content:\"\"}.fa-list:before{content:\"\"}.fa-dedent:before,.fa-outdent:before{content:\"\"}.fa-indent:before{content:\"\"}.fa-video-camera:before{content:\"\"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:\"\"}.fa-pencil:before{content:\"\"}.fa-map-marker:before{content:\"\"}.fa-adjust:before{content:\"\"}.fa-tint:before{content:\"\"}.fa-edit:before,.fa-pencil-square-o:before{content:\"\"}.fa-share-square-o:before{content:\"\"}.fa-check-square-o:before{content:\"\"}.fa-arrows:before{content:\"\"}.fa-step-backward:before{content:\"\"}.fa-fast-backward:before{content:\"\"}.fa-backward:before{content:\"\"}.fa-play:before{content:\"\"}.fa-pause:before{content:\"\"}.fa-stop:before{content:\"\"}.fa-forward:before{content:\"\"}.fa-fast-forward:before{content:\"\"}.fa-step-forward:before{content:\"\"}.fa-eject:before{content:\"\"}.fa-chevron-left:before{content:\"\"}.fa-chevron-right:before{content:\"\"}.fa-plus-circle:before{content:\"\"}.fa-minus-circle:before{content:\"\"}.fa-times-circle:before{content:\"\"}.fa-check-circle:before{content:\"\"}.fa-question-circle:before{content:\"\"}.fa-info-circle:before{content:\"\"}.fa-crosshairs:before{content:\"\"}.fa-times-circle-o:before{content:\"\"}.fa-check-circle-o:before{content:\"\"}.fa-ban:before{content:\"\"}.fa-arrow-left:before{content:\"\"}.fa-arrow-right:before{content:\"\"}.fa-arrow-up:before{content:\"\"}.fa-arrow-down:before{content:\"\"}.fa-mail-forward:before,.fa-share:before{content:\"\"}.fa-expand:before{content:\"\"}.fa-compress:before{content:\"\"}.fa-plus:before{content:\"\"}.fa-minus:before{content:\"\"}.fa-asterisk:before{content:\"\"}.fa-exclamation-circle:before{content:\"\"}.fa-gift:before{content:\"\"}.fa-leaf:before{content:\"\"}.fa-fire:before{content:\"\"}.fa-eye:before{content:\"\"}.fa-eye-slash:before{content:\"\"}.fa-exclamation-triangle:before,.fa-warning:before{content:\"\"}.fa-plane:before{content:\"\"}.fa-calendar:before{content:\"\"}.fa-random:before{content:\"\"}.fa-comment:before{content:\"\"}.fa-magnet:before{content:\"\"}.fa-chevron-up:before{content:\"\"}.fa-chevron-down:before{content:\"\"}.fa-retweet:before{content:\"\"}.fa-shopping-cart:before{content:\"\"}.fa-folder:before{content:\"\"}.fa-folder-open:before{content:\"\"}.fa-arrows-v:before{content:\"\"}.fa-arrows-h:before{content:\"\"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:\"\"}.fa-twitter-square:before{content:\"\"}.fa-facebook-square:before{content:\"\"}.fa-camera-retro:before{content:\"\"}.fa-key:before{content:\"\"}.fa-cogs:before,.fa-gears:before{content:\"\"}.fa-comments:before{content:\"\"}.fa-thumbs-o-up:before{content:\"\"}.fa-thumbs-o-down:before{content:\"\"}.fa-star-half:before{content:\"\"}.fa-heart-o:before{content:\"\"}.fa-sign-out:before{content:\"\"}.fa-linkedin-square:before{content:\"\"}.fa-thumb-tack:before{content:\"\"}.fa-external-link:before{content:\"\"}.fa-sign-in:before{content:\"\"}.fa-trophy:before{content:\"\"}.fa-github-square:before{content:\"\"}.fa-upload:before{content:\"\"}.fa-lemon-o:before{content:\"\"}.fa-phone:before{content:\"\"}.fa-square-o:before{content:\"\"}.fa-bookmark-o:before{content:\"\"}.fa-phone-square:before{content:\"\"}.fa-twitter:before{content:\"\"}.fa-facebook-f:before,.fa-facebook:before{content:\"\"}.fa-github:before{content:\"\"}.fa-unlock:before{content:\"\"}.fa-credit-card:before{content:\"\"}.fa-feed:before,.fa-rss:before{content:\"\"}.fa-hdd-o:before{content:\"\"}.fa-bullhorn:before{content:\"\"}.fa-bell:before{content:\"\"}.fa-certificate:before{content:\"\"}.fa-hand-o-right:before{content:\"\"}.fa-hand-o-left:before{content:\"\"}.fa-hand-o-up:before{content:\"\"}.fa-hand-o-down:before{content:\"\"}.fa-arrow-circle-left:before{content:\"\"}.fa-arrow-circle-right:before{content:\"\"}.fa-arrow-circle-up:before{content:\"\"}.fa-arrow-circle-down:before{content:\"\"}.fa-globe:before{content:\"\"}.fa-wrench:before{content:\"\"}.fa-tasks:before{content:\"\"}.fa-filter:before{content:\"\"}.fa-briefcase:before{content:\"\"}.fa-arrows-alt:before{content:\"\"}.fa-group:before,.fa-users:before{content:\"\"}.fa-chain:before,.fa-link:before{content:\"\"}.fa-cloud:before{content:\"\"}.fa-flask:before{content:\"\"}.fa-cut:before,.fa-scissors:before{content:\"\"}.fa-copy:before,.fa-files-o:before{content:\"\"}.fa-paperclip:before{content:\"\"}.fa-floppy-o:before,.fa-save:before{content:\"\"}.fa-square:before{content:\"\"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:\"\"}.fa-list-ul:before{content:\"\"}.fa-list-ol:before{content:\"\"}.fa-strikethrough:before{content:\"\"}.fa-underline:before{content:\"\"}.fa-table:before{content:\"\"}.fa-magic:before{content:\"\"}.fa-truck:before{content:\"\"}.fa-pinterest:before{content:\"\"}.fa-pinterest-square:before{content:\"\"}.fa-google-plus-square:before{content:\"\"}.fa-google-plus:before{content:\"\"}.fa-money:before{content:\"\"}.fa-caret-down:before{content:\"\"}.fa-caret-up:before{content:\"\"}.fa-caret-left:before{content:\"\"}.fa-caret-right:before{content:\"\"}.fa-columns:before{content:\"\"}.fa-sort:before,.fa-unsorted:before{content:\"\"}.fa-sort-desc:before,.fa-sort-down:before{content:\"\"}.fa-sort-asc:before,.fa-sort-up:before{content:\"\"}.fa-envelope:before{content:\"\"}.fa-linkedin:before{content:\"\"}.fa-rotate-left:before,.fa-undo:before{content:\"\"}.fa-gavel:before,.fa-legal:before{content:\"\"}.fa-dashboard:before,.fa-tachometer:before{content:\"\"}.fa-comment-o:before{content:\"\"}.fa-comments-o:before{content:\"\"}.fa-bolt:before,.fa-flash:before{content:\"\"}.fa-sitemap:before{content:\"\"}.fa-umbrella:before{content:\"\"}.fa-clipboard:before,.fa-paste:before{content:\"\"}.fa-lightbulb-o:before{content:\"\"}.fa-exchange:before{content:\"\"}.fa-cloud-download:before{content:\"\"}.fa-cloud-upload:before{content:\"\"}.fa-user-md:before{content:\"\"}.fa-stethoscope:before{content:\"\"}.fa-suitcase:before{content:\"\"}.fa-bell-o:before{content:\"\"}.fa-coffee:before{content:\"\"}.fa-cutlery:before{content:\"\"}.fa-file-text-o:before{content:\"\"}.fa-building-o:before{content:\"\"}.fa-hospital-o:before{content:\"\"}.fa-ambulance:before{content:\"\"}.fa-medkit:before{content:\"\"}.fa-fighter-jet:before{content:\"\"}.fa-beer:before{content:\"\"}.fa-h-square:before{content:\"\"}.fa-plus-square:before{content:\"\"}.fa-angle-double-left:before{content:\"\"}.fa-angle-double-right:before{content:\"\"}.fa-angle-double-up:before{content:\"\"}.fa-angle-double-down:before{content:\"\"}.fa-angle-left:before{content:\"\"}.fa-angle-right:before{content:\"\"}.fa-angle-up:before{content:\"\"}.fa-angle-down:before{content:\"\"}.fa-desktop:before{content:\"\"}.fa-laptop:before{content:\"\"}.fa-tablet:before{content:\"\"}.fa-mobile-phone:before,.fa-mobile:before{content:\"\"}.fa-circle-o:before{content:\"\"}.fa-quote-left:before{content:\"\"}.fa-quote-right:before{content:\"\"}.fa-spinner:before{content:\"\"}.fa-circle:before{content:\"\"}.fa-mail-reply:before,.fa-reply:before{content:\"\"}.fa-github-alt:before{content:\"\"}.fa-folder-o:before{content:\"\"}.fa-folder-open-o:before{content:\"\"}.fa-smile-o:before{content:\"\"}.fa-frown-o:before{content:\"\"}.fa-meh-o:before{content:\"\"}.fa-gamepad:before{content:\"\"}.fa-keyboard-o:before{content:\"\"}.fa-flag-o:before{content:\"\"}.fa-flag-checkered:before{content:\"\"}.fa-terminal:before{content:\"\"}.fa-code:before{content:\"\"}.fa-mail-reply-all:before,.fa-reply-all:before{content:\"\"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:\"\"}.fa-location-arrow:before{content:\"\"}.fa-crop:before{content:\"\"}.fa-code-fork:before{content:\"\"}.fa-chain-broken:before,.fa-unlink:before{content:\"\"}.fa-question:before{content:\"\"}.fa-info:before{content:\"\"}.fa-exclamation:before{content:\"\"}.fa-superscript:before{content:\"\"}.fa-subscript:before{content:\"\"}.fa-eraser:before{content:\"\"}.fa-puzzle-piece:before{content:\"\"}.fa-microphone:before{content:\"\"}.fa-microphone-slash:before{content:\"\"}.fa-shield:before{content:\"\"}.fa-calendar-o:before{content:\"\"}.fa-fire-extinguisher:before{content:\"\"}.fa-rocket:before{content:\"\"}.fa-maxcdn:before{content:\"\"}.fa-chevron-circle-left:before{content:\"\"}.fa-chevron-circle-right:before{content:\"\"}.fa-chevron-circle-up:before{content:\"\"}.fa-chevron-circle-down:before{content:\"\"}.fa-html5:before{content:\"\"}.fa-css3:before{content:\"\"}.fa-anchor:before{content:\"\"}.fa-unlock-alt:before{content:\"\"}.fa-bullseye:before{content:\"\"}.fa-ellipsis-h:before{content:\"\"}.fa-ellipsis-v:before{content:\"\"}.fa-rss-square:before{content:\"\"}.fa-play-circle:before{content:\"\"}.fa-ticket:before{content:\"\"}.fa-minus-square:before{content:\"\"}.fa-minus-square-o:before{content:\"\"}.fa-level-up:before{content:\"\"}.fa-level-down:before{content:\"\"}.fa-check-square:before{content:\"\"}.fa-pencil-square:before{content:\"\"}.fa-external-link-square:before{content:\"\"}.fa-share-square:before{content:\"\"}.fa-compass:before{content:\"\"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:\"\"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:\"\"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:\"\"}.fa-eur:before,.fa-euro:before{content:\"\"}.fa-gbp:before{content:\"\"}.fa-dollar:before,.fa-usd:before{content:\"\"}.fa-inr:before,.fa-rupee:before{content:\"\"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:\"\"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:\"\"}.fa-krw:before,.fa-won:before{content:\"\"}.fa-bitcoin:before,.fa-btc:before{content:\"\"}.fa-file:before{content:\"\"}.fa-file-text:before{content:\"\"}.fa-sort-alpha-asc:before{content:\"\"}.fa-sort-alpha-desc:before{content:\"\"}.fa-sort-amount-asc:before{content:\"\"}.fa-sort-amount-desc:before{content:\"\"}.fa-sort-numeric-asc:before{content:\"\"}.fa-sort-numeric-desc:before{content:\"\"}.fa-thumbs-up:before{content:\"\"}.fa-thumbs-down:before{content:\"\"}.fa-youtube-square:before{content:\"\"}.fa-youtube:before{content:\"\"}.fa-xing:before{content:\"\"}.fa-xing-square:before{content:\"\"}.fa-youtube-play:before{content:\"\"}.fa-dropbox:before{content:\"\"}.fa-stack-overflow:before{content:\"\"}.fa-instagram:before{content:\"\"}.fa-flickr:before{content:\"\"}.fa-adn:before{content:\"\"}.fa-bitbucket:before{content:\"\"}.fa-bitbucket-square:before{content:\"\"}.fa-tumblr:before{content:\"\"}.fa-tumblr-square:before{content:\"\"}.fa-long-arrow-down:before{content:\"\"}.fa-long-arrow-up:before{content:\"\"}.fa-long-arrow-left:before{content:\"\"}.fa-long-arrow-right:before{content:\"\"}.fa-apple:before{content:\"\"}.fa-windows:before{content:\"\"}.fa-android:before{content:\"\"}.fa-linux:before{content:\"\"}.fa-dribbble:before{content:\"\"}.fa-skype:before{content:\"\"}.fa-foursquare:before{content:\"\"}.fa-trello:before{content:\"\"}.fa-female:before{content:\"\"}.fa-male:before{content:\"\"}.fa-gittip:before,.fa-gratipay:before{content:\"\"}.fa-sun-o:before{content:\"\"}.fa-moon-o:before{content:\"\"}.fa-archive:before{content:\"\"}.fa-bug:before{content:\"\"}.fa-vk:before{content:\"\"}.fa-weibo:before{content:\"\"}.fa-renren:before{content:\"\"}.fa-pagelines:before{content:\"\"}.fa-stack-exchange:before{content:\"\"}.fa-arrow-circle-o-right:before{content:\"\"}.fa-arrow-circle-o-left:before{content:\"\"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:\"\"}.fa-dot-circle-o:before{content:\"\"}.fa-wheelchair:before{content:\"\"}.fa-vimeo-square:before{content:\"\"}.fa-try:before,.fa-turkish-lira:before{content:\"\"}.fa-plus-square-o:before{content:\"\"}.fa-space-shuttle:before{content:\"\"}.fa-slack:before{content:\"\"}.fa-envelope-square:before{content:\"\"}.fa-wordpress:before{content:\"\"}.fa-openid:before{content:\"\"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:\"\"}.fa-graduation-cap:before,.fa-mortar-board:before{content:\"\"}.fa-yahoo:before{content:\"\"}.fa-google:before{content:\"\"}.fa-reddit:before{content:\"\"}.fa-reddit-square:before{content:\"\"}.fa-stumbleupon-circle:before{content:\"\"}.fa-stumbleupon:before{content:\"\"}.fa-delicious:before{content:\"\"}.fa-digg:before{content:\"\"}.fa-pied-piper-pp:before{content:\"\"}.fa-pied-piper-alt:before{content:\"\"}.fa-drupal:before{content:\"\"}.fa-joomla:before{content:\"\"}.fa-language:before{content:\"\"}.fa-fax:before{content:\"\"}.fa-building:before{content:\"\"}.fa-child:before{content:\"\"}.fa-paw:before{content:\"\"}.fa-spoon:before{content:\"\"}.fa-cube:before{content:\"\"}.fa-cubes:before{content:\"\"}.fa-behance:before{content:\"\"}.fa-behance-square:before{content:\"\"}.fa-steam:before{content:\"\"}.fa-steam-square:before{content:\"\"}.fa-recycle:before{content:\"\"}.fa-automobile:before,.fa-car:before{content:\"\"}.fa-cab:before,.fa-taxi:before{content:\"\"}.fa-tree:before{content:\"\"}.fa-spotify:before{content:\"\"}.fa-deviantart:before{content:\"\"}.fa-soundcloud:before{content:\"\"}.fa-database:before{content:\"\"}.fa-file-pdf-o:before{content:\"\"}.fa-file-word-o:before{content:\"\"}.fa-file-excel-o:before{content:\"\"}.fa-file-powerpoint-o:before{content:\"\"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:\"\"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:\"\"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:\"\"}.fa-file-movie-o:before,.fa-file-video-o:before{content:\"\"}.fa-file-code-o:before{content:\"\"}.fa-vine:before{content:\"\"}.fa-codepen:before{content:\"\"}.fa-jsfiddle:before{content:\"\"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:\"\"}.fa-circle-o-notch:before{content:\"\"}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:\"\"}.fa-empire:before,.fa-ge:before{content:\"\"}.fa-git-square:before{content:\"\"}.fa-git:before{content:\"\"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:\"\"}.fa-tencent-weibo:before{content:\"\"}.fa-qq:before{content:\"\"}.fa-wechat:before,.fa-weixin:before{content:\"\"}.fa-paper-plane:before,.fa-send:before{content:\"\"}.fa-paper-plane-o:before,.fa-send-o:before{content:\"\"}.fa-history:before{content:\"\"}.fa-circle-thin:before{content:\"\"}.fa-header:before{content:\"\"}.fa-paragraph:before{content:\"\"}.fa-sliders:before{content:\"\"}.fa-share-alt:before{content:\"\"}.fa-share-alt-square:before{content:\"\"}.fa-bomb:before{content:\"\"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:\"\"}.fa-tty:before{content:\"\"}.fa-binoculars:before{content:\"\"}.fa-plug:before{content:\"\"}.fa-slideshare:before{content:\"\"}.fa-twitch:before{content:\"\"}.fa-yelp:before{content:\"\"}.fa-newspaper-o:before{content:\"\"}.fa-wifi:before{content:\"\"}.fa-calculator:before{content:\"\"}.fa-paypal:before{content:\"\"}.fa-google-wallet:before{content:\"\"}.fa-cc-visa:before{content:\"\"}.fa-cc-mastercard:before{content:\"\"}.fa-cc-discover:before{content:\"\"}.fa-cc-amex:before{content:\"\"}.fa-cc-paypal:before{content:\"\"}.fa-cc-stripe:before{content:\"\"}.fa-bell-slash:before{content:\"\"}.fa-bell-slash-o:before{content:\"\"}.fa-trash:before{content:\"\"}.fa-copyright:before{content:\"\"}.fa-at:before{content:\"\"}.fa-eyedropper:before{content:\"\"}.fa-paint-brush:before{content:\"\"}.fa-birthday-cake:before{content:\"\"}.fa-area-chart:before{content:\"\"}.fa-pie-chart:before{content:\"\"}.fa-line-chart:before{content:\"\"}.fa-lastfm:before{content:\"\"}.fa-lastfm-square:before{content:\"\"}.fa-toggle-off:before{content:\"\"}.fa-toggle-on:before{content:\"\"}.fa-bicycle:before{content:\"\"}.fa-bus:before{content:\"\"}.fa-ioxhost:before{content:\"\"}.fa-angellist:before{content:\"\"}.fa-cc:before{content:\"\"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:\"\"}.fa-meanpath:before{content:\"\"}.fa-buysellads:before{content:\"\"}.fa-connectdevelop:before{content:\"\"}.fa-dashcube:before{content:\"\"}.fa-forumbee:before{content:\"\"}.fa-leanpub:before{content:\"\"}.fa-sellsy:before{content:\"\"}.fa-shirtsinbulk:before{content:\"\"}.fa-simplybuilt:before{content:\"\"}.fa-skyatlas:before{content:\"\"}.fa-cart-plus:before{content:\"\"}.fa-cart-arrow-down:before{content:\"\"}.fa-diamond:before{content:\"\"}.fa-ship:before{content:\"\"}.fa-user-secret:before{content:\"\"}.fa-motorcycle:before{content:\"\"}.fa-street-view:before{content:\"\"}.fa-heartbeat:before{content:\"\"}.fa-venus:before{content:\"\"}.fa-mars:before{content:\"\"}.fa-mercury:before{content:\"\"}.fa-intersex:before,.fa-transgender:before{content:\"\"}.fa-transgender-alt:before{content:\"\"}.fa-venus-double:before{content:\"\"}.fa-mars-double:before{content:\"\"}.fa-venus-mars:before{content:\"\"}.fa-mars-stroke:before{content:\"\"}.fa-mars-stroke-v:before{content:\"\"}.fa-mars-stroke-h:before{content:\"\"}.fa-neuter:before{content:\"\"}.fa-genderless:before{content:\"\"}.fa-facebook-official:before{content:\"\"}.fa-pinterest-p:before{content:\"\"}.fa-whatsapp:before{content:\"\"}.fa-server:before{content:\"\"}.fa-user-plus:before{content:\"\"}.fa-user-times:before{content:\"\"}.fa-bed:before,.fa-hotel:before{content:\"\"}.fa-viacoin:before{content:\"\"}.fa-train:before{content:\"\"}.fa-subway:before{content:\"\"}.fa-medium:before{content:\"\"}.fa-y-combinator:before,.fa-yc:before{content:\"\"}.fa-optin-monster:before{content:\"\"}.fa-opencart:before{content:\"\"}.fa-expeditedssl:before{content:\"\"}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:\"\"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:\"\"}.fa-battery-2:before,.fa-battery-half:before{content:\"\"}.fa-battery-1:before,.fa-battery-quarter:before{content:\"\"}.fa-battery-0:before,.fa-battery-empty:before{content:\"\"}.fa-mouse-pointer:before{content:\"\"}.fa-i-cursor:before{content:\"\"}.fa-object-group:before{content:\"\"}.fa-object-ungroup:before{content:\"\"}.fa-sticky-note:before{content:\"\"}.fa-sticky-note-o:before{content:\"\"}.fa-cc-jcb:before{content:\"\"}.fa-cc-diners-club:before{content:\"\"}.fa-clone:before{content:\"\"}.fa-balance-scale:before{content:\"\"}.fa-hourglass-o:before{content:\"\"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:\"\"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:\"\"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:\"\"}.fa-hourglass:before{content:\"\"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:\"\"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:\"\"}.fa-hand-scissors-o:before{content:\"\"}.fa-hand-lizard-o:before{content:\"\"}.fa-hand-spock-o:before{content:\"\"}.fa-hand-pointer-o:before{content:\"\"}.fa-hand-peace-o:before{content:\"\"}.fa-trademark:before{content:\"\"}.fa-registered:before{content:\"\"}.fa-creative-commons:before{content:\"\"}.fa-gg:before{content:\"\"}.fa-gg-circle:before{content:\"\"}.fa-tripadvisor:before{content:\"\"}.fa-odnoklassniki:before{content:\"\"}.fa-odnoklassniki-square:before{content:\"\"}.fa-get-pocket:before{content:\"\"}.fa-wikipedia-w:before{content:\"\"}.fa-safari:before{content:\"\"}.fa-chrome:before{content:\"\"}.fa-firefox:before{content:\"\"}.fa-opera:before{content:\"\"}.fa-internet-explorer:before{content:\"\"}.fa-television:before,.fa-tv:before{content:\"\"}.fa-contao:before{content:\"\"}.fa-500px:before{content:\"\"}.fa-amazon:before{content:\"\"}.fa-calendar-plus-o:before{content:\"\"}.fa-calendar-minus-o:before{content:\"\"}.fa-calendar-times-o:before{content:\"\"}.fa-calendar-check-o:before{content:\"\"}.fa-industry:before{content:\"\"}.fa-map-pin:before{content:\"\"}.fa-map-signs:before{content:\"\"}.fa-map-o:before{content:\"\"}.fa-map:before{content:\"\"}.fa-commenting:before{content:\"\"}.fa-commenting-o:before{content:\"\"}.fa-houzz:before{content:\"\"}.fa-vimeo:before{content:\"\"}.fa-black-tie:before{content:\"\"}.fa-fonticons:before{content:\"\"}.fa-reddit-alien:before{content:\"\"}.fa-edge:before{content:\"\"}.fa-credit-card-alt:before{content:\"\"}.fa-codiepie:before{content:\"\"}.fa-modx:before{content:\"\"}.fa-fort-awesome:before{content:\"\"}.fa-usb:before{content:\"\"}.fa-product-hunt:before{content:\"\"}.fa-mixcloud:before{content:\"\"}.fa-scribd:before{content:\"\"}.fa-pause-circle:before{content:\"\"}.fa-pause-circle-o:before{content:\"\"}.fa-stop-circle:before{content:\"\"}.fa-stop-circle-o:before{content:\"\"}.fa-shopping-bag:before{content:\"\"}.fa-shopping-basket:before{content:\"\"}.fa-hashtag:before{content:\"\"}.fa-bluetooth:before{content:\"\"}.fa-bluetooth-b:before{content:\"\"}.fa-percent:before{content:\"\"}.fa-gitlab:before{content:\"\"}.fa-wpbeginner:before{content:\"\"}.fa-wpforms:before{content:\"\"}.fa-envira:before{content:\"\"}.fa-universal-access:before{content:\"\"}.fa-wheelchair-alt:before{content:\"\"}.fa-question-circle-o:before{content:\"\"}.fa-blind:before{content:\"\"}.fa-audio-description:before{content:\"\"}.fa-volume-control-phone:before{content:\"\"}.fa-braille:before{content:\"\"}.fa-assistive-listening-systems:before{content:\"\"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:\"\"}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:\"\"}.fa-glide:before{content:\"\"}.fa-glide-g:before{content:\"\"}.fa-sign-language:before,.fa-signing:before{content:\"\"}.fa-low-vision:before{content:\"\"}.fa-viadeo:before{content:\"\"}.fa-viadeo-square:before{content:\"\"}.fa-snapchat:before{content:\"\"}.fa-snapchat-ghost:before{content:\"\"}.fa-snapchat-square:before{content:\"\"}.fa-pied-piper:before{content:\"\"}.fa-first-order:before{content:\"\"}.fa-yoast:before{content:\"\"}.fa-themeisle:before{content:\"\"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:\"\"}.fa-fa:before,.fa-font-awesome:before{content:\"\"}.fa-handshake-o:before{content:\"\"}.fa-envelope-open:before{content:\"\"}.fa-envelope-open-o:before{content:\"\"}.fa-linode:before{content:\"\"}.fa-address-book:before{content:\"\"}.fa-address-book-o:before{content:\"\"}.fa-address-card:before,.fa-vcard:before{content:\"\"}.fa-address-card-o:before,.fa-vcard-o:before{content:\"\"}.fa-user-circle:before{content:\"\"}.fa-user-circle-o:before{content:\"\"}.fa-user-o:before{content:\"\"}.fa-id-badge:before{content:\"\"}.fa-drivers-license:before,.fa-id-card:before{content:\"\"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:\"\"}.fa-quora:before{content:\"\"}.fa-free-code-camp:before{content:\"\"}.fa-telegram:before{content:\"\"}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:\"\"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:\"\"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:\"\"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:\"\"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:\"\"}.fa-shower:before{content:\"\"}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:\"\"}.fa-podcast:before{content:\"\"}.fa-window-maximize:before{content:\"\"}.fa-window-minimize:before{content:\"\"}.fa-window-restore:before{content:\"\"}.fa-times-rectangle:before,.fa-window-close:before{content:\"\"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:\"\"}.fa-bandcamp:before{content:\"\"}.fa-grav:before{content:\"\"}.fa-etsy:before{content:\"\"}.fa-imdb:before{content:\"\"}.fa-ravelry:before{content:\"\"}.fa-eercast:before{content:\"\"}.fa-microchip:before{content:\"\"}.fa-snowflake-o:before{content:\"\"}.fa-superpowers:before{content:\"\"}.fa-wpexplorer:before{content:\"\"}.fa-meetup:before{content:\"\"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.m-draft-message-advanced{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;background-color:#f6fcff}.m-draft-message-advanced__toggle-simple{flex:0 0 auto;width:3.438rem;height:3.438rem}.m-draft-message-advanced__identity{height:3.438rem;margin-bottom:.625rem}.m-draft-message-advanced__container{flex:1 1 0px;padding-right:5rem;padding-left:.625rem}.m-draft-message-advanced__action-send,.m-draft-message-advanced__errors{flex:0 0 100%;max-width:100%;padding-right:5rem;background-color:#ecf9ff}.m-draft-message-advanced__errors{padding-bottom:1rem;text-align:center}.m-draft-message-advanced__encryption{width:100%;padding-right:5rem;padding-left:.625rem;text-align:right}.m-draft-message-advanced__action-send{text-align:right}.m-draft-message-advanced__action-button{margin-top:1rem;margin-bottom:1rem;margin-left:.625rem}.m-draft-message-advanced__button-send{position:relative}.m-draft-message-advanced__button-send:after{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:0;bottom:0;width:1rem;height:1rem;font-size:1rem}.m-draft-message-advanced__button-send--encrypted{background-color:#05b898}.m-draft-message-advanced__button-send--encrypted:after{content:\"\"}.m-draft-message-advanced__button-send--unencrypted{background-color:#ebc064;color:#444}.m-draft-message-advanced__button-send--unencrypted:after{content:\"\"}.m-draft-message-advanced__attachments,.m-draft-message-advanced__body,.m-draft-message-advanced__recipient-list,.m-draft-message-advanced__subject{margin-bottom:1.5rem}.m-draft-message-placeholder{height:3.438rem}.m-toggle-advanced-draft-button{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding-top:.375rem;background-color:#07b798;font-size:1.3rem}.ugly .m-message-pi__metaphor,.ugly .m-message-pi__meter{border-color:#edccd4}.ugly .m-message-pi__numeric-value{color:#ed0e47}.bad .m-message-pi__metaphor,.bad .m-message-pi__meter{border-color:#f6dbe6}.bad .m-message-pi__numeric-value{color:#d66291}.good .m-message-pi__metaphor,.good .m-message-pi__meter{border-color:#fcf7ec}.good .m-message-pi__numeric-value{color:#ebc064}.super .m-message-pi__metaphor,.super .m-message-pi__meter{border-color:#cbe8e3}.super .m-message-pi__numeric-value{color:#05b898}.m-message-pi{display:flex;flex-direction:row;flex-wrap:wrap;width:100%}.m-message-pi__progress{height:.25rem;margin-bottom:0}.m-message-pi__progress--ugly{background-color:#edccd4}.m-message-pi__progress--bad{background-color:#f6dbe6}.m-message-pi__progress--good{background-color:#fcf7ec}.m-message-pi__progress--super{background-color:#cbe8e3}.m-message-pi__meter{flex-grow:1;width:45%;border-bottom:2px solid #999}.m-message-pi__meter--no-separator{border-bottom:none}.m-message-pi__progress-meter{height:.25rem}.m-message-pi__progress-meter--ugly{background-color:#ed0e47}.m-message-pi__progress-meter--bad{background-color:#d66291}.m-message-pi__progress-meter--good{background-color:#ebc064}.m-message-pi__progress-meter--super{background-color:#05b898}.m-message-pi__numeric{display:flex;align-items:center}.m-message-pi__numeric-legend{font-size:.75rem}.m-message-pi__numeric-value{flex-grow:1;font-size:1.5rem;font-weight:500;text-align:right}.m-message-pi__illustration{display:flex;flex-direction:row;width:45%;margin-bottom:.825rem}.m-message-pi__illustration i,.m-message-pi__illustration img{margin-right:.25rem;font-size:.875rem}.m-message-pi__types li{font-size:.75rem;font-weight:600;line-height:1rem}.m-message-pi__types li.super{color:#05b898}.m-message-pi__types li.good{color:#ebc064}.m-message-pi__types li.bad{color:#d66291}.m-message-pi__types li.ugly{color:#ed0e47}.m-message-pi__metaphor{color:#999;font-size:.875rem;font-style:italic}@media print,screen and (min-width:40em){.m-message-pi__illustration,.m-message-pi__meter,.m-message-pi__numeric{width:100%}.m-message-pi__metaphor{width:100%;padding-bottom:.625rem;border-bottom:2px solid #999;font-size:.75rem}}.m-message-tags-list__tag{display:inline;margin-right:.5rem;margin-bottom:.5rem}.m-message-list__load-more+.m-instant-message{margin-top:1rem}.m-instant-message{max-width:none;display:flex;flex-flow:row wrap;max-width:44.5rem;margin-right:0;margin-bottom:1rem;margin-left:0}.m-instant-message--from-user{flex-direction:row-reverse;margin-left:auto}.m-instant-message--from-user .m-instant-message__info{background-color:#feffff}.m-instant-message--from-user .m-instant-message__content{background-color:#f0faff}.m-instant-message--from-user .m-instant-message__author{padding-right:.25rem;padding-left:.25rem}.m-instant-message__author{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.m-instant-message__author{padding-right:.75rem;padding-left:.75rem}}.m-instant-message__info{flex:1 1 0px;background-color:#c5e8f6;overflow:hidden}.m-instant-message__content{flex:0 0 100%;max-width:100%;padding:.625rem;background-color:#b7e2f4}@media print,screen and (min-width:40em){.m-instant-message__author{flex:0 0 auto;padding-right:.5rem;padding-left:0}.m-instant-message__info{flex:0 0 50%;max-width:50%;flex-basis:12.125rem;padding-bottom:.5rem}.m-instant-message__content{flex:1 1 0px;padding:1rem}}.m-instant-message-aside__info{padding-top:.5rem;padding-right:1rem;padding-left:1rem}.m-instant-message-aside__actions{white-space:nowrap}.m-instant-message-author{display:flex;flex-direction:column;align-content:center;overflow:hidden;text-align:center}.m-instant-message-author__avatar{margin-bottom:.25rem}.m-instant-message-author__time{color:#a7abad;font-size:.625rem}.m-instant-message-participants{width:auto;margin-right:0}.m-instant-message-participants__from,.m-instant-message-participants__to{display:block;font-size:.825rem}.m-instant-message-participants__from{font-size:.875rem}.m-instant-message-participants__to{color:#a7abad}.m-message-attachments{padding-top:.5rem;border-top:1px solid #f9f9f9}.m-message-attachments__item{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;margin-top:.5rem;background:#f9f9f9;font-size:.875rem;font-weight:700;line-height:2.625rem;text-align:left}.m-message-attachments__name{flex:1 1 0px}.m-message-attachments__size{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;margin-left:auto}@media print,screen and (min-width:40em){.m-message-attachments__size{padding-right:.75rem;padding-left:.75rem}}.m-message-attachments__icon{flex:0 0 auto;margin-left:auto}.m-instant-message+.s-mail-message{margin-top:2rem}.s-mail-message{display:grid;grid-template-columns:1fr;grid-template-rows:auto;grid-template-areas:\"details\" \"info\" \"container\" \"actions\";margin-bottom:1rem;background-color:#fff}.s-mail-message__info{grid-area:info;display:flex;flex-direction:row;flex-wrap:wrap;padding:1rem;background-color:#eee}.s-mail-message__info--bad,.s-mail-message__info--good,.s-mail-message__info--super{background-color:#edfffc}.s-mail-message__info--ugly{background-color:#fff4f7}.s-mail-message__tags{margin-top:.5rem}.s-mail-message__participants,.s-mail-message__tags{display:none}.s-mail-message__details{grid-area:details}.s-mail-message__container{grid-area:container}.s-mail-message__container,.s-mail-message__details{overflow:hidden}.s-mail-message__encrypted{float:right;color:#05b898;font-size:1rem;font-weight:400;text-align:right}.s-mail-message__content,.s-mail-message__subject{padding:.5rem;border-bottom:1px solid #f9f9f9;color:#515151}.s-mail-message__content{padding-bottom:3rem}.s-mail-message__actions{grid-area:actions;background-color:#ecf9ff;font-size:.88rem;text-align:right}.s-mail-message__encrypted-attachments-info{margin-right:1rem;margin-left:1rem}@media print,screen and (min-width:40em){.s-mail-message{grid-template-columns:13rem 1fr;grid-template-rows:2.625rem auto 2.625rem;grid-template-areas:\"info details\" \"info container\" \"info actions\"}.s-mail-message__info{float:left;flex-direction:column;order:-1;width:13rem;padding:3rem 1.5rem}.s-mail-message__details{align-self:center}.s-mail-message__participants,.s-mail-message__tags{display:block}.s-mail-message__participants-from,.s-mail-message__participants-to{overflow:hidden;text-overflow:ellipsis;font-size:.875rem}.s-mail-message__participants-from a,.s-mail-message__participants-from a:visited,.s-mail-message__participants-to a,.s-mail-message__participants-to a:visited{color:#515151;font-weight:600}.s-mail-message__content,.s-mail-message__subject{margin-right:6rem;margin-left:6rem}.s-mail-message__actions{padding:0 6rem}}.m-mail-message-details{max-width:none;display:flex;flex-flow:row wrap;margin-right:1rem;margin-left:1rem;padding-top:.5rem;padding-bottom:.5rem;color:grey}.m-mail-message-details__author,.m-mail-message-details__recipients{flex:0 0 100%;max-width:100%;color:#858585;font-size:.825rem}.m-mail-message-details--encrypted__icon{color:#05b898}.m-mail-message-details__author-name{font-weight:800}@media print,screen and (min-width:40em){.m-mail-message-details{border-bottom:1px solid #f9f9f9}.m-mail-message-details__author,.m-mail-message-details__recipients{flex:0 0 50%;max-width:50%}.m-mail-message-details__recipients{text-align:right}}.s-new-draft__form{padding-top:2.625rem;background-color:#f6fcff}.s-new-draft__discussion{margin-top:2.625rem}.m-message-date__time{display:block}@media print,screen and (min-width:40em){.m-message-date__time{display:inline}}.s-search-results-highlights__term{display:inline-block;background:#d9f2fe;color:#2b2b2b}.s-message-result-item{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;align-items:center;min-height:2.625rem;padding-top:.5rem;padding-bottom:.5rem;background-color:#f3f3f3;color:#505050;font-size:.875rem}.s-message-result-item:hover{color:#111}.s-message-result-item--draft,.s-message-result-item--unread{background-color:#fff}.s-message-result-item__col-avatar{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.s-message-result-item__col-avatar{padding-right:.75rem;padding-left:.75rem}}.s-message-result-item__col-title{flex:1 1 0px;min-width:0}.s-message-result-item__col-date{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;display:none;margin-left:auto}@media print,screen and (min-width:40em){.s-message-result-item__col-date{padding-right:.75rem;padding-left:.75rem}}.s-message-result-item__subject{color:#111}.s-message-result-item__author{color:#0a617f;font-size:1rem;font-weight:600}.s-message-result-item__topic{padding-left:.5rem}.s-message-result-item__topic--unread{color:#111;font-weight:600}.s-message-result-item__topic--draft{font-style:italic}.s-message-result-item__tags{padding-left:.5rem}.s-message-result-item__highlights{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%;padding-top:.5rem}@media print,screen and (min-width:40em){.s-message-result-item__highlights{padding-right:.75rem;padding-left:.75rem}}@media print,screen and (min-width:40em){.s-message-result-item__col-date{display:block}}.m-contact-result-item{max-width:expanded;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;align-items:center;min-height:2.625rem;padding-top:.5rem;padding-bottom:.5rem;background-color:#fff;font-size:.875rem}.m-contact-result-item:hover{background-color:#fff;color:#111}.m-contact-result-item:active{background-color:#f3f3f3;color:#2b2b2b}.m-contact-result-item__contact-avatar{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.m-contact-result-item__contact-avatar{padding-right:.75rem;padding-left:.75rem}}.m-contact-result-item__col-title{flex:0 0 auto;color:#0a617f;font-size:1rem;font-weight:600}.m-contact-result-item__col-highlights{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.m-contact-result-item__col-highlights{padding-right:.75rem;padding-left:.75rem}}.m-contact-result-item__col-tags{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.m-contact-result-item__col-tags{padding-right:.75rem;padding-left:.75rem}}.m-contact-result-item__contact-title{margin-right:.5rem}.m-contact-result-item__contact-prefix,.m-contact-result-item__contact-suffix{margin-right:.5rem;color:#505050}.s-search-results{background:#fff}.s-search-results__preview{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.s-search-results__label{flex:0 0 100%;max-width:100%;padding:.5rem .625rem;background:#d9f2fe;color:#2b2b2b;font-weight:600}@media print,screen and (min-width:40em){.s-search-results__label{padding-right:.75rem;padding-left:.75rem}}.s-search-results__list{flex:0 0 100%;max-width:100%}.s-search-results__load-more{padding:.5rem;text-align:center}.s-search-results__link{color:#0a617f}.s-search-results__link:hover{color:#0b7497}.m-user-profile-details__header{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;position:relative}.m-user-profile-details__title{margin:0;color:#505050}.m-user-profile-details__subtitle{margin:0;color:#444;font-size:.875rem;font-weight:400}.m-user-profile-details__rank{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-user-profile-details__rank-badge{flex:0 0 33.33333%;max-width:33.33333%}.m-user-profile-details__rank-info{flex:0 0 66.66667%;padding-right:.625rem;padding-left:.625rem;max-width:66.66667%;text-align:center}@media print,screen and (min-width:40em){.m-user-profile-details__rank-info{padding-right:.75rem;padding-left:.75rem}}.m-user-profile-details__rank-title{color:#505050;font-weight:600}.m-user-profile-details__subscribed-date{display:block;color:#505050;font-weight:600}.m-user-profile-details__avatar-wrapper{flex:0 0 auto}.m-user-profile-details__avatar{width:4rem;height:4rem;font-size:2rem;font-weight:600;line-height:4rem;margin-bottom:.75rem}.m-user-profile-details__avatar__letter:before{font-size:inherit;line-height:inherit}.m-user-profile-details__name{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.m-user-profile-details__name{padding-right:.75rem;padding-left:.75rem}}@media print,screen and (min-width:40em){.m-user-profile-details__avatar-wrapper{flex:0 0 100%;max-width:100%}.m-user-profile-details__avatar{width:8rem;height:8rem;font-size:4rem;font-weight:600;line-height:8rem}.m-user-profile-details__avatar__letter:before{font-size:inherit;line-height:inherit}.m-user-profile-details__name{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%}}@media print,screen and (min-width:40em) and (min-width:40em){.m-user-profile-details__name{padding-right:.75rem;padding-left:.75rem}}@media print,screen and (min-width:40em){.m-user-profile-details__rank-badge{flex:0 0 33.33333%;max-width:33.33333%}.m-user-profile-details__rank-info{flex:0 0 66.66667%;padding-right:.625rem;padding-left:.625rem;max-width:66.66667%}}@media print,screen and (min-width:40em) and (min-width:40em){.m-user-profile-details__rank-info{padding-right:.75rem;padding-left:.75rem}}@media print,screen and (min-width:40em){.m-user-profile-details__avatar-wrapper,.m-user-profile-details__name{text-align:center}}.s-user-profile{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;margin-top:1.5rem}.s-user-profile__info{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%}@media print,screen and (min-width:40em){.s-user-profile__info{padding-right:.75rem;padding-left:.75rem}}.s-user-profile__details{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%}@media print,screen and (min-width:40em){.s-user-profile__details{padding-right:.75rem;padding-left:.75rem}}.s-user-profile__delete{float:right}@media print,screen and (min-width:40em){.s-user-profile__info{flex:0 0 33.33333%;max-width:33.33333%}.s-user-profile__details{flex:0 0 66.66667%;max-width:66.66667%}}@media print,screen and (min-width:64em){.s-user-profile__info{flex:0 0 25%;max-width:25%}.s-user-profile__details{flex:0 0 75%;max-width:75%}}.m-openpgp-key{margin-bottom:1px;background:#fff}.m-openpgp-key__main{display:flex;line-height:2.625rem}.m-openpgp-key__icon{flex:0 1;margin-right:.375rem}.m-openpgp-key__fingerprint{flex:1 0;overflow:hidden;text-overflow:ellipsis}.m-openpgp-key__actions{flex:0 1;text-align:right;white-space:nowrap}.m-openpgp-key__detail{margin-bottom:1.5rem}.m-openpgp-key__summary{font-style:italic}.m-openpgp-key__toggle-info--warning{color:#eba000}.m-account-openpgp-form__main{line-height:2.625rem}.m-account-openpgp-form__field-group{margin-bottom:1.5rem}.m-account-openpgp-form__switch-mode-container{display:inline-block}.m-account-openpgp__info{margin-bottom:.75rem;padding:.75rem;border-radius:.2rem;background-color:#fff;color:#2b2b2b;border:1px solid #8fd8eb;background-color:#d0eff6}.m-account-openpgp__form,.m-account-openpgp__keys{margin-top:1.5rem}.m-password-form__row{align-items:flex-end;margin-bottom:1.5625rem}.m-password-form__tip{display:none;font-size:.875rem}.m-password-form__action{line-height:2.625rem}.m-password-form__strength{margin-top:.625rem}@media print,screen and (min-width:40em){.m-password-form__tip{display:inline-block;height:2.625rem}}.m-password-details{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-password-details__title{flex:0 0 100%;max-width:100%;margin-bottom:1.5rem}.m-password-details__strength{flex:1 1 0px}.m-password-details__form{flex:0 0 100%;max-width:100%}.m-password-details__action{flex:0 0 auto}@media print,screen and (min-width:40em){.m-password-details__strength{flex:1 1 0px}.m-password-details__action{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}}@media print,screen and (min-width:40em) and (min-width:40em){.m-password-details__action{padding-right:.75rem;padding-left:.75rem}}@media print,screen and (min-width:64em){.m-password-details__strength{flex:0 0 66.66667%;max-width:66.66667%}}.m-login-details{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-login-details__title{margin-bottom:1.5rem}.m-login-details__input,.m-login-details__title{flex:0 0 100%;max-width:100%}.m-login-details__action{flex:0 0 auto}@media print,screen and (min-width:40em){.m-login-details__input{flex:1 1 0px}.m-login-details__action{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}}@media print,screen and (min-width:40em) and (min-width:40em){.m-login-details__action{padding-right:.75rem;padding-left:.75rem}}.s-user-account-security__credentials{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.s-user-account-security__login,.s-user-account-security__password{flex:0 0 100%;max-width:100%;margin-bottom:1.5rem}@media print,screen and (min-width:40em){.s-user-account-security__login,.s-user-account-security__password{flex:0 0 50%;padding-right:.625rem;padding-left:.625rem;max-width:50%;margin-bottom:0}}@media print,screen and (min-width:40em) and (min-width:40em){.s-user-account-security__login,.s-user-account-security__password{padding-right:.75rem;padding-left:.75rem}}.m-remote-identity__provider-logo{height:1rem}.m-remote-identity-email__action:not(:first-child){margin-left:.5rem}.m-remote-identity-email__action--push-right{float:right}.m-provider-button{transition:background-color .1s ease-out,color .1s ease-out;color:#0f98c6;font-weight:700;text-decoration:none;cursor:pointer;width:100%;height:100%;min-height:3.5rem;border:1px solid #9a9a9a;background-color:#f3f3f3;overflow:hidden;text-overflow:ellipsis;color:#444;font-size:1.25rem;font-weight:unset;line-height:1.25rem;white-space:nowrap}.m-provider-button:disabled{cursor:default}.m-provider-button:disabled:hover,.m-provider-button:hover{color:#505050}.m-provider-button:active{color:#fff}.m-provider-button--primary{border-color:#0f98c6;color:#0f98c6}.m-provider-button--primary-plain:not(:disabled),.m-provider-button--primary:focus:not(:disabled),.m-provider-button--primary:hover:not(:disabled){background-color:#084c63;color:#505050}.m-provider-button--secondary{border-color:#13ba95;color:#13ba95}.m-provider-button--secondary-plain:not(:disabled),.m-provider-button--secondary:focus:not(:disabled),.m-provider-button--secondary:hover:not(:disabled){background-color:#0a5d4b;color:#505050}.m-provider-button--success{border-color:#3adb76;color:#3adb76}.m-provider-button--success-plain:not(:disabled),.m-provider-button--success:focus:not(:disabled),.m-provider-button--success:hover:not(:disabled){background-color:#157539;color:#505050}.m-provider-button--warning{border-color:#ffae00;color:#ffae00}.m-provider-button--warning-plain:not(:disabled),.m-provider-button--warning:focus:not(:disabled),.m-provider-button--warning:hover:not(:disabled){background-color:#805700;color:#505050}.m-provider-button--alert{border-color:#e43737;color:#e43737}.m-provider-button--alert-plain:not(:disabled),.m-provider-button--alert:focus:not(:disabled),.m-provider-button--alert:hover:not(:disabled){background-color:#7d1111;color:#505050}.m-provider-button--disabled{border-color:#ccc;color:#ccc}.m-provider-button--disabled-plain:not(:disabled),.m-provider-button--disabled:focus:not(:disabled),.m-provider-button--disabled:hover:not(:disabled){background-color:#666;color:#505050}.m-oauth-button__logo{height:2rem}.m-provider-button-container{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;background-color:#f8f8f8}.m-provider-button-container__button{flex:0 0 33.33333%;max-width:33.33333%}.m-provider-button-container__label{flex:0 0 66.66667%;max-width:66.66667%;padding:.625rem 1rem;color:#505050}.m-provider-list__provider{margin-top:.625rem}.m-provider-list__provider:first-of-type{margin-top:unset}.m-new-identity__main-help{color:#919191}.m-new-identity__email-form{margin-top:.625rem;padding:.625rem;background-color:#edf9fe}.m-provider-email-button__icon{vertical-align:middle;height:2.5rem;color:#0f98c6;font-size:2.125rem}.s-settings-identities{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;padding:.75rem}.s-settings-identities__create,.s-settings-identities__menu{flex:0 0 100%;max-width:100%}.s-settings-identities__identity{flex:0 0 100%;max-width:100%;margin-top:.75rem;margin-bottom:.75rem}@media print,screen and (min-width:64em){.s-settings-identities__identity{flex:0 0 50%;padding-right:.625rem;padding-left:.625rem;max-width:50%;margin-top:.75rem;margin-bottom:.75rem}}@media print,screen and (min-width:64em) and (min-width:40em){.s-settings-identities__identity{padding-right:.75rem;padding-left:.75rem}}.m-desktop-notifications--allowed{color:#3adb76}.m-desktop-notifications--denied{color:#e43737}.s-application-settings{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;align-items:stretch;padding:1.5rem .75rem}.s-application-settings__col-sections{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%;margin-bottom:1.5rem}@media print,screen and (min-width:40em){.s-application-settings__col-sections{padding-right:.75rem;padding-left:.75rem}}.s-application-settings__section{height:100%}.s-application-settings__action{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%;text-align:right}@media print,screen and (min-width:40em){.s-application-settings__action{padding-right:.75rem;padding-left:.75rem}}@media print,screen and (min-width:40em){.s-application-settings__col-sections{flex:0 0 50%;padding-right:.625rem;padding-left:.625rem;max-width:50%}}@media print,screen and (min-width:40em) and (min-width:40em){.s-application-settings__col-sections{padding-right:.75rem;padding-left:.75rem}}.m-add-tag{max-width:expanded;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;margin-bottom:1.5625rem}.m-add-tag__input{flex:1 1 0px}.m-add-tag__button{flex:0 0 auto}.m-tag-input{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;margin-bottom:.625rem;background:#fff}.m-tag-input__button{flex:1 1 0px;line-height:2.625rem;text-align:left}.m-tag-input__button:hover{background:#fff}.m-tag-input__input{flex:1 1 0px}.m-tag-input__delete{flex:0 0 auto;line-height:2.625rem}.m-tag-input__errors{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%}@media print,screen and (min-width:40em){.m-tag-input__errors{padding-right:.75rem;padding-left:.75rem}}.s-tags-settings{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;padding:1.5rem .75rem;background:#fff}.s-tags-settings__create,.s-tags-settings__tags{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%}@media print,screen and (min-width:40em){.s-tags-settings__create,.s-tags-settings__tags{padding-right:.75rem;padding-left:.75rem;flex:0 0 50%;padding-right:.625rem;padding-left:.625rem;max-width:50%}}@media print,screen and (min-width:40em) and (min-width:40em){.s-tags-settings__create,.s-tags-settings__tags{padding-right:.75rem;padding-left:.75rem}}.m-protocol-switch{display:flex;flex-direction:row;align-content:center;justify-content:stretch;width:100%;margin:1rem 0;font-size:.875rem}.m-protocol-switch__bar{flex-grow:1;height:.25rem;margin:.375rem 1rem .375rem 0;background-color:#c8e9f9}.ugly .m-protocol-switch__bar{background-color:#edccd4}.super .m-protocol-switch__bar{background-color:#0cbe8e}.m-message-list__placeholder{height:3.5rem;margin-bottom:1rem}.m-message-list__load-more{text-align:center}.m-message-list__load-more .m-button{margin:4px 0 3px}.m-reply-excerpt{width:100%;height:2.625rem;padding:.5rem;background:#fff;overflow:hidden;text-overflow:ellipsis;color:#444;white-space:nowrap}.m-add-participants-dropdown__label{vertical-align:middle}@media screen and (max-width:23.6875em){.m-add-participants-dropdown__label{max-width:87%}}.s-discussion{display:flex;position:relative;flex-direction:column}.s-discussion__tags{display:flex;flex-direction:row}.s-discussion__tag{display:block;margin-left:.5rem}.s-discussion__load-more{padding-top:1.5rem;text-align:center}.s-discussion__reply,.s-discussion__reply-excerpt{position:fixed;right:0;bottom:0;left:0;box-shadow:0 -1px .8rem rgba(0,0,0,.2)}.s-discussion__reply{display:none}@media print,screen and (min-width:40em){.s-discussion__reply{display:block;position:static;box-shadow:none}}.s-discussion__reply--open{display:block}@media print,screen and (min-width:40em){.s-discussion__reply-excerpt{display:none}}.s-discussion__reply-excerpt--close{display:none}.s-discussion-action-bar{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;justify-content:flex-end}.s-discussion-action-bar__tags{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.s-discussion-action-bar__tags{padding-right:.75rem;padding-left:.75rem}}.s-discussion-action-bar__actions{flex:1 1 0px;max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;justify-content:flex-end}.s-discussion-action-bar__action{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.s-discussion-action-bar__action{padding-right:.75rem;padding-left:.75rem}}.s-discussion-action-bar__action-label{display:none;display:block;color:#0b7497}.s-page-not-found{padding-top:7.5rem;padding-bottom:7.5rem;background-color:#fff;text-align:center}.s-page-not-found__title{position:relative;margin-bottom:4.5rem;padding-top:64px;color:#0b7497;font-size:1.5rem;font-weight:700}.s-page-not-found__title:before{display:block;content:\" \";position:absolute;top:0;left:50%;width:4px;height:4px;margin-left:-38px;box-shadow:12px 4px 0 #ffeb3b,24px 4px 0 red,28px 4px 0 red,32px 4px 0 red,16px 8px 0 #ffeb3b,20px 8px 0 red,24px 8px 0 red,28px 8px 0 #ffc107,32px 8px 0 #ffc107,36px 8px 0 #c8efff,20px 12px 0 #ffeb3b,24px 12px 0 #c8efff,28px 12px 0 #000,32px 12px 0 #c8efff,36px 12px 0 #03a9f4,68px 12px 0 #03a9f4,72px 12px 0 red,16px 16px 0 #c8efff,20px 16px 0 #c8efff,24px 16px 0 #c8efff,28px 16px 0 #c8efff,32px 16px 0 #c8efff,36px 16px 0 #3f51b5,40px 16px 0 #3f51b5,64px 16px 0 #3f51b5,68px 16px 0 #ffc107,72px 16px 0 red,76px 16px 0 #ffeb3b,12px 20px 0 #c8efff,16px 20px 0 #c8efff,20px 20px 0 #c8efff,24px 20px 0 #c8efff,28px 20px 0 #c8efff,32px 20px 0 #c8efff,36px 20px 0 #3f51b5,40px 20px 0 #9c27b0,60px 20px 0 #9c27b0,64px 20px 0 #3f51b5,12px 24px 0 #c8efff,16px 24px 0 #c8efff,24px 24px 0 #c8efff,28px 24px 0 #c8efff,32px 24px 0 #c8efff,36px 24px 0 #9c27b0,40px 24px 0 #9c27b0,44px 24px 0 #9c27b0,56px 24px 0 #9c27b0,60px 24px 0 #9c27b0,24px 28px 0 #c8efff,28px 28px 0 #c8efff,32px 28px 0 #c8efff,36px 28px 0 #c8efff,40px 28px 0 #c8efff,44px 28px 0 #c8efff,48px 28px 0 #c8efff,52px 28px 0 #c8efff,56px 28px 0 #c8efff,20px 32px 0 #c8efff,24px 32px 0 #c8efff,28px 32px 0 #c8efff,32px 32px 0 #c8efff,36px 32px 0 #c8efff,40px 32px 0 #c8efff,44px 32px 0 #c8efff,48px 32px 0 #c8efff,52px 32px 0 #c8efff,56px 32px 0 #c8efff,12px 36px 0 #c8efff,16px 36px 0 #c8efff,20px 36px 0 #c8efff,24px 36px 0 #c8efff,28px 36px 0 #c8efff,32px 36px 0 #c8efff,36px 36px 0 #c8efff,40px 36px 0 #c8efff,44px 36px 0 #c8efff,48px 36px 0 #c8efff,52px 36px 0 #c8efff,12px 40px 0 #c8efff,24px 40px 0 #c8efff,44px 40px 0 #c8efff,48px 40px 0 #c8efff,52px 40px 0 #c8efff,12px 44px 0 #c8efff,16px 44px 0 #c8efff,24px 44px 0 #c8efff,48px 44px 0 #c8efff,52px 44px 0 #c8efff,56px 44px 0 #c8efff,24px 48px 0 #c8efff,56px 48px 0 #c8efff,24px 52px 0 #c8efff,56px 52px 0 #c8efff}.s-page-not-found__thanks{color:#505050}.s-page-not-found__ascii,.s-page-not-found__ascii-short{margin-right:auto;margin-left:auto;overflow:hidden;font-family:monospace,monospace;font-size:9px;line-height:9px;text-align:left}.s-page-not-found__ascii{display:none;width:500px}@media print,screen and (min-width:40em){.s-page-not-found__ascii{display:block}}.s-page-not-found__ascii-short{width:120px}@media print,screen and (min-width:40em){.s-page-not-found__ascii-short{display:none}}.m-device-form{margin-bottom:1.5rem}.m-device-form__label{display:inline-block;margin-bottom:.5rem}.m-device-form__location-group{display:flex;flex-flow:row wrap}.m-device-form__location-address{flex:1 1 0px}.m-device-form__location-type{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.m-device-form__location-type{padding-right:.75rem;padding-left:.75rem}}.m-device-information{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;margin-bottom:1.5rem}.m-device-information__icon{flex:0 0 auto;padding-right:.75rem;color:#0f98c6;font-size:2.625rem;line-height:2.625rem}.m-device-information__device{flex:1 1 0px}.m-device-information__info{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap}.m-device-information__current-device{flex:0 0 auto;padding-right:.5rem;color:#0f98c6;font-weight:700}.m-device-information__name{flex:0 0 auto;color:#0a617f;font-weight:700}.m-device-information__status{flex:0 0 auto;margin-left:auto;font-size:.75rem;font-weight:400}.m-device-information__status-icon{color:#4bbc07}.m-device-information__user-agent{color:#505050}.s-devices-settings{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;padding:.75rem}.s-devices-settings__device,.s-devices-settings__info{flex:0 0 100%;max-width:100%}.s-devices-settings__device{margin-top:.75rem;margin-bottom:.75rem}@media print,screen and (min-width:64em){.s-devices-settings__info{flex:0 0 100%;padding-right:.625rem;padding-left:.625rem;max-width:100%}}@media print,screen and (min-width:64em) and (min-width:40em){.s-devices-settings__info{padding-right:.75rem;padding-left:.75rem}}@media print,screen and (min-width:64em){.s-devices-settings__device{flex:0 0 50%;padding-right:.625rem;padding-left:.625rem;max-width:50%}}@media print,screen and (min-width:64em) and (min-width:40em){.s-devices-settings__device{padding-right:.75rem;padding-left:.75rem}}.s-new-device-info{width:45rem;margin:auto;padding-top:7.875rem;padding-bottom:7.875rem;text-align:justify}.s-new-device-info__title{margin-bottom:1.5rem}.s-new-device-info__actions{margin-top:5.25rem;text-align:center}.s-new-device-info__learn-more{margin-right:1.5rem}.s-new-device-info__modal{width:100%;max-width:47.5rem;max-height:100%;overflow:auto}.s-validate-device{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;padding:.75rem}.s-validate-device__feedback{flex:0 0 100%;max-width:100%}.s-message-item{max-width:none;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;position:relative;align-items:center;min-height:2.625rem;padding-top:.5rem;padding-bottom:.5rem;background-color:#f3f3f3;font-size:.875rem}.s-message-item:before{display:block;content:\"\";position:absolute;top:0;bottom:0;width:7px;background:#c2c2c2}.s-message-item--pi-ugly:before{background:#ed0e47}.s-message-item--pi-bad:before{background:#d66291}.s-message-item--pi-good:before{background:#ebc064}.s-message-item--pi-super:before{background:#05b898}.s-message-item--draft,.s-message-item--unread{position:relative;background-color:#fff}.s-message-item--is-selected{background-color:#d9f2fe}.s-message-item__col-avatar{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;margin-left:7px}@media print,screen and (min-width:40em){.s-message-item__col-avatar{padding-right:.75rem;padding-left:.75rem}}.s-message-item__col-content{flex:1 1 0px;min-width:0}.s-message-item__col-date{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;color:#919191;text-align:right}@media print,screen and (min-width:40em){.s-message-item__col-date{padding-right:.75rem;padding-left:.75rem}}.s-message-item__col-select{display:none;flex:0 0 auto;padding-right:.625rem;padding-left:.625rem;margin-left:auto}@media print,screen and (min-width:40em){.s-message-item__col-select{padding-right:.75rem;padding-left:.75rem}}.s-message-item__content{max-width:1200px;margin-right:auto;margin-left:auto;display:flex;flex-flow:row wrap;align-items:center;min-height:2.625rem;color:#919191;font-weight:400}.s-message-item__content--draft{font-style:italic}.s-message-item__participants{flex:0 0 100%;max-width:100%;color:#0a617f;font-weight:600}.s-message-item__title{flex:1 1 0px;margin-right:.5rem}.s-message-item__subject{color:#2b2b2b}.s-message-item__file{flex:0 0 auto;padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.s-message-item__file{padding-right:.75rem;padding-left:.75rem}}.s-message-item__tags{flex:0 0 auto}.s-message-item__tag{display:inline-block;margin-right:.25rem}.s-message-item__tag:last-child{margin-right:0}@media print,screen and (min-width:40em){.s-message-item{padding-top:0;padding-bottom:0}.s-message-item__col-select{display:flex}.s-message-item__col-content{flex:1 1 0px}.s-message-item__author{flex:0 0 16.66667%;max-width:16.66667%;min-width:220px}.s-message-item__tags{flex:0 0 auto}}.m-message-selector{display:flex;align-items:center;justify-content:flex-end;max-width:1200px;height:2.625rem;margin:0 auto}.m-message-selector__checkbox{margin:auto .5rem}.s-view__message{border-top:2px solid #f9f9f9}.l-error-boundary{min-height:100vh;border-top:8px solid #0f98c6}.l-error-boundary__content{max-width:45rem;margin:auto}.l-error-boundary__header{padding-top:86px;padding-bottom:36px;text-align:center}.l-error-boundary__brand{height:42px}.l-error-boundary__body{margin:0 3rem}.l-error-boundary__description{margin-bottom:1.5rem}.l-error-boundary__error-details{margin-top:1.5rem;margin-bottom:1.5rem}.l-error-boundary__footer{padding:1.5rem 0;font-size:.875rem;text-align:center}\n/*! normalize-scss | MIT/GPLv2 License | bit.ly/normalize-scss */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}main{display:block}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}input{overflow:visible}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;display:table;padding:0;color:inherit;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}details{display:block}summary{display:list-item}menu{display:block}canvas{display:inline-block}[hidden],template{display:none}.foundation-mq{font-family:\"small=0em&medium-small=23.75em&medium=40em&large=64em&xlarge=75em&xxlarge=90em\"}html{box-sizing:border-box;font-size:100%}*,:after,:before{box-sizing:inherit}body{margin:0;padding:0;background:#bbe8fd;font-family:Open Sans,Helvetica Neue,Helvetica,Roboto,Arial,sans-serif;font-weight:400;line-height:1.5;color:#444;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}img{display:inline-block;vertical-align:middle;max-width:100%;height:auto;-ms-interpolation-mode:bicubic}textarea{height:auto;min-height:50px;border-radius:0}select{box-sizing:border-box;width:100%;border-radius:0}.map_canvas embed,.map_canvas img,.map_canvas object,.mqa-display embed,.mqa-display img,.mqa-display object{max-width:none!important}button{padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;border-radius:0;background:transparent;line-height:1}[data-whatinput=mouse] button{outline:0}pre{overflow:auto}.is-visible{display:block!important}.is-hidden{display:none!important}.menu{margin:0;list-style-type:none}.menu>li{display:table-cell;vertical-align:middle}[data-whatinput=mouse] .menu>li{outline:0}.menu>li>a{display:block;padding:.7rem 1rem;line-height:1}.menu a,.menu button,.menu input,.menu select{margin-bottom:0}.menu>li>a i,.menu>li>a i+span,.menu>li>a img,.menu>li>a img+span,.menu>li>a svg,.menu>li>a svg+span{vertical-align:middle}.menu>li>a i,.menu>li>a img,.menu>li>a svg{margin-right:.25rem;display:inline-block}.menu.horizontal>li,.menu>li{display:table-cell}.menu.expanded{display:table;width:100%;table-layout:fixed}.menu.expanded>li:first-child:last-child{width:100%}.menu.vertical>li{display:block}@media print,screen and (min-width:40em){.menu.medium-horizontal>li{display:table-cell}.menu.medium-expanded{display:table;width:100%;table-layout:fixed}.menu.medium-expanded>li:first-child:last-child{width:100%}.menu.medium-vertical>li{display:block}}@media print,screen and (min-width:64em){.menu.large-horizontal>li{display:table-cell}.menu.large-expanded{display:table;width:100%;table-layout:fixed}.menu.large-expanded>li:first-child:last-child{width:100%}.menu.large-vertical>li{display:block}}.menu.simple li{display:inline-block;vertical-align:top;line-height:1}.menu.simple a{padding:0}.menu.simple li{margin-left:0;margin-right:1rem}.menu.simple.align-right li{margin-right:0;margin-left:1rem}.menu.align-right:after,.menu.align-right:before{display:table;content:\" \"}.menu.align-right:after{clear:both}.menu.align-right>li{float:right}.menu.icon-top>li>a{text-align:center}.menu.icon-top>li>a i,.menu.icon-top>li>a img,.menu.icon-top>li>a svg{display:block;margin:0 auto .25rem}.menu.icon-top.vertical a>span{margin:auto}.menu.nested{margin-left:1rem}.menu .active>a{background:#0f98c6;color:#fefefe}.menu.menu-bordered li{border:1px solid #e6e6e6}.menu.menu-bordered li:not(:first-child){border-top:0}.menu.menu-hover li:hover{background-color:#e6e6e6}.menu-text{padding:.7rem 1rem;font-weight:700;line-height:1;color:inherit}.menu-centered{text-align:center}.menu-centered>.menu{display:inline-block;vertical-align:top}.no-js [data-responsive-menu] ul{display:none}.menu-icon{position:relative;display:inline-block;vertical-align:middle;width:20px;height:16px;cursor:pointer}.menu-icon:after{position:absolute;top:0;left:0;display:block;width:100%;height:2px;background:#fefefe;box-shadow:0 7px 0 #fefefe,0 14px 0 #fefefe;content:\"\"}.menu-icon:hover:after{background:#cacaca;box-shadow:0 7px 0 #cacaca,0 14px 0 #cacaca}.menu-icon.dark{position:relative;display:inline-block;vertical-align:middle;width:20px;height:16px;cursor:pointer}.menu-icon.dark:after{position:absolute;top:0;left:0;display:block;width:100%;height:2px;background:#0a0a0a;box-shadow:0 7px 0 #0a0a0a,0 14px 0 #0a0a0a;content:\"\"}.menu-icon.dark:hover:after{background:#8a8a8a;box-shadow:0 7px 0 #8a8a8a,0 14px 0 #8a8a8a}.is-off-canvas-open{overflow:hidden}.js-off-canvas-overlay{position:absolute;top:0;left:0;width:100%;height:100%;transition:opacity .5s ease,visibility .5s ease;background:hsla(0,0%,99.6%,.25);opacity:0;visibility:hidden;overflow:hidden}.js-off-canvas-overlay.is-visible{opacity:1;visibility:visible}.js-off-canvas-overlay.is-closable{cursor:pointer}.js-off-canvas-overlay.is-overlay-absolute{position:absolute}.js-off-canvas-overlay.is-overlay-fixed{position:fixed}.off-canvas-wrapper{position:relative;overflow:hidden}.off-canvas{position:fixed;z-index:1;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;background:#e6e6e6}[data-whatinput=mouse] .off-canvas{outline:0}.off-canvas.is-transition-overlap{z-index:10}.off-canvas.is-transition-overlap.is-open{box-shadow:0 0 10px rgba(10,10,10,.7)}.off-canvas.is-open{-webkit-transform:translate(0);transform:translate(0)}.off-canvas-absolute{position:absolute;z-index:1;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;background:#e6e6e6}[data-whatinput=mouse] .off-canvas-absolute{outline:0}.off-canvas-absolute.is-transition-overlap{z-index:10}.off-canvas-absolute.is-transition-overlap.is-open{box-shadow:0 0 10px rgba(10,10,10,.7)}.off-canvas-absolute.is-open{-webkit-transform:translate(0);transform:translate(0)}.position-left{top:0;left:0;width:250px;height:100%;-webkit-transform:translateX(-250px);transform:translateX(-250px);overflow-y:auto}.position-left.is-open~.off-canvas-content{-webkit-transform:translateX(250px);transform:translateX(250px)}.position-left.is-transition-push:after{position:absolute;top:0;right:0;height:100%;width:1px;box-shadow:0 0 10px rgba(10,10,10,.7);content:\" \"}.position-left.is-transition-overlap.is-open~.off-canvas-content{-webkit-transform:none;transform:none}.position-right{top:0;right:0;width:250px;height:100%;-webkit-transform:translateX(250px);transform:translateX(250px);overflow-y:auto}.position-right.is-open~.off-canvas-content{-webkit-transform:translateX(-250px);transform:translateX(-250px)}.position-right.is-transition-push:after{position:absolute;top:0;left:0;height:100%;width:1px;box-shadow:0 0 10px rgba(10,10,10,.7);content:\" \"}.position-right.is-transition-overlap.is-open~.off-canvas-content{-webkit-transform:none;transform:none}.position-top{top:0;left:0;width:100%;height:250px;-webkit-transform:translateY(-250px);transform:translateY(-250px);overflow-x:auto}.position-top.is-open~.off-canvas-content{-webkit-transform:translateY(250px);transform:translateY(250px)}.position-top.is-transition-push:after{position:absolute;bottom:0;left:0;height:1px;width:100%;box-shadow:0 0 10px rgba(10,10,10,.7);content:\" \"}.position-top.is-transition-overlap.is-open~.off-canvas-content{-webkit-transform:none;transform:none}.position-bottom{bottom:0;left:0;width:100%;height:250px;-webkit-transform:translateY(250px);transform:translateY(250px);overflow-x:auto}.position-bottom.is-open~.off-canvas-content{-webkit-transform:translateY(-250px);transform:translateY(-250px)}.position-bottom.is-transition-push:after{position:absolute;top:0;left:0;height:1px;width:100%;box-shadow:0 0 10px rgba(10,10,10,.7);content:\" \"}.position-bottom.is-transition-overlap.is-open~.off-canvas-content{-webkit-transform:none;transform:none}.off-canvas-content{transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden}@media print,screen and (min-width:40em){.position-left.reveal-for-medium{-webkit-transform:none;transform:none;z-index:1}.position-left.reveal-for-medium~.off-canvas-content{margin-left:250px}.position-right.reveal-for-medium{-webkit-transform:none;transform:none;z-index:1}.position-right.reveal-for-medium~.off-canvas-content{margin-right:250px}.position-top.reveal-for-medium{-webkit-transform:none;transform:none;z-index:1}.position-top.reveal-for-medium~.off-canvas-content{margin-top:250px}.position-bottom.reveal-for-medium{-webkit-transform:none;transform:none;z-index:1}.position-bottom.reveal-for-medium~.off-canvas-content{margin-bottom:250px}}@media print,screen and (min-width:64em){.position-left.reveal-for-large{-webkit-transform:none;transform:none;z-index:1}.position-left.reveal-for-large~.off-canvas-content{margin-left:250px}.position-right.reveal-for-large{-webkit-transform:none;transform:none;z-index:1}.position-right.reveal-for-large~.off-canvas-content{margin-right:250px}.position-top.reveal-for-large{-webkit-transform:none;transform:none;z-index:1}.position-top.reveal-for-large~.off-canvas-content{margin-top:250px}.position-bottom.reveal-for-large{-webkit-transform:none;transform:none;z-index:1}.position-bottom.reveal-for-large~.off-canvas-content{margin-bottom:250px}}.progress{height:1rem;margin-bottom:1rem;border-radius:0;background-color:#cacaca}.progress.primary .progress-meter{background-color:#0f98c6}.progress.secondary .progress-meter{background-color:#13ba95}.progress.success .progress-meter{background-color:#3adb76}.progress.warning .progress-meter{background-color:#ffae00}.progress.alert .progress-meter{background-color:#e43737}.progress.disabled .progress-meter{background-color:#ccc}.progress-meter{position:relative;display:block;width:0;height:100%;background-color:#0f98c6}.progress-meter-text{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;margin:0;font-size:.75rem;font-weight:700;color:#fefefe;white-space:nowrap}.hide{display:none!important}.invisible{visibility:hidden}@media screen and (max-width:23.6875em){.hide-for-small-only{display:none!important}}@media screen and (max-width:0em),screen and (min-width:23.75em){.show-for-small-only{display:none!important}}@media print,screen and (min-width:40em){.hide-for-medium{display:none!important}}@media screen and (max-width:39.9375em){.show-for-medium{display:none!important}}@media screen and (min-width:40em) and (max-width:63.9375em){.hide-for-medium-only{display:none!important}}@media screen and (max-width:39.9375em),screen and (min-width:64em){.show-for-medium-only{display:none!important}}@media print,screen and (min-width:64em){.hide-for-large{display:none!important}}@media screen and (max-width:63.9375em){.show-for-large{display:none!important}}@media screen and (min-width:64em) and (max-width:74.9375em){.hide-for-large-only{display:none!important}}@media screen and (max-width:63.9375em),screen and (min-width:75em){.show-for-large-only{display:none!important}}.show-for-sr,.show-on-focus{position:absolute!important;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}.show-on-focus:active,.show-on-focus:focus{position:static!important;width:auto;height:auto;overflow:visible;clip:auto}.hide-for-portrait,.show-for-landscape{display:block!important}@media screen and (orientation:landscape){.hide-for-portrait,.show-for-landscape{display:block!important}}@media screen and (orientation:portrait){.hide-for-portrait,.show-for-landscape{display:none!important}}.hide-for-landscape,.show-for-portrait{display:none!important}@media screen and (orientation:landscape){.hide-for-landscape,.show-for-portrait{display:none!important}}@media screen and (orientation:portrait){.hide-for-landscape,.show-for-portrait{display:block!important}}.float-left{float:left!important}.float-right{float:right!important}.float-center{display:block;margin-right:auto;margin-left:auto}.clearfix:after,.clearfix:before{display:table;content:\" \"}.clearfix:after{clear:both}:focus{outline:none}button,input,optgroup,select,textarea{font-family:Open Sans,Helvetica Neue,Helvetica,Roboto,Arial,sans-serif}input:-moz-submit-invalid,input:-moz-ui-invalid,input:invalid,input:required{box-shadow:none}fieldset{margin:0;padding:0;border:0}legend{max-width:100%;margin:0}dd,dl,li,ul{list-style:none;margin:0;padding:0}pre{font-family:inherit;font-size:inherit;line-height:inherit;white-space:pre-wrap;word-wrap:break-word}blockquote,pre{margin:0}@media print,screen and (min-width:64em){html{font-size:88%}}\n"
  },
  {
    "path": "src/frontend/web_application/.babelrc",
    "content": "{\n  \"env\": {\n    \"test\": {\n      \"presets\": [\n        [\n          \"@babel/preset-env\",\n          { \"modules\": \"auto\", \"targets\": { \"node\": \"current\" } }\n        ],\n        \"@babel/preset-react\",\n        \"@babel/preset-typescript\"\n      ],\n      \"plugins\": [\n        \"babel-plugin-dynamic-import-node\",\n        [\"@babel/plugin-proposal-decorators\", { \"legacy\": true }],\n        [\"@babel/plugin-proposal-class-properties\", { \"loose\": true }]\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/.dockerignore",
    "content": "node_modules\ndist\n"
  },
  {
    "path": "src/frontend/web_application/.eslintrc.json",
    "content": "{\n  \"extends\": [\n    \"airbnb\",\n    \"prettier\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:import/typescript\",\n    \"plugin:jest-dom/recommended\"\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@typescript-eslint\", \"jest-dom\"],\n  \"root\": true,\n  \"env\": {\n    \"browser\": true,\n    \"worker\": true,\n    \"es6\": true,\n    \"jest\": true,\n    \"protractor\": true\n  },\n  \"settings\": {\n    \"import/resolver\": { \"typescript\": {} },\n    \"import/extensions\": [\".js\", \".jsx\", \".ts\", \".tsx\", \".d.ts\"]\n  },\n  \"rules\": {\n    \"arrow-body-style\": \"warn\",\n    // usefull until UNSAFE_componentWill... is removed\n    \"camelcase\": \"warn\",\n    // tmp: FC migration\n    \"class-methods-use-this\": \"warn\",\n    \"default-case-last\": \"off\",\n    \"default-param-last\": \"off\",\n    \"import/extensions\": [\n      \"error\",\n      \"ignorePackages\",\n      {\n        \"js\": \"never\",\n        \"jsx\": \"never\",\n        \"ts\": \"never\",\n        \"tsx\": \"never\"\n      }\n    ],\n    \"import/no-cycle\": [\"error\", { \"maxDepth\": 1 }],\n    \"import/no-extraneous-dependencies\": [\n      \"error\",\n      {\n        \"devDependencies\": [\n          \"**/*.spec.js\",\n          \"**/*.spec.jsx\",\n          \"**/*.spec.ts\",\n          \"**/*.spec.tsx\"\n        ]\n      }\n    ],\n    \"import/prefer-default-export\": 0,\n    \"no-plusplus\": \"off\",\n    \"prefer-destructuring\": \"warn\",\n    \"jsx-a11y/label-has-associated-control\": [\n      \"error\",\n      {\n        \"labelComponents\": [\"Label\"],\n        \"controlComponents\": [\"Field\"]\n      }\n    ],\n    \"jsx-a11y/anchor-is-valid\": [\n      \"warn\",\n      {\n        \"components\": [\"Link\"],\n        \"specialLink\": [\"to\"],\n        \"aspects\": [\"noHref\", \"invalidHref\", \"preferButton\"]\n      }\n    ],\n    \"react/jsx-filename-extension\": [\n      \"error\",\n      { \"extensions\": [\".jsx\", \".tsx\"] }\n    ],\n    \"no-async-promise-executor\": \"warn\",\n    \"react/destructuring-assignment\": \"off\",\n    // tmp: FC migration\n    \"react/forbid-prop-types\": \"warn\",\n    // useful until babel compile </> directly\n    \"react/jsx-fragments\": \"off\",\n    \"react/jsx-props-no-spreading\": \"off\",\n    \"react/no-array-index-key\": \"off\",\n    \"react/no-unescaped-entities\": [\n      \"warn\",\n      { \"forbid\": [\">\", \"\\\"\", \"'\", \"}\"] }\n    ],\n    // tmp: FC migration\n    \"react/no-unused-class-component-methods\": \"warn\",\n    \"react/no-unused-prop-types\": \"warn\",\n    \"react/no-unused-state\": \"warn\",\n    \"react/prefer-stateless-function\": \"warn\",\n    \"react/prop-types\": \"off\",\n    \"react/require-default-props\": [\n      \"warn\",\n      { \"ignoreFunctionalComponents\": true }\n    ],\n    \"react/static-property-placement\": [\"warn\", \"static public field\"],\n    \"react/state-in-constructor\": \"off\",\n    \"prefer-promise-reject-errors\": \"warn\",\n    // ---\n    //  compat prettier\n    \"react/jsx-curly-newline\": \"off\",\n    \"react/jsx-indent\": \"off\",\n    \"react/jsx-one-expression-per-line\": \"off\",\n    \"react/jsx-wrap-multilines\": \"off\",\n    // ---\n    \"@typescript-eslint/ban-ts-comment\": \"warn\",\n    \"@typescript-eslint/no-empty-function\": \"off\",\n    // disabled base rule since it report incorrect error\n    \"no-use-before-define\": \"off\",\n    \"@typescript-eslint/no-use-before-define\": [\"error\"]\n  },\n  \"globals\": {\n    \"inject\": true,\n    \"Foundation\": true,\n    \"jQuery\": true,\n    \"BUILD_TARGET\": true,\n    \"SERVER_API\": true,\n    \"CALIOPEN_ENV\": true,\n    \"CALIOPEN_OPTIONS\": true\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/.gitignore",
    "content": "# dependencies\nnode_modules\n\n# testing\ncoverage\n\n# production\nbuild\ndist\n\n# misc\n.DS_Store\n.env\nnpm-debug.log\n\n# build targets\n.kotatsu\n\n# lingui\nlocale/_build\nlocale/**/*.ts\nlocale/**/*.d.ts\n"
  },
  {
    "path": "src/frontend/web_application/.prettierignore",
    "content": "dist/\nlocale/\nnode_modules/\ntsconfig.json\n"
  },
  {
    "path": "src/frontend/web_application/.prettierrc.js",
    "content": "module.exports = {\n  singleQuote: true,\n};\n"
  },
  {
    "path": "src/frontend/web_application/.stylelintrc.yml",
    "content": "extends:\n  - stylelint-config-standard-scss\n  - stylelint-config-prettier\n\nplugins:\n  - stylelint-scss\n  - stylelint-order\n\n# Rule Configuration\nrules:\n  # Override standard rules\n  declaration-block-no-redundant-longhand-properties: null\n  keyframes-name-pattern: null\n  selector-class-pattern: null\n  scss/comment-no-empty: null\n  scss/dollar-variable-pattern: null\n  scss/at-mixin-pattern: null\n  # Rules to fix\n  block-no-empty:\n    - true\n    - severity: 'warning'\n  no-descending-specificity:\n    - true\n    - severity: 'warning'\n  selector-no-vendor-prefix:\n    - true\n    - severity: 'warning'\n  scss/no-global-function-names:\n    - true\n    - severity: 'warning'\n\n  # prettier incompatibility\n  scss/operator-no-newline-after: null\n\n  # Caliopen specific\n\n  # Allow a bigger nesting depth max in waiting of:\n  # https://github.com/sasstools/sass-lint/issues/603\n  max-nesting-depth:\n    - 3\n    - severity: 'warning'\n\n  # Name Formats\n  #\n  # selector-class-pattern: '(?![A-Z]|-{3}|_{3}|[^_]_[^_])' # hyphenatedbem\n  #\n  # rules from sass-lint not yet migrated:\n  # function-name-format:\n  #   [1, {\n  #     convention: '(?![A-Z]|-{3}|_{3}|[^_]_[^_])' # hyphenatedbem\n  #   }]\n  # mixin-name-format:\n  #   [1, {\n  #     convention: '(?![A-Z]|-{3}|_{3}|[^_]_[^_])' # hyphenatedbem\n  #   }]\n  # placeholder-name-format:\n  #   [1, {\n  #     convention: '(?![A-Z]|-{3}|_{3}|[^_]_[^_])' # hyphenatedbem\n  #   }]\n  # variable-name-format:\n  #   [1, {\n  #     convention: '(?![A-Z]|-{3}|_{3}|[^_]_[^_])' # hyphenatedbem\n  #   }]\n  #\n  selector-max-class:\n    - 2\n    - severity: 'warning'\n\n  # Property sort order, inspired from SMACCS\n  # https://github.com/sasstools/sass-lint/blob/develop/lib/config/property-sort-orders/smacss.yml\n  order/properties-order:\n    # Box\n    - 'display'\n    - 'content'\n\n    # Position\n    - 'position'\n    - 'top'\n    - 'right'\n    - 'bottom'\n    - 'left'\n    - 'z-index'\n\n    # Disposition\n    - 'vertical-align'\n    - 'float'\n    - 'clear'\n    - 'list-style'\n    - 'list-style-image'\n    - 'list-style-position'\n    - 'list-style-type'\n\n    - 'flex'\n    - 'flex-basis'\n    - 'flex-direction'\n    - 'flex-flow'\n    - 'flex-grow'\n    - 'flex-shrink'\n    - 'flex-wrap'\n    - 'align-content'\n    - 'align-items'\n    - 'align-self'\n    - 'justify-content'\n    - 'order'\n\n    - 'columns'\n    - 'column-gap'\n    - 'column-fill'\n    - 'column-rule'\n    - 'column-span'\n    - 'column-count'\n    - 'column-width'\n\n    # Dimension\n    - 'width'\n    - 'min-width'\n    - 'max-width'\n\n    - 'height'\n    - 'min-height'\n    - 'max-height'\n\n    - 'margin'\n    - 'margin-top'\n    - 'margin-right'\n    - 'margin-bottom'\n    - 'margin-left'\n\n    - 'padding'\n    - 'padding-top'\n    - 'padding-right'\n    - 'padding-bottom'\n    - 'padding-left'\n\n    # Appearance\n    - 'transform'\n    - 'transform-box'\n    - 'transform-origin'\n    - 'transform-style'\n\n    - 'transition'\n    - 'transition-delay'\n    - 'transition-duration'\n    - 'transition-property'\n    - 'transition-timing-function'\n\n    # Border\n    - 'border'\n    - 'border-top'\n    - 'border-right'\n    - 'border-bottom'\n    - 'border-left'\n    - 'border-width'\n    - 'border-top-width'\n    - 'border-right-width'\n    - 'border-bottom-width'\n    - 'border-left-width'\n\n    - 'border-style'\n    - 'border-top-style'\n    - 'border-right-style'\n    - 'border-bottom-style'\n    - 'border-left-style'\n\n    - 'border-radius'\n    - 'border-top-left-radius'\n    - 'border-top-right-radius'\n    - 'border-bottom-left-radius'\n    - 'border-bottom-right-radius'\n\n    - 'border-color'\n    - 'border-top-color'\n    - 'border-right-color'\n    - 'border-bottom-color'\n    - 'border-left-color'\n\n    - 'border-collapse'\n    - 'border-spacing'\n\n    - 'outline'\n    - 'outline-color'\n    - 'outline-offset'\n    - 'outline-style'\n    - 'outline-width'\n\n    # Background\n    - 'background'\n    - 'background-attachment'\n    - 'background-clip'\n    - 'background-color'\n    - 'background-image'\n    - 'background-repeat'\n    - 'background-position'\n    - 'background-size'\n\n    # Shadow\n    - 'box-shadow'\n\n    # Text\n    - 'overflow'\n    - 'text-overflow'\n\n    - 'color'\n\n    - 'font'\n    - 'font-family'\n    - 'font-size'\n    - 'font-smoothing'\n    - 'font-style'\n    - 'font-variant'\n    - 'font-weight'\n\n    - 'letter-spacing'\n    - 'line-height'\n\n    - 'text-align'\n    - 'text-decoration'\n    - 'text-indent'\n    - 'text-rendering'\n    - 'text-shadow'\n    - 'text-transform'\n    - 'text-wrap'\n\n    - 'white-space'\n    - 'word-spacing'\n\n    # Other\n    - 'caption-side'\n    - 'cursor'\n    - 'empty-cells'\n    - 'opacity'\n    - 'quotes'\n    - 'speak'\n    - 'table-layout'\n    - 'visibility'\n"
  },
  {
    "path": "src/frontend/web_application/CONTRIBUTING.md",
    "content": "# Contributing\n\n## Development\n\n### installation\n\n```\nyarn\n```\n\n### USAGE\n\n**For web**\n\n```\nyarn start\n```\n\n**For web (without SSR)**\n\n```\nyarn start:dev\n```\n\n**For desktop**\n\n```\nyarn start:desktop\n```\n\nOpen console with `ctrl+maj+I`.  \nTo see changes:`ctrl+R` .\n"
  },
  {
    "path": "src/frontend/web_application/Dockerfile",
    "content": "# This file creates a container that runs a {package} caliopen frontend\n# Important:\n# Author: Caliopen\n# Date: 2016-01-05\n\nFROM node:16 as builder\nMAINTAINER Caliopen\n\nADD . /srv/caliopen/frontend/\nWORKDIR /srv/caliopen/frontend/\nRUN yarn install\nRUN yarn run release\n\nFROM node:16-alpine\nMAINTAINER Caliopen\n\nWORKDIR /srv/caliopen/frontend/\nCOPY --from=builder /srv/caliopen/frontend/dist ./dist\nCOPY --from=builder /srv/caliopen/frontend/bin ./bin\nCOPY --from=builder /srv/caliopen/frontend/public ./public\nCOPY --from=builder /srv/caliopen/frontend/package.json .\nCOPY --from=builder /srv/caliopen/frontend/yarn.lock .\nENV NODE_ENV=production\nRUN yarn install\n\nEXPOSE 4000\n\nCMD [\"yarn\", \"run\", \"start:prod\"]\n"
  },
  {
    "path": "src/frontend/web_application/Dockerfile.caliopen-node",
    "content": "# Image to do fonctionnal Selenium tests locally, with Chrome and Firefox\n# Author: Caliopen\n# Date: 2018-08-03\n\nFROM ubuntu:16.04\n\nRUN apt-get update && apt-get install -y curl wget unzip software-properties-common\n\n# node\nRUN curl -sL https://deb.nodesource.com/setup_12.x | bash -\n\n# yarn\nRUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \\\necho \"deb https://dl.yarnpkg.com/debian/ stable main\" | tee /etc/apt/sources.list.d/yarn.list\n\n# Install chromedriver\nRUN wget -N http://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip -P ~/ && \\\nunzip ~/chromedriver_linux64.zip -d ~/ && \\\nrm ~/chromedriver_linux64.zip && \\\nmv -f ~/chromedriver /usr/local/bin/chromedriver && \\\nchmod 0755 /usr/local/bin/chromedriver\n\n# Install geckodriver\nRUN wget https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz && \\\ntar -xvzf geckodriver* && \\\nrm geckodriver-v0.21.0-linux64.tar.gz && \\\nchmod +x geckodriver && \\\nmv geckodriver /usr/local/bin/\n\n# Install chrome\nRUN curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add &&\\\necho \"deb http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google-chrome.list\n\n# We need firefox 62 https://bugzilla.mozilla.org/show_bug.cgi?id=1447977\n# After the firefox beta releases we can drop this line\nRUN add-apt-repository ppa:mozillateam/firefox-next\n\nRUN apt-get update && apt-get install -y make g++ nodejs yarn default-jdk google-chrome-stable firefox\n\nENTRYPOINT [\"node\"]\n"
  },
  {
    "path": "src/frontend/web_application/README.md",
    "content": "# Caliopen frontend\n\n**Important note:**\n\nPlease refer to global documentation on [readthedocs](http://caliopen.readthedocs.io/en/latest/) the following documentation wil not be maintained anymore. Some informations are still useful.\n\nThis subtree is meant for building and serving CaliOpen frontend.\n\n- serving html, js and static files\n- build web application\n- build or run desktop applications\n- build or run mobile devices applications\n\n## Prerequisite\n\n- [yarn](https://yarnpkg.com/en/docs/install)\n\n_(make sure you have an up-to-date version)_\n\n## Usage (production ready)\n\nThis will install dependencies then start the web server. Depending you custom options, you will be\nable to access to Caliopen interface via http://0.0.0.0:4000\n\n```\nexport NODE_ENV=production\nyarn\nbin/server\n```\n\n### Web Server Options\n\nYou can set instance config via environment variables and a json file:\n\n```\nexport CALIOPEN_API_HOSTNAME=api.foobar.tld\n```\n\n```\nNODE_ENV=production bin/server --config=<path-to-file>.json\n```\n\nThe default variables are set in `config/server.default.js`.\nThe environment variables are set in `config/server.env-var.js`.\n\nWe **strongly** recommand to overide `CALIOPEN_COOKIE_SECRET` and `CALIOPEN_SEAL_SECRET`.\n\nThe precedence of config definitions:\n\n> env var > custom file > defaults\n\nConfig for the client is not dynamic and can be done **in release** phase (see below).\n\n## Release\n\n```\nNODE_ENV=production yarn release\n```\n\nThis will build the different packages to run Caliopen web server in the dist directory.\n\n### Client options\n\nDue to webpack restrictions, config can be pass using environment variables only.\n\nThe default variables and environment variables definitions are set in `config/client.default.js`.\n\nWe **strongly** recommand to overide `CALIOPEN_PIWIK_SITE_ID`.\n\n## Code architecture\n\nAll the things related to react follows [this guide](https://medium.com/@alexmngn/how-to-better-organize-your-react-applications-2fd3ea1920f1#.rwqbggzgf) in `src` folder.\n\nEach build target has its folder:\n\n- server (the web server providing html pages thanks to SSR and the javascript browser client)\n- electron (for linux, macos and windows)\n\nThe `index.html` is generated using webpack and a `template`.\n\nAnd each target has its `webpack` config.\n\n## Code quality\n\nIn order to make sure the code is relatively valid and consistent, some linting and formating checks are done on CI.\nYou can run `yarn lint` to check if valid before sending a pull request.\nTo fix automatically your code, run those 2 commands (or use eslint and prettier in your dev env):\n\n```\nyarn lint:js --fix\nyarn prettier --write .\n```\n"
  },
  {
    "path": "src/frontend/web_application/__mocks__/fileMock.js",
    "content": "module.exports = 'test-file-stub';\n"
  },
  {
    "path": "src/frontend/web_application/__mocks__/styleMock.js",
    "content": "module.exports = {};\n"
  },
  {
    "path": "src/frontend/web_application/bin/dev-server",
    "content": "#!/usr/bin/env node\n\nconst path = require('path');\nconst fs = require('fs');\nconst webpack = require('webpack');\nconst winston = require('winston');\nconst fork = require('child_process').fork;\nconst config = require('../webpack/webpack.config.server');\n\nconst logger = winston.createLogger({\n  format: winston.format.combine(\n    winston.format.colorize(),\n    winston.format.timestamp(),\n    winston.format.label({ label: 'webpack-server' }),\n    winston.format.align(),\n    winston.format.printf(info => `${info.timestamp} ${info.level} ${info.label} - ${info.message}`),\n  ),\n  transports: [\n    new winston.transports.Console(),\n  ],\n});\n\nlet caliopen;\nconst compiler = webpack(config);\nnew webpack.ProgressPlugin({ profile: true }).apply(compiler)\n\nconst start = () => {\n  const watching = compiler.watch({\n    aggregateTimeout: 300,\n    poll: undefined,\n  }, (err, stats) => {\n    if (caliopen) {\n      caliopen.kill();\n    }\n\n    logger.debug(stats.toString({ chunk: false, colors: true }));\n    if (err || stats.hasErrors()) {\n      logger.error('unable to compile');\n      console.log(stats.compilation.errors);\n\n      return;\n    }\n    logger.info('done');\n    try {\n      caliopen = fork(path.join(__dirname, '../dist/server/index.js'), { cwd: path.join(__dirname, '../') });\n    } catch (forkErr) {\n      logger.error('Unable to start caliopen express server. Wait for rebuild.');\n      logger.debug(forkErr);\n      if (caliopen) {\n        caliopen.kill();\n      }\n    }\n  });\n};\n\nconst checkForPrerequisites = () => new Promise((resolve) => {\n  fs.access(path.resolve(__dirname, '../dist/server/template.html'), fs.constants.R_OK, (err) => {\n    if (!err) {\n      resolve(true);\n    }\n  });\n\n  const intervalId = setInterval(() => {\n    logger.info('check for presence of template.html in dist/server/ directory');\n    fs.access(path.resolve(__dirname, '../dist/server/template.html'), fs.constants.R_OK, (err) => {\n      if (!err) {\n        clearInterval(intervalId);\n        resolve(true);\n      }\n    });\n  }, 30 * 1000);\n});\n\ncheckForPrerequisites().then(() => {\n  logger.info('Prerequisites OK. Start server compilation');\n  start();\n});\n"
  },
  {
    "path": "src/frontend/web_application/bin/server",
    "content": "#!/usr/bin/env node\n\nrequire('../dist/server');\n"
  },
  {
    "path": "src/frontend/web_application/config/client.default.js",
    "content": "const { version } = require('../package.json');\n\nconst siteId =\n  process.env.CALIOPEN_PIWIK_SITE_ID &&\n  JSON.parse(process.env.CALIOPEN_PIWIK_SITE_ID);\n\nmodule.exports = {\n  // render current release version in the auth pages\n  version: process.env.CALIOPEN_VERSION || `v${version}`,\n  // optional display a motd in auth pages for the release\n  motd: process.env.CALIOPEN_MOTD,\n  piwik: {\n    siteId: siteId === false ? false : siteId || 6,\n  },\n};\n"
  },
  {
    "path": "src/frontend/web_application/config/server.default.js",
    "content": "module.exports = {\n  // the public url of the instance\n  protocol: 'http',\n  hostname: 'localhost',\n  port: 80,\n  // the internal url of the expressjs server\n  webServer: {\n    port: 4000,\n    hostname: undefined,\n  },\n  // the internal url of the backend\n  api: {\n    protocol: 'http',\n    hostname: 'api.dev.caliopen.org',\n    port: 31415,\n    checkCertificate: true,\n  },\n  cookie: {\n    secret: '_4+%J;_F&?#!+mR&IsYq:Xg4A*wvse',\n  },\n  seal: {\n    secret: 'D}(2$5q)#_#yKX90,+0d5?4**a6ws8e`', // at least 32 chars\n  },\n  maxBodySize: '5mb',\n  enableSyslog: false,\n};\n"
  },
  {
    "path": "src/frontend/web_application/config/server.env-var.js",
    "content": "module.exports = {\n  protocol: process.env.CALIOPEN_PROTOCOL,\n  hostname: process.env.CALIOPEN_HOSTNAME,\n  port: process.env.CALIOPEN_PORT,\n  webServer: {\n    port: process.env.CALIOPEN_WEB_SERVER_PORT,\n    hostname: process.env.CALIOPEN_WEB_SERVER_HOSTNAME,\n  },\n  api: {\n    protocol: process.env.CALIOPEN_API_PROTOCOL,\n    hostname: process.env.CALIOPEN_API_HOSTNAME,\n    port: process.env.CALIOPEN_API_PORT,\n    checkCertificate: process.env.CALIOPEN_CHECK_CERTIFICATE,\n  },\n  cookie: {\n    secret: process.env.CALIOPEN_COOKIE_SECRET,\n  },\n  seal: {\n    secret: process.env.CALIOPEN_SEAL_SECRET,\n  },\n  maxBodySize: process.env.CALIOPEN_MAX_BODY_SIZE,\n  enableSyslog: process.env.CALIOPEN_ENABLE_SYSLOG,\n};\n"
  },
  {
    "path": "src/frontend/web_application/lingui.config.ts",
    "content": "export default {\n  catalogs: [\n    {\n      path: '<rootDir>/locale/{locale}/messages',\n      include: ['src'],\n      exclude: ['**/node_modules/**'],\n    },\n  ],\n  compileNamespace: 'ts',\n  extractBabelOptions: {\n    presets: [\n      ['@babel/preset-env', { modules: 'auto', targets: { node: 'current' } }],\n      '@babel/preset-react',\n      '@babel/preset-typescript',\n    ],\n    plugins: [\n      'babel-plugin-dynamic-import-node',\n      ['@babel/plugin-proposal-decorators', { legacy: true }],\n      ['@babel/plugin-proposal-class-properties', { loose: true }],\n    ],\n  },\n  // compilerBabelOptions: {},\n  fallbackLocales: {\n    default: 'en',\n  },\n  format: 'minimal',\n  locales: ['de', 'en', 'es', 'fr'],\n  // extractors: [],\n  // orderBy: 'messageId',\n  // pseudoLocale: '',\n  // rootDir: '.',\n  // runtimeConfigModule: ['@lingui/core', 'i18n'],\n  sourceLocale: 'en',\n};\n"
  },
  {
    "path": "src/frontend/web_application/locale/de/messages.json",
    "content": "{\n  \"about.action.toggle-create\": \"\",\n  \"about.footer.link-blog\": \"\",\n  \"about.footer.link-privacy-policy\": \"\",\n  \"about.footer.link-source-code\": \"\",\n  \"about.footer.link-website\": \"\",\n  \"about.identity\": \"\",\n  \"about.page-title\": \"\",\n  \"about.purpose\": \"\",\n  \"about.purpose-signup.beta-descr\": \"\",\n  \"about.purpose-signup.descr\": \"\",\n  \"about.purpose-signup.title\": \"\",\n  \"app-loader.action.retry\": \"\",\n  \"app-loader.activity.beckett\": \"\",\n  \"app-loader.activity.cats\": \"\",\n  \"app-loader.activity.chicken\": \"\",\n  \"app-loader.activity.ipaddr\": \"\",\n  \"app-loader.activity.macaroons\": \"\",\n  \"app-loader.activity.newcat\": \"\",\n  \"app-loader.activity.oof\": \"\",\n  \"app-loader.activity.trolls\": \"\",\n  \"app-loader.feedback.failure\": \"\",\n  \"call-to-action.action.compose\": \"Schreiben\",\n  \"call-to-action.action.compose_contact\": \"An diesen Kontakt schreiben\",\n  \"call-to-action.action.create_contact\": \"Kontakt hinzufügen\",\n  \"collection-field-group.action.add\": \"Hinzufügen\",\n  \"confirm.action.cancel\": \"\",\n  \"confirm.action.confirm\": \"\",\n  \"contact-asociation.loading\": \"\",\n  \"contact-association.action.add-new-contact\": \"\",\n  \"contact-book.action.add\": \"\",\n  \"contact-book.action.delete\": \"\",\n  \"contact-book.action.import\": \"\",\n  \"contact-book.action.manage-tags\": \"\",\n  \"contact-book.action.select\": \"\",\n  \"contact-book.action.select-all\": \"\",\n  \"contact-book.confirm-delete.content\": \"\",\n  \"contact-book.confirm-delete.title\": \"\",\n  \"contact-book.contacts.selected\": \"\",\n  \"contact-book.contacts.title\": \"\",\n  \"contact-book.groups.title\": \"\",\n  \"contact-book.my-contact-details\": \"\",\n  \"contact-book.tags.action.edit-groups\": \"\",\n  \"contact.action-bar.label\": \"\",\n  \"contact.action.add-address\": \"\",\n  \"contact.action.add-identity\": \"\",\n  \"contact.action.add-organization\": \"\",\n  \"contact.action.add_new_field\": \"Hinzufügen\",\n  \"contact.action.cancel\": \"\",\n  \"contact.action.cancel_edit\": \"Abbrechen\",\n  \"contact.action.delete_contact\": \"Verwerfen\",\n  \"contact.action.edit_contact\": \"Kontakt bearbeiten\",\n  \"contact.action.edit_tags\": \"Labels bearbeiten\",\n  \"contact.action.save\": \"\",\n  \"contact.action.validate_edit\": \"Bestätitgen\",\n  \"contact.address_form.city.label\": \"Stadt\",\n  \"contact.address_form.country.label\": \"Land\",\n  \"contact.address_form.legend\": \"Postleitzahl\",\n  \"contact.address_form.postal_code.label\": \"PLZ\",\n  \"contact.address_form.region.label\": \"Region\",\n  \"contact.address_form.select_country\": \"Land wählen\",\n  \"contact.address_form.select_region\": \"Region wählen\",\n  \"contact.address_form.street.label\": \"Straße\",\n  \"contact.address_form.type.label\": \"Typ\",\n  \"contact.address_type.home\": \"Privat\",\n  \"contact.address_type.other\": \"Andere\",\n  \"contact.address_type.work\": \"Geschäftlich\",\n  \"contact.addresses\": \"\",\n  \"contact.confirm-delete.content\": \"\",\n  \"contact.confirm-delete.title\": \"\",\n  \"contact.contact-details.title\": \"\",\n  \"contact.edit_contact.title\": \"Kontakt bearbeiten\",\n  \"contact.email_form.address.label\": \"Adresse\",\n  \"contact.email_form.address.placeholder\": \"\",\n  \"contact.email_form.legend\": \"Email\",\n  \"contact.email_form.type.label\": \"Typ\",\n  \"contact.email_type.home\": \"Privat\",\n  \"contact.email_type.other\": \"Andere\",\n  \"contact.email_type.work\": \"Geschäftlich\",\n  \"contact.feedback.unable_to_delete\": \"\",\n  \"contact.feedback.unable_to_save\": \"\",\n  \"contact.feedback.unable_to_save_address_already_used\": \"\",\n  \"contact.form-selector.add_new_field.label\": \"Ein Feld hinzufügen\",\n  \"contact.form-selector.address_form.label\": \"Adresse\",\n  \"contact.form-selector.email_form.label\": \"Email\",\n  \"contact.form-selector.im_form.label\": \"IM\",\n  \"contact.form-selector.phone_form.label\": \"Telefon\",\n  \"contact.form.identity.not_mastodon\": \"\",\n  \"contact.form.identity.not_twitter\": \"\",\n  \"contact.identities\": \"\",\n  \"contact.identity_form.identity.label\": \"Identität\",\n  \"contact.identity_form.identity.placeholder\": \"\",\n  \"contact.identity_form.legend\": \"Identitäten\",\n  \"contact.identity_form.service.label\": \"Dienst\",\n  \"contact.im_form.address.label\": \"Adresse\",\n  \"contact.im_form.address.placeholder\": \"\",\n  \"contact.im_form.legend\": \"Instant messaging\",\n  \"contact.im_form.type.label\": \"Typ\",\n  \"contact.im_type.home\": \"Privat\",\n  \"contact.im_type.netmeeting\": \"Webkonferenz\",\n  \"contact.im_type.other\": \"Andere\",\n  \"contact.im_type.work\": \"Geschäftlich\",\n  \"contact.keys.title\": \"\",\n  \"contact.last-messages.title\": \"\",\n  \"contact.orga_form.department.label\": \"Abteilung\",\n  \"contact.orga_form.job_description.label\": \"Beruf\",\n  \"contact.orga_form.label.label\": \"Label\",\n  \"contact.orga_form.legend\": \"Unternehmen\",\n  \"contact.orga_form.name.label\": \"Name\",\n  \"contact.orga_form.remove-button\": \"\",\n  \"contact.orga_form.title.label\": \"Title\",\n  \"contact.organization\": \"\",\n  \"contact.organizations\": \"\",\n  \"contact.phone_form.legend\": \"Telefon\",\n  \"contact.phone_form.number.label\": \"Telefonnummer\",\n  \"contact.phone_form.number.placeholder\": \"\",\n  \"contact.phone_form.type.label\": \"Type\",\n  \"contact.phone_type.home\": \"Privat\",\n  \"contact.phone_type.other\": \"Andere\",\n  \"contact.phone_type.work\": \"Beruf\",\n  \"contact.primary\": \"Primär\",\n  \"contact.profile.name_not_set\": \"(N/A)\",\n  \"contact.public_key_form.cancel\": \"\",\n  \"contact.public_key_form.confirm_delete.content\": \"\",\n  \"contact.public_key_form.confirm_delete.title\": \"\",\n  \"contact.public_key_form.delete_key\": \"\",\n  \"contact.public_key_form.key.label\": \"\",\n  \"contact.public_key_form.label.label\": \"\",\n  \"contact.public_key_form.legend\": \"\",\n  \"contact.public_key_form.validate\": \"\",\n  \"contact.public_key_list.download_key\": \"\",\n  \"contact.public_keys_list.add_key.label\": \"\",\n  \"contact_profile.action.edit_contact\": \"Bearbeiten\",\n  \"contact_profile.form.birthday.label\": \"Geburtsdatum\",\n  \"contact_profile.form.firstname.label\": \"Vorname\",\n  \"contact_profile.form.lastname.label\": \"Nachname\",\n  \"contact_profile.form.name-prefix.label\": \"Präfix\",\n  \"contact_profile.form.name-suffix.label\": \"Suffix\",\n  \"desktop.notification.new_messages\": \"\",\n  \"device.action.revoke\": \"Dieses Gerät widerrufen\",\n  \"device.action.save_changes\": \"Änderungen speichern\",\n  \"device.action.verify\": \"Überprüfen Sie dieses Gerät\",\n  \"device.current_device\": \"\",\n  \"device.feedback.revoke_success\": \"\",\n  \"device.feedback.save_success\": \"\",\n  \"device.feedback.send-validation-success\": \"\",\n  \"device.manage_form.name.label\": \"Name:\",\n  \"device.manage_form.type.label\": \"Gerätetyp:\",\n  \"device.type.desktop\": \"Desktop-Computer\",\n  \"device.type.laptop\": \"Laptop-Computer\",\n  \"device.type.other\": \"\",\n  \"device.type.smartphone\": \"Smartphone\",\n  \"device.type.tablet\": \"Tabletcomputer\",\n  \"device.validation.failed\": \"\",\n  \"device.validation.in_progress\": \"\",\n  \"device.validation.suceed\": \"\",\n  \"devices.feedback.unverified_device\": \"\",\n  \"devices.feedback.unverified_device_more\": \"\",\n  \"discussion-draft.last-messages\": \"\",\n  \"discussion.action.goto_unread_message\": \"\",\n  \"discussion.action.label\": \"Whole discussion\",\n  \"discussion.action.reply\": \"Reply\",\n  \"draft-message.action.fix-error-on-advanced-form\": \"\",\n  \"draft-message.action.send\": \"\",\n  \"draft-message.action.toggle-advanced\": \"\",\n  \"draft-message.encryption.ko\": \"\",\n  \"draft-message.encryption.ok\": \"\",\n  \"draft-message.errors.empty-body\": \"\",\n  \"draft-message.errors.invalid-participant\": \"\",\n  \"draft-message.errors.missing-identity\": \"\",\n  \"draft-message.form.contact-recipient-selector\": \"\",\n  \"draft-message.form.identity.label\": \"\",\n  \"draft-message.form.identity.placeholder\": \"\",\n  \"draft-message.form.placeholder.quick-reply\": \"\",\n  \"draft-message.form.placeholder.quick-reply-no-recipients\": \"\",\n  \"draft-message.form.placeholder.quick-start\": \"\",\n  \"draft-message.no-available-identities\": \"\",\n  \"draft.action.import_attachement\": \"\",\n  \"draft.action.import_attachement.encryption_warning\": \"\",\n  \"draft.attachement.form.descr\": \"\",\n  \"draft.feedback.send-error\": \"\",\n  \"encryption.ask-passphrase.error.invalid-passphrase\": \"\",\n  \"encryption.ask-passphrase.explain\": \"\",\n  \"encryption.ask-passphrase.validate\": \"\",\n  \"encryption.locked-message.primary-text\": \"\",\n  \"encryption.locked-message.status.decrypting\": \"\",\n  \"encryption.locked-message.status.error\": \"\",\n  \"encryption.locked-message.status.need_passphrase\": \"\",\n  \"encryption.locked-message.status.need_privatekey\": \"\",\n  \"encryption.locked-message.status.no-detail\": \"\",\n  \"encrytion.ask-passphrase.error.unknown\": \"\",\n  \"error-boundary.back-to-home\": \"\",\n  \"error-boundary.description\": \"\",\n  \"error-boundary.link-to-feedback\": \"\",\n  \"error-boundary.send-feedback\": \"\",\n  \"error-boundary.short-description\": \"\",\n  \"error-boundary.title\": \"\",\n  \"file.size.B\": \"\",\n  \"file.size.gB\": \"\",\n  \"file.size.kB\": \"\",\n  \"file.size.mB\": \"\",\n  \"file.size.tB\": \"\",\n  \"form-validation.required\": \"\",\n  \"general.action.cancel\": \"Abbrechen\",\n  \"general.action.load_more\": \"Mehr laden\",\n  \"header.actions.search\": \"\",\n  \"header.help.faq\": \"\",\n  \"header.help.feedback\": \"\",\n  \"header.help.last-changes\": \"\",\n  \"header.help.menu\": \"\",\n  \"header.help.privacy-policy\": \"\",\n  \"header.menu.account\": \"Konto\",\n  \"header.menu.agenda\": \"\",\n  \"header.menu.contact-association\": \"\",\n  \"header.menu.contacts\": \"Kontakte\",\n  \"header.menu.discussions\": \"Diskussionen\",\n  \"header.menu.files\": \"\",\n  \"header.menu.search\": \"Suche\",\n  \"header.menu.settings\": \"Einstellungen\",\n  \"header.menu.signin\": \"Anmelden\",\n  \"header.menu.signout\": \"Abmelden\",\n  \"import-contact.action.import\": \"Neuen Kontakt hinzufügen\",\n  \"import-contact.action.import_contacts\": \"Neue Kontakte hinzufügen\",\n  \"import-contact.feedback.error-contact\": \"Die Datei ist gültig aber neue Kontakte können nicht erstellt werden\",\n  \"import-contact.feedback.error-file\": \"Diese Datei kann nicht zum Importieren von Kontakten verwendet werden\",\n  \"import-contact.feedback.successfull\": \"Kontakte erfolgreich importiert\",\n  \"import-contact.feedback.unexpected-error\": \"Ein unerwarteter Fehler ist aufgetreten.\",\n  \"import-contact.form.button.close\": \"Schließen\",\n  \"import-contact.form.descr\": \"Sie können eine VCF- oder VCard-Datei importieren.\",\n  \"import-contact.form.success\": \"Erfolgreich importiert!\",\n  \"input-file-group.error.file_is_required\": \"\",\n  \"input-file-group.error.max_size\": \"\",\n  \"input-file-group.error.no_valid_ext\": \"Nur {0}-Dateien\",\n  \"input-file.add_a_file.label\": \"Datei hinzufügen\",\n  \"login.details.label\": \"Benutzername\",\n  \"login.details.title\": \"Benutzername:\",\n  \"message-list.action.delete\": \"Verwerfen\",\n  \"message-list.action.select_all\": \"\",\n  \"message-list.action.select_all_discussions\": \"\",\n  \"message-list.action.select_single_message\": \"\",\n  \"message-list.confirm-delete.content\": \"\",\n  \"message-list.confirm-delete.title\": \"\",\n  \"message-list.message.action.delete\": \"Verwerfen\",\n  \"message-list.message.action.mark_as_read\": \"Als gelesen markieren\",\n  \"message-list.message.action.mark_as_unread\": \"Als ungelesen markieren\",\n  \"message-list.message.action.reply\": \"Antworten\",\n  \"message-list.message.action.tags\": \"Tags\",\n  \"message-list.message.confirm-delete.content\": \"\",\n  \"message-list.message.confirm-delete.title\": \"\",\n  \"message-list.message.protocol.email\": \"Nachricht\",\n  \"message-list.message.received-on\": \"\",\n  \"message-list.selected\": \"\",\n  \"message.action.add-participant-to-contacts\": \"\",\n  \"message.action.add-to-contacts\": \"\",\n  \"message.attachment-encryption-not-available\": \"\",\n  \"message.compose.action.delete_attachement\": \"\",\n  \"message.compose.action.open_import_attachements\": \"\",\n  \"message.from\": \"\",\n  \"message.img.postal-card\": \"\",\n  \"message.img.simple-envelope\": \"\",\n  \"message.participants.me\": \"\",\n  \"message.pi.comportment\": \"\",\n  \"message.pi.context\": \"\",\n  \"message.pi.description.metaphor.encrypted\": \"\",\n  \"message.pi.description.metaphor.unencrypted\": \"\",\n  \"message.pi.label\": \"\",\n  \"message.pi.technic\": \"\",\n  \"message.to\": \"\",\n  \"messages.compose.action.remove-recipient\": \"Empfänger löschen\",\n  \"messages.compose.form.body.label\": \"Schreiben Sie Ihre Nachricht hier...\",\n  \"messages.compose.form.subject.label\": \"Betreff\",\n  \"messages.compose.form.to.label\": \"An\",\n  \"messages.participants.and_x_others\": \"\",\n  \"new-device-info.i-understand\": \"\",\n  \"new-device-info.learn-more\": \"\",\n  \"new-device-info.learn-more.content\": \"\",\n  \"new-device-info.modal.title\": \"\",\n  \"new-device-info.text\": \"\",\n  \"new-device-info.title\": \"\",\n  \"next-feature.generic\": \"\",\n  \"openpgp-key.download\": \"\",\n  \"openpgp.action.remove-key\": \"Löschen\",\n  \"openpgp.action.toggle-details\": \"Details anzeigen\",\n  \"openpgp.details.algorithm\": \"Algorithmus\",\n  \"openpgp.details.creation\": \"Erstellung\",\n  \"openpgp.details.expiration\": \"Ablauf\",\n  \"openpgp.details.identities\": \"Identitäten\",\n  \"openpgp.details.key-size\": \"Schlüssellänge\",\n  \"openpgp.details.status\": \"Status\",\n  \"openpgp.feedback.fingerprints-not-match\": \"Fingerabdrücke stimmen nicht überein\",\n  \"openpgp.feedback.need-passphrase\": \"\",\n  \"openpgp.feedback.private-key-required\": \"\",\n  \"openpgp.feedback.unable-read-private-key\": \"Der private Schlüssel konnte nicht gelesen werden\",\n  \"openpgp.feedback.wrong-passphrase\": \"\",\n  \"openpgp.private-key\": \"Geheimer Schlüssel\",\n  \"openpgp.public-key\": \"Öffentlicher Schlüssel\",\n  \"openpgp.status.expired\": \"Abgelaufen\",\n  \"openpgp.status.invalid\": \"Ungültig\",\n  \"openpgp.status.no_self_cert\": \"No self cert\",\n  \"openpgp.status.revoked\": \"Widerrufen\",\n  \"openpgp.status.valid\": \"Gültig\",\n  \"orga-details.job.desc-full\": \"{jobDesc} bei {orgaName} {department}\",\n  \"page_not_found.caliopen-ascii\": \"\",\n  \"page_not_found.caliopen-ascii-logo\": \"\",\n  \"page_not_found.page_title\": \"\",\n  \"page_not_found.thank_you\": \"\",\n  \"page_not_found.title\": \"\",\n  \"password.action.go_signin\": \"\",\n  \"password.details.action.change\": \"Verändern\",\n  \"password.details.password_strength.title\": \"Kennwortstärke:\",\n  \"password.forgot-form.action.login\": \"Ok\",\n  \"password.forgot-form.action.send\": \"Senden\",\n  \"password.forgot-form.cancel\": \"Abbrechen\",\n  \"password.forgot-form.instructions\": \"Geben Sie Ihren Benutzernamen ein und wir senden Ihnen die Anleitung per Email, um Ihre Kennwort zurücksetzen.\",\n  \"password.forgot-form.recovery_email.label\": \"\",\n  \"password.forgot-form.recovery_email.placeholder\": \"\",\n  \"password.forgot-form.success\": \"Geschafft! Sie werden in Kürze einen Email mit der Anleitung bekommen, um Ihre Kennwort zurücksetzen.\",\n  \"password.forgot-form.title\": \"Kennwort vergessen\",\n  \"password.forgot-form.username.label\": \"Benutzername\",\n  \"password.forgot-form.username.placeholder\": \"Benutzername\",\n  \"password.form.action.cancel\": \"Abbrechen\",\n  \"password.form.action.validate\": \"Veränderungen anwenden\",\n  \"password.form.current_password.label\": \"Aktuelles Kennwort:\",\n  \"password.form.current_password.placeholder\": \"Geben Sie Ihr aktuelles Kennwort ein\",\n  \"password.form.current_password.tip\": \"Das Kennwort, das Sie ersetzen möchten.\",\n  \"password.form.feedback.error-old-password\": \"\",\n  \"password.form.feedback.successfull\": \"\",\n  \"password.form.feedback.unexpected-error\": \"\",\n  \"password.form.new_password.label\": \"Neues Kennwort:\",\n  \"password.form.new_password.placeholder\": \"Kennwort\",\n  \"password.form.new_password.tip\": \"Das Kennwort, das Sie ab sofort verwenden möchten.\",\n  \"password.form.new_password_confirmation.error\": \"Kennwörter stimmen nicht überein\",\n  \"password.form.new_password_confirmation.label\": \"Neues Kennwort bestätigen:\",\n  \"password.form.new_password_confirmation.placeholder\": \"Kennwort\",\n  \"password.reset-form.success\": \"Erledigt!\",\n  \"password.reset-form.title\": \"Kennwort vergessen?\",\n  \"password_strength.feedback.moderate\": \"Stärke: mäßig\",\n  \"password_strength.feedback.strong\": \"Stärke: stark\",\n  \"password_strength.feedback.weak\": \"Stärke: schwach\",\n  \"passwords.form.error.empty\": \"\",\n  \"passwords.form.error.identifiants_mismatch\": \"\",\n  \"passwords.form.error.user_not_found\": \"\",\n  \"pwa.action.install\": \"\",\n  \"pwa.feedback.install-success\": \"\",\n  \"remote-identity-settings.unexpected-error\": \"\",\n  \"remote_identity.action.cancel\": \"Abbrechen\",\n  \"remote_identity.action.delete\": \"\",\n  \"remote_identity.action.edit\": \"\",\n  \"remote_identity.action.save\": \"\",\n  \"remote_identity.action.toggle-advanced-form\": \"\",\n  \"remote_identity.action.toggle-email-form\": \"\",\n  \"remote_identity.action.toggle-simple-form\": \"\",\n  \"remote_identity.add_account\": \"\",\n  \"remote_identity.choose-provider.title\": \"\",\n  \"remote_identity.confirm-delete.content\": \"\",\n  \"remote_identity.confirm-delete.title\": \"\",\n  \"remote_identity.create_help\": \"\",\n  \"remote_identity.email.help\": \"\",\n  \"remote_identity.form.identifier.error\": \"\",\n  \"remote_identity.form.identifier.error.uniqueness\": \"\",\n  \"remote_identity.form.identifier.label\": \"\",\n  \"remote_identity.form.identifier.placeholder\": \"\",\n  \"remote_identity.form.incomming_mail_server.label\": \"Posteingangsserver\",\n  \"remote_identity.form.inserver\": \"\",\n  \"remote_identity.form.mastodon.instance_label\": \"\",\n  \"remote_identity.form.outgoing_mail_server.label\": \"\",\n  \"remote_identity.form.outserver\": \"\",\n  \"remote_identity.form.password.error\": \"\",\n  \"remote_identity.form.password.label\": \"Kennwort:\",\n  \"remote_identity.form.port.label\": \"Port:\",\n  \"remote_identity.form.protocol.error\": \"\",\n  \"remote_identity.form.serverHostname.error\": \"\",\n  \"remote_identity.form.serverPort.error\": \"\",\n  \"remote_identity.form.username.error\": \"\",\n  \"remote_identity.form.username.label\": \"\",\n  \"remote_identity.gmail.help\": \"\",\n  \"remote_identity.how_to\": \"\",\n  \"remote_identity.last_connection\": \"\",\n  \"remote_identity.last_connection.never\": \"\",\n  \"remote_identity.mastodon.help\": \"\",\n  \"remote_identity.provider-logo\": \"\",\n  \"remote_identity.status.active\": \"\",\n  \"remote_identity.status.inactive\": \"\",\n  \"remote_identity.twitter.help\": \"\",\n  \"reply-form.in-reply-to\": \"Als Antwort auf: {0}\",\n  \"reset-password.form.errors.token_not_found\": \"\",\n  \"route.compose.label\": \"Schreiben\",\n  \"route.contact-association.label\": \"\",\n  \"route.contact-book.label\": \"\",\n  \"route.discussion.label\": \"\",\n  \"route.draft.label\": \"\",\n  \"route.new-contact.label\": \"Neuen Kontakt\",\n  \"route.search-results.label\": \"\",\n  \"route.settings.label.application\": \"Anwendung\",\n  \"route.settings.label.default\": \"Einstellungen\",\n  \"route.settings.label.devices\": \"\",\n  \"route.settings.label.identities\": \"Identitäten\",\n  \"route.settings.label.tags\": \"Tags\",\n  \"route.timeline.label\": \"\",\n  \"route.user.label.default\": \"Konto\",\n  \"route.user.label.profile\": \"Benutzerprofil\",\n  \"route.user.label.security\": \"Sicherheit\",\n  \"screenshot.desktop\": \"\",\n  \"screenshot.smartphone\": \"\",\n  \"search-results.actions.display-all\": \"\",\n  \"search-results.all\": \"\",\n  \"search-results.contacts\": \"\",\n  \"search-results.messages\": \"\",\n  \"search-results.preview.nb-contacts\": \"\",\n  \"search-results.preview.nb-messages\": \"\",\n  \"settings.application\": \"Anwendung\",\n  \"settings.contact.display_format.options.first_last\": \"Vorname, Nachname\",\n  \"settings.contact.display_format.options.last_first\": \"Nachname, Vorname\",\n  \"settings.contact.display_order_by.options.firstname\": \"Vorname\",\n  \"settings.contact.display_order_by.options.lastname\": \"Nachname\",\n  \"settings.contact.title\": \"Kontakteinstellungen\",\n  \"settings.contacts.display.label\": \"Anzeige\",\n  \"settings.contacts.order.label\": \"Sortierung nach\",\n  \"settings.desktop_notification.action.request-desktop_notification_permission\": \"Desktopbenachrichtigungen aktivieren\",\n  \"settings.desktop_notification.action.test_desktop_notification\": \"Desktopbenachrichtigungen überprüfen\",\n  \"settings.desktop_notification.desktop_notifications_enabled\": \"Desktopbenachrichtigungen aktiviert\",\n  \"settings.desktop_notification.disabled\": \"Desktopbenachrichtigungen sind deaktiviert, bitte überprüfen Sie Ihre Browser-Einstellungen\",\n  \"settings.desktop_notification.feedback.enabled\": \"Desktopbenachrichtigungen werden aktiviert\",\n  \"settings.desktop_notification.no_support\": \"Desktopbenachrichtigungen werden von Ihrem Browser nicht unterstützt\",\n  \"settings.desktop_notification.title\": \"Desktopbenachrichtigungen\",\n  \"settings.devices\": \"\",\n  \"settings.form.feedback.successfull\": \"\",\n  \"settings.form.feedback.unexpected-error\": \"\",\n  \"settings.identities\": \"Identitäten\",\n  \"settings.interface.language.label\": \"Sprache\",\n  \"settings.interface.language.options.de\": \"\",\n  \"settings.interface.language.options.en\": \"Englisch\",\n  \"settings.interface.language.options.fr\": \"Französisch\",\n  \"settings.interface.title\": \"Passen Sie Ihr Interface an\",\n  \"settings.message.display_format.label\": \"Display\",\n  \"settings.message.display_format.options.plain_text\": \"Einfacher Text\",\n  \"settings.message.display_format.options.rich_text\": \"HTML\",\n  \"settings.message.title\": \"Nachrichteinstellungen\",\n  \"settings.notification.delay_disappear.label\": \"Anzeigeverzögerung\",\n  \"settings.notification.delay_disappear.options.second\": \"{0} Sekunden\",\n  \"settings.notification.enabled.label\": \"Aktiviert\",\n  \"settings.notification.message_preview.label\": \"Nachrichtenvorschau\",\n  \"settings.notification.message_preview.options.always\": \"Immer\",\n  \"settings.notification.message_preview.options.off\": \"Aus\",\n  \"settings.notification.sound_enabled.label\": \"Töne aktiviert\",\n  \"settings.notification.title\": \"Benachrichtigungeneinstellungen\",\n  \"settings.presentation.update.action\": \"Einstellungen speichern\",\n  \"settings.signature.label\": \"Unterschrift\",\n  \"settings.signature.update.action\": \"Speichern\",\n  \"settings.signatures.title\": \"Ihr Unterschrift verändern\",\n  \"settings.tag.action.save-tag\": \"\",\n  \"settings.tag.form.error.create_fail\": \"\",\n  \"settings.tag.form.error.update_failed\": \"\",\n  \"settings.tags\": \"Tags\",\n  \"settings.tags.action.delete\": \"\",\n  \"settings.tags.title\": \"\",\n  \"settings.tags.title.create\": \"\",\n  \"signin.action.forgot_password\": \"Kennwort vergessen?\",\n  \"signin.action.login\": \"Benutzername\",\n  \"signin.create_an_account\": \"Konto erstellen\",\n  \"signin.feedback.invalid\": \"Anmeldeinformationen sind ungültig\",\n  \"signin.feedback.required_password\": \"Ein Kennwort ist erforderlich\",\n  \"signin.feedback.required_username\": \"Ein Benutzername ist erforderlich\",\n  \"signin.form.password.label\": \"Kennwort:\",\n  \"signin.form.password.placeholder\": \"Kennwort\",\n  \"signin.form.username.label\": \"Benutzername:\",\n  \"signin.form.username.placeholder\": \"Benutzername\",\n  \"signup.action.create\": \"Erstellen\",\n  \"signup.feedback.invalid\": \"Anmeldeinformationen sind ungültig\",\n  \"signup.feedback.invalid_recovery_email\": \"Die E-mail sollte gültig sein\",\n  \"signup.feedback.required_password\": \"Ein Kennwort ist erforderlich\",\n  \"signup.feedback.required_privacy\": \"Wir benötigen Ihre Datenschutzvereinbarung\",\n  \"signup.feedback.required_recovery_email\": \"Eine Backup E-mail-Adresse ist erforderlich\",\n  \"signup.feedback.required_tos\": \"Ich akzeptiere die Nutzungsbedingungen\",\n  \"signup.feedback.required_username\": \"Ein Benutzername ist erforderlich\",\n  \"signup.feedback.unable_to_signup\": \"\",\n  \"signup.feedback.unavailable_username\": \"Es tut uns leid, dieser Benutzername ist nicht verfügbar\",\n  \"signup.feedback.username_double_dots\": \"Der Benutzername darf nicht zwei Pünkte (.) neben dem anderen enthalten\",\n  \"signup.feedback.username_invalid_characters\": \"Der Benutzername darf nicht Sonderzeichen wie {0} und Leerzeichen enthalten\",\n  \"signup.feedback.username_length\": \"Die Länge des Benutzernamens muss zwischen 3 und 42 liegen\",\n  \"signup.feedback.username_starting_ending_dot\": \"Der Benutzername darf nicht mit einem Punkt (.) beginnen oder enden. \",\n  \"signup.form.invitation_email.label\": \"\",\n  \"signup.form.invitation_email.placeholder\": \"\",\n  \"signup.form.invitation_email.tip\": \"\",\n  \"signup.form.password.label\": \"Kennwort:\",\n  \"signup.form.password.placeholder\": \"Kennwort\",\n  \"signup.form.privacy.checkbox.label\": \"Ich habe die Nutzungsbedingungen verstanden und erkläre mich mit diesen einverstanden.\",\n  \"signup.form.privacy.intro\": \"Während der Entwicklungsphase sammeln wir Daten (aber nicht mehr als die NSA :) ).\",\n  \"signup.form.privacy.more_info\": \"Mehr Infos\",\n  \"signup.form.privacy.title\": \"Datenschutzbedingungen\",\n  \"signup.form.username.label\": \"Benutzername:\",\n  \"signup.form.username.placeholder\": \"Benutzername\",\n  \"signup.go_signin\": \"Ich bin schon registriert\",\n  \"signup.privacy.modal.close\": \"Ok, verstanden!\",\n  \"signup.privacy.modal.label\": \"Über Piwik\",\n  \"signup.privacy.modal.text.alpha_tester\": \"Als Alphatester ist Ihr Beitrag wertvoll und es ermöglicht uns, Caliopen fertigzumachen\",\n  \"signup.privacy.modal.text.desactivate_dnt\": \"Sie müssen die DoNotTrack Option in Ihren Browsereinstellungen deaktivieren (weitere Informationen unter http://donottrack.us) und Cookies zulassen.\",\n  \"signup.privacy.modal.text.get_data\": \"Zu diesem Zweck gewähren Sie uns das Recht, Daten zu Ihrer Nutzung zu sammeln (angezeigte Seiten, Timings, Clics, Scrolls ... fast alles, was gesammelt werden kann!).\",\n  \"signup.privacy.modal.text.piwik\": \"Wir benutzen https://piwik.org/, die Open-Source-Webanwendung. Die versammelte Daten werden mit keinem Dritte geteilt und werden nur im Rahmen Caliopens Beta Kontext benutzt.\",\n  \"signup.privacy.modal.title\": \"Caliopen wird noch entwickelt!\",\n  \"spinner.loading\": \"\",\n  \"tag_list.all_contacts\": \"Alle Kontakte\",\n  \"tags.action.add\": \"\",\n  \"tags.action.remove\": \"\",\n  \"tags.common_tags_applied\": \"\",\n  \"tags.form.add.label\": \"\",\n  \"tags.form.add.placeholder\": \"\",\n  \"tags.form.search.label\": \"Suche\",\n  \"tags.form.search.placeholder\": \"Tagsuche...\",\n  \"tags.header.label\": \"Tags\",\n  \"tags.header.title\": \"Tags <0>(Gesamtzahl: {nb})</0>\",\n  \"tags.label.important\": \"\",\n  \"tags.label.inbox\": \"\",\n  \"tags.label.spam\": \"\",\n  \"take-a-tour.action.close\": \"\",\n  \"take-a-tour.action.last-step\": \"\",\n  \"take-a-tour.action.next\": \"\",\n  \"take-a-tour.action.prev\": \"\",\n  \"take-a-tour.action.skip\": \"\",\n  \"take-a-tour.action.toggle\": \"\",\n  \"take-a-tour.current-step\": \"\",\n  \"take-a-tour.step.call-to-action.content\": \"\",\n  \"take-a-tour.step.call-to-action.title\": \"\",\n  \"take-a-tour.step.install.content\": \"\",\n  \"take-a-tour.step.install.title\": \"\",\n  \"take-a-tour.step.intro.content\": \"\",\n  \"take-a-tour.step.intro.title\": \"\",\n  \"take-a-tour.step.search.content\": \"\",\n  \"take-a-tour.step.search.title\": \"\",\n  \"take-a-tour.step.user-menu.content\": \"\",\n  \"take-a-tour.step.user-menu.title\": \"\",\n  \"timeline.action.delete\": \"\",\n  \"timeline.action.display-spam\": \"\",\n  \"timeline.confirm-delete.content\": \"\",\n  \"timeline.confirm-delete.title\": \"\",\n  \"timeline.discussions.selected\": \"\",\n  \"timeline.draft-prefix\": \"Draft in progress:\",\n  \"unexpected_error.page_title\": \"\",\n  \"user.action.cancel_edit\": \"Abbrechen\",\n  \"user.action.delete\": \"\",\n  \"user.action.edit_profile\": \"Bearbeiten\",\n  \"user.action.update\": \"Verändern\",\n  \"user.delete-form.action.delete\": \"\",\n  \"user.delete-form.error.incorrect_password\": \"\",\n  \"user.delete-form.modal-content\": \"\",\n  \"user.delete-form.modal-title\": \"\",\n  \"user.delete-form.password.label\": \"\",\n  \"user.delete-form.password.placeholder\": \"\",\n  \"user.feedback.delete_account_sucessful\": \"\",\n  \"user.openpgp.action.add\": \"Hinzufügen\",\n  \"user.openpgp.action.create\": \"Erstellen\",\n  \"user.openpgp.action.edit-keys\": \"Schlüssel bearbeiten und hinzufügen\",\n  \"user.openpgp.action.switch-generate-key\": \"Schlüssel generieren\",\n  \"user.openpgp.action.switch-import-raw-key\": \"Import key\",\n  \"user.openpgp.form.email.label\": \"E-Mail\",\n  \"user.openpgp.form.passphrase.label\": \"Passphrase\",\n  \"user.openpgp.form.private-key.label\": \"Privat Schlüssel\",\n  \"user.openpgp.has-passphrase\": \"Passphrase aktivieren\",\n  \"user.privacy.improve_pi\": \"Ihren Datenschutzindex verbessern\",\n  \"user.profile\": \"Profil\",\n  \"user.profile.form.family_name.label\": \"Nachname\",\n  \"user.profile.form.given_name.label\": \"Vorname\",\n  \"user.profile.form.recovery_email.label\": \"Backup E-mail-Adresse\",\n  \"user.profile.form.title\": \"Ergänzen Sie Ihr Profil\",\n  \"user.profile.form.username.label\": \"Benutzername\",\n  \"user.profile.subscribed_date\": \"Abonniert am\",\n  \"user.security\": \"Sicherheit\",\n  \"user.security.section_password.title\": \"Kennwort\",\n  \"user.security.section_pgpkeys.title\": \"PGP geheime Schlüssel\",\n  \"view.draft.label\": \"\"\n}"
  },
  {
    "path": "src/frontend/web_application/locale/en/messages.json",
    "content": "{\n  \"about.action.toggle-create\": \"Create\",\n  \"about.footer.link-blog\": \"Blog\",\n  \"about.footer.link-privacy-policy\": \"Privacy Policy\",\n  \"about.footer.link-source-code\": \"Open Source\",\n  \"about.footer.link-website\": \"About\",\n  \"about.identity\": \"Caliopen is a non-profit organization.\",\n  \"about.page-title\": \"Welcome on Caliopen\",\n  \"about.purpose\": \"Caliopen is a private correspondence aggregator (you receive all your emails and private Twitter messages in the same place) that displays a level of confidentiality associated with all your messages.\",\n  \"about.purpose-signup\": \"You still have no account on the Beta? By signup today it will be first step to secure your conversations and have all your communications in the same place, it's easy and free.\",\n  \"about.purpose-signup.beta-descr\": \"* Caliopen is currently in beta version, we need your help to make the best messaging and secured system.\",\n  \"about.purpose-signup.descr\": \"By signup today it will be the first step to secure your conversations and have all your communications in the same place, it's easy and free.\",\n  \"about.purpose-signup.title\": \"You still have no account on the Beta*?\",\n  \"app-loader.action.retry\": \"Please click here to retry\",\n  \"app-loader.activity.beckett\": \"Waiting for Godot, he'll soon be there, trust me\",\n  \"app-loader.activity.cats\": \"Feeding the cats, wait a moment please\",\n  \"app-loader.activity.chicken\": \"Telling the chicken to get out from the kitchen, wait a moment please\",\n  \"app-loader.activity.ipaddr\": \"Forging new ip addresses, wait a moment please\",\n  \"app-loader.activity.macaroons\": \"Baking macaroons, wait a moment please\",\n  \"app-loader.activity.newcat\": \"Trying to tell if there is a new cat in house, wait a moment please\",\n  \"app-loader.activity.oof\": \"Configuring OpenOffice firewall, wait a moment please\",\n  \"app-loader.activity.trolls\": \"Correcting wrong people on the Internet, wait a moment please\",\n  \"app-loader.feedback.failure\": \"Something went wrong. Are you offline?\",\n  \"app.notification.new_messages\": \"You received {0} new messages\",\n  \"call-to-action.action.compose\": \"Compose\",\n  \"call-to-action.action.compose_contact\": \"Compose to this contact\",\n  \"call-to-action.action.create_contact\": \"Create a contact\",\n  \"collection-field-group.action.add\": \"Add\",\n  \"confirm.action.cancel\": \"Cancel\",\n  \"confirm.action.confirm\": \"Yes I'm sure\",\n  \"contact-asociation.loading\": \"Contact list is loading.\",\n  \"contact-association.action.add-new-contact\": \"Add new contact\",\n  \"contact-book.action.add\": \"Add\",\n  \"contact-book.action.delete\": \"Delete\",\n  \"contact-book.action.import\": \"Import\",\n  \"contact-book.action.manage-tags\": \"Manage tags\",\n  \"contact-book.action.select\": \"Select the contact\",\n  \"contact-book.action.select-all\": \"Select all\",\n  \"contact-book.confirm-delete.content\": \"{count, plural, one { The deletion is permanent, are you sure you want to delete this contact? } other { The deletion is permanent, are you sure you want to delete these contacts? }}\",\n  \"contact-book.confirm-delete.title\": \"{count, plural, one {Delete contact} other {Delete contacts}}\",\n  \"contact-book.contacts.selected\": \"{count, plural, one {#/{totalCount} selected contact:} other {#/{totalCount} selected contacts:}}\",\n  \"contact-book.contacts.title\": \"Contacts\",\n  \"contact-book.groups.title\": \"Tags\",\n  \"contact-book.my-contact-details\": \"My contact details\",\n  \"contact-book.tags.action.edit-groups\": \"Edit tags\",\n  \"contact.action-bar.label\": \"Contact:\",\n  \"contact.action.add-address\": \"Add an address\",\n  \"contact.action.add-identity\": \"Add an identity\",\n  \"contact.action.add-organization\": \"Add an organization\",\n  \"contact.action.add_new_field\": \"Add new\",\n  \"contact.action.cancel\": \"Cancel\",\n  \"contact.action.cancel_edit\": \"Cancel\",\n  \"contact.action.delete_contact\": \"Delete\",\n  \"contact.action.edit_contact\": \"Edit contact\",\n  \"contact.action.edit_tags\": \"Edit tags\",\n  \"contact.action.save\": \"Save\",\n  \"contact.action.validate_edit\": \"Validate\",\n  \"contact.address_form.city.label\": \"City\",\n  \"contact.address_form.country.label\": \"Country\",\n  \"contact.address_form.legend\": \"Postal address\",\n  \"contact.address_form.postal_code.label\": \"Postal code\",\n  \"contact.address_form.region.label\": \"Region\",\n  \"contact.address_form.select_country\": \"Select a country\",\n  \"contact.address_form.select_region\": \"Select a region\",\n  \"contact.address_form.street.label\": \"Street\",\n  \"contact.address_form.type.label\": \"Type\",\n  \"contact.address_type.home\": \"Personal\",\n  \"contact.address_type.other\": \"Other\",\n  \"contact.address_type.work\": \"Professional\",\n  \"contact.addresses\": \"Addresses\",\n  \"contact.confirm-delete.content\": \"The deletion is permanent, are you sure you want to delete this contact?\",\n  \"contact.confirm-delete.title\": \"Delete the contact\",\n  \"contact.contact-details.title\": \"Contact details\",\n  \"contact.edit_contact.title\": \"Edit contact\",\n  \"contact.email_form.address.label\": \"Address\",\n  \"contact.email_form.address.placeholder\": \"Email\",\n  \"contact.email_form.legend\": \"Email\",\n  \"contact.email_form.type.label\": \"Type\",\n  \"contact.email_type.home\": \"Personal\",\n  \"contact.email_type.other\": \"Other\",\n  \"contact.email_type.work\": \"Professional\",\n  \"contact.feedback.unable_to_delete\": \"Unable to delete the contact\",\n  \"contact.feedback.unable_to_save\": \"Unable to save the contact\",\n  \"contact.feedback.unable_to_save_address_already_used\": \"The address \\\"{address}\\\" belongs to <0>{name}</0>. You can remove it from that contact before using it here.\",\n  \"contact.form-selector.add_new_field.label\": \"Add a new field\",\n  \"contact.form-selector.address_form.label\": \"Address\",\n  \"contact.form-selector.email_form.label\": \"Email\",\n  \"contact.form-selector.im_form.label\": \"IM\",\n  \"contact.form-selector.phone_form.label\": \"Phone\",\n  \"contact.form.identity.not_mastodon\": \"The mastodon username {name} is invalid. It should be in the format of an email. For example «my_friend@mastodon.instance».\",\n  \"contact.form.identity.not_twitter\": \"The twitter username {name} is invalid. It should be between 1 or 15 characters with no special characters. For example «caliopen_org».\",\n  \"contact.identities\": \"Identities\",\n  \"contact.identity_form.identity.label\": \"Identity\",\n  \"contact.identity_form.identity.placeholder\": \"username\",\n  \"contact.identity_form.legend\": \"Identities\",\n  \"contact.identity_form.service.label\": \"Service\",\n  \"contact.im_form.address.label\": \"Address\",\n  \"contact.im_form.address.placeholder\": \"Address\",\n  \"contact.im_form.legend\": \"Instant messaging\",\n  \"contact.im_form.type.label\": \"Type\",\n  \"contact.im_type.home\": \"Personal\",\n  \"contact.im_type.netmeeting\": \"Netmeeting\",\n  \"contact.im_type.other\": \"Other\",\n  \"contact.im_type.work\": \"Professional\",\n  \"contact.keys.title\": \"Public keys\",\n  \"contact.last-messages.title\": \"Last messages\",\n  \"contact.orga_form.department.label\": \"Department\",\n  \"contact.orga_form.job_description.label\": \"Position\",\n  \"contact.orga_form.label.label\": \"Label\",\n  \"contact.orga_form.legend\": \"Organization\",\n  \"contact.orga_form.name.label\": \"Organization's name\",\n  \"contact.orga_form.remove-button\": \"Remove\",\n  \"contact.orga_form.title.label\": \"Title\",\n  \"contact.organization\": \"Organization\",\n  \"contact.organizations\": \"Organizations:\",\n  \"contact.phone_form.legend\": \"Phone\",\n  \"contact.phone_form.number.label\": \"Number\",\n  \"contact.phone_form.number.placeholder\": \"Number\",\n  \"contact.phone_form.type.label\": \"Type\",\n  \"contact.phone_type.home\": \"Home\",\n  \"contact.phone_type.other\": \"Other\",\n  \"contact.phone_type.work\": \"Work\",\n  \"contact.primary\": \"Primary\",\n  \"contact.profile.name_not_set\": \"(N/A)\",\n  \"contact.public_key_form.cancel\": \"Cancel\",\n  \"contact.public_key_form.confirm_delete.content\": \"Are you sure you want to delete the key \\\"{label} - {fingerprint}\\\"? This action cannot be undone.\",\n  \"contact.public_key_form.confirm_delete.title\": \"Delete public key\",\n  \"contact.public_key_form.delete_key\": \"Delete Key\",\n  \"contact.public_key_form.key.label\": \"Key content\",\n  \"contact.public_key_form.label.label\": \"Public keys\",\n  \"contact.public_key_form.legend\": \"Public Key\",\n  \"contact.public_key_form.validate\": \"Validate\",\n  \"contact.public_key_list.download_key\": \"Download key\",\n  \"contact.public_keys_list.add_key.label\": \"Add public key\",\n  \"contact_profile.action.edit_contact\": \"Edit\",\n  \"contact_profile.form.birthday.label\": \"Birthday\",\n  \"contact_profile.form.firstname.label\": \"Firstname\",\n  \"contact_profile.form.lastname.label\": \"Lastname\",\n  \"contact_profile.form.name-prefix.label\": \"Prefix\",\n  \"contact_profile.form.name-suffix.label\": \"Suffix\",\n  \"desktop.notification.new_messages\": \"You received {0} new messages\",\n  \"device.action.revoke\": \"Revoke this device\",\n  \"device.action.save_changes\": \"Save modifications\",\n  \"device.action.verify\": \"Verify this device\",\n  \"device.current_device\": \"Current device\",\n  \"device.feedback.revoke_success\": \"The device has been revoked\",\n  \"device.feedback.save_success\": \"The device has been saved\",\n  \"device.feedback.send-validation-success\": \"An email has been sent to your backup email in order to verify the device.\",\n  \"device.manage_form.name.label\": \"Name:\",\n  \"device.manage_form.type.label\": \"Type:\",\n  \"device.type.desktop\": \"Desktop\",\n  \"device.type.laptop\": \"Laptop\",\n  \"device.type.other\": \"Other\",\n  \"device.type.smartphone\": \"Smartphone\",\n  \"device.type.tablet\": \"Tablet\",\n  \"device.validation.failed\": \"The device cannot be verified, the validation link might not be valid anymore or may be the device has been revoked.<0/> You can send the verification link from <1>the device list</1>.\",\n  \"device.validation.in_progress\": \"In progress.\",\n  \"device.validation.suceed\": \"The device is now verified, you can continue to use <0>Caliopen</0>.\",\n  \"devices.feedback.unverified_device\": \"You are connected on an unverified device.\",\n  \"devices.feedback.unverified_device_more\": \"To respect privacy and security rules, your discussions history will not fully appear<0/> Please verify this device from a trusted one.\",\n  \"discussion-draft.last-messages\": \"Last messages with {participants}\",\n  \"discussion.action.goto_unread_message\": \"You have unread messages ↑\",\n  \"discussion.action.label\": \"Whole discussion\",\n  \"discussion.action.reply\": \"Reply\",\n  \"draft-message.action.fix-error-on-advanced-form\": \"Unable to send the message, you can fix it or delete it in the <0>Advanced form</0>\",\n  \"draft-message.action.send\": \"Send\",\n  \"draft-message.action.toggle-advanced\": \"Toggle advanced or quick message form\",\n  \"draft-message.encryption.ko\": \"This message is not encrypted\",\n  \"draft-message.encryption.ok\": \"This message is encrypted\",\n  \"draft-message.errors.empty-body\": \"A body is required for a {protocol} message.\",\n  \"draft-message.errors.invalid-participant\": \"According to your identity, all your participants must use a(n) {protocol} address to contact them all together.\",\n  \"draft-message.errors.missing-identity\": \"An identity is mandatory to create a draft.\",\n  \"draft-message.form.contact-recipient-selector\": \"To:\",\n  \"draft-message.form.identity.label\": \"From:\",\n  \"draft-message.form.identity.placeholder\": \"My identity\",\n  \"draft-message.form.placeholder.quick-reply\": \"Quick reply to {recipients} by {protocol}\",\n  \"draft-message.form.placeholder.quick-reply-no-recipients\": \"Quick reply from {identifier}\",\n  \"draft-message.form.placeholder.quick-start\": \"Start a new discussion\",\n  \"draft-message.no-available-identities\": \"You have no available identities for this discussion. You can add one in your <0>account</0>.\",\n  \"draft.action.import_attachement\": \"Import attachement\",\n  \"draft.action.import_attachement.encryption_warning\": \"WARNING: Caliopen cannot encrypt messages with attachments yet. If you proceed now, message will be sent unencrypted.\",\n  \"draft.attachement.form.descr\": \"Attach a file.\",\n  \"draft.feedback.send-error\": \"Unable to send the message\",\n  \"encryption.ask-passphrase.error.invalid-passphrase\": \"Incorrect passphrase\",\n  \"encryption.ask-passphrase.explain\": \"Please enter passphrase for key {fingerprint} to unlock {numberMessages} messages.\",\n  \"encryption.ask-passphrase.validate\": \"Validate\",\n  \"encryption.locked-message.primary-text\": \"This message's content is encrypted.\",\n  \"encryption.locked-message.status.decrypting\": \"Decryption in progress…\",\n  \"encryption.locked-message.status.error\": \"Error while trying to decrypt.\",\n  \"encryption.locked-message.status.need_passphrase\": \"Enter your passphrase to unlock.\",\n  \"encryption.locked-message.status.need_privatekey\": \"No available private key can decrypt this message.\",\n  \"encryption.locked-message.status.no-detail\": \"…\",\n  \"encrytion.ask-passphrase.error.unknown\": \"Unknown error while decrypting private key.\",\n  \"error-boundary.back-to-home\": \"Go back to home\",\n  \"error-boundary.description\": \"<0>We are are very sorry, an error occured on your account. It is what we call a «BSOD», that should never happens (but sometimes it does).</0><1>You can help us to resolve this bug by creating a new post on our feedback website (see link below). We will try to answer as soon as possible.</1><2>Of course you can browse <3>feedback.caliopen.org</3> and help us to improve Caliopen.</2>\",\n  \"error-boundary.link-to-feedback\": \"Send us a feedback\",\n  \"error-boundary.send-feedback\": \"Send us a feedback\",\n  \"error-boundary.short-description\": \"We are are very sorry, an error occured on your account. This should never happens (but sometimes it does). Please retry later or send us a feedback.\",\n  \"error-boundary.title\": \"Something went wrong.\",\n  \"file.size.B\": \"{size} B\",\n  \"file.size.gB\": \"{size} gB\",\n  \"file.size.kB\": \"{size} kB\",\n  \"file.size.mB\": \"{size} mB\",\n  \"file.size.tB\": \"{size} tB\",\n  \"form-validation.required\": \"Required\",\n  \"general.action.cancel\": \"Cancel\",\n  \"general.action.load_more\": \"Load more\",\n  \"header.actions.search\": \"Search\",\n  \"header.help.faq\": \"FAQ [fr]\",\n  \"header.help.feedback\": \"Feedback\",\n  \"header.help.last-changes\": \"Last changes\",\n  \"header.help.menu\": \"Help & info\",\n  \"header.help.privacy-policy\": \"Privacy Policy\",\n  \"header.menu.account\": \"Account\",\n  \"header.menu.agenda\": \"Agenda\",\n  \"header.menu.contact-association\": \"Contact association\",\n  \"header.menu.contacts\": \"Contacts\",\n  \"header.menu.discussions\": \"Messages\",\n  \"header.menu.files\": \"Files\",\n  \"header.menu.search\": \"Search\",\n  \"header.menu.settings\": \"Settings\",\n  \"header.menu.signin\": \"Signin\",\n  \"header.menu.signout\": \"Signout\",\n  \"import-contact.action.import\": \"Import\",\n  \"import-contact.action.import_contacts\": \"Import contacts\",\n  \"import-contact.feedback.error-contact\": \"The file is valid but new contacts cannot be created\",\n  \"import-contact.feedback.error-file\": \"This file cannot be used to import contacts\",\n  \"import-contact.feedback.successfull\": \"Contacts successfully imported\",\n  \"import-contact.feedback.unexpected-error\": \"An unexpected error occured.\",\n  \"import-contact.form.button.close\": \"Close\",\n  \"import-contact.form.descr\": \"You can import a .vcf or .vcard file (Caliopen doesn't support .vcard files in version < 3.1).\",\n  \"import-contact.form.success\": \"Successfully imported!\",\n  \"input-file-group.error.file_is_required\": \"A file is required\",\n  \"input-file-group.error.max_size\": \"The file size must be under <0/>\",\n  \"input-file-group.error.no_valid_ext\": \"Only files {0}\",\n  \"input-file.add_a_file.label\": \"Add a file\",\n  \"login.details.label\": \"login\",\n  \"login.details.title\": \"Login:\",\n  \"message-list.action.delete\": \"Delete\",\n  \"message-list.action.select_all\": \"Select/deselect all messages\",\n  \"message-list.action.select_all_discussions\": \"Select/deselect all discussions\",\n  \"message-list.action.select_single_message\": \"Select/deselect this message\",\n  \"message-list.confirm-delete.content\": \"The deletion is permanent, are you sure you want to delete the messages including eventual drafts?\",\n  \"message-list.confirm-delete.title\": \"Delete the discussion\",\n  \"message-list.message.action.delete\": \"Delete\",\n  \"message-list.message.action.mark_as_read\": \"Mark as read\",\n  \"message-list.message.action.mark_as_unread\": \"Mark as unread\",\n  \"message-list.message.action.reply\": \"Reply\",\n  \"message-list.message.action.tags\": \"Tags\",\n  \"message-list.message.confirm-delete.content\": \"The deletion is permanent, are you sure you want to delete this message?\",\n  \"message-list.message.confirm-delete.title\": \"Delete a message\",\n  \"message-list.message.protocol.email\": \"email\",\n  \"message-list.message.received-on\": \"received on\",\n  \"message-list.selected\": \"{count, plural, one {#/{totalCount} message:} other {#/{totalCount} messages:}}\",\n  \"message.action.add-participant-to-contacts\": \"Add a participant to the contact book\",\n  \"message.action.add-to-contacts\": \"Add to contact book\",\n  \"message.attachment-encryption-not-available\": \"Attachments decryption will be available in a futur version, please be patient.\",\n  \"message.compose.action.delete_attachement\": \"Delete the attachment\",\n  \"message.compose.action.open_import_attachements\": \"Add an attachement\",\n  \"message.from\": \"From:\",\n  \"message.img.postal-card\": \"This message is like a postal card\",\n  \"message.img.simple-envelope\": \"This message is like a standard envelop\",\n  \"message.participants.me\": \"Me\",\n  \"message.pi.comportment\": \"Sender\",\n  \"message.pi.context\": \"Departure\",\n  \"message.pi.description.metaphor.encrypted\": \"In real life this message would be somewhat equivalent to letter within an envelope.\",\n  \"message.pi.description.metaphor.unencrypted\": \"In real life this message would be somewhat equivalent to a postal card visible by everyone.\",\n  \"message.pi.label\": \"Privacy index:\",\n  \"message.pi.technic\": \"Travel\",\n  \"message.to\": \"To:\",\n  \"messages.compose.action.remove-recipient\": \"Remove recipient\",\n  \"messages.compose.form.body.label\": \"Type your message here...\",\n  \"messages.compose.form.subject.label\": \"Subject\",\n  \"messages.compose.form.to.label\": \"To:\",\n  \"messages.participants.and_x_others\": \"{first} and {number, plural, one {# other} other {# others}}\",\n  \"new-device-info.i-understand\": \"I understand\",\n  \"new-device-info.learn-more\": \"Learn more\",\n  \"new-device-info.learn-more.content\": \"<p>In order to compute the Privacy Index (PI) of a message, Caliopen needs to know more about the terminal you're using to consult said message: by nature a laptop isn't as private as a desktop (since you will be using your laptop in public more often).</p><p>Likewise a shared terminal (e.g. a free access computer in a public place) cannot be considered as private as your personal computer only you use.</p><p>Caliopen will take into account the informations you enter on the next screen, to better assess whether your more private messages can be displayed without a risk to be read by a third party: by default only the messages adapted to your terminal's privacy will be displayed.</p><p>It is therefore important to precisely describe a terminal's attributes on the first use, so that Caliopen can more effectively protect your privacy.</p>\",\n  \"new-device-info.modal.title\": \"About new device\",\n  \"new-device-info.text\": \"To respect privacy and security rules, your discussion history will not appear here for now. Please verify this device and eventually set restrictions from your trust client to use Caliopen on this client.\",\n  \"new-device-info.title\": \"It's the first time you attemp to connect to your Caliopen account on this client.\",\n  \"next-feature.generic\": \"This feature is not available for now but is planned to be added to Caliopen.\",\n  \"openpgp-key.download\": \"Save and keep in a safe place.\",\n  \"openpgp.action.remove-key\": \"Remove\",\n  \"openpgp.action.toggle-details\": \"Toggle details\",\n  \"openpgp.details.algorithm\": \"Algorithm\",\n  \"openpgp.details.creation\": \"Creation\",\n  \"openpgp.details.expiration\": \"Expiration\",\n  \"openpgp.details.identities\": \"Identities\",\n  \"openpgp.details.key-size\": \"Key size\",\n  \"openpgp.details.status\": \"Status\",\n  \"openpgp.feedback.fingerprints-not-match\": \"Fingerprints do not match\",\n  \"openpgp.feedback.need-passphrase\": \"Passphrase is needed to retrieve public key\",\n  \"openpgp.feedback.private-key-required\": \"A file is required (extension .asc)\",\n  \"openpgp.feedback.unable-read-private-key\": \"Unable to read private key\",\n  \"openpgp.feedback.wrong-passphrase\": \"Cannot decrypt your private key with current passphrase\",\n  \"openpgp.private-key\": \"Private key\",\n  \"openpgp.public-key\": \"Public key\",\n  \"openpgp.status.expired\": \"Expired\",\n  \"openpgp.status.invalid\": \"Invalid\",\n  \"openpgp.status.no_self_cert\": \"No self cert\",\n  \"openpgp.status.revoked\": \"Revoked\",\n  \"openpgp.status.valid\": \"Valid\",\n  \"orga-details.job.desc-full\": \"{jobDesc} at {orgaName} {department}\",\n  \"page_not_found.caliopen-ascii\": \"Caliopen is draw using ASCIi art\",\n  \"page_not_found.caliopen-ascii-logo\": \"Logo of Caliopen in ASCIi art\",\n  \"page_not_found.page_title\": \"Page not found\",\n  \"page_not_found.thank_you\": \"Thank you for using\",\n  \"page_not_found.title\": \"Unicorn not found\",\n  \"password.action.go_signin\": \"Signin\",\n  \"password.details.action.change\": \"Change\",\n  \"password.details.password_strength.title\": \"Password strength:\",\n  \"password.forgot-form.action.login\": \"Login\",\n  \"password.forgot-form.action.send\": \"Send\",\n  \"password.forgot-form.cancel\": \"Cancel\",\n  \"password.forgot-form.instructions\": \"Enter your username and we'll email instructions on how to reset your password.\",\n  \"password.forgot-form.recovery_email.label\": \"Recovery email\",\n  \"password.forgot-form.recovery_email.placeholder\": \"example@domain.tld\",\n  \"password.forgot-form.success\": \"Done! You will receive an email shortly with instructions to reset your password.\",\n  \"password.forgot-form.title\": \"Forgot password\",\n  \"password.forgot-form.username.label\": \"Username\",\n  \"password.forgot-form.username.placeholder\": \"username\",\n  \"password.form.action.cancel\": \"Cancel\",\n  \"password.form.action.validate\": \"Apply modifications\",\n  \"password.form.current_password.label\": \"Current password:\",\n  \"password.form.current_password.placeholder\": \"Enter your current password\",\n  \"password.form.current_password.tip\": \"The password you want to replace.\",\n  \"password.form.feedback.error-old-password\": \"Wrong old password.\",\n  \"password.form.feedback.successfull\": \"Password updated!\",\n  \"password.form.feedback.unexpected-error\": \"Error when updating password.\",\n  \"password.form.new_password.label\": \"New password:\",\n  \"password.form.new_password.placeholder\": \"Password\",\n  \"password.form.new_password.tip\": \"The password you want to use from now.\",\n  \"password.form.new_password_confirmation.error\": \"Passwords don't match\",\n  \"password.form.new_password_confirmation.label\": \"New password confirmation:\",\n  \"password.form.new_password_confirmation.placeholder\": \"Password\",\n  \"password.reset-form.success\": \"Done!\",\n  \"password.reset-form.title\": \"Reset your password\",\n  \"password_strength.feedback.moderate\": \"Strength: moderate\",\n  \"password_strength.feedback.strong\": \"Strength: strong\",\n  \"password_strength.feedback.weak\": \"Strength: weak\",\n  \"passwords.form.error.empty\": \"At least one field is required.\",\n  \"passwords.form.error.identifiants_mismatch\": \"Identifiers don't match.\",\n  \"passwords.form.error.user_not_found\": \"User not found.\",\n  \"pwa.action.install\": \"Install\",\n  \"pwa.feedback.install-success\": \"Caliopen has been installed on your device.\",\n  \"remote-identity-settings.unexpected-error\": \"Unexpected error occured\",\n  \"remote_identity.action.cancel\": \"Cancel\",\n  \"remote_identity.action.delete\": \"Delete\",\n  \"remote_identity.action.edit\": \"Edit\",\n  \"remote_identity.action.save\": \"Save\",\n  \"remote_identity.action.toggle-advanced-form\": \"Advanced\",\n  \"remote_identity.action.toggle-email-form\": \"Email (via IMAP)\",\n  \"remote_identity.action.toggle-simple-form\": \"Simple\",\n  \"remote_identity.add_account\": \"Add an external account\",\n  \"remote_identity.choose-provider.title\": \"Select a provider\",\n  \"remote_identity.confirm-delete.content\": \"The external account will deactivated then deleted.\",\n  \"remote_identity.confirm-delete.title\": \"Delete the external account\",\n  \"remote_identity.create_help\": \"Add an external account will allow you to aggregate messages for this account in your Caliopen inbox and answer to your contacts using one of these acounts.\",\n  \"remote_identity.email.help\": \"You will have to fill the form with your imap server's parameters\",\n  \"remote_identity.form.identifier.error\": \"a valid email is required\",\n  \"remote_identity.form.identifier.error.uniqueness\": \"This address is already configured\",\n  \"remote_identity.form.identifier.label\": \"Email:\",\n  \"remote_identity.form.identifier.placeholder\": \"john@doe.tld\",\n  \"remote_identity.form.incomming_mail_server.label\": \"Incoming mail server\",\n  \"remote_identity.form.inserver\": \"In server\",\n  \"remote_identity.form.mastodon.instance_label\": \"Before pushing Mastodon button, enter your account address below (ex.: username@instance.tld)\",\n  \"remote_identity.form.outgoing_mail_server.label\": \"Outgoing mail server:\",\n  \"remote_identity.form.outserver\": \"Out server\",\n  \"remote_identity.form.password.error\": \"password is required\",\n  \"remote_identity.form.password.label\": \"Password:\",\n  \"remote_identity.form.port.label\": \"Port:\",\n  \"remote_identity.form.protocol.error\": \"The protocol is required.\",\n  \"remote_identity.form.serverHostname.error\": \"mail server is required\",\n  \"remote_identity.form.serverPort.error\": \"port is required\",\n  \"remote_identity.form.username.error\": \"login is required\",\n  \"remote_identity.form.username.label\": \"Login:\",\n  \"remote_identity.gmail.help\": \"You will be redirected to gmail authentication\",\n  \"remote_identity.how_to\": \"<0>External accounts are fetched every 15 minutes.<1/>Currently there is no indicator to inform that the account is correctly configured until first try is done.</0>\",\n  \"remote_identity.last_connection\": \"Last connection: <0/>\",\n  \"remote_identity.last_connection.never\": \"Never\",\n  \"remote_identity.mastodon.help\": \"You will be redirected to mastodon authentication and authorize the application\",\n  \"remote_identity.provider-logo\": \"{name} logo\",\n  \"remote_identity.status.active\": \"Enabled\",\n  \"remote_identity.status.inactive\": \"Disabled\",\n  \"remote_identity.twitter.help\": \"You will be redirected to twitter authentication and authorize the application\",\n  \"reply-form.in-reply-to\": \"In reply to: {0}\",\n  \"reset-password.form.errors.token_not_found\": \"Token is no more valid. Please retry.\",\n  \"route.compose.label\": \"Compose\",\n  \"route.contact-association.label\": \"Associate \\\"{label}\\\"\",\n  \"route.contact-book.label\": \"Contacts\",\n  \"route.discussion.label\": \"Discussion  ...\",\n  \"route.draft.label\": \"Drafts\",\n  \"route.new-contact.label\": \"New contact\",\n  \"route.search-results.label\": \"Results for: {term}\",\n  \"route.settings.label.application\": \"Application\",\n  \"route.settings.label.default\": \"Settings\",\n  \"route.settings.label.devices\": \"Devices\",\n  \"route.settings.label.identities\": \"Identities\",\n  \"route.settings.label.tags\": \"Tags\",\n  \"route.timeline.label\": \"Timeline\",\n  \"route.user.label.default\": \"Account\",\n  \"route.user.label.profile\": \"Profile\",\n  \"route.user.label.security\": \"Security\",\n  \"screenshot.desktop\": \"blurry screenshot of Caliopen for desktop\",\n  \"screenshot.smartphone\": \"blurry screenshot of Caliopen for smartphone\",\n  \"search-results.actions.display-all\": \"Display all\",\n  \"search-results.all\": \"All ({total})\",\n  \"search-results.contacts\": \"Contacts ({nbContacts})\",\n  \"search-results.messages\": \"Messages ({nbMessages})\",\n  \"search-results.preview.nb-contacts\": \"{nbContacts} contacts contains \\\"{term}\\\" in their label or profile:\",\n  \"search-results.preview.nb-messages\": \"{nbMessages} messages contains \\\"{term}\\\" in their subject or content:\",\n  \"settings.application\": \"Application\",\n  \"settings.contact.display_format.options.first_last\": \"Firstname, Lastname\",\n  \"settings.contact.display_format.options.last_first\": \"Lastname, Firstname\",\n  \"settings.contact.display_order_by.options.firstname\": \"Firstname\",\n  \"settings.contact.display_order_by.options.lastname\": \"Lastname\",\n  \"settings.contact.title\": \"Contact settings\",\n  \"settings.contacts.display.label\": \"Display\",\n  \"settings.contacts.order.label\": \"Order by\",\n  \"settings.desktop_notification.action.request-desktop_notification_permission\": \"Enable desktop notifications\",\n  \"settings.desktop_notification.action.test_desktop_notification\": \"Check desktop notifications\",\n  \"settings.desktop_notification.desktop_notifications_enabled\": \"Desktop notifications enabled\",\n  \"settings.desktop_notification.disabled\": \"Notifications are disabled, please check your browser settings\",\n  \"settings.desktop_notification.feedback.enabled\": \"Desktop notifications are enabled\",\n  \"settings.desktop_notification.no_support\": \"Notifications are not supported by your browser\",\n  \"settings.desktop_notification.title\": \"Desktop notifications\",\n  \"settings.devices\": \"Devices\",\n  \"settings.form.feedback.successfull\": \"Settings successfully updated!\",\n  \"settings.form.feedback.unexpected-error\": \"Error when updating settings.\",\n  \"settings.identities\": \"External accounts\",\n  \"settings.interface.language.label\": \"Language\",\n  \"settings.interface.language.options.de\": \"German\",\n  \"settings.interface.language.options.en\": \"English\",\n  \"settings.interface.language.options.fr\": \"French\",\n  \"settings.interface.title\": \"Customize your interface\",\n  \"settings.message.display_format.label\": \"Display\",\n  \"settings.message.display_format.options.plain_text\": \"Plain text\",\n  \"settings.message.display_format.options.rich_text\": \"Rich text\",\n  \"settings.message.title\": \"Messages settings\",\n  \"settings.notification.delay_disappear.label\": \"Display delay\",\n  \"settings.notification.delay_disappear.options.second\": \"{0} Seconds\",\n  \"settings.notification.enabled.label\": \"Enabled\",\n  \"settings.notification.message_preview.label\": \"Messages preview\",\n  \"settings.notification.message_preview.options.always\": \"Always\",\n  \"settings.notification.message_preview.options.off\": \"Off\",\n  \"settings.notification.sound_enabled.label\": \"Sounds enabled\",\n  \"settings.notification.title\": \"Notifications settings\",\n  \"settings.presentation.update.action\": \"Save settings\",\n  \"settings.signature.label\": \"Signature\",\n  \"settings.signature.update.action\": \"Save\",\n  \"settings.signatures.title\": \"Update your signature\",\n  \"settings.tag.action.save-tag\": \"Save\",\n  \"settings.tag.form.error.create_fail\": \"Unable to create the tag. A tag with the same id may already exist.\",\n  \"settings.tag.form.error.update_failed\": \"Unexpected error occured\",\n  \"settings.tags\": \"Tags\",\n  \"settings.tags.action.delete\": \"Delete\",\n  \"settings.tags.title\": \"Tags\",\n  \"settings.tags.title.create\": \"Create new tag\",\n  \"signin.action.forgot_password\": \"Forgot password?\",\n  \"signin.action.login\": \"Login\",\n  \"signin.create_an_account\": \"Create an account\",\n  \"signin.feedback.invalid\": \"Credentials are invalid\",\n  \"signin.feedback.required_password\": \"A password is required\",\n  \"signin.feedback.required_username\": \"A username is required\",\n  \"signin.form.password.label\": \"Password:\",\n  \"signin.form.password.placeholder\": \"password\",\n  \"signin.form.username.label\": \"Username:\",\n  \"signin.form.username.placeholder\": \"username\",\n  \"signup.action.create\": \"Create\",\n  \"signup.feedback.invalid\": \"Credentials are invalid\",\n  \"signup.feedback.invalid_recovery_email\": \"The email should be valid\",\n  \"signup.feedback.required_password\": \"A password is required\",\n  \"signup.feedback.required_privacy\": \"We need your privacy policy agreement\",\n  \"signup.feedback.required_recovery_email\": \"A backup email is required\",\n  \"signup.feedback.required_tos\": \"We need your terms and conditions agreement\",\n  \"signup.feedback.required_username\": \"A username is required\",\n  \"signup.feedback.unable_to_signup\": \"Unable to signup. Please retry or contact an administrator.\",\n  \"signup.feedback.unavailable_username\": \"We are sorry, this username is not available\",\n  \"signup.feedback.username_double_dots\": \"The username cannot contain two dots (.) next to the other\",\n  \"signup.feedback.username_invalid_characters\": \"The username cannot contain some special characters like {0} and space\",\n  \"signup.feedback.username_length\": \"The length of the username must be be between 3 and 42\",\n  \"signup.feedback.username_starting_ending_dot\": \"The username cannot start or end with a dot (.)\",\n  \"signup.form.invitation_email.label\": \"Rescue email address:\",\n  \"signup.form.invitation_email.placeholder\": \"example@domain.tld\",\n  \"signup.form.invitation_email.tip\": \"Please fill in an email so that we can send you updates about Caliopen.\",\n  \"signup.form.password.label\": \"Password:\",\n  \"signup.form.password.placeholder\": \"password\",\n  \"signup.form.privacy.checkbox.label\": \"I understand and agree\",\n  \"signup.form.privacy.intro\": \"Throughout the development phase, we collect some data, but no more than the NSA :-).\",\n  \"signup.form.privacy.more_info\": \"More info\",\n  \"signup.form.privacy.title\": \"Privacy policy\",\n  \"signup.form.username.label\": \"Username:\",\n  \"signup.form.username.placeholder\": \"username\",\n  \"signup.go_signin\": \"I already have an account\",\n  \"signup.privacy.modal.close\": \"Ok got it!\",\n  \"signup.privacy.modal.label\": \"About Piwik\",\n  \"signup.privacy.modal.text.alpha_tester\": \"As a beta-tester your contribution is precious and will allow us to finalize Caliopen.\",\n  \"signup.privacy.modal.text.desactivate_dnt\": \"You need to deactivate the DoNotTrack setting from your browser preferences (more informations at http://donottrack.us), as well as allowing cookies.\",\n  \"signup.privacy.modal.text.get_data\": \"For this purpose, you grant us the right to collect data related to your usage (displayed pages, timings, clics, scrolls ...almost everything that can be collected!).\",\n  \"signup.privacy.modal.text.piwik\": \"We use https://piwik.org/ the open-source analytics plateform. The collected data will not be disclosed to any third party, and will stay scoped to Caliopen's beta testing purpose.\",\n  \"signup.privacy.modal.title\": \"Caliopen is under development!\",\n  \"spinner.loading\": \"Loading …\",\n  \"tag_list.all_contacts\": \"All contacts\",\n  \"tags.action.add\": \"Add\",\n  \"tags.action.remove\": \"Remove\",\n  \"tags.common_tags_applied\": \"Common tags applied to the current selection:\",\n  \"tags.form.add.label\": \"Add a tag\",\n  \"tags.form.add.placeholder\": \"New tag ...\",\n  \"tags.form.search.label\": \"Search\",\n  \"tags.form.search.placeholder\": \"Search a tag ...\",\n  \"tags.header.label\": \"Tags\",\n  \"tags.header.title\": \"Tags <0>(Total: {nb})</0>\",\n  \"tags.label.important\": \"Important\",\n  \"tags.label.inbox\": \"Inbox\",\n  \"tags.label.spam\": \"Spam\",\n  \"take-a-tour.action.close\": \"Close\",\n  \"take-a-tour.action.last-step\": \"Finish\",\n  \"take-a-tour.action.next\": \"Next\",\n  \"take-a-tour.action.prev\": \"Previous\",\n  \"take-a-tour.action.skip\": \"Skip\",\n  \"take-a-tour.action.toggle\": \"Take a tour\",\n  \"take-a-tour.current-step\": \"Take a tour ({current} of {total})\",\n  \"take-a-tour.step.call-to-action.content\": \"Create a new message on the fly.\",\n  \"take-a-tour.step.call-to-action.title\": \"Create quickly\",\n  \"take-a-tour.step.install.content\": \"<0>If available, the installation button will be displayed.</0><1>On android and ios it will add Caliopen to your homescreen. On desktop, this feature is available for chrome and chromium.</1><2>This technology is called «Progressive Web App», it will make the app faster to load and some features will be available offline and it will help to interract with your device (like the notification feature) if your are agree.</2>\",\n  \"take-a-tour.step.install.title\": \"Installation\",\n  \"take-a-tour.step.intro.content\": \"<0>With using  Caliopen, you can access to all of your private messages (Email, and more to come) through a single login.</0><1>Now, take a look at our main features, such as unified message management, intuitive search and more!</1><2>The first time, the timeline might look a bit empty, you can easily <3>add a provider</3></2>\",\n  \"take-a-tour.step.intro.title\": \"Welcome!\",\n  \"take-a-tour.step.search.content\": \"<0>Every search can include filters. All of the unencrypted data can be searched.</0><1>Here you can search everything in your messages and contacts.</1>\",\n  \"take-a-tour.step.search.title\": \"Intuitive search\",\n  \"take-a-tour.step.user-menu.content\": \"<0>Keep up-to-date your account information and manage your settings from here!</0><1>Customize your application in your settings.</1><2>And connect providers like Gmail or Twitter.</2>\",\n  \"take-a-tour.step.user-menu.title\": \"Account menu\",\n  \"timeline.action.delete\": \"Delete\",\n  \"timeline.action.display-spam\": \"Show spam\",\n  \"timeline.confirm-delete.content\": \"The deletion is permanent, are you sure you want to delete these messages?\",\n  \"timeline.confirm-delete.title\": \"Delete message(s)\",\n  \"timeline.discussions.selected\": \"{count, plural, one {#/{totalCount} discussion} other {#/{totalCount} discussions}}\",\n  \"timeline.draft-prefix\": \"Draft in progress:\",\n  \"timeline.new_messages\": \"You have {0} new messages\",\n  \"unexpected_error.page_title\": \"Page not found\",\n  \"user.action.cancel_edit\": \"Cancel\",\n  \"user.action.delete\": \"Delete account\",\n  \"user.action.edit_profile\": \"Edit\",\n  \"user.action.update\": \"Update\",\n  \"user.delete-form.action.delete\": \"Delete my Caliopen account (your present username won't be available for future use)\",\n  \"user.delete-form.error.incorrect_password\": \"Unable to delete your account, the given password is incorrect.\",\n  \"user.delete-form.modal-content\": \"Are you sure to delete your Caliopen account?\",\n  \"user.delete-form.modal-title\": \"Delete account\",\n  \"user.delete-form.password.label\": \"Password\",\n  \"user.delete-form.password.placeholder\": \"password\",\n  \"user.feedback.delete_account_sucessful\": \"Your account has been deleted\",\n  \"user.openpgp.action.add\": \"Add\",\n  \"user.openpgp.action.create\": \"Create\",\n  \"user.openpgp.action.edit-keys\": \"Edit and add keys\",\n  \"user.openpgp.action.switch-generate-key\": \"Generate key\",\n  \"user.openpgp.action.switch-import-raw-key\": \"Import key\",\n  \"user.openpgp.form.email.label\": \"Email\",\n  \"user.openpgp.form.passphrase.label\": \"Passphrase\",\n  \"user.openpgp.form.private-key.label\": \"Private key\",\n  \"user.openpgp.has-passphrase\": \"Enable passphrase\",\n  \"user.privacy.improve_pi\": \"Improve your privacy index\",\n  \"user.profile\": \"Profile\",\n  \"user.profile.form.family_name.label\": \"Family name\",\n  \"user.profile.form.given_name.label\": \"Given name\",\n  \"user.profile.form.recovery_email.label\": \"Recovery email\",\n  \"user.profile.form.title\": \"Complete your profile\",\n  \"user.profile.form.username.label\": \"Username\",\n  \"user.profile.subscribed_date\": \"Subscribed on\",\n  \"user.security\": \"Security\",\n  \"user.security.section_password.title\": \"Password\",\n  \"user.security.section_pgpkeys.title\": \"PGP private keys\",\n  \"view.draft.label\": \"Drafts\"\n}\n"
  },
  {
    "path": "src/frontend/web_application/locale/es/messages.json",
    "content": "{\n  \"about.action.toggle-create\": \"Crear\",\n  \"about.footer.link-blog\": \"Blog\",\n  \"about.footer.link-privacy-policy\": \"Política de privacidad\",\n  \"about.footer.link-source-code\": \"Código abierto\",\n  \"about.footer.link-website\": \"Acerca de\",\n  \"about.identity\": \"Caliopen es una organización sin fines de lucro.\",\n  \"about.page-title\": \"Bienvenido en Caliopen\",\n  \"about.purpose\": \"Caliopen es un agregador de correspondencia privada (recibes todos tus correos electrónicos y mensajes privados Twitter en el mismo lugar) mostrando un nivel de confidencialidad asociado a cada mensaje.\",\n  \"about.purpose-signup\": \"Todavía no tienes cuenta en la Beta? Registrándote ahora, darás el primer paso para proteger tus conversaciones y colocar todas tus comunicaciones en el mismo lugar, es sencillo y es gratuito.\",\n  \"about.purpose-signup.beta-descr\": \"* Caliopen está actualmente en su versión beta, necesitamos tu ayuda para crear el mejor sistema protegido de mensajería.\",\n  \"about.purpose-signup.descr\": \"Registrándote ahora, darás el primer paso para proteger tus conversaciones y colocar todas tus comunicaciones en el mismo lugar, es sencillo y es gratuito.\",\n  \"about.purpose-signup.title\": \"Todavía no tienes cuenta en la Beta*?\",\n  \"app-loader.action.retry\": \"Clique aquí para volver a intentarlo\",\n  \"app-loader.activity.beckett\": \"Esperando a Godot, aquí estará pronto, créeme\",\n  \"app-loader.activity.cats\": \"Dando de comer a los gatos, espera un momento por favor\",\n  \"app-loader.activity.chicken\": \"Echando a las gallinas de la cocina, espera un momento por favor\",\n  \"app-loader.activity.ipaddr\": \"Creación de nuevas direcciones IP, espera un momento por favor\",\n  \"app-loader.activity.macaroons\": \"Preparando macarones, espera un momento por favor\",\n  \"app-loader.activity.newcat\": \"Intentando saber si hay un nuevo gato en la casa, espera un momento por favor\",\n  \"app-loader.activity.oof\": \"Configurando el cortafuegos OpenOffice, espera un momento por favor\",\n  \"app-loader.activity.trolls\": \"Corrigiendo los errores de la gente en internet, espera un momento por favor\",\n  \"app-loader.feedback.failure\": \"Algo ha salido mal. Estás conectado a internet?\",\n  \"app.notification.new_messages\": \"Ha recibido {0} mensajes nuevos\",\n  \"call-to-action.action.compose\": \"Redactar\",\n  \"call-to-action.action.compose_contact\": \"Escribir a este contacto\",\n  \"call-to-action.action.create_contact\": \"Crear un contacto\",\n  \"collection-field-group.action.add\": \"Añadir\",\n  \"confirm.action.cancel\": \"Cancelar\",\n  \"confirm.action.confirm\": \"Si, estoy seguro\",\n  \"contact-asociation.loading\": \"\",\n  \"contact-association.action.add-new-contact\": \"Añadir nuevo contacto\",\n  \"contact-book.action.add\": \"Añadir\",\n  \"contact-book.action.delete\": \"Borrar\",\n  \"contact-book.action.import\": \"Importar\",\n  \"contact-book.action.manage-tags\": \"Gestionar etiquetas\",\n  \"contact-book.action.select\": \"Elegir contacto\",\n  \"contact-book.action.select-all\": \"Seleccionar todo\",\n  \"contact-book.confirm-delete.content\": \"{count, plural, one { La supresión es permanente. ¿Estás seguro que deseas eliminar a este contacto? } other { La supresión es permanente. ¿Estás seguro que deseas eliminar a estos contactos? }}\",\n  \"contact-book.confirm-delete.title\": \"{count, plural, one {Eliminar al contacto} other {Eliminar a los contactos}}\",\n  \"contact-book.contacts.selected\": \"{count, plural, one {#/{totalCount} contacto seleccionado :} other {#/{totalCount} contactos seleccionados :}}\",\n  \"contact-book.contacts.title\": \"Contactos\",\n  \"contact-book.groups.title\": \"Etiquetas\",\n  \"contact-book.my-contact-details\": \"Detalles de mi contacto\",\n  \"contact-book.tags.action.edit-groups\": \"Editar etiquetas\",\n  \"contact.action-bar.label\": \"Contacto:\",\n  \"contact.action.add-address\": \"\",\n  \"contact.action.add-identity\": \"Añadir una identidad\",\n  \"contact.action.add-organization\": \"Añadir una organización\",\n  \"contact.action.add_new_field\": \"Añadir nuevo/a\",\n  \"contact.action.cancel\": \"\",\n  \"contact.action.cancel_edit\": \"Cancelar\",\n  \"contact.action.delete_contact\": \"Borrar\",\n  \"contact.action.edit_contact\": \"Editar contacto\",\n  \"contact.action.edit_tags\": \"Editar etiquetas\",\n  \"contact.action.save\": \"\",\n  \"contact.action.validate_edit\": \"Validar\",\n  \"contact.address_form.city.label\": \"Ciudad\",\n  \"contact.address_form.country.label\": \"País\",\n  \"contact.address_form.legend\": \"Dirección\",\n  \"contact.address_form.postal_code.label\": \"Código postal\",\n  \"contact.address_form.region.label\": \"Región\",\n  \"contact.address_form.select_country\": \"Elegir un país\",\n  \"contact.address_form.select_region\": \"Elegir una región\",\n  \"contact.address_form.street.label\": \"Calle\",\n  \"contact.address_form.type.label\": \"Tipo\",\n  \"contact.address_type.home\": \"Personal\",\n  \"contact.address_type.other\": \"Otros\",\n  \"contact.address_type.work\": \"Profesional\",\n  \"contact.addresses\": \"\",\n  \"contact.confirm-delete.content\": \"El borrado es permanente, está seguro de que quiere borrar este contacto?\",\n  \"contact.confirm-delete.title\": \"Borrar el contacto\",\n  \"contact.contact-details.title\": \"Detalles del contacto\",\n  \"contact.edit_contact.title\": \"Editar contacto\",\n  \"contact.email_form.address.label\": \"Dirección\",\n  \"contact.email_form.address.placeholder\": \"\",\n  \"contact.email_form.legend\": \"Correo electrónico\",\n  \"contact.email_form.type.label\": \"Tipo\",\n  \"contact.email_type.home\": \"Personal\",\n  \"contact.email_type.other\": \"Otro\",\n  \"contact.email_type.work\": \"Professional\",\n  \"contact.feedback.unable_to_delete\": \"\",\n  \"contact.feedback.unable_to_save\": \"Intento fallido al guardar al contacto\",\n  \"contact.feedback.unable_to_save_address_already_used\": \"\",\n  \"contact.form-selector.add_new_field.label\": \"Añadir un campo\",\n  \"contact.form-selector.address_form.label\": \"Dirección\",\n  \"contact.form-selector.email_form.label\": \"Correo electrónico\",\n  \"contact.form-selector.im_form.label\": \"Mensajería instantánea\",\n  \"contact.form-selector.phone_form.label\": \"Teléfono\",\n  \"contact.form.identity.not_mastodon\": \"\",\n  \"contact.form.identity.not_twitter\": \"El nombre de usuario Twitter \\\"{name}\\\" no es válido. Debe tener entre 1 y 15 caracteres no especiales. Por ejemplo « caliopen_org ».\",\n  \"contact.identities\": \"Identidades\",\n  \"contact.identity_form.identity.label\": \"identidad\",\n  \"contact.identity_form.identity.placeholder\": \"nombre de usuario\",\n  \"contact.identity_form.legend\": \"Identidades\",\n  \"contact.identity_form.service.label\": \"Servicio\",\n  \"contact.im_form.address.label\": \"Dirección\",\n  \"contact.im_form.address.placeholder\": \"\",\n  \"contact.im_form.legend\": \"Mensajería instantánea\",\n  \"contact.im_form.type.label\": \"Tipo\",\n  \"contact.im_type.home\": \"Personal\",\n  \"contact.im_type.netmeeting\": \"Conferencia en línea\",\n  \"contact.im_type.other\": \"Otro\",\n  \"contact.im_type.work\": \"Profesional\",\n  \"contact.keys.title\": \"Claves públicas\",\n  \"contact.last-messages.title\": \"Últimos mensajes\",\n  \"contact.orga_form.department.label\": \"Departamento\",\n  \"contact.orga_form.job_description.label\": \"Puesto\",\n  \"contact.orga_form.label.label\": \"Etiqueta\",\n  \"contact.orga_form.legend\": \"Organización\",\n  \"contact.orga_form.name.label\": \"Nombre de la organización\",\n  \"contact.orga_form.remove-button\": \"\",\n  \"contact.orga_form.title.label\": \"Título\",\n  \"contact.organization\": \"Organización\",\n  \"contact.organizations\": \"Organizaciones :\",\n  \"contact.phone_form.legend\": \"Teléfono\",\n  \"contact.phone_form.number.label\": \"Número\",\n  \"contact.phone_form.number.placeholder\": \"\",\n  \"contact.phone_form.type.label\": \"Tipo\",\n  \"contact.phone_type.home\": \"\",\n  \"contact.phone_type.other\": \"Otro\",\n  \"contact.phone_type.work\": \"Profesional\",\n  \"contact.primary\": \"\",\n  \"contact.profile.name_not_set\": \"\",\n  \"contact.public_key_form.cancel\": \"Cancelar\",\n  \"contact.public_key_form.confirm_delete.content\": \"¿Estás seguro que deseas eliminar a esta clave « {label} - {fingerprint} »? Esta acción no se puede deshacer.\",\n  \"contact.public_key_form.confirm_delete.title\": \"Eliminar la clave pública\",\n  \"contact.public_key_form.delete_key\": \"Eliminar la clave\",\n  \"contact.public_key_form.key.label\": \"Contenido de la clave\",\n  \"contact.public_key_form.label.label\": \"Nombre de la clave\",\n  \"contact.public_key_form.legend\": \"Clave pública\",\n  \"contact.public_key_form.validate\": \"Validar\",\n  \"contact.public_key_list.download_key\": \"Descargar la clave\",\n  \"contact.public_keys_list.add_key.label\": \"Añadir una clave\",\n  \"contact_profile.action.edit_contact\": \"Editar\",\n  \"contact_profile.form.birthday.label\": \"Cumpleaños\",\n  \"contact_profile.form.firstname.label\": \"Nombre\",\n  \"contact_profile.form.lastname.label\": \"Apellido\",\n  \"contact_profile.form.name-prefix.label\": \"Prefijo\",\n  \"contact_profile.form.name-suffix.label\": \"Sufijo\",\n  \"desktop.notification.new_messages\": \"Recibiste {0} nuevos mensajes\",\n  \"device.action.revoke\": \"Revocar este dispositivo\",\n  \"device.action.save_changes\": \"Guardar las modificaciones\",\n  \"device.action.verify\": \"Verificar este dispositivo\",\n  \"device.current_device\": \"Dispositivo actual\",\n  \"device.feedback.revoke_success\": \"El dispositivo ha sido revocado\",\n  \"device.feedback.save_success\": \"El dispositivo ha sido guardado\",\n  \"device.feedback.send-validation-success\": \"Se ha enviado un mensaje a tu correo electrónico de rescate para verificar el dispositivo.\",\n  \"device.manage_form.name.label\": \"Apellido :\",\n  \"device.manage_form.type.label\": \"Tipo :\",\n  \"device.type.desktop\": \"\",\n  \"device.type.laptop\": \"Laptop\",\n  \"device.type.other\": \"Otro\",\n  \"device.type.smartphone\": \"Smartphone\",\n  \"device.type.tablet\": \"Tableta\",\n  \"device.validation.failed\": \"El dispositivo no pudo ser verificado, el enlace de validación puede estar vencido o el dispositivo pudo haber sido revocado.<0/> Puedes volver a enviar el enlace de verificación desde <1>la lista de los dispositivos</1>.\",\n  \"device.validation.in_progress\": \"Validación en progreso.\",\n  \"device.validation.suceed\": \"El dispositivo está verificado ahora, puedes seguir utilizando <0>Caliopen</0>.\",\n  \"devices.feedback.unverified_device\": \"Estás conectado con un dispositivo no verificado.\",\n  \"devices.feedback.unverified_device_more\": \"Para cumplir con los requisitos de seguridad y de confidencialidad, el historial de tus conversaciones no estará totalmente disponible.<0/> Favor de verificar este dispositivo desde otro ya verificado.\",\n  \"discussion-draft.last-messages\": \"Últimos mensajes con {participants}\",\n  \"discussion.action.goto_unread_message\": \"Tienes mensajes sin leer ↑\",\n  \"discussion.action.label\": \"Discusión completa\",\n  \"discussion.action.reply\": \"Contestar\",\n  \"draft-message.action.fix-error-on-advanced-form\": \"\",\n  \"draft-message.action.send\": \"Enviar\",\n  \"draft-message.action.toggle-advanced\": \"Formulario avanzado o básico\",\n  \"draft-message.encryption.ko\": \"Este mensaje no está encriptado\",\n  \"draft-message.encryption.ok\": \"Este mensaje está encriptado\",\n  \"draft-message.errors.empty-body\": \"Se requiere un texto en un mensaje {protocol}.\",\n  \"draft-message.errors.invalid-participant\": \"\",\n  \"draft-message.errors.missing-identity\": \"Se requiere una identidad para crear un borrador.\",\n  \"draft-message.form.contact-recipient-selector\": \"Para :\",\n  \"draft-message.form.identity.label\": \"De :\",\n  \"draft-message.form.identity.placeholder\": \"Mi identidad\",\n  \"draft-message.form.placeholder.quick-reply\": \"Respuesta rápida para {recipients} por {protocol}\",\n  \"draft-message.form.placeholder.quick-reply-no-recipients\": \"Respuesta rápida de {identifier}\",\n  \"draft-message.form.placeholder.quick-start\": \"Iniciar una nueva conversación\",\n  \"draft-message.no-available-identities\": \"No tienes identidad disponible para esta conversación. Puedes añadir una en tu <0>cuenta</0>.\",\n  \"draft.action.import_attachement\": \"Importar un archivo adjunto\",\n  \"draft.action.import_attachement.encryption_warning\": \"ATENCIÓN : Caliopen aún no sabe encriptar los archivos adjuntos. Si continúas, el mensaje no se encriptará cuando lo envíes.\",\n  \"draft.attachement.form.descr\": \"Adjuntar un archivo.\",\n  \"draft.feedback.send-error\": \"No se puede enviar el mensaje\",\n  \"encryption.ask-passphrase.error.invalid-passphrase\": \"Frase de contraseña errónea\",\n  \"encryption.ask-passphrase.explain\": \"Introduce la frase de contraseña de la clave {fingerprint} para desencriptar {numberMessages} mensaje(s).\",\n  \"encryption.ask-passphrase.validate\": \"Validar\",\n  \"encryption.locked-message.primary-text\": \"El contenido de este mensaje está encriptado.\",\n  \"encryption.locked-message.status.decrypting\": \"Desencriptación en progreso…\",\n  \"encryption.locked-message.status.error\": \"Intento fallido al desencriptar.\",\n  \"encryption.locked-message.status.need_passphrase\": \"Introduce tu frase de contraseña para desbloquear la clave.\",\n  \"encryption.locked-message.status.need_privatekey\": \"No hay ninguna clave disponible para desencriptar este mensaje.\",\n  \"encryption.locked-message.status.no-detail\": \"…\",\n  \"encrytion.ask-passphrase.error.unknown\": \"Error desconocido al desencriptar la clave privada.\",\n  \"error-boundary.back-to-home\": \"Volver al inicio\",\n  \"error-boundary.description\": \"<0>Lo sentimos mucho, ha ocurrido un error en tu cuenta. A esto lo llamamos un «BSOD», no debería suceder nunca (pero a veces sucede).</0><1>Nos puedes ayudar para resolver este problema creando una nueva entrada en nuestro sitio web «feedback» (ver enlace más abajo). Procuraremos contestar a la mayor brevedad.</1><2>Por supuesto puedes explorar el <3>feedback.caliopen.org</3> y ayudarnos a mejorar Caliopen.<2>\",\n  \"error-boundary.link-to-feedback\": \"\",\n  \"error-boundary.send-feedback\": \"\",\n  \"error-boundary.short-description\": \"\",\n  \"error-boundary.title\": \"\",\n  \"file.size.B\": \"\",\n  \"file.size.gB\": \"\",\n  \"file.size.kB\": \"\",\n  \"file.size.mB\": \"\",\n  \"file.size.tB\": \"\",\n  \"form-validation.required\": \"\",\n  \"general.action.cancel\": \"\",\n  \"general.action.load_more\": \"\",\n  \"header.actions.search\": \"\",\n  \"header.help.faq\": \"\",\n  \"header.help.feedback\": \"\",\n  \"header.help.last-changes\": \"\",\n  \"header.help.menu\": \"\",\n  \"header.help.privacy-policy\": \"\",\n  \"header.menu.account\": \"\",\n  \"header.menu.agenda\": \"\",\n  \"header.menu.contact-association\": \"\",\n  \"header.menu.contacts\": \"\",\n  \"header.menu.discussions\": \"\",\n  \"header.menu.files\": \"\",\n  \"header.menu.search\": \"\",\n  \"header.menu.settings\": \"\",\n  \"header.menu.signin\": \"\",\n  \"header.menu.signout\": \"\",\n  \"import-contact.action.import\": \"\",\n  \"import-contact.action.import_contacts\": \"\",\n  \"import-contact.feedback.error-contact\": \"\",\n  \"import-contact.feedback.error-file\": \"\",\n  \"import-contact.feedback.successfull\": \"\",\n  \"import-contact.feedback.unexpected-error\": \"\",\n  \"import-contact.form.button.close\": \"\",\n  \"import-contact.form.descr\": \"\",\n  \"import-contact.form.success\": \"\",\n  \"input-file-group.error.file_is_required\": \"\",\n  \"input-file-group.error.max_size\": \"\",\n  \"input-file-group.error.no_valid_ext\": \"\",\n  \"input-file.add_a_file.label\": \"\",\n  \"login.details.label\": \"\",\n  \"login.details.title\": \"\",\n  \"message-list.action.delete\": \"\",\n  \"message-list.action.select_all\": \"\",\n  \"message-list.action.select_all_discussions\": \"\",\n  \"message-list.action.select_single_message\": \"\",\n  \"message-list.confirm-delete.content\": \"\",\n  \"message-list.confirm-delete.title\": \"\",\n  \"message-list.message.action.delete\": \"\",\n  \"message-list.message.action.mark_as_read\": \"\",\n  \"message-list.message.action.mark_as_unread\": \"\",\n  \"message-list.message.action.reply\": \"\",\n  \"message-list.message.action.tags\": \"\",\n  \"message-list.message.confirm-delete.content\": \"\",\n  \"message-list.message.confirm-delete.title\": \"\",\n  \"message-list.message.protocol.email\": \"\",\n  \"message-list.message.received-on\": \"\",\n  \"message-list.selected\": \"\",\n  \"message.action.add-participant-to-contacts\": \"\",\n  \"message.action.add-to-contacts\": \"\",\n  \"message.attachment-encryption-not-available\": \"\",\n  \"message.compose.action.delete_attachement\": \"\",\n  \"message.compose.action.open_import_attachements\": \"\",\n  \"message.from\": \"\",\n  \"message.img.postal-card\": \"\",\n  \"message.img.simple-envelope\": \"\",\n  \"message.participants.me\": \"\",\n  \"message.pi.comportment\": \"\",\n  \"message.pi.context\": \"\",\n  \"message.pi.description.metaphor.encrypted\": \"\",\n  \"message.pi.description.metaphor.unencrypted\": \"\",\n  \"message.pi.label\": \"\",\n  \"message.pi.technic\": \"\",\n  \"message.to\": \"\",\n  \"messages.compose.action.remove-recipient\": \"\",\n  \"messages.compose.form.body.label\": \"\",\n  \"messages.compose.form.subject.label\": \"\",\n  \"messages.compose.form.to.label\": \"\",\n  \"messages.participants.and_x_others\": \"\",\n  \"new-device-info.i-understand\": \"\",\n  \"new-device-info.learn-more\": \"\",\n  \"new-device-info.learn-more.content\": \"\",\n  \"new-device-info.modal.title\": \"\",\n  \"new-device-info.text\": \"\",\n  \"new-device-info.title\": \"\",\n  \"next-feature.generic\": \"\",\n  \"openpgp-key.download\": \"\",\n  \"openpgp.action.remove-key\": \"\",\n  \"openpgp.feedback.fingerprints-not-match\": \"\",\n  \"openpgp.feedback.need-passphrase\": \"\",\n  \"openpgp.feedback.private-key-required\": \"\",\n  \"openpgp.feedback.unable-read-private-key\": \"\",\n  \"openpgp.feedback.wrong-passphrase\": \"\",\n  \"openpgp.status.expired\": \"\",\n  \"openpgp.status.invalid\": \"\",\n  \"openpgp.status.no_self_cert\": \"\",\n  \"openpgp.status.revoked\": \"\",\n  \"openpgp.status.valid\": \"\",\n  \"orga-details.job.desc-full\": \"\",\n  \"page_not_found.caliopen-ascii\": \"\",\n  \"page_not_found.caliopen-ascii-logo\": \"\",\n  \"page_not_found.page_title\": \"\",\n  \"page_not_found.thank_you\": \"\",\n  \"page_not_found.title\": \"\",\n  \"password.action.go_signin\": \"\",\n  \"password.details.action.change\": \"\",\n  \"password.details.password_strength.title\": \"\",\n  \"password.forgot-form.action.login\": \"\",\n  \"password.forgot-form.action.send\": \"\",\n  \"password.forgot-form.cancel\": \"\",\n  \"password.forgot-form.instructions\": \"\",\n  \"password.forgot-form.recovery_email.label\": \"\",\n  \"password.forgot-form.recovery_email.placeholder\": \"\",\n  \"password.forgot-form.success\": \"\",\n  \"password.forgot-form.title\": \"\",\n  \"password.forgot-form.username.label\": \"\",\n  \"password.forgot-form.username.placeholder\": \"\",\n  \"password.form.action.cancel\": \"\",\n  \"password.form.action.validate\": \"\",\n  \"password.form.current_password.label\": \"\",\n  \"password.form.current_password.placeholder\": \"\",\n  \"password.form.current_password.tip\": \"\",\n  \"password.form.feedback.error-old-password\": \"\",\n  \"password.form.feedback.successfull\": \"\",\n  \"password.form.feedback.unexpected-error\": \"\",\n  \"password.form.new_password.label\": \"\",\n  \"password.form.new_password.placeholder\": \"\",\n  \"password.form.new_password.tip\": \"\",\n  \"password.form.new_password_confirmation.error\": \"\",\n  \"password.form.new_password_confirmation.label\": \"\",\n  \"password.form.new_password_confirmation.placeholder\": \"\",\n  \"password.reset-form.success\": \"\",\n  \"password.reset-form.title\": \"\",\n  \"password_strength.feedback.moderate\": \"\",\n  \"password_strength.feedback.strong\": \"\",\n  \"password_strength.feedback.weak\": \"\",\n  \"passwords.form.error.empty\": \"\",\n  \"passwords.form.error.identifiants_mismatch\": \"\",\n  \"passwords.form.error.user_not_found\": \"\",\n  \"pwa.action.install\": \"\",\n  \"pwa.feedback.install-success\": \"\",\n  \"remote-identity-settings.unexpected-error\": \"\",\n  \"remote_identity.action.cancel\": \"\",\n  \"remote_identity.action.delete\": \"\",\n  \"remote_identity.action.edit\": \"\",\n  \"remote_identity.action.save\": \"\",\n  \"remote_identity.action.toggle-advanced-form\": \"\",\n  \"remote_identity.action.toggle-email-form\": \"\",\n  \"remote_identity.action.toggle-simple-form\": \"\",\n  \"remote_identity.add_account\": \"\",\n  \"remote_identity.choose-provider.title\": \"\",\n  \"remote_identity.confirm-delete.content\": \"\",\n  \"remote_identity.confirm-delete.title\": \"\",\n  \"remote_identity.create_help\": \"\",\n  \"remote_identity.email.help\": \"\",\n  \"remote_identity.form.identifier.error\": \"\",\n  \"remote_identity.form.identifier.error.uniqueness\": \"\",\n  \"remote_identity.form.identifier.label\": \"\",\n  \"remote_identity.form.identifier.placeholder\": \"\",\n  \"remote_identity.form.incomming_mail_server.label\": \"\",\n  \"remote_identity.form.inserver\": \"\",\n  \"remote_identity.form.mastodon.instance_label\": \"\",\n  \"remote_identity.form.outgoing_mail_server.label\": \"\",\n  \"remote_identity.form.outserver\": \"\",\n  \"remote_identity.form.password.error\": \"\",\n  \"remote_identity.form.password.label\": \"\",\n  \"remote_identity.form.port.label\": \"\",\n  \"remote_identity.form.protocol.error\": \"\",\n  \"remote_identity.form.serverHostname.error\": \"\",\n  \"remote_identity.form.serverPort.error\": \"\",\n  \"remote_identity.form.username.error\": \"\",\n  \"remote_identity.form.username.label\": \"\",\n  \"remote_identity.gmail.help\": \"\",\n  \"remote_identity.how_to\": \"\",\n  \"remote_identity.last_connection\": \"\",\n  \"remote_identity.last_connection.never\": \"\",\n  \"remote_identity.mastodon.help\": \"\",\n  \"remote_identity.provider-logo\": \"\",\n  \"remote_identity.status.active\": \"\",\n  \"remote_identity.status.inactive\": \"\",\n  \"remote_identity.twitter.help\": \"\",\n  \"reply-form.in-reply-to\": \"\",\n  \"reset-password.form.errors.token_not_found\": \"\",\n  \"route.compose.label\": \"\",\n  \"route.contact-association.label\": \"\",\n  \"route.contact-book.label\": \"\",\n  \"route.discussion.label\": \"\",\n  \"route.draft.label\": \"\",\n  \"route.new-contact.label\": \"\",\n  \"route.search-results.label\": \"\",\n  \"route.settings.label.application\": \"\",\n  \"route.settings.label.default\": \"\",\n  \"route.settings.label.devices\": \"\",\n  \"route.settings.label.identities\": \"\",\n  \"route.settings.label.tags\": \"\",\n  \"route.timeline.label\": \"\",\n  \"route.user.label.default\": \"\",\n  \"route.user.label.profile\": \"\",\n  \"route.user.label.security\": \"\",\n  \"screenshot.desktop\": \"\",\n  \"screenshot.smartphone\": \"\",\n  \"search-results.actions.display-all\": \"\",\n  \"search-results.all\": \"\",\n  \"search-results.contacts\": \"\",\n  \"search-results.messages\": \"\",\n  \"search-results.preview.nb-contacts\": \"\",\n  \"search-results.preview.nb-messages\": \"\",\n  \"settings.application\": \"\",\n  \"settings.contact.display_format.options.first_last\": \"\",\n  \"settings.contact.display_format.options.last_first\": \"\",\n  \"settings.contact.display_order_by.options.firstname\": \"\",\n  \"settings.contact.display_order_by.options.lastname\": \"\",\n  \"settings.contact.title\": \"\",\n  \"settings.contacts.display.label\": \"\",\n  \"settings.contacts.order.label\": \"\",\n  \"settings.desktop_notification.action.request-desktop_notification_permission\": \"\",\n  \"settings.desktop_notification.action.test_desktop_notification\": \"\",\n  \"settings.desktop_notification.desktop_notifications_enabled\": \"\",\n  \"settings.desktop_notification.disabled\": \"\",\n  \"settings.desktop_notification.feedback.enabled\": \"\",\n  \"settings.desktop_notification.no_support\": \"\",\n  \"settings.desktop_notification.title\": \"\",\n  \"settings.devices\": \"\",\n  \"settings.form.feedback.successfull\": \"\",\n  \"settings.form.feedback.unexpected-error\": \"\",\n  \"settings.identities\": \"\",\n  \"settings.interface.language.label\": \"\",\n  \"settings.interface.language.options.de\": \"\",\n  \"settings.interface.language.options.en\": \"\",\n  \"settings.interface.language.options.fr\": \"\",\n  \"settings.interface.title\": \"\",\n  \"settings.message.display_format.label\": \"\",\n  \"settings.message.display_format.options.plain_text\": \"\",\n  \"settings.message.display_format.options.rich_text\": \"\",\n  \"settings.message.title\": \"\",\n  \"settings.notification.delay_disappear.label\": \"\",\n  \"settings.notification.delay_disappear.options.second\": \"\",\n  \"settings.notification.enabled.label\": \"\",\n  \"settings.notification.message_preview.label\": \"\",\n  \"settings.notification.message_preview.options.always\": \"\",\n  \"settings.notification.message_preview.options.off\": \"\",\n  \"settings.notification.sound_enabled.label\": \"\",\n  \"settings.notification.title\": \"\",\n  \"settings.presentation.update.action\": \"\",\n  \"settings.signature.label\": \"\",\n  \"settings.signature.update.action\": \"\",\n  \"settings.signatures.title\": \"\",\n  \"settings.tag.action.save-tag\": \"\",\n  \"settings.tag.form.error.create_fail\": \"\",\n  \"settings.tag.form.error.update_failed\": \"\",\n  \"settings.tags\": \"\",\n  \"settings.tags.action.delete\": \"\",\n  \"settings.tags.title\": \"\",\n  \"settings.tags.title.create\": \"\",\n  \"signin.action.forgot_password\": \"\",\n  \"signin.action.login\": \"\",\n  \"signin.create_an_account\": \"\",\n  \"signin.feedback.invalid\": \"\",\n  \"signin.feedback.required_password\": \"\",\n  \"signin.feedback.required_username\": \"\",\n  \"signin.form.password.label\": \"\",\n  \"signin.form.password.placeholder\": \"\",\n  \"signin.form.username.label\": \"\",\n  \"signin.form.username.placeholder\": \"\",\n  \"signup.action.create\": \"\",\n  \"signup.feedback.invalid\": \"\",\n  \"signup.feedback.invalid_recovery_email\": \"\",\n  \"signup.feedback.required_password\": \"\",\n  \"signup.feedback.required_privacy\": \"\",\n  \"signup.feedback.required_recovery_email\": \"\",\n  \"signup.feedback.required_tos\": \"\",\n  \"signup.feedback.required_username\": \"\",\n  \"signup.feedback.unable_to_signup\": \"\",\n  \"signup.feedback.unavailable_username\": \"\",\n  \"signup.feedback.username_double_dots\": \"\",\n  \"signup.feedback.username_invalid_characters\": \"\",\n  \"signup.feedback.username_length\": \"\",\n  \"signup.feedback.username_starting_ending_dot\": \"\",\n  \"signup.form.invitation_email.label\": \"\",\n  \"signup.form.invitation_email.placeholder\": \"\",\n  \"signup.form.invitation_email.tip\": \"\",\n  \"signup.form.password.label\": \"\",\n  \"signup.form.password.placeholder\": \"\",\n  \"signup.form.privacy.checkbox.label\": \"\",\n  \"signup.form.privacy.intro\": \"\",\n  \"signup.form.privacy.more_info\": \"\",\n  \"signup.form.privacy.title\": \"\",\n  \"signup.form.username.label\": \"\",\n  \"signup.form.username.placeholder\": \"\",\n  \"signup.go_signin\": \"\",\n  \"signup.privacy.modal.close\": \"\",\n  \"signup.privacy.modal.label\": \"\",\n  \"signup.privacy.modal.text.alpha_tester\": \"\",\n  \"signup.privacy.modal.text.desactivate_dnt\": \"\",\n  \"signup.privacy.modal.text.get_data\": \"\",\n  \"signup.privacy.modal.text.piwik\": \"\",\n  \"signup.privacy.modal.title\": \"\",\n  \"spinner.loading\": \"\",\n  \"tag_list.all_contacts\": \"\",\n  \"tags.action.add\": \"\",\n  \"tags.action.remove\": \"\",\n  \"tags.common_tags_applied\": \"\",\n  \"tags.form.add.label\": \"\",\n  \"tags.form.add.placeholder\": \"\",\n  \"tags.form.search.label\": \"\",\n  \"tags.form.search.placeholder\": \"\",\n  \"tags.header.label\": \"\",\n  \"tags.header.title\": \"\",\n  \"tags.label.important\": \"\",\n  \"tags.label.inbox\": \"\",\n  \"tags.label.spam\": \"\",\n  \"take-a-tour.action.close\": \"\",\n  \"take-a-tour.action.last-step\": \"\",\n  \"take-a-tour.action.next\": \"\",\n  \"take-a-tour.action.prev\": \"\",\n  \"take-a-tour.action.skip\": \"\",\n  \"take-a-tour.action.toggle\": \"\",\n  \"take-a-tour.current-step\": \"\",\n  \"take-a-tour.step.call-to-action.content\": \"\",\n  \"take-a-tour.step.call-to-action.title\": \"\",\n  \"take-a-tour.step.install.content\": \"\",\n  \"take-a-tour.step.install.title\": \"\",\n  \"take-a-tour.step.intro.content\": \"\",\n  \"take-a-tour.step.intro.title\": \"\",\n  \"take-a-tour.step.search.content\": \"\",\n  \"take-a-tour.step.search.title\": \"\",\n  \"take-a-tour.step.user-menu.content\": \"\",\n  \"take-a-tour.step.user-menu.title\": \"\",\n  \"timeline.action.delete\": \"\",\n  \"timeline.action.display-spam\": \"\",\n  \"timeline.confirm-delete.content\": \"\",\n  \"timeline.confirm-delete.title\": \"\",\n  \"timeline.discussions.selected\": \"\",\n  \"timeline.draft-prefix\": \"\",\n  \"unexpected_error.page_title\": \"\",\n  \"user.action.cancel_edit\": \"\",\n  \"user.action.delete\": \"\",\n  \"user.action.edit_profile\": \"\",\n  \"user.action.update\": \"\",\n  \"user.delete-form.action.delete\": \"\",\n  \"user.delete-form.error.incorrect_password\": \"\",\n  \"user.delete-form.modal-content\": \"\",\n  \"user.delete-form.modal-title\": \"\",\n  \"user.delete-form.password.label\": \"\",\n  \"user.delete-form.password.placeholder\": \"\",\n  \"user.feedback.delete_account_sucessful\": \"\",\n  \"user.openpgp.action.add\": \"\",\n  \"user.openpgp.action.create\": \"\",\n  \"user.openpgp.action.edit-keys\": \"\",\n  \"user.openpgp.action.switch-generate-key\": \"\",\n  \"user.openpgp.action.switch-import-raw-key\": \"\",\n  \"user.openpgp.form.email.label\": \"\",\n  \"user.openpgp.form.passphrase.label\": \"\",\n  \"user.openpgp.form.private-key.label\": \"\",\n  \"user.openpgp.has-passphrase\": \"\",\n  \"user.privacy.improve_pi\": \"\",\n  \"user.profile\": \"\",\n  \"user.profile.form.family_name.label\": \"\",\n  \"user.profile.form.given_name.label\": \"\",\n  \"user.profile.form.recovery_email.label\": \"\",\n  \"user.profile.form.title\": \"\",\n  \"user.profile.form.username.label\": \"\",\n  \"user.profile.subscribed_date\": \"\",\n  \"user.security\": \"\",\n  \"user.security.section_password.title\": \"\",\n  \"user.security.section_pgpkeys.title\": \"\",\n  \"view.draft.label\": \"\"\n}"
  },
  {
    "path": "src/frontend/web_application/locale/fr/messages.json",
    "content": "{\n  \"about.action.toggle-create\": \"Créer\",\n  \"about.footer.link-blog\": \"Blog\",\n  \"about.footer.link-privacy-policy\": \"Politique de confidentialité\",\n  \"about.footer.link-source-code\": \"Open Source\",\n  \"about.footer.link-website\": \"À propos\",\n  \"about.identity\": \"Caliopen est une association à but non lucratif.\",\n  \"about.page-title\": \"Bienvenue sur Caliopen\",\n  \"about.purpose\": \"Caliopen est un agrégateur de correspondance privée (vous recevez tous vos emails et messages privés Twitter, Mastodon au même endroit) qui affiche un niveau de confidentialité associé à tous vos messages.\",\n  \"about.purpose-signup\": \"Vous n'avez toujours pas de compte sur la Beta ? En vous inscrivant maintenant, ce sera un premier pas pour sécuriser vos conversations et avoir toutes vos communications au même endroit, c'est facile et gratuit.\",\n  \"about.purpose-signup.beta-descr\": \"* Caliopen est actuellement en version beta, nous avons besoin de votre aide pour fabriquer le meilleurs système de messagerie sécurisé.\",\n  \"about.purpose-signup.descr\": \"En vous inscrivant maintenant, ce sera un premier pas pour sécuriser vos conversations et avoir toutes vos communications au même endroit, c'est facile et gratuit.\",\n  \"about.purpose-signup.title\": \"Vous n'avez toujours pas de compte sur la Beta* ?\",\n  \"app-loader.action.retry\": \"Cliquez ici pour réessayer\",\n  \"app-loader.activity.beckett\": \"J'attends Godot, il sera là bientôt, croyez-moi\",\n  \"app-loader.activity.cats\": \"En train de nourrir les chats, merci de patienter\",\n  \"app-loader.activity.chicken\": \"En train de dire à la poule de sortir de la cuisine, merci de patienter\",\n  \"app-loader.activity.ipaddr\": \"Création de nouvelles adresses IP, attendez un moment s'il vous plaît\",\n  \"app-loader.activity.macaroons\": \"Les macarons sont presque prêts, merci de patienter\",\n  \"app-loader.activity.newcat\": \"En train d'essayer de déterminer s'il y a un nouveau chat dans la maison, attendez un moment s'il vous plaît\",\n  \"app-loader.activity.oof\": \"En train de configurer le pare-feu OpenOffice, attendez un moment s'il vous plaît\",\n  \"app-loader.activity.trolls\": \"En train de corriger les erreurs des gens sur Internet, attendez un moment s'il vous plaît\",\n  \"app-loader.feedback.failure\": \"Quelque chose ne s'est pas passé correctement. Êtes vous hors ligne ?\",\n  \"app.notification.new_messages\": \"Vous avez reçu {0} nouveaux messages\",\n  \"call-to-action.action.compose\": \"Écrire\",\n  \"call-to-action.action.compose_contact\": \"Écrire à ce contact\",\n  \"call-to-action.action.create_contact\": \"Créer un contact\",\n  \"collection-field-group.action.add\": \"Ajouter\",\n  \"confirm.action.cancel\": \"Annuler\",\n  \"confirm.action.confirm\": \"Oui je suis sûr\",\n  \"contact-asociation.loading\": \"\",\n  \"contact-association.action.add-new-contact\": \"Ajouter un nouveau contact\",\n  \"contact-book.action.add\": \"Ajouter\",\n  \"contact-book.action.delete\": \"Supprimer\",\n  \"contact-book.action.import\": \"Importer\",\n  \"contact-book.action.manage-tags\": \"Gérer les tags\",\n  \"contact-book.action.select\": \"Sélectionner le contact\",\n  \"contact-book.action.select-all\": \"Tout sélectionner\",\n  \"contact-book.confirm-delete.content\": \"{count, plural, one { La suppression est permanente, êtes-vous sûr de vouloir supprimer ce contact ? } other { La suppression est permanente, êtes-vous sûr de vouloir supprimer ces contacts ? }}\",\n  \"contact-book.confirm-delete.title\": \"{count, plural, one {Supprimer le contact} other {Supprimer les contacts}}\",\n  \"contact-book.contacts.selected\": \"{count, plural, one {#/{totalCount} contact sélectionné :} other {#/{totalCount} contacts sélectionnés :}}\",\n  \"contact-book.contacts.title\": \"Contacts\",\n  \"contact-book.groups.title\": \"Tags\",\n  \"contact-book.my-contact-details\": \"Mes coordonnées\",\n  \"contact-book.tags.action.edit-groups\": \"Éditer les tags\",\n  \"contact.action-bar.label\": \"Contact :\",\n  \"contact.action.add-address\": \"\",\n  \"contact.action.add-identity\": \"Ajouter une identité\",\n  \"contact.action.add-organization\": \"Ajouter une organisation\",\n  \"contact.action.add_new_field\": \"Ajouter\",\n  \"contact.action.cancel\": \"\",\n  \"contact.action.cancel_edit\": \"Annuler\",\n  \"contact.action.delete_contact\": \"Supprimer le contact\",\n  \"contact.action.edit_contact\": \"Modifier le contact\",\n  \"contact.action.edit_tags\": \"Modifier les tags\",\n  \"contact.action.save\": \"\",\n  \"contact.action.validate_edit\": \"Valider\",\n  \"contact.address_form.city.label\": \"Ville\",\n  \"contact.address_form.country.label\": \"Pays\",\n  \"contact.address_form.legend\": \"Adresse postale\",\n  \"contact.address_form.postal_code.label\": \"Code Postal\",\n  \"contact.address_form.region.label\": \"Région\",\n  \"contact.address_form.select_country\": \"Sélectionner un pays\",\n  \"contact.address_form.select_region\": \"Sélectionner une région\",\n  \"contact.address_form.street.label\": \"Rue\",\n  \"contact.address_form.type.label\": \"Type\",\n  \"contact.address_type.home\": \"Personnel\",\n  \"contact.address_type.other\": \"Autre\",\n  \"contact.address_type.work\": \"Professionnel\",\n  \"contact.addresses\": \"\",\n  \"contact.confirm-delete.content\": \"La suppression est permanente, êtes-vous sûr de vouloir supprimer ce contact ?\",\n  \"contact.confirm-delete.title\": \"Supprimer le contact\",\n  \"contact.contact-details.title\": \"Coordonnées\",\n  \"contact.edit_contact.title\": \"Éditer le contact\",\n  \"contact.email_form.address.label\": \"Adresse\",\n  \"contact.email_form.address.placeholder\": \"\",\n  \"contact.email_form.legend\": \"Email\",\n  \"contact.email_form.type.label\": \"Type\",\n  \"contact.email_type.home\": \"Personnel\",\n  \"contact.email_type.other\": \"Autre\",\n  \"contact.email_type.work\": \"Professionnel\",\n  \"contact.feedback.unable_to_delete\": \"\",\n  \"contact.feedback.unable_to_save\": \"Échec de la sauvegarde du contact\",\n  \"contact.feedback.unable_to_save_address_already_used\": \"L'adresse \\\"{address}\\\" est déjà associée à <0>{name}</0>. Vous pouvez la supprimer de ce contact pour pouvoir l'utiliser ici.\",\n  \"contact.form-selector.add_new_field.label\": \"Ajouter un champ\",\n  \"contact.form-selector.address_form.label\": \"Adresse\",\n  \"contact.form-selector.email_form.label\": \"Email\",\n  \"contact.form-selector.im_form.label\": \"Messagerie instantanée\",\n  \"contact.form-selector.phone_form.label\": \"Téléphone\",\n  \"contact.form.identity.not_mastodon\": \"Le nom d'utilisateur Mastodon \\\"{name}\\\" n'est pas valide. Il doit avoir la forme d'une adresse email. Par exemple « mon_ami@mastodon.instance ».\",\n  \"contact.form.identity.not_twitter\": \"Le nom d'utilisateur Twitter \\\"{name}\\\" n'est pas valide. Il doit avoir entre 1 et 15 caractères non spéciaux. Par exemple « caliopen_org ».\",\n  \"contact.identities\": \"Identités\",\n  \"contact.identity_form.identity.label\": \"Identité\",\n  \"contact.identity_form.identity.placeholder\": \"nom d'utilisateur\",\n  \"contact.identity_form.legend\": \"Identités\",\n  \"contact.identity_form.service.label\": \"Service\",\n  \"contact.im_form.address.label\": \"Adresse\",\n  \"contact.im_form.address.placeholder\": \"\",\n  \"contact.im_form.legend\": \"Messagerie instantanée\",\n  \"contact.im_form.type.label\": \"Type\",\n  \"contact.im_type.home\": \"Personnel\",\n  \"contact.im_type.netmeeting\": \"Conférence en ligne\",\n  \"contact.im_type.other\": \"Autre\",\n  \"contact.im_type.work\": \"Professionnel\",\n  \"contact.keys.title\": \"Clés publiques\",\n  \"contact.last-messages.title\": \"Derniers messages\",\n  \"contact.orga_form.department.label\": \"Département\",\n  \"contact.orga_form.job_description.label\": \"Poste\",\n  \"contact.orga_form.label.label\": \"Label\",\n  \"contact.orga_form.legend\": \"Organisation\",\n  \"contact.orga_form.name.label\": \"Nom de l’organisation\",\n  \"contact.orga_form.remove-button\": \"\",\n  \"contact.orga_form.title.label\": \"Titre\",\n  \"contact.organization\": \"Organisation\",\n  \"contact.organizations\": \"Organisations :\",\n  \"contact.phone_form.legend\": \"Téléphone\",\n  \"contact.phone_form.number.label\": \"Numéro\",\n  \"contact.phone_form.number.placeholder\": \"\",\n  \"contact.phone_form.type.label\": \"Type\",\n  \"contact.phone_type.home\": \"Personnel\",\n  \"contact.phone_type.other\": \"Autre\",\n  \"contact.phone_type.work\": \"Professionnel\",\n  \"contact.primary\": \"Principale\",\n  \"contact.profile.name_not_set\": \"(N.C.)\",\n  \"contact.public_key_form.cancel\": \"Annuler\",\n  \"contact.public_key_form.confirm_delete.content\": \"Voulez-vous vraiment effacer la clé « {label} - {fingerprint} » ? Cette action est irréversible.\",\n  \"contact.public_key_form.confirm_delete.title\": \"Supprimer la clé\",\n  \"contact.public_key_form.delete_key\": \"Supprimer\",\n  \"contact.public_key_form.key.label\": \"Contenu de la clé\",\n  \"contact.public_key_form.label.label\": \"Nom de la clé\",\n  \"contact.public_key_form.legend\": \"Clé publique\",\n  \"contact.public_key_form.validate\": \"Valider\",\n  \"contact.public_key_list.download_key\": \"Télécharger la clé\",\n  \"contact.public_keys_list.add_key.label\": \"Ajouter une clé\",\n  \"contact_profile.action.edit_contact\": \"Modifier\",\n  \"contact_profile.form.birthday.label\": \"Anniversaire\",\n  \"contact_profile.form.firstname.label\": \"Prénom\",\n  \"contact_profile.form.lastname.label\": \"Nom\",\n  \"contact_profile.form.name-prefix.label\": \"Préfixe\",\n  \"contact_profile.form.name-suffix.label\": \"Suffixe\",\n  \"desktop.notification.new_messages\": \"Vous avez reçu {0} nouveaux messages\",\n  \"device.action.revoke\": \"Révoquer\",\n  \"device.action.save_changes\": \"Enregistrer les modifications\",\n  \"device.action.verify\": \"Vérifier\",\n  \"device.current_device\": \"Cet appareil\",\n  \"device.feedback.revoke_success\": \"L'appareil a été révoqué\",\n  \"device.feedback.save_success\": \"L'appareil a été sauvegardé\",\n  \"device.feedback.send-validation-success\": \"Un email a été envoyé à votre email de secours pour terminer la vérification de l'appareil.\",\n  \"device.manage_form.name.label\": \"Nom :\",\n  \"device.manage_form.type.label\": \"Type :\",\n  \"device.type.desktop\": \"Ordinateur fixe\",\n  \"device.type.laptop\": \"Ordinateur portable\",\n  \"device.type.other\": \"Autre\",\n  \"device.type.smartphone\": \"Smartphone\",\n  \"device.type.tablet\": \"Tablette\",\n  \"device.validation.failed\": \"L'appareil n'a pu être vérifié, le lien de validation n'est peut être plus valide ou peut être, l'appareil a-t-il été révoqué.<0/> Vous pouvez renvoyer le lien de vérification depuis <1>la liste des appareils</1>.\",\n  \"device.validation.in_progress\": \"Validation en cours.\",\n  \"device.validation.suceed\": \"L'appareil est maintenant vérifié, vous pouvez continuer à utiliser <0>Caliopen</0>.\",\n  \"devices.feedback.unverified_device\": \"Vous êtes connecté avec un appareil non-vérifié.\",\n  \"devices.feedback.unverified_device_more\": \"Pour respecter les règles de sécurité et de confidentialité, l'historique de vos conversations ne sera pas entièrement disponible.<0/> Merci de le vérifier cet appareil depuis un connu et vérifié.\",\n  \"discussion-draft.last-messages\": \"Derniers messages avec {participants}\",\n  \"discussion.action.goto_unread_message\": \"Vous avez des messages non-lu ↑\",\n  \"discussion.action.label\": \"Discussion complète\",\n  \"discussion.action.reply\": \"Répondre\",\n  \"draft-message.action.fix-error-on-advanced-form\": \"\",\n  \"draft-message.action.send\": \"Envoyer\",\n  \"draft-message.action.toggle-advanced\": \"Formulaire avancé ou rapide\",\n  \"draft-message.encryption.ko\": \"Ce message n'est pas chiffré\",\n  \"draft-message.encryption.ok\": \"Ce message est chiffré\",\n  \"draft-message.errors.empty-body\": \"Un texte est requis pour un message {protocol}.\",\n  \"draft-message.errors.invalid-participant\": \"En fonction de votre identité, tous vos participants doivent utiliser une adresse {protocol} pour les contacter ensemble.\",\n  \"draft-message.errors.missing-identity\": \"Une identité est nécessaire pour créer un brouillon.\",\n  \"draft-message.form.contact-recipient-selector\": \"À :\",\n  \"draft-message.form.identity.label\": \"De :\",\n  \"draft-message.form.identity.placeholder\": \"Mon identité\",\n  \"draft-message.form.placeholder.quick-reply\": \"Réponse rapide pour {recipients} par {protocol}\",\n  \"draft-message.form.placeholder.quick-reply-no-recipients\": \"Réponse rapide de {identifier}\",\n  \"draft-message.form.placeholder.quick-start\": \"Démarrer une nouvelle discussion\",\n  \"draft-message.no-available-identities\": \"Vous n'avez pas d'identité disponibles pour cette discussion. Vous pouvez en ajouter une dans votre <0>compte</0>.\",\n  \"draft.action.import_attachement\": \"Importer un fichier joint\",\n  \"draft.action.import_attachement.encryption_warning\": \"ATTENTION : Caliopen ne sait pas encore chiffrer les pièces jointes. Si vous continuez, le message ne sera pas chiffré lors de l'envoi.\",\n  \"draft.attachement.form.descr\": \"Attacher un fichier.\",\n  \"draft.feedback.send-error\": \"Impossible d'envoyer le message\",\n  \"encryption.ask-passphrase.error.invalid-passphrase\": \"Phrase de passe erronnée\",\n  \"encryption.ask-passphrase.explain\": \"Entrez votre phrase de passe pour la clé {fingerprint} pour déchiffrer {numberMessages} message(s).\",\n  \"encryption.ask-passphrase.validate\": \"Valider\",\n  \"encryption.locked-message.primary-text\": \"Le contenu de ce message est chiffré.\",\n  \"encryption.locked-message.status.decrypting\": \"Déchiffrement en cours…\",\n  \"encryption.locked-message.status.error\": \"Erreur lors de la tentative de déchiffrement.\",\n  \"encryption.locked-message.status.need_passphrase\": \"Entrez votre phrase de passe pour déverrouiller la clé.\",\n  \"encryption.locked-message.status.need_privatekey\": \"Pas de clé disponible pour déchiffrer ce message.\",\n  \"encryption.locked-message.status.no-detail\": \"…\",\n  \"encrytion.ask-passphrase.error.unknown\": \"Erreur inconnue.\",\n  \"error-boundary.back-to-home\": \"Retour à l'accueil\",\n  \"error-boundary.description\": \"<0>Nous sommes désolé, une erreur est survenue sur votre compte. C'est ce que nous appelons un « BSOD », ça ne devrait jamais arriver (mais des fois ça arrive quand même).</0><1>Vous pouvez nous aider à résoudre ce bogue en créant un nouveau sujet sur notre site web « feedback » (voir le lien plus bas). Nous essaierons de vous répondre le plus vite possible.</1><2>Bien sûr vous pouvez naviguer sur <3>feedback.caliopen.org</3> et proposer des améliorations sur Caliopen.</2>\",\n  \"error-boundary.link-to-feedback\": \"\",\n  \"error-boundary.send-feedback\": \"Nous envoyer le rapport d'erreur\",\n  \"error-boundary.short-description\": \"\",\n  \"error-boundary.title\": \"Quelque chose ne va pas.\",\n  \"file.size.B\": \"{size} o\",\n  \"file.size.gB\": \"{size} Go\",\n  \"file.size.kB\": \"{size} Ko\",\n  \"file.size.mB\": \"{size} Mo\",\n  \"file.size.tB\": \"{size} To\",\n  \"form-validation.required\": \"\",\n  \"general.action.cancel\": \"Annuler\",\n  \"general.action.load_more\": \"Charger plus\",\n  \"header.actions.search\": \"Lancer la recherche\",\n  \"header.help.faq\": \"Foire au questions\",\n  \"header.help.feedback\": \"Quelque chose ne va pas ?\",\n  \"header.help.last-changes\": \"Derniers changements [en]\",\n  \"header.help.menu\": \"Aide & Infos\",\n  \"header.help.privacy-policy\": \"Politique de confidentialité [en]\",\n  \"header.menu.account\": \"Compte\",\n  \"header.menu.agenda\": \"Agenda\",\n  \"header.menu.contact-association\": \"Associer un contact\",\n  \"header.menu.contacts\": \"Contacts\",\n  \"header.menu.discussions\": \"Messages\",\n  \"header.menu.files\": \"Fichiers\",\n  \"header.menu.search\": \"Recherche\",\n  \"header.menu.settings\": \"Paramètres\",\n  \"header.menu.signin\": \"Connexion\",\n  \"header.menu.signout\": \"Déconnexion\",\n  \"import-contact.action.import\": \"Importer\",\n  \"import-contact.action.import_contacts\": \"Importer des contacts\",\n  \"import-contact.feedback.error-contact\": \"Le fichier est valide mais les nouveaux contacts n'ont pas pu être créés\",\n  \"import-contact.feedback.error-file\": \"Ce fichier n'a pas pu être utilisé pour importer des contacts\",\n  \"import-contact.feedback.successfull\": \"Les contacts ont été importés\",\n  \"import-contact.feedback.unexpected-error\": \"Une erreur est survenue.\",\n  \"import-contact.form.button.close\": \"Fermer\",\n  \"import-contact.form.descr\": \"Importer un fichier .vcf ou .vcard (attention : Caliopen ne supporte pas les fichiers .vcard en version <3.1).\",\n  \"import-contact.form.success\": \"Fichier importé !\",\n  \"input-file-group.error.file_is_required\": \"Un fichier est requis\",\n  \"input-file-group.error.max_size\": \"La taille du fichier doit être inférieur à <0/>\",\n  \"input-file-group.error.no_valid_ext\": \"Seulement des fichiers {0}\",\n  \"input-file.add_a_file.label\": \"Ajouter un fichier\",\n  \"login.details.label\": \"Login\",\n  \"login.details.title\": \"Login :\",\n  \"message-list.action.delete\": \"Supprimer\",\n  \"message-list.action.select_all\": \"Selectionner/déselectionner tous les messages\",\n  \"message-list.action.select_all_discussions\": \"Tout sélectionner\",\n  \"message-list.action.select_single_message\": \"Selectionner/déselectionner ce message\",\n  \"message-list.confirm-delete.content\": \"La suppression est permanente, êtes-vous sûr de vouloir supprimer ces messages ainsi que d'eventuels brouillons ?\",\n  \"message-list.confirm-delete.title\": \"Supprimer la discussion\",\n  \"message-list.message.action.delete\": \"Supprimer\",\n  \"message-list.message.action.mark_as_read\": \"Marquer comme lu\",\n  \"message-list.message.action.mark_as_unread\": \"Marquer comme non lu\",\n  \"message-list.message.action.reply\": \"Répondre\",\n  \"message-list.message.action.tags\": \"Tags\",\n  \"message-list.message.confirm-delete.content\": \"La suppression est permanente, êtes-vous sûr de vouloir supprimer ce message ?\",\n  \"message-list.message.confirm-delete.title\": \"Supprimer un message\",\n  \"message-list.message.protocol.email\": \"email\",\n  \"message-list.message.received-on\": \"reçu le\",\n  \"message-list.selected\": \"{count, plural, one {#/{totalCount} message :} other {#/{totalCount} messages :}}\",\n  \"message.action.add-participant-to-contacts\": \"Ajouter un contact au carnet d'adresses\",\n  \"message.action.add-to-contacts\": \"Ajouter au carnet d'adresse\",\n  \"message.attachment-encryption-not-available\": \"Le déchiffrement des fichiers joints sera disponible dans une prochaine version, merci d'être patient.\",\n  \"message.compose.action.delete_attachement\": \"Supprimer le fichier joint\",\n  \"message.compose.action.open_import_attachements\": \"Ajouter un fichier joint\",\n  \"message.from\": \"De :\",\n  \"message.img.postal-card\": \"Ce message est comme une carte postale\",\n  \"message.img.simple-envelope\": \"Ce message est comme une enveloppe simple\",\n  \"message.participants.me\": \"Moi\",\n  \"message.pi.comportment\": \"Expéditeur\",\n  \"message.pi.context\": \"Départ\",\n  \"message.pi.description.metaphor.encrypted\": \"Dans la vie courante, ce message serait plus ou moins l'équivalent d'une lettre sous enveloppe.\",\n  \"message.pi.description.metaphor.unencrypted\": \"Dans la vie courante, ce message serait plus ou moins l'équivalent d'une carte postale visible par tous.\",\n  \"message.pi.label\": \"Indice de confidentialité :\",\n  \"message.pi.technic\": \"Trajet\",\n  \"message.to\": \"À :\",\n  \"messages.compose.action.remove-recipient\": \"Supprimer le ou la destinataire\",\n  \"messages.compose.form.body.label\": \"Message...\",\n  \"messages.compose.form.subject.label\": \"Sujet\",\n  \"messages.compose.form.to.label\": \"À :\",\n  \"messages.participants.and_x_others\": \"{first} et {number, plural, one {# autre} other {# autres}}\",\n  \"new-device-info.i-understand\": \"Je comprends\",\n  \"new-device-info.learn-more\": \"En savoir plus\",\n  \"new-device-info.learn-more.content\": \"<p>Pour évaluer le degré de confidentialité (PI) d'un message, Caliopen a besoin d'en savoir plus sur le terminal sur lequel vous allez les afficher : par nature, un terminal mobile n'est pas aussi confidentiel qu'un terminal fixe (parce que vous risquez de l'utiliser en public plus souvent).</p><p>De même un terminal partagé (ordinateur en libre accès dans un lieu public, par exemple) ne peut pas être considéré comme étant un outil aussi confidentiel que l'ordinateur personnel que vous êtes seul à utiliser.</p><p>Caliopen va tenir compte des informations que vous allez saisir ici pour évaluer si l'affichage de vos messages les plus confidentiels est possible sans risque d'être lus par des tiers : par défaut seuls les messages adaptés à la confidentialité du terminal utilisé seront affichés.</p><p>Il est donc important de décrire précisément les attributs d'un terminal depuis lequel vous ne vous êtes jamais connecté, de manière à ce que Caliopen puisse protéger au mieux votre vie privée.</p>\",\n  \"new-device-info.modal.title\": \"À propos d'un nouveau terminal\",\n  \"new-device-info.text\": \"Pour le respect de la confidentialité et des règles de sécurité, l'historique de vos discussions ne sera pas visible pour l'instant. Merci de vérifier cet appareil et éventuellement définir des restrictions depuis un appareil connu.\",\n  \"new-device-info.title\": \"C'est la première fois que vous tentez de vous connecter à votre compte Caliopen avec cet appareil.\",\n  \"next-feature.generic\": \"Cette fonctionnalité n'est pas disponible pour l'instant mais elle est plannifiée pour être ajoutée à Caliopen.\",\n  \"openpgp-key.download\": \"Télécharger et garder en lieu sûr.\",\n  \"openpgp.action.remove-key\": \"Supprimer\",\n  \"openpgp.action.toggle-details\": \"Afficher les détails\",\n  \"openpgp.details.algorithm\": \"Algorithme\",\n  \"openpgp.details.creation\": \"Création\",\n  \"openpgp.details.expiration\": \"Expiration\",\n  \"openpgp.details.identities\": \"Identités\",\n  \"openpgp.details.key-size\": \"Taille de la clé\",\n  \"openpgp.details.status\": \"Statut\",\n  \"openpgp.feedback.fingerprints-not-match\": \"Les empreintes ne correspondent pas\",\n  \"openpgp.feedback.need-passphrase\": \"La passphrase est nécessaire pour extraire la clé publique\",\n  \"openpgp.feedback.private-key-required\": \"\",\n  \"openpgp.feedback.unable-read-private-key\": \"Impossible de lire la clé privée\",\n  \"openpgp.feedback.wrong-passphrase\": \"La passphrase est incorrecte\",\n  \"openpgp.private-key\": \"Clé privée\",\n  \"openpgp.public-key\": \"Clé publique\",\n  \"openpgp.status.expired\": \"Expirée\",\n  \"openpgp.status.invalid\": \"Invalide\",\n  \"openpgp.status.no_self_cert\": \"Pas de certificat auto-signé\",\n  \"openpgp.status.revoked\": \"Révoquée\",\n  \"openpgp.status.valid\": \"Valide\",\n  \"orga-details.job.desc-full\": \"{jobDesc} chez {orgaName} {department}\",\n  \"page_not_found.caliopen-ascii\": \"Caliopen est dessiné en utilisant du ASCIi art\",\n  \"page_not_found.caliopen-ascii-logo\": \"Logo de Caliopen en ASCIi art\",\n  \"page_not_found.page_title\": \"Page introuvable\",\n  \"page_not_found.thank_you\": \"Merci d'utiliser\",\n  \"page_not_found.title\": \"Licorne introuvable\",\n  \"password.action.go_signin\": \"Se connecter\",\n  \"password.details.action.change\": \"Changer\",\n  \"password.details.password_strength.title\": \"Complexité du mot de passe :\",\n  \"password.forgot-form.action.login\": \"Se connecter\",\n  \"password.forgot-form.action.send\": \"Envoyer\",\n  \"password.forgot-form.cancel\": \"Annuler\",\n  \"password.forgot-form.instructions\": \"Entrez votre nom d'utilisateur ou l'email que vous avez utilisé lors de votre inscription et nous vous enverrons les instructions pour réinitialiser votre mot de passe.\",\n  \"password.forgot-form.recovery_email.label\": \"Email de secours\",\n  \"password.forgot-form.recovery_email.placeholder\": \"example@domaine.tld\",\n  \"password.forgot-form.success\": \"Et voilà ! Vous allez recevoir rapidement un e-mail contenant les instructions pour réinitialiser votre mot de passe.\",\n  \"password.forgot-form.title\": \"Mot de passe oublié\",\n  \"password.forgot-form.username.label\": \"Nom d'utilisateur\",\n  \"password.forgot-form.username.placeholder\": \"nom d'utilisateur\",\n  \"password.form.action.cancel\": \"Annuler\",\n  \"password.form.action.validate\": \"Valider\",\n  \"password.form.current_password.label\": \"Mot de passe actuel :\",\n  \"password.form.current_password.placeholder\": \"Saisissez votre mot de passe actuel\",\n  \"password.form.current_password.tip\": \"Le mot de passe que vous voulez remplacer.\",\n  \"password.form.feedback.error-old-password\": \"L'ancien mot de passe ne correspond pas.\",\n  \"password.form.feedback.successfull\": \"Mot de passe mis à jour !\",\n  \"password.form.feedback.unexpected-error\": \"Erreur lors de la mise à jour du mot de passe.\",\n  \"password.form.new_password.label\": \"Nouveau mot de passe :\",\n  \"password.form.new_password.placeholder\": \"Mot de passe\",\n  \"password.form.new_password.tip\": \"Le mot de passe que vous voulez utiliser à présent.\",\n  \"password.form.new_password_confirmation.error\": \"Les mots de passe ne correspondent pas\",\n  \"password.form.new_password_confirmation.label\": \"Confirmez le nouveau mot de passe :\",\n  \"password.form.new_password_confirmation.placeholder\": \"Mot de passe\",\n  \"password.reset-form.success\": \"Et voilà !\",\n  \"password.reset-form.title\": \"Réinitialiser le mot de passe\",\n  \"password_strength.feedback.moderate\": \"Complexité : moyenne\",\n  \"password_strength.feedback.strong\": \"Complexité : forte\",\n  \"password_strength.feedback.weak\": \"Complexité : faible\",\n  \"passwords.form.error.empty\": \"Au moins un champ est requis.\",\n  \"passwords.form.error.identifiants_mismatch\": \"Les identifiants ne correspondent pas.\",\n  \"passwords.form.error.user_not_found\": \"Utilisateur introuvable.\",\n  \"pwa.action.install\": \"Installer\",\n  \"pwa.feedback.install-success\": \"Caliopen a été installé sur votre appareil.\",\n  \"remote-identity-settings.unexpected-error\": \"\",\n  \"remote_identity.action.cancel\": \"Annuler\",\n  \"remote_identity.action.delete\": \"Supprimer\",\n  \"remote_identity.action.edit\": \"Modifier\",\n  \"remote_identity.action.save\": \"Enregistrer\",\n  \"remote_identity.action.toggle-advanced-form\": \"Avancé\",\n  \"remote_identity.action.toggle-email-form\": \"Email (en IMAP)\",\n  \"remote_identity.action.toggle-simple-form\": \"Simple\",\n  \"remote_identity.add_account\": \"Ajouter un compte externe\",\n  \"remote_identity.choose-provider.title\": \"Choisissez un fournisseur\",\n  \"remote_identity.confirm-delete.content\": \"Le compte externe sera désactivé est supprimé.\",\n  \"remote_identity.confirm-delete.title\": \"Supprimer un compte externe\",\n  \"remote_identity.create_help\": \"Ajouter un compte externe vous permettra d’agréger les messages de ce compte dans la boite de réception de Caliopen et de répondre à vos contacts par l’intermédiaire d’un de ces comptes.\",\n  \"remote_identity.email.help\": \"Vous pourrez entrer les données de votre serveur imap\",\n  \"remote_identity.form.identifier.error\": \"une adresse email est requise\",\n  \"remote_identity.form.identifier.error.uniqueness\": \"Cette adresse est déjà configurée\",\n  \"remote_identity.form.identifier.label\": \"Email :\",\n  \"remote_identity.form.identifier.placeholder\": \"john@doeuf.tld\",\n  \"remote_identity.form.incomming_mail_server.label\": \"Serveur de mails entrant\",\n  \"remote_identity.form.inserver\": \"Server entrant\",\n  \"remote_identity.form.mastodon.instance_label\": \"Avant d'appuyer sur Mastodon, entrez votre adresse ci-dessous (ex.: utilisateur@instance.tld)\",\n  \"remote_identity.form.outgoing_mail_server.label\": \"Serveur de mail sortant :\",\n  \"remote_identity.form.outserver\": \"Server sortant\",\n  \"remote_identity.form.password.error\": \"le mot de passe est requis\",\n  \"remote_identity.form.password.label\": \"Mot de passe :\",\n  \"remote_identity.form.port.label\": \"Numéro de port :\",\n  \"remote_identity.form.protocol.error\": \"Un protocole est requis.\",\n  \"remote_identity.form.serverHostname.error\": \"l'adresse de serveur de mail est requise\",\n  \"remote_identity.form.serverPort.error\": \"le port est requis\",\n  \"remote_identity.form.username.error\": \"le nom d'utilisateur est requis\",\n  \"remote_identity.form.username.label\": \"Identifiant :\",\n  \"remote_identity.gmail.help\": \"Vous serez redirigé vers gmail pour vous authentifier\",\n  \"remote_identity.how_to\": \"<0>Les comptes externes sont récupérés toutes les 15 minutes.<1/>Actuellemement il n'y a pas d'indicateur pour informer que le compte est correctement configuré avant le premier essai.</0>\",\n  \"remote_identity.last_connection\": \"Dernière connexion : <0/>\",\n  \"remote_identity.last_connection.never\": \"Jamais\",\n  \"remote_identity.mastodon.help\": \"Vous serez redirigé vers mastodon pour vous authentifier et autoriser l’application\",\n  \"remote_identity.provider-logo\": \"{name} logo\",\n  \"remote_identity.status.active\": \"Activé\",\n  \"remote_identity.status.inactive\": \"Désactivé\",\n  \"remote_identity.twitter.help\": \"Vous serez redirigé vers twitter pour vous authentifier et autoriser l’application\",\n  \"reply-form.in-reply-to\": \"En réponse à : {0}\",\n  \"reset-password.form.errors.token_not_found\": \"Le jeton n'est plus valide. Merci de réessayer.\",\n  \"route.compose.label\": \"Écrire\",\n  \"route.contact-association.label\": \"Associer \\\"{label}\\\"\",\n  \"route.contact-book.label\": \"Contacts\",\n  \"route.discussion.label\": \"Discussion ...\",\n  \"route.draft.label\": \"Brouillons\",\n  \"route.new-contact.label\": \"Nouveau contact\",\n  \"route.search-results.label\": \"Resultats pour : {term}\",\n  \"route.settings.label.application\": \"Application\",\n  \"route.settings.label.default\": \"Paramètres\",\n  \"route.settings.label.devices\": \"Appareils\",\n  \"route.settings.label.identities\": \"Identités\",\n  \"route.settings.label.tags\": \"Tags\",\n  \"route.timeline.label\": \"Discussions\",\n  \"route.user.label.default\": \"Compte\",\n  \"route.user.label.profile\": \"Profil\",\n  \"route.user.label.security\": \"Sécurité\",\n  \"screenshot.desktop\": \"Un écran de Caliopen sur ordinateur\",\n  \"screenshot.smartphone\": \"Un écran de Caliopen sur téléphone\",\n  \"search-results.actions.display-all\": \"Afficher tout\",\n  \"search-results.all\": \"Tous ({total})\",\n  \"search-results.contacts\": \"Contacts ({nbContacts})\",\n  \"search-results.messages\": \"Messages ({nbMessages})\",\n  \"search-results.preview.nb-contacts\": \"{nbContacts} contacts contiennent \\\"{term}\\\" dans leur libellé ou profil :\",\n  \"search-results.preview.nb-messages\": \"{nbMessages} messages contiennent \\\"{term}\\\" dans leur sujet ou contenu :\",\n  \"settings.application\": \"Application\",\n  \"settings.contact.display_format.options.first_last\": \"Prénom, Nom\",\n  \"settings.contact.display_format.options.last_first\": \"Nom, Prénom\",\n  \"settings.contact.display_order_by.options.firstname\": \"Prénom\",\n  \"settings.contact.display_order_by.options.lastname\": \"Nom\",\n  \"settings.contact.title\": \"Paramètres des contacts\",\n  \"settings.contacts.display.label\": \"Affichage\",\n  \"settings.contacts.order.label\": \"Trier par\",\n  \"settings.desktop_notification.action.request-desktop_notification_permission\": \"Activer les notifications de bureau\",\n  \"settings.desktop_notification.action.test_desktop_notification\": \"Tester les notifications de bureau\",\n  \"settings.desktop_notification.desktop_notifications_enabled\": \"Les notifications de bureau sont activées\",\n  \"settings.desktop_notification.disabled\": \"Les notifications sont désactivées, merci de vérifier les paramètres de votre navigateur\",\n  \"settings.desktop_notification.feedback.enabled\": \"Les notifications de bureau sont activées\",\n  \"settings.desktop_notification.no_support\": \"Les notifications de bureau ne sont pas prises en charge par votre navigateur\",\n  \"settings.desktop_notification.title\": \"Notifications du bureau\",\n  \"settings.devices\": \"Appareils\",\n  \"settings.form.feedback.successfull\": \"Paramètres mis à jour !\",\n  \"settings.form.feedback.unexpected-error\": \"Erreur lors de la mise à jour des paramètres.\",\n  \"settings.identities\": \"Comptes externes\",\n  \"settings.interface.language.label\": \"Langue\",\n  \"settings.interface.language.options.de\": \"Allemand\",\n  \"settings.interface.language.options.en\": \"Anglais\",\n  \"settings.interface.language.options.fr\": \"Français\",\n  \"settings.interface.title\": \"Personnalisez votre interface\",\n  \"settings.message.display_format.label\": \"Affichage\",\n  \"settings.message.display_format.options.plain_text\": \"Texte seul\",\n  \"settings.message.display_format.options.rich_text\": \"Texte enrichi\",\n  \"settings.message.title\": \"Paramètres des messages\",\n  \"settings.notification.delay_disappear.label\": \"Délai d'affichage\",\n  \"settings.notification.delay_disappear.options.second\": \"{0} secondes\",\n  \"settings.notification.enabled.label\": \"Activé\",\n  \"settings.notification.message_preview.label\": \"Prévisualisation des messages\",\n  \"settings.notification.message_preview.options.always\": \"Toujours\",\n  \"settings.notification.message_preview.options.off\": \"Non\",\n  \"settings.notification.sound_enabled.label\": \"Activation des sons\",\n  \"settings.notification.title\": \"Paramètres des notifications\",\n  \"settings.presentation.update.action\": \"Enregistrer les paramètres\",\n  \"settings.signature.label\": \"Signature\",\n  \"settings.signature.update.action\": \"Sauvegarder\",\n  \"settings.signatures.title\": \"Mettre à jour votre signature\",\n  \"settings.tag.action.save-tag\": \"Enregistrer\",\n  \"settings.tag.form.error.create_fail\": \"Impossible de créer le tag. Peut-être qu'un tag avec un id identique existe déjà.\",\n  \"settings.tag.form.error.update_failed\": \"\",\n  \"settings.tags\": \"Tags\",\n  \"settings.tags.action.delete\": \"Supprimer\",\n  \"settings.tags.title\": \"Tags\",\n  \"settings.tags.title.create\": \"Créer un nouveau tag\",\n  \"signin.action.forgot_password\": \"Mot de passe oublié ?\",\n  \"signin.action.login\": \"Connexion\",\n  \"signin.create_an_account\": \"Créer un compte\",\n  \"signin.feedback.invalid\": \"Les identifiants ne sont pas valides\",\n  \"signin.feedback.required_password\": \"Un mot de passe est requis\",\n  \"signin.feedback.required_username\": \"Un nom d'utilisateur est requis\",\n  \"signin.form.password.label\": \"Mot de passe :\",\n  \"signin.form.password.placeholder\": \"Mot de passe\",\n  \"signin.form.username.label\": \"Nom d'utilisateur :\",\n  \"signin.form.username.placeholder\": \"Nom d'utilisateur\",\n  \"signup.action.create\": \"Créer\",\n  \"signup.feedback.invalid\": \"Les identifiants ne sont pas valides\",\n  \"signup.feedback.invalid_recovery_email\": \"L'email doit être valide\",\n  \"signup.feedback.required_password\": \"Un mot de passe est requis\",\n  \"signup.feedback.required_privacy\": \"Vous devez accepter la collecte de données\",\n  \"signup.feedback.required_recovery_email\": \"Un email de secours est requis\",\n  \"signup.feedback.required_tos\": \"Nous avons besoin de votre accord avec les conditions générales d'utilisation\",\n  \"signup.feedback.required_username\": \"Un nom d'utilisateur est requis\",\n  \"signup.feedback.unable_to_signup\": \"Impossible de créer un compte. Merci d'essayer à nouveau ou de contacter un administrateur.\",\n  \"signup.feedback.unavailable_username\": \"Nous sommes désolés, ce nom d'utilisateur n'est pas disponible\",\n  \"signup.feedback.username_double_dots\": \"Le nom d'utilisateur ne peut contenir deux points (.) d'affilée\",\n  \"signup.feedback.username_invalid_characters\": \"Le nom d'utilisateur ne peut contenir certains caractères spéciaux tels que {0} et espace\",\n  \"signup.feedback.username_length\": \"La longueur du nom d'utilisateur doit être comprise entre 3 et 42\",\n  \"signup.feedback.username_starting_ending_dot\": \"Le nom d'utilisateur ne peut commencer ou finir par un point (.)\",\n  \"signup.form.invitation_email.label\": \"Adresse email de secours :\",\n  \"signup.form.invitation_email.placeholder\": \"example@domaine.tld\",\n  \"signup.form.invitation_email.tip\": \"Merci de renseigner un email pour que nous puissions vous informer des évolutions de Caliopen.\",\n  \"signup.form.password.label\": \"Mot de passe :\",\n  \"signup.form.password.placeholder\": \"Mot de passe\",\n  \"signup.form.privacy.checkbox.label\": \"Je comprends et j'accepte\",\n  \"signup.form.privacy.intro\": \"Durant toute la phase de développement, nous recueillons certaines données d'utilisation, mais pas plus que la NSA :-).\",\n  \"signup.form.privacy.more_info\": \"En savoir plus\",\n  \"signup.form.privacy.title\": \"Confidentialité\",\n  \"signup.form.username.label\": \"Nom d'utilisateur :\",\n  \"signup.form.username.placeholder\": \"nom d'utilisateur\",\n  \"signup.go_signin\": \"J'ai déjà un compte\",\n  \"signup.privacy.modal.close\": \"Ok compris !\",\n  \"signup.privacy.modal.label\": \"À propos de Piwik\",\n  \"signup.privacy.modal.text.alpha_tester\": \"En tant que beta-testeur, votre contribution est précieuse et nous permet de travailler à la finalisation de Caliopen.\",\n  \"signup.privacy.modal.text.desactivate_dnt\": \"Vous devez pour cela désactiver l'option DoNotTrack de votre navigateur (plus d'information ici http://donottrack.us) et autoriser l'installation de cookies.\",\n  \"signup.privacy.modal.text.get_data\": \"Dans ce but, vous acceptez que nous recueillions certaines données relatives à votre utilisation de l’application (pages visitées, durées, clics, scrolls… en fait, pas loin de tout ce qui peut être tracé !).\",\n  \"signup.privacy.modal.text.piwik\": \"Nous utilisons l'outil de mesure d’audience open source https://piwik.org/, les données récoltées ne seront pas divulguées à qui que ce soit hors du contexte de la version beta de Caliopen.\",\n  \"signup.privacy.modal.title\": \"Caliopen est en développement !\",\n  \"spinner.loading\": \"\",\n  \"tag_list.all_contacts\": \"Tous les contacts\",\n  \"tags.action.add\": \"Ajouter\",\n  \"tags.action.remove\": \"Supprimer\",\n  \"tags.common_tags_applied\": \"Tags en commun s'appliquant à la sélection en cours :\",\n  \"tags.form.add.label\": \"Ajouter un tag\",\n  \"tags.form.add.placeholder\": \"Nouveau tag ...\",\n  \"tags.form.search.label\": \"Rechercher\",\n  \"tags.form.search.placeholder\": \"Rechercher un tag…\",\n  \"tags.header.label\": \"Tags\",\n  \"tags.header.title\": \"Tags <0>(Total: {nb})</0>\",\n  \"tags.label.important\": \"Important\",\n  \"tags.label.inbox\": \"Boite de réception\",\n  \"tags.label.spam\": \"Spam\",\n  \"take-a-tour.action.close\": \"Fermer\",\n  \"take-a-tour.action.last-step\": \"Terminer\",\n  \"take-a-tour.action.next\": \"Suivant\",\n  \"take-a-tour.action.prev\": \"Précédent\",\n  \"take-a-tour.action.skip\": \"Ignorer\",\n  \"take-a-tour.action.toggle\": \"Visite guidée\",\n  \"take-a-tour.current-step\": \"Visite guidée ({current} de {total})\",\n  \"take-a-tour.step.call-to-action.content\": \"Créer à la volée un nouveau message.\",\n  \"take-a-tour.step.call-to-action.title\": \"Créer rapidement\",\n  \"take-a-tour.step.install.content\": \"<0>Un boutton d'installationsera visible si l'appareil est compatible.</0><1>Sur android and ios Caliopen sera ajouté dans l'accueil de l'appareil. Sur ordinateur, cette fonctionnalité est disponible sur chrome et chromium.</1><2>Cette technologie est appelé « Progressive Web App », cela permet d'avoir une application plus rapide à charger, certaines fonctionnalités seront disponibles hors ligne et aidera à interrargir avec votre appareil (comme les notifications) si vous êtes d'accord.</2>\",\n  \"take-a-tour.step.install.title\": \"Installation\",\n  \"take-a-tour.step.intro.content\": \"<0>Avec Caliopen, vous pouvez accéder à tous vos messages privés (Email, et plus à venir) via un seul login. </0><1>Maintenant, jetez un coup d'oeil à nos principales fonctionnalités, telles que la gestion unifiée des messages, la recherche intuitive et plus encore !</1><2>La première fois, le fil de discussion peut sembler vide, vous pouvez facilement <3>ajouter un compte externe</3> depuis le menu utilisateur</2>\",\n  \"take-a-tour.step.intro.title\": \"Bienvenue !\",\n  \"take-a-tour.step.search.content\": \"<0>Chaque recherche peut inclure des filtres. Toutes les données non chiffrées peuvent être recherchées.</0><1>Ici, vous pouvez rechercher tout ce qui se trouve dans vos messages et vos contacts.</1>\",\n  \"take-a-tour.step.search.title\": \"Recherche intuitive\",\n  \"take-a-tour.step.user-menu.content\": \"<0>Mettez à jour les informations de votre compte.</0><1>Personnalisez votre application dans vos paramètres.</1><2>Et connectez des applications comme GMail, Twitter, Mastodon</2>\",\n  \"take-a-tour.step.user-menu.title\": \"Menu utilisateur\",\n  \"timeline.action.delete\": \"Supprimer\",\n  \"timeline.action.display-spam\": \"Afficher les messages indésirables\",\n  \"timeline.confirm-delete.content\": \"La suppression est permanente, êtes-vous sûr de vouloir supprimer ces messages ?\",\n  \"timeline.confirm-delete.title\": \"Supprimer des message(s)\",\n  \"timeline.discussions.selected\": \"{count, plural, one {#/{totalCount} discussion} other {#/{totalCount} discussions}}\",\n  \"timeline.draft-prefix\": \"Brouillon en cours :\",\n  \"timeline.new_messages\": \"Vous avez {0} nouveaux messages\",\n  \"unexpected_error.page_title\": \"\",\n  \"user.action.cancel_edit\": \"Annuler\",\n  \"user.action.delete\": \"Suppression de compte\",\n  \"user.action.edit_profile\": \"Éditer\",\n  \"user.action.update\": \"Mettre à jour\",\n  \"user.delete-form.action.delete\": \"Supprimer mon compte Caliopen (votre identifiant sera interdit pour toute utilisation future)\",\n  \"user.delete-form.error.incorrect_password\": \"Impossible de supprimer votre compte, mot de passe incorrect.\",\n  \"user.delete-form.modal-content\": \"Êtes-vous sûr de supprimer votre compte Caliopen ?\",\n  \"user.delete-form.modal-title\": \"Suppression de compte\",\n  \"user.delete-form.password.label\": \"Mot de passe\",\n  \"user.delete-form.password.placeholder\": \"mot de passe\",\n  \"user.feedback.delete_account_sucessful\": \"Votre compte a été supprimé\",\n  \"user.openpgp.action.add\": \"Ajouter\",\n  \"user.openpgp.action.create\": \"Créer\",\n  \"user.openpgp.action.edit-keys\": \"Éditer et ajouter des clés\",\n  \"user.openpgp.action.switch-generate-key\": \"Générer des clés\",\n  \"user.openpgp.action.switch-import-raw-key\": \"Importer des clés\",\n  \"user.openpgp.form.email.label\": \"Email\",\n  \"user.openpgp.form.passphrase.label\": \"Phrase de passe\",\n  \"user.openpgp.form.private-key.label\": \"Clé privée\",\n  \"user.openpgp.has-passphrase\": \"Activer la phrase de passe\",\n  \"user.privacy.improve_pi\": \"Améliorez votre indice de confidentialité\",\n  \"user.profile\": \"Profil\",\n  \"user.profile.form.family_name.label\": \"Nom de famille\",\n  \"user.profile.form.given_name.label\": \"Prénom\",\n  \"user.profile.form.recovery_email.label\": \"Email de secours\",\n  \"user.profile.form.title\": \"Complétez votre profil\",\n  \"user.profile.form.username.label\": \"Pseudo\",\n  \"user.profile.subscribed_date\": \"Inscrit⋅e le\",\n  \"user.security\": \"Sécurité\",\n  \"user.security.section_password.title\": \"Mot de passe\",\n  \"user.security.section_pgpkeys.title\": \"Clés privées PGP\",\n  \"view.draft.label\": \"Brouillons\"\n}\n"
  },
  {
    "path": "src/frontend/web_application/package.json",
    "content": "{\n  \"name\": \"@caliopen/frontend\",\n  \"version\": \"0.27.1\",\n  \"description\": \"CaliOpen UI is the user interface for CaliOpen.\",\n  \"homepage\": \"https://caliopen.org/\",\n  \"private\": true,\n  \"engines\": {\n    \"node\": \">=8.11.1\"\n  },\n  \"scripts\": {\n    \"start\": \"npm-run-all -l build:clean build:i18n --parallel --race start:dev:server start:dev:assets\",\n    \"start:hostname\": \"CALIOPEN_HOSTNAME=caliopen.local CALIOPEN_WEB_SERVER_HOSTNAME=caliopen.local npm-run-all start\",\n    \"start:dev\": \"CALIOPEN_API_HOSTNAME=localhost HAS_SSR=false npm-run-all -l build:clean build:i18n --parallel --race start:dev:server start:dev:assets\",\n    \"start:dev:server\": \"CALIOPEN_WEB_SERVER_PORT=4001 bin/dev-server\",\n    \"start:dev:assets\": \"NODE_ENV=development CALIOPEN_MOTD='credentials: dev / 123456' webpack serve --host 0.0.0.0 --progress --config webpack/webpack.config.browser.js --port 4000\",\n    \"start:prod\": \"NODE_ENV=production ./bin/server\",\n    \"start:api\": \"cd ../../../devtools/ && docker-compose up -d objectstore proxyapi lmtpd identitypoller && docker-compose restart proxyapi && sleep 10 && docker-compose ps\",\n    \"start:mock-api\": \"../../../devtools/api-mock/bin/start\",\n    \"build:clean\": \"if [ -d dist ];then rm -r dist;fi\",\n    \"build:browser:test\": \"NODE_ENV=production CALIOPEN_PIWIK_SITE_ID=false webpack --mode production --config webpack/webpack.config.browser.js\",\n    \"build:server:test\": \"NODE_ENV=production HAS_SSR=true webpack --mode production --config webpack/webpack.config.server.js\",\n    \"build:i18n\": \"lingui compile\",\n    \"release\": \"npm-run-all build:i18n release:web:browser release:web:server\",\n    \"release:web:server\": \"NODE_ENV=production webpack --bail --mode production --progress --config webpack/webpack.config.server.js\",\n    \"release:web:browser\": \"NODE_ENV=production webpack --bail --mode production --progress --config webpack/webpack.config.browser.js\",\n    \"test\": \"npm-run-all lint test:unit\",\n    \"test:unit\": \"NODE_ENV=test jest\",\n    \"test:unit:watch\": \"jest --watch\",\n    \"lint\": \"npm-run-all lint:js lint:styles prettier:check\",\n    \"lint:js\": \"eslint --ext .js,.jsx,.ts,.tsx --ignore-pattern *.spec.js,*.spec.jsx,*.spec.ts,*.spec.tsx  src/\",\n    \"lint:styles\": \"stylelint 'src/**/*.scss'\",\n    \"prettier\": \"prettier\",\n    \"prettier:check\": \"prettier --check .\",\n    \"i18n\": \"react-translate-scripts\",\n    \"lingui\": \"lingui\",\n    \"update-browser-list\": \"npx browserslist@latest --update-db\"\n  },\n  \"author\": \"CaliOpen <contact@caliopen.org> (https://caliopen.org/)\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.17.8\",\n    \"@lingui/core\": \"^3.13.2\",\n    \"@lingui/react\": \"^3.13.2\",\n    \"@reduxjs/toolkit\": \"^1.8.0\",\n    \"@types/zxcvbn\": \"^4.4.1\",\n    \"argv\": \"^0.0.2\",\n    \"async-validator\": \"^3.2.4\",\n    \"axios\": \"^0.20.0\",\n    \"babel-runtime\": \"^6.26.0\",\n    \"base64-js\": \"^1.5.1\",\n    \"body-parser\": \"^1.19.2\",\n    \"classnames\": \"^2.3.1\",\n    \"cookie\": \"^0.4.2\",\n    \"cookie-parser\": \"^1.4.6\",\n    \"debug\": \"^4.3.4\",\n    \"elliptic\": \"^6.5.4\",\n    \"express\": \"^4.17.3\",\n    \"express-http-proxy\": \"1.6.3\",\n    \"font-awesome\": \"^4.6.3\",\n    \"formik\": \"^2.2.9\",\n    \"foundation-sites\": \"~6.3.0\",\n    \"history\": \"^4.10.1\",\n    \"iron\": \"5.0.6\",\n    \"jquery\": \"^3.6.0\",\n    \"jssha\": \"^2.3.1\",\n    \"linkifyjs\": \"^2.1.9\",\n    \"locale\": \"^0.1.0\",\n    \"lodash\": \"^4.17.21\",\n    \"mime-types\": \"^2.1.35\",\n    \"mimemessage\": \"^1.0.5\",\n    \"moment\": \"^2.29.1\",\n    \"moment-timezone\": \"^0.5.34\",\n    \"openpgp\": \"^4.10.10\",\n    \"piwik-react-router\": \"^0.12.1\",\n    \"prop-types\": \"^15.8.1\",\n    \"quoted-printable\": \"^1.0.1\",\n    \"react\": \"^17.0.2\",\n    \"react-country-region-selector\": \"^1.4.7\",\n    \"react-datepicker\": \"2.4.0\",\n    \"react-document-title\": \"^2.0.3\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-loadable\": \"^5.5.0\",\n    \"react-modal\": \"^3.14.4\",\n    \"react-moment\": \"^0.9.7\",\n    \"react-query\": \"^3.34.16\",\n    \"react-redux\": \"^7.2.6\",\n    \"react-redux-notify\": \"^4.2.1\",\n    \"react-router-dom\": \"^5.2.0\",\n    \"react-swipeable\": \"^5.5.1\",\n    \"react-visibility-sensor\": \"^5.1.1\",\n    \"reactour\": \"^1.10.0\",\n    \"redux\": \"^4.1.2\",\n    \"redux-axios-middleware\": \"^4.0.1\",\n    \"redux-form\": \"^8.3.8\",\n    \"reselect\": \"^4.1.5\",\n    \"scroll\": \"^3.0.1\",\n    \"serialize-javascript\": \"^3.1.0\",\n    \"serve-favicon\": \"^2.5.0\",\n    \"styled-components\": \"5.3.3\",\n    \"utf8\": \"^3.0.0\",\n    \"uuid\": \"3.3.2\",\n    \"winston\": \"^3.6.0\",\n    \"winston-syslog\": \"^2.5.0\",\n    \"xregexp\": \"^4.3.0\",\n    \"zxcvbn\": \"^4.4.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.17.8\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.16.7\",\n    \"@babel/plugin-proposal-decorators\": \"^7.17.8\",\n    \"@babel/plugin-proposal-export-default-from\": \"^7.16.7\",\n    \"@babel/plugin-proposal-export-namespace-from\": \"^7.16.7\",\n    \"@babel/plugin-proposal-object-rest-spread\": \"^7.17.3\",\n    \"@babel/plugin-syntax-dynamic-import\": \"^7.8.3\",\n    \"@babel/plugin-transform-runtime\": \"^7.17.0\",\n    \"@babel/polyfill\": \"^7.12.1\",\n    \"@babel/preset-env\": \"^7.16.11\",\n    \"@babel/preset-react\": \"^7.16.7\",\n    \"@babel/preset-typescript\": \"^7.16.7\",\n    \"@lingui/cli\": \"^3.13.2\",\n    \"@testing-library/jest-dom\": \"5.16.5\",\n    \"@testing-library/react\": \"12.1.4\",\n    \"@testing-library/user-event\": \"^13.5.0\",\n    \"@types/classnames\": \"^2.3.1\",\n    \"@types/elliptic\": \"^6.4.14\",\n    \"@types/express\": \"^4.17.13\",\n    \"@types/express-http-proxy\": \"^1.6.3\",\n    \"@types/iron\": \"^5.0.2\",\n    \"@types/jest\": \"^27.4.1\",\n    \"@types/lingui__react\": \"^3.0.0\",\n    \"@types/lodash\": \"^4.14.180\",\n    \"@types/openpgp\": \"^4.4.18\",\n    \"@types/react\": \"^17.0.41\",\n    \"@types/react-dom\": \"^17.0.14\",\n    \"@types/react-redux\": \"^7.1.23\",\n    \"@types/react-router-dom\": \"^5.3.3\",\n    \"@types/redux\": \"^3.6.0\",\n    \"@types/redux-form\": \"^8.3.3\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.15.0\",\n    \"@typescript-eslint/parser\": \"^5.15.0\",\n    \"autoprefixer\": \"^10.4.4\",\n    \"axios-mock-adapter\": \"^1.20.0\",\n    \"babel-core\": \"^7.0.0-bridge.0\",\n    \"babel-eslint\": \"10.1.0\",\n    \"babel-jest\": \"^27.5.1\",\n    \"babel-loader\": \"^8.2.3\",\n    \"babel-plugin-dynamic-import-node\": \"^2.3.3\",\n    \"babel-plugin-macros\": \"^3.1.0\",\n    \"css-loader\": \"^5.2.4\",\n    \"deep-freeze-strict\": \"^1.1.1\",\n    \"enzyme\": \"^3.11.0\",\n    \"enzyme-adapter-react-16\": \"^1.15.6\",\n    \"eslint\": \"^8.11.0\",\n    \"eslint-config-airbnb\": \"^19.0.4\",\n    \"eslint-config-prettier\": \"^8.5.0\",\n    \"eslint-import-resolver-typescript\": \"^2.5.0\",\n    \"eslint-plugin-import\": \"^2.25.4\",\n    \"eslint-plugin-jest-dom\": \"^4.0.1\",\n    \"eslint-plugin-jsx-a11y\": \"^6.5.1\",\n    \"eslint-plugin-react\": \"^7.29.4\",\n    \"expose-loader\": \"^2.0.0\",\n    \"extract-text-webpack-plugin\": \"^4.0.0-beta.0\",\n    \"file-loader\": \"^6.2.0\",\n    \"hard-source-webpack-plugin\": \"^0.13.1\",\n    \"html-webpack-harddisk-plugin\": \"^2.0.0\",\n    \"html-webpack-plugin\": \"^5.3.1\",\n    \"http-server\": \"^0.12.3\",\n    \"image-webpack-loader\": \"^7.0.1\",\n    \"jasmine-spec-reporter\": \"^6.0.0\",\n    \"jest-cli\": \"^29.0.3\",\n    \"jest-environment-jsdom\": \"^29.0.3\",\n    \"mini-css-extract-plugin\": \"^2.6.0\",\n    \"msw\": \"^0.39.2\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"null-loader\": \"^4.0.1\",\n    \"offline-plugin\": \"^5.0.6\",\n    \"optimize-css-assets-webpack-plugin\": \"^6.0.1\",\n    \"postcss-loader\": \"^6.2.1\",\n    \"prettier\": \"^2.6.0\",\n    \"raf\": \"^3.4.1\",\n    \"raw-loader\": \"^4.0.2\",\n    \"react-addons-test-utils\": \"^15.6.2\",\n    \"react-dev-utils\": \"^12.0.0\",\n    \"react-test-renderer\": \"^17.0.2\",\n    \"redux-mock-store\": \"^1.5.4\",\n    \"sass\": \"^1.49.9\",\n    \"sass-loader\": \"^12.6.0\",\n    \"script-loader\": \"^0.7.2\",\n    \"source-map-loader\": \"^3.0.1\",\n    \"style-loader\": \"^3.3.1\",\n    \"stylelint\": \"^14.6.0\",\n    \"stylelint-config-prettier\": \"^9.0.3\",\n    \"stylelint-config-standard-scss\": \"^3.0.0\",\n    \"stylelint-order\": \"^5.0.0\",\n    \"stylelint-scss\": \"^4.2.0\",\n    \"ts-loader\": \"^9.2.8\",\n    \"typescript\": \"^4.6.2\",\n    \"uglifyjs-webpack-plugin\": \"^2.2.0\",\n    \"webpack\": \"^5.70.0\",\n    \"webpack-cli\": \"^4.9.2\",\n    \"webpack-dev-server\": \"^3.11.2\",\n    \"webpack-merge\": \"^5.8.0\",\n    \"webpack-pwa-manifest\": \"^4.3.0\",\n    \"worker-loader\": \"^3.0.8\"\n  },\n  \"jest\": {\n    \"testEnvironment\": \"jsdom\",\n    \"setupFilesAfterEnv\": [\n      \"<rootDir>/test/unit/setup.ts\"\n    ],\n    \"moduleFileExtensions\": [\n      \"js\",\n      \"json\",\n      \"jsx\",\n      \"ts\",\n      \"tsx\",\n      \"d.ts\"\n    ],\n    \"moduleNameMapper\": {\n      \"^.+\\\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$\": \"<rootDir>/__mocks__/fileMock.js\",\n      \"^.+\\\\.(css|less|scss)$\": \"<rootDir>/__mocks__/styleMock.js\",\n      \"^src/(.*)$\": \"<rootDir>/src/$1\",\n      \"^locale/(.*)$\": \"<rootDir>/locale/$1\",\n      \"^test/(.*)$\": \"<rootDir>/test/$1\"\n    },\n    \"globals\": {\n      \"CALIOPEN_ENV\": \"test\",\n      \"BUILD_TARGET\": \"browser\",\n      \"CALIOPEN_OPTIONS\": {}\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "src/frontend/web_application/public/privacy-policy.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"shortcut icon\" href=\"/favicon.ico\" />\n    <title>CaliOpen - Privacy Policy</title>\n  </head>\n  <body>\n    <h1>Privacy Policy</h1>\n    <p>\n      In this policy, Caliopen refers to the web service of the Caliopen\n      Project. This Privacy Policy explains (1) what information is collected\n      when you access and use Caliopen (2) the use Caliopen makes of this\n      information; and (3) the security applied to protect this information.\n    </p>\n    <p>\n      By using Caliopen, you consent to the terms outlined in this privacy\n      policy.\n    </p>\n    <h2>Legal Framework</h2>\n    <p>\n      Caliopen is currently being built by Gandi. Gandi is located in France and\n      all data storage are handled by Gandi under the french laws and\n      regulations.\n    </p>\n    <p>\n      Any information provided to Caliopen is considered personal data as\n      defined and protected by the french DPA (CNIL).\n    </p>\n    <p>\n      Such data will only be used to contact you with important notifications\n      about Caliopen (to create your account, recover your password...). You are\n      free, at any given time, to delete your account and all the information it\n      holds through the account settings panel.\n    </p>\n    <p>\n      Service's user data collection is limited to the following:<br />\n      Visiting or using our website: We employ a local installation of Matomo,\n      an open source analytics tool. Analytics are anonymized whenever possible\n      and stored locally.\n    </p>\n    <p>\n      <em>Account creation:</em> You have to provide an external email address\n      for notification or password recovery purposes.\n    </p>\n    <p>\n      <em>Account activity:</em> Depending on the protocols used when you\n      exchange with your contacts, Caliopen may have to process some metadata:\n      sender and recipient email addresses, the IP address incoming messages\n      originated from, message subject, and message sent and received times. We\n      do NOT have access to encrypted message content but unencrypted messages\n      sent from external providers to Caliopen are scanned for spam and viruses\n      for the protection of our users. We also have access to the following\n      records of account activity: devices signatures and parameters, number of\n      messages sent, amount of storage space used, total number of messages,\n      last login time.\n    </p>\n    <p>\n      <em>Communicating with Caliopen:</em> Your communications, such as support\n      requests, bug reports, or feature requests may be saved by our staff.\n    </p>\n    <p>\n      <em>IP Logging:</em> IP logs are kept for one year for statistics and to\n      combat abuse and fraud, and your IP address may be retained if you are\n      engaged in activities that breach our terms and conditions (spamming, DDoS\n      attacks against Caliopen infrastructure, brute force attacks, etc).\n    </p>\n    <p>\n      We do NOT do any analysis on the limited data we do possess with two\n      exceptions:<br />\n      Emails sent unencrypted to Caliopen accounts are scanned automatically for\n      spam so we can block IPs which are sending a lot of spam to Caliopen users\n      and tag spam messages with a very low importance level.\n    </p>\n    <p>\n      Emails sent unencrypted to Caliopen accounts are also scanned\n      automatically for \"smart\" tagging.\n    </p>\n    <h2>Data Storage</h2>\n    <p>\n      All servers used in connection with Caliopen are wholly owned and operated\n      by Gandi. Only Gandi's employees have physical or other access to the\n      servers. User credentials needed for remote mailbox or private\n      communication source like Twitter DM, are stored in encrypted format on\n      our vault servers.\n    </p>\n    <h2>Right to Access, Rectification, Erasure and Portability</h2>\n    <p>\n      Through your interface, you can directly access, edit, or delete personal\n      data processed by Caliopen.\n    </p>\n    <h2>Data Retention</h2>\n    <p>\n      When a Caliopen account is closed, data is immediately deleted from\n      production servers. Deleted messages are also permanently deleted from\n      production servers.\n    </p>\n    <h2>Modifications to Privacy Policy</h2>\n    <p>\n      Caliopen reserves the right to periodically review and change this policy\n      from time to time and will notify users when it does. Continued use of\n      Caliopen will be deemed as acceptance of such changes.\n    </p>\n    <h2>Applicable Law</h2>\n    <p>This Agreement shall be governed in all respects by french laws.</p>\n  </body>\n</html>\n"
  },
  {
    "path": "src/frontend/web_application/server/api/index.js",
    "content": "import proxy from 'express-http-proxy';\nimport url from 'url';\n\nconst { getConfig, getApiHost } = require('../config');\n\nexport default (app) => {\n  const {\n    api: { checkCertificate },\n    maxBodySize,\n  } = getConfig();\n  const target = getApiHost();\n\n  app.use(\n    '/api',\n    proxy(target, {\n      limit: maxBodySize,\n      proxyReqPathResolver: (req) => url.parse(req.originalUrl).path,\n      proxyReqOptDecorator: (proxyReqOpts, srcReq) => {\n        const decoratedReqOpts = {\n          ...proxyReqOpts,\n          rejectUnauthorized: checkCertificate,\n        };\n\n        if (!srcReq.user) {\n          return decoratedReqOpts;\n        }\n\n        // TODO refactor in Auth library may be or ...\n        const bearer = new Buffer(\n          `${srcReq.user.user_id}:${srcReq.user.tokens.access_token}`\n        ).toString('base64');\n\n        return {\n          ...decoratedReqOpts,\n          headers: {\n            ...proxyReqOpts.headers,\n            Authorization: `Bearer ${bearer}`,\n          },\n        };\n      },\n    })\n  );\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/api/lib/api.ts",
    "content": "import http from 'http';\nimport https from 'https';\nimport createDebug from 'debug';\nimport { getConfig } from '../../config';\nimport { ServerError } from '../../error/consts';\n\nconst debug = createDebug('caliopen.web:app:api-query');\n\ntype RequestParams = http.RequestOptions | https.RequestOptions;\n\ninterface Options {\n  body?: string | Record<string, any>;\n}\n\n/**\n * Internal promisified requests.\n * This is a basic wrapper that use current host configuration to make requests on the API.\n *\n * @returns data from response in a Promise\n */\nexport const query = (\n  requestParams: RequestParams,\n  opts: Options = {}\n): Promise<string | Array<any> | Record<string, any>> => {\n  return new Promise((resolve, reject) => {\n    const {\n      api: { protocol, hostname, port, checkCertificate },\n    } = getConfig();\n    const params = {\n      protocol: `${protocol}:`,\n      hostname,\n      port,\n      ...requestParams,\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json',\n        ...(requestParams.headers || {}),\n      },\n    };\n\n    const options = {\n      body: undefined,\n      ...opts,\n    };\n\n    let postData: undefined | string;\n    if (options.body) {\n      postData = JSON.stringify(options.body);\n      params.headers['Content-Length'] = Buffer.byteLength(postData);\n    }\n\n    if (!checkCertificate && protocol === 'https') {\n      // @ts-ignore: this param only exists for `https.RequestOptions`\n      params.rejectUnauthorized = false;\n    }\n\n    debug('\\n', 'Preparing API query:', '\\n', params);\n\n    const request = protocol === 'https' ? https.request : http.request;\n    const req = request(params, (res) => {\n      debug(\n        '\\n',\n        'API query response:',\n        '\\n',\n        res.statusCode,\n        res.statusMessage,\n        res.headers\n      );\n\n      const data: Array<Buffer> = [];\n\n      res.on('data', (chunk) => {\n        data.push(chunk);\n      });\n\n      res.on('end', () => {\n        let responseBody = Buffer.concat(data).toString();\n\n        if (\n          res.headers['content-type'] &&\n          res.headers['content-type'].indexOf('json') !== -1\n        ) {\n          responseBody = JSON.parse(responseBody);\n        }\n\n        if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {\n          resolve(responseBody);\n        } else {\n          const error = new ServerError(\n            `API Query Error ${res.statusCode} : ${res.statusMessage}`\n          );\n          if (res.statusCode) {\n            error.status = res.statusCode;\n          }\n          reject(error);\n        }\n      });\n    });\n\n    if (postData) {\n      req.write(postData);\n    }\n\n    debug('\\n', 'Outgoing API query:', '\\n', req);\n\n    req.end();\n  });\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/api/lib/sub-request-manager.js",
    "content": "const CALIOPEN_SUBREQUEST = 'X-Caliopen-SSR';\nconst CALIOPEN_SUBREQUEST_VALUE = 'SSR';\n\nexport const getSubRequestHeaders = () => ({\n  [CALIOPEN_SUBREQUEST]: CALIOPEN_SUBREQUEST_VALUE,\n});\n\nexport const isSubRequest = (req) =>\n  req.headers[CALIOPEN_SUBREQUEST] === CALIOPEN_SUBREQUEST_VALUE;\n"
  },
  {
    "path": "src/frontend/web_application/server/app.ts",
    "content": "import express from 'express';\nimport applyAPI from './api';\nimport applyAssets from './assets';\nimport applyAuth from './auth';\nimport applyError from './error';\nimport { configure as applyExpressReact } from './express-react';\nimport applySSR from './ssr';\nimport { httpLog } from './logger';\n\nexport default () => {\n  const app = express();\n\n  app.use('*', httpLog);\n\n  //-------\n  // assets always public\n  applyAssets(app);\n  applyAuth(app);\n  applyAPI(app);\n  applyExpressReact(app);\n  applySSR(app);\n  applyError(app);\n\n  return app;\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/assets/index.js",
    "content": "import express from 'express';\nimport favicon from 'serve-favicon';\n\nconst PUBLIC_PATHS = {\n  '/': ['dist/server/public/', 'public/'],\n};\nconst FAVICON_PATH = 'public/favicon.ico';\n\nexport default (app) => {\n  app.use(favicon(FAVICON_PATH));\n  Object.keys(PUBLIC_PATHS).forEach((target) => {\n    PUBLIC_PATHS[target].forEach((path) => {\n      app.use(target, express.static(path));\n    });\n  });\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/auth/index.js",
    "content": "import bodyParser from 'body-parser';\nimport cookieParser from 'cookie-parser';\nimport getRouter from './router';\nimport { decodeCookieMiddleware } from './middlewares';\nimport { getConfig } from '../config';\n\nexport default (app) => {\n  const {\n    cookie: { secret },\n  } = getConfig();\n  app.use(cookieParser(secret));\n  app.use('/auth', bodyParser.json());\n  app.use('/auth', bodyParser.urlencoded({ extended: false }));\n  app.use('/auth', getRouter());\n\n  app.use(decodeCookieMiddleware);\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/auth/lib/cookie.ts",
    "content": "import type { Response } from 'express';\n\nimport { encode } from './seal';\nimport { getConfig } from '../../config';\n\nexport const COOKIE_NAME = 'caliopen.web';\nexport const COOKIE_OPTIONS = {\n  signed: true,\n  domain: undefined,\n  path: '/',\n};\n\nconst getCookieOptions = () => {\n  const { hostname } = getConfig();\n\n  return { ...COOKIE_OPTIONS, domain: hostname };\n};\n\nexport const authenticate = (res: Response, { user }) => {\n  const {\n    seal: { secret },\n  } = getConfig();\n\n  return encode(user, secret).then(\n    (sealed) => {\n      res.cookie(COOKIE_NAME, sealed, getCookieOptions());\n    },\n    () => {\n      return Promise.reject('Unexpected Error');\n    }\n  );\n};\n\nexport const invalidate = (res: Response) =>\n  res.clearCookie(COOKIE_NAME, getCookieOptions());\n"
  },
  {
    "path": "src/frontend/web_application/server/auth/lib/redirect.ts",
    "content": "export const DEFAULT_REDIRECT = '/';\n"
  },
  {
    "path": "src/frontend/web_application/server/auth/lib/seal.ts",
    "content": "import Iron from 'iron';\n\nexport function encode(obj: object, secret: string) {\n  return Iron.seal(obj, secret, Iron.defaults);\n}\n\nexport function decode(sealed: string, secret: string) {\n  return Iron.unseal(sealed, secret, Iron.defaults);\n}\n"
  },
  {
    "path": "src/frontend/web_application/server/auth/middlewares/decode-cookie.ts",
    "content": "import { decode } from '../lib/seal';\nimport { COOKIE_NAME } from '../lib/cookie';\nimport { getConfig } from '../../config';\n\nclass ResponseError extends Error {\n  status: number;\n  err: any;\n}\n\nexport const decodeCookieMiddleware = (req, res, next) => {\n  const {\n    seal: { secret },\n  } = getConfig();\n  const cookie = req.signedCookies && req.signedCookies[COOKIE_NAME];\n\n  if (!cookie) {\n    next();\n\n    return;\n  }\n\n  decode(cookie, secret).then(\n    (obj) => {\n      req.user = obj;\n\n      // TODO: refresh token\n      // new Date(Date.UTC(req.user.tokens.expires_at)).getTime() < new Date().getTime() + (1000 * 60 * 10)\n\n      next();\n    },\n    (err) => {\n      const error = new ResponseError(\n        'Unexpected Server Error on cookie decoding'\n      );\n      error.status = 500;\n      error.err = err;\n\n      next(error);\n    }\n  );\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/auth/middlewares/index.ts",
    "content": "export * from './decode-cookie';\n"
  },
  {
    "path": "src/frontend/web_application/server/auth/router/index.js",
    "content": "import { Router as createRouter } from 'express';\nimport createSigninRouting from './signin';\nimport createSignupRouting from './signup';\n\nconst getRouter = () => {\n  const router = createRouter();\n  createSigninRouting(router);\n  createSignupRouting(router);\n\n  return router;\n};\n\nexport default getRouter;\n"
  },
  {
    "path": "src/frontend/web_application/server/auth/router/signin.js",
    "content": "import proxy from 'express-http-proxy';\nimport { getApiHost } from '../../config';\n\nconst { authenticate, invalidate } = require('../lib/cookie');\n\nconst createLoginRouting = (router) => {\n  const target = getApiHost();\n\n  router.post(\n    '/signin',\n    proxy(target, {\n      proxyReqPathResolver: () => '/api/v1/authentications',\n      userResDecorator: async (proxyRes, proxyResData, userReq, userRes) => {\n        if (proxyRes.statusCode >= 200 && proxyRes.statusCode < 400) {\n          const { device, ...user } = JSON.parse(proxyResData.toString('utf8'));\n\n          // according to the doc, cookie manipulation.should not be done in userResDecorator but\n          // possible since userRes is pass as a reference\n          await authenticate(userRes, { user });\n\n          return JSON.stringify({ device });\n        }\n\n        return proxyResData;\n      },\n    })\n  );\n\n  router.get('/signout', (req, res) => {\n    invalidate(res);\n    const redirect = (() => {\n      const parts = req.originalUrl.split('redirect=');\n\n      return parts.length > 1 ? `?redirect=${parts[parts.length - 1]}` : '';\n    })();\n    // XXX: render a temporary confirmation on next rendering\n    res.redirect(`/auth/signin${redirect}`);\n  });\n};\n\nexport default createLoginRouting;\n"
  },
  {
    "path": "src/frontend/web_application/server/auth/router/signup.ts",
    "content": "import type { Router } from 'express';\nimport { query } from '../../api/lib/api';\nimport { authenticate } from '../lib/cookie';\nimport { ServerError } from '../../error/consts';\n\nconst CONTEXT_SAFE = 'safe';\n\nconst createSignupRouting = (router: Router) => {\n  router.post('/signup', async (req, res, next) => {\n    try {\n      await query(\n        { path: '/api/v1/users', method: 'post', headers: req.headers },\n        { body: req.body }\n      );\n    } catch (err) {\n      const error = new ServerError(`Unable to signup: ${err.message}`);\n      error.status = 400;\n\n      return next(error);\n    }\n\n    const { username, password, device } = req.body;\n\n    try {\n      const user = await query(\n        {\n          path: '/api/v1/authentications',\n          method: 'post',\n        },\n        {\n          body: { username, password, device, context: CONTEXT_SAFE },\n        }\n      );\n\n      await authenticate(res, { user });\n\n      return res.status(204).send();\n    } catch (err) {\n      const error = new ServerError(\n        `Unable to automatically authenticate after signup: ${err.message}`\n      );\n      return next(error);\n    }\n  });\n};\n\nexport default createSignupRouting;\n"
  },
  {
    "path": "src/frontend/web_application/server/config/index.js",
    "content": "import merge from 'lodash/merge';\nimport defaults from '../../config/server.default';\nimport envVars from '../../config/server.env-var';\n\nlet config = merge(defaults, envVars);\n\nexport const initConfig = (forceDefaults) => {\n  config = merge(defaults, forceDefaults, envVars);\n};\n\nexport const getConfig = () => config;\n\nexport const getApiHost = () => {\n  const {\n    api: { hostname, port, protocol },\n  } = getConfig();\n\n  return `${protocol}://${hostname}:${port}`;\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/error/components/Error/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport AuthPage from '../../../../src/layouts/AuthPage';\n\nconst Error = ({ error }) => (\n  <AuthPage>\n    <center>\n      <h2>\n        Error {error.status}: {error.message}{' '}\n      </h2>\n    </center>\n\n    {error.stack && <pre>{error.stack}</pre>}\n  </AuthPage>\n);\n\nError.propTypes = {\n  error: PropTypes.shape({}),\n};\n\nexport default Error;\n"
  },
  {
    "path": "src/frontend/web_application/server/error/consts.ts",
    "content": "export class ServerError extends Error {\n  status = 500;\n}\n"
  },
  {
    "path": "src/frontend/web_application/server/error/index.js",
    "content": "import catchError from './middlewares/catch-error';\n\nexport default (app) => {\n  app.use(catchError);\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/error/middlewares/catch-error.js",
    "content": "import { getLogger } from '../../logger';\n\nconst logger = getLogger();\n\nconst isDev = process.env.NODE_ENV === 'development';\n\nconst catchError = (err, req, res, next) => {\n  if (!err.status || err.status >= 500) {\n    logger.error(err.message);\n  } else {\n    logger.info(err.message);\n  }\n\n  const publicError = {\n    status: err.status || 500,\n    message: err.message || err,\n    error: isDev ? err : {},\n    stack: isDev ? err.stack : '',\n  };\n\n  if (req.accepts('json')) {\n    res.status(publicError.status).json(publicError);\n\n    return;\n  }\n\n  if (req.accepts('html')) {\n    res\n      .status(publicError.status)\n      .render('error.component', { error: publicError });\n\n    return;\n  }\n\n  next(err);\n};\n\nexport default catchError;\n"
  },
  {
    "path": "src/frontend/web_application/server/express-react/create-engine.js",
    "content": "import React from 'react';\nimport ReactDOMServer from 'react-dom/server';\nimport template from '../../dist/server/template.html';\n\nconst renderTemplate = (markup) =>\n  [{ key: '%MARKUP%', value: markup }].reduce(\n    (str, current) => str.replace(current.key, current.value),\n    template\n  );\n\n/**\n * This engine uses react components, those are pre-compiled and given in the route argument e.g:\n *\n * `createEngine({ 'login.component': require('./Login').default })`\n *\n * the components are in-memory, a view path is not required. The express render method will work as\n * usually: `app.render('login.component', {})`\n */\nconst createEngine = (routes = {}) => {\n  const renderview = (viewName, options, cb) => {\n    // XXX: cb is useless : not async, try catch also\n    let markup;\n    try {\n      markup = ReactDOMServer.renderToStaticMarkup(\n        React.createElement(routes[viewName], options)\n      );\n    } catch (e) {\n      return cb(e);\n    }\n\n    return cb(null, renderTemplate(markup));\n  };\n\n  return renderview;\n};\n\nexport default createEngine;\n"
  },
  {
    "path": "src/frontend/web_application/server/express-react/index.js",
    "content": "import ErrorComponent from '../error/components/Error';\nimport createEngine from './create-engine';\nimport View from './view';\n\nexport const configure = (app) => {\n  app.set('view', View);\n  app.set('view engine', 'component');\n  app.engine(\n    'component',\n    createEngine({\n      'error.component': ErrorComponent,\n    })\n  );\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/express-react/view.js",
    "content": "class View {\n  constructor(name, opts = {}) {\n    this.defaultEngine = opts.defaultEngine;\n    this.name = name;\n    this.ext =\n      this.defaultEngine[0] !== '.'\n        ? `.${this.defaultEngine}`\n        : this.defaultEngine;\n\n    this.engine = opts.engines[this.ext];\n    // there is no path because components are in memory\n    // this prevent express render to throw an error\n    this.path = 'in-memory';\n  }\n\n  render(options, callback) {\n    this.engine(this.name, options, callback);\n  }\n}\n\nexport default View;\n"
  },
  {
    "path": "src/frontend/web_application/server/index.js",
    "content": "import path from 'path';\nimport fs from 'fs';\nimport argv from 'argv';\nimport http from 'http';\nimport getApp from './app';\nimport { getConfig, initConfig } from './config';\nimport { version } from '../package.json';\nimport { getLogger } from './logger';\n\nconst logger = getLogger();\n\nconst {\n  options: { config: configPath },\n} = argv\n  .option([\n    {\n      name: 'config',\n      type: 'path',\n      description:\n        'the file to set Caliopen options. See config/server.default.js for default options. This can be a regular js or a json file, cf. README.md.',\n      example: 'server --config=~/caliopen.webserver.json',\n    },\n  ])\n  .version(version)\n  .info('Run Caliopen web server')\n  .run();\n\nif (configPath) {\n  // read user's config file outside of webpack\n  const userOpts = JSON.parse(fs.readFileSync(path.join(configPath), 'utf8'));\n\n  initConfig(userOpts);\n}\n\nconst server = http.createServer(getApp());\nconst {\n  webServer: { port, hostname },\n} = getConfig();\nconst params = [\n  port,\n  ...(hostname ? [hostname] : []),\n  () => {\n    const { address, port: runningPort } = server.address();\n    logger.info(`Server started: http://${address}:${runningPort}/`);\n  },\n];\n\nserver.listen(...params);\n"
  },
  {
    "path": "src/frontend/web_application/server/logger/getLogger.js",
    "content": "import winston from 'winston';\nimport { Syslog } from 'winston-syslog';\nimport { getConfig } from '../config';\n\nconst LOGGER_CATEGORY = 'caliopen-frontend';\nconst stdFormat = winston.format.combine(\n  winston.format.colorize(),\n  winston.format.timestamp(),\n  winston.format.align(),\n  winston.format.label({ label: LOGGER_CATEGORY }),\n  winston.format.printf(\n    (info) => `${info.timestamp} ${info.level} ${info.label} - ${info.message}`\n  )\n);\n\nlet logger;\n\nexport const getLogger = () => {\n  if (!logger) {\n    const { enableSyslog } = getConfig();\n    logger = winston.createLogger({\n      level: process.env.NODE_ENV === 'development' ? 'info' : 'debug',\n      format: winston.format.simple(),\n      transports: [\n        new winston.transports.Console({\n          format: stdFormat,\n        }),\n        ...(enableSyslog\n          ? [\n              new Syslog({\n                app_name: LOGGER_CATEGORY,\n                facility: 'user',\n                protocol: 'unix',\n                path: '/dev/log',\n              }),\n            ]\n          : []),\n      ],\n      levels: winston.config.syslog.levels,\n    });\n  }\n\n  return logger;\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/logger/index.js",
    "content": "export * from './middlewares/httpLog';\nexport * from './getLogger';\n"
  },
  {
    "path": "src/frontend/web_application/server/logger/middlewares/httpLog.js",
    "content": "import { getLogger } from '../getLogger';\nimport { getConfig } from '../../config';\n\nconst logger = getLogger();\n// real used port is in listener var wich is not available in req\nconst {\n  webServer: { port },\n} = getConfig();\nconst portStr = [80, 443].includes(port) ? '' : `:${port}`;\n\nexport const httpLog = (req, res, next) => {\n  const { method, protocol, hostname, originalUrl } = req;\n\n  res.on('finish', () => {\n    const status = res.headersSent ? res.statusCode : '';\n    logger.debug(\n      `req: ${method} - ${protocol}://${hostname}${portStr}${originalUrl} - ${status}`\n    );\n  });\n  next();\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/ssr/components/Bootstrap.tsx",
    "content": "import * as React from 'react';\nimport { StaticRouter, StaticRouterProps } from 'react-router-dom';\nimport App, { AppProps } from 'src/App';\n\ninterface BootstrapProps {\n  store: AppProps['store'];\n  location: StaticRouterProps['location'];\n  context: StaticRouterProps['context'];\n}\n\nconst Bootstrap = ({ store, location, context }: BootstrapProps) => (\n  <StaticRouter location={location} context={context}>\n    <App store={store} />\n  </StaticRouter>\n);\n\nexport default Bootstrap;\n"
  },
  {
    "path": "src/frontend/web_application/server/ssr/index.js",
    "content": "import cookieParser from 'cookie-parser';\nimport ssrMiddleware from './ssr-middleware';\n\nexport default (app) => {\n  app.use(cookieParser());\n  app.get('*', ssrMiddleware);\n};\n"
  },
  {
    "path": "src/frontend/web_application/server/ssr/ssr-middleware.ts",
    "content": "import * as React from 'react';\nimport { renderToString } from 'react-dom/server';\nimport DocumentTitle from 'react-document-title';\nimport serialize from 'serialize-javascript';\nimport locale from 'locale';\n\nimport { RootState } from 'src/store/reducer';\nimport Bootstrap from './components/Bootstrap';\nimport configureStore from '../../src/store/configure-store';\nimport { getUserLocales } from '../../src/modules/i18n';\nimport { getDefaultSettings } from '../../src/modules/settings';\nimport { initConfig } from '../../src/services/config';\n// @ts-ignore\nimport template from '../../dist/server/template.html';\nimport { getConfig } from '../config';\nimport { initialState as initialStateSettings } from '../../src/store/modules/settings';\nimport { getLogger } from '../logger';\n\ndeclare global {\n  // eslint-disable-next-line @typescript-eslint/no-namespace\n  namespace NodeJS {\n    interface Global {\n      user: any;\n      USER_LOCALES: string[];\n    }\n  }\n}\n\nconst logger = getLogger();\n/**\n * base html template\n */\nfunction getMarkup({ store, context, location }) {\n  try {\n    const { protocol, hostname, port, maxBodySize } = getConfig();\n    const config = { protocol, hostname, port };\n    initConfig(config);\n    const hasSSR =\n      process.env.HAS_SSR === undefined || process.env.HAS_SSR === 'true';\n    const markup = !hasSSR\n      ? ''\n      : renderToString(\n          React.createElement(Bootstrap, {\n            context,\n            location,\n            store,\n          })\n        );\n    const documentTitle = DocumentTitle.rewind();\n    const initialState = store.getState();\n\n    return [\n      { key: '</title>', value: `${documentTitle}</title>` },\n      {\n        key: '</head>',\n        value: `<script>window.__STORE__ = ${serialize(\n          initialState\n        )};window.__INSTANCE_CONFIG__ = ${serialize({\n          hostname,\n          maxBodySize,\n        })}</script></head>`,\n      },\n      { key: '%MARKUP%', value: markup },\n    ].reduce(\n      (str, current) => str.replace(current.key, current.value),\n      template\n    );\n  } catch (e) {\n    logger.error('Unable to render markup:', e);\n\n    throw e;\n  }\n}\n\nfunction applyUserToGlobal(req) {\n  global.user = req.user;\n}\n\nfunction applyUserLocaleToGlobal(req) {\n  const locales = new locale.Locales(req.headers['accept-language']).toJSON();\n\n  global.USER_LOCALES = locales.map((loc) => loc.code);\n}\n\nexport default (req, res, next) => {\n  applyUserToGlobal(req);\n  applyUserLocaleToGlobal(req);\n  const initialState: Partial<RootState> = {\n    settings: {\n      ...initialStateSettings,\n      settings: getDefaultSettings(getUserLocales()),\n    },\n  };\n\n  const store = configureStore(initialState);\n  const context: { url?: string; action?: string } = {};\n\n  const html = getMarkup({ store, location: req.url, context });\n\n  if (context.url) {\n    const status = context.action === 'PUSH' ? 302 : 301;\n    res.writeHead(status, {\n      Location: context.url,\n    });\n    res.end();\n  } else {\n    res.write(html);\n    res.end();\n  }\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/App.tsx",
    "content": "import * as React from 'react';\nimport { Provider, ProviderProps } from 'react-redux';\nimport { QueryClient, QueryClientProvider } from 'react-query';\nimport { ReactQueryDevtools } from 'react-query/devtools';\nimport { I18nLoader } from './modules/i18n';\nimport { WithSettings } from './modules/settings';\nimport { InstallPromptProvider } from './modules/pwa';\nimport { SwitchWithRoutes, RoutingConsumer } from './modules/routing';\nimport RoutingProvider from './modules/routing/components/RoutingProvider';\nimport { PageTitle } from './components';\nimport { NotificationProvider } from './modules/notification';\nimport ErrorBoundary from './layouts/ErrorBoundary';\nimport './app.scss';\n\nexport interface AppProps {\n  store: ProviderProps['store'];\n}\n\nexport default function App({\n  store,\n}: AppProps): React.ReactElement<typeof InstallPromptProvider> {\n  const queryClient = new QueryClient();\n\n  return (\n    <InstallPromptProvider>\n      <QueryClientProvider client={queryClient}>\n        <Provider store={store}>\n          <WithSettings\n            networkDisabled\n            render={(settings) => (\n              <I18nLoader locale={settings.default_locale}>\n                <ErrorBoundary>\n                  <RoutingProvider settings={settings}>\n                    <PageTitle />\n                    <RoutingConsumer\n                      render={({ routes }) => (\n                        <SwitchWithRoutes routes={routes} />\n                      )}\n                    />\n                    {/* @ts-ignore */}\n                    <NotificationProvider />\n                  </RoutingProvider>\n                </ErrorBoundary>\n              </I18nLoader>\n            )}\n          />\n        </Provider>\n        <ReactQueryDevtools />\n      </QueryClientProvider>\n    </InstallPromptProvider>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/app.scss",
    "content": "@import './styles/common';\n@import './styles/vendor/bootstrap_foundation-sites';\n\n// CSS Exports\n@include foundation-global-styles;\n\n// --- Reset zf-normalizer (after foundation-global-styles)\n// stylelint-disable-next-line no-invalid-position-at-import-rule\n@import 'styles/base/reset';\n\n// @include foundation-grid;\n// @include foundation-flex-grid;\n// @include foundation-typography;\n// @include foundation-forms;\n// @include foundation-button;\n// @include foundation-accordion;\n// @include foundation-accordion-menu;\n// @include foundation-badge;\n// @include foundation-breadcrumbs;\n// @include foundation-button-group;\n// @include foundation-callout;\n// @include foundation-close-button;\n@include foundation-menu;\n@include foundation-menu-icon;\n\n// @include foundation-drilldown-menu;\n// @include foundation-dropdown;\n// @include foundation-dropdown-menu;\n// @include foundation-flex-video;\n// @include foundation-label;\n// @include foundation-media-object;\n@include foundation-off-canvas;\n\n// @include foundation-orbit;\n// @include foundation-pagination;\n@include foundation-progress-bar;\n\n// @include foundation-slider;\n// @include foundation-sticky;\n// @include foundation-reveal;\n// @include foundation-switch;\n// @include foundation-table;\n// @include foundation-tabs;\n// @include foundation-thumbnail;\n// @include foundation-title-bar;\n// @include foundation-tooltip;\n// @include foundation-top-bar;\n@include foundation-visibility-classes;\n@include foundation-float-classes;\n\n// @include foundation-flex-classes;\n\nhtml {\n  @include breakpoint(large) {\n    font-size: 88%; // ~14px\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/ActionBar/components/ActionBarButton/index.tsx",
    "content": "import classnames from 'classnames';\nimport * as React from 'react';\nimport Button from '../../../Button';\n\ntype ButtonProps = React.ComponentProps<typeof Button>;\ninterface Props extends ButtonProps {\n  className?: string;\n  innerRef: React.ForwardedRef<HTMLButtonElement>;\n}\nfunction ActionBarButton({ className, innerRef, ...props }: Props) {\n  return (\n    <Button\n      ref={innerRef}\n      className={classnames('m-action-bar__action-btn', className)}\n      {...props}\n    />\n  );\n}\n\ntype ActionBarButtonProps = Omit<Props, 'innerRef'>;\nexport default React.forwardRef<HTMLButtonElement, ActionBarButtonProps>(\n  (props, ref) => <ActionBarButton innerRef={ref} {...props} />\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/components/ActionBar/components/ActionBarWrapper/index.tsx",
    "content": "import classnames from 'classnames';\nimport * as React from 'react';\nimport './styles.scss';\n\ninterface Props {\n  isSticky?: boolean;\n  children: React.ReactNode;\n}\n\nfunction ActionBarWrapper({ isSticky = false, ...props }: Props) {\n  return (\n    <div\n      className={classnames('m-action-bar-wrapper', {\n        'm-action-bar-wrapper--sticky': isSticky,\n      })}\n      {...props}\n    />\n  );\n}\n\nexport default ActionBarWrapper;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/ActionBar/components/ActionBarWrapper/styles.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../../styles/object/o-pi-border';\n\n$navbar-item-height: 2.75rem + (rem_calc($pi-border__size) * 2); // $m-navbar-item-height\n\n// XXX: may be move to a layout component (do not forget that it can be recursive)\n.m-action-bar-wrapper {\n  background-color: $co-color__fg__back;\n\n  &--sticky {\n    position: fixed;\n    top: $navbar-item-height;\n    right: 0;\n    left: 0;\n    z-index: $l-z-index__action-bar;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/ActionBar/index.tsx",
    "content": "import classnames from 'classnames';\nimport * as React from 'react';\nimport Spinner from '../Spinner';\nimport './style.scss';\n\ninterface Props {\n  children?: React.ReactNode;\n  className?: string;\n  isLoading?: boolean;\n  actionsNode?: React.ReactNode;\n  hr?: boolean;\n}\n\nfunction ActionBar({\n  children,\n  className,\n  isLoading = false,\n  actionsNode,\n  hr = true,\n}: Props): JSX.Element {\n  return (\n    <div\n      className={classnames(className, 'm-action-bar', {\n        'm-action-bar--hr': hr,\n      })}\n    >\n      <div\n        className={classnames('m-action-bar__loading', {\n          'm-action-bar__loading--is-loading': isLoading,\n        })}\n      >\n        <Spinner\n          svgTitleId=\"action-bar-spinner\"\n          isLoading={isLoading}\n          display=\"inline\"\n        />\n      </div>\n      {actionsNode && (\n        <div className=\"m-action-bar__actions\">{actionsNode}</div>\n      )}\n      {children}\n    </div>\n  );\n}\n\nexport default ActionBar;\nexport { default as ActionBarButton } from './components/ActionBarButton';\nexport { default as ActionBarWrapper } from './components/ActionBarWrapper';\n"
  },
  {
    "path": "src/frontend/web_application/src/components/ActionBar/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n@import '../../styles/object/o-pi-border';\n\n.m-action-bar {\n  position: relative;\n\n  // XXX: dirty fix: form elements go in front of action-bar when sticky since the position\n  // attribute is also set in @o-form-element-select-wrapper\n  z-index: $l-z-index__action-bar;\n  background: $co-color__fg__back;\n  line-height: $co-component__height + rem_calc($pi-border__size);\n\n  &--hr {\n    border-bottom: 3px solid $co-color__sidebar__back;\n  }\n\n  &__loading {\n    position: absolute;\n\n    // XXX: see above\n    z-index: $l-z-index__action-bar;\n    width: 0;\n    transition: $co-animation__width-transition;\n    background: $co-color__fg__back;\n    overflow: hidden;\n\n    &--is-loading {\n      width: 100%;\n      padding-left: map_get($co-margin_, 'xsmall');\n    }\n  }\n\n  &__actions {\n    color: #1a5773; // XXX\n    font-weight: bold;\n  }\n\n  &__action-btn {\n    margin-left: map_get($co-margin_, 'xsmall');\n    color: #0e97c3; // XXX\n  }\n\n  @include breakpoint(medium) {\n    max-width: $co-site-width -\n      (map_get($co-page-container-paddings, 'medium') * 2);\n    margin-right: auto;\n    margin-left: auto;\n  }\n\n  @include breakpoint(xlarge) {\n    max-width: $co-site-width -\n      (map_get($co-page-container-paddings, 'xlarge') * 2);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/AdvancedSelectFieldGroup/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { act, render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport AdvancedSelectFieldGroup from '.';\n\nfunction Comp() {\n  const [val, setVal] = React.useState(undefined);\n  const props = {\n    selectId: 'test-select',\n    dropdownId: 'test-dropdown',\n    label: 'Foo',\n    options: [\n      { label: 'a opt', value: 1 },\n      { label: 'b opt', value: 3 },\n    ],\n    value: val,\n    onChange: (selected) => {\n      setVal(selected);\n    },\n    placeholder: 'Choose',\n  };\n\n  return <AdvancedSelectFieldGroup {...props} />;\n}\ndescribe('component AdvancedSelectFieldGroup', () => {\n  // XXX: dropdown event listener make it hard to test\n  it.skip('should render', async () => {\n    render(<Comp />);\n\n    expect(screen.getAllByRole('option').length).toEqual(2);\n    act(() => {\n      userEvent.click(screen.getByText('Choose'));\n    });\n\n    // click the option\n    userEvent.click(await screen.findByRole('button', { name: 'a opt' }));\n    // act(() => {\n    // });\n    act(() => {\n      // click the Control\n      userEvent.click(screen.getByRole('button', { name: 'a opt' }));\n    });\n    // click the option\n    userEvent.click(screen.getByRole('button', { name: 'b opt' }));\n    expect(screen.getByRole('listbox')).not.toBeVisible();\n    expect(screen.getByRole('listbox')).toBeInTheDocument();\n    // selected value\n    expect(screen.getByRole('button', { name: 'b opt' }));\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/AdvancedSelectFieldGroup/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport Label from '../Label';\nimport FieldGroup from '../FieldGroup';\nimport Dropdown, { withDropdownControl } from '../Dropdown';\nimport VerticalMenu, { VerticalMenuItem } from '../VerticalMenu';\nimport TextBlock from '../TextBlock';\nimport Button from '../Button';\n\nimport './style.scss';\n\nconst DropdownControl = withDropdownControl(\n  React.forwardRef<HTMLDivElement>((props, ref) => {\n    // mandatory for A11Y but handled by dropdown event handler\n    // FIXME: onKeyDown is actually not handled, but for screen-reader, dropdown is hidden and an actual native select is provided\n    const handleEv = () => {};\n    return (\n      // eslint-disable-next-line jsx-a11y/interactive-supports-focus\n      <div\n        role=\"button\"\n        onClick={handleEv}\n        onKeyDown={handleEv}\n        ref={ref}\n        {...props}\n      />\n    );\n  })\n);\n\ninterface Option {\n  advancedlabel?: React.ReactNode;\n  label: string | number;\n  value: string | number;\n}\n\ninterface Props {\n  selectId: string;\n  dropdownId: string;\n  label?: React.ReactNode;\n  showLabelforSr?: boolean;\n  value: undefined | string | number;\n  expanded?: boolean;\n  options: Option[];\n  errors?: React.ReactNode[];\n  onChange: (value: string | number) => void;\n  className?: string;\n  placeholder?: React.ReactNode;\n  inline?: boolean;\n}\n\nfunction AdvancedSelectFieldGroup({\n  selectId,\n  dropdownId,\n  label,\n  showLabelforSr = false,\n  value,\n  expanded = false,\n  options = [],\n  errors,\n  onChange,\n  className,\n  placeholder = null,\n  inline = false,\n}: Props) {\n  const dropdownControlRef = React.useRef<HTMLDivElement>(null);\n\n  const createHandleClick = (nextValue) => () => {\n    onChange(nextValue);\n  };\n\n  const handleRawChange: React.ChangeEventHandler<HTMLSelectElement> = (ev) => {\n    onChange(ev.currentTarget.value);\n  };\n\n  const renderSelected = ({\n    advancedlabel,\n    label: selectedLabel,\n  }: Partial<Option> = {}) => {\n    const text = advancedlabel || selectedLabel || placeholder;\n    if (inline) {\n      return text;\n    }\n\n    return <TextBlock>{text}</TextBlock>;\n  };\n\n  const selectedOpt = options.find((opt) => opt.value === value);\n  const selectClassName = classnames(\n    className,\n    'm-advanced-select-field-group',\n    { 'm-advanced-select-field-group--inline': inline }\n  );\n  const selectWrapperClassName = classnames(\n    'm-advanced-select-field-group__select-wrapper',\n    {\n      'm-advanced-select-field-group--expanded__select-wrapper': expanded,\n      'm-advanced-select-field-group--inline__select-wrapper': inline,\n    }\n  );\n  const labelClassName = classnames('m-advanced-select-field-group__label', {\n    'm-advanced-select-field-group--inline__label': inline,\n    'show-for-sr': showLabelforSr,\n  });\n  const inputClassName = classnames('m-advanced-select-field-group__input', {\n    'm-advanced-select-field-group--inline__input': inline,\n    'm-advanced-select-field-group__input--has-placeholder': !!selectedOpt,\n  });\n  const errorsClassname = classnames('m-advanced-select-field-group__errors', {\n    'm-advanced-select-field-group--inline__errors': inline,\n  });\n\n  return (\n    <FieldGroup\n      className={selectClassName}\n      errorsClassname={errorsClassname}\n      errors={errors}\n    >\n      <Label htmlFor={selectId} className={labelClassName}>\n        {label}\n      </Label>\n      <div className={selectWrapperClassName} aria-hidden=\"true\">\n        <DropdownControl className={inputClassName} ref={dropdownControlRef}>\n          {renderSelected(selectedOpt)}\n        </DropdownControl>\n      </div>\n      <Dropdown\n        id={dropdownId}\n        className=\"hide-for-sr\" // is it working?\n        dropdownControlRef={dropdownControlRef}\n        isMenu\n        closeOnClick=\"all\"\n      >\n        <VerticalMenu>\n          {options.map((option) => (\n            <VerticalMenuItem key={option.value}>\n              <Button\n                onClick={createHandleClick(option.value)}\n                display=\"expanded\"\n                className=\"m-advanced-select-field-group__option-button\"\n              >\n                <TextBlock>{option.advancedlabel || option.label}</TextBlock>\n              </Button>\n            </VerticalMenuItem>\n          ))}\n        </VerticalMenu>\n      </Dropdown>\n      <select\n        onChange={handleRawChange}\n        className=\"show-for-sr\"\n        id={selectId}\n        value={value}\n      >\n        {options.map((selectOption) => (\n          <option key={selectOption.label} value={selectOption.value}>\n            {selectOption.label}\n          </option>\n        ))}\n      </select>\n    </FieldGroup>\n  );\n}\n\nexport default AdvancedSelectFieldGroup;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/AdvancedSelectFieldGroup/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-form-element';\n\n.m-advanced-select-field-group {\n  margin-bottom: $co-component__spacing;\n\n  &__label {\n    display: block;\n  }\n\n  &__input {\n    @include o-form-element;\n\n    min-width: map_get($co-margin_, 'xlarge');\n\n    &--decorated {\n      @include o-form-element--light;\n    }\n  }\n\n  &__select-wrapper {\n    @include o-form-element-select-wrapper;\n  }\n\n  &__option-button {\n    height: $co-component__height;\n    text-align: left;\n  }\n\n  &--dark {\n    &__select-wrapper,\n    &__select {\n      @include o-form-element--dark;\n    }\n  }\n\n  &--inline {\n    @include o-form-element--inline;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/AppLoader/index.tsx",
    "content": "import { Trans } from '@lingui/react';\nimport classnames from 'classnames';\nimport * as React from 'react';\nimport Brand from '../Brand';\nimport Link from '../Link';\nimport Spinner from '../Spinner';\nimport './style.scss';\n\nfunction Activity() {\n  const activities = [\n    <Trans\n      id=\"app-loader.activity.macaroons\"\n      message=\"Baking macaroons, wait a moment please\"\n    />,\n    <Trans\n      id=\"app-loader.activity.cats\"\n      message=\"Feeding the cats, wait a moment please\"\n    />,\n    <Trans\n      id=\"app-loader.activity.chicken\"\n      message=\"Telling the chicken to get out from the kitchen, wait a moment please\"\n    />,\n    <Trans\n      id=\"app-loader.activity.beckett\"\n      message=\"Waiting for Godot, he'll soon be there, trust me\"\n    />,\n    <Trans\n      id=\"app-loader.activity.newcat\"\n      message=\"Trying to tell if there is a new cat in house, wait a moment please\"\n    />,\n    <Trans\n      id=\"app-loader.activity.ipaddr\"\n      message=\"Forging new ip addresses, wait a moment please\"\n    />,\n    <Trans\n      id=\"app-loader.activity.oof\"\n      message=\"Configuring OpenOffice firewall, wait a moment please\"\n    />,\n    <Trans\n      id=\"app-loader.activity.trolls\"\n      message=\"Correcting wrong people on the Internet, wait a moment please\"\n    />,\n  ];\n  const now = new Date();\n  const hour = now.getHours();\n  const nb = Math.ceil((hour + 1) / (24 / activities.length));\n\n  return activities[nb - 1];\n}\n\ninterface Props {\n  className?: string;\n  children: React.ReactNode;\n  isLoading?: boolean;\n  hasFailure?: boolean;\n  fallbackUrl?: string;\n}\n\nfunction AppLoader({\n  children,\n  className,\n  hasFailure = false,\n  isLoading = true,\n  fallbackUrl = '/',\n}: Props): React.ReactNode {\n  if (!hasFailure && !isLoading) {\n    return children;\n  }\n\n  return (\n    <div className={classnames(className, 'm-app-loader')}>\n      <div>\n        <div className=\"m-app-loader__header\">\n          <Brand className=\"m-app-loader__brand\" />\n        </div>\n        <div className=\"m-app-loader__content\">\n          {isLoading ? (\n            <>\n              <Spinner svgTitleId=\"app-spinner\" isLoading />\n              <Activity />\n            </>\n          ) : (\n            <>\n              <p>\n                <Trans\n                  id=\"app-loader.feedback.failure\"\n                  message=\"Something went wrong. Are you offline ?\"\n                />\n              </p>\n              <p>\n                <Link href={fallbackUrl}>\n                  <Trans\n                    id=\"app-loader.action.retry\"\n                    message=\"Please click here to retry\"\n                  />\n                </Link>\n              </p>\n            </>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default AppLoader;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/AppLoader/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$m-app-loader__brand-height: 42px !default;\n$m-app-loader__header-space: (\n  bottom: 36px,\n);\n\n.m-app-loader {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 100vh;\n\n  &__brand {\n    height: $m-app-loader__brand-height;\n  }\n\n  &__header {\n    padding-bottom: map_get($m-app-loader__header-space, bottom);\n    text-align: center;\n  }\n\n  &__content {\n    border-top: 8px solid $co-color__primary;\n    text-align: center;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Badge/index.spec.tsx",
    "content": "import { render, screen } from '@testing-library/react';\nimport * as React from 'react';\nimport Badge from '.';\n\ndescribe('component Badge', () => {\n  it('render', () => {\n    render(<Badge>Foo</Badge>);\n\n    expect(screen.getByText('Foo')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Badge/index.tsx",
    "content": "import classnames from 'classnames';\nimport * as React from 'react';\nimport { Link as BaseLink } from 'react-router-dom';\nimport Button from '../Button';\nimport Spinner from '../Spinner';\nimport './style.scss';\n\ninterface Props {\n  children: React.ReactNode;\n  className?: string;\n  large?: boolean;\n  low?: boolean;\n  radiusType?: 'no' | 'normal' | 'rounded';\n  color?: 'success' | 'alert' | 'secondary' | 'active';\n  onDelete?: () => void; // If onDelete is set, the delete button will be shown\n  ariaLabel?: string; // option to show aria-label on delete button\n  isLoading?: boolean; // option to show spinner on delete button\n  rightSpaced?: boolean;\n  to?: string;\n}\nfunction Badge({\n  children,\n  className,\n  large = false,\n  low = false,\n  radiusType = 'normal',\n  color,\n  onDelete,\n  ariaLabel,\n  isLoading = false,\n  rightSpaced = false,\n  to,\n}: Props): JSX.Element {\n  const textClassName = classnames('m-badge__text', {\n    'm-badge__text--large': large,\n    'm-badge__text--has-button': onDelete,\n  });\n\n  const badgeProps = {\n    className: classnames(\n      'm-badge',\n      {\n        'm-badge--low': low,\n        'm-badge--large': large,\n        'm-badge--no-radius': radiusType === 'no',\n        'm-badge--normal-radius': radiusType === 'normal',\n        'm-badge--rounded-radius': radiusType === 'rounded',\n        'm-badge--right-spaced': rightSpaced,\n        'm-badge--active': color === 'active',\n        'm-badge--alert': color === 'alert',\n        'm-badge--secondary': color === 'secondary',\n        'm-badge--success': color === 'success',\n        'm-badge--is-link': to !== undefined,\n      },\n      className\n    ),\n  };\n\n  const buttonClassName = classnames('m-badge__button', {\n    'm-badge__button--large': large,\n  });\n\n  return (\n    <span {...badgeProps}>\n      {children &&\n        (to ? (\n          <BaseLink to={to} className={textClassName}>\n            {children}\n          </BaseLink>\n        ) : (\n          <span className={textClassName}>{children}</span>\n        ))}\n      {onDelete && (\n        <Button\n          className={buttonClassName}\n          display=\"inline\"\n          onClick={onDelete}\n          icon={\n            isLoading ? (\n              <Spinner svgTitleId=\"badge-spinner\" isLoading display=\"inline\" />\n            ) : (\n              'remove'\n            )\n          }\n          aria-label={ariaLabel}\n        />\n      )}\n    </span>\n  );\n}\n\nexport default Badge;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Badge/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$m-badge__height: 1.6rem !default;\n$m-badge__font-size: ($m-badge__height / 2) !default;\n$m-badge__padding: ($m-badge__height - $m-badge__font-size) / 2;\n$m-badge--large__height: $co-component__height !default;\n$m-badge--large__font-size: $co-font__size !default;\n$m-badge--large__padding: ($m-badge--large__height - $m-badge--large__font-size) /\n  2;\n\n.m-badge {\n  $baseName: &;\n\n  display: inline-flex;\n  align-items: center;\n  height: $m-badge__height;\n  background: $co-color__primary--low;\n  color: $co-color__contrast__text;\n  font-size: $m-badge__font-size;\n  font-weight: 600;\n  line-height: $m-badge__height;\n  white-space: nowrap;\n\n  &--right-spaced {\n    margin-right: calc($co-margin / 4);\n  }\n\n  &--is-link {\n    #{$baseName}__text {\n      color: $co-color__contrast__text;\n      text-decoration: none;\n    }\n  }\n\n  &__text {\n    @include flex-grid-size;\n\n    padding: $m-badge__padding;\n\n    &--large {\n      padding: $m-badge--large__padding;\n    }\n\n    &--has-button {\n      padding-right: 0;\n    }\n  }\n\n  &__button {\n    @include flex-grid-size(shrink);\n\n    padding: $m-badge__padding;\n    color: $co-color__contrast__text;\n    font-size: $co-font__size;\n\n    &--large {\n      padding: $m-badge--large__padding;\n    }\n\n    &:hover {\n      color: $co-color__primary;\n    }\n  }\n\n  &--no-radius {\n    border-radius: 0;\n  }\n\n  &--normal-radius {\n    border-radius: $co-radius;\n  }\n\n  &--rounded-radius {\n    padding-right: $m-badge__height / 2;\n    padding-left: $m-badge__height / 2;\n    border-radius: ($m-badge__height / 2);\n  }\n\n  &--low {\n    background: $co-color__primary--lower;\n    color: $co-color__contrast__text--higher;\n  }\n\n  &--large {\n    height: $m-badge--large__height;\n    font-size: $m-badge--large__font-size;\n    line-height: $m-badge--large__height;\n  }\n\n  @each $name, $color in $caliopen-palette {\n    &--#{$name} {\n      background-color: $color;\n      color: $co-color__contrast__text;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/BlockList/index.spec.tsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport BlockList from '.';\n\ndescribe('component BlockList', () => {\n  it('render', () => {\n    const comp = shallow(<BlockList>{['Foo', 'Bar']}</BlockList>);\n\n    expect(comp.find('li').length).toEqual(2);\n    expect(comp.find('li').first().text()).toEqual('Foo');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/BlockList/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport './style.scss';\n\ninterface Props {\n  className?: string;\n  children: React.ReactNode[];\n}\nfunction BlockList({ className, children, ...props }: Props) {\n  return (\n    <ul className={classnames('m-block-list', className)} {...props}>\n      {children.map((comp, key) => (\n        <li className=\"m-block-list__item\" key={key}>\n          {comp}\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nexport default BlockList;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/BlockList/style.scss",
    "content": "@import '../../styles/common';\n\n.m-block-list {\n  list-style-type: none;\n  margin: 0;\n  padding: 0;\n\n  &__item {\n    border-top: 1px solid $co-color__fg__border--higher;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Brand/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport './style.scss';\n\ninterface Props {\n  className?: string;\n  theme?: 'low';\n  responsive?: boolean;\n}\nfunction Brand({ className, theme, responsive = false }: Props) {\n  const brandClassName = classnames(\n    'm-brand',\n    {\n      'm-brand--low': theme === 'low',\n      'm-brand--responsive': responsive,\n    },\n    className\n  );\n\n  return (\n    <div className={brandClassName}>\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 350 250\"\n        className=\"m-brand__logo\"\n        aria-label=\"Logo Caliopen\"\n      >\n        <path d=\"M231.7 2c-43.9 0-81.5 26.4-98 64.1A84.1 84.1 0 0 0 97 57.8a85.4 85.4 0 0 0-85.4 85.4 84.4 84.4 0 0 0 17.7 52l2.3 46 43.2-15.5a84.9 84.9 0 0 0 22.3 3 85.3 85.3 0 0 0 69.4-35.7 106 106 0 0 0 65.2 22.3c10.3 0 20.3-1.5 29.7-4.3l53.6 18.4 3.2-58.4a106 106 0 0 0 20.2-62.5A106.8 106.8 0 0 0 231.7 2zm-.3 24a82.1 82.1 0 0 1 82.1 82.1 83.2 83.2 0 0 1-17 49.8l-.7 40.7-38.2-12.7a83 83 0 0 1-26.4 4.3 82 82 0 0 1-72.2-43 62 62 0 0 1-62 58c-6.8 0-13.3-1.1-19.5-3.1L51 211.9l-1.5-29A61.8 61.8 0 0 1 35.1 143a62.1 62.1 0 0 1 62-62 62 62 0 0 1 52.1 28.2v-1.2A82.1 82.1 0 0 1 231.4 26zm-1 24.5A57.4 57.4 0 0 0 173 108a57.4 57.4 0 0 0 57.5 57.5c6.5 0 12.6-1 18.4-3l26.6 8.8-2.7-24.3a57.2 57.2 0 0 0 15.2-39 57.4 57.4 0 0 0-57.5-57.5zM95.7 104a38.8 38.8 0 0 0-28.9 64.7l-.7 18 16.1-7.6a39.5 39.5 0 0 0 13.6 2.5 38.8 38.8 0 1 0 0-77.6z\" />\n      </svg>\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 925 250\"\n        className=\"m-brand__brand-name\"\n        aria-label=\"Caliopen\"\n      >\n        <path d=\"M251 100.2a43 43 0 0 0-3.6-7.2 59 59 0 0 0-48.6-22.2h-5.4c-7 0-14.7 1.6-22.9 4.8-22.7 12-34 30-34 54.2 0 5.9 1.2 12.6 3.4 20.3 10.5 25.8 28.9 38.7 55 38.7h3.8a67.6 67.6 0 0 0 31-7.7c1 3.9 4.6 6.6 10.3 7.8h2c6.4 0 10.1-4.3 11-13V107c-.2-3-1-5.1-2-6.9zM198.4 165h-3.8c-4.8 0-9-.8-12.7-2.4-14.3-6.4-21.5-17.2-21.5-32.4l1-9.7c5.5-17.4 16.5-26 33-26h4c13 0 23 4.1 30 12.5l1 5v35a32.7 32.6 0 0 0-1 4.9c-6.3 8.8-16.3 13-30 13zm90-165h2c7.1 1.4 10.7 5 10.7 10.6v135.5c0 10 9.4 16.3 28.2 18.9 9.2 0 14.5 2.6 16 7.7l.6 2.9v2.9c0 5.1-3.6 8.5-10.7 10.1h-4.3a61 61 0 0 1-46.8-20.8 38.8 38.8 0 0 1-6.8-24.7V15.5c0-8 2.4-12.8 7.3-14.5zm83.8 0h2c6.2 0 10 4 11 12-1.5 7.8-5 11.6-10.6 11.6h-2.4c-7.1-1.5-10.7-5-10.7-10.6v-2.4C363 3.6 366.5 0 372.1 0zm0 70.7h2.9c5.2 0 8.5 4 10.2 12v92.9c-1 8.7-4.7 13-11.2 13h-1.9c-7.1-1.5-10.7-5-10.7-10.6V81.3c1.5-7 5.1-10.6 10.7-10.6zm86.3 0h4.4c19 0 35 10 47.7 30a54.5 54.5 0 0 1 6.4 24.6v8.7c0 19.3-9.8 35.3-29.3 48-8.9 4.4-17.5 6.7-25.7 6.7h-3.4c-20.8 0-37.3-10.8-49.6-32.3a62 62 0 0 1-5.4-26.2v-1c0-27.7 13.8-46.6 41.4-56.5a48 48 0 0 1 13.5-2zm-31.1 57v2.9c0 17.7 9 29 26.7 33.9l4.4.5h3.4c13.7 0 23.8-8 30.2-23.8l1-7.2v-8.8c0-13.7-8-23.7-24-30l-6.3-.9h-4.4c-13.2 0-23 7.6-29.7 22.7-.8 4.1-1.3 7.7-1.3 10.7zm163.7-58c28.1.9 46.9 13.3 56.4 37.3a48.9 48.9 0 0 1 3.8 18.3v6.8c0 20.6-10.2 37.2-30.6 49.8a67.2 67 0 0 1-28.3 6.3h-33.5V239c-1.6 7.4-5.1 11.1-10.7 11.1h-2.4c-5.5 0-9-3.7-10.7-11.1V111.7c0-21.3 15.1-35 45.3-41zm-32.1 40.6v54.2h34c16.7 0 27.7-8 33-24.2 1-2.6 1.5-5.4 1.5-8.3v-2.4l.5-.4-.5-.5v-1.4c0-16.6-8.7-27.5-26.2-33l-9.3-1c-15.3 0-26.1 4.7-32.6 14zM723 70.7h5.4c23.7 0 41 9.3 52.1 28a58.6 58.4 0 0 1 2.4 12c0 7.4-5.6 11.7-17 13.1l-74.4 15.5c0 5.4 4.2 12 12.7 19.8a40.8 40.8 0 0 0 20 5.9h3.8a50 50 0 0 0 28.3-9.2l4.9-1c7.5 1.7 11.2 5.4 11.2 11.1v2.4c0 7.5-11 14-32.6 19.4l-11.7 1h-3.3c-25.5 0-43.7-12.5-54.5-37.3a66 66 0 0 1-3.9-21.8c0-24.3 11.4-42.4 34-54.1a61 61 0 0 1 22.6-4.8zm-30.1 44l60.3-12.5v-.6a47.5 47.5 0 0 0-24.3-7.3H724a32.3 32.3 0 0 0-31.1 20.4zm159.6-44c22 0 38.8 8.4 50.6 25.1 2.3 5.5 3.3 9.7 3.3 12.6V178c-1.5 7-5 10.6-10.6 10.6h-2c-6.2 0-10-4.2-11.1-12.5v-66.3c0-3.7-3.7-7.3-11.1-11a43.4 43.4 0 0 0-19.5-4.4h-3.8c-13.7 0-23.7 4.3-30.2 13l-1 3.9V178c-1.6 7-5.1 10.6-10.6 10.6h-2c-6.4 0-10.1-4.3-11-13v-62.4c0-23.2 16.4-37.4 49.5-42.6h9.5zm-771.7 0h-5.3c-7 0-14.7 1.6-23 4.8-22.6 11.8-34 30-34 54.2 0 7 1.4 14.3 3.9 21.8 10.9 24.8 29 37.2 54.5 37.2h3.4l11.6-1c21.7-5.3 32.6-11.8 32.6-19.3V166c0-5.8-3.8-9.5-11.2-11-3 0-4.9.9-4.9.9-9 6.1-18.4 9.2-28.2 9.2h-3.9a40 40 0 0 1-20-5.9c-8.4-7.8-12.7-14.4-12.7-19.8-1.9-16 1.5-24.7 1.5-24.7a32.2 32.2 0 0 1 31.1-20.3h5c8 0 16 2.4 24.2 7.3l1.7 1s1.8 1 4.9 1c7.5-1.6 11.2-5.4 11.2-11.1V90c0-7.5-11.3-13.5-33.1-18.3 0 0-2.1-.8-9.3-1.1z\" />\n      </svg>\n    </div>\n  );\n}\n\nexport default Brand;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Brand/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.m-brand {\n  $baseName: &;\n\n  display: inline-block;\n  max-width: $co-brand-width;\n  max-height: 2rem;\n\n  path {\n    fill: $co-color__primary;\n  }\n\n  &--low {\n    path {\n      fill: $co-color__primary__text--alt;\n    }\n  }\n\n  &--responsive {\n    #{$baseName}__brand-name {\n      display: none;\n\n      @include breakpoint(medium) {\n        display: inline-block;\n      }\n    }\n  }\n\n  &__logo,\n  &__brand-name {\n    height: 100%;\n  }\n\n  &__brand-name {\n    margin-left: 0.4rem;\n  }\n\n  @include breakpoint(medium) {\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Button/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport Button from '.';\n\ndescribe('component Button', () => {\n  it('should render', () => {\n    const handleClick = jest.fn();\n\n    render(<Button onClick={handleClick}>Foo</Button>);\n\n    userEvent.click(screen.getByText('Foo'));\n    expect(handleClick).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Button/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport Icon from '../Icon';\nimport RawButton from '../RawButton';\nimport './style.scss';\nimport Spinner from '../Spinner';\n\ninterface Props extends React.ComponentProps<'button'> {\n  className?: string;\n  shape?: 'plain' | 'hollow';\n  icon?: React.ReactElement | string;\n  isLoading?: boolean;\n  display?: 'inline' | 'inline-block' | 'block' | 'expanded';\n  color?: 'success' | 'alert' | 'secondary' | 'active' | 'disabled';\n  responsive?: 'icon-only' | 'text-only';\n  accessKey?: string;\n  noDecoration?: boolean;\n  disabled?: boolean;\n  center?: boolean;\n  innerRef: React.ForwardedRef<HTMLButtonElement>;\n}\nfunction Button({\n  children,\n  className,\n  isLoading,\n  icon = isLoading ? (\n    <Spinner svgTitleId=\"badge-spinner\" isLoading display=\"inline\" />\n  ) : undefined,\n  display = 'inline-block',\n  color,\n  shape,\n  responsive,\n  noDecoration = false,\n  center = true,\n  innerRef,\n  ...props\n}: Props) {\n  const renderIcon = () => {\n    if (React.isValidElement(icon)) {\n      const iconProps = {\n        ...icon.props,\n        className: classnames(icon.props.className, 'm-button__icon'),\n      };\n\n      return <icon.type {...iconProps} />;\n    }\n\n    // @ts-ignore\n    return <Icon className=\"m-button__icon\" type={icon} />;\n  };\n\n  const buttonProps = {\n    ...props,\n    className: classnames(className, 'm-button', {\n      'm-button--center': center,\n      'm-button--active': color === 'active',\n      'm-button--alert': color === 'alert',\n      'm-button--secondary': color === 'secondary',\n      'm-button--success': color === 'success',\n      'm-button--disabled': color === 'disabled',\n\n      'm-button--expanded': display === 'expanded',\n      'm-button--inline': display === 'inline',\n      'm-button--text': display === 'inline' && !noDecoration,\n      'm-button--inline-block': display === 'inline-block',\n      'm-button--block': display === 'block',\n\n      'm-button--plain': shape === 'plain',\n      'm-button--hollow': shape === 'hollow',\n\n      'm-button--icon': icon,\n\n      'm-button--icon-only': responsive === 'icon-only',\n      'm-button--text-only': responsive === 'text-only',\n    }),\n  };\n\n  if (icon) {\n    return (\n      <RawButton {...buttonProps} ref={innerRef}>\n        {renderIcon()}\n        {children && <span className=\"m-button__text\">{children}</span>}\n      </RawButton>\n    );\n  }\n\n  return (\n    <RawButton {...buttonProps} ref={innerRef}>\n      {children}\n    </RawButton>\n  );\n}\n\ntype ButtonProps = Omit<Props, 'innerRef'>;\nexport default React.forwardRef<HTMLButtonElement, ButtonProps>(\n  (props, ref) => <Button {...props} innerRef={ref} />\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Button/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-clickable';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.m-button {\n  @include o-clickable;\n\n  text-align: left;\n\n  &__text {\n    margin-left: calc($co-margin / 4);\n  }\n\n  &--center {\n    text-align: center;\n  }\n\n  // display: inline, inline-block or expanded :\n  &--inline-block {\n    @include o-clickable--button;\n  }\n\n  &--block {\n    @include o-clickable--button;\n\n    width: 100%;\n  }\n\n  &--inline {\n    @include o-clickable--inline;\n  }\n\n  &--text {\n    @include o-clickable--text;\n  }\n\n  &--expanded {\n    @include o-clickable--button;\n    @include o-clickable--expanded;\n  }\n\n  // shape: plain or hollow :\n  &--plain {\n    @include o-clickable--plain;\n    @each $name, $color in $caliopen-palette {\n      &.m-button--#{$name} {\n        $color-hover: scale-color($color, $lightness: -50%);\n\n        border-color: transparent;\n        background-color: $color;\n        color: $co-color__clickable-text--contrasted;\n\n        &:hover,\n        &:focus {\n          &:not(:disabled) {\n            background-color: $color-hover;\n            color: $co-color__clickable-text--contrasted;\n          }\n        }\n      }\n    }\n  }\n\n  &--hollow {\n    @include o-clickable--hollow;\n    @each $name, $color in $caliopen-palette {\n      &.m-button--#{$name} {\n        $color-hover: scale-color($color, $lightness: -50%);\n\n        &:hover,\n        &:focus {\n          background-color: transparent;\n        }\n      }\n    }\n  }\n\n  &--active {\n    @include o-clickable--active-text;\n    @include o-clickable--active-button;\n  }\n\n  @include breakpoint(small only) {\n    &__text {\n      max-width: 1.5 * $co-component__height;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      font-size: $co-font__size--small;\n      white-space: nowrap;\n    }\n\n    &--icon-only {\n      .m-button__text {\n        display: none;\n      }\n    }\n\n    &--text-only {\n      .m-button__icon {\n        display: none;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Callout/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport './style.scss';\n\ntype Intent = 'success' | 'info' | 'warning' | 'alert';\n\ninterface Props {\n  className?: string;\n  children: React.ReactNode;\n  color?: Intent;\n}\nfunction Callout({ className, children, color }: Props) {\n  const calloutClassName = classnames(className, 'm-callout', {\n    'm-callout--success': color === 'success',\n    'm-callout--info': color === 'info',\n    'm-callout--warning': color === 'warning',\n    'm-callout--alert': color === 'alert',\n  });\n\n  return <div className={calloutClassName}>{children}</div>;\n}\n\nexport default Callout;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Callout/style.scss",
    "content": "@import '../../styles/object/o-callout';\n\n.m-callout {\n  @include o-callout;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Checkbox/index.tsx",
    "content": "import * as React from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { isBoolean } from 'lodash';\nimport { v4 as uuidv4 } from 'uuid';\nimport Label from '../Label';\nimport './style.scss';\n\nexport interface Props\n  extends React.DetailedHTMLProps<\n    React.InputHTMLAttributes<HTMLInputElement>,\n    HTMLInputElement\n  > {\n  label: React.ReactNode;\n  id?: string;\n  indeterminate?: boolean;\n  showLabelforSr?: boolean;\n  className?: string;\n}\n\nclass Checkbox extends React.Component<Props> {\n  static propTypes = {\n    label: PropTypes.node.isRequired,\n    id: PropTypes.string,\n    indeterminate: PropTypes.bool,\n    showLabelforSr: PropTypes.bool,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    id: undefined,\n    indeterminate: null,\n    showLabelforSr: false,\n    className: undefined,\n  };\n\n  state = {};\n\n  selector: null | HTMLInputElement = null;\n\n  componentDidMount() {\n    // apply the indeterminate attribute of the real checkbox element\n    if (this.selector && isBoolean(this.props.indeterminate)) {\n      this.selector.indeterminate = this.props.indeterminate;\n    }\n  }\n\n  componentDidUpdate(prevProps) {\n    if (\n      this.selector &&\n      isBoolean(this.props.indeterminate) &&\n      prevProps.indeterminate !== this.props.indeterminate\n    ) {\n      this.selector.indeterminate = this.props.indeterminate;\n    }\n  }\n\n  render() {\n    const {\n      id = uuidv4(),\n      label,\n      showLabelforSr,\n      indeterminate,\n      className,\n      ...inputProps\n    } = this.props;\n\n    return (\n      <div className={classnames(className, 'm-checkbox')}>\n        <input\n          type=\"checkbox\"\n          className=\"m-checkbox__input\"\n          ref={(el) => {\n            this.selector = el;\n          }}\n          id={id}\n          {...inputProps}\n        />\n        <Label\n          className={classnames('m-checkbox__label', {\n            'show-for-sr': showLabelforSr,\n          })}\n          htmlFor={id}\n        >\n          {label}\n        </Label>\n      </div>\n    );\n  }\n}\n\nexport default Checkbox;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Checkbox/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$co-checkbox-size: 14px;\n\n.m-checkbox {\n  @include flex-grid-row;\n\n  align-items: center;\n\n  &__label {\n    @include flex-grid-column(shrink);\n\n    cursor: pointer;\n  }\n\n  &__input {\n    @include flex-grid-size(shrink);\n\n    width: $co-checkbox-size;\n    height: $co-checkbox-size;\n    border: 1px solid $co-color__primary;\n    background: $co-color__fg__back;\n    cursor: pointer;\n    appearance: none;\n\n    &::before {\n      display: none;\n      content: ' ';\n      position: absolute;\n      right: 1px;\n      left: 1px;\n      background: $co-color__primary--low;\n    }\n\n    &:checked {\n      position: relative;\n\n      &::before {\n        display: block;\n        top: 1px;\n        bottom: 1px;\n      }\n    }\n\n    &:indeterminate {\n      position: relative;\n\n      &::before {\n        display: block;\n        top: 4px;\n        bottom: 4px;\n      }\n    }\n\n    &:disabled {\n      position: relative;\n      border: 1px solid $co-color__fg__border--high;\n      background: $co-color__fg__back--low;\n      cursor: default;\n\n      &::before {\n        background: $co-color__fg__border--high;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/CheckboxFieldGroup/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { v1 as uuidV1 } from 'uuid';\nimport Switch from '../Switch';\nimport Checkbox from '../Checkbox';\nimport Label from '../Label';\nimport FieldGroup from '../FieldGroup';\n\nimport './style.scss';\n\ninterface Props\n  extends React.DetailedHTMLProps<\n    React.InputHTMLAttributes<HTMLInputElement>,\n    HTMLInputElement\n  > {\n  id?: string;\n  className?: string;\n  label: React.ReactNode;\n  labelClassname?: string;\n  showTextLabel?: boolean;\n  displaySwitch?: boolean;\n  errors?: React.ReactNode[];\n}\n\nfunction CheckboxFieldGroup({\n  id,\n  label,\n  labelClassname,\n  className,\n  errors,\n  showTextLabel,\n  displaySwitch,\n  ...inputProps\n}: Props) {\n  const checkboxId = id || uuidV1();\n\n  const renderCheckbox = () => (\n    // @ts-ignore\n    <Checkbox id={checkboxId} label={label} {...inputProps} />\n  );\n\n  const renderSwitch = () => (\n    <div>\n      <Switch id={checkboxId} label={label} {...inputProps} />\n      {showTextLabel && (\n        <Label\n          htmlFor={checkboxId}\n          className={classnames(\n            'm-checkbox-field-group__label',\n            labelClassname\n          )}\n        >\n          {label}\n        </Label>\n      )}\n    </div>\n  );\n\n  return (\n    <FieldGroup\n      errors={errors}\n      className={classnames('m-checkbox-field-group', className)}\n    >\n      {displaySwitch ? renderSwitch() : renderCheckbox()}\n    </FieldGroup>\n  );\n}\n\nexport default CheckboxFieldGroup;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/CheckboxFieldGroup/style.scss",
    "content": "@import '../../styles/common';\n\n.m-checkbox-field-group {\n  &__label {\n    padding-left: map_get($co-form__spacing, 'small');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Confirm/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { act, render, screen } from '@testing-library/react';\nimport Confirm from './index';\nimport Button from '../Button';\n\ndescribe('component Confirm', () => {\n  it('render', () => {\n    const handleConfirm = jest.fn();\n    render(\n      <>\n        <div id=\"root\" />\n        <Confirm\n          onConfirm={handleConfirm}\n          render={(confirm) => <Button onClick={confirm}>Delete</Button>}\n        />\n      </>\n    );\n\n    expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Confirm/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans } from '@lingui/react';\nimport classnames from 'classnames';\nimport Button from '../Button';\nimport Modal from '../Modal';\nimport './style.scss';\n\ntype callback = () => void;\n\ninterface Props {\n  onConfirm: () => void;\n  onCancel?: () => void;\n  onClose?: () => void;\n  render: (confirm: callback) => React.ReactNode;\n  title?: React.ReactNode;\n  content?: React.ReactNode;\n  confirmButtonContent?: React.ReactNode;\n  className?: string;\n}\n\nconst noop = () => {};\nconst defaultConfirmButton = (\n  <Trans id=\"confirm.action.confirm\" message=\"Yes I'm sure\" />\n);\n\n/**\n * const usage = () => (\n *  <Confirm render={confirm => (<Button onClick={confirm}>Delete</Button>)} />\n * );\n */\nfunction Confirm({\n  render,\n  onConfirm,\n  onCancel = noop,\n  onClose = noop,\n  title,\n  content,\n  confirmButtonContent = defaultConfirmButton,\n  className,\n}: Props) {\n  const [isModalOpen, setIsModalOpen] = React.useState(false);\n\n  const confirm = () => {\n    setIsModalOpen(true);\n  };\n\n  const handleClose = () => {\n    setIsModalOpen(false);\n    onClose();\n  };\n\n  const handleCancel = () => {\n    setIsModalOpen(false);\n    onCancel();\n  };\n\n  const handleConfirm = async () => {\n    await Promise.resolve(onConfirm());\n    // FIXME: it throws when unmounted in case onConfirm drop the component (e.g. delete)\n    setIsModalOpen(false);\n  };\n\n  return (\n    <div className={classnames(className, 'm-confirm')}>\n      {render(confirm)}\n      <Modal title={title} onClose={handleClose} isOpen={isModalOpen}>\n        {content}\n        <div className=\"m-confirm__actions\">\n          <Button shape=\"plain\" onClick={handleCancel}>\n            <Trans id=\"confirm.action.cancel\" message=\"Cancel\" />\n          </Button>{' '}\n          <Button shape=\"plain\" color=\"alert\" onClick={handleConfirm}>\n            {confirmButtonContent}\n          </Button>\n        </div>\n      </Modal>\n    </div>\n  );\n}\n\nexport default Confirm;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Confirm/style.scss",
    "content": "@import '../../styles/common';\n\n.m-confirm {\n  display: inline-block;\n\n  &__actions {\n    padding-top: $co-component__spacing;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/DatePickerGroup/index.jsx",
    "content": "import { createSelector } from 'reselect';\nimport { connect } from 'react-redux';\nimport Presenter from './presenter';\n\nconst i18nSelector = (state) => state.i18n;\nconst mapStateToProps = createSelector([i18nSelector], (i18n) => ({\n  locale: i18n.locale,\n}));\n\nexport default connect(mapStateToProps)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/components/DatePickerGroup/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport DatePicker from 'react-datepicker'; // https://github.com/Hacker0x01/react-datepicker\nimport 'react-datepicker/dist/react-datepicker.css';\nimport moment from 'moment';\nimport classnames from 'classnames';\n\nimport './style.scss';\n\nclass DatePickerGroup extends PureComponent {\n  static propTypes = {\n    calendarClassName: PropTypes.string,\n    className: PropTypes.string,\n    id: PropTypes.string.isRequired,\n    inputClassName: PropTypes.string,\n    label: PropTypes.string,\n    locale: PropTypes.string,\n    onChange: PropTypes.func.isRequired,\n    selected: PropTypes.string,\n  };\n\n  static defaultProps = {\n    calendarClassName: null,\n    className: null,\n    inputClassName: null,\n    label: null,\n    locale: undefined,\n    selected: null,\n  };\n\n  render() {\n    const {\n      calendarClassName,\n      className,\n      id,\n      inputClassName,\n      label,\n      locale,\n      onChange,\n      selected,\n      ...props\n    } = this.props;\n\n    return (\n      <div className={classnames('m-date-picker-group', className)}>\n        {label && <label htmlFor={id}>{label}</label>}\n\n        <DatePicker\n          id={id}\n          className={classnames('m-date-picker-group__input', inputClassName)}\n          calendarClassName={classnames(\n            'm-date-picker-group__calendar',\n            calendarClassName\n          )}\n          selected={selected ? moment(selected) : null}\n          onChange={onChange}\n          locale={locale}\n          {...props}\n        />\n      </div>\n    );\n  }\n}\n\nexport default DatePickerGroup;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/DatePickerGroup/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n@import '../../styles/object/o-date-picker';\n@import '../../styles/object/o-form-element';\n\n.m-date-picker-group {\n  @include o-date-picker-popper;\n\n  &__label {\n    display: block;\n  }\n\n  &__input {\n    @include o-form-element;\n    @include o-form-element--light;\n  }\n\n  &__calendar {\n    @include o-date-picker-calendar;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/DefList/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nfunction DefList({ className, definitions }) {\n  const nodes = definitions.reduce((prev, definition) => {\n    prev.push(\n      <dt className=\"m-def-list__title\" key={prev.length + 1}>\n        {definition.title}\n      </dt>\n    );\n    definition.descriptions.forEach((description) => {\n      prev.push(\n        <dd className=\"m-def-list__def\" key={prev.length + 1}>\n          {description}\n        </dd>\n      );\n    });\n\n    return prev;\n  }, []);\n\n  return <dl className={classnames('m-def-list', className)}>{nodes}</dl>;\n}\n\nDefList.propTypes = {\n  className: PropTypes.string,\n  definitions: PropTypes.arrayOf(PropTypes.shape({})),\n};\nDefList.defaultProps = {\n  className: undefined,\n  definitions: [],\n};\n\nexport default DefList;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/DefList/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport Presenter from './index';\n\ndescribe('component DefList', () => {\n  it('render', () => {\n    const props = {\n      definitions: [\n        { title: 'A', descriptions: ['A.1', 'A.2'] },\n        { title: 'B', descriptions: ['B.1'] },\n      ],\n    };\n\n    const comp = shallow(<Presenter {...props} />);\n\n    expect(comp.find('dt').first().text()).toEqual('A');\n    expect(comp.find('dt').length).toEqual(2);\n    expect(comp.find('dd').length).toEqual(3);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/DefList/style.scss",
    "content": "@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.m-def-list {\n  @include clearfix;\n\n  &__title {\n    margin-bottom: map_get($co-form__spacing, 'small');\n  }\n\n  &__def {\n    margin-bottom: map_get($co-form__spacing, 'large');\n    padding: map_get($co-form__spacing, 'xsmall')\n      map_get($co-form__spacing, 'small');\n    border: 1px solid $co-color__fg__border--lower;\n    color: $co-color__fg__text;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Dropdown/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { act, render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport Button from 'src/components/Button';\nimport Dropdown, { withDropdownControl } from '.';\n\nconst DropdownControl = withDropdownControl(Button);\n\nfunction Comp() {\n  const ref = React.useRef<HTMLButtonElement>(null);\n\n  return (\n    <>\n      <p>Outside</p>\n      <DropdownControl ref={ref}>Click</DropdownControl>\n      <Dropdown id=\"test-dropdown\" dropdownControlRef={ref}>\n        Hello world\n      </Dropdown>\n    </>\n  );\n}\n\nfunction CompControlled() {\n  const ref = React.useRef<HTMLDivElement>(null);\n  const [visible, setVisible] = React.useState(false);\n\n  return (\n    <>\n      <p>Outside</p>\n      <button type=\"button\" onClick={() => setVisible((prev) => !prev)}>\n        {visible ? 'Hide' : 'Show'}\n      </button>\n      <div ref={ref}>Ctrl</div>\n      <Dropdown\n        id=\"test-dropdown\"\n        dropdownControlRef={ref}\n        closeOnClick=\"doNotClose\"\n        show={visible}\n      >\n        Hello world\n      </Dropdown>\n    </>\n  );\n}\n\ndescribe('component > Dropdown', () => {\n  it('toggle dropdown', async () => {\n    render(<Comp />);\n\n    expect(screen.queryByText('Hello world')).not.toBeInTheDocument();\n    act(() => {\n      userEvent.click(screen.getByRole('button', { name: 'Click' }));\n    });\n    expect(screen.getByText('Hello world')).toBeInTheDocument();\n    act(() => {\n      userEvent.click(screen.getByText('Outside'));\n    });\n    expect(screen.queryByText('Hello world')).not.toBeInTheDocument();\n  });\n\n  it('toggles controlled', () => {\n    render(<CompControlled />);\n\n    expect(screen.queryByText('Hello world')).not.toBeInTheDocument();\n    userEvent.click(screen.getByText('Ctrl'));\n    expect(screen.queryByText('Hello world')).not.toBeInTheDocument();\n    userEvent.click(screen.getByRole('button', { name: 'Show' }));\n    expect(screen.getByText('Hello world')).toBeInTheDocument();\n    userEvent.click(screen.getByText('Outside'));\n    expect(screen.getByText('Hello world')).toBeInTheDocument();\n    userEvent.click(screen.getByRole('button', { name: 'Hide' }));\n    expect(screen.queryByText('Hello world')).not.toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Dropdown/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport throttle from 'lodash/throttle';\nimport { useForwardedRef } from 'src/hooks/forwardedRef';\nimport { getDropdownStyle } from './services/getDropdownStyle';\nimport { addEventListener } from '../../services/event-manager';\nimport './style.scss';\n\nexport const CONTROL_PREFIX = 'toggle';\nconst CLOSE_ON_CLICK_ALL = 'all';\nconst CLOSE_ON_CLICK_EXCEPT_SELF = 'exceptSelf';\nconst DO_NOT_CLOSE = 'doNotClose';\n\ntype CloseOn =\n  | typeof CLOSE_ON_CLICK_ALL\n  | typeof CLOSE_ON_CLICK_EXCEPT_SELF\n  | typeof DO_NOT_CLOSE;\n\nexport const withDropdownControl = (WrappedComponent) => {\n  function WithDropdownControl(props, ref) {\n    if (!ref) {\n      throw new Error(\n        `a ref is mandatory for a dropdown controller created with \"${\n          WrappedComponent.displayName || WrappedComponent.name || 'Component'\n        }\"`\n      );\n    }\n\n    return <WrappedComponent role=\"button\" tabIndex=\"0\" ref={ref} {...props} />;\n  }\n\n  // does this work since it is a forwarded component\n  WithDropdownControl.displayName = `WithDropdownControl(${\n    WrappedComponent.displayName || WrappedComponent.name || 'Component'\n  })`;\n\n  return React.forwardRef(WithDropdownControl);\n};\n\ninterface DropdownProps {\n  id: string;\n  alignRight?: boolean; // force align right\n  children: React.ReactNode;\n  className?: string;\n  closeOnClick?: CloseOn;\n  closeOnScroll?: boolean; // should Dropdown close on windows scroll ?\n  isMenu?: boolean;\n  /** even if controlled (`doNotClose`), some behaviors (resize) can autoclose dropdown, so `onToggle` must be handled */\n  onToggle?: (visibility: boolean) => void;\n  show?: boolean;\n  displayFirstLayer?: boolean;\n  innerRef: React.ForwardedRef<HTMLDivElement>;\n  dropdownControlRef: React.MutableRefObject<HTMLElement | null>;\n  /* display css box-shadow */\n  withShadow?: boolean;\n}\nconst defaultDropdownStyle: React.CSSProperties = {\n  // force postion in order to have the correct width when calc position\n  top: 0,\n  left: 0,\n  // XXX: not required but testing-lib does not see attached css (may be take a look to `jest-transform-css`)\n  visibility: 'hidden',\n};\nfunction Dropdown({\n  id,\n  alignRight = false,\n  children,\n  className,\n  closeOnClick = CLOSE_ON_CLICK_EXCEPT_SELF,\n  closeOnScroll = false,\n  isMenu = false,\n  onToggle = () => {},\n  show = false,\n  displayFirstLayer = false,\n  innerRef,\n  dropdownControlRef,\n  withShadow,\n}: DropdownProps) {\n  const dropdownRef = useForwardedRef<HTMLDivElement>(innerRef);\n\n  const [isOpen, setIsOpen] = React.useState(show);\n\n  const handleToggleVisibility = (visibility: boolean) => {\n    onToggle(visibility);\n    if (closeOnClick !== 'doNotClose') {\n      setIsOpen(visibility);\n    }\n  };\n\n  React.useEffect(() => {\n    const unsubscribeResizeEvent = addEventListener('resize', () => {\n      // this prevent dropdown to be misplaced on window resize\n      // TODO: get new offset instead of closing dropdown\n      handleToggleVisibility(false);\n    });\n\n    const unsubscribeScrollEvent = closeOnScroll\n      ? addEventListener(\n          'scroll',\n          throttle(\n            () => {\n              const scrollSize = window.scrollY;\n              const closeDropdown = scrollSize > 10;\n\n              if (closeDropdown) {\n                handleToggleVisibility(false);\n              }\n            },\n            100,\n            { leading: true, trailing: true }\n          )\n        )\n      : () => {\n          // noop\n        };\n\n    return () => {\n      unsubscribeResizeEvent();\n      unsubscribeScrollEvent();\n    };\n  }, []);\n\n  React.useEffect(() => {\n    let unsubscribeClickEvent = () => {};\n\n    if (closeOnClick !== DO_NOT_CLOSE) {\n      unsubscribeClickEvent = addEventListener(\n        'click',\n        (ev: React.SyntheticEvent<HTMLElement>) => {\n          const { target } = ev;\n          let dropdownClick = false;\n          if (\n            dropdownRef &&\n            typeof dropdownRef !== 'function' &&\n            dropdownRef?.current\n          ) {\n            dropdownClick =\n              dropdownRef.current === target ||\n              // https://stackoverflow.com/questions/61164018/typescript-ev-target-and-node-contains-eventtarget-is-not-assignable-to-node\n              dropdownRef.current?.contains(target as Node);\n          }\n\n          const controlClick: boolean =\n            dropdownControlRef.current === target ||\n            dropdownControlRef.current?.contains(target as Node) ||\n            false;\n\n          if (controlClick) {\n            handleToggleVisibility(!isOpen);\n\n            return;\n          }\n\n          if (\n            closeOnClick === CLOSE_ON_CLICK_EXCEPT_SELF &&\n            (dropdownClick || controlClick)\n          ) {\n            return;\n          }\n\n          if (isOpen === true) {\n            handleToggleVisibility(false);\n          }\n        }\n      );\n    }\n\n    return () => {\n      unsubscribeClickEvent();\n    };\n  }, [isOpen, dropdownRef, dropdownControlRef]);\n\n  const getStyles = (visibility: boolean): React.CSSProperties => {\n    if (!visibility) {\n      // dropdown must be at default position when not visible for correct calc when displaying it\n      return defaultDropdownStyle;\n    }\n\n    // FIXME: when no dropdownControl declared, it should calc position according to ??? position\n    // may be a relativeRef which is optionnal?\n    // relativeRef = relativeRef || dropdownControlRef;\n    if (\n      dropdownControlRef &&\n      typeof dropdownControlRef !== 'function' &&\n      dropdownControlRef.current &&\n      dropdownRef &&\n      typeof dropdownRef !== 'function' &&\n      dropdownRef.current\n    ) {\n      return getDropdownStyle({\n        alignRight,\n        controlElement: dropdownControlRef.current,\n        dropdownElement: dropdownRef.current,\n      });\n    }\n\n    return defaultDropdownStyle;\n  };\n\n  // Since dropdown position is computed according to previous position, the\n  // memoization makes sure styles are kept during multiples re-render.\n  const dropdownStyle = React.useMemo(\n    () => getStyles(isOpen || show),\n    [isOpen, show]\n  );\n\n  const dropdownProps = {\n    id,\n    className: classnames(\n      'm-dropdown',\n      { 'm-dropdown--is-open': (isOpen || show) && children },\n      { 'm-dropdown--is-menu': isMenu },\n      { 'm-dropdown--display-first-layer': displayFirstLayer },\n      { 'm-dropdown--shadow': withShadow },\n      className\n    ),\n    tabIndex: 0,\n    role: 'presentation',\n    style: dropdownStyle,\n  };\n\n  // XXX: use native html element <dialog open> ?\n  return (\n    <div ref={dropdownRef} {...dropdownProps}>\n      {(isOpen || show) && children}\n    </div>\n  );\n}\n\ntype ForwardedProps = Omit<DropdownProps, 'innerRef'>;\ntype Ref = HTMLDivElement;\n\nexport default React.forwardRef<Ref, ForwardedProps>((props, ref) => (\n  <Dropdown {...props} innerRef={ref} />\n));\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Dropdown/services/getDropdownStyle.spec.ts",
    "content": "import { getDropdownStyle } from './getDropdownStyle';\n\nlet controlElement;\nlet dropdownElement;\ndescribe('component DropdownControl - getDropdownStyle', () => {\n  beforeEach(() => {\n    controlElement = document.createElement('div');\n    dropdownElement = document.createElement('div');\n  });\n  const win = {\n    pageYOffset: 0,\n    pageXOffset: 0,\n    innerWidth: 100,\n    innerHeight: 200,\n  } as Window;\n\n  it('align right to its controlElement', () => {\n    controlElement.getBoundingClientRect = jest.fn(\n      () =>\n        ({\n          x: 80,\n          left: 80,\n          y: 10,\n          top: 10,\n          width: 10,\n          height: 10,\n        } as DOMRect)\n    );\n    dropdownElement.getBoundingClientRect = jest.fn(\n      () =>\n        ({\n          x: 0,\n          left: 0,\n          y: 0,\n          top: 0,\n          width: 50,\n          height: 50,\n        } as DOMRect)\n    );\n\n    const props = {\n      controlElement,\n      dropdownElement,\n      win,\n    };\n\n    expect(getDropdownStyle(props)).toEqual({\n      left: `${80 + 10 - 50}px`,\n      top: '20px',\n      position: 'absolute',\n    });\n  });\n\n  describe('force align', () => {\n    beforeEach(() => {\n      controlElement.getBoundingClientRect = jest.fn(\n        () =>\n          ({\n            x: 50,\n            left: 50,\n            y: 10,\n            top: 10,\n            width: 10,\n            height: 10,\n          } as DOMRect)\n      );\n      dropdownElement.getBoundingClientRect = jest.fn(\n        () =>\n          ({\n            x: 0,\n            left: 0,\n            y: 0,\n            top: 0,\n            width: 30,\n            height: 50,\n          } as DOMRect)\n      );\n    });\n\n    it('align left to its controlElement', () => {\n      const props = {\n        controlElement,\n        dropdownElement,\n        win,\n      };\n\n      expect(getDropdownStyle(props)).toEqual({\n        left: '50px',\n        top: '20px',\n        position: 'absolute',\n      });\n    });\n\n    it('force align right', () => {\n      const props = {\n        alignRight: true,\n        controlElement,\n        dropdownElement,\n        win,\n      };\n\n      expect(getDropdownStyle(props)).toEqual({\n        left: `${50 + 10 - 30}px`,\n        top: '20px',\n        position: 'absolute',\n      });\n    });\n  });\n\n  describe('small viewPort', () => {\n    it('position fixed', () => {\n      controlElement.getBoundingClientRect = jest.fn(\n        () =>\n          ({\n            x: 50,\n            left: 50,\n            y: 10,\n            top: 10,\n            width: 10,\n            height: 10,\n          } as DOMRect)\n      );\n      dropdownElement.getBoundingClientRect = jest.fn(\n        () =>\n          ({\n            x: 0,\n            left: 0,\n            y: 0,\n            top: 0,\n            width: 30,\n            height: 210,\n          } as DOMRect)\n      );\n      const props = {\n        controlElement,\n        dropdownElement,\n        win,\n      };\n\n      expect(getDropdownStyle(props)).toEqual({\n        left: '50px',\n        top: '42px',\n        position: 'fixed',\n        height: `${200 - 42}px`,\n      });\n    });\n\n    it('width 100%', () => {\n      controlElement.getBoundingClientRect = jest.fn(\n        () =>\n          ({\n            x: 50,\n            left: 50,\n            y: 10,\n            top: 10,\n            width: 10,\n            height: 10,\n          } as DOMRect)\n      );\n      dropdownElement.getBoundingClientRect = jest.fn(\n        () =>\n          ({\n            x: 0,\n            left: 0,\n            y: 0,\n            top: 0,\n            width: 110,\n            height: 50,\n          } as DOMRect)\n      );\n\n      const props = {\n        controlElement,\n        dropdownElement,\n        win,\n      };\n\n      expect(getDropdownStyle(props)).toEqual({\n        left: '0px',\n        top: '20px',\n        position: 'absolute',\n        width: '100%',\n      });\n    });\n  });\n\n  it('align on scroll', () => {\n    controlElement.getBoundingClientRect = jest.fn(\n      () =>\n        ({\n          x: 80,\n          left: 80,\n          y: 10,\n          top: 10,\n          width: 10,\n          height: 10,\n        } as DOMRect)\n    );\n    dropdownElement.getBoundingClientRect = jest.fn(\n      () =>\n        ({\n          x: 0,\n          left: 0,\n          y: 0,\n          top: 0,\n          width: 50,\n          height: 50,\n        } as DOMRect)\n    );\n\n    const props = {\n      controlElement,\n      dropdownElement,\n      win,\n    };\n\n    expect(getDropdownStyle(props)).toEqual({\n      left: `${80 + 10 - 50}px`,\n      top: '20px',\n      position: 'absolute',\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Dropdown/services/getDropdownStyle.ts",
    "content": "import { CSSProperties } from 'react';\n\nconst HEADER__HEIGHT = 42;\n\ninterface GetDropdownStyleProps {\n  alignRight?: boolean;\n  controlElement: HTMLElement;\n  dropdownElement: HTMLElement;\n  win?: Window;\n}\n// FIXME: how to calc when there is no controlElement?\nexport const getDropdownStyle = ({\n  alignRight = false,\n  controlElement,\n  dropdownElement,\n  win = window,\n}: GetDropdownStyleProps): CSSProperties => {\n  const controlRect = controlElement.getBoundingClientRect();\n  const dropdownRect = dropdownElement.getBoundingClientRect();\n\n  const winY = win.pageYOffset;\n  const winX = win.pageXOffset;\n  const winWidth = win.innerWidth;\n  const winHeight = win.innerHeight;\n\n  const offsetLeftWhenRightAligned = controlRect.width - dropdownRect.width;\n  // since dropdownElement is position absolute & top & left at 0 this is the real offset according\n  // to its relative parent\n  const initLeft = controlRect.left - dropdownRect.left;\n  const initTop = controlRect.top - dropdownRect.top;\n  const isAlignRight = alignRight && initLeft + offsetLeftWhenRightAligned >= 0;\n  const isTouchingRight = initLeft + dropdownRect.width >= winWidth + winX;\n  const isTouchingLeft = controlRect.right - dropdownRect.width < 0;\n\n  const isFullWidth = dropdownRect.width > winWidth;\n  const isFullHeight = dropdownRect.height > winHeight;\n\n  let position = 'absolute';\n  let height: undefined | string;\n  let width: undefined | string;\n  let top = `${initTop + controlRect.height}px`;\n  let left = `${initLeft}px`;\n\n  switch (true) {\n    case isFullWidth || (isTouchingLeft && isTouchingRight):\n      left = `${winX}px`;\n      width = '100%';\n      break;\n    case (isAlignRight || isTouchingRight) && !isFullWidth:\n      left = `${initLeft + offsetLeftWhenRightAligned}px`;\n      break;\n    default:\n  }\n\n  if (isFullHeight) {\n    position = 'fixed';\n    height = `${winHeight - HEADER__HEIGHT}px`;\n    top = `${winY + HEADER__HEIGHT}px`;\n  }\n\n  const offset = {\n    left,\n    top,\n    position,\n    height,\n    width,\n  } as React.CSSProperties; // XXX: workaround position incompatible type\n\n  return offset;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Dropdown/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-triangle';\n\n.m-dropdown {\n  position: absolute;\n  max-width: 100%; // prevent too large content on small screens\n  visibility: hidden;\n  overflow-y: auto;\n\n  &--is-open {\n    z-index: $l-z-index__dropdown;\n    visibility: visible;\n  }\n\n  &--display-first-layer {\n    z-index: $l-z-index__dropdown--first-layer !important;\n  }\n\n  &--is-menu {\n    background-color: $co-color__fg__back;\n    box-shadow: $co-shadow;\n  }\n\n  &--has-triangle {\n    &::before {\n      @include o-triangle('top', 1rem, $co-color__primary--lower);\n\n      right: 1rem;\n    }\n  }\n\n  &--shadow {\n    box-shadow: $co-shadow;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/DropdownMenu/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport Dropdown, { withDropdownControl } from '../Dropdown';\nimport './style.scss';\n\nexport { withDropdownControl };\n\n// @deprecated: use Dropdown\nfunction DropdownMenu({ className, hasTriangle, ...props }) {\n  return (\n    <Dropdown\n      className={classnames(\n        'm-dropdown-menu',\n        { 'm-dropdown-menu--has-triangle': hasTriangle },\n        className\n      )}\n      {...props}\n    />\n  );\n}\nDropdownMenu.propTypes = {\n  className: PropTypes.string,\n  hasTriangle: PropTypes.bool,\n};\n\nDropdownMenu.defaultProps = {\n  className: null,\n  hasTriangle: false,\n};\n\nexport default DropdownMenu;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/DropdownMenu/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-triangle';\n\n.m-dropdown-menu {\n  background-color: $co-color__fg__back;\n\n  &--has-triangle {\n    &::before {\n      @include o-triangle('top', 1rem, $co-color__primary--lower);\n\n      right: 1rem;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/FieldErrors/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport './style.scss';\nimport { ErrorMessage } from 'formik';\n\ninterface Props {\n  errors?: React.ReactNode;\n  className?: string;\n}\n\nfunction FieldErrors({ errors, className }: Props) {\n  let displayedErrors: React.ReactNode[];\n  if (!Array.isArray(errors)) {\n    displayedErrors = [errors];\n  } else {\n    displayedErrors = errors;\n  }\n\n  if (!displayedErrors || displayedErrors.length === 0) {\n    return null;\n  }\n\n  return (\n    <ul className={classnames('m-field-errors', className)}>\n      {displayedErrors.map((error, idx) => (\n        <li key={idx}>{error}</li>\n      ))}\n    </ul>\n  );\n}\n\ninterface FormikFieldErrorsProps {\n  name: string;\n}\nexport function FormikFieldErrors({ name }: FormikFieldErrorsProps) {\n  return (\n    <ErrorMessage name={name}>\n      {(msg) => <FieldErrors errors={[msg]} />}\n    </ErrorMessage>\n  );\n}\n\nexport default FieldErrors;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/FieldErrors/style.scss",
    "content": "@import '../../styles/common';\n\n.m-field-errors {\n  list-style: none;\n  padding-top: map_get($co-form__spacing, 'xsmall');\n  color: map_get($caliopen-palette, 'alert');\n  font-size: $co-font__size--xsmall;\n  font-weight: 700;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/FieldGroup/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport FieldErrors from '../FieldErrors';\n\nimport './style.scss';\n\nclass FieldGroup extends PureComponent {\n  static propTypes = {\n    children: PropTypes.node.isRequired,\n    className: PropTypes.string,\n    errorsClassname: PropTypes.string,\n    errors: PropTypes.arrayOf(PropTypes.node),\n  };\n\n  static defaultProps = {\n    className: null,\n    errorsClassname: null,\n    errors: [],\n  };\n\n  state = {};\n\n  render() {\n    const { className, children, errors, errorsClassname } = this.props;\n\n    return (\n      <div className={classnames('m-field-group', className)}>\n        {children}\n        {errors.length > 0 && (\n          <FieldErrors\n            className={classnames('m-field-group__errors', errorsClassname)}\n            errors={errors}\n          />\n        )}\n      </div>\n    );\n  }\n}\n\nexport default FieldGroup;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/FieldGroup/style.scss",
    "content": "@import '../../styles/common';\n\n.m-field-group {\n  &__errors {\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Fieldset/components/Legend/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\n\nexport class Legend extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  render() {\n    const { className, ...props } = this.props;\n\n    return (\n      <legend\n        className={classnames('m-fieldset__legend', className)}\n        {...props}\n      />\n    );\n  }\n}\n\nexport default Legend;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Fieldset/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nclass Fielset extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  render() {\n    const { className, ...props } = this.props;\n\n    return (\n      <fieldset className={classnames('m-fieldset', className)} {...props} />\n    );\n  }\n}\n\nexport { default as Legend } from './components/Legend';\nexport default Fielset;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Fieldset/style.scss",
    "content": "@import '../../styles/common';\n\n.m-fieldset {\n  &__legend {\n    margin-bottom: map_get($co-form__spacing, 'small');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/FileSize/index.spec.tsx",
    "content": "import React from 'react';\nimport { mount } from 'enzyme';\nimport FileSize from '.';\n\ndescribe.skip('component FileSize', () => {\n  it('render mb', () => {\n    const comp = mount(<FileSize size={10000000} />);\n\n    expect(comp.text()).toEqual('file.size.mB');\n  });\n\n  it('render kb', () => {\n    const comp = mount(<FileSize size={10500} />);\n\n    expect(comp.text()).toEqual('file.size.kB');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/FileSize/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, useLingui } from '@lingui/react';\n\ninterface Props {\n  size: number;\n}\nfunction FileSize({ size }: Props): React.ReactElement<typeof Trans> {\n  const { i18n } = useLingui();\n\n  if (size / 100 < 1) {\n    return <Trans id=\"file.size.B\" message=\"{size} B\" values={{ size }} />;\n  }\n\n  const renderNumber = (value) =>\n    i18n.number(value, { maximumFractionDigits: 1 });\n\n  if (size / 1000000 < 1) {\n    return (\n      <Trans\n        id=\"file.size.kB\"\n        message=\"{size} kB\"\n        values={{\n          size: renderNumber(Math.round(size / 10) / 100),\n        }}\n      />\n    );\n  }\n\n  if (size / 1000000000 < 1) {\n    return (\n      <Trans\n        id=\"file.size.mB\"\n        message=\"{size} mB\"\n        values={{\n          size: renderNumber(Math.round(size / 10000) / 100),\n        }}\n      />\n    );\n  }\n\n  if (size / 1000000000000 < 1) {\n    return (\n      <Trans\n        id=\"file.size.gB\"\n        message=\"{size} gB\"\n        values={{\n          size: renderNumber(Math.round(size / 10000000) / 100),\n        }}\n      />\n    );\n  }\n\n  return (\n    <Trans\n      id=\"file.size.tB\"\n      message=\"{size} tB\"\n      values={{\n        size: renderNumber(Math.round(size / 10000000000) / 100),\n      }}\n    />\n  );\n}\n\nexport default FileSize;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/FormGrid/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport './style.scss';\n\ntype Size = 'shrink' | 'small' | 'medium' | 'large';\n\ninterface FormColumnProps extends React.HTMLAttributes<HTMLDivElement> {\n  className?: string;\n  bottomSpace?: boolean;\n  size?: Size;\n  fluid?: boolean;\n  rightSpace?: boolean;\n}\nexport function FormColumn({\n  className,\n  bottomSpace,\n  size,\n  fluid,\n  rightSpace = true, // --right-space style is default for FormColumn\n  ...props\n}: FormColumnProps) {\n  const colClassName = classnames(\n    'm-form-grid__column',\n    {\n      'm-form-grid__column--fluid': fluid,\n      'm-form-grid__column--bottom-space': bottomSpace,\n      'm-form-grid__column--shrink': size === 'shrink',\n      'm-form-grid__column--small': size === 'small',\n      'm-form-grid__column--medium': size === 'medium',\n      'm-form-grid__column--large': size === 'large',\n      'm-form-grid__column--right-space': rightSpace,\n    },\n    className\n  );\n\n  return <div className={colClassName} {...props} />;\n}\n\ninterface FormRowProps extends React.HTMLAttributes<HTMLDivElement> {\n  className?: string;\n  reverse?: boolean;\n}\n\nexport function FormRow({ className, reverse, ...props }: FormRowProps) {\n  const rowClassName = classnames(\n    'm-form-grid__row',\n    {\n      'm-form-grid__row--reverse': reverse,\n    },\n    className\n  );\n\n  return <div className={rowClassName} {...props} />;\n}\n\ninterface FormGridProps extends React.HTMLAttributes<HTMLDivElement> {\n  className?: string;\n  reverse?: boolean;\n}\n\nfunction FormGrid({ className, ...props }: FormGridProps) {\n  return <div className={classnames('m-form-grid', className)} {...props} />;\n}\n\nexport default FormGrid;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/FormGrid/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.m-form-grid {\n  &__row {\n    @include flex-grid-row;\n\n    &--reverse {\n      flex-direction: row-reverse;\n    }\n  }\n\n  &__column {\n    @include flex-grid-size(12);\n\n    display: block;\n\n    &--align-center {\n      text-align: center;\n    }\n\n    &--fluid {\n      @include flex-grid-size;\n\n      padding-right: map_get($co-form__spacing, 'xsmall');\n    }\n\n    &--bottom-space {\n      margin-bottom: map_get($co-form__spacing, 'medium');\n    }\n\n    @include breakpoint(medium) {\n      &--right-space {\n        padding-right: map_get($co-form__spacing, 'medium');\n      }\n\n      &--align-right {\n        text-align: right;\n      }\n\n      &--small {\n        @include flex-grid-size(4);\n      }\n\n      &--medium {\n        @include flex-grid-size(6);\n      }\n\n      &--large {\n        @include flex-grid-size(8);\n      }\n\n      &--shrink {\n        @include flex-grid-size(shrink);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Icon/__snapshots__/index.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`component Icon render 1`] = `\n<div>\n  <i\n    class=\"fa fa-edit\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Icon/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { render } from '@testing-library/react';\nimport Icon from '.';\n\ndescribe('component Icon', () => {\n  it('render', () => {\n    const { container } = render(<Icon type=\"edit\" />);\n\n    expect(container).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Icon/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\n\nimport './style.scss';\n\nexport const typeAssoc = {\n  at: 'fa fa-at',\n  'address-book': 'fa fa-address-book',\n  'angle-down': 'fa fa-angle-down',\n  'arrow-right': 'fa fa-arrow-right',\n  'arrow-left': 'fa fa-arrow-left',\n  bell: 'fa fa-bell',\n  'birthday-cake': 'fa fa-birthday-cake',\n  briefcase: 'fa fa-briefcase',\n  building: 'fa fa-building',\n  calendar: 'fa fa-calendar',\n  'caret-up': 'fa fa-caret-up',\n  'caret-down': 'fa fa-caret-down',\n  check: 'fa fa-check',\n  circle: 'fa fa-circle',\n  cog: 'fa fa-cog',\n  comment: 'fa fa-comment',\n  comments: 'fa fa-comments',\n  'comments-o': 'fa fa-comments-o',\n  crosshairs: 'fa fa-crosshairs',\n  download: 'fa fa-download',\n  'dot-circle': 'fa fa-dot-circle',\n  edit: 'fa fa-edit',\n  envelope: 'fa fa-envelope',\n  'envelope-open': 'fa fa-envelope-open',\n  'ellipsis-v': 'fa fa-ellipsis-v',\n  email: 'fa fa-envelope',\n  editor: 'fa fa-font',\n  exchange: 'fa fa-exchange',\n  'exclamation-triangle': 'fa fa-exclamation-triangle',\n  'expire-soon': 'fa fa-clock-o',\n  desktop: 'fa fa-desktop',\n  facebook: 'fa fa-facebook',\n  file: 'fa fa-file',\n  filter: 'fa fa-filter',\n  folder: 'fa fa-folder-open',\n  github: 'fa fa-github',\n  google: 'fa fa-google',\n  home: 'fa fa-home',\n  'info-circle': 'fa fa-info-circle',\n  key: 'fa fa-key',\n  laptop: 'fa fa-laptop',\n  'list-ul': 'fa fa-list-ul',\n  lock: 'fa fa-lock',\n  'map-marker': 'fa fa-map-marker',\n  mastodon: 'svg svg-mastodon',\n  'paper-plane': 'fa fa-paper-plane',\n  paperclip: 'fa fa-paperclip',\n  pencil: 'fa fa-pencil',\n  plug: 'fa fa-plug',\n  plus: 'fa fa-plus',\n  phone: 'fa fa-phone',\n  question: 'fa fa-question',\n  'question-circle': 'fa fa-question-circle',\n  remove: 'fa fa-remove',\n  reply: 'fa fa-reply',\n  save: 'fa fa-floppy-o',\n  send: 'fa fa-paper-plane',\n  search: 'fa fa-search',\n  server: 'fa fa-server',\n  share: 'fa fa-share',\n  'share-alt': 'fa fa-share-alt',\n  shield: 'fa fa-shield',\n  smartphone: 'fa fa-mobile',\n  tag: 'fa fa-tag',\n  times: 'fa fa-times',\n  upload: 'fa fa-upload',\n  user: 'fa fa-user',\n  users: 'fa fa-users',\n  tablet: 'fa fa-tablet',\n  tags: 'fa fa-tags',\n  twitter: 'fa fa-twitter',\n  'th-large': 'fa fa-th-large',\n  trash: 'fa fa-trash',\n  warning: 'fa fa-warning',\n  'window-maximize': 'fa fa-window-maximize',\n};\n\nexport type Type = keyof typeof typeAssoc;\n\nconst svgGlyphs = {\n  mastodon: (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"61.076954mm\"\n      height=\"65.47831mm\"\n      viewBox=\"0 0 216.4144 232.00976\"\n    >\n      <path d=\"M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z\" />\n    </svg>\n  ),\n};\n\nfunction SvgIcon({ type, iconProps }) {\n  const markup = svgGlyphs[type];\n\n  return <span {...iconProps}>{markup}</span>;\n}\n\ninterface IconProps {\n  className?: string;\n  type: Type | string;\n  spaced?: boolean;\n  rightSpaced?: boolean;\n  [key: string]: any;\n}\n\nfunction Icon({\n  className,\n  type,\n  spaced = false,\n  rightSpaced = false,\n  ...props\n}: IconProps): React.ReactElement<typeof SvgIcon> {\n  // eslint-disable-next-line no-console\n  if (!typeAssoc[type]) {\n    console.error(`The type \"${type}\" is not a valid Icon component type`);\n  }\n  const typeClassName = typeAssoc[type] || typeAssoc.question;\n  const isSVG = typeClassName.startsWith('svg');\n  const iconProps = {\n    ...props,\n    className: classnames(className, typeClassName, {\n      'm-icon--spaced': spaced,\n      'm-icon--right-spaced': rightSpaced,\n      'm-icon--svg': isSVG,\n    }),\n  };\n\n  return isSVG ? (\n    <SvgIcon type={type} iconProps={iconProps} />\n  ) : (\n    <i {...iconProps} />\n  );\n}\n\nexport default Icon;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Icon/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_font-awesome';\n\n.m-icon {\n  &--spaced {\n    margin-right: calc($co-margin / 4);\n    margin-left: calc($co-margin / 4);\n  }\n\n  &--right-spaced {\n    margin-right: calc($co-margin / 4);\n  }\n\n  &--svg svg {\n    width: 1em;\n    height: 1em;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/InfiniteScroll/index.jsx",
    "content": "import { Component, Children } from 'react';\nimport PropTypes from 'prop-types';\nimport throttle from 'lodash/throttle';\nimport { addEventListener } from '../../services/event-manager';\n\nconst THROTTLE_INTERVAL = 100;\nconst FOOTER_HEIGHT = 300;\n\nclass InfiniteScroll extends Component {\n  static propTypes = {\n    onReachBottom: PropTypes.func.isRequired,\n    children: PropTypes.node,\n  };\n\n  static defaultProps = {\n    children: undefined,\n  };\n\n  componentDidMount() {\n    this.unesubscribeScrollEvent = addEventListener(\n      'scroll',\n      throttle(\n        () => {\n          const scrollSize =\n            window.scrollY || document.documentElement.scrollTop;\n          const { scrollHeight, clientHeight } = document.documentElement;\n          if (scrollHeight - (clientHeight + scrollSize) <= FOOTER_HEIGHT) {\n            this.props.onReachBottom();\n          }\n        },\n        THROTTLE_INTERVAL,\n        { leading: true, trailing: false }\n      )\n    );\n  }\n\n  componentWillUnmount() {\n    this.unesubscribeScrollEvent();\n  }\n\n  render() {\n    return Children.only(this.props.children);\n  }\n}\n\nexport default InfiniteScroll;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/InputFile/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { v1 as uuidV1 } from 'uuid';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport FieldErrors from '../FieldErrors';\nimport Label from '../Label';\nimport Button from '../Button';\nimport Icon from '../Icon';\n\nimport './style.scss';\n\nfunction InputFile({ onChange, className, accept, errors, multiple, id }) {\n  const actualId = id || uuidV1();\n\n  return (\n    <div className={classnames('m-input-file', className)}>\n      <Label htmlFor={actualId} className=\"m-input-file__label\">\n        <Button\n          className=\"m-input-file__label__button\"\n          icon=\"plus\"\n          shape=\"plain\"\n        />\n        <span className=\"m-input-file__label__text\">\n          <Trans id=\"input-file.add_a_file.label\" message=\"Add a file\" />\n        </span>\n        <span className=\"m-input-file__label__icon\">\n          <Icon type=\"folder\" />\n        </span>\n        <input\n          id={actualId}\n          type=\"file\"\n          name=\"files\"\n          className=\"m-input-file__input\"\n          onChange={onChange}\n          accept={accept}\n          multiple={multiple}\n        />\n      </Label>\n      {errors.length > 0 && <FieldErrors errors={errors} />}\n    </div>\n  );\n}\n\nInputFile.propTypes = {\n  onChange: PropTypes.func.isRequired,\n  multiple: PropTypes.bool,\n  accept: PropTypes.arrayOf(PropTypes.string),\n  errors: PropTypes.arrayOf(PropTypes.node),\n  className: PropTypes.string,\n  id: PropTypes.string,\n};\n\nInputFile.defaultProps = {\n  className: null,\n  errors: null,\n  multiple: false,\n  accept: undefined,\n  id: undefined,\n};\n\nexport default InputFile;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/InputFile/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-clickable';\n@import '../../styles/object/o-form-element';\n@import '../../styles/object/o-hidden-element';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.m-input-file {\n  &__input {\n    @include o-hidden-element--input-file;\n  }\n\n  &__label {\n    @include flex-grid-row;\n    @include o-form-element--light;\n    @include o-hidden-element--label;\n\n    margin-top: $co-margin;\n    overflow: hidden;\n    line-height: $co-component__height;\n    cursor: pointer;\n\n    &__button {\n      @include flex-grid-size(shrink);\n    }\n\n    &__text {\n      @include flex-grid-column;\n\n      text-overflow: ellipsis;\n      white-space: nowrap;\n    }\n\n    &__icon {\n      @include flex-grid-column(shrink);\n\n      margin-left: auto;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/InputFileGroup/components/File/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport Button from '../../../Button';\nimport FileSize from '../../../FileSize';\n\nclass File extends PureComponent {\n  static propTypes = {\n    onRemove: PropTypes.func.isRequired,\n    file: PropTypes.shape({\n      name: PropTypes.string,\n      size: PropTypes.number,\n    }).isRequired,\n  };\n\n  static defaultProps = {};\n\n  render() {\n    const { file, onRemove } = this.props;\n\n    return (\n      <div className=\"m-input-file-group__file\">\n        <Button\n          className=\"m-input-file-group__file__remove\"\n          icon=\"remove\"\n          value={file}\n          onClick={onRemove}\n          shape=\"plain\"\n        />\n        <span className=\"m-input-file-group__file__name\">{file.name}</span>\n        <span className=\"m-input-file-group__file__size\">\n          <FileSize size={file.size} />\n        </span>\n      </div>\n    );\n  }\n}\n\nexport default File;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/InputFileGroup/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Trans, withI18n } from '@lingui/react';\nimport FieldGroup from '../FieldGroup';\nimport InputFile from '../InputFile';\nimport FileSize from '../FileSize';\n\nimport File from './components/File';\n\nimport './style.scss';\n\n@withI18n()\nclass InputFileGroup extends Component {\n  static propTypes = {\n    onInputChange: PropTypes.func.isRequired,\n    errors: PropTypes.shape({}),\n    i18n: PropTypes.shape({\n      _: PropTypes.func,\n    }).isRequired,\n    multiple: PropTypes.bool,\n    fileTypes: PropTypes.arrayOf(PropTypes.string),\n    maxSize: PropTypes.number,\n    className: PropTypes.string,\n    descr: PropTypes.string,\n  };\n\n  static defaultProps = {\n    errors: {},\n    fileTypes: undefined,\n    maxSize: undefined,\n    multiple: false,\n    className: null,\n    descr: null,\n  };\n\n  state = {\n    files: [],\n    fieldErrors: [],\n  };\n\n  handleInputChange = (ev) => {\n    const { onInputChange, multiple } = this.props;\n    const files = ev.target.files.length > 0 ? Array.from(ev.target.files) : [];\n\n    return Promise.all(files.map((file) => this.validate(file)))\n      .then((validatedFiles) => {\n        this.setState({ files: validatedFiles });\n\n        // XXX: input file should always provide array: on unset or on set\n        if (!multiple) {\n          return onInputChange(validatedFiles[0]);\n        }\n\n        return onInputChange(validatedFiles);\n      })\n      .catch((fieldErrors) => this.setState({ fieldErrors }));\n  };\n\n  resetForm = () => {\n    this.setState({\n      files: [],\n      fieldErrors: [],\n    });\n\n    return this.props.onInputChange([]);\n  };\n\n  validate = (file) => {\n    const { i18n, fileTypes, maxSize } = this.props;\n    const errors = [];\n\n    if (!file) {\n      return Promise.reject(\n        i18n._(/* i18n */ 'input-file-group.error.file_is_required', null, {\n          message: 'A file is required',\n        })\n      );\n    }\n\n    const ext = file.name ? `.${file.name.split('.').pop()}` : null;\n    if (fileTypes && (!ext || !fileTypes.includes(ext))) {\n      errors.push(\n        <Trans id=\"input-file-group.error.no_valid_ext\">\n          Only files {fileTypes.join(', ')}\n        </Trans>\n      );\n    }\n\n    if (maxSize && file.size > maxSize) {\n      errors.push(\n        <Trans\n          id=\"input-file-group.error.max_size\"\n          message=\"The file size must be under {filesize}\"\n          values={{\n            filesize: <FileSize size={maxSize} />,\n          }}\n        />\n      );\n    }\n\n    if (errors.length) {\n      return Promise.reject(errors);\n    }\n\n    return Promise.resolve(file);\n  };\n\n  render() {\n    const { errors, descr, className, fileTypes, multiple } = this.props;\n    const allErrors = errors\n      ? Object.keys(errors).map((key) => errors[key])\n      : null;\n    const acceptProp = fileTypes ? { accept: fileTypes } : {};\n\n    return (\n      <FieldGroup\n        className={classnames('m-input-file-group', className)}\n        errors={allErrors}\n      >\n        {descr && <p>{descr}</p>}\n\n        {this.state.files.length > 0 ? (\n          this.state.files.map((file, i) => (\n            <File key={i} file={file} onRemove={this.resetForm} />\n          ))\n        ) : (\n          <InputFile\n            onChange={this.handleInputChange}\n            errors={this.state.fieldErrors}\n            {...acceptProp}\n            multiple={multiple}\n          />\n        )}\n      </FieldGroup>\n    );\n  }\n}\n\nexport default InputFileGroup;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/InputFileGroup/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-clickable';\n@import '../../styles/object/o-form-element';\n@import '../../styles/object/o-hidden-element';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.m-input-file-group {\n  &__file {\n    @include flex-grid-row;\n    @include o-form-element--light;\n\n    margin-top: $co-margin;\n    line-height: $co-component__height;\n\n    &__name {\n      @include flex-grid-column(6);\n\n      overflow: hidden;\n      text-overflow: ellipsis;\n      font-weight: 600;\n      white-space: nowrap;\n    }\n\n    &__size {\n      @include flex-grid-column(4);\n\n      overflow: hidden;\n      text-overflow: ellipsis;\n      color: $co-color__fg__text--low;\n      text-align: right;\n      white-space: nowrap;\n    }\n\n    &__remove {\n      @include flex-grid-size(shrink);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/InputText/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport './style.scss';\n\ninterface Props extends React.InputHTMLAttributes<HTMLInputElement> {\n  expanded?: boolean;\n  theme?: string;\n  bottomSpace?: boolean;\n  hasError?: boolean;\n  className?: string;\n  innerRef: React.ForwardedRef<HTMLInputElement>;\n}\nfunction InputText({\n  className,\n  expanded = false,\n  theme = 'light',\n  bottomSpace = false,\n  hasError = false,\n  innerRef,\n  ...inputProps\n}: Props) {\n  const inputTextClassName = classnames(\n    'm-input-text',\n    {\n      'm-input-text--expanded': expanded,\n      'm-input-text--light': theme === 'light',\n      'm-input-text--dark': theme === 'dark',\n      'm-input-text--contrasted': theme === 'contrasted',\n      'm-input-text--bottom-space': bottomSpace,\n      'm-input-text--error': hasError,\n    },\n    className\n  );\n\n  return (\n    <input\n      type=\"text\"\n      className={inputTextClassName}\n      ref={innerRef}\n      {...inputProps}\n    />\n  );\n}\n\ntype TextFieldProps = Omit<Props, 'innerRef'>;\n\nexport default React.forwardRef<HTMLInputElement, TextFieldProps>(\n  (props, ref) => <InputText {...props} innerRef={ref} />\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/components/InputText/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-form-element';\n\n@mixin input-text {\n  @include o-form-element;\n\n  $baseName: &;\n\n  &--dark {\n    @include o-form-element--dark;\n  }\n\n  &--light {\n    @include o-form-element--light;\n\n    &#{$baseName}--error {\n      border-color: $co-color__alert;\n      box-shadow: initial;\n    }\n  }\n\n  &--contrasted {\n    @include o-form-element--contrasted;\n  }\n\n  &--expanded {\n    width: 100%;\n  }\n\n  &--error {\n    box-shadow: inset 0 0 0 1px $co-color__alert;\n    color: $co-color__fg__text;\n\n    &:focus {\n      color: $co-color__fg__text;\n    }\n  }\n\n  &--bottom-space {\n    margin: 0 0 map_get($co-form__spacing, 'medium');\n  }\n}\n\n.m-input-text {\n  @include input-text;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Label/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nclass Label extends PureComponent {\n  static propTypes = {\n    children: PropTypes.oneOfType([PropTypes.string, PropTypes.node])\n      .isRequired,\n    htmlFor: PropTypes.string.isRequired,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: null,\n  };\n\n  state = {};\n\n  render() {\n    const { className, htmlFor, children } = this.props;\n    const labelClassName = classnames('m-label', className);\n\n    return (\n      <label htmlFor={htmlFor} className={labelClassName}>\n        {children}\n      </label>\n    );\n  }\n}\n\nexport default Label;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Label/style.scss",
    "content": "@import '../../styles/common';\n\n.m-label {\n  color: $co-color__primary;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Link/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport { AllProviders } from 'test/providers';\nimport Link from '.';\n\ndescribe('component Link', () => {\n  it('render', () => {\n    render(<Link to=\"/foo\">Foo</Link>, { wrapper: AllProviders });\n\n    expect(screen.getByText('Foo')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Link/index.tsx",
    "content": "import * as React from 'react';\nimport { Link as BaseLink, LinkProps as BaseLinkProps } from 'react-router-dom';\nimport classnames from 'classnames';\nimport Icon from '../Icon';\nimport './style.scss';\n\ninterface CommonProps {\n  children?: React.ReactNode;\n  center?: boolean;\n  display?: 'inline' | 'inline-block' | 'block' | 'expanded';\n  target?: string;\n  noDecoration?: boolean;\n  className?: string;\n  shape?: 'plain' | 'hollow';\n  button?: boolean;\n  badge?: boolean;\n  active?: boolean;\n  icon?: React.ReactElement | string;\n}\n\ntype ALinkProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & CommonProps;\ntype LinkProps = BaseLinkProps & CommonProps;\ntype Props = ALinkProps | LinkProps;\n\nfunction Link({\n  children,\n  display,\n  noDecoration,\n  className,\n  shape,\n  button,\n  badge,\n  active,\n  target,\n  icon,\n  center = button, // by default, link content is centered when it acts like button\n  ...props\n}: Props) {\n  const linkProps = {\n    ...props,\n    target,\n    // https://mathiasbynens.github.io/rel-noopener/\n    rel: target && 'noopener noreferrer',\n    className: classnames(className, 'm-link', {\n      'm-link--button': button,\n      'm-link--center': center,\n      'm-link--badge': badge,\n      'm-link--active': active,\n      // display\n      'm-link--block': display === 'block',\n      'm-link--expanded': display === 'expanded',\n      'm-link--inline': display === 'inline',\n      'm-link--inline-block': display === 'inline-block',\n      'm-link--text': (!button || display === 'inline') && !noDecoration,\n      // shape\n      'm-link--plain': shape === 'plain',\n      'm-link--hollow': shape === 'hollow',\n    }),\n  };\n\n  const renderIcon = () => {\n    if (React.isValidElement(icon)) {\n      return React.cloneElement(icon, {\n        // @ts-ignore: className not recognized as a known prop\n        className: classnames(icon.props.className, 'm-link__icon'),\n      });\n    }\n\n    // @ts-ignore\n    return <Icon className=\"m-link__icon\" type={icon} />;\n  };\n\n  // @ts-ignore\n  if (props.to) {\n    return (\n      // @ts-ignore\n      <BaseLink {...linkProps}>\n        {icon ? (\n          <>\n            {renderIcon()}\n            <span className=\"m-link__text\">{children}</span>\n          </>\n        ) : (\n          children\n        )}\n      </BaseLink>\n    );\n  }\n\n  return (\n    // @ts-ignore: incompatible prop referrerPolicy …\n    <a {...linkProps}>\n      {icon ? (\n        <>\n          {renderIcon()}\n          <span className=\"m-link__text\">{children}</span>\n        </>\n      ) : (\n        children\n      )}\n    </a>\n  );\n}\n\nexport default Link;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Link/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-clickable';\n\n.m-link {\n  @include o-clickable;\n\n  &--button {\n    @include o-clickable--button;\n  }\n\n  // display: inline, inline-block or expanded :\n  &--block {\n    @include o-clickable--button;\n\n    width: 100%;\n  }\n\n  &--center {\n    text-align: center;\n  }\n\n  &--expanded {\n    @include o-clickable--button;\n    @include o-clickable--expanded;\n\n    height: 100%;\n  }\n\n  &--inline {\n    @include o-clickable--inline;\n  }\n\n  &--inline-block {\n    @include o-clickable--button;\n  }\n\n  &--text {\n    @include o-clickable--text;\n  }\n\n  &--badge {\n    padding-right: $co-radius * 2;\n    padding-left: $co-radius * 2;\n    border-radius: $co-radius;\n    background: $co-color__primary--low;\n    color: $co-color__contrast__text;\n    text-decoration: none;\n\n    &:focus,\n    &:visited,\n    &:hover {\n      background: $co-color__primary;\n      color: $co-color__contrast__text;\n    }\n  }\n\n  &--plain {\n    @include o-clickable--plain;\n  }\n\n  &--hollow {\n    @include o-clickable--hollow;\n  }\n\n  &--active {\n    @include o-clickable--active-text;\n  }\n\n  &__text {\n    margin-left: calc($co-margin / 4);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/MenuBar/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport NavList, { NavItem } from '../NavList';\nimport Link from '../Link';\nimport './style.scss';\n\nclass MenuBar extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n    children: PropTypes.node,\n    navLinks: PropTypes.arrayOf(\n      PropTypes.shape({\n        key: PropTypes.string.isRequired,\n        label: PropTypes.node.isRequired,\n        title: PropTypes.string,\n        isActive: PropTypes.bool,\n        to: PropTypes.string.isRequired,\n      })\n    ),\n  };\n\n  static defaultProps = {\n    className: null,\n    children: null,\n    navLinks: null,\n  };\n\n  render() {\n    const { className, navLinks, children } = this.props;\n    const menuBarClassName = classnames('m-menu-bar', className);\n\n    return (\n      <div className={menuBarClassName}>\n        {navLinks && (\n          <NavList className=\"m-menu-bar__navlist\">\n            {navLinks.map((link) => (\n              <NavItem active={link.isActive} large key={link.key}>\n                <Link\n                  display=\"button\"\n                  noDecoration\n                  title={link.title}\n                  to={link.to}\n                >\n                  {link.label}\n                </Link>\n              </NavItem>\n            ))}\n          </NavList>\n        )}\n        {!navLinks && children}\n      </div>\n    );\n  }\n}\n\nexport default MenuBar;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/MenuBar/style.scss",
    "content": "@import '../../styles/common';\n\n.m-menu-bar {\n  background: $co-color__fg__back;\n\n  &__navlist {\n    padding-left: $co-margin;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/MessageDate/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/MessageDate/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport Moment from 'moment';\nimport './style.scss';\n\nclass MessageDate extends PureComponent {\n  static propTypes = {\n    dateTime: PropTypes.instanceOf(Moment).isRequired,\n  };\n\n  static defaultProps = {};\n\n  renderDate = () => {\n    const { dateTime } = this.props;\n\n    const diffDays = Moment().diff(dateTime, 'days');\n    const diffYear = Moment().diff(dateTime, 'years');\n\n    switch (true) {\n      default:\n      case diffDays === 0:\n        return dateTime.format('LT'); // 11:00\n      case diffDays > 0 && diffDays <= 7:\n        return dateTime.format('dddd'); // monday\n      case diffYear === 0 && diffDays > 7:\n        return dateTime.format('LL'); // 'March 22, 2018'\n      case diffYear >= 1:\n        return dateTime.format('YYYY'); // 2017\n    }\n  };\n\n  render() {\n    const { dateTime } = this.props;\n\n    return (\n      <time title={dateTime.format('LLL')} dateTime={dateTime.format('')}>\n        <span className=\"m-message-date__date\">{this.renderDate()}</span>\n      </time>\n    );\n  }\n}\n\nexport default MessageDate;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/MessageDate/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.m-message-date {\n  &__time {\n    display: block;\n  }\n\n  @include breakpoint(medium) {\n    &__time {\n      display: inline;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Modal/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport ReactModal from 'react-modal';\nimport classnames from 'classnames';\nimport Button from '../Button';\nimport './style.scss';\n\nclass Modal extends Component {\n  static propTypes = {\n    className: PropTypes.string,\n    title: PropTypes.node,\n    children: PropTypes.node,\n    onClose: PropTypes.func,\n  };\n\n  static defaultProps = {\n    className: undefined,\n    title: undefined,\n    children: undefined,\n    onClose: () => {\n      // noop\n    },\n  };\n\n  componentDidMount() {\n    ReactModal.setAppElement('#root');\n  }\n\n  render() {\n    const { className, title, children, onClose, ...props } = this.props;\n\n    return (\n      <ReactModal\n        className={classnames('m-modal', className)}\n        overlayClassName=\"m-modal__overlay\"\n        {...props}\n      >\n        <header className=\"m-modal__header\">\n          {title && <div className=\"m-modal__title\">{title}</div>}\n          <Button className=\"m-modal__close\" onClick={onClose} icon=\"remove\" />\n        </header>\n        <div className=\"m-modal__content\">{children}</div>\n      </ReactModal>\n    );\n  }\n}\n\nexport default Modal;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Modal/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$m-modal__overlay-background: rgb(0 0 0 / 75%);\n\n.m-modal {\n  position: absolute;\n  top: 50%;\n  right: auto;\n  bottom: auto;\n  left: 50%;\n  margin-right: -50%;\n  transform: translate(-50%, -50%);\n  background: $co-color__fg__back;\n\n  &__content {\n    padding: map_get($co-form__spacing, 'medium');\n  }\n\n  &__header {\n    @include flex-grid-row($size: expanded);\n\n    border-bottom: 1px solid $co-color__bg__back;\n  }\n\n  &__title {\n    @include flex-grid-column(shrink);\n\n    margin: 0;\n    padding: map_get($co-form__spacing, 'small')\n      map_get($co-form__spacing, 'medium');\n    color: $co-color__fg__text--high;\n    font-weight: 700;\n  }\n\n  &__close {\n    @include flex-grid-size(shrink);\n\n    margin-left: auto; // flew way to align right\n    padding: 0 map_get($co-form__spacing, 'medium');\n    background: none;\n\n    &:hover {\n      background: none;\n    }\n  }\n\n  &__overlay {\n    position: fixed;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    z-index: $l-z-index__modal-overlay;\n    background-color: $m-modal__overlay-background;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/NavList/components/NavItem.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\n\ninterface Props {\n  className?: string;\n  active?: boolean;\n  large?: boolean;\n  children?: React.ReactNode;\n}\nfunction NavItem({\n  className,\n  active = false,\n  large = false,\n  ...props\n}: Props): JSX.Element {\n  const itemClassName = classnames(\n    'm-nav-list__item',\n    {\n      'm-nav-list__item--large': large,\n      'm-nav-list__item--active': active,\n    },\n    className\n  );\n\n  return <li className={itemClassName} {...props} />;\n}\n\nexport default NavItem;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/NavList/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport NavList, { NavItem } from '.';\n\ndescribe('component NavList', () => {\n  it('render', () => {\n    render(\n      <NavList>\n        {[<NavItem key=\"0\">Foo</NavItem>, <NavItem key=\"1\">Bar</NavItem>]}\n      </NavList>\n    );\n\n    expect(screen.getByText('Foo')).toBeInTheDocument();\n    expect(screen.getByText('Bar')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/NavList/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport NavItem from './components/NavItem';\nimport './style.scss';\n\ninterface Props {\n  className?: string;\n  dir?: 'vertical';\n  children?: React.ReactNode;\n}\nfunction NavList({ className, dir, ...props }: Props): JSX.Element {\n  const navClassName = classnames('m-nav-list', {\n    'm-nav-list--vertical': dir === 'vertical',\n  });\n\n  return (\n    <nav className={classnames('m-nav', className)}>\n      <ul className={navClassName} {...props} />\n    </nav>\n  );\n}\n\nexport { NavItem };\n\nexport default NavList;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/NavList/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.m-nav {\n  display: block;\n}\n\n.m-nav-list {\n  $baseClassName: &;\n  @include flex-grid-row;\n\n  list-style: none;\n  margin: 0;\n  padding: 0;\n\n  &__item {\n    @include flex-grid-column(shrink);\n\n    line-height: $co-font__line-height;\n\n    &--large {\n      line-height: $co-component__height;\n    }\n\n    &--active {\n      font-weight: 600;\n    }\n  }\n\n  &--vertical {\n    flex-direction: column;\n\n    #{$baseClassName}__item {\n      @include flex-grid-size(12);\n\n      margin-bottom: map_get($co-form__spacing, 'small');\n      padding-right: 0;\n      padding-left: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PageTitle/index.jsx",
    "content": "import { createSelector } from 'reselect';\nimport { connect } from 'react-redux';\nimport { userSelector } from '../../modules/user';\nimport { getConfig } from '../../services/config';\n\nimport Presenter from './presenter';\n\nconst { hostname } = getConfig();\nconst mapStateToProps = createSelector([userSelector], (user) => ({\n  user,\n  hostname,\n}));\n\nexport default connect(mapStateToProps)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PageTitle/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport DocumentTitle from 'react-document-title';\n\nconst DEFAULT_DOCUMENT_TITLE = 'Caliopen, be good!';\n\nclass PageTitle extends PureComponent {\n  static propTypes = {\n    user: PropTypes.shape({\n      name: PropTypes.string,\n    }),\n    // FIXME always undefined\n    currentTab: PropTypes.shape({\n      routeConfig: PropTypes.shape({\n        tab: PropTypes.shape({\n          renderLabel: PropTypes.func,\n        }),\n      }),\n    }),\n    title: PropTypes.string,\n    hostname: PropTypes.string,\n  };\n\n  static defaultProps = {\n    title: '',\n    hostname: 'unknown',\n    currentTab: undefined,\n    user: undefined,\n  };\n\n  render() {\n    const { user, title, currentTab, hostname } = this.props;\n    const userName = user ? `${user.name}@${hostname} - ` : '';\n    const currentTabLabel = currentTab\n      ? `${currentTab.routeConfig.tab.renderLabel()} - `\n      : '';\n    const pageTitle = title ? `${title} - ` : currentTabLabel;\n    const documentTitle = `${pageTitle}${userName}${DEFAULT_DOCUMENT_TITLE}`;\n\n    return <DocumentTitle title={documentTitle} />;\n  }\n}\n\nexport default PageTitle;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/ParticipantsIconLetter/index.jsx",
    "content": "// back from archives: https://github.com/CaliOpen/Caliopen/blob/1ca94e1443ae05807a073e8ddd41d2ad2541a7c6/src/frontend/web_application/src/scenes/DiscussionList/components/ParticipantsIcon/index.jsx\nimport React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { AvatarLetter } from '../../modules/avatar';\nimport './style.scss';\n\nclass ParticipantsIconLetter extends PureComponent {\n  static propTypes = {\n    labels: PropTypes.arrayOf(PropTypes.string).isRequired,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  render() {\n    const { className, labels } = this.props;\n    const hasMore = labels.length > 1;\n    const iconClass = 'm-participants-icon__letter--1';\n\n    return (\n      <div className={classnames(className, 'm-participants-icon')}>\n        <AvatarLetter\n          className={classnames('m-participants-icon__letter', iconClass)}\n          word={hasMore ? '+' : labels[0]}\n        />\n      </div>\n    );\n  }\n}\n\nexport default ParticipantsIconLetter;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/ParticipantsIconLetter/style.scss",
    "content": "@import '../../styles/common';\n\n$m-participants-icon__size: 25px;\n$m-participants-icon__padding: 0;\n\n.m-participants-icon {\n  display: inline-block;\n  vertical-align: baseline;\n  width: $m-participants-icon__size;\n  height: $m-participants-icon__size;\n  border-radius: 50%;\n  overflow: hidden;\n\n  &__letter {\n    &::before {\n      display: block;\n      float: left;\n      text-align: center;\n    }\n  }\n\n  &__letter--1 {\n    &::before {\n      width: $m-participants-icon__size;\n      height: $m-participants-icon__size;\n      line-height: $m-participants-icon__size;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PasswordStrength/index.tsx",
    "content": "import { useLingui } from '@lingui/react';\nimport * as React from 'react';\nimport classnames from 'classnames';\nimport Icon from '../Icon';\nimport './style.scss';\n\ninterface Props {\n  strength: number;\n  className?: string;\n}\nfunction PasswordStrength({ strength, className }: Props) {\n  const { i18n } = useLingui();\n\n  const feedbacks = {\n    weak: i18n._(/* i18n */ 'password_strength.feedback.weak', undefined, {\n      message: 'Strength: weak',\n    }),\n    moderate: i18n._(\n      /* i18n */ 'password_strength.feedback.moderate',\n      undefined,\n      {\n        message: 'Strength: moderate',\n      }\n    ),\n    strong: i18n._(/* i18n */ 'password_strength.feedback.strong', undefined, {\n      message: 'Strength: strong',\n    }),\n  };\n\n  const classNameModifiers = {\n    weak: 'm-password-strength--weak',\n    moderate: 'm-password-strength--moderate',\n    strong: 'm-password-strength--strong',\n  };\n\n  let key;\n  switch (true) {\n    default:\n    case strength <= 1:\n      key = 'weak';\n      break;\n    case strength >= 2 && strength <= 3:\n      key = 'moderate';\n      break;\n    case strength >= 4:\n      key = 'strong';\n      break;\n  }\n  const passwordStrengthClassName = classnames(\n    'm-password-strength',\n    classNameModifiers[key],\n    className\n  );\n\n  return (\n    <div className={passwordStrengthClassName}>\n      <div className=\"m-password-strength__graph\">\n        <Icon type=\"lock\" className=\"m-password-strength__icon\" />\n        <div className=\"m-password-strength__guide\">\n          <div className=\"m-password-strength__bar\" />\n        </div>\n        <span className=\"m-password-strength__text\">{feedbacks[key]}</span>\n      </div>\n    </div>\n  );\n}\n\nexport default PasswordStrength;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PasswordStrength/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/config/config';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n// Graph presets\n$bar-width-weak: 15%;\n$bar-width-moderate: 50%;\n$bar-width-strong: 100%;\n$bar-height: 3px;\n$bar-margin: 7px;\n$color-weak: map_get($caliopen-palette, 'alert');\n$color-moderate: map_get($caliopen-palette, 'warning');\n$color-strong: map_get($caliopen-palette, 'success');\n\n.m-password-strength {\n  &__graph {\n    @include flex-grid-row($size: expand);\n  }\n\n  &__icon {\n    @include flex-grid-size(shrink);\n\n    padding-right: $bar-margin;\n  }\n\n  &__guide {\n    @include flex-grid-size;\n\n    height: $bar-height;\n    margin-top: $bar-margin;\n    background-color: $co-color__fg__back--lower;\n  }\n\n  &__bar {\n    height: $bar-height;\n  }\n\n  &--weak {\n    color: $color-weak;\n\n    .m-password-strength__bar {\n      width: $bar-width-weak;\n      background-color: $color-weak;\n    }\n  }\n\n  &--moderate {\n    color: $color-moderate;\n\n    .m-password-strength__bar {\n      width: $bar-width-moderate;\n      background-color: $color-moderate;\n    }\n  }\n\n  &--strong {\n    color: $color-strong;\n\n    .m-password-strength__bar {\n      width: $bar-width-strong;\n      background-color: $color-strong;\n    }\n  }\n\n  &__text {\n    @include flex-grid-size(12);\n\n    padding-left: map_get($co-form__spacing, 'medium');\n    font-size: $co-font__size--xsmall;\n    font-weight: 700;\n    text-transform: uppercase;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PiBar/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PiBar/presenter.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport Icon from '../Icon';\nimport './style.scss';\n\nfunction PiBar({ level, className }) {\n  const classNameModifiers = {\n    disabled: 'm-pi-bar--disabled',\n    ugly: 'm-pi-bar--ugly',\n    bad: 'm-pi-bar--bad',\n    good: 'm-pi-bar--good',\n    super: 'm-pi-bar--super',\n  };\n  const disabledLevel = 50;\n\n  let key;\n  switch (true) {\n    default:\n    case !level && level !== 0:\n      key = 'disabled';\n      break;\n    case level <= 20:\n      key = 'ugly';\n      break;\n    case level > 20 && level <= 50:\n      key = 'bad';\n      break;\n    case level > 50 && level <= 80:\n      key = 'good';\n      break;\n    case level > 80:\n      key = 'super';\n      break;\n  }\n\n  const barStyle = {\n    width: `${!level && level !== 0 ? disabledLevel : level}%`,\n  };\n\n  const piBarClassName = classnames(\n    'm-pi-bar',\n    classNameModifiers[key],\n    className\n  );\n\n  return (\n    <div className={piBarClassName}>\n      <div className=\"m-pi-bar__graph\">\n        <Icon type=\"shield\" className=\"m-pi-bar__icon\" />\n        <div className=\"m-pi-bar__guide\">\n          <div className=\"m-pi-bar__bar\" style={barStyle} />\n        </div>\n        <div className=\"m-pi-bar__level\">{level}</div>\n      </div>\n    </div>\n  );\n}\n\nPiBar.propTypes = {\n  level: PropTypes.number,\n  className: PropTypes.string,\n};\nPiBar.defaultProps = {\n  level: undefined,\n  className: undefined,\n};\n\nexport default PiBar;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PiBar/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/config/config';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n// Graph presets\n$bar-height: 3px;\n$bar-margin: 7px;\n\n.m-pi-bar {\n  &__graph {\n    @include flex-grid-row;\n  }\n\n  &__icon {\n    @include flex-grid-size(shrink);\n\n    margin-right: map_get($co-form__spacing, 'medium');\n  }\n\n  &__level {\n    @include flex-grid-size(shrink);\n\n    margin-left: map_get($co-form__spacing, 'medium');\n    font-size: $co-font__size--xsmall;\n    font-weight: 700;\n  }\n\n  &__guide {\n    @include flex-grid-size;\n\n    height: $bar-height;\n    margin-top: $bar-margin;\n    background-color: $co-color__fg__back--lower;\n  }\n\n  &__bar {\n    height: $bar-height;\n    background-color: $co-color__fg__text--lower;\n  }\n\n  @each $level, $color in $privacy-palette {\n    &--#{$level} {\n      color: $color;\n\n      .m-pi-bar__bar {\n        background-color: $color;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PlaceholderBlock/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nclass PlaceholderBlock extends PureComponent {\n  static propTypes = {\n    children: PropTypes.node,\n    className: PropTypes.string,\n    shape: PropTypes.oneOf(['round', 'line', 'rect', 'square', 'avatar']),\n    size: PropTypes.oneOf(['small', 'default', 'large']), // XXX: for now only available for avatar\n    width: PropTypes.oneOf(['small', 'default', 'large', 'xlarge']), // available only when display is inline-block\n    display: PropTypes.oneOf(['inline-block', 'block']),\n  };\n\n  static defaultProps = {\n    children: ' ',\n    className: undefined,\n    shape: 'rect',\n    size: 'default',\n    width: 'default',\n    display: 'block',\n  };\n\n  render() {\n    const { className, shape, display, size, width, ...props } = this.props;\n    const modifiers = {\n      // 'm-placeholder-block--round': shape === 'round',\n      'm-placeholder-block--line': shape === 'line',\n      'm-placeholder-block--square': shape === 'square',\n      'm-placeholder-block--rect': shape === 'rect',\n      'm-placeholder-block--avatar': shape === 'avatar',\n      'm-placeholder-block--small': size === 'small',\n      'm-placeholder-block--large': size === 'large',\n      'm-placeholder-block--w-small': width === 'small',\n      'm-placeholder-block--w-large': width === 'large',\n      'm-placeholder-block--w-xlarge': width === 'xlarge',\n      'm-placeholder-block--inline-block': display === 'inline-block',\n    };\n\n    return (\n      <div\n        className={classnames(className, 'm-placeholder-block', modifiers)}\n        {...props}\n      />\n    );\n  }\n}\n\nexport default PlaceholderBlock;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PlaceholderBlock/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-avatar';\n\n.m-placeholder-block {\n  $baseName: &;\n\n  height: $co-component__height;\n  border-radius: $co-radius;\n  background-color: $co-color__fg__back--lower;\n\n  &--line {\n    height: $co-font__size--xsmall;\n    border-radius: ($co-font__size--xsmall / 2);\n  }\n\n  &--round {\n    width: $co-component__height;\n    border-radius: 50%;\n  }\n\n  &--square {\n    display: inline-block;\n    min-width: $co-component__height;\n  }\n\n  &--rect {\n    min-width: 4 * $co-component__height;\n  }\n\n  &--avatar {\n    @include m-avatar--size(medium);\n\n    border-radius: 50%;\n\n    &#{$baseName}--large {\n      @include m-avatar--size(large);\n    }\n\n    &#{$baseName}--small {\n      @include m-avatar--size(small);\n    }\n  }\n\n  &--inline-block {\n    display: inline-block;\n    min-width: 5rem;\n\n    // not sure about that\n    // FIXME: fail to compile with sass-dart\n    // &:not():first-of-type {\n    //   margin-left: map_get($co-margin_, 'xsmall');\n    // }\n  }\n\n  &--w-small {\n    min-width: 3rem;\n  }\n\n  &--w-large {\n    min-width: 10rem;\n  }\n\n  &--w-xlarge {\n    min-width: 30rem;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PlaceholderList/index.scss",
    "content": "@import '../../styles/common';\n\n.m-placeholder-list__item {\n  border-top: 2px solid $co-color__fg__border--higher;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/PlaceholderList/index.tsx",
    "content": "import * as React from 'react';\nimport PlaceholderBlock from '../PlaceholderBlock';\nimport './index.scss';\n\nconst renderDefault = () => (\n  <PlaceholderBlock className=\"m-placeholder-list__item\" />\n);\ninterface Props {\n  count?: number;\n  title: string;\n  renderItem: () => React.ReactNode;\n}\nexport default function PlaceholderList({\n  count = 5,\n  title,\n  renderItem = renderDefault,\n}: Props) {\n  const items: number[] = [];\n  for (let i = 0; i < count; i++) {\n    items.push(i);\n  }\n\n  return (\n    <ul title={title} aria-busy>\n      {items.map((n) => (\n        <div key={n}>{renderItem()}</div>\n      ))}\n    </ul>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/RadioFieldGroup/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { v1 as uuidV1 } from 'uuid';\nimport classnames from 'classnames';\nimport Label from '../Label';\nimport FieldGroup from '../FieldGroup';\n\nimport './style.scss';\n\nconst alphaNumPropType = PropTypes.oneOfType([\n  PropTypes.string,\n  PropTypes.number,\n]);\n\nclass RadioFieldGroup extends PureComponent {\n  static propTypes = {\n    name: PropTypes.string.isRequired,\n    value: alphaNumPropType,\n    onChange: PropTypes.func,\n    options: PropTypes.arrayOf(PropTypes.shape({})),\n    errors: PropTypes.arrayOf(PropTypes.node),\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    value: undefined,\n    onChange: undefined,\n    options: [],\n    errors: [],\n    className: undefined,\n  };\n\n  renderOption = (option, key) => {\n    const id = uuidV1();\n    const { name, value, onChange } = this.props;\n\n    return (\n      <div key={key} className=\"m-radio-field-group__entry\">\n        <input\n          id={id}\n          className=\"m-radio-field-group__input\"\n          type=\"radio\"\n          name={name}\n          checked={value === option.value}\n          value={option.value}\n          onChange={onChange}\n        />{' '}\n        <Label className=\"m-radio-field-group__label\" htmlFor={id}>\n          {option.label}\n        </Label>\n      </div>\n    );\n  };\n\n  render() {\n    const { options, errors, className } = this.props;\n\n    return (\n      <FieldGroup\n        errors={errors}\n        className={classnames('m-radio-field-group', className)}\n      >\n        {options.length > 0 && options.map(this.renderOption)}\n      </FieldGroup>\n    );\n  }\n}\n\nexport default RadioFieldGroup;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/RadioFieldGroup/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport RadioFieldGroup from '.';\n\ndescribe('component RadioFieldGroup', () => {\n  it('render', () => {\n    const props = {\n      name: 'my_radio',\n      options: [\n        { label: 'a', value: 1 },\n        { label: 'b', value: 3 },\n      ],\n      value: 3,\n      onChange: jest.fn(),\n    };\n\n    const comp = shallow(<RadioFieldGroup {...props} />);\n\n    expect(comp.find('input').length).toEqual(2);\n    expect(comp.find('input[value=3]').prop('checked')).toEqual(true);\n    comp.find('input[value=1]').simulate('change');\n    expect(props.onChange).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/RadioFieldGroup/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$co-radio-size: 14px;\n\n.m-radio-field-group {\n  &__entry {\n    @include flex-grid-row;\n\n    align-items: center;\n\n    & > .m-radio-field-group__entry {\n      margin-bottom: $co-component__spacing;\n    }\n  }\n\n  &__label {\n    @include flex-grid-column(shrink);\n\n    cursor: pointer;\n  }\n\n  &__input {\n    @include flex-grid-size(shrink);\n\n    width: $co-radio-size;\n    height: $co-radio-size;\n    appearance: none;\n    border: 1px solid $co-color__primary;\n    border-radius: 50%;\n    background: $co-color__fg__back;\n    cursor: pointer;\n\n    &::before {\n      display: none;\n      content: ' ';\n      position: absolute;\n      right: 1px;\n      left: 1px;\n      border-radius: 50%;\n      background: $co-color__primary--low;\n    }\n\n    &:checked {\n      position: relative;\n\n      &::before {\n        display: block;\n        top: 1px;\n        bottom: 1px;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/RawButton/index.tsx",
    "content": "import * as React from 'react';\n\ninterface Props extends React.ComponentProps<'button'> {\n  children: React.ReactNode;\n  innerRef: React.ForwardedRef<HTMLButtonElement>;\n}\nfunction RawButton({\n  type = 'button',\n  innerRef,\n  children,\n  ...buttonProps\n}: Props) {\n  // this rules does not understand vars (2019-04-05)\n  return (\n    // eslint-disable-next-line react/button-has-type\n    <button type={type} {...buttonProps} ref={innerRef}>\n      {children}\n    </button>\n  );\n}\n\ntype ButtonProps = Omit<Props, 'innerRef'>;\nexport default React.forwardRef<HTMLButtonElement, ButtonProps>(\n  (props, ref) => <RawButton {...props} innerRef={ref} />\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Section/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport Title from '../Title';\nimport './style.scss';\n\nclass Section extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n    title: PropTypes.node,\n    titleProps: PropTypes.shape({}),\n    descr: PropTypes.node,\n    hasSeparator: PropTypes.bool,\n    children: PropTypes.node,\n    borderContext: PropTypes.oneOf(['disabled', 'safe', 'public', 'unsecure']),\n    shape: PropTypes.oneOf(['plain', 'none']),\n  };\n\n  static defaultProps = {\n    className: undefined,\n    title: undefined,\n    titleProps: {},\n    descr: undefined,\n    hasSeparator: false,\n    children: undefined,\n    borderContext: undefined,\n    shape: 'plain',\n  };\n\n  render() {\n    const {\n      title,\n      titleProps,\n      descr,\n      children,\n      hasSeparator,\n      className,\n      borderContext,\n      shape,\n    } = this.props;\n\n    const borderModifier = borderContext && {\n      'm-section--border-disabled': borderContext === 'disabled',\n      'm-section--border-safe': borderContext === 'safe',\n      'm-section--border-public': borderContext === 'public',\n      'm-section--border-unsecure': borderContext === 'unsecure',\n    };\n\n    const modifiers = {\n      'm-section--plain': shape === 'plain',\n      'm-section--separator': hasSeparator,\n    };\n\n    return (\n      <section\n        className={classnames(\n          'm-section',\n          modifiers,\n          borderModifier,\n          className\n        )}\n      >\n        {(title || descr) && (\n          <header className=\"m-section__header\">\n            {title && (\n              <Title className=\"m-section__title\" {...titleProps}>\n                {title}\n              </Title>\n            )}\n            {descr && <p className=\"m-section__descr\">{descr}</p>}\n          </header>\n        )}\n        {children}\n      </section>\n    );\n  }\n}\n\nexport default Section;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Section/style.scss",
    "content": "@import '../../styles/common';\n\n$section_border-width: 5px;\n\n.m-section {\n  padding: map_get($co-form__spacing, 'medium');\n\n  &--plain {\n    background-color: $co-color__bg__back--higher;\n  }\n\n  &--separator {\n    border-bottom: 1px solid $co-color__bg__back;\n  }\n\n  @each $name, $color in $context-palette {\n    &--border-#{$name} {\n      border-left: $section_border-width solid $color;\n    }\n  }\n\n  &__header {\n    margin-bottom: map_get($co-form__spacing, 'medium');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/SelectFieldGroup/index.spec.tsx",
    "content": "import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport * as React from 'react';\nimport SelectFieldGroup from '.';\n\ndescribe('component SelectFieldGroup', () => {\n  it('should render', () => {\n    const onValueChanged = jest.fn();\n    const props = {\n      label: 'Foo',\n      options: [\n        { label: 'a', value: 1 },\n        { label: 'b', value: 3 },\n      ],\n      value: '',\n      onChange: (ev) => {\n        onValueChanged(ev?.target.value);\n      },\n    };\n\n    render(<SelectFieldGroup {...props} />);\n\n    const combobox = screen.getByRole('combobox', { name: 'Foo' });\n    expect(combobox).toBeInTheDocument();\n    userEvent.selectOptions(combobox, screen.getByText('b'));\n\n    expect(onValueChanged).toHaveBeenCalledWith('3');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/SelectFieldGroup/index.tsx",
    "content": "import * as React from 'react';\nimport { FieldProps } from 'formik';\nimport { v1 as uuidV1 } from 'uuid';\nimport classnames from 'classnames';\nimport Label from '../Label';\nimport FieldGroup from '../FieldGroup';\n\nimport './style.scss';\n\nconst noop = () => {};\ninterface Props extends React.InputHTMLAttributes<HTMLSelectElement> {\n  label?: React.ReactNode;\n  showLabelforSr?: boolean;\n  expanded?: boolean;\n  options?: { label: string | number; value: string | number }[];\n  errors?: React.ReactNodeArray;\n  className?: string;\n}\nfunction SelectFieldGroup({\n  id = uuidV1(),\n  label,\n  showLabelforSr = false,\n  value,\n  expanded = false,\n  options = [],\n  errors = [],\n  onChange = noop,\n  className,\n  ...props\n}: Props): React.ReactElement<typeof FieldGroup> {\n  const selectWrapperClassName = classnames(\n    'm-select-field-group__select-wrapper',\n    {\n      'm-select-field-group--expanded__select-wrapper': expanded,\n    }\n  );\n  const labelClassName = classnames('m-select-field-group__label', {\n    'show-for-sr': showLabelforSr,\n  });\n\n  return (\n    <FieldGroup\n      className={classnames('m-select-field-group', className)}\n      errors={errors}\n    >\n      <Label htmlFor={id} className={labelClassName}>\n        {label}\n      </Label>\n      <div className={selectWrapperClassName}>\n        <select\n          onChange={onChange}\n          className=\"m-select-field-group__select\"\n          id={id}\n          value={value}\n          {...props}\n        >\n          {options.map((selectOption) => (\n            <option key={selectOption.label} value={selectOption.value}>\n              {selectOption.label}\n            </option>\n          ))}\n        </select>\n      </div>\n    </FieldGroup>\n  );\n}\n\nexport default SelectFieldGroup;\n\ntype FormikSelectFieldGroupProps = FieldProps & Props;\n\nexport function FormikSelectFieldGroup({\n  id,\n  label,\n  field,\n  form,\n  meta,\n  ...props\n}: FormikSelectFieldGroupProps): React.ReactElement<Props> {\n  const inputProps = { ...props, ...field };\n  return (\n    <SelectFieldGroup\n      id={id}\n      label={label}\n      errors={meta?.error ? [meta.error] : undefined}\n      {...inputProps}\n    />\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/SelectFieldGroup/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-form-element';\n\n.m-select-field-group {\n  &__label {\n    display: block;\n    margin-bottom: $co-component__spacing;\n  }\n\n  &__select-wrapper {\n    @include o-form-element-select-wrapper;\n  }\n\n  &__select {\n    @include o-form-element;\n    @include o-form-element-select;\n  }\n\n  &--dark {\n    &__select-wrapper,\n    &__select {\n      @include o-form-element--dark;\n    }\n  }\n\n  &,\n  &--light {\n    &__select {\n      @include o-form-element--light;\n    }\n  }\n\n  &--expanded {\n    &__select-wrapper {\n      width: 100%;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/SidebarLayout/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nclass SidebarLayout extends PureComponent {\n  static propTypes = {\n    sidebar: PropTypes.node,\n    children: PropTypes.node,\n    className: PropTypes.string,\n    sidebarClassName: PropTypes.string,\n    panelClassName: PropTypes.string,\n  };\n\n  static defaultProps = {\n    sidebar: null,\n    children: null,\n    className: undefined,\n    sidebarClassName: undefined,\n    panelClassName: undefined,\n  };\n\n  render() {\n    const { className, sidebarClassName, panelClassName, sidebar, children } =\n      this.props;\n\n    return (\n      <div className={classnames(className, 'm-sidebar-layout')}>\n        <div className={classnames(sidebarClassName, 'm-sidebar-layout__left')}>\n          {sidebar}\n        </div>\n        <div className={classnames(panelClassName, 'm-sidebar-layout__panel')}>\n          {children}\n        </div>\n      </div>\n    );\n  }\n}\n\nexport default SidebarLayout;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/SidebarLayout/style.scss",
    "content": "@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.m-sidebar-layout {\n  @include flex-grid-row;\n\n  background: $co-color__bg__back;\n\n  &__left {\n    @include flex-grid-size(12);\n\n    background-color: $co-color__sidebar__back;\n  }\n\n  &__panel {\n    @include flex-grid-size(12);\n\n    background-color: $co-color__fg__back;\n  }\n\n  @include breakpoint(medium) {\n    &__left {\n      @include flex-grid-size(shrink);\n    }\n\n    &__panel {\n      @include flex-grid-size;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Spinner/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport { AllProviders } from 'test/providers';\nimport Spinner from '.';\n\ndescribe('component Spinner', () => {\n  it('render', () => {\n    render(<Spinner svgTitleId=\"page-spinner\" isLoading />, {\n      wrapper: AllProviders,\n    });\n\n    expect(screen.getByText('Loading …')).toBeInTheDocument();\n  });\n\n  it('does not render', () => {\n    render(<Spinner svgTitleId=\"page-spinner\" isLoading={false} />, {\n      wrapper: AllProviders,\n    });\n\n    expect(screen.queryByText('Loading …')).not.toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Spinner/index.tsx",
    "content": "import * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport classnames from 'classnames';\nimport './style.scss';\n\ninterface Props extends withI18nProps {\n  svgTitleId: string;\n  isLoading?: boolean;\n  height?: number;\n  width?: number;\n  display?: 'inline' | 'inline-block';\n  theme?: 'default' | 'bright';\n  className?: string;\n}\n\nfunction Spinner({\n  svgTitleId,\n  isLoading = true,\n  height = 14,\n  width = 14,\n  display = 'inline-block',\n  theme = 'default',\n  className,\n  i18n,\n}: Props) {\n  const spinnerProps = {\n    className: classnames(className, 'm-spinner', {\n      'm-spinner--inline-block': display === 'inline-block',\n      'm-spinner--bright': theme === 'bright',\n    }),\n  };\n\n  if (!isLoading) {\n    return null;\n  }\n\n  return (\n    <span {...spinnerProps}>\n      <span className=\"m-spinner__icon\" aria-busy>\n        <svg\n          viewBox=\"0 0 640 640\"\n          height={height}\n          width={width}\n          aria-labelledby={svgTitleId}\n        >\n          <title id={svgTitleId}>\n            {i18n._(/* i18n */ 'spinner.loading', undefined, {\n              message: 'Loading …',\n            })}\n          </title>\n          <path d=\"m 590.54755,358.9064 c -6.67713,-1.73495 -16.85815,-7.99709 -20.47315,-12.5926 -7.73885,-9.83791 -9.03406,-14.17323 -10.21759,-34.20013 -0.60673,-10.26709 -2.00516,-24.36473 -3.1076,-31.32807 -3.59526,-22.7089 -14.25471,-54.93433 -22.1366,-66.92295 -1.62271,-2.46821 -5.65778,-9.2689 -8.96681,-15.11265 C 508.97576,169.31072 481.67649,140.17894 452.41389,120.60225 414.87201,95.486734 375.79457,82.607981 329.71466,80.164268 309.74501,79.105235 308.82406,78.936079 301.35544,74.955344 293.27355,70.647744 288.55679,65.664029 283.65202,56.25 281.24885,51.637427 280.72138,48.712636 280.72138,40 c 0,-8.712636 0.52747,-11.637428 2.93064,-16.25 4.86542,-9.338502 9.60471,-14.388774 17.40352,-18.5454996 L 308.47494,1.25 l 19.82504,0.00576 c 45.1974,0.013125 90.71825,11.3818445 134.82502,33.6722145 33.69714,17.029621 57.93446,34.914471 85.01861,62.735688 25.48335,26.176855 41.213,48.527605 57.50549,81.711345 20.68606,42.13235 30.81344,82.42236 32.6838,130.0268 0.73345,18.66795 0.60645,20.43461 -1.8717,26.03688 -6.39121,14.4484 -18.47868,23.05554 -33.64742,23.95939 -4.64133,0.27656 -10.16113,0.0553 -12.26623,-0.49167 z\" />\n        </svg>\n      </span>\n    </span>\n  );\n}\n\nexport default withI18n()(Spinner);\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Spinner/style.scss",
    "content": "@import '../../styles/common';\n\n@keyframes m-loading__spin {\n  from {\n    transform: scale(1) rotate(0deg);\n  }\n\n  to {\n    transform: scale(1) rotate(360deg);\n  }\n}\n\n.m-spinner {\n  $baseName: &;\n\n  &--inline-block {\n    display: inline-block;\n    padding: (calc($co-margin / 2));\n  }\n\n  &__icon {\n    display: inline-block;\n    font-size: 1rem;\n    animation: m-loading__spin 0.5s infinite linear;\n\n    svg {\n      path {\n        fill: $co-color__fg__text--higher;\n      }\n    }\n  }\n\n  &--bright {\n    #{$baseName}__icon {\n      svg {\n        path {\n          fill: $co-color__contrast__text;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Subtitle/index.spec.tsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport Subtitle from '.';\n\ndescribe('component Subtitle', () => {\n  it('render', () => {\n    const comp = shallow(<Subtitle>Foo</Subtitle>);\n\n    expect(comp.text()).toEqual('Foo');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Subtitle/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport './style.scss';\n\ninterface Props {\n  children: React.ReactNode;\n  actions?: React.ReactNode;\n  hr?: boolean;\n  className?: string;\n}\nfunction Subtitle({ children, actions, hr, className, ...props }: Props) {\n  return (\n    <div\n      className={classnames('m-subtitle', { 'm-subtitle--hr': hr }, className)}\n      {...props}\n    >\n      <h3 className=\"m-subtitle__text\">{children}</h3>\n      {actions && <span className=\"m-subtitle__actions\">{actions}</span>}\n    </div>\n  );\n}\n\nexport default Subtitle;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Subtitle/style.scss",
    "content": "@import '../../styles/common';\n\n$m-subtitle__height: $co-component__height !default;\n\n.m-subtitle {\n  display: block;\n  line-height: $m-subtitle__height;\n\n  &__text {\n    display: inline;\n    color: $co-color__primary--lower;\n    font-size: 1rem;\n    font-weight: 600;\n  }\n\n  &--hr {\n    border-width: 1px 0;\n    border-style: solid;\n    border-color: $co-color__fg__border;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Switch/index.tsx",
    "content": "import * as React from 'react';\nimport PropTypes from 'prop-types';\nimport './style.scss';\n\nexport interface Props\n  extends React.DetailedHTMLProps<\n    React.InputHTMLAttributes<HTMLInputElement>,\n    HTMLInputElement\n  > {\n  label: React.ReactNode;\n  id: string;\n}\nfunction Switch({ label, id, ...inputProps }: Props) {\n  return (\n    <div className=\"m-switch\">\n      <input\n        type=\"checkbox\"\n        className=\"m-switch__input\"\n        id={id}\n        {...inputProps}\n      />\n      <label className=\"m-switch__paddle\" htmlFor={id}>\n        <span className=\"show-for-sr\">{label}</span>\n      </label>\n    </div>\n  );\n}\n\nSwitch.propTypes = {\n  label: PropTypes.node.isRequired,\n  id: PropTypes.string.isRequired,\n};\n\nexport default Switch;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Switch/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$switch-height: 2rem !default;\n$switch-radius: 1rem;\n$switch-paddle-radius: 1rem;\n\n.m-switch {\n  @include switch-container;\n\n  display: inline-block;\n  vertical-align: middle;\n  height: $switch-height;\n  margin-bottom: 0;\n\n  &__input {\n    @include switch-input;\n  }\n\n  &__paddle {\n    @include switch-paddle;\n\n    background-color: $co-color__fg__back--lower;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextBlock/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport './style.scss';\n\ninterface Props extends React.HTMLAttributes<HTMLSpanElement> {\n  inline?: boolean;\n  nowrap?: boolean;\n  className?: string;\n  size?: 'small';\n  weight?: 'strong';\n  children?: React.ReactNode;\n}\n\nfunction TextBlock({\n  inline = false,\n  nowrap = true,\n  size,\n  weight,\n  className,\n  children,\n  ...props\n}: Props) {\n  const textBlockClassName = classnames(\n    'm-text-block',\n    {\n      'm-text-block--inline': inline,\n      'm-text-block--nowrap': nowrap,\n      'm-text-block--small': size === 'small',\n      'm-text-block--strong': weight === 'strong',\n    },\n    className\n  );\n\n  return (\n    <span className={textBlockClassName} {...props}>\n      {children}\n    </span>\n  );\n}\n\nexport default TextBlock;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextBlock/style.scss",
    "content": "@import '../../styles/common';\n\n.m-text-block {\n  display: block;\n  overflow: hidden;\n  text-overflow: ellipsis;\n\n  &--nowrap {\n    white-space: nowrap;\n  }\n\n  &--inline {\n    display: inline-block;\n  }\n\n  &--small {\n    font-size: $co-font__size--xsmall;\n    line-height: $co-font__size--xsmall;\n  }\n\n  &--strong {\n    font-weight: 700;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextFieldGroup/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport TextFieldGroup from '.';\n\ndescribe('component TextFieldGroup', () => {\n  it('should render', () => {\n    const cb = jest.fn();\n    const handleChange = (ev) => cb(ev.target.value);\n\n    render(\n      <TextFieldGroup\n        id=\"test\"\n        label=\"foo\"\n        inputProps={{ onChange: handleChange }}\n      />\n    );\n\n    userEvent.type(screen.getByRole('textbox', { name: 'foo' }), 'hello');\n    expect(cb).toHaveBeenCalledWith('hello');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextFieldGroup/index.tsx",
    "content": "import classnames from 'classnames';\nimport { FieldProps, getIn } from 'formik';\nimport * as React from 'react';\nimport { WrappedFieldProps } from 'redux-form';\nimport FieldGroup from '../FieldGroup';\nimport InputText from '../InputText';\nimport Label from '../Label';\nimport './style.scss';\n\ninterface Props {\n  id: string;\n  label: React.ReactNode;\n  showLabelforSr?: boolean;\n  errors?: React.ReactNode[];\n  className?: string;\n  display?: 'inline' | 'block';\n  innerRef: React.ForwardedRef<HTMLInputElement>;\n  inputProps: Omit<React.ComponentProps<typeof InputText>, 'innerRef'>;\n}\nfunction TextFieldGroup({\n  id,\n  label,\n  showLabelforSr = false,\n  errors = [],\n  className,\n  display = 'block',\n  innerRef,\n  inputProps: {\n    onBlur,\n    className: inputClassName,\n    required,\n    ...restInputProps\n  } = {},\n}: Props) {\n  const [isPristine, setIsPristine] = React.useState(true);\n\n  const handleBlur = (ev: any) => {\n    setIsPristine(false);\n\n    return onBlur && onBlur(ev);\n  };\n\n  const hasError = errors.length > 0 && !isPristine;\n\n  const groupClassName = classnames(className, 'm-text-field-group', {\n    'm-text-field-group--inline': display === 'inline',\n  });\n\n  const labelClassName = classnames('m-text-field-group__label', {\n    'show-for-sr': showLabelforSr,\n    'm-text-field-group--inline__label': display === 'inline',\n    'm-text-field-group__label--required': required,\n  });\n\n  const inputPropsClassName = classnames(\n    'm-text-field-group__input',\n    {\n      'm-text-field-group--inline__input': display === 'inline',\n    },\n    inputClassName\n  );\n\n  const errorsClassName = classnames('m-text-field-group__errors', {\n    'm-text-field-group--inline__errors': display === 'inline',\n  });\n\n  return (\n    <FieldGroup\n      className={groupClassName}\n      errors={errors}\n      errorsClassname={errorsClassName}\n    >\n      <Label htmlFor={id} className={labelClassName}>\n        {label}\n      </Label>\n      <InputText\n        id={id}\n        className={inputPropsClassName}\n        hasError={hasError}\n        expanded={display === 'block'}\n        {...restInputProps}\n        onBlur={handleBlur}\n        required={required}\n        ref={innerRef}\n      />\n    </FieldGroup>\n  );\n}\n\ntype TextFieldGroupProps = Omit<Props, 'innerRef'>;\n\nconst ForwardedTextFieldGroup = React.forwardRef<\n  HTMLInputElement,\n  TextFieldGroupProps\n>((props, ref) => <TextFieldGroup {...props} innerRef={ref} />);\n\nexport default ForwardedTextFieldGroup;\n\ntype ReduxTextFieldGroupProps = WrappedFieldProps & Props;\n\nexport function ReduxTextFieldGroup({\n  input,\n  meta,\n  inputProps: inputPropsBase,\n  ...props\n}: ReduxTextFieldGroupProps): React.ReactNode {\n  const inputProps = {\n    ...inputPropsBase,\n    ...input,\n  };\n\n  const errors = meta.error ? [meta.error] : undefined;\n\n  return <TextFieldGroup inputProps={inputProps} errors={errors} {...props} />;\n}\n\ntype FormikTextFieldGroupProps = FieldProps & Props;\n\nexport function FormikTextFieldGroup({\n  id,\n  label,\n  field,\n  // for some reason meta is not set\n  // meta,\n  form,\n  inputProps,\n  ...props\n}: FormikTextFieldGroupProps): React.ReactElement<Props> {\n  return (\n    <ForwardedTextFieldGroup\n      id={id}\n      label={label}\n      inputProps={{ ...inputProps, ...field }}\n      errors={\n        getIn(form?.errors, field.name) && getIn(form.touched, field.name)\n          ? [getIn(form.errors, field.name)]\n          : undefined\n      }\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextFieldGroup/style.scss",
    "content": "@import '../../styles/common';\n\n.m-text-field-group {\n  position: relative;\n\n  &__label {\n    display: block;\n\n    &--required {\n      font-weight: bold;\n\n      &::after {\n        content: ' *';\n      }\n    }\n  }\n\n  &--inline {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-between;\n\n    &__label,\n    &__input,\n    &__errors {\n      margin: auto;\n    }\n\n    &__label {\n      flex: 0 1;\n      margin-right: map_get($co-form__spacing, 'small');\n    }\n\n    &__input {\n      flex: 1 0;\n    }\n\n    &__errors {\n      flex: 1 0 100%;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextList/components/TextItem.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\n\ninterface Props {\n  large?: boolean;\n  className?: string;\n  children?: React.ReactNode;\n}\n\nfunction TextItem({ className, large = false, ...props }: Props): JSX.Element {\n  const itemClassName = classnames(\n    'm-text-list__item',\n    {\n      'm-text-list__item--large': large,\n    },\n    className\n  );\n\n  return <li className={itemClassName} {...props} />;\n}\n\nexport default TextItem;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextList/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport TextList, { TextItem } from '.';\n\ndescribe('component TextList', () => {\n  it('render', () => {\n    const comp = shallow(\n      <TextList>\n        {[<TextItem key=\"0\">Foo</TextItem>, <TextItem key=\"1\">Bar</TextItem>]}\n      </TextList>\n    );\n\n    expect(comp.find('TextItem').length).toEqual(2);\n    expect(comp.find('TextItem').first().dive().text()).toEqual('Foo');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextList/index.tsx",
    "content": "import React from 'react';\nimport classnames from 'classnames';\nimport TextItem from './components/TextItem';\nimport './style.scss';\n\ninterface Props extends React.ComponentProps<'ul'> {\n  className?: string;\n}\nfunction TextList({ className, ...props }: Props): JSX.Element {\n  return <ul className={classnames('m-text-list', className)} {...props} />;\n}\n\nexport { TextItem };\n\nexport default TextList;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextList/style.scss",
    "content": "@import '../../styles/common';\n\n.m-text-list {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n\n  &__item {\n    color: $co-color__fg__text--low;\n    font-size: $co-font__size;\n    line-height: $co-font__line-height;\n\n    &--large {\n      line-height: $co-component__height;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Textarea/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\n\nimport './style.scss';\n\nclass Textarea extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n    expanded: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    className: undefined,\n    expanded: true,\n  };\n\n  render() {\n    const { className, expanded, ...props } = this.props;\n\n    return (\n      <textarea\n        className={classnames(\n          'm-textarea',\n          { 'm-textarea--expanded': expanded },\n          className\n        )}\n        {...props}\n      />\n    );\n  }\n}\n\nexport default Textarea;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Textarea/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport Textarea from '.';\n\ndescribe('component Textarea', () => {\n  it('should render', () => {\n    const props = {\n      onChange: jest.fn(),\n    };\n\n    const comp = shallow(<Textarea {...props} />);\n\n    expect(comp.find('textarea').length).toEqual(1);\n    comp.find('textarea').simulate('change');\n    expect(props.onChange).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Textarea/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-form-element';\n\n.m-textarea {\n  @include o-form-element;\n  @include o-form-element--light;\n\n  min-height: 3 * $co-component__height;\n\n  &--expanded {\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextareaFieldGroup/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { v1 as uuidV1 } from 'uuid';\nimport classnames from 'classnames';\nimport Label from '../Label';\nimport FieldGroup from '../FieldGroup';\nimport Textarea from '../Textarea';\n\nimport './style.scss';\n\nclass TextareaFieldGroup extends PureComponent {\n  static propTypes = {\n    label: PropTypes.node.isRequired,\n    showLabelForSR: PropTypes.bool,\n    errors: PropTypes.arrayOf(PropTypes.node),\n    onChange: PropTypes.func,\n    className: PropTypes.string,\n    inputProps: PropTypes.shape({}),\n  };\n\n  static defaultProps = {\n    showLabelForSR: false,\n    errors: [],\n    onChange: (str) => str,\n    className: undefined,\n    inputProps: {},\n  };\n\n  render() {\n    const { label, errors, onChange, className, inputProps, showLabelForSR } =\n      this.props;\n    const id = uuidV1();\n\n    return (\n      <FieldGroup\n        className={classnames('m-textarea-field-group', className)}\n        errors={errors}\n      >\n        <Label\n          htmlFor={id}\n          className={classnames('m-textarea-field-group__label', {\n            'sr-only': showLabelForSR,\n          })}\n        >\n          {label}\n        </Label>\n        <Textarea id={id} onChange={onChange} {...inputProps} />\n      </FieldGroup>\n    );\n  }\n}\n\nexport default TextareaFieldGroup;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextareaFieldGroup/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport TextareaFieldGroup from '.';\n\ndescribe('component TextareaFieldGroup', () => {\n  it('should render', () => {\n    const props = {\n      label: 'Foo',\n      inputProps: {\n        onChange: jest.fn(),\n      },\n    };\n\n    const comp = shallow(<TextareaFieldGroup {...props} />);\n\n    expect(comp.find('FieldGroup').length).toEqual(1);\n    expect(comp.find('Textarea').length).toEqual(1);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/components/TextareaFieldGroup/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/object/o-form-element';\n\n.m-textarea-field-group {\n  &__label {\n    display: block;\n    margin-bottom: map_get($co-form__spacing, 'small');\n  }\n\n  &__errors {\n    margin-top: map_get($co-form__spacing, 'medium');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Title/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nclass Title extends PureComponent {\n  static propTypes = {\n    children: PropTypes.node.isRequired,\n    actions: PropTypes.node,\n    hr: PropTypes.bool,\n    caps: PropTypes.bool,\n    size: PropTypes.oneOf(['medium', 'large']),\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    actions: undefined,\n    hr: false,\n    caps: false,\n    size: 'medium',\n    className: undefined,\n  };\n\n  render() {\n    const { children, actions, hr, caps, size, className, ...props } =\n      this.props;\n\n    const titleClassName = classnames(\n      'm-title',\n      {\n        'm-title--hr': hr,\n        'm-title--caps': caps,\n        'm-title--large': size === 'large',\n      },\n      className\n    );\n\n    return (\n      <div className={titleClassName} {...props}>\n        <h2 className=\"m-title__text\">{children}</h2>\n        {actions && <span className=\"m-title__actions\">{actions}</span>}\n      </div>\n    );\n  }\n}\n\nexport default Title;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/Title/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$m-title__height: $co-component__height !default;\n\n.m-title {\n  $baseName: &;\n\n  display: block;\n\n  &__text {\n    display: inline-block;\n    max-width: 100%; // compat with TexetBlock\n    color: $co-color__primary;\n    font-size: 1rem;\n    font-weight: 700;\n    line-height: 1rem;\n  }\n\n  &--caps {\n    text-transform: capitalize;\n  }\n\n  &--hr {\n    @include flex-grid-row;\n\n    align-items: center;\n    margin-left: 0;\n\n    #{$baseName}__text {\n      @include flex-grid-size(shrink);\n\n      margin-right: map_get($co-margin_, 'small');\n    }\n\n    &::after {\n      @include flex-grid-size;\n\n      display: block;\n      content: '';\n      border-top: 2px solid #8dceec;\n    }\n  }\n\n  &--large {\n    #{$baseName}__text {\n      font-size: 1.25rem;\n      line-height: 1.25rem;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/VerticalMenu/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport './style.scss';\n\nexport function Separator() {\n  return <li className=\"m-vertical-menu__separator\" />;\n}\n\ninterface VerticalMenuItemProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nexport function VerticalMenuItem({\n  children,\n  className,\n  ...props\n}: VerticalMenuItemProps) {\n  const itemProps = {\n    ...props,\n    className: classnames('m-vertical-menu__item', className),\n  };\n\n  return <li {...itemProps}>{children}</li>;\n}\n\nexport function VerticalMenuTextItem(props: VerticalMenuItemProps) {\n  return (\n    <VerticalMenuItem {...props} className=\"m-vertical-menu__item-content\" />\n  );\n}\n\ninterface VerticalMenuProps {\n  children: React.ReactNode;\n  className?: string;\n}\nfunction VerticalMenu({ children, className }: VerticalMenuProps) {\n  return (\n    <ul className={classnames('m-vertical-menu', className)}>{children}</ul>\n  );\n}\n\nexport default VerticalMenu;\n"
  },
  {
    "path": "src/frontend/web_application/src/components/VerticalMenu/style.scss",
    "content": "@import '../../styles/common';\n\n// XXX: move or remove\n@mixin m-menu--horizontal {\n  &__item {\n    float: left;\n  }\n\n  &__separator {\n    float: left;\n    width: 1px;\n    height: auto;\n    margin: 0 (calc($co-margin / 4));\n  }\n}\n\n@mixin m-menu--vertical {\n  &__item {\n    // float: none;\n  }\n\n  &__separator {\n    // float: none;\n    // width: auto;\n    height: 1px;\n    margin: (calc($co-margin / 4)) 0;\n  }\n}\n\n@mixin m-menu--height($height) {\n  @if map-has-key($co-form__height, $height) {\n    $height: map_get($co-form__height, $height);\n  }\n\n  &__item {\n    min-height: $height;\n  }\n\n  &__item-content {\n    line-height: $height;\n  }\n}\n\n@mixin m-menu {\n  // @include clearfix;\n\n  list-style-type: none;\n  margin: 0;\n  padding: 0;\n\n  &__item--is-active {\n    color: $co-color__fg__text--high;\n  }\n\n  &__item-content {\n    display: block;\n    padding: 0 1rem;\n    white-space: nowrap;\n  }\n\n  &__separator {\n    background-color: $co-color__fg__border;\n  }\n\n  // @include m-menu--horizontal;\n  @include m-menu--height(medium);\n\n  //\n  // &--horizontal   { @include m-menu--horizontal; }\n  // &--vertical { @include m-menu--vertical; }\n\n  &--small {\n    @include m-menu--height(small);\n  }\n\n  &--medium {\n    @include m-menu--height(medium);\n  }\n\n  &--large {\n    @include m-menu--height(large);\n  }\n}\n\n.m-vertical-menu {\n  @include m-menu;\n  @include m-menu--vertical;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/components/index.js",
    "content": "export {\n  default as ActionBar,\n  ActionBarButton,\n  ActionBarWrapper,\n} from './ActionBar';\nexport { default as AppLoader } from './AppLoader';\nexport { default as Badge } from './Badge';\nexport { default as Button } from './Button';\nexport { default as Callout } from './Callout';\nexport { default as BlockList } from './BlockList';\nexport { default as Brand } from './Brand';\nexport { default as Confirm } from './Confirm';\nexport { default as DefList } from './DefList';\nexport { default as Dropdown, withDropdownControl } from './Dropdown';\nexport { default as DropdownMenu } from './DropdownMenu';\nexport { default as FileSize } from './FileSize';\nexport { default as Icon } from './Icon';\nexport { default as InfiniteScroll } from './InfiniteScroll';\nexport { default as Link } from './Link';\nexport { default as MenuBar } from './MenuBar';\nexport { default as Modal } from './Modal';\nexport { default as NavList, NavItem } from './NavList';\nexport { default as PageTitle } from './PageTitle';\nexport { default as PlaceholderBlock } from './PlaceholderBlock';\nexport { default as PlaceholderList } from './PlaceholderList';\nexport { default as RawButton } from './RawButton';\nexport { default as Section } from './Section';\nexport { default as SidebarLayout } from './SidebarLayout';\nexport { default as Spinner } from './Spinner';\nexport { default as Subtitle } from './Subtitle';\nexport { default as TextBlock } from './TextBlock';\nexport { default as TextList, TextItem } from './TextList';\nexport { default as Title } from './Title';\nexport {\n  default as VerticalMenu,\n  VerticalMenuItem,\n  Separator,\n} from './VerticalMenu';\n\n// Forms\nexport { default as AdvancedSelectFieldGroup } from './AdvancedSelectFieldGroup';\nexport { default as Checkbox } from './Checkbox';\nexport { default as CheckboxFieldGroup } from './CheckboxFieldGroup';\nexport { default as DatePickerGroup } from './DatePickerGroup';\nexport { default as FieldErrors, FormikFieldErrors } from './FieldErrors';\nexport { default as FieldGroup } from './FieldGroup';\nexport { default as Fieldset, Legend } from './Fieldset';\nexport { default as FormGrid, FormRow, FormColumn } from './FormGrid';\nexport { default as InputFile } from './InputFile';\nexport { default as InputFileGroup } from './InputFileGroup';\nexport { default as InputText } from './InputText';\nexport { default as Label } from './Label';\nexport { default as PasswordStrength } from './PasswordStrength';\nexport { default as RadioFieldGroup } from './RadioFieldGroup';\nexport { default as SelectFieldGroup } from './SelectFieldGroup';\nexport { default as Switch } from './Switch';\nexport {\n  default as TextFieldGroup,\n  FormikTextFieldGroup,\n} from './TextFieldGroup';\nexport { default as Textarea } from './Textarea';\nexport { default as TextareaFieldGroup } from './TextareaFieldGroup';\n"
  },
  {
    "path": "src/frontend/web_application/src/global.d.ts",
    "content": "declare const BUILD_TARGET: 'server' | 'browser';\n"
  },
  {
    "path": "src/frontend/web_application/src/hooks/forwardedRef.ts",
    "content": "import * as React from 'react';\n\nexport function useForwardedRef<T extends HTMLElement>(\n  ref: React.ForwardedRef<T>\n) {\n  const innerRef = React.useRef<T>(null);\n\n  React.useEffect(() => {\n    if (!ref) {\n      return;\n    }\n\n    if (typeof ref === 'function') {\n      ref(innerRef.current);\n    } else {\n      // eslint-disable-next-line no-param-reassign\n      ref.current = innerRef.current;\n    }\n  }, [ref]);\n\n  return innerRef;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/image.d.ts",
    "content": "declare module '*.svg' {\n  const path: string;\n\n  export default path;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/index.jsx",
    "content": "import * as React from 'react';\nimport ReactDOM from 'react-dom';\nimport { Router } from 'react-router-dom';\nimport PiwikReactRouter from 'piwik-react-router';\n// import {\n//   install as PWAOfflineInstall,\n//   applyUpdate,\n//   // eslint-disable-next-line import/no-extraneous-dependencies\n// } from 'offline-plugin/runtime';\nimport App from './App';\nimport configureStore from './store/configure-store';\nimport { initialState as initialStateSettings } from './store/modules/settings';\nimport { getRouterHistory } from './modules/routing';\nimport { getUserLocales } from './modules/i18n';\nimport { getDefaultSettings } from './modules/settings';\nimport { getConfig } from './services/config';\n\nlet devTools;\n\nconst locales = getUserLocales();\nconst settings = getDefaultSettings(locales);\nconst getHistory = () => {\n  const history = getRouterHistory();\n  const {\n    piwik: { siteId },\n  } = getConfig();\n  if (siteId) {\n    const piwik = PiwikReactRouter({\n      url: 'https://piwik.caliopen.org/analytics',\n      siteId,\n    });\n\n    return piwik.connectToHistory(history);\n  }\n\n  return history;\n};\n\n// XXX: exported for typing\nexport const store = configureStore(\n  {\n    settings: {\n      ...initialStateSettings,\n      settings,\n    },\n  },\n  devTools\n);\n\n// Disabled since offline plugin is not compat with webpack 5\n// PWAOfflineInstall({\n//   onUpdateReady: () => {\n//     // Tells to new SW to take control immediately\n//     applyUpdate();\n//   },\n// });\nconst rootEl = document.getElementById('root');\nReactDOM.hydrate(\n  <Router history={getHistory()}>\n    <App store={store} />\n  </Router>,\n  rootEl\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/AboutPage/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Brand, Link, Icon } from '../../components';\nimport { getConfig } from '../../services/config';\nimport { BackgroundImage } from '../../modules/pi';\nimport NotificationCenter from '../Page/components/NotificationCenter';\nimport './style.scss';\n\nfunction AboutPage({ children }) {\n  const { version } = getConfig();\n\n  return (\n    <BackgroundImage context=\"secure\" className=\"l-about-page\">\n      <div className=\"l-about-page__content\">{children}</div>\n      <footer className=\"l-about-page__footer\">\n        <ul className=\"l-about-page__footer-list\">\n          <li className=\"l-about-page__footer-entry\">\n            <Brand className=\"l-about-page__footer-brand\" />\n            <span> - Be good.</span>\n          </li>\n          <li className=\"l-about-page__footer-entry\">\n            <Link href=\"https://www.caliopen.org\" target=\"_blank\">\n              <Trans id=\"about.footer.link-website\" message=\"About\" />\n            </Link>\n          </li>\n          <li className=\"l-about-page__footer-entry\">\n            <Link href=\"https://www.caliopen.org/blog\" target=\"_blank\">\n              <Trans id=\"about.footer.link-blog\" message=\"Blog\" />\n            </Link>\n          </li>\n          <li className=\"l-about-page__footer-entry\">\n            <Link href=\"https://github.com/CaliOpen/Caliopen\" target=\"_blank\">\n              <Icon type=\"github\" />{' '}\n              <Trans id=\"about.footer.link-source-code\" message=\"Open Source\" />\n            </Link>\n          </li>\n          <li className=\"l-about-page__footer-entry\">\n            <Link href=\"/privacy-policy.html\" target=\"_blank\">\n              <Trans\n                id=\"about.footer.link-privacy-policy\"\n                message=\"Privacy Policy\"\n              />\n            </Link>\n          </li>\n          <li className=\"l-about-page__footer-entry\">{version}</li>\n        </ul>\n      </footer>\n      <NotificationCenter />\n    </BackgroundImage>\n  );\n}\n\nAboutPage.propTypes = {\n  children: PropTypes.node.isRequired,\n};\n\nexport default AboutPage;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/AboutPage/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$l-auth-page__brand-height: 42px !default;\n$l-auth-page__header-space: (\n  top: 86px,\n  bottom: 36px,\n);\n\n.l-about-page {\n  display: grid;\n  grid-template-columns: 1fr;\n  grid-template-areas:\n    'content'\n    'footer';\n  min-height: 100vh;\n\n  &__content {\n    grid-area: content;\n  }\n\n  &__footer {\n    grid-area: footer;\n    background-color: #e1f5ff; // cf. Page layout's header\n    line-height: $co-component__height;\n    white-space: nowrap;\n  }\n\n  &__footer-list {\n    display: flex;\n    flex-wrap: wrap;\n  }\n\n  &__footer-entry {\n    @include flex-grid-column(shrink);\n\n    @include breakpoint(medium) {\n      @include flex-grid-column;\n\n      text-align: center;\n    }\n  }\n\n  &__footer-brand {\n    vertical-align: middle;\n\n    // XXX .m-brand forces width 100%\n    width: initial !important;\n    height: $co-component__height - 1rem;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/AuthPage/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Brand } from '../../components';\nimport { getConfig } from '../../services/config';\nimport NotificationCenter from '../Page/components/NotificationCenter';\nimport './style.scss';\n\nfunction AuthPage({ children }) {\n  const { version, motd } = getConfig();\n\n  return (\n    <div className=\"l-auth-page\">\n      <div className=\"l-auth-page__content\">\n        <header className=\"l-auth-page__header\">\n          <Brand className=\"l-auth-page__brand\" theme=\"low\" />\n        </header>\n        <section className=\"l-auth-page__form\">{children}</section>\n        <footer className=\"l-auth-page__footer\">\n          {motd && <div>{motd}</div>}\n          <div>\n            {version} - Be good. -{' '}\n            <a href=\"/privacy-policy.html\" target=\"_blank\">\n              Privacy Policy\n            </a>\n          </div>\n        </footer>\n      </div>\n      <NotificationCenter />\n    </div>\n  );\n}\n\nAuthPage.propTypes = {\n  children: PropTypes.node.isRequired,\n};\n\nexport default AuthPage;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/AuthPage/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$l-auth-page__brand-height: 42px !default;\n$l-auth-page__header-space: (\n  top: 86px,\n  bottom: 36px,\n);\n\n.l-auth-page {\n  min-height: 100vh;\n  border-top: 8px solid $co-color__primary; // TODO: update border color according to context\n  background: $co-color__fg__back;\n\n  &__content {\n    max-width: map_get($co-form__width, 'xlarge');\n    margin: auto;\n  }\n\n  &__header {\n    padding-top: map_get($l-auth-page__header-space, top);\n    padding-bottom: map_get($l-auth-page__header-space, bottom);\n    text-align: center;\n  }\n\n  &__brand {\n    height: $l-auth-page__brand-height;\n  }\n\n  &__form {\n    margin: 0 $co-margin * 2;\n  }\n\n  &__footer {\n    padding: $co-margin 0;\n    font-size: $co-font__size--small;\n    text-align: center;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/ErrorBoundary/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Link, Brand } from '../../components';\nimport { getConfig } from '../../services/config';\nimport './style.scss';\n\nclass ErrorBoundary extends Component {\n  static propTypes = {\n    children: PropTypes.node.isRequired,\n  };\n\n  state = { error: null, errorInfo: null };\n\n  // eslint-disable-next-line react/sort-comp\n  componentDidCatch(error, errorInfo) {\n    this.setState({\n      error,\n      errorInfo,\n    });\n  }\n\n  getFeedbackLink = () => {\n    const stackTrace = `\nStackTrace:\n\n\\`\\`\\`\n${this.state.error && this.state.error.toString()}\n${this.state.errorInfo && this.state.errorInfo.componentStack}\n\\`\\`\\`\n    `;\n    const params = {\n      title: this.state.error\n        ? this.state.error.toString()\n        : 'undefined catched error',\n      category: 'bugs',\n      tags: 'BSOD',\n      body: stackTrace,\n    };\n    const esc =\n      typeof encodeURIComponent !== 'undefined'\n        ? encodeURIComponent\n        : (str) => str;\n    const queryString = Object.keys(params)\n      .map((key) => `${key}=${esc(params[key])}`)\n      .join('&');\n\n    return `https://feedback.caliopen.org/new-topic?${queryString}`;\n  };\n\n  render() {\n    if (this.state.errorInfo) {\n      const { version } = getConfig();\n\n      return (\n        <div className=\"l-error-boundary\">\n          <div className=\"l-error-boundary__content\">\n            <header className=\"l-error-boundary__header\">\n              <Brand className=\"l-error-boundary__brand\" theme=\"low\" />\n            </header>\n            <section className=\"l-error-boundary__body\">\n              <h2>\n                <Trans\n                  id=\"error-boundary.title\"\n                  message=\"Something went wrong.\"\n                />\n              </h2>\n              <div className=\"l-error-boundary__description\">\n                <Trans\n                  id=\"error-boundary.description\"\n                  message=\"<0>We are are very sorry, an error occured on your account. It is what we call a «BSOD», that should never happens (but sometimes it does).</0><1>You can help us to resolve this bug by creating a new post on our feedback website (see link below). We will try to answer as soon as possible.</1><2>Of course you can browse <3>feedback.caliopen.org</3> and help us to improve Caliopen.</2>\"\n                  components={[\n                    <p />,\n                    <p />,\n                    <p />,\n                    <Link\n                      href=\"https://feedback.caliopen.org\"\n                      target=\"_blank\"\n                      rel=\"noreferrer noopener\"\n                    />,\n                  ]}\n                />\n              </div>\n              <div>\n                <Link\n                  href={this.getFeedbackLink()}\n                  target=\"_blank\"\n                  rel=\"noreferrer noopener\"\n                >\n                  <Trans\n                    id=\"error-boundary.send-feedback\"\n                    message=\"Send us a feedback\"\n                  />\n                </Link>\n              </div>\n              <div>\n                <Link href=\"/\" button shape=\"plain\">\n                  <Trans\n                    id=\"error-boundary.back-to-home\"\n                    message=\"Go back to home\"\n                  />\n                </Link>\n              </div>\n              <details\n                className=\"l-error-boundary__error-details\"\n                style={{ whiteSpace: 'pre-wrap' }}\n              >\n                {this.state.error && this.state.error.toString()}\n                <br />\n                {this.state.errorInfo.componentStack}\n              </details>\n            </section>\n            <footer className=\"l-error-boundary__footer\">\n              {version} - Be good.\n            </footer>\n          </div>\n        </div>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nexport default ErrorBoundary;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/ErrorBoundary/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$l-error-boundary__brand-height: 42px !default;\n$l-error-boundary__header-space: (\n  top: 86px,\n  bottom: 36px,\n);\n\n.l-error-boundary {\n  min-height: 100vh;\n  border-top: 8px solid $co-color__primary;\n\n  &__content {\n    max-width: 45rem;\n    margin: auto;\n  }\n\n  &__header {\n    padding-top: map_get($l-error-boundary__header-space, top);\n    padding-bottom: map_get($l-error-boundary__header-space, bottom);\n    text-align: center;\n  }\n\n  &__brand {\n    height: $l-error-boundary__brand-height;\n  }\n\n  &__body {\n    margin: 0 $co-margin * 2;\n  }\n\n  &__description {\n    margin-bottom: $co-margin;\n  }\n\n  &__error-details {\n    margin-top: $co-margin;\n    margin-bottom: $co-margin;\n  }\n\n  &__footer {\n    padding: $co-margin 0;\n    font-size: $co-font__size--small;\n    text-align: center;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Footer/footer.scss",
    "content": "@import '../../../../styles/common';\n\n.l-footer {\n  padding-top: 3.625rem;\n  padding-bottom: 2.875rem;\n  text-align: center;\n\n  // &__tips {\n  //   margin-bottom: 1.625rem;\n  //   color: $co-color__primary;\n  // }\n\n  &__brand {\n    height: 32px;\n    margin-bottom: 0.625rem;\n  }\n\n  &__release {\n    color: $co-color__primary__text--alt;\n    font-size: $co-font__size--small;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Footer/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { getConfig } from '../../../../services/config';\nimport { Brand } from '../../../../components';\nimport './footer.scss';\n\nclass Footer extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  render() {\n    const { className } = this.props;\n    const { version } = getConfig();\n\n    return (\n      <div className={classnames('l-footer', className)}>\n        {/* <div className=\"l-footer__tips\">\n          <b>Astuce : </b>\n          pour améliorer la confidentialitéde vos échanges, saviez-vous que vous pouviez lorem\n          ipsum dolor sit amet!\n        </div> */}\n\n        <Brand className=\"l-footer__brand\" theme=\"low\" />\n        <div className=\"l-footer__release\">\n          {version} - Be good. -{' '}\n          <a href=\"/privacy-policy.html\" target=\"_blank\">\n            Privacy Policy\n          </a>\n        </div>\n      </div>\n    );\n  }\n}\n\nexport default Footer;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/HorizontalScroll/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport throttle from 'lodash/throttle';\nimport { Icon, Button } from 'src/components';\nimport { addEventListener } from 'src/services/event-manager';\nimport { NavbarItem } from '../Navbar/components';\nimport './style.scss';\n\nconst STEP_SIZE = 200;\nconst SIZE_UNIT = 'px';\nconst VELOCITY_RIGHT = -1;\nconst VELOCITY_LEFT = 1;\nconst VISIBLE_MARGIN = 10; // a margin used after a programatic move\n\ninterface Props {\n  children: React.ReactNode;\n  className?: string;\n}\nfunction HorizontalScroll({ children, className }: Props) {\n  const container = React.useRef<HTMLDivElement>(null);\n  const visibleZone = React.useRef<HTMLDivElement>(null);\n\n  const [navigationSliders, setNavigationSliders] = React.useState(false);\n\n  const getDistance = (velocity: number) => {\n    if (!container.current || !visibleZone.current) {\n      return undefined;\n    }\n\n    const remain =\n      velocity === VELOCITY_RIGHT\n        ? container.current.clientWidth +\n          container.current.offsetLeft -\n          visibleZone.current.clientWidth\n        : -1 * container.current.offsetLeft;\n\n    return velocity * Math.min(STEP_SIZE, Math.max(remain, 0));\n  };\n\n  const resetContainerPosition = () => {\n    if (!container.current) {\n      return;\n    }\n\n    container.current.style.left = `0${SIZE_UNIT}`;\n  };\n\n  // Force position 0 if navigation becomes hidden\n  React.useEffect(() => {\n    if (!navigationSliders) {\n      resetContainerPosition();\n    }\n  }, [navigationSliders]);\n\n  const displayNavigationsliders = ({ containerWidth, visibleZoneWidth }) => {\n    const hasSliders = containerWidth > visibleZoneWidth;\n\n    if (hasSliders !== navigationSliders) {\n      setNavigationSliders(hasSliders);\n    }\n  };\n\n  // When the visible container is almost empty but still require navigation\n  const moveToTheClosestVisiblePosition = () => {\n    if (!navigationSliders) {\n      // nothing to do\n      return;\n    }\n    if (!container.current || !visibleZone.current) {\n      return;\n    }\n    if (container.current.offsetLeft > 0) {\n      // should not happen\n      return;\n    }\n    const containerWidth = container.current.clientWidth;\n    const visibleZoneWidth = visibleZone.current.clientWidth;\n\n    // width - (-1 x offset)\n    if (containerWidth + container.current.offsetLeft < visibleZoneWidth / 2) {\n      const offset = containerWidth - visibleZoneWidth + VISIBLE_MARGIN;\n      container.current.style.left = `-${offset}${SIZE_UNIT}`;\n    }\n  };\n\n  // configure resize event handler and evetually toggle visibility of navigation\n  React.useEffect(() => {\n    let containerWidth: number;\n    let visibleZoneWidth: number;\n    const handleZoneSizesChange = throttle(\n      () => {\n        if (!container.current || !visibleZone.current) {\n          return;\n        }\n        // FIXME undefined container after saving settings\n        if (\n          containerWidth !== container.current.clientWidth ||\n          visibleZoneWidth !== visibleZone.current.clientWidth\n        ) {\n          containerWidth = container.current.clientWidth;\n          visibleZoneWidth = visibleZone.current.clientWidth;\n          displayNavigationsliders({ containerWidth, visibleZoneWidth });\n        }\n      },\n      1000,\n      { leading: true, trailing: true }\n    );\n\n    handleZoneSizesChange(); // probably useless\n\n    const unsubscribeResizeEvent = addEventListener(\n      'resize',\n      handleZoneSizesChange\n    );\n\n    return unsubscribeResizeEvent;\n  }, []);\n\n  // eventually toggle navigation visibility on content change\n  React.useEffect(() => {\n    if (!container.current || !visibleZone.current) {\n      return;\n    }\n    const containerWidth = container.current.clientWidth;\n    const visibleZoneWidth = visibleZone.current.clientWidth;\n    displayNavigationsliders({ containerWidth, visibleZoneWidth });\n    moveToTheClosestVisiblePosition();\n  }, [children]);\n\n  const moveContainer = (velocity: number) => {\n    if (!container.current) {\n      return;\n    }\n    const distance = getDistance(velocity);\n    if (distance) {\n      container.current.style.left = `${\n        container.current.offsetLeft + distance\n      }${SIZE_UNIT}`;\n    }\n  };\n\n  const handleLeftTrigger = () => {\n    moveContainer(VELOCITY_LEFT);\n  };\n\n  const handleRightTrigger = () => {\n    moveContainer(VELOCITY_RIGHT);\n  };\n\n  return (\n    <div className={classnames(className, 'm-horizontal-scroll')}>\n      <div className=\"m-horizontal-scroll__visible-zone\" ref={visibleZone}>\n        <div className=\"m-menu m-horizontal-scroll__container\" ref={container}>\n          {children}\n        </div>\n      </div>\n      {navigationSliders && (\n        <>\n          <NavbarItem className=\"scroll-anchor-item\">\n            <Button\n              onClick={handleLeftTrigger}\n              className=\"m-horizontal-scroll__anchor scroll-anchor--left\"\n            >\n              <Icon type=\"arrow-left\" />\n            </Button>\n          </NavbarItem>\n          <NavbarItem className=\"scroll-anchor-item\">\n            <Button\n              onClick={handleRightTrigger}\n              className=\"m-horizontal-scroll__anchor scroll-anchor--right\"\n            >\n              <Icon type=\"arrow-right\" />\n            </Button>\n          </NavbarItem>\n        </>\n      )}\n    </div>\n  );\n}\n\nexport default HorizontalScroll;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/HorizontalScroll/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-navigation';\n@import '../../../../styles/object/o-pi-border';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-horizontal-scroll {\n  display: flex;\n  height: $l-navigation__height + 2 * rem_calc($pi-border__size);\n  background-image: linear-gradient(180deg, transparent 5px, #e1f5ff 5px);\n\n  &__anchor,\n  &__visible-zone {\n    display: inline-block;\n  }\n\n  &__anchor {\n    flex: 0;\n    background-color: $co-color__bg__back--high;\n  }\n\n  &__visible-zone {\n    position: relative;\n    flex: 1;\n    overflow-y: hidden;\n\n    @include breakpoint(medium) {\n      overflow: hidden;\n    }\n  }\n\n  &__container {\n    display: inline-flex;\n    position: absolute;\n    left: 0;\n    transition: left 0.5s ease;\n    background: linear-gradient(180deg, transparent 5px, #fff 2px);\n  }\n\n  &__item {\n    flex: shrink;\n  }\n}\n\n.scroll-anchor--left {\n  padding: 0 3px 0 12px;\n}\n\n.scroll-anchor--right {\n  padding: 0 12px 0 3px;\n}\n\n.scroll-anchor-item {\n  margin-right: 0;\n  border-top: 0;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/InstallButton/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Button, VerticalMenuItem } from '../../../../components';\nimport { withNotification } from '../../../../modules/userNotify';\nimport { InstallPromptConsumer } from '../../../../modules/pwa';\n\n@withNotification()\nclass InstallButton extends Component {\n  static propTypes = {\n    notifySuccess: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {};\n\n  state = {\n    isInstalled: false,\n  };\n\n  handleInstallFactory =\n    ({ defferedPrompt }) =>\n    async () => {\n      const { notifySuccess } = this.props;\n\n      defferedPrompt.prompt();\n      const { outcome } = await defferedPrompt.userChoice;\n\n      if (outcome === 'accepted') {\n        notifySuccess({\n          message: (\n            <Trans\n              id=\"pwa.feedback.install-success\"\n              message=\"Caliopen has been installed on your device.\"\n            />\n          ),\n        });\n        this.setState({ isInstalled: true });\n      }\n    };\n\n  render() {\n    return (\n      <InstallPromptConsumer\n        render={(defferedPrompt) => {\n          if (!defferedPrompt) {\n            // the app is not installable or already installed\n            return null;\n          }\n\n          if (this.state.isInstalled) {\n            return null;\n          }\n\n          return (\n            <VerticalMenuItem>\n              <Button\n                display=\"expanded\"\n                icon=\"download\"\n                center={false}\n                onClick={this.handleInstallFactory({ defferedPrompt })}\n              >\n                <Trans id=\"pwa.action.install\" message=\"Install\" />\n              </Button>\n            </VerticalMenuItem>\n          );\n        }}\n      />\n    );\n  }\n}\n\nexport default InstallButton;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/ApplicationTab/application-tab.scss",
    "content": ".m-application-tab {\n  width: 3.75rem;\n  font-size: 1.25rem;\n  text-align: center;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/ApplicationTab/index.jsx",
    "content": "import React from 'react';\nimport classnames from 'classnames';\nimport { Icon } from '../../../../../../components';\nimport Tab from '../Tab';\nimport NavbarItem from '../NavbarItem';\nimport ItemLink from '../ItemLink';\nimport { getTabUrl } from '../../../../../../modules/tab';\nimport './application-tab.scss';\n\nclass ApplicationTab extends Tab {\n  render() {\n    const {\n      className,\n      isActive,\n      tab: { location },\n      routeConfig,\n    } = this.props;\n\n    return (\n      <NavbarItem\n        className={classnames('m-application-tab', className)}\n        active={isActive}\n        color=\"contrasted\"\n        contentChildren={\n          <ItemLink\n            to={getTabUrl(location)}\n            title={routeConfig.tab.renderLabel()}\n          >\n            <Icon type={routeConfig.tab.icon} />\n          </ItemLink>\n        }\n      />\n    );\n  }\n}\n\nexport default ApplicationTab;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/ContactAssociationTab/index.jsx",
    "content": "import React from 'react';\nimport classnames from 'classnames';\nimport { Icon } from '../../../../../../components';\nimport { getTabUrl } from '../../../../../../modules/tab';\nimport Tab from '../Tab';\nimport NavbarItem from '../NavbarItem';\nimport ItemLink from '../ItemLink';\nimport ItemButton from '../ItemButton';\n\nclass ContactAssociationTab extends Tab {\n  render() {\n    const { className, isActive, tab, routeConfig } = this.props;\n\n    const { label, address } = tab.getMatch({ routeConfig }).params;\n    const tabLabel = routeConfig.tab.renderLabel({ label, address });\n\n    return (\n      <NavbarItem\n        className={classnames('m-tab', className)}\n        active={isActive}\n        contentChildren={\n          <ItemLink\n            to={getTabUrl(tab.location)}\n            title={tabLabel}\n            className=\"m-tab__content\"\n          >\n            <Icon type=\"address-book\" className=\"tab__icon\" rightSpaced />\n            {tabLabel}\n          </ItemLink>\n        }\n        actionChildren={\n          <ItemButton\n            onClick={this.handleRemove}\n            icon=\"remove\"\n            className=\"m-tab__action\"\n          />\n        }\n      />\n    );\n  }\n}\n\nexport default ContactAssociationTab;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/ContactTab/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { useQuery } from 'react-query';\nimport { getContact, getQueryKeys } from 'src/modules/contact/query';\nimport { Icon } from 'src/components';\nimport { getTabUrl } from 'src/modules/tab';\nimport Tab from '../Tab';\nimport NavbarItem from '../NavbarItem';\nimport ItemLink from '../ItemLink';\nimport ItemButton from '../ItemButton';\n\ntype Props = React.ComponentProps<typeof Tab>;\n\nfunction ContactTab({\n  className,\n  isActive,\n  tab,\n  routeConfig,\n  onRemove,\n}: Props): React.ReactElement<typeof NavbarItem> {\n  const { contactId } = tab.getMatch({ routeConfig }).params;\n  const { data } = useQuery(getQueryKeys({ contactId }), () =>\n    getContact(contactId)\n  );\n\n  const contact = data?.data;\n\n  const label = routeConfig.tab.renderLabel({ contact });\n\n  return (\n    <NavbarItem\n      className={classnames('m-tab', className)}\n      active={isActive}\n      contentChildren={\n        <ItemLink\n          to={getTabUrl(tab.location)}\n          title={label}\n          className=\"m-tab__content\"\n        >\n          <Icon type=\"address-book\" className=\"m-tab__icon\" rightSpaced />\n          {label}\n        </ItemLink>\n      }\n      actionChildren={\n        <ItemButton\n          onClick={() => {\n            onRemove({ tab });\n          }}\n          icon=\"remove\"\n          className=\"m-tab__action\"\n        />\n      }\n    />\n  );\n}\n\nexport default ContactTab;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/DiscussionTab/index.jsx",
    "content": "import React from 'react';\nimport classnames from 'classnames';\nimport { connect } from 'react-redux';\nimport { createSelector } from 'reselect';\nimport { AvatarLetter } from '../../../../../../modules/avatar';\nimport { getTabUrl } from '../../../../../../modules/tab';\nimport Tab from '../Tab';\nimport NavbarItem from '../NavbarItem';\nimport ItemLink from '../ItemLink';\nimport ItemButton from '../ItemButton';\n\nconst discussionStateSelector = (state) => state.discussion;\nconst tabSelector = (state, props) => props.tab;\nconst routeConfigSelector = (state, props) => props.routeConfig;\n\nconst mapStateToProps = createSelector(\n  [discussionStateSelector, tabSelector, routeConfigSelector],\n  (discussionState, tab, routeConfig) => ({\n    discussion:\n      discussionState.discussionsById[\n        tab.getMatch({ routeConfig }).params.discussionId\n      ],\n  })\n);\n\n@connect(mapStateToProps)\nclass DiscussionTab extends Tab {\n  render() {\n    const { className, isActive, tab, routeConfig, discussion } = this.props;\n\n    const label = routeConfig.tab.renderLabel({ discussion });\n\n    return (\n      <NavbarItem\n        className={classnames('m-tab', className)}\n        active={isActive}\n        contentChildren={\n          <ItemLink\n            to={getTabUrl(tab.location)}\n            title={label}\n            className=\"m-tab__content\"\n          >\n            {discussion && (\n              <AvatarLetter className=\"m-tab__icon\" word={label} />\n            )}\n            {label}\n          </ItemLink>\n        }\n        actionChildren={\n          <ItemButton\n            onClick={this.handleRemove}\n            icon=\"remove\"\n            className=\"m-tab__action\"\n          />\n        }\n      />\n    );\n  }\n}\n\nexport default DiscussionTab;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/ItemButton/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Button } from '../../../../../../components';\nimport './style.scss';\n\nclass ItemButton extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  render() {\n    const { className, ...props } = this.props;\n\n    return (\n      <Button className={classnames(className, 'm-item-button')} {...props} />\n    );\n  }\n}\n\nexport default ItemButton;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/ItemButton/style.scss",
    "content": "@import '../../../../../../styles/common';\n\n.m-item-button {\n  display: inline-block;\n  color: $co-color__primary--low;\n  line-height: $co-component__height;\n\n  &:hover {\n    background: none;\n    color: $co-color__primary--high;\n  }\n\n  &:active {\n    color: $co-color__primary--lower;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/ItemLink/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Link } from '../../../../../../components';\nimport './style.scss';\n\nclass ItemLink extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  render() {\n    const { className, ...props } = this.props;\n\n    return (\n      <Link\n        className={classnames('m-item-link', className)}\n        noDecoration\n        {...props}\n      />\n    );\n  }\n}\n\nexport default ItemLink;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/ItemLink/style.scss",
    "content": "@import '../../../../../../styles/common';\n\n.m-item-link {\n  display: inline-block;\n  color: inherit;\n\n  &:hover {\n    color: $co-color__primary;\n  }\n\n  &:active {\n    color: $co-color__primary--lower;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/NavbarItem/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nconst renderComponent = (C, className) => {\n  const { className: originalClassName, ...props } = C.props;\n  const componentProps = {\n    ...props,\n    className: classnames(className, originalClassName),\n  };\n\n  return <C.type {...componentProps} />;\n};\n\nclass NavbarItem extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n    active: PropTypes.bool,\n    contentChildren: PropTypes.node,\n    actionChildren: PropTypes.node,\n    children: PropTypes.node,\n    color: PropTypes.oneOf(['secondary', 'contrasted']),\n  };\n\n  static defaultProps = {\n    className: undefined,\n    active: false,\n    contentChildren: null,\n    actionChildren: null,\n    children: null,\n    color: undefined,\n  };\n\n  state = {};\n\n  render() {\n    const {\n      className,\n      active,\n      contentChildren,\n      actionChildren,\n      children,\n      color,\n      ...props\n    } = this.props;\n\n    const navbarItemProps = {\n      ...props,\n      className: classnames('m-navbar-item', className, {\n        'm-navbar-item--is-active': active,\n        'm-navbar-item--secondary': color === 'secondary',\n        'm-navbar-item--contrasted': color === 'contrasted',\n      }),\n    };\n\n    return (\n      <div {...navbarItemProps}>\n        {contentChildren &&\n          renderComponent(contentChildren, 'm-navbar-item__content')}\n        {actionChildren &&\n          renderComponent(actionChildren, 'm-navbar-item__action')}\n        {children}\n      </div>\n    );\n  }\n}\n\nexport default NavbarItem;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/NavbarItem/style.scss",
    "content": "@import '../../../../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../../../../styles/object/o-navigation';\n@import '../../../../../../styles/object/o-pi-border';\n\n$m-navbar-item__content--max-width: 12rem !default;\n$m-navbar-item-height: 2.75rem + (rem_calc($pi-border__size) * 2); // 44px + 3px * 2\n$m-navbar-item-height--active: 3.125rem + (rem_calc($pi-border__size) * 2); // 50px + 3px * 2\n\n.m-navbar-item {\n  $baseClassName: &;\n\n  display: flex;\n  height: $m-navbar-item-height;\n  margin-top: 5px;\n  margin-left: 2px;\n  background-color: #e1f5ff;\n  line-height: $m-navbar-item-height;\n\n  &:first-of-type {\n    margin-left: 0;\n  }\n\n  &--secondary {\n    background-color: #f4f4f4;\n  }\n\n  &--contrasted {\n    background-color: #8dceec;\n    color: white;\n    &#{$baseClassName}--is-active {\n      color: #115972;\n    }\n  }\n\n  &--is-active {\n    position: relative;\n    z-index: $l-z-index__active-tab;\n    height: $m-navbar-item-height--active;\n    margin-top: 0;\n    margin-left: 0;\n    background-color: white;\n    line-height: $m-navbar-item-height--active;\n\n    & + #{$baseClassName} {\n      margin-left: 0;\n    }\n  }\n\n  &__content {\n    flex: 1 0;\n    max-width: $m-navbar-item__content--max-width;\n    padding-right: calc($co-margin / 4);\n    padding-left: calc($co-margin / 2);\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  &__action {\n    flex: 0 1;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/SearchTab/index.jsx",
    "content": "import React from 'react';\nimport classnames from 'classnames';\nimport { getTabUrl } from '../../../../../../modules/tab';\nimport { URLSearchParams } from '../../../../../../modules/routing';\nimport { Icon } from '../../../../../../components';\nimport Tab from '../Tab';\nimport NavbarItem from '../NavbarItem';\nimport ItemLink from '../ItemLink';\nimport ItemButton from '../ItemButton';\n\nclass SearchTab extends Tab {\n  render() {\n    const { className, isActive, tab, routeConfig } = this.props;\n\n    const searchParams = new URLSearchParams(tab.location.search);\n\n    const label = routeConfig.tab.renderLabel({\n      term: searchParams.get('term'),\n    });\n\n    return (\n      <NavbarItem\n        className={classnames('m-tab', className)}\n        active={isActive}\n        color=\"secondary\"\n        contentChildren={\n          <ItemLink\n            to={getTabUrl(tab.location)}\n            title={label}\n            className=\"m-tab__content\"\n          >\n            <Icon className=\"m-tab__icon\" type={routeConfig.tab.icon} />\n            {label}\n          </ItemLink>\n        }\n        actionChildren={\n          <ItemButton\n            onClick={this.handleRemove}\n            icon=\"remove\"\n            className=\"m-tab__action\"\n          />\n        }\n      />\n    );\n  }\n}\n\nexport default SearchTab;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/Tab/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Icon } from '../../../../../../components';\nimport { getTabUrl, Tab as TabModel } from '../../../../../../modules/tab';\nimport ItemLink from '../ItemLink';\nimport ItemButton from '../ItemButton';\nimport NavbarItem from '../NavbarItem';\nimport './style.scss';\n\nclass Tab extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n    onRemove: PropTypes.func,\n    isActive: PropTypes.bool.isRequired,\n    tab: PropTypes.instanceOf(TabModel).isRequired,\n    routeConfig: PropTypes.shape({\n      tab: PropTypes.shape({\n        renderLabel: PropTypes.func,\n        icon: PropTypes.string,\n      }),\n    }).isRequired,\n  };\n\n  static defaultProps = {\n    className: undefined,\n    onRemove: () => {\n      // noop\n    },\n  };\n\n  handleRemove = () => {\n    const { tab, onRemove } = this.props;\n    onRemove({ tab });\n  };\n\n  render() {\n    const {\n      className,\n      isActive,\n      routeConfig,\n      tab: { location },\n    } = this.props;\n\n    return (\n      <NavbarItem\n        className={classnames('m-tab', className)}\n        active={isActive}\n        color=\"secondary\"\n        contentChildren={\n          <ItemLink\n            to={getTabUrl(location)}\n            title={routeConfig.tab.renderLabel()}\n            className=\"m-tab__content\"\n          >\n            <Icon className=\"m-tab__icon\" type={routeConfig.tab.icon} />\n            {routeConfig.tab.renderLabel()}\n          </ItemLink>\n        }\n        actionChildren={\n          <ItemButton\n            onClick={this.handleRemove}\n            icon=\"remove\"\n            className=\"m-tab__action\"\n          />\n        }\n      />\n    );\n  }\n}\n\nexport default Tab;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/Tab/style.scss",
    "content": "@import '../../../../../../styles/common';\n@import '../../../../../../styles/object/o-navigation';\n@import '../../../../../../styles/object/o-pi-border';\n\n.m-tab {\n  border-top: $pi-border__size solid $pi-border__color;\n\n  &__icon {\n    margin-right: $co-component__spacing;\n\n    &::before {\n      display: inline-block;\n      vertical-align: middle;\n      width: calc($co-component__height / 1.5);\n      height: calc($co-component__height / 1.5);\n      border-radius: calc($co-component__height / 3);\n      line-height: calc($co-component__height / 1.5);\n      text-align: center;\n    }\n  }\n\n  @each $name, $color in $privacy-palette {\n    &--#{$name} {\n      border-top: $pi-border__size solid $color;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navbar/components/index.js",
    "content": "export { default as ApplicationTab } from './ApplicationTab';\nexport { default as ContactAssociationTab } from './ContactAssociationTab';\nexport { default as ContactTab } from './ContactTab';\nexport { default as DiscussionTab } from './DiscussionTab';\nexport { default as ItemLink } from './ItemLink';\nexport { default as ItemButton } from './ItemButton';\nexport { default as SearchTab } from './SearchTab';\nexport { default as Tab } from './Tab';\nexport { default as NavbarItem } from './NavbarItem';\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navigation/index.jsx",
    "content": "import React from 'react';\nimport { compose } from 'redux';\nimport Presenter from './presenter';\nimport { RoutingConsumer } from '../../../../modules/routing';\nimport { withTabs } from './withTabs';\n\nconst withRoutes = () => (C) =>\n  function (props) {\n    return (\n      <RoutingConsumer\n        render={({ routes }) => <C routes={routes} {...props} />}\n      />\n    );\n  };\n\nexport default compose(withTabs(), withRoutes())(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navigation/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport HorizontalScroll from '../HorizontalScroll';\nimport {\n  Tab,\n  NavbarItem,\n  ApplicationTab,\n  DiscussionTab,\n  ContactAssociationTab,\n  ContactTab,\n  SearchTab,\n} from '../Navbar/components';\nimport { Button, Icon } from '../../../../components';\nimport { Tab as TabModel, withCurrentTab } from '../../../../modules/tab';\nimport { withPush, findTabbableRouteConfig } from '../../../../modules/routing';\nimport './style.scss';\n\n@withPush()\n@withCurrentTab()\nclass Navigation extends Component {\n  static propTypes = {\n    className: PropTypes.string,\n    currentTab: PropTypes.shape({}),\n    isSticky: PropTypes.bool,\n    tabs: PropTypes.arrayOf(PropTypes.instanceOf(TabModel)).isRequired,\n    routes: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n    removeTab: PropTypes.func.isRequired,\n    push: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    className: undefined,\n    currentTab: undefined,\n    isSticky: false,\n  };\n\n  getTabIdentifier = ({ pathname, search, hash }) =>\n    `${pathname}${search}${hash}`;\n\n  handleClickCompose = () => {\n    this.props.push('/compose');\n  };\n\n  renderTab({ tab }) {\n    const { removeTab, routes, currentTab } = this.props;\n    const routeConfig = findTabbableRouteConfig({\n      pathname: tab.location.pathname,\n      routes,\n    });\n    const isActive = currentTab === tab;\n\n    switch (routeConfig.tab.type) {\n      case 'application':\n        return (\n          <ApplicationTab\n            key={this.getTabIdentifier(tab.location)}\n            tab={tab}\n            routeConfig={routeConfig}\n            isActive={isActive}\n          />\n        );\n      case 'contact-association':\n        return (\n          <ContactAssociationTab\n            key={this.getTabIdentifier(tab.location)}\n            tab={tab}\n            routeConfig={routeConfig}\n            isActive={isActive}\n            onRemove={removeTab}\n          />\n        );\n      case 'contact':\n        return (\n          <ContactTab\n            key={this.getTabIdentifier(tab.location)}\n            tab={tab}\n            routeConfig={routeConfig}\n            isActive={isActive}\n            onRemove={removeTab}\n          />\n        );\n      case 'discussion':\n        return (\n          <DiscussionTab\n            key={this.getTabIdentifier(tab.location)}\n            tab={tab}\n            routeConfig={routeConfig}\n            isActive={isActive}\n            onRemove={removeTab}\n          />\n        );\n      case 'search':\n        return (\n          <SearchTab\n            key={this.getTabIdentifier(tab.location)}\n            tab={tab}\n            routeConfig={routeConfig}\n            isActive={isActive}\n            onRemove={removeTab}\n          />\n        );\n      default:\n        return (\n          <Tab\n            key={this.getTabIdentifier(tab.location)}\n            tab={tab}\n            routeConfig={routeConfig}\n            isActive={isActive}\n            onRemove={removeTab}\n          />\n        );\n    }\n  }\n\n  render() {\n    const { tabs, className, isSticky } = this.props;\n    const subscribedState = { tabs, isSticky };\n\n    return (\n      <div className={classnames('l-navigation', className)}>\n        <HorizontalScroll subscribedState={subscribedState}>\n          {tabs.map((tab) => this.renderTab({ tab }))}\n          <NavbarItem\n            key=\"compose-button\"\n            className={classnames('l-navigation__compose-item', {\n              'l-navigation__compose-item--sticky': isSticky,\n            })}\n          >\n            <Button\n              onClick={this.handleClickCompose}\n              shape=\"plain\"\n              display=\"expanded\"\n              className=\"l-navigation__compose-btn\"\n            >\n              <Icon type=\"pencil\" /> <Icon type=\"plus\" />\n            </Button>\n          </NavbarItem>\n        </HorizontalScroll>\n      </div>\n    );\n  }\n}\n\nexport default Navigation;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navigation/style.scss",
    "content": "@import '../../../../styles/object/o-pi-border';\n\n.l-navigation {\n  margin-bottom: $pi-border__size * -1;\n\n  &::after {\n    display: block;\n    content: ' ';\n    position: relative;\n    top: $pi-border__size * -1;\n    z-index: 1;\n    border-bottom: $pi-border__size solid $pi-border__color;\n  }\n\n  &__compose-item {\n    display: none;\n    border-top: none;\n\n    &--sticky {\n      display: inline-block;\n    }\n  }\n\n  &__compose-btn {\n    padding: 0 0.5rem;\n    color: white;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/Navigation/withTabs.jsx",
    "content": "import React from 'react';\nimport { TabConsumer } from '../../../../modules/tab';\n\nexport const withTabs = () => (C) =>\n  function (props) {\n    return (\n      <TabConsumer\n        render={({ tabs, removeTab }) => (\n          <C tabs={tabs} removeTab={removeTab} {...props} />\n        )}\n      />\n    );\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/NotificationCenter/index.jsx",
    "content": "import React from 'react';\nimport { Notify } from 'react-redux-notify';\nimport 'react-redux-notify/dist/ReactReduxNotify.css';\nimport './style.scss';\n\nconst notificationCustomStyles = {\n  containerTopRight: 'l-notification-center',\n};\n\nfunction NotificationCenter() {\n  return <Notify customStyles={notificationCustomStyles} />;\n}\n\nexport default NotificationCenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/NotificationCenter/style.scss",
    "content": "@import '../../../../styles/vendor/bootstrap_react-redux-notify';\n\n.l-notification-center {\n  // overrides react-redux-notify/src/components/Notify/Notify.scss\n  @include container;\n\n  top: 50px; // cf. header\n  right: 0;\n  flex-direction: column;\n\n  &__notification {\n    // see react-redux-notify/src/components/Notification/Notification.scss\n    &--has-close {\n      // overrides `.has-close .item--message`\n      .l-notification-center__notification-item-message {\n        padding-right: 10px + 24px; // right space for close icon\n      }\n    }\n\n    &--has-close-all {\n      // overrides `.has-close-all .item--message`\n      .l-notification-center__notification-item-message {\n        padding-right: 10px * 2 + 95px; // right space for close icon and button 'close all'\n      }\n    }\n  }\n\n  &__notification-item-message {\n    // see react-redux-notify/src/components/Notification/Notification.scss\n    // overrides `.item__message`\n    max-height: $max-height;\n    padding: 10px;\n    font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Roboto, Arial,\n      sans-serif;\n    font-size: 0.9rem;\n    -webkit-font-smoothing: antialiased;\n    font-weight: 700;\n    overflow-y: auto;\n\n    p:first-child {\n      margin-top: 0;\n    }\n\n    p:last-child {\n      margin-bottom: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/TakeATour/components/Guide/index.jsx",
    "content": "/*\n  eslint-disable semi, arrow-parens, no-unused-vars, arrow-body-style, no-mixed-operators,\n  no-plusplus, newline-before-return, no-shadow, no-nested-ternary\n*/\nimport styled from 'styled-components';\nimport * as hx from 'reactour/dist/helpers';\n\nconst Guide = styled.div`\n  position: fixed;\n  transition: transform 0.3s;\n  top: 0;\n  left: 0;\n  z-index: 1000000;\n  max-width: 331px;\n  min-width: 150px;\n  outline: 0;\n  transform: ${(props) => {\n    const {\n      targetTop,\n      targetRight,\n      targetBottom,\n      targetLeft,\n      targetWidth,\n      targetHeight,\n      windowWidth,\n      windowHeight,\n      helperWidth,\n      helperHeight,\n      helperPosition,\n      padding,\n    } = props;\n\n    const available = {\n      left: targetLeft,\n      right: windowWidth - targetRight,\n      top: targetTop,\n      bottom: windowHeight - targetBottom,\n    };\n\n    const couldPositionAt = (position) => {\n      return (\n        available[position] >\n        (hx.isHoriz(position)\n          ? helperWidth + padding * 2\n          : helperHeight + padding * 2)\n      );\n    };\n\n    const autoPosition = (coords) => {\n      const positionsOrder = hx.bestPositionOf(available);\n      for (let j = 0; j < positionsOrder.length; j++) {\n        if (couldPositionAt(positionsOrder[j])) {\n          return coords[positionsOrder[j]];\n        }\n      }\n      return coords.center;\n    };\n\n    const pos = (helperPosition) => {\n      const outsideY = targetTop + helperHeight > windowHeight;\n      const hX = hx.isOutsideX(targetLeft + helperWidth, windowWidth)\n        ? hx.isOutsideX(targetRight + padding, windowWidth)\n          ? targetRight - helperWidth\n          : targetRight - helperWidth + padding\n        : targetLeft - padding;\n      const hY = hx.isOutsideY(targetTop + helperHeight, windowHeight)\n        ? hx.isOutsideY(targetBottom + padding, windowHeight)\n          ? targetBottom - helperHeight\n          : targetBottom - helperHeight + padding\n        : targetTop - padding;\n      const coords = {\n        top: [hX, targetTop - helperHeight - padding * 2],\n        right: [targetRight + padding * 2, hY],\n        bottom: [hX, targetBottom + padding * 2],\n        left: [targetLeft - helperWidth - padding * 2, hY],\n        center: [\n          windowWidth / 2 - helperWidth / 2,\n          windowHeight / 2 - helperHeight / 2,\n        ],\n      };\n      if (helperPosition === 'center' || couldPositionAt(helperPosition)) {\n        return coords[helperPosition];\n      }\n      return autoPosition(coords);\n    };\n\n    const p = pos(helperPosition);\n\n    return `translate(${p[0]}px, ${p[1]}px)`;\n  }};\n`;\n\nexport default Guide;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/TakeATour/components/Tour/index.jsx",
    "content": "import React from 'react';\nimport TourBase from 'reactour';\nimport ReactDOM from 'react-dom';\nimport TourPortal from '../TourPortal';\n\nconst renderSubtreeIntoContainer = ReactDOM.unstable_renderSubtreeIntoContainer;\n\nclass Tour extends TourBase {\n  renderPortal(props) {\n    if (props.isOpen) {\n      document.body.classList.add('reactour__body');\n    } else {\n      document.body.classList.remove('reactour__body');\n    }\n\n    this.portal = renderSubtreeIntoContainer(\n      this,\n      <TourPortal {...props} />,\n      this.node\n    );\n  }\n}\n\nexport default Tour;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/TakeATour/components/TourPortal/index.jsx",
    "content": "/* eslint-disable no-unneeded-ternary, no-nested-ternary */\nimport React from 'react';\n// extends https://github.com/elrumordelaluz/reactour/blob/master/src/TourPortal.js\nimport TourPortalBase from 'reactour/dist/TourPortal';\nimport { Trans } from '@lingui/react';\nimport { Navigation, Dot, SvgMask, Controls } from 'reactour/dist/components';\nimport cn from 'classnames';\nimport Guide from '../Guide';\nimport { Button } from '../../../../../../components';\nimport './style.scss';\n\nconst CN = {\n  mask: {\n    base: 'm-tour__mask',\n    isOpen: 'm-tour__mask--is-open',\n  },\n  helper: {\n    base: 'm-tour-portal',\n    isOpen: 'm-tour-portal--is-open',\n  },\n};\n\nclass TourPortal extends TourPortalBase {\n  render() {\n    const {\n      className,\n      steps,\n      maskClassName,\n      showButtons,\n      showCloseButton,\n      showNavigation,\n      showNavigationNumber,\n      showNumber,\n      onRequestClose,\n      maskSpace,\n      lastStepNextButton,\n      nextButton,\n      prevButton,\n      badgeContent,\n      highlightedMaskClassName,\n      disableInteraction,\n      disableDotsNavigation,\n      nextStep,\n      prevStep,\n      rounded,\n      accentColor,\n    } = this.props;\n\n    const {\n      isOpen,\n      current,\n      inDOM,\n      top: targetTop,\n      right: targetRight,\n      bottom: targetBottom,\n      left: targetLeft,\n      width: targetWidth,\n      height: targetHeight,\n      w: windowWidth,\n      h: windowHeight,\n      helperWidth,\n      helperHeight,\n      helperPosition,\n    } = this.state;\n\n    if (isOpen) {\n      return (\n        <div>\n          <div\n            role=\"button\"\n            tabIndex={-1}\n            ref={(c) => {\n              this.mask = c;\n            }}\n            onClick={this.maskClickHandler}\n            onKeyPress={this.maskClickHandler}\n            className={cn(CN.mask.base, {\n              [CN.mask.isOpen]: isOpen,\n            })}\n          >\n            <SvgMask\n              windowWidth={windowWidth}\n              windowHeight={windowHeight}\n              targetWidth={targetWidth}\n              targetHeight={targetHeight}\n              targetTop={targetTop}\n              targetLeft={targetLeft}\n              padding={maskSpace}\n              rounded={rounded}\n              className={maskClassName}\n              disableInteraction={\n                disableInteraction && steps[current].stepInteraction\n                  ? !steps[current].stepInteraction\n                  : disableInteraction\n              }\n              disableInteractionClassName={`${CN.mask.disableInteraction} ${highlightedMaskClassName}`}\n            />\n          </div>\n          <Guide\n            ref={this.helper}\n            targetHeight={targetHeight}\n            targetWidth={targetWidth}\n            targetTop={targetTop}\n            targetRight={targetRight}\n            targetBottom={targetBottom}\n            targetLeft={targetLeft}\n            windowWidth={windowWidth}\n            windowHeight={windowHeight}\n            helperWidth={helperWidth}\n            helperHeight={helperHeight}\n            helperPosition={helperPosition}\n            padding={maskSpace}\n            tabIndex={-1}\n            current={current}\n            style={steps[current].style ? steps[current].style : {}}\n            rounded={rounded}\n            className={cn(CN.helper.base, className, {\n              [CN.helper.isOpen]: isOpen,\n            })}\n            accentColor={accentColor}\n          >\n            {showNumber && (\n              // overide Badge with className\n              <div className=\"m-tour-portal__badge\">\n                {typeof badgeContent === 'function'\n                  ? badgeContent(current + 1, steps.length)\n                  : current + 1}\n              </div>\n            )}\n            {/* append close section */}\n            {showCloseButton && (\n              <div className=\"m-tour-portal__close\">\n                <Button onClick={onRequestClose} icon=\"remove\">\n                  <span className=\"show-for-sr\">\n                    <Trans id=\"take-a-tour.action.close\" message=\"Close\" />\n                  </span>\n                </Button>\n              </div>\n            )}\n            {/* add className */}\n            <div className=\"m-tour-portal__content\">\n              {steps[current] &&\n                (typeof steps[current].content === 'function'\n                  ? steps[current].content({\n                      goTo: this.gotoStep,\n                      inDOM,\n                      step: current + 1,\n                    })\n                  : steps[current].content)}\n            </div>\n            <div className=\"m-tour-portal__controls\">\n              {(showButtons || showNavigation) && (\n                <Controls data-tour-elem=\"controls\">\n                  {showButtons && (\n                    // replace SvgArrow components by simple Buttons\n                    <Button\n                      onClick={\n                        typeof prevStep === 'function'\n                          ? prevStep\n                          : this.prevStep\n                      }\n                      disabled={current === 0}\n                    >\n                      {prevButton}\n                    </Button>\n                  )}\n\n                  {showNavigation && (\n                    <Navigation data-tour-elem=\"navigation\">\n                      {steps.map((s, i) => (\n                        <Dot\n                          key={`${s.selector ? s.selector : 'undef'}_${i}`}\n                          onClick={() => this.gotoStep(i)}\n                          current={current}\n                          index={i}\n                          disabled={current === i || disableDotsNavigation}\n                          showNumber={showNavigationNumber}\n                          data-tour-elem=\"dot\"\n                        />\n                      ))}\n                    </Navigation>\n                  )}\n\n                  {showButtons && (\n                    <Button\n                      onClick={\n                        current === steps.length - 1\n                          ? lastStepNextButton\n                            ? onRequestClose\n                            : () => {\n                                // noop\n                              }\n                          : typeof nextStep === 'function'\n                          ? nextStep\n                          : this.nextStep\n                      }\n                      disabled={\n                        !lastStepNextButton && current === steps.length - 1\n                      }\n                    >\n                      {lastStepNextButton && current === steps.length - 1\n                        ? lastStepNextButton\n                        : nextButton\n                        ? nextButton\n                        : null}\n                    </Button>\n                  )}\n                </Controls>\n              )}\n            </div>\n          </Guide>\n          {this.props.children}\n        </div>\n      );\n    }\n\n    return <div />;\n  }\n}\n\nexport default TourPortal;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/TakeATour/components/TourPortal/style.scss",
    "content": "@import '../../../../../../styles/common';\n\n.m-tour-portal {\n  display: flex;\n  flex-flow: row wrap;\n  background: $co-color__fg__back;\n  color: $co-color__fg__text--lower;\n\n  &__badge,\n  &__close {\n    border-bottom: 1px solid $co-color__bg__back;\n  }\n\n  &__badge {\n    flex: 1 0;\n    padding-left: map_get($co-form__spacing, 'medium');\n    line-height: $co-component__height;\n  }\n\n  &__close {\n    flex: 0 1;\n  }\n\n  &__content {\n    flex: 1 0 100%;\n    padding-right: map_get($co-form__spacing, 'medium');\n    padding-left: map_get($co-form__spacing, 'medium');\n  }\n\n  &__controls {\n    flex: 1 0 100%;\n    padding-right: map_get($co-form__spacing, 'medium');\n    padding-bottom: map_get($co-form__spacing, 'small');\n    padding-left: map_get($co-form__spacing, 'medium');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/TakeATour/index.js",
    "content": "import { compose } from 'redux';\nimport { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default compose(withI18n())(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/TakeATour/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport Tour from './components/Tour';\nimport { Button } from '../../../../components';\nimport './style.scss';\n\nclass TakeATour extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({\n      _: PropTypes.func,\n    }).isRequired,\n  };\n\n  static defaultProps = {};\n\n  state = {\n    isTourActive: false,\n  };\n\n  handleToggleTour = () => {\n    this.setState((prevState) => ({ isTourActive: !prevState.isTourActive }));\n  };\n\n  handleChangeStep = (step) => {\n    this.setState({ tourStep: step });\n  };\n\n  handleclose = () => {\n    this.setState({ isTourActive: false });\n  };\n\n  render() {\n    const { i18n } = this.props;\n\n    const steps = [\n      {\n        selector: '.s-timeline',\n        content: (\n          <div>\n            <h2>\n              <Trans id=\"take-a-tour.step.intro.title\" message=\"Welcome!\" />\n            </h2>\n            <div>\n              <Trans\n                id=\"take-a-tour.step.intro.content\"\n                message={`\n                  <0>\n                    With using Caliopen, you can access to all of your private\n                    messages (Email, and more to come) through a single login.\n                  </0>\n                  <1>\n                    Now, take a look at our main features, such as unified message\n                    management, intuitive search and more!\n                  </1>\n                  <2>\n                    The first time, the timeline might look a bit empty, you can\n                    easily <3>add a provider</3>.\n                  </2>\n                `}\n                components={[\n                  <p />,\n                  <p />,\n                  <p />,\n                  // FIXME: Link cannot work outside a Router\n                  <span />,\n                  // <Link to=\"/user/identities\" />,\n                ]}\n              />\n            </div>\n          </div>\n        ),\n        position: 'center',\n      },\n      {\n        selector: '.m-page-actions__search-field',\n        content: (\n          <div>\n            <h2>\n              <Trans\n                id=\"take-a-tour.step.search.title\"\n                message=\"Intuitive search\"\n              />\n            </h2>\n            <div>\n              <Trans\n                id=\"take-a-tour.step.search.content\"\n                message={`\n                <0>\n                  Every search can include filters. All of the unencrypted data\n                  can be searched.\n                </0>\n                <1>\n                  Here you can search everything in your messages and contacts.\n                </1>\n              `}\n                components={[<p />, <p />]}\n              />\n            </div>\n          </div>\n        ),\n      },\n      {\n        selector: '.m-user-menu',\n        content: (\n          <div>\n            <h2>\n              <Trans\n                id=\"take-a-tour.step.user-menu.title\"\n                message=\"Account menu\"\n              />\n            </h2>\n            <div>\n              <Trans\n                id=\"take-a-tour.step.user-menu.content\"\n                message={`\n                <0>\n                  Keep up-to-date your account information and manage your\n                  settings from here!\n                </0>\n                <1>Customize your application in your settings.</1>\n                <2>And connect providers like Gmail or Twitter.</2>\n              `}\n                components={[<p />, <p />, <p />]}\n              />\n            </div>\n          </div>\n        ),\n        position: 'bottom',\n      },\n      {\n        selector: '.m-page-actions__action-btns',\n        content: (\n          <div>\n            <h2>\n              <Trans\n                id=\"take-a-tour.step.call-to-action.title\"\n                message=\"Create quickly\"\n              />\n            </h2>\n            <div>\n              <p>\n                <Trans\n                  id=\"take-a-tour.step.call-to-action.content\"\n                  message=\"Create a new message on the fly.\"\n                />\n              </p>\n            </div>\n          </div>\n        ),\n      },\n      {\n        selector: '.l-header__take-a-tour',\n        content: (\n          <div>\n            <h2>\n              <Trans\n                id=\"take-a-tour.step.install.title\"\n                message=\"Installation\"\n              />\n            </h2>\n            <div>\n              <Trans\n                id=\"take-a-tour.step.install.content\"\n                message={`\n                <0>If available, the installation button will be displayed.</0>\n                <1>\n                  On android and ios it will add Caliopen to your homescreen. On\n                  desktop, this feature is available for chrome and chromium.\n                </1>\n                <2>\n                  This technology is called «Progressive Web App», it will make\n                  the app faster to load and some features will be available\n                  offline and it will help to interract with your device (like\n                  the notification feature) if your are agree.\n                </2>\n              `}\n                components={[<p />, <p />, <p />]}\n              />\n            </div>\n          </div>\n        ),\n        position: 'center',\n      },\n    ];\n\n    return (\n      <Button\n        onClick={this.handleToggleTour}\n        icon=\"question-circle\"\n        display=\"expanded\"\n        className=\"m-take-a-tour\"\n      >\n        <Trans id=\"take-a-tour.action.toggle\" message=\"Take a tour\" />\n        <Tour\n          isOpen={this.state.isTourActive}\n          step={this.state.tourStep}\n          onRequestClose={this.handleclose}\n          steps={steps}\n          // eslint-disable-next-line react/no-unstable-nested-components\n          badgeContent={(current, total) => (\n            <Trans\n              id=\"take-a-tour.current-step\"\n              message=\"Take a tour ({current} of {total})\"\n              values={{ current, total }}\n            />\n          )}\n          showNavigation={false}\n          skipButton={i18n._(/* i18n */ 'take-a-tour.action.skip', null, {\n            message: 'Skip',\n          })}\n          prevButton={i18n._(/* i18n */ 'take-a-tour.action.prev', null, {\n            message: 'Previous',\n          })}\n          nextButton={i18n._(/* i18n */ 'take-a-tour.action.next', null, {\n            message: 'Next',\n          })}\n          lastStepNextButton={i18n._(\n            /* i18n */ 'take-a-tour.action.last-step',\n            null,\n            {\n              message: 'Finish',\n            }\n          )}\n          closeButton={i18n._(/* i18n */ 'take-a-tour.action.close', null, {\n            message: 'Close',\n          })}\n        />\n      </Button>\n    );\n  }\n}\n\nexport default TakeATour;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/components/TakeATour/style.scss",
    "content": ".m-take-a-tour {\n  text-align: left;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/header.scss",
    "content": "@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.l-header {\n  height: $co-component__height;\n  background-color: #e1f5ff;\n  line-height: $co-component__height;\n\n  &__container {\n    @include flex-grid-row;\n  }\n\n  &__brand-link {\n    @include flex-grid-size(expand);\n  }\n\n  &__user-menu,\n  &__take-a-tour {\n    @include flex-grid-size(shrink);\n  }\n\n  &__brand {\n    display: inline-block;\n    vertical-align: middle;\n    height: 29px;\n    margin-left: 0.25rem;\n\n    @include breakpoint(medium) {\n      margin-left: 0;\n    }\n  }\n\n  &__button-label {\n    display: none;\n\n    @include breakpoint(medium) {\n      display: inline;\n    }\n  }\n\n  // &__notif-menu {\n  //   display: inline-block;\n  //   float: right;\n  //   .m-button {\n  //     color: #115972;\n  //   }\n  // }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/index.jsx",
    "content": "import React, { Component, createRef } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport {\n  Brand,\n  Link,\n  Button,\n  withDropdownControl,\n  Dropdown,\n  VerticalMenu,\n  VerticalMenuItem,\n  Icon,\n} from '../../components';\nimport { BackgroundImage } from '../../modules/pi';\nimport { TabProvider } from '../../modules/tab';\nimport { PageActions } from '../../modules/control';\nimport { UserMenu } from '../../modules/user';\nimport { ScrollDetector } from '../../modules/scroll';\nimport InstallButton from './components/InstallButton';\nimport Navigation from './components/Navigation';\nimport Footer from './components/Footer';\nimport NotificationCenter from './components/NotificationCenter';\nimport TakeATour from './components/TakeATour';\nimport PageContainer from '../PageContainer';\nimport './style.scss';\nimport './header.scss';\nimport './navbar.scss';\n\nconst DropdownControl = withDropdownControl(Button);\n\n// eslint-disable-next-line react/prefer-stateless-function\nclass Page extends Component {\n  static propTypes = {\n    children: PropTypes.node.isRequired,\n  };\n\n  state = {\n    isDropdownHelpOpen: false,\n  };\n\n  dropdownControlRef = createRef();\n\n  handleDropdownToggle = (isDropdownHelpOpen) => {\n    this.setState({ isDropdownHelpOpen });\n  };\n\n  render() {\n    const { children } = this.props;\n\n    return (\n      <TabProvider>\n        <BackgroundImage context=\"secure\" className=\"l-page\">\n          {/* <div className=\"header-device\">\n            <PageContainer>\n              Vous consultez actuellement vos messages dans un environement que vous avez classé\n              comme <Link href=\"#\">sûr</Link>, depuis un <Link href=\"#\">appareil de\n              confiance.</Link>\n            </PageContainer>\n          </div> */}\n          <div className=\"l-header\">\n            <PageContainer className=\"l-header__container\">\n              <Link to=\"/\" className=\"l-header__brand-link\">\n                <Brand className=\"l-header__brand\" responsive />\n              </Link>\n              {/* <div className=\"l-header__notif-menu\"><Button href=\"#\"><Icon type=\"bell\"\n              /></Button></div> */}\n              <div className=\"l-header__take-a-tour\">\n                <DropdownControl\n                  ref={this.dropdownControlRef}\n                  icon=\"info-circle\"\n                  display=\"inline-block\"\n                >\n                  <span className=\"l-header__button-label\">\n                    <Trans id=\"header.help.menu\" message=\"Help & info\" />\n                  </span>\n                  <Icon\n                    type={\n                      this.state.isDropdownHelpOpen ? 'caret-up' : 'caret-down'\n                    }\n                  />\n                </DropdownControl>\n                <Dropdown\n                  dropdownControlRef={this.dropdownControlRef}\n                  alignRight\n                  isMenu\n                  hasTriangle\n                  closeOnClick=\"all\"\n                  onToggle={this.handleDropdownToggle}\n                  displayFirstLayer\n                >\n                  <VerticalMenu>\n                    {/* FIXME: unable to get the router outside of the App */}\n                    <VerticalMenuItem>\n                      <TakeATour />\n                    </VerticalMenuItem>\n                    <VerticalMenuItem>\n                      <Link\n                        button\n                        display=\"expanded\"\n                        href=\"https://github.com/CaliOpen/Caliopen/blob/master/CHANGELOG.md\"\n                        target=\"_blank\"\n                      >\n                        <Trans\n                          id=\"header.help.last-changes\"\n                          message=\"Last changes\"\n                        />\n                      </Link>\n                    </VerticalMenuItem>\n                    <VerticalMenuItem>\n                      <Link\n                        button\n                        display=\"expanded\"\n                        href=\"https://feedback.caliopen.org/t/questions-frequemment-posees-frequently-asked-questions/162\"\n                        target=\"_blank\"\n                      >\n                        <Trans id=\"header.help.faq\" message=\"FAQ [fr]\" />\n                      </Link>\n                    </VerticalMenuItem>\n                    <VerticalMenuItem>\n                      <Link\n                        button\n                        display=\"expanded\"\n                        href=\"https://feedback.caliopen.org\"\n                        target=\"_blank\"\n                      >\n                        <Trans id=\"header.help.feedback\" message=\"Feedback\" />\n                      </Link>\n                    </VerticalMenuItem>\n                    <VerticalMenuItem>\n                      <Link\n                        button\n                        display=\"expanded\"\n                        href=\"/privacy-policy.html\"\n                        target=\"_blank\"\n                      >\n                        <Trans\n                          id=\"header.help.privacy-policy\"\n                          message=\"Privacy Policy\"\n                        />\n                      </Link>\n                    </VerticalMenuItem>\n                    <InstallButton />\n                  </VerticalMenu>\n                </Dropdown>\n              </div>\n              <div className=\"l-header__user-menu\">\n                <UserMenu />\n              </div>\n            </PageContainer>\n          </div>\n\n          <PageContainer>\n            <PageActions className=\"l-page__main-actions\" />\n          </PageContainer>\n\n          <ScrollDetector\n            offset={136}\n            render={(isSticky) => (\n              <div className=\"l-navbar\">\n                <div\n                  className={classnames('l-navbar__wrapper', {\n                    'l-navbar__wrapper--sticky': isSticky,\n                  })}\n                >\n                  <PageContainer>\n                    <Navigation isSticky={isSticky} />\n                  </PageContainer>\n                </div>\n              </div>\n            )}\n          />\n\n          <PageContainer>{children}</PageContainer>\n\n          <PageContainer>\n            <Footer />\n          </PageContainer>\n        </BackgroundImage>\n        <NotificationCenter />\n      </TabProvider>\n    );\n  }\n}\n\nexport default Page;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/navbar.scss",
    "content": "@import '../../styles/object/o-navigation';\n@import '../../styles/object/o-pi-border';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$item-active-height: 3.125rem + (rem_calc($pi-border__size) * 2); // $m-navbar-item-height--active\n$item-height: 2.75rem + (rem_calc($pi-border__size) * 2); // $m-navbar-item-height\n\n.l-navbar {\n  height: $item-active-height;\n\n  &__wrapper {\n    z-index: $l-z-index__navbar;\n\n    &--sticky {\n      position: fixed;\n      top: 0;\n      width: 100%;\n      background-color: #d9f2fe;\n\n      // XXX: refactor\n      .m-horizontal-scroll {\n        height: $item-height;\n        background-image: none;\n      }\n\n      .m-navbar-item,\n      .tab {\n        margin-top: 0;\n      }\n\n      .m-navbar-item--is-active {\n        height: $item-height;\n\n        .m-item-link {\n          line-height: $item-height;\n        }\n      }\n\n      .m-horizontal-scroll__container {\n        background: #fff;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Page/style.scss",
    "content": ".l-page {\n  &__main-actions {\n    padding-top: 1.625rem;\n    padding-bottom: 1.25rem;\n  }\n}\n\n// .header-device {\n//   background-color: #2bc1ac;\n//   height: 40px;\n//   line-height: 40px;\n//   font-size: 14px;\n//\n//   &,\n//   .m-link {\n//     color: black;\n//     font-weight: normal;\n//   }\n// }\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/PageContainer/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nclass PageContainer extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n    children: PropTypes.node.isRequired,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  render() {\n    const { children, className } = this.props;\n\n    return (\n      <div className={classnames(className, 'l-page-container')}>\n        {children}\n      </div>\n    );\n  }\n}\n\nexport default PageContainer;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/PageContainer/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/util/breakpoint';\n\n.l-page-container {\n  max-width: $co-site-width;\n  margin-right: auto;\n  margin-left: auto;\n  padding-right: 0;\n  padding-left: 0;\n\n  @include breakpoint(medium) {\n    padding-right: map_get($co-page-container-paddings, 'medium');\n    padding-left: map_get($co-page-container-paddings, 'medium');\n  }\n\n  @include breakpoint(xlarge) {\n    padding-right: map_get($co-page-container-paddings, 'xlarge');\n    padding-left: map_get($co-page-container-paddings, 'xlarge');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/SearchResults/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/SearchResults/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport { Trans } from '@lingui/react';\nimport PropTypes from 'prop-types';\nimport { matchPath, withRouter } from 'react-router-dom';\nimport MenuBar from '../../components/MenuBar';\n\n@withRouter\nclass SearchResults extends PureComponent {\n  static propTypes = {\n    location: PropTypes.shape({\n      pathname: PropTypes.string.isRequired,\n      search: PropTypes.string.isRequired,\n    }).isRequired,\n    term: PropTypes.string,\n    searchResultsPreview: PropTypes.shape({\n      total: PropTypes.number,\n      contact_hits: PropTypes.shape({\n        total: PropTypes.number,\n      }),\n      messages_hits: PropTypes.shape({\n        total: PropTypes.number,\n      }),\n    }),\n    children: PropTypes.node,\n  };\n\n  static defaultProps = {\n    term: '',\n    searchResultsPreview: {\n      contact_hits: {\n        total: 0,\n      },\n      messages_hits: {\n        total: 0,\n      },\n      total: 0,\n    },\n    children: null,\n  };\n\n  render() {\n    const {\n      children,\n      location: { pathname, search },\n      term,\n      searchResultsPreview: {\n        total,\n        contact_hits: { total: nbContacts },\n        messages_hits: { total: nbMessages },\n      },\n    } = this.props;\n\n    const location = `${pathname}${search}`;\n\n    const navLinks = [\n      {\n        key: 'search-results.all',\n        label: (\n          <Trans id=\"search-results.all\" values={{ total }}>\n            All ({total})\n          </Trans>\n        ),\n        to: `/search-results?term=${term}`,\n      },\n      {\n        key: 'search-results.messages',\n        label: (\n          <Trans id=\"search-results.messages\" values={{ nbMessages }}>\n            Messages ({nbMessages})\n          </Trans>\n        ),\n        to: `/search-results?term=${term}&doctype=message`,\n      },\n      {\n        key: 'search-results.contacts',\n        label: (\n          <Trans id=\"search-results.contacts\" values={{ nbContacts }}>\n            Contacts ({nbContacts})\n          </Trans>\n        ),\n        to: `/search-results?term=${term}&doctype=contact`,\n      },\n    ].map((link) => ({\n      ...link,\n      isActive:\n        matchPath(location, { path: link.to, exact: false, strict: false }) &&\n        true,\n    }));\n\n    return (\n      <div className=\"l-search-results\">\n        <MenuBar className=\"l-search-results__menu-bar\" navLinks={navLinks} />\n        <div className=\"l-search-results__panel\">{children}</div>\n      </div>\n    );\n  }\n}\n\nexport default SearchResults;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/SearchResults/style.scss",
    "content": "@import '../../styles/common';\n\n.l-search-results {\n  &__panel {\n    padding-top: $co-margin;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Settings/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Settings/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { matchPath, withRouter } from 'react-router-dom';\nimport { withI18n } from '@lingui/react';\nimport MenuBar from '../../components/MenuBar';\nimport './style.scss';\n\n@withI18n()\n@withRouter\nclass Settings extends PureComponent {\n  static propTypes = {\n    location: PropTypes.shape({\n      pathname: PropTypes.string,\n    }).isRequired,\n    children: PropTypes.node,\n    i18n: PropTypes.shape({\n      _: PropTypes.func,\n    }).isRequired,\n  };\n\n  static defaultProps = {\n    children: null,\n  };\n\n  render() {\n    const {\n      i18n,\n      children,\n      location: { pathname },\n    } = this.props;\n\n    const navLinks = [\n      {\n        key: 'settings.application',\n        label: i18n._(/* i18n */ 'settings.application', null, {\n          message: 'Application',\n        }),\n        to: '/settings/application',\n      },\n      {\n        key: 'settings.tags',\n        label: i18n._(/* i18n */ 'settings.tags', null, { message: 'Tags' }),\n        to: '/settings/tags',\n      },\n      {\n        key: 'settings.devices',\n        label: i18n._(/* i18n */ 'settings.devices', null, {\n          message: 'Devices',\n        }),\n        to: '/settings/devices',\n      },\n      // { key: 'settings.signatures', label: i18n._(/* i18n */ 'settings.signatures', null,\n      // { message: 'Signatures' }),\n      // to: '/settings/signatures' },\n    ].map((link) => ({\n      ...link,\n      isActive:\n        matchPath(pathname, { path: link.to, exact: false, strict: false }) &&\n        true,\n    }));\n\n    return (\n      <div className=\"l-settings\">\n        <MenuBar navLinks={navLinks} />\n        <div>{children}</div>\n      </div>\n    );\n  }\n}\n\nexport default Settings;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/Settings/style.scss",
    "content": "@import '../../styles/common';\n\n.l-settings {\n  background-color: $co-color__fg__back;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/User/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/User/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { matchPath, withRouter } from 'react-router-dom';\nimport { withI18n } from '@lingui/react';\nimport MenuBar from '../../components/MenuBar';\n\nimport './styles.scss';\n\n@withI18n()\n@withRouter\nclass User extends PureComponent {\n  static propTypes = {\n    location: PropTypes.shape({\n      pathname: PropTypes.string.isRequired,\n    }).isRequired,\n    children: PropTypes.node,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {\n    children: null,\n  };\n\n  render() {\n    const {\n      i18n,\n      children,\n      location: { pathname },\n    } = this.props;\n\n    const navLinks = [\n      {\n        key: 'user.identities',\n        label: i18n._(/* i18n */ 'settings.identities', null, {\n          message: 'External accounts',\n        }),\n        to: '/user/identities',\n      },\n      {\n        key: 'user.profile',\n        label: i18n._(/* i18n */ 'user.profile', null, { message: 'Profile' }),\n        to: '/user/profile',\n      },\n      // { key: 'user.privacy', label: i18n._(/* i18n */ 'user.privacy', null, { message: 'Privacy' }), to:\n      // '/user/privacy' },\n      {\n        key: 'user.security',\n        label: i18n._(/* i18n */ 'user.security', null, {\n          message: 'Security',\n        }),\n        to: '/user/security',\n      },\n    ].map((link) => ({\n      ...link,\n      isActive:\n        matchPath(pathname, { path: link.to, exact: false, strict: false }) &&\n        true,\n    }));\n\n    return (\n      <div className=\"l-user\">\n        <MenuBar className=\"l-user__menu-bar\" navLinks={navLinks} />\n        <div className=\"l-user__panel\">{children}</div>\n      </div>\n    );\n  }\n}\n\nexport default User;\n"
  },
  {
    "path": "src/frontend/web_application/src/layouts/User/styles.scss",
    "content": "@import '../../styles/common';\n\n.l-user {\n  background-color: $co-color__fg__back;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/a11y/index.js",
    "content": "export * from './services/tabIndexes';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/a11y/services/tabIndexes.js",
    "content": "export const draftExcerptIndex = 0;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/AuthorAvatarLetter/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { renderParticipant, getAuthor } from '../../../../services/message';\nimport AvatarLetterWrapper from '../AvatarLetterWrapper';\nimport AvatarLetter from '../AvatarLetter';\n\nclass AuthorAvatarLetter extends PureComponent {\n  static propTypes = {\n    message: PropTypes.shape({}).isRequired,\n  };\n\n  static defaultProps = {};\n\n  render() {\n    const { message, ...props } = this.props;\n    const participant = getAuthor(message);\n\n    return (\n      <AvatarLetterWrapper {...props}>\n        <AvatarLetter word={renderParticipant(participant)} />\n      </AvatarLetterWrapper>\n    );\n  }\n}\n\nexport default AuthorAvatarLetter;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/AuthorAvatarLetter/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport AuthorAvatarLetter from '.';\n\ndescribe('component ContactAvatarLetter', () => {\n  it('render', () => {\n    const comp = shallow(\n      <AuthorAvatarLetter\n        message={{ participants: [{ label: 'Foobar', type: 'From' }] }}\n      />\n    );\n\n    expect(comp.find('AvatarLetter').length).toEqual(1);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/AvatarLetter/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport getClassName from './services/stylesheet-helper';\nimport './style.scss';\n\nfunction AvatarLetter({ word, className, isSelected }) {\n  return (\n    <span\n      className={classnames(\n        className,\n        isSelected ? 'm-letter--checked' : getClassName(word)\n      )}\n    />\n  );\n}\n\nAvatarLetter.propTypes = {\n  word: PropTypes.string,\n  className: PropTypes.string,\n  isSelected: PropTypes.bool,\n};\n\nAvatarLetter.defaultProps = {\n  word: null,\n  className: null,\n  isSelected: false,\n};\n\nexport default AvatarLetter;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/AvatarLetter/services/stylesheet-helper/index.js",
    "content": "export default function getClassName(word, defaultLetter = 'none') {\n  let letter = defaultLetter;\n\n  if (word) {\n    letter = word.substr(0, 1).toLowerCase();\n  }\n\n  if ('abcdefghijklmnopqrstuvwxyz'.indexOf(letter) === -1) {\n    letter = letter === '+' ? 'plus' : defaultLetter;\n  }\n\n  return `m-letter--${letter}`;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/AvatarLetter/services/stylesheet-helper/index.spec.js",
    "content": "import getClassName from './index';\n\ndescribe('Service Helper StylesheetHelper', () => {\n  describe('getClassName', () => {\n    it('make stylesheet class from undefined', () => {\n      expect(getClassName()).toEqual('m-letter--none');\n    });\n\n    it('make stylesheet class from standard letter', () => {\n      expect(getClassName('fry')).toEqual('m-letter--f');\n    });\n\n    it('make stylesheet class from non-standard letter', () => {\n      expect(getClassName('ß-Ligatur')).toEqual('m-letter--none');\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/AvatarLetter/style.scss",
    "content": "@import '../../../../styles/common';\n\n// ok\n.m-letter--none {\n  &::before {\n    content: ' ';\n    height: 100%;\n    background-color: steelblue;\n  }\n}\n\n.m-letter--checked {\n  &::before {\n    content: '✓';\n    background-color: $co-color__primary--low;\n  }\n}\n\n.m-letter--plus {\n  &::before {\n    content: '+';\n    background-color: darkcyan;\n  }\n}\n\n.m-letter--a {\n  &::before {\n    content: 'A';\n    background-color: cornflowerblue;\n  }\n}\n\n.m-letter--b {\n  &::before {\n    content: 'B';\n    background-color: slategray;\n  }\n}\n\n.m-letter--c {\n  &::before {\n    content: 'C';\n    background-color: peru;\n  }\n}\n\n.m-letter--d {\n  &::before {\n    content: 'D';\n    background-color: tomato;\n  }\n}\n\n.m-letter--e {\n  &::before {\n    content: 'E';\n    background-color: firebrick;\n  }\n}\n\n.m-letter--f {\n  &::before {\n    content: 'F';\n    background-color: forestgreen;\n  }\n}\n\n.m-letter--g {\n  &::before {\n    content: 'G';\n    background-color: darkorchid;\n  }\n}\n\n.m-letter--h {\n  &::before {\n    content: 'H';\n    background-color: darkslateblue;\n  }\n}\n\n.m-letter--i {\n  &::before {\n    content: 'I';\n    background-color: palevioletred;\n  }\n}\n\n.m-letter--j {\n  &::before {\n    content: 'J';\n    background-color: mediumorchid;\n  }\n}\n\n.m-letter--k {\n  &::before {\n    content: 'K';\n    background-color: darkolivegreen;\n  }\n}\n\n.m-letter--l {\n  &::before {\n    content: 'L';\n    background-color: lightskyblue;\n  }\n}\n\n.m-letter--m {\n  &::before {\n    content: 'M';\n    background-color: darkkhaki;\n  }\n}\n\n.m-letter--n {\n  &::before {\n    content: 'N';\n    background-color: olivedrab;\n  }\n}\n\n.m-letter--o {\n  &::before {\n    content: 'O';\n    background-color: mediumpurple;\n  }\n}\n\n.m-letter--p {\n  &::before {\n    content: 'P';\n    background-color: cadetblue;\n  }\n}\n\n.m-letter--q {\n  &::before {\n    content: 'Q';\n    background-color: saddlebrown;\n  }\n}\n\n.m-letter--r {\n  &::before {\n    content: 'R';\n    background-color: midnightblue;\n  }\n}\n\n.m-letter--s {\n  &::before {\n    content: 'S';\n    background-color: teal;\n  }\n}\n\n.m-letter--t {\n  &::before {\n    content: 'T';\n    background-color: turquoise;\n  }\n}\n\n.m-letter--u {\n  &::before {\n    content: 'U';\n    background-color: orangered;\n  }\n}\n\n.m-letter--v {\n  &::before {\n    content: 'V';\n    background-color: seagreen;\n  }\n}\n\n.m-letter--w {\n  &::before {\n    content: 'W';\n    background-color: olive;\n  }\n}\n\n.m-letter--x {\n  &::before {\n    content: 'X';\n    background-color: darkmagenta;\n  }\n}\n\n.m-letter--y {\n  &::before {\n    content: 'Y';\n    background-color: indigo;\n  }\n}\n\n.m-letter--z {\n  &::before {\n    content: 'Z';\n    background-color: chocolate;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/AvatarLetterWrapper/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nexport const SIZE_SMALL = 'small';\nexport const SIZE_MEDIUM = 'medium';\nexport const SIZE_LARGE = 'large';\nexport const SIZE_XLARGE = 'xlarge';\nexport const SIZE_XXLARGE = 'xxlarge';\n\nclass AvatarLetterWrapper extends PureComponent {\n  static propTypes = {\n    children: PropTypes.shape({}),\n    size: PropTypes.oneOf([\n      SIZE_SMALL,\n      SIZE_MEDIUM,\n      SIZE_LARGE,\n      SIZE_XLARGE,\n      SIZE_XXLARGE,\n    ]),\n    isRound: PropTypes.bool,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    children: undefined,\n    size: undefined,\n    isRound: true,\n    className: undefined,\n  };\n\n  render() {\n    const { children, className, size, isRound, ...props } = this.props;\n    const classNameShape = classnames({\n      'm-avatar-letter-wrapper--round': isRound,\n    });\n    const classNameModifiers = {\n      [SIZE_SMALL]: 'm-avatar-letter-wrapper--small',\n      [SIZE_MEDIUM]: 'm-avatar-letter-wrapper--medium',\n      [SIZE_LARGE]: 'm-avatar-letter-wrapper--large',\n      [SIZE_XLARGE]: 'm-avatar-letter-wrapper--xlarge',\n      [SIZE_XXLARGE]: 'm-avatar-letter-wrapper--xxlarge',\n    };\n    const classNameSize = classNameModifiers[size];\n    const letterClassNameSize = classNameSize\n      ? `${classNameSize}__letter`\n      : null;\n    const avatarLetter = React.Children.map(children, (child) => {\n      const childProps = {\n        ...child.props,\n        ...props,\n        className: classnames(\n          'm-avatar-letter-wrapper__letter',\n          letterClassNameSize\n        ),\n      };\n\n      return <child.type {...childProps} />;\n    });\n\n    return (\n      <div\n        className={classnames(\n          'm-avatar-letter-wrapper',\n          className,\n          classNameSize,\n          classNameShape\n        )}\n      >\n        {avatarLetter}\n      </div>\n    );\n  }\n}\n\nexport default AvatarLetterWrapper;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/AvatarLetterWrapper/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-avatar';\n\n.m-avatar-letter-wrapper {\n  display: inline-block;\n  vertical-align: middle;\n  border-radius: $co-radius;\n  background-color: $co-color__primary--low;\n  overflow: hidden;\n\n  &__letter {\n    &::before {\n      display: block;\n      color: $co-color__contrast__text--high;\n      text-align: center;\n    }\n  }\n\n  @include m-avatar--size(medium);\n\n  &--small {\n    @include m-avatar--size(small);\n  }\n\n  &--medium {\n    @include m-avatar--size(medium);\n  }\n\n  &--large {\n    @include m-avatar--size(large);\n  }\n\n  &--xlarge {\n    @include m-avatar--size(xlarge);\n  }\n\n  &--round {\n    border-radius: 50%;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/ContactAvatarLetter/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { formatName } from 'src/modules/contact/services/format';\nimport AvatarLetterWrapper from '../AvatarLetterWrapper';\nimport AvatarLetter from '../AvatarLetter';\n\nclass ContactAvatarLetter extends PureComponent {\n  static propTypes = {\n    contact: PropTypes.shape({}).isRequired,\n    contactDisplayFormat: PropTypes.string,\n  };\n\n  static defaultProps = {\n    contactDisplayFormat: 'title',\n  };\n\n  render() {\n    const { contact, contactDisplayFormat: format, ...props } = this.props;\n\n    return (\n      <AvatarLetterWrapper {...props}>\n        <AvatarLetter word={formatName({ contact, format })} />\n      </AvatarLetterWrapper>\n    );\n  }\n}\n\nexport default ContactAvatarLetter;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/components/ContactAvatarLetter/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport ContactAvatarLetter from '.';\n\ndescribe('component ContactAvatarLetter', () => {\n  it('render', () => {\n    const comp = shallow(<ContactAvatarLetter contact={{ title: 'Foobar' }} />);\n\n    expect(comp.find('AvatarLetter').length).toEqual(1);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/index.js",
    "content": "export { default as ContactAvatarLetter } from './components/ContactAvatarLetter';\nexport { default as AuthorAvatarLetter } from './components/AuthorAvatarLetter';\nexport { default as AvatarLetter } from './components/AvatarLetter';\nexport * from './components/AvatarLetterWrapper';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/services/stylesheet-helper/index.js",
    "content": "export default function getClassName(word, defaultLetter = 'none') {\n  let letter = defaultLetter;\n\n  if (word) {\n    letter = word.substr(0, 1).toLowerCase();\n  }\n\n  if ('abcdefghijklmnopqrstuvwxyz'.indexOf(letter) === -1) {\n    letter = defaultLetter;\n  }\n\n  return `m-letter--${letter}`;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/avatar/services/stylesheet-helper/index.spec.js",
    "content": "import getClassName from './index';\n\ndescribe('Service Helper StylesheetHelper', () => {\n  describe('getClassName', () => {\n    it('make stylesheet class from undefined', () => {\n      expect(getClassName()).toEqual('m-letter--none');\n    });\n\n    it('make stylesheet class from standard letter', () => {\n      expect(getClassName('fry')).toEqual('m-letter--f');\n    });\n\n    it('make stylesheet class from non-standard letter', () => {\n      expect(getClassName('ß-Ligatur')).toEqual('m-letter--none');\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/actions/deleteContacts.js",
    "content": "import {\n  deleteContact as deleteContactBase,\n  invalidate,\n  removeMultipleFromCollection,\n} from '../store/reducer';\n\nexport const deleteContacts =\n  ({ contacts }) =>\n  async (dispatch) => {\n    try {\n      const results = await Promise.all(\n        contacts.map((contact) =>\n          dispatch(deleteContactBase({ contactId: contact.contact_id }))\n        )\n      );\n\n      await dispatch(removeMultipleFromCollection({ contacts }));\n\n      return results;\n    } catch (err) {\n      await dispatch(invalidate());\n\n      return Promise.reject(err);\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/actions/getContact.ts",
    "content": "import { AppDispatch, GetState } from 'src/types';\nimport { requestContact } from '../store/reducer';\nimport { contactSelector } from '../selectors/contactSelector';\n\nexport const getContact =\n  ({ contactId }: { contactId: string }) =>\n  async (dispatch: AppDispatch, getState: GetState) => {\n    const contact = contactSelector(getState(), contactId);\n\n    if (contact) {\n      return contact;\n    }\n\n    await dispatch(requestContact(contactId));\n\n    return contactSelector(getState(), contactId);\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/actions/loadMoreContacts.ts",
    "content": "import { AppDispatch } from 'src/types';\n\nimport { getNextOffset, requestContacts } from '../store/reducer';\n\nexport const loadMoreContacts = () => (dispatch: AppDispatch, getState) => {\n  const offset = getNextOffset(getState().contacts);\n\n  return dispatch(requestContacts({ offset }));\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/actions/updateContact.js",
    "content": "import { requestUser } from 'src/modules/user/store/reducer';\nimport {\n  updateContact as updateContactBase,\n  requestContact,\n} from '../store/reducer';\nimport { userSelector } from '../../user';\n\n/**\n * @deprecated, use useMutation\n */\nexport const updateContact =\n  ({ contact, original }) =>\n  async (dispatch, getState) => {\n    await dispatch(updateContactBase({ contact, original }));\n    const userContact = userSelector(getState()).contact;\n\n    if (userContact.contact_id === contact.contact_id) {\n      dispatch(requestUser());\n    }\n\n    return dispatch(requestContact(contact.contact_id));\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/components/ContactList/components/ContactItem/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { Trans, useLingui } from '@lingui/react';\nimport { ContactAvatarLetter, SIZE_MEDIUM } from 'src/modules/avatar';\nimport {\n  getCleanedTagCollection,\n  getTagLabel,\n  useTags,\n} from 'src/modules/tags';\nimport { Button, Link, TextBlock, Icon, Checkbox, Badge } from 'src/components';\nimport { formatName } from 'src/modules/contact';\nimport { Contact } from 'src/modules/contact/types';\nimport { useSettings } from 'src/modules/settings';\nimport './style.scss';\n\nconst TYPE_FACEBOOK = 'facebook';\nconst TYPE_TWITTER = 'twitter';\nconst TYPE_MASTODON = 'mastodon';\n\nconst getAddress = ({ attrName, attr }) => {\n  switch (attrName) {\n    case 'emails':\n      return {\n        type: 'email',\n        identifier: attr.address,\n      };\n\n    case 'phones':\n      return {\n        type: 'phone',\n        identifier: attr.number,\n      };\n\n    case 'identities':\n      switch (attr.type) {\n        case TYPE_FACEBOOK:\n        case TYPE_TWITTER:\n        case TYPE_MASTODON:\n        default:\n          return {\n            type: attr.type,\n            identifier: attr.name,\n          };\n      }\n\n    case 'ims':\n      return {\n        type: 'comment',\n        identifier: attr.address,\n      };\n    default:\n      console.warn('Unable to render the main address for:');\n      console.table(attr);\n\n      return undefined;\n  }\n};\n\nconst getMainAddresses = ({ contact }) =>\n  ['emails', 'phones', 'identities', 'ims'].reduce((acc, attrName) => {\n    if (acc.length === 2) {\n      return acc;\n    }\n\n    if (!contact[attrName]) {\n      return acc;\n    }\n\n    const mainAddress = contact[attrName].reduce((attrAcc, attr) => {\n      if (!attrAcc) {\n        return getAddress({ attrName, attr });\n      }\n\n      if (attr.is_primary) {\n        return getAddress({ attrName, attr });\n      }\n\n      return attrAcc;\n    }, undefined);\n\n    if (!mainAddress) {\n      return acc;\n    }\n\n    return [...acc, mainAddress];\n  }, []);\n\ninterface Props {\n  contact: Contact;\n  className?: string;\n  onSelectEntity?: (action: 'add' | 'remove', contactId: string) => void;\n  onClickContact?: (contact: Contact) => void;\n  isContactSelected?: boolean;\n  selectDisabled: boolean;\n}\nfunction ContactItem({\n  contact,\n  className,\n  onSelectEntity,\n  onClickContact,\n  isContactSelected,\n  selectDisabled,\n}: Props) {\n  const { i18n } = useLingui();\n  const { tags } = useTags();\n  const { contact_display_format } = useSettings();\n\n  const onCheckboxChange = (ev) => {\n    const { checked } = ev.target;\n\n    if (onSelectEntity) {\n      onSelectEntity(checked ? 'add' : 'remove', contact.contact_id);\n    }\n  };\n\n  const handleClickContact = () => {\n    if (onClickContact) {\n      onClickContact(contact);\n    }\n  };\n\n  const renderClickable = (clikableProps) =>\n    onClickContact ? (\n      <Button noDecoration onClick={handleClickContact} {...clikableProps} />\n    ) : (\n      <Link\n        noDecoration\n        to={`/contacts/${contact.contact_id}`}\n        {...clikableProps}\n      />\n    );\n\n  const contactTitle = formatName({ contact, format: contact_display_format });\n  const mainAddresses = getMainAddresses({ contact });\n\n  return (\n    <div className={classnames('m-contact-item', className)}>\n      {renderClickable({\n        className: 'm-contact-item__title',\n        children: (\n          <>\n            <div className=\"m-contact-item__avatar\">\n              <ContactAvatarLetter\n                isRound\n                contact={contact}\n                size={SIZE_MEDIUM}\n                contactDisplayFormat={contact_display_format}\n              />\n            </div>\n            <div className=\"m-contact-item__contact\">\n              {/* FIXME: name is hidden if too long */}\n              <TextBlock className=\"m-contact-item__name\">\n                {contact.name_prefix && (\n                  <span className=\"m-contact-item__contact-prefix\">\n                    {contact.name_prefix}\n                  </span>\n                )}\n                <span className=\"m-contact-item__contact-title\">\n                  {contactTitle}\n                </span>\n                {contact.name_suffix && (\n                  <span className=\"m-contact-item__contact-suffix\">\n                    ,{contact.name_suffix}\n                  </span>\n                )}\n              </TextBlock>\n              <div className=\"m-contact-item__tags\">\n                {contact.tags &&\n                  getCleanedTagCollection(tags, contact.tags).map((tag) => (\n                    <Badge key={tag.name} rightSpaced>\n                      {getTagLabel(i18n, tag)}\n                    </Badge>\n                  ))}\n              </div>\n            </div>\n          </>\n        ),\n      })}\n      <div className=\"m-contact-item__info\">\n        {mainAddresses.map((address) => (\n          <TextBlock key={address.identifier}>\n            {/* @ts-ignore */}\n            <Icon type={address.type} /> {address.identifier}\n          </TextBlock>\n        ))}\n      </div>\n      <TextBlock className=\"m-contact-item__select\">\n        {!selectDisabled && (\n          <Checkbox\n            label={\n              <Trans\n                id=\"contact-book.action.select\"\n                message=\"Select the contact\"\n              />\n            }\n            showLabelforSr\n            onChange={onCheckboxChange}\n            checked={isContactSelected}\n          />\n        )}\n      </TextBlock>\n    </div>\n  );\n}\n\nexport default ContactItem;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/components/ContactList/components/ContactItem/style.scss",
    "content": "@import 'src/styles/vendor/bootstrap_foundation-sites';\n\n.m-contact-item {\n  @include flex-grid-row($size: expanded);\n\n  align-items: center;\n\n  &__title {\n    @include flex-grid-size(10);\n    @include flex-grid-row;\n\n    align-items: center;\n    margin-left: map_get($co-margin_, 'xsmall');\n    color: $co-color__fg__text--high;\n\n    &:hover {\n      background-color: $co-color__fg__back;\n      color: $co-color__fg__text--higher;\n    }\n\n    &:active {\n      background-color: $co-color__fg__back--low;\n      color: $co-color__fg__text--low;\n    }\n  }\n\n  &__avatar {\n    @include flex-grid-size(shrink);\n\n    margin-right: map_get($co-margin_, 'xsmall');\n  }\n\n  &__contact {\n    @include flex-grid-size(9);\n\n    overflow: hidden;\n  }\n\n  &__tags {\n    min-height: $co-font__size;\n    white-space: nowrap;\n  }\n\n  &__info {\n    @include flex-grid-column(12);\n\n    order: 3;\n    padding-top: map_get($co-margin_, 'xsmall');\n    color: $co-color__fg__text--higher;\n    text-align: right;\n  }\n\n  &__select {\n    @include flex-grid-size(shrink);\n  }\n\n  &__contact-title {\n    margin-right: $co-component__spacing;\n  }\n\n  &__contact-prefix,\n  &__contact-suffix {\n    margin-right: $co-component__spacing;\n    color: $co-color__fg__text--lower;\n  }\n\n  &__interactions-nb {\n    font-weight: bold;\n    text-decoration: underline;\n  }\n\n  @include breakpoint(medium) {\n    &__title {\n      @include flex-grid-column(6);\n    }\n\n    &__info {\n      @include flex-grid-column(4);\n\n      order: 0;\n      padding-top: 0;\n    }\n\n    &__select {\n      @include flex-grid-size(shrink);\n\n      margin-left: map_get($co-margin_, 'xsmall');\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/components/ContactList/components/ContactItemPlaceholder.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { PlaceholderBlock } from 'src/components';\n\ninterface Props {\n  className?: string;\n}\nexport default function ContactItemPlaceholder({ className }: Props) {\n  return (\n    <div className={classnames('m-contact-item', className)}>\n      <div className=\"m-contact-item__title\">\n        <div className=\"m-contact-item__avatar\">\n          <PlaceholderBlock shape=\"avatar\" />\n        </div>\n        <div className=\"m-contact-item__contact\">\n          <div className=\"m-contact-item__name\">\n            <PlaceholderBlock shape=\"line\" />\n          </div>\n          <div className=\"m-contact-item__tags\" />\n        </div>\n      </div>\n      <div className=\"m-contact-item__info\">\n        <PlaceholderBlock />\n      </div>\n      <div className=\"m-contact-item__select\">&nbsp;</div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/components/ContactList/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, useLingui } from '@lingui/react';\nimport classnames from 'classnames';\nimport { partition } from 'lodash';\nimport { Title, Link, PlaceholderList } from 'src/components';\nimport { useSettings } from 'src/modules/settings';\nimport { useUser } from 'src/modules/user';\nimport {\n  getFirstLetter,\n  formatName,\n  getContactTitle,\n} from '../../services/format';\nimport { useContacts } from '../../hooks/useContacts';\nimport { DEFAULT_SORT_DIR } from '../../consts';\nimport { Contact, TSortDir } from '../../types';\n\nimport ContactItem from './components/ContactItem';\nimport ContactItemPlaceholder from './components/ContactItemPlaceholder';\nimport './style.scss';\n\nconst ALPHA = '#abcdefghijklmnopqrstuvwxyz';\nconst MODE_ASSOCIATION = 'association';\nconst MODE_CONTACT_BOOK = 'contact-book';\n\ntype TMode = typeof MODE_ASSOCIATION | typeof MODE_CONTACT_BOOK;\n\nconst getNavLetter = (sortDir: TSortDir) =>\n  ALPHA.split('').sort((a, b) => {\n    switch (sortDir) {\n      default:\n      case 'ASC':\n        return (a || '').localeCompare(b);\n      case 'DESC':\n        return (b || '').localeCompare(a);\n    }\n  });\n\ninterface NavProps {\n  sortDir: TSortDir;\n  firstLettersWithContacts?: string[];\n}\nfunction Nav({ sortDir, firstLettersWithContacts = [] }: NavProps) {\n  const firstLetters = getNavLetter(sortDir);\n\n  return (\n    <div className=\"m-contact-list__nav\">\n      {firstLetters.map((letter) => {\n        const isActive = firstLettersWithContacts.includes(letter);\n\n        return (\n          <Link\n            key={letter}\n            href={`#letter-${letter}`}\n            className={classnames('m-contact-list__alpha-letter', {\n              'm-contact-list__alpha-letter--active': isActive,\n            })}\n          >\n            {letter}\n          </Link>\n        );\n      })}\n    </div>\n  );\n}\n\nconst EMPTY_OBJECT = {};\n\ninterface Props {\n  selectedContactsIds?: string[]; // mode contact_book\n  onSelectEntity?: (type: 'add' | 'remove', id: string) => void; // mode contact_book\n  onClickContact?: (contact: Contact) => void; // mode association\n  sortDir?: TSortDir;\n  mode: TMode;\n  contacts: undefined | Contact[];\n}\nfunction ContactList({\n  onClickContact,\n  onSelectEntity,\n  mode,\n  sortDir = DEFAULT_SORT_DIR,\n  selectedContactsIds,\n  contacts,\n}: Props) {\n  const { i18n } = useLingui();\n  const { contact_display_order, contact_display_format } = useSettings();\n  const { user, initialized: userInitialized } = useUser();\n  const { isFetched: contactsInitialized } = useContacts();\n\n  let userContact;\n  let contactsExceptUser = contacts;\n  if (user?.contact) {\n    const [a, b] = partition(\n      contacts,\n      (contact) => contact.contact_id === user.contact.contact_id\n    );\n    userContact = a[0];\n    contactsExceptUser = b;\n  }\n\n  const initialized = userInitialized && contactsInitialized;\n\n  if (!initialized) {\n    return (\n      <div className=\"m-contact-list\">\n        <Nav sortDir={sortDir} />\n        <div className=\"m-contact-list__list\">\n          <div className=\"m-contact-list__group\">\n            <PlaceholderList\n              renderItem={() => (\n                <ContactItemPlaceholder className=\"m-contact-list__contact\" />\n              )}\n              title={i18n._(\n                /* i18n */ 'contact-asociation.loading',\n                undefined,\n                {\n                  message: 'Contact list is loading.',\n                }\n              )}\n            />\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  const contactsGroupedByLetter =\n    contactsExceptUser\n      ?.sort((a, b) =>\n        (a[contact_display_order] || getContactTitle(a)).localeCompare(\n          b[contact_display_order] || getContactTitle(b)\n        )\n      )\n      .reduce((acc, contact) => {\n        const firstLetter = getFirstLetter(\n          contact[contact_display_order] ||\n            formatName({ contact, format: contact_display_format }),\n          '#'\n        );\n\n        return {\n          ...acc,\n          [firstLetter]: [...(acc[firstLetter] || []), contact],\n        };\n      }, {}) || EMPTY_OBJECT;\n\n  const firstLettersWithContacts = Object.keys(contactsGroupedByLetter);\n  const firstLetters = getNavLetter(sortDir);\n\n  return (\n    <div className=\"m-contact-list\">\n      <Nav\n        sortDir={sortDir}\n        firstLettersWithContacts={firstLettersWithContacts}\n      />\n      <div className=\"m-contact-list__list\">\n        {userContact && (\n          <div className=\"m-contact-list__group\">\n            <Title caps hr size=\"large\" className=\"m-contact-list__alpha-title\">\n              <Trans\n                id=\"contact-book.my-contact-details\"\n                message=\"My contact details\"\n              />\n            </Title>\n            <ContactItem\n              className=\"m-contact-list__contact\"\n              contact={userContact}\n              selectDisabled\n              onClickContact={onClickContact}\n            />\n          </div>\n        )}\n        {firstLetters.map(\n          (letter) =>\n            contactsGroupedByLetter[letter] && (\n              <div key={letter} className=\"m-contact-list__group\">\n                <Title\n                  caps\n                  hr\n                  size=\"large\"\n                  className=\"m-contact-list__alpha-title\"\n                  id={`letter-${letter}`}\n                >\n                  {letter}\n                </Title>\n                {contactsGroupedByLetter[letter].map((contact) => (\n                  <ContactItem\n                    key={contact.contact_id}\n                    className=\"m-contact-list__contact\"\n                    contact={contact}\n                    onSelectEntity={onSelectEntity}\n                    onClickContact={onClickContact}\n                    isContactSelected={\n                      selectedContactsIds &&\n                      selectedContactsIds.includes(contact.contact_id)\n                    }\n                    selectDisabled={mode === MODE_ASSOCIATION}\n                  />\n                ))}\n              </div>\n            )\n        )}\n      </div>\n    </div>\n  );\n}\n\nexport default ContactList;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/components/ContactList/style.scss",
    "content": "@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n$m-contact-list__contact-spacing: 1.25rem;\n$m-contact-list__nav-alpha-color: #ccc;\n$m-contact-list__nav-alpha-color--active: #9a9a9a;\n\n.m-contact-list {\n  &__nav {\n    display: flex;\n    justify-content: space-between;\n    margin-top: 2rem; // fixed height\n    margin-right: map_get($co-margin_, 'xsmall');\n    margin-left: map_get($co-margin_, 'xsmall');\n  }\n\n  &__alpha-letter {\n    color: $m-contact-list__nav-alpha-color;\n    font-weight: 400;\n    text-decoration: none;\n    text-transform: capitalize;\n\n    &--active {\n      color: $m-contact-list__nav-alpha-color--active;\n      font-weight: 600;\n    }\n  }\n\n  &__list {\n    margin-bottom: 2.25rem;\n  }\n\n  &__alpha-title {\n    margin-right: map_get($co-margin_, 'xsmall');\n    margin-left: map_get($co-margin_, 'xsmall');\n\n    // usefull for anchor position without incidences\n    padding-top: rem-calc($co-component__height + 0.5rem);\n    color: $m-contact-list__nav-alpha-color--active;\n  }\n\n  &__contact {\n    padding-top: $m-contact-list__contact-spacing;\n    padding-bottom: $m-contact-list__contact-spacing;\n    border-bottom: 1px solid $co-color__fg__border--higher;\n  }\n\n  @include breakpoint(medium) {\n    &__contact {\n      margin-right: map_get($co-margin_, 'large');\n      margin-left: map_get($co-margin_, 'large');\n    }\n\n    &__nav {\n      margin-right: map_get($co-margin_, 'large') / 2;\n      margin-left: map_get($co-margin_, 'large') / 2;\n    }\n\n    &__alpha-title {\n      margin-right: map_get($co-margin_, 'medium');\n      margin-left: map_get($co-margin_, 'medium');\n    }\n  }\n\n  @include breakpoint(large) {\n    &__contact {\n      margin-right: map_get($co-margin_, 'xlarge');\n      margin-left: map_get($co-margin_, 'xlarge');\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/components/WithContacts/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport Presenter from './presenter';\nimport { stateSelector, requestContacts } from '../../store';\n\nconst mapStateToProps = createSelector(\n  [stateSelector],\n  ({ contacts, contactsById, isFetching, didInvalidate }) => ({\n    contacts: contacts.map((contactId) => contactsById[contactId]),\n    isFetching,\n    didInvalidate,\n  })\n);\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      requestContacts,\n    },\n    dispatch\n  );\n\nexport default compose(connect(mapStateToProps, mapDispatchToProps))(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/components/WithContacts/presenter.jsx",
    "content": "import { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nclass WithContacts extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n    contacts: PropTypes.arrayOf(PropTypes.shape({})),\n    isFetching: PropTypes.bool,\n    didInvalidate: PropTypes.bool,\n    requestContacts: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    contacts: [],\n    isFetching: false,\n    didInvalidate: false,\n  };\n\n  state = {};\n\n  componentDidMount() {\n    const { isFetching, contacts, requestContacts, didInvalidate } = this.props;\n\n    if (!isFetching && (contacts.length === 0 || didInvalidate)) {\n      requestContacts();\n    }\n  }\n\n  render() {\n    const { render, contacts } = this.props;\n\n    return render({ contacts });\n  }\n}\n\nexport default WithContacts;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/consts.ts",
    "content": "import { TSortDir } from './types';\n\nexport const SORT_VIEW_GIVEN_NAME = 'given_name';\nexport const SORT_VIEW_FAMILY_NAME = 'family_name';\nexport const SORT_VIEW_TITLE = 'title';\nexport const DEFAULT_SORT_VIEW = SORT_VIEW_GIVEN_NAME;\nexport const DEFAULT_SORT_DIR: TSortDir = 'ASC';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/hoc/withContacts.jsx",
    "content": "import React from 'react';\nimport WithContactsBase from '../components/WithContacts';\n\nexport const withContacts = () => (WrappedComp) => {\n  function WithContacts(props) {\n    return (\n      <WithContactsBase\n        render={({ contacts }) => (\n          <WrappedComp contacts={contacts} {...props} />\n        )}\n      />\n    );\n  }\n  WithContacts.displayName = `(${\n    WrappedComp.displayName || WrappedComp.name || 'Component'\n  })`;\n\n  return WithContacts;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/hooks/useContacts.ts",
    "content": "import { AxiosResponse } from 'axios';\nimport { useIsFetching, useQuery, UseQueryResult } from 'react-query';\nimport { flatten } from 'lodash';\nimport { APIAxiosError } from 'src/services/api-client/types';\nimport { Contact, GETContactListPayload } from '../types';\nimport { getAllContactCollection, getConfigList } from '../query';\n\ntype UseContactsResult = UseQueryResult<Contact[], APIAxiosError>;\n\nconst queryConfig = getConfigList();\n\n/**\n * Load all the contacts (by chunks of 1000) and keep data for 1h.\n */\nexport function useContacts(): UseContactsResult {\n  const result = useQuery<\n    AxiosResponse<GETContactListPayload>[],\n    APIAxiosError,\n    Contact[]\n  >(queryConfig.queryKeys, () => getAllContactCollection(), {\n    // XXX: The data will not be refetched even after a window focus, it is\n    // probably required to invalidate query manually.\n    staleTime: 3600000, // 1h\n    select: (allPages) =>\n      flatten(allPages.map((response) => response.data.contacts)),\n  });\n\n  return result;\n}\n\nexport function useContactsIsFetching(): boolean {\n  return useIsFetching(queryConfig.queryKeys) > 0;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/index.ts",
    "content": "export * from './actions/deleteContacts';\nexport * from './actions/getContact';\nexport * from './actions/loadMoreContacts';\nexport * from './actions/updateContact';\nexport { default as WithContacts } from './components/WithContacts';\nexport { default as ContactList } from './components/ContactList';\nexport * as ContactListUtility from './components/ContactList';\nexport * from './hoc/withContacts';\nexport * from './selectors/contactSelector';\nexport * from './services/addAddressToContact';\nexport * from './services/form';\nexport * from './services/format';\nexport * from './services/identityTypes';\nexport * from './hooks/useContacts';\nexport * from './consts';\nexport * as store from './store';\n// does not compile:\n// export * from './types';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/query.ts",
    "content": "import { AxiosResponse } from 'axios';\nimport { flatten } from 'lodash';\nimport getClient from 'src/services/api-client';\nimport calcObjectForPatch from 'src/services/api-patch';\nimport { FetchConfig, QueryConfig, QueryKey } from 'src/types';\nimport { TagPayload } from '../tags/types';\nimport { Contact, ContactPayload, GETContactListPayload } from './types';\n\nconst client = getClient();\nexport interface PostContactSuccess {\n  location: string;\n  contact_id: string;\n}\n\nexport const getQueryKeys = ({\n  contactId,\n  fetchParams,\n}: {\n  contactId?: string;\n  fetchParams?: any;\n} = {}): QueryKey =>\n  // @ts-ignore\n  [\n    'contacts',\n    contactId,\n    fetchParams ? JSON.stringify(fetchParams) : undefined,\n  ].filter(Boolean);\n\nexport const getConfigOne = (contactId: string): FetchConfig => ({\n  url: `/api/v2/contacts/${contactId}`,\n});\n\nexport const getConfigList = (fetchParams?: any): QueryConfig => ({\n  url: `/api/v2/contacts`,\n  fetchParams,\n  queryKeys: getQueryKeys({ fetchParams }),\n});\nexport const getConfigNew = (): FetchConfig => ({\n  url: `/api/v2/contacts`,\n});\nexport const getConfigDelete = (contactId?: string): FetchConfig => ({\n  url: `/api/v2/contacts/${contactId}`,\n});\nexport const getConfigUpdateTags = (contactId?: string): FetchConfig => ({\n  url: `/api/v2/contacts/${contactId}/tags`,\n});\n\n// -----------------\n\n// QUERIES\n\nexport function getContact(contactId: string) {\n  return client.get<Contact>(getConfigOne(contactId).url);\n}\n\nexport function getContactList(fetchParams) {\n  return client.get<GETContactListPayload>(getConfigList().url, {\n    params: fetchParams,\n  });\n}\n\nconst hasMore = (contacts: Contact[], total) => contacts.length < total;\nconst getNextOffset = (contacts: Contact[]) => contacts.length;\n\nconst getNextPageParam = (\n  lastPage: AxiosResponse<GETContactListPayload>,\n  allPages: AxiosResponse<GETContactListPayload>[]\n) => {\n  const currentContacts = flatten(allPages.map((res) => res.data.contacts));\n\n  if (!hasMore(currentContacts, lastPage.data.total)) {\n    return undefined;\n  }\n  return {\n    offset: getNextOffset(currentContacts),\n  };\n};\n\nconst getContactListRecursive = async (\n  fetchParams = {},\n  allPages: AxiosResponse<GETContactListPayload>[] = []\n): Promise<AxiosResponse<GETContactListPayload>[]> => {\n  const response = await getContactList(fetchParams);\n  const nextAllPages = [...allPages, response];\n  const nextPageParams = getNextPageParam(response, nextAllPages);\n\n  if (nextPageParams) {\n    return getContactListRecursive(nextPageParams, nextAllPages);\n  }\n\n  return nextAllPages;\n};\n\nexport function getAllContactCollection() {\n  return getContactListRecursive({ limit: 1000 });\n}\n\n// MUTATIONS\n\nexport function createContact({ value: payload }: { value: ContactPayload }) {\n  return client.post<PostContactSuccess>(getConfigNew().url, payload);\n}\n\nexport function updateContact({\n  value,\n  original,\n}: {\n  value: ContactPayload;\n  original: Contact;\n}) {\n  const payload = calcObjectForPatch(value, original);\n\n  return client.patch(getConfigOne(original.contact_id).url, payload);\n}\n\nexport function deleteContact(contactId: string) {\n  return client.delete(getConfigDelete(contactId).url);\n}\n\n// --- Sub-resources\n\nexport function updateTags(contact: Contact, tags: TagPayload[]) {\n  const payload = {\n    tags: tags.map((tag) => tag.name),\n    current_state: { tags: contact.tags },\n  };\n\n  return client.patch(getConfigUpdateTags(contact.contact_id).url, payload);\n}\n\n// ------------------\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/selectors/contactSelector.ts",
    "content": "import { createSelector } from 'reselect';\nimport { RootState } from 'src/store/reducer';\nimport { stateSelector } from '../store';\nimport { Contact } from '../types';\n\nexport const contactSelector = (state: RootState, contactId: string): Contact =>\n  stateSelector(state).contactsById[contactId];\n\nexport const contactsSelector = createSelector<\n  [(state: RootState) => RootState['contact']],\n  Contact[]\n>(stateSelector, ({ contacts, contactsById }) =>\n  contacts.map((contactId) => contactsById[contactId])\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/services/addAddressToContact.js",
    "content": "import {\n  PROTOCOL_EMAIL,\n  PROTOCOL_TWITTER,\n  PROTOCOL_MASTODON,\n} from '../../remoteIdentity';\n\nexport const addAddressToContact = (contact, { address, protocol }) => {\n  switch (protocol) {\n    case PROTOCOL_EMAIL:\n      return {\n        ...contact,\n        emails: [...(contact.emails ? contact.emails : []), { address }],\n      };\n    case PROTOCOL_TWITTER:\n    case PROTOCOL_MASTODON:\n      return {\n        ...contact,\n        identities: [\n          ...(contact.identities ? contact.identities : []),\n          { type: protocol, name: address },\n        ],\n      };\n    default:\n      throw new Error(`unknown protocol \"${protocol}\"`);\n  }\n};\n\nexport const addAddressesToContact = (contact, addresses) => {\n  const emails = [\n    ...(contact.emails ? contact.emails : []),\n    ...addresses\n      .filter((address) => address.protocol === PROTOCOL_EMAIL)\n      .map((address) => ({ address: address.email })),\n  ];\n\n  const identities = [\n    ...(contact.identities ? contact.identities : []),\n    ...addresses\n      .filter((address) =>\n        [PROTOCOL_TWITTER, PROTOCOL_MASTODON].includes(address.protocol)\n      )\n      .map((address) => ({\n        address: address.identifier,\n        type: address.protocol,\n      })),\n  ];\n\n  return {\n    ...contact,\n    emails,\n    identities,\n  };\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/services/form.ts",
    "content": "import { ContactFormData } from '../types';\n\nexport const getNewContact = (): ContactFormData => ({\n  name_prefix: '',\n  given_name: '',\n  family_name: '',\n  name_suffix: '',\n  organizations: [],\n  identities: [],\n  infos: {},\n  emails: [],\n  phones: [],\n  ims: [],\n  addresses: [],\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/services/format.spec.ts",
    "content": "import * as contactService from './format';\n\ndescribe('contact services', () => {\n  describe('getFirstLetter', () => {\n    it('get an alpha letter', () => {\n      expect(contactService.getFirstLetter('foobar', '+')).toEqual('f');\n    });\n\n    it('default letter is mandatory', () => {\n      // @ts-ignore\n      expect(contactService.getFirstLetter('^azdjhk')).toEqual(undefined);\n    });\n\n    it('get the default letter for a special character', () => {\n      expect(contactService.getFirstLetter('↓azdazdþ', '+')).toEqual('+');\n    });\n\n    it('get the ascii letter for a special letter', () => {\n      expect(contactService.getFirstLetter('Éazdazdþ', '+')).toEqual('e');\n      expect(contactService.getFirstLetter('àazdazdþ', '+')).toEqual('a');\n      expect(contactService.getFirstLetter('ñazdazdþ', '+')).toEqual('n');\n    });\n  });\n\n  describe('formatName', () => {\n    const contact = {\n      contact_id: 'abcd-efgh',\n      user_id: '',\n      title: 'J.D.',\n      given_name: 'John',\n      family_name: 'Doe',\n    };\n\n    it('extract a simple property', () => {\n      expect(contactService.formatName({ contact, format: 'title' })).toEqual(\n        contact.title\n      );\n    });\n\n    it('extract properties separated by a comma', () => {\n      expect(\n        contactService.formatName({\n          contact,\n          format: 'given_name, family_name',\n        })\n      ).toEqual('John Doe');\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/services/format.ts",
    "content": "import { asciify } from 'src/services/asciify';\nimport { Contact, ContactPayload } from '../types';\n\nexport function getContactTitle(contact: Contact | ContactPayload) {\n  return contact.title || '';\n}\n\nexport function formatName({\n  contact,\n  format,\n}: {\n  contact: Contact | ContactPayload;\n  format: string;\n}) {\n  const title = format\n    .split(',')\n    .map((field) => field.trim())\n    .map((field) => contact[field])\n    .join(' ')\n    .trim();\n\n  return title || getContactTitle(contact);\n}\n\nexport function getFirstLetter(string: string, defaultLetter: string) {\n  let firstLetter = defaultLetter;\n  if (string) {\n    firstLetter = asciify(string.substr(0, 1).toLowerCase());\n  }\n\n  if (!/^[a-z]$/.test(firstLetter)) {\n    firstLetter = defaultLetter;\n  }\n\n  return firstLetter;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/services/identityTypes.js",
    "content": "export const IDENTITY_TYPE_TWITTER = 'twitter';\nexport const IDENTITY_TYPE_MASTODON = 'mastodon';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/store/index.ts",
    "content": "export * from './reducer';\nexport * from './selectors';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/store/reducer.ts",
    "content": "import { PagerParams, AxiosActionPayload, ResourceStatus } from 'src/types';\nimport calcObjectForPatch from 'src/services/api-patch';\nimport { Contact } from '../types';\n\nexport const REQUEST_CONTACTS = 'co/contact/REQUEST_CONTACTS';\nexport const REQUEST_CONTACTS_SUCCESS = 'co/contact/REQUEST_CONTACTS_SUCCESS';\nexport const REQUEST_CONTACTS_FAIL = 'co/contact/REQUEST_CONTACTS_FAIL';\nexport const INVALIDATE_CONTACTS = 'co/contact/INVALIDATE_CONTACTS';\nexport const LOAD_MORE_CONTACTS = 'co/contact/LOAD_MORE_CONTACTS';\nexport const REQUEST_CONTACT = 'co/contact/REQUEST_CONTACT';\nexport const REQUEST_CONTACT_SUCCESS = 'co/contact/REQUEST_CONTACT_SUCCESS';\nexport const UPDATE_CONTACT = 'co/contact/UPDATE_CONTACT';\nexport const UPDATE_CONTACT_SUCCESS = 'co/contact/UPDATE_CONTACT_SUCCESS';\nexport const UPDATE_CONTACT_FAIL = 'co/contact/UPDATE_CONTACT_FAIL';\nexport const CREATE_CONTACT = 'co/contact/CREATE_CONTACT';\nexport const CREATE_CONTACT_SUCCESS = 'co/contact/CREATE_CONTACT_SUCCESS';\nexport const CREATE_CONTACT_FAIL = 'co/contact/CREATE_CONTACT_FAIL';\nexport const DELETE_CONTACT = 'co/contact/DELETE_CONTACT';\nexport const UPDATE_TAGS = 'co/contact/UPDATE_TAGS';\nexport const UPDATE_TAGS_SUCCESS = 'co/contact/UPDATE_TAGS_SUCCESS';\nexport const UPDATE_TAGS_FAIL = 'co/contact/UPDATE_TAGS_FAIL';\nexport const REMOVE_MULTIPLE_FROM_COLLECTION =\n  'co/contact/REMOVE_MULTIPLE_FROM_COLLECTION';\nexport const REQUEST_CONTACT_IDS_FOR_URI =\n  'co/contact/REQUEST_CONTACT_IDS_FOR_URI';\n\n// Actions --------------------------------------\n// TODO: type axios middleware success/fail actions and extends it\n\ninterface RequestContactsAction {\n  type: typeof REQUEST_CONTACTS;\n  payload: AxiosActionPayload<PagerParams>;\n}\n\ninterface RequestContactsSuccessAction {\n  type: typeof REQUEST_CONTACTS_SUCCESS;\n  payload: {\n    data: {\n      contacts: Contact[];\n      total: number;\n    };\n  };\n}\ninterface RequestContactsFailAction {\n  type: typeof REQUEST_CONTACTS_FAIL;\n  error: any;\n}\n\ninterface RequestContactAction {\n  type: typeof REQUEST_CONTACT;\n  payload: AxiosActionPayload<undefined>;\n}\n\ninterface RequestContactSuccessAction {\n  type: typeof REQUEST_CONTACT_SUCCESS;\n  payload: {\n    data: Contact;\n  };\n}\n\ninterface RemoveMultipleFromCollectionAction {\n  type: typeof REMOVE_MULTIPLE_FROM_COLLECTION;\n  payload: {\n    contacts: Contact[];\n  };\n}\n\ninterface InvalidateAction {\n  type: typeof INVALIDATE_CONTACTS;\n  payload: Record<string, unknown>;\n}\n\ntype ContactAction =\n  | RequestContactsAction\n  | RequestContactsSuccessAction\n  | RequestContactsFailAction\n  | RequestContactAction\n  | RequestContactSuccessAction\n  | RemoveMultipleFromCollectionAction\n  | InvalidateAction;\n//  ---------------------------------------------\n\n// State ----------------------------------------\ninterface ContactState {\n  initialized: boolean;\n  status: ResourceStatus;\n  // @deprected: use status instead\n  isFetching: boolean;\n  didInvalidate: boolean;\n  // ---\n  contactsById: {\n    [id: string]: Contact;\n  };\n  contacts: string[];\n  total: number;\n}\n// ----------------------------------------------\n\nconst PROTOCOL_PREFIXES = {\n  email: 'email',\n  twitter: 'twitter',\n  mastodon: 'mastodon',\n};\n\ntype RequestContactParams = PagerParams;\nexport function requestContacts(\n  params: RequestContactParams = {}\n): RequestContactsAction {\n  const { offset = 0, limit = 1000 } = params;\n\n  return {\n    type: REQUEST_CONTACTS,\n    payload: {\n      request: {\n        url: '/api/v2/contacts',\n        params: { offset, limit },\n      },\n    },\n  };\n}\n\nexport function requestContact(contactId: string): RequestContactAction {\n  return {\n    type: REQUEST_CONTACT,\n    payload: {\n      request: {\n        url: `/api/v2/contacts/${contactId}`,\n      },\n    },\n  };\n}\n\nexport function deleteContact({ contactId }) {\n  return {\n    type: DELETE_CONTACT,\n    payload: {\n      request: {\n        method: 'delete',\n        url: `/api/v2/contacts/${contactId}`,\n      },\n    },\n  };\n}\n\nexport function invalidate() {\n  return {\n    type: INVALIDATE_CONTACTS,\n    payload: {},\n  };\n}\n\n/**\n * @deprecated, use useMutation\n */\nexport function updateContact({ contact, original }) {\n  const data = calcObjectForPatch(contact, original);\n\n  return {\n    type: UPDATE_CONTACT,\n    payload: {\n      request: {\n        method: 'patch',\n        url: `/api/v2/contacts/${contact.contact_id}`,\n        data,\n      },\n    },\n  };\n}\n\nexport function createContact({ contact }) {\n  return {\n    type: CREATE_CONTACT,\n    payload: {\n      request: {\n        method: 'post',\n        url: '/api/v2/contacts',\n        data: contact,\n      },\n    },\n  };\n}\n\n/**\n * @deprecated: use query instead\n */\nexport function updateTags({ contact, tags }) {\n  const data = {\n    tags,\n    current_state: { tags: contact.tags },\n  };\n\n  return {\n    type: UPDATE_TAGS,\n    payload: {\n      request: {\n        method: 'patch',\n        url: `/api/v2/contacts/${contact.contact_id}/tags`,\n        data,\n      },\n    },\n  };\n}\n\nexport function removeMultipleFromCollection({ contacts }) {\n  return {\n    type: REMOVE_MULTIPLE_FROM_COLLECTION,\n    payload: {\n      contacts,\n    },\n  };\n}\n\nexport function requestContactIdsForURI({ protocol, address }) {\n  const protocolPrefix = PROTOCOL_PREFIXES[protocol];\n\n  return {\n    type: REQUEST_CONTACT_IDS_FOR_URI,\n    payload: {\n      request: {\n        method: 'get',\n        url: `/api/v2/contacts?uri=${protocolPrefix}:${address}`,\n      },\n    },\n  };\n}\n\ntype ContactsByReducerAction =\n  | RequestContactsSuccessAction\n  | RequestContactSuccessAction\n  | RemoveMultipleFromCollectionAction;\n\nfunction contactsByIdReducer(state = {}, action: ContactsByReducerAction) {\n  switch (action.type) {\n    case REQUEST_CONTACTS_SUCCESS:\n      return action.payload.data.contacts.reduce(\n        (previousState, contact) => ({\n          ...previousState,\n          [contact.contact_id]: contact,\n        }),\n        state\n      );\n    case REQUEST_CONTACT_SUCCESS:\n      return {\n        ...state,\n        [action.payload.data.contact_id]: action.payload.data,\n      };\n    case REMOVE_MULTIPLE_FROM_COLLECTION:\n      return action.payload.contacts.reduce(\n        (prevState, contact) => ({\n          ...prevState,\n          [contact.contact_id]: undefined,\n        }),\n        state\n      );\n    default:\n      return state;\n  }\n}\n\nconst filterContactIds = (contactsIds: string[], contacts: Contact[]) => {\n  const contactIdsToRemove = contacts.map((contact) => contact.contact_id);\n\n  return contactsIds.filter(\n    (contactId) => !contactIdsToRemove.includes(contactId)\n  );\n};\n\ntype ContactListReducerAction =\n  | RequestContactsSuccessAction\n  | RemoveMultipleFromCollectionAction;\nfunction contactListReducer(\n  state: string[] = [],\n  action: ContactListReducerAction\n) {\n  switch (action.type) {\n    case REQUEST_CONTACTS_SUCCESS:\n      return [...state]\n        .concat(\n          action.payload.data.contacts.map((contact) => contact.contact_id)\n        )\n        .reduce((prev: string[], curr: string) => {\n          if (prev.indexOf(curr) === -1) {\n            prev.push(curr);\n          }\n\n          return prev;\n        }, []);\n    case REMOVE_MULTIPLE_FROM_COLLECTION:\n      return filterContactIds(state, action.payload.contacts);\n    default:\n      return state;\n  }\n}\n\nexport function getNextOffset(state: ContactState) {\n  return state.contacts.length;\n}\n\nexport function hasMore(state: ContactState) {\n  return state.total > state.contacts.length;\n}\n\nconst initialState: ContactState = {\n  initialized: false,\n  status: 'idle',\n  isFetching: false,\n  didInvalidate: false,\n  contactsById: {},\n  contacts: [],\n  total: 0,\n};\n\nexport function reducer(\n  state: ContactState = initialState,\n  action: ContactAction\n): ContactState {\n  switch (action.type) {\n    case REQUEST_CONTACTS:\n      return { ...state, status: 'pending', isFetching: true };\n    case REQUEST_CONTACTS_SUCCESS:\n      return {\n        ...state,\n        initialized: true,\n        status: 'resolved',\n        isFetching: false,\n        didInvalidate: false,\n        contacts: contactListReducer(\n          state.didInvalidate === true ? [] : state.contacts,\n          action\n        ),\n        contactsById: contactsByIdReducer(\n          state.didInvalidate === true ? {} : state.contactsById,\n          action\n        ),\n        total: action.payload.data.total,\n      };\n    case REQUEST_CONTACTS_FAIL:\n      return {\n        ...state,\n        status: 'rejected',\n      };\n    case INVALIDATE_CONTACTS:\n      return { ...state, didInvalidate: true };\n    case REQUEST_CONTACT:\n      return {\n        ...state,\n        status: 'pending',\n        isFetching: true,\n      };\n    case REQUEST_CONTACT_SUCCESS:\n      return {\n        ...state,\n        status: 'resolved',\n        isFetching: false,\n        contactsById: contactsByIdReducer(state.contactsById, action),\n      };\n    case REMOVE_MULTIPLE_FROM_COLLECTION:\n      return {\n        ...state,\n        status: 'invalidated',\n        didInvalidate: true,\n        contacts: contactListReducer(state.contacts, action),\n        contactsById: contactsByIdReducer(state.contactsById, action),\n        total: state.total - action.payload.contacts.length,\n      };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/store/selectors.ts",
    "content": "import { RootState } from 'src/store/reducer';\n\nexport const stateSelector = (state: RootState) => state.contact;\n\nexport const shouldFetchSelector = (state: RootState) =>\n  ['idle', 'invalidated'].includes(stateSelector(state).status);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/contact/types.d.ts",
    "content": "import { PI, PrivacyFeature } from 'src/modules/pi/types';\n\n// Entity ---------------------------------------\ninterface PostalAddressPayload {\n  address_id?: string;\n  city: string;\n  country?: string;\n  is_primary?: boolean;\n  label?: string;\n  postal_code?: string;\n  region?: string;\n  street?: string;\n  type?: string;\n}\n\ninterface PostalAddress extends PostalAddressPayload {\n  address_id: string;\n  city: string;\n}\n\ninterface EmailPayload {\n  address: string;\n  type: string;\n}\ninterface Email extends EmailPayload {\n  email_id: string;\n  is_primary: number;\n  date_update?: string;\n  label?: string;\n}\n\ninterface SocialIdentityPayload {\n  infos?: { [key: string]: any };\n  name: string;\n  type: string;\n}\n\ninterface SocialIdentity extends SocialIdentityPayload {\n  social_id: string;\n}\n\ninterface OrganizationPayload {\n  department?: string;\n  is_primary?: boolean;\n  job_description?: string;\n  label?: string;\n  name: string;\n  title?: string;\n  type?: string;\n}\n\ninterface Organization extends OrganizationPayload {\n  deleted?: boolean;\n  organization_id: string;\n}\n\ninterface PhonePayload {\n  is_primary?: boolean;\n  number: string;\n  type?: string;\n  uri?: string;\n}\n\ninterface Phone extends PhonePayload {\n  phone_id: string;\n  normalized_number?: string;\n}\n\ninterface IMPayload {\n  address: string;\n  is_primary?: boolean;\n  label?: string;\n  protocol?: string;\n  type?: string;\n}\n\ninterface IM extends IMPayload {\n  im_id: string;\n}\n\ninterface PublicKeyPayload {\n  key: string; // description : \"DER or PEM key, base64 encoded\"\n  label: string;\n}\n\ninterface PublicKey extends PublicKeyPayload {\n  alg?: string;\n  crv?: string;\n  date_insert?: string; // format: date-time\n  date_update?: string; // format: date-time\n  emails?: string[];\n  expire_date?: string; // format: date-time\n  fingerprint?: string;\n  kty?: string;\n  key_id: string;\n  type?: string;\n  resource_id: string;\n  resource_type?: string;\n  size?: number; // format: int32\n  use?: string;\n  user_id: string;\n  x?: number; // format: int64\n  y?: number; // format: int64\n}\n\ninterface ContactCommon {\n  additional_name?: string;\n  avatar?: string;\n  date_insert?: string;\n  date_update?: string;\n  deleted?: string; // date-time\n  family_name?: string;\n  given_name?: string;\n  groups?: string[];\n  infos?: {\n    // object\n    birthday?: string;\n  };\n  name_prefix?: string;\n  name_suffix?: string;\n  tags?: string[];\n  title?: string;\n}\n\ninterface Contact extends ContactCommon {\n  addresses?: PostalAddress[];\n  avatar?: string;\n  contact_id: string;\n  date_insert?: string;\n  date_update?: string;\n  deleted?: string; // date-time\n  emails?: Email[];\n  identities?: SocialIdentity[];\n  ims?: IM[];\n  organizations?: Organization[];\n  phones?: Phone[];\n  pi?: PI;\n  privacy_features?: PrivacyFeature;\n  public_keys?: PublicKey[];\n  user_id: string;\n}\n// ---------------------------------------------\n\n// PAYLOAD -------------------------------------\n\nexport interface ContactPayload extends ContactCommon {\n  addresses?: PostalAddressPayload[];\n  identities?: SocialIdentityPayload[];\n  organizations?: OrganizationPayload[];\n  emails?: EmailPayload[];\n  phones?: PhonePayload[];\n  ims?: IMPayload[];\n  contact_id?: string;\n}\n\nexport interface GETContactListPayload {\n  total: number;\n  contacts: Contact[];\n}\n\n// ---------------------------------------------\n\n// FORM ----------------------------------------\n\nexport interface ContactFormData extends ContactPayload {\n  key?: string;\n}\n\n// ---------------------------------------------\n\n// STORE ---------------------------------------\n\nexport interface NewContactState {\n  currentKey: undefined | string;\n}\n\n// ---------------------------------------------\n\nexport type TSortDir = 'ASC' | 'DESC';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/control/components/ComposeButton/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport { withPush } from '../../../routing';\nimport { Button, Icon } from '../../../../components';\n\nimport './style.scss';\n\n@withPush()\nclass ComposeButton extends PureComponent {\n  static propTypes = {\n    push: PropTypes.func.isRequired,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  handleClick = () => {\n    this.props.push('/compose');\n  };\n\n  render() {\n    const { className } = this.props;\n\n    return (\n      <Button\n        shape=\"plain\"\n        onClick={this.handleClick}\n        className={classnames(className, 'm-control-compose-button')}\n        icon=\"pencil\"\n      >\n        <span className=\"m-control-compose-button__label\">\n          <Trans id=\"call-to-action.action.compose\" message=\"Compose\" />\n        </span>\n        <span className=\"m-control-compose-button__icon\">\n          <Icon type=\"plus\" />\n        </span>\n      </Button>\n    );\n  }\n}\n\nexport default ComposeButton;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/control/components/ComposeButton/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-control-compose-button {\n  &__label {\n    display: none;\n  }\n\n  @include breakpoint(medium) {\n    &__label {\n      display: inline;\n    }\n\n    &__icon {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/control/components/ComposeContactButton/index.js",
    "content": "import Presenter from './presenter';\nimport { withNotification } from '../../../userNotify';\n\nexport default withNotification()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/control/components/ComposeContactButton/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Button } from '../../../../components';\n\nclass ComposeContactButton extends PureComponent {\n  static propTypes = {\n    notify: PropTypes.func.isRequired,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  handleEventAction = () => {\n    this.props.notify({\n      message: 'Compose to contact is not yet implemented.',\n    });\n  };\n\n  render() {\n    const { className } = this.props;\n\n    return (\n      <Button\n        shape=\"plain\"\n        onClick={this.handleEventAction}\n        icon=\"comment-o\"\n        className={className}\n      >\n        <Trans\n          id=\"call-to-action.action.compose_contact\"\n          message=\"Compose to this contact\"\n        />\n      </Button>\n    );\n  }\n}\n\nexport default ComposeContactButton;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/control/components/CreateContactButton/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Button } from '../../../../components';\nimport { withPush } from '../../../routing';\n\n@withPush()\nclass ComposeContactButton extends PureComponent {\n  static propTypes = {\n    push: PropTypes.func.isRequired,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  handleClick = () => {\n    this.props.push('/new-contact');\n  };\n\n  render() {\n    const { className } = this.props;\n\n    return (\n      <Button\n        shape=\"plain\"\n        onClick={this.handleClick}\n        className={className}\n        icon=\"user\"\n      >\n        <Trans\n          id=\"call-to-action.action.create_contact\"\n          message=\"Create a contact\"\n        />\n      </Button>\n    );\n  }\n}\n\nexport default ComposeContactButton;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/control/components/PageActions/action-btns.scss",
    "content": "@import '../../../../styles/common';\n\n.m-action-btns {\n  display: flex;\n\n  &__btn {\n    margin-left: $co-margin;\n\n    &:first-child {\n      margin-left: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/control/components/PageActions/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport ComposeButton from '../ComposeButton';\nimport { SearchField } from '../../../search';\nimport './style.scss';\nimport './action-btns.scss';\n\nclass PageActions extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  render() {\n    const { className } = this.props;\n\n    return (\n      <div className={classnames(className, 'm-page-actions')}>\n        <div className=\"m-page-actions__action-btns m-action-btns\">\n          <ComposeButton className=\"m-action-btns__btn\" />\n        </div>\n        <SearchField className=\"m-page-actions__search-field\" />\n      </div>\n    );\n  }\n}\n\nexport default PageActions;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/control/components/PageActions/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/util/flex-grid';\n\n.m-page-actions {\n  @include flex-grid-row;\n\n  &__action-btns {\n    @include flex-grid-size(shrink);\n\n    margin-right: $co-margin;\n  }\n\n  &__search-field {\n    @include flex-grid-size;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/control/index.js",
    "content": "export { default as PageActions } from './components/PageActions';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/actions/requestDevice.js",
    "content": "import { requestDevice as requestDeviceBase } from '../../../store/modules/device';\n\nexport const requestDevice =\n  ({ deviceId }) =>\n  (dispatch) =>\n    dispatch(requestDeviceBase({ deviceId })).then(\n      (response) => response.payload.data\n    );\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/actions/requestDevices.js",
    "content": "import { requestDevices as requestDevicesBase } from '../../../store/modules/device';\n\nexport const requestDevices = () => (dispatch) =>\n  dispatch(requestDevicesBase()).then((response) => response.payload.data);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/actions/revokeDevice.js",
    "content": "import { removeDevice, invalidate } from '../../../store/modules/device';\nimport { tryCatchAxiosPromise } from '../../../services/api-client';\n\nexport const revokeDevice =\n  ({ device }) =>\n  async (dispatch) => {\n    const result = await tryCatchAxiosPromise(\n      dispatch(removeDevice({ device }))\n    );\n    dispatch(invalidate());\n\n    return result;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/actions/saveDevice.js",
    "content": "import {\n  createDevice,\n  updateDevice,\n  invalidate,\n} from '../../../store/modules/device';\nimport { tryCatchAxiosPromise } from '../../../services/api-client';\n\nexport const saveDevice =\n  ({ device, original }) =>\n  async (dispatch) => {\n    let result;\n\n    if (!original) {\n      result = await tryCatchAxiosPromise(dispatch(createDevice({ device })));\n    } else {\n      result = await tryCatchAxiosPromise(\n        dispatch(updateDevice({ device, original }))\n      );\n    }\n    dispatch(invalidate());\n\n    return result;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/actions/verifyDevice.js",
    "content": "import { verifyDevice as verifyDeviceBase } from '../../../store/modules/device';\nimport { requestDevice } from './requestDevice';\n\nexport const verifyDevice =\n  ({ device }) =>\n  async (dispatch) => {\n    await dispatch(verifyDeviceBase({ device }));\n\n    return dispatch(requestDevice({ deviceId: device.device_id }));\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/hooks/useDevice.ts",
    "content": "import { useSelector } from 'src/store/reducer';\nimport { selectDevice } from '../selectors';\nimport { useDevices } from './useDevices';\n\nexport function useDevice(id: string) {\n  const { isFetching } = useDevices();\n  const device = useSelector((state) => selectDevice(state, id));\n\n  return {\n    isFetching,\n    device,\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/hooks/useDevices.ts",
    "content": "import * as React from 'react';\nimport { useDispatch } from 'react-redux';\nimport { useSelector } from 'src/store/reducer';\nimport { requestDevices } from '../actions/requestDevices';\nimport {\n  selectDevices,\n  selectDidInvalidate,\n  selectIsFetching,\n} from '../selectors';\n\nexport function useDevices() {\n  const dispatch = useDispatch();\n  const devices = useSelector(selectDevices);\n  const isFetching = useSelector(selectIsFetching);\n  const didInvalidate = useSelector(selectDidInvalidate);\n\n  React.useEffect(() => {\n    if (!isFetching && (devices.length === 0 || didInvalidate)) {\n      dispatch(requestDevices());\n    }\n  }, [devices, isFetching, didInvalidate]);\n\n  return {\n    devices,\n    isFetching,\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/index.ts",
    "content": "export * from './actions/requestDevice';\nexport * from './actions/requestDevices';\nexport * from './actions/revokeDevice';\nexport * from './actions/saveDevice';\nexport * from './actions/verifyDevice';\nexport * from './services/ecdsa';\nexport * from './services/storage';\nexport * from './services/status';\nexport * from './services/clientDevice';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/selectors.ts",
    "content": "import { createSelector } from 'reselect';\nimport { RootState } from 'src/store/reducer';\nimport { Device } from './types';\n\nexport const selectDevice = (\n  state: RootState,\n  deviceId: string\n): void | Device => state.device.devicesById[deviceId];\n\nexport const selectDeviceState = (state: RootState) => state.device;\nexport const selectDevices = createSelector(selectDeviceState, (deviceState) =>\n  deviceState.devices.map((id) => deviceState.devicesById[id])\n);\n\nexport const selectIsFetching = (state: RootState) =>\n  selectDeviceState(state).isFetching;\n\nexport const selectDidInvalidate = (state: RootState) =>\n  selectDeviceState(state).didInvalidate;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/services/clientDevice.ts",
    "content": "import { ClientDevice, getConfig, initConfig } from './storage';\nimport { Config } from '../types';\n\nconst initClientDevice = () => {\n  let config = getConfig();\n\n  if (!config) {\n    initConfig();\n    config = getConfig() as Config;\n  }\n  return new ClientDevice(config);\n};\n\nlet clientDevice: ClientDevice;\n\n/**\n * client device singleton. Can only be used in browser.\n */\nexport const getClientDevice = () => {\n  if (BUILD_TARGET !== 'browser') {\n    throw new Error('`getClientDevice` must be used in a browser');\n  }\n\n  if (!clientDevice) {\n    clientDevice = initClientDevice();\n  }\n\n  return clientDevice;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/services/ecdsa/index.ts",
    "content": "import { ec as EC } from 'elliptic';\n\nexport const CURVE_TYPE = 'p256';\nexport const CURVE_TYPE_ASSOC = {\n  p256: 'P-256',\n};\n\n// it depends on default props in curve p256 https://github.com/indutny/elliptic/blob/master/lib/elliptic/curves.js#L72-L79\nexport const HASH_NAME = 'SHA256';\n\nlet ec: EC;\nconst getEC = () => {\n  if (!ec) {\n    ec = new EC(CURVE_TYPE);\n  }\n\n  return ec;\n};\n\nexport const generateKeypair = () => getEC().genKeyPair();\nexport const getKeypair = (priv: string) => getEC().keyFromPrivate(priv, 'hex');\nexport const sign = (keypair, hash) => keypair.sign(hash);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/services/signature/index.spec.ts",
    "content": "import base64 from 'base64-js';\nimport { ec as EC } from 'elliptic';\nimport { AxiosRequestConfig } from 'axios';\nimport { getSignatureHeaders } from '.';\nimport { CURVE_TYPE } from '../ecdsa';\nimport UploadFileAsFormField from '../../../file/services/uploadFileAsFormField';\nimport { ClientDevice } from '../storage';\n\nconst device = new ClientDevice({\n  id: 'THIS-IS-NOT-A-DEVICE',\n  priv: '841daea97dcd00713508fba1934d40116bffbb4004043d7071a41115122f911',\n  curve: 'P-256',\n  hash: 'SHA256',\n});\n\ndescribe('HTTP Request signature headers', () => {\n  const ec = new EC(CURVE_TYPE);\n  const publicKey = ec.keyFromPublic({\n    x: 'a3e89871354b6abbf13feb497f7ea5fcbfacf8f74faf5a472c3c7f4de9eb1919',\n    y: 'e8d3e5eaea22f57f1db50ebadd708b4618cf21bbaa023cc74e4de3d3e96723e6',\n  });\n\n  it('Creates a verifiable signature from URL', async () => {\n    const req: AxiosRequestConfig = { method: 'get', url: '/run/to/the/hills' };\n    const hash =\n      '11ef093b532721968d7d7f7123bf498379f9455695fc7ec4d8bf4d98ab230c3c';\n\n    const signatureHeaders = await getSignatureHeaders(req, device);\n    expect(signatureHeaders['X-Caliopen-Device-ID']).toEqual(\n      'THIS-IS-NOT-A-DEVICE'\n    );\n    expect(\n      publicKey.verify(\n        hash,\n        base64.toByteArray(signatureHeaders['X-Caliopen-Device-Signature'])\n      )\n    ).toBe(true);\n  });\n\n  it('Creates a signature from URL + params', async () => {\n    const req: AxiosRequestConfig = {\n      method: 'get',\n      url: '/run',\n      params: {\n        to: 'the hills',\n        for: 'your life',\n      },\n    };\n    const hash =\n      '9119e04a31dbd4ec86a666e932e9d0102497e31bbff273e4802f08ad38a0b7f6';\n\n    const signatureHeaders = await getSignatureHeaders(req, device);\n    expect(signatureHeaders['X-Caliopen-Device-ID']).toEqual(\n      'THIS-IS-NOT-A-DEVICE'\n    );\n    expect(\n      publicKey.verify(\n        hash,\n        base64.toByteArray(signatureHeaders['X-Caliopen-Device-Signature'])\n      )\n    ).toBe(true);\n  });\n\n  it('Creates a signature from URL + file', async () => {\n    const file = new UploadFileAsFormField(\n      new Blob([\n        new Uint8Array([\n          0x004d, 0x0065, 0x0072, 0x0064, 0x0065, 0x0020, 0x001f, 0x0020,\n          0x0063, 0x0065, 0x006c, 0x0075, 0x0069, 0x0020, 0x0071, 0x0075,\n          0x0069, 0x0020, 0x006c, 0x0069, 0x0072, 0x0061, 0x002e,\n        ]),\n      ]),\n      'attachment'\n    );\n    const req: AxiosRequestConfig = {\n      method: 'post',\n      url: '/run',\n      data: file,\n    };\n    const hash =\n      '19806d4340f7db759bb7710b0d3344b597121b989197cc006c3b350cdb5c74d0';\n\n    const signatureHeaders = await getSignatureHeaders(req, device);\n\n    expect(signatureHeaders['X-Caliopen-Device-ID']).toEqual(\n      'THIS-IS-NOT-A-DEVICE'\n    );\n    expect(\n      publicKey.verify(\n        hash,\n        base64.toByteArray(signatureHeaders['X-Caliopen-Device-Signature'])\n      )\n    ).toBe(true);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/services/signature/index.ts",
    "content": "import base64 from 'base64-js';\nimport SHA from 'jssha';\nimport { AxiosRequestConfig } from 'axios';\nimport { getKeypair, sign } from '../ecdsa';\nimport { buildURL } from '../../../routing';\nimport { readAsArrayBuffer } from '../../../file/services';\nimport UploadFileAsFormField from '../../../file/services/uploadFileAsFormField';\nimport { ClientDevice } from '../storage';\n\n// see : https://jsperf.com/string-to-uint8array\nconst toByteArray = (str) => {\n  const byteArray = new Uint8Array(str.length);\n\n  for (let i = 0, j = str.length; i < j; i += 1) {\n    byteArray[i] = str.charCodeAt(i);\n  }\n\n  return byteArray;\n};\n\nconst buildMessage = async ({\n  method,\n  url,\n  params,\n  data,\n}: AxiosRequestConfig) => {\n  const sha256 = new SHA('SHA-256', 'ARRAYBUFFER');\n  const methodBytes = toByteArray(method?.toUpperCase());\n  const builtURL = toByteArray(buildURL(url, params));\n\n  sha256.update(methodBytes.buffer);\n  sha256.update(builtURL.buffer);\n\n  if (data instanceof UploadFileAsFormField) {\n    const file = await readAsArrayBuffer(data.file);\n\n    sha256.update(file);\n  }\n\n  if (data === Object(data)) {\n    sha256.update(toByteArray(JSON.stringify(data)).buffer);\n  }\n\n  return sha256.getHash('HEX');\n};\n\nexport const signRequest = async (\n  req: AxiosRequestConfig,\n  privateKey: string\n): Promise<string> => {\n  const message = await buildMessage(req);\n\n  return base64.fromByteArray(sign(getKeypair(privateKey), message).toDER());\n};\n\nexport const getSignatureHeaders = async (\n  req: AxiosRequestConfig,\n  device: ClientDevice\n): Promise<Record<string, string>> => {\n  const { id, priv } = device.config;\n  // XXX: if no access to storage, this won't make sens. Throw?\n  const signature = await signRequest(req, priv);\n\n  return {\n    'X-Caliopen-Device-ID': id,\n    'X-Caliopen-Device-Signature': signature,\n  };\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/services/status.js",
    "content": "export const STATUS_VERIFIED = 'verified';\nexport const STATUS_UNVERIFIED = 'unverified';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/services/storage/index.ts",
    "content": "import { v4 as uuidV4 } from 'uuid';\nimport { getLocalstorage } from '../../../../services/localStorage';\nimport { Config, PublicDevice } from '../../types';\nimport {\n  CURVE_TYPE,\n  HASH_NAME,\n  CURVE_TYPE_ASSOC,\n  getKeypair,\n  generateKeypair,\n} from '../ecdsa';\n\nconst DEVICE_NAMESPACE = 'device';\n\nlet ls;\nconst getStorage = () => {\n  if (!ls) {\n    ls = getLocalstorage();\n  }\n\n  return ls;\n};\n\nexport const saveConfig = ({ id, priv, hash, curve }) => {\n  const storage = getStorage();\n  storage.save(DEVICE_NAMESPACE, 'id', id);\n  storage.save(DEVICE_NAMESPACE, 'priv', priv);\n  storage.save(DEVICE_NAMESPACE, 'hash', hash);\n  storage.save(DEVICE_NAMESPACE, 'curve', curve);\n};\n\nexport const save = ({ id, keypair }) => {\n  saveConfig({\n    id,\n    curve: CURVE_TYPE,\n    hash: HASH_NAME,\n    priv: keypair.priv.toString('hex'),\n  });\n};\n\nconst configKeys = ['id', 'priv', 'hash', 'curve'];\n\nexport const getConfig = (): Config | void => {\n  const params = getStorage().findAll(DEVICE_NAMESPACE);\n\n  if (params.length !== configKeys.length) {\n    return undefined;\n  }\n\n  return params.reduce(\n    (acc, item) => ({\n      ...acc,\n      [item.id]: item.value,\n    }),\n    {}\n  );\n};\n\nexport function initConfig() {\n  const id = uuidV4();\n  const keypair = generateKeypair();\n\n  saveConfig({\n    id,\n    curve: CURVE_TYPE,\n    hash: HASH_NAME,\n    // @ts-ignore: update lib?\n    priv: keypair.priv.toString('hex'),\n  });\n}\n\nexport class ClientDevice {\n  constructor(config: Config) {\n    this.config = config;\n    this.device_id = config.id;\n\n    const keypair = getKeypair(config.priv);\n    const pub = keypair.getPublic();\n\n    this.ecdsa_key = {\n      curve: CURVE_TYPE_ASSOC[CURVE_TYPE],\n      hash: HASH_NAME,\n      x: pub.getX().toString('hex'),\n      y: pub.getY().toString('hex'),\n    };\n  }\n\n  config: Config;\n\n  device_id: string;\n\n  ecdsa_key: {\n    curve: string;\n    hash: typeof HASH_NAME;\n    x: string;\n    y: string;\n  };\n\n  getPublic: () => PublicDevice = () => ({\n    device_id: this.device_id,\n    ecdsa_key: this.ecdsa_key,\n  });\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/device/types.d.ts",
    "content": "export type STATUS_VERIFIED = 'verified';\nexport type STATUS_UNVERIFIED = 'unverified';\n\nexport interface Device {\n  type: 'other' | string;\n  name: string;\n  device_id: string;\n  status: STATUS_VERIFIED | STATUS_UNVERIFIED;\n  user_agent: string;\n}\n\nexport interface Config {\n  id: string;\n  curve: string;\n  hash: string;\n  priv: string;\n}\n\nexport interface PublicDevice {\n  device_id: string;\n  ecdsa_key: {\n    curve: string;\n    hash: string;\n    x: string;\n    y: string;\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/discussion/actions/requestDiscussionIdForParticipants.js",
    "content": "import { requestDiscussionByParticipants } from '../../../store/modules/discussion';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\nimport { getModuleStateSelector } from '../../../store/selectors/getModuleStateSelector';\n\nexport const requestDiscussionIdForParticipants =\n  ({ participants, internalHash }) =>\n  async (dispatch, getState) => {\n    const { discussionId } =\n      getModuleStateSelector('discussion')(getState())\n        .discussionByParticipantsHash[internalHash] || {};\n    if (discussionId) {\n      return discussionId;\n    }\n\n    try {\n      const result = await tryCatchAxiosAction(() =>\n        dispatch(\n          requestDiscussionByParticipants({ participants, internalHash })\n        )\n      );\n\n      return result.discussion_id;\n    } catch (e) {\n      return undefined;\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/discussion/hooks/useDiscussion.ts",
    "content": "import * as React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { requestDiscussion } from 'src/store/modules/discussion';\nimport {\n  discussionSelector,\n  shouldFetchSelector,\n} from '../selectors/discussionSelector';\nimport { isFetchingSelector } from '../selectors/discussionsSelector';\n\nexport function useDiscussion(discussionId: string) {\n  const dispatch = useDispatch();\n  const shouldFetch = useSelector((state) =>\n    shouldFetchSelector(state, discussionId)\n  );\n  const isFetching = useSelector(isFetchingSelector);\n  const discussion = useSelector((state) =>\n    discussionSelector(state, { discussionId })\n  );\n\n  React.useEffect(() => {\n    if (shouldFetch) {\n      dispatch(requestDiscussion({ discussionId }));\n    }\n  }, [shouldFetch]);\n\n  return {\n    discussion,\n    isFetching,\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/discussion/index.js",
    "content": "export * from './actions/requestDiscussionIdForParticipants';\nexport * from './hooks/useDiscussion';\nexport * from './selectors/discussionDraftSelector';\nexport * from './selectors/discussionIdSelector';\nexport * from './selectors/discussionSelector';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/discussion/query.ts",
    "content": "import getClient from 'src/services/api-client';\nimport { Discussion } from 'src/store/modules/discussion';\nimport { FetchConfig, QueryKey } from 'src/types';\nimport { TagPayload } from '../tags/types';\n\nconst client = getClient();\n\nexport const getQueryKeys = ({\n  discussionId,\n  fetchParams,\n}: {\n  discussionId?: string;\n  fetchParams?: any;\n} = {}): QueryKey =>\n  // @ts-ignore\n  [\n    'discussions',\n    discussionId,\n    fetchParams ? JSON.stringify(fetchParams) : undefined,\n  ].filter(Boolean);\n\nexport const getConfigUpdateTags = (discussionId?: string): FetchConfig => ({\n  url: `/api/v2/discussions/${discussionId}/tags`,\n});\n\n// ---------------------------------\n\nexport function updateTags(discussion: Discussion, tags: TagPayload[]) {\n  const payload = {\n    tags: tags.map((tag) => tag.name),\n    current_state: { tags: discussion.tags },\n  };\n\n  return client.patch(getConfigUpdateTags(discussion.id).url, payload);\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/discussion/selectors/discussionDraftSelector.js",
    "content": "import { createSelector } from 'reselect';\n// prevent circular reference draftMessage.requestDraft > message.getDraft >\n// discussion.discussionDraftSelector > message.sortMessages\nimport { sortMessages } from '../../message/services/sortMessages';\nimport { createMessageCollectionStateSelector } from '../../../store/selectors/message';\nimport { discussionIdSelector } from './discussionIdSelector';\n\nconst messageCollectionSelector = createMessageCollectionStateSelector(\n  () => 'discussion',\n  discussionIdSelector\n);\n\nexport const discussionDraftSelector = (state, { discussionId }) =>\n  createSelector([messageCollectionSelector], ({ messages }) => {\n    const [draft] = sortMessages(messages, true).filter(\n      (message) => message.is_draft === true\n    );\n\n    return draft;\n  })(state, { discussionId });\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/discussion/selectors/discussionIdSelector.js",
    "content": "export const discussionIdSelector = (state, { discussionId }) => {\n  if (!discussionId) {\n    throw new Error(\n      'discussionId must be passed when calling discussionIdSelector, e.g discussionIdSelector(state, { discussionId })'\n    );\n  }\n\n  return discussionId;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/discussion/selectors/discussionSelector.js",
    "content": "import { getModuleStateSelector } from '../../../store/selectors/getModuleStateSelector';\n\nexport const discussionSelector = (state, { discussionId }) =>\n  getModuleStateSelector('discussion')(state).discussionsById[discussionId];\n\nexport const shouldFetchSelector = (state, id) => {\n  const discussion = discussionSelector(state, { discussionId: id });\n  const { didInvalidate, isFetching } =\n    getModuleStateSelector('discussion')(state);\n\n  return (!discussion || didInvalidate) && !isFetching;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/discussion/selectors/discussionsSelector.ts",
    "content": "import { getModuleStateSelector } from '../../../store/selectors/getModuleStateSelector';\n\nexport const isFetchingSelector = (state) =>\n  getModuleStateSelector('discussion')(state).isFetching;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftIdentity/hooks/useAvailableIdentities.ts",
    "content": "import * as React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useContacts } from 'src/modules/contact';\nimport { filterIdentities } from 'src/modules/draftMessage/services/filterIdentities';\nimport { useIdentities } from 'src/modules/identity';\nimport { messageSelector, getMessage } from 'src/modules/message';\nimport { useUser } from 'src/modules/user';\nimport { IDraftMessageFormData } from 'src/modules/draftMessage/types';\n\nconst EMPTY_ARRAY = [];\n\nexport function useAvailableIdentities(\n  draft: undefined | IDraftMessageFormData\n) {\n  const dispatch = useDispatch();\n  const { identities } = useIdentities();\n  const { user } = useUser();\n  const { data: contacts } = useContacts();\n  const parentMessage = useSelector((state) =>\n    messageSelector(state, { messageId: draft?.parent_id })\n  );\n  const [loaded, setLoaded] = React.useState<boolean>(false);\n  React.useEffect(() => {\n    (async function fetch() {\n      if (draft && draft.parent_id && !parentMessage && !loaded) {\n        await dispatch(getMessage({ messageId: draft.parent_id }));\n        setLoaded(true);\n      }\n    })();\n  }, [draft, loaded, parentMessage]);\n\n  if (!identities) {\n    return EMPTY_ARRAY;\n  }\n\n  if (!user) {\n    return EMPTY_ARRAY;\n  }\n\n  return filterIdentities({\n    identities,\n    user,\n    contacts,\n    parentMessage,\n  });\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftIdentity/index.ts",
    "content": "export * from './hooks/useAvailableIdentities';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/actions/consolidateParticipants.js",
    "content": "import { requestContactIdsForURI } from 'src/modules/contact/store/reducer';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\n\nconst consolidateParticipant = (participant) => async (dispatch) => ({\n  ...participant,\n  contact_ids: [\n    ...(\n      await tryCatchAxiosAction(() =>\n        dispatch(\n          requestContactIdsForURI({\n            protocol: participant.protocol,\n            address: participant.address,\n          })\n        )\n      )\n    ).contacts.map((contact) => contact.contact_id),\n  ],\n});\n\nexport const consolidateParticipants = (participants) => (dispatch) =>\n  Promise.all(\n    participants.map(async (participant) =>\n      participant.contact_ids && participant.contact_ids.length > 0\n        ? participant\n        : dispatch(consolidateParticipant(participant))\n    )\n  );\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/actions/getDefaultIdentity.js",
    "content": "import { getIdentities } from '../../identity';\nimport { getIdentityProtocol } from '../services/getIdentityProtocol';\n\nconst isIdentityUsed = ({ participants, identity }) =>\n  participants.some(\n    (participant) =>\n      participant.address === identity.identifier &&\n      participant.protocol === getIdentityProtocol(identity)\n  );\n\nexport const getDefaultIdentity =\n  ({ participants = undefined, protocol = 'email' } = { protocol: 'email' }) =>\n  async (dispatch) => {\n    const identities = await dispatch(getIdentities());\n\n    if (!participants) {\n      return [\n        ...identities.sort((identity) => {\n          if (identity.type === 'local') {\n            return -1;\n          }\n\n          return 1;\n        }),\n      ].find((identity) => getIdentityProtocol(identity) === protocol);\n    }\n\n    return identities.reduce((acc, curr) => {\n      if (getIdentityProtocol(curr) !== protocol) {\n        return acc;\n      }\n\n      if (!acc) {\n        return curr;\n      }\n\n      if (\n        !isIdentityUsed({ participants, identity: acc }) &&\n        isIdentityUsed({ participants, identity: curr })\n      ) {\n        return curr;\n      }\n\n      return acc;\n    }, undefined);\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/actions/getDefaultIdentity.spec.js",
    "content": "import configureMockStore from 'redux-mock-store';\nimport { getDefaultMiddleware } from '@reduxjs/toolkit';\nimport { getDefaultIdentity } from './getDefaultIdentity';\n\nconst mockStore = configureMockStore(getDefaultMiddleware());\n\ndescribe('modules identity - actions - getDefaultIdentity', () => {\n  const localIdentity = {\n    identity_id: 'local-identity',\n    identifier: 'foo@caliopen.local',\n    protocol: 'email',\n    type: 'local',\n  };\n  const remoteEmailIdentity = {\n    identity_id: 'remote-email-identity',\n    identifier: 'foo@remote.tld',\n    protocol: 'email',\n    type: 'remote',\n  };\n  const remoteEmailIdentity2 = {\n    identity_id: 'remote-email-identity2',\n    identifier: 'foo2@remote.tld',\n    protocol: 'email',\n    type: 'remote',\n  };\n  const remoteTwitterIdentity = {\n    identity_id: 'remote-twitter-identity',\n    identifier: '@foo',\n    protocol: 'twitter',\n    type: 'remote',\n  };\n  const store = mockStore({\n    localIdentity: {\n      isFetching: false,\n      didInvalidate: false,\n      localIdentities: [localIdentity],\n      total: 1,\n    },\n    remoteIdentity: {\n      isFetching: false,\n      didInvalidate: false,\n      remoteIdentitiesById: {\n        [remoteEmailIdentity.identity_id]: remoteEmailIdentity,\n        [remoteEmailIdentity2.identity_id]: remoteEmailIdentity2,\n        [remoteTwitterIdentity.identity_id]: remoteTwitterIdentity,\n      },\n      remoteIdentities: [\n        remoteEmailIdentity.identity_id,\n        remoteEmailIdentity2.identity_id,\n        remoteTwitterIdentity.identity_id,\n      ],\n      total: 2,\n    },\n  });\n\n  it('returns default local identity', async () => {\n    const expectedActions = [];\n    const action = getDefaultIdentity();\n\n    const result = await store.dispatch(action);\n    expect(result).toEqual(localIdentity);\n    expect(store.getActions()).toEqual(expectedActions);\n  });\n\n  describe('with parentMessage', () => {\n    it('uses local identity', async () => {\n      const protocol = 'email';\n      const participants = [\n        { address: localIdentity.identifier, type: 'To', protocol: 'email' },\n        {\n          address: remoteEmailIdentity.identifier,\n          type: 'From',\n          protocol: 'email',\n        },\n      ];\n      const action = getDefaultIdentity({ participants, protocol });\n      const expectedActions = [];\n\n      const result = await store.dispatch(action);\n      expect(result).toEqual(localIdentity);\n      expect(store.getActions()).toEqual(expectedActions);\n    });\n\n    it('uses email remote identity', async () => {\n      const protocol = 'email';\n      const participants = [\n        { address: 'bar@contact.tld', type: 'To', protocol: 'email' },\n        {\n          address: remoteEmailIdentity.identifier,\n          type: 'From',\n          protocol: 'email',\n        },\n      ];\n      const action = getDefaultIdentity({ participants, protocol });\n      const expectedActions = [];\n\n      const result = await store.dispatch(action);\n      expect(result).toEqual(remoteEmailIdentity);\n      expect(store.getActions()).toEqual(expectedActions);\n    });\n\n    // XXX: this test does not more than previous one\n    xit('uses email remote identity according to recipients', async () => {\n      const protocol = 'email';\n      const participants = [\n        { address: 'bar@contact.tld', type: 'To', protocol: 'email' },\n        {\n          address: remoteEmailIdentity.identifier,\n          type: 'From',\n          protocol: 'email',\n        },\n        {\n          address: remoteEmailIdentity2.identifier,\n          type: 'To',\n          protocol: 'email',\n        },\n      ];\n      const action = getDefaultIdentity({ participants, protocol });\n      const expectedActions = [];\n\n      const result = await store.dispatch(action);\n      expect(result).toEqual(remoteEmailIdentity);\n      expect(store.getActions()).toEqual(expectedActions);\n    });\n\n    it('uses twitter identity', async () => {\n      const protocol = 'twitter';\n      const participants = [\n        { address: '@whatever', type: 'To', protocol: 'twitter' },\n        { address: '@whateverToo', type: 'From', protocol: 'twitter' },\n      ];\n      const action = getDefaultIdentity({ participants, protocol });\n      const expectedActions = [];\n\n      const result = await store.dispatch(action);\n      expect(result).toEqual(remoteTwitterIdentity);\n      expect(store.getActions()).toEqual(expectedActions);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/actions/reply.ts",
    "content": "import { saveDraft } from './saveDraft';\nimport { createDiscussionDraft } from './requestDraft';\nimport { getDraft, Message, messageSelector } from '../../message';\nimport { mapMessageToDraftMessageFormData } from '../models';\n\n/**\n * In a discussion, I want to reply to a message\n * find the draft if any for the discussion\n * or create new draft\n * set the parent and save\n *\n * @param messageInReply Message\n */\nexport const reply =\n  (messageInReply: Message) =>\n  async (dispatch, getState): Promise<Message> => {\n    const discussionId = messageInReply.discussion_id;\n    const draftMessage = await dispatch(getDraft({ discussionId }));\n\n    // FIXME: change subject & co? (not only parent_id)\n    const draft = draftMessage\n      ? {\n          ...mapMessageToDraftMessageFormData(draftMessage),\n          parent_id: messageInReply.message_id,\n        }\n      : await dispatch(\n          createDiscussionDraft({\n            discussionId,\n            values: {\n              parent_id: messageInReply.message_id,\n            },\n          })\n        );\n\n    await dispatch(\n      saveDraft(draft, {\n        withThrottle: false,\n        force: true,\n      })\n    );\n    const message = messageSelector(getState(), {\n      messageId: draft.message_id,\n    });\n\n    return message;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/actions/requestDraft.ts",
    "content": "import { createDraft, syncDraft } from '../../../store/modules/draft-message';\nimport {\n  getDraft as getDiscussionDraftBase,\n  getMessage,\n  getLastMessage,\n  Participant,\n} from '../../message';\nimport { getUser } from '../../user';\nimport { getDefaultIdentity } from './getDefaultIdentity';\nimport { changeAuthorInParticipants } from '../services/changeAuthorInParticipants';\nimport {\n  DraftMessageFormData,\n  mapMessageToDraftMessageFormData,\n} from '../models';\n\nexport const createDiscussionDraft =\n  ({ discussionId, values }) =>\n  async (dispatch) => {\n    const [parentMessage, user] = await Promise.all([\n      values.parent_id\n        ? dispatch(getMessage({ messageId: values.parent_id }))\n        : dispatch(getLastMessage({ discussionId })),\n      dispatch(getUser()),\n    ]);\n    // parentMessage may not exist at all\n    const { participants, protocol } = parentMessage || {};\n    const identity = await dispatch(\n      getDefaultIdentity({ participants, protocol })\n    );\n    const newDraft = new DraftMessageFormData({\n      // discussion_id is never saved for a draft, it is set by the backend when the message is sent\n      // rollback 5fb2eb667210cfe8336b03d48efb5a350ccf32cd and await for new discussion algo (still\n      // not working with current API)\n      // XXX: the next API will give the discussionId according to participants\n      // may be better to give the participants and the getDraftDefault identity according to the\n      // discussionId?\n      // how to deal w/ no network capability?\n      discussion_id: discussionId,\n      subject: parentMessage?.subject || '',\n      parent_id: parentMessage?.message_id,\n      user_identities: identity ? [identity.identity_id] : [],\n      recipients: changeAuthorInParticipants({\n        participants: parentMessage?.participants,\n        user,\n        identity,\n      })\n        // we only want recipients in draft message\n        .filter((participant: Participant) => participant.type !== 'From'),\n      identity_id: identity?.identity_id,\n      ...values,\n    });\n\n    await dispatch(createDraft({ draft: newDraft }));\n    dispatch(syncDraft(newDraft));\n\n    return newDraft;\n  };\n\nexport const getOrCreateDiscussionDraft =\n  ({ discussionId }) =>\n  async (dispatch): Promise<DraftMessageFormData> => {\n    const draft = await dispatch(getDiscussionDraftBase({ discussionId }));\n\n    if (draft) {\n      const draftFormData = mapMessageToDraftMessageFormData(draft);\n      dispatch(syncDraft(draftFormData));\n\n      return draftFormData;\n    }\n\n    return dispatch(createDiscussionDraft({ discussionId, values: {} }));\n  };\n\nexport const getOrCreateDraft =\n  (messageId) =>\n  async (dispatch): Promise<void | DraftMessageFormData> => {\n    try {\n      const message = await Promise.resolve(\n        dispatch(getMessage({ messageId }))\n      );\n      if (!message.is_draft) {\n        return undefined;\n      }\n      const draft = mapMessageToDraftMessageFormData(message);\n      dispatch(syncDraft(draft));\n\n      return draft;\n    } catch (err) {\n      const identity = await dispatch(getDefaultIdentity());\n      const newDraft = new DraftMessageFormData({\n        message_id: messageId,\n        identity_id: identity?.identity_id,\n      });\n      await dispatch(createDraft({ draft: newDraft }));\n      dispatch(syncDraft(newDraft));\n\n      return newDraft;\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/actions/requestParticipantSuggestions.spec.ts",
    "content": "import configureMockStore from 'redux-mock-store';\nimport { getDefaultMiddleware } from '@reduxjs/toolkit';\nimport { requestParticipantSuggestions } from './requestParticipantSuggestions';\n\njest.mock('../../../store/modules/participant-suggestions', () => ({\n  suggest: (term, context) => ({\n    type: 'axiosReqMocked',\n    payload: {\n      data: [],\n    },\n  }),\n  searchSuccess: () => ({\n    type: 'searchSuccess',\n    payload: {},\n  }),\n}));\n\nconst mockStore = configureMockStore(getDefaultMiddleware());\n\ndescribe('modules draftMessage - actions - requestParticipantSuggestions', () => {\n  it('suggests', async () => {\n    const store = mockStore({\n      settings: { settings: { contact_display_format: 'given_name' } },\n    });\n    const expectedActions = [\n      {\n        type: 'axiosReqMocked',\n        payload: {\n          data: [],\n        },\n      },\n      {\n        type: 'searchSuccess',\n        payload: {},\n      },\n    ];\n    const action = requestParticipantSuggestions('foo', 'msg_compose');\n\n    const result = await store.dispatch(action);\n    expect(result).toEqual({\n      type: 'searchSuccess',\n      payload: {},\n    });\n    expect(store.getActions()).toEqual(expectedActions);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/actions/requestParticipantSuggestions.ts",
    "content": "import { AppDispatch, GetState } from 'src/types';\nimport { Contact } from 'src/modules/contact/types';\nimport {\n  suggest,\n  searchSuccess,\n  Context,\n  Suggestion,\n  SuggestionPayload,\n  ParticipantSuggestionPayload,\n} from 'src/store/modules/participant-suggestions';\nimport { formatName, getContact } from 'src/modules/contact';\nimport { settingsSelector } from '../../settings';\n\nconst getSuggestion = ({\n  label,\n  address,\n  protocol,\n  source,\n  ...opts\n}): Suggestion => ({\n  label,\n  address,\n  protocol,\n  source,\n  ...opts,\n});\n\nconst createGetContactSuggestions = (format: string) => (contact: Contact) => {\n  let suggestions: Suggestion[] = [];\n\n  if (contact.emails) {\n    suggestions = [\n      ...suggestions,\n      ...contact.emails.map((email) =>\n        getSuggestion({\n          label: formatName({ contact, format }),\n          address: email.address,\n          protocol: 'email',\n          contact_id: contact.contact_id,\n          source: 'contact',\n        })\n      ),\n    ];\n  }\n\n  if (contact.identities) {\n    suggestions = [\n      ...suggestions,\n      ...contact.identities.map((identity) =>\n        getSuggestion({\n          label: formatName({ contact, format }),\n          address: identity.name,\n          protocol: identity.type,\n          contact_id: contact.contact_id,\n          source: 'contact',\n        })\n      ),\n    ];\n  }\n\n  return suggestions;\n};\n\nconst createExtractSuggestionsFromContacts =\n  (getContactSuggestions: (contact: Contact) => Suggestion[]) =>\n  (contacts: Contact[]) =>\n    contacts.reduce<Suggestion[]>(\n      (acc, contact) => [...acc, ...getContactSuggestions(contact)],\n      []\n    );\n\nconst getContactIdsFromSuggestions = (results) =>\n  results\n    .filter((result) => result.source === 'contact')\n    .map((result) => result.contact_id);\n\nconst getContacts = (contactIds: string[]) => (dispatch: AppDispatch) =>\n  Promise.all(\n    // @ts-ignore: dispatch thunk action\n    contactIds.map((contactId) => dispatch(getContact({ contactId })))\n  );\n\nexport const requestParticipantSuggestions =\n  (terms: string, context: Context) =>\n  async (dispatch: AppDispatch, getState: GetState) => {\n    const { contact_display_format: contactDisplayFormat } = settingsSelector(\n      getState()\n    ).settings;\n    const getContactSuggestions =\n      createGetContactSuggestions(contactDisplayFormat);\n    const extractSuggsFromContacts = createExtractSuggestionsFromContacts(\n      getContactSuggestions\n    );\n\n    const axiosResponse = await dispatch(suggest(terms, context));\n    const contactIds: string[] = getContactIdsFromSuggestions(\n      // @ts-ignore: dispatch didn't transform axios action in response\n      axiosResponse.payload.data\n    );\n\n    // @ts-ignore: dispatch thunk\n    const contacts: Contact[] = await dispatch(getContacts(contactIds));\n    // @ts-ignore: dispatch didn't transform axios action in response\n    const suggestionPayloads: SuggestionPayload[] = axiosResponse.payload.data;\n    const contactSuggestions = extractSuggsFromContacts(\n      contacts.filter(Boolean)\n    );\n\n    const participantSuggestion = suggestionPayloads\n      .filter((payload) => payload.source === 'participant')\n      // deduplicate addresses\n      .filter(\n        (payload: ParticipantSuggestionPayload) =>\n          !contactSuggestions.some(\n            (contactSuggestion) =>\n              payload.protocol === contactSuggestion.protocol &&\n              payload.address === contactSuggestion.address\n          )\n      )\n\n      .map((payload: ParticipantSuggestionPayload) => getSuggestion(payload));\n\n    const results = [...contactSuggestions, ...participantSuggestion];\n\n    return dispatch(searchSuccess(terms, context, results));\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/actions/saveDraft.ts",
    "content": "import throttle from 'lodash/throttle';\nimport { IDraftMessagePayload } from 'src/modules/message/types';\nimport { calcSyncDraft } from '../services/calcSyncDraft';\nimport { updateMessage } from '../../../store/actions/message';\nimport { createMessage, getMessage, Message } from '../../message';\nimport {\n  editDraft as editDraftBase,\n  syncDraft,\n} from '../../../store/modules/draft-message';\nimport { consolidateParticipants } from './consolidateParticipants';\nimport { mapDraftMessageFormDataToMessage } from '../models';\nimport { IDraftMessageFormData } from '../types';\n\nconst UPDATE_WAIT_TIME = 5 * 1000;\n\nconst createDraft =\n  (draftMessage: IDraftMessagePayload) =>\n  async (dispatch): Promise<Message> => {\n    try {\n      const message = await dispatch(createMessage({ message: draftMessage }));\n      const nextDraft = calcSyncDraft(draftMessage, message);\n      dispatch(syncDraft(nextDraft));\n\n      return message;\n    } catch (err) {\n      return Promise.reject(err);\n    }\n  };\n\nconst updateDraft =\n  (draftMessage: IDraftMessagePayload, message: Message) =>\n  async (dispatch): Promise<Message> => {\n    try {\n      const messageUpToDate = await dispatch(\n        updateMessage({ message: draftMessage, original: message })\n      );\n      const nextDraft = calcSyncDraft(draftMessage, message);\n      dispatch(syncDraft(nextDraft));\n\n      return messageUpToDate;\n    } catch (err) {\n      return Promise.reject(err);\n    }\n  };\n\nconst createOrUpdateDraft =\n  (draftMessage: IDraftMessagePayload) =>\n  async (dispatch): Promise<Message> => {\n    const participants = await dispatch(\n      consolidateParticipants(draftMessage.participants)\n    );\n    const consolidatedDraft = { ...draftMessage, participants };\n\n    let message;\n    try {\n      message = await dispatch(\n        getMessage({ messageId: draftMessage.message_id })\n      );\n    } catch (err) {\n      // new draft: nothing to do\n    }\n\n    if (message) {\n      return dispatch(updateDraft(consolidatedDraft, message));\n    }\n\n    return dispatch(createDraft(consolidatedDraft));\n  };\n\nconst throttled = {};\nconst createThrottle = (\n  resolve,\n  reject,\n  dispatch,\n  draftMessage: IDraftMessagePayload\n) =>\n  throttle(\n    async () => {\n      throttled[draftMessage.message_id || 'current'] = undefined;\n\n      try {\n        const messageUpToDate = await dispatch(\n          createOrUpdateDraft(draftMessage)\n        );\n        resolve(messageUpToDate);\n      } catch (err) {\n        reject(err);\n      }\n    },\n    UPDATE_WAIT_TIME,\n    { leading: false }\n  );\n\nexport const saveDraft =\n  (\n    draft: IDraftMessageFormData,\n    {\n      withThrottle = false,\n      force = false,\n    }: { withThrottle?: boolean; force?: boolean } = {}\n  ) =>\n  async (dispatch): Promise<void | Message> => {\n    dispatch(editDraftBase(draft));\n\n    if (throttled[draft.message_id]) {\n      throttled[draft.message_id].cancel();\n    }\n\n    const { body, recipients } = draft;\n\n    if (\n      body.length === 0 &&\n      (!recipients || recipients.length === 0) &&\n      force === false\n    ) {\n      // do not create draft until body & recipients are filled\n      return undefined;\n    }\n\n    const draftMessage = mapDraftMessageFormDataToMessage(draft);\n\n    if (!withThrottle) {\n      return dispatch(createOrUpdateDraft(draftMessage));\n    }\n\n    return new Promise((resolve, reject) => {\n      throttled[draft.message_id] = createThrottle(\n        resolve,\n        reject,\n        dispatch,\n        draftMessage\n      );\n      throttled[draft.message_id]();\n    });\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/actions/sendDraft.ts",
    "content": "import { Message } from 'src/modules/message';\nimport { postActions, requestMessage } from '../../../store/modules/message';\nimport { messagesByIdSelector } from '../../../store/selectors/message';\nimport { clearDraft } from '../../../store/modules/draft-message';\n\nexport const sendDraft =\n  (draft: Message) =>\n  async (dispatch, getState): Promise<Message> => {\n    try {\n      // discussion_id is set after the message has been sent for new drafts\n      await dispatch(postActions({ message: draft, actions: ['send'] }));\n      await dispatch(requestMessage(draft.message_id));\n\n      const messageUpToDate = messagesByIdSelector(getState())[\n        draft.message_id\n      ];\n\n      dispatch(clearDraft({ draft }));\n\n      return messageUpToDate;\n    } catch (err) {\n      return Promise.reject(err);\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/AttachmentManager/index.js",
    "content": "import { compose } from 'redux';\nimport { withI18n } from '@lingui/react';\nimport { withNotification } from '../../../userNotify';\nimport Presenter from './presenter';\n\nexport default compose(withI18n(), withNotification())(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/AttachmentManager/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport {\n  Link,\n  FileSize,\n  TextBlock,\n  Button,\n  Modal,\n  InputFileGroup,\n  Spinner,\n  Icon,\n} from '../../../../components';\nimport { getMaxSize } from '../../../../services/config';\nimport './style.scss';\n\nfunction generateStateFromProps(props) {\n  const { message } = props;\n\n  if (message) {\n    return { attachments: message.attachments || [] };\n  }\n\n  return {};\n}\n\nclass AttachmentManager extends Component {\n  static propTypes = {\n    onUploadAttachments: PropTypes.func.isRequired,\n    message: PropTypes.shape({}),\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    notifyError: PropTypes.func.isRequired,\n    onDeleteAttachement: PropTypes.func.isRequired,\n    disabled: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    message: undefined,\n    disabled: false,\n  };\n\n  state = {\n    attachments: [],\n    isFetching: {},\n    isImportModalOpen: false,\n    isAttachmentsLoading: false,\n  };\n\n  UNSAFE_componentWillMount() {\n    this.setState(generateStateFromProps(this.props));\n  }\n\n  UNSAFE_componentWillReceiveProps(nextProps) {\n    if (nextProps.message !== this.props.message) {\n      this.setState(generateStateFromProps(nextProps));\n    }\n  }\n\n  createHandleDeleteAttachement = (tempId) => async () => {\n    this.setState((state) => ({\n      isFetching: {\n        ...state.isFetching,\n        [tempId]: true,\n      },\n    }));\n\n    const { onDeleteAttachement, notifyError } = this.props;\n\n    try {\n      await onDeleteAttachement(\n        this.state.attachments.find(\n          (attachement) => attachement.temp_id === tempId\n        )\n      );\n    } catch ({ message }) {\n      notifyError({ message });\n    }\n\n    this.setState((state) => ({\n      isFetching: {\n        ...state.isFetching,\n        [tempId]: undefined,\n      },\n    }));\n  };\n\n  handleOpenImportModal = () => {\n    this.setState({\n      isImportModalOpen: true,\n    });\n  };\n\n  handleCloseImportModal = () => {\n    this.setState({\n      isImportModalOpen: false,\n    });\n  };\n\n  handleInputFileChange = async (attachment) => {\n    const { onUploadAttachments, notifyError } = this.props;\n\n    this.setState({\n      isAttachmentsLoading: true,\n      isImportModalOpen: false,\n    });\n\n    try {\n      // const FIXME cf. #480 multiple files\n      await onUploadAttachments({ attachments: [attachment] });\n    } catch (errors) {\n      new Set(errors.map((err) => err.message)).forEach((message) =>\n        notifyError({ message })\n      );\n    }\n\n    this.setState({\n      isAttachmentsLoading: false,\n    });\n  };\n\n  renderImportModal = () => {\n    const { i18n } = this.props;\n\n    const errors = {};\n\n    return (\n      <Modal\n        isOpen={this.state.isImportModalOpen}\n        contentLabel={i18n._(\n          /* i18n */ 'draft.action.import_attachement',\n          null,\n          {\n            message: 'Import attachement',\n          }\n        )}\n        title={i18n._(/* i18n */ 'draft.action.import_attachement', null, {\n          message: 'Import attachement',\n        })}\n        onClose={this.handleCloseImportModal}\n      >\n        <TextBlock\n          nowrap={false}\n          className=\"m-attachement-manager__encryption-warning\"\n        >\n          <Trans id=\"draft.action.import_attachement.encryption_warning\">\n            WARNING: Caliopen cannot encrypt messages with attachments yet. If\n            you proceed now, message will be sent unencrypted.\n          </Trans>\n        </TextBlock>\n        <InputFileGroup\n          onInputChange={this.handleInputFileChange}\n          errors={errors}\n          descr={i18n._(/* i18n */ 'draft.attachement.form.descr', null, {\n            message: 'Attach a file.',\n          })}\n          maxSize={getMaxSize()}\n          multiple={false} // disable multiple due to issue in api cf. #840\n        />\n      </Modal>\n    );\n  };\n\n  render() {\n    const { i18n, message, disabled } = this.props;\n\n    return (\n      <div className=\"m-attachement-manager\">\n        <ul className=\"m-attachement-manager__list\">\n          {this.state.attachments.map((attachement) => (\n            <li\n              key={attachement.temp_id}\n              className=\"m-attachement-manager__item\"\n            >\n              <Link\n                className=\"m-attachement-manager__file-name\"\n                href={`/api/v2/messages/${message.message_id}/attachments/${attachement.temp_id}`}\n                download={attachement.file_name}\n              >\n                {attachement.file_name}\n              </Link>\n              <TextBlock className=\"m-attachement-manager__file-size\">\n                <FileSize size={attachement.size} />\n              </TextBlock>\n              <Button\n                className=\"m-attachement-manager__file-delete\"\n                onClick={this.createHandleDeleteAttachement(\n                  attachement.temp_id\n                )}\n                icon={\n                  this.state.isFetching[attachement.temp_id] ? (\n                    <Spinner\n                      svgTitleId=\"attachement-delete-spinner\"\n                      isLoading\n                      display=\"inline\"\n                    />\n                  ) : (\n                    <Icon type=\"remove\" />\n                  )\n                }\n                aria-label={i18n._(\n                  /* i18n */\n                  'message.compose.action.delete_attachement',\n                  null,\n                  { message: 'Delete the attachment' }\n                )}\n              />\n            </li>\n          ))}\n        </ul>\n        <Button\n          className=\"m-attachement-manager__button\"\n          onClick={this.handleOpenImportModal}\n          icon={\n            this.state.isAttachmentsLoading ? (\n              <Spinner\n                svgTitleId=\"attachement-import-spinner\"\n                isLoading\n                display=\"inline\"\n              />\n            ) : (\n              <Icon type=\"paperclip\" />\n            )\n          }\n          disabled={disabled}\n        >\n          <Trans\n            id=\"message.compose.action.open_import_attachements\"\n            message=\"Add an attachement\"\n          />\n        </Button>\n        {this.renderImportModal()}\n      </div>\n    );\n  }\n}\n\nexport default AttachmentManager;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/AttachmentManager/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-attachement-manager {\n  @include flex-grid-row;\n\n  &__list {\n    @include flex-grid-size(12);\n  }\n\n  &__button {\n    @include flex-grid-size(shrink);\n\n    margin-top: $co-component__spacing;\n    margin-left: auto;\n  }\n\n  &__item {\n    @include flex-grid-row;\n\n    align-items: center;\n    margin-top: 1px;\n    background: $co-color__primary--high;\n    font-weight: 700;\n  }\n\n  &__file-name {\n    @include flex-grid-column;\n\n    color: $co-color__fg__text;\n    text-decoration: none;\n\n    &:hover {\n      color: $co-color__fg__text;\n      text-decoration: underline;\n    }\n  }\n\n  &__file-size {\n    @include flex-grid-column(shrink);\n\n    margin-left: auto;\n  }\n\n  &__file-delete {\n    @include flex-grid-size(shrink);\n\n    margin-left: auto;\n  }\n\n  &__encryption-warning {\n    color: map_get($caliopen-palette, 'alert');\n    font-weight: bold;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/Recipient/index.js",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/Recipient/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Badge, Icon } from '../../../../components';\nimport { getIconType } from '../../../../services/protocols-config';\n\nclass Recipient extends Component {\n  static propTypes = {\n    participant: PropTypes.shape({}).isRequired,\n    onRemove: PropTypes.func,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    className: PropTypes.string,\n    isValid: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    onRemove: () => {\n      // noop\n    },\n    className: undefined,\n    isValid: true,\n  };\n\n  handleClickRemove = () => {\n    this.props.onRemove(this.props.participant);\n  };\n\n  render() {\n    const { participant, className, i18n, isValid } = this.props;\n\n    return (\n      <Badge\n        large\n        className={className}\n        onDelete={this.handleClickRemove}\n        ariaLabel={i18n._(\n          /* i18n */ 'messages.compose.action.remove-recipient',\n          null,\n          {\n            message: 'Remove recipient',\n          }\n        )}\n        color={!isValid ? 'alert' : undefined}\n      >\n        <Icon type={getIconType(participant.protocol)} rightSpaced />\n        {participant.address}\n      </Badge>\n    );\n  }\n}\n\nexport default Recipient;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/Recipient/presenter.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport Recipient from './presenter';\n\ndescribe('component Recipient', () => {\n  const props = {\n    i18n: { _: (id) => id },\n  };\n\n  it('render', () => {\n    const participant = {\n      address: 'foo@bar.tld',\n      label: 'foobar',\n    };\n    const comp = shallow(<Recipient participant={participant} {...props} />);\n\n    expect(comp.find('Badge').length).toEqual(1);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/RecipientList.scss",
    "content": "@import '../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../styles/object/o-form-element';\n\n.m-recipient-list {\n  @include o-form-element;\n  @include o-form-element--light;\n  @include flex-grid-row($size: expand);\n\n  min-height: $co-component__height;\n  margin-bottom: 0;\n\n  &__placeholder {\n    display: inline-block;\n    margin-right: $co-component__spacing;\n    line-height: $co-component__height;\n  }\n\n  &__recipient {\n    @include flex-grid-size(shrink);\n\n    display: inline-block;\n    margin-right: $co-component__spacing;\n    margin-bottom: $co-component__spacing;\n  }\n\n  &__search {\n    @include flex-grid-size;\n\n    position: relative;\n  }\n\n  &__suggest {\n    top: 100%;\n    left: 0;\n  }\n\n  &__search-input {\n    display: block;\n    width: 100%;\n    min-width: 6rem;\n    border: 0;\n    background: transparent;\n    color: $co-color__fg__text;\n    line-height: $co-component__height;\n  }\n\n  &__dropdown {\n    // XXX: dirty fix when dropdown has no controller then getDropdownStyle() can not be used correctly\n    margin-top: $co-component__height;\n  }\n\n  &__search-result {\n    @include flex-grid-row;\n\n    min-height: $co-component__height;\n    padding-top: $co-component__spacing;\n    padding-bottom: $co-component__spacing;\n    line-height: $co-font__line-height;\n    text-align: left; // within a button : force align left\n  }\n\n  &__search-result-title {\n    @include flex-grid-size(12);\n\n    font-weight: 600;\n  }\n\n  &__search-result-info {\n    @include flex-grid-column(12);\n\n    font-size: $co-font__size--small;\n    font-style: italic;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/RecipientList.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport { AllProviders } from 'test/providers';\nimport RecipientList from './RecipientList';\nimport { Recipient } from '../types';\n\ndescribe('RecipientList', () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  const recipients: Recipient[] = [\n    {\n      contact_ids: [],\n      address: 'foo bar',\n      protocol: 'unknown',\n      type: 'To',\n    },\n    {\n      address: 'barbar@rian.tld',\n      contact_ids: ['1234'],\n      label: 'bar bar rian',\n      protocol: 'email',\n      type: 'To',\n    },\n    {\n      address: 'john.doe',\n      contact_ids: ['4666'],\n      label: 'John doerémifasol',\n      protocol: 'facebook',\n      type: 'To',\n    },\n  ];\n  it('render', async () => {\n    const handleRecipientChange = jest.fn();\n    render(<RecipientList onRecipientsChange={handleRecipientChange} />, {\n      wrapper: AllProviders,\n    });\n\n    expect(screen.getByText('To:')).toBeVisible();\n  });\n\n  describe('remove a recipient', () => {\n    it('should remove a recipient', () => {\n      const handleRecipientChange = jest.fn();\n      render(\n        <RecipientList\n          onRecipientsChange={handleRecipientChange}\n          recipients={recipients}\n        />,\n        {\n          wrapper: AllProviders,\n        }\n      );\n\n      expect(screen.getByText(recipients[0].address));\n\n      // userEvent.click(screen.getByText(recipients[0].address));\n\n      //       const props = {\n      //         recipients: [...recipients],\n      //         onRecipientsChange: jest.fn(),\n      //       };\n\n      //       const comp = shallow(<RecipientList {...requiredProps} {...props} />);\n      //       const inst = comp.instance();\n\n      //       inst.removeRecipient(recipients[1]);\n      //       expect(props.onRecipientsChange).toHaveBeenCalledWith([\n      //         recipients[0],\n      //         recipients[2],\n      //       ]);\n    });\n\n    it.todo('should edit a recipient eventually');\n    // , () => {\n    //       const props = {\n    //         recipients: [...recipients],\n    //         onRecipientsChange: jest.fn(),\n    //       };\n    //       const comp = shallow(<RecipientList {...requiredProps} {...props} />);\n    //       const inst = comp.instance();\n    //       inst.eventuallyEditRecipient();\n    //       expect(props.onRecipientsChange).toHaveBeenCalledWith([\n    //         recipients[0],\n    //         recipients[1],\n    //       ]);\n    // });\n\n    it.todo('should not remove a recipient eventually');\n    // , () => {\n    //       const props = {\n    //         recipients: [...recipients],\n    //         onRecipientsChange: jest.fn(),\n    //       };\n    //       const comp = shallow(<RecipientList {...requiredProps} {...props} />);\n    //       const inst = comp.instance();\n    //       inst.setState({ searchTerms: 'foobar' });\n    //       inst.eventuallyEditRecipient();\n    //       expect(props.onRecipientsChange).not.toHaveBeenCalledWith();\n    // });\n  });\n\n  describe('add a recipient', () => {\n    it.todo('should add an unknown recipient');\n    // , () => {\n    //   const props = {\n    //     recipients: [],\n    //     onRecipientsChange: jest.fn(),\n    //   };\n\n    //   const comp = shallow(<RecipientList {...requiredProps} {...props} />);\n    //   const inst = comp.instance();\n\n    //   const address = 'foo@bar.tld';\n\n    //   inst.addUnknownParticipant(address);\n    //   expect(props.onRecipientsChange).toHaveBeenCalled();\n    //   expect(props.onRecipientsChange.mock.calls[0][0][0].address).toEqual(\n    //     address\n    //   );\n    //   expect(props.onRecipientsChange.mock.calls[0][0][0].protocol).toEqual(\n    //     'email'\n    //   );\n    // });\n\n    it.todo('should add an unknown recipient with unknown protocol');\n    // , () => {\n    //   const props = {\n    //     recipients: [],\n    //     onRecipientsChange: jest.fn(),\n    //   };\n\n    //   const comp = shallow(<RecipientList {...requiredProps} {...props} />);\n    //   const inst = comp.instance();\n\n    //   const address = 'foo@';\n\n    //   inst.addUnknownParticipant(address);\n    //   expect(props.onRecipientsChange).toHaveBeenCalled();\n    //   expect(props.onRecipientsChange.mock.calls[0][0][0].address).toEqual(\n    //     address\n    //   );\n    //   expect(props.onRecipientsChange.mock.calls[0][0][0].protocol).toEqual(\n    //     'unknown'\n    //   );\n    // });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/RecipientList.tsx",
    "content": "import * as React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport classnames from 'classnames';\nimport debounce from 'lodash/debounce';\nimport { Trans } from '@lingui/react';\nimport {\n  Dropdown,\n  Button,\n  Icon,\n  VerticalMenu,\n  VerticalMenuItem,\n  withDropdownControl,\n} from 'src/components';\nimport protocolsConfig, {\n  ASSOC_PROTOCOL_ICON,\n} from 'src/services/protocols-config';\nimport { addEventListener } from 'src/services/event-manager';\nimport { getKey, Suggestion } from 'src/store/modules/participant-suggestions';\nimport { IIdentity } from 'src/modules/identity/types';\nimport { RootState } from 'src/store/reducer';\nimport { requestParticipantSuggestions } from '../actions/requestParticipantSuggestions';\nimport { isValidRecipient } from '../services/isValidRecipient';\nimport { Participant } from '../../message';\nimport { Recipient } from '../types';\nimport RecipientItem from './Recipient';\nimport './RecipientList.scss';\n\nexport const KEY = {\n  BACKSPACE: 8,\n  TAB: 9,\n  COMMA: ',',\n  SEMICOLON: ';',\n  ENTER: 13,\n  ESC: 27,\n  UP: 38,\n  DOWN: 40,\n};\n\nconst EMPTY_SEARCH_RESULTS: Suggestion[] = [];\nconst participantSuggestionsSelector = (state: RootState) =>\n  state.participantSuggestions;\n\nconst searchResultsSelector = (\n  state: RootState,\n  { searchTerms, identity }: { searchTerms: string; identity?: IIdentity }\n) => {\n  if (!searchTerms || !identity) {\n    return EMPTY_SEARCH_RESULTS;\n  }\n\n  const { resultsByKey } = participantSuggestionsSelector(state);\n\n  return resultsByKey[getKey(searchTerms)] || EMPTY_SEARCH_RESULTS;\n};\n\nconst InputDropdownControl = withDropdownControl(\n  React.forwardRef<HTMLInputElement>((props, ref) => (\n    <input ref={ref} {...props} />\n  ))\n);\n\ninterface Props {\n  className?: string;\n  messageId?: string;\n  recipients?: Recipient[];\n  onRecipientsChange: (recipients: Recipient[]) => void;\n  identity?: IIdentity;\n}\n\nfunction RecipientList({\n  className,\n  messageId,\n  recipients = [],\n  identity,\n  onRecipientsChange,\n}: Props): JSX.Element {\n  const dispatch = useDispatch();\n  const searchInputRef = React.useRef<HTMLInputElement>(null);\n  const [searchTerms, setSearchTerms] = React.useState('');\n  const [activeSearchResultIndex, setActiveSearchResultIndex] =\n    React.useState(0);\n  const [searchOpened, setSearchOpened] = React.useState(false);\n\n  const resetSearch = () => {\n    setSearchTerms('');\n    setActiveSearchResultIndex(0);\n  };\n\n  const addRecipient = (recipient: Recipient) => {\n    const compareRecipients = (a: Recipient, b: Recipient) =>\n      a.address === b.address && a.protocol === b.protocol;\n\n    const nextRecipients = [\n      ...recipients.filter(\n        (previousRecipient) => !compareRecipients(previousRecipient, recipient)\n      ),\n      recipient,\n    ];\n\n    onRecipientsChange(nextRecipients);\n  };\n\n  const addUnknownParticipant = (address: string) => {\n    const getProtocol = (search: string) =>\n      Object.keys(protocolsConfig).reduce((previous, current) => {\n        if (!previous && protocolsConfig[current].default) {\n          return current;\n        }\n\n        const { regexp } = protocolsConfig[current];\n\n        if (\n          protocolsConfig[previous].default &&\n          regexp &&\n          regexp.test(search)\n        ) {\n          return current;\n        }\n\n        return previous;\n      });\n\n    const protocol = identity ? identity.protocol : getProtocol(address);\n\n    addRecipient(\n      new Participant({\n        address,\n        protocol,\n        label: address,\n      })\n    );\n  };\n\n  React.useEffect(() => {\n    const unsubscribeClickEvent = addEventListener(\n      'click',\n      (ev: React.SyntheticEvent<HTMLElement>) => {\n        const searchClick =\n          searchInputRef.current === ev.target ||\n          searchInputRef.current?.contains(ev.target as Node);\n\n        if (searchClick) {\n          setSearchOpened((prev) => !prev);\n          return;\n        }\n\n        // Note that suggestions onClick stop propation, so it never comes here when a suggestion is clicked\n\n        if (searchTerms.length > 0) {\n          addUnknownParticipant(searchTerms);\n          resetSearch();\n          setSearchOpened(false);\n        }\n      }\n    );\n\n    return () => unsubscribeClickEvent();\n  }, [searchTerms]);\n\n  const searchResults = useSelector<RootState, Suggestion[]>((state) =>\n    searchResultsSelector(state, { searchTerms, identity })\n  ).filter(\n    // filter already added recipients\n    (suggestion) =>\n      !recipients.some(\n        (recipient) =>\n          recipient.protocol === suggestion.protocol &&\n          recipient.address === suggestion.address\n      )\n  );\n\n  const doSearch = React.useCallback(\n    debounce<(terms: string) => any>(\n      (terms: string) =>\n        dispatch(requestParticipantSuggestions(terms, 'msg_compose')),\n      1 * 1000,\n      {\n        leading: false,\n        trailing: true,\n      }\n    ),\n    []\n  );\n\n  const focusSearch = () => {\n    searchInputRef.current?.focus();\n  };\n\n  const handleClickRecipientList = (ev) => {\n    if (ev.target === ev.currentTarget) {\n      focusSearch();\n    }\n  };\n\n  const addKnownParticipant = (suggestion: Suggestion) => {\n    const { address, protocol, label } = suggestion;\n    addRecipient(\n      new Participant({\n        address,\n        label: label || address,\n        contact_ids: suggestion.contact_id ? [suggestion.contact_id] : [],\n        protocol,\n      })\n    );\n  };\n\n  const removeRecipient = (recipient: Recipient) => {\n    const nextRecipients = recipients.filter((curr) => curr !== recipient);\n    onRecipientsChange(nextRecipients);\n  };\n\n  const editRecipient = (recipient: Recipient) => {\n    removeRecipient(recipient);\n    setSearchTerms(recipient.address);\n    setSearchOpened(true);\n  };\n\n  const eventuallyEditRecipient = () => {\n    if (searchTerms.length === 0 && recipients.length > 0) {\n      editRecipient(recipients[recipients.length - 1]);\n    }\n  };\n\n  const handleSearchChange: React.ChangeEventHandler<HTMLInputElement> = (\n    ev\n  ) => {\n    if (!messageId) {\n      return;\n    }\n\n    const { value } = ev.target;\n\n    if (!value.length) {\n      resetSearch();\n      return;\n    }\n\n    if (value.length >= 3) {\n      doSearch(value);\n    }\n\n    setSearchTerms(value);\n  };\n\n  const handleSearchKeydown = (ev: React.KeyboardEvent<HTMLInputElement>) => {\n    const { which: keyCode, key } = ev;\n\n    if ([KEY.ENTER, KEY.UP, KEY.DOWN].indexOf(keyCode) !== -1) {\n      ev.preventDefault();\n      ev.stopPropagation();\n    }\n\n    if (\n      keyCode === KEY.UP &&\n      searchResults.length > 0 &&\n      activeSearchResultIndex > 0\n    ) {\n      setActiveSearchResultIndex((prev) => prev - 1);\n    }\n\n    if (\n      keyCode === KEY.DOWN &&\n      searchResults.length > 0 &&\n      activeSearchResultIndex < searchResults.length - 1\n    ) {\n      setActiveSearchResultIndex((prev) => prev + 1);\n    }\n\n    if (keyCode === KEY.BACKSPACE) {\n      if (searchTerms.length === 0) {\n        ev.preventDefault();\n      }\n      eventuallyEditRecipient();\n    }\n\n    if (keyCode === KEY.ENTER && searchResults.length > 0) {\n      addKnownParticipant(searchResults[activeSearchResultIndex]);\n      resetSearch();\n      setSearchOpened(false);\n      return;\n    }\n\n    if (\n      ([KEY.ENTER, KEY.TAB].indexOf(keyCode) !== -1 ||\n        [KEY.COMMA, KEY.SEMICOLON].indexOf(key) !== -1) &&\n      searchTerms.length > 0\n    ) {\n      addUnknownParticipant(searchTerms);\n      resetSearch();\n      setSearchOpened(false);\n      return;\n    }\n\n    if ([KEY.COMMA, KEY.SEMICOLON].indexOf(key) !== -1) {\n      ev.preventDefault();\n    }\n  };\n\n  const renderSearchResult = (suggestion: Suggestion, index: number) => {\n    const isContact = suggestion.source === 'contact';\n    // searchResults are sorted by contact\n    const hasLabel =\n      isContact &&\n      index ===\n        searchResults.findIndex(\n          (result) => result.contact_id === suggestion.contact_id\n        );\n\n    const infoClassName = classnames({\n      'm-recipient-list__search-result-info': hasLabel,\n      'm-recipient-list__search-result-title': !hasLabel,\n    });\n\n    return (\n      <Button\n        display=\"expanded\"\n        onClick={(ev) => {\n          ev.stopPropagation();\n          addKnownParticipant(suggestion);\n          resetSearch();\n          setSearchOpened(false);\n        }}\n        className=\"m-recipient-list__search-result\"\n        color={index === activeSearchResultIndex ? 'active' : undefined}\n      >\n        {hasLabel && (\n          <span className=\"m-recipient-list__search-result-title\">\n            {isContact && <Icon type=\"user\" rightSpaced />}\n            {suggestion.label}\n          </span>\n        )}\n\n        <span className={infoClassName}>\n          <Icon\n            type={ASSOC_PROTOCOL_ICON[suggestion.protocol]}\n            aria-label={suggestion.protocol}\n            rightSpaced\n          />\n          <i>{suggestion.address}</i>\n        </span>\n      </Button>\n    );\n  };\n\n  return (\n    <div\n      onClick={handleClickRecipientList}\n      role=\"presentation\"\n      className={classnames('m-recipient-list', className)}\n    >\n      {!recipients.length && (\n        <span className=\"m-recipient-list__placeholder\">\n          <Trans id=\"messages.compose.form.to.label\" message=\"To:\" />\n        </span>\n      )}\n      {recipients.map((participant) => (\n        <RecipientItem\n          key={`${participant.address}_${participant.protocol}`}\n          className=\"m-recipient-list__recipient\"\n          participant={participant}\n          onRemove={removeRecipient}\n          isValid={isValidRecipient({ recipient: participant, identity })}\n        />\n      ))}\n      <div className=\"m-recipient-list__search\">\n        <InputDropdownControl\n          ref={searchInputRef}\n          className=\"m-recipient-list__search-input\"\n          onChange={handleSearchChange}\n          value={searchTerms}\n          onKeyDown={handleSearchKeydown}\n        />\n        <Dropdown\n          id=\"dropdown-recipient-list\"\n          dropdownControlRef={searchInputRef}\n          className=\"m-recipient-list__dropdown\"\n          show={searchTerms ? searchResults.length > 0 && searchOpened : false}\n          closeOnClick=\"doNotClose\"\n          isMenu\n          onToggle={setSearchOpened}\n        >\n          <VerticalMenu>\n            {searchResults.map((ident, index: number) => (\n              <VerticalMenuItem key={`${ident.address}_${ident.protocol}`}>\n                {renderSearchResult(ident, index)}\n              </VerticalMenuItem>\n            ))}\n          </VerticalMenu>\n        </Dropdown>\n      </div>\n    </div>\n  );\n}\n\nexport default RecipientList;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/components/RecipientSelector.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport { createSelector } from 'reselect';\nimport { useSelector } from 'react-redux';\nimport { identitiesSelector } from 'src/modules/identity';\nimport { AdvancedSelectFieldGroup, Icon } from 'src/components';\nimport { Participant } from 'src/modules/message';\nimport {\n  IDENTITY_TYPE_TWITTER,\n  IDENTITY_TYPE_MASTODON,\n  contactSelector,\n} from 'src/modules/contact';\nimport { RootState } from 'src/store/reducer';\nimport { Contact } from 'src/modules/contact/types';\nimport { ProviderIcon } from 'src/modules/remoteIdentity';\nimport { getIdentityProtocol } from '../services/getIdentityProtocol';\nimport { Recipient } from '../types';\n\nconst PROTOCOL_EMAIL = 'email';\nconst PROTOCOL_TWITTER = 'twitter';\nconst PROTOCOL_MASTODON = 'mastodon';\n\nconst availableProtocolsSelector = createSelector(\n  [identitiesSelector],\n  (identities) => identities.map((identity) => getIdentityProtocol(identity))\n);\n\nconst computeValue = (recipient: Recipient) =>\n  `${recipient.address}_${recipient.protocol}`;\n\ninterface Props {\n  contactId: string;\n  className?: string;\n  onChange: (recipient: undefined | Recipient) => void;\n  current?: Recipient;\n}\n\nfunction RecipientSelector({ contactId, className, current, onChange }: Props) {\n  const contact = useSelector<RootState, void | Contact>((state) =>\n    contactSelector(state, contactId)\n  );\n  const availableProtocols = useSelector(availableProtocolsSelector);\n\n  const renderAdvancedLabel = (recipient: Recipient) => {\n    switch (recipient.protocol) {\n      default:\n      case PROTOCOL_EMAIL:\n        return (\n          <>\n            <Icon type=\"email\" /> {recipient.label} &lt;\n            {recipient.address}&gt;\n          </>\n        );\n      case PROTOCOL_TWITTER:\n        return (\n          <>\n            <ProviderIcon size=\"normal\" providerName=\"twitter\" /> @\n            {recipient.address}\n          </>\n        );\n      case PROTOCOL_MASTODON:\n        return (\n          <>\n            <ProviderIcon size=\"normal\" providerName=\"mastodon\" />{' '}\n            {recipient.address}\n          </>\n        );\n    }\n  };\n  const renderLabel = (recipient: Recipient) => {\n    switch (recipient.protocol) {\n      default:\n      case PROTOCOL_EMAIL:\n        return `${recipient.label} &lt;${recipient.address}&gt;`;\n      case PROTOCOL_TWITTER:\n        return `@${recipient.address} (${recipient.protocol})`;\n      case PROTOCOL_MASTODON:\n        return `${recipient.address} (${recipient.protocol})`;\n    }\n  };\n\n  // the contact associated to the participant might be deleted\n  if (!contact) {\n    return null;\n  }\n\n  const availableRecipients = [\n    ...(contact.emails\n      ? contact.emails.map(\n          (email) =>\n            new Participant({\n              address: email.address,\n              label: contact.given_name || email.address,\n              contact_ids: [contact.contact_id],\n              protocol: PROTOCOL_EMAIL,\n            })\n        )\n      : []),\n    ...(contact.identities\n      ? contact.identities\n          .filter((identity) => [IDENTITY_TYPE_TWITTER].includes(identity.type))\n          .map(\n            (identity) =>\n              new Participant({\n                address: identity.name,\n                label: identity.name,\n                contact_ids: [contact.contact_id],\n                protocol: PROTOCOL_TWITTER,\n              })\n          )\n      : []),\n    ...(contact.identities\n      ? contact.identities\n          .filter((identity) =>\n            [IDENTITY_TYPE_MASTODON].includes(identity.type)\n          )\n          .map(\n            (identity) =>\n              new Participant({\n                address: identity.name,\n                label: identity.name,\n                contact_ids: [contact.contact_id],\n                protocol: PROTOCOL_MASTODON,\n              })\n          )\n      : []),\n  ];\n\n  const options = availableRecipients\n    .filter((recipient) => availableProtocols.includes(recipient.protocol))\n    .map((recipient) => ({\n      label: renderLabel(recipient),\n      advancedlabel: renderAdvancedLabel(recipient),\n      value: computeValue(recipient),\n    }));\n\n  if (options.length <= 1) {\n    return null;\n  }\n\n  const value = current ? computeValue(current) : undefined;\n\n  const handleChange = (nextValue) => {\n    const selected = availableRecipients.find(\n      (recipient) => computeValue(recipient) === nextValue\n    );\n    onChange(selected);\n  };\n\n  return (\n    <AdvancedSelectFieldGroup\n      selectId=\"recipient-select\"\n      dropdownId=\"recipient-dropdown-select\"\n      className={classnames(className)}\n      label={\n        <Trans\n          id=\"draft-message.form.contact-recipient-selector\"\n          message=\"To:\"\n        />\n      }\n      inline\n      options={options}\n      onChange={handleChange}\n      value={value}\n    />\n  );\n}\n\nexport default RecipientSelector;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/index.js",
    "content": "export * from './actions/getDefaultIdentity';\nexport * from './actions/reply';\nexport * from './actions/requestDraft';\nexport * from './actions/saveDraft';\nexport * from './actions/sendDraft';\nexport { default as AttachmentManager } from './components/AttachmentManager';\nexport * from './models';\nexport * from './selectors/draft';\nexport * from './services/getIdentityProtocol';\nexport * from './services/validate';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/models.ts",
    "content": "import { v4 as uuidv4 } from 'uuid';\nimport { Message, NewMessage, Participant } from 'src/modules/message';\nimport { IDraftMessagePayload } from 'src/modules/message/types';\nimport { IDraftMessageFormData, Recipient } from './types';\n\nexport class DraftMessageFormData implements IDraftMessageFormData {\n  constructor(props: Partial<DraftMessageFormData> = {}) {\n    Object.assign(this, props);\n  }\n\n  message_id: string = uuidv4();\n\n  discussion_id?: string;\n\n  subject?: string = '';\n\n  body = '';\n\n  parent_id?: string;\n\n  recipients: Array<Recipient> = [];\n\n  // FIXME: Keep a copy of participants because of a bug backend side: the indenty is duplicated (from and to in participant) in draft. So it defeats discussion id selection on advanced draft form\n  participants: Array<Participant> = [];\n\n  identity_id: string;\n}\n\nexport function mapRecipientToParticipant(recipient: Recipient): Participant {\n  return recipient;\n}\n\nexport function mapDraftMessageFormDataToMessage(\n  formData: DraftMessageFormData\n): IDraftMessagePayload {\n  const { identity_id, recipients, ...rest } = formData;\n  return new NewMessage({\n    ...rest,\n    participants: recipients.map(mapRecipientToParticipant),\n    user_identities: [identity_id],\n  });\n}\n\nexport function mapMessageToDraftMessageFormData(\n  message: Message\n): DraftMessageFormData {\n  const {\n    message_id,\n    discussion_id,\n    subject,\n    body,\n    parent_id,\n    participants,\n    user_identities,\n  } = message;\n\n  return new DraftMessageFormData({\n    message_id,\n    discussion_id,\n    subject,\n    body,\n    parent_id,\n    recipients: participants.filter(\n      (participant) => participant.type !== 'From'\n    ),\n    participants,\n    identity_id: user_identities?.[0] || '',\n  });\n}\nexport function mapParticipantToRecipient(participant: Participant): Recipient {\n  return participant;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/selectors/draft.ts",
    "content": "import { createSelector } from 'reselect';\nimport { RootState } from 'src/store/reducer';\n\nexport const draftMessagesSelector = createSelector(\n  (state: RootState) => state.draftMessage,\n  (draftMessageState) =>\n    Object.keys(draftMessageState.draftsByMessageId).map(\n      (messageid) => draftMessageState.draftsByMessageId[messageid]\n    )\n);\n\nexport const draftMessageSelector = (state: RootState, messageId: string) =>\n  state.draftMessage.draftsByMessageId[messageId];\n\nexport function discussionDraftSelector(\n  state: RootState,\n  discussionId: string\n) {\n  return Object.keys(state.draftMessage.draftsByMessageId)\n    .map((id) => state.draftMessage.draftsByMessageId[id])\n    .filter(Boolean)\n    .find((draft) => draft.discussion_id === discussionId);\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/calcSyncDraft.spec.ts",
    "content": "import { Message, Participant } from 'src/modules/message';\nimport { IDraftMessagePayload } from 'src/modules/message/types';\nimport { calcSyncDraft } from './calcSyncDraft';\n\ndescribe.skip('modules identity - service - calcSyncDraft', () => {\n  const messageBase: Message = {\n    message_id: '111',\n    discussion_id: '112',\n    subject: '',\n    body: '',\n    user_identities: [],\n    is_unread: true,\n    protocol: 'whatever',\n    participants: [],\n    tags: [],\n    date: '',\n    date_insert: '',\n    is_answered: false,\n    is_draft: true,\n    raw_msg_id: '111',\n    user_id: '114',\n  };\n  const draftBase: IDraftMessagePayload = {\n    user_identities: [],\n  };\n  const participantBase: Participant = {\n    address: '',\n    protocol: '',\n    type: 'To',\n  };\n  it('update body', () => {\n    const draft = {\n      ...draftBase,\n      message_id: '111',\n      participants: [],\n      body: 'new body',\n    };\n    const message = {\n      ...messageBase,\n      body: 'old body',\n      message_id: '111',\n      discussion_id: '112',\n    };\n    expect(calcSyncDraft(draft, message)).toMatchInlineSnapshot(`\n      Object {\n        \"body\": \"new body\",\n        \"date\": \"\",\n        \"date_insert\": \"\",\n        \"discussion_id\": \"112\",\n        \"identity_id\": \"\",\n        \"is_answered\": false,\n        \"is_draft\": true,\n        \"is_unread\": true,\n        \"message_id\": \"111\",\n        \"parent_id\": undefined,\n        \"protocol\": \"whatever\",\n        \"raw_msg_id\": \"111\",\n        \"recipients\": Array [],\n        \"subject\": undefined,\n        \"tags\": Array [],\n        \"user_id\": \"114\",\n        \"user_identities\": Array [],\n      }\n    `);\n  });\n\n  it('message with new attachments', () => {\n    const draft = {\n      ...draftBase,\n      message_id: '111',\n      participants: [],\n      body: 'new body',\n      attachments: [{ file_name: 'foo.png', temp_id: 'aabbb111' }],\n    };\n    const message = {\n      ...messageBase,\n      body: 'old body',\n      message_id: '111',\n      discussion_id: '112',\n      attachments: [\n        { file_name: 'foo.png', temp_id: 'aabbb111' },\n        { file_name: 'bar.png', temp_id: 'aabbb222' },\n      ],\n    };\n    expect(calcSyncDraft(draft, message)).toMatchInlineSnapshot(`\n      Object {\n        \"attachments\": Array [\n          Object {\n            \"file_name\": \"foo.png\",\n            \"temp_id\": \"aabbb111\",\n          },\n          Object {\n            \"file_name\": \"bar.png\",\n            \"temp_id\": \"aabbb222\",\n          },\n        ],\n        \"body\": \"new body\",\n        \"date\": \"\",\n        \"date_insert\": \"\",\n        \"discussion_id\": \"112\",\n        \"identity_id\": \"\",\n        \"is_answered\": false,\n        \"is_draft\": true,\n        \"is_unread\": true,\n        \"message_id\": \"111\",\n        \"parent_id\": undefined,\n        \"protocol\": \"whatever\",\n        \"raw_msg_id\": \"111\",\n        \"recipients\": Array [],\n        \"subject\": undefined,\n        \"tags\": Array [],\n        \"user_id\": \"114\",\n        \"user_identities\": Array [],\n      }\n    `);\n  });\n\n  it('message with removed attachments', () => {\n    const draft = {\n      ...draftBase,\n      message_id: '111',\n      participants: [],\n      body: 'new body',\n      attachments: [{ file_name: 'foo.png', temp_id: 'aabbb111' }],\n    };\n    const message = {\n      ...messageBase,\n      body: 'old body',\n      message_id: '111',\n      discussion_id: '112',\n    };\n    expect(calcSyncDraft(draft, message)).toMatchInlineSnapshot(`\n      Object {\n        \"body\": \"new body\",\n        \"date\": \"\",\n        \"date_insert\": \"\",\n        \"discussion_id\": \"112\",\n        \"identity_id\": \"\",\n        \"is_answered\": false,\n        \"is_draft\": true,\n        \"is_unread\": true,\n        \"message_id\": \"111\",\n        \"parent_id\": undefined,\n        \"protocol\": \"whatever\",\n        \"raw_msg_id\": \"111\",\n        \"recipients\": Array [],\n        \"subject\": undefined,\n        \"tags\": Array [],\n        \"user_id\": \"114\",\n        \"user_identities\": Array [],\n      }\n    `);\n  });\n\n  describe('participants', () => {\n    it('uses the participants of up to date message', () => {\n      const draft = {\n        ...draftBase,\n        message_id: '111',\n        participants: [],\n        parent_id: 'aabbb1',\n        body: 'new body',\n        attachments: [{ file_name: 'foo.png', temp_id: 'aabbb111' }],\n        discussion_id: '112',\n      };\n      const message = {\n        ...messageBase,\n        participants: [\n          { ...participantBase, participant_id: '01' },\n          { ...participantBase, participant_id: '02' },\n        ],\n        parent_id: 'aabbb0',\n        body: 'old body',\n        message_id: '111',\n        discussion_id: '112',\n      };\n      expect(calcSyncDraft(draft, message)).toMatchInlineSnapshot(`\n        Object {\n          \"body\": \"new body\",\n          \"date\": \"\",\n          \"date_insert\": \"\",\n          \"discussion_id\": \"112\",\n          \"identity_id\": \"\",\n          \"is_answered\": false,\n          \"is_draft\": true,\n          \"is_unread\": true,\n          \"message_id\": \"111\",\n          \"parent_id\": \"aabbb1\",\n          \"protocol\": \"whatever\",\n          \"raw_msg_id\": \"111\",\n          \"recipients\": Array [\n            Object {\n              \"address\": \"\",\n              \"participant_id\": \"01\",\n              \"protocol\": \"\",\n              \"type\": \"To\",\n            },\n            Object {\n              \"address\": \"\",\n              \"participant_id\": \"02\",\n              \"protocol\": \"\",\n              \"type\": \"To\",\n            },\n          ],\n          \"subject\": undefined,\n          \"tags\": Array [],\n          \"user_id\": \"114\",\n          \"user_identities\": Array [],\n        }\n      `);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/calcSyncDraft.ts",
    "content": "import { Message } from 'src/modules/message';\nimport { IDraftMessagePayload } from 'src/modules/message/types';\nimport {\n  DraftMessageFormData,\n  mapMessageToDraftMessageFormData,\n  mapParticipantToRecipient,\n} from '../models';\n\nexport const calcSyncDraft = (\n  draft: IDraftMessagePayload,\n  message: Message\n): DraftMessageFormData => {\n  const nextDraft = mapMessageToDraftMessageFormData(message);\n  const { body = '', subject, user_identities } = draft;\n\n  const parentId = draft.parent_id || message.parent_id;\n\n  return {\n    ...nextDraft,\n    body,\n    subject,\n    parent_id: parentId,\n    recipients: message.participants\n      .filter((participant) => participant.type !== 'From')\n      .map(mapParticipantToRecipient),\n    // FIXME\n    identity_id: user_identities?.[0] || '',\n  };\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/changeAuthorInParticipants.js",
    "content": "import { getIdentityProtocol } from './getIdentityProtocol';\n\nconst isUserParticipant = ({ user, participant }) =>\n  participant.contact_ids &&\n  participant.contact_ids.includes(user?.contact.contact_id);\n\nconst isIdentityParticipant = ({ identity, participant }) =>\n  identity.identifier === participant.address &&\n  getIdentityProtocol(identity) === participant.protocol;\n\n/**\n * 1. retrieve the identity used in participants\n * 2. or retrieve the participant associated to the user\n * 3. Set as author (it will be filtered in draft because we only want recipients)\n * 4. In case of no detections, a draft cannot be sent so all participants will be recipient\n * but it will have no effects\n */\nexport const changeAuthorInParticipants = ({\n  participants,\n  user,\n  identity = undefined,\n}) => {\n  if (!participants) {\n    return undefined;\n  }\n\n  const authorParticipant =\n    (identity &&\n      participants.find((participant) =>\n        isIdentityParticipant({ identity, participant })\n      )) ||\n    participants.find((participant) =>\n      isUserParticipant({ user, participant })\n    );\n\n  return participants.map((participant) => ({\n    ...participant,\n    type: participant === authorParticipant ? 'From' : 'To',\n  }));\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/changeAuthorInParticipants.spec.js",
    "content": "import { changeAuthorInParticipants } from './changeAuthorInParticipants';\n\ndescribe('modules draftMessage - service - changeAuthorInParticipants', () => {\n  const user = {\n    contact: { contact_id: 'user-contact' },\n  };\n\n  describe('identity', () => {\n    it('has already identity as author', () => {\n      const identity = {\n        identifier: 'leela@planet-express.tld',\n        protocol: 'imap',\n      };\n      const participants = [\n        {\n          address: 'fry@planet-express.tld',\n          contact_ids: ['user-contact'],\n          protocol: 'email',\n          type: 'To',\n        },\n        {\n          address: 'zoidberg@planet-express.tld',\n          contact_ids: ['t2'],\n          protocol: 'email',\n          type: 'To',\n        },\n        {\n          address: 'leela@planet-express.tld',\n          protocol: 'email',\n          type: 'From',\n        },\n      ];\n      expect(\n        changeAuthorInParticipants({ participants, user, identity })\n      ).toEqual(participants);\n    });\n\n    it('changes the author using the identity (an other recipient is associated to the user)', () => {\n      const identity = {\n        identifier: 'leela@planet-express.tld',\n        protocol: 'imap',\n      };\n      const participants = [\n        {\n          address: 'fry@planet-express.tld',\n          contact_ids: ['user-contact'],\n          protocol: 'email',\n          type: 'From',\n        },\n        {\n          address: 'zoidberg@planet-express.tld',\n          contact_ids: ['t2'],\n          protocol: 'email',\n          type: 'To',\n        },\n        { address: 'leela@planet-express.tld', protocol: 'email', type: 'To' },\n      ];\n      expect(\n        changeAuthorInParticipants({ participants, user, identity })\n      ).toEqual([\n        { ...participants[0], type: 'To' },\n        participants[1],\n        { ...participants[2], type: 'From' },\n      ]);\n    });\n  });\n\n  describe('user', () => {\n    it('has already user as author', () => {\n      const identity = undefined;\n\n      const participants = [\n        {\n          address: 'fry@planet-express.tld',\n          contact_ids: ['user-contact'],\n          protocol: 'email',\n          type: 'From',\n        },\n        {\n          address: 'zoidberg@planet-express.tld',\n          contact_ids: ['t2'],\n          protocol: 'email',\n          type: 'To',\n        },\n        { address: 'leela@planet-express.tld', protocol: 'email', type: 'To' },\n      ];\n      expect(\n        changeAuthorInParticipants({ participants, user, identity })\n      ).toEqual(participants);\n    });\n\n    it('changes the author using the user', () => {\n      const identity = undefined;\n\n      const participants = [\n        {\n          address: 'zoidberg@planet-express.tld',\n          contact_ids: ['t2'],\n          protocol: 'email',\n          type: 'From',\n        },\n        {\n          address: 'fry@planet-express.tld',\n          contact_ids: ['user-contact'],\n          protocol: 'email',\n          type: 'To',\n        },\n        { address: 'leela@planet-express.tld', protocol: 'email', type: 'To' },\n      ];\n      expect(\n        changeAuthorInParticipants({ participants, user, identity })\n      ).toEqual([\n        { ...participants[0], type: 'To' },\n        { ...participants[1], type: 'From' },\n        participants[2],\n      ]);\n    });\n  });\n\n  describe('edge cases', () => {\n    it('has no identity anymore and no associated user', () => {\n      const identity = {\n        identifier: 'whatever@example.tld',\n        protocol: 'imap',\n      };\n\n      const participants = [\n        { address: 'fry@old-address.example', protocol: 'email', type: 'To' },\n        {\n          address: 'zoidberg@planet-express.tld',\n          contact_ids: ['t2'],\n          protocol: 'email',\n          type: 'From',\n        },\n        { address: 'leela@planet-express.tld', protocol: 'email', type: 'To' },\n      ];\n      expect(\n        changeAuthorInParticipants({ participants, user, identity })\n      ).toEqual([\n        participants[0],\n        { ...participants[1], type: 'To' },\n        participants[2],\n      ]);\n    });\n\n    it('has not participants', () => {\n      const identity = undefined;\n\n      const participants = undefined;\n      expect(\n        changeAuthorInParticipants({ participants, user, identity })\n      ).toEqual(participants);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/filterIdentities.js",
    "content": "import { IDENTITY_TYPE_TWITTER, IDENTITY_TYPE_MASTODON } from '../../contact';\nimport { getIdentityProtocol } from './getIdentityProtocol';\n\nconst getParticipantsContactsExceptUser = ({ contacts, participants, user }) =>\n  participants.reduce((acc, participant) => {\n    if (!participant.contact_ids) {\n      return acc;\n    }\n\n    const contactIds = participant.contact_ids.filter(\n      (contactId) => contactId !== user?.contact.contact_id\n    );\n\n    if (contactIds.length === 0) {\n      return acc;\n    }\n\n    const participantContacts = contactIds\n      .map((contactId) => contacts.find((ct) => ct.contact_id === contactId))\n      // filter deleted contact that still present in recipients\n      .filter((ct) => !!ct);\n\n    return [...acc, ...participantContacts];\n  }, []);\n\nconst getAvailableProtocolsForTheContact = ({ contact }) => {\n  const protocols = [];\n  if (contact.emails && contact.emails.length >= 1) {\n    protocols.push('email');\n  }\n\n  if (\n    contact.identities &&\n    contact.identities.filter(\n      (identity) => identity.type === IDENTITY_TYPE_TWITTER\n    ).length >= 1\n  ) {\n    protocols.push('twitter');\n  }\n\n  if (\n    contact.identities &&\n    contact.identities.filter(\n      (identity) => identity.type === IDENTITY_TYPE_MASTODON\n    ).length >= 1\n  ) {\n    protocols.push('mastodon');\n  }\n\n  return protocols;\n};\n\nconst getMessageProtocol = (message) => {\n  const { protocol } = message;\n  if (!protocol) {\n    return 'email';\n  }\n\n  return message.protocol;\n};\n\nexport const filterIdentities = ({\n  identities,\n  parentMessage,\n  contacts,\n  user,\n}) => {\n  if (!parentMessage) {\n    return identities;\n  }\n\n  const participantsContacts = getParticipantsContactsExceptUser({\n    contacts,\n    participants: parentMessage.participants,\n    user,\n  });\n\n  // in a discussion 1-to-n we allow to switch identity of the same protocol\n  if (\n    contacts.length === 0 ||\n    parentMessage.participants.length >= 3 ||\n    participantsContacts.length === 0 ||\n    participantsContacts.length > 1\n  ) {\n    return identities.filter(\n      (identity) =>\n        getIdentityProtocol(identity) === getMessageProtocol(parentMessage)\n    );\n  }\n\n  // in a discussion 1-to-1 we allow to switch identity of the protocol that the associated contact\n  // can receive\n  const [contact] = participantsContacts;\n  const availableProtocols = getAvailableProtocolsForTheContact({ contact });\n\n  return identities.filter((identity) =>\n    availableProtocols.includes(getIdentityProtocol(identity))\n  );\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/filterIdentities.spec.js",
    "content": "import { filterIdentities } from './filterIdentities';\n\ndescribe('modules identity - service - filterIdentities', () => {\n  const identities = [\n    { identity_id: 'foo', type: 'local', protocol: 'email' },\n    { identity_id: 'bar', type: 'remote', protocol: 'twitter' },\n    { identity_id: 'baz', type: 'remote', protocol: 'email' },\n  ];\n\n  const contacts = [\n    {\n      contact_id: 'contact-user',\n      emails: [{ address: 'me@caliopen.local' }],\n      identities: [{ name: 'me', type: 'twitter' }],\n    },\n    {\n      contact_id: 'contact-with-all-protocols',\n      emails: [{ address: 'foo@bar.tld' }],\n      identities: [{ name: 'foo', type: 'twitter' }],\n    },\n    {\n      contact_id: 'contact-with-email-protocol',\n      emails: [{ address: 'foo2@bar2.tld' }],\n    },\n    {\n      contact_id: 'contact-with-same-email-user',\n      emails: [{ address: 'me@caliopen.local' }],\n    },\n  ];\n\n  const user = {\n    contact: {\n      contact_id: 'contact-user',\n    },\n  };\n\n  it('list all when no parent', () => {\n    expect(filterIdentities({ identities, contacts, user })).toEqual(\n      identities\n    );\n  });\n\n  it('filter parent message protocol (email) (1-to-n no contacts)', () => {\n    const parentMessage = {\n      protocol: 'email',\n      participants: [\n        { address: 'foo@contact.tld', protocol: 'email' },\n        { address: 'foo2@contact.tld', protocol: 'email' },\n        {\n          address: 'me@caliopen.local',\n          protocol: 'email',\n          contact_ids: ['contact-user'],\n        },\n      ],\n    };\n\n    expect(\n      filterIdentities({\n        identities,\n        parentMessage,\n        contacts,\n        user,\n      })\n    ).toEqual([identities[0], identities[2]]);\n  });\n\n  it('filter parent message protocol (twitter) (1-to-n)', () => {\n    const parentMessage = {\n      protocol: 'twitter',\n      participants: [\n        { address: '@contact', protocol: 'twitter' },\n        { address: '@contact2', protocol: 'twitter' },\n        { address: '@me', protocol: 'twitter', contact_ids: ['contact-user'] },\n      ],\n    };\n\n    expect(\n      filterIdentities({\n        identities,\n        parentMessage,\n        contacts,\n        user,\n      })\n    ).toEqual([identities[1]]);\n  });\n\n  it('filter discussion 1-to-1 available contact address', () => {\n    const parentMessage = {\n      protocol: 'twitter',\n      participants: [\n        {\n          address: '@contact',\n          protocol: 'twitter',\n          contact_ids: ['contact-with-all-protocols'],\n        },\n        { address: '@me', protocol: 'twitter', contact_ids: ['contact-user'] },\n      ],\n    };\n\n    expect(\n      filterIdentities({\n        identities,\n        parentMessage,\n        contacts,\n        user,\n      })\n    ).toEqual(identities);\n  });\n\n  it('filter discussion 1-to-1 without contact', () => {\n    const parentMessage = {\n      protocol: 'twitter',\n      participants: [\n        { address: '@contact', protocol: 'twitter' },\n        { address: '@me', protocol: 'twitter', contact_ids: ['contact-user'] },\n      ],\n    };\n\n    expect(\n      filterIdentities({\n        identities,\n        parentMessage,\n        contacts,\n        user,\n      })\n    ).toEqual([identities[1]]);\n  });\n\n  it('filter discussion 1-to-n with contacts', () => {\n    const parentMessage = {\n      protocol: 'email',\n      participants: [\n        {\n          address: 'foo@bar.tld',\n          protocol: 'email',\n          contacts_ids: ['contact-with-all-protocols'],\n        },\n        {\n          address: 'foo2@bar2.tld',\n          protocol: 'email',\n          contacts_ids: ['contact-with-email-protocol'],\n        },\n        {\n          address: 'me@caliopen.local',\n          protocol: 'email',\n          contact_ids: ['contact-user'],\n        },\n      ],\n    };\n\n    expect(\n      filterIdentities({\n        identities,\n        parentMessage,\n        contacts,\n        user,\n      })\n    ).toEqual([identities[0], identities[2]]);\n  });\n\n  // TODO: temporary fix for https://github.com/CaliOpen/Caliopen/issues/1131\n  it('mail identities can be several protocols', () => {\n    const multiMailIdentitities = [\n      { identity_id: 'foo', type: 'local', protocol: 'smtp' },\n      { identity_id: 'bar', type: 'remote', protocol: 'imap' },\n      { identity_id: 'baz', type: 'remote', protocol: 'email' },\n      { identity_id: 'bad', type: 'remote', protocol: 'twitter' },\n    ];\n    const parentMessage = {\n      protocol: 'email',\n      participants: [\n        {\n          address: 'foo@bar.tld',\n          protocol: 'email',\n          contacts_ids: ['contact-with-all-protocols'],\n        },\n        {\n          address: 'foo2@bar2.tld',\n          protocol: 'email',\n          contacts_ids: ['contact-with-email-protocol'],\n        },\n        {\n          address: 'me@caliopen.local',\n          protocol: 'email',\n          contact_ids: ['contact-user'],\n        },\n      ],\n    };\n\n    expect(\n      filterIdentities({\n        identities: multiMailIdentitities,\n        parentMessage,\n        contacts,\n        user,\n      })\n    ).toEqual([\n      multiMailIdentitities[0],\n      multiMailIdentitities[1],\n      multiMailIdentitities[2],\n    ]);\n  });\n\n  // TODO: temporary fix for https://github.com/CaliOpen/Caliopen/issues/1130\n  it('parent message might not has protocol', () => {\n    const parentMessage = {\n      participants: [\n        {\n          address: 'foo@bar.tld',\n          protocol: 'email',\n          contacts_ids: ['contact-with-all-protocols'],\n        },\n        {\n          address: 'foo2@bar2.tld',\n          protocol: 'email',\n          contacts_ids: ['contact-with-email-protocol'],\n        },\n        {\n          address: 'me@caliopen.local',\n          protocol: 'email',\n          contact_ids: ['contact-user'],\n        },\n      ],\n    };\n\n    expect(\n      filterIdentities({\n        identities,\n        parentMessage,\n        contacts,\n        user,\n      })\n    ).toEqual([identities[0], identities[2]]);\n  });\n\n  it('only the message protocol when an unknown participant and one contact', () => {\n    const parentMessage = {\n      participants: [\n        {\n          address: 'simple@participant.tld',\n          protocol: 'email',\n          type: 'To',\n        },\n        {\n          address: 'foo@bar.tld',\n          contact_ids: ['contact-with-all-protocols'],\n          protocol: 'email',\n          type: 'To',\n        },\n        {\n          address: 'me@caliopen.local',\n          contact_ids: ['contact-user'],\n          protocol: 'smtp',\n          type: 'From',\n        },\n      ],\n    };\n\n    expect(\n      filterIdentities({\n        identities,\n        parentMessage,\n        contacts,\n        user,\n      })\n    ).toEqual([identities[0], identities[2]]);\n  });\n\n  it('contacts not loaded', () => {\n    const parentMessage = {\n      participants: [\n        {\n          address: 'simple@participant.tld',\n          protocol: 'email',\n          type: 'To',\n        },\n        {\n          address: 'me@caliopen.local',\n          contact_ids: ['contact-with-same-email-user'],\n          label: 'chamal@alpha.caliopen.org',\n          protocol: 'email',\n          type: 'To',\n        },\n        {\n          address: 'me@caliopen.local',\n          contact_ids: ['contact-user'],\n          label: 'chamal None',\n          protocol: 'smtp',\n          type: 'From',\n        },\n      ],\n    };\n\n    expect(\n      filterIdentities({\n        identities,\n        parentMessage,\n        contacts: [],\n        user,\n      })\n    ).toEqual([identities[0], identities[2]]);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/getIdentityProtocol.js",
    "content": "const IDENTITY_PROTOCOLS = {\n  email: 'email',\n  smtp: 'email',\n  imap: 'email',\n  twitter: 'twitter',\n  mastodon: 'mastodon',\n};\n\nexport const getIdentityProtocol = (identity) =>\n  IDENTITY_PROTOCOLS[identity.protocol];\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/isValidRecipient.js",
    "content": "import { getIdentityProtocol } from './getIdentityProtocol';\nimport protocolsConfig from '../../../services/protocols-config';\n\nexport const isValidRecipient = ({ recipient, identity }) => {\n  const { regexp } = protocolsConfig[recipient.protocol] || {};\n\n  return (\n    !identity ||\n    (recipient.protocol === getIdentityProtocol(identity) &&\n      regexp &&\n      regexp.test(recipient.address))\n  );\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/isValidRecipient.spec.js",
    "content": "import { isValidRecipient } from './isValidRecipient';\n\ndescribe('draftMessage module - services - isValidRecipient', () => {\n  it('recipient is not same type as identity', async () => {\n    const identity = { protocol: 'email' };\n    const recipient = { protocol: 'twitter', address: 'foo' };\n\n    expect(isValidRecipient({ recipient, identity })).toEqual(false);\n  });\n\n  it('recipient is same type as identity', async () => {\n    const identity = { protocol: 'email' };\n    const recipient = { protocol: 'email', address: 'foo@bar.tld' };\n\n    expect(isValidRecipient({ recipient, identity })).toEqual(true);\n  });\n\n  it('recipient is same type as identity but address does not match config', async () => {\n    const identity = { protocol: 'email' };\n    const recipient = { protocol: 'email', address: 'bar_tld' };\n\n    expect(isValidRecipient({ recipient, identity })).toEqual(false);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/services/validate.ts",
    "content": "import { I18n } from '@lingui/core';\nimport { IDraftMessageFormData } from '../types';\nimport { getIdentityProtocol } from './getIdentityProtocol';\nimport { PROTOCOL_MASTODON, PROTOCOL_TWITTER } from '../../message';\n\nexport const validate = ({\n  draftMessage,\n  i18n,\n  availableIdentities,\n  options = {\n    ignoreBody: false,\n  },\n}: {\n  draftMessage: IDraftMessageFormData;\n  i18n: I18n;\n  availableIdentities: any[];\n  options?: { ignoreBody: boolean };\n}): string[] => {\n  const identity = availableIdentities.find(\n    (ident) => ident.identity_id === draftMessage.identity_id\n  );\n\n  if (!identity) {\n    return [\n      i18n._(/* i18n */ 'draft-message.errors.missing-identity', undefined, {\n        message: 'An identity is mandatory to create a draft',\n      }),\n    ];\n  }\n\n  const errors: string[] = [];\n  const protocol = getIdentityProtocol(identity);\n\n  if (\n    draftMessage.recipients.some(\n      (participant) => participant.protocol !== protocol\n    )\n  ) {\n    errors.push(\n      i18n._(\n        /* i18n */ 'draft-message.errors.invalid-participant',\n        { protocol },\n        {\n          message:\n            'According to your identity, all your participants must use a {protocol} address to contact them all together',\n        }\n      )\n    );\n  }\n\n  if (\n    !options.ignoreBody &&\n    (protocol === PROTOCOL_TWITTER || protocol === PROTOCOL_MASTODON) &&\n    draftMessage.body.length === 0\n  ) {\n    errors.push(\n      i18n._(\n        /* i18n */ 'draft-message.errors.empty-body',\n        { protocol },\n        { message: 'The body cannot be empty for a {protocol} message.' }\n      )\n    );\n  }\n\n  return errors;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/draftMessage/types.d.ts",
    "content": "import { Participant } from '../message';\n\n// export interface IDraftMessage extends Message {\n//   // TODO: verify\n//   user_identities: string[];\n//   participants: {\n//     address: string;\n//     label?: string;\n//     protocol: string;\n//     type: 'To' | 'From';\n//     contact_ids?: string[];\n//   }[];\n//   body: string;\n//   subject?: string;\n//   parent_id: void | string;\n// }\n\n// XXX extend Participant?\nexport interface Recipient {\n  address: string;\n  label?: string;\n  protocol: string;\n  // XXX: without from?\n  type: 'To' | 'Cc' | 'Bcc' | 'From' | 'Reply-To' | 'Sender';\n  contact_ids?: string[];\n}\n\nexport interface IDraftMessageFormData {\n  message_id: string;\n  discussion_id?: string;\n  recipients: Array<Recipient>;\n  // FIXME: Keep a copy of participants because of a bug backend side: the indenty is duplicated (from and to in participant) in draft. So it defeats discussion id selection on advanced draft form\n  participants: Array<Participant>;\n  body: string;\n  subject?: string;\n  parent_id?: string;\n  identity_id: string;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/actions/decryptMessage.js",
    "content": "import {\n  isMessageEncrypted,\n  decryptMessage as decryptMessageConcret,\n} from '../../../services/encryption';\nimport { getKeysForMessage } from '../../../services/openpgp-keychain-repository';\nimport {\n  askPassphrase,\n  needPassphrase,\n  needPrivateKey,\n  decryptMessage as decryptMessageStart,\n  decryptMessageSuccess,\n  decryptMessageFail,\n} from '../../../store/modules/encryption';\n\nconst getKeyPassphrase = (state, fingerprint) => {\n  const { privateKeysByFingerprint } = state.encryption;\n\n  return (\n    privateKeysByFingerprint[fingerprint] &&\n    privateKeysByFingerprint[fingerprint].status === 'ok' &&\n    privateKeysByFingerprint[fingerprint].passphrase\n  );\n};\n\n// XXX: refactor this ASAP\nexport const decryptMessage =\n  ({ message }) =>\n  async (dispatch, getState) => {\n    if (!isMessageEncrypted(message)) {\n      return message;\n    }\n\n    try {\n      dispatch(decryptMessageStart({ message }));\n      const keys = await getKeysForMessage(message);\n\n      if (keys.length <= 0) {\n        dispatch(needPrivateKey({ message }));\n\n        return message;\n      }\n\n      let usableKey = keys.find((key) => key.isDecrypted());\n      let passphrase = null;\n\n      if (!usableKey) {\n        const state = getState();\n        usableKey = keys.find((key) =>\n          getKeyPassphrase(state, key.getFingerprint())\n        );\n\n        if (usableKey) {\n          passphrase = getKeyPassphrase(state, usableKey.getFingerprint());\n          try {\n            await usableKey.decrypt(passphrase);\n          } catch (e) {\n            dispatch(\n              askPassphrase({\n                fingerprint: usableKey.getFingerprint(),\n                error: e.message,\n              })\n            );\n\n            return message;\n          }\n        }\n      }\n\n      if (!usableKey) {\n        keys.forEach((key) =>\n          dispatch(askPassphrase({ fingerprint: key.getFingerprint() }))\n        );\n        dispatch(\n          needPassphrase({\n            message,\n            fingerprints: keys.map((key) => key.getFingerprint()),\n          })\n        );\n\n        return message;\n      }\n\n      const decryptedMessage = await decryptMessageConcret(message, [\n        usableKey,\n      ]);\n      dispatch(decryptMessageSuccess({ message, decryptedMessage }));\n\n      return decryptedMessage;\n    } catch (e) {\n      const { message: error } = e;\n\n      dispatch(decryptMessageFail({ message, error }));\n\n      return message;\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/actions/encryptMessage.js",
    "content": "import { identitiesSelector } from '../../identity';\nimport {\n  getKeysForEmail,\n  PUBLIC_KEY,\n} from '../../../services/openpgp-keychain-repository';\nimport { getRecipientKeys } from './getRecipientKeys';\nimport { encryptMessage as encryptMessageConcret } from '../../../services/encryption';\nimport {\n  encryptMessage as encryptMessageStart,\n  encryptMessageSuccess,\n  encryptMessageFail,\n} from '../../../store/modules/encryption';\n\nexport const encryptMessage =\n  ({ message }) =>\n  async (dispatch, getState) => {\n    try {\n      if (!message.user_identities || message.user_identities.length === 0) {\n        throw new Error('identity is missing');\n      }\n\n      if (message.attachments) {\n        throw new Error(\n          'Encryption for message with attachments is not supported yet'\n        );\n      }\n\n      const identity = identitiesSelector(getState()).find((curr) =>\n        message.user_identities.includes(curr.identity_id)\n      );\n\n      // 1. we need to check all addresses to find keys.\n      const [userKey, keys] = await Promise.all([\n        getKeysForEmail(identity.identifier, PUBLIC_KEY),\n        dispatch(getRecipientKeys({ message })),\n      ]);\n\n      if (!keys || keys.length === 0 || !userKey) {\n        throw new Error('Keys are missing');\n      }\n\n      dispatch(encryptMessageStart({ message }));\n      // 2. but there is no need for more than 1 key\n      const encryptedMessage = await encryptMessageConcret(message, [\n        userKey[0].armor(),\n        ...keys,\n      ]);\n\n      dispatch(encryptMessageSuccess({ message, encryptedMessage }));\n\n      return encryptedMessage;\n    } catch (error) {\n      dispatch(encryptMessageFail({ message, error: error.message || error }));\n\n      return undefined;\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/actions/fetchRemoteKeys.js",
    "content": "import { requestPublicKeys } from '../../../store/modules/public-key';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\n\nexport const fetchRemoteKeys = (contactIds) => (dispatch) =>\n  Promise.all(\n    contactIds.map((contactId) =>\n      tryCatchAxiosAction(() => dispatch(requestPublicKeys({ contactId })))\n    )\n  );\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/actions/getRecipientKeys.js",
    "content": "import {\n  getParticipantsAddresses,\n  getParticipantsContactIds,\n  getRecipients,\n} from '../../../services/message';\nimport {\n  filterKeysByAddress,\n  checkEachAddressHasKey,\n  getStoredKeys,\n} from '../services/keyring/remoteKeys';\nimport { fetchRemoteKeys } from './fetchRemoteKeys';\n\nexport const getRecipientKeys =\n  ({ message }) =>\n  async (dispatch, getState) => {\n    const recipients = getRecipients(message);\n    const allContactIds = getParticipantsContactIds({\n      participants: recipients,\n    });\n    const allAddresses = getParticipantsAddresses({ participants: recipients });\n\n    const { keys: cachedKeys, missingKeysContactIds } = getStoredKeys(\n      getState(),\n      allContactIds\n    );\n    const fetchedKeys =\n      (await dispatch(fetchRemoteKeys(missingKeysContactIds))) || [];\n\n    // filter out unnecessary public keys.\n    const filteredKeys = filterKeysByAddress(\n      [\n        ...cachedKeys,\n        ...fetchedKeys.reduce((acc, key) => [...acc, ...key.pubkeys], []),\n      ],\n      allAddresses\n    );\n\n    // Check if we have all needed public keys.\n    if (!checkEachAddressHasKey(allAddresses, filteredKeys)) {\n      throw new Error('Some public keys are missing.');\n    }\n\n    return filteredKeys;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/components/AskPassphraseForm/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { Trans, withI18n } from '@lingui/react';\nimport classNames from 'classnames';\nimport {\n  FieldErrors,\n  TextFieldGroup,\n  Button,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\nimport { setPassphrase } from '../../../../store/modules/encryption';\n\nimport './style.scss';\n\nconst mapDispatchToProps = (dispatch, ownProps) => ({\n  onSubmit: (passphrase) =>\n    dispatch(setPassphrase({ passphrase, fingerprint: ownProps.fingerprint })),\n});\n\nconst mapStateToProps = (state, ownProps) => ({\n  error: state.encryption.privateKeysByFingerprint[ownProps.fingerprint].error,\n});\n\n@withI18n()\n@connect(mapStateToProps, mapDispatchToProps)\nclass AskPassphraseForm extends Component {\n  static propTypes = {\n    numberMessages: PropTypes.number.isRequired,\n    fingerprint: PropTypes.string.isRequired,\n    onSubmit: PropTypes.func.isRequired,\n    className: PropTypes.string,\n    error: PropTypes.string,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {\n    className: '',\n    error: undefined,\n  };\n\n  state = {\n    passphrase: '',\n  };\n\n  getErrorId = (message) => {\n    const { i18n } = this.props;\n\n    switch (message) {\n      case 'Incorrect key passphrase':\n        return i18n._(\n          /* i18n */ 'encryption.ask-passphrase.error.invalid-passphrase',\n          null,\n          'Invalid passphrase'\n        );\n      default:\n        return i18n._(\n          /* i18n */ 'encrytion.ask-passphrase.error.unknown',\n          null,\n          'Unknown error'\n        );\n    }\n  };\n\n  handleChange = (event) => this.setState({ passphrase: event.target.value });\n\n  handleSubmit = (event) => {\n    const { onSubmit } = this.props;\n\n    onSubmit(this.state.passphrase);\n    event.preventDefault();\n  };\n\n  render() {\n    const { error, className, fingerprint, numberMessages } = this.props;\n\n    return (\n      <FormGrid className={classNames(className, 'm-ask-passphrase')}>\n        <form onSubmit={this.handleSubmit}>\n          <Trans\n            id=\"encryption.ask-passphrase.explain\"\n            values={{ fingerprint: fingerprint.toUpperCase(), numberMessages }}\n          >\n            Please enter passphrase for key {`${fingerprint}`} to unlock{' '}\n            {`${numberMessages}`} messages.\n          </Trans>\n          {error && (\n            <FormRow>\n              <FormColumn>\n                <FieldErrors errors={[this.getErrorId(error)]} />\n              </FormColumn>\n            </FormRow>\n          )}\n          <FormRow>\n            <FormColumn>\n              {\n                // this input is intended to enable multiple key/passphrase\n                // storage in password manager.\n              }\n              <input\n                type=\"text\"\n                className=\"m-ask-passphrase--input-fingerprint\"\n                name={`login-${fingerprint}`}\n                value={fingerprint.toUpperCase()}\n                readOnly\n              />\n              <TextFieldGroup\n                inputProps={{\n                  type: 'password',\n                  name: `passphrase-${fingerprint}`,\n                  value: this.state.passphrase,\n                  onChange: this.handleChange,\n                  autocomplete: 'current-password',\n                }}\n              />\n            </FormColumn>\n            <FormColumn>\n              <Button type=\"submit\" shape=\"plain\">\n                <Trans\n                  id=\"encryption.ask-passphrase.validate\"\n                  message=\"Validate\"\n                />\n              </Button>\n            </FormColumn>\n          </FormRow>\n        </form>\n      </FormGrid>\n    );\n  }\n}\n\nexport default AskPassphraseForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/components/AskPassphraseForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-ask-passphrase {\n  &--input-fingerprint {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/components/CheckDecryption/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { createSelector } from 'reselect';\n// import { Trans } from '@lingui/react';\nimport AskPassphraseForm from '../AskPassphraseForm';\nimport { messageEncryptionStatusesSelector } from '../../selectors/message';\n\nimport './style.scss';\n\nconst lockedMessagesSelector = (messagesIds) =>\n  createSelector([messageEncryptionStatusesSelector], (encryptionState) => ({\n    lockedMessagesByKey: messagesIds.reduce((acc, messageId) => {\n      const messageStatus = encryptionState[messageId];\n\n      if (messageStatus && messageStatus.status === 'need_passphrase') {\n        return {\n          ...acc,\n          [messageStatus.keyFingerprint]:\n            (acc[messageStatus.keyFingerprint] || 0) + 1,\n        };\n      }\n\n      return acc;\n    }, {}),\n    keyLessMessages: messagesIds.reduce(\n      (acc, messageId) =>\n        encryptionState[messageId] &&\n        encryptionState[messageId].status === 'need_privatekey'\n          ? acc + 1\n          : acc,\n      0\n    ),\n  }));\n\nconst mapStateToProps = (state, ownProps) =>\n  lockedMessagesSelector(\n    ownProps.messages.map((message) => message.message_id)\n  );\n\n@connect(mapStateToProps)\nclass CheckDecryption extends Component {\n  static propTypes = {\n    // message is mandatory for mapStateToProps calculation.\n    // eslint-disable-next-line react/no-unused-prop-types\n    messages: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n    lockedMessagesByKey: PropTypes.shape({}).isRequired,\n    keyLessMessages: PropTypes.number.isRequired,\n  };\n\n  render() {\n    const { lockedMessagesByKey } = this.props;\n\n    return (\n      <section className=\"m-check-decryption\">\n        {Object.entries(lockedMessagesByKey).map(\n          ([fingerprint, numberMessages]) => (\n            <AskPassphraseForm\n              fingerprint={fingerprint}\n              numberMessages={numberMessages}\n              key={fingerprint}\n              className=\"m-check-decryption-panel\"\n            />\n          )\n        )}\n        {\n          // TODO re-enable when dismiss ok.\n          /* (keyLessMessages > 0) && (\n          <div className=\"m-check-decryption__missing-key m-check-decryption-panel\">\n            <Trans id=\"encryption.messages.missing-key\" values={{ keyLessMessages }}>\n              {`${keyLessMessages}`} messages cannot be decrypted with available secret keys.\n            </Trans>\n          </div>\n        ) */\n        }\n      </section>\n    );\n  }\n}\n\nexport default CheckDecryption;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/components/CheckDecryption/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/util/breakpoint';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../../styles/vendor/bootstrap_font-awesome';\n\n.m-check-decryption {\n  position: fixed;\n  bottom: 0;\n  z-index: 1;\n  width: 100%;\n}\n\n.m-check-decryption-panel {\n  &::before {\n    @include fa-icon;\n\n    content: $fa-var-lock;\n    vertical-align: middle;\n    float: left;\n    width: 2.5rem;\n    margin: 1rem - map_get($co-margin_, 'xsmall');\n    margin-right: 1rem;\n    padding: 0.375rem 0.125rem;\n    border-radius: 1.25rem;\n    background-color: $co-color__primary;\n    color: $co-color__contrast__text;\n    font-size: 1.75rem;\n    text-align: center;\n  }\n\n  padding: map_get($co-margin_, 'small');\n  border-top: map_get($co-margin_, 'xsmall') solid $co-color__primary;\n  background-color: $co-color__fg__back;\n  overflow: hidden;\n\n  @include breakpoint(medium) {\n    max-width: $co-site-width - map_get($co-page-container-paddings, 'medium') *\n      2;\n  }\n\n  @include breakpoint(xlarge) {\n    max-width: $co-site-width - map_get($co-page-container-paddings, 'xlarge') *\n      2;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/components/LockedMessage/index.jsx",
    "content": "import React, { PureComponent, Fragment } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Icon } from '../../../../components';\n\nimport './style.scss';\n\nclass LockedMessage extends PureComponent {\n  static propTypes = {\n    encryptionStatus: PropTypes.shape({}),\n  };\n\n  static defaultProps = {\n    encryptionStatus: undefined,\n  };\n\n  renderStatusText = () => {\n    const { encryptionStatus } = this.props;\n\n    if (!encryptionStatus) {\n      return (\n        <Trans id=\"encryption.locked-message.status.no-detail\" message=\"…\" />\n      );\n    }\n\n    switch (encryptionStatus.status) {\n      case 'need_passphrase':\n        return (\n          <Trans\n            id=\"encryption.locked-message.status.need_passphrase\"\n            message=\"Enter your passphrase to unlock.\"\n          />\n        );\n      case 'decrypting':\n        return (\n          <Trans\n            id=\"encryption.locked-message.status.decrypting\"\n            message=\"Decryption in progress…\"\n          />\n        );\n      case 'need_privatekey':\n        return (\n          <Trans\n            id=\"encryption.locked-message.status.need_privatekey\"\n            message=\"No available private key can decrypt this message.\"\n          />\n        );\n      case 'error':\n        return (\n          <Fragment>\n            <Trans\n              id=\"encryption.locked-message.status.error\"\n              message=\"Error while trying to decrypt.\"\n            />\n            {` (${encryptionStatus.error})`}\n          </Fragment>\n        );\n      default:\n        return (\n          <Trans id=\"encryption.locked-message.status.no-detail\" message=\"…\" />\n        );\n    }\n  };\n\n  render() {\n    return (\n      <div className=\"m-encryption-locked-message\">\n        <div className=\"m-encryption-locked-message__message\">\n          <Icon type=\"lock\" className=\"m-encryption-locked-message__icon\" />\n          <Trans\n            id=\"encryption.locked-message.primary-text\"\n            message=\"Le contenu de ce message est chiffré.\"\n          />\n          <br />\n          <span className=\"m-encryption-locked-message__status m-encryption-locked-message__status--error\">\n            {this.renderStatusText()}\n          </span>\n        </div>\n      </div>\n    );\n  }\n}\n\nexport default LockedMessage;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/components/LockedMessage/style.scss",
    "content": "@import '../../../../styles/common';\n\n.m-encryption-locked-message {\n  position: relative;\n  margin: $co-margin $co-margin $co-margin 6.5rem;\n\n  // vertical padding here is meant to avoid margin fusion\n  padding: 1px 0;\n  background: no-repeat top left / 95% url('../../assets/fake-text.svg');\n\n  &__message {\n    position: relative;\n    width: 70%;\n    margin: $co-margin auto;\n    padding: map_get($co-margin_, 'xsmall');\n    border-radius: 3rem;\n    background-color: transparentize($co-color__fg__back, 0.2);\n    overflow: hidden;\n  }\n\n  &__icon {\n    vertical-align: middle;\n    float: left;\n    width: 2.5rem;\n    margin: 1rem - map_get($co-margin_, 'xsmall');\n    margin-right: 1rem;\n    padding: 0.375rem 0.125rem;\n    border-radius: 1.25rem;\n    background-color: $co-color__primary;\n    color: $co-color__contrast__text;\n    font-size: 1.75rem;\n    text-align: center;\n  }\n\n  &__status {\n    &--error {\n      color: map_get($caliopen-palette, 'alert');\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/index.js",
    "content": "export * from './actions/decryptMessage';\nexport * from './actions/encryptMessage';\nexport * from './actions/getRecipientKeys';\nexport { default as CheckDecryption } from './components/CheckDecryption';\nexport { default as LockedMessage } from './components/LockedMessage';\nexport * from './selectors/message';\nexport * from './selectors/publicKey';\nexport * from './services/keyring/remoteKeys';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/selectors/message.ts",
    "content": "import { RootState } from 'src/store/reducer';\n\nexport function encryptionSelector(state: RootState) {\n  return state.encryption;\n}\n\nexport function messageEncryptionStatusesSelector(state: RootState) {\n  return encryptionSelector(state).messageEncryptionStatusById;\n}\n\nexport function messageEncryptionStatusSelector(\n  state: RootState,\n  messageId: string\n) {\n  return messageEncryptionStatusesSelector(state)[messageId];\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/selectors/publicKey.js",
    "content": "export const selectKeys = (state, contactId) => {\n  if (state.publicKey && state.publicKey[contactId]) {\n    return state.publicKey[contactId].keys;\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/encryption/services/keyring/remoteKeys.js",
    "content": "import { selectKeys } from '../../selectors/publicKey';\n\nconst intersect = (arr1, arr2) => arr1.some((value) => arr2.includes(value));\n\n// XXX: refactor as a redux selector: const keysSelector = (state, { contactIds }) => {};\nexport const getStoredKeys = (state, contactIds) => {\n  const missingKeysContactIds = [];\n  const cachedKeys = contactIds.reduce((acc, contactId) => {\n    const keys = selectKeys(state, contactId);\n\n    if (!(keys && keys.length > 0)) {\n      missingKeysContactIds.push(contactId);\n\n      return acc;\n    }\n\n    return [...acc, ...keys];\n  }, []);\n\n  return { keys: cachedKeys, missingKeysContactIds };\n};\n\nexport const filterKeysByAddress = (keys, addresses) =>\n  keys.filter(({ emails }) => intersect(emails, addresses));\n\nexport const checkEachAddressHasKey = (addresses, keys) =>\n  addresses.every((address) =>\n    keys.some(({ emails }) => emails.includes(address))\n  );\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/file/actions/deleteDraftAttachment.js",
    "content": "import {\n  requestMessage,\n  deleteAttachment,\n} from '../../../store/modules/message';\nimport { tryCatchAxiosPromise } from '../../../services/api-client';\n\nexport const deleteDraftAttachment =\n  ({ message, attachment }) =>\n  async (dispatch) => {\n    try {\n      await tryCatchAxiosPromise(\n        dispatch(deleteAttachment({ message, attachment }))\n      );\n\n      return tryCatchAxiosPromise(dispatch(requestMessage(message.message_id)));\n    } catch (err) {\n      return Promise.reject(err);\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/file/actions/uploadDraftAttachments.js",
    "content": "import {\n  uploadAttachment,\n  requestMessage,\n} from '../../../store/modules/message';\nimport { tryCatchAxiosPromise } from '../../../services/api-client';\nimport UploadFileAsFormField from '../services/uploadFileAsFormField';\n\nexport const uploadDraftAttachments =\n  ({ message, attachments }) =>\n  async (dispatch) => {\n    try {\n      await Promise.all(\n        attachments.map((file) => {\n          const attachment = new UploadFileAsFormField(file, 'attachment');\n\n          return tryCatchAxiosPromise(\n            dispatch(uploadAttachment({ message, attachment }))\n          );\n        })\n      );\n\n      return tryCatchAxiosPromise(dispatch(requestMessage(message.message_id)));\n    } catch (err) {\n      return Promise.reject(err);\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/file/index.js",
    "content": "export * from './services';\nexport * from './actions/deleteDraftAttachment';\nexport * from './actions/uploadDraftAttachments';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/file/services/index.js",
    "content": "export const readAsArrayBuffer = (file) =>\n  new Promise((resolve, reject) => {\n    const reader = new FileReader();\n\n    reader.onload = (e) => resolve(e.target.result);\n    reader.onerror = (e) =>\n      reject(new Error(`File ${file.name} is unreadable: ${e.target.result}`));\n\n    reader.readAsArrayBuffer(file);\n  });\n\nexport const readAsText = (file) =>\n  new Promise((resolve, reject) => {\n    const reader = new FileReader();\n\n    reader.onload = (e) => resolve(e.target.result);\n    reader.onerror = (e) =>\n      reject(new Error(`File ${file.name} is unreadable: ${e.target.result}`));\n\n    reader.readAsText(file);\n  });\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/file/services/uploadFileAsFormField.js",
    "content": "export default class UploadFileAsFormField {\n  constructor(file, fieldName) {\n    this.file = file;\n    this.fieldName = fieldName;\n  }\n\n  toFormData(formData) {\n    if (typeof FormData !== 'function') {\n      throw new Error('not a browser environment');\n    }\n\n    const fData = formData || new FormData();\n    fData.append(this.fieldName, this.file);\n\n    return fData;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/form/components/FormikPersist.tsx",
    "content": "import * as React from 'react';\nimport { FormikErrors, useFormikContext } from 'formik';\n\nconst computeKey = (name: string): string => `formik.form.${name}`;\n\nexport function clearPersisted(name: string) {\n  const key = computeKey(name);\n  sessionStorage.removeItem(key);\n}\n\ntype FormikPersistorProps<Values> = {\n  name: string;\n  values: Values;\n  errors: FormikErrors<any>;\n  setValues: (values: Values) => void;\n  setErrors: (errors: FormikErrors<Values>) => void;\n};\n\nfunction FormikPersistor<Values = Record<string, string>>({\n  name,\n  setValues,\n  setErrors,\n  values,\n  errors,\n}: FormikPersistorProps<Values>) {\n  const storageKey = computeKey(name);\n  React.useLayoutEffect(() => {\n    window.addEventListener('beforeunload', () => clearPersisted(name));\n  }, []);\n\n  React.useEffect(() => {\n    const data = sessionStorage.getItem(storageKey);\n\n    if (data) {\n      const { values: storedValue, errors: storedError } = JSON.parse(data);\n      setValues(storedValue);\n      setErrors(storedError);\n    }\n\n    return () => {\n      window.removeEventListener('beforeunload', () => clearPersisted(name));\n    };\n  }, []);\n\n  React.useEffect(() => {\n    sessionStorage.setItem(storageKey, JSON.stringify({ values, errors }));\n  }, [values, errors]);\n\n  return null;\n}\n\nfunction FormikPersist<Values = Record<string, string>>({\n  name,\n}: {\n  name: string;\n}): React.ReactElement<typeof FormikPersistor> {\n  const { values, errors, setValues, setErrors } = useFormikContext<Values>();\n  return (\n    <FormikPersistor\n      name={name}\n      setValues={setValues}\n      setErrors={setErrors}\n      values={values}\n      errors={errors}\n    />\n  );\n}\n\nexport default FormikPersist;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/form/services/validators.ts",
    "content": "import { withI18nProps } from '@lingui/react';\n\nexport const validateRequired =\n  (i18n: withI18nProps['i18n']) =>\n  (value: string): string | undefined => {\n    if (!value) {\n      return i18n._(/* i18n */ 'form-validation.required', undefined, {\n        message: 'Required',\n      });\n    }\n\n    return undefined;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/i18n/components/I18nLoader.tsx",
    "content": "import * as React from 'react';\nimport { I18nProvider } from '@lingui/react';\nimport { i18n } from '@lingui/core';\nimport { getBestLocale } from '../services/getBestLocale';\nimport { getLanguage } from '../services/getLanguage';\n\ntype Locale = 'en' | 'fr' | 'de' | 'es';\n\nasync function getCatalog(language: Locale) {\n  const { messages } = await import(\n    /* webpackMode: \"lazy\", webpackChunkName: \"i18n-[index]\" */ `locale/${language}/messages.ts`\n  );\n\n  return messages;\n}\n\nconst getBestLanguage = (locale: string) =>\n  getLanguage(getBestLocale([locale]));\n\ninterface Props {\n  locale: string;\n  children: React.ReactNode;\n}\nfunction I18nLoader({\n  locale,\n  children,\n}: Props): React.ReactElement<typeof I18nProvider> {\n  React.useEffect(() => {\n    (async () => {\n      const language = getBestLanguage(locale);\n      i18n.load(language, await getCatalog(language));\n      i18n.activate(language);\n    })();\n  }, [locale]);\n\n  return <I18nProvider i18n={i18n}>{children}</I18nProvider>;\n}\n\nexport default I18nLoader;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/i18n/index.js",
    "content": "export { default as I18nLoader } from './components/I18nLoader';\nexport * from './services/getBestLocale';\nexport * from './services/getUserLocales';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/i18n/services/getBestLocale.js",
    "content": "import { getLanguage } from './getLanguage';\n\nexport const DEFAULT_LOCALE = 'en_US';\nexport const AVAILABLE_LOCALES = [DEFAULT_LOCALE, 'fr_FR', 'de_DE'];\nexport const getBestLocale = (locales = []) => {\n  const bestLocale = locales.reduce((acc, locale) => {\n    if (acc) {\n      return acc;\n    }\n    if (AVAILABLE_LOCALES.includes(locale)) {\n      return locale;\n    }\n\n    const currentLocale = AVAILABLE_LOCALES.find((availableLang) =>\n      locale.startsWith(getLanguage(availableLang))\n    );\n\n    if (currentLocale) {\n      return currentLocale;\n    }\n\n    return acc;\n  }, undefined);\n\n  return bestLocale || DEFAULT_LOCALE;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/i18n/services/getLanguage.js",
    "content": "export const getLanguage = (locale) => locale.slice(0, 2);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/i18n/services/getUserLocales.js",
    "content": "import { DEFAULT_LOCALE } from './getBestLocale';\n\nexport const getUserLocales = () => {\n  if (BUILD_TARGET === 'browser') {\n    const languages = window.navigator.languages || [];\n\n    return [\n      ...languages,\n      window.navigator.language ||\n        window.navigator.userLanguage ||\n        DEFAULT_LOCALE,\n    ];\n  }\n\n  if (BUILD_TARGET === 'server' && global.USER_LOCALES) {\n    return global.USER_LOCALES;\n  }\n\n  return [];\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/actions/getIdentities.js",
    "content": "import { getLocalIdentities } from './getLocalIdentities';\nimport { getRemoteIdentities } from './getRemoteIdentities';\nimport { identitiesSelector } from '../selectors/identitiesSelector';\n\nexport const getIdentities = () => async (dispatch, getState) => {\n  await Promise.all([\n    dispatch(getLocalIdentities()),\n    dispatch(getRemoteIdentities()),\n  ]);\n\n  return identitiesSelector(getState());\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/actions/getLocalIdentities.js",
    "content": "import { requestLocalIdentities } from '../../../store/modules/local-identity';\n\nexport const getLocalIdentities = () => async (dispatch, getState) => {\n  const { localIdentities, didInvalidate } = getState().localIdentity;\n\n  if (localIdentities.length > 0 && !didInvalidate) {\n    return localIdentities;\n  }\n\n  await dispatch(requestLocalIdentities());\n\n  return getState().localIdentity.localIdentities;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/actions/getRemoteIdentities.js",
    "content": "import { requestRemoteIdentities } from '../../../store/modules/remote-identity';\n\nexport const getRemoteIdentities = () => async (dispatch, getState) => {\n  let remoteIdentityState = getState().remoteIdentity;\n  const { remoteIdentities, didInvalidate } = remoteIdentityState;\n\n  if (remoteIdentities.length > 0 && !didInvalidate) {\n    return remoteIdentities.map(\n      (identityId) => remoteIdentityState.remoteIdentitiesById[identityId]\n    );\n  }\n\n  await dispatch(requestRemoteIdentities());\n\n  remoteIdentityState = getState().remoteIdentity;\n\n  return remoteIdentities.map(\n    (identityId) => remoteIdentityState.remoteIdentitiesById[identityId]\n  );\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/components/WithIdentities/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport Presenter from './presenter';\nimport { getIdentities } from '../../actions/getIdentities';\nimport { identitiesSelector } from '../../selectors/identitiesSelector';\n\nconst remoteIdentitystateSelector = (state) => state.remoteIdentity;\nconst localIdentityStateSelector = (state) => state.localIdentity;\n\nconst mapStateToProps = createSelector(\n  [remoteIdentitystateSelector, localIdentityStateSelector, identitiesSelector],\n  ({ remoteIsFetching }, { localIsFetching }, identities) => ({\n    identities,\n    remoteIsFetching,\n    localIsFetching,\n  })\n);\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      getIdentities,\n    },\n    dispatch\n  );\n\nexport default compose(connect(mapStateToProps, mapDispatchToProps))(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/components/WithIdentities/presenter.jsx",
    "content": "import { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nclass WithIdentities extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n    identities: PropTypes.arrayOf(PropTypes.shape({})),\n    remoteIsFetching: PropTypes.bool,\n    getIdentities: PropTypes.func.isRequired,\n    localIsFetching: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    identities: undefined,\n    remoteIsFetching: false,\n    localIsFetching: false,\n  };\n\n  componentDidMount() {\n    const { remoteIsFetching, getIdentities, localIsFetching } = this.props;\n\n    if (!remoteIsFetching && !localIsFetching) {\n      getIdentities();\n    }\n  }\n\n  render() {\n    console.warn('DEPRECATED: useIdentities instead');\n\n    const { render, identities, remoteIsFetching, localIsFetching } =\n      this.props;\n\n    const isFetching = remoteIsFetching || localIsFetching;\n\n    return render({ identities, isFetching });\n  }\n}\n\nexport default WithIdentities;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/hoc/withIdentities.jsx",
    "content": "import React from 'react';\nimport WithIdentities from '../components/WithIdentities';\n\nexport const withIdentities =\n  ({ namespace } = {}) =>\n  (WrappedComp) => {\n    function C(props) {\n      return (\n        <WithIdentities\n          render={({ identities, isFetching }) => {\n            const localProps = namespace\n              ? { [namespace]: { identities, isFetching } }\n              : { identities, isFetching };\n\n            return <WrappedComp {...localProps} {...props} />;\n          }}\n        />\n      );\n    }\n    C.displayName = `C(${\n      WrappedComp.displayName || WrappedComp.name || 'Component'\n    })`;\n\n    return C;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/hooks/useIdentities.ts",
    "content": "import * as React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Identity } from 'src/modules/remoteIdentity';\nimport { getIdentities } from '../actions/getIdentities';\nimport { identitiesSelector } from '../selectors/identitiesSelector';\n\nconst remoteIdentitystateSelector = (state) => state.remoteIdentity;\nconst localIdentityStateSelector = (state) => state.localIdentity;\n\nexport function useIdentities(): {\n  identities: Array<Identity>;\n  isFetching: boolean;\n} {\n  const dispatch = useDispatch();\n  const { remoteIsFetching } = useSelector(remoteIdentitystateSelector);\n  const { localIsFetching } = useSelector(localIdentityStateSelector);\n  const identities = useSelector(identitiesSelector);\n  const isFetching = remoteIsFetching || localIsFetching;\n\n  // TODO: invalidated\n  React.useEffect(() => {\n    if (!isFetching) {\n      dispatch(getIdentities());\n    }\n  }, []);\n\n  return {\n    identities,\n    isFetching,\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/index.js",
    "content": "export * from './actions/getIdentities';\nexport * from './actions/getLocalIdentities';\nexport * from './actions/getRemoteIdentities';\nexport { default as WithIdentities } from './components/WithIdentities';\nexport * from './hoc/withIdentities';\nexport * from './selectors/identitiesSelector';\nexport * from './services/identityToParticipant';\nexport * from './hooks/useIdentities';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/selectors/identitiesSelector.js",
    "content": "import { createSelector } from 'reselect';\nimport { getModuleStateSelector } from '../../../store/selectors/getModuleStateSelector';\n\nexport const identitiesSelector = createSelector(\n  [\n    getModuleStateSelector('remoteIdentity'),\n    getModuleStateSelector('localIdentity'),\n  ],\n  ({ remoteIdentities, remoteIdentitiesById }, { localIdentities }) => [\n    ...remoteIdentities.map((identityId) => remoteIdentitiesById[identityId]),\n    ...localIdentities,\n  ]\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/services/identityToParticipant.js",
    "content": "// prevent circular reference between identity and draftMessage modules\nimport { getIdentityProtocol } from '../../draftMessage/services/getIdentityProtocol';\nimport { Participant } from '../../message';\n\nexport const identityToParticipant = ({ identity, user, type = 'From' }) => {\n  const { identifier, display_name: label } = identity;\n  const {\n    contact: { contact_id: contactId },\n  } = user;\n\n  return new Participant({\n    address: identifier,\n    protocol: getIdentityProtocol(identity),\n    label,\n    type,\n    contact_ids: [contactId],\n  });\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/identity/types.d.ts",
    "content": "interface Infos {\n  authType?: string;\n  avatar?: string;\n  errorsCount?: string;\n  firstErrorDate?: string;\n  lastErrorDate?: string;\n  lastFetchError?: string;\n  lastseendm?: string;\n  lastsync?: string;\n  mastodon_id?: string;\n  pollinterval?: string;\n  // mastodon|…\n  provider?: string;\n  url?: string;\n}\n\nexport interface IIdentity {\n  credentials?: Record<string, unknown>;\n  display_name: string;\n  identity_id: string;\n  /** address or username */\n  identifier: string;\n  infos: Infos;\n  last_check: string;\n  // mastodon|…\n  protocol: string;\n  // active|…\n  status: string;\n  type: 'remote' | 'local';\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/createMessage.js",
    "content": "import {\n  createMessage as createMessageBase,\n  requestMessage,\n  addToCollection,\n} from '../../../store/modules/message';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\n\nexport const createMessage =\n  ({ message }) =>\n  async (dispatch) => {\n    await dispatch(createMessageBase({ message }));\n\n    const messageUpToDate = await tryCatchAxiosAction(() =>\n      dispatch(requestMessage(message.message_id))\n    );\n\n    await dispatch(addToCollection({ message: messageUpToDate }));\n\n    return messageUpToDate;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/deleteMessage.js",
    "content": "import {\n  deleteMessage as deleteMessageBase,\n  removeFromCollection,\n  invalidateAll,\n} from '../../../store/modules/message';\nimport {\n  requestDiscussion,\n  removeDiscussionFromCollection,\n  invalidate,\n} from '../../../store/modules/discussion';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\n\nexport const deleteMessage =\n  ({ message }) =>\n  async (dispatch) => {\n    try {\n      const result = await dispatch(deleteMessageBase({ message }));\n      // this must be placed after the real deletion in order to prevent re-render of connected\n      // components and children\n      dispatch(removeFromCollection({ message }));\n\n      if (message.discussion_id) {\n        try {\n          const discussion = await tryCatchAxiosAction(() =>\n            dispatch(requestDiscussion({ discussionId: message.discussion_id }))\n          );\n\n          // 1/2 Discussion is not removed when we get 404\n          dispatch(\n            removeDiscussionFromCollection({\n              discussionId: discussion.discussion_id,\n            })\n          );\n        } catch (apiErrors) {\n          if (Array.isArray(apiErrors) && apiErrors[0].code === 404) {\n            // 2/2 But it should since it does not exists (anymore)\n            dispatch(\n              removeDiscussionFromCollection({\n                discussionId: message.discussion_id,\n              })\n            );\n          } else if (!Array.isArray(apiErrors) || apiErrors[0].code !== 404) {\n            throw apiErrors;\n          }\n        }\n      }\n\n      return result;\n    } catch (err) {\n      await dispatch(invalidateAll());\n      // XXX: force invalidate discussions but it should not be necessary\n      dispatch(invalidate());\n\n      return Promise.reject(err);\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/fetchMessages.js",
    "content": "import { fetchMessages as fetchMessagesBase } from '../../../store/modules/message';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\n\n// TODO: refactor in requestMessages cf. store/modules/message\nexport const fetchMessages =\n  (...params) =>\n  async (dispatch) => {\n    const { messages } = await tryCatchAxiosAction(() =>\n      dispatch(fetchMessagesBase(...params))\n    );\n\n    return messages;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/getDraft.spec.ts",
    "content": "import configureMockStore from 'redux-mock-store';\nimport { getDefaultMiddleware } from '@reduxjs/toolkit';\nimport { getDraft } from './getDraft';\n\nconst mockStore = configureMockStore(getDefaultMiddleware());\n\ndescribe('message module - actions - getDraft', () => {\n  it('getDraft from store', async () => {\n    const now = new Date();\n    const store = mockStore({\n      message: {\n        timelineFilter: '',\n        messagesCollections: {\n          discussion: {\n            '02': {\n              messages: ['a', 'b', 'c'],\n            },\n          },\n        },\n        messagesById: {\n          a: {\n            message_id: 'a',\n            discussion_id: '02',\n            date_sort: new Date(now.getTime() - 2 * 60 * 60 * 1000),\n            is_draft: true,\n          },\n          b: {\n            message_id: 'b',\n            discussion_id: '02',\n            date_sort: now,\n            is_draft: true,\n          },\n          c: {\n            message_id: 'c',\n            discussion_id: '02',\n            date_sort: now,\n            is_draft: true,\n          },\n        },\n      },\n    });\n\n    const expectedActions = [];\n    const action = getDraft({ discussionId: '02' });\n\n    const result = await store.dispatch(action);\n    expect(result).toEqual(store.getState().message.messagesById.b);\n    expect(store.getActions()).toEqual(expectedActions);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/getDraft.ts",
    "content": "import { handleClientResponseSuccess } from '../../../services/api-client';\nimport { requestDraft } from '../../../store/modules/message';\n// prevent circular reference to something in unit tests\nimport { discussionDraftSelector } from '../../discussion/selectors/discussionDraftSelector';\nimport { Message } from '../models/Message';\n\nexport const getDraft =\n  ({ discussionId }: { discussionId: string }) =>\n  async (dispatch, getState): Promise<void | Message> => {\n    let draft = discussionDraftSelector(getState(), { discussionId });\n\n    if (draft) {\n      return draft;\n    }\n\n    try {\n      const data = handleClientResponseSuccess(\n        await dispatch(requestDraft({ discussionId }))\n      );\n      [draft] = data.messages;\n\n      return draft;\n    } catch (error) {\n      if (error.error.response.status === 404) {\n        return undefined;\n      }\n\n      throw error;\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/getLastMessage.js",
    "content": "import { discussionIdSelector } from '../../discussion';\nimport { fetchMessages } from './fetchMessages';\nimport { createMessageCollectionStateSelector } from '../../../store/selectors/message';\nimport { sortMessages } from '../services/sortMessages';\n\nconst messageCollectionSelector = createMessageCollectionStateSelector(\n  () => 'discussion',\n  discussionIdSelector\n);\n\nexport const getLastMessage =\n  ({ discussionId }) =>\n  async (dispatch, getState) => {\n    const { messages } = messageCollectionSelector(getState(), {\n      discussionId,\n    });\n    messages.sort(\n      (a, b) => (new Date(a.date_sort) - new Date(b.date_sort)) * -1\n    );\n    let [lastMessage] = sortMessages(messages, true);\n\n    if (lastMessage) {\n      return lastMessage;\n    }\n\n    [lastMessage] = await dispatch(\n      fetchMessages({\n        discussion_id: discussionId,\n        is_draft: false,\n        limit: 1,\n      })\n    );\n\n    return lastMessage;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/getLastMessage.spec.js",
    "content": "import configureMockStore from 'redux-mock-store';\nimport { getDefaultMiddleware } from '@reduxjs/toolkit';\nimport { getLastMessage } from './getLastMessage';\n\nconst mockStore = configureMockStore(getDefaultMiddleware());\njest.mock('./fetchMessages', () => ({\n  fetchMessages: (params) => (dispatch) => {\n    dispatch({ type: 'FETCH_MESSAGES', payload: params });\n\n    const lastMessageDate = new Date(2018, 1, 1);\n\n    switch (params.discussion_id) {\n      case '01':\n        // the older is always the first\n        return Promise.resolve([\n          {\n            message_id: 'd',\n            discussion_id: '01',\n            date_sort: lastMessageDate,\n          },\n          {\n            message_id: 'c',\n            discussion_id: '01',\n            date_sort: new Date(lastMessageDate - 2 * 60 * 60 * 1000),\n          },\n        ]);\n      default:\n        return Promise.resolve([]);\n    }\n  },\n}));\n\ndescribe('message module - actions - getLastMessage', () => {\n  it('getLastMessage from store', async () => {\n    const now = new Date();\n    const store = mockStore({\n      message: {\n        messagesCollections: {\n          discussion: {\n            '02': {\n              messages: ['a', 'b'],\n            },\n          },\n        },\n        messagesById: {\n          a: {\n            message_id: 'a',\n            discussion_id: '02',\n            date_sort: new Date(now - 2 * 60 * 60 * 1000),\n          },\n          b: {\n            message_id: 'b',\n            discussion_id: '02',\n            date_sort: now,\n          },\n        },\n      },\n    });\n\n    const expectedActions = [];\n    const action = getLastMessage({ discussionId: '02' });\n\n    const result = await store.dispatch(action);\n    expect(result).toEqual(store.getState().message.messagesById.b);\n    expect(store.getActions()).toEqual(expectedActions);\n  });\n\n  it('getLastMessage from fetch', async () => {\n    const now = new Date();\n    const store = mockStore({\n      message: {\n        messagesCollections: {\n          discussion: {\n            '02': {\n              messages: ['a', 'b'],\n            },\n          },\n        },\n        messagesById: {\n          a: {\n            message_id: 'a',\n            discussion_id: '02',\n            date_sort: new Date(now - 2 * 60 * 60 * 1000),\n          },\n          b: {\n            message_id: 'b',\n            discussion_id: '02',\n            date_sort: now,\n          },\n        },\n      },\n    });\n\n    const expectedActions = [\n      {\n        type: 'FETCH_MESSAGES',\n        payload: { discussion_id: '01', is_draft: false, limit: 1 },\n      },\n    ];\n    const action = getLastMessage({ discussionId: '01' });\n\n    const result = await store.dispatch(action);\n    const lastMessageDate = new Date(2018, 1, 1);\n    expect(result).toEqual({\n      message_id: 'd',\n      discussion_id: '01',\n      date_sort: lastMessageDate,\n    });\n    expect(store.getActions()).toEqual(expectedActions);\n  });\n\n  it('getLastMessage has no lastMessage', async () => {\n    const now = new Date();\n    const store = mockStore({\n      message: {\n        messagesCollections: {\n          discussion: {\n            '02': {\n              messages: ['a', 'b'],\n            },\n          },\n        },\n        messagesById: {\n          a: {\n            message_id: 'a',\n            discussion_id: '02',\n            date_sort: new Date(now - 2 * 60 * 60 * 1000),\n          },\n          b: {\n            message_id: 'b',\n            discussion_id: '02',\n            date_sort: now,\n          },\n        },\n      },\n    });\n\n    const expectedActions = [\n      {\n        type: 'FETCH_MESSAGES',\n        payload: { discussion_id: '03', is_draft: false, limit: 1 },\n      },\n    ];\n    const action = getLastMessage({ discussionId: '03' });\n\n    const result = await store.dispatch(action);\n    expect(result).toEqual(undefined);\n    expect(store.getActions()).toEqual(expectedActions);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/getMessage.js",
    "content": "import { requestMessage } from '../../../store/modules/message';\nimport { messagesByIdSelector } from '../../../store/selectors/message';\nimport { handleAxiosPromise } from '../../../services/api-client';\n\nexport const getMessage =\n  ({ messageId }) =>\n  async (dispatch, getState) => {\n    const message = messagesByIdSelector(getState())[messageId];\n\n    if (message) {\n      return message;\n    }\n\n    await handleAxiosPromise(dispatch(requestMessage(messageId)));\n\n    return messagesByIdSelector(getState())[messageId];\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/getMessages.js",
    "content": "import { getMessage } from './getMessage';\n\nexport const getMessages = (messageids) => (dispatch) =>\n  Promise.all(\n    messageids.map((messageId) => dispatch(getMessage({ messageId })))\n  );\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/getParentMessage.js",
    "content": "import { getMessage } from './getMessage';\n\nexport const getParentMessage =\n  ({ message }) =>\n  (dispatch) => {\n    if (!message.parent_id) {\n      return undefined;\n    }\n\n    return dispatch(getMessage({ messageId: message.parent_id }));\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/requestDiscussion.js",
    "content": "import { tryCatchAxiosAction } from '../../../services/api-client';\nimport { requestDiscussion as requestDiscussionBase } from '../../../store/modules/discussion';\nimport { requestMessages as requestMessagesBase } from '../../../store/modules/message';\nimport { discussionSelector } from '../../discussion';\n\nexport const requestDiscussion =\n  ({ discussionId }) =>\n  async (dispatch, getState) => {\n    // the discussion can be outdated but not the message's collection\n    const discussion = discussionSelector(getState(), { discussionId });\n\n    const results = await Promise.all([\n      discussion\n        ? { discussion }\n        : tryCatchAxiosAction(() =>\n            dispatch(requestDiscussionBase({ discussionId }))\n          ),\n      dispatch(\n        requestMessagesBase('discussion', discussionId, {\n          discussion_id: discussionId,\n        })\n      ),\n    ]);\n\n    return results[0];\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/requestMessages.js",
    "content": "import { requestMessages as requestMessagesBase } from '../../../store/modules/message';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\n\n// @deprecated: use fetchMessages instead or request collection directly\nexport const requestMessages =\n  (...params) =>\n  (dispatch) =>\n    tryCatchAxiosAction(() => dispatch(requestMessagesBase(...params)));\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/actions/setMessageRead.js",
    "content": "import { postActions, requestMessage } from '../../../store/modules/message';\n\nexport const setMessageRead =\n  ({ message, isRead = true }) =>\n  async (dispatch) => {\n    const action = isRead ? 'set_read' : 'set_unread';\n\n    await dispatch(postActions({ message, actions: [action] }));\n\n    return dispatch(requestMessage(message.message_id));\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/components/ParticipantLabel/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { compose } from 'redux';\nimport { connect } from 'react-redux';\nimport { getModuleStateSelector } from '../../../../store/selectors/getModuleStateSelector';\nimport Presenter from './presenter';\n\nconst participantSelector = (state, ownProps) => ownProps.participant;\nconst mapStateToProps = createSelector(\n  [participantSelector, getModuleStateSelector('contact')],\n  (participant, contactState) => ({\n    // a participant can be associated to only one participant\n    contact:\n      participant.contact_ids &&\n      contactState.contactsById[participant.contact_ids[0]],\n  })\n);\n\nexport default compose(connect(mapStateToProps))(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/components/ParticipantLabel/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\n\nfunction Layout({ className, ...props }) {\n  return <span className={classnames(className)} {...props} />;\n}\nLayout.propTypes = {\n  className: PropTypes.string,\n};\nLayout.defaultProps = {\n  className: undefined,\n};\n\nclass ParticipantLabel extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n    participant: PropTypes.shape({}).isRequired,\n    contact: PropTypes.shape({}),\n  };\n\n  static defaultProps = {\n    className: undefined,\n    contact: undefined,\n  };\n\n  renderParticipantLabel() {\n    const { participant } = this.props;\n\n    if (participant.label !== participant.address) {\n      return `${participant.label} (${participant.address})`;\n    }\n\n    return participant.label;\n  }\n\n  render() {\n    const { className, contact } = this.props;\n\n    if (contact && contact.given_name) {\n      return (\n        <Layout className={className} title={this.renderParticipantLabel()}>\n          {contact.given_name}\n        </Layout>\n      );\n    }\n\n    return (\n      <Layout className={className}>{this.renderParticipantLabel()}</Layout>\n    );\n  }\n}\n\nexport default ParticipantLabel;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/hooks/useMessage.ts",
    "content": "import * as React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport {\n  messageSelector,\n  shouldFetchSelector,\n} from '../selectors/messageSelector';\nimport { getMessage } from '../actions/getMessage';\n\nexport function useMessage(messageId: string) {\n  const dispatch = useDispatch();\n  const shouldFetch = useSelector((state) =>\n    shouldFetchSelector(state, messageId)\n  );\n  const message = useSelector((state) => messageSelector(state, { messageId }));\n\n  React.useEffect(() => {\n    if (shouldFetch) {\n      dispatch(dispatch(getMessage({ messageId })));\n    }\n  }, [shouldFetch]);\n\n  return {\n    message,\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/index.js",
    "content": "export * from './actions/createMessage';\nexport * from './actions/deleteMessage';\nexport * from './actions/fetchMessages';\nexport * from './actions/getDraft';\nexport * from './actions/getLastMessage';\nexport * from './actions/getMessage';\nexport * from './actions/getMessages';\nexport * from './actions/getParentMessage';\nexport * from './actions/requestDiscussion';\nexport * from './actions/requestMessages';\nexport * from './actions/setMessageRead';\nexport { default as ParticipantLabel } from './components/ParticipantLabel';\nexport * from './models/Message';\nexport * from './models/Participant';\nexport * from './selectors/messageSelector';\nexport * from './services/findUserParticipant';\nexport * from './services/getLastMessageFromArray';\nexport * from './services/isUserParticipant';\nexport * from './services/sortMessages';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/models/Message.ts",
    "content": "/* eslint-disable max-classes-per-file,camelcase */\nimport { IDraftMessagePayload } from 'src/modules/message/types';\nimport { PI, MessagePI, PrivacyFeature } from 'src/modules/pi/types';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Participant } from './Participant';\n\nexport class Message {\n  constructor(props = {}) {\n    Object.assign(this, props);\n  }\n\n  attachments?: Array<any>;\n\n  body?: string;\n\n  body_is_plain?: boolean;\n\n  date: string;\n\n  date_delete?: string;\n\n  date_insert: string;\n\n  date_sort?: string;\n\n  discussion_id: string;\n\n  external_references?: any;\n\n  excerpt?: string;\n\n  user_identities?: Array<string>;\n\n  importance_level?: number;\n\n  is_answered: boolean;\n\n  is_draft: boolean;\n\n  is_unread: boolean;\n\n  is_received?: boolean;\n\n  message_id: string = uuidv4();\n\n  parent_id?: string;\n\n  participants: Array<Participant> = [];\n\n  pi_message?: MessagePI;\n\n  privacy_features?: PrivacyFeature;\n\n  pi?: PI;\n\n  raw_msg_id: string;\n\n  subject?: string = '';\n\n  tags?: Array<string> = [];\n\n  protocol: string;\n\n  user_id: string;\n}\n\nexport class NewMessage implements IDraftMessagePayload {\n  constructor(props = {}) {\n    Object.assign(this, props);\n  }\n\n  message_id?: string = uuidv4();\n\n  subject?: string;\n\n  body?: string;\n\n  parent_id?: string;\n\n  user_identities: Array<string>;\n\n  tags?: Array<string>;\n\n  participants?: Array<Participant>;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/models/Participant.ts",
    "content": "/* eslint-disable camelcase */\n\nexport const PROTOCOL_EMAIL = 'email';\nexport const PROTOCOL_TWITTER = 'twitter';\nexport const PROTOCOL_MASTODON = 'mastodon';\n\nexport class Participant {\n  constructor(props = {}) {\n    Object.assign(this, props);\n  }\n\n  address: string;\n\n  protocol: string;\n\n  label?: string;\n\n  type: 'To' | 'Cc' | 'Bcc' | 'From' | 'Reply-To' | 'Sender' = 'To';\n\n  contact_ids?: Array<string> = [];\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/query.ts",
    "content": "import getClient from 'src/services/api-client';\nimport { FetchConfig, QueryKey } from 'src/types';\nimport { TagPayload } from '../tags/types';\nimport { Message } from './models/Message';\n\nconst client = getClient();\n\nexport const getQueryKeys = ({\n  messageId,\n  fetchParams,\n}: {\n  messageId?: string;\n  fetchParams?: any;\n} = {}): QueryKey =>\n  // @ts-ignore\n  [\n    'messages',\n    messageId,\n    fetchParams ? JSON.stringify(fetchParams) : undefined,\n  ].filter(Boolean);\n\nexport const getConfigUpdateTags = (messageId?: string): FetchConfig => ({\n  url: `/api/v2/messages/${messageId}/tags`,\n});\n\n// ---------------------------------\n\nexport function updateTags(message: Message, tags: TagPayload[]) {\n  const payload = {\n    tags: tags.map((tag) => tag.name),\n    current_state: { tags: message.tags },\n  };\n\n  return client.patch(getConfigUpdateTags(message.message_id).url, payload);\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/selectors/messageSelector.js",
    "content": "import { getModuleStateSelector } from '../../../store/selectors/getModuleStateSelector';\n\nexport const messageSelector = (state, { messageId }) =>\n  getModuleStateSelector('message')(state).messagesById[messageId];\n\n// FIXME: isfetching, isLoaded …\nexport const shouldFetchSelector = (state, messageId) => {\n  const message = messageSelector(state, { messageId });\n\n  return !message;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/services/findUserParticipant.js",
    "content": "import { isUserParticipant } from './isUserParticipant';\n\nexport const findUserParticipant = ({ user, participants }) =>\n  participants &&\n  participants.find((participant) => isUserParticipant({ participant, user }));\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/services/findUserParticipant.spec.js",
    "content": "import { findUserParticipant } from './findUserParticipant';\n\ndescribe('message module - services - findUserParticipant', () => {\n  const user = {\n    contact: {\n      contact_id: 'abc',\n    },\n  };\n\n  it('has no participants', async () => {\n    expect(findUserParticipant({ user })).toBeUndefined();\n  });\n\n  it('has not the participant', async () => {\n    const participants = [{ address: 'foo@bar.tld' }];\n    expect(findUserParticipant({ participants, user })).toBeUndefined();\n  });\n\n  it('has the participant', async () => {\n    const participants = [\n      { address: 'foo@bar.tld' },\n      { address: 'abc@bar.tld', contact_ids: ['abc'] },\n    ];\n    expect(findUserParticipant({ participants, user })).toEqual(\n      participants[1]\n    );\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/services/getLastMessageFromArray.js",
    "content": "import { sortMessages } from './sortMessages';\n\nexport const getLastMessageFromArray = (messages) =>\n  sortMessages(messages, true)[0];\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/services/isUserParticipant.js",
    "content": "export const isUserParticipant = ({ participant, user }) => {\n  const isUserContactId = (contactId) => contactId === user?.contact.contact_id;\n\n  return (\n    (participant.contact_ids &&\n      participant.contact_ids.some(isUserContactId)) ||\n    false\n  );\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/services/isUserParticipant.spec.js",
    "content": "import { isUserParticipant } from './isUserParticipant';\n\ndescribe('message module - services - isUserParticipant', () => {\n  const user = {\n    contact: {\n      contact_id: 'john',\n    },\n  };\n\n  it('unknown contact', () => {\n    const participant = {\n      type: 'From',\n    };\n    expect(isUserParticipant({ participant, user })).toEqual(false);\n  });\n  it('known contact', () => {\n    const participant = {\n      type: 'From',\n      contact_ids: ['whatever'],\n    };\n    expect(isUserParticipant({ participant, user })).toEqual(false);\n  });\n  it('is actually the user', () => {\n    const participant = {\n      type: 'From',\n      contact_ids: ['john'],\n    };\n    expect(isUserParticipant({ participant, user })).toEqual(true);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/services/sortMessages.js",
    "content": "// Array.prototype.sort() is destructive, hence we need a copy of messages array.\nexport const sortMessages = (messages, reversed = false) =>\n  [...messages].sort((a, b) => {\n    if (reversed) {\n      return new Date(b.date_sort) - new Date(a.date_sort);\n    }\n\n    return new Date(a.date_sort) - new Date(b.date_sort);\n  });\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/message/types.d.ts",
    "content": "import { Participant } from './models/Participant';\n/**\n * cf. NewMessageV2.ymaml\n */\nexport interface IDraftMessagePayload {\n  message_id?: string;\n  subject?: string;\n  body?: string;\n  parent_id?: string;\n  user_identities: Array<string>;\n  tags?: Array<string>;\n  participants?: Array<Participant>;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/notification/components/MessageNotificationHandler/index.js",
    "content": "import { compose, bindActionCreators } from 'redux';\nimport { connect } from 'react-redux';\nimport { invalidateAll } from '../../../../store/modules/message';\nimport { invalidate } from '../../../../store/modules/discussion';\nimport { messageNotificationsSelector } from '../../selectors/messageNotificationsSelector';\nimport Presenter from './presenter';\n\nconst mapStateToProps = (state) => ({\n  notifications: messageNotificationsSelector(state),\n  initialized: state.notification.initialized,\n});\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      invalidateCollections: invalidateAll,\n      invalidateDiscussions: invalidate,\n    },\n    dispatch\n  );\n\nexport default compose(connect(mapStateToProps, mapDispatchToProps))(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/notification/components/MessageNotificationHandler/presenter.jsx",
    "content": "import { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n } from '@lingui/react';\nimport isEqual from 'lodash/isEqual';\nimport { withSettings } from '../../../settings';\nimport { notify as browserNotify } from '../../../../services/browser-notification';\n\nconst getNbNewMessages = (notifications) =>\n  notifications.reduce((acc, notif) => acc + notif.body.size, 0);\n\n@withI18n()\n@withSettings()\nclass MessageNotificationHandler extends Component {\n  static propTypes = {\n    invalidateDiscussions: PropTypes.func.isRequired,\n    invalidateCollections: PropTypes.func.isRequired,\n    initialized: PropTypes.bool,\n    notifications: PropTypes.arrayOf(PropTypes.shape({})),\n    settings: PropTypes.shape({}),\n    i18n: PropTypes.shape({ _: PropTypes.func.isRequired }).isRequired,\n  };\n\n  static defaultProps = {\n    initialized: false,\n    notifications: [],\n    settings: undefined,\n  };\n\n  componentDidMount() {\n    this.handleNotifications(this.props);\n  }\n\n  componentDidUpdate(prevProps) {\n    if (!isEqual(prevProps.notifications, this.props.notifications)) {\n      this.handleNotifications(this.props);\n    }\n  }\n\n  handleNotifications = async ({ notifications, settings }) => {\n    if (!settings) {\n      return;\n    }\n\n    if (notifications.length === 0) {\n      return;\n    }\n\n    const { invalidateDiscussions, invalidateCollections, initialized } =\n      this.props;\n\n    if (initialized) {\n      invalidateDiscussions();\n      invalidateCollections();\n    }\n\n    if (settings.notification_enabled) {\n      const { i18n } = this.props;\n      const nbNewMessages = getNbNewMessages(notifications);\n\n      // this notify only when document is not visible\n      browserNotify({\n        message: i18n._(\n          /* i18n */ 'desktop.notification.new_messages',\n          [nbNewMessages],\n          {\n            message: 'You received {0} new messages',\n          }\n        ),\n      });\n    }\n  };\n\n  render() {\n    return null;\n  }\n}\n\nexport default MessageNotificationHandler;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/notification/components/NotificationProvider/index.js",
    "content": "import { bindActionCreators } from 'redux';\nimport { connect } from 'react-redux';\nimport {\n  updateAll,\n  setInitialized,\n} from '../../../../store/modules/notification';\nimport Presenter from './presenter';\n\nconst mapStateToProps = (state) => ({\n  notifications: state.notification.notifications,\n});\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      updateNotifications: updateAll,\n      setInitialized,\n    },\n    dispatch\n  );\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/notification/components/NotificationProvider/presenter.jsx",
    "content": "import React, { Component, Fragment } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n } from '@lingui/react';\nimport isEqual from 'lodash/isEqual';\nimport { withUser } from 'src/modules/user';\nimport MessageNotificationHandler from '../MessageNotificationHandler';\nimport { getConfig } from '../../../device/services/storage';\nimport { signout } from '../../../routing';\n\n@withI18n()\n@withUser()\nclass NotificationProvider extends Component {\n  static propTypes = {\n    children: PropTypes.node,\n    updateNotifications: PropTypes.func.isRequired,\n    setInitialized: PropTypes.func.isRequired,\n    notifications: PropTypes.arrayOf(PropTypes.shape({})),\n    userState: PropTypes.shape({ user: PropTypes.shape({}) }).isRequired,\n  };\n\n  static defaultProps = {\n    children: null,\n    notifications: [],\n  };\n\n  state = {\n    isWorking: false,\n    initialized: false,\n  };\n\n  componentDidMount() {\n    // eslint-disable-next-line import/extensions\n    import('../../services/notification.worker.js').then(\n      ({ default: Worker }) => {\n        this.setState(\n          {\n            initialized: true,\n          },\n          () => {\n            this.worker = new Worker();\n            if (this.props.user) {\n              this.startWorker();\n            }\n          }\n        );\n      }\n    );\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    if (prevProps.userState.user !== this.props.userState.user) {\n      this.toggleWorker(!!this.props.userState.user);\n    }\n\n    if (prevState.isWorking === true && this.state.isWorking === false) {\n      // try to restart after a while\n      setTimeout(() => {\n        this.startWorker();\n      }, 1 * 60 * 1000);\n    }\n  }\n\n  componentWillUnmount() {\n    this.stopWorker();\n  }\n\n  toggleWorker = (hasUser = false) => {\n    if (hasUser) {\n      this.startWorker();\n\n      return;\n    }\n\n    this.stopWorker();\n  };\n\n  startWorker = () => {\n    if (this.state.isWorking || !this.state.initialized) {\n      return;\n    }\n    this.setState({ isWorking: true }, () => {\n      this.worker.postMessage({ action: 'start', device: getConfig() });\n      this.worker.addEventListener('message', (ev) => {\n        this.handleWorkerStatus(ev.data);\n        this.handleWorkerResults(ev.data);\n      });\n    });\n  };\n\n  stopWorker = () => {\n    if (!this.state.isWorking || !this.state.initialized) {\n      return;\n    }\n    this.setState({ isWorking: false }, () => {\n      this.worker.postMessage({ action: 'stop' });\n    });\n  };\n\n  updateLastNotifId = (lastNotif) => {\n    this.worker.postMessage({\n      action: 'updateFilters',\n      filters: { from: lastNotif.notif_id },\n    });\n  };\n\n  handleWorkerStatus = (message) => {\n    const { status } = message;\n    if (!status) {\n      return;\n    }\n\n    switch (status) {\n      case 'terminated':\n        this.stopWorker();\n        break;\n      case 'auth_lost':\n        this.stopWorker();\n        signout({ withRedirect: true });\n        break;\n      default:\n        break;\n    }\n  };\n\n  handleWorkerResults = (message) => {\n    const { results } = message;\n    if (!results) {\n      return;\n    }\n    const { updateNotifications, notifications, setInitialized } = this.props;\n    const prevNotifIds = notifications.map((notif) => notif.notif_id).sort();\n    const notifIds = results.notifications\n      .map((notif) => notif.notif_id)\n      .sort();\n\n    if (!isEqual(prevNotifIds, notifIds)) {\n      const lastNotification = [...results.notifications].pop();\n      if (lastNotification) {\n        this.updateLastNotifId(lastNotification);\n      }\n      updateNotifications(results.notifications);\n    }\n    setInitialized();\n  };\n\n  render() {\n    const { children } = this.props;\n\n    return (\n      <Fragment>\n        {children}\n        <MessageNotificationHandler />\n      </Fragment>\n    );\n  }\n}\n\nexport default NotificationProvider;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/notification/index.js",
    "content": "export { default as NotificationProvider } from './components/NotificationProvider';\nexport { default as MessageNotificationHandler } from './components/MessageNotificationHandler';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/notification/selectors/messageNotificationsSelector.js",
    "content": "import { createSelector } from 'reselect';\n\nconst MESSAGE_NOTIFICATION_TYPE = 'new_message';\n\nexport const messageNotificationsSelector = createSelector(\n  [(state) => state.notification],\n  (notificationState) =>\n    notificationState.notifications.filter(\n      (notif) => notif.type === MESSAGE_NOTIFICATION_TYPE\n    )\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/notification/services/notification.worker.js",
    "content": "/* eslint-disable import/no-extraneous-dependencies, no-restricted-globals */\nimport '@babel/polyfill';\nimport { getUnsignedClient } from '../../../services/api-client';\nimport { getSignatureHeaders } from '../../device/services/signature';\n\nconst THROTTLE_DURATION = 40 * 1000;\n\nclass Poller {\n  client = getUnsignedClient();\n\n  intervalId = undefined;\n\n  notificationFilters = {};\n\n  installInterceptor = (device) => {\n    if (this.interceptor) return;\n\n    this.interceptor = this.client.interceptors.request.use(async (req) => {\n      const signatureHeaders = await getSignatureHeaders(req, device);\n\n      return {\n        ...req,\n        headers: {\n          ...req.headers,\n          ...signatureHeaders,\n        },\n      };\n    });\n  };\n\n  stop = () => {\n    if (this.intervalId) {\n      clearInterval(this.intervalId);\n    }\n  };\n\n  start = (device) => {\n    self.postMessage({ status: 'active' });\n    this.installInterceptor(device);\n    this.intervalId = setInterval(async () => {\n      try {\n        const params = Object.keys(this.notificationFilters)\n          .map((key) => `${key}=${this.notificationFilters[key]}`)\n          .join('&');\n        const queryString = params.length > 0 ? `?${params}` : '';\n        const { data: results } = await this.client.get(\n          `/api/v2/notifications${queryString}`\n        );\n        self.postMessage({ results });\n      } catch (err) {\n        if (err.response.status === 401) {\n          this.stop();\n          self.postMessage({ status: 'auth_lost' });\n\n          return;\n        }\n        if (err.response.status >= 400) {\n          this.stop();\n          self.postMessage({ status: 'terminated' });\n\n          return;\n        }\n        throw err;\n      }\n    }, THROTTLE_DURATION);\n  };\n\n  handleStart = (message) => {\n    if (message.action === 'start') {\n      this.start(message.device);\n    }\n  };\n\n  handleStop = (message) => {\n    if (message.action === 'stop') {\n      this.stop();\n    }\n  };\n\n  handleUpdateNotificationFilters = (message) => {\n    if (message.action === 'updateFilters') {\n      this.notificationFilters = message.filters;\n    }\n  };\n\n  handleEvent = (event) => {\n    this.handleStart(event.data);\n    this.handleStop(event.data);\n    this.handleUpdateNotificationFilters(event.data);\n  };\n}\n\nconst poller = new Poller();\n\nself.addEventListener('install', (event) => {\n  event.waitUntil(self.skipWaiting());\n});\n\nself.addEventListener('activate', (event) => {\n  event.waitUntil(self.clients.claim());\n});\n\nself.addEventListener('message', (event) => {\n  poller.handleEvent(event);\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/BackgroundImage/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\n\nimport './style.scss';\n\nclass BackgroundImage extends PureComponent {\n  static propTypes = {\n    children: PropTypes.node.isRequired,\n    context: PropTypes.string.isRequired,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  render() {\n    const { context, children, className } = this.props;\n\n    return (\n      <div\n        className={classnames(className, 'm-background-image', {\n          'm-background-image--secure': context === 'secure',\n          // 'm-background-image--public': context === 'public',\n        })}\n      >\n        {children}\n      </div>\n    );\n  }\n}\n\nexport default BackgroundImage;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/BackgroundImage/o-background-image.scss",
    "content": "$co-background-image__path: './assets/' !default;\n$co-background-image__files: (\n  secure: 'co_bg_secure.jpg',\n  // public: 'co_bg_public.jpg',\n);\n\n@mixin o-background-image {\n  @each $context, $file in $co-background-image__files {\n    &--#{$context} {\n      background-image: url('#{$co-background-image__path}#{$file}');\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/BackgroundImage/style.scss",
    "content": "@import './o-background-image';\n\n.m-background-image {\n  @include o-background-image;\n\n  min-height: 100vh;\n  background-attachment: fixed;\n  background-repeat: no-repeat;\n  background-position: top center;\n  background-size: cover;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/MultidimensionalPi/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/MultidimensionalPi/presenter.jsx",
    "content": "import React, { createRef } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport PiGraph from '../PiGraph';\nimport Ratings from '../Ratings';\nimport { Button, Dropdown, withDropdownControl } from '../../../../components';\n\nimport './style.scss';\n\nconst PI_MAX = 100; // max value for PI levels\nconst DropdownControl = withDropdownControl(Button);\n\nfunction MultidimensionalPi({ pi, displayAveragePi, className, mini }) {\n  const gridWidth = PI_MAX * 2;\n  const dropdownControlRef = createRef();\n\n  return (\n    <div className={classnames('m-multidimensional-pi', className)}>\n      {mini ? (\n        <div className=\"m-multidimensional-pi__mini-pi\">\n          <DropdownControl\n            ref={dropdownControlRef}\n            display=\"inline\"\n            className=\"m-multidimensional-pi__toggle-mini-pi\"\n          >\n            <Ratings pi={pi} piMax={PI_MAX} mini />\n          </DropdownControl>\n\n          <Dropdown\n            dropdownControlRef={dropdownControlRef}\n            className=\"m-multidimensional-pi__mini-graph\"\n            position=\"bottom\"\n            closeOnClick=\"all\"\n          >\n            <Ratings\n              pi={pi}\n              piMax={PI_MAX}\n              displayAveragePi={displayAveragePi}\n            />\n          </Dropdown>\n        </div>\n      ) : (\n        <div>\n          <PiGraph gridWidth={gridWidth} piMax={PI_MAX} pi={pi} />\n          <Ratings pi={pi} piMax={PI_MAX} displayAveragePi={displayAveragePi} />\n        </div>\n      )}\n    </div>\n  );\n}\nMultidimensionalPi.defaultProps = {\n  displayAveragePi: false,\n  className: null,\n  mini: false,\n};\nMultidimensionalPi.propTypes = {\n  pi: PropTypes.shape({}).isRequired,\n  mini: PropTypes.bool,\n  className: PropTypes.string,\n  displayAveragePi: PropTypes.bool,\n};\nexport default MultidimensionalPi;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/MultidimensionalPi/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../../styles/object/o-triangle';\n\n.m-multidimensional-pi {\n  &__mini-pi {\n    display: flex;\n    position: relative;\n    align-items: stretch;\n    height: 100%;\n  }\n\n  &__toggle-mini-pi {\n    flex: auto;\n    padding: 0;\n  }\n\n  &__mini-graph {\n    width: map_get($co-pi-width, 'ratings');\n    padding: $co-component__spacing;\n    background: $co-color__fg__back;\n    box-shadow: $co-shadow;\n\n    &::before {\n      @include o-triangle('top', 0.5rem, $co-color__fg__back);\n\n      left: 0.5rem;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/PiGraph/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { calcPolygonPoints, calcGridCoordinates } from '../../services/svg';\nimport './style.scss';\n\nfunction Grid({ axeLength }) {\n  const { axeCoordinates, outlinePoints } = calcGridCoordinates({ axeLength });\n\n  return (\n    <g className=\"m-pi-graph__grid\">\n      <polygon\n        className=\"m-pi-graph__outline\"\n        points={outlinePoints.join(' ')}\n      />\n      {axeCoordinates.map((p) => (\n        <line\n          key={p.axeName}\n          className=\"m-pi-graph__line\"\n          id={p.axeName}\n          x1={axeLength}\n          y1={axeLength}\n          x2={p.x}\n          y2={p.y}\n        />\n      ))}\n    </g>\n  );\n}\n\nGrid.propTypes = {\n  axeLength: PropTypes.number.isRequired,\n};\n\nfunction Polygon({ pi, axeLength }) {\n  const polygonPoints = calcPolygonPoints({ pi, axeLength });\n\n  return (\n    <polygon className=\"m-pi-graph__shape\" points={polygonPoints.join(' ')} />\n  );\n}\n\nPolygon.propTypes = {\n  pi: PropTypes.shape({}).isRequired,\n  axeLength: PropTypes.number.isRequired,\n};\n\nfunction PiGraph({ pi, gridWidth }) {\n  const viewBox = [0, 0, gridWidth, gridWidth * 0.85]; // 0.85 ratio to avoid margin under PiGraph\n  const axeLength = gridWidth / 2;\n\n  return (\n    <svg\n      id=\"pi\"\n      className=\"m-pi-graph\"\n      viewBox={viewBox}\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <Polygon pi={pi} axeLength={axeLength} />\n      <Grid axeLength={axeLength} />\n    </svg>\n  );\n}\n\nPiGraph.propTypes = {\n  pi: PropTypes.shape({}).isRequired,\n  gridWidth: PropTypes.number.isRequired,\n};\n\nexport default PiGraph;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/PiGraph/style.scss",
    "content": "@import '../../../../styles/common';\n\n$gridColor: $co-color__fg__back--lower;\n\n.m-pi-graph {\n  display: block;\n  max-width: map_get($co-pi-width, 'graph');\n  margin-right: auto;\n  margin-left: auto;\n  pointer-events: none;\n\n  &__line {\n    stroke: $gridColor;\n    stroke-width: 0.7;\n    fill: none;\n  }\n\n  &__outline {\n    fill: transparent;\n    stroke: $gridColor;\n    stroke-width: 0.7;\n  }\n\n  &__shape {\n    fill: $co-color__privacy;\n    stroke: none;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/PiScore/index.tsx",
    "content": "import { useLingui } from '@lingui/react';\nimport * as React from 'react';\nimport { PI } from 'src/modules/pi/types';\nimport { computeScoreLetter } from '../../services/pi';\nimport svgSrc from './PI-score.svg';\n\nconst colors = 'E0D699,C1EDFE,AFD9EF,9CC7DC,7198A7';\n\ninterface Props extends React.HTMLAttributes<HTMLEmbedElement> {\n  average: number;\n  title?: string;\n}\n\nexport default function PiScore({ title, average, ...props }: Props) {\n  const { i18n } = useLingui();\n\n  const score = computeScoreLetter(average);\n\n  return (\n    <embed\n      src={`${svgSrc}?score=${score}&colors=${colors}`}\n      title={\n        title ||\n        i18n._(\n          'pi-score.default-title',\n          { score },\n          { message: 'This is scored as {score}.' }\n        )\n      }\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/Ratings/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { getAveragePI, PI_PROPERTIES } from '../../services/pi';\nimport './style.scss';\n\nfunction Rating({ name, level, piMax, className, mini }) {\n  const pi = level <= piMax ? level : piMax;\n  const width = (pi / piMax) * 100;\n  const style = { width: `${width}%` };\n\n  return (\n    <div\n      className={classnames(\n        'm-pi-ratings__item',\n        { 'm-pi-ratings--mini__item': mini },\n        className\n      )}\n    >\n      <div\n        className={classnames('m-pi-ratings__item-name', {\n          'm-pi-ratings--mini__item-name': mini,\n        })}\n      >\n        {name}\n      </div>\n      <div\n        className={classnames('m-pi-ratings__item-level', {\n          'm-pi-ratings--mini__item-level': mini,\n        })}\n      >\n        <div\n          className={classnames('m-pi-ratings__item-level-bar', {\n            'm-pi-ratings--mini__item-level-bar': mini,\n          })}\n          style={style}\n        />\n      </div>\n      <div\n        className={classnames('m-pi-ratings__item-level-label', {\n          'm-pi-ratings--mini__item-level-label': mini,\n        })}\n      >\n        {level}\n      </div>\n    </div>\n  );\n}\n\nRating.propTypes = {\n  name: PropTypes.string.isRequired,\n  mini: PropTypes.bool,\n  className: PropTypes.string,\n  level: PropTypes.number.isRequired,\n  piMax: PropTypes.number.isRequired,\n};\n\nRating.defaultProps = {\n  className: '',\n  mini: false,\n};\n\nfunction Ratings({ pi, piMax, displayAveragePi, mini }) {\n  const ratingsClassName = classnames('m-pi-ratings', {\n    'm-pi-ratings--mini': mini,\n  });\n  const title = PI_PROPERTIES.map((name) => `${name}: ${pi[name]}`).join(',\\n');\n\n  return (\n    <div className={ratingsClassName} title={title}>\n      {displayAveragePi && (\n        <Rating\n          className=\"m-pi-ratings__item--average\"\n          name=\"Average PI\"\n          level={Math.round(getAveragePI(pi))}\n          piMax={piMax}\n          mini={mini}\n        />\n      )}\n      {PI_PROPERTIES.map((name) => (\n        <Rating\n          name={name}\n          level={pi[name] || 0}\n          key={name}\n          piMax={piMax}\n          mini={mini}\n        />\n      ))}\n    </div>\n  );\n}\n\nRatings.defaultProps = {\n  displayAveragePi: false,\n  mini: false,\n};\nRatings.propTypes = {\n  pi: PropTypes.shape({}).isRequired,\n  piMax: PropTypes.number.isRequired,\n  displayAveragePi: PropTypes.bool,\n  mini: PropTypes.bool,\n};\n\nexport default Ratings;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/components/Ratings/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n$mini-pi-bar__height: 6px;\n$mini-pi-bar__margin: 1px;\n$mini-pi__height: 3 * ($mini-pi-bar__height + ($mini-pi-bar__margin * 2));\n\n.m-pi-ratings {\n  max-width: map_get($co-pi-width, 'ratings');\n  margin-right: auto;\n  margin-left: auto;\n  font-size: $co-font__size--xsmall;\n  font-weight: 600;\n\n  &__item {\n    @include flex-grid-row;\n\n    align-items: center;\n    padding-top: $co-component__spacing;\n  }\n\n  &__item-name {\n    @include flex-grid-size(5);\n\n    padding-right: $co-component__spacing;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    text-align: right;\n    text-transform: uppercase;\n  }\n\n  &__item-level {\n    @include flex-grid-size(5);\n\n    background: $co-color__fg__back--lower;\n  }\n\n  &__item-level-label {\n    @include flex-grid-size(2);\n\n    text-align: center;\n  }\n\n  &__item-level-bar {\n    height: $co-component__height / 3;\n    transition: 0.5s width;\n    background: $co-color__privacy;\n    line-height: $co-component__height / 3;\n  }\n\n  &--mini {\n    width: $mini-pi__height;\n    max-width: $mini-pi__height;\n    padding: $co-component__spacing;\n    cursor: help;\n    box-sizing: content-box;\n\n    &__item {\n      display: block;\n      width: 100%;\n      max-width: 100%;\n      padding: $mini-pi-bar__margin 0;\n    }\n\n    &__item-level {\n      display: block;\n      width: 100%;\n      max-width: 100%;\n    }\n\n    &__item-level-bar {\n      height: $mini-pi-bar__height;\n    }\n\n    &__item-level-label,\n    &__item-name {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/index.jsx",
    "content": "export { default as MultidimensionalPi } from './components/MultidimensionalPi';\nexport { default as BackgroundImage } from './components/BackgroundImage';\nexport { default as PiScore } from './components/PiScore';\nexport * from './services/pi';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/services/pi/index.spec.ts",
    "content": "import {\n  computeScoreLetter,\n  getAngles,\n  getAveragePI,\n  getAveragePIMessage,\n  PI_PROPERTIES,\n} from '.';\n\ndescribe('MultidimensionalPi > services > pi', () => {\n  describe('getAngles', () => {\n    it('gives 360 / 3 or n props', () => {\n      expect(getAngles()).toEqual(360 / PI_PROPERTIES.length);\n    });\n  });\n  describe('getAveragePI', () => {\n    it('gives a basic average value', () => {\n      expect(\n        getAveragePI(\n          {\n            comportment: 5,\n            technic: 10,\n            context: 15,\n            version: 1,\n          },\n          PI_PROPERTIES\n        )\n      ).toEqual((5 + 10 + 15) / 3);\n    });\n  });\n\n  describe('getAveragePIMessage', () => {\n    it('gives a basic average PIMessage value ', () => {\n      expect(\n        getAveragePIMessage({\n          message: {\n            pi_message: {\n              content: 5,\n              transport: 10,\n              social: 15,\n              rab: 20,\n              version: 1,\n            },\n          },\n        })\n      ).toEqual((5 + 10 + 15) / 3);\n    });\n  });\n\n  describe('computeScoreLetter', () => {\n    it('calc', () => {\n      expect(computeScoreLetter(100)).toEqual('A');\n      expect(computeScoreLetter(90)).toEqual('A');\n      expect(computeScoreLetter(81)).toEqual('A');\n      expect(computeScoreLetter(80)).toEqual('B');\n      expect(computeScoreLetter(66)).toEqual('B');\n      expect(computeScoreLetter(61)).toEqual('B');\n      expect(computeScoreLetter(60)).toEqual('C');\n      expect(computeScoreLetter(50)).toEqual('C');\n      expect(computeScoreLetter(41)).toEqual('C');\n      expect(computeScoreLetter(40)).toEqual('D');\n      expect(computeScoreLetter(33)).toEqual('D');\n      expect(computeScoreLetter(30)).toEqual('D');\n      expect(computeScoreLetter(21)).toEqual('D');\n      expect(computeScoreLetter(20)).toEqual('E');\n      expect(computeScoreLetter(10)).toEqual('E');\n      expect(computeScoreLetter(0)).toEqual('E');\n    });\n\n    it('calc above min/max', () => {\n      expect(computeScoreLetter(150)).toEqual('A');\n      expect(computeScoreLetter(-100)).toEqual('E');\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/services/pi/index.ts",
    "content": "import { PI, MessagePI } from '../../types';\n\nexport const PI_PROPERTIES = ['comportment', 'technic', 'context'];\nexport const PI_MESSAGE_PROPERTIES = ['content', 'transport', 'social'];\nexport const PI_LEVEL_DISABLED = 'disabled-pi';\nexport const PI_LEVEL_UGLY = 'ugly';\nexport const PI_LEVEL_BAD = 'bad';\nexport const PI_LEVEL_GOOD = 'good';\nexport const PI_LEVEL_SUPER = 'super';\n\nexport const getPiClass = (piAggregate: number | any) => {\n  if (Number.isNaN(piAggregate)) return PI_LEVEL_DISABLED;\n\n  if (piAggregate < 25) return PI_LEVEL_UGLY;\n  if (piAggregate < 50) return PI_LEVEL_BAD;\n\n  return piAggregate < 75 ? PI_LEVEL_GOOD : PI_LEVEL_SUPER;\n};\n\nexport const getAngles = () => {\n  const piLength = PI_PROPERTIES.length;\n  if (piLength === 0) {\n    return 0;\n  }\n\n  return 360 / piLength;\n};\n\nexport const getAveragePI = (pi: PI | MessagePI | void, piProps: string[]) => {\n  if (!pi) return NaN;\n\n  return Math.round(\n    piProps.reduce((acc, name) => acc + pi[name] || 0, 0) / piProps.length\n  );\n};\n\nexport const getAveragePIMessage = ({ message }) =>\n  getAveragePI(message.pi_message, PI_MESSAGE_PROPERTIES);\n\nexport type ScoreLetter = 'A' | 'B' | 'C' | 'D' | 'E';\n\nexport const computeScoreLetter = (percent: number) => {\n  let key = Math.ceil(percent / 20);\n  if (key < 1) {\n    key = 1;\n  }\n  if (key > 5) {\n    key = 5;\n  }\n\n  // reverse: A eq 0; E eq 4;\n  const baseCharCode = 5 - key;\n\n  return String.fromCharCode(65 + baseCharCode) as ScoreLetter;\n};\n\nexport const computePiScoreLetter = (pi: PI) =>\n  computeScoreLetter(getAveragePI(pi, PI_PROPERTIES));\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/services/svg/index.js",
    "content": "import { PI_PROPERTIES, getAngles } from '../pi';\n\nexport function calcXpoint(level, tilt, axeLength) {\n  return Math.round(axeLength - level * Math.sin((tilt * Math.PI) / 180));\n}\n\nexport function calcYpoint(level, tilt, axeLength) {\n  return Math.round(axeLength - level * Math.cos((tilt * Math.PI) / 180));\n}\n\nexport const calcGridCoordinates = ({ axeLength }) => {\n  const angle = getAngles();\n  const axeCoordinates = [];\n  const outlinePoints = [];\n  let tilt = 0;\n\n  PI_PROPERTIES.forEach((axeName) => {\n    const pointX = calcXpoint(axeLength, tilt, axeLength);\n    const pointY = calcYpoint(axeLength, tilt, axeLength);\n    outlinePoints.push(pointX, pointY);\n    axeCoordinates.push({ axeName, x: pointX, y: pointY });\n    tilt -= angle;\n  });\n\n  return { axeCoordinates, outlinePoints };\n};\n\nexport const calcPolygonPoints = ({ pi, axeLength }) => {\n  const angle = getAngles();\n  const polygonPoints = [];\n  let tilt = 0;\n  PI_PROPERTIES.forEach((name) => {\n    const piLevel = pi[name] || 0;\n\n    polygonPoints.push(\n      calcXpoint(piLevel, tilt, axeLength),\n      calcYpoint(piLevel, tilt, axeLength)\n    );\n    tilt -= angle;\n  });\n\n  return polygonPoints;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/services/svg/index.spec.jsx",
    "content": "import {\n  calcGridCoordinates,\n  calcPolygonPoints,\n  calcXpoint,\n  calcYpoint,\n} from '.';\n\ndescribe('comp PiGraph > services > svg', () => {\n  describe('calcGridCoordinates', () => {\n    it('returns normal values', () => {\n      const result = calcGridCoordinates({\n        axeLength: 100,\n      });\n\n      const expected = {\n        outlinePoints: [\n          calcXpoint(100, 0, 100),\n          calcYpoint(100, 0, 100),\n          calcXpoint(100, -120, 100),\n          calcYpoint(100, -120, 100),\n          calcXpoint(100, -240, 100),\n          calcYpoint(100, -240, 100),\n        ],\n        axeCoordinates: [\n          {\n            axeName: 'comportment',\n            x: calcXpoint(100, 0, 100),\n            y: calcYpoint(100, 0, 100),\n          },\n          {\n            axeName: 'technic',\n            x: calcXpoint(100, -120, 100),\n            y: calcYpoint(100, -120, 100),\n          },\n          {\n            axeName: 'context',\n            x: calcXpoint(100, -240, 100),\n            y: calcYpoint(100, -240, 100),\n          },\n        ],\n      };\n\n      expect(result).toEqual(expected);\n    });\n  });\n\n  describe('calcPolygonPoints', () => {\n    it('returns normal values', () => {\n      const result = calcPolygonPoints({\n        pi: {\n          comportment: 80,\n          technic: 50,\n          context: 10,\n          version: 1,\n        },\n        axeLength: 100,\n      });\n\n      const expected = [\n        calcXpoint(80, 0, 100),\n        calcYpoint(80, 0, 100),\n        calcXpoint(50, -120, 100),\n        calcYpoint(50, -120, 100),\n        calcXpoint(10, -240, 100),\n        calcYpoint(10, -240, 100),\n      ];\n\n      expect(result).toEqual(expected);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pi/types.d.ts",
    "content": "export interface PI {\n  technic: number;\n  context: number;\n  comportment: number;\n  version: number;\n  // [additional: string]: any, // XXX: not sure how to type `additionalProperties`\n}\n\nexport interface MessagePI {\n  transport: number;\n  social: number;\n  content: number;\n}\n\ntype PrivacyFeatureKey =\n  | 'is_internal'\n  | 'is_spam'\n  | 'message_encrypted'\n  | 'message_encrypted_method'\n  | 'message_signed'\n  | 'nb_external_hops'\n  | 'spam_score'\n  | 'transport_signed';\n\ntype PrivacyFeatureValue = 'False' | 'True' | string;\n\nexport interface PrivacyFeature {\n  [key: PrivacyFeatureKey | string]: PrivacyFeatureValue;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/publicKey/actions/saveUserPublicKeyAction.js",
    "content": "import isEqual from 'lodash/isEqual';\nimport { addAddressesToContact, updateContact } from '../../contact';\nimport { PROTOCOL_EMAIL } from '../../remoteIdentity';\nimport { updatePublicKey } from './updatePublicKey';\n\nexport const saveUserPublicKeyAction =\n  (publicKeyArmored, { contact }) =>\n  async (dispatch, getState) => {\n    const openpgp = await import(/* webpackChunkName: \"openpgp\" */ 'openpgp');\n    const publicKey = await openpgp.key.readArmored(publicKeyArmored);\n    const keyEmails = publicKey.keys[0].users.map(({ userId }) => userId.email);\n    const contactEmails = contact.emails.map((email) => email.address);\n\n    const missingEmails = keyEmails\n      .filter((email) => !contactEmails.includes(email))\n      .map((email) => ({ email, protocol: PROTOCOL_EMAIL }));\n    const contactWithNewEmails = addAddressesToContact(contact, missingEmails);\n\n    if (!isEqual(contact, contactWithNewEmails)) {\n      // Await needed in case no email coincides in pubKey/contact.\n      await updateContact({\n        contact: contactWithNewEmails,\n        original: contact,\n      })(dispatch, getState);\n    }\n\n    return dispatch(updatePublicKey(contactWithNewEmails, publicKeyArmored));\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/publicKey/actions/updatePublicKey.js",
    "content": "import {\n  requestPublicKeys,\n  createPublicKey,\n  updatePublicKey as updatePublicKeyBase,\n} from '../../../store/modules/public-key';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\n\nconst readArmored = async (armoredKey) => {\n  const openpgp = await import(/* webpackChunkName: \"openpgp\" */ 'openpgp');\n\n  return openpgp.key.readArmored(armoredKey);\n};\n\nexport const updatePublicKey =\n  (contact, publicKeyArmored) => async (dispatch) => {\n    const publicKey = await readArmored(publicKeyArmored);\n    const keys = await tryCatchAxiosAction(() =>\n      dispatch(requestPublicKeys({ contactId: contact.contact_id }))\n    );\n    const existingKey = keys.pubkeys.find(\n      (key) =>\n        key.fingerprint === publicKey.keys[0].getFingerprint().toUpperCase()\n    );\n\n    if (existingKey) {\n      return dispatch(\n        updatePublicKeyBase({\n          contactId: contact.contact_id,\n          publicKey: {\n            ...existingKey,\n            key: publicKeyArmored,\n          },\n          original: existingKey,\n        })\n      );\n    }\n\n    const publicKeyObject = {\n      label: 'PGP', // Have a better default name idea ?\n      key: publicKeyArmored,\n    };\n\n    return dispatch(\n      createPublicKey({\n        contactId: contact.contact_id,\n        publicKey: publicKeyObject,\n      })\n    );\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/publicKey/index.js",
    "content": "export * from './actions/updatePublicKey';\nexport * from './actions/saveUserPublicKeyAction';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pwa/components/InstallPromptConsumer.tsx",
    "content": "import * as React from 'react';\nimport { InstallPromptContext } from '../contexts/InstallPromptContext';\n\ninterface Props {\n  render: (value) => React.ReactNode;\n}\nfunction InstallPromptConsumer({ render }: Props) {\n  return (\n    <InstallPromptContext.Consumer>\n      {(value) => render(value)}\n    </InstallPromptContext.Consumer>\n  );\n}\n\nexport default InstallPromptConsumer;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pwa/components/InstallPromptProvider.tsx",
    "content": "import * as React from 'react';\nimport { InstallPromptContext } from '../contexts/InstallPromptContext';\n\nfunction InstallPromptProvider(props: { children: React.ReactNode }) {\n  const [value, setValue] = React.useState<{\n    defferedPrompt: undefined | Event;\n  }>({ defferedPrompt: undefined });\n\n  React.useEffect(() => {\n    window.addEventListener('beforeinstallprompt', (ev) => {\n      ev.preventDefault();\n      setValue({ defferedPrompt: ev });\n    });\n  }, []);\n\n  return <InstallPromptContext.Provider value={value} {...props} />;\n}\n\nexport default InstallPromptProvider;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pwa/contexts/InstallPromptContext.ts",
    "content": "import { createContext } from 'react';\n\nexport const InstallPromptContext = createContext<{\n  defferedPrompt: undefined | Event;\n}>({\n  defferedPrompt: undefined,\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/pwa/index.js",
    "content": "export { default as InstallPromptProvider } from './components/InstallPromptProvider';\nexport { default as InstallPromptConsumer } from './components/InstallPromptConsumer';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/actions/createIdentity.js",
    "content": "import {\n  createRemoteIdentity as createRemoteIdentityBase,\n  requestRemoteIdentity,\n  addToCollection,\n} from '../../../store/modules/remote-identity';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\n\nexport const createIdentity =\n  ({ identity }) =>\n  async (dispatch) => {\n    // FIXME: it should be identity_id\n    const { remote_id: identityId } = await tryCatchAxiosAction(() =>\n      dispatch(createRemoteIdentityBase({ remoteIdentity: identity }))\n    );\n\n    const remoteIdentityUpToDate = await tryCatchAxiosAction(() =>\n      dispatch(requestRemoteIdentity({ identityId }))\n    );\n\n    await dispatch(addToCollection({ remoteIdentity: remoteIdentityUpToDate }));\n\n    return remoteIdentityUpToDate;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/actions/deleteIdentity.js",
    "content": "import {\n  deleteRemoteIdentity as deleteRemoteIdentityBase,\n  removeFromCollection,\n} from '../../../store/modules/remote-identity';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\n\nexport const deleteIdentity =\n  ({ identity }) =>\n  async (dispatch) => {\n    await tryCatchAxiosAction(() =>\n      dispatch(deleteRemoteIdentityBase({ remoteIdentity: identity }))\n    );\n\n    return dispatch(removeFromCollection({ remoteIdentity: identity }));\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/actions/updateIdentity.js",
    "content": "import {\n  updateRemoteIdentity as updateRemoteIdentityBase,\n  requestRemoteIdentity,\n} from '../../../store/modules/remote-identity';\nimport { tryCatchAxiosAction } from '../../../services/api-client';\nimport { identitySelector } from '../selectors/identitySelector';\n\nexport const updateIdentity =\n  ({ identity }) =>\n  async (dispatch, getState) => {\n    const { identity_id: identityId } = identity;\n    const original = identitySelector(getState(), { identityId });\n\n    try {\n      await tryCatchAxiosAction(() =>\n        dispatch(\n          updateRemoteIdentityBase({\n            remoteIdentity: identity,\n            original,\n          })\n        )\n      );\n\n      const remoteIdentityUpToDate = await tryCatchAxiosAction(() =>\n        dispatch(requestRemoteIdentity({ identityId }))\n      );\n\n      return remoteIdentityUpToDate;\n    } catch (err) {\n      return Promise.reject(err);\n    }\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/components/ProviderIcon/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { withI18n } from '@lingui/react';\nimport providerSrc from './assets/providers.svg';\nimport { capitalize } from '../../../../services/capitalize';\nimport {\n  PROVIDER_GMAIL,\n  PROVIDER_MASTODON,\n  PROVIDER_TWITTER,\n} from '../../model/Identity';\nimport './style.scss';\n\nconst PROVIDER_SIZE_NORMAL = 'normal';\n\n@withI18n()\nclass ProviderIcon extends PureComponent {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    className: PropTypes.string,\n    providerName: PropTypes.oneOf([\n      PROVIDER_GMAIL,\n      PROVIDER_TWITTER,\n      PROVIDER_MASTODON,\n    ]).isRequired,\n    // XXX: refactor me\n    size: PropTypes.oneOf([PROVIDER_SIZE_NORMAL]),\n  };\n\n  static defaultProps = {\n    className: undefined,\n    size: undefined,\n  };\n\n  render() {\n    const { className, providerName, i18n, size } = this.props;\n    const imageClassName = classnames(className, {\n      'm-provider-icon--normal': size === PROVIDER_SIZE_NORMAL,\n    });\n\n    return (\n      <img\n        className={imageClassName}\n        src={`${providerSrc}#${providerName}`}\n        alt={i18n._(\n          /* i18n */ 'remote_identity.provider-logo',\n          { name: capitalize(providerName) },\n          { message: '{name} logo' }\n        )}\n      />\n    );\n  }\n}\n\nexport default ProviderIcon;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/components/ProviderIcon/style.scss",
    "content": ".m-provider-icon {\n  &--normal {\n    height: 1rem;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/components/WithProviders/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators } from 'redux';\nimport { connect } from 'react-redux';\nimport { requestProviders } from '../../../../store/modules/provider';\nimport { providerStateSelector } from '../../../../store/selectors/provider';\nimport Presenter from './presenter';\n\nconst mapStateToProps = createSelector(\n  [providerStateSelector],\n  ({ isFetching, didInvalidate, providers }) => ({\n    isFetching,\n    didInvalidate,\n    providers /* FIXME sort */,\n  })\n);\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      requestProviders,\n    },\n    dispatch\n  );\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/components/WithProviders/presenter.jsx",
    "content": "import { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nclass WithProviders extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n    requestProviders: PropTypes.func.isRequired,\n    providers: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n    isFetching: PropTypes.bool,\n    didInvalidate: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    isFetching: false,\n    didInvalidate: false,\n  };\n\n  componentDidMount() {\n    const { providers, requestProviders, didInvalidate, isFetching } =\n      this.props;\n\n    if ((!providers || didInvalidate) && !isFetching) {\n      requestProviders();\n    }\n  }\n\n  UNSAFE_componentWillReceiveProps(nextProps) {\n    const { requestProviders, didInvalidate, isFetching } = nextProps;\n\n    if (didInvalidate && !isFetching) {\n      requestProviders();\n    }\n  }\n\n  render() {\n    const { render, providers, isFetching } = this.props;\n\n    return render({ providers, isFetching });\n  }\n}\n\nexport default WithProviders;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/hoc/withAuthorize.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withAuthorizePopup } from './withAuthorizePopup';\nimport { getProvider } from '../services/getProvider';\n\nexport const withAuthorize = () => (C) => {\n  @withAuthorizePopup()\n  class Wrapper extends Component {\n    static propTypes = {\n      initPopup: PropTypes.func.isRequired,\n      authorizePopup: PropTypes.func.isRequired,\n    };\n\n    authorize = async ({ providerName, identifier }) => {\n      const { initPopup, authorizePopup } = this.props;\n\n      const provider = await getProvider({ providerName, identifier });\n      initPopup({ providerName });\n\n      return authorizePopup({ provider });\n    };\n\n    render() {\n      const { initPopup, authorizePopup, ...props } = this.props;\n\n      return <C authorize={this.authorize} {...props} />;\n    }\n  }\n\n  return Wrapper;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/hoc/withAuthorizePopup.jsx",
    "content": "/* eslint-disable no-param-reassign */\nimport React from 'react';\n\nconst popups = {};\nconst MAX_TIMEOUT = 3600;\n\nclass Popup {\n  constructor(props = {}) {\n    Object.assign(this, props);\n  }\n\n  provider;\n\n  window;\n\n  detectionId;\n\n  detectionInc = 0;\n\n  static getPopupName({ providerName }) {\n    return `authorization_${providerName}`;\n  }\n}\n\nexport const withAuthorizePopup = () => (C) => {\n  // a popup must be initialized in a synchronous way or it will be blocked by the browser\n  const initPopup = ({ providerName }) => {\n    const popup = popups[providerName] || new Popup();\n\n    if (!popup.window || popup.window.closed) {\n      popup.window = window.open(\n        '',\n        Popup.getPopupName({ providerName }),\n        'resizable,scrollbars,status'\n      );\n      popups[providerName] = popup;\n    }\n  };\n\n  const stopDetection = ({ popup }) => {\n    popup.window.close();\n    clearInterval(popup.detectionId);\n    popups[popup.provider.name] = undefined;\n  };\n\n  const detectPopupSuccess = ({ popup }) =>\n    new Promise((resolve, reject) => {\n      popup.detectionInc = 0;\n      popup.detectionId = setInterval(() => {\n        popup.detectionInc += 1;\n        try {\n          if (\n            popup.window.location.href.includes(\n              popup.provider.oauth_callback_uri\n            )\n          ) {\n            stopDetection({ popup });\n            resolve('success');\n\n            return;\n          }\n        } catch (err) {\n          // still on distant location\n        }\n\n        if (popup.window.closed) {\n          stopDetection({ popup });\n          reject(new Error('popup has been closed'));\n        }\n\n        if (popup.detectionInc >= MAX_TIMEOUT) {\n          stopDetection({ popup });\n          reject(new Error('popup timeout'));\n        }\n      }, 1000);\n    });\n\n  const authorizePopup = ({ provider }) => {\n    const popup = popups[provider.name];\n    if (!popup || popup.window.closed) {\n      return Promise.reject(new Error('popup has been closed'));\n    }\n\n    popup.provider = provider;\n    popup.window.location.href = provider.oauth_request_url;\n\n    return detectPopupSuccess({ popup });\n  };\n\n  const authorizeProps = {\n    initPopup,\n    authorizePopup,\n  };\n\n  return function (props) {\n    return <C {...authorizeProps} {...props} />;\n  };\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/hoc/withProviders.jsx",
    "content": "import React from 'react';\nimport WithProviders from '../components/WithProviders';\n\nexport const withProviders = () => (WrappedComp) => {\n  function Wrapper(props) {\n    return (\n      <WithProviders\n        render={({ providers, isFetching }) => (\n          <WrappedComp\n            providers={providers}\n            providersIsFetching={isFetching}\n            {...props}\n          />\n        )}\n      />\n    );\n  }\n  Wrapper.displayName = `Wrapper(${\n    WrappedComp.displayName || WrappedComp.name || 'Component'\n  })`;\n\n  return Wrapper;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/index.js",
    "content": "export * from './actions/createIdentity';\nexport * from './actions/deleteIdentity';\nexport * from './actions/updateIdentity';\nexport { default as ProviderIcon } from './components/ProviderIcon';\nexport * from './hoc/withAuthorize';\nexport * from './hoc/withProviders';\nexport * from './model/Identity';\nexport * from './selectors/identitiesSelector';\nexport * from './selectors/identitySelector';\nexport * from './selectors/identityStateSelector';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/model/Identity.js",
    "content": "export const IDENTITY_TYPE_REMOTE = 'remote';\nexport const REMOTE_IDENTITY_STATUS_ACTIVE = 'active';\nexport const REMOTE_IDENTITY_STATUS_INACTIVE = 'inactive';\nexport const PROVIDER_EMAIL = 'email';\nexport const PROVIDER_GMAIL = 'gmail';\nexport const PROVIDER_TWITTER = 'twitter';\nexport const PROVIDER_MASTODON = 'mastodon';\n\n// XXX: should be in message module\nexport const PROTOCOL_EMAIL = 'email';\nexport const PROTOCOL_TWITTER = 'twitter';\nexport const PROTOCOL_MASTODON = 'mastodon';\n// ---\n\n/* eslint-disable camelcase */\nexport class Identity {\n  constructor(props) {\n    Object.assign(this, props);\n  }\n\n  identity_id;\n\n  display_name;\n\n  credentials = {};\n\n  identifier;\n\n  infos = {};\n\n  // FIXME: backend doen't have this info for now: usefull for remoteIdentForm display\n  provider;\n\n  protocol;\n\n  status = REMOTE_IDENTITY_STATUS_INACTIVE;\n\n  type = IDENTITY_TYPE_REMOTE;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/selectors/identitiesSelector.js",
    "content": "import { createSelector } from 'reselect';\nimport { identityStateSelector } from './identityStateSelector';\n\nexport const identitiesSelector = createSelector(\n  [identityStateSelector],\n  (remoteIdentityState) =>\n    remoteIdentityState.remoteIdentities.map(\n      (identityId) => remoteIdentityState.remoteIdentitiesById[identityId]\n    )\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/selectors/identitySelector.js",
    "content": "import { createSelector } from 'reselect';\nimport { identityStateSelector } from './identityStateSelector';\n\nexport const identitySelector = createSelector(\n  [identityStateSelector, (state, { identityId }) => identityId],\n  (remoteIdentityState, identityId) =>\n    remoteIdentityState.remoteIdentitiesById[identityId]\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/selectors/identityStateSelector.js",
    "content": "export const identityStateSelector = (state) => state.remoteIdentity;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/remoteIdentity/services/getProvider.js",
    "content": "import getClient from '../../../services/api-client';\n\nexport const getProvider = ({ providerName, identifier }) =>\n  getClient()\n    .get(\n      `/api/v2/providers/${providerName}?identifier=${encodeURIComponent(\n        identifier\n      )}`\n    )\n    .then((payload) => payload.data);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/components/AuthenticatedLayout/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withRouter, Redirect } from 'react-router-dom';\nimport { AppLoader } from '../../../../components';\nimport { isAuthenticated } from '../../../user';\nimport { withAuthenticatedProps } from './withAuthenticatedProps';\n\n@withRouter\n@withAuthenticatedProps()\nclass AuthenticatedLayout extends Component {\n  static propTypes = {\n    children: PropTypes.node.isRequired,\n    user: PropTypes.shape({}),\n    settings: PropTypes.shape({}),\n    isFetching: PropTypes.bool.isRequired,\n    location: PropTypes.shape({ pathname: PropTypes.string }).isRequired,\n    didLostAuth: PropTypes.bool.isRequired,\n  };\n\n  static defaultProps = {\n    user: undefined,\n    settings: undefined,\n  };\n\n  state = {\n    initialized:\n      !!this.props.user && !!this.props.settings && !this.props.isFetching,\n  };\n\n  componentDidUpdate(prevProps, prevState) {\n    const { user, settings, isFetching } = this.props;\n    if (!prevState.initialized && !!user && !!settings && !isFetching) {\n      // eslint-disable-next-line react/no-did-update-set-state\n      this.setState({\n        initialized: true,\n      });\n    }\n  }\n\n  render() {\n    const {\n      user,\n      settings,\n      children,\n      location: { pathname },\n      didLostAuth,\n    } = this.props;\n\n    const redirectRequired = !isAuthenticated();\n\n    if (redirectRequired && pathname === '/') {\n      return <Redirect push to=\"/about\" />;\n    }\n\n    if (didLostAuth || redirectRequired) {\n      return <Redirect push to={`/auth/signin?redirect=${pathname}`} />;\n    }\n\n    const appLoadProps = {\n      isLoading: !this.state.initialized,\n      hasFailure: this.state.initialized && (!user || !settings),\n      fallbackUrl: pathname,\n      children,\n    };\n\n    return <AppLoader {...appLoadProps} />;\n  }\n}\n\nexport default AuthenticatedLayout;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/components/AuthenticatedLayout/withAuthenticatedProps.jsx",
    "content": "import React, { Component } from 'react';\nimport { WithSettings } from '../../../settings';\nimport { WithUser } from '../../../user';\n\nexport const withAuthenticatedProps = () => (C) => {\n  class Wrapper extends Component {\n    renderComponent({\n      settings,\n      isSettingsFetching,\n      user,\n      isUserFetching,\n      didLostAuthSettings,\n      didLostAuthUser,\n    }) {\n      const isFetching = isUserFetching || isSettingsFetching;\n      const didLostAuth = didLostAuthSettings && didLostAuthUser;\n\n      return (\n        <C\n          settings={settings}\n          user={user}\n          isFetching={isFetching}\n          didLostAuth={didLostAuth}\n          {...this.props}\n        />\n      );\n    }\n\n    render() {\n      return (\n        <WithSettings\n          render={(settings, isSettingsFetching, didLostAuthSettings) => (\n            <WithUser\n              render={(user, isUserFetching, didLostAuthUser) =>\n                this.renderComponent({\n                  settings,\n                  isSettingsFetching,\n                  user,\n                  isUserFetching,\n                  didLostAuthSettings,\n                  didLostAuthUser,\n                })\n              }\n            />\n          )}\n        />\n      );\n    }\n  }\n\n  return Wrapper;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/components/RoutingConsumer.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { RoutingContext } from '../contexts/RoutingContext';\n\n// eslint-disable-next-line react/prefer-stateless-function\nclass RoutingConsumer extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n  };\n\n  render() {\n    const { render } = this.props;\n\n    return (\n      <RoutingContext.Consumer>\n        {(value) => render(value)}\n      </RoutingContext.Consumer>\n    );\n  }\n}\n\nexport default RoutingConsumer;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/components/RoutingProvider.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { matchPath } from 'react-router-dom';\nimport { withI18n } from '@lingui/react';\nimport { RoutingContext } from '../contexts/RoutingContext';\nimport About from '../../../scenes/About';\nimport Signin from '../../../scenes/Signin';\nimport Signup from '../../../scenes/Signup';\nimport ForgotPassword from '../../../scenes/ForgotPassword';\nimport ResetPassword from '../../../scenes/ResetPassword';\nimport Contact from '../../../scenes/contact/Contact';\nimport EditContact from '../../../scenes/contact/EditContact';\nimport NewContact from '../../../scenes/contact/NewContact';\nimport ContactAssociation from '../../../scenes/ContactAssociation';\nimport AboutPageLayout from '../../../layouts/AboutPage';\nimport AuthPageLayout from '../../../layouts/AuthPage';\nimport PageLayout from '../../../layouts/Page';\nimport SettingsLayout from '../../../layouts/Settings';\nimport UserLayout from '../../../layouts/User';\nimport Timeline from '../../../scenes/Timeline';\nimport NewDraft from '../../../scenes/NewDraft';\nimport SearchResults from '../../../scenes/SearchResults';\nimport UserProfile from '../../../scenes/UserProfile';\nimport UserSecurity from '../../../scenes/UserSecurity';\n// XXX: UserPrivacy not used yet\n// import UserPrivacy from '../../../scenes/UserPrivacy';\nimport RemoteIdentitySettings from '../../../scenes/RemoteIdentitySettings';\n// import SettingsSignatures from '../../../scenes/SettingsSignatures';\nimport ApplicationSettings from '../../../scenes/ApplicationSettings';\nimport Tags from '../../../scenes/TagsSettings';\nimport Discussion from '../../../scenes/Discussion';\nimport ContactBook from '../../../scenes/contact/ContactBook';\nimport PageNotFound from '../../../scenes/error/PageNotFound';\nimport DevicesSettings from '../../../scenes/DevicesSettings';\nimport NewDeviceInfo from '../../../scenes/NewDeviceInfo';\nimport ValidateDevice from '../../../scenes/ValidateDevice';\nimport View from '../../../scenes/View';\nimport { renderParticipant } from '../../../services/message';\nimport { formatName } from '../../contact';\nimport AuthenticatedLayout from './AuthenticatedLayout';\n\nconst tabMatchForSettings = ({ pathname }) =>\n  matchPath(pathname, {\n    path: '/settings/:type',\n    exact: true,\n    strict: false,\n  });\nconst tabMatchRoute = ({ pathname, routeConfig }) =>\n  matchPath(pathname, routeConfig);\nconst tabMatchPathname = ({ pathname, tab }) =>\n  pathname === tab.location.pathname;\nconst tabMatchForContact = ({ pathname, tab, routeConfig }) =>\n  tabMatchRoute({ pathname, routeConfig }) &&\n  pathname.split('/edit')[0] === tab.location.pathname.split('/edit')[0];\n\n@withI18n()\nclass RoutingProvider extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({\n      _: PropTypes.func.isRequired,\n    }).isRequired,\n    settings: PropTypes.shape({}).isRequired,\n  };\n\n  state = {\n    routes: [],\n  };\n\n  UNSAFE_componentWillMount() {\n    this.initializeRoutes();\n  }\n\n  // TODO: refactor\n  //    route initialization can be done by scene via root App component.\n  //    it is not easy, a route injection method here cannot be used because it will re-render and\n  //    so the component it self which injects the routes\n  // TODO: refactor\n  //    may be change the tab config to a render func because it a bit spaghetti to have tab\n  //    component far away in layout. historycally a tab can be rendered diffently in sidebar on\n  //    mobile or in the navbar on large screens\n  initializeRoutes = () => {\n    const { i18n } = this.props;\n    this.setState({\n      // eslint-disable-next-line react/no-unused-state\n      routes: [\n        {\n          path: '/about',\n          component: AboutPageLayout,\n          app: 'auth',\n          routes: [{ path: '/about', component: About }],\n        },\n        {\n          path: '/auth',\n          component: AuthPageLayout,\n          app: 'auth',\n          routes: [\n            { path: '/auth/', exact: true, redirect: '/auth/signin' },\n            { path: '/auth/signin', component: Signin },\n            { path: '/auth/signup', component: Signup },\n            { path: '/auth/forgot-password', component: ForgotPassword },\n            { path: '/auth/passwords/reset/:key', component: ResetPassword },\n          ],\n        },\n        {\n          path: '/',\n          component: AuthenticatedLayout,\n          routes: [\n            {\n              path: '/',\n              component: PageLayout,\n              routes: [\n                {\n                  path: '/',\n                  exact: true,\n                  component: Timeline,\n                  app: 'discussion',\n                  tab: {\n                    type: 'application',\n                    icon: 'home',\n                    renderLabel: () =>\n                      i18n._(/* i18n */ 'route.timeline.label', null, {\n                        message: 'Timeline',\n                      }),\n                    tabMatch: tabMatchRoute,\n                  },\n                },\n                {\n                  path: '/discussions/:discussionId',\n                  component: Discussion,\n                  app: 'discussion',\n                  tab: {\n                    type: 'discussion',\n                    renderLabel: ({ discussion }) => {\n                      if (discussion) {\n                        return discussion.participants\n                          .map(renderParticipant)\n                          .join(' ');\n                      }\n\n                      return i18n._(/* i18n */ 'route.discussion.label', null, {\n                        message: 'Discussion ...',\n                      });\n                    },\n                    tabMatch: tabMatchPathname,\n                  },\n                },\n                {\n                  path: '/compose',\n                  component: NewDraft,\n                  app: 'discussion',\n                  exact: true,\n                  strict: true,\n                  // tab: {\n                  //   type: 'compose',\n                  //   renderLabel: () => i18n._(/* i18n */ 'compose.route.label'),\n                  //   icon: 'pencil',\n                  //   tabMatch: tabMatchRoute,\n                  // },\n                },\n                {\n                  path: '/messages/:messageId',\n                  component: NewDraft,\n                  app: 'discussion',\n                  tab: {\n                    type: 'compose',\n                    icon: 'pencil',\n                    renderLabel: () =>\n                      i18n._(/* i18n */ 'route.compose.label', null, {\n                        message: 'Compose',\n                      }),\n                    tabMatch: tabMatchPathname,\n                  },\n                },\n                {\n                  path: '/contacts',\n                  exact: true,\n                  component: ContactBook,\n                  app: 'contact',\n                  tab: {\n                    type: 'application',\n                    icon: 'address-book',\n                    renderLabel: () =>\n                      i18n._(/* i18n */ 'route.contact-book.label', null, {\n                        message: 'Contacts',\n                      }),\n                    tabMatch: tabMatchRoute,\n                  },\n                },\n                {\n                  path: '/contact-association/:protocol/:address',\n                  exact: true,\n                  component: ContactAssociation,\n                  app: 'contact',\n                  tab: {\n                    type: 'contact-association',\n                    icon: 'address-book',\n                    renderLabel: ({ label, address }) =>\n                      i18n._(\n                        /* i18n */ 'route.contact-association.label',\n                        { label: label || address },\n                        { message: 'Associate \"{label}\"' }\n                      ),\n                    tabMatch: tabMatchPathname,\n                  },\n                },\n                {\n                  path: '/contacts/:contactId',\n                  exact: false,\n                  strict: false,\n                  routes: [\n                    {\n                      path: '/contacts/:contactId',\n                      exact: true,\n                      strict: false,\n                      component: Contact,\n                    },\n                    {\n                      path: '/contacts/:contactId/edit',\n                      exact: true,\n                      strict: false,\n                      component: EditContact,\n                    },\n                  ],\n                  app: 'contact',\n                  tab: {\n                    type: 'contact',\n                    icon: 'address-book',\n                    renderLabel: ({ contact }) => {\n                      const {\n                        settings: { contact_display_format: format },\n                      } = this.props;\n\n                      return (\n                        (contact && formatName({ contact, format })) ||\n                        i18n._(\n                          /* i18n */ 'contact.profile.name_not_set',\n                          null,\n                          {\n                            message: '(N/A)',\n                          }\n                        )\n                      );\n                    },\n                    tabMatch: tabMatchForContact,\n                  },\n                },\n                {\n                  path: '/new-contact',\n                  component: NewContact,\n                  app: 'contact',\n                  tab: {\n                    type: 'default',\n                    icon: 'address-book',\n                    renderLabel: () =>\n                      i18n._(/* i18n */ 'route.new-contact.label', null, {\n                        message: 'New contact',\n                      }),\n                    tabMatch: tabMatchRoute,\n                  },\n                },\n                {\n                  path: '/search-results',\n                  component: SearchResults,\n                  app: 'discussion',\n                  tab: {\n                    type: 'search',\n                    icon: 'search',\n                    renderLabel: ({ term }) =>\n                      i18n._(\n                        /* i18n */ 'route.search-results.label',\n                        { term },\n                        { message: 'Results for: {term}' }\n                      ),\n                    tabMatch: tabMatchRoute,\n                  },\n                },\n                {\n                  path: '/user',\n                  app: 'user',\n                  component: UserLayout,\n                  tab: {\n                    type: 'default',\n                    icon: 'user',\n                    renderLabel: () =>\n                      i18n._(/* i18n */ 'route.user.label.default', null, {\n                        message: 'Account',\n                      }),\n                    tabMatch: tabMatchRoute,\n                  },\n                  routes: [\n                    {\n                      path: '/user/identities',\n                      component: RemoteIdentitySettings,\n                      tab: {\n                        type: 'default',\n                        icon: 'user',\n                        renderLabel: () =>\n                          i18n._(\n                            /* i18n */ 'route.settings.label.identities',\n                            null,\n                            {\n                              message: 'Security',\n                            }\n                          ),\n                        tabMatch: tabMatchForSettings,\n                      },\n                    },\n                    {\n                      path: '/user/profile',\n                      component: UserProfile,\n                      tab: {\n                        type: 'default',\n                        icon: 'user',\n                        renderLabel: () =>\n                          i18n._(/* i18n */ 'route.user.label.profile', null, {\n                            message: 'Profile',\n                          }),\n                        tabMatch: tabMatchRoute,\n                      },\n                    },\n                    // XXX: unused\n                    // {\n                    //   path: '/user/privacy',\n                    //   component: UserPrivacy,\n                    //   tab: {\n                    //     type: 'default',\n                    //     icon: 'user',\n                    //     renderLabel: () => i18n._(/* i18n */ 'route.user.label.privacy', null, { message:\n                    //     'Privacy' }),\n                    //     tabMatch: tabMatchRoute,\n                    //   },\n                    // },\n                    {\n                      path: '/user/security',\n                      component: UserSecurity,\n                      tab: {\n                        type: 'default',\n                        icon: 'user',\n                        renderLabel: () =>\n                          i18n._(/* i18n */ 'route.user.label.security', null, {\n                            message: 'Security',\n                          }),\n                        tabMatch: tabMatchRoute,\n                      },\n                    },\n                  ],\n                },\n                {\n                  path: '/settings',\n                  app: 'settings',\n                  component: SettingsLayout,\n                  renderLabel: () =>\n                    i18n._(/* i18n */ 'route.settings.label.default', null, {\n                      message: 'Settings',\n                    }),\n                  routes: [\n                    {\n                      path: '/settings/application',\n                      component: ApplicationSettings,\n                      tab: {\n                        type: 'default',\n                        icon: 'cog',\n                        renderLabel: () =>\n                          i18n._(\n                            /* i18n */ 'route.settings.label.application',\n                            null,\n                            {\n                              message: 'Devices',\n                            }\n                          ),\n                        tabMatch: tabMatchForSettings,\n                      },\n                    },\n                    {\n                      path: '/settings/tags',\n                      component: Tags,\n                      tab: {\n                        type: 'default',\n                        icon: 'cog',\n                        renderLabel: () =>\n                          i18n._(/* i18n */ 'route.settings.label.tags', null, {\n                            message: 'Tags',\n                          }),\n                        tabMatch: tabMatchForSettings,\n                      },\n                    },\n\n                    // TODO: enable devices when API ready: https://tree.taiga.io/project/caliopen-caliopen/us/314?no-milestone=1\n                    {\n                      path: '/settings/devices',\n                      component: DevicesSettings,\n                      tab: {\n                        type: 'default',\n                        icon: 'cog',\n                        renderLabel: () =>\n                          i18n._(\n                            /* i18n */ 'route.settings.label.devices',\n                            null,\n                            {\n                              message: 'Devices',\n                            }\n                          ),\n                        tabMatch: tabMatchForSettings,\n                      },\n                    },\n                    {\n                      path: '/settings/new-device',\n                      component: NewDeviceInfo,\n                      tab: {\n                        type: 'default',\n                        icon: 'cog',\n                        renderLabel: () =>\n                          i18n._(\n                            /* i18n */ 'route.settings.label.devices',\n                            null,\n                            {\n                              message: 'Devices',\n                            }\n                          ),\n                        tabMatch: tabMatchForSettings,\n                      },\n                    },\n\n                    // TODO: enable signatures\n                    // {\n                    //  path: '/settings/signatures',\n                    //  component: SettingsSignatures,\n                    // tab: {\n                    //   type: 'default',\n                    //   icon: 'cog',\n                    //   renderLabel: () => i18n._(/* i18n */ 'route.settings.label.signatures', null,\n                    // { message: 'Signatures' }),\n                    //   tabMatch: tabMatchRoute,\n                    // },\n                    // },\n                  ],\n                },\n                {\n                  path: '/validate-device/:token',\n                  component: SettingsLayout,\n                  routes: [\n                    {\n                      path: '/validate-device/:token',\n                      component: ValidateDevice,\n                      tab: {\n                        type: 'default',\n                        icon: 'cog',\n                        renderLabel: () =>\n                          i18n._(\n                            /* i18n */ 'route.settings.label.devices',\n                            null,\n                            {\n                              message: 'Devices',\n                            }\n                          ),\n                        tabMatch: tabMatchRoute,\n                      },\n                    },\n                  ],\n                },\n                {\n                  path: '/views/:viewId',\n                  component: View,\n                  tab: {\n                    type: 'application',\n                    icon: 'file',\n                    renderLabel: () =>\n                      i18n._(/* i18n */ 'route.draft.label', null, {\n                        message: 'Drafts',\n                      }),\n                    tabMatch: tabMatchPathname,\n                  },\n                },\n                {\n                  component: PageNotFound,\n                },\n              ],\n            },\n          ],\n        },\n        {\n          component: PageNotFound,\n        },\n      ],\n    });\n  };\n\n  render() {\n    return <RoutingContext.Provider value={this.state} {...this.props} />;\n  }\n}\n\nexport default RoutingProvider;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/components/SwitchWithRoutes/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Switch, Route, Redirect } from 'react-router-dom';\n\nclass SwitchWithRoutes extends Component {\n  static propTypes = {\n    routes: PropTypes.arrayOf(PropTypes.shape({})),\n  };\n\n  static defaultProps = {\n    routes: [],\n  };\n\n  getChildrenConfig = ({ routes, component: C, path }) => {\n    switch (true) {\n      case C && routes && routes.length > 0:\n        return {\n          render: () => (\n            <C>\n              <SwitchWithRoutes routes={routes} />\n            </C>\n          ),\n        };\n      case routes && routes.length > 0:\n        return { render: () => <SwitchWithRoutes routes={routes} /> };\n      case C && true:\n        return { component: C };\n      default:\n        throw new Error(\n          `no routes nor component property specified for the route: ${path}`\n        );\n    }\n  };\n\n  renderRoute = (i, routeConfig) => {\n    const { path, exact, strict, redirect } = routeConfig;\n\n    const config = {\n      path,\n      exact,\n      strict,\n    };\n\n    if (redirect) {\n      return (\n        <Route key={i} {...config}>\n          <Redirect to={redirect} />\n        </Route>\n      );\n    }\n\n    return (\n      <Route key={i} {...config} {...this.getChildrenConfig(routeConfig)} />\n    );\n  };\n\n  render() {\n    const { routes } = this.props;\n\n    return (\n      <Switch>\n        {routes.map((routeConfig, i) => this.renderRoute(i, routeConfig))}\n      </Switch>\n    );\n  }\n}\n\nexport default SwitchWithRoutes;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/components/SwitchWithRoutes/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport SwitchWithRoutes from '.';\n\ndescribe('routing - SwitchWithRoutes', () => {\n  const routes = [\n    {\n      path: '/foo',\n      component: ({ children }) => <div>Foo {children}</div>,\n      app: 'auth',\n      routes: [\n        {\n          path: '/foo/subfoo1',\n          component: () => <div>Sub Foo</div>,\n        },\n      ],\n    },\n    {\n      path: '/bar',\n      app: 'auth',\n      routes: [\n        {\n          path: '/bar/subbar1',\n          component: () => <div>Bar 1</div>,\n        },\n      ],\n    },\n  ];\n\n  it('render', () => {\n    const comp = shallow(<SwitchWithRoutes routes={routes} />);\n\n    expect(comp.find('Route').length).toEqual(2);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/contexts/RoutingContext.js",
    "content": "import { createContext } from 'react';\n\nexport const RoutingContext = createContext({\n  routes: [],\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/hoc/withPush.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withRouter } from 'react-router-dom';\n\nexport const withPush = () => (C) => {\n  @withRouter\n  class WithPush extends Component {\n    static propTypes = {\n      history: PropTypes.shape({}).isRequired,\n      match: PropTypes.shape({}).isRequired,\n      location: PropTypes.shape({}).isRequired,\n    };\n\n    render() {\n      const {\n        history: { push },\n        match,\n        location,\n        ...props\n      } = this.props;\n\n      return <C push={push} {...props} />;\n    }\n  }\n\n  return WithPush;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/hoc/withReplace.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withRouter } from 'react-router-dom';\n\nexport const withReplace = () => (C) => {\n  @withRouter\n  class WithReplace extends Component {\n    static propTypes = {\n      history: PropTypes.shape({}).isRequired,\n      match: PropTypes.shape({}).isRequired,\n      location: PropTypes.shape({}).isRequired,\n    };\n\n    render() {\n      const {\n        history: { replace },\n        match,\n        location,\n        ...props\n      } = this.props;\n\n      return <C replace={replace} {...props} />;\n    }\n  }\n\n  return WithReplace;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/hoc/withRouteParams.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withRouter } from 'react-router-dom';\n\nexport const withRouteParams = () => (C) => {\n  @withRouter\n  class WithRouteParams extends Component {\n    static propTypes = {\n      history: PropTypes.shape({}).isRequired,\n      match: PropTypes.shape({ params: PropTypes.shape({}) }).isRequired,\n      location: PropTypes.shape({}).isRequired,\n    };\n\n    render() {\n      const {\n        history,\n        match: { params },\n        location,\n        ...props\n      } = this.props;\n\n      return <C routeParams={params} {...props} />;\n    }\n  }\n\n  return WithRouteParams;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/hoc/withSearchParams.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withRouter } from 'react-router-dom';\nimport { URLSearchParams } from '../services/url';\nimport { getSearchParams } from '../services/getSearchParams';\n\n/**\n * @deprecated, use useSearchParams\n */\nexport const withSearchParams = () => (C) => {\n  @withRouter\n  class WithSearchParams extends Component {\n    static propTypes = {\n      location: PropTypes.shape({\n        search: PropTypes.string.isRequired,\n      }).isRequired,\n    };\n\n    render() {\n      const { location, ...props } = this.props;\n\n      const searchParams = getSearchParams(location.search);\n\n      return <C searchParams={searchParams} {...props} />;\n    }\n  }\n\n  return WithSearchParams;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/hooks/useSearchParams.ts",
    "content": "import { useLocation } from 'react-router-dom';\nimport { getSearchParams } from '../services/getSearchParams';\n\nexport function useSearchParams() {\n  const { search } = useLocation();\n\n  return getSearchParams(search);\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/index.js",
    "content": "export { default as SwitchWithRoutes } from './components/SwitchWithRoutes';\nexport { default as RoutingConsumer } from './components/RoutingConsumer';\n// FIXME: cycle dependency problem at build time, RoutingProvider uses scene components that has\n// dependencies from this module.\n// Loading the routes at run time as described in the component will solve this problem.\n// export { default as RoutingProvider } from './components/RoutingProvider';\nexport * from './hoc/withPush';\nexport * from './hoc/withReplace';\nexport * from './hoc/withRouteParams';\nexport * from './hoc/withSearchParams';\nexport * from './services/url';\nexport * from './services/findTabbableRouteConfig';\nexport * from './services/flattenRouteConfig';\nexport * from './services/getRouterHistory';\nexport * from './services/signout';\nexport * from './services/getSearchParams';\nexport * from './hooks/useSearchParams';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/findTabbableRouteConfig.js",
    "content": "import { matchPath } from 'react-router-dom';\nimport { flattenRouteConfig } from './flattenRouteConfig';\n\nexport const findTabbableRouteConfig = ({ pathname, routes }) =>\n  flattenRouteConfig(routes).find(\n    ({ tab, path, exact, strict }) =>\n      !!tab && matchPath(pathname, { path, exact, strict })\n  );\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/flattenRouteConfig.js",
    "content": "export const flattenRouteConfig = (routes) =>\n  routes.reduce((acc, route) => {\n    const { routes: subRoutes, ...routeConfig } = route;\n    let flattened = [...acc, routeConfig];\n    if (subRoutes) {\n      flattened = [...flattened, ...flattenRouteConfig(subRoutes)];\n    }\n\n    return flattened;\n  }, []);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/flattenRouteConfig.spec.jsx",
    "content": "import { flattenRouteConfig } from './flattenRouteConfig';\n\ndescribe('routes \"service\"', () => {\n  describe('flattenRouteConfig', () => {\n    it('get a flattened config', () => {\n      expect(\n        flattenRouteConfig([\n          { path: 'a' },\n          {\n            path: 'b',\n            routes: [\n              { path: 'b1', routes: [{ path: 'b1I' }] },\n              { path: 'b2' },\n              { path: 'b3', routes: [{ path: 'b3I' }, { path: 'b3II' }] },\n            ],\n          },\n        ])\n      ).toEqual([\n        { path: 'a' },\n        { path: 'b' },\n        { path: 'b1' },\n        { path: 'b1I' },\n        { path: 'b2' },\n        { path: 'b3' },\n        { path: 'b3I' },\n        { path: 'b3II' },\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/getRouterHistory.js",
    "content": "let history;\nexport const getRouterHistory = () => {\n  if (!history) {\n    if (BUILD_TARGET === 'browser') {\n      history = require('history').createBrowserHistory(); // eslint-disable-line\n    }\n\n    if (BUILD_TARGET === 'server') {\n      history = require('history').createMemoryHistory(); // eslint-disable-line\n    }\n  }\n\n  return history;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/getSearchParams.spec.ts",
    "content": "import { getSearchParams } from './getSearchParams';\n\ndescribe('routing > services > getSearchparams', () => {\n  it('receive empty string', () => {\n    expect(getSearchParams('')).toEqual({});\n  });\n  it('receive queryString', () => {\n    expect(getSearchParams('?foo=bar&fool=baz')).toEqual({\n      foo: 'bar',\n      fool: 'baz',\n    });\n  });\n  it('receive queryString with number', () => {\n    expect(getSearchParams('?foo=bar&fool=3&float=1.33')).toEqual({\n      foo: 'bar',\n      fool: 3,\n      float: 1.33,\n    });\n  });\n  it('receive queryString with multiple values', () => {\n    expect(getSearchParams('?foo=bar&foo=3&foo=baz&fool=car')).toEqual({\n      foo: ['bar', 3, 'baz'],\n      fool: 'car',\n    });\n  });\n  it('receive queryString with no value', () => {\n    expect(getSearchParams('?foo=')).toEqual({\n      foo: '',\n    });\n  });\n  it('receive not queryString', () => {\n    expect(getSearchParams('foo@bar')).toEqual({\n      'foo@bar': '',\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/getSearchParams.ts",
    "content": "import { toNumber } from 'lodash';\n\ntype Value = string | number;\ntype TResult = Record<string, undefined | Value | Value[]>;\n\nexport function getSearchParams(queryString: string): TResult {\n  const paramsIterator = new URLSearchParams(queryString);\n  const res: TResult = {};\n  paramsIterator.forEach((_val, key) => {\n    const values = paramsIterator\n      .getAll(key)\n      .map((value) =>\n        Number.isFinite(parseFloat(value)) ? toNumber(value) : value\n      );\n    res[key] = values.length > 1 ? values : values[0];\n  });\n\n  return res;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/signout.js",
    "content": "import { getRouterHistory } from './getRouterHistory';\n\nexport const signout = ({ withRedirect } = { withRedirect: false }) => {\n  if (BUILD_TARGET !== 'browser') {\n    throw new Error('`signout` must be used in a browser');\n  }\n\n  const history = getRouterHistory();\n  const { pathname, search, hash } = history.location;\n  const redirect = withRedirect ? `?redirect=${pathname}${hash}${search}` : '';\n\n  // force full reload in order to clear stores\n  window.location.href = `/auth/signout${redirect}`;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/url/CrappyURLSearchParams.js",
    "content": "const splitSearch = (search) =>\n  (search.indexOf('?') !== -1 ? search.split('?')[1] : search)\n    .split('&')\n    .map((keyValue) => keyValue.split('='));\n\nexport default class CrappyURLSearchParams {\n  constructor(search) {\n    this.map = new Map(splitSearch(search));\n  }\n\n  get(name) {\n    return this.map.get(name);\n  }\n\n  [Symbol.iterator]() {\n    return this.map[Symbol.iterator]();\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/url/CrappyURLSearchParams.spec.js",
    "content": "import CrappyURLSearchParams from './CrappyURLSearchParams';\n\ndescribe('CrappyURLSearchParams', () => {\n  describe('acts like a URLSearchParams', () => {\n    it('get', () => {\n      expect(\n        new CrappyURLSearchParams('?foo=bar&hello=world').get('hello')\n      ).toEqual('world');\n    });\n\n    it('cast in array', () => {\n      const iterator = new CrappyURLSearchParams('?foo=bar&hello=world');\n      expect(Array.from(iterator)).toEqual([\n        ['foo', 'bar'],\n        ['hello', 'world'],\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/url/QueryStringSerializer.js",
    "content": "const isObject = (obj) => obj === Object(obj);\n\n/* following code is a refactor of :\n * https://github.com/axios/axios/blob/master/lib/helpers/buildURL.js\n\nCopyright (c) 2014-present Matt Zabriskie\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n *\n */\n\nconst encode = (val) =>\n  encodeURIComponent(val)\n    .replace(/%40/gi, '@')\n    .replace(/%3A/gi, ':')\n    .replace(/%24/g, '$')\n    .replace(/%2C/gi, ',')\n    .replace(/%20/g, '+')\n    .replace(/%5B/gi, '[')\n    .replace(/%5D/gi, ']');\n\nexport const queryStringify = (params) => {\n  const parts = [];\n\n  Object.keys(params).forEach((key) => {\n    if (params[key] === null || typeof params[key] === 'undefined') {\n      return;\n    }\n\n    let k = key;\n    let v = params[key];\n\n    if (Array.isArray(v)) {\n      k += '[]';\n    } else {\n      v = [v];\n    }\n\n    v.forEach((subV) => {\n      let finalv = subV;\n      if (subV.toISOString) {\n        finalv = subV.toISOString();\n      } else if (isObject(subV)) {\n        finalv = JSON.stringify(subV);\n      }\n      parts.push(`${encode(k)}=${encode(finalv)}`);\n    });\n  });\n\n  return parts.join('&');\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/url/buildURL.js",
    "content": "import { queryStringify } from './QueryStringSerializer';\n\nexport const buildURL = (url, params) => {\n  if (params) {\n    const queryString = queryStringify(params);\n\n    return url + (url.indexOf('?') === -1 ? '?' : '&') + queryString;\n  }\n\n  return url;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/routing/services/url/index.js",
    "content": "// FIXME or remove\nexport { default as URLSearchParams } from './CrappyURLSearchParams';\nexport * from './buildURL';\nexport * from './QueryStringSerializer';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/components/ScrollDetector/index.js",
    "content": "import { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport throttle from 'lodash/throttle';\nimport { addEventListener } from '../../../../services/event-manager';\n\nclass ScrollDetector extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n    offset: PropTypes.number,\n  };\n\n  static defaultProps = {\n    offset: 0,\n  };\n\n  state = {\n    isScrollDetected: false,\n  };\n\n  componentDidMount() {\n    this.scrollDetection();\n    this.unsubscribeScrollEvent = addEventListener(\n      'scroll',\n      throttle(\n        () => {\n          this.scrollDetection();\n        },\n        10,\n        { leading: true, trailing: true }\n      )\n    );\n  }\n\n  componentWillUnmount() {\n    this.unsubscribeScrollEvent();\n  }\n\n  scrollDetection = () => {\n    const { offset } = this.props;\n    const scrollSize = window.scrollY || document.documentElement.scrollTop;\n    const isScrollDetected = scrollSize > offset;\n\n    if (this.state.isScrollDetected !== isScrollDetected) {\n      this.setState({ isScrollDetected });\n    }\n  };\n\n  render() {\n    const { render } = this.props;\n\n    return render(this.state.isScrollDetected);\n  }\n}\n\nexport default ScrollDetector;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/components/ScrollDetector/index.js.w_detection",
    "content": "import { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport throttle from 'lodash/throttle';\nimport { addEventListener } from '../../../../services/event-manager';\n\nclass ScrollDetector extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n    // start scroll detection after this offset, ex: detected if scrollY is higher than offset\n    offset: PropTypes.number,\n    // when set, this also gives the values before and after each scroll\n    minScrollBeforeDetection: PropTypes.number,\n  };\n\n  static defaultProps = {\n    offset: 0,\n    minScrollBeforeDetection: undefined,\n  };\n\n  state = {\n    isScrollDetected: false,\n    scrollSize: 0,\n    prevScrollSize: 0,\n  };\n\n  componentDidMount() {\n    this.scrollDetection();\n    this.unsubscribeScrollEvent = addEventListener('scroll', throttle(() => {\n      this.scrollDetection();\n    }, 10, { leading: true, trailing: true }));\n  }\n\n componentWillUnmount() {\n    this.unsubscribeScrollEvent();\n  }\n\n  scrollDetection = () => {\n    const { offset, minScrollBeforeDetection } = this.props;\n    const scrollSize = window.scrollY || document.documentElement.scrollTop;\n    const isScrollDetected = scrollSize > offset;\n\n    if (this.state.isScrollDetected !== isScrollDetected) {\n      this.setState({ isScrollDetected });\n    }\n\n    // not working\n    if (!!minScrollBeforeDetection && isScrollDetected) {\n      this.setState((prevState) => {\n        if (\n          prevState.scrollSize + minScrollBeforeDetection < scrollSize ||\n          prevState.scrollSize - minScrollBeforeDetection > scrollSize\n        ) {\n          return { scrollSize, prevScrollSize: prevState.scrollSize };\n        }\n\n        return {};\n      });\n    }\n  }\n\n  saveScrollSize = () => this.setState(prevState => ({ prevScrollSize: prevState.scrollSize }));\n\n  render() {\n    const { render, minScrollBeforeDetection } = this.props;\n\n    const { isScrollDetected, scrollSize, prevScrollSize } = this.state;\n\n    if (!minScrollBeforeDetection) {\n      return render(isScrollDetected);\n    }\n\n    return render(isScrollDetected, {\n      scrollSize, prevScrollSize, saveScrollSize: this.saveScrollSize,\n    });\n  }\n}\n\nexport default ScrollDetector;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/hoc/withScrollManager.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport debounce from 'lodash/debounce';\nimport { withRouter } from 'react-router-dom';\nimport { withCurrentTab, withUpdateTab, Tab } from '../../tab';\nimport { addEventListener } from '../../../services/event-manager';\n\nconst SCROLL_DEBOUNCE_WAIT = 700;\n\n// XXX: may be scrollRestauration will be usefull https://reacttraining.com/react-router/web/guides/scroll-restoration\nexport const withScrollManager =\n  ({ namespace = 'scrollManager' } = {}) =>\n  (Comp) => {\n    @withUpdateTab()\n    @withCurrentTab()\n    @withRouter\n    class ScrollManager extends Component {\n      static propTypes = {\n        updateTab: PropTypes.func.isRequired,\n        currentTab: PropTypes.shape({ scrollY: PropTypes.number }),\n        location: PropTypes.shape({\n          hash: PropTypes.string.isRequired,\n          key: PropTypes.string.isRequired,\n        }).isRequired,\n      };\n\n      static defaultProps = {\n        currentTab: undefined,\n      };\n\n      state = { alreadyScrolledContext: '' };\n\n      componentDidMount() {\n        this.unmounted = false;\n        this.unsubscribeScrollEvent = addEventListener(\n          'scroll',\n          debounce(this.saveScrollPosition, SCROLL_DEBOUNCE_WAIT, {\n            trailing: true,\n          })\n        );\n\n        this.scroll();\n      }\n\n      componentDidUpdate() {\n        this.scroll();\n      }\n\n      componentWillUnmount() {\n        if (this.unsubscribeScrollEvent) this.unsubscribeScrollEvent();\n        this.unmounted = true;\n      }\n\n      saveScrollPosition = () => {\n        if (this.unmounted === true) return;\n\n        const { currentTab, updateTab: setTabScroll } = this.props;\n        const { scrollY: newScrollY } = window;\n        const tab = new Tab({ ...currentTab, scrollY: newScrollY });\n\n        setTabScroll({ tab, original: currentTab });\n      };\n\n      scroll = () => {\n        if (!this.props.currentTab) return;\n\n        const {\n          currentTab: { scrollY },\n          location,\n        } = this.props;\n        const hash =\n          location.hash && location.hash.length > 1\n            ? location.hash.slice(1)\n            : '';\n\n        if (\n          (!hash || hash === '') &&\n          !Number.isNaN(scrollY) &&\n          this.state.alreadyScrolledContext !== this.props.location.key\n        ) {\n          this.restoreScroll(scrollY);\n          this.saveScrollPosition();\n        }\n      };\n\n      restoreScroll = (scrollY) => {\n        window.scrollTo(0, scrollY);\n        this.setState({ alreadyScrolledContext: this.props.location.key });\n      };\n\n      shouldScrollToTarget = () => this.props.location.key;\n\n      render() {\n        const injected = {\n          [namespace]: {\n            scrollToTarget: this.shouldScrollToTarget,\n          },\n        };\n\n        return <Comp {...injected} {...this.props} />;\n      }\n    }\n\n    return ScrollManager;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/hoc/withScrollTarget.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { scrollTop } from '../services/scrollTop';\nimport { getViewPortTop } from '../services/getViewPortTop';\n\nexport const withScrollTarget =\n  ({ namespace = 'scrollTarget' } = {}) =>\n  (Component) => {\n    class ScrollTarget extends PureComponent {\n      static propTypes = {\n        scrollToMe: PropTypes.func,\n      };\n\n      static defaultProps = {\n        scrollToMe: undefined,\n      };\n\n      state = { alreadyScrolledContext: undefined };\n\n      componentDidMount() {\n        return this.scroll();\n      }\n\n      componentDidUpdate() {\n        return this.scroll();\n      }\n\n      getRef = (ref) => {\n        this.element = ref;\n      };\n\n      scroll = () => {\n        if (!this.props.scrollToMe || !this.element) return;\n\n        const scrollContext = this.props.scrollToMe();\n\n        if (this.state.alreadyScrolledContext !== scrollContext) {\n          const top = getViewPortTop(this.element);\n          scrollTop(top);\n\n          this.setState({ alreadyScrolledContext: scrollContext });\n        }\n      };\n\n      render() {\n        const injected = {\n          [namespace]: {\n            forwardRef: this.getRef,\n          },\n        };\n\n        return <Component {...injected} {...this.props} />;\n      }\n    }\n\n    return ScrollTarget;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/hooks/useScrollToMe.ts",
    "content": "import * as React from 'react';\nimport { useLocation } from 'react-router-dom';\n\nimport { scrollTop } from '../services/scrollTop';\nimport { getViewPortTop } from '../services/getViewPortTop';\n\ninterface Options {\n  focusable?: boolean;\n}\n/**\n * Usage, it will scroll to the div when the hash becomes 'reply':\n * const ref = useScrollToMe('reply');\n * …\n * <div ref={ref} />\n *\n * @param {string} me corresponding hash to handle\n */\nexport function useScrollToMe(me: string, { focusable }: Options = {}) {\n  const { hash } = useLocation();\n  const ref = React.useRef<any>();\n\n  React.useEffect(() => {\n    if (hash === me && ref.current) {\n      const top = getViewPortTop(ref.current);\n      scrollTop(top);\n      if (focusable) {\n        ref.current.focus();\n      }\n    }\n  }, [hash]);\n\n  return ref;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/index.js",
    "content": "export { default as ScrollDetector } from './components/ScrollDetector';\nexport * from './hoc/withScrollManager';\nexport * from './hoc/withScrollTarget';\nexport * from './hooks/useScrollToMe';\nexport * from './services/getTop';\nexport * from './services/getViewPortTop';\nexport * from './services/scrollTop';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/services/getTop.js",
    "content": "export const HEADER_HEIGHT = 120;\nexport const getTop = (domNode) =>\n  domNode.getBoundingClientRect().top - HEADER_HEIGHT;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/services/getViewPortTop.js",
    "content": "import { getTop } from './getTop';\n\nexport const getViewPortTop = (domNode, target = window) =>\n  getTop(domNode) + target.scrollY;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/services/scrollTop.js",
    "content": "import scroll from 'scroll';\nimport scrollDoc from '../vendors/scroll-doc';\n\nexport const scrollTop = (y, isAnimated = false) => {\n  if (isAnimated) {\n    scroll.top(scrollDoc(), y, { duration: 350 });\n\n    return;\n  }\n\n  window.scroll(0, y);\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/scroll/vendors/scroll-doc.js",
    "content": "// source: https://github.com/GreenGremlin/scroll-doc\n\n// The MIT License (MIT)\n//\n// Copyright (c) 2016 Jonathan\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nlet scrollElem;\n\nconst detectScrollElem = () => {\n  const win = window || {};\n  const doc = document || {};\n\n  // IE < 9 & Node\n  if (win.pageYOffset === undefined && doc.documentElement) {\n    return doc.documentElement;\n  }\n\n  const startScrollTop = win.pageYOffset;\n  doc.documentElement.scrollTop = startScrollTop + 1;\n  if (win.pageYOffset > startScrollTop) {\n    doc.documentElement.scrollTop = startScrollTop;\n    // IE > 9 & FF (standard)\n\n    return doc.documentElement;\n  }\n  // Chrome (non-standard)\n\n  return doc.scrollingElement || doc.body;\n};\n\nexport default () => {\n  if (!scrollElem) {\n    scrollElem = detectScrollElem();\n  }\n\n  return scrollElem;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/search/components/SearchField/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/search/components/SearchField/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Trans, withI18n } from '@lingui/react';\nimport { withRouter } from 'react-router-dom';\nimport { Icon, InputText, Button } from '../../../../components';\nimport { withSearchParams } from '../../../routing';\nimport './style.scss';\n\nconst SEARCH_PATH = '/search-results';\nconst MIN_TERM_LENGTH = 3;\n\n@withI18n()\n@withRouter\n@withSearchParams()\nclass SearchField extends Component {\n  static propTypes = {\n    className: PropTypes.string,\n    history: PropTypes.shape({\n      push: PropTypes.func.isRequired,\n    }).isRequired,\n    searchParams: PropTypes.shape({\n      term: PropTypes.string,\n    }).isRequired,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {\n    className: '',\n  };\n\n  state = {\n    term: '',\n  };\n\n  UNSAFE_componentWillMount() {\n    const {\n      searchParams: { term = '' },\n    } = this.props;\n\n    this.setState({\n      term,\n    });\n  }\n\n  handleSubmit = (ev) => {\n    ev.preventDefault();\n    const {\n      history: { push },\n    } = this.props;\n    push(`${SEARCH_PATH}?term=${this.state.term}`);\n  };\n\n  handleInputChange = (event) => {\n    const { target } = event;\n    const { name, type } = target;\n    const value = type === 'checkbox' ? target.checked : target.value;\n\n    this.setState({\n      [name]: value,\n    });\n  };\n\n  render() {\n    const { i18n, className } = this.props;\n\n    return (\n      <div className={classnames('m-search-field', className)}>\n        <form\n          action=\"/search-results\"\n          method=\"get\"\n          onSubmit={this.handleSubmit}\n        >\n          <InputText\n            name=\"term\"\n            onChange={this.handleInputChange}\n            value={this.state.term}\n            placeholder={i18n._(/* i18n */ 'header.menu.search', null, {\n              message: 'Search',\n            })}\n            className=\"m-search-field__search-input\"\n          />\n          <Button\n            className=\"m-search-field__search-button\"\n            type=\"submit\"\n            shape=\"plain\"\n            disabled={this.state.term.length < MIN_TERM_LENGTH}\n          >\n            <span className=\"m-search-field__button-label\">\n              <Trans id=\"header.actions.search\" message=\"Search\" />\n            </span>\n            <Icon type=\"search\" />\n          </Button>\n        </form>\n      </div>\n    );\n  }\n}\n\nexport default SearchField;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/search/components/SearchField/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n$search-padding: rem-calc(0.625rem / 2);\n\n.m-search-field {\n  position: relative;\n\n  &__search-input {\n    width: 100%;\n    padding-right: 4rem; // about the size of the simple icon button\n    border: none;\n\n    @include breakpoint(medium) {\n      padding-right: 14rem; // about the size of the button\n    }\n  }\n\n  &__search-button {\n    position: absolute;\n    top: $search-padding;\n    right: $search-padding;\n    height: $co-component__height - ($search-padding * 2);\n    line-height: $co-component__height - ($search-padding * 2);\n  }\n\n  &__button-label {\n    display: none;\n    margin-right: $co-component__spacing;\n\n    @include breakpoint(medium) {\n      display: inline;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/search/index.js",
    "content": "export { default as SearchField } from './components/SearchField';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/settings/components/WithSettings/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport Presenter from './presenter';\nimport { requestSettings } from '../../../../store/modules/settings';\nimport { settingsStateSelector } from '../../../../store/selectors/settings';\n\nconst mapStateToProps = createSelector(\n  [settingsStateSelector],\n  ({ settings, isFetching, isInvalidated, didLostAuth }) => ({\n    settings,\n    isFetching,\n    isInvalidated,\n    didLostAuth,\n  })\n);\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      requestSettings,\n    },\n    dispatch\n  );\n\nexport default compose(connect(mapStateToProps, mapDispatchToProps))(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/settings/components/WithSettings/presenter.jsx",
    "content": "import { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { isAuthenticated } from '../../../user';\n\nclass WithSettings extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n    synced: PropTypes.bool,\n    requestSettings: PropTypes.func.isRequired,\n    settings: PropTypes.shape({}),\n    isInvalidated: PropTypes.bool.isRequired,\n    didLostAuth: PropTypes.bool.isRequired,\n    isFetching: PropTypes.bool.isRequired,\n    networkDisabled: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    settings: undefined,\n    synced: false,\n    networkDisabled: false,\n  };\n\n  componentDidMount() {\n    const {\n      settings,\n      requestSettings,\n      isInvalidated,\n      isFetching,\n      networkDisabled,\n    } = this.props;\n\n    if (\n      !networkDisabled &&\n      (!settings || isInvalidated) &&\n      !isFetching &&\n      isAuthenticated()\n    ) {\n      requestSettings();\n    }\n  }\n\n  render() {\n    const { synced, render, settings, isFetching, didLostAuth } = this.props;\n\n    if (synced && !settings) {\n      return null;\n    }\n\n    return render(settings, isFetching, didLostAuth);\n  }\n}\n\nexport default WithSettings;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/settings/components/WithSettings/presenter.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport WithSettings from './presenter';\n\njest.mock('../../../user', () => ({\n  isAuthenticated: () => true,\n}));\n\ndescribe('component WithSettings', () => {\n  it('render', () => {\n    const ownProps = {\n      requestSettings: jest.fn(),\n    };\n    const comp = shallow(\n      <WithSettings\n        {...ownProps}\n        render={(settings) => {\n          if (settings) {\n            return 'set';\n          }\n\n          return 'not set';\n        }}\n      />\n    );\n\n    expect(comp.text()).toEqual('not set');\n    expect(ownProps.requestSettings).toHaveBeenCalled();\n  });\n\n  it('does not refetch', () => {\n    const ownProps = {\n      requestSettings: jest.fn(),\n      settings: {},\n    };\n    const comp = shallow(\n      <WithSettings\n        {...ownProps}\n        render={(settings) => {\n          if (settings) {\n            return 'set';\n          }\n\n          return 'not set';\n        }}\n      />\n    );\n\n    expect(comp.text()).toEqual('set');\n    expect(ownProps.requestSettings).not.toHaveBeenCalled();\n  });\n\n  it('forces refetch', () => {\n    const ownProps = {\n      requestSettings: jest.fn(),\n      settings: {},\n      isInvalidated: true,\n    };\n    const comp = shallow(\n      <WithSettings\n        {...ownProps}\n        render={(settings) => {\n          if (settings) {\n            return 'set';\n          }\n\n          return 'not set';\n        }}\n      />\n    );\n\n    expect(comp.text()).toEqual('set');\n    expect(ownProps.requestSettings).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/settings/hoc/withSettings.js",
    "content": "import { createSelector } from 'reselect';\nimport { connect } from 'react-redux';\nimport { settingsSelector } from '../../../store/selectors/settings';\n\nconst mapStateToProps = createSelector([settingsSelector], (settings) => ({\n  settings,\n}));\n\nconst withSettings = () => (Component) => connect(mapStateToProps)(Component);\n\n// TODO: refactor  with the new context API\nexport default withSettings;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/settings/hooks/useSettings.ts",
    "content": "import * as React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { requestSettings } from 'src/store/modules/settings';\nimport { isAuthenticated } from 'src/modules/user';\nimport { settingsSelector } from '../selectors/settings';\nimport { Settings } from '../types';\n\nconst isToFetchSelector = (state) => {\n  const { settings, isFetching, isInvalidated, didLostAuth } =\n    settingsSelector(state);\n\n  return (!settings || isInvalidated || didLostAuth) && !isFetching;\n};\n\nconst getSettings = () => async (dispatch, getState) => {\n  if (isToFetchSelector(getState())) {\n    await dispatch(requestSettings());\n  }\n\n  return settingsSelector(getState()).settings;\n};\n\nexport function useSettings(): Settings {\n  const dispatch = useDispatch();\n  const { settings } = useSelector(settingsSelector);\n  const isToFetch = useSelector(isToFetchSelector);\n\n  React.useEffect(() => {\n    if (isToFetch && isAuthenticated()) {\n      dispatch(getSettings());\n    }\n  }, [isToFetch, dispatch]);\n\n  return settings;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/settings/index.js",
    "content": "export { default as WithSettings } from './components/WithSettings';\nexport { default as withSettings } from './hoc/withSettings';\nexport * from './selectors/settings';\nexport * from './services/getDefaultSettings';\nexport * from './hooks/useSettings';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/settings/selectors/settings.ts",
    "content": "import { RootState } from 'src/store/reducer';\n\nexport const settingsSelector = (state: RootState): RootState['settings'] =>\n  state.settings;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/settings/services/getDefaultSettings/index.js",
    "content": "import { getBestLocale } from '../../../i18n';\n\nexport const getDefaultSettings = (locales) => ({\n  default_locale: getBestLocale(locales),\n  message_display_format: 'rich_text',\n  contact_display_format: 'given_name, family_name',\n  contact_display_order: 'given_name',\n  notification_enabled: true,\n  notification_message_preview: 'always',\n  notification_sound_enabled: false,\n  notification_delay_disappear: 10,\n  notification_delay_disappear_unit: 's',\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/settings/types.d.ts",
    "content": "import { AVAILABLE_LOCALES } from 'src/modules/i18n';\n\nexport interface Settings {\n  default_locale: typeof AVAILABLE_LOCALES[number];\n  message_display_format: 'rich_text';\n  contact_display_format: 'given_name, family_name';\n  contact_display_order: 'given_name';\n  notification_enabled: boolean;\n  notification_message_preview: 'always';\n  notification_sound_enabled: boolean;\n  notification_delay_disappear: number;\n  notification_delay_disappear_unit: 's';\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/components/TabConsumer/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { TabContext } from '../../contexts/TabContext';\n\n// eslint-disable-next-line react/prefer-stateless-function\nclass TabConsumer extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n  };\n\n  render() {\n    const { render } = this.props;\n\n    return (\n      <TabContext.Consumer>{(value) => render(value)}</TabContext.Consumer>\n    );\n  }\n}\n\nexport default TabConsumer;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/components/TabProvider/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n } from '@lingui/react';\nimport { withRouter } from 'react-router-dom';\nimport { Tab } from '../../model/Tab';\nimport { TabContext } from '../../contexts/TabContext';\nimport { RoutingConsumer, findTabbableRouteConfig } from '../../../routing';\n\nconst withRoutes = () => (C) =>\n  function (props) {\n    return (\n      <RoutingConsumer\n        render={({ routes }) => <C routes={routes} {...props} />}\n      />\n    );\n  };\n\n@withRouter\n@withRoutes()\n@withI18n()\nclass TabProvider extends Component {\n  static propTypes = {\n    children: PropTypes.node,\n    location: PropTypes.shape({ pathname: PropTypes.string }),\n    history: PropTypes.shape({\n      push: PropTypes.func.isRequired,\n      goBack: PropTypes.func.isRequired,\n    }).isRequired,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    routes: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n  };\n\n  static defaultProps = {\n    children: null,\n    location: {},\n  };\n\n  state = {\n    previousPathname: undefined,\n    providerValue: {\n      tabs: [],\n      removeTab: () => {\n        // noop\n      },\n      updateTab: () => {\n        // noop\n      },\n      getCurrentTab: () => {\n        // noop\n      },\n    },\n  };\n\n  UNSAFE_componentWillMount() {\n    this.setState((prevState) => ({\n      providerValue: {\n        ...prevState.providerValue,\n        removeTab: this.removeTab,\n        updateTab: this.updateTab,\n        getCurrentTab: this.getCurrentTab,\n      },\n    }));\n    this.initializeApps();\n  }\n\n  UNSAFE_componentWillReceiveProps(nextProps) {\n    if (nextProps.location !== this.props.location) {\n      this.setState((prevState) => ({\n        previousPathname: this.props.location && this.props.location.pathname,\n        providerValue: {\n          ...prevState.providerValue,\n          tabs: this.updateOrAddTab({\n            location: nextProps.location,\n            tabs: prevState.providerValue.tabs,\n          }),\n        },\n      }));\n    }\n  }\n\n  getPreviousTab = () =>\n    this.state.providerValue.tabs.find(\n      (tab) => tab.location.pathname === this.state.previousPathname\n    );\n\n  getCurrentTab = () =>\n    this.state.providerValue.tabs.find(\n      (tab) => tab.location.pathname === this.props.location.pathname\n    );\n\n  initializeApps = () => {\n    const tabs = [\n      new Tab({\n        location: this.normalizeLocation({ pathname: '/' }),\n      }),\n      new Tab({\n        location: this.normalizeLocation({ pathname: '/contacts' }),\n      }),\n      new Tab({\n        location: this.normalizeLocation({ pathname: '/views/draft' }),\n      }),\n    ];\n    const { location } = this.props;\n\n    this.setState((prevState) => ({\n      providerValue: {\n        ...prevState.providerValue,\n        tabs: this.updateOrAddTab({ location, tabs }),\n      },\n    }));\n  };\n\n  removeTab = ({ tab }) => {\n    this.setState(\n      (prevState) => ({\n        providerValue: {\n          ...prevState.providerValue,\n          tabs: prevState.providerValue.tabs.filter((i) => i !== tab),\n        },\n      }),\n      () => {\n        const {\n          location: { pathname },\n        } = this.props;\n        if (pathname !== tab.location.pathname) {\n          return;\n        }\n        const previousTab = this.getPreviousTab();\n        if (previousTab) {\n          this.props.history.goBack();\n\n          return;\n        }\n\n        this.props.history.push('/');\n      }\n    );\n  };\n\n  updateTab = ({ tab, original }) => {\n    this.setState((prevState) => {\n      const i = prevState.providerValue.tabs.indexOf(original);\n      const nextTabs = [...prevState.providerValue.tabs];\n\n      nextTabs[i] = tab;\n\n      return {\n        providerValue: {\n          ...prevState.providerValue,\n          tabs: nextTabs,\n        },\n      };\n    });\n  };\n\n  normalizeLocation = ({ pathname = '', search = '', hash = '' }) => ({\n    pathname,\n    search,\n    hash,\n  });\n\n  updateOrAddTab = ({ location, tabs }) => {\n    if (!location) {\n      return tabs;\n    }\n\n    const { pathname } = location;\n\n    const routeConfig = findTabbableRouteConfig({\n      pathname,\n      routes: this.props.routes,\n    });\n\n    // there might be a better way to select a tab whenever it can be by id or route pattern\n    // e.g contacts/<contactId> -> each contact has a tab\n    // settings/<cat> -> all sub routes uses only one tab\n    const original = tabs.find((tab) => {\n      const currentRouteConfig = findTabbableRouteConfig({\n        pathname: tab.location.pathname,\n        routes: this.props.routes,\n      });\n\n      return currentRouteConfig.tab.tabMatch({\n        pathname,\n        tab,\n        routeConfig: currentRouteConfig,\n      });\n    });\n\n    if (original) {\n      const i = tabs.indexOf(original);\n      const nextTabs = [...tabs];\n\n      nextTabs[i] = new Tab({\n        ...original,\n        location: this.normalizeLocation(location),\n      });\n\n      return nextTabs;\n    }\n\n    if (routeConfig) {\n      const nextTabs = [\n        ...tabs,\n        new Tab({\n          location: this.normalizeLocation(location),\n        }),\n      ];\n\n      return nextTabs;\n    }\n\n    return tabs;\n  };\n\n  render() {\n    return (\n      <TabContext.Provider value={this.state.providerValue} {...this.props} />\n    );\n  }\n}\n\nexport default TabProvider;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/contexts/TabContext.js",
    "content": "import { createContext } from 'react';\n\nexport const TabContext = createContext({\n  tabs: [],\n  removeTab: ({ tab }) => {\n    // noop\n  },\n  updateTab: () => {\n    // noop\n  },\n  getCurrentTab: () => {\n    // noop\n  },\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/hoc/withCloseTab.jsx",
    "content": "import React from 'react';\nimport TabConsumer from '../components/TabConsumer';\n\nexport const withCloseTab = () => (C) =>\n  function (props) {\n    return (\n      <TabConsumer\n        render={({ removeTab, getCurrentTab }) => {\n          const closeTab = (tab) => {\n            if (!tab) {\n              return removeTab({ tab: getCurrentTab() });\n            }\n\n            return removeTab({ tab });\n          };\n\n          return <C closeTab={closeTab} {...props} />;\n        }}\n      />\n    );\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/hoc/withCurrentTab.jsx",
    "content": "import React from 'react';\nimport TabConsumer from '../components/TabConsumer';\n\nexport const withCurrentTab = () => (C) =>\n  function (props) {\n    return (\n      <TabConsumer\n        render={({ getCurrentTab }) => {\n          const currentTab = getCurrentTab();\n\n          return <C currentTab={currentTab} {...props} />;\n        }}\n      />\n    );\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/hoc/withUpdateTab.jsx",
    "content": "import React from 'react';\nimport TabConsumer from '../components/TabConsumer';\n\nexport const withUpdateTab = () => (C) =>\n  function (props) {\n    return (\n      <TabConsumer\n        render={({ updateTab }) => <C updateTab={updateTab} {...props} />}\n      />\n    );\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/hooks/useCloseTab.ts",
    "content": "import * as React from 'react';\nimport { TabContext } from '../contexts/TabContext';\n\nexport function useCloseTab() {\n  const { getCurrentTab, removeTab } = React.useContext(TabContext);\n\n  function closeTab(tab?: any) {\n    if (!tab) {\n      return removeTab({ tab: getCurrentTab() });\n    }\n\n    return removeTab({ tab });\n  }\n\n  return closeTab;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/hooks/useCurentTab.ts",
    "content": "import * as React from 'react';\nimport { TabContext } from '../contexts/TabContext';\n\nexport function useCurrentTab() {\n  const { getCurrentTab } = React.useContext(TabContext);\n\n  return getCurrentTab();\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/index.js",
    "content": "export { default as TabProvider } from './components/TabProvider';\nexport { default as TabConsumer } from './components/TabConsumer';\nexport * from './hoc/withCurrentTab';\nexport * from './hoc/withCloseTab';\nexport * from './hoc/withUpdateTab';\nexport * from './hooks/useCloseTab';\nexport * from './hooks/useCurentTab';\nexport * from './model/Tab';\nexport * from './services/getTabUrl';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/model/Tab.js",
    "content": "import { matchPath } from 'react-router-dom';\n\nexport class Tab {\n  constructor({ location, scrollY = 0 }) {\n    this.location = location;\n    this.scrollY = scrollY;\n  }\n\n  getMatch = ({ routeConfig }) =>\n    matchPath(this.location.pathname, routeConfig);\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tab/services/getTabUrl.js",
    "content": "export const getTabUrl = ({ pathname, search }) => `${pathname}${search}`;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/ManageEntityTags.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport { AllProviders } from 'test/providers';\nimport ManageEntityTags from './ManageEntityTags';\n\ndescribe('component ManageEntityTags', () => {\n  it('render', async () => {\n    const onSuccess = jest.fn();\n    const entity = {\n      tags: ['INBOX', 'foobar'],\n    };\n\n    render(\n      <ManageEntityTags\n        entity={entity}\n        type=\"contact\"\n        onSuccessChange={onSuccess}\n      />,\n      {\n        wrapper: AllProviders,\n      }\n    );\n    expect(screen.getByText('Inbox')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/ManageEntityTags.tsx",
    "content": "import * as React from 'react';\nimport { useMutation, useQueryClient } from 'react-query';\nimport { useLingui } from '@lingui/react';\nimport TagsForm from './TagsForm';\nimport { getCleanedTagCollection } from '../services/getTagLabel';\nimport { useTags } from '../hooks/useTags';\nimport { Entity, EntityType, NewTag, TagPayload } from '../types';\nimport { updateTagCollection } from '../query';\n\ninterface Props {\n  type: EntityType;\n  entity: Entity;\n  onSuccessChange?: () => void;\n}\n\nconst EMPTY_ARRAY = [];\n\nfunction ManageEntityTags({\n  entity,\n  type,\n  onSuccessChange,\n}: Props): React.ReactElement<typeof TagsForm> {\n  const { tags } = useTags();\n  const { i18n } = useLingui();\n  const queryClient = useQueryClient();\n  const updateTags = React.useCallback(\n    (nextTags: (NewTag | TagPayload)[]) =>\n      updateTagCollection(i18n, queryClient, {\n        type,\n        entity,\n        tags: nextTags,\n      }),\n    [type, entity]\n  );\n\n  const { mutateAsync } = useMutation<unknown, unknown, TagPayload[]>(\n    updateTags,\n    {\n      onSuccess: onSuccessChange,\n    }\n  );\n\n  return (\n    <TagsForm\n      initialTags={\n        !entity || !entity.tags\n          ? EMPTY_ARRAY\n          : getCleanedTagCollection(tags, entity.tags)\n      }\n      onSubmit={mutateAsync}\n    />\n  );\n}\n\nexport default ManageEntityTags;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagFieldGroup/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport 'test/unit/lingui-react';\nimport TagFieldGroup from '.';\n\ndescribe('component TagFieldGroup', () => {\n  const props = {\n    i18n: { _: (id) => id },\n  };\n\n  it('render', () => {\n    const noop = (str) => str;\n    const comp = shallow(<TagFieldGroup onSubmit={noop} {...props} />);\n\n    expect(() => {\n      comp.render();\n    }).not.toThrow();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagFieldGroup/index.tsx",
    "content": "import * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport classnames from 'classnames';\nimport { TextFieldGroup, Button, Spinner } from 'src/components';\n\nimport './style.scss';\n\ninterface TagFieldGroupProps extends withI18nProps {\n  terms: string;\n  onSubmit: any;\n  onChange: any;\n  input?: React.ComponentProps<typeof TextFieldGroup>['inputProps'];\n  innerRef?: React.ComponentProps<typeof TextFieldGroup>['ref'];\n  errors: React.ReactNode[];\n}\n\nfunction TagFieldGroup({\n  i18n,\n  terms,\n  input = {},\n  errors = [],\n  onSubmit,\n  onChange,\n  innerRef,\n}: TagFieldGroupProps) {\n  const handleChange = (ev) => {\n    const nextTerms = ev.target.value;\n    onChange(nextTerms);\n  };\n\n  const handleSubmit = () => {\n    if (terms.length === 0) {\n      return;\n    }\n\n    onSubmit();\n  };\n\n  const inputProps = {\n    ...input,\n    className: classnames(input.className),\n    placeholder: i18n._(/* i18n */ 'tags.form.search.placeholder', undefined, {\n      message: 'Search a tag ...',\n    }),\n    onChange: handleChange,\n    name: 'terms',\n    value: terms,\n    autoComplete: 'off',\n  };\n\n  return (\n    <div className=\"m-tags-search\">\n      <TextFieldGroup\n        id=\"search_tags\"\n        label={i18n._(/* i18n */ 'tags.form.search.label', undefined, {\n          message: 'Search',\n        })}\n        showLabelforSr\n        inputProps={inputProps}\n        className=\"m-tags-search__input\"\n        errors={errors}\n        ref={innerRef}\n      />\n      <Button\n        className=\"m-tags-search__button\"\n        icon=\"plus\"\n        onClick={handleSubmit}\n        aria-label={i18n._(/* i18n */ 'tags.action.add', undefined, {\n          message: 'Add',\n        })}\n      />\n    </div>\n  );\n}\n\nexport default withI18n()(TagFieldGroup);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagFieldGroup/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-tags-search {\n  @include flex-grid-row($size: expand);\n\n  margin-bottom: map_get($co-form__spacing, 'large');\n\n  &__input {\n    @include flex-grid-size;\n  }\n\n  &__button {\n    margin-left: 1px;\n    background: $co-color__fg__back--lower;\n    font-size: $co-font__size--xsmall;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagItem/__snapshots__/index.spec.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`component TagItem render 1`] = `\n<div\n  id=\"root\"\n>\n  <span\n    class=\"m-badge m-badge--normal-radius m-tag-item\"\n  >\n    <span\n      class=\"m-badge__text m-badge__text--has-button\"\n    >\n      Foo\n    </span>\n    <button\n      aria-label=\"Remove\"\n      class=\"m-badge__button m-button m-button--center m-button--inline m-button--text m-button--icon\"\n      type=\"button\"\n    >\n      <i\n        class=\"m-button__icon fa fa-remove\"\n      />\n    </button>\n  </span>\n</div>\n`;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagItem/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { render } from '@testing-library/react';\nimport { AllProviders } from 'test/providers';\nimport TagItem from '.';\nimport { TagPayload } from '../../types';\n\ndescribe('component TagItem', () => {\n  it('render', () => {\n    const tag: TagPayload = { name: 'foo', label: 'Foo', type: 'user' };\n    const onDelete = () => {};\n\n    const { container } = render(<TagItem tag={tag} onDelete={onDelete} />, {\n      wrapper: AllProviders,\n    });\n\n    expect(container.firstChild).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagItem/index.tsx",
    "content": "import React, { Component } from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport { Badge } from 'src/components';\nimport { getTagLabel } from '../../services/getTagLabel';\n\nimport './style.scss';\nimport { TagMixed } from '../../query';\nimport { TagPayload } from '../../types';\n\ninterface Props extends withI18nProps {\n  tag: TagMixed;\n  onDelete: (tag: TagMixed) => void | Promise<void>;\n}\nclass TagItem extends Component<Props> {\n  handleDeleteTag = async () => {\n    const { onDelete, tag } = this.props;\n\n    onDelete(tag);\n  };\n\n  render() {\n    const { tag, i18n } = this.props;\n\n    return (\n      <Badge\n        className=\"m-tag-item\"\n        onDelete={this.handleDeleteTag}\n        ariaLabel={i18n._(/* i18n */ 'tags.action.remove', undefined, {\n          message: 'Remove',\n        })}\n      >\n        {tag.name ? getTagLabel(i18n, tag as TagPayload) : tag.label}\n      </Badge>\n    );\n  }\n}\n\nexport default withI18n()(TagItem);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagItem/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-tag-item {\n  margin-right: map_get($co-form__spacing, 'small');\n  margin-bottom: map_get($co-form__spacing, 'small');\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagsForm/index.spec.tsx",
    "content": "import * as React from 'react';\nimport {\n  render,\n  screen,\n  waitForElementToBeRemoved,\n  within,\n} from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { AllProviders } from 'test/providers';\nimport TagsForm from '.';\nimport { TagPayload } from '../../types';\n\ndescribe('component TagsForm', () => {\n  it('render', async () => {\n    const updateTags = jest.fn();\n    const tags: TagPayload[] = [\n      { name: 'INBOX', type: 'system', label: '' },\n      { name: 'foobar', type: 'user', label: 'Foobar' },\n    ];\n    render(<TagsForm initialTags={tags} onSubmit={updateTags} />, {\n      wrapper: AllProviders,\n    });\n    expect(screen.getByText('Inbox')).toBeInTheDocument();\n  });\n\n  it('add tag', async () => {\n    const updateTags = jest.fn();\n    const tags: TagPayload[] = [\n      { name: 'INBOX', type: 'system', label: '' },\n      { name: 'foobar', type: 'user', label: 'Foobar' },\n    ];\n    render(<TagsForm initialTags={tags} onSubmit={updateTags} />, {\n      wrapper: AllProviders,\n    });\n    expect(await screen.findByText('Foobar')).toBeInTheDocument();\n    screen.getByRole('textbox').focus();\n\n    userEvent.type(screen.getByLabelText('Search'), 'I am test');\n    userEvent.click(screen.getByRole('button', { name: 'Add' }));\n    userEvent.click(screen.getByRole('button', { name: 'Validate' }));\n\n    await waitForElementToBeRemoved(screen.queryByText('Loading …'));\n\n    expect(updateTags).toHaveBeenCalledWith([\n      { name: 'INBOX', type: 'system', label: '' },\n      { name: 'foobar', type: 'user', label: 'Foobar' },\n      { label: 'I am test' },\n    ]);\n  });\n\n  it('remove tag', async () => {\n    const updateTags = jest.fn();\n    const tags: TagPayload[] = [\n      { name: 'INBOX', type: 'system', label: '' },\n      { name: 'foobar', type: 'user', label: 'Foobar' },\n    ];\n    render(<TagsForm initialTags={tags} onSubmit={updateTags} />, {\n      wrapper: AllProviders,\n    });\n    expect(screen.getByText('Inbox')).toBeInTheDocument();\n\n    const tag = screen.getByText('Foobar');\n\n    if (!tag.parentElement) {\n      throw new Error('missing parent');\n    }\n\n    userEvent.click(\n      within(tag.parentElement).getByRole('button', { name: 'Remove' })\n    );\n\n    userEvent.click(screen.getByRole('button', { name: 'Validate' }));\n\n    await waitForElementToBeRemoved(screen.queryByText('Loading …'));\n\n    expect(updateTags).toHaveBeenCalledWith([\n      { name: 'INBOX', type: 'system', label: '' },\n    ]);\n  });\n\n  it('add & remove tag', async () => {\n    const updateTags = jest.fn();\n    const tags: TagPayload[] = [\n      { name: 'INBOX', type: 'system', label: '' },\n      { name: 'foobar', type: 'user', label: 'Foobar' },\n    ];\n    render(<TagsForm initialTags={tags} onSubmit={updateTags} />, {\n      wrapper: AllProviders,\n    });\n    expect(screen.getByText('Inbox')).toBeInTheDocument();\n\n    const tag = screen.getByText('Foobar');\n\n    if (!tag.parentElement) {\n      throw new Error('missing parent');\n    }\n\n    userEvent.click(\n      within(tag.parentElement).getByRole('button', { name: 'Remove' })\n    );\n\n    screen.getByRole('textbox').focus();\n\n    userEvent.type(screen.getByLabelText('Search'), 'I am test');\n    userEvent.click(screen.getByRole('button', { name: 'Add' }));\n\n    userEvent.click(screen.getByRole('button', { name: 'Validate' }));\n\n    await waitForElementToBeRemoved(screen.queryByText('Loading …'));\n\n    await expect(updateTags).toHaveBeenCalledWith([\n      { name: 'INBOX', type: 'system', label: '' },\n      { label: 'I am test' },\n    ]);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagsForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18nProps, withI18n, useLingui } from '@lingui/react';\nimport {\n  Button,\n  Icon,\n  Dropdown,\n  VerticalMenu,\n  VerticalMenuItem,\n} from 'src/components';\nimport TagItem from '../TagItem';\nimport TagFieldGroup from '../TagFieldGroup';\nimport { getTagLabel } from '../../services/getTagLabel';\nimport { searchTags } from '../../services/searchTags';\nimport { useTags } from '../../hooks/useTags';\nimport { NewTag, TagPayload } from '../../types';\nimport './style.scss';\nimport { TagMixed } from '../../query';\n\nfunction useFoundTags(selectedTags: TagMixed[], terms: string) {\n  const { i18n } = useLingui();\n  const { tags } = useTags();\n\n  const selectedTagSet = new Set(selectedTags);\n  const availableTags = tags.filter((tag) => !selectedTagSet.has(tag));\n\n  if (!terms) {\n    return availableTags.slice(0, 20);\n  }\n\n  return searchTags(i18n, availableTags, terms).slice(0, 20);\n}\n\ninterface TagsFormProps extends withI18nProps {\n  initialTags: TagPayload[];\n  onSubmit: (tags: TagMixed[]) => void;\n}\n\nfunction TagsForm({ initialTags, onSubmit }: TagsFormProps) {\n  const { i18n } = useLingui();\n  const [terms, setTerms] = React.useState('');\n  const [isLoading, setIsLoading] = React.useState(false);\n  const [errors, setErrors] = React.useState<React.ReactNode[]>([]);\n  const [nextTags, setNextTags] = React.useState<TagMixed[]>(initialTags);\n  const inputSearchElement = React.useRef<HTMLInputElement>(null);\n  const dropdownElement = React.useRef<HTMLDivElement>(null);\n\n  const foundTags = useFoundTags(nextTags, terms);\n\n  const handleSearchChange = async (searchTerms) => {\n    setTerms(searchTerms);\n  };\n\n  const handleAddNewTag = async () => {\n    if (terms.length > 0) {\n      let newTag: NewTag = { label: terms };\n      if (foundTags.length === 1) {\n        [newTag] = foundTags;\n      }\n\n      if (nextTags.findIndex((tag) => tag.label === newTag.label) >= 0) {\n        // nothing to do\n        return;\n      }\n\n      setNextTags((tags) => [...tags, newTag]);\n      setTerms('');\n    }\n  };\n\n  const createHandleAddTag = (nextTag: TagPayload) => () => {\n    setErrors([]);\n\n    if (nextTags.findIndex((tag) => tag.label === nextTag.label) >= 0) {\n      // nothing to do\n      return;\n    }\n    setNextTags((tags) => [...tags, nextTag]);\n    setTerms('');\n  };\n\n  const handleDeleteTag = (oldTag: TagPayload) => {\n    setErrors([]);\n    setNextTags((tags) => tags.filter((tag) => tag !== oldTag));\n  };\n\n  const handleClickValidate = async () => {\n    try {\n      setIsLoading(true);\n      await onSubmit(nextTags);\n    } catch (err) {\n      setErrors([\n        <Trans\n          id=\"settings.tag.form.error.update_failed\"\n          message=\"Unexpected error occured\"\n        />,\n      ]);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"m-tags-form\">\n      {nextTags.length > 0 && (\n        <div className=\"m-tags-form__section\">\n          {nextTags.map((tag) => (\n            <TagItem tag={tag} key={tag.name} onDelete={handleDeleteTag} />\n          ))}\n        </div>\n      )}\n\n      <div className=\"m-tags-form__section\">\n        <TagFieldGroup\n          errors={errors}\n          terms={terms}\n          onChange={handleSearchChange}\n          onSubmit={handleAddNewTag}\n          innerRef={inputSearchElement}\n        />\n        <Dropdown\n          id=\"search-tag-dropdown\"\n          className=\"m-tags-form__dropdown\"\n          ref={dropdownElement}\n          dropdownControlRef={inputSearchElement}\n          closeOnClick=\"exceptSelf\"\n          withShadow\n        >\n          {foundTags.length > 0 && (\n            <VerticalMenu>\n              {foundTags.map((tag) => (\n                <VerticalMenuItem key={tag.name}>\n                  <Button\n                    className=\"m-tags-form__found-tag\"\n                    display=\"expanded\"\n                    shape=\"plain\"\n                    onClick={createHandleAddTag(tag)}\n                  >\n                    <span className=\"m-tags-form__found-tag-text\">\n                      {getTagLabel(i18n, tag)}\n                    </span>{' '}\n                    <Icon type=\"plus\" />\n                  </Button>\n                </VerticalMenuItem>\n              ))}\n            </VerticalMenu>\n          )}\n        </Dropdown>\n      </div>\n      <div className=\"m-tags-form__buttons\">\n        <Button\n          type=\"button\"\n          onClick={handleClickValidate}\n          shape=\"plain\"\n          disabled={isLoading}\n          isLoading={isLoading}\n        >\n          <Trans id=\"tag-form.validate\" message=\"Validate\" />\n        </Button>\n      </div>\n    </div>\n  );\n}\n\nexport default withI18n()(TagsForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/components/TagsForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-tags-form {\n  width: 100%;\n  max-width: map_get($co-form__width, 'xlarge');\n\n  &__count {\n    padding-left: map_get($co-form__spacing, 'small');\n    color: $co-color__fg__text--lower;\n    font-weight: 400;\n  }\n\n  &__section {\n    // XXX: required for the dropdown position\n    position: relative;\n    margin-bottom: map_get($co-form__spacing, 'large');\n  }\n\n  &__dropdown {\n    // XXX: dirty fix when dropdown has no controller then getDropdownStyle() can not be used correctly\n    margin-top: $co-component__height;\n    padding: map_get($co-form__spacing, 'small');\n    background-color: $co-color__fg__back;\n  }\n\n  &__buttons {\n    text-align: right;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/hoc/withTags/index.jsx",
    "content": "import * as React from 'react';\nimport { useTags } from '../../hooks/useTags';\n\n/**\n * @deprecated use `useTags` instead\n */\nconst withTags = () => (WrappedComponent) => {\n  function WithTags(props) {\n    const { tags } = useTags();\n    return <WrappedComponent tags={tags} {...props} />;\n  }\n\n  return WithTags;\n};\n\nexport default withTags;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/hooks/useTags.ts",
    "content": "import { useQuery, UseQueryResult } from 'react-query';\nimport { AxiosResponse } from 'axios';\nimport { APIAxiosError } from 'src/services/api-client/types';\nimport { TagAPIGetList, TagPayload } from '../types';\nimport { getQueryKeys, getTagList } from '../query';\n\ntype QueryData = AxiosResponse<TagAPIGetList['response']>;\ntype UseQueryTagsResult = UseQueryResult<QueryData, APIAxiosError>;\n\nconst EMPTY_TAG_LIST: TagPayload[] = [];\n\ninterface HookRes {\n  status: UseQueryTagsResult['status'];\n  tags: NonNullable<UseQueryTagsResult['data']>['data']['tags'];\n  data: UseQueryTagsResult['data'];\n  error: UseQueryTagsResult['error'];\n}\nexport function useTags(): HookRes {\n  const { data, error, status } = useQuery<QueryData, APIAxiosError>(\n    getQueryKeys(),\n    () => getTagList(),\n    {\n      staleTime: 300000, // 5min\n    }\n  );\n\n  return {\n    status,\n    // always return a tag list for convenience, use data instead if needed to test missing data\n    tags: data?.data.tags || EMPTY_TAG_LIST,\n    data,\n    error,\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/index.ts",
    "content": "export { default as ManageEntityTags } from './components/ManageEntityTags';\nexport { default as TagItem } from './components/TagItem';\nexport { default as TagFieldGroup } from './components/TagFieldGroup';\nexport { default as TagsForm } from './components/TagsForm';\nexport { default as withTags } from './hoc/withTags';\nexport * from './hooks/useTags';\nexport * from './services/getTagLabel';\nexport * from './services/getTagNamesInCommon';\nexport * from './services/searchTags';\nexport * as store from './store';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/query.ts",
    "content": "import { I18n } from '@lingui/core';\nimport { isEqual } from 'lodash';\nimport { QueryClient, QueryKey } from 'react-query';\nimport getClient from 'src/services/api-client';\nimport {\n  updateTags as updateContactTags,\n  getQueryKeys as getContactQueryKeys,\n} from 'src/modules/contact/query';\nimport {\n  updateTags as updateMessageTags,\n  getQueryKeys as getMessageQueryKeys,\n} from 'src/modules/message/query';\nimport {\n  updateTags as updateDiscussionTags,\n  getQueryKeys as getDiscussionQueryKeys,\n} from 'src/modules/discussion/query';\nimport calcObjectForPatch from 'src/services/api-patch';\nimport { MutateConfig, QueryConfig } from 'src/types';\nimport { getTagLabel } from './services/getTagLabel';\nimport {\n  Entity,\n  EntityType,\n  NewTag,\n  TagAPIGetList,\n  TagAPIPost,\n  TagPayload,\n} from './types';\n\nconst client = getClient();\n\n// CONFIGS\n\nexport const getQueryKeys = ({\n  fetchParams,\n}: {\n  fetchParams?: any;\n} = {}): QueryKey =>\n  ['tags', fetchParams ? JSON.stringify(fetchParams) : undefined].filter(\n    Boolean\n  );\n\nexport const getConfigList = (): QueryConfig => ({\n  url: `/api/v2/tags`,\n  queryKeys: 'tags',\n});\n\nexport const getConfigNew = (): MutateConfig => ({\n  url: `/api/v2/tags`,\n});\n\nexport const getConfigUpdate = (tag: TagPayload): MutateConfig => ({\n  url: `/api/v2/tags/${tag.name}`,\n});\n\nexport const getConfigDelete = (tag: TagPayload): MutateConfig => ({\n  url: `/api/v2/tags/${tag.name}`,\n});\n// -------\n\n// QUERIES\n\nexport function getTagList() {\n  return client.get<TagAPIGetList['response']>(getConfigList().url);\n}\n\n// MUTATIONS\n\n// --- Basic\n\nexport const createTag = (tag: NewTag) => {\n  const payload: TagAPIPost['parameters']['body'] = tag;\n\n  return client.post(getConfigNew().url, payload);\n};\n\nexport const updateTag = ({\n  value,\n  original,\n}: {\n  value: TagPayload;\n  original: TagPayload;\n}) => {\n  const payload = calcObjectForPatch(value, original);\n\n  return client.patch(getConfigUpdate(original).url, payload);\n};\n\nexport const deleteTag = (tag: TagPayload) =>\n  client.delete(getConfigDelete(tag).url);\n\n// --- Entities\n\ntype UpdateAction = (entity: Entity, tags: TagPayload[]) => Promise<any>;\nconst getUpdateAction = (type: EntityType): UpdateAction => {\n  switch (type) {\n    case 'discussion':\n      return updateDiscussionTags;\n    case 'message':\n      return updateMessageTags;\n    case 'contact':\n      return updateContactTags;\n    default:\n      throw new Error(`Entity ${type} not supported`);\n  }\n};\n\nconst getQueryKeyAction = (type: EntityType): ((...any) => QueryKey) => {\n  switch (type) {\n    case 'discussion':\n      return getDiscussionQueryKeys;\n    case 'message':\n      return getMessageQueryKeys;\n    case 'contact':\n      return getContactQueryKeys;\n    default:\n      throw new Error(`Entity ${type} not supported`);\n  }\n};\nexport const updateEntityTags = (\n  type: EntityType,\n  entity: Entity,\n  tags: TagPayload[]\n) => {\n  const action = getUpdateAction(type);\n\n  return action(entity, tags);\n};\n\nconst computeNewTags = (\n  i18n: I18n,\n  knownTags: TagPayload[],\n  tagCollection: TagMixed[]\n) => {\n  const knownLabels = knownTags.map((tag) =>\n    getTagLabel(i18n, tag).toLowerCase()\n  );\n  const newTags: NewTag[] = tagCollection\n    .filter((tag) => !tag.name)\n    .filter((tag: NewTag) => !knownLabels.includes(tag.label.toLowerCase()));\n\n  return newTags;\n};\n\nexport const createMissingTags = async (\n  i18n: I18n,\n  queryClient: QueryClient,\n  tagCollection: TagMixed[]\n) => {\n  const { queryKeys } = getConfigList();\n  const knownTags = await queryClient.fetchQuery(queryKeys, getTagList, {\n    staleTime: 300000, // 5min\n  });\n\n  const newTags = computeNewTags(i18n, knownTags.data.tags, tagCollection);\n\n  if (newTags.length === 0) {\n    return;\n  }\n\n  await Promise.all(newTags.map((tag) => createTag(tag)));\n  queryClient.invalidateQueries(queryKeys);\n};\n\nconst getTagFromLabel = (i18n: I18n, tags: TagPayload[], label: string) =>\n  tags.find(\n    (tag) => getTagLabel(i18n, tag).toLowerCase() === label.toLowerCase()\n  );\n\n// unable to refined `(TagPayload | Newtag)` type.\nexport type TagMixed = Partial<TagPayload> & NewTag;\n\nexport const updateTagCollection = async (\n  i18n: I18n,\n  queryClient: QueryClient,\n  {\n    type,\n    entity,\n    tags: tagCollection,\n  }: {\n    type: EntityType;\n    entity: Entity;\n    tags: TagMixed[];\n  }\n) => {\n  await createMissingTags(i18n, queryClient, tagCollection);\n  const { queryKeys } = getConfigList();\n  const {\n    data: { tags: upToDateTags },\n  } = await queryClient.fetchQuery(queryKeys, getTagList, {\n    staleTime: 300000, // 5min, same as `useTags`\n  });\n\n  // @ts-ignore: Boolean filter does not help\n  const normalizedTags: TagPayload[] = tagCollection\n    .map((tag) =>\n      !tag.name ? getTagFromLabel(i18n, upToDateTags, tag.label) : tag\n    )\n    .filter(Boolean);\n\n  const tagNames = normalizedTags.map((tag) => tag.name);\n  if (!isEqual(entity.tags, tagNames)) {\n    await updateEntityTags(type, entity, normalizedTags);\n\n    const entityQueryKeys = getQueryKeyAction(type);\n\n    // invalidate entity & collection\n    queryClient.invalidateQueries(entityQueryKeys());\n  }\n};\n\n// ------------------\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/services/getTagLabel/index.spec.ts",
    "content": "import { TagPayload } from '../../types';\nimport { getCleanedTagCollection } from '.';\n\ndescribe('getTagLabel', () => {\n  describe('getCleanedTagCollection', () => {\n    it('should retrieve no tags', () => {\n      const tags = [];\n      const names = ['foo'];\n      expect(getCleanedTagCollection(tags, names)).toEqual([]);\n    });\n    it('should retrieve tags by names', () => {\n      const tags: TagPayload[] = [\n        { type: 'user', name: 'foo', label: 'Foo' },\n        { type: 'user', name: 'bar', label: 'Bar' },\n      ];\n      const names = ['foo'];\n      expect(getCleanedTagCollection(tags, names)).toEqual([\n        { type: 'user', name: 'foo', label: 'Foo' },\n      ]);\n    });\n    it('should retrieve tags by label', () => {\n      const tags: TagPayload[] = [\n        { type: 'user', name: 'foo', label: 'Foo' },\n        { type: 'user', name: 'bar', label: 'Bar' },\n      ];\n      const names = ['Foo'];\n      expect(getCleanedTagCollection(tags, names)).toEqual([\n        { type: 'user', name: 'foo', label: 'Foo' },\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/services/getTagLabel/index.ts",
    "content": "import { I18n } from '@lingui/core';\nimport { TagPayload } from '../../types';\n\nexport const TAG_IMPORTANT = 'IMPORTANT';\nexport const TAG_INBOX = 'INBOX';\nexport const TAG_SPAM = 'SPAM';\n\nexport const getCleanedTagCollection = (\n  tags: TagPayload[],\n  namesOrLabels: string[]\n): TagPayload[] =>\n  tags.filter(\n    (tag) =>\n      namesOrLabels.includes(tag.name) || namesOrLabels.includes(tag.label)\n  );\n\nexport const getTag = (\n  tags: TagPayload[],\n  name: string\n): TagPayload | undefined => tags.find((item) => item.name === name);\n\nexport const getTagLabel = (i18n: I18n, tag: TagPayload): string => {\n  if (!tag.label) {\n    switch (tag.name) {\n      case TAG_IMPORTANT:\n        return i18n._(/* i18n */ 'tags.label.important', undefined, {\n          message: tag.name,\n        });\n      case TAG_INBOX:\n        return i18n._(/* i18n */ 'tags.label.inbox', undefined, {\n          message: tag.name,\n        });\n      case TAG_SPAM:\n        return i18n._(/* i18n */ 'tags.label.spam', undefined, {\n          message: tag.name,\n        });\n      default:\n        return tag.name;\n    }\n  }\n\n  return tag.label;\n};\n\nexport const getTagLabelFromName = (\n  i18n: I18n,\n  tags: TagPayload[],\n  name: string\n): string => {\n  const tag = getTag(tags, name);\n\n  if (!tag) {\n    return name;\n  }\n\n  return getTagLabel(i18n, tag);\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/services/getTagNamesInCommon.spec.ts",
    "content": "import { getTagNamesInCommon } from './getTagNamesInCommon';\n\ndescribe('modules tags - services - getTagNamesInCommon', () => {\n  it('getTagNamesInCommon & ignore empty tags', () => {\n    const messages = [{ tags: ['foobar', 'bar'] }, {}, { tags: ['foobar'] }];\n\n    const results = getTagNamesInCommon(messages);\n\n    expect(results).toEqual(['foobar']);\n  });\n\n  it('getTagNamesInCommon strict', () => {\n    const messages = [{ tags: ['foobar', 'bar'] }, {}, { tags: ['foobar'] }];\n\n    const results = getTagNamesInCommon(messages, { strict: true });\n\n    expect(results).toEqual([]);\n  });\n\n  it('getTagNamesInCommon only one item', () => {\n    const messages = [{ tags: ['foobar', 'bar'] }];\n\n    const results = getTagNamesInCommon(messages, { strict: true });\n\n    expect(results).toEqual(['foobar', 'bar']);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/services/getTagNamesInCommon.ts",
    "content": "import intersection from 'lodash/intersection';\nimport { Entity } from '../types';\n\nexport const getTagNamesInCommon = (\n  entities: Entity[],\n  { strict = false } = {}\n) =>\n  intersection(\n    ...(strict\n      ? entities\n      : entities\n          // when not strict, this ignore entities with no tags, E.g I need to delete one tag in common\n          // for a large selection which include entities with no tags\n          .filter((entity) => entity.tags)\n    ).map((entity) => entity.tags)\n  );\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/services/searchTags.spec.ts",
    "content": "import { withI18nProps } from '@lingui/react';\nimport { TagPayload } from '../types';\nimport { searchTags } from './searchTags';\n\ndescribe('modules tags - services - searchTags', () => {\n  it('searchTags', () => {\n    const i18n = { _: (id) => id } as withI18nProps['i18n'];\n    const userTags: TagPayload[] = [\n      { type: 'user', name: 'foobar', label: 'Foobar' },\n      { type: 'user', name: 'bar', label: 'Bar' },\n    ];\n\n    const results = searchTags(i18n, userTags, 'foo');\n\n    expect(results).toEqual([userTags[0]]);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/services/searchTags.ts",
    "content": "import { withI18nProps } from '@lingui/react';\nimport { TagPayload } from '../types';\nimport { getTagLabel } from './getTagLabel';\n\nconst EMPTY_ARRAY = [];\nexport function searchTags(\n  i18n: withI18nProps['i18n'],\n  tags: TagPayload[],\n  terms: string\n) {\n  if (!terms.length) {\n    return EMPTY_ARRAY;\n  }\n\n  return tags.filter((tag) =>\n    getTagLabel(i18n, tag).toLowerCase().startsWith(terms.toLowerCase())\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/store/index.ts",
    "content": "export * from './reducer';\nexport * from './selectors';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/store/reducer.ts",
    "content": "import { AxiosActionPayload, PagerParams, ResourceStatus } from 'src/types';\nimport { TagPayload } from '../types';\nimport calcObjectForPatch from '../../../services/api-patch';\n\nexport const REQUEST_TAGS = 'co/tag/REQUEST_TAGS';\nexport const REQUEST_TAGS_SUCCESS = 'co/tag/REQUEST_TAGS_SUCCESS';\nexport const REQUEST_TAGS_FAIL = 'co/tag/REQUEST_TAGS_FAIL';\nexport const INVALIDATE_TAGS = 'co/tag/INVALIDATE_TAGS';\nexport const CREATE_TAG = 'co/tag/CREATE_TAG';\nexport const CREATE_TAG_SUCCESS = 'co/tag/CREATE_TAG_SUCCESS';\nexport const REQUEST_TAG = 'co/tag/REQUEST_TAG';\nexport const UPDATE_TAG = 'co/tag/UPDATE_TAG';\nexport const REMOVE_TAG = 'co/tag/REMOVE_TAG';\n\n// Actions --------------------------------------\n// TODO: type axios middleware success/fail actions and extends it\n\ninterface RequestTagsAction {\n  type: typeof REQUEST_TAGS;\n  payload: AxiosActionPayload<PagerParams>;\n}\n\ninterface RequestTagsSuccessAction {\n  type: typeof REQUEST_TAGS_SUCCESS;\n  payload: {\n    data: {\n      tags: TagPayload[];\n      total: number;\n    };\n  };\n}\ninterface RequestTagsFailAction {\n  type: typeof REQUEST_TAGS_FAIL;\n  error: any;\n}\ninterface InvalidateAction {\n  type: typeof INVALIDATE_TAGS;\n  payload: Record<string, unknown>;\n}\n\ntype TagAction =\n  | RequestTagsAction\n  | RequestTagsSuccessAction\n  | RequestTagsFailAction\n  | InvalidateAction;\n\n// State ----------------------------------------\nexport interface TagState {\n  initialized: boolean;\n  status: ResourceStatus;\n  tags: TagPayload[];\n  total: number;\n}\n// ----------------------------------------------\n/**\n * @deprecated\n */\nexport function updateTag({ tag, original }) {\n  const data = calcObjectForPatch(tag, original);\n\n  return {\n    type: UPDATE_TAG,\n    payload: {\n      request: {\n        method: 'patch',\n        url: `/api/v2/tags/${original.name}`,\n        data,\n      },\n    },\n  };\n}\n\n// Reducer --------------------------------------\nconst initialState: TagState = {\n  status: 'idle',\n  initialized: false,\n  tags: [],\n  total: 0,\n};\n\nexport function reducer(\n  state: TagState = initialState,\n  action: TagAction\n): TagState {\n  switch (action.type) {\n    case REQUEST_TAGS:\n      return { ...state, status: 'pending' };\n    case REQUEST_TAGS_SUCCESS:\n      return {\n        ...state,\n        initialized: true,\n        status: 'resolved',\n        tags: action.payload.data.tags,\n        total: action.payload.data.total,\n      };\n    case REQUEST_TAGS_FAIL:\n      return {\n        ...state,\n        status: 'rejected',\n      };\n    case INVALIDATE_TAGS:\n      return { ...state, status: 'invalidated' };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/store/selectors.ts",
    "content": "import { RootState } from 'src/store/reducer';\nimport { TagState } from './reducer';\n\n/**\n * @deprecated\n */\nexport const stateSelector = (state: RootState): TagState => state.tag;\n\n/**\n * @deprecated\n */\nexport const shouldFetchSelector = (state: RootState) =>\n  ['idle', 'invalidated'].includes(stateSelector(state).status);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/tags/types.d.ts",
    "content": "export interface TagPayload {\n  type: 'system' | 'user';\n  name: string;\n  label: string;\n}\nexport type NewTag = {\n  label: string;\n};\n\nexport type TagAPIPost = {\n  parameters: {\n    body: NewTag;\n  };\n};\n\nexport interface TagAPIGetList {\n  response: {\n    tags: TagPayload[];\n    total: number;\n  };\n}\n\n// Entity inheritance\nexport type EntityType = 'contact' | 'discussion' | 'message';\n\nexport interface Entity {\n  tags?: string[];\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/actions/getUser.js",
    "content": "import { tryCatchAxiosAction } from '../../../services/api-client';\nimport { requestUser } from '../store/reducer';\nimport { userSelector } from '../selectors/userSelector';\n\nexport const getUser = () => async (dispatch, getState) => {\n  const user = userSelector(getState());\n\n  if (user) {\n    return user;\n  }\n\n  return tryCatchAxiosAction(() => dispatch(requestUser()));\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/SigninForm/components/SubmitButton.tsx",
    "content": "import { Trans } from '@lingui/react';\nimport { useFormikContext } from 'formik';\nimport * as React from 'react';\nimport { Button, Spinner } from 'src/components';\n\nexport default function SubmitButton() {\n  const { isSubmitting, isValid } = useFormikContext();\n\n  return (\n    <Button\n      type=\"submit\"\n      display=\"expanded\"\n      shape=\"plain\"\n      disabled={!isValid || isSubmitting}\n      icon={\n        isSubmitting ? (\n          <Spinner\n            svgTitleId=\"signin-spinner\"\n            isLoading\n            display=\"inline\"\n            theme=\"bright\"\n          />\n        ) : undefined\n      }\n    >\n      <Trans id=\"signin.action.login\" message=\"Login\" />\n    </Button>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/SigninForm/index.tsx",
    "content": "import * as React from 'react';\nimport { useDispatch } from 'react-redux';\nimport { Trans, useLingui } from '@lingui/react';\nimport { Field, Form, Formik, FormikHelpers } from 'formik';\nimport { Redirect, useLocation } from 'react-router-dom';\nimport {\n  Link,\n  FormGrid,\n  FormRow,\n  FormColumn,\n  FormikTextFieldGroup,\n} from 'src/components';\nimport getClient from 'src/services/api-client';\nimport { validateRequired } from 'src/modules/form/services/validators';\nimport { notifyError } from 'src/modules/userNotify';\nimport { getClientDevice, STATUS_VERIFIED } from 'src/modules/device';\nimport { usernameNormalizer } from '../../services/usernameNormalizer';\nimport './style.scss';\nimport SubmitButton from './components/SubmitButton';\n\nconst CONTEXT_SAFE = 'safe';\n// const CONTEXT_PUBLIC = 'public';\n// const CONTEXT_UNSECURE = 'unsecure';\n\nconst URL_DEVICES = '/settings/new-device';\n\nconst getRedirect = (queryString: string) => {\n  const paramRedirect = queryString\n    .split(/[?|&]/)\n    .find((str) => /^redirect/.test(str));\n\n  return paramRedirect ? paramRedirect.split('=')[1] : undefined;\n};\n\ninterface SigninFormValues {\n  username: string;\n  password: string;\n}\n\nconst defaultIdentifier = { username: '', password: '' };\nfunction SigninForm() {\n  const { search } = useLocation();\n  const { i18n } = useLingui();\n  const dispatch = useDispatch();\n  const clientDevice = getClientDevice();\n  const [context] = React.useState(CONTEXT_SAFE);\n\n  const [isAuthenticated, setIsAuthenticated] = React.useState(false);\n  const [shouldRedirectDevice, setShouldRedirectDevice] = React.useState(false);\n\n  const handleSignin = async (\n    { username, password }: SigninFormValues,\n    { setFieldError }: FormikHelpers<SigninFormValues>\n  ) => {\n    try {\n      const response = await getClient().post('/auth/signin', {\n        context,\n        password,\n        username: usernameNormalizer(username),\n        device: clientDevice.getPublic(),\n      });\n      if (response.data.device.status !== STATUS_VERIFIED) {\n        setShouldRedirectDevice(true);\n      }\n      setIsAuthenticated(true);\n    } catch (err) {\n      const isExpectedError =\n        err.response &&\n        err.response.status >= 400 &&\n        err.response.status < 500 &&\n        err.response.data.errors;\n\n      if (isExpectedError) {\n        setFieldError(\n          'password',\n          i18n._('signin.feedback.invalid', undefined, {\n            message: 'Credentials are invalid',\n          })\n        );\n      } else {\n        dispatch(\n          notifyError({\n            message: i18n._(\n              /* i18n */ 'general.feedback.unexpected-error',\n              undefined,\n              { message: 'Unexpected error occured' }\n            ),\n          })\n        );\n      }\n    }\n  };\n\n  const redirect = getRedirect(search) || '/';\n\n  if (\n    isAuthenticated &&\n    shouldRedirectDevice &&\n    !redirect.includes('/validate-device/')\n  ) {\n    return <Redirect push to={URL_DEVICES} />;\n  }\n\n  if (isAuthenticated) {\n    return <Redirect push to={redirect} />;\n  }\n\n  return (\n    <div className=\"s-signin\">\n      <FormGrid className=\"s-signin__form\">\n        <Formik<SigninFormValues>\n          initialValues={defaultIdentifier}\n          onSubmit={handleSignin}\n        >\n          <Form method=\"post\">\n            <FormRow>\n              <FormColumn rightSpace={false} bottomSpace>\n                <Field\n                  id=\"signin_username\"\n                  name=\"username\"\n                  component={FormikTextFieldGroup}\n                  validate={validateRequired(i18n)}\n                  inputProps={{\n                    placeholder: i18n._(\n                      'signin.form.username.placeholder',\n                      undefined,\n                      { message: 'username' }\n                    ),\n                    expanded: true,\n                  }}\n                  label={i18n._('signin.form.username.label', undefined, {\n                    message: 'Username',\n                  })}\n                />\n              </FormColumn>\n              <FormColumn rightSpace={false} bottomSpace>\n                <Field\n                  id=\"signin_password\"\n                  name=\"password\"\n                  component={FormikTextFieldGroup}\n                  validate={validateRequired(i18n)}\n                  label={i18n._('signin.form.password.label', undefined, {\n                    message: 'Password',\n                  })}\n                  inputProps={{\n                    placeholder: i18n._(\n                      'signin.form.password.placeholder',\n                      undefined,\n                      { message: 'password' }\n                    ),\n                    type: 'password',\n                    expanded: true,\n                  }}\n                />\n              </FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn\n                rightSpace={false}\n                className=\"s-signin__action\"\n                bottomSpace\n              >\n                <SubmitButton />\n              </FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn rightSpace={false} className=\"s-signin__link\">\n                <Link to=\"/auth/forgot-password\">\n                  <Trans\n                    id=\"signin.action.forgot_password\"\n                    message=\"Forgot password?\"\n                  />\n                </Link>\n              </FormColumn>\n              <FormColumn rightSpace={false} className=\"s-signin__link\">\n                <Link to=\"/auth/signup\">\n                  <Trans\n                    id=\"signin.create_an_account\"\n                    message=\"Create an account\"\n                  />\n                </Link>\n              </FormColumn>\n            </FormRow>\n          </Form>\n        </Formik>\n      </FormGrid>\n    </div>\n  );\n}\n\nexport default SigninForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/SigninForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.s-signin {\n  &__action {\n    line-height: $co-component__height;\n  }\n\n  &__link {\n    text-align: center;\n  }\n\n  &__login-safe {\n    background-color: map_get($context-palette, 'safe');\n  }\n\n  &__login-public {\n    background-color: map_get($context-palette, 'public');\n  }\n\n  &__login-unsecure {\n    background-color: map_get($context-palette, 'unsecure');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/SignupForm/components/PiwikModal.tsx",
    "content": "import { Trans, useLingui } from '@lingui/react';\nimport * as React from 'react';\nimport { Button, Modal } from 'src/components';\n\ninterface Props {\n  isModalOpen: boolean;\n  handleCloseModal: () => void;\n}\n\nexport default function PiwikModal({ isModalOpen, handleCloseModal }: Props) {\n  const { i18n } = useLingui();\n\n  return (\n    <Modal\n      className=\"s-signup__modal\"\n      isOpen={isModalOpen}\n      contentLabel={i18n._(/* i18n */ 'signup.privacy.modal.label', undefined, {\n        message: 'About Piwik',\n      })}\n      title={i18n._(/* i18n */ 'signup.privacy.modal.label', undefined, {\n        message: 'About Piwik',\n      })}\n      onClose={handleCloseModal}\n    >\n      <p>\n        <Trans\n          id=\"signup.privacy.modal.title\"\n          message=\"Caliopen is under development !\"\n        />\n      </p>\n      <p>\n        <Trans\n          id=\"signup.privacy.modal.text.alpha_tester\"\n          message=\"As an alpha-tester your contribution is precious and will allow us to finalize Caliopen.\"\n        />\n      </p>\n      <p>\n        <Trans\n          id=\"signup.privacy.modal.text.get_data\"\n          message=\"For this purpose, you grant us the right to collect data related to your usage (displayed pages, timings, clics, scrolls ...almost everything that can be collected!).\"\n        />\n      </p>\n      <p>\n        <Trans\n          id=\"signup.privacy.modal.text.desactivate_dnt\"\n          message=\"You need to deactivate the DoNotTrack setting from your browser preferences (more informations at http://donottrack.us), as well as allowing cookies.\"\n        />\n      </p>\n      <p>\n        <Trans\n          id=\"signup.privacy.modal.text.piwik\"\n          message=\"We use https://piwik.org/ the open-source analytics plateform. The collected data will not be disclosed to any third party, and will stay scoped to Caliopen's alpha testing purpose.\"\n        />\n      </p>\n      <Button shape=\"plain\" onClick={handleCloseModal}>\n        <Trans id=\"signup.privacy.modal.close\" message=\"Ok got it !\" />\n      </Button>\n    </Modal>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/SignupForm/form-validator.spec.ts",
    "content": "import { validate } from './form-validator';\n\njest.mock('src/services/username-utils/username-availability', () => ({\n  checkAvailability: () => Promise.resolve(true),\n}));\n\ndescribe('scene Signup form-validator', () => {\n  const i18n = { _: (id) => id };\n  it('validates', async () => {\n    const isValid = await validate(\n      {\n        username: 'bender',\n        password: '101121',\n        tos: true,\n        privacy: true,\n        recovery_email: 'bender@planetexpress.tld',\n      },\n      i18n,\n      'full'\n    );\n    expect(isValid).toBe(true);\n  });\n  it('render errors for form', async () => {\n    try {\n      const isValid = await validate(\n        {\n          password: '',\n          // tos: false,\n          privacy: false,\n          recovery_email: 'bender',\n        },\n        i18n,\n        'full'\n      );\n      expect(isValid).not.toBe(true);\n    } catch (e) {\n      expect(e).toEqual({\n        username: ['signup.feedback.required_username'],\n        // tos: ['signup.feedback.required_tos'],\n        privacy: ['signup.feedback.required_privacy'],\n        password: ['signup.feedback.required_password'],\n        recovery_email: ['signup.feedback.invalid_recovery_email'],\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/SignupForm/form-validator.ts",
    "content": "import Schema, { RuleItem, Rules } from 'async-validator';\nimport usernameDescriptor, {\n  ERR_MIN_MAX,\n  ERR_INVALID_CHARACTER,\n  ERR_DOTS,\n  ERR_DOUBLE_DOTS,\n} from 'src/services/username-utils/username-validity';\nimport { checkAvailability } from 'src/services/username-utils/username-availability';\n\nexport const ERR_REQUIRED_TOS = 'ERR_REQUIRED_TOS';\nexport const ERR_REQUIRED_PRIVACY = 'ERR_REQUIRED_PRIVACY';\nexport const ERR_INVALID_GLOBAL = 'ERR_INVALID_GLOBAL';\nexport const ERR_REQUIRED_USERNAME = 'ERR_REQUIRED_USERNAME';\nexport const ERR_REQUIRED_PASSWORD = 'ERR_REQUIRED_PASSWORD';\nexport const ERR_UNAVAILABLE_USERNAME = 'ERR_UNAVAILABLE_USERNAME';\nexport const ERR_INVALID_RECOVERY_EMAIL = 'ERR_INVALID_RECOVERY_EMAIL';\nexport const ERR_REQUIRED_RECOVERY_EMAIL = 'ERR_REQUIRED_RECOVERY_EMAIL';\nexport const ERR_UNABLE_TO_SIGNUP = 'ERR_UNABLE_TO_SIGNUP';\n\nexport const getLocalizedErrors = (i18n) => ({\n  [ERR_DOTS]: i18n._(\n    /* i18n */ 'signup.feedback.username_starting_ending_dot',\n    null,\n    {\n      message: 'The username cannot start or end with a dot (.)',\n    }\n  ),\n  [ERR_MIN_MAX]: i18n._(/* i18n */ 'signup.feedback.username_length', null, {\n    message: 'The length of the username must be be between 3 and 42',\n  }),\n  [ERR_DOUBLE_DOTS]: i18n._(\n    /* i18n */ 'signup.feedback.username_double_dots',\n    null,\n    {\n      message: 'The username cannot contain two dots (.) next to the other',\n    }\n  ),\n  [ERR_REQUIRED_PRIVACY]: i18n._(\n    /* i18n */ 'signup.feedback.required_privacy',\n    null,\n    {\n      message: 'We need your privacy policy agreement',\n    }\n  ),\n  [ERR_REQUIRED_TOS]: i18n._(/* i18n */ 'signup.feedback.required_tos', null, {\n    message: 'We need your terms and conditions agreement',\n  }),\n  [ERR_INVALID_GLOBAL]: i18n._(/* i18n */ 'signup.feedback.invalid', null, {\n    message: 'Credentials are invalid',\n  }),\n  [ERR_REQUIRED_USERNAME]: i18n._(\n    /* i18n */ 'signup.feedback.required_username',\n    null,\n    {\n      message: 'A username is required',\n    }\n  ),\n  [ERR_INVALID_CHARACTER]: i18n._(\n    /* i18n */ 'signup.feedback.username_invalid_characters',\n    { 0: '\"@`:;<>[]\\\\' },\n    {\n      message:\n        'The username cannot contain some special characters like {0} and space',\n    }\n  ),\n  [ERR_REQUIRED_PASSWORD]: i18n._(\n    /* i18n */ 'signup.feedback.required_password',\n    null,\n    {\n      message: 'A password is required',\n    }\n  ),\n  [ERR_UNAVAILABLE_USERNAME]: i18n._(\n    /* i18n */ 'signup.feedback.unavailable_username',\n    null,\n    { message: 'We are sorry, this username is not available' }\n  ),\n  [ERR_INVALID_RECOVERY_EMAIL]: i18n._(\n    /* i18n */ 'signup.feedback.invalid_recovery_email',\n    null,\n    { message: 'The email should be valid' }\n  ),\n  [ERR_REQUIRED_RECOVERY_EMAIL]: i18n._(\n    /* i18n */ 'signup.feedback.required_recovery_email',\n    null,\n    { message: 'A backup email is required' }\n  ),\n  [ERR_UNABLE_TO_SIGNUP]: i18n._(\n    /* i18n */ 'signup.feedback.unable_to_signup',\n    null,\n    {\n      message: 'Unable to signup. Please retry or contact an administrator.',\n    }\n  ),\n});\n\nconst descriptor: {\n  username: RuleItem[];\n  privacy: RuleItem[];\n  recovery_email: RuleItem[];\n  password: RuleItem[];\n} = {\n  username: [\n    ...usernameDescriptor.username,\n    { type: 'string', required: true, message: ERR_REQUIRED_USERNAME },\n  ],\n  // Alpha: hide TOS checkbox\n  // tos: [\n  //   (rule, value, callback) => {\n  //     if (value !== true) {\n  //       return callback({ message: ERR_REQUIRED_TOS });\n  //     }\n  //\n  //     return callback();\n  //   },\n  // ],\n  privacy: [\n    // @ts-ignore: outdated lib?\n    (rule, value, callback) => {\n      if (value !== true) {\n        return callback({ message: ERR_REQUIRED_PRIVACY });\n      }\n\n      return callback();\n    },\n  ],\n  recovery_email: [\n    { type: 'string', required: true, message: ERR_REQUIRED_RECOVERY_EMAIL },\n    { type: 'email', message: ERR_INVALID_RECOVERY_EMAIL },\n  ],\n  password: [\n    { type: 'string', required: true, message: ERR_REQUIRED_PASSWORD },\n  ],\n};\n\nconst usernameAvailabilityDescriptor = {\n  username: [\n    ...descriptor.username,\n    async (rule, value, callback) => {\n      if (!value) {\n        callback();\n\n        return undefined;\n      }\n      const result = await checkAvailability(value);\n      if (result === true) {\n        return callback();\n      }\n\n      return callback({ message: ERR_UNAVAILABLE_USERNAME });\n    },\n  ],\n};\n\nconst makeDescriptor = (type: string) => {\n  switch (type) {\n    case 'full':\n      return {\n        ...descriptor,\n        ...usernameAvailabilityDescriptor,\n      };\n    case 'usernameAvailability':\n      return usernameAvailabilityDescriptor;\n    default:\n    case 'username':\n      return usernameDescriptor;\n  }\n};\n\nconst extractErrors = (fieldsErrors, i18n) => {\n  const localizedErrors = getLocalizedErrors(i18n);\n\n  return Object.keys(fieldsErrors).reduce(\n    (prev, fieldname) => ({\n      ...prev,\n      [fieldname]: fieldsErrors[fieldname].map(\n        (error) => localizedErrors[error.message] || error.message\n      ),\n    }),\n    {}\n  );\n};\n\nexport const validate = (formValues, i18n, descriptorType = 'username') =>\n  new Promise((resolve, reject) => {\n    const validator = new Schema(makeDescriptor(descriptorType) as Rules);\n    // @ts-ignore: outdated lib?\n    validator.validate(formValues, (errors, fields) => {\n      if (errors) {\n        return reject(extractErrors(fields, i18n));\n      }\n\n      return resolve(true);\n    });\n  });\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/SignupForm/index.spec.tsx",
    "content": "import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { rest } from 'msw';\nimport * as React from 'react';\nimport { AllProviders } from 'test/providers';\nimport { server } from 'test/server';\nimport SignupForm from '.';\n\ndescribe('scene - Signup', () => {\n  it('render', () => {\n    render(<SignupForm />, { wrapper: AllProviders });\n\n    expect(\n      screen.getByRole('textbox', { name: 'Username:' })\n    ).toBeInTheDocument();\n    expect(screen.getByLabelText('Password:')).toBeInTheDocument();\n    expect(\n      screen.getByRole('textbox', { name: /Rescue email address:/ })\n    ).toBeInTheDocument();\n    expect(\n      screen.getByRole('checkbox', { name: 'I understand and agree' })\n    ).toBeInTheDocument();\n  });\n\n  it('handle username usernameAvailability not available', async () => {\n    server.use(\n      rest.get<any, { username: string }>(\n        '/api/v2/username/isAvailable',\n        async (req, res, ctx) =>\n          res(\n            ctx.json({ available: false, username: req.params.username }),\n            ctx.status(200)\n          )\n      )\n    );\n\n    render(<SignupForm />, { wrapper: AllProviders });\n\n    expect(\n      screen.getByRole('textbox', { name: 'Username:' })\n    ).toBeInTheDocument();\n    await userEvent.type(\n      screen.getByRole('textbox', { name: 'Username:' }),\n      'exists'\n    );\n\n    // Force blur field\n    await userEvent.click(screen.getByLabelText('Password:'));\n\n    expect(\n      await screen.findByText('We are sorry, this username is not available')\n    ).toBeInTheDocument();\n  });\n\n  it('handle username usernameAvailability errors', async () => {\n    server.use(\n      rest.get<any, { username: string }>(\n        '/api/v2/username/isAvailable',\n        async (req, res, ctx) =>\n          res(ctx.text('Unexpected error occured'), ctx.status(500))\n      )\n    );\n\n    render(<SignupForm />, { wrapper: AllProviders });\n\n    expect(\n      screen.getByRole('textbox', { name: 'Username:' })\n    ).toBeInTheDocument();\n    await userEvent.type(\n      screen.getByRole('textbox', { name: 'Username:' }),\n      'exists'\n    );\n\n    // Force blur field\n    await userEvent.click(screen.getByLabelText('Password:'));\n\n    expect(\n      await screen.findByText('Request failed with status code 500')\n    ).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/SignupForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, useLingui } from '@lingui/react';\nimport { Link, useHistory } from 'react-router-dom';\nimport type ZxcvbnFunc from 'zxcvbn';\nimport {\n  Button,\n  CheckboxFieldGroup,\n  FieldErrors,\n  FormColumn,\n  FormGrid,\n  FormRow,\n  Label,\n  PasswordStrength,\n  Spinner,\n  Subtitle,\n  TextBlock,\n  TextFieldGroup,\n} from 'src/components';\nimport { useSettings } from 'src/modules/settings';\nimport { getConfig } from 'src/services/config';\nimport { getClientDevice } from 'src/modules/device';\n\nimport { signup } from '../../services/signup';\nimport {\n  validate,\n  getLocalizedErrors,\n  ERR_UNABLE_TO_SIGNUP,\n} from './form-validator';\nimport PiwikModal from './components/PiwikModal';\nimport './style.scss';\n\nexport const USER_IDENTITIES_ROUTE = '/user/identities';\n\nfunction Signup() {\n  const { i18n } = useLingui();\n  // totally useless: settings aren't fetch until authenticated\n  const settings = useSettings();\n  const { push } = useHistory();\n  const clientDevice = getClientDevice();\n\n  const [fieldErrors, setFieldErrors] = React.useState<{\n    global?: string[];\n    username?: string[];\n    password?: string[];\n    tos?: string[];\n    privacy?: string[];\n    recovery_email?: string[];\n  }>({});\n  const [isValidating, setIsValidating] = React.useState(false);\n\n  const resetErrorsState = (fieldname) => {\n    setFieldErrors((prevState) => ({\n      ...prevState,\n      [fieldname]: [],\n    }));\n  };\n\n  const onUsernameChanged = (username) => {\n    if (username.length === 0) {\n      resetErrorsState('username');\n    }\n\n    if (username.length >= 3) {\n      resetErrorsState('username');\n      validate({ username }, i18n, 'username').catch((errors) => {\n        setFieldErrors((prev) => ({ ...prev, ...errors }));\n      });\n    }\n  };\n\n  const [isModalOpen, setIsModalOpen] = React.useState(false);\n  const [passwordStrength, setPasswordStrength] = React.useState<void | number>(\n    undefined\n  );\n  const [username, setUsername] = React.useState('');\n  const [password, setPassword] = React.useState('');\n  const [tos, setTos] = React.useState(false);\n  const [privacy, setPrivacy] = React.useState(false);\n  const [recovery_email, setRecoveryEmail] = React.useState('');\n\n  const zxcvbn = React.useRef<typeof ZxcvbnFunc>();\n\n  React.useEffect(() => {\n    import(/* webpackChunkName: \"zxcvbn\" */ 'zxcvbn').then(\n      ({ default: loaded }) => {\n        zxcvbn.current = loaded;\n      }\n    );\n  }, []);\n\n  const handleOpenModal = () => setIsModalOpen(true);\n  const handleCloseModal = () => setIsModalOpen(false);\n\n  const calcPasswordStrength = (value: string) => {\n    if (!value.length || !zxcvbn.current) {\n      setPasswordStrength(undefined);\n\n      return;\n    }\n\n    const { score } = zxcvbn.current(value);\n    setPasswordStrength(score);\n  };\n\n  const handleInputChange = (event) => {\n    const { name, value: inputValue, type, checked } = event.target;\n    const value = type === 'checkbox' ? checked : inputValue;\n    switch (name) {\n      case 'username':\n        setUsername(value);\n        onUsernameChanged(value);\n        break;\n      case 'password':\n        setPassword(value);\n        calcPasswordStrength(value);\n        break;\n      case 'tos':\n        setTos(value);\n        break;\n      case 'privacy':\n        setPrivacy(value);\n        break;\n      case 'recovery_email':\n        setRecoveryEmail(value);\n        break;\n      default:\n        break;\n    }\n  };\n\n  const handleInputBlur = async (event) => {\n    const { name, value } = event.target;\n\n    if (name !== 'username') {\n      return;\n    }\n\n    if (value.length === 0) {\n      resetErrorsState('username');\n\n      return;\n    }\n\n    try {\n      await validate({ username: value }, i18n, 'usernameAvailability');\n    } catch (errors) {\n      setFieldErrors((prev) => ({ ...prev, ...errors }));\n    }\n  };\n\n  const handleSubmit = async (ev) => {\n    ev.preventDefault();\n    const formValues = {\n      username,\n      password,\n      tos,\n      privacy,\n      recovery_email,\n    };\n\n    try {\n      setIsValidating(true);\n      await validate(formValues, i18n, 'full');\n    } catch (errors) {\n      setFieldErrors(errors);\n      setIsValidating(false);\n\n      return undefined;\n    }\n\n    try {\n      await signup({\n        ...formValues,\n        device: clientDevice.getPublic(),\n        settings,\n      });\n\n      return push(USER_IDENTITIES_ROUTE);\n    } catch (err) {\n      const localizedErrors = getLocalizedErrors(i18n);\n\n      setFieldErrors((prev) => ({\n        ...prev,\n        global: [localizedErrors[ERR_UNABLE_TO_SIGNUP]],\n      }));\n      setIsValidating(false);\n\n      return undefined;\n    }\n  };\n\n  const { hostname } = getConfig() || {};\n\n  return (\n    <div className=\"s-signup\">\n      <FormGrid className=\"s-signup__form\">\n        <form method=\"post\" name=\"ac_form\" onSubmit={handleSubmit}>\n          {fieldErrors.global && fieldErrors.global.length !== 0 && (\n            <FormRow>\n              <FormColumn rightSpace={false} bottomSpace>\n                <FieldErrors\n                  className=\"s-signup__global-errors\"\n                  errors={fieldErrors.global}\n                />\n              </FormColumn>\n            </FormRow>\n          )}\n          <FormRow>\n            <FormColumn rightSpace={false} bottomSpace>\n              <TextFieldGroup\n                id=\"signup_username\"\n                inputProps={{\n                  name: 'username',\n                  placeholder: i18n._(\n                    /* i18n */ 'signup.form.username.placeholder',\n                    undefined,\n                    { message: 'username' }\n                  ),\n                  value: username,\n                  onChange: handleInputChange,\n                  onBlur: handleInputBlur,\n                  expanded: true,\n                }}\n                label={i18n._(\n                  /* i18n */ 'signup.form.username.label',\n                  undefined,\n                  {\n                    message: 'Username',\n                  }\n                )}\n                errors={fieldErrors.username}\n              />\n              <TextBlock className=\"s-signup__user\">\n                <span className=\"s-signup__username\">{username}</span>@\n                {hostname}\n              </TextBlock>\n            </FormColumn>\n          </FormRow>\n          <FormRow>\n            <FormColumn rightSpace={false} bottomSpace>\n              <TextFieldGroup\n                id=\"signup_password\"\n                inputProps={{\n                  name: 'password',\n                  placeholder: i18n._(\n                    /* i18n */ 'signup.form.password.placeholder',\n                    undefined,\n                    { message: 'password' }\n                  ),\n                  type: 'password',\n                  value: password,\n                  onChange: handleInputChange,\n                  onBlur: handleInputBlur,\n                  expanded: true,\n                }}\n                label={i18n._(\n                  /* i18n */ 'signup.form.password.label',\n                  undefined,\n                  {\n                    message: 'Password',\n                  }\n                )}\n                errors={fieldErrors.password}\n              />\n            </FormColumn>\n            {passwordStrength !== undefined && (\n              <FormColumn rightSpace={false} bottomSpace>\n                <PasswordStrength strength={passwordStrength} />\n              </FormColumn>\n            )}\n          </FormRow>\n          <FormRow>\n            <FormColumn rightSpace={false} bottomSpace>\n              <TextFieldGroup\n                id=\"signup_recovery_email\"\n                inputProps={{\n                  name: 'recovery_email',\n                  placeholder: i18n._(\n                    /* i18n */ 'signup.form.invitation_email.placeholder',\n                    undefined,\n                    { message: 'example@domain.tld' }\n                  ),\n                  value: recovery_email,\n                  onChange: handleInputChange,\n                  onBlur: handleInputBlur,\n                  theme: 'contrasted',\n                  expanded: true,\n                }}\n                // Alpha: label \"recovery email\" replaced by \"invitation email\"\n                // label={\n                // i18n._(/* i18n */ 'signup.form.recovery_email.label',\n                //  undefined, { message: 'Backup email address' })\n                // }\n                // placeholder={i18n._(/* i18n */ 'signup.form.recovery_email.placeholder', null, { message: '' })}\n                label={i18n._(\n                  /* i18n */ 'signup.form.invitation_email.label',\n                  undefined,\n                  {\n                    message: 'Invitation email:',\n                  }\n                )}\n                errors={fieldErrors.recovery_email}\n              />\n              <Label\n                htmlFor=\"signup_recovery_email\"\n                className=\"s-signup__recovery-label\"\n              >\n                <Trans\n                  id=\"signup.form.invitation_email.tip\"\n                  message=\"Please fill with the email provided when you requested an invitation.\"\n                />\n              </Label>\n            </FormColumn>\n          </FormRow>\n          {/* Alpha: hide TOS checkbox\n              <FormRow>\n                <FormColumn rightSpace={false} bottomSpace>\n                  <CheckboxFieldGroup\n                    id=\"signup_tos\"\n                    className=\"s-signup__tos-checkbox\"\n              label={i18n._(/* i18n  'signup.form.tos.label',\n                null, { message: 'I agree Terms and conditions' })}\n                    name=\"tos\"\n                    checked={this.state.formValues.tos}\n                    errors={errors.tos}\n                    onChange={this.handleInputChange}\n                  />\n                </FormColumn>\n              </FormRow>\n              */}\n          <FormRow>\n            <FormColumn\n              rightSpace={false}\n              className=\"s-signup__privacy\"\n              bottomSpace\n            >\n              <Subtitle>\n                <Trans\n                  id=\"signup.form.privacy.title\"\n                  message=\"Privacy policy\"\n                />\n              </Subtitle>\n              <p className=\"s-signup__privacy-text\">\n                <Trans\n                  id=\"signup.form.privacy.intro\"\n                  message=\"Throughout the development phase, we collect some data (but no more than the NSA).\"\n                />{' '}\n                <Button\n                  className=\"s-signup__privacy-link\"\n                  onClick={handleOpenModal}\n                  display=\"inline\"\n                >\n                  <Trans\n                    id=\"signup.form.privacy.more_info\"\n                    message=\"More info\"\n                  />\n                </Button>\n              </p>\n              <PiwikModal\n                handleCloseModal={handleCloseModal}\n                isModalOpen={isModalOpen}\n              />\n              <CheckboxFieldGroup\n                id=\"signup_privacy\"\n                className=\"s-signup__privacy-checkbox\"\n                label={i18n._(\n                  /* i18n */ 'signup.form.privacy.checkbox.label',\n                  undefined,\n                  {\n                    message: 'I understand and agree',\n                  }\n                )}\n                name=\"privacy\"\n                checked={privacy}\n                errors={fieldErrors.privacy}\n                onChange={handleInputChange}\n              />\n            </FormColumn>\n          </FormRow>\n          <FormRow>\n            <FormColumn\n              rightSpace={false}\n              className=\"s-signup__action\"\n              bottomSpace\n            >\n              <Button\n                type=\"submit\"\n                display=\"expanded\"\n                shape=\"plain\"\n                disabled={isValidating}\n                icon={\n                  isValidating ? (\n                    <Spinner\n                      svgTitleId=\"signup-spinner\"\n                      isLoading\n                      display=\"inline\"\n                      theme=\"bright\"\n                    />\n                  ) : undefined\n                }\n              >\n                <Trans id=\"signup.action.create\" message=\"Create\" />\n              </Button>\n            </FormColumn>\n          </FormRow>\n          <FormRow>\n            <FormColumn rightSpace={false} className=\"s-signup__link\">\n              <Link to=\"/auth/signin\">\n                <Trans\n                  id=\"signup.go_signin\"\n                  message=\"I already have an account\"\n                />\n              </Link>\n            </FormColumn>\n          </FormRow>\n        </form>\n      </FormGrid>\n    </div>\n  );\n}\n\nexport default Signup;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/SignupForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../../styles/object/o-callout';\n@import '../../../../styles/object/o-clickable';\n\n.s-signup {\n  &__user {\n    color: $co-color__fg__text--high;\n    font-size: $co-font__size--small;\n    text-align: right;\n  }\n\n  &__username {\n    color: $co-color__fg__text--low;\n  }\n\n  &__recovery-label {\n    font-size: $co-font__size--small;\n  }\n\n  &__action {\n    line-height: $co-component__height;\n  }\n\n  &__modal {\n    width: 100%;\n    max-width: map_get($co-form__width, 'xlarge');\n    max-height: 100%;\n    overflow: auto;\n  }\n\n  &__privacy {\n    @include o-callout-base;\n    @include o-callout--color($co-color__info);\n  }\n\n  &__privacy-checkbox {\n    margin-top: $co-margin;\n  }\n\n  &__link {\n    text-align: center;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/UserInfo/UserInfo.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen, waitFor } from '@testing-library/react';\nimport { rest } from 'msw';\nimport { AllProviders } from 'test/providers';\nimport { server } from 'test/server';\nimport { generateUser } from 'test/fixtures/user';\nimport { settings } from 'test/msw-handlers/settings';\nimport WithUser from '../WithUser';\nimport UserInfo from './index';\n\njest.mock('src/modules/user/services/isAuthenticated', () => ({\n  isAuthenticated: () => true,\n}));\n\n// XXX: use a hook instead\nfunction Comp() {\n  return (\n    <WithUser\n      // @ts-ignore\n      render={(user) => (user ? <UserInfo user={user} /> : 'loading')}\n    />\n  );\n}\n\ndescribe('component UserInfo', () => {\n  const user = generateUser();\n  beforeEach(() => {\n    server.use(\n      rest.get('/api/v1/me', (req, res, ctx) =>\n        res(ctx.json(user), ctx.status(200))\n      ),\n      rest.get('/api/v1/settings', (req, res, ctx) =>\n        res(ctx.json(settings), ctx.status(200))\n      )\n    );\n  });\n\n  it('render', async () => {\n    render(<Comp />, {\n      wrapper: AllProviders,\n    });\n\n    // TODO: rm (after using hooks)\n    //  Force re-render due to With<Thing> fetching\n    await waitFor(() => screen.findByText('loading'));\n    await waitFor(() => screen.findByText(user.name || 'fail'));\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/UserInfo/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators } from 'redux';\nimport { connect } from 'react-redux';\nimport { userSelector } from '../../selectors/userSelector';\nimport Presenter from './presenter';\nimport { requestLocalIdentities } from '../../../../store/modules/local-identity';\n\nconst mapStateToProps = createSelector([userSelector], (user) => ({\n  user,\n}));\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators({ requestLocalIdentities }, dispatch);\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/UserInfo/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { ContactAvatarLetter } from '../../../avatar';\nimport { WithSettings } from '../../../settings';\nimport './style.scss';\n\nclass UserInfo extends PureComponent {\n  static propTypes = {\n    user: PropTypes.shape({}),\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    user: {},\n    className: undefined,\n  };\n\n  renderAvatar = () => {\n    const { user } = this.props;\n    const contact = user && user.contact;\n\n    return (\n      contact && (\n        <WithSettings\n          render={(settings) => {\n            const format = settings.contact_display_format;\n\n            return (\n              <ContactAvatarLetter\n                contact={contact}\n                contactDisplayFormat={format}\n              />\n            );\n          }}\n        />\n      )\n    );\n  };\n\n  render() {\n    const { user, className } = this.props;\n\n    return (\n      <div className={classnames('m-user-info', className)}>\n        <div className=\"m-user-info__avatar\">{this.renderAvatar()}</div>\n        <div className=\"m-user-info__username\">{user.name}</div>\n      </div>\n    );\n  }\n}\n\nexport default UserInfo;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/UserInfo/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-user-info {\n  @include flex-grid-row;\n\n  align-items: center;\n\n  &__avatar {\n    @include flex-grid-column(shrink);\n  }\n\n  &__username {\n    @include flex-grid-column;\n\n    overflow: hidden;\n    color: $co-color__fg__text;\n    font-weight: 600;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/UserMenu/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { connect } from 'react-redux';\nimport { compose, bindActionCreators } from 'redux';\nimport Presenter from './presenter';\nimport { getUser } from '../../actions/getUser';\nimport { userSelector } from '../../selectors/userSelector';\n\nconst mapStateToProps = createSelector([userSelector], (user) => ({ user }));\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators({ getUser }, dispatch);\n\nexport default compose(connect(mapStateToProps, mapDispatchToProps))(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/UserMenu/next-feature-button.scss",
    "content": "@import '../../../../styles/common';\n\n.m-next-feature-button {\n  background-color: $co-color__next-feature;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/UserMenu/presenter.jsx",
    "content": "import React, { Component, createRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans, withI18n } from '@lingui/react';\nimport UserInfo from '../UserInfo';\nimport {\n  Link,\n  Button,\n  Icon,\n  Dropdown,\n  withDropdownControl,\n  VerticalMenu,\n  VerticalMenuItem,\n  Separator,\n} from '../../../../components';\n// some circular reference breaks unit tests when importing directly from the module\nimport { withNotification } from '../../../userNotify/hoc/withNotification';\nimport './style.scss';\nimport './next-feature-button.scss';\n\nconst DropdownControl = withDropdownControl(Button);\n\n@withI18n()\n@withNotification()\nclass Presenter extends Component {\n  static propTypes = {\n    user: PropTypes.shape({}),\n    getUser: PropTypes.func.isRequired,\n    notifyInfo: PropTypes.func.isRequired,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {\n    user: {},\n  };\n\n  state = {\n    isDropdownOpen: false,\n  };\n\n  dropdownControlRef = createRef();\n\n  componentDidMount() {\n    this.props.getUser();\n  }\n\n  handleDropdownToggle = (isDropdownOpen) => {\n    this.setState({ isDropdownOpen });\n  };\n\n  handleClickNewFeature = () => {\n    const { notifyInfo, i18n } = this.props;\n\n    notifyInfo({\n      message: i18n._(/* i18n */ 'next-feature.generic', null, {\n        message:\n          'This feature is not available for now but is planned to be added to Caliopen.',\n      }),\n    });\n  };\n\n  render() {\n    const { user } = this.props;\n\n    return (\n      <div className=\"m-user-menu\">\n        <DropdownControl\n          ref={this.dropdownControlRef}\n          icon=\"user\"\n          display=\"inline-block\"\n        >\n          <span className=\"m-user-menu__button-label\">{user && user.name}</span>\n          &nbsp;\n          <Icon type={this.state.isDropdownOpen ? 'caret-up' : 'caret-down'} />\n        </DropdownControl>\n        <Dropdown\n          dropdownControlRef={this.dropdownControlRef}\n          alignRight\n          isMenu\n          hasTriangle\n          closeOnClick=\"all\"\n          onToggle={this.handleDropdownToggle}\n          displayFirstLayer\n        >\n          <VerticalMenu>\n            <VerticalMenuItem>\n              <UserInfo className=\"m-user-menu__user-info\" />\n            </VerticalMenuItem>\n            <Separator />\n            <VerticalMenuItem>\n              <Link to=\"/contacts\" display=\"expanded\" button>\n                <Trans id=\"header.menu.contacts\" message=\"Contacts\" />\n              </Link>\n            </VerticalMenuItem>\n            <VerticalMenuItem>\n              <Link to=\"/user/profile\" display=\"expanded\" button>\n                <Trans id=\"header.menu.account\" message=\"Account\" />\n              </Link>\n            </VerticalMenuItem>\n            <VerticalMenuItem>\n              <Link to=\"/settings/application\" display=\"expanded\" button>\n                <Trans id=\"header.menu.settings\" message=\"Settings\" />\n              </Link>\n            </VerticalMenuItem>\n            <VerticalMenuItem>\n              <Button\n                className=\"m-next-feature-button\"\n                center={false}\n                display=\"expanded\"\n                onClick={this.handleClickNewFeature}\n              >\n                <Trans id=\"header.menu.agenda\" message=\"Agenda\" />\n              </Button>\n            </VerticalMenuItem>\n            <VerticalMenuItem>\n              <Button\n                className=\"m-next-feature-button\"\n                center={false}\n                display=\"expanded\"\n                onClick={this.handleClickNewFeature}\n              >\n                <Trans id=\"header.menu.files\" message=\"Files\" />\n              </Button>\n            </VerticalMenuItem>\n            <VerticalMenuItem>\n              {user && (\n                <Link href=\"/auth/signout\" button display=\"expanded\">\n                  <Trans id=\"header.menu.signout\" message=\"Signout\" />\n                </Link>\n              )}\n              {!user && (\n                <Link to=\"/auth/signin\" button display=\"expanded\">\n                  <Trans id=\"header.menu.signin\" message=\"Signin\" />\n                </Link>\n              )}\n            </VerticalMenuItem>\n          </VerticalMenu>\n        </Dropdown>\n      </div>\n    );\n  }\n}\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/UserMenu/presenter.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport 'test/unit/lingui-react';\nimport Presenter from './presenter';\n\njest.mock('../../../../modules/settings', () => ({\n  WithSettings: ({ render }) => render({}, false),\n}));\njest.mock('../../../../modules/userNotify', () => ({\n  withNotification: () => (C) => C,\n}));\njest.mock(\n  '../../../../components/PageTitle',\n  () =>\n    function () {\n      return null;\n    }\n);\njest.mock('../../../../modules/userNotify/hoc/withNotification', () => ({\n  withNotification: () => (noop) => noop,\n}));\n\ndescribe('component UserMenu', () => {\n  it('render', () => {\n    const props = {\n      getUser: () => {\n        // noop\n      },\n      notifyInfo: () => {\n        // noop\n      },\n    };\n    const comp = shallow(<Presenter {...props} />);\n    expect(comp.dive().find('VerticalMenu').length).toEqual(1);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/UserMenu/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-user-menu {\n  &__user-info {\n    margin-top: $co-component__spacing;\n  }\n\n  &__button-label {\n    display: none;\n\n    @include breakpoint(medium) {\n      display: inline;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/WithUser/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport Presenter from './presenter';\nimport { getUser } from '../../actions/getUser';\nimport { stateSelector } from '../../store/selectors';\n\nconst mapStateToProps = createSelector(\n  [stateSelector],\n  ({ user, isFetching, didInvalidate, didLostAuth }) => ({\n    user,\n    isFetching,\n    didInvalidate,\n    didLostAuth,\n  })\n);\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      getUser,\n    },\n    dispatch\n  );\n\nexport default compose(connect(mapStateToProps, mapDispatchToProps))(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/components/WithUser/presenter.jsx",
    "content": "import { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { isAuthenticated } from '../..';\n\nclass WithUser extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n    getUser: PropTypes.func.isRequired,\n    user: PropTypes.shape({}),\n    didInvalidate: PropTypes.bool.isRequired,\n    didLostAuth: PropTypes.bool.isRequired,\n    isFetching: PropTypes.bool.isRequired,\n    synced: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    user: undefined,\n    synced: false,\n  };\n\n  componentDidMount() {\n    const { user, getUser, didInvalidate, isFetching } = this.props;\n\n    if ((!user || didInvalidate) && !isFetching && isAuthenticated()) {\n      getUser();\n    }\n  }\n\n  render() {\n    const { render, user, isFetching, synced, didLostAuth } = this.props;\n\n    if (synced && !user) {\n      return null;\n    }\n\n    return render(user, isFetching, didLostAuth);\n  }\n}\n\nexport default WithUser;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/hoc/withUser.jsx",
    "content": "import React from 'react';\nimport WithUser from '../components/WithUser';\n\nexport const withUser =\n  ({ namespace = 'userState' } = {}) =>\n  (WrappedComp) => {\n    function WithUserHoc(props) {\n      return (\n        <WithUser\n          render={(user, isFetching, didLostAuth) => {\n            const injected = {\n              [namespace]: { user, isFetching, didLostAuth },\n            };\n\n            return <WrappedComp {...injected} {...props} />;\n          }}\n        />\n      );\n    }\n    WithUserHoc.displayName = `(${\n      WrappedComp.displayName || WrappedComp.name || 'Component'\n    })`;\n\n    return WithUserHoc;\n  };\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/hooks/useUser.ts",
    "content": "import * as React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { requestUser } from '../store/reducer';\nimport { shouldFetchSelector, stateSelector } from '../store/selectors';\nimport { isAuthenticated } from '../services/isAuthenticated';\n\nexport function useUser() {\n  const dispatch = useDispatch();\n  const { user, didLostAuth, status, initialized } = useSelector(stateSelector);\n  const shouldFetch = useSelector(shouldFetchSelector);\n  const authenticated = isAuthenticated();\n\n  React.useEffect(() => {\n    if (shouldFetch && authenticated) {\n      dispatch(requestUser());\n    }\n  }, [shouldFetch, dispatch, authenticated]);\n\n  return { initialized, status, didLostAuth, user };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/index.ts",
    "content": "export * from './actions/getUser';\nexport * from './hoc/withUser';\nexport * from './selectors/userSelector';\nexport * from './services/usernameNormalizer';\nexport * from './services/signup';\nexport * from './services/isAuthenticated';\nexport * from './services/deleteUser';\nexport * from './hooks/useUser';\nexport { default as SigninForm } from './components/SigninForm';\nexport { default as SignupForm } from './components/SignupForm';\nexport { default as UserMenu } from './components/UserMenu';\nexport { default as WithUser } from './components/WithUser';\nexport * as store from './store';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/selectors/userSelector.ts",
    "content": "import { RootState } from 'src/store/reducer';\n\nexport const userSelector = (state: RootState) => state.user.user;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/services/deleteUser.js",
    "content": "import getClient from '../../../services/api-client';\n\nexport const deleteUser = async ({ user, password }) => {\n  try {\n    return await getClient().post(`/api/v2/users/${user.user_id}/actions`, {\n      actions: ['delete'],\n      params: { password },\n    });\n  } catch (err) {\n    if (err && err.response && err.response.data && err.response.data.errors) {\n      return Promise.reject(err.response.data.errors);\n    }\n\n    return Promise.reject(err);\n  }\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/services/isAuthenticated/index.js",
    "content": "import cookie from 'cookie';\n\nexport const isAuthenticated = () => {\n  if (BUILD_TARGET === 'server') {\n    return global.user && true;\n  }\n\n  if (BUILD_TARGET === 'browser') {\n    const { 'caliopen.web': token } = cookie.parse(document.cookie);\n\n    return token && true;\n  }\n\n  throw new Error('isAuthenticated does not support the current BUILD_TARGET');\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/services/signup/index.js",
    "content": "import getClient from '../../../../services/api-client';\n\nexport const signup = (params) => getClient().post('/auth/signup', params);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/services/usernameNormalizer/index.js",
    "content": "export const usernameNormalizer = (username) => username.trim();\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/store/index.ts",
    "content": "export * from './reducer';\nexport * from './selectors';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/store/reducer.ts",
    "content": "import {\n  AxiosActionPayload,\n  AxiosActionError,\n  ResourceStatus,\n} from 'src/types';\nimport calcObjectForPatch from 'src/services/api-patch';\nimport { UserPatchPayload, UserPayload } from '../types';\n\nexport const REQUEST_USER = 'co/user/REQUEST_USER';\nexport const REQUEST_USER_SUCCESS = 'co/user/REQUEST_USER_SUCCESS';\nexport const REQUEST_USER_FAIL = 'co/user/REQUEST_USER_FAIL';\nexport const UPDATE_USER = 'co/user/UPDATE_USER';\nexport const INVALIDATE_USER = 'co/user/INVALIDATE_USER';\n\n// Actions --------------------------------------\n\ninterface RequestUserAction {\n  type: typeof REQUEST_USER;\n  payload: AxiosActionPayload;\n}\ninterface RequestUserSuccessAction {\n  type: typeof REQUEST_USER_SUCCESS;\n  payload: {\n    data: UserPayload;\n  };\n}\ninterface RequestUserFailAction {\n  type: typeof REQUEST_USER_FAIL;\n  error: AxiosActionError;\n}\n\ninterface InvalidateAction {\n  type: typeof INVALIDATE_USER;\n  payload: Record<string, unknown>;\n}\n\ninterface UpdateUserAction {\n  type: typeof UPDATE_USER;\n  payload: AxiosActionPayload<undefined, UserPatchPayload>;\n}\n\ntype UserAction =\n  | RequestUserAction\n  | RequestUserSuccessAction\n  | RequestUserFailAction\n  | InvalidateAction;\n//  ---------------------------------------------\n\n// State ----------------------------------------\ninterface UserState {\n  initialized: boolean;\n  status: ResourceStatus;\n  // @deprecated\n  isFetching: boolean;\n  didInvalidate: boolean;\n  // ---\n  didLostAuth: boolean;\n  user: undefined | UserPayload;\n}\n// ----------------------------------------------\n\nexport function requestUser(): RequestUserAction {\n  return {\n    type: REQUEST_USER,\n    payload: {\n      request: {\n        url: '/api/v1/me',\n      },\n    },\n  };\n}\n\nexport function invalidate(): InvalidateAction {\n  return {\n    type: INVALIDATE_USER,\n    payload: {},\n  };\n}\n\nexport function updateUser(\n  user: UserPayload,\n  original: UserPayload\n): UpdateUserAction {\n  const data = calcObjectForPatch(user, original);\n\n  return {\n    type: UPDATE_USER,\n    payload: {\n      request: {\n        method: 'patch',\n        url: `/api/v2/users/${user.user_id}`,\n        data,\n      },\n    },\n  };\n}\n\n// // XXX: unused?\n// export function updateUserContact(contact) {\n//   return (dispatch) => {\n//     dispatch(this.ContactsActions.updateContact(contact));\n//     dispatch(invalidate());\n//   };\n// }\n\nconst initialState: UserState = {\n  initialized: false,\n  status: 'idle',\n  isFetching: false,\n  didInvalidate: false,\n  didLostAuth: false,\n  user: undefined,\n};\n\nexport function reducer(\n  state: UserState = initialState,\n  action: UserAction\n): UserState {\n  switch (action.type) {\n    case REQUEST_USER:\n      return { ...state, status: 'pending', isFetching: true };\n    case REQUEST_USER_FAIL:\n      return {\n        ...state,\n        status: 'rejected',\n        isFetching: false,\n        didLostAuth: action.error.response?.status === 401,\n      };\n    case REQUEST_USER_SUCCESS:\n      return {\n        ...state,\n        initialized: true,\n        status: 'resolved',\n        isFetching: false,\n        didInvalidate: false,\n        user: action.payload.data,\n        didLostAuth: false,\n      };\n    case INVALIDATE_USER:\n      return { ...state, status: 'invalidated', didInvalidate: true };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/store/selectors.ts",
    "content": "import { RootState } from 'src/store/reducer';\n\nexport const stateSelector = (state: RootState) => state.user;\n\nexport const shouldFetchSelector = (state: RootState) =>\n  ['idle', 'invalidated'].includes(stateSelector(state).status);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/user/types.d.ts",
    "content": "import { PatchPayload } from 'src/types';\nimport { Contact } from 'src/modules/contact/types';\nimport { PI, PrivacyFeature } from 'src/modules/pi/types';\n\n// Entity ---------------------------------------\nexport interface UserPayload {\n  contact: Contact;\n  date_insert?: string; // format: date-time\n  family_name?: string;\n  given_name?: string;\n  name?: string;\n  password?: string;\n  params?: Record<string, unknown>;\n  privacy_features?: PrivacyFeature;\n  pi?: PI;\n  user_id: string;\n  recovery_email?: string;\n}\n\nexport type UserPatchPayload = PatchPayload<UserPayload>;\n//  ---------------------------------------------\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/userNotify/actions/notify.js",
    "content": "import {\n  createNotification,\n  NOTIFICATION_TYPE_SUCCESS,\n  NOTIFICATION_TYPE_INFO,\n  NOTIFICATION_TYPE_WARNING,\n  NOTIFICATION_TYPE_ERROR,\n} from 'react-redux-notify';\nimport { settingsSelector } from '../../settings';\n\nconst notify =\n  ({\n    message,\n    duration,\n    type = NOTIFICATION_TYPE_INFO,\n    canDismiss = true,\n    ...opts\n  }) =>\n  (dispatch, getState) => {\n    const {\n      settings: {\n        notification_enabled: notificationEnabled,\n        notification_delay_disappear: notificationDelayDisappear,\n      },\n    } = settingsSelector(getState());\n\n    const customStyles = {\n      // customStyles applied to Notification component\n      'has-close': 'l-notification-center__notification--has-close',\n      'has-close-all': 'l-notification-center__notification--has-close-all',\n      item__message: 'l-notification-center__notification-item-message',\n    };\n\n    if (notificationEnabled) {\n      return dispatch(\n        createNotification({\n          ...opts,\n          message,\n          type,\n          duration:\n            duration >= 0 ? duration : notificationDelayDisappear * 1000,\n          canDismiss,\n          customStyles,\n        })\n      );\n    }\n\n    return undefined;\n  };\n\nexport const notifySuccess = (opts) => (dispatch) =>\n  dispatch(notify({ type: NOTIFICATION_TYPE_SUCCESS, ...opts }));\nexport const notifyInfo = (opts) => (dispatch) =>\n  dispatch(notify({ type: NOTIFICATION_TYPE_INFO, ...opts }));\nexport const notifyWarning = (opts) => (dispatch) =>\n  dispatch(notify({ type: NOTIFICATION_TYPE_WARNING, ...opts }));\nexport const notifyError = (opts) => (dispatch) =>\n  dispatch(notify({ type: NOTIFICATION_TYPE_ERROR, ...opts }));\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/userNotify/hoc/withNotification.jsx",
    "content": "import { bindActionCreators } from 'redux';\nimport { connect } from 'react-redux';\nimport {\n  notifySuccess,\n  notifyInfo,\n  notifyWarning,\n  notifyError,\n} from '../actions/notify';\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      notifySuccess,\n      notifyInfo,\n      notifyWarning,\n      notifyError,\n    },\n    dispatch\n  );\n\n/**\n * @deprecated\n */\nexport const withNotification = () => (Component) =>\n  connect(null, mapDispatchToProps)(Component);\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/userNotify/index.js",
    "content": "export * from './actions/notify';\nexport * from './hoc/withNotification';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/view/actions/requestMessages.js",
    "content": "import { fetchMessages } from '../../message';\n\nexport const requestMessages =\n  ({ view }) =>\n  (dispatch) =>\n    dispatch(fetchMessages(view.getRequestParams()));\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/view/components/WithViewModel/index.jsx",
    "content": "import { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n } from '@lingui/react';\nimport { View } from '../../models/View';\nimport { getConfig } from '../../config';\n\n@withI18n()\nclass WithViewModel extends Component {\n  static propTypes = {\n    render: PropTypes.func.isRequired,\n    viewId: PropTypes.string.isRequired,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  getView = () => {\n    const { i18n, viewId } = this.props;\n    const viewConfig = getConfig(i18n).find((cfg) => cfg.id === viewId);\n\n    if (!viewConfig) {\n      throw new Error(`View not found \"${viewId}\"`);\n    }\n\n    return new View(viewConfig);\n  };\n\n  render() {\n    const { render } = this.props;\n    const view = this.getView();\n\n    return render({\n      view,\n    });\n  }\n}\n\nexport default WithViewModel;\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/view/config.js",
    "content": "export const getConfig = (i18n) => [\n  {\n    id: 'draft',\n    // this can evolve in `conditions[{ propName, values[], test='equal|lower|in…'}]`\n    condition: { propertyName: 'is_draft', value: true },\n    label: i18n._(/* i18n */ 'view.draft.label', null, { message: 'Drafts' }),\n  },\n];\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/view/index.js",
    "content": "export * from './actions/requestMessages';\nexport { default as WithViewModel } from './components/WithViewModel';\nexport * from './models/View';\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/view/models/View.js",
    "content": "export class View {\n  id;\n\n  condition;\n\n  label;\n\n  isFetching = false;\n\n  constructor(props = {}) {\n    Object.assign(this, props);\n  }\n\n  has = ({ message }) => {\n    const { propertyName, value } = this.condition;\n\n    return message && message[propertyName] === value;\n  };\n\n  getRequestParams = () => {\n    const { propertyName, value } = this.condition;\n\n    return {\n      [propertyName]: value,\n    };\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/modules/view/selectors/viewSelector.js",
    "content": "import { createSelector } from 'reselect';\nimport { getModuleStateSelector } from '../../../store/selectors/getModuleStateSelector';\n\nconst viewIdSelector = (state, { viewId }) => {\n  if (!viewId) {\n    throw new Error('viewId must be defined in component properties');\n  }\n\n  return viewId;\n};\n\nexport const viewModelSelector = createSelector(\n  [viewIdSelector, getModuleStateSelector('view')],\n  (viewId, viewState) => viewState[viewId]\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/About/components/StylezedScreenshot/index.tsx",
    "content": "import * as React from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport desktopSrc from './assets/screenshot_desktop.svg';\nimport smartphoneSrc from './assets/screenshot_smartphone.svg';\n\ninterface Props extends withI18nProps {\n  className: string;\n  type: 'smartphone' | 'desktop';\n}\n\nfunction StylezedScreenshot({ className, i18n, type }: Props) {\n  switch (type) {\n    case 'smartphone':\n      return (\n        <img\n          className={className}\n          src={smartphoneSrc}\n          alt={i18n._(/* i18n */ 'screenshot.smartphone', undefined, {\n            message: 'blurry screenshot of Caliopen for smartphone',\n          })}\n        />\n      );\n    default:\n    case 'desktop':\n      return (\n        <img\n          className={className}\n          src={desktopSrc}\n          alt={i18n._(/* i18n */ 'screenshot.desktop', undefined, {\n            message: 'blurry screenshot of Caliopen for desktop',\n          })}\n        />\n      );\n  }\n}\n\nexport default withI18n()(StylezedScreenshot);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/About/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport { AllProviders } from 'test/providers';\nimport About from './index';\n\njest.mock('src/modules/user/services/isAuthenticated', () => ({\n  isAuthenticated: () => false,\n}));\n\ndescribe('scenes > About', () => {\n  it('render', () => {\n    render(<About />, { wrapper: AllProviders });\n\n    expect(screen.getByText('Welcome on Caliopen')).toBeVisible();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/About/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport { Brand, Button, Link, Section } from '../../components';\nimport { SigninForm, SignupForm } from '../../modules/user';\nimport StylezedScreenshot from './components/StylezedScreenshot';\nimport './style.scss';\n\nfunction About(): JSX.Element {\n  const [createFormOpen, setCreateFormOpen] = React.useState(false);\n\n  const handleToggleCreate = () => {\n    setCreateFormOpen((prev) => !prev);\n  };\n\n  return (\n    <div className=\"s-about\">\n      <div className=\"s-about__info\">\n        <div className=\"s-about__section-content\">\n          <h1 className=\"s-about__title\">\n            <Trans id=\"about.page-title\" message=\"Welcome on Caliopen\" />\n          </h1>\n          <Section className=\"s-about__pupose\" hasSeparator>\n            <Trans\n              id=\"about.purpose\"\n              message=\"Caliopen is a private correspondence aggregator (you receive all your emails and private Twitter messages in the same place) that displays a level of confidentiality associated with all your messages.\"\n            />\n          </Section>\n          <div className=\"s-about__screenshot\">\n            <StylezedScreenshot\n              type=\"desktop\"\n              className=\"s-about__screenshot-desktop\"\n            />\n            <StylezedScreenshot\n              type=\"smartphone\"\n              className=\"s-about__screenshot-smartphone\"\n            />\n          </div>\n          <div className=\"s-about__brand\">\n            <Link to=\"/\">\n              <Brand className=\"s-about__brand-logo\" />\n            </Link>\n            <br />\n            <em>Be good.</em>\n            <p>\n              <Trans\n                id=\"about.identity\"\n                message=\"Caliopen is a non-profit organization.\"\n              />\n            </p>\n          </div>\n        </div>\n      </div>\n      <div className=\"s-about__interactions\">\n        <div className=\"s-about__section-content\">\n          <Section className=\"s-about__signin-form\" hasSeparator>\n            <SigninForm />\n          </Section>\n          <Section className=\"s-about__purpose-signup\" hasSeparator>\n            <h2>\n              <Trans\n                id=\"about.purpose-signup.title\"\n                message=\"You still have no account on the Beta*?\"\n              />\n            </h2>\n            <p>\n              <Trans\n                id=\"about.purpose-signup.descr\"\n                message=\"By signup today it will be first step to secure your conversations and have all your communications in the same place, it's easy and free.\"\n              />\n            </p>\n            <Button onClick={handleToggleCreate} shape=\"plain\">\n              <Trans id=\"about.action.toggle-create\" message=\"Create\" />\n            </Button>\n          </Section>\n          <div\n            className={classnames('s-about__signup-form', {\n              's-about__signup-form--opened': createFormOpen,\n            })}\n          >\n            <SignupForm />\n            <p className=\"s-about__signup-beta-descr\">\n              <Trans\n                id=\"about.purpose-signup.beta-descr\"\n                message=\"* Caliopen is currently in beta version, we need your help to make the best messaging and secured system.\"\n              />\n            </p>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default About;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/About/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-about {\n  @include flex-grid-row($size: expand);\n\n  &__info,\n  &__interactions {\n    @include flex-grid-column(12);\n\n    padding-top: map_get($co-margin_, small);\n    padding-bottom: map_get($co-margin_, small);\n\n    @include breakpoint(medium) {\n      @include flex-grid-column(6);\n    }\n  }\n\n  &__section-content {\n    padding: map_get($co-margin_, small);\n    background: $co-color__fg__back;\n  }\n\n  &__title {\n    color: $co-color__primary;\n    text-align: center;\n  }\n\n  &__brand {\n    position: relative;\n    margin-top: map_get($co-margin_, large);\n    text-align: center;\n  }\n\n  &__brand-logo {\n    max-width: 100%;\n    height: 4rem;\n    max-height: initial;\n  }\n\n  &__screenshot {\n    margin-top: map_get($co-margin_, large);\n    text-align: center;\n  }\n\n  &__screenshot-desktop {\n    display: none;\n    width: 100%;\n    max-width: 40rem;\n\n    @include breakpoint(medium) {\n      display: inline-block;\n    }\n  }\n\n  &__screenshot-smartphone {\n    @include breakpoint(medium) {\n      display: none;\n    }\n  }\n\n  &__purpose {\n  }\n\n  &__purpose-signup {\n    margin-top: map_get($co-margin_, large);\n  }\n\n  &__signup-form {\n    max-height: 0;\n\n    // allow animation to happen, it must be higher than the actual height\n    transition: $co-animation__height-transition;\n    overflow: hidden;\n\n    &--opened {\n      max-height: 100rem;\n      padding: map_get($co-form__spacing, 'medium'); // cf. Section's padding\n    }\n  }\n\n  &__signup-beta-descr {\n    font-size: $co-font__size--xsmall;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/ContactSettings/index.js",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/ContactSettings/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Field } from 'redux-form';\nimport {\n  SelectFieldGroup as SelectFieldGroupBase,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\nimport renderReduxField from '../../../../services/renderReduxField';\n\nconst SelectFieldGroup = renderReduxField(SelectFieldGroupBase);\nconst DISPLAY_FORMAT = ['given_name, family_name', 'family_name, given_name'];\nconst DISPLAY_ORDER_BY = ['given_name', 'family_name'];\n\nclass ContactsForm extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {};\n\n  UNSAFE_componentWillMount() {\n    this.initTranslations();\n  }\n\n  getOptionsFromArray = (options) =>\n    options.map((value) => ({\n      value,\n      label: this.i18n[value] || value,\n    }));\n\n  initTranslations() {\n    const { i18n } = this.props;\n    this.i18n = {\n      'given_name, family_name': i18n._(\n        /* i18n */ 'settings.contact.display_format.options.first_last',\n        null,\n        { message: 'Firstname, Lastname' }\n      ),\n      'family_name, given_name': i18n._(\n        /* i18n */ 'settings.contact.display_format.options.last_first',\n        null,\n        { message: 'Lastname, Firstname' }\n      ),\n      given_name: i18n._(\n        /* i18n */ 'settings.contact.display_order_by.options.firstname',\n        null,\n        { message: 'Firstname' }\n      ),\n      family_name: i18n._(\n        /* i18n */ 'settings.contact.display_order_by.options.lastname',\n        null,\n        { message: 'Lastname' }\n      ),\n    };\n  }\n\n  render() {\n    const { i18n } = this.props;\n\n    const displayFormatOptions = this.getOptionsFromArray(DISPLAY_FORMAT);\n    const displayOrderByOptions = this.getOptionsFromArray(DISPLAY_ORDER_BY);\n\n    return (\n      <FormGrid className=\"m-contacts-form\">\n        <FormRow>\n          <FormColumn rightSpace={false} bottomSpace>\n            <Field\n              component={SelectFieldGroup}\n              name=\"contact_display_format\"\n              label={i18n._(\n                /* i18n */ 'settings.contacts.display.label',\n                null,\n                {\n                  message: 'Display',\n                }\n              )}\n              options={displayFormatOptions}\n              expanded\n            />\n          </FormColumn>\n          <FormColumn rightSpace={false}>\n            <Field\n              component={SelectFieldGroup}\n              name=\"contact_display_order\"\n              label={i18n._(/* i18n */ 'settings.contacts.order.label', null, {\n                message: 'Order by',\n              })}\n              options={displayOrderByOptions}\n              expanded\n            />\n          </FormColumn>\n        </FormRow>\n      </FormGrid>\n    );\n  }\n}\n\nexport default ContactsForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/DesktopNotificationSettings/index.js",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/DesktopNotificationSettings/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Icon, Button } from '../../../../components';\nimport {\n  notify,\n  requestPermission,\n  isSupported,\n  PERMISSION_DENIED,\n  PERMISSION_GRANTED,\n} from '../../../../services/browser-notification';\nimport './style.scss';\n\nclass DesktopNotificationSettings extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  UNSAFE_componentWillMount() {\n    this.setState({\n      hasBrowserNotificationPermission: isSupported && Notification.permission,\n    });\n  }\n\n  handleRequestBrowserNotification = async () => {\n    const permission = await requestPermission();\n    this.setState({\n      hasBrowserNotificationPermission: permission,\n    });\n  };\n\n  handleClickTestBrowser = () => {\n    const { i18n } = this.props;\n\n    return notify({\n      message: i18n._(\n        /* i18n */ 'settings.desktop_notification.feedback.enabled',\n        null,\n        {\n          message: 'Desktop notifications are enabled',\n        }\n      ),\n      force: true,\n    });\n  };\n\n  // eslint-disable-next-line\n  renderNoSupport() {\n    return (\n      <div>\n        <Trans\n          id=\"settings.desktop_notification.no_support\"\n          message=\"Notifications are not supported by your browser\"\n        />\n      </div>\n    );\n  }\n\n  renderNotification() {\n    if (this.state.hasBrowserNotificationPermission === PERMISSION_GRANTED) {\n      return (\n        <div>\n          <span className=\"m-desktop-notifications--allowed\">\n            <Icon type=\"check\" />{' '}\n            <Trans\n              id=\"settings.desktop_notification.desktop_notifications_enabled\"\n              message=\"Desktop notifications enabled\"\n            />{' '}\n          </span>{' '}\n          <Button onClick={this.handleClickTestBrowser} display=\"inline\">\n            <Trans\n              id=\"settings.desktop_notification.action.test_desktop_notification\"\n              message=\"Check desktop notifications\"\n            />\n          </Button>\n        </div>\n      );\n    }\n\n    if (this.state.hasBrowserNotificationPermission === PERMISSION_DENIED) {\n      return (\n        <div className=\"m-desktop-notifications--denied\">\n          <Icon type=\"remove\" />{' '}\n          <Trans\n            id=\"settings.desktop_notification.disabled\"\n            message=\"Notifications are disabled, please check your browser settings\"\n          />\n        </div>\n      );\n    }\n\n    return (\n      <div>\n        <Button onClick={this.handleRequestBrowserNotification}>\n          <Trans\n            id=\"settings.desktop_notification.action.request-desktop_notification_permission\"\n            message=\"Enable desktop notifications\"\n          />\n        </Button>\n      </div>\n    );\n  }\n\n  render() {\n    return (\n      <div>\n        {isSupported ? this.renderNotification() : this.renderNoSupport()}\n      </div>\n    );\n  }\n}\n\nexport default DesktopNotificationSettings;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/DesktopNotificationSettings/style.scss",
    "content": "@import '../../../../styles/common';\n\n.m-desktop-notifications {\n  &--allowed {\n    color: map_get($caliopen-palette, 'success');\n  }\n\n  &--denied {\n    color: map_get($caliopen-palette, 'alert');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/InterfaceSettings/index.js",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/InterfaceSettings/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { Field } from 'redux-form';\nimport {\n  SelectFieldGroup as SelectFieldGroupBase,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\nimport renderReduxField from '../../../../services/renderReduxField';\nimport { AVAILABLE_LOCALES } from '../../../../modules/i18n';\n\nconst languages = {\n  fr_FR: 'Français',\n  en_US: 'English',\n  de_DE: 'Deutsch',\n};\nconst SelectFieldGroup = renderReduxField(SelectFieldGroupBase);\n\nclass InterfaceSettings extends PureComponent {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  getOptionsFromArray = (options) =>\n    options.map((value) => ({\n      value,\n      label: languages[value] || value,\n    }));\n\n  render() {\n    const { i18n } = this.props;\n    const languageOptions = this.getOptionsFromArray(AVAILABLE_LOCALES);\n\n    return (\n      <FormGrid className=\"m-interface-form\">\n        <FormRow>\n          <FormColumn rightSpace={false}>\n            <Field\n              component={SelectFieldGroup}\n              name=\"default_locale\"\n              label={i18n._(\n                /* i18n */ 'settings.interface.language.label',\n                null,\n                {\n                  message: 'Language',\n                }\n              )}\n              options={languageOptions}\n              expanded\n            />\n          </FormColumn>\n        </FormRow>\n      </FormGrid>\n    );\n  }\n}\n\nexport default InterfaceSettings;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/MessageSettings/index.js",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/MessageSettings/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { Field } from 'redux-form';\nimport {\n  SelectFieldGroup as SelectFieldGroupBase,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\nimport renderReduxField from '../../../../services/renderReduxField';\n\nconst SelectFieldGroup = renderReduxField(SelectFieldGroupBase);\n\nconst DISPLAY_FORMATS = ['rich_text', 'plain_text'];\n\nclass PresentationForm extends PureComponent {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  UNSAFE_componentWillMount() {\n    this.initTranslations();\n  }\n\n  getOptionsFromArray = (options) =>\n    options.map((value) => ({\n      value,\n      label: this.i18n[value] || value,\n    }));\n\n  initTranslations() {\n    const { i18n } = this.props;\n    this.i18n = {\n      rich_text: i18n._(\n        /* i18n */ 'settings.message.display_format.options.rich_text',\n        null,\n        { message: 'Rich text' }\n      ),\n      plain_text: i18n._(\n        /* i18n */ 'settings.message.display_format.options.plain_text',\n        null,\n        { message: 'Plain text' }\n      ),\n    };\n  }\n\n  render() {\n    const { i18n } = this.props;\n\n    const displayFormatOptions = this.getOptionsFromArray(DISPLAY_FORMATS);\n\n    return (\n      <FormGrid className=\"m-settings-message-form\">\n        <FormRow>\n          <FormColumn rightSpace={false}>\n            <Field\n              component={SelectFieldGroup}\n              name=\"message_display_format\"\n              label={i18n._(\n                /* i18n */ 'settings.message.display_format.label',\n                null,\n                {\n                  message: 'Display',\n                }\n              )}\n              options={displayFormatOptions}\n              expanded\n            />\n          </FormColumn>\n        </FormRow>\n      </FormGrid>\n    );\n  }\n}\n\nexport default PresentationForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/NotificationSettings/index.js",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/components/NotificationSettings/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Field } from 'redux-form';\nimport {\n  CheckboxFieldGroup as CheckboxFieldGroupBase,\n  SelectFieldGroup as SelectFieldGroupBase,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\nimport renderReduxField from '../../../../services/renderReduxField';\n\nconst SelectFieldGroup = renderReduxField(SelectFieldGroupBase);\nconst CheckboxFieldGroup = renderReduxField(CheckboxFieldGroupBase);\n\nconst MESSAGE_PREVIEW = ['off', 'always'];\nconst DELAY_DISAPPEAR = [0, 5, 10, 30];\n\nclass NotificationForm extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {};\n\n  UNSAFE_componentWillMount() {\n    this.initTranslations();\n  }\n\n  getOptionsFromArray = (options) =>\n    options.map((value) => ({\n      value,\n      label: this.i18n[value] || value,\n    }));\n\n  initTranslations() {\n    const { i18n } = this.props;\n    this.i18n = {\n      off: i18n._(\n        /* i18n */ 'settings.notification.message_preview.options.off',\n        null,\n        {\n          message: 'Off',\n        }\n      ),\n      always: i18n._(\n        /* i18n */ 'settings.notification.message_preview.options.always',\n        null,\n        { message: 'Always' }\n      ),\n    };\n  }\n\n  render() {\n    const { i18n } = this.props;\n\n    const messagePreviewOptions = this.getOptionsFromArray(MESSAGE_PREVIEW);\n    const delayDisappearOptions = DELAY_DISAPPEAR.map((delay) => ({\n      value: delay,\n      label: i18n._(\n        /* i18n */ 'settings.notification.delay_disappear.options.second',\n        { 0: delay },\n        { message: '{0} Seconds' }\n      ),\n    }));\n\n    return (\n      <FormGrid className=\"m-contacts-form\">\n        <FormRow>\n          <FormColumn rightSpace={false} bottomSpace>\n            <Field\n              component={CheckboxFieldGroup}\n              type=\"checkbox\"\n              name=\"notification_enabled\"\n              label={i18n._(\n                /* i18n */ 'settings.notification.enabled.label',\n                null,\n                {\n                  message: 'Enabled',\n                }\n              )}\n            />\n          </FormColumn>\n          <FormColumn rightSpace={false} bottomSpace>\n            <Field\n              component={CheckboxFieldGroup}\n              type=\"checkbox\"\n              name=\"notification_sound_enabled\"\n              label={i18n._(\n                /* i18n */ 'settings.notification.sound_enabled.label',\n                null,\n                {\n                  message: 'Sound enabled',\n                }\n              )}\n            />\n          </FormColumn>\n          <FormColumn rightSpace={false} bottomSpace>\n            <Field\n              component={SelectFieldGroup}\n              name=\"notification_message_preview\"\n              label={i18n._(\n                /* i18n */ 'settings.notification.message_preview.label',\n                null,\n                { message: 'Message preview' }\n              )}\n              options={messagePreviewOptions}\n              expanded\n            />\n          </FormColumn>\n          <FormColumn rightSpace={false}>\n            <Field\n              component={SelectFieldGroup}\n              name=\"notification_delay_disappear\"\n              label={i18n._(\n                /* i18n */ 'settings.notification.delay_disappear.label',\n                null,\n                { message: 'Display delay' }\n              )}\n              options={delayDisappearOptions}\n              expanded\n            />\n          </FormColumn>\n        </FormRow>\n      </FormGrid>\n    );\n  }\n}\n\nexport default NotificationForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/index.jsx",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport { withI18n } from '@lingui/react';\nimport { reduxForm } from 'redux-form';\nimport { withNotification } from '../../modules/userNotify';\nimport Presenter from './presenter';\nimport { requestSettings, updateSettings } from '../../store/modules/settings';\nimport { settingsSelector } from '../../store/selectors/settings';\n\nconst mapStateToProps = createSelector([settingsSelector], (settings) => ({\n  initialValues: settings,\n}));\n\nconst normalizeValues = (values) => ({\n  ...values,\n  notification_delay_disappear: Number.parseInt(\n    values.notification_delay_disappear,\n    10\n  ),\n});\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      requestSettings,\n      onSubmit: (values, disp, props) =>\n        updateSettings({\n          settings: normalizeValues(values),\n          original: props.initialValues,\n        }),\n    },\n    dispatch\n  );\n\nexport default compose(\n  connect(mapStateToProps, mapDispatchToProps),\n  withI18n(),\n  withNotification(),\n  reduxForm({\n    form: 'settings-application',\n    enableReinitialize: true,\n  })\n)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Section, PageTitle, Button, FieldErrors } from '../../components';\nimport InterfaceSettings from './components/InterfaceSettings';\nimport MessageSettings from './components/MessageSettings';\nimport ContactSettings from './components/ContactSettings';\nimport NotificationSettings from './components/NotificationSettings';\nimport DesktopNotificationSettings from './components/DesktopNotificationSettings';\nimport './style.scss';\n\nclass ApplicationSettings extends PureComponent {\n  static propTypes = {\n    handleSubmit: PropTypes.func.isRequired,\n    requestSettings: PropTypes.func.isRequired,\n    notifyError: PropTypes.func.isRequired,\n    notifySuccess: PropTypes.func.isRequired,\n    errors: PropTypes.shape({}),\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {\n    errors: {},\n  };\n\n  handleSubmit = (ev) => {\n    /* TODO: autosave settings */\n    const { handleSubmit } = this.props;\n    handleSubmit(ev).then(this.handleSuccess, this.handleError);\n  };\n\n  handleSuccess = async () => {\n    const { i18n, notifySuccess, requestSettings } = this.props;\n    await requestSettings();\n\n    return notifySuccess({\n      message: i18n._(/* i18n */ 'settings.form.feedback.successfull', null, {\n        message: 'Settings successfully updated!',\n      }),\n    });\n  };\n\n  handleError = () => {\n    const { i18n, notifyError } = this.props;\n    notifyError({\n      message: i18n._(\n        /* i18n */ 'settings.form.feedback.unexpected-error',\n        null,\n        {\n          message: 'Error when updating settings.',\n        }\n      ),\n    });\n  };\n\n  render() {\n    const { errors, i18n } = this.props;\n\n    return (\n      <form\n        method=\"post\"\n        className=\"s-application-settings\"\n        name=\"settings_application_form\"\n        onSubmit={this.handleSubmit}\n      >\n        <PageTitle />\n        {errors.global && errors.global.length !== 0 && (\n          <FieldErrors errors={errors.global} />\n        )}\n        <div className=\"s-application-settings__col-sections\">\n          <Section\n            title={i18n._(/* i18n */ 'settings.interface.title', null, {\n              message: 'Customize your interface',\n            })}\n          >\n            <InterfaceSettings />\n          </Section>\n        </div>\n        <div className=\"s-application-settings__col-sections\">\n          <Section\n            title={i18n._(/* i18n */ 'settings.message.title', null, {\n              message: 'Messages settings',\n            })}\n          >\n            <MessageSettings />\n          </Section>\n        </div>\n        <div className=\"s-application-settings__col-sections\">\n          <Section\n            className=\"s-application-settings__section\"\n            title={i18n._(/* i18n */ 'settings.contact.title', null, {\n              message: 'Contact settings',\n            })}\n          >\n            <ContactSettings />\n          </Section>\n        </div>\n        <div className=\"s-application-settings__col-sections\">\n          <Section\n            title={i18n._(/* i18n */ 'settings.notification.title', null, {\n              message: 'Notifications settings',\n            })}\n          >\n            <NotificationSettings />\n          </Section>\n          <Section\n            title={i18n._(\n              /* i18n */ 'settings.desktop_notification.title',\n              null,\n              {\n                message: 'Desktop notifications settings',\n              }\n            )}\n          >\n            <DesktopNotificationSettings />\n          </Section>\n        </div>\n\n        <div className=\"s-application-settings__action\">\n          <Button type=\"submit\" shape=\"plain\" icon=\"check\">\n            <Trans\n              id=\"settings.presentation.update.action\"\n              message=\"Save settings\"\n            />\n          </Button>\n        </div>\n      </form>\n    );\n  }\n}\n\nexport default ApplicationSettings;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ApplicationSettings/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-application-settings {\n  @include flex-grid-row($size: expand);\n\n  align-items: stretch;\n  padding: $co-margin (calc($co-margin / 2));\n\n  &__col-sections {\n    @include flex-grid-column(12);\n\n    margin-bottom: $co-margin;\n  }\n\n  &__section {\n    height: 100%;\n  }\n\n  &__action {\n    @include flex-grid-column(12);\n\n    text-align: right;\n  }\n\n  @include breakpoint(medium) {\n    &__col-sections {\n      @include flex-grid-column(6);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ContactAssociation/contact-association.scss",
    "content": "@import '../../styles/common';\n\n.s-contact-association {\n  background-color: $co-color__fg__back;\n\n  &__action-bar {\n    padding-right: map_get($co-margin_, 'xsmall');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ContactAssociation/index.spec.tsx",
    "content": "import * as React from 'react';\nimport {\n  render,\n  screen,\n  waitForElementToBeRemoved,\n} from '@testing-library/react';\nimport { rest } from 'msw';\nimport { server } from 'test/server';\nimport { AllProviders } from 'test/providers';\nimport { generateContact } from 'test/fixtures/contacts';\nimport ContactAssociation from '.';\n\nconst contacts = [\n  generateContact({\n    contact_id: 'foo',\n    title: 'Foo',\n    family_name: 'Bar',\n  }),\n  generateContact({\n    title: 'Me',\n    family_name: 'Em',\n    user_id: 'u-john-01',\n  }),\n];\n\njest.mock('src/modules/user/services/isAuthenticated', () => ({\n  isAuthenticated: () => true,\n}));\n\ndescribe('ContactAssociation', () => {\n  beforeEach(() => {\n    server.use(\n      rest.get('/api/v2/contacts', (req, res, ctx) =>\n        res(ctx.json({ contacts, total: contacts.length }), ctx.status(200))\n      )\n    );\n  });\n  it('render', async () => {\n    render(<ContactAssociation />, { wrapper: AllProviders });\n    await waitForElementToBeRemoved(\n      screen.getByRole('list', { name: 'Contact list is loading.' }),\n      { timeout: 3000 }\n    );\n    const expectedName = `${contacts[0].given_name} ${contacts[0].family_name}`;\n    expect(screen.getByText(expectedName)).toBeVisible();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ContactAssociation/index.tsx",
    "content": "import * as React from 'react';\nimport { useHistory, useParams } from 'react-router-dom';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { PageTitle, Button, ActionBar } from 'src/components';\nimport {\n  ContactList,\n  useContactsIsFetching,\n  useContacts,\n} from 'src/modules/contact';\nimport { Contact } from 'src/modules/contact/types';\nimport { useSearchParams } from 'src/modules/routing';\nimport { useCloseTab, useCurrentTab } from 'src/modules/tab';\n\nimport './contact-association.scss';\n\ntype Props = withI18nProps;\n\nfunction ContactAssociation({ i18n }: Props) {\n  const isLoading = useContactsIsFetching();\n  const closeTab = useCloseTab();\n  const currentTab = useCurrentTab();\n  const { push } = useHistory();\n  const { address, protocol } = useParams<{\n    address: string;\n    protocol: string;\n  }>();\n  const { label } = useSearchParams();\n  const { data: contacts } = useContacts();\n\n  const handleClickContact = (contact: Contact) => {\n    push(\n      `/contacts/${contact.contact_id}/edit?address=${address}&protocol=${protocol}&label=${label}`\n    );\n    closeTab(currentTab);\n  };\n\n  const handleClickNewContact = () => {\n    push(`/new-contact?address=${address}&protocol=${protocol}&label=${label}`);\n    closeTab(currentTab);\n  };\n\n  return (\n    <div className=\"s-contact-association\">\n      <PageTitle\n        title={i18n._(/* i18n */ 'header.menu.contact-association', undefined, {\n          message: 'Contact association',\n        })}\n      />\n      <ActionBar\n        hr={false}\n        isLoading={isLoading}\n        actionsNode={\n          <div className=\"s-contact-book-menu s-contact-association__action-bar\">\n            <Button\n              className=\"s-contact-book-menu__action-btn\"\n              display=\"inline\"\n              noDecoration\n              icon=\"plus\"\n              onClick={handleClickNewContact}\n            >\n              <Trans\n                id=\"contact-association.action.add-new-contact\"\n                message=\"Add new contact\"\n              />\n            </Button>\n          </div>\n        }\n      />\n      <ContactList\n        onClickContact={handleClickContact}\n        mode=\"association\"\n        contacts={contacts}\n      />\n    </div>\n  );\n}\n\nexport default withI18n()(ContactAssociation);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/DeviceForm/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { screen, render } from '@testing-library/react';\nimport { AllProviders } from 'test/providers';\nimport { generateDevice } from 'test/fixtures/device';\nimport DeviceForm from '.';\n\ndescribe('component Device DeviceForm', () => {\n  it('render', () => {\n    const device = generateDevice();\n\n    render(<DeviceForm device={device} />, { wrapper: AllProviders });\n\n    expect(screen.getByRole('textbox', { name: 'Name:' }));\n    expect(screen.getByRole('combobox', { name: 'Type:' }));\n  });\n\n  // input has been disabled\n  xdescribe('validates ip', () => {\n    it('is valid with simple ip', () => {\n      const device = generateDevice();\n\n      render(<DeviceForm device={device} />);\n\n      // expect(inst.validateIP('192.168.1.1')).toEqual({ isValid: true });\n    });\n\n    it('is not valid', () => {\n      const device = generateDevice();\n\n      render(<DeviceForm device={device} />);\n\n      // expect(inst.validateIP('foo')).toEqual({\n      //   isValid: false,\n      //   errors: ['device.feedback.invalid_ip'],\n      // });\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/DeviceForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, useLingui } from '@lingui/react';\nimport { Device } from 'src/modules/device/types';\nimport {\n  Button,\n  FormGrid,\n  FormRow,\n  FormColumn,\n  TextFieldGroup,\n  SelectFieldGroup,\n} from 'src/components';\nimport './style.scss';\nimport { saveDevice } from 'src/modules/device';\nimport { useDispatch } from 'react-redux';\nimport { notifyError, notifySuccess } from 'src/modules/userNotify';\n\ninterface Props {\n  device: Device;\n}\nfunction DeviceForm({ device }: Props) {\n  const dispatch = useDispatch();\n  const { i18n } = useLingui();\n  const [formData, setFormData] = React.useState({\n    name: device.name,\n    type: device.type,\n  });\n\n  const handleFieldChange = (ev) => {\n    const { name, value } = ev.target;\n    setFormData((prev) => ({\n      ...prev,\n      [name]: value,\n    }));\n  };\n\n  // const handleLocationsChange = (locations) => {\n  //   this.setState(prevState => ({\n  //     device: {\n  //       ...prevState.device,\n  //       locations,\n  //     },\n  //   }));\n  // }\n\n  // validateIP = (ip) => {\n  //   // XXX: add IP V6 support\n  //   if (/^[0-9]{1,3}(\\.[-/0-9]*){1,3}$/.test(ip)) {\n  //     return { isValid: true };\n  //   }\n  //\n  //   const { i18n } = this.props;\n  //\n  //   return { isValid: false, errors: [i18n._(/* i18n */ 'device.feedback.invalid_ip', null, { message: 'IP\n  //   or subnet address is invalid.' })] };\n  // }\n\n  const handleSubmit = async (event) => {\n    event.preventDefault();\n    try {\n      await dispatch(\n        saveDevice({\n          device: {\n            ...device,\n            ...formData,\n          },\n          original: device,\n        })\n      );\n      dispatch(\n        notifySuccess({\n          message: (\n            <Trans\n              id=\"device.feedback.save_success\"\n              message=\"The device has been saved\"\n            />\n          ),\n        })\n      );\n    } catch (errors) {\n      errors.forEach(({ message }) => dispatch(notifyError({ message })));\n    }\n  };\n\n  const deviceTypes = [\n    {\n      value: 'desktop',\n      label: i18n._(/* i18n */ 'device.type.desktop', undefined, {\n        message: 'Desktop',\n      }),\n    },\n    {\n      value: 'laptop',\n      label: i18n._(/* i18n */ 'device.type.laptop', undefined, {\n        message: 'Laptop',\n      }),\n    },\n    {\n      value: 'smartphone',\n      label: i18n._(/* i18n */ 'device.type.smartphone', undefined, {\n        message: 'Smartphone',\n      }),\n    },\n    {\n      value: 'tablet',\n      label: i18n._(/* i18n */ 'device.type.tablet', undefined, {\n        message: 'Tablet',\n      }),\n    },\n    {\n      value: 'other',\n      label: i18n._(/* i18n */ 'device.type.other', undefined, {\n        message: 'Other',\n      }),\n    },\n  ];\n  // const locationTypes = [\n  //   { label: i18n._(/* i18n */ 'device.location.type.unknown', undefined, { message: 'Unknown' }), value:\n  //   'unknown' },\n  //   { label: i18n._(/* i18n */ 'device.location.type.home', undefined, { message: 'Home' }), value: 'home' },\n  //   { label: i18n._(/* i18n */ 'device.location.type.work', undefined, { message: 'Work' }), value: 'work' },\n  //   { label: i18n._(/* i18n */ 'device.location.type.public', undefined, { message: 'Public' }), value:\n  //   'public' },\n  // ];\n  // const defaultLocation = { address: '', type: locationTypes[0].value };\n  //\n  // const locationTemplate = ({ item: location, onChange, className }) => {\n  //   const handleChange = (ev) => {\n  //     const { name, value } = ev.target;\n  //     onChange({\n  //       item: {\n  //         ...location,\n  //         [name]: value,\n  //       },\n  //     });\n  //   };\n  //\n  //   return (\n  //     <div className={classnames('m-device-form__location-group', className)}>\n  //       <TextFieldGroup\n  //         showLabelforSr\n  //         name=\"address\"\n  //         label={i18n._(/* i18n */ 'device.form.locations.address.label', null, { message: 'IP or subnet\n  //         mask' })}\n  //         value={location.address}\n  //         onChange={handleChange}\n  //         className=\"m-device-form__location-address\"\n  //       />\n  //       <SelectFieldGroup\n  //         showLabelforSr\n  //         name=\"type\"\n  //         label={i18n._(/* i18n */ 'device.form.locations.type.label', null, { message: 'Connection\n  //         location' })}\n  //         value={location.type}\n  //         options={locationTypes}\n  //         onChange={handleChange}\n  //         className=\"m-device-form__location-type\"\n  //       />\n  //     </div>\n  //   );\n  // };\n\n  return (\n    <FormGrid className=\"m-device-form\">\n      <form method=\"post\" onSubmit={handleSubmit}>\n        <FormRow>\n          <FormColumn bottomSpace rightSpace={false}>\n            <TextFieldGroup\n              label={i18n._(\n                /* i18n */ 'device.manage_form.name.label',\n                undefined,\n                {\n                  message: 'Name:',\n                }\n              )}\n              id=\"device-name\"\n              inputProps={{\n                name: 'name',\n                value: formData.name,\n                onChange: handleFieldChange,\n              }}\n            />\n          </FormColumn>\n          {\n            // XXX: hidden for now, backend does not save it and it is not used anywere\n            // we need a specification for this feature https://trello.com/c/j5iKNg7x/192-securisation-des-devices-par-ip-de-connexion\n            //   <FormColumn bottomSpace rightSpace={false}>\n            //   <Label htmlFor=\"device-ips\" className=\"m-device-form__label\">\n            //     <Trans id=\"device.manage_form.ips.infotext\">Restrict the access of your account\n            //     to certain IP addresses for this device. (e.g. 192.168.10 or 192.168.1.1/24 or\n            //     192.168.1.1-20)</Trans>\n            //   </Label>\n            //   <CollectionFieldGroup\n            //     defaultValue={defaultLocation}\n            //     collection={this.state.device.locations}\n            //     addTemplate={locationTemplate}\n            //     editTemplate={locationTemplate}\n            //     onChange={this.handleLocationsChange}\n            //   />\n            // </FormColumn>\n          }\n        </FormRow>\n        <FormRow>\n          <FormColumn rightSpace={false} bottomSpace>\n            <SelectFieldGroup\n              className=\"m-device-form__type\"\n              label={i18n._(\n                /* i18n */ 'device.manage_form.type.label',\n                undefined,\n                {\n                  message: 'Type:',\n                }\n              )}\n              name=\"type\"\n              id=\"device-type\"\n              value={formData.type}\n              options={deviceTypes}\n              onChange={handleFieldChange}\n              expanded\n            />\n          </FormColumn>\n        </FormRow>\n        <FormRow>\n          <FormColumn>\n            <Button shape=\"plain\" type=\"submit\">\n              <Trans\n                id=\"device.action.save_changes\"\n                message=\"Save modifications\"\n              />\n            </Button>\n          </FormColumn>\n        </FormRow>\n      </form>\n    </FormGrid>\n  );\n}\n\nexport default DeviceForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/DeviceForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-device-form {\n  margin-bottom: $co-margin;\n\n  &__label {\n    display: inline-block;\n    margin-bottom: $co-component__spacing;\n  }\n\n  &__location-group {\n    display: flex;\n    flex-flow: row wrap;\n  }\n\n  &__location-address {\n    @include flex-grid-size;\n  }\n\n  &__location-type {\n    @include flex-grid-column(shrink);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/DeviceInformation/index.spec.tsx",
    "content": "import React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport { generateDevice } from 'test/fixtures/device';\nimport { AllProviders } from 'test/providers';\nimport DeviceInformation from '.';\n\ndescribe('scene DeviceInformation', () => {\n  it('render current device', () => {\n    const device = generateDevice();\n    render(<DeviceInformation device={device} isCurrentDevice />, {\n      wrapper: AllProviders,\n    });\n    expect(screen.getByText('Current device')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/DeviceInformation/index.tsx",
    "content": "import * as React from 'react';\n// import Moment from 'react-moment';\nimport { Trans } from '@lingui/react';\nimport { Device } from 'src/modules/device/types';\nimport { Icon, TextBlock } from 'src/components';\nimport './style.scss';\n\ninterface Props {\n  device: Device;\n  isCurrentDevice: boolean;\n}\nfunction DeviceInformation({ device, isCurrentDevice }: Props) {\n  const deviceType = device.type === 'other' ? 'question-circle' : device.type;\n\n  return (\n    <div className=\"m-device-information\">\n      <div className=\"m-device-information__icon\">\n        <Icon type={deviceType} />\n      </div>\n      <div className=\"m-device-information__device\">\n        <div className=\"m-device-information__info\">\n          {isCurrentDevice && (\n            <TextBlock className=\"m-device-information__current-device\">\n              (<Trans id=\"device.current_device\" message=\"Current device\" />)\n            </TextBlock>\n          )}\n          <TextBlock className=\"m-device-information__name\">\n            {device.name}\n          </TextBlock>\n          {/* TODO: display connected status for device\n            <span className=\"m-device-information__status\">\n              device.isConnected && (\n                <Trans id=\"device.status.connected\" message=\"Connected\" />\n                {' '}\n                <Icon type=\"check\" className=\"m-device-information__status-icon\" />\n              )\n            </span>\n          */}\n        </div>\n        <span className=\"m-device-information__user-agent\">\n          {device.user_agent}\n        </span>\n        {/* date insert: <Moment format=\"LLL\" locale={locale}>{device.date_insert}</Moment><br />\n          date created: <Moment format=\"LLL\" locale={locale}>{device.last_seen}</Moment> */}\n      </div>\n    </div>\n  );\n}\n\nexport default DeviceInformation;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/DeviceInformation/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-device-information {\n  @include flex-grid-row($size: expand);\n\n  margin-bottom: $co-margin;\n\n  &__icon {\n    @include flex-grid-size(shrink);\n\n    padding-right: calc($co-margin / 2);\n    color: $co-color__primary;\n    font-size: $co-component__height;\n    line-height: $co-component__height;\n  }\n\n  &__device {\n    @include flex-grid-size;\n  }\n\n  &__info {\n    @include flex-grid-row($size: expand);\n  }\n\n  &__current-device {\n    @include flex-grid-size(shrink);\n\n    padding-right: $co-component__spacing;\n    color: $co-color__primary;\n    font-weight: 700;\n  }\n\n  &__name {\n    @include flex-grid-size(shrink);\n\n    color: $co-color__primary--lower;\n    font-weight: 700;\n  }\n\n  &__status {\n    @include flex-grid-size(shrink);\n\n    margin-left: auto;\n    font-size: $co-font__size--xsmall;\n    font-weight: 400;\n  }\n\n  &__status-icon {\n    color: $co-color__success;\n  }\n\n  &__user-agent {\n    color: $co-color__fg__text--high;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/DeviceSettings/index.tsx",
    "content": "import * as React from 'react';\nimport { Device } from 'src/modules/device/types';\nimport { selectDevice } from 'src/modules/device/selectors';\nimport { useSelector } from 'src/store/reducer';\nimport { getClientDevice, STATUS_VERIFIED } from 'src/modules/device';\nimport { Section } from '../../../../components';\nimport DeviceForm from '../DeviceForm';\nimport DeviceInformation from '../DeviceInformation';\nimport VerifyDevice from '../VerifyDevice';\nimport RevokeDevice from '../RevokeDevice';\n\ninterface Props {\n  device: Device;\n}\n\nexport default function DeviceSettings({ device }: Props) {\n  const clientDevice = getClientDevice();\n  const currentDevice = useSelector((state) =>\n    clientDevice ? selectDevice(state, clientDevice.device_id) : undefined\n  );\n\n  const isCurrentDevice = currentDevice === device;\n  const isCurrentDeviceVerified = currentDevice?.status === STATUS_VERIFIED;\n\n  const renderVerifyDevice = () => {\n    if (device.status === STATUS_VERIFIED) {\n      return null;\n    }\n\n    return <VerifyDevice device={device} />;\n  };\n\n  const renderRevokeDevice = () => {\n    if (isCurrentDeviceVerified || isCurrentDevice) {\n      return <RevokeDevice device={device} />;\n    }\n\n    return null;\n  };\n\n  const borderContext = device.status === STATUS_VERIFIED ? 'safe' : 'disabled';\n\n  return (\n    <Section borderContext={borderContext}>\n      <DeviceInformation device={device} isCurrentDevice={isCurrentDevice} />\n      <DeviceForm device={device} />\n      <div>\n        {renderVerifyDevice()} {renderRevokeDevice()}\n      </div>\n    </Section>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/RevokeDevice/index.spec.tsx",
    "content": "import * as React from 'react';\n\nimport { render, screen } from '@testing-library/react';\nimport { generateDevice } from 'test/fixtures/device';\nimport { AllProviders } from 'test/providers';\nimport RevokeDevice from '.';\n\ndescribe('scene RevokeDevice', () => {\n  it('render', () => {\n    const device = generateDevice();\n    render(<RevokeDevice device={device} />, { wrapper: AllProviders });\n    expect(screen.getByRole('button')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/RevokeDevice/index.tsx",
    "content": "import * as React from 'react';\nimport { useDispatch } from 'react-redux';\nimport { Trans } from '@lingui/react';\nimport { Button, Icon } from 'src/components';\nimport { signout } from 'src/modules/routing';\nimport { revokeDevice, getClientDevice } from 'src/modules/device';\nimport { notifyError, notifySuccess } from 'src/modules/userNotify';\nimport { Device } from 'src/modules/device/types';\n// unused for now: we want inline buttons\n// import './style.scss';\n\ninterface Props {\n  device: Device;\n}\nfunction RevokeDevice({ device }: Props) {\n  const dispatch = useDispatch();\n  const clientDevice = getClientDevice();\n\n  const handleRevoke = async () => {\n    try {\n      await dispatch(revokeDevice({ device }));\n      dispatch(\n        notifySuccess({\n          message: (\n            <Trans\n              id=\"device.feedback.revoke_success\"\n              message=\"The device has been revoked\"\n            />\n          ),\n        })\n      );\n      if (device.device_id === clientDevice?.device_id) {\n        signout();\n      }\n    } catch ({ message }) {\n      dispatch(notifyError({ message }));\n    }\n  };\n\n  return (\n    <span className=\"m-device-revoke\">\n      {/* TODO: At this time we can't prevent any device to connect */}\n      {/* <span className=\"m-device-revoke__info\">\n          <Trans id=\"device.revoke_info\">You can prevent this device to connect to your account in\n          the future.</Trans>\n        </span> */}\n      <Button\n        className=\"m-device-revoke__button\"\n        shape=\"plain\"\n        color=\"alert\"\n        onClick={handleRevoke}\n      >\n        <Icon type=\"remove\" rightSpaced />\n        <Trans id=\"device.action.revoke\" message=\"Revoke this device\" />\n      </Button>\n    </span>\n  );\n}\n\nexport default RevokeDevice;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/RevokeDevice/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-device-revoke {\n  @include flex-grid-row($size: expand);\n\n  align-items: center;\n\n  &__info {\n    @include flex-grid-size(12);\n\n    padding-right: $co-component__spacing;\n    color: $co-color__primary;\n  }\n\n  &__button {\n    @include flex-grid-size(shrink);\n\n    margin-top: $co-component__spacing;\n  }\n\n  @include breakpoint(medium) {\n    &__info {\n      @include flex-grid-size;\n\n      text-align: right;\n    }\n\n    &__button {\n      margin-top: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/VerifyDevice/index.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport { generateDevice } from 'test/fixtures/device';\nimport { AllProviders } from 'test/providers';\n\nimport VerifyDevice from '.';\n\njest.mock('../../../../modules/userNotify', () => ({\n  withNotification: () => (noop) => noop,\n}));\n\ndescribe('scene VerifyDevice', () => {\n  it('render', () => {\n    const device = generateDevice();\n    render(<VerifyDevice device={device} />, { wrapper: AllProviders });\n\n    expect(screen.getByRole('button')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/VerifyDevice/index.tsx",
    "content": "import * as React from 'react';\nimport { useDispatch } from 'react-redux';\nimport { Trans } from '@lingui/react';\nimport { Device } from 'src/modules/device/types';\nimport { Button, Spinner } from 'src/components';\nimport { notifySuccess } from 'src/modules/userNotify';\nimport { verifyDevice } from 'src/modules/device';\n\ninterface Props {\n  device: Device;\n}\n\nfunction VerifyDevice({ device }: Props) {\n  const dispatch = useDispatch();\n  const [sending, setSending] = React.useState(false);\n\n  const handleVerify = async () => {\n    setSending(true);\n    try {\n      await dispatch(verifyDevice({ device }));\n      dispatch(\n        notifySuccess({\n          message: (\n            <Trans\n              id=\"device.feedback.send-validation-success\"\n              message=\"An email has been sent to your backup email in order to verify the device.\"\n            />\n          ),\n        })\n      );\n    } catch (e) {\n      // continue to the next handler\n      throw new Error(e);\n    } finally {\n      setSending(false);\n    }\n  };\n\n  return (\n    <Button\n      icon={\n        sending ? (\n          <Spinner\n            svgTitleId=\"verify-device-spinner\"\n            isLoading\n            display=\"inline\"\n          />\n        ) : (\n          'check'\n        )\n      }\n      shape=\"plain\"\n      className=\"m-device-verify__button\"\n      onClick={handleVerify}\n      disabled={sending}\n    >\n      <Trans id=\"device.action.verify\" message=\"Verify this device\" />\n    </Button>\n  );\n}\n\nexport default VerifyDevice;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/components/VerifyDevice/style.scss",
    "content": "@import '../../../../styles/common';\n\n.m-device-verify {\n  text-align: center;\n\n  &__button {\n    margin-bottom: map_get($co-form__spacing, 'small');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans } from '@lingui/react';\nimport { useDevices } from 'src/modules/device/hooks/useDevices';\nimport { getClientDevice, STATUS_VERIFIED } from 'src/modules/device';\nimport { useDevice } from 'src/modules/device/hooks/useDevice';\nimport { PageTitle, Section } from 'src/components';\nimport DeviceSettings from './components/DeviceSettings';\nimport './style.scss';\n\nexport default function DevicesSettings() {\n  const { devices } = useDevices();\n  const clientDevice = getClientDevice();\n  const { device } = useDevice(clientDevice.device_id);\n  // we consider the device as verified until devices have been loaded\n  const isCurrentDeviceVerified =\n    !devices || device?.status === STATUS_VERIFIED;\n\n  return (\n    <div className=\"s-devices-settings\">\n      <PageTitle />\n      {!isCurrentDeviceVerified && (\n        <div className=\"s-devices-settings__info\">\n          <Section>\n            <div>\n              <Trans\n                id=\"devices.feedback.unverified_device\"\n                message=\"It's the first time you attempt to connect to your Caliopen account on this device.\"\n              />\n            </div>\n            <div>\n              <Trans\n                id=\"devices.feedback.unverified_device_more\"\n                message={`\n                  To respect privacy and security rules, your discussions\n                  history will not fully appear according to Privacy settings\n                  <0/>\n                  Please verify this device and eventually set restrictions from\n                  your trusted device.\n                `}\n                components={[<br />]}\n              />\n            </div>\n          </Section>\n        </div>\n      )}\n      {devices?.map((item) => (\n        <div key={item.device_id} className=\"s-devices-settings__device\">\n          <DeviceSettings device={item} />\n        </div>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/DevicesSettings/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-devices-settings {\n  @include flex-grid-row($size: expand);\n\n  padding: calc($co-margin / 2);\n\n  &__info,\n  &__device {\n    @include flex-grid-size(12);\n  }\n\n  &__device {\n    margin-top: calc($co-margin / 2);\n    margin-bottom: calc($co-margin / 2);\n  }\n\n  @include breakpoint(large) {\n    &__info {\n      @include flex-grid-column(12);\n    }\n\n    &__device {\n      @include flex-grid-column(6);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/AddParticipantsToContactBook/add-participants-dropdown.scss",
    "content": "@import '../../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../../styles/common';\n\n.m-add-participants-dropdown {\n  &__label {\n    vertical-align: middle;\n  }\n\n  @include breakpoint(small only) {\n    &__label {\n      max-width: 87%;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/AddParticipantsToContactBook/index.jsx",
    "content": "import React, { Component, Fragment, createRef } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { withI18n } from '@lingui/react';\nimport {\n  ActionBarButton,\n  Link,\n  Icon,\n  Dropdown,\n  withDropdownControl,\n  VerticalMenu,\n  VerticalMenuItem,\n  TextBlock,\n} from '../../../../components';\nimport { getParticipantsExceptUser } from '../../../../services/message';\nimport { withUser } from '../../../../modules/user';\n\nimport './add-participants-dropdown.scss';\n\nconst DropdownControl = withDropdownControl(ActionBarButton);\n\n@withI18n()\n@withUser()\nclass AddParticipantsToContactBook extends Component {\n  static propTypes = {\n    className: PropTypes.string,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    userState: PropTypes.shape({\n      user: PropTypes.shape({}),\n    }).isRequired,\n    // we only need one message of a discussion\n    // - for one-to-many: since it has all the participant of the discussion (and the protocol\n    // cannot be mixed)\n    // - for one-to-one: if the participant is not in the contact book it will be always a different\n    // discussion\n    message: PropTypes.shape({}).isRequired,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  dropdownControlRef = createRef();\n\n  getUnknownParticipants = () => {\n    const {\n      message,\n      userState: { user },\n    } = this.props;\n\n    return getParticipantsExceptUser(message, user).filter(\n      (participant) => !participant.contact_ids\n    );\n  };\n\n  render() {\n    const { i18n, className } = this.props;\n    const participants = this.getUnknownParticipants();\n\n    if (participants.length === 0) {\n      return null;\n    }\n\n    return (\n      <Fragment>\n        <DropdownControl\n          ref={this.dropdownControlRef}\n          display=\"inline\"\n          noDecoration\n          className={classnames(className)}\n          title={i18n._(\n            /* i18n */ 'message.action.add-participant-to-contacts',\n            null,\n            {\n              message: 'Add a participant to the contact book',\n            }\n          )}\n        >\n          <Icon type=\"address-book\" /> <Icon type=\"plus\" />\n        </DropdownControl>\n        <Dropdown\n          dropdownControlRef={this.dropdownControlRef}\n          isMenu\n          className=\"m-add-participants-dropdown\"\n        >\n          <VerticalMenu>\n            {participants.map((participant) => (\n              <VerticalMenuItem\n                key={`${participant.protocol}_${participant.address}`}\n                className=\"m-dropdown-unknown-participants__participant\"\n              >\n                <Link\n                  button\n                  display=\"expanded\"\n                  to={`/contact-association/${participant.protocol}/${participant.address}?label=${participant.label}`}\n                  title={i18n._(\n                    /* i18n */ 'message.action.add-to-contacts',\n                    null,\n                    {\n                      message: 'Add to contact book',\n                    }\n                  )}\n                >\n                  <TextBlock\n                    inline\n                    className=\"m-add-participants-dropdown__label\"\n                  >\n                    {participant.label}\n                  </TextBlock>{' '}\n                  <Icon type=\"address-book\" /> <Icon type=\"plus\" />\n                </Link>\n              </VerticalMenuItem>\n            ))}\n          </VerticalMenu>\n        </Dropdown>\n      </Fragment>\n    );\n  }\n}\n\nexport default AddParticipantsToContactBook;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/DownloadFileProgression/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\n\nconst DownloadFileProgression = React.forwardRef(\n  ({ isFetching, value, max }, ref) => (\n    <div ref={ref}>{isFetching && <progress value={value} max={max} />}</div>\n  )\n);\n\nDownloadFileProgression.propTypes = {\n  isFetching: PropTypes.bool,\n  value: PropTypes.number,\n  max: PropTypes.number,\n};\nDownloadFileProgression.defaultProps = {\n  isFetching: false,\n  value: undefined,\n  max: undefined,\n};\n\nexport default DownloadFileProgression;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/InstantMessage/index.tsx",
    "content": "import * as React from 'react';\nimport Moment from 'react-moment';\nimport classNames from 'classnames';\nimport Linkify from 'linkifyjs/react';\nimport { Trans, useLingui } from '@lingui/react';\nimport { useDispatch } from 'react-redux';\n\nimport { getAveragePIMessage, getPiClass } from 'src/modules/pi';\nimport { AuthorAvatarLetter } from 'src/modules/avatar';\nimport { LockedMessage } from 'src/modules/encryption';\nimport { ParticipantLabel, setMessageRead } from 'src/modules/message';\nimport { deleteMessage } from 'src/store/modules/message';\nimport { Button, Confirm, Icon, TextBlock } from 'src/components';\nimport {\n  isMessageFromUser,\n  getAuthor,\n  isUserRecipient,\n  getRecipientsExceptUser,\n  getRecipients,\n} from 'src/services/message';\nimport { useUser } from 'src/modules/user';\n\nimport MessagePi from '../MessagePi';\nimport TagList from '../TagList';\nimport { ConcreteMessageProps } from '../../types';\n\nimport './style.scss';\nimport './instant-message-aside.scss';\nimport './instant-message-author.scss';\nimport './instant-message-participants.scss';\n\nconst PROTOCOL_ICONS = {\n  facebook: 'facebook',\n  twitter: 'twitter',\n  mastodon: 'mastodon',\n  sms: 'phone',\n  email: 'envelope',\n  default: 'comment',\n};\n\nexport default function InstantMessage({\n  message,\n  onDeleteMessageSuccess,\n  isLocked,\n  encryptionStatus,\n  forwardedRef,\n  noInteractions,\n  onOpenTags,\n  onReply,\n}: ConcreteMessageProps) {\n  const dispatch = useDispatch();\n  const { i18n } = useLingui();\n  const { user } = useUser();\n\n  const handleMessageDelete = async () => {\n    await dispatch(deleteMessage({ message }));\n    onDeleteMessageSuccess();\n  };\n\n  const handleToggleMarkAsRead = () => {\n    if (message.is_unread) {\n      dispatch(setMessageRead({ message, isRead: true }));\n    } else {\n      dispatch(setMessageRead({ message, isRead: false }));\n    }\n  };\n\n  const handleReply = () => {\n    onReply();\n  };\n\n  const getProtocolIconType = ({ protocol }) =>\n    PROTOCOL_ICONS[protocol] || 'comment';\n  const getRecipientsLabels = (recipients) => {\n    if (!recipients) return [];\n\n    return recipients.map((recipient) =>\n      recipient.label ? recipient.label : recipient.address\n    );\n  };\n\n  const getRecipientsArray = () =>\n    isUserRecipient(message, user)\n      ? [\n          i18n._(/* i18n */ 'message.participants.me', undefined, {\n            message: 'Me',\n          }),\n          ...getRecipientsLabels(getRecipientsExceptUser(message, user)),\n        ]\n      : getRecipientsLabels(getRecipients(message));\n\n  const getRecipientsString = (shorten) => {\n    const recipients = getRecipientsArray();\n    const numberRecipients = recipients.length;\n\n    if (numberRecipients === 0) {\n      return i18n._(/* i18n */ 'message.participants.me', undefined, {\n        message: 'Me',\n      });\n    }\n    if (!shorten || numberRecipients === 1) {\n      return recipients.join(', ');\n    }\n\n    return i18n._(\n      /* i18n */ 'messages.participants.and_x_others',\n      { first: recipients[0], number: numberRecipients - 1 },\n      {\n        message: '{first} and {number, plural, one {# other} other {# others}}',\n      }\n    );\n  };\n\n  const renderBody = () => {\n    if (isLocked) {\n      return <LockedMessage encryptionStatus={encryptionStatus} />;\n    }\n\n    const content = encryptionStatus?.decryptedMessage || message.body;\n    if (!message.body_is_plain && content) {\n      return (\n        <TextBlock\n          nowrap={false}\n          className=\"m-instant-message__content\"\n          dangerouslySetInnerHTML={{ __html: content }}\n        />\n      );\n    }\n\n    return (\n      <TextBlock className=\"m-instant-message__content\" nowrap={false}>\n        <Linkify>{content}</Linkify>\n      </TextBlock>\n    );\n  };\n\n  const author = getAuthor(message);\n  const pi = getAveragePIMessage({ message });\n\n  const articleClassNames = classNames(\n    'm-instant-message',\n    `${getPiClass(pi)}`,\n    {\n      'm-instant-message--from-user': isMessageFromUser(message, user),\n    }\n  );\n\n  return (\n    <article className={articleClassNames} ref={forwardedRef}>\n      <header className=\"m-instant-message__author m-instant-message-author\">\n        <AuthorAvatarLetter\n          message={message}\n          className=\"m-instant-message-author__avatar\"\n        />\n        <Icon type={getProtocolIconType(message)} />\n        <Moment\n          className=\"m-instant-message-author__time\"\n          format=\"HH:mm\"\n          titleFormat=\"LLLL\"\n          withTitle\n        >\n          {message.date}\n        </Moment>\n      </header>\n      <aside className=\"m-instant-message__info m-instant-message-aside\">\n        <div className=\"m-instant-message-aside__info\">\n          <div className=\"m-instant-message__participants m-instant-message-participants\">\n            <TextBlock className=\"m-instant-message-participants__from\">\n              <ParticipantLabel participant={author} />\n            </TextBlock>\n            <TextBlock className=\"m-instant-message-participants__to\">\n              {getRecipientsString(true)}\n              <Icon type=\"caret-down\" title={getRecipientsString(false)} />\n            </TextBlock>\n          </div>\n          <MessagePi message={message} />\n        </div>\n        {!noInteractions && (\n          <div className=\"m-instant-message-aside__actions\">\n            <Button\n              onClick={handleReply}\n              icon=\"reply\"\n              title={i18n._(\n                /* i18n */ 'message-list.message.action.reply',\n                undefined,\n                {\n                  message: 'Reply',\n                }\n              )}\n            />\n            <Button\n              onClick={onOpenTags}\n              icon=\"tags\"\n              title={i18n._(\n                /* i18n */ 'message-list.message.action.tags',\n                undefined,\n                {\n                  message: 'Tags',\n                }\n              )}\n            />\n            <Confirm\n              onConfirm={handleMessageDelete}\n              title={\n                <Trans\n                  id=\"message-list.message.confirm-delete.title\"\n                  message=\"Delete a message\"\n                />\n              }\n              content={\n                <Trans\n                  id=\"message-list.message.confirm-delete.content\"\n                  message=\"The deletion is permanent, are you sure you want to delete this message ?\"\n                />\n              }\n              render={(confirm) => (\n                <Button\n                  onClick={confirm}\n                  icon=\"trash\"\n                  title={i18n._(\n                    /* i18n */ 'message-list.message.action.delete',\n                    undefined,\n                    {\n                      message: 'Delete',\n                    }\n                  )}\n                />\n              )}\n            />\n            <Button\n              onClick={handleToggleMarkAsRead}\n              icon={message.is_unread ? 'envelope-open' : 'envelope'}\n              title={\n                message.is_unread\n                  ? i18n._(\n                      /* i18n */ 'message-list.message.action.mark_as_read',\n                      undefined,\n                      {\n                        message: 'Mark as read',\n                      }\n                    )\n                  : i18n._(\n                      /* i18n */ 'message-list.message.action.mark_as_unread',\n                      undefined,\n                      {\n                        message: 'Mark as unread',\n                      }\n                    )\n              }\n            />\n          </div>\n        )}\n        <TagList className=\"m-instant-message-aside__tags\" message={message} />\n      </aside>\n\n      {renderBody()}\n    </article>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/InstantMessage/instant-message-aside.scss",
    "content": ".m-instant-message-aside {\n  &__info {\n    padding-top: 0.5rem;\n    padding-right: 1rem;\n    padding-left: 1rem;\n  }\n\n  &__actions {\n    white-space: nowrap;\n  }\n\n  &__tags {\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/InstantMessage/instant-message-author.scss",
    "content": ".m-instant-message-author {\n  display: flex;\n  flex-direction: column;\n  align-content: center;\n  overflow: hidden;\n  text-align: center;\n\n  &__avatar {\n    margin-bottom: 0.25rem;\n  }\n\n  &__time {\n    color: #a7abad;\n    font-size: 0.625rem;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/InstantMessage/instant-message-participants.scss",
    "content": ".m-instant-message-participants {\n  width: auto;\n  margin-right: 0;\n\n  &__to,\n  &__from {\n    display: block;\n    font-size: 0.825rem;\n  }\n\n  &__from {\n    font-size: 0.875rem;\n  }\n\n  &__to {\n    color: #a7abad;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/InstantMessage/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n/* XXX: add a margin if message is on top of the list */\n.m-message-list__load-more + .m-instant-message {\n  margin-top: 1rem;\n}\n\n.m-instant-message {\n  $baseName: &;\n  @include flex-grid-row($size: expand);\n\n  max-width: 44.5rem;\n  margin-right: initial;\n  margin-bottom: 1rem;\n  margin-left: initial;\n\n  &--from-user {\n    flex-direction: row-reverse;\n    margin-left: auto;\n\n    #{$baseName}__info {\n      background-color: #feffff;\n    }\n\n    #{$baseName}__content {\n      background-color: #f0faff;\n    }\n\n    #{$baseName}__author {\n      padding-right: 0.25rem;\n      padding-left: 0.25rem;\n    }\n  }\n\n  &__author {\n    @include flex-grid-column(shrink);\n  }\n\n  &__info {\n    @include flex-grid-size;\n\n    background-color: #c5e8f6;\n    overflow: hidden;\n  }\n\n  &__content {\n    @include flex-grid-size(12);\n\n    padding: map_get($co-margin_, 'xsmall');\n    background-color: #b7e2f4;\n  }\n\n  @include breakpoint(medium) {\n    &__author {\n      @include flex-grid-size(shrink);\n\n      padding-right: 0.5rem;\n      padding-left: 0;\n    }\n\n    &__info {\n      @include flex-grid-size(6);\n\n      flex-basis: 12.125rem;\n      padding-bottom: 0.5rem;\n    }\n\n    &__content {\n      @include flex-grid-size;\n\n      padding: map_get($co-margin_, 'small');\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MailMessage/index.tsx",
    "content": "import * as React from 'react';\nimport Moment from 'react-moment';\nimport { Trans } from '@lingui/react';\nimport classnames from 'classnames';\nimport Linkify from 'linkifyjs/react';\nimport { useDispatch } from 'react-redux';\n\nimport { useSettings } from 'src/modules/settings';\nimport {\n  ParticipantLabel,\n  deleteMessage,\n  setMessageRead,\n} from 'src/modules/message';\nimport { Button, Confirm, Icon, TextBlock, Callout } from 'src/components';\nimport { LockedMessage } from 'src/modules/encryption';\nimport { getAuthor, getRecipients } from 'src/services/message';\nimport { getAveragePIMessage, getPiClass } from 'src/modules/pi/services/pi';\nimport { STATUS_DECRYPTED } from 'src/store/modules/encryption';\nimport MessageAttachments from '../MessageAttachments';\nimport MessageRecipients from '../MessageRecipients';\nimport MessagePi from '../MessagePi';\nimport TagList from '../TagList';\n\nimport './style.scss';\nimport './mail-message-details.scss';\nimport { ConcreteMessageProps } from '../../types';\n\nexport default function MailMessage({\n  message,\n  onOpenTags,\n  noInteractions,\n  encryptionStatus,\n  forwardedRef,\n  onDeleteMessageSuccess,\n  onReply,\n  isLocked,\n}: ConcreteMessageProps) {\n  const dispatch = useDispatch();\n  const { default_locale: locale } = useSettings();\n  const handleMessageDelete = async () => {\n    await dispatch(deleteMessage({ message }));\n    onDeleteMessageSuccess();\n  };\n\n  const handleToggleMarkAsRead = () => {\n    if (message.is_unread) {\n      dispatch(setMessageRead({ message, isRead: true }));\n    } else {\n      dispatch(setMessageRead({ message, isRead: false }));\n    }\n  };\n\n  const handleReply = () => {\n    onReply();\n  };\n\n  const renderBody = () => {\n    if (isLocked) {\n      return <LockedMessage encryptionStatus={encryptionStatus} />;\n    }\n\n    const content = encryptionStatus?.decryptedMessage || message.body;\n\n    if (!message.body_is_plain && content) {\n      return (\n        <TextBlock\n          nowrap={false}\n          className=\"s-mail-message__content\"\n          dangerouslySetInnerHTML={{ __html: content }}\n        />\n      );\n    }\n\n    return (\n      <TextBlock nowrap={false}>\n        <Linkify tagName=\"pre\" className=\"s-mail-message__content\">\n          {content}\n        </Linkify>\n      </TextBlock>\n    );\n  };\n\n  const isDecrypted =\n    encryptionStatus && encryptionStatus.status === STATUS_DECRYPTED;\n  const author = getAuthor(message);\n  const pi = getAveragePIMessage({ message });\n  const piType = getPiClass(pi);\n  const recipients = getRecipients(message);\n\n  const infoPiClassName = {\n    's-mail-message__info--super': piType === 'super',\n    's-mail-message__info--good': piType === 'good',\n    's-mail-message__info--bad': piType === 'bad',\n    's-mail-message__info--ugly': piType === 'ugly',\n  };\n\n  return (\n    <article\n      id={`message-${message.message_id}`}\n      ref={forwardedRef}\n      className=\"s-mail-message\"\n    >\n      <div className=\"s-mail-message__details m-mail-message-details\">\n        <TextBlock\n          className={classnames('m-mail-message-details__author', {\n            'm-mail-message-details__author--encrypted': isDecrypted,\n          })}\n        >\n          {(isDecrypted || isLocked) && (\n            <>\n              <Icon\n                type=\"lock\"\n                className=\"m-mail-message-details--encrypted__icon\"\n              />{' '}\n            </>\n          )}\n          <Icon\n            type=\"envelope\"\n            className={classnames({\n              'm-mail-message-details--encrypted__icon':\n                isDecrypted || isLocked,\n            })}\n          />{' '}\n          <ParticipantLabel\n            className=\"m-mail-message-details__author-name\"\n            participant={author}\n          />{' '}\n          <Moment fromNow locale={locale} titleFormat=\"LLLL\" withTitle>\n            {message.date}\n          </Moment>\n        </TextBlock>\n        <TextBlock className=\"m-mail-message-details__recipients\">\n          <Trans id=\"message.to\" message=\"To:\" />{' '}\n          <MessageRecipients message={message} shorten />\n        </TextBlock>\n      </div>\n      <aside className={classnames('s-mail-message__info', infoPiClassName)}>\n        <MessagePi message={message} describe />\n        <div className=\"s-mail-message__participants\">\n          <div className=\"s-mail-message__participants-from\">\n            <span className=\"direction\">\n              <Trans id=\"message.from\" message=\"From:\" />\n            </span>{' '}\n            <ParticipantLabel participant={author} />\n          </div>\n          <div className=\"s-mail-message__participants-to\">\n            <span className=\"direction\">\n              <Trans id=\"message.to\" message=\"To:\" />\n            </span>{' '}\n            {recipients.map((participant, i) => (\n              <React.Fragment key={participant.address}>\n                {i > 0 && ', '}\n                <ParticipantLabel participant={participant} />\n              </React.Fragment>\n            ))}\n          </div>\n        </div>\n        <TagList className=\"s-mail-message__tags\" message={message} />\n      </aside>\n      <div className=\"s-mail-message__container\">\n        <h2 className=\"s-mail-message__subject\">\n          <TextBlock nowrap={false}>{message.subject}</TextBlock>\n        </h2>\n        {renderBody()}\n        <div className=\"m-message__attachments\">\n          {\n            // Attachments' not decrypted\n            isDecrypted && (\n              <Callout\n                color=\"info\"\n                className=\"s-mail-message__encrypted-attachments-info\"\n              >\n                <Trans\n                  id=\"message.attachment-encryption-not-available\"\n                  message=\"Attachments decryption will be available in a futur version, please be patient.\"\n                />\n              </Callout>\n            )\n          }\n          <MessageAttachments message={message} />\n        </div>\n      </div>\n      {!noInteractions && (\n        <footer className=\"s-mail-message__actions\">\n          <Button\n            className=\"m-message-action-container__action\"\n            onClick={handleReply}\n            icon=\"reply\"\n            responsive=\"icon-only\"\n          >\n            <Trans id=\"message-list.message.action.reply\" message=\"Reply\" />\n          </Button>\n          <Button\n            onClick={onOpenTags}\n            className=\"m-message-actions-container__action\"\n            icon=\"tags\"\n            responsive=\"icon-only\"\n          >\n            <Trans id=\"message-list.message.action.tags\" message=\"Tags\" />\n          </Button>\n          <Confirm\n            onConfirm={handleMessageDelete}\n            title={\n              <Trans\n                id=\"message-list.message.confirm-delete.title\"\n                message=\"Delete a message\"\n              />\n            }\n            content={\n              <Trans\n                id=\"message-list.message.confirm-delete.content\"\n                message=\"The deletion is permanent, are you sure you want to delete this message ?\"\n              />\n            }\n            render={(confirm) => (\n              <Button\n                className=\"m-message-action-container__action\"\n                onClick={confirm}\n                icon=\"trash\"\n                responsive=\"icon-only\"\n              >\n                <Trans\n                  id=\"message-list.message.action.delete\"\n                  message=\"Delete\"\n                />\n              </Button>\n            )}\n          />\n          <Button\n            className=\"m-message-action-container__action\"\n            onClick={handleToggleMarkAsRead}\n            responsive=\"icon-only\"\n            icon={message.is_unread ? 'envelope-open' : 'envelope'}\n          >\n            {message.is_unread ? (\n              <Trans\n                id=\"message-list.message.action.mark_as_read\"\n                message=\"Mark as read\"\n              />\n            ) : (\n              <Trans\n                id=\"message-list.message.action.mark_as_unread\"\n                message=\"Mark as unread\"\n              />\n            )}\n          </Button>\n        </footer>\n      )}\n    </article>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MailMessage/mail-message-details.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-mail-message-details {\n  @include flex-grid-row($size: expand);\n\n  margin-right: 1rem;\n  margin-left: 1rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n  color: grey;\n\n  &__author,\n  &__recipients {\n    @include flex-grid-size(12);\n\n    color: #858585;\n    font-size: 0.825rem;\n  }\n\n  &--encrypted__icon {\n    color: map_get($privacy-palette, 'super');\n  }\n\n  &__author-name {\n    font-weight: 800;\n  }\n\n  @include breakpoint(medium) {\n    border-bottom: 1px solid #f9f9f9;\n\n    &__recipients,\n    &__author {\n      @include flex-grid-size(6);\n    }\n\n    &__recipients {\n      text-align: right;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MailMessage/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n/* XXX add a margin when MailMessage is followed by MailMessage */\n.m-instant-message + .s-mail-message {\n  margin-top: 2rem;\n}\n\n.s-mail-message {\n  display: grid;\n  grid-template-columns: 1fr;\n  grid-template-rows: auto;\n  grid-template-areas:\n    'details'\n    'info'\n    'container'\n    'actions';\n  margin-bottom: 1rem;\n  background-color: #fff;\n\n  &__info {\n    grid-area: info;\n    display: flex;\n    flex-direction: row;\n    flex-wrap: wrap;\n    padding: 1rem;\n    background-color: #eee;\n\n    &--super {\n      background-color: #edfffc;\n    }\n\n    &--good {\n      background-color: #edfffc;\n    }\n\n    &--bad {\n      background-color: #edfffc;\n    }\n\n    &--ugly {\n      background-color: #fff4f7;\n    }\n  }\n\n  &__tags {\n    margin-top: $co-component__spacing;\n  }\n\n  &__participants,\n  &__tags {\n    display: none;\n  }\n\n  &__details {\n    grid-area: details;\n  }\n\n  &__container {\n    grid-area: container;\n  }\n\n  &__details,\n  &__container {\n    overflow: hidden;\n  }\n\n  &__encrypted {\n    float: right;\n    color: map_get($privacy-palette, 'super');\n    font-size: 1rem;\n    font-weight: normal;\n    text-align: right;\n  }\n\n  &__content,\n  &__subject {\n    padding: $co-component__spacing;\n    border-bottom: 1px solid #f9f9f9;\n    color: #515151;\n  }\n\n  &__content {\n    padding-bottom: 3rem;\n  }\n\n  &__actions {\n    grid-area: actions;\n    background-color: $co-color__bg__action;\n    font-size: 0.88rem;\n    text-align: right;\n  }\n\n  // XXX: to remove when attachments decryption is available\n  &__encrypted-attachments-info {\n    margin-right: 1rem;\n    margin-left: 1rem;\n  }\n\n  @include breakpoint(medium) {\n    grid-template-columns: 13rem 1fr;\n    grid-template-rows: $co-component__height auto $co-component__height;\n    grid-template-areas:\n      'info details'\n      'info container'\n      'info actions';\n\n    &__info {\n      float: left;\n      flex-direction: column;\n      order: -1;\n      width: 13rem;\n      padding: 3rem 1.5rem;\n    }\n\n    &__details {\n      align-self: center;\n    }\n\n    &__participants,\n    &__tags {\n      display: block;\n    }\n\n    &__participants-from,\n    &__participants-to {\n      overflow: hidden;\n      text-overflow: ellipsis;\n      font-size: $co-font__size--small;\n\n      a,\n      a:visited {\n        color: #515151;\n        font-weight: 600;\n      }\n    }\n\n    &__content,\n    &__subject {\n      margin-right: 6rem;\n      margin-left: 6rem;\n    }\n\n    &__actions {\n      padding: 0 6rem;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/Message.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { useDispatch } from 'react-redux';\nimport VisibilitySensor from 'react-visibility-sensor';\nimport { useHistory } from 'react-router-dom';\nimport { Modal } from 'src/components';\nimport { isMessageEncrypted } from 'src/services/encryption';\nimport { STATUS_DECRYPTED } from 'src/store/modules/encryption';\nimport { messageEncryptionStatusSelector } from 'src/modules/encryption';\nimport {\n  Message as MessageClass,\n  PROTOCOL_EMAIL,\n  PROTOCOL_MASTODON,\n  PROTOCOL_TWITTER,\n  setMessageRead,\n} from 'src/modules/message';\nimport { ManageEntityTags } from 'src/modules/tags';\nimport { reply } from 'src/modules/draftMessage';\nimport { useScrollToMe } from 'src/modules/scroll';\nimport { useSelector } from 'src/store/reducer';\nimport MailMessage from './MailMessage';\nimport InstantMessage from './InstantMessage';\n\ninterface Props extends withI18nProps {\n  message: MessageClass;\n  scrollToMe: () => string;\n  noInteractions?: boolean;\n  onDeleteMessageSuccess: () => any;\n}\n\nfunction Message({\n  message,\n  i18n,\n  noInteractions,\n  onDeleteMessageSuccess,\n}: Props) {\n  const history = useHistory();\n  const dispatch = useDispatch();\n  const ref = useScrollToMe(`#${message.message_id}`);\n  const [isTagModalOpen, setTagModalOpen] = React.useState(false);\n\n  const encryptionStatus = useSelector((state) =>\n    messageEncryptionStatusSelector(state, message.message_id)\n  );\n  const isDecrypted = encryptionStatus?.status === STATUS_DECRYPTED;\n  const hasEncryption = isMessageEncrypted(message);\n  const isLocked = hasEncryption && !isDecrypted;\n  const isMail = !message.protocol || message.protocol === 'email';\n\n  const onVisibilityChange = (isVisible) => {\n    if (!isLocked && isVisible && message.is_unread) {\n      dispatch(setMessageRead({ message, isRead: true }));\n    }\n  };\n\n  const handleOpenTags = () => {\n    setTagModalOpen(true);\n  };\n\n  const handleCloseTags = () => {\n    setTagModalOpen(false);\n  };\n\n  const handleReplyMessage = async () => {\n    const draft = (await dispatch(reply(message))) as any as MessageClass;\n    history.push(`/messages/${draft.message_id}`);\n  };\n\n  let content: React.ReactNode = null;\n  switch (message.protocol) {\n    default:\n    case PROTOCOL_EMAIL:\n      content = (\n        <MailMessage\n          forwardedRef={ref}\n          message={message}\n          onOpenTags={handleOpenTags}\n          onDeleteMessageSuccess={onDeleteMessageSuccess}\n          onReply={handleReplyMessage}\n          encryptionStatus={encryptionStatus}\n          isLocked={isLocked}\n        />\n      );\n      break;\n\n    case PROTOCOL_TWITTER:\n    case PROTOCOL_MASTODON:\n      content = (\n        <InstantMessage\n          forwardedRef={ref}\n          message={message}\n          onOpenTags={handleOpenTags}\n          onDeleteMessageSuccess={onDeleteMessageSuccess}\n          onReply={handleReplyMessage}\n          encryptionStatus={encryptionStatus}\n          isLocked={isLocked}\n        />\n      );\n      break;\n  }\n\n  return (\n    <VisibilitySensor\n      // @ts-ignore: actually : void|false|string\n      partialVisibility=\"bottom\"\n      onChange={onVisibilityChange}\n      scrollCheck\n      scrollThrottle={2000}\n    >\n      <>\n        {content}\n        {!noInteractions && (\n          <Modal\n            isOpen={isTagModalOpen}\n            contentLabel={i18n._(/* i18n */ 'tags.header.label', undefined, {\n              message: 'Tags',\n            })}\n            title={\n              <Trans\n                id=\"tags.header.title\"\n                message=\"Tags <0>(Total: {nb})</0>\"\n                values={{ nb: message.tags ? message.tags.length : 0 }}\n                components={[<span className=\"m-tags-form__count\" />]}\n              />\n            }\n            onClose={handleCloseTags}\n          >\n            <ManageEntityTags type=\"message\" entity={message} />\n          </Modal>\n        )}\n      </>\n    </VisibilitySensor>\n  );\n}\n\nexport default withI18n()(Message);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MessageAttachments/index.jsx",
    "content": "import React, { Component, createRef } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n } from '@lingui/react';\n// FIXME: why webpack ignores this ts lib?\nimport { CancelToken } from 'axios/dist/axios';\n\nimport { Button, TextBlock, FileSize, Icon } from '../../../../components';\nimport getClient from '../../../../services/api-client';\nimport DownloadFileProgression from '../DownloadFileProgression';\nimport './style.scss';\n\n@withI18n()\nclass MessageAttachments extends Component {\n  static propTypes = {\n    message: PropTypes.shape({}).isRequired,\n  };\n\n  static defaultProps = {};\n\n  state = {\n    downloads: {},\n  };\n\n  downloadRefs = {};\n\n  cancelTokenSource = CancelToken.source();\n\n  componentWillUnmount() {\n    this.cancelTokenSource.cancel();\n  }\n\n  getRef = (index) => {\n    if (!this.downloadRefs[index] || !this.downloadRefs[index].ref) {\n      this.downloadRefs[index] = {\n        ...this.downloadRefs[index],\n        ref: createRef(),\n      };\n    }\n\n    return this.downloadRefs[index].ref;\n  };\n\n  sendFileToBrowser = (index, { data, filename, mime }) => {\n    const blob = new Blob([data], { type: mime || 'application/octet-stream' });\n    const blobURL = window.URL.createObjectURL(blob);\n    const link = document.createElement('a');\n    link.style.display = 'none';\n    link.href = blobURL;\n    link.setAttribute('download', filename);\n    this.downloadRefs[index].ref.current.appendChild(link);\n    link.click();\n    this.downloadRefs[index].ref.current.removeChild(link);\n    window.URL.revokeObjectURL(blobURL);\n  };\n\n  createHandleClickDownload = ({ messageId, index, filename }) => {\n    const client = getClient();\n\n    return async () => {\n      this.setState((prevState) => ({\n        downloads: {\n          ...prevState.downloads,\n          [index]: {\n            isFetching: true,\n          },\n        },\n      }));\n      const response = await client.get(\n        `/api/v2/messages/${messageId}/attachments/${index}`,\n        {\n          cancelToken: this.cancelTokenSource.token,\n          responseType: 'blob',\n          onDownloadProgress: (ev) =>\n            this.setState((prevState) => ({\n              downloads: {\n                ...prevState.downloads,\n                [index]: {\n                  progression: ev.loaded,\n                  isFetching: ev.loaded < ev.total,\n                },\n              },\n            })),\n        }\n      );\n      this.sendFileToBrowser(index, { data: response.data, filename });\n    };\n  };\n\n  renderDownload({ attachment, index }) {\n    const {\n      message: { message_id: messageId },\n    } = this.props;\n\n    return (\n      <Button\n        className=\"m-message-attachments__item\"\n        display=\"expanded\"\n        onClick={this.createHandleClickDownload({\n          messageId,\n          index,\n          filename: attachment.file_name,\n        })}\n      >\n        <TextBlock className=\"m-message-attachments__name\">\n          {attachment.file_name}\n        </TextBlock>\n        <DownloadFileProgression\n          className=\"m-message-attachments__progression\"\n          isFetching={\n            this.state.downloads[index] &&\n            this.state.downloads[index].isFetching\n          }\n          value={\n            this.state.downloads[index] &&\n            this.state.downloads[index].progression\n          }\n          max={attachment.size}\n        />\n        <TextBlock className=\"m-message-attachments__size\">\n          <FileSize size={attachment.size} />\n        </TextBlock>\n        <span className=\"m-message-attachments__icon\">\n          <Icon type=\"download\" />\n        </span>\n      </Button>\n    );\n  }\n\n  render() {\n    const {\n      message: { attachments },\n    } = this.props;\n\n    if (!attachments || attachments.length === 0) {\n      return null;\n    }\n\n    return (\n      <ul className=\"m-message-attachments\">\n        {attachments.map((attachment, index) => (\n          <li key={index} ref={this.getRef(index)}>\n            {this.renderDownload({ attachment, index })}\n          </li>\n        ))}\n      </ul>\n    );\n  }\n}\n\nexport default MessageAttachments;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MessageAttachments/style.scss",
    "content": "@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-message-attachments {\n  padding-top: $co-component__spacing;\n  border-top: 1px solid $co-color__fg__border--higher;\n\n  &__item {\n    @include flex-grid-row;\n\n    margin-top: $co-component__spacing;\n    background: $co-color__fg__border--higher;\n    font-size: $co-font__size--small;\n    font-weight: 700;\n    line-height: $co-component__height;\n    text-align: left;\n  }\n\n  &__name {\n    @include flex-grid-size;\n  }\n\n  &__size {\n    @include flex-grid-column(shrink);\n\n    margin-left: auto;\n  }\n\n  &__icon {\n    @include flex-grid-size(shrink);\n\n    margin-left: auto;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MessageList/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MessageList/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Button, PlaceholderBlock } from '../../../../components';\nimport Message from '../Message';\nimport ProtocolSwitch from '../ProtocolSwitch';\nimport { getAveragePIMessage } from '../../../../modules/pi';\nimport { withSettings } from '../../../../modules/settings';\nimport { CheckDecryption } from '../../../../modules/encryption';\n\nimport './style.scss';\n\n@withSettings()\nclass MessageList extends Component {\n  static propTypes = {\n    messages: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n    loadMore: PropTypes.func,\n    hasMore: PropTypes.bool,\n    scrollToTarget: PropTypes.func,\n    isFetching: PropTypes.bool.isRequired,\n    onMessageRead: PropTypes.func.isRequired,\n    onMessageUnread: PropTypes.func.isRequired,\n    onDeleteMessageSuccess: PropTypes.func.isRequired,\n    user: PropTypes.shape({}),\n    hash: PropTypes.string,\n    settings: PropTypes.shape({}).isRequired,\n  };\n\n  static defaultProps = {\n    scrollToTarget: undefined,\n    hash: undefined,\n    loadMore: undefined,\n    hasMore: false,\n    user: undefined,\n  };\n\n  findMessageBefore(message) {\n    const { messages } = this.props;\n    const index = messages.indexOf(message) - 1;\n\n    return messages[index];\n  }\n\n  renderPlaceholder = () => (\n    <div className=\"m-message-list\">\n      {[1, 2, 3].map((n) => (\n        <PlaceholderBlock key={n} className=\"m-message-list__placeholder\" />\n      ))}\n    </div>\n  );\n\n  renderLoadMore() {\n    const { hasMore, loadMore, isFetching } = this.props;\n\n    return (\n      loadMore &&\n      !isFetching &&\n      hasMore && (\n        <Button\n          shape=\"hollow\"\n          onClick={loadMore}\n          className=\"m-message-list__load-more\"\n        >\n          <Trans id=\"general.action.load_more\" message=\"Load more\" />\n        </Button>\n      )\n    );\n  }\n\n  renderList() {\n    const {\n      messages,\n      onMessageRead,\n      onMessageUnread,\n      onMessageDelete,\n      hash,\n      scrollToTarget,\n      user,\n      settings,\n      onDeleteMessageSuccess,\n    } = this.props;\n\n    return messages.reduce((acc, message) => {\n      const result = [...acc];\n      if (\n        message.pi_message &&\n        message.protocol !== 'email' &&\n        acc.length > 0 &&\n        this.findMessageBefore(message).protocol !== message.protocol\n      ) {\n        result.push(\n          <ProtocolSwitch\n            newProtocol={message.protocol}\n            pi={getAveragePIMessage({ message })}\n            date={message.date}\n            key={`switch-${message.message_id}`}\n            settings={settings}\n          />\n        );\n      }\n\n      result.push(\n        <Message\n          onMessageRead={onMessageRead}\n          onMessageUnread={onMessageUnread}\n          onDeleteMessageSuccess={onDeleteMessageSuccess}\n          message={message}\n          key={message.message_id}\n          scrollToMe={message.message_id === hash ? scrollToTarget : undefined}\n          user={user}\n          settings={settings}\n        />\n      );\n\n      return result;\n    }, []);\n  }\n\n  render() {\n    const { isFetching, messages } = this.props;\n\n    if (isFetching && messages.length === 0) {\n      return this.renderPlaceholder();\n    }\n\n    return (\n      <div className=\"m-message-list\">\n        <div className=\"m-message-list__load-more\">{this.renderLoadMore()}</div>\n        {this.renderList()}\n        <CheckDecryption messages={messages} />\n      </div>\n    );\n  }\n}\n\nexport default MessageList;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MessageList/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-message-list {\n  &__placeholder {\n    height: $co-component__height--large;\n    margin-bottom: map_get($co-margin_, small);\n  }\n\n  &__load-more {\n    text-align: center;\n\n    // FIXME\n    & .m-button {\n      margin: 4px 0 3px;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MessagePi/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans } from '@lingui/react';\nimport { getAveragePIMessage, PiScore } from 'src/modules/pi';\nimport { isMessageEncrypted } from 'src/services/encryption';\nimport { Message } from 'src/modules/message';\nimport './style.scss';\n\ninterface Props {\n  message: Message;\n  describe?: boolean;\n}\nexport default function MessagePi({ message, describe }: Props) {\n  if (!message.pi_message) {\n    return null;\n  }\n\n  const piAggregate = getAveragePIMessage({ message });\n  return (\n    <div className=\"m-message-pi\">\n      <PiScore average={piAggregate} className=\"m-message-pi__pi-score\" />\n      {describe && (\n        <p className=\"m-message-pi__metaphor\">\n          {isMessageEncrypted(message) ? (\n            <Trans\n              id=\"message.pi.description.metaphor.encrypted\"\n              message=\"In real life this message would be somewhat equivalent to letter within an envelope.\"\n            />\n          ) : (\n            <Trans\n              id=\"message.pi.description.metaphor.unencrypted\"\n              message=\"In real life this message would be somewhat equivalent to a postal card visible by everyone.\"\n            />\n          )}\n        </p>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MessagePi/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.ugly {\n  .m-message-pi__metaphor,\n  .m-message-pi__meter {\n    border-color: map_get($privacy-palette-high, 'ugly');\n  }\n\n  .m-message-pi__numeric-value {\n    color: map_get($privacy-palette, 'ugly');\n  }\n}\n\n.bad {\n  .m-message-pi__metaphor,\n  .m-message-pi__meter {\n    border-color: map_get($privacy-palette-high, 'bad');\n  }\n\n  .m-message-pi__numeric-value {\n    color: map_get($privacy-palette, 'bad');\n  }\n}\n\n.good {\n  .m-message-pi__metaphor,\n  .m-message-pi__meter {\n    border-color: map_get($privacy-palette-high, 'good');\n  }\n\n  .m-message-pi__numeric-value {\n    color: map_get($privacy-palette, 'good');\n  }\n}\n\n.super {\n  .m-message-pi__metaphor,\n  .m-message-pi__meter {\n    border-color: map_get($privacy-palette-high, 'super');\n  }\n\n  .m-message-pi__numeric-value {\n    color: map_get($privacy-palette, 'super');\n  }\n}\n\n.m-message-pi {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n  width: 100%;\n\n  &__pi-score {\n    width: 10rem; // compat with aside\n  }\n\n  &__progress {\n    height: map_get($co-pi-height, 'progressbar');\n    margin-bottom: 0;\n\n    &--ugly {\n      background-color: map_get($privacy-palette-high, 'ugly');\n    }\n\n    &--bad {\n      background-color: map_get($privacy-palette-high, 'bad');\n    }\n\n    &--good {\n      background-color: map_get($privacy-palette-high, 'good');\n    }\n\n    &--super {\n      background-color: map_get($privacy-palette-high, 'super');\n    }\n  }\n\n  &__meter {\n    flex-grow: 1;\n    width: 45%;\n    border-bottom: 2px solid #999;\n\n    &--no-separator {\n      border-bottom: none;\n    }\n  }\n\n  &__progress-meter {\n    height: map_get($co-pi-height, 'progressbar');\n\n    &--ugly {\n      background-color: map_get($privacy-palette, 'ugly');\n    }\n\n    &--bad {\n      background-color: map_get($privacy-palette, 'bad');\n    }\n\n    &--good {\n      background-color: map_get($privacy-palette, 'good');\n    }\n\n    &--super {\n      background-color: map_get($privacy-palette, 'super');\n    }\n  }\n\n  &__numeric {\n    display: flex;\n    align-items: center;\n\n    &-legend {\n      font-size: $co-font__size--xsmall;\n    }\n\n    &-value {\n      flex-grow: 1;\n      font-size: $co-font__size--large;\n      font-weight: 500;\n      text-align: right;\n    }\n  }\n\n  &__illustration {\n    display: flex;\n    flex-direction: row;\n    width: 45%;\n    margin-bottom: 0.825rem;\n\n    img,\n    i {\n      margin-right: 0.25rem;\n      font-size: $co-font__size--small;\n    }\n  }\n\n  &__types {\n    li {\n      font-size: $co-font__size--xsmall;\n      font-weight: 600;\n      line-height: 1rem;\n\n      &.super {\n        color: map_get($privacy-palette, 'super');\n      }\n\n      &.good {\n        color: map_get($privacy-palette, 'good');\n      }\n\n      &.bad {\n        color: map_get($privacy-palette, 'bad');\n      }\n\n      &.ugly {\n        color: map_get($privacy-palette, 'ugly');\n      }\n    }\n  }\n\n  &__metaphor {\n    color: #999;\n    font-size: $co-font__size--small;\n    font-style: italic;\n  }\n\n  @include breakpoint(medium) {\n    &__illustration,\n    &__meter,\n    &__numeric {\n      width: 100%;\n    }\n\n    &__metaphor {\n      width: 100%;\n      padding-bottom: map_get($co-margin_, 'xsmall');\n      border-bottom: 2px solid #999;\n      font-size: $co-font__size--xsmall;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/MessageRecipients.tsx",
    "content": "import * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport { useUser } from 'src/modules/user';\nimport { Message } from 'src/modules/message';\nimport {\n  getRecipients,\n  getRecipientsExceptUser,\n  isUserRecipient,\n} from '../../../services/message';\n\ninterface Props extends withI18nProps {\n  message: Message;\n  shorten?: boolean;\n}\nfunction MessageRecipients({ message, shorten = false, i18n }: Props) {\n  const { user } = useUser();\n\n  const getRecipientsLabels = (recipients) => {\n    if (!recipients) return [];\n\n    return recipients.map((recipient) =>\n      recipient.label ? recipient.label : recipient.address\n    );\n  };\n\n  const recipients =\n    user && isUserRecipient(message, user)\n      ? [\n          i18n._(/* i18n */ 'message.participants.me', undefined, {\n            message: 'Me',\n          }),\n          ...getRecipientsLabels(getRecipientsExceptUser(message, user)),\n        ]\n      : getRecipientsLabels(getRecipients(message));\n\n  const numberRecipients = recipients.length;\n\n  if (numberRecipients === 0) {\n    return i18n._(/* i18n */ 'message.participants.me', undefined, {\n      message: 'Me',\n    });\n  }\n  if (!shorten || numberRecipients === 1) return recipients.join(', ');\n\n  const recipientsStr = i18n._(\n    /* i18n */ 'messages.participants.and_x_others',\n    { first: recipients[0], number: numberRecipients - 1 },\n    {\n      message: '{first} and {number, plural, one {# other} other {# others}}',\n    }\n  );\n\n  return <span className=\"m-message-recipients\">{recipientsStr}</span>;\n}\n\nexport default withI18n()(MessageRecipients);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/ProtocolSwitch/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport Moment from 'react-moment';\nimport { getPiClass } from '../../../../modules/pi';\n\nimport './style.scss';\n\nclass ProtocolSwitch extends PureComponent {\n  static propTypes = {\n    newProtocol: PropTypes.string.isRequired,\n    date: PropTypes.string.isRequired,\n    pi: PropTypes.number.isRequired,\n    settings: PropTypes.shape({ default_locale: PropTypes.string.isRequired })\n      .isRequired,\n  };\n\n  protocolIcon = (protocol) => {\n    switch (protocol) {\n      case 'facebook':\n        return 'facebook-square';\n      case 'twitter':\n        return 'twitter';\n      case 'mastodon':\n        return 'mastodon';\n      case 'sms':\n        return 'phone';\n      case 'email':\n        return 'envelope';\n      default:\n        return 'comment';\n    }\n  };\n\n  render() {\n    const {\n      pi,\n      newProtocol,\n      date,\n      settings: { default_locale: locale },\n    } = this.props;\n\n    return (\n      <div className={`m-protocol-switch ${getPiClass(pi)}`}>\n        <div className=\"m-protocol-switch__bar\" />\n        <i className={`fa fa-${this.protocolIcon(newProtocol)}`} />\n        &nbsp;\n        <Moment fromNow locale={locale}>\n          {date}\n        </Moment>\n      </div>\n    );\n  }\n}\n\nexport default ProtocolSwitch;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/ProtocolSwitch/style.scss",
    "content": ".m-protocol-switch {\n  display: flex;\n  flex-direction: row;\n  align-content: center;\n  justify-content: stretch;\n  width: 100%;\n  margin: 1rem 0;\n  font-size: 0.875rem;\n}\n\n.m-protocol-switch__bar {\n  flex-grow: 1;\n  height: 0.25rem;\n  margin: 0.375rem 1rem 0.375rem 0;\n  background-color: #c8e9f9;\n}\n\n.ugly {\n  .m-protocol-switch__bar {\n    background-color: #edccd4;\n  }\n}\n\n.super {\n  .m-protocol-switch__bar {\n    background-color: #0cbe8e;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/QuickDraftForm/components/ToggleAdvancedFormButton/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n } from '@lingui/react';\nimport { Button, Icon, Spinner } from '../../../../../../components';\nimport './toggle-advanced-draft-button.scss';\n\nfunction ToggleAdvancedFormButton({\n  i18n,\n  handleToggleAdvancedForm,\n  hasActivity,\n}) {\n  return (\n    <Button\n      display=\"expanded\"\n      shape=\"plain\"\n      className=\"m-toggle-advanced-draft-button\"\n      title={i18n._(/* i18n */ 'draft-message.action.toggle-advanced', null, {\n        message: 'Toggle advanced or quick message form',\n      })}\n      onClick={handleToggleAdvancedForm}\n      disabled={hasActivity}\n    >\n      {hasActivity ? (\n        <Spinner\n          svgTitleId=\"toggle-advanced-draft-spinner\"\n          display=\"inline\"\n          theme=\"bright\"\n        />\n      ) : (\n        <Icon type=\"envelope\" />\n      )}\n      <Icon type=\"caret-down\" />\n    </Button>\n  );\n}\n\nToggleAdvancedFormButton.propTypes = {\n  i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  handleToggleAdvancedForm: PropTypes.func.isRequired,\n  advancedForm: PropTypes.bool,\n  hasActivity: PropTypes.bool,\n};\nToggleAdvancedFormButton.defaultProps = {\n  advancedForm: false,\n  hasActivity: false,\n};\n\nexport default withI18n()(ToggleAdvancedFormButton);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/QuickDraftForm/components/ToggleAdvancedFormButton/toggle-advanced-draft-button.scss",
    "content": "@import '../../../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-toggle-advanced-draft-button {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  height: 100%;\n  padding-top: 0.375rem;\n  background-color: #07b798;\n  font-size: 1.3rem;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/QuickDraftForm/draft-message-quick.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../../styles/vendor/bootstrap_font-awesome';\n@import '../../../../styles/object/o-form-element';\n\n.m-draft-message-quick {\n  &__recipient-list {\n    margin-right: $co-margin;\n    margin-bottom: $co-margin;\n    margin-left: $co-margin;\n  }\n\n  &__container {\n    @include flex-grid-row($size: expand);\n\n    border: 1px solid #9a989b;\n    background-color: $co-color__bg__action;\n  }\n\n  &__encryption {\n    text-align: right;\n  }\n\n  &__toggle-advanced {\n    @include flex-grid-size(shrink);\n\n    width: 3.438rem;\n    height: 3.438rem;\n  }\n\n  &__input {\n    @include flex-grid-size;\n    @include o-form-element;\n    @include o-form-element--light;\n\n    width: 100%;\n    border: none;\n\n    &--encrypted {\n      position: relative;\n    }\n  }\n\n  &__send {\n    @include flex-grid-size(shrink);\n\n    width: 3.438rem;\n    height: 3.438rem;\n  }\n\n  &__send-button {\n    position: relative;\n    height: 100%;\n    font-size: 1.3rem;\n\n    &::after {\n      @include fa-icon;\n\n      position: absolute;\n      right: 0;\n      bottom: 0;\n      width: 1rem;\n      height: 1rem;\n      font-size: 1rem;\n    }\n\n    &--encrypted {\n      background-color: map_get($privacy-palette, 'super');\n\n      &::after {\n        content: $fa-var-lock;\n      }\n    }\n\n    &--unencrypted {\n      background-color: map_get($privacy-palette, 'good');\n      color: $co-color__fg__text;\n\n      &::after {\n        content: $fa-var-unlock;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/QuickDraftForm/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { withI18n, Trans, withI18nProps } from '@lingui/react';\nimport { compose } from 'redux';\nimport { useSelector, useDispatch } from 'react-redux';\nimport { useHistory } from 'react-router-dom';\nimport { useCurrentTab, useCloseTab } from 'src/modules/tab';\nimport { IDraftMessageFormData } from 'src/modules/draftMessage/types';\nimport { useScrollToMe } from 'src/modules/scroll';\nimport { useAvailableIdentities } from 'src/modules/draftIdentity';\nimport { RootState } from 'src/store/reducer';\nimport {\n  Button,\n  Spinner,\n  FieldErrors,\n  Link,\n  PlaceholderBlock,\n} from 'src/components';\n\nimport {\n  LockedMessage,\n  messageEncryptionStatusSelector,\n} from 'src/modules/encryption';\nimport {\n  discussionDraftSelector,\n  getOrCreateDiscussionDraft,\n  saveDraft,\n  validate,\n  sendDraft,\n} from 'src/modules/draftMessage';\nimport { messageSelector } from 'src/modules/message';\nimport { notifyError } from 'src/modules/userNotify';\n\nimport { isMessageEncrypted } from 'src/services/encryption';\nimport { STATUS_DECRYPTED, STATUS_ERROR } from 'src/store/modules/encryption';\n\nimport ToggleAdvancedFormButton from './components/ToggleAdvancedFormButton';\nimport './draft-message-quick.scss';\n\nexport const KEY = {\n  ENTER: 13,\n};\n\nfunction useDraftMessage(\n  discussionId: string\n): undefined | IDraftMessageFormData {\n  const dispatch = useDispatch();\n  const draftMessage = useSelector((state: RootState) =>\n    discussionDraftSelector(state, discussionId)\n  );\n  React.useEffect(() => {\n    if (!draftMessage) {\n      dispatch(getOrCreateDiscussionDraft({ discussionId }));\n    }\n  }, [draftMessage]);\n\n  return draftMessage;\n}\ninterface QuickDraftFormProps extends withI18nProps {\n  encryptionChildren?: React.ReactNode;\n  className?: string;\n  innerRef: React.Ref<HTMLDivElement>;\n  onFocus: any;\n  discussionId: string;\n}\n\nfunction QuickDraftForm({\n  i18n,\n  encryptionChildren = null,\n  className = undefined,\n  innerRef,\n  onFocus,\n  discussionId,\n}: QuickDraftFormProps) {\n  const ref = useScrollToMe('#reply', { focusable: true });\n  const dispatch = useDispatch();\n  const [isSaving, setIsSaving] = React.useState(false);\n  const [isSending, setIsSending] = React.useState(false);\n  const draftMessage = useDraftMessage(discussionId);\n  const message = useSelector(\n    (state) =>\n      draftMessage &&\n      messageSelector(state, { messageId: draftMessage.message_id })\n  );\n\n  const closeTab = useCloseTab();\n  const tab = useCurrentTab();\n  const history = useHistory();\n\n  const availableIdentities = useAvailableIdentities(draftMessage);\n\n  const draftEncryption = useSelector<RootState>(\n    (state) =>\n      draftMessage &&\n      messageEncryptionStatusSelector(state, draftMessage.message_id)\n  );\n  const isEncrypted = message && isMessageEncrypted(message);\n\n  const isLocked =\n    isEncrypted &&\n    // @ts-ignore\n    ![STATUS_DECRYPTED, STATUS_ERROR].includes(draftEncryption.status);\n  const encryptionEnabled =\n    // @ts-ignore\n    isEncrypted && draftEncryption.status === STATUS_DECRYPTED;\n\n  const handleChange = (ev) => {\n    if (!draftMessage) {\n      return;\n    }\n\n    const { name, value } = ev.target;\n    dispatch(\n      saveDraft(\n        {\n          ...draftMessage,\n          [name]: value,\n        },\n        { withThrottle: true }\n      )\n    );\n  };\n\n  const getRecipientList = () => {\n    // participants may not be present when the draft is new, it is the responsibility of the\n    // backend to calculate what will be the participants for a reply\n    if (!draftMessage || !draftMessage.recipients) {\n      return null;\n    }\n\n    const { recipients } = draftMessage;\n\n    if (!recipients) {\n      return '';\n    }\n\n    if (recipients.length > 3) {\n      return recipients\n        .map((recipient) => recipient.address)\n        .join(', ')\n        .concat('...');\n    }\n\n    return recipients.map((recipient) => recipient.address).join(', ');\n  };\n\n  const getQuickInputPlaceholder = () => {\n    if (!draftMessage) {\n      return undefined;\n    }\n    const { identifier } =\n      availableIdentities.find(\n        (identity) => identity.identity_id === draftMessage.identity_id\n      ) || {};\n\n    const recipientsList = getRecipientList();\n\n    if (draftMessage && recipientsList && identifier) {\n      return i18n._(\n        /* i18n */ 'draft-message.form.placeholder.quick-reply',\n        { recipients: recipientsList, protocol: identifier },\n        { message: 'Quick reply to {recipients} from {protocol}' }\n      );\n    }\n\n    if (draftMessage && identifier) {\n      return i18n._(\n        /* i18n */ 'draft-message.form.placeholder.quick-reply-no-recipients',\n        { identifier },\n        { message: 'Quick reply from {identifier}' }\n      );\n    }\n\n    return i18n._(\n      /* i18n */ 'draft-message.form.placeholder.quick-start',\n      undefined,\n      {\n        message: 'Start a new discussion',\n      }\n    );\n  };\n\n  const handleSend = async (ev) => {\n    ev.preventDefault();\n    setIsSending(true);\n\n    if (!draftMessage) {\n      return;\n    }\n\n    try {\n      const savedMessage = await dispatch(\n        saveDraft(draftMessage, {\n          withThrottle: false,\n        })\n      );\n      // @ts-ignore: probably related to middlewares typings\n      await dispatch(sendDraft(savedMessage));\n\n      setIsSending(false);\n    } catch (err) {\n      dispatch(\n        notifyError({\n          message: i18n._(/* i18n */ 'draft.feedback.send-error', undefined, {\n            message: 'Unable to send the message',\n          }),\n        })\n      );\n      setIsSending(false);\n    }\n  };\n\n  const handlePressSendShortKey = (ev) => {\n    const { which: keyCode, ctrlKey } = ev;\n    if (keyCode === KEY.ENTER && ctrlKey) {\n      ev.preventDefault();\n      handleSend(ev);\n    }\n  };\n\n  const handleClickToggleAdvanced = async () => {\n    if (!draftMessage) {\n      return;\n    }\n    setIsSaving(true);\n    try {\n      await dispatch(saveDraft(draftMessage));\n      history.push(`/messages/${draftMessage.message_id}#compose`);\n      closeTab(tab);\n    } catch (err) {\n      setIsSaving(false);\n    }\n  };\n\n  // TODO: isLoaded & isLocked\n  if (!draftMessage) {\n    // const ref = (el) => {\n    //   innerRef(el);\n    //   forwardRef(el);\n    // };\n\n    return (\n      <div ref={innerRef}>\n        <PlaceholderBlock\n          className={classnames(className, 'm-draft-message-placeholder')}\n        />\n      </div>\n    );\n  }\n\n  const errors = validate({\n    draftMessage,\n    i18n,\n    availableIdentities,\n    options: { ignoreBody: true },\n  });\n  if (errors.length) {\n    return (\n      <div className=\"m-quickdraft-errors\" ref={innerRef}>\n        <FieldErrors errors={errors} />\n        <p>\n          <Trans\n            id=\"draft-message.action.fix-error-on-advanced-form\"\n            message=\"Unable to send the message, you can fix it or delete it in the <0>Advanced form</0>\"\n            components={[<Link to={`/messages/${draftMessage.message_id}`} />]}\n          />\n        </p>\n      </div>\n    );\n  }\n\n  const canSend = !isSending && draftMessage.body.length > 0;\n\n  return (\n    // eslint-disable-next-line react/jsx-filename-extension\n    <div className={classnames(className, 'm-draft-message-quick')}>\n      <form onSubmit={handleSend}>\n        <div\n          className={classnames(className, 'm-draft-message-quick__container')}\n        >\n          <div className=\"m-draft-message-quick__toggle-advanced\">\n            <ToggleAdvancedFormButton\n              handleToggleAdvancedForm={handleClickToggleAdvanced}\n              hasActivity={isSaving}\n            />\n          </div>\n          {isLocked ? (\n            <LockedMessage encryptionStatus={draftEncryption} />\n          ) : (\n            <textarea\n              // @ts-ignore\n              ref={ref}\n              rows={/\\n+/.test(draftMessage.body) ? 7 : 1}\n              className={classnames('m-draft-message-quick__input', {\n                'm-draft-message-quick__input--encrypted': encryptionEnabled,\n              })}\n              onChange={handleChange}\n              onFocus={onFocus}\n              onKeyPress={handlePressSendShortKey}\n              name=\"body\"\n              value={draftMessage.body}\n              placeholder={getQuickInputPlaceholder()}\n            />\n          )}\n          <div\n            className={classnames('m-draft-message-quick__send', {\n              'm-draft-message-quick__send--encrypted': encryptionEnabled,\n              'm-draft-message-quick__send--unencrypted': !encryptionEnabled,\n            })}\n          >\n            <Button\n              // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n              type=\"submit\"\n              display=\"expanded\"\n              shape=\"plain\"\n              icon={\n                isSending ? (\n                  <Spinner svgTitleId=\"send-draft-spinner\" isLoading />\n                ) : (\n                  'paper-plane'\n                )\n              }\n              title={i18n._(/* i18n */ 'draft-message.action.send', undefined, {\n                message: 'Send',\n              })}\n              className={classnames('m-draft-message-quick__send-button', {\n                'm-draft-message-quick__send-button--encrypted':\n                  encryptionEnabled,\n                'm-draft-message-quick__send-button--unencrypted':\n                  !encryptionEnabled,\n              })}\n              disabled={!canSend}\n            />\n          </div>\n        </div>\n        <div className=\"m-draft-message-quick__encryption\">\n          {encryptionChildren}\n        </div>\n      </form>\n    </div>\n  );\n}\n\nexport default compose(\n  withI18n()\n  // @ts-ignore: can be fixed with compose(...[hoc,]) but then fails in forwardRef\n)(QuickDraftForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/ReplyExcerpt/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { compose } from 'redux';\nimport { connect } from 'react-redux';\nimport { withI18n } from '@lingui/react';\nimport { createMessageCollectionStateSelector } from '../../../../store/selectors/message';\nimport Presenter from './presenter';\n\nconst messageDraftSelector = (state) => state.draftMessage.draftsByMessageId;\nconst discussionIdSelector = (state, ownProps) => ownProps.discussionId;\n// FIXME: no mnessageId: use withdraft instead\nconst messageIdSelector = (state, ownProps) => ownProps.messageId;\nconst messageCollectionStateSelector = createMessageCollectionStateSelector(\n  () => 'discussion',\n  discussionIdSelector\n);\n\nconst mapStateToProps = createSelector(\n  [messageDraftSelector, messageIdSelector, messageCollectionStateSelector],\n  (drafts, messageId, { messages }) => {\n    const message = messages && messages.find((item) => item.is_draft === true);\n    const draft = drafts[messageId] || message;\n\n    return {\n      draft,\n    };\n  }\n);\n\nexport default compose(connect(mapStateToProps), withI18n())(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/ReplyExcerpt/presenter.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Icon } from '../../../../components';\nimport { draftExcerptIndex } from '../../../../modules/a11y';\nimport './style.scss';\n\nclass ReplyExcerpt extends PureComponent {\n  static propTypes = {\n    draft: PropTypes.shape({}),\n    onFocus: PropTypes.func.isRequired,\n    draftExcerptRef: PropTypes.func,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    draft: undefined,\n    draftExcerptRef: () => {\n      // noop\n    },\n    className: undefined,\n  };\n\n  render() {\n    const { draft, draftExcerptRef, onFocus, className } = this.props;\n\n    return (\n      <div\n        className={classnames('m-reply-excerpt', className)}\n        ref={draftExcerptRef}\n        onClick={onFocus}\n        onKeyPress={onFocus}\n        role=\"button\"\n        tabIndex={draftExcerptIndex}\n      >\n        <Icon type=\"pencil\" spaced />\n        {draft && draft.body}\n      </div>\n    );\n  }\n}\n\nexport default ReplyExcerpt;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/ReplyExcerpt/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-form-element';\n\n.m-reply-excerpt {\n  width: 100%;\n  height: $co-component__height;\n  padding: $co-component__spacing;\n  background: $co-color__fg__back;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  color: $co-color__fg__text;\n  white-space: nowrap;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/TagList/index.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n } from '@lingui/react';\nimport { compose } from 'redux';\nimport classnames from 'classnames';\nimport { Badge } from '../../../../components';\nimport { getTagLabelFromName, withTags } from '../../../../modules/tags';\n\nimport './style.scss';\n\nfunction TagList({ className, i18n, tags: allTags, message }) {\n  if (!message.tags || message.tags.length === 0) {\n    return null;\n  }\n\n  return (\n    <ul className={classnames(className, 'm-message-tags-list')}>\n      {message.tags.map((tag) => (\n        <li key={tag} className=\"s-mail-message__tag m-message-tags-list__tag\">\n          <Badge\n            to={`/search-results?term=${getTagLabelFromName(\n              i18n,\n              allTags,\n              tag\n            )}&doctype=message`}\n          >\n            {getTagLabelFromName(i18n, allTags, tag)}\n          </Badge>\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nTagList.propTypes = {\n  i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  className: PropTypes.string,\n  message: PropTypes.shape({\n    tags: PropTypes.arrayOf(PropTypes.string),\n  }).isRequired,\n  tags: PropTypes.arrayOf(PropTypes.shape({})),\n};\nTagList.defaultProps = {\n  className: undefined,\n  tags: [],\n};\n\nexport default compose(withTags(), withI18n())(TagList);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/components/TagList/style.scss",
    "content": "@import '../../../../styles/common';\n\n.m-message-tags-list {\n  &__tag {\n    display: inline;\n    margin-right: $co-component__spacing;\n    margin-bottom: $co-component__spacing;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/discussion-action-bar.scss",
    "content": "@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-discussion-action-bar {\n  @include flex-grid-row($size: expand);\n\n  justify-content: flex-end;\n\n  &__tags {\n    @include flex-grid-column(shrink);\n  }\n\n  &__actions {\n    @include flex-grid-size;\n    @include flex-grid-row($size: expand);\n\n    justify-content: flex-end;\n  }\n\n  &__action {\n    @include flex-grid-column(shrink);\n  }\n\n  &__action-label {\n    @include breakpoint(small) {\n      display: none;\n    }\n\n    display: block;\n    color: $co-color__primary--low;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport { userSelector } from 'src/modules/user';\nimport {\n  loadMore,\n  invalidate,\n  deleteMessage as deleteMessageRaw,\n} from '../../store/modules/message';\nimport {\n  setMessageRead,\n  deleteMessage,\n  requestDiscussion,\n  sortMessages,\n  getLastMessageFromArray,\n  getDraft,\n} from '../../modules/message';\nimport { reply, draftMessagesSelector } from '../../modules/draftMessage';\nimport { createMessageCollectionStateSelector } from '../../store/selectors/message';\nimport { withTags } from '../../modules/tags';\nimport { getUser } from '../../modules/user/actions/getUser';\nimport { withPush } from '../../modules/routing/hoc/withPush';\nimport Discussion from './presenter';\n\nconst getDiscussionIdFromProps = (props) => props.match.params.discussionId;\n// FIXME: bad selector, coupling w/ location\nconst discussionIdSelector = (state, ownProps) =>\n  getDiscussionIdFromProps(ownProps);\nconst discussionStateSelector = (state) => state.discussion;\n\nconst messageByIdSelector = (state) => state.message.messagesById;\nconst messageCollectionStateSelector = createMessageCollectionStateSelector(\n  () => 'discussion',\n  discussionIdSelector\n);\nconst sortedMessagesSelector = createSelector(\n  [messageByIdSelector, messageCollectionStateSelector],\n  (messagesById, { messageIds }) =>\n    sortMessages(\n      messageIds\n        .map((messageId) => messagesById[messageId])\n        .filter((msg) => msg.is_draft === false),\n      false\n    )\n);\n\nconst lastMessageSelector = createSelector(\n  [sortedMessagesSelector],\n  (sortedMessages) => getLastMessageFromArray(sortedMessages)\n);\nconst firstUnreadMessageSelector = createSelector(\n  [sortedMessagesSelector],\n  (sortedMessages) => sortedMessages.filter((message) => message.is_unread)[0]\n);\n\nconst draftMessageSelector = createSelector(\n  [\n    messageByIdSelector,\n    messageCollectionStateSelector,\n    draftMessagesSelector,\n    discussionIdSelector,\n  ],\n  (messagesById, { messageIds }, draftMessages, discussionId) => {\n    const draftMessage = draftMessages.find(\n      (draft) => draft.discussion_id === discussionId\n    );\n    if (draftMessage) {\n      return draftMessage;\n    }\n\n    if (messageIds.length === 0) {\n      return undefined;\n    }\n\n    const message = sortMessages(\n      messageIds\n        .filter(\n          (messageId) =>\n            messagesById[messageId] && messagesById[messageId].is_draft\n        )\n        .map((messageId) => messagesById[messageId]),\n      false\n    ).pop();\n\n    return message;\n  }\n);\n\n// actual saved drafts for the discussion\nconst messagesDraftByDiscussionIdSelector = createSelector(\n  [\n    (state) => state.message.messagesById,\n    (_, { discussionId }) => discussionId,\n  ],\n  (messagesByIdState, discussionId) =>\n    Object.keys(messagesByIdState)\n      .filter(\n        (id) =>\n          messagesByIdState[id]?.is_draft &&\n          messagesByIdState[id]?.discussion_id === discussionId\n      )\n      .map((id) => messagesByIdState[id])\n);\n\nconst mapStateToProps = createSelector(\n  [\n    sortedMessagesSelector,\n    lastMessageSelector,\n    firstUnreadMessageSelector,\n    messageByIdSelector,\n    discussionStateSelector,\n    discussionIdSelector,\n    userSelector,\n    messageCollectionStateSelector,\n    draftMessageSelector,\n  ],\n  (\n    sortedMessages,\n    lastMessage,\n    firstUnreadMessage,\n    messagesById,\n    discussionState,\n    discussionId,\n    userState,\n    { didInvalidate, messageIds, hasMore, isFetching },\n    draftMessage\n  ) => {\n    const canBeClosed = messageIds.length === 0;\n\n    return {\n      discussionId,\n      user: userState.user,\n      isUserFetching: userState.isFetching,\n      discussion: discussionState.discussionsById[discussionId],\n      messages: sortedMessages,\n      isFetching,\n      didInvalidate,\n      hasMore,\n      canBeClosed,\n      lastMessage,\n      firstUnreadMessage,\n      draftMessage,\n    };\n  }\n);\n\nconst deleteDiscussion =\n  ({ discussionId, messages }) =>\n  async (dispatch) => {\n    await Promise.all(\n      messages.map((message) => dispatch(deleteMessageRaw({ message })))\n    );\n\n    return dispatch(invalidate('discussion', discussionId));\n  };\n\nconst onMessageReply = (message) => async (dispatch) => {\n  dispatch(reply(message));\n};\n\nconst requestDiscussionAndDraft =\n  ({ discussionId }) =>\n  async (dispatch, getState) => {\n    const discussion = await dispatch(requestDiscussion({ discussionId }));\n    const draftMessage = messagesDraftByDiscussionIdSelector(getState(), {\n      discussionId,\n    }).pop();\n\n    if (!draftMessage) {\n      await dispatch(getDraft({ discussionId }));\n    }\n\n    return discussion;\n  };\n\nconst mapDispatchToProps = (dispatch, ownProps) =>\n  bindActionCreators(\n    {\n      loadMore: loadMore.bind(\n        null,\n        'discussion',\n        getDiscussionIdFromProps(ownProps)\n      ),\n      setMessageRead,\n      deleteMessage,\n      deleteDiscussion,\n      requestDiscussion: requestDiscussionAndDraft.bind(null, {\n        discussionId: getDiscussionIdFromProps(ownProps),\n      }),\n      onMessageReply,\n      getUser,\n    },\n    dispatch\n  );\n\nexport default compose(\n  connect(mapStateToProps, mapDispatchToProps),\n  withTags(),\n  withPush()\n)(Discussion);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport throttle from 'lodash/throttle';\nimport classnames from 'classnames';\nimport { withRouter } from 'react-router-dom';\nimport { Trans, withI18n } from '@lingui/react';\nimport {\n  ActionBarWrapper,\n  ActionBar,\n  ActionBarButton,\n  Badge,\n  Modal,\n  Link,\n} from '../../components';\nimport MessageList from './components/MessageList';\nimport ReplyExcerpt from './components/ReplyExcerpt';\nimport AddParticipantsToContactBook from './components/AddParticipantsToContactBook';\nimport QuickDraftForm from './components/QuickDraftForm';\nimport { withCloseTab } from '../../modules/tab';\nimport { ManageEntityTags, getTagLabel } from '../../modules/tags';\nimport { ScrollDetector, withScrollManager } from '../../modules/scroll';\nimport { addEventListener } from '../../services/event-manager';\n\nimport './style.scss';\nimport './discussion-action-bar.scss';\n\nconst LOAD_MORE_THROTTLE = 1000;\n\n@withScrollManager()\n@withCloseTab()\n@withI18n()\n@withRouter\nclass Discussion extends Component {\n  static propTypes = {\n    loadMore: PropTypes.func.isRequired,\n    discussionId: PropTypes.string.isRequired,\n    draftMessageId: PropTypes.string,\n    draftMessage: PropTypes.shape({}),\n    discussion: PropTypes.shape({}),\n    requestDiscussion: PropTypes.func.isRequired,\n    user: PropTypes.shape({}),\n    isUserFetching: PropTypes.bool.isRequired,\n    scrollManager: PropTypes.shape({\n      scrollToTarget: PropTypes.func.isRequired,\n    }).isRequired,\n    isFetching: PropTypes.bool.isRequired,\n    didInvalidate: PropTypes.bool.isRequired,\n    hasMore: PropTypes.bool.isRequired,\n    location: PropTypes.shape({}).isRequired,\n    lastMessage: PropTypes.shape({}),\n    messages: PropTypes.arrayOf(PropTypes.shape({})),\n    canBeClosed: PropTypes.bool.isRequired,\n    onMessageReply: PropTypes.func.isRequired,\n    setMessageRead: PropTypes.func.isRequired,\n    deleteMessage: PropTypes.func.isRequired,\n    // XXX: waiting for API\n    // deleteDiscussion: PropTypes.func.isRequired,\n    closeTab: PropTypes.func.isRequired,\n    push: PropTypes.func.isRequired,\n    getUser: PropTypes.func.isRequired,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    tags: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n    firstUnreadMessage: PropTypes.shape({}),\n  };\n\n  static defaultProps = {\n    draftMessageId: undefined,\n    draftMessage: undefined,\n    discussion: undefined,\n    lastMessage: undefined,\n    messages: [],\n    user: undefined,\n    firstUnreadMessage: undefined,\n  };\n\n  state = {\n    isDraftFocus: false,\n    isTagModalOpen: false,\n    initialized: false,\n  };\n\n  componentDidMount() {\n    const { getUser, user, isUserFetching, requestDiscussion, isFetching } =\n      this.props;\n\n    if (!user && !isUserFetching) {\n      getUser();\n    }\n\n    if (!this.state.initialized && !isFetching) {\n      requestDiscussion().then(() => {\n        this.setState(\n          {\n            initialized: true,\n          },\n          () => {\n            this.eventuallyCloseTab();\n          }\n        );\n      });\n    }\n\n    this.throttledLoadMore = throttle(\n      () => this.props.loadMore(),\n      LOAD_MORE_THROTTLE,\n      { trailing: false }\n    );\n\n    this.unsubscribeClickEvent = addEventListener('click', (ev) => {\n      const { target } = ev;\n      const testClick = (tg, node) => node === tg || node.contains(tg);\n\n      if (\n        !(this.replyFormRef && testClick(target, this.replyFormRef)) &&\n        !(this.replyExcerptRef && testClick(target, this.replyExcerptRef))\n      ) {\n        if (this.state.isDraftFocus) {\n          ev.preventDefault();\n        }\n        this.setState({\n          isDraftFocus: false,\n        });\n      }\n    });\n  }\n\n  UNSAFE_componentWillReceiveProps(nextProps) {\n    const { didInvalidate, isFetching } = nextProps;\n\n    if (didInvalidate && !isFetching) {\n      this.props.requestDiscussion().then(() => this.eventuallyCloseTab());\n    }\n  }\n\n  componentWillUnmount() {\n    this.unsubscribeClickEvent();\n  }\n\n  eventuallyCloseTab = () => {\n    const { isFetching, canBeClosed, closeTab } = this.props;\n\n    if (this.state.initialized && !isFetching && canBeClosed) {\n      closeTab();\n    }\n  };\n\n  handleOpenTags = () => {\n    this.setState((prevState) => ({\n      ...prevState,\n      isTagModalOpen: true,\n    }));\n  };\n\n  handleCloseTags = () => {\n    this.setState((prevState) => ({\n      ...prevState,\n      isTagModalOpen: false,\n    }));\n  };\n\n  handleFocusDraft = () => {\n    this.setState({\n      isDraftFocus: true,\n    });\n  };\n\n  handleDeleteMessage = async ({ message }) => {\n    const { deleteMessage } = this.props;\n\n    await deleteMessage({ message });\n    this.eventuallyCloseTab();\n  };\n\n  handleSetMessageRead = ({ message }) => {\n    this.props.setMessageRead({ message, isRead: true });\n  };\n\n  handleSetMessageUnread = ({ message }) => {\n    this.props.setMessageRead({ message, isRead: false });\n  };\n\n  handleMessageReply = () => {\n    const { messages, onMessageReply, push } = this.props;\n    const message = messages[messages.length - 1];\n\n    onMessageReply(message);\n    push({ hash: 'reply' });\n  };\n\n  loadMore = () => {\n    const { hasMore, isFetching } = this.props;\n\n    if (!isFetching && hasMore) {\n      this.throttledLoadMore();\n    }\n  };\n\n  // handleDeleteAll = async () => {\n  //   const {\n  //     messages, deleteDiscussion, discussionId, closeTab,\n  //   } = this.props;\n  //\n  //   // FIXME : only deletes fetched messages.\n  //   deleteDiscussion({ discussionId, messages });\n  //   closeTab();\n  // };\n\n  getHash = () => {\n    const { location } = this.props;\n\n    return location.hash ? location.hash.slice(1) : null;\n  };\n\n  renderTags = ({ tags }) => {\n    const { i18n } = this.props;\n\n    return (\n      tags && (\n        <ul className=\"s-discussion-action-bar__tags s-discussion-action-bar__action s-discussion__tags\">\n          {tags.map((tag) => (\n            <li key={tag.name} className=\"s-discussion__tag\">\n              <Badge\n                to={`/search-results?term=${getTagLabel(\n                  i18n,\n                  tag\n                )}&doctype=message`}\n              >\n                {tag.name}\n              </Badge>\n            </li>\n          ))}\n        </ul>\n      )\n    );\n  };\n\n  renderTagModal = () => {\n    const { discussion, i18n } = this.props;\n    const nb = discussion && discussion.tags ? discussion.tags.length : 0;\n    const title = (\n      <Trans\n        id=\"tags.header.title\"\n        message=\"Tags <0>(Total: {nb})</0>\"\n        values={{ nb }}\n        components={[<span className=\"m-tags-form__count\" />]}\n      />\n    );\n\n    return (\n      <Modal\n        isOpen={this.state.isTagModalOpen}\n        contentLabel={i18n._(/* i18n */ 'tags.header.label', null, {\n          message: 'Tags',\n        })}\n        title={title}\n        onClose={this.handleCloseTags}\n      >\n        <ManageEntityTags type=\"discussion\" entity={discussion} />\n      </Modal>\n    );\n  };\n\n  renderActionBar() {\n    const { lastMessage, isFetching, discussion } = this.props;\n\n    return (\n      <ScrollDetector\n        offset={136}\n        render={(isSticky) => (\n          <ActionBarWrapper isSticky={isSticky}>\n            <ActionBar\n              hr={false}\n              isLoading={isFetching}\n              actionsNode={\n                <div className=\"s-discussion-action-bar\">\n                  {discussion ? this.renderTags(discussion) : null}\n                  <div className=\"s-discussion-action-bar__actions\">\n                    {lastMessage && (\n                      <AddParticipantsToContactBook\n                        className=\"s-discussion-action-bar__action\"\n                        message={lastMessage}\n                      />\n                    )}\n                    <strong className=\"s-discussion-action-bar__action-label\">\n                      <Trans\n                        id=\"discussion.action.label\"\n                        message=\"Whole discussion\"\n                      />\n                      :\n                    </strong>\n                    <ActionBarButton\n                      className=\"s-discussion-action-bar__action\"\n                      display=\"inline\"\n                      noDecoration\n                      icon=\"reply\"\n                      onClick={this.handleMessageReply}\n                      responsive=\"icon-only\"\n                    >\n                      <Trans id=\"discussion.action.reply\" message=\"Reply\" />\n                    </ActionBarButton>\n                    {/*\n                      <ActionBarButton\n                        className=\"m-message-list__action\"\n                        display=\"inline\"\n                        noDecoration\n                        icon=\"tags\"\n                        onClick={this.handleOpenTags}\n                        responsive=\"icon-only\"\n                      >\n                        Tag\n                      </ActionBarButton>\n                      <ActionBarButton\n                        className=\"m-message-list__action\"\n                        display=\"inline\"\n                        noDecoration\n                        icon=\"trash\"\n                        onClick={this.handleDeleteAll}\n                        responsive=\"icon-only\"\n                      >\n                        Delete\n                      </ActionBarButton>\n                    */}\n                  </div>\n                </div>\n              }\n            />\n          </ActionBarWrapper>\n        )}\n      />\n    );\n  }\n\n  render() {\n    const {\n      discussionId,\n      messages,\n      scrollManager: { scrollToTarget },\n      isFetching,\n      hasMore,\n      user,\n      isUserFetching,\n      firstUnreadMessage,\n      draftMessage,\n      draftMessageId,\n    } = this.props;\n    const hash = this.getHash();\n\n    return (\n      <section id={`discussion-${discussionId}`} className=\"s-discussion\">\n        {this.renderActionBar()}\n        {this.renderTagModal()}\n        <MessageList\n          className=\"m-message-list\"\n          messages={messages}\n          loadMore={this.loadMore}\n          hasMore={hasMore}\n          isFetching={isFetching}\n          hash={hash}\n          onMessageRead={this.handleSetMessageRead}\n          onMessageUnread={this.handleSetMessageUnread}\n          onDeleteMessageSuccess={this.eventuallyCloseTab}\n          scrollToTarget={scrollToTarget}\n          user={user}\n          isUserFetching={isUserFetching}\n        />\n        {!!firstUnreadMessage && (\n          <Link\n            className=\"s-discussion__goto-unread-button\"\n            to={{\n              hash: firstUnreadMessage.message_id,\n              state: { key: Math.random() },\n            }}\n            badge\n          >\n            <Trans\n              id=\"discussion.action.goto_unread_message\"\n              message=\"You have unread messages ↑\"\n            />\n          </Link>\n        )}\n        <div\n          className={classnames('s-discussion__reply', {\n            's-discussion__reply--open': this.state.isDraftFocus,\n          })}\n        >\n          <QuickDraftForm\n            discussionId={discussionId}\n            onFocus={this.handleFocusDraft}\n            innerRef={(node) => {\n              this.replyFormRef = node;\n            }}\n          />\n        </div>\n        <div\n          className={classnames('s-discussion__reply-excerpt', {\n            's-discussion__reply-excerpt--close': this.state.isDraftFocus,\n          })}\n        >\n          <ReplyExcerpt\n            discussionId={discussionId}\n            onFocus={this.handleFocusDraft}\n            draftExcerptRef={(node) => {\n              this.replyExcerptRef = node;\n            }}\n          />\n        </div>\n      </section>\n    );\n  }\n}\n\nexport default Discussion;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/style.scss",
    "content": "@import '../../styles/vendor/bootstrap_foundation-sites';\n@import '../../styles/common';\n\n.s-discussion {\n  display: flex;\n  position: relative;\n  flex-direction: column;\n\n  &__tags {\n    display: flex;\n    flex-direction: row;\n  }\n\n  &__tag {\n    display: block;\n    margin-left: $co-component__spacing;\n  }\n\n  &__load-more {\n    padding-top: $co-margin;\n    text-align: center;\n  }\n\n  &__reply-excerpt,\n  &__reply {\n    @include breakpoint(small) {\n      position: fixed;\n      right: 0;\n      bottom: 0;\n      left: 0;\n      box-shadow: $co-shadow--higher;\n    }\n  }\n\n  &__reply {\n    @include breakpoint(small) {\n      display: none;\n    }\n    @include breakpoint(medium) {\n      display: block;\n      position: static;\n      box-shadow: none;\n    }\n\n    &--open {\n      display: block;\n    }\n  }\n\n  &__reply-excerpt {\n    @include breakpoint(medium) {\n      display: none;\n    }\n\n    &--close {\n      display: none;\n    }\n  }\n\n  &__goto-unread-button {\n    max-width: 47rem;\n    margin-right: auto;\n    margin-bottom: $co-margin;\n    margin-left: auto;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Discussion/types.d.ts",
    "content": "import { Message } from 'src/modules/message';\nimport { MessageEncryptionStatus } from 'src/store/modules/encryption';\n\nexport interface ConcreteMessageProps {\n  message: Message;\n  onDeleteMessageSuccess: () => any;\n  onOpenTags: () => any;\n  onReply: () => any;\n  noInteractions?: boolean;\n  isLocked: boolean;\n  encryptionStatus: void | MessageEncryptionStatus;\n  // scrollToMe\n  forwardedRef: React.MutableRefObject<any>;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ForgotPassword/components/ForgotPasswordForm/index.jsx",
    "content": "import { withI18n } from '@lingui/react';\n\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ForgotPassword/components/ForgotPasswordForm/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport {\n  Title,\n  Link,\n  Button,\n  Icon,\n  TextFieldGroup,\n  FieldErrors,\n  Fieldset,\n  Legend,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\n\nimport './style.scss';\n\nclass ForgotPasswordForm extends Component {\n  static propTypes = {\n    errors: PropTypes.shape({}),\n    onSubmit: PropTypes.func.isRequired,\n    success: PropTypes.bool,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {\n    errors: {},\n    success: false,\n  };\n\n  state = {\n    formValues: {\n      username: '',\n    },\n  };\n\n  handleInputChange = (event) => {\n    const { name, value } = event.target;\n    this.setState((prevState) => ({\n      formValues: {\n        ...prevState.formValues,\n        [name]: value,\n      },\n    }));\n  };\n\n  handleSubmit = (ev) => {\n    ev.preventDefault();\n    const { formValues } = this.state;\n    this.props.onSubmit({ formValues });\n  };\n\n  render() {\n    const { i18n, errors, success } = this.props;\n\n    return (\n      <div className=\"m-forgot-password-form\">\n        <Title>\n          <Trans id=\"password.forgot-form.title\" message=\"Forgot password\" />\n        </Title>\n        <FormGrid className=\"m-forgot-password-form\">\n          <form method=\"post\" onSubmit={this.handleSubmit}>\n            <Fieldset>\n              {errors.global && (\n                <FormRow>\n                  <FormColumn rightSpace={false} bottomSpace>\n                    <FieldErrors errors={errors.global} />\n                  </FormColumn>\n                </FormRow>\n              )}\n              {success ? (\n                <FormRow>\n                  <FormColumn\n                    rightSpace={false}\n                    bottomSpace\n                    className=\"m-forgot-password-form__success\"\n                  >\n                    <Icon type=\"check\" rightSpaced />\n                    <Trans id=\"password.forgot-form.success\">\n                      Done! You will receive an email shortly with instructions\n                      to reset your password.\n                    </Trans>\n                  </FormColumn>\n                  <FormColumn\n                    rightSpace={false}\n                    className=\"m-forgot-password-form__action\"\n                    bottomSpace\n                  >\n                    <Link\n                      button\n                      shape=\"plain\"\n                      display=\"expanded\"\n                      to=\"/auth/signin\"\n                    >\n                      <Trans\n                        id=\"password.forgot-form.action.login\"\n                        message=\"Ok\"\n                      />\n                    </Link>\n                  </FormColumn>\n                </FormRow>\n              ) : (\n                <FormRow>\n                  <FormColumn rightSpace={false} bottomSpace>\n                    <Legend>\n                      <Trans id=\"password.forgot-form.instructions\">\n                        Enter your username and we&apos;ll email instructions on\n                        how to reset your password.\n                      </Trans>\n                    </Legend>\n                  </FormColumn>\n                  <FormColumn rightSpace={false} bottomSpace>\n                    <TextFieldGroup\n                      label={i18n._(\n                        /* i18n */ 'password.forgot-form.username.label',\n                        null,\n                        { message: 'Username' }\n                      )}\n                      inputProps={{\n                        placeholder: i18n._(\n                          /* i18n */ 'password.forgot-form.username.placeholder',\n                          null,\n                          { message: 'Username' }\n                        ),\n                        name: 'username',\n                        value: this.state.formValues.username,\n                        onChange: this.handleInputChange,\n                        theme: 'contrasted',\n                      }}\n                      errors={errors.username}\n                    />\n                  </FormColumn>\n                  <FormColumn rightSpace={false} bottomSpace>\n                    <TextFieldGroup\n                      label={i18n._(\n                        /* i18n */ 'password.forgot-form.recovery_email.label',\n                        null,\n                        { message: 'Recovery email' }\n                      )}\n                      inputProps={{\n                        placeholder: i18n._(\n                          /* i18n */ 'password.forgot-form.recovery_email.placeholder',\n                          null,\n                          { message: 'Recovery email' }\n                        ),\n                        name: 'recovery_email',\n                        value: this.state.formValues.recovery_email,\n                        onChange: this.handleInputChange,\n                        theme: 'contrasted',\n                      }}\n                      errors={errors.recovery_email}\n                    />\n                  </FormColumn>\n                  <FormColumn\n                    rightSpace={false}\n                    className=\"m-forgot-password-form__action\"\n                    bottomSpace\n                  >\n                    <Button type=\"submit\" display=\"expanded\" shape=\"plain\">\n                      <Trans\n                        id=\"password.forgot-form.action.send\"\n                        message=\"Send\"\n                      />\n                    </Button>\n                  </FormColumn>\n                  <FormColumn\n                    rightSpace={false}\n                    className=\"m-forgot-password-form__link\"\n                  >\n                    <Link to=\"/auth/signin\">\n                      <Trans\n                        id=\"password.forgot-form.cancel\"\n                        message=\"Cancel\"\n                      />\n                    </Link>\n                  </FormColumn>\n                </FormRow>\n              )}\n            </Fieldset>\n          </form>\n        </FormGrid>\n      </div>\n    );\n  }\n}\n\nexport default ForgotPasswordForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ForgotPassword/components/ForgotPasswordForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-clickable';\n@import '../../../../styles/object/o-callout';\n\n.m-forgot-password-form {\n  &__action {\n    line-height: $co-component__height;\n    text-align: center;\n  }\n\n  &__link {\n    text-align: center;\n  }\n\n  &__success {\n    @include o-callout-base;\n    @include o-callout--color($co-color__success);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ForgotPassword/index.js",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ForgotPassword/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport ForgotPasswordForm from './components/ForgotPasswordForm';\nimport getClient from '../../services/api-client';\n\nconst STATUS_ERROR = 424;\nconst STATUS_INVALID_FORM = 400;\nconst ERROR_IDENTIFIANTS_MISMATCH =\n  '[RESTfacility] username and recovery email mismatch';\nconst ERROR_USER_NOT_FOUND = '[RESTfacility] user not found';\n\nclass ForgotPassword extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  state = {\n    errors: {},\n    isSuccess: false,\n  };\n\n  handleSuccess = () => {\n    this.setState({ isSuccess: true });\n  };\n\n  handleSubmit = (ev) => {\n    this.setState({ errors: {} });\n\n    getClient()\n      .post('/api/v2/passwords/reset', {\n        ...ev.formValues,\n      })\n      .then(this.handleSigninSuccess, this.handleSigninError);\n  };\n\n  handleSigninSuccess = () => {\n    this.setState({\n      isSuccess: true,\n    });\n  };\n\n  handleSigninError = ({\n    response: {\n      status,\n      data: { errors: globalErrors },\n    },\n  }) => {\n    const { i18n } = this.props;\n\n    if (![STATUS_ERROR, STATUS_INVALID_FORM].includes(status)) {\n      throw new Error('Unexpected error');\n    }\n\n    const localizedErrors = {\n      [ERROR_IDENTIFIANTS_MISMATCH]: i18n._(\n        /* i18n */ 'passwords.form.error.identifiants_mismatch',\n        null,\n        { message: \"Identifiers don't match.\" }\n      ),\n      [ERROR_USER_NOT_FOUND]: i18n._(\n        /* i18n */ 'passwords.form.error.user_not_found',\n        null,\n        { message: 'User not found.' }\n      ),\n      [STATUS_INVALID_FORM]: i18n._(\n        /* i18n */ 'passwords.form.error.empty',\n        null,\n        {\n          message: 'At least one field is required.',\n        }\n      ),\n    };\n\n    const errors = {\n      global: globalErrors.map(\n        (error) => localizedErrors[error.message] || localizedErrors[error.code]\n      ),\n    };\n\n    this.setState({\n      isSuccess: false,\n      errors,\n    });\n  };\n\n  render() {\n    return (\n      <ForgotPasswordForm\n        onSubmit={this.handleSubmit}\n        errors={this.state.errors}\n        success={this.state.isSuccess}\n      />\n    );\n  }\n}\n\nexport default ForgotPassword;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ForgotPassword/presenter.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport Presenter from './presenter';\n\ndescribe('scene - ForgotPassword', () => {\n  const props = {\n    i18n: { _: (str) => str },\n  };\n\n  it('render', () => {\n    const comp = shallow(<Presenter {...props} />)\n      .dive()\n      .dive();\n\n    expect(comp.text()).toContain('ForgotPasswordForm');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDeviceInfo/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans, withI18n } from '@lingui/react';\nimport { Link, Title, Button, Modal } from '../../components';\nimport './style.scss';\n\nconst URL_DEVICES = '/settings/devices';\n\n/* eslint-disable */\n/* const MODAL_CONTENT_FR = (<p>\n  <p>Pour évaluer le degré de confidentialité de vos messages, Caliopena besoin d'en savoir plus sur le terminal sur lequel vous allez lesafficher: par nature, un terminal mobile n'est pas aussi confidential qu'un terminal fixe (parce que vous risquez de l'utiliser en public plus souvent).<br />De même un terminal partagé (ordinateur en libre accès dans un lieu public, par exemple) ne peut pas être considéré comme étant un outil aussi confidentiel que l'ordinateur personnel que vous êtes seul à\nutiliser.</p>\n  <p>Caliopen va tenir compte des informations que vous allez saisir ici pour évaluer si l'affichage de vos essages les plus confidentiels est possible sans risque d'être lus par des tiers : par défaut seuls les messages adaptés à la confidentialité du terminal utilisé seront affichés.</p>\n  <p>Il est donc important de décrire précisément les attributs d'un terminal depuis lequel vous ne vous êtes jamais connecté, de manière à ce que Caliopen puisse protéger au mieux votre vie privée.</p>\n</p>); */\n/* eslint-enable */\n\n@withI18n()\nclass NewDeviceInfo extends PureComponent {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  state = {\n    isModalOpen: false,\n  };\n\n  handleOpenModal = () => {\n    this.setState({\n      isModalOpen: true,\n    });\n  };\n\n  handleCloseModal = () => {\n    this.setState({\n      isModalOpen: false,\n    });\n  };\n\n  renderModal = () => {\n    const { i18n } = this.props;\n\n    return (\n      <Modal\n        isOpen={this.state.isModalOpen}\n        className=\"s-new-device-info__modal\"\n        title={i18n._(/* i18n */ 'new-device-info.modal.title', null, {\n          message: 'About new device',\n        })}\n        onClose={this.handleCloseModal}\n      >\n        <div\n          dangerouslySetInnerHTML={{\n            __html: i18n._(\n              /* i18n */ 'new-device-info.learn-more.content',\n              null,\n              {\n                message:\n                  \"<p>In order to compute the Privacy Index (PI) of a message, Caliopen needs to know more about the terminal you're using to consult said message: by nature a laptop isn't as private as a desktop (since you will be using your laptop in public more often).</p><p>Likewise a shared terminal (e.g. a free access computer in a public place) cannot be considered as private as your personal computer only you use.</p><p>Caliopen will take into account the informations you enter on the next screen, to better assess whether your more private messages can be displayed without a risk to be read by a third party: by default only the messages adapted to your terminal's privacy will be displayed.</p><p>It is therefore important to precisely describe a terminal's attributes on the first use, so that Caliopen can more effectively protect your privacy.</p>\",\n              }\n            ),\n          }}\n        />\n      </Modal>\n    );\n  };\n\n  render() {\n    return (\n      <div className=\"s-new-device-info\">\n        <Title className=\"s-new-device-info__title\">\n          <Trans id=\"new-device-info.title\">\n            It&apos;s the first time you attemp to connect to your Caliopen\n            account on this client.\n          </Trans>\n        </Title>\n        <Trans id=\"new-device-info.text\">\n          To respect privacy and security rules, your discussion history will\n          not appear here for now. Please verify this device and eventually set\n          restrictions from your trust client to use Caliopen on this client.\n        </Trans>\n\n        <div className=\"s-new-device-info__actions\">\n          <Button\n            onClick={this.handleOpenModal}\n            className=\"s-new-device-info__learn-more\"\n          >\n            <Trans id=\"new-device-info.learn-more\" message=\"Learn more\" />\n          </Button>\n          {this.renderModal()}\n          <Link shape=\"plain\" button to={URL_DEVICES}>\n            <Trans id=\"new-device-info.i-understand\" message=\"I understand\" />\n          </Link>\n        </div>\n      </div>\n    );\n  }\n}\n\nexport default NewDeviceInfo;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDeviceInfo/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n$message_width: 45rem;\n\n.s-new-device-info {\n  max-width: $message_width;\n  margin-right: map_get($co-margin_, 'medium');\n  margin-left: map_get($co-margin_, 'medium');\n  padding-top: map_get($co-margin_, 'medium');\n  padding-bottom: map_get($co-margin_, 'medium');\n  text-align: justify;\n\n  &__title {\n    margin-bottom: $co-margin;\n  }\n\n  &__actions {\n    margin-top: map_get($co-margin_, 'medium');\n    text-align: center;\n  }\n\n  &__learn-more {\n    margin-right: $co-margin;\n  }\n\n  &__modal {\n    width: 100%;\n    max-width: 2 * map_get($co-form__width, 'xlarge');\n    max-height: 100%;\n    overflow: auto;\n  }\n\n  @include breakpoint(medium) {\n    margin-right: auto;\n    margin-left: auto;\n    padding-top: map_get($co-margin_, 'xlarge');\n    padding-bottom: map_get($co-margin_, 'xlarge');\n\n    &__actions {\n      margin-top: map_get($co-margin_, 'large');\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDraft/components/DraftDiscussion/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport { Title, TextBlock } from 'src/components';\nimport { useUser } from 'src/modules/user';\nimport { useDraftDiscussion } from './useDraftDiscussion';\nimport Message from '../../../Discussion/components/Message';\n\nconst EMPTY_ARRAY = [];\n\ntype Props = {\n  messageId: string;\n  className?: string;\n};\nfunction DraftDiscussion({ messageId, className }: Props) {\n  const { discussion, messages } = useDraftDiscussion(messageId);\n  const { user } = useUser();\n\n  if (!discussion) {\n    return null;\n  }\n\n  // participants except user\n  const participants = discussion.participants\n    .filter(\n      (participant) =>\n        !user ||\n        !(participant.contact_ids || EMPTY_ARRAY).some(\n          (contactId) => contactId === user?.contact.contact_id\n        )\n    )\n    .map((participant) => participant.label)\n    .join(', ');\n\n  return (\n    <div className={classnames(className)}>\n      <Title hr>\n        <TextBlock>\n          <Trans\n            id=\"discussion-draft.last-messages\"\n            message=\"Last messages with {participants}\"\n            values={{ participants }}\n          />\n        </TextBlock>\n      </Title>\n      {(messages || EMPTY_ARRAY).map((message) => (\n        // @ts-ignore: unrecognized props\n        <Message key={message.message_id} message={message} noInteractions />\n      ))}\n    </div>\n  );\n}\n\nexport default DraftDiscussion;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDraft/components/DraftDiscussion/useDraftDiscussion.tsx",
    "content": "import * as React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport sha1 from 'uuid/lib/sha1-browser';\nimport bytesToUuid from 'uuid/lib/bytesToUuid';\nimport { getModuleStateSelector } from 'src/store/selectors/getModuleStateSelector';\nimport { createMessageCollectionStateSelector } from 'src/store/selectors/message';\nimport { draftMessageSelector } from 'src/modules/draftMessage';\nimport {\n  requestDiscussionIdForParticipants,\n  discussionSelector as discussionSelectorBase,\n} from 'src/modules/discussion';\nimport { requestDiscussion, getMessage } from 'src/modules/message';\nimport { RootState } from 'src/store/reducer';\n\nconst getParticipantsHash = ({ participants }) => {\n  if (participants.length === 0) {\n    return undefined;\n  }\n\n  return bytesToUuid(\n    sha1(\n      participants\n        .map((participant) => `${participant.address}_${participant.protocol}`)\n        .sort()\n        .join('+')\n    )\n  );\n};\nconst discussionStateSelector = getModuleStateSelector('discussion');\n\nconst discussionIdSelector = (state, messageId: string) => {\n  const discussionState = discussionStateSelector(state);\n  const draftMessage = draftMessageSelector(state, messageId);\n  if (\n    !draftMessage ||\n    !draftMessage.participants ||\n    draftMessage.participants.length === 0\n  ) {\n    return undefined;\n  }\n\n  const { participants } = draftMessage;\n  const discussionId =\n    discussionState.discussionByParticipantsHash[\n      getParticipantsHash({\n        participants,\n      })\n    ]?.discussionId;\n\n  return discussionId;\n};\n\nconst discussionSelector = (state, messageId: string) =>\n  discussionSelectorBase(state, {\n    discussionId: discussionIdSelector(state, messageId),\n  });\n\nconst messageCollectionStateSelector = createMessageCollectionStateSelector(\n  () => 'discussion',\n  discussionIdSelector\n);\n\nconst requestDraftDiscussion = (messageId: string) => async (dispatch) => {\n  const draftMessage = await dispatch(getMessage({ messageId })).catch(\n    () => undefined\n  );\n\n  if (!draftMessage) {\n    return;\n  }\n\n  const { participants } = draftMessage;\n  const internalHash = getParticipantsHash({ participants });\n\n  const discussionId = await dispatch(\n    requestDiscussionIdForParticipants({\n      participants,\n      internalHash,\n    })\n  );\n  if (discussionId && discussionId.length > 0) {\n    dispatch(requestDiscussion({ discussionId }));\n  }\n};\n\nconst EMPTY_ARRAY = [];\n\n/**\n * This hook relies on draftMessage participants to get the discussion and the related messages.\n * @param messageId string\n */\nexport function useDraftDiscussion(messageId: string) {\n  const dispatch = useDispatch();\n\n  const draftMessage = useSelector((state: RootState) =>\n    draftMessageSelector(state, messageId)\n  );\n  const discussion = useSelector((state) =>\n    discussionSelector(state, messageId)\n  );\n\n  const messageCollectionState = useSelector(\n    // @ts-ignore: messageId is required by discussionIdSelector\n    (state) => discussion && messageCollectionStateSelector(state, messageId)\n  );\n\n  const { messages, didInvalidate, isFetching } = messageCollectionState || {};\n\n  const shouldFetchDraftMessage =\n    !isFetching && (didInvalidate || !draftMessage || !messageCollectionState);\n\n  React.useEffect(() => {\n    if (shouldFetchDraftMessage) {\n      dispatch(requestDraftDiscussion(messageId));\n    }\n  }, [shouldFetchDraftMessage, messageId]);\n\n  return {\n    discussion,\n    messages: (messages || EMPTY_ARRAY).filter(\n      (message) => message.message_id !== messageId\n    ),\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDraft/components/DraftMessage/components/IdentitySelector.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport { AdvancedSelectFieldGroup, Icon } from 'src/components';\nimport {\n  PROVIDER_EMAIL,\n  PROVIDER_GMAIL,\n  PROVIDER_TWITTER,\n  PROVIDER_MASTODON,\n  ProviderIcon,\n} from 'src/modules/remoteIdentity';\nimport { IIdentity } from 'src/modules/identity/types';\n\ninterface Props {\n  identityId: undefined | string;\n  identities: IIdentity[];\n  className?: string;\n  onChange: (identity?: IIdentity) => void;\n}\n\nfunction IdentitySelector({\n  identityId,\n  identities,\n  className,\n  onChange,\n}: Props) {\n  const handleChange = (value: string) => {\n    const identity = identities.find((curr) => curr.identity_id === value);\n    onChange(identity);\n  };\n\n  const renderProvider = (identity: IIdentity) => {\n    switch (identity?.infos.provider || '') {\n      default:\n      case PROVIDER_EMAIL:\n        return <Icon type=\"email\" />;\n      case PROVIDER_GMAIL:\n      case PROVIDER_TWITTER:\n      case PROVIDER_MASTODON:\n        return (\n          <ProviderIcon providerName={identity?.infos.provider} size=\"normal\" />\n        );\n    }\n  };\n\n  return (\n    <div className={classnames(className)}>\n      <AdvancedSelectFieldGroup\n        selectId=\"identity-select\"\n        dropdownId=\"identity-select-dropdown\"\n        label={<Trans id=\"draft-message.form.identity.label\" message=\"From:\" />}\n        placeholder={\n          <Trans\n            id=\"draft-message.form.identity.placeholder\"\n            message=\"What's your identity ?\"\n          />\n        }\n        onChange={handleChange}\n        value={identityId}\n        inline\n        options={identities.map((identity) => ({\n          label: `${identity.display_name} <${identity.identifier}>`,\n          advancedlabel: (\n            <>\n              {renderProvider(identity)} {identity.display_name} &lt;\n              {identity.identifier}\n              &gt;\n            </>\n          ),\n          value: identity.identity_id,\n        }))}\n      />\n    </div>\n  );\n}\n\nexport default IdentitySelector;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDraft/components/DraftMessage/components/Recipients.tsx",
    "content": "import * as React from 'react';\nimport { Trans } from '@lingui/react';\nimport { useDispatch } from 'react-redux';\nimport { editDraft } from 'src/store/modules/draft-message';\nimport { Icon } from 'src/components';\nimport { getIconType } from 'src/services/protocols-config';\nimport RecipientList from 'src/modules/draftMessage/components/RecipientList';\nimport { DraftMessageFormData } from 'src/modules/draftMessage';\nimport { Recipient } from 'src/modules/draftMessage/types';\nimport RecipientSelector from 'src/modules/draftMessage/components/RecipientSelector';\nimport { IIdentity } from 'src/modules/identity/types';\n\ninterface RecipientsProps {\n  draftMessage: DraftMessageFormData;\n  className?: string;\n  availableIdentities: IIdentity[];\n}\n\nexport default function Recipients({\n  draftMessage,\n  className,\n  availableIdentities,\n}: RecipientsProps) {\n  const dispatch = useDispatch();\n  if (!draftMessage.parent_id) {\n    const identity = availableIdentities.find(\n      (ident) => ident.identity_id === draftMessage.identity_id\n    );\n\n    const handleRecipientsChange = (recipients: Recipient[]) => {\n      dispatch(\n        editDraft({\n          ...draftMessage,\n          recipients,\n        })\n      );\n    };\n\n    return (\n      <RecipientList\n        className={className}\n        messageId={draftMessage.message_id}\n        recipients={draftMessage.recipients}\n        onRecipientsChange={handleRecipientsChange}\n        identity={identity}\n      />\n    );\n  }\n\n  // /!\\ the associated contact might be deleted\n  const isOne2One =\n    draftMessage.recipients.length === 1 &&\n    draftMessage.recipients[0].contact_ids &&\n    draftMessage.recipients[0].contact_ids.length > 0;\n  const [firstRecipient] = (isOne2One && draftMessage.recipients) || [];\n\n  if (isOne2One) {\n    const handleChangeOne2OneRecipient: React.ComponentProps<\n      typeof RecipientSelector\n    >['onChange'] = (recipient) => {\n      // TODO: select the identity that match the new protocol\n      const recipients = recipient ? [recipient] : [];\n      dispatch(\n        editDraft({\n          ...draftMessage,\n          recipients,\n        })\n      );\n    };\n\n    return (\n      <RecipientSelector\n        className={className}\n        contactId={(firstRecipient?.contact_ids || [])[0]}\n        current={firstRecipient}\n        onChange={handleChangeOne2OneRecipient}\n      />\n    );\n  }\n\n  // XXX: remove this behavior: always allow to change the recipients, it will need to adjust the parent_id property\n\n  return (\n    <div>\n      <Trans id=\"messages.compose.form.to.label\" message=\"To:\" />{' '}\n      {draftMessage.recipients.map((recipient) => (\n        <span key={`${recipient.address}_${recipient.protocol}`}>\n          {/* @ts-ignore */}\n          <Icon type={getIconType(recipient.protocol)} rightSpaced />\n          {recipient.address}{' '}\n        </span>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDraft/components/DraftMessage/draft-message-advanced.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../../styles/vendor/bootstrap_font-awesome';\n\n.m-draft-message-advanced {\n  @include flex-grid-row($size: expand);\n\n  background-color: #f6fcff;\n\n  &__recipient-list {\n    // margin-right: $co-margin;\n    // margin-bottom: $co-margin;\n    // margin-left: $co-margin;\n  }\n\n  &__toggle-simple {\n    @include flex-grid-size(shrink);\n\n    width: 3.438rem;\n    height: 3.438rem;\n  }\n\n  &__identity {\n    // height: 3.438rem;\n    margin-bottom: map_get($co-margin_, 'xsmall');\n  }\n\n  &__container {\n    @include flex-grid-size;\n\n    max-width: 100%;\n    padding-right: map_get($co-margin_, 'xsmall');\n    padding-left: map_get($co-margin_, 'xsmall');\n\n    @include breakpoint(medium) {\n      padding-right: map_get($co-margin_, 'xlarge');\n      padding-left: map_get($co-margin_, 'xlarge');\n    }\n  }\n\n  &__parent {\n    margin-bottom: map_get($co-margin_, 'medium');\n  }\n\n  &__errors,\n  &__action-send {\n    @include flex-grid-size(12);\n\n    padding-right: map_get($co-margin_, 'xsmall');\n    background-color: $co-color__bg__action;\n\n    @include breakpoint(medium) {\n      padding-right: map_get($co-margin_, 'xlarge');\n    }\n  }\n\n  &__errors {\n    padding-bottom: map_get($co-margin_, 'small');\n    text-align: center;\n  }\n\n  &__encryption {\n    width: 100%;\n    padding-right: map_get($co-margin_, 'xlarge');\n    padding-left: map_get($co-margin_, 'xsmall');\n    text-align: right;\n  }\n\n  &__action-send {\n    text-align: right;\n  }\n\n  &__action-button {\n    margin-top: map_get($co-margin_, 'small');\n    margin-bottom: map_get($co-margin_, 'small');\n    margin-left: map_get($co-margin_, 'xsmall');\n  }\n\n  &__button-send {\n    position: relative;\n\n    &::after {\n      @include fa-icon;\n\n      position: absolute;\n      right: 0;\n      bottom: 0;\n      width: 1rem;\n      height: 1rem;\n      font-size: 1rem;\n    }\n\n    &--encrypted {\n      background-color: map_get($privacy-palette, 'super');\n\n      &::after {\n        content: $fa-var-lock;\n      }\n    }\n\n    &--unencrypted {\n      background-color: map_get($privacy-palette, 'good');\n      color: $co-color__fg__text;\n\n      &::after {\n        content: $fa-var-unlock;\n      }\n    }\n  }\n\n  &__subject,\n  &__recipient-list,\n  &__body,\n  &__attachments {\n    margin-bottom: $co-margin;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDraft/components/DraftMessage/draft-message-placeholder.scss",
    "content": ".m-draft-message-placeholder {\n  height: 3.438rem;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDraft/components/DraftMessage/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport {\n  Button,\n  Icon,\n  TextareaFieldGroup,\n  TextFieldGroup,\n  Link,\n  Confirm,\n  FieldErrors,\n  Spinner,\n  TextBlock,\n} from 'src/components';\nimport {\n  deleteDraft,\n  deleteDraftSuccess,\n  clearDraft,\n  syncDraft,\n} from 'src/store/modules/draft-message';\nimport { STATUS_DECRYPTED, STATUS_ERROR } from 'src/store/modules/encryption';\nimport {\n  saveDraft,\n  sendDraft,\n  draftMessageSelector,\n  AttachmentManager,\n  validate,\n  getOrCreateDraft,\n  DraftMessageFormData,\n} from 'src/modules/draftMessage';\nimport { calcSyncDraft } from 'src/modules/draftMessage/services/calcSyncDraft';\nimport { getIdentityProtocol } from 'src/modules/draftMessage/services/getIdentityProtocol';\nimport { LockedMessage } from 'src/modules/encryption';\nimport {\n  uploadDraftAttachments,\n  deleteDraftAttachment,\n} from 'src/modules/file';\nimport {\n  deleteMessage,\n  PROTOCOL_EMAIL,\n  messageSelector,\n  Message,\n} from 'src/modules/message';\nimport { notifyError } from 'src/modules/userNotify';\nimport { messageEncryptionStatusSelector } from 'src/modules/encryption/selectors/message';\nimport { IIdentity } from 'src/modules/identity/types';\nimport { isMessageEncrypted } from 'src/services/encryption';\nimport { useAvailableIdentities } from 'src/modules/draftIdentity';\nimport { RootState } from 'src/store/reducer';\nimport IdentitySelector from './components/IdentitySelector';\nimport Recipients from './components/Recipients';\nimport './draft-message-advanced.scss';\nimport './draft-message-placeholder.scss';\n\nfunction useDraftMessage(messageId: string): DraftMessageFormData | undefined {\n  const dispatch = useDispatch();\n  const draftMessage = useSelector((state: RootState) =>\n    draftMessageSelector(state, messageId)\n  );\n  React.useEffect(() => {\n    if (!draftMessage) {\n      dispatch(getOrCreateDraft(messageId));\n    }\n  }, [draftMessage]);\n\n  return draftMessage;\n}\n\nconst onDeleteMessage =\n  ({ message }) =>\n  async (dispatch) => {\n    dispatch(deleteDraft({ draft: message }));\n    const result = await dispatch(deleteMessage({ message }));\n\n    await dispatch(clearDraft({ draft: message }));\n    dispatch(deleteDraftSuccess({ draft: message }));\n\n    return result;\n  };\n\nconst uploadAttachments = (draft, attachments) => async (dispatch) => {\n  try {\n    const savedDraft = await dispatch(\n      saveDraft(draft, {\n        withThrottle: false,\n        force: true,\n      })\n    );\n\n    const messageUpTodate = await dispatch(\n      uploadDraftAttachments({\n        message: savedDraft,\n        attachments,\n      })\n    );\n    const nextDraft = calcSyncDraft(draft, messageUpTodate);\n\n    return dispatch(syncDraft(nextDraft));\n  } catch (err) {\n    return Promise.reject(err);\n  }\n};\n\nconst deleteAttachement = (draft, attachment) => async (dispatch) => {\n  try {\n    const savedDraft = await dispatch(\n      saveDraft(draft, {\n        withThrottle: false,\n        force: true,\n      })\n    );\n\n    const messageUpTodate = await dispatch(\n      deleteDraftAttachment({\n        message: savedDraft,\n        attachment,\n      })\n    );\n\n    const nextDraft = calcSyncDraft(draft, messageUpTodate);\n\n    return dispatch(syncDraft(nextDraft));\n  } catch (err) {\n    return Promise.reject(err);\n  }\n};\n\nconst onSendDraft = (draft) => async (dispatch) => {\n  try {\n    const savedMessage = await dispatch(\n      saveDraft(draft, {\n        withThrottle: false,\n      })\n    );\n    // discussion_id is set after the message has been sent for new drafts\n    const messageUpToDate = await dispatch(sendDraft(savedMessage));\n\n    return messageUpToDate;\n  } catch (err) {\n    return Promise.reject(err);\n  }\n};\n\nfunction isSubjectSupported(\n  availableIdentities,\n  draft: DraftMessageFormData\n): boolean {\n  if (!draft.identity_id) {\n    return false;\n  }\n\n  const currIdentity = availableIdentities.find(\n    (ident) => ident.identity_id === draft.identity_id\n  );\n\n  if (!currIdentity) {\n    return false;\n  }\n\n  return getIdentityProtocol(currIdentity) === PROTOCOL_EMAIL;\n}\n\ninterface DraftMessageProps extends withI18nProps {\n  scrollToMe?: () => string;\n  className?: string;\n  messageId: string;\n  onDeleteMessageSuccessfull: () => void;\n  onSent: (message: Message) => void;\n}\n\nfunction DraftMessage(props: DraftMessageProps) {\n  const { className, messageId, i18n, onDeleteMessageSuccessfull, onSent } =\n    props;\n  const [isSending, setIsSending] = React.useState(false);\n  const dispatch = useDispatch();\n  const draftMessageFormData = useDraftMessage(messageId);\n  const message = useSelector((state) => messageSelector(state, { messageId }));\n  const availableIdentities = useAvailableIdentities(draftMessageFormData);\n  const parentMessage = useSelector(\n    (state) =>\n      draftMessageFormData &&\n      messageSelector(state, { messageId: draftMessageFormData.parent_id })\n  );\n  const hasSubject =\n    draftMessageFormData &&\n    isSubjectSupported(availableIdentities, draftMessageFormData);\n\n  const identity = availableIdentities.find(\n    (ident) => ident.identity_id === draftMessageFormData?.identity_id\n  );\n  const draftEncryption = useSelector((state: RootState) =>\n    messageEncryptionStatusSelector(state, messageId)\n  );\n\n  const isEncrypted = message && isMessageEncrypted(message);\n\n  const isLocked =\n    isEncrypted &&\n    draftEncryption &&\n    ![STATUS_DECRYPTED, STATUS_ERROR].includes(draftEncryption.status);\n  const encryptionEnabled =\n    isEncrypted && draftEncryption?.status === STATUS_DECRYPTED;\n  const isReply = draftMessageFormData?.parent_id && true;\n\n  const errors =\n    (draftMessageFormData &&\n      validate({\n        draftMessage: draftMessageFormData,\n        i18n,\n        availableIdentities,\n      })) ||\n    [];\n  const isValid = (errors?.length || 0) === 0;\n  const hasRecipients = (draftMessageFormData?.recipients.length || 0) > 0;\n\n  const canSend = (isReply || hasRecipients) && !isSending && isValid;\n\n  const handleIdentityChange = async (nextIdentity: IIdentity) => {\n    if (!draftMessageFormData) {\n      return undefined;\n    }\n    const prevIdentity = availableIdentities.find(\n      (ident) => ident.identity_id === draftMessageFormData?.identity_id\n    );\n\n    let recipients;\n\n    if (\n      !isReply &&\n      prevIdentity &&\n      prevIdentity.protocol !== nextIdentity.protocol\n    ) {\n      // force protocol for all recipients\n      recipients = (draftMessageFormData?.recipients || []).map(\n        (participant) => ({\n          ...participant,\n          protocol: nextIdentity.protocol,\n        })\n      );\n    }\n\n    const draft = {\n      ...draftMessageFormData,\n      ...(recipients ? { recipients } : {}),\n      identity_id: nextIdentity.identity_id,\n    };\n\n    return dispatch(saveDraft(draft, { withThrottle: true }));\n  };\n\n  const handleChange = (ev) => {\n    if (!draftMessageFormData) {\n      return undefined;\n    }\n\n    const { name, value } = ev.target;\n\n    const draft = {\n      ...draftMessageFormData,\n      [name]: value,\n    };\n\n    return draft && dispatch(saveDraft(draft, { withThrottle: true }));\n  };\n\n  const handleFilesChange = ({ attachments }) =>\n    dispatch(uploadAttachments(draftMessageFormData, attachments));\n\n  const handleDeleteAttachement = (attachment) =>\n    dispatch(deleteAttachement(draftMessageFormData, attachment));\n  const handleDelete = async () => {\n    await dispatch(onDeleteMessage({ message }));\n\n    onDeleteMessageSuccessfull();\n  };\n\n  const handleSend = async (ev) => {\n    ev.preventDefault();\n\n    setIsSending(true);\n\n    try {\n      const messageToSend = await dispatch(onSendDraft(draftMessageFormData));\n\n      // @ts-ignore\n      onSent(messageToSend);\n      setIsSending(false);\n    } catch (err) {\n      dispatch(\n        notifyError({\n          message: i18n._(/* i18n */ 'draft.feedback.send-error', undefined, {\n            message: 'Unable to send the message',\n          }),\n        })\n      );\n      setIsSending(false);\n    }\n  };\n\n  return (\n    <div\n      className={classnames(className, 'm-draft-message-advanced')}\n      // ref={ref}\n    >\n      <div className=\"m-draft-message-advanced__container\">\n        <IdentitySelector\n          className=\"m-draft-message-advanced__identity\"\n          identities={availableIdentities}\n          identityId={draftMessageFormData?.identity_id}\n          onChange={handleIdentityChange}\n        />\n        {draftMessageFormData && (\n          <Recipients\n            className=\"m-draft-message-advanced__recipient-list\"\n            draftMessage={draftMessageFormData}\n            availableIdentities={availableIdentities}\n          />\n        )}\n        {parentMessage && (\n          <div className=\"m-draft-message-advanced__parent\">\n            {/* @ts-ignore */}\n            <TextBlock>\n              <Link\n                to={`/discussions/${parentMessage.discussion_id}#${parentMessage.message_id}`}\n                className=\"m-reply__parent-link\"\n              >\n                <Trans\n                  id=\"reply-form.in-reply-to\"\n                  values={{ 0: parentMessage.excerpt }}\n                  message=\"In reply to: {0}\"\n                />\n              </Link>\n            </TextBlock>\n          </div>\n        )}\n        {!parentMessage && hasSubject && (\n          <TextFieldGroup\n            id=\"draft-message_subject\"\n            className=\"m-draft-message-advanced__subject\"\n            display=\"inline\"\n            label={\n              <Trans\n                id=\"messages.compose.form.subject.label\"\n                message=\"Subject\"\n              />\n            }\n            inputProps={{\n              name: 'subject',\n              value: draftMessageFormData?.subject,\n              onChange: handleChange,\n              disabled: !identity,\n            }}\n          />\n        )}\n        {isLocked ? (\n          <LockedMessage encryptionStatus={draftEncryption} />\n        ) : (\n          <TextareaFieldGroup\n            className=\"m-draft-advanced__body\"\n            label={\n              <Trans\n                id=\"messages.compose.form.body.label\"\n                message=\"Type your message here...\"\n              />\n            }\n            showLabelForSR\n            inputProps={{\n              name: 'body',\n              onChange: handleChange,\n              value: draftMessageFormData?.body,\n              disabled: !identity,\n            }}\n          />\n        )}\n        <AttachmentManager\n          // @ts-ignore\n          className=\"m-draft-message-advanced__attachments\"\n          onUploadAttachments={handleFilesChange}\n          onDeleteAttachement={handleDeleteAttachement}\n          message={draftMessageFormData}\n          disabled={!identity}\n        />\n      </div>\n      <div className=\"m-draft-message-advanced__action-send\">\n        {message && (\n          <Confirm\n            onConfirm={handleDelete}\n            title={\n              <Trans\n                id=\"message-list.message.confirm-delete.title\"\n                message=\"Delete a message\"\n              />\n            }\n            content={\n              <Trans\n                id=\"message-list.message.confirm-delete.content\"\n                message=\"The deletion is permanent, are you sure you want to delete this message ?\"\n              />\n            }\n            render={(confirm) => (\n              <Button\n                onClick={confirm}\n                icon=\"trash\"\n                color=\"alert\"\n                className=\"m-draft-message-advanced__action-button\"\n              >\n                <Trans\n                  id=\"message-list.message.action.delete\"\n                  message=\"Delete\"\n                />\n              </Button>\n            )}\n          />\n        )}\n        <Button\n          shape=\"plain\"\n          className={classnames(\n            'm-draft-message-advanced__action-button',\n            'm-draft-message-advanced__button-send',\n            {\n              'm-draft-message-advanced__button-send--encrypted':\n                encryptionEnabled,\n              'm-draft-message-advanced__button-send--unencrypted':\n                !encryptionEnabled,\n            }\n          )}\n          onClick={handleSend}\n          disabled={!canSend}\n        >\n          {isSending && (\n            <Spinner\n              svgTitleId=\"send-draft-spinner\"\n              display=\"inline\"\n              theme=\"bright\"\n            />\n          )}\n          {/* @ts-ignore */}\n          {!isSending && <Icon type=\"paper-plane\" />}{' '}\n          <Trans id=\"draft-message.action.send\" message=\"Send\" />\n        </Button>\n      </div>\n      {errors.length > 0 && (\n        <div className=\"m-draft-message-advanced__errors\">\n          <FieldErrors errors={errors} />\n        </div>\n      )}\n      <div className=\"m-draft-message-advanced__encryption\">\n        {encryptionEnabled ? (\n          <Trans\n            id=\"draft-message.encryption.ok\"\n            message=\"This message is encrypted\"\n          />\n        ) : (\n          <Trans\n            id=\"draft-message.encryption.ko\"\n            message=\"This message is not encrypted\"\n          />\n        )}\n      </div>\n    </div>\n  );\n}\n\nexport default withI18n()(DraftMessage);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDraft/index.tsx",
    "content": "import * as React from 'react';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Redirect, useHistory, useParams } from 'react-router-dom';\nimport { useDispatch } from 'react-redux';\nimport { useCloseTab, useCurrentTab } from '../../modules/tab';\nimport { withScrollManager } from '../../modules/scroll';\nimport { getMessage, Message } from '../../modules/message';\nimport DraftMessage from './components/DraftMessage';\nimport DraftDiscussion from './components/DraftDiscussion';\nimport './style.scss';\n\ninterface NewDraftProps {\n  scrollManager: {\n    scrollToTarget: () => string;\n  };\n  location: {\n    hash: string;\n  };\n}\nfunction NewDraft(props: NewDraftProps) {\n  const dispatch = useDispatch();\n  const history = useHistory();\n  const closeTab = useCloseTab();\n  const tab = useCurrentTab();\n  const { messageId } = useParams<{ messageId?: string }>();\n\n  const handleCloseTab = () => closeTab(tab);\n\n  // TODO: rollback pour garder le redirect si c'est pas draft\n  const redirectDiscussion = (message: Message) => {\n    history.push(`/discussions/${message.discussion_id}#${message.message_id}`);\n    handleCloseTab();\n  };\n\n  React.useEffect(() => {\n    if (messageId) {\n      // @ts-ignore\n      dispatch(getMessage({ messageId })).then(\n        (message) => {\n          if (!message.is_draft) {\n            redirectDiscussion(message);\n          }\n        },\n        () => {\n          // actually new draft, nothing to do\n        }\n      );\n    }\n  }, [messageId]);\n\n  const handleSent = (message) => {\n    redirectDiscussion(message);\n  };\n\n  const getHash = () => {\n    const { location } = props;\n\n    return location.hash ? location.hash.slice(1) : null;\n  };\n\n  const {\n    scrollManager: { scrollToTarget },\n  } = props;\n\n  if (!messageId) {\n    return <Redirect to={`/messages/${uuidv4()}`} />;\n  }\n\n  const hash = getHash();\n\n  return (\n    <div className=\"s-new-draft\">\n      <DraftMessage\n        scrollToMe={hash === 'compose' ? scrollToTarget : undefined}\n        className=\"s-new-draft__form\"\n        messageId={messageId}\n        onDeleteMessageSuccessfull={handleCloseTab}\n        onSent={handleSent}\n      />\n      <DraftDiscussion\n        className=\"s-new-draft__discussion\"\n        messageId={messageId}\n      />\n    </div>\n  );\n}\n\nexport default withScrollManager()(NewDraft);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/NewDraft/style.scss",
    "content": "@import '../../styles/common';\n\n.s-new-draft {\n  &__form {\n    padding-top: map_get($co-margin_, 'large');\n    background-color: #f6fcff;\n  }\n\n  &__discussion {\n    margin-top: map_get($co-margin_, 'large');\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/AuthButton/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport classnames from 'classnames';\nimport {\n  Spinner,\n  TextFieldGroup,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\nimport { capitalize } from '../../../../services/capitalize';\nimport {\n  PROVIDER_GMAIL,\n  PROVIDER_TWITTER,\n  PROVIDER_MASTODON,\n  withAuthorize,\n  ProviderIcon,\n} from '../../../../modules/remoteIdentity';\nimport ProviderButton from '../ProviderButton';\nimport './style.scss';\n\n@withAuthorize()\nclass AuthButton extends Component {\n  static propTypes = {\n    className: PropTypes.string,\n    onDone: PropTypes.func.isRequired,\n    authorize: PropTypes.func.isRequired,\n    providerName: PropTypes.oneOf([\n      PROVIDER_GMAIL,\n      PROVIDER_TWITTER,\n      PROVIDER_MASTODON,\n    ]).isRequired,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  state = {\n    hasActivity: false,\n    mastodonAcct: '',\n  };\n\n  handleAcctChange = (event) => {\n    this.setState({ mastodonAcct: event.target.value });\n  };\n\n  authorize = async () => {\n    const { authorize, onDone, providerName } = this.props;\n    const identifier = this.state.mastodonAcct;\n    this.setState({\n      hasActivity: true,\n    });\n\n    try {\n      const result = await authorize({ providerName, identifier });\n      onDone(result);\n    } catch (err) {\n      onDone(err);\n    } finally {\n      this.setState({\n        hasActivity: false,\n      });\n    }\n  };\n\n  render() {\n    const { className, providerName } = this.props;\n\n    return (\n      <FormGrid>\n        <FormRow>\n          <ProviderButton\n            onClick={this.authorize}\n            shape=\"plain\"\n            className={classnames(className, 'm-oauth-button')}\n            disabled={this.state.hasActivity}\n          >\n            {this.state.hasActivity ? (\n              <Spinner\n                svgTitleId={`authorize-provider-${providerName}-spinner`}\n                isloading\n              />\n            ) : (\n              <ProviderIcon\n                className=\"m-oauth-button__logo\"\n                providerName={providerName}\n              />\n            )}\n            {capitalize(providerName)}\n          </ProviderButton>\n        </FormRow>\n        {providerName === PROVIDER_MASTODON && (\n          <FormRow>\n            <FormColumn bottomSpace fluid>\n              <TextFieldGroup\n                label={\n                  <Trans id=\"remote_identity.form.mastodon.instance_label\">\n                    Before pushing Mastodon button, enter your account address\n                    below (ex.: username@instance.tld)\n                  </Trans>\n                }\n                inputProps={{\n                  value: this.state.mastodonAcct,\n                  onChange: this.handleAcctChange,\n                  name: 'mastodonAcct',\n                  autoComplete: 'on',\n                  required: true,\n                }}\n              />\n            </FormColumn>\n          </FormRow>\n        )}\n      </FormGrid>\n    );\n  }\n}\n\nexport default AuthButton;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/AuthButton/style.scss",
    "content": ".m-oauth-button {\n  &__logo {\n    height: 2rem;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/LastConnection/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport Moment from 'react-moment';\nimport { withSettings } from '../../../../modules/settings';\n\n@withSettings()\nclass lastConnection extends PureComponent {\n  static propTypes = {\n    lastCheck: PropTypes.string,\n    settings: PropTypes.shape({ default_locale: PropTypes.string.isRequired })\n      .isRequired,\n  };\n\n  static defaultProps = {\n    lastCheck: undefined,\n  };\n\n  render() {\n    const {\n      lastCheck,\n      settings: { default_locale: locale },\n    } = this.props;\n\n    if (lastCheck && lastCheck.length) {\n      return (\n        <Moment fromNow locale={locale}>\n          {lastCheck}\n        </Moment>\n      );\n    }\n\n    return <Trans id=\"remote_identity.last_connection.never\" message=\"Never\" />;\n  }\n}\n\nexport default lastConnection;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/NewIdentity/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Section, Callout, Icon, Title } from '../../../../components';\nimport {\n  PROVIDER_GMAIL,\n  PROVIDER_TWITTER,\n  PROVIDER_MASTODON,\n} from '../../../../modules/remoteIdentity';\nimport RemoteIdentityEmail from '../RemoteIdentityEmail';\nimport AuthButton from '../AuthButton';\nimport ProviderButtonContainer from '../ProviderButtonContainer';\nimport ProviderButton from '../ProviderButton';\nimport './provider-list.scss';\nimport './new-identity.scss';\nimport './provider-email-button.scss';\n\nclass NewIdentity extends Component {\n  static propTypes = {\n    providers: PropTypes.arrayOf(PropTypes.string),\n    onRemoteIdentityChange: PropTypes.func.isRequired,\n    onClear: PropTypes.func.isRequired,\n    onError: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    providers: [],\n  };\n\n  state = {\n    emailFormOpened: false,\n  };\n\n  handleToggleFormEmail = () => {\n    this.setState((prevState) => ({\n      emailFormOpened: !prevState.emailFormOpened,\n    }));\n  };\n\n  handleChangeRemoteIdentityEmail = async (...params) => {\n    const { onRemoteIdentityChange } = this.props;\n    await onRemoteIdentityChange(...params);\n    this.handleToggleFormEmail();\n    this.props.onClear();\n  };\n\n  handleOAuthDone = (result) => {\n    if (result && result.response) {\n      this.props.onError(result.response);\n    }\n    this.props.onClear();\n  };\n\n  render() {\n    const { providers } = this.props;\n\n    return (\n      <Section\n        title={\n          <Trans\n            id=\"remote_identity.add_account\"\n            message=\"Add an external account\"\n          />\n        }\n        titleProps={{ size: 'large' }}\n        className=\"m-new-identity\"\n        shape=\"none\"\n      >\n        <p className=\"m-new-identity__main-help\">\n          <Trans id=\"remote_identity.create_help\">\n            Add an external account will allow you to aggregate messages for\n            this account in your Caliopen inbox and answer to your contacts\n            using one of these acounts.\n          </Trans>\n        </p>\n        <Callout color=\"info\">\n          <Trans\n            id=\"remote_identity.how_to\"\n            components={[<p />, <br />]}\n            message={`\n            <0>\n              External accounts are fetched every 15 minutes. <1 />\n              Currently there is no indicator to inform that the account is correctly configured until first try is done.\n            </0>\n            `}\n          />\n        </Callout>\n        <Title hr>\n          <Trans\n            id=\"remote_identity.choose-provider.title\"\n            message=\"Select a provider\"\n          />\n        </Title>\n        <ul className=\"m-provider-list\">\n          {providers.includes(PROVIDER_GMAIL) && (\n            <li className=\"m-provider-list__provider\">\n              <ProviderButtonContainer\n                label={\n                  <Trans\n                    id=\"remote_identity.gmail.help\"\n                    message=\"You will be redirected to gmail authentication\"\n                  />\n                }\n              >\n                <AuthButton\n                  onDone={this.handleOAuthDone}\n                  providerName={PROVIDER_GMAIL}\n                />\n              </ProviderButtonContainer>\n            </li>\n          )}\n          {providers.includes(PROVIDER_TWITTER) && (\n            <li className=\"m-provider-list__provider\">\n              <ProviderButtonContainer\n                label={\n                  <Trans id=\"remote_identity.twitter.help\">\n                    You will be redirected to twitter authentication and\n                    authorize the application\n                  </Trans>\n                }\n              >\n                <AuthButton\n                  onDone={this.handleOAuthDone}\n                  providerName={PROVIDER_TWITTER}\n                />\n              </ProviderButtonContainer>\n            </li>\n          )}\n          {providers.includes(PROVIDER_MASTODON) && (\n            <li className=\"m-provider-list__provider\">\n              <ProviderButtonContainer\n                label={\n                  <Trans id=\"remote_identity.mastodon.help\">\n                    You will be redirected to mastodon authentication and\n                    authorize the application\n                  </Trans>\n                }\n              >\n                <AuthButton\n                  onDone={this.handleOAuthDone}\n                  providerName={PROVIDER_MASTODON}\n                />\n              </ProviderButtonContainer>\n            </li>\n          )}\n          <li className=\"m-provider-list__provider\">\n            <ProviderButtonContainer\n              label={\n                <Trans id=\"remote_identity.email.help\">\n                  You will have to fill the form with your imap server&apos;s\n                  parameters\n                </Trans>\n              }\n            >\n              <ProviderButton onClick={this.handleToggleFormEmail}>\n                <Icon\n                  className=\"m-provider-email-button__icon\"\n                  type=\"email\"\n                  rightSpaced\n                />\n                <Trans\n                  id=\"remote_identity.action.toggle-email-form\"\n                  message=\"Email (via IMAP)\"\n                />\n              </ProviderButton>\n            </ProviderButtonContainer>\n          </li>\n        </ul>\n        {this.state.emailFormOpened && (\n          <RemoteIdentityEmail\n            className=\"m-new-identity__email-form\"\n            onChange={this.handleChangeRemoteIdentityEmail}\n            onCancel={this.handleToggleFormEmail}\n          />\n        )}\n      </Section>\n    );\n  }\n}\n\nexport default NewIdentity;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/NewIdentity/new-identity.scss",
    "content": "@import '../../../../styles/common';\n\n.m-new-identity {\n  &__main-help {\n    color: $co-color__fg__text--higher;\n  }\n\n  &__email-form {\n    margin-top: map_get($co-margin_, 'xsmall');\n    padding: map_get($co-margin_, 'xsmall');\n    background-color: $co-color__bg__back--higher;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/NewIdentity/provider-email-button.scss",
    "content": "@import '../../../../styles/common';\n\n.m-provider-email-button {\n  &__icon {\n    vertical-align: middle;\n    height: 2.5rem;\n    color: $co-color__primary;\n    font-size: 2.125rem;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/NewIdentity/provider-list.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-provider-list {\n  &__provider {\n    margin-top: map_get($co-margin_, 'xsmall');\n\n    &:first-of-type {\n      margin-top: unset;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/ProviderButton/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { RawButton } from '../../../../components';\nimport './style.scss';\n\nclass ProviderButton extends PureComponent {\n  static propTypes = {\n    children: PropTypes.node,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    children: null,\n    className: undefined,\n  };\n\n  render() {\n    const { className, children, ...props } = this.props;\n\n    return (\n      <RawButton\n        className={classnames(className, 'm-provider-button')}\n        {...props}\n      >\n        {children}\n      </RawButton>\n    );\n  }\n}\n\nexport default ProviderButton;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/ProviderButton/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-clickable';\n\n.m-provider-button {\n  @include o-clickable;\n\n  width: 100%;\n  height: 100%;\n  min-height: $co-component__height--large;\n  border: 1px solid $co-color__fg__border--low;\n  background-color: $co-color__fg__back--low;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  color: $co-color__fg__text;\n  font-size: 1.25rem;\n  font-weight: unset;\n  line-height: 1.25rem;\n  white-space: nowrap;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/ProviderButtonContainer/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport './style.scss';\n\nclass ProviderButtonContainer extends PureComponent {\n  static propTypes = {\n    children: PropTypes.node,\n    label: PropTypes.node,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    children: null,\n    label: null,\n    className: undefined,\n  };\n\n  render() {\n    const { className, children, label } = this.props;\n\n    return (\n      <div className={classnames(className, 'm-provider-button-container')}>\n        <div className=\"m-provider-button-container__button\">{children}</div>\n        <div className=\"m-provider-button-container__label\">{label}</div>\n      </div>\n    );\n  }\n}\n\nexport default ProviderButtonContainer;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/ProviderButtonContainer/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-provider-button-container {\n  @include flex-grid-row($size: expand);\n\n  background-color: #f8f8f8;\n\n  &__button {\n    @include flex-grid-size(4);\n  }\n\n  &__label {\n    @include flex-grid-size(8);\n\n    padding: map_get($co-margin_, 'xsmall') map_get($co-margin_, 'small');\n    color: $co-color__fg__text--high;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/RemoteIdentity/index.jsx",
    "content": "import React, { Component, Fragment } from 'react';\nimport PropTypes from 'prop-types';\nimport { Section, Icon } from '../../../../components';\nimport {\n  PROVIDER_EMAIL,\n  PROVIDER_GMAIL,\n  PROVIDER_TWITTER,\n  PROVIDER_MASTODON,\n  ProviderIcon,\n} from '../../../../modules/remoteIdentity';\nimport RemoteIdentityEmail from '../RemoteIdentityEmail';\nimport RemoteIdentityOauth from '../RemoteIdentityOauth';\n\nclass RemoteIdentity extends Component {\n  static propTypes = {\n    remoteIdentity: PropTypes.shape({}),\n    onRemoteIdentityChange: PropTypes.func.isRequired,\n    onRemoteIdentityDelete: PropTypes.func.isRequired,\n    onClear: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    remoteIdentity: undefined,\n  };\n\n  renderType() {\n    const { onRemoteIdentityDelete, onRemoteIdentityChange, remoteIdentity } =\n      this.props;\n\n    const renderComponent = (C) => (\n      <C\n        key={remoteIdentity.identity_id || 'new'}\n        remoteIdentity={remoteIdentity}\n        onChange={onRemoteIdentityChange}\n        onDelete={onRemoteIdentityDelete}\n        clear={this.props.onClear}\n      />\n    );\n\n    switch (remoteIdentity.infos.provider) {\n      default:\n      case PROVIDER_EMAIL:\n        return renderComponent(RemoteIdentityEmail);\n      case PROVIDER_GMAIL:\n      case PROVIDER_TWITTER:\n      case PROVIDER_MASTODON:\n        return renderComponent(RemoteIdentityOauth);\n    }\n  }\n\n  renderIcon = (remoteIdentity) => {\n    if (!remoteIdentity.infos.provider) {\n      return <Icon type=\"email\" rightSpaced />;\n    }\n\n    return (\n      <ProviderIcon\n        className=\"m-remote-identity__provider-logo\"\n        providerName={remoteIdentity.infos.provider}\n      />\n    );\n  };\n\n  renderTitle() {\n    const { remoteIdentity } = this.props;\n    const displayName = remoteIdentity.display_name || 'N/A';\n\n    return (\n      <Fragment>\n        {this.renderIcon(remoteIdentity)}\n        {displayName}\n      </Fragment>\n    );\n  }\n\n  render() {\n    const remoteIdentity =\n      this.props.remoteIdentity || this.state.newRemoteIdentity;\n    const context = remoteIdentity.status === 'active' ? 'safe' : 'disabled';\n\n    return (\n      <Section title={this.renderTitle(remoteIdentity)} borderContext={context}>\n        {this.renderType()}\n      </Section>\n    );\n  }\n}\n\nexport default RemoteIdentity;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/RemoteIdentityDetails/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport {\n  Callout,\n  CheckboxFieldGroup,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\nimport LastConnection from '../LastConnection';\nimport Status from '../Status';\nimport './style.scss';\n\nclass RemoteIdentityDetails extends Component {\n  static propTypes = {\n    remoteIdentity: PropTypes.shape({}).isRequired,\n    className: PropTypes.string,\n    onToggleActivate: PropTypes.func.isRequired,\n    active: PropTypes.bool.isRequired,\n    errors: PropTypes.shape({\n      status: PropTypes.bool,\n    }),\n  };\n\n  static defaultProps = {\n    className: undefined,\n    errors: {},\n  };\n\n  handleActivate = (event) => {\n    const { checked } = event.target;\n\n    this.props.onToggleActivate(checked);\n  };\n\n  render() {\n    const { className, remoteIdentity, active, errors } = this.props;\n\n    if (!remoteIdentity.identity_id) {\n      return null;\n    }\n\n    return (\n      <FormGrid className={classnames(className)}>\n        <FormRow>\n          <FormColumn bottomSpace>\n            <Trans\n              id=\"remote_identity.last_connection\"\n              message=\"Last connection: <0/>\"\n              components={[\n                <LastConnection lastCheck={remoteIdentity.last_check} />,\n              ]}\n            />\n          </FormColumn>\n        </FormRow>\n        {remoteIdentity.infos.lastFetchError && (\n          <FormRow>\n            <FormColumn bottomSpace>\n              <Callout color=\"alert\">\n                {remoteIdentity.infos.lastFetchError}\n              </Callout>\n            </FormColumn>\n          </FormRow>\n        )}\n        <FormRow>\n          <FormColumn bottomSpace>\n            <CheckboxFieldGroup\n              checked={active}\n              errors={errors.status}\n              onChange={this.handleActivate}\n              name=\"active\"\n              label={<Status status={active ? 'active' : 'inactive'} />}\n              displaySwitch\n              showTextLabel\n            />\n          </FormColumn>\n        </FormRow>\n      </FormGrid>\n    );\n  }\n}\n\nexport default RemoteIdentityDetails;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/RemoteIdentityDetails/style.scss",
    "content": ".m-remote-identity {\n  &__provider-logo {\n    height: 1rem;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/RemoteIdentityEmail/index.jsx",
    "content": "import React, { Component, Fragment } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { withI18n, Trans } from '@lingui/react';\nimport {\n  TextFieldGroup,\n  CheckboxFieldGroup,\n  FormGrid,\n  FormRow,\n  FormColumn,\n  FieldErrors,\n  Confirm,\n  Button,\n  Spinner,\n  TextBlock,\n} from '../../../../components';\nimport {\n  REMOTE_IDENTITY_STATUS_ACTIVE,\n  REMOTE_IDENTITY_STATUS_INACTIVE,\n  PROTOCOL_EMAIL,\n  Identity,\n} from '../../../../modules/remoteIdentity';\nimport Status from '../Status';\nimport RemoteIdentityDetails from '../RemoteIdentityDetails';\nimport './style.scss';\n\nfunction generateStateFromProps(props, prevState) {\n  const {\n    identity_id: identityId,\n    infos,\n    credentials = {},\n    status,\n    protocol,\n    identifier,\n  } = props.remoteIdentity || {};\n  const [inserverHostname = '', inserverPort = ''] =\n    infos && infos.inserver\n      ? infos.inserver.split(':')\n      : [\n          prevState.remoteIdentity.inserverHostname,\n          prevState.remoteIdentity.inserverPort,\n        ];\n  const [outserverHostname = '', outserverPort = ''] =\n    infos && infos.outserver\n      ? infos.outserver.split(':')\n      : [\n          prevState.remoteIdentity.outserverHostname,\n          prevState.remoteIdentity.outserverPort,\n        ];\n  const active = status\n    ? status === REMOTE_IDENTITY_STATUS_ACTIVE\n    : prevState.remoteIdentity.active;\n\n  return {\n    editing: !identityId,\n    remoteIdentity: {\n      ...prevState.remoteIdentity,\n      ...(identifier ? { identifier } : {}),\n      inserverHostname,\n      inserverPort,\n      outserverHostname,\n      outserverPort,\n      credentials,\n      active,\n      ...(protocol ? { protocol } : {}),\n    },\n  };\n}\n\nfunction getIdentityFromState(state, props) {\n  const {\n    remoteIdentity: {\n      identifier,\n      active,\n      protocol,\n      inserverHostname,\n      inserverPort,\n      inusername,\n      inpassword,\n      outserverHostname,\n      outserverPort,\n      outusername,\n      outpassword,\n    },\n  } = state;\n  const { remoteIdentity } = props;\n  const credentials =\n    inusername || inpassword || outusername || outpassword\n      ? {\n          ...(remoteIdentity.credentials ? remoteIdentity.credentials : {}),\n          inusername,\n          inpassword,\n          outusername,\n          outpassword,\n        }\n      : undefined;\n\n  return new Identity({\n    ...remoteIdentity,\n    credentials,\n    display_name: identifier,\n    identifier,\n    infos: {\n      ...(remoteIdentity.infos ? remoteIdentity.infos : {}),\n      inserver: `${inserverHostname}:${inserverPort}`,\n      outserver: `${outserverHostname}:${outserverPort}`,\n    },\n    protocol,\n    status: active\n      ? REMOTE_IDENTITY_STATUS_ACTIVE\n      : REMOTE_IDENTITY_STATUS_INACTIVE,\n  });\n}\n\n@withI18n()\nclass RemoteIdentityEmail extends Component {\n  static propTypes = {\n    className: PropTypes.string,\n    remoteIdentity: PropTypes.shape({}),\n    onChange: PropTypes.func.isRequired,\n    onDelete: PropTypes.func,\n    onCancel: PropTypes.func,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {\n    className: undefined,\n    remoteIdentity: {},\n    onDelete: () => {\n      // noop\n    },\n    onCancel: () => {\n      // noop\n    },\n  };\n\n  static initialState = {\n    editing: false,\n    formErrors: {},\n    advancedForm: false,\n    remoteIdentity: {\n      identifier: '',\n      inserverHostname: '',\n      inserverPort: '993',\n      inusername: '',\n      inpassword: '',\n      outserverHostname: '',\n      outserverPort: '465',\n      outusername: '',\n      outpassword: '',\n      active: true,\n      protocol: PROTOCOL_EMAIL,\n    },\n  };\n\n  state = { ...this.constructor.initialState };\n\n  UNSAFE_componentWillMount() {\n    this.init();\n  }\n\n  init = () => {\n    this.setState((prevState) => generateStateFromProps(this.props, prevState));\n  };\n\n  reset = () => {\n    this.setState(\n      generateStateFromProps(this.props, { ...this.constructor.initialState })\n    );\n  };\n\n  save = async () => {\n    const { onChange, remoteIdentity } = this.props;\n    try {\n      await onChange({\n        identity: getIdentityFromState(this.state, this.props),\n      });\n      if (remoteIdentity.identity_id) {\n        this.reset();\n      }\n    } catch (errs) {\n      if (errs.some((err) => err.code === 6)) {\n        this.setState({\n          formErrors: {\n            identifier: [\n              <Trans\n                id=\"remote_identity.form.identifier.error.uniqueness\"\n                message=\"This address is already configured\"\n              />,\n            ],\n          },\n        });\n      } else {\n        this.setState({\n          formErrors: { global: errs.map(({ message }) => message) },\n        });\n      }\n    }\n  };\n\n  handleSave = async () => {\n    if (!this.validate()) {\n      return;\n    }\n\n    this.save();\n  };\n\n  handleDelete = () => {\n    const { remoteIdentity: identity } = this.props;\n\n    this.props.onDelete({ identity });\n  };\n\n  handleCancel = () => {\n    this.reset();\n    // force usage of the callback using batching mecanism of setState\n    this.setState(\n      () => ({}),\n      () => this.props.onCancel()\n    );\n  };\n\n  handleParamsChange = (event) => {\n    const { name, value } = event.target;\n\n    this.setState((prevState) => ({\n      remoteIdentity: {\n        ...prevState.remoteIdentity,\n        [name]: value,\n      },\n    }));\n  };\n\n  handleBlurIdentifier = () => {\n    const { remoteIdentity } = this.props;\n\n    if (remoteIdentity.entity_id) {\n      return;\n    }\n\n    this.setState((prevState) => {\n      if (prevState.remoteIdentity.inusername.length > 0) {\n        return {};\n      }\n\n      return {\n        remoteIdentity: {\n          ...prevState.remoteIdentity,\n          inusername: prevState.remoteIdentity.identifier,\n          outusername: prevState.remoteIdentity.identifier,\n        },\n      };\n    });\n  };\n\n  createHandleBlurInOutParam = (shortname) => () => {\n    const { remoteIdentity } = this.props;\n\n    if (remoteIdentity.entity_id) {\n      return;\n    }\n\n    this.setState((prevState) => {\n      if (prevState.remoteIdentity[`out${shortname}`].length > 0) {\n        return {};\n      }\n\n      return {\n        remoteIdentity: {\n          ...prevState.remoteIdentity,\n          [`out${shortname}`]: prevState.remoteIdentity[`in${shortname}`],\n        },\n      };\n    });\n  };\n\n  handleActivate = (active) => {\n    this.setState(\n      (prevState) => ({\n        remoteIdentity: {\n          ...prevState.remoteIdentity,\n          active,\n        },\n      }),\n      () => {\n        this.props.onChange({\n          identity: getIdentityFromState(this.state, this.props),\n        });\n      }\n    );\n  };\n\n  handleToggleEdit = () => {\n    this.setState((prevState) => ({\n      editing: !prevState.editing,\n    }));\n  };\n\n  handlToggleAdvanced = () => {\n    this.setState((prevState) => ({\n      advancedForm: !prevState.advancedForm,\n    }));\n  };\n\n  validate = () => {\n    let isValid = true;\n\n    const phaseValidation = (properties) => {\n      properties.forEach(({ formProperty, error }) => {\n        const value = this.state.remoteIdentity[formProperty];\n\n        if (!value || value.length === 0) {\n          this.setState((prevState) => ({\n            formErrors: {\n              ...prevState.formErrors,\n              [formProperty]: [error],\n            },\n          }));\n          isValid = false;\n        } else {\n          this.setState((prevState) => ({\n            formErrors: {\n              ...prevState.formErrors,\n              [formProperty]: [],\n            },\n          }));\n        }\n      });\n    };\n\n    const { remoteIdentity } = this.props;\n\n    phaseValidation([\n      {\n        formProperty: 'identifier',\n        error: (\n          <Trans\n            id=\"remote_identity.form.identifier.error\"\n            message=\"a valid email is required\"\n          />\n        ),\n      },\n      {\n        formProperty: 'inserverHostname',\n        error: (\n          <Trans\n            id=\"remote_identity.form.serverHostname.error\"\n            message=\"mail server is required\"\n          />\n        ),\n      },\n      {\n        formProperty: 'inserverPort',\n        error: (\n          <Trans\n            id=\"remote_identity.form.serverPort.error\"\n            message=\"port is required\"\n          />\n        ),\n      },\n      {\n        formProperty: 'outserverHostname',\n        error: (\n          <Trans\n            id=\"remote_identity.form.serverHostname.error\"\n            message=\"mail server is required\"\n          />\n        ),\n      },\n      {\n        formProperty: 'outserverPort',\n        error: (\n          <Trans\n            id=\"remote_identity.form.serverPort.error\"\n            message=\"port is required\"\n          />\n        ),\n      },\n      {\n        formProperty: 'protocol',\n        error: (\n          <Trans\n            id=\"remote_identity.form.protocol.error\"\n            message=\"protocol is required\"\n          />\n        ),\n      },\n      ...(!remoteIdentity.identity_id ||\n      this.state.remoteIdentity.inusername.length > 0 ||\n      this.state.remoteIdentity.inpassword.length > 0 ||\n      this.state.remoteIdentity.outusername.length > 0 ||\n      this.state.remoteIdentity.outpassword.length > 0\n        ? [\n            {\n              formProperty: 'inusername',\n              error: (\n                <Trans\n                  id=\"remote_identity.form.username.error\"\n                  message=\"login is required\"\n                />\n              ),\n            },\n            {\n              formProperty: 'inpassword',\n              error: (\n                <Trans\n                  id=\"remote_identity.form.password.error\"\n                  message=\"password is required\"\n                />\n              ),\n            },\n            {\n              formProperty: 'outusername',\n              error: (\n                <Trans\n                  id=\"remote_identity.form.username.error\"\n                  message=\"login is required\"\n                />\n              ),\n            },\n            {\n              formProperty: 'outpassword',\n              error: (\n                <Trans\n                  id=\"remote_identity.form.password.error\"\n                  message=\"password is required\"\n                />\n              ),\n            },\n          ]\n        : []),\n    ]);\n\n    return isValid;\n  };\n\n  renderForm() {\n    const { remoteIdentity, i18n } = this.props;\n\n    return (\n      <FormGrid>\n        <FormRow>\n          <FormColumn bottomSpace size=\"medium\">\n            <TextFieldGroup\n              inputProps={{\n                type: 'email',\n                placeholder: i18n._(\n                  /* i18n */ 'remote_identity.form.identifier.placeholder',\n                  null,\n                  { message: 'john@doe.tld' }\n                ),\n                value: this.state.remoteIdentity.identifier,\n                onChange: this.handleParamsChange,\n                onBlur: this.handleBlurIdentifier,\n                name: 'identifier',\n                // specificity of backend: the identifier and protocol are unique and immutable\n                // cf. https://github.com/CaliOpen/Caliopen/blob/d6bbe43cc1098844f53eaec283e08c19c5f871bc/doc/specifications/identities/index.md#model\n                disabled: remoteIdentity && remoteIdentity.identity_id && true,\n                autoComplete: 'email',\n                required: true,\n              }}\n              label={\n                <Trans\n                  id=\"remote_identity.form.identifier.label\"\n                  message=\"Email:\"\n                />\n              }\n              errors={this.state.formErrors.identifier}\n            />\n          </FormColumn>\n        </FormRow>\n        {this.state.advancedForm && (\n          <FormRow>\n            <FormColumn bottomSpace>\n              <TextBlock weight=\"strong\">\n                <Trans id=\"remote_identity.form.inserver\" message=\"In server\" />\n              </TextBlock>\n            </FormColumn>\n          </FormRow>\n        )}\n        <FormRow>\n          {/* <FormColumn bottomSpace size=\"medium\">\n            <SelectFieldGroup\n              label={<Trans id=\"remote_identity.form.protocol.label\" message=\"Protocol:\" />}\n              value={this.state.remoteIdentity.protocol}\n              options={MAIL_PROTOCOLS.map(key => ({ value: key, label: key }))}\n              errors={this.state.formErrors.protocol}\n              onChange={this.handleParamsChange}\n              name=\"protocol\"\n              required\n              disabled\n            />\n          </FormColumn> */}\n          <FormColumn bottomSpace fluid>\n            <TextFieldGroup\n              inputProps={{\n                value: this.state.remoteIdentity.inserverHostname,\n                onChange: this.handleParamsChange,\n                onBlur: this.createHandleBlurInOutParam('serverHostname'),\n                name: 'inserverHostname',\n                autoComplete: 'on',\n                required: true,\n              }}\n              label={\n                <Trans\n                  id=\"remote_identity.form.incomming_mail_server.label\"\n                  message=\"Incoming mail server:\"\n                />\n              }\n              errors={this.state.formErrors.inserverHostname}\n            />\n          </FormColumn>\n          <FormColumn bottomSpace size=\"shrink\">\n            <TextFieldGroup\n              inputProps={{\n                value: this.state.remoteIdentity.inserverPort,\n                onChange: this.handleParamsChange,\n                name: 'inserverPort',\n                type: 'number',\n                autoComplete: 'on',\n                required: true,\n              }}\n              label={\n                <Trans id=\"remote_identity.form.port.label\" message=\"Port:\" />\n              }\n              errors={this.state.formErrors.inserverPort}\n            />\n          </FormColumn>\n        </FormRow>\n        {!this.state.advancedForm && (\n          <FormRow>\n            <FormColumn bottomSpace fluid>\n              <TextFieldGroup\n                inputProps={{\n                  value: this.state.remoteIdentity.outserverHostname,\n                  onChange: this.handleParamsChange,\n                  name: 'outserverHostname',\n                  autoComplete: 'on',\n                  required: true,\n                }}\n                label={\n                  <Trans\n                    id=\"remote_identity.form.outgoing_mail_server.label\"\n                    message=\"Outgoing mail server:\"\n                  />\n                }\n                errors={this.state.formErrors.outserverHostname}\n              />\n            </FormColumn>\n            <FormColumn bottomSpace size=\"shrink\">\n              <TextFieldGroup\n                inputProps={{\n                  value: this.state.remoteIdentity.outserverPort,\n                  onChange: this.handleParamsChange,\n                  name: 'outserverPort',\n                  type: 'number',\n                  autoComplete: 'on',\n                  required: true,\n                }}\n                label={\n                  <Trans id=\"remote_identity.form.port.label\" message=\"Port:\" />\n                }\n                errors={this.state.formErrors.outserverPort}\n              />\n            </FormColumn>\n          </FormRow>\n        )}\n        <FormRow>\n          <FormColumn bottomSpace size=\"medium\">\n            <TextFieldGroup\n              inputProps={{\n                value: this.state.remoteIdentity.inusername,\n                onChange: this.handleParamsChange,\n                onBlur: this.createHandleBlurInOutParam('username'),\n                name: 'inusername',\n                autoComplete: 'username',\n                required: true,\n              }}\n              label={\n                <Trans\n                  id=\"remote_identity.form.username.label\"\n                  message=\"Login:\"\n                />\n              }\n              errors={this.state.formErrors.inusername}\n            />\n          </FormColumn>\n          <FormColumn bottomSpace size=\"medium\">\n            <TextFieldGroup\n              inputProps={{\n                type: 'password',\n                value: this.state.remoteIdentity.inpassword,\n                onChange: this.handleParamsChange,\n                onBlur: this.createHandleBlurInOutParam('password'),\n                name: 'inpassword',\n                autoComplete: 'new-password',\n                required: true,\n              }}\n              label={\n                <Trans\n                  id=\"remote_identity.form.password.label\"\n                  message=\"Password:\"\n                />\n              }\n              errors={this.state.formErrors.inpassword}\n            />\n          </FormColumn>\n        </FormRow>\n        {this.state.advancedForm && (\n          <Fragment>\n            <FormRow>\n              <FormColumn bottomSpace>\n                <TextBlock weight=\"strong\">\n                  <Trans\n                    id=\"remote_identity.form.outserver\"\n                    message=\"Out server\"\n                  />\n                </TextBlock>\n              </FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn bottomSpace fluid>\n                <TextFieldGroup\n                  inputProps={{\n                    value: this.state.remoteIdentity.outserverHostname,\n                    onChange: this.handleParamsChange,\n                    name: 'outserverHostname',\n                    autoComplete: 'on',\n                    required: true,\n                  }}\n                  label={\n                    <Trans\n                      id=\"remote_identity.form.outgoing_mail_server.label\"\n                      message=\"Outgoing mail server:\"\n                    />\n                  }\n                  errors={this.state.formErrors.outserverHostname}\n                />\n              </FormColumn>\n              <FormColumn bottomSpace size=\"shrink\">\n                <TextFieldGroup\n                  inputProps={{\n                    value: this.state.remoteIdentity.outserverPort,\n                    onChange: this.handleParamsChange,\n                    name: 'outserverPort',\n                    type: 'number',\n                    autoComplete: 'on',\n                    required: true,\n                  }}\n                  label={\n                    <Trans\n                      id=\"remote_identity.form.port.label\"\n                      message=\"Port:\"\n                    />\n                  }\n                  errors={this.state.formErrors.outserverPort}\n                />\n              </FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn bottomSpace size=\"medium\">\n                <TextFieldGroup\n                  inputProps={{\n                    value: this.state.remoteIdentity.outusername,\n                    onChange: this.handleParamsChange,\n                    name: 'outusername',\n                    autoComplete: 'username',\n                    required: true,\n                  }}\n                  label={\n                    <Trans\n                      id=\"remote_identity.form.username.label\"\n                      message=\"Login:\"\n                    />\n                  }\n                  errors={this.state.formErrors.outusername}\n                />\n              </FormColumn>\n              <FormColumn bottomSpace size=\"medium\">\n                <TextFieldGroup\n                  inputProps={{\n                    type: 'password',\n                    value: this.state.remoteIdentity.outpassword,\n                    onChange: this.handleParamsChange,\n                    name: 'outpassword',\n                    autoComplete: 'new-password',\n                    required: true,\n                  }}\n                  label={\n                    <Trans\n                      id=\"remote_identity.form.password.label\"\n                      message=\"Password:\"\n                    />\n                  }\n                  errors={this.state.formErrors.outpassword}\n                />\n              </FormColumn>\n            </FormRow>\n          </Fragment>\n        )}\n        {remoteIdentity && remoteIdentity.identity_id && (\n          <FormRow>\n            <FormColumn bottomSpace>\n              <CheckboxFieldGroup\n                checked={this.state.remoteIdentity.active}\n                errors={this.state.formErrors.status}\n                onChange={this.handleParamsChange}\n                name=\"active\"\n                label={\n                  <Status\n                    status={\n                      this.state.remoteIdentity.active ? 'active' : 'inactive'\n                    }\n                  />\n                }\n                displaySwitch\n                showTextLabel\n              />\n            </FormColumn>\n          </FormRow>\n        )}\n      </FormGrid>\n    );\n  }\n\n  renderGlobalError() {\n    if (!this.state.formErrors.global) {\n      return null;\n    }\n\n    return <FieldErrors errors={this.state.formErrors.global} />;\n  }\n\n  renderContent() {\n    const { remoteIdentity } = this.props;\n    if (this.state.editing) {\n      return this.renderForm();\n    }\n\n    return (\n      <RemoteIdentityDetails\n        remoteIdentity={remoteIdentity}\n        active={this.state.remoteIdentity.active}\n        onToggleActivate={this.handleActivate}\n        onChange={this.handleChange}\n        onDelete={this.handleDelete}\n        onEdit={this.handleToggleEdit}\n        onClear={this.handleClear}\n      />\n    );\n  }\n\n  renderActions() {\n    const { remoteIdentity } = this.props;\n\n    return (\n      <div>\n        {remoteIdentity.identity_id && (\n          <Confirm\n            title={\n              <Trans\n                id=\"remote_identity.confirm-delete.title\"\n                message=\"Delete the external account\"\n              />\n            }\n            content={\n              <Trans\n                id=\"remote_identity.confirm-delete.content\"\n                message=\"The external account will deactivated then deleted.\"\n              />\n            }\n            onConfirm={this.handleDelete}\n            render={(confirm) => (\n              <Button\n                onClick={confirm}\n                shape=\"plain\"\n                color=\"alert\"\n                className=\"m-remote-identity-email__action\"\n              >\n                <Trans id=\"remote_identity.action.delete\" message=\"Delete\" />\n              </Button>\n            )}\n          />\n        )}\n        {!this.state.editing && (\n          <Button\n            onClick={this.handleToggleEdit}\n            shape=\"hollow\"\n            className=\"m-remote-identity-email__action\"\n          >\n            <Trans id=\"remote_identity.action.edit\" message=\"Edit\" />\n          </Button>\n        )}\n        {this.state.editing && (\n          <Fragment>\n            <Button\n              onClick={this.handleCancel}\n              shape=\"hollow\"\n              className=\"m-remote-identity-email__action\"\n            >\n              <Trans id=\"remote_identity.action.cancel\" message=\"Cancel\" />\n            </Button>\n            <Button\n              onClick={this.handlToggleAdvanced}\n              shape=\"plain\"\n              className=\"m-remote-identity-email__action\"\n            >\n              {!this.state.advancedForm && (\n                <Trans\n                  id=\"remote_identity.action.toggle-advanced-form\"\n                  message=\"Advanced\"\n                />\n              )}\n              {this.state.advancedForm && (\n                <Trans\n                  id=\"remote_identity.action.toggle-simple-form\"\n                  message=\"Simple\"\n                />\n              )}\n            </Button>\n            <Button\n              onClick={this.handleSave}\n              shape=\"plain\"\n              className=\"m-remote-identity-email__action\"\n              icon={\n                this.state.hasActivity ? (\n                  <Spinner\n                    svgTitleId=\"save-remote-email-spinner\"\n                    isloading\n                    display=\"inline\"\n                  />\n                ) : (\n                  'email'\n                )\n              }\n              disabled={this.state.hasActivity}\n            >\n              <Trans id=\"remote_identity.action.save\" message=\"Save\" />\n            </Button>\n          </Fragment>\n        )}\n      </div>\n    );\n  }\n\n  render() {\n    const { className } = this.props;\n\n    return (\n      <div className={classnames('m-remote-identity-email', className)}>\n        {this.renderGlobalError()}\n        {this.renderContent()}\n        {this.renderActions()}\n      </div>\n    );\n  }\n}\n\nexport default RemoteIdentityEmail;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/RemoteIdentityEmail/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport 'test/unit/lingui-react';\nimport RemoteIdentityEmail from '.';\n\njest.mock('../../../../modules/settings', () => ({\n  WithSettings: ({ render }) => render({}, false),\n  withSettings: () => (C) =>\n    function (props) {\n      return <C {...props} />;\n    },\n}));\n\ndescribe('component RemoteIdentityEmail', () => {\n  it('init form', () => {\n    const props = {\n      remoteIdentity: {},\n      onChange: jest.fn(),\n      onDelete: jest.fn(),\n      onCancel: jest.fn(),\n    };\n\n    const wrapper = shallow(<RemoteIdentityEmail {...props} />).dive();\n    expect(wrapper.state().editing).toEqual(true);\n  });\n\n  it('existing remoteIdentity', () => {\n    const props = {\n      remoteIdentity: {\n        identity_id: 'foo',\n        display_name: 'Foo',\n        infos: {},\n      },\n      onChange: jest.fn(),\n      onDelete: jest.fn(),\n      onCancel: jest.fn(),\n    };\n\n    const wrapper = shallow(<RemoteIdentityEmail {...props} />).dive();\n    expect(wrapper.state().editing).toEqual(false);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/RemoteIdentityEmail/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-remote-identity-email {\n  &__action {\n    &:not(:first-child) {\n      margin-left: $co-component__spacing;\n    }\n\n    &--push-right {\n      float: right;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/RemoteIdentityOauth/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Trans, withI18n } from '@lingui/react';\nimport { Confirm, Button } from '../../../../components';\nimport {\n  REMOTE_IDENTITY_STATUS_ACTIVE,\n  REMOTE_IDENTITY_STATUS_INACTIVE,\n  Identity,\n} from '../../../../modules/remoteIdentity';\nimport RemoteIdentityDetails from '../RemoteIdentityDetails';\n\n@withI18n()\nclass RemoteIdentitOauth extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    className: PropTypes.string,\n    remoteIdentity: PropTypes.shape({}).isRequired,\n    onChange: PropTypes.func.isRequired,\n    onDelete: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  state = {\n    remoteIdentity: {\n      identifier: '',\n      active: true,\n    },\n  };\n\n  UNSAFE_componentWillMount() {\n    this.setState(this.getStateFromProps());\n  }\n\n  getStateFromProps = () => {\n    const {\n      remoteIdentity: { identifier, status },\n    } = this.props;\n    const active = status\n      ? status === REMOTE_IDENTITY_STATUS_ACTIVE\n      : this.state.remoteIdentity.active;\n\n    return {\n      remoteIdentity: {\n        ...this.state.remoteIdentity,\n        identifier,\n        active,\n      },\n    };\n  };\n\n  getIdentityFromState = () => {\n    const {\n      remoteIdentity: { identifier, active },\n    } = this.state;\n    const { remoteIdentity } = this.props;\n\n    return new Identity({\n      ...remoteIdentity,\n      identifier,\n      display_name: identifier,\n      status: active\n        ? REMOTE_IDENTITY_STATUS_ACTIVE\n        : REMOTE_IDENTITY_STATUS_INACTIVE,\n    });\n  };\n\n  handleDelete = () => {\n    const { remoteIdentity, onDelete } = this.props;\n\n    onDelete({ identity: remoteIdentity });\n  };\n\n  handleActivate = (active) => {\n    this.setState(\n      (prevState) => ({\n        remoteIdentity: {\n          ...prevState.remoteIdentity,\n          active,\n        },\n      }),\n      () => {\n        const identity = this.getIdentityFromState();\n        this.props.onChange({ identity });\n      }\n    );\n  };\n\n  renderActions() {\n    return (\n      <div>\n        <Confirm\n          title={\n            <Trans\n              id=\"remote_identity.confirm-delete.title\"\n              message=\"Delete the external account\"\n            />\n          }\n          content={\n            <Trans\n              id=\"remote_identity.confirm-delete.content\"\n              message=\"The external account will deactivated then deleted.\"\n            />\n          }\n          onConfirm={this.handleDelete}\n          render={(confirm) => (\n            <Button\n              onClick={confirm}\n              shape=\"plain\"\n              color=\"alert\"\n              className=\"m-remote-identity-email__action\"\n            >\n              <Trans id=\"remote_identity.action.delete\" message=\"Delete\" />\n            </Button>\n          )}\n        />\n      </div>\n    );\n  }\n\n  renderContent() {\n    const { remoteIdentity } = this.props;\n\n    return (\n      <RemoteIdentityDetails\n        remoteIdentity={remoteIdentity}\n        onToggleActivate={this.handleActivate}\n        onChange={this.handleChange}\n        onDelete={this.handleDelete}\n        onEdit={this.handleToggleEdit}\n        onClear={this.handleClear}\n        active={this.state.remoteIdentity.active}\n      />\n    );\n  }\n\n  render() {\n    const { className } = this.props;\n\n    return (\n      <div className={classnames(className)}>\n        {this.renderContent()}\n        {this.renderActions()}\n      </div>\n    );\n  }\n}\n\nexport default RemoteIdentitOauth;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/components/Status/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\n\nclass Status extends PureComponent {\n  static propTypes = {\n    status: PropTypes.string.isRequired,\n  };\n\n  render() {\n    const { status } = this.props;\n    const statusLabels = {\n      active: <Trans id=\"remote_identity.status.active\" message=\"Enabled\" />,\n      inactive: (\n        <Trans id=\"remote_identity.status.inactive\" message=\"Disabled\" />\n      ),\n    };\n\n    return statusLabels[status];\n  }\n}\n\nexport default Status;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport { withI18n } from '@lingui/react';\nimport {\n  createIdentity,\n  deleteIdentity,\n  updateIdentity,\n  identitiesSelector,\n  identityStateSelector,\n} from '../../modules/remoteIdentity';\nimport { requestRemoteIdentities } from '../../store/modules/remote-identity';\nimport { requestProviders } from '../../store/modules/provider';\nimport { getModuleStateSelector } from '../../store/selectors/getModuleStateSelector';\nimport { withNotification } from '../../modules/userNotify';\nimport Presenter from './presenter';\n\nconst providersSelector = (state) => {\n  const { providers } = getModuleStateSelector('provider')(state);\n\n  return providers && providers.map((provider) => provider.name);\n};\n\nconst mapStateToProps = createSelector(\n  [identityStateSelector, identitiesSelector, providersSelector],\n  (identityState, identities, providers) => ({\n    isFetching: identityState.isFetching,\n    identities: [...identities].sort((a, b) =>\n      a.display_name.localeCompare(b.display_name)\n    ),\n    providers,\n  })\n);\n\nconst onIdentityChange =\n  ({ identity }) =>\n  (dispatch) => {\n    if (!identity.identity_id) {\n      return dispatch(createIdentity({ identity }));\n    }\n\n    return dispatch(updateIdentity({ identity }));\n  };\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      requestRemoteIdentities,\n      onIdentityChange,\n      onIdentityDelete: deleteIdentity,\n      requestProviders,\n    },\n    dispatch\n  );\n\nexport default compose(\n  connect(mapStateToProps, mapDispatchToProps),\n  withI18n(),\n  withNotification()\n)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/presenter.jsx",
    "content": "import React, { Component, Fragment } from 'react';\nimport PropTypes from 'prop-types';\nimport NewIdentity from './components/NewIdentity';\nimport RemoteIdentity from './components/RemoteIdentity';\nimport { PageTitle } from '../../components';\n\nimport './style.scss';\n\nclass RemoteIdentitySettings extends Component {\n  static propTypes = {\n    requestProviders: PropTypes.func.isRequired,\n    providers: PropTypes.arrayOf(PropTypes.string),\n    requestRemoteIdentities: PropTypes.func.isRequired,\n    onIdentityChange: PropTypes.func.isRequired,\n    onIdentityDelete: PropTypes.func.isRequired,\n    identities: PropTypes.arrayOf(PropTypes.shape({})),\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    notifyError: PropTypes.func.isRequired,\n    notifySuccess: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    identities: undefined,\n    providers: undefined,\n  };\n\n  componentDidMount() {\n    this.props.requestRemoteIdentities();\n    this.props.requestProviders();\n  }\n\n  handleChange = async (...params) => {\n    const { onIdentityChange } = this.props;\n\n    return onIdentityChange(...params);\n  };\n\n  handleDelete = async (...params) => {\n    const { onIdentityDelete } = this.props;\n\n    return onIdentityDelete(...params);\n  };\n\n  handleError = (error) => {\n    // TODO : better error handling\n    // console.log(error);\n    let errorMessage;\n    if (error.data && error.data.errors && error.data.errors.length > 0) {\n      errorMessage = error.data.errors[0].message;\n    } else {\n      errorMessage = this.props.i18n._(\n        /* i18n */ 'remote-identity-settings.unexpected-error',\n        undefined,\n        { message: 'Unexpected error occured' }\n      );\n    }\n    const { notifyError } = this.props;\n\n    notifyError({ message: errorMessage });\n  };\n\n  handleClear = () => {\n    this.props.requestRemoteIdentities();\n  };\n\n  handleCreate = async (...params) => {\n    await this.handleChange(...params);\n    this.handleClear();\n  };\n\n  renderIdentity(identity) {\n    return (\n      <div\n        className=\"s-settings-identities__identity\"\n        key={identity.identity_id}\n      >\n        <RemoteIdentity\n          remoteIdentity={identity}\n          lint\n          onRemoteIdentityChange={this.handleChange}\n          onRemoteIdentityDelete={this.handleDelete}\n          onClear={this.handleClear}\n        />\n      </div>\n    );\n  }\n\n  render() {\n    const { onIdentityDelete, identities, providers } = this.props;\n\n    return (\n      <div className=\"s-settings-identities\">\n        <PageTitle />\n        <div className=\"s-settings-identities__create\">\n          <NewIdentity\n            providers={providers}\n            onRemoteIdentityChange={this.handleCreate}\n            onRemoteIdentityDelete={onIdentityDelete}\n            onClear={this.handleClear}\n            onError={this.handleError}\n          />\n        </div>\n        {identities.map((identity) => this.renderIdentity(identity))}\n      </div>\n    );\n  }\n}\n\nexport default RemoteIdentitySettings;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/RemoteIdentitySettings/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-settings-identities {\n  @include flex-grid-row($size: expand);\n\n  padding: calc($co-margin / 2);\n\n  &__create,\n  &__menu {\n    @include flex-grid-size(12);\n  }\n\n  &__identity {\n    @include flex-grid-size(12);\n\n    margin-top: calc($co-margin / 2);\n    margin-bottom: calc($co-margin / 2);\n  }\n\n  @include breakpoint(large) {\n    &__identity {\n      @include flex-grid-column(6);\n\n      margin-top: calc($co-margin / 2);\n      margin-bottom: calc($co-margin / 2);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ResetPassword/components/ResetPasswordForm/index.jsx",
    "content": "import { withI18n } from '@lingui/react';\n\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ResetPassword/components/ResetPasswordForm/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport {\n  Section,\n  Link,\n  PasswordStrength,\n  Button,\n  Icon,\n  FieldErrors,\n  TextFieldGroup,\n  FormGrid,\n  FormColumn,\n  FormRow,\n} from '../../../../components';\n\nimport './style.scss';\n\nclass ResetPasswordForm extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    errors: PropTypes.shape({}),\n    onSubmit: PropTypes.func.isRequired,\n    success: PropTypes.bool,\n    valid: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    errors: {},\n    success: false,\n    valid: true,\n  };\n\n  state = {\n    formErrors: {\n      passwordError: [],\n    },\n    confirmPassword: '',\n    passwordStrength: '',\n    formValues: {\n      password: '',\n    },\n  };\n\n  componentDidMount() {\n    import(/* webpackChunkName: \"zxcvbn\" */ 'zxcvbn').then(\n      ({ default: zxcvbn }) => {\n        this.zxcvbn = zxcvbn;\n      }\n    );\n  }\n\n  calcPasswordStrengh = () => {\n    if (this.zxcvbn) {\n      this.setState((prevState) => {\n        const { password } = prevState.formValues;\n        const passwordStrength = !password.length\n          ? ''\n          : this.zxcvbn(password).score;\n\n        return {\n          ...prevState,\n          passwordStrength,\n        };\n      });\n    }\n  };\n\n  handlePasswordChange = (event) => {\n    const { value } = event.target;\n    this.setState((prevState) => ({\n      ...prevState,\n      formValues: {\n        password: value,\n      },\n    }));\n    this.calcPasswordStrengh();\n  };\n\n  handleConfirmPasswordChange = (event) => {\n    const { i18n } = this.props;\n    const { value } = event.target;\n\n    this.setState((prevState) => {\n      const { password } = prevState.formValues;\n      const error = i18n._(\n        /* i18n */ 'password.form.new_password_confirmation.error',\n        null,\n        { message: \"Passwords don't match\" }\n      );\n      const passwordError = password === value ? [] : [error];\n\n      return {\n        confirmPassword: value,\n        formErrors: {\n          passwordError,\n        },\n      };\n    });\n  };\n\n  handleSubmit = (ev) => {\n    ev.preventDefault();\n    const { formValues } = this.state;\n    this.props.onSubmit({ formValues });\n  };\n\n  renderSuccess = () => (\n    <div className=\"m-reset-password-form__success\">\n      <Icon type=\"check\" rightSpaced />\n      <Trans id=\"password.reset-form.success\" message=\"Done!\" />\n    </div>\n  );\n\n  renderInvalid = () => (\n    <div className=\"m-reset-password-form__error\">\n      <Trans\n        id=\"reset-password.form.errors.token_not_found\"\n        message=\"Token is no more valid. Please retry.\"\n      />\n    </div>\n  );\n\n  renderForm() {\n    const { i18n, errors } = this.props;\n\n    const submitButtonProps = {\n      // enable submitButton only if password and confirmPassword are matching\n      disabled:\n        this.state.formValues.password !== '' &&\n        this.state.formValues.password === this.state.confirmPassword\n          ? null\n          : true,\n    };\n\n    return (\n      <FormGrid className=\"m-reset-password-form\">\n        <form\n          method=\"post\"\n          name=\"reset-password-form\"\n          onSubmit={this.handleSubmit}\n        >\n          {errors.global && (\n            <FormRow>\n              <FormColumn rightSpace={false} bottomSpace>\n                <FieldErrors errors={errors.global} />\n              </FormColumn>\n            </FormRow>\n          )}\n          <FormRow>\n            <FormColumn rightSpace={false} bottomSpace>\n              <TextFieldGroup\n                inputProps={{\n                  name: 'password',\n                  type: 'password',\n                  value: this.state.formValues.password,\n                  onChange: this.handlePasswordChange,\n                  placeholder: i18n._(\n                    /* i18n */ 'password.form.new_password.placeholder',\n                    null,\n                    { message: 'Enter new password' }\n                  ),\n                  required: true,\n                  theme: 'contrasted',\n                }}\n                label={i18n._(\n                  /* i18n */ 'password.form.new_password.label',\n                  null,\n                  {\n                    message: 'New password:',\n                  }\n                )}\n              />\n            </FormColumn>\n            {this.state.passwordStrength.length !== 0 && (\n              <FormColumn rightSpace={false} bottomSpace>\n                <PasswordStrength strength={this.state.passwordStrength} />\n              </FormColumn>\n            )}\n            <FormColumn rightSpace={false} bottomSpace>\n              <TextFieldGroup\n                inputProps={{\n                  name: 'confirmPassword',\n                  type: 'password',\n                  value: this.state.confirmPassword,\n                  onChange: this.handleConfirmPasswordChange,\n                  placeholder: i18n._(\n                    /* i18n */ 'password.form.new_password_confirmation.placeholder',\n                    null,\n                    { message: 'Password' }\n                  ),\n                  required: true,\n                  theme: 'contrasted',\n                }}\n                errors={this.state.formErrors.passwordError}\n                label={i18n._(\n                  /* i18n */ 'password.form.new_password_confirmation.label',\n                  null,\n                  { message: 'New password confirmation:' }\n                )}\n              />\n            </FormColumn>\n            <FormColumn\n              className=\"m-reset-password-form__action\"\n              rightSpace={false}\n            >\n              <Button\n                shape=\"plain\"\n                display=\"expanded\"\n                type=\"submit\"\n                {...submitButtonProps}\n              >\n                <Trans\n                  id=\"password.form.action.validate\"\n                  message=\"Apply modifications\"\n                />\n              </Button>\n            </FormColumn>\n          </FormRow>\n        </form>\n      </FormGrid>\n    );\n  }\n\n  renderSection() {\n    const { valid, success } = this.props;\n    switch (true) {\n      case !valid:\n        return this.renderInvalid();\n      case success:\n        return this.renderSuccess();\n      default:\n        return this.renderForm();\n    }\n  }\n\n  render() {\n    const { i18n } = this.props;\n\n    return (\n      <Section\n        title={i18n._(/* i18n */ 'password.reset-form.title', null, {\n          message: 'Reset your password',\n        })}\n        className=\"m-reset-password-form\"\n      >\n        {this.renderSection()}\n        <div>\n          <Link to=\"/auth/signin\">\n            <Trans id=\"password.action.go_signin\" message=\"Signin\" />\n          </Link>\n        </div>\n      </Section>\n    );\n  }\n}\n\nexport default ResetPasswordForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ResetPassword/components/ResetPasswordForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-clickable';\n@import '../../../../styles/object/o-callout';\n\n.m-reset-password-form {\n  &__action {\n    line-height: $co-component__height;\n    text-align: center;\n  }\n\n  &__error,\n  &__success {\n    @include o-callout-base;\n    @include o-callout--color($co-color__success);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ResetPassword/index.js",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ResetPassword/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport ResetPasswordForm from './components/ResetPasswordForm';\nimport getClient from '../../services/api-client';\n\nconst STATUS_TOKEN_NOT_FOUND = 404;\nconst STATUS_TOKEN_UNPROCESSABLE = 424;\n\nclass ResetPassword extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    match: PropTypes.shape({\n      params: PropTypes.shape({ key: PropTypes.string }),\n    }).isRequired,\n  };\n\n  state = {\n    errors: {},\n    isSuccess: false,\n    isValid: true,\n  };\n\n  componentDidMount() {\n    const {\n      match: {\n        params: { key },\n      },\n    } = this.props;\n\n    getClient()\n      .get(`/api/v2/passwords/reset/${key}`)\n      .catch(({ response: { status } }) => {\n        if (status !== STATUS_TOKEN_NOT_FOUND) {\n          throw new Error('Unexpected error');\n        }\n\n        this.setState({\n          isValid: false,\n        });\n      });\n  }\n\n  handleSubmit = (ev) => {\n    this.setState({ errors: {} });\n    const {\n      match: {\n        params: { key },\n      },\n    } = this.props;\n\n    getClient()\n      .post(`/api/v2/passwords/reset/${key}`, {\n        ...ev.formValues,\n      })\n      .then(this.handleSuccess, this.handleError);\n  };\n\n  handleError = ({ response: { status } }) => {\n    if (status !== STATUS_TOKEN_UNPROCESSABLE) {\n      throw new Error('Unexpected error');\n    }\n\n    const { i18n } = this.props;\n\n    const errors = {\n      global: [\n        i18n._(/* i18n */ 'reset-password.form.errors.token_not_found', null, {\n          message: 'Token is no more valid. Please retry.',\n        }),\n      ],\n    };\n    this.setState({\n      isSuccess: false,\n      errors,\n    });\n  };\n\n  handleSuccess = () => {\n    this.setState({ isSuccess: true });\n  };\n\n  render() {\n    return (\n      <ResetPasswordForm\n        onSubmit={this.handleSubmit}\n        errors={this.state.errors}\n        success={this.state.isSuccess}\n        valid={this.state.isValid}\n      />\n    );\n  }\n}\n\nexport default ResetPassword;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ResetPassword/presenter.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport 'test/unit/lingui-react';\nimport Presenter from './presenter';\n\ndescribe('scene - ResetPassword', () => {\n  const i18n = { _: (id) => id };\n\n  it('render', () => {\n    const comp = shallow(\n      <Presenter i18n={i18n} match={{ params: { key: 'foobar' } }} />\n    ).dive();\n\n    expect(comp.text()).toContain('ResetPasswordForm');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/components/ContactResultItem/index.tsx",
    "content": "import * as React from 'react';\nimport { useLingui } from '@lingui/react';\nimport { formatName } from 'src/modules/contact';\nimport { Contact } from 'src/modules/contact/types';\nimport { useSettings } from 'src/modules/settings';\nimport {\n  getTagLabel,\n  getCleanedTagCollection,\n  useTags,\n} from '../../../../modules/tags';\nimport { ContactAvatarLetter, SIZE_SMALL } from '../../../../modules/avatar';\nimport { Badge, Link, TextBlock } from '../../../../components';\nimport Highlights from '../Highlights';\nimport './style.scss';\n\nconst CONTACT_NAME_PROPERTIES = [\n  'title',\n  'name_prefix, name_suffix',\n  'family_name',\n  'given_name',\n];\n\nconst EMPTY_ARRAY = [];\n\ninterface Props {\n  term: string;\n  highlights?: Record<string, any>;\n  contact: Contact;\n}\n\nfunction ContactResultItem({ contact, term, highlights }: Props) {\n  const { i18n } = useLingui();\n  const { contact_display_format: contactDisplayFormat } = useSettings();\n  const { tags } = useTags();\n  const contactTags = contact.tags\n    ? getCleanedTagCollection(tags, contact.tags)\n    : EMPTY_ARRAY;\n\n  const highlight = !highlights\n    ? ''\n    : Object.keys(highlights)\n        .filter(\n          (contactProperty) =>\n            CONTACT_NAME_PROPERTIES.indexOf(contactProperty) === -1\n        )\n        .map((contactProperty) => highlights[contactProperty])\n        .join(' ... ');\n\n  return (\n    <Link\n      noDecoration\n      className=\"m-contact-result-item\"\n      to={`/contacts/${contact.contact_id}`}\n    >\n      <div className=\"m-contact-result-item__contact-avatar\">\n        <ContactAvatarLetter contact={contact} size={SIZE_SMALL} />\n      </div>\n      <TextBlock className=\"m-contact-result-item__col-title\">\n        {contact.name_prefix && (\n          <span className=\"m-contact-result-item__contact-prefix\">\n            <Highlights term={term} highlights={contact.name_prefix} />\n          </span>\n        )}\n        <span className=\"m-contact-result-item__contact-title\">\n          <Highlights\n            term={term}\n            highlights={formatName({ contact, format: contactDisplayFormat })}\n          />\n        </span>\n        {contact.name_suffix && (\n          <span className=\"m-contact-result-item__contact-suffix\">\n            , <Highlights term={term} highlights={contact.name_suffix} />\n          </span>\n        )}\n      </TextBlock>\n      <TextBlock className=\"m-contact-result-item__col-highlights\">\n        <Highlights term={term} highlights={highlight} />\n      </TextBlock>\n      <TextBlock className=\"m-contact-result-item__col-tags\">\n        {contactTags.map((tag) => (\n          <span key={tag.name}>\n            {' '}\n            <Badge className=\"m-contact-result-item__tag\">\n              {getTagLabel(i18n, tag)}\n            </Badge>\n          </span>\n        ))}\n      </TextBlock>\n    </Link>\n  );\n}\n\nexport default ContactResultItem;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/components/ContactResultItem/style.scss",
    "content": "@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-contact-result-item {\n  @include flex-grid-row($size: expanded);\n\n  align-items: center;\n  min-height: $co-component__height;\n  padding-top: $co-component__spacing;\n  padding-bottom: $co-component__spacing;\n  background-color: $co-color__fg__back;\n  font-size: $co-font__size--small;\n\n  &:hover {\n    background-color: $co-color__fg__back;\n    color: $co-color__fg__text--lower;\n  }\n\n  &:active {\n    background-color: $co-color__fg__back--low;\n    color: $co-color__fg__text--low;\n  }\n\n  &__contact-avatar {\n    @include flex-grid-column(shrink);\n  }\n\n  &__col-title {\n    @include flex-grid-size(shrink);\n\n    color: $co-color__primary--lower;\n    font-size: $co-font__size;\n    font-weight: 600;\n  }\n\n  &__col-highlights {\n    @include flex-grid-column(shrink);\n  }\n\n  &__col-tags {\n    @include flex-grid-column(shrink);\n  }\n\n  &__contact-title {\n    margin-right: $co-component__spacing;\n  }\n\n  &__contact-prefix,\n  &__contact-suffix {\n    margin-right: $co-component__spacing;\n    color: $co-color__fg__text--high;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/components/Highlights/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport './style.scss';\n\nconst DEFAULT_HIGHLIGHT_TAG = 'em';\n\nfunction removeTagInString(string) {\n  return string\n    .replace(`<${DEFAULT_HIGHLIGHT_TAG}>`, '')\n    .replace(`</${DEFAULT_HIGHLIGHT_TAG}>`, '');\n}\n\nclass Highlights extends Component {\n  static propTypes = {\n    term: PropTypes.string,\n    highlights: PropTypes.string,\n  };\n\n  static defaultProps = {\n    term: '',\n    highlights: '',\n  };\n\n  state = {};\n\n  getTerms = () => {\n    const { term } = this.props;\n\n    if (!term.length) {\n      return [];\n    }\n\n    return Array.from(new Set(term.trim().split(' ')));\n  };\n\n  parseHighlight = (terms, highlight) => {\n    if (!terms.length) {\n      return [highlight];\n    }\n\n    const escapedTerms = terms.map((term) =>\n      term.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, '\\\\$&')\n    );\n    const termRegExp = RegExp(`(${escapedTerms.join('|')})`, 'gi');\n    const parts = [];\n    let results;\n    let prevIndex = 0;\n    let endIndex = 0;\n\n    // eslint-disable-next-line no-cond-assign\n    while ((results = termRegExp.exec(highlight)) !== null) {\n      const prefix = highlight.substr(prevIndex, results.index - prevIndex);\n      prevIndex = termRegExp.lastIndex;\n      endIndex = results.index + results[0].length;\n      parts.push(removeTagInString(prefix));\n      parts.push(\n        <b className=\"s-search-results-highlights__term\">{results[0]}</b>\n      );\n    }\n\n    if (endIndex < highlight.length) {\n      parts.push(removeTagInString(highlight.substr(endIndex)));\n    }\n\n    return parts;\n  };\n\n  render() {\n    const { highlights } = this.props;\n    const terms = this.getTerms();\n\n    if (!highlights) {\n      return null;\n    }\n\n    return (\n      <span>\n        {this.parseHighlight(terms, highlights).map((highlighted, idx) => (\n          <span key={idx}>{highlighted}</span>\n        ))}\n      </span>\n    );\n  }\n}\n\nexport default Highlights;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/components/Highlights/index.spec.jsx",
    "content": "import React from 'react';\nimport { shallow } from 'enzyme';\nimport Highlights from '.';\n\ndescribe('parseHighlight', () => {\n  it('render an highlighted string', () => {\n    const highlights = 'azertyuiop, bar qsdfgh jklm.Foo wxcvbn';\n    const terms = 'foo bar';\n    const comp = shallow(<Highlights highlights={highlights} term={terms} />);\n\n    expect(comp.text()).toEqual(highlights);\n    expect(comp.html()).toEqual(\n      '<span><span>azertyuiop, </span><span><b class=\"s-search-results-highlights__term\">bar</b></span><span> qsdfgh jklm.</span><span><b class=\"s-search-results-highlights__term\">Foo</b></span><span> wxcvbn</span></span>'\n    );\n  });\n\n  it('render an highlighted string w/ terms at begining and end', () => {\n    const highlights = 'fooazertyuiop, bar qsdfgh jklm.Foo wxcvbn bar';\n    const terms = 'foo bar';\n    const comp = shallow(<Highlights highlights={highlights} term={terms} />);\n\n    expect(comp.text()).toEqual(highlights);\n    expect(comp.html()).toEqual(\n      '<span><span></span><span><b class=\"s-search-results-highlights__term\">foo</b></span><span>azertyuiop, </span><span><b class=\"s-search-results-highlights__term\">bar</b></span><span> qsdfgh jklm.</span><span><b class=\"s-search-results-highlights__term\">Foo</b></span><span> wxcvbn </span><span><b class=\"s-search-results-highlights__term\">bar</b></span></span>'\n    );\n  });\n\n  it('render with no terms', () => {\n    const highlights = 'fooazertyuiop, bar qsdfgh jklm.Foo wxcvbn bar';\n    const comp = shallow(<Highlights highlights={highlights} />);\n\n    expect(comp.text()).toEqual(highlights);\n    expect(comp.html()).toEqual(\n      '<span><span>fooazertyuiop, bar qsdfgh jklm.Foo wxcvbn bar</span></span>'\n    );\n  });\n\n  it('render with nothing', () => {\n    const comp = shallow(<Highlights />);\n\n    expect(comp.text()).toEqual('');\n    expect(comp.html()).toEqual(null);\n  });\n\n  it('render with a trailing space', () => {\n    const highlights = 'azertyuiop, bar qsdfgh jklm.Foo wxcvbn';\n    const terms = 'foo bar ';\n    const comp = shallow(<Highlights highlights={highlights} term={terms} />);\n\n    expect(comp.text()).toEqual(highlights);\n    expect(comp.html()).toEqual(\n      '<span><span>azertyuiop, </span><span><b class=\"s-search-results-highlights__term\">bar</b></span><span> qsdfgh jklm.</span><span><b class=\"s-search-results-highlights__term\">Foo</b></span><span> wxcvbn</span></span>'\n    );\n  });\n\n  it('render with regex special chars', () => {\n    const highlights = 'azertyuiop, bar( qsdfgh jklm.Foo wxcvbn';\n    const terms = ') .foo bar( $\\\\}]^#|\\\\s\\\\*?-,';\n    const comp = shallow(<Highlights highlights={highlights} term={terms} />);\n\n    expect(comp.text()).toEqual(highlights);\n    expect(comp.html()).toEqual(\n      '<span><span>azertyuiop, </span><span><b class=\"s-search-results-highlights__term\">bar(</b></span><span> qsdfgh jklm</span><span><b class=\"s-search-results-highlights__term\">.Foo</b></span><span> wxcvbn</span></span>'\n    );\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/components/Highlights/style.scss",
    "content": "@import '../../../../styles/common';\n\n.s-search-results-highlights {\n  &__term {\n    display: inline-block;\n    background: $co-color__bg__back--high;\n    color: $co-color__fg__text--low;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/components/MessageResultItem/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport Moment from 'react-moment';\nimport { Trans, useLingui } from '@lingui/react';\nimport { Message } from 'src/modules/message';\nimport { useSettings } from 'src/modules/settings';\nimport {\n  getTagLabel,\n  getCleanedTagCollection,\n  useTags,\n} from '../../../../modules/tags';\nimport MessageDate from '../../../../components/MessageDate';\nimport { AuthorAvatarLetter, SIZE_SMALL } from '../../../../modules/avatar';\nimport { Badge, Link, Icon, TextBlock } from '../../../../components';\nimport { renderParticipant } from '../../../../services/message';\nimport Highlights from '../Highlights';\n\nimport './style.scss';\n\nconst EMPTY_ARRAY = [];\ninterface Props {\n  message: Message;\n  term: string;\n}\n\nfunction MessageResultItem({ message, term }: Props) {\n  const { i18n } = useLingui();\n  const { default_locale: locale } = useSettings();\n  const { participants } = message;\n\n  const author = participants.find(\n    (participant) => participant.type === 'From'\n  );\n\n  const { tags } = useTags();\n  const messageTags = message.tags\n    ? getCleanedTagCollection(tags, message.tags)\n    : EMPTY_ARRAY;\n\n  const resultItemClassNames = classnames('s-message-result-item', {\n    's-message-result-item--unread': message.is_unread,\n    's-message-result-item--draft': message.is_draft,\n  });\n\n  const topicClassNames = classnames('s-message-result-item__topic', {\n    's-message-result-item__topic--unread': message.is_unread,\n    's-message-result-item__topic--draft': message.is_draft,\n  });\n\n  return (\n    <Link\n      to={`/discussions/${message.discussion_id}`}\n      className={resultItemClassNames}\n      noDecoration\n    >\n      <div className=\"s-message-result-item__col-avatar\">\n        <AuthorAvatarLetter message={message} size={SIZE_SMALL} />\n      </div>\n\n      <TextBlock className=\"s-message-result-item__col-title\">\n        <span className=\"s-message-result-item__author\">\n          <Highlights term={term} highlights={renderParticipant(author)} />\n        </span>\n        <span className={topicClassNames}>\n          {message.attachments && message.attachments.length > 0 && (\n            <Icon type=\"paperclip\" spaced />\n          )}\n          {message.is_draft && (\n            <span className=\"s-message-result-item__draft-prefix\">\n              <Trans id=\"timeline.draft-prefix\" message=\"Draft in progress:\" />{' '}\n            </span>\n          )}\n          {message.subject && (\n            <span className=\"s-message-result-item__info-subject\">\n              <Highlights term={term} highlights={message.subject} />\n            </span>\n          )}\n        </span>\n        <span className=\"s-message-result-item__tags\">\n          {' '}\n          {messageTags.map((tag) => (\n            <span key={tag.name}>\n              {' '}\n              <Badge className=\"s-message-result-item__tag\">\n                {getTagLabel(i18n, tag)}\n              </Badge>\n            </span>\n          ))}\n        </span>\n      </TextBlock>\n\n      <div className=\"s-message-result-item__col-date\">\n        <Moment locale={locale} element={MessageDate}>\n          {message.date_insert}\n        </Moment>\n      </div>\n\n      <TextBlock className=\"s-message-result-item__highlights\">\n        <span title={message.excerpt}>\n          <Highlights term={term} highlights={message.excerpt} />\n        </span>\n      </TextBlock>\n    </Link>\n  );\n}\n\nexport default MessageResultItem;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/components/MessageResultItem/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.s-message-result-item {\n  @include flex-grid-row($size: expand);\n\n  align-items: center;\n  min-height: $co-component__height;\n  padding-top: $co-component__spacing;\n  padding-bottom: $co-component__spacing;\n  background-color: $co-color__fg__back--low;\n  color: $co-color__fg__text--high;\n  font-size: $co-font__size--small;\n\n  &:hover {\n    color: $co-color__fg__text--lower;\n  }\n\n  &--draft,\n  &--unread {\n    background-color: $co-color__fg__back;\n  }\n\n  &__col-avatar {\n    @include flex-grid-column(shrink);\n  }\n\n  &__col-title {\n    @include flex-grid-size;\n\n    min-width: 0; // https://github.com/zurb/foundation-sites/pull/8511\n  }\n\n  &__col-date {\n    @include flex-grid-column(shrink);\n\n    display: none;\n    margin-left: auto;\n  }\n\n  &__subject {\n    color: $co-color__fg__text--lower;\n  }\n\n  &__author {\n    color: $co-color__primary--lower;\n    font-size: $co-font__size;\n    font-weight: 600;\n  }\n\n  &__topic {\n    padding-left: $co-component__spacing;\n\n    &--unread {\n      color: $co-color__fg__text--lower;\n      font-weight: 600;\n    }\n\n    &--draft {\n      font-style: italic;\n    }\n  }\n\n  &__tags {\n    padding-left: $co-component__spacing;\n  }\n\n  &__highlights {\n    @include flex-grid-column(12);\n\n    padding-top: $co-component__spacing;\n  }\n\n  @include breakpoint(medium) {\n    &__col-date {\n      display: block;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport { search, loadMore, getKey, hasMore } from '../../store/modules/search';\nimport { withSearchParams } from '../../modules/routing';\nimport Presenter from './presenter';\n\nconst searchSelector = (state) => state.search;\nconst searchParamsSelector = (state, { searchParams: { term, doctype } }) => ({\n  term,\n  doctype,\n});\n\nconst mapStateToProps = createSelector(\n  [searchParamsSelector, searchSelector],\n  (searchParams, searchState) => {\n    const { resultsByKey } = searchState;\n    const { term, doctype } = searchParams;\n    const hasMoreByDoctype = doctype && {\n      [doctype]: hasMore(term, doctype, searchState),\n    };\n\n    return {\n      term,\n      doctype,\n      hasMoreByDoctype,\n      searchResults: resultsByKey[getKey(term, doctype)],\n      searchResultsPreview: resultsByKey[getKey(term, '')],\n    };\n  }\n);\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      search,\n      loadMore,\n    },\n    dispatch\n  );\n\nexport default compose(\n  withSearchParams(),\n  connect(mapStateToProps, mapDispatchToProps)\n)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport debounce from 'lodash/debounce';\nimport { Trans } from '@lingui/react';\nimport { WithSettings } from '../../modules/settings';\nimport SearchResultsLayout from '../../layouts/SearchResults';\nimport {\n  Link,\n  InfiniteScroll,\n  PageTitle,\n  Button,\n  BlockList,\n} from '../../components';\nimport MessageResultItem from './components/MessageResultItem';\nimport ContactResultItem from './components/ContactResultItem';\n\nimport './style.scss';\n\nconst LOAD_MORE_INTERVAL = 1000;\n\nclass SearchResults extends Component {\n  static propTypes = {\n    search: PropTypes.func.isRequired,\n    loadMore: PropTypes.func.isRequired,\n    term: PropTypes.string,\n    doctype: PropTypes.string,\n    hasMoreByDoctype: PropTypes.shape({}),\n    searchResults: PropTypes.shape({}),\n    searchResultsPreview: PropTypes.shape({}),\n  };\n\n  static defaultProps = {\n    term: undefined,\n    doctype: undefined,\n    hasMoreByDoctype: undefined,\n    searchResults: undefined,\n    searchResultsPreview: undefined,\n  };\n\n  state = {};\n\n  componentDidMount() {\n    const { term, doctype, search } = this.props;\n\n    if (term) {\n      search({ term });\n      if (doctype) {\n        this.initLoadMore({ doctype, term });\n        search({ term, doctype });\n      }\n    }\n  }\n\n  UNSAFE_componentWillReceiveProps(nextProps) {\n    const { term, doctype, search } = this.props;\n    if (\n      nextProps.term &&\n      (nextProps.term !== term || nextProps.doctype !== doctype)\n    ) {\n      this.initLoadMore({ doctype: nextProps.doctype, term: nextProps.term });\n      search({ term: nextProps.term, doctype: nextProps.doctype });\n    }\n  }\n\n  initLoadMore = ({ doctype, term }) => {\n    this.debouncedLoadMore = debounce(\n      () =>\n        this.props.hasMoreByDoctype &&\n        this.props.hasMoreByDoctype[doctype] &&\n        this.props.loadMore({ term, doctype }),\n      LOAD_MORE_INTERVAL,\n      {\n        leading: true,\n        trailing: false,\n      }\n    );\n  };\n\n  renderMessages(messages) {\n    const { term } = this.props;\n\n    return (\n      <BlockList className=\"s-search-results__item\">\n        {messages.map((messageHit) => (\n          <MessageResultItem\n            key={messageHit.document.message_id}\n            message={messageHit.document}\n            highlights={messageHit.highlights}\n            term={term}\n          />\n        ))}\n      </BlockList>\n    );\n  }\n\n  renderContacts(contacts) {\n    const { term } = this.props;\n\n    return (\n      <BlockList className=\"s-search-results__item\">\n        {contacts.map((contactHit) => (\n          <ContactResultItem\n            key={contactHit.document.contact_id}\n            contact={contactHit.document}\n            highlights={contactHit.highlights}\n            term={term}\n          />\n        ))}\n      </BlockList>\n    );\n  }\n\n  renderPreview() {\n    if (!this.props.searchResultsPreview) {\n      return null;\n    }\n\n    const {\n      term,\n      searchResultsPreview: {\n        messages_hits: { total: nbMessages, messages },\n        contact_hits: { total: nbContacts, contacts },\n      },\n    } = this.props;\n\n    return (\n      <div className=\"s-search-results__preview\">\n        {messages && (\n          <div className=\"s-search-results__label\">\n            <Trans\n              id=\"search-results.preview.nb-messages\"\n              values={{ nbMessages, term }}\n            >\n              {nbMessages} messages contains &quot;{term}&quot; in their subject\n              or content:\n            </Trans>{' '}\n            <Link\n              className=\"s-search-results__link\"\n              to={`/search-results?term=${term}&doctype=message`}\n            >\n              <Trans\n                id=\"search-results.actions.display-all\"\n                message=\"Display all\"\n              />\n            </Link>\n          </div>\n        )}\n        <div className=\"s-search-results__list\">\n          {messages && this.renderMessages(messages)}\n        </div>\n        {contacts && (\n          <div className=\"s-search-results__label\">\n            <Trans\n              id=\"search-results.preview.nb-contacts\"\n              values={{ nbContacts, term }}\n            >\n              {nbContacts} contacts contains &quot;{term}&quot; in their label\n              or profile:\n            </Trans>{' '}\n            <Link\n              className=\"s-search-results__link\"\n              to={`/search-results?term=${term}&doctype=contact`}\n            >\n              <Trans\n                id=\"search-results.actions.display-all\"\n                message=\"Display all\"\n              />\n            </Link>\n          </div>\n        )}\n        <div className=\"s-search-results__list\">\n          {contacts && this.renderContacts(contacts)}\n        </div>\n      </div>\n    );\n  }\n\n  renderResults() {\n    if (!this.props.searchResults) {\n      return null;\n    }\n\n    const {\n      doctype,\n      hasMoreByDoctype,\n      searchResults: {\n        messages_hits: { messages },\n        contact_hits: { contacts },\n      },\n    } = this.props;\n\n    switch (doctype) {\n      case 'message':\n        return (\n          <div>\n            <InfiniteScroll onReachBottom={this.debouncedLoadMore}>\n              {this.renderMessages(messages)}\n            </InfiniteScroll>\n            {hasMoreByDoctype && hasMoreByDoctype.message && (\n              <div className=\"s-search-results__load-more\">\n                <Button shape=\"hollow\" onClick={this.debouncedLoadMore}>\n                  <Trans id=\"general.action.load_more\" message=\"Load more\" />\n                </Button>\n              </div>\n            )}\n          </div>\n        );\n      case 'contact':\n        return (\n          <div>\n            <InfiniteScroll onReachBottom={this.debouncedLoadMore}>\n              {this.renderContacts(contacts)}\n            </InfiniteScroll>\n            {hasMoreByDoctype && hasMoreByDoctype.contact && (\n              <div className=\"s-search-results__load-more\">\n                <Button shape=\"hollow\" onClick={this.debouncedLoadMore}>\n                  <Trans id=\"general.action.load_more\" message=\"Load more\" />\n                </Button>\n              </div>\n            )}\n          </div>\n        );\n      default:\n        return null;\n    }\n  }\n\n  render() {\n    const { term, doctype, searchResultsPreview } = this.props;\n\n    return (\n      <div className=\"s-search-results\">\n        <PageTitle />\n        <SearchResultsLayout\n          term={term}\n          searchResultsPreview={searchResultsPreview}\n        >\n          {doctype ? this.renderResults() : this.renderPreview()}\n        </SearchResultsLayout>\n      </div>\n    );\n  }\n}\n\nexport default SearchResults;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SearchResults/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-search-results {\n  background: $co-color__fg__back;\n\n  &__preview {\n    @include flex-grid-row;\n  }\n\n  &__label {\n    @include flex-grid-column(12);\n\n    padding-top: $co-component__spacing;\n    padding-bottom: $co-component__spacing;\n    background: $co-color__bg__back--high;\n    color: $co-color__fg__text--low;\n    font-weight: 600;\n  }\n\n  &__list {\n    @include flex-grid-size(12);\n  }\n\n  &__load-more {\n    padding: $co-component__spacing;\n    text-align: center;\n  }\n\n  &__link {\n    color: $co-color__primary--lower;\n\n    &:hover {\n      color: $co-color__primary--low;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SettingsSignatures/components/SignatureForm/index.jsx",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SettingsSignatures/components/SignatureForm/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport {\n  Button,\n  FieldErrors,\n  TextareaFieldGroup,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\n\nfunction generateStateFromProps(props, prevState) {\n  return {\n    settings: {\n      ...prevState.settings,\n      ...props.settings,\n    },\n  };\n}\n\nclass SignatureForm extends Component {\n  static propTypes = {\n    errors: PropTypes.shape({}),\n    onSubmit: PropTypes.func.isRequired,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n  };\n\n  static defaultProps = {\n    errors: {},\n  };\n\n  state = {\n    settings: {\n      signature: '',\n    },\n  };\n\n  UNSAFE_componentWillMount() {\n    this.setState((prevState) => generateStateFromProps(this.props, prevState));\n  }\n\n  UNSAFE_componentWillReceiveProps(newProps) {\n    this.setState((prevState) => generateStateFromProps(newProps, prevState));\n  }\n\n  getOptionsFromArray = (options, setting) => {\n    const selectedOptions = options.map((value) => ({\n      value,\n      label: value,\n      selected: setting === value && setting !== null && true,\n    }));\n\n    return selectedOptions;\n  };\n\n  handleTextareaChange = (/* ev */) => {\n    // const { name, value } = ev.target;\n    // this.setState((prevState) => {});\n  };\n\n  handleSubmit = () => {\n    const { settings } = this.state;\n    this.props.onSubmit({ settings });\n  };\n\n  render() {\n    const { errors, i18n } = this.props;\n\n    return (\n      <FormGrid className=\"m-signature-form\">\n        <form method=\"post\" name=\"signature_form\">\n          {errors.global && errors.global.length !== 0 && (\n            <FormRow>\n              <FormColumn bottomSpace>\n                <FieldErrors errors={errors.global} />\n              </FormColumn>\n            </FormRow>\n          )}\n          <FormRow>\n            <FormColumn size=\"shrink\" bottomSpace>\n              <TextareaFieldGroup\n                inputProps={{\n                  name: 'signature',\n                  defaultValue: this.state.settings.signature,\n                  onChange: this.handleTextareaChange,\n                }}\n                label={i18n._(/* i18n */ 'settings.signature.label', {\n                  message: 'Signature',\n                })}\n              />\n            </FormColumn>\n          </FormRow>\n          <FormRow>\n            <FormColumn\n              size=\"shrink\"\n              className=\"m-signature-form__action\"\n              bottomSpace\n            >\n              <Button type=\"submit\" onClick={this.handleSubmit} shape=\"plain\">\n                <Trans id=\"settings.signature.update.action\" message=\"Save\" />\n              </Button>\n            </FormColumn>\n          </FormRow>\n        </form>\n      </FormGrid>\n    );\n  }\n}\n\nexport default SignatureForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SettingsSignatures/index.jsx",
    "content": "import { withI18n } from '@lingui/react';\nimport Presenter from './presenter';\n\nexport default withI18n()(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SettingsSignatures/presenter.jsx",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Section, Link, PageTitle, NavList, NavItem } from '../../components';\nimport SignatureForm from './components/SignatureForm';\n\nimport './style.scss';\n\nconst fakeSignaturesSettings = {\n  signature: 'bla',\n};\n\nconst navLinks = [\n  { title: 'myself@caliopen.local', to: '/settings/signatures' },\n  { title: 'myothermyself@caliopen.local', to: '/settings/signatures' },\n];\n\nfunction SettingsSignatures({ i18n }) {\n  return (\n    <div className=\"s-settings-signatures\">\n      <PageTitle />\n      {navLinks && (\n        <NavList dir=\"vertical\" className=\"s-settings-signatures__menu\">\n          {navLinks.map((link) => (\n            // this should be identities.map(identity => ... )\n            <NavItem active={false} large key={link.title}>\n              <Link noDecoration {...link}>\n                {link.title}\n              </Link>\n            </NavItem>\n          ))}\n        </NavList>\n      )}\n      <div className=\"s-settings-signatures__panel\">\n        <Section\n          title={i18n._(/* i18n */ 'settings.signatures.title', {\n            message: 'Update your signature',\n          })}\n        >\n          <SignatureForm\n            settings={fakeSignaturesSettings}\n            onSubmit={(str) => str}\n          />\n        </Section>\n      </div>\n    </div>\n  );\n}\n\nSettingsSignatures.propTypes = {\n  i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n};\n\nexport default SettingsSignatures;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/SettingsSignatures/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-settings-signatures {\n  @include flex-grid-row($size: expand);\n\n  margin-top: $co-margin;\n\n  &__menu {\n    @include flex-grid-column(12);\n  }\n\n  &__panel {\n    @include flex-grid-size(12);\n  }\n\n  @include breakpoint(medium) {\n    &__menu {\n      @include flex-grid-size(shrink);\n    }\n\n    &__panel {\n      @include flex-grid-size;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Signin.tsx",
    "content": "import { SigninForm } from '../modules/user';\n\nexport default SigninForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Signup.tsx",
    "content": "import { SignupForm } from '../modules/user';\n\nexport default SignupForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/TagsSettings/components/TagInput/index.tsx",
    "content": "import * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport { useMutation, useQueryClient } from 'react-query';\nimport { getTagLabel } from 'src/modules/tags';\nimport { getQueryKeys, updateTag, deleteTag } from 'src/modules/tags/query';\nimport { TagPayload } from 'src/modules/tags/types';\nimport {\n  Button,\n  Icon,\n  Spinner,\n  FormGrid,\n  FieldErrors,\n  TextFieldGroup,\n} from 'src/components';\nimport './style.scss';\n\nconst TAG_TYPE_USER = 'user';\n\ntype ErrorPayload = { message: string }[];\n\ninterface Props extends withI18nProps {\n  tag: TagPayload;\n}\n\nfunction TagInput({ i18n, tag }: Props) {\n  const [tagLabel, setTagLabel] = React.useState(tag.label);\n  const [edit, setEdit] = React.useState(false);\n  const [tagErrors, setTagErrors] = React.useState<string[]>([]);\n\n  const queryClient = useQueryClient();\n  const {\n    mutateAsync: updateTagMutate,\n    isLoading: isUpdating,\n    error,\n  } = useMutation<\n    unknown,\n    ErrorPayload,\n    { value: TagPayload; original: TagPayload }\n  >(updateTag, {\n    onSuccess: () => {\n      queryClient.invalidateQueries(getQueryKeys());\n    },\n  });\n  const { mutateAsync: deleteTagMutate, isLoading: isDeleting } = useMutation<\n    unknown,\n    ErrorPayload,\n    TagPayload\n  >(deleteTag, {\n    onSuccess: () => {\n      queryClient.invalidateQueries(getQueryKeys());\n    },\n  });\n\n  React.useEffect(() => {\n    setTagErrors(error?.map((err) => err.message) || []);\n  }, [error]);\n\n  const pending = isUpdating || isDeleting;\n\n  const handleChange = (ev) => {\n    setTagLabel(ev.target.value);\n    setTagErrors([]);\n  };\n\n  const handleClickTag = () => {\n    setEdit(true);\n  };\n\n  const handleUpdateTag = async () => {\n    try {\n      await updateTagMutate({\n        original: tag,\n        value: {\n          ...tag,\n          label: tagLabel,\n        },\n      });\n    } catch (errors) {\n      setTagErrors(errors.map((err) => err.message));\n    }\n\n    setEdit(false);\n  };\n\n  const handleDeleteTag = async () => {\n    try {\n      await deleteTagMutate(tag);\n    } catch (errors) {\n      setTagErrors(errors.map((err) => err.message));\n    }\n  };\n\n  if (edit) {\n    return (\n      <FormGrid className=\"m-tag-input\">\n        <TextFieldGroup\n          id=\"tag_settings_edit_tag\"\n          className=\"m-tag-input__input\"\n          inputProps={{\n            name: tag.name,\n            placeholder: getTagLabel(i18n, tag),\n            value: tagLabel,\n            onChange: handleChange,\n            autoFocus: true,\n          }}\n          label={tag.name}\n          showLabelforSr\n          errors={tagErrors}\n        />\n        <Button\n          onClick={handleUpdateTag}\n          aria-label={i18n._(\n            /* i18n */ 'settings.tag.action.save-tag',\n            undefined,\n            {\n              message: 'Save',\n            }\n          )}\n          icon={\n            isUpdating ? (\n              <Spinner\n                svgTitleId=\"save-tag-spinner\"\n                isLoading\n                display=\"inline\"\n              />\n            ) : (\n              'check'\n            )\n          }\n          disabled={pending}\n        />\n      </FormGrid>\n    );\n  }\n\n  return (\n    <FormGrid className=\"m-tag-input\">\n      <Button className=\"m-tag-input__button\" onClick={handleClickTag}>\n        <span className=\"m-tag-input__text\">{getTagLabel(i18n, tag)}</span>\n        {/* @ts-ignore */}\n        <Icon className=\"m-tag-input__icon\" type=\"edit\" spaced />\n      </Button>\n      {tag.type === TAG_TYPE_USER && (\n        <Button\n          className=\"m-tag-input__delete\"\n          aria-label={i18n._(\n            /* i18n */ 'settings.tags.action.delete',\n            undefined,\n            {\n              message: 'Delete',\n            }\n          )}\n          disabled={pending}\n          onClick={handleDeleteTag}\n          icon={\n            pending ? (\n              <Spinner\n                svgTitleId=\"delete-tag-spinner\"\n                isLoading\n                display=\"inline\"\n              />\n            ) : (\n              'remove'\n            )\n          }\n        />\n      )}\n      {tagErrors && (\n        <FieldErrors errors={tagErrors} className=\"m-tag-input__errors\" />\n      )}\n    </FormGrid>\n  );\n}\n\nexport default withI18n()(TagInput);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/TagsSettings/components/TagInput/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-tag-input {\n  @include flex-grid-row;\n\n  margin-bottom: map_get($co-form__spacing, 'small');\n  background: $co-color__fg__back;\n\n  &__button {\n    @include flex-grid-size;\n\n    line-height: $co-component__height;\n    text-align: left;\n\n    &:hover {\n      background: $co-color__fg__back;\n    }\n  }\n\n  &__input {\n    @include flex-grid-size;\n  }\n\n  &__delete {\n    @include flex-grid-size(shrink);\n\n    line-height: $co-component__height;\n  }\n\n  &__errors {\n    @include flex-grid-column(12);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/TagsSettings/components/TagSearch/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, useLingui } from '@lingui/react';\nimport { useMutation, useQueryClient } from 'react-query';\nimport { Button, Spinner } from 'src/components';\nimport TextFieldGroup from 'src/components/TextFieldGroup';\nimport { createTag, getQueryKeys } from 'src/modules/tags/query';\nimport { NewTag } from 'src/modules/tags/types';\nimport './style.scss';\n\nconst noop = () => {};\n\ninterface Props {\n  onCreateSuccess?: () => void;\n}\nfunction TagSearch({ onCreateSuccess = noop }: Props) {\n  const { i18n } = useLingui();\n  const [terms, setTerms] = React.useState('');\n  const [tagErrors, setTagErrors] = React.useState<React.ReactNode[]>([]);\n  const queryClient = useQueryClient();\n  const { mutateAsync, isLoading } = useMutation<unknown, unknown, NewTag>(\n    createTag,\n    {\n      onSuccess: () => {\n        queryClient.invalidateQueries(getQueryKeys());\n      },\n    }\n  );\n  const handleChange = (ev) => {\n    setTerms(ev.target.value);\n    setTagErrors([]);\n  };\n\n  const handleSubmit = async () => {\n    if (terms.length === 0) {\n      return;\n    }\n\n    try {\n      await mutateAsync({ label: terms });\n      setTerms('');\n      onCreateSuccess();\n    } catch (err) {\n      setTagErrors([\n        <Trans\n          id=\"settings.tag.form.error.create_fail\"\n          message=\"Unable to create the tag. A tag with the same id may already exist.\"\n        />,\n      ]);\n    }\n  };\n\n  return (\n    <div className=\"m-add-tag\">\n      <TextFieldGroup\n        id=\"tag_settings_add_input\"\n        className=\"m-add-tag__input\"\n        inputProps={{\n          name: 'terms',\n          value: terms,\n          placeholder: i18n._(\n            /* i18n */ 'tags.form.add.placeholder',\n            undefined,\n            {\n              message: 'New tag ...',\n            }\n          ),\n          onChange: handleChange,\n        }}\n        label={i18n._(/* i18n */ 'tags.form.add.label', undefined, {\n          message: 'Add a tag',\n        })}\n        showLabelforSr\n        errors={tagErrors}\n      />\n      <Button\n        className=\"m-add-tag__button\"\n        icon={\n          isLoading ? (\n            <Spinner svgTitleId=\"add-tag-spinner\" isLoading display=\"inline\" />\n          ) : (\n            'plus'\n          )\n        }\n        disabled={isLoading}\n        shape=\"plain\"\n        onClick={handleSubmit}\n        aria-label={i18n._(/* i18n */ 'tags.action.add', undefined, {\n          message: 'Add',\n        })}\n      />\n    </div>\n  );\n}\n\nexport default TagSearch;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/TagsSettings/components/TagSearch/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-add-tag {\n  @include flex-grid-row($size: expanded);\n\n  margin-bottom: map_get($co-form__spacing, 'large');\n\n  &__input {\n    @include flex-grid-size;\n  }\n\n  &__button {\n    @include flex-grid-size(shrink);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/TagsSettings/index.spec.tsx",
    "content": "import * as React from 'react';\nimport {\n  render,\n  screen,\n  waitForElementToBeRemoved,\n} from '@testing-library/react';\n// import userEvent from '@testing-library/user-event';\nimport { rest } from 'msw';\nimport { server } from 'test/server';\nimport { AllProviders } from 'test/providers';\nimport TagsSettings from '.';\n\njest.mock('src/modules/user/services/isAuthenticated', () => ({\n  isAuthenticated: () => true,\n}));\n\ndescribe('TagsSettings', () => {\n  // afterEach(() => {\n  //   jest.clearAllMocks();\n  // });\n  it('render', async () => {\n    // const tags = [\n    //   {\n    //     name: 'INBOX',\n    //     type: 'system',\n    //   },\n    //   {\n    //     name: 'IMPORTANT',\n    //     type: 'system',\n    //   },\n    //   {\n    //     name: 'SPAM',\n    //     type: 'system',\n    //   },\n    //   {\n    //     name: 'foobar',\n    //     label: 'Foobar',\n    //     type: 'user',\n    //   },\n    // ];\n    // server.use(\n    //   rest.get('/api/v2/tags', (req, res, ctx) => {\n    //     const ret = new Promise((resolve) => {\n    //       setTimeout(() => {\n    //         resolve(\n    //           res(ctx.json({ tags, total: tags.length }), ctx.status(200))\n    //         );\n    //       }, 1000);\n    //     });\n    //     return ret;\n    //   })\n    // );\n\n    render(<TagsSettings />, { wrapper: AllProviders });\n\n    await waitForElementToBeRemoved(screen.getByLabelText('Loading …'), {\n      timeout: 2000,\n    });\n    expect(screen.getByText('Create new tag')).toBeVisible();\n  });\n\n  it.skip('creates a tag', async () => {\n    const tags = [\n      {\n        name: 'INBOX',\n        type: 'system',\n      },\n      {\n        name: 'IMPORTANT',\n        type: 'system',\n      },\n      {\n        name: 'SPAM',\n        type: 'system',\n      },\n      {\n        name: 'foobar',\n        label: 'Foobar',\n        type: 'user',\n      },\n    ];\n\n    server.use(\n      rest.get('/api/v2/tags', (req, res, ctx) =>\n        res(ctx.json({ tags, total: tags.length }), ctx.status(200))\n      ),\n      rest.post('/api/v2/tags', (req, res, ctx) => {\n        // @ts-ignore\n        const { label } = req.body;\n        tags.push({ name: label, label, type: 'user' });\n\n        return res(ctx.text('OK'));\n      })\n    );\n    render(<TagsSettings />, { wrapper: AllProviders });\n\n    await waitForElementToBeRemoved(screen.getByLabelText('Loading …'));\n\n    // XXX: ReferenceError failure\n    // userEvent.type(screen.getByLabelText('Add a tag'), 'my tag');\n    // userEvent.click(screen.getByRole('button', { name: 'Add' }));\n    await waitForElementToBeRemoved(screen.getByLabelText('Loading …'));\n\n    expect(await screen.findByText('my tag')).toBeVisible();\n  });\n  it.skip('edits a tag', async () => {\n    const tags = [\n      {\n        name: 'INBOX',\n        type: 'system',\n      },\n      {\n        name: 'IMPORTANT',\n        type: 'system',\n      },\n      {\n        name: 'SPAM',\n        type: 'system',\n      },\n      {\n        name: 'foobar',\n        label: 'Foobar',\n        type: 'user',\n      },\n    ];\n\n    server.use(\n      rest.get('/api/v2/tags', (req, res, ctx) =>\n        res(ctx.json({ tags, total: tags.length }), ctx.status(200))\n      ),\n      rest.patch('/api/v2/tags/:name', (req, res, ctx) => {\n        // @ts-ignore\n        const { label } = req.body;\n\n        const idx = tags.findIndex((tag) => tag.name === req.params.name);\n\n        if (idx === -1) {\n          return res(ctx.text('not found'), ctx.status(404));\n        }\n        tags[idx] = {\n          ...tags[idx],\n          label,\n        };\n\n        return res(ctx.text('OK'));\n      })\n    );\n    render(<TagsSettings />, { wrapper: AllProviders });\n\n    await waitForElementToBeRemoved(screen.getByLabelText('Loading …'));\n\n    // XXX: ReferenceError failure\n    // userEvent.click(screen.getByText('Foobar'));\n\n    // userEvent.type(screen.getByLabelText('foobar'), ' - Edited');\n    // userEvent.click(screen.getByRole('button', { name: 'Save' }));\n    await waitForElementToBeRemoved(screen.getByLabelText('Loading …'));\n\n    expect(screen.queryByLabelText('foobar')).not.toBeInTheDocument();\n\n    expect(await screen.findByText('Foobar - Edited')).toBeVisible();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/TagsSettings/index.tsx",
    "content": "import * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport { useTags } from 'src/modules/tags';\nimport { Section, Spinner } from 'src/components';\nimport TagSearch from './components/TagSearch';\nimport TagInput from './components/TagInput';\nimport './style.scss';\n\ntype Props = withI18nProps;\n\nfunction TagsSettings({ i18n }: Props) {\n  const { tags, status } = useTags();\n\n  return (\n    <div className=\"s-tags-settings\">\n      <div className=\"s-tags-settings__create\">\n        <Section\n          title={i18n._(/* i18n */ 'settings.tags.title.create', undefined, {\n            message: 'Create new tag',\n          })}\n        >\n          <TagSearch />\n        </Section>\n      </div>\n      <div className=\"s-tags-settings__tags\">\n        <Section\n          title={i18n._(/* i18n */ 'settings.tags.title', undefined, {\n            message: 'Tags',\n          })}\n        >\n          {['idle', 'error'].includes(status) ? (\n            <Spinner svgTitleId=\"tags-spinner\" isLoading />\n          ) : (\n            tags.map((tag) => <TagInput key={tag.name} tag={tag} />)\n          )}\n        </Section>\n      </div>\n    </div>\n  );\n}\n\nexport default withI18n()(TagsSettings);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/TagsSettings/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-tags-settings {\n  @include flex-grid-row($size: expand);\n\n  padding: $co-margin (calc($co-margin / 2));\n  background: $co-color__fg__back;\n\n  &__tags,\n  &__create {\n    @include flex-grid-column(12);\n  }\n\n  @include breakpoint(medium) {\n    &__tags,\n    &__create {\n      @include flex-grid-column(6);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Timeline/components/DiscussionItem/discussion-item-content.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n$s-discussion-item__spacing: 0.5rem;\n\n.s-discussion-item-content {\n  display: grid;\n  grid-template-areas:\n    'participants participants participants protocol'\n    'subject subject date date'\n    'excerpt excerpt excerpt excerpt';\n  grid-template-columns: auto auto auto 1fr;\n  min-height: $co-component__height;\n  color: $co-color__fg__text--higher;\n  font-weight: 400;\n  line-height: 1.5rem;\n  text-decoration: none;\n\n  @include breakpoint(medium) {\n    grid-template-areas:\n      'participants participants protocol date'\n      'subject excerpt excerpt excerpt';\n    grid-template-columns: auto auto 1fr auto;\n  }\n\n  &__participants {\n    grid-area: participants;\n    color: $co-blue;\n  }\n\n  &__excerpt {\n    grid-area: excerpt;\n    color: $co-color__fg__text--higher;\n    @include breakpoint(medium) {\n      margin-left: $s-discussion-item__spacing * 2;\n    }\n  }\n\n  &__subject {\n    grid-area: subject;\n    color: $co-color__fg__text--lower;\n\n    &--unread {\n      font-weight: 800;\n    }\n  }\n\n  &__protocol {\n    grid-area: protocol;\n    margin-left: $s-discussion-item__spacing;\n    text-align: right;\n  }\n\n  &__date {\n    grid-area: date;\n    margin-left: $s-discussion-item__spacing;\n    text-align: right;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Timeline/components/DiscussionItem/index.jsx",
    "content": "import React, { PureComponent, Fragment } from 'react';\nimport PropTypes from 'prop-types';\nimport Moment from 'react-moment';\nimport classnames from 'classnames';\n// import { withI18n } from '@lingui/react';\nimport { Icon, Link, TextBlock } from '../../../../components';\nimport ParticipantsIconLetter from '../../../../components/ParticipantsIconLetter';\nimport {\n  getAveragePI,\n  getPiClass,\n  PI_PROPERTIES,\n} from '../../../../modules/pi';\nimport { ParticipantLabel } from '../../../../modules/message';\nimport './style.scss';\nimport './discussion-item-content.scss';\n\n// @withI18n()\nclass DiscussionItem extends PureComponent {\n  static propTypes = {\n    className: PropTypes.string,\n    discussion: PropTypes.shape({\n      excerpt: PropTypes.string.isRequired,\n      discussion_id: PropTypes.string.isRequired,\n      total_count: PropTypes.number.isRequired,\n      date_update: PropTypes.string.isRequired,\n      last_message_id: PropTypes.string.isRequired,\n      unread_count: PropTypes.number.isRequired,\n      pi: PropTypes.shape({}),\n      participants: PropTypes.arrayOf(\n        PropTypes.shape({ protocol: PropTypes.string })\n      ),\n    }).isRequired,\n    // i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    onSelectDiscussion: PropTypes.func.isRequired,\n    isDiscussionSelected: PropTypes.bool.isRequired,\n    // isDeleting: PropTypes.bool.isRequired,\n    user: PropTypes.shape({\n      contact: PropTypes.shape({ contact_id: PropTypes.string.isRequired })\n        .isRequired,\n    }).isRequired,\n    settings: PropTypes.shape({ default_locale: PropTypes.string.isRequired })\n      .isRequired,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  onCheckboxChange = (ev) => {\n    const { discussion, onSelectDiscussion } = this.props;\n    const { checked } = ev.target;\n\n    onSelectDiscussion(checked ? 'add' : 'remove', discussion.discussion_id);\n  };\n\n  getParticipantsExceptUser = () => {\n    const { discussion } = this.props;\n\n    const participants = discussion.participants.filter(\n      (participant) =>\n        !(\n          participant.contact_ids &&\n          participant.contact_ids.some(\n            (contactId) => contactId === this.props.user?.contact.contact_id\n          )\n        )\n    );\n\n    return participants.length > 0\n      ? participants\n      : [discussion.participants[0]];\n  };\n\n  renderMessageSubject = (discussion) => {\n    const {\n      last_message_subject: lastMessageSubject,\n      unread_count: unreadCount,\n    } = discussion;\n\n    if (lastMessageSubject) {\n      return (\n        <TextBlock\n          className={classnames('s-discussion-item-content__subject', {\n            's-discussion-item-content__subject--unread': unreadCount > 0,\n          })}\n        >\n          {lastMessageSubject}\n        </TextBlock>\n      );\n    }\n\n    return null;\n  };\n\n  renderParticipants() {\n    const {\n      discussion: { discussion_id: discussionId },\n    } = this.props;\n    const participants = this.getParticipantsExceptUser();\n\n    return participants.map((participant, i) => (\n      <Fragment\n        key={`${participant.address}-${participant.type}-${participant.protocol}-${discussionId}`}\n      >\n        {i > 0 && ', '}\n        <ParticipantLabel participant={participant} />\n      </Fragment>\n    ));\n  }\n\n  render() {\n    const { settings, className } = this.props;\n    const {\n      excerpt,\n      discussion_id: discussionId,\n      total_count: total,\n      date_update: date,\n      last_message_id: lastMessageId,\n      unread_count: unreadCount,\n      pi,\n    } = this.props.discussion;\n\n    // XXX: discussion does not support pi_message yet\n    const piAggregate = pi ? getAveragePI(pi, PI_PROPERTIES) : undefined;\n\n    // const { isDeleting, isDiscussionSelected, i18n } = this.props;\n\n    const participants = this.getParticipantsExceptUser();\n    const iconProtocol = this.props.discussion.participants[0].protocol;\n\n    return (\n      <li\n        id={`discussion-${discussionId}`}\n        data-nb-messages={total}\n        data-date={date}\n        className={classnames(\n          className,\n          's-discussion-item',\n          { 's-discussion-item--is-unread': unreadCount > 0 },\n          `s-discussion-item--${getPiClass(piAggregate)}`\n        )}\n      >\n        <div className=\"s-discussion-item__col-avatar\">\n          <ParticipantsIconLetter\n            labels={participants.map((participant) => participant.label)}\n          />\n        </div>\n        <div className=\"s-discussion-item__col-content\">\n          <Link\n            to={`/discussions/${discussionId}#${lastMessageId}`}\n            noDecoration\n            className=\"s-discussion-item-content\"\n          >\n            <TextBlock className=\"s-discussion-item-content__participants\">\n              {this.renderParticipants()}\n            </TextBlock>\n            {this.renderMessageSubject(this.props.discussion)}\n            <TextBlock className=\"s-discussion-item-content__excerpt\">\n              {excerpt}\n            </TextBlock>\n            <span className=\"s-discussion-item-content__protocol\">\n              <Icon type={iconProtocol} />\n            </span>\n            <TextBlock className=\"s-discussion-item-content__date\">\n              <Moment\n                fromNow\n                locale={settings.default_locale}\n                titleFormat=\"LLLL\"\n                withTitle\n              >\n                {date}\n              </Moment>\n            </TextBlock>\n          </Link>\n        </div>\n        {/*\n        <div className=\"s-discussion-item__select\">\n          <Checkbox\n            label={i18n._(/* i18n  'message-list.action.select_single_discussion', null,\n              { message: 'Select/deselect this discussion' })}\n            onChange={this.onCheckboxChange}\n            id={discussionId}\n            checked={isDiscussionSelected}\n            disabled={isDeleting}\n            showLabelforSr\n          />\n        </div>\n        */}\n      </li>\n    );\n  }\n}\n\nexport default DiscussionItem;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Timeline/components/DiscussionItem/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n$s-discussion-item__spacing: 0.5rem;\n\n.s-discussion-item {\n  @include flex-grid-row($size: expand);\n\n  position: relative;\n  align-items: center;\n  min-height: $co-component__height;\n  padding-top: $co-component__spacing;\n  padding-right: $co-component__spacing;\n  padding-bottom: $co-component__spacing;\n  border-left: 0.5rem solid $co-color__fg__back--lower;\n  background-color: $co-color__fg__back--low;\n\n  @include breakpoint(medium) {\n    padding-top: $co-component__spacing * 2;\n    padding-bottom: $co-component__spacing * 2;\n  }\n\n  &--ugly {\n    border-left: 0.5rem solid map_get($privacy-palette, 'ugly');\n  }\n\n  &--bad {\n    border-left: 0.5rem solid map_get($privacy-palette, 'bad');\n  }\n\n  &--good {\n    border-left: 0.5rem solid map_get($privacy-palette, 'good');\n  }\n\n  &--super {\n    border-left: 0.5rem solid map_get($privacy-palette, 'super');\n  }\n\n  &--is-unread {\n    background-color: $co-color__fg__back;\n  }\n\n  &__col-avatar {\n    @include flex-grid-column(shrink);\n  }\n\n  &__col-content {\n    @include flex-grid-size;\n\n    min-width: 0; // https://github.com/zurb/foundation-sites/pull/8511\n  }\n\n  &__select {\n    display: inline-block;\n    flex-grow: 0;\n    flex-shrink: 0;\n    margin: auto $s-discussion-item__spacing auto 1rem;\n\n    @include breakpoint(small) {\n      display: none;\n    }\n  }\n}\n\n.m-participants-icon {\n  flex: 0 0 auto;\n  margin: auto $s-discussion-item__spacing;\n  color: $co-color__contrast__text;\n  line-height: 2rem;\n\n  .fa {\n    color: $co-color__contrast__text--lower;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Timeline/components/DiscussionSelector/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n, Trans } from '@lingui/react';\nimport { Checkbox, Button, Spinner, Confirm } from '../../../../components';\n\nimport './style.scss';\n\n@withI18n()\nclass DiscussionSelector extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    onSelectAllDiscussions: PropTypes.func,\n    onEditTags: PropTypes.func,\n    onDeleteDiscussions: PropTypes.func,\n    count: PropTypes.number,\n    totalCount: PropTypes.number,\n    indeterminate: PropTypes.bool,\n    checked: PropTypes.bool,\n    isDeleting: PropTypes.bool.isRequired,\n  };\n\n  static defaultProps = {\n    onSelectAllDiscussions: (str) => str,\n    onEditTags: (str) => str,\n    onDeleteDiscussions: (str) => str,\n    count: 0,\n    totalCount: 0,\n    indeterminate: false,\n    checked: false,\n  };\n\n  toggleCheckbox = (ev) => {\n    const { checked } = ev.target;\n    this.props.onSelectAllDiscussions(checked ? 'select' : 'unselect');\n  };\n\n  handleEditTags = () => {\n    this.props.onEditTags();\n  };\n\n  handleDelete = () => {\n    this.props.onDeleteDiscussions();\n  };\n\n  renderDeleteButton() {\n    const { i18n, count, isDeleting } = this.props;\n\n    if (count === 0) {\n      return (\n        <Button\n          icon={\n            isDeleting ? (\n              <Spinner\n                svgTitleId=\"delete-discussions-spinner\"\n                isLoading\n                display=\"inline\"\n              />\n            ) : (\n              'trash'\n            )\n          }\n          disabled\n          aria-label={i18n._(/* i18n */ 'timeline.action.delete', null, {\n            message: 'Delete selected',\n          })}\n        />\n      );\n    }\n\n    return (\n      <Confirm\n        onConfirm={this.handleDelete}\n        title={\n          <Trans\n            id=\"timeline.confirm-delete.title\"\n            message=\"Delete discussion(s)\"\n          />\n        }\n        content={\n          <Trans id=\"timeline.confirm-delete.content\">\n            The deletion is permanent, are you sure you want to delete these\n            discussions ?\n          </Trans>\n        }\n        render={(confirm) => (\n          <Button\n            icon={\n              isDeleting ? (\n                <Spinner\n                  svgTitleId=\"delete-discussion-confirm-spinner\"\n                  isLoading\n                  display=\"inline\"\n                />\n              ) : (\n                'trash'\n              )\n            }\n            onClick={confirm}\n            aria-label={i18n._(/* i18n */ 'timeline.action.delete', null, {\n              message: 'Delete selected',\n            })}\n          />\n        )}\n      />\n    );\n  }\n\n  render() {\n    const { i18n, count, totalCount, checked, isDeleting, indeterminate } =\n      this.props;\n\n    return (\n      <div className=\"m-discussion-selector\">\n        {count !== 0 && (\n          <span className=\"m-discussion-selector__title\">\n            <Trans\n              id=\"timeline.discussions.selected\"\n              message=\"{count, plural, one {#/{totalCount} discussion:} other {#/{totalCount} discussions:}\"\n              value={{ count, totalCount }}\n            />\n          </span>\n        )}\n        {/*\n          (checked || indeterminate) && // show buttons only when one or more is selected\n          <span className=\"m-discussion-selector__actions\">\n            <Button\n              icon=\"tags\"\n              onClick={this.handleEditTags}\n              disabled={count === 0}\n              aria-label={i18n._(/* i18n  'timeline.action.manage-tags', null, { message: 'Manage tags' })}\n            />\n            {this.renderDeleteButton()}\n          </span>\n          */}\n        <span className=\"m-discussion-selector__checkbox\">\n          <Checkbox\n            label={i18n._(\n              /* i18n */ 'message-list.action.select_all_discussions',\n              null,\n              {\n                message: 'Select/deselect all discussions',\n              }\n            )}\n            id=\"discussion-selector\"\n            checked={checked}\n            indeterminate={indeterminate}\n            onChange={this.toggleCheckbox}\n            disabled={isDeleting}\n            showLabelforSr\n          />\n        </span>\n      </div>\n    );\n  }\n}\n\nexport default DiscussionSelector;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Timeline/components/DiscussionSelector/style.scss",
    "content": "@import '../../../../styles/common';\n\n.m-discussion-selector {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  max-width: $co-site-width;\n  height: $co-component__height;\n  margin: 0 auto;\n\n  &__checkbox {\n    margin: auto $co-component__spacing;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Timeline/index.js",
    "content": "import { createSelector } from 'reselect';\nimport { compose, bindActionCreators } from 'redux';\nimport { connect } from 'react-redux';\nimport { store } from 'src/modules/user';\nimport {\n  requestDiscussions,\n  hasMore,\n  loadMoreDiscussions,\n  filterImportance,\n} from '../../store/modules/discussion';\nimport { getUser } from '../../modules/user/actions/getUser';\nimport { withContacts } from '../../modules/contact';\n\nimport Presenter from './presenter';\n\nconst discussionStateSelector = (state) => state.discussion;\n\nconst mapStateToProps = createSelector(\n  [discussionStateSelector, store.stateSelector],\n  (discussionState, userState) => ({\n    discussions: discussionState.discussions\n      .map((id) => discussionState.discussionsById[id])\n      .filter(\n        (discussion) =>\n          discussion.importance_level >= discussionState.importanceRange.min &&\n          discussion.importance_level <= discussionState.importanceRange.max\n      )\n      .sort((a, b) => new Date(b.date_update) - new Date(a.date_update)),\n    hasMore: discussionState && hasMore(discussionState),\n    isFetching: discussionState.isFetching,\n    didInvalidate: discussionState.didInvalidate,\n    importanceRange: discussionState.importanceRange,\n    isUserFetching: userState.isFetching,\n    user: userState.user,\n  })\n);\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      requestDiscussions,\n      loadMore: loadMoreDiscussions,\n      getUser,\n      filterImportance,\n      // deleteDiscussion: onDeleteMessage,\n    },\n    dispatch\n  );\n\nexport default compose(\n  // this initialize contacts for Participant rendering\n  withContacts(),\n  connect(mapStateToProps, mapDispatchToProps)\n)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Timeline/presenter.jsx",
    "content": "import React, { Component, Fragment } from 'react';\nimport PropTypes from 'prop-types';\nimport { throttle, isEqual } from 'lodash';\nimport { Trans } from '@lingui/react';\nimport { withScrollManager, ScrollDetector } from '../../modules/scroll';\nimport {\n  Button,\n  InfiniteScroll,\n  ActionBarWrapper,\n  ActionBar,\n  CheckboxFieldGroup,\n  PlaceholderBlock,\n  TextBlock,\n} from '../../components';\nimport DiscussionItem from './components/DiscussionItem';\nimport { withSettings } from '../../modules/settings';\n\n// XXX waiting for API\n// import DiscussionSelector from './components/DiscussionSelector';\n\nimport './style.scss';\nimport './timeline-action-bar.scss';\n\nconst LOAD_MORE_THROTTLE = 1000;\nconst FILTER_RANGE_DEFAULT = { min: 0, max: 10 };\nconst FILTER_RANGE_ALL = { min: -10, max: 10 };\n\n@withSettings()\n@withScrollManager()\nclass Timeline extends Component {\n  static propTypes = {\n    user: PropTypes.shape({}),\n    getUser: PropTypes.func.isRequired,\n    isUserFetching: PropTypes.bool.isRequired,\n    requestDiscussions: PropTypes.func.isRequired,\n    // XXX no API for now\n    // deleteDiscussion: PropTypes.func.isRequired,\n    loadMore: PropTypes.func.isRequired,\n    discussions: PropTypes.arrayOf(PropTypes.shape({})),\n    // tags: PropTypes.arrayOf(PropTypes.shape({})),\n    isFetching: PropTypes.bool,\n    didInvalidate: PropTypes.bool,\n    hasMore: PropTypes.bool,\n    settings: PropTypes.shape({}).isRequired,\n    filterImportance: PropTypes.func.isRequired,\n    importanceRange: PropTypes.shape({\n      min: PropTypes.number,\n      max: PropTypes.number,\n    }).isRequired,\n  };\n\n  static defaultProps = {\n    discussions: [],\n    // tags: [],\n    user: {},\n    isFetching: false,\n    didInvalidate: false,\n    hasMore: false,\n  };\n\n  state = {\n    initialized: false,\n    selectedDiscussions: [],\n    // XXX no API for now\n    isTagModalOpen: false,\n    // isDeleting: false,\n  };\n\n  componentDidMount() {\n    const { user, isUserFetching, getUser } = this.props;\n\n    if (!(user || isUserFetching)) {\n      getUser();\n    }\n\n    this.loadDiscussions(this.props);\n\n    this.throttledLoadMore = throttle(\n      () => this.props.loadMore(),\n      LOAD_MORE_THROTTLE,\n      { trailing: false }\n    );\n  }\n\n  UNSAFE_componentWillReceiveProps(nextProps) {\n    this.loadDiscussions(nextProps);\n  }\n\n  onSelectDiscussion = (type, discussionId) => {\n    if (type === 'add') {\n      this.setState((prevState) => ({\n        ...prevState,\n        selectedDiscussions: [...prevState.selectedDiscussions, discussionId],\n      }));\n    }\n\n    if (type === 'remove') {\n      this.setState((prevState) => ({\n        ...prevState,\n        selectedDiscussions: [...prevState.selectedDiscussions].filter(\n          (item) => item !== discussionId\n        ),\n      }));\n    }\n  };\n\n  onSelectAllDiscussions = (type) => {\n    if (type === 'select') {\n      const { discussions } = this.props;\n\n      this.setState((prevState) => ({\n        ...prevState,\n        selectedDiscussions: discussions.map(\n          (discussion) => discussion.discussion_id\n        ),\n      }));\n    }\n\n    if (type === 'unselect') {\n      this.setState((prevState) => ({ ...prevState, selectedDiscussions: [] }));\n    }\n  };\n\n  loadDiscussions = async (props, force = false) => {\n    const { requestDiscussions, isFetching, didInvalidate } = props;\n    if ((!this.state.initialized || force || didInvalidate) && !isFetching) {\n      // \"initialized\" is not well named,\n      // we consider it \"initialized\" as soon as we start fetching messages to prevent multiple\n      // fetchs because setState would be applied at the very end after multiple\n      // UNSAFE_componentWillReceiveProps\n      this.setState({ initialized: true });\n      requestDiscussions();\n    }\n\n    return Promise.resolve();\n  };\n\n  loadMore = () => {\n    if (this.props.hasMore) {\n      this.throttledLoadMore();\n    }\n  };\n\n  handleToggleShowSpam = () => {\n    const { filterImportance, importanceRange } = this.props;\n\n    const nextRange = isEqual(importanceRange, FILTER_RANGE_DEFAULT)\n      ? FILTER_RANGE_ALL\n      : FILTER_RANGE_DEFAULT;\n\n    filterImportance(nextRange);\n  };\n\n  makeHandleClickClearNotifications = (cb) => () => {\n    this.loadDiscussions(this.props, true);\n    cb();\n  };\n\n  renderPlaceholder = () => (\n    <ul className=\"s-timeline__discussion-list\">\n      {[1, 2, 3, 4, 5].map((n) => (\n        <PlaceholderBlock key={n} className=\"s-timeline__discussion\" />\n      ))}\n    </ul>\n  );\n\n  renderDiscussions() {\n    const { discussions, user, settings } = this.props;\n    const { selectedDiscussions } = this.state;\n\n    if (!discussions.length) {\n      return this.renderPlaceholder();\n    }\n\n    return (\n      <ul className=\"s-timeline__discussion-list\">\n        {discussions.map((discussion) => (\n          <DiscussionItem\n            key={discussion.discussion_id}\n            className=\"s-timeline__discussion\"\n            user={user}\n            discussion={discussion}\n            onSelectDiscussion={this.onSelectDiscussion}\n            onSelectAllDiscussions={this.onSelectAllDiscussions}\n            isDiscussionSelected={selectedDiscussions.includes(\n              discussion.discussion_id\n            )}\n            settings={settings}\n          />\n        ))}\n      </ul>\n    );\n  }\n\n  renderActionBar() {\n    const { isFetching, importanceRange } = this.props;\n    const hasSpam = isEqual(importanceRange, FILTER_RANGE_ALL);\n    // const nbSelectedDiscussions = this.state.selectedDiscussions.length;\n\n    return (\n      <ScrollDetector\n        offset={136}\n        render={(isSticky) => (\n          <ActionBarWrapper isSticky={isSticky}>\n            <ActionBar\n              hr={false}\n              isLoading={isFetching}\n              actionsNode={\n                <div className=\"s-timeline-action-bar\">\n                  <TextBlock>\n                    <CheckboxFieldGroup\n                      className=\"s-timeline-action-bar__filters\"\n                      displaySwitch\n                      showTextLabel\n                      label={\n                        <Trans\n                          id=\"timeline.action.display-spam\"\n                          message=\"Show spam\"\n                        />\n                      }\n                      onChange={this.handleToggleShowSpam}\n                      checked={hasSpam}\n                    />\n                  </TextBlock>\n                  {/*  <DiscussionSelector\n                    count={nbSelectedDiscussions}\n                    checked={nbSelectedDiscussions > 0\n                        && nbSelectedDiscussions === discussions.length}\n                        totalCount={discussions.length}\n                        onSelectAllDiscussions={this.onSelectAllDiscussions}\n                        onEditTags={this.handleOpenTags}\n                        onDeleteDiscussions={this.handleDeleteDiscussions}\n                        isDeleting={this.state.isDeleting}\n                        indeterminate={nbSelectedDiscussions > 0\n                            && nbSelectedDiscussions < discussions.length}\n                          />\n                  */}\n                </div>\n              }\n            />\n          </ActionBarWrapper>\n        )}\n      />\n    );\n  }\n\n  render() {\n    const { hasMore } = this.props;\n\n    return (\n      <Fragment>\n        <section className=\"s-timeline\">\n          {this.renderActionBar()}\n          <InfiniteScroll onReachBottom={this.loadMore}>\n            <Fragment>{this.renderDiscussions()}</Fragment>\n          </InfiniteScroll>\n        </section>\n        {hasMore && (\n          <div className=\"s-timeline__load-more\">\n            <Button shape=\"hollow\" onClick={this.loadMore}>\n              <Trans id=\"general.action.load_more\" message=\"Load more\" />\n            </Button>\n          </div>\n        )}\n      </Fragment>\n    );\n  }\n}\n\nexport default Timeline;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Timeline/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-timeline {\n  background-color: white;\n\n  &__discussion-list {\n    background-color: lightgrey;\n  }\n\n  &__discussion {\n    border-top: 2px solid $co-color__fg__border--higher;\n  }\n\n  &__load-more {\n    margin: 1rem auto;\n    text-align: center;\n  }\n\n  &__new-msg {\n    background-color: $co-color__fg__back;\n    text-align: center;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/Timeline/timeline-action-bar.scss",
    "content": "@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-timeline-action-bar {\n  @include flex-grid-row($size: expand);\n\n  &__filters {\n    @include flex-grid-column(shrink);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserPrivacy/index.tsx",
    "content": "/* eslint-disable */\n// unused for now\nimport * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport {\n  Section,\n  PageTitle,\n  // MultidimensionalPi,\n  TextList,\n  TextItem,\n} from '../../components';\n\nimport './style.scss';\n\ntype Props = withI18nProps;\n\nfunction UserPrivacy({ i18n }: Props) {\n  const fakePi = { technic: 87, context: 45, comportment: 25 };\n\n  const privacyTips = [\n    {\n      name: 'userTip',\n      content:\n        'Haec dum oriens diu perferret, caeli reserato tepore Constantiu',\n    },\n    {\n      name: 'contactTip',\n      content:\n        'Consulatu suo septies et Caesaris ter egressus Arelate Valentiam',\n    },\n    {\n      name: 'deviceTip',\n      content:\n        'Gundomadum et Vadomarium fratres Alamannorum reges arma moturus',\n    },\n    {\n      name: 'tutorialTip',\n      content: 'Quorum crebris excursibus vastabantur confines limitibu.',\n    },\n  ];\n\n  return (\n    <div className=\"s-user-privacy\">\n      <PageTitle />\n\n      {/* <MultidimensionalPi className=\"s-user-privacy__pi\" pi={fakePi} /> */}\n\n      <Section\n        className=\"s-user-privacy__info\"\n        title={i18n._(/* i18n */ 'user.privacy.improve_pi', undefined, {\n          message: 'Improve your privacy index',\n        })}\n      >\n        <TextList className=\"s-user-privacy__tips\">\n          {privacyTips.map((tip) => (\n            <TextItem className=\"s-user-privacy__tip\" key={tip.name}>\n              {tip.content}\n            </TextItem>\n          ))}\n        </TextList>\n      </Section>\n    </div>\n  );\n}\n\nexport default withI18n()(UserPrivacy);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserPrivacy/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-user-privacy {\n  @include flex-grid-row($size: expand);\n\n  margin-top: $co-margin;\n\n  &__pi {\n    @include flex-grid-column(12);\n  }\n\n  &__info {\n    @include flex-grid-column(12);\n  }\n\n  &__tips {\n    list-style: inside disc;\n  }\n\n  &__tip {\n    padding: $co-component__spacing 0;\n  }\n\n  @include breakpoint(medium) {\n    &__pi {\n      @include flex-grid-size(4);\n    }\n\n    &__info {\n      @include flex-grid-column(8);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserProfile/components/ProfileForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Field } from 'formik';\nimport { withI18nProps, withI18n } from '@lingui/react';\nimport { FieldErrors, FormikTextFieldGroup } from 'src/components';\nimport './style.scss';\n\ninterface Props extends withI18nProps {\n  errors?: Record<string, string[]>;\n  editMode?: boolean;\n}\n\nfunction ProfileForm({ errors, i18n, editMode = false }: Props) {\n  return (\n    <div className=\"s-profile-form\">\n      {errors && errors.global?.length !== 0 && (\n        <FieldErrors errors={errors.global} />\n      )}\n      {/* disables avatar managment on alpha\n              <Field\n                component={TextFieldGroup}\n                name=\"contact.avatar\"\n                label={__('user.profile.form.avatar.label')}\n                disabled\n              />\n        */}\n      <Field\n        id=\"profile_form.name\"\n        component={FormikTextFieldGroup}\n        name=\"name\"\n        label={i18n._(\n          /* i18n */ 'user.profile.form.username.label',\n          undefined,\n          {\n            message: 'Username',\n          }\n        )}\n        inputProps={{\n          disabled: true,\n        }}\n      />\n      <Field\n        id=\"profile_form.recovery_email\"\n        component={FormikTextFieldGroup}\n        name=\"recovery_email\"\n        label={i18n._(\n          /* i18n */ 'user.profile.form.recovery_email.label',\n          undefined,\n          {\n            message: 'Recovery email',\n          }\n        )}\n        inputProps={{\n          disabled: true,\n        }}\n      />\n      <Field\n        id=\"profile_form.given_name\"\n        component={FormikTextFieldGroup}\n        name=\"contact.given_name\"\n        label={i18n._(\n          /* i18n */ 'user.profile.form.given_name.label',\n          undefined,\n          {\n            message: 'Given name',\n          }\n        )}\n        inputProps={{\n          disabled: !editMode,\n        }}\n      />\n      <Field\n        id=\"profile_form.famaily_name\"\n        component={FormikTextFieldGroup}\n        name=\"contact.family_name\"\n        label={i18n._(\n          /* i18n */ 'user.profile.form.family_name.label',\n          undefined,\n          {\n            message: 'Family name',\n          }\n        )}\n        inputProps={{\n          disabled: !editMode,\n        }}\n      />\n    </div>\n  );\n}\n\nexport default withI18n()(ProfileForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserProfile/components/ProfileForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.s-profile-form {\n  display: grid;\n  column-gap: map_get($co-margin_, 'xsmall');\n  row-gap: map_get($co-margin_, 'xsmall');\n\n  @include breakpoint(medium) {\n    grid-template-columns: auto auto;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserProfile/components/ProfileInfo/index.tsx",
    "content": "import * as React from 'react';\nimport Moment from 'react-moment';\nimport { Trans } from '@lingui/react';\nimport { formatName } from 'src/modules/contact';\nimport { ContactAvatarLetter } from 'src/modules/avatar';\nimport { useSettings } from 'src/modules/settings';\n\nimport './style.scss';\nimport { useUser } from 'src/modules/user';\n\nfunction ProfileInfo(): JSX.Element {\n  const settings = useSettings();\n  const { user } = useUser();\n  const format = settings.contact_display_format;\n  const locale = settings.default_locale;\n  const contact = user?.contact;\n\n  return (\n    <div className=\"m-user-profile-details\">\n      <div className=\"m-user-profile-details__header\">\n        <div className=\"m-user-profile-details__avatar-wrapper\">\n          {user && (\n            <ContactAvatarLetter\n              contact={user.contact}\n              contactDisplayFormat={format}\n              className=\"m-user-profile-details__avatar\"\n            />\n          )}\n        </div>\n      </div>\n      <div className=\"m-user-profile-details__name\">\n        <h3 className=\"m-user-profile-details__title\">{user && user.name}</h3>\n        <h4 className=\"m-user-profile-details__subtitle\">\n          {contact && formatName({ contact, format })}\n        </h4>\n        <p>\n          <Trans id=\"user.profile.subscribed_date\" message=\"Subscribed on\" />{' '}\n          {user && (\n            <Moment\n              className=\"m-user-profile-details__subscribed-date\"\n              format=\"ll\"\n              locale={locale}\n            >\n              {user.date_insert}\n            </Moment>\n          )}\n        </p>\n      </div>\n\n      {/* \"rank\" is not implemt by now\n              <div className=\"m-user-profile-details__rank\">\n                <div className=\"m-user-profile-details__rank-badge\" />\n                <div className=\"m-user-profile-details__rank-info\">\n                  <h4 className=\"m-user-profile-details__rank-title\">fake rank</h4>\n                  <Link to=\"\"><Trans id=\"user.action.improve_rank\" message=\"Improve your rank\" /></Link>\n                </div>\n              </div>\n              */}\n    </div>\n  );\n}\n\nexport default ProfileInfo;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserProfile/components/ProfileInfo/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n@import '../../../../styles/object/o-avatar';\n\n@mixin m-user-profile-info--horizontal {\n  &__avatar-wrapper {\n    @include flex-grid-size(shrink);\n  }\n\n  &__avatar {\n    @include m-avatar--size(large);\n\n    margin-bottom: calc($co-margin / 2);\n  }\n\n  &__name {\n    @include flex-grid-column(shrink);\n  }\n}\n\n@mixin m-user-profile-info--vertical {\n  &__avatar-wrapper {\n    @include flex-grid-size(12);\n  }\n\n  &__avatar {\n    @include m-avatar--size(xlarge);\n  }\n\n  &__name {\n    @include flex-grid-column(12);\n  }\n}\n\n.m-user-profile-details {\n  &__header {\n    @include flex-grid-row($size: expand);\n\n    position: relative;\n  }\n\n  &__title {\n    margin: 0;\n    color: $co-color__fg__text--high;\n  }\n\n  &__subtitle {\n    margin: 0;\n    color: $co-color__fg__text;\n    font-size: $co-font__size--small;\n    font-weight: 400;\n  }\n\n  &__rank {\n    @include flex-grid-row($size: expand);\n  }\n\n  &__rank-badge {\n    @include flex-grid-size(4);\n  }\n\n  &__rank-info {\n    @include flex-grid-column(8);\n\n    text-align: center;\n  }\n\n  &__rank-title {\n    color: $co-color__fg__text--high;\n    font-weight: 600;\n  }\n\n  &__subscribed-date {\n    display: block;\n    color: $co-color__fg__text--high;\n    font-weight: 600;\n  }\n\n  @include m-user-profile-info--horizontal;\n\n  @include breakpoint(medium) {\n    @include m-user-profile-info--vertical;\n\n    &__rank-badge {\n      @include flex-grid-size(4);\n    }\n\n    &__rank-info {\n      @include flex-grid-column(8);\n    }\n\n    &__name,\n    &__avatar-wrapper {\n      text-align: center;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserProfile/index.spec.tsx",
    "content": "import * as React from 'react';\nimport {\n  render,\n  screen,\n  waitFor,\n  waitForElementToBeRemoved,\n} from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { rest } from 'msw';\nimport { server } from 'test/server';\nimport { AllProviders } from 'test/providers';\nimport { generateUser } from 'test/fixtures/user';\nimport { generateContact } from 'test/fixtures/contacts';\nimport UserProfile from '.';\n\njest.mock('src/modules/user/services/isAuthenticated', () => ({\n  isAuthenticated: () => true,\n}));\n\ndescribe('UserProfile', () => {\n  it('renders', async () => {\n    server.use(\n      rest.get('/api/v1/me', (req, res, ctx) =>\n        res(\n          ctx.json(\n            generateUser({ contact: generateContact({ given_name: 'John' }) })\n          ),\n          ctx.status(200)\n        )\n      ),\n      rest.patch('/api/v2/contacts/:id', (req, res, ctx) =>\n        res(ctx.json({ message: 'ok' }), ctx.status(200))\n      )\n    );\n\n    render(<UserProfile />, { wrapper: AllProviders });\n    await waitForElementToBeRemoved(screen.getByText('Loading …'), {\n      timeout: 5000,\n    });\n    expect(screen.getByText('John Dœuf')).toBeVisible();\n\n    expect(screen.getByLabelText('Given name')).toHaveValue('John');\n    expect(screen.getByLabelText('Given name')).toBeDisabled();\n\n    userEvent.click(screen.getByRole('button', { name: 'Edit' }));\n    expect(screen.getByLabelText('Given name')).toBeEnabled();\n\n    userEvent.clear(screen.getByLabelText('Given name'));\n    userEvent.type(screen.getByLabelText('Given name'), 'Foobar');\n\n    userEvent.click(screen.getByRole('button', { name: 'Update' }));\n    await waitFor(() =>\n      expect(screen.getByLabelText('Given name')).toBeDisabled()\n    );\n    expect(screen.getByLabelText('Given name')).toHaveValue('Foobar');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserProfile/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { useDispatch } from 'react-redux';\nimport { Formik, Form, FormikConfig } from 'formik';\nimport { compose } from 'redux';\nimport { notifyError, notifySuccess } from 'src/modules/userNotify';\nimport { updateContact } from 'src/modules/contact/store';\nimport { UserPayload } from 'src/modules/user/types';\nimport { invalidate } from 'src/modules/user/store';\nimport {\n  Section,\n  PageTitle,\n  Button,\n  Confirm,\n  TextFieldGroup,\n  Spinner,\n} from '../../components';\nimport ProfileForm from './components/ProfileForm';\nimport ProfileInfo from './components/ProfileInfo';\nimport { signout } from '../../modules/routing';\nimport { deleteUser, useUser } from '../../modules/user';\n\nimport './style.scss';\n\ntype Props = withI18nProps;\n\nfunction UserProfile({ i18n }: Props) {\n  const dispatch = useDispatch();\n  const [editMode, setEditMode] = React.useState(false);\n  const [password, setPassword] = React.useState('');\n  const [errorDeleteAccount, setErrorDeleteAccount] = React.useState<\n    undefined | React.ReactNode[]\n  >();\n\n  const { user } = useUser();\n\n  const handleSubmit: FormikConfig<UserPayload>['onSubmit'] = async (\n    values\n  ) => {\n    try {\n      await dispatch(\n        updateContact({ contact: values.contact, original: user?.contact })\n      );\n      dispatch(invalidate());\n      setEditMode(false);\n    } catch (err) {\n      dispatch(\n        notifyError({\n          message: i18n._(\n            /* i18n */ 'contact.feedback.unable_to_save',\n            undefined,\n            {\n              message: 'Unable to save the contact',\n            }\n          ),\n        })\n      );\n    }\n  };\n\n  const handlePasswordChange = (ev) => {\n    setPassword(ev.target.value);\n    setErrorDeleteAccount(undefined);\n  };\n\n  const handleDeleteAccount = async () => {\n    try {\n      await deleteUser({\n        user,\n        password,\n      });\n\n      dispatch(\n        notifySuccess({\n          message: (\n            <Trans\n              id=\"user.feedback.delete_account_sucessful\"\n              message=\"Your account has been deleted, you will be automatically disconnected.\"\n            />\n          ),\n        })\n      );\n      signout();\n\n      return undefined;\n    } catch (errors) {\n      if (\n        errors?.some(\n          (err) => err.message === '[RESTfacility] DeleteUser Wrong password'\n        )\n      ) {\n        setErrorDeleteAccount([\n          <Trans\n            id=\"user.delete-form.error.incorrect_password\"\n            message=\"Unable to delete your account, the given password is incorrect.\"\n          />,\n        ]);\n      } else {\n        setErrorDeleteAccount(errors.map((err) => err.message));\n      }\n\n      return Promise.reject(new Error('Unable to delete account'));\n    }\n  };\n\n  const handleCloseDeleteConfirm = () => {\n    setPassword('');\n    setErrorDeleteAccount(undefined);\n  };\n\n  return (\n    <div className=\"s-user-profile\">\n      <PageTitle />\n      <div className=\"s-user-profile__info\">\n        <ProfileInfo />\n      </div>\n      <Section\n        className=\"s-user-profile__details\"\n        title={i18n._(/* i18n */ 'user.profile.form.title', undefined, {\n          message: 'Complete your profile',\n        })}\n      >\n        {user ? (\n          <Formik initialValues={user} onSubmit={handleSubmit}>\n            {({ handleReset, isSubmitting, touched }) => (\n              <Form method=\"post\" name=\"user-profile\">\n                {/* XXX: an errors prop can be passed  */}\n                <div className=\"s-user-profile__profile\">\n                  <ProfileForm editMode={editMode} />\n                </div>\n                <div className=\"s-user-profile__actions\">\n                  {!editMode ? (\n                    <Button onClick={() => setEditMode(true)} shape=\"plain\">\n                      <Trans id=\"user.action.edit_profile\" message=\"Edit\" />\n                    </Button>\n                  ) : (\n                    <>\n                      <Button\n                        type=\"submit\"\n                        shape=\"plain\"\n                        disabled={isSubmitting || !touched}\n                        icon={\n                          isSubmitting ? (\n                            <Spinner\n                              display=\"inline\"\n                              svgTitleId=\"update-user-spinner\"\n                            />\n                          ) : undefined\n                        }\n                      >\n                        <Trans id=\"user.action.update\" message=\"Update\" />\n                      </Button>{' '}\n                      <Button\n                        onClick={() => {\n                          setEditMode(false);\n                          handleReset();\n                        }}\n                        shape=\"hollow\"\n                      >\n                        <Trans id=\"user.action.cancel_edit\" message=\"Cancel\" />\n                      </Button>\n                    </>\n                  )}\n                  <Confirm\n                    className=\"s-user-profile__delete\"\n                    render={(confirm) => (\n                      <Button shape=\"plain\" onClick={confirm} color=\"alert\">\n                        <Trans\n                          id=\"user.action.delete\"\n                          message=\"Delete account\"\n                        />\n                      </Button>\n                    )}\n                    title={\n                      <Trans\n                        id=\"user.delete-form.modal-title\"\n                        message=\"Delete account\"\n                      />\n                    }\n                    content={\n                      <div className=\"s-user-profile__modal-delete-form\">\n                        <p>\n                          <Trans\n                            id=\"user.delete-form.modal-content\"\n                            message=\"Are you sure to delete your Caliopen account ?\"\n                          />\n                        </p>\n                        <TextFieldGroup\n                          id=\"user-delete_password\"\n                          label={i18n._(\n                            /* i18n */ 'user.delete-form.password.label',\n                            undefined,\n                            {\n                              message: 'Password',\n                            }\n                          )}\n                          inputProps={{\n                            placeholder: i18n._(\n                              /* i18n */ 'user.delete-form.password.placeholder',\n                              undefined,\n                              { message: 'password' }\n                            ),\n                            name: 'password',\n                            type: 'password',\n                            value: password,\n                            onChange: handlePasswordChange,\n                          }}\n                          errors={errorDeleteAccount}\n                        />\n                      </div>\n                    }\n                    confirmButtonContent={\n                      <Trans\n                        id=\"user.delete-form.action.delete\"\n                        message=\"Delete my Caliopen account\"\n                      />\n                    }\n                    onConfirm={handleDeleteAccount}\n                    onCancel={handleCloseDeleteConfirm}\n                    onClose={handleCloseDeleteConfirm}\n                  />\n                </div>\n              </Form>\n            )}\n          </Formik>\n        ) : (\n          <Spinner svgTitleId=\"user-profile-loading\" />\n        )}\n      </Section>\n    </div>\n  );\n}\n\nexport default compose(withI18n())(UserProfile);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserProfile/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-user-profile {\n  @include flex-grid-row($size: expand);\n\n  margin-top: $co-margin;\n\n  &__profile {\n    margin-bottom: map_get($map: $co-margin_, $key: 'xsmall');\n  }\n\n  &__info {\n    @include flex-grid-column(12);\n  }\n\n  &__details {\n    @include flex-grid-column(12);\n  }\n\n  &__delete {\n    float: right;\n  }\n\n  @include breakpoint(medium) {\n    &__info {\n      @include flex-grid-size(4);\n    }\n\n    &__details {\n      @include flex-grid-size(8);\n    }\n  }\n\n  @include breakpoint(large) {\n    &__info {\n      @include flex-grid-size(3);\n    }\n\n    &__details {\n      @include flex-grid-size(9);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/LoginDetails/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n, Trans } from '@lingui/react';\n// import { Button } from '../../../../components/';\nimport { TextBlock, TextFieldGroup } from '../../../../components';\nimport './style.scss';\n\n@withI18n()\nclass LoginDetails extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    user: PropTypes.shape({}).isRequired,\n  };\n\n  // state = {\n  //   editMode: false,\n  // }\n  //\n  // toggleEditMode = () => {\n  //   this.setState(prevState => ({ editMode: !prevState.editMode }));\n  // }\n\n  render() {\n    const { i18n, user } = this.props;\n\n    return (\n      <div className=\"m-login-details\">\n        <TextBlock className=\"m-login-details__title\">\n          <Trans id=\"login.details.title\" message=\"Login:\" />\n        </TextBlock>\n        <TextFieldGroup\n          className=\"m-login-details__input\"\n          inputProps={{\n            value: user && user.name,\n            disabled: true,\n          }}\n          label={i18n._(/* i18n */ 'login.details.label', null, {\n            message: 'Login:',\n          })}\n          showLabelforSr\n        />\n        {/* TODO: enable editing login info\n          <div className=\"m-login-details__action\">\n          <Button onClick={this.toggleEditMode}>\n            <Trans id=\"login.details.action.change\" message=\"Change your login\" />\n          </Button>\n        </div>\n      */}\n      </div>\n    );\n  }\n}\n\nexport default LoginDetails;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/LoginDetails/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-login-details {\n  @include flex-grid-row;\n\n  &__title {\n    @include flex-grid-size(12);\n\n    margin-bottom: $co-margin;\n  }\n\n  &__input {\n    @include flex-grid-size(12);\n  }\n\n  &__action {\n    @include flex-grid-size(shrink);\n  }\n\n  @include breakpoint(medium) {\n    &__input {\n      @include flex-grid-size;\n    }\n\n    &__action {\n      @include flex-grid-column(shrink);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/OpenPGPForm.scss",
    "content": "@import '../../../styles/common';\n\n.m-account-openpgp-form {\n  &__container {\n    margin-top: $co-margin;\n  }\n\n  &__field-group {\n    margin-bottom: $co-margin;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/OpenPGPGenerateForm.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport {\n  Spinner,\n  Button,\n  CheckboxFieldGroup,\n  SelectFieldGroup,\n  TextFieldGroup,\n  FieldErrors,\n} from 'src/components';\nimport getPGPManager from 'src/services/openpgp-manager';\nimport { useUser } from 'src/modules/user';\nimport { saveUserPublicKeyAction } from 'src/modules/publicKey';\nimport { saveKey } from 'src/services/openpgp-keychain-repository';\nimport { useDispatch } from 'react-redux';\nimport './OpenPGPForm.scss';\n\ninterface GenerateValue {\n  email: string;\n  passphrase: string;\n}\nconst initGenerateValues: GenerateValue = {\n  email: '',\n  passphrase: '',\n};\n\ninterface Props extends withI18nProps {\n  cancel: () => void;\n  onSuccess: () => void;\n}\n\nfunction OpenPGPGenerateForm({ i18n, cancel, onSuccess }: Props) {\n  const dispatch = useDispatch();\n  const [generateValues, setGenerateValues] =\n    React.useState(initGenerateValues);\n  const [hasPassphrase, setHasPassphrase] = React.useState<boolean>(false);\n\n  const [isLoading, setIsLoading] = React.useState<boolean>(false);\n  const [errors, setErrors] = React.useState<undefined | string[]>();\n\n  const { user } = useUser();\n\n  const emailOptions =\n    user?.contact.emails?.map((email) => ({\n      label: email.address,\n      value: email.address,\n    })) || [];\n\n  const errorsLabels = {};\n\n  const handleToggleHasPassprase = () => {\n    setHasPassphrase((prev) => !prev);\n  };\n\n  const handleGenerateChanges = (event) => {\n    const { name, value } = event.target;\n    setGenerateValues((prev) => ({\n      ...prev,\n      [name]: value,\n    }));\n    setErrors(undefined);\n  };\n\n  const handleGenerateSubmit = async (event) => {\n    event.preventDefault();\n    if (!user) {\n      return;\n    }\n    setIsLoading(true);\n    const { passphrase, email } = generateValues;\n    const manager = await getPGPManager();\n\n    const { privateKeyArmored, publicKeyArmored } = await manager.generateKey(\n      user.contact.given_name,\n      email,\n      passphrase\n    );\n    try {\n      await saveKey(publicKeyArmored, privateKeyArmored);\n      dispatch(saveUserPublicKeyAction(publicKeyArmored, user));\n      setGenerateValues(initGenerateValues);\n      onSuccess();\n    } catch (error) {\n      setErrors([error]);\n    }\n\n    setIsLoading(false);\n  };\n\n  const handleCancelForm = () => {\n    cancel();\n  };\n\n  return (\n    <form onSubmit={handleGenerateSubmit}>\n      {errors && (\n        <FieldErrors\n          className=\"m-account-openpgp-form__field-group\"\n          errors={errors.map((key) => errorsLabels[key] || key)}\n        />\n      )}\n      {emailOptions.length > 1 && (\n        <SelectFieldGroup\n          className=\"m-account-openpgp-form__field-group\"\n          label={i18n._(/* i18n */ 'user.openpgp.form.email.label', undefined, {\n            message: 'Email:',\n          })}\n          value={generateValues.email}\n          onChange={handleGenerateChanges}\n          name=\"email\"\n          options={emailOptions}\n          required\n        />\n      )}\n      {emailOptions.length === 1 && (\n        <p className=\"m-account-openpgp-form__field-group\">\n          <Trans id=\"user.openpgp.form.email.label\" message=\"Email:\" />{' '}\n          {generateValues.email}\n        </p>\n      )}\n      <div className=\"m-account-openpgp-form__field-group\">\n        <Trans id=\"user.openpgp.has-passphrase\" message=\"Enable passphrase\" />{' '}\n        <CheckboxFieldGroup\n          displaySwitch\n          label={i18n._(/* i18n */ 'user.openpgp.has-passphrase', undefined, {\n            message: 'Enable passphrase',\n          })}\n          // @ts-ignore\n          value={hasPassphrase}\n          onChange={handleToggleHasPassprase}\n        />\n      </div>\n\n      {hasPassphrase && (\n        <div className=\"m-account-openpgp-form__field-group\">\n          <TextFieldGroup\n            id=\"openpgp_passphrase\"\n            label={i18n._(\n              /* i18n */ 'user.openpgp.form.passphrase.label',\n              undefined,\n              {\n                message: 'Passphrase',\n              }\n            )}\n            inputProps={{\n              type: 'password',\n              value: generateValues.passphrase,\n              onChange: handleGenerateChanges,\n              name: 'passphrase',\n              autoComplete: 'new-password',\n            }}\n          />\n        </div>\n      )}\n      <div className=\"m-account-openpgp-form__field-group\">\n        {/* @ts-ignore */}\n        <Button type=\"submit\" shape=\"plain\">\n          <Spinner svgTitleId=\"create-pgpkey-spinner\" isLoading={isLoading} />{' '}\n          <Trans id=\"user.openpgp.action.create\" message=\"Create\" />\n        </Button>{' '}\n        {/* @ts-ignore */}\n        <Button onClick={handleCancelForm} shape=\"hollow\">\n          <Trans id=\"general.action.cancel\" message=\"Cancel\" />\n        </Button>\n      </div>\n    </form>\n  );\n}\n\nexport default withI18n()(OpenPGPGenerateForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/OpenPGPImportForm.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { useDispatch } from 'react-redux';\nimport {\n  Spinner,\n  Button,\n  TextFieldGroup,\n  FieldErrors,\n  InputFileGroup,\n} from 'src/components';\nimport { useUser } from 'src/modules/user';\nimport { saveUserPublicKeyAction } from 'src/modules/publicKey';\nimport { saveKey } from 'src/services/openpgp-keychain-repository';\nimport { readAsText } from 'src/modules/file';\nimport {\n  ERROR_UNABLE_READ_PRIVATE_KEY,\n  ERROR_FINGERPRINTS_NOT_MATCH,\n} from 'src/services/openpgp-manager';\nimport {\n  ERROR_NEED_PASSPHRASE,\n  ERROR_WRONG_PASSPHRASE,\n  getPublicKeyFromPrivateKey,\n} from 'src/services/encryption';\n\nimport './OpenPGPForm.scss';\n\nconst ERROR_REQUIRED = 'required';\n\ninterface ImportValue {\n  passphrase: string;\n  privateKeyArmored: string;\n}\nconst initImportValues = {\n  passphrase: '',\n  privateKeyArmored: '',\n};\n\ninterface Errors {\n  global?: string[];\n  privateKeyArmored?: string[];\n}\n\nfunction validate(values: ImportValue): void | Errors {\n  if (!values.privateKeyArmored.length) {\n    return {\n      privateKeyArmored: [ERROR_REQUIRED],\n    };\n  }\n\n  return undefined;\n}\n\ninterface Props extends withI18nProps {\n  cancel: () => void;\n  onSuccess: () => void;\n}\n\nfunction OpenPGPImportForm({ i18n, cancel, onSuccess }: Props) {\n  const dispatch = useDispatch();\n  const [importValues, setImportValues] =\n    React.useState<ImportValue>(initImportValues);\n\n  const [isLoading, setIsLoading] = React.useState<boolean>(false);\n  const [errors, setErrors] = React.useState<Errors>({});\n\n  const { user } = useUser();\n\n  const errorsLabels = {\n    [ERROR_REQUIRED]: i18n._(\n      /* i18n */ 'openpgp.feedback.private-key-required',\n      undefined,\n      { message: 'A file is required (extension .asc)' }\n    ),\n    [ERROR_UNABLE_READ_PRIVATE_KEY]: i18n._(\n      /* i18n */ 'openpgp.feedback.unable-read-private-key',\n      undefined,\n      { message: 'Unable to read private key' }\n    ),\n    [ERROR_FINGERPRINTS_NOT_MATCH]: i18n._(\n      /* i18n */ 'openpgp.feedback.fingerprints-not-match',\n      undefined,\n      { message: 'Fingerprints do not match' }\n    ),\n    [ERROR_NEED_PASSPHRASE]: i18n._(\n      /* i18n */ 'openpgp.feedback.need-passphrase',\n      undefined,\n      { message: 'Passphrase is needed to retrieve public key' }\n    ),\n    [ERROR_WRONG_PASSPHRASE]: i18n._(\n      /* i18n */ 'openpgp.feedback.wrong-passphrase',\n      undefined,\n      { message: 'Cannot decrypt with current passphrase' }\n    ),\n  };\n\n  const clearForm = () => {\n    setErrors({});\n    setImportValues(initImportValues);\n    // XXX: clear file, implement ref on file input. for now, set edit mode false is enough\n  };\n\n  const handleFileChanges = async (file) => {\n    // XXX: input file should always provide array: on unset or on set\n    const privateKeyArmored =\n      file.length === undefined ? await readAsText(file) : '';\n    setImportValues((prev) => ({\n      ...prev,\n      privateKeyArmored,\n    }));\n    setErrors({});\n  };\n\n  const handleImportChanges = (event) => {\n    const { name, value } = event.target;\n    setImportValues((prev) => ({ ...prev, [name]: value }));\n    setErrors({});\n  };\n\n  const handleImportSubmit = async (event) => {\n    event.preventDefault();\n\n    const importErrors = validate(importValues);\n\n    if (!user) {\n      return;\n    }\n\n    if (importErrors) {\n      setErrors(importErrors);\n      return;\n    }\n\n    setIsLoading(true);\n    try {\n      const publicKeyArmored = await getPublicKeyFromPrivateKey(\n        importValues.privateKeyArmored,\n        importValues.passphrase\n      );\n      await saveKey(publicKeyArmored, importValues.privateKeyArmored);\n\n      dispatch(\n        saveUserPublicKeyAction(publicKeyArmored, { contact: user.contact })\n        // @ts-ignore: does not see the promise\n      ).catch((err) => {\n        // silent fail. may be notify the user.\n      });\n    } catch (error) {\n      setErrors((prev) => ({ ...prev, global: [error.message] }));\n      setIsLoading(false);\n      return;\n    }\n\n    clearForm();\n    onSuccess();\n    setIsLoading(false);\n  };\n\n  const handleCancelForm = () => {\n    cancel();\n  };\n\n  return (\n    <form onSubmit={handleImportSubmit}>\n      {errors.global && (\n        <FieldErrors\n          className=\"m-account-openpgp-form__field-group\"\n          errors={errors.global.map((key) => errorsLabels[key] || key)}\n        />\n      )}\n\n      <TextFieldGroup\n        id=\"pgp-passphrase\"\n        label={i18n._(\n          /* i18n */ 'user.openpgp.form.passphrase.label',\n          undefined,\n          {\n            message: 'Passphrase',\n          }\n        )}\n        inputProps={{\n          value: importValues.passphrase,\n          onChange: handleImportChanges,\n          name: 'passphrase',\n          type: 'password',\n        }}\n      />\n      <InputFileGroup\n        required\n        className=\"m-account-openpgp-form__field-group\"\n        descr={i18n._(\n          /* i18n */ 'user.openpgp.form.private-key.label',\n          undefined,\n          {\n            message: 'Private key',\n          }\n        )}\n        onInputChange={handleFileChanges}\n        name=\"privateKeyArmored\"\n        accept=\"application/x-pgp\"\n        errors={\n          errors.privateKeyArmored &&\n          errors.privateKeyArmored.map((key) => errorsLabels[key] || key)\n        }\n      />\n      <div className=\"m-account-openpgp-form__field-group\">\n        {/* @ts-ignore */}\n        <Button\n          className=\"m-account-openpgp-form__field-group\"\n          type=\"submit\"\n          shape=\"plain\"\n        >\n          <Spinner svgTitleId=\"add-pgpkey-spinner\" isLoading={isLoading} />\n          <Trans id=\"user.openpgp.action.add\" message=\"Add\" />\n        </Button>{' '}\n        {/* @ts-ignore */}\n        <Button onClick={handleCancelForm} shape=\"hollow\">\n          <Trans id=\"general.action.cancel\" message=\"Cancel\" />\n        </Button>\n      </div>\n    </form>\n  );\n}\n\nexport default withI18n()(OpenPGPImportForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/OpenPGPKey/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport Moment from 'react-moment';\nimport classnames from 'classnames';\nimport { useSelector } from 'react-redux';\nimport { key, enums } from 'openpgp'; // XXX: is this included in the main bundle?\nimport { Button, Spinner, Icon, Link } from 'src/components';\nimport getPGPManager from 'src/services/openpgp-manager';\nimport { strToBase64 } from 'src/services/encode-utils';\nimport { RootState } from 'src/store/reducer';\nimport './style.scss';\n\ninterface KeyDetails {\n  fingerprint: string;\n  created: Date;\n  userId: string;\n  expirationTime: string;\n  algorithm: string;\n  bitSize: number;\n  userIds: string[];\n  keyStatus: enums.keyStatus;\n}\nasync function getKeyDetails(publicKey: key.Key): Promise<KeyDetails> {\n  const {\n    user: {\n      userId: { userid: userId },\n    },\n  } = await publicKey.getPrimaryUser();\n\n  const { expirationTime } = await publicKey.getExpirationTime();\n  const {\n    // @ts-ignore: the given key is deduced from the documentation\n    bits: bitSize,\n    // @ts-ignore\n    algorithm,\n  } = await publicKey.primaryKey.getAlgorithmInfo();\n  const keyStatus = await publicKey.verifyPrimaryKey();\n\n  return {\n    fingerprint: publicKey.getFingerprint(),\n    created: publicKey.primaryKey.getCreationTime(),\n    userId,\n    expirationTime,\n    algorithm,\n    bitSize,\n    userIds: publicKey.getUserIds(),\n    keyStatus,\n  };\n}\n\ninterface Props extends withI18nProps {\n  className: string;\n  publicKeyArmored: string;\n  privateKeyArmored: string;\n  editMode: boolean;\n  onDeleteKey: (fingerprint: string) => void;\n}\nfunction OpenPGPKey({\n  className,\n  publicKeyArmored,\n  privateKeyArmored,\n  onDeleteKey,\n  i18n,\n  editMode = false,\n}: Props) {\n  const [keyDetails, setKeyDetails] = React.useState<KeyDetails>();\n  const [loading, setIsLoading] = React.useState<boolean>(false);\n  const locale = useSelector<RootState, string>((state) => state.i18n.locale);\n\n  React.useEffect(() => {\n    setIsLoading(true);\n    getPGPManager().then(async ({ getKeyFromASCII }) => {\n      const pubKey = await getKeyFromASCII(publicKeyArmored);\n\n      if (pubKey) {\n        setKeyDetails(await getKeyDetails(pubKey));\n      }\n\n      setIsLoading(false);\n    });\n  }, [publicKeyArmored]);\n\n  const privateKeyDataUrl = `data:application/x-pgp;base64,${strToBase64(\n    privateKeyArmored\n  )}`;\n\n  const handleDeleteKey = () => {\n    if (!keyDetails) {\n      return;\n    }\n\n    onDeleteKey(keyDetails.fingerprint);\n  };\n\n  const openpgpStatuses = {\n    invalid: i18n._(/* i18n */ 'openpgp.status.invalid', undefined, {\n      message: 'Invalid',\n    }),\n    expired: i18n._(/* i18n */ 'openpgp.status.expired', undefined, {\n      message: 'Expired',\n    }),\n    revoked: i18n._(/* i18n */ 'openpgp.status.revoked', undefined, {\n      message: 'Revoked',\n    }),\n    valid: i18n._(/* i18n */ 'openpgp.status.valid', undefined, {\n      message: 'Valid',\n    }),\n    no_self_cert: i18n._(/* i18n */ 'openpgp.status.no_self_cert', undefined, {\n      message: 'No self cert',\n    }),\n  };\n\n  return (\n    <div className={classnames(['m-openpgp-key', className])}>\n      <div className=\"m-openpgp-key__main\">\n        <Spinner svgTitleId=\"page-pgpkey-spinner\" isLoading={loading} />\n        {keyDetails && (\n          <>\n            <div className=\"m-openpgp-key__icon\">\n              {/* @ts-ignore */}\n              <Icon type=\"key\" />\n            </div>\n            <div className=\"m-openpgp-key__fingerprint\">\n              {keyDetails.fingerprint.toUpperCase()}\n            </div>\n\n            <div className=\"m-openpgp-key__actions\">\n              <Link\n                button\n                shape=\"plain\"\n                href={privateKeyDataUrl}\n                download=\"private-key.asc\"\n              >\n                <Trans\n                  id=\"openpgp-key.download\"\n                  message=\"Save and keep in a safe place.\"\n                />\n                {' '}\n                {/* @ts-ignore */}\n                <Icon type=\"download\" />\n              </Link>\n              {editMode && (\n                // @ts-ignore\n                <Button color=\"alert\" onClick={handleDeleteKey}>\n                  {/* @ts-ignore */}\n                  <Icon type=\"remove\" />\n                  <span className=\"show-for-sr\">\n                    <Trans id=\"openpgp.action.remove-key\" message=\"Remove\" />\n                  </span>\n                </Button>\n              )}\n            </div>\n          </>\n        )}\n      </div>\n\n      {keyDetails && (\n        <div className=\"m-openpgp-key__summary\">\n          <span>{keyDetails?.userIds.join(', ')}</span>\n          {' '}\n          {keyDetails?.created && (\n            <Moment format=\"ll\" locale={locale}>\n              {keyDetails.created}\n            </Moment>\n          )}\n          {' '}\n          {keyDetails.expirationTime && keyDetails.expirationTime.length && (\n            <span>\n              {'/ '}\n              <Moment format=\"LL\" locale={locale}>\n                {keyDetails.expirationTime}\n              </Moment>\n            </span>\n          )}\n          {' '}\n          {keyDetails.keyStatus && openpgpStatuses[keyDetails.keyStatus]}\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport default withI18n()(OpenPGPKey);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/OpenPGPKey/style.scss",
    "content": "@import '../../../../styles/common';\n\n.m-openpgp-key {\n  margin-bottom: 1px;\n  padding: map_get($co-form__spacing, 'small');\n  background: $co-color__fg__back;\n\n  &__main {\n    display: flex;\n    line-height: $co-component__height;\n  }\n\n  &__icon {\n    flex: 0 1;\n    margin-right: calc($co-margin / 4);\n  }\n\n  &__fingerprint {\n    flex: 1 0;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n\n  &__actions {\n    flex: 0 1;\n    text-align: right;\n    white-space: nowrap;\n  }\n\n  &__detail {\n    margin-bottom: $co-margin;\n  }\n\n  &__summary {\n    font-style: italic;\n  }\n\n  &__toggle-info--warning {\n    color: $co-color__warning;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/OpenPGPPrivateKeys/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport { useDispatch } from 'react-redux';\nimport { toPairs } from 'lodash';\nimport { Button } from 'src/components';\nimport { useUser } from 'src/modules/user';\nimport {\n  getPrimaryKeysByFingerprint,\n  deleteKey,\n} from 'src/services/openpgp-keychain-repository';\n\nimport OpenPGPKey from '../OpenPGPKey';\nimport OpenPGPGenerateForm from '../OpenPGPGenerateForm';\nimport OpenPGPImportForm from '../OpenPGPImportForm';\nimport './style.scss';\n\nconst FORM_TYPE_GENERATE = 'generate';\nconst FORM_TYPE_IMPORT = 'import';\ntype FormType = typeof FORM_TYPE_GENERATE | typeof FORM_TYPE_IMPORT;\n\n// cf. getPrimaryKeysByFingerprint\ntype KeysByFingerPrint = {\n  [fingerprint: string]: {\n    publicKeyArmored?: string;\n    privateKeyArmored?: string;\n  };\n};\n\nfunction OpenPGPPrivateKeys(): React.ReactNode {\n  const [keys, setKeys] = React.useState<KeysByFingerPrint>();\n  const [editMode, setEditMode] = React.useState(false);\n  const [formType, setFormType] = React.useState<FormType>(FORM_TYPE_GENERATE);\n\n  const loadKeys = () => {\n    getPrimaryKeysByFingerprint().then(setKeys);\n  };\n\n  React.useEffect(() => {\n    loadKeys();\n  }, []);\n\n  const handleKeyAddedSuccess = () => {\n    setEditMode(false);\n    loadKeys();\n  };\n\n  const handleClickEditMode = () => {\n    setEditMode((prev) => !prev);\n  };\n\n  const handleDeleteKey = async (fingerprint) => {\n    await deleteKey(fingerprint);\n    loadKeys();\n  };\n\n  const handleSwitchFormType = (event) => {\n    setFormType(event.target.name);\n  };\n\n  const generateHollowProp =\n    formType === FORM_TYPE_GENERATE ? { shape: 'hollow' } : {};\n  const rawHollowProp =\n    formType === FORM_TYPE_IMPORT ? { shape: 'hollow' } : {};\n\n  return (\n    <div className=\"m-account-openpgp\">\n      {toPairs(keys || []).map(([fingerprint, key]) => (\n        <OpenPGPKey\n          key={fingerprint}\n          className=\"m-account-openpgp__keys\"\n          publicKeyArmored={key.publicKeyArmored || ''}\n          privateKeyArmored={key.privateKeyArmored || ''}\n          editMode={editMode}\n          onDeleteKey={handleDeleteKey}\n        />\n      ))}\n      {editMode ? (\n        <>\n          <div className=\"m-account-openpgp__switch-mode-btns\">\n            {/* @ts-ignore */}\n            <Button\n              onClick={handleSwitchFormType}\n              name={FORM_TYPE_GENERATE}\n              {...generateHollowProp}\n            >\n              <Trans\n                id=\"user.openpgp.action.switch-generate-key\"\n                message=\"Generate key\"\n              />\n            </Button>{' '}\n            {/* @ts-ignore */}\n            <Button\n              onClick={handleSwitchFormType}\n              name={FORM_TYPE_IMPORT}\n              {...rawHollowProp}\n            >\n              <Trans\n                id=\"user.openpgp.action.switch-import-raw-key\"\n                message=\"Import key\"\n              />\n            </Button>\n          </div>\n          <div className=\"m-account-openpgp__form\">\n            {formType === FORM_TYPE_GENERATE && (\n              <OpenPGPGenerateForm\n                cancel={handleClickEditMode}\n                onSuccess={handleKeyAddedSuccess}\n              />\n            )}\n            {formType === FORM_TYPE_IMPORT && (\n              <OpenPGPImportForm\n                cancel={handleClickEditMode}\n                onSuccess={handleKeyAddedSuccess}\n              />\n            )}\n          </div>\n        </>\n      ) : (\n        <div className=\"m-account-openpgp__toggle-edit\">\n          {/* @ts-ignore */}\n          <Button onClick={handleClickEditMode} shape=\"plain\" icon=\"plus\">\n            <Trans\n              id=\"user.openpgp.action.edit-keys\"\n              message=\"Edit and add keys\"\n            />\n          </Button>\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport default OpenPGPPrivateKeys;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/OpenPGPPrivateKeys/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-callout';\n\n.m-account-openpgp {\n  &__info {\n    @include o-callout-base;\n    @include o-callout--color($co-color__info);\n  }\n\n  &__switch-mode-btns {\n    display: inline-block;\n    margin-top: $co-margin;\n    line-height: $co-component__height;\n  }\n\n  &__keys,\n  &__form,\n  &__toggle-edit {\n    margin-top: $co-margin;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/PasswordDetails/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { PasswordStrength, Button, TextBlock } from '../../../../components';\nimport PasswordForm from '../PasswordForm';\nimport './style.scss';\n\nfunction generateStateFromProps(props, prevState) {\n  return {\n    ...prevState,\n    editMode: prevState.editMode && !props.updated,\n  };\n}\n\nclass PasswordDetails extends Component {\n  static propTypes = {\n    user: PropTypes.shape({}),\n    onSubmit: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    user: undefined,\n  };\n\n  state = {\n    editMode: false,\n  };\n\n  UNSAFE_componentWillMount() {\n    this.setState((prevState) => generateStateFromProps(this.props, prevState));\n  }\n\n  UNSAFE_componentWillReceiveProps(newProps) {\n    this.setState((prevState) => generateStateFromProps(newProps, prevState));\n  }\n\n  toggleEditMode = () => {\n    this.setState((prevState) => ({ editMode: !prevState.editMode }));\n  };\n\n  render() {\n    const { user, onSubmit } = this.props;\n    // privacy_features.password_strength is a string\n    const passwordStrengthNumber = user\n      ? Number(user.privacy_features.password_strength)\n      : 0;\n\n    return (\n      <div className=\"m-password-details\">\n        {!this.state.editMode && (\n          <TextBlock className=\"m-password-details__title\">\n            <Trans\n              id=\"password.details.password_strength.title\"\n              message=\"Password strength:\"\n            />\n          </TextBlock>\n        )}\n        {this.state.editMode ? (\n          <div className=\"m-password-details__form\">\n            <PasswordForm onSubmit={onSubmit} onCancel={this.toggleEditMode} />\n          </div>\n        ) : (\n          <PasswordStrength\n            className=\"m-password-details__strength\"\n            strength={passwordStrengthNumber}\n          />\n        )}\n        {!this.state.editMode && (\n          <div className=\"m-password-details__action\">\n            <Button onClick={this.toggleEditMode}>\n              <Trans id=\"password.details.action.change\" message=\"Change\" />\n            </Button>\n          </div>\n        )}\n      </div>\n    );\n  }\n}\n\nexport default PasswordDetails;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/PasswordDetails/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-password-details {\n  @include flex-grid-row($size: expand);\n\n  &__title {\n    @include flex-grid-size(12);\n\n    margin-bottom: $co-margin;\n  }\n\n  &__strength {\n    @include flex-grid-size;\n  }\n\n  &__form {\n    @include flex-grid-size(12);\n  }\n\n  &__action {\n    @include flex-grid-size(shrink);\n  }\n\n  @include breakpoint(medium) {\n    &__strength {\n      @include flex-grid-size;\n    }\n\n    &__action {\n      @include flex-grid-column(shrink);\n    }\n  }\n\n  @include breakpoint(large) {\n    &__strength {\n      @include flex-grid-size(8);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/PasswordForm/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n, Trans } from '@lingui/react';\nimport {\n  TextFieldGroup,\n  Button,\n  PasswordStrength,\n  FormGrid,\n  FormColumn,\n  FormRow,\n} from '../../../../components';\nimport './style.scss';\n\n@withI18n()\nclass PasswordForm extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    onCancel: PropTypes.func.isRequired,\n    onSubmit: PropTypes.func.isRequired,\n  };\n\n  state = {\n    passwordError: [],\n    passwordConfirmation: '',\n    passwordStrength: '',\n    formValues: {\n      password: '',\n      newPassword: '',\n      // tfa: '',\n    },\n  };\n\n  componentDidMount() {\n    import(/* webpackChunkName: \"zxcvbn\" */ 'zxcvbn').then(\n      ({ default: zxcvbn }) => {\n        this.zxcvbn = zxcvbn;\n      }\n    );\n  }\n\n  calcPasswordStrengh = () => {\n    if (this.zxcvbn) {\n      this.setState((prevState) => {\n        const { newPassword } = prevState.formValues;\n        const passwordStrength = !newPassword.length\n          ? ''\n          : this.zxcvbn(newPassword).score;\n\n        return {\n          ...prevState,\n          passwordStrength,\n        };\n      });\n    }\n  };\n\n  handleInputChange = (event) => {\n    const { name, value } = event.target;\n    this.setState((prevState) => ({\n      formValues: {\n        ...prevState.formValues,\n        [name]: value,\n      },\n    }));\n  };\n\n  handleNewPasswordChange = (event) => {\n    this.handleInputChange(event);\n    this.calcPasswordStrengh();\n  };\n\n  handleConfirmPasswordChange = (event) => {\n    const { i18n } = this.props;\n    const { value } = event.target;\n\n    this.setState((prevState) => {\n      const { newPassword } = prevState.formValues;\n      const error = i18n._(\n        /* i18n */ 'password.form.new_password_confirmation.error',\n        null,\n        { message: \"Passwords don't match\" }\n      );\n      const passwordError = newPassword === value ? [] : [error];\n\n      return {\n        ...prevState,\n        passwordConfirmation: value,\n        passwordError,\n      };\n    });\n  };\n\n  handleSubmit = () => {\n    const { formValues } = this.state;\n    const user = { password: formValues.newPassword };\n    const original = { password: formValues.password };\n\n    const data = {\n      ...user,\n      current_state: {\n        ...original,\n      },\n    };\n\n    this.props.onSubmit(data);\n  };\n\n  render() {\n    const { i18n, onCancel } = this.props;\n\n    const submitButtonProps = {\n      disabled:\n        this.state.formValues.newPassword !== '' &&\n        this.state.formValues.password !== '' &&\n        this.state.formValues.newPassword === this.state.passwordConfirmation\n          ? null\n          : true,\n    };\n\n    return (\n      <FormGrid className=\"m-password-form\">\n        <FormRow className=\"m-password-form__row\">\n          <FormColumn size=\"medium\">\n            <TextFieldGroup\n              inputProps={{\n                name: 'password',\n                type: 'password',\n                onChange: this.handleInputChange,\n                placeholder: i18n._(\n                  /* i18n */ 'password.form.current_password.placeholder',\n                  null,\n                  { message: 'Enter your current password' }\n                ),\n                required: true,\n              }}\n              label={i18n._(\n                /* i18n */ 'password.form.current_password.label',\n                null,\n                {\n                  message: 'Current password:',\n                }\n              )}\n            />\n          </FormColumn>\n          <FormColumn size=\"medium\">\n            <div className=\"m-password-form__tip\">\n              <Trans\n                id=\"password.form.current_password.tip\"\n                message=\"The password you want to replace.\"\n              />\n            </div>\n          </FormColumn>\n        </FormRow>\n        <FormRow className=\"m-password-form__row\">\n          <FormColumn size=\"medium\">\n            <TextFieldGroup\n              inputProps={{\n                name: 'newPassword',\n                type: 'password',\n                onChange: this.handleNewPasswordChange,\n                placeholder: i18n._(\n                  /* i18n */ 'password.form.new_password.placeholder',\n                  null,\n                  { message: 'Enter new password' }\n                ),\n                required: true,\n              }}\n              label={i18n._(\n                /* i18n */ 'password.form.new_password.label',\n                null,\n                {\n                  message: 'New password:',\n                }\n              )}\n            />\n          </FormColumn>\n          <FormColumn size=\"medium\">\n            <div className=\"m-password-form__tip\">\n              <Trans\n                id=\"password.form.new_password.tip\"\n                message=\"The password you want to use from now.\"\n              />\n            </div>\n          </FormColumn>\n          {this.state.passwordStrength.length !== 0 && (\n            <FormColumn\n              size=\"medium\"\n              bottomSpace\n              className=\"m-password-form__strength\"\n            >\n              <PasswordStrength strength={this.state.passwordStrength} />\n            </FormColumn>\n          )}\n        </FormRow>\n        <FormRow className=\"m-password-form__row\">\n          <FormColumn size=\"medium\">\n            <TextFieldGroup\n              inputProps={{\n                name: 'confirmNewPassword',\n                type: 'password',\n                onChange: this.handleConfirmPasswordChange,\n                placeholder: i18n._(\n                  /* i18n */ 'password.form.new_password_confirmation.placeholder',\n                  null,\n                  { message: 'Password' }\n                ),\n                required: true,\n              }}\n              errors={this.state.passwordError}\n              label={i18n._(\n                /* i18n */ 'password.form.new_password_confirmation.label',\n                null,\n                { message: 'New password confirmation:' }\n              )}\n            />\n          </FormColumn>\n        </FormRow>\n        {/*\n        <FormRow className=\"m-password-form__row\">\n        <FormColumn size=\"medium\">\n          <Field\n            name=\"tfa\"\n            component={ReduxTextFieldGroup}\n            onChange={this.handleInputChange}\n            label={i18n._(/* i18n 'password.form.tfa.label', null, { message: 'TOTP validation code:' })}\n            placeholder={i18n._(/* i18n 'password.form.tfa.placeholder',\n              null, { message: 'Enter 2-auth code' })}\n            disabled\n          />\n        </FormColumn>\n        <FormColumn size=\"medium\">\n          <div className=\"m-password-form__tip\">\n            <Trans id=\"password.form.tfa.tip\" message=\"Only if you have enabled the 2-Factor Authentification method.\" />\n          </div>\n        </FormColumn>\n      </FormRow>\n        */}\n        <FormRow>\n          <FormColumn\n            size=\"medium\"\n            className=\"m-password-form__action\"\n            bottomSpace\n          >\n            <Button\n              shape=\"plain\"\n              display=\"expanded\"\n              onClick={this.handleSubmit}\n              {...submitButtonProps}\n            >\n              <Trans\n                id=\"password.form.action.validate\"\n                message=\"Apply modifications\"\n              />\n            </Button>\n          </FormColumn>\n          <FormColumn size=\"shrink\" className=\"m-password-form__action\">\n            <Button shape=\"hollow\" display=\"expanded\" onClick={onCancel}>\n              <Trans id=\"password.form.action.cancel\" message=\"Cancel\" />\n            </Button>\n          </FormColumn>\n        </FormRow>\n      </FormGrid>\n    );\n  }\n}\n\nexport default PasswordForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/PasswordForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-password-form {\n  &__row {\n    align-items: flex-end;\n    margin-bottom: map_get($co-form__spacing, 'large');\n  }\n\n  &__tip {\n    display: none;\n    font-size: $co-font__size--small;\n  }\n\n  &__action {\n    line-height: $co-component__height;\n  }\n\n  &__strength {\n    margin-top: map_get($co-form__spacing, 'small');\n  }\n\n  @include breakpoint(medium) {\n    &__tip {\n      display: inline-block;\n      height: $co-component__height;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/components/TFAForm/index.jsx",
    "content": "import React, { Component } from 'react';\n// import PropTypes from 'prop-types';\n\nclass TFAForm extends Component {\n  static propTypes = {\n    // i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    // requestUser: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {};\n\n  componentDidMount() {\n    // this.props.requestUser();\n  }\n\n  render() {\n    return <div className=\"m-tfa-form\" />;\n  }\n}\n\nexport default TFAForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/index.jsx",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport { withI18n } from '@lingui/react';\nimport { withUser } from 'src/modules/user';\nimport { requestUser } from 'src/modules/user/store/reducer';\nimport { withNotification } from '../../modules/userNotify';\nimport Presenter from './presenter';\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      requestUser,\n    },\n    dispatch\n  );\n\nexport default compose(\n  connect(undefined, mapDispatchToProps),\n  withI18n(),\n  withUser(),\n  withNotification()\n)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport getClient from '../../services/api-client';\nimport OpenPGPPrivateKeys from './components/OpenPGPPrivateKeys';\nimport { PageTitle, Section } from '../../components';\nimport PasswordDetails from './components/PasswordDetails';\nimport LoginDetails from './components/LoginDetails';\n\nimport './style.scss';\n\nclass UserSecurity extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    requestUser: PropTypes.func.isRequired,\n    userState: PropTypes.shape({ user: PropTypes.shape({}) }).isRequired,\n    notifySuccess: PropTypes.func.isRequired,\n    notifyError: PropTypes.func.isRequired,\n  };\n\n  state = {\n    updated: false,\n  };\n\n  componentDidMount() {\n    this.saveKey =\n      /* eslint-disable-next-line */\n      require('../../services/openpgp-keychain-repository').saveKey;\n\n    this.props.requestUser();\n  }\n\n  handleSubmit = (data) => {\n    const { user } = this.props;\n    getClient()\n      .patch(`/api/v2/users/${user.user_id}`, {\n        ...data,\n      })\n      .then(this.handleSubmitSuccess, this.handleSubmitError);\n  };\n\n  handleSubmitSuccess = () => {\n    this.setState({ updated: true }, () => {\n      const { requestUser, notifySuccess, i18n } = this.props;\n      notifySuccess({\n        message: i18n._(/* i18n */ 'password.form.feedback.successfull', null, {\n          message: 'Password updated!',\n        }),\n        duration: 0,\n      });\n      requestUser();\n    });\n  };\n\n  handleSubmitError = ({ response }) => {\n    const { notifyError, i18n } = this.props;\n\n    if (response.status === 424) {\n      return notifyError({\n        message: i18n._(\n          /* i18n */ 'password.form.feedback.error-old-password',\n          null,\n          {\n            message: 'Wrong old password.',\n          }\n        ),\n        duration: 0,\n      });\n    }\n\n    return notifyError({\n      message: i18n._(\n        /* i18n */ 'password.form.feedback.unexpected-error',\n        null,\n        {\n          message: 'Error when updating password.',\n        }\n      ),\n      duration: 0,\n    });\n  };\n\n  render() {\n    const {\n      i18n,\n      userState: { user },\n    } = this.props;\n\n    return (\n      <div className=\"s-user-account-security\">\n        <PageTitle />\n        <form method=\"post\" name=\"user_security_form\">\n          <Section\n            title={i18n._(\n              /* i18n */ 'user.security.section_password.title',\n              null,\n              {\n                message: 'Customize your interface',\n              }\n            )}\n          >\n            <div className=\"s-user-account-security__credentials\">\n              <div className=\"s-user-account-security__login\">\n                <LoginDetails user={user} />\n              </div>\n              <div className=\"s-user-account-security__password\">\n                <PasswordDetails\n                  user={user}\n                  updated={this.state.updated}\n                  onSubmit={this.handleSubmit}\n                />\n              </div>\n            </div>\n          </Section>\n        </form>\n        {/* TODO: enable TFA section\n          <Section\n          title={i18n._(/* i18n 'user.security.section_tfa.title',\n            null, { message: '2-factor authentication' })}\n          >\n            <TFAForm user={user} />\n          </Section>\n          */}\n        <Section\n          title={i18n._(\n            /* i18n */ 'user.security.section_pgpkeys.title',\n            null,\n            {\n              message: 'PGP private keys',\n            }\n          )}\n        >\n          <OpenPGPPrivateKeys />\n        </Section>\n      </div>\n    );\n  }\n}\n\nexport default UserSecurity;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/UserSecurity/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-user-account-security {\n  &__credentials {\n    @include flex-grid-row;\n  }\n\n  &__login,\n  &__password {\n    @include flex-grid-size(12);\n\n    margin-bottom: $co-margin;\n  }\n\n  @include breakpoint(medium) {\n    &__login,\n    &__password {\n      @include flex-grid-column(6);\n\n      margin-bottom: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ValidateDevice/index.js",
    "content": "import { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport Presenter from './presenter';\nimport { requestDeviceVerification } from '../../store/modules/device';\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      requestDeviceVerification,\n    },\n    dispatch\n  );\n\nexport default compose(connect(null, mapDispatchToProps))(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ValidateDevice/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { Trans } from '@lingui/react';\nimport { Spinner, Link, Section } from '../../components';\nimport './style.scss';\n\nconst STATUS_IN_PROGRESS = 'in_progress';\nconst STATUS_SUCCEED = 'succeed';\nconst STATUS_FAILED = 'failed';\n\nclass ValidateDevice extends Component {\n  static propTypes = {\n    className: PropTypes.string,\n    match: PropTypes.shape({\n      params: PropTypes.shape({\n        token: PropTypes.string,\n      }).isRequired,\n    }).isRequired,\n    requestDeviceVerification: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    className: undefined,\n  };\n\n  state = {\n    status: undefined,\n  };\n\n  componentDidMount() {\n    this.init();\n  }\n\n  init = async () => {\n    if (this.state.status !== undefined) {\n      return;\n    }\n\n    try {\n      const {\n        requestDeviceVerification,\n        match: {\n          params: { token },\n        },\n      } = this.props;\n      this.setState({\n        status: STATUS_IN_PROGRESS,\n      });\n      await requestDeviceVerification({ token });\n      this.setState({\n        status: STATUS_SUCCEED,\n      });\n    } catch (e) {\n      this.setState({\n        status: STATUS_FAILED,\n      });\n    }\n  };\n\n  renderVerification() {\n    switch (this.state.status) {\n      default:\n      case STATUS_IN_PROGRESS:\n        return (\n          <Section\n            className=\"s-validate-device__feedback\"\n            borderContext=\"disabled\"\n          >\n            <Spinner svgTitleId=\"device-verification-spinner\" isLoading />{' '}\n            <Trans id=\"device.validation.in_progress\" message=\"In progress.\" />\n          </Section>\n        );\n      case STATUS_SUCCEED:\n        return (\n          <Section className=\"s-validate-device__feedback\" borderContext=\"safe\">\n            <Trans\n              id=\"device.validation.suceed\"\n              message=\"The device is now verified, you can continue to use <0>Caliopen</0>\"\n              components={[<Link to=\"/\" />]}\n            />\n          </Section>\n        );\n      case STATUS_FAILED:\n        return (\n          <Section\n            className=\"s-validate-device__feedback\"\n            borderContext=\"unsecure\"\n          >\n            <Trans\n              id=\"device.validation.failed\"\n              message={`\n              The device cannot be verified, the validation link might not be\n              valid anymore or may be the device has been revoked.\n              <0 />\n              You can send the verification link from\n              <1>the device list</1>.\n              `}\n              components={[<br />, <Link to=\"/settings/devices\" />]}\n            />\n          </Section>\n        );\n    }\n  }\n\n  render() {\n    const { className } = this.props;\n\n    return (\n      <div className={classnames(className, 's-validate-device')}>\n        {this.renderVerification()}\n      </div>\n    );\n  }\n}\n\nexport default ValidateDevice;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/ValidateDevice/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-validate-device {\n  @include flex-grid-row($size: expand);\n\n  padding: calc($co-margin / 2);\n\n  &__feedback {\n    @include flex-grid-size(12);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/View/components/MessageItem/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/View/components/MessageItem/presenter.jsx",
    "content": "import React, { Component, Fragment } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport Moment from 'react-moment';\nimport { Trans, withI18n } from '@lingui/react';\nimport { AuthorAvatarLetter, SIZE_SMALL } from '../../../../modules/avatar';\nimport { ParticipantLabel } from '../../../../modules/message';\nimport { withSettings } from '../../../../modules/settings';\nimport { getTagLabel, getCleanedTagCollection } from '../../../../modules/tags';\nimport { withUser } from '../../../../modules/user';\n\nimport MessageDate from '../../../../components/MessageDate';\nimport { Badge, Link, Checkbox, Icon, TextBlock } from '../../../../components';\n\nimport './style.scss';\n\n@withSettings()\n@withI18n()\n@withUser()\nclass MessageItem extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    className: PropTypes.string,\n    userState: PropTypes.shape({\n      user: PropTypes.shape({}).isRequired,\n    }).isRequired,\n    message: PropTypes.shape({}).isRequired,\n    settings: PropTypes.shape({}).isRequired,\n    userTags: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n    onToggleSelectMessage: PropTypes.func.isRequired,\n    isMessageSelected: PropTypes.bool,\n    isDeleting: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    className: undefined,\n    isMessageSelected: false,\n    isDeleting: false,\n  };\n\n  onCheckboxChange = () => {\n    const { message, onToggleSelectMessage } = this.props;\n\n    onToggleSelectMessage({ message });\n  };\n\n  getParticipantsExceptUser = () => {\n    const {\n      message,\n      userState: { user },\n    } = this.props;\n\n    return message.participants.filter(\n      (participant) =>\n        !(\n          participant.contact_ids &&\n          participant.contact_ids.some(\n            (contactId) => contactId === user.contact.contact_id\n          )\n        )\n    );\n  };\n\n  renderParticipants() {\n    const participants = this.getParticipantsExceptUser();\n\n    return participants.map((participant, i) => (\n      <Fragment key={participant.address}>\n        {i > 0 && ', '}\n        <ParticipantLabel participant={participant} />\n      </Fragment>\n    ));\n  }\n\n  renderDate = () => {\n    const {\n      message,\n      settings: { default_locale: locale },\n    } = this.props;\n\n    return (\n      <TextBlock>\n        {this.renderType()}{' '}\n        <Moment locale={locale} element={MessageDate}>\n          {message.date_sort}\n        </Moment>\n      </TextBlock>\n    );\n  };\n\n  renderTags() {\n    const { userTags, message, i18n } = this.props;\n\n    return (\n      message.tags && (\n        <ul className=\"s-message-item__tags\">\n          {getCleanedTagCollection(userTags, message.tags).map((tag) => (\n            <li key={tag.name} className=\"s-message-item__tag\">\n              <Badge>{getTagLabel(i18n, tag)}</Badge>\n            </li>\n          ))}\n        </ul>\n      )\n    );\n  }\n\n  renderContent = () => {\n    const { message } = this.props;\n    const { attachments } = message;\n\n    return (\n      <Link\n        className={classnames('s-message-item__content', {\n          's-message-item__content--draft': message.is_draft,\n        })}\n        to={`/messages/${message.message_id}`}\n        noDecoration\n      >\n        <TextBlock className=\"s-message-item__participants\">\n          {this.renderParticipants()}\n        </TextBlock>\n        <TextBlock className=\"s-message-item__title\">\n          {message.is_draft && (\n            <span className=\"s-message-item__draft-prefix\">\n              <Trans id=\"timeline.draft-prefix\" message=\"Draft in progress:\" />{' '}\n            </span>\n          )}\n          {message.subject && (\n            <span className=\"s-message-item__subject\">{message.subject} </span>\n          )}\n          <span className=\"s-message-item__excerpt\">{message.excerpt}</span>\n        </TextBlock>\n        {attachments && attachments.length !== 0 && (\n          <span className=\"s-message-item__file\">\n            <Icon type=\"paperclip\" />\n          </span>\n        )}\n        {this.renderTags()}\n      </Link>\n    );\n  };\n\n  renderType = () => {\n    const { i18n, message } = this.props;\n    const typeTranslations = {\n      email: i18n._(/* i18n */ 'message-list.message.protocol.email', null, {\n        message: 'email',\n      }),\n    };\n\n    const messageType = message.type && typeTranslations[message.type];\n\n    return (\n      message.type && (\n        <span className=\"s-message-item__type\">\n          <Icon\n            type={message.type}\n            spaced\n            className=\"s-message-item__type-icon\"\n          />\n          <span className=\"s-message-item__type-label sr-only\">\n            {messageType}{' '}\n            <Trans\n              id=\"message-list.message.received-on\"\n              message=\"received on\"\n            />\n          </span>\n        </span>\n      )\n    );\n  };\n\n  render() {\n    const { className, i18n, message, isMessageSelected, isDeleting } =\n      this.props;\n\n    return (\n      <div\n        className={classnames(className, 's-message-item', {\n          's-message-item--unread': message.is_unread,\n          's-message-item--draft': message.is_draft,\n          's-message-item--is-selected': isMessageSelected,\n          // TODO: define how to compute PIs for rendering\n          // 's-message-item--pi-super': pi.context >= 90,\n          // 's-message-item--pi-good': pi.context >= 50 && pi.context < 90,\n          // 's-message-item--pi-bad': pi.context >= 25 && pi.context < 50,\n          // 's-message-item--pi-ugly': pi.context >= 0 && pi.context < 25,\n        })}\n      >\n        <div className=\"s-message-item__col-avatar\">\n          <AuthorAvatarLetter size={SIZE_SMALL} message={message} />\n        </div>\n        <div className=\"s-message-item__col-content\">\n          {this.renderContent()}\n        </div>\n        <div className=\"s-message-item__col-date\">{this.renderDate()}</div>\n        <div className=\"s-message-item__col-select\">\n          <Checkbox\n            label={i18n._(\n              /* i18n */ 'message-list.action.select_single_message',\n              null,\n              {\n                message: 'Select/deselect this message',\n              }\n            )}\n            onChange={this.onCheckboxChange}\n            id={message.message_id}\n            checked={isMessageSelected}\n            disabled={isDeleting}\n            showLabelforSr\n          />\n        </div>\n      </div>\n    );\n  }\n}\n\nexport default MessageItem;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/View/components/MessageItem/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n$s-message-item__pi-width: 7px;\n\n.s-message-item {\n  @include flex-grid-row($size: expand);\n\n  position: relative;\n  align-items: center;\n  min-height: $co-component__height;\n  padding-top: $co-component__spacing;\n  padding-bottom: $co-component__spacing;\n  background-color: $co-color__fg__back--low;\n  font-size: $co-font__size--small;\n\n  &::before {\n    display: block;\n    content: '';\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    width: $s-message-item__pi-width;\n    background: map_get($privacy-palette, 'disabled');\n  }\n\n  &--pi-ugly {\n    &::before {\n      background: map_get($privacy-palette, 'ugly');\n    }\n  }\n\n  &--pi-bad {\n    &::before {\n      background: map_get($privacy-palette, 'bad');\n    }\n  }\n\n  &--pi-good {\n    &::before {\n      background: map_get($privacy-palette, 'good');\n    }\n  }\n\n  &--pi-super {\n    &::before {\n      background: map_get($privacy-palette, 'super');\n    }\n  }\n\n  &--draft,\n  &--unread {\n    position: relative;\n    background-color: $co-color__fg__back;\n  }\n\n  &--is-selected {\n    background-color: $co-color__bg__back--high;\n  }\n\n  &__col-avatar {\n    @include flex-grid-column(shrink);\n\n    margin-left: $s-message-item__pi-width;\n  }\n\n  &__col-content {\n    @include flex-grid-size;\n\n    min-width: 0; // https://github.com/zurb/foundation-sites/pull/8511\n  }\n\n  &__col-date {\n    @include flex-grid-column(shrink);\n\n    color: $co-color__fg__text--higher;\n    text-align: right;\n  }\n\n  &__col-select {\n    display: none;\n    @include flex-grid-column(shrink);\n\n    margin-left: auto;\n  }\n\n  &__content {\n    @include flex-grid-row;\n\n    align-items: center;\n    min-height: $co-component__height;\n    color: $co-color__fg__text--higher;\n    font-weight: 400;\n\n    &--draft {\n      font-style: italic;\n    }\n  }\n\n  &__participants {\n    @include flex-grid-size(12);\n\n    color: $co-color__primary--lower;\n    font-weight: 600;\n  }\n\n  &__title {\n    @include flex-grid-size;\n\n    margin-right: $co-component__spacing;\n  }\n\n  &__subject {\n    color: $co-color__fg__text--low;\n  }\n\n  &__file {\n    @include flex-grid-column(shrink);\n  }\n\n  &__tags {\n    @include flex-grid-size(shrink);\n  }\n\n  &__tag {\n    display: inline-block;\n    margin-right: $co-component__spacing / 2;\n\n    &:last-child {\n      margin-right: 0;\n    }\n  }\n\n  @include breakpoint(medium) {\n    padding-top: 0;\n    padding-bottom: 0;\n\n    &__col-select {\n      display: flex;\n    }\n\n    &__col-content {\n      @include flex-grid-size;\n    }\n\n    &__author {\n      @include flex-grid-size(2);\n\n      min-width: 220px;\n    }\n\n    &__tags {\n      @include flex-grid-size(shrink);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/View/components/MessageSelector/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n, Trans } from '@lingui/react';\nimport {\n  Checkbox,\n  Spinner,\n  Confirm,\n  ActionBarButton,\n} from '../../../../components';\n\nimport './style.scss';\n\n@withI18n()\nclass MessageSelector extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    onToggleSelectAllMessages: PropTypes.func,\n    onEditTags: PropTypes.func,\n    onDeleteMessages: PropTypes.func,\n    count: PropTypes.number,\n    totalCount: PropTypes.number,\n    indeterminate: PropTypes.bool,\n    checked: PropTypes.bool,\n    isDeleting: PropTypes.bool.isRequired,\n  };\n\n  static defaultProps = {\n    onToggleSelectAllMessages: (str) => str,\n    onEditTags: (str) => str,\n    onDeleteMessages: (str) => str,\n    count: 0,\n    totalCount: 0,\n    indeterminate: false,\n    checked: false,\n  };\n\n  toggleCheckbox = () => {\n    this.props.onToggleSelectAllMessages();\n  };\n\n  handleEditTags = () => {\n    this.props.onEditTags();\n  };\n\n  handleDelete = () => {\n    this.props.onDeleteMessages();\n  };\n\n  renderDeleteButton() {\n    const { i18n, count, isDeleting } = this.props;\n\n    if (count === 0) {\n      return (\n        <ActionBarButton\n          icon={\n            isDeleting ? (\n              <Spinner\n                svgTitleId=\"delete-messages-spinner\"\n                isLoading\n                display=\"inline\"\n              />\n            ) : (\n              'trash'\n            )\n          }\n          disabled\n          aria-label={i18n._(/* i18n */ 'message-list.action.delete', null, {\n            message: 'Delete selected',\n          })}\n        />\n      );\n    }\n\n    return (\n      <Confirm\n        onConfirm={this.handleDelete}\n        title={\n          <Trans\n            id=\"message-list.confirm-delete.title\"\n            message=\"Delete message(s)\"\n          />\n        }\n        content={\n          <Trans id=\"message-list.confirm-delete.content\">\n            The deletion is permanent, are you sure you want to delete these\n            messages?\n          </Trans>\n        }\n        render={(confirm) => (\n          <ActionBarButton\n            icon={\n              isDeleting ? (\n                <Spinner\n                  svgTitleId=\"delete-messages-spinner\"\n                  isLoading\n                  display=\"inline\"\n                />\n              ) : (\n                'trash'\n              )\n            }\n            onClick={confirm}\n            aria-label={i18n._(/* i18n */ 'message-list.action.delete', null, {\n              message: 'Delete selected',\n            })}\n          />\n        )}\n      />\n    );\n  }\n\n  render() {\n    const { i18n, count, totalCount, checked, isDeleting, indeterminate } =\n      this.props;\n\n    return (\n      <div className=\"m-message-selector\">\n        {count !== 0 && (\n          <span className=\"m-message-selector__actions\">\n            {this.renderDeleteButton()}\n          </span>\n        )}\n        {count !== 0 && (\n          <span className=\"m-message-selector__title\">\n            <Trans\n              id=\"message-list.selected\"\n              values={{ count, totalCount }}\n              message=\"{count, plural, one {#/{totalCount} message:} other {#/{totalCount} messages:}}\"\n            />\n          </span>\n        )}\n        <span className=\"m-message-selector__checkbox\">\n          <Checkbox\n            label={i18n._(/* i18n */ 'message-list.action.select_all', null, {\n              message: 'Select/deselect all messages',\n            })}\n            id=\"message-list-selector\"\n            checked={checked}\n            indeterminate={indeterminate}\n            onChange={this.toggleCheckbox}\n            disabled={isDeleting}\n            showLabelforSr\n          />\n        </span>\n      </div>\n    );\n  }\n}\n\nexport default MessageSelector;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/View/components/MessageSelector/style.scss",
    "content": "@import '../../../../styles/common';\n\n.m-message-selector {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  max-width: $co-site-width;\n  height: $co-component__height;\n  margin: 0 auto;\n\n  &__checkbox {\n    margin: auto $co-component__spacing;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/View/index.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport classnames from 'classnames';\nimport { bindActionCreators } from 'redux';\nimport { connect } from 'react-redux';\nimport { ActionBarWrapper, ActionBar } from '../../components';\nimport { ScrollDetector } from '../../modules/scroll';\nimport { deleteMessage } from '../../modules/message';\nimport { withTags } from '../../modules/tags';\nimport { withCurrentView } from './withCurrentView';\nimport MessageItem from './components/MessageItem';\nimport MessageSelector from './components/MessageSelector';\nimport './style.scss';\n\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      deleteMessage,\n    },\n    dispatch\n  );\nconst connecting = connect(null, mapDispatchToProps);\n\n@connecting\n@withTags()\n@withCurrentView()\nclass View extends Component {\n  static propTypes = {\n    className: PropTypes.string,\n    deleteMessage: PropTypes.func.isRequired,\n    messages: PropTypes.arrayOf(PropTypes.shape({})),\n    tags: PropTypes.arrayOf(PropTypes.shape({})),\n    isFetching: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    className: undefined,\n    messages: undefined,\n    tags: undefined,\n    isFetching: false,\n  };\n\n  state = {\n    selectedMessages: [],\n    isDeleting: false,\n  };\n\n  handleToggleSelectMessage = ({ message }) => {\n    this.setState((prevState) => {\n      if (prevState.selectedMessages.includes(message)) {\n        return {\n          selectedMessages: prevState.selectedMessages.filter(\n            (msg) => msg !== message\n          ),\n        };\n      }\n\n      return {\n        selectedMessages: [...prevState.selectedMessages, message],\n      };\n    });\n  };\n\n  handleToggleSelectAllMessages = () => {\n    const { messages } = this.props;\n\n    this.setState((prevState) => ({\n      selectedMessages: prevState.selectedMessages.length > 0 ? [] : messages,\n    }));\n  };\n\n  handleDeleteMessages = async () => {\n    this.setState({ isDeleting: true });\n    try {\n      await Promise.all(\n        this.state.selectedMessages.map((message) =>\n          this.props.deleteMessage({ message })\n        )\n      );\n    } finally {\n      this.setState({ isDeleting: false, selectedMessages: [] });\n    }\n  };\n\n  renderActionBar() {\n    const { isFetching, messages } = this.props;\n    const nbSelectedMessages = this.state.selectedMessages.length;\n\n    return (\n      <ScrollDetector\n        offset={136}\n        render={(isSticky) => (\n          <ActionBarWrapper isSticky={isSticky}>\n            <ActionBar\n              hr={false}\n              isLoading={isFetching}\n              actionsNode={\n                <div className=\"s-view-action-bar\">\n                  <MessageSelector\n                    count={nbSelectedMessages}\n                    checked={\n                      nbSelectedMessages > 0 &&\n                      nbSelectedMessages === messages.length\n                    }\n                    totalCount={messages.length}\n                    onToggleSelectAllMessages={\n                      this.handleToggleSelectAllMessages\n                    }\n                    onDeleteMessages={this.handleDeleteMessages}\n                    isDeleting={this.state.isDeleting}\n                    indeterminate={\n                      nbSelectedMessages > 0 &&\n                      nbSelectedMessages < messages.length\n                    }\n                  />\n                </div>\n              }\n            />\n          </ActionBarWrapper>\n        )}\n      />\n    );\n  }\n\n  render() {\n    const { className, messages, tags } = this.props;\n\n    return (\n      <div className={classnames(className)}>\n        {this.renderActionBar()}\n        <div>\n          {messages.map((message) => (\n            <MessageItem\n              key={message.message_id}\n              message={message}\n              className=\"s-view__message\"\n              userTags={tags}\n              isMessageSelected={this.state.selectedMessages.includes(message)}\n              onToggleSelectMessage={this.handleToggleSelectMessage}\n              isDeleting={this.state.isDeleting}\n            />\n          ))}\n        </div>\n      </div>\n    );\n  }\n}\n\nexport default View;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/View/style.scss",
    "content": "@import '../../styles/common';\n\n.s-view {\n  &__message {\n    border-top: 2px solid $co-color__fg__border--higher;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/View/withCurrentView.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { View, WithViewModel, requestMessages } from '../../modules/view';\nimport { withRouteParams } from '../../modules/routing';\nimport { getModuleStateSelector } from '../../store/selectors/getModuleStateSelector';\n\nconst withViewModel = () => (C) =>\n  withRouteParams()(({ routeParams: { viewId }, ...props }) => (\n    <WithViewModel\n      viewId={viewId}\n      render={({ view }) => <C view={view} {...props} />}\n    />\n  ));\n\nconst viewModelSelector = (state, { view }) => view;\n\nconst viewSelector = createSelector(\n  [viewModelSelector, getModuleStateSelector('view')],\n  (viewModel, { viewById }) => viewById[viewModel.id]\n);\n\nconst mapStateToProps = createSelector(\n  [viewModelSelector, getModuleStateSelector('message'), viewSelector],\n  (view, { messagesById }, viewState) => ({\n    view,\n    isFetching: viewState && viewState.isFetching,\n    messages: Object.keys(messagesById)\n      .map((messageId) => messagesById[messageId])\n      .filter((message) => view.has({ message }))\n      .sort((a, b) => (new Date(a.date_sort) - new Date(b.date_sort)) * -1),\n  })\n);\n\nconst mapDispatchToProps = (dispatch, ownProps) =>\n  bindActionCreators(\n    {\n      requestMessages: requestMessages.bind(null, { view: ownProps.view }),\n    },\n    dispatch\n  );\n\nconst connecting = compose(\n  withViewModel(),\n  connect(mapStateToProps, mapDispatchToProps)\n);\n\nexport const withCurrentView = () => (C) => {\n  @connecting\n  class WithCurrentView extends Component {\n    static propTypes = {\n      requestMessages: PropTypes.func.isRequired,\n      messages: PropTypes.arrayOf(PropTypes.shape({})),\n      isFetching: PropTypes.bool,\n      view: PropTypes.instanceOf(View),\n    };\n\n    static defaultProps = {\n      messages: [],\n      isFetching: false,\n    };\n\n    componentDidMount() {\n      this.fetchMessages();\n    }\n\n    // componentDidUpdate(prevProps) {\n    //   // if (prevProps.isFetching) {}\n    // }\n\n    fetchMessages = () => {\n      const { view, isFetching } = this.props;\n\n      if (!isFetching) {\n        this.props.requestMessages({ view });\n      }\n    };\n\n    render() {\n      const { view, messages, isFetching, ...props } = this.props;\n\n      const withProps = {\n        view,\n        messages,\n        isFetching,\n      };\n\n      return <C {...withProps} {...props} />;\n    }\n  }\n\n  return WithCurrentView;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/Contact.tsx",
    "content": "import { Trans } from '@lingui/react';\nimport { AxiosResponse } from 'axios';\nimport * as React from 'react';\nimport { useQuery } from 'react-query';\nimport { useParams } from 'react-router-dom';\nimport {\n  Icon,\n  PlaceholderBlock,\n  TextBlock,\n  TextItem,\n  TextList,\n  Title,\n} from 'src/components';\nimport { ContactAvatarLetter } from 'src/modules/avatar';\nimport { getContact, getQueryKeys } from 'src/modules/contact/query';\nimport { Contact as IContact } from 'src/modules/contact/types';\nimport { getAveragePI, PI_PROPERTIES } from 'src/modules/pi';\nimport { useSettings } from 'src/modules/settings';\nimport { APIAxiosError } from 'src/services/api-client/types';\nimport { formatName } from 'src/modules/contact';\nimport PageError from '../error/PageError';\nimport PageNotFound from '../error/PageNotFound';\nimport AddressDetails from './components/AddressDetails';\nimport BirthdayDetails from './components/BirthdayDetails';\nimport ContactPageWrapper from './components/ContactPageWrapper';\nimport EmailDetails from './components/EmailDetails';\nimport IdentityDetails from './components/IdentityDetails';\nimport ImDetails from './components/ImDetails';\nimport OrgaDetails from './components/OrgaDetails';\nimport PhoneDetails from './components/PhoneDetails';\nimport PublicKeyList from './components/PublicKeyList';\n\nfunction ContactPlaceholder() {\n  return (\n    <>\n      <div className=\"s-contact__tags\">\n        <PlaceholderBlock shape=\"line\" display=\"inline-block\" width=\"small\" />\n        <PlaceholderBlock shape=\"line\" display=\"inline-block\" />\n      </div>\n      <div className=\"s-contact__main-title s-contact-main-title\">\n        <div className=\"s-contact-main-title__avatar\">\n          <PlaceholderBlock shape=\"avatar\" size=\"large\" />\n        </div>\n        <div className=\"s-contact-main-title__name\">\n          <PlaceholderBlock display=\"inline-block\" width=\"large\" />\n        </div>\n\n        <div className=\"s-contact-main-title__pi\">\n          <PlaceholderBlock shape=\"square\" />\n        </div>\n      </div>\n      <div className=\"s-contact__contact-details\">\n        <Title hr>\n          <PlaceholderBlock shape=\"line\" display=\"inline-block\" width=\"large\" />\n        </Title>\n        <TextList className=\"s-contact__details-group\">\n          <TextItem>\n            <PlaceholderBlock\n              shape=\"line\"\n              display=\"inline-block\"\n              width=\"xlarge\"\n            />\n          </TextItem>\n          <TextItem>\n            <PlaceholderBlock\n              shape=\"line\"\n              display=\"inline-block\"\n              width=\"xlarge\"\n            />\n          </TextItem>\n          <TextItem>\n            <PlaceholderBlock\n              shape=\"line\"\n              display=\"inline-block\"\n              width=\"xlarge\"\n            />\n          </TextItem>\n        </TextList>\n      </div>\n    </>\n  );\n}\n\nfunction Contact():\n  | JSX.Element\n  | React.ReactElement<typeof ContactPageWrapper> {\n  const { contactId } = useParams<{ contactId: string }>();\n  const { data, isFetching, isError, error } = useQuery<\n    AxiosResponse<IContact>,\n    APIAxiosError\n  >(getQueryKeys({ contactId }), () => getContact(contactId));\n\n  const contact = data?.data;\n\n  const settings = useSettings();\n\n  // XXX: problem, API return status 500 instead of 404 in this case\n  if (!contact && isError && error?.response?.status === 404) {\n    return <PageNotFound />;\n  }\n\n  if (!contact && isError) {\n    return <PageError />;\n  }\n\n  if (!contact) {\n    return (\n      <ContactPageWrapper contact={contact} hasActivity={isFetching}>\n        <ContactPlaceholder />\n      </ContactPageWrapper>\n    );\n  }\n\n  const averagePI = contact.pi\n    ? getAveragePI(contact.pi, PI_PROPERTIES)\n    : ' - ';\n  const { identities = [], ims = [], addresses = [], infos } = contact;\n  const restOfDetails = [\n    ...identities.map((identity) => (\n      <TextItem className=\"s-contact__detail\" key={identity.social_id}>\n        <IdentityDetails identity={identity} />\n      </TextItem>\n    )),\n    ...ims.map((detail) => (\n      <TextItem className=\"s-contact__detail\" key={detail.im_id}>\n        <ImDetails im={detail} />\n      </TextItem>\n    )),\n\n    ...addresses.map((detail) => (\n      <TextItem className=\"s-contact__detail\" key={detail.address_id}>\n        <AddressDetails address={detail} />\n      </TextItem>\n    )),\n  ];\n\n  if (infos?.birthday) {\n    restOfDetails.push(\n      <TextItem className=\"s-contact__detail\" key={infos.birthday}>\n        <BirthdayDetails birthday={infos.birthday} />\n      </TextItem>\n    );\n  }\n\n  return (\n    <ContactPageWrapper contact={contact} hasActivity={isFetching}>\n      <div className=\"s-contact__main-title s-contact-main-title\">\n        <div className=\"s-contact-main-title__avatar\">\n          <ContactAvatarLetter\n            contact={contact}\n            contactDisplayFormat={settings.contact_display_format}\n            size=\"large\"\n          />\n        </div>\n        <div className=\"s-contact-main-title__name\">\n          {formatName({\n            contact,\n            format: settings.contact_display_format,\n          })}\n        </div>\n\n        <div className=\"s-contact-main-title__pi\">{averagePI}</div>\n\n        {contact.organizations && contact.organizations.length > 0 && (\n          <TextBlock className=\"s-contact-main-title__organizations\">\n            <Icon type=\"building\" rightSpaced />\n            <Trans id=\"contact.organizations\" message=\"Organizations:\" />{' '}\n            {contact.organizations.map((orga) => (\n              <OrgaDetails key={orga.organization_id} organization={orga} />\n            ))}\n          </TextBlock>\n        )}\n        {/* <ContactStats className=\"stats\" /> */}\n      </div>\n      <div className=\"s-contact__contact-details\">\n        <Title hr>\n          <Trans id=\"contact.contact-details.title\" message=\"Contact details\" />\n        </Title>\n        <TextList className=\"s-contact__details-group\">\n          {contact.emails?.map((email) => (\n            <TextItem className=\"s-contact__detail\" key={email.email_id}>\n              <EmailDetails email={email} />\n            </TextItem>\n          ))}\n        </TextList>\n        <TextList className=\"s-contact__details-group\">\n          {contact.phones?.map((phone) => (\n            <TextItem className=\"s-contact__detail\" key={phone.phone_id}>\n              <PhoneDetails phone={phone} />\n            </TextItem>\n          ))}\n        </TextList>\n        <TextList className=\"s-contact__details-group\">\n          {restOfDetails}\n        </TextList>\n      </div>\n      <div className=\"s-contact__keys\">\n        <Title hr>\n          <Trans id=\"contact.keys.title\" message=\"Public keys\" />\n        </Title>\n        <PublicKeyList contactId={contactId} />\n      </div>\n    </ContactPageWrapper>\n  );\n}\n\nexport default Contact;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/components/ImportContact/index.js",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/components/ImportContact/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { withI18n } from '@lingui/react';\nimport { withNotification } from 'src/modules/userNotify';\nimport getClient from 'src/services/api-client';\nimport UploadFileAsFormField from 'src/modules/file/services/uploadFileAsFormField';\nimport ImportContactForm from '../ImportContactForm';\n\n@withI18n()\n@withNotification()\nclass ImportContact extends Component {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    onCancel: PropTypes.func,\n    onUploadSuccess: PropTypes.func,\n    notifySuccess: PropTypes.func.isRequired,\n    notifyError: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    onCancel: null,\n    onUploadSuccess: () => {\n      // noop\n    },\n  };\n\n  state = {\n    errors: {},\n    hasImported: false,\n    isLoading: false,\n  };\n\n  handleImportContact = ({ file }) => {\n    const data = new UploadFileAsFormField(file, 'file');\n\n    this.setState({ isLoading: true });\n    getClient()\n      .post('/api/v2/imports', data)\n      .then(this.handleImportContactSuccess, this.handleImportContactError)\n      .then(() => this.setState({ isLoading: false }));\n  };\n\n  handleImportContactSuccess = () => {\n    this.setState({ hasImported: true }, () => {\n      const { onUploadSuccess, notifySuccess, i18n } = this.props;\n      notifySuccess({\n        message: i18n._(\n          /* i18n */ 'import-contact.feedback.successfull',\n          null,\n          {\n            message: 'Contacts successfully imported',\n          }\n        ),\n        duration: 0,\n      });\n      onUploadSuccess();\n    });\n  };\n\n  handleImportContactError = ({ response }) => {\n    const { notifyError, i18n } = this.props;\n\n    if (response.status === 400) {\n      return notifyError({\n        message: i18n._(/* i18n */ 'import-contact.feedback.error-file', null, {\n          message: 'This file cannot be used to import contacts',\n        }),\n        duration: 0,\n      });\n    }\n\n    if (response.status === 422) {\n      return notifyError({\n        message: i18n._(\n          /* i18n */ 'import-contact.feedback.error-contact',\n          null,\n          {\n            message: 'The file is valid but new contacts cannot be created',\n          }\n        ),\n        duration: 0,\n      });\n    }\n\n    return notifyError({\n      message: i18n._(\n        /* i18n */ 'import-contact.feedback.unexpected-error',\n        null,\n        {\n          message: 'An unexpected error occured.',\n        }\n      ),\n      duration: 0,\n    });\n  };\n\n  render() {\n    const { onCancel } = this.props;\n\n    return (\n      <ImportContactForm\n        onCancel={onCancel}\n        onSubmit={this.handleImportContact}\n        errors={this.state.errors}\n        hasImported={this.state.hasImported}\n        isLoading={this.state.isLoading}\n      />\n    );\n  }\n}\n\nexport default ImportContact;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/components/ImportContactButton/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { Trans, useLingui } from '@lingui/react';\nimport { Button, Modal } from 'src/components';\nimport ImportContact from '../ImportContact';\n\ninterface Props {\n  className?: string;\n  onUploadSuccess: () => void;\n}\nfunction ImportContactButton({ className, onUploadSuccess }: Props) {\n  const { i18n } = useLingui();\n  const [isImportModalOpen, setIsImportModalOpen] = React.useState(false);\n\n  const handleOpenImportModal = () => {\n    setIsImportModalOpen(true);\n  };\n\n  const handleCloseImportModal = () => {\n    setIsImportModalOpen(false);\n  };\n\n  return (\n    <>\n      <Button\n        className={classnames(className)}\n        icon=\"upload\"\n        shape=\"plain\"\n        display=\"block\"\n        onClick={handleOpenImportModal}\n      >\n        <Trans id=\"contact-book.action.import\" message=\"Import\" />\n      </Button>\n      <Modal\n        isOpen={isImportModalOpen}\n        contentLabel={i18n._(\n          /* i18n */ 'import-contact.action.import_contacts',\n          undefined,\n          {\n            message: 'Import contacts',\n          }\n        )}\n        title={i18n._(\n          /* i18n */ 'import-contact.action.import_contacts',\n          undefined,\n          {\n            message: 'Import contacts',\n          }\n        )}\n        onClose={handleCloseImportModal}\n      >\n        <ImportContact\n          onCancel={handleCloseImportModal}\n          onUploadSuccess={onUploadSuccess}\n        />\n      </Modal>\n    </>\n  );\n}\n\nexport default ImportContactButton;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/components/ImportContactForm/index.jsx",
    "content": "import Presenter from './presenter';\n\nexport default Presenter;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/components/ImportContactForm/presenter.jsx",
    "content": "import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans, withI18n } from '@lingui/react';\nimport { Button, InputFileGroup, Spinner } from 'src/components';\nimport { getMaxSize } from 'src/services/config';\n\nimport './style.scss';\n\nconst VALID_EXT = ['.vcf', '.vcard']; // Valid file extensions for input#file\n\n@withI18n()\nclass ImportContactForm extends Component {\n  static propTypes = {\n    onSubmit: PropTypes.func.isRequired,\n    onCancel: PropTypes.func,\n    errors: PropTypes.shape({}),\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    hasImported: PropTypes.bool,\n    isLoading: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    onCancel: null,\n    errors: {},\n    hasImported: false,\n    isLoading: false,\n  };\n\n  state = {\n    file: null,\n  };\n\n  handleInputFileChange = (file) => {\n    this.setState({\n      file,\n    });\n  };\n\n  handleSubmitForm = (ev) => {\n    ev.preventDefault();\n    const { file } = this.state;\n    this.props.onSubmit({ file });\n  };\n\n  renderButtons() {\n    const { onCancel, isLoading } = this.props;\n\n    return (\n      <div className=\"m-import-contact-form__buttons\">\n        {!this.props.hasImported && (\n          <Button\n            className=\"m-import-contact-form__button\"\n            shape=\"hollow\"\n            onClick={onCancel}\n          >\n            <Trans id=\"general.action.cancel\" message=\"Cancel\" />\n          </Button>\n        )}\n\n        {this.state.file && !this.props.hasImported && (\n          <Button\n            className=\"m-import-contact-form__button m-import-contact-form__button--right\"\n            type=\"submit\"\n            shape=\"plain\"\n            icon={\n              isLoading ? (\n                <Spinner\n                  svgTitleId=\"import-contact-spinner\"\n                  isLoading\n                  display=\"inline\"\n                />\n              ) : (\n                'download'\n              )\n            }\n            disabled={isLoading}\n          >\n            <Trans id=\"import-contact.action.import\" message=\"Import\" />\n          </Button>\n        )}\n\n        {this.props.hasImported && (\n          <Button\n            className=\"m-import-contact-form__button m-import-contact-form__button--right\"\n            shape=\"plain\"\n            onClick={onCancel}\n          >\n            <Trans id=\"import-contact.form.button.close\" message=\"Close\" />\n          </Button>\n        )}\n      </div>\n    );\n  }\n\n  render() {\n    const { i18n, hasImported, errors } = this.props;\n\n    return (\n      <form\n        className=\"m-import-contact-form\"\n        onSubmit={this.handleSubmitForm}\n        method=\"post\"\n        encType=\"multipart/form-data\"\n      >\n        {!hasImported ? (\n          <InputFileGroup\n            onInputChange={this.handleInputFileChange}\n            errors={errors}\n            descr={i18n._(/* i18n */ 'import-contact.form.descr', null, {\n              message: 'You can import one .vcf or .vcard file.',\n            })}\n            fileTypes={VALID_EXT}\n            maxSize={getMaxSize()}\n          />\n        ) : (\n          <p>\n            <Trans\n              id=\"import-contact.form.success\"\n              message=\"Successfuly imported !\"\n            />\n          </p>\n        )}\n        {this.renderButtons()}\n      </form>\n    );\n  }\n}\n\nexport default ImportContactForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/components/ImportContactForm/style.scss",
    "content": "@import 'src/styles/common';\n@import 'src/styles/object/o-clickable';\n@import 'src/styles/object/o-form-element';\n@import 'src/styles/object/o-hidden-element';\n@import 'src/styles/vendor/bootstrap_foundation-sites';\n\n.m-import-contact-form {\n  width: map_get($co-form__width, 'xlarge');\n\n  &__buttons {\n    @include flex-grid-row;\n\n    justify-content: space-between;\n    margin-top: $co-margin;\n  }\n\n  &__button {\n    @include flex-grid-column(shrink);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/components/TagList/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { getTagLabel, useTags } from 'src/modules/tags';\nimport { Link, NavList, NavItem } from 'src/components';\n\nimport './style.scss';\n\ntype Props = withI18nProps;\n\nfunction TagList({ i18n }: Props) {\n  const { tags } = useTags();\n\n  const sortedTags = React.useMemo(\n    () =>\n      [...tags].sort((a, b) =>\n        getTagLabel(i18n, a).localeCompare(getTagLabel(i18n, b))\n      ),\n    [tags]\n  );\n\n  return (\n    <NavList className=\"m-tag-list\" dir=\"vertical\">\n      <NavItem>\n        <Link\n          display=\"inline\"\n          noDecoration\n          to=\"/contacts\"\n          className=\"m-tag-list__tag\"\n        >\n          <Trans id=\"tag_list.all_contacts\" message=\"All contacts\" />\n        </Link>\n      </NavItem>\n      {sortedTags.map((tag) => (\n        <NavItem key={tag.name}>\n          <Link\n            display=\"inline\"\n            noDecoration\n            to={`/contacts?tag=${tag.name}`}\n            className=\"m-tag-list__tag\"\n          >\n            {getTagLabel(i18n, tag)}\n          </Link>\n        </NavItem>\n      ))}\n    </NavList>\n  );\n}\n\nexport default withI18n()(TagList);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/components/TagList/style.scss",
    "content": "@import 'src/styles/vendor/bootstrap_foundation-sites';\n\n.m-tag-list {\n  &__tag {\n    color: $co-color__fg__text--high;\n    font-weight: 400;\n    line-height: $co-font__line-height;\n\n    &--active {\n      color: $co-color__fg__text;\n      font-weight: 600;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/contact-book-menu.scss",
    "content": "@import 'src/styles/common';\n@import 'src/styles/vendor/bootstrap_foundation-sites';\n\n.s-contact-book-menu {\n  text-align: right;\n\n  &__action-btn {\n    margin-left: map_get($co-margin_, 'xsmall');\n    color: #0e97c3;\n  }\n\n  &__select-all {\n    display: inline-block;\n    margin-left: map_get($co-margin_, 'xsmall');\n\n    &--label-hidden {\n      margin-right: map_get($co-margin_, 'xsmall');\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/index.spec.tsx",
    "content": "import {\n  render,\n  screen,\n  waitForElementToBeRemoved,\n  within,\n} from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { rest } from 'msw';\nimport * as React from 'react';\nimport { NewTag } from 'src/modules/tags/types';\nimport { generateContact } from 'test/fixtures/contacts';\nimport { AllProviders } from 'test/providers';\nimport { server } from 'test/server';\nimport ContactBook from '.';\n\njest.mock('src/modules/user/services/isAuthenticated', () => ({\n  isAuthenticated: () => true,\n}));\n\nconst contacts = [\n  generateContact({ contact_id: '1', tags: ['me', 'bar'], given_name: 'Me' }),\n  generateContact({\n    contact_id: 'a',\n    tags: ['foo', 'foobar', 'baz'],\n    given_name: 'Perso A',\n    family_name: '',\n  }),\n  generateContact({\n    contact_id: 'b',\n    tags: ['foobar', 'bar', 'baz'],\n    given_name: 'Perso B',\n    family_name: '',\n  }),\n];\ndescribe('ContactBook', () => {\n  describe('edit tags', () => {\n    it('update tags of selected contacts with n tags in common: keep not common, remove one common & add existing one & create new one', async () => {\n      const createTagsAPI = jest.fn();\n      const contactTagAPI = jest.fn();\n      const knownTags = [\n        { label: 'Important', name: 'important', type: 'system' },\n        { label: 'foo', name: 'foo', type: 'user' },\n        { label: 'bar', name: 'bar', type: 'user' },\n        { label: 'Foobar', name: 'foobar', type: 'user' },\n        { label: 'Baz', name: 'baz', type: 'user' },\n      ];\n\n      server.use(\n        rest.get('/api/v2/tags', (req, res, ctx) =>\n          res(\n            ctx.json({ tags: knownTags, total: knownTags.length }),\n            ctx.status(200)\n          )\n        ),\n        rest.post<NewTag>('/api/v2/tags', (req, res, ctx) => {\n          createTagsAPI(req.body);\n          const tag = {\n            ...req.body,\n            // @ts-ignore :shrug:\n            name: req.body.label.replaceAll(' ', '_').toLowerCase(),\n            type: 'user',\n          };\n          knownTags.push(tag);\n\n          return res(ctx.json({ message: 'OK' }), ctx.status(201));\n        }),\n        rest.patch<{ tags: string[] }>(\n          '/api/v2/contacts/:contactId/tags',\n          (req, res, ctx) => {\n            contactTagAPI(req.params.contactId, req.body);\n\n            const idx = contacts.findIndex(\n              (contact) => contact.contact_id === req.params.contactId\n            );\n            const { tags } = req.body;\n            contacts[idx].tags = tags;\n\n            return res(ctx.status(200), ctx.json({ message: 'OK' }));\n          }\n        ),\n        rest.get('/api/v2/contacts', (req, res, ctx) =>\n          res(ctx.json({ contacts, total: contacts.length }), ctx.status(200))\n        )\n      );\n\n      render(<ContactBook />, {\n        wrapper: AllProviders,\n      });\n\n      await waitForElementToBeRemoved(\n        screen.queryByTitle('Contact list is loading.'),\n        { timeout: 5000 } // why is this so long?\n      );\n\n      const persoA = screen.getByRole('link', { name: /Perso A/ });\n      if (!persoA.parentElement) {\n        throw Error('perso A parent is missing');\n      }\n      const persoB = screen.getByRole('link', { name: /Perso B/ });\n      if (!persoB.parentElement) {\n        throw Error('perso A parent is missing');\n      }\n      userEvent.click(\n        within(persoA.parentElement).getByRole('checkbox', {\n          name: 'Select the contact',\n        })\n      );\n      userEvent.click(\n        within(persoB.parentElement).getByRole('checkbox', {\n          name: 'Select the contact',\n        })\n      );\n\n      userEvent.click(screen.getByRole('button', { name: 'Manage tags' }));\n\n      const modal = screen.getByRole('dialog', { name: 'Tags' });\n\n      const banner = within(modal).getByRole('banner');\n      expect(banner).toHaveTextContent('Tags (Total: 2)');\n\n      // remove one in common\n      const tag = within(modal).getByText('Baz').parentElement;\n      if (!tag) {\n        throw Error('tag element cannot be found');\n      }\n      userEvent.click(within(tag).getByRole('button', { name: 'Remove' }));\n\n      // create new one\n      screen.getByRole('textbox').focus();\n      userEvent.type(screen.getByLabelText('Search'), 'I am a new tag');\n      userEvent.click(screen.getByRole('button', { name: 'Add' }));\n\n      // add existing one\n      userEvent.type(screen.getByLabelText('Search'), 'impo'); // existing tag\n      userEvent.click(screen.getByRole('button', { name: 'Add' }));\n\n      userEvent.click(screen.getByRole('button', { name: 'Validate' }));\n\n      await waitForElementToBeRemoved(\n        screen.getByRole('button', { name: /Loading …/ })\n      );\n\n      expect(createTagsAPI).toHaveBeenCalledWith({ label: 'I am a new tag' });\n\n      expect(contactTagAPI).toHaveBeenCalledTimes(2);\n      expect(contactTagAPI).toHaveBeenNthCalledWith(1, 'a', {\n        current_state: { tags: ['foo', 'foobar', 'baz'] },\n        tags: ['important', 'foobar', 'i_am_a_new_tag'],\n      });\n      expect(contactTagAPI).toHaveBeenNthCalledWith(2, 'b', {\n        current_state: { tags: ['foobar', 'bar', 'baz'] },\n        tags: ['important', 'foobar', 'i_am_a_new_tag'],\n      });\n\n      expect(await screen.findByText('I am a new tag')).toBeInTheDocument();\n\n      expect(\n        screen.getByText('Perso A').parentElement?.parentElement\n      ).toHaveTextContent('Perso AImportantFoobarI am a new tag');\n      expect(\n        screen.getByText('Perso B').parentElement?.parentElement\n      ).toHaveTextContent('Perso BImportantFoobarI am a new tag');\n    }, 5000);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/index.tsx",
    "content": "import * as React from 'react';\nimport classnames from 'classnames';\nimport { Trans, useLingui } from '@lingui/react';\nimport { useMutation, useQueryClient } from 'react-query';\nimport { useDispatch } from 'react-redux';\nimport { notifyError } from 'src/modules/userNotify';\nimport {\n  ContactList,\n  DEFAULT_SORT_DIR,\n  useContacts,\n} from 'src/modules/contact';\nimport { useSearchParams } from 'src/modules/routing';\nimport {\n  PageTitle,\n  Spinner,\n  Button,\n  ActionBarWrapper,\n  ActionBar,\n  Checkbox,\n  Link,\n  SidebarLayout,\n  NavList,\n  NavItem,\n  Confirm,\n  Modal,\n} from 'src/components';\nimport { withScrollManager, ScrollDetector } from 'src/modules/scroll';\nimport {\n  TagsForm,\n  getCleanedTagCollection,\n  getTagNamesInCommon,\n  useTags,\n} from 'src/modules/tags';\nimport {\n  getQueryKeys,\n  deleteContact as baseDeleteContact,\n} from 'src/modules/contact/query';\nimport { TagMixed } from 'src/modules/tags/query';\nimport TagList from './components/TagList';\nimport ImportContactButton from './components/ImportContactButton';\nimport './style.scss';\nimport './contact-book-menu.scss';\nimport { useUpdateContactsTags } from './query';\n\nfunction getFilteredContacts(contactList, tag) {\n  if (tag === '') {\n    return contactList;\n  }\n\n  return contactList.filter(\n    (contact) => contact.tags && contact.tags.includes(tag)\n  );\n}\n\nconst EMPTY_ARRAY: string[] = [];\nconst EMPTY_ENTITIES = [];\n\nfunction ContactBook() {\n  const { i18n } = useLingui();\n  const dispatch = useDispatch();\n  const queryClient = useQueryClient();\n  const { tags: userTags } = useTags();\n  const { tag: tagSearched = '' } = useSearchParams();\n\n  const sortDir = DEFAULT_SORT_DIR;\n  const [selectedEntitiesIds, setSelectedEntitiesIds] =\n    React.useState(EMPTY_ARRAY);\n  const [isTagModalOpen, setIsTagModalOpen] = React.useState(false);\n\n  const { data: contactsBase, refetch, isFetching } = useContacts();\n  const { mutateAsync: updateContacsTags } = useUpdateContactsTags();\n\n  const contacts = getFilteredContacts(contactsBase, tagSearched);\n\n  const { mutate: deleteContact, isLoading: isDeleting } = useMutation<\n    unknown,\n    unknown,\n    string\n  >(baseDeleteContact, {\n    onSuccess: () => {\n      queryClient.invalidateQueries(getQueryKeys());\n    },\n  });\n\n  const onSelectEntity = (type: 'add' | 'remove', id: string) => {\n    if (type === 'add') {\n      setSelectedEntitiesIds((prev) => [...prev, id]);\n    }\n\n    if (type === 'remove') {\n      setSelectedEntitiesIds((prev) => [...prev].filter((item) => item !== id));\n    }\n  };\n\n  const onSelectAllEntities = (checked) => {\n    const contactIds = getFilteredContacts(contacts, tagSearched).map(\n      ({ contact_id: contactId }) => contactId\n    );\n\n    setSelectedEntitiesIds(checked ? contactIds : EMPTY_ARRAY);\n  };\n\n  const handleSelectAllEntitiesChange = (ev) => {\n    const { checked } = ev.target;\n    onSelectAllEntities(checked);\n  };\n\n  const handleDeleteContacts = async () => {\n    const selectedContactIds = new Set(selectedEntitiesIds);\n    selectedContactIds.forEach((id) => {\n      deleteContact(id, {\n        onSuccess: () => {\n          // This will executed only one time at the end of the promises pool\n          setSelectedEntitiesIds(EMPTY_ARRAY);\n        },\n        onError: () => {\n          dispatch(\n            notifyError({\n              message: (\n                <Trans\n                  id=\"contact-book.feedback.unable_to_delete\"\n                  message=\"Unable to delete the contact\"\n                />\n              ),\n            })\n          );\n        },\n      });\n    });\n  };\n\n  const handleUploadSuccess = () => {\n    refetch();\n  };\n\n  const handleOpenTags = () => {\n    setIsTagModalOpen(true);\n  };\n\n  const handleCloseTags = () => {\n    setIsTagModalOpen(false);\n  };\n\n  const handleTagsChange = async (tags: TagMixed[]) => {\n    try {\n      await updateContacsTags({ contactIds: selectedEntitiesIds, tags });\n      setIsTagModalOpen(false);\n    } catch (err) {\n      dispatch(\n        notifyError({\n          message: (\n            <Trans\n              id=\"contact-book.feedback.unable_to_delete\"\n              message=\"Unable to save tags\"\n            />\n          ),\n        })\n      );\n    }\n  };\n\n  const selectedEntitiesIdsSet = new Set(selectedEntitiesIds);\n  const selectedEntities =\n    contacts?.filter((contact) =>\n      selectedEntitiesIdsSet.has(contact.contact_id)\n    ) || EMPTY_ENTITIES;\n\n  const tagNamesInCommon = getTagNamesInCommon(selectedEntities);\n  const tagsInCommon = getCleanedTagCollection(userTags, tagNamesInCommon);\n\n  const count = selectedEntitiesIds.length;\n  const totalCount = contacts?.length || 0;\n\n  return (\n    <div className=\"s-contact-book\">\n      <PageTitle\n        title={i18n._(/* i18n */ 'header.menu.contacts', undefined, {\n          message: 'Contacts',\n        })}\n      />\n      <ScrollDetector\n        offset={136}\n        render={(isSticky) => (\n          <ActionBarWrapper isSticky={isSticky}>\n            <ActionBar\n              isLoading={isFetching}\n              actionsNode={\n                <div className=\"s-contact-book-menu\">\n                  {count > 0 && (\n                    <>\n                      <span className=\"s-contact-book-menu__label\">\n                        <Trans\n                          id=\"contact-book.contacts.selected\"\n                          values={{ count, totalCount }}\n                          message=\"{count, plural, one {#/{totalCount} selected contact:} other {#/{totalCount} selected contacts:}}\"\n                        />\n                      </span>\n                      <Confirm\n                        onConfirm={handleDeleteContacts}\n                        title={\n                          <Trans\n                            id=\"contact-book.confirm-delete.title\"\n                            values={{ count }}\n                            message=\"{count, plural, one {Delete contact} other {Delete contacts}}\"\n                          />\n                        }\n                        content={\n                          <Trans\n                            id=\"contact-book.confirm-delete.content\"\n                            values={{ count }}\n                            message=\"{count, plural, one { The deletion is permanent, are you sure you want to delete this contact? } other { The deletion is permanent, are you sure you want to delete these contacts? }}\"\n                          />\n                        }\n                        render={(confirm) => (\n                          <Button\n                            className=\"s-contact-book-menu__action-btn\"\n                            display=\"inline\"\n                            noDecoration\n                            icon={\n                              isDeleting ? (\n                                <Spinner\n                                  svgTitleId=\"delete-contacts-spinner\"\n                                  isLoading\n                                  display=\"inline\"\n                                />\n                              ) : (\n                                'trash'\n                              )\n                            }\n                            onClick={confirm}\n                            disabled={isDeleting}\n                          >\n                            <Trans\n                              id=\"contact-book.action.delete\"\n                              message=\"Delete\"\n                            />\n                          </Button>\n                        )}\n                      />\n                      <Button\n                        className=\"s-contact-book-menu__action-btn\"\n                        display=\"inline\"\n                        noDecoration\n                        icon=\"tag\"\n                        onClick={handleOpenTags}\n                      >\n                        <Trans\n                          id=\"contact-book.action.manage-tags\"\n                          message=\"Manage tags\"\n                        />\n                      </Button>\n                      {isTagModalOpen && (\n                        <Modal\n                          isOpen\n                          contentLabel={i18n._(\n                            /* i18n */ 'tags.header.label',\n                            undefined,\n                            {\n                              message: 'Tags',\n                            }\n                          )}\n                          title={\n                            <Trans\n                              id=\"tags.header.title\"\n                              message=\"Tags <0>(Total: {nb})</0>\"\n                              values={{ nb: tagsInCommon.length }}\n                              components={[\n                                <span className=\"m-tags-form__count\" />,\n                              ]}\n                            />\n                          }\n                          onClose={handleCloseTags}\n                        >\n                          {tagNamesInCommon.length > 1 && (\n                            <Trans\n                              id=\"tags.common_tags_applied\"\n                              message=\"Common tags applied to the current selection:\"\n                            />\n                          )}\n\n                          <TagsForm\n                            initialTags={tagsInCommon}\n                            onSubmit={handleTagsChange}\n                          />\n                        </Modal>\n                      )}\n                      {/* <Button\n                            className=\"s-contact-book-menu__action-btn\"\n                            display=\"inline\"\n                            noDecoration\n                            icon=\"share\"\n                            >\n                            <Trans id=\"contact-book.action.start-discussion\" message=\"Start discussion\" />\n                          </Button> */}\n                    </>\n                  )}\n                  <div\n                    className={classnames('s-contact-book-menu__select-all', {\n                      's-contact-book-menu__select-all--label-hidden':\n                        count > 0,\n                    })}\n                  >\n                    <Checkbox\n                      checked={count > 0 && count === totalCount}\n                      indeterminate={count > 0 && count < totalCount}\n                      onChange={handleSelectAllEntitiesChange}\n                      label={\n                        <Trans\n                          id=\"contact-book.action.select-all\"\n                          message=\"Select all contacts\"\n                        />\n                      }\n                      showLabelforSr={count > 0}\n                    />\n                  </div>\n                </div>\n              }\n            />\n          </ActionBarWrapper>\n        )}\n      />\n      <SidebarLayout\n        sidebar={\n          <div className=\"s-contact-book__sidebar\">\n            <div>\n              <h2 className=\"s-contact-book__tags-title\">\n                <Trans id=\"contact-book.contacts.title\" message=\"Contacts\" />\n              </h2>\n              <NavList dir=\"vertical\">\n                <NavItem>\n                  <Link\n                    className=\"s-contact-book__action-button\"\n                    button\n                    icon=\"plus\"\n                    shape=\"plain\"\n                    display=\"block\"\n                    to=\"/new-contact\"\n                  >\n                    <Trans id=\"contact-book.action.add\" message=\"Add\" />\n                  </Link>\n                </NavItem>\n                <NavItem>\n                  <ImportContactButton\n                    className=\"s-contact-book__action-button\"\n                    onUploadSuccess={handleUploadSuccess}\n                  />\n                </NavItem>\n              </NavList>\n            </div>\n            <div>\n              <h2 className=\"s-contact-book__tags-title\">\n                <Trans id=\"contact-book.groups.title\" message=\"Groups\" />\n              </h2>\n              <TagList />\n              <NavList dir=\"vertical\">\n                <NavItem>\n                  <Link\n                    className=\"s-contact-book__action-button\"\n                    button\n                    icon=\"tag\"\n                    shape=\"plain\"\n                    display=\"block\"\n                    to=\"/settings/tags\"\n                  >\n                    <Trans\n                      id=\"contact-book.tags.action.edit-groups\"\n                      message=\"Edit groups\"\n                    />\n                  </Link>\n                </NavItem>\n              </NavList>\n            </div>\n          </div>\n        }\n      >\n        <ContactList\n          mode=\"contact-book\"\n          sortDir={sortDir}\n          onSelectEntity={onSelectEntity}\n          selectedContactsIds={selectedEntitiesIds}\n          contacts={contacts}\n        />\n      </SidebarLayout>\n    </div>\n  );\n}\n\nexport default withScrollManager()(ContactBook);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/query.spec.tsx",
    "content": "import * as React from 'react';\nimport {\n  render,\n  screen,\n  waitForElementToBeRemoved,\n} from '@testing-library/react';\nimport { rest } from 'msw';\nimport { NewTag } from 'src/modules/tags/types';\nimport { TagMixed } from 'src/modules/tags/query';\nimport { AllProviders } from 'test/providers';\nimport { server } from 'test/server';\nimport { generateContact } from 'test/fixtures/contacts';\nimport { useUpdateContactsTags } from './query';\n\nconst contactIds = ['1', 'no-changes', '5', 'missing'];\nconst contacts = [\n  generateContact({ contact_id: '1', tags: ['me', 'bar'] }),\n  generateContact({\n    contact_id: 'no-changes',\n    tags: ['important', 'foo', 'bar', 'new', 'new too'], // desync user's tags & contact tags, it should not happen\n  }),\n  generateContact({ contact_id: '5', tags: [] }),\n  generateContact({ contact_id: 'other-1', tags: ['important'] }),\n  generateContact({ contact_id: 'other-2', tags: [] }),\n];\nconst tags: TagMixed[] = [\n  { label: 'Important', name: 'important', type: 'system' },\n  { label: 'foo', name: 'foo', type: 'user' },\n  { label: 'bar' },\n  { label: 'new' },\n  { label: 'new too' },\n];\n\nfunction My() {\n  const { mutateAsync, isSuccess, isError } = useUpdateContactsTags();\n\n  React.useEffect(() => {\n    const fetcher = async () => {\n      try {\n        await mutateAsync({ contactIds, tags });\n      } catch (err) {\n        console.error('----------Mutation Error-------------', err);\n      }\n    };\n    fetcher();\n  }, []);\n\n  return (\n    <div>\n      {isSuccess && 'Done'}\n      {isError && 'Failed'}\n      {!isError && !isSuccess && 'In progress'}\n    </div>\n  );\n}\n\ndescribe('scenes > contact > ContactBook > query', () => {\n  it('useUpdateContactsTags', async () => {\n    const knownTagsAPI = jest.fn();\n    const createTagsAPI = jest.fn();\n    const contactTagAPI = jest.fn();\n    const knownTags = [\n      { label: 'Important', name: 'important', type: 'system' },\n      { label: 'foo', name: 'foo', type: 'user' },\n      { label: 'bar', name: 'bar', type: 'user' },\n    ];\n\n    server.use(\n      rest.get('/api/v2/tags', (req, res, ctx) => {\n        knownTagsAPI();\n\n        return res(\n          ctx.json({ tags: knownTags, total: knownTags.length }),\n          ctx.status(200)\n        );\n      }),\n      rest.post<NewTag>('/api/v2/tags', (req, res, ctx) => {\n        createTagsAPI(req.body);\n        const tag = {\n          ...req.body,\n          name: req.body.label,\n          type: 'user',\n        };\n        knownTags.push(tag);\n\n        return res(ctx.json({ message: 'OK' }), ctx.status(201));\n      }),\n      rest.patch('/api/v2/contacts/:contactId/tags', (req, res, ctx) => {\n        contactTagAPI(req.params.contactId, req.body);\n\n        return res(ctx.status(200), ctx.json({ message: 'OK' }));\n      }),\n      rest.get('/api/v2/contacts', (req, res, ctx) =>\n        res(ctx.json({ contacts, total: contacts.length }), ctx.status(200))\n      )\n    );\n\n    render(<My />, { wrapper: AllProviders });\n\n    expect(screen.getByText('In progress')).toBeInTheDocument();\n\n    await waitForElementToBeRemoved(() => screen.queryByText('In progress'), {\n      timeout: 2000, // FIXME: too looong 😱\n    });\n    expect(screen.getByText('Done')).toBeInTheDocument();\n\n    expect(knownTagsAPI).toHaveBeenCalled();\n    expect(createTagsAPI).toHaveBeenCalledTimes(2);\n    expect(createTagsAPI).toHaveBeenNthCalledWith(1, { label: 'new' });\n    expect(createTagsAPI).toHaveBeenNthCalledWith(2, { label: 'new too' });\n    expect(knownTagsAPI).toHaveBeenCalled();\n\n    expect(contactTagAPI).toHaveBeenCalledTimes(2);\n    expect(contactTagAPI).toHaveBeenNthCalledWith(1, '1', {\n      tags: ['important', 'foo', 'bar', 'new', 'new too'],\n      current_state: {\n        tags: ['me', 'bar'],\n      },\n    });\n    expect(contactTagAPI).toHaveBeenNthCalledWith(2, '5', {\n      tags: ['important', 'foo', 'bar', 'new', 'new too'],\n      current_state: {\n        tags: [],\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/query.ts",
    "content": "import { getCleanedTagCollection } from 'src/modules/tags';\nimport {\n  createMissingTags,\n  getConfigList,\n  getTagList,\n  TagMixed,\n  updateTagCollection,\n} from 'src/modules/tags/query';\nimport { useLingui } from '@lingui/react';\nimport { I18n } from '@lingui/core';\nimport { Contact } from 'src/modules/contact/types';\nimport { QueryClient, useMutation, useQueryClient } from 'react-query';\nimport {\n  getAllContactCollection,\n  getConfigList as getContactConfigList,\n} from 'src/modules/contact/query';\nimport { flatten } from 'lodash';\n\nconst contactsQueryKeys = getContactConfigList().queryKeys;\n\nexport const updateContactTagsConcrete = async (\n  i18n: I18n,\n  queryClient: QueryClient,\n  contactIds: string[],\n  tags: TagMixed[]\n) => {\n  const contactsPromise = queryClient.fetchQuery(\n    contactsQueryKeys,\n    getAllContactCollection\n  );\n  await createMissingTags(i18n, queryClient, tags);\n  const { queryKeys } = getConfigList();\n  const {\n    data: { tags: userTags },\n  } = await queryClient.fetchQuery(queryKeys, getTagList, {\n    staleTime: 300000, // 5min same as `useTags`\n  });\n\n  const cleanTags = getCleanedTagCollection(\n    userTags,\n    tags.map((tag) => tag.name || tag.label)\n  );\n\n  const allPages = await contactsPromise;\n  const contactCollection = flatten(\n    allPages.map((response) => response.data.contacts)\n  );\n  // @ts-ignore: boolean filter not handled by ts\n  const contacts: Contact[] = contactIds\n    .map((id) =>\n      contactCollection?.find((contact) => contact.contact_id === id)\n    )\n    .filter(Boolean);\n\n  await Promise.all(\n    contacts.map((contact) =>\n      updateTagCollection(i18n, queryClient, {\n        type: 'contact',\n        entity: contact,\n        tags: cleanTags,\n      })\n    )\n  );\n};\n\nexport function useUpdateContactsTags() {\n  const { i18n } = useLingui();\n  const queryClient = useQueryClient();\n\n  return useMutation<\n    any,\n    unknown,\n    { contactIds: Contact['contact_id'][]; tags: TagMixed[] }\n  >(({ contactIds, tags }) =>\n    updateContactTagsConcrete(i18n, queryClient, contactIds, tags)\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactBook/style.scss",
    "content": "@import 'src/styles/common';\n@import 'src/styles/vendor/bootstrap_foundation-sites';\n\n.s-contact-book {\n  &__sidebar {\n    padding-right: $co-margin;\n    padding-left: $co-margin;\n  }\n\n  &__tags-title {\n    margin-top: 1.4rem;\n    margin-bottom: 0.8rem;\n    color: $co-color__fg__text--high;\n    font-size: 1.3rem;\n    line-height: 1rem;\n  }\n\n  &__import {\n    @include flex-grid-row;\n\n    margin-top: 12px;\n    margin-bottom: $co-margin;\n  }\n\n  &__action-button {\n    text-transform: uppercase;\n  }\n\n  &__select-all {\n    display: inline-block;\n    vertical-align: middle;\n    margin-left: 10px;\n  }\n\n  &__load-more {\n    padding-top: $co-margin;\n    padding-bottom: $co-margin;\n    text-align: center;\n  }\n\n  @include breakpoint(medium) {\n    &__sidebar {\n      @include flex-grid-column(shrink);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/ContactForm.tsx",
    "content": "import { Trans } from '@lingui/react';\nimport { FieldArray, Form, Formik, FormikConfig } from 'formik';\nimport * as React from 'react';\nimport {\n  Button,\n  FormColumn,\n  FormGrid,\n  FormRow,\n  Spinner,\n  TextItem,\n  TextList,\n  Title,\n} from 'src/components';\nimport { IDENTITY_TYPE_TWITTER } from 'src/modules/contact';\nimport { ContactPayload } from 'src/modules/contact/types';\nimport FormikPersist, {\n  clearPersisted,\n} from 'src/modules/form/components/FormikPersist';\nimport ContactProfileForm from './components/ContactProfileForm';\nimport EmailForm from './components/EmailForm';\nimport ImForm from './components/ImForm';\nimport PhoneForm from './components/PhoneForm';\nimport AddressForm from './components/AddressForm';\nimport OrgaForm from './components/OrgaForm';\nimport AddFormFieldForm from './components/AddFormFieldForm';\nimport IdentityForm from './components/IdentityForm';\n\ninterface Props {\n  initialValues: ContactPayload;\n  onSubmit: FormikConfig<ContactPayload>['onSubmit'];\n  onCancel: () => void;\n  hasActivity: boolean;\n  initialProfileSectionOpen?: boolean;\n  formName: string;\n}\nfunction ContactForm({\n  initialValues,\n  onSubmit,\n  onCancel,\n  hasActivity,\n  initialProfileSectionOpen,\n  formName,\n}: Props): JSX.Element {\n  const handleSubmit: FormikConfig<ContactPayload>['onSubmit'] = (\n    value,\n    helpers\n  ) => {\n    onSubmit(value, helpers);\n    clearPersisted(formName);\n  };\n\n  const handleCancel = () => {\n    onCancel();\n    clearPersisted(formName);\n  };\n\n  return (\n    <div className=\"s-contact__form\">\n      <Formik<ContactPayload>\n        initialValues={initialValues}\n        onSubmit={handleSubmit}\n        enableReinitialize\n      >\n        {({ values }) => (\n          <Form method=\"post\" name=\"edit-contact\">\n            <FormikPersist name={formName} />\n            <Title hr>\n              <Trans\n                id=\"contact.edit_contact.title\"\n                message=\"Edit the contact\"\n              />\n            </Title>\n            <ContactProfileForm isNew={initialProfileSectionOpen} />\n            <div>\n              <Title hr>\n                <Trans\n                  id=\"contact.contact-details.title\"\n                  message=\"Contact details\"\n                />\n              </Title>\n              <FieldArray\n                name=\"emails\"\n                render={({ remove }) => (\n                  <TextList>\n                    {values.emails?.map((item, index) => (\n                      <TextItem key={index} large>\n                        <EmailForm\n                          name={`emails.${index}`}\n                          onDelete={() => remove(index)}\n                        />\n                      </TextItem>\n                    ))}\n                  </TextList>\n                )}\n              />\n              <FieldArray\n                name=\"phones\"\n                render={({ remove }) => (\n                  <TextList>\n                    {values.phones?.map((item, index) => (\n                      <TextItem key={index} large>\n                        <PhoneForm\n                          name={`phones.${index}`}\n                          onDelete={() => remove(index)}\n                        />\n                      </TextItem>\n                    ))}\n                  </TextList>\n                )}\n              />\n              <FieldArray\n                name=\"ims\"\n                render={({ remove }) => (\n                  <TextList>\n                    {values.ims?.map((item, index) => (\n                      <TextItem key={index} large>\n                        <ImForm\n                          name={`ims.${index}`}\n                          onDelete={() => remove(index)}\n                        />\n                      </TextItem>\n                    ))}\n                  </TextList>\n                )}\n              />\n              {/* {hasBirthday && (<BirthdayForm form={form} />)} */}\n              <AddFormFieldForm />\n            </div>\n            <Title hr>\n              <Trans id=\"contact.addresses\" message=\"Addresses\" />\n            </Title>\n            <FieldArray\n              name=\"addresses\"\n              render={({ remove, handlePush }) => (\n                <TextList>\n                  {values.addresses?.map((item, index) => (\n                    <TextItem key={index} large>\n                      <AddressForm\n                        name={`addresses.${index}`}\n                        onDelete={() => remove(index)}\n                      />\n                    </TextItem>\n                  ))}\n                  <TextItem>\n                    <FormGrid>\n                      <FormRow>\n                        <FormColumn>\n                          <Button\n                            icon=\"plus\"\n                            shape=\"plain\"\n                            onClick={handlePush({})}\n                          >\n                            <Trans\n                              id=\"contact.action.add-address\"\n                              message=\"Add an address\"\n                            />\n                          </Button>\n                        </FormColumn>\n                      </FormRow>\n                    </FormGrid>\n                  </TextItem>\n                </TextList>\n              )}\n            />\n            <Title hr>\n              <Trans id=\"contact.organization\" message=\"Organization\" />\n            </Title>\n            <FieldArray\n              name=\"organizations\"\n              render={({ remove, push: pushOrga }) => (\n                <TextList>\n                  {values.organizations?.map((item, index) => (\n                    <TextItem key={index}>\n                      <OrgaForm\n                        name={`organizations.${index}`}\n                        onDelete={() => remove(index)}\n                      />\n                    </TextItem>\n                  ))}\n                  <TextItem>\n                    <FormGrid>\n                      <FormRow>\n                        <FormColumn>\n                          <Button\n                            icon=\"plus\"\n                            shape=\"plain\"\n                            onClick={() => pushOrga({})}\n                          >\n                            <Trans\n                              id=\"contact.action.add-organization\"\n                              message=\"Add an organization\"\n                            />\n                          </Button>\n                        </FormColumn>\n                      </FormRow>\n                    </FormGrid>\n                  </TextItem>\n                </TextList>\n              )}\n            />\n            <Title hr>\n              <Trans id=\"contact.identities\" message=\"Identities\" />\n            </Title>\n            <FieldArray\n              name=\"identities\"\n              render={({ remove, push: pushIdentity }) => (\n                <TextList>\n                  {values.identities?.map((item, index) => (\n                    <TextItem key={index} large>\n                      <IdentityForm\n                        name={`identities.${index}`}\n                        onDelete={() => remove(index)}\n                      />\n                    </TextItem>\n                  ))}\n                  <TextItem>\n                    <FormGrid>\n                      <FormRow>\n                        <FormColumn>\n                          <Button\n                            icon=\"plus\"\n                            shape=\"plain\"\n                            onClick={() =>\n                              pushIdentity({ type: IDENTITY_TYPE_TWITTER })\n                            }\n                          >\n                            <Trans\n                              id=\"contact.action.add-identity\"\n                              message=\"Add an identity\"\n                            />\n                          </Button>\n                        </FormColumn>\n                      </FormRow>\n                    </FormGrid>\n                  </TextItem>\n                </TextList>\n              )}\n            />\n            <div className=\"s-contact__edit-bar\">\n              <Button\n                onClick={handleCancel}\n                responsive=\"icon-only\"\n                icon=\"remove\"\n                className=\"s-contact__action\"\n                shape=\"plain\"\n                color=\"disabled\"\n              >\n                <Trans id=\"contact.action.cancel\" message=\"Cancel\" />\n              </Button>\n              <Button\n                type=\"submit\"\n                responsive=\"icon-only\"\n                icon={\n                  hasActivity ? (\n                    <Spinner\n                      svgTitleId=\"validate-contact-spinner\"\n                      isLoading\n                      display=\"inline\"\n                    />\n                  ) : (\n                    'check'\n                  )\n                }\n                className=\"s-contact__action\"\n                shape=\"plain\"\n                disabled={hasActivity}\n              >\n                <Trans id=\"contact.action.save\" message=\"Save\" />\n              </Button>\n            </div>\n          </Form>\n        )}\n      </Formik>\n    </div>\n  );\n}\n\nexport default ContactForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/EditContact.spec.tsx",
    "content": "import * as React from 'react';\nimport { render, screen, waitFor } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { rest } from 'msw';\nimport reactRouter from 'react-router-dom';\nimport { server } from 'test/server';\nimport { AllProviders } from 'test/providers';\nimport { generateContact } from 'test/fixtures/contacts';\nimport EditContact from './EditContact';\n\nconst mockHistory = {\n  push: jest.fn(),\n};\n\njest.mock('react-router-dom', () => ({\n  ...(jest.requireActual('react-router-dom') as any),\n  useHistory: () => mockHistory,\n}));\n\ndescribe('EditContact', () => {\n  it('renders', async () => {\n    const id = 'abcd';\n    jest.spyOn(reactRouter, 'useParams').mockReturnValue({ contactId: id });\n    const apiPatch = jest.fn();\n    server.use(\n      rest.patch('/api/v2/contacts/:id', (req, res, ctx) => {\n        apiPatch(req.body);\n\n        return res(ctx.status(201));\n      }),\n      rest.get<any, { id: string }>(\n        '/api/v2/contacts/:id',\n        async (req, res, ctx) =>\n          res(\n            ctx.json(\n              generateContact({\n                contact_id: req.params.id,\n                given_name: 'Fry',\n                family_name: '',\n              })\n            ),\n            ctx.status(200)\n          )\n      )\n    );\n\n    render(<EditContact />, {\n      wrapper: AllProviders,\n    });\n\n    expect(await screen.findByText('Contact details')).toBeInTheDocument();\n    expect(\n      await screen.findByRole('button', { name: 'Fry' })\n    ).toBeInTheDocument();\n\n    userEvent.click(screen.getByRole('button', { name: 'Fry' }));\n    userEvent.clear(screen.getByRole('textbox', { name: 'Firstname' }));\n    userEvent.type(\n      screen.getByRole('textbox', { name: 'Firstname' }),\n      'Bender'\n    );\n\n    userEvent.click(screen.getByRole('button', { name: 'Save' }));\n    await waitFor(() =>\n      expect(apiPatch).toHaveBeenCalledWith({\n        current_state: { given_name: 'Fry' },\n        given_name: 'Bender',\n      })\n    );\n\n    await waitFor(() =>\n      expect(mockHistory.push).toHaveBeenCalledWith(`/contacts/${id}`)\n    );\n  }, 5000);\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/EditContact.tsx",
    "content": "import { Trans } from '@lingui/react';\nimport { FormikConfig } from 'formik';\nimport * as React from 'react';\nimport { useMutation, useQuery, useQueryClient } from 'react-query';\nimport { useDispatch } from 'react-redux';\nimport { AxiosResponse } from 'axios';\nimport { Link, useHistory, useParams } from 'react-router-dom';\nimport { fromPairs } from 'lodash';\nimport { APIAxiosError } from 'src/services/api-client/types';\nimport {\n  getContact,\n  updateContact,\n  getQueryKeys,\n} from 'src/modules/contact/query';\nimport { getNewContact } from 'src/modules/contact';\nimport { Contact, ContactPayload } from 'src/modules/contact/types';\nimport { notifyError } from 'src/modules/userNotify';\nimport PageNotFound from 'src/scenes/error/PageNotFound';\nimport PageError from 'src/scenes/error/PageError';\nimport ContactPageWrapper from './components/ContactPageWrapper';\nimport ContactForm from './ContactForm';\nimport {\n  ContactError,\n  CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT,\n  handleContactSaveErrors,\n} from './services/handleContactSaveErrors';\n\nconst emptyContact = getNewContact();\n\nfunction EditContact(): React.ReactElement<typeof ContactPageWrapper> {\n  const dispatch = useDispatch();\n  const { push } = useHistory();\n  const queryClient = useQueryClient();\n  const { contactId } = useParams<{ contactId: string }>();\n  const {\n    data: contact,\n    isFetching,\n    isError,\n    error,\n  } = useQuery<AxiosResponse<Contact>, APIAxiosError, Contact>(\n    getQueryKeys({ contactId }),\n    () => getContact(contactId),\n    {\n      select: (response) => response.data,\n    }\n  );\n  const { mutateAsync, isLoading: isUpdating } = useMutation<\n    unknown,\n    unknown,\n    { value: ContactPayload; original: Contact }\n  >(updateContact, {\n    onSuccess: () => {\n      queryClient.invalidateQueries(getQueryKeys());\n    },\n  });\n\n  const hasActivity = isFetching || isUpdating;\n\n  const handleCancel = () => {\n    push(`/contacts/${contactId}`);\n  };\n\n  // XXX: problem, API return status 500 instead of 404 in this case\n  if (!contact && isError && error?.response?.status === 404) {\n    return <PageNotFound />;\n  }\n\n  if (!contact && isError) {\n    return <PageError />;\n  }\n\n  const handleSubmit: FormikConfig<ContactPayload>['onSubmit'] = async (\n    value\n  ) => {\n    if (!contact) {\n      return;\n    }\n    try {\n      await mutateAsync({ value, original: contact });\n      push(`/contacts/${contactId}`);\n    } catch (err) {\n      const contactErrors = handleContactSaveErrors(err);\n\n      const contactIdsToGet = contactErrors\n        ?.filter(\n          (contactErr) =>\n            contactErr.type === CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT\n        )\n        .map((contactErr: ContactError) => contactErr.ownerContactId);\n\n      const contactsUsed = await Promise.all(\n        contactIdsToGet?.map((ctId) => getContact(ctId)) || []\n      );\n      // XXX: may be it would more efficient to use `useContacts`\n      const contactsById = fromPairs(\n        contactsUsed.map((response) => [\n          response.data.contact_id,\n          response.data,\n        ])\n      );\n\n      const message = contactErrors?.map((contactErr, index) => {\n        switch (contactErr.type) {\n          case CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT:\n            // eslint-disable-next-line no-case-declarations\n            const { address, ownerContactId } = contactErr as ContactError;\n\n            return (\n              <p key={`${contactErr.type}_${address}`}>\n                <Trans\n                  id=\"contact.feedback.unable_to_save_address_already_used\"\n                  message='The address \"{address}\" belongs to <0>{name}</0>. You can remove it from that contact before using it here.'\n                  values={{\n                    name:\n                      (contactsById[ownerContactId] &&\n                        contactsById[ownerContactId].given_name) ||\n                      '?',\n                    address,\n                  }}\n                  components={[<Link to={`/contacts/${ownerContactId}`} />]}\n                />\n              </p>\n            );\n          default:\n            return (\n              <p key={index}>\n                <Trans\n                  id=\"contact.feedback.unable_to_save\"\n                  message=\"Unable to save the contact\"\n                />\n              </p>\n            );\n        }\n      });\n\n      dispatch(\n        notifyError({\n          duration: 0,\n          message,\n        })\n      );\n    }\n  };\n\n  return (\n    <ContactPageWrapper contact={contact} isEditing hasActivity={hasActivity}>\n      <ContactForm\n        initialValues={contact || emptyContact}\n        onCancel={handleCancel}\n        onSubmit={handleSubmit}\n        hasActivity={hasActivity}\n        formName={`edit-contact_${\n          contact?.contact_id || emptyContact.contact_id || 'empty'\n        }`}\n      />\n    </ContactPageWrapper>\n  );\n}\nexport default EditContact;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/NewContact.spec.tsx",
    "content": "import * as React from 'react';\nimport {\n  render,\n  screen,\n  waitForElementToBeRemoved,\n} from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { rest } from 'msw';\nimport { getNewContact } from 'src/modules/contact';\nimport { server } from 'test/server';\nimport { AllProviders } from 'test/providers';\nimport NewContact from './NewContact';\n\nconst mockHistory = {\n  push: jest.fn(),\n};\n\njest.mock('react-router-dom', () => ({\n  ...(jest.requireActual('react-router-dom') as any),\n  useHistory: () => mockHistory,\n}));\n\ndescribe('NewContact', () => {\n  it('renders', async () => {\n    const id = 'abcd';\n    const contactPayload = getNewContact();\n    const apiPost = jest.fn();\n    server.use(\n      rest.post('/api/v2/contacts', (req, res, ctx) => {\n        apiPost(req.body);\n\n        return res(\n          ctx.json({ location: `/api/v2/contacts/${id}`, contact_id: id }),\n          ctx.status(200)\n        );\n      })\n    );\n\n    render(<NewContact />, {\n      wrapper: AllProviders,\n    });\n    expect(screen.getByText('Contact details')).toBeInTheDocument();\n\n    userEvent.type(\n      screen.getByRole('textbox', { name: 'Firstname' }),\n      'Bender'\n    );\n\n    userEvent.click(screen.getByRole('button', { name: 'Save' }));\n    expect(await screen.findByText('Loading …')).toBeInTheDocument();\n\n    await waitForElementToBeRemoved(screen.queryByText('Loading …'));\n    expect(apiPost).toHaveBeenCalledWith({\n      ...contactPayload,\n      given_name: 'Bender',\n    });\n\n    expect(mockHistory.push).toHaveBeenCalledWith(`/contacts/${id}`);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/NewContact.tsx",
    "content": "import { Trans } from '@lingui/react';\nimport { AxiosResponse } from 'axios';\nimport { FormikConfig } from 'formik';\nimport { fromPairs } from 'lodash';\nimport * as React from 'react';\nimport { useMutation, useQueryClient } from 'react-query';\nimport { useDispatch } from 'react-redux';\nimport { Link, useHistory } from 'react-router-dom';\nimport {\n  createContact,\n  getQueryKeys,\n  getContact,\n  PostContactSuccess,\n} from 'src/modules/contact/query';\nimport { ContactPayload } from 'src/modules/contact/types';\nimport { useCloseTab, useCurrentTab } from 'src/modules/tab';\nimport { notifyError } from 'src/modules/userNotify';\nimport { getNewContact } from 'src/modules/contact';\nimport ContactPageWrapper from './components/ContactPageWrapper';\nimport ContactForm from './ContactForm';\nimport {\n  ContactError,\n  CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT,\n  handleContactSaveErrors,\n} from './services/handleContactSaveErrors';\n\nfunction NewContact(): React.ReactElement<typeof ContactPageWrapper> {\n  const dispatch = useDispatch();\n  const queryClient = useQueryClient();\n  const closeTab = useCloseTab();\n  const currentTab = useCurrentTab();\n\n  const { push } = useHistory();\n\n  const { mutateAsync, isLoading: isUpdating } = useMutation<\n    AxiosResponse<PostContactSuccess>,\n    unknown,\n    { value: ContactPayload }\n  >(createContact, {\n    onSuccess: () => {\n      queryClient.invalidateQueries(getQueryKeys());\n    },\n  });\n  const contact = getNewContact();\n  const handleSubmit: FormikConfig<ContactPayload>['onSubmit'] = async (\n    value\n  ) => {\n    try {\n      const {\n        data: { contact_id: contactId },\n      } = await mutateAsync({ value });\n      push(`/contacts/${contactId}`);\n      closeTab(currentTab);\n    } catch (err) {\n      const contactErrors = handleContactSaveErrors(err);\n\n      const contactIdsToGet = contactErrors\n        ?.filter(\n          (contactErr) =>\n            contactErr.type === CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT\n        )\n        .map((contactErr: ContactError) => contactErr.ownerContactId);\n\n      const contactsUsed = await Promise.all(\n        contactIdsToGet?.map((ctId) => getContact(ctId)) || []\n      );\n      const contactsById = fromPairs(\n        contactsUsed.map((response) => [\n          response.data.contact_id,\n          response.data,\n        ])\n      );\n\n      const message = contactErrors?.map((contactErr, index) => {\n        switch (contactErr.type) {\n          case CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT:\n            // eslint-disable-next-line no-case-declarations\n            const { address, ownerContactId } = contactErr as ContactError;\n\n            return (\n              <p key={`${contactErr.type}_${address}`}>\n                <Trans\n                  id=\"contact.feedback.unable_to_save_address_already_used\"\n                  message='The address \"{address}\" belongs to <0>{name}</0>. You can remove it from that contact before using it here.'\n                  values={{\n                    name:\n                      (contactsById[ownerContactId] &&\n                        contactsById[ownerContactId].given_name) ||\n                      '?',\n                    address,\n                  }}\n                  components={[<Link to={`/contacts/${ownerContactId}`} />]}\n                />\n              </p>\n            );\n          default:\n            return (\n              <p key={index}>\n                <Trans\n                  id=\"contact.feedback.unable_to_save\"\n                  message=\"Unable to save the contact\"\n                />\n              </p>\n            );\n        }\n      });\n\n      dispatch(\n        notifyError({\n          duration: 0,\n          message,\n        })\n      );\n    }\n  };\n\n  const handleCancel = () => {\n    closeTab();\n  };\n\n  return (\n    <ContactPageWrapper isEditing>\n      <ContactForm\n        initialValues={contact}\n        onCancel={handleCancel}\n        onSubmit={handleSubmit}\n        hasActivity={isUpdating}\n        initialProfileSectionOpen\n        formName=\"new-contact\"\n      />\n    </ContactPageWrapper>\n  );\n}\n\nexport default NewContact;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/AddFormFieldForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18nProps, withI18n } from '@lingui/react';\nimport { FieldArray } from 'formik';\nimport {\n  Button,\n  Icon,\n  SelectFieldGroup,\n  FormGrid,\n  FormRow,\n  FormColumn,\n  Legend,\n  TextList,\n  TextItem,\n} from 'src/components';\nimport {\n  EmailPayload,\n  IMPayload,\n  PhonePayload,\n} from 'src/modules/contact/types';\nimport './style.scss';\n\ntype FormType = 'emails' | 'phones' | 'ims';\ntype FormData = PhonePayload | EmailPayload | IMPayload;\nconst getNewFormData = (formType: FormType): FormData | undefined => {\n  switch (formType) {\n    case 'emails':\n      return { type: '', address: '' };\n    case 'phones':\n      return { type: '', number: '' };\n    case 'ims':\n      return { type: '', address: '' };\n    default:\n      return undefined;\n  }\n};\n\ntype Props = withI18nProps;\n\nfunction AddFormFieldForm({\n  i18n,\n}: Props): React.ReactElement<typeof TextList> {\n  const [formType, setFormType] = React.useState<FormType>('emails');\n\n  const handleSelectChange = (ev) => {\n    const { value } = ev.target;\n    setFormType(value);\n  };\n\n  const typeOptions = [\n    {\n      label: i18n._(\n        /* i18n */ 'contact.form-selector.email_form.label',\n        undefined,\n        {\n          message: 'Email',\n        }\n      ),\n      value: 'emails',\n    },\n    {\n      label: i18n._(\n        /* i18n */ 'contact.form-selector.phone_form.label',\n        undefined,\n        {\n          message: 'Phone',\n        }\n      ),\n      value: 'phones',\n    },\n    {\n      label: i18n._(\n        /* i18n */ 'contact.form-selector.im_form.label',\n        undefined,\n        {\n          message: 'IM',\n        }\n      ),\n      value: 'ims',\n    },\n    // ...(hasBirthday ? [] : [{\n    //   label:\n    //    i18n._(/* i18n */ 'contact.form-selector.birthday_form.label', undefined, { message: 'Birthday' }),\n    //   value: 'info.birthday',\n    // }]),\n  ];\n\n  return (\n    <TextList className=\"m-add-form-field-form\">\n      <TextItem>\n        <FormGrid>\n          <FormRow>\n            <FormColumn size=\"shrink\">\n              <Legend>\n                <Icon type=\"crosshairs\" rightSpaced />\n                <span className=\"m-add-form-field-form__legend\">\n                  <Trans\n                    id=\"contact.form-selector.add_new_field.label\"\n                    message=\"Add a new field\"\n                  />\n                </span>\n              </Legend>\n            </FormColumn>\n            <FormColumn size=\"shrink\" fluid bottomSpace>\n              <SelectFieldGroup\n                name=\"selectedForm\"\n                onChange={handleSelectChange}\n                value={formType}\n                options={typeOptions}\n                showLabelforSr\n                label={i18n._(\n                  /* i18n */ 'contact.form-selector.add_new_field.label',\n                  undefined,\n                  { message: 'Add a new field' }\n                )}\n              />\n            </FormColumn>\n            <FormColumn\n              size=\"shrink\"\n              className=\"m-add-form-field-form__col-button\"\n            >\n              <FieldArray\n                name={formType}\n                render={({ push }) => (\n                  <Button\n                    icon=\"plus\"\n                    shape=\"plain\"\n                    onClick={() => push(getNewFormData(formType))}\n                  >\n                    <Trans\n                      id=\"contact.action.add_new_field\"\n                      message=\"Add new\"\n                    />\n                  </Button>\n                )}\n              />\n            </FormColumn>\n          </FormRow>\n        </FormGrid>\n      </TextItem>\n    </TextList>\n  );\n}\n\nexport default withI18n()(AddFormFieldForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/AddFormFieldForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-add-form-field-form {\n  &__col-button {\n    @include flex-grid-size(shrink);\n  }\n\n  @include breakpoint(medium) {\n    &__legend {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/AddressDetails/index.tsx",
    "content": "import * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport { Icon } from 'src/components';\nimport { PostalAddress } from 'src/modules/contact/types';\nimport './style.scss';\n\ninterface Props extends withI18nProps {\n  address: PostalAddress;\n}\n\nfunction AddressDetails({ i18n, address }: Props) {\n  const addressTypesTranslations = {\n    work: i18n._(/* i18n */ 'contact.address_type.work', undefined, {\n      message: 'Professional',\n    }),\n    home: i18n._(/* i18n */ 'contact.address_type.home', undefined, {\n      message: 'Personal',\n    }),\n    other: i18n._(/* i18n */ 'contact.address_type.other', undefined, {\n      message: 'Other',\n    }),\n  };\n\n  return (\n    <span className=\"m-address-details\">\n      <Icon type=\"map-marker\" rightSpaced />\n      <address className=\"m-address-details__postal-address\">\n        {address.street}\n        {address.street && ', '}\n        {address.postal_code}\n        {address.postal_code && ' '}\n        {address.city}\n        {address.city && ' '}\n        {address.country}\n        {address.country && ' '}\n        {address.region}\n      </address>{' '}\n      {(address.label || address.type) && (\n        <small>\n          <em>\n            ({address.label}\n            {address.label && ' '}\n            {address.type && addressTypesTranslations[address.type]})\n          </em>\n        </small>\n      )}\n    </span>\n  );\n}\n\nexport default withI18n()(AddressDetails);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/AddressDetails/style.scss",
    "content": "@import '../../../../styles/common';\n\n.m-address-details {\n  &__postal-address {\n    display: inline-block;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/AddressForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, useLingui } from '@lingui/react';\nimport {\n  CountryDropdown,\n  CountryDropdownProps,\n  RegionDropdown,\n  RegionDropdownProps,\n} from 'react-country-region-selector';\nimport { Field, FieldProps, getIn, useFormikContext } from 'formik';\nimport { FormikSelectFieldGroup } from 'src/components/SelectFieldGroup';\nimport { FormikTextFieldGroup } from 'src/components/TextFieldGroup';\nimport { ContactPayload } from 'src/modules/contact/types';\nimport {\n  Button,\n  Icon,\n  Fieldset,\n  Legend,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from 'src/components';\nimport { validateRequired } from 'src/modules/form/services/validators';\nimport './style.scss';\nimport { ItemProps } from '../FormCollection';\n\nconst ADDRESS_TYPES = ['', 'work', 'home', 'other'];\n\ntype CountryFieldProps = FieldProps & CountryDropdownProps;\n\nfunction CountryField({\n  id,\n  form,\n  field,\n  meta,\n  ...props\n}: CountryFieldProps): React.ReactElement<CountryDropdownProps> {\n  const onChange = (val, evt) => field.onChange(evt);\n\n  return (\n    <CountryDropdown\n      id={id}\n      {...field}\n      {...props}\n      // @ts-ignore: not typed in CountryDropdown but 2nd param is the native event\n      onChange={onChange}\n    />\n  );\n}\n\ntype RegionFieldProps = FieldProps<\n  NonNullable<ContactPayload['addresses']>[number]['region']\n> &\n  RegionDropdownProps & { countryFieldName: string };\nfunction RegionField({\n  countryFieldName,\n  id,\n  form,\n  field,\n  meta,\n  ...props\n}: RegionFieldProps): React.ReactElement<RegionDropdownProps> {\n  const { values } = useFormikContext<ContactPayload>();\n  const country: string = getIn(values, countryFieldName);\n\n  const { onBlur } = field;\n  const onChange = (val, evt) => field.onChange(evt);\n\n  return (\n    // @ts-ignore cf. below\n    <RegionDropdown\n      id={id}\n      {...field}\n      {...props}\n      country={country}\n      // @ts-ignore: bad type in RegionDropdown\n      onBlur={onBlur}\n      // @ts-ignore: not typed in CountryDropdown but 2nd param is the native event\n      onChange={onChange}\n    />\n  );\n}\n\nfunction AddressForm({\n  onDelete,\n  name,\n}: ItemProps): React.ReactElement<typeof FormGrid> {\n  const { i18n } = useLingui();\n  const { values } = useFormikContext<ContactPayload>();\n  const country = values[`${name}.country`];\n\n  const addressTypes = {\n    work: i18n._(/* i18n */ 'contact.address_type.work', undefined, {\n      message: 'Professional',\n    }),\n    home: i18n._(/* i18n */ 'contact.address_type.home', undefined, {\n      message: 'Personal',\n    }),\n    other: i18n._(/* i18n */ 'contact.address_type.other', undefined, {\n      message: 'Other',\n    }),\n  };\n\n  const addressTypeOptions = ADDRESS_TYPES.map((value) => ({\n    value,\n    label: addressTypes[value] || '',\n  }));\n\n  return (\n    <FormGrid className=\"m-address-form\">\n      <Fieldset>\n        <FormRow>\n          <FormColumn>\n            <Legend>\n              <Icon rightSpaced type=\"map-marker\" />\n              <Trans\n                id=\"contact.address_form.legend\"\n                message=\"Postal address\"\n              />\n            </Legend>\n          </FormColumn>\n          {/* {errors.length > 0 && (\n              <FormColumn>\n                <FieldErrors errors={errors} />\n              </FormColumn>\n            )} */}\n        </FormRow>\n        <FormRow>\n          <FormColumn bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              name={`${name}.street`}\n              label={i18n._(\n                /* i18n */ 'contact.address_form.street.label',\n                undefined,\n                {\n                  message: 'Street',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */\n                  'contact.address_form.street.label',\n                  undefined,\n                  {\n                    message: 'Street',\n                  }\n                ),\n                expanded: true,\n              }}\n              showLabelforSr\n            />\n          </FormColumn>\n        </FormRow>\n        <FormRow>\n          <FormColumn size=\"small\" bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              name={`${name}.postal_code`}\n              label={i18n._(\n                /* i18n */ 'contact.address_form.postal_code.label',\n                undefined,\n                {\n                  message: 'Postal Code',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */\n                  'contact.address_form.postal_code.label',\n                  undefined,\n                  { message: 'Postal Code' }\n                ),\n                expanded: true,\n              }}\n              showLabelforSr\n            />\n          </FormColumn>\n          <FormColumn size=\"large\" bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              validate={validateRequired(i18n)}\n              name={`${name}.city`}\n              label={i18n._(\n                /* i18n */ 'contact.address_form.city.label',\n                undefined,\n                {\n                  message: 'City',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */\n                  'contact.address_form.city.label',\n                  undefined,\n                  {\n                    message: 'City',\n                  }\n                ),\n                expanded: true,\n                required: true,\n              }}\n              showLabelforSr\n            />\n          </FormColumn>\n        </FormRow>\n        <FormRow>\n          <FormColumn size=\"medium\" bottomSpace>\n            {\n              // TODO: insert select-wrapper to fit SelectFieldGroup architecture\n            }\n            <label className=\"show-for-sr\" htmlFor=\"contact-adress-country\">\n              <Trans\n                id=\"contact.address_form.country.label\"\n                message=\"Country\"\n              />\n            </label>\n            <Field\n              component={CountryField}\n              id=\"contact-adress-country\"\n              name={`${name}.country`}\n              classes=\"m-address-form__select\"\n              defaultOptionLabel={i18n._(\n                /* i18n */ 'contact.address_form.select_country',\n                undefined,\n                { message: 'Country' }\n              )}\n            />\n          </FormColumn>\n          <FormColumn size=\"medium\" bottomSpace>\n            {\n              // TODO: insert select-wrapper to fit SelectFieldGroup architecture\n            }\n            <label className=\"show-for-sr\" htmlFor=\"contact-adress-region\">\n              <Trans id=\"contact.address_form.region.label\" message=\"Region\" />\n            </label>\n            <Field\n              component={RegionField}\n              id=\"contact-adress-region\"\n              countryFieldName={`${name}.country`}\n              name={`${name}.region`}\n              classes=\"m-address-form__select\"\n              defaultOptionLabel={i18n._(\n                /* i18n */ 'contact.address_form.select_region',\n                undefined,\n                { message: 'Region' }\n              )}\n              country={country}\n            />\n          </FormColumn>\n          <FormColumn size=\"shrink\" bottomSpace fluid>\n            <Field\n              component={FormikSelectFieldGroup}\n              name={`${name}.type`}\n              label={i18n._(\n                /* i18n */ 'contact.address_form.type.label',\n                undefined,\n                {\n                  message: 'Type',\n                }\n              )}\n              options={addressTypeOptions}\n              showLabelforSr\n            />\n          </FormColumn>\n          <FormColumn size=\"shrink\" className=\"m-address-form__col-button\">\n            <Button color=\"alert\" icon=\"remove\" onClick={onDelete} />\n          </FormColumn>\n        </FormRow>\n      </Fieldset>\n    </FormGrid>\n  );\n}\n\nexport default AddressForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/AddressForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-form-element';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-address-form {\n  &__select {\n    // TODO: insert a wrapper to select to fit new SelectFieldGroup architecture\n    @include o-form-element;\n    @include o-form-element--light;\n  }\n\n  &__col-button {\n    @include flex-grid-size(shrink);\n  }\n\n  &__button {\n    margin-left: map_get($co-form__spacing, 'medium');\n  }\n\n  @include breakpoint(medium) {\n    &__button {\n      margin-left: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/BirthdayDetails/index.tsx",
    "content": "import * as React from 'react';\nimport { Icon } from 'src/components';\n\ninterface Props {\n  birthday: string;\n}\nfunction BirthdayDetails({ birthday }: Props): JSX.Element {\n  return (\n    <span className=\"m-birthday-details\">\n      {birthday && <Icon rightSpaced type=\"birthday-cake\" />}\n      {birthday}\n    </span>\n  );\n}\n\nexport default BirthdayDetails;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/BirthdayForm/index.ts",
    "content": "import { createSelector } from 'reselect';\nimport { bindActionCreators, compose } from 'redux';\nimport { connect } from 'react-redux';\nimport { withI18n } from '@lingui/react';\nimport { autofill } from 'redux-form';\nimport Presenter from './presenter';\n\n// const formSelector = state => state.form;\n// const formNameSelector = (state, ownProps) => ownProps.form;\n\nconst mapStateToProps = createSelector([], () => ({}));\nconst mapDispatchToProps = (dispatch) =>\n  bindActionCreators(\n    {\n      changeField: autofill,\n    },\n    dispatch\n  );\n\nexport default compose(\n  connect(mapStateToProps, mapDispatchToProps),\n  withI18n()\n)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/BirthdayForm/presenter.tsx",
    "content": "import * as React from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans } from '@lingui/react';\nimport { Field } from 'redux-form';\nimport renderReduxField from '../../../../services/renderReduxField';\nimport {\n  Button,\n  Icon,\n  FieldErrors,\n  DatePickerGroup as DatePickerGroupBase,\n  Fieldset,\n  Legend,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from '../../../../components';\n\nconst DatePickerGroup = renderReduxField(DatePickerGroupBase);\n\nclass BirthdayForm extends React.PureComponent {\n  static propTypes = {\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    form: PropTypes.string.isRequired,\n    changeField: PropTypes.func.isRequired,\n    errors: PropTypes.arrayOf(PropTypes.shape({})),\n  };\n\n  static defaultProps = {\n    errors: [],\n  };\n\n  handleBirthdayChanges: any;\n\n  // handleBirthdayChanges = (date) => {\n  //   this.setState(prevState => ({\n  //     contact: {\n  //       ...prevState.contact,\n  //       infos: {\n  //         ...prevState.contact.infos,\n  //         birthday: date.format('YYYY-MM-DD'),\n  //       },\n  //     },\n  //   }));\n  // }\n\n  handleDelete = () => {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'changeField' does not exist on type 'Rea... Remove this comment to see the full error message\n    const { changeField, form } = this.props;\n    // tips to remove a field: `change` handle empty string and launch unregister field action\n    changeField(form, 'info.birthday', '');\n  };\n\n  render() {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'i18n' does not exist on type 'Readonly<{... Remove this comment to see the full error message\n    const { i18n, errors } = this.props;\n\n    return (\n      <FormGrid className=\"m-email-form\">\n        <Fieldset>\n          <FormRow>\n            <FormColumn size=\"shrink\">\n              <Legend>\n                <Icon type=\"envelope\" rightSpaced />\n                <span className=\"m-email-form__legend\">\n                  <Trans id=\"contact.email_form.legend\" message=\"Email\" />\n                </span>\n              </Legend>\n            </FormColumn>\n            {errors.length > 0 && (\n              <FormColumn>\n                <FieldErrors errors={errors} />\n              </FormColumn>\n            )}\n            <FormColumn size=\"shrink\" bottomSpace>\n              {/* // prevent selecting dates after today\n                  // see https://github.com/Hacker0x01/react-datepicker */}\n              <Field\n                component={DatePickerGroup}\n                name=\"info.birthday\"\n                id=\"contact-form-birthday\"\n                className=\"m-contact-profile-form__birthday\"\n                inputClassName=\"m-contact-profile-form__birthday-input\"\n                label={i18n._(\n                  /* i18n */ 'contact_profile.form.birthday.label',\n                  null,\n                  {\n                    message: 'Birthday',\n                  }\n                )}\n                onDateChange={this.handleBirthdayChanges}\n                showMonthDropdown\n                showYearDropdown\n                dropdownMode=\"select\"\n                // selected={this.state.contact.infos.birthday ?\n                //   this.state.contact.infos.birthday : null}\n              />\n            </FormColumn>\n            <FormColumn className=\"m-birthday-form__col-button\">\n              <Button icon=\"remove\" color=\"alert\" onClick={this.handleDelete} />\n            </FormColumn>\n          </FormRow>\n        </Fieldset>\n      </FormGrid>\n    );\n  }\n}\n\nexport default BirthdayForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ContactPageWrapper.tsx",
    "content": "import { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport * as React from 'react';\nimport { useDispatch } from 'react-redux';\nimport { useHistory } from 'react-router-dom';\nimport {\n  ActionBar,\n  ActionBarButton,\n  ActionBarWrapper,\n  Badge,\n  Confirm,\n  Modal,\n  PageTitle,\n  Spinner,\n} from 'src/components';\nimport { Contact } from 'src/modules/contact/types';\nimport { ScrollDetector } from 'src/modules/scroll';\nimport { useCloseTab, useCurrentTab } from 'src/modules/tab';\nimport {\n  getCleanedTagCollection,\n  getTagLabel,\n  ManageEntityTags,\n  useTags,\n} from 'src/modules/tags';\nimport { deleteContact, getQueryKeys } from 'src/modules/contact/query';\nimport { useMutation, useQueryClient } from 'react-query';\nimport { notifyError } from 'src/modules/userNotify';\nimport { useUser } from 'src/modules/user';\n\nimport '../style.scss';\nimport '../contact-action-bar.scss';\nimport '../contact-main-title.scss';\n\ninterface Props extends withI18nProps {\n  children: React.ReactNode;\n  hasActivity?: boolean;\n  contact?: Contact;\n  isEditing?: boolean;\n}\nfunction ContactPageWrapper({\n  children,\n  hasActivity,\n  contact,\n  i18n,\n  isEditing,\n}: Props): JSX.Element {\n  const queryClient = useQueryClient();\n  const dispatch = useDispatch();\n  const { push } = useHistory();\n  const closeTab = useCloseTab();\n  const currentTab = useCurrentTab();\n  const [isTagModalOpen, setIsTagModalOpen] = React.useState(false);\n  const { user } = useUser();\n  const { tags } = useTags();\n\n  const { mutateAsync, isLoading: isDeleting } = useMutation<\n    unknown,\n    unknown,\n    string\n  >(deleteContact, {\n    onSuccess: () => {\n      queryClient.removeQueries(\n        getQueryKeys({ contactId: contact?.contact_id })\n      );\n      queryClient.invalidateQueries(getQueryKeys());\n    },\n  });\n\n  const contactIsUser =\n    contact?.contact_id &&\n    user &&\n    user.contact.contact_id === contact?.contact_id;\n\n  const nbTags = contact?.tags?.length || 0;\n\n  const handleDelete = async () => {\n    if (!contact) {\n      return;\n    }\n\n    try {\n      await mutateAsync(contact.contact_id);\n      push('/contacts');\n      closeTab(currentTab);\n    } catch (err) {\n      dispatch(\n        notifyError({\n          message: (\n            <Trans\n              id=\"contact.feedback.unable_to_delete\"\n              message=\"Unable to delete the contact\"\n            />\n          ),\n        })\n      );\n    }\n  };\n\n  const handleClickEditContact = () => {\n    if (!contact) {\n      return;\n    }\n    push(`/contacts/${contact.contact_id}/edit`);\n  };\n\n  return (\n    <div className=\"s-contact\">\n      <PageTitle />\n\n      <ScrollDetector\n        offset={136}\n        render={(isSticky) => (\n          <ActionBarWrapper isSticky={isSticky}>\n            <ActionBar\n              hr={false}\n              isLoading={hasActivity || isDeleting}\n              actionsNode={\n                <div className=\"s-contact-action-bar\">\n                  {contact && (\n                    <>\n                      <Trans id=\"contact.action-bar.label\" message=\"Contact:\" />\n                      {!contactIsUser && (\n                        <Confirm\n                          onConfirm={handleDelete}\n                          title={\n                            <Trans\n                              id=\"contact.confirm-delete.title\"\n                              message=\"Delete the contact\"\n                            />\n                          }\n                          content={\n                            <Trans\n                              id=\"contact.confirm-delete.content\"\n                              message=\"The deletion is permanent, are you sure you want to delete this contact ?\"\n                            />\n                          }\n                          render={(confirm) => (\n                            <ActionBarButton\n                              onClick={confirm}\n                              display=\"inline\"\n                              icon={\n                                isDeleting ? (\n                                  <Spinner\n                                    svgTitleId=\"delete-contact-spinner\"\n                                    isLoading\n                                    display=\"inline\"\n                                  />\n                                ) : (\n                                  'trash'\n                                )\n                              }\n                              noDecoration\n                            >\n                              <Trans\n                                id=\"contact.action.delete_contact\"\n                                message=\"Delete\"\n                              />\n                            </ActionBarButton>\n                          )}\n                        />\n                      )}\n                      {/* TODO: replace by a link */}\n                      {!isEditing && (\n                        <ActionBarButton\n                          onClick={handleClickEditContact}\n                          display=\"inline\"\n                          noDecoration\n                          icon=\"list-ul\"\n                        >\n                          <Trans\n                            id=\"contact.action.edit_contact\"\n                            message=\"Edit contact\"\n                          />\n                        </ActionBarButton>\n                      )}\n                      <ActionBarButton\n                        onClick={() => setIsTagModalOpen(true)}\n                        display=\"inline\"\n                        noDecoration\n                        icon=\"tag\"\n                      >\n                        <Trans\n                          id=\"contact.action.edit_tags\"\n                          message=\"Edit tags\"\n                        />\n                      </ActionBarButton>\n                      <Modal\n                        isOpen={isTagModalOpen}\n                        contentLabel={i18n._(\n                          /* i18n */ 'tags.header.label',\n                          undefined,\n                          {\n                            message: 'Tags',\n                          }\n                        )}\n                        title={\n                          <Trans\n                            id=\"tags.header.title\"\n                            message=\"Tags <0>(Total: {nb})</0>\"\n                            values={{ nb: nbTags }}\n                            components={[\n                              <span className=\"m-tags-form__count\" />,\n                            ]}\n                          />\n                        }\n                        onClose={() => setIsTagModalOpen(false)}\n                      >\n                        <ManageEntityTags type=\"contact\" entity={contact} />\n                      </Modal>\n                    </>\n                  )}\n                </div>\n              }\n            />\n          </ActionBarWrapper>\n        )}\n      />\n\n      {contact?.tags && (\n        <div className=\"s-contact__tags\">\n          {getCleanedTagCollection(tags, contact.tags).map((tag) => (\n            <Badge\n              key={tag.name}\n              rightSpaced\n              to={`/search-results?term=${getTagLabel(\n                i18n,\n                tag\n              )}&doctype=contact`}\n            >\n              {getTagLabel(i18n, tag)}\n            </Badge>\n          ))}\n        </div>\n      )}\n\n      {children}\n    </div>\n  );\n}\n\nexport default withI18n()(ContactPageWrapper);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ContactProfileForm/index.tsx",
    "content": "import { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport * as React from 'react';\nimport { Field } from 'formik';\nimport { Button, FormikTextFieldGroup } from 'src/components';\nimport ContactTitleField from '../ContactTitleField';\nimport './style.scss';\n\ninterface Props extends withI18nProps {\n  isNew?: boolean;\n}\nfunction ContactProfileForm({ isNew = false, i18n }: Props) {\n  const [isExpanded, setIsExpanded] = React.useState(isNew);\n\n  const toggleExpandForm = () => {\n    setIsExpanded((prevState) => !prevState);\n  };\n\n  return (\n    <div className=\"m-contact-profile-form\">\n      <div className=\"m-contact-profile-form__header\">\n        <Field>\n          {({ field }) => (\n            <ContactTitleField\n              contact={field.value}\n              onClick={toggleExpandForm}\n              className=\"m-contact-profile-form__title\"\n            />\n          )}\n        </Field>\n        <Button\n          icon={isExpanded ? 'caret-up' : 'caret-down'}\n          display=\"inline\"\n          onClick={toggleExpandForm}\n          className=\"m-contact-profile-form__expand-button\"\n        >\n          <span className=\"show-for-sr\">\n            <Trans id=\"contact_profile.action.edit_contact\" message=\"Edit\" />\n          </span>\n        </Button>\n      </div>\n\n      {isExpanded && (\n        <div className=\"m-contact-profile-form__expanded-form\">\n          <Field\n            id=\"name_prefix\"\n            component={FormikTextFieldGroup}\n            inputProps={{\n              // name: 'name_prefix',\n              placeholder: i18n._(\n                /* i18n */ 'contact_profile.form.name-prefix.label',\n                undefined,\n                { message: 'Prefix' }\n              ),\n              expanded: true,\n            }}\n            showLabelforSr\n            className=\"m-contact-profile-form__input\"\n            label={i18n._(\n              /* i18n */ 'contact_profile.form.name-prefix.label',\n              undefined,\n              {\n                message: 'Prefix',\n              }\n            )}\n            name=\"name_prefix\"\n          />\n          <Field\n            id=\"given_name\"\n            component={FormikTextFieldGroup}\n            className=\"m-contact-profile-form__input\"\n            label={i18n._(\n              /* i18n */ 'contact_profile.form.firstname.label',\n              undefined,\n              {\n                message: 'Firstname',\n              }\n            )}\n            inputProps={{\n              placeholder: i18n._(\n                /* i18n */ 'contact_profile.form.firstname.label',\n                undefined,\n                { message: 'Firstname' }\n              ),\n              expanded: true,\n            }}\n            name=\"given_name\"\n            showLabelforSr\n          />\n          <Field\n            id=\"family_name\"\n            component={FormikTextFieldGroup}\n            className=\"m-contact-profile-form__input\"\n            label={i18n._(\n              /* i18n */ 'contact_profile.form.lastname.label',\n              undefined,\n              {\n                message: 'Lastname',\n              }\n            )}\n            inputProps={{\n              placeholder: i18n._(\n                /* i18n */ 'contact_profile.form.lastname.label',\n                undefined,\n                {\n                  message: 'Lastname',\n                }\n              ),\n              expanded: true,\n            }}\n            name=\"family_name\"\n            showLabelforSr\n          />\n          <Field\n            id=\"name_suffix\"\n            component={FormikTextFieldGroup}\n            className=\"m-contact-profile-form__input\"\n            label={i18n._(\n              /* i18n */ 'contact_profile.form.name-suffix.label',\n              undefined,\n              {\n                message: 'Suffix',\n              }\n            )}\n            inputProps={{\n              placeholder: i18n._(\n                /* i18n */ 'contact_profile.form.name-suffix.label',\n                undefined,\n                { message: 'Suffix' }\n              ),\n              expanded: true,\n            }}\n            name=\"name_suffix\"\n            showLabelforSr\n          />\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport default withI18n()(ContactProfileForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ContactProfileForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-contact-profile-form {\n  margin: $co-margin 0;\n\n  &__header,\n  &__expanded-form {\n    @include flex-grid-row($size: expand);\n\n    margin-bottom: calc($co-margin / 2);\n  }\n\n  &__title {\n    @include flex-grid-size;\n  }\n\n  &__expand-button {\n    @include flex-grid-column(shrink);\n  }\n\n  &__input {\n    @include flex-grid-size(12);\n\n    margin-bottom: calc($co-margin / 2);\n  }\n\n  &__birthday,\n  &__birthday-input {\n    width: 100%;\n  }\n\n  &__birthday-input {\n    margin-bottom: calc($co-margin / 2);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ContactTitleField/index.tsx",
    "content": "import classnames from 'classnames';\nimport * as React from 'react';\nimport { useSelector } from 'react-redux';\nimport { RawButton } from 'src/components';\nimport { ContactPayload } from 'src/modules/contact/types';\nimport { settingsSelector } from 'src/modules/settings';\nimport { formatName } from 'src/modules/contact';\nimport { RootState } from 'src/store/reducer';\nimport './style.scss';\n\ninterface Props {\n  contact: ContactPayload;\n  onClick: () => void;\n  className: string;\n}\nfunction ContactTitleField({\n  contact,\n  onClick,\n  className,\n}: Props): React.ReactElement<typeof RawButton> {\n  const {\n    settings: { contact_display_format: format },\n  } = useSelector<RootState, RootState['settings']>(settingsSelector);\n\n  return (\n    <RawButton\n      className={classnames(className, 's-contact-title-field')}\n      onClick={onClick}\n    >\n      {formatName({ contact, format })}\n    </RawButton>\n  );\n}\n\nexport default ContactTitleField;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ContactTitleField/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-form-element';\n\n.s-contact-title-field {\n  @include o-form-element;\n  @include o-form-element--light;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/EmailDetails/index.tsx",
    "content": "import * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport { Icon } from 'src/components';\nimport { Email } from 'src/modules/contact/types';\n\ninterface Props extends withI18nProps {\n  email: Email;\n}\nfunction EmailDetails({ email, i18n }: Props) {\n  const emailTypesTranslations = {\n    work: i18n._(/* i18n */ 'contact.email_type.work', undefined, {\n      message: 'Professional',\n    }),\n    home: i18n._(/* i18n */ 'contact.email_type.home', undefined, {\n      message: 'Personal',\n    }),\n    other: i18n._(/* i18n */ 'contact.email_type.other', undefined, {\n      message: 'Other',\n    }),\n  };\n\n  const address = !email.is_primary ? (\n    email.address\n  ) : (\n    <strong\n      title={i18n._(/* i18n */ 'contact.primary', undefined, {\n        message: 'Primary',\n      })}\n    >\n      {email.address}\n    </strong>\n  );\n\n  return (\n    <span className=\"m-email-details\">\n      <Icon rightSpaced type=\"envelope\" />\n      {address}{' '}\n      <small>\n        <em>{emailTypesTranslations[email.type]}</em>\n      </small>\n    </span>\n  );\n}\n\nexport default withI18n()(EmailDetails);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/EmailForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { Field } from 'formik';\nimport { FormikTextFieldGroup } from 'src/components/TextFieldGroup';\nimport { FormikSelectFieldGroup } from 'src/components/SelectFieldGroup';\nimport {\n  Button,\n  Icon,\n  Fieldset,\n  Legend,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from 'src/components';\nimport { validateRequired } from 'src/modules/form/services/validators';\nimport './style.scss';\nimport { ItemProps } from '../FormCollection';\n\nconst EMAIL_TYPES = ['', 'work', 'home', 'other'];\n\nfunction EmailForm({ onDelete, i18n, name }: ItemProps & withI18nProps) {\n  const addressTypes = {\n    work: i18n._(/* i18n */ 'contact.email_type.work', undefined, {\n      message: 'Professional',\n    }),\n    home: i18n._(/* i18n */ 'contact.email_type.home', undefined, {\n      message: 'Personal',\n    }),\n    other: i18n._(/* i18n */ 'contact.email_type.other', undefined, {\n      message: 'Other',\n    }),\n  };\n\n  const addressTypeOptions = EMAIL_TYPES.map((value) => ({\n    value,\n    label: addressTypes[value] || '',\n  }));\n\n  return (\n    <FormGrid className=\"m-email-form\">\n      <Fieldset>\n        <FormRow>\n          <FormColumn size=\"shrink\">\n            <Legend>\n              <Icon type=\"envelope\" />\n              <span className=\"m-email-form__legend\">\n                <Trans id=\"contact.email_form.legend\" message=\"Email\" />\n              </span>\n            </Legend>\n          </FormColumn>\n          <FormColumn size=\"shrink\" bottomSpace>\n            <Field\n              component={FormikSelectFieldGroup}\n              name={`${name}.type`}\n              label={i18n._(\n                /* i18n */ 'contact.email_form.type.label',\n                undefined,\n                {\n                  message: 'Type',\n                }\n              )}\n              showLabelforSr\n              options={addressTypeOptions}\n            />\n          </FormColumn>\n          <FormColumn size=\"medium\" fluid bottomSpace>\n            <Field\n              id={`edit-contact_${name}.address`}\n              component={FormikTextFieldGroup}\n              name={`${name}.address`}\n              validate={validateRequired(i18n)}\n              type=\"email\"\n              label={i18n._(\n                /* i18n */ 'contact.email_form.address.label',\n                undefined,\n                {\n                  message: 'Address',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */ 'contact.email_form.address.placeholder',\n                  undefined,\n                  { message: 'Email' }\n                ),\n                expanded: true,\n                required: true,\n              }}\n              showLabelforSr\n            />\n          </FormColumn>\n          <FormColumn className=\"m-email-form__col-button\">\n            <Button icon=\"remove\" color=\"alert\" onClick={onDelete} />\n          </FormColumn>\n        </FormRow>\n      </Fieldset>\n    </FormGrid>\n  );\n}\n\nexport default withI18n()(EmailForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/EmailForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-email-form {\n  &__col-button {\n    @include flex-grid-size(shrink);\n  }\n\n  &__button {\n    margin-left: map_get($co-form__spacing, 'medium');\n  }\n\n  @include breakpoint(medium) {\n    &__legend {\n      display: none;\n    }\n\n    &__button {\n      margin-left: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/FormCollection/index.tsx",
    "content": "// TODEL\n\nimport * as React from 'react';\nimport { Trans } from '@lingui/react';\nimport { Field, FieldArray } from 'formik';\nimport { Button, FormGrid, FormRow, FormColumn } from 'src/components';\nimport TextList, { TextItem } from 'src/components/TextList';\n\nexport interface ItemProps {\n  name: string;\n  onDelete: () => void;\n}\ninterface Props {\n  propertyName: string;\n  component: React.ComponentType<ItemProps>;\n  showAdd?: boolean;\n}\nfunction FormCollection({\n  propertyName,\n  component: C,\n  showAdd,\n}: Props): React.ReactElement<typeof FieldArray> {\n  // static propTypes = {\n  //   propertyName: PropTypes.string.isRequired,\n  //   component: PropTypes.element.isRequired,\n  //   showAdd: PropTypes.bool,\n  //   addButtonLabel: PropTypes.node,\n  //   validate: PropTypes.func,\n  //   defaultValues: PropTypes.shape({}),\n  // };\n\n  // static defaultProps = {\n  //   showAdd: true,\n  //   addButtonLabel: undefined,\n  //   validate: undefined,\n  //   defaultValues: {},\n  // };\n\n  return (\n    <FieldArray\n      name={propertyName}\n      render={({ form, remove }) => (\n        <TextList>\n          {form.values[propertyName]?.map((item, index) => (\n            <TextItem key={index} large>\n              {/* <FormSection name={fieldName}> */}\n              <C\n                name={`${propertyName}.${index}`}\n                onDelete={() => remove(index)}\n              />\n              {/* </FormSection> */}\n            </TextItem>\n          ))}\n          {/* {showAdd && (\n              <TextItem large>\n                <FormGrid>\n                  <FormRow>\n                    <FormColumn>\n                      <Button\n                        icon=\"plus\"\n                        shape=\"plain\"\n                        onClick={() => fields.push({ ...defaultValues })}\n                      >\n                        {addButtonLabel || (\n      <Trans id=\"contact.action.add_new_field\" message=\"Add new\" />)}\n                      </Button>\n                    </FormColumn>\n                  </FormRow>\n                </FormGrid>\n              </TextItem>\n            )} */}\n        </TextList>\n      )}\n      // validate={validate}\n    />\n  );\n}\n\nexport default FormCollection;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/IdentityDetails/index.tsx",
    "content": "import * as React from 'react';\nimport { SocialIdentity } from 'src/modules/contact/types';\nimport { Icon } from '../../../../components';\n\ninterface Props {\n  identity: SocialIdentity;\n}\nfunction IdentityDetails({ identity }: Props): JSX.Element {\n  return (\n    <span className=\"m-identity-details\">\n      <Icon type={identity.type} rightSpaced />\n      {identity.name}\n    </span>\n  );\n}\n\nexport default IdentityDetails;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/IdentityForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { Field } from 'formik';\nimport { FormikTextFieldGroup } from 'src/components/TextFieldGroup';\nimport { FormikSelectFieldGroup } from 'src/components/SelectFieldGroup';\nimport {\n  IDENTITY_TYPE_TWITTER,\n  IDENTITY_TYPE_MASTODON,\n} from 'src/modules/contact';\nimport {\n  Button,\n  Icon,\n  Fieldset,\n  Legend,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from 'src/components';\nimport { validateRequired } from 'src/modules/form/services/validators';\n\nimport './style.scss';\nimport { ItemProps } from '../FormCollection';\n\nconst IDENTITY_TYPES = [IDENTITY_TYPE_TWITTER, IDENTITY_TYPE_MASTODON];\n\nfunction IdentityForm({ name, onDelete, i18n }: ItemProps & withI18nProps) {\n  const identityTypeOptions = IDENTITY_TYPES.map((value) => ({\n    value,\n    label: value,\n  }));\n\n  return (\n    <FormGrid className=\"m-identity-form\">\n      <Fieldset>\n        <FormRow>\n          <FormColumn size=\"shrink\">\n            <Legend>\n              <Icon rightSpaced type=\"user\" />\n              <span className=\"m-identity-form__legend\">\n                <Trans id=\"contact.identity_form.legend\" message=\"Identities\" />\n              </span>\n            </Legend>\n          </FormColumn>\n          <FormColumn size=\"shrink\" bottomSpace>\n            <Field\n              component={FormikSelectFieldGroup}\n              name={`${name}.type`}\n              label={i18n._(\n                /* i18n */ 'contact.identity_form.service.label',\n                undefined,\n                {\n                  message: 'Service',\n                }\n              )}\n              options={identityTypeOptions}\n              showLabelforSr\n              required\n            />\n          </FormColumn>\n          <FormColumn size=\"medium\" fluid bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              name={`${name}.name`}\n              validate={validateRequired(i18n)}\n              label={i18n._(\n                /* i18n */ 'contact.identity_form.identity.label',\n                undefined,\n                {\n                  message: 'Identity',\n                }\n              )}\n              showLabelforSr\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */ 'contact.identity_form.identity.placeholder',\n                  undefined,\n                  { message: \"username, account's URL...\" }\n                ),\n                expanded: true,\n                required: true,\n              }}\n            />\n          </FormColumn>\n          <FormColumn className=\"m-identity-form__col-button\">\n            <Button color=\"alert\" icon=\"remove\" onClick={onDelete} />\n          </FormColumn>\n        </FormRow>\n      </Fieldset>\n    </FormGrid>\n  );\n}\n\nexport default withI18n()(IdentityForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/IdentityForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-identity-form {\n  &__col-button {\n    @include flex-grid-size(shrink);\n  }\n\n  &__button {\n    margin-left: map_get($co-form__spacing, 'medium');\n  }\n\n  @include breakpoint(medium) {\n    &__legend {\n      display: none;\n    }\n\n    &__button {\n      margin-left: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ImDetails/index.tsx",
    "content": "import * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport { Icon } from 'src/components';\nimport { IM } from 'src/modules/contact/types';\n\ninterface Props extends withI18nProps {\n  im: IM;\n}\nfunction ImDetails({ im, i18n }: Props) {\n  const imTypesTranslations = {\n    work: i18n._(/* i18n */ 'contact.im_type.work', undefined, {\n      message: 'Professional',\n    }),\n    home: i18n._(/* i18n */ 'contact.im_type.home', undefined, {\n      message: 'Personal',\n    }),\n    other: i18n._(/* i18n */ 'contact.im_type.other', undefined, {\n      message: 'Other',\n    }),\n  };\n\n  return (\n    <span className=\"m-im-details\">\n      <Icon rightSpaced type=\"comment\" />\n      {im.address}{' '}\n      {im.type && (\n        <small>\n          <em>{imTypesTranslations[im.type]}</em>\n        </small>\n      )}\n    </span>\n  );\n}\n\nexport default withI18n()(ImDetails);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ImForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { Field } from 'formik';\nimport { FormikTextFieldGroup } from 'src/components/TextFieldGroup';\nimport { FormikSelectFieldGroup } from 'src/components/SelectFieldGroup';\nimport {\n  Button,\n  Icon,\n  Fieldset,\n  Legend,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from 'src/components';\nimport { validateRequired } from 'src/modules/form/services/validators';\nimport { ItemProps } from '../FormCollection';\n\nimport './style.scss';\n\nconst IM_TYPES = ['', 'work', 'home', 'other', 'netmeeting'];\n\nfunction ImForm({ i18n, name, onDelete }: ItemProps & withI18nProps) {\n  const addressTypes = {\n    work: i18n._(/* i18n */ 'contact.im_type.work', undefined, {\n      message: 'Professional',\n    }),\n    home: i18n._(/* i18n */ 'contact.im_type.home', undefined, {\n      message: 'Personal',\n    }),\n    other: i18n._(/* i18n */ 'contact.im_type.other', undefined, {\n      message: 'Other',\n    }),\n    netmeeting: i18n._(/* i18n */ 'contact.im_type.netmeeting', undefined, {\n      message: 'Netmeeting',\n    }),\n  };\n\n  const addressTypeOptions = IM_TYPES.map((value) => ({\n    value,\n    label: addressTypes[value] || '',\n  }));\n\n  return (\n    <FormGrid className=\"m-im-form\">\n      <Fieldset>\n        <FormRow>\n          <FormColumn size=\"shrink\">\n            <Legend>\n              <Icon type=\"comment\" rightSpaced />\n              <span className=\"m-im-form__legend\">\n                <Trans\n                  id=\"contact.im_form.legend\"\n                  message=\"Instant messaging\"\n                />\n              </span>\n            </Legend>\n          </FormColumn>\n          <FormColumn size=\"shrink\" bottomSpace>\n            <Field\n              component={FormikSelectFieldGroup}\n              name={`${name}.type`}\n              label={i18n._(\n                /* i18n */ 'contact.im_form.type.label',\n                undefined,\n                {\n                  message: 'Type',\n                }\n              )}\n              showLabelforSr\n              options={addressTypeOptions}\n            />\n          </FormColumn>\n\n          <FormColumn size=\"medium\" fluid bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              name={`${name}.address`}\n              validate={validateRequired(i18n)}\n              label={i18n._(\n                /* i18n */ 'contact.im_form.address.label',\n                undefined,\n                {\n                  message: 'Address',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */ 'contact.im_form.address.placeholder',\n                  undefined,\n                  {\n                    message: 'Address',\n                  }\n                ),\n                expanded: true,\n                required: true,\n              }}\n              showLabelforSr\n            />\n          </FormColumn>\n          <FormColumn className=\"m-im-form__col-button\">\n            <Button color=\"alert\" icon=\"remove\" onClick={onDelete} />\n          </FormColumn>\n        </FormRow>\n      </Fieldset>\n    </FormGrid>\n  );\n}\n\nexport default withI18n()(ImForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ImForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-im-form {\n  &__col-button {\n    @include flex-grid-size(shrink);\n  }\n\n  &__button {\n    margin-left: map_get($co-form__spacing, 'medium');\n  }\n\n  @include breakpoint(medium) {\n    &__legend {\n      display: none;\n    }\n\n    &__button {\n      margin-left: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/OrgaDetails/index.tsx",
    "content": "import * as React from 'react';\nimport { Trans } from '@lingui/react';\nimport { Organization } from 'src/modules/contact/types';\n\ninterface Props {\n  organization: Organization;\n}\nfunction OrgaDetails({ organization }: Props): JSX.Element {\n  const department = organization.department\n    ? ` (${organization.department})`\n    : '';\n  let organizationDescription = `${\n    organization.job_description || organization.name\n  } ${department}`;\n\n  if (organization.job_description && organization.name) {\n    const { job_description: jobDesc, name: orgaName } = organization;\n\n    // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'.\n    organizationDescription = (\n      <Trans\n        id=\"orga-details.job.desc-full\"\n        values={{\n          jobDesc,\n          orgaName,\n          department,\n        }}\n        message=\"{jobDesc} at {orgaName} {department}\"\n      />\n    );\n  }\n\n  return <span title={organization.label}>{organizationDescription}</span>;\n}\n\nexport default OrgaDetails;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/OrgaForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Field } from 'formik';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { FormikTextFieldGroup } from 'src/components/TextFieldGroup';\nimport {\n  Icon,\n  Button,\n  Fieldset,\n  Legend,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from 'src/components';\nimport { validateRequired } from 'src/modules/form/services/validators';\nimport './style.scss';\nimport { ItemProps } from '../FormCollection';\n\nfunction OrgaForm({\n  name,\n  i18n,\n  onDelete,\n}: ItemProps & withI18nProps): React.ReactElement<typeof FormGrid> {\n  return (\n    <FormGrid className=\"m-orga-form\">\n      <Fieldset>\n        <FormRow>\n          <FormColumn>\n            <Legend>\n              <Icon rightSpaced type=\"building\" />\n              <Trans id=\"contact.orga_form.legend\" message=\"Organization\" />\n            </Legend>\n          </FormColumn>\n        </FormRow>\n        <FormRow>\n          <FormColumn rightSpace={false} bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              name={`${name}.label`}\n              label={i18n._(\n                /* i18n */ 'contact.orga_form.label.label',\n                undefined,\n                {\n                  message: 'Label',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */ 'contact.orga_form.label.label',\n                  undefined,\n                  {\n                    message: 'Label',\n                  }\n                ),\n                expanded: true,\n              }}\n            />\n          </FormColumn>\n          <FormColumn rightSpace={false} bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              name={`${name}.name`}\n              validate={validateRequired(i18n)}\n              label={i18n._(\n                /* i18n */ 'contact.orga_form.name.label',\n                undefined,\n                {\n                  message: 'Name',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */ 'contact.orga_form.name.label',\n                  undefined,\n                  {\n                    message: 'Name',\n                  }\n                ),\n                expanded: true,\n                required: true,\n              }}\n            />\n          </FormColumn>\n          <FormColumn rightSpace={false} bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              name={`${name}.title`}\n              label={i18n._(\n                /* i18n */ 'contact.orga_form.title.label',\n                undefined,\n                {\n                  message: 'Title',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */ 'contact.orga_form.title.label',\n                  undefined,\n                  {\n                    message: 'Title',\n                  }\n                ),\n                expanded: true,\n              }}\n            />\n          </FormColumn>\n          <FormColumn rightSpace={false} bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              name={`${name}.department`}\n              label={i18n._(\n                /* i18n */ 'contact.orga_form.department.label',\n                undefined,\n                {\n                  message: 'Department',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */ 'contact.orga_form.department.label',\n                  undefined,\n                  { message: 'Department' }\n                ),\n                expanded: true,\n              }}\n            />\n          </FormColumn>\n          <FormColumn rightSpace={false} bottomSpace>\n            <Field\n              component={FormikTextFieldGroup}\n              name={`${name}.job_description`}\n              label={i18n._(\n                /* i18n */ 'contact.orga_form.job_description.label'\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */ 'contact.orga_form.job_description.label'\n                ),\n                expanded: true,\n              }}\n            />\n          </FormColumn>\n          <FormColumn className=\"m-orga-form__col-button\" rightSpace={false}>\n            <Button icon=\"remove\" color=\"alert\" onClick={onDelete}>\n              <Trans id=\"contact.orga_form.remove-button\" message=\"Remove\" />\n            </Button>\n          </FormColumn>\n        </FormRow>\n      </Fieldset>\n    </FormGrid>\n  );\n}\n\nexport default withI18n()(OrgaForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/OrgaForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-orga-form {\n  &__col-button {\n    display: flex;\n    justify-content: flex-end;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/PhoneDetails/index.tsx",
    "content": "import * as React from 'react';\nimport { withI18n, withI18nProps } from '@lingui/react';\nimport { Icon } from 'src/components';\nimport { Phone } from 'src/modules/contact/types';\n\ninterface Props extends withI18nProps {\n  phone: Phone;\n}\nfunction PhoneDetails({ phone, i18n }: Props) {\n  const typeTranslations = {\n    work: i18n._(/* i18n */ 'contact.phone_type.work', undefined, {\n      message: 'Professional',\n    }),\n    home: i18n._(/* i18n */ 'contact.phone_type.home', undefined, {\n      message: 'Personal',\n    }),\n    other: i18n._(/* i18n */ 'contact.phone_type.other', undefined, {\n      message: 'Other',\n    }),\n  };\n\n  return (\n    <span className=\"m-phone-details\">\n      <Icon rightSpaced type=\"phone\" />\n      {phone.number}{' '}\n      {phone.type && (\n        <small>\n          <em>{typeTranslations[phone.type]}</em>\n        </small>\n      )}\n    </span>\n  );\n}\n\nexport default withI18n()(PhoneDetails);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/PhoneForm/index.tsx",
    "content": "import * as React from 'react';\nimport { Field } from 'formik';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { FormikTextFieldGroup } from 'src/components/TextFieldGroup';\nimport { FormikSelectFieldGroup } from 'src/components/SelectFieldGroup';\nimport {\n  Icon,\n  Button,\n  Fieldset,\n  Legend,\n  FormGrid,\n  FormRow,\n  FormColumn,\n} from 'src/components';\nimport { validateRequired } from 'src/modules/form/services/validators';\nimport './style.scss';\nimport { ItemProps } from '../FormCollection';\n\nconst PHONE_TYPES = ['', 'work', 'home', 'other'];\n\nfunction PhoneForm({ onDelete, i18n, name }: ItemProps & withI18nProps) {\n  const addressTypes = {\n    work: i18n._(/* i18n */ 'contact.phone_type.work', undefined, {\n      message: 'Professional',\n    }),\n    home: i18n._(/* i18n */ 'contact.phone_type.home', undefined, {\n      message: 'Personal',\n    }),\n    other: i18n._(/* i18n */ 'contact.phone_type.other', undefined, {\n      message: 'Other',\n    }),\n  };\n\n  const typeOptions = PHONE_TYPES.map((value) => ({\n    value,\n    label: addressTypes[value] || '',\n  }));\n\n  return (\n    <FormGrid className=\"m-phone-form\">\n      <Fieldset>\n        <FormRow>\n          {/* {errors.length > 0 && (\n              <FormColumn>\n                <FieldErrors errors={errors} />\n              </FormColumn>\n            )} */}\n          <FormColumn size=\"shrink\">\n            <Legend>\n              <Icon rightSpaced type=\"phone\" />\n              <span className=\"m-phone-form__legend\">\n                <Trans id=\"contact.phone_form.legend\" message=\"Phone\" />\n              </span>\n            </Legend>\n          </FormColumn>\n          <FormColumn size=\"shrink\" bottomSpace>\n            <Field\n              component={FormikSelectFieldGroup}\n              name={`${name}.type`}\n              label={i18n._(\n                /* i18n */ 'contact.phone_form.type.label',\n                undefined,\n                {\n                  message: 'Type',\n                }\n              )}\n              showLabelforSr\n              options={typeOptions}\n            />\n          </FormColumn>\n          <FormColumn size=\"medium\" fluid bottomSpace>\n            <Field\n              id={`edit-contact_${name}.number`}\n              component={FormikTextFieldGroup}\n              name={`${name}.number`}\n              validate={validateRequired(i18n)}\n              type=\"tel\"\n              label={i18n._(\n                /* i18n */ 'contact.phone_form.number.label',\n                undefined,\n                {\n                  message: 'Number',\n                }\n              )}\n              inputProps={{\n                placeholder: i18n._(\n                  /* i18n */ 'contact.phone_form.number.placeholder',\n                  undefined,\n                  {\n                    message: 'Number',\n                  }\n                ),\n                expanded: true,\n                required: true,\n              }}\n              showLabelforSr\n            />\n          </FormColumn>\n          <FormColumn className=\"m-phone-form__col-button\">\n            <Button icon=\"remove\" color=\"alert\" onClick={onDelete} />\n          </FormColumn>\n        </FormRow>\n      </Fieldset>\n    </FormGrid>\n  );\n}\n\nexport default withI18n()(PhoneForm);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/PhoneForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-phone-form {\n  &__col-button {\n    @include flex-grid-size(shrink);\n  }\n\n  &__button {\n    margin-left: map_get($co-form__spacing, 'medium');\n  }\n\n  @include breakpoint(medium) {\n    &__legend {\n      display: none;\n    }\n\n    &__button {\n      margin-left: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/PublicKeyForm/index.ts",
    "content": "import { compose, bindActionCreators } from 'redux';\nimport { connect } from 'react-redux';\nimport { reduxForm } from 'redux-form';\nimport {\n  requestPublicKeys,\n  createPublicKey,\n  updatePublicKey,\n  deletePublicKey,\n} from '../../../../store/modules/public-key';\nimport { notifyError } from '../../../../modules/userNotify';\nimport { tryCatchAxiosAction } from '../../../../services/api-client';\nimport Presenter from './presenter';\n\nconst mapStateToProps = (state, ownProps) => ({\n  initialValues: ownProps.publicKey,\n  form: ownProps.publicKey\n    ? `public-key-${ownProps.publicKey.key_id}`\n    : 'public-key-new',\n});\n\nconst makeHandleSubmit = (props, dispatch) => async (values) => {\n  const { contactId, publicKey, onSuccess } = props;\n\n  try {\n    if (publicKey) {\n      const { key_id: publicKeyId, label } = publicKey;\n\n      await tryCatchAxiosAction(() =>\n        dispatch(\n          updatePublicKey({\n            contactId,\n            publicKey: { ...values, publicKeyId },\n            original: { label },\n          })\n        )\n      );\n    } else {\n      await tryCatchAxiosAction(() =>\n        dispatch(createPublicKey({ contactId, publicKey: values }))\n      );\n    }\n\n    onSuccess();\n  } catch (err) {\n    const { message } = err[0];\n\n    dispatch(notifyError({ message }));\n  }\n};\n\nconst mapDispatchToProps = (dispatch, ownProps) => ({\n  ...bindActionCreators(\n    {\n      requestPublicKeys,\n      deletePublicKey,\n    },\n    dispatch\n  ),\n  onSubmit: makeHandleSubmit(ownProps, dispatch),\n});\n\nexport default compose(\n  connect(mapStateToProps, mapDispatchToProps),\n  // @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.\n  reduxForm()\n)(Presenter);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/PublicKeyForm/presenter.tsx",
    "content": "import * as React from 'react';\nimport PropTypes from 'prop-types';\nimport { Field } from 'redux-form';\nimport { withI18n, Trans } from '@lingui/react';\nimport renderReduxField from '../../../../services/renderReduxField';\nimport {\n  Button,\n  Confirm,\n  FieldErrors,\n  Fieldset,\n  FormColumn,\n  FormGrid,\n  FormRow,\n  Icon,\n  Legend,\n  TextFieldGroup as TextFieldGroupBase,\n} from '../../../../components';\nimport { getMaxSize } from '../../../../services/config';\nimport ReduxedInputFileGroup from '../ReduxedInputFileGroup';\n\nimport './style.scss';\n\nconst TextFieldGroup = renderReduxField(TextFieldGroupBase);\n\n// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message\n@withI18n()\nclass PublicKeyForm extends React.PureComponent {\n  static propTypes = {\n    errors: PropTypes.arrayOf(PropTypes.string),\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    contactId: PropTypes.string.isRequired,\n    publicKey: PropTypes.shape({}),\n    handleSubmit: PropTypes.func.isRequired,\n    form: PropTypes.string.isRequired,\n    deletePublicKey: PropTypes.func.isRequired,\n    onSuccess: PropTypes.func.isRequired,\n    onCancel: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    publicKey: undefined,\n    errors: [],\n  };\n\n  handleDelete = () => {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'contactId' does not exist on type 'Reado... Remove this comment to see the full error message\n    const { contactId, publicKey, deletePublicKey, onSuccess } = this.props;\n\n    deletePublicKey({ contactId, publicKeyId: publicKey.key_id });\n    onSuccess();\n  };\n\n  render() {\n    const {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'form' does not exist on type 'Readonly<{... Remove this comment to see the full error message\n      form,\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'errors' does not exist on type 'Readonly... Remove this comment to see the full error message\n      errors,\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'i18n' does not exist on type 'Readonly<{... Remove this comment to see the full error message\n      i18n,\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'publicKey' does not exist on type 'Reado... Remove this comment to see the full error message\n      publicKey,\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'onCancel' does not exist on type 'Readon... Remove this comment to see the full error message\n      onCancel,\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'handleSubmit' does not exist on type 'Re... Remove this comment to see the full error message\n      handleSubmit,\n    } = this.props;\n\n    return (\n      <form onSubmit={handleSubmit} method=\"post\">\n        <FormGrid className=\"m-public-key-form\">\n          <Fieldset>\n            <FormRow>\n              <FormColumn bottomSpace>\n                <Legend>\n                  <Icon rightSpaced type=\"key\" />\n                  <Trans\n                    id=\"contact.public_key_form.legend\"\n                    message=\"Public Key\"\n                  />\n                </Legend>\n              </FormColumn>\n              {errors.length > 0 && (\n                <FormColumn>\n                  <FieldErrors errors={errors} />\n                </FormColumn>\n              )}\n            </FormRow>\n            <FormRow>\n              <FormColumn bottomSpace>\n                <Field\n                  component={TextFieldGroup}\n                  name=\"label\"\n                  label={i18n._(\n                    /* i18n */ 'contact.public_key_form.label.label',\n                    null,\n                    {\n                      message: 'Key label',\n                    }\n                  )}\n                  required\n                  accept=\"application/x-pgp\"\n                />\n              </FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn bottomSpace>\n                <Field\n                  component={ReduxedInputFileGroup}\n                  fileAsContent\n                  label={i18n._(\n                    /* i18n */ 'contact.public_key_form.key.label',\n                    null,\n                    {\n                      message: 'Key (ascii armored)',\n                    }\n                  )}\n                  maxSize={getMaxSize()}\n                  name=\"key\"\n                  required={publicKey === undefined}\n                  disabled={publicKey !== undefined}\n                  type=\"file\"\n                  accept=\"application/x-pgp\"\n                />\n              </FormColumn>\n            </FormRow>\n            <FormRow>\n              <FormColumn\n                className=\"m-public-key-form__actions\"\n                // @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: (Element | null)[]; className: s... Remove this comment to see the full error message\n                rightSpaced={false}\n              >\n                {publicKey ? (\n                  <Confirm\n                    onConfirm={this.handleDelete}\n                    title={i18n._(\n                      /* i18n */ 'contact.public_key_form.confirm_delete.title',\n                      null,\n                      { message: 'Delete public key' }\n                    )}\n                    content={i18n._(\n                      /* i18n */ 'contact.public_key_form.confirm_delete.content',\n                      {\n                        label: publicKey.label,\n                        fingerprint: publicKey.fingerprint,\n                      },\n                      {\n                        message:\n                          'Are you sure you want to delete the key \"{label} - {fingerprint}\" ? This action cannot be undone.',\n                      }\n                    )}\n                    render={(confirm) => (\n                      <Button\n                        className=\"m-public-key-form__button-remove\"\n                        type=\"button\"\n                        color=\"alert\"\n                        icon=\"remove\"\n                        onClick={confirm}\n                      >\n                        <Trans\n                          id=\"contact.public_key_form.delete_key\"\n                          message=\"Delete Key\"\n                        />\n                      </Button>\n                    )}\n                  />\n                ) : null}\n                <Button\n                  icon=\"remove\"\n                  className=\"m-public-key-form__button-cancel\"\n                  onClick={onCancel}\n                >\n                  <Trans id=\"contact.public_key_form.cancel\" message=\"Cancel\" />\n                </Button>\n                <Button\n                  type=\"submit\"\n                  icon=\"check\"\n                  shape=\"plain\"\n                  className=\"m-public-key-form__button-validate\"\n                >\n                  <Trans\n                    id=\"contact.public_key_form.validate\"\n                    message=\"Validate\"\n                  />\n                </Button>\n              </FormColumn>\n            </FormRow>\n          </Fieldset>\n        </FormGrid>\n      </form>\n    );\n  }\n}\n\nexport default PublicKeyForm;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/PublicKeyForm/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-public-key-form {\n  padding-left: map_get($co-form__spacing, 'medium');\n  background-color: $co-color__bg__back--higher;\n\n  &__actions {\n    display: flex;\n  }\n\n  &__button-cancel {\n    margin-left: auto;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/PublicKeyList/index.tsx",
    "content": "import { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport * as React from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { requestPublicKeys } from 'src/store/modules/public-key';\nimport { RootState } from 'src/store/reducer';\nimport { Button, Icon, Link } from 'src/components';\nimport { strToBase64 } from 'src/services/encode-utils';\nimport PublicKeyForm from '../PublicKeyForm';\nimport './style.scss';\n\nconst KEY_QUALITY_CLASSES = ['weak', 'average', 'good'];\nconst KEY_QUALITY_ICONS = [\n  'exclamation-triangle',\n  'expire-soon',\n  'info-circle',\n];\n\n// FIXME: migrate keyState reducer\ntype KeyState = any;\ninterface Props extends withI18nProps {\n  contactId: string;\n}\nfunction PublicKeyList({ contactId, i18n }: Props) {\n  const dispatch = useDispatch();\n  const [editMode, setEditMode] = React.useState(false);\n  const [editingKey, setEditingKey] = React.useState(undefined);\n  const keyState: KeyState | undefined = useSelector<RootState>(\n    (state) => state.publicKey[contactId]\n  );\n  const needsFetching = !keyState;\n\n  React.useEffect(() => {\n    if (!keyState?.isFetching && (needsFetching || keyState?.didInvalidate)) {\n      dispatch(requestPublicKeys({ contactId }));\n    }\n  }, [keyState]);\n\n  const onSuccess = () => {\n    dispatch(requestPublicKeys({ contactId }));\n    setEditMode(false);\n  };\n\n  const getKeyQuality = (publicKey) => {\n    let score = 2;\n\n    score -= Date.parse(publicKey.expire_date) > Date.now() ? 0 : 1;\n    // XXX: not sure about this\n    // score -= publicKey.size >= 2048 ? 0 : 1;\n\n    return score;\n  };\n\n  const enterAddMode = () => {\n    setEditMode(true);\n    setEditingKey(undefined);\n  };\n\n  const quitEditMode = () => {\n    setEditMode(false);\n  };\n\n  const getPublicKeyDataUrl = ({ key }) =>\n    `data:application/x-pgp;base64,${strToBase64(key)}`;\n\n  const handleEdit = (publicKey) => () => {\n    setEditMode(true);\n    setEditingKey(publicKey.key_id);\n  };\n\n  return (\n    <>\n      {keyState?.publicKeys && (\n        <ul>\n          {keyState.publicKeys.map((publicKey) => {\n            if (editMode && editingKey === publicKey.key_id) {\n              return (\n                <li key={publicKey.key_id}>\n                  {/* @ts-ignore */}\n                  <PublicKeyForm\n                    key={publicKey.key_id}\n                    contactId={contactId}\n                    publicKey={publicKey}\n                    onSuccess={onSuccess}\n                    onCancel={quitEditMode}\n                  />\n                </li>\n              );\n            }\n\n            return (\n              <li key={publicKey.key_id} className=\"m-public-key-list__key\">\n                <Icon\n                  type={KEY_QUALITY_ICONS[getKeyQuality(publicKey)]}\n                  className={`m-public-key-list__quality-icon--${\n                    KEY_QUALITY_CLASSES[getKeyQuality(publicKey)]\n                  }`}\n                  rightSpaced\n                />\n                <strong className=\"m-public-key-list__key-label\">\n                  {publicKey.label}\n                </strong>\n                &nbsp;:&nbsp;\n                {publicKey.fingerprint}\n                <Link\n                  button\n                  href={getPublicKeyDataUrl(publicKey)}\n                  download={`${publicKey.label}.pubkey.asc`}\n                  title={i18n._(\n                    /* i18n */ 'contact.public_key_list.download_key',\n                    undefined,\n                    {\n                      message: 'Download key',\n                    }\n                  )}\n                >\n                  <Icon type=\"download\" />\n                </Link>\n                <Button\n                  icon=\"edit\"\n                  className=\"m-public-key-list__edit-button\"\n                  onClick={handleEdit(publicKey)}\n                />\n              </li>\n            );\n          })}\n        </ul>\n      )}\n      {editMode && !editingKey ? (\n        // @ts-ignore\n        <PublicKeyForm\n          contactId={contactId}\n          onSuccess={onSuccess}\n          onCancel={quitEditMode}\n        />\n      ) : (\n        <Button onClick={enterAddMode} icon=\"key\" type=\"button\" shape=\"plain\">\n          <Trans\n            id=\"contact.public_keys_list.add_key.label\"\n            message=\"Add public key\"\n          />\n        </Button>\n      )}\n    </>\n  );\n}\n\nexport default withI18n()(PublicKeyList);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/PublicKeyList/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-public-key-list {\n  &__key {\n    display: flex;\n    align-items: center;\n    margin-left: $co-margin;\n  }\n\n  &__edit-button {\n    margin-left: auto;\n  }\n\n  &__quality-icon {\n    &--weak {\n      color: map_get($privacy-palette, 'ugly');\n    }\n\n    &--average {\n      color: map_get($privacy-palette, 'good');\n    }\n\n    &--good {\n      color: map_get($privacy-palette, 'super');\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ReduxedInputFileGroup/index.tsx",
    "content": "import * as React from 'react';\nimport PropTypes from 'prop-types';\nimport { Trans, withI18n } from '@lingui/react';\nimport { v1 as uuidV1 } from 'uuid';\nimport classnames from 'classnames';\nimport { readAsText } from '../../../../modules/file';\nimport { InputFile, FieldGroup, FileSize } from '../../../../components';\nimport File from '../../../../components/InputFileGroup/components/File';\n\nimport './style.scss';\n\n// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message\n@withI18n()\nclass ReduxedInputFileGroup extends React.PureComponent {\n  static propTypes = {\n    input: PropTypes.shape({\n      onChange: PropTypes.func.isRequired,\n      onBlur: PropTypes.func.isRequired,\n      value: PropTypes.arrayOf(PropTypes.object),\n    }).isRequired,\n    meta: PropTypes.shape({\n      error: PropTypes.string,\n    }).isRequired,\n    i18n: PropTypes.shape({ _: PropTypes.func }).isRequired,\n    id: PropTypes.string,\n    mimeTypes: PropTypes.arrayOf(PropTypes.string),\n    className: PropTypes.string,\n    label: PropTypes.string,\n    fileAsContent: PropTypes.bool,\n    maxSize: PropTypes.number,\n    accept: PropTypes.string,\n  };\n\n  state = {\n    files: [],\n    localErrors: [],\n  };\n\n  static defaultProps = {\n    id: undefined,\n    mimeTypes: [],\n    className: undefined,\n    label: undefined,\n    fileAsContent: false,\n    maxSize: undefined,\n    accept: undefined,\n  };\n\n  makeHandleEvent = (eventHandler) => async (e) => {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'fileAsContent' does not exist on type 'R... Remove this comment to see the full error message\n    const { fileAsContent } = this.props;\n\n    try {\n      const files = Array.from(e.target.files);\n\n      this.setState({\n        files: await Promise.all(files.map((file) => this.validate(file))),\n        errors: [],\n      });\n\n      if (fileAsContent) {\n        return eventHandler(await readAsText(files[0]));\n      }\n    } catch (errors) {\n      this.setState({\n        files: [],\n        errors,\n      });\n    }\n\n    return eventHandler(this.state.files);\n  };\n\n  resetForm = () => {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'fileAsContent' does not exist on type 'R... Remove this comment to see the full error message\n    const { fileAsContent, input } = this.props;\n\n    input.onChange(fileAsContent ? null : []);\n\n    this.setState({\n      files: [],\n      localErrors: [],\n    });\n  };\n\n  validate = (file) => {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'i18n' does not exist on type 'Readonly<{... Remove this comment to see the full error message\n    const { i18n, maxSize } = this.props;\n    const errors: React.ReactElement<any>[] = [];\n\n    if (!file) {\n      return Promise.reject(\n        i18n._(/* i18n */ 'input-file-group.error.file_is_required', null, {\n          message: 'A file is required',\n        })\n      );\n    }\n\n    if (maxSize && file.size > maxSize) {\n      errors.push(\n        // ts-expect-error ts-migrate(2345) FIXME: Argument of type 'Element' is not assignable to pa... Remove this comment to see the full error message\n        <Trans\n          id=\"input-file-group.error.max_size\"\n          message=\"The file size must be under {filesize}\"\n          values={{\n            filesize: <FileSize size={maxSize} />,\n          }}\n        />\n      );\n    }\n\n    if (errors.length) {\n      return Promise.reject(errors);\n    }\n\n    return Promise.resolve(file);\n  };\n\n  render() {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'accept' does not exist on type 'Readonly... Remove this comment to see the full error message\n    const { accept, label, id, input, meta, className } = this.props;\n    const actualId = id || uuidV1();\n\n    return (\n      <FieldGroup\n        className={classnames('m-input-file-group', className)}\n        errors={[...(meta.error || []), ...this.state.localErrors]}\n      >\n        {\n          // label does not need to be unigue\n          // see : https://www.w3.org/TR/html50/forms.html#dom-lfe-labels\n          label && (\n            <label\n              htmlFor={actualId}\n              className=\"m-label m-reduxed-input-file-group__label\"\n            >\n              {label}\n            </label>\n          )\n        }\n\n        {\n          // first condition mandatory on first re-render after a reset\n          // as input.value is still here.\n          this.state.files.length > 0 && input.value ? (\n            this.state.files.map((file) => (\n              <File file={file} onRemove={this.resetForm} />\n            ))\n          ) : (\n            <InputFile\n              id={actualId}\n              // @ts-expect-error ts-migrate(2322) FIXME: Type '{ id: any; name: string; onChange: (e: any) ... Remove this comment to see the full error message\n              name=\"files\"\n              onChange={this.makeHandleEvent(input.onChange)}\n              accept={accept}\n              errors={[]}\n            />\n          )\n        }\n      </FieldGroup>\n    );\n  }\n}\n\nexport default ReduxedInputFileGroup;\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/components/ReduxedInputFileGroup/style.scss",
    "content": "@import '../../../../styles/common';\n@import '../../../../styles/object/o-clickable';\n@import '../../../../styles/object/o-form-element';\n@import '../../../../styles/object/o-hidden-element';\n@import '../../../../styles/vendor/bootstrap_foundation-sites';\n\n.m-reduxed-input-file-group {\n  &__file {\n    @include flex-grid-row;\n    @include o-form-element--light;\n\n    margin-top: $co-margin;\n    line-height: $co-component__height;\n\n    &__name {\n      @include flex-grid-column(6);\n\n      overflow: hidden;\n      text-overflow: ellipsis;\n      font-weight: 600;\n      white-space: nowrap;\n    }\n\n    &__size {\n      @include flex-grid-column(4);\n\n      overflow: hidden;\n      text-overflow: ellipsis;\n      color: $co-color__fg__text--low;\n      text-align: right;\n      white-space: nowrap;\n    }\n\n    &__remove {\n      @include flex-grid-size(shrink);\n    }\n  }\n\n  &__label {\n    display: block;\n    margin-top: 1.5rem;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/contact-action-bar.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-contact-action-bar {\n  padding-right: map_get($co-margin_, 'xsmall');\n  text-align: right;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/contact-main-title.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-contact-main-title {\n  display: grid;\n  grid-template-columns: auto auto 1fr;\n  grid-template-areas: 'avatar name' 'pi orga';\n\n  &__avatar {\n    grid-area: avatar;\n    align-self: center;\n    margin-right: 1rem;\n  }\n\n  &__name {\n    grid-area: name;\n    color: #0f98c6;\n    font-size: 1.75rem;\n    font-weight: 700;\n  }\n\n  &__pi {\n    grid-area: pi;\n    justify-self: center;\n    margin-right: 1rem;\n    color: rgb(7 183 152 / 80%);\n    font-size: 2.625rem;\n    font-weight: 700;\n    line-height: 2.625rem;\n  }\n\n  &__organizations {\n    grid-area: orga;\n    font-weight: 700;\n  }\n\n  @include breakpoint(medium) {\n    & {\n      grid-template-areas: 'avatar name name' 'avatar pi orga';\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/services/contactValidation.jsx",
    "content": "import React from 'react';\nimport { Trans } from '@lingui/react';\nimport {\n  IDENTITY_TYPE_TWITTER,\n  IDENTITY_TYPE_MASTODON,\n} from '../../../modules/contact';\nimport protocolsConfig from '../../../services/protocols-config';\n\nconst twitterHandleRegExpr = protocolsConfig.twitter.regexp;\nconst mastodonHandleRegExpr = protocolsConfig.mastodon.regexp;\n\nexport const contactValidation = (values) => {\n  const errors = {};\n\n  const identityErrors = !values.identities\n    ? []\n    : values.identities.map((identity) => {\n        const identityError = {};\n        let hasError = false;\n        if (!identity.type) {\n          identityError.type = 'type missing';\n          hasError = true;\n        }\n\n        if (\n          identity.type === IDENTITY_TYPE_TWITTER &&\n          !twitterHandleRegExpr.test(identity.name)\n        ) {\n          identityError.name = (\n            <Trans\n              id=\"contact.form.identity.not_twitter\"\n              values={{ name: identity.name }}\n              message=\"The twitter username {name} is invalid. It should be between 1 or 15 characters with no special characters. For example «caliopen_org».\"\n            />\n          );\n          hasError = true;\n        }\n\n        if (\n          identity.type === IDENTITY_TYPE_MASTODON &&\n          !mastodonHandleRegExpr.test(identity.name)\n        ) {\n          identityError.name = (\n            <Trans\n              id=\"contact.form.identity.not_mastodon\"\n              values={{ name: identity.name }}\n              message=\"The mastodon username {name} is invalid. It should be in the format of an email. For example «my_friend@mastodon.instance».\"\n            />\n          );\n          hasError = true;\n        }\n\n        return hasError ? identityError : undefined;\n      }, []);\n\n  if (identityErrors && identityErrors.length) {\n    errors.identities = identityErrors;\n  }\n\n  return errors;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/services/contactValidation.spec.jsx",
    "content": "import { contactValidation } from './contactValidation';\n\ndescribe('contact scene - services - contactValidation', () => {\n  it('init', () => {\n    const values = {};\n    expect(contactValidation(values)).toEqual({});\n  });\n  describe('test identities', () => {\n    it('fails on bad Twitter handle', () => {\n      const values = {\n        identities: [\n          { type: 'twitter', name: '@foo' },\n          { type: 'twitter', name: 'foo' },\n          { type: 'twitter', name: 'https://twitter.com/foo' },\n        ],\n      };\n      expect(contactValidation(values).identities.length).toEqual(3);\n      expect(contactValidation(values).identities[1]).toBeUndefined();\n    });\n    it('works with good Twitter handle', () => {\n      const values = {\n        identities: [{ type: 'twitter', name: 'foo' }],\n      };\n      expect(contactValidation(values).identities[0]).toBeUndefined();\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/services/handleContactSaveErrors.spec.ts",
    "content": "import {\n  handleContactSaveErrors,\n  CONTACT_UNKNOWN_ERROR,\n  CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT,\n} from './handleContactSaveErrors';\n\ndescribe('scene Contact > services > handleContactSaveErrors', () => {\n  it('is not an unicity error', () => {\n    const axiosError = {\n      response: {\n        status: 403,\n        statusText: 'Forbidden',\n        data: {\n          errors: [\n            {\n              code: 403,\n              message: '[RESTfacility] PatchContact forbidden',\n              name: '',\n            },\n          ],\n        },\n      },\n    };\n    // @ts-ignore\n    expect(handleContactSaveErrors(axiosError)).toEqual([\n      {\n        type: CONTACT_UNKNOWN_ERROR,\n      },\n    ]);\n  });\n  it('is a field error ', () => {\n    const axiosError = {\n      response: {\n        status: 422,\n        statusText: 'Unprocessable Entity',\n        data: {\n          errors: [\n            {\n              code: 602,\n              message: 'patch.organizations.name in body is required',\n              name: '',\n            },\n          ],\n        },\n      },\n    };\n    // @ts-ignore\n    expect(handleContactSaveErrors(axiosError)).toEqual([\n      {\n        type: CONTACT_UNKNOWN_ERROR,\n      },\n    ]);\n  });\n\n  it('is an unicity error', () => {\n    const axiosError = {\n      response: {\n        // XXX: bad status code at this moment (2019-05-02)\n        status: 403,\n        statusText: 'Forbidden',\n        data: {\n          errors: [\n            {\n              code: 403,\n              message: '[RESTfacility] PatchContact forbidden',\n              name: '',\n            },\n            {\n              code: 6,\n              message:\n                '[RESTfacility] PatchContact forbidden : uri <email:contact@foobar.tld> belongs to contact 62844b07-e59f-41e1-b1d6-6dff9dd03710',\n              name: '',\n            },\n            {\n              code: 422,\n              message:\n                'uri <email:contact@foobar.tld> belongs to contact 62844b07-e59f-41e1-b1d6-6dff9dd03710',\n              name: '',\n            },\n          ],\n        },\n      },\n    };\n    // @ts-ignore\n    expect(handleContactSaveErrors(axiosError)).toEqual([\n      {\n        type: CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT,\n        address: 'email:contact@foobar.tld',\n        ownerContactId: '62844b07-e59f-41e1-b1d6-6dff9dd03710',\n      },\n    ]);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/services/handleContactSaveErrors.ts",
    "content": "import { AxiosError } from 'axios';\n\nexport const CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT =\n  'address_unicity_constraint';\nexport const CONTACT_UNKNOWN_ERROR = 'unknown_error';\n\n// eslint-disable-next-line prefer-regex-literals\nconst UNICITY_PARSE_EXPR = new RegExp(\n  /^uri <(.*)> belongs to contact ([a-f0-9\\\\-]+)$/\n);\n\ninterface APIErrorResponse {\n  errors: { message: string }[];\n}\n\ninterface FieldError {\n  type: string;\n}\nexport interface ContactError {\n  type: typeof CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT;\n  address: string;\n  ownerContactId: string;\n}\nexport type ContactErrors = (ContactError | FieldError)[];\n\nexport const handleContactSaveErrors = (\n  axiosResponse: AxiosError<APIErrorResponse>\n): ContactErrors | undefined =>\n  axiosResponse?.response?.data?.errors?.reduce((acc, err, index, arr) => {\n    const unicityConstraintError = UNICITY_PARSE_EXPR.exec(err.message);\n\n    if (unicityConstraintError) {\n      return [\n        ...acc,\n        {\n          type: CONTACT_ERROR_ADDRESS_UNICITY_CONSTRAINT,\n          address: unicityConstraintError[1],\n          ownerContactId: unicityConstraintError[2],\n        },\n      ];\n    }\n\n    if (arr.length - 1 === index && Object.keys(acc).length === 0) {\n      return [{ type: CONTACT_UNKNOWN_ERROR }];\n    }\n\n    return acc;\n  }, []);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/contact/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-contact {\n  // prevent margin to collapse\n  &::before {\n    display: table;\n    content: '';\n    clear: both;\n    height: 0;\n    margin-top: -1px;\n  }\n\n  padding-bottom: map_get($co-margin_, 'xlarge');\n  background: $co-color__fg__back;\n\n  &__tags {\n    margin-top: map_get($co-margin_, 'small');\n    margin-right: map_get($co-margin_, 'xsmall');\n    margin-bottom: map_get($co-margin_, 'small');\n    margin-left: map_get($co-margin_, 'xsmall');\n  }\n\n  &__main-title,\n  &__contact-details,\n  &__keys,\n  &__form {\n    margin-right: map_get($co-margin_, 'xsmall');\n    margin-left: map_get($co-margin_, 'xsmall');\n  }\n\n  &__details-group:not:first-of-type {\n    border-bottom: 2px solid rgb(141 206 236 / 20%);\n  }\n\n  &__detail {\n    margin-left: 1.5rem;\n  }\n\n  &__edit-bar {\n    margin-top: map_get($co-margin_, 'small');\n    text-align: right;\n  }\n\n  &__action {\n    margin-left: map_get($co-margin_, 'xsmall');\n  }\n\n  @include breakpoint(medium) {\n    &__main-title,\n    &__contact-details,\n    &__keys,\n    &__last-messages,\n    &__tags,\n    &__form {\n      margin-right: map_get($co-margin_, 'large');\n      margin-left: map_get($co-margin_, 'large');\n    }\n\n    &__tags {\n      text-align: right;\n    }\n  }\n\n  @include breakpoint(large) {\n    &__main-title,\n    &__contact-details,\n    &__keys,\n    &__last-messages,\n    &__tags,\n    &__form {\n      margin-right: map_get($co-margin_, 'xlarge');\n      margin-left: map_get($co-margin_, 'xlarge');\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/error/PageError.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { Link, PageTitle } from 'src/components';\nimport CaliopenAscii from './components/CaliopenAscii';\nimport './style.scss';\n\nfunction PageError({ i18n }: withI18nProps) {\n  return (\n    <div className=\"s-error\">\n      <PageTitle\n        title={i18n._(/* i18n */ 'unexpected_error.page_title', undefined, {\n          message: 'Page not found',\n        })}\n      />\n      <h2>\n        <Trans id=\"error-boundary.title\" message=\"Something went wrong.\" />\n      </h2>\n      <div>\n        <Trans\n          id=\"error-boundary.short-description\"\n          message=\"We are are very sorry, an error occured on your account. This should never happens (but sometimes it does). Please retry later or send us a feedback.\"\n        />\n      </div>\n      <div>\n        <Link\n          href=\"https://feedback.caliopen.org\"\n          target=\"_blank\"\n          rel=\"noreferrer noopener\"\n        >\n          <Trans\n            id=\"error-boundary.link-to-feedback\"\n            message=\"Send us a feedback\"\n          />\n        </Link>\n      </div>\n      <CaliopenAscii />\n    </div>\n  );\n}\n\nexport default withI18n()(PageError);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/error/PageNotFound.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\nimport { PageTitle } from '../../components';\nimport CaliopenAscii from './components/CaliopenAscii';\nimport './style.scss';\n\nfunction PageNotFound({ i18n }: withI18nProps) {\n  return (\n    <div className=\"s-page-not-found\">\n      <PageTitle\n        title={i18n._(/* i18n */ 'page_not_found.page_title', undefined, {\n          message: 'Page not found',\n        })}\n      />\n      <h2 className=\"s-page-not-found__title\">\n        <Trans id=\"page_not_found.title\" message=\"Unicorn not found\" />\n      </h2>\n\n      <CaliopenAscii />\n    </div>\n  );\n}\n\nexport default withI18n()(PageNotFound);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/error/components/CaliopenAscii.tsx",
    "content": "import * as React from 'react';\nimport { Trans, withI18n, withI18nProps } from '@lingui/react';\n\nfunction CaliopenAscii({ i18n }: withI18nProps) {\n  return (\n    <div>\n      <span className=\"s-page-not-found__thanks\">\n        <Trans id=\"page_not_found.thank_you\" message=\"Thank you for using\" />\n      </span>\n      <pre\n        className=\"s-page-not-found__ascii\"\n        aria-label={i18n._(\n          /* i18n */ 'page_not_found.caliopen-ascii',\n          undefined,\n          {\n            message: 'Caliopen is draw using ASCIi art',\n          }\n        )}\n      >\n        {`\n        ▄▀▀▀▀▀▀▄                     █   ▀\n  ▄▀▀▀▀▄ ▄████▄ ▌      ▄▀▀▀▀▄ ▄▀▀▀▀▄ █   ▄   ▄▀▀▀▀▄ ▄▀▀▀▀▄ ▄▀▀▀▄ ▄▀▀▀▀▄\n ▐ ▄██▄  ██████ ▌      █      █    █ █   █   █    █ █    █ █ ▄▀  █    █\n ▐ ▀██▀  ▀████▀ ▌      ▀▄▄▄▄▀ ▀▄▄▄▀█ ▀▄▄ ▀▄▄ ▀▄▄▄▄▀ █▄▄▄▄▀ ▀█▄▄▀ █    █\n ▄▀▄▄▄▄▀▀▄▄▄▄▄▄▀▄                                   █\n`}\n      </pre>\n      <pre\n        className=\"s-page-not-found__ascii-short\"\n        aria-label={i18n._(\n          /* i18n */ 'page_not_found.caliopen-ascii-logo',\n          undefined,\n          {\n            message: 'Logo of Caliopen in ASCIi art',\n          }\n        )}\n      >\n        {`\n        ▄▀▀▀▀▀▀▄\n  ▄▀▀▀▀▄ ▄████▄ ▌\n ▐ ▄██▄  ██████ ▌\n ▐ ▀██▀  ▀████▀ ▌\n ▄▀▄▄▄▄▀▀▄▄▄▄▄▄▀▄\n`}\n      </pre>\n    </div>\n  );\n}\n\nexport default withI18n()(CaliopenAscii);\n"
  },
  {
    "path": "src/frontend/web_application/src/scenes/error/style.scss",
    "content": "@import '../../styles/common';\n@import '../../styles/vendor/bootstrap_foundation-sites';\n\n.s-page-not-found,\n.s-error {\n  padding-top: 5 * $co-margin;\n  padding-bottom: 5 * $co-margin;\n  background-color: $co-color__fg__back;\n  text-align: center;\n\n  &__thanks {\n    color: $co-color__fg__text--high;\n  }\n\n  &__ascii-short,\n  &__ascii {\n    margin-right: auto;\n    margin-left: auto;\n    overflow: hidden;\n    font-family: monospace, monospace; // stylelint-disable-line -- https://github.com/necolas/normalize.css/issues/519#issuecomment-197131966\n    font-size: 9px;\n    line-height: 9px;\n    text-align: left;\n  }\n\n  &__ascii {\n    display: none;\n    width: 500px;\n    @include breakpoint(medium) {\n      display: block;\n    }\n  }\n\n  &__ascii-short {\n    width: 120px;\n\n    @include breakpoint(medium) {\n      display: none;\n    }\n  }\n}\n\n.s-page-not-found {\n  &__title {\n    position: relative;\n    margin-bottom: 3 * $co-margin;\n    padding-top: 64px; // approx. height of unicorn's shadow\n    color: $co-color__primary--low;\n    font-size: $co-font__size--large;\n    font-weight: 700;\n\n    &::before {\n      // unicorn\n      display: block;\n      content: ' ';\n      position: absolute;\n      top: 0;\n      left: 50%;\n      width: 4px;\n      height: 4px;\n      margin-left: -38px; // minus (approx. width of unicorn's shadow / 2)\n      box-shadow: 12px 4px 0 #ffeb3b, 24px 4px 0 #f00, 28px 4px 0 #f00,\n        32px 4px 0 #f00, 16px 8px 0 #ffeb3b, 20px 8px 0 #f00, 24px 8px 0 #f00,\n        28px 8px 0 #ffc107, 32px 8px 0 #ffc107, 36px 8px 0 #c8efff,\n        20px 12px 0 #ffeb3b, 24px 12px 0 #c8efff, 28px 12px 0 #000,\n        32px 12px 0 #c8efff, 36px 12px 0 #03a9f4, 68px 12px 0 #03a9f4,\n        72px 12px 0 #f00, 16px 16px 0 #c8efff, 20px 16px 0 #c8efff,\n        24px 16px 0 #c8efff, 28px 16px 0 #c8efff, 32px 16px 0 #c8efff,\n        36px 16px 0 #3f51b5, 40px 16px 0 #3f51b5, 64px 16px 0 #3f51b5,\n        68px 16px 0 #ffc107, 72px 16px 0 #f00, 76px 16px 0 #ffeb3b,\n        12px 20px 0 #c8efff, 16px 20px 0 #c8efff, 20px 20px 0 #c8efff,\n        24px 20px 0 #c8efff, 28px 20px 0 #c8efff, 32px 20px 0 #c8efff,\n        36px 20px 0 #3f51b5, 40px 20px 0 #9c27b0, 60px 20px 0 #9c27b0,\n        64px 20px 0 #3f51b5, 12px 24px 0 #c8efff, 16px 24px 0 #c8efff,\n        24px 24px 0 #c8efff, 28px 24px 0 #c8efff, 32px 24px 0 #c8efff,\n        36px 24px 0 #9c27b0, 40px 24px 0 #9c27b0, 44px 24px 0 #9c27b0,\n        56px 24px 0 #9c27b0, 60px 24px 0 #9c27b0, 24px 28px 0 #c8efff,\n        28px 28px 0 #c8efff, 32px 28px 0 #c8efff, 36px 28px 0 #c8efff,\n        40px 28px 0 #c8efff, 44px 28px 0 #c8efff, 48px 28px 0 #c8efff,\n        52px 28px 0 #c8efff, 56px 28px 0 #c8efff, 20px 32px 0 #c8efff,\n        24px 32px 0 #c8efff, 28px 32px 0 #c8efff, 32px 32px 0 #c8efff,\n        36px 32px 0 #c8efff, 40px 32px 0 #c8efff, 44px 32px 0 #c8efff,\n        48px 32px 0 #c8efff, 52px 32px 0 #c8efff, 56px 32px 0 #c8efff,\n        12px 36px 0 #c8efff, 16px 36px 0 #c8efff, 20px 36px 0 #c8efff,\n        24px 36px 0 #c8efff, 28px 36px 0 #c8efff, 32px 36px 0 #c8efff,\n        36px 36px 0 #c8efff, 40px 36px 0 #c8efff, 44px 36px 0 #c8efff,\n        48px 36px 0 #c8efff, 52px 36px 0 #c8efff, 12px 40px 0 #c8efff,\n        24px 40px 0 #c8efff, 44px 40px 0 #c8efff, 48px 40px 0 #c8efff,\n        52px 40px 0 #c8efff, 12px 44px 0 #c8efff, 16px 44px 0 #c8efff,\n        24px 44px 0 #c8efff, 48px 44px 0 #c8efff, 52px 44px 0 #c8efff,\n        56px 44px 0 #c8efff, 24px 48px 0 #c8efff, 56px 48px 0 #c8efff,\n        24px 52px 0 #c8efff, 56px 52px 0 #c8efff;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/services/api-client/index.ts",
    "content": "import axios, { AxiosInstance } from 'axios';\nimport { getClientDevice } from 'src/modules/device';\nimport { getBaseUrl } from '../config';\nimport { importanceLevelHeader } from '../importance-level';\nimport { queryStringify } from '../../modules/routing';\nimport { getSignatureHeaders } from '../../modules/device/services/signature';\nimport UploadFileAsFormField from '../../modules/file/services/uploadFileAsFormField';\n\nlet client: AxiosInstance;\nlet signingClient: AxiosInstance;\n\nlet headers: Record<string, string> = {\n  ...importanceLevelHeader,\n  'X-Caliopen-PI': '0;100',\n};\n\nif (BUILD_TARGET !== 'server') {\n  headers = {\n    ...headers,\n    'X-Requested-With': 'XMLHttpRequest',\n  };\n}\n\nif (BUILD_TARGET === 'server') {\n  const {\n    getSubRequestHeaders,\n  } = require('../../../server/api/lib/sub-request-manager'); // eslint-disable-line global-require,@typescript-eslint/no-var-requires\n  headers = {\n    ...headers,\n    ...getSubRequestHeaders(),\n  };\n}\n\nconst buildClient = (): AxiosInstance =>\n  axios.create({\n    baseURL: getBaseUrl(),\n    responseType: 'json',\n    headers,\n    paramsSerializer: (params) => queryStringify(params),\n    transformRequest: [\n      (data) => {\n        if (data instanceof UploadFileAsFormField) {\n          return data.toFormData();\n        }\n\n        return data;\n      },\n      // @ts-ignore: typed as undef or not an array\n    ].concat(axios.defaults.transformRequest),\n  });\n\nexport const getUnsignedClient = (): AxiosInstance => {\n  if (!client) {\n    client = buildClient();\n  }\n\n  return client;\n};\n\nexport default function getClient(): AxiosInstance {\n  if (!signingClient) {\n    signingClient = buildClient();\n\n    signingClient.interceptors.request.use(async (config) => {\n      const device = getClientDevice();\n      const signatureHeaders = await getSignatureHeaders(config, device);\n\n      return {\n        ...config,\n        headers: {\n          ...config.headers,\n          ...signatureHeaders,\n        },\n      };\n    });\n  }\n\n  return signingClient;\n}\n\nexport const handleClientResponseSuccess = (response) => {\n  if (!response || !response.payload) {\n    throw new Error('Not an axios success Promise');\n  }\n\n  return response.payload.data;\n};\n\n/**\n * @deprecated unused\n */\nexport const handleClientResponseError = (payload) => {\n  if (payload instanceof Error) {\n    throw payload;\n  }\n\n  if (!payload || !payload.error || !payload.error.response) {\n    console.error({ payload });\n    throw new Error('Not an axios catched Promise');\n  }\n\n  return payload.error.response.data.errors;\n};\n\n/**\n * @deprecated\n */\nexport const tryCatchAxiosAction = async (action) => {\n  try {\n    const response = await action();\n\n    return handleClientResponseSuccess(response);\n  } catch (err) {\n    return Promise.reject(handleClientResponseError(err));\n  }\n};\n\n/**\n * @deprecated\n */\nexport const tryCatchAxiosPromise = async (prom) => {\n  try {\n    const response = await prom;\n\n    return handleClientResponseSuccess(response);\n  } catch (err) {\n    return Promise.reject(handleClientResponseError(err));\n  }\n};\n\n/**\n * @deprecated\n */\nexport function handleAxiosPromise(prom) {\n  return prom.then(handleClientResponseSuccess, (err) =>\n    Promise.reject(handleClientResponseError(err))\n  );\n}\n\nexport interface StandardErrorData {\n  errors: [\n    {\n      message: string;\n      code: number;\n      description: string;\n      component: string;\n      values: number;\n      property: string;\n      type: string;\n    }\n  ];\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/services/api-client/types.d.ts",
    "content": "import { AxiosError } from 'axios';\n\nexport interface ErrorResponse {\n  errors: [{ code: number; message: string; name: string }];\n}\n\nexport type APIAxiosError = AxiosError<ErrorResponse>;\n"
  },
  {
    "path": "src/frontend/web_application/src/services/api-location/index.js",
    "content": "import getClient from '../api-client';\n\nexport default function fetchLocation(location) {\n  return getClient().get(location);\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/services/api-patch/index.js",
    "content": "import isEqual from 'lodash/isEqual';\n\n// /!\\ this function is not recursive; sub-objects are not properly calculated\n// actually remote-identity patch requires full sub-objects : credentials & infos\nconst calcObjectForPatch = (newObj, previousObj) =>\n  Array.from(\n    new Set(Object.keys(newObj).concat(Object.keys(previousObj)))\n  ).reduce(\n    (acc, prop) => {\n      if (!isEqual(newObj[prop], previousObj[prop])) {\n        const nextProp = newObj[prop] ? { [prop]: newObj[prop] } : {};\n        const previousProp = previousObj[prop]\n          ? { [prop]: previousObj[prop] }\n          : {};\n\n        return {\n          ...acc,\n          ...nextProp,\n          current_state: {\n            ...acc.current_state,\n            ...previousProp,\n          },\n        };\n      }\n\n      return acc;\n    },\n    { current_state: {} }\n  );\n\nexport default calcObjectForPatch;\n"
  },
  {
    "path": "src/frontend/web_application/src/services/api-patch/index.spec.js",
    "content": "import calcObjectForPatch from './index';\n\ndescribe('Service calcObjectForPatch', () => {\n  describe('update simple fields', () => {\n    it('give infos from name', () => {\n      const previous = { foo: 'bar' };\n      const updated = { foo: 'foo' };\n\n      expect(calcObjectForPatch(updated, previous)).toEqual({\n        foo: 'foo',\n        current_state: {\n          foo: 'bar',\n        },\n      });\n    });\n\n    it('handle undefined as new value', () => {\n      const previous = { foo: 'bar' };\n      const updated = {};\n\n      expect(calcObjectForPatch(updated, previous)).toEqual({\n        current_state: {\n          foo: 'bar',\n        },\n      });\n    });\n\n    it('handle undefined as previous value', () => {\n      const updated = { foo: 'bar' };\n      const previous = {};\n\n      expect(calcObjectForPatch(updated, previous)).toEqual({\n        foo: 'bar',\n        current_state: {},\n      });\n    });\n  });\n\n  describe('update sub-object', () => {\n    it('does not update sub-object clone', () => {\n      const previous = { foo: { value: 'bar' } };\n      const updated = { ...previous, foo: { ...previous.foo } };\n\n      expect(calcObjectForPatch(updated, previous)).toEqual({\n        current_state: {},\n      });\n    });\n\n    describe('does not calc difference for sub-objects', () => {\n      it('has a new property', () => {\n        const sub = { a: 1 };\n        const previous = { foo: { sub }, bar: 'hello' };\n        const updated = { ...previous, foo: { ...sub, b: 2 } };\n\n        expect(calcObjectForPatch(updated, previous)).toEqual({\n          foo: { ...updated.foo },\n          current_state: { foo: { ...previous.foo } },\n        });\n      });\n\n      it('has a changed property', () => {\n        const sub = { a: 1, b: 2 };\n        const previous = { foo: { sub }, bar: 'hello' };\n        const updated = { ...previous, foo: { ...sub, b: 3 } };\n\n        expect(calcObjectForPatch(updated, previous)).toEqual({\n          foo: { ...updated.foo },\n          current_state: { foo: { ...previous.foo } },\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/services/asciify.js",
    "content": "// remove non ascii chars, leave chars from 32 to 126 of the ascii table\n// https://duckduckgo.com/?q=ascii+table&t=ffab&ia=answer&iax=answer\nexport const asciify = (str) =>\n  str.normalize('NFKD').replace(/[^\\x20-\\x7E]/g, '');\n"
  },
  {
    "path": "src/frontend/web_application/src/services/asciify.spec.js",
    "content": "import { asciify } from './asciify';\n\ndescribe('service asciify', () => {\n  it('asciify', () => {\n    expect(asciify('Ñareèm')).toEqual('Nareem');\n    expect(asciify('Ñがarêèm ?!')).toEqual('Nareem ?!');\n    expect(asciify('Ñがarêè-m ?!')).toEqual('Naree-m ?!');\n    expect(asciify('ẹ́')).toEqual('e');\n    // XXX: could be better to equal `-e`\n    expect(asciify('がé')).toEqual('e');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/services/browser-notification/index.js",
    "content": "export const PERMISSION_GRANTED = 'granted';\nexport const PERMISSION_DENIED = 'denied';\n\nexport const isSupported =\n  typeof window !== 'undefined' && 'Notification' in window;\n\nexport const requestPermission = () =>\n  new Promise((resolve, reject) => {\n    if (!isSupported) {\n      reject(new Error('Browser notifications not supported'));\n\n      return;\n    }\n\n    window.Notification.requestPermission((permission) => resolve(permission));\n  });\n\nexport const isBrowserNotificationGrantedOrAsk = async () => {\n  let { permission } = window.Notification;\n  if (![PERMISSION_DENIED, PERMISSION_GRANTED].includes(permission)) {\n    try {\n      permission = await requestPermission();\n    } catch (err) {\n      return false;\n    }\n  }\n\n  return permission === PERMISSION_GRANTED;\n};\n\n// cf. https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API#Use_cases\nconst isDocumentVisible = () => {\n  let hidden;\n  if (typeof document.hidden !== 'undefined') {\n    // Opera 12.10 and Firefox 18 and later support\n    hidden = 'hidden';\n  } else if (typeof document.msHidden !== 'undefined') {\n    hidden = 'msHidden';\n  } else if (typeof document.webkitHidden !== 'undefined') {\n    hidden = 'webkitHidden';\n  }\n\n  return !document[hidden];\n};\n\nexport const notify = async ({ message, force = false }) => {\n  if (!isSupported) {\n    return Promise.reject(new Error('Browser notifications not supported'));\n  }\n\n  if (isDocumentVisible() && !force) {\n    return undefined;\n  }\n\n  if (await isBrowserNotificationGrantedOrAsk()) {\n    return new window.Notification(message);\n  }\n\n  return Promise.reject(new Error('Browser notifications is not granted'));\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/services/capitalize.js",
    "content": "export const capitalize = (str) =>\n  `${str.charAt(0).toUpperCase()}${str.slice(1)}`;\n"
  },
  {
    "path": "src/frontend/web_application/src/services/config.ts",
    "content": "import merge from 'lodash/merge';\n\nlet config =\n  BUILD_TARGET === 'browser' && typeof window !== 'undefined'\n    ? // @ts-ignore\n      { ...window.__INSTANCE_CONFIG__, ...CALIOPEN_OPTIONS } // eslint-disable-line no-underscore-dangle\n    : // @ts-ignore\n      CALIOPEN_OPTIONS;\n\nconst initConfig = (cfg) => {\n  config = merge(config, cfg);\n};\nconst getConfig = () => config;\nconst getBaseUrl = () => {\n  if (BUILD_TARGET === 'browser') {\n    return '';\n  }\n\n  if (BUILD_TARGET === 'server') {\n    const { protocol, hostname, port } = getConfig();\n\n    return `${protocol}://${hostname}:${port}`;\n  }\n\n  throw new Error(`Unsupported build target \"${BUILD_TARGET}\"`);\n};\n\nconst getMaxSize = () => {\n  const { maxBodySize } = getConfig();\n  const numberSize = maxBodySize\n    .toLowerCase()\n    .replace('kb', '000')\n    .replace('mb', '000000');\n\n  return Number(numberSize);\n};\n\nexport { getBaseUrl, initConfig, getConfig, getMaxSize };\n"
  },
  {
    "path": "src/frontend/web_application/src/services/encode-utils/index.js",
    "content": "import base64 from 'base64-js';\n\nexport * from './utf8ArrayToString';\n\n// see : https://jsperf.com/string-to-uint8array\nexport const toByteArray = (str) => {\n  const byteArray = new Uint8Array(str.length);\n\n  for (let i = 0, j = str.length; i < j; i += 1) {\n    byteArray[i] = str.charCodeAt(i);\n  }\n\n  return byteArray;\n};\n\nexport const strToBase64 = (str) => base64.fromByteArray(toByteArray(str));\n"
  },
  {
    "path": "src/frontend/web_application/src/services/encode-utils/utf8ArrayToString.js",
    "content": "// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt\n\n/* utf.js - UTF-8 <=> UTF-16 convertion\n *\n * Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>\n * Version: 1.0\n * LastModified: Dec 25 1999\n * This library is free.  You can redistribute it and/or modify it.\n */\n\n/* eslint-disable */\nexport const utf8ArrayToString = (array) => {\n  var out, i, len, c;\n  var char2, char3;\n\n  out = '';\n  len = array.length;\n  i = 0;\n  while (i < len) {\n    c = array[i++];\n    switch (c >> 4) {\n      case 0:\n      case 1:\n      case 2:\n      case 3:\n      case 4:\n      case 5:\n      case 6:\n      case 7:\n        // 0xxxxxxx\n        out += String.fromCharCode(c);\n        break;\n      case 12:\n      case 13:\n        // 110x xxxx   10xx xxxx\n        char2 = array[i++];\n        out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));\n        break;\n      case 14:\n        // 1110 xxxx  10xx xxxx  10xx xxxx\n        char2 = array[i++];\n        char3 = array[i++];\n        out += String.fromCharCode(\n          ((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)\n        );\n        break;\n    }\n  }\n\n  return out;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/services/encryption/index.js",
    "content": "// TODO: refactor with src/services/openpgp-manager/api\n\nimport { getPlainTextFromMime, mimeEncapsulate } from '../mime';\nimport { ERROR_UNABLE_READ_PRIVATE_KEY } from '../openpgp-manager';\n\nexport const [ERROR_NEED_PASSPHRASE, ERROR_WRONG_PASSPHRASE] = [\n  'error_need_passphrase',\n  'error_wrong_passphrase',\n];\n\nconst DEFAULT_KEY_OPTIONS = { numBits: 4096 };\n\nconst prepareKeys = async (openpgp, armoredKeys) => {\n  const disarmoredKeys = await Promise.all(\n    armoredKeys.map((armoredKey) =>\n      openpgp.key.readArmored(armoredKey.key || armoredKey)\n    )\n  );\n\n  return disarmoredKeys.reduce(\n    (acc, disarmoredKey) => [...acc, ...disarmoredKey.keys],\n    []\n  );\n};\n\nexport const isMessageEncrypted = (message) =>\n  !!message.privacy_features &&\n  message.privacy_features.message_encrypted === 'True' &&\n  message.privacy_features.message_encryption_method === 'pgp';\n\nexport const encryptMessage = async (message, keys) => {\n  const openpgp = await import(/* webpackChunkName: \"openpgp\" */ 'openpgp');\n\n  if (keys.length === 0) return message;\n\n  const mimeBody = mimeEncapsulate(message.body).toString();\n  const options = {\n    message: openpgp.message.fromText(mimeBody),\n    publicKeys: await prepareKeys(openpgp, keys),\n    privateKeys: null,\n  };\n\n  const { data: body } = await openpgp.encrypt(options);\n\n  return { ...message, body };\n};\n\nexport const decryptMessage = async (message, keys) => {\n  if (!isMessageEncrypted(message)) return message;\n  if (!message.user_identities) return message;\n\n  const openpgp = await import(/* webpackChunkName: \"openpgp\" */ 'openpgp');\n\n  const encryptedBody = await openpgp.message.readArmored(message.body);\n  const options = {\n    message: encryptedBody,\n    privateKeys: keys,\n    publicKeys: null,\n  };\n\n  const { data: body } = await openpgp.decrypt(options);\n  const decryptedMessage = { ...message, body: getPlainTextFromMime({ body }) };\n\n  return decryptedMessage;\n};\n\nexport const getPublicKeyFromPrivateKey = async (\n  privateKeyArmored,\n  passphrase\n) => {\n  const openpgp = await import(/* webpackChunkName: \"openpgp\" */ 'openpgp');\n  const {\n    keys: [privateKey],\n  } = await openpgp.key.readArmored(privateKeyArmored);\n\n  if (!privateKey) {\n    throw new Error(ERROR_UNABLE_READ_PRIVATE_KEY);\n  }\n\n  if (!privateKey.isDecrypted()) {\n    if (!passphrase) {\n      throw new Error(ERROR_NEED_PASSPHRASE);\n    }\n\n    try {\n      await privateKey.decrypt(passphrase);\n    } catch (e) {\n      throw new Error(ERROR_WRONG_PASSPHRASE);\n    }\n  }\n\n  return privateKey.toPublic().armor();\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/services/encryption/index.spec.js",
    "content": "import { encryptMessage, isMessageEncrypted } from './index';\n\njest.mock('openpgp', () => ({\n  key: {\n    readArmored: async (a) => ({ keys: [a] }),\n  },\n  message: {\n    fromText: (a) => a,\n  },\n  encrypt: async (options) => ({\n    data: options.publicKeys.join(', '),\n  }),\n}));\n\ndescribe('modules draftMessage -- service -- encryption', () => {\n  it('Encrypt a simple draft', async () => {\n    const publicKeys = [\n      { emails: 'jaune@john.com', key: 'Hello' },\n      { emails: 'boo@far.com', key: 'encryption.' },\n    ];\n    const draft = { body: 'Thou shalt not read my mails !' };\n\n    expect(await encryptMessage(draft, publicKeys)).toEqual({\n      body: 'Hello, encryption.',\n      // XXX: thoses privacy_features are set in encryption middleware. I'm not sure who's\n      // responsible of this\n      // privacy_features: {\n      //   message_encrypted: 'True',\n      //   message_encryption_method: 'pgp',\n      // },\n    });\n  });\n\n  it('Properly detects non-encryption in privacy_features', () => {\n    const message = {\n      body: 'Empty body',\n      privacy_features: {\n        message_encrypted: 'False',\n        message_encryption_method: 'pgp',\n      },\n    };\n\n    const message2 = {\n      body: 'Empty body',\n      privacy_features: {\n        message_encrypted: 'True',\n        message_encryption_method: 'aes',\n      },\n    };\n\n    expect(isMessageEncrypted(message)).toBeFalsy();\n    expect(isMessageEncrypted(message2)).toBeFalsy();\n  });\n\n  it('Properly detects encryption in privacy_features', () => {\n    const message = {\n      body: 'Void body',\n      privacy_features: {\n        message_encrypted: 'True',\n        message_encryption_method: 'pgp',\n      },\n    };\n\n    expect(isMessageEncrypted(message)).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/services/event-manager/index.js",
    "content": "export const addEventListener = (type, eventListener, ref = window) => {\n  ref.addEventListener(type, eventListener, false);\n\n  return () => {\n    ref.removeEventListener(type, eventListener);\n  };\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/services/filter-contacts/index.js",
    "content": "export default ({ contacts, searchTerms }) =>\n  contacts.filter((contact) => {\n    const match = ({ item, props }) =>\n      props.find(\n        (propName) =>\n          item[propName] &&\n          item[propName].toLowerCase().includes(searchTerms.toLowerCase())\n      );\n\n    const searchContactProps = [\n      'title',\n      'additional_name',\n      'family_name',\n      'given_name',\n      'title',\n    ];\n\n    if (match({ item: contact, props: searchContactProps })) {\n      return true;\n    }\n\n    if (\n      contact.identities &&\n      contact.identities.find((ident) =>\n        match({ item: ident, props: ['infos', 'name', 'identity_id'] })\n      )\n    ) {\n      return true;\n    }\n\n    if (\n      contact.emails &&\n      contact.emails.find((ident) => match({ item: ident, props: ['address'] }))\n    ) {\n      return true;\n    }\n\n    if (\n      contact.ims &&\n      contact.ims.find((ident) => match({ item: ident, props: ['address'] }))\n    ) {\n      return true;\n    }\n\n    if (\n      contact.phones &&\n      contact.phones.find((ident) => match({ item: ident, props: ['number'] }))\n    ) {\n      return true;\n    }\n\n    if (\n      contact.tags &&\n      contact.tags.find((ident) => match({ item: ident, props: ['name'] }))\n    ) {\n      return true;\n    }\n\n    return false;\n  });\n"
  },
  {
    "path": "src/frontend/web_application/src/services/filter-contacts/index.spec.js",
    "content": "import contactFilter from './index';\n\ndescribe('Service contactFilter', () => {\n  it('finds a contact using a part of a simple property like title', () => {\n    const contacts = [\n      {\n        addresses: [],\n        privacy_features: {},\n        phones: [],\n        contact_id: '92d3727a-eefc-4537-b879-85f1c9d197bb',\n        date_insert: '2016-05-09T15:01:42.381000',\n        identities: [],\n        user_id: '124489c3-fc63-4e41-b490-5f4dd317aa50',\n        title: 'Bender',\n        additional_name: '',\n        date_update: null,\n        organizations: [],\n        ims: [],\n        given_name: 'Bender Bending',\n        name_prefix: null,\n        deleted: 0,\n        privacy_index: 0,\n        infos: {},\n        emails: [\n          {\n            email_id: '93f03145-4398-4bd4-9bd5-00000001',\n            is_primary: 0,\n            date_update: null,\n            label: null,\n            address: 'bender@caliopen.local',\n            date_insert: '2016-05-09T15:01:42.116000',\n            type: 'other',\n          },\n        ],\n        family_name: 'Rodriguez',\n        name_suffix: null,\n        avatar: 'avatar.png',\n        public_keys: [],\n      },\n      {\n        addresses: [],\n        privacy_features: {},\n        phones: [],\n        contact_id: '0ba2e346-e4f8-4c45-9adc-eeb1d42fuie0',\n        date_insert: '2016-05-09T15:01:43.381000',\n        identities: [],\n        user_id: '344489c3-fc63-4e41-b490-5f4dd317aa51',\n        title: 'Zoidberg',\n        additional_name: null,\n        date_update: null,\n        organizations: [],\n        ims: [],\n        given_name: 'John',\n        name_prefix: 'Dr',\n        deleted: 0,\n        privacy_index: 0,\n        infos: {},\n        emails: [\n          {\n            email_id: '93f03145-4398-4bd4-9bd5-00000002',\n            is_primary: 0,\n            date_update: null,\n            label: null,\n            address: 'zoidberg@caliopen.local',\n            date_insert: '2016-05-09T15:01:43.116000',\n            type: 'other',\n          },\n        ],\n        family_name: 'Zoidberg',\n        name_suffix: null,\n        avatar: 'avatar.png',\n        public_keys: [],\n      },\n    ];\n    expect(\n      contactFilter({\n        contacts,\n        searchTerms: 'end',\n      })\n    ).toEqual([contacts[0]]);\n  });\n\n  it('finds a contact by a sub property like identities', () => {\n    const contacts = [\n      { identities: [{ name: 'foobar' }] },\n      { identities: [{ identity_id: 'foobar' }] },\n      { identities: [{ identity_id: 'bar' }] },\n    ];\n    expect(\n      contactFilter({\n        contacts,\n        searchTerms: 'oba',\n      })\n    ).toEqual([contacts[0], contacts[1]]);\n  });\n\n  it('finds a contact which contains special chars', () => {\n    const contacts = [{ given_name: \"J'own d'œuf\" }, { given_name: 'jérém' }];\n\n    expect(\n      contactFilter({\n        contacts,\n        searchTerms: 'œuf',\n      })\n    ).toEqual([contacts[0]]);\n\n    expect(\n      contactFilter({\n        contacts,\n        searchTerms: 'jérém',\n      })\n    ).toEqual([contacts[1]]);\n  });\n\n  it('finds a contact which contains uppercases', () => {\n    const contacts = [{ given_name: 'jOhny' }];\n\n    expect(\n      contactFilter({\n        contacts,\n        searchTerms: 'john',\n      })\n    ).toEqual([contacts[0]]);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/services/importance-level/index.js",
    "content": "export const IL_MIN = -10;\nexport const IL_MAX = 10;\n\nexport const importanceLevelHeader = {\n  'X-Caliopen-IL': `${IL_MIN};${IL_MAX}`,\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/services/localStorage/index.js",
    "content": "export const getLocalstorage = () => {\n  if (typeof window !== 'object' || !window.localStorage) {\n    throw new Error('No window nor LocalStorage is available.');\n  }\n\n  return {\n    findAll: (namespace) =>\n      Object.keys(window.localStorage)\n        .filter((name) => name.indexOf(namespace) === 0)\n        .map((name) => ({\n          id: name.replace(`${namespace}.`, ''),\n          value: window.localStorage.getItem(name),\n        })),\n    findOne: (namespace, id) =>\n      window.localStorage.getItem(`${namespace}.${id}`),\n    save: (namespace, id, value) =>\n      window.localStorage.setItem(`${namespace}.${id}`, value),\n    remove: (namespace, id) =>\n      window.localStorage.removeItem(`${namespace}.${id}`),\n  };\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/services/message/index.js",
    "content": "import { isUserParticipant } from '../../modules/message';\n\nexport const renderParticipant = (participant) =>\n  `${participant.label}` || `(${participant.address})`;\n\nexport const getAuthor = (message) =>\n  message.participants &&\n  message.participants.find((participant) => participant.type === 'From');\n\nexport const getRecipients = (message) =>\n  message.participants &&\n  message.participants.filter((participant) => participant.type !== 'From');\n\nexport const getRecipientsExceptUser = (message, user) =>\n  getRecipients(message).filter(\n    (participant) => !isUserParticipant({ participant, user })\n  );\n\nexport const getParticipantsExceptUser = (message, user) =>\n  message.participants &&\n  message.participants.filter(\n    (participant) => !isUserParticipant({ participant, user })\n  );\n\nexport const isMessageFromUser = (message, user) => {\n  const author = getAuthor(message);\n\n  return isUserParticipant({ participant: author, user });\n};\n\nexport const isUserRecipient = (message, user) =>\n  getRecipients(message).some((recipient) =>\n    isUserParticipant({ participant: recipient, user })\n  );\n\nexport const getParticipantsContactIds = ({ participants }) =>\n  participants.reduce((acc, participant) => {\n    const { contact_ids: contactIds } = participant;\n    if (contactIds && contactIds.length > 0) {\n      return [...acc, ...contactIds];\n    }\n\n    throw new Error(\n      `No contact for participant ${participant.label}, cannot encrypt`\n    );\n  }, []);\n\nexport const getParticipantsAddresses = ({ participants }) =>\n  participants.map((participant) => participant.address);\n"
  },
  {
    "path": "src/frontend/web_application/src/services/message/index.spec.js",
    "content": "import {\n  isMessageFromUser,\n  getRecipients,\n  getRecipientsExceptUser,\n  isUserRecipient,\n} from './index';\n\ndescribe('message services', () => {\n  const user = {\n    contact: {\n      contact_id: 'john',\n    },\n  };\n\n  describe('isMessageFromUser', () => {\n    it('unknown contact', () => {\n      const message = {\n        participants: [\n          {\n            type: 'From',\n          },\n        ],\n      };\n      expect(isMessageFromUser(message, user)).toEqual(false);\n    });\n\n    it('known contact', () => {\n      const message = {\n        participants: [\n          {\n            type: 'From',\n            contact_ids: ['whatever'],\n          },\n        ],\n      };\n      expect(isMessageFromUser(message, user)).toEqual(false);\n    });\n\n    it('is actually the user', () => {\n      const message = {\n        participants: [\n          {\n            type: 'From',\n            contact_ids: ['john'],\n          },\n        ],\n      };\n      expect(isMessageFromUser(message, user)).toEqual(true);\n    });\n  });\n\n  it('getRecipients', () => {\n    const message = {\n      participants: [\n        {\n          type: 'From',\n        },\n        {\n          type: 'To',\n        },\n        {\n          type: 'Cc',\n        },\n      ],\n    };\n    expect(getRecipients(message).length).toEqual(2);\n  });\n\n  describe('getRecipientsExceptUser', () => {\n    it('user is a recipient', () => {\n      const message = {\n        participants: [\n          {\n            type: 'From',\n          },\n          {\n            type: 'To',\n            contact_ids: ['john'],\n          },\n          {\n            type: 'Cc',\n          },\n        ],\n      };\n      expect(getRecipientsExceptUser(message, user).length).toEqual(1);\n    });\n\n    it('user is author', () => {\n      const message = {\n        participants: [\n          {\n            type: 'From',\n            contact_ids: ['john'],\n          },\n          {\n            type: 'To',\n          },\n          {\n            type: 'Cc',\n          },\n        ],\n      };\n      expect(getRecipientsExceptUser(message, user).length).toEqual(2);\n    });\n  });\n\n  describe('isUserRecipient', () => {\n    it('user is a recipient', () => {\n      const message = {\n        participants: [\n          {\n            type: 'From',\n          },\n          {\n            type: 'To',\n            contact_ids: ['john'],\n          },\n          {\n            type: 'Cc',\n          },\n        ],\n      };\n      expect(isUserRecipient(message, user)).toEqual(true);\n    });\n\n    it('user is author', () => {\n      const message = {\n        participants: [\n          {\n            type: 'From',\n            contact_ids: ['john'],\n          },\n          {\n            type: 'To',\n          },\n          {\n            type: 'Cc',\n          },\n        ],\n      };\n      expect(isUserRecipient(message, user)).toEqual(false);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/services/mime/index.js",
    "content": "import { parse, factory } from 'mimemessage';\nimport base64 from 'base64-js';\nimport utf8 from 'utf8';\nimport quotedPrintable from 'quoted-printable';\nimport { utf8ArrayToString } from '../encode-utils';\n\nconst findEntityByContentType = (entity, contentType) => {\n  if (entity.isMultiPart()) {\n    return entity.body.reduce(\n      (acc, part) => findEntityByContentType(part, contentType) || acc,\n      null\n    );\n  }\n\n  const { fulltype } = entity.contentType();\n  if (fulltype === contentType) {\n    return entity;\n  }\n\n  return null;\n};\n\n// mail sometimes do not have CRLF as they should.\nconst crlfize = (str) => str.replace(/([^\\r])?\\n/g, '$1\\r\\n');\n\nexport const getPlainTextFromMime = ({ body }) => {\n  const entity = parse(crlfize(body));\n\n  if (entity) {\n    const plainBody = findEntityByContentType(entity, 'text/plain');\n\n    if (plainBody) {\n      const contentTransferEncoding = plainBody.contentTransferEncoding();\n\n      switch (contentTransferEncoding) {\n        case 'quoted-printable':\n          return utf8.decode(quotedPrintable.decode(plainBody.body));\n        case 'base64':\n          return utf8ArrayToString(base64.toByteArray(plainBody.body));\n        default:\n          return plainBody.body;\n      }\n    }\n  }\n\n  return body;\n};\n\nexport const mimeEncapsulate = (body) =>\n  factory({\n    contentType: 'text/plain;charset=utf8',\n    body,\n  });\n"
  },
  {
    "path": "src/frontend/web_application/src/services/openpgp-keychain-repository/index.js",
    "content": "export const [PUBLIC_KEY, PRIVATE_KEY] = ['public', 'private'];\n\nconst getKeyring = async () => {\n  const { Keyring } = await import(/* webpackChunkName: \"openpgp\" */ 'openpgp');\n\n  return new Keyring();\n};\n\nconst loadKeyring = async () => {\n  const keyring = await getKeyring();\n\n  await keyring.load();\n\n  return keyring;\n};\n\nconst getKeyIdsForMessage = async ({ body }) => {\n  const openpgp = await import(/* webpackChunkName: \"openpgp\" */ 'openpgp');\n  const encryptedMessage = await openpgp.message.readArmored(body);\n  const keyIds = encryptedMessage.getEncryptionKeyIds();\n\n  return keyIds;\n};\n\nexport const getKeysForMessage = async (message) => {\n  const keyring = await loadKeyring();\n  const keyIds = await getKeyIdsForMessage(message);\n\n  return keyIds.reduce((acc, keyId) => {\n    const keys = keyring.privateKeys.getForId(keyId.toHex(), true);\n\n    return keys ? [...acc, keys] : acc;\n  }, []);\n};\n\nexport async function getPrimaryKeysByFingerprint() {\n  const keyring = await loadKeyring();\n\n  return keyring.getAllKeys().reduce((acc, key) => {\n    const fingerprint = key.getFingerprint();\n    const keyType = key.isPublic() ? 'publicKeyArmored' : 'privateKeyArmored';\n\n    return {\n      ...acc,\n      [fingerprint]: {\n        ...acc[fingerprint],\n        [keyType]: key.armor(),\n      },\n    };\n  }, {});\n}\n\nexport async function getKeysForEmail(email, keyType = PUBLIC_KEY) {\n  const keyring = await loadKeyring();\n\n  if (keyType === PUBLIC_KEY) {\n    return keyring.publicKeys.getForAddress(email);\n  }\n\n  if (keyType === PRIVATE_KEY) {\n    return keyring.privateKeys.getForAddress(email);\n  }\n\n  throw new Error('keyType must be either PUBLIC_KEY or PRIVATE_KEY');\n}\n\nexport async function saveKey(publicKeyArmored, privateKeyArmored) {\n  const keyring = await loadKeyring();\n\n  await keyring.publicKeys.importKey(publicKeyArmored);\n  await keyring.privateKeys.importKey(privateKeyArmored);\n\n  const error = await keyring.store();\n\n  if (error) {\n    throw new Error(error);\n  }\n\n  return undefined;\n}\n\nexport async function deleteKey(fingerprint) {\n  const keyring = await loadKeyring();\n\n  keyring.removeKeysForId(fingerprint);\n  const error = await keyring.store();\n\n  return error;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/services/openpgp-manager/api.ts",
    "content": "import flatMap from 'lodash/flatMap';\nimport * as openPgpModule from 'openpgp';\n\nimport {\n  ERROR_UNABLE_READ_PUBLIC_KEY,\n  ERROR_UNABLE_READ_PRIVATE_KEY,\n  ERROR_FINGERPRINTS_NOT_MATCH,\n} from './errors';\n\nconst GENERATE_KEY_OPTIONS = {\n  numBits: 4096,\n};\n\nexport { openPgpModule as module };\n\n// http://monsur.hossa.in/2012/07/20/utf-8-in-javascript.html\nfunction encodeUTF8(str: string) {\n  return unescape(encodeURIComponent(str));\n}\n\n// function decodeUTF8(str) {\n//   return decodeURIComponent(escape(str));\n// }\n\nexport function generateKey(name, email, passphrase, options = {}) {\n  return openPgpModule.generateKey({\n    ...GENERATE_KEY_OPTIONS,\n    ...options,\n    userIds: [{ name: encodeUTF8(name), email }],\n    passphrase,\n  });\n}\n\nexport async function getKeyFromASCII(armored) {\n  const { keys } = await openPgpModule.key.readArmored(armored);\n\n  if (keys && keys.length) {\n    return keys[0];\n  }\n\n  return undefined;\n}\n\nexport async function encrypt({ content, recipientPubKeys }, key) {\n  const keysList = await Promise.all(\n    recipientPubKeys.map((pubkey) => openPgpModule.key.readArmored(pubkey))\n  );\n  const publicKeys = flatMap(keysList, ({ keys }) => keys);\n\n  const options = {\n    message: content,\n    publicKeys,\n    privateKeys: [key],\n  };\n\n  return openPgpModule.encrypt(options);\n}\n\nexport async function decrypt({ content, authorPubKeys = [] }, key) {\n  const keysList = await Promise.all(\n    authorPubKeys.map((pubkey) => openPgpModule.key.readArmored(pubkey))\n  );\n\n  const publicKeys = flatMap(keysList, ({ keys }) => keys);\n  const message = await openPgpModule.message.readArmored(content);\n\n  const options = {\n    message,\n    publicKeys,\n    privateKey: key,\n  };\n\n  openPgpModule.decrypt(options);\n}\n\nexport async function validatePublicKeyChain(__, publicKeyArmored) {\n  const publicKey = await getKeyFromASCII(publicKeyArmored);\n\n  if (!publicKey) {\n    return Promise.reject(ERROR_UNABLE_READ_PUBLIC_KEY);\n  }\n\n  return { key: publicKey, publicKeyArmored };\n}\n\ninterface ValidateKeyChainPairErrors {\n  publicKeyArmored?: [typeof ERROR_UNABLE_READ_PUBLIC_KEY];\n  privateKeyArmored?: [typeof ERROR_UNABLE_READ_PRIVATE_KEY];\n  global?: [typeof ERROR_FINGERPRINTS_NOT_MATCH];\n}\nexport async function validateKeyChainPair(\n  publicKeyArmored,\n  privateKeyArmored\n) {\n  const publicKey = await getKeyFromASCII(publicKeyArmored);\n  const privateKey = await getKeyFromASCII(privateKeyArmored);\n\n  const errors: ValidateKeyChainPairErrors = {};\n  if (!publicKey) {\n    errors.publicKeyArmored = [ERROR_UNABLE_READ_PUBLIC_KEY];\n  }\n\n  if (!privateKey) {\n    errors.privateKeyArmored = [ERROR_UNABLE_READ_PRIVATE_KEY];\n  }\n\n  if (\n    publicKey &&\n    privateKey &&\n    publicKey.primaryKey.getFingerprint() !==\n      privateKey.primaryKey.getFingerprint()\n  ) {\n    errors.global = [ERROR_FINGERPRINTS_NOT_MATCH];\n  }\n\n  if (errors) {\n    return Promise.reject(errors);\n  }\n\n  return { key: publicKey, publicKeyArmored, privateKeyArmored };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/services/openpgp-manager/errors.ts",
    "content": "export const ERROR_UNABLE_READ_PUBLIC_KEY = 'unable-read-public-key';\nexport const ERROR_UNABLE_READ_PRIVATE_KEY = 'unable-read-private-key';\nexport const ERROR_FINGERPRINTS_NOT_MATCH = 'fingerprints-not-match';\n"
  },
  {
    "path": "src/frontend/web_application/src/services/openpgp-manager/index.spec.ts",
    "content": "import * as openPGPManager from './index';\n\ndescribe('Service OpenPGPManager', () => {\n  it('init', () => {\n    expect(openPGPManager).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/services/openpgp-manager/index.ts",
    "content": "export * from './errors';\nexport default () => import(/* webpackChunkName: \"openpgp-wrapper\" */ './api');\n"
  },
  {
    "path": "src/frontend/web_application/src/services/protocols-config/index.ts",
    "content": "import { Type } from 'src/components/Icon';\nimport { Protocol } from 'src/store/modules/participant-suggestions';\n\n// XXX: unexpected lint error\n// eslint-disable-next-line no-shadow\nexport enum ASSOC_PROTOCOL_ICON {\n  email = 'envelope',\n  mastodon = 'mastodon',\n  twitter = 'twitter',\n  unknown = 'question-circle',\n}\n\nexport const getIconType = (protocol: Protocol): Type =>\n  ASSOC_PROTOCOL_ICON[protocol] || ASSOC_PROTOCOL_ICON.unknown;\n\nexport default {\n  unknown: {\n    default: true,\n  },\n  sms: {},\n  email: {\n    regexp:\n      /^[a-z0-9.!#$%&*+=?_{}~-]+@([a-zA-Z0-9]+\\.)?[a-zA-Z0-9][a-zA-Z0-9-]*\\.[a-zA-Z]{2,60}$/,\n  },\n  twitter: {\n    // https://help.twitter.com/en/managing-your-account/twitter-username-rules\n    regexp: /^\\w{1,15}$/,\n  },\n  facebook: {},\n  mastodon: {\n    regexp:\n      /^[a-z0-9.!#$%&*+=?_{}~-]+@([a-zA-Z0-9]+\\.)?[a-zA-Z0-9][a-zA-Z0-9-]*\\.[a-zA-Z]{2,60}$/,\n  },\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/services/renderReduxField/index.jsx",
    "content": "import React, { PureComponent } from 'react';\nimport PropTypes from 'prop-types';\n\nconst renderReduxField = (WrappedComponent) => {\n  // TODO: use same api in caliopen form fields as redux-form\n  class ReduxField extends PureComponent {\n    static propTypes = {\n      input: PropTypes.shape({}).isRequired,\n    };\n\n    static defaultProps = {};\n\n    render() {\n      // extract unused props to prevent \"Warning: Unknown prop\"\n      // eslint-disable-next-line react/prop-types\n      const { input, meta, ...props } = this.props;\n\n      // eslint-disable-next-line react/prop-types\n      const errors = meta.error ? [meta.error] : [];\n\n      return <WrappedComponent {...input} errors={errors} {...props} />;\n    }\n  }\n\n  return ReduxField;\n};\n\nexport default renderReduxField;\n"
  },
  {
    "path": "src/frontend/web_application/src/services/username-utils/username-availability.ts",
    "content": "import axios from 'axios';\n\nexport const checkAvailability = async (username: string) => {\n  const response = await axios.get('/api/v2/username/isAvailable', {\n    params: { username },\n    headers: { 'X-Requested-With': 'XMLHttpRequest' },\n  });\n  return response.data.available;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/services/username-utils/username-validity.spec.ts",
    "content": "import Schema from 'async-validator';\nimport descriptor from './username-validity';\n\ndescribe('services username-utils username-validity', () => {\n  const validator = new Schema(descriptor);\n\n  const validate = (username) =>\n    new Promise((resolve, reject) => {\n      // @ts-ignore: old version?\n      validator.validate({ username }, (errors, fields) => {\n        if (errors) {\n          return reject({ errors, fields });\n        }\n\n        return resolve(true);\n      });\n    });\n\n  const unexpectedValid = () => expect(true).toBe(false);\n  const expectTrue = (res) => expect(res).toBe(true);\n  const expectOneError = (res) => expect(res.errors.length).toEqual(1);\n\n  describe('isValid', () => {\n    it('not valid under 3 characters & after 42 characters', async () => {\n      try {\n        await validate('aa');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n\n      try {\n        await validate('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n    });\n\n    it('valid with 3 characters', async () => {\n      let isValid = await validate('aaa');\n      expectTrue(isValid);\n      isValid = await validate('a.b');\n      expectTrue(isValid);\n    });\n\n    it('valid with alphabet', async () => {\n      const isValid = await validate('thequickbrownfoxjumpsoverthelazydog');\n      expectTrue(isValid);\n    });\n\n    it('valid with capital', async () => {\n      let isValid = await validate('AbcD');\n      expectTrue(isValid);\n      isValid = await validate('ABCD');\n      expectTrue(isValid);\n      isValid = await validate('AbCd');\n      expectTrue(isValid);\n    });\n\n    it('not valid with spaces', async () => {\n      try {\n        await validate('Ab cD');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('A bc D');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate(' AbcD ');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('Ab  cD');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n    });\n\n    it('valid with some specials characters', async () => {\n      let isValid = await validate('aa.b');\n      expectTrue(isValid);\n      isValid = await validate('b-b');\n      expectTrue(isValid);\n      isValid = await validate('été');\n      expectTrue(isValid);\n      isValid = await validate('test+à');\n      expectTrue(isValid);\n      isValid = await validate('❤κξαδιθροχ');\n      expectTrue(isValid);\n      isValid = await validate('名無しの権兵衛');\n      expectTrue(isValid);\n      isValid = await validate(')-oꞁꞁǝH');\n      expectTrue(isValid);\n    });\n\n    // XXX : requires better regex\n    // it('not valid with unicode combining characters', async () => {\n    //   try {\n    //     await validate('e\\u0301te\\u0301'); // été\n    //     unexpectedValid();\n    //   } catch (e) {\n    //     expectOneError(e);\n    //   }\n    // });\n\n    it('not valid with starting & ending dot', async () => {\n      try {\n        await validate('.abcd');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abcd.');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n    });\n\n    it('not valid with double dot', async () => {\n      try {\n        await validate('ab..cd');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n    });\n\n    it('not valid with not compliant email local-part', async () => {\n      try {\n        await validate('abc\"d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abc@d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abc`d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abc:d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abc;d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abc<d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abc>d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abc[d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abc]d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n      try {\n        await validate('abc\\\\d');\n        unexpectedValid();\n      } catch (e) {\n        expectOneError(e);\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/services/username-utils/username-validity.ts",
    "content": "import { RuleItem } from 'async-validator';\n\nexport const ERR_MIN_MAX = 'ERR_MIN_MAX';\nexport const ERR_INVALID_CHARACTER = 'ERR_INVALID_CHARACTER';\nexport const ERR_DOTS = 'ERR_DOTS';\nexport const ERR_DOUBLE_DOTS = 'ERR_DOUBLE_DOTS';\n\nconst descriptor: { username: RuleItem[] } = {\n  username: [\n    {\n      type: 'string',\n      min: 3,\n      max: 42,\n      message: ERR_MIN_MAX,\n    },\n    // @ts-ignore: old version?\n    { type: 'pattern', pattern: /^[^.].*[^.]$/, message: ERR_DOTS }, // https://regex101.com/r/TLUfNZ/2\n    // @ts-ignore: old version?\n    { type: 'pattern', pattern: /^(?!.*\\.\\.).*$/, message: ERR_DOUBLE_DOTS }, // https://regex101.com/r/TLUfNZ/3\n    {\n      // @ts-ignore: old version?\n      type: 'pattern',\n      pattern: /^[^\\s\"@`:;<>[\\]\\\\]*$/,\n      message: ERR_INVALID_CHARACTER,\n    }, // https://regex101.com/r/w5nue1/1\n  ],\n};\n\nexport default descriptor;\n"
  },
  {
    "path": "src/frontend/web_application/src/store/actions/message.js",
    "content": "import {\n  requestMessage,\n  updateMessage as updateMessageBase,\n} from '../modules/message';\nimport { tryCatchAxiosAction } from '../../services/api-client';\n\nexport const updateMessage =\n  ({ message, original }) =>\n  (dispatch) =>\n    tryCatchAxiosAction(async () => {\n      await dispatch(updateMessageBase({ message, original }));\n\n      return dispatch(requestMessage(message.message_id));\n    });\n"
  },
  {
    "path": "src/frontend/web_application/src/store/actions/timeline.js",
    "content": "import {\n  TIMELINE_FILTER_ALL,\n  TIMELINE_FILTER_RECEIVED,\n  TIMELINE_FILTER_SENT,\n  TIMELINE_FILTER_DRAFT,\n  setTimelineFilter,\n  requestMessages,\n} from '../modules/message';\n\nconst getQuery = (type) => {\n  switch (type) {\n    default:\n    case TIMELINE_FILTER_ALL:\n      return {};\n    case TIMELINE_FILTER_SENT:\n      return { is_received: false, is_draft: false };\n    case TIMELINE_FILTER_RECEIVED:\n      return { is_received: true };\n    case TIMELINE_FILTER_DRAFT:\n      return { is_draft: true };\n  }\n};\n\nexport const filterTimeline = (type) => (dispatch) => {\n  dispatch(setTimelineFilter(type));\n\n  return dispatch(requestMessages('timeline', type, getQuery(type)));\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/store/configure-store.ts",
    "content": "import { configureStore, ConfigureStoreOptions } from '@reduxjs/toolkit';\nimport rootReducer, { RootState } from './reducer';\nimport axiosMiddleware from './middlewares/axios-middleware';\nimport encryptionMiddleware from './middlewares/encryption-middleware';\nimport decryptionMiddleware from './middlewares/decryption-middleware';\nimport discussionMiddleware from './middlewares/discussions-middleware';\nimport importanceLevelMiddleware from './middlewares/importance-level-middleware';\nimport messageMiddleware from './middlewares/messages-middleware';\nimport searchMiddleware from './middlewares/search-middleware';\n\nconst middlewares = [\n  encryptionMiddleware,\n  axiosMiddleware,\n  decryptionMiddleware,\n  discussionMiddleware,\n  importanceLevelMiddleware,\n  messageMiddleware,\n  searchMiddleware,\n];\ntype PreloadedState = ConfigureStoreOptions<RootState>['preloadedState'];\n\nfunction configureAppStore(preloadedState?: PreloadedState) {\n  return configureStore({\n    reducer: rootReducer,\n    middleware: (getDefaultMiddleware) =>\n      getDefaultMiddleware({\n        // prevent non serializable error on axios actions (only msw?)\n        serializableCheck: false,\n      }).prepend(...middlewares),\n    preloadedState,\n  });\n}\n\nexport default configureAppStore;\n"
  },
  {
    "path": "src/frontend/web_application/src/store/middlewares/axios-middleware.ts",
    "content": "import axiosMiddleware from 'redux-axios-middleware';\nimport {\n  createNotification,\n  NOTIFICATION_TYPE_ERROR,\n} from 'react-redux-notify';\nimport getClient from '../../services/api-client';\nimport { signout } from '../../modules/routing';\n\nexport default axiosMiddleware(getClient(), {\n  returnRejectedPromiseOnError: true,\n  interceptors: {\n    request: [\n      ({ getState }, config) => {\n        const [min, max] = getState().importanceLevel.range;\n\n        return {\n          ...config,\n          headers: {\n            ...config.headers,\n            'X-Caliopen-IL': `${min};${max}`,\n          },\n        };\n      },\n    ],\n    response: [\n      {\n        error: ({ getState, dispatch }, error) => {\n          // customStyles applied to Notification component\n          const customStyles = {\n            'has-close': 'l-notification-center__notification--has-close',\n            'has-close-all':\n              'l-notification-center__notification--has-close-all',\n            item__message: 'l-notification-center__notification-item-message',\n          };\n          if (error.response && error.response.status === 401) {\n            signout({ withRedirect: true });\n            throw error;\n          }\n\n          if (error.response && error.response.status >= 500) {\n            const notification = {\n              // FIXME: trad\n              message:\n                'Sorry, an unexpected error occured. developers will work hard on this error during alpha phase. Please feel free to describe us what happened.',\n              type: NOTIFICATION_TYPE_ERROR,\n              duration: 0,\n              canDismiss: true,\n              customStyles,\n            };\n\n            if (\n              !getState().notifications.find(\n                ({ message }) => message === notification.message\n              )\n            ) {\n              dispatch(createNotification(notification));\n            }\n          }\n\n          throw error;\n        },\n      },\n    ],\n  },\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/store/middlewares/crash-reporter-middleware.js",
    "content": "export default () => (next) => (action) => {\n  try {\n    return next(action);\n  } catch (err) {\n    // eslint-disable-next-line no-console\n    console.error('Caught an exception!', err);\n\n    throw err;\n  }\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/store/middlewares/decryption-middleware.js",
    "content": "import { decryptMessage } from '../../modules/encryption';\n// Renaming REQUEST_DRAFT_SUCCESS actions for disambiguation.\nimport {\n  decryptDraftSuccess,\n  // REQUEST_DRAFT_SUCCESS as DRAFT_REQUEST_DRAFT_SUCCESS,\n} from '../modules/draft-message';\nimport {\n  FETCH_MESSAGES_SUCCESS,\n  REQUEST_MESSAGE_SUCCESS,\n  REQUEST_MESSAGES_SUCCESS,\n  REQUEST_DRAFT_SUCCESS as MESSAGE_REQUEST_DRAFT_SUCCESS,\n} from '../modules/message';\nimport {\n  setPassphraseSuccess,\n  resetPassphrase,\n  SET_PASSPHRASE,\n  SET_PASSPHRASE_SUCCESS,\n} from '../modules/encryption';\n\nconst extractMessagesFromAction = ({ payload, type }) => {\n  switch (type) {\n    // case DRAFT_REQUEST_DRAFT_SUCCESS:\n    //   return [payload.draft];\n    case REQUEST_MESSAGE_SUCCESS:\n      return [payload.data];\n    case REQUEST_MESSAGES_SUCCESS:\n    case FETCH_MESSAGES_SUCCESS:\n    case MESSAGE_REQUEST_DRAFT_SUCCESS:\n      return payload.data.messages;\n    default:\n      return [];\n  }\n};\n\nconst decryptMessages =\n  ({ messages }) =>\n  async (dispatch) => {\n    if (messages.length <= 0) return messages;\n\n    return Promise.all(\n      messages.map((message) => dispatch(decryptMessage({ message })))\n    );\n  };\n\nconst findMessagesEncryptedWithKey = (state, fingerprint) => {\n  const { messageEncryptionStatusById } = state.encryption;\n\n  return Object.values(messageEncryptionStatusById).filter(\n    (messageEntry) =>\n      messageEntry.keyFingerprint &&\n      Object.values(messageEntry.keyFingerprint).includes(fingerprint)\n  );\n};\n\nconst setPassphrase =\n  ({ fingerprint }) =>\n  (dispatch, getState) => {\n    const messages = findMessagesEncryptedWithKey(getState(), fingerprint).map(\n      (message) => message.encryptedMessage\n    );\n\n    dispatch(decryptMessages({ messages }));\n    // discard passphrase after 20 minutes\n    setTimeout(() => dispatch(resetPassphrase({ fingerprint })), 12000000);\n  };\n\nexport default (store) => (next) => (action) => {\n  switch (action.type) {\n    // FIXME: this does not exist anymore, is it syncDraft now? (but it is a draftFormData now)\n    // What is the algo: fetch draft > decrypt draft > store draft form data > edit draft > encrypt draft > save ?\n    // case DRAFT_REQUEST_DRAFT_SUCCESS:\n    //   store\n    //     .dispatch(\n    //       decryptMessage({ message: extractMessagesFromAction(action)[0] })\n    //     )\n    //     .then((draft) =>\n    //       store.dispatch(\n    //         decryptDraftSuccess({\n    //           internalId: action.payload.internalId,\n    //           draft,\n    //         })\n    //       )\n    //     );\n\n    //   return next(action);\n    case MESSAGE_REQUEST_DRAFT_SUCCESS:\n    case REQUEST_MESSAGE_SUCCESS:\n    case REQUEST_MESSAGES_SUCCESS:\n    case FETCH_MESSAGES_SUCCESS:\n      store.dispatch(\n        decryptMessages({ messages: extractMessagesFromAction(action) })\n      );\n\n      return next(action);\n    case SET_PASSPHRASE:\n      next(action);\n\n      return store.dispatch(\n        setPassphraseSuccess({ fingerprint: action.payload.fingerprint })\n      );\n    case SET_PASSPHRASE_SUCCESS:\n      store.dispatch(\n        setPassphrase({ fingerprint: action.payload.fingerprint })\n      );\n\n      return next(action);\n    default:\n      break;\n  }\n\n  return next(action);\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/store/middlewares/discussions-middleware.js",
    "content": "import {\n  getNextOffset,\n  requestDiscussions,\n  LOAD_MORE_DISCUSSIONS,\n} from '../modules/discussion';\n\nexport default (store) => (next) => (action) => {\n  const result = next(action);\n\n  if (action.type === LOAD_MORE_DISCUSSIONS) {\n    const offset = getNextOffset(store.getState().discussion);\n    store.dispatch(requestDiscussions({ offset }));\n  }\n\n  return result;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/store/middlewares/encryption-middleware.js",
    "content": "import { encryptMessage } from '../../modules/encryption';\nimport {\n  CREATE_MESSAGE,\n  UPDATE_MESSAGE,\n  createMessage,\n  updateMessage,\n} from '../modules/message';\n\nconst modifyActionWithMessage = ({ action, message }) => {\n  switch (action.type) {\n    case CREATE_MESSAGE:\n      return createMessage({ message });\n    case UPDATE_MESSAGE:\n      return updateMessage({ message, original: action.payload.original });\n    default:\n      throw new Error(`action type \"${action.type}\" is not supported`);\n  }\n};\n\nexport default (store) => (next) => (action) => {\n  if (action.type !== CREATE_MESSAGE) {\n    return next(action);\n  }\n\n  if (![CREATE_MESSAGE, UPDATE_MESSAGE].includes(action.type)) {\n    return next(action);\n  }\n\n  const { message } = action.payload;\n\n  return (async () => {\n    const encryptedMessage = await store.dispatch(encryptMessage({ message }));\n\n    if (encryptedMessage) {\n      return next(\n        modifyActionWithMessage({\n          action,\n          message: {\n            ...encryptedMessage,\n            privacy_features: {\n              ...encryptedMessage.privacy_features,\n              message_encrypted: 'True',\n              message_encryption_method: 'pgp',\n            },\n          },\n        })\n      );\n    }\n\n    return next(\n      modifyActionWithMessage({\n        action,\n        message: {\n          ...message,\n          privacy_features: !message.privacy_features\n            ? message.privacy_features\n            : {\n                ...message.privacy_features,\n                message_encrypted: 'False',\n              },\n        },\n      })\n    );\n  })();\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/store/middlewares/freeze.js",
    "content": "/**\n * Inspired by\n * https://raw.githubusercontent.com/buunguyen/redux-freeze/master/src/middleware.js\n */\nimport deepFreeze from 'deep-freeze-strict'; // eslint-disable-line import/no-extraneous-dependencies\n\nfunction isFreezable(value) {\n  return value !== null && typeof value === 'object';\n}\n\nfunction freezeStoreState(store) {\n  const state = store.getState();\n  if (isFreezable(state)) {\n    deepFreeze(state);\n  }\n}\n\n/**\n * Middleware that prevents state from being mutated anywhere in the app.\n */\nexport default function freezeState(store) {\n  return (next) => (action) => {\n    freezeStoreState(store);\n    try {\n      return next(action);\n    } catch (err) {\n      console.error('State may has mutated', err.stack); // eslint-disable-line no-console\n    }\n\n    freezeStoreState(store);\n\n    return undefined;\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/middlewares/importance-level-middleware.js",
    "content": "import { SET_IMPORTANCE_LEVEL } from '../modules/importance-level';\nimport { invalidateAll } from '../modules/message';\n\nconst importanceLevelHandler = ({ store, action }) => {\n  if (action.type !== SET_IMPORTANCE_LEVEL) {\n    return;\n  }\n\n  store.dispatch(invalidateAll());\n};\n\nexport default (store) => (next) => (action) => {\n  const result = next(action);\n\n  importanceLevelHandler({ store, action });\n\n  return result;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/store/middlewares/messages-middleware.js",
    "content": "import {\n  LOAD_MORE_MESSAGES,\n  requestMessages,\n  getNextOffset,\n} from '../modules/message';\n\nconst loadMoreHandler = ({ store, action }) => {\n  if (action.type !== LOAD_MORE_MESSAGES) {\n    return;\n  }\n\n  const { type, key } = action.payload;\n  const collectionState =\n    store.getState().message.messagesCollections[type][key];\n  const offset = getNextOffset(collectionState);\n  const { params = {} } = collectionState.request;\n  store.dispatch(requestMessages(type, key, { ...params, offset }));\n};\n\nexport default (store) => (next) => (action) => {\n  const result = next(action);\n\n  loadMoreHandler({ store, action });\n\n  return result;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/store/middlewares/search-middleware.js",
    "content": "import { LOAD_MORE, search, getNextOffset } from '../modules/search';\n\nconst loadMoreHandler = ({ store, action }) => {\n  if (action.type !== LOAD_MORE) {\n    return;\n  }\n\n  const { term, doctype } = action.payload;\n  const offset = getNextOffset(term, doctype, store.getState().search);\n\n  store.dispatch(search({ term, doctype }, { offset }));\n};\n\nexport default (store) => (next) => (action) => {\n  const result = next(action);\n\n  loadMoreHandler({ store, action });\n\n  return result;\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/device.ts",
    "content": "import { Device } from 'src/modules/device/types';\nimport calcObjectForPatch from '../../services/api-patch';\n\nexport const SET_NEW_DEVICE = 'co/device/SET_NEW_DEVICE';\nexport const SET_DEVICE_GENERATED = 'co/device/SET_DEVICE_GENERATED';\nexport const REQUEST_DEVICES = 'co/device/REQUEST_DEVICES';\nexport const REQUEST_DEVICES_SUCCESS = 'co/device/REQUEST_DEVICES_SUCCESS';\nexport const REQUEST_DEVICES_FAIL = 'co/device/REQUEST_DEVICES_FAIL';\nexport const INVALIDATE_DEVICES = 'co/device/INVALIDATE_DEVICES';\nexport const REQUEST_DEVICE = 'co/device/REQUEST_DEVICE';\nexport const CREATE_DEVICE = 'co/device/CREATE_DEVICE';\nexport const UPDATE_DEVICE = 'co/device/UPDATE_DEVICE';\nexport const REMOVE_DEVICE = 'co/device/REMOVE_DEVICE';\nexport const VERIFY_DEVICE = 'co/device/VERIFY_DEVICE';\nexport const VALIDATE_DEVICE = 'co/device/VALIDATE_DEVICE';\n\nexport function setNewDevice(isNew) {\n  return {\n    type: SET_NEW_DEVICE,\n    payload: {\n      isNew,\n    },\n  };\n}\n\nexport function setDeviceGenerated(isGenerated) {\n  return {\n    type: SET_DEVICE_GENERATED,\n    payload: {\n      isGenerated,\n    },\n  };\n}\n\nexport function requestDevices() {\n  return {\n    type: REQUEST_DEVICES,\n    payload: {\n      request: {\n        url: '/api/v2/devices',\n      },\n    },\n  };\n}\n\nexport function createDevice({ device }) {\n  return {\n    type: CREATE_DEVICE,\n    payload: {\n      request: {\n        method: 'post',\n        url: '/api/v2/devices',\n        data: device,\n      },\n    },\n  };\n}\n\nexport function requestDevice({ deviceId }) {\n  return {\n    type: REQUEST_DEVICE,\n    payload: {\n      request: {\n        url: `/api/v2/devices/${deviceId}`,\n      },\n    },\n  };\n}\n\nexport function invalidate() {\n  return {\n    type: INVALIDATE_DEVICES,\n    payload: {},\n  };\n}\n\nexport function removeDevice({ device }) {\n  return {\n    type: REMOVE_DEVICE,\n    payload: {\n      request: {\n        method: 'delete',\n        url: `/api/v2/devices/${device.device_id}`,\n      },\n    },\n  };\n}\n\n// TODO: move to a device's module action\nexport function verifyDevice({ device }) {\n  return {\n    type: VERIFY_DEVICE,\n    payload: {\n      request: {\n        method: 'post',\n        url: `/api/v2/devices/${device.device_id}/actions`,\n        data: {\n          actions: ['device-validation'],\n          params: { channel: 'email' },\n        },\n      },\n    },\n  };\n}\n\nexport function requestDeviceVerification({ token }) {\n  return {\n    type: VALIDATE_DEVICE,\n    payload: {\n      request: {\n        method: 'get',\n        url: `/api/v2/validate-device/${token}`,\n      },\n    },\n  };\n}\n\nexport function updateDevice({\n  device,\n  original,\n}: {\n  device: Device;\n  original: Device;\n}) {\n  const data = calcObjectForPatch(device, original);\n\n  return {\n    type: UPDATE_DEVICE,\n    payload: {\n      request: {\n        method: 'patch',\n        url: `/api/v2/devices/${device.device_id}`,\n        data,\n      },\n    },\n  };\n}\n\nfunction devicesByIdReducer(state, action) {\n  switch (action.type) {\n    case REQUEST_DEVICES_SUCCESS:\n      return action.payload.data.devices.reduce(\n        (acc, device: Device) => ({\n          ...acc,\n          [device.device_id]: device,\n        }),\n        {}\n      );\n    default:\n      return state;\n  }\n}\n\nfunction devicesReducer(state, action) {\n  switch (action.type) {\n    case REQUEST_DEVICES_SUCCESS:\n      return action.payload.data.devices.map((device) => device.device_id);\n    default:\n      return state;\n  }\n}\n\ninterface State {\n  isFetching: boolean;\n  didInvalidate: boolean;\n  devices: string[];\n  devicesById: Record<string, Device>;\n  total: number;\n  isNew: boolean;\n  isGenerated: boolean;\n}\n\nconst initialState: State = {\n  isFetching: false,\n  didInvalidate: false,\n  devices: [],\n  devicesById: {},\n  total: 0,\n  isNew: false,\n  isGenerated: false,\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case SET_NEW_DEVICE:\n      return { ...state, isNew: action.payload.isNew };\n    case SET_DEVICE_GENERATED:\n      return { ...state, isGenerated: action.payload.isGenerated };\n    case REQUEST_DEVICES:\n      return { ...state, isFetching: true };\n    case REQUEST_DEVICES_SUCCESS:\n      return {\n        ...state,\n        isFetching: false,\n        didInvalidate: false,\n        devices: devicesReducer(state.devices, action),\n        devicesById: devicesByIdReducer(state.devicesById, action),\n        total: action.payload.data.total,\n      };\n    case INVALIDATE_DEVICES:\n      return { ...state, didInvalidate: true };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/discussion.ts",
    "content": "import { Participant } from '../../modules/message/index';\n\ninterface Action {\n  type: string;\n  payload: any;\n  meta?: any;\n}\n// import calcObjectForPatch from '../../services/api-patch';\nexport const REQUEST_DISCUSSIONS = 'co/discussion/REQUEST_DISCUSSIONS';\nexport const REQUEST_DISCUSSIONS_SUCCESS =\n  'co/discussion/REQUEST_DISCUSSIONS_SUCCESS';\nexport const REQUEST_DISCUSSIONS_FAIL =\n  'co/discussion/REQUEST_DISCUSSIONS_FAIL';\nexport const INVALIDATE_DISCUSSIONS = 'co/discussion/INVALIDATE_DISCUSSIONS';\nexport const REMOVE_DISCUSSION_FROM_COLLECTION =\n  'co/discussion/REMOVE_DISCUSSION_FROM_COLLECTION';\nexport const LOAD_MORE_DISCUSSIONS = 'co/discussion/LOAD_MORE_DISCUSSIONS';\nexport const FILTER_IMPORTANCE = 'co/discussion/FILTER_IMPORTANCE';\nexport const REQUEST_DISCUSSION = 'co/discussion/REQUEST_DISCUSSION';\nexport const REQUEST_DISCUSSION_SUCCESS =\n  'co/discussion/REQUEST_DISCUSSION_SUCCESS';\nexport const REQUEST_DISCUSSION_FAIL = 'co/discussion/REQUEST_DISCUSSION_FAIL';\nexport const REQUEST_DISCUSSION_BY_PARTICIPANTS =\n  'co/discussion/REQUEST_DISCUSSION_BY_PARTICIPANTS';\nexport const REQUEST_DISCUSSION_BY_PARTICIPANTS_SUCCESS =\n  'co/discussion/REQUEST_DISCUSSION_BY_PARTICIPANTS_SUCCESS';\nexport const REQUEST_DISCUSSION_BY_PARTICIPANTS_FAIL =\n  'co/discussion/REQUEST_DISCUSSION_BY_PARTICIPANTS_FAIL';\nexport const UPDATE_DISCUSSION = 'co/discussion/UPDATE_DISCUSSION';\nexport const REMOVE_DISCUSSION = 'co/discussion/REMOVE_DISCUSSION';\n\nexport function requestDiscussions(\n  params: { offset?: number; limit?: number } = {}\n): Action {\n  const { offset = 0, limit = 20 } = params;\n\n  return {\n    type: REQUEST_DISCUSSIONS,\n    payload: {\n      request: {\n        url: '/api/v2/discussions',\n        params: { offset, limit },\n      },\n    },\n  };\n}\n\nexport function loadMoreDiscussions(): Action {\n  return {\n    type: LOAD_MORE_DISCUSSIONS,\n    payload: {},\n  };\n}\n\nexport function filterImportance({\n  min,\n  max,\n}: {\n  min: number;\n  max: number;\n}): Action {\n  return {\n    type: FILTER_IMPORTANCE,\n    payload: { min, max },\n  };\n}\n\nexport function requestDiscussion({\n  discussionId,\n}: {\n  discussionId: string;\n}): Action {\n  return {\n    type: REQUEST_DISCUSSION,\n    payload: {\n      request: {\n        url: `/api/v2/discussions/${discussionId}`,\n      },\n    },\n  };\n}\n\nexport function requestDiscussionByParticipants({\n  internalHash,\n  participants,\n}: {\n  internalHash: string;\n  participants: Participant[];\n}): Action {\n  return {\n    type: REQUEST_DISCUSSION_BY_PARTICIPANTS,\n    payload: {\n      internalHash,\n      request: {\n        method: 'post',\n        url: '/api/v2/participants/discussion',\n        data: participants,\n      },\n    },\n  };\n}\n\nexport function invalidate(): Action {\n  return {\n    type: INVALIDATE_DISCUSSIONS,\n    payload: {},\n  };\n}\nexport function removeDiscussionFromCollection({\n  discussionId,\n}: {\n  discussionId: string;\n}): Action {\n  return {\n    type: REMOVE_DISCUSSION_FROM_COLLECTION,\n    payload: {\n      discussionId,\n    },\n  };\n}\n\nexport function updateDiscussion(): void {\n  const deprecated = new Error('updateDiscussion is deprecated');\n  console.warn(deprecated);\n}\n\nfunction discussionsByIdReducer(state = {}, action: Action) {\n  switch (action.type) {\n    case REQUEST_DISCUSSIONS_SUCCESS:\n      return action.payload.data.discussions.reduce(\n        (previousState, discussion) => ({\n          ...previousState,\n          [discussion.discussion_id]: discussion,\n        }),\n        state\n      );\n    case REMOVE_DISCUSSION_FROM_COLLECTION:\n      return {\n        ...state,\n        [action.payload.discussionId]: undefined,\n      };\n    default:\n      return state;\n  }\n}\n\nfunction discussionIdsReducer(state: string[] = [], action: Action) {\n  switch (action.type) {\n    case REQUEST_DISCUSSIONS_SUCCESS:\n      return [...state]\n        .concat(\n          action.payload.data.discussions.map(\n            (discussion) => discussion.discussion_id\n          )\n        )\n        .reduce((prev: string[], curr) => {\n          if (prev.indexOf(curr) === -1) {\n            prev.push(curr);\n          }\n\n          return prev;\n        }, []);\n    case REMOVE_DISCUSSION_FROM_COLLECTION:\n      return [...state].filter(\n        (discussionId) => discussionId !== action.payload.discussionId\n      );\n    default:\n      return state;\n  }\n}\n\ninterface HashState {\n  discussionId?: string;\n}\n\nconst hashInitialState: HashState = {\n  discussionId: undefined,\n};\n\nfunction discussionByParticipantsHashReducer(state = hashInitialState, action) {\n  switch (action.type) {\n    case REQUEST_DISCUSSION_BY_PARTICIPANTS_SUCCESS:\n      return {\n        discussionId: action.payload.data.discussion_id,\n      };\n    case REQUEST_DISCUSSION_BY_PARTICIPANTS:\n    default:\n      return state;\n  }\n}\n\nexport interface Discussion {\n  id: string;\n  tags?: string[];\n}\n\nexport interface State {\n  isFetching: boolean;\n  didInvalidate: boolean;\n  discussionsById: {\n    [uuid: string]: Discussion;\n  };\n  discussions: Array<string>;\n  discussionByParticipantsHash: {\n    [hash: string]: HashState;\n  };\n  importanceRange: {\n    min: number;\n    max: number;\n  };\n  total: number;\n}\n\nexport function getNextOffset(state: State): number {\n  return state.discussions.length;\n}\n\nexport function hasMore(state: State): boolean {\n  return state.total > state.discussions.length;\n}\n\nconst initialState: State = {\n  isFetching: false,\n  didInvalidate: false,\n  discussionsById: {},\n  discussions: [],\n  discussionByParticipantsHash: {},\n  importanceRange: {\n    min: 0,\n    max: 10,\n  },\n  total: 0,\n};\n\nexport default function reducer(state = initialState, action: Action): State {\n  switch (action.type) {\n    case REQUEST_DISCUSSION:\n    case REQUEST_DISCUSSIONS:\n      return { ...state, isFetching: true };\n    case REQUEST_DISCUSSION_FAIL:\n    case REQUEST_DISCUSSIONS_FAIL:\n      return { ...state, isFetching: false };\n    case REQUEST_DISCUSSION_SUCCESS:\n      return {\n        ...state,\n        isFetching: false,\n        discussionsById: {\n          ...state.discussionsById,\n          [action.payload.data.discussion_id]: action.payload.data,\n        },\n      };\n    case REQUEST_DISCUSSIONS_SUCCESS:\n      return {\n        ...state,\n        isFetching: false,\n        didInvalidate: false,\n        discussions: discussionIdsReducer(\n          state.didInvalidate === true ? [] : state.discussions,\n          action\n        ),\n        discussionsById: discussionsByIdReducer(\n          state.didInvalidate === true ? {} : state.discussionsById,\n          action\n        ),\n        total: action.payload.data.total,\n      };\n    case INVALIDATE_DISCUSSIONS:\n      return { ...state, didInvalidate: true };\n    case REMOVE_DISCUSSION_FROM_COLLECTION:\n      return {\n        ...state,\n        discussions: discussionIdsReducer(state.discussions, action),\n        discussionsById: discussionsByIdReducer(state.discussionsById, action),\n        total: state.total - 1,\n      };\n    case FILTER_IMPORTANCE:\n      return {\n        ...state,\n        importanceRange: {\n          min: action.payload.min,\n          max: action.payload.max,\n        },\n      };\n    case REQUEST_DISCUSSION_BY_PARTICIPANTS:\n      return {\n        ...state,\n        discussionByParticipantsHash: {\n          ...state.discussionByParticipantsHash,\n          [action.payload.internalHash]: discussionByParticipantsHashReducer(\n            state.discussionByParticipantsHash[action.payload.internalHash],\n            action\n          ),\n        },\n      };\n    case REQUEST_DISCUSSION_BY_PARTICIPANTS_SUCCESS:\n    case REQUEST_DISCUSSION_BY_PARTICIPANTS_FAIL:\n      return {\n        ...state,\n        discussionByParticipantsHash: {\n          ...state.discussionByParticipantsHash,\n          [action.meta.previousAction.payload.internalHash]:\n            discussionByParticipantsHashReducer(\n              state.discussionByParticipantsHash[\n                action.meta.previousAction.payload.internalHash\n              ],\n              action\n            ),\n        },\n      };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/draft-message.spec.ts",
    "content": "import { IDraftMessageFormData } from 'src/modules/draftMessage/types';\nimport reducer, * as drafMessageModule from './draft-message';\n\ndescribe('ducks module draft-message', () => {\n  describe('reducer', () => {\n    it('reduces EDIT_DRAFT', () => {\n      const draft: IDraftMessageFormData = {\n        message_id: '111',\n        recipients: [],\n        participants: [],\n        body: 'bar',\n        identity_id: 'aaa',\n      };\n      const initialState = {\n        ...reducer(undefined, { type: '@@INIT' }),\n        draftsByMessageId: {\n          111: draft,\n        },\n      };\n      const body = 'foo';\n      expect(\n        reducer(undefined, drafMessageModule.editDraft({ ...draft, body }))\n      ).toEqual({\n        ...initialState,\n        draftsByMessageId: {\n          111: {\n            ...draft,\n            body,\n          },\n        },\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/draft-message.ts",
    "content": "import { DraftMessageFormData } from 'src/modules/draftMessage';\nimport { IDraftMessageFormData } from 'src/modules/draftMessage/types';\n\nexport const CREATE_DRAFT = 'co/draft-message/CREATE_DRAFT';\nexport const SYNC_DRAFT = 'co/draft-message/SYNC_DRAFT';\nexport const EDIT_DRAFT = 'co/draft-message/EDIT_DRAFT';\nexport const SAVE_DRAFT = 'co/draft-message/SAVE_DRAFT';\nexport const SEND_DRAFT = 'co/draft-message/SEND_DRAFT';\nexport const CLEAR_DRAFT = 'co/draft-message/CLEAR_DRAFT';\nexport const DELETE_DRAFT = 'co/draft-message/DELETE_DRAFT';\nexport const DELETE_DRAFT_SUCCESS = 'co/draft-message/DELETE_DRAFT_SUCCESS';\nexport const SET_RECIPIENT_SEARCH_TERMS =\n  'co/draft-message/SET_RECIPIENT_SEARCH_TERMS';\nexport const DECRYPT_DRAFT_SUCCESS = 'co/draft-message/DECRYPT_DRAFT_SUCCESS';\n\nexport function editDraft(draft: IDraftMessageFormData) {\n  return {\n    type: EDIT_DRAFT,\n    payload: { draft },\n  };\n}\n\n/**\n * @deprecated: not reduced nor axios action\n */\nexport function saveDraft({ draft }) {\n  return {\n    type: SAVE_DRAFT,\n    payload: { draft },\n  };\n}\n\nexport function createDraft({ draft }) {\n  return {\n    type: CREATE_DRAFT,\n    payload: { draft },\n  };\n}\n\nexport function syncDraft(draft: IDraftMessageFormData) {\n  return {\n    type: SYNC_DRAFT,\n    payload: { draft },\n  };\n}\n\n// useful?\nexport function sendDraft({ draft }) {\n  return {\n    type: SEND_DRAFT,\n    payload: { draft },\n  };\n}\n\nexport function clearDraft({ draft }) {\n  return {\n    type: CLEAR_DRAFT,\n    payload: { draft },\n  };\n}\n\nexport function deleteDraft({ draft }) {\n  return {\n    type: DELETE_DRAFT,\n    payload: { draft },\n  };\n}\n\nexport function deleteDraftSuccess({ draft }) {\n  return {\n    type: DELETE_DRAFT_SUCCESS,\n    payload: { draft },\n  };\n}\n\nexport function setRecipientSearchTerms({ messageId, searchTerms }) {\n  return {\n    type: SET_RECIPIENT_SEARCH_TERMS,\n    payload: { messageId, searchTerms },\n  };\n}\n\nexport function decryptDraftSuccess({ draft }) {\n  return {\n    type: DECRYPT_DRAFT_SUCCESS,\n    payload: { draft },\n  };\n}\n\nfunction deleteDraftFromState(state, id) {\n  const nextState = { ...state };\n  delete nextState[id];\n\n  return nextState;\n}\n\nfunction draftReducer(state = {}, action) {\n  switch (action.type) {\n    case CREATE_DRAFT:\n    case EDIT_DRAFT:\n    case DECRYPT_DRAFT_SUCCESS:\n      return {\n        ...state,\n        ...action.payload.draft,\n      };\n    case SYNC_DRAFT:\n      return { ...action.payload.draft };\n    default:\n      return state;\n  }\n}\n\nfunction dratfsByMessageIdReducer(state, action) {\n  switch (action.type) {\n    case CREATE_DRAFT:\n    case SYNC_DRAFT:\n    case EDIT_DRAFT:\n    case DECRYPT_DRAFT_SUCCESS:\n      return {\n        ...state,\n        [action.payload.draft.message_id]: draftReducer(\n          state[action.payload.draft.message_id],\n          action\n        ),\n      };\n    case CLEAR_DRAFT:\n      return deleteDraftFromState(state, action.payload.draft.message_id);\n    default:\n      return state;\n  }\n}\n\nfunction draftActivityByMessageIdReducer(state, action) {\n  switch (action.type) {\n    case DELETE_DRAFT:\n      return {\n        ...state,\n        [action.payload.draft.message_id]: {\n          ...state[action.payload.draft.message_id],\n          isDeletingDraft: true,\n        },\n      };\n    case DELETE_DRAFT_SUCCESS:\n      return {\n        ...state,\n        [action.payload.draft.message_id]: {\n          ...state[action.payload.draft.message_id],\n          isDeletingDraft: false,\n        },\n      };\n    default:\n      return state;\n  }\n}\n\nexport interface State {\n  didInvalidate: boolean;\n  draftsByMessageId: {\n    [messageId: string]: DraftMessageFormData;\n  };\n  recipientSearchTermsByMessageId: {\n    [messageId: string]: string;\n  };\n  draftActivityByMessageId: {\n    [messageId: string]: {\n      isRequestingDraft: boolean;\n      isDeletingDraft: boolean;\n    };\n  };\n}\n\nconst initialState: State = {\n  didInvalidate: false,\n  draftsByMessageId: {},\n  recipientSearchTermsByMessageId: {},\n  draftActivityByMessageId: {},\n};\n\nexport default function reducer(state = initialState, action): State {\n  switch (action.type) {\n    case CREATE_DRAFT:\n      return {\n        ...state,\n        draftsByMessageId: dratfsByMessageIdReducer(\n          state.draftsByMessageId,\n          action\n        ),\n      };\n    case EDIT_DRAFT:\n    case CLEAR_DRAFT:\n    case SYNC_DRAFT:\n      return {\n        ...state,\n        draftsByMessageId: dratfsByMessageIdReducer(\n          state.draftsByMessageId,\n          action\n        ),\n      };\n    case DELETE_DRAFT:\n    case DELETE_DRAFT_SUCCESS:\n      return {\n        ...state,\n        draftActivityByMessageId: draftActivityByMessageIdReducer(\n          state.draftActivityByMessageId,\n          action\n        ),\n      };\n    case DECRYPT_DRAFT_SUCCESS:\n      return {\n        ...state,\n        draftActivityByMessageId: draftActivityByMessageIdReducer(\n          state.draftActivityByMessageId,\n          action\n        ),\n        draftsByMessageId: dratfsByMessageIdReducer(\n          state.draftsByMessageId,\n          action\n        ),\n      };\n    case SET_RECIPIENT_SEARCH_TERMS: {\n      return {\n        ...state,\n        recipientSearchTermsByMessageId: {\n          ...state.recipientSearchTermsByMessageId,\n          [action.payload.messageId]: action.payload.searchTerms,\n        },\n      };\n    }\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/encryption.ts",
    "content": "export const ASK_PASSPHRASE = 'co/encryption/ASK_PASSPHRASE';\nexport const SET_PASSPHRASE = 'co/encryption/SET_PASSPHRASE';\nexport const SET_PASSPHRASE_SUCCESS = 'co/encryption/SET_PASSPHRASE_SUCCESS';\nexport const RESET_PASSPHRASE = 'co/encryption/RESET_PASSPHRASE';\nexport const NEED_PASSPHRASE = 'co/encryption/NEED_PASSPHRASE';\nexport const NEED_PRIVATEKEY = 'co/encryption/NEED_PRIVATEKEY';\nexport const ENCRYPT_MESSAGE = 'co/encryption/ENCRYPT_MESSAGE';\nexport const ENCRYPT_MESSAGE_SUCCESS = 'co/encryption/ENCRYPT_MESSAGE_SUCCESS';\nexport const ENCRYPT_MESSAGE_FAIL = 'co/encryption/ENCRYPT_MESSAGE_FAIL';\nexport const DECRYPT_MESSAGE = 'co/encryption/DECRYPT_MESSAGE';\nexport const DECRYPT_MESSAGE_SUCCESS = 'co/encryption/DECRYPT_MESSAGE_SUCCESS';\nexport const DECRYPT_MESSAGE_FAIL = 'co/encryption/DECRYPT_MESSAGE_FAIL';\nexport const RESET_ENCRYPTION = 'co/encryption/RESET_ENCRYPTION';\n\nexport const STATUS_NEED_PASSPHRASE = 'need_passphrase';\nexport const STATUS_NEED_PRIVATEKEY = 'need_privatekey';\nexport const STATUS_ENCRYPTING = 'encrypting';\nexport const STATUS_ENCRYPTED = 'encrypted';\nexport const STATUS_DECRYPTING = 'decrypting';\nexport const STATUS_DECRYPTED = 'decrypted';\nexport const STATUS_ERROR = 'error';\n\nexport function askPassphrase({\n  fingerprint,\n  error,\n}: {\n  fingerprint: string;\n  error: Error | undefined;\n}) {\n  return {\n    type: ASK_PASSPHRASE,\n    payload: {\n      fingerprint,\n      error,\n    },\n  };\n}\n\nexport function setPassphrase({ fingerprint, passphrase }) {\n  return {\n    type: SET_PASSPHRASE,\n    payload: {\n      fingerprint,\n      passphrase,\n    },\n  };\n}\n\nexport function setPassphraseSuccess({ fingerprint }) {\n  return {\n    type: SET_PASSPHRASE_SUCCESS,\n    payload: {\n      fingerprint,\n    },\n  };\n}\n\nexport function resetPassphrase({ fingerprint }) {\n  return {\n    type: RESET_PASSPHRASE,\n    payload: {\n      fingerprint,\n    },\n  };\n}\n\nexport function needPassphrase({ message, fingerprints }) {\n  return {\n    type: NEED_PASSPHRASE,\n    payload: {\n      message,\n      fingerprints,\n    },\n  };\n}\n\nexport function needPrivateKey({ message }) {\n  return {\n    type: NEED_PRIVATEKEY,\n    payload: {\n      message,\n    },\n  };\n}\n\nexport function encryptMessage({ message }) {\n  return {\n    type: ENCRYPT_MESSAGE,\n    payload: { message },\n  };\n}\n\nexport function encryptMessageSuccess({ message, encryptedMessage }) {\n  return {\n    type: ENCRYPT_MESSAGE_SUCCESS,\n    payload: { message, encryptedMessage },\n  };\n}\n\nexport function encryptMessageFail({ message, error }) {\n  return {\n    type: ENCRYPT_MESSAGE_FAIL,\n    payload: {\n      message,\n      error,\n    },\n  };\n}\n\nexport function decryptMessage({ message }) {\n  return {\n    type: DECRYPT_MESSAGE,\n    payload: { message },\n  };\n}\n\nexport function decryptMessageSuccess({ message, decryptedMessage }) {\n  return {\n    type: DECRYPT_MESSAGE_SUCCESS,\n    payload: { message, decryptedMessage },\n  };\n}\n\nexport function decryptMessageFail({ message, error }) {\n  return {\n    type: DECRYPT_MESSAGE_FAIL,\n    payload: {\n      message,\n      error,\n    },\n  };\n}\n\nexport function resetEncryption({ message }) {\n  return {\n    type: RESET_ENCRYPTION,\n    payload: {\n      message,\n    },\n  };\n}\n\nexport type PrivateKeysStatus = 'ask' | 'ok' | 'ko';\n\nexport type EncryptionStatus =\n  | typeof STATUS_NEED_PASSPHRASE\n  | typeof STATUS_NEED_PRIVATEKEY\n  | typeof STATUS_ENCRYPTING\n  | typeof STATUS_ENCRYPTED\n  | typeof STATUS_DECRYPTING\n  | typeof STATUS_DECRYPTED\n  | typeof STATUS_ERROR;\n\nexport type MessageEncryptionStatus = {\n  status: EncryptionStatus;\n  keyFingerprint: string; // ?\n  decryptedMessage: string | void;\n  encryptedMessage: string | void;\n  error?: Error;\n};\n\nexport interface State {\n  messageEncryptionStatusById: {\n    [id: string]: void | MessageEncryptionStatus;\n  };\n  privateKeysByFingerprint: {\n    [fingerprint: string]: void | {\n      status: PrivateKeysStatus;\n      passphrase: string | void;\n      error: Error | void;\n    };\n  };\n}\n\nconst initialState: State = {\n  messageEncryptionStatusById: {},\n  privateKeysByFingerprint: {},\n};\n\nexport default function reducer(state = initialState, action): State {\n  switch (action.type) {\n    case ASK_PASSPHRASE:\n      return {\n        ...state,\n        privateKeysByFingerprint: {\n          ...state.privateKeysByFingerprint,\n          [action.payload.fingerprint]: {\n            status: 'ask',\n            passphrase: undefined,\n            error: action.payload.error,\n          },\n        },\n      };\n    case SET_PASSPHRASE:\n      return {\n        ...state,\n        privateKeysByFingerprint: {\n          ...state.privateKeysByFingerprint,\n          [action.payload.fingerprint]: {\n            status: 'ok',\n            passphrase: action.payload.passphrase,\n            error: undefined,\n          },\n        },\n      };\n    case RESET_PASSPHRASE:\n      return {\n        ...state,\n        privateKeysByFingerprint: {\n          ...state.privateKeysByFingerprint,\n          [action.payload.fingerprint]: {\n            status: 'ko',\n            passphrase: undefined,\n            error: undefined,\n          },\n        },\n      };\n    case NEED_PASSPHRASE:\n      return {\n        ...state,\n        messageEncryptionStatusById: {\n          ...state.messageEncryptionStatusById,\n          [action.payload.message.message_id]: {\n            status: STATUS_NEED_PASSPHRASE,\n            keyFingerprint: action.payload.fingerprints,\n            decryptedMessage: null,\n            encryptedMessage: action.payload.message,\n          },\n        },\n      };\n    case NEED_PRIVATEKEY:\n      return {\n        ...state,\n        messageEncryptionStatusById: {\n          ...state.messageEncryptionStatusById,\n          [action.payload.message.message_id]: {\n            status: STATUS_NEED_PRIVATEKEY,\n            decryptedMessage: null,\n            encryptedMessage: action.payload.message,\n          },\n        },\n      };\n    case ENCRYPT_MESSAGE:\n      return {\n        ...state,\n        messageEncryptionStatusById: {\n          ...state.messageEncryptionStatusById,\n          [action.payload.message.message_id]: {\n            status: STATUS_ENCRYPTING,\n            encryptedMessage: null,\n            decryptedMessage: action.payload.message,\n          },\n        },\n      };\n    case ENCRYPT_MESSAGE_SUCCESS:\n      return {\n        ...state,\n        messageEncryptionStatusById: {\n          ...state.messageEncryptionStatusById,\n          [action.payload.message.message_id]: {\n            status: STATUS_ENCRYPTED,\n            encryptedMessage: action.payload.encryptedMessage,\n            decryptedMessage: action.payload.message,\n          },\n        },\n      };\n    case ENCRYPT_MESSAGE_FAIL:\n      return {\n        ...state,\n        messageEncryptionStatusById: {\n          ...state.messageEncryptionStatusById,\n          [action.payload.message.message_id]: {\n            status: STATUS_ERROR,\n            error: action.payload.error,\n            encryptedMessage: null,\n            decryptedMessage: action.payload.message,\n          },\n        },\n      };\n    case DECRYPT_MESSAGE:\n      return {\n        ...state,\n        messageEncryptionStatusById: {\n          ...state.messageEncryptionStatusById,\n          [action.payload.message.message_id]: {\n            error: undefined,\n            status: STATUS_DECRYPTING,\n            encryptedMessage: action.payload.message,\n            decryptedMessage: null,\n          },\n        },\n      };\n    case DECRYPT_MESSAGE_SUCCESS:\n      return {\n        ...state,\n        messageEncryptionStatusById: {\n          ...state.messageEncryptionStatusById,\n          [action.payload.message.message_id]: {\n            error: undefined,\n            status: STATUS_DECRYPTED,\n            encryptedMessage: action.payload.message,\n            decryptedMessage: action.payload.decryptedMessage,\n          },\n        },\n      };\n    case DECRYPT_MESSAGE_FAIL:\n      return {\n        ...state,\n        messageEncryptionStatusById: {\n          ...state.messageEncryptionStatusById,\n          [action.payload.message.message_id]: {\n            error: action.payload.error,\n            status: STATUS_ERROR,\n            encryptMessage: action.payload.message,\n            decryptedMessage: null,\n          },\n        },\n      };\n    case RESET_ENCRYPTION:\n      return {\n        ...state,\n        messageEncryptionStatusById: {\n          ...state.messageEncryptionStatusById,\n          [action.payload.message.message_id]: undefined,\n        },\n      };\n\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/i18n.js",
    "content": "export const REQUEST_LOCALE = 'co/i18n/REQUEST_LOCALE';\nexport const SET_LOCALE = 'co/i18n/SET_LOCALE';\n\nexport function requestLocale() {\n  return {\n    type: REQUEST_LOCALE,\n    payload: {},\n  };\n}\n\nexport function setLocale({ locale }) {\n  return {\n    type: SET_LOCALE,\n    payload: { locale },\n  };\n}\n\nconst initialState = {\n  locale: 'en',\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case SET_LOCALE:\n      return { ...state, locale: action.payload.locale };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/importance-level.js",
    "content": "import { IL_MIN, IL_MAX } from '../../services/importance-level';\n\nexport const SET_IMPORTANCE_LEVEL = 'co/importance-level/SET_IMPORTANCE_LEVEL';\n\nconst initialState = { range: [IL_MIN, IL_MAX] };\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case SET_IMPORTANCE_LEVEL:\n      return {\n        ...state,\n        range: action.payload.range,\n      };\n    default:\n      return state;\n  }\n}\n\nexport function setImportanceLevel(range) {\n  return {\n    type: SET_IMPORTANCE_LEVEL,\n    payload: { range },\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/local-identity.js",
    "content": "export const REQUEST_LOCAL_IDENTITIES =\n  'co/local-identity/REQUEST_LOCAL_IDENTITIES';\nexport const REQUEST_LOCAL_IDENTITIES_SUCCESS =\n  'co/local-identity/REQUEST_LOCAL_IDENTITIES_SUCCESS';\nexport const INVALIDATE = 'co/local-identity/INVALIDATE';\n\nexport function requestLocalIdentities() {\n  return {\n    type: REQUEST_LOCAL_IDENTITIES,\n    payload: {\n      request: {\n        url: '/api/v2/identities/locals',\n      },\n    },\n  };\n}\n\n// XXX: no id in api response\n// function localIdentitiesByIdReducer(state, action) {\n//   switch (action.type) {\n//     case REQUEST_LOCAL_IDENTITIES_SUCCESS:\n//       return action.payload.data.local_identities.reduce((previousState, localIdentity) => ({\n//         ...previousState,\n//         [localIdentity.id]: localIdentity,\n//       }), state);\n//     default:\n//       return state;\n//   }\n// }\n\nconst initialState = {\n  isFetching: false,\n  didInvalidate: false,\n  localIdentities: [],\n  total: 0,\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case REQUEST_LOCAL_IDENTITIES:\n      return { ...state, isFetching: true };\n    case REQUEST_LOCAL_IDENTITIES_SUCCESS:\n      return {\n        ...state,\n        isFetching: false,\n        didInvalidate: false,\n        localIdentities:\n          state.didInvalidate === true\n            ? []\n            : action.payload.data.local_identities,\n        total: action.payload.data.total,\n      };\n    case INVALIDATE:\n      return { ...state, didInvalidate: true };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/message.js",
    "content": "import calcObjectForPatch from '../../services/api-patch';\n\nexport const SET_TIMELINE_FILTER = 'co/message/SET_TIMELINE_FILTER';\nexport const REQUEST_MESSAGES = 'co/message/REQUEST_MESSAGES';\nexport const REQUEST_MESSAGES_SUCCESS = 'co/message/REQUEST_MESSAGES_SUCCESS';\nexport const REQUEST_MESSAGES_FAIL = 'co/message/REQUEST_MESSAGES_FAIL';\nexport const INVALIDATE_MESSAGES = 'co/message/INVALIDATE_MESSAGES';\nexport const INVALIDATE_ALL_MESSAGES = 'co/message/INVALIDATE_ALL_MESSAGES';\nexport const LOAD_MORE_MESSAGES = 'co/message/LOAD_MORE_MESSAGES';\nexport const REQUEST_MESSAGE = 'co/message/REQUEST_MESSAGE';\nexport const REQUEST_MESSAGE_SUCCESS = 'co/message/REQUEST_MESSAGE_SUCCESS';\nexport const UPDATE_MESSAGE = 'co/message/UPDATE_MESSAGE';\nexport const UPDATE_MESSAGE_SUCCESS = 'co/message/UPDATE_MESSAGE_SUCCESS';\nexport const UPDATE_MESSAGE_FAIL = 'co/message/UPDATE_MESSAGE_FAIL';\nexport const DELETE_MESSAGE = 'co/message/DELETE_MESSAGE';\nexport const DELETE_MESSAGE_SUCCESS = 'co/message/DELETE_MESSAGE_SUCCESS';\nexport const CREATE_MESSAGE = 'co/message/CREATE_MESSAGE';\nexport const CREATE_MESSAGE_SUCCESS = 'co/message/CREATE_MESSAGE_SUCCESS';\nexport const SYNC_MESSAGE = 'co/message/SYNC_MESSAGE';\nexport const POST_ACTIONS = 'co/message/POST_ACTIONS';\nexport const POST_ACTIONS_SUCCESS = 'co/message/POST_ACTIONS_SUCCESS';\nexport const REPLY_TO_MESSAGE = 'co/message/REPLY_TO_MESSAGE';\nexport const UPDATE_TAGS = 'co/message/UPDATE_TAGS';\nexport const UPDATE_TAGS_SUCCESS = 'co/message/UPDATE_TAGS_SUCCESS';\nexport const UPDATE_TAGS_FAIL = 'co/message/UPDATE_TAGS_FAIL';\nexport const UPLOAD_ATTACHMENT = 'co/message/UPLOAD_ATTACHMENT';\nexport const DELETE_ATTACHMENT = 'co/message/DELETE_ATTACHMENT';\nexport const REMOVE_FROM_COLLECTION = 'co/message/REMOVE_FROM_COLLECTION';\nexport const ADD_TO_COLLECTION = 'co/message/ADD_TO_COLLECTION';\nexport const REQUEST_DRAFT = 'co/message/REQUEST_DRAFT';\nexport const REQUEST_DRAFT_SUCCESS = 'co/message/REQUEST_DRAFT_SUCCESS';\nexport const FETCH_MESSAGES = 'co/message/FETCH_MESSAGES';\nexport const FETCH_MESSAGES_SUCCESS = 'co/message/FETCH_MESSAGES_SUCCESS';\n\nexport const TIMELINE_FILTER_ALL = 'all';\nexport const TIMELINE_FILTER_RECEIVED = 'received';\nexport const TIMELINE_FILTER_SENT = 'sent';\nexport const TIMELINE_FILTER_DRAFT = 'draft';\n\nexport function requestMessages(\n  type = 'timeline',\n  key = '0',\n  { offset = 0, limit = 20, ...opts } = {}\n) {\n  const params = { offset, limit, ...opts };\n\n  return {\n    type: REQUEST_MESSAGES,\n    payload: {\n      type,\n      key,\n      request: {\n        url: '/api/v2/messages',\n        params,\n      },\n    },\n  };\n}\n\nexport function loadMore(type, key) {\n  return {\n    type: LOAD_MORE_MESSAGES,\n    payload: { type, key },\n  };\n}\n\nexport function setTimelineFilter(type = TIMELINE_FILTER_RECEIVED) {\n  return {\n    type: SET_TIMELINE_FILTER,\n    payload: { type },\n  };\n}\n\nexport function invalidate(type, key) {\n  return {\n    type: INVALIDATE_MESSAGES,\n    payload: { type, key },\n  };\n}\n\nexport function invalidateAll() {\n  return {\n    type: INVALIDATE_ALL_MESSAGES,\n    payload: {},\n  };\n}\n\nexport function createMessage({ message }) {\n  return {\n    type: CREATE_MESSAGE,\n    payload: {\n      request: {\n        url: '/api/v1/messages',\n        method: 'post',\n        data: { ...message },\n      },\n      message,\n    },\n  };\n}\n\nexport function requestMessage(messageId) {\n  return {\n    type: REQUEST_MESSAGE,\n    payload: {\n      request: {\n        url: `/api/v2/messages/${messageId}`,\n      },\n    },\n  };\n}\n\nexport function updateMessage({ message, original }) {\n  const data = calcObjectForPatch(message, original);\n\n  return {\n    type: UPDATE_MESSAGE,\n    payload: {\n      request: {\n        method: 'patch',\n        url: `/api/v1/messages/${message.message_id}`,\n        data,\n      },\n      message,\n      original,\n    },\n  };\n}\n\nexport function deleteMessage({ message }) {\n  return {\n    type: DELETE_MESSAGE,\n    payload: {\n      message,\n      discussionId: message.discussion_id,\n      request: {\n        method: 'delete',\n        url: `/api/v1/messages/${message.message_id}`,\n      },\n    },\n  };\n}\n\nexport function syncMessage({ message }) {\n  return {\n    type: SYNC_MESSAGE,\n    payload: { message },\n  };\n}\n\nexport function postActions({ message, actions }) {\n  return {\n    type: POST_ACTIONS,\n    payload: {\n      message,\n      request: {\n        method: 'post',\n        url: `/api/v2/messages/${message.message_id}/actions`,\n        data: {\n          actions,\n        },\n      },\n    },\n  };\n}\n\nexport function replyToMessage({ internalId, message }) {\n  return {\n    type: REPLY_TO_MESSAGE,\n    payload: { internalId, message },\n  };\n}\n\nexport function uploadAttachment({ message, attachment }) {\n  return {\n    type: UPLOAD_ATTACHMENT,\n    payload: {\n      request: {\n        method: 'post',\n        url: `/api/v2/messages/${message.message_id}/attachments`,\n        data: attachment,\n      },\n    },\n  };\n}\n\nexport function deleteAttachment({ message, attachment }) {\n  return {\n    type: DELETE_ATTACHMENT,\n    payload: {\n      request: {\n        method: 'delete',\n        url: `/api/v2/messages/${message.message_id}/attachments/${attachment.temp_id}`,\n      },\n    },\n  };\n}\n\nexport function addToCollection({ message }) {\n  return {\n    type: ADD_TO_COLLECTION,\n    payload: {\n      message,\n    },\n  };\n}\n\nexport function removeFromCollection({ message }) {\n  return {\n    type: REMOVE_FROM_COLLECTION,\n    payload: {\n      message,\n    },\n  };\n}\n\n// TODO: refactor me should be named requestMessages\n// and requestMessages -> requestCollection\nexport function fetchMessages(params) {\n  return {\n    type: FETCH_MESSAGES,\n    payload: {\n      request: {\n        method: 'get',\n        url: '/api/v2/messages',\n        params,\n      },\n    },\n  };\n}\n\nexport function requestDraft({ discussionId }) {\n  return {\n    type: REQUEST_DRAFT,\n    payload: {\n      request: {\n        method: 'get',\n        url: '/api/v2/messages',\n        params: {\n          discussion_id: discussionId,\n          is_draft: true,\n          limit: 1,\n        },\n      },\n    },\n  };\n}\nexport function getNextOffset(state) {\n  return state.messages.length;\n}\n\nexport function hasMore(state) {\n  return state.total > state.messages.length;\n}\n\n/**\n * @deprecated use createMessageCollectionStateSelector() instead (cf. selectors)\n */\nexport function getMessagesFromCollection(type, key, { state }) {\n  if (\n    !state.messagesCollections[type] ||\n    !state.messagesCollections[type][key]\n  ) {\n    return [];\n  }\n\n  return state.messagesCollections[type][key].messages.map(\n    (id) => state.messagesById[id]\n  );\n}\n\nfunction draftMessageReducer(state = {}, action) {\n  if (action.type !== REQUEST_DRAFT_SUCCESS) {\n    return state;\n  }\n\n  const [draft] = action.payload.data.messages;\n\n  if (!draft) {\n    return state;\n  }\n\n  return {\n    ...state,\n    [draft.message_id]: draft,\n  };\n}\n\nfunction messagesByIdReducer(state = {}, action = {}) {\n  switch (action.type) {\n    case REQUEST_DRAFT_SUCCESS:\n      return draftMessageReducer(state, action);\n    case REQUEST_MESSAGE_SUCCESS:\n      return {\n        ...state,\n        [action.payload.data.message_id]: action.payload.data,\n      };\n    case FETCH_MESSAGES_SUCCESS:\n    case REQUEST_MESSAGES_SUCCESS:\n      return action.payload.data.messages.reduce(\n        (previousState, message) => ({\n          ...previousState,\n          [message.message_id]: message,\n        }),\n        state\n      );\n    case SYNC_MESSAGE:\n      return {\n        ...state,\n        [action.payload.message.message_id]: action.payload.message,\n      };\n    case REMOVE_FROM_COLLECTION:\n      return {\n        ...state,\n        [action.payload.message.message_id]: undefined,\n      };\n    default:\n      return state;\n  }\n}\n\nconst addMessageToCollection = (messageId, collection) => {\n  if (!messageId) {\n    return collection;\n  }\n\n  return [messageId, ...collection];\n};\n\nconst messagesCollectionReducer = (\n  state = {\n    isFetching: false,\n    didInvalidate: false,\n    messages: [],\n    total: 0,\n    request: {},\n  },\n  action\n) => {\n  const { type, key } = action.internal;\n\n  switch (action.type) {\n    case REQUEST_MESSAGES:\n      return {\n        ...state,\n        isFetching: true,\n      };\n    case REQUEST_MESSAGES_SUCCESS:\n      // FIXME: uniqueness messageIds\n      return {\n        ...state,\n        isFetching: false,\n        didInvalidate: false,\n        messages: Array.from(\n          new Set([\n            ...((state.didInvalidate && []) || state.messages),\n            ...action.payload.data.messages.map(\n              (message) => message.message_id\n            ),\n          ])\n        ),\n        total: action.payload.data.total,\n        request: action.meta.previousAction.payload.request,\n      };\n    case ADD_TO_COLLECTION:\n      return {\n        ...state,\n        messages: Array.from(\n          new Set(\n            addMessageToCollection(\n              (type === 'timeline' &&\n                [TIMELINE_FILTER_ALL, TIMELINE_FILTER_DRAFT].some(\n                  (k) => k === key\n                )) ||\n                key === action.payload.message.discussion_id\n                ? action.payload.message.message_id\n                : undefined,\n              state.messages\n            )\n          )\n        ),\n      };\n    case REMOVE_FROM_COLLECTION:\n      return {\n        ...state,\n        messages: state.messages.filter(\n          (id) => id !== action.payload.message.message_id\n        ),\n      };\n    case INVALIDATE_ALL_MESSAGES:\n    case INVALIDATE_MESSAGES:\n      return {\n        ...state,\n        didInvalidate: true,\n      };\n    default:\n      return state;\n  }\n};\n\nconst getTypeAndKeyFromAction = (action) => {\n  switch (action.type) {\n    case REQUEST_MESSAGES:\n    case INVALIDATE_MESSAGES:\n      return { type: action.payload.type, key: action.payload.key };\n    case REQUEST_MESSAGES_SUCCESS:\n      return {\n        type: action.meta.previousAction.payload.type,\n        key: action.meta.previousAction.payload.key,\n      };\n    default:\n      throw new Error('invalid action', action);\n  }\n};\n\nconst makeMessagesCollectionTypeReducer = (action) => {\n  const { type, key } = getTypeAndKeyFromAction(action);\n\n  return (state = {}, act) => ({\n    ...state,\n    [type]: {\n      ...(state[type] || {}),\n      [key]: messagesCollectionReducer(state[type] && state[type][key], {\n        ...act,\n        internal: { type, key },\n      }),\n    },\n  });\n};\n\nconst allMessagesCollectionsReducer = (state, action) =>\n  Object.keys(state).reduce(\n    (accType, type) => ({\n      ...accType,\n      [type]: Object.keys(state[type]).reduce(\n        (accKey, key) => ({\n          ...accKey,\n          [key]: messagesCollectionReducer(state[type][key], {\n            ...action,\n            internal: { type, key },\n          }),\n        }),\n        {}\n      ),\n    }),\n    {}\n  );\n\nconst initialState = {\n  timelineFilter: TIMELINE_FILTER_RECEIVED,\n  messagesById: {},\n  messagesCollections: {},\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case REQUEST_MESSAGE_SUCCESS:\n    case REQUEST_DRAFT_SUCCESS:\n    case SYNC_MESSAGE:\n      return {\n        ...state,\n        messagesById: messagesByIdReducer(state.messagesById, action),\n      };\n    case REQUEST_MESSAGES:\n      return {\n        ...state,\n        messagesCollections: makeMessagesCollectionTypeReducer(action)(\n          state.messagesCollections,\n          action\n        ),\n      };\n    case REQUEST_MESSAGES_SUCCESS:\n      return {\n        ...state,\n        messagesById: messagesByIdReducer(state.messagesById, action),\n        messagesCollections: makeMessagesCollectionTypeReducer(action)(\n          state.messagesCollections,\n          action\n        ),\n      };\n    case FETCH_MESSAGES_SUCCESS:\n      return {\n        ...state,\n        messagesById: messagesByIdReducer(state.messagesById, action),\n      };\n    case SET_TIMELINE_FILTER:\n      return {\n        ...state,\n        timelineFilter: action.payload.type,\n      };\n    case INVALIDATE_MESSAGES:\n      return {\n        ...state,\n        messagesCollections: makeMessagesCollectionTypeReducer(action)(\n          state.messagesCollections,\n          action\n        ),\n      };\n    case ADD_TO_COLLECTION:\n    case INVALIDATE_ALL_MESSAGES:\n      return {\n        ...state,\n        messagesCollections: allMessagesCollectionsReducer(\n          state.messagesCollections,\n          action\n        ),\n      };\n    case REMOVE_FROM_COLLECTION:\n      return {\n        ...state,\n        messagesById: messagesByIdReducer(state.messagesById, action),\n        messagesCollections: allMessagesCollectionsReducer(\n          state.messagesCollections,\n          action\n        ),\n      };\n\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/message.spec.js",
    "content": "import reducer, * as messageModule from './message';\n\ndescribe('ducks module message', () => {\n  describe('reducer', () => {\n    it('reduces SYNC_MESSAGE', () => {\n      const initialState = reducer(undefined, { type: '@@INIT' });\n      const message = { body: 'foo', message_id: '111', discussion_id: 'a111' };\n      expect(\n        reducer(undefined, messageModule.syncMessage({ message }))\n      ).toEqual({\n        ...initialState,\n        messagesById: {\n          111: message,\n        },\n      });\n    });\n\n    it('reduces REQUEST_MESSAGES', () => {\n      const initialState = {\n        ...reducer(undefined, { type: '@@INIT' }),\n      };\n      const action = messageModule.requestMessages('timeline', '0');\n      expect(reducer(initialState, action)).toEqual({\n        ...initialState,\n        messagesCollections: {\n          timeline: {\n            0: {\n              isFetching: true,\n              didInvalidate: false,\n              messages: [],\n              total: 0,\n              request: {},\n            },\n          },\n        },\n      });\n    });\n\n    it('reduces INVALIDATE_MESSAGES', () => {\n      const initialState = {\n        ...reducer(undefined, { type: '@@INIT' }),\n        messagesById: {\n          a0: { body: 'already here', discussion_id: 'a', message_id: 'a0' },\n        },\n        messagesCollections: {\n          timeline: {\n            0: {\n              isFetching: false,\n              didInvalidate: false,\n              messages: ['a0'],\n              total: 1,\n              request: {\n                foo: 'bar',\n              },\n            },\n          },\n        },\n      };\n      const action = messageModule.invalidate('timeline', '0');\n      expect(reducer(initialState, action).messagesCollections).toEqual({\n        ...initialState.messagesCollections,\n        timeline: {\n          0: {\n            ...initialState.messagesCollections.timeline[0],\n            didInvalidate: true,\n          },\n        },\n      });\n    });\n\n    it('reduces INVALIDATE_ALL_MESSAGES', () => {\n      const initialState = {\n        ...reducer(undefined, { type: '@@INIT' }),\n        messagesById: {\n          a0: { body: 'already here', discussion_id: 'a001', message_id: 'a0' },\n          a1: {\n            body: 'already here 2',\n            discussion_id: 'a002',\n            message_id: 'a1',\n          },\n        },\n        messagesCollections: {\n          timeline: {\n            0: {\n              isFetching: false,\n              didInvalidate: false,\n              messages: ['a0', 'a1'],\n              total: 2,\n              request: {\n                foo: 'bar',\n              },\n            },\n          },\n          discussion: {\n            a001: {\n              isFetching: false,\n              didInvalidate: false,\n              messages: ['a0'],\n              total: 1,\n              request: {\n                foo: 'bar',\n              },\n            },\n            a002: {\n              isFetching: false,\n              didInvalidate: false,\n              messages: ['a1'],\n              total: 1,\n              request: {\n                foo: 'bar',\n              },\n            },\n          },\n        },\n      };\n      const action = messageModule.invalidateAll();\n      expect(reducer(initialState, action).messagesCollections).toEqual({\n        ...initialState.messagesCollections,\n        timeline: {\n          0: {\n            ...initialState.messagesCollections.timeline[0],\n            didInvalidate: true,\n          },\n        },\n        discussion: {\n          a001: {\n            ...initialState.messagesCollections.discussion.a001,\n            didInvalidate: true,\n          },\n          a002: {\n            ...initialState.messagesCollections.discussion.a002,\n            didInvalidate: true,\n          },\n        },\n      });\n    });\n\n    describe('reduces REQUEST_MESSAGES_SUCCESS', () => {\n      it('reduces without invalidateDiscussion', () => {\n        const initialState = {\n          ...reducer(undefined, { type: '@@INIT' }),\n          messagesById: {\n            a0: { body: 'already here', discussion_id: 'a', message_id: 'a0' },\n          },\n          messagesCollections: {\n            timeline: {\n              0: {\n                isFetching: false,\n                didInvalidate: false,\n                messages: ['a0'],\n                total: 1,\n                request: {\n                  foo: 'bar',\n                },\n              },\n            },\n          },\n        };\n        const message = { body: 'foo', message_id: 'a1', discussion_id: 'a' };\n        const action = {\n          type: messageModule.REQUEST_MESSAGES_SUCCESS,\n          payload: {\n            data: {\n              total: 2,\n              messages: [message],\n            },\n          },\n          meta: {\n            previousAction: {\n              type: messageModule.REQUEST_MESSAGES,\n              payload: {\n                type: 'timeline',\n                key: '0',\n                discussionId: 'a',\n                request: { foo: 'bar' },\n              },\n            },\n          },\n        };\n        expect(reducer(initialState, action)).toEqual({\n          ...initialState,\n          messagesById: {\n            ...initialState.messagesById,\n            a1: message,\n          },\n          messagesCollections: {\n            timeline: {\n              0: {\n                isFetching: false,\n                didInvalidate: false,\n                messages: ['a0', 'a1'],\n                total: 2,\n                request: {\n                  foo: 'bar',\n                },\n              },\n            },\n          },\n        });\n      });\n\n      it('reduces with invalidateDiscussion', () => {\n        const initialState = {\n          ...reducer(undefined, { type: '@@INIT' }),\n          messagesById: {\n            a0: { body: 'I am invalid', discussion_id: 'a', message_id: 'a0' },\n          },\n          messagesCollections: {\n            timeline: {\n              0: {\n                isFetching: false,\n                didInvalidate: true,\n                messages: ['a0'],\n                total: 1,\n                request: {\n                  foo: 'bar',\n                },\n              },\n            },\n          },\n        };\n        const message = { body: 'foo', message_id: 'a1', discussion_id: 'a' };\n        const action = {\n          type: messageModule.REQUEST_MESSAGES_SUCCESS,\n          payload: {\n            data: {\n              total: 1,\n              messages: [message],\n            },\n          },\n          meta: {\n            previousAction: {\n              type: messageModule.REQUEST_MESSAGES,\n              payload: {\n                type: 'timeline',\n                key: '0',\n                discussionId: 'a',\n                request: { foo: 'bar' },\n              },\n            },\n          },\n        };\n        expect(reducer(initialState, action)).toEqual({\n          ...initialState,\n          messagesById: {\n            ...initialState.messagesById,\n            a1: message,\n          },\n          messagesCollections: {\n            timeline: {\n              0: {\n                isFetching: false,\n                didInvalidate: false,\n                messages: ['a1'],\n                total: 1,\n                request: {\n                  foo: 'bar',\n                },\n              },\n            },\n          },\n        });\n      });\n    });\n\n    describe('REQUEST_MESSAGE_SUCCESS', () => {\n      it('reduces', () => {\n        const initialState = {\n          ...reducer(undefined, { type: '@@INIT' }),\n          messagesById: {\n            a0: { body: 'I am invalid', discussion_id: 'a', message_id: 'a0' },\n          },\n        };\n        const message = { body: 'foo', message_id: 'a1', discussion_id: 'a' };\n        const action = {\n          type: messageModule.REQUEST_MESSAGE_SUCCESS,\n          payload: {\n            data: message,\n          },\n        };\n        expect(reducer(initialState, action)).toEqual({\n          ...initialState,\n          messagesById: {\n            ...initialState.messagesById,\n            a1: message,\n          },\n        });\n      });\n    });\n  });\n\n  describe('hasMore', () => {\n    it('return true', () => {\n      expect(messageModule.hasMore({ messages: ['a1'], total: 2 })).toEqual(\n        true\n      );\n    });\n    it('return false', () => {\n      expect(messageModule.hasMore({ messages: ['a1'], total: 1 })).toEqual(\n        false\n      );\n    });\n  });\n\n  describe('getNextOffset', () => {\n    it('return 0', () => {\n      expect(messageModule.getNextOffset({ messages: [] })).toEqual(0);\n    });\n    it('return n', () => {\n      expect(messageModule.getNextOffset({ messages: ['a1', 'a3'] })).toEqual(\n        2\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/notification.js",
    "content": "export const SET_INITIALIZED = 'co/notification/SET_INITIALIZED';\nexport const UPDATE_ALL = 'co/notification/UPDATE_ALL';\nexport const REMOVE = 'co/notification/REMOVE';\n\nexport function setInitialized() {\n  return {\n    type: SET_INITIALIZED,\n    payload: {},\n  };\n}\n\nexport function updateAll(notifications) {\n  return {\n    type: UPDATE_ALL,\n    payload: {\n      notifications,\n    },\n  };\n}\n\nexport function remove(notifications) {\n  return {\n    type: REMOVE,\n    payload: {\n      notifications,\n    },\n  };\n}\n\nconst removeNotificationsReducer = (state, action) => {\n  if (action.type !== REMOVE) {\n    return state;\n  }\n\n  const notifs = new Set(action.payload.notifications);\n\n  return [...state.filter((notification) => !notifs.has(notification))];\n};\n\nconst initialState = {\n  // true if first notifications request has been done\n  initialized: false,\n  notifications: [],\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case SET_INITIALIZED:\n      return state.initialized ? state : { ...state, initialized: true };\n    case UPDATE_ALL:\n      return {\n        ...state,\n        notifications: [...action.payload.notifications],\n      };\n    case REMOVE:\n      return {\n        ...state,\n        notifications: removeNotificationsReducer(state.notifications, action),\n      };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/participant-suggestions.ts",
    "content": "export const SEARCH = 'co/participant-suggestions/SEARCH';\nexport const SEARCH_SUCCESS = 'co/participant-suggestions/SEARCH_SUCCESS';\nexport const SUGGEST = 'co/participant-suggestions/SUGGEST';\nexport const SUGGEST_SUCCESS = 'co/participant-suggestions/SUGGEST_SUCCESS';\n\nexport interface Action {\n  type:\n    | typeof SEARCH\n    | typeof SEARCH_SUCCESS\n    | typeof SUGGEST\n    | typeof SUGGEST_SUCCESS;\n  payload: any;\n}\n// XXX: declared by usage\nexport type Context = 'msg_compose';\n// XXX: declared by usage\nexport type Protocol = 'email' | 'twitter' | 'mastodon';\n\nexport interface Suggestion {\n  label: string;\n  address: string;\n  protocol: Protocol;\n  contact_id?: string;\n  source: 'contact' | 'participant';\n}\n\nexport interface ContactSuggestionPayload {\n  source: 'contact';\n  contact_id: string;\n  label: string;\n}\nexport interface ParticipantSuggestionPayload {\n  source: 'participant';\n  address: string;\n  label: string;\n  protocol: Protocol;\n}\nexport type SuggestionPayload =\n  | ContactSuggestionPayload\n  | ParticipantSuggestionPayload;\n\nexport function search(terms: string, context: Context = 'msg_compose') {\n  return {\n    type: SEARCH,\n    payload: {\n      terms,\n      context,\n    },\n  };\n}\n\nexport function searchSuccess(\n  terms: string,\n  context: Context,\n  results: Suggestion[]\n) {\n  return {\n    type: SEARCH_SUCCESS,\n    payload: {\n      terms,\n      context,\n      results,\n    },\n  };\n}\n\nexport function suggest(terms: string, context: Context = 'msg_compose') {\n  return {\n    type: SUGGEST,\n    payload: {\n      request: {\n        url: '/api/v2/participants/suggest',\n        params: { q: terms, context },\n      },\n    },\n  };\n}\n\nexport const getKey = (terms: string, context: Context = 'msg_compose') =>\n  `${context}_${terms}`;\n\nexport interface State {\n  isFetching: boolean;\n  resultsByKey: {\n    [key: string]: Suggestion[];\n  };\n}\nconst initialState: State = {\n  isFetching: false,\n  resultsByKey: {},\n};\n\nexport default function reducer(state = initialState, action: Action) {\n  switch (action.type) {\n    case SEARCH:\n      return { ...state, isFetching: true };\n    case SEARCH_SUCCESS:\n      return {\n        ...state,\n        resultsByKey: {\n          [getKey(action.payload.terms, action.payload.context)]:\n            action.payload.results,\n        },\n      };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/provider.js",
    "content": "export const REQUEST_PROVIDERS = 'co/provider/REQUEST_PROVIDERS';\nexport const REQUEST_PROVIDERS_SUCCESS =\n  'co/provider/REQUEST_PROVIDERS_SUCCESS';\n\nexport const providerStateSelector = (state) => state.provider;\n\nexport function requestProviders() {\n  return {\n    type: REQUEST_PROVIDERS,\n    payload: {\n      request: {\n        url: '/api/v2/providers',\n      },\n    },\n  };\n}\n\nconst initialState = {\n  isFetching: false,\n  didInvalidate: false,\n  providers: undefined,\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case REQUEST_PROVIDERS:\n      return { ...state, isFetching: true };\n    case REQUEST_PROVIDERS_SUCCESS:\n      return {\n        ...state,\n        isFetching: false,\n        didInvalidate: false,\n        providers: action.payload.data.providers,\n      };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/public-key.js",
    "content": "import { strToBase64 } from '../../services/encode-utils';\n\nexport const REQUEST_PUBLIC_KEYS = 'co/public-key/REQUEST_PUBLIC_KEYS';\nexport const REQUEST_PUBLIC_KEYS_SUCCESS =\n  'co/public-key/REQUEST_PUBLIC_KEYS_SUCCESS';\nexport const REQUEST_PUBLIC_KEYS_FAIL =\n  'co/public-key/REQUEST_PUBLIC_KEYS_FAIL';\nexport const CREATE_PUBLIC_KEY = 'co/public-key/CREATE_PUBLIC_KEYS';\nexport const CREATE_PUBLIC_KEY_SUCCESS =\n  'co/public-key/CREATE_PUBLIC_KEYS_SUCCESS';\nexport const CREATE_PUBLIC_KEY_FAIL = 'co/public-key/CREATE_PUBLIC_KEYS_FAIL';\nexport const UPDATE_PUBLIC_KEY = 'co/public-key/UPDATE_PUBLIC_KEYS';\nexport const UPDATE_PUBLIC_KEY_SUCCESS =\n  'co/public-key/UPDATE_PUBLIC_KEYS_SUCCESS';\nexport const UPDATE_PUBLIC_KEY_FAIL = 'co/public-key/UPDATE_PUBLIC_KEYS_FAIL';\nexport const DELETE_PUBLIC_KEY = 'co/public-key/DELETE_PUBLIC_KEYS';\nexport const DELETE_PUBLIC_KEY_SUCCESS =\n  'co/public-key/DELETE_PUBLIC_KEYS_SUCCESS';\nexport const DELETE_PUBLIC_KEY_FAIL = 'co/public-key/DELETE_PUBLIC_KEYS_FAIL';\n\nexport function requestPublicKeys({ contactId }) {\n  return {\n    type: REQUEST_PUBLIC_KEYS,\n    payload: {\n      contactId,\n      request: {\n        url: `/api/v2/contacts/${contactId}/publickeys`,\n      },\n    },\n  };\n}\n\nexport function createPublicKey({ contactId, publicKey }) {\n  const encodedKey = {\n    ...publicKey,\n    key: strToBase64(publicKey.key),\n  };\n\n  return {\n    type: CREATE_PUBLIC_KEY,\n    payload: {\n      contactId,\n      request: {\n        method: 'post',\n        url: `/api/v2/contacts/${contactId}/publickeys`,\n        data: encodedKey,\n      },\n    },\n  };\n}\n\nexport function updatePublicKey({ contactId, publicKey, original }) {\n  const { key_id, label } = publicKey;\n\n  if (label === original.label) {\n    // Nothing to update\n    return Promise.resolve();\n  }\n\n  // XXX: anything else except label to update?\n  const data = {\n    label,\n    current_state: {\n      label: original.label,\n    },\n  };\n\n  return {\n    type: UPDATE_PUBLIC_KEY,\n    payload: {\n      contactId,\n      request: {\n        method: 'patch',\n        url: `/api/v2/contacts/${contactId}/publickeys/${key_id}`,\n        data,\n      },\n    },\n  };\n}\n\nexport function deletePublicKey({ contactId, publicKeyId }) {\n  return {\n    type: DELETE_PUBLIC_KEY,\n    payload: {\n      contactId,\n      request: {\n        method: 'delete',\n        url: `/api/v2/contacts/${contactId}/publickeys/${publicKeyId}`,\n      },\n    },\n  };\n}\n\nconst initialState = {};\n\nconst extractContactIdFromAction = (action) =>\n  action.meta.previousAction.payload.contactId;\n\nconst keyListReducer = (state = [], action = {}) => {\n  const contactId = extractContactIdFromAction(action);\n\n  return {\n    ...state,\n    [contactId]: {\n      keys: action.payload.data.pubkeys,\n      isFetching: false,\n      total: action.payload.data.total,\n      didInvalidate: false,\n    },\n  };\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case CREATE_PUBLIC_KEY_SUCCESS:\n    case UPDATE_PUBLIC_KEY_SUCCESS:\n    case DELETE_PUBLIC_KEY_SUCCESS:\n      return {\n        ...state,\n        [extractContactIdFromAction(action)]: {\n          ...state[extractContactIdFromAction(action)],\n          didInvalidate: true,\n        },\n      };\n    case REQUEST_PUBLIC_KEYS:\n      return {\n        ...state,\n        [action.payload.contactId]: {\n          ...state[action.payload.contactId],\n          isFetching: true,\n        },\n      };\n    case REQUEST_PUBLIC_KEYS_SUCCESS:\n      return keyListReducer(state, action);\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/remote-identity.js",
    "content": "import calcObjectForPatch from '../../services/api-patch';\n\nexport const REQUEST_REMOTE_IDENTITIES =\n  'co/remote-identity/REQUEST_REMOTE_IDENTITIES';\nexport const REQUEST_REMOTE_IDENTITIES_SUCCESS =\n  'co/remote-identity/REQUEST_REMOTE_IDENTITIES_SUCCESS';\nexport const REQUEST_REMOTE_IDENTITY =\n  'co/remote-identity/REQUEST_REMOTE_IDENTITY';\nexport const REQUEST_REMOTE_IDENTITY_SUCCESS =\n  'co/remote-identity/REQUEST_REMOTE_IDENTITY_SUCCESS';\nexport const CREATE_REMOTE_IDENTITY =\n  'co/remote-identity/CREATE_REMOTE_IDENTITY';\nexport const UPDATE_REMOTE_IDENTITY =\n  'co/remote-identity/UPDATE_REMOTE_IDENTITY';\nexport const DELETE_REMOTE_IDENTITY =\n  'co/remote-identity/DELETE_REMOTE_IDENTITY';\nexport const DELETE_REMOTE_IDENTITY_SUCCESS =\n  'co/remote-identity/DELETE_REMOTE_IDENTITY_SUCCESS';\nexport const ADD_TO_COLLECTION = 'co/remote-identity/ADD_TO_COLLECTION';\nexport const REMOVE_FROM_COLLECTION =\n  'co/remote-identity/REMOVE_FROM_COLLECTION';\nexport const INVALIDATE = 'co/remote-identity/INVALIDATE';\n\nexport function requestRemoteIdentities() {\n  return {\n    type: REQUEST_REMOTE_IDENTITIES,\n    payload: {\n      request: {\n        url: '/api/v2/identities/remotes',\n      },\n    },\n  };\n}\n\nexport function requestRemoteIdentity({ identityId }) {\n  return {\n    type: REQUEST_REMOTE_IDENTITY,\n    payload: {\n      request: {\n        url: `/api/v2/identities/remotes/${identityId}`,\n      },\n    },\n  };\n}\n\nexport function createRemoteIdentity({ remoteIdentity }) {\n  return {\n    type: CREATE_REMOTE_IDENTITY,\n    payload: {\n      request: {\n        url: '/api/v2/identities/remotes',\n        method: 'post',\n        data: {\n          ...remoteIdentity,\n        },\n      },\n    },\n  };\n}\n\nexport function updateRemoteIdentity({ remoteIdentity, original }) {\n  const data = calcObjectForPatch(remoteIdentity, original);\n\n  return {\n    type: UPDATE_REMOTE_IDENTITY,\n    payload: {\n      request: {\n        url: `/api/v2/identities/remotes/${remoteIdentity.identity_id}`,\n        method: 'patch',\n        data,\n      },\n    },\n  };\n}\n\nexport function deleteRemoteIdentity({ remoteIdentity }) {\n  return {\n    type: DELETE_REMOTE_IDENTITY,\n    payload: {\n      request: {\n        url: `/api/v2/identities/remotes/${remoteIdentity.identity_id}`,\n        method: 'delete',\n      },\n    },\n  };\n}\n\nexport function addToCollection({ remoteIdentity }) {\n  return {\n    type: ADD_TO_COLLECTION,\n    payload: { remoteIdentity },\n  };\n}\n\nexport function removeFromCollection({ remoteIdentity }) {\n  return {\n    type: REMOVE_FROM_COLLECTION,\n    payload: { remoteIdentity },\n  };\n}\n\nconst initialState = {\n  isFetching: false,\n  didInvalidate: false,\n  remoteIdentitiesById: {},\n  remoteIdentities: [],\n  total: 0,\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case REQUEST_REMOTE_IDENTITIES:\n      return { ...state, isFetching: true };\n    case REQUEST_REMOTE_IDENTITIES_SUCCESS:\n      return {\n        ...state,\n        isFetching: false,\n        didInvalidate: false,\n        remoteIdentitiesById: action.payload.data.remote_identities.reduce(\n          (acc, remoteIdentity) => ({\n            ...acc,\n            [remoteIdentity.identity_id]: remoteIdentity,\n          }),\n          state.remoteIdentitiesById\n        ),\n        remoteIdentities:\n          state.didInvalidate === true\n            ? []\n            : action.payload.data.remote_identities.map(\n                (remoteIdentity) => remoteIdentity.identity_id\n              ),\n        total: action.payload.data.total,\n      };\n    case REQUEST_REMOTE_IDENTITY_SUCCESS:\n      return {\n        ...state,\n        remoteIdentitiesById: {\n          ...state.remoteIdentitiesById,\n          [action.payload.data.identity_id]: action.payload.data,\n        },\n      };\n    case ADD_TO_COLLECTION:\n      return {\n        ...state,\n        remoteIdentities: [\n          ...state.remoteIdentities,\n          action.payload.remoteIdentity.identity_id,\n        ],\n        total: state.total + 1,\n      };\n    case REMOVE_FROM_COLLECTION:\n      return {\n        ...state,\n        remoteIdentities: state.remoteIdentities.filter(\n          (identityId) =>\n            identityId !== action.payload.remoteIdentity.identity_id\n        ),\n        total: state.total - 1,\n      };\n    case INVALIDATE:\n      return { ...state, didInvalidate: true };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/search.js",
    "content": "export const SEARCH = 'co/search/SEARCH';\nexport const SEARCH_SUCCESS = 'co/search/SEARCH_SUCCESS';\nexport const LOAD_MORE = 'co/search/LOAD_MORE';\n\nconst RESULT_LIMIT = 20;\n\nexport function search({ term, doctype }, options = {}) {\n  const limit = doctype ? RESULT_LIMIT : undefined;\n\n  return {\n    type: SEARCH,\n    payload: {\n      term,\n      doctype,\n      request: {\n        url: '/api/v2/search',\n        params: {\n          term,\n          doctype,\n          limit,\n          ...options,\n        },\n      },\n    },\n  };\n}\n\nexport function loadMore({ term, doctype }) {\n  return {\n    type: LOAD_MORE,\n    payload: {\n      term,\n      doctype,\n    },\n  };\n}\n\nconst getHitKey = (doctype) => {\n  switch (doctype) {\n    case 'message':\n      return 'messages_hits';\n    case 'contact':\n      return 'contact_hits';\n    default:\n      throw new Error(\n        'doctype is unknown and must be one of \"message\" or \"contact\"'\n      );\n  }\n};\n\nexport const getKey = (term, doctype = '') => `${doctype}_${term}`;\nconst getDoctypeResultHits = (term, doctype, state) => {\n  const results = state.resultsByKey[getKey(term, doctype)];\n\n  if (!results) {\n    return false;\n  }\n\n  return results[getHitKey(doctype)];\n};\n\nexport const hasMore = (term, doctype, state) => {\n  const results = getDoctypeResultHits(term, doctype, state);\n\n  if (!results) {\n    return false;\n  }\n\n  const { total, [`${doctype}s`]: entities } = results;\n\n  return total > entities.length;\n};\n\nexport const getNextOffset = (term, doctype, state) => {\n  const { [`${doctype}s`]: entities } = getDoctypeResultHits(\n    term,\n    doctype,\n    state\n  );\n\n  return entities.length;\n};\n\nconst initialState = {\n  resultsByKey: {},\n};\n\nfunction hitUpdater(hit = {}, payload) {\n  const { total, ...doctypes } = payload;\n\n  return {\n    ...hit,\n    total,\n    ...Object.keys(doctypes).reduce(\n      (acc, doctype) => ({\n        ...acc,\n        [doctype]: [...(hit[doctype] ? hit[doctype] : []), ...payload[doctype]],\n      }),\n      {}\n    ),\n  };\n}\n\nfunction hitsUpdater(results = {}, payload) {\n  const { total, ...hits } = payload;\n\n  return {\n    ...results,\n    total,\n    ...Object.keys(hits).reduce(\n      (acc, hitName) => ({\n        ...acc,\n        [hitName]: hitUpdater(results[hitName], hits[hitName]),\n      }),\n      {}\n    ),\n  };\n}\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case SEARCH:\n      return { ...state, isFetching: true };\n    case SEARCH_SUCCESS:\n      return {\n        ...state,\n        resultsByKey: {\n          ...state.resultsByKey,\n          [getKey(\n            action.meta.previousAction.payload.term,\n            action.meta.previousAction.payload.doctype\n          )]: action.meta.previousAction.payload.request.params.offset\n            ? hitsUpdater(\n                state.resultsByKey[\n                  getKey(\n                    action.meta.previousAction.payload.term,\n                    action.meta.previousAction.payload.doctype\n                  )\n                ],\n                action.payload.data\n              )\n            : action.payload.data,\n        },\n      };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/settings.js",
    "content": "import calcObjectForPatch from '../../services/api-patch';\n\nexport const REQUEST_SETTINGS = 'co/settings/REQUEST_SETTINGS';\nexport const REQUEST_SETTINGS_SUCCESS = 'co/settings/REQUEST_SETTINGS_SUCCESS';\nexport const REQUEST_SETTINGS_FAIL = 'co/settings/REQUEST_SETTINGS_FAIL';\nexport const UPDATE_SETTINGS = 'co/settings/UPDATE_SETTINGS';\n\nexport function requestSettings() {\n  return {\n    type: REQUEST_SETTINGS,\n    payload: {\n      request: {\n        url: '/api/v1/settings',\n      },\n    },\n  };\n}\n\nexport function updateSettings({ settings, original }) {\n  const data = calcObjectForPatch(settings, original);\n\n  return {\n    type: UPDATE_SETTINGS,\n    payload: {\n      request: {\n        method: 'patch',\n        url: '/api/v1/settings',\n        data,\n      },\n    },\n  };\n}\n\nexport const initialState = {\n  isFetching: false,\n  isInvalidated: true,\n  settings: {},\n  didLostAuth: false,\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case REQUEST_SETTINGS:\n      return { ...state, isFetching: true };\n    case REQUEST_SETTINGS_FAIL:\n      return {\n        ...state,\n        isFetching: false,\n        didLostAuth: action.error.response.status === 401,\n      };\n    case REQUEST_SETTINGS_SUCCESS:\n      return {\n        ...state,\n        isFetching: false,\n        isInvalidated: false,\n        settings: action.payload.data,\n        didLostAuth: false,\n      };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/modules/view.js",
    "content": "export const ADD_VIEW = 'co/view/ADD_VIEW';\n\nexport function addView({ viewConfig }) {\n  return {\n    type: ADD_VIEW,\n    payload: {\n      viewConfig,\n    },\n  };\n}\n\nconst initialViewState = {\n  viewConfig: {},\n  isFetching: false,\n};\n\nfunction viewReducer(state = initialViewState, action) {\n  switch (action.type) {\n    case ADD_VIEW:\n      return {\n        ...state,\n        viewConfig: action.payload.viewConfig,\n      };\n    default:\n      return state;\n  }\n}\n\nfunction viewByIdReducer(state, action) {\n  switch (action.type) {\n    case ADD_VIEW:\n      return {\n        ...state,\n        [action.payload.viewConfig.id]: viewReducer(\n          state[action.payload.viewConfig.id],\n          action\n        ),\n      };\n    default:\n      return state;\n  }\n}\n\nconst initialState = {\n  viewById: {},\n};\n\nexport default function reducer(state = initialState, action) {\n  switch (action.type) {\n    case ADD_VIEW:\n      return {\n        ...state,\n        viewById: viewByIdReducer(state.viewById, action),\n      };\n    // FIXME: case FETCH\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/store/reducer.ts",
    "content": "import { combineReducers } from 'redux';\nimport notifyReducer from 'react-redux-notify';\nimport {\n  TypedUseSelectorHook,\n  useSelector as useSelectorBase,\n} from 'react-redux';\nimport { reducer as formReducer } from 'redux-form';\nimport { store as contactStore } from 'src/modules/contact';\nimport { store as tagStore } from 'src/modules/tags';\nimport { store as userStore } from 'src/modules/user';\nimport deviceReducer from './modules/device';\nimport discussionReducer from './modules/discussion';\nimport draftMessageReducer from './modules/draft-message';\nimport i18nReducer from './modules/i18n';\nimport importanceLevelReducer from './modules/importance-level';\nimport localIdentityReducer from './modules/local-identity';\nimport messageReducer from './modules/message';\nimport notificationReducer from './modules/notification';\nimport participantSuggestionsReducer from './modules/participant-suggestions';\nimport providerReducer from './modules/provider';\nimport remoteIdentityReducer from './modules/remote-identity';\nimport searchReducer from './modules/search';\nimport settingsReducer from './modules/settings';\nimport publicKeyReducer from './modules/public-key';\nimport viewReducer from './modules/view';\nimport encryptionReducer from './modules/encryption';\n\nconst reducer = combineReducers({\n  notifications: notifyReducer,\n  contact: contactStore.reducer,\n  device: deviceReducer,\n  discussion: discussionReducer,\n  draftMessage: draftMessageReducer,\n  i18n: i18nReducer,\n  importanceLevel: importanceLevelReducer,\n  localIdentity: localIdentityReducer,\n  message: messageReducer,\n  notification: notificationReducer,\n  participantSuggestions: participantSuggestionsReducer,\n  provider: providerReducer,\n  remoteIdentity: remoteIdentityReducer,\n  search: searchReducer,\n  settings: settingsReducer,\n  tag: tagStore.reducer,\n  user: userStore.reducer,\n  form: formReducer,\n  publicKey: publicKeyReducer,\n  view: viewReducer,\n  encryption: encryptionReducer,\n});\n\nexport type RootState = ReturnType<typeof reducer>;\n\nexport const useSelector: TypedUseSelectorHook<RootState> = useSelectorBase;\n\nexport default reducer;\n"
  },
  {
    "path": "src/frontend/web_application/src/store/selectors/device.js",
    "content": "export const deviceStateSelector = (state) => state.device;\n"
  },
  {
    "path": "src/frontend/web_application/src/store/selectors/getModuleStateSelector.js",
    "content": "export const getModuleStateSelector = (moduleName) => (state) => {\n  if (!state[moduleName]) {\n    throw new Error(`\"${moduleName}\" does not exist in redux store`);\n  }\n\n  return state[moduleName];\n};\n"
  },
  {
    "path": "src/frontend/web_application/src/store/selectors/message.js",
    "content": "import { createSelector } from 'reselect';\nimport { hasMore } from '../modules/message';\n\nexport const messagesByIdSelector = (state) => state.message.messagesById;\nexport const collectionsSelector = (state) => state.message.messagesCollections;\n\nexport const createMessageCollectionStateSelector = (\n  typeSelector,\n  keySelector\n) =>\n  createSelector(\n    [messagesByIdSelector, collectionsSelector, typeSelector, keySelector],\n    (messagesById, collections, type, key) => {\n      const collection =\n        (collections[type] && collections[type][key]) || undefined;\n      const messageIds = (collection && collection.messages) || [];\n      const messages = messageIds.map((messageId) => messagesById[messageId]);\n\n      return {\n        messages,\n        messageIds,\n        didInvalidate: (collection && collection.didInvalidate) || false,\n        hasMore: (collection !== undefined && hasMore(collection)) || false,\n        isFetching: (collection && collection.isFetching) || false,\n      };\n    }\n  );\n"
  },
  {
    "path": "src/frontend/web_application/src/store/selectors/provider.js",
    "content": "export const providerStateSelector = (state) => state.provider;\n"
  },
  {
    "path": "src/frontend/web_application/src/store/selectors/settings.js",
    "content": "// @deprecated cf. settings module\nexport const settingsStateSelector = (state) => state.settings;\nexport const settingsSelector = (state) => state.settings.settings;\n"
  },
  {
    "path": "src/frontend/web_application/src/store/selectors/tab.js",
    "content": "// @deprecated, routes & tabs are not managed by redux anymore\nimport { createSelector } from 'reselect';\nimport { matchPath } from 'react-router-dom';\n\nexport const currentTabSelector = createSelector(\n  [\n    (state) => state.tab.tabs,\n    (state) => state.router.location && state.router.location.pathname,\n  ],\n  (tabs, pathname) =>\n    tabs.find((tab) => matchPath(pathname, { path: tab.pathname, exact: true }))\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/store/selectors/timeline.js",
    "content": "export const timelineFilterSelector = (state) => state.message.timelineFilter;\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/base/fonts.scss",
    "content": "$co-font-path: '../../fonts' !default;\n\n@mixin fontdef($FontPath, $FontPrefix, $FontVersion, $FontType) {\n  src: url('#{$FontPath}/#{$FontType}/#{$FontPrefix}-#{$FontType}.eot?v=#{$FontVersion}');\n  src: url('#{$FontPath}/#{$FontType}/#{$FontPrefix}-#{$FontType}.eot?#iefix&v=#{$FontVersion}')\n      format('embedded-opentype'),\n    url('#{$FontPath}/#{$FontType}/#{$FontPrefix}-#{$FontType}.woff2?v=#{$FontVersion}')\n      format('woff2'),\n    url('#{$FontPath}/#{$FontType}/#{$FontPrefix}-#{$FontType}.woff?v=#{$FontVersion}')\n      format('woff'),\n    url('#{$FontPath}/#{$FontType}/#{$FontPrefix}-#{$FontType}.ttf?v=#{$FontVersion}')\n      format('truetype'),\n    url('#{$FontPath}/#{$FontType}/#{$FontPrefix}-#{$FontType}.svg?v=#{$FontVersion}##{$FontType}')\n      format('svg');\n}\n\n// Open Sans\n$OpenSansTitle: 'Open Sans';\n$OpenSansPrefix: 'OpenSans';\n$OpenSansPath: '#{$co-font-path}/#{$OpenSansPrefix}';\n$OpenSansVersion: '1.1.0' !default;\n\n// Open Sans Regular\n@font-face {\n  @include fontdef($OpenSansPath, $OpenSansPrefix, $OpenSansVersion, 'Regular');\n\n  font-family: $OpenSansTitle;\n  font-style: normal;\n  font-weight: normal;\n}\n\n// Open Sans Semibold\n@font-face {\n  @include fontdef(\n    $OpenSansPath,\n    $OpenSansPrefix,\n    $OpenSansVersion,\n    'Semibold'\n  );\n\n  font-family: $OpenSansTitle;\n  font-style: normal;\n  font-weight: 600;\n}\n\n// Open Sans Bold\n@font-face {\n  @include fontdef($OpenSansPath, $OpenSansPrefix, $OpenSansVersion, 'Bold');\n\n  font-family: $OpenSansTitle;\n  font-style: normal;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/base/reset.scss",
    "content": "* {\n  // Remove Chrome focus blue shadow\n  &:focus {\n    outline: none;\n  }\n}\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  // force custom font-family on form elements\n  font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Roboto, Arial,\n    sans-serif;\n}\n\ninput {\n  &:invalid,\n  &:required,\n  &:-moz-submit-invalid,\n  &:-moz-ui-invalid {\n    // disable pseudo-classes automatically applied to `required` or `invalid` inputs\n    // https://developer.mozilla.org/fr/docs/Web/CSS/:invalid\n    box-shadow: none;\n  }\n}\n\nfieldset {\n  margin: 0;\n  padding: 0;\n  border: 0;\n}\n\nlegend {\n  max-width: 100%;\n  margin: 0;\n}\n\ndd,\ndl,\nul,\nli {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n\npre {\n  margin: 0;\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n  white-space: pre-wrap;\n  word-wrap: break-word;\n}\n\nblockquote {\n  margin: 0;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/common.scss",
    "content": "// --- Config ---\n@import 'config/config';\n\n// @import 'config/theme/dark';\n@import 'config/theme/light';\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/config/config.scss",
    "content": "// do not use rem, this allow more content for the same width by\n// changing font-size for breakpoints in the root element (e.g 88% for large screen)\n$co-site-width: 1200px;\n$co-page-container-paddings: (\n  medium: 4px,\n  xlarge: 40px,\n);\n$co-header__height: 3rem !default; // 48px\n$co-brand-width: 11rem;\n$co-pi-width: (\n  graph: 15rem,\n  ratings: 17rem,\n  mini-ratings: 1rem,\n);\n$co-pi-height: (\n  progressbar: 0.25rem,\n);\n$co-radius: 0.2rem;\n\n// TODO refactor\n// 24px\n$co-margin: 1.5rem;\n$co-margin_: (\n  // 10px\n  xsmall: 0.625rem,\n  // 16px\n  small: 1rem,\n  // 24px\n  medium: 1.5rem,\n  // 42px\n  large: 2.625rem,\n  // 80px\n  xlarge: 5rem\n);\n$co-font__size: 1rem;\n$co-font__size--xsmall: 0.75rem;\n$co-font__size--small: 0.875rem;\n$co-font__size--large: 1.5rem;\n$co-font__line-height: 1.4rem;\n$co-component__height: 2.625rem;\n$co-component__height--large: 3.5rem;\n$co-component__spacing: 0.5rem;\n$co-form__width: (\n  // 160 px\n  small: 10rem,\n  // 208 px\n  medium: 13rem,\n  // 256 px\n  large: 16rem,\n  // 380 px\n  xlarge: 23.75rem\n);\n$co-form__height: (\n  // 24 px\n  small: 1.5rem,\n  // 36 px\n  medium: 2.25rem,\n  // 48 px\n  large: 3rem\n);\n$co-form__spacing: (\n  // 4 px\n  xsmall: 0.25rem,\n  // 10 px\n  small: 0.625rem,\n  // 15 px\n  medium: 0.9375rem,\n  // 25 px\n  large: 1.5625rem,\n  // 42px\n  xlarge: 2.625rem\n);\n$co-grid__gutter: $co-margin;\n$co-grid__gutter--small: 20px;\n$co-animation__clickable-transition: background-color 0.1s ease-out,\n  color 0.1s ease-out !default;\n$co-animation__box-transition: box-shadow 0.15s, border-color 0.15s ease-in-out !default;\n$co-animation__margin-transition: margin 0.15s, padding 0.15s ease !default;\n$co-animation__width-transition: width 0.15s ease-in-out !default;\n$co-animation__height-transition: height, max-height 0.15s ease-in-out !default;\n$l-z-index__navbar-shadow: -1;\n$l-z-index__dropdown: 1;\n$l-z-index__action-bar: 1;\n$l-z-index__active-tab: 2;\n$l-z-index__flash-message: 2;\n$l-z-index__navbar: 10;\n$l-z-index__modal-overlay: 20;\n$l-z-index__dropdown--first-layer: 30;\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/config/theme/dark.scss",
    "content": "// --- Colors ---\n// bright colors\n$co-color__primary: #1395ba;\n$co-color__primary--lower: darken($co-color__primary, 15%);\n$co-color__primary--low: darken($co-color__primary, 10%);\n$co-color__primary--high: lighten($co-color__primary, 10%);\n$co-color__primary--higher: lighten($co-color__primary, 15%);\n$co-color__primary__text: #8dceec;\n$co-color__secondary: #13ba95;\n$co-color__secondary--lower: darken($co-color__secondary, 15%);\n$co-color__secondary--low: darken($co-color__secondary, 10%);\n$co-color__secondary--high: lighten($co-color__secondary, 10%);\n$co-color__secondary--higher: lighten($co-color__secondary, 15%);\n\n// semantic\n$co-color__success: #4bbc07;\n$co-color__success--low: darken($co-color__success, 10%);\n$co-color__success--high: lighten($co-color__success, 10%);\n$co-color__info: #39badb;\n$co-color__info--low: darken($co-color__info, 10%);\n$co-color__info--high: lighten($co-color__info, 10%);\n$co-color__warning: #eba000;\n$co-color__warning--low: darken($co-color__warning, 10%);\n$co-color__warning--high: lighten($co-color__warning, 10%);\n$co-color__alert: #9d3535;\n$co-color__alert--low: darken($co-color__alert, 10%);\n$co-color__alert--high: lighten($co-color__alert, 10%);\n$co-color__importance: #d2bc00;\n\n// Privacy\n$co-color__privacy: #97b707;\n$co-color__privacy--alt: #04d02f;\n$privacy-palette: (\n  disabled: #c2c2c2,\n  super: $co-color__privacy,\n  good: $co-color__privacy,\n  bad: $co-color__privacy,\n  ugly: $co-color__privacy,\n);\n\n// contrasted foreground\n$co-color__contrast__back: #fff;\n$co-color__contrast__back--lower: darken($co-color__contrast__back, 5%);\n$co-color__contrast__back--low: darken($co-color__contrast__back, 2.5%);\n$co-color__contrast__back--high: lighten($co-color__contrast__back, 5%);\n$co-color__contrast__back--higher: lighten($co-color__contrast__back, 10%);\n$co-color__contrast__border: #b3b3b3;\n$co-color__contrast__border--lower: darken($co-color__contrast__border, 20%);\n$co-color__contrast__border--low: darken($co-color__contrast__border, 10%);\n$co-color__contrast__border--high: lighten($co-color__contrast__border, 10%);\n$co-color__contrast__border--higher: lighten($co-color__contrast__border, 20%);\n$co-color__contrast__text: #1a1a1a;\n$co-color__contrast__text--lower: darken($co-color__contrast__text, 30%);\n$co-color__contrast__text--low: darken($co-color__contrast__text, 15%);\n$co-color__contrast__text--high: lighten($co-color__contrast__text, 10%);\n$co-color__contrast__text--higher: lighten($co-color__contrast__text, 20%);\n\n// foreground\n$co-color__fg__back: #333;\n$co-color__fg__back--lower: darken($co-color__fg__back, 5%);\n$co-color__fg__back--low: darken($co-color__fg__back, 2.5%);\n$co-color__fg__back--high: lighten($co-color__fg__back, 5%);\n$co-color__fg__back--higher: lighten($co-color__fg__back, 10%);\n$co-color__fg__border: #424242;\n$co-color__fg__border--lower: darken($co-color__fg__border, 20%);\n$co-color__fg__border--low: darken($co-color__fg__border, 10%);\n$co-color__fg__border--high: lighten($co-color__fg__border, 10%);\n$co-color__fg__border--higher: lighten($co-color__fg__border, 20%);\n$co-color__fg__text: #e8e8e8;\n$co-color__fg__text--lower: darken($co-color__fg__text, 35%);\n$co-color__fg__text--low: darken($co-color__fg__text, 15%);\n$co-color__fg__text--high: lighten($co-color__fg__text, 10%);\n$co-color__fg__text--higher: lighten($co-color__fg__text, 20%);\n\n// background\n$co-color__bg__back: #1d1d1d;\n$co-color__bg__back--lower: darken($co-color__bg__back, 5%);\n$co-color__bg__back--low: darken($co-color__bg__back, 2.5%);\n$co-color__bg__back--high: lighten($co-color__bg__back, 5%);\n$co-color__bg__back--higher: lighten($co-color__bg__back, 10%);\n$co-color__bg__border: #424242;\n$co-color__bg__border--lower: darken($co-color__bg__border, 20%);\n$co-color__bg__border--low: darken($co-color__bg__border, 10%);\n$co-color__bg__border--high: lighten($co-color__bg__border, 10%);\n$co-color__bg__border--higher: lighten($co-color__bg__border, 20%);\n$co-color__bg__text: #888;\n$co-color__bg__text--lower: darken($co-color__bg__text, 30%);\n$co-color__bg__text--low: darken($co-color__bg__text, 15%);\n$co-color__bg__text--high: lighten($co-color__bg__text, 10%);\n$co-color__bg__text--higher: lighten($co-color__bg__text, 20%);\n\n// --- Shadows ---\n$co-color__shadow: rgb(0 0 0 / 30%);\n$co-shadow--low: 0 1px 0.25rem $co-color__shadow;\n$co-shadow: 0 1px 0.5rem $co-color__shadow;\n$co-shadow--high: 0 1px 0.75rem $co-color__shadow;\n$co-shadow--higher: 0 -1px 0.8rem $co-color__shadow;\n\n// --- Palette ---\n$caliopen-palette: (\n  primary: $co-color__primary,\n  secondary: $co-color__secondary,\n  success: #3adb76,\n  warning: #ffae00,\n  alert: #e43737,\n);\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/config/theme/light.scss",
    "content": "// --- Colors ---\n// bright colors\n$co-color__primary: #0f98c6;\n$co-color__primary--lower: darken($co-color__primary, 15%);\n$co-color__primary--low: darken($co-color__primary, 10%);\n$co-color__primary--high: #8dceec;\n$co-color__primary--higher: lighten($co-color__primary--high, 15%);\n$co-color__primary__text: #8dceec;\n$co-color__primary__text--alt: black;\n$co-color__secondary: #13ba95;\n$co-color__secondary--lower: darken($co-color__secondary, 15%);\n$co-color__secondary--low: darken($co-color__secondary, 10%);\n$co-color__secondary--high: lighten($co-color__secondary, 10%);\n$co-color__secondary--higher: lighten($co-color__secondary, 15%);\n\n// semantic\n$co-color__success: #4bbc07;\n$co-color__success--low: darken($co-color__success, 10%);\n$co-color__success--high: lighten($co-color__success, 10%);\n$co-color__info: #39badb;\n$co-color__info--low: darken($co-color__info, 10%);\n$co-color__info--high: lighten($co-color__info, 10%);\n$co-color__warning: #eba000;\n$co-color__warning--low: darken($co-color__warning, 10%);\n$co-color__warning--high: lighten($co-color__warning, 10%);\n$co-color__alert: #9d3535;\n$co-color__alert--low: darken($co-color__alert, 10%);\n$co-color__alert--high: lighten($co-color__alert, 10%);\n$co-color__importance: #d2bc00;\n$co-color__clickable-text: $co-color__primary;\n$co-color__clickable-text--contrasted: white;\n$co-color__clickable-text--hover: #505050;\n\n// Privacy\n$co-color__privacy: #97b707;\n$co-color__privacy--alt: #04d02f;\n$co-color__next-feature: #c3e4de;\n$privacy-palette: (\n  disabled: #c2c2c2,\n  super: #05b898,\n  good: #ebc064,\n  bad: #d66291,\n  ugly: #ed0e47,\n);\n$privacy-palette-high: (\n  disabled: #c2c2c2,\n  super: #cbe8e3,\n  good: lighten(#ebc064, 30%),\n  bad: lighten(#d66291, 30%),\n  ugly: #edccd4,\n);\n$context-palette: (\n  disabled: #c2c2c2,\n  safe: #07b798,\n  public: #ebc064,\n  unsecure: #ec0f48,\n);\n\n// contrasted foreground\n$co-color__contrast__back: #333;\n$co-color__contrast__back--lower: darken($co-color__contrast__back, 5%);\n$co-color__contrast__back--low: darken($co-color__contrast__back, 2.5%);\n$co-color__contrast__back--high: lighten($co-color__contrast__back, 5%);\n$co-color__contrast__back--higher: lighten($co-color__contrast__back, 10%);\n$co-color__contrast__border: #424242;\n$co-color__contrast__border--lower: darken($co-color__contrast__border, 20%);\n$co-color__contrast__border--low: darken($co-color__contrast__border, 10%);\n$co-color__contrast__border--high: lighten($co-color__contrast__border, 10%);\n$co-color__contrast__border--higher: lighten($co-color__contrast__border, 20%);\n$co-color__contrast__text: #fff;\n$co-color__contrast__text--lower: darken($co-color__contrast__text, 35%);\n$co-color__contrast__text--low: darken($co-color__contrast__text, 15%);\n$co-color__contrast__text--high: lighten($co-color__contrast__text, 10%);\n$co-color__contrast__text--higher: lighten($co-color__contrast__text, 20%);\n\n// foreground\n$co-color__fg__back: #fff;\n$co-color__fg__back--lower: darken($co-color__fg__back, 10%);\n$co-color__fg__back--low: #f3f3f3;\n\n// $co-color__fg__back--high:            lighten($co-color__fg__back, 5%);\n// $co-color__fg__back--higher:          lighten($co-color__fg__back, 10%);\n\n$co-color__fg__border: #b3b3b3;\n$co-color__fg__border--lower: darken($co-color__fg__border, 25%);\n$co-color__fg__border--low: darken($co-color__fg__border, 10%);\n$co-color__fg__border--high: lighten($co-color__fg__border, 15%);\n$co-color__fg__border--higher: #f9f9f9;\n$co-color__fg__text: #444;\n$co-color__fg__text--lower: darken($co-color__fg__text, 20%);\n$co-color__fg__text--low: darken($co-color__fg__text, 10%);\n$co-color__fg__text--high: #505050;\n$co-color__fg__text--higher: lighten($co-color__fg__text, 30%);\n\n// sidebar\n$co-color__sidebar__back: #e8f5fb;\n\n// background\n$co-color__bg__back: #bbe8fd;\n$co-color__bg__back--lower: darken($co-color__bg__back, 5%);\n$co-color__bg__back--low: darken($co-color__bg__back, 2.5%);\n$co-color__bg__back--high: lighten($co-color__bg__back, 6%);\n$co-color__bg__back--higher: lighten($co-color__bg__back, 10%);\n$co-color__bg__border: #eee;\n$co-color__bg__border--lower: darken($co-color__bg__border, 20%);\n$co-color__bg__border--low: darken($co-color__bg__border, 10%);\n$co-color__bg__border--high: lighten($co-color__bg__border, 10%);\n$co-color__bg__border--higher: lighten($co-color__bg__border, 20%);\n$co-color__bg__text: #444;\n$co-color__bg__text--lower: darken($co-color__bg__text, 20%);\n$co-color__bg__text--low: darken($co-color__bg__text, 10%);\n$co-color__bg__text--high: lighten($co-color__bg__text, 15%);\n$co-color__bg__text--higher: lighten($co-color__bg__text, 30%);\n$co-color__bg__action: #ecf9ff;\n\n// --- Shadows ---\n$co-color__shadow: rgb(0 0 0 / 20%);\n$co-shadow--low: 0 1px 0.25rem $co-color__shadow;\n$co-shadow: 0 1px 0.5rem $co-color__shadow;\n$co-shadow--high: 0 1px 0.75rem $co-color__shadow;\n$co-shadow--higher: 0 -1px 0.8rem $co-color__shadow;\n\n// --- Palette ---\n$caliopen-palette: (\n  primary: $co-color__primary,\n  secondary: $co-color__secondary,\n  success: #3adb76,\n  warning: #ffae00,\n  alert: #e43737,\n  disabled: #ccc,\n);\n\n// XXX: refactor me\n$co-blue-light: #8dceec;\n$co-blue: #3386a2;\n$co-blue-darker: #3386a2;\n$co-blue-darkest: #115972;\n$co-bg: #1b1b1b;\n$co-green-pi: #97b707;\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-avatar.scss",
    "content": "$m-avatar__size: (\n  small: 1.75rem,\n  medium: 2.5rem,\n  large: 4rem,\n  xlarge: 8rem,\n);\n\n@mixin m-avatar--size($size) {\n  @if type-of($size) == string {\n    $size: map_get($m-avatar__size, $size);\n  }\n\n  width: $size;\n  height: $size;\n  font-size: 0.5 * $size;\n  font-weight: 600;\n  line-height: $size;\n\n  &__letter {\n    &::before {\n      font-size: inherit;\n      line-height: inherit;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-callout.scss",
    "content": "@import '../common';\n\n@mixin o-callout--color($color) {\n  border-color: lighten($color, 20%);\n  background-color: lighten($color, 35%);\n}\n\n@mixin o-callout-base {\n  margin-bottom: calc($co-margin / 2);\n  padding: calc($co-margin / 2);\n  border: 1px solid $co-color__fg__border;\n  border-radius: $co-radius;\n  background-color: $co-color__fg__back;\n\n  // box-shadow: $co-shadow;\n\n  color: $co-color__fg__text--low;\n}\n\n@mixin o-callout {\n  @include o-callout-base;\n\n  &--success {\n    @include o-callout--color($co-color__success);\n  }\n\n  &--info {\n    @include o-callout--color($co-color__info);\n  }\n\n  &--warning {\n    @include o-callout--color($co-color__warning);\n  }\n\n  &--alert {\n    @include o-callout--color($co-color__alert);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-clickable.scss",
    "content": "@import '../common';\n@import '../vendor/bootstrap_foundation-sites';\n\n@mixin o-clickable {\n  transition: $co-animation__clickable-transition;\n  border-color: $co-color__primary;\n  color: $co-color__clickable-text;\n  font-weight: 700;\n  text-decoration: none;\n  cursor: pointer;\n\n  &:disabled {\n    cursor: default;\n\n    &:hover {\n      color: $co-color__clickable-text--hover;\n    }\n  }\n\n  &:hover {\n    color: $co-color__clickable-text--hover;\n  }\n\n  &:active {\n    color: $co-color__clickable-text--contrasted;\n  }\n\n  @each $name, $color in $caliopen-palette {\n    &--#{$name} {\n      $color-hover: scale-color($color, $lightness: -50%);\n\n      border-color: $color;\n      color: $color;\n\n      &-plain,\n      &:hover,\n      &:focus {\n        &:not(:disabled) {\n          background-color: $color-hover;\n          color: $co-color__clickable-text--hover;\n        }\n      }\n    }\n\n    // &--#{$name}#{'-plain'} {\n    //   background-color: $color-hover;\n    //   color: $co-color__fg__text--high;\n    // }\n  }\n}\n\n@mixin o-clickable--text {\n  color: $co-color__primary--low;\n  text-decoration: underline;\n\n  &:hover,\n  &:focus {\n    color: $co-color__primary--low;\n    text-decoration: none;\n  }\n}\n\n// display: inline, inline-block or expanded (\n@mixin o-clickable--inline {\n  display: inline;\n}\n@mixin o-clickable--button {\n  display: inline-block;\n  padding: 0 map_get($co-margin_, xsmall);\n  line-height: $co-component__height;\n  white-space: nowrap;\n\n  @include breakpoint(medium) {\n    padding-right: map_get($co-margin_, small);\n    padding-left: map_get($co-margin_, small);\n  }\n\n  &:hover {\n    color: $co-color__clickable-text--hover;\n\n    &:not(:disabled) {\n      background-color: $co-color__primary;\n    }\n  }\n\n  &:active {\n    background-color: $co-color__primary--lower;\n  }\n}\n@mixin o-clickable--expanded {\n  display: block;\n  width: 100%;\n  line-height: inherit;\n}\n\n// shape: hollow or plain\n@mixin o-clickable--hollow {\n  border-width: 1px;\n  border-style: solid;\n}\n@mixin o-clickable--plain {\n  background-color: $co-color__primary;\n  color: $co-color__clickable-text--contrasted;\n\n  &:hover:disabled {\n    // force initial color\n    color: $co-color__clickable-text--contrasted;\n  }\n}\n\n@mixin o-clickable--active-text {\n  color: $co-color__clickable-text;\n  cursor: default;\n}\n\n@mixin o-clickable--active-button {\n  background-color: $co-color__primary--low;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-date-picker.scss",
    "content": "// overrides react-datepicker/dist/react-datepicker.css\n\n$date-picker-bg: $co-color__fg__back--low;\n$date-picker-header-bg: $co-color__fg__back;\n$date-picker-font-size: $co-font__size--small;\n$date-picker-text-color: $co-color__fg__text;\n\n@mixin o-date-picker-popper {\n  .react-datepicker-popper[data-placement^='bottom'] {\n    .react-datepicker__triangle,\n    .react-datepicker__triangle::before {\n      border-bottom-color: $date-picker-header-bg;\n    }\n  }\n\n  .react-datepicker-popper[data-placement^='top'] {\n    .react-datepicker__triangle,\n    .react-datepicker__triangle::before {\n      border-top-color: $date-picker-bg;\n    }\n  }\n\n  .react-datepicker-wrapper,\n  .react-datepicker__input-container {\n    width: 100%;\n  }\n}\n\n@mixin o-date-picker-calendar {\n  border: 0;\n  border-radius: 0;\n  background-color: $date-picker-bg;\n  box-shadow: $co-shadow--higher;\n  color: $date-picker-text-color;\n  font-family: inherit;\n  font-size: $date-picker-font-size;\n\n  * {\n    border-radius: 0;\n    color: $date-picker-text-color;\n    font-family: inherit;\n    font-size: $date-picker-font-size;\n  }\n\n  .react-datepicker {\n    &__navigation {\n      &--previous {\n        border-right-color: $date-picker-text-color;\n      }\n\n      &--next {\n        border-left-color: $date-picker-text-color;\n      }\n    }\n\n    &__header {\n      border: 0;\n      background-color: $date-picker-header-bg;\n\n      * {\n        background-color: $date-picker-header-bg;\n      }\n\n      &__dropdown {\n        margin-top: calc($co-margin / 3);\n\n        select {\n          padding: $co-component__spacing / 2;\n          border: 0;\n          background: $date-picker-bg;\n        }\n      }\n    }\n\n    &__day {\n      border-radius: 0;\n\n      &--today {\n        background: $date-picker-header-bg;\n      }\n\n      &:hover,\n      &--selected {\n        border-radius: 0;\n        background: $co-color__primary;\n      }\n    }\n\n    &__day-name {\n      margin-top: calc($co-margin / 3);\n      color: $co-color__fg__text--lower;\n      font-size: $co-font__size--xsmall;\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-form-element.scss",
    "content": "@import '../../styles/vendor/bootstrap_foundation-sites';\n\n@mixin o-form-element {\n  display: inline-block;\n  border: 0;\n  background-color: transparent;\n\n  @if has-value($co-animation__box-transition) {\n    transition: $co-animation__box-transition;\n  }\n}\n\n@mixin o-form-element-select-wrapper {\n  display: inline-block;\n  position: relative;\n  padding-right: 12px;\n\n  &,\n  > * {\n    cursor: pointer;\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    top: 50%;\n    right: 0;\n    width: 0;\n    margin-top: -3px;\n    border-width: 6px 4px;\n    border-style: solid;\n    border-color: $co-color__fg__text--higher transparent transparent;\n    pointer-events: none;\n  }\n}\n\n@mixin o-form-element-select {\n  margin-right: 10px;\n  user-select: none;\n  appearance: none;\n}\n\n@mixin o-form-element-placeholder($color) {\n  // ::placeholder\n  &::-webkit-input-placeholder {\n    /* Chrome/Opera/Safari */\n    color: rgba($color, 0.8);\n  }\n\n  &::-moz-placeholder {\n    /* Firefox 19+ */\n    color: rgba($color, 0.8);\n  }\n\n  &:-ms-input-placeholder {\n    /* IE 10+ */\n    color: rgba($color, 0.8);\n  }\n\n  &:-moz-placeholder {\n    /* Firefox 18- */\n    color: rgba($color, 0.8);\n  }\n}\n\n@mixin o-form-element--decorated {\n  min-height: $co-component__height;\n  padding: $co-component__spacing;\n}\n\n@mixin o-form-element--dark {\n  @include o-form-element-placeholder($co-color__contrast__text);\n\n  @include o-form-element--decorated;\n\n  background-color: $co-color__contrast__back--lower;\n  color: $co-color__fg__text;\n\n  &:focus {\n    background-color: $co-color__contrast__back--lower;\n    color: $co-color__contrast__text;\n  }\n\n  &:disabled {\n    background: $co-color__contrast__back--high;\n  }\n}\n\n@mixin o-form-element--light {\n  @include o-form-element--decorated;\n\n  border: 1px solid $co-color__primary;\n  background-color: $co-color__fg__back;\n  color: $co-color__fg__text;\n\n  &:focus {\n    border-color: $co-color__primary--higher;\n  }\n\n  &:disabled {\n    background: transparent;\n    color: $co-color__primary--low;\n  }\n\n  @include o-form-element-placeholder($co-color__fg__text--high);\n}\n\n@mixin o-form-element--contrasted {\n  @include o-form-element--decorated;\n\n  border-bottom: 2px solid $co-color__primary;\n  background-color: $co-color__bg__back--higher;\n  color: $co-color__fg__text;\n\n  &:focus {\n    border-color: $co-color__primary--higher;\n  }\n\n  &:disabled {\n    background: transparent;\n    color: $co-color__primary--low;\n  }\n\n  @include o-form-element-placeholder($co-color__fg__text--high);\n}\n\n@mixin o-form-element--inline {\n  @include flex-grid-row($size: expand);\n\n  &__label {\n    @include flex-grid-size(shrink);\n\n    display: block;\n    padding-right: map_get($co-form__spacing, small);\n  }\n\n  &__select-wrapper {\n    display: block;\n    flex: 0 1 auto;\n    overflow: hidden;\n  }\n\n  &__input {\n    display: block;\n  }\n\n  &__errors {\n    @include flex-grid-size(12);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-hidden-element.scss",
    "content": "@mixin o-hidden-element--input-file {\n  display: block;\n  position: absolute;\n  top: 0;\n  right: 0;\n  min-width: 100%;\n  min-height: 100%;\n  font-size: 999px;\n  text-align: right;\n  cursor: inherit;\n  opacity: 0;\n  filter: alpha(opacity=0);\n}\n\n@mixin o-hidden-element--label {\n  position: relative;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-navigation.scss",
    "content": "@import '../common';\n\n$l-navigation-item__border-width: 0.25rem;\n$l-navigation__height: $co-component__height + 2 *\n  $l-navigation-item__border-width;\n\n@mixin o-navigation-wrapper {\n  z-index: $l-z-index__navbar;\n\n  &--sticky {\n    position: fixed;\n    top: 0;\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-pi-border.scss",
    "content": "@import '../common';\n\n$pi-border__size: 3px;\n$pi-border__color: $co-color__primary;\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-reset-html-message.scss",
    "content": "@mixin o-reset-html-message {\n  * {\n    width: auto;\n    max-width: 100%;\n  }\n\n  > * {\n    &:first-child {\n      margin-top: 0;\n      padding-top: 0;\n    }\n  }\n\n  blockquote {\n    display: block;\n    margin-left: $co-component__spacing / 2;\n    padding: $co-component__spacing;\n    border-left: 2px solid $co-color__fg__back--low;\n    color: $co-color__fg__text--lower;\n    font-size: $co-font__size--small;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/object/o-triangle.scss",
    "content": "@mixin o-triangle($position, $width, $color) {\n  $border: $width solid $color;\n\n  display: block;\n  content: '';\n  position: absolute;\n  width: 0;\n  height: 0;\n  border: $width solid transparent;\n\n  @if $position == 'top' {\n    bottom: 100%;\n    border-bottom: $border;\n  }\n  @if $position == 'right' {\n    left: 100%;\n    border-left: $border;\n  }\n  @if $position == 'bottom' {\n    top: 100%;\n    border-top: $border;\n  }\n  @if $position == 'left' {\n    right: 100%;\n    border-right: $border;\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/util/breakpoint.scss",
    "content": "@import '../vendor/bootstrap_foundation-sites';\n\n// path related to foundation-sites\n// @import 'util/breakpoint';\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/util/flex-grid.scss",
    "content": "@import '../vendor/bootstrap_foundation-sites';\n\n// path related to foundation-sites\n// @import 'grid/flex-grid';\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/util/u-gradient.scss",
    "content": "// https://github.com/twbs/bootstrap-sass/blob/master/assets/stylesheets/bootstrap/mixins/_gradients.scss\n@mixin u-gradient-striped(\n  $color: rgba(255, 255, 255, 0.15),\n  $angle: 45deg,\n  $size: 4px\n) {\n  background-image: linear-gradient(\n    $angle,\n    $color 25%,\n    transparent 25%,\n    transparent 50%,\n    $color 50%,\n    $color 75%,\n    transparent 75%,\n    transparent\n  );\n  background-size: $size $size;\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/vendor/bootstrap_font-awesome.scss",
    "content": "$fa-font-path: '~font-awesome/fonts';\n@import '~font-awesome/scss/font-awesome';\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/vendor/bootstrap_foundation-sites.scss",
    "content": "@import 'foundation-sites/config';\n@import 'foundation-sites/flex-grid';\n@import 'foundation';\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/vendor/bootstrap_react-redux-notify.scss",
    "content": "@import 'components/Notify/Notify';\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/vendor/foundation-sites/_config.scss",
    "content": "@import '../../common';\n\n//  Foundation for Sites Settings\n//  -----------------------------\n//\n// === for defaults see:\n// https://github.com/zurb/foundation-sites/blob/develop/scss/settings/_settings.scss\n//\n//  Table of Contents:\n//\n//   1. Global\n//   2. Breakpoints\n//   3. The Grid\n//   4. Base Typography\n//   5. Typography Helpers\n//   6. Abide\n//   7. Accordion\n//   8. Accordion Menu\n//   9. Badge\n//  10. Breadcrumbs\n//  11. Button\n//  12. Button Group\n//  13. Callout\n//  14. Close Button\n//  15. Drilldown\n//  16. Dropdown\n//  17. Dropdown Menu\n//  18. Flex Video\n//  19. Forms\n//  20. Label\n//  21. Media Object\n//  22. Menu\n//  23. Meter\n//  24. Off-canvas\n//  25. Orbit\n//  26. Pagination\n//  27. Progress Bar\n//  28. Reveal\n//  29. Slider\n//  30. Switch\n//  31. Table\n//  32. Tabs\n//  33. Thumbnail\n//  34. Title Bar\n//  35. Tooltip\n//  36. Top Bar\n\n// @import 'util/util';\n\n// 1. Global\n// ---------\n\n// $global-font-size: 100%;\n$global-width: $co-site-width;\n\n// $global-lineheight: 1.5;\n$foundation-palette: $caliopen-palette;\n\n// $light-gray: #e6e6e6;\n// $medium-gray: #cacaca;\n// $dark-gray: #8a8a8a;\n// $black: #0a0a0a;\n// $white: #fefefe;\n$body-background: $co-color__bg__back;\n$body-font-color: $co-color__fg__text;\n$body-font-family: 'Open Sans', 'Helvetica Neue', helvetica, roboto, arial,\n  sans-serif;\n\n// $body-antialiased: true;\n// $global-margin: 1rem;\n// $global-padding: 1rem;\n// $global-weight-normal: normal;\n// $global-weight-bold: bold;\n// $global-radius: 0;\n// $global-text-direction: ltr;\n// $global-flexbox: false;\n// $print-transparent-backgrounds: true;\n\n// @include add-foundation-colors;\n\n// 2. Breakpoints\n// --------------\n\n$breakpoints: (\n  small: 0,\n  medium-small: 380px,\n  medium: 640px,\n  large: 1024px,\n  xlarge: 1200px,\n  xxlarge: 1440px,\n);\n\n// $breakpoint-classes: (small medium large);\n\n// 3. The Grid\n// -----------\n\n$grid-row-width: $global-width;\n\n// $grid-column-count: 12;\n$grid-column-gutter: (\n  small: $co-grid__gutter--small,\n  medium: $co-grid__gutter,\n);\n\n// $grid-column-align-edge: true;\n// $block-grid-max: 8;\n\n// 4. Base Typography\n// ------------------\n\n// $header-font-family: $body-font-family;\n// $header-font-weight: $global-weight-normal;\n// $header-font-style: normal;\n// $font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace;\n// $header-sizes: (\n//   small: (\n//     'h1': 24,\n//     'h2': 20,\n//     'h3': 19,\n//     'h4': 18,\n//     'h5': 17,\n//     'h6': 16,\n//   ),\n//   medium: (\n//     'h1': 48,\n//     'h2': 40,\n//     'h3': 31,\n//     'h4': 25,\n//     'h5': 20,\n//     'h6': 16,\n//   ),\n// );\n// $header-color: inherit;\n// $header-lineheight: 1.4;\n// $header-margin-bottom: 0.5rem;\n// $header-text-rendering: optimizeLegibility;\n// $small-font-size: 80%;\n// $header-small-font-color: $medium-gray;\n// $paragraph-lineheight: 1.6;\n// $paragraph-margin-bottom: 1rem;\n// $paragraph-text-rendering: optimizeLegibility;\n// $code-color: $black;\n// $code-font-family: $font-family-monospace;\n// $code-font-weight: $global-weight-normal;\n// $code-background: $light-gray;\n// $code-border: 1px solid $medium-gray;\n// $code-padding: rem-calc(2 5 1);\n// $anchor-color: $primary-color;\n// $anchor-color-hover: scale-color($anchor-color, $lightness: -14%);\n// $anchor-text-decoration: none;\n// $anchor-text-decoration-hover: none;\n// $hr-width: $global-width;\n// $hr-border: 1px solid $medium-gray;\n// $hr-margin: rem-calc(20) auto;\n// $list-lineheight: $paragraph-lineheight;\n// $list-margin-bottom: $paragraph-margin-bottom;\n// $list-style-type: disc;\n// $list-style-position: outside;\n// $list-side-margin: 1.25rem;\n// $list-nested-side-margin: 1.25rem;\n// $defnlist-margin-bottom: 1rem;\n// $defnlist-term-weight: $global-weight-bold;\n// $defnlist-term-margin-bottom: 0.3rem;\n// $blockquote-color: $dark-gray;\n// $blockquote-padding: rem-calc(9 20 0 19);\n// $blockquote-border: 1px solid $medium-gray;\n// $cite-font-size: rem-calc(13);\n// $cite-color: $dark-gray;\n// $keystroke-font: $font-family-monospace;\n// $keystroke-color: $black;\n// $keystroke-background: $light-gray;\n// $keystroke-padding: rem-calc(2 4 0);\n// $keystroke-radius: $global-radius;\n// $abbr-underline: 1px dotted $black;\n\n// 5. Typography Helpers\n// ---------------------\n\n// $lead-font-size: $global-font-size * 1.25;\n// $lead-lineheight: 1.6;\n// $subheader-lineheight: 1.4;\n// $subheader-color: $dark-gray;\n// $subheader-font-weight: $global-weight-normal;\n// $subheader-margin-top: 0.2rem;\n// $subheader-margin-bottom: 0.5rem;\n// $stat-font-size: 2.5rem;\n\n// 6. Abide\n// --------\n\n// $abide-inputs: true;\n// $abide-labels: true;\n// $input-background-invalid: map_get($foundation-palette, alert);\n// $form-label-color-invalid: map_get($foundation-palette, alert);\n// $input-error-color: map_get($foundation-palette, alert);\n// $input-error-font-size: rem-calc(12);\n// $input-error-font-weight: $global-weight-bold;\n\n// 7. Accordion\n// ------------\n\n// $accordion-background: $white;\n// $accordion-plusminus: true;\n// $accordion-item-color: foreground($accordion-background, $primary-color);\n// $accordion-item-background-hover: $light-gray;\n// $accordion-item-padding: 1.25rem 1rem;\n// $accordion-content-background: $white;\n// $accordion-content-border: 1px solid $light-gray;\n// $accordion-content-color: foreground($accordion-background, $primary-color);\n// $accordion-content-padding: 1rem;\n\n// 8. Accordion Menu\n// -----------------\n\n// $accordionmenu-arrows: true;\n// $accordionmenu-arrow-color: $primary-color;\n\n// 9. Badge\n// --------\n\n// $badge-background: $primary-color;\n// $badge-color: foreground($badge-background);\n// $badge-padding: 0.3em;\n// $badge-minwidth: 2.1em;\n// $badge-font-size: 0.6rem;\n\n// 10. Breadcrumbs\n// ---------------\n\n// $breadcrumbs-margin: 0 0 $global-margin 0;\n// $breadcrumbs-item-font-size: rem-calc(11);\n// $breadcrumbs-item-color: $primary-color;\n// $breadcrumbs-item-color-current: $black;\n// $breadcrumbs-item-color-disabled: $medium-gray;\n// $breadcrumbs-item-margin: 0.75rem;\n// $breadcrumbs-item-uppercase: true;\n// $breadcrumbs-item-slash: true;\n\n// 11. Button\n// ----------\n\n// $button-padding: 0.85em 1em;\n// $button-margin: 0 0 $global-margin 0;\n// $button-fill: solid;\n// $button-background: $primary-color;\n// $button-background-hover: scale-color($button-background, $lightness: -15%);\n// $button-color: $white;\n// $button-color-alt: $black;\n// $button-radius: $global-radius;\n// $button-sizes: (\n//   tiny: 0.6rem,\n//   small: 0.75rem,\n//   default: 0.9rem,\n//   large: 1.25rem,\n// );\n// $button-opacity-disabled: 0.25;\n\n// 12. Button Group\n// ----------------\n\n// $buttongroup-margin: 1rem;\n// $buttongroup-spacing: 1px;\n// $buttongroup-child-selector: '.button';\n// $buttongroup-expand-max: 6;\n\n// 13. Callout\n// -----------\n\n// $callout-background: $white;\n// $callout-background-fade: 85%;\n// $callout-border: 1px solid rgba($black, 0.25);\n// $callout-margin: 0 0 1rem 0;\n// $callout-padding: 1rem;\n// $callout-font-color: $body-font-color;\n// $callout-font-color-alt: $body-background;\n// $callout-radius: $global-radius;\n// $callout-link-tint: 30%;\n\n// 14. Close Button\n// ----------------\n\n// $closebutton-position: right top;\n// $closebutton-offset-horizontal: 1rem;\n// $closebutton-offset-vertical: 0.5rem;\n// $closebutton-size: 2em;\n// $closebutton-lineheight: 1;\n// $closebutton-color: $dark-gray;\n// $closebutton-color-hover: $black;\n\n// 15. Drilldown\n// -------------\n\n// $drilldown-transition: transform 0.15s linear;\n// $drilldown-arrows: true;\n// $drilldown-arrow-color: $primary-color;\n// $drilldown-background: $white;\n\n// 16. Dropdown\n// ------------\n\n$dropdown-padding: 0;\n$dropdown-border: 1px solid $co-color__fg__border;\n\n// $dropdown-font-size: 1rem;\n// $dropdown-width: 300px;\n// $dropdown-radius: $global-radius;\n// $dropdown-sizes: (\n//   tiny: 100px,\n//   small: 200px,\n//   large: 400px,\n// );\n\n// 17. Dropdown Menu\n// -----------------\n\n// $dropdownmenu-arrows: true;\n// $dropdownmenu-arrow-color: $anchor-color;\n// $dropdownmenu-min-width: 200px;\n// $dropdownmenu-background: $white;\n// $dropdownmenu-border: 1px solid $medium-gray;\n\n// 18. Flex Video\n// --------------\n\n// $flexvideo-margin-bottom: rem-calc(16);\n// $flexvideo-ratio: 4 by 3;\n// $flexvideo-ratio-widescreen: 16 by 9;\n\n// 19. Forms\n// ---------\n\n// $fieldset-border: 1px solid $medium-gray;\n// $fieldset-padding: rem-calc(20);\n// $fieldset-margin: rem-calc(18 0);\n// $legend-padding: rem-calc(0 3);\n// $form-spacing: rem-calc(16);\n// $helptext-color: $black;\n// $helptext-font-size: rem-calc(13);\n// $helptext-font-style: italic;\n// $input-prefix-color: $black;\n// $input-prefix-background: $light-gray;\n// $input-prefix-border: 1px solid $medium-gray;\n// $input-prefix-padding: 1rem;\n$form-label-color: $co-color__fg__text--lower;\n$form-label-font-size: inherit;\n\n// $form-label-font-weight: $global-weight-normal;\n$form-label-line-height: inherit;\n\n// $select-background: $white;\n// $select-triangle-color: $dark-gray;\n// $select-radius: $global-radius;\n// $input-color: $black;\n// $input-placeholder-color: $medium-gray;\n// $input-font-family: inherit;\n// $input-font-size: rem-calc(16);\n// $input-background: $white;\n// $input-background-focus: $white;\n// $input-background-disabled: $light-gray;\n// $input-border: 1px solid $medium-gray;\n// $input-border-focus: 1px solid $dark-gray;\n// $input-shadow: inset 0 1px 2px rgba($black, 0.1);\n// $input-shadow-focus: 0 0 5px $medium-gray;\n// $input-cursor-disabled: default;\n// $input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out;\n// $input-number-spinners: true;\n// $input-radius: $global-radius;\n\n// 20. Label\n// ---------\n\n// $label-background: $primary-color;\n// $label-color: foreground($label-background);\n// $label-font-size: 0.8rem;\n// $label-padding: 0.33333rem 0.5rem;\n// $label-radius: $global-radius;\n\n// 21. Media Object\n// ----------------\n\n// $mediaobject-margin-bottom: $global-margin;\n// $mediaobject-section-padding: $global-padding;\n// $mediaobject-image-width-stacked: 100%;\n\n// 22. Menu\n// --------\n\n// $menu-margin: 0;\n// $menu-margin-nested: 1rem;\n// $menu-item-padding: 0.7rem 1rem;\n// $menu-item-color-active: $white;\n// $menu-item-background-active: map_get($foundation-palette, primary);\n// $menu-icon-spacing: 0.25rem;\n\n// 23. Meter\n// ---------\n\n// $meter-height: 1rem;\n// $meter-radius: $global-radius;\n// $meter-background: $medium-gray;\n// $meter-fill-good: $success-color;\n// $meter-fill-medium: $warning-color;\n// $meter-fill-bad: $alert-color;\n\n// 24. Off-canvas\n// --------------\n\n// $offcanvas-size: 250px;\n// $offcanvas-background: $light-gray;\n// $offcanvas-zindex: -1;\n// $offcanvas-transition-length: 0.5s;\n// $offcanvas-transition-timing: ease;\n// $offcanvas-fixed-reveal: true;\n// $offcanvas-exit-background: rgba($white, 0.25);\n// $maincontent-class: 'off-canvas-content';\n// $maincontent-shadow: 0 0 10px rgba($black, 0.5);\n\n// 25. Orbit\n// ---------\n\n// $orbit-bullet-background: $medium-gray;\n// $orbit-bullet-background-active: $dark-gray;\n// $orbit-bullet-diameter: 1.2rem;\n// $orbit-bullet-margin: 0.1rem;\n// $orbit-bullet-margin-top: 0.8rem;\n// $orbit-bullet-margin-bottom: 0.8rem;\n// $orbit-caption-background: rgba($black, 0.5);\n// $orbit-caption-padding: 1rem;\n// $orbit-control-background-hover: rgba($black, 0.5);\n// $orbit-control-padding: 1rem;\n// $orbit-control-zindex: 10;\n\n// 26. Pagination\n// --------------\n\n// $pagination-font-size: rem-calc(14);\n// $pagination-margin-bottom: $global-margin;\n// $pagination-item-color: $black;\n// $pagination-item-padding: rem-calc(3 10);\n// $pagination-item-spacing: rem-calc(1);\n// $pagination-radius: $global-radius;\n// $pagination-item-background-hover: $light-gray;\n// $pagination-item-background-current: $primary-color;\n// $pagination-item-color-current: foreground($pagination-item-background-current);\n// $pagination-item-color-disabled: $medium-gray;\n// $pagination-ellipsis-color: $black;\n// $pagination-mobile-items: false;\n// $pagination-arrows: true;\n\n// 27. Progress Bar\n// ----------------\n\n// $progress-height: 1rem;\n// $progress-background: $medium-gray;\n// $progress-margin-bottom: $global-margin;\n// $progress-meter-background: $primary-color;\n// $progress-radius: $global-radius;\n\n// 28. Reveal\n// ----------\n\n// $reveal-background: $white;\n// $reveal-width: 600px;\n// $reveal-max-width: $global-width;\n// $reveal-padding: $global-padding;\n// $reveal-border: 1px solid $medium-gray;\n// $reveal-radius: $global-radius;\n// $reveal-zindex: 1005;\n// $reveal-overlay-background: rgba($black, 0.45);\n\n// 29. Slider\n// ----------\n\n// $slider-width-vertical: 0.5rem;\n// $slider-transition: all 0.2s ease-in-out;\n// $slider-height: 0.5rem;\n// $slider-background: $light-gray;\n// $slider-fill-background: $medium-gray;\n// $slider-handle-height: 1.4rem;\n// $slider-handle-width: 1.4rem;\n// $slider-handle-background: $primary-color;\n// $slider-opacity-disabled: 0.25;\n// $slider-radius: $global-radius;\n\n// 30. Switch\n// ----------\n\n// $switch-background: $medium-gray;\n// $switch-background-active: $primary-color;\n// $switch-height: 2rem;\n// $switch-height-tiny: 1.5rem;\n// $switch-height-small: 1.75rem;\n// $switch-height-large: 2.5rem;\n// $switch-radius: $global-radius;\n// $switch-margin: $global-margin;\n// $switch-paddle-background: $white;\n// $switch-paddle-offset: 0.25rem;\n// $switch-paddle-radius: $global-radius;\n// $switch-paddle-transition: all 0.25s ease-out;\n\n// 31. Table\n// ---------\n\n// $table-background: $white;\n// $table-color-scale: 5%;\n// $table-border: 1px solid smart-scale($table-background, $table-color-scale);\n// $table-padding: rem-calc(8 10 10);\n// $table-hover-scale: 2%;\n// $table-row-hover: darken($table-background, $table-hover-scale);\n// $table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale);\n// $table-striped-background: smart-scale($table-background, $table-color-scale);\n// $table-stripe: even;\n// $table-head-background: smart-scale($table-background, calc($table-color-scale / 2));\n// $table-foot-background: smart-scale($table-background, $table-color-scale);\n// $table-head-font-color: $body-font-color;\n// $show-header-for-stacked: false;\n\n// 32. Tabs\n// --------\n\n// $tab-margin: 0;\n// $tab-background: $white;\n// $tab-background-active: $light-gray;\n// $tab-item-font-size: rem-calc(12);\n// $tab-item-background-hover: $white;\n// $tab-item-padding: 1.25rem 1.5rem;\n// $tab-expand-max: 6;\n// $tab-content-background: $white;\n// $tab-content-border: $light-gray;\n// $tab-content-color: foreground($tab-background, $primary-color);\n// $tab-content-padding: 1rem;\n\n// 33. Thumbnail\n// -------------\n\n// $thumbnail-border: solid 4px $white;\n// $thumbnail-margin-bottom: $global-margin;\n// $thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);\n// $thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);\n// $thumbnail-transition: box-shadow 200ms ease-out;\n// $thumbnail-radius: $global-radius;\n\n// 34. Title Bar\n// -------------\n\n// $titlebar-background: $black;\n// $titlebar-color: $white;\n// $titlebar-padding: 0.5rem;\n// $titlebar-text-font-weight: bold;\n// $titlebar-icon-color: $white;\n// $titlebar-icon-color-hover: $medium-gray;\n// $titlebar-icon-spacing: 0.25rem;\n\n// 35. Tooltip\n// -----------\n\n// $has-tip-font-weight: $global-weight-bold;\n// $has-tip-border-bottom: dotted 1px $dark-gray;\n// $tooltip-background-color: $black;\n// $tooltip-color: $white;\n// $tooltip-padding: 0.75rem;\n// $tooltip-font-size: $small-font-size;\n// $tooltip-pip-width: 0.75rem;\n// $tooltip-pip-height: $tooltip-pip-width * 0.866;\n// $tooltip-radius: $global-radius;\n\n// 36. Top Bar\n// -----------\n\n// $topbar-padding: 0.5rem;\n// $topbar-background: $light-gray;\n// $topbar-submenu-background: $topbar-background;\n// $topbar-title-spacing: 1rem;\n// $topbar-input-width: 200px;\n// $topbar-unstack-breakpoint: medium;\n"
  },
  {
    "path": "src/frontend/web_application/src/styles/vendor/foundation-sites/_flex-grid.scss",
    "content": "@mixin flex-grid-size($columns: null) {\n  flex: flex-grid-column($columns);\n\n  // max-width fixes IE 10/11 not respecting the flex-basis property\n  @if $columns != null and $columns != shrink {\n    max-width: grid-column($columns);\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/src/types.d.ts",
    "content": "import { AxiosError, AxiosRequestConfig } from 'axios';\n\nimport configureAppStore from './store/configure-store';\nimport { RootState } from './store/reducer';\n\n// redux ----------------------------------------\n\nexport type AppDispatch = ReturnType<typeof configureAppStore>['dispatch'];\nexport type GetState = () => RootState;\n\nexport interface AxiosActionPayload<P = any, D = any> {\n  request: {\n    url: string;\n    method?: 'get' | 'patch' | 'delete';\n    params?: P;\n    data?: D;\n  };\n}\n\n// XXX: not sure about that\nexport type AxiosActionError = AxiosError;\n\n//  ---------------------------------------------\n\n// fetch ----------------------------------------\n\nexport interface PagerParams {\n  offset?: number;\n  limit?: number;\n}\n\nexport type PatchPayload<\n  E extends { [key: string]: any } = Record<string, unknown>\n> = Partial<E> & {\n  current_state: Partial<E>;\n};\n\nexport type ResourceStatus =\n  | 'idle'\n  | 'pending'\n  | 'resolved'\n  | 'rejected'\n  | 'invalidated';\n\n//  ---------------------------------------------\n\n// react-query ----------------------------------\n\n// force some reasons, QueryKey from react-query is hard to use (see if solved with rq4)\nexport type QueryKey = string[] | string;\n\nexport interface MutateConfig {\n  url: string;\n}\n\nexport interface FetchConfig {\n  url: string;\n  fetchParams?: Record<string, string | number>;\n}\n\nexport interface QueryConfig extends FetchConfig {\n  queryKeys: QueryKey;\n}\n\n//  ---------------------------------------------\n"
  },
  {
    "path": "src/frontend/web_application/template/index.ejs",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"shortcut icon\" href=\"/favicon.ico\">\n    <title></title>\n  </head>\n  <body>\n    <div id=\"root\">%MARKUP%</div>\n  </body>\n</html>\n"
  },
  {
    "path": "src/frontend/web_application/template/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"shortcut icon\" href=\"/favicon.ico\" />\n    <title>CaliOpen</title>\n    %HEAD%\n  </head>\n  <body>\n    <div id=\"root\">%MARKUP%</div>\n    %BODY_SCRIPT%\n  </body>\n</html>\n"
  },
  {
    "path": "src/frontend/web_application/test/fixtures/contacts/data.json",
    "content": "[\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"c-john-01\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"u-john-01\",\n    \"title\": \"John\",\n    \"additional_name\": \"\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"John Dœuf\",\n    \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"me\"],\n    \"infos\": {\n      \"birthday\": \"2016-05-09T15:01:42.11600\"\n    },\n    \"emails\": [\n      {\n        \"email_id\": \"me-email\",\n        \"is_primary\": 0,\n        \"address\": \"john@caliopen.local\",\n        \"type\": \"home\"\n      }\n    ],\n    \"family_name\": \"Dœuf\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"92d3727a-eefc-4537-b879-85f1c9d197bb\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"u-john-01\",\n    \"title\": \"Bender\",\n    \"additional_name\": \"\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Bender Bending\",\n    \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"robot\", \"planet-express\"],\n    \"infos\": {\n      \"birthday\": \"2016-05-09T15:01:42.11600\"\n    },\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00000001\",\n        \"is_primary\": 0,\n        \"address\": \"bender@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Rodriguez\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"0ba2e346-e4f8-4c45-9adc-eeb1d42fuie0\",\n    \"date_insert\": \"2016-05-09T15:01:43.381000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-fc63-4e41-b490-5f4dd317aa51\",\n    \"title\": \"Zoidberg\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Zoidberg\",\n    \"name_prefix\": \"Dr\",\n    \"pi\": { \"technic\": 50, \"context\": 45, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"robot\", \"INPORTANT\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00000002\",\n        \"is_primary\": 0,\n        \"address\": \"zoidberg@caliopen.local\",\n        \"type\": \"other\"\n      },\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00000003\",\n        \"is_primary\": 0,\n        \"address\": \"zoidberg@planet-express.tld\",\n        \"type\": \"work\"\n      }\n    ],\n    \"family_name\": \"Zoidberg\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"19c3ui42-e3ba-46e7-984f-4c3e8de11c05\",\n    \"date_insert\": \"2016-05-09T15:01:40.034000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-tref-4e41-b490-5f4dd317aa52\",\n    \"title\": \"Leela\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Leela\",\n    \"pi\": { \"technic\": 87, \"context\": 20, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"planet-express\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00000100\",\n        \"is_primary\": 0,\n        \"address\": \"leela@john.doe\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Turanga\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"92d3907a-eeui-4537-b229-85f1c9d197bb\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-fc63-4e41-b490-5f4dd1234553\",\n    \"title\": \"Fry\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Philip J.\",\n    \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 35, \"version\": 0 },\n    \"tags\": [\"humans\", \"planet-express\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00002001\",\n        \"is_primary\": 0,\n        \"address\": \"fry@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Fry\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"0ba2e346-e4f8-4c45-999c-eeb1d42f07e0\",\n    \"date_insert\": \"2016-05-09T15:01:43.381000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-gtpu-4e41-b490-5f4dd317aa54\",\n    \"title\": \"Amy\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Amy\",\n    \"pi\": { \"technic\": 45, \"context\": 34, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"humans\", \"planet-express\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00000022\",\n        \"is_primary\": 0,\n        \"address\": \"amy@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Wong\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"19c3ce42-e3ba-46e7-902f-4cui8de11c05\",\n    \"date_insert\": \"2016-05-09T15:01:40.034000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-hyzc-4e41-b490-5f4dd317aa55\",\n    \"title\": \"Farnsworth\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Hubert J.\",\n    \"name_prefix\": \"Prof.\",\n    \"pi\": { \"technic\": 46, \"context\": 45, \"comportment\": 87, \"version\": 0 },\n    \"tags\": [\"humans\", \"planet-express\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00000120\",\n        \"is_primary\": 0,\n        \"address\": \"professor@planet.express\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Farnsworth\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"9ui3907a-eefc-4537-b229-85f1c9d197bb\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-bjsq-4e41-b490-5f4dd317aa53\",\n    \"title\": \"Lrrr\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Lrrr\",\n    \"name_prefix\": \"Emperor\",\n    \"pi\": { \"technic\": 87, \"context\": 20, \"comportment\": 50, \"version\": 0 },\n    \"tags\": [\"aliens\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-02000001\",\n        \"is_primary\": 0,\n        \"address\": \"lrrr@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"98o3907a-eefc-6724-bv28-85f1c9d197bb\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-olds-4e41-b490-5f4dd317aa53\",\n    \"title\": \"Stephen Hawking\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Stephen\",\n    \"name_prefix\": \"Dr\",\n    \"pi\": { \"technic\": 10, \"context\": 15, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"humans\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-20020001\",\n        \"is_primary\": 0,\n        \"address\": \"stephen@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Hawking\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"0327707a-eefc-6724-bv28-85f1c9d197bb\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"355489c3-olds-4e41-b490-5f4dd317aa53\",\n    \"title\": \"Kif\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Kif\",\n    \"pi\": { \"technic\": 4, \"context\": 24, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"aliens\", \"planet-express\", \"amphibians\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"0327707a-4398-4bd4-9bd5-20020001\",\n        \"is_primary\": 0,\n        \"address\": \"kroker@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Kroker\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"5488707a-eefc-6724-bv28-85f1c9d197bb\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"356489c3-olds-4e41-b490-5f4dd317aa53\",\n    \"title\": \"Zapp Brannigan\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Zapp\",\n    \"pi\": { \"technic\": 41, \"context\": 15, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"humans\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"5488707a-4398-4bd4-9bd5-20020001\",\n        \"is_primary\": 0,\n        \"address\": \"zapp@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Brannigan\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"19c3ce42-e3ba-46e7-902f-4cui8d\",\n    \"date_insert\": \"2016-05-09T15:01:40.034000\",\n    \"identities\": [],\n    \"user_id\": \"340089c3-hyzc-4e41-b490-5f4dd317a\",\n    \"title\": \"Elzar\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Elzar\",\n    \"name_prefix\": \"Chef\",\n    \"pi\": { \"technic\": 48, \"context\": 45, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"aliens\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00000120\",\n        \"is_primary\": 0,\n        \"address\": \"francine@doop\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"a3ce42-e3ba-46e7-902f-4cfdsfsui8d\",\n    \"date_insert\": \"d016-05-09T15:01:40.034000\",\n    \"identities\": [],\n    \"user_id\": \"40489c3-hyzc-4e41-b490-5f4dd317a\",\n    \"title\": \"Scruffy\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Scruffy\",\n    \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 35, \"version\": 0 },\n    \"tags\": [\"humans\", \"planet-express\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00000120\",\n        \"is_primary\": 0,\n        \"address\": \"algore@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Scruffington\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"a3ce42-e3ba-46e7-dfsd902f-4cui8d\",\n    \"date_insert\": \"d016-05-09T15:01:40.034000\",\n    \"identities\": [],\n    \"user_id\": \"04489c3-hyzc-4e41-b490-5f4dd317a\",\n    \"title\": \"Hermès\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Hermès\",\n    \"pi\": { \"technic\": 87, \"context\": 70, \"comportment\": 95, \"version\": 0 },\n    \"tags\": [\"humans\", \"planet-express\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00000120\",\n        \"is_primary\": 0,\n        \"address\": \"Conrad@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Conrad\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"92d3727a-eefc-4537-b879-85f1c7bb\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"u-john-01\",\n    \"title\": \"Flexo\",\n    \"additional_name\": \"\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Flexo\",\n    \"pi\": { \"technic\": 48, \"context\": 56, \"comportment\": 32, \"version\": 0 },\n    \"tags\": [\"robot\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00900001\",\n        \"is_primary\": 0,\n        \"address\": \"flexo@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"0ba2e346-e4f8-4c45-9adc-eeb1die0\",\n    \"date_insert\": \"2016-05-09T15:01:43.381000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-fc63-4e41-b490-5f4dd317aa51\",\n    \"title\": \"Calculon\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Antonio\",\n    \"name_prefix\": \"\",\n    \"pi\": { \"technic\": 10, \"context\": 15, \"comportment\": 12, \"version\": 0 },\n    \"tags\": [\"robot\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-90000002\",\n        \"is_primary\": 0,\n        \"address\": \"calc@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Calculon\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"19c3ui42-e3ba-46e7-984f-4c3e8c05\",\n    \"date_insert\": \"2016-05-09T15:01:40.034000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-tref-4e41-b490-5f4dd317aa52\",\n    \"title\": \"Mom\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"\",\n    \"pi\": { \"technic\": 56, \"context\": 35, \"comportment\": 7, \"version\": 0 },\n    \"tags\": [\"humans\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00900100\",\n        \"is_primary\": 0,\n        \"address\": \"mom@john.doe\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Mom\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"92d3907a-eeui-4537-b229-85f197bb\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-fc63-4e41-b490-5f4dd1234553\",\n    \"title\": \"Morbo the Annihilator\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Morbo\",\n    \"pi\": { \"technic\": 41, \"context\": 45, \"comportment\": 7, \"version\": 0 },\n    \"tags\": [\"aliens\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00002901\",\n        \"is_primary\": 0,\n        \"address\": \"morbo@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"0ba2e346-e4f8-4c45-999c-eeb107e0\",\n    \"date_insert\": \"2016-05-09T15:01:43.381000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-gtpu-4e41-b490-5f4dd317aa54\",\n    \"title\": \"Panucci\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"\",\n    \"name_prefix\": \"Mr.\",\n    \"pi\": { \"technic\": 20, \"context\": 45, \"comportment\": 75, \"version\": 0 },\n    \"tags\": [\"humans\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-00900022\",\n        \"is_primary\": 0,\n        \"address\": \"Panucci@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"Panucci\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"19c3ce42-e3ba-46e7-902f-4cuie11c05\",\n    \"date_insert\": \"2016-05-09T15:01:40.034000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-hyzc-4e41-b490-5f4dd317aa55\",\n    \"title\": \"Nibbler\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Nibbler\",\n    \"name_prefix\": \"Lord\",\n    \"pi\": { \"technic\": 65, \"context\": 45, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"aliens\", \"planet-express\"],\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-0009120\",\n        \"is_primary\": 0,\n        \"address\": \"nib@planet.express\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  },\n  {\n    \"addresses\": [],\n    \"privacy_features\": {},\n    \"phones\": [],\n    \"contact_id\": \"9ui3907a-eefc-4537-b229-f1c9d19b\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"identities\": [],\n    \"user_id\": \"344489c3-bjsq-4e41-b490-5f4dd317aa53\",\n    \"title\": \"Hypnotoad\",\n    \"organizations\": [],\n    \"ims\": [],\n    \"given_name\": \"Hypnotoad\",\n    \"name_prefix\": \"\",\n    \"tags\": [\"amphibians\", \"aliens\"],\n    \"pi\": { \"technic\": 87, \"context\": 75, \"comportment\": 25, \"version\": 0 },\n    \"infos\": {},\n    \"emails\": [\n      {\n        \"email_id\": \"93f03145-4398-4bd4-9bd5-02000901\",\n        \"is_primary\": 0,\n        \"address\": \"Hypnotoad@caliopen.local\",\n        \"type\": \"other\"\n      }\n    ],\n    \"family_name\": \"\",\n    \"avatar\": \"avatar.png\",\n    \"public_keys\": []\n  }\n]\n"
  },
  {
    "path": "src/frontend/web_application/test/fixtures/contacts/index.ts",
    "content": "import { Contact } from 'src/modules/contact/types';\nimport data from './data.json';\n\nconst contactBase: Contact = data[0];\nexport function generateContact(props: Partial<Contact>): Contact {\n  return {\n    ...contactBase,\n    ...props,\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/test/fixtures/device/index.ts",
    "content": "import { STATUS_UNVERIFIED } from 'src/modules/device';\nimport { Device } from 'src/modules/device/types';\n\nexport function generateDevice(): Device {\n  return {\n    device_id: 'aunrsite',\n    name: 'My Device',\n    status: STATUS_UNVERIFIED,\n    type: 'other',\n    user_agent: 'whatever',\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/test/fixtures/tags/data.json",
    "content": "[\n  {\n    \"name\": \"INBOX\",\n    \"type\": \"system\"\n  },\n  {\n    \"name\": \"IMPORTANT\",\n    \"type\": \"system\"\n  },\n  {\n    \"name\": \"SPAM\",\n    \"type\": \"system\"\n  },\n  {\n    \"name\": \"foobar\",\n    \"label\": \"Foobar\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"planet-express\",\n    \"label\": \"Planet Express\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"humans\",\n    \"label\": \"Humans\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"aliens\",\n    \"label\": \"Aliens\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"amphibians\",\n    \"label\": \"Amphibians\",\n    \"type\": \"user\"\n  },\n  {\n    \"name\": \"robot\",\n    \"label\": \"Robot\",\n    \"type\": \"user\"\n  }\n]\n"
  },
  {
    "path": "src/frontend/web_application/test/fixtures/user/data.json",
    "content": "{\n  \"user_id\": \"u-john-01\",\n  \"name\": \"Jaune\",\n  \"given_name\": \"John Dœuf\",\n  \"contact_id\": \"foobar112\",\n  \"contact\": {\n    \"contact_id\": \"c-john-01\",\n    \"date_insert\": \"2016-05-09T15:01:42.381000\",\n    \"user_id\": \"u-john-01\",\n    \"title\": \"Dr\",\n    \"given_name\": \"John Dœuf\",\n    \"pi\": { \"technic\": 87, \"context\": 45, \"comportment\": 25, \"version\": 0 },\n    \"tags\": [\"me\"],\n    \"infos\": {\n      \"birthday\": \"2016-05-09T15:01:42.11600\"\n    },\n    \"emails\": [\n      {\n        \"email_id\": \"me-email\",\n        \"is_primary\": 0,\n        \"address\": \"john@caliopen.local\",\n        \"date_insert\": \"2016-05-09T15:01:42.116000\",\n        \"type\": \"home\"\n      }\n    ],\n    \"family_name\": \"Dœuf\",\n    \"avatar\": \"avatar.png\"\n  },\n  \"privacy_features\": {\n    \"password_strength\": \"2\"\n  }\n}\n"
  },
  {
    "path": "src/frontend/web_application/test/fixtures/user/index.ts",
    "content": "import { UserPayload } from 'src/modules/user/types';\n\nimport data from './data.json';\n\nexport function generateUser(props: Partial<UserPayload> = {}): UserPayload {\n  return {\n    ...data,\n    ...props,\n  };\n}\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/00_home-spec.js",
    "content": "const userUtil = require('../utils/user-util');\n\ndescribe('Home', () => {\n  const EC = protractor.ExpectedConditions;\n\n  describe('Authentication', () => {\n    afterEach(() => {\n      userUtil.signout();\n    });\n\n    it('Log In', async () => {\n      await userUtil.signin();\n      expect(\n        element(by.css('.m-application-tab [title=Timeline]')).isPresent()\n      ).toEqual(true);\n    });\n\n    it('Requires authentication', async () => {\n      browser.get('/');\n      await browser.wait(\n        EC.presenceOf(element(by.css('.s-signin__action'))),\n        1000\n      );\n\n      expect(\n        element(by.css('.s-signin__action .m-button')).getText()\n      ).toContain('Login');\n    });\n\n    it('Log out', async () => {\n      await userUtil.signin();\n      await userUtil.signout();\n      await browser.wait(\n        EC.presenceOf(element(by.css('.s-signin__action'))),\n        1000\n      );\n\n      expect(\n        element(by.css('.s-signin__action .m-button')).getText()\n      ).toContain('Login');\n    });\n  });\n\n  it('Page not found', async () => {\n    await userUtil.signin();\n    await browser.get('/whatever');\n    await browser.wait(\n      EC.presenceOf(element(by.css('.s-page-not-found'))),\n      5 * 1000\n    );\n\n    expect(element(by.css('.s-page-not-found__title')).isPresent()).toEqual(\n      true\n    );\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/10_discussion-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\nconst { filter } = require('../utils/timeline');\n\ndescribe('Discussions', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n  });\n\n  it('List', async () => {\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    expect(\n      element\n        .all(by.css('.s-discussion-item__message_excerpt'))\n        .first()\n        .getText()\n    ).toContain(\"Fry! Stay back! He's too powerful!\");\n    expect(element.all(by.css('.s-discussion-item')).count()).toEqual(6);\n    expect(\n      element(\n        by.cssContainingText('.s-timeline__load-more', 'Load more')\n      ).isPresent()\n    ).toBe(false);\n  });\n\n  describe('Thread', () => {\n    it('Render and listed contacts describe the thread', async () => {\n      // await filter('All');\n      await browser.wait(\n        EC.presenceOf($('.s-timeline .s-discussion-item')),\n        5 * 1000\n      );\n      element(\n        by.cssContainingText(\n          '.s-discussion-item__message_excerpt',\n          \"Fry! Stay back! He's too powerful!\"\n        )\n      ).click();\n\n      expect(\n        element(\n          by.cssContainingText(\n            '.m-navbar-item--is-active .m-navbar-item__content',\n            'zoidberg'\n          )\n        ).isPresent()\n      ).toEqual(true);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/20-message-list-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\nconst { filter } = require('../utils/timeline');\n\ndescribe('Message list', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n  });\n\n  it('List', async () => {\n    // await filter('All');\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(\n      by.cssContainingText(\n        '.s-discussion-item__message_excerpt',\n        \"Fry! Stay back! He's too powerful!\"\n      )\n    ).click();\n    await browser.wait(EC.presenceOf($('.s-discussion')), 5 * 1000);\n    console.log('wait success for .m-message');\n    expect(element.all(by.css('article')).count()).toEqual(2);\n  });\n\n  it('Does not display \"Load More\" on complete discussion', async () => {\n    // await filter('All');\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(\n      by.cssContainingText(\n        '.s-discussion-item__message_excerpt',\n        \"Fry! Stay back! He's too powerful!\"\n      )\n    ).click();\n    expect(element.all(by.css('m-message-list__load-more')).count()).toEqual(0);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/21-reply-to-messag-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\n// const { filter } = require('../utils/timeline');\n\ndescribe('Reply to message -', () => {\n  const EC = protractor.ExpectedConditions;\n\n  const waitAndRefresh = async (waitForElementSelector) => {\n    await browser.sleep(6 * 1000);\n    await browser.refresh();\n    await browser.wait(EC.presenceOf(waitForElementSelector), 5 * 1000);\n    console.log('page refreshed');\n  };\n\n  beforeAll(() => {\n    userUtil.signin();\n  });\n\n  beforeEach(() => {\n    home();\n  });\n\n  it('Automatically saves a draft', async () => {\n    const discussion1Selector = by.cssContainingText(\n      '.s-discussion-item__message_excerpt',\n      \"Fry! Stay back! He's too powerful!\"\n    );\n    const text1 = 'Automatically saves a draft, then refresh.';\n    const text2 = ' Automatically updates a draft, then refresh.';\n\n    // await filter('All');\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(discussion1Selector).click();\n    await browser.wait(\n      EC.presenceOf($('.m-draft-message-quick__input')),\n      5 * 1000\n    );\n    console.log('write msg');\n    await element(by.css('.m-draft-message-quick__input')).sendKeys(text1);\n    console.log('wait 5sec, refresh & see msg');\n    await waitAndRefresh(element(by.css('.m-draft-advanced__body')));\n\n    const draftBodyElement1 = element(\n      by.css('.m-draft-advanced__body .m-textarea')\n    );\n    expect(draftBodyElement1.getText()).toEqual(text1);\n    console.log('write again and refresh');\n    draftBodyElement1.sendKeys(text2);\n\n    await waitAndRefresh(element(by.css('.m-draft-advanced__body')));\n    expect(\n      element(by.css('.m-draft-advanced__body .m-textarea')).getText()\n    ).toContain(`${text1}${text2}`);\n\n    await element(by.cssContainingText('button', 'Send')).click();\n    await browser.wait(\n      EC.presenceOf(element(by.css('.m-message-list'))),\n      1 * 1000\n    );\n  });\n\n  it('Automatically saves a draft while browsing', async () => {\n    const discussion1Selector = by.cssContainingText(\n      '.s-discussion-item__message_excerpt',\n      \"Fry! Stay back! He's too powerful!\"\n    );\n    const discussion2Selector = by.cssContainingText(\n      '.s-discussion-item__message_subject',\n      'Shut up and take my money'\n    );\n    const text3 =\n      \"Add an answer to second discussion, don't wait and go to first one.\";\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(discussion2Selector).click();\n    await browser.wait(\n      EC.presenceOf($('.m-draft-message-quick__input')),\n      5 * 1000\n    );\n    await element(by.css('.m-draft-message-quick__input')).sendKeys(text3);\n\n    console.log(\"back to first discussion, don't wait\");\n    await home();\n    // await filter('All');\n    await element(discussion1Selector).click();\n    await browser.wait(EC.presenceOf($('.m-message-list')), 5 * 1000);\n    console.log('go to 2nd discussion, wait then refresh');\n\n    await home();\n    await element(discussion2Selector).click();\n    await browser.wait(\n      EC.presenceOf(element(by.css('.m-message-list'))),\n      5 * 1000\n    );\n    await waitAndRefresh(element(by.css('.m-draft-advanced__body')));\n    const draftBodyElement2 = element(\n      by.css('.m-draft-advanced__body .m-textarea')\n    );\n    expect(draftBodyElement2.getText()).toEqual(text3);\n    console.log('send');\n    await element(by.cssContainingText('button', 'Send')).click();\n    await browser.wait(\n      EC.presenceOf(element(by.css('.m-message-list'))),\n      5 * 1000\n    );\n  });\n\n  // XXX: save has been removed, but will be restored soon\n  xit('Force saves a draft', async () => {\n    const discussion1Selector = by.cssContainingText(\n      '.s-discussion-item__message_excerpt',\n      \"Fry! Stay back! He's too powerful!\"\n    );\n    const text1 = 'Force save a draft.';\n    // await filter('All');\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(discussion1Selector).click();\n    await browser.wait(\n      EC.presenceOf($('.m-draft-message-quick__input')),\n      5 * 1000\n    );\n    console.info('write msg');\n    // await element(by.css('.m-draft-message-quick__input')).sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a'), text1);\n    await element(by.css('.m-draft-message-quick__input')).sendKeys(text1);\n\n    await element(by.cssContainingText('button', 'Save')).click();\n    await browser.sleep(1 * 1000);\n    await browser.refresh();\n    await browser.wait(\n      EC.presenceOf(element(by.css('.m-draft-message-quick__input'))),\n      5 * 1000\n    );\n    const draftBodyElement1 = element(by.css('.m-draft-message-quick__input'));\n    expect(draftBodyElement1.getText()).toContain(text1);\n    await element(by.cssContainingText('button', 'Send')).click();\n    await browser.wait(\n      EC.presenceOf(element(by.css('.m-message-list'))),\n      5 * 1000\n    );\n  });\n\n  it('Sends a draft', async () => {\n    const discussion1Selector = by.cssContainingText(\n      '.s-discussion-item__message_excerpt',\n      \"Fry! Stay back! He's too powerful!\"\n    );\n    const text1 = 'yes I am!';\n\n    // await filter('All');\n    await browser.wait(\n      EC.presenceOf(element(by.css('.s-timeline .s-discussion-item'))),\n      5 * 1000\n    );\n    await element(discussion1Selector).click();\n    await browser.wait(\n      EC.presenceOf(element(by.css('.m-draft-message-quick__input'))),\n      5 * 1000\n    );\n    console.info('write msg');\n    const draftBodyElement1 = element(by.css('.m-draft-message-quick__input'));\n    // draftBodyElement1.sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a'), text1);\n    draftBodyElement1.sendKeys(text1);\n\n    await element(\n      by.css('.m-draft-message-quick__send-button[title=\"Send\"]')\n    ).click();\n    await browser.sleep(1 * 1000);\n    await browser.wait(\n      EC.presenceOf(element(by.css('.m-draft-message-quick__input'))),\n      5 * 1000\n    );\n    const draftBodyElement2 = element(by.css('.m-draft-message-quick__input'));\n    expect(draftBodyElement2.getText()).toEqual('');\n    expect(element(by.cssContainingText('article', text1)).isPresent()).toEqual(\n      true\n    );\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/22-compose-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\n// const { filter } = require('../utils/timeline');\n\ndescribe('Compose new message', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n  });\n\n  describe('Navigation between drafts', () => {\n    it('Creates a new draft', async () => {\n      const text1 = 'Compose creates a new draft';\n      const writeButtonSelector = by.cssContainingText(\n        '.m-action-btns__btn',\n        'Compose'\n      );\n\n      await element(writeButtonSelector).click();\n      await browser.wait(EC.presenceOf($('.s-new-draft')), 1000);\n      const items = await element\n        .all(by.css('.m-navbar-item .m-item-link'))\n        .filter((item) => item.getText().then((text) => text === 'Compose'));\n      expect(items.length).toEqual(1);\n      console.log('write msg');\n      const draftBodyElement1 = element(\n        by.css('.m-draft-advanced__body .m-textarea')\n      );\n      await draftBodyElement1.sendKeys(text1);\n      await element(writeButtonSelector).click();\n      await browser.wait(EC.presenceOf($('.s-new-draft')), 1000);\n      await expect(draftBodyElement1.getText()).not.toEqual(text1);\n      const items2 = await element\n        .all(by.css('.m-navbar-item .m-item-link'))\n        .filter((item) => item.getText().then((text) => text === 'Compose'));\n      expect(items2.length).toEqual(2);\n      await items2[0].click();\n      await browser.wait(EC.presenceOf($('.s-new-draft')), 1000);\n      expect(draftBodyElement1.getText()).toEqual(text1);\n    });\n  });\n\n  describe('Save a draft and send', () => {\n    it('Composes a new message with no recipients', async () => {\n      const text1 = 'new message with no rcpts ';\n\n      const writeButtonSelector = by.cssContainingText(\n        '.m-action-btns__btn',\n        'Compose'\n      );\n\n      await element(writeButtonSelector).click();\n      await browser.wait(EC.presenceOf($('.s-new-draft')), 1000);\n      expect(\n        element(by.cssContainingText('.m-navbar-item', 'Compose')).isPresent()\n      ).toEqual(true);\n      console.log('write msg');\n      const draftBodyElement1 = element(\n        by.css('.m-draft-advanced__body .m-textarea')\n      );\n      await draftBodyElement1.sendKeys(text1);\n      // await element(by.cssContainingText('button', 'Save')).click();\n      // XXX: save button will be back soon; wait for autosave\n      await browser.sleep(6 * 1000);\n      await browser.wait(EC.presenceOf(draftBodyElement1), 3 * 1000);\n      expect(\n        element.all(by.css('.m-recipient-list__recipient')).count()\n      ).toEqual(0);\n      expect(draftBodyElement1.getText()).toEqual(text1);\n      await home();\n      // await filter('All');\n      await browser.wait(\n        EC.presenceOf($('.s-timeline .s-discussion-item')),\n        3 * 1000\n      );\n      expect(\n        element\n          .all(\n            by.cssContainingText('.s-discussion-item__message_excerpt', text1)\n          )\n          .count()\n      ).toEqual(1);\n    });\n\n    it('Composes a new message with a known recipient', async () => {\n      const text1 = 'new message for a known recipient';\n      const writeButtonSelector = by.cssContainingText(\n        '.m-action-btns__btn',\n        'Compose'\n      );\n\n      await element(writeButtonSelector).click();\n      await browser.wait(EC.presenceOf($('.s-new-draft')), 1000);\n      console.info('search recipient');\n      const searchInputElement = element(\n        by.css('.m-recipient-list__search-input')\n      );\n      await searchInputElement.sendKeys('ben');\n      await browser.wait(\n        EC.presenceOf($('.m-recipient-list__search-result')),\n        3 * 1000\n      );\n      const results = await element.all(\n        by.css('.m-recipient-list__search-result')\n      );\n      await results[0].click();\n      console.info('write msg');\n      const draftBodyElement1 = element(\n        by.css('.m-draft-advanced__body .m-textarea')\n      );\n      await draftBodyElement1.sendKeys(text1);\n      // await element(by.cssContainingText('button', 'Save')).click();\n      await browser.wait(EC.presenceOf(draftBodyElement1), 3 * 1000);\n      const items = await element.all(by.css('.m-recipient-list__recipient'));\n      expect(items.length).toEqual(1);\n      expect(items[0].getText()).toContain('bender@caliopen.local');\n    });\n  });\n\n  describe('Recipient search results manipulation', () => {\n    it('Has no suggestions for an already selected recipient', async () => {\n      const writeButtonSelector = by.cssContainingText(\n        '.m-action-btns__btn',\n        'Compose'\n      );\n\n      await element(writeButtonSelector).click();\n      await browser.wait(EC.presenceOf($('.s-new-draft')), 1000);\n      console.info('search recipient');\n      const searchInputElement = element(\n        by.css('.m-recipient-list__search-input')\n      );\n\n      await searchInputElement.sendKeys('caliopen');\n      await browser.wait(\n        EC.presenceOf($('.m-recipient-list__search-result')),\n        3 * 1000\n      );\n      await element(\n        by.cssContainingText(\n          '.m-recipient-list__search-result',\n          'bender@caliopen.local'\n        )\n      ).click();\n      const searchInputElement2 = element(\n        by.css('.m-recipient-list__search-input')\n      );\n\n      await searchInputElement2.sendKeys('caliopen');\n      await browser.wait(\n        EC.presenceOf($('.m-recipient-list__search-result')),\n        3 * 1000\n      );\n      expect(\n        element(\n          by.cssContainingText(\n            '.m-recipient-list__search-result',\n            'bender@caliopen.local'\n          )\n        ).isPresent()\n      ).toEqual(false);\n      const items = await element.all(by.css('.m-recipient-list__recipient'));\n      expect(items.length).toEqual(1);\n      expect(items[0].getText()).toContain('bender@caliopen.local');\n    });\n\n    it('Adds a recipient when clicking outside', async () => {\n      const writeButtonSelector = by.cssContainingText(\n        '.m-action-btns__btn',\n        'Compose'\n      );\n      const dropdownSelector = by.css('.m-recipient-list__search .m-dropdown');\n      const searchTerm = 'ben';\n\n      await element(writeButtonSelector).click();\n      await browser.wait(EC.presenceOf($('.s-new-draft')), 1000);\n      console.log('search recipient');\n      const searchInputElement = element(\n        by.css('.m-recipient-list__search-input')\n      );\n      await searchInputElement.sendKeys(searchTerm);\n      await browser.wait(\n        EC.presenceOf($('.m-recipient-list__search-result')),\n        3 * 1000\n      );\n      const t1 = await element(dropdownSelector).isDisplayed();\n      expect(t1).toEqual(true);\n      await element(by.css('.m-recipient-list__search-input')).click();\n      const t2 = await element(dropdownSelector).isDisplayed();\n      expect(t2).toEqual(true);\n      await element(by.cssContainingText('.s-new-draft', 'From')).click();\n      const t3 = await element(dropdownSelector).isDisplayed();\n      expect(t3).toEqual(false);\n      await browser.sleep(1 * 1000);\n      expect(\n        element(\n          by.cssContainingText('.m-recipient-list__recipient', searchTerm)\n        ).isPresent()\n      ).toEqual(true);\n    });\n\n    it('Can use keyboard arrows to select search result', async () => {\n      const writeButtonSelector = by.cssContainingText(\n        '.m-action-btns__btn',\n        'Compose'\n      );\n      const searchResultItemsSelector = by.css(\n        '.m-recipient-list__search-result'\n      );\n\n      await element(writeButtonSelector).click();\n      await browser.wait(EC.presenceOf($('.s-new-draft')), 1000);\n      console.log('search recipient');\n      const searchInputElement = element(\n        by.css('.m-recipient-list__search-input')\n      );\n      await searchInputElement.sendKeys('caliopen');\n      await browser.wait(\n        EC.presenceOf($('.m-recipient-list__search-result')),\n        3 * 1000\n      );\n\n      const items = await element.all(searchResultItemsSelector);\n      expect(items[0].getAttribute('class')).toContain('m-button--active');\n      await searchInputElement.sendKeys(protractor.Key.ARROW_DOWN);\n      const items2 = await element.all(searchResultItemsSelector);\n      expect(items2[0].getAttribute('class')).not.toContain('m-button--active');\n      expect(items2[1].getAttribute('class')).toContain('m-button--active');\n      await searchInputElement.sendKeys(protractor.Key.ARROW_DOWN);\n      const items3 = await element.all(searchResultItemsSelector);\n      expect(items3[2].getAttribute('class')).toContain('m-button--active');\n      await searchInputElement.sendKeys(\n        protractor.Key.ARROW_UP,\n        protractor.Key.ENTER\n      );\n      const items4 = await element.all(by.css('.m-recipient-list__recipient'));\n      expect(items4.length).toEqual(1);\n      expect(items4[0].getText()).toContain('zoidberg@planet-express.tld');\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/23-delete-message-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\nconst { filter } = require('../utils/timeline');\n\ndescribe('Delete message', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n  });\n\n  const clickBtnInModal = (btnText) =>\n    element(by.cssContainingText('.m-modal button', btnText)).click();\n\n  it('Delete message one by one', async () => {\n    const discussion1Selector = by.cssContainingText(\n      '.s-discussion-item__message_subject',\n      'remove message one by one'\n    );\n    const message1ToDel = 'first message to remove individually';\n    const message2ToDel = 'last message to remove individually';\n    const deleteAMessage = async (messageText) => {\n      const messageElem = element(by.cssContainingText('article', messageText));\n      await messageElem\n        .element(\n          by.cssContainingText('.m-message-action-container__action', 'Delete')\n        )\n        .click();\n\n      return clickBtnInModal(\"Yes I'm sure\");\n    };\n\n    // await filter('All');\n    await browser.wait(EC.presenceOf($('.s-timeline')), 5 * 1000);\n    await element(discussion1Selector).click();\n    await browser.wait(EC.presenceOf($('article')), 5 * 1000);\n    await deleteAMessage(message1ToDel);\n    await browser.wait(EC.presenceOf($('article')), 5 * 1000);\n    expect(\n      element(by.cssContainingText('article', message1ToDel)).isPresent()\n    ).toBe(false);\n    await deleteAMessage(message2ToDel);\n    await browser.wait(EC.presenceOf($('.s-timeline')), 5 * 1000);\n  });\n\n  xit('Delete all messages of a collection', async () => {\n    const discussionSelector = by.cssContainingText(\n      '.s-discussion-item__message_excerpt',\n      'a message of a collection to remove'\n    );\n\n    // await filter('All');\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(discussionSelector).click();\n    await browser.wait(EC.presenceOf($('.m-message-list__action')), 5 * 1000);\n    await browser.executeScript('window.scrollTo(0,0);');\n    await element(\n      by.cssContainingText('.m-message-list__action', 'Delete')\n    ).click();\n    await clickBtnInModal(\"Yes I'm sure\");\n    await browser.wait(EC.presenceOf($('.s-timeline')), 5 * 1000);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/24-scroll-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\nconst { filter } = require('../utils/timeline');\n\ndescribe('Scroll on Timeline and Discussion', () => {\n  const EC = protractor.ExpectedConditions;\n  const clickReply = (message) => {\n    const msg = element(by.cssContainingText('article', message));\n\n    return msg.element(by.cssContainingText('.m-button', 'Reply')).click();\n  };\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n  });\n\n  it('Reaches targets and goes back home', async () => {\n    // await filter('All');\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(\n      by.cssContainingText(\n        '.s-discussion-item__message_excerpt',\n        'Moi, je verrais plutôt les champignons'\n      )\n    ).click();\n    await browser.wait(EC.presenceOf($('article')), 5 * 1000);\n    const scrollYD = await browser.executeScript(() => window.scrollY);\n    expect(scrollYD).toBeGreaterThan(0);\n\n    await browser.executeScript('window.scrollTo(0,0);');\n    await clickReply('Rien du tout !');\n    const scrollYR = await browser.executeScript(() => window.scrollY);\n    expect(scrollYR).toBeGreaterThan(scrollYD);\n\n    // await home();\n    // const scrollYH = await browser.executeScript(() => window.scrollY);\n    // always 0 because Timeline is height has not enough discussions\n    // expect(scrollYH).not.toBe(0);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/25-assoc-participant-contact-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\n\ndescribe('Associate participant to contact', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n  });\n\n  it('redirect to association page', async () => {\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(\n      by.cssContainingText(\n        '.s-discussion-item__message_excerpt',\n        'Les champignons gardent toute leur saveur quand ils'\n      )\n    ).click();\n    await browser.wait(EC.presenceOf($('.s-discussion')), 5 * 1000);\n    await element(\n      by.css(\n        '.s-discussion-action-bar__action[title=\"Add a participant to the contact book\"]'\n      )\n    ).click();\n    await element(\n      by.cssContainingText('.m-action-bar__actions .m-link', 'Astérix')\n    ).click();\n    await browser.wait(EC.presenceOf($('.s-contact-association')), 5 * 1000);\n    expect(browser.getCurrentUrl()).toContain(\n      '/contact-association/email/asterix@caliopen.local?label=Ast%C3%A9rix'\n    );\n  });\n\n  it('new contact form', async () => {\n    await browser.get(\n      '/contact-association/email/asterix@caliopen.local?label=Asterix'\n    );\n    await browser.wait(EC.presenceOf($('.s-contact-association')), 5 * 1000);\n    await element(by.cssContainingText('.m-button', 'Add new contact')).click();\n    await browser.wait(EC.presenceOf($('.s-contact__form')), 5 * 1000);\n    expect(\n      element(by.css('input[name=\"given_name\"]')).getAttribute('value')\n    ).toEqual('Asterix');\n    expect(\n      element(by.css('input[name=\"emails[0].address\"]')).getAttribute('value')\n    ).toEqual('asterix@caliopen.local');\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/30-settings-spec.js",
    "content": "const userUtil = require('../utils/user-util');\n\ndescribe('Settings', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  it('Display form', async () => {\n    await userUtil.showSettings('Application');\n    await browser.wait(EC.presenceOf($('.l-settings')), 5 * 1000);\n    await expect(\n      element.all(by.css('.m-section__title')).first().getText()\n    ).toEqual('Customize your interface');\n    await expect(\n      element(by.cssContainingText('.m-button', 'Save settings')).isPresent()\n    ).toBe(true);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/35-remote-identitiy-settings-spec.js",
    "content": "const userUtil = require('../utils/user-util');\n\nconst capitalize = (str) => `${str.charAt(0).toUpperCase()}${str.slice(1)}`;\n\ndescribe('Remote Identity Settings', () => {\n  const EC = protractor.ExpectedConditions;\n\n  const crudOAuth = async ({ providerName }) => {\n    await element(\n      by.cssContainingText('.m-provider-button', capitalize(providerName))\n    ).click();\n    const mainWindow = await browser.getWindowHandle();\n    await browser.switchTo().window(`authorization_${providerName}`);\n    await element(\n      by.cssContainingText('a', `authorize ${providerName}`)\n    ).click();\n    await browser.switchTo().window(mainWindow);\n    let identityElement;\n    // eslint-disable-next-line default-case\n    switch (providerName) {\n      case 'gmail':\n        identityElement = element(\n          by.cssContainingText('.s-settings-identities__identity', 'dev@gmail')\n        );\n        break;\n      case 'twitter':\n        identityElement = element(\n          by.cssContainingText('.s-settings-identities__identity', '@dev')\n        );\n        break;\n    }\n    await browser.wait(EC.presenceOf(identityElement), 5 * 1000);\n    await identityElement\n      .element(by.cssContainingText('.m-button', 'Delete'))\n      .click();\n    await element(by.cssContainingText('.m-button', \"Yes I'm sure\")).click();\n    await browser.wait(EC.stalenessOf(identityElement), 5 * 1000);\n    expect(identityElement.isPresent()).toEqual(false);\n  };\n\n  beforeAll(async () => {\n    await userUtil.signin();\n    await userUtil.showAccount('External accounts');\n    await browser.wait(EC.presenceOf($('.s-settings-identities')), 5 * 1000);\n  });\n\n  it('crud remote email', async () => {\n    await element(by.cssContainingText('.m-provider-button', 'Email')).click();\n    const newFormElement = element(by.css('.m-new-identity__email-form'));\n    await newFormElement\n      .element(by.css('input[name=identifier]'))\n      .sendKeys('foobar');\n    await newFormElement\n      .element(by.css('input[name=inserverHostname]'))\n      .sendKeys('foobar.bar');\n    await newFormElement\n      .element(by.css('input[name=inserverPort]'))\n      .sendKeys('993');\n    await newFormElement\n      .element(by.css('input[name=inusername]'))\n      .sendKeys('foo');\n    await newFormElement\n      .element(by.css('input[name=inpassword]'))\n      .sendKeys('secret');\n    await newFormElement\n      .element(by.cssContainingText('.m-button', 'Save'))\n      .click();\n    const identityElement = element(\n      by.cssContainingText('.s-settings-identities__identity', 'foobar')\n    );\n    await browser.wait(EC.presenceOf(identityElement), 5 * 1000);\n    expect(newFormElement.isPresent()).toEqual(false);\n\n    await identityElement\n      .element(by.cssContainingText('.m-button', 'Edit'))\n      .click();\n    expect(\n      identityElement\n        .element(by.css('input[name=identifier]'))\n        .getAttribute('disabled')\n    ).toEqual('true');\n    await identityElement\n      .element(by.css('input[name=inserverHostname]'))\n      .sendKeys(' edit');\n    await identityElement\n      .element(by.cssContainingText('.m-button', 'Save'))\n      .click();\n    await browser.wait(EC.presenceOf(identityElement), 5 * 1000);\n    expect(\n      identityElement.element(by.css('input[name=identifier]')).isPresent()\n    ).toBe(false);\n\n    await identityElement\n      .element(by.cssContainingText('.m-button', 'Delete'))\n      .click();\n    await element(by.cssContainingText('.m-button', \"Yes I'm sure\")).click();\n    await browser.wait(EC.stalenessOf(identityElement), 5 * 1000);\n    expect(identityElement.isPresent()).toEqual(false);\n  });\n\n  it('crud oauth gmail', async () => {\n    await crudOAuth({ providerName: 'gmail' });\n  });\n\n  it('crud oauth twitter', async () => {\n    await crudOAuth({ providerName: 'twitter' });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/40-new-contact-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\n\ndescribe('Create new contact', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n  });\n\n  it('New', async () => {\n    const name = 'Foobar';\n    await element(by.css('.m-navbar-item .m-link[title=\"Contacts\"]')).click();\n    await browser.wait(\n      EC.presenceOf($('.s-contact-book__action-button')),\n      1000\n    );\n    await element(\n      by.cssContainingText('.s-contact-book__action-button', 'Add')\n    ).click();\n    await browser.wait(\n      EC.presenceOf($('.s-contact .m-contact-profile-form')),\n      1000\n    );\n    await element(\n      by.css('.m-contact-profile-form__input input[name=\"given_name\"]')\n    ).sendKeys(name);\n    await browser.wait(\n      EC.stalenessOf(\n        element(\n          by.cssContainingText('.s-contact__action[disabled]', 'Validate')\n        )\n      ),\n      2000\n    );\n    await element(\n      by.cssContainingText('.s-contact__action', 'Validate')\n    ).click();\n    await browser.wait(\n      EC.stalenessOf($('.m-action-bar__loading .m-spinner')),\n      1000\n    );\n    expect(element(by.css('.s-contact-main-title__name')).getText()).toEqual(\n      name\n    );\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/50-search-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\n\ndescribe('Search', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n  });\n\n  it('Search for Foobar', async () => {\n    const searchTerms = 'Foobar';\n    const tabLabel = 'Results for: Foobar';\n    const msgLabel = '2 messages contains \"Foobar\" in their subject or content';\n    const contactLabel =\n      '0 contacts contains \"Foobar\" in their label or profile';\n\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(by.css('.m-search-field__search-input')).sendKeys(\n      searchTerms\n    );\n    await element(by.css('.m-search-field__search-button')).click();\n    await browser.wait(\n      EC.presenceOf(\n        element(by.cssContainingText('.l-search-results__panel', msgLabel))\n      ),\n      5 * 1000\n    );\n    expect(\n      element(\n        by.cssContainingText('.l-search-results__panel', contactLabel)\n      ).isPresent()\n    ).toEqual(true);\n    expect(\n      element(by.cssContainingText('.m-navbar-item', tabLabel)).isPresent()\n    ).toEqual(true);\n    expect(element.all(by.css('.s-message-result-item')).count()).toEqual(2);\n    await element(\n      by.cssContainingText('.m-nav-list__item .m-link', 'Message')\n    ).click();\n    await browser.wait(EC.presenceOf($('.l-search-results')), 5 * 1000);\n    expect(\n      element(\n        by.cssContainingText('.l-search-results__panel', msgLabel)\n      ).isPresent()\n    ).toEqual(false);\n    expect(element.all(by.css('.s-message-result-item')).count()).toEqual(2);\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/60-tag-spec.js",
    "content": "const { signin, showSettings } = require('../utils/user-util');\nconst { switchApp, home } = require('../utils/navigation');\nconst { filter } = require('../utils/timeline');\n\ndescribe('Tag', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n  });\n\n  it('Manage tags on a message of a discussion', async () => {\n    const content = 'Shut up and take my money! Leela, are you alright';\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    await element(by.cssContainingText('.m-link', content)).click();\n    await browser.wait(\n      EC.presenceOf(element(by.cssContainingText('.s-mail-message', content))),\n      5 * 1000\n    );\n    console.log('click tag');\n    // FIXME\n    await element(by.cssContainingText('.s-mail-message', content))\n      .element(\n        by.cssContainingText('.m-message-actions-container__action', 'Tags')\n      )\n      .click();\n    expect(\n      element(by.cssContainingText('.m-modal', 'Tags')).isPresent()\n    ).toEqual(true);\n    await element(by.css('.m-modal__close')).click();\n  });\n\n  xit('Manage tags on timeline', async () => {\n    const tagName = 'Mon tag';\n\n    // await filter('All');\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    const messageElement = element(\n      by.cssContainingText(\n        '.s-discussion-item',\n        \"Fry! Stay back! He's too powerful!\"\n      )\n    );\n    await messageElement\n      .element(by.css('.s-discussion-item__select input[type=checkbox]'))\n      .click();\n    await element(\n      by.css('.m-message-selector__actions .m-button[aria-label=\"Manage tags\"]')\n    ).click();\n    expect(\n      element(by.cssContainingText('.m-modal', 'Tags')).isPresent()\n    ).toEqual(true);\n    expect(\n      element.all(by.css('.m-tags-form__section .m-tag-item')).count()\n    ).toEqual(2);\n    await browser.element(by.css('.m-tags-form .m-input-text')).sendKeys('am');\n    await browser\n      .element(by.cssContainingText('.m-tags-form__found-tag', 'Amphibians'))\n      .click();\n    await browser.wait(\n      EC.presenceOf(\n        element(\n          by.cssContainingText(\n            '.m-tags-form__section .m-tag-item',\n            'Amphibians'\n          )\n        )\n      ),\n      5 * 1000\n    );\n\n    await browser\n      .element(by.css('.m-tags-form .m-input-text'))\n      .sendKeys(tagName);\n    await browser\n      .element(by.css('.m-tags-search__button[aria-label=Add]'))\n      .click();\n    await browser.wait(\n      EC.presenceOf(\n        element(\n          by.cssContainingText('.m-tags-form__section .m-tag-item', tagName)\n        )\n      ),\n      8 * 1000\n    );\n\n    await element(\n      by.cssContainingText('.m-tags-form__section .m-tag-item', 'Inbox')\n    )\n      .element(by.css('[aria-label=\"Remove\"]'))\n      .click();\n    await browser.wait(\n      EC.stalenessOf(\n        element(\n          by.cssContainingText('.m-tags-form__section .m-tag-item', 'Inbox')\n        )\n      ),\n      5 * 1000\n    );\n\n    await element(\n      by.cssContainingText('.m-tags-form__section .m-tag-item', tagName)\n    )\n      .element(by.css('[aria-label=\"Remove\"]'))\n      .click();\n    await browser.wait(\n      EC.stalenessOf(\n        element(\n          by.cssContainingText('.m-tags-form__section .m-tag-item', tagName)\n        )\n      ),\n      5 * 1000\n    );\n    await browser.wait(\n      EC.stalenessOf(\n        messageElement.element(\n          by.cssContainingText('.s-discussion-item__tags', tagName)\n        )\n      ),\n      5 * 1000\n    );\n    await element(by.css('.m-modal__close')).click();\n  });\n\n  xit('Manage tags for multiple messages on timeline', async () => {\n    const tagName = 'Mon tag';\n    // await filter('All');\n    await browser.wait(\n      EC.presenceOf($('.s-timeline .s-discussion-item')),\n      5 * 1000\n    );\n    const messageElement1 = element(\n      by.cssContainingText('.s-discussion-item', 'zoidberg')\n    );\n    const messageElement2 = element(\n      by.cssContainingText('.s-discussion-item', 'Fry! Stay back!')\n    );\n\n    await messageElement1\n      .element(by.css('.s-discussion-item__select input[type=checkbox]'))\n      .click();\n    await messageElement2\n      .element(by.css('.s-discussion-item__select input[type=checkbox]'))\n      .click();\n    await element(\n      by.css('.m-message-selector__actions .m-button[aria-label=\"Manage tags\"]')\n    ).click();\n    expect(\n      element(by.cssContainingText('.m-modal', 'Tags')).isPresent()\n    ).toEqual(true);\n    expect(\n      element.all(by.css('.m-tags-form__section .m-tag-item')).count()\n    ).toEqual(1);\n    await browser.element(by.css('.m-tags-form .m-input-text')).sendKeys('in');\n    await browser\n      .element(by.cssContainingText('.m-tags-form__found-tag', 'Inbox'))\n      .click();\n    await browser.wait(\n      EC.presenceOf(\n        element(\n          by.cssContainingText('.m-tags-form__section .m-tag-item', 'Inbox')\n        )\n      ),\n      5 * 1000\n    );\n    await browser\n      .element(by.css('.m-tags-form .m-input-text'))\n      .sendKeys(tagName);\n    await browser\n      .element(by.css('.m-tags-search__button[aria-label=Add]'))\n      .click();\n    await browser.wait(\n      EC.presenceOf(\n        element(\n          by.cssContainingText('.m-tags-form__section .m-tag-item', tagName)\n        )\n      ),\n      5 * 1000\n    );\n    await element(\n      by.cssContainingText('.m-tags-form__section .m-tag-item', 'Inbox')\n    )\n      .element(by.css('[aria-label=\"Remove\"]'))\n      .click();\n    await browser.wait(\n      EC.stalenessOf(\n        element(\n          by.cssContainingText('.m-tags-form__section .m-tag-item', 'Inbox')\n        )\n      ),\n      5 * 1000\n    );\n    await element(\n      by.cssContainingText('.m-tags-form__section .m-tag-item', tagName)\n    )\n      .element(by.css('[aria-label=\"Remove\"]'))\n      .click();\n    await browser.wait(\n      EC.stalenessOf(\n        element(\n          by.cssContainingText('.m-tags-form__section .m-tag-item', tagName)\n        )\n      ),\n      5 * 1000\n    );\n\n    await element(by.css('.m-modal__close')).click();\n  });\n\n  it('Manage tags on a contact', async () => {\n    await element(by.css('.m-navbar-item .m-link[title=\"Contacts\"]')).click();\n    const contactItemElement = element(\n      by.cssContainingText('.m-contact-item__title', 'Bender Bending Rodriguez')\n    );\n    await browser.wait(EC.presenceOf(contactItemElement), 5 * 1000);\n    await contactItemElement.click();\n    await browser.wait(\n      EC.presenceOf(\n        element(\n          by.cssContainingText(\n            '.s-contact-main-title__name',\n            'Bender Bending Rodriguez'\n          )\n        )\n      ),\n      5 * 1000\n    );\n    // XXX: may be fix w/ tab scroll\n    await browser.executeScript('window.scrollTo(0,0);');\n    await element(\n      by.cssContainingText('.m-action-bar__action-btn', 'Edit tags')\n    ).click();\n    expect(\n      element(by.cssContainingText('.m-modal', 'Tags')).isPresent()\n    ).toEqual(true);\n    await element(by.css('.m-modal__close')).click();\n  });\n\n  describe('Manage tags in settings', () => {\n    it('Add and remove a new tag', async () => {\n      const tagName = 'Mon nouveau tag';\n      await showSettings('Tags');\n      await browser.wait(\n        EC.presenceOf($('.m-add-tag .m-input-text')),\n        5 * 1000\n      );\n      await element(by.css('.m-add-tag .m-input-text')).sendKeys(tagName);\n      await element(by.css('.m-add-tag__button[aria-label=Add]')).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText('.s-tags-settings__tags .m-tag-input', tagName)\n          )\n        ),\n        5 * 1000\n      );\n      await element(\n        by.cssContainingText('.s-tags-settings__tags .m-tag-input', tagName)\n      )\n        .element(by.css('.m-tag-input__delete'))\n        .click();\n      await browser.wait(\n        EC.stalenessOf(\n          element(\n            by.cssContainingText('.s-tags-settings__tags .m-tag-input', tagName)\n          )\n        ),\n        5 * 1000\n      );\n    });\n\n    it('Should not allow to create a tag that already exist', async () => {\n      const tagName = 'my_tag';\n      await showSettings('Tags');\n      await browser.wait(\n        EC.presenceOf($('.m-add-tag .m-input-text')),\n        5 * 1000\n      );\n      await element(by.css('.m-add-tag .m-input-text')).sendKeys(tagName);\n      await element(by.css('.m-add-tag__button[aria-label=Add]')).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText('.s-tags-settings__tags .m-tag-input', tagName)\n          )\n        ),\n        5 * 1000\n      );\n      await element(by.css('.m-add-tag .m-input-text')).sendKeys(tagName);\n      await element(by.css('.m-add-tag__button[aria-label=Add]')).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.m-field-errors',\n              'Unable to create the tag. A tag with the same id may already exist.'\n            )\n          )\n        ),\n        5 * 1000\n      );\n      // ---\n      await element(\n        by.cssContainingText('.s-tags-settings__tags .m-tag-input', tagName)\n      )\n        .element(by.css('.m-tag-input__delete'))\n        .click();\n      await browser.wait(\n        EC.stalenessOf(\n          element(\n            by.cssContainingText('.s-tags-settings__tags .m-tag-input', tagName)\n          )\n        ),\n        5 * 1000\n      );\n    });\n\n    it('Rename a tag', async () => {\n      const tagName = ' Edited';\n      await showSettings('Tags');\n      await browser.wait(\n        EC.presenceOf(element(by.css('.m-add-tag .m-input-text'))),\n        5 * 1000\n      );\n      await element(\n        by.cssContainingText(\n          '.s-tags-settings__tags .m-tag-input .m-tag-input__button',\n          'Inbox'\n        )\n      ).click();\n      // key chord works on FF not chrome\n      // await element(by.css('.m-tag-input__input .m-input-text')).sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a'), tagName);\n      await element(by.css('.m-tag-input__input .m-input-text')).sendKeys(\n        tagName\n      );\n      await element(by.css('.m-tag-input .m-button[aria-label=Save]')).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.s-tags-settings__tags .m-tag-input .m-tag-input__button',\n              tagName\n            )\n          ),\n          5 * 1000\n        )\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/70-device-spec.js",
    "content": "const { signin, signout } = require('../utils/user-util');\nconst { showSettings } = require('../utils/user-util');\nconst {\n  clearKeypairInLocalStorage,\n  restoreKeypairInLocalStorage,\n} = require('../utils/window');\n\ndescribe('Device', () => {\n  const EC = protractor.ExpectedConditions;\n\n  it('Redirect to device management on a new device for an existing account', async () => {\n    await signin();\n    await expect(browser.getCurrentUrl()).not.toContain('/settings/new-device');\n    await clearKeypairInLocalStorage({ save: true });\n    await signin();\n    await expect(browser.getCurrentUrl()).toContain('/settings/new-device');\n    await restoreKeypairInLocalStorage();\n    await signin();\n    await expect(browser.getCurrentUrl()).not.toContain('/settings/new-device');\n    // delete the created device\n    await showSettings('Devices');\n    const deviceBlock = element(\n      by.cssContainingText('.s-devices-settings__device', 'desktop 2')\n    );\n    await deviceBlock\n      .element(by.cssContainingText('.m-button', 'Revoke this device'))\n      .click();\n    await browser.wait(\n      EC.presenceOf(\n        element(\n          by.cssContainingText(\n            '.l-notification-center',\n            'The device has been revoked'\n          )\n        )\n      ),\n      5 * 1000\n    );\n  });\n\n  describe('Revoke on a verified device', () => {\n    beforeEach(async () => {\n      await signin();\n    });\n\n    it('Revoke an other device', async () => {\n      await showSettings('Devices');\n      await expect(\n        element.all(by.css('.s-devices-settings__device')).count()\n      ).toEqual(2);\n      const deviceBlock = element(\n        by.cssContainingText('.s-devices-settings__device', 'device to revoke')\n      );\n      await deviceBlock\n        .element(by.cssContainingText('.m-button', 'Revoke this device'))\n        .click();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.l-notification-center',\n              'The device has been revoked'\n            )\n          )\n        ),\n        5 * 1000\n      );\n      expect(\n        element.all(by.css('.s-devices-settings__device')).count()\n      ).toEqual(1);\n    });\n  });\n\n  describe('Revoke on a unverified device', () => {\n    it('Hides the button except itself', async () => {\n      await signin(); // create default\n      await clearKeypairInLocalStorage({ save: true });\n      await signin();\n      await clearKeypairInLocalStorage();\n      await signin();\n      await browser.wait(\n        EC.presenceOf(element(by.css('.s-new-device-info')), 5 * 1000)\n      );\n      await element(by.cssContainingText('.m-link', 'I understand')).click();\n      await browser.wait(\n        EC.presenceOf(element(by.css('.s-devices-settings__device')), 5 * 1000)\n      );\n      const nbDevices = await element\n        .all(by.css('.s-devices-settings__device'))\n        .count();\n      const deviceBlock = element(\n        by.cssContainingText(\n          '.s-devices-settings__device',\n          `desktop ${nbDevices - 2}`\n        )\n      );\n      expect(\n        deviceBlock\n          .element(by.cssContainingText('.m-button', 'Revoke this device'))\n          .isPresent()\n      ).toEqual(false);\n      const deviceBlock2 = element(\n        by.cssContainingText(\n          '.s-devices-settings__device',\n          `desktop ${nbDevices - 1}`\n        )\n      );\n      expect(\n        deviceBlock2\n          .element(by.cssContainingText('.m-button', 'Revoke this device'))\n          .isPresent()\n      ).toEqual(true);\n      // clear created devices\n      await restoreKeypairInLocalStorage();\n      await signin();\n      await showSettings('Devices');\n      const deviceBlock3 = element(\n        by.cssContainingText(\n          '.s-devices-settings__device',\n          `desktop ${nbDevices - 2}`\n        )\n      );\n      await deviceBlock3\n        .element(by.cssContainingText('.m-button', 'Revoke this device'))\n        .click();\n      const deviceBlock4 = element(\n        by.cssContainingText(\n          '.s-devices-settings__device',\n          `desktop ${nbDevices - 1}`\n        )\n      );\n      await deviceBlock4\n        .element(by.cssContainingText('.m-button', 'Revoke this device'))\n        .click();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.l-notification-center',\n              'The device has been revoked'\n            )\n          )\n        ),\n        5 * 1000\n      );\n    });\n\n    it('Revoke the device then redirect to signout', async () => {\n      await signin();\n      await clearKeypairInLocalStorage({ save: true });\n      await signin();\n      await browser.wait(\n        EC.presenceOf(element(by.css('.s-new-device-info')), 5 * 1000)\n      );\n      await element(by.cssContainingText('.m-link', 'I understand')).click();\n      const nbDevices = await element\n        .all(by.css('.s-devices-settings__device'))\n        .count();\n      const deviceBlock = element(\n        by.cssContainingText(\n          '.s-devices-settings__device',\n          `desktop ${nbDevices - 1}`\n        )\n      );\n      await deviceBlock\n        .element(by.cssContainingText('.m-button', 'Revoke this device'))\n        .click();\n      expect(browser.getCurrentUrl()).toContain('signin');\n      await restoreKeypairInLocalStorage();\n    });\n  });\n\n  describe('Validate device', () => {\n    beforeAll(async () => {\n      await signin();\n    });\n\n    it('request validation', async () => {\n      await showSettings('Devices');\n      await element(\n        by.cssContainingText('.m-device-verify__button', 'Verify this device')\n      ).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.l-notification-center',\n              'An email has been sent to your backup email in order to verify the device.'\n            )\n          ),\n          5 * 1000\n        )\n      );\n    });\n\n    it('verify with valid token not authenticated', async () => {\n      await signout();\n      await browser.get('/validate-device/aaaa-bbbb');\n      await signin();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.s-validate-device',\n              'The device is now verified, you can continue to use Caliopen.'\n            )\n          ),\n          5 * 1000\n        )\n      );\n    });\n\n    it('verify with valid token', async () => {\n      await browser.get('/validate-device/aaaa-bbbb');\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.s-validate-device',\n              'The device is now verified, you can continue to use Caliopen.'\n            )\n          ),\n          5 * 1000\n        )\n      );\n    });\n\n    it('verify with an invalid token', async () => {\n      await browser.get('/validate-device/foobar');\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.s-validate-device',\n              'The device cannot be verified, the validation link might not be valid anymore or may be the device has been revoked. You can send the verification link from the device list'\n            )\n          ),\n          5 * 1000\n        )\n      );\n    });\n\n    it('verification crashed', async () => {\n      await browser.get('/validate-device/fail');\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.s-validate-device',\n              'The device cannot be verified, the validation link might not be valid anymore or may be the device has been revoked. You can send the verification link from the device list'\n            )\n          ),\n          5 * 1000\n        )\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/80-account-spec.js",
    "content": "const userUtil = require('../utils/user-util');\n\ndescribe('Account', () => {\n  const EC = protractor.ExpectedConditions;\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await userUtil.showAccount();\n  });\n\n  it('Change password', async () => {\n    await element(\n      by.cssContainingText('.l-user .m-nav-list a', 'Security')\n    ).click();\n    await browser.wait(\n      EC.presenceOf(element(by.css('.m-password-details'))),\n      5 * 1000\n    );\n\n    await element(\n      by.cssContainingText('.m-password-details__action .m-button', 'Change')\n    ).click();\n    await element(\n      by.cssContainingText('.m-text-field-group', 'Current password:')\n    )\n      .element(by.css('.m-input-text'))\n      .sendKeys('Foo');\n    await element(by.cssContainingText('.m-text-field-group', 'New password:'))\n      .element(by.css('.m-input-text'))\n      .sendKeys('Bar');\n    await element(\n      by.cssContainingText('.m-text-field-group', 'New password confirmation:')\n    )\n      .element(by.css('.m-input-text'))\n      .sendKeys('Bar');\n    await element(\n      by.cssContainingText('.m-button', 'Apply modifications')\n    ).click();\n    await browser.wait(\n      EC.presenceOf(\n        element(\n          by.cssContainingText('.m-password-details', 'Password strength:')\n        )\n      ),\n      5 * 1000\n    );\n  });\n\n  describe('Delete account', () => {\n    it('Fails because of invalid password', async () => {\n      await element(\n        by.cssContainingText('.m-button', 'Delete account')\n      ).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(by.cssContainingText('.m-modal__title', 'Delete account'))\n        ),\n        5 * 1000\n      );\n      await element(by.cssContainingText('.m-text-field-group', 'Password'))\n        .element(by.css('.m-input-text'))\n        .sendKeys('123');\n\n      await element(\n        by.cssContainingText('.m-button', 'Delete my Caliopen account')\n      ).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText(\n              '.m-field-group__errors',\n              'Unable to delete your account, the given password is incorrect.'\n            )\n          )\n        ),\n        5 * 1000\n      );\n      await element(by.cssContainingText('.m-button', 'Cancel')).click();\n    });\n\n    it('Succeed', async () => {\n      await element(\n        by.cssContainingText('.m-button', 'Delete account')\n      ).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(by.cssContainingText('.m-modal__title', 'Delete account'))\n        ),\n        5 * 1000\n      );\n      await element(by.cssContainingText('.m-text-field-group', 'Password'))\n        .element(by.css('.m-input-text'))\n        .sendKeys('123456');\n\n      await element(\n        by.cssContainingText('.m-button', 'Delete my Caliopen account')\n      ).click();\n      await browser.wait(EC.urlContains('/auth/signin'), 5 * 1000);\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/features/90_notification-spec.js",
    "content": "const userUtil = require('../utils/user-util');\nconst { home } = require('../utils/navigation');\n\n// make sure jasmin's defaultTimeoutInterval is under this value\nconst THROTTLE_DURATION = 50 * 1000; // cf. src/modules/notification/services/notification.worker\n\n// FIXME: Works fine on dev env, nothing appears on CI\n// disabled since it is a very long test\nxdescribe('Notification', () => {\n  const EC = protractor.ExpectedConditions;\n  const resetNotif = () =>\n    browser.executeScript(`\n    const req = new XMLHttpRequest();\n    req.open('GET', '/api/v2/notifications/reset-dev');\n    return req.send();\n  `);\n\n  beforeAll(async () => {\n    await userUtil.signin();\n  });\n\n  beforeEach(async () => {\n    await home();\n    await resetNotif();\n  });\n\n  describe('Timeline', () => {\n    it('shows N new messages and clear', async () => {\n      await browser.wait(\n        EC.presenceOf(\n          element(by.cssContainingText('.s-timeline__new-msg', 'new messages'))\n        ),\n        THROTTLE_DURATION\n      );\n      expect(\n        element(\n          by.cssContainingText(\n            '.s-timeline__new-msg',\n            'You have 4 new messages'\n          )\n        ).isPresent()\n      ).toEqual(true);\n      expect(\n        element(\n          by.css('.l-notification-center__notification-item-message')\n        ).isPresent()\n      ).toEqual(false);\n      await element(\n        by.cssContainingText('.s-timeline__new-msg', 'You have 4 new messages')\n      ).click();\n      await browser.wait(\n        EC.stalenessOf(element(by.css('.s-timeline__new-msg'))),\n        1000\n      );\n    });\n  });\n\n  // XXX: discussion is like everywhere in app cf. MessageNotifier::PATHS_TO_IGNORE\n  xdescribe('Discussion', () => {\n    it('shows N new messages and clear', async () => {\n      await browser.wait(\n        EC.presenceOf($('.s-timeline .s-discussion-item')),\n        5 * 1000\n      );\n      await element(\n        by.cssContainingText(\n          '.s-discussion-item .s-discussion-item__title .s-discussion-item__message_excerpt',\n          'msg with notifications'\n        )\n      ).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(by.cssContainingText('.m-message', 'new messages'))\n        ),\n        THROTTLE_DURATION\n      );\n      expect(\n        element(\n          by.cssContainingText('.m-message', 'new messages for the discussion')\n        ).isPresent()\n      ).toEqual(true);\n      expect(\n        element(\n          by.cssContainingText('.m-message', 'new other messages')\n        ).isPresent()\n      ).toEqual(false);\n      expect(element(by.css('.notification')).isPresent()).toEqual(false);\n      await element(\n        by.cssContainingText('.m-message', 'new messages for the discussion')\n      ).click();\n      expect(\n        element(by.cssContainingText('.m-message', 'new messages')).isPresent()\n      ).toEqual(false);\n    });\n\n    it('shows N new messages and a specific note for other discussion', async () => {\n      await browser.wait(\n        EC.presenceOf($('.s-timeline .s-discussion-item')),\n        5 * 1000\n      );\n      await element(\n        by.cssContainingText(\n          '.s-discussion-item .s-discussion-item__title .s-discussion-item__message_excerpt',\n          'msg with notifications'\n        )\n      ).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(by.cssContainingText('.m-message', 'new messages'))\n        ),\n        THROTTLE_DURATION\n      );\n      expect(\n        element(\n          by.cssContainingText('.m-message', 'new messages for the discussion')\n        ).isPresent()\n      ).toEqual(true);\n      expect(\n        element(\n          by.cssContainingText('.m-message', 'new other messages')\n        ).isPresent()\n      ).toEqual(true);\n      expect(element(by.css('.notification')).isPresent()).toEqual(false);\n    });\n\n    it('shows a notification', async () => {\n      await browser.wait(\n        EC.presenceOf($('.s-timeline .s-discussion-item')),\n        5 * 1000\n      );\n      await element(\n        by.cssContainingText(\n          '.s-discussion-item .s-discussion-item__title .s-discussion-item__message_excerpt',\n          'other msg'\n        )\n      ).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(\n            by.cssContainingText('.m-message', 'You received 4 new messages')\n          )\n        )\n      );\n      expect(\n        element(by.cssContainingText('.m-message', 'new messages')).isPresent()\n      ).toEqual(false);\n      expect(element(by.css('.notification')).isPresent()).toEqual(true);\n    });\n  });\n\n  describe('Everywhere', () => {\n    it('shows a notification', async () => {\n      await element(by.css('.m-navbar-item .m-link[title=\"Contacts\"]')).click();\n      await browser.wait(\n        EC.presenceOf(\n          element(by.css('.l-notification-center__notification-item-message'))\n        ),\n        THROTTLE_DURATION\n      );\n      expect(\n        element(\n          by.css('.l-notification-center__notification-item-message')\n        ).getText()\n      ).toContain('You received 4 new messages');\n    });\n  });\n});\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/protractor.conf.js",
    "content": "const { SpecReporter } = require('jasmine-spec-reporter');\n\nconst frontUrl = process.env.FRONTEND_ADDRESS || 'localhost';\n\nconst cfg = {\n  SELENIUM_PROMISE_MANAGER: true,\n  multiCapabilities: [\n    {\n      browserName: 'chrome',\n      maxInstances: 1,\n      chromeOptions: {\n        args: [\n          '--no-sandbox',\n          '--disable-dev-shm-usage',\n          '--disable-gpu',\n          '--disable-extensions',\n          'lang=en-US',\n        ],\n        prefs: {\n          intl: { accept_languages: 'en-US' },\n        },\n      },\n    },\n    // {\n    //   browserName: 'firefox',\n    //   maxInstances: 1,\n    //   \"moz:firefoxOptions\": {\n    //     args: ['-safe-mode', '-headless']\n    //   },\n    // },\n  ],\n  specs: ['./features/**/*-spec.js'],\n  jasmineNodeOpts: {\n    showColors: true,\n    // print: () => {},\n    defaultTimeoutInterval: 70 * 1000,\n  },\n  baseUrl: `http://${frontUrl}:4000/`,\n  onPrepare: () => {\n    browser.ignoreSynchronization = true;\n    browser.manage().window().setSize(1024, 768);\n\n    jasmine.getEnv().addReporter(\n      new SpecReporter({\n        suite: {\n          displayNumber: true,\n        },\n        spec: {\n          displayErrorMessages: true,\n          displayFailed: true,\n          displayStacktrace: true,\n        },\n        // summary: {\n        //   displayErrorMessages: true,\n        //   displayStacktrace: true,\n        //   displaySuccessful: true,\n        //   displayFailed: true,\n        // },\n      })\n    );\n  },\n  plugins: [\n    {\n      package: 'protractor-console-plugin',\n      failOnWarning: false,\n      logWarnings: false,\n      exclude: [/Warning:/],\n    },\n  ],\n  logLevel: 'INFO',\n  debug: false,\n};\n\nif (process.env.SAUCE_USERNAME && process.env.SAUCE_ACCESS_KEY) {\n  // platform config: https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/\n\n  cfg.sauceUser = process.env.SAUCE_USERNAME;\n  cfg.sauceKey = process.env.SAUCE_ACCESS_KEY;\n  cfg.sauceSeleniumAddress =\n    process.env.SAUCE_ADDRESS + ':' + process.env.SAUCE_PORT + '/wd/hub';\n  cfg.sauceSeleniumUseHttp = true;\n  const branch = process.env.DRONE_BRANCH;\n  const prNumber = process.env.DRONE_PULL_REQUEST;\n  const name = `CaliOpen e2e - ${prNumber ? `#${prNumber}` : branch}`;\n\n  cfg.multiCapabilities = [\n    // ****************************\n    // * since openpgp has been added to the bundle, it fails to run on old browsers\n    // {\n    //   browserName: 'chrome',\n    //   platform: 'Linux',\n    //   version: '48.0',\n    //   'tunnel-identifier': 'caliopen',\n    //   name,\n    // },\n    // {\n    //   browserName: 'firefox',\n    //   platform: 'Linux',\n    //   version: '45.0',\n    //   'tunnel-identifier': 'caliopen',\n    //   name,\n    // },\n    // ****************************\n    {\n      browserName: 'firefox',\n      platform: 'Windows 10',\n      version: '65.0',\n      'tunnel-identifier': 'caliopen',\n      name,\n      'moz:firefoxOptions': {\n        prefs: {\n          'security.insecure_password.ui.enabled': false,\n          'security.insecure_field_warning.contextual.enabled': false,\n        },\n      },\n    },\n    // {\n    //   browserName: 'chrome',\n    //   platform: 'Windows 10',\n    //   version: '72.0',\n    //   'tunnel-identifier': 'caliopen',\n    //   name,\n    // },\n    // {\n    //   browserName: 'MicrosoftEdge',\n    //   platform: 'Windows 10',\n    //   version: '16.16299',\n    //   'tunnel-identifier': 'caliopen',\n    //   name,\n    // },\n    // {\n    //   browserName: 'safari',\n    //   platform: 'macOS 10.13',\n    //   version: '11.1',\n    //   'tunnel-identifier': 'caliopen',\n    //   name,\n    // }\n  ];\n}\n\nexports.config = cfg;\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/utils/navigation.js",
    "content": "module.exports = {\n  switchApp: (application) =>\n    element(by.css('.m-application-switcher__toggler'))\n      .click()\n      .then(() =>\n        element(\n          by.cssContainingText(\n            '.m-application-switcher__dropdown .m-link',\n            application\n          )\n        ).click()\n      ),\n  home: async () => {\n    // force scroll top due to fixed navbar\n    await browser.executeScript('window.scrollTo(0,0);');\n\n    return element(by.css('.l-header a[href=\"/\"]')).click();\n  },\n};\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/utils/timeline.js",
    "content": "module.exports = {\n  filter: async (type) => {\n    await element(\n      by.css('#toggle-timeline-filter_navigation_dropdown')\n    ).click();\n\n    return element(by.cssContainingText('.m-button', type)).click();\n  },\n};\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/utils/user-util.js",
    "content": "const EC = protractor.ExpectedConditions;\n\nmodule.exports = {\n  signin: async (login, password) => {\n    const loginKeys = login || 'dev';\n    const passwordKeys = password || '123456';\n\n    const url = await browser.getCurrentUrl();\n\n    if (!url.includes('/auth/signin')) {\n      await browser.get('/auth/signin');\n    }\n    await element(by.css('input[name=username]')).sendKeys(loginKeys);\n    await element(by.css('input[name=password]')).sendKeys(passwordKeys);\n    await element(by.cssContainingText('button[type=submit]', 'Login')).click();\n\n    return browser.wait(\n      EC.presenceOf(element(by.css('.l-header__brand'))),\n      5 * 1000\n    );\n  },\n  signout: () => {\n    browser.get('/auth/signout');\n  },\n  showAccount: async (pageName) => {\n    const userMenu = element(by.css('.l-header__user-menu'));\n    userMenu.element(by.css('.m-button')).click();\n    userMenu.element(by.cssContainingText('.m-link', 'Account')).click();\n    userMenu.element(by.css('.m-button')).click();\n\n    const result = await browser.wait(EC.urlContains('user/profile'), 5 * 1000);\n\n    if (pageName) {\n      return element(\n        by.cssContainingText('.l-user .m-nav-list .m-link', pageName)\n      ).click();\n    }\n\n    return result;\n  },\n  showSettings: (pageName = null) =>\n    element(by.css('.m-user-menu .m-button'))\n      .click()\n      .then(() =>\n        element(\n          by.cssContainingText('.m-user-menu .m-link', 'Settings')\n        ).click()\n      )\n      .then(() => {\n        if (!pageName) {\n          return undefined;\n        }\n\n        return element(\n          by.cssContainingText('.l-settings .m-nav-list .m-link', pageName)\n        ).click();\n      }),\n};\n"
  },
  {
    "path": "src/frontend/web_application/test/functional/utils/window.js",
    "content": "let keypair;\n\nconst getBrowserKeypair = () =>\n  browser.executeScript(`\n    return {\n      curve: window.localStorage.getItem('device.curve'),\n      hash: window.localStorage.getItem('device.hash'),\n      id: window.localStorage.getItem('device.id'),\n      priv: window.localStorage.getItem('device.priv'),\n    };\n  `);\n\nmodule.exports = {\n  clearKeypairInLocalStorage: ({ save = false } = {}) =>\n    getBrowserKeypair()\n      .then((result) => {\n        if (save) {\n          keypair = result;\n        }\n      })\n      .then(() =>\n        browser.executeScript(`\n      window.localStorage.removeItem('device.curve');\n      window.localStorage.removeItem('device.hash');\n      window.localStorage.removeItem('device.id');\n      window.localStorage.removeItem('device.priv');\n    `)\n      ),\n  restoreKeypairInLocalStorage: () => {\n    const { curve, hash, id, priv } = keypair;\n\n    return browser.executeScript(`\n      window.localStorage.setItem('device.curve', '${curve}');\n      window.localStorage.setItem('device.hash', '${hash}');\n      window.localStorage.setItem('device.id', '${id}');\n      window.localStorage.setItem('device.priv', '${priv}');\n    `);\n  },\n};\n"
  },
  {
    "path": "src/frontend/web_application/test/msw-handlers/contacts.ts",
    "content": "import { rest } from 'msw';\nimport contacts from '../fixtures/contacts/data.json';\n\nexport const contactsHandlers = [\n  rest.get('/api/v2/contacts', (req, res, ctx) =>\n    res(ctx.json({ contacts, total: contacts.length }), ctx.status(200))\n  ),\n  rest.get('/api/v2/contacts/:id', (req, res, ctx) => {\n    const found = contacts.find(\n      (contact) => contact.contact_id === req.params.id\n    );\n\n    if (found) {\n      return res(ctx.json(contacts[0]), ctx.status(200));\n    }\n\n    return res(ctx.text('not found'), ctx.status(404));\n  }),\n];\n"
  },
  {
    "path": "src/frontend/web_application/test/msw-handlers/settings.ts",
    "content": "import { rest } from 'msw';\n\nexport const settings = {\n  default_locale: 'en-US',\n  message_display_format: 'rich_text',\n  contact_display_format: 'given_name, family_name',\n  contact_display_order: 'given_name',\n  notification_enabled: true,\n  notification_message_preview: 'always',\n  notification_sound_enabled: true,\n  notification_delay_disappear: 10,\n};\n\nexport const settingsHandlers = [\n  rest.get('/api/v1/settings', (req, res, ctx) => {\n    return res(ctx.json(settings), ctx.status(200));\n  }),\n];\n"
  },
  {
    "path": "src/frontend/web_application/test/msw-handlers/tags.ts",
    "content": "// eslint-disable-next-line import/no-extraneous-dependencies\nimport { rest } from 'msw';\n\nimport tags from '../fixtures/tags/data.json';\n\nexport const tagsHandlers = [\n  rest.get('/api/v2/tags', (req, res, ctx) => {\n    return res(ctx.json({ tags, total: tags.length }), ctx.status(200));\n  }),\n];\n"
  },
  {
    "path": "src/frontend/web_application/test/msw-handlers/user.ts",
    "content": "import { rest } from 'msw';\nimport user from '../fixtures/user/data.json';\n\nexport const userHandlers = [\n  rest.get('/api/v1/me', (req, res, ctx) => {\n    return res(ctx.json(user), ctx.status(200));\n  }),\n];\n"
  },
  {
    "path": "src/frontend/web_application/test/providers.tsx",
    "content": "import * as React from 'react';\nimport { Provider } from 'react-redux';\nimport { StaticRouter } from 'react-router-dom';\nimport { I18nProvider } from '@lingui/react';\nimport { i18n } from '@lingui/core';\nimport { QueryClient, QueryClientProvider } from 'react-query';\n\nimport configureAppStore from 'src/store/configure-store';\nimport { initialState as initialStateSettings } from 'src/store/modules/settings';\nimport { getUserLocales } from 'src/modules/i18n';\nimport { getDefaultSettings } from 'src/modules/settings';\n// for some reason, jest won't import correctly `message.ts`\nimport messages from 'locale/en/messages.json';\n\nconst locales = getUserLocales();\nconst settings = getDefaultSettings(locales);\nconst initialState = {\n  settings: {\n    ...initialStateSettings,\n    settings,\n  },\n};\n\ni18n.load('en', messages);\ni18n.activate('en');\n\nconst queryClient = new QueryClient();\n\nexport function AllProviders({\n  children,\n}: {\n  children: React.ReactNode;\n}): React.ReactElement<React.HTMLAttributes<HTMLDivElement>> {\n  const store = configureAppStore(initialState);\n\n  return (\n    // id=root required for modals\n    <div id=\"root\">\n      <QueryClientProvider client={queryClient}>\n        <StaticRouter>\n          <I18nProvider i18n={i18n}>\n            <Provider store={store}>{children}</Provider>\n          </I18nProvider>\n        </StaticRouter>\n      </QueryClientProvider>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/frontend/web_application/test/server.ts",
    "content": "import { setupServer } from 'msw/node';\nimport { settingsHandlers } from './msw-handlers/settings';\nimport { contactsHandlers } from './msw-handlers/contacts';\nimport { tagsHandlers } from './msw-handlers/tags';\nimport { userHandlers } from './msw-handlers/user';\n\nexport const server = setupServer(\n  ...contactsHandlers,\n  ...settingsHandlers,\n  ...tagsHandlers,\n  ...userHandlers\n);\n"
  },
  {
    "path": "src/frontend/web_application/test/unit/lingui-react.jsx",
    "content": "import * as React from 'react';\n\njest.mock('@lingui/react', () => ({\n  withI18n: () => (WrappedComponent) => (props) =>\n    (\n      <WrappedComponent\n        i18n={{ _: (id, values, { defaults }) => defaults || id }}\n        {...props}\n      />\n    ),\n  i18nMark: (str) => str,\n  Trans: ({ id, children }) => {\n    if (!children) {\n      return id;\n    }\n\n    return children;\n  },\n  NumberFormat: ({ value }) => value,\n}));\n"
  },
  {
    "path": "src/frontend/web_application/test/unit/setup.ts",
    "content": "import 'raf/polyfill';\nimport { configure } from 'enzyme';\nimport Adapter from 'enzyme-adapter-react-16';\nimport { saveConfig } from 'src/modules/device/services/storage';\nimport { cleanup } from '@testing-library/react';\nimport '@testing-library/jest-dom';\nimport { server } from '../server';\n\nbeforeAll(() => {\n  // Save mandatory device for signed request\n  saveConfig({\n    id: 'test',\n    priv: 'private',\n    hash: 'a-hash',\n    curve: 'whatever',\n  });\n  server.listen({\n    onUnhandledRequest: 'warn',\n  });\n});\nafterEach(() => {\n  server.resetHandlers();\n  cleanup();\n});\nafterAll(() => server.close());\n\n// TODO: Clean enzyme\nconfigure({ adapter: new Adapter() });\n"
  },
  {
    "path": "src/frontend/web_application/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist/\", // path to output directory\n    \"sourceMap\": true, // allow sourcemap support\n    \"strictNullChecks\": true, // enable strict null checks as a best practice\n    \"module\": \"es2020\", // specify module code generation\n    \"moduleResolution\": \"Node\",\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"src\": [\"src\"],\n      \"locale\": [\"locale\"],\n      \"test\": [\"test\"],\n    },\n    \"jsx\": \"react\", // use typescript to transpile jsx to js\n    \"target\": \"es5\", // specify ECMAScript target version\n    \"allowJs\": true, // allow a partial TypeScript and JavaScript codebase\n    \"experimentalDecorators\": true,\n    // config to import fixtures in tests\n    \"resolveJsonModule\": true,\n    \"allowSyntheticDefaultImports\": true\n    // ---\n  },\n  \"include\": [\"./server\", \"./src/\", \"./test/unit/setup.ts\"]\n}\n"
  },
  {
    "path": "src/frontend/web_application/webpack/config.js",
    "content": "const path = require('path');\nconst webpack = require('webpack');\nconst OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin');\nconst clientOptions = require('../config/client.default.js');\n\nconst isDev = process.env.NODE_ENV === 'development';\n\nconst configureSrcTsLoader = ({ isNode } = { isNode: false }) => {\n  return {\n    module: {\n      rules: [\n        {\n          test: /(?<!\\.worker)\\.(j|t)sx?$/,\n          exclude: /node_modules/,\n          include: path.join(__dirname, '../src/'),\n          loader: 'ts-loader',\n        },\n        ...(isNode\n          ? [\n              {\n                test: /\\.worker\\.(t|j)s$/,\n                use: [{ loader: 'null-loader' }],\n              },\n            ]\n          : [\n              {\n                test: /\\.worker\\.(t|j)s$/,\n                exclude: /node_modules/,\n                use: [\n                  { loader: 'worker-loader' },\n                  {\n                    loader: 'ts-loader',\n                  },\n                ],\n              },\n            ]),\n      ],\n    },\n  };\n};\n\nconst configureStylesheet = () => {\n  return {\n    plugins: [\n      new OptimizeCssAssetsPlugin({ canPrint: false }),\n      new MiniCssExtractPlugin({\n        filename: isDev ? 'client.css' : 'client.[hash].css',\n      }),\n    ],\n    module: {\n      rules: [\n        {\n          test: /\\.css$/,\n          use: [\n            MiniCssExtractPlugin.loader,\n            { loader: 'css-loader', options: { sourceMap: true } },\n          ],\n        },\n        {\n          test: /\\.scss$/,\n          use: [\n            MiniCssExtractPlugin.loader,\n            { loader: 'css-loader', options: { sourceMap: true } },\n            { loader: 'postcss-loader', options: { sourceMap: true } },\n            {\n              loader: 'sass-loader',\n              options: {\n                sourceMap: false,\n                sassOptions: {\n                  includePaths: [\n                    path.resolve(__dirname, '../src'),\n                    // XXX: use tilde ~ instead of those includes https://webpack.js.org/loaders/sass-loader/#resolving-import-at-rules\n                    path.resolve(\n                      __dirname,\n                      '../node_modules/foundation-sites/scss'\n                    ),\n                    path.resolve(\n                      __dirname,\n                      '../node_modules/react-redux-notify/src'\n                    ),\n                  ],\n                },\n              },\n            },\n          ],\n        },\n      ],\n    },\n  };\n};\n\nconst configureAssets = (outputPath = 'assets/') => ({\n  module: {\n    rules: [\n      {\n        test: /\\.(jpe?g|png|gif)$/i,\n        use: [\n          {\n            loader: 'file-loader',\n            options: {\n              hash: 'sha512',\n              digest: 'hex',\n              name: '[name].[ext]',\n              outputPath,\n            },\n          },\n          // XXX: disabled for now as it always output webp, which is not diplayed\n          // by many browsers.\n          // image-webpack-loader does'nt work well on debian 9 and travis for now\n          // https://github.com/tcoopman/image-webpack-loader/issues/142#issuecomment-380751197\n          // {\n          //   loader: 'image-webpack-loader',\n          //   options: {\n          //     bypassOnDebug: true,\n          //     // XXX: dosen't work on travis\n          //     mozjpeg: {\n          //       enabled: false,\n          //     },\n          //     // XXX: doesn't work on debian 9\n          //     pngquant: {\n          //       enabled: false,\n          //     },\n          //     webp: {\n          //       quality: 75,\n          //     },\n          //   },\n          // },\n        ],\n      },\n      {\n        test: /\\.svg(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        loader: 'file-loader',\n        options: {\n          mimetype: 'image/svg+xml',\n          name: '[name].[ext]',\n          outputPath,\n        },\n      },\n      {\n        test: /\\.woff(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        loader: 'file-loader',\n        options: {\n          mimetype: 'application/font-woff',\n          name: '[name].[ext]',\n          outputPath,\n        },\n      },\n      {\n        test: /\\.woff2(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        loader: 'file-loader',\n        options: {\n          mimetype: 'application/font-woff',\n          name: '[name].[ext]',\n          outputPath,\n        },\n      },\n      {\n        test: /\\.ttf(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        loader: 'file-loader',\n        options: {\n          mimetype: 'application/octet-stream',\n          name: '[name].[ext]',\n          outputPath,\n        },\n      },\n      {\n        test: /\\.eot(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        loader: 'file-loader',\n        options: { name: '[name].[ext]', outputPath },\n      },\n    ],\n  },\n});\n\nconst configureNoAssets = () => ({\n  module: {\n    rules: [\n      {\n        test: /\\.(jpe?g|png|gif|svg)$/,\n        loader: 'null-loader',\n      },\n    ],\n  },\n});\n\nconst configureVendorSplit = () => ({\n  entry: {\n    vendor: [\n      'async-validator',\n      'axios',\n      'bn.js',\n      'classnames',\n      'elliptic',\n      'history',\n      'lodash',\n      'moment-timezone',\n      'openpgp',\n      'prop-types',\n      'react',\n      'react-country-region-selector',\n      'react-datepicker',\n      'react-dom',\n      'react-modal',\n      'react-moment',\n      'react-redux',\n      'react-redux-notify',\n      'react-router-dom',\n      'redux',\n      'redux-axios-middleware',\n      'redux-form',\n      'reselect',\n      'uuid',\n      'xregexp',\n    ],\n  },\n  optimization: {\n    splitChunks: {\n      cacheGroups: {\n        vendor: {\n          chunks: 'initial',\n          name: 'vendor',\n          test: 'vendor',\n          enforce: true,\n        },\n      },\n    },\n    runtimeChunk: {\n      name: 'manifest',\n    },\n  },\n});\n\nconst configureHTMLTemplate = () => ({\n  plugins: [\n    new HtmlWebpackPlugin({\n      filename: path.resolve(__dirname, '../dist/server/template.html'),\n      template: path.resolve(__dirname, '../template/index.ejs'),\n      alwaysWriteToDisk: true,\n    }),\n    new HtmlWebpackHarddiskPlugin(),\n  ],\n});\n\nconst configureEnv = (buildTarget) => {\n  const defined = Object.assign({\n    BUILD_TARGET: JSON.stringify(buildTarget),\n    HAS_SSR: process.env.HAS_SSR || true,\n    CALIOPEN_ENV: JSON.stringify(process.env.NODE_ENV),\n    CALIOPEN_OPTIONS: JSON.stringify(clientOptions),\n  });\n\n  return {\n    plugins: [new webpack.DefinePlugin(defined)],\n  };\n};\n\nmodule.exports = {\n  configureSrcTsLoader,\n  configureStylesheet,\n  configureAssets,\n  configureNoAssets,\n  configureVendorSplit,\n  configureHTMLTemplate,\n  configureEnv,\n};\n"
  },
  {
    "path": "src/frontend/web_application/webpack/webpack.common.js",
    "content": "const path = require('path');\nconst isDev = process.env.NODE_ENV === 'development';\nconst mode = isDev ? 'development' : 'production';\n\nmodule.exports = {\n  mode,\n  devtool: isDev ? 'eval-source-map' : 'source-map',\n  entry: {\n    app: [],\n    vendor: [],\n  },\n  output: {},\n  plugins: [],\n  module: {\n    rules: [],\n  },\n  resolve: {\n    extensions: ['.js', '.jsx', '.ts', '.tsx'],\n    // to match paths in ts config\n    alias: {\n      src: path.resolve(__dirname, '../src'),\n      locale: path.resolve(__dirname, '../locale'),\n    },\n  },\n};\n"
  },
  {
    "path": "src/frontend/web_application/webpack/webpack.config.browser.js",
    "content": "const path = require('path');\nconst { merge } = require('webpack-merge');\nconst OfflinePlugin = require('offline-plugin');\nconst WebpackPwaManifest = require('webpack-pwa-manifest');\nconst configs = require('./config.js');\nconst common = require('./webpack.common.js');\n\nconst PUBLIC_PATH = '/';\nconst isDev = process.env.NODE_ENV === 'development';\n\nconst base = {\n  target: 'web',\n  entry: {\n    app: [\n      '@babel/polyfill',\n      'script-loader!jquery',\n      'script-loader!foundation-sites',\n      path.join(__dirname, '../src/index.jsx'),\n    ],\n    vendor: [\n      '@babel/polyfill',\n      'script-loader!jquery',\n      'script-loader!foundation-sites',\n    ],\n  },\n  output: {\n    path: path.join(__dirname, '..', 'dist/server/public'),\n    filename: isDev ? '[name].js' : '[name].[chunkhash].js',\n    chunkFilename: isDev ? '[name].js' : '[name].[chunkhash].js',\n    publicPath: PUBLIC_PATH,\n  },\n};\n\nconst configurePWA = () => ({\n  plugins: [\n    new WebpackPwaManifest({\n      name: 'Caliopen',\n      short_name: 'Caliopen',\n      description: 'Private messages aggregator with privacy',\n      background_color: '#2196f3',\n      theme_color: '#ffffff',\n      icons: [\n        {\n          src: path.join(__dirname, '..', 'assets/caliopen-icon.png'),\n          sizes: [96, 128, 192, 256, 384, 512],\n        },\n        {\n          src: path.join(__dirname, '..', 'assets/caliopen-logo.png'),\n          size: '1022x265',\n        },\n      ],\n      ios: true,\n    }),\n    // FIXME: disabled, webpak5 no supported: https://github.com/NekR/offline-plugin/issues/491\n    // new OfflinePlugin({\n    //   appShell: '/about',\n    //   responseStrategy: 'network-first',\n    //   externals: [\n    //     '/',\n    //     // disable caching of authenticated routes since the plugin tries to preload its\n    //     // '/api/v1/me',\n    //     // '/api/v1/settings',\n    //   ],\n    //   excludes: ['**/*.worker.js'],\n    //   relativePaths: false,\n    //   ServiceWorker: {\n    //     output: 'offline.worker.js',\n    //     navigationPreload: false,\n    //     events: true,\n    //   },\n    // }),\n  ],\n});\n\nconst configureDevServer = () => {\n  if (!isDev) {\n    return {};\n  }\n\n  return {\n    devServer: {\n      // using a hostname like caliopen.local, it requires this:\n      // https://github.com/webpack/webpack-dev-server/issues/1604\n      disableHostCheck: true,\n      contentBase: false,\n      hot: false,\n      inline: true,\n      liveReload: false,\n      port: '8080',\n      proxy: {\n        '/': {\n          target: 'http://localhost:4001',\n        },\n      },\n    },\n    watchOptions: {\n      aggregateTimeout: 300,\n      poll: 1000,\n    },\n  };\n};\n\nconst config = merge(\n  common,\n  configs.configureSrcTsLoader(),\n  configs.configureStylesheet(),\n  configs.configureAssets(),\n  configs.configureVendorSplit(),\n  configs.configureHTMLTemplate(),\n  configs.configureEnv('browser'),\n  configurePWA(),\n  configureDevServer(),\n  base\n);\n\nmodule.exports = config;\n"
  },
  {
    "path": "src/frontend/web_application/webpack/webpack.config.server.js",
    "content": "const path = require('path');\nconst { merge } = require('webpack-merge');\nconst { WatchIgnorePlugin } = require('webpack');\nconst HardSourceWebpackPlugin = require('hard-source-webpack-plugin');\nconst configs = require('./config.js');\nconst common = require('./webpack.common.js');\n\nconst isDev = process.env.NODE_ENV === 'development';\n\nconst base = {\n  target: 'node',\n  node: {\n    __dirname: true,\n    __filename: true,\n  },\n  entry: ['@babel/polyfill', path.join(__dirname, '../server/index.js')],\n  output: {\n    path: path.join(__dirname, '../dist/server/'),\n    filename: 'index.js',\n    chunkFilename: '[name].[chunkhash].js',\n    publicPath: '/',\n  },\n  externals: [\n    (context, request, callback) => {\n      if (\n        [\n          'body-parser',\n          'cookie-parser',\n          'debug',\n          'express',\n          'express-http-proxy',\n          'iron',\n          'locale',\n          'serve-favicon',\n          'config/server.defaults.js',\n          'argv',\n          'winston',\n          'winston-syslog',\n          'lingui-react',\n          'lingui-i18n',\n        ].some((module) => module === request)\n      ) {\n        return callback(null, `commonjs ${request}`);\n      }\n\n      if (['locale/.*'].some((module) => new RegExp(module).test(request))) {\n        return callback(null, `commonjs ${request}`);\n      }\n\n      return callback();\n    },\n  ],\n  module: {\n    rules: [\n      {\n        test: /\\.(s?css)$/,\n        // test: /\\.(s?css|jpe?g|png|gif|svg)$/,\n        loader: 'null-loader',\n      },\n      {\n        test: /\\.(j|t)sx?$/,\n        exclude: /node_modules/,\n        include: path.join(__dirname, '../server/'),\n        loader: 'ts-loader',\n      },\n      { test: /\\.html$/, loader: 'raw-loader' },\n    ],\n  },\n  optimization: {\n    minimize: false,\n  },\n  plugins: [\n    // new WatchIgnorePlugin([\n    //   path.join(__dirname, '../src/'),\n    //   path.join(__dirname, '../locale/'),\n    // ]),\n    ...(isDev ? [new HardSourceWebpackPlugin()] : []),\n  ],\n};\n\nconst config = merge(\n  common,\n  configs.configureEnv('server'),\n  configs.configureSrcTsLoader({ isNode: true }),\n  configs.configureAssets(),\n  base\n);\n\nmodule.exports = config;\n"
  }
]